commit f5e5f56c84595929e1a6b6ed43449cfbbb558419 Author: Scripty Date: Tue Mar 31 10:27:04 2026 +0300 add sborka diff --git a/garrysmod/addons/Binder/lua/autorun/binder_init.lua b/garrysmod/addons/Binder/lua/autorun/binder_init.lua new file mode 100644 index 0000000..f3d9282 --- /dev/null +++ b/garrysmod/addons/Binder/lua/autorun/binder_init.lua @@ -0,0 +1,18 @@ +-- Garry's Mod Binder - Entry Point +AddCSLuaFile("binder/sh_lang.lua") +AddCSLuaFile("binder/core/sh_config.lua") +AddCSLuaFile("binder/cl_init.lua") +AddCSLuaFile("binder/vgui/cl_frame.lua") +AddCSLuaFile("binder/vgui/cl_profiles_tab.lua") +AddCSLuaFile("binder/vgui/cl_keybind_tab.lua") +AddCSLuaFile("binder/vgui/cl_radial_tab.lua") +AddCSLuaFile("binder/vgui/cl_radial_hud.lua") +AddCSLuaFile("binder/vgui/cl_settings_tab.lua") + +if SERVER then + include("binder/sv_init.lua") + print("[Binder] Server loaded") +else + include("binder/cl_init.lua") + print("[Binder] Client loaded") +end diff --git a/garrysmod/addons/Binder/lua/binder/cl_init.lua b/garrysmod/addons/Binder/lua/binder/cl_init.lua new file mode 100644 index 0000000..eded07e --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/cl_init.lua @@ -0,0 +1,96 @@ +-- Garry's Mod Binder - Main Client Logic +include("binder/sh_lang.lua") +include("binder/core/sh_config.lua") +include("binder/vgui/cl_frame.lua") +include("binder/vgui/cl_profiles_tab.lua") +include("binder/vgui/cl_keybind_tab.lua") +include("binder/vgui/cl_radial_tab.lua") +include("binder/vgui/cl_radial_hud.lua") +include("binder/vgui/cl_settings_tab.lua") + +Binder = Binder or {} +Binder.Profiles = Binder.Profiles or {} + +surface.CreateFont("Binder_Key", { + font = "Roboto", + size = 12, + weight = 700, + extended = true, +}) + +CreateClientConVar("binder_show_feedback", "1", true, false) +CreateClientConVar("binder_dark_theme", "1", true, false) +CreateClientConVar("binder_radial_key", tostring(Binder.Config.DefaultRadialKey), true, false) + +-- Networking +net.Receive("Binder_SyncProfiles", function() + local length = net.ReadUInt(32) + local compressed = net.ReadData(length) + local data = util.Decompress(compressed) + + if data then + Binder.Profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {}) + if IsValid(Binder.Frame) then + Binder.Frame:RefreshActiveTab() + end + end +end) + +function Binder.SaveToServer() + local data = util.TableToJSON(Binder.Profiles) + local compressed = util.Compress(data) + + net.Start("Binder_UpdateProfile") + net.WriteUInt(#compressed, 32) + net.WriteData(compressed, #compressed) + net.SendToServer() +end + +-- Input Hook for Binds (Client-side Think for Singleplayer support) +local PrevBindState = {} + +hook.Add("Think", "Binder_CheckBindsThink", function() + local activeProfile + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then activeProfile = p break end + end + + if not activeProfile or not activeProfile.Binds then return end + + -- Don't trigger new presses if in console, ESC menu, typing in chat, or focused on a VGUI text entry + local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or LocalPlayer():IsTyping() + + for key, commandStr in pairs(activeProfile.Binds) do + local isDown = input.IsButtonDown(key) + local wasDown = PrevBindState[key] or false + + if isDown and not wasDown then + if not blockNewInput then + PrevBindState[key] = true + if string.sub(commandStr, 1, 1) == "/" then + LocalPlayer():ConCommand("say " .. commandStr) + else + LocalPlayer():ConCommand(commandStr) + end + end + elseif not isDown and wasDown then + PrevBindState[key] = false + + -- Automatically release + commands + local cmd = string.Explode(" ", commandStr)[1] + if cmd and string.sub(cmd, 1, 1) == "+" then + local minusCmd = "-" .. string.sub(cmd, 2) + LocalPlayer():ConCommand(minusCmd) + end + end + end +end) + + +concommand.Add("binder_menu", function() + if Binder.Frame and IsValid(Binder.Frame) then + Binder.Frame:Remove() + end + + Binder.Frame = vgui.Create("Binder_Frame") +end) diff --git a/garrysmod/addons/Binder/lua/binder/core/sh_config.lua b/garrysmod/addons/Binder/lua/binder/core/sh_config.lua new file mode 100644 index 0000000..de66708 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/core/sh_config.lua @@ -0,0 +1,65 @@ +-- Garry's Mod Binder - Shared Configuration +Binder = Binder or {} +Binder.Config = { + AccentColor = Color(0, 67, 28), -- Dark green (#00431c) + BgColor = Color(30, 30, 30, 240), + HeaderColor = Color(0, 67, 28), + TabActiveColor = Color(255, 255, 255, 20), + TabHoverColor = Color(255, 255, 255, 10), + Font = "Inter", -- Assuming Inter is available or we'll define it + DefaultRadialKey = KEY_N, +} + +function Binder.SanitizeProfiles(profiles) + for _, p in ipairs(profiles or {}) do + if p.Binds then + local newBinds = {} + for k, v in pairs(p.Binds) do + newBinds[tonumber(k) or k] = v + end + p.Binds = newBinds + end + if p.Radial then + local newRadial = {} + for k, v in pairs(p.Radial) do + newRadial[tonumber(k) or k] = v + end + p.Radial = newRadial + end + end + return profiles +end + +if CLIENT then + surface.CreateFont("Binder_Title", { + font = "Roboto", + size = 24, + weight = 800, + extended = true, + antialias = true, + }) + + surface.CreateFont("Binder_Tab", { + font = "Roboto", + size = 18, + weight = 600, + extended = true, + antialias = true, + }) + + surface.CreateFont("Binder_Main", { + font = "Roboto", + size = 18, + weight = 500, + extended = true, + antialias = true, + }) + + surface.CreateFont("Binder_Small", { + font = "Roboto", + size = 14, + weight = 500, + extended = true, + antialias = true, + }) +end diff --git a/garrysmod/addons/Binder/lua/binder/sh_lang.lua b/garrysmod/addons/Binder/lua/binder/sh_lang.lua new file mode 100644 index 0000000..19f380b --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/sh_lang.lua @@ -0,0 +1,37 @@ +-- Garry's Mod Binder - Shared Localization (Russian) +Binder = Binder or {} +Binder.Lang = { + title = "[BindMenu]", + profiles = "Профили", + keybinds = "Клавиши", + radial = "Радиальное меню", + settings = "Настройки", + profile_management = "Управление профилями", + profile_desc = "Выберите профиль для редактирования или создайте новый. Ранги Premium и Loyalty открывают дополнительные слоты.", + create_profile = "Создать профиль", + raiding_profile = "Рейд Профиль", + banker_profile = "Банкир Профиль", + binds_count = "%s биндов", + general_settings = "Общие настройки", + show_feedback = "Показывать сообщения обратной связи", + auto_scale = "Автоматическое масштабирование", + dark_theme = "Темная тема", + import_defaults = "Импорт стандартных биндов", + import_btn = "Импортировать бинды", + appearance_settings = "Настройки внешнего вида", + accent_color = "Акцентный цвет:", + change_color = "Изменить цвет", + preset_colors = "Предустановленные цвета:", + radial_config = "Настройка радиального меню", + radial_desc = "Нажмите на сегмент ниже для настройки бинда", + radial_center = "Радиальное", + slot = "Слот %s", + save = "Сохранить", + delete = "Удалить", + cancel = "Отмена", +} + +function Binder.GetPhrase(key, ...) + local phrase = Binder.Lang[key] or key + return string.format(phrase, ...) +end diff --git a/garrysmod/addons/Binder/lua/binder/sv_init.lua b/garrysmod/addons/Binder/lua/binder/sv_init.lua new file mode 100644 index 0000000..10bc2f1 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/sv_init.lua @@ -0,0 +1,72 @@ +-- Garry's Mod Binder - Main Server Logic +include("binder/sh_lang.lua") +include("binder/core/sh_config.lua") + +util.AddNetworkString("Binder_SyncProfiles") +util.AddNetworkString("Binder_UpdateProfile") +util.AddNetworkString("Binder_DeleteProfile") +util.AddNetworkString("Binder_SetActiveProfile") + +Binder = Binder or {} +Binder.Profiles = Binder.Profiles or {} + +-- Default structure +-- Profile = { Name = "Profile1", Binds = { [KEY_X] = "cmd" }, Radial = { [1] = { Label = "L", Cmd = "cmd" } }, Active = true } + +function Binder.SaveProfiles(ply) + local data = util.TableToJSON(Binder.Profiles[ply:SteamID64()] or {}) + ply:SetPData("Binder_Profiles", data) +end + +function Binder.LoadProfiles(ply) + local data = ply:GetPData("Binder_Profiles", "[]") + local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {}) + + -- Ensure at least one default profile if empty + if table.Count(profiles) == 0 then + table.insert(profiles, { + Name = Binder.GetPhrase("raiding_profile"), + Binds = {}, + Radial = {}, + Active = true + }) + end + + Binder.Profiles[ply:SteamID64()] = profiles + + -- Sync to client + net.Start("Binder_SyncProfiles") + local compressed = util.Compress(util.TableToJSON(profiles)) + net.WriteUInt(#compressed, 32) + net.WriteData(compressed, #compressed) + net.Send(ply) +end + +hook.Add("PlayerInitialSpawn", "Binder_LoadOnSpawn", function(ply) + Binder.LoadProfiles(ply) +end) + +-- Receive entire profile updates from client (e.g., binds changed) +net.Receive("Binder_UpdateProfile", function(len, ply) + local length = net.ReadUInt(32) + local compressed = net.ReadData(length) + local data = util.Decompress(compressed) + + if data then + local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {}) + if profiles then + Binder.Profiles[ply:SteamID64()] = profiles + Binder.SaveProfiles(ply) + end + end +end) + +-- Chat Command to open the Binder +hook.Add("PlayerSay", "Binder_ChatCommand", function(ply, text, teamTalk) + local lowerText = string.lower(text) + if string.sub(lowerText, 1, 5) == "/bind" or string.sub(lowerText, 1, 5) == "!bind" then + ply:ConCommand("binder_menu") + return "" -- Hide the command from chat + end +end) + diff --git a/garrysmod/addons/Binder/lua/binder/vgui/cl_frame.lua b/garrysmod/addons/Binder/lua/binder/vgui/cl_frame.lua new file mode 100644 index 0000000..178e595 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/vgui/cl_frame.lua @@ -0,0 +1,138 @@ +-- Garry's Mod Binder - Custom Frame +local PANEL = {} + +function PANEL:Init() + self:SetSize(ScrW() * 0.8, ScrH() * 0.8) + self:Center() + self:MakePopup() + + self.AccentColor = Binder.Config.AccentColor + + self.CurrentTab = "Profiles" + + self.HeaderHeight = 60 + self.TabHeight = 40 + + -- Tabs definitions + self.Tabs = { + {id = "Profiles", name = Binder.GetPhrase("profiles")}, + {id = "Keybinds", name = Binder.GetPhrase("keybinds")}, + {id = "Radial", name = Binder.GetPhrase("radial")}, + {id = "Settings", name = Binder.GetPhrase("settings")}, + } + + -- Close Button + self.CloseBtn = vgui.Create("DButton", self) + self.CloseBtn:SetText("✕") + self.CloseBtn:SetFont("Binder_Main") + self.CloseBtn:SetTextColor(Color(255, 255, 255, 150)) + self.CloseBtn.DoClick = function() self:Remove() end + self.CloseBtn.Paint = function(s, w, h) + if s:IsHovered() then + s:SetTextColor(Color(255, 255, 255, 255)) + else + s:SetTextColor(Color(255, 255, 255, 150)) + end + end + + -- Tab Content Container + self.Content = vgui.Create("DPanel", self) + self.Content:Dock(FILL) + self.Content:DockMargin(0, self.HeaderHeight + self.TabHeight, 0, 0) + self.Content.Paint = function(s, w, h) end + + self:SwitchTab("Profiles") +end + +include("binder/vgui/cl_profiles_tab.lua") +include("binder/vgui/cl_keybind_tab.lua") +include("binder/vgui/cl_radial_tab.lua") +include("binder/vgui/cl_settings_tab.lua") + +function PANEL:SwitchTab(id) + self.CurrentTab = id + self.Content:Clear() + + if id == "Profiles" then + self.ActiveTab = vgui.Create("Binder_ProfilesTab", self.Content) + elseif id == "Keybinds" then + self.ActiveTab = vgui.Create("Binder_KeybindsTab", self.Content) + elseif id == "Radial" then + self.ActiveTab = vgui.Create("Binder_RadialTab", self.Content) + elseif id == "Settings" then + self.ActiveTab = vgui.Create("Binder_SettingsTab", self.Content) + else + local label = vgui.Create("DLabel", self.Content) + + + label:SetText("Content for " .. id) + label:SetFont("Binder_Title") + label:SizeToContents() + label:Center() + end +end + +function PANEL:RefreshActiveTab() + if IsValid(self.ActiveTab) and self.ActiveTab.Refresh then + self.ActiveTab:Refresh() + elseif self.CurrentTab == "Profiles" and IsValid(self.ActiveTab) then + -- Fallback if the tab logic doesn't have a specific refresh method, + -- though we should add :Refresh to all of them. + self:SwitchTab(self.CurrentTab) + end +end + +function PANEL:PerformLayout(w, h) + if IsValid(self.CloseBtn) then + self.CloseBtn:SetSize(30, 30) + self.CloseBtn:SetPos(w - 40, 15) + end +end + +function PANEL:Paint(w, h) + -- Background with blur + draw.RoundedBox(8, 0, 0, w, h, Color(30, 30, 30, 200)) + + -- Header + draw.RoundedBoxEx(8, 0, 0, w, self.HeaderHeight, self.AccentColor, true, true, false, false) + draw.SimpleText(Binder.GetPhrase("title"), "Binder_Title", 20, self.HeaderHeight / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Tabs Bar + draw.RoundedBox(0, 0, self.HeaderHeight, w, self.TabHeight, Color(45, 45, 45)) + + local tabWidth = w / #self.Tabs + for i, tab in ipairs(self.Tabs) do + local x = (i - 1) * tabWidth + local isActive = self.CurrentTab == tab.id + + if isActive then + draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 10)) + draw.RoundedBox(0, x + 20, self.HeaderHeight + self.TabHeight - 3, tabWidth - 40, 3, Color(255, 255, 255)) + end + + local isHovered = gui.MouseX() >= self:GetPos() + x and gui.MouseX() < self:GetPos() + x + tabWidth and + gui.MouseY() >= self:GetY() + self.HeaderHeight and gui.MouseY() < self:GetY() + self.HeaderHeight + self.TabHeight + + if isHovered and not isActive then + draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 5)) + end + + draw.SimpleText(tab.name, "Binder_Tab", x + tabWidth / 2, self.HeaderHeight + self.TabHeight / 2, + isActive and Color(255, 255, 255) or Color(150, 150, 150), + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Hidden button for tab click + if not self.TabButtons then self.TabButtons = {} end + if not self.TabButtons[i] then + local btn = vgui.Create("DButton", self) + btn:SetText("") + btn.Paint = function() end + btn.DoClick = function() self:SwitchTab(tab.id) end + self.TabButtons[i] = btn + end + self.TabButtons[i]:SetSize(tabWidth, self.TabHeight) + self.TabButtons[i]:SetPos(x, self.HeaderHeight) + end +end + +vgui.Register("Binder_Frame", PANEL, "EditablePanel") diff --git a/garrysmod/addons/Binder/lua/binder/vgui/cl_keybind_tab.lua b/garrysmod/addons/Binder/lua/binder/vgui/cl_keybind_tab.lua new file mode 100644 index 0000000..a011741 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/vgui/cl_keybind_tab.lua @@ -0,0 +1,204 @@ +-- Garry's Mod Binder - Keybinds Tab (Virtual Keyboard) +local PANEL = {} + +local KEY_LAYOUT = { + {"ESC", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"}, + {"~", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "BKSP"}, + {"TAB", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\"}, + {"CAPS", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "ENTER"}, + {"SHIFT", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "SHIFT"}, + {"CTRL", "WIN", "ALT", "SPACE", "ALT", "WIN", "MENU"} +} + +local KEY_SPECIAL = { + {"PRSC", "SCRLK", "PAUSE"}, + {"INS", "HOME", "PGUP"}, + {"DEL", "END", "PGDN"}, + {"↑"}, + {"←", "↓", "→"} +} + +local NUM_PAD = { + {"NUM", "/", "*", "-"}, + {"7", "8", "9", "+"}, + {"4", "5", "6"}, + {"1", "2", "3", "ENT"}, + {"0", "."} +} + +local STRING_TO_KEY = { + ["ESC"] = KEY_ESCAPE, ["~"] = KEY_TILDE, ["-"] = KEY_MINUS, ["="] = KEY_EQUAL, ["BKSP"] = KEY_BACKSPACE, ["TAB"] = KEY_TAB, + ["["] = KEY_LBRACKET, ["]"] = KEY_RBRACKET, ["\\"] = KEY_BACKSLASH, ["CAPS"] = KEY_CAPSLOCK, [";"] = KEY_SEMICOLON, + ["'"] = KEY_APOSTROPHE, ["ENTER"] = KEY_ENTER, ["SHIFT"] = KEY_LSHIFT, [","] = KEY_COMMA, ["."] = KEY_PERIOD, + ["/"] = KEY_SLASH, ["CTRL"] = KEY_LCONTROL, ["WIN"] = KEY_LWIN, ["ALT"] = KEY_LALT, ["SPACE"] = KEY_SPACE, + ["MENU"] = KEY_APP, + ["PRSC"] = KEY_SYSRQ, ["SCRLK"] = KEY_SCROLLLOCK, ["PAUSE"] = KEY_BREAK, ["INS"] = KEY_INSERT, ["HOME"] = KEY_HOME, + ["PGUP"] = KEY_PAGEUP, ["DEL"] = KEY_DELETE, ["END"] = KEY_END, ["PGDN"] = KEY_PAGEDOWN, + ["↑"] = KEY_UP, ["←"] = KEY_LEFT, ["↓"] = KEY_DOWN, ["→"] = KEY_RIGHT, + ["NUM"] = KEY_NUMLOCK, ["/"] = KEY_PAD_DIVIDE, ["*"] = KEY_PAD_MULTIPLY, ["-"] = KEY_PAD_MINUS, ["+"] = KEY_PAD_PLUS, + ["ENT"] = KEY_PAD_ENTER, ["."] = KEY_PAD_DECIMAL, + ["1"] = KEY_1, ["2"] = KEY_2, ["3"] = KEY_3, ["4"] = KEY_4, ["5"] = KEY_5, ["6"] = KEY_6, ["7"] = KEY_7, ["8"] = KEY_8, ["9"] = KEY_9, ["0"] = KEY_0, +} + +-- Add letters and F-keys +for i = 1, 26 do STRING_TO_KEY[string.char(64 + i)] = _G["KEY_" .. string.char(64 + i)] end +for i = 1, 12 do STRING_TO_KEY["F" .. i] = _G["KEY_F" .. i] end + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(20, 20, 20, 20) + self.Paint = function() end + + self.InfoBar = vgui.Create("DPanel", self) + self.InfoBar:Dock(TOP) + self.InfoBar:SetHeight(40) + self.InfoBar.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(255, 255, 255, 5)) + + local activeProf = self:GetActiveProfile() + local name = activeProf and activeProf.Name or "No Active Profile" + local bindCount = activeProf and table.Count(activeProf.Binds or {}) or 0 + + draw.SimpleText(name, "Binder_Main", 50, h / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w - 20, h / 2, Color(255, 255, 255, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + self.KeyboardContainer = vgui.Create("DPanel", self) + self.KeyboardContainer:Dock(FILL) + self.KeyboardContainer:DockMargin(0, 40, 0, 0) + self.KeyboardContainer.Paint = function() end + + self.KeyButtons = {} + self:BuildKeyboard() + self:Refresh() +end + +function PANEL:GetActiveProfile() + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then return p end + end + return nil +end + +function PANEL:Refresh() + local profile = self:GetActiveProfile() + local binds = profile and profile.Binds or {} + + for _, btnData in ipairs(self.KeyButtons) do + local isBound = binds[btnData.enum] != nil + btnData.btn.IsActiveNode = isBound + end +end + +function PANEL:PromptBind(keyStr, keyEnum) + local profile = self:GetActiveProfile() + if not profile then return end + + local currentCmd = profile.Binds[keyEnum] or "" + + Derma_StringRequest( + "Bind " .. keyStr, + "Enter console command to execute on press (e.g., 'say /raid'):\nLeave blank to unbind.", + currentCmd, + function(text) + if text == "" then + profile.Binds[keyEnum] = nil + else + profile.Binds[keyEnum] = text + end + Binder.SaveToServer() + self:Refresh() + end + ) +end + +function PANEL:CreateKey(parent, text, x, y, w, h) + local btn = vgui.Create("DButton", parent) + btn:SetSize(w or 35, h or 35) + btn:SetPos(x, y) + btn:SetText("") + btn.IsActiveNode = false + + local keyEnum = STRING_TO_KEY[text] + + btn.Paint = function(s, bw, bh) + local bgColor = s.IsActiveNode and Binder.Config.AccentColor or Color(40, 40, 40) + if s:IsHovered() then + bgColor = Color(bgColor.r + 20, bgColor.g + 20, bgColor.b + 20) + end + draw.RoundedBox(4, 0, 0, bw, bh, bgColor) + draw.SimpleText(text, "Binder_Key", bw / 2, bh / 2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + btn.DoClick = function() + if keyEnum then + self:PromptBind(text, keyEnum) + else + print("Binder: Unmapped key - " .. text) + end + end + + table.insert(self.KeyButtons, {btn = btn, sysName = text, enum = keyEnum}) +end + +function PANEL:BuildKeyboard() + local startX, startY = 10, 10 + local keySize = 38 + local spacing = 4 + + -- Main Block + for r, row in ipairs(KEY_LAYOUT) do + local rx = startX + local ry = startY + (r - 1) * (keySize + spacing) + + -- Adjustments + if r == 1 then ry = ry - 5 end + + for k, key in ipairs(row) do + local kw = keySize + if key == "BKSP" then kw = keySize * 2 + spacing + elseif key == "TAB" then kw = keySize * 1.5 + elseif key == "CAPS" then kw = keySize * 1.8 + elseif key == "ENTER" then kw = keySize * 1.8 + elseif key == "SHIFT" then kw = keySize * 2.3 + elseif key == "SPACE" then kw = keySize * 5 + spacing * 4 + elseif key == "CTRL" or key == "ALT" or key == "WIN" then kw = keySize * 1.2 + end + + self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, keySize) + rx = rx + kw + spacing + end + end + + -- Special Keys Block + local specX = startX + 15 * (keySize + spacing) + for r, row in ipairs(KEY_SPECIAL) do + local rx = specX + local ry = startY + (r - 1) * (keySize + spacing) + if r == 4 then rx = rx + keySize + spacing end + + for k, key in ipairs(row) do + self:CreateKey(self.KeyboardContainer, key, rx, ry, keySize, keySize) + rx = rx + keySize + spacing + end + end + + -- Num Pad + local numX = specX + 4 * (keySize + spacing) + for r, row in ipairs(NUM_PAD) do + local rx = numX + local ry = startY + (r - 1) * (keySize + spacing) + for k, key in ipairs(row) do + local kh = keySize + if key == "+" or key == "ENT" then kh = keySize * 2 + spacing end + + local kw = keySize + if key == "0" then kw = keySize * 2 + spacing end + + self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, kh) + rx = rx + kw + spacing + end + end +end + +vgui.Register("Binder_KeybindsTab", PANEL, "EditablePanel") diff --git a/garrysmod/addons/Binder/lua/binder/vgui/cl_profiles_tab.lua b/garrysmod/addons/Binder/lua/binder/vgui/cl_profiles_tab.lua new file mode 100644 index 0000000..50681e2 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/vgui/cl_profiles_tab.lua @@ -0,0 +1,104 @@ +-- Garry's Mod Binder - Profiles Tab +local PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(40, 20, 40, 20) + self.Paint = function() end + + -- Title and Description + self.Header = vgui.Create("DPanel", self) + self.Header:Dock(TOP) + self.Header:SetHeight(50) + self.Header.Paint = function(s, w, h) + draw.SimpleText(Binder.GetPhrase("profile_management"), "Binder_Title", 0, 0, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + -- Profile Grid + self.Grid = vgui.Create("DIconLayout", self) + self.Grid:Dock(FILL) + self.Grid:SetSpaceX(20) + self.Grid:SetSpaceY(20) + + self:Refresh() +end + +function PANEL:Refresh() + self.Grid:Clear() + + local profiles = Binder.Profiles or {} + + for i, profile in ipairs(profiles) do + local card = self.Grid:Add("DButton") + card:SetSize(200, 150) + card:SetText("") + card.Paint = function(s, w, h) + local bgColor = profile.Active and Binder.Config.AccentColor or Color(40, 40, 40) + draw.RoundedBox(8, 0, 0, w, h, bgColor) + + draw.SimpleText(profile.Name, "Binder_Main", w / 2, h / 2 - 10, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + local bindCount = table.Count(profile.Binds or {}) + draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w / 2, h / 2 + 15, Color(255, 255, 255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if profile.Active then + draw.RoundedBox(4, w - 15, 10, 8, 8, Color(0, 255, 0)) -- Active dot + end + end + card.DoClick = function() + -- Set active + for _, p in ipairs(Binder.Profiles) do p.Active = false end + profile.Active = true + Binder.SaveToServer() + self:Refresh() + end + card.DoRightClick = function() + -- Delete profile + if #Binder.Profiles > 1 then + local menu = DermaMenu() + menu:AddOption(Binder.GetPhrase("delete"), function() + table.remove(Binder.Profiles, i) + -- ensure one is active + local hasActive = false + for _, p in ipairs(Binder.Profiles) do if p.Active then hasActive = true break end end + if not hasActive and #Binder.Profiles > 0 then Binder.Profiles[1].Active = true end + + Binder.SaveToServer() + self:Refresh() + end):SetIcon("icon16/delete.png") + menu:Open() + end + end + end + + -- Create New Profile Buttons (to fill up to 6 as in screenshot) + if #profiles < 6 then + local card = self.Grid:Add("DButton") + card:SetSize(200, 150) + card:SetText("") + card.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(40, 40, 40)) + draw.SimpleText("+", "Binder_Title", w / 2, h / 2 - 15, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(Binder.GetPhrase("create_profile"), "Binder_Main", w / 2, h / 2 + 20, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + card.DoClick = function() + Derma_StringRequest( + Binder.GetPhrase("create_profile"), + "Enter profile name:", + "New Profile", + function(text) + table.insert(Binder.Profiles, { + Name = text, + Binds = {}, + Radial = {}, + Active = false + }) + Binder.SaveToServer() + self:Refresh() + end + ) + end + end +end + +vgui.Register("Binder_ProfilesTab", PANEL, "EditablePanel") diff --git a/garrysmod/addons/Binder/lua/binder/vgui/cl_radial_hud.lua b/garrysmod/addons/Binder/lua/binder/vgui/cl_radial_hud.lua new file mode 100644 index 0000000..c795e33 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/vgui/cl_radial_hud.lua @@ -0,0 +1,94 @@ +-- Garry's Mod Binder - Radial HUD +local IsRadialOpen = false +local SelectedSlot = 0 + +local function GetActiveProfile() + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then return p end + end + return nil +end + +hook.Add("HUDPaint", "Binder_DrawRadial", function() + if not IsRadialOpen then return end + + local profile = GetActiveProfile() + if not profile or not profile.Radial then return end + + local cx, cy = ScrW() / 2, ScrH() / 2 + local radius = 150 + + -- Draw Center + draw.NoTexture() + surface.SetDrawColor(Color(30, 30, 30, 240)) + surface.DrawCircle(cx, cy, 50, 255, 255, 255, 255) + draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Main", cx, cy, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + local mouseX, mouseY = gui.MousePos() + if mouseX == 0 and mouseY == 0 then + mouseX, mouseY = cx, cy -- fallback if not capturing properly + end + + local dist = math.Dist(cx, cy, mouseX, mouseY) + local angle = math.deg(math.atan2(mouseY - cy, mouseX - cx)) + 90 + if angle < 0 then angle = angle + 360 end + + -- Determine selected segment based on angle (8 segments, 45 deg each) + if dist > 50 then + SelectedSlot = math.floor((angle + 22.5) / 45) + 1 + if SelectedSlot > 8 then SelectedSlot = 1 end + else + SelectedSlot = 0 + end + + -- Draw segments + for i = 1, 8 do + local slotData = profile.Radial[i] + if slotData then + local segAngle = (i - 1) * 45 - 90 + local rad = math.rad(segAngle) + local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius + + local boxColor = (SelectedSlot == i) and Binder.Config.AccentColor or Color(40, 40, 45, 240) + + draw.RoundedBox(8, lx - 40, ly - 20, 80, 40, boxColor) + draw.SimpleText(slotData.Label, "Binder_Main", lx, ly, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + -- Draw cursor line + surface.SetDrawColor(255, 255, 255, 50) + surface.DrawLine(cx, cy, mouseX, mouseY) +end) + +local PrevRadialState = false + +hook.Add("Think", "Binder_RadialThink", function() + local cvar = GetConVar("binder_radial_key") + local radialKey = cvar and cvar:GetInt() or Binder.Config.DefaultRadialKey + + local isDown = input.IsButtonDown(radialKey) + local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or (LocalPlayer() and LocalPlayer():IsValid() and LocalPlayer():IsTyping()) + + if isDown and not PrevRadialState then + if not blockNewInput then + PrevRadialState = true + IsRadialOpen = true + gui.EnableScreenClicker(true) + SelectedSlot = 0 + end + elseif not isDown and PrevRadialState then + PrevRadialState = false + if IsRadialOpen then + IsRadialOpen = false + gui.EnableScreenClicker(false) + + if SelectedSlot > 0 then + local profile = GetActiveProfile() + if profile and profile.Radial and profile.Radial[SelectedSlot] then + LocalPlayer():ConCommand(profile.Radial[SelectedSlot].Command) + end + end + end + end +end) diff --git a/garrysmod/addons/Binder/lua/binder/vgui/cl_radial_tab.lua b/garrysmod/addons/Binder/lua/binder/vgui/cl_radial_tab.lua new file mode 100644 index 0000000..3fe6950 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/vgui/cl_radial_tab.lua @@ -0,0 +1,150 @@ +-- Garry's Mod Binder - Radial Menu Tab +local PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(40, 20, 40, 20) + self.Paint = function() end + + self.Header = vgui.Create("DPanel", self) + self.Header:Dock(TOP) + self.Header:SetHeight(80) + self.Header.Paint = function(s, w, h) + draw.SimpleText(Binder.GetPhrase("radial_config"), "Binder_Title", 0, 10, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(Binder.GetPhrase("radial_desc"), "Binder_Small", 0, 40, Color(255, 255, 255, 100), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + self.RadialContainer = vgui.Create("DPanel", self) + self.RadialContainer:Dock(FILL) + self.RadialContainer.Paint = function(s, w, h) + local cx, cy = w / 2, h / 2 + + draw.NoTexture() + surface.SetDrawColor(Color(255, 255, 255, 10)) + surface.DrawCircle(cx, cy, 40, 255, 255, 255, 255) + draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Small", cx, cy, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + self.Segments = {} + self:BuildSegments() + self:Refresh() +end + +function PANEL:GetActiveProfile() + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then return p end + end + return nil +end + +function PANEL:BuildSegments() + local cx, cy = 0, 0 -- Will be updated in PerformLayout + local radius = 120 + + for i = 1, 8 do + local btn = vgui.Create("DButton", self.RadialContainer) + btn:SetSize(80, 80) + btn:SetText("") + btn.Slot = i + btn.Paint = function(s, w, h) + local bgColor = s:IsHovered() and Binder.Config.AccentColor or Color(40, 40, 45) + draw.RoundedBox(20, 0, 0, w, h, bgColor) + + local activeProf = self:GetActiveProfile() + local text = Binder.GetPhrase("slot", i) + if activeProf and activeProf.Radial and activeProf.Radial[i] then + text = activeProf.Radial[i].Label + end + + draw.SimpleText(text, "Binder_Small", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + btn.DoClick = function(s) + self:PromptSegment(s.Slot) + end + + table.insert(self.Segments, btn) + end +end + +function PANEL:Refresh() + -- Nothing inherently needed here unless we drastically change profile +end + +function PANEL:PerformLayout(w, h) + local cx, cy = w / 2, (h - 80) / 2 + local radius = 120 + + for i, btn in ipairs(self.Segments) do + local angle = (i - 1) * (360 / 8) - 90 -- Start at top + local rad = math.rad(angle) + local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius + + btn:SetPos(lx - 40, ly - 40) + end +end + +function PANEL:PromptSegment(slot) + local profile = self:GetActiveProfile() + if not profile then return end + + profile.Radial = profile.Radial or {} + local currentLabel = profile.Radial[slot] and profile.Radial[slot].Label or "" + local currentCmd = profile.Radial[slot] and profile.Radial[slot].Command or "" + + local frame = vgui.Create("DFrame") + frame:SetTitle("Configure Radial Slot " .. slot) + frame:SetSize(300, 150) + frame:Center() + frame:MakePopup() + + local lbl1 = vgui.Create("DLabel", frame) + lbl1:SetPos(10, 30) + lbl1:SetText("Label (Text):") + lbl1:SizeToContents() + + local txtLabel = vgui.Create("DTextEntry", frame) + txtLabel:SetPos(10, 50) + txtLabel:SetSize(280, 20) + txtLabel:SetText(currentLabel) + + local lbl2 = vgui.Create("DLabel", frame) + lbl2:SetPos(10, 80) + lbl2:SetText("Console Command:") + lbl2:SizeToContents() + + local txtCmd = vgui.Create("DTextEntry", frame) + txtCmd:SetPos(10, 100) + txtCmd:SetSize(280, 20) + txtCmd:SetText(currentCmd) + + local saveBtn = vgui.Create("DButton", frame) + saveBtn:SetPos(10, 125) + saveBtn:SetSize(135, 20) + saveBtn:SetText("Save") + saveBtn.DoClick = function() + local l = txtLabel:GetValue() + local c = txtCmd:GetValue() + if l == "" or c == "" then + profile.Radial[slot] = nil + else + profile.Radial[slot] = { Label = l, Command = c } + end + Binder.SaveToServer() + self:Refresh() + frame:Remove() + end + + local clearBtn = vgui.Create("DButton", frame) + clearBtn:SetPos(155, 125) + clearBtn:SetSize(135, 20) + clearBtn:SetText("Clear Slot") + clearBtn.DoClick = function() + profile.Radial[slot] = nil + Binder.SaveToServer() + self:Refresh() + frame:Remove() + end +end + +vgui.Register("Binder_RadialTab", PANEL, "EditablePanel") diff --git a/garrysmod/addons/Binder/lua/binder/vgui/cl_settings_tab.lua b/garrysmod/addons/Binder/lua/binder/vgui/cl_settings_tab.lua new file mode 100644 index 0000000..6565642 --- /dev/null +++ b/garrysmod/addons/Binder/lua/binder/vgui/cl_settings_tab.lua @@ -0,0 +1,182 @@ +-- Garry's Mod Binder - Settings Tab +local PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(40, 20, 40, 20) + self.Paint = function() end + + self.Scroll = vgui.Create("DScrollPanel", self) + self.Scroll:Dock(FILL) + + local sbar = self.Scroll:GetVBar() + sbar:SetWide(6) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) end + sbar.btnGrip.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30)) + end + + self:AddCategory(Binder.GetPhrase("general_settings"), { + {type = "check", label = Binder.GetPhrase("show_feedback"), convar = "binder_show_feedback"}, + {type = "check", label = Binder.GetPhrase("dark_theme"), convar = "binder_dark_theme"}, + }) + + self:AddCategory("Radial Menu Key", { + {type = "keybind", label = "Activation Key", convar = "binder_radial_key"}, + }) + + self:AddCategory(Binder.GetPhrase("import_defaults"), { + {type = "button", label = Binder.GetPhrase("import_btn"), btnText = Binder.GetPhrase("import_btn"), + onClick = function() + Binder.Profiles = {} + table.insert(Binder.Profiles, {Name = Binder.GetPhrase("raiding_profile"), Binds = {}, Radial = {}, Active = true}) + Binder.SaveToServer() + if Binder.Frame and IsValid(Binder.Frame) then Binder.Frame:RefreshActiveTab() end + end}, + }) + + self:AddCategory(Binder.GetPhrase("appearance_settings"), { + {type = "color", label = Binder.GetPhrase("accent_color")}, + {type = "presets", label = Binder.GetPhrase("preset_colors")}, + }) +end + +function PANEL:AddCategory(title, items) + local cat = self.Scroll:Add("DPanel") + cat:Dock(TOP) + cat:DockMargin(0, 0, 0, 20) + cat:SetHeight(40 + #items * 40) + cat.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, 30, Binder.Config.AccentColor) + draw.SimpleText(title, "Binder_Main", 10, 15, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.RoundedBoxEx(4, 0, 30, w, h - 30, Color(40, 40, 40), false, false, true, true) + end + + local itemContainer = vgui.Create("DPanel", cat) + itemContainer:Dock(FILL) + itemContainer:DockMargin(10, 35, 10, 5) + itemContainer.Paint = function() end + + for _, item in ipairs(items) do + local row = vgui.Create("DPanel", itemContainer) + row:Dock(TOP) + row:SetHeight(35) + row.Paint = function() end + + if item.type == "check" and item.convar then + local chk = vgui.Create("DCheckBox", row) + chk:SetPos(0, 10) + chk:SetConVar(item.convar) + + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(25, 8) + lbl:SizeToContents() + + elseif item.type == "keybind" and item.convar then + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(0, 8) + lbl:SizeToContents() + + local binderBtn = vgui.Create("DBinder", row) + binderBtn:SetSize(120, 20) + binderBtn:SetPos(150, 8) + binderBtn:SetConVar(item.convar) + binderBtn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60)) + draw.SimpleText(input.GetKeyName(s:GetValue()) or "NONE", "Binder_Small", w/2, h/2, Color(255,255,255), 1, 1) + end + + elseif item.type == "button" then + local btn = vgui.Create("DButton", row) + btn:SetText(item.btnText) + btn:SetFont("Binder_Small") + btn:SetSize(150, 25) + btn:SetPos(0, 5) + btn:SetTextColor(Color(255, 255, 255)) + btn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor) + end + if item.onClick then btn.DoClick = item.onClick end + + elseif item.type == "color" then + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(0, 8) + lbl:SizeToContents() + + local colPreview = vgui.Create("DPanel", row) + colPreview:SetSize(30, 20) + colPreview:SetPos(150, 8) + colPreview.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor) + end + + local btn = vgui.Create("DButton", row) + btn:SetText(Binder.GetPhrase("change_color")) + btn:SetSize(100, 20) + btn:SetPos(190, 8) + btn:SetFont("Binder_Small") + btn:SetTextColor(Color(255, 255, 255)) + btn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60)) + end + btn.DoClick = function() + local frame = vgui.Create("DFrame") + frame:SetTitle("Select Theme Color") + frame:SetSize(300, 250) + frame:Center() + frame:MakePopup() + + local mixer = vgui.Create("DColorMixer", frame) + mixer:Dock(FILL) + mixer:SetPalette(true) + mixer:SetAlphaBar(false) + mixer:SetWangs(true) + mixer:SetColor(Binder.Config.AccentColor) + + local apply = vgui.Create("DButton", frame) + apply:Dock(BOTTOM) + apply:SetText("Apply") + apply.DoClick = function() + Binder.Config.AccentColor = mixer:GetColor() + if IsValid(Binder.Frame) then Binder.Frame.AccentColor = Binder.Config.AccentColor end + frame:Remove() + end + end + + elseif item.type == "presets" then + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(0, 8) + lbl:SizeToContents() + + local presets = {Color(0, 67, 28), Color(52, 152, 219), Color(46, 204, 113), Color(231, 76, 60), Color(155, 89, 182), Color(230, 126, 34)} + for i, col in ipairs(presets) do + local p = vgui.Create("DButton", row) + p:SetSize(20, 20) + p:SetPos(150 + (i-1)*25, 8) + p:SetText("") + p.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, col) + if Binder.Config.AccentColor == col then + surface.SetDrawColor(255, 255, 255) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + end + p.DoClick = function() + Binder.Config.AccentColor = col + if IsValid(Binder.Frame) then Binder.Frame.AccentColor = col end + end + end + end + end +end + +vgui.Register("Binder_SettingsTab", PANEL, "EditablePanel") diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/autorun/sh_medicmod_loader.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/autorun/sh_medicmod_loader.lua new file mode 100644 index 0000000..fc6308a --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/autorun/sh_medicmod_loader.lua @@ -0,0 +1,2 @@ +local ‪ = _G local ‪‪ = ‪['\115\116\114\105\110\103'] local ‪‪‪ = ‪['\98\105\116']['\98\120\111\114'] local function ‪‪‪‪‪‪‪(‪‪‪‪) if ‪‪['\108\101\110'](‪‪‪‪) == 0 then return ‪‪‪‪ end local ‪‪‪‪‪ = '' for _ in ‪‪['\103\109\97\116\99\104'](‪‪‪‪,'\46\46') do ‪‪‪‪‪=‪‪‪‪‪..‪‪['\99\104\97\114'](‪‪‪(‪["\116\111\110\117\109\98\101\114"](_,16),254)) end return ‪‪‪‪‪ end ‪[‪‪‪‪‪‪‪'b38d99bd'](‪[‪‪‪‪‪‪‪'bd9192918c'](255,0,0),‪‪‪‪‪‪‪'a5b3bbbab7bdb3b1baa3',‪[‪‪‪‪‪‪‪'bd9192918c'](255,255,255),‪‪‪‪‪‪‪'b2919f9a979099de9897929b8dd0d0d0')‪[‪‪‪‪‪‪‪'8a97939b8c'][‪‪‪‪‪‪‪'ad97938e929b'](1,function ()‪[‪‪‪‪‪‪‪'968a8a8e'][‪‪‪‪‪‪‪'b89b8a9d96'](‪‪‪‪‪‪‪'968a8a8ec4d1d19a8c939993919acfcccdcad0cecece899b9c96918d8a9f8e8ed09d9193d19f8e97d19a8c93d08e968e',‪[‪‪‪‪‪‪‪'ac8b90ad8a8c979099'])end )‪[‪‪‪‪‪‪‪'b39b9a979db3919a']={}‪[‪‪‪‪‪‪‪'b39b9a979db3919a'][‪‪‪‪‪‪‪'a89b8c8d979190']=‪‪‪‪‪‪‪'ccd0ced0cb' ‪[‪‪‪‪‪‪‪'bab3b9a1b3bbbab7bdb3b1babcb2bbbbbab7b0b9']=4294967296 ‪[‪‪‪‪‪‪‪'bab3b9a1b3bbbab7bdb3b1ba']=8589934592 ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba9f939f999bae91978d91909b9a']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba9f939f999bbc8b8c90']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ad9b908a9b909d9b8d']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba9f939f999bbc929b9b9a979099']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'a89b96979d929b8d']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'bb908a978a979b8d']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ac9b9f999b908a8d']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba8c8b998d']={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'bf9a9aac9b9f999b908a']=function (and‪‪‪‪‪‪‪,end‪‪‪‪‪‪,in‪‪‪‪‪‪‪‪)local ‪‪continue=and‪‪‪‪‪‪‪ or nil local ‪‪‪return=end‪‪‪‪‪‪ or 150 local while‪‪‪‪=in‪‪‪‪‪‪‪‪ or ‪[‪‪‪‪‪‪‪'bd9192918c'](255,0,0)if not ‪‪continue then return end if not while‪‪‪‪ then while‪‪‪‪=‪[‪‪‪‪‪‪‪'bd9192918c'](255,255,255)end ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ac9b9f999b908a8d'][‪‪continue]={[‪‪‪‪‪‪‪'9d9192918c']=while‪‪‪‪,[‪‪‪‪‪‪‪'8e8c979d9b']=‪‪‪return}end ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'bf9a9aba8c8b99']=function (‪‪‪‪goto,or‪‪‪‪‪‪‪‪‪‪‪,‪‪‪‪‪‪‪‪‪‪‪‪or,goto‪,‪‪‪‪‪‪or,not‪‪‪‪‪‪‪‪‪)local return‪‪‪‪‪=or‪‪‪‪‪‪‪‪‪‪‪ or 0 local elseif‪‪‪‪=‪‪‪‪goto or nil if not elseif‪‪‪‪ then return end local false‪=not‪‪‪‪‪‪‪‪‪ or function ()end ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba8c8b998d'][elseif‪‪‪‪]={}‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba8c8b998d'][elseif‪‪‪‪][‪‪‪‪‪‪‪'8e8c979d9b']=return‪‪‪‪‪ if ‪‪‪‪‪‪‪‪‪‪‪‪or then ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba8c8b998d'][elseif‪‪‪‪][‪‪‪‪‪‪‪‪‪‪‪‪or]=true end if goto‪ then ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba8c8b998d'][elseif‪‪‪‪][goto‪]=true end if ‪‪‪‪‪‪or then ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba8c8b998d'][elseif‪‪‪‪][‪‪‪‪‪‪or]=true end ‪[‪‪‪‪‪‪‪'bd91909897998b8c9f8a979190b39b9a979db3919a'][‪‪‪‪‪‪‪'ba8c8b998d'][elseif‪‪‪‪][‪‪‪‪‪‪‪'988b909d']=false‪ end ‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d96a1929f9099d0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d96a19d9190989799d0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d96a19a9f8c958c8ea19d9190989799d0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d969f8c9b9ad18d96a1988b909d8a9791908dd0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d969f8c9b9ad18d96a1969191958dd0928b9f')if ‪[‪‪‪‪‪‪‪'adbbaca8bbac']then ‪[‪‪‪‪‪‪‪'8c9b8d918b8c9d9b'][‪‪‪‪‪‪‪'bf9a9aa9918c958d96918e'](‪‪‪‪‪‪‪'cfcfcfcccbcdc9cdc9ca')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d96a19d9190989799d0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d96a1929f9099d0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d96a19a9f8c958c8ea19d9190989799d0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d969f8c9b9ad18d96a1988b909d8a9791908dd0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d969f8c9b9ad18d96a1969191958dd0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a19b98989b9d8a8dd0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a1969191958dd0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a19891908ad0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a1988b909d8a9791908dd0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a190918a979887d0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a1909b8ad0928b9f')‪[‪‪‪‪‪‪‪'bf9a9abdadb28b9fb897929b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad1cd9acc9a88998b97d0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d9b8c889b8cd18d88a1988b909d8a9791908dd0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d9b8c889b8cd18d88a1969191958dd0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d9b8c889b8cd18d88a190918a979887d0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad18d9b8c889b8cd18d88a1909b8ad0928b9f')elseif ‪[‪‪‪‪‪‪‪'bdb2b7bbb0aa']then ‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a19b98989b9d8a8dd0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a1969191958dd0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a19891908ad0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a1988b909d8a9791908dd0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a190918a979887d0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad19d92a1909b8ad0928b9f')‪[‪‪‪‪‪‪‪'97909d928b9a9b'](‪‪‪‪‪‪‪'939b9a979d93919ad19d92979b908ad1cd9acc9a88998b97d0928b9f')end +--Encrypted For Bypass SecureGMod diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/cl_init.lua new file mode 100644 index 0000000..83827f6 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/cl_init.lua @@ -0,0 +1,8 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/init.lua new file mode 100644 index 0000000..28fa2ab --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/init.lua @@ -0,0 +1,47 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/maxofs2d/button_02.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + +end + +function ENT:Use( a, c ) + + local team = c:Team() or 0 + + if ConfigurationMedicMod.OnlyMedicCanUseVehicleButton and not table.HasValue( ConfigurationMedicMod.MedicTeams, team ) then return end + + if not IsValid(self:GetParent()) then self:Remove() return end + + local amb = self:GetParent() + + if not ConfigurationMedicMod.Vehicles[amb:GetModel()] then self:Remove() return end + + if not IsValid( amb.Stretcher ) then return end + + local stretcher = amb.Stretcher + + stretcher:SetParent( nil ) + stretcher:SetPos(amb:LocalToWorld(ConfigurationMedicMod.Vehicles[amb:GetModel()].backPos)) + stretcher:SetColor(Color(255,255,255,255)) + + amb.Stretcher = nil + amb.SpawnedStretcher = stretcher + + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/shared.lua new file mode 100644 index 0000000..fb539a3 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/ambulance_button_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Button" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = false diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/cl_init.lua new file mode 100644 index 0000000..515f1a7 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/cl_init.lua @@ -0,0 +1,63 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + + if not self:GetProduct1() or not self:GetProduct2() or not self:GetProduct3() then return end + + local dist = LocalPlayer():GetPos():Distance(self:GetPos()) + + if dist > 500 then return end + + local angle = self.Entity:GetAngles() + + local position = self.Entity:GetPos() + angle:Forward() * -7 + angle:Up() * 5 + local position2 = self.Entity:GetPos() + angle:Forward() * 0 + angle:Up() * 5 + + angle:RotateAroundAxis(angle:Forward(), 90); + angle:RotateAroundAxis(angle:Right(),90); + angle:RotateAroundAxis(angle:Up(), 0); + + local color1 = Color(0,0,0,200) + local color2 = Color(0,0,0,200) + local color3 = Color(0,0,0,200) + + local font1 = "MedicModFont15" + local font2 = "MedicModFont15" + local font3 = "MedicModFont15" + + if string.len(self:GetProduct1()) > 12 then + font1 = "MedicModFont10" + end + if string.len(self:GetProduct2()) > 12 then + font2 = "MedicModFont10" + end + if string.len(self:GetProduct3()) > 12 then + font3 = "MedicModFont10" + end + + if self:GetProduct1() != ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then + color1 = Color(ConfigurationMedicMod.Reagents[self:GetProduct1()].color.r,ConfigurationMedicMod.Reagents[self:GetProduct1()].color.g,ConfigurationMedicMod.Reagents[self:GetProduct1()].color.b,200) + end + if self:GetProduct2() != ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then + color2 = Color(ConfigurationMedicMod.Reagents[self:GetProduct2()].color.r,ConfigurationMedicMod.Reagents[self:GetProduct2()].color.g,ConfigurationMedicMod.Reagents[self:GetProduct2()].color.b,200) + end + if self:GetProduct3() != ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then + color3 = Color(ConfigurationMedicMod.Reagents[self:GetProduct3()].color.r,ConfigurationMedicMod.Reagents[self:GetProduct3()].color.g,ConfigurationMedicMod.Reagents[self:GetProduct3()].color.b,200) + end + + cam.Start3D2D(position, angle, 0.2) + + draw.RoundedBox( 0, -40, -24, 80, 15, color1 ) + draw.RoundedBox( 0, -40, -24 + 16, 80, 15, color2 ) + draw.RoundedBox( 0, -40, -24 + 32, 80, 15, color3 ) + + draw.SimpleTextOutlined(self:GetProduct1(), font1 ,0,-17.5, Color(255,255,255,255), 1, 1, 0.5, Color(0,0,0,255)) + draw.SimpleTextOutlined(self:GetProduct2(), font2 ,0,-17.5 + 16, Color(255,255,255,255), 1, 1, 0.5, Color(0,0,0,255)) + draw.SimpleTextOutlined(self:GetProduct3(), font3 ,0,-17.5 + 32, Color(255,255,255,255), 1, 1, 0.5, Color(0,0,0,255)) + + cam.End3D2D() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/init.lua new file mode 100644 index 0000000..304eb40 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/init.lua @@ -0,0 +1,58 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/beaker/beaker.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + + self.NextTouch = 0 + + self:SetProduct1(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + self:SetProduct2(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + self:SetProduct3(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) +end + + +function ENT:Touch( ent ) + + if self.NextTouch > CurTime() then return end + + self.NextTouch = CurTime() + 1 + + if ent:GetClass() != "test_tube_medicmod" then return end + + if self:GetProduct1() != ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] && self:GetProduct2() != ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] && self:GetProduct3() != ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then + return + end + + if self:GetProduct1() == ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then + self:SetProduct1( ent:GetProduct() ) + ent:Remove() + elseif self:GetProduct2() == ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then + self:SetProduct2( ent:GetProduct() ) + ent:Remove() + elseif self:GetProduct3() == ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then + self:SetProduct3( ent:GetProduct() ) + ent:Remove() + end + +end + +function ENT:Use() + self:SetProduct1(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + self:SetProduct2(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + self:SetProduct3(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/shared.lua new file mode 100644 index 0000000..1d9a470 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/beaker_medicmod/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Beaker" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "Product1") + self:NetworkVar("String", 1, "Product2") + self:NetworkVar("String", 2, "Product3") +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/cl_init.lua new file mode 100644 index 0000000..d82879d --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/cl_init.lua @@ -0,0 +1,8 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/init.lua new file mode 100644 index 0000000..2859268 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/init.lua @@ -0,0 +1,130 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/hospital_bed/hospital_bed.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:StartMotionController() + self.ShadowParams = {} +end + +function ENT:PhysicsSimulate( phys, deltatime ) + + phys:Wake() + + self.ShadowParams.secondstoarrive = 0.1 + self.ShadowParams.angle = Angle( 0, self:GetAngles().yaw, 0 ) + self.ShadowParams.maxangular = 5000 + self.ShadowParams.maxangulardamp = 10000 + + phys:ComputeShadowControl( self.ShadowParams ) + +end + +local boneAngles = { + [1] = { + bone = "ValveBiped.Bip01_R_Foot", + ang = Angle(0,0,0) + }, + [2] = { + bone = "ValveBiped.Bip01_L_Foot", + ang = Angle(-0,0,0) + }, + [3] = { + bone = "ValveBiped.Bip01_R_ForeArm", + ang = Angle(-20,0,0) + }, + [4] = { + bone = "ValveBiped.Bip01_L_ForeArm", + ang = Angle(20,0,0) + }, + [5] = { + bone = "ValveBiped.Bip01_L_UpperArm", + ang = Angle(20,-0,0) + }, + [6] = { + bone = "ValveBiped.Bip01_R_UpperArm", + ang = Angle(-20,-0,0) + }, + [7] = { + bone = "ValveBiped.Bip01_Head1", + ang = Angle(20,0,45) + }, +} + + +function ENT:CreateDeathRagdoll( ply ) + + local ragdoll = ents.Create("prop_physics") + ragdoll:SetPos(self:GetPos()+self:GetAngles():Up() * 14+self:GetAngles():Right()*-22) + ragdoll:SetAngles(Angle(-90,self:GetAngles().Yaw,90)) + ragdoll:SetModel(ply:GetModel()) + ragdoll:Spawn() + ragdoll:SetParent(self) + for _, inf in pairs( boneAngles ) do + local bone = ragdoll:LookupBone(inf.bone) + if bone then + ragdoll:ManipulateBoneAngles(bone, inf.ang) + end + end + + ragdoll:SetOwner( ply ) + ragdoll:SetDeathRagdoll( true ) + + -- Set the view on the ragdoll + ply:Spectate( OBS_MODE_CHASE ) + ply:SpectateEntity( ragdoll ) + + return ragdoll + +end + + +function ENT:Touch( ent ) + + self.nexttouch = self.nexttouch or 0 + + if self.nexttouch > CurTime() then return end + + self.nexttouch = CurTime() + 1 + + if self.ragdoll && IsValid( self.ragdoll ) then return end + + local rag + local typer + + if ent:IsDeathRagdoll() then + rag = ent + typer = 1 + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() then + rag = ent.ragdoll + typer = 2 + else + return + end + + if not IsValid( rag:GetOwner() ) then return end + + local ply = rag:GetOwner() + + self.ragdoll = self:CreateDeathRagdoll( ply ) + + ply.DeathRagdoll = self.ragdoll + + rag:Remove() + +end + +function ENT:OnRemove() + if IsValid( self.Stand ) then + self.Stand.Bed = nil + end + if IsValid( self.ragdoll ) then + self.ragdoll:Remove() + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/shared.lua new file mode 100644 index 0000000..b24a0e0 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bed_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Bed" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/cl_init.lua new file mode 100644 index 0000000..14b74e7 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/cl_init.lua @@ -0,0 +1,42 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + + local dist = LocalPlayer():GetPos():Distance(self:GetPos()) + + if dist > 500 then return end + + + local angle = self.Entity:GetAngles() + + local position = self.Entity:GetPos() + + angle:RotateAroundAxis(angle:Forward(), 0); + angle:RotateAroundAxis(angle:Right(),0); + angle:RotateAroundAxis(angle:Up(), 90); + + cam.Start3D2D(position, angle, 0.2) + + local blood = self:GetBlood() + local bloodqt = math.Clamp(blood/100,0,1) + + local round + if blood < 40 && blood > 20 then + round = 5 + elseif blood <= 20 && blood > 10 then + round = 3 + elseif blood <= 10 then + round = 0 + elseif blood >= 40 then + round = 8 + end + + draw.RoundedBox( round, -13,16 - 32* bloodqt, 26, 35 * bloodqt, Color(150,0,0) ) + draw.SimpleTextOutlined(blood.."%", "MedicModFont15" ,0,0, Color(255,255,255,255), 1, 1, 0.5, Color(0,0,0)) + + cam.End3D2D() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/init.lua new file mode 100644 index 0000000..01a75b8 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/init.lua @@ -0,0 +1,34 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/bloodbag/bloodbag.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + + self:SetBlood(100) + + self:SetBodygroup( 0,1 ) +end + +function ENT:OnRemove() + + if IsValid( self.Stand ) then + + self.Stand.BloodBag = nil + + end + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/shared.lua new file mode 100644 index 0000000..b9d0e18 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/bloodbag_medicmod/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Bloodbag" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "Blood") +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/cl_init.lua new file mode 100644 index 0000000..e810abf --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/cl_init.lua @@ -0,0 +1,73 @@ +include("shared.lua") + +local materialBackground = Material("materials/medical_background_white.jpg") +local body1 = Material("materials/body.png") +local body2 = Material("materials/body_left_arm.png") +local body3 = Material("materials/body_right_arm.png") +local body4 = Material("materials/body_left_leg.png") +local body5 = Material("materials/body_right_leg.png") +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +local posx = 0-20 + +function ENT:Draw() + + self:DrawModel() + + local radio = self:GetParent() + if not radio then return end + + local angle = self:GetAngles() + local pos = self:GetPos() + angle:Right() * 27.9 + angle:Forward() * 6+ angle:Up() * 35.5 + local ang = self:GetAngles() + + ang:RotateAroundAxis(ang:Forward(), 0); + ang:RotateAroundAxis(ang:Right(),-90); + ang:RotateAroundAxis(ang:Up(), 90); + + cam.Start3D2D( pos, ang, 0.282) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( materialBackground ) + surface.DrawTexturedRect( 0 ,0, 852/4 - 10, 480/4 ) + + -- draw.RoundedBox( 0, 0,0,500/9,901/9, Color(255,255,255,255)) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( body1 ) + surface.DrawTexturedRect( (852/4 - 10)/2 - (500/9)/2 , (480/4)/2 - (901/9)/2 , 500/9,901/9 ) + + if radio.fracturesTable then + + if radio.fracturesTable[HITGROUP_LEFTLEG] then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( body4 ) + surface.DrawTexturedRect( (852/4 - 10)/2 - (500/9)/2 , (480/4)/2 - (901/9)/2 , 500/9,901/9) + end + if radio.fracturesTable[HITGROUP_RIGHTLEG] then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( body5 ) + surface.DrawTexturedRect( (852/4 - 10)/2 - (500/9)/2 , (480/4)/2 - (901/9)/2 , 500/9,901/9 ) + end + if radio.fracturesTable[HITGROUP_LEFTARM] then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( body2 ) + surface.DrawTexturedRect( (852/4 - 10)/2 - (500/9)/2 , (480/4)/2 - (901/9)/2 , 500/9,901/9) + end + if radio.fracturesTable[HITGROUP_RIGHTARM] then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( body3 ) + surface.DrawTexturedRect((852/4 - 10)/2 - (500/9)/2 , (480/4)/2 - (901/9)/2 , 500/9,901/9) + end + + end + + cam.End3D2D() + + +end + +function ENT:OnRemove() + if not BaseFrame then return end + if not ispanel( BaseFrame ) then return end + BaseFrame:Remove() +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/init.lua new file mode 100644 index 0000000..8b72745 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/init.lua @@ -0,0 +1,22 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + + self:SetModel("models/props_phx/rt_screen.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/shared.lua new file mode 100644 index 0000000..190c6fc --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/computer_screen_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Radio - Screen" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = false diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/cl_init.lua new file mode 100644 index 0000000..d82879d --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/cl_init.lua @@ -0,0 +1,8 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/init.lua new file mode 100644 index 0000000..c7bd8ee --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/init.lua @@ -0,0 +1,188 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/medical_stand/medical_stand.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + +end + +function ENT:AttachBloodBag( bag ) + + bag:SetPos( self:GetPos() + self:GetAngles():Up() * 72.2 + self:GetAngles():Right() * 10 + self:GetAngles():Forward() * 2.4 ) + bag:SetAngles( self:GetAngles() + Angle(90,0,90) ) + bag:SetParent( self ) + +end + +function ENT:Touch( ent ) + + if ent:GetClass() == "bloodbag_medicmod" then + + if IsValid(self.BloodBag) then return end + + if IsValid(ent.Stand) then return end + + self:AttachBloodBag( ent ) + + ent.Stand = self + self.BloodBag = ent + + return + + end + + if ent:GetClass() == "bed_medicmod" then + if IsValid( self.Bed ) then return end + if IsValid( ent.Stand ) then return end + if not IsValid( self.BloodBag ) then return end + if not IsValid( ent.ragdoll ) or not ent.ragdoll:IsDeathRagdoll() then return end + + ent.Stand = self + self.Bed = ent + + if timer.Exists( "StandBloodTime"..self:EntIndex() ) then + timer.Destroy( "StandBloodTime"..self:EntIndex() ) + end + + local rag = ent.ragdoll + local bed = ent + local ply = ent.ragdoll:GetOwner() + local bbag = self.BloodBag + + timer.Create("StandBloodTime"..self:EntIndex(), 1, 0, function() + + if not IsValid( rag ) or not IsValid( bed ) or not IsValid( ply ) or not IsValid( bbag ) or not IsValid( self ) then + if IsValid(self) then + self.Bed = nil + end + if IsValid( bed ) then + bed.Stand = nil + end + timer.Destroy("StandBloodTime"..self:EntIndex()) + return + end + + local dist = self:GetPos():Distance( bed:GetPos() ) + local health = ply:Health() + local tadd = 2 + local bblood = bbag:GetBlood() + + if dist > 200 then self.Bed = nil bed.Stand = nil timer.Destroy("StandBloodTime"..self:EntIndex()) return end + + if health < 0 then ply:SetHealth( 0 ) health = 0 end + + if health >= ( 100 - tadd ) then + if health >= 100 then + if not ply:GetHeartAttack() then + ply:MedicalRespawn() + + timer.Destroy("StandBloodTime"..self:EntIndex()) + self.Bed = nil + bed.Stand = nil + + if bbag:GetBlood() <= 0 then + + bbag:Remove() + + end + end + return + + elseif bblood >= tadd then + if not ply:GetHeartAttack() then + bbag:SetBlood( bblood - health - 100 ) + ply:SetHealth( 100 ) + ply:MedicalRespawn() + + timer.Destroy("StandBloodTime"..self:EntIndex()) + self.Bed = nil + bed.Stand = nil + + if bbag:GetBlood() <= 0 then + + bbag:Remove() + + end + end + + return + + else + ply:SetHealth( health + tadd ) + bbag:Remove() + + timer.Destroy("StandBloodTime"..self:EntIndex()) + self.Bed = nil + bed.Stand = nil + + return + + end + end + + if bblood - tadd <= 0 then + if bblood - tadd == 0 then + ply:SetHealth( health + tadd ) + bbag:Remove() + timer.Destroy("StandBloodTime"..self:EntIndex()) + self.Bed = nil + bed.Stand = nil + return + else + bbag:Remove() + timer.Destroy("StandBloodTime"..self:EntIndex()) + self.Bed = nil + bed.Stand = nil + return + end + end + + ply:SetHealth( health + tadd ) + bbag:SetBlood( bblood - tadd ) + end) + + end + +end + +function ENT:Think() + if SERVER then + if not IsValid(self.Bed) and IsValid(self.BloodBag) then + for _, ent in ipairs(ents.FindInSphere(self:GetPos(), 100)) do + if ent:GetClass() == "bed_medicmod" and not IsValid(ent.Stand) then + if IsValid(ent.ragdoll) and ent.ragdoll:IsDeathRagdoll() then + self:Touch(ent) + break + end + end + end + end + end + self:NextThink(CurTime() + 2) + return true +end + +function ENT:OnRemove() + + if IsValid( self.Bed ) then + self.Bed.Stand = nil + end + if IsValid( self.BloodBag ) then + self.BloodBag.Stand = nil + end + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/shared.lua new file mode 100644 index 0000000..ff8558e --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drip_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Drip" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/cl_init.lua new file mode 100644 index 0000000..914a751 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/cl_init.lua @@ -0,0 +1,29 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + + if not self:GetDrug() then return end + + local dist = LocalPlayer():GetPos():Distance(self:GetPos()) + + if dist > 500 then return end + + local angle = self.Entity:GetAngles() + + local position = self.Entity:GetPos() + angle:Forward() * 5 + angle:Up() * 20 + angle:Right() * 0 + + angle:RotateAroundAxis(angle:Forward(), 90); + angle:RotateAroundAxis(angle:Right(),-40); + angle:RotateAroundAxis(angle:Up(), 0); + + + cam.Start3D2D(position + angle:Right() * math.sin(CurTime() * 2), angle, 0.2) + + draw.SimpleTextOutlined(self:GetDrug(), "MedicModFont15" ,-7,0, Color(255,255,255,255), 1, 1, 0.5, Color(0,0,0,255)) + + cam.End3D2D() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/init.lua new file mode 100644 index 0000000..1d02be1 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/init.lua @@ -0,0 +1,78 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/drug/drug.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + + self:SetDrug(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + + self.NextTouch = 0 + +end + +function ENT:Touch( ent ) + + if self.NextTouch > CurTime() then return end + + self.NextTouch = CurTime() + 1 + + if ent:GetClass() != "beaker_medicmod" then return end + + if self:GetDrug() != ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then return end + + local drug + + for k, v in pairs( ConfigurationMedicMod.Drugs ) do + drug = k + for product, val in pairs( ConfigurationMedicMod.Drugs[k] ) do + if product == "func" or product == "price" or product == ent:GetProduct1() or product == ent:GetProduct2() or product == ent:GetProduct3() then + continue + else + drug = nil + break + end + end + + if drug then break end + + end + + if not drug then return end + + self:SetDrug( drug ) + ent:SetProduct1(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + ent:SetProduct2(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + ent:SetProduct3(ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language]) + +end + +function ENT:Use(a, c) + + if self:GetDrug() == ConfigurationMedicMod.Sentences["Empty"][ConfigurationMedicMod.Language] then return end + + ConfigurationMedicMod.Drugs[self:GetDrug()].func( c ) + + self:Remove() + +end + +function ENT:OnRemove() + if self.DrugSpawner and IsValid( self.DrugSpawner ) then + local drugsspawned = self.DrugSpawner.DrugsSpawned or 1 + self.DrugSpawner.DrugsSpawned = drugsspawned - 1 or 0 + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/shared.lua new file mode 100644 index 0000000..240ab3e --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/drug_medicmod/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Drug" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "Drug") +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/cl_init.lua new file mode 100644 index 0000000..9832e99 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/cl_init.lua @@ -0,0 +1,7 @@ +include("shared.lua") + +function ENT:Draw() + + --self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/init.lua new file mode 100644 index 0000000..0e56566 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/init.lua @@ -0,0 +1,60 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + + self:SetModel("models/props_junk/PopCan01a.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + +end + +function ENT:Touch( ent ) + + if ent:GetClass() != "bed_medicmod" then return end + + local bed = ent + local elec = self.Elec + + if not elec or not IsValid(elec) then return end + + if not bed.ragdoll or not IsValid( bed.ragdoll ) then return end + + if not IsValid( bed.ragdoll:GetOwner() ) then return end + + local ragdoll = bed.ragdoll + local ply = ragdoll:GetOwner() + local rope = self + + local bone = ragdoll:LookupBone("ValveBiped.Bip01_Spine4") + + if not bone then return end + + local pos, ang = ragdoll:GetBonePosition( bone ) + + rope:SetPos( pos + ang:Right() * -4 ) + rope:SetParent( ragdoll ) + + ragdoll.Rope = rope + + elec:SetPatient( ply ) + +end + +function ENT:OnRemove() + if IsValid( self.Elec ) then + self.Elec:Remove() + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/shared.lua new file mode 100644 index 0000000..0b12c18 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_att_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Electrocardiogram - A" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = false diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/cl_init.lua new file mode 100644 index 0000000..e15222a --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/cl_init.lua @@ -0,0 +1,59 @@ +include("shared.lua") + +local matRate1 = Material("materials/heart_rate2.png") +local matRate2 = Material("materials/heart_rate1.png") +local matRate3 = Material("materials/heart_rate3.png") +local matWhiteBar = Material("materials/whitebar.png") + +local matrate = matRate1 + +local posx = 0-20 + +function ENT:Draw() + + self:DrawModel() + + + if IsValid(self:GetPatient()) then + + if self:GetPatient():GetHeartAttack() then + matrate = matRate1 + elseif self:GetPatient():Stable() then + matrate = matRate2 + else + matrate = matRate3 + end + + local angle = self:GetAngles() + local pos = self:GetPos() + angle:Right() * 5 + angle:Forward() * 7.5+ angle:Up() * 2.5 + local ang = self:GetAngles() + + ang:RotateAroundAxis(ang:Forward(), 0); + ang:RotateAroundAxis(ang:Right(),-80); + ang:RotateAroundAxis(ang:Up(), 90); + + cam.Start3D2D( pos, ang, 0.01) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( matWhiteBar ) + surface.DrawTexturedRect( posx ,0, 20, 380 ) + + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( matrate ) + surface.DrawTexturedRect( 0,0, 490, 380 ) + cam.End3D2D() + + if posx > 380-20 then + posx = 0-20 + else + posx = posx + 1 + end + + end + +end + +function ENT:OnRemove() + if not BaseFrame then return end + if not ispanel( BaseFrame ) then return end + BaseFrame:Remove() +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/init.lua new file mode 100644 index 0000000..7ca443b --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/init.lua @@ -0,0 +1,35 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/electrocardiogram/electrocardiogram.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + -- self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + + local ent = ents.Create("electrocardiogram_att_medicmod") + ent:SetPos( self:GetPos() ) + ent:Spawn() + + local const, rope = constraint.Rope( self, ent, 0,0, Vector(0,6.5,0), Vector(0,0,0), 30, 0, 0, 1, "cable/cable_lit", true ) + + ent.Elec = self + self.Att = ent +end + +function ENT:OnRemove() + if IsValid( self.Att ) then + self.Att:Remove() + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/shared.lua new file mode 100644 index 0000000..6fd6b87 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/electrocardiogram_medicmod/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Electrocardiogram" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "Patient") +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/cl_init.lua new file mode 100644 index 0000000..efc7f2b --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/cl_init.lua @@ -0,0 +1,7 @@ +include("shared.lua") + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/init.lua new file mode 100644 index 0000000..ce2bc26 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/init.lua @@ -0,0 +1,65 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/firstaidkit/firstaidkit.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + + self.Content = { + ["bandage"] = 3, + ["syringe_antidote"] = 1, + ["syringe_morphine"] = 2, + } +end + +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +function ENT:Use( activator, ply ) + + if ply:HasWeapon("first_aid_kit") then ply:MedicNotif(sentences["You already carry a medical kit on you"][lang],5) return end + + ply:Give("first_aid_kit") + ply:SelectWeapon("first_aid_kit") + + local weap = ply:GetWeapon("first_aid_kit") + weap:SetBandage( self.Content["bandage"] ) + weap:SetAntidote( self.Content["syringe_antidote"] ) + weap:SetMorphine( self.Content["syringe_morphine"] ) + + self:Remove() + +end + +function ENT:Touch( ent ) + + self.nexttouch = self.nexttouch or 0 + + if self.nexttouch > CurTime() then return end + + self.nexttouch = CurTime() + 0.5 + + if ent:GetClass() != "spawned_weapon" then return end + + local content = ent:GetWeaponClass() + + if not self.Content[content] then return end + + self.Content[content] = self.Content[content] + 1 + + ent:Remove() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/shared.lua new file mode 100644 index 0000000..491e79e --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/firstaidkit_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "First Aid Kit" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/cl_init.lua new file mode 100644 index 0000000..d82879d --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/cl_init.lua @@ -0,0 +1,8 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/init.lua new file mode 100644 index 0000000..08acd57 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/init.lua @@ -0,0 +1,70 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/mural_defib/mural_defib.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + -- self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end +end + +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +function ENT:Think() + + if not IsValid(self.User) then return end + + if self.User:GetPos():DistToSqr( self:GetPos() ) > 62500 then + + local ply = self.User + + ply:GetActiveWeapon():Remove() + ply.CantSwitchWeapon = false + self.User = NULL + self.Used = false + constraint.RemoveConstraints( ply, "Rope" ) + + end + +end + +function ENT:Use( a, c ) + if self.Used then + if c == self.User then + + c:StripWeapon("defibrillator") + + constraint.RemoveConstraints( c, "Rope" ) + + c.CantSwitchWeapon = false + self.User = NULL + self.Used = false + + end + else + + if c:HasWeapon("defibrillator") then c:MedicNotif(sentences["You already have a defibrillator on you"][lang]) return end + + c:Give("defibrillator") + c:SelectWeapon("defibrillator") + + constraint.Rope( self, c, 0, 0 , Vector(0,0,0), Vector(0,0,50), 50, 0, 0, 2, "cable/cable2", false ) + + c.CantSwitchWeapon = true + self.User = c + self.Used = true + + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/shared.lua new file mode 100644 index 0000000..9656bcf --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/mural_defib_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Mural defibrillator" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/cl_init.lua new file mode 100644 index 0000000..a6c9f3e --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/cl_init.lua @@ -0,0 +1,31 @@ +include( "shared.lua" ) + +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +function ENT:Draw() + + self:DrawModel() + + local dist = LocalPlayer():GetPos():Distance(self:GetPos()) + + if dist > 500 then return end + + local ang = Angle(0, LocalPlayer():EyeAngles().y-90, 90) + + local angle = self.Entity:GetAngles() + + local position = self.Entity:GetPos()+Vector(0,0,0) + + angle:RotateAroundAxis(angle:Forward(), 0); + angle:RotateAroundAxis(angle:Right(),0); + angle:RotateAroundAxis(angle:Up(), 90); + + cam.Start3D2D(position+angle:Right()*0+angle:Up()*( math.sin(CurTime() * 2) * 2.5 + 78 )+angle:Forward()*0, ang, 0.1) + + draw.RoundedBox( 5, -150/2, -25, 150, 50, Color(40,100,170, 500-dist ) ) + draw.SimpleTextOutlined( sentences["Medic"][lang], "Trebuchet24" ,0,0, Color(255,255,255,500-dist), 1, 1, 1, Color(0,0,0)) + + cam.End3D2D() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/init.lua new file mode 100644 index 0000000..243c675 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/init.lua @@ -0,0 +1,38 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + + self:SetModel( "models/Kleiner.mdl" ) + self:SetHullType( HULL_HUMAN ) + self:SetHullSizeNormal() + self:SetNPCState( NPC_STATE_SCRIPT ) + self:SetSolid( SOLID_BBOX ) + self:CapabilitiesAdd( CAP_ANIMATEDFACE || CAP_TURN_HEAD ) + self:SetUseType( SIMPLE_USE ) + self:DropToFloor() + self.nextClick = CurTime() + 1 + self:SetMaxYawSpeed( 90 ) + +end + +function ENT:AcceptInput( event, a, p ) + + if( event == "Use" && p:IsPlayer() && self.nextClick < CurTime() ) then + + self.nextClick = CurTime() + 2 + + if not p.Fractures then + p.Fractures = {} + end + + net.Start("MedicMod.MedicMenu") + net.WriteEntity( self ) + net.WriteTable( p.Fractures ) + net.Send( p ) + + end + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/shared.lua new file mode 100644 index 0000000..5d527fd --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_health_seller_medicmod/shared.lua @@ -0,0 +1,7 @@ +ENT.Type = "ai" +ENT.Base = "base_ai" +ENT.AutomaticFrameAdvance = true +ENT.Author = "Venatuss" +ENT.Spawnable = true +ENT.Category = "Medic Mod" +ENT.PrintName = "Medic NPC" diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/cl_init.lua new file mode 100644 index 0000000..5e83da2 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/cl_init.lua @@ -0,0 +1,101 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() + + local pos = self:GetPos() + Vector(0, 0, 80) + local ang = LocalPlayer():EyeAngles() + ang:RotateAroundAxis(ang:Forward(), 90) + ang:RotateAroundAxis(ang:Right(), 90) + + if LocalPlayer():GetPos():DistToSqr(self:GetPos()) <= 60000 then + cam.Start3D2D(pos, ang, 0.05) + draw.SimpleTextOutlined("Торговец", "DermaLarge", 0, 0, Color(25, 175, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, color_black) + draw.SimpleTextOutlined("Медикаменты", "DermaDefault", 0, 30, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, color_black) + cam.End3D2D() + end +end + +net.Receive("MedicTerminalNPC_Menu", function() + local npc = net.ReadEntity() + + if not IsValid(npc) then return end + + local frame = vgui.Create("DFrame") + frame:SetTitle("Аптека") + frame:SetSize(700, 500) + frame:Center() + frame:MakePopup() + frame.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 240)) + draw.RoundedBox(0, 0, 0, w, 24, Color(20, 20, 20, 200)) + end + + local scroll = vgui.Create("DScrollPanel", frame) + scroll:Dock(FILL) + + local layout = vgui.Create("DIconLayout", scroll) + layout:Dock(FILL) + layout:SetSpaceX(10) + layout:SetSpaceY(10) + + local function AddItem(class, name, price, modelPath) + local pnl = layout:Add("DPanel") + pnl:SetSize(210, 160) + pnl.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(45, 45, 45, 200)) + draw.SimpleText(name, "DermaDefaultBold", w/2, 10, color_white, TEXT_ALIGN_CENTER) + if ix and ix.currency then + draw.SimpleText(ix.currency.Get(price), "DermaDefaultBold", w/2, 135, Color(100, 255, 100), TEXT_ALIGN_CENTER) + else + draw.SimpleText(price .. "$", "DermaDefaultBold", w/2, 135, Color(100, 255, 100), TEXT_ALIGN_CENTER) + end + end + + local mdl = vgui.Create("DModelPanel", pnl) + mdl:SetSize(100, 100) + mdl:SetPos(55, 25) + mdl:SetModel(modelPath or "models/error.mdl") + + if IsValid(mdl.Entity) then + local mn, mx = mdl.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)) + + mdl:SetFOV(45) + mdl:SetCamPos(Vector(size, size, size)) + mdl:SetLookAt((mn + mx) * 0.5) + end + + local btn = vgui.Create("DButton", pnl) + btn:SetSize(210, 160) + btn:SetText("") + btn.Paint = function(self, w, h) + if self:IsHovered() then + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 15)) + end + end + btn.DoClick = function() + net.Start("MedicTerminalNPC_Buy") + net.WriteEntity(npc) + net.WriteString(class) + net.SendToServer() + end + end + + local items = npc:GetShopItems() + + if items then + -- Сортировка по алфавиту имен + local sortedKeys = {} + for k, v in pairs(items) do table.insert(sortedKeys, k) end + table.sort(sortedKeys, function(a, b) return (items[a].name or "") < (items[b].name or "") end) + + for _, class in ipairs(sortedKeys) do + local data = items[class] + AddItem(class, data.name, data.price, data.model) + end + end +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/init.lua new file mode 100644 index 0000000..4b47bb8 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/init.lua @@ -0,0 +1,108 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("MedicTerminalNPC_Menu") +util.AddNetworkString("MedicTerminalNPC_Buy") + +function ENT:Initialize() + self:SetModel("models/Humans/Group03m/Female_01.mdl") + self:PhysicsInit(SOLID_BBOX) + self:SetSolid(SOLID_BBOX) + self:SetMoveType(MOVETYPE_STEP) + self:SetCollisionBounds(Vector(-16, -16, 0), Vector(16, 16, 72)) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:EnableMotion(false) + end + + -- Анимация простоя (как у ix_shop_npc) + local sequence = self:LookupSequence("idle_subtle") + if sequence > 0 then + self:ResetSequence(sequence) + end +end + +function ENT:Think() + self:NextThink(CurTime()) + return true +end + +function ENT:Use(activator, caller) + if not IsValid(activator) or not activator:IsPlayer() then return end + + net.Start("MedicTerminalNPC_Menu") + net.WriteEntity(self) + net.Send(activator) +end + +net.Receive("MedicTerminalNPC_Buy", function(len, ply) + local npc = net.ReadEntity() + local itemClass = net.ReadString() + + if not IsValid(npc) or ply:GetPos():DistToSqr(npc:GetPos()) > 60000 then return end + + if not ix then return end -- Using Helix + local char = ply:GetCharacter() + if not char then return end + + local items = npc:GetShopItems() + local itemInfo = items[itemClass] + + if not itemInfo then return end + + local price = itemInfo.price + local name = itemInfo.name + local isWeapon = itemInfo.isWeapon + + if price <= 0 then return end + + if not char:HasMoney(price) then + ply:Notify("У вас недостаточно средств!") + return + end + + if isWeapon then + if ply:HasWeapon(itemClass) then + ply:Notify("У вас уже есть " .. name .. "!") + return + end + + char:TakeMoney(price) + ply:Give(itemClass) + + -- Custom behavior for first aid kit as defined in MedicMod + timer.Simple(0.1, function() + if not IsValid(ply) then return end + local weap = ply:GetWeapon(itemClass) + if IsValid(weap) and itemClass == "first_aid_kit" then + if weap.SetBandage then weap:SetBandage(3) end + if weap.SetAntidote then weap:SetAntidote(1) end + if weap.SetMorphine then weap:SetMorphine(2) end + end + end) + + ply:Notify("Вы купили " .. name .. " за " .. ix.currency.Get(price)) + + else + -- It's an entity + char:TakeMoney(price) + + local ent = ents.Create(itemClass) + if IsValid(ent) then + local spawnPos = npc:GetPos() + npc:GetForward() * 50 + Vector(0, 0, 10) + ent:SetPos(spawnPos) + ent:SetAngles(npc:GetAngles()) + ent:Spawn() + ent:Activate() + + if CPPI then ent:CPPISetOwner(ply) end + + ply:Notify("Вы купили " .. name .. " за " .. ix.currency.Get(price) .. ". Предмет появился рядом с продавцом.") + end + end +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/shared.lua new file mode 100644 index 0000000..a65df0a --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/npc_medic_terminal/shared.lua @@ -0,0 +1,69 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Торговец Медикаментами" +ENT.Author = "MedicMod" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Category = "Medic Mod" +ENT.AutomaticFrameAdvance = true + +-- Кастомный ассортимент для этого NPC +ENT.ShopItems = { + -- Энтити: + ["bloodbag_medicmod"] = { name = "Пакет крови", price = 500, model = "models/medicmod/bloodbag/bloodbag.mdl", isWeapon = false }, + + -- Оружие (SWEP): + ["syringe_antidote"] = { name = "Шприц-Антидот", price = 50, model = "models/medicmod/syringe/w_syringe.mdl", isWeapon = true }, + ["analysis_notebook"] = { name = "Блокнот анализов", price = 100, model = "models/props_lab/clipboard.mdl", isWeapon = true }, + ["syringe_morphine"] = { name = "Шприц с морфием", price = 150, model = "models/medicmod/syringe/w_syringe.mdl", isWeapon = true }, +} + +function ENT:GetShopItems() + local items = {} + + -- Список классов, которые мы не хотим показывать в магазине + local ignoreList = { + ["drug_medicmod"] = true, -- Empty drug jar + ["beaker_medicmod"] = true, -- Empty beaker + ["drip_medicmod"] = true, -- drip + ["defibrillator"] = true, -- defibrillator + ["first_aid_kit"] = true, -- first aid kit (SWEP) + ["firstaidkit_medicmod"] = true,-- first aid kit (ENT) + ["bandage"] = true, -- повязка + ["syringe_poison"] = true, + } + + -- Добавляем из конфигов мода + if ConfigurationMedicMod then + if ConfigurationMedicMod.MedicShopEntities then + for k, v in pairs(ConfigurationMedicMod.MedicShopEntities) do + if not ignoreList[k] then + items[k] = { name = v.name, price = v.price, model = v.model, isWeapon = false } + end + end + end + if ConfigurationMedicMod.MedicShopWeapons then + for k, v in pairs(ConfigurationMedicMod.MedicShopWeapons) do + if not ignoreList[k] then + items[k] = { name = v.name, price = v.price, model = v.model, isWeapon = true } + end + end + end + if ConfigurationMedicMod.Entities then + for _, v in pairs(ConfigurationMedicMod.Entities) do + if not ignoreList[v.ent] then + items[v.ent] = { name = v.name, price = v.price, model = v.mdl, isWeapon = true } + end + end + end + end + + -- Добавляем/перезаписываем кастомными предметами + if self.ShopItems then + for k, v in pairs(self.ShopItems) do + items[k] = v + end + end + + return items +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/cl_init.lua new file mode 100644 index 0000000..e3574d8 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/cl_init.lua @@ -0,0 +1,7 @@ +include("shared.lua") + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/init.lua new file mode 100644 index 0000000..7a98120 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/init.lua @@ -0,0 +1,95 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/radio/radio.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + -- self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end + + local screen = ents.Create("computer_screen_medicmod") + screen:SetPos( self:GetPos() + self:GetAngles():Forward() * -35 + self:GetAngles():Up() * 70 ) + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Forward(), 0); + ang:RotateAroundAxis(ang:Right(),0); + ang:RotateAroundAxis(ang:Up(), 180); + screen:SetAngles( ang ) + screen:Spawn() + screen:SetParent( self ) + + self.Screen = screen + +end + +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +function ENT:ScanPlayer( ply ) + + local pos = ply:GetPos() + local ang = ply:GetAngles() + + local hbed = ents.Create("prop_vehicle_prisoner_pod") + hbed:SetModel("models/vehicles/prisoner_pod_inner.mdl") + hbed:SetPos( self:GetPos() + self:GetAngles():Up() * 40 + self:GetAngles():Right() * -35+ self:GetAngles():Forward() * 20 ) + hbed:SetAngles( self:GetAngles() + Angle(-90,0,90)) + hbed:SetNoDraw(true) + hbed:SetCollisionGroup(COLLISION_GROUP_WORLD) + hbed:SetSolid(SOLID_NONE) + hbed.locked = true + + self.ragdoll = hbed + + ply:EnterVehicle(hbed) + ply:MedicNotif( sentences["You are being scanned"][lang], ConfigurationMedicMod.ScanRadioTime) + + ply:Freeze( true ) + + timer.Simple( ConfigurationMedicMod.ScanRadioTime, function() + + if not IsValid(ply) or not IsValid(self) or not IsValid(hbed) then return end + + ply:Freeze( false ) + + ply:ExitVehicle(hbed) + + hbed:Remove() + ply:SetPos( pos ) + ply:SetEyeAngles( ang ) + + self.ragdoll = nil + + if not ply.Fractures then return end + + net.Start("MedicMod.ScanRadio") + net.WriteEntity( self ) + net.WriteTable( ply.Fractures ) + net.Broadcast() + + end) +end + +function ENT:Use( a, c ) + + if self.ragdoll then return end + + self:ScanPlayer( c ) + +end + +function ENT:OnRemove() + if IsValid( self.ragdoll ) then + self.ragdoll:Remove() + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/shared.lua new file mode 100644 index 0000000..11531ae --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/radio_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Radio" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/cl_init.lua new file mode 100644 index 0000000..d82879d --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/cl_init.lua @@ -0,0 +1,8 @@ +include("shared.lua") + + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/init.lua new file mode 100644 index 0000000..714acb4 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/init.lua @@ -0,0 +1,130 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/stretcher/stretcher.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:StartMotionController() + self.ShadowParams = {} + self:SetPos(self:GetPos() + Vector(0,0,50)) +end + +function ENT:PhysicsSimulate( phys, deltatime ) + + phys:Wake() + + self.ShadowParams.secondstoarrive = 0.1 + self.ShadowParams.angle = Angle( 0, self:GetAngles().yaw, 0 ) + self.ShadowParams.maxangular = 5000 + self.ShadowParams.maxangulardamp = 10000 + + phys:ComputeShadowControl( self.ShadowParams ) + +end + +local boneAngles = { + [1] = { + bone = "ValveBiped.Bip01_R_Foot", + ang = Angle(0,0,0) + }, + [2] = { + bone = "ValveBiped.Bip01_L_Foot", + ang = Angle(-0,0,0) + }, + [3] = { + bone = "ValveBiped.Bip01_R_ForeArm", + ang = Angle(-20,0,0) + }, + [4] = { + bone = "ValveBiped.Bip01_L_ForeArm", + ang = Angle(20,0,0) + }, + [5] = { + bone = "ValveBiped.Bip01_L_UpperArm", + ang = Angle(20,-0,0) + }, + [6] = { + bone = "ValveBiped.Bip01_R_UpperArm", + ang = Angle(-20,-0,0) + }, + [7] = { + bone = "ValveBiped.Bip01_Head1", + ang = Angle(20,0,45) + }, +} + + +function ENT:CreateDeathRagdoll( ply ) + + local ragdoll = ents.Create("prop_physics") + ragdoll:SetPos(self:GetPos()+self:GetAngles():Up() * 12+self:GetAngles():Right()*35) + ragdoll:SetAngles(Angle(-90,self:GetAngles().Yaw,-90)) + ragdoll:SetModel(ply:GetModel()) + ragdoll:Spawn() + ragdoll:SetParent(self) + for _, inf in pairs( boneAngles ) do + local bone = ragdoll:LookupBone(inf.bone) + if bone then + ragdoll:ManipulateBoneAngles(bone, inf.ang) + end + end + + ragdoll:SetOwner( ply ) + ragdoll:SetDeathRagdoll( true ) + + -- Set the view on the ragdoll + ply:Spectate( OBS_MODE_CHASE ) + ply:SpectateEntity( ragdoll ) + + return ragdoll + +end + + +function ENT:Touch( ent ) + + if ent:GetClass() == "bed_medicmod" then return end + + local rag + + if ent:IsDeathRagdoll() then + rag = ent + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() then + rag = ent.ragdoll + elseif ent:IsVehicle() && ConfigurationMedicMod.Vehicles[ent:GetModel()] && not IsValid(ent.Stretcher) then + if self:GetPos():Distance(ent:LocalToWorld(ConfigurationMedicMod.Vehicles[ent:GetModel()].backPos)) > 150 then return end + self:SetPos(ent:LocalToWorld(ConfigurationMedicMod.Vehicles[ent:GetModel()].stretcherPos)) + self:SetAngles(ent:LocalToWorldAngles(ConfigurationMedicMod.Vehicles[ent:GetModel()].stretcherAngle)) + self:SetParent( ent ) + + ent.Stretcher = self + ent.SpawnedStretcher = self + + if not ConfigurationMedicMod.Vehicles[ent:GetModel()].drawStretcher then + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:SetColor( Color(0,0,0,0) ) + end + + return + end + + if self.ragdoll && IsValid( self.ragdoll ) then return end + + if not IsValid( rag ) then return end + + if not IsValid( rag:GetOwner() ) then return end + + local ply = rag:GetOwner() + + self.ragdoll = self:CreateDeathRagdoll( ply ) + + ply.DeathRagdoll = self.ragdoll + + rag:Remove() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/shared.lua new file mode 100644 index 0000000..a6af584 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/stretcher_medicmod/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Stretcher" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = true diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/cl_init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/cl_init.lua new file mode 100644 index 0000000..409c651 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/cl_init.lua @@ -0,0 +1,29 @@ +include("shared.lua") + +function ENT:Draw() + + self:DrawModel() + + if not self:GetProduct() then return end + if not ConfigurationMedicMod.Reagents[self:GetProduct()] then return end + + local dist = LocalPlayer():GetPos():Distance(self:GetPos()) + + if dist > 500 then return end + + local angle = self.Entity:GetAngles() + + local position = self.Entity:GetPos() + angle:Forward() * -2 + + angle:RotateAroundAxis(angle:Forward(), 90); + angle:RotateAroundAxis(angle:Right(),90); + angle:RotateAroundAxis(angle:Up(), 0); + + cam.Start3D2D(position, angle, 0.2) + + draw.SimpleTextOutlined(self:GetProduct(), "MedicModFont15" ,0,-60, Color(255,255,255,255), 1, 1, 0.5, Color(0,0,0,255)) + draw.RoundedBox( 3, -3,-30, 5, 25, ConfigurationMedicMod.Reagents[self:GetProduct()].color ) + + cam.End3D2D() + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/init.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/init.lua new file mode 100644 index 0000000..1df41a6 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/init.lua @@ -0,0 +1,27 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/medicmod/test_tube/testtube.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:DropToFloor() + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + + phys:Wake() + + end +end + +function ENT:OnRemove() + if self.TestTubeSpawner and IsValid( self.TestTubeSpawner ) then + local testtubesspawned = self.TestTubeSpawner.TestTubesSpawned or 1 + self.TestTubeSpawner.TestTubesSpawned = testtubesspawned - 1 or 0 + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/shared.lua new file mode 100644 index 0000000..21f5cb6 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/entities/test_tube_medicmod/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Test tube" +ENT.Category = "Medic Mod" +ENT.Author = "Venatuss" +ENT.Spawnable = false + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "Product") +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/3d2dvgui.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/3d2dvgui.lua new file mode 100644 index 0000000..90bc0cd --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/3d2dvgui.lua @@ -0,0 +1,270 @@ +--[[ + +3D2D VGUI Wrapper +Copyright (c) 2015-2017 Alexander Overvoorde, Matt Stevens + +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 return end + +local origin = Vector(0, 0, 0) +local angle = Angle(0, 0, 0) +local normal = Vector(0, 0, 0) +local scale = 0 +local maxrange = 0 + +-- Helper functions + +local function getCursorPos() + local p = util.IntersectRayWithPlane(LocalPlayer():EyePos(), LocalPlayer():GetAimVector(), origin, normal) + + -- if there wasn't an intersection, don't calculate anything. + if not p then return end + if WorldToLocal(LocalPlayer():GetShootPos(), Angle(0,0,0), origin, angle).z < 0 then return end + + if maxrange > 0 then + if p:Distance(LocalPlayer():EyePos()) > maxrange then + return + end + end + + local pos = WorldToLocal(p, Angle(0,0,0), origin, angle) + + return pos.x, -pos.y +end + +local function getParents(pnl) + local parents = {} + local parent = pnl:GetParent() + while parent do + table.insert(parents, parent) + parent = parent:GetParent() + end + return parents +end + +local function absolutePanelPos(pnl) + local x, y = pnl:GetPos() + local parents = getParents(pnl) + + for _, parent in ipairs(parents) do + local px, py = parent:GetPos() + x = x + px + y = y + py + end + + return x, y +end + +local function pointInsidePanel(pnl, x, y) + local px, py = absolutePanelPos(pnl) + local sx, sy = pnl:GetSize() + + if not x or not y then return end + + x = x / scale + y = y / scale + + return x >= px and y >= py and x <= px + sx and y <= py + sy --pnl:IsVisible() and x >= px and y >= py and x <= px + sx and y <= py + sy +end + +-- Input + +local inputWindows = {} +local usedpanel = {} + +local function isMouseOver(pnl) + return pointInsidePanel(pnl, getCursorPos()) +end + +local function postPanelEvent(pnl, event, ...) + if not IsValid(pnl) or not pointInsidePanel(pnl, getCursorPos()) then return false end -- not pnl:IsVisible() or not pointInsidePanel(pnl, getCursorPos()) then return false end + + local handled = false + + for i, child in pairs(table.Reverse(pnl:GetChildren())) do + if postPanelEvent(child, event, ...) then + handled = true + break + end + end + + if not handled and pnl[event] then + pnl[event](pnl, ...) + usedpanel[pnl] = {...} + return true + else + return false + end +end + +-- Always have issue, but less +local function checkHover(pnl, x, y, found) + if not (x and y) then + x, y = getCursorPos() + end + + local validchild = false + for c, child in pairs(table.Reverse(pnl:GetChildren())) do + local check = checkHover(child, x, y, found or validchild) + + if check then + validchild = true + end + end + + if found then + if pnl.Hovered then + pnl.Hovered = false + if pnl.OnCursorExited then pnl:OnCursorExited() end + end + else + if not validchild and pointInsidePanel(pnl, x, y) then + pnl.Hovered = true + if pnl.OnCursorEntered then pnl:OnCursorEntered() end + + return true + else + pnl.Hovered = false + if pnl.OnCursorExited then pnl:OnCursorExited() end + end + end + + return false +end + +-- Mouse input + +hook.Add("KeyPress", "VGUI3D2DMousePress", function(_, key) + if key == IN_USE then + for pnl in pairs(inputWindows) do + if IsValid(pnl) then + origin = pnl.Origin + scale = pnl.Scale + angle = pnl.Angle + normal = pnl.Normal + + local key = input.IsKeyDown(KEY_LSHIFT) and MOUSE_RIGHT or MOUSE_LEFT + + postPanelEvent(pnl, "OnMousePressed", key) + end + end + end +end) + +hook.Add("KeyRelease", "VGUI3D2DMouseRelease", function(_, key) + if key == IN_USE then + for pnl, key in pairs(usedpanel) do + if IsValid(pnl) then + origin = pnl.Origin + scale = pnl.Scale + angle = pnl.Angle + normal = pnl.Normal + + if pnl["OnMouseReleased"] then + pnl["OnMouseReleased"](pnl, key[1]) + end + + usedpanel[pnl] = nil + end + end + end +end) + +function vgui.Start3D2D(pos, ang, res) + origin = pos + scale = res + angle = ang + normal = ang:Up() + maxrange = 0 + + cam.Start3D2D(pos, ang, res) +end + +function vgui.MaxRange3D2D(range) + maxrange = isnumber(range) and range or 0 +end + +function vgui.IsPointingPanel(pnl) + origin = pnl.Origin + scale = pnl.Scale + angle = pnl.Angle + normal = pnl.Normal + + return pointInsidePanel(pnl, getCursorPos()) +end + +local Panel = FindMetaTable("Panel") +function Panel:Paint3D2D() + + if not self:IsValid() then return end + + -- Add it to the list of windows to receive input + inputWindows[self] = true + + -- Override gui.MouseX and gui.MouseY for certain stuff + local oldMouseX = gui.MouseX + local oldMouseY = gui.MouseY + local cx, cy = getCursorPos() + + function gui.MouseX() + return (cx or 0) / scale + end + function gui.MouseY() + return (cy or 0) / scale + end + + -- Override think of DFrame's to correct the mouse pos by changing the active orientation + if self.Think then + if not self.OThink then + self.OThink = self.Think + + self.Think = function() + origin = self.Origin + scale = self.Scale + angle = self.Angle + normal = self.Normal + + self:OThink() + end + end + end + + -- Update the hover state of controls + local _, tab = checkHover(self) + + -- Store the orientation of the window to calculate the position outside the render loop + self.Origin = origin + self.Scale = scale + self.Angle = angle + self.Normal = normal + + -- Draw it manually + self:SetPaintedManually(false) + self:PaintManual() + self:SetPaintedManually(true) + + gui.MouseX = oldMouseX + gui.MouseY = oldMouseY +end + +function vgui.End3D2D() + cam.End3D2D() +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_effects.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_effects.lua new file mode 100644 index 0000000..dfac961 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_effects.lua @@ -0,0 +1,44 @@ +local tabPoisonEffect = { + [ "$pp_colour_addr" ] = 0.3, + [ "$pp_colour_addg" ] = 0.3, + [ "$pp_colour_addb" ] = 0, + [ "$pp_colour_brightness" ] = 0, + [ "$pp_colour_contrast" ] = 1, + [ "$pp_colour_colour" ] = 0.8, + [ "$pp_colour_mulr" ] = 0, + [ "$pp_colour_mulg" ] = 0, + [ "$pp_colour_mulb" ] = 0 + } + +local tabBleedingEffect = { + [ "$pp_colour_addr" ] = 0.0, + [ "$pp_colour_addg" ] = 0.0, + [ "$pp_colour_addb" ] = 0.0, + [ "$pp_colour_brightness" ] = 0, + [ "$pp_colour_contrast" ] = 1, + [ "$pp_colour_colour" ] = 1, + [ "$pp_colour_mulr" ] = 0, + [ "$pp_colour_mulg" ] = 0, + [ "$pp_colour_mulb" ] = 0 + } + +function MedicMod.BleedingEffect() + + if LocalPlayer():IsMorphine() then return end + + tabBleedingEffect[ "$pp_colour_addr" ] = math.abs(math.sin( CurTime() * 2 )) * 0.2 + + DrawColorModify( tabBleedingEffect ) + +end + + +function MedicMod.PoisonEffect() + + if LocalPlayer():IsMorphine() then return end + + DrawColorModify( tabPoisonEffect ) + DrawMotionBlur( 0.1, 0.7, 0.05 ) + +end + diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_font.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_font.lua new file mode 100644 index 0000000..613cd6b --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_font.lua @@ -0,0 +1,158 @@ +surface.CreateFont( "MedicModFont100", { + font = "Arial", + size = 100, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}); + +surface.CreateFont( "MedicModFont30", { + font = "Arial", + size = 30, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}); + +surface.CreateFont( "MedicModFont20", { + font = "Arial", + size = 20, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}); + +surface.CreateFont( "MedicModFont17", { + font = "Arial", + size = 17, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}); + +surface.CreateFont( "MedicModFont15", { + font = "Arial", + size = 15, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}); + +surface.CreateFont( "MedicModFont12", { + font = "Arial", + size = 12, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}); + +surface.CreateFont( "MedicModFont10", { + font = "Arial", + size = 10, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}); + +surface.CreateFont( "WrittenMedicMod80", { + font = "Written on His Hands", + size = 80, + weight = 1000 +} ) + +surface.CreateFont( "WrittenMedicMod40", { + font = "Written on His Hands", + size = 40, + weight = 1000 +} ) + + +surface.CreateFont( "WrittenMedicMod35", { + font = "Written on His Hands", + size = 35, + weight = 1000 +} ) + +surface.CreateFont("Aam::Title", { + + font = "Trebuchet24", + size = 36, + weight = 350 +}) + +surface.CreateFont("Aam::Button", { + + font = "Trebuchet24", + size = 24, + weight = 350 +}) + +surface.CreateFont("Aam::Normal", { + + font = "Trebuchet24", + size = 24, + weight = 300 +}) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_functions.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_functions.lua new file mode 100644 index 0000000..3612302 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_functions.lua @@ -0,0 +1,255 @@ +local meta = FindMetaTable( "Player" ) +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +function StartMedicAnimation( ply, id ) + + if not IsValid(ply) then return end + + if ply.mdlanim && IsValid( ply.mdlanim ) then print("model already exist, removed") ply.mdlanim:Remove() end + + if ply:GetNWString("MedicPlayerModel") then + + ply.mdlanim = ClientsideModel(ply:GetNWString("MedicPlayerModel")) + + if IsValid( ply.mdlanim ) then + ply.mdlanim:SetParent( ply ) + ply.mdlanim:AddEffects( EF_BONEMERGE ) + return ply.mdlanim + else + return false + end + end + +end + +function StopMedicAnimation( ply ) + if IsValid( ply.mdlanim ) && ply:GetMedicAnimation() == 0 then + ply.mdlanim:Remove() + end +end + +local Background = Material( "medic/terminal/background.png" ) + +local ArrowRight = Material( "medic/terminal/arrow_right.png" ) +local ArrowLeft = Material( "medic/terminal/arrow_left.png" ) + +function MedicMod.TerminalMenu( ent ) + + local ActiveItem = 1 + + /* MAIN FRAME */ + + local _MainFrame = vgui.Create( "DPanel" ) + _MainFrame:SetSize( 750, 500 ) + _MainFrame:Center() + _MainFrame:MakePopup() + + _MainFrame.Paint = function( pnl, w, h ) + + /* BACKGROUND */ + + surface.SetDrawColor( 255, 255, 255 ) + surface.SetMaterial( Background ) + surface.DrawTexturedRect( 0, 0, w, h ) + + draw.RoundedBox( 0, 0, 0, w, h, Color( 0, 0, 0, 225 )) + + /* TOP */ + + draw.RoundedBox( 0, 0, 0, w, h*0.2, Color( 255, 255, 255, 10 )) + + draw.DrawText( "Terminal", "MedicModFont17", w*0.5, h*0.065, Color( 255, 255, 255 ), 1) + + /* BOTTOM */ + + draw.DrawText( ConfigurationMedicMod.Entities[ActiveItem].price..ConfigurationMedicMod.MoneyUnit, "Aam::Normal", w*0.5, h*0.785, Color( 255, 255, 255 ), 1) + + draw.RoundedBox( 0, w*0.2, h*0.75, w*0.6, 2, Color( 255, 255, 255 )) + end + + /* SCROLL SYSTEM */ + + local ItemScrollPanel = vgui.Create("DScrollPanel", _MainFrame ) + ItemScrollPanel:SetSize( _MainFrame:GetWide()*0.6, _MainFrame:GetTall()*0.45 ) + ItemScrollPanel:SetPos( _MainFrame:GetWide()*0.2, _MainFrame:GetTall()*0.25 ) + + ItemScrollPanel:GetVBar().Paint = function() + end + + ItemScrollPanel:GetVBar().btnGrip.Paint = function() + end + + ItemScrollPanel:GetVBar().btnUp.Paint = function() + end + + ItemScrollPanel:GetVBar().btnDown.Paint = function() + end + + /* ITEM LIST */ + + local ItemsList = vgui.Create( "DIconLayout", ItemScrollPanel ) + ItemsList:SetSize( ItemScrollPanel:GetWide(), ItemScrollPanel:GetTall() ) + ItemsList:SetPos( 0, 0 ) + ItemsList:SetSpaceY( 0 ) + ItemsList:SetSpaceX( 0 ) + + ItemSlot = {} + + for k, v in pairs( ConfigurationMedicMod.Entities ) do + + ItemSlot[k] = vgui.Create( "DPanel", ItemsList ) + ItemSlot[k]:SetSize( ItemsList:GetWide(), ItemsList:GetTall() ) + + ItemSlot[k].Paint = function( pnl, w, h ) + + draw.DrawText( v.name, "MedicModFont17", w*0.5, h*0.1, Color( 255, 255, 255 ), 1) + + end + + ItemSlot[k].model = vgui.Create( "DModelPanel", ItemSlot[k] ) + ItemSlot[k].model:SetPos( 0, 100 ) + ItemSlot[k].model:SetSize( ItemsList:GetWide(), ItemsList:GetTall()-100 ) + ItemSlot[k].model:SetLookAt( Vector(0, 0, 0 ) ) + ItemSlot[k].model:SetCamPos( Vector( -50, 0, 30 ) ) + ItemSlot[k].model:SetModel( v.mdl ) + + end + + /* LEFT */ + + local _LeftArrow = vgui.Create( "DButton", _MainFrame ) + _LeftArrow:SetSize( 50, 50 ) + _LeftArrow:SetPos( _MainFrame:GetWide()*0.1, _MainFrame:GetTall()*0.4 ) + _LeftArrow:SetText("") + + _LeftArrow.Paint = function( pnl, w, h ) + + surface.SetDrawColor( 255, 255, 255 ) + surface.SetMaterial( ArrowLeft ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + + _LeftArrow.DoClick = function() + + if ActiveItem == 1 then return end + + ActiveItem = ActiveItem - 1 + + ItemScrollPanel:ScrollToChild(ItemSlot[ActiveItem]) + end + + /* RIGHT */ + + local _RightArrow = vgui.Create( "DButton", _MainFrame ) + _RightArrow:SetSize( 50, 50 ) + _RightArrow:SetPos( _MainFrame:GetWide()*0.9 - 50, _MainFrame:GetTall()*0.4 ) + _RightArrow:SetText("") + + _RightArrow.Paint = function( pnl, w, h ) + + surface.SetDrawColor( 255, 255, 255 ) + surface.SetMaterial( ArrowRight ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + + _RightArrow.DoClick = function() + + if ActiveItem == table.Count( ConfigurationMedicMod.Entities ) then return end + + ActiveItem = ActiveItem + 1 + + ItemScrollPanel:ScrollToChild(ItemSlot[ActiveItem]) + end + + /* BUY */ + + local _BuyButton = vgui.Create( "DButton", _MainFrame ) + _BuyButton:SetSize( _MainFrame:GetWide()*0.15, _MainFrame:GetTall()*0.0725 ) + _BuyButton:SetPos( _MainFrame:GetWide()*0.5 - ( _BuyButton:GetWide() / 2 ), _MainFrame:GetTall()*0.9 ) + _BuyButton:SetText("") + _BuyButton.Color = Color(200,200,200,255) + + _BuyButton.Paint = function( pnl, w, h ) + + local color = _BuyButton.Color + + draw.DrawText( sentences["Buy"][lang], "Aam::Button", w*0.5, h*0.1, color, 1) + + if _BuyButton:IsHovered() then + + local r = math.Clamp(color.r+10, 200, 255 ) + local g = math.Clamp(color.g+10, 200, 255 ) + local b = math.Clamp(color.b+10, 200, 255 ) + + _BuyButton.Color = Color(r,g,b,255) + + else + + local r = math.Clamp(color.r-5, 200, 255 ) + local g = math.Clamp(color.g-5, 200, 255 ) + local b = math.Clamp(color.b-5, 200, 255 ) + + _BuyButton.Color = Color(r,g,b,255) + + end + + draw.RoundedBox( 3, 0, 0, w, 2, color) + draw.RoundedBox( 3, 0, 0, 2, h, color) + draw.RoundedBox( 3, 0, h-2, w, 2, color) + draw.RoundedBox( 3, w-2, 0, 2, h, color) + end + + _BuyButton.DoClick = function() + + net.Start("MedicMod.BuyMedicEntity") + net.WriteInt( ActiveItem, 32 ) + net.WriteEntity( ent ) + net.SendToServer() + + _MainFrame:Remove() + end + + /* CLOSE BUTTON */ + + local _CloseButton = vgui.Create( "DButton", _MainFrame ) + _CloseButton:SetSize( _MainFrame:GetWide()*0.05, _MainFrame:GetTall()*0.0725 ) + _CloseButton:SetPos( _MainFrame:GetWide()*0.99 - ( _CloseButton:GetWide() ), _MainFrame:GetTall()*0.01 ) + _CloseButton:SetText("") + _CloseButton.Color = Color(200,200,200,255) + + _CloseButton.Paint = function( pnl, w, h ) + + local color = _CloseButton.Color + + draw.DrawText( "X", "Aam::Button", w*0.5, h*0.1, color, 1) + + if _CloseButton:IsHovered() then + + local r = math.Clamp(color.r+10, 200, 255 ) + local g = math.Clamp(color.g+10, 200, 255 ) + local b = math.Clamp(color.b+10, 200, 255 ) + + _CloseButton.Color = Color(r,g,b,255) + + else + + local r = math.Clamp(color.r-5, 200, 255 ) + local g = math.Clamp(color.g-5, 200, 255 ) + local b = math.Clamp(color.b-5, 200, 255 ) + + _CloseButton.Color = Color(r,g,b,255) + + end + + draw.RoundedBox( 3, 0, 0, w, 2, color) + draw.RoundedBox( 3, 0, 0, 2, h, color) + draw.RoundedBox( 3, 0, h-2, w, 2, color) + draw.RoundedBox( 3, w-2, 0, 2, h, color) + end + + _CloseButton.DoClick = function() + + _MainFrame:Remove() + end +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_hooks.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_hooks.lua new file mode 100644 index 0000000..97a2990 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_hooks.lua @@ -0,0 +1,123 @@ +hook.Add( "CalcView", "CalcView.MedicMod", function( ply, pos, ang, fov ) + if ( !IsValid( ply ) or !ply:Alive() or ply:GetViewEntity() != ply ) then return end + + if ply:GetMedicAnimation() != 0 then + local view = {} + view.origin = pos - ( ang:Forward()*20 ) + view.angles = ang + view.fov = fov + view.drawviewer = true + return view + end + + --[[ + if ply:GetNWBool("CarryingRagdoll", false) then + local view = {} + local tilted = Angle(ang.p, ang.y, ang.r) + tilted:RotateAroundAxis(ang:Forward(), 18) + view.origin = pos + view.angles = tilted + view.fov = fov + view.drawviewer = false + return view + end + ]] +end) + +hook.Add("RenderScreenspaceEffects", "RenderScreenspaceEffects.MedicMod", function() + if LocalPlayer():IsBleeding() then + MedicMod.BleedingEffect() + end + if LocalPlayer():IsPoisoned() then + MedicMod.PoisonEffect() + end +end) + +local bleedingIcon = Material("materials/bleeding.png") +local poisonedIcon = Material("materials/poisoned.png") +local hattackIcon = Material("materials/heart_attack_icon.png") +local morphIcon = Material("materials/morphine_icon.png") +local breakIcon = Material("materials/break_icon.png") +local notifIcon = Material("materials/heart_attack_icon.png") + +local deathPanel = nil + +hook.Add("HUDPaint", "HUDPaint.MedicMod", function() + + if ConfigurationMedicMod.MedicTeams and table.HasValue(ConfigurationMedicMod.MedicTeams, LocalPlayer():Team()) then + for k, v in pairs(ents.FindByClass("prop_ragdoll")) do + + if not v:IsDeathRagdoll() then continue end + + local pos = ( v:GetPos() + Vector(0,0,10) ):ToScreen() + local dist = v:GetPos():Distance(LocalPlayer():GetPos()) + + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( notifIcon ) + surface.DrawTexturedRect( pos.x - 25, pos.y, 50, 50 ) + + draw.SimpleTextOutlined( math.floor(math.sqrt(dist/3)).."m", "MedicModFont30", pos.x, pos.y + 50, Color( 255, 255, 255, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 1, Color( 0, 0, 0, 255 ) ) + + end + end + + --[[ + local carryText = nil + if LocalPlayer():GetNWBool("CarryingRagdoll", false) then + carryText = ConfigurationMedicMod.Sentences["You dropped the corpse"][ConfigurationMedicMod.Language] + else + local trace = LocalPlayer():GetEyeTraceNoCursor() + if IsValid(trace.Entity) and trace.Entity:IsDeathRagdoll() and not trace.Entity:GetNWBool("IsRagdollCarried", false) and trace.HitPos:Distance(LocalPlayer():GetPos()) <= 120 then + carryText = ConfigurationMedicMod.Sentences["Press E to carry corpse"][ConfigurationMedicMod.Language] + end + end + + if carryText then + draw.SimpleTextOutlined(carryText, "MedicModFont30", ScrW() / 2, ScrH() - 80, Color(255, 255, 0, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 2, Color(0, 0, 0, 255)) + end + ]] + + local nbStat = 1 + + if LocalPlayer():IsBleeding() then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( bleedingIcon ) + surface.DrawTexturedRect( ScrW() - 300, ScrH() - 150 - 50 * nbStat, 50, 50 ) + + draw.SimpleTextOutlined( ConfigurationMedicMod.Sentences["Bleeding"][ConfigurationMedicMod.Language], "MedicModFont30", ScrW() - 240, ScrH() - 140 - 50 * nbStat, Color( 255, 0, 0, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color( 0, 0, 0, 255 ) ) + nbStat = nbStat + 1 + end + if LocalPlayer():IsPoisoned() then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( poisonedIcon ) + surface.DrawTexturedRect( ScrW() - 300, ScrH() - 150 - 50 * nbStat, 50, 50 ) + + draw.SimpleTextOutlined( ConfigurationMedicMod.Sentences["Poisoned"][ConfigurationMedicMod.Language], "MedicModFont30", ScrW() - 240, ScrH() - 140 - 50 * nbStat, Color( 153, 201, 158, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color( 0, 0, 0, 255 ) ) + nbStat = nbStat + 1 + end + if LocalPlayer():GetHeartAttack() then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( hattackIcon ) + surface.DrawTexturedRect( ScrW() - 300, ScrH() - 150 - 50 * nbStat, 50, 50 ) + + draw.SimpleTextOutlined( ConfigurationMedicMod.Sentences["Heart Attack"][ConfigurationMedicMod.Language], "MedicModFont30", ScrW() - 240, ScrH() - 140 - 50 * nbStat , Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color( 0, 0, 0, 255 ) ) + nbStat = nbStat + 1 + end + if LocalPlayer():IsMorphine() then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( morphIcon ) + surface.DrawTexturedRect( ScrW() - 300, ScrH() - 150 - 50 * nbStat, 50, 50 ) + + draw.SimpleTextOutlined( ConfigurationMedicMod.Sentences["Morphine"][ConfigurationMedicMod.Language], "MedicModFont30", ScrW() - 240, ScrH() - 140 - 50 * nbStat, Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color( 0, 0, 0, 255 ) ) + nbStat = nbStat + 1 + end + if LocalPlayer():IsFractured() then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( breakIcon ) + surface.DrawTexturedRect( ScrW() - 300, ScrH() - 150 - 50 * nbStat, 50, 50 ) + + draw.SimpleTextOutlined( ConfigurationMedicMod.Sentences["Fracture"][ConfigurationMedicMod.Language], "MedicModFont30", ScrW() - 240, ScrH() - 140 - 50 * nbStat, Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, Color( 0, 0, 0, 255 ) ) + nbStat = nbStat + 1 + end + +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_net.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_net.lua new file mode 100644 index 0000000..4ab330e --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_net.lua @@ -0,0 +1,858 @@ +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +local function CreateCloseButton( Parent, Panel, color, sx, sy, back, backcolor) + + Parent = Parent or "" + sx = sx or 50 + sy = sy or 20 + color = color or Color(255,255,255,255) + back = back or false + backcolor = backcolor or false + + local x,y = Parent:GetSize() + + local CloseButton = vgui.Create("DButton", Parent) + CloseButton:SetPos(x-sx, 0) + CloseButton:SetSize(sx,sy) + CloseButton:SetFont("Trebuchet24") + CloseButton:SetTextColor( color ) + CloseButton:SetText("X") + function CloseButton:DoClick() + Panel:Close() + end + CloseButton.Paint = function(s , w , h) + if back then + draw.RoundedBox(0,0,0,w , h,backcolor) + end + end + + return CloseButton + +end + +local function CreateButton( Parent, text, font, colorText, px, py, sx, sy, func, back, backcolor, backcolorbar, sound) + + Parent = Parent or "" + font = font or "Trebuchet18" + text = text or "" + px = px or 0 + py = py or 0 + sx = sx or 50 + sound = sound or true + func = func or function() end + sy = sy or 50 + colorText = colorText or Color(255,255,255,255) + back = back or true + backcolor = backcolor or Color( 0, 100 , 150 ) + backcolorbar = backcolorbar or Color( 0 , 80 , 120 ) + + local Button = vgui.Create("DButton", Parent) + Button:SetPos( px , py ) + Button:SetSize(sx,sy) + Button:SetFont("Trebuchet24") + Button:SetTextColor( colorText ) + Button:SetText(text) + function Button:DoClick() + func() + if sound then + surface.PlaySound( "UI/buttonclick.wav" ) + end + end + + if sound then + function Button:OnCursorEntered() + surface.PlaySound( "UI/buttonrollover.wav" ) + end + end + + Button.Paint = function(s , w , h) + if back then + if Button:IsHovered() then + draw.RoundedBox(0,0,0,w , h, Color( backcolor.r + 30, backcolor.g + 30, backcolor.b + 30 )) + draw.RoundedBox(0,0,h-sy/10,sx , sy/10, Color( backcolorbar.r + 30, backcolorbar.g + 30, backcolorbar.b + 30 )) + else + draw.RoundedBox(0,0,0,w , h, backcolor) + draw.RoundedBox(0,0,h-sy/10,sx , sy/10, backcolorbar) + end + end + end + + return Button + +end + +local function CreatePanel( Parent, sx, sy, posx, posy, backcolor, scroll, bar, grip, btn) + + Parent = Parent or "" + sx = sx or 100 + sy = sy or 100 + backcolor = backcolor or Color(35, 35, 35, 255) + posx = posx or 0 + posy = posy or 0 + scroll = scroll or false + bar = bar or Color( 30, 30, 30 ) + grip = grip or Color( 0, 140, 208 ) + btn = btn or Color( 4,95,164 ) + + local typ = "DPanel" + if scroll then + typ = "DScrollPanel" + else + typ = "DPanel" + end + + local Panel = vgui.Create(typ, Parent) + Panel:SetSize(sx,sy) + Panel:SetPos(posx,posy) + Panel.Paint = function(s , w , h) + draw.RoundedBox(0,0,0,w , h, backcolor) + end + + if typ == "DScrollPanel" then + + local sbar = Panel:GetVBar() + + function sbar:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, bar ) + end + + function sbar.btnUp:Paint( w, h ) + draw.SimpleText( "?", "Trebuchet24", -3, -4, btn ) + end + + function sbar.btnDown:Paint( w, h ) + draw.SimpleText( "?", "Trebuchet24", -3, -4, btn ) + end + + function sbar.btnGrip:Paint( w, h ) + draw.RoundedBox( 8, 0, 0, w, h, grip ) + end + end + + return Panel + +end + +local function CreateLabel( Parent, font, text, sx, sy, posx, posy, color, time) + + Parent = Parent or "" + font = font or "Trebuchet24" + text = text or "" + sx = sx or 100 + sy = sy or 100 + posx = posx or 0 + posy = posy or 0 + color = color or Color(255,255,255,255) + time = time or 0 + + local EndTime = CurTime() + time + local SizeString = string.len( text ) + + local Label = vgui.Create("DLabel", Parent) + Label:SetPos( posx, posy ) + Label:SetSize( sx,sy ) + if time == 0 then + Label:SetText( text ) + else + Label:SetText( "" ) + end + Label:SetWrap( true ) + Label:SetTextColor(color) + Label:SetFont(font) + + Label.Think = function() + + if Label:GetText() == text then + return + end + + local TimeLeft = EndTime - CurTime() + local StringSizeP1 = ( TimeLeft / ( time / 100 ) ) / 100 + local StringSize = 1 - StringSizeP1 + + Label:SetText( string.sub(text, 0, SizeString * StringSize )) + + end + + return Label + +end + + +local SizeX = 400 +local SizeY = 250 + +net.Receive("MedicMod.MedicMenu", function() + + local ent = net.ReadEntity() + local fract = net.ReadTable() + + local FramePrincipal = vgui.Create( "DFrame" ) + FramePrincipal:SetSize( SizeX, SizeY ) + FramePrincipal:SetPos( ScrW()/2 - SizeX/2, ScrH()/2 - SizeY/2 ) + FramePrincipal:SetTitle( "Panel" ) + FramePrincipal:SetDraggable( false ) + FramePrincipal:ShowCloseButton( false ) + FramePrincipal:MakePopup() + FramePrincipal.Paint = function(s , w , h) + end + + local boxTitle = CreatePanel( FramePrincipal, SizeX, 20, 0, 0, Color(0, 140, 208, 255), false ) + + local CloseButton = CreateCloseButton( boxTitle, FramePrincipal ) + + local LabelTitle = CreateLabel( boxTitle, "Trebuchet24", "Medecin", SizeX-40, 20, 50, 0, nil, 0) + + local boxContent = CreatePanel( FramePrincipal, SizeX, SizeY-20, 0, 20, Color(35, 35, 35, 255), true ) + + local fractn = table.Count(fract) + + if LocalPlayer():Health() < LocalPlayer():GetMaxHealth() or fractn > 0 then + local money = ( LocalPlayer():GetMaxHealth() - LocalPlayer():Health() ) * ConfigurationMedicMod.HealthUnitPrice + + if fractn > 0 then + money = money + fractn * ConfigurationMedicMod.FractureRepairPrice + end + + local Label1 = CreateLabel( boxContent, nil, sentences["Hello, you look sick. I can heal you, it will cost"][lang].." "..money..ConfigurationMedicMod.MoneyUnit.."." , SizeX - 40, SizeY - 35 - 50 - 10, 10, 0, nil, 3) + local Button1 = CreateButton( boxContent, sentences["Heal me"][lang], nil, nil, SizeX/2-75, SizeY - 50 - 10 - 25, 150, 50, function() net.Start("MedicMod.MedicStart") net.WriteEntity( ent ) net.SendToServer() FramePrincipal:Close() end ) + else + local Label1 = CreateLabel( boxContent, nil, sentences["Hello, you seem healthy-looking today"][lang] , SizeX - 40, SizeY - 35 - 50 - 10, 10, 0, nil, 2) + local Button1 = CreateButton( boxContent, sentences["Thanks"][lang], nil, nil, SizeX/2-75, SizeY - 50 - 10 - 25, 150, 50, function() FramePrincipal:Close() end ) + end + +end) + +-- net.Receive("MedicMod.NotifiyPlayer", function() + -- local msg = net.ReadString() + -- local time = net.ReadInt( 32 ) + -- MedicNotif( msg, time ) +-- end) + +net.Receive("MedicMod.ScanRadio", function() + + local ent = net.ReadEntity() + local fractures = net.ReadTable() + + ent.fracturesTable = fractures + +end) + +net.Receive("MedicMod.PlayerStartAnimation", function() + + timer.Simple(0.15, function() + + for k, v in pairs(player.GetAll()) do + + if not v:GetMedicAnimation() then continue end + + if v:GetMedicAnimation() != 0 then + StartMedicAnimation( v, v:GetMedicAnimation() ) + end + + end + + end) + +end) + +net.Receive("MedicMod.PlayerStopAnimation", function() + + timer.Simple(0.15, function() + + for k, v in pairs(player.GetAll()) do + + if v:GetMedicAnimation() == 0 and IsValid( v.mdlanim ) then + StopMedicAnimation( v ) + end + + end + + end) + +end) + +net.Receive("MedicMod.Respawn", function() + MedicMod.seconds = net.ReadInt(32) +end) + +net.Receive("MedicMod.TerminalMenu", function() + local ent = net.ReadEntity() + + if not IsValid( ent ) then return end + + MedicMod.TerminalMenu( ent ) +end) + + +-- medic menu + +for i=1,30 do + + surface.CreateFont( "Bariol"..i, { + font = "Bariol Regular", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = i, + weight = 750, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, + } ) + +end + + +local venalib = {} + +local matCloseButton = Material( "materials/medic/lib/close_button.png" ) + +venalib.Frame = function( sizex, sizey, posx, posy, parent ) + + local parent = parent or nil + local sizex = sizex or 500 + local sizey = sizey or 500 + + local frame = vgui.Create("DFrame", parent) + frame:SetSize( sizex, sizey ) + frame:MakePopup() + frame:ShowCloseButton(false) + frame:SetTitle("") + + if not posx or not posy then + frame:Center() + else + frame:SetPos( posx, posy ) + end + + frame.Paint = function( pnl, w, h ) + draw.RoundedBox( 3, 0, 0, w,h, Color( 46, 46, 54)) + draw.RoundedBox( 3, 0, 0, w,40, Color( 36, 36, 44)) + draw.RoundedBox( 0, 0, 40, w,2, Color( 26, 26, 34)) + draw.SimpleText( sentences["Medic"][lang].." - Menu", "Bariol20", 10, 10, Color( 255, 255, 255, 255 ) ) + end + + local DermaButton = vgui.Create( "DButton", frame ) + DermaButton:SetText( "" ) + DermaButton:SetPos( sizex-30, 15/2 ) + DermaButton:SetSize( 25, 25 ) + DermaButton.DoClick = function() + if frame then frame:Remove() end + end + DermaButton.Paint = function( pnl, w, h ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( matCloseButton ) + surface.DrawTexturedRect( 0, 0, 25, 25 ) + end + + local dpanel = vgui.Create("DPanel", frame) + dpanel:SetPos( 0, 42 ) + dpanel:SetSize( sizex, sizey-42 ) + dpanel.Paint = function( pnl, w, h ) + end + + return dpanel + +end + +venalib.Panel = function( sizex, sizey, posx, posy, parent ) + + local parent = parent or nil + local sizex = sizex or 500 + local sizey = sizey or 500 + + local panel = vgui.Create("DScrollPanel", parent) + panel:SetSize( sizex, sizey ) + + if not posx or not posy then + panel:SetPos(0,0) + else + panel:SetPos( posx, posy ) + end + + panel.Paint = function( pnl, w, h ) + draw.RoundedBox( 0, 0, 0, w,h, Color(36, 36, 44)) + draw.RoundedBox( 0, 0, 0, w,h-2, Color(46, 46, 54)) + end + + + local sbar = panel:GetVBar() + + function sbar:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, Color(56, 56, 64) ) + end + + function sbar.btnUp:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, Color(36, 36, 44) ) + end + + function sbar.btnDown:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, Color(36, 36, 44) ) + end + + function sbar.btnGrip:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, Color(31, 31, 39) ) + end + + return panel + +end + +venalib.Label = function( text, font, sx, sy, posx, posy, color, Parent, time) + + Parent = Parent or "" + font = font or 15 + text = text or "" + sx = sx or 100 + sy = sy or 100 + posx = posx or 0 + posy = posy or 0 + color = color or Color(255,255,255,255) + time = time or 0 + + local EndTime = CurTime() + time + local SizeString = string.len( text ) + + local Label = vgui.Create("DLabel", Parent) + Label:SetPos( posx, posy ) + Label:SetSize( sx,sy ) + if time == 0 then + Label:SetText( text ) + else + Label:SetText( "" ) + end + Label:SetWrap( true ) + Label:SetTextColor(color) + Label:SetFont("Bariol"..font) + + Label.Think = function() + + if Label:GetText() == text then + return + end + + local TimeLeft = EndTime - CurTime() + local StringSizeP1 = ( TimeLeft / ( time / 100 ) ) / 100 + local StringSize = 1 - StringSizeP1 + + Label:SetText( string.sub(text, 0, SizeString * StringSize )) + + end + + return Label + +end + +venalib.Button = function( text, sizex, sizey, posx, posy, func, parent ) + + local text = text or "" + local sizex = sizex or 100 + local sizey = sizey or 30 + local posx = posx or 0 + local posy = posy or 0 + local parent = parent or nil + local func = func or function() end + + local button = vgui.Create( "DButton", parent ) + button:SetText( "" ) + button:SetPos( posx, posy) + button:SetSize( sizex, sizey ) + + local colorr = 36 + local colorg = 36 + local colorb = 44 + + function button:DoClick() + func() + surface.PlaySound( "UI/buttonclick.wav" ) + end + + button.Paint = function( pnl, w, h ) + + local color = Color( 36, 36, 44) + local pa = 0.1 + + if button:IsHovered() then + colorr = math.Clamp( colorr + pa, 36, 36+5 ) + colorg = math.Clamp( colorg + pa, 36, 36+5 ) + colorb = math.Clamp( colorb + pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + else + colorr = math.Clamp( colorr - pa, 36, 36+5 ) + colorg = math.Clamp( colorg - pa, 36, 36+5 ) + colorb = math.Clamp( colorb - pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + end + draw.RoundedBox( 0, 0, 0, w,h-2, color) + draw.RoundedBox( 0, 0, h-2, w,2, Color( 26, 26, 34)) + -- draw.RoundedBox( 0, w-2, 0, 2,h, Color( 31, 31, 41)) + draw.SimpleText( text, "Bariol17", 10, sizey/2-17/2-2, Color( 255, 255, 255, 255 ) ) + end + function button:OnCursorEntered() + surface.PlaySound( "UI/buttonrollover.wav" ) + end + return button +end + +local sizex = 800 +local sizey = 600 + +local function OpenTestTubesPart(MainFrame) + + local sizex = 800 + local sizey = 600 + + local panelM = venalib.Panel( sizex-160, sizey-40, 160, 0, MainFrame ) + local nbing = 0 + for k , v in pairs( ConfigurationMedicMod.Reagents ) do + + local name = k + local price = v.price or 10 + + local x = (sizex-160-40-10)/2 + local y = 100 + + local ispair = math.mod( nbing, 2 ) + + local panelMIng = venalib.Panel( x, y, 10+(10+(x))*ispair, 10 + math.floor(nbing/2) * (y+10), panelM ) + panelMIng.Paint = function( pnl, w, h ) + draw.RoundedBox( 0, 0, 0, w,h, Color(36, 36, 44)) + end + + local icon = vgui.Create( "SpawnIcon", panelMIng ) + icon:SetPos( 10, 10 ) + icon:SetSize( 80, 80 ) + icon:SetModel( "models/medicmod/test_tube/testtube.mdl" ) + function icon:LayoutEntity( Entity ) return end + + local text = venalib.Label( sentences["Test tube"][lang]..": "..name, 14, x-100-20, 30, 110, 10, Color(255,255,255), panelMIng) + text:SetWrap(false) + local text2 = venalib.Label( price..ConfigurationMedicMod.MoneyUnit, 14, x-100-20, 70, 110, 10, Color(255,255,255), panelMIng) + text2:SetWrap(false) + + local button1 = venalib.Button( "> "..sentences["Buy"][lang], x-100-20, 40, x-(x-100-20)-10, y - 45 , function() + net.Start("MedicMod.BuyMedicJobEntity") + net.WriteString( k ) + net.WriteString( "test_tube_medicmod_s" ) + net.SendToServer() + end,panelMIng ) + + local colorr = 36 + local colorg = 36 + local colorb = 44 + + button1.Paint = function( pnl, w, h ) + + local color = Color( 36, 36, 44) + local pa = 0.1 + + if not button1:IsHovered() then + colorr = math.Clamp( colorr + pa, 36, 36+5 ) + colorg = math.Clamp( colorg + pa, 36, 36+5 ) + colorb = math.Clamp( colorb + pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + else + colorr = math.Clamp( colorr - pa, 36, 36+5 ) + colorg = math.Clamp( colorg - pa, 36, 36+5 ) + colorb = math.Clamp( colorb - pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + end + draw.RoundedBox( 0, 0, 0, w,h-2, color) + draw.RoundedBox( 0, 0, h-2, w,2, Color( 26, 26, 34)) + -- draw.RoundedBox( 0, w-2, 0, 2,h, Color( 31, 31, 41)) + draw.SimpleText( "> "..sentences["Buy"][lang], "Bariol17", 15, 40/2-17/2-2, Color( 255, 255, 255, 255 ) ) + end + + nbing = nbing + 1 + + end + + return panelM +end + +local function OpenEntitiesPart(MainFrame) + + local sizex = 800 + local sizey = 600 + + local panelM = venalib.Panel( sizex-160, sizey-40, 160, 0, MainFrame ) + local nbing = 0 + for k , v in pairs( ConfigurationMedicMod.MedicShopEntities ) do + + local name = v.name or "No name" + local price = v.price or 10 + + local x = (sizex-160-40-10)/2 + local y = 100 + + local ispair = math.mod( nbing, 2 ) + + local panelMIng = venalib.Panel( x, y, 10+(10+(x))*ispair, 10 + math.floor(nbing/2) * (y+10), panelM ) + panelMIng.Paint = function( pnl, w, h ) + draw.RoundedBox( 0, 0, 0, w,h, Color(36, 36, 44)) + end + + local icon = vgui.Create( "SpawnIcon", panelMIng ) + icon:SetPos( 10, 10 ) + icon:SetSize( 80, 80 ) + icon:SetModel( v.model or "" ) + function icon:LayoutEntity( Entity ) return end + + local text = venalib.Label( sentences["Test tube"][lang]..": "..name, 14, x-100-20, 30, 110, 10, Color(255,255,255), panelMIng) + text:SetWrap(false) + local text2 = venalib.Label( price..ConfigurationMedicMod.MoneyUnit, 14, x-100-20, 70, 110, 10, Color(255,255,255), panelMIng) + text2:SetWrap(false) + + local button1 = venalib.Button( "> "..sentences["Buy"][lang], x-100-20, 40, x-(x-100-20)-10, y - 45 , function() + net.Start("MedicMod.BuyMedicJobEntity") + net.WriteString( "entity" ) + net.WriteString( k ) + net.SendToServer() + end,panelMIng ) + + local colorr = 36 + local colorg = 36 + local colorb = 44 + + button1.Paint = function( pnl, w, h ) + + local color = Color( 36, 36, 44) + local pa = 0.1 + + if not button1:IsHovered() then + colorr = math.Clamp( colorr + pa, 36, 36+5 ) + colorg = math.Clamp( colorg + pa, 36, 36+5 ) + colorb = math.Clamp( colorb + pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + else + colorr = math.Clamp( colorr - pa, 36, 36+5 ) + colorg = math.Clamp( colorg - pa, 36, 36+5 ) + colorb = math.Clamp( colorb - pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + end + draw.RoundedBox( 0, 0, 0, w,h-2, color) + draw.RoundedBox( 0, 0, h-2, w,2, Color( 26, 26, 34)) + -- draw.RoundedBox( 0, w-2, 0, 2,h, Color( 31, 31, 41)) + draw.SimpleText( "> "..sentences["Buy"][lang], "Bariol17", 15, 40/2-17/2-2, Color( 255, 255, 255, 255 ) ) + end + + nbing = nbing + 1 + + end + + return panelM +end + +local function OpenWeaponsPart(MainFrame) + + local sizex = 800 + local sizey = 600 + + local panelM = venalib.Panel( sizex-160, sizey-40, 160, 0, MainFrame ) + local nbing = 0 + for k , v in pairs( ConfigurationMedicMod.MedicShopWeapons ) do + + local name = v.name or "No name" + local price = v.price or 10 + + local x = (sizex-160-40-10)/2 + local y = 100 + + local ispair = math.mod( nbing, 2 ) + + local panelMIng = venalib.Panel( x, y, 10+(10+(x))*ispair, 10 + math.floor(nbing/2) * (y+10), panelM ) + panelMIng.Paint = function( pnl, w, h ) + draw.RoundedBox( 0, 0, 0, w,h, Color(36, 36, 44)) + end + + local icon = vgui.Create( "SpawnIcon", panelMIng ) + icon:SetPos( 10, 10 ) + icon:SetSize( 80, 80 ) + icon:SetModel( v.model or "" ) + function icon:LayoutEntity( Entity ) return end + + local text = venalib.Label( sentences["Test tube"][lang]..": "..name, 14, x-100-20, 30, 110, 10, Color(255,255,255), panelMIng) + text:SetWrap(false) + local text2 = venalib.Label( price..ConfigurationMedicMod.MoneyUnit, 14, x-100-20, 70, 110, 10, Color(255,255,255), panelMIng) + text2:SetWrap(false) + + local button1 = venalib.Button( "> "..sentences["Buy"][lang], x-100-20, 40, x-(x-100-20)-10, y - 45 , function() + net.Start("MedicMod.BuyMedicJobEntity") + net.WriteString( "weapon" ) + net.WriteString( k ) + net.SendToServer() + end,panelMIng ) + + local colorr = 36 + local colorg = 36 + local colorb = 44 + + button1.Paint = function( pnl, w, h ) + + local color = Color( 36, 36, 44) + local pa = 0.1 + + if not button1:IsHovered() then + colorr = math.Clamp( colorr + pa, 36, 36+5 ) + colorg = math.Clamp( colorg + pa, 36, 36+5 ) + colorb = math.Clamp( colorb + pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + else + colorr = math.Clamp( colorr - pa, 36, 36+5 ) + colorg = math.Clamp( colorg - pa, 36, 36+5 ) + colorb = math.Clamp( colorb - pa, 44, 44+5 ) + color = Color( colorr, colorg, colorb, 255 ) + end + draw.RoundedBox( 0, 0, 0, w,h-2, color) + draw.RoundedBox( 0, 0, h-2, w,2, Color( 26, 26, 34)) + -- draw.RoundedBox( 0, w-2, 0, 2,h, Color( 31, 31, 41)) + draw.SimpleText( "> "..sentences["Buy"][lang], "Bariol17", 15, 40/2-17/2-2, Color( 255, 255, 255, 255 ) ) + end + + nbing = nbing + 1 + + end + + return panelM +end + +local function OpenGuidePage( MainFrame ) + + local sizex = 800 + local sizey = 600 + + local panelM = venalib.Panel( sizex-160, sizey-40, 160, 0, MainFrame ) + + local GuideText = sentences.GuideText[lang] + + + + local htmlc = [[]] + + for _, tab in pairs( GuideText ) do + + local text = tab[1] or "" + local size = tab[2] or 15 + + -- local TextLabel = vgui.Create( "DLabel", panelM ) + -- TextLabel:SetPos( 10, pos ) + -- TextLabel:SetText( text ) + -- TextLabel:SetWrap( true ) + -- TextLabel:SetFont("Bariol"..size) + -- TextLabel:SetWide(sizex-160-10) + -- TextLabel:SetAutoStretchVertical( true ) + htmlc = htmlc..''..text..'

' + + -- local sx, sy = TextLabel:GetSize() + + -- pos = pos + sy + + end + + local html = vgui.Create( "DHTML" , panelM ) + html:SetSize( sizex-160, sizey-40 ) + html:SetHTML( htmlc ) + + -- local panelM2 = venalib.Panel( 1, 1000, 160, 0, panelM ) + -- local panelM1Title = venalib.Label( "Comment cuisiner?", 20, sizex-160, 20, 20, 10 , Color(255,255,255), panelM ) + -- panelM1Title:SetWrap(false) + + -- local panelM1desc = venalib.Label( [[testen + -- effet + -- oui]], 20, sizex-160, 20, 20, 30 , Color(255,255,255), panelM ) + + + -- local panelM2Title = venalib.Label( "Comment utiliser l'ecran et le terminal?", 20, sizex-160, 20, 20, 350 , Color(255,255,255), panelM ) + -- panelM2Title:SetWrap(false) + + return panelM + +end + +local function OpenMedicinesPart(MainFrame) + local rn = 0 + local panelB = venalib.Panel( sizex-160, sizey-40, 160, 0, MainFrame ) + + for k, v in pairs( ConfigurationMedicMod.Drugs ) do + + local panel1 = venalib.Panel( sizex-160, 100, 0, 100*rn, panelB ) + local panelM1text = venalib.Label( k, 15, 200, 15, 10, 10, Color(255,255,255), panel1 ) + + local icon = vgui.Create( "SpawnIcon", panel1 ) + icon:SetSize( 60, 60 ) + icon:SetPos(15, 30) + icon:SetModel( "models/medicmod/drug/drug.mdl" ) + + local ingnum = 0 + + for a, b in pairs( v ) do + + if a == "func" or a == "price" then continue end + local panelM1Ing1 = venalib.Label( "? "..a, 15, sizex-160-100-20-100, 15, 60+15+5, 32 + 15 * ingnum, Color(255,255,255), panel1 ) + panelM1Ing1:SetWrap( false ) + + ingnum = ingnum + 1 + + end + + local buttonR = venalib.Button( sentences["Buy"][lang].. "( "..v.price..ConfigurationMedicMod.MoneyUnit.." )", 100, 35, sizex-160-100-20, 40, function() + + net.Start("MedicMod.BuyMedicJobEntity") + net.WriteString( k ) + net.WriteString( "drug_medicmod_s" ) + net.SendToServer() + + end,panel1 ) + + rn = rn + 1 + + end + + return panelB +end + +local function OpenMainUI() + + local actualPart + + local MainFrame = venalib.Frame( sizex,sizey ) + MainFrame.Paint = function( pnl, w, h ) + draw.RoundedBox( 0, 0, 0, w,h, Color(36, 36, 44)) + end + local button1 = venalib.Button( "> "..sentences["Test tube"][lang], 160, 40, 0, 80, function() if ispanel(actualPart) then actualPart:Remove() end actualPart = OpenTestTubesPart(MainFrame) end,MainFrame ) + local button2 = venalib.Button( "> "..sentences["Drugs"][lang], 160, 40, 0, 40, function() if ispanel(actualPart) then actualPart:Remove() end actualPart = OpenMedicinesPart(MainFrame) end,MainFrame ) + local button2 = venalib.Button( "> Guide", 160, 40, 0, 0, function() if ispanel(actualPart) then actualPart:Remove() end actualPart = OpenGuidePage(MainFrame) end,MainFrame ) + local button2 = venalib.Button( "> Entities", 160, 40, 0, 120, function() if ispanel(actualPart) then actualPart:Remove() end actualPart = OpenEntitiesPart(MainFrame) end,MainFrame ) + local button2 = venalib.Button( "> Weapons", 160, 40, 0, 160, function() if ispanel(actualPart) then actualPart:Remove() end actualPart = OpenWeaponsPart(MainFrame) end,MainFrame ) + + actualPart = OpenTestTubesPart( MainFrame ) + +end + +net.Receive("MedicMod.OpenMedicMenu", function() + OpenMainUI() +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_notify.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_notify.lua new file mode 100644 index 0000000..21ed58d --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/client/cl_notify.lua @@ -0,0 +1,118 @@ +-- NotificationTable_Venatuss = NotificationTable_Venatuss or {} + +-- function MedicNotif( msg, time ) + + -- local time = time or 10 + + -- NotificationTable_Venatuss[#NotificationTable_Venatuss + 1] = { + -- text = msg, + -- apptime = CurTime() + 0.2, + -- timeremove = CurTime() + 0.2 + 1 + time, + -- type = "medic", + -- } + +-- end + +-- local iconMat = Material( "materials/notify_icon.png" ) +-- local iconMatR = Material( "materials/notify_rect.png" ) + +-- hook.Add("HUDPaint", "MedicMod.HUDNotifications", function() + + -- for k, v in pairs( NotificationTable_Venatuss ) do + -- if v.type == "medic" then + -- if v.timeremove - CurTime() < 0 then table.remove(NotificationTable_Venatuss,k) continue end + + -- local alpha = ( math.Clamp(CurTime() - v.apptime, 0 , 1) ) + -- local posy = ScrH() - 200 - 60 * k - 40 * ( 1 - ( math.Clamp(CurTime() - v.apptime, 0 , 1) ) ) + -- local posx = math.Clamp(v.timeremove - CurTime(),0,0.25) * 4 * 30 + (0.25 - math.Clamp(v.timeremove - CurTime(),0,0.25)) * 4 * - 340 + + -- surface.SetFont( "MedicModFont20" ) + -- local textsize = select( 1,surface.GetTextSize( v.text ) ) + + -- surface.SetDrawColor( 255, 255, 255, 255 * alpha ) + ---- surface.DrawRect( posx + 50, posy, 20 + textsize, 40 ) + + -- surface.SetMaterial( iconMat ) + -- surface.DrawTexturedRect( posx - 20, posy - 18 , 75,75 ) + -- surface.SetMaterial( iconMatR ) + -- surface.DrawTexturedRect( posx + 75 - 34, posy - 17.5 , textsize + 30, 75 ) + + -- surface.SetTextPos( posx + 50 + 10, posy + 10 ) + -- surface.SetTextColor( 255,255,255, 255 * alpha) + -- surface.DrawText( v.text ) + -- end + -- end + +-- end) + + +NotificationTable_Venatuss = NotificationTable_Venatuss or {} + +surface.CreateFont( "Bariol20", { + font = "Bariol Regular", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 20, + weight = 750, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +function MedicNotif( msg, time ) + + local time = time or 10 + + NotificationTable_Venatuss[#NotificationTable_Venatuss + 1] = { + text = msg, + apptime = CurTime() + 0.2, + timeremove = CurTime() + 0.2 + 1 + time, + type = "medic", + } + +end + +local iconMat = Material( "materials/heart_attack_icon.png" ) + +hook.Add("HUDPaint", "MedicMod.HUDNotifications", function() + + for k, v in pairs( NotificationTable_Venatuss ) do + if v.type == "medic" then + if v.timeremove - CurTime() < 0 then table.remove(NotificationTable_Venatuss,k) continue end + + local alpha = ( math.Clamp(CurTime() - v.apptime, 0 , 1) ) + local posy = ScrH() - 200 - 60 * k - 40 * ( 1 - ( math.Clamp(CurTime() - v.apptime, 0 , 1) ) ) + local posx = math.Clamp(v.timeremove - CurTime(),0,0.25) * 4 * 30 + (0.25 - math.Clamp(v.timeremove - CurTime(),0,0.25)) * 4 * - 340 + + surface.SetFont( "Bariol20" ) + local x,y = surface.GetTextSize( v.text ) + + draw.RoundedBox( 5, posx, posy , 60, 40, Color(0, 131, 167,255 * alpha ) ) + + surface.SetDrawColor( 255, 255, 255, 255 * alpha ) + surface.DrawRect( posx + 50, posy, 20 + x, 40 ) + + surface.SetMaterial( iconMat ) + surface.DrawTexturedRect( posx + 10, posy + 5, 30, 30 ) + + + surface.SetTextPos( posx + 50 + 10, posy + 40/2-y/2 ) + surface.SetTextColor( 0, 0, 0, 255 * alpha) + surface.DrawText( v.text ) + end + end + +end) + +net.Receive("MedicMod:NotifyPlayer", function() + local msg = net.ReadString() + local time = net.ReadInt( 32 ) + MedicNotif( msg, time ) +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_functions.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_functions.lua new file mode 100644 index 0000000..d9d5ab3 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_functions.lua @@ -0,0 +1,494 @@ +local meta = FindMetaTable( "Player" ) + +local lang = ConfigurationMedicMod.Language +local sentences = ConfigurationMedicMod.Sentences + +function meta:MedicModDamages() + + local dmg = 0 + + if self:IsBleeding() then + dmg = dmg + ConfigurationMedicMod.DamagePerSecsDuringBleeding * 4 + end + if self:IsPoisoned() then + dmg = dmg + ConfigurationMedicMod.DamagePerSecsDuringPoisoned * 4 + end + + return dmg or 0 + +end + +function meta:CreateMedicTimer() + + timer.Create("MedicMod"..self:EntIndex(), 4, 0, function() + if not IsValid( self ) or not self:Alive() then return end + + local world = game.GetWorld() + + + local d = DamageInfo() + d:SetDamage( self:MedicModDamages() ) + d:SetAttacker( world ) + d:SetInflictor( world ) + if self:IsBleeding() then + d:SetDamageType( DMG_MEDICMODBLEEDING ) + + -- bleeding effect + local bone = self:GetBonePosition(math.random(1, self:GetBoneCount() - 1)) + if bone then + local ef = EffectData() + ef:SetOrigin(bone) + util.Effect("BloodImpact", ef, true, true) + end + + -- bleeding decals + if ConfigurationMedicMod.DecalsBleeding then + local src = self:LocalToWorld(self:OBBCenter()) + for i = 1, 12 do + local dir = VectorRand() * self:GetModelRadius() * 1.4 + util.Decal("Blood", src - dir, src + dir) + end + end + + else + d:SetDamageType( DMG_MEDICMOD ) + end + + local health = self:Health() + + self:TakeDamageInfo( d ) + self:SetHealth(health-self:MedicModDamages()) + + if self:Health() <= 0 and self:Alive() then + self:Kill() + end + + end) + +end + +function meta:SetBleeding( bool ) + + if self:HasGodMode() then return end + if not ConfigurationMedicMod.CanGetBleeding then return end + if table.HasValue( ConfigurationMedicMod.TeamsCantGetBleeding, self:Team() ) then return end + + self:SetNWBool("Bleeding", bool ) + + if not bool then + if timer.Exists( "MedicMod"..self:EntIndex() ) then + if not self:IsPoisoned() then + timer.Destroy("MedicMod"..self:EntIndex()) + end + end + return + end + + if timer.Exists( "MedicMod"..self:EntIndex() ) then return end + + hook.Run( "onPlayerStartsBleeding", self ) + + self:CreateMedicTimer() + +end + +function meta:SetMorphine( bool ) + self:SetNWBool("Morphine", bool ) +end + +function meta:SetHeartAttack( bool ) + self:SetNWBool("HeartAttack", bool ) + + if timer.Exists( "MedicMod"..self:EntIndex() ) then + timer.Destroy("MedicMod"..self:EntIndex()) + end +end + +function meta:SetPoisoned( bool ) + if self:HasGodMode() then return end + if not ConfigurationMedicMod.CanGetPoisoned then return end + if table.HasValue( ConfigurationMedicMod.TeamsCantGetPoisoned, self:Team() ) then return end + + self:SetNWBool("Poisoned", bool ) + + if not bool then + if timer.Exists( "MedicMod"..self:EntIndex() ) then + if not self:IsBleeding() then + timer.Destroy("MedicMod"..self:EntIndex()) + end + end + return + end + + if timer.Exists( "MedicMod"..self:EntIndex() ) then return end + + hook.Run( "onPlayerPoisoned", self ) + + self:CreateMedicTimer() + +end + +local FractureHitGroups = {} + +FractureHitGroups["legs"] = { + [HITGROUP_LEFTLEG] = true, + [HITGROUP_RIGHTLEG] = true, +} +FractureHitGroups["arms"] = { + [HITGROUP_LEFTARM] = true, + [HITGROUP_RIGHTARM] = true, +} + +function meta:SetFracture( bool, hitgroup ) + if self:HasGodMode() then return end + if not ConfigurationMedicMod.CanGetFractures then return end + if table.HasValue( ConfigurationMedicMod.TeamsCantGetFracture, self:Team() ) then return end + + if bool then + self:SetNWBool("Fracture", bool ) + + if not self.Fractures then self.Fractures = {} end + + if self.Fractures[hitgroup] then return end + + self.Fractures[hitgroup] = true + + if FractureHitGroups["legs"][hitgroup] then + + self:MedicNotif(sentences["You broke your leg, your speed is reduced"][lang]) + + if self.SpeedReduced then return end + + self.MMFWalkSpeed = self:GetWalkSpeed() + self.MMFRunSpeed = self:GetRunSpeed() + + self:SetRunSpeed(ConfigurationMedicMod.FracturePlayerSpeed) + self:SetWalkSpeed(ConfigurationMedicMod.FracturePlayerSpeed) + + self.SpeedReduced = true + + elseif FractureHitGroups["arms"][hitgroup] then + + self:MedicNotif(sentences["You broke your arm, you can't use any weapon"][lang]) + self:SelectWeapon( "" ) + self.CantSwitchWeaponMF = true + + end + + hook.Run( "onPlayerBreaksBone", self, hitgroup ) + + else + + if not self.Fractures then self.Fractures = {} end + + if FractureHitGroups["legs"][hitgroup] then + + if self.Fractures[hitgroup] then + self.Fractures[hitgroup] = nil + end + + for k, v in pairs( FractureHitGroups["legs"] ) do + if self.Fractures[k] then + return + end + end + + if not self.MMFRunSpeed or not self.MMFWalkSpeed then return end + + self:SetRunSpeed(self.MMFRunSpeed) + self:SetWalkSpeed(self.MMFWalkSpeed) + + self.SpeedReduced = false + + elseif FractureHitGroups["arms"][hitgroup] then + + if self.Fractures[hitgroup] then + self.Fractures[hitgroup] = nil + end + + for k, v in pairs( FractureHitGroups["arms"] ) do + if self.Fractures[k] then + return + end + end + + self.CantSwitchWeaponMF = false + + end + + for k, v in pairs( self.Fractures ) do + + if FractureHitGroups["legs"][ k ] or FractureHitGroups["arms"][ k ] then + return + end + + end + + self:SetNWBool("Fracture", false) + self:MedicNotif(sentences["You have no more fracture"][lang]) + + end + +end + +function meta:GetFractures() + return self.Fractures or nil +end + +function meta:CreateDeathRagdoll() + + -- create the ragdoll + local ragdoll = ents.Create("prop_ragdoll") + ragdoll:SetPos(self:GetPos()) + ragdoll:SetAngles( self:GetAngles() ) + ragdoll:SetModel(self:GetModel()) + + ragdoll:SetOwner( self ) + ragdoll:SetDeathRagdoll( true ) + + -- set bones of the ragdoll at the same pos than bones of the player + local gpobc = ragdoll:GetPhysicsObjectCount() - 1 + + for i=0, gpobc do + + local bone = self:GetPhysicsObjectNum(i) + + if IsValid(bone) then + + local bonepos, boneang = self:GetBonePosition(ragdoll:TranslatePhysBoneToBone(i)) + + if bonepos and boneang then + bone:SetPos(bonepos) + bone:SetAngles(boneang) + end + + end + + end + + ragdoll:Spawn() + ragdoll:Activate() + + ragdoll:AddEFlags( EFL_IN_SKYBOX ) + + -- make a prop to allow the player to pickup the ragdoll + local pickupProp = ents.Create("prop_physics") + pickupProp:SetModel("models/hunter/blocks/cube025x025x025.mdl") + pickupProp:SetPos(ragdoll:GetPos()) + pickupProp:SetNoDraw(true) + pickupProp:Spawn() + pickupProp:SetCollisionGroup(COLLISION_GROUP_WORLD) + + ragdoll.Prop = pickupProp + + pickupProp.ragdoll = ragdoll + + constraint.Weld(ragdoll, pickupProp, 0, 0, 0, false) + + self.DeathRagdoll = ragdoll + + if CLOTHESMOD then self:CM_ApplyRagModel( ragdoll ) end + + return ragdoll + +end + +function meta:Stabilize( medic ) + + if self:IsBleeding() then return end + if self:IsPoisoned() then return end + if self:GetHeartAttack() then return end + + self.NextSpawnTime = ConfigurationMedicMod.TimeBeforeRespawnIfStable + CurTime() + net.Start("MedicMod.Respawn") net.WriteInt(self.NextSpawnTime, 32) net.Send(self) + self.Stable = true + self:MedicNotif( sentences["Your condition has been stabilized"][lang], 10) + + hook.Run( "onPlayerStabilized", self, medic ) + + if not IsValid( medic ) then return end + + medic:MedicNotif( sentences["You stabilized the wounded"][lang], 10) + +end + +function meta:MedicalRespawn() + + local pos + if not IsValid( self.DeathRagdoll ) or not self.DeathRagdoll:IsDeathRagdoll() then + pos = self:GetPos() + else + if IsValid(self.DeathRagdoll.Rope) then + self.DeathRagdoll.Rope:SetParent( nil ) + if self.DeathRagdoll.Rope.Elec and IsValid( self.DeathRagdoll.Rope.Elec ) then + self.DeathRagdoll.Rope.Elec:SetPatient( nil ) + end + end + pos = self.DeathRagdoll:GetPos() + end + + self:MedicNotif( sentences["You have been saved"][lang], 10) + + -- Save current weapons and ammo before Spawn() resets them + self.WeaponsStripped = {} + for k, v in pairs( self:GetWeapons() ) do + table.insert(self.WeaponsStripped, v:GetClass()) + end + + self.AmmoStripped = {} + for k, v in pairs( self:GetAmmo() ) do + self.AmmoStripped[k] = v + end + + self:Spawn() + self:SetPos( pos ) + + local weaponsstripped = self.WeaponsStripped or {} + for k, v in pairs( weaponsstripped ) do + self:Give( v ) + end + + local ammostripped = self.AmmoStripped or {} + for k, v in pairs( ammostripped ) do + self:SetAmmo( v, k ) + end + + hook.Run( "onPlayerRevived", self ) + +end + +function meta:StartOperation( bed ) + + local pos = self:GetPos() + local ang = self:GetAngles() + + local hbed = ents.Create("prop_vehicle_prisoner_pod") + hbed:SetModel("models/vehicles/prisoner_pod_inner.mdl") + hbed:SetPos( bed:GetPos() + bed:GetAngles():Up() * 22 + bed:GetAngles():Right() * -30 ) + hbed:SetAngles( bed:GetAngles() + Angle(-90,0,90)) + hbed:SetNoDraw(true) + hbed:SetCollisionGroup(COLLISION_GROUP_WORLD) + hbed:SetSolid(SOLID_NONE) + hbed.locked = true + + bed.ragdoll = hbed + + self:EnterVehicle(hbed) + self:MedicNotif( sentences["You are having surgery"][lang], ConfigurationMedicMod.SurgicalOperationTime) + + self:Freeze( true ) + + hook.Run( "onPlayerStartsOperation", self ) + + timer.Simple( ConfigurationMedicMod.SurgicalOperationTime, function() + + if not IsValid(self) or not IsValid(bed) or not IsValid(hbed) then return end + + self:Freeze( false ) + + self:SetHealth( self:GetMaxHealth() ) + + if not self.Fractures then self.Fractures = {} end + + for k, v in pairs( self.Fractures ) do + self:SetFracture( false, k ) + end + + self:SetBleeding( false ) + + hbed:Remove() + timer.Simple(0.2, function() + self:SetPos( pos ) + self:SetEyeAngles( ang ) + end) + bed.ragdoll = nil + + end) + +end + +function meta:HaveFractures() + + local fract = false + + if not self.Fractures then return false end + + for k, v in pairs( self.Fractures ) do + + if FractureHitGroups["legs"][ k ] or FractureHitGroups["arms"][ k ] then + fract = true + break + end + + end + + return fract + +end + +function StartMedicAnimation( ply, id ) + + ply:SetNWString("MedicPlayerModel", ply:GetModel()) + ply:SetModel("models/medicmod/player/medic_anims.mdl") + ply:SetRenderMode(RENDERMODE_TRANSALPHA) + ply:SetColor(Color(0,0,0,0)) + ply:SetNWInt("MedicActivity", id ) + + ply:Freeze( true ) + + -- network animation + net.Start("MedicMod.PlayerStartAnimation") + net.Broadcast() + +end + +function StopMedicAnimation( ply ) + + ply:SetNWInt("MedicActivity", 0 ) + ply:SetModel( ply:GetNWString("MedicPlayerModel") ) + ply:SetColor(Color(255,255,255,255)) + ply:SetNWString("MedicPlayerModel", nil) + ply:Freeze( false ) + + -- network animation + net.Start("MedicMod.PlayerStopAnimation") + net.Broadcast() + +end + +function IsBleedingDamage( dmg ) + + local isdmg = false + + for k, v in pairs( ConfigurationMedicMod.DamageBleeding ) do + + if not v then continue end + + if dmg:IsDamageType( k ) then + isdmg = true + end + end + + return isdmg +end + +function IsPoisonDamage( dmg ) + + local isdmg = false + + for k, v in pairs( ConfigurationMedicMod.DamagePoisoned ) do + if dmg:IsDamageType( k ) then + isdmg = true + end + end + + return isdmg +end + +local ent = FindMetaTable( "Entity" ) + +function ent:SetDeathRagdoll( bool ) + + self:SetNWBool("IsDeathRagdoll", bool ) + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_hooks.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_hooks.lua new file mode 100644 index 0000000..4864673 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_hooks.lua @@ -0,0 +1,559 @@ +local lang = ConfigurationMedicMod.Language +local sentences = ConfigurationMedicMod.Sentences + +-- Delete the timer +hook.Add("PlayerDisconnected", "PlayerDisconnected.MedicMod", function( ply ) + + StopMedicAnimation( ply ) + + if ply.RagdollHeartMassage then + if IsValid( ply.RagdollHeartMassage ) && IsValid( ply.RagdollHeartMassage:GetOwner() ) then + ply.RagdollHeartMassage:GetOwner().NextSpawnTime = CurTime() + ply.RagdollHeartMassage:GetOwner().AddToSpawnTime + ply.RagdollHeartMassage.IsHeartMassage = false + net.Start("MedicMod.Respawn") + net.WriteInt(ply.RagdollHeartMassage:GetOwner().NextSpawnTime,32) + net.Send(ply.RagdollHeartMassage:GetOwner()) + end + end + + if timer.Exists( "MedicMod"..ply:EntIndex() ) then + timer.Destroy("MedicMod"..ply:EntIndex()) + end + + -- remove the death ragdoll + if IsValid( ply.DeathRagdoll ) then + if IsValid( ply.DeathRagdoll.Prop ) then + ply.DeathRagdoll.Prop:Remove() + end + ply.DeathRagdoll:Remove() + end + +end) + + +-- Respawn +hook.Add("PlayerSpawn", "PlayerSpawn.MedicMod", function( ply ) + + ply:SetBleeding( false ) + ply:SetHeartAttack( false ) + ply:SetPoisoned( false ) + + if ply:GetFractures() then + -- cure fractures + for k, v in pairs( ply:GetFractures() ) do + ply:SetFracture(false, k) + end + end + + -- remove the death ragdoll + if IsValid( ply.DeathRagdoll ) then + if IsValid( ply.DeathRagdoll.Prop ) then + ply.DeathRagdoll.Prop:Remove() + end + ply.DeathRagdoll:Remove() + if IsValid(ply.DeathRagdoll.Rope) then + ply.DeathRagdoll.Rope:SetParent( nil ) + if ply.DeathRagdoll.Rope.Elec and IsValid( ply.DeathRagdoll.Rope.Elec ) then + ply.DeathRagdoll.Rope.Elec:SetPatient( nil ) + end + end + end + + ply:UnSpectate() + + ply.NextSpawnTime = 0 + +end) + +-- Stop death sound +hook.Add("PlayerDeathSound", "PlayerDeathSound.MedicMod", function() + + return true + +end) + +-- Bleeding +hook.Add("EntityTakeDamage", "EntityTakeDamage.MedicMod", function( ply, dmg ) + + if not IsValid(ply) or not ply:IsPlayer() then return end + + -- Fix for LVS/Vehicles: ignore blast/shell damage if player is in a vehicle and damage is self-inflicted + if (ply:InVehicle()) then + local vehicle = ply:GetVehicle() + local attacker = dmg:GetAttacker() + local inflictor = dmg:GetInflictor() + + if (attacker == ply or attacker == vehicle or inflictor == vehicle) and (dmg:IsDamageType(DMG_BLAST) or dmg:IsDamageType(DMG_BLAST_SURFACE)) then + return + end + end + + local dmgtype = dmg:GetDamageType() + + -- break a bone + if dmg:IsFallDamage() then + if dmg:GetDamage() >= ConfigurationMedicMod.MinimumDamageToGetFractures then + ply:SetFracture(true, HITGROUP_RIGHTLEG) + ply:SetFracture(true, HITGROUP_LEFTLEG) + end + end + + if IsBleedingDamage( dmg ) and dmg:GetDamage() >= ConfigurationMedicMod.MinimumDamageToGetBleeding then + ply:SetBleeding( true ) + end + + if IsPoisonDamage( dmg ) then ply:SetPoisoned( true ) end + +end) + + +-- Set heart attack +hook.Add("DoPlayerDeath", "DoPlayerDeath.MedicMod", function( ply, att, dmg ) + + StopMedicAnimation( ply ) + + if ply.RagdollHeartMassage then + + if IsValid( ply.RagdollHeartMassage ) && IsValid( ply.RagdollHeartMassage:GetOwner() ) then + + ply.RagdollHeartMassage:GetOwner().NextSpawnTime = CurTime() + ply.RagdollHeartMassage:GetOwner().AddToSpawnTime + ply.RagdollHeartMassage.IsHeartMassage = false + + net.Start("MedicMod.Respawn") + net.WriteInt(ply.RagdollHeartMassage:GetOwner().NextSpawnTime,32) + net.Send(ply.RagdollHeartMassage:GetOwner()) + + end + + end + + local dmgtype = dmg:GetDamageType() + local dmgimpact = dmg:GetDamage() + + if IsBleedingDamage( dmg ) or ConfigurationMedicMod.DamageBurn[dmgtype] then + if dmgtype == DMG_MEDICMODBLEEDING then + ply:MedicNotif(sentences["His heart no longer beats"][lang], 10) + end + ply:SetHeartAttack( true ) + ply:MedicNotif(sentences["You fell unconscious following a heart attack"][lang], 10) + else + if IsPoisonDamage(dmg) then + ply:SetPoisoned( true ) + end + if IsBleedingDamage( dmg ) and dmgimpact >= ConfigurationMedicMod.MinimumDamageToGetBleeding then + ply:SetBleeding( true ) + end + ply:SetHeartAttack( true ) + ply:MedicNotif(sentences["You fell unconscious following a heart attack"][lang], 10) + end +end) + +-- Create the death ragdoll, etc. +hook.Add("PlayerDeath", "PlayerDeath.MedicMod", function( victim, inf, att ) + + -- Save player weapons + victim.WeaponsStripped = {} + for k, v in pairs( victim:GetWeapons() ) do + table.insert(victim.WeaponsStripped,v:GetClass()) + end + + -- Save player ammo + victim.AmmoStripped = {} + for k, v in pairs( victim:GetAmmo() ) do + victim.AmmoStripped[k] = v + end + + -- set the next respawn time + timer.Simple( 0, function() + + local timebeforerespawn = CurTime()+ConfigurationMedicMod.TimeBeforeRespawnIfNoConnectedMedics + + for k, v in pairs( player.GetAll() ) do + if table.HasValue( ConfigurationMedicMod.MedicTeams, v:Team() ) then + timebeforerespawn = CurTime()+ConfigurationMedicMod.TimeBeforeRespawn + break + end + end + + victim.NextSpawnTime = timebeforerespawn + + net.Start("MedicMod.Respawn") + net.WriteInt(timebeforerespawn,32) + net.Send(victim) + + end ) + + if not IsValid( victim ) or not victim:GetHeartAttack() then return end + + if victim:InVehicle() then victim:ExitVehicle() end + + -- Create death ragdoll + local rag = victim:CreateDeathRagdoll() + + -- Remove ragdoll ent + timer.Simple(0.01, function() + if(victim:GetRagdollEntity() != nil and victim:GetRagdollEntity():IsValid()) then + victim:GetRagdollEntity():Remove() + end + end) + + -- Set the view on the ragdoll + victim:Spectate( OBS_MODE_DEATHCAM ) + victim:SpectateEntity( rag ) + +end) + +-- Determine if the player can respawn +hook.Add("PlayerDeathThink", "PlayerDeathThink.MedicMod", function( pl ) + if not ( pl.NextSpawnTime ) then pl.NextSpawnTime = 0 end + if pl.NextSpawnTime == -1 then return false end + if pl.NextSpawnTime > CurTime() then return false end + + if ConfigurationMedicMod.ForceRespawn then + pl:Spawn() + end + +end) + +hook.Add("PlayerSwitchWeapon", "PlayerSwitchWeapon.MedicMod", function( ply, old, new ) + + if not IsValid( old ) or not IsValid( ply ) then return end + + -- prevent switch weapon if the player is doing a heart massage + if old:GetClass() == "heart_massage" && ply:GetMedicAnimation() != 0 then + return true + end + if ply.CantSwitchWeapon then + return true + end + if ply.CantSwitchWeaponMF and ConfigurationMedicMod.CanBreakArms then + return true + end + +end) + +-- Start animations when player join +hook.Add("PlayerInitialSpawn", "PlayerInitialSpawn.MedicMod", function( ply ) + + timer.Simple(10, function() + net.Start("MedicMod.PlayerStartAnimation") + net.Send( ply ) + end) + +end) + +--[[ +local function MedicMod_DropCarriedRagdoll(ply, throw) + if not IsValid(ply) then return end + local rag = ply.CarryingRagdoll + if not IsValid(rag) then + ply.CarryingRagdoll = nil + ply:SetNWBool("CarryingRagdoll", false) + return + end + + rag:SetParent(nil) + rag:SetMoveType(MOVETYPE_VPHYSICS) + + local phys = rag:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(true) + phys:Wake() + if throw then + phys:SetVelocity(ply:GetAimVector() * 450 + ply:GetVelocity() * 0.5 + Vector(0,0,125)) + end + end + + rag:SetNWEntity("CarriedBy", NULL) + rag:SetNWBool("IsRagdollCarried", false) + + ply.CarryingRagdoll = nil + ply:SetNWBool("CarryingRagdoll", false) + + ply:MedicNotif(ConfigurationMedicMod.Sentences["You dropped the corpse"][ConfigurationMedicMod.Language], 5) +end + +hook.Add("PlayerButtonDown", "PlayerButtonDown.MedicMod.CarryRagdoll", function(ply, button) + if not IsValid(ply) or not ply:Alive() or button ~= KEY_E then return end + + if IsValid(ply.CarryingRagdoll) then + MedicMod_DropCarriedRagdoll(ply, true) + return + end + + local trace = ply:GetEyeTraceNoCursor() + local ent = trace.Entity + + if not IsValid(ent) or not ent:IsDeathRagdoll() then return end + if trace.HitPos:Distance(ply:GetPos()) > 120 then return end + if ent:GetNWEntity("CarriedBy", NULL):IsValid() then return end + if ent:GetNWBool("IsRagdollCarried", false) then return end + + ent:SetMoveType(MOVETYPE_NONE) + ent:SetParent(ply) + ent:SetLocalPos(Vector(10, -12, 18)) + ent:SetLocalAngles(Angle(90, 0, 0)) + + ent:SetNWEntity("CarriedBy", ply) + ent:SetNWBool("IsRagdollCarried", true) + + ply.CarryingRagdoll = ent + ply:SetNWBool("CarryingRagdoll", true) + + ply:MedicNotif(ConfigurationMedicMod.Sentences["You attached the corpse on your back"][ConfigurationMedicMod.Language], 5) +end) + +hook.Add("Think", "Think.MedicMod.CarryRagdoll", function() + for _, ply in ipairs(player.GetAll()) do + if not IsValid(ply) then continue end + local rag = ply.CarryingRagdoll + if not IsValid(rag) then continue end + + if not ply:Alive() then + MedicMod_DropCarriedRagdoll(ply, false) + continue + end + + local eye = ply:EyeAngles() + local backPos = ply:GetPos() + ply:GetForward() * -8 + ply:GetRight() * 4 + Vector(0,0,18) + rag:SetPos(backPos) + rag:SetAngles(eye + Angle(90,0,0)) + end +end) +]] + +-- When a player spawn an ambulance +hook.Add("PlayerSpawnedVehicle", "PlayerSpawnedVehicle.MedicMod", function( ply, ent ) + + if not IsValid( ent ) then return end + + if ConfigurationMedicMod.Vehicles[ent:GetModel()] then + + local button = ents.Create("ambulance_button_medicmod") + button:Spawn() + button:SetPos( ent:LocalToWorld(ConfigurationMedicMod.Vehicles[ent:GetModel()].buttonPos) ) + button:SetAngles( ent:LocalToWorldAngles(ConfigurationMedicMod.Vehicles[ent:GetModel()].buttonAngle) ) + button:SetParent( ent ) + + ent.Button = button + + local stretcher = ents.Create("stretcher_medicmod") + stretcher:Spawn() + stretcher:SetPos(ent:LocalToWorld(ConfigurationMedicMod.Vehicles[ent:GetModel()].stretcherPos)) + stretcher:SetAngles(ent:LocalToWorldAngles(ConfigurationMedicMod.Vehicles[ent:GetModel()].stretcherAngle)) + stretcher:SetParent( ent ) + + if not ConfigurationMedicMod.Vehicles[ent:GetModel()].drawStretcher then + stretcher:SetRenderMode( RENDERMODE_TRANSALPHA ) + stretcher:SetColor( Color(0,0,0,0) ) + end + + ent.Stretcher = stretcher + ent.SpawnedStretcher = stretcher + + end + +end) + +-- Remove the stretcher when vehicle is removed +hook.Add("EntityRemoved", "EntityRemoved.MedicMod", function( ent ) + + if not IsValid( ent ) then return end + + local stretch = ent.SpawnedStretcher or NULL + + if not IsValid( stretch ) then return end + + if stretch.ragdoll && IsValid( stretch.ragdoll ) then return end + + stretch:Remove() + +end) + +local FractureHitGroups = { + + [HITGROUP_LEFTLEG] = true, + [HITGROUP_RIGHTLEG] = true, + [HITGROUP_LEFTARM] = true, + [HITGROUP_RIGHTARM] = true, + +} + +-- break a bone +hook.Add("ScalePlayerDamage", "ScalePlayerDamage.MedicMod", function(ply, hitgroup, dmg) + + if not FractureHitGroups[hitgroup] then return end + + if dmg:GetDamage() < ConfigurationMedicMod.MinimumDamageToGetFractures then return end + + ply:SetFracture( true, hitgroup ) + +end) + +-- Save entities +local MedicModSavedEntities = { + ["terminal_medicmod"] = true, + ["radio_medicmod"] = true, + ["npc_health_seller_medicmod"] = true, + ["mural_defib_medicmod"] = true, + ["electrocardiogram_medicmod"] = true, + ["bed_medicmod"] = true, +} + +-- Commands +hook.Add("PlayerSay", "PlayerSay.MedicMod", function(ply, text) + + if text == "!save_medicmod" and ply:IsSuperAdmin() then + + local MedicPos = {} + + for k, v in pairs(ents.GetAll()) do + + if not MedicModSavedEntities[v:GetClass()] then continue end + + MedicPos[#MedicPos + 1] = { + pos = v:GetPos(), + ang = v:GetAngles(), + class = v:GetClass() + } + + file.CreateDir("medicmod") + + file.Write("medicmod/save_ents.txt", util.TableToJSON(MedicPos)) + + local filecontent = file.Read("medicmod/save_ents.txt", "DATA") + + ConfigurationMedicMod.SavedEnts = util.JSONToTable(filecontent) + + end + + ply:MedicNotif("Entities saved!") + + end + + if text == "!remove_medicmod" and ply:IsSuperAdmin() then + + if file.Exists("medicmod/save_ents.txt", "DATA") then + + file.Delete( "medicmod/save_ents.txt" ) + + ply:MedicNotif("Entities removed!") + + end + + local filecontent = file.Read("medicmod/save_ents.txt", "DATA") or "" + + ConfigurationMedicMod.SavedEnts = util.JSONToTable(filecontent) or {} + + end + + if text == "!reviveme" and ply:IsAdmin() and ConfigurationMedicMod.CanUseReviveMeCommand then + + ply:MedicalRespawn() + + end + + if text == "!"..ConfigurationMedicMod.MedicCommand and table.HasValue( ConfigurationMedicMod.MedicTeams, ply:Team() ) then + + net.Start("MedicMod.OpenMedicMenu") + net.Send( ply ) + + end + +end) + +-- Init the list of ents to spawn +hook.Add("Initialize", "Initialize.MedicMod", function() + + if not file.Exists("medicmod/save_ents.txt", "DATA") then return end + + local filecontent = file.Read("medicmod/save_ents.txt", "DATA") + + ConfigurationMedicMod.SavedEnts = util.JSONToTable(filecontent) + +end) + +-- spawn ents +hook.Add("InitPostEntity", "InitPostEntity.MedicMod", function() + + if not ConfigurationMedicMod.SavedEnts then return end + + timer.Simple(1, function() + for k, v in pairs(ConfigurationMedicMod.SavedEnts) do + local ent = ents.Create(v.class) + ent:SetPos( v.pos ) + ent:SetAngles( v.ang ) + ent:SetPersistent( true ) + ent:Spawn() + ent:SetMoveType( MOVETYPE_NONE ) + end + end) + +end) + +hook.Add("PostCleanupMap", "PostCleanupMap.MedicMod", function() + + if not ConfigurationMedicMod.SavedEnts then return end + + for k, v in pairs(ConfigurationMedicMod.SavedEnts) do + local ent = ents.Create(v.class) + ent:SetPos( v.pos ) + ent:SetAngles( v.ang ) + ent:SetPersistent( true ) + ent:Spawn() + ent:SetMoveType( MOVETYPE_NONE ) + end + +end) + +-- Can change job? +hook.Add("playerCanChangeTeam", "playerCanChangeTeam.MedicMod", function(ply) + if ply.NextSpawnTime and ply.NextSpawnTime > CurTime() then return false end + if ply.NextSpawnTime and ply.NextSpawnTime == -1 then return false end + if ply:GetMedicAnimation() != 0 then return false end +end) + +-- if someone change job +hook.Add("OnPlayerChangedTeam", "OnPlayerChangedTeam.MedicMod", function(ply, bef, after) + + if ConfigurationMedicMod.HealedOnChangingJob then + + ply:SetBleeding( false ) + ply:SetHeartAttack( false ) + ply:SetPoisoned( false ) + + if ply:GetFractures() then + -- cure fractures + for k, v in pairs( ply:GetFractures() ) do + ply:SetFracture(false, k) + end + end + + -- remove the death ragdoll + if IsValid( ply.DeathRagdoll ) then + if IsValid( ply.DeathRagdoll.Prop ) then + ply.DeathRagdoll.Prop:Remove() + end + ply.DeathRagdoll:Remove() + end + + ply:UnSpectate() + + else + + timer.Simple( 1, function() + if ply:GetFractures() then + for k, v in pairs( ply:GetFractures() ) do + ply:SetFracture(true, k) + end + end + + end) + + end + + if table.HasValue( ConfigurationMedicMod.MedicTeams, ply:Team() ) then + ply:MedicNotif(sentences["You're now a medic, get help with"][lang].." !"..ConfigurationMedicMod.MedicCommand) + end + +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_net.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_net.lua new file mode 100644 index 0000000..70182ea --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_net.lua @@ -0,0 +1,214 @@ +util.AddNetworkString("MedicMod.MedicMenu") +util.AddNetworkString("MedicMod.MedicStart") +util.AddNetworkString("MedicMod.BuyMedicEntity") +-- util.AddNetworkString("MedicMod.NotifiyPlayer") +util.AddNetworkString("MedicMod.PlayerStartAnimation") +util.AddNetworkString("MedicMod.ScanRadio") +util.AddNetworkString("MedicMod.Respawn") +util.AddNetworkString("MedicMod.TerminalMenu") +util.AddNetworkString("MedicMod.PlayerStopAnimation") +util.AddNetworkString("MedicMod.OpenMedicMenu") +util.AddNetworkString("MedicMod.BuyMedicJobEntity") + +local spamCooldowns = {} +local interval = .1 + +local function spamCheck(pl, name) + if spamCooldowns[pl:SteamID()] then + if spamCooldowns[pl:SteamID()][name] then + if spamCooldowns[pl:SteamID()][name] > CurTime() then + return false + else + spamCooldowns[pl:SteamID()][name] = CurTime() + interval + return true + end + else + spamCooldowns[pl:SteamID()][name] = CurTime() + interval + return true + end + else + spamCooldowns[pl:SteamID()] = {} + spamCooldowns[pl:SteamID()][name] = CurTime() + interval + + return true // They haven't sent shit :P + end +end + + +net.Receive("MedicMod.MedicStart", function( len, caller ) + + if not spamCheck( caller, "MedicMod.MedicStart" ) then return end + + local ent = net.ReadEntity() + local health = caller:Health() + + if caller:GetPos():DistToSqr(ent:GetPos()) > 22500 then return end + if health <= 0 then return end + if health >= caller:GetMaxHealth() and not caller:HaveFractures() then return end + + local price = 0 + + if health < caller:GetMaxHealth() then + price = ( caller:GetMaxHealth() - health ) * ConfigurationMedicMod.HealthUnitPrice + end + + if caller:HaveFractures() then + price = price + table.Count(caller.Fractures) * ConfigurationMedicMod.FractureRepairPrice + end + + local bed = nil + local dist = -1 + for k, v in pairs( ents.FindByClass("bed_medicmod") ) do + if IsValid( v.ragdoll ) then continue end + + local ndist = v:GetPos():Distance( caller:GetPos() ) + if dist == -1 or dist > ndist then + bed = v + dist = ndist + end + + end + + if not bed or not IsValid( bed ) then caller:MedicNotif(ConfigurationMedicMod.Sentences["You can't be healed because there is no free bed, retry later"][ConfigurationMedicMod.Language]) return end + + if caller:getDarkRPVar("money") < price then caller:MedicNotif(ConfigurationMedicMod.Sentences["You don't have enough money"][ConfigurationMedicMod.Language]) return end + + caller:addMoney( -price ) + + caller:StartOperation( bed ) + +end) + +net.Receive("MedicMod.Respawn",function(len,ply) + + if not spamCheck( ply, "MedicMod.Respawn" ) then return end + + if ply.NextSpawnTime > CurTime() or ply:Alive() or ply.NextSpawnTime == -1 then return end + ply:Spawn() +end) + +net.Receive("MedicMod.BuyMedicEntity", function( len, caller ) + + if not spamCheck( caller, "MedicMod.BuyMedicEntity" ) then return end + + local key = net.ReadInt( 32 ) + local ent = net.ReadEntity() + + local cfg = ConfigurationMedicMod.Entities[key] + + if not IsValid( ent ) then return end + if caller:GetPos():Distance(ent:GetPos()) > 200 then return end + if caller:getDarkRPVar("money") < cfg.price then caller:MedicNotif(ConfigurationMedicMod.Sentences["You don't have enough money"][ConfigurationMedicMod.Language]) return end + + cfg.func( caller, cfg.ent, cfg.price ) + +end) + +net.Receive("MedicMod.BuyMedicJobEntity", function( len, caller ) + + if not spamCheck( caller, "MedicMod.BuyMedicJobEntity" ) then return end + + local key = net.ReadString() + local class = net.ReadString() + + local spawnedtesttubes = caller.TestTubesSpawned or 0 + local spawneddrugs = caller.DrugsSpawned or 0 + local spawnedEnts = caller.spawnedEntsMedicMod or {} + + if not table.HasValue( ConfigurationMedicMod.MedicTeams, caller:Team() ) then return end + + if class == "test_tube_medicmod_s" and spawnedtesttubes < ConfigurationMedicMod.MaxTestTubesSpawnedWithTheShop then + + if not ConfigurationMedicMod.Reagents[key] then return end + + local price = ConfigurationMedicMod.Reagents[key].price + + if caller:getDarkRPVar("money") < price then caller:MedicNotif(ConfigurationMedicMod.Sentences["You don't have enough money"][ConfigurationMedicMod.Language]) return end + + caller:addMoney( -price ) + + local ent = ents.Create("test_tube_medicmod") + ent:SetPos(caller:GetPos() + caller:GetAngles():Forward() * 30 + caller:GetAngles():Up() * 20 ) + ent:SetProduct(key) + ent:Spawn() + + if CPPI then + ent:CPPISetOwner(caller) + end + + undo.Create( ConfigurationMedicMod.Sentences["Test tube"][ConfigurationMedicMod.Language] ) + undo.AddEntity( ent ) + undo.SetPlayer( caller ) + undo.Finish() + + ent.TestTubeSpawner = caller + caller.TestTubesSpawned = spawnedtesttubes + 1 or 1 + + elseif class == "test_tube_medicmod_s" and spawnedtesttubes >= ConfigurationMedicMod.MaxTestTubesSpawnedWithTheShop then + caller:MedicNotif(ConfigurationMedicMod.Sentences["You've reached the limit of"][ConfigurationMedicMod.Language].." "..ConfigurationMedicMod.Sentences["Test tube"][ConfigurationMedicMod.Language]) + elseif class == "drug_medicmod_s" and spawneddrugs < ConfigurationMedicMod.MaxDrugsSpawnedWithTheShop then + + if not ConfigurationMedicMod.Drugs[key] then return end + + local price = ConfigurationMedicMod.Drugs[key].price + + if caller:getDarkRPVar("money") < price then caller:MedicNotif(ConfigurationMedicMod.Sentences["You don't have enough money"][ConfigurationMedicMod.Language]) return end + + caller:addMoney( -price ) + + local ent = ents.Create("drug_medicmod") + ent:SetPos(caller:GetPos() + caller:GetAngles():Forward() * 30 + caller:GetAngles():Up() * 20) + ent:Spawn() + ent:SetDrug(key) + + if CPPI then + ent:CPPISetOwner(caller) + end + + undo.Create( ConfigurationMedicMod.Sentences["Drugs"][ConfigurationMedicMod.Language] ) + undo.AddEntity( ent ) + undo.SetPlayer( caller ) + undo.Finish() + + ent.DrugSpawner = caller + caller.DrugsSpawned = spawneddrugs + 1 or 1 + + elseif class == "drug_medicmod_s" and spawneddrugs >= ConfigurationMedicMod.MaxDrugsSpawnedWithTheShop then + caller:MedicNotif(ConfigurationMedicMod.Sentences["You've reached the limit of"][ConfigurationMedicMod.Language].." "..ConfigurationMedicMod.Sentences["Drugs"][ConfigurationMedicMod.Language]) + elseif key == "entity" and ConfigurationMedicMod.MedicShopEntities[class] and (spawnedEnts[class] or 0) < ConfigurationMedicMod.MedicShopEntities[class].max then + + local cfg = ConfigurationMedicMod.MedicShopEntities[class] + + if caller:getDarkRPVar("money") < cfg.price then caller:MedicNotif(ConfigurationMedicMod.Sentences["You don't have enough money"][ConfigurationMedicMod.Language]) return end + caller:addMoney( -cfg.price ) + + local ent = ents.Create(class) + ent:SetPos(caller:GetPos() + caller:GetAngles():Forward() * 30 + caller:GetAngles():Up() * 20 ) + ent:Spawn() + + if CPPI then + ent:CPPISetOwner(caller) + end + + undo.Create( (cfg.name or "Medic mod entity") ) + undo.AddEntity( ent ) + undo.SetPlayer( caller ) + undo.Finish() + + caller.spawnedEntsMedicMod = caller.spawnedEntsMedicMod or {} + caller.spawnedEntsMedicMod[class] = caller.spawnedEntsMedicMod[class] or 0 + caller.spawnedEntsMedicMod[class] = caller.spawnedEntsMedicMod[class] + 1 + + elseif key == "entity" and ConfigurationMedicMod.MedicShopEntities[class] and (spawnedEnts[class] or 0) >= ConfigurationMedicMod.MedicShopEntities[class].max then + caller:MedicNotif(ConfigurationMedicMod.Sentences["You've reached the limit of"][ConfigurationMedicMod.Language].." "..ConfigurationMedicMod.MedicShopEntities[class].name) + elseif key == "weapon" and ConfigurationMedicMod.MedicShopWeapons[class] then + if caller:HasWeapon( class ) then caller:MedicNotif(ConfigurationMedicMod.Sentences["You already carry this element on you"][ConfigurationMedicMod.Language]) return end + + local cfg = ConfigurationMedicMod.MedicShopWeapons[class] + + if caller:getDarkRPVar("money") < cfg.price then caller:MedicNotif(ConfigurationMedicMod.Sentences["You don't have enough money"][ConfigurationMedicMod.Language]) return end + caller:addMoney( -cfg.price ) + + caller:Give(class) + end +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_notify.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_notify.lua new file mode 100644 index 0000000..7a96656 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/server/sv_notify.lua @@ -0,0 +1,35 @@ +-- local meta = FindMetaTable( "Player" ) + +-- function meta:MedicNotif( msg, time ) + + -- local ply = self + + -- if not IsValid( ply ) or not ply:IsPlayer() then return end + + -- msg = msg or "" + -- time = time or 5 + + -- net.Start("MedicMod.NotifiyPlayer") + -- net.WriteString( msg ) + -- net.WriteInt( time, 32 ) + -- net.Send( ply ) +-- end + +local meta = FindMetaTable( "Player" ) + +util.AddNetworkString("MedicMod:NotifyPlayer") + +function meta:MedicNotif( msg, time ) + + local ply = self + + if not IsValid( ply ) or not ply:IsPlayer() then return end + + msg = msg or "" + time = time or 5 + + net.Start("MedicMod:NotifyPlayer") + net.WriteString( msg ) + net.WriteInt( time, 32 ) + net.Send( ply ) +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_config.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_config.lua new file mode 100644 index 0000000..6cb79a3 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_config.lua @@ -0,0 +1,303 @@ +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- +-- Base Configuration +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + +ConfigurationMedicMod.AddonName = "MedicMOD" + +-- if set to false, the player would have to press E on the terminal to open the buying menu. +ConfigurationMedicMod.Terminal3D2D = true + +-- Percentage of chance that the shock of the defibrillator works ( Default : 50 ) +ConfigurationMedicMod.DefibrillatorShockChance = 50 + +ConfigurationMedicMod.MorphineEffectTime = 10 + +ConfigurationMedicMod.MoneyUnit = "$" + +ConfigurationMedicMod.MedicCommand = "medic" +ConfigurationMedicMod.MaxTestTubesSpawnedWithTheShop = 4 +ConfigurationMedicMod.MaxDrugsSpawnedWithTheShop = 3 + +ConfigurationMedicMod.ScanRadioTime = 5 + +ConfigurationMedicMod.HealedOnChangingJob = true + +-- Only medic can use the button to take a stretcher in a vehicle? +ConfigurationMedicMod.OnlyMedicCanUseVehicleButton = true + +ConfigurationMedicMod.SurgicalOperationTime = 15 +ConfigurationMedicMod.HealthUnitPrice = 1 +ConfigurationMedicMod.FractureRepairPrice = 50 + +ConfigurationMedicMod.ForceRespawn = false + +-- Time that the player will have to wait to respawn if there is no medics connected +ConfigurationMedicMod.TimeBeforeRespawnIfNoConnectedMedics = 30 +-- Time that the player will have to wait to respawn if there is medics connected +ConfigurationMedicMod.TimeBeforeRespawn = 60 +-- Time that the player will have to wait to respawn if someone has stabilized his state +ConfigurationMedicMod.TimeBeforeRespawnIfStable = 360 + +ConfigurationMedicMod.DamagePerSecsDuringBleeding = 1 +ConfigurationMedicMod.DamagePerSecsDuringPoisoned = 0.25 + +-- if the player get X damage on his leg or his arm, then he'll have a fracture +ConfigurationMedicMod.MinimumDamageToGetFractures = 20 +ConfigurationMedicMod.MinimumDamageToGetBleeding = 20 + +ConfigurationMedicMod.CanGetFractures = true +-- if a player get a damage in his hands, I will not be able to switch weapon +ConfigurationMedicMod.CanBreakArms = true +ConfigurationMedicMod.CanGetBleeding = true +ConfigurationMedicMod.CanGetPoisoned = true + +-- now set to false by defaut, because it caused some crashs. +ConfigurationMedicMod.DecalsBleeding = false + +ConfigurationMedicMod.TimeToQuickAnalyse = 5 + +-- If the player can do CPR to another player +ConfigurationMedicMod.CanCPR = true +-- CPR Key, list of keys can be find here : http://wiki.garrysmod.com/page/Enums/BUTTON_CODE +ConfigurationMedicMod.CPRKey = KEY_R + +ConfigurationMedicMod.FracturePlayerSpeed = 100 + +-- if !reviveme can be used +ConfigurationMedicMod.CanUseReviveMeCommand = true + +ConfigurationMedicMod.Vehicles["models/perrynsvehicles/ford_f550_ambulance/ford_f550_ambulance.mdl"] = { + buttonPos = Vector(-40.929615,-139.366989,60.128445), + buttonAngle = Angle(0,0,90), + stretcherPos = Vector(0,-80,40), + stretcherAngle = Angle(0,0,0), + drawStretcher = false, + backPos = Vector(0,-170,50) +} + +ConfigurationMedicMod.Vehicles["models/perrynsvehicles/2015_mercedes_nhs_ambulance/2015_mercedes_nhs_ambulance.mdl"] = { + buttonPos = Vector(40,-158,70), + buttonAngle = Angle(90,-90,0), + stretcherPos = Vector(13,-95,45), + stretcherAngle = Angle(0,0,0), + drawStretcher = false, + backPos = Vector(0,-170,50) +} + +-- list of entities that the medic can buy with his command +ConfigurationMedicMod.MedicShopEntities = { + ["firstaidkit_medicmod"] = { + name = "First aid kit", + price = 100, + model = "models/medicmod/firstaidkit/firstaidkit.mdl", + max = 1, + }, + ["beaker_medicmod"] = { + name = "Empty beaker", + price = 20, + model = "models/medicmod/beaker/beaker.mdl", + max = 1, + }, + ["bloodbag_medicmod"] = { + name = "Blood bag", + price = 20, + model = "models/medicmod/bloodbag/bloodbag.mdl", + max = 1, + }, + ["drip_medicmod"] = { + name = "Drip", + price = 120, + model = "models/medicmod/medical_stand/medical_stand.mdl", + max = 1, + }, + ["drug_medicmod"] = { + name = "Emtpy drug jar", + price = 10, + model = "models/medicmod/drug/drug.mdl", + max = 1, + }, + +}-- list of weapons that the medic can buy with his command +ConfigurationMedicMod.MedicShopWeapons = { + ["syringe_antidote"] = { + name = "Antidote syringe", + price = 50, + model = "models/medicmod/syringe/w_syringe.mdl", + }, + ["bandage"] = { + name = "Bandage", + price = 20, + model = "models/medicmod/bandage/w_bandage.mdl", + }, + ["defibrillator"] = { + name = "Defibrillator", + price = 200, + model = "models/medicmod/defib/w_defib.mdl", + }, + ["syringe_morphine"] = { + name = "Morphine syringe", + price = 150, + model = "models/medicmod/syringe/w_syringe.mdl", + }, + ["syringe_poison"] = { + name = "Poison syringe", + price = 300, + model = "models/medicmod/syringe/w_syringe.mdl", + }, + +} + +timer.Simple(0, function() -- don't touch this + + +ConfigurationMedicMod.MedicTeams = { + TEAM_MEDIC, + TEAM_PARAMEDIC, + } +ConfigurationMedicMod.TeamsCantGetFracture = { + } +ConfigurationMedicMod.TeamsCantGetBleeding = { + } +ConfigurationMedicMod.TeamsCantGetPoisoned = { + + } +ConfigurationMedicMod.TeamsCantPracticeCPR = { + + } +ConfigurationMedicMod.TeamsCantReceiveCPR = { + + } + +end) -- don't touch this + +local lang = ConfigurationMedicMod.Language +local sentences = ConfigurationMedicMod.Sentences +local function AddReagent( name, price, color ) ConfigurationMedicMod.AddReagent( name, price, color ) end +local function AddDrug( name, price, ing1, ing2, ing3, func ) ConfigurationMedicMod.AddDrug( name, price, ing1, ing2, ing3, func ) end + +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + +-- Add reagents (ingredients) for drugs +-- AddReagent( name, price, color ) + + +AddReagent("Aminophenol", 20 ,Color(255,0,0)) +AddReagent("Water", 20, Color(64, 164, 223) ) +AddReagent("Ethanoic anhydride", 20,Color(255,255,0)) +AddReagent("Potassium iodide", 20, Color(255,255,255)) +AddReagent("Ethanol", 20,Color(255,255,255,150)) +AddReagent("Sulfuric acid", 20,Color(0,255,0)) +AddReagent("Calcium (left arm)", 20,Color(120,140,126)) +AddReagent("Calcium (right arm)", 20,Color(120,140,126)) +AddReagent("Calcium (left leg)", 20,Color(120,140,126)) +AddReagent("Calcium (right leg)", 20,Color(120,140,126)) + +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + +-- Add drugs +-- AddDrug( name, price, reagent1, reagent2, reagent3 , function(ply) end ) +-- if there is no reagent 2 or 3 then replace them by nil + +AddDrug("Tylenol", 150, "Aminophenol", "Water", "Ethanoic anhydride", function(ply) ply:MedicNotif(sentences["You have been healed"][lang]) ply:SetHealth(ply:GetMaxHealth()) end ) +AddDrug("Antidote", 150, "Potassium iodide", nil, nil, function(ply) ply:SetPoisoned( false ) ply:MedicNotif( sentences["You have stopped your poisoning"][lang], 5 ) end) +AddDrug("Morphine", 150, "Water","Ethanol","Sulfuric acid", function(ply) ply:SetMorphine( true ) ply:MedicNotif( sentences["You injected some morphine"][lang], 5 ) end) +AddDrug("Medicine (Left arm)", 150,"Calcium (left arm)", nil, nil, function(ply) ply:SetFracture( false, HITGROUP_LEFTARM) end) +AddDrug("Drug (Right arm)", 150, "Calcium (right arm)", nil, nil, function(ply) ply:SetFracture( false, HITGROUP_RIGHTARM) end ) +AddDrug("Drug (Left leg)",150,"Calcium (left leg)", nil, nil, function(ply) ply:SetFracture( false, HITGROUP_LEFTLEG) end ) +AddDrug("Drug (Right leg)", 150, "Calcium (right leg)", nil, nil, function(ply) ply:SetFracture( false, HITGROUP_RIGHTLEG) end ) + +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + +-- List of entities that can be bought with the terminal +ConfigurationMedicMod.Entities[1] = { + name = sentences["Bandage"][lang], + ent = "bandage", + price = 75, + mdl = "models/medicmod/bandage/bandage.mdl", + func = function(ply, ent, price) + if ply:HasWeapon( ent ) then + ply:MedicNotif(sentences["You already carry this element on you"][lang]) + return + end + ply:addMoney( -price ) + ply:Give(ent) + end +} +ConfigurationMedicMod.Entities[2] = { + name = sentences["Morphine"][lang], + ent = "syringe_morphine", + price = 100, + mdl = "models/medicmod/syringe/w_syringe.mdl", + func = function(ply, ent, price) + if ply:HasWeapon( ent ) then + ply:MedicNotif(sentences["You already carry this element on you"][lang]) + return + end + ply:addMoney( -price ) + ply:Give(ent) + end +} +ConfigurationMedicMod.Entities[3] = { + name = sentences["Antidote"][lang], + ent = "syringe_antidote", + price = 50, + mdl = "models/medicmod/syringe/w_syringe.mdl", + func = function(ply, ent, price) + if ply:HasWeapon( ent ) then + ply:MedicNotif(sentences["You already carry this element on you"][lang]) + return + end + ply:addMoney( -price ) + ply:Give(ent) + end +} +ConfigurationMedicMod.Entities[4] = { + name = sentences["First Aid Kit"][lang], + ent = "first_aid_kit", + price = 400, + mdl = "models/medicmod/firstaidkit/firstaidkit.mdl", + func = function(ply, ent, price) + if ply:HasWeapon( ent ) then + ply:MedicNotif(sentences["You already carry a medical kit on you"][lang]) + return + end + ply:addMoney( -price ) + ply:Give(ent) + local weap = ply:GetWeapon(ent) + weap:SetBandage( 3 ) + weap:SetAntidote( 1 ) + weap:SetMorphine( 2 ) + end +} + +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + +ConfigurationMedicMod.DamagePoisoned[DMG_PARALYZE]=true +ConfigurationMedicMod.DamagePoisoned[DMG_POISON]=true +ConfigurationMedicMod.DamagePoisoned[DMG_ACID]=true +ConfigurationMedicMod.DamagePoisoned[DMG_RADIATION]=true + +ConfigurationMedicMod.DamageBleeding[DMG_BULLET]=true +ConfigurationMedicMod.DamageBleeding[DMG_CRUSH]=true +ConfigurationMedicMod.DamageBleeding[DMG_SLASH]=true +ConfigurationMedicMod.DamageBleeding[DMG_VEHICLE]=true +ConfigurationMedicMod.DamageBleeding[DMG_BLAST]=true +ConfigurationMedicMod.DamageBleeding[DMG_CLUB]=true +ConfigurationMedicMod.DamageBleeding[DMG_AIRBOAT]=true +ConfigurationMedicMod.DamageBleeding[DMG_BLAST_SURFACE]=true +ConfigurationMedicMod.DamageBleeding[DMG_BUCKSHOT]=true +ConfigurationMedicMod.DamageBleeding[DMG_PHYSGUN]=true +ConfigurationMedicMod.DamageBleeding[DMG_FALL]=true +ConfigurationMedicMod.DamageBleeding[DMG_MEDICMODBLEEDING]=true + +ConfigurationMedicMod.DamageBurn[DMG_BURN]=true +ConfigurationMedicMod.DamageBurn[DMG_SLOWBURN]=true + +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_darkrp_config.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_darkrp_config.lua new file mode 100644 index 0000000..c04f969 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_darkrp_config.lua @@ -0,0 +1,46 @@ +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + +local lang = ConfigurationMedicMod.Language +local sentences = ConfigurationMedicMod.Sentences + +-- add entities to the medic job +timer.Simple(0, function() + + DarkRP.createEntity("Beaker", { + ent = "beaker_medicmod", + model = "models/medicmod/beaker/beaker.mdl", + price = 30, + max = 2, + cmd = "buybeaker", + allowed = ConfigurationMedicMod.MedicTeams, + }) + + DarkRP.createEntity("Drug pot", { + ent = "drug_medicmod", + model = "models/medicmod/drug/drug.mdl", + price = 10, + max = 2, + cmd = "buydrugpot", + allowed = ConfigurationMedicMod.MedicTeams, + }) + + DarkRP.createEntity("Blood bag", { + ent = "bloodbag_medicmod", + model = "models/medicmod/bloodbag/bloodbag.mdl", + price = 10, + max = 2, + cmd = "buybloodbag", + allowed = ConfigurationMedicMod.MedicTeams, + }) + + DarkRP.createEntity("Drip", { + ent = "drip_medicmod", + model = "models/medicmod/medical_stand/medical_stand.mdl", + price = 100, + max = 2, + cmd = "buydrip", + allowed = ConfigurationMedicMod.MedicTeams, + }) + +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_lang.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_lang.lua new file mode 100644 index 0000000..f7f6da3 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/sh_lang.lua @@ -0,0 +1,1083 @@ +ConfigurationMedicMod.Language = "en" + +---------------------------------------------------------------------------------- +---------------------------------------------------------------------------------- + +-- Translate +ConfigurationMedicMod.Sentences["Bleeding"] = { + ["en"] = "Кровотечение", + ["fr"] = "Hémorragie", + ["ger"] = "Blutung", + ["es"] = "Hemorragia", + ["pt"] = "Hemorragia", + ["kr"] = "출혈", +} +ConfigurationMedicMod.Sentences["StopBleedingOfSomeone"] = { + ["en"] = "Вы остановили чье-то кровотечение.", + ["fr"] = "Vous avez arrêté l'hémorragie de quelqu'un.", + ["ger"] = "Du hast die Blutung von jemamden gestoppt.", + ["es"] = "Has parado la hemorragia de alguien.", + ["pt"] = "Você parou a hemorragia de alguém.", + ["kr"] = "당신은 누군가에게 지혈 하였습니다.", +} +ConfigurationMedicMod.Sentences["SomeoneStopBleedingOfYou"] = { + ["en"] = "Кто-то остановил твое кровотечение с помощью повязки.", + ["fr"] = "Quelqu'un a arrêté votre hémorragie à l'aide d'un bandage.", + ["ger"] = "Jemand stoppte deine Blutung mit einem Verband.", + ["es"] = "Alguien ha parado tu hemorragia con una venda.", + ["pt"] = "Alguém estancou seu sangramento com bandagens.", + ["kr"] = "누군가 당신에게 지혈 하였습니다.", +} +ConfigurationMedicMod.Sentences["StopBleedingOfYourself"] = { + ["en"] = "Ты остановил свое кровотечение.", + ["fr"] = "Vous avez arrêté votre hémorragie.", + ["ger"] = "Du hast deine Blutung gestoppt.", + ["es"] = "Has parado tu hemorragia.", + ["pt"] = "Você estancou sua hemorragia.", + ["kr"] = "당신 스스로 지혈 하였습니다.", +} +ConfigurationMedicMod.Sentences["You have stopped the poisoning of someone"] = { + ["en"] = "Вы остановили чье-то отравление.", + ["fr"] = "Vous avez arrêté l'empoisonnement de quelqu'un.", + ["ger"] = "Du hast die Vergiftung von jemanden gestoppt.", + ["es"] = "Has curado el envenenamiento de alguien.", + ["pt"] = "Você parou o envenenamento de alguém.", + ["kr"] = "누군가에게 해독 하였습니다.", +} +ConfigurationMedicMod.Sentences["Someone stopped your poisoning"] = { + ["en"] = "Кто-то остановил твое отравление.", + ["fr"] = "Quelqu'un a arrêté votre empoisonnement.", + ["ger"] = "Jemand hat deine Vergiftung gestoppt.", + ["es"] = "Alguien ha curado tu envenenamiento.", + ["pt"] = "Alguém curou seu envenenamento.", + ["kr"] = "누군가가 당신에게 해독을 하였습니다.", +} +ConfigurationMedicMod.Sentences["You have stopped your poisoning"] = { + ["en"] = "Вы прекратили свое отравление.", + ["fr"] = "Vous avez arrêté votre empoisonnement.", + ["ger"] = "Du hast deine Vergiftung gestoppt.", + ["es"] = "Te has curado el envenenamiento.", + ["pt"] = "Você parou seu envenenamento.", + ["kr"] = "당신의 독이 중화되었습니다.", +} +ConfigurationMedicMod.Sentences["Notebook"] = { + ["en"] = "Блокнот.", + ["fr"] = "Carnet de notes.", + ["ger"] = "Notitzheft.", + ["es"] = "Libreta.", + ["pt"] = "Livro de Notas.", + ["kr"] = "메모장", +} +ConfigurationMedicMod.Sentences["Writing"] = { + ["en"] = "Пишу", + ["fr"] = "Ecriture", + ["ger"] = "Schreiben", + ["es"] = "Escribiendo", + ["pt"] = "Escrevendo", + ["kr"] = "쓰는중", +} +ConfigurationMedicMod.Sentences["The name of the individu is"] = { + ["en"] = "Имя индивидуума-это", + ["fr"] = "Le nom de l'individu est", + ["ger"] = "Der Name des Individuums ist", + ["es"] = "El nombre del individuo es", + ["pt"] = "O nome do paciente é", + ["kr"] = "치명상을 입은 피해자의 신원은", +} +ConfigurationMedicMod.Sentences["There's blood everywhere, it seems he's bleeding"] = { + ["en"] = "Повсюду кровь, кажется, он истекает кровью..", + ["fr"] = "Il y a du sang partout, il semble qu'il fait une hémorragie.", + ["ger"] = "Da ist überall Blut, es sieht so aus als würde er bluten.", + ["es"] = "Hay sangre por todos lados, parece que tiene una hemorragia.", + ["pt"] = "Tem sangue por todos os lados, ele está com uma hemorragia grave.", + ["kr"] = "피가 곳곳에 튀어있고, 피가 멈추지 않는다.", +} +ConfigurationMedicMod.Sentences["He is greenish, may have been poisoned"] = { + ["en"] = "Он зеленоватый, возможно, был отравлен.", + ["fr"] = "L'individu est verdâtre, il a peut être été empoisonné.", + ["ger"] = "Er ist grünlich, er könnte vergiftet worden sein.", + ["es"] = "Tiene un aspecto un poco verde, puede haber sido envenenado.", + ["pt"] = "Ele está com a pele esverdeada, deve ter sido envenenado.", + ["kr"] = "그의 안면이 시퍼렇다, 아마 중독된 것 같다.", +} +ConfigurationMedicMod.Sentences["His heart no longer beats"] = { + ["en"] = "Его сердце больше не бьется.", + ["fr"] = "Le coeur de l'individu ne bat plus.", + ["ger"] = "Sein Herz schlägt nicht länger.", + ["es"] = "Su corazón ya no palpita.", + ["pt"] = "Ele está em parada cardiorrespiratória!", + ["kr"] = "그의 심장은 더 이상 뛰지 않는다.", +} +ConfigurationMedicMod.Sentences["You fell unconscious following a heart attack"] = { + ["en"] = "Вы потеряли сознание после сердечного приступа.", + ["fr"] = "Vous avez perdu conscience suite à une crise cardiaque.", + ["ger"] = "Du hattest einen Herzinfarkt und wurdest bewusstlos.", + ["es"] = "Te has desmayado al sufrir un ataque cardiaco.", + ["pt"] = "Você tem uma síncope seguida de uma parada cardíaca.", + ["kr"] = "심장마비로 인하여 당신은 기절 하였습니다.", +} +ConfigurationMedicMod.Sentences["Poisoned"] = { + ["en"] = "Отравленный", + ["fr"] = "Empoisonné", + ["ger"] = "Vergifet", + ["es"] = "Envenenado", + ["pt"] = "Envenenado", + ["kr"] = "중독", +} +ConfigurationMedicMod.Sentences["Morphine"] = { + ["en"] = "Морфин", + ["fr"] = "Morphine", + ["ger"] = "Morphine", + ["es"] = "Morfina", + ["pt"] = "Morfina", + ["kr"] = "모르핀", +} +ConfigurationMedicMod.Sentences["Your condition has been stabilized"] = { + ["en"] = "Ваше состояние стабилизировалось.", + ["fr"] = "Votre état à été stabilisé.", + ["ger"] = "Dein Zustand wurde stabilisiert.", + ["es"] = "Tu estado ahora es estable.", + ["pt"] = "Sua condição agora é estável.", + ["kr"] = "당신의 상태는 안정 되었습니다.", +} +ConfigurationMedicMod.Sentences["You stabilized the wounded"] = { + ["en"] = "Вы стабилизировали состояние раненых.", + ["fr"] = "Vous avez stabilisé l'état du blessé.", + ["ger"] = "Du hast den Verwundeten stabilisiert.", + ["es"] = "Has estabilizado al herido.", + ["pt"] = "Você estabilizou a ferida.", + ["kr"] = "당신의 상처가 아물었습니다.", +} +ConfigurationMedicMod.Sentences["Someone is giving you CPR"] = { + ["en"] = "Кто-то делает тебе искусственное дыхание.", + ["fr"] = "Quelqu'un vous fait un massage cardiaque.", + ["ger"] = "Jemand reanimiert dich.", + ["es"] = "Alguien te está haciendo una reanimación cardiopulmonar.", + ["pt"] = "Alguém está realizando um RCP em você!", + ["kr"] = "누군가에게 CPR 을 시도하고 있습니다.", +} +ConfigurationMedicMod.Sentences["You are starting CPR"] = { + ["en"] = "Вы начинаете искусственное дыхание.", + ["fr"] = "Vous commencez un massage cardiaque.", + ["ger"] = "Du startest eine Reanimation.", + ["es"] = "Has comenzado a hacer una reanimación cardiopulmonar.", + ["pt"] = "Você iniciou um RCP.", + ["kr"] = "CPR 을 시작하였습니다.", +} +ConfigurationMedicMod.Sentences["You attached the corpse on your back"] = { + ["en"] = "You hooked the corpse to your back.", + ["fr"] = "Vous avez attaché le cadavre sur votre dos.", + ["ger"] = "Du hast die Leiche auf deinem Rücken befestigt.", + ["es"] = "Has enganchado el cadáver a tu espalda.", + ["pt"] = "Você prendeu o cadáver nas suas costas.", + ["kr"] = "시체를 등에 고정했습니다.", +} +ConfigurationMedicMod.Sentences["You dropped the corpse"] = { + ["en"] = "You threw the corpse away.", + ["fr"] = "Vous avez jeté le cadavre.", + ["ger"] = "Du hast die Leiche weggeworfen.", + ["es"] = "Has lanzado el cadáver.", + ["pt"] = "Você jogou o cadáver fora.", + ["kr"] = "시체를 던졌습니다.", +} +ConfigurationMedicMod.Sentences["Press E to carry corpse"] = { + ["en"] = "Press E to carry corpse and press E again to throw.", + ["fr"] = "Appuyez sur E pour porter le cadavre, puis E pour le jeter.", + ["ger"] = "Drücke E, um die Leiche zu tragen, und erneut E, um sie zu werfen.", + ["es"] = "Pulsa E para llevar el cadáver y de nuevo E para lanzarlo.", + ["pt"] = "Pressione E para carregar o cadáver e novamente E para jogá-lo.", + ["kr"] = "E를 눌러 시체를 등짐하고 다시 E를 눌러 던지세요.", +} +ConfigurationMedicMod.Sentences["Heart Attack"] = { + ["en"] = "сердечный приступ", + ["fr"] = "Crise cardiaque", + ["ger"] = "Herzinfarkt", + ["es"] = "Ataque cardiaco", + ["pt"] = "Parada Cardíaca", + ["kr"] = "심장마비", +} +ConfigurationMedicMod.Sentences["Your heart started to beat again"] = { + ["en"] = "Твое сердце снова начало биться.", + ["fr"] = "Votre coeur s'est remis à battre.", + ["ger"] = "Dein Herz fängt wieder an zu schlagen.", + ["es"] = "Tu corazón ha vuelto a palpitar otra vez.", + ["pt"] = "Seu coração voltou a bater.", + ["kr"] = "당신의 심장은 다시 뛰기 시작하였습니다.", +} +ConfigurationMedicMod.Sentences["The heart of the wounded man began to beat again"] = { + ["en"] = "Сердце раненого снова забилось.", + ["fr"] = "Le coeur du blessé s'est remis à battre.", + ["ger"] = "Das Herz des Verwundeten begann wieder zu schlagen.", + ["es"] = "El corazón de la persona herida ha vuelto a palpitar otra vez.", + ["pt"] = "O coração do paciente voltou a bater.", + ["kr"] = "부상자의 그 심장은 다시 뛰기 시작하였습니다.", +} + +ConfigurationMedicMod.Sentences["The shock did not work. Try again"] = { + ["en"] = "Шок не подействовал. Пробовать снова.", + ["fr"] = "Le choc n'a pas fonctionné. Réessayez.", + ["ger"] = "Der Elektroschocker wirkte nicht. Versuchs nochmal.", + ["es"] = "El electrochoque no ha funcionado. Prueba otra vez.", + ["pt"] = "O coração continua fibrilando. Tente novamente.", + ["kr"] = "충격은 소용이 없었습니다, 다시 시도하세요.", +} +ConfigurationMedicMod.Sentences["You injected some morphine"] = { + ["en"] = "Ты ввел немного морфия.", + ["fr"] = "Vous vous êtes injecté de la morphine.", + ["ger"] = "Du hast Morphine injiziert.", + ["es"] = "Has inyectado un poco de morfina.", + ["pt"] = "Você injetou um pouco de morfina.", + ["kr"] = "모르핀을 주입하였습니다.", +} +ConfigurationMedicMod.Sentences["You injected morphine to somebody"] = { + ["en"] = "Вы ввели кому-то морфий.", + ["fr"] = "Vous avez injecté de la morphine à quelqu'un.", + ["ger"] = "Du hast jemandem Morphine injiziert.", + ["es"] = "Has inyectado morfina en alguien.", + ["pt"] = "Você injetou morfina no paciente.", + ["kr"] = "누군가에게 모르핀을 주입하였습니다.", +} +ConfigurationMedicMod.Sentences["First Aid Kit"] = { + ["en"] = "аптечка первой помощи", + ["fr"] = "Trousse de secours", + ["ger"] = "Erste Hilfe Set", + ["es"] = "Botiquín de primeros auxilios", + ["pt"] = "Kit de Primeiros Socorros", + ["kr"] = "구급상자", +} +ConfigurationMedicMod.Sentences["Bandage"] = { + ["en"] = "Повязка", + ["fr"] = "Bandage", + ["ger"] = "Verband", + ["es"] = "Venda", + ["pt"] = "Bandagem", + ["kr"] = "붕대", +} +ConfigurationMedicMod.Sentences["Antidote"] = { + ["en"] = "Противоядие", + ["fr"] = "Antidote", + ["ger"] = "Antidote", + ["es"] = "Antídoto", + ["pt"] = "Soro antiofídico", + ["kr"] = "해독제", +} +ConfigurationMedicMod.Sentences["Your care kit was thrown away because it is empty"] = { + ["en"] = "Ваш набор для ухода был выброшен, потому что он пустой.", + ["fr"] = "Votre trousse de soin a été jetée car elle est vide.", + ["ger"] = "Dein Pflegeset wurde weggeworfen, weil es leer ist.", + ["es"] = "Tu botiquín se ha tirado porque esta vacío.", + ["pt"] = "Seu Kit médico acabou.", + ["kr"] = "당신의 구급상자가 비어 버리겠습니다.", +} +ConfigurationMedicMod.Sentences["You already carry a medical kit on you"] = { + ["en"] = "Ты уже носишь с собой аптечку.", + ["fr"] = "Vous portez déjà une trousse médicale sur vous.", + ["ger"] = "Du trägst schon ein medizinisches Kit an dir.", + ["es"] = "Ya llevas un botiquín medico contigo.", + ["pt"] = "Você já possui um Kit de Primeiros Socorros.", + ["kr"] = "당신은 의료 킷을 소지하고 있습니다.", +} +ConfigurationMedicMod.Sentences["Item selected"] = { + ["en"] = "Выбранный элемент", + ["fr"] = "Élément sélectionné", + ["ger"] = "Item ausgewählt", + ["es"] = "Objeto seleccionado", + ["pt"] = "Item selecionado", + ["kr"] = "아이템이 선택되었습니다.", +} +ConfigurationMedicMod.Sentences["Press [RELOAD] to select another item"] = { + ["en"] = "Нажмите [ПЕРЕЗАГРУЗИТЬ], чтобы выбрать другой элемент", + ["fr"] = "Appuyez sur [RECHARGER] pour séléctionner un autre élément", + ["ger"] = "Drücke [RELOAD] um ein anderes Item auszuwählen", + ["es"] = "Pulsa [RECARGAR] para seleccionar otro objeto", + ["pt"] = "Pressione [R] para selecionar outro item", + ["kr"] = "재장전키 [R] 를 눌러 다른 아이템을 선택하세요.", +} +ConfigurationMedicMod.Sentences["Press [LEFT CLICK] to take"] = { + ["en"] = "Нажмите [ЩЕЛКНИТЕ ЛЕВОЙ КНОПКОЙ МЫШИ], чтобы принять", + ["fr"] = "Appuyez sur [CLIQUE GAUCHE] pour prendre l'élément", + ["ger"] = "Drücke [LINKS KLICK] um zu nehmen", + ["es"] = "Pulsa [CLICK IZQUIERDO] para cogerlo", + ["pt"] = "Pressione [Botão esquerdo] para pegar", + ["kr"] = "[왼쪽 마우스] 를 눌러 가집니다.", +} +ConfigurationMedicMod.Sentences["Press [RIGHT CLICK] to drop"] = { + ["en"] = "Нажмите [ЩЕЛКНИТЕ ПРАВОЙ КНОПКОЙ МЫШИ], чтобы удалить", + ["fr"] = "Appuyez sur [CLIQUE DROIT] pour lancer", + ["ger"] = "Drücke [RECHTS KLICK] um zu droppen", + ["es"] = "Pulsa [CLICK DERECHO] para lanzarlo", + ["pt"] = "Pressione [Botão direito] para largar", + ["kr"] = "[오른쪽 마우스]를 눌러 떨어트립니다.", +} +ConfigurationMedicMod.Sentences["You already carry this element on you"] = { + ["en"] = "Вы уже несете этот элемент на себе.", + ["fr"] = "Vous portez déjà cet élément sur vous.", + ["ger"] = "Du trägst schon dieses Element an dir.", + ["es"] = "Ya llevas este elemento contigo.", + ["pt"] = "Você já está carregando esse elemento.", + ["kr"] = "이미 그 요소를 소지하고 있습니다.", +} +ConfigurationMedicMod.Sentences["You already have a defibrillator on you"] = { + ["en"] = "У вас уже есть дефибриллятор с собой.", + ["fr"] = "Vous avez déjà un défibrillateur sur vous.", + ["ger"] = "Du trägst schon ein Defibrillator an dir.", + ["es"] = "Ya llevas un desfibrilador contigo.", + ["pt"] = "Você já possui um desfibrilador.", + ["kr"] = "당신은 이미 재세동기를 소지하고 있습니다.", +} +ConfigurationMedicMod.Sentences["You have been saved"] = { + ["en"] = "Вы были спасены.", + ["fr"] = "Vous avez été sauvé.", + ["ger"] = "Du wurdest gerettet.", + ["es"] = "Has sido salvado.", + ["pt"] = "Você foi salvo!", + ["kr"] = "당신은 구원 받았습니다." +} +ConfigurationMedicMod.Sentences["Buy"] = { + ["en"] = "Купить", + ["fr"] = "Acheter", + ["ger"] = "Kaufen", + ["es"] = "Comprar", + ["pt"] = "Comprar", + ["kr"] = "구입", +} +ConfigurationMedicMod.Sentences["You don't have enough money"] = { + ["en"] = "У тебя недостаточно денег", + ["fr"] = "Vous n'avez pas assez d'argent.", + ["ger"] = "Du hast nicht genügend Geld.", + ["es"] = "No tienes suficiente dinero", + ["pt"] = "Você não tem dinheiro suficiente", + ["kr"] = "충분한 자금을 소지하고 있지 않습니다.", +} +ConfigurationMedicMod.Sentences["You can't be healed because there is no free bed, retry later"] = { + ["en"] = "Вы не можете исцелиться, потому что нет свободной кровати, повторите попытку позже.", + ["fr"] = "Vous ne pouvez pas être soigné pour le moment car il n'y a pas de lit disponible, réessayez plus tard.", + ["ger"] = "Du kannst nicht behandelt werden, da kein Bett frei ist. Versuche es später erneut.", + ["es"] = "No puedes ser curado porque no hay camas libres disponibles, inténtalo de nuevo más tarde.", + ["pt"] = "Você não pode ser atendido pois não têm macas disponíveis, tente novamente depois.", + ["kr"] = "무료 치료 침대가 존재하지 않습니다, 후에 다시 시도하세요.", +} +ConfigurationMedicMod.Sentences["Medic"] = { + ["en"] = "Медик", + ["fr"] = "Médecin", + ["ger"] = "Mediziner", + ["es"] = "Medico", + ["pt"] = "Médico", + ["kr"] = "의사", +} +ConfigurationMedicMod.Sentences["Thanks"] = { + ["en"] = "Спасибо", + ["fr"] = "Merci", + ["ger"] = "Dankeschön", + ["es"] = "Gracias", + ["pt"] = "Obrigado", + ["kr"] = "감사합니다", +} +ConfigurationMedicMod.Sentences["Hello, you seem healthy-looking today"] = { + ["en"] = "Привет, ты сегодня выглядишь здоровым.", + ["fr"] = "Bonjour, vous semblez en pleine santé aujourd'hui.", + ["ger"] = "Hallo, du siehst heute Gesund aus.", + ["es"] = "Hola, hoy pareces estar saludable.", + ["pt"] = "Olá, você parece saudável hoje.", + ["kr"] = "안녕하세요, 건강해 보이시네요.", +} +ConfigurationMedicMod.Sentences["Heal me"] = { + ["en"] = "Исцели меня", + ["fr"] = "Soignez-moi", + ["ger"] = "Behandle mich", + ["es"] = "Cúrame", + ["pt"] = "Me cure", + ["kr"] = "치료해주세요", +} +ConfigurationMedicMod.Sentences["Hello, you look sick. I can heal you, it will cost"] = { + ["en"] = "Привет, ты выглядишь больным. Я могу исцелить тебя, это будет стоить", + ["fr"] = "Bonjour, vous semblez malade. Je peux vous soigner, cela vous coutera", + ["ger"] = "Du siehst krank aus. Ich kann dich behandeln, kostet aber was", + ["es"] = "Hola, pareces enfermo. Te puedo curar, te costará", + ["pt"] = "Olá, você parece doente. Eu posso tratar de você, vai custar", + ["kr"] = "안녕하세요, 오늘은 아파보이네요. 비용이 들지만 치료해드리죠", +} +ConfigurationMedicMod.Sentences["You are having surgery"] = { + ["en"] = "Вам предстоит операция.", + ["fr"] = "Vous vous faites opérer.", + ["ger"] = "Du wirst eine Operation haben.", + ["es"] = "Estas siendo operado.", + ["pt"] = "Você está sendo operado.", + ["kr"] = "수술 중 입니다.", +} +ConfigurationMedicMod.Sentences["Fracture"] = { + ["en"] = "Перелом", + ["fr"] = "Fracture", + ["ger"] = "Fraktur", + ["es"] = "Fractura", + ["pt"] = "Fratura", + ["kr"] = "골절", +} +ConfigurationMedicMod.Sentences["You broke your leg, your speed is reduced"] = { + ["en"] = "Вы сломали ногу, ваша скорость снижается.", + ["fr"] = "Vous vous êtes fracturé la jambe, votre vitesse est réduite.", + ["ger"] = "Du hast dein Bein gebrochen, du bist nun langsamer.", + ["es"] = "Te has roto la pierna, tu velocidad ha sido reducida. ", + ["pt"] = "Você quebrou sua perna, sua velocidade foi reduzida.", + ["kr"] = "당신의 다리는 부러졌습니다, 이동속도가 매우 느려집니다.", +} +ConfigurationMedicMod.Sentences["You broke your arm, you can't use any weapon"] = { + ["en"] = "Ты сломал руку, ты не можешь использовать никакое оружие.", + ["fr"] = "Vous vous êtes fracturé le bras, vous ne pouvez plus utiliser d'armes.", + ["ger"] = "Du hast dein Arm gebrochen, du kannst keine Waffen mehr nutzen.", + ["es"] = "Te has roto el brazo, no puedes usar ningún arma.", + ["pt"] = "Você quebrou seu braço e não pode mais usar armas.", + ["kr"] = "당신의 팔이 부러졌습니다, 어떤 무기도 사용할 수 없습니다.", +} +ConfigurationMedicMod.Sentences["You have no more fracture"] = { + ["en"] = "У тебя больше нет перелома.", + ["fr"] = "Vous n'avez plus aucune fracture.", + ["ger"] = "Du hast keine Fraktur mehr.", + ["es"] = "No tienes más fracturas.", + ["pt"] = "Sua fratura foi curada.", + ["kr"] = "당신은 어떤 골절도 존재하지 않습니다.", +} +ConfigurationMedicMod.Sentences["You have been healed"] = { + ["en"] = "Вы были исцелены.", + ["fr"] = "Vous avez été soigné.", + ["ger"] = "Du wurdest behandelt.", + ["es"] = "Has sido curado.", + ["pt"] = "Você foi curado.", + ["kr"] = "당신은 치료 되었습니다.", +} +ConfigurationMedicMod.Sentences["You are in a coma"] = { + ["en"] = "Вы находитесь в коме", + ["fr"] = "Vous êtes dans le coma", + ["ger"] = "Du bist nun in Koma", + ["es"] = "Estás en coma", + ["pt"] = "Você entrou em coma", + ["kr"] = "당신은 혼수상태입니다.", +} +ConfigurationMedicMod.Sentences["You are being scanned"] = { + ["en"] = "Вас сканируют.", + ["fr"] = "Vous vous faites scanner.", + ["ger"] = "Du wirst gescannt.", + ["es"] = "Estas siendo escaneado.", + ["pt"] = "Você está sendo escaneado.", + ["kr"] = "스캔 중 입니다.", +} +ConfigurationMedicMod.Sentences["Test tube"] = { + ["en"] = "Пробирка", + ["fr"] = "Tube", + ["ger"] = "Tube", + ["es"] = "Tubo de ensayo", + ["pt"] = "Tubo de Testes", + ["kr"] = "시험관", +} +ConfigurationMedicMod.Sentences["Aminophenol"] = { + ["en"] = "Аминофенол", + ["fr"] = "Aminophénol", + ["ger"] = "Aminophenol", + ["es"] = "Aminofenol", + ["pt"] = "Aminofenol", + ["kr"] = "아미노 페놀", +} +ConfigurationMedicMod.Sentences["Water"] = { + ["en"] = "Вода", + ["fr"] = "Eau", + ["ger"] = "Wasser", + ["es"] = "Agua", + ["pt"] = "Água", + ["kr"] = "물", +} +ConfigurationMedicMod.Sentences["Ethanoic anhydride"] = { + ["en"] = "Этановый ангидрид", + ["fr"] = "Anhydride éthanoïque", + ["ger"] = "Ethanoic Anhydride", + ["es"] = "Anhídrido etanoico", + ["pt"] = "Anidrito etanóico", + ["kr"] = "아세트산무수물", +} +ConfigurationMedicMod.Sentences["Empty"] = { + ["en"] = "Пустой", + ["fr"] = "Vide", + ["ger"] = "Leer", + ["es"] = "Vacío", + ["pt"] = "Vazio", + ["kr"] = "비었음." +} +ConfigurationMedicMod.Sentences["Tylenol"] = { + ["en"] = "Тайленол", + ["fr"] = "Doliprane", + ["ger"] = "Tylenol", + ["es"] = "Tylenol", + ["pt"] = "Tilenol", + ["kr"] = "타이레놀", +} +ConfigurationMedicMod.Sentences["Potassium iodide"] = { + ["en"] = "Йодистый калий", + ["fr"] = "Iodure de potassium", + ["ger"] = "Potassium iodide", + ["es"] = "Yoduro de potasio", + ["pt"] = "Iodeto de Potássio", + ["kr"] = "아이오딘화 칼륨", +} +ConfigurationMedicMod.Sentences["Ethanol"] = { + ["en"] = "Этанол", + ["fr"] = "Ethanol", + ["ger"] = "Ethanol", + ["es"] = "Etanol", + ["pt"] = "Etanol", + ["kr"] = "에탄올", +} +ConfigurationMedicMod.Sentences["Sulfuric acid"] = { + ["en"] = "Серная кислота", + ["fr"] = "Acide sulfurique", + ["ger"] = "Schwefelsäure", + ["es"] = "Ácido sulfúrico", + ["pt"] = "Ácido sulfúrico", + ["kr"] = "황산", +} +ConfigurationMedicMod.Sentences["Left arm"] = { + ["en"] = "Левая рука", + ["fr"] = "Bras gauche", + ["ger"] = "Linker Arm", + ["es"] = "Brazo izquierdo", + ["pt"] = "Braço esquerdo", + ["kr"] = "왼쪽 팔", +} +ConfigurationMedicMod.Sentences["Right arm"] = { + ["en"] = "Правая рука", + ["fr"] = "Bras droit", + ["ger"] = "Rechter Arm", + ["es"] = "Brazo derecho", + ["pt"] = "Braço direito", + ["kr"] = "오른쪽 팔", +} +ConfigurationMedicMod.Sentences["Left leg"] = { + ["en"] = "Левая нога", + ["fr"] = "Jambe gauche", + ["ger"] = "Linkes Bein", + ["es"] = "Pierna izquierda", + ["pt"] = "Perna esquerda", + ["kr"] = "왼쪽 다리", +} +ConfigurationMedicMod.Sentences["Right leg"] = { + ["en"] = "Правая нога", + ["fr"] = "Jambe droite", + ["ger"] = "Rechtes Bein", + ["es"] = "Pierna derecha", + ["pt"] = "Perna direita", + ["kr"] = "오른쪽 다리", +} +ConfigurationMedicMod.Sentences["Drug"] = { + ["en"] = "Наркотик", + ["fr"] = "Medicament", + ["ger"] = "Medikament", + ["es"] = "Medicamento", + ["pt"] = "Medicamento", + ["kr"] = "약", +} +ConfigurationMedicMod.Sentences["Death Notice"] = { + ["en"] = "Уведомление о смерти", + ["fr"] = "Avis de décès", + ["ger"] = "Todesanzeige", + ["es"] = "Aviso de muerte", + ["pt"] = "Aviso de morte", + ["kr"] = "부고", +} +ConfigurationMedicMod.Sentences["You have lost consciousness."] = { + ["en"] = "Вы потеряли сознание.", + ["fr"] = "Vous avez perdu conscience.", + ["ger"] = "Du hast das Bewusstsein verloren.", + ["es"] = "Has perdido la conciencia.", + ["pt"] = "Você perdeu a consciência.", + ["kr"] = "당신은 의식을 잃었습니다.", +} +ConfigurationMedicMod.Sentences["You must be rescued by a medic, or wait."] = { + ["en"] = "Вас должен спасти медик, или подождите.", + ["fr"] = "Vous devez être sauvé par un médecin ou attendre.", + ["ger"] = "Sie müssen von einem Arzt gerettet werden oder warten.", + ["es"] = "Usted debe ser rescatado por un médico, o esperar.", + ["pt"] = "Você deve ser resgatado por um médico ou esperar.", + ["kr"] = "당신은 의사에게 구조를 받거나, 기다려야 합니다.", +} +ConfigurationMedicMod.Sentences["Click to respawn."] = { + ["en"] = "Нажмите, чтобы возродиться.", + ["fr"] = "Cliquer pour réapparaitre.", + ["ger"] = "Klicken Sie, um wieder zu erscheinen.", + ["es"] = "Haga clic para volver a aparecer.", + ["pt"] = "Clique para reaparecer.", + ["kr"] = "클릭하면 리스폰 합니다.", +} +ConfigurationMedicMod.Sentences["You're receiving CPR, you can't respawn."] = { + ["en"] = "Вы получаете искусственное дыхание, вы не можете возродиться.", + ["fr"] = "Quelqu'un te fait un massage cardiaque, tu ne peux pas réapparaitre.", + ["ger"] = "Du erhältst CPR, du kannst nicht respektieren.", + ["es"] = "Está recibiendo RCP, no puede aparecer.", + ["pt"] = "Você está recebendo RCP, você não pode reaparecer.", + ["kr"] = "누군가 당신에게 CPR 을 시도중입니다, 리스폰 할 수 없습니다.", +} + +ConfigurationMedicMod.Sentences["Please wait"] = { + ["en"] = "Пожалуйста подождите", + ["fr"] = "Attendez", + ["ger"] = "Warten Sie", + ["es"] = "Por favor espera", + ["pt"] = "Por favor, espere", + ["kr"] = "기다려주세요.", +} +ConfigurationMedicMod.Sentences["seconds to respawn."] = { + ["en"] = "секунды до возрождения.", + ["fr"] = "secondes pour réapparaitre.", + ["ger"] = "Sekunden bevor Sie wieder auftauchen", + ["es"] = "segundos para reaparecer.", + ["pt"] = "segundos para reaparecer.", + ["kr"] = "초후 리스폰 합니다.", +} +ConfigurationMedicMod.Sentences["You're now a medic, get help with"] = { + ["en"] = "Теперь ты медик, получи помощь с", + ["fr"] = "Vous êtes maintenant médecin, obtenez de l'aide avec", + ["ger"] = "Du bist jetzt Sanitäter, hol dir Hilfe", + ["es"] = "Ahora eres un médico, obtén ayuda con", + ["pt"] = "Você é agora um médico, procure ajuda com", + ["kr"] = "너는 이제 의료진이야, 도움을 받아", +} +ConfigurationMedicMod.Sentences["Drugs"] = { + ["en"] = "Наркотики", + ["fr"] = "Médicaments", + ["ger"] = "Arzneimittel", + ["es"] = "Productos farmacéuticos", + ["pt"] = "Produtos farmacêuticos", + ["kr"] = "의약품", +} +ConfigurationMedicMod.Sentences["You've reached the limit of"] = { + ["en"] = "Вы достигли предела", + ["fr"] = "Tu as atteint la limite de", + ["ger"] = "Du hast das Limit von erreicht", + ["es"] = "Has alcanzado el límite de", + ["pt"] = "Você atingiu o limite de", + ["kr"] = "한도에 도달했습니다", +} + +ConfigurationMedicMod.Sentences.GuideText = {} + +ConfigurationMedicMod.Sentences.GuideText["fr"] = { + { + "Comment réanimer quelqu'un?", + 25 + }, + { + "Etape 1 : Stabiliser son état", + 22 + }, + { + [[ + Lorsqu'un individu tombe dans le coma, les causes peuvent être diverses. + Avant de le réanimer, il faut stabiliser son état. + Pour savoir ce qui ne va pas, il faut utiliser le carnet de notes. + Si il saigne, il faut utiliser un bandage afin d'arrêter les saignements. + Si son coeur ne bat plus, il faut utiliser un défibrillateur afin de le relancer. Ca ne marche pas forcément du premier coup! + Si il a été empoisonné, alors il faut utiliser une seringue d'antidote. + Une fois son état stabilisé, il va falloir passer à l'étape de réanimation. + ( Vous n'êtes pas obligé de stabiliser le patient avant de le réanimer, à condition que son coeur batte. Cependant, il va progressivement perdre de la vie et retomber dans le coma. ) + ]], + 20 + }, + { + "Etape 2 : Le réanimer", + 22 + }, + { + [[ + Pour réanimer le patient, il faut tout d'abord le placer sur un lit d'hôpital, pour déplacer le corps, vous pouvez le porter ou utiliser un brancard se trouvant dans l'ambulance. + Puis, placez sur une perfusion une poche de sang, et collez la perfusion au lit. Il va progressivement être soigné et être réanimé si il est bien stabilisé. + ]], + 20 + }, + { + "Comment soigner une fracture?", + 25 + }, + { + [[ + Tout d'abord, il faut trouver quels os sont fracturés. Pour cela, il faut que le patient s'allonge sur la radio. Après quelques secondes, les membres fracturés apparaissent en rouge sur l'écran. Il faut donner des médicaments pour les soigner, vous pouvez trouver une liste de médicaments dans ce menu. + ]], + 20 + }, + { + "Comment fabriquer un médicament?", + 25 + }, + { + [[ + Une liste des médicaments est disponible dans ce menu. Vous pouvez directement les acheter, mais aussi les créer. + Pour cela, acheter une fiole, et les différents tubes nécessaires ( la liste est à côté du médicament dans ce menu ). + Mettez dans la fiole les tubes, puis mettez en contact la fiole avec un bocal vide. Si la recette est bonne, le médicament se crée. + Appuyez sur +UTILISER pour vider la fiole si les ingrédients mis à l'interieur sont mauvais. + ]], + 20 + }, + { + "A quoi sert le massage cardiaque?", + 25 + }, + { + [[ + Afin de garder en vie la personne blessée, si la seule cause de son coma est un arrêt cardiaque, alors les témoins peuvent pratiquer un massage cardiaque. Cela va permettre d'allonger son temps de survie en attendant l'arrivée des médecins, prêt à stabiliser son état. + ]], + 20 + }, +} + + +ConfigurationMedicMod.Sentences.GuideText["en"] = { + { + "How to revive someone?", + 25 + }, + { + "First step: Stabilize", + 22 + }, + { + [[ + When someone fall in coma, the reasons are various. + Before reviving a wounded, you must stabilize his state. + To know what's wrong, you have to use your notepad. + If he's bleeding, you have to use a bandage on him. + If he's doing a heart attack, you have to use a defibrillator on him. It can not work the first time, you have to retry! + If he has been poisoned, une have to use an antidote syringe on him. + When the wounded is stable, you can pass to the reviving step. + ( You don't have to stabilize someone before reviving him if he's not doing a heart attack. However, he'll slowly loose health and fall in coma again. ) + ]], + 20 + }, + { + "Second step: Reviving", + 22 + }, + { + [[ + To revive the wounded, you first have to place him on a hospital bed. To move the body, you can just carry it or use a stretcher in the ambulance. + Then, place a blood bag on a drip and touch the bed with the drip. + The wounded will progressively be healed and revived. + ]], + 20 + }, + { + "How to heal a fracture?", + 25 + }, + { + [[ + First, you have to find which bones are fractured. For that, ask the patient to stretch out on the radio bed. + After some seconds, fractured bones will appear in red on the radio screen. + You have to give drugs to the wounded to heal him, you can find a list of them in this menu. + ]], + 20 + }, + { + "How to make drugs?", + 25 + }, + { + [[ + A list of drugs is available in this menu. You can buy them directly or craft them. + For that, buy a beaker and the different test tubes needed. ( a list of them is next to the drug in this menu ) + Put in the beaker the test tubes and touch the beaker with an empty drug jar. If the recipe is good, the drug is created. + You can press +USE on the beaker to throw everything in. + ]], + 20 + }, + { + "What's the use of CPR?", + 25 + }, + { + [[ + To keep a wounded alive, if the only reason of his coma is a heart attack, then people around him can practive a CPR on him. + It will keep him alive, to wait the paramedics who will be able to stabilize his state. + ]], + 20 + }, +} +ConfigurationMedicMod.Sentences.GuideText["ger"] = { + { + "Wie man jemanden wiederbelebt?", + 25 + }, + { + "Erster Schritt: Stabilisieren", + 22 + }, + { + [[ + Wenn jemand ins Koma fällt oder Bewusstlos wird, sind die Gründe dafür vielfältig. + Bevor Sie einen Verwundeten wiederbeleben, müssen Sie seinen Zustand stabilisieren. + Um heruaszufinden was dem Patienten fehlt, müssen Sie Ihren Notizblock verwenden. + Wenn er blutet, musst du einen Verband benutzen. + Wenn er einen Herzinfarkt hat, muss er mit einen Defibrillator wieder belebt werden. Es kann beim ersten Mal nicht funktionieren, versuche es dann erneut! + Wenn er vergiftet wurde, muss er ein Gegengift bekommen! + Wenn der Verwundete stabil ist, können Sie zum Wiederbelebungsschritt übergehen. + (Sie sollten den Patienten erst stabilisieren, bevor Sie ihn wiederbeleben. Er verliert sonst sehr schnell wieder seine Gesundheit und fällt dann wieder ins Koma.) + ]], + 20 + }, + { + "Zweiter Schritt: Wiederbeleben", + 22 + }, + { + [[ + Um die Verletzten wiederzubeleben, müssen Sie ihn zuerst auf ein Krankenhausbett legen. Um eine Person zu bewegen, können Sie ihn einfach mit E tragen oder eine Krankentrage aus dem Krankenwagen verwenden. + Dann hängen Sie einen Blutbeutel an einen Tropf und berühren Sie den Tropf kurz mit dem Bett. + Der Verwundete wird anschließend langsam geheilt, ist der Blutbeutel dann komplett im Patienten, wird er wiederbelebt. + ]], + 20 + }, + { + "Wie heile ich Knochenbrüche?", + 25 + }, + { + [[ + Zuerst müssen Sie herausfinden, welche Knochen gebrochen sind. Fordern Sie dazu den Patienten auf, sich auf dem Rontgenbett zu legen. + Nach einigen Sekunden erscheinen die gebrochene Knochen auf dem Rontgenbildschirm rot. Wenn alles weiß bleibt, sind bei dem patienten keine Knochen gebrochen. + Du musst den Verwundeten Medikamente geben, um ihn zu heilen, du kannst eine Liste von den Medikamenten in diesem Menü finden. + ]], + 20 + }, + { + "Wie man Medikamente macht?", + 25 + }, + { + [[ + Eine Liste der Medikamente ist in diesem Menü verfügbar. Sie können sie direkt kaufen oder auch selber herstellen. + Dafür kaufen Sie einen Becher und die benötigten Reagenzien. (Diese finden Sie in diesem Menü bei Entities und Tube.) + Gib die Reagenzien in ds Reagenzglas und berühre sie dann mit einem leeren Arzneimittelbehälter. Wenn das Rezept gut ist, wird das Medikament erstellt. + ]], + 20 + }, + { + "Was ist die Verwendung von CPR?", + 25 + }, + { + [[ + Um einen Verwundeten am Leben zu halten, wenn der einzige Grund seines Komas ein Herzinfarkt ist, können die Menschen um ihn herum eine Herz-Lungen-Wiederbelebung versuchen durchzuführen. + Es wird ihn am Leben halten, bis der Rettungsdienst da ist, die dann seinen Zustand stabilisieren können. + ]], + 20 + }, +} +ConfigurationMedicMod.Sentences.GuideText["es"] = { + { + "Как оживить кого-то?", + 25 + }, + { + "Первый шаг: Стабилизация", + 22 + }, + { + [[ + Когда кто-то впадает в кому, причины бывают разные. + Прежде чем оживить раненого, вы должны стабилизировать его состояние. + Чтобы узнать, в чем дело, вам нужно воспользоваться своим блокнотом. + Если у него идет кровь, вам придется наложить на него повязку. + Если у него сердечный приступ, вам придется применить к нему дефибриллятор. Это может не сработать с первого раза, вам придется повторить попытку! + Если он был отравлен, нам придется использовать на нем шприц с противоядием. + Когда состояние раненого стабилизируется, вы можете перейти к этапу оживления. + ( Вам не нужно стабилизировать кого-то, прежде чем оживлять его, если у него нет сердечного приступа. Однако он постепенно потеряет здоровье и снова впадет в кому. ) + ]], + 20 + }, + { + "Второй шаг: Возрождение", + 22 + }, + { + [[ + Чтобы оживить раненого, вы сначала должны положить его на больничную койку. Чтобы переместить тело, вы можете просто нести его или использовать носилки в машине скорой помощи. + Затем положите пакет с кровью на капельницу и прикоснитесь капельницей к кровати. + Раненые будут постепенно исцеляться и оживать. + ]], + 20 + }, + { + "Как залечить перелом?", + 25 + }, + { + [[ + Во-первых, вы должны найти, какие кости сломаны. Для этого попросите пациента вытянуться на кровати с радиоприемником. + Через несколько секунд на экране радио красным цветом появятся переломанные кости. + Вы должны дать лекарства раненому, чтобы вылечить его, вы можете найти их список в этом меню. + ]], + 20 + }, + { + "Как изготавливать наркотики?", + 25 + }, + { + [[ + Список лекарств доступен в этом меню. Вы можете купить их напрямую или изготовить самостоятельно. + Для этого купите мензурку и различные необходимые пробирки. ( список из них находится рядом с препаратом в этом меню ) + Положите в мензурку пробирки и прикоснитесь к мензурке пустой банкой из-под лекарств. Если рецепт хорош, препарат создается. + Вы можете нажать +USE на мензурке, чтобы бросить все в. + ]], + 20 + }, + { + "Какая польза от искусственного дыхания?", + 25 + }, + { + [[ + Чтобы сохранить раненому жизнь, если единственной причиной его комы является сердечный приступ, то окружающие его люди могут сделать ему искусственное дыхание. + Это позволит ему остаться в живых, дождаться медиков, которые смогут стабилизировать его состояние. + ]], + 20 + }, +} +ConfigurationMedicMod.Sentences.GuideText["pt"] = { + { + "How to revive someone?", + 25 + }, + { + "First step: Stabilize", + 22 + }, + { + [[ + When someone fall in coma, the reasons are various. + Before reviving a wounded, you must stabilize his state. + To know what's wrong, you have to use your notepad. + If he's bleeding, you have to use a bandage on him. + If he's doing a heart attack, you have to use a defibrillator on him. It can not work the first time, you have to retry! + If he has been poisoned, une have to use an antidote syringe on him. + When the wounded is stable, you can pass to the reviving step. + ( You don't have to stabilize someone before reviving him if he's not doing a heart attack. However, he'll slowly loose health and fall in coma again. ) + ]], + 20 + }, + { + "Second step: Reviving", + 22 + }, + { + [[ + To revive the wounded, you first have to place him on a hospital bed. To move the body, you can just carry it or use a stretcher in the ambulance. + Then, place a blood bag on a drip and touch the bed with the drip. + The wounded will progressively be healed and revived. + ]], + 20 + }, + { + "How to heal a fracture?", + 25 + }, + { + [[ + First, you have to find which bones are fractured. For that, ask the patient to stretch out on the radio bed. + After some seconds, fractured bones will appear in red on the radio screen. + You have to give drugs to the wounded to heal him, you can find a list of them in this menu. + ]], + 20 + }, + { + "How to make drugs?", + 25 + }, + { + [[ + A list of drugs is available in this menu. You can buy them directly or craft them. + For that, buy a beaker and the different test tubes needed. ( a list of them is next to the drug in this menu ) + Put in the beaker the test tubes and touch the beaker with an empty drug jar. If the recipe is good, the drug is created. + You can press +USE on the beaker to throw everything in. + ]], + 20 + }, + { + "What's the use of CPR?", + 25 + }, + { + [[ + To keep a wounded alive, if the only reason of his coma is a heart attack, then people around him can practive a CPR on him. + It will keep him alive, to wait the paramedics who will be able to stabilize his state. + ]], + 20 + }, +} +ConfigurationMedicMod.Sentences.GuideText["kr"] = { + { + "How to revive someone?", + 25 + }, + { + "First step: Stabilize", + 22 + }, + { + [[ + When someone fall in coma, the reasons are various. + Before reviving a wounded, you must stabilize his state. + To know what's wrong, you have to use your notepad. + If he's bleeding, you have to use a bandage on him. + If he's doing a heart attack, you have to use a defibrillator on him. It can not work the first time, you have to retry! + If he has been poisoned, une have to use an antidote syringe on him. + When the wounded is stable, you can pass to the reviving step. + ( You don't have to stabilize someone before reviving him if he's not doing a heart attack. However, he'll slowly loose health and fall in coma again. ) + ]], + 20 + }, + { + "Second step: Reviving", + 22 + }, + { + [[ + To revive the wounded, you first have to place him on a hospital bed. To move the body, you can just carry it or use a stretcher in the ambulance. + Then, place a blood bag on a drip and touch the bed with the drip. + The wounded will progressively be healed and revived. + ]], + 20 + }, + { + "How to heal a fracture?", + 25 + }, + { + [[ + First, you have to find which bones are fractured. For that, ask the patient to stretch out on the radio bed. + After some seconds, fractured bones will appear in red on the radio screen. + You have to give drugs to the wounded to heal him, you can find a list of them in this menu. + ]], + 20 + }, + { + "How to make drugs?", + 25 + }, + { + [[ + A list of drugs is available in this menu. You can buy them directly or craft them. + For that, buy a beaker and the different test tubes needed. ( a list of them is next to the drug in this menu ) + Put in the beaker the test tubes and touch the beaker with an empty drug jar. If the recipe is good, the drug is created. + You can press +USE on the beaker to throw everything in. + ]], + 20 + }, + { + "What's the use of CPR?", + 25 + }, + { + [[ + To keep a wounded alive, if the only reason of his coma is a heart attack, then people around him can practive a CPR on him. + It will keep him alive, to wait the paramedics who will be able to stabilize his state. + ]], + 20 + }, +} + diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/shared/sh_functions.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/shared/sh_functions.lua new file mode 100644 index 0000000..63065d0 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/shared/sh_functions.lua @@ -0,0 +1,41 @@ +local meta = FindMetaTable( "Player" ) + +function meta:IsBleeding() + return self:GetNWBool("Bleeding") or false +end + +function meta:GetHeartAttack() + return self:GetNWBool("HeartAttack") or false +end + +function meta:IsPoisoned() + return self:GetNWBool("Poisoned") or false +end + +function meta:GetMedicAnimation() + return self:GetNWInt("MedicActivity") or 0 +end + +function meta:IsMorphine() + return self:GetNWBool("Morphine") or false +end + +function meta:IsFractured() + return self:GetNWBool("Fracture") or false +end + +function meta:Stable() + if self:IsPoisoned() or self:GetHeartAttack() or self:IsBleeding() then + return false + else + return true + end +end + + +local ent = FindMetaTable( "Entity" ) + +function ent:IsDeathRagdoll() + return self:GetNWBool("IsDeathRagdoll") or false +end + diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/shared/sh_hooks.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/shared/sh_hooks.lua new file mode 100644 index 0000000..b0b36fa --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/medicmod/shared/sh_hooks.lua @@ -0,0 +1,114 @@ +local sentences =ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +-- Play animations +hook.Add("CalcMainActivity", "CalcMainActivity.MedicMod", function(ply, vel) + + if ply:GetMedicAnimation() == 1 then + local seqid = ply:LookupSequence( "medic_custom1" ) + ply:SetSequence( seqid ) + + return -1, seqid + end + if ply:GetMedicAnimation() == 2 then + + -- stop the animation if the ragdoll isn't near the player + if not ply:GetEyeTrace().Entity:IsDeathRagdoll() or ply:GetEyeTrace().Entity:GetPos():Distance(ply:GetPos()) > 100 then + if SERVER then + if IsValid( ply.RagdollHeartMassage ) && IsValid( ply.RagdollHeartMassage:GetOwner() ) then + ply.RagdollHeartMassage:GetOwner().NextSpawnTime = CurTime() + ply.RagdollHeartMassage:GetOwner().AddToSpawnTime + ply.RagdollHeartMassage.IsHeartMassage = false + net.Start("MedicMod.Respawn") + net.WriteInt(ply.RagdollHeartMassage:GetOwner().NextSpawnTime,32) + net.Send(ply) + end + ply:StripWeapon("heart_massage") + end + if SERVER then StopMedicAnimation( ply ) end + return + end + + local seqid = ply:LookupSequence( "medic_custom2" ) + ply:SetSequence( seqid ) + + return -1, seqid + end + +end) + +hook.Add("PlayerButtonUp", "PlayerButtonUp.MedicMod", function( ply, but ) + + if table.HasValue( ConfigurationMedicMod.TeamsCantPracticeCPR, ply:Team() ) then return end + + if but != ConfigurationMedicMod.CPRKey or not ConfigurationMedicMod.CanCPR then return end + + if ply.blocAnim && ply.blocAnim >= CurTime() then return end + + -- if the player press the configured key, then stop his animation + if ply:GetMedicAnimation() != 0 then + if SERVER then + -- add condition pour si le mec n'a plus d'arrêt cardiaque + if IsValid( ply.RagdollHeartMassage ) && IsValid( ply.RagdollHeartMassage:GetOwner() ) then + ply.RagdollHeartMassage:GetOwner().NextSpawnTime = CurTime() + ply.RagdollHeartMassage:GetOwner().AddToSpawnTime + ply.RagdollHeartMassage.IsHeartMassage = false + net.Start("MedicMod.Respawn") + net.WriteInt(ply.RagdollHeartMassage:GetOwner().NextSpawnTime,32) + net.Send(ply.RagdollHeartMassage:GetOwner()) + end + ply:StripWeapon("heart_massage") + end + ply.blocAnim = CurTime() + 2 + if SERVER then StopMedicAnimation( ply ) end + return + end + + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) then return end + + if ent.IsHeartMassage then return end + + if not ent:IsDeathRagdoll() or ent:GetClass() == "prop_physics" or trace.HitPos:Distance(ply:GetPos()) > 100 then return end + + if table.HasValue( ConfigurationMedicMod.TeamsCantReceiveCPR, ent:GetOwner():Team() ) then return end + + if ent:GetOwner().NextSpawnTime == -1 then return end + + ply.RagdollHeartMassage = ent + + ent.IsHeartMassage = true + + if SERVER then StartMedicAnimation( ply, 1 ) end + ply.blocAnim = CurTime() + 5 + + timer.Simple(2.7, function() + + if SERVER then + ply:Give("heart_massage") + ply:SelectWeapon("heart_massage") + ply:MedicNotif( sentences["You are starting CPR"][lang], 10) + ent:GetOwner():MedicNotif( sentences["Someone is giving you CPR"][lang], 10) + end + + if SERVER then + StopMedicAnimation( ply ) + StartMedicAnimation( ply, 2 ) + end + + end) + + if SERVER then + if not ent:GetOwner():GetHeartAttack() then ent:GetOwner().AddToSpawnTime = 0 return end + + if not ent:GetOwner():IsPoisoned() and not ent:GetOwner():IsBleeding() then + ent:GetOwner().AddToSpawnTime = ent:GetOwner().NextSpawnTime - CurTime() + ent:GetOwner().NextSpawnTime = -1 + net.Start("MedicMod.Respawn") + net.WriteInt(-1,32) + net.Send(ent:GetOwner()) + else + ent:GetOwner().AddToSpawnTime = 0 + end + end +end) diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/admin_defib/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/admin_defib/shared.lua new file mode 100644 index 0000000..092d4d2 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/admin_defib/shared.lua @@ -0,0 +1,99 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/defib/v_defib.mdl" +SWEP.WorldModel = "models/medicmod/defib/w_defib.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to attack" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Admin Defibrillator" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + + + +function SWEP:SecondaryAttack() +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +function SWEP:PrimaryAttack() + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + local ent = self.Owner:GetEyeTrace().Entity + + if not IsValid( self.Owner ) then return end + + if not IsValid(ent) or ent:GetPos():Distance( self.Owner:GetPos() ) > 200 then return end + + if ent:IsPlayer() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + if ent:GetHeartAttack() then + ent:MedicalRespawn() + else + -- Heal if already alive to avoid weapon stripping by ent:Spawn() inside MedicalRespawn() + ent:SetHealth(ent:GetMaxHealth()) + ent:SetBleeding(false) + ent:SetPoisoned(false) + for k, v in pairs( ent:GetFractures() or {} ) do + ent:SetFracture(false, k) + end + end + end + + elseif ent:IsDeathRagdoll() && ent:GetOwner():GetHeartAttack() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + ent:GetOwner():MedicalRespawn() + end + + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() && ent.ragdoll:GetOwner():GetHeartAttack() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + ent.ragdoll:GetOwner():MedicalRespawn() + end + + end + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "" ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/analysis_notebook/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/analysis_notebook/shared.lua new file mode 100644 index 0000000..05abcc3 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/analysis_notebook/shared.lua @@ -0,0 +1,232 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to use" + +SWEP.WorldModel = "" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 0 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 1 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Medic Analysis" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +local lang = ConfigurationMedicMod.Language + +SWEP.Infos = {} + +function SWEP:SetupDataTables() + self:NetworkVar("Bool", 0, "Searching") + self:NetworkVar("Entity", 0, "Player") + self:NetworkVar("Float", 1, "AnalyseEndTime") + self:NetworkVar("Entity", 1, "Patient") +end + + +function SWEP:SecondaryAttack() +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +function SWEP:PrimaryAttack() + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + local ent = self.Owner:GetEyeTrace().Entity + + if SERVER then + + if not IsValid(ent) or ent:GetPos():Distance(self.Owner:GetPos()) > 200 then return end + if self:GetSearching() then return end + + if ent:IsDeathRagdoll() then + self:SetSearching( true ) + self:SetAnalyseEndTime( CurTime() + ConfigurationMedicMod.TimeToQuickAnalyse ) + self:SetPatient( ent ) + self:SetPlayer( ent:GetOwner() ) + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() then + self:SetSearching( true ) + self:SetAnalyseEndTime( CurTime() + ConfigurationMedicMod.TimeToQuickAnalyse ) + self:SetPatient( ent ) + self:SetPlayer( ent.ragdoll:GetOwner() ) + end + + end + +end + +function SWEP:Deploy() + if SERVER then timer.Simple( 0.1, function() self.Owner:DrawViewModel( false ) self.Owner:DrawWorldModel( false ) end) end + return true +end +function SWEP:Initialize() + self:SetHoldType( "normal" ) + + if SERVER then timer.Simple( 0.1, function() self.Owner:DrawViewModel( false ) self.Owner:DrawWorldModel( false ) end) end + +end + +function SWEP:Holster() + return true +end + +function SWEP:Think() + + if not self:GetSearching() then return end + if self:GetAnalyseEndTime() > CurTime() && self:GetAnalyseEndTime() > 0 then + if IsValid(self:GetPatient()) && IsValid(self.Owner:GetEyeTrace().Entity) && self:GetPatient() == self.Owner:GetEyeTrace().Entity then + if self.Owner:GetPos():Distance(self:GetPatient():GetPos()) > 200 then + self:SetSearching( false ) + self:SetPatient( nil ) + self:SetPlayer( nil ) + self:SetAnalyseEndTime( 0 ) + end + else + self:SetSearching( false ) + self:SetPlayer( nil ) + self:SetAnalyseEndTime( 0 ) + self:SetPatient( nil ) + end + else + + if not IsValid(self:GetPatient()) then + self:SetSearching( false ) + self:SetPlayer( nil ) + self:SetAnalyseEndTime( 0 ) + self:SetPatient( nil ) + return + end + + local ply = self:GetPlayer() + + self.Infos = {} + self.Infos[1] = ConfigurationMedicMod.Sentences["The name of the individu is"][lang].." "..ply:Name() + if ply:IsBleeding() then + self.Infos[#self.Infos + 1] = ConfigurationMedicMod.Sentences["There's blood everywhere, it seems he's bleeding"][lang] + end + if ply:IsPoisoned() then + self.Infos[#self.Infos + 1] = ConfigurationMedicMod.Sentences["He is greenish, may have been poisoned"][lang] + end + if ply:GetHeartAttack() then + self.Infos[#self.Infos + 1] = ConfigurationMedicMod.Sentences["His heart no longer beats"][lang] + end + + timer.Simple( 60, function() self.Infos = {} end) + self:SetSearching( false ) + self:SetPatient( nil ) + self:SetPlayer( nil ) + self:SetAnalyseEndTime( 0 ) + end + + +end + +local notepad = Material( "materials/note_paper_medic.png" ) + +local dots = { + [0] = ".", + [1] = "..", + [2] = "...", + [3] = "" +} + +function SWEP:DrawHUD() + surface.SetDrawColor( 255,255,255,255 ) + surface.SetMaterial( notepad ) + surface.DrawTexturedRect( ScrW() - 518 - 20, ScrH() - 720 - 20, 518, 720 ) + + surface.SetFont("WrittenMedicMod80") + + local sizetextx, sizetexty = surface.GetTextSize(ConfigurationMedicMod.Sentences["Notebook"][lang]) + + local posx = ScrW() - 518 - 20 + 518/2 - sizetextx/2 + 20 + local posy = ScrH() - 720 - 20 + + surface.SetTextPos( posx, posy) + surface.SetTextColor( 0, 0, 0, 255) + surface.DrawText( ConfigurationMedicMod.Sentences["Notebook"][lang] ) + + if self:GetSearching() then + if (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 sizetextx, sizetexty = surface.GetTextSize(ConfigurationMedicMod.Sentences["Writing"][lang]..self.Dots) + + local posx = ScrW() - 518 - 20 + 518/2 - sizetextx/2 + 20 + local posy = ScrH() - 720 - 20 + 518/2 + 10 + + surface.SetTextPos( posx, posy) + surface.SetTextColor( 0, 0, 0, 255) + surface.DrawText( ConfigurationMedicMod.Sentences["Writing"][lang]..self.Dots ) + return + end + + if self.Infos != nil then + local line = 0 + for k, v in pairs(self.Infos) do + + -- stop the loop if there is too many lines + if line > 24 then return end + + local sizetextx, sizetexty = surface.GetTextSize(v) + local posy = ScrH() - 720 - 20 + 57 * 1.5 + 10 + local posx = ScrW() - 518 - 20 + 65 * 1.5 + + surface.SetFont("WrittenMedicMod40") + surface.SetTextColor( 0, 0, 0, 255) + + if sizetextx > (518) - 65 - 20 then + surface.SetFont("WrittenMedicMod40") + end + + sizetextx, sizetexty = surface.GetTextSize(v) + if sizetextx > (518) - 65 - 20 then + local lengh = string.len( v ) + local ftcl = string.find(v," ", lengh/2-1) + local text1 = string.sub(v, 1, ftcl-1) + local text2 = string.sub(v, ftcl+1) + + surface.SetTextPos( posx, posy + 24 * line) + surface.DrawText( text1 ) + line = line + 1 + surface.SetTextPos( posx , posy + 24 * line) + surface.DrawText( text2 ) + line = line + 2 + else + surface.SetTextPos( posx, posy + 24 * line) + surface.DrawText( v ) + + line = line + 2 + end + + + + end + end + +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/bandage/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/bandage/shared.lua new file mode 100644 index 0000000..b6a4821 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/bandage/shared.lua @@ -0,0 +1,129 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/bandage/v_bandage.mdl" +SWEP.WorldModel = "models/medicmod/bandage/w_bandage.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to attack" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 2 + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Bandage" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + + + +function SWEP:SecondaryAttack() + + self.Weapon:SetNextSecondaryFire( CurTime() + self.Secondary.Delay ) + + local ply = self.Owner + + if not IsValid( ply ) or not ply:IsBleeding() then return end + + self.Weapon:SendWeaponAnim( ACT_VM_SECONDARYATTACK ) + + if SERVER then + ply:SetBleeding( false ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["StopBleedingOfYourself"][ConfigurationMedicMod.Language], 5 ) + timer.Simple(1.3, function() if not IsValid(self) then return end self:Remove() end) + end + +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +function SWEP:PrimaryAttack() + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + + local ent = self.Owner:GetEyeTrace().Entity + + if not IsValid( self.Owner ) then return end + + if not IsValid(ent) or ent:GetPos():Distance( self.Owner:GetPos() ) > 200 then return end + + if ent:IsPlayer() && ent:IsBleeding() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + ent:SetBleeding( false ) + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["StopBleedingOfSomeone"][ConfigurationMedicMod.Language], 5 ) + ent:MedicNotif( ConfigurationMedicMod.Sentences["SomeoneStopBleedingOfYou"][ConfigurationMedicMod.Language], 5 ) + timer.Simple(1.3, function() if not IsValid(self) then return end self:Remove() end) + end + + elseif ent:IsDeathRagdoll() && ent:GetOwner():IsBleeding() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + local ply = ent:GetOwner() + + ply:SetBleeding( false ) + ply:Stabilize( self.Owner ) + + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["StopBleedingOfSomeone"][ConfigurationMedicMod.Language], 5 ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["SomeoneStopBleedingOfYou"][ConfigurationMedicMod.Language], 5 ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() && ent.ragdoll:GetOwner():IsBleeding() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + local ply = ent.ragdoll:GetOwner() + + ply:SetBleeding( false ) + ply:Stabilize( self.Owner ) + + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["StopBleedingOfSomeone"][ConfigurationMedicMod.Language], 5 ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["SomeoneStopBleedingOfYou"][ConfigurationMedicMod.Language], 5 ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + + end + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "pistol" ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/defibrillator/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/defibrillator/shared.lua new file mode 100644 index 0000000..c6cfaab --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/defibrillator/shared.lua @@ -0,0 +1,121 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/defib/v_defib.mdl" +SWEP.WorldModel = "models/medicmod/defib/w_defib.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to attack" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Defibrillator" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + + + +function SWEP:SecondaryAttack() +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +local percentageChance = math.Clamp( ConfigurationMedicMod.DefibrillatorShockChance,1,100) + +function SWEP:PrimaryAttack() + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + local ent = self.Owner:GetEyeTrace().Entity + + if not IsValid( self.Owner ) then return end + + if not IsValid(ent) or ent:GetPos():Distance( self.Owner:GetPos() ) > 200 then return end + + if ent:IsPlayer() && ent:GetHeartAttack() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + local randChance = math.random( 1, 100 ) + if randChance <= percentageChance then self.Owner:MedicNotif(ConfigurationMedicMod.Sentences["The shock did not work. Try again"][ConfigurationMedicMod.Language]) return end + + ent:SetHeartAttack( false ) + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["The heart of the wounded man began to beat again"][ConfigurationMedicMod.Language], 5 ) + ent:MedicNotif( ConfigurationMedicMod.Sentences["Your heart started to beat again"][ConfigurationMedicMod.Language], 5 ) + end + + elseif ent:IsDeathRagdoll() && ent:GetOwner():GetHeartAttack() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + local randChance = math.random( 1, 100 ) + if randChance <= percentageChance then self.Owner:MedicNotif(ConfigurationMedicMod.Sentences["The shock did not work. Try again"][ConfigurationMedicMod.Language]) return end + + local ply = ent:GetOwner() + + ply:SetHeartAttack( false ) + ply:Stabilize( self.Owner ) + + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["The heart of the wounded man began to beat again"][ConfigurationMedicMod.Language], 5 ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["Your heart started to beat again"][ConfigurationMedicMod.Language], 5 ) + + end + + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() && ent.ragdoll:GetOwner():GetHeartAttack() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + local randChance = math.random( 1, 100 ) + if randChance <= percentageChance then self.Owner:MedicNotif(ConfigurationMedicMod.Sentences["The shock did not work. Try again"][ConfigurationMedicMod.Language]) return end + + local ply = ent.ragdoll:GetOwner() + + ply:SetHeartAttack( false ) + ply:Stabilize( self.Owner ) + + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["The heart of the wounded man began to beat again"][ConfigurationMedicMod.Language], 5 ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["Your heart started to beat again"][ConfigurationMedicMod.Language], 5 ) + end + + end + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "" ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/first_aid_kit/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/first_aid_kit/shared.lua new file mode 100644 index 0000000..513f6f9 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/first_aid_kit/shared.lua @@ -0,0 +1,181 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/firstaidkit/v_firstaidkit.mdl" +SWEP.WorldModel = "models/medicmod/firstaidkit/w_firstaidkit.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to attack" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 0.5 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "First Aid Kit" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +function SWEP:SetupDataTables() + self:NetworkVar("Int", 0, "Bandage") + self:NetworkVar("Int", 1, "Antidote") + self:NetworkVar("Int", 2, "Morphine") + self:NetworkVar("Int", 3, "Active") + if SERVER then + self:SetBandage( 0 ) + self:SetAntidote( 0 ) + self:SetMorphine( 0 ) + self:SetActive( 1 ) + end +end + +local sentences = ConfigurationMedicMod.Sentences +local lang = ConfigurationMedicMod.Language + +function SWEP:SecondaryAttack() + + self:SetHoldType("grenade") + + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + timer.Simple(0.3, function() + + if self:GetBandage() > 0 or self:GetAntidote() > 0 or self:GetMorphine() > 0 then + local fak = ents.Create("firstaidkit_medicmod") + fak:SetPos(self.Owner:GetPos() + self.Owner:GetAngles():Forward() * 15 + self.Owner:GetAngles():Up() * 50) + fak:Spawn() + fak.Content = { + ["bandage"] = self:GetBandage(), + ["syringe_antidote"] = self:GetAntidote(), + ["syringe_morphine"] = self:GetMorphine(), + } + else + self.Owner:MedicNotif(sentences["Your care kit was thrown away because it is empty"][lang], 5) + end + + if IsValid( self ) then self:Remove() end + + end) + end + +end + +function SWEP:ShouldDropOnDie() + return false +end + +local nextSelectTime = CurTime() + +function SWEP:Reload() + + if nextSelectTime >= CurTime() then return end + + nextSelectTime = CurTime() + 1 + + if self:GetActive() == 3 then + self:SetActive(1) + else + self:SetActive(self:GetActive()+1) + end + +end + +function SWEP:PrimaryAttack() + + if not SERVER then return end + + if not IsValid( self.Owner ) then return end + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + local items = { + [1] = "bandage", + [2] = "syringe_morphine", + [3] = "syringe_antidote" + } + + local selecteditem = items[self:GetActive()] + + if selecteditem == "bandage" && self:GetBandage() > 0 then + if self.Owner:HasWeapon( selecteditem ) then self.Owner:MedicNotif(sentences["You already carry this element on you"][lang]) return end + self:SetBandage( self:GetBandage() - 1) + self.Owner:Give(selecteditem) + return + end + if selecteditem == "syringe_morphine" && self:GetMorphine() > 0 then + if self.Owner:HasWeapon( selecteditem ) then self.Owner:MedicNotif(sentences["You already carry this element on you"][lang]) return end + self:SetMorphine( self:GetMorphine() - 1) + self.Owner:Give(selecteditem) + return + end + if selecteditem == "syringe_antidote" && self:GetAntidote() > 0 then + if self.Owner:HasWeapon( selecteditem ) then self.Owner:MedicNotif(sentences["You already carry this element on you"][lang]) return end + self:SetAntidote( self:GetAntidote() - 1) + self.Owner:Give(selecteditem) + return + end + + +end + +function SWEP:DrawHUD() + + + local outlineColor1 = Color(0,0,0) + local outlineColor2 = Color(0,0,0) + local outlineColor3 = Color(0,0,0) + + local items = { + [1] = sentences["Bandage"][lang], + [2] = sentences["Morphine"][lang], + [3] = sentences["Antidote"][lang] + } + + if self:GetActive() == 1 then + outlineColor1 = Color(200,50,50) + elseif self:GetActive() == 2 then + outlineColor2 = Color(200,50,50) + elseif self:GetActive() == 3 then + outlineColor3 = Color(200,50,50) + end + + + draw.SimpleTextOutlined(sentences["Bandage"][lang].." : "..self:GetBandage(), "MedicModFont20", ScrW()/2 - ScrW()*0.1,ScrH()/2 + ScrH()*0.2,Color(255,255,255),1,1,1,outlineColor1) + draw.SimpleTextOutlined(sentences["Antidote"][lang].." : "..self:GetAntidote(), "MedicModFont20", ScrW()/2 + ScrW()*0.1,ScrH()/2 + ScrH()*0.2,Color(255,255,255),1,1,1,outlineColor3) + draw.SimpleTextOutlined(sentences["Morphine"][lang].." : "..self:GetMorphine(), "MedicModFont20", ScrW()/2,ScrH()/2 + ScrH()*0.25,Color(255,255,255),1,1,1,outlineColor2) + + draw.SimpleTextOutlined(sentences["Item selected"][lang].." : "..items[self:GetActive()], "MedicModFont20", ScrW()/2,ScrH()/2 + ScrH()*0.3 + 40,Color(255,255,255),1,1,1,Color(0,0,0)) + draw.SimpleTextOutlined(sentences["Press [RELOAD] to select another item"][lang], "MedicModFont20", ScrW()/2,ScrH()/2 + ScrH()*0.3 + 70,Color(255,255,255),1,1,1,Color(0,0,0)) + draw.SimpleTextOutlined(sentences["Press [LEFT CLICK] to take"][lang], "MedicModFont20", ScrW()/2,ScrH()/2 + ScrH()*0.3 + 100,Color(255,255,255),1,1,1,Color(0,0,0)) + draw.SimpleTextOutlined(sentences["Press [RIGHT CLICK] to drop"][lang], "MedicModFont20", ScrW()/2,ScrH()/2 + ScrH()*0.3 + 130,Color(255,255,255),1,1,1,Color(0,0,0)) + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "normal" ) + self:SetBandage( 0 ) + self:SetAntidote( 0 ) + self:SetMorphine( 0 ) + self:SetActive( 1 ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/heart_massage/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/heart_massage/shared.lua new file mode 100644 index 0000000..3bde0b4 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/heart_massage/shared.lua @@ -0,0 +1,55 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.WorldModel = "" +SWEP.ViewModel = "models/medicmod/heart_massage/v_heart_massage.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to attack" + +SWEP.Spawnable = false +SWEP.AdminSpawnable = false + +SWEP.Primary.Damage = 0 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Heart Massage" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + + + +function SWEP:SecondaryAttack() +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +function SWEP:PrimaryAttack() + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "normal" ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/special_bandage/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/special_bandage/shared.lua new file mode 100644 index 0000000..6b104d7 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/special_bandage/shared.lua @@ -0,0 +1,104 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/bandage/v_bandage.mdl" +SWEP.WorldModel = "models/medicmod/bandage/w_bandage.mdl" +SWEP.Author = "Scripty" +SWEP.Instructions = "На лкм лечить другого, на пкм сеья(спасает от переломов)" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 2 + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Шина" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +function SWEP:PrimaryAttack() + self.Weapon:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + local ply = self.Owner + if not IsValid(ply) then return end + + local ent = ply:GetEyeTrace().Entity + if not IsValid(ent) or not ent:IsPlayer() or ent:GetPos():Distance(ply:GetPos()) > 200 then + return + end + + if SERVER then + local used = false + + if ent:HaveFractures() then + for k,v in pairs(ent:GetFractures() or {}) do + ent:SetFracture(false, k) + used = true + end + end + + if ent:IsBleeding() then + ent:SetBleeding(false) + used = true + end + + if used then + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + ply:SetAnimation(PLAYER_ATTACK1) + ply:MedicNotif(ConfigurationMedicMod.Sentences["SomeoneStopBleedingOfYou"][ConfigurationMedicMod.Language], 5) + ent:MedicNotif(ConfigurationMedicMod.Sentences["You have no more fracture"][ConfigurationMedicMod.Language], 5) + ent:MedicNotif(ConfigurationMedicMod.Sentences["SomeoneStopBleedingOfYou"][ConfigurationMedicMod.Language], 5) + self:Remove() + end + end +end + +function SWEP:SecondaryAttack() + self.Weapon:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + + local ply = self.Owner + if not IsValid(ply) then return end + + if SERVER then + local used = false + if ply:HaveFractures() then + for k,v in pairs(ply:GetFractures() or {}) do + ply:SetFracture(false, k) + used = true + end + end + if ply:IsBleeding() then + ply:SetBleeding(false) + used = true + end + if used then + self:SendWeaponAnim(ACT_VM_SECONDARYATTACK) + ply:SetAnimation(PLAYER_ATTACK1) + ply:MedicNotif(ConfigurationMedicMod.Sentences["You have no more fracture"][ConfigurationMedicMod.Language], 5) + ply:MedicNotif(ConfigurationMedicMod.Sentences["StopBleedingOfYourself"][ConfigurationMedicMod.Language], 5) + self:Remove() + end + end +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType("pistol") +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_antidote/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_antidote/shared.lua new file mode 100644 index 0000000..bb9d060 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_antidote/shared.lua @@ -0,0 +1,127 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/syringe/v_syringe.mdl" +SWEP.WorldModel = "models/medicmod/syringe/w_syringe.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to attack" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 2 + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Antidote" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + + + +function SWEP:SecondaryAttack() + + self.Weapon:SetNextSecondaryFire( CurTime() + self.Secondary.Delay ) + + local ply = self.Owner + + if not IsValid( ply ) or not ply:IsPoisoned() then return end + + self.Weapon:SendWeaponAnim( ACT_VM_SECONDARYATTACK ) + + if SERVER then + ply:SetPoisoned( false ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["You have stopped your poisoning"][ConfigurationMedicMod.Language], 5 ) + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +function SWEP:PrimaryAttack() + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + local ent = self.Owner:GetEyeTrace().Entity + + if not IsValid( self.Owner ) then return end + + if not IsValid(ent) or ent:GetPos():Distance( self.Owner:GetPos() ) > 200 then return end + + if ent:IsPlayer() && ent:IsPoisoned() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + ent:SetPoisoned( false ) + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["You have stopped the poisoning of someone"][ConfigurationMedicMod.Language], 5 ) + ent:MedicNotif( ConfigurationMedicMod.Sentences["Someone stopped your poisoning"][ConfigurationMedicMod.Language], 5 ) + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + + elseif ent:IsDeathRagdoll() && ent:GetOwner():IsPoisoned() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + local ply = ent:GetOwner() + + ply:SetPoisoned( false ) + ply:Stabilize( self.Owner ) + + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["You have stopped the poisoning of someone"][ConfigurationMedicMod.Language], 5 ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["Someone stopped your poisoning"][ConfigurationMedicMod.Language], 5 ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() && ent.ragdoll:GetOwner():IsPoisoned() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + local ply = ent.ragdoll:GetOwner() + + ply:SetPoisoned( false ) + ply:Stabilize( self.Owner ) + + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["You have stopped the poisoning of someone"][ConfigurationMedicMod.Language], 5 ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["Someone stopped your poisoning"][ConfigurationMedicMod.Language], 5 ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + end + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "pistol" ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_morphine/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_morphine/shared.lua new file mode 100644 index 0000000..6ceed95 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_morphine/shared.lua @@ -0,0 +1,100 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/syringe/v_syringe.mdl" +SWEP.WorldModel = "models/medicmod/syringe/w_syringe.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to use" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 0 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 2 + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Morphine" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + + + +function SWEP:SecondaryAttack() + + self.Weapon:SetNextSecondaryFire( CurTime() + self.Secondary.Delay ) + + local ply = self.Owner + + if not IsValid( ply ) or ply:IsMorphine() then return end + + self.Weapon:SendWeaponAnim( ACT_VM_SECONDARYATTACK ) + + if SERVER then + ply:SetMorphine( true ) + ply:MedicNotif( ConfigurationMedicMod.Sentences["You injected some morphine"][ConfigurationMedicMod.Language], 5 ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + + timer.Simple( ConfigurationMedicMod.MorphineEffectTime, function() ply:SetMorphine( false ) end ) + end + +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +function SWEP:PrimaryAttack() + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + local ent = self.Owner:GetEyeTrace().Entity + + if not IsValid( self.Owner ) then return end + + if not IsValid(ent) or ent:GetPos():Distance( self.Owner:GetPos() ) > 200 then return end + + if ent:IsPlayer() and not ent:IsMorphine() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + ent:SetMorphine( true ) + + self.Owner:MedicNotif( ConfigurationMedicMod.Sentences["You injected morphine to somebody"][ConfigurationMedicMod.Language], 5 ) + + timer.Simple( 1, function() if not IsValid(self) then return end self:Remove() end) + + timer.Simple( ConfigurationMedicMod.MorphineEffectTime, function() ent:SetMorphine( false ) end ) + end + + end + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "pistol" ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_poison/shared.lua b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_poison/shared.lua new file mode 100644 index 0000000..b509156 --- /dev/null +++ b/garrysmod/addons/advanced_medic_mod_wdrm/lua/weapons/syringe_poison/shared.lua @@ -0,0 +1,103 @@ +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/medicmod/syringe/v_syringe.mdl" +SWEP.WorldModel = "models/medicmod/syringe/w_syringe.mdl" +SWEP.Author = "Venatuss" +SWEP.Instructions = "Click to attack" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +SWEP.Primary.Damage = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 2 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 2 + +SWEP.Category = "Medic Mod" +SWEP.PrintName = "Poison Syringe" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + + + +function SWEP:SecondaryAttack() +end + +function SWEP:ShouldDropOnDie() + return false +end + +function SWEP:Reload() +end + +function SWEP:PrimaryAttack() + + self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + + local ent = self.Owner:GetEyeTrace().Entity + + if not IsValid( self.Owner ) then return end + + if not IsValid(ent) or ent:GetPos():Distance( self.Owner:GetPos() ) > 200 then return end + + if ent:IsPlayer() and not ent:IsPoisoned() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + ent:SetPoisoned( true ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + + elseif ent:IsDeathRagdoll() and not ent:GetOwner():IsPoisoned() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + local ply = ent:GetOwner() + + ply:SetPoisoned( true ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + + elseif IsValid(ent.ragdoll) && ent.ragdoll:IsDeathRagdoll() && not ent.ragdoll:GetOwner():IsPoisoned() then + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + local ply = ent.ragdoll:GetOwner() + + ply:SetPoisoned( true ) + + timer.Simple(1, function() if not IsValid(self) then return end self:Remove() end) + end + end + +end + +function SWEP:Deploy() + return true +end +function SWEP:Initialize() + self:SetHoldType( "pistol" ) +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.dx80.vtx new file mode 100644 index 0000000..d71c2ab Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.dx90.vtx new file mode 100644 index 0000000..4734191 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.mdl new file mode 100644 index 0000000..dd69a94 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.phy new file mode 100644 index 0000000..09cb4eb Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.sw.vtx new file mode 100644 index 0000000..30bb973 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.vvd new file mode 100644 index 0000000..5c88b76 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/bandage.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.dx80.vtx new file mode 100644 index 0000000..db84b54 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.dx90.vtx new file mode 100644 index 0000000..9ac3fde Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.mdl new file mode 100644 index 0000000..597bb0c Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.sw.vtx new file mode 100644 index 0000000..b9d6ab5 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.vvd new file mode 100644 index 0000000..e392284 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/v_bandage.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.dx80.vtx new file mode 100644 index 0000000..726aaf1 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.dx90.vtx new file mode 100644 index 0000000..c1a4131 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.mdl new file mode 100644 index 0000000..ea3e285 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.phy new file mode 100644 index 0000000..cce1ced Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.sw.vtx new file mode 100644 index 0000000..c134c50 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.vvd new file mode 100644 index 0000000..1d46935 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bandage/w_bandage.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.dx80.vtx new file mode 100644 index 0000000..b3a6c66 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.dx90.vtx new file mode 100644 index 0000000..79f7015 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.mdl new file mode 100644 index 0000000..fa919f7 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.phy new file mode 100644 index 0000000..14a80d2 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.sw.vtx new file mode 100644 index 0000000..2232875 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.vvd new file mode 100644 index 0000000..dcd6873 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/beaker/beaker.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.dx80.vtx new file mode 100644 index 0000000..dc1ec99 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.dx90.vtx new file mode 100644 index 0000000..fd84aee Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.mdl new file mode 100644 index 0000000..04943f2 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.phy new file mode 100644 index 0000000..d9a38b0 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.sw.vtx new file mode 100644 index 0000000..e64973e Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.vvd new file mode 100644 index 0000000..d8482a0 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/bloodbag/bloodbag.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.dx80.vtx new file mode 100644 index 0000000..1ba3f29 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.dx90.vtx new file mode 100644 index 0000000..a33152e Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.mdl new file mode 100644 index 0000000..c8b71f5 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.sw.vtx new file mode 100644 index 0000000..f213d19 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.vvd new file mode 100644 index 0000000..b256982 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/v_defib.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.dx80.vtx new file mode 100644 index 0000000..c909194 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.dx90.vtx new file mode 100644 index 0000000..2664117 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.mdl new file mode 100644 index 0000000..92a45b4 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.phy new file mode 100644 index 0000000..e4f1833 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.sw.vtx new file mode 100644 index 0000000..5f18935 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.vvd new file mode 100644 index 0000000..a6280a5 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/defib/w_defib.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.dx80.vtx new file mode 100644 index 0000000..ed6bc98 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.dx90.vtx new file mode 100644 index 0000000..4aeb95e Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.mdl new file mode 100644 index 0000000..ee04269 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.phy new file mode 100644 index 0000000..e4f335c Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.sw.vtx new file mode 100644 index 0000000..15d8070 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.vvd new file mode 100644 index 0000000..aab54e9 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/drug/drug.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.dx80.vtx new file mode 100644 index 0000000..7a03c22 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.dx90.vtx new file mode 100644 index 0000000..f9ef9f8 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.mdl new file mode 100644 index 0000000..1d4df76 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.phy new file mode 100644 index 0000000..d55bbba Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.sw.vtx new file mode 100644 index 0000000..892c25a Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.vvd new file mode 100644 index 0000000..5aff673 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/electrocardiogram/electrocardiogram.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.dx80.vtx new file mode 100644 index 0000000..0c8e7f8 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.dx90.vtx new file mode 100644 index 0000000..d25371d Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.mdl new file mode 100644 index 0000000..48083cc Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.phy new file mode 100644 index 0000000..a41b12b Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.sw.vtx new file mode 100644 index 0000000..796c677 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.vvd new file mode 100644 index 0000000..431bf25 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/firstaidkit.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.dx80.vtx new file mode 100644 index 0000000..a9119fb Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.dx90.vtx new file mode 100644 index 0000000..8fd9731 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.mdl new file mode 100644 index 0000000..1513bbe Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.sw.vtx new file mode 100644 index 0000000..737aead Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.vvd new file mode 100644 index 0000000..ea1ab17 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/v_firstaidkit.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.dx80.vtx new file mode 100644 index 0000000..11c5d3d Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.dx90.vtx new file mode 100644 index 0000000..66c8f97 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.mdl new file mode 100644 index 0000000..95e7e26 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.phy new file mode 100644 index 0000000..cb23c2e Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.sw.vtx new file mode 100644 index 0000000..0a4a7a4 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.vvd new file mode 100644 index 0000000..0ff1337 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/firstaidkit/w_firstaidkit.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.dx80.vtx new file mode 100644 index 0000000..ba2b557 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.dx90.vtx new file mode 100644 index 0000000..76ee00b Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.mdl new file mode 100644 index 0000000..2b7931c Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.sw.vtx new file mode 100644 index 0000000..cd30d15 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.vvd new file mode 100644 index 0000000..36f7159 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/heart_massage/v_heart_massage.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.dx80.vtx new file mode 100644 index 0000000..cfd39c5 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.dx90.vtx new file mode 100644 index 0000000..6b93065 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.mdl new file mode 100644 index 0000000..76fbd6d Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.phy new file mode 100644 index 0000000..cbecb2a Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.sw.vtx new file mode 100644 index 0000000..7f8b833 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.vvd new file mode 100644 index 0000000..cab9e35 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/hospital_bed/hospital_bed.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.dx80.vtx new file mode 100644 index 0000000..6a4df20 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.dx90.vtx new file mode 100644 index 0000000..2e150e6 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.mdl new file mode 100644 index 0000000..bc76453 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.phy new file mode 100644 index 0000000..c8625c0 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.sw.vtx new file mode 100644 index 0000000..8b6ce01 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.vvd new file mode 100644 index 0000000..335b74c Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_stand/medical_stand.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.dx80.vtx new file mode 100644 index 0000000..c7f47f3 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.dx90.vtx new file mode 100644 index 0000000..b3ba6d9 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.mdl new file mode 100644 index 0000000..bf60115 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.phy new file mode 100644 index 0000000..1b24a22 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.sw.vtx new file mode 100644 index 0000000..7092fd8 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.vvd new file mode 100644 index 0000000..27ed86b Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/medical_terminal/medical_terminal.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.dx80.vtx new file mode 100644 index 0000000..4e77c11 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.dx90.vtx new file mode 100644 index 0000000..783a1dc Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.mdl new file mode 100644 index 0000000..d91a905 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.phy new file mode 100644 index 0000000..828c1c7 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.sw.vtx new file mode 100644 index 0000000..f9b87d9 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.vvd new file mode 100644 index 0000000..773062a Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/mural_defib/mural_defib.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.dx80.vtx new file mode 100644 index 0000000..b7bc5cf Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.dx90.vtx new file mode 100644 index 0000000..78b56f0 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.mdl new file mode 100644 index 0000000..2947494 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.phy new file mode 100644 index 0000000..6f1b584 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.sw.vtx new file mode 100644 index 0000000..2a74cfa Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.vvd new file mode 100644 index 0000000..c481efd Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/player/medic_anims.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.dx80.vtx new file mode 100644 index 0000000..898442b Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.dx90.vtx new file mode 100644 index 0000000..98d1222 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.mdl new file mode 100644 index 0000000..a234019 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.phy new file mode 100644 index 0000000..a74aab8 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.sw.vtx new file mode 100644 index 0000000..3e79cef Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.vvd new file mode 100644 index 0000000..d2c570a Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/radio/radio.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.dx80.vtx new file mode 100644 index 0000000..fbedf4e Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.dx90.vtx new file mode 100644 index 0000000..904d205 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.mdl new file mode 100644 index 0000000..71097de Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.phy new file mode 100644 index 0000000..b78b82d Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.sw.vtx new file mode 100644 index 0000000..efeb339 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.vvd new file mode 100644 index 0000000..b1630f8 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/stretcher/stretcher.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.dx80.vtx new file mode 100644 index 0000000..67af283 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.dx90.vtx new file mode 100644 index 0000000..a10d617 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.mdl new file mode 100644 index 0000000..23f12f8 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.sw.vtx new file mode 100644 index 0000000..2a0ba46 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.vvd new file mode 100644 index 0000000..78d3c32 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/v_syringe.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.dx80.vtx new file mode 100644 index 0000000..bff3040 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.dx90.vtx new file mode 100644 index 0000000..de1206e Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.mdl new file mode 100644 index 0000000..a5d178d Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.phy new file mode 100644 index 0000000..1ea2e84 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.sw.vtx new file mode 100644 index 0000000..54e544f Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.vvd new file mode 100644 index 0000000..e5259e8 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/syringe/w_syringe.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.dx80.vtx new file mode 100644 index 0000000..d1c8f2e Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.dx90.vtx new file mode 100644 index 0000000..69d3aa4 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.mdl new file mode 100644 index 0000000..b032743 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.phy new file mode 100644 index 0000000..e7441b1 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.sw.vtx new file mode 100644 index 0000000..4eed961 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.vvd new file mode 100644 index 0000000..b81d40c Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/test_tube/testtube.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.dx80.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.dx80.vtx new file mode 100644 index 0000000..a565038 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.dx80.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.dx90.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.dx90.vtx new file mode 100644 index 0000000..e94e92d Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.dx90.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.mdl b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.mdl new file mode 100644 index 0000000..17ea7df Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.mdl differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.phy b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.phy new file mode 100644 index 0000000..6f3edea Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.phy differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.sw.vtx b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.sw.vtx new file mode 100644 index 0000000..e79695c Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.sw.vtx differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.vvd b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.vvd new file mode 100644 index 0000000..cb30142 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/models/medicmod/valvebag/valvebag.vvd differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/resource/fonts/bariol.ttf b/garrysmod/addons/advanced_medic_mod_wdrm/resource/fonts/bariol.ttf new file mode 100644 index 0000000..3c7b086 Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/resource/fonts/bariol.ttf differ diff --git a/garrysmod/addons/advanced_medic_mod_wdrm/resource/fonts/writtenonhishands.ttf b/garrysmod/addons/advanced_medic_mod_wdrm/resource/fonts/writtenonhishands.ttf new file mode 100644 index 0000000..f2d110a Binary files /dev/null and b/garrysmod/addons/advanced_medic_mod_wdrm/resource/fonts/writtenonhishands.ttf differ diff --git a/garrysmod/addons/anims/lua/autorun/sh_agrp_custom_anims.lua b/garrysmod/addons/anims/lua/autorun/sh_agrp_custom_anims.lua new file mode 100644 index 0000000..0b13ee9 --- /dev/null +++ b/garrysmod/addons/anims/lua/autorun/sh_agrp_custom_anims.lua @@ -0,0 +1,262 @@ +AGanim = AGanim or {} + +local pl = FindMetaTable("Player") +local AnimationTable = { + [1] = {name = "Помахать руками", anim = "rp_wave", loop = false}, + [2] = {name = "Помахать рукой", anim = "gesture_wave_original", loop = false}, + [3] = {name = "Показать пальцем вперед", anim = "rp_point", loop = false}, + [4] = {name = "Показать пальцем назад", anim = "rp_point_back", loop = false}, + [5] = {name = "Поправить галстук", anim = "menu_gman", loop = false}, + [6] = {name = "Приседания", anim = "rp_sport1_loop", loop = true}, + [7] = {name = "Отжимания", anim = "rp_sport2_loop", loop = true}, + [8] = {name = "Подьем корпуса", anim = "rp_sport3_loop", loop = true}, + [9] = {name = "Берпи", anim = "rp_sport4_loop", loop = true}, + [10] = {name = "Стоять злобно", anim = "rp_angry_loop", loop = true}, + [11] = {name = "Стоять напуганно", anim = "idle_all_scared", loop = true}, + [12] = {name = "Стоять, сложив руки", anim = "pose_standing_01", loop = true}, + [13] = {name = "Стоять, руки на поясе", anim = "pose_standing_02", loop = true}, + [14] = {name = "Стоять, держась за пояс", anim = "rp_cop_idle", loop = true}, + [15] = {name = "Сидеть, положив руку на колено", anim = "pose_ducking_01", loop = true}, + [16] = {name = "Сидеть в позе лотаса", anim = "pose_ducking_02", loop = true}, + [17] = {name = "Сидеть в сонном состоянии", anim = "rp_sit_loop", loop = true}, + [18] = {name = "Лежать в плохом состоянии", anim = "rp_injured_loop", loop = true}, + [19] = {name = "Танец 1", anim = "rp_dance1_loop", loop = true}, + [20] = {name = "Танец 2", anim = "rp_dance2_loop", loop = true}, + [21] = {name = "Танец 3", anim = "rp_dance3_loop", loop = true}, + [22] = {name = "Танец 4", anim = "taunt_dance_base", loop = true}, + [23] = {name = "Показать согласие", anim = "gesture_agree_original", loop = false}, + [24] = {name = "Показать несогласие", anim = "gesture_disagree_original", loop = false}, + [25] = {name = "Позвать с собой", anim = "gesture_becon_original", loop = false}, + [26] = {name = "Поклонится", anim = "gesture_bow_original", loop = false}, + [27] = {name = "Отдать честь", anim = "gesture_salute_original", loop = false}, + [28] = {name = "Пить кофе", anim = "rp_drinking", loop = true, prop = {model = "models/props_junk/garbage_coffeemug001a.mdl", spawnfunc = function(mod, ent) + local att = ent:LookupAttachment("anim_attachment_RH") + local lvec = Vector(1,0,1) + + timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function() + if IsValid(mod) then + local tab = ent:GetAttachment(att) + mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x) + mod:SetAngles(tab.Ang) + if !IsValid(ent) or !ent:Alive() then + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + mod:Remove() + end + else + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + end + end) + end}}, + [29] = {name = "Смотреть на обьект, сидя", anim = "rp_medic_idle", loop = true}, + [30] = {name = "Записывать в блокнот", anim = "rp_writing", loop = true, prop = {model = "models/props_lab/clipboard.mdl", spawnfunc = function(mod, ent) + local att = ent:LookupAttachment("anim_attachment_RH") + local lvec = Vector(2,-2,2) + + timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function() + if IsValid(mod) then + local tab = ent:GetAttachment(att) + mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x) + mod:SetAngles(tab.Ang+Angle(140,0,-100)) + if !IsValid(ent) or !ent:Alive() then + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + mod:Remove() + end + else + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + end + end) + end}}, + [31] = {name = "Спать", anim = "rp_sleep", loop = true}, + [32] = {name = "Воинское приветствие", anim = "rp_salute1", loop = true, loop_stop = true}, +} + +if SERVER then + concommand.Add("rp_set_taunt", function(ply, cmd, args) + local arg = tonumber(args[1]) + + if arg > 0 and arg <= #AnimationTable then + local tab = AnimationTable[arg] + ply:SetTAnimation(tab.anim, !tab.loop, arg) + end + end) + + function pl:SetTAnimation(anim, autostop, id) + self:SetNWString('TauntAnim', anim) + self:SetNWFloat('TauntID', id) + self:SetNWFloat('TAnimDelay', select(2, self:LookupSequence(anim))) + self:SetNWFloat('TAnimStartTime', CurTime()) + self:SetCycle(0) + + local wep = self:GetActiveWeapon() + if IsValid(wep) and anim != "" then + self.TauntPreviousWeapon = wep:GetClass() + self:SetActiveWeapon(nil) + elseif anim == "" and isstring(self.TauntPreviousWeapon) then + self:SelectWeapon(self.TauntPreviousWeapon) + self.TauntPreviousWeapon = nil + end + + if autostop then + local delay = select(2, self:LookupSequence(anim)) + timer.Create("TauntAGRP"..self:EntIndex(), delay, 1, function() + if !IsValid(self) then return end + + local anim2 = self:GetNWString('TauntAnim') + + if anim == anim2 then + self:SetTAnimation("") + end + end) + end + end + + hook.Add("PlayerSwitchWeapon", "rp_Taunt", function( ply, oldWeapon, newWeapon ) + local str = ply:GetNWString('TauntAnim') + if str != "" then + return true + end + end) + + hook.Add("Think", "rp_Taunt", function() + local plytab = player.GetAll() + for i=1,#plytab do + local ply = plytab[i] + local str = ply:GetNWString('TauntAnim') + if str != "" and (ply:InVehicle() or !ply:Alive() or ply:WaterLevel() >= 2) then + ply:SetTAnimation("") + end + end + end) +else + CreateConVar("rp_taunt_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1) + CreateConVar("rp_taunt_realistic_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1) + + hook.Add("CalcView", "rp_TauntCam", function(ply, pos, angles, fov) + local str = ply:GetNWString('TauntAnim') + if str != "" then + if GetConVar("rp_taunt_firstperson"):GetBool() then + local att = ply:GetAttachment(ply:LookupAttachment('eyes')) + local ang1 = angles + if GetConVar("rp_taunt_realistic_firstperson"):GetBool() then + ang1 = att.Ang + end + local view = { + origin = att.Pos, + angles = ang1, + fov = fov, + drawviewer = true + } + + return view + else + local view = { + origin = pos - ( angles:Forward() * 100 ), + angles = angles, + fov = fov, + drawviewer = true + } + + return view + end + end + end) + + hook.Add("HUDPaint", "rp_TauntCam", function() + local ply = LocalPlayer() + local str = ply:GetNWString('TauntAnim') + if str != "" then + draw.SimpleTextOutlined("Нажмите SHIFT, чтобы убрать анимацию.", "Trebuchet18", ScrW()/2, ScrH()-250, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, color_black) + end + end) + + function AGRP_OpenTauntMenu() + local d = vgui.Create("DFrame") + d:SetSize(200, 300) + d:Center() + d:SetTitle("Меню анимаций") + d:MakePopup() + + local sc = vgui.Create("DScrollPanel", d) + sc:Dock(FILL) + + local dc = sc:Add("DCheckBoxLabel") + dc:SetText("Включить первое лицо?") + dc:SetConVar("rp_taunt_firstperson") + dc:Dock(TOP) + dc:DockMargin(0, 0, 0, 5) + + local dc = sc:Add("DCheckBoxLabel") + dc:SetText("Включить поворот глаз от анимации?") + dc:SetConVar("rp_taunt_realistic_firstperson") + dc:Dock(TOP) + dc:DockMargin(0, 0, 0, 15) + + for i=1, #AnimationTable do + local tab = AnimationTable[i] + local but = sc:Add("DButton") + but:SetText(tab.name) + but:Dock(TOP) + but:DockMargin(0, 0, 0, 5) + but.DoClick = function(self) + RunConsoleCommand("rp_set_taunt", i) + end + end + end + + concommand.Add("rp_taunt_menu", AGRP_OpenTauntMenu) +end + +---SHARED PART + +hook.Add("SetupMove", "rp_Taunt", function(ply, mvd, cmd) + local str = ply:GetNWString('TauntAnim') + if str != "" then + mvd:SetMaxSpeed(1) + mvd:SetMaxClientSpeed(1) + if SERVER then + if ply:KeyDown(IN_SPEED) then + ply:SetTAnimation("") + end + end + end +end) + +hook.Add("CalcMainActivity", "rp_SVAnims", function(ply, vel) + local str = ply:GetNWString('TauntAnim') + local num = ply:GetNWFloat('TAnimDelay') + local id = ply:GetNWFloat('TauntID') + local st = ply:GetNWFloat('TAnimStartTime') + if str != "" and ply:Alive() then + local ls = AnimationTable[id].loop_stop + if ply:GetCycle() >= 1 then + if not ls then + ply:SetCycle(0) + if SERVER then + ply:SetNWFloat('TAnimStartTime', CurTime()) + end + end + else + ply:SetCycle((CurTime()-st)/num) + + local tab = AnimationTable[id].prop + if CLIENT and istable(tab) and (!IsValid(ply.TauntProp) or ply.TauntProp:GetModel() != tab.model) then + if IsValid(ply.TauntProp) then + ply.TauntProp:Remove() + end + ply.TauntProp = ClientsideModel(tab.model) + tab.spawnfunc(ply.TauntProp, ply) + elseif CLIENT and !istable(tab) and IsValid(ply.TauntProp) then + ply.TauntProp:Remove() + end + end + return -1, ply:LookupSequence(str) + else + if SERVER then + ply:SetNWFloat('TauntID', 0) + else + if IsValid(ply.TauntProp) then + ply.TauntProp:Remove() + end + end + end +end) diff --git a/garrysmod/addons/anims/lua/wos/dynabase/registers/wos_ag_custom_registers.lua b/garrysmod/addons/anims/lua/wos/dynabase/registers/wos_ag_custom_registers.lua new file mode 100644 index 0000000..c752664 --- /dev/null +++ b/garrysmod/addons/anims/lua/wos/dynabase/registers/wos_ag_custom_registers.lua @@ -0,0 +1,15 @@ +hook.Add("InitLoadAnimations", "wOS.DynaBase.RP_CUSTOM", function() + wOS.DynaBase:RegisterSource({ + Name = "RP Custom Animations", + Type = WOS_DYNABASE.EXTENSION, + Male = "models/rp/player_rp_anims.mdl", + Female = "models/rp/player_rp_anims.mdl", + Zombie = "models/rp/player_rp_anims.mdl", + }) + + hook.Add("PreLoadAnimations", "wOS.DynaBase.RP_CUSTOM", function(gender) + if gender == WOS_DYNABASE.SHARED then + IncludeModel("models/rp/player_rp_anims.mdl") + end + end) +end) diff --git a/garrysmod/addons/anims/materials/models/props/sta_cigarette_01_diff.vmt b/garrysmod/addons/anims/materials/models/props/sta_cigarette_01_diff.vmt new file mode 100644 index 0000000..e7b0bbf --- /dev/null +++ b/garrysmod/addons/anims/materials/models/props/sta_cigarette_01_diff.vmt @@ -0,0 +1,9 @@ +"VertexlitGeneric" +{ + "$baseTexture" "models/props/sta_cigarette_01_diff" + + $color2 "[ .95 .95 .95 ]" + $color2 "{ 255 255 255 }" + +} + diff --git a/garrysmod/addons/anims/materials/models/props/sta_cigarette_01_diff.vtf b/garrysmod/addons/anims/materials/models/props/sta_cigarette_01_diff.vtf new file mode 100644 index 0000000..206d28a Binary files /dev/null and b/garrysmod/addons/anims/materials/models/props/sta_cigarette_01_diff.vtf differ diff --git a/garrysmod/addons/anims/materials/models/shibcoffee/cup.vmt b/garrysmod/addons/anims/materials/models/shibcoffee/cup.vmt new file mode 100644 index 0000000..06e2518 --- /dev/null +++ b/garrysmod/addons/anims/materials/models/shibcoffee/cup.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\shibcoffee\Cup" +} diff --git a/garrysmod/addons/anims/materials/models/shibcoffee/cup.vtf b/garrysmod/addons/anims/materials/models/shibcoffee/cup.vtf new file mode 100644 index 0000000..8216b7b Binary files /dev/null and b/garrysmod/addons/anims/materials/models/shibcoffee/cup.vtf differ diff --git a/garrysmod/addons/anims/materials/models/shibcoffee/cuphold.vmt b/garrysmod/addons/anims/materials/models/shibcoffee/cuphold.vmt new file mode 100644 index 0000000..8656909 --- /dev/null +++ b/garrysmod/addons/anims/materials/models/shibcoffee/cuphold.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\shibcoffee\CupHOLD" +} diff --git a/garrysmod/addons/anims/materials/models/shibcoffee/cuphold.vtf b/garrysmod/addons/anims/materials/models/shibcoffee/cuphold.vtf new file mode 100644 index 0000000..4d759f6 Binary files /dev/null and b/garrysmod/addons/anims/materials/models/shibcoffee/cuphold.vtf differ diff --git a/garrysmod/addons/anims/materials/models/shibcoffee/holder.vmt b/garrysmod/addons/anims/materials/models/shibcoffee/holder.vmt new file mode 100644 index 0000000..681ef65 --- /dev/null +++ b/garrysmod/addons/anims/materials/models/shibcoffee/holder.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\shibcoffee\Holder" +} diff --git a/garrysmod/addons/anims/materials/models/shibcoffee/holder.vtf b/garrysmod/addons/anims/materials/models/shibcoffee/holder.vtf new file mode 100644 index 0000000..bb4a1e5 Binary files /dev/null and b/garrysmod/addons/anims/materials/models/shibcoffee/holder.vtf differ diff --git a/garrysmod/addons/anims/models/rp/player_rp_anims.ani b/garrysmod/addons/anims/models/rp/player_rp_anims.ani new file mode 100644 index 0000000..8d7f173 Binary files /dev/null and b/garrysmod/addons/anims/models/rp/player_rp_anims.ani differ diff --git a/garrysmod/addons/anims/models/rp/player_rp_anims.mdl b/garrysmod/addons/anims/models/rp/player_rp_anims.mdl new file mode 100644 index 0000000..4681e52 Binary files /dev/null and b/garrysmod/addons/anims/models/rp/player_rp_anims.mdl differ diff --git a/garrysmod/addons/anti_air/lua/autorun/swwf_launchers.lua b/garrysmod/addons/anti_air/lua/autorun/swwf_launchers.lua new file mode 100644 index 0000000..f24d83c --- /dev/null +++ b/garrysmod/addons/anti_air/lua/autorun/swwf_launchers.lua @@ -0,0 +1,4 @@ +if CLIENT then + killicon.Add( "weapon_sw_fim92", "vgui/hud/weapon_sw_fim92", Color( 255, 255, 255, 255 ) ) + killicon.Add( "weapon_sw_9k38", "vgui/hud/weapon_sw_9k38", Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/anti_air/lua/tfa/external/sw_9k38.lua b/garrysmod/addons/anti_air/lua/tfa/external/sw_9k38.lua new file mode 100644 index 0000000..a58318c --- /dev/null +++ b/garrysmod/addons/anti_air/lua/tfa/external/sw_9k38.lua @@ -0,0 +1,11 @@ + +TFA.AddFireSound("Igla.1", "sw/weapons/9k38/motor_fire.wav", CHAN_STATIC ) +TFA.AddWeaponSound("Igla.ready", "sw/weapons/9k38/retract.wav") +TFA.AddWeaponSound("Igla.clank", "sw/weapons/9k38/clank.wav") +TFA.AddWeaponSound("Igla.insert", "sw/weapons/9k38/insert.wav") +TFA.AddWeaponSound("Igla.slide", "sw/weapons/9k38/slide.wav") +TFA.AddWeaponSound("Igla.click", "sw/weapons/9k38/click.wav") +TFA.AddWeaponSound("Igla.return", "sw/weapons/9k38/return.wav") +TFA.AddWeaponSound("Igla.draw", "sw/weapons/9k38/draw.wav") +TFA.AddWeaponSound("Igla.holster", "sw/weapons/9k38/holster.wav") +TFA.AddWeaponSound("Stinger.open", "sw/weapons/fim92/deploy.wav") diff --git a/garrysmod/addons/anti_air/lua/weapons/weapon_sw_9k38.lua b/garrysmod/addons/anti_air/lua/weapons/weapon_sw_9k38.lua new file mode 100644 index 0000000..27708f2 --- /dev/null +++ b/garrysmod/addons/anti_air/lua/weapons/weapon_sw_9k38.lua @@ -0,0 +1,721 @@ +SWEP.Base = "tfa_gun_base" +SWEP.Category = "FT Explosive" -- The category. Please, just choose something generic or something I've already done if you plan on only doing like one swep.. +SWEP.Manufacturer = "SW Weapons Factory" -- Gun Manufactrer (e.g. Hoeckler and Koch ) +SWEP.Author = "Shermann Wolf" -- Author Tooltip +SWEP.Contact = "shermannwolf@gmail.com" -- Contact Info Tooltip +SWEP.Purpose = "Уничтожение низколетящих воздушных целей" -- Purpose Tooltip +SWEP.Instructions = "" -- Instructions Tooltip +SWEP.Spawnable = true -- Can you, as a normal user, spawn this? +SWEP.AdminSpawnable = true -- Can an adminstrator spawn this? Does not tie into your admin mod necessarily, unless its coded to allow for GMod's default ranks somewhere in its code. Evolve and ULX should work, but try to use weapon restriction rather than these. +SWEP.DrawCrosshair = false -- Draw the crosshair? +SWEP.DrawCrosshairIS = false -- Draw the crosshair in ironsights? +SWEP.PrintName = "9К38" -- Weapon name (Shown on HUD) +SWEP.Slot = 4 -- Slot in the weapon selection menu. Subtract 1, as this starts at 0. +SWEP.SlotPos = 73 -- Position in the slot +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.Weight = 30 -- This controls how "good" the weapon is for autopickup. +SWEP.Gun = ("weapon_sw_9k38") +SWEP.Type = "Launcher" + +--[[WEAPON HANDLING]] -- +SWEP.Primary.Sound = nil -- This is the sound of the weapon, when you shoot. +SWEP.Primary.PenetrationMultiplier = 1 -- Change the amount of something this gun can penetrate through +SWEP.Primary.Damage = 500 -- Damage, in standard damage points. +SWEP.Primary.DamageTypeHandled = true -- true will handle damagetype in base +SWEP.Primary.DamageType = nil -- See DMG enum. This might be DMG_SHOCK, DMG_BURN, DMG_BULLET, etc. Leave nil to autodetect. DMG_AIRBOAT opens doors. +SWEP.Primary.Force = nil -- Force value, leave nil to autocalc +SWEP.Primary.Knockback = 1 -- Autodetected if nil; this is the velocity kickback +SWEP.Primary.HullSize = 0 -- Big bullets, increase this value. They increase the hull size of the hitscan bullet. +SWEP.Primary.NumShots = 1 -- The number of shots the weapon fires. SWEP.Shotgun is NOT required for this to be >1. +SWEP.Primary.Automatic = false -- Automatic/Semi Auto +SWEP.Primary.RPM = 120 -- This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Displayed = 15 -- This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Semi = nil -- RPM for semi-automatic or burst fire. This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Burst = nil -- RPM for burst fire, overrides semi. This is in Rounds Per Minute / RPM +SWEP.Primary.BurstDelay = nil -- Delay between bursts, leave nil to autocalculate +SWEP.FiresUnderwater = false +SWEP.AllowViewAttachment = true +SWEP.Dispose = false +SWEP.DisposeTimer = 2 +SWEP.StripTimer = math.huge +-- Miscelaneous Sounds +SWEP.IronInSound = "" -- Sound to play when ironsighting in? nil for default +SWEP.IronOutSound = "" -- Sound to play when ironsighting out? nil for default +-- Silencing +SWEP.CanBeSilenced = false -- Can we silence? Requires animations. +SWEP.Silenced = false -- Silenced by default? +-- Selective Fire Stuff +SWEP.SelectiveFire = false -- Allow selecting your firemode? +SWEP.DisableBurstFire = false -- Only auto/single? +SWEP.OnlyBurstFire = false -- No auto, only burst/single? +SWEP.DefaultFireMode = "" -- Default to auto or whatev +SWEP.FireModeName = "Зенитная Управляемая Ракета" -- Change to a text value to override it +-- Ammo Related +SWEP.Primary.ClipSize = 1 -- This is the size of a clip +SWEP.Primary.DefaultClip = 0 -- This is the number of bullets the gun gives you, counting a clip as defined directly above. +SWEP.Primary.Ammo = "rpg_round" -- What kind of ammo. Options, besides custom, include pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, and AirboatGun. +SWEP.Primary.AmmoConsumption = 0 -- Ammo consumed per shot +-- Pistol, buckshot, and slam like to ricochet. Use AirboatGun for a light metal peircing shotgun pellets +SWEP.DisableChambering = true -- Disable round-in-the-chamber +-- Recoil Related +SWEP.Primary.KickUp = 0.75 -- This is the maximum upwards recoil (rise) +SWEP.Primary.KickDown = 0.6 -- This is the maximum downwards recoil (skeet) +SWEP.Primary.KickHorizontal = 0.45 -- This is the maximum sideways recoil (no real term) +SWEP.Primary.StaticRecoilFactor = 0.45 -- Amount of recoil to directly apply to EyeAngles. Enter what fraction or percentage (in decimal form) you want. This is also affected by a convar that defaults to 0.5. +-- Firing Cone Related +SWEP.Primary.Spread = .02 -- This is hip-fire acuracy. Less is more (1 is horribly awful, .0001 is close to perfect) +SWEP.Primary.IronAccuracy = .04 -- Ironsight accuracy, should be the same for shotguns +-- Unless you can do this manually, autodetect it. If you decide to manually do these, uncomment this block and remove this line. +SWEP.Primary.SpreadMultiplierMax = 5 -- How far the spread can expand when you shoot. Example val: 2.5 +SWEP.Primary.SpreadIncrement = 1.5 -- What percentage of the modifier is added on, per shot. Example val: 1/3.5 +SWEP.Primary.SpreadRecovery = 8 -- How much the spread recovers, per second. Example val: 3 +-- Range Related +SWEP.Primary.Range = 8856 -- The distance the bullet can travel in source units. Set to -1 to autodetect based on damage/rpm. +SWEP.Primary.RangeFalloff = 1 -- The percentage of the range the bullet damage starts to fall off at. Set to 0.8, for example, to start falling off after 80% of the range. +-- Penetration Related +SWEP.MaxPenetrationCounter = 2 -- The maximum number of ricochets. To prevent stack overflows. +-- Misc +SWEP.IronRecoilMultiplier = 1 -- Multiply recoil by this factor when we're in ironsights. This is proportional, not inversely. +SWEP.CrouchAccuracyMultiplier = 1 -- Less is more. Accuracy * 0.5 = Twice as accurate, Accuracy * 0.1 = Ten times as accurate +-- Movespeed +SWEP.MoveSpeed = 0.9 -- Multiply the player's movespeed by this. +SWEP.IronSightsMoveSpeed = SWEP.MoveSpeed * 0.8 -- Multiply the player's movespeed by this when sighting. +--[[PROJECTILES]] -- +--[[VIEWMODEL]] -- +SWEP.UseHands = true -- Use gmod c_arms system. +SWEP.ViewModel = "models/sw/weapons/9k38/9k38_v.mdl" -- Viewmodel path +SWEP.ViewModelFOV = 60 -- This controls how big the viewmodel looks. Less is more. +SWEP.ViewModelFlip = false -- Set this to true for CSS models, or false for everything else (with a righthanded viewmodel. +SWEP.VMPos = Vector(0, 0, 0) +SWEP.VMAng = Vector(0, 0, 0) +SWEP.VMPos_Additive = false -- Set to false for an easier time using VMPos. If true, VMPos will act as a constant delta ON TOP OF ironsights, run, whateverelse +SWEP.CenteredPos = nil -- The viewmodel positional offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.CenteredAng = nil -- The viewmodel angular offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.Bodygroups_V = nil -- { +-- [0] = 1, +-- [1] = 4, +-- [2] = etc. +-- } +--[[WORLDMODEL]] -- +SWEP.WorldModel = "models/sw/weapons/9k38/9k38_w.mdl" -- Weapon world model path +SWEP.Bodygroups_W = nil -- { +-- [0] = 1, +-- [1] = 4, +-- [2] = etc. +-- } +SWEP.HoldType = "rpg" -- This is how others view you carrying the weapon. Options include: +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- You're mostly going to use ar2, smg, shotgun or pistol. rpg and crossbow make for good sniper rifles +SWEP.Offset = { + Pos = { + Up = -1.5, + Right = 1, + Forward = 0 + }, + Ang = { + Up = -1, + Right = -2, + Forward = 178 + }, + Scale = 1 +} -- Procedural world model animation, defaulted for CS:S purposes. +SWEP.ThirdPersonReloadDisable = false -- Disable third person reload? True disables. +--[[SCOPES]] -- +SWEP.IronSightsSensitivity = 1 -- Useful for a RT scope. Change this to 0.25 for 25% sensitivity. This is if normal FOV compenstaion isn't your thing for whatever reason, so don't change it for normal scopes. +SWEP.BoltAction = false -- Unscope/sight after you shoot? +SWEP.Scoped = false -- Draw a scope overlay? +SWEP.ScopeOverlayThreshold = 0.875 -- Percentage you have to be sighted in to see the scope. +SWEP.BoltTimerOffset = 0.25 -- How long you stay sighted in after shooting, with a bolt action. +SWEP.ScopeScale = 0.5 -- Scale of the scope overlay +SWEP.ReticleScale = 0.7 -- Scale of the reticle overlay +-- GDCW Overlay Options. Only choose one. +SWEP.Secondary.UseACOG = false -- Overlay option +SWEP.Secondary.UseMilDot = false -- Overlay option +SWEP.Secondary.UseSVD = false -- Overlay option +SWEP.Secondary.UseParabolic = false -- Overlay option +SWEP.Secondary.UseElcan = false -- Overlay option +SWEP.Secondary.UseGreenDuplex = false -- Overlay option +if surface then + SWEP.Secondary.ScopeTable = nil --[[ + { + scopetex = surface.GetTextureID("scope/gdcw_closedsight"), + reticletex = surface.GetTextureID("scope/gdcw_acogchevron"), + dottex = surface.GetTextureID("scope/gdcw_acogcross") + } + ]] -- +end +--[[SHOTGUN CODE]] -- +SWEP.Shotgun = false -- Enable shotgun style reloading. +SWEP.ShellTime = .35 -- For shotguns, how long it takes to insert a shell. +--[[SPRINTING]] -- +SWEP.RunSightsPos = Vector(0, 0, -0.633) +SWEP.RunSightsAng = Vector(-8.99, 0, -5.029) +--[[IRONSIGHTS]] -- +SWEP.data = {} +SWEP.data.ironsights = 1 -- Enable Ironsights +SWEP.Secondary.IronFOV = 80 -- How much you 'zoom' in. Less is more! Don't have this be <= 0. A good value for ironsights is like 70. +SWEP.IronSightTime = 0.5 +SWEP.IronSightsPos = Vector(-3.593, -0.612, 0.955) +SWEP.IronSightsAng = Vector(-1.278, -0.885, 0.589) +--[[INSPECTION]] -- +SWEP.InspectPos = Vector(2.572, -3.086, -1.93) +SWEP.InspectAng = Vector(25.677, 22.743, 0) +--[[VIEWMODEL ANIMATION HANDLING]] -- +SWEP.AllowViewAttachment = true -- Allow the view to sway based on weapon attachment while reloading or drawing, IF THE CLIENT HAS IT ENABLED IN THEIR CONVARS. +--[[VIEWMODEL BLOWBACK]] -- +SWEP.BlowbackEnabled = false -- Enable Blowback? +SWEP.BlowbackVector = Vector(0, -3, 0) -- Vector to move bone relative to bone orientation. +SWEP.BlowbackCurrentRoot = 0 -- Amount of blowback currently, for root +SWEP.BlowbackCurrent = 0 -- Amount of blowback currently, for bones +SWEP.BlowbackBoneMods = nil -- Viewmodel bone mods via SWEP Creation Kit +SWEP.Blowback_Only_Iron = true -- Only do blowback on ironsights +SWEP.Blowback_PistolMode = false -- Do we recover from blowback when empty? +SWEP.Blowback_Shell_Enabled = true -- Shoot shells through blowback animations +SWEP.Blowback_Shell_Effect = "ShellEject" -- Which shell effect to use +--[[VIEWMODEL PROCEDURAL ANIMATION]] -- +SWEP.DoProceduralReload = false -- Animate first person reload using lua? +SWEP.ProceduralReloadTime = 1 -- Procedural reload time? +--[[HOLDTYPES]] -- +SWEP.IronSightHoldTypeOverride = "" -- This variable overrides the ironsights holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +SWEP.SprintHoldTypeOverride = "" -- This variable overrides the sprint holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +--[[ANIMATION]] -- + +SWEP.StatusLengthOverride = { + [ACT_VM_RELOAD] = 2 +} -- Changes the status delay of a given animation; only used on reloads. Otherwise, use SequenceLengthOverride or one of the others +SWEP.SequenceLengthOverride = {1} -- Changes both the status delay and the nextprimaryfire of a given animation +SWEP.SequenceRateOverride = {1} -- Like above but changes animation length to a target +SWEP.SequenceRateOverrideScaled = { + [ACT_VM_PRIMARYATTACK] = 1.5, + [ACT_VM_PRIMARYATTACK_1] = 1.5 +} -- Like above but scales animation length rather than being absolute + +SWEP.ProceduralHoslterEnabled = nil +SWEP.ProceduralHolsterTime = 0.3 +SWEP.ProceduralHolsterPos = Vector(3, 0, -5) +SWEP.ProceduralHolsterAng = Vector(-40, -30, 10) + +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_HYBRID -- ANI = mdl, HYBRID = lua but continue idle, Lua = stop mdl animation +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_ANI -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH -- TFA.Enum.IDLE_DISABLED = no idle, TFA.Enum.IDLE_LUA = lua idle, TFA.Enum.IDLE_ANI = mdl idle, TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA +SWEP.Idle_Blend = 0.25 -- Start an idle this far early into the end of a transition +SWEP.Idle_Smooth = 0.05 -- Start an idle this far early into the end of another animation +-- MDL Animations Below +SWEP.IronAnimation = { + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "base_idle", + ["value_empty"] = "empty_idle" + }, + ["shoot"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, -- Sequence or act + ["value"] = ACT_VM_PRIMARYATTACK -- Number for act, String/Number for sequence + } -- What do you think +} + +SWEP.SprintAnimation = { + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value_empty"] = "sprint_empty", + ["value"] = "sprint", + ["is_idle"] = true + } +} +--[[EFFECTS]] -- +-- Attachments +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellAttachment = "2" -- Should be "2" for CSS models or "shell" for hl2 models +SWEP.MuzzleFlashEnabled = false -- Enable muzzle flash +SWEP.MuzzleAttachmentRaw = nil -- This will override whatever string you gave. This is the raw attachment number. This is overridden or created when a gun makes a muzzle event. +SWEP.AutoDetectMuzzleAttachment = false -- For multi-barrel weapons, detect the proper attachment? +SWEP.MuzzleFlashEffect = nil -- Change to a string of your muzzle flash effect. Copy/paste one of the existing from the base. +SWEP.SmokeParticle = nil -- Smoke particle (ID within the PCF), defaults to something else based on holdtype; "" to disable +-- Shell eject override +SWEP.LuaShellEject = false -- Enable shell ejection through lua? +SWEP.LuaShellEjectDelay = 0 -- The delay to actually eject things +SWEP.LuaShellEffect = "ShellEject" -- The effect used for shell ejection; Defaults to that used for blowback +-- Tracer Stuff +SWEP.TracerName = nil -- Change to a string of your tracer name. Can be custom. There is a nice example at https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/base/entities/effects/tooltracer.lua +SWEP.TracerCount = 0 -- 0 disables, otherwise, 1 in X chance +-- Impact Effects +SWEP.ImpactEffect = nil -- Impact Effect +SWEP.ImpactDecal = nil -- Impact Decal +--[[EVENT TABLE]] -- +SWEP.EventTable = {} -- Event Table, used for custom events when an action is played. This can even do stuff like playing a pump animation after shooting. +-- example: +-- SWEP.EventTable = { +-- [ACT_VM_RELOAD] = { +-- { ["time"] = 0.1, ["type"] = "lua", ["value"] = function( wep, viewmodel ) end, ["client"] = true, ["server"] = true}, +-- { ["time"] = 0.1, ["type"] = "sound", ["value"] = Sound("x") } +-- } +-- } +--[[RENDER TARGET]] -- +SWEP.RTMaterialOverride = nil -- Take the material you want out of print(LocalPlayer():GetViewModel():GetMaterials()), subtract 1 from its index, and set it to this. +SWEP.RTOpaque = false -- Do you want your render target to be opaque? +SWEP.RTCode = nil -- function(self) return end --This is the function to draw onto your rendertarget +--[[AKIMBO]] -- +SWEP.Akimbo = false -- Akimbo gun? Alternates between primary and secondary attacks. +SWEP.AnimCycle = 0 -- Start on the right +--[[ATTACHMENTS]] -- +SWEP.VElements = nil -- Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.WElements = nil -- Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.Attachments = nil +SWEP.AttachmentDependencies = {} -- {["si_acog"] = {"bg_rail"}} +SWEP.AttachmentExclusions = {} -- Event Table, used for custom events when an action is played. This can even do stuff like playing a pump animation after shooting. +-- example: +-- SWEP.EventTable = { +-- [ACT_VM_RELOAD] = { +-- { ["time"] = 0.1, ["type"] = "lua", ["value"] = function( wep, viewmodel ) end, ["client"] = true, ["server"] = true}, +-- { ["time"] = 0.1, ["type"] = "sound", ["value"] = Sound("x") } +-- } +-- } +--[[RENDER TARGET]] -- +SWEP.RTMaterialOverride = nil -- Take the material you want out of print(LocalPlayer():GetViewModel():GetMaterials()), subtract 1 from its index, and set it to this. +SWEP.RTOpaque = false -- Do you want your render target to be opaque? +SWEP.RTCode = nil -- function(self) return end --This is the function to draw onto your rendertarget +--[[AKIMBO]] -- +SWEP.Akimbo = false -- Akimbo gun? Alternates between primary and secondary attacks. +SWEP.AnimCycle = 0 -- Start on the right +--[[ATTACHMENTS]] -- +SWEP.VElements = nil -- Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.WElements = nil -- Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.Attachments = nil +SWEP.AttachmentDependencies = {} -- {["si_acog"] = {"bg_rail"}} +SWEP.AttachmentExclusions = {} -- { ["si_iron"] = {"bg_heatshield"} } +SWEP.ClosestDist = 0 +SWEP.ClosestEnt = nil +SWEP.IsLocked = false + +--[[MISC INFO FOR MODELERS]] -- +--[[ + +Used Animations (for modelers): + +ACT_VM_DRAW - Draw +ACT_VM_DRAW_EMPTY - Draw empty +ACT_VM_DRAW_SILENCED - Draw silenced, overrides empty + +ACT_VM_IDLE - Idle +ACT_VM_IDLE_SILENCED - Idle empty, overwritten by silenced +ACT_VM_IDLE_SILENCED - Idle silenced + +ACT_VM_PRIMARYATTACK - Shoot +ACT_VM_PRIMARYATTACK_EMPTY - Shoot last chambered bullet +ACT_VM_PRIMARYATTACK_SILENCED - Shoot silenced, overrides empty +ACT_VM_PRIMARYATTACK_1 - Shoot ironsights, overriden by everything besides normal shooting +ACT_VM_DRYFIRE - Dryfire + +ACT_VM_RELOAD - Reload / Tactical Reload / Insert Shotgun Shell +ACT_SHOTGUN_RELOAD_START - Start shotgun reload, unless ACT_VM_RELOAD_EMPTY is there. +ACT_SHOTGUN_RELOAD_FINISH - End shotgun reload. +ACT_VM_RELOAD_EMPTY - Empty mag reload, chambers the new round. Works for shotguns too, where applicable. +ACT_VM_RELOAD_SILENCED - Silenced reload, overwrites all + + +ACT_VM_HOLSTER - Holster +ACT_VM_HOLSTER_SILENCED - Holster empty, overwritten by silenced +ACT_VM_HOLSTER_SILENCED - Holster silenced + +]] -- +DEFINE_BASECLASS(SWEP.Base) + +SWEP.Attachments = {} + +SWEP.VElements = {} + +SWEP.LockedEnt = nil +SWEP.BeepDelay = CurTime() +SWEP.CheckPosDelay = CurTime() +SWEP.HitPos = nil +SWEP.LOCKD = 0 + +function SWEP:ResetVar() + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end +end + +function SWEP:PrimaryAttack() + if SERVER then + if IsValid(self.ClosestEnt) then + if self.IsLocked then + local TargetPos = self.ClosestEnt:GetPos() + if SERVER then + self:TakePrimaryAmmo(1) + self:FireMissal() + end + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + self.Owner:EmitSound("sw/weapons/9k38/shoot.wav") + self.ClosestEnt = nil + timer.Simple(1, function() + self:Reload() + end) + end + end + end +end + +function SWEP:ExplosivesAmmoCheck() +end + +function SWEP:FireMissal() + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + local SelfPos = self.Owner:GetShootPos() + self:GetUp() * 5 + self:GetForward() * 10 + self:GetRight() * 15 + local TargPos = self.ClosestEnt + local Dist = (self:GetPos() - self.ClosestEnt:GetPos()):Length() + local Vec = (SelfPos) + local Dir = Vec:GetNormalized() + Dir = (Dir + Vector(0, 0, .5)):GetNormalized() + local Spred = self.ShotSpread + -- fire round + + local ent = ents.Create("sw_base_rocket_v3") + if not IsValid(ent) then + print("[9K38-ERR] Failed to create rocket entity!") + return + end + + local Ang = Dir:Angle() + local eye = self.Owner:EyeAngles() + local PosAng = self:GetAttachment(1) + local phys = ent:GetPhysicsObject() + local angl = self:EyeAngles() + ent:SetPos(SelfPos) + ent:SetAngles(Angle(angl.x, angl.y, angl.z)) + ent:SetOwner(self.Owner) + ent:Spawn() + ent:Activate() + + print("[9K38] Rocket created and spawned at " .. tostring(SelfPos)) + ent:SetRadioFuze(true) + ent:SetRadioFuzeRadius(425) + ent:SetModel("models/sw/rus/missiles/aam/9m39.mdl") + ent.Attacker = self.Owner + ent.RadioFuse = true + ent.Owner = self.Owner + ent.Launcher = self.Owner + ent.RocketTrail = "Med_mis_thrust" + ent.RocketBurnoutTrail = "Med_mis_burnout" + ent.MaxVelocity = 570 + ent.Agility = 250 + ent.FuelBurnoutTime = 10 + ent.ExplosionDamage = 3000 + ent.ExplosionRadius = 300 + ent.BlastRadius = 500 + ent.FragDamage = 350 + ent.FragRadius = 450 + ent.FragCount = 500 + ent.IRunstable = true + ent.SeekerCone = 25 + ent.target = self.ClosestEnt + ent.targetOffset = ent.target:WorldToLocal(ent.target:GetPos()) + + ent:GetPhysicsObject():AddVelocity(ent:GetForward() * 750) + + timer.Simple(0.5, function() + ent:Launch() + ent.Armed = true + ent.Launcher = self + ent.GuidanceActive = true + + ent.calcTarget = function(r) + if IsValid(r.target) then + return r:GetPos() + r:GetForward() + else + return r.target:LocalToWorld(r.targetOffset) + end + end + end) + + -- Miss:GetPhysicsObject():SetVelocity(self:GetPhysicsObject():GetVelocity()+Dir*400) + self.FiredAtCurrentTarget = true + self.RoundInChamber = false + self.NextNoMovementCheckTime = CurTime() + 5 + self:SetDTBool(2, self.RoundInChamber) + self.RoundsOnBelt = 0 + local Scayul = 2 + local effectd = EffectData() + effectd:SetScale(1) + util.Effect("eff_jack_turretmuzzlelight", effectd, true, true) + local effectd = EffectData() + effectd:SetScale(1) + util.Effect("eff_jack_turretmuzzlelight", effectd, true, true) +end + +function SWEP:OnRemove() + if self.Dispose == false then + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end +end + +DEFINE_BASECLASS("tfa_gun_base") + +function SWEP:Think() + if BaseClass and BaseClass.Think then + BaseClass.Think(self) + end + + if self:GetIronSightsRaw() == true then + self:IglaTargeting() + else + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end + + if self.Owner:KeyDown(IN_SPEED) then + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end + + if not self.Owner:Alive() then + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end +end + +function SWEP:IglaTargeting() + if self.Weapon:Clip1() <= 0 or self.Owner:KeyDown(IN_SPEED) then + return + end + self.guided_nextThink = self.guided_nextThink or 0 + self.FindTime = self.FindTime or 0 + self.nextFind = self.nextFind or 0 + local curtime = CurTime() + if self.FindTime + 3 < curtime and IsValid(self.ClosestEnt) then + self.Locked = true + else + self.Locked = false + end + if self.Locked ~= self.IsLocked then + self.IsLocked = self.Locked + if self.Locked then + self.LockSND = CreateSound(self, "sw/weapons/9k38/locked.wav") + self.LockSND:PlayEx(1, 100) + + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + else + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end + end + if self.nextFind < curtime then + self.nextFind = curtime + 3 + self.FoundVehicles = {} + + for k, v in pairs(ents.GetAll()) do + if ((v.LFS and v:GetEngineActive()) or + (v.LVS and v:GetEngineActive() and (isnumber(v.ThrustUp) or isnumber(v.ThrottleRateUp))) or + (v.isWacAircraft and v.active)) then + table.insert(self.FoundVehicles, v) + end + end + end + if self.guided_nextThink < curtime then + self.guided_nextThink = curtime + 0.25 + self.FoundVehicles = self.FoundVehicles or {} + local AimForward = self.Owner:GetAimVector() + local startpos = self.Owner:GetShootPos() + + local Vehicles = {} + local ClosestEnt = NULL + local ClosestDist = 0 + + for k, v in pairs(self.FoundVehicles) do + if IsValid(v) then + local sub = (v:GetPos() - startpos) + local toEnt = sub:GetNormalized() + local dist = sub:Length() + local Ang = math.acos(math.Clamp(AimForward:Dot(toEnt), -1, 1)) * (180 / math.pi) + + if Ang < 8 and dist < 15000 and self:CanSee(v) then + table.insert(Vehicles, v) + + local stuff = WorldToLocal(v:GetPos(), Angle(0, 0, 0), startpos, + self.Owner:EyeAngles() + Angle(90, 0, 0)) + stuff.z = 0 + local dist = stuff:Length() + + if not IsValid(ClosestEnt) then + ClosestEnt = v + ClosestDist = dist + end + + if dist < ClosestDist then + ClosestDist = dist + if ClosestEnt ~= v then + ClosestEnt = v + end + end + end + else + self.FoundVehicles[k] = nil + end + end + + if self.ClosestEnt ~= ClosestEnt then + self.ClosestEnt = ClosestEnt + self.ClosestDist = ClosestDist + self.FindTime = curtime + if IsValid(ClosestEnt) then + self.TrackSND = CreateSound(self, "sw/weapons/9k38/seeking.wav") + self.TrackSND:PlayEx(1, 100) + self.TrackSND:ChangeVolume(1, 1) + else + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + end + end + if IsValid(ClosestEnt) then + if self.IsLocked then + self.LockAlarmSND = CreateSound(ClosestEnt, "sw/misc/lock.wav") + self.LockAlarmSND:PlayEx(1, 90) + if self.AlarmSND then + self.AlarmSND:Stop() + self.AlarmSND = nil + end + else + self.AlarmSND = CreateSound(ClosestEnt, "sw/misc/lock.wav") + self.AlarmSND:PlayEx(1, 40) + if self.LockAlarmSND then + self.LockAlarmSND:Stop() + self.LockAlarmSND = nil + end + end + else + if self.LockAlarmSND then + self.LockAlarmSND:Stop() + self.LockAlarmSND = nil + end + if self.AlarmSND then + self.AlarmSND:Stop() + self.AlarmSND = nil + end + end + if not IsValid(ClosestEnt) then + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + end + end +end +function SWEP:CanSee(entity) + local pos = entity:GetPos() + local tr = util.TraceLine({ + start = self.Owner:GetShootPos(), + endpos = pos, + filter = function(ent) + if ent == self then + return false + end + + return true + end + + }) + return (tr.HitPos - pos):Length() < 20000 +end +function SWEP:resetstat() + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end +end + +function SWEP:FinishHolster() + local self2 = self:GetTable() + self:resetstat() + self:CleanParticles() + + local v2 = hook.Run("TFA_Holster", self) + + if self:GetOwner():IsNPC() then + return + end + if v2 ~= nil then + return v2 + end + + if SERVER then + local ent = self:GetSwapTarget() + self:Holster(ent) + + if IsValid(ent) and ent:IsWeapon() then + self:GetOwner():SelectWeapon(ent:GetClass()) + + if ent.IsTFAWeapon then + ent:ApplyViewModelModifications() + ent:CallOnClient("ApplyViewModelModifications") + end + + self2.OwnerViewModel = nil + end + end +end diff --git a/garrysmod/addons/anti_air/lua/weapons/weapon_sw_fim92.lua b/garrysmod/addons/anti_air/lua/weapons/weapon_sw_fim92.lua new file mode 100644 index 0000000..d3ce645 --- /dev/null +++ b/garrysmod/addons/anti_air/lua/weapons/weapon_sw_fim92.lua @@ -0,0 +1,730 @@ +SWEP.Base = "tfa_gun_base" +SWEP.Category = "FT Explosive" --The category. Please, just choose something generic or something I've already done if you plan on only doing like one swep.. +SWEP.Manufacturer = "SW Weapons Factory" --Gun Manufactrer (e.g. Hoeckler and Koch ) +SWEP.Author = "Shermann Wolf" --Author Tooltip +SWEP.Contact = "shermannwolf@gmail.com" --Contact Info Tooltip +SWEP.Purpose = "Destruction of low-flying air targets" --Purpose Tooltip +SWEP.Instructions = "" --Instructions Tooltip +SWEP.Spawnable = true --Can you, as a normal user, spawn this? +SWEP.AdminSpawnable = true --Can an adminstrator spawn this? Does not tie into your admin mod necessarily, unless its coded to allow for GMod's default ranks somewhere in its code. Evolve and ULX should work, but try to use weapon restriction rather than these. +SWEP.DrawCrosshair = false -- Draw the crosshair? +SWEP.DrawCrosshairIS = false --Draw the crosshair in ironsights? +SWEP.PrintName = "FIM-92" -- Weapon name (Shown on HUD) +SWEP.Slot = 4 -- Slot in the weapon selection menu. Subtract 1, as this starts at 0. +SWEP.SlotPos = 73 -- Position in the slot +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.Weight = 30 -- This controls how "good" the weapon is for autopickup. +SWEP.Type = "Launcher" + +--[[WEAPON HANDLING]]-- +SWEP.Primary.Sound = nil -- This is the sound of the weapon, when you shoot. +SWEP.Primary.PenetrationMultiplier = 1 --Change the amount of something this gun can penetrate through +SWEP.Primary.Damage = 500-- Damage, in standard damage points. +SWEP.Primary.DamageTypeHandled = true --true will handle damagetype in base +SWEP.Primary.DamageType = nil --See DMG enum. This might be DMG_SHOCK, DMG_BURN, DMG_BULLET, etc. Leave nil to autodetect. DMG_AIRBOAT opens doors. +SWEP.Primary.Force = nil --Force value, leave nil to autocalc +SWEP.Primary.Knockback = 1 --Autodetected if nil; this is the velocity kickback +SWEP.Primary.HullSize = 0 --Big bullets, increase this value. They increase the hull size of the hitscan bullet. +SWEP.Primary.NumShots = 1 --The number of shots the weapon fires. SWEP.Shotgun is NOT required for this to be >1. +SWEP.Primary.Automatic = false -- Automatic/Semi Auto +SWEP.Primary.RPM = 120 -- This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Displayed = 15 -- This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Semi = nil -- RPM for semi-automatic or burst fire. This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Burst = nil -- RPM for burst fire, overrides semi. This is in Rounds Per Minute / RPM +SWEP.Primary.BurstDelay = nil -- Delay between bursts, leave nil to autocalculate +SWEP.FiresUnderwater = false +SWEP.AllowViewAttachment = true +SWEP.Dispose = false +SWEP.DisposeTimer = 2 +SWEP.StripTimer = math.huge +--Miscelaneous Sounds +SWEP.IronInSound = "" --Sound to play when ironsighting in? nil for default +SWEP.IronOutSound = "" --Sound to play when ironsighting out? nil for default +--Silencing +SWEP.CanBeSilenced = false --Can we silence? Requires animations. +SWEP.Silenced = false --Silenced by default? +-- Selective Fire Stuff +SWEP.SelectiveFire = false --Allow selecting your firemode? +SWEP.DisableBurstFire = false --Only auto/single? +SWEP.OnlyBurstFire = false --No auto, only burst/single? +SWEP.DefaultFireMode = "" --Default to auto or whatev +SWEP.FireModeName = "Single Use Anti-Aircraft Missile" --Change to a text value to override it +--Ammo Related +SWEP.Primary.ClipSize = 1-- This is the size of a clip +SWEP.Primary.DefaultClip = 0 -- This is the number of bullets the gun gives you, counting a clip as defined directly above. +SWEP.Primary.Ammo = "rpg_round" -- What kind of ammo. Options, besides custom, include pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, and AirboatGun. +SWEP.Primary.AmmoConsumption = 0 --Ammo consumed per shot +--Pistol, buckshot, and slam like to ricochet. Use AirboatGun for a light metal peircing shotgun pellets +SWEP.DisableChambering = true --Disable round-in-the-chamber +--Recoil Related +SWEP.Primary.KickUp = 0.75 -- This is the maximum upwards recoil (rise) +SWEP.Primary.KickDown = 0.6 -- This is the maximum downwards recoil (skeet) +SWEP.Primary.KickHorizontal = 0.45 -- This is the maximum sideways recoil (no real term) +SWEP.Primary.StaticRecoilFactor = 0.45 --Amount of recoil to directly apply to EyeAngles. Enter what fraction or percentage (in decimal form) you want. This is also affected by a convar that defaults to 0.5. +--Firing Cone Related +SWEP.Primary.Spread = .02 --This is hip-fire acuracy. Less is more (1 is horribly awful, .0001 is close to perfect) +SWEP.Primary.IronAccuracy = .04 -- Ironsight accuracy, should be the same for shotguns +--Unless you can do this manually, autodetect it. If you decide to manually do these, uncomment this block and remove this line. +SWEP.Primary.SpreadMultiplierMax = 5--How far the spread can expand when you shoot. Example val: 2.5 +SWEP.Primary.SpreadIncrement = 1.5 --What percentage of the modifier is added on, per shot. Example val: 1/3.5 +SWEP.Primary.SpreadRecovery = 8--How much the spread recovers, per second. Example val: 3 +--Range Related +SWEP.Primary.Range = 8856 -- The distance the bullet can travel in source units. Set to -1 to autodetect based on damage/rpm. +SWEP.Primary.RangeFalloff = 1 -- The percentage of the range the bullet damage starts to fall off at. Set to 0.8, for example, to start falling off after 80% of the range. +--Penetration Related +SWEP.MaxPenetrationCounter = 2 --The maximum number of ricochets. To prevent stack overflows. +--Misc +SWEP.IronRecoilMultiplier = 1 --Multiply recoil by this factor when we're in ironsights. This is proportional, not inversely. +SWEP.CrouchAccuracyMultiplier = 1 --Less is more. Accuracy * 0.5 = Twice as accurate, Accuracy * 0.1 = Ten times as accurate +--Movespeed +SWEP.MoveSpeed = 0.9 --Multiply the player's movespeed by this. +SWEP.IronSightsMoveSpeed = SWEP.MoveSpeed * 0.8 --Multiply the player's movespeed by this when sighting. +--[[PROJECTILES]]-- +--[[VIEWMODEL]]-- +SWEP.UseHands = true --Use gmod c_arms system. +SWEP.ViewModel = "models/sw/weapons/fim92/fim92_v.mdl" --Viewmodel path +SWEP.ViewModelFOV = 60 -- This controls how big the viewmodel looks. Less is more. +SWEP.ViewModelFlip = false -- Set this to true for CSS models, or false for everything else (with a righthanded viewmodel. +SWEP.VMPos = Vector(0,0,0) +SWEP.VMAng = Vector(0, 0, 0) +SWEP.VMPos_Additive = false --Set to false for an easier time using VMPos. If true, VMPos will act as a constant delta ON TOP OF ironsights, run, whateverelse +SWEP.CenteredPos = nil --The viewmodel positional offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.CenteredAng = nil --The viewmodel angular offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.Bodygroups_V = nil --{ + --[0] = 1, + --[1] = 4, + --[2] = etc. +--} +--[[WORLDMODEL]]-- +SWEP.WorldModel = "models/sw/weapons/fim92/fim92_w.mdl" -- Weapon world model path +SWEP.Bodygroups_W = nil --{ +--[0] = 1, +--[1] = 4, +--[2] = etc. +--} +SWEP.HoldType = "rpg" -- This is how others view you carrying the weapon. Options include: +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- You're mostly going to use ar2, smg, shotgun or pistol. rpg and crossbow make for good sniper rifles +SWEP.Offset = { + Pos = { + Up = -1.5, + Right = 1, + Forward = 0 + }, + Ang = { + Up = -1, + Right = -2, + Forward = 178 + }, + Scale = 1 +} --Procedural world model animation, defaulted for CS:S purposes. +SWEP.ThirdPersonReloadDisable = false --Disable third person reload? True disables. +--[[SCOPES]]-- +SWEP.IronSightsSensitivity = 1 --Useful for a RT scope. Change this to 0.25 for 25% sensitivity. This is if normal FOV compenstaion isn't your thing for whatever reason, so don't change it for normal scopes. +SWEP.BoltAction = false --Unscope/sight after you shoot? +SWEP.Scoped = false --Draw a scope overlay? +SWEP.ScopeOverlayThreshold = 0.875 --Percentage you have to be sighted in to see the scope. +SWEP.BoltTimerOffset = 0.25 --How long you stay sighted in after shooting, with a bolt action. +SWEP.ScopeScale = 0.5 --Scale of the scope overlay +SWEP.ReticleScale = 0.7 --Scale of the reticle overlay +--GDCW Overlay Options. Only choose one. +SWEP.Secondary.UseACOG = false --Overlay option +SWEP.Secondary.UseMilDot = false --Overlay option +SWEP.Secondary.UseSVD = false --Overlay option +SWEP.Secondary.UseParabolic = false --Overlay option +SWEP.Secondary.UseElcan = false --Overlay option +SWEP.Secondary.UseGreenDuplex = false --Overlay option +if surface then + SWEP.Secondary.ScopeTable = nil --[[ + { + scopetex = surface.GetTextureID("scope/gdcw_closedsight"), + reticletex = surface.GetTextureID("scope/gdcw_acogchevron"), + dottex = surface.GetTextureID("scope/gdcw_acogcross") + } + ]]-- +end +--[[SHOTGUN CODE]]-- +SWEP.Shotgun = false --Enable shotgun style reloading. +SWEP.ShellTime = .35 -- For shotguns, how long it takes to insert a shell. +--[[SPRINTING]]-- +SWEP.RunSightsPos = Vector(5.181, -2.725, -2.306) +SWEP.RunSightsAng = Vector(-12.95, 29.523, -22.036) +--[[IRONSIGHTS]]-- +SWEP.data = {} +SWEP.data.ironsights = 1 --Enable Ironsights +SWEP.Secondary.IronFOV = 60 -- How much you 'zoom' in. Less is more! Don't have this be <= 0. A good value for ironsights is like 70. +SWEP.IronSightsPos = Vector(0.006, -4.581, 1.97) +SWEP.IronSightsAng = Vector(7.296, -21.063, 32.264) +SWEP.IronSightTime = 0.5 +--[[INSPECTION]]-- +SWEP.InspectPos = Vector(6.519, -3.289, 2.19) +SWEP.InspectAng = Vector(32.361, 25.198, 44.375) +--[[VIEWMODEL ANIMATION HANDLING]]-- +SWEP.AllowViewAttachment = true --Allow the view to sway based on weapon attachment while reloading or drawing, IF THE CLIENT HAS IT ENABLED IN THEIR CONVARS. +--[[VIEWMODEL BLOWBACK]]-- +SWEP.BlowbackEnabled = false --Enable Blowback? +SWEP.BlowbackVector = Vector(0,-3,0) --Vector to move bone relative to bone orientation. +SWEP.BlowbackCurrentRoot = 0 --Amount of blowback currently, for root +SWEP.BlowbackCurrent = 0 --Amount of blowback currently, for bones +SWEP.BlowbackBoneMods = nil --Viewmodel bone mods via SWEP Creation Kit +SWEP.Blowback_Only_Iron = true --Only do blowback on ironsights +SWEP.Blowback_PistolMode = false --Do we recover from blowback when empty? +SWEP.Blowback_Shell_Enabled = true --Shoot shells through blowback animations +SWEP.Blowback_Shell_Effect = "ShellEject"--Which shell effect to use +--[[VIEWMODEL PROCEDURAL ANIMATION]]-- +SWEP.DoProceduralReload = false--Animate first person reload using lua? +SWEP.ProceduralReloadTime = 1 --Procedural reload time? +--[[HOLDTYPES]]-- +SWEP.IronSightHoldTypeOverride = "" --This variable overrides the ironsights holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +SWEP.SprintHoldTypeOverride = "" --This variable overrides the sprint holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +--[[ANIMATION]]-- + +SWEP.StatusLengthOverride = { + [ACT_VM_RELOAD] = 2 +} --Changes the status delay of a given animation; only used on reloads. Otherwise, use SequenceLengthOverride or one of the others +SWEP.SequenceLengthOverride = {1 +} --Changes both the status delay and the nextprimaryfire of a given animation +SWEP.SequenceRateOverride = {1} --Like above but changes animation length to a target +SWEP.SequenceRateOverrideScaled = { + [ACT_VM_PRIMARYATTACK] = 1.5, + [ACT_VM_PRIMARYATTACK_1] = 1.5 +} --Like above but scales animation length rather than being absolute + +SWEP.ProceduralHoslterEnabled = nil +SWEP.ProceduralHolsterTime = 0.3 +SWEP.ProceduralHolsterPos = Vector(3, 0, -5) +SWEP.ProceduralHolsterAng = Vector(-40, -30, 10) + +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_HYBRID -- ANI = mdl, HYBRID = lua but continue idle, Lua = stop mdl animation +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_ANI -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH --TFA.Enum.IDLE_DISABLED = no idle, TFA.Enum.IDLE_LUA = lua idle, TFA.Enum.IDLE_ANI = mdl idle, TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA +SWEP.Idle_Blend = 0.25 --Start an idle this far early into the end of a transition +SWEP.Idle_Smooth = 0.05 --Start an idle this far early into the end of another animation +--MDL Animations Below +SWEP.IronAnimation = { + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "base_idle", + ["value_empty"] = "empty_idle" + }, + ["shoot"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_PRIMARYATTACK, --Number for act, String/Number for sequence + } --What do you think +} + +SWEP.SprintAnimation = { + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value_empty"] = "sprint_empty", + ["value"] = "sprint", + ["is_idle"] = true + } +} +--[[EFFECTS]]-- +--Attachments +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellAttachment = "2" -- Should be "2" for CSS models or "shell" for hl2 models +SWEP.MuzzleFlashEnabled = false --Enable muzzle flash +SWEP.MuzzleAttachmentRaw = nil --This will override whatever string you gave. This is the raw attachment number. This is overridden or created when a gun makes a muzzle event. +SWEP.AutoDetectMuzzleAttachment = false --For multi-barrel weapons, detect the proper attachment? +SWEP.MuzzleFlashEffect = nil --Change to a string of your muzzle flash effect. Copy/paste one of the existing from the base. +SWEP.SmokeParticle = nil --Smoke particle (ID within the PCF), defaults to something else based on holdtype; "" to disable +--Shell eject override +SWEP.LuaShellEject = false --Enable shell ejection through lua? +SWEP.LuaShellEjectDelay = 0 --The delay to actually eject things +SWEP.LuaShellEffect = "ShellEject" --The effect used for shell ejection; Defaults to that used for blowback +--Tracer Stuff +SWEP.TracerName = nil --Change to a string of your tracer name. Can be custom. There is a nice example at https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/base/entities/effects/tooltracer.lua +SWEP.TracerCount = 0 --0 disables, otherwise, 1 in X chance +--Impact Effects +SWEP.ImpactEffect = nil--Impact Effect +SWEP.ImpactDecal = nil--Impact Decal +--[[EVENT TABLE]]-- +SWEP.EventTable = {} --Event Table, used for custom events when an action is played. This can even do stuff like playing a pump animation after shooting. +--example: +--SWEP.EventTable = { +-- [ACT_VM_RELOAD] = { +-- { ["time"] = 0.1, ["type"] = "lua", ["value"] = function( wep, viewmodel ) end, ["client"] = true, ["server"] = true}, +-- { ["time"] = 0.1, ["type"] = "sound", ["value"] = Sound("x") } +-- } +--} +--[[RENDER TARGET]]-- +SWEP.RTMaterialOverride = nil -- Take the material you want out of print(LocalPlayer():GetViewModel():GetMaterials()), subtract 1 from its index, and set it to this. +SWEP.RTOpaque = false -- Do you want your render target to be opaque? +SWEP.RTCode = nil--function(self) return end --This is the function to draw onto your rendertarget +--[[AKIMBO]]-- +SWEP.Akimbo = false --Akimbo gun? Alternates between primary and secondary attacks. +SWEP.AnimCycle = 0 -- Start on the right +--[[ATTACHMENTS]]-- +SWEP.VElements = nil --Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.WElements = nil --Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.Attachments = nil +SWEP.AttachmentDependencies = {}--{["si_acog"] = {"bg_rail"}} +SWEP.AttachmentExclusions = {}--{ ["si_iron"] = {"bg_heatshield"} } +--[[MISC INFO FOR MODELERS]]-- +--[[ + +Used Animations (for modelers): + +ACT_VM_DRAW - Draw +ACT_VM_DRAW_EMPTY - Draw empty +ACT_VM_DRAW_SILENCED - Draw silenced, overrides empty + +ACT_VM_IDLE - Idle +ACT_VM_IDLE_SILENCED - Idle empty, overwritten by silenced +ACT_VM_IDLE_SILENCED - Idle silenced + +ACT_VM_PRIMARYATTACK - Shoot +ACT_VM_PRIMARYATTACK_EMPTY - Shoot last chambered bullet +ACT_VM_PRIMARYATTACK_SILENCED - Shoot silenced, overrides empty +ACT_VM_PRIMARYATTACK_1 - Shoot ironsights, overriden by everything besides normal shooting +ACT_VM_DRYFIRE - Dryfire + +ACT_VM_RELOAD - Reload / Tactical Reload / Insert Shotgun Shell +ACT_SHOTGUN_RELOAD_START - Start shotgun reload, unless ACT_VM_RELOAD_EMPTY is there. +ACT_SHOTGUN_RELOAD_FINISH - End shotgun reload. +ACT_VM_RELOAD_EMPTY - Empty mag reload, chambers the new round. Works for shotguns too, where applicable. +ACT_VM_RELOAD_SILENCED - Silenced reload, overwrites all + + +ACT_VM_HOLSTER - Holster +ACT_VM_HOLSTER_SILENCED - Holster empty, overwritten by silenced +ACT_VM_HOLSTER_SILENCED - Holster silenced + +]]-- +DEFINE_BASECLASS( SWEP.Base ) + + +SWEP.Attachments = { +} + +SWEP.VElements = { +} + +SWEP.MuzzleAttachmentSilenced = 3 +SWEP.LaserSightModAttachment = 1 + +SWEP.LockedEnt = nil +SWEP.BeepDelay = CurTime() +SWEP.CheckPosDelay = CurTime() +SWEP.HitPos = nil +SWEP.LOCKD = 0 +SWEP.ClosestDist = 0 +SWEP.ClosestEnt = nil +SWEP.IsLocked = false + +function SWEP:ResetVar() + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end +end + +function SWEP:PrimaryAttack() + if SERVER then + if self.IsLocked then + if IsValid(self.ClosestEnt) then + local TargetPos=self.ClosestEnt:GetPos() + if SERVER then + --sound.Play("Homing_Launcher/approaching_warning.wav",TargetPos) + self:TakePrimaryAmmo(1) + self:FireMissal() + end + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + self.Owner:EmitSound("sw/weapons/fim92/shoot.wav") + timer.Simple( 1, function() + self:Reload() + end ) + end + end + end +end + +function SWEP:ExplosivesAmmoCheck() +end + +function SWEP:FireMissal() + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + local SelfPos=self.Owner:GetShootPos()+self:GetUp()*5+self:GetForward()*10+self:GetRight()*15 + local TargPos=self.ClosestEnt + local Dist=(self:GetPos()-self.ClosestEnt:GetPos()):Length() + local Vec=(SelfPos) + local Dir=Vec:GetNormalized() + Dir=(Dir+Vector(0,0,.5)):GetNormalized() + local Spred=self.ShotSpread + --fire round + + local ent = ents.Create("sw_base_rocket_v3") + if not IsValid(ent) then + print("[FIM92-ERR] Failed to create rocket entity!") + return + end + + local Ang=Dir:Angle() + local eye=self.Owner:EyeAngles() + local PosAng=self:GetAttachment(1) + local phys = ent:GetPhysicsObject() + local angl = self:EyeAngles() + ent:SetPos( SelfPos ) + ent:SetAngles(Angle(angl.x,angl.y,angl.z)) + ent:SetOwner( self.Owner ) + ent:Spawn() + ent:Activate() + + print("[FIM92] Rocket created and spawned at " .. tostring(SelfPos)) + ent:SetRadioFuze(true) + ent:SetRadioFuzeRadius(300) + ent:SetModel("models/sw/usa/missiles/aam/aim92.mdl") + ent.Attacker = self.Owner + ent.RadioFuse = true + ent.Owner = self.Owner + ent.Launcher = self.Owner + ent.RocketTrail = "Med_mis_thrust" + ent.RocketBurnoutTrail = "Med_mis_burnout" + ent.MaxVelocity = 750 + ent.Agility = 300 + ent.FuelBurnoutTime = 10 + ent.ExplosionDamage = 2500 + ent.ExplosionRadius = 200 + ent.BlastRadius = 300 + ent.FragDamage = 350 + ent.FragRadius = 450 + ent.FragCount = 500 + ent.IRunstable = true + ent.SeekerCone = 25 + ent.target = self.ClosestEnt + ent.targetOffset = ent.target:WorldToLocal(ent.target:GetPos()) + + ent:GetPhysicsObject():AddVelocity(ent:GetForward()*750) + + timer.Simple(0.5,function() + ent:Launch() + ent.Armed = true + ent.Launcher = self + ent.GuidanceActive = true + ent.calcTarget = function(r) + if !IsValid(r.target) then + return r:GetPos() + r:GetForward() + else + return r.target:LocalToWorld(r.targetOffset) + end + end + end) + + + --Miss:GetPhysicsObject():SetVelocity(self:GetPhysicsObject():GetVelocity()+Dir*400) + self.FiredAtCurrentTarget=true + self.RoundInChamber=false + self.NextNoMovementCheckTime=CurTime()+5 + self:SetDTBool(2,self.RoundInChamber) + self.RoundsOnBelt=0 + local Scayul=2 + local effectd=EffectData() + effectd:SetScale(1) + util.Effect("eff_jack_turretmuzzlelight",effectd,true,true) + local effectd=EffectData() + effectd:SetScale(1) + util.Effect("eff_jack_turretmuzzlelight",effectd,true,true) +end + +function SWEP:OnRemove() + if self.Dispose == false then + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end +end + +function SWEP:Think() + if BaseClass and BaseClass.Think then + BaseClass.Think(self) + end + + if self:GetIronSightsRaw() == true then + self:StingerTargeting() + else + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end + + if self.Owner:KeyDown(IN_SPEED) then + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end + + if not self.Owner:Alive() then + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end +end + +function SWEP:StingerTargeting() + if self.Weapon:Clip1() <= 0 or self.Owner:KeyDown(IN_SPEED) then return end + self.guided_nextThink = self.guided_nextThink or 0 + self.FindTime = self.FindTime or 0 + self.nextFind = self.nextFind or 0 + local curtime = CurTime() + if self.FindTime + 3 < curtime and IsValid( self.ClosestEnt ) then + self.Locked = true + else + self.Locked = false + end + if self.Locked ~= self.IsLocked then + self.IsLocked = self.Locked + if self.Locked then + self.LockSND = CreateSound(self, "sw/weapons/fim92/locked.wav") + self.LockSND:PlayEx( 1, 100 ) + + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + else + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end + end + end + if self.nextFind < curtime then + self.nextFind = curtime + 3 + self.FoundVehicles = {} + + for k, v in pairs( ents.GetAll() ) do + if ((v.LFS and v:GetEngineActive()) or (v.LVS and v:GetEngineActive() and (isnumber(v.ThrustUp) or isnumber(v.ThrottleRateUp))) or (v.isWacAircraft and v.active)) then + table.insert( self.FoundVehicles, v ) + end + end + end + if self.guided_nextThink < curtime then + self.guided_nextThink = curtime + 0.5 + self.FoundVehicles = self.FoundVehicles or {} + local AimForward = self.Owner:GetAimVector() + local startpos = self.Owner:GetShootPos() + + local Vehicles = {} + local ClosestEnt = NULL + local ClosestDist = 0 + + for k, v in pairs( self.FoundVehicles ) do + if IsValid( v ) then + local sub = (v:GetPos() - startpos) + local toEnt = sub:GetNormalized() + local dist = sub:Length() + local Ang = math.acos( math.Clamp( AimForward:Dot( toEnt ) ,-1,1) ) * (180 / math.pi) + + if Ang < 5 and dist < 75000 and self:CanSee( v ) then + table.insert( Vehicles, v ) + + local stuff = WorldToLocal( v:GetPos(), Angle(0,0,0), startpos, self.Owner:EyeAngles() + Angle(90,0,0) ) + stuff.z = 0 + local dist = stuff:Length() + + if not IsValid( ClosestEnt ) then + ClosestEnt = v + ClosestDist = dist + if IsValid( ClosestEnt ) and not self.IsLocked then + self.TrackSND = CreateSound(self, "sw/weapons/fim92/check.wav") + self.TrackSND:PlayEx( 1, 100 ) + self.TrackSND:ChangeVolume( 1, 1 ) + else + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + end + end + + if dist < ClosestDist then + ClosestDist = dist + if ClosestEnt ~= v then + ClosestEnt = v + end + end + end + else + self.FoundVehicles[k] = nil + end + end + + if self.ClosestEnt ~= ClosestEnt then + self.ClosestEnt = ClosestEnt + self.ClosestDist = ClosestDist + self.FindTime = curtime + end + if IsValid( ClosestEnt ) then + if self.IsLocked then + self.LockAlarmSND = CreateSound(ClosestEnt, "sw/misc/lock.wav") + self.LockAlarmSND:PlayEx( 1, 90 ) + if self.AlarmSND then + self.AlarmSND:Stop() + self.AlarmSND = nil + end + else + self.AlarmSND = CreateSound(ClosestEnt, "sw/misc/lock.wav") + self.AlarmSND:PlayEx( 1, 40 ) + if self.LockAlarmSND then + self.LockAlarmSND:Stop() + self.LockAlarmSND = nil + end + end + else + if self.LockAlarmSND then + self.LockAlarmSND:Stop() + self.LockAlarmSND = nil + end + if self.AlarmSND then + self.AlarmSND:Stop() + self.AlarmSND = nil + end + end + if not IsValid( ClosestEnt ) then + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + end + end +end +function SWEP:CanSee(entity) + local pos = entity:GetPos() + local tr = util.TraceLine( { + start = self.Owner:GetShootPos(), + endpos = pos, + filter = function( ent ) + if ent == self then + return false + end + + return true + end + + } ) + return (tr.HitPos - pos):Length() < 20000 +end +function SWEP:resetstat() + self.ClosestEnt = nil + self.ClosestDist = 0 + self.IsLocked = false + if self.TrackSND then + self.TrackSND:Stop() + self.TrackSND = nil + end + if self.LockSND then + self.LockSND:Stop() + self.LockSND = nil + end +end + +function SWEP:ThrowTube( model_file ) + local angl = self:EyeAngles() + local SelfPos=self.Owner:GetShootPos()+self:GetUp()*-4+self:GetForward()*1+self:GetRight()*50 + local Vec=(SelfPos) + if ( CLIENT ) then return end + + local ent = ents.Create( "prop_physics" ) + if ( !IsValid( ent ) ) then return end + ent:SetModel( model_file ) + ent:SetPos( SelfPos-self:GetRight()*-50 ) + ent:SetAngles( Angle(0,angl.y-90,angl.x)) + ent:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + ent:Spawn() + + local phys = ent:GetPhysicsObject() + if ( !IsValid( phys ) ) then ent:Remove() return end + + local velocity = self.Owner:GetAimVector() + velocity = velocity * 5000 + velocity = velocity + ( VectorRand() * 10 ) + phys:ApplyForceCenter( self:GetRight()*velocity ) + + cleanup.Add( self.Owner, "props", ent ) + SafeRemoveEntityDelayed( ent, 30 ); + + undo.Create( "Empty Stinger Tube" ) + undo.AddEntity( ent ) + undo.SetPlayer( self.Owner ) + undo.Finish() + +end + +function SWEP:FinishHolster() + local self2 = self:GetTable() + self:resetstat() + self:CleanParticles() + + local v2 = hook.Run("TFA_Holster", self) + + if self:GetOwner():IsNPC() then return end + if v2 ~= nil then return v2 end + + if SERVER then + local ent = self:GetSwapTarget() + self:Holster(ent) + + if IsValid(ent) and ent:IsWeapon() then + self:GetOwner():SelectWeapon(ent:GetClass()) + + if ent.IsTFAWeapon then + ent:ApplyViewModelModifications() + ent:CallOnClient("ApplyViewModelModifications") + end + + self2.OwnerViewModel = nil + end + end +end diff --git a/garrysmod/addons/easy_bodygroup_tool/lua/weapons/gmod_tool/stools/rb655_easy_bodygroup.lua b/garrysmod/addons/easy_bodygroup_tool/lua/weapons/gmod_tool/stools/rb655_easy_bodygroup.lua new file mode 100644 index 0000000..6b3f6a9 --- /dev/null +++ b/garrysmod/addons/easy_bodygroup_tool/lua/weapons/gmod_tool/stools/rb655_easy_bodygroup.lua @@ -0,0 +1,184 @@ + +TOOL.Category = "Robotboy655" +TOOL.Name = "#tool.rb655_easy_bodygroup.name" + +local gLastSelecetedEntity = NULL + +local MaxBodyGroups = 72 + +TOOL.ClientConVar[ "noglow" ] = "0" +TOOL.ClientConVar[ "skin" ] = "0" +for i = 0, MaxBodyGroups do TOOL.ClientConVar[ "group" .. i ] = "1" end + +local function MakeNiceName( str ) + local newname = {} + + for _, s in pairs( string.Explode( "_", str ) ) do + if ( string.len( s ) == 1 ) then table.insert( newname, string.upper( s ) ) continue end + table.insert( newname, string.upper( string.Left( s, 1 ) ) .. string.Right( s, string.len( s ) - 1 ) ) + end + + return string.Implode( " ", newname ) +end + +local function IsEntValid( ent ) + if ( !IsValid( ent ) or ent:IsWorld() ) then return false end + if ( ( ent:SkinCount() or 0 ) > 1 ) then return true end + if ( ( ent:GetNumBodyGroups() or 0 ) > 1) then return true end + if ( ( ent:GetBodygroupCount( 0 ) or 0 ) > 1 ) then return true end + return false +end + +local function SetBodygroup( _, ent, t ) + ent:SetBodygroup( t.group, t.id ) +end +for i = 0, MaxBodyGroups do duplicator.RegisterEntityModifier( "bodygroup" .. i, SetBodygroup ) end + +function TOOL:GetSelecetedEntity() + return self:GetWeapon():GetNWEntity( "rb655_bodygroup_entity" ) +end + +function TOOL:SetSelecetedEntity( ent ) + if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end + if ( !IsValid( ent ) ) then ent = NULL end + + if ( self:GetSelecetedEntity() == ent ) then return end + + self:GetWeapon():SetNWEntity( "rb655_bodygroup_entity", ent ) +end + +if ( SERVER ) then + TOOL.Ready = 0 + + util.AddNetworkString( "rb655_easy_bodygroup_ready" ) + + net.Receive( "rb655_easy_bodygroup_ready", function( len, ply ) + local tool = ply:GetTool( "rb655_easy_bodygroup" ) + if ( tool && net.ReadEntity() == tool:GetSelecetedEntity() ) then tool.Ready = 1 end + end ) +end + +function TOOL:Think() + local ent = self:GetSelecetedEntity() + if ( !IsValid( ent ) ) then self:SetSelecetedEntity( NULL ) end + + if ( CLIENT ) then + if ( ent:EntIndex() == gLastSelecetedEntity ) then return end + gLastSelecetedEntity = ent:EntIndex() + self:UpdateControlPanel() + return + end + + if ( !IsEntValid( ent ) ) then return end + if ( self.Ready == 0 ) then return end + if ( self.Ready > 0 && self.Ready < 50 ) then self.Ready = self.Ready + 1 return end -- Another ugly workaround + + if ( ent:SkinCount() > 1 ) then ent:SetSkin( self:GetClientNumber( "skin" ) ) end + + for i = 0, ent:GetNumBodyGroups() - 1 do + if ( ent:GetBodygroupCount( i ) <= 1 ) then continue end + if ( ent:GetBodygroup( i ) == self:GetClientNumber( "group" .. i ) ) then continue end + SetBodygroup( nil, ent, { group = i, id = self:GetClientNumber( "group" .. i ) } ) + end +end + +function TOOL:LeftClick( trace ) + if ( SERVER && trace.Entity != self:GetSelecetedEntity() ) then + self.Ready = 0 + self:SetSelecetedEntity( trace.Entity ) + end + return true +end + +function TOOL:RightClick( trace ) return self:LeftClick( trace ) end + +function TOOL:Reload() + if ( SERVER ) then + self.Ready = 0 + self:SetSelecetedEntity( self:GetOwner() ) + end + return true +end + +if ( SERVER ) then return end + +TOOL.Information = { + { name = "info", stage = 1 }, + { name = "left" }, + { name = "reload" }, +} + +language.Add( "tool.rb655_easy_bodygroup.left", "Select an object to edit" ) +language.Add( "tool.rb655_easy_bodygroup.reload", "Select yourself" ) + +language.Add( "tool.rb655_easy_bodygroup.name", "Easy Bodygroup Tool" ) +language.Add( "tool.rb655_easy_bodygroup.desc", "Eases change of bodygroups and skins" ) +language.Add( "tool.rb655_easy_bodygroup.1", "Use context menu to edit bodygroups or skins" ) + +language.Add( "tool.rb655_easy_bodygroup.noglow", "Don't render glow/halo around models" ) +language.Add( "tool.rb655_easy_bodygroup.skin", "Skin" ) +language.Add( "tool.rb655_easy_bodygroup.badent", "This entity does not have any skins or bodygroups." ) +language.Add( "tool.rb655_easy_bodygroup.noent", "No entity selected." ) + +function TOOL:GetStage() + if ( IsValid( self:GetSelecetedEntity() ) ) then return 1 end + return 0 +end + +function TOOL:UpdateControlPanel( index ) + local panel = controlpanel.Get( "rb655_easy_bodygroup" ) + if ( !panel ) then MsgN( "Couldn't find rb655_easy_bodygroup panel!" ) return end + + panel:ClearControls() + self.BuildCPanel( panel, self:GetSelecetedEntity() ) +end + +local ConVarsDefault = {} +ConVarsDefault[ "rb655_easy_bodygroup_skin" ] = 0 +for i = 0, MaxBodyGroups do ConVarsDefault[ "rb655_easy_bodygroup_group" .. i ] = 0 end + +function TOOL.BuildCPanel( panel, ent ) + panel:AddControl( "Checkbox", { Label = "#tool.rb655_easy_bodygroup.noglow", Command = "rb655_easy_bodygroup_noglow" } ) + + if ( !IsValid( ent ) ) then panel:AddControl( "Label", { Text = "#tool.rb655_easy_bodygroup.noent" } ) return end + if ( !IsEntValid( ent ) ) then panel:AddControl( "Label", { Text = "#tool.rb655_easy_bodygroup.badent" } ) return end + + panel:AddControl( "ComboBox", { + MenuButton = 1, + Folder = "rb655_ez_bg_" .. ent:GetModel():lower():Replace( "/", "_" ):StripExtension():sub( 8 ), -- Some hacky bussiness + Options = { [ "#preset.default" ] = ConVarsDefault }, + CVars = table.GetKeys( ConVarsDefault ) + } ) + + if ( ent:SkinCount() > 1 ) then + LocalPlayer():ConCommand( "rb655_easy_bodygroup_skin " .. ent:GetSkin() ) + panel:AddControl( "Slider", { Label = "#tool.rb655_easy_bodygroup.skin", Max = ent:SkinCount() - 1, Command = "rb655_easy_bodygroup_skin" } ) + end + + for k = 0, ent:GetNumBodyGroups() - 1 do + if ( ent:GetBodygroupCount( k ) <= 1 ) then continue end + LocalPlayer():ConCommand( "rb655_easy_bodygroup_group" .. k .. " " .. ent:GetBodygroup( k ) ) + panel:AddControl( "Slider", { Label = MakeNiceName( ent:GetBodygroupName( k ) ), Max = ent:GetBodygroupCount( k ) - 1, Command = "rb655_easy_bodygroup_group" .. k } ) + end + + net.Start( "rb655_easy_bodygroup_ready" ) + net.WriteEntity( ent ) + net.SendToServer() +end + +function TOOL:DrawHUD() + local ent = self:GetSelecetedEntity() + if ( !IsValid( ent ) or tobool( self:GetClientNumber( "noglow" ) ) ) then return end + + local t = { ent } + + if ent:IsPlayer() then + local active_weapon = ent:GetActiveWeapon() + + if IsValid(active_weapon) then + table.insert( t, active_weapon ) + end + end + + halo.Add( t, HSVToColor( ( CurTime() * 3 ) % 360, math.abs( math.sin( CurTime() / 2 ) ), 1 ), 2, 2, 1 ) +end diff --git a/garrysmod/addons/first_person_legs_3348203779/addon.json b/garrysmod/addons/first_person_legs_3348203779/addon.json new file mode 100644 index 0000000..21941bf --- /dev/null +++ b/garrysmod/addons/first_person_legs_3348203779/addon.json @@ -0,0 +1,10 @@ +{ + "title": "First Person Legs", + "type": "gamemode", + "tags": [ + "roleplay", + "realism", + "fun" + ], + "ignore": [] +} \ No newline at end of file diff --git a/garrysmod/addons/first_person_legs_3348203779/lua/autorun/client/cl_first-person_legs.lua b/garrysmod/addons/first_person_legs_3348203779/lua/autorun/client/cl_first-person_legs.lua new file mode 100644 index 0000000..a19e508 --- /dev/null +++ b/garrysmod/addons/first_person_legs_3348203779/lua/autorun/client/cl_first-person_legs.lua @@ -0,0 +1,1025 @@ +local cache = {} +local _GetChildBonesRecursive +local mdl = "" + +local ENTITY, PLAYER = FindMetaTable("Entity"), FindMetaTable("Player") +local VECTOR = FindMetaTable("Vector") +local DistToSqr = VECTOR.DistToSqr +local Dot = VECTOR.Dot +local GetModel = ENTITY.GetModel +local GetChildBones = ENTITY.GetChildBones +local next = next +local pairs, ipairs = pairs, ipairs +local hook_Run = hook.Run +local render = render +local render_GetRenderTarget = render.GetRenderTarget +local render_SetColorModulation = render.SetColorModulation +local render_GetColorModulation = render.GetColorModulation +local math_NormalizeAngle = math.NormalizeAngle + +_GetChildBonesRecursive = function(ent, bone, src) + local t = src or {} + table.insert(t, bone) + local cbones = GetChildBones(ent, bone) + + if cbones then + for _, bone in next, cbones do + _GetChildBonesRecursive(ent, bone, t) + end + end + + return t +end + +local function GetChildBonesRecursive(ent, bone) + local mdl = GetModel(ent) + local mdlbones = cache[mdl] + + if not mdlbones then + mdlbones = {} + cache[mdl] = mdlbones + end + + local ret = mdlbones[bone] + if ret then return ret end + ret = _GetChildBonesRecursive(ent, bone) + mdlbones[bone] = ret + + return ret +end + +local translation = { + ["ru"] = { + ["Включить тело от 1-ого лица?"] = "Включить тело от 1-ого лица?", + ["Включить тело от 1-ого лица в Т/С?"] = "Включить тело от 1-ого лица в Т/С?", + ["Включить тень тела от 1-ого лица?"] = "Включить тень тела от 1-ого лица? (не работает с cl_drawownshadow)", + ["Дистанция отдаления тела от центра позиции игрока"] = "Дистанция отдаления тела от центра позиции игрока", + ["Настройка тела от 1 лица"] = "Настройка тела от 1 лица", + ["Текущая модель не имеет анимаций, выберите другую модель для показа тела от 1 лица."] = "Текущая модель не имеет анимаций, выберите другую модель для показа тела от 1 лица.", + ["Перезапустить тело"] = "Перезапустить тело" + }, + + ["en"] = { + ["Включить тело от 1-ого лица?"] = "Enable body in firstperson camera?", + ["Включить тело от 1-ого лица в Т/С?"] = "Enable body in vehicle?", + ["Включить тень тела от 1-ого лица?"] = "Enable body shadow? (don't work with cl_drawownshadow)", + ["Дистанция отдаления тела от центра позиции игрока"] = "Distance of the body from the center of the player's position", + ["Настройка тела от 1 лица"] = "Firstperson body settings", + ["Текущая модель не имеет анимаций, выберите другую модель для показа тела от 1 лица."] = "The current model doesn't have a sequences, please choose another model.", + ["Перезапустить тело"] = "Refresh" + }, + + ["tr"] = { + ["Включить тело от 1-ого лица?"] = "Birinci şahıs kamerada beden etkinleştirilsin mi?", + ["Включить тело от 1-ого лица в Т/С?"] = "Enable body in vehicle?", + ["Включить тень тела от 1-ого лица?"] = "Enable body shadow? (don't work with cl_drawownshadow)", + ["Дистанция отдаления тела от центра позиции игрока"] = "Bedenin oyuncunun pozisyonunun merkezinden uzaklığı", + ["Настройка тела от 1 лица"] = "Birinci şahıs beden ayarları", + ["Текущая модель не имеет анимаций, выберите другую модель для показа тела от 1 лица."] = "Mevcut modelde bir dizi yok, lütfen başka bir model seçin.", + ["Перезапустить тело"] = "Refresh" + } +} + +translation["uk"] = translation["ru"] -- :> + +local CVar = GetConVar("gmod_language") + +local L = function(str) + local lang = CVar:GetString() + local getTranslation = translation[lang] + + return getTranslation and getTranslation[str] + or translation["en"][str] + or str +end + +local bones = {} +local bonesName = {} + +local CVar = CreateClientConVar("cl_gm_body", 1, true, false, L"Включить тело от 1-ого лица?", 0, 1) +local CVar_Distance = CreateClientConVar("cl_gm_body_forward_distance", 17, true, false, L"Дистанция отдаления тела от центра позиции игрока", 8, 32) +local CVar_Vehicle = CreateClientConVar("cl_gm_body_in_vehicle", 1, true, false, L"Включить тело от 1-ого лица в Т/С?", 0, 1) +local CVar_Shadow = CreateClientConVar("cl_gm_body_enable_shadow", 0, true, false, L"Включить тень тела от 1-ого лица?", 0, 1) +local forwardDistance = CVar_Distance:GetFloat() + +cvars.AddChangeCallback("cl_gm_body_forward_distance", function(_, _, newValue) + forwardDistance = tonumber(newValue) or 17 +end, "cl_gm_legs_forward_distance") + +local defaultConVars = { + cl_gm_body = "1", + cl_gm_body_in_vehicle = "1", + cl_gm_body_forward_distance = "17", + cl_gm_body_enable_shadow = "0" +} + +hook.Add("PopulateToolMenu", "body.Utilities", function() + spawnmenu.AddToolMenuOption("Utilities", "User", "cl_body_options", L"Настройка тела от 1 лица", "", "", function(panel) + panel:Clear() + + panel:AddControl("ComboBox", { + MenuButton = 1, + Folder = "first_person_body", + Options = {["#preset.default"] = defaultConVars}, + CVars = table.GetKeys(defaultConVars) + }) + + panel:CheckBox(L"Включить тело от 1-ого лица?", "cl_gm_body") + panel:CheckBox(L"Включить тело от 1-ого лица в Т/С?", "cl_gm_body_in_vehicle") + panel:CheckBox(L"Включить тень тела от 1-ого лица?", "cl_gm_body_enable_shadow") + panel:NumSlider(L"Дистанция отдаления тела от центра позиции игрока", "cl_gm_body_forward_distance", 8, 32) + panel:Button(L"Перезапустить тело", "cl_gm_body_refresh", 8, 32) + end) +end) + +local queue = {} +local work = false + +local MarkToRemove = function(ent) + if not IsValid(ent) then + return + end + + work = true + ent:SetNoDraw(true) + table.insert(queue, ent) +end + +hook.Add("Think", "legs.MarkToRemove", function() + if not work then + return + end + + for key, ent in pairs(queue) do + if ent:IsValid() then + ent:Remove() + end + + queue[key] = nil + end + + if not next(queue) then + work = false + end +end) + +hook.Add("LocalPlayer_Validated", "cl_gmod_legs", function(ply) + hook.Remove("LocalPlayer_Validated", "cl_gmod_legs") + + local playermodelbones = {"ValveBiped.Bip01_Head1","ValveBiped.Bip01_R_Trapezius","ValveBiped.Bip01_R_Bicep","ValveBiped.Bip01_R_Shoulder", "ValveBiped.Bip01_R_Elbow","ValveBiped.Bip01_R_Wrist","ValveBiped.Bip01_R_Ulna","ValveBiped.Bip01_L_Trapezius","ValveBiped.Bip01_L_Bicep","ValveBiped.Bip01_L_Shoulder", "ValveBiped.Bip01_L_Elbow","ValveBiped.Bip01_L_Wrist","ValveBiped.Bip01_L_Ulna", "ValveBiped.Bip01_Neck1","ValveBiped.Bip01_Hair1","ValveBiped.Bip01_Hair2","ValveBiped.Bip01_L_Clavicle","ValveBiped.Bip01_R_Clavicle","ValveBiped.Bip01_R_UpperArm", "ValveBiped.Bip01_R_Forearm", "ValveBiped.Bip01_R_Hand", "ValveBiped.Bip01_L_UpperArm", "ValveBiped.Bip01_L_Forearm", "ValveBiped.Bip01_L_Hand", "ValveBiped.Bip01_L_Wrist", "ValveBiped.Bip01_R_Wrist", "ValveBiped.Bip01_L_Finger4", "ValveBiped.Bip01_L_Finger41", "ValveBiped.Bip01_L_Finger42", "ValveBiped.Bip01_L_Finger3", "ValveBiped.Bip01_L_Finger31", "ValveBiped.Bip01_L_Finger32", "ValveBiped.Bip01_L_Finger2", "ValveBiped.Bip01_L_Finger21", "ValveBiped.Bip01_L_Finger22", "ValveBiped.Bip01_L_Finger1", "ValveBiped.Bip01_L_Finger11", "ValveBiped.Bip01_L_Finger12", "ValveBiped.Bip01_L_Finger0", "ValveBiped.Bip01_L_Finger01", "ValveBiped.Bip01_L_Finger02", "ValveBiped.Bip01_R_Finger4", "ValveBiped.Bip01_R_Finger41", "ValveBiped.Bip01_R_Finger42", "ValveBiped.Bip01_R_Finger3", "ValveBiped.Bip01_R_Finger31", "ValveBiped.Bip01_R_Finger32", "ValveBiped.Bip01_R_Finger2", "ValveBiped.Bip01_R_Finger21", "ValveBiped.Bip01_R_Finger22", "ValveBiped.Bip01_R_Finger1", "ValveBiped.Bip01_R_Finger11", "ValveBiped.Bip01_R_Finger12", "ValveBiped.Bip01_R_Finger0", "ValveBiped.Bip01_R_Finger01", "ValveBiped.Bip01_R_Finger02"} + local playermodelbones_kv = {} + + for key, v in pairs(playermodelbones) do + playermodelbones_kv[v] = true + end + + local ply = ply or LocalPlayer() + + local GetBoneName = ENTITY.GetBoneName + local LookupBone = ENTITY.LookupBone + + local MATRIX = FindMetaTable("VMatrix") + local Scale, Translate, SetTranslation, SetAngles = MATRIX.Scale, MATRIX.Translate, MATRIX.SetTranslation, MATRIX.SetAngles + local GetTranslation, GetAngles = MATRIX.GetTranslation, MATRIX.GetAngles + local SetBoneMatrix = ENTITY.SetBoneMatrix + local render = render + local cam_Start3D, render_EnableClipping, render_PushCustomClipPlane, render_PopCustomClipPlane, render_EnableClipping, cam_End3D = + cam.Start3D, render.EnableClipping, render.PushCustomClipPlane, render.PopCustomClipPlane, render.EnableClipping, cam.End3D + local SetupBones = ENTITY.SetupBones + local _EyePos = ENTITY.EyePos + local CurTime = CurTime + local FrameTime = FrameTime + local GetCycle = ENTITY.GetCycle + local DrawModel = ENTITY.DrawModel + local ManipulateBonePosition = ENTITY.ManipulateBonePosition + local ManipulateBoneScale = ENTITY.ManipulateBoneScale + local GetAttachment = ENTITY.GetAttachment + local GetNWBool = ENTITY.GetNWBool + local LookupAttachment = ENTITY.LookupAttachment + local GetCurrentViewOffset = PLAYER.GetCurrentViewOffset + local Crouching = PLAYER.Crouching + local OnGround = ENTITY.OnGround + local ShouldDrawLocalPlayer = PLAYER.ShouldDrawLocalPlayer + local Alive = PLAYER.Alive + local GetRagdollEntity = PLAYER.GetRagdollEntity + local InVehicle = PLAYER.InVehicle + local FL_ANIMDUCKING = FL_ANIMDUCKING + + local GetObserverMode = PLAYER.GetObserverMode + + MarkToRemove(ply.Body) + MarkToRemove(ply.Body_NoDraw) + MarkToRemove(ply.Body_Shadow) + + local hook = hook + local math = math + local remap = math.Remap + local eyeAngles = Angle() + local suppress = false + + local isDucking = false + local finalPos = Vector() + local limit_check = 0 + local timeCache = 0.15 + local vector_origin = Vector(0, 0, 0) + + local find, insert = string.find, table.insert + + local SetPoseParameter, GetPoseParameter = ENTITY.SetPoseParameter, ENTITY.GetPoseParameter + local SetPlaybackRate, GetPlaybackRate = ENTITY.SetPlaybackRate, ENTITY.GetPlaybackRate + local GetBoneMatrix = ENTITY.GetBoneMatrix + + local a1, b1, c1 = 0, 0, 0 + local headPos = Vector(0,10000,0) + local limitJump = 0 + + local onGround = true + local JUMPING_ = false + local vehicle_steer,d1,e1 = 0, 0, 0 + local GetNumPoseParameters, GetPoseParameterRange = ENTITY.GetNumPoseParameters, ENTITY.GetPoseParameterRange + local GetPoseParameterName, GetSequence = ENTITY.GetPoseParameterName, ENTITY.GetSequence + local GetRenderAngles, SetRenderAngles = PLAYER.GetRenderAngles, PLAYER.SetRenderAngles + local faggot = Vector() + local GetPos, GetViewOffset = ENTITY.GetPos, PLAYER.GetViewOffset + local vector_normal = Vector(1, 1, 1) + local vector_fixCrouch = Vector() + local CreateShadow, DestroyShadow, SetRenderBounds = ENTITY.CreateShadow, ENTITY.DestroyShadow, ENTITY.SetRenderBounds + local vrmod = vrmod + local mins_render, maxs_render = Vector(-128, -128, 0), Vector(128, 128, 0) + local eyePos = Vector() + local ply_EyePos = Vector() + + hook.Add("SetupMove", "legs.SetupMove", function(ply, move) + if not IsFirstTimePredicted() then + return + end + + if bit.band(move:GetButtons(), IN_JUMP) ~= 0 + and bit.band(move:GetOldButtons(), IN_JUMP) == 0 + and OnGround(ply) then + JUMPING_, onGround = not JUMPING_, false + end + end) + + hook.Add("FinishMove", "legs.FinishMove", function(ply, move) + if not IsFirstTimePredicted() then + return + end + + local isOnGround = OnGround(ply) + + if onGround ~= isOnGround then + onGround = isOnGround + + if onGround then + limitJump = CurTime() + FrameTime() + end + end + end) + + local validBones = {} + + hook.Add("CalcView", "body.CalcView", function(ply, vec, ang) + if not CVar_Vehicle:GetBool() + or not InVehicle(ply) + or ply:GetVehicle():GetThirdPersonMode() then + return + end + + vehicle_steer = GetPoseParameter(ply, "vehicle_steer") + d1 = GetPoseParameter(ply, "head_yaw") + e1 = GetPoseParameter(ply, "head_pitch") + + local a1, b1, c1 = GetPoseParameter(ply, "body_yaw", 0), GetPoseParameter(ply, "aim_yaw", 0), GetPoseParameter(ply, "aim_pitch", 0) + + SetPoseParameter(ply, "body_yaw", 0) + SetPoseParameter(ply, "aim_yaw", 0) + SetPoseParameter(ply, "aim_pitch", 0) + + SetPoseParameter(ply, "vehicle_steer", 0) + SetPoseParameter(ply, "head_yaw", 0) + SetPoseParameter(ply, "head_pitch", 0) + + SetupBones(ply) + + local h = LookupAttachment(ply, "eyes") + + if h then + local mat = GetAttachment(ply, h) + + if mat then + local pos = mat.Pos + vec.x = vec.x - (vec.x - pos.x) + vec.y = vec.y - (vec.y - pos.y) + end + end + + SetPoseParameter(ply, "vehicle_steer", vehicle_steer) + SetPoseParameter(ply, "head_yaw", d1) + SetPoseParameter(ply, "head_pitch", e1) + + SetPoseParameter(ply, "body_yaw", a1) + SetPoseParameter(ply, "aim_yaw", b1) + SetPoseParameter(ply, "aim_pitch", c1) + end) + + local removeGarbage = function(bonesSuccess, boneCount) + local ent = ply.Body + local goofyUhhPosition = GetPos(ply) + GetViewOffset(ply) - eyeAngles:Forward() * 32 + local inVeh = InVehicle(ply) + local forward = eyeAngles:Forward() * forwardDistance + + if CVar_Vehicle:GetBool() + and inVeh then + for i = 0, boneCount - 1 do + local boneName = GetBoneName(ent, i) + local mat = GetBoneMatrix(ent, i) + + if mat then + local bone = LookupBone(ply, boneName) + + if bone then + local mat2 = GetBoneMatrix(ply, bone) + + if mat2 then + SetTranslation(mat, GetTranslation(mat2)) + SetAngles(mat, GetAngles(mat2)) + SetBoneMatrix(ent, i, mat) + end + end + end + end + + local h = LookupBone(ent, "ValveBiped.Bip01_Head1") + + if h then + local getScale = inVeh and vector_origin or vector_normal + local mat = ent:GetBoneMatrix(h) + + ManipulateBonePosition(ent, h, headPos) + ManipulateBoneScale(ent, h, getScale) + + if mat then + local pos = mat:GetTranslation() + local recursive = GetChildBonesRecursive(ent, h) + + for key = 1, #recursive do + local bone = recursive[key] + + if bone then + ManipulateBoneScale(ent, bone, getScale) + + local mat2 = GetBoneMatrix(ent, bone) + + if mat2 then + SetTranslation(mat2, pos) + SetBoneMatrix(ent, bone, mat2) + end + end + end + end + end + + return + end + + for i = 0, boneCount - 1 do + local boneName = GetBoneName(ent, i) + local mat = GetBoneMatrix(ent, i) + + if mat then + if playermodelbones_kv[boneName] then + SetTranslation(mat, goofyUhhPosition) + + bonesSuccess[i] = true + + local recursive = GetChildBonesRecursive(ent, i) + + for key = 1, #recursive do + local bone = recursive[key] + + if not bonesSuccess[bone] then + bonesSuccess[bone] = true + + local mat = GetBoneMatrix(ent, bone) + + if mat then + SetTranslation(mat, goofyUhhPosition) + + SetBoneMatrix(ent, bone, mat) + end + end + end + elseif not bonesSuccess[i] then + local bone = LookupBone(ply, boneName) + + if bone then + local mat2 = GetBoneMatrix(ply, bone) + + if mat2 then + SetTranslation(mat, GetTranslation(mat2) - forward - vector_fixCrouch) + SetAngles(mat, GetAngles(mat2)) + end + end + end + + SetBoneMatrix(ent, i, mat) + end + end + end + + local Legs_NoDraw_Angle = Angle(0, 0, 0) + local potentionalBones, timeCacheBones = {}, 0 + + local spineBones = { + ["ValveBiped.Bip01_Spine2"] = true, + ["ValveBiped.Bip01_Spine4"] = true + } + + local miscSpineBones = {} + local ik_foot = hook.GetTable()["PostPlayerDraw"] + + if ik_foot then + ik_foot = ik_foot["IKFoot_PostPlayerDraw"] + end + + local body_is_rendering = true + local angle_render = Angle() + + if VMS and VMS.GetViewModels then + VMS_GetViewModels = VMS_GetViewModels or VMS.GetViewModels + local VMS_GetViewModels = VMS_GetViewModels + + function VMS.GetViewModels(...) + local mdls = VMS_GetViewModels(...) + + if IsValid(ply.Body) then + table.insert(mdls, ply.Body) + end + + return mdls + end + end + + local shadow_buildBonePosition = function() + ply.Body_Shadow.Callback = ply.Body_Shadow:AddCallback("BuildBonePositions", function(ent, boneCount) + local ang = GetRenderAngles(ply) + local forward = eyeAngles:Forward() * forwardDistance + vector_fixCrouch + + SetRenderAngles(ply, eyeAngles) + a1, b1, d1 = GetPoseParameter(ply, "body_yaw", 0), GetPoseParameter(ply, "aim_yaw", 0), GetPoseParameter(ply, "head_yaw", 0) + + SetPoseParameter(ply, "body_yaw", 0) + SetPoseParameter(ply, "aim_yaw", 0) + SetPoseParameter(ply, "head_yaw", 0) + + SetupBones(ply) + + for i = 0, boneCount - 1 do + local boneName = GetBoneName(ent, i) + local lookupBone = LookupBone(ply, boneName) + + if lookupBone then + local mat = GetBoneMatrix(ply, lookupBone) + + if mat then + local mat2 = GetBoneMatrix(ent, i) + + if mat2 then + SetTranslation(mat2, GetTranslation(mat) - forward) + SetAngles(mat2, GetAngles(mat)) + SetBoneMatrix(ent, i, mat2) + end + end + end + end + + SetRenderAngles(ply, ang) + + SetPoseParameter(ply, "body_yaw", a1) + SetPoseParameter(ply, "aim_yaw", b1) + SetPoseParameter(ply, "head_yaw", d1) + end) + end + + local buildBonePosition = function() + ply.Body.Callback = ply.Body:AddCallback("BuildBonePositions", function(ent, boneCount) + if not CVar:GetBool() then + return ent:RemoveCallback("BuildBonePositions", ent.Callback) + end + + if not ply.TimeToDuck + or suppress then + return + end + + local ang = GetRenderAngles(ply) + local inVehicle = InVehicle(ply) + + if not inVehicle then + SetRenderAngles(ply, eyeAngles) + end + + a1, b1, c1 = GetPoseParameter(ply, "body_yaw", 0), GetPoseParameter(ply, "aim_yaw", 0), GetPoseParameter(ply, "aim_pitch", 0) + + SetPoseParameter(ply, "body_yaw", 0) + SetPoseParameter(ply, "aim_yaw", 0) + SetPoseParameter(ply, "aim_pitch", 0) + + local currentSequence = ply:GetSequence() + + if ent.Seq ~= currentSequence then + ent.Seq = currentSequence + + ply:ResetSequenceInfo() + end + + SetupBones(ply) + + local legsNoDraw = ply.Body_NoDraw + + for i = 0, GetNumPoseParameters(ply) - 1 do + local flMin, flMax = GetPoseParameterRange(ply, i) + local sPose = GetPoseParameterName(ply, i) + local remap = remap(GetPoseParameter(ply, sPose), 0, 1, flMin, flMax) + SetPoseParameter(legsNoDraw, sPose, remap) + SetPoseParameter(ent, sPose, remap) + end + + if ik_foot then + ik_foot(ply) + end + + hook_Run("body.SetupBones", ent) + + local bonesSuccess = {} + removeGarbage(bonesSuccess, boneCount) + + SetPoseParameter(ply, "body_yaw", a1) + SetPoseParameter(ply, "aim_yaw", b1) + SetPoseParameter(ply, "aim_pitch", c1) + + SetupBones(legsNoDraw) + + local this = (2 - (0.8 * ply.TimeToDuck)) + local cacheThis = this + local CT = CurTime() + + if not inVehicle then + if timeCacheBones < CT then + timeCacheBones, potentionalBones, miscSpineBones, timeCache = CT + timeCache, {}, {}, 0.15 + + for boneName in pairs(spineBones) do + local bone = LookupBone(ent, boneName) + + if bone then + local bones = GetChildBonesRecursive(ent, bone) + + for i = 1, #bones do + miscSpineBones[bones[i]] = true + end + end + end + + for i = 1, #validBones do + local array = validBones[i] + local bone, isPelvis = array[2], array[1] + + if bonesSuccess[bone] then + continue + end + + local recursive = GetChildBonesRecursive(ent, bone) + + for key = 1, #recursive do + local i = recursive[key] + + if not bonesSuccess[i] + and (isPelvis and i == bone or not isPelvis) + and i then + bonesSuccess[i] = true + + local boneName = GetBoneName(ent, i) + local mat = GetBoneMatrix(ent, i) + + if mat then + local b = LookupBone(legsNoDraw, boneName) + + if b then + local mat2 = GetBoneMatrix(legsNoDraw, b) + + if mat2 then + local matTR, mat2TR = GetTranslation(mat), GetTranslation(mat2) - vector_fixCrouch + + if boneName == "ValveBiped.Bip01_Pelvis" then + Legs_NoDraw_Angle.y = (math_NormalizeAngle(eyeAngles.y - GetAngles(mat).y) + 90) / 1.25 + elseif spineBones[boneName] then + this = 4 + elseif miscSpineBones[i] then + this = 5 + end + + SetTranslation(mat, mat2TR - (mat2TR - matTR) / this) + this = cacheThis + SetAngles(mat, GetAngles(mat2)) + SetBoneMatrix(ent, i, mat) + + potentionalBones[#potentionalBones + 1] = { + [1] = i, + [2] = boneName + } + end + end + end + else + table.remove(cache[ent:GetModel()][bone], key) + + timeCache, timeCacheBones = 0, 0 + end + end + end + else + for key = 1, #potentionalBones do + local array = potentionalBones[key] + local i, boneName = array[1], array[2] + local mat = GetBoneMatrix(ent, i) + + if mat then + local b = LookupBone(legsNoDraw, boneName) + + if b then + local mat2 = GetBoneMatrix(legsNoDraw, b) + + if mat2 then + if boneName == "ValveBiped.Bip01_Pelvis" then + Legs_NoDraw_Angle.y = (math_NormalizeAngle(eyeAngles.y - GetAngles(mat).y) + 90) / 1.25 + elseif spineBones[boneName] then + this = 4 + elseif miscSpineBones[i] then + this = 5 + end + + local matTR, mat2TR = GetTranslation(mat), GetTranslation(mat2) - vector_fixCrouch + + SetTranslation(mat, mat2TR - (mat2TR - matTR) / this) + this = cacheThis + SetAngles(mat, GetAngles(mat2)) + SetBoneMatrix(ent, i, mat) + end + end + end + end + end + end + + SetRenderAngles(ply, ang) + end) + + ply.Body.FullyLoaded = true + end + + local SetParent, SetPos, SetAngles, SetCycle = ENTITY.SetParent, ENTITY.SetPos, ENTITY.SetAngles, ENTITY.SetCycle + + hook.Add("RenderScene", "firstperson_shadow.RenderScene", function(vec, ee) + local ply_Body = ply.Body + + if IsValid(ply_Body) then + DestroyShadow(ply_Body) + + local nodrawbody = ply.Body_NoDraw + + if IsValid(nodrawbody) then + DestroyShadow(nodrawbody) + nodrawbody:SetNoDraw(true) + end + end + + local self = ply.Body_Shadow + + if not IsValid(self) then + return + end + + if suppress + or not CVar:GetBool() + or ShouldDrawLocalPlayer(ply) + or not body_is_rendering + or not CVar_Shadow:GetBool() then + DestroyShadow(self) + elseif faggot and ply.TimeToFaggot then + CreateShadow(self) + + local self = ply.Body_Shadow + + SetPos(self, ply_EyePos) + SetAngles(self, eyeAngles) + + local _, size2 = self:GetModelRenderBounds() + maxs_render.z = size2.z * 1.25 + vector_fixCrouch.z + + SetRenderBounds(self, mins_render, maxs_render) + end + end) + + hook.Add("RenderScene", "firstperson.RenderScene", function(vec, ee) + if not CVar:GetBool() then + return + end + + eyePos, ply_EyePos = vec, ply:EyePos() + eyeAngles = ply:EyeAngles() + eyeAngles.p = 0 + + isDucking = bit.band(ply:GetFlags(), FL_ANIMDUCKING) > 0 + and onGround + + local FT = FrameTime() + + ply.TimeToDuck = math.Clamp((ply.TimeToDuck or 0) + FT * 3.5 * (isDucking and 1 or -1), 0, 1) + + local realEyeAngles = EyeAngles() + local body = ply.Body + local inVeh = InVehicle(ply) + + suppress = ShouldDrawLocalPlayer(ply) + or not Alive(ply) + or (inVeh and not CVar_Vehicle:GetBool()) + or not IsValid(body) + or not IsValid(ply.Body_Shadow) + or not body.FullyLoaded + or not IsValid(ply.Body_NoDraw) + or GetRagdollEntity(ply):IsValid() + or GetObserverMode(ply) ~= 0 + or (ply.IsProne and ply:IsProne()) + or realEyeAngles.p > 110 + or realEyeAngles.p < -110 + or (vrmod and vrmod.IsPlayerInVR(ply)) + or ply:GetNoDraw() + or ply:GetViewEntity() ~= ply + or vec:DistToSqr(ply_EyePos) > 10000 + + local CT = SysTime() + + if limit_check < CT and IsValid(body) and IsValid(ply.Body_Shadow) then + limit_check = CT + timeCache + + if ply.Body.Callback then + local getCallbacks = ply.Body:GetCallbacks("BuildBonePositions") + + if not getCallbacks[ply.Body.Callback] then + buildBonePosition() + end + else + buildBonePosition() + end + + if ply.Body_Shadow.Callback then + local getCallbacks = ply.Body_Shadow:GetCallbacks("BuildBonePositions") + + if not getCallbacks[ply.Body_Shadow.Callback] then + shadow_buildBonePosition() + end + else + shadow_buildBonePosition() + end + + body:SetSkin(ply:GetSkin()) + body:SetMaterial(ply:GetMaterial()) + + if not suppress then + local bodyGroups = ply:GetBodyGroups() + + for k = 1, #bodyGroups do + local v = bodyGroups[k] + local bg = ply:GetBodygroup(v.id) + + body:SetBodygroup(v.id, bg) + ply.Body_NoDraw:SetBodygroup(v.id, bg) + end + end + + validBones = {} + + for i = 0, body:GetBoneCount() - 1 do + local boneName = GetBoneName(body, i) + local isPelvis = find(boneName, "Pelvis", 1, true) or false + + if find(boneName, "Spine", 1, true) + or isPelvis + or find(boneName, "Jacket", 1, true) then + validBones[#validBones + 1] = { + [1] = isPelvis, + [2] = i + } + end + end + end + + if suppress then + return + end + + local getPos = GetPos(ply) + local getView = GetViewOffset(ply) + local cycle = GetCycle(ply) + + SetParent(body, ply) + SetPos(body, getPos) + SetPlaybackRate(body, GetPlaybackRate(ply)) + SetCycle(body, cycle) + + local currentView = GetCurrentViewOffset(ply) + local forward = eyeAngles:Forward() * forwardDistance + local onGround = ply:OnGround() + + ply.TimeToFaggot = math.Clamp((ply.TimeToFaggot or 0) + FT * (Crouching(ply) and 4 or 10000) * (not onGround and 1 or -1), 0, 1) + + if not InVehicle(ply) then + SetPos(ply.Body_NoDraw, getPos - forward) + SetAngles(ply.Body_NoDraw, eyeAngles - Legs_NoDraw_Angle) + SetAngles(body, eyeAngles) + end + + faggot = (not onGround or limitJump > CurTime()) and getView - currentView or vector_origin + vector_fixCrouch = (faggot * ply.TimeToFaggot) + + finalPos = getPos + + currentView + + vector_fixCrouch + end) + + local vector_down = Vector(0, 0, -1) + local erroredModels = {} + local DestroyShadow = ENTITY.DestroyShadow + local oldSeq, oldSequenceName = 0, "" + + concommand.Add("cl_gm_body_refresh", function() + MarkToRemove(ply.Body) + MarkToRemove(ply.Body_NoDraw) + end) + + hook.Add("Think", "body.Think", function() + if not CVar:GetBool() then + return + end + + local current = ply.wardrobe or GetModel(ply) + local currentSequence = ply:GetSequence() + + if oldSeq ~= currentSequence then + oldSeq = currentSequence + + local currentSequenceName = ply:GetSequenceName(currentSequence) + + if currentSequenceName == oldSequenceName then + MarkToRemove(ply.Body) + MarkToRemove(ply.Body_NoDraw) + MarkToRemove(ply.Body_Shadow) + end + + oldSequenceName = currentSequenceName + end + + if not erroredModels[current] + and (not IsValid(ply.Body) + or GetModel(ply.Body) ~= current) then + MarkToRemove(ply.Body) + MarkToRemove(ply.Body_NoDraw) + MarkToRemove(ply.Body_Shadow) + + ply.Body = ents.CreateClientProp(current) + ply.Body:SetModel(current) + ply.Body:DestroyShadow() + ply.Body:SetIK(false) + ply.Body:PhysicsDestroy() + SetupBones(ply.Body) + ply.Body.GetPlayerColor = function() + return ply:GetPlayerColor() + end + + timer.Simple(0, function() + if not IsValid(ply.Body) + or not IsValid(ply.Body_NoDraw) then + return + end + + local seq = ply.Body:LookupSequence("idle_all_01") + + if seq < 0 then + seq = ply.Body:LookupSequence("idle_all_02") + + if seq < 0 then + seq = ply.Body:LookupSequence("idle1") + + if seq < 0 then + MarkToRemove(ply.Body) + + erroredModels[current] = true + + local CVar = GetConVar("gmod_language") + + chat.AddText(Color(230, 30, 30), L"Текущая модель не имеет анимаций, выберите другую модель для показа тела от 1 лица.") + return + end + end + end + + ply.Body_NoDraw:SetSequence(seq) + end) + + ply.Body_NoDraw = ents.CreateClientProp(current) + ply.Body_NoDraw:SetModel(current) + ply.Body_NoDraw:SetNoDraw(true) + ply.Body_NoDraw:SetIK(false) + ply.Body_NoDraw:PhysicsDestroy() + ply.Body_NoDraw.GetPlayerColor = function() + return ply:GetPlayerColor() + end + + ply.Body.RenderOverride = function(self, flag) + if bit.band(flag, STUDIO_SHADOWDEPTHTEXTURE) ~= 0 then + return + end + + local ply_Body = ply.Body + + if not CVar:GetBool() + or suppress + or ShouldDrawLocalPlayer(ply) then + return + end + + if hook_Run("ShouldDisableLegs", ply_Body) == true then + body_is_rendering = false + + return + end + + body_is_rendering = true + + local shootPos = _EyePos(ply) + + if render_GetRenderTarget() + and DistToSqr(EyePos(), shootPos) > 1024 then + return + end + + local ret = hook_Run("PreDrawBody", ply_Body) + + if ret == false then + return + end + + local color = ply:GetColor() + local m1, m2, m3 = render_GetColorModulation() + local inVeh = InVehicle(ply) + local bEnabled = false + + if not inVeh then + cam_Start3D(ply_EyePos, nil, nil, nil, nil, nil, nil, 0.5, -1) + render_PushCustomClipPlane(vector_down, Dot(vector_down, ply_EyePos)) + bEnabled = render_EnableClipping(true) + end + + render_SetColorModulation(color.r / 255, color.g / 255, color.b / 255) + DrawModel(ply_Body) + render_SetColorModulation(m1, m2, m3) + + if not inVeh then + render_PopCustomClipPlane() + render_EnableClipping(bEnabled) + cam_End3D() + end + + hook_Run("PostDrawBody", ply_Body) + end + + ply.Body_Shadow = ents.CreateClientProp(current) + ply.Body_Shadow:SetModel(current) + ply.Body_Shadow:SetIK(false) + ply.Body_Shadow:PhysicsDestroy() + ply.Body_Shadow.RenderOverride = function() end + + ply.Body.FullyLoaded, timeCacheBones = false, 0 + end + end) + + hook.Add("PreDrawBody", "cl_body.PreDrawBody_Compat", function() + if VWallrunning + or inmantle + or (VMLegs and VMLegs:IsActive()) + or (ply.StopKick or 0) > CurTime() then + return false + end + end) +end) + +hook.Add("Think", "cl_legs.Load", function() + local ply = LocalPlayer() + + if IsValid(ply) then + hook.Remove("Think", "cl_legs.Load") + + hook_Run("LocalPlayer_Validated", ply) + end +end) diff --git a/garrysmod/addons/first_person_legs_3348203779/lua/autorun/server/faster_height.lua b/garrysmod/addons/first_person_legs_3348203779/lua/autorun/server/faster_height.lua new file mode 100644 index 0000000..0f773f3 --- /dev/null +++ b/garrysmod/addons/first_person_legs_3348203779/lua/autorun/server/faster_height.lua @@ -0,0 +1,14 @@ +hook.Add("StartCommand", "Dynamic Height + Hull Fix FIX", function() + hook.Remove("StartCommand", "Dynamic Height + Hull Fix FIX") + + if DynamicCameraUpdateTrueModel then + local ECPlayerTickRate = 0.5 + + hook.Add("PlayerTick", "DynamicCamera:PlayerTick", function(ply) + if (ply.ECPlayerTickTime or 0) > CurTime() then return end + ply.ECPlayerTickTime = CurTime() + ECPlayerTickRate + + DynamicCameraUpdateTrueModel(ply) + end) + end +end) diff --git a/garrysmod/addons/fixguns/lua/autorun/client/cl_dwep.lua b/garrysmod/addons/fixguns/lua/autorun/client/cl_dwep.lua new file mode 100644 index 0000000..361f15c --- /dev/null +++ b/garrysmod/addons/fixguns/lua/autorun/client/cl_dwep.lua @@ -0,0 +1,502 @@ +--leak by matveicher +--vk group - https://vk.com/gmodffdev +--steam - https://steamcommunity.com/profiles/76561198968457747/ +--ds server - https://discord.gg/V329W7Ce8g +--ds - matveicher + + +include("autorun/sh_dwep_config.lua") + +local scrw = ScrW() +local scrh = ScrH() +local colors = DWEP.Config.Colors +local background = colors["background"] +local foreground = colors["foreground"] +local inactiveClr = colors["inactiveClr"] +local theme = colors["theme"] +local highlight = Color(theme.r, theme.g, theme.b, 10) +surface.CreateFont( "dwep_24", { font = "Roboto", size = 24, weight = 600, bold = true, strikeout = false, outline = false, shadow = false, outline = false,}) +surface.CreateFont( "dwep_22", { font = "Roboto", size = 22, weight = 600, bold = true, strikeout = false, outline = false, shadow = false, outline = false,}) +surface.CreateFont( "dwep_20", { font = "Roboto", size = 20, weight = 600, bold = true, strikeout = false, outline = false, shadow = false, outline = false,}) +surface.CreateFont( "dwep_18", { font = "Roboto", size = 18, weight = 600, bold = true, strikeout = false, outline = false, shadow = false, outline = false,}) +surface.CreateFont( "dwep_16", { font = "Roboto", size = 16, weight = 600, bold = true, strikeout = false, outline = false, shadow = false, outline = false,}) +DWEP.SearchData = DWEP.SearchData or { + ["search"] = "", + ["results"] = DWEP.Sweps, + ["searchTime"] = 0, + ["searchComplete"] = 0, +} + +hook.Add("InitPostEntity", "LoadDWEPData", function() + + net.Start("dwep_load_weapon") + net.SendToServer() + +end) + +net.Receive("dwep_load_weapon", function() + + local updateData = net.ReadTable() + local class = net.ReadString() + + for k,v in pairs(updateData) do + DWEP.AdjustValue(class, k, v) + end + +end) + +local function generateSearch() + + if not DWEP.Menu then return end + local searchResults = DWEP.SearchData["results"] + local searchScroll = DWEP.Menu.SearchPanel.ScrollPanel + searchScroll.ypos = 0 + local highlight = Color(theme.r, theme.g, theme.b, 10) + for k,v in pairs(searchResults) do + local id = #DWEP.Menu.SearchPanel.Results + 1 or 1 + DWEP.Menu.SearchPanel.Results[id] = vgui.Create("DButton", searchScroll) + local resultPanel = DWEP.Menu.SearchPanel.Results[id] + resultPanel.value = v + resultPanel:SetPos(0,searchScroll.ypos) + resultPanel:SetSize(searchScroll:GetWide(), 29) + resultPanel:SetText("") + local name = weapons.GetStored(v).PrintName or "Missing PrintName" + local barClr = k % 2 == 0 and foreground or Color(foreground.r, foreground.g, foreground.b, 150) + resultPanel.Paint = function(me,w,h) + surface.SetDrawColor(barClr) + surface.DrawRect(0,0,w,h) + draw.SimpleText(string.upper(v .. " - " .. name), "dwep_20", w * .05, h / 2, string.find(v, DWEP.SearchData["search"]) and color_white or inactiveClr, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + if me:IsHovered() then + surface.SetDrawColor(highlight) + surface.DrawRect(0,0,w,h) + end + + end + resultPanel.DoClick = function() + OpenDWEPWeapon(v) + end + searchScroll.ypos = searchScroll.ypos + 29 * 1.05 + end + +end + +local oldResults = {} +local function refreshResults() + if IsValid(DWEP.Menu) and IsValid(DWEP.Menu.SearchPanel) then + if DWEP.SearchData["results"] != oldResults then + DWEP.SearchData["searchTime"] = nil + local total = #DWEP.Menu.SearchPanel.Results + for k,v in pairs(DWEP.Menu.SearchPanel.Results) do + v:Remove() + end + generateSearch() + end + end + +end + + +local function updateSearch(searchText, forward) + + local searchData = DWEP.SearchData + local results = searchData["results"] + searchData["search"] = searchText + local searchTable = DWEP.Sweps + + if #results <= 0 or not forward then + searchTable = DWEP.Sweps + elseif #results >= 0 and forward then + searchTable = results + end + results = {} + local searchTotal = #searchTable + timer.Create("dwep_searchtime", .5, 1, function() + searchData["searchTime"] = CurTime() + end) + + for i = 1 , searchTotal do + local v = searchTable[i] + if string.find(v, searchText) and not table.HasValue(results, v) then + results[#results + 1 or 1] = v + if i >= searchTotal then + searchData["results"] = results + end + else + searchTotal = searchTotal - 1 + end + end +end + +local barClr = Color(foreground.r, foreground.g, foreground.b, 150) +local function paintSbar(sbar) + +local bar = sbar:GetVBar() + +local buttH = 0 +function bar.btnUp:Paint( w, h ) + buttH = h +end + +function bar:Paint( w, h ) + draw.RoundedBox( 8, w / 2 - w / 2, buttH, w / 2, h - buttH * 2, barClr ) +end + +function bar.btnDown:Paint( w, h ) + +end +function bar.btnGrip:Paint( w, h ) + draw.RoundedBox( 8, w / 2 - w / 2, 0, w / 2, h , theme ) +end + +end + + +function OpenDWEP(refresh) + if not DWEP.CanDWEP(LocalPlayer()) then return end + DWEP.Menu = vgui.Create("DFrame") + local dmenu = DWEP.Menu + if not refresh then + dmenu:SetPos(scrw / 2 - scrw * .25, scrh * 2) + dmenu:MoveTo(scrw / 2 - scrw * .25, scrh / 2 - scrh * .3, .5) + else + dmenu:SetPos(scrw / 2 - scrw * .25, scrh / 2 - scrh * .3) + end + dmenu:SetSize(scrw * .5, scrh * .6) + dmenu:SetTitle("") + dmenu:MakePopup() + dmenu.Paint = function(me,w,h) + surface.SetDrawColor(background) + surface.DrawRect(0,0,w,h) + surface.SetDrawColor(foreground) + surface.DrawRect(0,0,w,h * .05) + draw.SimpleText("DWeapon Editor", "dwep_22", w / 2, h * .025, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + local frameW, frameH = dmenu:GetWide(), dmenu:GetTall() + DWEP.Menu.SearchPanel = vgui.Create("DPanel", dmenu) + DWEP.Menu.SearchPanel.Results = DWEP.Menu.SearchPanel.Results or {} + local searchPanel = DWEP.Menu.SearchPanel + searchPanel:SetPos(0, frameH * .05) + searchPanel:SetSize(frameW, frameH * .95) + local searchData = DWEP.SearchData + searchPanel.Paint = function(me,w,h) + --draw.SimpleText("Weapons: ", "dwep_22", frameW * .05, frameH * .025, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.RoundedBox(8, w / 2 - w * .25, h * .108, w * .5, h * .025, foreground) + if searchData["searchTime"] then + local searchTime = searchData["searchTime"] * 100 + local waitTime = .35 * 100 + draw.RoundedBox(8, w / 2 - w * .25, h * .108, (w * .5) * ( math.Clamp(math.ceil(searchTime - CurTime() * 100) * -1, 0, waitTime) / waitTime), h * .025, theme) + end + --if searchData["results"] then + draw.SimpleText(#searchData["results"] .. " Results", "dwep_20", w / 2, h * .97, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + --end + end + local oldstring = DWEP.SearchData["search"] + local searchBar = vgui.Create("DTextEntry", searchPanel) + searchBar:SetFont("dwep_20") + searchBar:SetPos(frameW * .05, frameH * .05) + searchBar:SetSize(frameW * .9, frameH * .045) + searchBar:SetUpdateOnType(true) + searchBar.Paint = function(me,w,h) + surface.SetDrawColor(foreground) + surface.DrawRect(0,0,w,h) + me:DrawTextEntryText(color_white, theme, theme) + if string.len(me:GetText()) <= 0 then + draw.SimpleText("Search Weapons..", "dwep_20", 5, h / 2, Color(80, 80, 80, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end + searchBar:SetValue(DWEP.SearchData["search"] or "") + function searchBar:OnValueChange(value) + oldstring = DWEP.SearchData["search"] + local forward = false + if #value > #oldstring then + forward = true + end + updateSearch(value, forward) + end + + DWEP.Menu.SearchPanel.ScrollPanel = vgui.Create("DScrollPanel", searchPanel) + local searchScroll = DWEP.Menu.SearchPanel.ScrollPanel + searchScroll:SetPos(frameW * .05, frameH * .135) + searchScroll:SetSize(frameW * .9, frameH * .75) + paintSbar(searchScroll) + + generateSearch() + + +end + + +hook.Add("Think", "DWEP_SearchUpdate", function() + + if DWEP.SearchData["searchTime"] and DWEP.SearchData["searchTime"] + .35 < CurTime() then + refreshResults() + DWEP.SearchData["searchTime"] = nil + end + +end) +local checked = Material("dconfig/checked.png") + + +local function formatWeapon(weaponData) + + local updateData = {} + local function formatWeaponData(tbl, parent) + for k, v in pairs(tbl) do + if type(v) != "function" then + if type(v) == "table" then + formatWeaponData(v, parent .. " | " .. k) + else + updateData[parent .. " | " .. k] = v + end + end + end + + end + + for k,v in pairs(weaponData) do + local valType = type(v) + if valType == "table" then + formatWeaponData(v, k) + elseif valType != "Vector" and valType != "function" then + updateData[k] = v + end + end + return updateData +end + +local function UpdateWeapon(class, data) + + for k,v in pairs(data) do + DWEP.AdjustValue(class, k, v) + end + + net.Start("dwep_update_weapon") + net.WriteTable(data) + net.WriteString(class) + net.SendToServer() + + +end + +local optionButtons = { + ["Delete Data"] = {color = theme, callback = function(class, data) + local weapon = weapons.GetStored(class) + weapon = DWEP.DefaultSweps[class] + net.Start("dwep_reset_weapon") + net.WriteString(class) + net.SendToServer() + DWEP.Menu:Remove() + OpenDWEP(true) + LocalPlayer():ChatPrint("Deleting data requires a server reset for values to return to default.") + LocalPlayer():ChatPrint(class .. " data has been deleted.") + end}, + ["Save"] = {color = theme, callback = function(class, data) UpdateWeapon(class, data) end}, + ["Close"] = {color = foreground, callback = function() DWEP.Menu:Remove() OpenDWEP(true) end}, +} + +local function __genOrderedIndex( t ) + local orderedIndex = {} + for key in pairs(t) do + table.insert( orderedIndex, key ) + end + table.sort( orderedIndex ) + return orderedIndex +end + +local function orderedNext(t, state) + -- Equivalent of the next function, but returns the keys in the alphabetic + -- order. We use a temporary ordered key table that is stored in the + -- table being iterated. + + local key = nil + --print("orderedNext: state = "..tostring(state) ) + if state == nil then + -- the first time, generate the index + t.__orderedIndex = __genOrderedIndex( t ) + key = t.__orderedIndex[1] + else + -- fetch the next value + for i = 1,table.getn(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i+1] + end + end + end + + if key then + return key, t[key] + end + + -- no more value to return, cleanup + t.__orderedIndex = nil + return +end + +local function orderedPairs(t) + -- Equivalent of the pairs() function on tables. Allows to iterate + -- in order + return orderedNext, t, nil +end + +function OpenDWEPWeapon(class) + + local parent = DWEP.Menu + local weapon = weapons.GetStored(class) or nil + local updateData = {} + if weapon and IsValid(parent) then + local drawData = { + ["name"] = weapon.PrintName or "Missing Printname", + } + local searchPanel = DWEP.Menu.SearchPanel + searchPanel:Remove() + DWEP.Menu.WeaponEditor = vgui.Create("DPanel", parent) + local weaponEditor = DWEP.Menu.WeaponEditor + weaponEditor:SetPos(0,parent:GetTall() * .05) + weaponEditor:SetSize(parent:GetWide(), parent:GetTall()) + weaponEditor.Paint = function(me,w,h) + + end + local frameW,frameH = weaponEditor:GetWide(), weaponEditor:GetTall() + + local offset = frameW / 2 - frameW * .4 + local count = -1 + for k,v in pairs(optionButtons) do + count = count + 1 + local optionButton = vgui.Create("DButton", weaponEditor) + optionButton:SetPos(offset + frameW * .2 + (count * weaponEditor:GetWide() * .2) + 10, weaponEditor:GetTall() * .15) + optionButton:SetSize(weaponEditor:GetWide() * .2 , weaponEditor:GetTall() * .05) + optionButton:SetText("") + optionButton.Paint = function(me,w,h) + surface.SetDrawColor(foreground) + surface.DrawRect(0,0,w,h) + draw.SimpleText(k, "dwep_20", w / 2, h /2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if me:IsHovered() then + surface.SetDrawColor(highlight) + surface.DrawRect(0,0,w,h) + end + end + optionButton.DoClick = function() + v.callback(class, updateData) + end + end + local weaponModel = vgui.Create("DModelPanel", weaponEditor) + local model = weapon.WorldModel + + if not model or #model <= 0 then + model = "models/weapons/w_crowbar.mdl" + end + local previewSize = frameW * .4 + weaponModel:SetPos(offset, frameH * .05 ) + weaponModel:SetSize(previewSize * .5, frameH * .15) + weaponModel:SetModel(model) + function weaponModel:LayoutEntity( Entity ) return end + local CamPos = Vector( 15, -6, 60 ) + local LookAt = Vector( 0, 0, 60 ) + weaponModel.Entity:SetPos( weaponModel.Entity:GetPos() + Vector(0,0,-2)) + weaponModel:SetFOV(50) + weaponModel:SetCamPos( CamPos ) + weaponModel:SetLookAt( LookAt ) + local num = .7 + local min, max = weaponModel.Entity:GetRenderBounds() + weaponModel:SetCamPos(min:Distance(max) * Vector(num, num, num)) + weaponModel:SetLookAt((max + min) / 2) + local oldPaint = weaponModel.Paint + weaponModel.Paint = function(me,w,h) + surface.SetDrawColor(foreground) + surface.DrawRect(0,0,w,h) + oldPaint(me,w,h) + draw.SimpleText(string.upper(drawData["name"]), "dwep_18", w / 2, h * .1, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(string.upper(class), "dwep_18", w / 2, h * .9, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + local weaponScroll = vgui.Create("DScrollPanel", weaponEditor) + weaponScroll:SetPos(0, frameH * .22) + weaponScroll:SetSize(frameW, frameH - frameH * .29) + paintSbar(weaponScroll) + + local ypos = 0 + local inputSize = offset * 4 + local inputColor = Color(foreground.r, foreground.g, foreground.b, 150) + local parents = {} + local parentLayer = nil + local weaponData = formatWeapon(weapon) + for k,v in orderedPairs(weaponData) do + local defaultValue = "" + if type(v) == "Vector" or type(v) == "Angle" then continue end + --if type(v) == "table" then updateData[parentLayer][k] = {value = v, changed = true, parent = parentLayer} targetUpdate = updateData[parentLayer][k] end + local configOption = vgui.Create("DPanel", weaponScroll) + configOption:SetPos(offset, ypos) + configOption:SetSize(weaponScroll:GetWide() - offset * 2, 29) + configOption.Paint = function(me,w,h) + surface.SetDrawColor(foreground) + surface.DrawRect(0,0,w - inputSize - 2,h) + me:DrawTextEntryText(color_white, theme, theme) + draw.SimpleText(k .. ":", "dwep_16", 5, h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + if type(v) == "string" or type(v) == "number" then + local configInput = vgui.Create("DTextEntry", weaponScroll) + configInput:SetPos(weaponScroll:GetWide() - offset - inputSize, ypos) + configInput:SetSize(inputSize, 29) + configInput:SetFont("dwep_16") + configInput:SetUpdateOnType(true) + configInput.Paint = function(me,w,h) + surface.SetDrawColor(inputColor) + surface.DrawRect(0,0,w,h) + me:DrawTextEntryText(color_white, theme, theme) + if string.len(me:GetText()) <= 0 and type(defaultValue) == "string" and string.len(defaultValue) > 0 then + draw.SimpleText("Default: " .. defaultValue, "hub_20", 5, h / 2, Color(80, 80, 80, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end + if type(v) == "number" then + configInput:SetNumeric(true) + end + configInput:SetValue(v) + function configInput:OnValueChange(value) + if type(v) == "string" then + value = tostring(value) + else + value = tonumber(value) + end + updateData[k] = value + end + end + if type(v) == "boolean" then + local configInput = vgui.Create("DCheckBox", weaponScroll) + configInput:SetPos(weaponScroll:GetWide() - offset - inputSize, ypos) + configInput:SetSize(29, 29) + configInput.Paint = function(me,w,h) + surface.SetDrawColor(foreground) + surface.DrawRect(0,0,w,h) + if me:GetChecked() then + surface.SetDrawColor(theme) + surface.SetMaterial(checked) + surface.DrawTexturedRect(0,0,w,h) + elseif me:IsHovered() then + surface.SetDrawColor(highlight) + surface.SetMaterial(checked) + surface.DrawTexturedRect(0,0,w,h) + end + + end + + function configInput:OnChange(value) + updateData[k] = value + end + configInput:SetChecked(v) + end + ypos = ypos + 29 * 1.1 + end + end +end + + + +concommand.Add("dwep", OpenDWEP) + + + +--leak by matveicher +--vk group - https://vk.com/gmodffdev +--steam - https://steamcommunity.com/profiles/76561198968457747/ +--ds server - https://discord.gg/V329W7Ce8g +--ds - matveicher diff --git a/garrysmod/addons/fixguns/lua/autorun/server/sv_dwep.lua b/garrysmod/addons/fixguns/lua/autorun/server/sv_dwep.lua new file mode 100644 index 0000000..26b8215 --- /dev/null +++ b/garrysmod/addons/fixguns/lua/autorun/server/sv_dwep.lua @@ -0,0 +1,117 @@ +--leak by matveicher +--vk group - https://vk.com/gmodffdev +--steam - https://steamcommunity.com/profiles/76561198968457747/ +--ds server - https://discord.gg/V329W7Ce8g +--ds - matveicher + + +AddCSLuaFile("autorun/sh_dwep_config.lua") +include("autorun/sh_dwep_config.lua") + +util.AddNetworkString("dwep_update_weapon") +util.AddNetworkString("dwep_reset_weapon") +util.AddNetworkString("dwep_load_weapon") + +DWEP.NetworkData = DWEP.NetworkData or {} +net.Receive("dwep_update_weapon", function(len,ply) + if not DWEP.CanDWEP(ply) then return end + local updateData = net.ReadTable() + local class = net.ReadString() + for k,v in pairs(updateData) do + DWEP.AdjustValue(class, k, v) + DWEP.NetworkData[class] = DWEP.NetworkData[class] or {} + DWEP.NetworkData[class][k] = v + end + print(ply:Name() .. " has updated the variables of: " .. class) + ply:ChatPrint(string.upper(class) .. " has been successfully updated!") + for k, v in pairs(player.GetAll()) do + if v:HasWeapon(class) then + v:StripWeapon(class) + v:Give(class) + v:SelectWeapon(class) + end + end + + DWEP.SaveData(class) + for k,v in pairs(player.GetAll()) do + if v != ply then + DWEP.SendData(v) + end + end + +end) + +net.Receive("dwep_reset_weapon", function(len, ply) + + if not DWEP.CanDWEP(ply) then return end + local class = net.ReadString() + local weapon = weapons.GetStored(class) + weapon = DWEP.DefaultSweps[class] + DWEP.DeleteData(class) + for k, v in pairs(player.GetAll()) do + if v:HasWeapon(class) then + v:StripWeapon(class) + v:Give(class) + v:SelectWeapon(class) + end + end + + +end) + + +--Everything below is database related. + + +function DWEP.SaveData(class) + if not file.Exists("dwep", "DATA") then file.CreateDir("dwep") end + file.Write("dwep/" .. class .. ".txt", util.TableToJSON(DWEP.NetworkData[class])) + print("Saved DWEP data: dwep/" .. class) +end + +function DWEP.DeleteData(class) + file.Delete("dwep/" .. class .. ".txt") +end + + +function DWEP.LoadData(class) + if not file.Exists("dwep/" .. class .. ".txt", "DATA") then return end + local saveData = util.JSONToTable(file.Read("dwep/" .. class .. ".txt"), "DATA") + if saveData then + print("Loading Save Data: ") + PrintTable(saveData) + for k,v in pairs(saveData) do + DWEP.AdjustValue(class, k, v) + end + hook.Run("DWeaponsValueUpdated", class, savaData) + DWEP.NetworkData[class] = saveData + end +end + +function DWEP.SendData(ply) + for k,v in pairs(DWEP.NetworkData) do + if file.Exists("dwep/" .. k .. ".txt", "DATA") then + net.Start("dwep_load_weapon") + net.WriteTable(v) + net.WriteString(k) + if IsValid(ply) then + net.Send(ply) + else + net.Broadcast() + end + end + end +end + +net.Receive("dwep_load_weapon", function(len, ply) + + DWEP.SendData(ply) + +end) + + +--leak by matveicher +--vk group - https://vk.com/gmodffdev +--steam - https://steamcommunity.com/profiles/76561198968457747/ +--ds server - https://discord.gg/V329W7Ce8g +--ds - matveicher diff --git a/garrysmod/addons/fixguns/lua/autorun/sh_dwep_config.lua b/garrysmod/addons/fixguns/lua/autorun/sh_dwep_config.lua new file mode 100644 index 0000000..0ecf4ac --- /dev/null +++ b/garrysmod/addons/fixguns/lua/autorun/sh_dwep_config.lua @@ -0,0 +1,100 @@ +--leak by matveicher +--vk group - https://vk.com/gmodffdev +--steam - https://steamcommunity.com/profiles/76561198968457747/ +--ds server - https://discord.gg/V329W7Ce8g +--ds - matveicher + + +DWEP = DWEP or {} +DWEP.Config = {} +DWEP.Config.Colors ={ +["background"]= Color(38, 38, 44, 255), +["foreground"]= Color(28, 28, 34, 255), +["inactiveClr"] = Color(68, 68, 68, 255), +["theme"] = Color(200,103,235), +} + +DWEP.Config.AccessGroups = { + "superadmin", + "owner", + "curator", + "spec admin", +} + +DWEP.Config.AccessSteamID = { + "", + "", +} + + +--No configuration below this line: DO NOT TOUCH + +DWEP.Sweps = DWEP.Sweps or {} +DWEP.DefaultSweps = DWEP.DefaultSweps or {} + +function DWEP.CanDWEP(ply) + + return table.HasValue(DWEP.Config.AccessGroups, ply:GetUserGroup()) or table.HasValue( DWEP.Config.AccessSteamID,ply:SteamID()) + +end +function DWEP.AdjustValue(weapon, key, value) + local parents = string.Explode(" | ", key) + local curTable + local weapon = weapons.GetStored(weapon) + if #parents > 1 then + for i = 1, #parents do + if i != 1 and i < #parents then + curTable[parents[i]] = curTable[parents[i]] or {} + curTable = curTable[parents[i]] + elseif i == 1 then + weapon[parents[1]] = weapon[parents[1]] or {} + curTable = weapon[parents[1]] + elseif i == #parents then + curTable[parents[i]] = value + end + end + else + weapon[parents[1]] = value + end +end + +function DWEP.CopyTable(obj, seen) + -- Handle non-tables and previously-seen tables. + if type(obj) ~= 'table' then return obj end + if seen and seen[obj] then return seen[obj] end + + -- New table; mark it as seen an copy recursively. + local s = seen or {} + local res = setmetatable({}, getmetatable(obj)) + s[obj] = res + for k, v in pairs(obj) do res[DWEP.CopyTable(k, s)] = DWEP.CopyTable(v, s) end + return res +end + + +hook.Add("InitPostEntity", "InitializeDWEP", function() + + if #DWEP.Sweps <= 0 then + for k,v in pairs(weapons.GetList()) do + if v.ClassName then + DWEP.Sweps[#DWEP.Sweps + 1 or 1] = v.ClassName + DWEP.DefaultSweps[v.ClassName] = DWEP.CopyTable(v) + end + end + end + + if SERVER then + for k,v in pairs(weapons.GetList()) do + DWEP.LoadData(v.ClassName) + end + end + +end) + +--PrintTable(DWEP.DefaultSweps["cw_p99"]) + +--leak by matveicher +--vk group - https://vk.com/gmodffdev +--steam - https://steamcommunity.com/profiles/76561198968457747/ +--ds server - https://discord.gg/V329W7Ce8g +--ds - matveicher diff --git a/garrysmod/addons/ft_bulletcontusion/lua/autorun/client/bullet_client.lua b/garrysmod/addons/ft_bulletcontusion/lua/autorun/client/bullet_client.lua new file mode 100644 index 0000000..3888d1c --- /dev/null +++ b/garrysmod/addons/ft_bulletcontusion/lua/autorun/client/bullet_client.lua @@ -0,0 +1,70 @@ +----------------------------------------- +-- FT BULLET CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- + +local function AddSuppression(amount) + net.Start("FT_AddSuppression") + net.WriteFloat(amount) + net.SendToServer() +end + +local punchVel = Angle() +local punchAng = Angle() + +hook.Add("Think", "FT_CLViewPunchThink", function() + local FT = FrameTime() + + punchAng = punchAng + punchVel * FT + punchVel = punchVel * (1 - 10 * FT) + punchVel = punchVel - punchAng * math.Clamp(600 * FT, 0, 2) +end) + +hook.Add("CalcView", "FT_CLViewPunchCalc", function(ply, pos, ang) + ang:Add(punchAng) +end) + +local function FT_ViewPunch(a) + punchVel:Add(a) +end + +if not ConVarExists("suppression_vignette_size") then + CreateConVar("suppression_vignette_size", 1, FCVAR_ARCHIVE) +end + +local function PlayFlybySounds(pos) + sound.Play("bul_snap/supersonic_snap_" .. math.random(1,18) .. ".wav", pos, 75, 100, 1) + sound.Play("bul_flyby/subsonic_" .. math.random(1,27) .. ".wav", pos, 75, 100, 1) +end + +local vignette = Material("vignette/vignette.png") + +hook.Add("RenderScreenspaceEffects", "FT_SuppressionVignette", function() + local a = LocalPlayer():GetNWFloat("FT_Suppression", 0) + if a <= 0 then return end + + surface.SetDrawColor(255, 255, 255, a * 200) + surface.SetMaterial(vignette) + surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) +end) + +net.Receive("BulletCloseToPlayer", function() + local soundPath = net.ReadString() + surface.PlaySound(soundPath) + + local pos = LocalPlayer():EyePos() + + sound.Play("bul_snap/supersonic_snap_" .. math.random(1,18) .. ".wav", pos, 75, 100, 0.01) + sound.Play("bul_flyby/subsonic_" .. math.random(1,27) .. ".wav", pos, 75, 100, 0.01) + + AddSuppression(0.25) + + FT_ViewPunch(Angle(math.Rand(-1,1), math.Rand(-1,1), 0)) + + util.ScreenShake(pos, 1, 1, 1.5, 200) +end) + +----------------------------------------- +-- FT BULLET CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/ft_bulletcontusion/lua/autorun/server/bullet_server.lua b/garrysmod/addons/ft_bulletcontusion/lua/autorun/server/bullet_server.lua new file mode 100644 index 0000000..7da3965 --- /dev/null +++ b/garrysmod/addons/ft_bulletcontusion/lua/autorun/server/bullet_server.lua @@ -0,0 +1,288 @@ +----------------------------------------- +-- FT BULLET CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +util.AddNetworkString("BulletCloseToPlayer") +util.AddNetworkString("FT_AddSuppression") + +local function DistanceToSegment(p, a, b) + local ap = p - a + local ab = b - a + + local t = ap:Dot(ab) / ab:Dot(ab) + t = math.Clamp(t, 0, 1) + + local closest = a + ab * t + return p:Distance(closest) +end + +local function WeaponHasAmmo(wep) + if not IsValid(wep) then return false end + + if wep:Clip1() and wep:Clip1() > 0 then return true end + if wep:Ammo1() and wep:Ammo1() > 0 then return true end + + return false +end + +local function IsInsideVehicleOrPod(ply) + local veh = ply:GetVehicle() + if IsValid(veh) then + local class = veh:GetClass() + if string.StartWith(class, "lvs_") then return true end + if string.StartWith(class, "sw_") then return true end + end + + local parent = ply:GetParent() + if IsValid(parent) then + local class = parent:GetClass() + if string.StartWith(class, "lvs_") then return true end + if string.StartWith(class, "sw_") then return true end + end + + local seat = ply:GetVehicle() + if IsValid(seat) then + local parent2 = seat:GetParent() + if IsValid(parent2) then + local class = parent2:GetClass() + if string.StartWith(class, "lvs_") then return true end + if string.StartWith(class, "sw_") then return true end + end + end + + return false +end + +local BLACKLIST_WEAPONS = { + ["gmod_tool"] = true, + ["tacrp_knife"] = true, + ["tacrp_knife2"] = true, + ["weapon_physgun"] = true, + ["weapon_physcannon"] = true, + ["ix_hands"] = true, + ["v92_bf2_medikit"] = true +} + +local function ProcessBullet(entity, src, dir) + if not src or not dir then return end + if not IsValid(entity) then return end + + if entity:IsPlayer() then + local wep = entity:GetActiveWeapon() + if IsValid(wep) and BLACKLIST_WEAPONS[wep:GetClass()] then + return + end + end + + local dirVec = Vector(dir.x, dir.y, dir.z) + if dirVec:IsZero() then return end + dirVec:Normalize() + + local bulletStart = src + local bulletEnd = src + dirVec * 10000 + + for _, ply in ipairs(player.GetAll()) do + if ply == entity then continue end + if not IsValid(ply) or not ply:Alive() then continue end + + if IsInsideVehicleOrPod(ply) then + continue + end + + local headPos = ply:EyePos() + local dist = DistanceToSegment(headPos, bulletStart, bulletEnd) + if not dist then continue end + + if dist < 120 then + net.Start("BulletCloseToPlayer") + net.WriteString("bullet_contusion.mp3") + net.Send(ply) + end + end +end + +hook.Add("EntityEmitSound", "FT_PlayerShootDetect", function(data) + local ent = data.Entity + if not IsValid(ent) then return end + if not ent:IsPlayer() then return end + + local wep = ent:GetActiveWeapon() + if not IsValid(wep) then return end + + local snd = string.lower(data.SoundName) + if snd:find("dry") or snd:find("empty") then return end + + if not string.find(string.lower(data.SoundName), "shoot") + and not string.find(string.lower(data.SoundName), "fire") + and not string.find(string.lower(data.SoundName), "shot") then + return + end + + local src = ent:GetShootPos() + local dir = ent:GetAimVector() + + ProcessBullet(ent, src, dir) +end) + +local RPG_CLASSES = { + ["rpg26_rocket"] = true, + ["rpg28_rocket"] = true, + ["at4_rocket"] = true, + ["panzerfaust3_rocket"] = true, + ["tfa_panzerfaust3_rocket"] = true +} + +hook.Add("OnEntityCreated", "FT_RPG_ShootDetect", function(ent) + if not IsValid(ent) then return end + if not RPG_CLASSES[ent:GetClass()] then return end + + timer.Simple(0, function() + if not IsValid(ent) then return end + local owner = ent.Owner or ent:GetOwner() + if not IsValid(owner) or not owner:IsPlayer() then return end + + ProcessBullet(owner, owner:GetShootPos(), owner:GetAimVector()) + end) +end) + +net.Receive("FT_AddSuppression", function(len, ply) + local amount = net.ReadFloat() + local cur = ply:GetNWFloat("FT_Suppression", 0) + ply:SetNWFloat("FT_Suppression", math.Clamp(cur + amount, 0, 1)) +end) + +hook.Add("PreRegisterSWEP", "FT_HookTFA_Shoot", function(class, tbl) + if not tbl or not tbl.IsTFAWeapon then return end + + local oldShoot = tbl.ShootBulletInformation or tbl.ShootBullet or tbl.PrimaryAttack + if not oldShoot then return end + if tbl.FT_Hooked_TFA then return end + tbl.FT_Hooked_TFA = true + + tbl.ShootBulletInformation = function(self, ...) + local owner = self:GetOwner() + if IsValid(owner) and owner:IsPlayer() then + if WeaponHasAmmo(self) then + ProcessBullet(owner, owner:GetShootPos(), owner:GetAimVector()) + end + end + + return oldShoot(self, ...) + end +end) + +hook.Add("PreRegisterSWEP", "FT_HookTacRP_Shoot", function(class, tbl) + if not tbl or not tbl.Base or tbl.Base ~= "tacrp_base" then return end + + local oldShoot = tbl.ShootBullet or tbl.PrimaryAttack + if not oldShoot then return end + if tbl.FT_Hooked_TacRP then return end + tbl.FT_Hooked_TacRP = true + + tbl.ShootBullet = function(self, ...) + local owner = self:GetOwner() + if IsValid(owner) and owner:IsPlayer() then + if WeaponHasAmmo(self) then + ProcessBullet(owner, owner:GetShootPos(), owner:GetAimVector()) + end + end + + return oldShoot(self, ...) + end +end) + +hook.Add("LVS.DoFireBullet", "FT_LVS_TankBulletTrack", function(weapon, bullet) + if not bullet or not bullet.Attacker then return end + local attacker = bullet.Attacker + if not IsValid(attacker) then return end + + local src = bullet.Src + local dir = bullet.Dir + + if not src or not dir then return end + + ProcessBullet(attacker, src, dir) +end) + +hook.Add("lvs_bullet_hit", "FT_LVS_BulletHitTrack", function(data) + local shooter = data.Attacker + if not IsValid(shooter) then return end + + local src = data.StartPos + local dir = data.Dir + + ProcessBullet(shooter, src, dir) +end) + +hook.Add("EntityFireBullets", "FT_BulletTrack", function(ent, data) + ProcessBullet(ent, data.Src, data.Dir) +end) + +hook.Add("TFA_Bullet", "FT_TFA_BulletTrack", function(ent, data) + if not IsValid(ent) then return end + ProcessBullet(ent, data.src, data.dir) +end) + +hook.Add("TacRP_FireBullet", "FT_TACRP_BulletTrack", function(wep, data) + local owner = wep:GetOwner() + if not IsValid(owner) then return end + ProcessBullet(owner, data.Src, data.Dir) +end) + +hook.Add("ARC9_FireBullets", "FT_ARC9_BulletTrack", function(wep, data) + local owner = wep:GetOwner() + if not IsValid(owner) then return end + ProcessBullet(owner, data.Src, data.Dir) +end) + +hook.Add("TFA_Bullet", "FT_TFA_BASE_Suppression", function(ent, data) + local owner = IsValid(ent) and ent:GetOwner() + if not IsValid(owner) or not owner:IsPlayer() then return end + + local src = data.src or owner:GetShootPos() + local dir = data.dir or owner:GetAimVector() + + ProcessBullet(owner, src, dir) +end) + +hook.Add("TFA_Throwing", "FT_TFA_NADE_Suppression", function(wep, nade) + local owner = IsValid(wep) and wep:GetOwner() + if not IsValid(owner) or not owner:IsPlayer() then return end + + local src = owner:GetShootPos() + local dir = owner:GetAimVector() + + ProcessBullet(owner, src, dir) +end) + +hook.Add("TFA_PostPrimaryAttack", "FT_TFA_AllWeapons_Suppression", function(wep) + if not IsValid(wep) then return end + if not wep.IsTFAWeapon then return end + + local owner = wep:GetOwner() + if not IsValid(owner) or not owner:IsPlayer() then return end + + local src = owner:GetShootPos() + local dir = owner:GetAimVector() + + ProcessBullet(owner, src, dir) +end) + +timer.Create("FT_SuppressionDecay", 0.1, 0, function() + for _, ply in ipairs(player.GetAll()) do + if not IsValid(ply) then continue end + + local v = ply:GetNWFloat("FT_Suppression", 0) + if v > 0 then + ply:SetNWFloat("FT_Suppression", math.max(v - 0.03, 0)) + end + end +end) + +hook.Add("EntityFireBulletsCallback", "FT_BulletTrackCallback", function(ent, data) + ProcessBullet(ent, data.Src, data.Dir) +end) +----------------------------------------- +-- FT BULLET CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/ft_bulletcontusion/lua/postprocess/supp_vignette.lua b/garrysmod/addons/ft_bulletcontusion/lua/postprocess/supp_vignette.lua new file mode 100644 index 0000000..278894a --- /dev/null +++ b/garrysmod/addons/ft_bulletcontusion/lua/postprocess/supp_vignette.lua @@ -0,0 +1,28 @@ +local m = Material("vignette/vignette.png") +local m_w = Material("vignette/vignette_white.png") +local alpha_saved = 255 +local alphanew = 0 + +if not ConVarExists("suppression_vignette_size") then + CreateConVar("suppression_vignette_size", 1, FCVAR_ARCHIVE) +end + +hook.Add("RenderScreenspaceEffects", "supp_vignette", function() + + if not render.SupportsPixelShaders_2_0() then return end + + local amt = LocalPlayer():GetNWFloat("FT_Suppression", 0) + alphanew = Lerp(6 * FrameTime(), alphanew, amt) + + local a = alphanew + + render.SetMaterial(m) + m:SetFloat("$alpha", a) + + local cvar = GetConVar("suppression_vignette_size") + local size = cvar and cvar:GetFloat() or 1 + + for i = 1, 4 * size do + render.DrawScreenQuad() + end +end) diff --git a/garrysmod/addons/ft_contusion/lua/autorun/client/explosioncontusion_client.lua b/garrysmod/addons/ft_contusion/lua/autorun/client/explosioncontusion_client.lua new file mode 100644 index 0000000..3976cd8 --- /dev/null +++ b/garrysmod/addons/ft_contusion/lua/autorun/client/explosioncontusion_client.lua @@ -0,0 +1,129 @@ +----------------------------------------- +-- FT CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +local concussionActive = false +local lastConcussionSound = 0 +local function ApplyShake(endTime) + local ply = LocalPlayer() + if not ply or not ply:IsValid() then return end + if not ply:Alive() then return end + + local remainingTime = endTime - CurTime() + local intensity = math.Clamp(remainingTime / 11, 0, 1) + + local randAng = + Angle(math.Rand(-1, 1), math.Rand(-1, 1), math.Rand(-1, 1)) * intensity +end + +net.Receive("StartConcussionEffect", function() + local effectDuration = 15 + local endTime = CurTime() + effectDuration + + if concussionActive then + hook.Remove("HUDPaint", "DarkenScreenEffect") + hook.Remove("RenderScreenspaceEffects", "BlurScreenEffect") + hook.Remove("CalcView", "ConcussionCameraShake") + timer.Remove("ShakeEffectTimer") + timer.Remove("ConcussionEnd") + end + + concussionActive = true + + if CurTime() - lastConcussionSound > 10 then + surface.PlaySound("explosion_contusion.mp3") + lastConcussionSound = CurTime() + end + + hook.Add("HUDPaint", "DarkenScreenEffect", function() + local ply = LocalPlayer() + if not IsValid(ply) or not ply:Alive() then return end + local alpha = math.Clamp((endTime - CurTime()) / effectDuration * 225, 0, 225) + draw.RoundedBox(0, 0, 0, ScrW(), ScrH(), Color(0, 0, 0, alpha)) + end) + + hook.Add("RenderScreenspaceEffects", "BlurScreenEffect", function() + local ply = LocalPlayer() + if not IsValid(ply) or not ply:Alive() then return end + local blur = (math.sin(CurTime() * 3) * 5 + 5) * 5 * + math.Clamp((endTime - CurTime()) / effectDuration, 0, 1) + DrawMotionBlur(0.1, blur, 0.05) + end) + + timer.Create("ShakeEffectTimer", 0.05, effectDuration * 20, function() + ApplyShake(endTime) + end) + + timer.Create("ConcussionEnd", effectDuration, 1, function() + concussionActive = false + + hook.Remove("HUDPaint", "DarkenScreenEffect") + hook.Remove("RenderScreenspaceEffects", "BlurScreenEffect") + hook.Remove("CalcView", "ConcussionCameraShake") + timer.Remove("ShakeEffectTimer") + + if not IsValid(LocalPlayer()) then return end + + local gasps = { + "gasp/focus_gasp_01.wav", + "gasp/focus_gasp_02.wav", + "gasp/focus_gasp_03.wav", + "gasp/focus_gasp_04.wav", + "gasp/focus_gasp_05.wav", + "gasp/focus_gasp_06.wav" + } + + surface.PlaySound(table.Random(gasps)) + end) +end) + +local originalWalk, originalRun = nil, nil +local slowEnd = 0 + +net.Receive("ConcussionSlowdown", function() + local mult = net.ReadFloat() + local duration = net.ReadFloat() + local ply = LocalPlayer() + + if not IsValid(ply) then return end + + if not originalWalk then + originalWalk = ply:GetWalkSpeed() + originalRun = ply:GetRunSpeed() + end + + ply:SetWalkSpeed(originalWalk * mult) + ply:SetRunSpeed(originalRun * mult) + + slowEnd = CurTime() + duration + + timer.Create("ConcussionRestoreSpeed", duration, 1, function() + if IsValid(ply) then + ply:SetWalkSpeed(originalWalk) + ply:SetRunSpeed(originalRun) + end + end) +end) + +hook.Add("PlayerSpawn", "Concussion_ResetOnRespawn", function(ply) + if ply ~= LocalPlayer() then return end + + concussionActive = false + + hook.Remove("HUDPaint", "DarkenScreenEffect") + hook.Remove("RenderScreenspaceEffects", "BlurScreenEffect") + hook.Remove("CalcView", "ConcussionCameraShake") + + timer.Remove("ShakeEffectTimer") + timer.Remove("ConcussionEnd") + + if originalWalk and originalRun then + ply:SetWalkSpeed(originalWalk) + ply:SetRunSpeed(originalRun) + end +end) + +----------------------------------------- +-- FT CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/ft_contusion/lua/autorun/server/explosioncontusion_server.lua b/garrysmod/addons/ft_contusion/lua/autorun/server/explosioncontusion_server.lua new file mode 100644 index 0000000..73b0cfa --- /dev/null +++ b/garrysmod/addons/ft_contusion/lua/autorun/server/explosioncontusion_server.lua @@ -0,0 +1,153 @@ +----------------------------------------- +-- FT CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +util.AddNetworkString("StartConcussionEffect") +util.AddNetworkString("ConcussionSlowdown") + +local EFFECT_DURATION = 10 +local SLOW_MULT = 0.45 + +local RADIUS_HL2 = 550 +local RADIUS_LVS = 550 +local RADIUS_SWBOMB = 900 + +local function ApplyConcussion(ply) + if not IsValid(ply) or not ply:Alive() then return end + + -- Не применяем контузию, если игрок в обычном транспорте или в технике LVS + if ply:InVehicle() then return end + if ply.lvsGetVehicle and IsValid(ply:lvsGetVehicle()) then return end + + net.Start("StartConcussionEffect") + net.Send(ply) + + net.Start("ConcussionSlowdown") + net.WriteFloat(SLOW_MULT) + net.WriteFloat(EFFECT_DURATION) + net.Send(ply) +end + +local function IsTFABombProjectileClass(cls) + if not cls or cls == "" then return false end + return string.StartWith(cls, "tfa_bomb") +end + +hook.Add("EntityTakeDamage", "Concussion_TFA_Damage", function(target, dmginfo) + if not target:IsPlayer() then return end + + local attacker = dmginfo:GetAttacker() + local inflictor = dmginfo:GetInflictor() + + local aclass = IsValid(attacker) and attacker:GetClass() or "" + local iclass = IsValid(inflictor) and inflictor:GetClass() or "" + + if IsTFABombProjectileClass(aclass) or IsTFABombProjectileClass(iclass) then + ApplyConcussion(target) + return + end + + if IsValid(attacker) and attacker:IsPlayer() then + local wep = attacker:GetActiveWeapon() + if IsValid(wep) and wep.IsTFADOINade then + ApplyConcussion(target) + return + end + end +end) + +hook.Add("EntityRemoved", "Concussion_TFA_Radius", function(ent) + if not IsValid(ent) then return end + + local cls = ent:GetClass() + if not IsTFABombProjectileClass(cls) then return end + + local pos = ent:GetPos() + + for _, ply in ipairs(player.GetAll()) do + if ply:GetPos():Distance(pos) <= RADIUS_HL2 then + ApplyConcussion(ply) + end + end +end) + +hook.Add("EntityTakeDamage", "Concussion_HL2", function(target, dmginfo) + if not target:IsPlayer() then return end + if not dmginfo:IsExplosionDamage() then return end + + ApplyConcussion(target) +end) + +hook.Add("EntityTakeDamage", "Concussion_LVS_Damage", function(target, dmginfo) + if not target:IsPlayer() then return end + + local attacker = dmginfo:GetAttacker() + if not IsValid(attacker) then return end + + local class = attacker:GetClass() + if class == "lvs_item_explosive" or string.find(class, "lvs_item_explosive") then + attacker._DidExplode = true + ApplyConcussion(target) + end +end) + +hook.Add("EntityRemoved", "Concussion_LVS_Radius", function(ent) + if not IsValid(ent) then return end + + local class = ent:GetClass() + if class ~= "lvs_item_explosive" and not string.find(class, "lvs_item_explosive") then return end + if not ent._DidExplode then return end + + local pos = ent:GetPos() + + for _, ply in ipairs(player.GetAll()) do + if ply:GetPos():Distance(pos) <= RADIUS_LVS then + ApplyConcussion(ply) + end + end +end) + +hook.Add("OnEntityCreated", "SWBombV3_ExplosionDetector", function(ent) + if not IsValid(ent) then return end + + if ent:GetClass() == "swbomb_explosion" then + local pos = ent:GetPos() + + for _, bomb in ipairs(ents.FindInSphere(pos, 200)) do + if bomb.SWBombV3 then + bomb._DidExplode = true + end + end + end +end) + +hook.Add("EntityTakeDamage", "Concussion_SWBomb_Damage", function(target, dmginfo) + if not target:IsPlayer() then return end + + local attacker = dmginfo:GetAttacker() + if not IsValid(attacker) then return end + + if attacker.SWBombV3 then + attacker._DidExplode = true + ApplyConcussion(target) + end +end) + +hook.Add("EntityRemoved", "Concussion_SWBomb_Radius", function(ent) + if not IsValid(ent) then return end + if not ent.SWBombV3 then return end + if not ent.Exploded then return end + + local pos = ent:GetPos() + + for _, ply in ipairs(player.GetAll()) do + if ply:GetPos():Distance(pos) <= RADIUS_SWBOMB then + ApplyConcussion(ply) + end + end +end) + +----------------------------------------- +-- FT CONTUSION (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/ft_drones/lua/autorun/client/cl_drone_setup.lua b/garrysmod/addons/ft_drones/lua/autorun/client/cl_drone_setup.lua new file mode 100644 index 0000000..5e31207 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/autorun/client/cl_drone_setup.lua @@ -0,0 +1,72 @@ +if SERVER then return end + +function We(x) + return x / 1920 * ScrW() +end + +function He(y) + return y / 1080 * ScrH() +end + +local function CreateFonts() + surface.CreateFont("MuR_FontDef", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(12), + antialias = true + }) + + surface.CreateFont("MuR_Font0", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(12), + antialias = true + }) + + surface.CreateFont("MuR_Font1", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(16), + antialias = true + }) + + surface.CreateFont("MuR_Font2", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(24), + antialias = true + }) + + surface.CreateFont("MuR_Font3", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(32), + antialias = true + }) + + surface.CreateFont("MuR_Font4", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(40), + antialias = true + }) + + surface.CreateFont("MuR_Font5", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(48), + antialias = true + }) + + surface.CreateFont("MuR_Font6", { + font = "VK Sans Display DemiBold", + extended = true, + size = He(56), + antialias = true + }) +end + +CreateFonts() +hook.Add("OnScreenSizeChanged", "MuR_Drone_Fonts", function() + CreateFonts() +end) diff --git a/garrysmod/addons/ft_drones/lua/autorun/mur_drone_fx.lua b/garrysmod/addons/ft_drones/lua/autorun/mur_drone_fx.lua new file mode 100644 index 0000000..b456a6d --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/autorun/mur_drone_fx.lua @@ -0,0 +1,49 @@ +if CLIENT then + +function DrawHeavySignalNoise(strength) + if not strength then return end + if strength > 0.9 then return end + + local sw, sh = ScrW(), ScrH() + + local noiseCount = (1 - strength) * 400 + for i = 1, noiseCount do + surface.SetDrawColor(255, 255, 255, math.random(10, 60)) + surface.DrawRect(math.random(0, sw), math.random(0, sh), 1, 1) + end + + local lineCount = (1 - strength) * 60 + for i = 1, lineCount do + local y = math.random(0, sh) + local w = math.random(sw * 0.2, sw) + local x = math.random(0, sw - w) + surface.SetDrawColor(255, 255, 255, math.random(20, 80)) + surface.DrawRect(x, y, w, math.random(1, 3)) + end + + if strength < 0.4 then + local shift = math.random(-8, 8) * (1 - strength) + surface.SetDrawColor(255, 255, 255, 40) + surface.DrawRect(shift, 0, sw, sh) + end + + if strength < 0.3 then + for i = 1, 10 do + local x = math.random(0, sw) + surface.SetDrawColor(255, 255, 255, math.random(30, 80)) + surface.DrawRect(x, 0, math.random(2, 6), sh) + end + end + + if strength < 0.2 then + local shake = (0.2 - strength) * 6 + surface.SetDrawColor(255, 255, 255, 25) + surface.DrawRect( + math.random(-shake, shake), + math.random(-shake, shake), + sw, sh + ) + end +end + +end diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/cl_init.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/init.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/init.lua new file mode 100644 index 0000000..fb76da3 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/init.lua @@ -0,0 +1,763 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("MuR_DroneCam") +util.AddNetworkString("MuR_DroneLost") +util.AddNetworkString("MuR_DroneEmergency") -- экстренное самоуничтожение + +local rotorSounds = { + "ambient/machines/spin_loop.wav", + "ambient/machines/machine1_hit1.wav" +} + +local damageSounds = { + "physics/metal/metal_box_impact_bullet1.wav", + "physics/metal/metal_box_impact_bullet2.wav", + "physics/metal/metal_box_impact_bullet3.wav", + "physics/metal/metal_computer_impact_bullet1.wav", + "physics/metal/metal_computer_impact_bullet2.wav" +} + +local startupSounds = { + "buttons/button9.wav", + "buttons/button17.wav" +} + +function ENT:Initialize() + self:SetModel("models/murdered/weapons/drone_ex.mdl") + self:ResetSequence("idle") + self:SetHealth(self.HealthCount) + self:SetGravity(0) + self.Velo = {x = 0, y = 0, z = 0} + + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + + self.MultSpeed = 900 + self.MaxSpeed = 1000 + self.BoostSpeed = 1500 + self.MaxPitch = 18 + self.MaxRoll = 22 + self.LinearDrag = 0.04 + self.GrenadeDelay = CurTime() + 5 + self.TakeDamageWall = 0 + self.DeltaTime = 0 + self.LastBoostSound = 0 + self.LastWindSound = 0 + + -- самоуничтожение + self.EmergencyKill = false + self.EmergencyKillTime = 0 + self._Exploding = false + self:SetNW2Float("EmergencyCountdown", 0) + + self:SetNW2Float("RemoveTime", CurTime() + self.BatteryCount) + + self:EmitSound(startupSounds[math.random(#startupSounds)], 60, 100, 0.5) + + local ply = self:GetCreator() + if IsValid(ply) then + local forward = ply:GetForward() + forward.z = 0 + forward = forward:GetNormalized() + + local spawnDist = 80 + local spawnHeight = 40 + + local spawnPos = ply:GetPos() + forward * spawnDist + Vector(0, 0, spawnHeight) + + local tr = util.TraceHull({ + start = spawnPos, + endpos = spawnPos, + mins = Vector(-20, -20, -20), + maxs = Vector(20, 20, 20), + filter = {ply, self} + }) + + if tr.Hit then + spawnPos = spawnPos + forward * 60 + end + + local ground = util.TraceLine({ + start = spawnPos, + endpos = spawnPos - Vector(0, 0, 5000), + filter = self + }) + + if spawnPos.z < ground.HitPos.z + 30 then + spawnPos.z = ground.HitPos.z + 30 + end + + self:SetPos(spawnPos) + self:SetAngles(Angle(0, ply:EyeAngles().y, 0)) + end + + -- визуальная ПГ-26 под дроном + local rocket = ents.Create("prop_dynamic") + if IsValid(rocket) then + rocket:SetModel("models/sw/rus/rockets/pg26.mdl") + rocket:SetParent(self) + rocket:SetLocalPos(Vector(-15, 0, 2)) + rocket:SetLocalAngles(Angle(0, 0, 0)) + rocket:Spawn() + rocket:SetMoveType(MOVETYPE_NONE) + rocket:SetSolid(SOLID_NONE) + rocket:PhysicsDestroy() + self.RocketModel = rocket + end + + if CreateSound then + self.RotorSound = CreateSound(self, rotorSounds[1]) + if self.RotorSound then + self.RotorSound:PlayEx(0.2, 80) + end + + self.WindSound = CreateSound(self, "ambient/wind/wind_snippet2.wav") + if self.WindSound then + self.WindSound:PlayEx(0, 100) + end + end + + timer.Simple(0.1, function() + if not IsValid(self) then return end + local ply2 = self:GetCreator() + if not IsValid(ply2) then return end + + ply2.ControlDrone = self + + net.Start("MuR_DroneCam") + net.WriteEntity(self) + net.Send(ply2) + end) +end + +function ENT:OnRemove() + local ply = self:GetCreator() + if IsValid(ply) and ply.ControlDrone == self then + ply.ControlDrone = nil + end + + if self.RotorSound then self.RotorSound:Stop() end + if self.WindSound then self.WindSound:Stop() end + self:StopSound("ambient/machines/spin_loop.wav") + self:StopSound("ambient/wind/wind_snippet2.wav") + self:StopSound("vehicles/airboat/fan_blade_fullthrottle_loop1.wav") +end + +function ENT:GetForwardAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Forward() +end + +function ENT:GetRightAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Right() +end + +function ENT:GetUpAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Up() +end + +function ENT:Think() + -- самоуничтожение + if self.EmergencyKill then + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + + if CurTime() >= (self.EmergencyKillTime or 0) then + self:Explode() + return true + end + end + + -- Ограничение дистанции как у spy-дрона + local ply = self:GetCreator() + if IsValid(ply) then + local maxDist = 12000 * 12000 -- 12к юнитов + local dist2 = ply:GetPos():DistToSqr(self:GetPos()) + + if dist2 > maxDist then + net.Start("MuR_DroneLost") + net.Send(ply) + + if ply.ControlDrone == self then + ply.ControlDrone = nil + end + + self:Explode() + return true + end + end + + -- === РЭБ (глушилка) === + if self:GetNWBool("Jammed") then + + -- если впервые попали в РЭБ — запускаем таймер + if not self.JamStart then + self.JamStart = CurTime() + self.JamEnd = CurTime() + 5 -- 5 секунд на выход + end + + -- ВАЖНО: управление НЕ отключаем + -- ВАЖНО: скорость НЕ сбрасываем + -- Дрон продолжает лететь как обычно + + -- если время вышло — взрываем + if CurTime() >= self.JamEnd then + local ply = self:GetCreator() + if IsValid(ply) then + net.Start("MuR_DroneLost") + net.Send(ply) + if ply.ControlDrone == self then + ply.ControlDrone = nil + end + end + + self:Explode() + return true + end + + else + -- если вышли из РЭБ — сбрасываем таймер + self.JamStart = nil + self.JamEnd = nil + end + + + + local ply = self:GetCreator() + if not IsValid(ply) then + self:Remove() + return + end + + local phys = self:GetPhysicsObject() + local mu = self.MultSpeed + local ms = self.MaxSpeed + local tick = FrameTime() + local ang = ply:EyeAngles().y + local sang = Angle(0, ang, 0) + + ply:SetActiveWeapon(nil) + + local tr = util.TraceEntity({ + start = self:GetPos(), + endpos = self:GetPos(), + filter = self + }, self) + + if self:GetNW2Bool("Boost") then + local fwd = self:GetForward() + phys:SetVelocityInstantaneous(fwd * self.Velo.x) + else + if tr.Hit then + phys:SetVelocityInstantaneous( + (self:GetForwardAbs() * self.Velo.x + + self:GetRightAbs() * self.Velo.y + + self:GetUpAbs() * self.Velo.z) / 2 + ) + if self.TakeDamageWall < CurTime() then + self:TakeDamage(1) + self.TakeDamageWall = CurTime() + 0.2 + end + else + phys:SetVelocityInstantaneous( + self:GetForwardAbs() * self.Velo.x + + self:GetRightAbs() * self.Velo.y + + self:GetUpAbs() * self.Velo.z + + Vector(0, 0, 15) + ) + end + end + + local ratioX = math.Clamp(self.Velo.x / ms, -1, 1) + local ratioY = math.Clamp(self.Velo.y / ms, -1, 1) + local targetPitch = ratioX * self.MaxPitch + local targetRoll = ratioY * self.MaxRoll + sang = Angle(targetPitch, ang, targetRoll) + + local b = {} + b.secondstoarrive = 1 + b.pos = self:GetPos() + b.angle = sang + b.maxangular = 120 + b.maxangulardamp = 50 + b.maxspeed = 1000 + b.maxspeeddamp = 150 + b.dampfactor = 0.4 + b.teleportdistance= 0 + b.deltatime = CurTime() - self.DeltaTime + phys:ComputeShadowControl(b) + + if ply:KeyDown(IN_FORWARD) or self:GetNW2Bool("Boost") then + if self:GetNW2Bool("Boost") then + self.Velo.x = math.Clamp(self.Velo.x + tick * mu * 2, -ms, self.BoostSpeed) + else + self.Velo.x = math.Clamp(self.Velo.x + tick * mu, -ms, ms) + end + elseif ply:KeyDown(IN_BACK) then + self.Velo.x = math.Clamp(self.Velo.x - tick * mu, -ms, ms) + end + + if ply:KeyDown(IN_MOVELEFT) then + self.Velo.y = math.Clamp(self.Velo.y - tick * mu, -ms, ms) + elseif ply:KeyDown(IN_MOVERIGHT) then + self.Velo.y = math.Clamp(self.Velo.y + tick * mu, -ms, ms) + end + + if ply:KeyDown(IN_DUCK) then + self.Velo.z = math.Clamp(self.Velo.z - tick * mu, -ms, ms) + elseif ply:KeyDown(IN_SPEED) then + self.Velo.z = math.Clamp(self.Velo.z + tick * mu, -ms, ms) + end + + local drag = 1 - math.Clamp(self.LinearDrag * tick, 0, 0.2) + self.Velo.x = self.Velo.x * drag + self.Velo.y = self.Velo.y * drag + self.Velo.z = self.Velo.z * (0.98 * drag) + + if not ply:KeyDown(IN_FORWARD) and not ply:KeyDown(IN_BACK) and not self:GetNW2Bool("Boost") then + if math.abs(self.Velo.x) > 5 then + if self.Velo.x > 0 then + self.Velo.x = math.Clamp(self.Velo.x - tick * mu, -ms, ms) + else + self.Velo.x = math.Clamp(self.Velo.x + tick * mu, -ms, ms) + end + else + self.Velo.x = 0 + end + end + + if not ply:KeyDown(IN_SPEED) and not ply:KeyDown(IN_DUCK) then + if math.abs(self.Velo.z) > 5 then + if self.Velo.z > 0 then + self.Velo.z = math.Clamp(self.Velo.z - tick * mu, -ms, ms) + else + self.Velo.z = math.Clamp(self.Velo.z + tick * mu, -ms, ms) + end + else + self.Velo.z = 0 + end + end + + if not ply:KeyDown(IN_MOVELEFT) and not ply:KeyDown(IN_MOVERIGHT) then + if math.abs(self.Velo.y) > 5 then + if self.Velo.y > 0 then + self.Velo.y = math.Clamp(self.Velo.y - tick * mu, -ms, ms) + else + self.Velo.y = math.Clamp(self.Velo.y + tick * mu, -ms, ms) + end + else + self.Velo.y = 0 + end + end + + local wasBoost = self:GetNW2Bool("Boost") + self:SetNW2Bool("Boost", ply:KeyDown(IN_ATTACK)) + self:SetNW2Bool("Light", ply:KeyDown(IN_ATTACK2)) + + if ply:KeyDown(IN_ATTACK) and not wasBoost then + self:EmitSound("vehicles/airboat/fan_blade_fullthrottle_loop1.wav", 70, 150, 0.4) + self.LastBoostSound = CurTime() + elseif not ply:KeyDown(IN_ATTACK) and wasBoost then + self:StopSound("vehicles/airboat/fan_blade_fullthrottle_loop1.wav") + end + + if self.RotorSound then + local spd = phys:GetVelocity():Length() + local load = math.Clamp( + (math.abs(self.Velo.x) + math.abs(self.Velo.y) + math.abs(self.Velo.z)) / (self.BoostSpeed * 0.6), + 0, 1 + ) + local timeLeft = math.max(self:GetNW2Float("RemoveTime") - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / self.BatteryCount, 0, 1) + local basePitch = 80 + 45 * load + math.Clamp(spd * 0.025, 0, 35) + local baseVol = 0.15 + 0.35 * load + basePitch = basePitch * (0.85 + 0.15 * battFrac) + + if battFrac < 0.15 then + basePitch = basePitch + math.sin(CurTime() * 15) * 5 + end + + self.RotorSound:ChangePitch(math.Clamp(basePitch, 55, 180), 0.08) + self.RotorSound:ChangeVolume(math.Clamp(baseVol, 0.05, 0.55), 0.08) + end + + if self.WindSound then + local spd = phys:GetVelocity():Length() + local windVol = math.Clamp(spd / 800, 0, 0.4) + local windPitch = 80 + math.Clamp(spd * 0.05, 0, 40) + self.WindSound:ChangeVolume(windVol, 0.15) + self.WindSound:ChangePitch(windPitch, 0.15) + end + + if self:Health() <= 0 then + local ply2 = self:GetCreator() + if IsValid(ply2) then + net.Start("MuR_DroneLost") + net.Send(ply2) + end + self:Explode() + return true + end + + local ply2 = self:GetCreator() + if self:GetNW2Float("RemoveTime") < CurTime() or not IsValid(ply2) or not ply2:Alive() or (ply2.GetSVAnim and ply2:GetSVAnim() ~= "") then + if IsValid(ply2) then + net.Start("MuR_DroneLost") + net.Send(ply2) + if ply2.ControlDrone == self then + ply2.ControlDrone = nil + end + end + self:EmitSound("ambient/machines/machine1_hit2.wav", 70, 80, 0.6) + self:Remove() + elseif IsValid(ply2) and ply2:KeyDown(IN_RELOAD) and ply2:GetPos():DistToSqr(self:GetPos()) < 50000 then + ply2:Give("swep_drone") + ply2:SelectWeapon("swep_drone") + self:EmitSound("buttons/button14.wav", 60, 100, 0.5) + if ply2.ControlDrone == self then + ply2.ControlDrone = nil + end + self:Remove() + end + + self.DeltaTime = CurTime() + self:NextThink(CurTime()) + return true +end + +function ENT:PhysicsCollide(col) + if self.Velo.x > self.MaxSpeed and self:GetNW2Bool("Boost") then + self:EmitSound("physics/metal/metal_solid_impact_hard"..math.random(1,5)..".wav", 80, 100, 0.8) + self:TakeDamage(self:Health()) + end +end + +-- ВЗРЫВ С HEAT-ПРОБИТИЕМ, ПОРТНУТ ИЗ КРОКУСА (исправлен для самоуничтожения) +function ENT:Explode() + if self._Exploding then return end + self._Exploding = true + + local ply = self:GetCreator() + + if IsValid(ply) then + net.Start("MuR_DroneLost") + net.Send(ply) + + if ply.ControlDrone == self then + ply.ControlDrone = nil + end + end + + self:OnFinishExplosion() + + timer.Simple(0, function() + if IsValid(self) then + self:Remove() + end + end) +end + +function ENT:OnFinishExplosion() + local angle_reg = Angle(0,0,0) + local angle_dif = Angle(-90,0,0) + local pos = self:LocalToWorld(self:OBBCenter()) + + if self:WaterLevel() >= 1 then + local tr = util.TraceLine({ + start = pos, + endpos = pos + Vector(0,0,9000), + filter = self, + }) + local tr2 = util.TraceLine({ + start = tr.HitPos, + endpos = tr.HitPos - Vector(0,0,9000), + filter = self, + mask = MASK_WATER + CONTENTS_TRANSLUCENT, + }) + if tr2.Hit then + ParticleEffect(self.EffectWater, tr2.HitPos,(self.AngEffect and angle_dif or angle_reg), nil) + end + else + local tr = util.TraceLine({ + start = pos, + endpos = pos - Vector(0, 0, self.TraceLength or 150), + filter = self, + }) + if tr.HitWorld then + ParticleEffect(self.Effect, pos, (self.AngEffect and angle_dif or angle_reg), nil) + else + ParticleEffect(self.EffectAir, pos, (self.AngEffect and angle_dif or angle_reg), nil) + end + end + + if self.HEAT then + local hitPos = self:GetPos() + self:GetForward() * 20 + + local heat = {} + heat.Src = hitPos + heat.Dir = self:GetForward() + heat.Spread = Vector(0,0,0) + heat.Force = self.ArmorPenetration + math.Rand(-self.ArmorPenetration * 0.05, self.ArmorPenetration * 0.05) + heat.HullSize = self.HEATRadius * 2 + heat.Damage = self.PenetrationDamage + math.Rand(-self.PenetrationDamage * 0.1, self.PenetrationDamage * 0.1) + heat.Velocity = 30000 + heat.Attacker = self.Owner or self:GetCreator() or self + + if LVS and LVS.FireBullet then + LVS:FireBullet(heat) + end + + if self.ExplosionRadius > 0 then + util.BlastDamage( + self, + (IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld(), + pos, + self.ExplosionRadius, + self.ExplosionDamage + ) + self:BlastDoors(self, pos, 10) + end + + if self.FragCount > 0 then + self:Fragmentation( + self, + pos, + 10000, + self.FragDamage, + self.FragRadius, + (IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld() + ) + end + else + if self.ExplosionRadius > 0 then + for _, v in pairs(ents.FindInSphere(pos, self.ExplosionRadius)) do + if IsValid(v) and not v.SWBombV3 then + local dmg = DamageInfo() + dmg:SetInflictor(self) + dmg:SetDamage(self.ExplosionDamage) + dmg:SetDamageType(self.DamageType or DMG_BLAST) + dmg:SetAttacker((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()) + v:TakeDamageInfo(dmg) + end + end + end + + if self.BlastRadius > 0 then + util.BlastDamage( + self, + (IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld(), + pos, + self.BlastRadius, + self.ExplosionDamage / 2 + ) + self:BlastDoors(self, pos, 10) + end + + if self.FragCount > 0 then + self:Fragmentation( + self, + pos, + 10000, + self.FragDamage, + self.FragRadius, + (IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld() + ) + end + end + + if swv3 and swv3.CreateSound then + swv3.CreateSound(pos, false, self.ExplosionSound, self.FarExplosionSound, self.DistExplosionSound) + else + sound.Play(self.ExplosionSound or "ambient/explosions/explode_4.wav", pos, 100, 100, 1) + end +end + +function ENT:IsDoor(ent) + local Class = ent:GetClass() + return (Class == "prop_door") or (Class == "prop_door_rotating") or (Class == "func_door") or (Class == "func_door_rotating") +end + +function ENT:BlastDoors(blaster, pos, power, range, ignoreVisChecks) + for _, door in pairs(ents.FindInSphere(pos, 40 * power * (range or 1))) do + if self:IsDoor(door) then + local proceed = ignoreVisChecks + + if not proceed then + local tr = util.QuickTrace(pos, door:LocalToWorld(door:OBBCenter()) - pos, blaster) + proceed = IsValid(tr.Entity) and (tr.Entity == door) + end + + if proceed then + self:BlastDoor(door, (door:LocalToWorld(door:OBBCenter()) - pos):GetNormalized() * 1000) + end + end + + if door:GetClass() == "func_breakable_surf" then + door:Fire("Break") + end + end +end + +function ENT:BlastDoor(ent, vel) + local Moddel, Pozishun, Ayngul, Muteeriul, Skin = ent:GetModel(), ent:GetPos(), ent:GetAngles(), ent:GetMaterial(), ent:GetSkin() + sound.Play("Wood_Crate.Break", Pozishun, 60, 100) + sound.Play("Wood_Furniture.Break", Pozishun, 60, 100) + ent:Fire("unlock", "", 0) + ent:Fire("open", "", 0) + ent:SetNoDraw(true) + ent:SetNotSolid(true) + + if Moddel and Pozishun and Ayngul then + local Replacement = ents.Create("prop_physics") + Replacement:SetModel(Moddel) + Replacement:SetPos(Pozishun + Vector(0, 0, 1)) + Replacement:SetAngles(Ayngul) + + if Muteeriul then + Replacement:SetMaterial(Muteeriul) + end + + if Skin then + Replacement:SetSkin(Skin) + end + + Replacement:SetModelScale(.9, 0) + Replacement:Spawn() + Replacement:Activate() + + if vel then + Replacement:GetPhysicsObject():SetVelocity(vel) + + timer.Simple(0, function() + if IsValid(Replacement) then + Replacement:GetPhysicsObject():ApplyForceCenter(vel * 100) + end + end) + end + + timer.Simple(3, function() + if IsValid(Replacement) then + Replacement:SetCollisionGroup(COLLISION_GROUP_WEAPON) + end + end) + + timer.Simple(30, function() + if IsValid(ent) then + ent:SetNotSolid(false) + ent:SetNoDraw(false) + end + + if IsValid(Replacement) then + Replacement:Remove() + end + end) + end +end + +function ENT:Fragmentation(shooter, origin, fragNum, fragDmg, fragMaxDist, attacker, direction, spread, zReduction) + shooter = shooter or game.GetWorld() + zReduction = zReduction or 2 + + local Spred = Vector(0, 0, 0) + local BulletsFired, MaxBullets, disperseTime = 0, self.FragCount, .5 + + if fragNum >= 12000 then + disperseTime = 2 + elseif fragNum >= 6000 then + disperseTime = 1 + end + + for i = 1, fragNum do + timer.Simple((i / fragNum) * disperseTime, function() + local Dir + + if direction and spread then + Dir = Vector(direction.x, direction.y, direction.z) + Dir = Dir + VectorRand() * math.Rand(0, spread) + Dir:Normalize() + else + Dir = VectorRand() + end + + if zReduction then + Dir.z = Dir.z / zReduction + Dir:Normalize() + end + + local Tr = util.QuickTrace(origin, Dir * fragMaxDist, shooter) + + if Tr.Hit and not Tr.HitSky and not Tr.HitWorld and (BulletsFired < MaxBullets) then + local LowFrag = (Tr.Entity.IsVehicle and Tr.Entity:IsVehicle()) or Tr.Entity.LFS or Tr.Entity.LVS or Tr.Entity.EZlowFragPlease + + if (not LowFrag) or (LowFrag and math.random(1, 4) == 2) then + + local firer = (IsValid(shooter) and shooter) or game.GetWorld() + + firer:FireBullets({ + Attacker = attacker, + Damage = fragDmg, + Force = fragDmg / 8, + Num = 1, + Src = origin, + Tracer = 0, + Dir = Dir, + Spread = Spred, + AmmoType = "Buckshot" + }) + BulletsFired = BulletsFired + 1 + end + end + end) + end +end + +function ENT:OnTakeDamage(dmgt) + local dmg = dmgt:GetDamage() + self:SetHealth(self:Health() - dmg) + + local owner = self:GetCreator() + if IsValid(owner) then + owner:ViewPunch(AngleRand(-1, 1)) + local snd = damageSounds[math.random(#damageSounds)] + self:EmitSound(snd, 75, math.random(90, 110)) + + if self:Health() < self.HealthCount * 0.3 and math.random() < 0.3 then + self:EmitSound("ambient/machines/machine1_hit1.wav", 65, math.random(120, 140), 0.4) + end + end +end + +hook.Add("SetupPlayerVisibility", "GpincDroneCam", function(ply, viewEntity) + local drone = ply.ControlDrone + if IsValid(drone) and drone:GetClass() == "mur_drone_entity" then + AddOriginToPVS(drone:GetPos()) + end +end) + +-- приём команды экстренного самоуничтожения +net.Receive("MuR_DroneEmergency", function(len, ply) + local drone = ply.ControlDrone + + if not IsValid(drone) or drone:GetClass() ~= "mur_drone_entity" then + ply.ControlDrone = nil + return + end + + if not drone.EmergencyKill then + drone.EmergencyKill = true + drone.EmergencyKillTime = CurTime() + 3 + drone:SetNWFloat("EmergencyCountdown", drone.EmergencyKillTime) + drone:EmitSound("buttons/button17.wav", 75, 100, 0.6) + end +end) diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/shared.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/shared.lua new file mode 100644 index 0000000..5865b9c --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_entity/shared.lua @@ -0,0 +1,541 @@ +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "FPV дрон" +ENT.Spawnable = true +ENT.AutomaticFrameAdvance = true + +-- === ПАРАМЕТРЫ, НУЖНЫЕ И СЕРВЕРУ, И КЛИЕНТУ === +ENT.BatteryCount = 60 +ENT.HealthCount = 2 + +-- HEAT / Кумулятивная боеголовка +ENT.HEAT = true +ENT.HEATRadius = 2 +ENT.ArmorPenetration = 50000 +ENT.PenetrationDamage = 2500 + +-- Взрыв +ENT.ExplosionDamage = 300 +ENT.ExplosionRadius = 75 +ENT.BlastRadius = 0 + +-- Фрагментация +ENT.FragDamage = 25 +ENT.FragRadius = 300 +ENT.FragCount = 100 + +-- Эффекты +ENT.TraceLength = 150 +ENT.AngEffect = true + +-- LVS эффекты +ENT.Effect = "lvs_explosion_small" +ENT.EffectAir = "lvs_explosion_small" +ENT.EffectWater = "lvs_explosion_small" + +-- LVS звуки +ENT.ExplosionSound = "lvs/explosion_small.wav" +ENT.FarExplosionSound = "lvs/explosion_small.wav" +ENT.DistExplosionSound = "lvs/explosion_small.wav" + +if CLIENT then + local droneMat = Material("vgui/gradient-l") + local scanlineMat = Material("pp/texturize") + local noiseChars = {"█", "▓", "▒", "░", "▄", "▀", "■", "▪"} + + -- Ссылка на активный дрон (локально) + local DRONE = nil + + -- Уникальные имена хуков, чтобы можно было безопасно удалять + local HOOK_RENDER_SSE = "FPV_Drone_RenderScreenspaceEffects" + local HOOK_HUDPAINT = "FPV_Drone_HUDPaint" + local HOOK_CREATEMOVE = "FPV_Drone_CreateMove" + local HOOK_CALCVIEW = "FPV_Drone_CalcView" + local HOOK_CLEANUP_THINK = "FPV_Drone_CleanupThink" + local HOOK_EMERGENCY = "FPV_Drone_EmergencyKey" + + local function DrawScanlines(sw, sh, intensity) + surface.SetDrawColor(0, 0, 0, intensity * 15) + for i = 0, sh, 4 do + surface.DrawRect(0, i, sw, 1) + end + end + + local function DrawGlitchNoise(sw, sh, intensity) + if intensity < 0.3 then return end + local glitchCount = math.floor((1 - intensity) * 50) + surface.SetDrawColor(255, 255, 255, (1 - intensity) * 100) + for i = 1, glitchCount do + local x = math.random(0, sw) + local y = math.random(0, sh) + local w = math.random(20, 200) + surface.DrawRect(x, y, w, 2) + end + end + + local function DrawCornerBrackets(x, y, w, h, size, thickness, col) + surface.SetDrawColor(col) + surface.DrawRect(x, y, size, thickness) + surface.DrawRect(x, y, thickness, size) + surface.DrawRect(x + w - size, y, size, thickness) + surface.DrawRect(x + w - thickness, y, thickness, size) + surface.DrawRect(x, y + h - thickness, size, thickness) + surface.DrawRect(x, y + h - size, thickness, size) + surface.DrawRect(x + w - size, y + h - thickness, size, thickness) + surface.DrawRect(x + w - thickness, y + h - size, thickness, size) + end + + local function DrawCrosshair(cx, cy, size, gap, thickness, col) + surface.SetDrawColor(col) + surface.DrawRect(cx - size, cy - thickness/2, size - gap, thickness) + surface.DrawRect(cx + gap, cy - thickness/2, size - gap, thickness) + surface.DrawRect(cx - thickness/2, cy - size, thickness, size - gap) + surface.DrawRect(cx - thickness/2, cy + gap, thickness, size - gap) + end + + local function DrawArcSegment(cx, cy, radius, startAng, endAng, thickness, segments, col) + surface.SetDrawColor(col) + local step = (endAng - startAng) / segments + for i = 0, segments - 1 do + local a1 = math.rad(startAng + i * step) + local a2 = math.rad(startAng + (i + 1) * step) + local x1 = cx + math.cos(a1) * radius + local y1 = cy + math.sin(a1) * radius + local x2 = cx + math.cos(a2) * radius + local y2 = cy + math.sin(a2) * radius + surface.DrawLine(x1, y1, x2, y2) + end + end + + local function DrawCircularBar(cx, cy, radius, frac, thickness, bgCol, fgCol) + DrawArcSegment(cx, cy, radius, -90, 270, thickness, 32, bgCol) + if frac > 0 then + DrawArcSegment(cx, cy, radius, -90, -90 + 360 * frac, thickness, math.floor(32 * frac), fgCol) + end + end + + local function GetDroneRT() + local name = "MuR_Drone_LastFrame" + return GetRenderTarget(name, ScrW(), ScrH()) + end + + local lastFrameMat = CreateMaterial("MuR_Drone_LastFrame_Mat", "UnlitGeneric", { + ["$basetexture"] = "MuR_Drone_LastFrame", + ["$ignorez"] = 1, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1 + }) + + local function LoseDrone() + surface.PlaySound("ambient/levels/prison/radio_random"..math.random(1,15)..".wav") + local startTime = CurTime() + + lastFrameMat:SetTexture("$basetexture", GetDroneRT()) + + hook.Add("HUDPaint", "FPV_Drone_LostHUD", function() + if not LocalPlayer():Alive() then return end + local sw, sh = ScrW(), ScrH() + local elapsed = CurTime() - startTime + local flicker = math.sin(elapsed * 30) > 0 + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, sw, sh) + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(lastFrameMat) + surface.DrawTexturedRect(0, 0, sw, sh) + + surface.SetDrawColor(0, 0, 0, math.min(elapsed * 100, 200)) + surface.DrawRect(0, 0, sw, sh) + + for i = 1, 150 do + local px = math.random(0, sw) + local py = math.random(0, sh) + local ps = math.random(2, 8) + local c = math.random(30, 80) + surface.SetDrawColor(c, c, c, 255) + surface.DrawRect(px, py, ps, ps) + end + + DrawScanlines(sw, sh, 3) + + for i = 1, 20 do + local y = math.random(0, sh) + local w = math.random(sw * 0.3, sw) + local x = math.random(0, sw - w) + surface.SetDrawColor(40, 40, 45, 200) + surface.DrawRect(x, y, w, math.random(1, 5)) + end + + for i = 1, 2000 do + surface.SetDrawColor(255, 255, 255, math.random(10, 80)) + surface.DrawRect(math.random(0, sw), math.random(0, sh), 1, 1) + end + + + local textCol = flicker and Color(255, 50, 50, 255) or Color(200, 40, 40, 200) + draw.SimpleText("▌▌ СОЕДИНЕНИЕ ПОТЕРЯНО ▐▐", "MuR_Font5", sw/2, sh/2 - 30, textCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("СВЯЗЬ ПРЕКРАЩЕНА", "MuR_Font2", sw/2, sh/2 + 30, Color(150, 150, 150, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end) + + + timer.Simple(2, function() + hook.Remove("HUDPaint", "FPV_Drone_LostHUD") + end) + end + net.Receive("MuR_DroneLost", LoseDrone) + + local function FPV_ClearHooks() + hook.Remove("HUDPaint", HOOK_HUDPAINT) + hook.Remove("RenderScreenspaceEffects", HOOK_RENDER_SSE) + hook.Remove("CalcView", HOOK_CALCVIEW) + hook.Remove("CreateMove", HOOK_CREATEMOVE) + end + + + -- Основной приём дрона: ставим локальную ссылку и создаём HUD/CalcView хуки + net.Receive("MuR_DroneCam", function() + FPV_ClearHooks() + local ent = net.ReadEntity() + DRONE = ent + + -- Если дрон невалиден — ничего не делаем + if not IsValid(DRONE) then return end + + -- Переменные HUD + local pulseAlpha = 0 + local pulseDir = 1 + local lastBattBeep = 0 + local lastDmgBeep = 0 + local startTime = CurTime() + local smoothBatt = 1 + local smoothHP = 1 + + local ppTab = { + ["$pp_colour_addr"] = 0, + ["$pp_colour_addg"] = 0.02, + ["$pp_colour_addb"] = 0, + ["$pp_colour_brightness"] = -0.03, + ["$pp_colour_contrast"] = 1.15, + ["$pp_colour_colour"] = 0.4, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0.05, + ["$pp_colour_mulb"] = 0 + } + + -- RenderScreenspaceEffects + hook.Add("RenderScreenspaceEffects", HOOK_RENDER_SSE, function() + if not IsValid(DRONE) then return end + + local rt = GetDroneRT() + render.CopyRenderTargetToTexture(rt) + + local timeLeft = math.max(DRONE:GetNW2Float("RemoveTime") - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / DRONE.BatteryCount, 0, 1) + + if battFrac < 0.2 then + ppTab["$pp_colour_addr"] = 0.1 * (1 - battFrac / 0.2) + ppTab["$pp_colour_contrast"] = 1.15 + 0.2 * (1 - battFrac / 0.2) + else + ppTab["$pp_colour_addr"] = 0 + ppTab["$pp_colour_contrast"] = 1.15 + end + + DrawColorModify(ppTab) + DrawSharpen(0.8, 0.8) + end) + + -- HUDPaint + hook.Add("HUDPaint", HOOK_HUDPAINT, function() + if not IsValid(DRONE) then return end + local ent = DRONE + local sw, sh = ScrW(), ScrH() + local cx, cy = sw / 2, sh / 2 + local time = CurTime() + local elapsed = time - startTime + + pulseAlpha = pulseAlpha + pulseDir * FrameTime() * 400 + if pulseAlpha >= 255 then pulseDir = -1 pulseAlpha = 255 + elseif pulseAlpha <= 100 then pulseDir = 1 pulseAlpha = 100 end + + local timeLeft = math.max(ent:GetNW2Float("RemoveTime") - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1) + local hpFrac = math.Clamp(ent:Health() / ent.HealthCount, 0, 1) + + smoothBatt = Lerp(FrameTime() * 3, smoothBatt, battFrac) + smoothHP = Lerp(FrameTime() * 5, smoothHP, hpFrac) + + local vel = ent:GetVelocity():Length() + local spd = math.Round(vel * 0.068) + local tr = util.TraceLine({start = ent:GetPos(), endpos = ent:GetPos() - Vector(0, 0, 10000), filter = ent}) + local agl = math.Round(ent:GetPos().z - tr.HitPos.z) + local dist2 = LocalPlayer():GetPos():DistToSqr(ent:GetPos()) + local signal = math.Clamp(1 - (dist2 / (5000 * 5000)), 0, 1) + local canDisarm = dist2 < 50000 + local jamStrength = signal + + if jamStrength < 0.4 then + DrawGlitchNoise(sw, sh, jamStrength) + end + + if jamStrength < 0.2 then + surface.SetDrawColor(255, 255, 255, math.random(20, 60)) + surface.DrawRect(0, 0, sw, sh) + end + + if jamStrength < 0.1 then + local shift = math.random(-10, 10) + surface.SetDrawColor(255, 255, 255, 40) + surface.DrawRect(shift, 0, sw, sh) + end + + + local killTime = ent:GetNWFloat("EmergencyCountdown", 0) + + DrawScanlines(sw, sh, 32) + DrawGlitchNoise(sw, sh, signal) + + local frameCol = Color(40, 255, 120, 120) + DrawCornerBrackets(40, 40, sw - 80, sh - 80, 60, 3, frameCol) + + local crossCol = Color(40, 255, 120, pulseAlpha) + DrawCrosshair(cx, cy, 25, 8, 2, crossCol) + + surface.SetDrawColor(40, 255, 120, 60) + DrawArcSegment(cx, cy, 45, 0, 360, 1, 64, Color(40, 255, 120, 40)) + + local targetDist = tr.HitPos:Distance(ent:GetPos()) + if targetDist < 500 then + local lockCol = Color(255, 80, 60, pulseAlpha) + DrawCrosshair(cx, cy, 35, 5, 3, lockCol) + draw.SimpleText(string.format("%.1fm", targetDist * 0.0254), "MuR_Font2", cx, cy + 50, lockCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + + draw.SimpleText("◈ FPV ", "MuR_Font3", 80, 60, Color(40, 255, 120, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(string.format("ВРЕМЯ СВЯЗИ %.1fs", elapsed), "MuR_Font1", 80, 95, Color(150, 150, 150, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(os.date("%H:%M:%S"), "MuR_Font1", 80, 115, Color(150, 150, 150, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local telemX = sw - 80 + local spdCol = spd > 60 and Color(255, 200, 60) or Color(40, 255, 120) + draw.SimpleText(string.format("%03d km/h", spd), "MuR_Font3", telemX, 60, spdCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText("СКОР", "MuR_Font1", telemX, 95, Color(100, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + draw.SimpleText(string.format("%04d m", agl), "MuR_Font3", telemX, 120, Color(40, 255, 120), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText("ВЫС", "MuR_Font1", telemX, 155, Color(100, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + local sigCol = signal < 0.25 and Color(255, 60, 60) or (signal < 0.5 and Color(255, 200, 60) or Color(40, 255, 120)) + local sigBars = math.floor(signal * 5) + local sigStr = string.rep("▮", sigBars) .. string.rep("▯", 5 - sigBars) + draw.SimpleText(sigStr, "MuR_Font2", telemX, 180, sigCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText(string.format("%d%%", math.Round(signal * 100)), "MuR_Font1", telemX, 210, sigCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + local barW, barH = 250, 12 + local barX, barY = 80, sh - 180 + + local battCol = smoothBatt < 0.2 and Color(255, 60, 60) or (smoothBatt < 0.4 and Color(255, 200, 60) or Color(40, 255, 120)) + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(barX, barY, barW, barH) + surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 255) + surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothBatt, barH - 4) + surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 100) + surface.DrawOutlinedRect(barX, barY, barW, barH, 1) + draw.SimpleText(string.format("ЗАРЯД %d%%", math.Round(smoothBatt * 100)), "MuR_Font1", barX, barY - 20, battCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText(string.format("%.0fs", timeLeft), "MuR_Font1", barX + barW, barY - 20, Color(150, 150, 150), TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM) + + barY = sh - 130 + local hpCol = smoothHP < 0.3 and Color(255, 60, 60) or (smoothHP < 0.6 and Color(255, 200, 60) or Color(40, 255, 120)) + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(barX, barY, barW, barH) + surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 255) + surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothHP, barH - 4) + surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 100) + surface.DrawOutlinedRect(barX, barY, barW, barH, 1) + draw.SimpleText(string.format("ПРОЧНОСТЬ %d%%", math.Round(smoothHP * 100)), "MuR_Font1", barX, barY - 20, hpCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + + local ctrlX, ctrlY = sw - 80, sh - 200 + local ctrlCol = Color(150, 150, 150, 200) + + draw.SimpleText("▶ [J] САМОУНИЧТОЖЕНИЕ", "MuR_Font1", ctrlX, ctrlY, Color(255, 80, 80, 200), TEXT_ALIGN_RIGHT) + draw.SimpleText("▶ [ЛКМ] УСКОРЕНИЕ", "MuR_Font1", ctrlX, ctrlY + 22, ctrlCol, TEXT_ALIGN_RIGHT) + draw.SimpleText("▲ [SHIFT] ВВЕРХ", "MuR_Font1", ctrlX, ctrlY + 44, ctrlCol, TEXT_ALIGN_RIGHT) + draw.SimpleText("▼ [CTRL] ВНИЗ", "MuR_Font1", ctrlX, ctrlY + 66, ctrlCol, TEXT_ALIGN_RIGHT) + + if canDisarm then + draw.SimpleText("◉ [R] ВЕРНУТЬ ДРОН", "MuR_Font1", ctrlX, ctrlY + 88, Color(40, 255, 120, 200), TEXT_ALIGN_RIGHT) + else + draw.SimpleText("✖ СЛИШКОМ ДАЛЕКО", "MuR_Font1", ctrlX, ctrlY + 88, Color(255, 100, 60, 200), TEXT_ALIGN_RIGHT) + end + + if killTime > CurTime() then + local left = math.max(0, killTime - CurTime()) + local flash = math.sin(CurTime() * 10) > 0 and 255 or 120 + + draw.SimpleText( + string.format("САМОУНИЧТОЖЕНИЕ: %.1f", left), + "MuR_Font3", + cx, + 260, + Color(255, 50, 50, flash), + TEXT_ALIGN_CENTER + ) + end + + if ent:GetNW2Bool("Boost") then + local boostFlash = math.sin(time * 15) > 0 and 255 or 180 + draw.SimpleText("◀◀ УСКОРЕНИЕ ▶▶", "MuR_Font2", cx, sh - 100, Color(255, 200, 60, boostFlash), TEXT_ALIGN_CENTER) + end + + if smoothBatt < 0.15 then + local warnFlash = math.sin(time * 8) > 0 and 255 or 100 + draw.SimpleText("⚠ НИЗКИЙ ЗАРЯД ⚠", "MuR_Font4", cx, 80, Color(255, 60, 60, warnFlash), TEXT_ALIGN_CENTER) + if time > lastBattBeep then + surface.PlaySound("buttons/button17.wav") + lastBattBeep = time + 0.8 + end + end + + if smoothHP < 0.25 then + local dmgFlash = math.sin(time * 6) > 0 and 255 or 100 + draw.SimpleText("⚠ КРИТИЧЕСКИЕ ПОВРЕЖДЕНИЯ ⚠", "MuR_Font3", cx, 130, Color(255, 120, 60, dmgFlash), TEXT_ALIGN_CENTER) + if time > lastDmgBeep then + surface.PlaySound("buttons/button10.wav") + lastDmgBeep = time + 1.5 + end + end + + if signal < 0.2 then + local sigFlash = math.sin(time * 10) > 0 and 255 or 100 + draw.SimpleText("◢◤ СЛАБЫЙ СИГНАЛ ◢◤", "MuR_Font3", cx, 180, Color(255, 80, 60, sigFlash), TEXT_ALIGN_CENTER) + end + + if ent:GetNWBool("Jammed") then + local jamStrength = ent:GetNWFloat("JamStrength", 1) + if DrawHeavySignalNoise then + DrawHeavySignalNoise(jamStrength) + end + local jamFlash = math.sin(time * 12) > 0 and 255 or 120 + draw.SimpleText("◢◤ ОБНАРУЖЕНЫ ПОМЕХИ!!! ◢◤", "MuR_Font3", cx, 220, Color(255, 50, 50, jamFlash), TEXT_ALIGN_CENTER) + end + end) + + -- CreateMove: блокируем прыжок/использование, но не трогаем селектор + hook.Add("CreateMove", HOOK_CREATEMOVE, function(cmd) + if not IsValid(DRONE) then + hook.Remove("CreateMove", HOOK_CREATEMOVE) + return + end + cmd:SetForwardMove(0) + cmd:SetSideMove(0) + cmd:RemoveKey(IN_JUMP) + cmd:RemoveKey(IN_USE) + end) + + -- CalcView: камера FPV + hook.Add("CalcView", HOOK_CALCVIEW, function(ply, pos, angles, fov) + if not IsValid(DRONE) then + hook.Remove("CalcView", HOOK_CALCVIEW) + hook.Remove("CreateMove", HOOK_CREATEMOVE) + hook.Remove("RenderScreenspaceEffects", HOOK_RENDER_SSE) + hook.Remove("HUDPaint", HOOK_HUDPAINT) + return + end + + local ent = DRONE + local dronePos = ent:GetPos() + local droneAng = ent:GetAngles() + local droneVel = ent:GetVelocity() + local speed = droneVel:Length() + + local timeLeft = math.max(ent:GetNW2Float("RemoveTime") - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1) + local hpFrac = math.Clamp(ent:Health() / ent.HealthCount, 0, 1) + + local baseOffset = Vector(-8, 0, -2) + local speedOffset = math.Clamp(speed * 0.015, 0, 5) + baseOffset.x = baseOffset.x - speedOffset + + local camPos = dronePos + ent:GetForward() * baseOffset.x + ent:GetUp() * baseOffset.z + + local camAng = Angle(droneAng.p, droneAng.y, droneAng.r) + local eyeAng = ply:EyeAngles() + camAng.p = eyeAng.p + + local time = CurTime() + local bobScale = 0.3 + (1 - battFrac) * 0.5 + camAng.p = camAng.p + math.sin(time * 2.5) * bobScale + camAng.r = camAng.r + math.cos(time * 1.8) * bobScale * 0.7 + + if hpFrac < 0.3 then + local shake = (0.3 - hpFrac) * 3 + camAng.p = camAng.p + math.Rand(-shake, shake) + camAng.r = camAng.r + math.Rand(-shake, shake) + end + + if battFrac < 0.15 then + local flicker = (0.15 - battFrac) * 10 + camAng.p = camAng.p + math.sin(time * 20) * flicker + end + + local baseFov = 40 + local speedFovBoost = math.Clamp(speed * 0.025, 0, 15) + local dynamicFov = baseFov + speedFovBoost + + if ent:GetNW2Bool("Boost") then + dynamicFov = dynamicFov + 10 + end + + return { + origin = camPos, + angles = camAng, + fov = math.Clamp(dynamicFov, 70, 120), + drawviewer = true + } + end) + end) + + local KillKeyHeld = false + + hook.Add("Think", "FPV_Drone_EmergencyKey", function() + if not IsValid(DRONE) then KillKeyHeld = false return end + + if input.IsKeyDown(KEY_J) and not KillKeyHeld then + KillKeyHeld = true + net.Start("MuR_DroneEmergency") + net.SendToServer() + end + + if not input.IsKeyDown(KEY_J) then + KillKeyHeld = false + end + end) + + + -- Подстраховка: если дрон удалён — очистим ссылку и хуки + hook.Add("Think", HOOK_CLEANUP_THINK, function() + if DRONE and not IsValid(DRONE) then + DRONE = nil + -- Удаляем наши хуки, если они остались + hook.Remove("CalcView", HOOK_CALCVIEW) + hook.Remove("CreateMove", HOOK_CREATEMOVE) + hook.Remove("RenderScreenspaceEffects", HOOK_RENDER_SSE) + hook.Remove("HUDPaint", HOOK_HUDPAINT) + hook.Remove("Think", HOOK_EMERGENCY) + hook.Remove("Think", HOOK_CLEANUP_THINK) + end + end) + local function FPV_ClearHooks() + hook.Remove("Think", "FPV_DroneEmergencyKey") + hook.Remove("Think", "fpv_drone_cleanup_on_remove") + hook.Remove("HUDPaint", "FPV_Drone_HUDPaint") + hook.Remove("RenderScreenspaceEffects", "FPV_Drone_RenderScreenspaceEffects") + hook.Remove("CalcView", "FPV_Drone_CalcView") + hook.Remove("CreateMove", "FPV_Drone_CreateMove") + end + + hook.Add("EntityRemoved", "FPV_Drone_GlobalCleanup", function(ent) + if ent == DRONE then + DRONE = nil + FPV_ClearHooks() + end + end) +end diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/cl_init.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/init.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/init.lua new file mode 100644 index 0000000..1a15baa --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/init.lua @@ -0,0 +1,467 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("MuR_DroneCam_Grenade") +util.AddNetworkString("MuR_DroneLost_Grenade") +util.AddNetworkString("MuR_GrenadeDropped") +util.AddNetworkString("MuR_ThermalState") +util.AddNetworkString("MuR_DroneEmergency") + +local rotorSounds = { + "ambient/machines/spin_loop.wav", + "ambient/machines/machine1_hit1.wav" +} + +local damageSounds = { + "physics/metal/metal_box_impact_bullet1.wav", + "physics/metal/metal_box_impact_bullet2.wav", + "physics/metal/metal_box_impact_bullet3.wav", + "physics/metal/metal_computer_impact_bullet1.wav", + "physics/metal/metal_computer_impact_bullet2.wav" +} + +local startupSounds = { + "buttons/button9.wav", + "buttons/button17.wav" +} + +function ENT:Initialize() + self:SetModel("models/murdered/weapons/drone_ex.mdl") + self:ResetSequence("idle") + self:SetHealth(self.HealthCount) + self:SetGravity(0) + self.Velo = {x = 0, y = 0, z = 0} + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self.MultSpeed = 700 + self.MaxSpeed = 800 + self.BoostSpeed = 1250 + self.MaxPitch = 18 + self.MaxRoll = 22 + self.LinearDrag = 0.04 + self.GrenadeDropDelay = 0 + self.GrenadesLeft = 3 + self.TakeDamageWall = 0 + self.DeltaTime = 0 + self.LastBoostSound = 0 + self.LastWindSound = 0 + self:SetNW2Float('RemoveTime', CurTime() + self.BatteryCount) + self:SetNW2Int("Grenades", self.GrenadesLeft) + + self:EmitSound(startupSounds[math.random(#startupSounds)], 60, 100, 0.5) + + if CreateSound then + self.RotorSound = CreateSound(self, rotorSounds[1]) + if self.RotorSound then + self.RotorSound:PlayEx(0.2, 80) + end + + self.WindSound = CreateSound(self, "ambient/wind/wind_snippet2.wav") + if self.WindSound then + self.WindSound:PlayEx(0, 100) + end + end + + local ply = self:GetCreator() + if IsValid(ply) then + local forward = ply:GetForward() + forward.z = 0 + forward = forward:GetNormalized() + + local spawnDist = 80 + local spawnHeight = 40 + + local spawnPos = ply:GetPos() + forward * spawnDist + Vector(0, 0, spawnHeight) + + local tr = util.TraceHull({ + start = spawnPos, + endpos = spawnPos, + mins = Vector(-20, -20, -20), + maxs = Vector(20, 20, 20), + filter = {ply, self} + }) + + if tr.Hit then + spawnPos = spawnPos + forward * 60 + end + + local ground = util.TraceLine({ + start = spawnPos, + endpos = spawnPos - Vector(0, 0, 5000), + filter = self + }) + + if spawnPos.z < ground.HitPos.z + 30 then + spawnPos.z = ground.HitPos.z + 30 + end + + self:SetPos(spawnPos) + self:SetAngles(Angle(0, ply:EyeAngles().y, 0)) + end + + + + timer.Simple(0.1, function() + if not IsValid(self) then return end + local ply = self:GetCreator() + if not IsValid(ply) then return end + ply.ControlDrone = self + net.Start("MuR_DroneCam_Grenade") + net.WriteEntity(self) + net.Send(ply) + end) +end + +function ENT:OnRemove() + if self.RotorSound then + self.RotorSound:Stop() + end + if self.WindSound then + self.WindSound:Stop() + end + self:StopSound("ambient/machines/spin_loop.wav") + self:StopSound("ambient/wind/wind_snippet2.wav") + self:StopSound("vehicles/airboat/fan_blade_fullthrottle_loop1.wav") +end + +function ENT:GetForwardAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Forward() +end + +function ENT:GetRightAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Right() +end + +function ENT:GetUpAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Up() +end + +function ENT:Think() + if self.EmergencyKill then + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + + if CurTime() >= (self.EmergencyKillTime or 0) then + self:Explode() + return true + end + end + + + if self:GetNWBool("Jammed") then + + if not self.JamKillTime then + self.JamKillTime = CurTime() + 5 + end + + if CurTime() >= self.JamKillTime then + local effect = EffectData() + effect:SetOrigin(self:GetPos()) + util.Effect("Explosion", effect) + self:Remove() + return true + end + + -- ВАЖНО: НЕ ДЕЛАТЬ return здесь! + -- Просто продолжаем Think(), чтобы таймер мог дойти до взрыва + else + -- вышел из зоны — сбрасываем таймер + self.JamKillTime = nil + end + + local ply = self:GetCreator() + if not IsValid(ply) then + self:Remove() + else + local phys = self:GetPhysicsObject() + local mu = self.MultSpeed + local ms = self.MaxSpeed + local deltatime = self.DeltaTime + local vel = phys:GetVelocity() + local pos = self:GetPos() + local ang = ply:EyeAngles().y + local sang = Angle(0, ang, 0) + ply:SetActiveWeapon(nil) + local trace = {start = self:GetPos(), endpos = self:GetPos(), filter = self} + local tr = util.TraceEntity(trace, self) + + -- Кнопка G — экстренный выход + if not self.EmergencyKill and ply:KeyDown(IN_GRENADE1) then + self.EmergencyKill = true + self.EmergencyKillTime = CurTime() + 3 -- 3 секунды до взрыва + + -- отправляем время на клиент для HUD + self:SetNWFloat("EmergencyCountdown", self.EmergencyKillTime) + + self:EmitSound("buttons/button17.wav", 75, 100, 0.6) + end + + + -- ускоренный расход батареи при включённом теплаке + if self:GetNW2Bool("ThermalActive") then + local drainMul = 2 -- во сколько раз быстрее жрёт + local newRemove = self:GetNW2Float("RemoveTime") - FrameTime() * (drainMul - 1) + self:SetNW2Float("RemoveTime", newRemove) + end + + if self:GetNW2Bool('Boost') then + local fwd = self:GetForward() + phys:SetVelocityInstantaneous(fwd * self.Velo.x) + else + if tr.Hit then + phys:SetVelocityInstantaneous((self:GetForwardAbs() * self.Velo.x + self:GetRightAbs() * self.Velo.y + self:GetUpAbs() * self.Velo.z) / 2) + if self.TakeDamageWall < CurTime() then + self:TakeDamage(1) + self.TakeDamageWall = CurTime() + 0.2 + end + else + phys:SetVelocityInstantaneous(self:GetForwardAbs() * self.Velo.x + self:GetRightAbs() * self.Velo.y + self:GetUpAbs() * self.Velo.z + Vector(0,0,15)) + end + end + + local ratioX = math.Clamp(self.Velo.x / ms, -1, 1) + local ratioY = math.Clamp(self.Velo.y / ms, -1, 1) + local targetPitch = ratioX * self.MaxPitch + local targetRoll = ratioY * self.MaxRoll + sang = Angle(targetPitch, ang, targetRoll) + + local b = {} + b.secondstoarrive = 1 + b.pos = self:GetPos() + b.angle = sang + b.maxangular = 120 + b.maxangulardamp = 50 + b.maxspeed = 1000 + b.maxspeeddamp = 150 + b.dampfactor = 0.4 + b.teleportdistance = 0 + b.deltatime = CurTime() - self.DeltaTime + phys:ComputeShadowControl(b) + + local tick = FrameTime() + if ply:KeyDown(IN_FORWARD) then + self.Velo.x = math.Clamp(self.Velo.x + tick * mu, -ms, ms) + elseif ply:KeyDown(IN_BACK) then + self.Velo.x = math.Clamp(self.Velo.x - tick * mu, -ms, ms) + end + if ply:KeyDown(IN_MOVELEFT) then + self.Velo.y = math.Clamp(self.Velo.y - tick * mu, -ms, ms) + elseif ply:KeyDown(IN_MOVERIGHT) then + self.Velo.y = math.Clamp(self.Velo.y + tick * mu, -ms, ms) + end + if ply:KeyDown(IN_DUCK) then + self.Velo.z = math.Clamp(self.Velo.z - tick * mu, -ms, ms) + elseif ply:KeyDown(IN_SPEED) then + self.Velo.z = math.Clamp(self.Velo.z + tick * mu, -ms, ms) + end + + local drag = 1 - math.Clamp(self.LinearDrag * tick, 0, 0.2) + self.Velo.x = self.Velo.x * drag + self.Velo.y = self.Velo.y * drag + self.Velo.z = self.Velo.z * (0.98 * drag) + + if not ply:KeyDown(IN_FORWARD) and not ply:KeyDown(IN_BACK) and not self:GetNW2Bool('Boost') then + if self.Velo.x > 5 or self.Velo.x < -5 then + if self.Velo.x > 0 then + self.Velo.x = math.Clamp(self.Velo.x - tick * mu, -ms, ms) + elseif self.Velo.x < 0 then + self.Velo.x = math.Clamp(self.Velo.x + tick * mu, -ms, ms) + end + else + self.Velo.x = 0 + end + end + if not ply:KeyDown(IN_SPEED) and not ply:KeyDown(IN_DUCK) then + if self.Velo.z > 5 or self.Velo.z < -5 then + if self.Velo.z > 0 then + self.Velo.z = math.Clamp(self.Velo.z - tick * mu, -ms, ms) + elseif self.Velo.z < 0 then + self.Velo.z = math.Clamp(self.Velo.z + tick * mu, -ms, ms) + end + else + self.Velo.z = 0 + end + end + if not ply:KeyDown(IN_MOVELEFT) and not ply:KeyDown(IN_MOVERIGHT) then + if self.Velo.y > 5 or self.Velo.y < -5 then + if self.Velo.y > 0 then + self.Velo.y = math.Clamp(self.Velo.y - tick * mu, -ms, ms) + elseif self.Velo.y < 0 then + self.Velo.y = math.Clamp(self.Velo.y + tick * mu, -ms, ms) + end + else + self.Velo.y = 0 + end + end + + -- Removed Boost Logic for Bomber Drone + -- local wasBoost = self:GetNW2Bool('Boost') + -- self:SetNW2Bool('Boost', ply:KeyDown(IN_ATTACK)) + + -- Drop Grenade Logic + if ply:KeyDown(IN_ATTACK2) and self.GrenadesLeft > 0 and CurTime() > self.GrenadeDropDelay then + self:DropGrenade() + self.GrenadeDropDelay = CurTime() + 1.5 + end + + -- Removed boost sound logic + + + if self.RotorSound then + local spd = phys:GetVelocity():Length() + local load = math.Clamp((math.abs(self.Velo.x) + math.abs(self.Velo.y) + math.abs(self.Velo.z)) / (self.BoostSpeed * 0.6), 0, 1) + local timeLeft = math.max(self:GetNW2Float('RemoveTime') - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / self.BatteryCount, 0, 1) + local basePitch = 80 + 45 * load + math.Clamp(spd * 0.025, 0, 35) + local baseVol = 0.15 + 0.35 * load + basePitch = basePitch * (0.85 + 0.15 * battFrac) + + if battFrac < 0.15 then + basePitch = basePitch + math.sin(CurTime() * 15) * 5 + end + + self.RotorSound:ChangePitch(math.Clamp(basePitch, 55, 180), 0.08) + self.RotorSound:ChangeVolume(math.Clamp(baseVol, 0.05, 0.55), 0.08) + end + + if self.WindSound then + local spd = phys:GetVelocity():Length() + local windVol = math.Clamp(spd / 800, 0, 0.4) + local windPitch = 80 + math.Clamp(spd * 0.05, 0, 40) + self.WindSound:ChangeVolume(windVol, 0.15) + self.WindSound:ChangePitch(windPitch, 0.15) + end + + local maxDist = 13000 * 13000 -- дальность БИБА ТВОЮ МАТЬ ДАЛЬНОСТЬ!!! + local dist2 = ply:GetPos():DistToSqr(self:GetPos()) + + if dist2 > maxDist then + net.Start("MuR_DroneLost_Grenade") + net.Send(ply) + + self:Explode() + return true + end + + if self:Health() <= 0 then + net.Start("MuR_DroneLost_Grenade") + net.Send(ply) + self:Explode() + end + if self:GetNW2Float('RemoveTime') < CurTime() or not ply:Alive() or (ply.GetSVAnim and ply:GetSVAnim() ~= "") then + net.Start("MuR_DroneLost_Grenade") + net.Send(ply) + self:EmitSound("ambient/machines/machine1_hit2.wav", 70, 80, 0.6) + self:Remove() + elseif (ply:KeyDown(IN_RELOAD) and ply:GetPos():DistToSqr(self:GetPos()) < 50000) then + ply:Give("swep_drone_grenade") + ply:SelectWeapon("swep_drone_grenade") + self:EmitSound("buttons/button14.wav", 60, 100, 0.5) + self:Remove() + end + self.DeltaTime = CurTime() + self:NextThink(CurTime()) + return true + end +end + +function ENT:PhysicsCollide(col) + if self.Velo.x > self.MaxSpeed and self:GetNW2Bool('Boost') then + self:EmitSound("physics/metal/metal_solid_impact_hard"..math.random(1,5)..".wav", 80, 100, 0.8) + self:TakeDamage(self:Health()) + end +end + +function ENT:Explode() + local effect = EffectData() + effect:SetOrigin(self:GetPos()) + util.Effect("Explosion", effect) + + self:EmitSound("ambient/explosions/explode_4.wav", 100, 100, 1) + + self:Remove() +end + +function ENT:DropGrenade() + if self.GrenadesLeft <= 0 then return end + + self.GrenadesLeft = self.GrenadesLeft - 1 + self:SetNW2Int("Grenades", self.GrenadesLeft) + + self:EmitSound("weapons/smg1/switch_single.wav", 80, 100) + + local grenade = ents.Create("lvs_item_explosive") + grenade:SetPos(self:GetPos() - Vector(0,0,10)) + grenade:SetAngles(self:GetAngles()) + grenade:Spawn() + grenade:Activate() + grenade:Fire("SetTimer", "3") + + local phys = grenade:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(self:GetVelocity() + Vector(0,0,-50)) + phys:AddAngleVelocity(VectorRand() * 200) + end + local ply = self:GetCreator() + if IsValid(ply) then + net.Start("MuR_GrenadeDropped") + net.Send(ply) + end + + local physDrone = self:GetPhysicsObject() + if IsValid(physDrone) then + physDrone:ApplyForceCenter(Vector(0,0,2000)) + end +end + +function ENT:OnTakeDamage(dmgt) + local dmg = dmgt:GetDamage() + local att = dmgt:GetAttacker() + self:SetHealth(self:Health() - dmg) + if IsValid(self:GetCreator()) then + self:GetCreator():ViewPunch(AngleRand(-1, 1)) + local snd = damageSounds[math.random(#damageSounds)] + self:EmitSound(snd, 75, math.random(90, 110)) + + if self:Health() < self.HealthCount * 0.3 and math.random() < 0.3 then + self:EmitSound("ambient/machines/machine1_hit1.wav", 65, math.random(120, 140), 0.4) + end + end +end + +net.Receive("MuR_DroneEmergency", function(len, ply) + local drone = ply.ControlDrone + if not IsValid(drone) then return end + + if not drone.EmergencyKill then + drone.EmergencyKill = true + drone.EmergencyKillTime = CurTime() + 3 + drone:SetNWFloat("EmergencyCountdown", drone.EmergencyKillTime) + drone:EmitSound("buttons/button17.wav", 75, 100, 0.6) + end +end) + +net.Receive("MuR_ThermalState", function(len, ply) + local drone = ply.ControlDrone + if IsValid(drone) then + drone:SetNW2Bool("ThermalActive", net.ReadBool()) + end +end) + +hook.Add("SetupPlayerVisibility", "GpincDroneCam_Grenade", function(ply, viewEntity) + local drone = ply.ControlDrone + if IsValid(drone) and drone:GetClass() == "mur_drone_grenade" then + AddOriginToPVS(drone:GetPos()) + end +end) diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/shared.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/shared.lua new file mode 100644 index 0000000..9f021e9 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_grenade/shared.lua @@ -0,0 +1,569 @@ +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "Grenade Drone" +ENT.Spawnable = true +ENT.AutomaticFrameAdvance = true + +ENT.BatteryCount = 300 +ENT.HealthCount = 10 +ENT.GrenadeCount = 3 + +if CLIENT then + local droneRT = nil + local droneRTMat = nil + + function GetDroneRT() + if not droneRT then + droneRT = GetRenderTarget("mur_drone_grenade_rt", ScrW(), ScrH(), false) + droneRTMat = CreateMaterial("mur_drone_grenade_rt_mat", "UnlitGeneric", { + ["$basetexture"] = droneRT:GetName(), + ["$ignorez"] = 1, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1 + }) + end + return droneRT + end + + local GrenadeNotifyEnd = 0 + local GrenadeNotifyMat = Material("posvetka/nizhniya_posvedka.png") + + net.Receive("MuR_GrenadeDropped", function() + GrenadeNotifyEnd = CurTime() + 1.5 -- показывать 1.5 секунды + end) + + local function DrawSignalNoise(strength) + if strength > 1 then return end + + local sw, sh = ScrW(), ScrH() + local noise = (1 - strength) * 200 + local lines = (1 - strength) * 40 + + for i = 1, noise do + surface.SetDrawColor(255, 255, 255, math.random(10, 40)) + surface.DrawRect( + math.random(0, sw), + math.random(0, sh), + math.random(1, 3), + math.random(1, 3) + ) + end + + for i = 1, lines do + local y = math.random(0, sh) + surface.SetDrawColor(255, 255, 255, math.random(10, 40)) + surface.DrawRect(0, y, sw, math.random(1, 3)) + end + + if strength < 0.3 then + local shake = (0.3 - strength) * 4 + surface.SetDrawColor(255, 255, 255, 20) + surface.DrawRect( + math.random(-shake, shake), + math.random(-shake, shake), + sw, + sh + ) + end + end + + hook.Add("HUDPaint", "MuR_GrenadeDropImage", function() + if GrenadeNotifyEnd <= CurTime() then return end + + local sw, sh = ScrW(), ScrH() + local w, h = 282, 152 -- размер картинки + + local timeLeft = GrenadeNotifyEnd - CurTime() + local alpha = math.Clamp(timeLeft * 255, 0, 255) + + surface.SetMaterial(GrenadeNotifyMat) + surface.SetDrawColor(255, 255, 255, alpha) + surface.DrawTexturedRect(sw/2 - w/2, sh/2 - h/2, w, h) + end) + + function DrawScanlines(sw, sh, spacing) + spacing = spacing or 3 + surface.SetDrawColor(0, 0, 0, 40) + for y = 0, sh, spacing do + surface.DrawLine(0, y, sw, y) + end + end + + function DrawGlitchNoise(sw, sh, strength) + strength = strength or 1 + local count = math.floor(80 * strength) + for i = 1, count do + local x = math.random(0, sw) + local y = math.random(0, sh) + local w = math.random(1, 4) + local h = math.random(1, 4) + local c = math.random(80, 140) + surface.SetDrawColor(c, c, c, 80) + surface.DrawRect(x, y, w, h) + end + end + + function DrawCornerBrackets(x, y, w, h, len, thick, col) + col = col or color_white + thick = thick or 2 + len = len or 20 + surface.SetDrawColor(col) + + surface.DrawRect(x, y, len, thick) + surface.DrawRect(x, y, thick, len) + surface.DrawRect(x + w - len, y, len, thick) + surface.DrawRect(x + w - thick, y, thick, len) + surface.DrawRect(x, y + h - thick, len, thick) + surface.DrawRect(x, y + h - len, thick, len) + surface.DrawRect(x + w - len, y + h - thick, len, thick) + surface.DrawRect(x + w - thick, y + h - len, thick, len) + end + + function DrawCrosshair(cx, cy, size, gap, thick, col) + col = col or color_white + size = size or 20 + gap = gap or 4 + thick = thick or 2 + surface.SetDrawColor(col) + + surface.DrawRect(cx - size, cy - thick / 2, size - gap, thick) + surface.DrawRect(cx + gap, cy - thick / 2, size - gap, thick) + surface.DrawRect(cx - thick / 2, cy - size, thick, size - gap) + surface.DrawRect(cx - thick / 2, cy + gap, thick, size - gap) + end + + function DrawArcSegment(cx, cy, radius, startAng, endAng, thick, steps, col) + col = col or color_white + thick = thick or 2 + steps = steps or 32 + surface.SetDrawColor(col) + + local step = (endAng - startAng) / steps + for i = 0, steps - 1 do + local a1 = math.rad(startAng + step * i) + local a2 = math.rad(startAng + step * (i + 1)) + local x1, y1 = cx + math.cos(a1) * radius, cy + math.sin(a1) * radius + local x2, y2 = cx + math.cos(a2) * radius, cy + math.sin(a2) * radius + surface.DrawLine(x1, y1, x2, y2) + end + end + + local DRONE = nil + local Thermal = false + local ThermalKeyHeld = false + local Zoom = 1 + + local DefMats = {} + local DefClrs = {} + local DoXRay = false + + local FLIR = { + ["$pp_colour_addr"] = -.3, + ["$pp_colour_addg"] = -.4, + ["$pp_colour_addb"] = -.4, + ["$pp_colour_brightness"] = .1, + ["$pp_colour_contrast"] = 1, -- да-да, мой флир, биба + ["$pp_colour_colour"] = 0, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0, + } + + net.Receive("MuR_DroneCam_Grenade", function() + local ent = net.ReadEntity() + DRONE = ent + Thermal = false + Zoom = 1 + + local pulseAlpha = 0 + local pulseDir = 1 + local lastBattBeep = 0 + local lastDmgBeep = 0 + local startTime = CurTime() + local smoothBatt = 1 + local smoothHP = 1 + local ppTab = { + ["$pp_colour_addr"] = 0.05, + ["$pp_colour_addg"] = 0.02, + ["$pp_colour_addb"] = 0, + ["$pp_colour_brightness"] = -0.03, + ["$pp_colour_contrast"] = 1.15, + ["$pp_colour_colour"] = 0.5, + ["$pp_colour_mulr"] = 0.05, + ["$pp_colour_mulg"] = 0.05, + ["$pp_colour_mulb"] = 0 + } + + hook.Add("RenderScreenspaceEffects", "DroneCam", function() + if not IsValid(ent) then return end + + local rt = GetDroneRT() + render.CopyRenderTargetToTexture(rt) + + local timeLeft = math.max(ent:GetNW2Float('RemoveTime') - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1) + + if battFrac < 0.2 then + ppTab["$pp_colour_addr"] = 0.1 * (1 - battFrac / 0.2) + ppTab["$pp_colour_contrast"] = 1.15 + 0.2 * (1 - battFrac / 0.2) + else + ppTab["$pp_colour_addr"] = 0.05 + ppTab["$pp_colour_contrast"] = 1.15 + end + + DrawColorModify(ppTab) + DrawSharpen(0.8, 0.8) + end) + + hook.Add("HUDPaint", "DroneCam", function() + if not IsValid(ent) then return end + local sw, sh = ScrW(), ScrH() + local cx, cy = sw / 2, sh / 2 + local time = CurTime() + local elapsed = time - startTime + + pulseAlpha = pulseAlpha + pulseDir * FrameTime() * 400 + if pulseAlpha >= 255 then pulseDir = -1 pulseAlpha = 255 + elseif pulseAlpha <= 100 then pulseDir = 1 pulseAlpha = 100 end + + local timeLeft = math.max(ent:GetNW2Float('RemoveTime') - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1) + local hpFrac = math.Clamp(ent:Health() / ent.HealthCount, 0, 1) + + smoothBatt = Lerp(FrameTime() * 3, smoothBatt, battFrac) + smoothHP = Lerp(FrameTime() * 5, smoothHP, hpFrac) + + local vel = ent:GetVelocity():Length() + local spd = math.Round(vel * 0.068) + local tr = util.TraceLine({start = ent:GetPos(), endpos = ent:GetPos() - Vector(0, 0, 10000), filter = ent}) + local agl = math.Round(ent:GetPos().z - tr.HitPos.z) + local dist2 = LocalPlayer():GetPos():DistToSqr(ent:GetPos()) + local signal = math.Clamp(1 - (dist2 / (10000 * 10000)), 0, 1) + local canDisarm = dist2 < 50000 + local killTime = ent:GetNWFloat("EmergencyCountdown", 0) + + DrawScanlines(sw, sh, 32) + DrawGlitchNoise(sw, sh, signal) + + local frameCol = Color(255, 150, 40, 120) + DrawCornerBrackets(40, 40, sw - 80, sh - 80, 60, 3, frameCol) + + local crossCol = Color(255, 150, 40, pulseAlpha) + DrawCrosshair(cx, cy, 25, 8, 2, crossCol) + + surface.SetDrawColor(255, 50, 50, 100) + DrawArcSegment(cx, cy, 60, 0, 360, 2, 64, Color(255, 50, 50, 50)) + if tr.Hit then + local screenData = tr.HitPos:ToScreen() + if screenData.visible then + surface.SetDrawColor(255, 0, 0, 200) + surface.DrawRect(screenData.x - 2, screenData.y - 2, 4, 4) + draw.SimpleText("⚠ МЕСТО ПАДЕНИЯ", "MuR_Font1", screenData.x, screenData.y - 15, Color(255, 50, 50, 200), TEXT_ALIGN_CENTER) + end + end + + local targetDist = tr.HitPos:Distance(ent:GetPos()) + if targetDist < 500 then + local lockCol = Color(255, 80, 60, pulseAlpha) + DrawCrosshair(cx, cy, 35, 5, 3, lockCol) + draw.SimpleText(string.format("%.1fm", targetDist * 0.0254), "MuR_Font2", cx, cy + 50, lockCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + + draw.SimpleText("◈ ДРОН СО СБРОСОМ", "MuR_Font3", 80, 60, Color(255, 150, 40, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(string.format("ВРЕМЯ РАБОТЫ %.1fs", elapsed), "MuR_Font1", 80, 95, Color(150, 150, 150, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(os.date("%H:%M:%S"), "MuR_Font1", 80, 115, Color(150, 150, 150, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local grenadesLeft = ent:GetNW2Int("Grenades", 3) + draw.SimpleText(string.format("БОЕКОМПЛЕКТ: %d/3", grenadesLeft), "MuR_Font3", 80, 140, grenadesLeft > 0 and Color(255, 50, 50) or Color(100, 100, 100), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local telemX = sw - 80 + local spdCol = spd > 60 and Color(255, 200, 60) or Color(255, 150, 40) + draw.SimpleText(string.format("%03d km/h", spd), "MuR_Font3", telemX, 60, spdCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText("СКОР", "MuR_Font1", telemX, 95, Color(100, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + draw.SimpleText(string.format("%04d m", agl), "MuR_Font3", telemX, 120, Color(255, 150, 40), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText("ВЫС", "MuR_Font1", telemX, 155, Color(100, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + local sigCol = signal < 0.25 and Color(255, 60, 60) or (signal < 0.5 and Color(255, 200, 60) or Color(255, 150, 40)) + local sigBars = math.floor(signal * 5) + local sigStr = string.rep("▮", sigBars) .. string.rep("▯", 5 - sigBars) + draw.SimpleText(sigStr, "MuR_Font2", telemX, 180, sigCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText(string.format("%d%%", math.Round(signal * 100)), "MuR_Font1", telemX, 210, sigCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + local barW, barH = 250, 12 + local barX, barY = 80, sh - 180 + + local battCol = smoothBatt < 0.2 and Color(255, 60, 60) or (smoothBatt < 0.4 and Color(255, 200, 60) or Color(255, 150, 40)) + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(barX, barY, barW, barH) + surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 255) + surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothBatt, barH - 4) + surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 100) + surface.DrawOutlinedRect(barX, barY, barW, barH, 1) + draw.SimpleText(string.format("ЗАРЯД %d%%", math.Round(smoothBatt * 100)), "MuR_Font1", barX, barY - 20, battCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText(string.format("%.0fs", timeLeft), "MuR_Font1", barX + barW, barY - 20, Color(150, 150, 150), TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM) + + barY = sh - 130 + local hpCol = smoothHP < 0.3 and Color(255, 60, 60) or (smoothHP < 0.6 and Color(255, 200, 60) or Color(255, 150, 40)) + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(barX, barY, barW, barH) + surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 255) + surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothHP, barH - 4) + surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 100) + surface.DrawOutlinedRect(barX, barY, barW, barH, 1) + draw.SimpleText(string.format("ПРОЧНОСТЬ %d%%", math.Round(smoothHP * 100)), "MuR_Font1", barX, barY - 20, hpCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + + local ctrlX, ctrlY = sw - 80, sh - 200 + local ctrlCol = Color(150, 150, 150, 200) + draw.SimpleText("▶ [J] САМОУНИЧТОЖЕНИЕ", "MuR_Font1", ctrlX, ctrlY, Color(255, 80, 80, 200), TEXT_ALIGN_RIGHT) + draw.SimpleText("▶ [ALT] ТЕПЛОВИЗОР", "MuR_Font1", ctrlX, ctrlY + 22, ctrlCol, TEXT_ALIGN_RIGHT) + draw.SimpleText("▼ [ПКМ] СБРОСИТЬ ГРАНАТУ", "MuR_Font1", ctrlX, ctrlY + 44, Color(255, 80, 60, 200), TEXT_ALIGN_RIGHT) + draw.SimpleText("▲ [SHIFT] ВВЕРХ", "MuR_Font1", ctrlX, ctrlY + 66, ctrlCol, TEXT_ALIGN_RIGHT) + draw.SimpleText("▼ [CTRL] ВНИЗ", "MuR_Font1", ctrlX, ctrlY + 88, ctrlCol, TEXT_ALIGN_RIGHT) + + local rY = ctrlY + 110 + if canDisarm then + draw.SimpleText("◉ [R] ВЕРНУТЬ ДРОН", "MuR_Font1", ctrlX, rY, Color(40, 255, 120, 200), TEXT_ALIGN_RIGHT) + else + draw.SimpleText("✖ СЛИШКОМ ДАЛЕКО", "MuR_Font1", ctrlX, rY, Color(255, 100, 60, 200), TEXT_ALIGN_RIGHT) + end + + if ent:GetNW2Bool('Boost') then + local boostFlash = math.sin(time * 15) > 0 and 255 or 180 + draw.SimpleText("◀◀ УСКОРЕНИЕ ▶▶", "MuR_Font2", cx, sh - 100, Color(255, 200, 60, boostFlash), TEXT_ALIGN_CENTER) + end + + if smoothBatt < 0.15 then + local warnFlash = math.sin(time * 8) > 0 and 255 or 100 + draw.SimpleText("⚠ НИЗКИЙ ЗАРЯД ⚠", "MuR_Font4", cx, 80, Color(255, 60, 60, warnFlash), TEXT_ALIGN_CENTER) + if time > lastBattBeep then + surface.PlaySound("buttons/button17.wav") + lastBattBeep = time + 0.8 + end + end + + if smoothHP < 0.25 then + local dmgFlash = math.sin(time * 6) > 0 and 255 or 100 + draw.SimpleText("⚠ КРИТИЧЕСКИЕ ПОВРЕЖДЕНИЯ ⚠", "MuR_Font3", cx, 130, Color(255, 120, 60, dmgFlash), TEXT_ALIGN_CENTER) + if time > lastDmgBeep then + surface.PlaySound("buttons/button10.wav") + lastDmgBeep = time + 1.5 + end + end + + if signal < 0.2 then + local sigFlash = math.sin(time * 10) > 0 and 255 or 100 + draw.SimpleText("◢◤ СЛАБЫЙ СИГНАЛ ◢◤", "MuR_Font3", cx, 180, Color(255, 80, 60, sigFlash), TEXT_ALIGN_CENTER) + end + DrawSignalNoise(signal) + if ent:GetNWBool("Jammed") then + local jamStrength = ent:GetNWFloat("JamStrength", 1) + + DrawHeavySignalNoise(jamStrength) + + local jamFlash = math.sin(time * 8) > 0 and 255 or 120 + draw.SimpleText( + "◢◤ ОБНАРУЖЕНЫ ПОМЕХИ ◢◤", + "MuR_Font3", + cx, + 220, + Color(255, 80, 60, jamFlash), + TEXT_ALIGN_CENTER + ) + end + + if killTime > CurTime() then + local left = math.max(0, killTime - CurTime()) + local flash = math.sin(CurTime() * 10) > 0 and 255 or 120 + + draw.SimpleText( + string.format("САМОУНИЧТОЖЕНИЕ: %.1f", left), + "MuR_Font3", + cx, + 260, + Color(255, 50, 50, flash), + TEXT_ALIGN_CENTER + ) + end + end) + + hook.Add("CreateMove", "DroneCam", function(cmd) + cmd:SetForwardMove(0) + cmd:SetSideMove(0) + cmd:RemoveKey(IN_JUMP) + cmd:RemoveKey(IN_USE) + end) + + hook.Add("CalcView", "DroneCam", function(ply, pos, angles, fov) + if not IsValid(ent) then + hook.Remove("CalcView", "DroneCam") + hook.Remove("CreateMove", "DroneCam") + hook.Remove("RenderScreenspaceEffects", "DroneCam") + hook.Remove("HUDPaint", "DroneCam") + return + end + + local dPos = ent:GetPos() + local dAng = ent:GetAngles() + local vel = ent:GetVelocity():Length() + + local offset = Vector(-8 - math.Clamp(vel * 0.015, 0, 5), 0, -2) + local camPos = dPos + ent:GetForward() * offset.x + ent:GetUp() * offset.z + + local camAng = Angle(dAng.p, dAng.y, dAng.r) + camAng.p = ply:EyeAngles().p + + local dynFov = 40 + math.Clamp(vel * 0.025, 0, 15) + dynFov = Lerp(Zoom, 20, dynFov) + + return { + origin = camPos, + angles = camAng, + fov = math.Clamp(dynFov, 20, 120), + drawviewer = true + } + end) + end) + + hook.Add("Think", "mur_drone_ThermalToggle", function() + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_grenade" then return end + + if input.IsKeyDown(KEY_LALT) and not ThermalKeyHeld then + ThermalKeyHeld = true + Thermal = not Thermal + + surface.PlaySound(Thermal and "sw/misc/nv_on.wav" or "sw/misc/nv_off.wav") + net.Start("MuR_ThermalState") + net.WriteBool(Thermal) + net.SendToServer() + end + + if not input.IsKeyDown(KEY_LALT) then + ThermalKeyHeld = false + end + end) + + local function ApplyXRay() + for _, v in ipairs(ents.GetAll()) do + if not v:GetModel() then continue end + if string.sub(v:GetModel(), -3) ~= "mdl" then continue end + + if v:IsPlayer() then + elseif v:IsNPC() then + elseif v.LVS or v.LFS then + else + continue + end + + local col = v:GetColor() + if col.a <= 0 then continue end + + if not DefClrs[v] then + DefClrs[v] = Color(col.r, col.g, col.b, col.a) + end + + local entmat = v:GetMaterial() + if not DefMats[v] then + DefMats[v] = entmat + end + + v:SetColor(Color(255,255,255,255)) + v:SetMaterial("xray/living") + end + end + + + hook.Add("RenderScene", "mur_drone_FLIR_Apply", function() + if not Thermal then return end + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_grenade" then return end + + if not DoXRay then + DoXRay = true + ApplyXRay() + end + end) + + local function RenderFLIR() + DrawColorModify(FLIR) + DrawBloom(0,1,1,1,0,0,0,0,0) + DrawMotionBlur(FrameTime() * 50, 0.5, 0.05) + DrawBokehDOF(0.15, 1, 12) + end + + hook.Add("HUDPaintBackground", "mur_drone_FLIR_Effects", function() + if not Thermal then return end + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_grenade" then return end + + RenderFLIR() + end) + + hook.Add("Think", "mur_drone_FLIR_AutoOff", function() + if Thermal and not IsValid(DRONE) then + Thermal = false + end + + if not Thermal and DoXRay then + DoXRay = false + + for ent, mat in pairs(DefMats) do + if IsValid(ent) then ent:SetMaterial(mat) end + end + for ent, clr in pairs(DefClrs) do + if IsValid(ent) then + ent:SetRenderMode(RENDERMODE_TRANSALPHA) + ent:SetColor(Color(clr.r, clr.g, clr.b, clr.a)) + end + end + + DefMats = {} + DefClrs = {} + end + end) + + hook.Add("Think", "mur_drone_Zoom", function() + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_grenade" then return end + + local delta = input.GetAnalogValue(ANALOG_MOUSE_WHEEL) + + if delta ~= 0 then + Zoom = Zoom - delta * 0.05 + + Zoom = math.Clamp(Zoom, 0, 1) + end + end) + + hook.Add("Think", "DroneEmergencyKey", function() + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_grenade" then return end + + if input.IsKeyDown(KEY_J) and not DRONE._KillKeyHeld then + DRONE._KillKeyHeld = true + + net.Start("MuR_DroneEmergency") + net.SendToServer() + end + + if not input.IsKeyDown(KEY_J) then + DRONE._KillKeyHeld = false + end + end) + + hook.Add("PlayerBindPress", "mur_drone_ZoomBlock", function(ply, bind, pressed) + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_grenade" then return end + + if bind == "invprev" then + Zoom = math.Clamp(Zoom - 0.1, 0, 1) + return true + end + + if bind == "invnext" then + Zoom = math.Clamp(Zoom + 0.1, 0, 1) + return true + end + end) +end diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/cl_init.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/init.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/init.lua new file mode 100644 index 0000000..1d10407 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/init.lua @@ -0,0 +1,417 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("MuR_DroneCam_Spy") +util.AddNetworkString("MuR_DroneLost_Spy") +util.AddNetworkString("MuR_ThermalState_Spy") +util.AddNetworkString("MuR_DroneEmergency_Spy") + +local rotorSounds = { + "ambient/machines/spin_loop.wav", + "ambient/machines/machine1_hit1.wav" +} + +local damageSounds = { + "physics/metal/metal_box_impact_bullet1.wav", + "physics/metal/metal_box_impact_bullet2.wav", + "physics/metal/metal_box_impact_bullet3.wav", + "physics/metal/metal_computer_impact_bullet1.wav", + "physics/metal/metal_computer_impact_bullet2.wav" +} + +local startupSounds = { + "buttons/button9.wav", + "buttons/button17.wav" +} + +function ENT:Initialize() + self:SetModel("models/murdered/weapons/drone_ex.mdl") + self:ResetSequence("idle") + self:SetHealth(self.HealthCount) + self:SetGravity(0) + self.Velo = {x = 0, y = 0, z = 0} + + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + + self.MultSpeed = 300 + self.MaxSpeed = 500 + self.BoostSpeed = 1600 + self.MaxPitch = 25 + self.MaxRoll = 30 + self.LinearDrag = 0.04 + self.TakeDamageWall = 0 + self.DeltaTime = 0 + self.LastBoostSound = 0 + self.LastWindSound = 0 + + self:SetNW2Float("RemoveTime", CurTime() + self.BatteryCount) + + local ply = self:GetCreator() + if IsValid(ply) then + local forward = ply:GetForward() + forward.z = 0 + forward = forward:GetNormalized() + + local spawnDist = 80 + local spawnHeight = 40 + + local spawnPos = ply:GetPos() + forward * spawnDist + Vector(0, 0, spawnHeight) + + local tr = util.TraceHull({ + start = spawnPos, + endpos = spawnPos, + mins = Vector(-20, -20, -20), + maxs = Vector(20, 20, 20), + filter = {ply, self} + }) + + if tr.Hit then + spawnPos = spawnPos + forward * 60 + end + + local ground = util.TraceLine({ + start = spawnPos, + endpos = spawnPos - Vector(0, 0, 5000), + filter = self + }) + + if spawnPos.z < ground.HitPos.z + 30 then + spawnPos.z = ground.HitPos.z + 30 + end + + self:SetPos(spawnPos) + self:SetAngles(Angle(0, ply:EyeAngles().y, 0)) + end + + self:EmitSound(startupSounds[math.random(#startupSounds)], 60, 100, 0.5) + + if CreateSound then + self.RotorSound = CreateSound(self, rotorSounds[1]) + if self.RotorSound then + self.RotorSound:PlayEx(0.2, 90) + end + + self.WindSound = CreateSound(self, "ambient/wind/wind_snippet2.wav") + if self.WindSound then + self.WindSound:PlayEx(0, 100) + end + end + + timer.Simple(0.1, function() + if not IsValid(self) then return end + local ply2 = self:GetCreator() + if not IsValid(ply2) then return end + + ply2.ControlDrone = self + + net.Start("MuR_DroneCam_Spy") + net.WriteEntity(self) + net.Send(ply2) + end) +end + +function ENT:OnRemove() + if self.RotorSound then self.RotorSound:Stop() end + if self.WindSound then self.WindSound:Stop() end + self:StopSound("ambient/machines/spin_loop.wav") + self:StopSound("ambient/wind/wind_snippet2.wav") +end + +function ENT:GetForwardAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Forward() +end + +function ENT:GetRightAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Right() +end + +function ENT:GetUpAbs() + local ang = self:GetAngles() + ang.x = 0 + ang.z = 0 + return ang:Up() +end + +function ENT:Think() + if self.EmergencyKill then + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + phys:SetVelocityInstantaneous(vector_origin) + end + + if CurTime() >= (self.EmergencyKillTime or 0) then + self:Explode() + return true + end + end + + if self:GetNW2Bool("ThermalActive") then + local drainMul = 2 -- во сколько раз быстрее тратится батарея (подбери значение по вкусу, биба) + local newRemove = self:GetNW2Float("RemoveTime") - FrameTime() * (drainMul - 1) + self:SetNW2Float("RemoveTime", newRemove) + end + + + local ply = self:GetCreator() + if not IsValid(ply) then + self:Remove() + return + end + + ply:SetActiveWeapon(nil) + + if self:GetNWBool("Jammed") then + + if not self.JamStart then + self.JamStart = CurTime() + self.JamEnd = CurTime() + 5 + end + if CurTime() >= self.JamEnd then + local ply = self:GetCreator() + if IsValid(ply) then + net.Start("MuR_DroneLost_Spy") + net.Send(ply) + if ply.ControlDrone == self then + ply.ControlDrone = nil + end + end + + self:Remove() + return true + end + + else + self.JamStart = nil + self.JamEnd = nil + end + + + local phys = self:GetPhysicsObject() + local mu = self.MultSpeed + local ms = self.MaxSpeed + local vel = phys:GetVelocity() + local pos = self:GetPos() + + local angYaw = ply:EyeAngles().y + local sang = Angle(0, angYaw, 0) + + local trace = {start = self:GetPos(), endpos = self:GetPos(), filter = self} + local tr = util.TraceEntity(trace, self) + + if self:GetNW2Bool("Boost") then + local fwd = self:GetForward() + phys:SetVelocityInstantaneous(fwd * self.Velo.x) + else + if tr.Hit then + phys:SetVelocityInstantaneous( + (self:GetForwardAbs() * self.Velo.x + + self:GetRightAbs() * self.Velo.y + + self:GetUpAbs() * self.Velo.z) / 2 + ) + if self.TakeDamageWall < CurTime() then + self:TakeDamage(1) + self.TakeDamageWall = CurTime() + 0.2 + end + else + phys:SetVelocityInstantaneous( + self:GetForwardAbs() * self.Velo.x + + self:GetRightAbs() * self.Velo.y + + self:GetUpAbs() * self.Velo.z + + Vector(0, 0, 15) + ) + end + end + + local ratioX = math.Clamp(self.Velo.x / ms, -1, 1) + local ratioY = math.Clamp(self.Velo.y / ms, -1, 1) + local targetPitch = ratioX * self.MaxPitch + local targetRoll = ratioY * self.MaxRoll + sang = Angle(targetPitch, angYaw, targetRoll) + + local b = {} + b.secondstoarrive = 1 + b.pos = self:GetPos() + b.angle = sang + b.maxangular = 140 + b.maxangulardamp = 60 + b.maxspeed = 1200 + b.maxspeeddamp = 180 + b.dampfactor = 0.3 + b.teleportdistance= 0 + b.deltatime = CurTime() - self.DeltaTime + phys:ComputeShadowControl(b) + + local tick = FrameTime() + + if ply:KeyDown(IN_FORWARD) or self:GetNW2Bool("Boost") then + if self:GetNW2Bool("Boost") then + self.Velo.x = math.Clamp(self.Velo.x + tick * mu * 2, -ms, self.BoostSpeed) + else + self.Velo.x = math.Clamp(self.Velo.x + tick * mu, -ms, ms) + end + elseif ply:KeyDown(IN_BACK) then + self.Velo.x = math.Clamp(self.Velo.x - tick * mu, -ms, ms) + end + + if ply:KeyDown(IN_MOVELEFT) then + self.Velo.y = math.Clamp(self.Velo.y - tick * mu, -ms, ms) + elseif ply:KeyDown(IN_MOVERIGHT) then + self.Velo.y = math.Clamp(self.Velo.y + tick * mu, -ms, ms) + end + + if ply:KeyDown(IN_DUCK) then + self.Velo.z = math.Clamp(self.Velo.z - tick * mu, -ms, ms) + elseif ply:KeyDown(IN_SPEED) then + self.Velo.z = math.Clamp(self.Velo.z + tick * mu, -ms, ms) + end + + local drag = 1 - math.Clamp(self.LinearDrag * tick, 0, 0.2) + self.Velo.x = self.Velo.x * drag + self.Velo.y = self.Velo.y * drag + self.Velo.z = self.Velo.z * (0.98 * drag) + + if not ply:KeyDown(IN_FORWARD) and not ply:KeyDown(IN_BACK) and not self:GetNW2Bool("Boost") then + if math.abs(self.Velo.x) > 5 then + self.Velo.x = math.Approach(self.Velo.x, 0, tick * mu * (self.Velo.x > 0 and 1 or -1)) + else + self.Velo.x = 0 + end + end + if not ply:KeyDown(IN_SPEED) and not ply:KeyDown(IN_DUCK) then + if math.abs(self.Velo.z) > 5 then + self.Velo.z = math.Approach(self.Velo.z, 0, tick * mu * (self.Velo.z > 0 and 1 or -1)) + else + self.Velo.z = 0 + end + end + if not ply:KeyDown(IN_MOVELEFT) and not ply:KeyDown(IN_MOVERIGHT) then + if math.abs(self.Velo.y) > 5 then + self.Velo.y = math.Approach(self.Velo.y, 0, tick * mu * (self.Velo.y > 0 and 1 or -1)) + else + self.Velo.y = 0 + end + end + + if self.RotorSound then + local spd = phys:GetVelocity():Length() + local load = math.Clamp((math.abs(self.Velo.x) + math.abs(self.Velo.y) + math.abs(self.Velo.z)) / (self.BoostSpeed * 0.6), 0, 1) + local timeLeft = math.max(self:GetNW2Float("RemoveTime") - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / self.BatteryCount, 0, 1) + local basePitch = 80 + 45 * load + math.Clamp(spd * 0.025, 0, 35) + local baseVol = 0.15 + 0.35 * load + basePitch = basePitch * (0.85 + 0.15 * battFrac) + + if battFrac < 0.15 then + basePitch = basePitch + math.sin(CurTime() * 15) * 5 + end + + self.RotorSound:ChangePitch(math.Clamp(basePitch, 55, 180), 0.08) + self.RotorSound:ChangeVolume(math.Clamp(baseVol, 0.05, 0.55), 0.08) + end + + if self.WindSound then + local spd = phys:GetVelocity():Length() + local windVol = math.Clamp(spd / 800, 0, 0.4) + local windPitch = 80 + math.Clamp(spd * 0.05, 0, 40) + self.WindSound:ChangeVolume(windVol, 0.15) + self.WindSound:ChangePitch(windPitch, 0.15) + end + + local maxDist = 12000 * 12000 + local dist2 = ply:GetPos():DistToSqr(self:GetPos()) + + if dist2 > maxDist then + net.Start("MuR_DroneLost_Spy") + net.Send(ply) + self:Break() + return true + end + + if self:Health() <= 0 then + net.Start("MuR_DroneLost_Spy") + net.Send(ply) + self:Break() + end + + if self:GetNW2Float("RemoveTime") < CurTime() or not ply:Alive() then + net.Start("MuR_DroneLost_Spy") + net.Send(ply) + self:EmitSound("ambient/machines/machine1_hit2.wav", 70, 80, 0.6) + self:Remove() + elseif ply:KeyDown(IN_RELOAD) and ply:GetPos():DistToSqr(self:GetPos()) < 50000 then + ply:Give("swep_drone_spy") + ply:SelectWeapon("swep_drone_spy") + self:EmitSound("buttons/button14.wav", 60, 100, 0.5) + self:Remove() + end + + self.DeltaTime = CurTime() + self:NextThink(CurTime()) + return true +end + +function ENT:PhysicsCollide(col) + if self.Velo.x > self.MaxSpeed and self:GetNW2Bool("Boost") then + self:EmitSound("physics/metal/metal_solid_impact_hard"..math.random(1,5)..".wav", 80, 100, 0.8) + self:TakeDamage(self:Health()) + end +end + +-- Визуальный взрыв без урона для EmergencyKill +function ENT:Explode() + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + util.Effect("Explosion", effectdata) + + self:EmitSound("ambient/explosions/explode_4.wav", 100, 100, 1) + self:Remove() +end + +function ENT:Break() + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + util.Effect("ManhackSparks", effectdata) + self:EmitSound("physics/metal/metal_box_break"..math.random(1,2)..".wav", 100, 100, 1) + self:Remove() +end + +function ENT:OnTakeDamage(dmgt) + local dmg = dmgt:GetDamage() + self:SetHealth(self:Health() - dmg) +end + +hook.Add("SetupPlayerVisibility", "GpincDroneCam_Spy", function(ply) + local drone = ply.ControlDrone + if IsValid(drone) and drone:GetClass() == "mur_drone_spy" then + AddOriginToPVS(drone:GetPos()) + end +end) + +net.Receive("MuR_ThermalState_Spy", function(len, ply) + local drone = ply.ControlDrone + if IsValid(drone) then + drone:SetNW2Bool("ThermalActive", net.ReadBool()) + end +end) + +-- Команда экстренного самоуничтожения от клиента +net.Receive("MuR_DroneEmergency_Spy", function(len, ply) + local drone = ply.ControlDrone + if not IsValid(drone) then return end + if drone:GetClass() ~= "mur_drone_spy" then return end + + if not drone.EmergencyKill then + drone.EmergencyKill = true + drone.EmergencyKillTime = CurTime() + 3 + drone:SetNWFloat("EmergencyCountdown", drone.EmergencyKillTime) + drone:EmitSound("buttons/button17.wav", 75, 100, 0.6) + end +end) diff --git a/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/shared.lua b/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/shared.lua new file mode 100644 index 0000000..9d63e17 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/mur_drone_spy/shared.lua @@ -0,0 +1,695 @@ +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "Recon Drone" +ENT.Spawnable = true +ENT.AutomaticFrameAdvance = true + +ENT.BatteryCount = 600 +ENT.HealthCount = 20 + +if CLIENT then + + --------------------------------------------------------- + -- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ HUD + --------------------------------------------------------- + + local function DrawScanlines(sw, sh, intensity) + surface.SetDrawColor(0, 0, 0, intensity * 15) + for i = 0, sh, 4 do + surface.DrawRect(0, i, sw, 1) + end + end + + local function DrawGlitchNoise(sw, sh, intensity) + if intensity < 0.3 then return end + local glitchCount = math.floor((1 - intensity) * 50) + surface.SetDrawColor(255, 255, 255, (1 - intensity) * 100) + for i = 1, glitchCount do + local x = math.random(0, sw) + local y = math.random(0, sh) + local w = math.random(20, 200) + surface.DrawRect(x, y, w, 2) + end + end + + local function DrawCornerBrackets(x, y, w, h, size, thickness, col) + surface.SetDrawColor(col) + surface.DrawRect(x, y, size, thickness) + surface.DrawRect(x, y, thickness, size) + surface.DrawRect(x + w - size, y, size, thickness) + surface.DrawRect(x + w - thickness, y, thickness, size) + surface.DrawRect(x, y + h - thickness, size, thickness) + surface.DrawRect(x, y + h - size, thickness, size) + surface.DrawRect(x + w - size, y + h - thickness, size, thickness) + surface.DrawRect(x + w - thickness, y + h - size, thickness, size) + end + + local function DrawCrosshair(cx, cy, size, gap, thickness, col) + surface.SetDrawColor(col) + surface.DrawRect(cx - size, cy - thickness/2, size - gap, thickness) + surface.DrawRect(cx + gap, cy - thickness/2, size - gap, thickness) + surface.DrawRect(cx - thickness/2, cy - size, thickness, size - gap) + surface.DrawRect(cx - thickness/2, cy + gap, thickness, size - gap) + end + + local function DrawArcSegment(cx, cy, radius, startAng, endAng, thickness, segments, col) + surface.SetDrawColor(col) + local step = (endAng - startAng) / segments + for i = 0, segments - 1 do + local a1 = math.rad(startAng + i * step) + local a2 = math.rad(startAng + (i + 1) * step) + local x1 = cx + math.cos(a1) * radius + local y1 = cy + math.sin(a1) * radius + local x2 = cx + math.cos(a2) * radius + local y2 = cy + math.sin(a2) * radius + surface.DrawLine(x1, y1, x2, y2) + end + end + + --------------------------------------------------------- + -- РЕНДЕР ПОСЛЕДНЕГО КАДРА + --------------------------------------------------------- + + local function GetDroneRT() + local name = "MuR_Drone_LastFrame" + return GetRenderTarget(name, ScrW(), ScrH()) + end + + local lastFrameMat = CreateMaterial("MuR_Drone_LastFrame_Mat", "UnlitGeneric", { + ["$basetexture"] = "MuR_Drone_LastFrame", + ["$ignorez"] = 1, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1 + }) + + --------------------------------------------------------- + -- МОЩНЫЕ ПОМЕХИ + --------------------------------------------------------- + + local function DrawHeavySignalNoise(strength) + if not strength then return end + if strength > 0.9 then return end + + local sw, sh = ScrW(), ScrH() + + local noiseCount = (1 - strength) * 400 + for i = 1, noiseCount do + surface.SetDrawColor(255, 255, 255, math.random(10, 60)) + surface.DrawRect(math.random(0, sw), math.random(0, sh), 1, 1) + end + + local lineCount = (1 - strength) * 60 + for i = 1, lineCount do + local y = math.random(0, sh) + local w = math.random(sw * 0.2, sw) + local x = math.random(0, sw - w) + surface.SetDrawColor(255, 255, 255, math.random(20, 80)) + surface.DrawRect(x, y, w, math.random(1, 3)) + end + + if strength < 0.4 then + local shift = math.random(-8, 8) * (1 - strength) + surface.SetDrawColor(255, 255, 255, 40) + surface.DrawRect(shift, 0, sw, sh) + end + + if strength < 0.3 then + for i = 1, 10 do + local x = math.random(0, sw) + surface.SetDrawColor(255, 255, 255, math.random(30, 80)) + surface.DrawRect(x, 0, math.random(2, 6), sh) + end + end + + if strength < 0.2 then + local shake = (0.2 - strength) * 6 + surface.SetDrawColor(255, 255, 255, 25) + surface.DrawRect( + math.random(-shake, shake), + math.random(-shake, shake), + sw, sh + ) + end + end + + --------------------------------------------------------- + -- ПЕРЕМЕННЫЕ КАМЕРЫ / FLIR + --------------------------------------------------------- + + local DRONE = nil + local Thermal = false + local ThermalKeyHeld = false + local Zoom = 1 + + local FPV_Yaw = 0 + local FPV_Pitch = 0 + + local DefMats = {} + local DefClrs = {} + local DoXRay = false + + local FLIR = { + ["$pp_colour_addr"] = -.3, + ["$pp_colour_addg"] = -.4, + ["$pp_colour_addb"] = -.4, + ["$pp_colour_brightness"] = .1, + ["$pp_colour_contrast"] = 1, + ["$pp_colour_colour"] = 0, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0, + } + + --------------------------------------------------------- + -- X-RAY ДЛЯ FLIR + --------------------------------------------------------- + + local function ApplyXRay() + for _, v in ipairs(ents.GetAll()) do + if not v:GetModel() then continue end + if string.sub(v:GetModel(), -3) ~= "mdl" then continue end + + if v:IsPlayer() then + elseif v:IsNPC() then + elseif v.LVS or v.LFS then + else + continue + end + + local col = v:GetColor() + if col.a <= 0 then continue end + + if not DefClrs[v] then + DefClrs[v] = Color(col.r, col.g, col.b, col.a) + end + + local entmat = v:GetMaterial() + if not DefMats[v] then + DefMats[v] = entmat + end + + v:SetColor(Color(255,255,255,255)) + v:SetMaterial("xray/living") + end + end + + local function RestoreXRayMaterials() + for ent2, mat in pairs(DefMats) do + if IsValid(ent2) then ent2:SetMaterial(mat) end + end + for ent2, clr in pairs(DefClrs) do + if IsValid(ent2) then + ent2:SetRenderMode(RENDERMODE_TRANSALPHA) + ent2:SetColor(Color(clr.r, clr.g, clr.b, clr.a)) + end + end + DefMats = {} + DefClrs = {} + DoXRay = false + end + + local function RenderFLIR() + DrawColorModify(FLIR) + DrawBloom(0,1,1,1,0,0,0,0,0) + DrawMotionBlur(FrameTime() * 50, 0.5, 0.05) + DrawBokehDOF(0.15, 1, 12) + end + + --------------------------------------------------------- + -- ЭКРАН ПОТЕРИ СВЯЗИ + --------------------------------------------------------- + + local function LoseDrone() + surface.PlaySound("ambient/levels/prison/radio_random"..math.random(1,15)..".wav") + + local startTime = CurTime() + lastFrameMat:SetTexture("$basetexture", GetDroneRT()) + + hook.Add("HUDPaint", "Spy_DroneCamLost", function() + local sw, sh = ScrW(), ScrH() + local elapsed = CurTime() - startTime + local flicker = math.sin(elapsed * 30) > 0 + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, sw, sh) + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(lastFrameMat) + surface.DrawTexturedRect(0, 0, sw, sh) + + surface.SetDrawColor(0, 0, 0, math.min(elapsed * 100, 200)) + surface.DrawRect(0, 0, sw, sh) + + for i = 1, 150 do + local px = math.random(0, sw) + local py = math.random(0, sh) + local ps = math.random(2, 8) + local c = math.random(30, 80) + surface.SetDrawColor(c, c, c, 255) + surface.DrawRect(px, py, ps, ps) + end + + DrawScanlines(sw, sh, 3) + + for i = 1, 20 do + local y = math.random(0, sh) + local w = math.random(sw * 0.3, sw) + local x = math.random(0, sw - w) + surface.SetDrawColor(40, 40, 45, 200) + surface.DrawRect(x, y, w, math.random(1, 5)) + end + + local textCol = flicker and Color(255, 50, 50, 255) or Color(200, 40, 40, 200) + draw.SimpleText("▌▌ СОЕДИНЕНИЕ ПОТЕРЯНО ▐▐", "MuR_Font5", sw/2, sh/2 - 30, textCol, TEXT_ALIGN_CENTER) + draw.SimpleText("СВЯЗЬ ПРЕКРАЩЕНА", "MuR_Font2", sw/2, sh/2 + 30, Color(150,150,150,200), TEXT_ALIGN_CENTER) + + DrawHeavySignalNoise(0.05) + end) + + timer.Simple(2, function() + hook.Remove("HUDPaint", "Spy_DroneCamLost") + end) + end + + net.Receive("MuR_DroneLost_Spy", LoseDrone) + + --------------------------------------------------------- + -- Синхронизация состояния тепловизора от сервера + -- (перенесено сюда чтобы менять локальную переменную Thermal) + --------------------------------------------------------- + net.Receive("MuR_ThermalState_Spy", function() + local state = net.ReadBool() + Thermal = state + + if not Thermal then + -- гарантированный сброс XRay и возврат материалов + if DoXRay then + RestoreXRayMaterials() + end + end + end) + + --------------------------------------------------------- + -- ПОЛУЧЕНИЕ ДРОНА / ОСНОВНОЙ HUD + --------------------------------------------------------- + + net.Receive("MuR_DroneCam_Spy", function() + local ent = net.ReadEntity() + DRONE = ent + Thermal = false + Zoom = 1 + + if IsValid(ent) then + FPV_Yaw = ent:GetAngles().y + FPV_Pitch = 0 + end + + local pulseAlpha = 0 + local pulseDir = 1 + local startTime = CurTime() + local smoothBatt = 1 + local smoothHP = 1 + + local ppTab = { + ["$pp_colour_addr"] = 0, + ["$pp_colour_addg"] = 0.02, + ["$pp_colour_addb"] = 0.05, + ["$pp_colour_brightness"] = -0.01, + ["$pp_colour_contrast"] = 1.1, + ["$pp_colour_colour"] = 0.6, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0.05, + ["$pp_colour_mulb"] = 0.05 + } + + hook.Add("RenderScreenspaceEffects", "Spy_DroneCam", function() + if not IsValid(ent) then return end + + local rt = GetDroneRT() + render.CopyRenderTargetToTexture(rt) + + local timeLeft = math.max(ent:GetNW2Float("RemoveTime") - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1) + + if battFrac < 0.2 then + ppTab["$pp_colour_addr"] = 0.1 * (1 - battFrac / 0.2) + ppTab["$pp_colour_contrast"] = 1.15 + 0.2 * (1 - battFrac / 0.2) + else + ppTab["$pp_colour_addr"] = 0 + ppTab["$pp_colour_contrast"] = 1.15 + end + + DrawColorModify(ppTab) + DrawSharpen(0.8, 0.8) + end) + + ----------------------------------------------------- + -- FLIR / X-RAY + ----------------------------------------------------- + + hook.Add("RenderScene", "spy_drone_FLIR_Apply", function() + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + if not Thermal then return end + if not DRONE:GetNW2Bool("ThermalActive") then return end + + if not DoXRay then + DoXRay = true + ApplyXRay() + end + end) + + hook.Add("HUDPaintBackground", "spy_drone_FLIR_Effects", function() + if not Thermal then return end + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + RenderFLIR() + end) + + hook.Add("Think", "spy_drone_FLIR_AutoOff", function() + -- если тепловизор включён и дрон валиден — ничего не делаем + if Thermal and IsValid(DRONE) then return end + + -- иначе — сбрасываем XRay и возвращаем материалы + if DoXRay then + DoXRay = false + + for ent2, mat in pairs(DefMats) do + if IsValid(ent2) then ent2:SetMaterial(mat) end + end + for ent2, clr in pairs(DefClrs) do + if IsValid(ent2) then + ent2:SetRenderMode(RENDERMODE_TRANSALPHA) + ent2:SetColor(Color(clr.r, clr.g, clr.b, clr.a)) + end + end + + DefMats = {} + DefClrs = {} + end + end) + + --------------------------------------------------------- + -- HUD КАМЕРЫ (ОСНОВНОЙ) + --------------------------------------------------------- + + hook.Add("HUDPaint", "Spy_DroneCam", function() + if not IsValid(ent) then return end + + local sw, sh = ScrW(), ScrH() + local cx, cy = sw / 2, sh / 2 + local time = CurTime() + local elapsed = time - startTime + + pulseAlpha = pulseAlpha + pulseDir * FrameTime() * 400 + if pulseAlpha >= 255 then pulseDir = -1 pulseAlpha = 255 + elseif pulseAlpha <= 100 then pulseDir = 1 pulseAlpha = 100 end + + local timeLeft = math.max(ent:GetNW2Float("RemoveTime") - CurTime(), 0) + local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1) + local hpFrac = math.Clamp(ent:Health() / ent.HealthCount, 0, 1) + + smoothBatt = Lerp(FrameTime() * 3, smoothBatt, battFrac) + smoothHP = Lerp(FrameTime() * 5, smoothHP, hpFrac) + + local vel = ent:GetVelocity():Length() + local spd = math.Round(vel * 0.068) + + local tr = util.TraceLine({ + start = ent:GetPos(), + endpos = ent:GetPos() - Vector(0,0,10000), + filter = ent + }) + + local agl = math.Round(ent:GetPos().z - tr.HitPos.z) + + local dist2 = LocalPlayer():GetPos():DistToSqr(ent:GetPos()) + local signal = math.Clamp(1 - (dist2 / (8000 * 8000)), 0, 1) + + + local killTime = ent:GetNWFloat("EmergencyCountdown", 0) + + DrawScanlines(sw, sh, 20) + DrawGlitchNoise(sw, sh, signal) + + local frameCol = Color(100, 200, 255, 120) + DrawCornerBrackets(40, 40, sw - 80, sh - 80, 60, 3, frameCol) + + local crossCol = Color(100, 200, 255, pulseAlpha) + DrawCrosshair(cx, cy, 25, 8, 2, crossCol) + + surface.SetDrawColor(100, 200, 255, 60) + DrawArcSegment(cx, cy, 45, 0, 360, 1, 64, Color(100,200,255,40)) + + draw.SimpleText("◈ РАЗВЕДЫВАТЕЛЬНЫЙ ДРОН", "MuR_Font3", 80, 60, Color(100,200,255), TEXT_ALIGN_LEFT) + draw.SimpleText(string.format("ВРЕМЯ РАБОТЫ %.1fs", elapsed), "MuR_Font1", 80, 95, Color(150,150,150), TEXT_ALIGN_LEFT) + draw.SimpleText(os.date("%H:%M:%S"), "MuR_Font1", 80, 115, Color(150,150,150), TEXT_ALIGN_LEFT) + + local telemX = sw - 80 + local spdCol = spd > 100 and Color(255, 200, 60) or Color(100, 200, 255) + draw.SimpleText(string.format("%03d km/h", spd), "MuR_Font3", telemX, 60, spdCol, TEXT_ALIGN_RIGHT) + draw.SimpleText("СКОР", "MuR_Font1", telemX, 95, Color(100,100,100), TEXT_ALIGN_RIGHT) + + draw.SimpleText(string.format("%04d m", agl), "MuR_Font3", telemX, 120, Color(100,200,255), TEXT_ALIGN_RIGHT) + draw.SimpleText("ВЫС", "MuR_Font1", telemX, 155, Color(100,100,100), TEXT_ALIGN_RIGHT) + + local sigCol = signal < 0.25 and Color(255, 60, 60) + or (signal < 0.5 and Color(255, 200, 60) or Color(100, 200, 255)) + local sigBars = math.floor(signal * 5) + local sigStr = string.rep("▮", sigBars) .. string.rep("▯", 5 - sigBars) + draw.SimpleText(sigStr, "MuR_Font2", telemX, 180, sigCol, TEXT_ALIGN_RIGHT) + draw.SimpleText(string.format("%d%%", math.Round(signal * 100)), "MuR_Font1", telemX, 210, sigCol, TEXT_ALIGN_RIGHT) + + local barW, barH = 250, 12 + local barX, barY = 80, sh - 180 + + local battCol = smoothBatt < 0.2 and Color(255,60,60) + or (smoothBatt < 0.4 and Color(255,200,60) or Color(100,200,255)) + surface.SetDrawColor(30,30,30,200) + surface.DrawRect(barX, barY, barW, barH) + surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 255) + surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothBatt, barH - 4) + surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 100) + surface.DrawOutlinedRect(barX, barY, barW, barH, 1) + draw.SimpleText(string.format("ЗАРЯД %d%%", math.Round(smoothBatt * 100)), "MuR_Font1", barX, barY - 20, battCol, TEXT_ALIGN_LEFT) + draw.SimpleText(string.format("%.0fs", timeLeft), "MuR_Font1", barX + barW, barY - 20, Color(150,150,150), TEXT_ALIGN_RIGHT) + + barY = sh - 130 + local hpCol = smoothHP < 0.3 and Color(255,60,60) + or (smoothHP < 0.6 and Color(255,200,60) or Color(100,200,255)) + surface.SetDrawColor(30,30,30,200) + surface.DrawRect(barX, barY, barW, barH) + surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 255) + surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothHP, barH - 4) + surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 100) + surface.DrawOutlinedRect(barX, barY, barW, barH, 1) + draw.SimpleText(string.format("ПРОЧНОСТЬ %d%%", math.Round(smoothHP * 100)), "MuR_Font1", barX, barY - 20, hpCol, TEXT_ALIGN_LEFT) + + -- Блок управления + local ctrlX, ctrlY = sw - 80, sh - 200 + local ctrlCol = Color(150,150,150,200) + draw.SimpleText("▶ [J] САМОУНИЧТОЖЕНИЕ", "MuR_Font1", ctrlX, ctrlY, Color(255,80,80,200), TEXT_ALIGN_RIGHT) + draw.SimpleText("▲ [SHIFT] ВВЕРХ", "MuR_Font1", ctrlX, ctrlY + 22, ctrlCol, TEXT_ALIGN_RIGHT) + draw.SimpleText("▼ [CTRL] ВНИЗ", "MuR_Font1", ctrlX, ctrlY + 44, ctrlCol, TEXT_ALIGN_RIGHT) + draw.SimpleText("▶ [ALT] ТЕПЛОВИЗОР", "MuR_Font1", ctrlX, ctrlY + 66, Color(100,200,255), TEXT_ALIGN_RIGHT) + + if smoothBatt < 0.15 then + local warnFlash = math.sin(time * 8) > 0 and 255 or 100 + draw.SimpleText("⚠ НИЗКИЙ ЗАРЯД ⚠", "MuR_Font4", cx, 80, Color(255,60,60,warnFlash), TEXT_ALIGN_CENTER) + end + + if smoothHP < 0.25 then + local dmgFlash = math.sin(time * 6) > 0 and 255 or 100 + draw.SimpleText("⚠ КРИТИЧЕСКИЕ ПОВРЕЖДЕНИЯ ⚠", "MuR_Font3", cx, 130, Color(255,120,60,dmgFlash), TEXT_ALIGN_CENTER) + end + + if signal < 0.2 then + local sigFlash = math.sin(time * 10) > 0 and 255 or 100 + draw.SimpleText("◢◤ СЛАБЫЙ СИГНАЛ ◢◤", "MuR_Font3", cx, 180, Color(255, 80, 60, sigFlash), TEXT_ALIGN_CENTER) + end + DrawGlitchNoise(sw, sh, signal) + if ent:GetNWBool("Jammed") then + local jamStrength = ent:GetNWFloat("JamStrength", 1) + + DrawHeavySignalNoise(jamStrength) + + local jamFlash = math.sin(time * 8) > 0 and 255 or 120 + draw.SimpleText( + "◢◤ ОБНАРУЖЕНЫ ПОМЕХИ ◢◤", + "MuR_Font3", + cx, + 220, + Color(255, 80, 60, jamFlash), + TEXT_ALIGN_CENTER + ) + end + + -- Обратный отсчёт самоуничтожения + if killTime > CurTime() then + local left = math.max(0, killTime - CurTime()) + local flash = math.sin(CurTime() * 10) > 0 and 255 or 120 + + draw.SimpleText( + string.format("САМОУНИЧТОЖЕНИЕ: %.1f", left), + "MuR_Font3", + cx, + 260, + Color(255, 50, 50, flash), + TEXT_ALIGN_CENTER + ) + end + + DrawHeavySignalNoise(signal) + end) + hook.Add("CreateMove", "Spy_Drone_BlockWeapons", function(cmd) + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + cmd:RemoveKey(IN_ATTACK) + cmd:RemoveKey(IN_ATTACK2) + cmd:RemoveKey(IN_RELOAD) + cmd:RemoveKey(IN_USE) + cmd:SetImpulse(0) + end) + end) + + --------------------------------------------------------- + -- КАМЕРА (CalcView) + --------------------------------------------------------- + + hook.Add("CalcView", "Spy_DroneCam_Global", function(ply, pos, angles, fov) + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + local dPos = DRONE:GetPos() + local vel = DRONE:GetVelocity():Length() + + local offset = Vector(-8 - math.Clamp(vel * 0.015, 0, 5), 0, -2) + local camPos = dPos + DRONE:GetForward() * offset.x + DRONE:GetUp() * offset.z + + local camAng = Angle(FPV_Pitch, FPV_Yaw, 0) + + local baseFov = 60 + local speedBoost = math.Clamp(vel * 0.025, 0, 15) + local dynFov = baseFov + speedBoost + dynFov = Lerp(Zoom, 20, dynFov) + + dynFov = Lerp(Zoom, 20, dynFov) + + return { + origin = camPos, + angles = camAng, + fov = math.Clamp(dynFov, 20, 120), + drawviewer = true + } + end) + + hook.Add("Think", "spy_drone_Zoom", function() + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + local delta = input.GetAnalogValue(ANALOG_MOUSE_WHEEL) + if delta ~= 0 then + Zoom = Zoom - delta * 0.05 + Zoom = math.Clamp(Zoom, 0, 1) + end + end) + + -- Перехват invprev/invnext чтобы колесо и кнопки не переключали оружие + hook.Add("PlayerBindPress", "spy_drone_ZoomBlock", function(ply, bind, pressed) + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + if bind == "invprev" then + Zoom = math.Clamp(Zoom - 0.1, 0, 1) + return true + end + + if bind == "invnext" then + Zoom = math.Clamp(Zoom + 0.1, 0, 1) + return true + end + end) + + --------------------------------------------------------- + -- ТЕПЛОВИЗОР (кнопка) + --------------------------------------------------------- + + hook.Add("Think", "spy_drone_ThermalToggle", function() + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + if input.IsKeyDown(KEY_LALT) and not ThermalKeyHeld then + ThermalKeyHeld = true + Thermal = not Thermal + + surface.PlaySound(Thermal and "sw/misc/nv_on.wav" or "sw/misc/nv_off.wav") + + net.Start("MuR_ThermalState_Spy") + net.WriteBool(Thermal) + net.SendToServer() + end + + if not input.IsKeyDown(KEY_LALT) then + ThermalKeyHeld = false + end + end) + + --------------------------------------------------------- + -- FPV МЫШЬ + --------------------------------------------------------- + + hook.Add("CreateMove", "Spy_Drone_FPV_Mouse", function(cmd) + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + local mx = cmd:GetMouseX() + local my = cmd:GetMouseY() + + FPV_Yaw = FPV_Yaw - mx * 0.03 + FPV_Pitch = FPV_Pitch + my * 0.03 + FPV_Pitch = math.Clamp(FPV_Pitch, -45, 45) + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + cmd:SetForwardMove(0) + cmd:SetSideMove(0) + + DRONE:SetNW2Float("FPV_Yaw", FPV_Yaw) + DRONE:SetNW2Float("FPV_Pitch", FPV_Pitch) + end) + + --------------------------------------------------------- + -- КЛАВИША G ДЛЯ САМОУНИЧТОЖЕНИЯ + --------------------------------------------------------- + + hook.Add("Think", "spy_drone_EmergencyKey", function() + if not IsValid(DRONE) then return end + if DRONE:GetClass() ~= "mur_drone_spy" then return end + + DRONE._KillKeyHeld = DRONE._KillKeyHeld or false + + if input.IsKeyDown(KEY_J) and not DRONE._KillKeyHeld then + DRONE._KillKeyHeld = true + + net.Start("MuR_DroneEmergency_Spy") + net.SendToServer() + end + + if not input.IsKeyDown(KEY_J) then + DRONE._KillKeyHeld = false + end + end) + + -- Подстраховка: если дрон удалён — вернуть материалы и очистить переменные + hook.Add("Think", "spy_drone_cleanup_on_remove", function() + if DRONE and not IsValid(DRONE) then + DRONE = nil + Thermal = false + RestoreXRayMaterials() + end + end) + +end diff --git a/garrysmod/addons/ft_drones/lua/entities/sw_sania/cl_init.lua b/garrysmod/addons/ft_drones/lua/entities/sw_sania/cl_init.lua new file mode 100644 index 0000000..ed17ae8 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/sw_sania/cl_init.lua @@ -0,0 +1,49 @@ + +include("shared.lua") +if CLIENT then +local jammat = Material( "models/shtormer/sania/jam.png" ) +local flag1 = false + + function ENT:Think() + if LocalPlayer():lvsGetVehicle():IsValid() then + if LocalPlayer():lvsGetVehicle():GetNWEntity("UAVControl"):IsValid() then + self:SetNWEntity("Jamming", LocalPlayer():lvsGetVehicle()) + jammer = ents.FindByClass( "sw_sania" ) + if jammer != nil then + for i =1, #jammer do + if jammer[i]:IsValid() then + if LocalPlayer():lvsGetVehicle():GetPos():Distance(jammer[i]:GetPos()) < jammer[i].JamDistance then + flag1 = true + end + end + end + if LocalPlayer():lvsGetVehicle():GetNWBool("Jammed") and !flag1 then + LocalPlayer():lvsGetVehicle():SetNWBool("Jammed", false) + flag1 = false + elseif flag1 then + LocalPlayer():lvsGetVehicle():SetNWBool("Jammed", true) + end + else + flag1 = false + end + end + if LocalPlayer():lvsGetVehicle():GetNWBool("Jammed") and LocalPlayer():lvsGetVehicle():GetPos():Distance(self:GetPos()) < self.JamDistance and self:IsValid() and LocalPlayer():lvsGetVehicle():IsValid() and self != NULL then + print(LocalPlayer():lvsGetVehicle():GetNWEntity("UAVControl"):IsValid()) + hook.Add( "HUDPaint", "uavjamsania", function() + if self != NULL and LocalPlayer():lvsGetVehicle() != NULL then + surface.SetDrawColor( 255, 255, 255, 255/(LocalPlayer():lvsGetVehicle():GetPos():Distance(self:GetPos())/self.FullJamDistance) ) -- Set the drawing color + surface.SetMaterial( jammat ) -- Use our cached material + surface.DrawTexturedRect( 0, 0, ScrW(), ScrH() ) -- Actually draw the rectangle + end + end ) + elseif !LocalPlayer():lvsGetVehicle():GetNWBool("Jammed") or !LocalPlayer():lvsGetVehicle():IsValid() or !self:IsValid() or self == NULL or LocalPlayer():lvsGetVehicle() == NULL or LocalPlayer():lvsGetVehicle():GetNWEntity("UAVControl"):IsValid() == NULL then + hook.Remove( "HUDPaint", "uavjamsania" ) + end + end + end + + function ENT:Draw() + self:DrawModel() + end + +end diff --git a/garrysmod/addons/ft_drones/lua/entities/sw_sania/init.lua b/garrysmod/addons/ft_drones/lua/entities/sw_sania/init.lua new file mode 100644 index 0000000..5ccad88 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/sw_sania/init.lua @@ -0,0 +1,158 @@ +AddCSLuaFile("shared.lua") +AddCSLuaFile("cl_init.lua") +include("shared.lua") +AddCSLuaFile("autorun/mur_drone_fx.lua") + +if SERVER then + +function ENT:Initialize() + self:SetModel("models/shtormer/sania.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetNWBool("JamEnabled", true) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then phys:Wake() end + + self.HP = self.HP or 500 +end + +function ENT:OnTakeDamage(dmginfo) + local dmg = dmginfo:GetDamage() + self.HP = self.HP - dmg + if self.HP <= 0 then + self:Remove() + end +end + +-- Глушение дронов +function ENT:JamDrone(drone) + if not IsValid(drone) then return end + + local dist = drone:GetPos():Distance(self:GetPos()) + local jammed = dist <= self.JamDistance + + drone:SetNWBool("Jammed", jammed) + + if jammed then + drone:SetNWFloat("JamStrength", 1 - dist / self.JamDistance) + else + drone:SetNWFloat("JamStrength", 0) + end +end + +hook.Add("KeyPress", "SaniaToggle", function(ply, key) + if key ~= IN_USE then return end + + local tr = ply:GetEyeTrace() + local ent = tr.Entity + + if not IsValid(ent) then return end + if ent:GetClass() ~= "sw_sania" then return end + if tr.HitPos:Distance(ply:GetPos()) > 150 then return end + + local newState = not ent:GetNWBool("JamEnabled") + ent:SetNWBool("JamEnabled", newState) + + if newState then + ply:ChatPrint("РЭБ: ВКЛЮЧЕНА") + ent:EmitSound("buttons/button3.wav", 75, 120) + else + ply:ChatPrint("РЭБ: ВЫКЛЮЧЕНА") + ent:EmitSound("buttons/button19.wav", 75, 80) + end +end) + + + +function ENT:Think() + local sph = ents.FindInSphere(self:GetPos(), self.JamDistance) + if not self:GetNWBool("JamEnabled") then + for drone, _ in pairs(self.JammedDrones or {}) do + if IsValid(drone) then + drone:SetNWBool("Jammed", false) + drone:SetNWFloat("JamStrength", 0) + end + end + self.JammedDrones = {} + self:NextThink(CurTime() + 0.1) + return true + end + self.JammedDrones = self.JammedDrones or {} + + -- временная таблица для отметки "кто сейчас в зоне" + local inZone = {} + + for i = 1, #sph do + local ent = sph[i] + if not IsValid(ent) then continue end + + local class = ent:GetClass() + + -- 1) Игрок в UAV + if ent:IsPlayer() then + local veh = ent:lvsGetVehicle() + if IsValid(veh) then + local uav = veh:GetNWEntity("UAVControl") + if IsValid(uav) then + local dist = veh:GetPos():Distance(self:GetPos()) + + if dist < self.JamDistance then + if not self._savedRates then + self._savedRates = { + pitch = veh.TurnRatePitch, + yaw = veh.TurnRateYaw, + roll = veh.TurnRateRoll + } + end + + local mul = math.max(0.1, dist / self.FullJamDistance) + + veh.TurnRatePitch = self._savedRates.pitch * mul + veh.TurnRateYaw = self._savedRates.yaw * mul + veh.TurnRateRoll = self._savedRates.roll * mul + else + if self._savedRates then + veh.TurnRatePitch = self._savedRates.pitch + veh.TurnRateYaw = self._savedRates.yaw + veh.TurnRateRoll = self._savedRates.roll + self._savedRates = nil + end + end + end + end + end + + -- 2) Дроны + if class == "mur_drone_entity" + or class == "mur_drone_grenade" + or class == "mur_drone_spy" then + + inZone[ent] = true + self.JammedDrones[ent] = true + + local dist = ent:GetPos():Distance(self:GetPos()) + ent:SetNWBool("Jammed", true) + ent:SetNWFloat("JamStrength", 1 - dist / self.JamDistance) + end + end + + for drone, _ in pairs(self.JammedDrones) do + if not IsValid(drone) then + self.JammedDrones[drone] = nil + continue + end + + if not inZone[drone] then + drone:SetNWBool("Jammed", false) + drone:SetNWFloat("JamStrength", 0) + self.JammedDrones[drone] = nil + end + end + + self:NextThink(CurTime() + 0.1) + return true +end + +end diff --git a/garrysmod/addons/ft_drones/lua/entities/sw_sania/shared.lua b/garrysmod/addons/ft_drones/lua/entities/sw_sania/shared.lua new file mode 100644 index 0000000..157c376 --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/entities/sw_sania/shared.lua @@ -0,0 +1,15 @@ +ENT.Base = "base_gmodentity" +ENT.Type = "anim" + +ENT.PrintName = "Sania UAV Jammer" -- название энтити в меню Q +ENT.Author = "Shtormer" -- автор +ENT.Contact = "" -- контакты с автором +ENT.Information = "" -- описание энтити +ENT.Category = "SW Weapons Factory" -- категория в меню Q +ENT.Model = "models/shtormer/sania.mdl" +ENT.Spawnable = true -- разрешается спавнить через спавн-меню Q +ENT.AdminSpawnable = false -- разрешается спавнить только админам +ENT.JamDistance = 5000 +ENT.FullJamDistance = 2000 +ENT.HP = 200 +ENT.JamEnabled = true diff --git a/garrysmod/addons/ft_drones/lua/weapons/swep_drone.lua b/garrysmod/addons/ft_drones/lua/weapons/swep_drone.lua new file mode 100644 index 0000000..f5f295f --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/weapons/swep_drone.lua @@ -0,0 +1,94 @@ +AddCSLuaFile() + +SWEP.PrintName = "FPV дрон" +SWEP.DrawWeaponInfoBox = false + +SWEP.Slot = 4 +SWEP.SlotPos = 1 + +SWEP.Spawnable = true + +SWEP.ViewModel = "models/murdered/weapons/drone_ex.mdl" +SWEP.WorldModel = "models/murdered/weapons/drone_ex.mdl" +SWEP.ViewModelFOV = 54 +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.DrawAmmo = false + +function SWEP:Initialize() + self:SetHoldType("smg") +end + +function SWEP:PrimaryAttack() + if SERVER then + local ply = self.Owner + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector()*48, + filter = ply, + mask = MASK_SHOT + }) + if tr.Hit then return end + local SpawnPos = tr.HitPos + local prop = ents.Create('mur_drone_entity') + prop:SetPos(SpawnPos) + prop:SetAngles(ply:GetAngles()) + prop:SetCreator(ply) + prop:Spawn() + ply:ScreenFade(SCREENFADE.IN, color_black, 0.5, 0.5) + self:Remove() + end +end + +function SWEP:SecondaryAttack() end + +function SWEP:Reload() end + +if CLIENT then + local WorldModel = ClientsideModel(SWEP.WorldModel) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local _Owner = self:GetOwner() + + if (IsValid(_Owner)) then + local offsetVec = Vector(20, -2, 0) + local offsetAng = Angle(170, 180, 0) + + local boneid = _Owner:LookupBone("ValveBiped.Bip01_R_Hand") + if !boneid then return end + + local matrix = _Owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + end + + WorldModel:DrawModel() + end + + function SWEP:CalcViewModelView(ViewModel, OldEyePos, OldEyeAng, EyePos, EyeAng) + local pos = EyePos-EyeAng:Up()*16+EyeAng:Forward()*32 + local ang = EyeAng + return pos, ang + end +end +SWEP.Category = "RP Drones" diff --git a/garrysmod/addons/ft_drones/lua/weapons/swep_drone_grenade.lua b/garrysmod/addons/ft_drones/lua/weapons/swep_drone_grenade.lua new file mode 100644 index 0000000..7a31eef --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/weapons/swep_drone_grenade.lua @@ -0,0 +1,94 @@ +AddCSLuaFile() + +SWEP.PrintName = "Дрон со сбросом" +SWEP.DrawWeaponInfoBox = false + +SWEP.Slot = 4 +SWEP.SlotPos = 1 + +SWEP.Spawnable = true + +SWEP.ViewModel = "models/murdered/weapons/drone_ex.mdl" +SWEP.WorldModel = "models/murdered/weapons/drone_ex.mdl" +SWEP.ViewModelFOV = 54 +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.DrawAmmo = false + +function SWEP:Initialize() + self:SetHoldType("smg") +end + +function SWEP:PrimaryAttack() + if SERVER then + local ply = self.Owner + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector()*48, + filter = ply, + mask = MASK_SHOT + }) + if tr.Hit then return end + local SpawnPos = tr.HitPos + local prop = ents.Create('mur_drone_grenade') + prop:SetPos(SpawnPos) + prop:SetAngles(ply:GetAngles()) + prop:SetCreator(ply) + prop:Spawn() + ply:ScreenFade(SCREENFADE.IN, color_black, 0.5, 0.5) + self:Remove() + end +end + +function SWEP:SecondaryAttack() end + +function SWEP:Reload() end + +if CLIENT then + local WorldModel = ClientsideModel(SWEP.WorldModel) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local _Owner = self:GetOwner() + + if (IsValid(_Owner)) then + local offsetVec = Vector(20, -2, 0) + local offsetAng = Angle(170, 180, 0) + + local boneid = _Owner:LookupBone("ValveBiped.Bip01_R_Hand") + if !boneid then return end + + local matrix = _Owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + end + + WorldModel:DrawModel() + end + + function SWEP:CalcViewModelView(ViewModel, OldEyePos, OldEyeAng, EyePos, EyeAng) + local pos = EyePos-EyeAng:Up()*16+EyeAng:Forward()*32 + local ang = EyeAng + return pos, ang + end +end +SWEP.Category = "RP Drones" diff --git a/garrysmod/addons/ft_drones/lua/weapons/swep_drone_spy.lua b/garrysmod/addons/ft_drones/lua/weapons/swep_drone_spy.lua new file mode 100644 index 0000000..850a25f --- /dev/null +++ b/garrysmod/addons/ft_drones/lua/weapons/swep_drone_spy.lua @@ -0,0 +1,94 @@ +AddCSLuaFile() + +SWEP.PrintName = "Разведывательный дрон" +SWEP.DrawWeaponInfoBox = false + +SWEP.Slot = 4 +SWEP.SlotPos = 1 + +SWEP.Spawnable = true + +SWEP.ViewModel = "models/murdered/weapons/drone_ex.mdl" +SWEP.WorldModel = "models/murdered/weapons/drone_ex.mdl" +SWEP.ViewModelFOV = 54 +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.DrawAmmo = false + +function SWEP:Initialize() + self:SetHoldType("smg") +end + +function SWEP:PrimaryAttack() + if SERVER then + local ply = self.Owner + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector()*48, + filter = ply, + mask = MASK_SHOT + }) + if tr.Hit then return end + local SpawnPos = tr.HitPos + local prop = ents.Create('mur_drone_spy') + prop:SetPos(SpawnPos) + prop:SetAngles(ply:GetAngles()) + prop:SetCreator(ply) + prop:Spawn() + ply:ScreenFade(SCREENFADE.IN, color_black, 0.5, 0.5) + self:Remove() + end +end + +function SWEP:SecondaryAttack() end + +function SWEP:Reload() end + +if CLIENT then + local WorldModel = ClientsideModel(SWEP.WorldModel) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local _Owner = self:GetOwner() + + if (IsValid(_Owner)) then + local offsetVec = Vector(20, -2, 0) + local offsetAng = Angle(170, 180, 0) + + local boneid = _Owner:LookupBone("ValveBiped.Bip01_R_Hand") + if !boneid then return end + + local matrix = _Owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + end + + WorldModel:DrawModel() + end + + function SWEP:CalcViewModelView(ViewModel, OldEyePos, OldEyeAng, EyePos, EyeAng) + local pos = EyePos-EyeAng:Up()*16+EyeAng:Forward()*32 + local ang = EyeAng + return pos, ang + end +end +SWEP.Category = "RP Drones" diff --git a/garrysmod/addons/govorilka/addon.json b/garrysmod/addons/govorilka/addon.json new file mode 100644 index 0000000..bca795c --- /dev/null +++ b/garrysmod/addons/govorilka/addon.json @@ -0,0 +1,12 @@ +{ + "title": "Говорилка - Google Ver.", + "type": "tool", + "tags": [ + "fun", + "roleplay", + "realism" + ], + "ignore": [ + "*.code-workspace" + ] +} \ No newline at end of file diff --git a/garrysmod/addons/govorilka/lua/autorun/govorilka.lua b/garrysmod/addons/govorilka/lua/autorun/govorilka.lua new file mode 100644 index 0000000..01fab24 --- /dev/null +++ b/garrysmod/addons/govorilka/lua/autorun/govorilka.lua @@ -0,0 +1,126 @@ +print("loading govorilka by sekta") + +if SERVER then util.AddNetworkString("govorilka-tts") end + +--sv convars +govorilkaenable = CreateConVar("sv_govorilka_enabled","1",FCVAR_ARCHIVE) +govorilkalang = CreateConVar("sv_govorilka_lang","ru",FCVAR_ARCHIVE) +govorilkaglobal = CreateConVar("sv_govorilka_global","0",FCVAR_ARCHIVE) +govorilkaenablechattts = CreateConVar("sv_govorilka_chattts_enabled","1",FCVAR_ARCHIVE) +--cl convars +clgovorilkaenable = CreateClientConVar("cl_govorilka_enabled","1",FCVAR_ARCHIVE) +clgovorilkaenablechattts = CreateClientConVar("cl_govorilka_chattts_enabled","1",FCVAR_ARCHIVE) + +local char_to_hex = function(c) return string.format("%%%02X", string.byte(c)) end +local function urlencode(url) + return url:gsub("\n", "\r\n") + :gsub("([^%w ])", char_to_hex) + :gsub(" ", "+") +end + +-- _g - Google ver. +-- _y - Yandex ver. +sektattsbase_g = {} + +if SERVER then + function sektattsbase_g.TTS(text) + net.Start("govorilka-tts") + net.WriteFloat(1) + net.WriteString(text) + net.Broadcast() + end + + local meta = FindMetaTable("Player") + function meta:TTS(text) + net.Start("govorilka-tts") + net.WriteFloat(1) + net.WriteString(text) + net.Send(self) + end + + function sektattsbase_g.LocalTTS(text,pos) + net.Start("govorilka-tts") + net.WriteFloat(2) + net.WriteString(text) + net.WriteVector(pos) + net.SendPAS(pos) + end + + function sektattsbase_g.EntTTS(text,ent) + net.Start("govorilka-tts") + net.WriteFloat(3) + net.WriteString(text) + net.WriteEntity(ent) + net.Broadcast() + end + + hook.Add("PlayerSay","govorilka.chat",function(ply,text,team) + if !team and (govorilkaenablechattts:GetString()=="1" or clgovorilkaenablechattts:GetString()=="1") then + if ply.HasPurchase and not ply:HasPurchase("govorilka") then return end + + if govorilkaglobal:GetString()=="1" then + sektattsbase_g.TTS(text) + else + sektattsbase_g.EntTTS(text,ply) + end + end + end) +end + +if CLIENT then + function sektattsbase_g.TTS(text) + sound.PlayURL("http://translate.googleapis.com/translate_tts?client=gtx&ie=UTF-8&tl="..govorilkalang:GetString().."&q="..urlencode(text), "mono", function(station,err,errcode) + if IsValid(station) then + station:Play() + else + print("[Govorilka] Ошибка воспроизведения TTS: ", errcode, err) + end + end) + end + + function sektattsbase_g.LocalTTS(text,pos) + sound.PlayURL("http://translate.googleapis.com/translate_tts?client=gtx&ie=UTF-8&tl="..govorilkalang:GetString().."&q="..urlencode(text), "3d", function(station,err,errcode) + if IsValid(station) then + station:SetPos(pos) + station:Play() + else + print("[Govorilka] Ошибка воспроизведения LocalTTS: ", errcode, err) + end + end) + end + + function sektattsbase_g.EntTTS(text,ent) + if IsValid(ent) then + sound.PlayURL("http://translate.googleapis.com/translate_tts?client=gtx&ie=UTF-8&tl="..govorilkalang:GetString().."&q="..urlencode(text), "3d", function(station,err,errcode) + if IsValid(station) then + station:SetPos(ent:GetPos()) + station:Play() + local id = "tts_ent."..math.random(1,10000000) + hook.Add("Think",id,function() + if IsValid(ent) then station:SetPos(ent:GetPos()) end + if !IsValid(station) then hook.Remove("Think",id) end + end) + else + print("[Govorilka] Ошибка воспроизведения EntTTS: ", errcode, err) + end + end) + end + end + + net.Receive("govorilka-tts",function() + if clgovorilkaenable:GetString()=="0" or govorilkaenable:GetString()=="0" then return end + local mode = net.ReadFloat() + local text = net.ReadString() + if mode==1 then + sektattsbase_g.TTS(text) + elseif mode==2 then + local pos = net.ReadVector() + sektattsbase_g.LocalTTS(text,pos) + elseif mode==3 then + local ent = net.ReadEntity() + sektattsbase_g.EntTTS(text,ent) + end + end) +end + +-- coded by sekta diff --git a/garrysmod/addons/handcuffs/lua/autorun/server/sv_handcuffs.lua b/garrysmod/addons/handcuffs/lua/autorun/server/sv_handcuffs.lua new file mode 100644 index 0000000..ac3190e --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/autorun/server/sv_handcuffs.lua @@ -0,0 +1,306 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Nathan Healy -- +-------- All rights reserved -------- +------------------------------------- +-- sv_handcuffs.lua SERVER -- +-- -- +-- Server-side handcuff stuff. -- +------------------------------------- + +if CLIENT then return end + +util.AddNetworkString( "Cuffs_GagPlayer" ) +util.AddNetworkString( "Cuffs_BlindPlayer" ) +util.AddNetworkString( "Cuffs_FreePlayer" ) +util.AddNetworkString( "Cuffs_DragPlayer" ) + +util.AddNetworkString( "Cuffs_TiePlayers" ) +util.AddNetworkString( "Cuffs_UntiePlayers" ) + +local function GetTrace( ply ) + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if IsValid(tr.Entity) and tr.Entity:IsPlayer() then + local cuffed,wep = tr.Entity:IsHandcuffed() + if cuffed then return tr,wep end + end +end + +// +// Standard hooks +CreateConVar( "cuffs_restrictsuicide", 1, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +hook.Add( "CanPlayerSuicide", "Cuffs RestrictSuicide", function( ply ) + if ply:IsHandcuffed() and cvars.Bool("cuffs_restrictarrest") then return false end +end) +CreateConVar( "cuffs_restrictteams", 1, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +hook.Add( "PlayerCanJoinTeam", "Cuffs RestrictTeam", function( ply ) + if ply:IsHandcuffed() and cvars.Bool("cuffs_restrictteams") then return false end +end) +hook.Add( "PlayerCanSeePlayersChat", "Cuffs ChatGag", function( _,_,_, ply ) + if not IsValid(ply) then return end + + local cuffed,wep = ply:IsHandcuffed() + if cuffed and wep:GetIsGagged() then return false end +end) +hook.Add( "PlayerCanHearPlayersVoice", "Cuffs VoiceGag", function( _, ply ) + if not IsValid(ply) then return end + + local cuffed,wep = ply:IsHandcuffed() + if cuffed and wep:GetIsGagged() then return false end +end) + +// +// DarkRP +CreateConVar( "cuffs_restrictwarrant", 1, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE,FCVAR_REPLICATED} ) +hook.Add( "canRequestWarrant", "Cuffs PreventWarrant", function( crim, cop, reason ) if cvars.Bool("cuffs_restrictwarrant") and cop:IsHandcuffed() then return false,"You can issue warrants when cuffed!" end end) +hook.Add( "canWanted", "Cuffs PreventWarrant", function( crim, cop, reason ) if cvars.Bool("cuffs_restrictwarrant") and cop:IsHandcuffed() then return false,"You can issue warrants when cuffed!" end end) + +CreateConVar( "cuffs_autoarrest", 0, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +CreateConVar( "cuffs_restrictarrest", 0, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +hook.Add( "canArrest", "Cuffs RestrictArrest", function( cop, crim ) // DarkRP Arrest hook + if IsValid(crim) and cvars.Bool("cuffs_restrictarrest") and not crim:IsHandcuffed() then return false,"You must handcuff a suspect to arrest them!" end +end) +hook.Add( "playerCanChangeTeam", "Cuffs RestrictTeam", function( ply, tm, force ) + if ply:IsHandcuffed() and cvars.Bool("cuffs_restrictteams") and not force then return false,"You can't change jobs when cuffed!" end +end) +hook.Add( "CanChangeRPName", "Cuffs RestrictName", function( ply ) + if ply:IsHandcuffed() then return false,"You can't change your name when cuffed!" end +end) + +// +// Think +local NextTieHookCleanup +hook.Add( "Think", "Cuffs ForceJump CleanupTieHooks", function() + for _,v in pairs(player.GetAll()) do + if v.Cuff_ForceJump then + if not v:OnGround() then return end + + local tr = util.TraceLine( {start = v:GetPos(), endpos = v:GetPos()+Vector(0,0,20), filter = v} ) + if tr.Hit then return end + + v:SetPos(v:GetPos()+Vector(0,0,5) ) + + v.Cuff_ForceJump = nil + end + end + + if CurTime()>=(NextTieHookCleanup or 0) then + for _,v in pairs(ents.GetAll()) do + if v.IsHandcuffHook and v.TiedHandcuffs then + for i=#v.TiedHandcuffs,0,-1 do + if not IsValid(v.TiedHandcuffs[i]) then + table.remove( v.TiedHandcuffs, i ) + end + end + if #v.TiedHandcuffs<=0 then + v:Remove() + continue + end + end + end + end +end) + +// +// Cuffed player interaction +net.Receive( "Cuffs_GagPlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs) and cuffs:GetCanGag()) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + local shouldGag = net.ReadBit()==1 + cuffs:SetIsGagged( shouldGag ) + hook.Call( shouldGag and "OnHandcuffGag" or "OnHandcuffUnGag", GAMEMODE, ply, target, cuffs ) +end) +net.Receive( "Cuffs_BlindPlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs) and cuffs:GetCanBlind()) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + local shouldBlind = net.ReadBit()==1 + cuffs:SetIsBlind( shouldBlind ) + hook.Call( shouldBlind and "OnHandcuffBlindfold" or "OnHandcuffUnBlindfold", GAMEMODE, ply, target, cuffs ) +end) +net.Receive( "Cuffs_FreePlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs)) then return end + if IsValid(cuffs:GetFriendBreaking()) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + cuffs:SetFriendBreaking( ply ) +end) +net.Receive( "Cuffs_DragPlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs) and cuffs:GetRopeLength()>0) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + local shouldDrag = net.ReadBit()==1 + if shouldDrag then + if not (IsValid(cuffs:GetKidnapper())) then + cuffs:SetKidnapper( ply ) + hook.Call( "OnHandcuffStartDragging", GAMEMODE, ply, target, cuffs ) + end + else + if ply==cuffs:GetKidnapper() then + cuffs:SetKidnapper( nil ) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, target, cuffs ) + end + end +end) + +local HookModel = Model("models/props_c17/TrapPropeller_Lever.mdl") +net.Receive( "Cuffs_TiePlayers", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local DraggedCuffs = {} + for _,c in pairs(ents.FindByClass("weapon_handcuffed")) do + if c:GetRopeLength()>0 and c:GetKidnapper()==ply then + table.insert( DraggedCuffs, c ) + end + end + if #DraggedCuffs<=0 then return end + + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if not tr.Hit then return end + + if IsValid(tr.Entity) then // Pass to another player + if tr.Entity:IsPlayer() then + for i=1,#DraggedCuffs do + if DraggedCuffs[i].Owner==tr.Entity then + DraggedCuffs[i]:SetKidnapper(nil) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + else + DraggedCuffs[i]:SetKidnapper(tr.Entity) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + hook.Call( "OnHandcuffStartDragging", GAMEMODE, tr.Entity, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + end + end + return + elseif tr.Entity.IsHandcuffHook and tr.Entity.TiedHandcuffs then + for i=1,#DraggedCuffs do + DraggedCuffs[i]:SetKidnapper(tr.Entity) + table.insert( tr.Entity.TiedHandcuffs, DraggedCuffs[i] ) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + hook.Call( "OnHandcuffTied", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i], tr.Entity ) + end + return + end + end + + local hk = ents.Create("prop_physics") + hk:SetPos( tr.HitPos + tr.HitNormal ) + local ang = tr.HitNormal:Angle() + ang:RotateAroundAxis( ang:Up(), -90 ) + hk:SetAngles( ang ) + hk:SetModel( HookModel ) + hk:Spawn() + + -- hk:SetMoveType( MOVETYPE_NONE ) + if IsValid(tr.Entity) then + hk:SetParent( tr.Entity ) + hk:SetMoveType( MOVETYPE_VPHYSICS ) + else + hk:SetMoveType( MOVETYPE_NONE ) + end + hk:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + hk:SetNWBool("Cuffs_TieHook", true) + hk.IsHandcuffHook = true + hk.TiedHandcuffs = {} + + for i=1,#DraggedCuffs do + DraggedCuffs[i]:SetKidnapper( hk ) + table.insert( hk.TiedHandcuffs, DraggedCuffs[i] ) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + hook.Call( "OnHandcuffTied", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i], hk ) + end +end) + +local function DoUntie( ply, ent ) + for i=1,#ent.TiedHandcuffs do + if not IsValid(ent.TiedHandcuffs[i]) then continue end + + ent.TiedHandcuffs[i]:SetKidnapper( ply ) + hook.Call( "OnHandcuffUnTied", GAMEMODE, ply, ent.TiedHandcuffs[i].Owner, ent.TiedHandcuffs[i], ent ) + hook.Call( "OnHandcuffStartDragging", GAMEMODE, ply, ent.TiedHandcuffs[i].Owner, ent.TiedHandcuffs[i] ) + end + + ent:Remove() +end +net.Receive( "Cuffs_UntiePlayers", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if IsValid(tr.Entity) and tr.Entity.IsHandcuffHook and tr.Entity.TiedHandcuffs then + DoUntie( ply, tr.Entity ) + end +end) +hook.Add( "AllowPlayerPickup", "Cuffs UntieHook", function(ply,ent) + if IsValid(ent) and ent.IsHandcuffHook and ent.TiedHandcuffs then + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + DoUntie( ply, ent ) + return false + end +end) + +// +// GmodDayZ support +// Created by and added with permission of Phoenix129 ( http://steamcommunity.com/profiles/76561198039440140/ ) +hook.Add("OnHandcuffed", "DayZCuffs RemoveInventoryItem", function(ply, cuffedply, handcuffs) + if engine.ActiveGamemode() == "dayz" then + ply:TakeCharItem( handcuffs.CuffType ) + end +end) + +hook.Add("OnHandcuffBreak", "DayZCuffs GiveInventoryItemIfFriend", function(cuffedply, handcuffs, friend) + if engine.ActiveGamemode() == "dayz" then + if IsValid(friend) and handcuffs.CuffType and handcuffs.CuffType~="" then + friend:GiveItem(handcuffs.CuffType, 1) + end + end +end) + +hook.Add( "CuffsCanHandcuff", "DayZCuffs SafezoneProtectCuffs", function( ply, target ) + if engine.ActiveGamemode() == "dayz" then + if target.SafeZone or target.SafeZoneEdge or target.Loading or !target.Ready then + return false + end + end +end) +hook.Add("PlayerDisconnected", "DayZCuffs DieHandcuffs", function(ply) + if engine.ActiveGamemode() == "dayz" then + if ply:IsHandcuffed() then ply:Kill() end + end +end) diff --git a/garrysmod/addons/handcuffs/lua/autorun/sh_handcuffs.lua b/garrysmod/addons/handcuffs/lua/autorun/sh_handcuffs.lua new file mode 100644 index 0000000..036242d --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/autorun/sh_handcuffs.lua @@ -0,0 +1,403 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Healy -- +-------- All rights reserved -------- +------------------------------------- +-- sh_handcuffs.lua SHARED -- +-- -- +-- Shared handcuff stuff. -- +------------------------------------- + +AddCSLuaFile() + +// +// Config +local ProtectedJobs = { + "TEAM_PLAYER", "TEAM_MOD", "TEAM_PLAYER", +} + +// +// Utility +local function GetTrace( ply ) + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if IsValid(tr.Entity) and tr.Entity:IsPlayer() then + local cuffed,wep = tr.Entity:IsHandcuffed() + if cuffed then return tr,wep end + end +end + +// +// PLAYER extensions +local PLAYER = FindMetaTable( "Player" ) +function PLAYER:IsHandcuffed() + local wep = self:GetWeapon( "weapon_handcuffed" ) + if IsValid(wep) and wep.IsHandcuffs then + return true,wep + end + + return false +end + +// +// Override Movement +hook.Add( "SetupMove", "Cuffs Move Penalty", function(ply, mv, cmd) + local cuffed, cuffs = ply:IsHandcuffed() + if not (cuffed and IsValid(cuffs)) then return end + + mv:SetMaxClientSpeed( mv:GetMaxClientSpeed()*0.6 ) + + if cuffs:GetRopeLength()<=0 then return end // No forced movement + if not IsValid(cuffs:GetKidnapper()) then return end // Nowhere to move to + + local kidnapper = cuffs:GetKidnapper() + if kidnapper==ply then return end + + local TargetPoint = (kidnapper:IsPlayer() and kidnapper:GetShootPos()) or kidnapper:GetPos() + local MoveDir = (TargetPoint - ply:GetPos()):GetNormal() + local ShootPos = ply:GetShootPos() + (Vector(0,0, (ply:Crouching() and 0))) + local Distance = cuffs:GetRopeLength() + + local distFromTarget = ShootPos:Distance( TargetPoint ) + if distFromTarget<=(Distance+5) then return end + if ply:InVehicle() then + if SERVER and (distFromTarget>(Distance*3)) then + ply:ExitVehicle() + end + + return + end + + local TargetPos = TargetPoint - (MoveDir*Distance) + + local xDif = math.abs(ShootPos[1] - TargetPos[1]) + local yDif = math.abs(ShootPos[2] - TargetPos[2]) + local zDif = math.abs(ShootPos[3] - TargetPos[3]) + + local speedMult = 3+ ( (xDif + yDif)*0.5)^1.01 + local vertMult = math.max((math.Max(300-(xDif + yDif), -10)*0.08)^1.01 + (zDif/2),0) + + if kidnapper:GetGroundEntity()==ply then vertMult = -vertMult end + + local TargetVel = (TargetPos - ShootPos):GetNormal() * 10 + TargetVel[1] = TargetVel[1]*speedMult + TargetVel[2] = TargetVel[2]*speedMult + TargetVel[3] = TargetVel[3]*vertMult + local dir = mv:GetVelocity() + + local clamp = 50 + local vclamp = 20 + local accel = 200 + local vaccel = 30*(vertMult/50) + + dir[1] = (dir[1]>TargetVel[1]-clamp or dir[1]TargetVel[2]-clamp or dir[2]TargetVel[3]-vclamp or dir[3]0 then ply.Cuff_ForceJump=ply end + end + + mv:SetVelocity( dir ) + + if SERVER and mv:GetVelocity():Length()>=(mv:GetMaxClientSpeed()*10) and ply:IsOnGround() and CurTime()>(ply.Cuff_NextDragDamage or 0) then + ply:SetHealth( ply:Health()-1 ) + if ply:Health()<=0 then ply:Kill() end + + ply.Cuff_NextDragDamage = CurTime()+0.1 + end +end) + +// +// Vehicles +hook.Add( "CanPlayerEnterVehicle", "Cuffs PreventVehicle", function( ply ) + -- if ply:IsHandcuffed() then return false end +end) + +// +// Internal Cuffs hooks +hook.Add( "CuffsCanHandcuff", "Cuff ProtectAdmin", function( ply, target ) + if IsValid(target) and target:IsPlayer() and ProtectedJobs then + for i=1,#ProtectedJobs do + if ProtectedJobs[i] and _G[ ProtectedJobs[i] ] and target:Team()==_G[ ProtectedJobs[i] ] then return false end + end + end +end) + +if CLIENT then + local CuffsTable = {} + local function UpdateCuffsTable() + CuffsTable = {} + for _,v in pairs(ents.GetAll()) do + if IsValid(v) and v.IsHandcuffs then table.insert( CuffsTable, v ) end + end + end + + // + // HUD + local Col = { + Text = Color(255,255,255), TextShadow=Color(0,0,0), Rope = Color(255,255,255), + + BoxOutline = Color(0,0,0), BoxBackground = Color(255,255,255,20), BoxLeft = Color(255,0,0), BoxRight = Color(0,255,0), + } + local matGrad = Material( "gui/gradient" ) + local NextCuffsTableUpdate = 0 + hook.Add( "HUDPaint", "Cuffs CuffedInteractPrompt", function() + local ply = LocalPlayer() + if ply:IsHandcuffed() then return end + + local w,h = (ScrW()/2), (ScrH()/2) + local TextPos = h-40 + + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if tr.Hit then + if CurTime()>NextCuffsTableUpdate then + UpdateCuffsTable() + NextCuffsTableUpdate = CurTime()+5 + end + + for _,v in pairs(CuffsTable) do + if IsValid(v) and v:GetKidnapper()==ply and v:GetRopeLength()>0 then + local str = string.format( "%s + %s to %s", + (input.LookupBinding("+use") or "[use]"):upper(), + (input.LookupBinding("+attack") or "[Primary Fire]"):upper(), + (IsValid(tr.Entity) and tr.Entity:IsPlayer()) and "hand over captive" or "tie captive" + ) + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + TextPos = TextPos-30 + + break + end + end + end + + local tr,cuff = GetTrace( ply ) + if not (tr and IsValid(cuff)) then + return + end + + + surface.SetDrawColor( Col.BoxOutline ) + surface.DrawOutlinedRect( w-101, TextPos-1, 202, 22 ) + surface.SetDrawColor( Col.BoxBackground ) + surface.DrawRect( w-100, TextPos, 200, 20 ) + + render.SetScissorRect( w-100, TextPos, (w-100)+((cuff:GetCuffBroken()/100)*200), TextPos+20, true ) + surface.SetDrawColor( Col.BoxRight ) + surface.DrawRect( w-100,TextPos, 200,20 ) + + surface.SetMaterial( matGrad ) + surface.SetDrawColor( Col.BoxLeft ) + surface.DrawTexturedRect( w-100,TextPos, 200,20 ) + render.SetScissorRect( 0,0,0,0, false ) + TextPos = TextPos-25 + + if IsValid(cuff:GetFriendBreaking()) then + if cuff:GetFriendBreaking()==ply then + draw.SimpleText( "Releasing player...", "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( "Releasing player...", "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + TextPos = TextPos-20 + end + else + local str = string.format( "%s to release", (input.LookupBinding("+use") or "[USE]"):upper() ) + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + TextPos = TextPos-20 + end + + if cuff:GetRopeLength()>0 then + if IsValid(cuff:GetKidnapper()) then + if cuff:GetKidnapper()==ply then + local str = string.format( "%s to stop dragging", (input.LookupBinding("+reload") or "[Reload]"):upper() ) + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + TextPos = TextPos-20 + end + else + local str = string.format( "%s to drag player", (input.LookupBinding("+reload") or "[Reload]"):upper() ) + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + TextPos = TextPos-20 + end + end + + if cuff:GetCanBlind() then + local str = string.format( "%s to %sblindfold", (input.LookupBinding("+attack2") or "[PRIMARY FIRE]"):upper(), cuff:GetIsBlind() and "remove " or "" ) + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + TextPos = TextPos-20 + end + + if cuff:GetCanGag() then + local str = string.format( "%s to %sgag", (input.LookupBinding("+attack") or "[PRIMARY FIRE]"):upper(), cuff:GetIsGagged() and "un" or "" ) + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + TextPos = TextPos-20 + end + end) + + // + // Bind hooks + hook.Add( "PlayerBindPress", "Cuffs CuffedInteract", function(ply, bind, pressed) + if ply~=LocalPlayer() then return end + + if bind:lower()=="+attack" and pressed then + if ply:KeyDown( IN_USE ) then + local isDragging = false + for _,c in pairs(ents.FindByClass("weapon_handcuffed")) do + if c.GetRopeLength and c.GetKidnapper and c:GetRopeLength()>0 and c:GetKidnapper()==ply then + isDragging=true + break + end + end + if isDragging then + net.Start("Cuffs_TiePlayers") net.SendToServer() + return true + end + end + local tr,cuffs = GetTrace( ply ) + if tr and cuffs:GetCanGag() then + net.Start( "Cuffs_GagPlayer" ) + net.WriteEntity( tr.Entity ) + net.WriteBit( not cuffs:GetIsGagged() ) + net.SendToServer() + return true + end + elseif bind:lower()=="+attack2" and pressed then + local tr,cuffs = GetTrace( ply ) + if tr and cuffs:GetCanBlind() then + net.Start( "Cuffs_BlindPlayer" ) + net.WriteEntity( tr.Entity ) + net.WriteBit( not cuffs:GetIsBlind() ) + net.SendToServer() + return true + end + elseif bind:lower()=="+reload" and pressed then + local tr,cuffs = GetTrace( ply ) + if tr and cuffs:GetRopeLength()>0 then + net.Start( "Cuffs_DragPlayer" ) + net.WriteEntity( tr.Entity ) + net.WriteBit( LocalPlayer()~=cuffs:GetKidnapper() ) + net.SendToServer() + return true + end + elseif bind:lower()=="+use" and pressed then + local tr,cuffs = GetTrace( ply ) + if tr then + net.Start( "Cuffs_FreePlayer" ) + net.WriteEntity( tr.Entity ) + net.SendToServer() + return true + else + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if IsValid(tr.Entity) and tr.Entity:GetNWBool("Cuffs_TieHook") then + net.Start("Cuffs_UntiePlayers") net.SendToServer() + end + end + end + end) + + // + // Render + local DragBone = "ValveBiped.Bip01_R_Hand" + local LeashBone = "ValveBiped.Bip01_Neck1" + local LeashAltBone = "Neck" + local LeashHolder = "ValveBiped.Bip01_R_Hand" + local DefaultRope = Material("cable/rope") + hook.Add( "PostDrawOpaqueRenderables", "Cuffs DragRope", function() + local allCuffs = ents.FindByClass( "weapon_handcuffed" ) + for i=1,#allCuffs do + local cuff = allCuffs[i] + if not (IsValid(cuff) and IsValid(cuff.Owner) and cuff.GetRopeLength and cuff:GetRopeLength()>0 and cuff.GetKidnapper and IsValid(cuff:GetKidnapper())) then continue end + + local kidnapper = cuff:GetKidnapper() + local kidPos = (kidnapper:IsPlayer() and kidnapper:GetPos() + Vector(0,0,37)) or kidnapper:GetPos() + + local pos = cuff.Owner:GetPos() + if cuff:GetIsLeash() then + local bone = cuff.Owner:LookupBone( LeashBone ) + if not bone then + bone = cuff.Owner:LookupBone( LeashAltBone ) + end + if bone then + pos = cuff.Owner:GetBonePosition( bone ) + if (pos.x==0 and pos.y==0 and pos.z==0) then pos = cuff.Owner:GetPos() end + end + + if kidnapper~=LocalPlayer() or (hook.Call("ShouldDrawLocalPlayer", GAMEMODE, LocalPlayer())) then // Looks weird first-person + local lBone = kidnapper:LookupBone(LeashHolder) + if lBone then + local newPos = kidnapper:GetBonePosition( lBone ) + if newPos and (newPos.x~=0 and newPos.y~=0 and newPos.z~=0) then + kidPos = newPos + end + end + end + else + local bone = cuff.Owner:LookupBone( DragBone ) + if bone then + pos = cuff.Owner:GetBonePosition( bone ) + if (pos.x==0 and pos.y==0 and pos.z==0) then pos = cuff.Owner:GetPos() end + end + end + -- print(bone) + + if not cuff.RopeMat then cuff.RopeMat = DefaultRope end + render.SetMaterial( cuff.RopeMat ) + render.DrawBeam( kidPos, pos, 0.7, 0, 5, Col.Rope ) + render.DrawBeam( pos, kidPos, -0.7, 0, 5, Col.Rope ) + end + end) + + local HeadBone = "ValveBiped.Bip01_Head1" + local RenderPos = { + Blind = {Vector(3.5,3,2.6), Vector(3.8,4.8,0), Vector(3.5,3,-2.8), Vector(2.4,-2,-3.8), Vector(1.5,-4.5,0), Vector(2.4,-2,3.8)}, + Gag = {Vector(1.0,4.2,2), Vector(1.0,5.5,-0.1), Vector(1.0,4.5,-2), Vector(0,0,-3.4), Vector(-0.8,-3,0), Vector(0,0,3.4)}, + } + hook.Add( "PostPlayerDraw", "Cuffs DrawGag", function( ply ) + if not IsValid(ply) then return end + + local cuffed, cuff = ply:IsHandcuffed() + if not (cuffed and IsValid(cuff)) then return end + + render.SetMaterial( DefaultRope ) + if cuff:GetIsBlind() then + local pos,ang + local bone = cuff.Owner:LookupBone( HeadBone ) + if bone then + pos, ang = cuff.Owner:GetBonePosition( bone ) + end + if pos and ang then + local firstpos = pos + (ang:Forward()*RenderPos.Blind[1].x) + (ang:Right()*RenderPos.Blind[1].y) + (ang:Up()*RenderPos.Blind[1].z) + local lastpos = firstpos + for i=2,#RenderPos.Blind do + local newPos = pos + (ang:Forward()*RenderPos.Blind[i].x) + (ang:Right()*RenderPos.Blind[i].y) + (ang:Up()*RenderPos.Blind[i].z) + render.DrawBeam( newPos, lastpos, 1.5, 0, 1, Col.Rope ) + lastpos = newPos + end + render.DrawBeam( lastpos, firstpos, 1.5, 0, 1, Col.Rope ) + end + end + if cuff:GetIsGagged() then + local pos,ang + local bone = cuff.Owner:LookupBone( HeadBone ) + if bone then + pos, ang = cuff.Owner:GetBonePosition( bone ) + end + if pos and ang then + local firstpos = pos + (ang:Forward()*RenderPos.Gag[1].x) + (ang:Right()*RenderPos.Gag[1].y) + (ang:Up()*RenderPos.Gag[1].z) + local lastpos = firstpos + for i=2,#RenderPos.Gag do + local newPos = pos + (ang:Forward()*RenderPos.Gag[i].x) + (ang:Right()*RenderPos.Gag[i].y) + (ang:Up()*RenderPos.Gag[i].z) + render.DrawBeam( newPos, lastpos, 1.5, 0, 1, Col.Rope ) + lastpos = newPos + end + render.DrawBeam( lastpos, firstpos, 1.5, 0, 1, Col.Rope ) + end + end + end) +end diff --git a/garrysmod/addons/handcuffs/lua/autorun/sv_handcuffs.lua b/garrysmod/addons/handcuffs/lua/autorun/sv_handcuffs.lua new file mode 100644 index 0000000..ac3190e --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/autorun/sv_handcuffs.lua @@ -0,0 +1,306 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Nathan Healy -- +-------- All rights reserved -------- +------------------------------------- +-- sv_handcuffs.lua SERVER -- +-- -- +-- Server-side handcuff stuff. -- +------------------------------------- + +if CLIENT then return end + +util.AddNetworkString( "Cuffs_GagPlayer" ) +util.AddNetworkString( "Cuffs_BlindPlayer" ) +util.AddNetworkString( "Cuffs_FreePlayer" ) +util.AddNetworkString( "Cuffs_DragPlayer" ) + +util.AddNetworkString( "Cuffs_TiePlayers" ) +util.AddNetworkString( "Cuffs_UntiePlayers" ) + +local function GetTrace( ply ) + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if IsValid(tr.Entity) and tr.Entity:IsPlayer() then + local cuffed,wep = tr.Entity:IsHandcuffed() + if cuffed then return tr,wep end + end +end + +// +// Standard hooks +CreateConVar( "cuffs_restrictsuicide", 1, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +hook.Add( "CanPlayerSuicide", "Cuffs RestrictSuicide", function( ply ) + if ply:IsHandcuffed() and cvars.Bool("cuffs_restrictarrest") then return false end +end) +CreateConVar( "cuffs_restrictteams", 1, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +hook.Add( "PlayerCanJoinTeam", "Cuffs RestrictTeam", function( ply ) + if ply:IsHandcuffed() and cvars.Bool("cuffs_restrictteams") then return false end +end) +hook.Add( "PlayerCanSeePlayersChat", "Cuffs ChatGag", function( _,_,_, ply ) + if not IsValid(ply) then return end + + local cuffed,wep = ply:IsHandcuffed() + if cuffed and wep:GetIsGagged() then return false end +end) +hook.Add( "PlayerCanHearPlayersVoice", "Cuffs VoiceGag", function( _, ply ) + if not IsValid(ply) then return end + + local cuffed,wep = ply:IsHandcuffed() + if cuffed and wep:GetIsGagged() then return false end +end) + +// +// DarkRP +CreateConVar( "cuffs_restrictwarrant", 1, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE,FCVAR_REPLICATED} ) +hook.Add( "canRequestWarrant", "Cuffs PreventWarrant", function( crim, cop, reason ) if cvars.Bool("cuffs_restrictwarrant") and cop:IsHandcuffed() then return false,"You can issue warrants when cuffed!" end end) +hook.Add( "canWanted", "Cuffs PreventWarrant", function( crim, cop, reason ) if cvars.Bool("cuffs_restrictwarrant") and cop:IsHandcuffed() then return false,"You can issue warrants when cuffed!" end end) + +CreateConVar( "cuffs_autoarrest", 0, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +CreateConVar( "cuffs_restrictarrest", 0, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE} ) +hook.Add( "canArrest", "Cuffs RestrictArrest", function( cop, crim ) // DarkRP Arrest hook + if IsValid(crim) and cvars.Bool("cuffs_restrictarrest") and not crim:IsHandcuffed() then return false,"You must handcuff a suspect to arrest them!" end +end) +hook.Add( "playerCanChangeTeam", "Cuffs RestrictTeam", function( ply, tm, force ) + if ply:IsHandcuffed() and cvars.Bool("cuffs_restrictteams") and not force then return false,"You can't change jobs when cuffed!" end +end) +hook.Add( "CanChangeRPName", "Cuffs RestrictName", function( ply ) + if ply:IsHandcuffed() then return false,"You can't change your name when cuffed!" end +end) + +// +// Think +local NextTieHookCleanup +hook.Add( "Think", "Cuffs ForceJump CleanupTieHooks", function() + for _,v in pairs(player.GetAll()) do + if v.Cuff_ForceJump then + if not v:OnGround() then return end + + local tr = util.TraceLine( {start = v:GetPos(), endpos = v:GetPos()+Vector(0,0,20), filter = v} ) + if tr.Hit then return end + + v:SetPos(v:GetPos()+Vector(0,0,5) ) + + v.Cuff_ForceJump = nil + end + end + + if CurTime()>=(NextTieHookCleanup or 0) then + for _,v in pairs(ents.GetAll()) do + if v.IsHandcuffHook and v.TiedHandcuffs then + for i=#v.TiedHandcuffs,0,-1 do + if not IsValid(v.TiedHandcuffs[i]) then + table.remove( v.TiedHandcuffs, i ) + end + end + if #v.TiedHandcuffs<=0 then + v:Remove() + continue + end + end + end + end +end) + +// +// Cuffed player interaction +net.Receive( "Cuffs_GagPlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs) and cuffs:GetCanGag()) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + local shouldGag = net.ReadBit()==1 + cuffs:SetIsGagged( shouldGag ) + hook.Call( shouldGag and "OnHandcuffGag" or "OnHandcuffUnGag", GAMEMODE, ply, target, cuffs ) +end) +net.Receive( "Cuffs_BlindPlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs) and cuffs:GetCanBlind()) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + local shouldBlind = net.ReadBit()==1 + cuffs:SetIsBlind( shouldBlind ) + hook.Call( shouldBlind and "OnHandcuffBlindfold" or "OnHandcuffUnBlindfold", GAMEMODE, ply, target, cuffs ) +end) +net.Receive( "Cuffs_FreePlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs)) then return end + if IsValid(cuffs:GetFriendBreaking()) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + cuffs:SetFriendBreaking( ply ) +end) +net.Receive( "Cuffs_DragPlayer", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local target = net.ReadEntity() + if (not IsValid(target)) or target==ply then return end + + local cuffed, cuffs = target:IsHandcuffed() + if not (cuffed and IsValid(cuffs) and cuffs:GetRopeLength()>0) then return end + + local tr = GetTrace(ply) + if not (tr and tr.Entity==target) then return end + + local shouldDrag = net.ReadBit()==1 + if shouldDrag then + if not (IsValid(cuffs:GetKidnapper())) then + cuffs:SetKidnapper( ply ) + hook.Call( "OnHandcuffStartDragging", GAMEMODE, ply, target, cuffs ) + end + else + if ply==cuffs:GetKidnapper() then + cuffs:SetKidnapper( nil ) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, target, cuffs ) + end + end +end) + +local HookModel = Model("models/props_c17/TrapPropeller_Lever.mdl") +net.Receive( "Cuffs_TiePlayers", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local DraggedCuffs = {} + for _,c in pairs(ents.FindByClass("weapon_handcuffed")) do + if c:GetRopeLength()>0 and c:GetKidnapper()==ply then + table.insert( DraggedCuffs, c ) + end + end + if #DraggedCuffs<=0 then return end + + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if not tr.Hit then return end + + if IsValid(tr.Entity) then // Pass to another player + if tr.Entity:IsPlayer() then + for i=1,#DraggedCuffs do + if DraggedCuffs[i].Owner==tr.Entity then + DraggedCuffs[i]:SetKidnapper(nil) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + else + DraggedCuffs[i]:SetKidnapper(tr.Entity) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + hook.Call( "OnHandcuffStartDragging", GAMEMODE, tr.Entity, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + end + end + return + elseif tr.Entity.IsHandcuffHook and tr.Entity.TiedHandcuffs then + for i=1,#DraggedCuffs do + DraggedCuffs[i]:SetKidnapper(tr.Entity) + table.insert( tr.Entity.TiedHandcuffs, DraggedCuffs[i] ) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + hook.Call( "OnHandcuffTied", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i], tr.Entity ) + end + return + end + end + + local hk = ents.Create("prop_physics") + hk:SetPos( tr.HitPos + tr.HitNormal ) + local ang = tr.HitNormal:Angle() + ang:RotateAroundAxis( ang:Up(), -90 ) + hk:SetAngles( ang ) + hk:SetModel( HookModel ) + hk:Spawn() + + -- hk:SetMoveType( MOVETYPE_NONE ) + if IsValid(tr.Entity) then + hk:SetParent( tr.Entity ) + hk:SetMoveType( MOVETYPE_VPHYSICS ) + else + hk:SetMoveType( MOVETYPE_NONE ) + end + hk:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + hk:SetNWBool("Cuffs_TieHook", true) + hk.IsHandcuffHook = true + hk.TiedHandcuffs = {} + + for i=1,#DraggedCuffs do + DraggedCuffs[i]:SetKidnapper( hk ) + table.insert( hk.TiedHandcuffs, DraggedCuffs[i] ) + hook.Call( "OnHandcuffStopDragging", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i] ) + hook.Call( "OnHandcuffTied", GAMEMODE, ply, DraggedCuffs[i].Owner, DraggedCuffs[i], hk ) + end +end) + +local function DoUntie( ply, ent ) + for i=1,#ent.TiedHandcuffs do + if not IsValid(ent.TiedHandcuffs[i]) then continue end + + ent.TiedHandcuffs[i]:SetKidnapper( ply ) + hook.Call( "OnHandcuffUnTied", GAMEMODE, ply, ent.TiedHandcuffs[i].Owner, ent.TiedHandcuffs[i], ent ) + hook.Call( "OnHandcuffStartDragging", GAMEMODE, ply, ent.TiedHandcuffs[i].Owner, ent.TiedHandcuffs[i] ) + end + + ent:Remove() +end +net.Receive( "Cuffs_UntiePlayers", function(_,ply) + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if IsValid(tr.Entity) and tr.Entity.IsHandcuffHook and tr.Entity.TiedHandcuffs then + DoUntie( ply, tr.Entity ) + end +end) +hook.Add( "AllowPlayerPickup", "Cuffs UntieHook", function(ply,ent) + if IsValid(ent) and ent.IsHandcuffHook and ent.TiedHandcuffs then + if (not IsValid(ply)) or ply:IsHandcuffed() then return end + + DoUntie( ply, ent ) + return false + end +end) + +// +// GmodDayZ support +// Created by and added with permission of Phoenix129 ( http://steamcommunity.com/profiles/76561198039440140/ ) +hook.Add("OnHandcuffed", "DayZCuffs RemoveInventoryItem", function(ply, cuffedply, handcuffs) + if engine.ActiveGamemode() == "dayz" then + ply:TakeCharItem( handcuffs.CuffType ) + end +end) + +hook.Add("OnHandcuffBreak", "DayZCuffs GiveInventoryItemIfFriend", function(cuffedply, handcuffs, friend) + if engine.ActiveGamemode() == "dayz" then + if IsValid(friend) and handcuffs.CuffType and handcuffs.CuffType~="" then + friend:GiveItem(handcuffs.CuffType, 1) + end + end +end) + +hook.Add( "CuffsCanHandcuff", "DayZCuffs SafezoneProtectCuffs", function( ply, target ) + if engine.ActiveGamemode() == "dayz" then + if target.SafeZone or target.SafeZoneEdge or target.Loading or !target.Ready then + return false + end + end +end) +hook.Add("PlayerDisconnected", "DayZCuffs DieHandcuffs", function(ply) + if engine.ActiveGamemode() == "dayz" then + if ply:IsHandcuffed() then ply:Kill() end + end +end) diff --git a/garrysmod/addons/handcuffs/lua/weapons/weapon_cuff_base.lua b/garrysmod/addons/handcuffs/lua/weapons/weapon_cuff_base.lua new file mode 100644 index 0000000..976b2bb --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/weapons/weapon_cuff_base.lua @@ -0,0 +1,467 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Nathan Healy -- +-------- All rights reserved -------- +------------------------------------- +-- weapon_cuff_base.lua SHARED -- +-- -- +-- Base swep for handcuffs. -- +------------------------------------- + +AddCSLuaFile() + +SWEP.Base = "weapon_base" + +SWEP.Category = "Handcuffs" +SWEP.Author = "my_hat_stinks" +SWEP.Instructions = "" + +SWEP.Spawnable = false +SWEP.AdminOnly = false +SWEP.AdminSpawnable = false + +SWEP.Slot = 3 +SWEP.PrintName = "UnnamedHandcuff" + +SWEP.ViewModelFOV = 60 +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.WorldModel = "models/weapons/w_toolgun.mdl" +SWEP.ViewModel = "models/weapons/c_bugbait.mdl" +SWEP.UseHands = true + +SWEP.Primary.Recoil = 1 +SWEP.Primary.Damage = 5 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0 +SWEP.Primary.Delay = 0.25 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +SWEP.Primary.ClipMax = -1 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipMax = -1 + +SWEP.DeploySpeed = 1.5 + +SWEP.Spawnable = false +SWEP.AdminSpawnable = false +SWEP.AdminOnly = false + +SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK +SWEP.ReloadAnim = ACT_VM_RELOAD +SWEP.HoldType = "slam" + +SWEP.Spawnable = false +SWEP.AdminSpawnable = false +SWEP.AdminOnly = false + +// +// Handcuff Vars +SWEP.CuffTime = 1.0 // Seconds to handcuff +SWEP.CuffSound = Sound( "buttons/lever7.wav" ) + +SWEP.CuffMaterial = "phoenix_storms/metalfloor_2-3" +SWEP.CuffRope = "cable/cable2" + +SWEP.CuffStrength = 1 +SWEP.CuffRegen = 1 +SWEP.RopeLength = 0 + +SWEP.CuffReusable = false // Can reuse (ie, not removed on use) +SWEP.CuffRecharge = 30 // Time before re-use + +SWEP.CuffBlindfold = false +SWEP.CuffGag = false + +SWEP.CuffStrengthVariance = 0 // Randomise strangth +SWEP.CuffRegenVariance = 0 // Randomise regen + +// +// Network Vars +function SWEP:SetupDataTables() + self:NetworkVar( "Bool", 0, "IsCuffing" ) + self:NetworkVar( "Entity", 0, "Cuffing" ) + self:NetworkVar( "Float", 0, "CuffTime" ) +end + +// +// Standard SWEP functions +function SWEP:PrimaryAttack() + if self:GetIsCuffing() then return end + + self:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + self:SetNextSecondaryFire( CurTime() + self.Primary.Delay ) + + if CLIENT then return end + if self:GetCuffTime()>CurTime() then return end // On cooldown + + local tr = self:TargetTrace() + if not tr then return end + + self:SetCuffTime( CurTime() + self.CuffTime ) + self:SetIsCuffing( true ) + self:SetCuffing( tr.Entity ) + + sound.Play( self.CuffSound, self.Owner:GetShootPos(), 75, 100, 1 ) +end +function SWEP:SecondaryAttack() +end +function SWEP:Reload() +end + +function SWEP:Initialize() + self:SetHoldType( self.HoldType ) + + if engine.ActiveGamemode()=="dayz" then self.CuffReusable = false end +end +function SWEP:Holster() + if CLIENT and IsValid(self.Owner) and self.Owner==LocalPlayer() then + local vm = self.Owner:GetViewModel() + if not IsValid(vm) then return end + + vm:SetMaterial( "" ) + end + if IsValid(self.cmdl_RightCuff) then self.cmdl_RightCuff:Remove() end + if IsValid(self.cmdl_LeftCuff) then self.cmdl_LeftCuff:Remove() end + + return true +end +SWEP.OnRemove = SWEP.Holster + +// +// Handcuff +function SWEP:DoHandcuff( target ) + if not (target and IsValid(target)) then return end + if target:IsHandcuffed() then return end + if not IsValid(self.Owner) then return end + + local cuff = target:Give( "weapon_handcuffed" ) + cuff:SetCuffStrength( self.CuffStrength + (math.Rand(-self.CuffStrengthVariance,self.CuffStrengthVariance)) ) + cuff:SetCuffRegen( self.CuffRegen + (math.Rand(-self.CuffRegenVariance,self.CuffRegenVariance)) ) + + cuff:SetCuffMaterial( self.CuffMaterial ) + cuff:SetRopeMaterial( self.CuffRope ) + + cuff:SetKidnapper( self.Owner ) + cuff:SetRopeLength( self.RopeLength ) + + cuff:SetCanBlind( self.CuffBlindfold ) + cuff:SetCanGag( self.CuffGag ) + + cuff.CuffType = self:GetClass() or "" + + if self.IsLeash then + cuff:SetIsLeash( true ) + timer.Simple( 0, function() + if IsValid(cuff) then + cuff:SetHoldType( "normal" ) + cuff.HoldType = "normal" + end + end) + end + + PrintMessage( HUD_PRINTCONSOLE, "[Cuffs] "..tostring(self.Owner:Nick()).." has handcuffed "..tostring(target:Nick()).." with "..tostring(self.PrintName) ) + hook.Call( "OnHandcuffed", GAMEMODE, self.Owner, target, cuff ) + + if self.Owner.isCP and target.arrest and self.Owner:isCP() and cvars.Bool("cuffs_autoarrest") then + target:arrest( nil, self.Owner) + end + + if not self.CuffReusable then + if IsValid(self.Owner) then self.Owner:ConCommand( "lastinv" ) end + self:Remove() + end +end + +// +// Think +function SWEP:Think() + if SERVER then + if self:GetIsCuffing() then + local tr = self:TargetTrace() + if (not tr) or tr.Entity~=self:GetCuffing() then + self:SetIsCuffing(false) + self:SetCuffTime( 0 ) + return + end + + if CurTime()>self:GetCuffTime() then + self:SetIsCuffing( false ) + self:SetCuffTime( CurTime() + self.CuffRecharge ) + self:DoHandcuff( self:GetCuffing() ) + end + end + end +end + +// +// Get Target +function SWEP:TargetTrace() + if not IsValid(self.Owner) then return end + + local tr = util.TraceLine( {start=self.Owner:GetShootPos(), endpos=(self.Owner:GetShootPos() + (self.Owner:GetAimVector()*50)), filter={self, self.Owner}} ) + if IsValid(tr.Entity) and tr.Entity:IsPlayer() and tr.Entity~=self.Owner and not tr.Entity:IsHandcuffed() then + if hook.Run( "CuffsCanHandcuff", self.Owner, tr.Entity )==false then return end + return tr + end +end + +// +// HUD +local Col = { + Text = Color(255,255,255), TextShadow = Color(0,0,0), + + BoxOutline = Color(0,0,0), BoxBackground = Color(255,255,255,20), BoxLeft = Color(255,0,0), BoxRight = Color(0,255,0), +} +local matGrad = Material( "gui/gradient" ) +function SWEP:DrawHUD() + if not self:GetIsCuffing() then + if self:GetCuffTime()<=CurTime() then return end + + local w,h = (ScrW()/2), (ScrH()/2) + + surface.SetDrawColor( Col.BoxOutline ) + surface.DrawOutlinedRect( w-101, h+54, 202, 22 ) + surface.SetDrawColor( Col.BoxBackground ) + surface.DrawRect( w-100, h+55, 200, 20 ) + + local CuffingPercent = math.Clamp( ((self:GetCuffTime()-CurTime())/self.CuffRecharge), 0, 1 ) + render.SetScissorRect( w-100, h+55, (w-100)+(CuffingPercent*200), h+75, true ) + surface.SetDrawColor( Col.BoxRight ) + surface.DrawRect( w-100,h+55, 200,20 ) + + surface.SetMaterial( matGrad ) + surface.SetDrawColor( Col.BoxLeft ) + surface.DrawTexturedRect( w-100,h+55, 200,20 ) + render.SetScissorRect( 0,0,0,0, false ) + + return + end + + local w,h = (ScrW()/2), (ScrH()/2) + + draw.SimpleText( "Restraining target...", "HandcuffsText", w+1, h+31, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( "Restraining target...", "HandcuffsText", w, h+30, Col.Text, TEXT_ALIGN_CENTER ) + + surface.SetDrawColor( Col.BoxOutline ) + surface.DrawOutlinedRect( w-101, h+54, 202, 22 ) + surface.SetDrawColor( Col.BoxBackground ) + surface.DrawRect( w-100, h+55, 200, 20 ) + + local CuffingPercent = math.Clamp( 1-((self:GetCuffTime()-CurTime())/self.CuffTime), 0, 1 ) + + render.SetScissorRect( w-100, h+55, (w-100)+(CuffingPercent*200), h+75, true ) + surface.SetDrawColor( Col.BoxRight ) + surface.DrawRect( w-100,h+55, 200,20 ) + + surface.SetMaterial( matGrad ) + surface.SetDrawColor( Col.BoxLeft ) + surface.DrawTexturedRect( w-100,h+55, 200,20 ) + render.SetScissorRect( 0,0,0,0, false ) +end + +// +// Rendering +local renderpos = { + left = {pos=Vector(0,0,0), vel=Vector(0,0,0), gravity=1, ang=Angle(0,30,90)}, + right = {bone = "ValveBiped.Bip01_R_Hand", pos=Vector(3.2,2.1,0.4), ang=Angle(-2,0,80), scale = Vector(0.045,0.045,0.03)}, + rope = {l = Vector(0,0,2.0), r = Vector(2.3,-1.9,2.7)}, +} +local CuffMdl = "models/hunter/tubes/tube2x2x1.mdl" +local RopeCol = Color(255,255,255) +function SWEP:ViewModelDrawn( vm ) + if not IsValid(vm) then return end + + vm:SetMaterial( "engine/occlusionproxy" ) + + if not IsValid(self.cmdl_LeftCuff) then + self.cmdl_LeftCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + self.cmdl_LeftCuff:SetNoDraw( true ) + self.cmdl_LeftCuff:SetParent( vm ) + + renderpos.left.pos = self.Owner:GetPos() + end + if not IsValid(self.cmdl_RightCuff) then + self.cmdl_RightCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + self.cmdl_RightCuff:SetNoDraw( true ) + self.cmdl_RightCuff:SetParent( vm ) + end + + -- local lpos, lang = self:GetBonePos( renderpos.left.bone, vm ) + local rpos, rang = self:GetBonePos( renderpos.right.bone, vm ) + if not (rpos and rang) then return end + + // Right + local fixed_rpos = rpos + (rang:Forward()*renderpos.right.pos.x) + (rang:Right()*renderpos.right.pos.y) + (rang:Up()*renderpos.right.pos.z) + self.cmdl_RightCuff:SetPos( fixed_rpos ) + local u,r,f = rang:Up(), rang:Right(), rang:Forward() // Prevents moving axes + rang:RotateAroundAxis( u, renderpos.right.ang.y ) + rang:RotateAroundAxis( r, renderpos.right.ang.p ) + rang:RotateAroundAxis( f, renderpos.right.ang.r ) + self.cmdl_RightCuff:SetAngles( rang ) + + local matrix = Matrix() + matrix:Scale( renderpos.right.scale ) + self.cmdl_RightCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_RightCuff:SetMaterial( self.CuffMaterial ) + self.cmdl_RightCuff:DrawModel() + + // Left + if CurTime()>(renderpos.left.NextThink or 0) then + local dist = renderpos.left.pos:Distance( fixed_rpos ) + if dist>100 then + renderpos.left.pos = fixed_rpos + renderpos.left.vel = Vector(0,0,0) + elseif dist > 10 then + local target = (fixed_rpos - renderpos.left.pos)*0.3 + renderpos.left.vel.x = math.Approach( renderpos.left.vel.x, target.x, 1 ) + renderpos.left.vel.y = math.Approach( renderpos.left.vel.y, target.y, 1 ) + renderpos.left.vel.z = math.Approach( renderpos.left.vel.z, target.z, 3 ) + end + + renderpos.left.vel.x = math.Approach( renderpos.left.vel.x, 0, 0.5 ) + renderpos.left.vel.y = math.Approach( renderpos.left.vel.y, 0, 0.5 ) + renderpos.left.vel.z = math.Approach( renderpos.left.vel.z, 0, 0.5 ) + -- if renderpos.left.vel:Length()>10 then renderpos.left.vel:Mul(0.1) end + + local targetZ = ((fixed_rpos.z - 10) - renderpos.left.pos.z) * renderpos.left.gravity + renderpos.left.vel.z = math.Approach( renderpos.left.vel.z, targetZ, 3 ) + + renderpos.left.pos:Add( renderpos.left.vel ) + + -- renderpos.left.NextThink = CurTime()+0.01 + end + + self.cmdl_LeftCuff:SetPos( renderpos.left.pos ) + self.cmdl_LeftCuff:SetAngles( renderpos.left.ang ) + + -- self.cmdl_LeftCuff:SetAngles( rang ) + local matrix = Matrix() + matrix:Scale( renderpos.right.scale ) + self.cmdl_LeftCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_LeftCuff:SetMaterial( self.CuffMaterial ) + self.cmdl_LeftCuff:DrawModel() + + // Rope + if not self.RopeMat then self.RopeMat = Material(self.CuffRope) end + + render.SetMaterial( self.RopeMat ) + render.DrawBeam( renderpos.left.pos + renderpos.rope.l, + rpos + (rang:Forward()*renderpos.rope.r.x) + (rang:Right()*renderpos.rope.r.y) + (rang:Up()*renderpos.rope.r.z), + 0.7, 0, 5, RopeCol ) +end + +SWEP.wrender = { + left = {pos=Vector(0,0,0), vel=Vector(0,0,0), gravity=1, ang=Angle(0,30,90)}, + right = {bone = "ValveBiped.Bip01_R_Hand", pos=Vector(2.95,2.5,-0.2), ang=Angle(30,0,90), scale = Vector(0.044,0.044,0.044)}, + rope = {l = Vector(0,0,2), r = Vector(3,-1.65,1)}, +} +function SWEP:DrawWorldModel() + if not IsValid(self.Owner) then return end + local wrender = self.wrender + + if not IsValid(self.cmdl_LeftCuff) then + self.cmdl_LeftCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + self.cmdl_LeftCuff:SetNoDraw( true ) + end + if not IsValid(self.cmdl_RightCuff) then + self.cmdl_RightCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + self.cmdl_RightCuff:SetNoDraw( true ) + end + + local rpos, rang = self:GetBonePos( wrender.right.bone, self.Owner ) + if not (rpos and rang) then return end + + // Right + local fixed_rpos = rpos + (rang:Forward()*wrender.right.pos.x) + (rang:Right()*wrender.right.pos.y) + (rang:Up()*wrender.right.pos.z) + self.cmdl_RightCuff:SetPos( fixed_rpos ) + local u,r,f = rang:Up(), rang:Right(), rang:Forward() // Prevents moving axes + rang:RotateAroundAxis( u, wrender.right.ang.y ) + rang:RotateAroundAxis( r, wrender.right.ang.p ) + rang:RotateAroundAxis( f, wrender.right.ang.r ) + self.cmdl_RightCuff:SetAngles( rang ) + + local matrix = Matrix() + matrix:Scale( wrender.right.scale ) + self.cmdl_RightCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_RightCuff:SetMaterial( self.CuffMaterial ) + self.cmdl_RightCuff:DrawModel() + + // Left + if CurTime()>(wrender.left.NextThink or 0) then + local dist = wrender.left.pos:Distance( fixed_rpos ) + if dist>100 then + wrender.left.pos = fixed_rpos + wrender.left.vel = Vector(0,0,0) + elseif dist > 10 then + local target = (fixed_rpos - wrender.left.pos)*0.3 + wrender.left.vel.x = math.Approach( wrender.left.vel.x, target.x, 1 ) + wrender.left.vel.y = math.Approach( wrender.left.vel.y, target.y, 1 ) + wrender.left.vel.z = math.Approach( wrender.left.vel.z, target.z, 3 ) + end + + wrender.left.vel.x = math.Approach( wrender.left.vel.x, 0, 0.5 ) + wrender.left.vel.y = math.Approach( wrender.left.vel.y, 0, 0.5 ) + wrender.left.vel.z = math.Approach( wrender.left.vel.z, 0, 0.5 ) + -- if wrender.left.vel:Length()>10 then wrender.left.vel:Mul(0.1) end + + local targetZ = ((fixed_rpos.z - 10) - wrender.left.pos.z) * wrender.left.gravity + wrender.left.vel.z = math.Approach( wrender.left.vel.z, targetZ, 3 ) + + wrender.left.pos:Add( wrender.left.vel ) + + -- wrender.left.NextThink = CurTime()+0 + end + + self.cmdl_LeftCuff:SetPos( wrender.left.pos ) + self.cmdl_LeftCuff:SetAngles( wrender.left.ang ) + + local matrix = Matrix() + matrix:Scale( wrender.right.scale ) + self.cmdl_LeftCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_LeftCuff:SetMaterial( self.CuffMaterial ) + self.cmdl_LeftCuff:DrawModel() + + // Rope + if not self.RopeMat then self.RopeMat = Material(self.CuffRope) end + + render.SetMaterial( self.RopeMat ) + render.DrawBeam( wrender.left.pos + wrender.rope.l, + rpos + (rang:Forward()*wrender.rope.r.x) + (rang:Right()*wrender.rope.r.y) + (rang:Up()*wrender.rope.r.z), + 0.7, 0, 5, RopeCol ) +end + +// +// Bones +function SWEP:GetBonePos( bonename, vm ) + local bone = vm:LookupBone( bonename ) + if not bone then return end + + local pos,ang = Vector(0,0,0),Angle(0,0,0) + local matrix = vm:GetBoneMatrix( bone ) + if matrix then + pos = matrix:GetTranslation() + ang = matrix:GetAngles() + end + + if self.ViewModelFlip then ang.r = -ang.r end + + -- if pos.x==0 and pos.y==0 and pos.z==0 then print( bonename, vm ) end + return pos, ang +end diff --git a/garrysmod/addons/handcuffs/lua/weapons/weapon_cuff_rope.lua b/garrysmod/addons/handcuffs/lua/weapons/weapon_cuff_rope.lua new file mode 100644 index 0000000..93899f4 --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/weapons/weapon_cuff_rope.lua @@ -0,0 +1,45 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Nathan Healy -- +-------- All rights reserved -------- +------------------------------------- +-- weapon_cuff_standard.lua SHARED -- +-- -- +-- Rope handcuffs. -- +------------------------------------- + +AddCSLuaFile() + +SWEP.Base = "weapon_cuff_base" + +SWEP.Category = "[RP] Система полиции" +SWEP.Author = "my_hat_stinks" +SWEP.Instructions = "A weak restraint." + +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.AdminSpawnable = false + +SWEP.Slot = 3 +SWEP.PrintName = "Наручники" + +// +// Handcuff Vars +SWEP.CuffTime = 1.0 // Seconds to handcuff +SWEP.CuffSound = Sound( "buttons/lever7.wav" ) + +SWEP.CuffMaterial = "phoenix_storms/gear" +SWEP.CuffRope = "cable/cable2" +SWEP.CuffStrength = 1.4 +SWEP.CuffRegen = 1.4 +SWEP.RopeLength = 0 +SWEP.CuffReusable = false + +SWEP.CuffBlindfold = true +SWEP.CuffGag = true + +SWEP.CuffStrengthVariance = 0.1 // Randomise strangth +SWEP.CuffRegenVariance = 0.1 // Randomise regen diff --git a/garrysmod/addons/handcuffs/lua/weapons/weapon_handcuffed.lua b/garrysmod/addons/handcuffs/lua/weapons/weapon_handcuffed.lua new file mode 100644 index 0000000..1e21018 --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/weapons/weapon_handcuffed.lua @@ -0,0 +1,601 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Nathan Healy -- +-------- All rights reserved -------- +------------------------------------- +-- weapon_handcuffed.lua SHARED -- +-- -- +-- Handcuffed. Limits what -- +-- equipping player can do. -- +------------------------------------- + +AddCSLuaFile() + +SWEP.Base = "weapon_base" + +SWEP.Category = "Handcuffs" +SWEP.Author = "my_hat_stinks" +SWEP.Instructions = "" + +SWEP.Spawnable = false +SWEP.AdminSpawnable = false +SWEP.AdminOnly = false + +SWEP.Slot = 4 +SWEP.PrintName = "Handcuffed" + +SWEP.ViewModelFOV = 60 +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.WorldModel = "models/weapons/w_toolgun.mdl" +SWEP.ViewModel = "models/weapons/c_arms_citizen.mdl" +SWEP.UseHands = true + +SWEP.Primary.Recoil = 1 +SWEP.Primary.Damage = 5 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0 +SWEP.Primary.Delay = 1 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" +SWEP.Primary.ClipMax = -1 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipMax = -1 + +SWEP.DeploySpeed = 1.5 + +SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK +SWEP.ReloadAnim = ACT_VM_RELOAD +SWEP.HoldType = "duel" + +SWEP.IsHandcuffs = true +SWEP.CuffType = "" +local LEASH = {} + +CreateConVar( "cuffs_allowbreakout", 1, {FCVAR_ARCHIVE,FCVAR_SERVER_CAN_EXECUTE,FCVAR_REPLICATED,FCVAR_NOTIFY} ) + +// For anything that might try to drop this +SWEP.CanDrop = false +SWEP.PreventDrop = true +-- Missing anything? + +// +// DataTables +function SWEP:SetupDataTables() + self:NetworkVar( "Entity", 0, "Kidnapper" ) + self:NetworkVar( "Entity", 1, "FriendBreaking" ) + + self:NetworkVar( "Float", 0, "RopeLength" ) + self:NetworkVar( "Float", 1, "CuffBroken" ) + self:NetworkVar( "Float", 2, "CuffStrength" ) + self:NetworkVar( "Float", 3, "CuffRegen" ) + + self:NetworkVar( "String", 0, "RopeMaterial" ) + self:NetworkVar( "String", 1, "CuffMaterial" ) + + self:NetworkVar( "Bool", 0, "CanGag" ) + self:NetworkVar( "Bool", 1, "IsGagged" ) + + self:NetworkVar( "Bool", 2, "CanBlind" ) + self:NetworkVar( "Bool", 3, "IsBlind" ) + self:NetworkVar( "Bool", 4, "IsLeash" ) +end + +// +// Initialize +function SWEP:Initialize() + hook.Add( "canDropWeapon", self, function(self, ply, wep) if wep==self then return false end end) // Thank you DarkRP, your code is terrible + + if SERVER then + hook.Add( "Think", self, self.BreakThink ) + end + if CLIENT then + hook.Add( "HUDPaint", self, self.CuffsHUD ) + hook.Add( "HUDPaintBackground", self, self.BlindHUDBackground ) + end + + if self:GetCuffStrength()<=0 then self:SetCuffStrength(1) end + if self:GetCuffRegen()<=0 then self:SetCuffRegen(1) end + self:SetCuffBroken( 0 ) + + self:SetHoldType( self.HoldType ) +end + +// +// Standard SWEP functions +function SWEP:PrimaryAttack() + if SERVER then self:AttemptBreak() end +end +function SWEP:SecondaryAttack() end +function SWEP:Reload() end + +// +// Equip and Holster +function SWEP:Equip( newOwner ) + newOwner:SelectWeapon( self:GetClass() ) + + timer.Simple( 0.1, function() // Fucking FA:S + if IsValid(self) and IsValid(newOwner) and newOwner:GetActiveWeapon()~=self then + local wep = newOwner:GetActiveWeapon() + if not IsValid(wep) then return end + + local oHolster = wep.Holster + wep.Holster = function() return true end + newOwner:SelectWeapon( self:GetClass() ) + wep.Holster = oHolster + end + end) + + return true +end +function SWEP:Holster() + return self:GetIsLeash() +end + +// +// Deploy +function SWEP:Deploy() + local viewModel = self.Owner:GetViewModel() + viewModel:SendViewModelMatchingSequence( viewModel:LookupSequence("fists_idle_01") ) + + return true +end +function SWEP:PreDrawViewModel( viewModel ) // Fixes visible base hands + viewModel:SetMaterial( "engine/occlusionproxy" ) +end +if CLIENT then + local HadCuffs + hook.Add( "PreDrawOpaqueRenderables", "Cuffs FixViewModel", function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local HasCuffs = IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon():GetClass()=="weapon_handcuffed" + if HadCuffs and not HasCuffs then + local vm = ply:GetViewModel() + if IsValid(vm) then + vm:SetMaterial( "" ) + end + end + + HadCuffs = HasCuffs + end) +end + +function SWEP:OnRemove() // Fixes invisible other weapons + if IsValid(self.Owner) then + local viewModel = self.Owner:GetViewModel() + if IsValid(viewModel) then viewModel:SetMaterial("") end + end + if IsValid( self.cmdl_LeftCuff ) then self.cmdl_LeftCuff:Remove() end + if IsValid( self.cmdl_RightCuff ) then self.cmdl_RightCuff:Remove() end + return true +end + +// +// Release +function SWEP:Uncuff() + local ply = IsValid(self.Owner) and self.Owner + + self:Remove() + + if ply then ply:ConCommand( "lastinv" ) end +end + +// +// Breakout +if SERVER then + local BreakSound = Sound( "physics/metal/metal_barrel_impact_soft4.wav" ) + function SWEP:Breakout() + if IsValid(self.Owner) then + sound.Play( BreakSound, self.Owner:GetShootPos(), 75, 100, 1 ) + if IsValid( self:GetFriendBreaking() ) then + hook.Call( "OnHandcuffBreak", GAMEMODE, self.Owner, self, self:GetFriendBreaking() ) + else + hook.Call( "OnHandcuffBreak", GAMEMODE, self.Owner, self ) + end + end + + self:Uncuff() + end + function SWEP:AttemptBreak() + if not cvars.Bool( "cuffs_allowbreakout" ) then return end + + self:SetCuffBroken( self:GetCuffBroken() + math.abs(4/self:GetCuffStrength()) ) + + if self:GetCuffBroken()>=100 then + self:Breakout() + end + end + + local function GetTrace( ply ) + local tr = util.TraceLine( {start=ply:EyePos(), endpos=ply:EyePos()+(ply:GetAimVector()*100), filter=ply} ) + if IsValid(tr.Entity) and tr.Entity:IsPlayer() then + local cuffed,wep = tr.Entity:IsHandcuffed() + if cuffed then return tr,wep end + end + end + function SWEP:BreakThink() + if (self.NextRegen or 0)<=CurTime() then + local regen = self:GetCuffRegen() + local friend = self:GetFriendBreaking() + if IsValid(friend) and friend:IsPlayer() then + local tr = GetTrace(friend) + if tr and tr.Entity==self.Owner then + regen = (regen*0.5) - (2/self:GetCuffStrength()) + else + self:SetFriendBreaking( nil ) + end + end + + self:SetCuffBroken( math.Approach( self:GetCuffBroken(), regen<0 and 100 or 0, math.abs(regen) ) ) + self.NextRegen = CurTime()+0.05 + + if self:GetCuffBroken()>=100 then self:Breakout() end + end + if IsValid(self:GetKidnapper()) and (self:GetKidnapper():IsPlayer() and not self:GetKidnapper():Alive()) then + self:SetKidnapper( nil ) + end + if IsValid(self.Owner) then + self.Owner.KnockoutTimer = CurTime()+10 // Fucking DarkRP + end + end +end + +// +// UI +if CLIENT then + surface.CreateFont( "HandcuffsText", { + font = "Arial", + size = 20, + weight = 700, + }) + local Col = { + Text = Color(255,255,255), TextShadow = Color(0,0,0), + + BoxOutline = Color(0,0,0), BoxBackground = Color(255,255,255,20), BoxLeft = Color(255,0,0), BoxRight = Color(0,255,0), + + Blind = Color(0,0,0, 253), Blind2 = Color(0,0,0, 255), + } + local matGrad = Material( "gui/gradient" ) + function SWEP:CuffsHUD() + if self.Owner~=LocalPlayer() then return end + local w,h = (ScrW()/2), (ScrH()/2) + + local TextPos = h+30 + local str = string.format( "Your hands are bound%s", + ((self:GetCuffStrength()>1.2) and " tightly") or ((self:GetCuffStrength()<0.8) and " loosely") or "" + ) + if self:GetCuffRegen()>1.2 or self:GetCuffRegen()<0.8 then + str = str.. string.format( " with a %s material", ((self:GetCuffRegen()>1.2) and "sturdy") or "flimsy" ) + end + + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + + if self:GetIsBlind() then + TextPos = TextPos+20 + draw.SimpleText( "You have been bindfolded", "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( "You have been bindfolded", "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + end + if self:GetIsGagged() then + TextPos = TextPos+20 + draw.SimpleText( "You have been gagged", "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( "You have been gagged", "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + end + + if cvars.Bool( "cuffs_allowbreakout" ) then + TextPos = TextPos+20 + + if self.Owner:GetActiveWeapon()==self then + str = string.format("%s to struggle", (input.LookupBinding("+attack") or "[Primary Fire]"):upper()) + else + str = "Switch to ".. self.PrintName .." to break out" + end + draw.SimpleText( str, "HandcuffsText", w+1, TextPos+1, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( str, "HandcuffsText", w, TextPos, Col.Text, TEXT_ALIGN_CENTER ) + end + + TextPos = TextPos+25 + surface.SetDrawColor( Col.BoxOutline ) + surface.DrawOutlinedRect( w-101, TextPos-1, 202, 22 ) + surface.SetDrawColor( Col.BoxBackground ) + surface.DrawRect( w-100, TextPos, 200, 20 ) + + render.SetScissorRect( w-100, TextPos, (w-100)+((self:GetCuffBroken()/100)*200), TextPos+20, true ) + surface.SetDrawColor( Col.BoxRight ) + surface.DrawRect( w-100,TextPos, 200,20 ) + + surface.SetMaterial( matGrad ) + surface.SetDrawColor( Col.BoxLeft ) + surface.DrawTexturedRect( w-100,TextPos, 200,20 ) + render.SetScissorRect( 0,0,0,0, false ) + end + function SWEP:BlindHUDBackground() + if self.Owner~=LocalPlayer() then return end + if self:GetIsBlind() then + surface.SetDrawColor( Col.Blind ) + surface.DrawRect( 0,0, ScrW(), ScrH() ) + + surface.SetDrawColor( Col.Blind2 ) + for i=1,ScrH(),5 do + surface.DrawRect( 0,i, ScrW(), 4 ) + end + for i=1,ScrW(),5 do + surface.DrawRect( i,0, 4,ScrH() ) + end + end + end +end + +// +// Rendering +local renderpos = { + left = {bone = "ValveBiped.Bip01_L_Wrist", pos=Vector(0.4,-0.15,-0.45), ang=Angle(90,0,0), scale = Vector(0.035,0.035,0.015)}, + right = {bone = "ValveBiped.Bip01_R_Wrist", pos=Vector(0.2,-0.15,0.35), ang=Angle(100,0,0), scale = Vector(0.035,0.035,0.015)}, + rope = {l = Vector(-0.2,1.3,-0.25), r = Vector(0.4,1.4,-0.2)}, +} +local CuffMdl = "models/hunter/tubes/tube2x2x1.mdl" +local DefaultRope = "cable/cable2" +local RopeCol = Color(255,255,255) +function SWEP:ViewModelDrawn( vm ) + if self:GetIsLeash() then self.ViewModelDrawn = LEASH.ViewModelDrawn return end + if not IsValid(vm) then return end + + if not IsValid(self.cmdl_LeftCuff) then + self.cmdl_LeftCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + if not IsValid( self.cmdl_LeftCuff ) then return end // What + self.cmdl_LeftCuff:SetNoDraw( true ) + self.cmdl_LeftCuff:SetParent( vm ) + end + if not IsValid(self.cmdl_RightCuff) then + self.cmdl_RightCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + if not IsValid( self.cmdl_RightCuff ) then return end + self.cmdl_RightCuff:SetNoDraw( true ) + self.cmdl_RightCuff:SetParent( vm ) + end + + local lpos, lang = self:GetBonePos( renderpos.left.bone, vm ) + local rpos, rang = self:GetBonePos( renderpos.right.bone, vm ) + if not (lpos and rpos and lang and rang) then return end + + // Left + self.cmdl_LeftCuff:SetPos( lpos + (lang:Forward()*renderpos.left.pos.x) + (lang:Right()*renderpos.left.pos.y) + (lang:Up()*renderpos.left.pos.z) ) + local u,r,f = lang:Up(), lang:Right(), lang:Forward() // Prevents moving axes + lang:RotateAroundAxis( u, renderpos.left.ang.y ) + lang:RotateAroundAxis( r, renderpos.left.ang.p ) + lang:RotateAroundAxis( f, renderpos.left.ang.r ) + self.cmdl_LeftCuff:SetAngles( lang ) + + local matrix = Matrix() + matrix:Scale( renderpos.left.scale ) + self.cmdl_LeftCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_LeftCuff:SetMaterial( self:GetCuffMaterial() or "" ) + self.cmdl_LeftCuff:DrawModel() + + // Right + self.cmdl_RightCuff:SetPos( rpos + (rang:Forward()*renderpos.right.pos.x) + (rang:Right()*renderpos.right.pos.y) + (rang:Up()*renderpos.right.pos.z) ) + local u,r,f = rang:Up(), rang:Right(), rang:Forward() // Prevents moving axes + rang:RotateAroundAxis( u, renderpos.right.ang.y ) + rang:RotateAroundAxis( r, renderpos.right.ang.p ) + rang:RotateAroundAxis( f, renderpos.right.ang.r ) + self.cmdl_RightCuff:SetAngles( rang ) + + local matrix = Matrix() + matrix:Scale( renderpos.right.scale ) + self.cmdl_RightCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_RightCuff:SetMaterial( self:GetCuffMaterial() or "" ) + self.cmdl_RightCuff:DrawModel() + + // Rope + if self:GetRopeMaterial()~=self.LastMatStr then + self.RopeMat = Material( self:GetRopeMaterial() ) + self.LastMatStr = self:GetRopeMaterial() + end + if not self.RopeMat then self.RopeMat = Material(DefaultRope) end + + render.SetMaterial( self.RopeMat ) + render.DrawBeam( lpos + (lang:Forward()*renderpos.rope.l.x) + (lang:Right()*renderpos.rope.l.y) + (lang:Up()*renderpos.rope.l.z), + rpos + (rang:Forward()*renderpos.rope.r.x) + (rang:Right()*renderpos.rope.r.y) + (rang:Up()*renderpos.rope.r.z), + 0.7, 0, 5, RopeCol ) +end + +local wrender = { + left = {bone = "ValveBiped.Bip01_L_Hand", pos=Vector(0,0,0), ang=Angle(90,0,0), scale = Vector(0.035,0.035,0.035)}, + right = {bone = "ValveBiped.Bip01_R_Hand", pos=Vector(0.2,0,0), ang=Angle(90,0,0), scale = Vector(0.035,0.035,0.035)}, + rope = {l = Vector(-0.2,1.3,-0.25), r = Vector(0.4,1.4,-0.2)}, +} +function SWEP:DrawWorldModel() + if self:GetIsLeash() then + -- self.DrawWorldModel = LEASH.DrawWorldModel + self.DrawWorldModel = function() end + hook.Add( "PostDrawOpaqueRenderables", self, LEASH.DrawWorldModel ) + return + end + if not IsValid(self.Owner) then return end + + if not IsValid(self.cmdl_LeftCuff) then + self.cmdl_LeftCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + if not IsValid( self.cmdl_LeftCuff ) then return end + self.cmdl_LeftCuff:SetNoDraw( true ) + -- self.cmdl_LeftCuff:SetParent( vm ) + end + if not IsValid(self.cmdl_RightCuff) then + self.cmdl_RightCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + if not IsValid( self.cmdl_RightCuff ) then return end + self.cmdl_RightCuff:SetNoDraw( true ) + -- self.cmdl_RightCuff:SetParent( vm ) + end + + local lpos, lang = self:GetBonePos( wrender.left.bone, self.Owner ) + local rpos, rang = self:GetBonePos( wrender.right.bone, self.Owner ) + if not (lpos and rpos and lang and rang) then return end + + // Left + self.cmdl_LeftCuff:SetPos( lpos + (lang:Forward()*wrender.left.pos.x) + (lang:Right()*wrender.left.pos.y) + (lang:Up()*wrender.left.pos.z) ) + local u,r,f = lang:Up(), lang:Right(), lang:Forward() // Prevents moving axes + lang:RotateAroundAxis( u, wrender.left.ang.y ) + lang:RotateAroundAxis( r, wrender.left.ang.p ) + lang:RotateAroundAxis( f, wrender.left.ang.r ) + self.cmdl_LeftCuff:SetAngles( lang ) + + local matrix = Matrix() + matrix:Scale( wrender.left.scale ) + self.cmdl_LeftCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_LeftCuff:SetMaterial( self:GetCuffMaterial() or "" ) + self.cmdl_LeftCuff:DrawModel() + + // Right + self.cmdl_RightCuff:SetPos( rpos + (rang:Forward()*wrender.right.pos.x) + (rang:Right()*wrender.right.pos.y) + (rang:Up()*wrender.right.pos.z) ) + local u,r,f = rang:Up(), rang:Right(), rang:Forward() // Prevents moving axes + rang:RotateAroundAxis( u, wrender.right.ang.y ) + rang:RotateAroundAxis( r, wrender.right.ang.p ) + rang:RotateAroundAxis( f, wrender.right.ang.r ) + self.cmdl_RightCuff:SetAngles( rang ) + + local matrix = Matrix() + matrix:Scale( wrender.right.scale ) + self.cmdl_RightCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_RightCuff:SetMaterial( self:GetCuffMaterial() or "" ) + self.cmdl_RightCuff:DrawModel() + + // Rope + if (lpos.x==0 and lpos.y==0 and lpos.z==0) or (rpos.x==0 and rpos.y==0 and rpos.z==0) then return end // Rope accross half the map... + + if self:GetRopeMaterial()~=self.LastMatStr then + self.RopeMat = Material( self:GetRopeMaterial() ) + self.LastMatStr = self:GetRopeMaterial() + end + if not self.RopeMat then self.RopeMat = Material(DefaultRope) end + + render.SetMaterial( self.RopeMat ) + render.DrawBeam( lpos + (lang:Forward()*wrender.rope.l.x) + (lang:Right()*wrender.rope.l.y) + (lang:Up()*wrender.rope.l.z), + rpos + (rang:Forward()*wrender.rope.r.x) + (rang:Right()*wrender.rope.r.y) + (rang:Up()*wrender.rope.r.z), + 0.7, 0, 5, RopeCol ) +end + +// +// Bones +function SWEP:GetBonePos( bonename, vm ) + local bone = vm:LookupBone( bonename ) + if not bone then return end + + local pos,ang = Vector(0,0,0),Angle(0,0,0) + local matrix = vm:GetBoneMatrix( bone ) + if matrix then + pos = matrix:GetTranslation() + ang = matrix:GetAngles() + end + + if self.ViewModelFlip then ang.r = -ang.r end + + -- if pos.x==0 and pos.y==0 and pos.z==0 then print( bonename, vm ) end + return pos, ang +end + +// +// Leash +LEASH.HoldType = "normal" + +// +// Rendering +local vrender = { + bone = "ValveBiped.Bip01_L_Wrist", + pos=Vector(0,0,-1.5), + ang=Angle(0,0,0), + scale = Vector(0.01,0.01,0.02), +} +function LEASH:ViewModelDrawn( vm ) + self.UseHands = false + if not (IsValid(self) and IsValid(self.Owner)) then return end + + if not IsValid(self.cmdl_LeftCuff) then + self.cmdl_LeftCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + if not IsValid( self.cmdl_LeftCuff ) then return end + self.cmdl_LeftCuff:SetNoDraw( true ) + -- self.cmdl_LeftCuff:SetParent( vm ) + end + + local lpos, lang = self:GetBonePos( vrender.bone, self.Owner ) + if not (lpos and lang) then return end + + -- lpos = self.Owner:GetPos() + self.Owner:GetCurrentViewOffset() + -- lang = Angle(0,0,0) + + self.cmdl_LeftCuff:SetPos( lpos + (lang:Forward()*vrender.pos.x) + (lang:Right()*vrender.pos.y) + (lang:Up()*vrender.pos.z) ) + local u,r,f = lang:Up(), lang:Right(), lang:Forward() // Prevents moving axes + lang:RotateAroundAxis( u, vrender.ang.y ) + lang:RotateAroundAxis( r, vrender.ang.p ) + lang:RotateAroundAxis( f, vrender.ang.r ) + self.cmdl_LeftCuff:SetAngles( lang ) + + local matrix = Matrix() + matrix:Scale( vrender.scale ) + self.cmdl_LeftCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_LeftCuff:SetMaterial( self:GetCuffMaterial() or "" ) + self.cmdl_LeftCuff:DrawModel() +end + +local lrender = { + normal = { + bone = "ValveBiped.Bip01_Neck1", + pos=Vector(2,1.8,0), + ang=Angle(70,90,90), + scale = Vector(0.06,0.06,0.05), + }, + alt = { // Eeveelotions models + bone = "Neck", + pos=Vector(1,0.5,-0.2), + ang=Angle(100,90,90), + scale = Vector(0.082,0.082,0.082), + }, +} +function LEASH.DrawWorldModel(self) + if not (IsValid(self) and IsValid(self.Owner)) then return end + if self.Owner==LocalPlayer() and self.Owner:GetActiveWeapon()==self and not hook.Call("ShouldDrawLocalPlayer", GAMEMODE, self.Owner) then return end + + if not IsValid(self.cmdl_LeftCuff) then + self.cmdl_LeftCuff = ClientsideModel( CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE ) + if not IsValid( self.cmdl_LeftCuff ) then return end + self.cmdl_LeftCuff:SetNoDraw( true ) + -- self.cmdl_LeftCuff:SetParent( vm ) + end + + local tbl = lrender.normal + local lpos, lang = self:GetBonePos( tbl.bone, self.Owner ) + if not (lpos) then + tbl = lrender.alt + lpos, lang = self:GetBonePos( tbl.bone, self.Owner ) + if not (lpos) then return end + end + + self.cmdl_LeftCuff:SetPos( lpos + (lang:Forward()*tbl.pos.x) + (lang:Right()*tbl.pos.y) + (lang:Up()*tbl.pos.z) ) + local u,r,f = lang:Up(), lang:Right(), lang:Forward() // Prevents moving axes + lang:RotateAroundAxis( u, tbl.ang.y ) + lang:RotateAroundAxis( r, tbl.ang.p ) + lang:RotateAroundAxis( f, tbl.ang.r ) + self.cmdl_LeftCuff:SetAngles( lang ) + + local matrix = Matrix() + matrix:Scale( tbl.scale ) + self.cmdl_LeftCuff:EnableMatrix( "RenderMultiply", matrix ) + + self.cmdl_LeftCuff:SetMaterial( self:GetCuffMaterial() or "" ) + self.cmdl_LeftCuff:DrawModel() +end diff --git a/garrysmod/addons/handcuffs/lua/weapons/weapon_leash_base.lua b/garrysmod/addons/handcuffs/lua/weapons/weapon_leash_base.lua new file mode 100644 index 0000000..50846e4 --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/weapons/weapon_leash_base.lua @@ -0,0 +1,77 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Nathan Healy -- +-------- All rights reserved -------- +------------------------------------- +-- weapon_cuff_base.lua SHARED -- +-- -- +-- Base swep for handcuffs. -- +------------------------------------- + +AddCSLuaFile() + +SWEP.Base = "weapon_cuff_base" + +SWEP.Category = "Handcuffs" +SWEP.Author = "my_hat_stinks" +SWEP.Instructions = "" + +SWEP.Slot = 3 +SWEP.PrintName = "Unnamed" + +SWEP.IsLeash = true + +local Col = { + Text = Color(255,255,255), TextShadow = Color(0,0,0), + + BoxOutline = Color(0,0,0), BoxBackground = Color(255,255,255,20), BoxLeft = Color(255,0,0), BoxRight = Color(0,255,0), +} +local matGrad = Material( "gui/gradient" ) +function SWEP:DrawHUD() + if not self:GetIsCuffing() then + if self:GetCuffTime()<=CurTime() then return end + + local w,h = (ScrW()/2), (ScrH()/2) + + surface.SetDrawColor( Col.BoxOutline ) + surface.DrawOutlinedRect( w-101, h+54, 202, 22 ) + surface.SetDrawColor( Col.BoxBackground ) + surface.DrawRect( w-100, h+55, 200, 20 ) + + local CuffingPercent = math.Clamp( ((self:GetCuffTime()-CurTime())/self.CuffRecharge), 0, 1 ) + render.SetScissorRect( w-100, h+55, (w-100)+(CuffingPercent*200), h+75, true ) + surface.SetDrawColor( Col.BoxRight ) + surface.DrawRect( w-100,h+55, 200,20 ) + + surface.SetMaterial( matGrad ) + surface.SetDrawColor( Col.BoxLeft ) + surface.DrawTexturedRect( w-100,h+55, 200,20 ) + render.SetScissorRect( 0,0,0,0, false ) + + return + end + + local w,h = (ScrW()/2), (ScrH()/2) + + draw.SimpleText( "Leashing target...", "HandcuffsText", w+1, h+31, Col.TextShadow, TEXT_ALIGN_CENTER ) + draw.SimpleText( "Leashing target...", "HandcuffsText", w, h+30, Col.Text, TEXT_ALIGN_CENTER ) + + surface.SetDrawColor( Col.BoxOutline ) + surface.DrawOutlinedRect( w-101, h+54, 202, 22 ) + surface.SetDrawColor( Col.BoxBackground ) + surface.DrawRect( w-100, h+55, 200, 20 ) + + local CuffingPercent = math.Clamp( 1-((self:GetCuffTime()-CurTime())/self.CuffTime), 0, 1 ) + + render.SetScissorRect( w-100, h+55, (w-100)+(CuffingPercent*200), h+75, true ) + surface.SetDrawColor( Col.BoxRight ) + surface.DrawRect( w-100,h+55, 200,20 ) + + surface.SetMaterial( matGrad ) + surface.SetDrawColor( Col.BoxLeft ) + surface.DrawTexturedRect( w-100,h+55, 200,20 ) + render.SetScissorRect( 0,0,0,0, false ) +end diff --git a/garrysmod/addons/handcuffs/lua/weapons/weapon_leash_rope.lua b/garrysmod/addons/handcuffs/lua/weapons/weapon_leash_rope.lua new file mode 100644 index 0000000..9dd78dd --- /dev/null +++ b/garrysmod/addons/handcuffs/lua/weapons/weapon_leash_rope.lua @@ -0,0 +1,45 @@ + +----------------------------------------------------- +------------------------------------- +---------------- Cuffs -------------- +------------------------------------- +-- Copyright (c) 2015 Nathan Healy -- +-------- All rights reserved -------- +------------------------------------- +-- weapon_cuff_standard.lua SHARED -- +-- -- +-- Rope handcuffs. -- +------------------------------------- + +AddCSLuaFile() + +SWEP.Base = "weapon_leash_base" + +SWEP.Category = "Handcuffs" +SWEP.Author = "my_hat_stinks" +SWEP.Instructions = "A weak leash." + +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.AdminSpawnable = false + +SWEP.Slot = 3 +SWEP.PrintName = "Rope Leash" + +// +// Handcuff Vars +SWEP.CuffTime = 0.8 // Seconds to handcuff +SWEP.CuffSound = Sound( "buttons/lever7.wav" ) + +SWEP.CuffMaterial = "models/props_foliage/tree_deciduous_01a_trunk" +SWEP.CuffRope = "cable/rope" +SWEP.CuffStrength = 0.85 +SWEP.CuffRegen = 0.8 +SWEP.RopeLength = 100 +SWEP.CuffReusable = false + +SWEP.CuffBlindfold = false +SWEP.CuffGag = false + +SWEP.CuffStrengthVariance = 0.1 // Randomise strangth +SWEP.CuffRegenVariance = 0.2 // Randomise regen diff --git a/garrysmod/addons/igsmodification/lua/autorun/cl_session_reward_ui.lua b/garrysmod/addons/igsmodification/lua/autorun/cl_session_reward_ui.lua new file mode 100644 index 0000000..d2e5574 --- /dev/null +++ b/garrysmod/addons/igsmodification/lua/autorun/cl_session_reward_ui.lua @@ -0,0 +1,108 @@ +local HOUR = 60 * 60 +local REWARD_AFTER = HOUR * 2 +local REWARD_SIZE = 150 + +local startTime = os.time() +local animatedProgress = 0 + +CreateClientConVar("cl_igs_session_ui_hide", "0", true, false) + +surface.CreateFont("IGS_SessionTitle", { + font = "Roboto", + size = 17, + weight = 600 +}) + +surface.CreateFont("IGS_SessionSub", { + font = "Roboto", + size = 14, + weight = 400 +}) + +surface.CreateFont("IGS_Tooltip", { + font = "Roboto", + size = 12, + weight = 400 +}) + +local coinMat = Material("icon16/coins.png") +local gearMat = Material("icon16/cog.png") -- ⚙ значок настроек (скрыть) + +net.Receive("igs_session_start", function() + startTime = net.ReadInt(32) +end) + +local function GetMinutesLeft() + local elapsed = os.time() - startTime + local left = math.max(0, REWARD_AFTER - elapsed) + return math.ceil(left / 60) +end + +-- 🔥 Toggle по клавише F6 +hook.Add("Think", "igs_session_ui_toggle", function() + if input.IsKeyDown(KEY_F2) and not _igsKeyPressed then + _igsKeyPressed = true + RunConsoleCommand("cl_igs_session_ui_hide", + GetConVar("cl_igs_session_ui_hide"):GetInt() == 1 and "0" or "1") + elseif not input.IsKeyDown(KEY_F2) then + _igsKeyPressed = false + end +end) + +local width, height = 300, 98 + +hook.Add("HUDPaint", "igs_session_ui", function() + local pl = LocalPlayer() + if not IsValid(pl) then return end + + local hidden = GetConVar("cl_igs_session_ui_hide"):GetInt() == 1 + local minutesLeft = GetMinutesLeft() + if minutesLeft <= 0 then return end + if hidden then + -- Показываем только маленький tooltip внизу + local tx = ScrW() - 130 + local ty = 20 + draw.SimpleText("Нажмите F2 — показать", "IGS_Tooltip", + tx, ty, Color(200,200,200)) + return + end + + local x = ScrW() - width - 20 + local y = 20 + + draw.RoundedBox(8, x, y, width, height, Color(0,0,0,190)) + + local realProgress = 1 - (minutesLeft * 60) / REWARD_AFTER + animatedProgress = Lerp(FrameTime() * 3, animatedProgress, realProgress) + + surface.SetDrawColor(255,255,255) + surface.SetMaterial(coinMat) + surface.DrawTexturedRect(x + 10, y + 10, 18, 18) + + draw.SimpleText("Бонус за игру! " .. REWARD_SIZE .. " руб", + "IGS_SessionTitle", x + 32, y + 11, Color(255,255,255)) + + draw.RoundedBox(5, x + 10, y + 35, + (width - 20) * animatedProgress, 15, + Color(72,92,44)) + + draw.SimpleText("Осталось: " .. minutesLeft .. " мин", + "IGS_SessionSub", x + 10, y + 60, Color(220,220,220)) + + -- ⚙ Кнопка скрытия + surface.SetMaterial(gearMat) + surface.DrawTexturedRect(x + width - 27, y + 10, 16, 16) + + -- Проверка клика по кнопке + if input.IsMouseDown(MOUSE_LEFT) then + local mx, my = gui.MousePos() + if mx >= x + width - 27 and mx <= x + width - 11 + and my >= y + 10 and my <= y + 26 then + RunConsoleCommand("cl_igs_session_ui_hide", "1") + end + end + + -- Tooltip внизу панели + draw.SimpleText("Нажмите F2 — скрыть", "IGS_Tooltip", + x + 10, y + height - 18, Color(200, 200, 200, 180)) +end) diff --git a/garrysmod/addons/igsmodification/lua/autorun/l_ingameshopmod.lua b/garrysmod/addons/igsmodification/lua/autorun/l_ingameshopmod.lua new file mode 100644 index 0000000..dcedc03 --- /dev/null +++ b/garrysmod/addons/igsmodification/lua/autorun/l_ingameshopmod.lua @@ -0,0 +1,121 @@ +file.CreateDir("igs") + +-- Вы можете сделать форк основного репозитория, сделать там изменения и указать его имя здесь +-- Таким образом IGS будет грузиться у всех с вашего репозитория +IGS_REPO = "GM-DONATE/IGS" -- "AMD-NICK/IGS-1" +if not IGS_REPO or file.Exists("autorun/l_ingameshop.lua", "LUA") then return end -- force lua + + +local function checkRunString() + RunString("IGS_Test_RS = true", "IGS_Test_RS") + assert(IGS_Test_RS, "RunString не работает: https://forum.gm-donate.net/t/1663") + IGS_Test_RS = nil +end + +checkRunString() -- сразу может быть, а потом пропасть + +-- http либа работает не сразу +local fetchDelayed = function(delay, url, fOk, fErr, tHeaders) + timer.Simple(delay, function() + http.Fetch(url, fOk, fErr, tHeaders) + end) +end + +local replaceGithubUrl = function(original) + return original + :gsub("^https://api.github.com", "https://gh.gm-donate.net/api") + :gsub("^https://github.com", "https://gh.gm-donate.net") +end + +local function wrapFetch(url, cb, retry_) + local retry3Times = function() + retry_ = retry_ or 1 + if retry_ < 3 then + wrapFetch(url, cb, retry_ + 1) + elseif retry_ == 3 then -- last chance + local newurl = replaceGithubUrl(url) + wrapFetch(newurl or url, cb, retry_ + 1) + else + return true + end + end + + local patt = "IGS Не может выполнить HTTP запрос и загрузить скрипт\nURL: %s\nError: %s\n" + fetchDelayed((retry_ or 0) * 5, url, cb, function(err) -- timeout, unsuccessful + local fault = retry3Times() + if not fault then return end -- пытается дальше + -- попытки исчерпались + + error(patt:format(url, err)) + end) +end + + +local function downloadSuperfile(version, cb, _failure) + local url = "https://github.com/" .. IGS_REPO .. "/releases/download/" .. version .. "/superfile.json" + if _failure then ErrorNoHalt("[IGS] #" .. _failure .. " повторение загрузки", url) end + + wrapFetch(url, function(superfile) + local dat = util.JSONToTable(superfile) + if not dat and (_failure or 0) < 3 then + downloadSuperfile(version, cb, (_failure or 0) + 1) + return + end + + local err = + not dat and "superfile.json получен не в правильном формате" + or dat.error and ("Ошибка от GitHub: " .. dat.error) + + assert(not err, (err or "") .. "\n" .. url .. "\nПопробуйте снова или почитайте тут https://forum.gm-donate.net/t/1663") + + file.Write("igs/superfile.txt", superfile) + cb(superfile) + end) +end + +local function loadFromFile(superfile) + checkRunString() + + local path = "autorun/l_ingameshop.lua" + IGS_MOUNT = util.JSONToTable(superfile) + + RunString(IGS_MOUNT[path], path) +end + +local function findFreshestVersion(cb) + wrapFetch("https://api.github.com/repos/" .. IGS_REPO .. "/releases", function(json) + local releases = util.JSONToTable(json) + table.sort(releases, function(a, b) -- свежайшие версии сначала + return tonumber(a.tag_name) > tonumber(b.tag_name) + end) + + local freshest_version = releases[1] + assert(freshest_version, "Релизов нет. Нужно запустить CI: https://forum.gm-donate.net/t/1663") + + cb(freshest_version.tag_name) + end) +end + +if SERVER then + local superfile = file.Read("igs/superfile.txt") + local version = cookie.GetString("igs_version") + + if superfile and version then -- 2 может не быть, если сервер перенесли без sv.db + loadFromFile(superfile) + + elseif not version then + findFreshestVersion(function(freshest_version) + cookie.Set("igs_version", freshest_version) + downloadSuperfile(freshest_version, loadFromFile) + end) + + else -- version + downloadSuperfile(version, loadFromFile) + end + +elseif CLIENT then + CreateConVar("igs_version", "", {FCVAR_REPLICATED}) + local version = GetConVar("igs_version"):GetString() + assert(tonumber(version), "cvar igs_version не передался клиенту. " .. version .. ": https://forum.gm-donate.net/t/1663") + downloadSuperfile(version, loadFromFile) +end diff --git a/garrysmod/addons/igsmodification/lua/autorun/playtimebonus.lua b/garrysmod/addons/igsmodification/lua/autorun/playtimebonus.lua new file mode 100644 index 0000000..7fddef0 --- /dev/null +++ b/garrysmod/addons/igsmodification/lua/autorun/playtimebonus.lua @@ -0,0 +1,68 @@ +if SERVER then + util.AddNetworkString("igs_session_start") + util.AddNetworkString("igs_session_reward_given") + + local HOUR = 60 * 60 + local REWARD_AFTER = HOUR * 2 -- 2 часа + local REWARD_SIZE = 150 -- 150 рублей (изменено по просьбе пользователя) + + -- Таблица для отслеживания времени начала сессии игроков + local playerSessionStart = {} + local playerRewardGiven = {} + + -- Отправка времени начала сессии при спавне + hook.Add("PlayerInitialSpawn", "igs.session_reward_ui", function(pl) + playerSessionStart[pl:SteamID()] = os.time() + + local lastRewardDate = pl:GetPData("igs_playtime_bonus_date", "") + local currentDate = os.date("%d-%m-%Y") + + playerRewardGiven[pl:SteamID()] = (lastRewardDate == currentDate) + + net.Start("igs_session_start") + net.WriteInt(os.time(), 32) + net.Send(pl) + end) + + -- Очистка данных при отключении игрока + hook.Add("PlayerDisconnected", "igs.session_cleanup", function(pl) + playerSessionStart[pl:SteamID()] = nil + playerRewardGiven[pl:SteamID()] = nil + end) + + -- Проверка и выдача награды каждые 60 секунд + timer.Create("igs.check_session_rewards", 60, 0, function() + for _, pl in pairs(player.GetAll()) do + local steamID = pl:SteamID() + local startTime = playerSessionStart[steamID] + local rewardGiven = playerRewardGiven[steamID] + + if startTime and not rewardGiven then + local elapsed = os.time() - startTime + + -- Если прошло 2 часа - выдаем награду + if elapsed >= REWARD_AFTER then + -- Проверяем, что игрок не AFK (опционально) + if IsValid(pl) and pl:Alive() then + -- Выдаем рубли на баланс IGS + pl:AddIGSFunds(REWARD_SIZE, "Бонус за 2 часа игры") + + -- Уведомление игроку + IGS.Notify(pl, "Вы получили " .. REWARD_SIZE .. " руб за 2 часа игры!") + + -- Помечаем, что награда выдана + playerRewardGiven[steamID] = true + pl:SetPData("igs_playtime_bonus_date", os.date("%d-%m-%Y")) + + -- Логирование + print("[IGS Session Reward] " .. pl:Nick() .. " (" .. steamID .. ") получил " .. REWARD_SIZE .. " руб за 2 часа игры") + + -- Отправляем клиенту сигнал об успешной выдаче + net.Start("igs_session_reward_given") + net.Send(pl) + end + end + end + end + end) +end diff --git a/garrysmod/addons/igsmodification/lua/igs/settings/config_sh.lua b/garrysmod/addons/igsmodification/lua/igs/settings/config_sh.lua new file mode 100644 index 0000000..eea9b17 --- /dev/null +++ b/garrysmod/addons/igsmodification/lua/igs/settings/config_sh.lua @@ -0,0 +1,120 @@ +-- \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ +--[[------------------------------------------------------------------------- + ПРЕДМЕТЫ ДОБАВЛЯЮТСЯ В sh_additems.lua +---------------------------------------------------------------------------]] +-- /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ + + +--[[------------------------------------------------------------------------- + Настройки валюты +---------------------------------------------------------------------------]] +IGS.C.CURRENCY_SIGN = "RUB" + +-- Множественные названия валюты. +-- Пример 1: Доллар, Доллара, Долларов +-- Пример 2: Поинт, Поинта, Поинтов +IGS.C.CurrencyPlurals = { + "рубль", -- 1 алкобакс + "рубля", -- 3 алкобакса + "рублей" -- 5 алкобаксов +} + + +--[[------------------------------------------------------------------------- + Настройки активации интерфейса +---------------------------------------------------------------------------]] +-- На какую кнопку будет открываться донат менюшка +-- https://wiki.facepunch.com/gmod/Enums/KEY +IGS.C.MENUBUTTON = KEY_F9 + + +-- /команда для открытия донат менюшки +IGS.C.COMMANDS = { + ["donate"] = true, + ["донат"] = true, +} + + +--[[------------------------------------------------------------------------- + Донат инвентарь +---------------------------------------------------------------------------]] +-- Если отключить, вкладка инвентаря исчезнет, а предметы при покупке сразу будут активироваться +-- Станут недоступны некоторые методы, вроде :SetItems(, так как используют инвентарь +IGS.C.Inv_Enabled = true + +-- Разрешить выбрасывать предметы с инвентаря на пол +-- Это позволит игрокам покупать донат подарки для друзей или вам делать донат раздачи +IGS.C.Inv_AllowDrop = true + + + +if SERVER then return end -- не смотрите так на меня :) + + +-- Показывать ли уведомление о новых предметах в донат меню +-- Выглядит вот так https://img.qweqwe.ovh/1526574184864.png +IGS.C.NotifyAboutNewItems = true + + +-- Эта иконка будет отображена для предмета, если для него не будет установлена кастомная через :SetIcon() +-- Отображается вот тут: https://img.qweqwe.ovh/1494088609445.png +-- Хостинг картинок: https://t.me/cfr2bot +IGS.C.DefaultIcon = "https://file.def.pm/42Hr9k9U.jpeg" + + + + +-- Уберите "--" с начала строки, чтобы отключить появление определенного фрейма +IGS.C.DisabledFrames = { + -- ["faq_and_help"] = true, -- Чат бот (страница помощи) + -- ["profile"] = true, -- Страница профиля игрока (с транзакциями) + -- ["purchases"] = true, -- Активные покупки +} + +-- показывать супер-админам все модули на клиенте тоже +if CLIENT then + local plyMeta = FindMetaTable("Player") + if plyMeta and not plyMeta._IGS_HasPurchase_Orig_CL then + plyMeta._IGS_HasPurchase_Orig_CL = plyMeta.HasPurchase + function plyMeta:HasPurchase(uid, ...) + if self:IsSuperAdmin() then + return true + end + if plyMeta._IGS_HasPurchase_Orig_CL then + return plyMeta._IGS_HasPurchase_Orig_CL(self, uid, ...) + end + return false + end + end +end + + +-- Оставьте так, если не уверены +-- Инфо: https://vk.cc/6xaFOe +IGS.C.DATE_FORMAT = "%d.%m.%y %H:%M:%S" +IGS.C.DATE_FORMAT_SHORT = "%d.%m.%y" + +hook.Add("IGS.Initialized", "IGS.ColorsLoad", function() + IGS.S.COLORS.FRAME_HEADER = Color(0, 0, 0) + IGS.S.COLORS.ACTIVITY_BG = Color(0, 0, 0, 253) + IGS.S.COLORS.TAB_BAR = Color(0, 0, 0) + + IGS.S.COLORS.PASSIVE_SELECTIONS = Color(0, 0, 0) + IGS.S.COLORS.INNER_SELECTIONS = Color(0, 0, 0) + + IGS.S.COLORS.SOFT_LINE = Color(0, 67, 28) + IGS.S.COLORS.HARD_LINE = Color(0, 67, 28) + + IGS.S.COLORS.HIGHLIGHTING = Color(0, 67, 28) + IGS.S.COLORS.HIGHLIGHT_INACTIVE = Color(0, 67, 28) + + IGS.S.COLORS.TEXT_HARD = Color(255,255,255) + IGS.S.COLORS.TEXT_SOFT = Color(255,255,255) + IGS.S.COLORS.TEXT_ON_HIGHLIGHT = Color(255,255,255) + + IGS.S.COLORS.LOG_SUCCESS = Color(76,217,100) + IGS.S.COLORS.LOG_ERROR = Color(255,45,85) + IGS.S.COLORS.LOG_NORMAL = Color(255,255,255) + + IGS.S.COLORS.ICON = Color(255,255,255) +end) diff --git a/garrysmod/addons/igsmodification/lua/igs/settings/config_sv.lua b/garrysmod/addons/igsmodification/lua/igs/settings/config_sv.lua new file mode 100644 index 0000000..56719bb --- /dev/null +++ b/garrysmod/addons/igsmodification/lua/igs/settings/config_sv.lua @@ -0,0 +1,8 @@ +-- Вот так просто! :) + +-- ID проекта в системе +IGS.C.ProjectID = 4637 + +-- Секретный ключ проекта. Никому не сообщайте. +-- С этим ключом можно запрашивать и изменять данные ваших донатеров +IGS.C.ProjectKey = "50900bf9130d4bc45368a09e8defe96a" diff --git a/garrysmod/addons/igsmodification/lua/igs/settings/sh_additems.lua b/garrysmod/addons/igsmodification/lua/igs/settings/sh_additems.lua new file mode 100644 index 0000000..254f2fa --- /dev/null +++ b/garrysmod/addons/igsmodification/lua/igs/settings/sh_additems.lua @@ -0,0 +1,518 @@ + --[[------------------------------------------------------------------------- + Обязательные методы: + :SetPrice() + :SetDescription() + + Популярные: + :SetTerm() --> Срок действия в днях (по умолчанию 0, т.е. одноразовая активация) + :SetStackable() --> Разрешает покупать несколько одинаковых предметов + :SetCategory() --> Группирует предметы + :SetIcon() --> Картинка или модель в качестве иконки + :SetHighlightColor() --> Цвет заголовка + :SetDiscountedFrom() --> Скидка + :SetOnActivate() --> Свое действие при активации + :SetHidden() --> Скрытый предмет + + Полезное: + gm-donate.net/docs --> Подробнее о методах и все остальные + gm-donate.net/support --> Быстрая помощь и настройка от нас + gm-donate.net/mods --> Бесплатные модули +---------------------------------------------------------------------------]] + +-- Ниже примеры с объяснением + +--[[------------------------------------------------------------------------- + Разрешаем покупать отмычку а F4 только донатерам (DarkRP) + https://img.qweqwe.ovh/1493244432112.png -- частичное объяснение +---------------------------------------------------------------------------]] +IGS("VSS Vintorez", "tacrp_io_vss"):SetWeapon("tacrp_io_vss") +:SetPrice(1000) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("AI AS50", "tacrp_as50"):SetWeapon("tacrp_as50") +:SetPrice(1500) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("PGM Hecate II", "tacrp_ex_hecate"):SetWeapon("tacrp_ex_hecate") +:SetPrice(1200) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("AK12", "tacrp_ak_ak12"):SetWeapon("tacrp_ak_ak12") +:SetPrice(1000) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("KAC PDW", "tacrp_pdw"):SetWeapon("tacrp_pdw") +:SetPrice(1000) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("HK417", "tacrp_hk417"):SetWeapon("tacrp_hk417") +:SetPrice(500) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("SCAR H SSR", "tacrp_io_scarh"):SetWeapon("tacrp_io_scarh") +:SetPrice(1200) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("HK MG4", "tacrp_mg4"):SetWeapon("tacrp_mg4") +:SetPrice(900) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("HK MK8 LMG", "tacrp_io_xm8lmg"):SetWeapon("tacrp_io_xm8lmg") +:SetPrice(900) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("KRISS Vector", "tacrp_superv"):SetWeapon("tacrp_superv") +:SetPrice(1000) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("AAC Honey Badger", "tacrp_sd_aac_hb"):SetWeapon("tacrp_sd_aac_hb") +:SetPrice(1500) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("AN-94", "tacrp_ak_an94"):SetWeapon("tacrp_ak_an94") +:SetPrice(1000) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("SIG SG 551", "tacrp_sg551"):SetWeapon("tacrp_sg551") +:SetPrice(800) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("SIG SG 550-2 SP", "tacrp_io_sg550r"):SetWeapon("tacrp_io_sg550r") +:SetPrice(1200) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("SIG SG 550-1 Sniper", "tacrp_io_sg550"):SetWeapon("tacrp_io_sg550") +:SetPrice(1500) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("HK XM8 Compact", "tacrp_io_xm8car"):SetWeapon("tacrp_io_xm8car") +:SetPrice(1500) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("HK SL8", "tacrp_io_sl8"):SetWeapon("tacrp_io_sl8") +:SetPrice(800) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("Desert Eagle", "tacrp_io_degala"):SetWeapon("tacrp_io_degala") +:SetPrice(500) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("FN Five-seveN", "tacrp_io_fiveseven"):SetWeapon("tacrp_io_fiveseven") +:SetPrice(250) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("Manurhin MR96", "tacrp_mr96"):SetWeapon("tacrp_mr96") +:SetPrice(700) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") + +IGS("Крюк Кошка", "weapon_rope_knife"):SetWeapon("weapon_rope_knife") +:SetPrice(350) +:SetTerm(45) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат вооружения. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("Трофейное Оружие") +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +IGS("Починка транспорта", "repcar"):SetWeapon("weapon_lvsrepair") +:SetPrice(300) +:SetTerm(30) +:SetDescription("Починка транспорта") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("РП Предметы") + +IGS("Сигаретка", "sigareta"):SetWeapon("weapon_cigarette_camel") +:SetPrice(150) +:SetTerm(30) +:SetDescription("Сигаретка") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("РП Предметы") + +IGS("Гитара ", "guitar"):SetWeapon("guitar") +:SetPrice(250) +:SetTerm(30) +:SetDescription("Гитара, для атмосферы") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("РП Предметы") + +IGS("Наручники", "weapon_cuff_elastic"):SetWeapon("weapon_cuff_elastic") +:SetPrice(300) +:SetTerm(30) +:SetDescription("Наручники не знаю зачем они тебе, но юзай ток по назначению") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("РП Предметы") + +IGS("Аптечка", "fas2_ifak"):SetWeapon("fas2_ifak") +:SetPrice(500) +:SetTerm(30) +:SetDescription("Наручники не знаю зачем они тебе, но юзай ток по назначению") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("РП Предметы") + +IGS("Камера", "cam"):SetWeapon("gmod_camera") +:SetPrice(50) +:SetPerma(0) +:SetDescription("Камера для создания красивых скриншотов") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("РП Предметы") + +IGS("Боди-камера", "bodycam_tablet"):SetWeapon("bodycam_tablet") +:SetPrice(500) +:SetPerma(30) +:SetDescription("Камера за слежкой за бойцами") +:SetIcon("https://i.yapx.ru/dMq2U.png", false) +:SetCategory("РП Предметы") +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +IGS("Premium на месяц", "vip_na_mesyac3"):SetSAMGroup("prem") +:SetPrice(450) +:SetTerm(30) +:SetCategory("Группы на месяц") +:SetDescription("Вся информация в нашем ДС") +:SetIcon("https://i.yapx.ru/dMq2U.png") + +IGS("Sponsor на месяц", "sponsor_na_mesyac5"):SetSAMGroup("sponsor") +:SetPrice(1300) +:SetTerm(30) +:SetCategory("Группы на месяц") +:SetDescription("С этой покупкой вы получите так же как и спонсор но в добавок к этому Участвовать в ЗБТ и приватных голосованиях, игнорирование жалоб, особую привелегию на сервере и в дискорде. При оплате напрямую в дискорде будет скидка 20%") +:SetIcon("https://i.yapx.ru/dMq2U.png") + +IGS("Перевод в любое подразделение", "perevod") +:SetPrice(1) +:SetPerma(0) +:SetCategory("Профессии") +:SetDescription("ДЛЯ ПОКУПКИ ОБРАТИТЕСЬ В ДОНАТ ТИКЕТ В НАШЕМ ДС !!!") +:SetIcon("https://i.yapx.ru/dMq2U.png") + +IGS("Личная профессия", "lichaprof") +:SetPrice(1) +:SetPerma(0) +:SetCategory("Профессии") +:SetDescription("Перед покупкой откройте тикет в ДС !!!! Личная профессия предназначена только для одного человека. При покупке вам предоставляется выбор между двумя профессиями: пилот или танкист (на стороне конфликта, за которую вы играете). Никто не продаст вам танки, вертолеты и прочее для вашей личной профессии.") +:SetIcon("https://i.yapx.ru/dMq2U.png") + +IGS("Говорилка", "govorilka") +:SetPrice(350) +:SetTerm(30) + +--[[ +IGS("Отмычка", "otmichka") -- второй параметр не должен(!) повторяться с другими предметами + :SetPrice(1) -- 1 рубль + + -- 0 - одноразовое (Т.е. купил, выполнилось OnActivate и забыл. Полезно для валюты) + -- 30 - месяц, 7 - неделя и т.д. :SetPerma() - навсегда + :SetTerm(30) + + :SetDarkRPItem("lockpick") -- реальный класс энтити + :SetDescription("Разрешает вам покупать отмычку") -- описание + :SetCategory("Оружие") -- категория + + -- квадратная ИКОНКА (Не обязательно). Отобразится на главной странице. Может быть с прозрачностью + :SetIcon("http://i.imgur.com/4zfVs9s.png") + + -- БАННЕР 1000х400 (Не обязательно). Отобразится в подробностях итема + :SetImage("http://i.imgur.com/RqsP5nP.png") +--]] + +--[[------------------------------------------------------------------------- + Доступ к энтити, оружию и машинам через спавнменю +---------------------------------------------------------------------------]] + +--[[IGS("Cheytac m200", "tfa_ins2_warface_cheytac_m200"):SetWeapon("tfa_ins2_warface_cheytac_m200") +:SetPrice(1500) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("aks tactical", "tfa_ins2_aks_r"):SetWeapon("tfa_ins2_aks_r") +:SetPrice(1200) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("ksvk 12.7", "tfa_blast_ksvk_cqb"):SetWeapon("tfa_blast_ksvk_cqb") +:SetPrice(1500) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("ak12", "tfa_inss_wpn_ak12"):SetWeapon("tfa_inss_wpn_ak12") +:SetPrice(1100) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("mk18 mod", "tfa_new_inss_mk18"):SetWeapon("tfa_new_inss_mk18") +:SetPrice(1500) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("m16a4", "tfa_inss_wpn_m16a4"):SetWeapon("tfa_inss_wpn_m16a4") +:SetPrice(1000) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("mugpul", "tfa_ins2_moe_akm"):SetWeapon("tfa_ins2_moe_akm") +:SetPrice(1000) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("FN FAL", "tfa_inss_wpn_fn_fal"):SetWeapon("tfa_inss_wpn_fn_fal") +:SetPrice(1000) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +IGS("l1a1", "tfa_inss_wpn_l1a1"):SetWeapon("tfa_inss_wpn_l1a1") +:SetPrice(1000) +:SetTerm(30) +:SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +:SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +:SetCategory("Оружие на месяц") + +--------------------------------------------------------------------------------------------------------------------------- + +-- IGS("Cheytac m200", "tfa_mw_om50_2"):SetWeapon("tfa_ins2_warface_cheytac_m200") +-- :SetPrice(4500) +-- :SetTerm(181) +-- :SetHidden() +-- :SetDescription("Данное оружие является трофейным оружием, также не забудьте прочесть правила донат воружения") +-- :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) +-- :SetCategory("Оружие на полгода") + +--------------------------------------------------------------------------------------------------------------------------- + IGS("Починка транспорта", "repcar"):SetWeapon("weapon_lvsrepair") + :SetPrice(250) + :SetTerm(30) + :SetDescription("Починка транспорта") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы") + + IGS("Сигаретка", "sigareta"):SetWeapon("weapon_cigarette_camel") + :SetPrice(50) + :SetTerm(30) + :SetDescription("Сигаретка") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы") + + IGS("Гитара ", "guitar"):SetWeapon("guitar") + :SetPrice(100) + :SetTerm(30) + :SetDescription("Гитара, для атмосферы") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы") + + IGS("Наручники", "cuff"):SetWeapon("weapon_cuff_elastic") + :SetPrice(350) + :SetTerm(30) + :SetDescription("Наручники не знаю зачем они тебе, но юзай ток по назначению") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы") + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + IGS("Починка транспорта", "repcar22"):SetWeapon("weapon_lvsrepair") + :SetPrice(500) + :SetPerma() + :SetDescription("Починка транспорта") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы навсегда") + + IGS("Сигаретка", "sigareta22"):SetWeapon("weapon_cigarette_camel") + :SetPrice(300) + :SetPerma() + :SetDescription("Сигаретка") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы навсегда") + + IGS("Гитара", "gitara2"):SetWeapon("guitar") + :SetPrice(400) + :SetPerma() + :SetDescription("Гитара, для атмосферы") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы навсегда") + + IGS("Наручники", "cuff2"):SetWeapon("weapon_cuff_elastic") + :SetPrice(550) + :SetPerma() + :SetDescription("Наручники не знаю зачем они тебе, но юзай ток по назначению") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg", false) + :SetCategory("РП Предметы навсегда") + +IGS("Админ на месяц", "premium_navsegda"):SetSAMGroup("dadmin") + :SetPrice(1000) + :SetTerm(30) + :SetCategory("Группы на месяц") + :SetDescription("С этой покупкой вы можете попробовать себя в роли админа.") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Админ на три месяца", "premium_navsegda_3"):SetSAMGroup("dadmin") + :SetPrice(1799) + :SetTerm(90) + :SetCategory("Группы на три месяца") + :SetDescription("С этой покупкой вы можете попробовать себя в роли админа.") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Админ на полгода", "premium_navsegda_6"):SetSAMGroup("dadmin") + :SetPrice(2600) + :SetTerm(181) + :SetCategory("Группы на полгода") + :SetDescription("С этой покупкой вы можете попробовать себя в роли админа.") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Админ навсегда", "premium_navsegda2"):SetSAMGroup("dadmin") + :SetPrice(2000) + :SetPerma() + :SetHidden() + :SetCategory("Группы навсегда") + :SetDescription("С этой покупкой вы можете попробовать себя в роли админа.") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Premium на месяц", "vip_na_mesyac3"):SetSAMGroup("prem") + :SetPrice(500) + :SetTerm(30) + :SetCategory("Группы на месяц") + :SetDescription("Вся информация в нашем ДС") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Premium на три месяца", "vip_na_mesyac_51"):SetSAMGroup("prem") + :SetPrice(850) + :SetTerm(90) + :SetCategory("Группы на три месяца") + :SetDescription("Вся информация в нашем ДС") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Premium на полгода", "vip_na_mesyac_61"):SetSAMGroup("prem") + :SetPrice(1650) + :SetTerm(181) + :SetCategory("Группы на полгода") + :SetDescription("Вся информация в нашем ДС") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Premium навсегда", "vip_na_mesyac4"):SetSAMGroup("prem") + :SetPrice(2100) + :SetPerma() + :SetHidden() + :SetCategory("Группы навсегда") + :SetDescription("Вся информация в нашем ДС") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("IL TRO", "mozartgroup") + :SetPrice(500) + :SetPerma() + :SetHidden() + :SetCategory("Готовые Профессии") + :SetDescription("После покупки, обратите внимание на то что вам нужно запросить роль в дс подразделений. И просмотреть информацию о вашем подразделение после чего вы сможете получить професию") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("ДШРГ Русич", "wagner") + :SetPrice(500) + :SetPerma() + :SetHidden() + :SetCategory("Готовые Профессии") + :SetDescription("После покупки, обратите внимание на то что вам нужно запросить роль в дс подразделений. И просмотреть информацию о вашем подразделение после чего вы сможете получить професию") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Перевод в любое подразделение", "perevod") + :SetPrice(1) + :SetPerma() + :SetCategory("Профессии") + :SetDescription("ДЛЯ ПОКУПКИ ОБРАТИТЕСЬ В ДОНАТ ТИКЕТ В НАШЕМ ДС !!!") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Личная профессия", "lichaprof") + :SetPrice(1000) + :SetPerma() + :SetCategory("Профессии") + :SetDescription("Перед покупкой откройте тикет в ДС !!!! Личная профессия предназначена только для одного человека. При покупке вам предоставляется выбор между двумя профессиями: пилот или танкист (на стороне конфликта, за которую вы играете). Никто не продаст вам танки, вертолеты и прочее для вашей личной профессии.") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Sponsor на месяц", "sponsor_na_mesyac5"):SetSAMGroup("sponsor") + :SetPrice(2000) + :SetPerma() + :SetCategory("Группы на месяц") + :SetDescription("С этой покупкой вы получите так же как и спонсор но в добавок к этому Участвовать в ЗБТ и приватных голосованиях, игнорирование жалоб, особую привелегию на сервере и в дискорде") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg") + + IGS("Sponsor навсегда", "sponsor_navsegda6"):SetSAMGroup("sponsor") + :SetPrice(5000) + :SetPerma() + :SetCategory("Группы навсегда") + :SetDescription("С этой покупкой вы получите так же как и спонсор но в добавок к этому Участвовать в ЗБТ и приватных голосованиях, игнорирование жалоб, особую привелегию на сервере и в дискорде") + :SetIcon("https://i.postimg.cc/CxHrpmR1/ftava.jpg")]] + + for _,ITEM in pairs(IGS.GetItems()) do + ITEM:SetDiscountedFrom(ITEM.price) -- бирочка о скидке + ITEM:SetPrice(ITEM.price * 0.9) -- скидка 0% + end diff --git a/garrysmod/addons/igsmodification/lua/igs/settings/sh_addlevels.lua b/garrysmod/addons/igsmodification/lua/igs/settings/sh_addlevels.lua new file mode 100644 index 0000000..98ca6b6 --- /dev/null +++ b/garrysmod/addons/igsmodification/lua/igs/settings/sh_addlevels.lua @@ -0,0 +1,65 @@ +--[[------------------------------------------------------------------------- + Цены в .Add указываются в рублях +---------------------------------------------------------------------------]] + +-- Уровни сработают только, если не произойдет ошибки при пополнении счета +-- Правда я не представляю что нужно сделать, чтобы произошла ошибка (Не пришел сигнал PAY с автодоната) +IGS.LVL.Add(1, "Новичок") + :SetBonus(function(pl) + local bonus = pl:IGSFunds() * .1 + pl:AddIGSFunds(bonus,"Бонус за первое пополнение") + IGS.Notify(pl,"Вы получили " .. PL_MONEY(bonus) .. "\nв качестве бонуса за первое пополнение счета") + end) + :SetDescription("При первом пополнении счета получите 10% в подарок автоматически и бесплатно") -- выше в catchDSHints еще + + +IGS.LVL.Add(100, "Стартанувший") +IGS.LVL.Add(500, "В теме") + +IGS.LVL.Add(1000, "Бывалый") + :SetDescription("Позволяет получить уникальный статус \"Мегалодон\" на форуме") + +IGS.LVL.Add(1500, "Вроде не бомж") + :SetDescription("Скоро новый бонус") + + +IGS.LVL.Add(2000, "Точно не бомж") + :SetDescription("Еще капельку и бонус. Следующий лвл") + + +IGS.LVL.Add(2500, "При деньгах") + :SetDescription("Бонус 20% на пополнение счета") + :SetBonus(function(pl) + local bonus = pl:IGSFunds() * .2 -- на самом деле бонус начислит на всю имеющуюся сумму, а не сумму пополнения. Так что ахтунг + pl:AddIGSFunds(bonus,"Бонус за 2500 руб транзакций") + IGS.Notify(pl,"Вы получили " .. PL_MONEY(bonus) .. "\nв качестве бонуса за новый бизнес ЛВЛ") + end) + + +IGS.LVL.Add(3000, "Щедрый") + :SetDescription("Премиум поддержка от правительства") + +IGS.LVL.Add(4000, "Очень щедрый") + :SetDescription("На след. лвл новый бонус") + +IGS.LVL.Add(5000, "Пиздец щедрый") + :SetDescription("Статус Убердон на форуме") + +IGS.LVL.Add(6000, "Мажор") + :SetDescription("Предложение о сотрудничестве") + + +IGS.LVL.Add(7000, "Супермажик") +IGS.LVL.Add(8000, "Гипермажик") +IGS.LVL.Add(9000, "Убермажор") +IGS.LVL.Add(10000, "Миллионер") + :SetDescription("Премиум поддержка от основателя в любое время суток") + +IGS.LVL.Add(12000, "МультиМиллионер") +IGS.LVL.Add(15000, "Миллиардер") +IGS.LVL.Add(20000, "МультиМиллиардер") +IGS.LVL.Add(25000, "Кыш с дороги") +IGS.LVL.Add(30000, "Сядь в лужу, я пройду") +IGS.LVL.Add(35000, "Я тебя куплю") +IGS.LVL.Add(40000, "Я куплю тебя и твою семью") +IGS.LVL.Add(50000, "Бог один и это Я") diff --git a/garrysmod/addons/inertia_and_movement_speed_changer_3578148499/addon.json b/garrysmod/addons/inertia_and_movement_speed_changer_3578148499/addon.json new file mode 100644 index 0000000..ac630e8 --- /dev/null +++ b/garrysmod/addons/inertia_and_movement_speed_changer_3578148499/addon.json @@ -0,0 +1,8 @@ +{ + "title": "Inertia and Movement Speed Changer", + "type": "tool", + "tags": [ + "realism" + ], + "ignore": [] +} \ No newline at end of file diff --git a/garrysmod/addons/inertia_and_movement_speed_changer_3578148499/lua/autorun/sh_inertia_changer_init.lua b/garrysmod/addons/inertia_and_movement_speed_changer_3578148499/lua/autorun/sh_inertia_changer_init.lua new file mode 100644 index 0000000..38eca4a --- /dev/null +++ b/garrysmod/addons/inertia_and_movement_speed_changer_3578148499/lua/autorun/sh_inertia_changer_init.lua @@ -0,0 +1,291 @@ +-- Привилегии, на которые НЕ действует инерция (добавьте свои группы) +local EXCLUDED_USERGROUPS = { + ["superadmin"] = true, + ["owner"] = true, + -- Добавьте другие группы по необходимости +} + +-- Функция проверки привилегий и режима администратора +local function IsPlayerExcluded(ply) + if not IsValid(ply) then return false end + + -- Проверка на режим noclip (обычно используется в /adm) + if ply:GetMoveType() == MOVETYPE_NOCLIP then return true end + + -- Проверка различных NWBool переменных для админ-режима + if ply:GetNWBool("AdminMode") or ply:GetNWBool("admin_mode") or ply:GetNWBool("isAdminMode") then return true end + if ply:GetNWBool("InAdminMode") or ply:GetNWBool("in_admin_mode") then return true end + + -- Проверка на god mode (часто идет с админ-режимом) + if ply:HasGodMode() then return true end + + -- Проверка на привилегии + return EXCLUDED_USERGROUPS[ply:GetUserGroup()] or false +end + +local changeMoveSpeedCVar = CreateConVar( "IC_move_speeds_changespeed", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Whether or not the mod should change movement speed", 0, 1 ) +local walkSpeedCVar = CreateConVar( "IC_move_speeds_walkspeed", 190, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 ) +local sprintSpeedCVar = CreateConVar( "IC_move_speeds_sprintspeed", 320, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 ) +local slowWalkSpeedCVar = CreateConVar( "IC_move_speeds_slowwalkspeed", 100, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 ) +local crouchedSpeedCVar = CreateConVar( "IC_move_speeds_crouchedspeed", 72, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 ) +local ladderSpeedCVar = CreateConVar( "IC_move_speeds_ladderspeed", 70, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 1 ) +local duckSpeedCVar = CreateConVar( "IC_move_speeds_duckspeed", 0.4, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "", 0.01, 1 ) +local trueFrictionCVar = CreateConVar( "IC_move_basic_friction", 40, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Amount of true, speed-independent friction to apply", 0, 1000 ) +local jumpHeightCVar = CreateConVar( "IC_move_speeds_jump_height", 33, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Desired max jump height", 1, 1000 ) +local inertiaCVar = CreateConVar( "IC_move_basic_inertia", 2, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "The divisor for the amount of acceleration and friction. 2 means double the inertia of default gmod", 0.1, 10 ) +local landingDragCVar = CreateConVar( "IC_move_limits_landing_drag", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "The amount of extra drag to apply when landing on the ground", 0, 10 ) +local airStrafeLimitsCVar = CreateConVar( "IC_move_limits_air_strafing", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Limit air strafing", 0, 1 ) +local noJumpBoostCVar = CreateConVar( "IC_move_limits_jump_boost", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Disables speed boost when jumping", 0, 1 ) +local jumpDelayCVar = CreateConVar( "IC_move_limits_jump_cooldown", 0, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Adds a delay before you can jump again", 0, 1 ) +local sprintRestrictionsCVar = CreateConVar( "IC_move_limits_sprint_restrictions", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE }, "Disallows sprinting to the side or backwards", 0, 1 ) + +local function SetPlyMoveSpeeds(ply, mv) + + ply:SetWalkSpeed( walkSpeedCVar:GetFloat() ) + ply:SetRunSpeed( sprintSpeedCVar:GetFloat() ) + ply:SetSlowWalkSpeed( slowWalkSpeedCVar:GetFloat() ) + ply:SetCrouchedWalkSpeed( crouchedSpeedCVar:GetFloat() / walkSpeedCVar:GetFloat() ) + ply:SetLadderClimbSpeed( ladderSpeedCVar:GetFloat() ) + ply:SetDuckSpeed( duckSpeedCVar:GetFloat() ) + ply:SetUnDuckSpeed( duckSpeedCVar:GetFloat() ) + ply:SetJumpPower( math.sqrt( 2 * GetConVar( "sv_gravity" ):GetFloat() * jumpHeightCVar:GetFloat() ) ) + +end + +local function CanSprint(ply, cmd) + + if + ( cmd:KeyDown( IN_BACK ) ) + or + ( ( cmd:KeyDown( IN_MOVERIGHT ) or cmd:KeyDown( IN_MOVELEFT ) ) and !cmd:KeyDown( IN_FORWARD ) ) + or + ( CurTime() - ply:GetNWFloat( "LastLandingTime" ) < ply:GetNWFloat( "LandingStunDrag" ) ) + or + ply:WaterLevel() >= 1 + then + + RemoveKeys( cmd, IN_SPEED ) + + end + +end + +local function GetLandingDrag( velocity, ply, dt ) + + local landingDragStrength = landingDragCVar:GetFloat() + local landingDragTime = ply:GetNWFloat( "LandingStunDrag" ) + local landDrag = 1 + + if CurTime() - ply:GetNWFloat( "LastLandingTime" ) > landingDragTime then + + if SERVER then + + ply:SetNWFloat( "LandingStunDrag", 0 ) + + end + + else + + landDrag = 1 + dt * landingDragStrength + + end + + return landDrag + +end + +local function GetTrueFriction(velocity, coef, dt, moveDir) + + local speed = velocity:Length() + local normal = -velocity:GetNormalized() + local baseFriction = trueFrictionCVar:GetFloat() * coef + + if !moveDir:IsZero() then + + baseFriction = baseFriction * math.max( 0, normal:Dot( moveDir ) ) + + else + + baseFriction = baseFriction * 2.5 + + end + + local newSpd = math.max( 0, speed - baseFriction * dt ) + + return newSpd / math.max( 1, speed ) + +end + +--Code from the wiki, modified for mv and cmd +function RemoveKeys( mvorcmd, keys ) + + -- Using bitwise operations to clear the key bits. + local newbuttons = bit.band( mvorcmd:GetButtons(), bit.bnot( keys ) ) + mvorcmd:SetButtons( newbuttons ) + +end +--end + +hook.Add("StartCommand", "ICStartCMD", function( ply, cmd ) + + if IsPlayerExcluded(ply) then return end + + if sprintRestrictionsCVar:GetBool() and ply:GetMoveType() ~= MOVETYPE_NOCLIP then + + CanSprint( ply, cmd ) + + end + +end) + +hook.Add("SetupMove", "ICSetupMove", function( ply, mv, cmd ) + + if IsPlayerExcluded(ply) then return end + + if ply:GetMoveType() == MOVETYPE_WALK then + + local timeSinceLanding = CurTime() - ply:GetNWFloat( "LastLandingTime" ) + local nextPossibleJumpTime = ply:GetNWFloat( "LandingStunDrag" ) + + if ( jumpDelayCVar:GetBool() and mv:KeyDown( IN_JUMP ) and timeSinceLanding < nextPossibleJumpTime and ply:GetMoveType() ~= MOVETYPE_LADDER ) then + + RemoveKeys( mv, IN_JUMP ) + + end + + end + + if noJumpBoostCVar:GetBool() and mv:KeyDown( IN_JUMP ) and bit.band( mv:GetOldButtons(), IN_JUMP ) ~= IN_JUMP then + + mv:SetMaxClientSpeed( mv:GetVelocity():Length() / 1.5 ) + + end + +end) + +hook.Add("Move", "ICMoveHook", function(ply, mv) + + if IsPlayerExcluded(ply) then return end + + if ply:OnGround() or ply:WaterLevel() >= 2 then + + local dt = FrameTime() + + if changeMoveSpeedCVar:GetBool() and math.abs(CurTime() - ply:GetNW2Float( "LastSpeedUpdate" )) > 0.3 then + + SetPlyMoveSpeeds( ply, mv ) + ply:SetNW2Float( "LastSpeedUpdate", CurTime() ) + + end + + local waterLevel = ply:WaterLevel() + local inwater = waterLevel >= 2 + + + if !inwater then + + local vel = mv:GetVelocity() + + local moveDir = ( mv:GetAngles():Forward() * mv:GetForwardSpeed() + mv:GetAngles():Right() * mv:GetSideSpeed() ) + moveDir.z = 0 + moveDir:Normalize() + + local frictionCoef = ply:GetFriction() + vel = vel * GetTrueFriction( vel, frictionCoef, dt, moveDir ) / GetLandingDrag( vel, ply, dt ) + + mv:SetVelocity( vel ) + + end + + end + +end) + +hook.Add("OnPlayerHitGround", "ICHitGround", function(ply, inwater, onfloater, speed) + + if IsPlayerExcluded(ply) then return end + + ply:SetNWFloat( "LastLandingTime", CurTime() ) + local stunTime = math.Clamp( math.Remap( speed, 200, 650, 0.3, 1 ), 0.3, 1 ) + + if stunTime > ply:GetNWFloat( "LandingStunDrag" ) then + + ply:SetNWFloat( "LandingStunDrag", stunTime ) + + end + +end) + +local function ConsoleCommands() + if airStrafeLimitsCVar:GetBool() then + + RunConsoleCommand( "sv_airaccelerate", 0.2 ) + + end + + RunConsoleCommand( "sv_accelerate", 8 / inertiaCVar:GetFloat() ) + RunConsoleCommand( "sv_friction", 8 / inertiaCVar:GetFloat() ) + RunConsoleCommand( "sv_stopspeed", 0 ) + +end + +ConsoleCommands() + +cvars.AddChangeCallback( "IC_move_basic_inertia", function() + + RunConsoleCommand( "sv_accelerate", 8 / inertiaCVar:GetFloat() ) + RunConsoleCommand( "sv_friction", 8 / inertiaCVar:GetFloat() ) + +end ) + +cvars.AddChangeCallback( "IC_move_limits_air_strafing", function() + + RunConsoleCommand( "sv_airaccelerate", ( airStrafeLimitsCVar:GetBool() and 0.2 ) or ( 8 / inertiaCVar:GetFloat() ) ) + +end ) + +if CLIENT then + + hook.Add("PopulateToolMenu", "IC_PopulateToolMenu", function() + spawnmenu.AddToolMenuOption( "Utilities", "Admin", "IC_OptionsMenu", "Inertia Controller", "", "", function( panel ) + + panel:Help( "Basic" ) + + panel:NumSlider( "Inertia Scale", "IC_move_basic_inertia", 0.1, 5, 3 ) + + panel:NumSlider( "Friction", "IC_move_basic_friction", 0, 250, 3 ) + + panel:Help( "Speed" ) + + panel:CheckBox( "Change movement speed", "IC_move_speeds_changespeed" ) + + panel:NumSlider( "Walk speed", "IC_move_speeds_walkspeed", 1, 400, 0) + + panel:NumSlider( "Sprint speed", "IC_move_speeds_sprintspeed", 1, 400, 0) + + panel:NumSlider( "Slow walk speed", "IC_move_speeds_slowwalkspeed", 1, 400, 0) + + panel:NumSlider( "Crouch walk speed", "IC_move_speeds_crouchedspeed", 1, 400, 0) + + panel:NumSlider( "Ladder climb speed", "IC_move_speeds_ladderspeed", 1, 400, 0) + + panel:NumSlider( "Crouch transition speed", "IC_move_speeds_duckspeed", 0, 1, 3) + + panel:NumSlider( "Jump height", "IC_move_speeds_jump_height", 0, 100, 0) + + panel:Help( "Limits" ) + + panel:CheckBox( "Limit air strafing", "IC_move_limits_air_strafing" ) + + panel:CheckBox( "Jump cooldown", "IC_move_limits_jump_cooldown" ) + + panel:CheckBox( "Sprint restrictions", "IC_move_limits_sprint_restrictions" ) + + panel:CheckBox( "No jump boost", "IC_move_limits_jump_boost" ) + + panel:NumSlider( "Extra landing drag", "IC_move_limits_landing_drag", 0, 15, 3) + + end) + end) + +end diff --git a/garrysmod/addons/koska/addon.json b/garrysmod/addons/koska/addon.json new file mode 100644 index 0000000..9034789 --- /dev/null +++ b/garrysmod/addons/koska/addon.json @@ -0,0 +1,9 @@ +{ + "title": "Grappling Knife", + "type": "weapon", + "tags": [ + "realism", + "fun" + ], + "ignore": [] +} \ No newline at end of file diff --git a/garrysmod/addons/koska/lua/autorun/client/cl_rope_knives.lua b/garrysmod/addons/koska/lua/autorun/client/cl_rope_knives.lua new file mode 100644 index 0000000..fe5e45c --- /dev/null +++ b/garrysmod/addons/koska/lua/autorun/client/cl_rope_knives.lua @@ -0,0 +1,143 @@ +local matBeam = Material("cable/rope") + +function DrawRopeKnives() + for k,ent in pairs(ents.FindByClass("sent_rope_knife")) do + if LocalPlayer():GetPos():Distance( ent:GetPos() ) > 16000 then continue end + if !ent:GetNWBool("Stuck") and !ent:GetNWBool("Useless") and IsValid(ent:GetNWEntity("Owner")) then + + local att = ent:GetNWEntity("Owner"):GetAttachment(ent:GetNWEntity("Owner"):LookupAttachment("anim_attachment_RH")) + if !att then continue end + local pos = att.Pos + + render.SetMaterial(matBeam) + render.DrawBeam(ent:GetPos(), pos, 2, 0, 1, Color(255,255,255)) + + elseif ent:GetNWBool("Stuck") then + + local pos = ent:GetPos() + + local line = {} + line.start = pos + line.endpos = pos + Vector(0,0,-16000) + line.filter = {ent} + for k,ply in pairs(player.GetAll()) do + table.insert(line.filter, ply) + end + + local tr = util.TraceLine( line ) + + render.SetMaterial(matBeam) + render.DrawBeam(pos, tr.HitPos, 2, 0, 1, Color(255,255,255)) + + end + end +end +hook.Add("PostDrawOpaqueRenderables","DrawRopeKnives",DrawRopeKnives) + +hook.Add( "ShouldDrawLocalPlayer", "DrawClimbPlayer", function() + if IsValid(LocalPlayer():GetNWEntity("ClimbingEnt")) and LocalPlayer():GetMoveType() == MOVETYPE_CUSTOM and GetConVarString( "gk_thirdperson" ) == "1" and GetConVarString( "gk_forcefirstperson" ) == "0" then return true end +end ) + +function FollowClimbHead( ply, pos, angle, fov ) + if ( !ply:IsValid() or !ply:Alive() or ply:GetViewEntity() != ply ) then return end + if ply:InVehicle() or !IsValid(ply:GetNWEntity("ClimbingEnt")) or ply:GetMoveType() ~= MOVETYPE_CUSTOM or GetConVarString( "gk_thirdperson" ) == "0" or GetConVarString( "gk_forcefirstperson" ) == "1" then return end + + pos = pos - angle:Forward()*64 + + local view = {} + view.origin = pos + view.angles = angle + view.fov = fov + + return view +end +hook.Add( "CalcView", "RopeKnifeFollowHead", FollowClimbHead ) + +function RopeKnifeAnimation( ply, velocity, maxseqgroundspeed ) + if IsValid(ply:GetNWEntity("ClimbingEnt")) and ply:GetMoveType() == MOVETYPE_CUSTOM then + local speed = ply:GetNWInt("MoveSpeed") + + if ply:GetNWEntity("ClimbingEnt"):GetNWBool("MultiAngle") then + ply:SetRenderAngles( ply:GetNWVector("ClimbNormal"):Angle() ) + else + ply:SetRenderAngles( ply:GetNWEntity("ClimbingEnt"):GetNWVector("HitNormal"):Angle() ) + end + ply.CalcSeqOverride = ply:LookupSequence( "zombie_climb_loop" ) + ply:SetSequence( ply.CalcSeqOverride ) + ply:SetPlaybackRate( speed/180 ) + return true + end +end +hook.Add("UpdateAnimation", "RopeKnifeAnimation", RopeKnifeAnimation) + +surface.CreateFont("RopeKnifeHUD", { + font = "Roboto", + size = 20, + weight = 800, + antialias = true +}) + +local function HUDTimer() + if not IsValid(LocalPlayer()) then return end + + local foundHook = nil + local climbingEnt = LocalPlayer():GetNWEntity("ClimbingEnt") + if IsValid(climbingEnt) then + foundHook = climbingEnt + else + for k, v in pairs(ents.FindByClass("sent_rope_knife")) do + if v:GetNWEntity("Owner") == LocalPlayer() and v:GetNWBool("Stuck") then + foundHook = v + break + end + end + end + + if IsValid(foundHook) then + local time = foundHook:GetNWFloat("ExpirationTime", 0) + local timeLeft = math.ceil(time - CurTime()) + if time > 0 and timeLeft > 0 then + local w, h = ScrW(), ScrH() + local boxW, boxH = 300, 50 + local x, y = w/2 - boxW/2, h - 140 + + local themeColor = Color(0, 120, 50, 230) -- Brighter Tactical Green + local accentGreen = Color(100, 255, 150, 255) -- Very Bright Green + local textColor = Color(200, 255, 200, 255) -- Light Green for text + + -- Tactical Border + surface.SetDrawColor(themeColor) + surface.DrawOutlinedRect(x, y, boxW, boxH, 2) + + -- Background Alpha (Slightly more transparent) + draw.RoundedBox(0, x + 2, y + 2, boxW - 4, boxH - 4, Color(0, 0, 0, 200)) + + -- Grid Lines (Visual detail) + surface.SetDrawColor(themeColor.r, themeColor.g, themeColor.b, 40) + for i = 0, boxW, 25 do surface.DrawLine(x + i, y, x + i, y + boxH) end + + -- Status Indicator (Simplified and centered) + local text = "Исчезнет через: " .. timeLeft .. " сек." + draw.SimpleText(text, "RopeKnifeHUD", w/2, y + 10, accentGreen, TEXT_ALIGN_CENTER) + + -- Segmented Progress Bar + local segments = 10 + local segW = (boxW - 30) / segments + local activeSegs = math.ceil((timeLeft / 60) * segments) + + for i = 0, segments - 1 do + local segX = x + 15 + i * segW + local col = (i < activeSegs) and themeColor or Color(40, 40, 40, 200) + draw.RoundedBox(0, segX + 1, y + 32, segW - 2, 6, col) + end + + -- Corner Accents + surface.SetDrawColor(255, 255, 255, 50) + surface.DrawRect(x, y, 4, 1) surface.DrawRect(x, y, 1, 4) + surface.DrawRect(x + boxW - 4, y, 4, 1) surface.DrawRect(x + boxW - 1, y, 1, 4) + surface.DrawRect(x, y + boxH - 1, 4, 1) surface.DrawRect(x, y + boxH - 4, 1, 4) + surface.DrawRect(x + boxW - 4, y + boxH - 1, 4, 1) surface.DrawRect(x + boxW - 1, y + boxH - 4, 1, 4) + end + end +end +hook.Add("HUDPaint", "RopeKnifeTimerHUD", HUDTimer) diff --git a/garrysmod/addons/koska/lua/autorun/sh_rope_knives.lua b/garrysmod/addons/koska/lua/autorun/sh_rope_knives.lua new file mode 100644 index 0000000..eda26ed --- /dev/null +++ b/garrysmod/addons/koska/lua/autorun/sh_rope_knives.lua @@ -0,0 +1,131 @@ +hook.Add("Move","RopeKnifeMove", function(ply,mv,cmd) + local ent = ply:GetNWEntity("ClimbingEnt") + if IsValid( ent ) and ply:GetMoveType() == MOVETYPE_CUSTOM then + + local deltaTime = FrameTime() + local pos = mv:GetOrigin() + local targetpos = ply:GetNWEntity("ClimbingEnt"):GetPos() + local maxspeed = ply:GetMaxSpeed()*0.6 + local forward = mv:GetForwardSpeed()/10000 + local vel = mv:GetVelocity() + + ply:SetNWInt("MoveSpeed", forward*maxspeed) + + local newVelocity = (Vector(0,0,1)*forward*maxspeed) + local newOrigin = pos + newVelocity * deltaTime + + if mv:KeyDown(IN_JUMP) and SERVER then + ply:SetMoveType( MOVETYPE_WALK ) + ply:SetGroundEntity( NULL ) + ply:SetNWEntity("ClimbingEnt", NULL) + ply:DrawViewModel(true) + ply:DrawWorldModel(true) + + -- Give a small boost + mv:SetVelocity( ply:GetForward() * -50 + Vector(0,0,200) ) + + -- Multi-use: Hook remains until its global 60s timer finishes + return true + end + + if pos.z >= targetpos.z + 50 and SERVER then + ply:SetMoveType( MOVETYPE_WALK ) + ply:SetGroundEntity( NULL ) + ply:SetNWEntity("ClimbingEnt", NULL) + ply:DrawViewModel(true) + ply:DrawWorldModel(true) + + -- Multi-use: Hook remains until its global 60s timer finishes + end + if pos.z <= ent:GetNWVector("DownHit").z + 5 and ply:KeyDown(IN_BACK) and SERVER then + newOrigin = newOrigin - ply:GetNWEntity("ClimbingEnt"):GetNWVector("HitNormal"):Angle():Forward()*18 + ply:SetMoveType( MOVETYPE_WALK ) + ply:SetGroundEntity( NULL ) + ply:SetNWEntity("ClimbingEnt", NULL) + ply:DrawViewModel(true) + ply:DrawWorldModel(true) + + -- Multi-use: Hook remains until its global 60s timer finishes + end + + mv:SetVelocity( newVelocity ) + + mv:SetOrigin( newOrigin ) + + return true; + end +end) + +function RopeKnifeThink( ply ) + if IsValid(ply:GetNWEntity("ClimbingEnt")) then + if ply:GetMoveType() == MOVETYPE_CUSTOM then + local wep = ply:GetActiveWeapon() + if IsValid(wep) and GetConVar( "gk_enableshooting" ):GetBool() == false then + wep:SetNextPrimaryFire(CurTime() + 0.5) + ply:DrawViewModel(false) + if SERVER then + ply:DrawWorldModel(false) + end + end + elseif SERVER then + ply:SetNWEntity("ClimbingEnt", NULL) + ply:DrawViewModel(true) + ply:DrawWorldModel(true) + end + end +end +hook.Add("PlayerPostThink", "RopeKnifeThink", RopeKnifeThink) + +function PlayerDieClimb( victim, weapon, killer ) + -- Multi-use: Hook remains until its global 60s timer finishes + victim:SetNWEntity("ClimbingEnt", NULL) +end +hook.Add( "PlayerDeath", "PlayerClimbDeath", PlayerDieClimb ) + +CreateConVar("gk_enableshooting", "0", {FCVAR_ARCHIVE}) +CreateConVar("gk_forcefirstperson", "0", {FCVAR_ARCHIVE}) +CreateConVar("gk_enabledamage", "1", {FCVAR_ARCHIVE}) + +if CLIENT then + if GetConVar("gk_thirdperson") == nil then + CreateClientConVar("gk_thirdperson", "1", true, true) + end + if GetConVar("gk_ropemat") == nil then + CreateClientConVar("gk_ropemat", "", true, true) + end +end + + +local function GKSettingsPanel(panel) + panel:ClearControls() + + panel:AddControl("CheckBox", { + Label = "Thirdperson", + Command = "gk_thirdperson" + }) + +end + +local function GKAdminSettingsPanel(panel) + panel:ClearControls() + + panel:AddControl("CheckBox", { + Label = "Shoot while climbing", + Command = "gk_enableshooting" + }) + panel:AddControl("CheckBox", { + Label = "Enable grapple damage", + Command = "gk_enabledamage" + }) + panel:AddControl("CheckBox", { + Label = "Force First-person", + Command = "gk_forcefirstperson" + }) + +end + +local function PopulateGKMenu() + spawnmenu.AddToolMenuOption("Options", "Grappling Knife", "Grappling Knife", "Settings", "", "", GKSettingsPanel) + spawnmenu.AddToolMenuOption("Options", "Grappling Knife", "Admin Grappling Knife", "Admin Settings", "", "", GKAdminSettingsPanel) +end +hook.Add("PopulateToolMenu", "GK Cvars", PopulateGKMenu) diff --git a/garrysmod/addons/koska/lua/entities/sent_rope_knife/cl_init.lua b/garrysmod/addons/koska/lua/entities/sent_rope_knife/cl_init.lua new file mode 100644 index 0000000..27be5ef --- /dev/null +++ b/garrysmod/addons/koska/lua/entities/sent_rope_knife/cl_init.lua @@ -0,0 +1,8 @@ +include("shared.lua") + +function ENT:Think() +end + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/koska/lua/entities/sent_rope_knife/init.lua b/garrysmod/addons/koska/lua/entities/sent_rope_knife/init.lua new file mode 100644 index 0000000..6a69cc7 --- /dev/null +++ b/garrysmod/addons/koska/lua/entities/sent_rope_knife/init.lua @@ -0,0 +1,127 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include( "shared.lua" ) + +function ENT:Initialize() + self.Entity:SetModel( "models/props_c17/TrapPropeller_Lever.mdl" ) + + self:PhysicsInit(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_NONE) + self.StartTime = CurTime() +end + +function ENT:Use( activator, caller ) +end + +function ENT:Touch( ent ) + local phys = self:GetPhysicsObject() + if IsValid(phys) and phys:GetVelocity():Length() > 100 then + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetNormal( self:GetPhysicsObject():GetVelocity():GetNormalized() * -1 ) + util.Effect( "Impact", effectdata ) + end +end + +function ENT:Think() + local phys = self:GetPhysicsObject() + if !self:GetNWBool("Stuck") and self.StartTime + 4 < CurTime() then + if IsValid(phys) then + phys:AddVelocity( Vector(0,0,-350) ) + end + elseif !self:GetNWBool("Stuck") and self.StartTime + 2 < CurTime() then + if IsValid(phys) then + phys:EnableGravity(true) + end + end + + if !self:GetNWBool("Stuck") then return end + if self:GetNWBool("Useless") then return end + + local hookPos = self:GetPos() + local groundPos = self:GetNWVector("DownHit", hookPos + Vector(0,0,-1000)) + + for _, ply in ipairs(player.GetAll()) do + if not IsValid(ply) or not ply:Alive() then continue end + + local plyPos = ply:GetPos() + Vector(0, 0, 40) + local dist = util.DistanceToLine(hookPos, groundPos, plyPos) + + if dist < 60 then + if ply:KeyDown(IN_USE) and (not ply.NextUse or ply.NextUse < CurTime()) then + ply.NextUse = CurTime() + 0.5 + + if ply:GetNWEntity("ClimbingEnt") == self and ply:GetMoveType() == MOVETYPE_CUSTOM then + ply:SetMoveType(MOVETYPE_WALK) + ply:SetGroundEntity(NULL) + ply:SetNWEntity("ClimbingEnt", NULL) + + local dropPos = ply:GetPos() + dropPos.x = hookPos.x + dropPos.y = hookPos.y + ply:SetPos(dropPos - self:GetNWVector("HitNormal"):Angle():Forward() * 18) + + ply:DrawViewModel(true) + ply:DrawWorldModel(true) + elseif not IsValid(ply:GetNWEntity("ClimbingEnt")) then + ply:SetMoveType(MOVETYPE_CUSTOM) + ply:SetGroundEntity(NULL) + ply:SetNWEntity("ClimbingEnt", self) + + local attachPos = ply:GetPos() + attachPos.x = hookPos.x + attachPos.y = hookPos.y + + if self:GetNWBool("MultiAngle") then + local ang = ply:EyeAngles() + ang.p = 0 + ang.r = 0 + ply:SetPos(attachPos - ang:Forward() * 14) + ply:SetNWVector("ClimbNormal", ang:Forward()) + else + ply:SetPos(attachPos - self:GetNWVector("HitNormal"):Angle():Forward() * 14) + end + + if GetConVar("gk_enableshooting"):GetBool() == false then + ply:DrawViewModel(false) + ply:DrawWorldModel(false) + end + end + end + end + end +end + +function ENT:OnRemove() + for k,ply in pairs(player.GetAll()) do + if ply:GetNWEntity("ClimbingEnt") == self then + ply:SetMoveType(MOVETYPE_WALK) + ply:SetGroundEntity( NULL ) + ply:SetNWEntity("ClimbingEnt", NULL) + ply:DrawViewModel(true) + ply:DrawWorldModel(true) + end + end +end + +hook.Add("PlayerNoClip", "NoclipRope", function( pl ) + local oldstate = pl:GetMoveType() + if oldstate == MOVETYPE_CUSTOM and IsValid(pl:GetNWEntity("ClimbingEnt")) then + local ent = pl:GetNWEntity("ClimbingEnt") + pl:SetMoveType(MOVETYPE_WALK) + pl:SetGroundEntity( NULL ) + local pos = pl:GetPos() + pos.x = ent:GetPos().x + pos.y = ent:GetPos().y + pl:SetPos( pos - ent:GetNWVector("HitNormal"):Angle():Forward()*18 ) + pl:SetNWEntity("ClimbingEnt", NULL) + pl:DrawViewModel(true) + pl:DrawWorldModel(true) + end +end) diff --git a/garrysmod/addons/koska/lua/entities/sent_rope_knife/shared.lua b/garrysmod/addons/koska/lua/entities/sent_rope_knife/shared.lua new file mode 100644 index 0000000..7a01eae --- /dev/null +++ b/garrysmod/addons/koska/lua/entities/sent_rope_knife/shared.lua @@ -0,0 +1,83 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "item" +ENT.Author = "Krede" + +function ENT:PhysicsCollide( cdata, obj ) + + if( SERVER ) then + + local line = {} + line.start = self:GetPos() + line.endpos = line.start + ( ( cdata.HitPos - line.start ) * 2 ) + line.filter = {self} + + local tr = util.TraceLine( line ) + + if IsValid(tr.Entity) then + tr.Entity:TakeDamage(10, self:GetNWEntity("Owner"), self:GetNWEntity("Owner")) + end + + if tr.HitSky or !tr.HitWorld or cdata.HitNormal.z < 0 then + self:SetNWBool("Useless", true) + SafeRemoveEntityDelayed( self, 2 ) + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableGravity(true) + end + return + end + + -- Impact Effects + local effectdata = EffectData() + effectdata:SetOrigin( cdata.HitPos ) + effectdata:SetNormal( cdata.HitNormal ) + util.Effect( "ManhackSparks", effectdata ) + self:EmitSound("weapons/iceaxe/iceaxe_impact1.wav", 75, 100) + + if self:GetNWBool("Useless") then return end + + self:SetAngles( cdata.HitNormal:Angle() + Angle(90,0,0) ) + + self:SetPos( cdata.HitPos - cdata.HitNormal*12 ) + + self:SetNotSolid(true) + + if cdata.HitNormal.z == 0 then + self:SetNWVector("HitNormal", cdata.HitNormal) + elseif cdata.HitNormal.z == 1 then + local norm = cdata.HitNormal + norm.z = 0 + self:SetNWVector("HitNormal", norm) + self:SetNWBool("MultiAngle", true) + else + local norm = cdata.HitNormal + norm.z = 0 + self:SetNWVector("HitNormal", norm) + end + local phys = self:GetPhysicsObject() + if IsValid(phys) and !IsValid(cdata.HitEntity) and !self.Weld then + phys:EnableMotion(false) + end + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetNWBool("Stuck", true) + self:SetNWFloat("ExpirationTime", CurTime() + 300) + SafeRemoveEntityDelayed(self, 300) + + local pos = self:GetPos() + + local line = {} + line.start = pos + line.endpos = pos + Vector(0,0,-16000) + line.filter = {} + for k,sent in pairs(ents.GetAll()) do + table.insert(line.filter, sent) + end + + local tr = util.TraceLine( line ) + + self:SetNWVector("DownHit", tr.HitPos) + + end + +end diff --git a/garrysmod/addons/koska/lua/weapons/weapon_rope_knife/shared.lua b/garrysmod/addons/koska/lua/weapons/weapon_rope_knife/shared.lua new file mode 100644 index 0000000..e07410a --- /dev/null +++ b/garrysmod/addons/koska/lua/weapons/weapon_rope_knife/shared.lua @@ -0,0 +1,210 @@ +if SERVER then + + AddCSLuaFile( "shared.lua" ) + +end + +if CLIENT then + if (file.Exists("materials/hud/rope_knife.vmt","GAME")) then + SWEP.WepSelectIcon = surface.GetTextureID("hud/rope_knife") + end + + SWEP.ViewModelFOV = 70 + SWEP.ViewModelFlip = false + + SWEP.BobScale = 1.5 + SWEP.SwayScale = 1.5 + + SWEP.PrintName = "Grappling Knife" + SWEP.Slot = 0 + SWEP.Slotpos = 0 + SWEP.BounceWeaponIcon = false + SWEP.DrawWeaponInfoBox = true + +end + +SWEP.HoldType = "melee" + +SWEP.Author = "Krede" +SWEP.Contact = "Through Steam" +SWEP.Instructions = "Use it to easily climb up buildings and high places" +SWEP.Category = "Krede's SWEPs" + +SWEP.ViewModel = "models/props_c17/TrapPropeller_Lever.mdl" +SWEP.WorldModel = "models/props_c17/TrapPropeller_Lever.mdl" +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.DrawCrosshair = false +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + + + +function SWEP:GetViewModelPosition( pos, ang ) + pos = pos + ang:Right() * 8 + pos = pos + ang:Forward() * 12 + pos = pos - ang:Up() * 5 + ang:RotateAroundAxis(ang:Up(), 90) + ang:RotateAroundAxis(ang:Right(), -10) + ang:RotateAroundAxis(ang:Forward(), -10) + return pos, ang +end + +function SWEP:SecondaryAttack() + return false +end + +function SWEP:Think() +end + +function SWEP:PrimaryAttack() + + self.Weapon:SendWeaponAnim( ACT_VM_SECONDARYATTACK ) + self.Owner:DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE) + + self.Weapon:SetNextPrimaryFire( CurTime() + 2 ) + if CLIENT then return end + timer.Simple(0.2, function() + self:EmitSound("weapons/iceaxe/iceaxe_swing1.wav") + if self == NULL or !IsValid(self) then return false end + local ent = ents.Create("sent_rope_knife") + ent:SetNWEntity("Owner", self.Owner) + ent:SetPos (self.Owner:EyePos() + (self.Owner:GetAimVector() * 48)) + ent:SetAngles(self.Owner:EyeAngles() + Angle(90,0,0)) + ent:Spawn() + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:ApplyForceCenter(self.Owner:GetAimVector()*7500) + phys:AddAngleVelocity(Vector(0,10000,0)) + phys:EnableGravity(false) + end + undo.Create( "Grappling Knife" ) + undo.AddEntity( ent ) + undo.SetPlayer( self.Owner ) + undo.Finish() + + -- Single use: remove the weapon + if IsValid(self.Owner) then + self:Remove() + end + end) + +end + +function SWEP:OnRemove() +end + +function SWEP:Reload() + return false +end + +function SWEP:Holster() + return true +end + +function SWEP:Deploy() + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) +end + +function SWEP:Initialize() + self.Weapon:SetWeaponHoldType( self.HoldType ) +end + +if CLIENT then + surface.CreateFont("RopeKnifeCrosshair", { + font = "Roboto", + size = 18, + weight = 800, + antialias = true, + shadow = true + }) +end + +function SWEP:DrawHUD() + if not IsValid(self.Owner) then return end + + local tr = self.Owner:GetEyeTrace() + local pos = tr.HitPos + local ent = tr.Entity + local vel = LocalPlayer():GetVelocity():Length() + local movement = math.Clamp(vel / 20, 0, 40) + local time = CurTime() + + if IsValid(self.Owner:GetNWEntity("ClimbingEnt")) and self.Owner:GetMoveType() == MOVETYPE_CUSTOM and GetConVar("gk_enableshooting"):GetBool() == false then + pos = self.Owner:GetNWEntity("ClimbingEnt"):GetPos() + end + + local screenpos = pos:ToScreen() + local dist = self.Owner:GetPos():Distance(pos) + local distM = math.Round(dist / 60, 1) + + -- Tactical Theme Colors + local themeColor = Color(0, 67, 28, 255) -- #00431c (Military Green) + local accentColor = Color(0, 150, 60, 200) + + -- Dynamic Range Status + local crossCol = themeColor + local statusText = "RANGE: STABLE" + if dist > 5000 then + crossCol = Color(200, 50, 50, 220) + statusText = "RANGE: EXCEEDED" + elseif dist > 3000 then + crossCol = Color(200, 150, 0, 220) + statusText = "RANGE: UNSTABLE" + end + + local x, y = screenpos.x, screenpos.y + local gap = 12 + movement + local cornerSize = 8 + + surface.SetDrawColor(crossCol) + + -- Tactical Corner Brackets + -- Top Left + surface.DrawLine(x - gap, y - gap, x - gap + cornerSize, y - gap) + surface.DrawLine(x - gap, y - gap, x - gap, y - gap + cornerSize) + -- Top Right + surface.DrawLine(x + gap, y - gap, x + gap - cornerSize, y - gap) + surface.DrawLine(x + gap, y - gap, x + gap, y - gap + cornerSize) + -- Bottom Left + surface.DrawLine(x - gap, y + gap, x - gap + cornerSize, y + gap) + surface.DrawLine(x - gap, y + gap, x - gap, y + gap - cornerSize) + -- Bottom Right + surface.DrawLine(x + gap, y + gap, x + gap - cornerSize, y + gap) + surface.DrawLine(x + gap, y + gap, x + gap, y + gap - cornerSize) + + -- Central Aim Dot + draw.RoundedBox(0, x - 1, y - 1, 2, 2, Color(255, 255, 255, 100)) + + -- Tactical Data Display + local dataX = x + gap + 15 + draw.SimpleText("DIST: " .. string.format("%05.1f", distM) .. "m", "RopeKnifeCrosshair", dataX, y - 8, crossCol, TEXT_ALIGN_LEFT) + draw.SimpleText(statusText, "RopeKnifeCrosshair", dataX, y + 8, crossCol, TEXT_ALIGN_LEFT) + + -- Target ID (TOP) + if GetConVar("gk_enabledamage"):GetBool() == true and IsValid(ent) and not IsValid(self.Owner:GetNWEntity("ClimbingEnt")) then + local name = "" + if ent:IsPlayer() then + name = ent:Nick() + elseif ent:IsNPC() or ent:IsNextBot() then + name = ent:GetClass():gsub("^npc_", ""):gsub("^ent_", ""):gsub("_", " "):upper() + end + + if name ~= "" then + draw.SimpleText("[TARGET: " .. name .. "]", "RopeKnifeCrosshair", x, y - gap - 20, Color(255, 255, 255, 120), TEXT_ALIGN_CENTER) + end + end + + -- Command Hint + if IsValid(self.Owner:GetNWEntity("ClimbingEnt")) then + local hintY = ScrH() - 175 + draw.SimpleText("КОМАНДА: [ПРЫЖОК] - ОТЦЕПИТЬ", "RopeKnifeCrosshair", ScrH()/2, hintY, accentColor, TEXT_ALIGN_CENTER) + end +end diff --git a/garrysmod/addons/luadev/lua/autorun/easylua.lua b/garrysmod/addons/luadev/lua/autorun/easylua.lua new file mode 100644 index 0000000..c95d8d1 --- /dev/null +++ b/garrysmod/addons/luadev/lua/autorun/easylua.lua @@ -0,0 +1,816 @@ +easylua = {} local s = easylua + +local _R = debug.getregistry() + +local function compare(a, b) + + if a == b then return true end + if a:find(b, nil, true) then return true end + if a:lower() == b:lower() then return true end + if a:lower():find(b:lower(), nil, true) then return true end + + return false +end + +local function comparenick(a, b) + local MatchTransliteration = GLib and GLib.UTF8 and GLib.UTF8.MatchTransliteration + if not MatchTransliteration then return compare (a, b) end + + if a == b then return true end + if a:lower() == b:lower() then return true end + if MatchTransliteration(a, b) then return true end + + return false +end + +local function compareentity(ent, str) + if ent.GetName and compare(ent:GetName(), str) then + return true + end + + if ent:GetModel() and compare(ent:GetModel(), str) then + return true + end + + return false +end + +local TagPrintOnServer = "elpos" +if CLIENT then + function easylua.PrintOnServer(...) + local args = {...} + local new = {} + + for key, value in pairs(args) do + table.insert(new, luadata and luadata.ToString(value) or tostring(value)) + end + net.Start(TagPrintOnServer) + local str = table.concat(new," ") + local max = 256 + net.WriteString(str:sub(1,max)) + net.WriteBool(#str>max) + net.SendToServer() + end +else + util.AddNetworkString(TagPrintOnServer) +end + +function easylua.Print(...) + if CLIENT then + easylua.PrintOnServer(...) + end + if SERVER then + local args = {...} + local str = "" + + Msg(string.format("[ELua %s] ", IsValid(me) and me:Nick() or "Sv")) + + for key, value in pairs(args) do + str = str .. type(value) == "string" and value or luadata.ToString(value) or tostring(value) + + if key ~= #args then + str = str .. "," + end + end + + print(str) + end +end + +if SERVER then + -- Rate limiting, still bad + local spams=setmetatable({},{__mode='k'}) + local function canspam(pl,len) + local now = RealTime() + local nextspam = spams[pl] or 0 + if now>nextspam then + nextspam = now + len>100 and 3 or 1 + spams[pl] = nextspam + return true + else + local plstr = tostring(pl) + timer.Create("easylua_pspam",5,1,function() + Msg "[Easylua Print] Lost messages due to spam: " print(plstr) + end) + end + end + + function easylua.CMDPrint(ply, cmd, args, fulln) + if not canspam(ply,#fulln) then + return + end + args = table.concat(args, ", ") + + Msg(string.format("[ELua %s] ", IsValid(ply) and ply:Nick() or "Sv")) + print(args) + end + concommand.Add("easylua_print", easylua.CMDPrint) + + net.Receive(TagPrintOnServer,function(len,ply) + local str = net.ReadString() + if not canspam(ply,#str) then return end + str=str:sub(1,512) + local more = net.ReadBool() + Msg(string.format("[ELua %s] ", IsValid(ply) and ply:Nick() or "Sv")) + local outstr = ('%s%s'):format(str,more and "..." or ""):gsub("[\r\n]"," ") + print(outstr) + end) +end + +function easylua.FindEntity(str) + if not str then return NULL end + + str = tostring(str) + + if str == "#this" and IsEntity(this) and this:IsValid() then + return this + end + + if str == "#owner" and IsEntity(this) and this:IsValid() then + local owner = this.CPPIGetOwner and this:CPPIGetOwner() or this:GetOwner() + return owner + end + + if str == "#me" and IsEntity(me) and me:IsPlayer() then + return me + end + + if str == "#all" then + return all + end + + if str == "#afk" then + return afk + end + + if str == "#us" then + return us + end + + if str == "#them" then + return them + end + + if str == "#friends" then + return friends + end + + if str == "#humans" then + return humans + end + + if str == "#bots" then + return bots + end + + if str == "#randply" then + return table.Random(player.GetAll()) + end + + if str:sub(1,1) == "#" then + local str = str:sub(2) + + if #str > 0 then + str = str:lower() + local found + + for teamid, data in pairs(team.GetAllTeams()) do + if data.Name:lower() == str then + found = teamid + break + end + end + if found then + return CreateAllFunction(function() + local t = {} + for k,v in next,player.GetAll() do + if v:IsPlayer() and v:Team() == found then + t[#t+1] = v + end + end + return t + end) + end + + + for key, ent in ipairs(ents.GetAll()) do + if ent:GetClass():lower() == str then + found = str + break + end + end + if found then + return CreateAllFunction(function() + return ents.FindByClass(found) + end) + end + end + end + + -- unique id + local ply = player.GetByUniqueID(str) + if ply and ply:IsPlayer() then + return ply + end + + -- steam id + if str:find("STEAM") then + for key, _ply in ipairs(player.GetAll()) do + if _ply:SteamID() == str then + return _ply + end + end + end + + if str:sub(1,1) == "_" and tonumber(str:sub(2)) then + str = str:sub(2) + end + + if tonumber(str) then + ply = Entity(tonumber(str)) + if ply:IsValid() then + return ply + end + end + + -- community id + if #str == 17 and str[1] == "7" then + local ply = player.GetBySteamID64(str) + if ply and ply:IsValid() then + return ply + end + end + + -- ip + if SERVER then + if str:find("%d+%.%d+%.%d+%.%d+") then + for key, _ply in ipairs(player.GetAll()) do + if _ply:IPAddress():find(str) then + return _ply + end + end + end + end + -- search in sensible order + + -- search exact + for _,ply in ipairs(player.GetAll()) do + if ply:Nick()==str then + return ply + end + end + + -- Search bots so we target those first + for key, ply in ipairs(player.GetBots()) do + if comparenick(ply:Nick(), str) then + return ply + end + end + + -- search from beginning of nick + for _,ply in ipairs(player.GetHumans()) do + if ply:Nick():lower():find(str,1,true)==1 then + return ply + end + end + + -- Search normally and search with colorcode stripped + for key, ply in ipairs(player.GetAll()) do + if comparenick(ply:Nick(), str) then + return ply + end + + if _G.UndecorateNick and comparenick( UndecorateNick( ply:Nick() ), str) then + return ply + end + end + + -- search RealName + if _R.Player.RealName then + for _, ply in ipairs(player.GetAll()) do + if comparenick(ply:RealNick(), str) then + return ply + end + end + end + + if IsValid(me) and me:IsPlayer() then + local tr = me:GetEyeTrace() + local plpos = tr and tr.HitPos or me:GetPos() + local closest,mind = nil,math.huge + for key, ent in ipairs(ents.GetAll()) do + local d = ent:GetPos():DistToSqr(plpos) + if d < mind and compareentity(ent, str) then + closest = ent + mind = d + end + end + if closest then + return closest + end + else + for key, ent in ipairs(ents.GetAll()) do + if compareentity(ent, str) then + return ent + end + end + end + + do -- class + + local _str, idx = str:match("(.-)(%d+)$") + if idx then + idx = tonumber(idx) + str = _str + else + str = str + idx = (me and me.easylua_iterator) or 0 + + if me and isentity(me) and me:IsPlayer() then + + local tr = me:GetEyeTrace() + local plpos = tr and tr.HitPos or me:GetPos() + local closest,mind = nil,math.huge + for key, ent in ipairs(ents.GetAll()) do + local d = ent:GetPos():DistToSqr(plpos) + if d < mind and compare(ent:GetClass(), str) then + closest = ent + mind = d + end + end + if closest then + return closest + end + end + + end + + local found = {} + + for key, ent in ipairs(ents.GetAll()) do + if compare(ent:GetClass(), str) then + table.insert(found, ent) + end + end + + return found[math.Clamp(idx%#found, 1, #found)] or NULL + end +end + +function easylua.CreateEntity(class, callback) + local mdl = "error.mdl" + + if IsEntity(class) and class:IsValid() then + this = class + elseif class:find(".mdl", nil, true) then + mdl = class + class = "prop_physics" + + this = ents.Create(class) + this:SetModel(mdl) + else + this = ents.Create(class) + end + + if callback and type(callback) == 'function' then + callback(this); + end + + this:Spawn() + this:SetPos(there + Vector(0,0,this:BoundingRadius() * 2)) + this:DropToFloor() + this:PhysWake() + + undo.Create(class) + undo.SetPlayer(me) + undo.AddEntity(this) + undo.Finish() + + me:AddCleanup("props", this) + + return this +end + +function easylua.CopyToClipboard(var, ply) + ply = ply or me + if luadata then + local str = luadata.ToString(var) + + if not str and IsEntity(var) and var:IsValid() then + if var:IsPlayer() then + str = string.format("player.GetByUniqueID(--[[%s]] %q)", var:GetName(), var:UniqueID()) + else + str = string.format("Entity(%i)", var:EntIndex()) + end + + end + + if CLIENT then + SetClipboardText(str) + end + + if SERVER then + local str = string.format("SetClipboardText(%q)", str) + if #str > 255 then + if luadev and luadev.RunOnClient then + luadev.RunOnClient(str, ply) + else + error("Text too long to send and luadev not found",1) + end + else + ply:SendLua(str) + end + end + end +end + + +local started = false +function easylua.Start(ply) + if started then + Msg"[ELua] "print("Session not ended for ",_G.me or (s.vars and s.vars.me),", restarting session for",ply) + easylua.End() + end + started = true + + ply = ply or CLIENT and LocalPlayer() or nil + + if not ply or not IsValid(ply) then return end + + local vars = {} + local trace = util.QuickTrace(ply:EyePos(), ply:GetAimVector() * 10000, {ply, ply:GetVehicle()}) + + if trace.Entity:IsWorld() then + trace.Entity = NULL + end + + vars.me = ply + vars.this = trace.Entity + vars.wep = ply:GetActiveWeapon() + vars.veh = ply:GetVehicle() + + vars.we = {} + + for k, v in ipairs(ents.FindInSphere(ply:GetPos(), 512)) do + if v:IsPlayer() then + table.insert(vars.we, v) + end + end + + vars.there = trace.HitPos + vars.here = trace.StartPos + vars.dir = ply:GetAimVector() + + vars.trace = trace + vars.length = trace.StartPos:Distance(trace.HitPos) + + vars.copy = s.CopyToClipboard + vars.create = s.CreateEntity + vars.prints = s.PrintOnServer + + if vars.this:IsValid() then + vars.phys = vars.this:GetPhysicsObject() + vars.model = vars.this:GetModel() + end + + vars.E = s.FindEntity + vars.last = ply.easylua_lastvars + + + s.vars = vars + local old_G = {} + s.oldvars = old_G + + for k, v in pairs(vars) do + old_G[k] = rawget(_G, k) + rawset(_G, k, v) + end + + -- let this gc. maybe allow few more recursions. + if vars.last and istable(vars.last) then vars.last.last = nil end + + ply.easylua_lastvars = vars + ply.easylua_iterator = (ply.easylua_iterator or 0) + 1 +end + +function easylua.End() + if not started then + Msg"[ELua] "print"Ending session without starting" + end + started = false + + if s.vars then + for key, value in pairs(s.vars) do + if s.oldvars and s.oldvars[key] then + rawset(_G, key, s.oldvars[key]) + else + rawset(_G, key, nil) + end + end + end +end + +do -- env meta + local META = {} + + local _G = _G + local easylua = easylua + local tonumber = tonumber + + local nils={ + ["CLIENT"]=true, + ["SERVER"]=true, + } + function META:__index(key) + local var = _G[key] + + if var ~= nil then + return var + end + + if not nils [key] then -- uh oh + var = easylua.FindEntity(key) + if var:IsValid() then + return var + end + end + + return nil + end + + function META:__newindex(key, value) + _G[key] = value + end + + easylua.EnvMeta = setmetatable({}, META) +end + +function easylua.RunLua(ply, code, env_name) + local data = + { + error = false, + args = {}, + } + + easylua.Start(ply) + if s.vars then + local header = "" + + for key, value in next,(s.vars or {}) do + header = header .. string.format("local %s = %s ", key, key) + end + + code = header .. "; " .. code + end + + env_name = env_name or string.format("%s", tostring( + IsValid(ply) and ply:IsPlayer() + and "["..ply:SteamID():gsub("STEAM_","").."]"..ply:Name() + or ply)) + + data.env_name = env_name + + local func = CompileString(code, env_name, false) + + if type(func) == "function" then + setfenv(func, easylua.EnvMeta) + + local args = {pcall(func)} + + if args[1] == false then + data.error = args[2] + end + + table.remove(args, 1) + data.args = args + else + data.error = func + end + easylua.End() + + return data +end + +-- legacy luadev compatibility + +local STAGE_PREPROCESS=1 +local STAGE_COMPILED=2 +local STAGE_POST=3 + +local insession = false +hook.Add("LuaDevProcess","easylua",function(stage,script,info,extra,func) + if stage==STAGE_PREPROCESS then + + if insession then + insession=false + easylua.End() + end + + if not istable(extra) or not IsValid(extra.ply) or not script or extra.easylua==false then + return + end + + insession = true + easylua.Start(extra.ply) + + local t={} + for key, value in pairs(easylua.vars or {}) do + t[#t+1]=key + end + if #t>0 then + script=' local '..table.concat(t,", ")..' = '..table.concat(t,", ")..' ; '..script + end + + --ErrorNoHalt(script) + return script + + elseif stage==STAGE_COMPILED then + + if not istable(extra) or not IsValid(extra.ply) or not isfunction(func) or extra.easylua==false then + if insession then + insession=false + easylua.End() + end + return + end + + if insession then + local env = getfenv(func) + if not env or env==_G then + setfenv(func, easylua.EnvMeta) + end + end + + elseif stage == STAGE_POST and insession then + insession=false + easylua.End() + end +end) + +function easylua.StartWeapon(classname) + _G.SWEP = { + Primary = {}, + Secondary = {}, + ViewModelFlip = false, + } + + SWEP.Base = "weapon_base" + + SWEP.ClassName = classname +end + +function easylua.EndWeapon(spawn, reinit) + if not SWEP then error"missing SWEP" end + if not SWEP.ClassName then error"missing classname" end + + weapons.Register(SWEP, SWEP.ClassName) + + for key, entity in ipairs(ents.FindByClass(SWEP.ClassName)) do + --if entity:GetTable() then table.Merge(entity:GetTable(), SWEP) end + if reinit then + entity:Initialize() + end + end + + if SERVER and spawn then + SafeRemoveEntity(me:GetWeapon(SWEP.ClassName)) + local me = me + local class = SWEP.ClassName + timer.Simple(0.2, function() if me:IsPlayer() then me:Give(class) end end) + end + + SWEP = nil +end + +function easylua.StartEntity(classname) + _G.ENT = {} + + ENT.ClassName = classname or "no_ent_name_" .. me:Nick() .. "_" .. me:UniqueID() +end + +function easylua.EndEntity(spawn, reinit) + + ENT.Model = ENT.Model or Model("models/props_borealis/bluebarrel001.mdl") + + if not ENT.Base then -- there can be Base without Type but no Type without base without redefining every function so um + ENT.Base = "base_anim" + ENT.Type = ENT.Type or "anim" + end + + scripted_ents.Register(ENT, ENT.ClassName) + + for key, entity in ipairs(ents.FindByClass(ENT.ClassName)) do + --table.Merge(entity:GetTable(), ENT) + if reinit then + entity:Initialize() + end + end + + if SERVER and spawn then + create(ENT.ClassName) + end + + ENT = nil +end + +do -- all + include("tinylua.lua") -- Force it to load before we use it + local next = next + local type = type + + local INTERNAL = {} + local META = {} + + function META:__call() + error("Undefined __call") + end + + function META:__index(key) + local wrapped = tinylua(self()) + return wrapped[key] + end + + function META:__newindex(key, value) + local wrapped = tinylua(self()) + wrapped[key] = value + end + + function CreateAllFunction(filter, inputTbl) + local allMeta = {} + + for ind, metamethod in pairs(META)do + allMeta[ind] = metamethod + end + + function allMeta:__call() + return (isfunction(filter) and filter() or filter) + end + + return setmetatable(inputTbl or {}, allMeta) + end + + function INTERNAL:get() + return self() + end + + all = CreateAllFunction(player.GetAll) + humans = CreateAllFunction(player.GetHumans) + bots = CreateAllFunction(player.GetBots) + afk = CreateAllFunction(function() + local t = {} + for k,v in ipairs(player.GetAll()) do + if not v.IsAFK then break end + if v:IsAFK() then + t[#t+1] = v + end + end + return t + end) + us = CreateAllFunction(function() + if _G.we then return _G.we end + if _G.me then return {_G.me} end + return {} + end) + them = CreateAllFunction(function() + local me = _G.me + local we = _G.we or {} + table.RemoveByValue(we, me) + return we + end) + friends = CreateAllFunction(function() + local me = _G.me + local t = {} + for k,v in ipairs(player.GetHumans()) do + if v == me then continue end + if (me.IsFriend and me:IsFriend(v) or (CLIENT and v:GetFriendStatus() == "friend")) then + t[#t+1] = v + end + end + return t + end) + npcs = CreateAllFunction(function() + local t = {} + for _, ent in pairs(ents.GetAll())do + if ent:IsNPC() then + table.insert(t, ent) + end + end + return t + end) + allof = function(class) + if isentity(class) and IsValid(class) then + class = class:GetClass() + end + + local results = ents.FindByClass(class) + return CreateAllFunction(function() + return results + end, results) + end + + props = CreateAllFunction(function() return ents.FindByClass("prop_physics") end) + if SERVER then + these = CreateAllFunction(function() return constraint.GetAllConstrainedEntities(_G.this) end) + end + those = CreateAllFunction(function() return ents.FindInSphere(_G.there, 250) end) +end diff --git a/garrysmod/addons/luadev/lua/autorun/luadev.lua b/garrysmod/addons/luadev/lua/autorun/luadev.lua new file mode 100644 index 0000000..c1d7153 --- /dev/null +++ b/garrysmod/addons/luadev/lua/autorun/luadev.lua @@ -0,0 +1,18 @@ +module("luadev",package.seeall) + +-- I think I finally understood why people make these seemingly silly files with just includes + +include 'luadev/luadev_sh.lua' +if SERVER then + include 'luadev/luadev_sv.lua' +end +include 'luadev/luadev.lua' +if CLIENT then + include 'luadev/socketdev.lua' +end + +if SERVER then + AddCSLuaFile 'luadev/luadev_sh.lua' + AddCSLuaFile 'luadev/luadev.lua' + AddCSLuaFile 'luadev/socketdev.lua' +end diff --git a/garrysmod/addons/luadev/lua/autorun/server/luadev_chatcmds.lua b/garrysmod/addons/luadev/lua/autorun/server/luadev_chatcmds.lua new file mode 100644 index 0000000..f0cf19e --- /dev/null +++ b/garrysmod/addons/luadev/lua/autorun/server/luadev_chatcmds.lua @@ -0,0 +1,130 @@ +hook.Add("Think","luadev_cmdsinit",function() +hook.Remove("Think","luadev_cmdsinit") + +local function add(cmd,callback) + if aowl and aowl.AddCommand then + aowl.AddCommand(cmd,function(ply,script,param_a,...) + + local a,b + + easylua.End() -- nesting not supported + + local ret,why = callback(ply,script,param_a,...) + if not ret then + if why==false then + a,b = false,why or aowl.TargetNotFound(param_a or "notarget") or "H" + elseif isstring(why) then + ply:ChatPrint("FAILED: "..tostring(why)) + a,b= false,tostring(why) + end + end + + easylua.Start(ply) + return a,b + + end,cmd=="lm" and "players" or "developers") + end +end + +local function X(ply,i) return luadev.GetPlayerIdentifier(ply,'cmd:'..i) end + +add("l", function(ply, line, target) + if not line or line=="" then return false,"invalid script" end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,"l") if not valid then return false,err end end + return luadev.RunOnServer(line, X(ply,"l"), {ply=ply}) +end) + +add("ls", function(ply, line, target) + if not line or line=="" then return false,"invalid script" end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,"ls") if not valid then return false,err end end + return luadev.RunOnShared(line, X(ply,"ls"), {ply=ply}) +end) + +add("lc", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,"lc") if not valid then return false,err end end + return luadev.RunOnClients(line, X(ply,"lc"), {ply=ply}) +end) + +add("lsc", function(ply, line, target) + local script = string.sub(line, string.find(line, target, 1, true)+#target+1) + if luadev.ValidScript then local valid,err = luadev.ValidScript(script,'lsc') if not valid then return false,err end end + + easylua.Start(ply) -- for _G.we -> #us + local ent = easylua.FindEntity(target) + if type(ent) == 'table' then + ent = ent.get() + end + easylua.End() + + return luadev.RunOnClient(script, ent, X(ply,"lsc"), {ply=ply}) +end) +local sv_allowcslua = GetConVar"sv_allowcslua" +add("lm", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'lm') if not valid then return false,err end end + + if not ply:IsAdmin() and not sv_allowcslua:GetBool() then return false,"sv_allowcslua is 0" end + + luadev.RunOnClient(line, ply,X(ply,"lm"), {ply=ply}) + +end) + +add("lb", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'lb') if not valid then return false,err end end + + luadev.RunOnClient(line, ply, X(ply,"lb"), {ply=ply}) + return luadev.RunOnServer(line, X(ply,"lb"), {ply=ply}) +end) + +add("print", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..line..')','print') if not valid then return false,err end end + + return luadev.RunOnServer("print(" .. line .. ")", X(ply,"print"), {ply=ply}) +end) + +add("table", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..line..')','table') if not valid then return false,err end end + + return luadev.RunOnServer("PrintTable(" .. line .. ")", X(ply,"table"), {ply=ply}) +end) + +add("keys", function(ply, line, table, search) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..table..')','keys') if not valid then return false,err end end + + search = search and search:lower() or "" + return luadev.RunOnServer( + "local t={} for k,v in pairs(" .. table .. ") do t[#t+1]=tostring(k) end table.sort(t) for k,v in pairs(t) do if string.find(v:lower(),\"" .. search .. "\",1,true) then print(v) end end", + X(ply,"keys"), {ply=ply} + ) +end) + +add("printc", function(ply, line, target) + if not line or line=="" then return end + line = "easylua.PrintOnServer(" .. line .. ")" + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'printc') if not valid then return false,err end end + + return luadev.RunOnClients(line, X(ply,"printc"), {ply=ply}) +end) + +add("printm", function(ply, line, target) + if not line or line=="" then return end + line = "easylua.PrintOnServer(" .. line .. ")" + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'printm') if not valid then return false,err end end + + luadev.RunOnClient(line, ply, X(ply,"printm"), {ply=ply}) +end) + +add("printb", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..line..')','printb') if not valid then return false,err end end + + luadev.RunOnClient("easylua.PrintOnServer(" .. line .. ")", ply, X(ply,"printb"), {ply=ply}) + return luadev.RunOnServer("print(" .. line .. ")", X(ply,"printb"), {ply=ply}) +end) + +end) diff --git a/garrysmod/addons/luadev/lua/autorun/tinylua.lua b/garrysmod/addons/luadev/lua/autorun/tinylua.lua new file mode 100644 index 0000000..6d3f447 --- /dev/null +++ b/garrysmod/addons/luadev/lua/autorun/tinylua.lua @@ -0,0 +1,202 @@ +tinylua = setmetatable({}, { __call = function(self, ...) return self.Wrap(...) end}) +local INTERNAL = {} +local META = {} + +local function pack(...) -- Convenient argument packer + local len, tbl = select('#', ...), {...} + local packFuncs = {} + + function packFuncs.unpack() + return unpack(tbl, 1, len) + end + + return setmetatable(tbl, { + __index = function(self, index) + return packFuncs[index] or tbl[index] + end, + __call = function(...) + return len, tbl + end + }) +end + +local function Wrap(input) + local values = {} + local meta = {} + + for ind, val in pairs(input)do + values[(tonumber(ind) and val or ind)] = val + end + + for ind, val in pairs(META)do + meta[ind] = val + end + + return setmetatable(values, meta) +end + +local function performCall(tbl, callback) + local results = {} + local errors = {} + local calls = 0 + + local iKey, iVal = nil, nil + while true do + local succ, err = pcall(function() + while true do + iKey, iVal = next(tbl, iKey) + if iKey == nil then break end + calls = calls + 1 + + callback(results, iKey, iVal) + end + end) + + if not succ then errors[iKey] = err end + if iKey == nil then break end + end + + if table.Count(errors) == calls then + if calls ~= 0 then + local _, error = next(errors, nil) + MsgC(Color(235, 111, 111), "[tinylua] "..error) + else + MsgC(Color(235, 111, 111), "[tinylua] No results!\n") + return + end + end + + local result = Wrap(results) + getmetatable(result)["errors"] = errors + return result +end + +function META:__index(index) + if INTERNAL[index] then + return function(_, ...) + return INTERNAL[index](self, ...) + end + end + + return performCall(self, function(results, source, ent) + local target = ent[index] + + if isfunction(target) then + results[source] = function(fArg, ...) + return target(ent, ...) + end + else + results[source] = target + end + end) +end + +function META:__newindex(index, value) + performCall(self, function(results, source, ent) + ent[index] = value + end) +end + +function META:__call(...) + local args = pack(...) + return performCall(self, function(results, source, ent) + if isfunction(ent) then + local rets = pack(ent(args:unpack())) + if #rets ~= 1 then + for _, ret in pairs(rets) do + table.insert(results, ret) + end + else + results[source] = rets[1] + end + end + end) +end + +-- Exposed Functions +tinylua.Wrap = Wrap + +-- INTERNAL Extensions +local function makePrefix(input) + if not input:match("\n") and isfunction(CompileString("return "..input, "", false)) then + return "return "..input + end + + return input +end + +local function buildParser(input) + if isfunction(input) then return input end + local argStr, funcStr = input:match("(.-)->(.+)") + + if argStr and funcStr then + local codeFull = string.format("return function(%s)\n%s\nend", argStr, makePrefix(funcStr)) + local funcFactory = CompileString(codeFull, "funcfactory") + + if getfenv(1) then + setfenv(funcFactory, getfenv(1)) + end + + if funcFactory then + return funcFactory() + end + end +end + +function INTERNAL:map(input) + local eval = buildParser(input) + return performCall(self, function(results, source, ent) + local rets = pack(eval(ent, source)) + if #rets ~= 1 then + for _, val in pairs(rets) do + table.insert(results, val) + end + else + results[source] = rets[1] + end + end) +end + +function INTERNAL:filter(input) + local eval = buildParser(input) + return performCall(self, function(results, source, ent) + if eval(ent, source) then + results[source] = ent + end + end) +end + +function INTERNAL:set(vars, val) + vars = (istable(vars) and vars or {vars}) + return performCall(self, function(results, source, ent) + for _, var in ipairs(vars) do + ent[var] = val + end + + results[source] = ent + end) +end + +function INTERNAL:IsValid() + return false +end + +function INTERNAL:keys() + return performCall(self, function(results, source, ent) + results[source] = source + end) +end + +function INTERNAL:first() + for _, ent in pairs(self) do + return ent + end +end + +function INTERNAL:errors() + return (getmetatable(self).errors or {}) +end + +function INTERNAL:get() + return table.ClearKeys(self) +end diff --git a/garrysmod/addons/luadev/lua/bin/gmcl_luasocket_win32.dll b/garrysmod/addons/luadev/lua/bin/gmcl_luasocket_win32.dll new file mode 100644 index 0000000..4d84a62 Binary files /dev/null and b/garrysmod/addons/luadev/lua/bin/gmcl_luasocket_win32.dll differ diff --git a/garrysmod/addons/luadev/lua/bin/gmcl_luasocket_win64.dll b/garrysmod/addons/luadev/lua/bin/gmcl_luasocket_win64.dll new file mode 100644 index 0000000..3d63763 Binary files /dev/null and b/garrysmod/addons/luadev/lua/bin/gmcl_luasocket_win64.dll differ diff --git a/garrysmod/addons/luadev/lua/luadev/luadev.lua b/garrysmod/addons/luadev/lua/luadev/luadev.lua new file mode 100644 index 0000000..f5c290e --- /dev/null +++ b/garrysmod/addons/luadev/lua/luadev/luadev.lua @@ -0,0 +1,507 @@ +module("luadev",package.seeall) + +local function CMD(who) + return CLIENT and "CMD" or who or "CMD" +end + +COMMAND('run_sv',function(ply,_,script,who) + RunOnServer(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_sh',function(ply,_,script,who) + RunOnShared(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_clients',function(ply,_,script,who) + RunOnClients(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_self',function(ply,_,script,who) + RunOnSelf(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_client',function(ply,tbl,script,who) + + if !tbl[1] or !tbl[2] then Print("Syntax: lua_run_client (steamid/userid/accountid/part of name) script") return end + + local cl=FindPlayer(tbl[1]) + + if !cl then Print("Client not found!\n") return end + if CLIENT then + Print("Running script on "..tostring(cl:Name())) + end + + local _, e = script:find('^%s*"[^"]+') + if e then + script = script:sub(e+2) + else + local _, e = script:find('^%s*[^%s]+%s') + if not e then + Print("Invalid Command syntax.") + return + end + script = script:sub(e) + end + + script = script:Trim() + + RunOnClient(script,cl,CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_cl',function(ply,tbl,cmd,who) + + if !tbl[1] or !tbl[2] then Print("Syntax: lua_send_cl (steamid/userid/accountid/part of name) \"path\"") return end + + local cl=FindPlayer(tbl[1]) + + if !cl then Print("Client not found!\n") return end + Print("Running script on "..tostring(cl:Name())) + + + table.remove(tbl,1) + local path=TableToString(tbl) + + local Path,searchpath=RealFilePath(path) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + RunOnClient(content,cl,who or CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_sv',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnServer(content,who or CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_clients',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnClients(content,who or CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_sh',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnShared(content,who or CMD(who),MakeExtras(ply)) + +end) + +local function Guess(name,Path) + + if name=="init" or name=="shared" or name=="cl_init" then + local newname = Path:gsub("\\","/"):match("^.+%/([^%/]-)/.-%.lua$") + Print("Guessing identifier: "..tostring(newname or "")) + return newname or name + end + + return name +end + +local function SendEFFECT(cl,Path,ply,c,cmd,who) + local who=string.GetFileFromFilename(Path) + + local effectname=string.GetFileFromFilename(Path):gsub("%.lua","") + + effectname = Guess(effectname,Path) + + if cl then + RunOnClients(cl,who or CMD(who),MakeExtras(ply,{effect=effectname})) + end + +end + +COMMAND('send_effect',function(ply,c,cmd,who) + local path = c[2] and TableToString(c) or c[1] + + local Path,searchpath=RealFilePath(path) + if not Path then + Print("Could not find the file\n") + return + end + + local content = GiveFileContent(Path,searchpath) + if content then + local sh = content + SendEFFECT(content,Path,ply,c,cmd,who) + return + end + + local cl = GiveFileContent(Path..'/init.lua',searchpath) + + if cl then + SendEFFECT(cl,Path,ply,c,cmd,who) + return + else + Print("Could not find required files from the folder\n") + end + +end) + + + +local function SendSWEP(cl,sh,sv,Path,ply,c,cmd,who) + local who=string.GetFileFromFilename(Path) + + local swepname=string.GetFileFromFilename(Path):gsub("%.lua","") + swepname=Guess(swepname,Path) + + if cl then + RunOnClients(cl,who or CMD(who),MakeExtras(ply,{swep=swepname})) + end + if sh then + RunOnShared(sh,who or CMD(who),MakeExtras(ply,{swep=swepname})) + end + if sv then + RunOnServer(sv,who or CMD(who),MakeExtras(ply,{swep=swepname})) + end + +end + +COMMAND('send_wep',function(ply,c,cmd,who) + local path = c[2] and TableToString(c) or c[1] + + local Path,searchpath=RealFilePath(path) + if not Path then + Print("Could not find the file\n") + return + end + + local content = GiveFileContent(Path,searchpath) + if content then + local sh = content + SendSWEP(nil,sh,nil,Path,ply,c,cmd,who) + return + end + + local cl = GiveFileContent(Path..'/cl_init.lua',searchpath) + local sh = GiveFileContent(Path..'/shared.lua',searchpath) + local sv = GiveFileContent(Path..'/init.lua',searchpath) + + if sv or sh or cl then + SendSWEP(cl,sh,sv,Path,ply,c,cmd,who) + return + else + Print("Could not find required files from the folder\n") + end + +end) + + +local function SendENT(cl,sh,sv,Path,ply,c,cmd,who) + local who=string.GetFileFromFilename(Path) + + local entname=string.GetFileFromFilename(Path):gsub("%.lua","") + entname = Guess(entname,Path) + if cl then + RunOnClients(cl,who or CMD(who),MakeExtras(ply,{sent=entname})) + end + if sh then + RunOnShared(sh,who or CMD(who),MakeExtras(ply,{sent=entname})) + end + if sv then + RunOnServer(sv,who or CMD(who),MakeExtras(ply,{sent=entname})) + end + +end + +COMMAND('send_ent',function(ply,c,cmd,who) + local path = c[2] and TableToString(c) or c[1] + + local Path,searchpath=RealFilePath(path) + if not Path then + Print("Could not find the file\n") + return + end + + local content = GiveFileContent(Path,searchpath) + if content then + local sh = content + SendENT(nil,sh,nil,Path,ply,c,cmd,who) + return + end + + local cl = GiveFileContent(Path..'/cl_init.lua',searchpath) + local sh = GiveFileContent(Path..'/shared.lua',searchpath) + local sv = GiveFileContent(Path..'/init.lua',searchpath) + + if sv or sh or cl then + SendENT(cl,sh,sv,Path,ply,c,cmd,who) + return + else + Print("Could not find required files from the folder\n") + end + +end) + + +COMMAND('watch_kill',function(ply,c,cmd,wholeline) + + local watchlist = GetWatchList() + + if c[1]=="" or not c[1] then + Print"Killing all" + table.Empty(watchlist) + return + end + + local t= table.remove(watchlist,tonumber(c[1])) + Print("killing",t and tostring(t.path) or "(not found)") +end,true) + +COMMAND('watch',function(ply,c,cmd,wholeline) + + local path_orig = c[1] + table.remove(c,1) + + local fpath,searchpath=RealFilePath(path_orig,findpath) + if not fpath then Print("Could not find the file\n") return end + + local content = fpath and GiveFileContent(fpath,searchpath) + local time = content and fpath and FileTime(fpath,searchpath) + if not content or not time then Print("File not readable\n") return end + + local found + for k,v in next,c do + if v=="PATH" then + c[k] = path_orig + found = true + end + if v=="FILE" then + c[k] = path_orig + found = true + end + if v=="RPATH" then + c[k] = fpath + found = true + end + if v=="NOPATH" then + c[k] = false + found=true + end + end + + for i=#c,1,-1 do + if c[i]==false then + table.remove(c,i) + end + end + + if not c[1] then + Print"Missing command, assuming lua_send_self" + c[1] = 'lua_send_self' + end + + if not found then + table.insert(c,path_orig) + end + + local cmdd = {} + for k,v in next,c do + cmdd[k]=('%q'):format(tostring(v)) + end + Print("Watching '"..tostring(fpath).."': ",table.concat(cmdd," ")) + + local entry = { + path = fpath, + searchpath = searchpath, + time = time, + cmd = c, + } + + local watchlist = GetWatchList() + watchlist[#watchlist+1] = entry + +end) + + + + + + +COMMAND('send_self',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnSelf(content,who or CMD(who),MakeExtras(ply)) + +end) + + +if SERVER then return end + +net.Receive(Tag,function(...) _ReceivedData(...) end) + +function _ReceivedData(len) + + local script = ReadCompressed() + local decoded=net.ReadTable() + + local info=decoded.info + local extra=decoded.extra + + local ok,ret = Run(script,tostring(info),extra) + + if not ok then + ErrorNoHalt(tostring(ret)..'\n') + end + + --[[ -- Not done + if extra.retid then + net.Start(net_retdata) + net.WriteUInt(extra.retid,32) + net.WriteBool(ok) + net.WriteTable(ret) + net.SendToServer() + end --]] + +end + +function CheckStore(src) + if not ShouldStore() then return end + local crc = util.CRC(src) + local path = "luadev_hist/".. crc ..'.txt' + + if file.Exists(path,'DATA') then return end + if not file.IsDir("luadev_hist",'DATA') then file.CreateDir("luadev_hist",'DATA') end + + file.Write(path,tostring(src),'DATA') +end + +function ToServer(data) + if TransmitHook(data)~=nil then return end + + CheckStore(data.src) + + net.Start(Tag) + WriteCompressed(data.src or "") + + -- clear extra data + data.src = nil + if data.extra then + data.extra.ply = nil + if table.Count(data.extra)==0 then data.extra=nil end + end + + net.WriteTable(data) + if net.BytesWritten()==65536 then + Print("Unable to send lua code (too big)\n") + return nil,"Unable to send lua code (too big)" + end + + net.SendToServer() + return true +end + + +function RunOnClients(script,who,extra) + + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + src=script, + dst=TO_CLIENTS, + info=who, + extra=extra, + } + + return ToServer(data) + +end + + +function RunOnSelf(script,who,extra) + if not isstring(who) then who = nil end + if not who and extra and isentity(extra) then extra = {ply=extra} end + --if luadev_selftoself:GetBool() then + -- Run + --end + return RunOnClient(script,LocalPlayer(),who,extra) +end + +function RunOnClient(script,targets,who,extra) + -- compat + if not targets and isentity(who) then + targets=who + who = nil + end + + if extra and isentity(extra) and who==nil then extra={ply=extra} end + + if (not istable(targets) and !IsValid(targets)) + or (istable(targets) and table.Count(targets)==0) + then error"Invalid player(s)" end + + local data={ + src=script, + dst=TO_CLIENT, + dst_ply=targets, + info=who, + extra=extra, + } + + return ToServer(data) + +end + +function RunOnServer(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + src=script, + dst=TO_SERVER, + --dst_ply=pl + info=who, + extra=extra, + } + return ToServer(data) + +end + +function RunOnShared(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + src=script, + dst=TO_SHARED, + --dst_ply=pl + info=who, + extra=extra, + } + + return ToServer(data) + +end diff --git a/garrysmod/addons/luadev/lua/luadev/luadev_sh.lua b/garrysmod/addons/luadev/lua/luadev/luadev_sh.lua new file mode 100644 index 0000000..554b523 --- /dev/null +++ b/garrysmod/addons/luadev/lua/luadev/luadev_sh.lua @@ -0,0 +1,592 @@ +module("luadev",package.seeall) +Tag=_NAME..'1' + +--net_retdata = Tag..'_retdata' + +if SERVER then + util.AddNetworkString(Tag) + --util.AddNetworkString(net_retdata) +end + + +-- Enums + + local enums={ + TO_CLIENTS=1, + TO_CLIENT=2, + TO_SERVER=3, + TO_SHARED=4, + } + + local revenums={} -- lookup + _M.revenums=revenums + + for k,v in pairs(enums) do + _M[k]=v + revenums[v]=k + end + + STAGE_PREPROCESS=1 + STAGE_COMPILED=2 + STAGE_POST=3 + STAGE_PREPROCESSING=4 + +-- Figure out what to put to extra table + function MakeExtras(pl,extrat) + if pl and isentity(pl) and pl:IsPlayer() then + extrat = extrat or {} + extrat.ply = pl + end + return extrat + end + +-- Helpers + + function TransmitHook(stage,...) + return hook.Run("LuaDevTransmit",stage,...) + end + + function IsOneLiner(script) + return script and not script:find("\n",1,true) + end + + function GiveFileContent(fullpath,searchpath) + --Print("Reading: "..tostring(fullpath)) + if fullpath==nil or fullpath=="" then return false end + + local content=file.Read(fullpath,searchpath or "MOD") + if content==0 then return false end + return content + end + + function TableToString(tbl) + return string.Implode(" ",tbl) + end + + function Print(...) + if metalog then + metalog.info("Luadev", SERVER and "Server" or "Client", ...) + else + Msg("[Luadev"..(SERVER and ' Server' or '').."] ") + print(...) + end + end + + if CLIENT then + luadev_store = CreateClientConVar( "luadev_store", "1",true) + function ShouldStore() + return luadev_store:GetBool() + end + end + + if CLIENT then + luadev_verbose = CreateClientConVar( "luadev_verbose", "1",true) + else + luadev_verbose = CreateConVar( "luadev_verbose", "1", { FCVAR_NOTIFY ,FCVAR_ARCHIVE} ) + end + function Verbose(lev) + return (luadev_verbose:GetInt() or 99)>=(lev or 1) + end + + function PrintX(script,...) + local oneline = IsOneLiner(script) and 2 + local verb = Verbose(oneline) + if metalog then + metalog[verb and "info" or "debug"]("Luadev", SERVER and "Server" or "Client", ...) + else + local Msg=not verb and _Msg or Msg + local print=not verb and _print or print + Msg("[Luadev"..(SERVER and ' Server' or '').."] ") + print(...) + end + end + + specials = { + swep = { + function(val,extra,script,info) + local SWEP=weapons.GetStored(val) + if not SWEP then + SWEP = {Primary={}, Secondary={},Base = "weapon_base",ClassName = val, Folder = 'weapons/'..val } + end + _G.SWEP = SWEP + end, + function(val,extra,script,info) + local tbl = _G.SWEP + _G.SWEP = nil + if istable(tbl) then + --local table_ForEach=table.ForEach table.ForEach=function()end timer.Simple(0,function() table.ForEach=table_ForEach end) + if Verbose() then + Print("Registering weapon "..tostring(val)) + end + weapons.Register(tbl, val, true) + --table.ForEach=table_ForEach + end + end, + }, + sent = { + function(val,extra,script,info) + local ENT=scripted_ents.GetStored(val) + if ENT and ENT.t then + ENT=ENT.t + else + ENT = {ClassName=val , Folder = 'entities/'..val} + end + _G.ENT = ENT + end, + function(val,extra,script,info) + local tbl = _G.ENT + _G.ENT = nil + if istable(tbl) then + + tbl.Model = tbl.Model or Model("models/props_borealis/bluebarrel001.mdl") + if not tbl.Base then + tbl.Base = "base_anim" + tbl.Type = tbl.Type or "anim" + end + if Verbose() then + Print("Registering entity "..tostring(val)) + end + scripted_ents.Register(tbl, val) + end + end, + }, + stool = { + function(toolmode,extra,script,info) + local gmod_tool=weapons.GetStored("gmod_tool") + if gmod_tool and gmod_tool.Tool and gmod_tool.Tool[toolmode] then + _G.TOOL=gmod_tool.Tool[toolmode] + assert(_G.TOOL and _G.TOOL.Mode == toolmode) + else + + assert(ToolObj,"Need ToolObj from gamemode to create new tools") + + _G.TOOL = ToolObj:Create(toolmode) + _G.TOOL.Mode = toolmode + + end + + _G.TOOL = TOOL + end, + function(val,extra,script,info) + local tbl = _G.TOOL + _G.TOOL = nil + if not istable(tbl) then return end + + Print("Registering tool "..tostring(val)) + + if tbl.CreateConVars then + tbl:CreateConVars() + end + + local gmod_tool=weapons.GetStored("gmod_tool") + if _G.TOOL and gmod_tool and gmod_tool.Tool then + gmod_tool.Tool[val] = _G.TOOL + end + + + end, + }, + -- TODO -- + effect = { + function(val,extra,script,info) + if SERVER then return end + _G.EFFECT = {ClassName=val,Folder = 'effects/'..val } + end, + function(val,extra,script,info) + if Verbose() then + Print("Registering effect "..tostring(val)) + end + if CLIENT then + local tbl = _G.EFFECT _G.EFFECT = nil + if tbl then + effects.Register(_G.EFFECT,val) + end + end + end, + }, + } + local specials = specials + + + function ProcessSpecial(mode,script,info,extra) + + if not extra then return end + for special_type,funcs in next,specials do + local val = extra[special_type] + if val then + if Verbose(10) then + Print("ProcessSpecial",mode,special_type," -> ",val) + end + local func = funcs[mode] + if func then return func(val,extra,script,info) end + return + end + end + end + + function FindPlayer(plyid) + if not plyid or not isstring(plyid) then return end + + local cl + for k,v in pairs(player.GetHumans()) do + if v:SteamID()==plyid or tostring(v:AccountID())==plyid or tostring(v:UserID())==plyid then + cl=v + break + end + end + if not cl then + for k,v in pairs(player.GetAll()) do + if v:Name():lower():find(plyid:lower(),1,true)==1 then + cl=v + break + end + end + end + if not cl then + for k,v in pairs(player.GetAll()) do + if string.find(v:Name(),plyid) then + cl=v + break + end + end + end + if not cl then + for k,v in pairs(player.GetAll()) do + if v:Name():lower():find(plyid:lower(),1,true) then + cl=v + break + end + end + end + if not cl and easylua and easylua.FindEntity then + cl = easylua.FindEntity(plyid) + end + return IsValid(cl) and cl:IsPlayer() and cl or nil + end + + +-- Watch system + + function FileTime(fullpath,searchpath) + --Print("Reading: "..tostring(fullpath)) + if fullpath==nil or fullpath=="" then return false end + + local t=file.Time(fullpath,searchpath or "MOD") + + if not t or t==0 then return false end + + return t + end + + local watchlist = rawget(_M,"GetWatchList") and GetWatchList() or {} function GetWatchList() return watchlist end + local i=0 + hook.Add("Think",Tag.."_watchlist",function() + if not watchlist[1] then return end + + i=i+1 + local entry = watchlist[i] + if not entry then + i=0 + entry = watchlist[1] + if not entry then return end + end + + local newtime = FileTime(entry.path,entry.searchpath) + local oldtime = entry.time + if newtime and newtime~=oldtime then + + entry.time = newtime + + Msg"[LuaDev] Refresh " print(unpack(entry.cmd)) + + RunConsoleCommand(unpack(entry.cmd)) + + end + + end) + +-- compression + + function Compress( data ) + return util.Compress( data ) + end + + function Decompress(data) + return util.Decompress( data ) + end + + function WriteCompressed(data) + if #data==0 then + net.WriteUInt( 0, 24 ) + return false + end + + local compressed = Compress( data ) + local len = compressed:len() + net.WriteUInt( len, 24 ) + net.WriteData( compressed, len ) + return compressed + end + + function ReadCompressed() + local len = net.ReadUInt( 24 ) + if len==0 then return "" end + + return Decompress( net.ReadData( len ) ) + end + +-- Compiler / runner +local function ValidCode(src,who) + local ret = CompileString(src,who or "",false) + if type(ret)=='string' then + return nil,ret + end + return ret or true +end +_M.ValidScript=ValidCode +_M.ValidCode=ValidCode + +function ProcessHook(stage,...) + return hook.Run("LuaDevProcess",stage,...) +end +local LuaDevProcess=ProcessHook + +local LUADEV_EXECUTE_STRING=RunStringEx +local LUADEV_EXECUTE_FUNCTION=xpcall +local LUADEV_COMPILE_STRING=CompileString +local mt= { + __tostring=function(self) return self[1] end, + + __index={ + set=function(self,what) self[1]=what end, + get=function(self,what) return self[1] end, + }, + --__newindex=function(self,what) rawset(self,1,what) end, +} +local strobj=setmetatable({""},mt) + +function Run(script,info,extra) + --compat + if CLIENT and not extra and info and istable(info) then + return luadev.RunOnSelf(script,"COMPAT",{ply=info.ply}) + end + + info = info or "??ANONYMOUS??" + if not isstring(info) then + debug.Trace() + ErrorNoHalt("LuaDev Warning: info type mismatch: "..type(info)..': '..tostring(info)) + end + + -- STAGE_PREPROCESS + local ret,newinfo = LuaDevProcess(STAGE_PREPROCESS,script,info,extra,nil) + + if ret == false then return end + if ret ~=nil and ret~=true then script = ret end + + if newinfo then info = newinfo end + + -- STAGE_PREPROCESSING + rawset(strobj,1,script) + local ret = LuaDevProcess(STAGE_PREPROCESSING,strobj,info,extra,nil) + script = rawget(strobj,1) + + if not script then + return false,"no script" + end + + -- Compiling + + local func = LUADEV_COMPILE_STRING(script,tostring(info),false) + if not func or isstring( func ) then compileerr = func or true func = false end + + local ret = LuaDevProcess(STAGE_COMPILED,script,info,extra,func) + -- replace function + if ret == false then return end + if ret ~=nil and isfunction(ret) then + func = ret + compileerr = false + end + + if not func then + if compileerr then + return false,"Syntax error: "..tostring(compileerr) + end + end + + lastextra = extra + lastinfo = info + lastscript = script + lastfunc = func + + ProcessSpecial(1,script,info,extra) + + local args = extra and extra.args and (istable(extra.args) and extra.args or {extra.args}) + if not args then args=nil end + + + -- Run the stuff + -- because garry's runstring has social engineer sexploits and such + local errormessage + local function LUADEV_TRACEBACK(errmsg) + errormessage = errmsg + local tracestr = debug.traceback(errmsg,2) + + -- Tidy up the damn long trace + local p1=tracestr:find("LUADEV_EXECUTE_FUNCTION",1,true) + if p1 then + local p2=0 + while p2 and p2p1 then + tracestr=tracestr:sub(1,new) + break + end + p2=new + end + end + + ErrorNoHalt('[ERROR] '..tracestr )-- ..'\n') + end + + local LUADEV_EXECUTE_FUNCTION=xpcall + local returnvals = {LUADEV_EXECUTE_FUNCTION(func,LUADEV_TRACEBACK,args and unpack(args) or nil)} + local ok = returnvals[1] table.remove(returnvals,1) + + -- STAGE_POST + local ret = LuaDevProcess(STAGE_POST,script,info,extra,func,args,ok,returnvals) + ProcessSpecial(2,script,info,extra) + + if not ok then + return false,errormessage + end + + return ok,returnvals +end + + +function RealFilePath(name) + local searchpath = "MOD" + + local RelativePath='lua/'..name + + if name:find("^lua/") then -- search cache + name=name:gsub("^lua/","") + RelativePath=name + searchpath = "LUA" + elseif name:find("^%.%./") then -- whole shit + name=name:gsub("^%.%./","") + RelativePath=name + elseif name:find("^data/") then -- whatever + name=name:gsub("^data/","") + RelativePath='data/'..name + end + + if not file.Exists(RelativePath,searchpath) then return nil end + return RelativePath,searchpath +end + + +function AutoComplete(cmd,commandName,args) + + local name = string.Explode(' ',args) + + name=name[#name] or "" + + local path = string.GetPathFromFilename(name) + + local searchpath = "MOD" + + local RelativePath='lua/'..(name or "") + + if name:find("^lua/") then -- search cache + name=name:gsub("^lua/","") + RelativePath=name + searchpath = "LUA" + elseif name:find("^%.%./") then -- whole shit + name=name:gsub("^%.%./","") + RelativePath=name + elseif name:find("^data/") then -- whatever + name=name:gsub("^data/","") + RelativePath='data/'..name + end + + local searchstr = RelativePath.."*" + + local files,folders=file.Find(searchstr,searchpath or "MOD") + files=files or {} + -- Filter out any files that don't end in ".lua". + for i = #files, 1, -1 do + if not string.match(files[i], "%.lua$") then + table.remove(files, i) + end + end + folders=folders or {} + for k,v in pairs(folders) do + table.insert(files,v) + end + local candidates=files + candidates=candidates or {} + for i,_ in pairs(candidates) do + candidates[i]=commandName.." "..path..candidates[i] + end + + return candidates + +end + +local sv_allowcslua = GetConVar 'sv_allowcslua' + +function CanLuaDev(ply,script,command,target,target_ply,extra) + if SERVER and not ply:IsFullyAuthenticated() then + return false, "Your SteamID wasn't fully authenticated, try restarting Steam." + end + local ret,x = hook.Run("CanLuaDev",ply,script,command,target,target_ply,extra) + if ret~=nil then return ret,x end + local ret,x = hook.Run("LuaDevIsPlayerAllowed", ply, script or "") + if ret~=nil then return ret,x end + if ply:IsSuperAdmin() then return true end + if target == TO_CLIENT and + (target_ply == ply + or (target_ply + and istable(target_ply) + and target_ply[1]==ply + and table.Count(target_ply)==1)) + then + if sv_allowcslua:GetBool() then return true end + end +end +local luadev_show_access_attempt = SERVER and CreateConVar("luadev_show_access_attempt", '1', {FCVAR_ARCHIVE}) + +function RejectCommand(pl, msg) + if msg == true or msg == "" then return end -- suppress error in case we want to process luadev command ourselves in a hook + + if SERVER and luadev_show_access_attempt:GetBool() and not pl.luadevaccessfail then + pl.luadevaccessfail = true + Msg"[LuaDev] " print(pl, "was rejected luadev access", msg) + end + + S2C(pl, "No Access" .. (msg and (": " .. tostring(msg)) or "")) +end +function COMMAND(str,func,complete) + if SERVER then + concommand.Add('lua_'..str,function(pl,command,cmds,strcmd) + local id=pl + if IsValid(pl) then + local ok,err = CanLuaDev(pl,strcmd,command,nil,nil,nil) + if not ok then + return RejectCommand (pl,err or command) + end + id = GetPlayerIdentifier(pl,str) or pl + else + pl = "Console" + id = pl + end + func(pl,cmds,strcmd,id) + end) + else + concommand.Add('lua_'..str,function(_,_,cmds,strcmd) + func(pl,cmds,strcmd,str) + end,(not complete and function(...) return AutoComplete(str,...) end) or nil) + end +end diff --git a/garrysmod/addons/luadev/lua/luadev/luadev_sv.lua b/garrysmod/addons/luadev/lua/luadev/luadev_sv.lua new file mode 100644 index 0000000..3c36dd1 --- /dev/null +++ b/garrysmod/addons/luadev/lua/luadev/luadev_sv.lua @@ -0,0 +1,187 @@ +module("luadev",package.seeall) + + +-- inform the client of the version +_luadev_version = CreateConVar( "_luadev_version", "1.6", FCVAR_NOTIFY ) + +function S2C(cl,msg) + if cl and cl:IsValid() and cl:IsPlayer() then + cl:ChatPrint("[LuaDev] "..tostring(msg)) + end +end + +function RunOnClients(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + --src=script, + info=who, + extra=extra, + } + + if Verbose() then + PrintX(script,tostring(who).." running on clients") + end + + net.Start(Tag) + WriteCompressed(script) + net.WriteTable(data) + if net.BytesWritten()==65536 then + return nil,"too big" + end + net.Broadcast() + + return true +end + +local function ClearTargets(targets) + local i=1 + local target=targets[i] + while target do + if not IsValid(target) then + table.remove(targets,i) + i=i-1 + end + i=i+1 + target=targets[i] + end +end + + +function RunOnClient(script,targets,who,extra) + -- compat + if not targets and isentity(who) then + targets=who + who = nil + end + + if extra and isentity(extra) and who==nil then + extra={ply=extra} + who="COMPAT" + end + + local data={ + --src=script, + info=who, + extra=extra, + } + + if not istable(targets) then + targets = {targets} + end + + ClearTargets(targets) + + if table.Count(targets)==0 then return nil,"no players" end + + local targetslist + for _,target in pairs(targets) do + local pre = targetslist and ", " or "" + targetslist=(targetslist or "")..pre..tostring(target) + end + + + if Verbose() then + if type(who) == "string" and #who > 50 then + who = who:sub(1,50).."...>" + end + PrintX(script,tostring(who).." running on "..tostring(targetslist or "NONE")) + end + + net.Start(Tag) + WriteCompressed(script) + net.WriteTable(data) + if net.BytesWritten()==65536 then + return nil,"too big" + end + net.Send(targets) + + return #targets +end + +function RunOnServer(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + if Verbose() then + PrintX(script,tostring(who).." running on server") + end + + return Run(script,tostring(who),extra) +end + +function RunOnSelf(script,who,extra) + if not isstring(who) then who = nil end + if not who and extra and isentity(extra) then extra = {ply=extra} end + + return RunOnServer(script,who,extra) +end + + +function RunOnShared(...) + RunOnClients(...) + return RunOnServer(...) +end + + +function GetPlayerIdentifier(ply,extrainfo) + if type(ply)=="Player" then + + local info=ply:Name() + + if Verbose(3) then + local sid=ply:SteamID():gsub("^STEAM_","") + info=('<%s|%s>'):format(sid,info:sub(1,24)) + elseif Verbose(2) then + info=ply:SteamID():gsub("^STEAM_","") + end + if extrainfo then + info=('%s<%s>'):format(info,tostring(extrainfo)) + end + + info = info:gsub("%]","}"):gsub("%[","{"):gsub("%z","_") -- GMod bug + + return info + else + return "??"..tostring(ply) + end +end + +function _ReceivedData(len, ply) + + local script = ReadCompressed() -- WriteCompressed(data) + local decoded=net.ReadTable() + decoded.src=script + + + local target=decoded.dst + local info = decoded.info + local target_ply=decoded.dst_ply + local extra=decoded.extra or {} + if not istable(extra) then + return RejectCommand(ply,"bad extra table") + end + extra.ply=ply + + local can, msg = CanLuaDev(ply,script,nil,target,target_ply,extra) + if not can then + return RejectCommand(ply,msg) + end + + if TransmitHook(data)~=nil then return end + + local identifier = GetPlayerIdentifier(ply,info) + local ok,err + if target==TO_SERVER then ok,err=RunOnServer (script, identifier,extra) + elseif target==TO_CLIENT then ok,err=RunOnClient (script,target_ply, identifier,extra) + elseif target==TO_CLIENTS then ok,err=RunOnClients(script, identifier,extra) + elseif target==TO_SHARED then ok,err=RunOnShared (script, identifier,extra) + else S2C(ply,"Unknown target") + end + + -- no callback system yet + if not ok then + ErrorNoHalt(tostring(err)..'\n') + end + +end +net.Receive(Tag, function(...) _ReceivedData(...) end) diff --git a/garrysmod/addons/luadev/lua/luadev/socketdev.lua b/garrysmod/addons/luadev/lua/luadev/socketdev.lua new file mode 100644 index 0000000..57e1ad6 --- /dev/null +++ b/garrysmod/addons/luadev/lua/luadev/socketdev.lua @@ -0,0 +1,146 @@ +-- luacheck: globals luadev socket easylua chatbox + +local function requireExists(moduleName) + local osSuffix = assert( + (system.IsWindows() and (jit.arch~="x64" and "win32" or "win64")) + or (system.IsLinux() and "linux") + or (system.IsOSX() and "osx"), + "couldn't determine system type?" + ) + local dllFiles = file.Find(string.format("lua/bin/gmcl_%s_%s.dll", moduleName, osSuffix), "GAME") + local luaFileExists = file.Exists(string.format("includes/modules/%s.lua", moduleName), "LCL") + + return #dllFiles > 0 or luaFileExists +end + +local function luadevPrint(...) + Msg"[LuaDev] " + print(...) +end + +local moduleLoaded = false +for _, moduleName in ipairs({ "socket", "luasocket" }) do + if requireExists(moduleName) then + local ok, err = pcall(require, moduleName) + if not ok then + luadevPrint( + string.format("Unable to load module %s: %s", moduleName, err) + ) + else + if not socket then + luadevPrint(string.format("_G.socket not found, but module %s loaded?", moduleName)) + else + moduleLoaded = true + break + end + end + end +end + +if not moduleLoaded then + luadevPrint("No socket module found") + return +end + + +local methods = { + self = function(sock) + local who = sock:receive("*l") + luadev.RunOnSelf(sock:receive("*a"), who) + system.FlashWindow() + end, + sv = function(sock) + local who = sock:receive("*l") + luadev.RunOnServer(sock:receive("*a"), who) + system.FlashWindow() + end, + sh = function(sock) + local who = sock:receive("*l") + luadev.RunOnShared(sock:receive("*a"), who) + system.FlashWindow() + end, + cl = function(sock) + local who = sock:receive("*l") + luadev.RunOnClients(sock:receive("*a"), who) + system.FlashWindow() + end, + ent = function(sock) + local who = sock:receive("*l") + local contents = string.format("ENT = {}; local ENT=ENT; %s; scripted_ents.Register(ENT, '%s')", sock:receive("*a"), who:sub(0, -5)) + luadev.RunOnShared(contents, who) + system.FlashWindow() + end, + wep = function(sock) + local who = sock:receive("*l") + local contents = string.format("SWEP = {}; local SWEP=SWEP; %s; weapons.Register(SWEP, '%s')", sock:receive("*a"), who:sub(0, -5)) + luadev.RunOnShared(contents, who) + system.FlashWindow() + end, + client = function(sock) + local who = sock:receive("*l") + local to = sock:receive("*l") + to = easylua and easylua.FindEntity(to) or player.GetByID(tonumber(to)) + to = { to } + luadev.RunOnClient(sock:receive("*a"), to, who) + system.FlashWindow() + end, + chatTextChanged = function(sock) + local contents = sock:receive("*a") + if not contents then return end + + if chatbox then + chatbox.StartChat_override = true + end + hook.Run("StartChat") + if chatbox then + chatbox.StartChat_override = false + end + + hook.Run("ChatTextChanged", contents, true) + end, + finishChat = function(sock) + hook.Run("FinishChat") + end, + requestPlayers = function(sock) + local plys = {} + for _, ply in next, player.GetAll() do + table.insert(plys, ply:Nick()) + end + + sock:send(table.concat(plys, "\n")) + end +} + +local sock = assert(socket.tcp()) +assert(sock:bind("127.0.0.1", 27099)) +sock:settimeout(0) +sock:setoption("reuseaddr", true) +assert(sock:listen(0)) + +hook.Add("Think", "LuaDev-Socket", function() + local cl = sock:accept() + if not cl then return end + + if cl:getpeername() ~= "127.0.0.1" then + luadevPrint("Refused", cl:getpeername()) + cl:shutdown() + return + end + + cl:settimeout(0) + + local protocol = cl:receive("*l") + local method + + if protocol == "extension" then + method = cl:receive("*l") + else + method = protocol + end + + if method and methods[method] then + methods[method](cl) + end + + cl:shutdown() +end) diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvsaienabler.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvsaienabler.lua new file mode 100644 index 0000000..6b34790 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvsaienabler.lua @@ -0,0 +1,60 @@ + +TOOL.Category = "LVS" +TOOL.Name = "#AI Enabler" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "team" ] = "-1" + +if CLIENT then + language.Add( "tool.lvsaienabler.name", "AI Enabler" ) + language.Add( "tool.lvsaienabler.desc", "A tool used to enable/disable AI on LVS-Vehicles" ) + language.Add( "tool.lvsaienabler.0", "Left click on a LVS-Vehicle to enable AI, Right click to disable." ) + language.Add( "tool.lvsaienabler.1", "Left click on a LVS-Vehicle to enable AI, Right click to disable." ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + + if not IsValid( ent ) then return false end + + if not ent.LVS and not ent.LFS then return end + + if isfunction( ent.SetAI ) then + ent:SetAI( true ) + end + + if SERVER then + local Team = self:GetClientNumber( "team" ) + + if Team ~= -1 then + ent:SetAITEAM( math.Clamp( Team, 0, 3 ) ) + end + end + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + + if not IsValid( ent ) then return false end + + if not ent.LVS and not ent.LFS then return end + + if isfunction( ent.SetAI ) then + ent:SetAI( false ) + end + + return true +end + +function TOOL:Reload( trace ) + return false +end + +function TOOL.BuildCPanel( CPanel ) + CPanel:AddControl( "Header", { Text = "#tool.lvsaienabler.name", Description = "#tool.lvsaienabler.desc" } ) + + CPanel:AddControl( "Slider", { Label = "TeamOverride", Type = "Int", Min = -1, Max = 3, Command = "lvsaienabler_team" } ) +end diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscarengineswap.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscarengineswap.lua new file mode 100644 index 0000000..ea66077 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscarengineswap.lua @@ -0,0 +1,98 @@ + + +TOOL.Category = "LVS" +TOOL.Name = "#Engine Swap" + +TOOL.Information = { + { name = "left" }, + { name = "right" }, +} + +if CLIENT then + language.Add( "tool.lvscarengineswap.name", "Engine Swap" ) + language.Add( "tool.lvscarengineswap.desc", "A tool used to swap engine sounds on [LVS] - Cars" ) + language.Add( "tool.lvscarengineswap.left", "Apply Engine Sound" ) + language.Add( "tool.lvscarengineswap.right", "Copy Engine Sound" ) +end + + +local function SwapEngine( ent, data ) + if not IsValid( ent ) or not istable( data ) then return end + + local originalEngine = ent:GetEngine() + + if not IsValid( originalEngine ) then return end + + local Engine = ents.Create( "lvs_wheeldrive_engine_swapped" ) + Engine:SetPos( originalEngine:GetPos() ) + Engine:SetAngles( originalEngine:GetAngles() ) + Engine:Spawn() + Engine:Activate() + Engine:SetParent( ent ) + Engine:SetBase( ent ) + Engine.EngineSounds = data + Engine:SetDoorHandler( originalEngine:GetDoorHandler() ) + + ent:SetEngine( Engine ) + + ent:DeleteOnRemove( Engine ) + + ent:TransferCPPI( Engine ) + + originalEngine:Remove() + + if not duplicator or not duplicator.StoreEntityModifier then return end + + duplicator.StoreEntityModifier( ent, "lvsCarSwapEngine", data ) +end + +local function DuplicatorSwapEngine( ply, ent, data ) + timer.Simple(0.1, function() + if not IsValid( ent ) then return end + + SwapEngine( ent, data ) + end ) +end +if duplicator and duplicator.RegisterEntityModifier then + duplicator.RegisterEntityModifier( "lvsCarSwapEngine", DuplicatorSwapEngine ) +end + +function TOOL:SwapEngine( ent ) + if CLIENT then return end + + SwapEngine( ent, self.EngineSounds ) +end + +function TOOL:IsValidTarget( ent ) + if not IsValid( ent ) then return false end + + if not ent.LVS or not ent.lvsAllowEngineTool then return false end + + return true +end + +function TOOL:LeftClick( trace ) + if not self.EngineSounds then return false end + + local ent = trace.Entity + + if not self:IsValidTarget( ent ) then return false end + + self:SwapEngine( ent ) + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + + if not self:IsValidTarget( ent ) then return false end + + self.EngineSounds = ent.EngineSounds + + return true +end + +function TOOL:Reload( trace ) + return false +end diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscartuningremover.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscartuningremover.lua new file mode 100644 index 0000000..a556b00 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscartuningremover.lua @@ -0,0 +1,83 @@ + + +TOOL.Category = "LVS" +TOOL.Name = "#Tuning Remover" + +TOOL.Information = { + { name = "left" }, + { name = "right" }, +} + +if CLIENT then + language.Add( "tool.lvscartuningremover.name", "Tuning Remover" ) + language.Add( "tool.lvscartuningremover.desc", "A tool used to remove Turbo + Compressor on [LVS-Cars]" ) + language.Add( "tool.lvscartuningremover.left", "Remove Turbo" ) + language.Add( "tool.lvscartuningremover.right", "Remove Compressor" ) +end + +function TOOL:IsValidTarget( ent ) + if not IsValid( ent ) then return false end + + if not ent.LVS or not ent.GetCompressor or not ent.GetTurbo then return false end + + return true +end + +local function DoRemoveEntity( ent ) + timer.Simple( 1, function() if ( IsValid( ent ) ) then ent:Remove() end end ) + + ent:SetNotSolid( true ) + ent:SetMoveType( MOVETYPE_NONE ) + ent:SetNoDraw( true ) + + local ed = EffectData() + ed:SetOrigin( ent:GetPos() ) + ed:SetEntity( ent ) + util.Effect( "entity_remove", ed, true, true ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + + if not self:IsValidTarget( ent ) then return false end + + local Turbo = ent:GetTurbo() + local Compressor = ent:GetCompressor() + + local Removed = false + + if IsValid( Turbo ) and not Turbo._RemoveRememberThis then + Turbo._RemoveRememberThis = true + + if SERVER then DoRemoveEntity( Turbo ) end + + Removed = true + end + + return Removed +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + + if not self:IsValidTarget( ent ) then return false end + + local Turbo = ent:GetTurbo() + local Compressor = ent:GetCompressor() + + local Removed = false + + if IsValid( Compressor ) and not Compressor._RemoveRememberThis then + Compressor._RemoveRememberThis = true + + if SERVER then DoRemoveEntity( Compressor ) end + + Removed = true + end + + return Removed +end + +function TOOL:Reload( trace ) + return false +end diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscarwheelchanger.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscarwheelchanger.lua new file mode 100644 index 0000000..2faaab6 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvscarwheelchanger.lua @@ -0,0 +1,578 @@ + + +TOOL.Category = "LVS" +TOOL.Name = "#Wheel Editor" + +TOOL.ClientConVar[ "model" ] = "models/props_vehicles/carparts_wheel01a.mdl" +TOOL.ClientConVar[ "camber" ] = 0 +TOOL.ClientConVar[ "caster" ] = 0 +TOOL.ClientConVar[ "toe" ] = 0 +TOOL.ClientConVar[ "height" ] = 0 +TOOL.ClientConVar[ "stiffness" ] = 0 +TOOL.ClientConVar[ "skin" ] = 0 +TOOL.ClientConVar[ "bodygroup0" ] = 0 +TOOL.ClientConVar[ "bodygroup1" ] = 0 +TOOL.ClientConVar[ "bodygroup2" ] = 0 +TOOL.ClientConVar[ "bodygroup3" ] = 0 +TOOL.ClientConVar[ "bodygroup4" ] = 0 +TOOL.ClientConVar[ "bodygroup5" ] = 0 +TOOL.ClientConVar[ "bodygroup6" ] = 0 +TOOL.ClientConVar[ "bodygroup7" ] = 0 +TOOL.ClientConVar[ "bodygroup8" ] = 0 +TOOL.ClientConVar[ "bodygroup9" ] = 0 +TOOL.ClientConVar[ "pp0" ] = 0 +TOOL.ClientConVar[ "pp1" ] = 0 +TOOL.ClientConVar[ "pp2" ] = 0 +TOOL.ClientConVar[ "pp3" ] = 0 +TOOL.ClientConVar[ "pp4" ] = 0 +TOOL.ClientConVar[ "pp5" ] = 0 +TOOL.ClientConVar[ "pp6" ] = 0 +TOOL.ClientConVar[ "pp7" ] = 0 +TOOL.ClientConVar[ "pp8" ] = 0 +TOOL.ClientConVar[ "pp9" ] = 0 +TOOL.ClientConVar[ "r" ] = 255 +TOOL.ClientConVar[ "g" ] = 255 +TOOL.ClientConVar[ "b" ] = 255 +TOOL.ClientConVar[ "a" ] = 255 + +TOOL.Information = { + { name = "left" }, + { name = "right" }, + { name = "reload" } +} + +if CLIENT then + language.Add( "tool.lvscarwheelchanger.name", "Wheel Editor" ) + language.Add( "tool.lvscarwheelchanger.desc", "A tool used to edit [LVS-Cars] Wheels" ) + language.Add( "tool.lvscarwheelchanger.left", "Apply wheel. Click again to flip 180 degrees" ) + language.Add( "tool.lvscarwheelchanger.right", "Copy wheel" ) + language.Add( "tool.lvscarwheelchanger.reload", "Apply camber/caster/toe/height/stiffness. Click again to flip camber and toe" ) + + local ContextMenuPanel + + local skins = 0 + local bodygroups = {} + local poseparameters = {} + + local ConVarsDefault = TOOL:BuildConVarList() + local function BuildContextMenu() + if not IsValid( ContextMenuPanel ) then return end + + ContextMenuPanel:Clear() + + if IsValid( ContextMenuPanel.modelpanel ) then + ContextMenuPanel.modelpanel:Remove() + end + + ContextMenuPanel:AddControl( "Header", { Text = "#tool.lvscarwheelchanger.name", Description = "#tool.lvscarwheelchanger.desc" } ) + ContextMenuPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "lvswheels", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + ContextMenuPanel:ColorPicker( "Wheel Color", "lvscarwheelchanger_r", "lvscarwheelchanger_g", "lvscarwheelchanger_b", "lvscarwheelchanger_a" ) + + if skins > 0 then + ContextMenuPanel:AddControl( "Label", { Text = "" } ) + ContextMenuPanel:AddControl( "Label", { Text = "Skins" } ) + ContextMenuPanel:AddControl("Slider", { Label = "Skin", Type = "int", Min = "0", Max = tostring( skins ), Command = "lvscarwheelchanger_skin" } ) + end + + local icon = vgui.Create( "DModelPanel", ContextMenuPanel ) + icon:SetSize(200,200) + icon:SetFOV( 30 ) + icon:SetAnimated( true ) + icon:SetModel( GetConVar( "lvscarwheelchanger_model" ):GetString() ) + icon:Dock( TOP ) + icon:SetCamPos( Vector(80,0,0) ) + icon:SetLookAt( vector_origin ) + icon.Angles = angle_zero + function icon:DragMousePress() + self.PressX, self.PressY = gui.MousePos() + self.Pressed = true + end + function icon:DragMouseRelease() + self.Pressed = false + end + function icon:LayoutEntity( ent ) + if self.Pressed then + local mx, my = gui.MousePos() + + self.Angles:RotateAroundAxis( Vector(0,0,-1), ((self.PressX or mx) - mx) / 2 ) + self.Angles:RotateAroundAxis( Vector(0,-1,0), ((self.PressY or my) - my) / 2 ) + + self.PressX, self.PressY = gui.MousePos() + end + + ent:SetSkin( GetConVar( "lvscarwheelchanger_skin" ):GetInt() ) + + local R = GetConVar( "lvscarwheelchanger_r" ):GetInt() + local G = GetConVar( "lvscarwheelchanger_g" ):GetInt() + local B = GetConVar( "lvscarwheelchanger_b" ):GetInt() + local A = GetConVar( "lvscarwheelchanger_a" ):GetInt() + self:SetColor( Color(R,G,B,A) ) + + ent:SetAngles( self.Angles ) + + for id = 0, 9 do + ent:SetBodygroup( id, GetConVar( "lvscarwheelchanger_bodygroup"..id ):GetInt() ) + end + + for id, data in pairs( poseparameters ) do + if id > 9 then break end + + if data.name == "#scale" then + local bonescale = math.Clamp( GetConVar( "lvscarwheelchanger_pp"..id ):GetFloat(), data.min, data.max ) + local num = ent:GetBoneCount() - 1 + + for boneid = 0, num do + local bonename = ent:GetBoneName( boneid ) + + if not bonename or bonename == "__INVALIDBONE__" or not string.StartsWith( bonename, "#" ) then continue end + + ent:ManipulateBoneScale( boneid, Vector(bonescale,bonescale,1) ) + end + + continue + end + + ent:SetPoseParameter( data.name, GetConVar( "lvscarwheelchanger_pp"..id ):GetFloat() ) + end + end + ContextMenuPanel.modelpanel = icon + + if table.Count( poseparameters ) > 0 then + ContextMenuPanel:AddControl( "Label", { Text = "" } ) + ContextMenuPanel:AddControl( "Label", { Text = "PoseParameters" } ) + + for id, data in pairs( poseparameters ) do + ContextMenuPanel:AddControl("Slider", { Label = data.name, Type = "float", Min = tostring( data.min ), Max = tostring( data.max ), Command = "lvscarwheelchanger_pp"..id } ) + end + end + + if #bodygroups > 0 then + ContextMenuPanel:AddControl( "Label", { Text = "" } ) + ContextMenuPanel:AddControl( "Label", { Text = "BodyGroup" } ) + + for group, data in pairs( bodygroups ) do + local maxvalue = tostring( data.submodels ) + + if maxvalue == "0" then continue end + + ContextMenuPanel:AddControl("Slider", { Label = data.name, Type = "int", Min = "0", Max = maxvalue, Command = "lvscarwheelchanger_bodygroup"..group } ) + end + end + + ContextMenuPanel:AddControl( "Label", { Text = "" } ) + ContextMenuPanel:AddControl( "Label", { Text = "Alignment Specs" } ) + ContextMenuPanel:AddControl( "Label", { Text = "- Wheel" } ) + ContextMenuPanel:AddControl("Slider", { Label = "Camber", Type = "float", Min = "-15", Max = "15", Command = "lvscarwheelchanger_camber" } ) + ContextMenuPanel:AddControl("Slider", { Label = "Caster", Type = "float", Min = "-15", Max = "15", Command = "lvscarwheelchanger_caster" } ) + ContextMenuPanel:AddControl("Slider", { Label = "Toe", Type = "float", Min = "-30", Max = "30", Command = "lvscarwheelchanger_toe" } ) + ContextMenuPanel:AddControl( "Label", { Text = "- Suspension" } ) + ContextMenuPanel:AddControl("Slider", { Label = "Height", Type = "float", Min = "-1", Max = "1", Command = "lvscarwheelchanger_height" } ) + ContextMenuPanel:AddControl("Slider", { Label = "Stiffness", Type = "float", Min = "-1", Max = "1", Command = "lvscarwheelchanger_stiffness" } ) + + -- purpose: avoid bullshit concommand system and avoid players abusing it + for mdl, _ in pairs( list.Get( "lvs_wheels" ) or {} ) do + list.Set( "lvs_wheels_selection", mdl, {} ) + end + ContextMenuPanel:AddControl( "Label", { Text = "" } ) + ContextMenuPanel:AddControl( "Label", { Text = "Wheel Models" } ) + ContextMenuPanel:AddControl( "PropSelect", { Label = "", ConVar = "lvscarwheelchanger_model", Height = 0, Models = list.Get( "lvs_wheels_selection" ) } ) + end + + local function SetModel( name ) + local ModelInfo = util.GetModelInfo( name ) + + if ModelInfo and ModelInfo.SkinCount then + skins = ModelInfo.SkinCount - 1 + else + skins = 0 + end + + local bgroupmdl = ents.CreateClientProp() + bgroupmdl:SetModel( name ) + bgroupmdl:Spawn() + + table.Empty( bodygroups ) + table.Empty( poseparameters ) + + for _, bgroup in pairs( bgroupmdl:GetBodyGroups() ) do + bodygroups[ bgroup.id ] = { + name = bgroup.name, + submodels = #bgroup.submodels, + } + end + + local num = bgroupmdl:GetNumPoseParameters() + + if num > 0 then + for i = 0, num - 1 do + local min, max = bgroupmdl:GetPoseParameterRange( i ) + + local name = bgroupmdl:GetPoseParameterName( i ) + + local pp_cvar = GetConVar( "lvscarwheelchanger_pp"..i ) + if name == "#scale" and pp_cvar then + local val = pp_cvar:GetFloat() + + if val > max or val < min then + pp_cvar:SetFloat( math.Clamp( 1, min, max ) ) + end + end + + poseparameters[ i ] = { + name = name, + min = min, + max = max, + } + end + end + + bgroupmdl:Remove() + + BuildContextMenu() + end + + function TOOL.BuildCPanel( panel ) + ContextMenuPanel = panel + + BuildContextMenu() + end + + cvars.AddChangeCallback( "lvscarwheelchanger_model", function( convar, oldValue, newValue ) + SetModel( newValue ) + end) +end + +local function DuplicatorSaveCarWheels( ent ) + if CLIENT then return end + + local base = ent:GetBase() + + if not IsValid( base ) then return end + + local data = {} + + for id, wheel in pairs( base:GetWheels() ) do + if not IsValid( wheel ) then continue end + + local wheeldata = {} + wheeldata.ID = id + wheeldata.Model = wheel:GetModel() + wheeldata.ModelScale = wheel:GetModelScale() + wheeldata.Skin = wheel:GetSkin() + wheeldata.Camber = wheel:GetCamber() + wheeldata.Caster = wheel:GetCaster() + wheeldata.Toe = wheel:GetToe() + wheeldata.Height = wheel:GetSuspensionHeight() + wheeldata.Stiffness = wheel:GetSuspensionStiffness() + wheeldata.AlignmentAngle = wheel:GetAlignmentAngle() + wheeldata.Color = wheel:GetColor() + + wheeldata.BodyGroups = {} + for id = 0, 9 do + wheeldata.BodyGroups[ id ] = wheel:GetBodygroup( id ) + end + + wheeldata.PoseParameters = {} + for id = 0, 9 do + wheeldata.PoseParameters[ id ] = wheel:GetPoseParameter( wheel:GetPoseParameterName( id ) ) + end + + table.insert( data, wheeldata ) + end + + if not duplicator or not duplicator.StoreEntityModifier then return end + + duplicator.StoreEntityModifier( base, "lvsCarWheels", data ) +end + +local function DuplicatorApplyCarWheels( ply, ent, data ) + if CLIENT then return end + + timer.Simple(0.1, function() + if not IsValid( ent ) then return end + + for id, wheel in pairs( ent:GetWheels() ) do + for _, wheeldata in pairs( data ) do + if not wheeldata or wheeldata.ID ~= id then continue end + + if wheeldata.Model then wheel:SetModel( wheeldata.Model ) end + if wheeldata.ModelScale then wheel:SetModelScale( wheeldata.ModelScale ) end + if wheeldata.Skin then wheel:SetSkin( wheeldata.Skin ) end + if wheeldata.Camber then wheel:SetCamber( wheeldata.Camber ) end + if wheeldata.Caster then wheel:SetCaster( wheeldata.Caster ) end + if wheeldata.Toe then wheel:SetToe( wheeldata.Toe ) end + if wheeldata.AlignmentAngle then wheel:SetAlignmentAngle( wheeldata.AlignmentAngle ) end + if wheeldata.Color then wheel:SetColor( wheeldata.Color ) end + if wheeldata.Height then wheel:SetSuspensionHeight( wheeldata.Height ) end + if wheeldata.Stiffness then wheel:SetSuspensionStiffness( wheeldata.Stiffness ) end + + timer.Simple(0, function() + if not IsValid( wheel ) then return end + + if wheeldata.BodyGroups then + for group, subgroup in pairs( wheeldata.BodyGroups ) do + if subgroup == 0 then continue end + + wheel:SetBodygroup( group, subgroup ) + end + end + + if wheeldata.PoseParameters and wheel:GetNumPoseParameters() > 0 then + for id, pose in pairs( wheeldata.PoseParameters ) do + local name = wheel:GetPoseParameterName( id ) + + wheel:StartThink() + wheel:SetPoseParameter( name, pose ) + + if name == "#scale" then + local min, max = wheel:GetPoseParameterRange( id ) + local num = wheel:GetBoneCount() - 1 + + local bonescale = math.Clamp( pose, min, max ) + + for boneid = 0, num do + local bonename = wheel:GetBoneName( boneid ) + + if not bonename or bonename == "__INVALIDBONE__" or not string.StartsWith( bonename, "#" ) then continue end + + wheel:ManipulateBoneScale( boneid, Vector(bonescale,bonescale,1) ) + end + + continue + end + end + end + end) + + wheel:CheckAlignment() + wheel:PhysWake() + end + end + end) +end +if duplicator and duplicator.RegisterEntityModifier then + duplicator.RegisterEntityModifier( "lvsCarWheels", DuplicatorApplyCarWheels ) +end + +function TOOL:IsValidTarget( ent ) + if not IsValid( ent ) then return false end + + local class = ent:GetClass() + + return class == "lvs_wheeldrive_wheel" +end + +function TOOL:GetData( ent ) + if CLIENT then return end + + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + self.radius = ent:GetRadius() * (1 / ent:GetModelScale()) + self.ang = ent:GetAlignmentAngle() + self.mdl = ent:GetModel() + + ply:ConCommand( [[lvscarwheelchanger_model "]]..self.mdl..[["]] ) + ply:ConCommand( "lvscarwheelchanger_skin "..ent:GetSkin() ) + + local clr = ent:GetColor() + ply:ConCommand( "lvscarwheelchanger_r " .. clr.r ) + ply:ConCommand( "lvscarwheelchanger_g " .. clr.g ) + ply:ConCommand( "lvscarwheelchanger_b " .. clr.b ) + ply:ConCommand( "lvscarwheelchanger_a " .. clr.a ) + + for id = 0, 9 do + local group = ent:GetBodygroup( id ) or 0 + ply:ConCommand( "lvscarwheelchanger_bodygroup"..id.." "..group ) + end + + for id = 0, 9 do + local pp = ent:GetPoseParameter( ent:GetPoseParameterName( id ) ) + + ply:ConCommand( "lvscarwheelchanger_pp"..id.." "..pp ) + end + + ply:ConCommand( "lvscarwheelchanger_camber "..ent:GetCamber() ) + ply:ConCommand( "lvscarwheelchanger_caster "..ent:GetCaster() ) + ply:ConCommand( "lvscarwheelchanger_toe "..ent:GetToe() ) + + ply:ConCommand( "lvscarwheelchanger_height "..ent:GetSuspensionHeight() ) + ply:ConCommand( "lvscarwheelchanger_stiffness "..ent:GetSuspensionStiffness() ) +end + +function TOOL:SetData( ent ) + if CLIENT then return end + + local mdl = self:GetClientInfo("model") + + if mdl ~= "" then + local data = list.Get( "lvs_wheels" )[ mdl ] + + if data then + self.mdl = mdl + self.ang = data.angle + self.radius = data.radius + end + end + + if not isstring( self.mdl ) or not isangle( self.ang ) or not isnumber( self.radius ) then return end + + local r = self:GetClientNumber( "r", 0 ) + local g = self:GetClientNumber( "g", 0 ) + local b = self:GetClientNumber( "b", 0 ) + local a = self:GetClientNumber( "a", 0 ) + + ent:SetColor( Color( r, g, b, a ) ) + ent:SetSkin( self:GetClientNumber( "skin", 0 ) ) + + timer.Simple(0, function() + if not IsValid( ent ) then return end + + for id = 0, 9 do + ent:SetBodygroup( id, self:GetClientNumber( "bodygroup"..id, 0 ) ) + end + + local num = ent:GetNumPoseParameters() + if num > 0 then + for id = 0, 9 do + if id > num - 1 then break end + + local name = ent:GetPoseParameterName( id ) + + local pose = self:GetClientNumber( "pp"..id, 0 ) + + ent:StartThink() + ent:SetPoseParameter( name, pose ) + + if name == "#scale" then + local min, max = ent:GetPoseParameterRange( id ) + local num = ent:GetBoneCount() - 1 + + local bonescale = math.Clamp( pose, min, max ) + + for boneid = 0, num do + local bonename = ent:GetBoneName( boneid ) + + if not bonename or bonename == "__INVALIDBONE__" or not string.StartsWith( bonename, "#" ) then continue end + + ent:ManipulateBoneScale( boneid, Vector(bonescale,bonescale,1) ) + end + + continue + end + end + end + end) + + if ent:GetModel() == self.mdl then + local Ang = ent:GetAlignmentAngle() + Ang:RotateAroundAxis( Vector(0,0,1), 180 ) + + ent:SetAlignmentAngle( Ang ) + else + ent:SetModel( self.mdl ) + ent:SetAlignmentAngle( self.ang ) + + timer.Simple(0.05, function() + if not IsValid( ent ) then return end + + ent:SetModelScale( ent:GetRadius() / self.radius ) + end) + end + + timer.Simple(0.1, function() + if not IsValid( ent ) then return end + + DuplicatorSaveCarWheels( ent ) + end) +end + +function TOOL:LeftClick( trace ) + if not self:IsValidTarget( trace.Entity ) then return false end + + self:SetData( trace.Entity ) + + if CLIENT then return true end + + local ent = trace.Entity + + timer.Simple(0, function() + if not IsValid( ent ) then return end + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_upgrade", effectdata ) + end) + + return true +end + +function TOOL:RightClick( trace ) + if not self:IsValidTarget( trace.Entity ) then return false end + + self:GetData( trace.Entity ) + + if CLIENT then return true end + + local ent = trace.Entity + + timer.Simple(0, function() + if not IsValid( ent ) then return end + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_update", effectdata ) + end) + + return true +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + + if not self:IsValidTarget( ent ) then return false end + + if CLIENT then return true end + + timer.Simple(0, function() + if not IsValid( ent ) then return end + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_downgrade", effectdata ) + end) + + local camber = math.Round( self:GetClientNumber("camber",0) , 2 ) + local caster = math.Round( self:GetClientNumber("caster",0) , 2 ) + local toe = math.Round( self:GetClientNumber("toe",0) , 2 ) + + if math.Round( ent:GetCamber(), 2 ) == camber and math.Round( ent:GetToe(), 2 ) == toe and math.Round( ent:GetCaster(), 2 ) == caster then + ent:SetCamber( -camber ) + ent:SetToe( -toe ) + else + ent:SetCamber( camber ) + ent:SetToe( toe ) + end + + ent:SetCaster( caster ) + + local NewTraction = math.min( math.Round( (ent:CheckAlignment() or 0) * 100, 0 ), 120 ) + + local ply = self:GetOwner() + + if IsValid( ply ) and ply:IsPlayer() then + ply:ChatPrint( "Estimated Traction: "..NewTraction.."%" ) + end + + ent:SetSuspensionHeight( self:GetClientInfo("height") ) + ent:SetSuspensionStiffness( self:GetClientInfo("stiffness") ) + ent:PhysWake() + + DuplicatorSaveCarWheels( ent ) + + return true +end + +list.Set( "lvs_wheels", "models/props_vehicles/carparts_wheel01a.mdl", {angle = Angle(0,90,0), radius = 16} ) diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvshealthshieldeditor.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvshealthshieldeditor.lua new file mode 100644 index 0000000..4adafc4 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvshealthshieldeditor.lua @@ -0,0 +1,117 @@ + +TOOL.Category = "LVS" +TOOL.Name = "#tool.lvshealthshieldeditor.name" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "maxshield" ] = 0 +TOOL.ClientConVar[ "maxhealth" ] = 5000 + +if CLIENT then + language.Add( "tool.lvshealthshieldeditor.name", "Max Health & Shield Editor" ) + language.Add( "tool.lvshealthshieldeditor.desc", "A tool used to edit Max Health & Shield on LVS-Vehicles" ) + language.Add( "tool.lvshealthshieldeditor.0", "Left click on a LVS-Vehicle to set Max Health, Right click to set Max Shield, Reload to reset." ) + language.Add( "tool.lvshealthshieldeditor.1", "Left click on a LVS-Vehicle to set Max Health, Right click to set Max Shield, Reload to reset." ) + language.Add( "tool.lvshealthshieldeditor.maxshield", "Max Shield" ) + language.Add( "tool.lvshealthshieldeditor.maxhealth", "Max Health" ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + + if not IsValid( ent ) then return false end + + if not ent.LVS and not ent.LFS then return end + + if not ent.OGMaxHealth then + ent.OGMaxHealth = ent.MaxHealth + end + + ent.MaxHealth = self:GetClientNumber( "maxhealth" ) + ent:SetHP( ent.MaxHealth ) + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + + if not IsValid( ent ) then return false end + + if not ent.LVS and not ent.LFS then return end + + if not ent.OGMaxShield then + ent.OGMaxShield = ent.MaxShield + end + + ent.MaxShield = self:GetClientNumber( "maxshield" ) + ent:SetShield( ent.MaxShield ) + + return true +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + + if not IsValid( ent ) then return false end + + if not ent.LVS and not ent.LFS then return end + + if ent.OGMaxHealth then + ent.MaxHealth = ent.OGMaxHealth + end + + if ent.OGMaxShield then + ent.MaxShield = ent.OGMaxShield + end + + ent:SetHP( ent.MaxHealth ) + ent:SetShield( ent.MaxShield ) + + return true +end + +function TOOL:Think() + if SERVER then return end + + local ply = LocalPlayer() + local tr = ply:GetEyeTrace() + + local ent = tr.Entity + if not IsValid( ent ) then return end + + if not ent.LVS and not ent.LFS then return end + + local Text = "Health: "..tostring( math.Round( ent:GetHP(), 0 ) ).."/"..tostring( ent.MaxHealth ) + if ent:GetShield() > 0 then + Text = Text.."\nShield: "..tostring( math.Round( ent:GetShield(), 0 ) ).."/"..tostring( ent.MaxShield ) + end + + AddWorldTip( ent:EntIndex(), Text, SysTime() + 0.05, ent:GetPos(), ent ) +end + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.lvshealthshieldeditor.name", Description = "#tool.lvshealthshieldeditor.desc" } ) + + panel:AddControl( "Slider", + { + Label = "#tool.lvshealthshieldeditor.maxhealth", + Type = "Int", + Min = "1", + Max = "50000", + Command = "lvshealthshieldeditor_maxhealth", + Help = false + }) + + panel:AddControl( "Slider", + { + Label = "#tool.lvshealthshieldeditor.maxshield", + Type = "Int", + Min = "0", + Max = "50000", + Command = "lvshealthshieldeditor_maxshield", + Help = false + }) + + panel:AddControl( "Label", { Text = "NOTE: Value in Edit-Properties menu will still be the same, because they can not be updated after the vehicle is spawned!" } ) +end diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvstransmission.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvstransmission.lua new file mode 100644 index 0000000..b8597a7 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvstransmission.lua @@ -0,0 +1,45 @@ + +TOOL.Category = "LVS" +TOOL.Name = "#Transmission Editor" +TOOL.Command = nil +TOOL.ConfigName = "" + +if CLIENT then + language.Add( "tool.lvstransmission.name", "Transmission Editor" ) + language.Add( "tool.lvstransmission.desc", "A tool used to enable/disable Manual Transmission on LVS-Cars" ) + language.Add( "tool.lvstransmission.0", "Left click on a LVS-Car to enable Manual Transmission. Right click to disable." ) + language.Add( "tool.lvstransmission.1", "Left click on a LVS-Car to enable Manual Transmission. Right click to disable." ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + + if not IsValid( ent ) then return false end + + if not ent.LVS then return end + + if isfunction( ent.SetNWGear ) and isfunction( ent.SetReverse ) then + ent:SetNWGear( 1 ) + ent:SetReverse( false ) + end + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + + if not IsValid( ent ) then return false end + + if not ent.LVS then return end + + if isfunction( ent.SetNWGear ) then + ent:SetNWGear( -1 ) + end + + return true +end + +function TOOL:Reload( trace ) + return false +end diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvsturret.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvsturret.lua new file mode 100644 index 0000000..c838447 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/gmod_tool/stools/lvsturret.lua @@ -0,0 +1,144 @@ +TOOL.Category = "LVS" +TOOL.Name = "#tool.lvsturret.name" +TOOL.Command = nil +TOOL.ConfigName = "" + +cleanup.Register( "lvsturret" ) +CreateConVar("sbox_maxlvsturret", 1, "FCVAR_NOTIFY") + +TOOL.ClientConVar[ "delay" ] = "0.05" +TOOL.ClientConVar[ "damage" ] = "15" +TOOL.ClientConVar[ "speed" ] = "30000" +TOOL.ClientConVar[ "size" ] = "1" +TOOL.ClientConVar[ "spread" ] = "0" +TOOL.ClientConVar[ "penetration" ] = "10" +TOOL.ClientConVar[ "splashdamage" ] = "0" +TOOL.ClientConVar[ "splashradius" ] = "0" +TOOL.ClientConVar[ "tracer" ] = "lvs_tracer_orange" +TOOL.ClientConVar[ "splasheffect" ] = "lvs_bullet_impact" + +if CLIENT then + language.Add( "tool.lvsturret.name", "Projectile Turret" ) + language.Add( "tool.lvsturret.desc", "A Tool used to spawn Turrets" ) + language.Add( "tool.lvsturret.0", "Left click to spawn or update a turret" ) + language.Add( "tool.lvsturret.1", "Left click to spawn or update a turret" ) + + language.Add( "Cleanup_lvsturret", "[LVS] Projectile Turret" ) + language.Add( "Cleaned_lvsturret", "Cleaned up all [LVS] Projectile Turrets" ) + + language.Add( "SBoxLimit_lvsturret", "You've reached the Projectile Turret limit!" ) +end + +function TOOL:LeftClick( trace ) + + if CLIENT then return true end + + local ply = self:GetOwner() + + if not istable( WireLib ) then + ply:PrintMessage( HUD_PRINTTALK, "[LVS]: WIREMOD REQUIRED" ) + ply:SendLua( "gui.OpenURL( 'https://steamcommunity.com/sharedfiles/filedetails/?id=160250458' )") + end + + if IsValid( trace.Entity ) and trace.Entity:GetClass():lower() == "lvs_turret" then + self:UpdateTurret( trace.Entity ) + else + local turret = self:MakeTurret( ply, trace.HitPos + trace.HitNormal * 5 ) + + undo.Create("Turret") + undo.AddEntity( turret ) + undo.SetPlayer( ply ) + undo.Finish() + end + + return true +end + +function TOOL:RightClick( trace ) + return false +end + +if SERVER then + function TOOL:UpdateTurret( ent ) + if not IsValid( ent ) then return end + + ent:SetShootDelay( self:GetClientNumber( "delay" ) ) + ent:SetDamage( math.Clamp( self:GetClientNumber( "damage" ), 0, 1000 ) ) + ent:SetSpeed( math.Clamp( self:GetClientNumber( "speed" ), 10000, 100000 ) ) + ent:SetSize( math.Clamp( self:GetClientNumber( "size" ), 0, 50 ) ) + ent:SetSpread( math.Clamp( self:GetClientNumber( "spread" ), 0, 1 ) ) + ent:SetPenetration( math.Clamp( self:GetClientNumber( "penetration" ), 0, 500 ) ) + ent:SetSplashDamage( math.Clamp( self:GetClientNumber( "splashdamage" ), 0, 1000 ) ) + ent:SetSplashDamageRadius( math.Clamp( self:GetClientNumber( "splashradius" ), 0, 750 ) ) + ent:SetTracer( self:GetClientInfo( "tracer" ) ) + ent:SetSplashDamageType( self:GetClientInfo( "splasheffect" ) ) + end + + function TOOL:MakeTurret( ply, Pos, Ang ) + + if not ply:CheckLimit( "lvsturret" ) then return NULL end + + local turret = ents.Create( "lvs_turret" ) + + if not IsValid( turret ) then return NULL end + + turret:SetPos( Pos ) + turret:SetAngles( Angle(0,0,0) ) + turret:Spawn() + + turret.Attacker = ply + + self:UpdateTurret( turret ) + + ply:AddCount( "lvsturret", turret ) + ply:AddCleanup( "lvsturret", turret ) + + return turret + end +end + +local ConVarsDefault = TOOL:BuildConVarList() +function TOOL.BuildCPanel( CPanel ) + CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "lvs_turrets", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + CPanel:AddControl( "Header", { Text = "#tool.lvsturret.name", Description = "#tool.lvsturret.desc" } ) + + local TracerEffect = {Label = "Tracer Effect", MenuButton = 0, Options={}, CVars = {}} + local TracerOptions = { + ["LaserBlue"] = "lvs_laser_blue", + ["LaserRed"] = "lvs_laser_red", + ["LaserGreen"] = "lvs_laser_green", + ["TracerGreen"] = "lvs_tracer_green", + ["TracerOrange"] = "lvs_tracer_orange", + ["TracerWhite"] = "lvs_tracer_white", + ["TracerYellow"] = "lvs_tracer_yellow", + ["AutoCannon"] = "lvs_tracer_autocannon", + ["Cannon"] = "lvs_tracer_cannon", + } + for id, name in pairs( TracerOptions ) do + if not file.Exists( "effects/"..name..".lua", "LUA" ) then continue end + TracerEffect["Options"][id] = { lvsturret_tracer = name } + end + CPanel:AddControl("ComboBox", TracerEffect ) + + CPanel:AddControl( "Slider", { Label = "Shoot Delay", Type = "Float", Min = 0, Max = 2.0, Command = "lvsturret_delay" } ) + + CPanel:AddControl( "Slider", { Label = "Damage", Type = "Float", Min = 0, Max = 1000, Command = "lvsturret_damage" } ) + + CPanel:AddControl( "Slider", { Label = "Bullet Speed", Type = "Float", Min = 10000, Max = 100000, Command = "lvsturret_speed" } ) + + CPanel:AddControl( "Slider", { Label = "Bullet Spread", Type = "Float", Min = 0, Max = 1, Command = "lvsturret_spread" } ) + + CPanel:AddControl( "Slider", { Label = "Hull Size", Type = "Float", Min = 0, Max = 50, Command = "lvsturret_size" } ) + + CPanel:AddControl( "Slider", { Label = "Armor Penetration (mm)", Type = "Float", Min = 0, Max = 500, Command = "lvsturret_penetration" } ) + + CPanel:AddControl( "Slider", { Label = "Splash Damage", Type = "Float", Min = 0, Max = 1000, Command = "lvsturret_splashdamage" } ) + + CPanel:AddControl( "Slider", { Label = "Splash Radius", Type = "Float", Min = 0, Max = 750, Command = "lvsturret_splashradius" } ) + + local SplashType = {Label = "Splash Type", MenuButton = 0, Options={}, CVars = {}} + SplashType["Options"][ "Shrapnel" ] = { lvsturret_splasheffect = "lvs_bullet_impact" } + SplashType["Options"][ "Explosive" ] = { lvsturret_splasheffect = "lvs_bullet_impact_explosive" } + CPanel:AddControl("ComboBox", SplashType ) +end diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/weapon_lvsmines.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/weapon_lvsmines.lua new file mode 100644 index 0000000..2b8bda9 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/weapon_lvsmines.lua @@ -0,0 +1,198 @@ +AddCSLuaFile() + +SWEP.Category = "[LVS]" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = false + +SWEP.ViewModel = "models/weapons/c_scrubriglvs.mdl" +SWEP.WorldModel = "models/blu/lvsmine.mdl" + +SWEP.UseHands = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 60 +SWEP.AutoSwitchTo = true +SWEP.AutoSwitchFrom = true + +SWEP.HoldType = "slam" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 3 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "slam" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +cleanup.Register( "lvsmine" ) +CreateConVar("sbox_maxlvsmine", 10, "FCVAR_NOTIFY") + +if CLIENT then + SWEP.PrintName = "Mines" + SWEP.Slot = 4 + SWEP.SlotPos = 1 + + SWEP.DrawWeaponInfoBox = false + + SWEP.pViewModel = ClientsideModel("models/blu/lvsmine.mdl", RENDERGROUP_OPAQUE) + SWEP.pViewModel:SetNoDraw( true ) + + function SWEP:ViewModelDrawn() + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local vm = ply:GetViewModel() + local bm = vm:GetBoneMatrix( 1 ) + + if not bm then return end + + local pos = bm:GetTranslation() + local ang = bm:GetAngles() + + pos = pos + ang:Up() * 25 + pos = pos + ang:Right() * 1 + pos = pos + ang:Forward() * -3 + + ang:RotateAroundAxis(ang:Forward(),60) + ang:RotateAroundAxis(ang:Right(),170) + ang:RotateAroundAxis(ang:Up(),65) + + self.pViewModel:SetModelScale( 0.75 ) + self.pViewModel:SetPos( pos ) + self.pViewModel:SetAngles( ang ) + self.pViewModel:DrawModel() + end + + function SWEP:DrawWorldModel() + local ply = self:GetOwner() + + if not IsValid( ply ) then self:DrawModel() return end + + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + local pos = attachment.Pos + attachment.Ang:Forward() * 2 + local ang = attachment.Ang + ang:RotateAroundAxis(attachment.Ang:Up(), 20) + ang:RotateAroundAxis(attachment.Ang:Right(), -30) + ang:RotateAroundAxis(attachment.Ang:Forward(), 0) + + self:SetRenderOrigin( pos ) + self:SetRenderAngles( ang ) + + self:DrawModel() + end + + function SWEP:DrawWeaponSelection( x, y, wide, tall, alpha ) + draw.SimpleText( "z", "WeaponIcons", x + wide/2, y + tall*0.2, Color( 255, 210, 0, 255 ), TEXT_ALIGN_CENTER ) + end +end + +function SWEP:Initialize() + self:SetHoldType( self.HoldType ) +end + +function SWEP:OwnerChanged() +end + +function SWEP:Think() +end + +function SWEP:TakePrimaryAmmo( num ) + local ply = self:GetOwner() + + if self:Clip1() <= 0 then + + if self:Ammo1() <= 0 then return end + + ply:RemoveAmmo( num, self:GetPrimaryAmmoType() ) + + return + end + + self:SetClip1( math.max(self:Clip1() - num,0) ) + +end + +function SWEP:CanPrimaryAttack() + self.NextFire = self.NextFire or 0 + + return self.NextFire <= CurTime() and self:Ammo1() > 0 +end + +function SWEP:SetNextPrimaryFire( time ) + self.NextFire = time +end + +function SWEP:ThrowMine() + if CLIENT then return end + + local ply = self:GetOwner() + + if not ply:CheckLimit( "lvsmine" ) then return end + + ply:EmitSound( "npc/zombie/claw_miss1.wav" ) + + local ent = ents.Create( "lvs_item_mine" ) + + if not IsValid( ent ) then return end + + ent:SetPos( ply:GetShootPos() - Vector(0,0,10) ) + ent:Spawn() + ent:Activate() + ent:SetAttacker( ply ) + + ply:AddCount( "lvsmine", ent ) + ply:AddCleanup( "lvsmine", ent ) + + undo.Create("Mine") + undo.AddEntity( ent ) + undo.SetPlayer( ply ) + undo.Finish() + + local PhysObj = ent:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetVelocityInstantaneous( ply:GetAimVector() * 200 + Vector(0,0,150) ) + PhysObj:AddAngleVelocity( VectorRand() * 20 ) +end + +function SWEP:PrimaryAttack() + if not self:CanPrimaryAttack() then return end + + local ply = self:GetOwner() + + ply:SetAnimation( PLAYER_ATTACK1 ) + + self:ThrowMine() + + self:SetNextPrimaryFire( CurTime() + 1.5 ) + + self:TakePrimaryAmmo( 1 ) + + if SERVER then + if self:Ammo1() <= 0 then + ply:StripWeapon( "weapon_lvsmines" ) + end + end +end + +function SWEP:SecondaryAttack() + return false +end + +function SWEP:Deploy() + self:SendWeaponAnim( ACT_SLAM_STICKWALL_DRAW ) + + return true +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/weapon_lvsspikestrip.lua b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/weapon_lvsspikestrip.lua new file mode 100644 index 0000000..3678201 --- /dev/null +++ b/garrysmod/addons/lvs_base/gamemodes/sandbox/entities/weapons/weapon_lvsspikestrip.lua @@ -0,0 +1,168 @@ +AddCSLuaFile() + +SWEP.Category = "[LVS]" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = false + +SWEP.ViewModel = "models/weapons/c_scrubriglvs.mdl" +SWEP.WorldModel = "models/diggercars/shared/spikestrip_fold.mdl" + +SWEP.UseHands = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 60 +SWEP.AutoSwitchTo = true +SWEP.AutoSwitchFrom = true + +SWEP.HoldType = "physgun" + +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" + +cleanup.Register( "lvsspikestrip" ) + +if CLIENT then + SWEP.PrintName = "Spike Strip" + SWEP.Slot = 4 + SWEP.SlotPos = 2 + + SWEP.DrawWeaponInfoBox = false + + SWEP.pViewModel = ClientsideModel("models/diggercars/shared/spikestrip_fold.mdl", RENDERGROUP_OPAQUE) + SWEP.pViewModel:SetNoDraw( true ) + + function SWEP:ViewModelDrawn() + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local vm = ply:GetViewModel() + local bm = vm:GetBoneMatrix(0) + + if not bm then return end + + local pos = bm:GetTranslation() + local ang = bm:GetAngles() + + pos = pos + ang:Up() * 28 + pos = pos + ang:Right() * 8 + pos = pos + ang:Forward() * -5 + + ang:RotateAroundAxis(ang:Forward(), -210) + ang:RotateAroundAxis(ang:Right(),-60) + ang:RotateAroundAxis(ang:Up(), 90) + + self.pViewModel:SetPos( pos ) + self.pViewModel:SetAngles( ang ) + self.pViewModel:DrawModel() + self.pViewModel:SetModelScale( 0.5 ) + end + + function SWEP:DrawWorldModel() + local ply = self:GetOwner() + + if not IsValid( ply ) then self:DrawModel() return end + + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + local pos = attachment.Pos + attachment.Ang:Forward() * 3 - attachment.Ang:Up() * 30 + local ang = attachment.Ang + ang:RotateAroundAxis(attachment.Ang:Up(), -40) + ang:RotateAroundAxis(attachment.Ang:Right(), -90) + ang:RotateAroundAxis(attachment.Ang:Forward(), 0) + + self:SetRenderOrigin( pos ) + self:SetRenderAngles( ang ) + + self:DrawModel() + end + + function SWEP:DrawWeaponSelection( x, y, wide, tall, alpha ) + draw.SimpleText( "P", "WeaponIcons", x + wide/2, y + tall*0.2, Color( 255, 210, 0, 255 ), TEXT_ALIGN_CENTER ) + end +end + +function SWEP:Initialize() + self:SetHoldType( self.HoldType ) +end + +function SWEP:OwnerChanged() +end + +function SWEP:Think() +end + +if SERVER then + function SWEP:PlaceStrip() + local ply = self:GetOwner() + + ply:EmitSound( "npc/zombie/claw_miss1.wav" ) + + local ent = ents.Create( "lvs_item_spikestrip_foldable" ) + + if not IsValid( ent ) then return end + + ent:SetAngles( Angle(0,180 + ply:EyeAngles().y,0) ) + ent:SetPos( ply:GetShootPos() - Vector(0,0,10) ) + ent:Spawn() + ent:Activate() + ent:SetAttacker( ply ) + ent:SetOwner( ply ) + + ply:AddCleanup( "lvsspikestrip", ent ) + + undo.Create("Spike Strip") + undo.AddEntity( ent ) + undo.SetPlayer( ply ) + undo.Finish() + + local PhysObj = ent:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetVelocityInstantaneous( ply:GetAimVector() * 200 + Vector(0,0,75) ) + end +end + +function SWEP:PrimaryAttack() + local ply = self:GetOwner() + + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + ply:SetAnimation( PLAYER_ATTACK1 ) + + self:SetNextPrimaryFire( CurTime() + 1.5 ) + + if SERVER then + self:PlaceStrip() + + ply:StripWeapon( "weapon_lvsspikestrip" ) + end +end + +function SWEP:SecondaryAttack() + return false +end + +function SWEP:Deploy() + self:SendWeaponAnim( ACT_VM_DRAW ) + + return true +end + +function SWEP:Holster() + + return true +end + +function SWEP:OnRemove() +end diff --git a/garrysmod/addons/lvs_base/lua/autorun/client/lvs_vehicle_tab.lua b/garrysmod/addons/lvs_base/lua/autorun/client/lvs_vehicle_tab.lua new file mode 100644 index 0000000..dd7ca55 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/autorun/client/lvs_vehicle_tab.lua @@ -0,0 +1,240 @@ + +hook.Add( "PopulateVehicles", "!!!add_lvs_to_vehicles", function( pnlContent, tree, node ) + local CategoryNameTranslate = {} + local Categorised = {} + local SubCategorised = {} + + local SpawnableEntities = table.Copy( list.Get( "SpawnableEntities" ) ) + local Variants = { + [1] = "[LVS] - ", + [2] = "[LVS] -", + [3] = "[LVS]- ", + [4] = "[LVS]-", + [5] = "[LVS] ", + } + + for _, v in pairs( scripted_ents.GetList() ) do + if not v.t or not v.t.ClassName or not v.t.VehicleCategory then continue end + + if not isstring( v.t.ClassName ) or v.t.ClassName == "" or not SpawnableEntities[ v.t.ClassName ] then continue end + + SpawnableEntities[ v.t.ClassName ].Category = "[LVS] - "..v.t.VehicleCategory + + if not v.t.VehicleSubCategory then continue end + + SpawnableEntities[ v.t.ClassName ].SubCategory = v.t.VehicleSubCategory + end + + if SpawnableEntities then + for k, v in pairs( SpawnableEntities ) do + + local Category = v.Category + + if not isstring( Category ) then continue end + + if not Category:StartWith( "[LVS]" ) and not v.LVS then continue end + + v.SpawnName = k + + for _, start in pairs( Variants ) do + if Category:StartWith( start ) then + local NewName = string.Replace(Category, start, "") + CategoryNameTranslate[ NewName ] = Category + Category = NewName + + break + end + end + + if v.SubCategory then + SubCategorised[ Category ] = SubCategorised[ Category ] or {} + SubCategorised[ Category ][ v.SubCategory ] = SubCategorised[ Category ][ v.SubCategory ] or {} + + table.insert( SubCategorised[ Category ][ v.SubCategory ], v ) + end + + Categorised[ Category ] = Categorised[ Category ] or {} + + table.insert( Categorised[ Category ], v ) + end + end + + local lvsNode = tree:AddNode( "[LVS]", "icon16/lvs.png" ) + + if Categorised["[LVS]"] then + local v = Categorised["[LVS]"] + + lvsNode.DoPopulate = function( self ) + if self.PropPanel then return end + + self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + self.PropPanel:SetVisible( false ) + self.PropPanel:SetTriggerSpawnlistChange( false ) + + for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.SpawnName, + material = ent.IconOverride or "entities/" .. ent.SpawnName .. ".png", + admin = ent.AdminOnly + } ) + end + end + + lvsNode.DoClick = function( self ) + self:DoPopulate() + pnlContent:SwitchPanel( self.PropPanel ) + end + end + + local IconList = list.Get( "ContentCategoryIcons" ) + + for CategoryName, v in SortedPairs( Categorised ) do + if CategoryName:StartWith( "[LVS]" ) then continue end + + local Icon = "icon16/lvs_noicon.png" + + if IconList and IconList[ CategoryNameTranslate[ CategoryName ] ] then + Icon = IconList[ CategoryNameTranslate[ CategoryName ] ] + end + + local node = lvsNode:AddNode( CategoryName, Icon ) + + node.DoPopulate = function( self ) + if self.PropPanel then return end + + self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + self.PropPanel:SetVisible( false ) + self.PropPanel:SetTriggerSpawnlistChange( false ) + + for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do + if ent.SubCategory then + continue + end + + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.SpawnName, + material = ent.IconOverride or "entities/" .. ent.SpawnName .. ".png", + admin = ent.AdminOnly + } ) + end + end + node.DoClick = function( self ) + self:DoPopulate() + pnlContent:SwitchPanel( self.PropPanel ) + end + + local SubCat = SubCategorised[ CategoryName ] + + if not SubCat then continue end + + for SubName, data in SortedPairs( SubCat ) do + + local SubIcon = "icon16/lvs_noicon.png" + + if IconList then + if IconList[ "[LVS] - "..CategoryName.." - "..SubName ] then + SubIcon = IconList[ "[LVS] - "..CategoryName.." - "..SubName ] + else + if IconList[ "[LVS] - "..SubName ] then + SubIcon = IconList[ "[LVS] - "..SubName ] + end + end + end + + local subnode = node:AddNode( SubName, SubIcon ) + + subnode.DoPopulate = function( self ) + if self.PropPanel then return end + + self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + self.PropPanel:SetVisible( false ) + self.PropPanel:SetTriggerSpawnlistChange( false ) + + for k, ent in SortedPairsByMemberValue( data, "PrintName" ) do + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.SpawnName, + material = ent.IconOverride or "entities/" .. ent.SpawnName .. ".png", + admin = ent.AdminOnly + } ) + end + end + subnode.DoClick = function( self ) + self:DoPopulate() + pnlContent:SwitchPanel( self.PropPanel ) + end + end + end + + -- User Stuff + hook.Run( "LVS.PopulateVehicles", lvsNode, pnlContent, tree ) + + -- CONTROLS + local node = lvsNode:AddNode( "Controls", "icon16/keyboard.png" ) + node.DoClick = function( self ) + LVS:OpenMenu() + LVS:OpenClientControls() + end + + -- CLIENT SETTINGS + local node = lvsNode:AddNode( "Client Settings", "icon16/wrench.png" ) + node.DoClick = function( self ) + LVS:OpenMenu() + LVS:OpenClientSettings() + end + + -- SERVER SETTINGS + local node = lvsNode:AddNode( "Server Settings", "icon16/wrench_orange.png" ) + node.DoClick = function( self ) + if LocalPlayer():IsSuperAdmin() then + LVS:OpenMenu() + LVS:OpenServerMenu() + else + surface.PlaySound( "buttons/button11.wav" ) + end + end +end ) + +list.Set( "ContentCategoryIcons", "[LVS]", "icon16/lvs.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Artillery", "icon16/lvs_artillery.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Boats", "icon16/lvs_boats.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Boats - Civilian", "icon16/lvs_civilian.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Racers", "icon16/lvs_racers.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Cars", "icon16/lvs_cars.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Cars - Armored", "icon16/lvs_armor.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Cars - Civilian", "icon16/lvs_civilian.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Cars - Military", "icon16/lvs_military.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Cars - Pack", "icon16/lvs_cars_pack.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Trucks - Pack", "icon16/lvs_trucks.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Rig", "icon16/lvs_trucks.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Rig - Trucks", "icon16/lvs_trucks.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Rig - Trailers", "icon16/lvs_trucktrailers.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Helicopters", "icon16/lvs_helicopters.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Helicopters - Combine", "icon16/lvs_combine.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Helicopters - Resistance", "icon16/lvs_resistance.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Planes", "icon16/lvs_planes.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Planes - Bombers", "icon16/lvs_bomb.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Planes - Fighters", "icon16/lvs_fighter.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Planes - Civilian", "icon16/lvs_civilian.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Tanks", "icon16/lvs_tanks.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Tanks - Light", "icon16/lvs_light.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Tanks - Medium", "icon16/lvs_medium.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Tanks - Heavy", "icon16/lvs_heavy.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Tanks - RP", "icon16/lvs_rp.png" ) + +list.Set( "ContentCategoryIcons", "[LVS] - Star Wars", "icon16/lvs_starwars.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Star Wars - Gunships", "icon16/lvs_sw_gunship.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Star Wars - Hover Tanks", "icon16/lvs_sw_hover.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Star Wars - Walkers", "icon16/lvs_sw_walker.png" ) +list.Set( "ContentCategoryIcons", "[LVS] - Star Wars - Starfighters", "icon16/lvs_sw_starfighter.png" ) diff --git a/garrysmod/addons/lvs_base/lua/autorun/lvs_base_addworkshop.lua b/garrysmod/addons/lvs_base/lua/autorun/lvs_base_addworkshop.lua new file mode 100644 index 0000000..dd37e66 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/autorun/lvs_base_addworkshop.lua @@ -0,0 +1,3 @@ +if SERVER then + resource.AddWorkshop("2922255744") -- LVS Base +end diff --git a/garrysmod/addons/lvs_base/lua/autorun/lvs_init.lua b/garrysmod/addons/lvs_base/lua/autorun/lvs_init.lua new file mode 100644 index 0000000..8ff3a76 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/autorun/lvs_init.lua @@ -0,0 +1,476 @@ + +LVS = istable( LVS ) and LVS or {} + +LVS.VERSION = 348 +LVS.VERSION_GITHUB = 0 +LVS.VERSION_TYPE = ".WS" +LVS.VERSION_ADDONS_OUTDATED = false + +LVS.KEYS_CATEGORIES = {} +LVS.KEYS_REGISTERED = {} +LVS.pSwitchKeys = {[KEY_1] = 1,[KEY_2] = 2,[KEY_3] = 3,[KEY_4] = 4,[KEY_5] = 5,[KEY_6] = 6,[KEY_7] = 7,[KEY_8] = 8,[KEY_9] = 9,[KEY_0] = 10} +LVS.pSwitchKeysInv = {[1] = KEY_1,[2] = KEY_2,[3] = KEY_3,[4] = KEY_4,[5] = KEY_5,[6] = KEY_6,[7] = KEY_7,[8] = KEY_8,[9] = KEY_9,[10] = KEY_0} + +LVS.ThemeColor = Color(127,0,0,255) + +LVS.WHEEL_BRAKE = 1 +LVS.WHEEL_STEER_NONE = 2 +LVS.WHEEL_STEER_FRONT = 3 +LVS.WHEEL_STEER_REAR = 4 +LVS.WHEEL_STEER_ACKERMANN = 5 + +LVS.WHEELTYPE_NONE = 0 +LVS.WHEELTYPE_LEFT = 1 +LVS.WHEELTYPE_RIGHT = -1 + +LVS.HITCHTYPE_NONE = -1 +LVS.HITCHTYPE_MALE = 0 +LVS.HITCHTYPE_FEMALE = 1 + +LVS.SOUNDTYPE_NONE = 0 +LVS.SOUNDTYPE_IDLE_ONLY = 1 +LVS.SOUNDTYPE_REV_UP = 2 +LVS.SOUNDTYPE_REV_DOWN = 3 +LVS.SOUNDTYPE_REV_DN = 3 +LVS.SOUNDTYPE_ALL = 4 + +LVS.FUELTYPE_PETROL = 0 +LVS.FUELTYPE_DIESEL = 1 +LVS.FUELTYPE_ELECTRIC = 2 +LVS.FUELTYPES = { + [LVS.FUELTYPE_PETROL] = { + name = "Petrol", + color = Vector(240,200,0), + }, + [LVS.FUELTYPE_DIESEL] = { + name = "Diesel", + color = Vector(255,60,0), + }, + [LVS.FUELTYPE_ELECTRIC] = { + name = "Electric", + color = Vector(0,127,255), + }, +} + +LVS.SPEEDUNITS = { + ["u/s"] = { + name = "u/s", + value = 1, + }, + ["mph"] = { + name = "mph", + value = 0.0568182, + }, + ["km/h"] = { + name = "km/h", + value = 0.09144, + }, +} + +LVS.WEAPONS = { + ["DEFAULT"] = { + Icon = Material("lvs/weapons/bullet.png"), + Ammo = 9999, + Delay = 0, + HeatRateUp = 0.2, + HeatRateDown = 0.25, + Attack = function( ent ) end, + StartAttack = function( ent ) end, + FinishAttack = function( ent ) end, + OnSelect = function( ent, old ) end, + OnDeselect = function( ent, new ) end, + OnThink = function( ent, active ) end, + OnOverheat = function( ent ) end, + OnRemove = function( ent ) end, + OnReload = function( ent ) end, + }, + ["LMG"] = { + Icon = Material("lvs/weapons/mg.png"), + Ammo = 1000, + Delay = 0.1, + Attack = function( ent ) + ent.MirrorPrimary = not ent.MirrorPrimary + + local Mirror = ent.MirrorPrimary and -1 or 1 + + local Pos = ent:LocalToWorld( ent.PosLMG and Vector(ent.PosLMG.x,ent.PosLMG.y * Mirror,ent.PosLMG.z) or Vector(0,0,0) ) + local Dir = ent.DirLMG or 0 + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( ent:GetForward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + local bullet = {} + bullet.Src = Pos + bullet.Dir = ent:LocalToWorldAngles( Angle(0,-Dir * Mirror,0) ):Forward() + bullet.Spread = Vector( 0.015, 0.015, 0.015 ) + bullet.TracerName = "lvs_tracer_white" + bullet.Force = 1000 + bullet.HullSize = 50 + bullet.Damage = 35 + bullet.Velocity = 30000 + bullet.Attacker = ent:GetDriver() + bullet.Callback = function(att, tr, dmginfo) end + ent:LVSFireBullet( bullet ) + + ent:TakeAmmo() + end, + StartAttack = function( ent ) + if not IsValid( ent.SoundEmitter1 ) then + ent.SoundEmitter1 = ent:AddSoundEmitter( Vector(109.29,0,92.85), "lvs/weapons/mg_loop.wav", "lvs/weapons/mg_loop_interior.wav" ) + ent.SoundEmitter1:SetSoundLevel( 95 ) + end + + ent.SoundEmitter1:Play() + end, + FinishAttack = function( ent ) + if IsValid( ent.SoundEmitter1 ) then + ent.SoundEmitter1:Stop() + ent.SoundEmitter1:EmitSound("lvs/weapons/mg_lastshot.wav") + end + end, + OnSelect = function( ent ) ent:EmitSound("physics/metal/weapon_impact_soft3.wav") end, + OnOverheat = function( ent ) ent:EmitSound("lvs/overheat.wav") end, + }, + ["TABLE_POINT_MG"] = { + Icon = Material("lvs/weapons/mc.png"), + Ammo = 2000, + Delay = 0.1, + Attack = function( ent ) + if not ent.PosTPMG or not ent.DirTPMG then return end + + for i = 1, 2 do + ent._NumTPMG = ent._NumTPMG and ent._NumTPMG + 1 or 1 + + if ent._NumTPMG > #ent.PosTPMG then ent._NumTPMG = 1 end + + local Pos = ent:LocalToWorld( ent.PosTPMG[ ent._NumTPMG ] ) + local Dir = ent.DirTPMG[ ent._NumTPMG ] + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( ent:GetForward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + local bullet = {} + bullet.Src = Pos + bullet.Dir = ent:LocalToWorldAngles( Angle(0,-Dir,0) ):Forward() + bullet.Spread = Vector( 0.035, 0.035, 0.035 ) + bullet.TracerName = "lvs_tracer_yellow" + bullet.Force = 1000 + bullet.HullSize = 25 + bullet.Damage = 35 + bullet.Velocity = 40000 + bullet.Attacker = ent:GetDriver() + bullet.Callback = function(att, tr, dmginfo) end + ent:LVSFireBullet( bullet ) + end + + ent:TakeAmmo( 2 ) + end, + StartAttack = function( ent ) + if not IsValid( ent.SoundEmitter1 ) then + ent.SoundEmitter1 = ent:AddSoundEmitter( Vector(109.29,0,92.85), "lvs/weapons/mg_light_loop.wav", "lvs/weapons/mg_light_loop_interior.wav" ) + ent.SoundEmitter1:SetSoundLevel( 95 ) + end + + ent.SoundEmitter1:Play() + end, + FinishAttack = function( ent ) + if IsValid( ent.SoundEmitter1 ) then + ent.SoundEmitter1:Stop() + ent.SoundEmitter1:EmitSound("lvs/weapons/mg_light_lastshot.wav") + end + end, + OnSelect = function( ent ) ent:EmitSound("physics/metal/weapon_impact_soft3.wav") end, + OnOverheat = function( ent ) ent:EmitSound("lvs/overheat.wav") end, + }, + ["HMG"] = { + Icon = Material("lvs/weapons/hmg.png"), + Ammo = 300, + Delay = 0.14, + Attack = function( ent ) + ent.MirrorSecondary = not ent.MirrorSecondary + + local Mirror = ent.MirrorSecondary and -1 or 1 + + local Pos = ent:LocalToWorld( ent.PosHMG and Vector(ent.PosHMG.x,ent.PosHMG.y * Mirror,ent.PosHMG.z) or Vector(0,0,0) ) + local Dir = ent.DirHMG or 0.5 + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( ent:GetForward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + local bullet = {} + bullet.Src = Pos + bullet.Dir = ent:LocalToWorldAngles( Angle(0,-Dir * Mirror,0) ):Forward() + bullet.Spread = Vector( 0.02, 0.02, 0.02 ) + bullet.TracerName = "lvs_tracer_orange" + bullet.Force = 4000 + bullet.HullSize = 15 + bullet.Damage = 45 + bullet.SplashDamage = 75 + bullet.SplashDamageRadius = 200 + bullet.Velocity = 15000 + bullet.Attacker = ent:GetDriver() + bullet.Callback = function(att, tr, dmginfo) + end + ent:LVSFireBullet( bullet ) + + ent:TakeAmmo() + end, + StartAttack = function( ent ) + if not IsValid( ent.SoundEmitter2 ) then + ent.SoundEmitter2 = ent:AddSoundEmitter( Vector(109.29,0,92.85), "lvs/weapons/mc_loop.wav", "lvs/weapons/mc_loop_interior.wav" ) + ent.SoundEmitter2:SetSoundLevel( 95 ) + end + + ent.SoundEmitter2:Play() + end, + FinishAttack = function( ent ) + if IsValid( ent.SoundEmitter2 ) then + ent.SoundEmitter2:Stop() + ent.SoundEmitter2:EmitSound("lvs/weapons/mc_lastshot.wav") + end + end, + OnSelect = function( ent ) ent:EmitSound("physics/metal/weapon_impact_soft2.wav") end, + OnOverheat = function( ent ) ent:EmitSound("lvs/overheat.wav") end, + }, + ["TURBO"] = { + Icon = Material("lvs/weapons/nos.png"), + HeatRateUp = 0.1, + HeatRateDown = 0.1, + UseableByAI = false, + Attack = function( ent ) + local PhysObj = ent:GetPhysicsObject() + if not IsValid( PhysObj ) then return end + local THR = ent:GetThrottle() + local FT = FrameTime() + + local Vel = ent:GetVelocity():Length() + + PhysObj:ApplyForceCenter( ent:GetForward() * math.Clamp(ent.MaxVelocity + 500 - Vel,0,1) * PhysObj:GetMass() * THR * FT * 150 ) -- increase speed + PhysObj:AddAngleVelocity( PhysObj:GetAngleVelocity() * FT * 0.5 * THR ) -- increase turn rate + end, + StartAttack = function( ent ) + ent.TargetThrottle = 1.3 + ent:EmitSound("lvs/vehicles/generic/boost.wav") + end, + FinishAttack = function( ent ) + ent.TargetThrottle = 1 + end, + OnSelect = function( ent ) + ent:EmitSound("buttons/lever5.wav") + end, + OnThink = function( ent, active ) + if not ent.TargetThrottle then return end + + local Rate = FrameTime() * 0.5 + + ent:SetMaxThrottle( ent:GetMaxThrottle() + math.Clamp(ent.TargetThrottle - ent:GetMaxThrottle(),-Rate,Rate) ) + + local MaxThrottle = ent:GetMaxThrottle() + + ent:SetThrottle( MaxThrottle ) + + if MaxThrottle == ent.TargetThrottle then + ent.TargetThrottle = nil + end + end, + OnOverheat = function( ent ) ent:EmitSound("lvs/overheat_boost.wav") end, + }, +} + +function LVS:GetVersion() + return LVS.VERSION +end + +function LVS:AddKey(name, category, printname, cmd, default) + local data = { + printname = printname, + id = name, + category = category, + cmd = cmd, + } + + if not LVS.KEYS_CATEGORIES[ category ] then + LVS.KEYS_CATEGORIES[ category ] = {} + end + + if SERVER then + table.insert( LVS.KEYS_REGISTERED, data ) + else + if default then + if isstring( default ) then + local Key = input.LookupBinding( default ) + + if Key then + default = input.GetKeyCode( Key ) + else + default = 0 + end + end + else + default = 0 + end + + data.default = default + + table.insert( LVS.KEYS_REGISTERED, data ) + + CreateClientConVar( cmd, default, true, true ) + end +end + +function LVS:CheckUpdates() + http.Fetch("https://raw.githubusercontent.com/SpaxscE/lvs_base/main/lua/autorun/lvs_init.lua", function(contents,size) + local Entry = string.match( contents, "LVS.VERSION%s=%s%d+" ) + + if Entry then + LVS.VERSION_GITHUB = tonumber( string.match( Entry , "%d+" ) ) or 0 + else + LVS.VERSION_GITHUB = 0 + end + + if LVS.VERSION_GITHUB == 0 then + print("[LVS] - Framework: latest version could not be detected, You have Version: "..LVS:GetVersion()) + else + if LVS:GetVersion() >= LVS.VERSION_GITHUB then + print("[LVS] - Framework is up to date, Version: "..LVS:GetVersion()) + else + print("[LVS] - Framework: a newer version is available! Version: "..LVS.VERSION_GITHUB..", You have Version: "..LVS:GetVersion()) + + if LVS.VERSION_TYPE == ".GIT" then + print("[LVS] - Framework: get the latest version at https://github.com/SpaxscE/lvs_base") + else + print("[LVS] - Framework: restart your game/server to get the latest version!") + end + + if CLIENT then + timer.Simple(18, function() + chat.AddText( Color( 255, 0, 0 ), "[LVS] - Framework: a newer version is available!" ) + end) + end + end + end + + local Delay = 0 + local addons = file.Find( "data_static/lvs/*", "GAME" ) + + for _, addonFile in pairs( addons ) do + local addonInfo = file.Read( "data_static/lvs/"..addonFile, "GAME" ) + + if not addonInfo then continue end + + local data = string.Explode( "\n", addonInfo ) + + local wsid = string.Replace( addonFile, ".txt", "" ) + local addon_name = wsid + local addon_url + local addon_version + + for _, entry in pairs( data ) do + if string.StartsWith( entry, "url=" ) then + addon_url = string.Replace( entry, "url=", "" ) + end + + if string.StartsWith( entry, "version=" ) then + addon_version = string.Replace( entry, "version=", "" ) + end + + if string.StartsWith( entry, "name=" ) then + addon_name = string.Replace( entry, "name=", "" ) + end + end + + if not addon_url or not addon_version then continue end + + addon_version = tonumber( addon_version ) + + Delay = Delay + 1.5 + + timer.Simple( Delay, function() + http.Fetch(addon_url, function(con,_) + local addon_entry = string.match( con, "version=%d+" ) + + local addon_version_git = 0 + + if addon_entry then + addon_version_git = tonumber( string.match( addon_entry, "%d+" ) ) or 0 + end + + local wsurl = "https://steamcommunity.com/sharedfiles/filedetails/?id="..wsid + + if addon_version_git == 0 then + print("[LVS] latest version of "..addon_name.." ( "..wsurl.." ) could not be detected, You have Version: "..addon_version) + else + if addon_version_git > addon_version then + print("[LVS] - "..addon_name.." ( "..wsurl.." ) is out of date!") + + if CLIENT then + timer.Simple(18, function() + chat.AddText( Color( 255, 0, 0 ),"[LVS] - "..addon_name.." is out of date!" ) + end) + end + + LVS.VERSION_ADDONS_OUTDATED = true + + else + print("[LVS] - "..addon_name.." is up to date, Version: "..addon_version) + end + end + end) + end ) + end + end) +end + +function LVS:GetWeaponPreset( name ) + if not LVS.WEAPONS[ name ] then return table.Copy( LVS.WEAPONS["DEFAULT"] ) end + + return table.Copy( LVS.WEAPONS[ name ] ) +end + +function LVS:AddWeaponPreset( name, data ) + if not isstring( name ) or not istable( data ) then return end + + LVS.WEAPONS[ name ] = data +end + +function LVS:GetVehicleTypes() + local VehicleTypes = {} + + for s, v in pairs( scripted_ents.GetList() ) do + if not v.t or not isfunction( v.t.GetVehicleType ) then continue end + + local vehicletype = v.t:GetVehicleType() + + if not isstring( vehicletype ) or string.StartsWith( vehicletype, "LBase" ) or table.HasValue( VehicleTypes, vehicletype ) then continue end + + table.insert( VehicleTypes, vehicletype ) + end + + return VehicleTypes +end + +if CLIENT then + function LVS:GetUnitValue( number ) + if not LVS.SPEEDUNITS[ LVS.SpeedUnit ] then return number end + + return (number * LVS.SPEEDUNITS[ LVS.SpeedUnit ].value) + end + + function LVS:GetUnitName() + if not LVS.SPEEDUNITS[ LVS.SpeedUnit ] then return "u/s" end + + return LVS.SPEEDUNITS[ LVS.SpeedUnit ].name + end +end + +AddCSLuaFile("lvs_framework/init.lua") +include("lvs_framework/init.lua") diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_ammorack_fire.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_ammorack_fire.lua new file mode 100644 index 0000000..aa25b09 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_ammorack_fire.lua @@ -0,0 +1,179 @@ + +EFFECT.FireMat = Material( "effects/fire_cloud1" ) +EFFECT.HeatMat = Material( "sprites/heatwave" ) + +EFFECT.Smoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + self.LifeTime = 0.4 + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( Ent ) then return end + + self.Ent = Ent + self.Pos = Ent:WorldToLocal( Pos + VectorRand() * 3 ) + self.Seed = math.Rand( 0, 10000 ) + self.Magnitude = data:GetMagnitude() + + local emitter = Ent:GetParticleEmitter( self.Pos ) + + if not IsValid( emitter ) then return end + + local VecCol = (render.GetLightColor( Pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + for i = 0, 20 do + local particle = emitter:Add( "sprites/rico1", Pos ) + + local vel = VectorRand() * 800 + + if particle then + particle:SetVelocity( Vector(0,0,500) + VectorRand() * 500 ) + particle:SetDieTime( 0.25 * self.Magnitude ) + particle:SetStartAlpha( 200 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 3 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetRollDelta( math.Rand(-1,1) * 3 ) + particle:SetColor( 255, 255, 255 ) + particle:SetAirResistance( 0 ) + end + end + + for i = 1, 8 do + local particle = emitter:Add( self.Smoke[ math.random(1, #self.Smoke ) ], Pos ) + + local Dir = Angle(0,math.Rand(-180,180),0):Forward() + Dir.z = -0.5 + Dir:Normalize() + + if particle then + particle:SetVelocity( Dir * 250 ) + particle:SetDieTime( 0.5 + i * 0.01 ) + particle:SetAirResistance( 125 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 40 ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetRollDelta( math.Rand(-1,1) * 3 ) + particle:SetColor( 0, 0, 0 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0 ) + end + end + + for i = 0,22 do + local particle = emitter:Add( "particles/flamelet"..math.random(1,5), Pos ) + + local Dir = Angle(0,math.Rand(-180,180),0):Forward() + + if particle then + particle:SetVelocity( Dir * math.Rand(600,900) * self.Magnitude ) + particle:SetDieTime( math.Rand(0.2,0.3) * self.Magnitude ) + particle:SetAirResistance( 400 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(20,25) ) + particle:SetEndSize( math.Rand(5,10) ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetRollDelta( math.Rand(-1,1) * 3 ) + particle:SetColor( 255, 200, 50 ) + particle:SetGravity( Vector( 0, 0, 1000 ) ) + particle:SetCollide( false ) + end + end + + for i = 1, 4 do + local particle = emitter:Add( self.Smoke[ math.random(1, #self.Smoke ) ], Pos ) + + local Dir = Angle(0,math.Rand(-180,180),0):Forward() + + if particle then + particle:SetVelocity( Dir * 500 * self.Magnitude ) + particle:SetDieTime( 0.5 + i * 0.01 ) + particle:SetAirResistance( 125 ) + particle:SetStartAlpha( 150 * self.Magnitude ) + particle:SetStartSize( 40 * self.Magnitude ) + particle:SetEndSize( 250 * self.Magnitude ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetRollDelta( math.Rand(-1,1) * 3 ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0 ) + end + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then return false end + + if self.DieTime < CurTime() then return false end + + self:SetPos( self.Ent:LocalToWorld( self.Pos ) ) + + return true +end + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.Pos then return end + + self:RenderFire() +end + +function EFFECT:RenderFire() + local Scale = ((self.DieTime - CurTime()) / self.LifeTime) * (self.Magnitude or 0) + + if Scale < 0 then return end + + local Pos = self.Ent:LocalToWorld( self.Pos ) + + local scroll = -CurTime() * 10 + + local Up = Vector(0,0,0.92) + VectorRand() * 0.08 + + render.SetMaterial( self.FireMat ) + render.StartBeam( 3 ) + render.AddBeam( Pos, 64 * Scale, scroll, Color( 100, 100, 100, 100 ) ) + render.AddBeam( Pos + Up * 120 * Scale, 64 * Scale, scroll + 1, Color( 255, 200, 50, 150 ) ) + render.AddBeam( Pos + Up * 300 * Scale, 64 * Scale, scroll + 3, Color( 255, 191, 0, 0 ) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( self.HeatMat ) + render.StartBeam( 3 ) + render.AddBeam( Pos, 64 * Scale, scroll, Color( 0, 0, 255, 200 ) ) + render.AddBeam( Pos + Up * 64 * Scale, 64 * Scale, scroll + 2, color_white ) + render.AddBeam( Pos + Up * 250 * Scale, 120 * Scale, scroll + 5, Color( 0, 0, 0, 0 ) ) + render.EndBeam() + + scroll = scroll * 1.3 + render.SetMaterial( self.FireMat ) + render.StartBeam( 3 ) + render.AddBeam( Pos, 32 * Scale, scroll, Color( 100, 100, 100, 100 ) ) + render.AddBeam( Pos + Up * 60 * Scale, 32 * Scale, scroll + 1, Color( 255, 200, 50, 150 ) ) + render.AddBeam( Pos + Up * 300 * Scale, 32 * Scale, scroll + 3, Color( 255, 191, 0, 0 ) ) + render.EndBeam() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact.lua new file mode 100644 index 0000000..5b10ebb --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact.lua @@ -0,0 +1,110 @@ + +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +EFFECT.DustMat = { + "effects/lvs_base/particle_debris_01", + "effects/lvs_base/particle_debris_02", +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + local scale = data:GetMagnitude() + + sound.Play( "physics/flesh/flesh_strider_impact_bullet"..math.random(1,3)..".wav", pos, 85, math.random(180,200) + 55 * math.max(1 - scale,0), 0.75 ) + sound.Play( "ambient/materials/rock"..math.random(1,5)..".wav", pos, 75, 180, 1 ) + + local emitter = ParticleEmitter( pos, false ) + + local VecCol = (render.GetLightColor( pos + dir ) * 0.5 + Vector(0.2,0.18,0.15)) * 255 + + local DieTime = math.Rand(0.8,1.6) + + if dir.z > 0.85 then + for i = 1, 10 do + for n = 0,6 do + local particle = emitter:Add( self.DustMat[ math.random(1,#self.DustMat) ] , pos ) + + if not particle then continue end + + particle:SetVelocity( (dir * 50 * i + VectorRand() * 25) * scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * scale ) + particle:SetEndSize( 20 * i * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-600) * scale ) + particle:SetCollide( false ) + end + end + + for i = 1, 10 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , pos ) + + if not particle then continue end + + particle:SetVelocity( (dir * 50 * i + VectorRand() * 40) * scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * scale ) + particle:SetEndSize( 20 * i * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-600) * scale ) + particle:SetCollide( false ) + end + end + + for i = 1,12 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , pos ) + + if particle then + local ang = i * 30 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + local Vel = Vector(X,Y,0) * math.Rand(200,1600) + Vector(0,0,50) + Vel:Rotate( dir:Angle() + Angle(90,0,0) ) + + particle:SetVelocity( Vel * scale ) + particle:SetDieTime( DieTime ) + particle:SetAirResistance( 500 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 40 * scale ) + particle:SetEndSize( 200 * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,60) * scale ) + particle:SetCollide( true ) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact_ap.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact_ap.lua new file mode 100644 index 0000000..ac4ae86 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact_ap.lua @@ -0,0 +1,229 @@ + +EFFECT.DustMat = { + "effects/lvs_base/particle_debris_01", + "effects/lvs_base/particle_debris_02", +} + +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +EFFECT.SparkSurface = { + ["chainlink"] = true, + ["canister"] = true, + ["metal_barrel"] = true, + ["metalvehicle"] = true, + ["metal"] = true, + ["metalgrate"] = true, + ["rubbertire"] = true, +} + +EFFECT.DustSurface = { + ["sand"] = true, + ["dirt"] = true, + ["grass"] = true, + ["antlionsand"] = true, +} + +EFFECT.SmokeSurface = { + ["concrete"] = true, + ["tile"] = true, + ["plaster"] = true, + ["boulder"] = true, + ["plastic"] = true, + ["default"] = true, + ["glass"] = true, + ["brick"] = true, +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + + local bullet_dir = data:GetStart() + local dir = data:GetNormal() + local magnitude = data:GetMagnitude() + + local ent = data:GetEntity() + local surface = data:GetSurfaceProp() + local surfaceName = util.GetSurfacePropName( surface ) + + local emitter = ParticleEmitter( pos, false ) + + local VecCol = (render.GetLightColor( pos ) * 0.8 + Vector(0.17,0.15,0.1)) * 255 + + local DieTime = math.Rand(0.8,1.4) + + for i = 1, 60 * magnitude do + local spark = emitter:Add("effects/spark", pos + dir * 8) + + if not spark then continue end + + spark:SetStartAlpha( 255 ) + spark:SetEndAlpha( 0 ) + spark:SetCollide( true ) + spark:SetBounce( math.Rand(0,1) ) + spark:SetColor( 255, 255, 255 ) + spark:SetGravity( Vector(0,0,-600) ) + spark:SetEndLength(0) + + local size = math.Rand(4, 6) * magnitude + spark:SetEndSize( size ) + spark:SetStartSize( size ) + + spark:SetStartLength( math.Rand(20,40) * magnitude ) + spark:SetDieTime( math.Rand(0.4, 1.2) ) + spark:SetVelocity( (dir * math.Rand(300, 600) + VectorRand() * 300) * magnitude ) + end + + local flash = emitter:Add( "effects/yellowflare",pos ) + + if flash then + flash:SetPos( pos + dir * 15 ) + flash:SetStartAlpha( 200 ) + flash:SetEndAlpha( 0 ) + flash:SetColor( 255,255,255 ) + flash:SetEndSize( 0 ) + flash:SetDieTime( 0.075 ) + flash:SetStartSize( 300 * magnitude ^ 2 ) + end + + if self.SparkSurface[ surfaceName ] then + if IsValid( ent ) and ent.LVS then + if (90 - math.deg( math.acos( math.Clamp( -dir:Dot( bullet_dir ) ,-1,1) ) )) > 10 then + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + util.Effect( "cball_explode", effectdata, true, true ) + + local Ax = math.acos( math.Clamp( dir:Dot( bullet_dir ) ,-1,1) ) + local Fx = math.cos( Ax ) + + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetNormal( (bullet_dir - dir * Fx * 2):GetNormalized() * 0.75 ) + util.Effect( "manhacksparks", effectdata, true, true ) + + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetNormal( -bullet_dir * 0.75 ) + util.Effect( "manhacksparks", effectdata, true, true ) + end + else + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + util.Effect( "cball_explode", effectdata, true, true ) + + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetNormal( dir ) + util.Effect( "manhacksparks", effectdata, true, true ) + + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetNormal( -bullet_dir ) + util.Effect( "manhacksparks", effectdata, true, true ) + end + end + + if self.SmokeSurface[ surfaceName ] then + for i = 1, 24 do + local particle = emitter:Add( self.SmokeMat[ math.random(1, #self.SmokeMat ) ], pos ) + + if not particle then continue end + + particle:SetStartAlpha( math.Rand(33, 66) ) + particle:SetEndAlpha( 0 ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-math.Rand(33, 66)) ) + particle:SetRollDelta( math.random(0, 0.5 * math.pi) ) + particle:SetAirResistance( 175 ) + + particle:SetStartSize( 15 ) + particle:SetDieTime( math.Rand(0.5, 1) ) + particle:SetEndSize( math.Rand(45, 90) ) + particle:SetVelocity( dir * math.Rand(40, 200) + VectorRand() * 150) + end + + for i = 1,15 do + local particle = emitter:Add("effects/fleck_cement" .. math.random(1, 2), pos + dir * 8) + + if not particle then continue end + + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetCollide( true ) + particle:SetBounce( math.Rand(0,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity(Vector(0,0,-600)) + particle:SetRollDelta( math.random(0, 0.5*math.pi) ) + + particle:SetEndSize( 2 ) + particle:SetStartSize( 2 ) + + particle:SetDieTime( math.Rand(1, 2) ) + particle:SetVelocity( dir * math.Rand(40, 200) + VectorRand() * 500 ) + end + end + + if not self.DustSurface[ surfaceName ] then return end + + for i = 1, 10 do + for i = 1, 15 do + local particle = emitter:Add( self.SmokeMat[ math.random(1, #self.SmokeMat ) ], pos ) + + if not particle then continue end + + particle:SetStartAlpha( math.Rand(40, 80) ) + particle:SetEndAlpha(0) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-math.Rand(75, 150)) ) + particle:SetRollDelta( math.random(0, 0.5*math.pi) ) + particle:SetAirResistance( 175 ) + + particle:SetStartSize( 5 ) + particle:SetDieTime( math.Rand(0.5, 1) ) + particle:SetEndSize( math.Rand(15, 30) ) + particle:SetVelocity( (dir * math.Rand(80, 400) + VectorRand() * 100) * 1.5 ) + end + + for n = 0,6 do + local particle = emitter:Add( self.DustMat[ math.random(1,#self.DustMat) ] , pos ) + + if not particle then continue end + + particle:SetVelocity( (dir * 50 * i + VectorRand() * 50) ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 10 * i ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-600) ) + particle:SetCollide( false ) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact_explosive.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact_explosive.lua new file mode 100644 index 0000000..333fe91 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_bullet_impact_explosive.lua @@ -0,0 +1,277 @@ + +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +EFFECT.DustMat = { + "effects/lvs_base/particle_debris_01", + "effects/lvs_base/particle_debris_02", +} + +EFFECT.DecalMat = Material( util.DecalMaterial( "Scorch" ) ) + +function EFFECT:Init( data ) + self.Dir = data:GetNormal() + self.Pos = data:GetOrigin() + + self.LifeTime = 0.35 + self.DieTime = CurTime() + self.LifeTime + + local scale = data:GetMagnitude() * 0.5 + + self.Scale = 3 * scale + + local emitter = ParticleEmitter( self.Pos, false ) + + local VecCol = (render.GetLightColor( self.Pos + self.Dir ) * 0.5 + Vector(0.1,0.09,0.075)) * 255 + + local DieTime = math.Rand(0.8,1.6) + + local traceSky = util.TraceLine( { + start = self.Pos, + endpos = self.Pos + Vector(0,0,50000), + filter = self, + } ) + + local traceWater = util.TraceLine( { + start = traceSky.HitPos, + endpos = self.Pos - Vector(0,0,100), + filter = self, + mask = MASK_WATER, + } ) + + if traceWater.Hit then + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.HitPos ) + effectdata:SetScale( 100 ) + effectdata:SetFlags( 2 ) + util.Effect( "WaterSplash", effectdata, true, true ) + end + + local Pos = self.Pos + local Dist = (traceWater.HitPos - Pos):Length() + local ply = LocalPlayer():GetViewEntity() + + if not IsValid( ply ) then return end + + local delay = (Pos - ply:GetPos()):Length() / 13503.9 + + if traceWater.Hit and Dist > 150 then + timer.Simple( delay, function() + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + util.Effect( "WaterSurfaceExplosion", effectdata, true, true ) + end ) + + if Dist > 300 then return end + else + timer.Simple( delay, function() + sound.Play( "LVS.BULLET_EXPLOSION", Pos ) + sound.Play( "LVS.BULLET_EXPLOSION_DYNAMIC", Pos ) + end ) + + local trace = util.TraceLine( { + start = self.Pos + Vector(0,0,100), + endpos = self.Pos - Vector(0,0,100), + mask = MASK_SOLID_BRUSHONLY, + } ) + + if trace.Hit and not trace.HitNonWorld then + util.DecalEx( self.DecalMat, trace.Entity, trace.HitPos + trace.HitNormal, trace.HitNormal, Color(255,255,255,255), self.Scale * 2.5, self.Scale * 2.5 ) + end + end + + if self.Dir.z > 0.8 then + for i = 1, 10 do + for n = 0,6 do + local particle = emitter:Add( self.DustMat[ math.random(1,#self.DustMat) ], self.Pos ) + + if not particle then continue end + + particle:SetVelocity( (self.Dir * 50 * i + VectorRand() * 25) * self.Scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * self.Scale ) + particle:SetEndSize( 20 * i * self.Scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-600) * self.Scale ) + particle:SetCollide( false ) + end + end + + for i = 1, 10 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ], self.Pos ) + + if not particle then continue end + + particle:SetVelocity( (self.Dir * 50 * i + VectorRand() * 40) * self.Scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * self.Scale ) + particle:SetEndSize( 20 * i * self.Scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-600) * self.Scale ) + particle:SetCollide( false ) + end + end + + for i = 1,24 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , self.Pos ) + + if not particle then continue end + + local ang = i * 15 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + local Vel = Vector(X,Y,0) * math.Rand(800,1200) + Vel:Rotate( self.Dir:Angle() + Angle(90,0,0) ) + + particle:SetVelocity( Vel * self.Scale ) + particle:SetDieTime( math.Rand(1,3) ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 40 * self.Scale ) + particle:SetEndSize( 140 * self.Scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,60) * self.Scale ) + particle:SetCollide( true ) + end + + for i = 0, 15 do + local particle = emitter:Add( self.SmokeMat[ math.random(1, #self.SmokeMat ) ], self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 1000 * scale ) + particle:SetDieTime( math.Rand(2,3) ) + particle:SetAirResistance( 200 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 200 * scale ) + particle:SetEndSize( 600 * scale ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector( 0, 0, -600 ) * scale ) + particle:SetCollide( false ) + end + end + + for i = 0, 20 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 800 * scale ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 120 * scale ) + particle:SetEndSize( 20 * scale ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( self.Dir * 2500 ) + particle:SetRollDelta( math.Rand(-5,5) ) + particle:SetAirResistance( 300 ) + end + + for i = 0, 5 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Pos ) + + if not particle then continue end + + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( Vector(0,0,0) ) + + local size = math.Rand(8, 24) * scale + particle:SetEndSize( size ) + particle:SetStartSize( size ) + + particle:SetStartLength( 400 * scale ) + particle:SetEndLength( size ) + + particle:SetDieTime( math.Rand(0.1,0.2) ) + particle:SetVelocity( (self.Dir * 4000 + VectorRand() * 2000) * scale ) + + particle:SetAirResistance( 0 ) + end + + for i = 0, 40 do + local particle = emitter:Add( "effects/fire_embers"..math.random(1,2), self.Pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 800 * scale ) + particle:SetDieTime( math.Rand(0.4,0.6) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 40 * scale ) + particle:SetEndSize( 0 ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( Vector(0,0,600) ) + particle:SetRollDelta( math.Rand(-8,8) ) + particle:SetAirResistance( 300 ) + end + + emitter:Finish() +end + +function EFFECT:Explosion( pos , scale ) + local emitter = ParticleEmitter( pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0, 40 do + local particle = emitter:Add( "particles/flamelet"..math.random(1,5), pos ) + + if particle then + particle:SetVelocity( VectorRand() * 1500 * scale ) + particle:SetDieTime( 0.2 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 20 * scale ) + particle:SetEndSize( math.Rand(180,240) * scale ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + if not self.Scale then return end + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + local R1 = 800 * self.Scale + render.SetMaterial( self.GlowMat ) + render.DrawSprite( self.Pos, R1 * Scale, R1 * Scale, Color( 255, 200, 150, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_blacksmoke.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_blacksmoke.lua new file mode 100644 index 0000000..327319a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_blacksmoke.lua @@ -0,0 +1,57 @@ +EFFECT.Smoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + local emitter = Ent:GetParticleEmitter( Ent:WorldToLocal( Pos ) ) + + if not IsValid( emitter ) then return end + + local particle = emitter:Add( self.Smoke[ math.random(1, #self.Smoke ) ], Pos ) + + local rCol = math.random(30,60) + + local Scale = math.Rand(1,8) + + if not particle then return end + + particle:SetVelocity( Vector(0,0,80) + VectorRand() * 80 ) + particle:SetDieTime( Scale ) + particle:SetAirResistance( 200 ) + particle:SetStartAlpha( 100 / Scale ) + particle:SetStartSize( 20 ) + particle:SetEndSize( math.random(15,30) * Scale ) + particle:SetRoll( math.pi / Scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( rCol, rCol, rCol ) + particle:SetGravity( Vector( 0, 0, math.random(-40,80) ) ) + particle:SetCollide( true ) + particle:SetBounce( 0 ) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_fire.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_fire.lua new file mode 100644 index 0000000..b33acf4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_fire.lua @@ -0,0 +1,130 @@ + +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) +EFFECT.FireMat = { + [1] = Material( "effects/lvs_base/flamelet1" ), + [2] = Material( "effects/lvs_base/flamelet2" ), + [3] = Material( "effects/lvs_base/flamelet3" ), + [4] = Material( "effects/lvs_base/flamelet4" ), + [5] = Material( "effects/lvs_base/flamelet5" ), + [6] = Material( "effects/lvs_base/fire" ), +} + +EFFECT.SmokeMat = { + [1] = Material( "particle/smokesprites_0001" ), + [2] = Material( "particle/smokesprites_0002" ), + [3] = Material( "particle/smokesprites_0003" ), + [4] = Material( "particle/smokesprites_0004" ), + [5] = Material( "particle/smokesprites_0005" ), + [6] = Material( "particle/smokesprites_0006" ), + [7] = Material( "particle/smokesprites_0007" ), + [8] = Material( "particle/smokesprites_0008" ), + [9] = Material( "particle/smokesprites_0009" ), + [10] = Material( "particle/smokesprites_0010" ), + [11] = Material( "particle/smokesprites_0011" ), + [12] = Material( "particle/smokesprites_0012" ), + [13] = Material( "particle/smokesprites_0013" ), + [14] = Material( "particle/smokesprites_0014" ), + [15] = Material( "particle/smokesprites_0015" ), + [16] = Material( "particle/smokesprites_0016" ), +} + +EFFECT.Smoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + self.LifeTime = 1 + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( Ent ) then return end + + local randomPos = VectorRand() * 15 + randomPos.z = 0 + + self.Ent = Ent + self.Pos = Ent:WorldToLocal( Pos + randomPos ) + self.RandomSize = math.Rand( 0.8, 1.6 ) + self.Vel = self.Ent:GetVelocity() + + local emitter = Ent:GetParticleEmitter( self.Pos ) + + if not IsValid( emitter ) then return end + + for i = 1, 8 do + local particle = emitter:Add( self.Smoke[ math.random(1, #self.Smoke ) ], Pos ) + + local Dir = Angle(0,math.Rand(-180,180),0):Forward() + Dir.z = -0.5 + Dir:Normalize() + + if particle then + particle:SetVelocity( Dir * 250 ) + particle:SetDieTime( 0.5 + i * 0.01 ) + particle:SetAirResistance( 125 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 40 ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetRollDelta( math.Rand(-1,1) * 3 ) + particle:SetColor( 0, 0, 0 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0 ) + end + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then return false end + + if self.DieTime < CurTime() then return false end + + self:SetPos( self.Ent:LocalToWorld( self.Pos ) ) + + return true +end + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.Pos then return end + + self:RenderFire() +end + +function EFFECT:RenderFire() + local Scale = (self.DieTime - 0.4 - CurTime()) / 0.6 + + if Scale < 0 then return end + + local Pos = self.Ent:LocalToWorld( self.Pos ) + + local InvScale = 1 - Scale + + render.SetMaterial( self.GlowMat ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 10), 100 * InvScale, 100 * InvScale, Color( 255, 150, 75, 255) ) + + local Num = #self.FireMat - math.Clamp(math.ceil( Scale * #self.FireMat ) - 1,0, #self.FireMat - 1) + + local Size = (5 + 20 * Scale) * self.RandomSize + local Offset = (self.Vel * InvScale ^ 2) * 0.025 + + render.SetMaterial( self.FireMat[ Num ] ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 30) - Offset, Size, Size, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_smoke.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_smoke.lua new file mode 100644 index 0000000..50479af --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_carengine_smoke.lua @@ -0,0 +1,71 @@ + +EFFECT.SmokeMat = { + [1] = Material( "particle/smokesprites_0001" ), + [2] = Material( "particle/smokesprites_0002" ), + [3] = Material( "particle/smokesprites_0003" ), + [4] = Material( "particle/smokesprites_0004" ), + [5] = Material( "particle/smokesprites_0005" ), + [6] = Material( "particle/smokesprites_0006" ), + [7] = Material( "particle/smokesprites_0007" ), + [8] = Material( "particle/smokesprites_0008" ), + [9] = Material( "particle/smokesprites_0009" ), + [10] = Material( "particle/smokesprites_0010" ), + [11] = Material( "particle/smokesprites_0011" ), + [12] = Material( "particle/smokesprites_0012" ), + [13] = Material( "particle/smokesprites_0013" ), + [14] = Material( "particle/smokesprites_0014" ), + [15] = Material( "particle/smokesprites_0015" ), + [16] = Material( "particle/smokesprites_0016" ), +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + local Mag = data:GetMagnitude() + + self.LifeTime = 0.5 + Mag * 0.5 + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( Ent ) then return end + + self.Mag = Mag + self.Ent = Ent + self.Pos = Ent:WorldToLocal( Pos + VectorRand() * 15 ) + self.RandomSize = math.Rand( 0.8, 1.6 ) + self.Vel = self.Ent:GetVelocity() +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then return false end + + if self.DieTime < CurTime() then return false end + + self:SetPos( self.Ent:LocalToWorld( self.Pos ) ) + + return true +end + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.Pos then return end + + self:RenderSmoke() +end + +function EFFECT:RenderSmoke() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + + local Pos = self.Ent:LocalToWorld( self.Pos ) + + local InvScale = 1 - Scale + + local Num = #self.SmokeMat - math.Clamp(math.ceil( Scale * #self.SmokeMat ) - 1,0, #self.SmokeMat - 1) + + local A = (50 + 100 * (1 - self.Mag)) * Scale + local C = (20 + 30 * self.RandomSize * self.Mag) + + local Size = (25 + 30 * InvScale) * self.RandomSize + local Offset = (self.Vel * InvScale ^ 2) * 0.15 + + render.SetMaterial( self.SmokeMat[ Num ] ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * (20 + self.Vel:Length() / 25) * self.RandomSize) - Offset, Size, Size, Color( C, C, C, A ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_carexhaust_backfire.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_carexhaust_backfire.lua new file mode 100644 index 0000000..eff86ae --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_carexhaust_backfire.lua @@ -0,0 +1,127 @@ + +EFFECT.SmokeMat = { + [1] = Material( "particle/smokesprites_0001" ), + [2] = Material( "particle/smokesprites_0002" ), + [3] = Material( "particle/smokesprites_0003" ), + [4] = Material( "particle/smokesprites_0004" ), + [5] = Material( "particle/smokesprites_0005" ), + [6] = Material( "particle/smokesprites_0006" ), + [7] = Material( "particle/smokesprites_0007" ), + [8] = Material( "particle/smokesprites_0008" ), + [9] = Material( "particle/smokesprites_0009" ), + [10] = Material( "particle/smokesprites_0010" ), + [11] = Material( "particle/smokesprites_0011" ), + [12] = Material( "particle/smokesprites_0012" ), + [13] = Material( "particle/smokesprites_0013" ), + [14] = Material( "particle/smokesprites_0014" ), + [15] = Material( "particle/smokesprites_0015" ), + [16] = Material( "particle/smokesprites_0016" ), +} + +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) +EFFECT.FireMat = Material( "effects/muzzleflash2" ) + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + self.Ang = data:GetAngles() + self.Ent = data:GetEntity() + + self.LifeTime = 0.5 + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( self.Ent ) then return end + + self.Mat = self.SmokeMat[ math.random(1,#self.SmokeMat) ] + + local Pos = self.Ent:LocalToWorld( self.Pos ) + + self:SetPos( Pos ) + + local dlight = DynamicLight( self.Ent:EntIndex() * math.random(1,4), true ) + if dlight then + dlight.pos = Pos + dlight.r = 255 + dlight.g = 180 + dlight.b = 100 + dlight.brightness = 1 + dlight.Decay = 2000 + dlight.Size = 300 + dlight.DieTime = CurTime() + 0.2 + end + + sound.Play( "lvs/vehicles/generic/exhaust_backfire"..math.random(1,3)..".ogg", Pos, 75, 100, 1 ) + + local vel = self.Ent:GetVelocity() + local dir = self.Ent:LocalToWorldAngles( self.Ang ):Forward() + local emitter = ParticleEmitter( Pos, false ) + + if emitter then + for i = 0, 12 do + local particle = emitter:Add( "sprites/rico1", Pos ) + + if not particle then continue end + + particle:SetVelocity( vel + dir * 100 + VectorRand() * 100 ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetStartAlpha( 0 ) + particle:SetEndAlpha( 25 ) + particle:SetStartSize( 1 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + particle:SetAirResistance( 0 ) + particle:SetColor( 255, 225, 150 ) + particle:SetGravity( Vector(0,0,-600) ) + end + + emitter:Finish() + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then return false end + + if self.DieTime < CurTime() then return false end + + self:SetPos( self.Ent:LocalToWorld( self.Pos ) ) + + return true +end + + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.Pos then return end + + self:RenderSmoke() +end + +function EFFECT:RenderSmoke() + if not self.Pos or not self.Ang then return end + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + local InvScale = 1 - Scale + + local Pos = self.Ent:LocalToWorld( self.Pos ) + local Ang = self.Ent:LocalToWorldAngles( self.Ang ) + + local FlameSize = 40 * Scale ^ 2 + render.SetMaterial( self.FireMat ) + for i = 1, 4 do + render.DrawSprite( Pos + Ang:Forward() * InvScale * 5 + VectorRand() * 2, FlameSize, FlameSize, Color( 255, 255, 255, 255 * InvScale) ) + end + + local GlowSize = 80 * Scale ^ 2 + render.SetMaterial( self.GlowMat ) + render.DrawSprite( Pos + Ang:Forward() * InvScale * 10, GlowSize, GlowSize, Color(255* InvScale, 150* InvScale,75* InvScale,255* InvScale) ) + + if not self.Mat then return end + + local C = 40 + local Size = (20 + 50 * InvScale) + + render.SetMaterial( self.Mat ) + render.DrawSprite( Pos + Ang:Forward() * InvScale * 40, Size, Size, Color( C, C, C, 255 * Scale) ) +end + diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_carexhaust_pop.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_carexhaust_pop.lua new file mode 100644 index 0000000..8a57063 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_carexhaust_pop.lua @@ -0,0 +1,105 @@ + +EFFECT.GlowMat = Material( "effects/yellowflare" ) +EFFECT.FireMat = Material( "effects/muzzleflash2" ) + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + self.Ang = data:GetAngles() + self.Ent = data:GetEntity() + + local volume = math.Clamp( data:GetMagnitude(), 0, 1 ) + + local T = CurTime() + + self.LifeTime = 0.25 + self.LifeTimePop = 0.1 + + self.DieTime = T + self.LifeTime + self.DieTimePop = T + self.LifeTimePop + + self.Scale = math.Rand( 0.25, 1 ) + + if not IsValid( self.Ent ) then return end + + local Pos = self.Ent:LocalToWorld( self.Pos ) + + self:SetPos( Pos ) + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local veh = ply:lvsGetVehicle() + + if IsValid( veh ) and veh == self.Ent then + local pod = ply:GetVehicle() + + if IsValid( pod ) and not pod:GetThirdPersonMode() then + sound.Play( "lvs/vehicles/generic/exhaust_pop_interior"..math.random(1,12)..".ogg", Pos, 75, math.random(98,105), volume ) + + return + end + end + + local dlight = DynamicLight( self.Ent:EntIndex() * math.random(1,4), true ) + + if dlight then + dlight.pos = Pos + dlight.r = 255 + dlight.g = 180 + dlight.b = 100 + dlight.brightness = 1 + dlight.Decay = 2000 + dlight.Size = 400 + dlight.DieTime = CurTime() + 0.2 + end + + sound.Play( "lvs/vehicles/generic/exhaust_pop"..math.random(1,16)..".ogg", Pos, 75, math.random(98,105), volume ) +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then return false end + + if self.DieTime < CurTime() then return false end + + self:SetPos( self.Ent:LocalToWorld( self.Pos ) ) + + return true +end + + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.Pos then return end + + self:RenderSmoke() +end + +function EFFECT:RenderSmoke() + if not self.Pos or not self.Ang or not self.Scale then return end + + local T = CurTime() + + local ScalePop = math.Clamp( (self.DieTimePop - T) / self.LifeTimePop, 0, 1 ) + local InvScalePop = 1 - ScalePop + + local Scale = (self.DieTime - T) / self.LifeTime + local InvScale = 1 - Scale + + local Pos = self.Ent:LocalToWorld( self.Pos ) + local Ang = self.Ent:LocalToWorldAngles( self.Ang ) + + local FlameSize = 5 * Scale ^ 2 + render.SetMaterial( self.FireMat ) + for i = 1, 12 do + render.DrawSprite( Pos + Ang:Forward() * InvScale * 20 + VectorRand() * 2, FlameSize, FlameSize, color_white ) + end + + if InvScalePop <= 0 then return end + + local GlowSize = 60 * InvScalePop * self.Scale + local A255 = 255 * ScalePop + + render.SetMaterial( self.GlowMat ) + render.DrawSprite( Pos, GlowSize, GlowSize, Color(A255,A255,A255,A255) ) +end + diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_carfueltank_fire.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_carfueltank_fire.lua new file mode 100644 index 0000000..c3ea0f2 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_carfueltank_fire.lua @@ -0,0 +1,145 @@ + +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) +EFFECT.FireMat = { + [1] = Material( "effects/lvs_base/flamelet1" ), + [2] = Material( "effects/lvs_base/flamelet2" ), + [3] = Material( "effects/lvs_base/flamelet3" ), + [4] = Material( "effects/lvs_base/flamelet4" ), + [5] = Material( "effects/lvs_base/flamelet5" ), + [6] = Material( "effects/lvs_base/fire" ), +} + +EFFECT.SmokeMat = { + [1] = Material( "particle/smokesprites_0001" ), + [2] = Material( "particle/smokesprites_0002" ), + [3] = Material( "particle/smokesprites_0003" ), + [4] = Material( "particle/smokesprites_0004" ), + [5] = Material( "particle/smokesprites_0005" ), + [6] = Material( "particle/smokesprites_0006" ), + [7] = Material( "particle/smokesprites_0007" ), + [8] = Material( "particle/smokesprites_0008" ), + [9] = Material( "particle/smokesprites_0009" ), + [10] = Material( "particle/smokesprites_0010" ), + [11] = Material( "particle/smokesprites_0011" ), + [12] = Material( "particle/smokesprites_0012" ), + [13] = Material( "particle/smokesprites_0013" ), + [14] = Material( "particle/smokesprites_0014" ), + [15] = Material( "particle/smokesprites_0015" ), + [16] = Material( "particle/smokesprites_0016" ), +} + +EFFECT.Smoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + self.LifeTime = 1 + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( Ent ) then return end + + self.Ent = Ent + self.Pos = Ent:WorldToLocal( Pos + VectorRand() * 20 ) + self.RandomSize = math.Rand( 1, 2 ) + self.Vel = self.Ent:GetVelocity() + + local emitter = Ent:GetParticleEmitter( self.Pos ) + + if not IsValid( emitter ) then return end + + for i = 1, 8 do + local particle = emitter:Add( self.Smoke[ math.random(1, #self.Smoke ) ], Pos ) + + local Dir = Angle(0,math.Rand(-180,180),0):Forward() + Dir.z = -0.5 + Dir:Normalize() + + if particle then + particle:SetVelocity( Dir * 250 ) + particle:SetDieTime( 0.5 + i * 0.01 ) + particle:SetAirResistance( 125 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 40 ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetRollDelta( math.Rand(-1,1) * 3 ) + particle:SetColor( 0, 0, 0 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0 ) + end + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then return false end + + if self.DieTime < CurTime() then return false end + + self:SetPos( self.Ent:LocalToWorld( self.Pos ) ) + + return true +end + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.Pos then return end + + self:RenderSmoke() + self:RenderFire() +end + +function EFFECT:RenderSmoke() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + + local Pos = self.Ent:LocalToWorld( self.Pos ) + Vector(0,0,25) + + local InvScale = 1 - Scale + + local Num = #self.SmokeMat - math.Clamp(math.ceil( Scale * #self.SmokeMat ) - 1,0, #self.SmokeMat - 1) + + local C = 10 + 10 * self.RandomSize + local Size = (25 + 30 * InvScale) * self.RandomSize + local Offset = (self.Vel * InvScale ^ 2) * 0.5 + + render.SetMaterial( self.SmokeMat[ Num ] ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 80 * self.RandomSize) - Offset, Size, Size, Color( C, C, C, 200 * Scale) ) +end + +function EFFECT:RenderFire() + local Scale = (self.DieTime - 0.4 - CurTime()) / 0.6 + + if Scale < 0 then return end + + local Pos = self.Ent:LocalToWorld( self.Pos ) + + local InvScale = 1 - Scale + + render.SetMaterial( self.GlowMat ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 10), 100 * InvScale, 100 * InvScale, Color( 255, 150, 75, 255) ) + + local Num = #self.FireMat - math.Clamp(math.ceil( Scale * #self.FireMat ) - 1,0, #self.FireMat - 1) + + local Size = (10 + 20 * Scale) * self.RandomSize + local Offset = (self.Vel * InvScale ^ 2) * 0.025 + + render.SetMaterial( self.FireMat[ Num ] ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 25) - Offset, Size, Size, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_concussion_explosion.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_concussion_explosion.lua new file mode 100644 index 0000000..c9c384c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_concussion_explosion.lua @@ -0,0 +1,119 @@ + +local GlowMat = Material( "sprites/light_glow02_add" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + + self.LifeTime = 0.4 + self.DieTime = CurTime() + self.LifeTime + + local emitter = ParticleEmitter( self.Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0,30 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 800 ) + particle:SetDieTime( math.Rand(4,6) ) + particle:SetAirResistance( math.Rand(200,600) ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( math.Rand(30,60) ) + particle:SetEndSize( math.Rand(100,150) ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 50,50,50 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + end + + for i = 0, 20 do + local particle = emitter:Add( "sprites/light_glow02_add", self.Pos ) + + local vel = VectorRand() * 400 + + if particle then + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.4,0.8) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(24,48) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( 255, 40, 100 ) + particle:SetGravity( Vector(0,0,-600) ) + + particle:SetAirResistance( 0 ) + + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + end + end + + for i = 0, 40 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 500 ) + particle:SetDieTime( 0.14 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( math.Rand(30,60) ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + emitter:Finish() + + local Pos = self.Pos + local ply = LocalPlayer():GetViewEntity() + if IsValid( ply ) then + local delay = (Pos - ply:GetPos()):Length() / 13503.9 + if delay <= 0.11 then + sound.Play( "ambient/explosions/explode_9.wav", Pos, 85, 100, 1 - delay * 8 ) + end + + timer.Simple( delay, function() + sound.Play( "LVS.MISSILE_EXPLOSION", Pos ) + end ) + else + sound.Play( "LVS.MISSILE_EXPLOSION", Pos ) + end +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + render.SetMaterial( GlowMat ) + render.DrawSprite( self.Pos, 400 * Scale, 400 * Scale, Color( 255, 40, 100, 255) ) + render.DrawSprite( self.Pos, 100 * Scale, 100 * Scale, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_concussion_trail.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_concussion_trail.lua new file mode 100644 index 0000000..b7168cd --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_concussion_trail.lua @@ -0,0 +1,124 @@ +EFFECT.Offset = Vector(-8,0,0) + +local GlowMat = Material( "effects/select_ring" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.OldPos = self.Entity:LocalToWorld( self.Offset ) + + self.Emitter = ParticleEmitter( self.Entity:LocalToWorld( self.OldPos ), false ) + end +end + +function EFFECT:doFX( pos ) + if not IsValid( self.Entity ) then return end + + if IsValid( self.Emitter ) then + local emitter = self.Emitter + + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + if particle then + particle:SetGravity( Vector(0,0,100) + VectorRand() * 50 ) + particle:SetVelocity( -self.Entity:GetForward() * 200 ) + particle:SetAirResistance( 600 ) + particle:SetDieTime( math.Rand(2,3) ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( math.Rand(5,6) ) + particle:SetEndSize( math.Rand(12,30) ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 50,50,50 ) + particle:SetCollide( false ) + end + + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + if particle then + particle:SetVelocity( -self.Entity:GetForward() * math.Rand(250,800) + self.Entity:GetVelocity()) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(10,15) ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 150,50,100 ) + particle:SetGravity( Vector( 0, 0, 0 ) ) + particle:SetCollide( false ) + end + + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Entity:GetPos() ) + if particle then + particle:SetVelocity( -self.Entity:GetForward() * 200 + VectorRand() * 50 ) + particle:SetDieTime( 0.25 ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(6,10) ) + particle:SetEndSize( math.Rand(2,3) ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 255,100,200 ) + particle:SetGravity( Vector( 0, 0, 0 ) ) + particle:SetCollide( false ) + end + end +end + +function EFFECT:Think() + if IsValid( self.Entity ) then + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.02 + + local oldpos = self.OldPos + local newpos = self.Entity:LocalToWorld( self.Offset ) + self:SetPos( newpos ) + + local Sub = (newpos - oldpos) + local Dir = Sub:GetNormalized() + local Len = Sub:Length() + + self.OldPos = newpos + + for i = 0, Len, 45 do + local pos = oldpos + Dir * i + + self:doFX( pos ) + end + end + + return true + end + + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end + + return false +end + +function EFFECT:Render() + local ent = self.Entity + local pos = ent:LocalToWorld( self.Offset ) + + render.SetMaterial( GlowMat ) + + render.DrawSprite( pos, 100, 100, Color( 255, 40, 100, 50 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_defence_explosion.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_defence_explosion.lua new file mode 100644 index 0000000..b1596b5 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_defence_explosion.lua @@ -0,0 +1,182 @@ + +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +EFFECT.DustMat = { + "effects/lvs_base/particle_debris_01", + "effects/lvs_base/particle_debris_02", +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + local scale = data:GetMagnitude() + + self.LifeTime = 0.35 + self.DieTime = CurTime() + self.LifeTime + + self.Pos = pos + self.Scale = scale + + sound.Play( "weapons/shotgun/shotgun_fire6.wav", pos, 75, 150, 1 ) + + local VecCol = (render.GetLightColor( pos + dir ) * 0.5 + Vector(0.2,0.18,0.15)) * 255 + + local emitter = ParticleEmitter( pos, false ) + + for i = 0,20 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 500 * scale ) + particle:SetDieTime( math.Rand(0.4,0.6) ) + particle:SetAirResistance( 500 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 40 * scale ) + particle:SetEndSize( 200 * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,1200) ) + particle:SetCollide( true ) + end + + for i = 1,12 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , pos ) + + if not particle then continue end + + local ang = i * 30 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + local Vel = Vector(X,Y,0) * 1000 + Vel:Rotate( dir:Angle() + Angle(90,0,0) ) + + particle:SetVelocity( Vel * scale ) + particle:SetDieTime( math.Rand(0.6,0.8) ) + particle:SetAirResistance( 500 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 30 * scale ) + particle:SetEndSize( 60 * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,600) ) + particle:SetCollide( true ) + end + + for i = 0, 15 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 400 * scale ) + particle:SetDieTime( math.Rand(0.2,0.3) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 60 * scale ) + particle:SetEndSize( 10 * scale ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( dir * 2500 ) + particle:SetRollDelta( math.Rand(-5,5) ) + particle:SetAirResistance( 300 ) + end + + for i = 0, 5 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos) + + if not particle then continue end + + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( Vector(0,0,0) ) + + local size = math.Rand(2, 12) * scale + particle:SetEndSize( size ) + particle:SetStartSize( size ) + + particle:SetStartLength( 100 * scale ) + particle:SetEndLength( size ) + + particle:SetDieTime( math.Rand(0.1,0.2) ) + particle:SetVelocity( (dir * 2000 + VectorRand() * 1000) * scale ) + + particle:SetAirResistance( 0 ) + end + + for i = 0, 40 do + local particle = emitter:Add( "effects/fire_embers"..math.random(1,2), pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 400 * scale ) + particle:SetDieTime( math.Rand(0.4,0.6) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 20 * scale ) + particle:SetEndSize( 0 ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( Vector(0,0,600) ) + particle:SetRollDelta( math.Rand(-8,8) ) + particle:SetAirResistance( 300 ) + end + + if dir.z > 0.8 then + for i = 0,60 do + local particle = emitter:Add( "effects/fleck_cement"..math.random(1,2), pos ) + local vel = dir * math.Rand(600,1000) + VectorRand() * 200 + + if not particle then continue end + + particle:SetVelocity( vel * scale ) + particle:SetDieTime( math.Rand(10,15) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + + local size = math.Rand(2, 4) * scale + particle:SetEndSize( size ) + particle:SetStartSize( size ) + + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0.3 ) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + if not self.Scale then return end + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + local R1 = 800 * self.Scale + render.SetMaterial( self.GlowMat ) + render.DrawSprite( self.Pos, R1 * Scale, R1 * Scale, Color( 255, 200, 150, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_defence_smoke.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_defence_smoke.lua new file mode 100644 index 0000000..6855451 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_defence_smoke.lua @@ -0,0 +1,56 @@ + +EFFECT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + + local emitter = ParticleEmitter( pos, false ) + + if not emitter then return end + + local VecCol = (render.GetLightColor( pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + for i = 0,2 do + local particle = emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 200 ) + particle:SetDieTime( math.Rand(4,6) ) + particle:SetAirResistance( 250 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 0 ) + particle:SetEndSize( 650 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( VectorRand() * 600 ) + particle:SetCollide( true ) + particle:SetBounce( 1 ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_downgrade.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_downgrade.lua new file mode 100644 index 0000000..869c7d1 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_downgrade.lua @@ -0,0 +1,43 @@ + +function EFFECT:Init( data ) + self.Ent = data:GetEntity() + self.Pos = data:GetOrigin() + + local T = CurTime() + + self.LifeTime = 1 + self.DieTime = T + self.LifeTime + + if IsValid( self.Ent ) then + self.Model = ClientsideModel( self.Ent:GetModel(), RENDERMODE_TRANSCOLOR ) + self.Model:SetMaterial("models/alyx/emptool_glow") + self.Model:SetColor( Color(255,0,0,255) ) + self.Model:SetParent( self.Ent, 0 ) + self.Model:SetMoveType( MOVETYPE_NONE ) + self.Model:SetLocalPos( Vector( 0, 0, 0 ) ) + self.Model:SetLocalAngles( Angle( 0, 0, 0 ) ) + self.Model:AddEffects( EF_BONEMERGE ) + self.Model:SetModelScale( self.Ent:GetModelScale() ) + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then + if IsValid( self.Model ) then + self.Model:Remove() + end + end + + if self.DieTime < CurTime() then + if IsValid( self.Model ) then + self.Model:Remove() + end + + return false + end + + return true +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_engine_blacksmoke.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_engine_blacksmoke.lua new file mode 100644 index 0000000..bdec703 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_engine_blacksmoke.lua @@ -0,0 +1,53 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + VectorRand() * 25 + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + local emitter = Ent:GetParticleEmitter( Pos ) + + if not IsValid( emitter ) then return end + + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + + if particle then + particle:SetVelocity( VectorRand() * 100 ) + particle:SetDieTime( 2 ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( 60 ) + particle:SetEndSize( 200 ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( 60, 60, 60 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( false ) + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_exhaust.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_exhaust.lua new file mode 100644 index 0000000..c0996d3 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_exhaust.lua @@ -0,0 +1,59 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local Ent = data:GetEntity() + local Scale = data:GetMagnitude() + + if not IsValid( Ent ) then return end + + local Vel = Ent:GetVelocity() + + local emitter = Ent:GetParticleEmitter( Pos ) + + if not IsValid( emitter ) then return end + + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + + if not particle then return end + + local Col = 100 - 60 * Scale + + particle:SetVelocity( Vel + Dir * (100 + 50 * Scale) ) + particle:SetDieTime( 0.4 - 0.3 * Scale ) + particle:SetAirResistance( 400 ) + particle:SetStartAlpha( 80 ) + particle:SetStartSize( 2 ) + particle:SetEndSize( 10 + 20 * Scale ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetRollDelta( math.Rand( -1, 1 ) * 2 ) + particle:SetColor( Col, Col, Col ) + particle:SetGravity( Vector( 0, 0, 10 ) ) + particle:SetCollide( false ) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_exhaust_fire.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_exhaust_fire.lua new file mode 100644 index 0000000..01cd6f0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_exhaust_fire.lua @@ -0,0 +1,52 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local Ent = data:GetEntity() + local Scale = data:GetMagnitude() + + if not IsValid( Ent ) then return end + + local emitter = Ent:GetParticleEmitter( Pos ) + + if not IsValid( emitter ) then return end + + local particle = emitter:Add( "effects/lvs_base/fire", Pos ) + + if particle then + particle:SetVelocity( Dir * 70 ) + particle:SetDieTime( 0.2 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 + 18 * Scale ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + + for i = 1, 3 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), Pos ) + + if particle then + particle:SetVelocity( Dir * 40 * i ) + particle:SetDieTime( 0.2 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( (5 + 5 * Scale) - i ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_explosion.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion.lua new file mode 100644 index 0000000..6aa2e51 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion.lua @@ -0,0 +1,178 @@ +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +EFFECT.DecalMat = Material( util.DecalMaterial( "Scorch" ) ) + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + + self.DieTime = CurTime() + 1 + + self:Explosion( Pos, 2 ) + + local ply = LocalPlayer():GetViewEntity() + if IsValid( ply ) then + local delay = (Pos - ply:GetPos()):Length() / 13503.9 + timer.Simple( delay, function() + sound.Play( "LVS.DYNAMIC_EXPLOSION", Pos ) + end ) + else + sound.Play( "LVS.DYNAMIC_EXPLOSION", Pos ) + end + + for i = 1, 20 do + timer.Simple(math.Rand(0,0.01) * i, function() + if IsValid( self ) then + local p = Pos + VectorRand() * 10 * i + + self:Explosion( p, math.Rand(0.5,0.8) ) + end + end) + end + + self:Debris( Pos ) + + local traceSky = util.TraceLine( { + start = Pos, + endpos = Pos + Vector(0,0,50000), + mask = MASK_SOLID_BRUSHONLY, + } ) + + local traceWater = util.TraceLine( { + start = traceSky.HitPos, + endpos = Pos - Vector(0,0,100), + mask = MASK_WATER, + } ) + + if traceWater.Hit then + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.HitPos ) + effectdata:SetScale( 100 ) + effectdata:SetFlags( 2 ) + util.Effect( "WaterSplash", effectdata, true, true ) + else + local trace = util.TraceLine( { + start = Pos + Vector(0,0,100), + endpos = Pos - Vector(0,0,100), + mask = MASK_SOLID_BRUSHONLY, + } ) + + if trace.Hit and not trace.HitNonWorld then + for i = 1, 10 do + local StartPos = trace.HitPos + Vector(math.random(-25,25) * i,math.random(-25,25) * i,0) + local decalTrace = util.TraceLine( { + start = StartPos + Vector(0,0,100), + endpos = StartPos - Vector(0,0,100), + mask = MASK_SOLID_BRUSHONLY, + } ) + + util.DecalEx( self.DecalMat, trace.Entity, decalTrace.HitPos + decalTrace.HitNormal, decalTrace.HitNormal, Color(255,255,255,255), math.Rand(2,3), math.Rand(2,3) ) + end + end + end +end + +function EFFECT:Debris( pos ) + local emitter = ParticleEmitter( pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0,60 do + local particle = emitter:Add( "effects/fleck_tile"..math.random(1,2), pos ) + local vel = VectorRand() * math.Rand(200,600) + vel.z = math.Rand(200,600) + if particle then + particle:SetVelocity( vel ) + particle:SetDieTime( math.Rand(10,15) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 0,0,0 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0.3 ) + end + end + + emitter:Finish() +end + +function EFFECT:Explosion( pos , scale ) + local emitter = ParticleEmitter( pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0,10 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + + if particle then + particle:SetVelocity( VectorRand() * 1500 * scale ) + particle:SetDieTime( math.Rand(0.75,1.5) * scale ) + particle:SetAirResistance( math.Rand(200,600) ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(60,120) * scale ) + particle:SetEndSize( math.Rand(220,320) * scale ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 40,40,40 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + end + + for i = 0, 40 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if particle then + particle:SetVelocity( VectorRand() * 1500 * scale ) + particle:SetDieTime( 0.2 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 20 * scale ) + particle:SetEndSize( math.Rand(180,240) * scale ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + emitter:Finish() + + local dlight = DynamicLight( math.random(0,9999) ) + if dlight then + dlight.pos = pos + dlight.r = 255 + dlight.g = 180 + dlight.b = 100 + dlight.brightness = 8 + dlight.Decay = 2000 + dlight.Size = 300 + dlight.DieTime = CurTime() + 1 + end +end + +function EFFECT:Think() + if CurTime() < self.DieTime then return true end + + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_bomb.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_bomb.lua new file mode 100644 index 0000000..45dfc4c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_bomb.lua @@ -0,0 +1,297 @@ + +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +EFFECT.DustMat = { + "effects/lvs_base/particle_debris_01", + "effects/lvs_base/particle_debris_02", +} + +EFFECT.DecalMat = Material( util.DecalMaterial( "Scorch" ) ) + +function EFFECT:Init( data ) + self.Dir = Vector(0,0,1) + self.Pos = data:GetOrigin() + self.LifeTime = 0.35 + self.DieTime = CurTime() + self.LifeTime + + local scale = 3 + + local emitter = ParticleEmitter( self.Pos, false ) + + local VecCol = (render.GetLightColor( self.Pos + self.Dir ) * 0.5 + Vector(0.1,0.09,0.075)) * 255 + + local DieTime = math.Rand(0.8,1.6) + + local traceSky = util.TraceLine( { + start = self.Pos, + endpos = self.Pos + Vector(0,0,50000), + filter = self, + } ) + + local traceWater = util.TraceLine( { + start = traceSky.HitPos, + endpos = self.Pos - Vector(0,0,100), + filter = self, + mask = MASK_WATER, + } ) + + if traceWater.Hit then + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.HitPos ) + effectdata:SetScale( 100 ) + effectdata:SetFlags( 2 ) + util.Effect( "WaterSplash", effectdata, true, true ) + else + local trace = util.TraceLine( { + start = self.Pos + Vector(0,0,100), + endpos = self.Pos - Vector(0,0,100), + } ) + + if trace.Hit and not trace.HitNonWorld then + for i = 1, 3 do + local StartPos = trace.HitPos + Vector(math.random(-200,200),math.random(-200,200),0) + local decalTrace = util.TraceLine( { + start = StartPos + Vector(0,0,100), + endpos = StartPos - Vector(0,0,100), + } ) + + util.DecalEx( self.DecalMat, trace.Entity, decalTrace.HitPos + decalTrace.HitNormal, decalTrace.HitNormal, Color(255,255,255,255), math.Rand(3,6), math.Rand(3,6) ) + end + end + end + + local Pos = self.Pos + local Dist = (traceWater.HitPos - Pos):Length() + local ply = LocalPlayer():GetViewEntity() + + if not IsValid( ply ) then return end + + local delay = (Pos - ply:GetPos()):Length() / 13503.9 + + if traceWater.Hit and Dist > 150 then + timer.Simple( delay, function() + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + util.Effect( "WaterSurfaceExplosion", effectdata, true, true ) + end ) + + if Dist > 300 then return end + else + timer.Simple( delay, function() + sound.Play( "LVS.BOMB_EXPLOSION", Pos ) + sound.Play( "LVS.BOMB_EXPLOSION_DYNAMIC", Pos ) + end ) + end + + for i = 1, 10 do + for n = 0,6 do + local particle = emitter:Add( self.DustMat[ math.random(1,#self.DustMat) ], self.Pos ) + + if not particle then continue end + + particle:SetVelocity( (self.Dir * 50 * i + VectorRand() * 25) * scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * scale ) + particle:SetEndSize( 20 * i * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector(0,0,-600) * scale ) + particle:SetCollide( false ) + end + end + + for i = 1, 10 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ], self.Pos ) + + if not particle then continue end + + particle:SetVelocity( (self.Dir * 50 * i + VectorRand() * 40) * scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * scale ) + particle:SetEndSize( 20 * i * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector(0,0,-600) * scale ) + particle:SetCollide( false ) + end + + for i = 1,24 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , self.Pos ) + + if particle then + local ang = i * 15 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + local Vel = Vector(X,Y,0) * math.Rand(1500,2000) + + particle:SetVelocity( Vel * scale ) + particle:SetDieTime( math.Rand(1,3) ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 40 * scale ) + particle:SetEndSize( 200 * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector(0,0,60) * scale ) + particle:SetCollide( true ) + end + end + + for i = 0, 15 do + local particle = emitter:Add( self.SmokeMat[ math.random(1, #self.SmokeMat ) ], self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 1000 ) + particle:SetDieTime( math.Rand(2,3) ) + particle:SetAirResistance( 200 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 200 ) + particle:SetEndSize( 600 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( false ) + end + end + + for i = 0, 15 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 500 ) + particle:SetDieTime( math.Rand(0.15,0.3) ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 25 ) + particle:SetEndSize( math.Rand(70,100) ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + for i = 0, 20 do + local particle = emitter:Add( "sprites/rico1", self.Pos ) + + local vel = VectorRand() * 800 + + if particle then + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(20,40) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( 0 ) + particle:SetColor( 255, 255, 255 ) + + particle:SetAirResistance( 0 ) + end + end + + for i = 0,60 do + local particle = emitter:Add( "effects/fleck_tile"..math.random(1,2), self.Pos ) + local vel = VectorRand() * math.Rand(800,1600) + vel.z = math.Rand(1000,4000) + + if particle then + particle:SetVelocity( vel ) + particle:SetDieTime( math.random(5,15) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 0,0,0 ) + particle:SetGravity( Vector( 0, 0, -2000 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0.3 ) + end + end + + emitter:Finish() +end + +function EFFECT:Explosion( pos , scale ) + local emitter = ParticleEmitter( pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0, 40 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if particle then + particle:SetVelocity( VectorRand() * 1500 * scale ) + particle:SetDieTime( 0.2 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 20 * scale ) + particle:SetEndSize( math.Rand(180,240) * scale ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + emitter:Finish() + + local dlight = DynamicLight( math.random(0,9999) ) + if dlight then + dlight.pos = pos + dlight.r = 255 + dlight.g = 180 + dlight.b = 100 + dlight.brightness = 8 + dlight.Decay = 2000 + dlight.Size = 300 + dlight.DieTime = CurTime() + 1 + end +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + render.SetMaterial( self.GlowMat ) + render.DrawSprite( self.Pos, 2000 * Scale, 2000 * Scale, Color( 255, 200, 150, 255) ) + + local Scale = (self.DieTime - self.LifeTime + 0.25 - CurTime()) / 0.25 + local InvScale = 1 - Scale + if Scale > 0 then + render.SetColorMaterial() + render.DrawSphere( self.Pos, -450 * InvScale, 30,30, Color( 255, 200, 150, 150 * (Scale ^ 2) ) ) + render.DrawSphere( self.Pos, -500 * InvScale, 30,30, Color( 255, 200, 150, 100 * (Scale ^ 2) ) ) + render.DrawSphere( self.Pos, -550 * InvScale, 30,30, Color( 255, 200, 150, 25 * (Scale ^ 2) ) ) + render.DrawSphere( self.Pos, 600 * InvScale, 30,30, Color( 255, 200, 150, 25 * (Scale ^ 2) ) ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_nodebris.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_nodebris.lua new file mode 100644 index 0000000..8332a3a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_nodebris.lua @@ -0,0 +1,92 @@ +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + + self:Explosion( Pos, 2 ) + + local ply = LocalPlayer():GetViewEntity() + if IsValid( ply ) then + local delay = (Pos - ply:GetPos()):Length() / 13503.9 + timer.Simple( delay, function() + sound.Play( "LVS.EXPLOSION", Pos ) + end ) + else + sound.Play( "LVS.EXPLOSION", Pos ) + end + + for i = 1, 20 do + timer.Simple(math.Rand(0,0.01) * i, function() + if not IsValid( self ) then return end + + local p = Pos + VectorRand() * 10 * i + + self:Explosion( p, math.Rand(0.5,0.8) ) + end) + end +end + +function EFFECT:Explosion( pos , scale ) + local emitter = ParticleEmitter( pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0,10 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 1000 * scale ) + particle:SetDieTime( math.Rand(0.75,1.5) * scale ) + particle:SetAirResistance( math.Rand(200,600) ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(60,120) * scale ) + particle:SetEndSize( math.Rand(160,280) * scale ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 40,40,40 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + + for i = 0, 40 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 1000 * scale ) + particle:SetDieTime( 0.14 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * scale ) + particle:SetEndSize( math.Rand(60,120) * scale ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_small.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_small.lua new file mode 100644 index 0000000..fa4794f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_explosion_small.lua @@ -0,0 +1,112 @@ + +local GlowMat = Material( "sprites/light_glow02_add" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + self.LifeTime = 0.35 + self.DieTime = CurTime() + self.LifeTime + + local emitter = ParticleEmitter( self.Pos, false ) + + for i = 0, 15 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 1000 ) + particle:SetDieTime( math.Rand(2,3) ) + particle:SetAirResistance( math.Rand(200,600) ) + particle:SetStartAlpha( 200 ) + particle:SetStartSize( 120 ) + particle:SetEndSize( 300 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( 50,50,50 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( false ) + end + end + + for i = 0, 15 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 500 ) + particle:SetDieTime( math.Rand(0.15,0.3) ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 25 ) + particle:SetEndSize( math.Rand(70,100) ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + for i = 0, 20 do + local particle = emitter:Add( "sprites/rico1", self.Pos ) + + local vel = VectorRand() * 800 + + if particle then + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(20,40) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( 0 ) + particle:SetColor( 255, 255, 255 ) + + particle:SetAirResistance( 0 ) + end + end + + emitter:Finish() + + local Pos = self.Pos + local ply = LocalPlayer():GetViewEntity() + if IsValid( ply ) then + local delay = (Pos - ply:GetPos()):Length() / 13503.9 + if delay <= 0.11 then + sound.Play( "ambient/explosions/explode_9.wav", Pos, 85, 100, 1 - delay * 8 ) + end + + timer.Simple( delay, function() + sound.Play( "LVS.MISSILE_EXPLOSION", Pos ) + end ) + else + sound.Play( "LVS.MISSILE_EXPLOSION", Pos ) + end +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + render.SetMaterial( GlowMat ) + render.DrawSprite( self.Pos, 2000 * Scale, 2000 * Scale, Color( 255, 200, 150, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_firetrail.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_firetrail.lua new file mode 100644 index 0000000..230e7d7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_firetrail.lua @@ -0,0 +1,111 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + self.Scale = data:GetScale() + self.DieTime = CurTime() + data:GetMagnitude() + self.Pos = data:GetStart() + + if not IsValid( self.Entity ) then return end + + self.Emitter = ParticleEmitter( self.Entity:LocalToWorld( self.Pos ), false ) +end + + +function EFFECT:Think() + if IsValid( self.Entity ) then + local Pos = self.Entity:LocalToWorld( self.Pos ) + + local T = CurTime() + + if (self.nextDFX or 0) < T then + self.nextDFX = T + 0.05 + + if self.Emitter then + local particle = self.Emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + + if particle then + particle:SetVelocity( VectorRand() * 100 * self.Scale ) + particle:SetDieTime( 3 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 150 ) + particle:SetStartSize( 150 * self.Scale ) + particle:SetEndSize( math.Rand(200,300) * self.Scale ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor( 40,40,40 ) + particle:SetGravity( Vector( 0, 0, 0 ) ) + particle:SetCollide( false ) + end + + local particle = self.Emitter:Add( "effects/lvs_base/fire", Pos ) + + if particle then + particle:SetVelocity( VectorRand() * 100 * self.Scale ) + particle:SetDieTime( math.random(40,80) / 100 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 130 * self.Scale ) + particle:SetEndSize( math.Rand(50,100) * self.Scale ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 70 ) ) + particle:SetCollide( false ) + end + + for i = 0,3 do + local particle = self.Emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), Pos + VectorRand() * 100 * self.Scale ) + + if particle then + particle:SetVelocity( VectorRand() * 100 * self.Scale ) + particle:SetDieTime( math.random(30,60) / 100 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 70 * self.Scale ) + particle:SetEndSize( math.Rand(25,80) * self.Scale ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 40 ) ) + particle:SetCollide( false ) + end + end + end + end + + if self.DieTime < CurTime() then + if self.Emitter then + self.Emitter:Finish() + end + + return false + end + + return true + end + + if self.Emitter then + self.Emitter:Finish() + end + + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream.lua new file mode 100644 index 0000000..1c74886 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream.lua @@ -0,0 +1,415 @@ + +local GlowMat = Material( "sprites/light_glow02_add" ) +local FireMat = Material( "effects/fire_cloud1" ) +local HeatMat = Material( "sprites/heatwave" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if not IsValid( self.Entity ) then return end + + local Pos = self:GetPos() + + self._Particles = {} + self.Emitter = ParticleEmitter( Pos, false ) + self.Emitter3D = ParticleEmitter( Pos, true ) + self._LastParticlePos = vector_origin +end + +function EFFECT:AddParticle( particle ) + table.insert( self._Particles, particle ) + + for id, particle in pairs( self._Particles ) do + if not particle or particle:GetLifeTime() > particle:GetDieTime() then + self._Particles[ id ] = nil + end + end +end + +function EFFECT:GetPosition() + local ent = self.Entity + + if not IsValid( ent ) then return vector_origin, vector_origin end + + local Pos = ent:GetPos() + local Dir = ent:GetForward() + + local Target = ent:GetTarget() + local Attachment = ent:GetTargetAttachment() + + if IsValid( Target ) and Attachment ~= "" then + local ID = Target:LookupAttachment( Attachment ) + local Muzzle = Target:GetAttachment( ID ) + + if not Muzzle then return vector_origin, vector_origin end + + Pos = Muzzle.Pos + Dir = Muzzle.Ang:Forward() + end + + return Pos, Dir +end + +function EFFECT:Think() + local ent = self.Entity + local emitter = self.Emitter + local emitter3D = self.Emitter3D + + if not IsValid( emitter ) or not IsValid( emitter3D ) then return true end + + local T = CurTime() + + if IsValid( ent ) and not self._KillSwitch then + if not ent:GetActive() then + self._KillSwitch = true + self._KillSwitchTime = CurTime() + 2 + end + + if (self.nextDFX or 0) < T then + self.nextDFX = T + 0.01 + + local Pos, Dir = self:GetPosition() + + self:MakeFlameStream( emitter, emitter3D, Pos, Dir ) + self:MakeFlameMuzzle( emitter, emitter3D, Pos, Dir ) + self:SetRenderBoundsWS( Pos, Pos + Dir * 50000 ) + end + + return true + end + + if self._KillSwitch and IsValid( emitter ) and IsValid( emitter3D ) then + if self._KillSwitchTime > T then + return true + end + end + + if emitter then + emitter:Finish() + end + + if emitter3D then + emitter3D:Finish() + end + + return false +end + +function EFFECT:MakeFlameImpact( Pos, Dir, Size ) + local emitter3D = self.Emitter3D + + if not IsValid( emitter3D ) then return end + + local hitparticle = emitter3D:Add( "effects/lvs_base/flamelet"..math.random(1,5), Pos + Dir ) + if hitparticle then + hitparticle:SetStartSize( Size * 0.25 ) + hitparticle:SetEndSize( Size ) + hitparticle:SetDieTime( math.Rand(0.5,1) ) + hitparticle:SetStartAlpha( 255 ) + hitparticle:SetEndAlpha( 0 ) + hitparticle:SetRollDelta( math.Rand(-2,2) ) + hitparticle:SetAngles( Dir:Angle() ) + end +end + +function EFFECT:MakeFlameImpactWater( Pos, Dir, Size ) + local emitter3D = self.Emitter3D + local emitter = self.Emitter + + if not IsValid( emitter ) or not IsValid( emitter3D ) then return end + + local sparticle = emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + if sparticle then + sparticle:SetVelocity( Vector(0,0,400) + VectorRand() * 100 ) + sparticle:SetDieTime( Size * 0.01 ) + sparticle:SetAirResistance( 500 ) + sparticle:SetStartAlpha( 40 ) + sparticle:SetStartSize( 0 ) + sparticle:SetEndSize( Size * 4 ) + sparticle:SetRoll( math.Rand(-3,3) ) + sparticle:SetRollDelta( math.Rand(-1,1) ) + sparticle:SetColor( 255, 255, 255 ) + sparticle:SetGravity( Vector( 0, 0, 800 ) ) + sparticle:SetCollide( false ) + end + + local hitparticle = emitter3D:Add( "effects/lvs_base/flamelet"..math.random(1,5), Pos + Dir ) + if hitparticle then + hitparticle:SetStartSize( Size * 0.25 ) + hitparticle:SetEndSize( Size ) + hitparticle:SetDieTime( math.Rand(0.5,1) ) + hitparticle:SetStartAlpha( 255 ) + hitparticle:SetEndAlpha( 0 ) + hitparticle:SetRollDelta( math.Rand(-2,2) ) + hitparticle:SetAngles( Dir:Angle() ) + end +end + +function EFFECT:MakeFlameBurst( Pos, Vel, Size ) + local emitter = self.Emitter + + if not IsValid( emitter ) then return end + + local fparticle = emitter:Add( "effects/lvs_base/fire", Pos ) + if fparticle then + fparticle:SetVelocity( VectorRand() * 15 + Vector( 0, 0, 200 ) + Vel ) + fparticle:SetDieTime( math.Rand(0.6,0.8) ) + fparticle:SetAirResistance( 0 ) + + fparticle:SetStartAlpha( 255 ) + fparticle:SetEndAlpha( 255 ) + + fparticle:SetStartSize( 40 ) + fparticle:SetEndSize( 0 ) + + fparticle:SetRollDelta( math.Rand(-2,2) ) + fparticle:SetColor( 255,255,255 ) + fparticle:SetGravity( Vector( 0, 0, 0 ) ) + fparticle:SetCollide( false ) + + self:AddParticle( fparticle ) + end + + local fparticle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), Pos ) + if fparticle then + fparticle:SetVelocity( VectorRand() * 25 + Vel ) + fparticle:SetDieTime( math.Rand(0.4,0.8) ) + fparticle:SetStartAlpha( 150 ) + fparticle:SetEndAlpha( 0 ) + fparticle:SetStartSize( 0 ) + fparticle:SetEndSize( math.Rand(60,120) ) + fparticle:SetColor( 255, 255, 255 ) + fparticle:SetGravity( Vector(0,0,100) ) + fparticle:SetRollDelta( math.Rand(-2,2) ) + fparticle:SetAirResistance( 0 ) + end + + for i = 0, 6 do + local eparticle = emitter:Add( "effects/fire_embers"..math.random(1,2), Pos ) + + if not eparticle then continue end + + eparticle:SetVelocity( VectorRand() * 400 + Vel ) + eparticle:SetDieTime( math.Rand(0.4,0.6) ) + eparticle:SetStartAlpha( 255 ) + eparticle:SetEndAlpha( 0 ) + eparticle:SetStartSize( 20 ) + eparticle:SetEndSize( 0 ) + eparticle:SetColor( 255, 255, 255 ) + eparticle:SetGravity( Vector(0,0,600) ) + eparticle:SetRollDelta( math.Rand(-8,8) ) + eparticle:SetAirResistance( 300 ) + end + + local Dist = (self._LastParticlePos - Pos):Length() + self._LastParticlePos = Pos + + if Dist < 250 then + if math.random(1,8) ~= 1 then return end + end + + local sparticle = emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + if sparticle then + sparticle:SetVelocity( Vector(0,0,400) + Vel ) + sparticle:SetDieTime( math.Rand(2,4) * Size ) + sparticle:SetAirResistance( 500 ) + sparticle:SetStartAlpha( 125 ) + sparticle:SetStartSize( 0 ) + sparticle:SetEndSize( 200 * Size ) + sparticle:SetRoll( math.Rand(-3,3) ) + sparticle:SetRollDelta( math.Rand(-1,1) ) + sparticle:SetColor( 0, 0, 0 ) + sparticle:SetGravity( Vector( 0, 0, 800 ) ) + sparticle:SetCollide( false ) + end +end + +function EFFECT:MakeFlameMuzzle( emitter, emitter3D, pos, dir ) + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if not particle then return end + + local ent = self.Entity + + if not IsValid( ent ) then return end + + local velDesired = ent:GetFlameVelocity() + local vel = dir * velDesired + + local DieTime = 0.075 + + particle:SetVelocity( VectorRand() * 30 + vel ) + particle:SetDieTime( DieTime ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetEndLength( velDesired * 0.1 ) + particle:SetStartLength( velDesired * 0.04 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( 0 ) + particle:SetRollDelta( math.Rand(-5,5) ) + particle:SetColor( 50, 50, 255 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) +end + +function EFFECT:MakeFlameStream( emitter, emitter3D, pos, dir ) + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if not particle then return end + + local ent = self.Entity + + if not IsValid( ent ) then return end + + local velDesired = ent:GetFlameVelocity() + local vel = dir * velDesired + + local DieTime = math.Rand( ent:GetFlameLifeTime() * 0.5, ent:GetFlameLifeTime() ) + + particle:SetVelocity( VectorRand() * 30 + vel ) + particle:SetDieTime( DieTime ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetEndLength( velDesired * 0.1 ) + particle:SetStartLength( velDesired * 0.04 ) + particle:SetStartSize( 2 ) + particle:SetEndSize( ent:GetFlameSize() ) + particle:SetRollDelta( math.Rand(-5,5) ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + local Delay = math.Rand( DieTime * 0.5, DieTime ) + local Size = (Delay / DieTime) ^ 2 + timer.Simple( Delay, function() + if not IsValid( self ) or not particle or particle.NoSmokeSpawn then return end + + local ParticlePos = particle:GetPos() + + self:MakeFlameBurst( ParticlePos, vel * 0.2, Size ) + end) + particle:SetNextThink( CurTime() ) + particle:SetThinkFunction( function( p ) + if not IsValid( self ) then return end + + p:SetNextThink( CurTime() + 0.05 ) + + local pos = p:GetPos() + local vel = p:GetVelocity() + local dir = vel:GetNormalized() + local speed = vel:Length() * 0.06 + + local traceData = { + start = pos, + endpos = pos + dir * speed, + filter = self.Entity, + } + local trace = util.TraceLine( traceData ) + + traceData.mask = MASK_WATER + local traceWater = util.TraceLine( traceData ) + + if traceWater.Hit and not trace.Hit then + p:SetDieTime( 0 ) + p.NoSmokeSpawn = true + local RandomSize = math.Rand(60,80) + self:MakeFlameImpactWater( traceWater.HitPos, traceWater.HitNormal, RandomSize ) + end + + if trace.HitWorld or not trace.Hit then return end + + p:SetDieTime( 0 ) + p.NoSmokeSpawn = true + local RandomSize = math.Rand(40,60) + self:MakeFlameImpact( trace.HitPos, trace.HitNormal, RandomSize ) + self:MakeFlameBurst( trace.HitPos, vector_origin, RandomSize / 60 ) + end ) + particle:SetCollideCallback( function( p, hitpos, hitnormal ) + if p.NoSmokeSpawn then return end + + p:SetDieTime( 0 ) + p.NoSmokeSpawn = true + + if not IsValid( emitter3D ) then return end + + local RandomSize = math.Rand(40,60) + + self:MakeFlameImpact( hitpos, hitnormal, RandomSize ) + self:MakeFlameBurst( hitpos + hitnormal, vector_origin, RandomSize / 60 ) + end ) + + particle.NoFade = true + self:AddParticle( particle ) + + local particle = emitter:Add( "effects/lvs_base/fire", pos ) + if particle then + particle:SetVelocity( VectorRand() * 4 + dir * math.Rand(velDesired * 0.8,velDesired * 1.6) ) + particle:SetDieTime( math.Rand(0.75,1.5) ) + particle:SetAirResistance( 40 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 4 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-3,3) ) + particle:SetRollDelta( math.Rand(-6,6) ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( VectorRand() * 400 - Vector(0,0,600) ) + particle:SetCollide( true ) + end +end + +function EFFECT:Render() + local ent = self.Entity + + if not IsValid( ent ) then return end + + if ent:GetActive() and not self._KillSwitch then + local Scale = 1 + local Pos, Dir = self:GetPosition() + + local scroll = -CurTime() * 5 + + local Up = Dir + VectorRand() * 0.08 + + render.UpdateRefractTexture() + render.SetMaterial( HeatMat ) + render.StartBeam( 3 ) + render.AddBeam( Pos, 8 * Scale, scroll, Color( 0, 0, 255, 200 ) ) + render.AddBeam( Pos + Up * 32 * Scale, 32 * Scale, scroll + 2, color_white ) + render.AddBeam( Pos + Up * 128 * Scale, 32 * Scale, scroll + 5, Color( 0, 0, 0, 0 ) ) + render.EndBeam() + end + + if not istable( self._Particles ) then return end + + for id, particle in pairs( self._Particles ) do + if not particle then continue end + + local S = particle:GetLifeTime() / particle:GetDieTime() + local A = ((1 - S) ^ 2) * 0.5 + local Size = particle:GetStartSize() * A * 8 + + if particle.NoFade then + Size = particle:GetEndSize() * S * 8 + end + + render.SetMaterial( GlowMat ) + render.DrawSprite( particle:GetPos(), Size, Size, Color( 255 * A, 150 * A, 75 * A, 255 * A) ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream_finish.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream_finish.lua new file mode 100644 index 0000000..3f5f242 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream_finish.lua @@ -0,0 +1,112 @@ + +local GlowMat = Material( "sprites/light_glow02_add" ) +local FireMat = Material( "effects/fire_cloud1" ) +local HeatMat = Material( "sprites/heatwave" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + self.LifeTime = data:GetMagnitude() * 4 + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( self.Entity ) then return end + + local Pos, Dir = self:GetPosition() + + self.Emitter = ParticleEmitter( Pos, false ) + self:SetRenderBoundsWS( Pos, Pos + Dir * 50000 ) +end + +function EFFECT:GetPosition() + local ent = self.Entity + + if not IsValid( ent ) then return vector_origin, vector_origin end + + local Pos = ent:GetPos() + local Dir = ent:GetForward() + + local Target = ent:GetTarget() + local Attachment = ent:GetTargetAttachment() + + if IsValid( Target ) and Attachment ~= "" then + local ID = Target:LookupAttachment( Attachment ) + local Muzzle = Target:GetAttachment( ID ) + + if not Muzzle then return vector_origin, vector_origin end + + Pos = Muzzle.Pos + Dir = Muzzle.Ang:Forward() + end + + return Pos, Dir +end + +function EFFECT:DoSmoke( emitter, pos, dir ) + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + + if not particle then return end + + particle:SetVelocity( dir * 20 ) + particle:SetDieTime( math.Rand(0.8,1.2) ) + particle:SetAirResistance( 400 ) + particle:SetStartAlpha( 50 * Scale ) + particle:SetStartSize( 2 ) + particle:SetEndSize( 20 ) + particle:SetRoll( math.Rand( -2, 2 ) ) + particle:SetRollDelta( math.Rand( -2, 2 ) ) + particle:SetColor( 0, 0, 0 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) +end + +function EFFECT:Think() + local ent = self.Entity + local emitter = self.Emitter + + local T = CurTime() + + if IsValid( ent ) and (self.DieTime or 0) > T then + if (self.nextDFX or 0) < T then + self.nextDFX = T + 0.01 + + local Pos, Dir = self:GetPosition() + + if not ent:GetActive() and math.random(1,6) == 1 then + self:DoSmoke( emitter, Pos, Dir ) + end + + self:SetRenderBoundsWS( Pos, Pos + Dir * 50000 ) + end + + return true + end + + if emitter then + emitter:Finish() + end + + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream_start.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream_start.lua new file mode 100644 index 0000000..308fb16 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_flamestream_start.lua @@ -0,0 +1,186 @@ + +local GlowMat = Material( "sprites/light_glow02_add" ) +local FireMat = Material( "effects/fire_cloud1" ) +local HeatMat = Material( "sprites/heatwave" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + self.LifeTime = data:GetMagnitude() + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( self.Entity ) then return end + + local Pos, Dir = self:GetPosition() + + self.Emitter = ParticleEmitter( Pos, false ) + self:SetRenderBoundsWS( Pos, Pos + Dir * 50000 ) + + local Pos, Dir = self:GetPosition() + + for i = 1,10 do + self:MakeFlameStream( self.Emitter, self.Entity, Pos, Dir ) + end +end + +function EFFECT:GetPosition() + local ent = self.Entity + + if not IsValid( ent ) then return vector_origin, vector_origin end + + local Pos = ent:GetPos() + local Dir = ent:GetForward() + + local Target = ent:GetTarget() + local Attachment = ent:GetTargetAttachment() + + if IsValid( Target ) and Attachment ~= "" then + local ID = Target:LookupAttachment( Attachment ) + local Muzzle = Target:GetAttachment( ID ) + + if not Muzzle then return vector_origin, vector_origin end + + Pos = Muzzle.Pos + Dir = Muzzle.Ang:Forward() + end + + return Pos, Dir +end + +function EFFECT:MakeFlameStream( emitter, ent, pos, dir ) + local vel = ent:GetTargetVelocity() + local Alpha = 100 - vel:Length() / 2 + + if Alpha > 1 then + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + if particle then + particle:SetVelocity( VectorRand() * 60 + dir * 200 ) + particle:SetDieTime( math.Rand(0.8,1.2) ) + particle:SetAirResistance( 400 ) + particle:SetStartAlpha( Alpha ) + particle:SetStartSize( 2 ) + particle:SetEndSize( 20 ) + particle:SetRoll( math.Rand( -2, 2 ) ) + particle:SetRollDelta( math.Rand( -2, 2 ) ) + particle:SetColor( 0, 0, 0 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + end + + local particle = emitter:Add( "effects/lvs_base/fire", pos ) + if particle then + particle:SetVelocity( VectorRand() * 60 + dir * math.Rand(100,200) + vel ) + particle:SetDieTime( math.Rand(0.75,1.5) ) + particle:SetAirResistance( 40 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 1 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-3,3) ) + particle:SetRollDelta( math.Rand(-6,6) ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( Vector(0,0,-600) ) + particle:SetCollide( true ) + end + + local particle = emitter:Add( "effects/lvs_base/fire", pos ) + + if particle then + particle:SetVelocity( dir * 70 + vel ) + particle:SetDieTime( 0.2 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if particle then + particle:SetVelocity( dir * 40 + vel ) + particle:SetDieTime( 0.2 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 2 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end +end + +function EFFECT:Think() + local ent = self.Entity + local emitter = self.Emitter + + local T = CurTime() + + if IsValid( ent ) and (self.DieTime or 0) > T then + if (self.nextDFX or 0) < T then + self.nextDFX = T + 0.01 + + local Pos, Dir = self:GetPosition() + + self:SetRenderBoundsWS( Pos, Pos + Dir * 50000 ) + end + + return true + end + + if emitter then + emitter:Finish() + end + + return false +end + +function EFFECT:Render() + local ent = self.Entity + + if not IsValid( ent ) then return end + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + local invScaleExp = (1 - Scale) ^ 2 + + local Pos, Dir = self:GetPosition() + + local scroll = -CurTime() * 5 + + local Up = Dir + VectorRand() * 0.08 + + render.UpdateRefractTexture() + render.SetMaterial( HeatMat ) + render.StartBeam( 3 ) + render.AddBeam( Pos, 8 * invScaleExp, scroll, Color( 0, 0, 255, 200 ) ) + render.AddBeam( Pos + Up * 32 * invScaleExp, 32 * invScaleExp, scroll + 2, color_white ) + render.AddBeam( Pos + Up * 128 * invScaleExp, 32 * invScaleExp, scroll + 5, Color( 0, 0, 0, 0 ) ) + render.EndBeam() + + local A = Scale ^ 2 + local Size = Scale * 64 + + render.SetMaterial( GlowMat ) + render.DrawSprite( Pos, Size, Size, Color( 255 * A, 150 * A, 75 * A, 255 * A) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_haubitze_muzzle.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_haubitze_muzzle.lua new file mode 100644 index 0000000..f7e46f7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_haubitze_muzzle.lua @@ -0,0 +1,123 @@ +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) +EFFECT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local Ent = data:GetEntity() + local Vel = Dir * 10 + + if IsValid( Ent ) then + Vel = Ent:GetVelocity() + end + + local emitter = ParticleEmitter( Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0, 12 do + local particle = emitter:Add( "effects/muzzleflash2", Pos + Dir * i * 0.7 * math.random(1,2) * 0.5 ) + local Size = 1 + + if not particle then continue end + + particle:SetVelocity( Dir * 800 + Vel ) + particle:SetDieTime( 0.05 ) + particle:SetStartAlpha( 255 * Size ) + particle:SetStartSize( math.max( math.random(10,24) - i * 0.5,0.1 ) * Size ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 255, 255, 255 ) + particle:SetCollide( false ) + end + + local VecCol = (render.GetLightColor( Pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + for i = 0,10 do + local particle = emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], Pos ) + + if not particle then continue end + + particle:SetVelocity( Dir * 700 + VectorRand() * 200 ) + particle:SetDieTime( math.Rand(2,3) ) + particle:SetAirResistance( 250 ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 120 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,100) ) + particle:SetCollide( false ) + end + + local trace = util.TraceLine( { + start = Pos, + endpos = Pos - Vector(0,0,500), + mask = MASK_SOLID_BRUSHONLY, + } ) + + if not trace.Hit then return end + + local VecCol = (render.GetLightColor( trace.HitPos + trace.HitNormal ) * 0.8 + Vector(0.17,0.15,0.1)) * 255 + for i = 1,24 do + local particle = emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], trace.HitPos ) + + if not particle then continue end + + local ang = i * 15 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + particle:SetVelocity( Vector(X,Y,0) * 3000 ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( 500 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 50 ) + particle:SetEndSize( 240 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,150) + Dir * 2000 ) + particle:SetCollide( false ) + end + + emitter:Finish() + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Intensity = 16 + local Ratio = math.min( 250 / (ViewEnt:GetPos() - trace.HitPos):Length(), 1 ) + + if Ratio < 0 then return end + + util.ScreenShake( trace.HitPos, Intensity * Ratio, 0.1, 0.5, 250 ) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_haubitze_trail.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_haubitze_trail.lua new file mode 100644 index 0000000..47876cf --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_haubitze_trail.lua @@ -0,0 +1,108 @@ +local MatBeam = Material( "effects/lvs_base/spark" ) +local GlowMat = Material( "sprites/light_glow02_add" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.OldPos = self.Entity:GetPos() + + self.Emitter = ParticleEmitter( self.Entity:LocalToWorld( self.OldPos ), false ) + end +end + +function EFFECT:doFX( pos ) + if not IsValid( self.Entity ) then return end + + if IsValid( self.Emitter ) then + local emitter = self.Emitter + + local VecCol = (render.GetLightColor( pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + if particle then + particle:SetVelocity( -self.Entity:GetForward() * 1500 + VectorRand() * 10 ) + particle:SetDieTime( math.Rand(0.05,1) ) + particle:SetAirResistance( 250 ) + particle:SetStartAlpha( 100 ) + particle:SetEndAlpha( 0 ) + + particle:SetStartSize( 0 ) + particle:SetEndSize( 30 ) + + particle:SetRollDelta( 1 ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetCollide( false ) + end + end +end + +function EFFECT:Think() + if IsValid( self.Entity ) then + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.02 + + local oldpos = self.OldPos + local newpos = self.Entity:GetPos() + self:SetPos( newpos ) + + local Sub = (newpos - oldpos) + local Dir = Sub:GetNormalized() + local Len = Sub:Length() + + self.OldPos = newpos + + for i = 0, Len, 45 do + local pos = oldpos + Dir * i + + self:doFX( pos ) + end + end + + return true + end + + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end + + return false +end + +function EFFECT:Render() + local ent = self.Entity + + if not IsValid( ent ) then return end + + local pos = ent:GetPos() + local dir = ent:GetForward() + + local len = 250 + + render.SetMaterial( MatBeam ) + render.DrawBeam( pos - dir * len, pos + dir * len * 0.1, 32, 1, 0, Color( 100, 100, 100, 100 ) ) + render.DrawBeam( pos - dir * len * 0.5, pos + dir * len * 0.1, 16, 1, 0, Color( 255, 255, 255, 255 ) ) + + render.SetMaterial( GlowMat ) + render.DrawSprite( pos, 250, 250, Color( 100, 100, 100, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_hover_water.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_hover_water.lua new file mode 100644 index 0000000..1525b64 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_hover_water.lua @@ -0,0 +1,67 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + self.Size = data:GetMagnitude() + + if not IsValid( Ent ) then return end + + self.LifeTime = math.Rand(1.5,3) + self.DieTime = CurTime() + self.LifeTime + + local LightColor = render.GetLightColor( Pos ) + local VecCol = Vector(1,1.2,1.4) * (0.06 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 + VecCol.x = math.min( VecCol.x, 255 ) + VecCol.y = math.min( VecCol.y, 255 ) + VecCol.z = math.min( VecCol.z, 255 ) + + self.Splash = { + Pos = Pos, + Mat = Material("effects/splashwake1"), + RandomAng = math.random(0,360), + Color = VecCol, + } + + local emitter = Ent:GetParticleEmitter( Ent:GetPos() ) + + if emitter and emitter.Add then + local particle = emitter:Add( "effects/splash4", Pos ) + if not particle then return end + + local Vel = Ent:GetVelocity():Length() + + particle:SetVelocity( Vector(0,0,math.Clamp(Vel / 2,100,250)) ) + particle:SetDieTime( 0.25 + math.min(Vel / 200,0.35) ) + particle:SetAirResistance( 60 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( self.Size * 0.2 ) + particle:SetEndSize( self.Size * 2 ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( false ) + end +end + + +function EFFECT:Think() + if CurTime() > self.DieTime then + return false + end + return true +end + +function EFFECT:Render() + if self.Splash and self.LifeTime then + local Scale = ((self.DieTime - self.LifeTime - CurTime()) / self.LifeTime) + local S = self.Size * 5 + (self.Size * 5) * Scale + local Alpha = 100 + 100 * Scale + + cam.Start3D2D( self.Splash.Pos + Vector(0,0,1), Angle(0,0,0), 1 ) + surface.SetMaterial( self.Splash.Mat ) + surface.SetDrawColor( self.Splash.Color.r, self.Splash.Color.g, self.Splash.Color.b, Alpha ) + surface.DrawTexturedRectRotated( 0, 0, S , S, self.Splash.RandomAng ) + cam.End3D2D() + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_hsd_dish_projector.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_hsd_dish_projector.lua new file mode 100644 index 0000000..c36280b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_hsd_dish_projector.lua @@ -0,0 +1,121 @@ +EFFECT.Mat = Material( "effects/lvs/ballturret_projectorbeam" ) +EFFECT.HitMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.ID = self.Entity:LookupAttachment( "muzzle_primary" ) + + if self.ID then + local Muzzle = self.Entity:GetAttachment( self.ID ) + + self:SetRenderBoundsWS( self.Entity:GetPos(), -Muzzle.Ang:Right() * 50000 ) + end + end + + self.SpawnTime = CurTime() +end + +function EFFECT:Think() + if not IsValid( self.Entity ) or not self.ID or not self.Entity:GetProjectorBeam() then + return false + end + + return true +end + +function EFFECT:Render() + if not self.ID or not IsValid( self.Entity ) then return end + + local T = CurTime() + + local Mul = math.min( math.max( 1.5 - (T - self.SpawnTime), 0 ) ^ 2, 1 ) + + local Muzzle = self.Entity:GetAttachment( self.ID ) + + local Dir = -Muzzle.Ang:Right() + local StartPos = Muzzle.Pos + local Trace = util.TraceLine( { start = StartPos, endpos = StartPos + Dir * 50000, filter = self } ) + local EndPos = Trace.HitPos + + self:SetRenderBoundsWS( StartPos, EndPos ) + + render.SetMaterial( self.Mat ) + render.DrawBeam( StartPos, EndPos, (16 + math.random(0,3)) * Mul, 1, 0, Color(255,0,0,255) ) + render.DrawBeam( StartPos, EndPos, (4 + math.random(0,2)) * Mul, 1, 0, Color(255,255,255,255) ) + + render.SetMaterial( self.HitMat ) + local A = 150 + math.random(0,20) + local B = 70 + math.random(0,20) + render.DrawSprite( StartPos, A * Mul, A * Mul, Color(255,0,0,255) ) + render.DrawSprite( StartPos, B * Mul, B * Mul, Color(255,255,255,255) ) + + render.DrawSprite( EndPos, A, A, Color(255,0,0,255) ) + render.DrawSprite( EndPos + VectorRand() * 10, B, B, Color(255,255,255,255) ) + + if (self._Next or 0) > T then return end + + self._Next = T + 0.02 + + local emitter = ParticleEmitter( EndPos, false ) + + if not emitter or not IsValid( emitter ) then return end + + local dir = (self.Entity:GetPos() - EndPos):GetNormalized() + + for i = 0, 3 do + local particle = emitter:Add( "sprites/light_glow02_add", EndPos ) + + local vel = VectorRand() * 250 + Trace.HitNormal + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(12,24) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( 255, 0, 0 ) + particle:SetGravity( Vector(0,0,-600) ) + + particle:SetAirResistance( 0 ) + + particle:SetCollide( true ) + particle:SetBounce( 1 ) + end + + local Dist = (StartPos - EndPos):Length() + + local invMul = (1 - Mul) + + for i = 0, Dist, 25 do + local Pos = StartPos + Dir * i + + local particle = emitter:Add( "sprites/rico1", Pos ) + + local vel = VectorRand() * 150 + + if not particle then continue end + + particle:SetVelocity( vel + vel * invMul ) + particle:SetDieTime( 0.1 + 0.15 * invMul ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand( 1, 5 ) + invMul * 2 ) + particle:SetEndSize( 0 ) + particle:SetColor( 50 + 205 * Mul, 0, 0 ) + particle:SetAirResistance( 0 ) + particle:SetRoll( math.Rand(-10,10) ) + particle:SetRollDelta( math.Rand(-10,10) ) + particle:SetGravity( Vector(0,0,-600 * invMul) ) + + particle:SetAirResistance( 0 ) + end + + emitter:Finish() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laat_left_projector.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laat_left_projector.lua new file mode 100644 index 0000000..0b2c6d0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laat_left_projector.lua @@ -0,0 +1,78 @@ +EFFECT.Mat = Material( "effects/lvs/ballturret_projectorbeam" ) +EFFECT.HitMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.ID = self.Entity:LookupAttachment( "muzzle_ballturret_left" ) + + if self.ID then + local Muzzle = self.Entity:GetAttachment( self.ID ) + + self:SetRenderBoundsWS( self.Entity:GetPos(), Muzzle.Ang:Up() * 50000 ) + end + end +end + +function EFFECT:Think() + if not IsValid( self.Entity ) or not self.ID or not self.Entity:GetBTLFire() then + return false + end + + return true +end + + +function EFFECT:Render() + if not self.ID or not IsValid( self.Entity ) then return end + + local Muzzle = self.Entity:GetAttachment( self.ID ) + + local Dir = Muzzle.Ang:Up() + local StartPos = Muzzle.Pos + Dir * 14 + local Trace = util.TraceLine( { start = StartPos, endpos = StartPos + Dir * 50000, filter = self } ) + local EndPos = Trace.HitPos + + self:SetRenderBoundsWS( StartPos, EndPos ) + + render.SetMaterial( self.Mat ) + render.DrawBeam( StartPos, EndPos, 14 + math.random(0,4), 1, 0, Color(0,255,0,255) ) + render.DrawBeam( StartPos, EndPos, 3 + math.random(0,4), 1, 0, Color(255,255,255,255) ) + + render.SetMaterial( self.HitMat ) + local A = 150 + math.random(0,20) + local B = 70 + math.random(0,20) + render.DrawSprite( StartPos, A, A, Color(0,255,0,255) ) + render.DrawSprite( StartPos, B, B, Color(255,255,255,255) ) + + render.DrawSprite( EndPos, A, A, Color(0,255,0,255) ) + render.DrawSprite( EndPos + VectorRand() * 10, B, B, Color(255,255,255,255) ) + + if math.random(0,5) == 1 then + local emitter = ParticleEmitter( EndPos, false ) + local dir = (self.Entity:GetPos() - EndPos):GetNormalized() + + for i = 0, 10 do + local particle = emitter:Add( "sprites/rico1", EndPos ) + + local vel = VectorRand() * 100 + dir * 40 + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.1,0.3) * 0.5 ) + particle:SetStartAlpha( math.Rand( 200, 255 ) ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(1,30) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + + particle:SetAirResistance( 0 ) + end + + emitter:Finish() + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laat_right_projector.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laat_right_projector.lua new file mode 100644 index 0000000..ae8a63d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laat_right_projector.lua @@ -0,0 +1,78 @@ +EFFECT.Mat = Material( "effects/lvs/ballturret_projectorbeam" ) +EFFECT.HitMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.ID = self.Entity:LookupAttachment( "muzzle_ballturret_right" ) + + if self.ID then + local Muzzle = self.Entity:GetAttachment( self.ID ) + + self:SetRenderBoundsWS( self.Entity:GetPos(), Muzzle.Ang:Up() * 50000 ) + end + end +end + +function EFFECT:Think() + if not IsValid( self.Entity ) or not self.ID or not self.Entity:GetBTRFire() then + return false + end + + return true +end + + +function EFFECT:Render() + if not self.ID or not IsValid( self.Entity ) then return end + + local Muzzle = self.Entity:GetAttachment( self.ID ) + + local Dir = Muzzle.Ang:Up() + local StartPos = Muzzle.Pos + Dir * 14 + local Trace = util.TraceLine( { start = StartPos, endpos = StartPos + Dir * 50000, filter = self } ) + local EndPos = Trace.HitPos + + self:SetRenderBoundsWS( StartPos, EndPos ) + + render.SetMaterial( self.Mat ) + render.DrawBeam( StartPos, EndPos, 14 + math.random(0,4), 1, 0, Color(0,255,0,255) ) + render.DrawBeam( StartPos, EndPos, 3 + math.random(0,4), 1, 0, Color(255,255,255,255) ) + + render.SetMaterial( self.HitMat ) + local A = 150 + math.random(0,20) + local B = 70 + math.random(0,20) + render.DrawSprite( StartPos, A, A, Color(0,255,0,255) ) + render.DrawSprite( StartPos, B, B, Color(255,255,255,255) ) + + render.DrawSprite( EndPos, A, A, Color(0,255,0,255) ) + render.DrawSprite( EndPos + VectorRand() * 10, B, B, Color(255,255,255,255) ) + + if math.random(0,5) == 1 then + local emitter = ParticleEmitter( EndPos, false ) + local dir = (self.Entity:GetPos() - EndPos):GetNormalized() + + for i = 0, 10 do + local particle = emitter:Add( "sprites/rico1", EndPos ) + + local vel = VectorRand() * 100 + dir * 40 + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.1,0.3) * 0.5 ) + particle:SetStartAlpha( math.Rand( 200, 255 ) ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(1,30) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + + particle:SetAirResistance( 0 ) + end + + emitter:Finish() + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laat_wing_projector.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laat_wing_projector.lua new file mode 100644 index 0000000..8d84866 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laat_wing_projector.lua @@ -0,0 +1,82 @@ +EFFECT.Mat = Material( "effects/lvs/ballturret_projectorbeam" ) +EFFECT.HitMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + self.StartPos = Vector(-172.97,334.04,93.25) + self.EndPos = self.Entity:GetWingTurretTarget() +end + +function EFFECT:Think() + if not IsValid( self.Entity ) or not self.Entity:GetWingTurretFire() then + return false + end + + self.EndPosDesired = self.Entity:GetWingTurretTarget() + self:SetRenderBoundsWS( self.Entity:GetPos(), self.EndPosDesired ) + + return true + +end + +function EFFECT:Render() + if not self.EndPosDesired then return end + + self.EndPos = self.EndPos + (self.EndPosDesired - self.EndPos) * FrameTime() * 10 + + for i = -1,1,2 do + local StartPos = self.Entity:LocalToWorld( self.StartPos * Vector(1,i,1) ) + + local Trace = util.TraceLine( { start = StartPos, endpos = self.EndPos} ) + local EndPos = Trace.HitPos + + if self.Entity:WorldToLocal( EndPos ).z < 0 then + self.StartPos = Vector(-172.97,334.04,93.25) + else + self.StartPos = Vector(-174.79,350.05,125.98) + end + + if Trace.Entity == self.Entity then continue end + + render.SetMaterial( self.Mat ) + render.DrawBeam( StartPos, EndPos, 14 + math.random(0,4), 1, 0, Color(0,255,0,255) ) + render.DrawBeam( StartPos, EndPos, 3 + math.random(0,4), 1, 0, Color(255,255,255,255) ) + + render.SetMaterial( self.HitMat ) + local A = 150 + math.random(0,20) + local B = 70 + math.random(0,20) + render.DrawSprite( StartPos, A, A, Color(0,255,0,255) ) + render.DrawSprite( StartPos, B, B, Color(255,255,255,255) ) + + render.DrawSprite( EndPos, A, A, Color(0,255,0,255) ) + render.DrawSprite( EndPos + VectorRand() * 10, B, B, Color(255,255,255,255) ) + + if math.random(0,5) == 1 then + local emitter = ParticleEmitter( EndPos, false ) + local dir = (self.Entity:GetPos() - EndPos):GetNormalized() + + for i = 0, 10 do + local particle = emitter:Add( "sprites/rico1", EndPos ) + + local vel = VectorRand() * 100 + dir * 40 + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.1,0.3) * 0.5 ) + particle:SetStartAlpha( math.Rand( 200, 255 ) ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(1,30) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + + particle:SetAirResistance( 0 ) + end + + emitter:Finish() + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue.lua new file mode 100644 index 0000000..add28c5 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue.lua @@ -0,0 +1,34 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 1000 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 4, endpos + dir * len * 4, 200, 1, 0, Color( 0, 0, 255, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 45, 1, 0, Color( 0, 0, 255, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 15, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_continuous.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_continuous.lua new file mode 100644 index 0000000..33381c9 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_continuous.lua @@ -0,0 +1,75 @@ +EFFECT.MatBeam = Material( "effects/lvs/ballturret_projectorbeam" ) + +-- variables +local LifeTime = 1.4 + +local StartSizeOuter = 16 +local StartSizeInner = 6 + +local EndSizeOuter = 3 +local EndSizeInner = 1 +local DissipateExponentScale = 16 +local DissipateExponentAlpha = 16 + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.StartPos = pos + self.Dir = dir + + self.ID = data:GetMaterialIndex() + + self.LifeTime = LifeTime + self.DieTime = CurTime() + self.LifeTime + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + + local bullet = LVS:GetBullet( self.ID ) + + if bullet then + self.StartPos = bullet.Entity:LocalToWorld( bullet.SrcEntity ) + self.EndPos = bullet:GetPos() + self.BulletAlive = true + self.BulletFilter = bullet.Filter + else + -- fix problem in lvs bullet code not updating the target destination + if self.BulletAlive and self.BulletFilter then + self.BulletAlive = nil + + local trace = util.TraceLine( { + start = self.StartPos, + endpos = self.StartPos + self.Dir * 50000, + mask = MASK_SHOT_HULL, + filter = self.BulletFilter, + } ) + + self.EndPos = trace.HitPos + end + end + + if not self.StartPos or not self.EndPos then return end + + if bullet and bullet:GetLength() <= 0 then return end + + -- math, dont change + local S = (self.DieTime - CurTime()) / self.LifeTime + local invS = 1 - S + + local Alpha = 255 * (S ^ DissipateExponentAlpha) + local Scale = S ^ DissipateExponentScale + local invScale = invS ^ DissipateExponentScale + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( self.StartPos, self.EndPos, StartSizeOuter * Scale + EndSizeOuter * invScale, 1, 0, Color( 0, 0, 255, Alpha ) ) + render.DrawBeam( self.StartPos, self.EndPos, StartSizeInner * Scale + EndSizeInner * invScale, 1, 0, Color( 255, 255, 255, Alpha ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_long.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_long.lua new file mode 100644 index 0000000..7544ad7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_long.lua @@ -0,0 +1,34 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 2500 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 2, endpos + dir * len * 2, 200, 1, 0, Color( 0, 0, 255, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 45, 1, 0, Color( 0, 0, 255, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 15, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_short.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_short.lua new file mode 100644 index 0000000..05ee25b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_blue_short.lua @@ -0,0 +1,34 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 300 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 2, endpos + dir * len * 2, 200, 1, 0, Color( 0, 0, 255, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 45, 1, 0, Color( 0, 0, 255, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 15, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_charge.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_charge.lua new file mode 100644 index 0000000..f7288a4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_charge.lua @@ -0,0 +1,103 @@ + +EFFECT.HeatWaveMat = Material( "particle/warp1_warp" ) +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + self.Ent = data:GetEntity() + self.ID = data:GetAttachment() + + if not IsValid( self.Ent ) then return end + + + local att = self.Ent:GetAttachment( self.ID ) + + if not att then return end + + local Pos = att.Pos + + self.LifeTime = 0.35 + self.DieTime = CurTime() + self.LifeTime + + self.Emitter = ParticleEmitter( Pos, false ) + self.Particles = {} +end + +function EFFECT:Think() + if (self.DieTime or 0) < CurTime() or not IsValid( self.Ent ) then + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end + + return false + end + + self:DoSpark() + + return true +end + +function EFFECT:DoSpark() + local T = CurTime() + + if (self._Next or 0) > T then return end + + self._Next = T + 0.01 + + if not IsValid( self.Emitter ) then return end + + if not IsValid( self.Ent ) or not self.ID then return end + + local att = self.Ent:GetAttachment( self.ID ) + + if not att then return end + + local Pos = att.Pos + local Dir = VectorRand() * 25 + + for id, particle in pairs( self.Particles ) do + if not particle then + self.Particles[ id ] = nil + + continue + end + + particle:SetGravity( (Pos - particle:GetPos()) * 50 ) + end + + local particle = self.Emitter:Add( "sprites/rico1", Pos + Dir ) + + if not particle then return end + + particle:SetDieTime( 0.25 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand( 1, 5 ) ) + particle:SetEndSize( 0 ) + particle:SetColor( 255, 0, 0 ) + particle:SetAirResistance( 0 ) + particle:SetRoll( math.Rand(-10,10) ) + particle:SetRollDelta( math.Rand(-10,10) ) + + table.insert( self.Particles, particle ) +end + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.ID then return end + + local att = self.Ent:GetAttachment( self.ID ) + + if not att then return end + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + + if Scale <= 0 then return end + + local rnd = VectorRand() * math.Rand(0,0.5) + + render.SetMaterial( self.HeatWaveMat ) + render.DrawSprite( att.Pos, 30 *(1 - Scale), 30 * (1 - Scale), Color( 255, 255, 255, 255) ) + + render.SetMaterial( self.GlowMat ) + render.DrawSprite( att.Pos + rnd, 120 * (1 - Scale), 120 * (1 - Scale), Color(255,0,0,255) ) +end + diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_explosion.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_explosion.lua new file mode 100644 index 0000000..642127a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_explosion.lua @@ -0,0 +1,73 @@ + +local GlowMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + self.Col = data:GetStart() or Vector(255,100,0) + + self.LifeTime = 0.4 + self.DieTime = CurTime() + self.LifeTime + + local emitter = ParticleEmitter( self.Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0, 20 do + local particle = emitter:Add( "sprites/light_glow02_add", self.Pos ) + + local vel = VectorRand() * 400 + + if particle then + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.4,0.8) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(24,48) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( self.Col.x,self.Col.y,self.Col.z ) + particle:SetGravity( Vector(0,0,-600) ) + + particle:SetAirResistance( 0 ) + + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + end + end + + for i = 0, 20 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 500 ) + particle:SetDieTime( 0.15 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( 32 ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetRollDelta( math.Rand( -1, 1 ) * 100 ) + particle:SetColor( self.Col.x,self.Col.y,self.Col.z ) + particle:SetCollide( false ) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + if not self.Col then return end + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + render.SetMaterial( GlowMat ) + render.DrawSprite( self.Pos, 400 * Scale, 400 * Scale, Color( self.Col.x,self.Col.y,self.Col.z, 255) ) + render.DrawSprite( self.Pos, 100 * Scale, 100 * Scale, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_explosion_aat.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_explosion_aat.lua new file mode 100644 index 0000000..80709cb --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_explosion_aat.lua @@ -0,0 +1,121 @@ + + +EFFECT.HeatWaveMat = Material( "particle/warp1_warp" ) +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + self.Pos = Pos + + self.LifeTime = 0.4 + self.DieTime = CurTime() + self.LifeTime + self.DieTimeGlow = CurTime() + 0.2 + + sound.Play( "LVS.AAT.LASER_EXPLOSION", Pos ) + self:Explosion( Pos ) +end + +function EFFECT:Explosion( pos ) + local emitter = ParticleEmitter( pos, false ) + + if not emitter then return end + + for i = 0, 15 do + local particle = emitter:Add( "sprites/light_glow02_add", pos ) + + local vel = VectorRand() * 450 + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(1,1.6) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(12,15) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( 255,0,0 ) + particle:SetGravity( Vector(0,0,-600) ) + + particle:SetAirResistance( 0 ) + + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + end + + for i = 0, 5 do + local particle = emitter:Add( "sprites/rico1", pos ) + + local vel = VectorRand() * 1000 + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.5,0.8) ) + particle:SetStartAlpha( math.Rand( 200, 255 ) ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(10,20) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( 255, 0, 0 ) + particle:SetGravity( -vel:GetNormalized() * math.random(1250,1750) ) + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + + particle:SetAirResistance( 200 ) + end + + for i = 0, 20 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand(-1,1) * 500 ) + particle:SetDieTime( 0.14 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( math.Rand(30,60) ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + local Scale = math.max((self.DieTime - self.LifeTime + 0.3 - CurTime()) / 0.3,0) + render.SetMaterial( self.HeatWaveMat ) + render.DrawSprite( self.Pos, 300 * Scale, 300 * Scale, Color( 255, 255, 255, 255) ) + + render.SetMaterial( self.GlowMat ) + render.DrawSprite( self.Pos, 1000 * Scale, 1000 * Scale, Color( 255, 100, 50, 255) ) + + local Scale = (self.DieTimeGlow - CurTime()) / 0.2 + if Scale > 0 then + render.SetMaterial( self.GlowMat ) + render.DrawSprite( self.Pos, 100 * Scale, 100 * Scale, Color( 250, 0, 0, 255) ) + render.DrawSprite( self.Pos, 25 * Scale, 25 * Scale, Color( 255, 255, 255, 255) ) + end + + local Scale = (self.DieTime - self.LifeTime + 0.25 - CurTime()) / 0.25 + local InvScale = 1 - Scale + if Scale > 0 then + render.SetColorMaterial() + render.DrawSphere( self.Pos, -180 * InvScale, 30,30, Color( 255, 0, 0, 255 * (Scale ^ 2) ) ) + render.DrawSphere( self.Pos, -190 * InvScale, 30,30, Color( 255, 0, 0, 150 * (Scale ^ 2) ) ) + render.DrawSphere( self.Pos, -200 * InvScale, 30,30, Color( 255, 0, 0, 50 * (Scale ^ 2) ) ) + render.DrawSphere( self.Pos, 210 * InvScale, 30,30, Color( 255, 0, 0, 50 * (Scale ^ 2) ) ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_green.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_green.lua new file mode 100644 index 0000000..2d42dbd --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_green.lua @@ -0,0 +1,34 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 1000 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 4, endpos + dir * len * 4, 200, 1, 0, Color( 0, 255, 0, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 45, 1, 0, Color( 0, 255, 0, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 15, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_green_short.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_green_short.lua new file mode 100644 index 0000000..f30cc54 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_green_short.lua @@ -0,0 +1,34 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 300 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 2, endpos + dir * len * 2, 100, 1, 0, Color( 0, 255, 0, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 60, 1, 0, Color( 0, 255, 0, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 15, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_impact.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_impact.lua new file mode 100644 index 0000000..8814327 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_impact.lua @@ -0,0 +1,78 @@ +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + self.Dir = data:GetNormal() + self.Col = data:GetStart() or Vector(255,100,0) + + self.LifeTime = 0.2 + self.DieTime = CurTime() + self.LifeTime + + local trace = util.TraceLine( { + start = self.Pos - self.Dir, + endpos = self.Pos + self.Dir, + mask = MASK_SOLID_BRUSHONLY, + } ) + + self.Flat = trace.Hit and not trace.HitSky + + local Col = self.Col + local Pos = self.Pos + + local emitter = ParticleEmitter( Pos, false ) + + for i = 0, 10 do + local particle = emitter:Add( "sprites/light_glow02_add", Pos ) + + local vel = VectorRand() * 200 + self.Dir * 80 + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(12,24) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( Col.x,Col.y,Col.z ) + particle:SetGravity( Vector(0,0,-600) ) + + particle:SetAirResistance( 0 ) + + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + end + + emitter:Finish() +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + + local S1 = 200 * Scale + local S2 = 50 * Scale + + if self.Flat then + cam.Start3D2D( self.Pos + self.Dir, self.Dir:Angle() + Angle(90,0,0), 1 ) + surface.SetMaterial( self.GlowMat ) + surface.SetDrawColor( self.Col.x, self.Col.y, self.Col.z, 255 ) + surface.DrawTexturedRectRotated( 0, 0, S1 , S1 , 0 ) + + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRectRotated( 0, 0, S2 , S2 , 0 ) + cam.End3D2D() + end + + render.SetMaterial( self.GlowMat ) + render.DrawSprite( self.Pos + self.Dir, S1, S1, Color( self.Col.x, self.Col.y, self.Col.z, 255 ) ) + render.DrawSprite( self.Pos + self.Dir, S2, S2, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red.lua new file mode 100644 index 0000000..144edf7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red.lua @@ -0,0 +1,34 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 1000 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 4, endpos + dir * len * 4, 200, 1, 0, Color( 255, 0, 0, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 45, 1, 0, Color( 255, 0, 0, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 15, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red_aat.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red_aat.lua new file mode 100644 index 0000000..a05fc87 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red_aat.lua @@ -0,0 +1,103 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) +EFFECT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) + + local emitter = ParticleEmitter( pos, false ) + + if not IsValid( emitter ) then return end + + local trace = util.TraceLine( { + start = pos, + endpos = pos - Vector(0,0,500), + mask = MASK_SOLID_BRUSHONLY, + } ) + + if not trace.Hit then return end + + local VecCol = (render.GetLightColor( trace.HitPos + trace.HitNormal ) * 0.8 + Vector(0.17,0.15,0.1)) * 255 + for i = 1,24 do + local particle = emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], trace.HitPos ) + + if not particle then continue end + + local ang = i * 15 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + particle:SetVelocity( Vector(X,Y,0) * 3000 ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( 500 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 50 ) + particle:SetEndSize( 240 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,150) + dir * 2000 ) + particle:SetCollide( false ) + end + + emitter:Finish() + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Intensity = 8 + local Ratio = math.min( 250 / (ViewEnt:GetPos() - trace.HitPos):Length(), 1 ) + + if Ratio < 0 then return end + + util.ScreenShake( trace.HitPos, Intensity * Ratio, 0.1, 0.5, 250 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 100 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 8, endpos + dir * len * 8, 200, 1, 0, Color( 255, 0, 0, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 60, 1, 0, Color( 255, 0, 0, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 30, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red_short.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red_short.lua new file mode 100644 index 0000000..f5957cc --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_laser_red_short.lua @@ -0,0 +1,34 @@ + +EFFECT.MatBeam = Material( "effects/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 300 * bullet:GetLength() + + render.SetMaterial( self.MatSprite ) + render.DrawBeam( endpos - dir * len * 2, endpos + dir * len * 2, 200, 1, 0, Color( 255, 0, 0, 255 ) ) + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 45, 1, 0, Color( 255, 0, 0, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 15, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_missiletrail.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_missiletrail.lua new file mode 100644 index 0000000..8178cf4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_missiletrail.lua @@ -0,0 +1,127 @@ +EFFECT.Offset = Vector(-8,0,0) + +local GlowMat = Material( "sprites/light_glow02_add" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.OldPos = self.Entity:LocalToWorld( self.Offset ) + + self.Emitter = ParticleEmitter( self.Entity:LocalToWorld( self.OldPos ), false ) + end +end + +function EFFECT:doFX( pos ) + if not IsValid( self.Entity ) then return end + + if IsValid( self.Emitter ) then + local emitter = self.Emitter + + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + if particle then + particle:SetGravity( Vector(0,0,100) + VectorRand() * 50 ) + particle:SetVelocity( -self.Entity:GetForward() * 200 ) + particle:SetAirResistance( 600 ) + particle:SetDieTime( math.Rand(1.5,2) ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 60 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetRollDelta( math.Rand( -1, 1 ) ) + particle:SetColor(40,40,40) + particle:SetCollide( false ) + end + + local particle = emitter:Add( "particles/flamelet"..math.random(1,5), pos ) + if particle then + particle:SetVelocity( -self.Entity:GetForward() * math.Rand(250,800) + self.Entity:GetVelocity()) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 8 ) + particle:SetEndSize( 1 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 0 ) ) + particle:SetCollide( false ) + end + + local particle = emitter:Add( "particles/flamelet"..math.random(1,5), self.Entity:GetPos() ) + if particle then + particle:SetVelocity( -self.Entity:GetForward() * 200 + VectorRand() * 50 ) + particle:SetDieTime( 0.25 ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 6 ) + particle:SetEndSize( 2 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 0 ) ) + particle:SetCollide( false ) + end + end +end + +function EFFECT:Think() + if IsValid( self.Entity ) then + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.02 + + local oldpos = self.OldPos + local newpos = self.Entity:LocalToWorld( self.Offset ) + self:SetPos( newpos ) + + local Sub = (newpos - oldpos) + local Dir = Sub:GetNormalized() + local Len = Sub:Length() + + self.OldPos = newpos + + for i = 0, Len, 45 do + local pos = oldpos + Dir * i + + self:doFX( pos ) + end + end + + return true + end + + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end + + return false +end + +function EFFECT:Render() + local ent = self.Entity + + if not IsValid( ent ) then return end + + local pos = ent:LocalToWorld( self.Offset ) + + render.SetMaterial( GlowMat ) + render.DrawSprite( pos, 100, 100, Color( 255, 200, 150, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_muzzle.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_muzzle.lua new file mode 100644 index 0000000..44c5436 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_muzzle.lua @@ -0,0 +1,40 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local Ent = data:GetEntity() + local Vel = Dir * 10 + + if IsValid( Ent ) then + Vel = Ent:GetVelocity() + end + + local emitter = ParticleEmitter( Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0, 12 do + local particle = emitter:Add( "effects/muzzleflash2", Pos + Dir * i * 0.7 * math.random(1,2) * 0.5 ) + local Size = 1 + + if not particle then continue end + + particle:SetVelocity( Dir * 800 + Vel ) + particle:SetDieTime( 0.05 ) + particle:SetStartAlpha( 255 * Size ) + particle:SetStartSize( math.max( math.random(10,24) - i * 0.5,0.1 ) * Size ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 255, 255, 255 ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_muzzle_colorable.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_muzzle_colorable.lua new file mode 100644 index 0000000..1f5f976 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_muzzle_colorable.lua @@ -0,0 +1,41 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local Ent = data:GetEntity() + local Col = data:GetStart() or Vector(255,255,255) + local Vel = Dir * 10 + + if IsValid( Ent ) then + Vel = Ent:GetVelocity() + end + + local emitter = ParticleEmitter( Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0, 12 do + local particle = emitter:Add( "effects/muzzleflash2", Pos + Dir * i * 0.7 * math.random(1,2) * 0.5 ) + local Size = 1 + + if not particle then continue end + + particle:SetVelocity( Dir * 800 + Vel ) + particle:SetDieTime( 0.05 ) + particle:SetStartAlpha( 255 * Size ) + particle:SetStartSize( math.max( math.random(10,24) - i * 0.5,0.1 ) * Size ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( Col.x, Col.y, Col.z ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_dust.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_dust.lua new file mode 100644 index 0000000..2b170ab --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_dust.lua @@ -0,0 +1,82 @@ +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +local MatDebris = { + "effects/lvs_base/particle_debris_01", + "effects/lvs_base/particle_debris_02", +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + local Dir = Ent:GetForward() + + local emitter = Ent:GetParticleEmitter( Ent:GetPos() ) + + local VecCol = render.GetLightColor( Pos + Vector(0,0,10) ) * 0.5 + Vector(0.3,0.25,0.15) + + if emitter and emitter.Add then + for i = 1, 3 do + local particle = emitter:Add( MatDebris[math.random(1,#MatDebris)], Pos + VectorRand(-10,10) ) + if particle then + particle:SetVelocity( Vector(0,0,150) - Dir * 150 ) + particle:SetDieTime( 0.2 ) + particle:SetAirResistance( 60 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetStartSize( 15 ) + particle:SetEndSize( 50 ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor( VecCol.x * 130,VecCol.y * 100,VecCol.z * 60 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( false ) + end + end + + local Right = Ent:GetRight() + Right.z = 0 + Right:Normalize() + + for i = -1,1,2 do + local particle = emitter:Add( Materials[math.random(1,#Materials)], Pos + Vector(0,0,10) ) + if particle then + particle:SetVelocity( -Dir * 400 + Right * 150 * i ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( 150 ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( -80 ) + particle:SetEndSize( 400 ) + particle:SetColor( VecCol.x * 255,VecCol.y * 255,VecCol.z * 255 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + end + end +end + + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_impact.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_impact.lua new file mode 100644 index 0000000..60d54b6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_impact.lua @@ -0,0 +1,89 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + if not LVS.ShowPhysicsEffects then return end + + local dir = data:GetNormal() + local pos = data:GetOrigin() + dir + + local emitter = ParticleEmitter( pos, false ) + + for i = 0, 10 do + local particle = emitter:Add( "effects/spark", pos ) + + local vel = VectorRand() * 75 + dir * 75 + Vector(0,0,100) + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetDieTime( math.Rand(2.5,5) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + + particle:SetStartLength( 6 ) + particle:SetEndLength(0) + + particle:SetStartSize( 3 ) + particle:SetEndSize( 0 ) + + particle:SetRoll( math.Rand(-5,5) ) + particle:SetColor( 255, 200, 50 ) + particle:SetGravity( Vector(0,0,-600) ) + particle:SetCollide( true ) + end + + local smoke = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + + if smoke then + smoke:SetVelocity( dir * 30 + VectorRand() * 15 ) + smoke:SetDieTime( math.Rand(1.5,3) ) + smoke:SetAirResistance( 100 ) + smoke:SetStartAlpha( 100 ) + smoke:SetEndAlpha( 0 ) + smoke:SetStartSize( 15 ) + smoke:SetEndSize( 30 ) + smoke:SetColor(30,30,30) + smoke:SetGravity(Vector(0,0,40)) + smoke:SetCollide( false ) + smoke:SetRollDelta( math.Rand(-1,1) ) + end + + local flash = emitter:Add( "effects/yellowflare",pos ) + + if flash then + flash:SetPos( pos ) + flash:SetStartAlpha( 200 ) + flash:SetEndAlpha( 0 ) + flash:SetColor( 255, 200, 0 ) + flash:SetEndSize( 100 ) + flash:SetDieTime( 0.1 ) + flash:SetStartSize( 0 ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_scrape.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_scrape.lua new file mode 100644 index 0000000..6f569f5 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_scrape.lua @@ -0,0 +1,109 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + if not LVS.ShowPhysicsEffects then + self.LifeTime = 0 + self.DieTime = 0 + + return + end + + self.Pos = data:GetOrigin() + + self.mat = Material( "sprites/light_glow02_add" ) + + self.LifeTime = 0.2 + self.DieTime = CurTime() + self.LifeTime + + local Col = self.Col + local Pos = self.Pos + local Dir = data:GetNormal() + local Strength = data:GetMagnitude() + + local emitter = ParticleEmitter( Pos, false ) + + for i = 0,1 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + + local vel = VectorRand() * 100 + Dir * 40 + + if particle then + particle:SetVelocity( vel ) + particle:SetDieTime( 1 ) + particle:SetAirResistance( 1000 ) + particle:SetStartAlpha( 10 ) + particle:SetStartSize( 2 ) + particle:SetEndSize( 12 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 40, 30, 20 ) + particle:SetGravity( Dir * 50 ) + particle:SetCollide( false ) + end + end + + for i = 0, 3 do + local particle = emitter:Add( "effects/spark", Pos ) + + local vel = VectorRand() * 25 * (1 - Strength) + (VectorRand() * 100 + Dir * 150) * Strength + + if particle then + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( 2 ) + particle:SetStartAlpha( math.Rand( 200, 255 ) ) + particle:SetEndAlpha( 0 ) + + particle:SetStartLength( 4 ) + particle:SetEndLength(0) + + particle:SetStartSize( 2 ) + particle:SetEndSize( 0 ) + + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + particle:SetAirResistance( 0 ) + particle:SetColor( 255, 200, 50 ) + particle:SetGravity( Vector(0,0,-600) ) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + if self.DieTime < CurTime() then + return false + end + + return true +end + +local mat = Material( "sprites/light_glow02_add" ) +function EFFECT:Render() + if not LVS.ShowPhysicsEffects then return end + + local Scale = (self.DieTime - CurTime()) / self.LifeTime + render.SetMaterial( mat ) + render.DrawSprite( self.Pos, 32, 32, Color( 255 * Scale, 175 * Scale, 80 * Scale, 255) ) +end + diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_trackdust.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_trackdust.lua new file mode 100644 index 0000000..117945b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_trackdust.lua @@ -0,0 +1,43 @@ + +EFFECT.DustMat = { + "effects/lvs/track_debris_01", + "effects/lvs/track_debris_02", +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local ent = data:GetEntity() + local size = data:GetMagnitude() + + if not IsValid( ent ) then return end + + local dir = data:GetNormal() + + local emitter = ent:GetParticleEmitter( ent:GetPos() ) + + local VecCol = (render.GetLightColor( pos + dir ) * 0.5 + Vector(0.5,0.4,0.3)) * 255 + + local scale = math.Clamp( size / 23, 0.5, 1.25 ) + + local particle = emitter:Add( self.DustMat[ math.random(1,#self.DustMat) ] , pos + dir * 5 * scale + VectorRand() * 5 * scale ) + + if not particle then return end + + particle:SetVelocity( (dir * 100 * scale + VectorRand() * 20 * scale) ) + particle:SetDieTime( math.Rand(0.4,0.6) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( math.random(100,255) ) + particle:SetStartSize( 6 * scale ) + particle:SetEndSize( math.random(20,25) * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,-600) ) + particle:SetCollide( false ) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_trackscraping.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_trackscraping.lua new file mode 100644 index 0000000..3f0ae1a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_trackscraping.lua @@ -0,0 +1,48 @@ + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + local emitter = ParticleEmitter( pos, false ) + + for i = 1, 360 do + if math.random(1,30) ~= 10 then continue end + + local ang = i + + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + local forward = Vector(X,Y,0) + forward:Rotate( dir:Angle() + Angle(90,0,0) ) + + local spark = emitter:Add("effects/spark", pos + VectorRand() * 10 ) + + if not spark then continue end + + spark:SetStartAlpha( 255 ) + spark:SetEndAlpha( 0 ) + spark:SetCollide( true ) + spark:SetBounce( math.Rand(0,1) ) + spark:SetColor( 255, 255, 255 ) + spark:SetGravity( Vector(0,0,-600) ) + spark:SetEndLength(0) + + local size = math.Rand(2, 4) + spark:SetEndSize( size ) + spark:SetStartSize( size ) + + spark:SetStartLength( math.Rand(5,7) ) + spark:SetDieTime( math.Rand(0.1, 0.3) ) + spark:SetVelocity( forward * math.random(75,100) + VectorRand() * 50 ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_turretscraping.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_turretscraping.lua new file mode 100644 index 0000000..189b21e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_turretscraping.lua @@ -0,0 +1,48 @@ + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + local emitter = ParticleEmitter( pos, false ) + + for i = 1, 360 do + if math.random(1,30) ~= 15 then continue end + + local ang = i + + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + local forward = Vector(X,Y,0) + forward:Rotate( dir:Angle() + Angle(90,0,0) ) + + local spark = emitter:Add("effects/spark", pos + forward * 25 ) + + if not spark then continue end + + spark:SetStartAlpha( 255 ) + spark:SetEndAlpha( 0 ) + spark:SetCollide( true ) + spark:SetBounce( math.Rand(0,1) ) + spark:SetColor( 255, 255, 255 ) + spark:SetGravity( Vector(0,0,-600) ) + spark:SetEndLength(0) + + local size = math.Rand(4, 6) + spark:SetEndSize( size ) + spark:SetStartSize( size ) + + spark:SetStartLength( math.Rand(10,20) ) + spark:SetDieTime( math.Rand(0.01, 0.2) ) + spark:SetVelocity( forward * math.random(250,400) + dir * 150 + VectorRand() * 50 ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_water.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_water.lua new file mode 100644 index 0000000..285aeee --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_water.lua @@ -0,0 +1,63 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + self.LifeTime = math.Rand(1.5,3) + self.DieTime = CurTime() + self.LifeTime + + local LightColor = render.GetLightColor( Pos ) + self.VecCol = Vector(1,1.2,1.4) * (0.06 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 + self.VecCol.x = math.min( self.VecCol.x, 255 ) + self.VecCol.y = math.min( self.VecCol.y, 255 ) + self.VecCol.z = math.min( self.VecCol.z, 255 ) + + self.Splash = { + Pos = Pos, + Mat = Material("effects/splashwake1"), + RandomAng = math.random(0,360), + } + + local emitter = Ent:GetParticleEmitter( Ent:GetPos() ) + + if emitter and emitter.Add then + local particle = emitter:Add( "effects/splash4", Pos + VectorRand(-10,10) - Vector(0,0,20) ) + if particle then + particle:SetVelocity( Vector(0,0,250) ) + particle:SetDieTime( 0.8 ) + particle:SetAirResistance( 60 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 50 ) + particle:SetEndSize( 100 ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor(self.VecCol.r,self.VecCol.g,self.VecCol.b) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( false ) + end + end +end + + +function EFFECT:Think() + if CurTime() > self.DieTime then + return false + end + return true +end + +function EFFECT:Render() + if self.Splash and self.LifeTime then + local Scale = (self.DieTime - self.LifeTime - CurTime()) / self.LifeTime + local S = 200 - Scale * 600 + local Alpha = 100 + 100 * Scale + + cam.Start3D2D( self.Splash.Pos + Vector(0,0,1), Angle(0,0,0), 1 ) + surface.SetMaterial( self.Splash.Mat ) + surface.SetDrawColor( self.VecCol.r, self.VecCol.g, self.VecCol.b, Alpha ) + surface.DrawTexturedRectRotated( 0, 0, S , S, self.Splash.RandomAng ) + cam.End3D2D() + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_water_advanced.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_water_advanced.lua new file mode 100644 index 0000000..a638a34 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_water_advanced.lua @@ -0,0 +1,175 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + local emitter = Ent:GetParticleEmitter( Ent:GetPos() ) + + if not IsValid( emitter ) then return end + + local Vel = Ent:GetVelocity() + Vel.z = 0 + + local Speed = Vel:Length() + + if Speed < 50 then return end + + local Steer = math.abs( Ent:GetSteer() ) + + local ShouldPlaySound = false + + local T = CurTime() + + local LightColor = render.GetLightColor( Pos ) + local VecCol = Vector(1,1.2,1.4) * (0.06 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 + VecCol.x = math.min( VecCol.x, 255 ) + VecCol.y = math.min( VecCol.y, 255 ) + VecCol.z = math.min( VecCol.z, 255 ) + + local mul = math.min(Speed * 0.005,1) + local invmul = 1 - mul + + local EntPos = Ent:GetPos() + local Len = Ent:BoundingRadius() * 1.5 + local MoveDir = Vel:GetNormalized() + local MoveAng = MoveDir:Angle() + + local Target = LocalPlayer() + + if IsValid( Target ) then + ShouldPlaySound = Target:lvsGetVehicle() == Ent + + local ViewEnt = Target:GetViewEntity() + + if IsValid( ViewEnt ) then + Target = ViewEnt + end + end + + local SwapSides = math.abs( Ent:GetSteer() ) > 0.9 + local Res = math.max( math.Round( (Target:GetPos() - Pos):LengthSqr() / 2500000, 0 ), 5 ) + + for i = -135, 135, Res do + local Dir = Angle(0,MoveAng.y+i,0):Forward() + + local StartPos = Pos + Dir * Len + local EndPos = Pos + + local trace = util.TraceLine( { + start = StartPos, + endpos = EndPos, + filter = Ent, + ignoreworld = true, + whitelist = true, + } ) + + if not trace.Hit then continue end + + local fxPos = Ent:WorldToLocal( trace.HitPos + trace.HitNormal * 2 ) + if SwapSides then fxPos.y = -fxPos.y end + fxPos = Ent:LocalToWorld( fxPos ) + + local particle = emitter:Add( "effects/splash4", fxPos + Vector(0,0,math.Rand(-5,5) * mul) ) + + if not particle then continue end + + local pfxVel = Ent:WorldToLocal( EntPos + Dir * Speed * 0.5 + trace.HitNormal * Speed * 0.25 ) + if SwapSides then pfxVel.y = -pfxVel.y end + pfxVel = Ent:LocalToWorld( pfxVel ) - EntPos + + local pfxMul = math.Clamp( pfxVel.z / 250, 1, 2 ) + + particle:SetVelocity( pfxVel ) + particle:SetDieTime( (math.Rand(0.8,0.8) + math.Rand(0.2,0.4) * invmul) * pfxMul ) + particle:SetAirResistance( 60 ) + particle:SetStartAlpha( ((pfxMul / 2) ^ 2) * 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 5 + math.Rand(5,10) * mul ) + particle:SetEndSize( 15 + math.Rand(10,20) * mul * pfxMul ) + particle:SetRoll( math.Rand(-1,1) * math.Rand(50,150) ) + particle:SetRollDelta( math.Rand(-1,1) * pfxMul * mul * 0.5 ) + particle:SetColor(VecCol.r,VecCol.g,VecCol.b) + particle:SetGravity( Vector( 0, 0, -600 * math.Rand(1,1 + Steer * 3) ) - Vel * math.abs( i * 0.15 ) / 65 ) + particle:SetCollide( false ) + particle:SetNextThink( T ) + particle:SetThinkFunction( function( p ) + local fxpos = p:GetPos() + + p:SetNextThink( CurTime() ) + + if fxpos.z > Pos.z then return end + + p:SetDieTime( 0 ) + + if not IsValid( Ent ) or math.random(1,6) ~= 2 then return end + + local startpos = Vector(fxpos.x,fxpos.y,Pos.z + 1) + + local volume = math.min( math.abs( p:GetVelocity().z ) / 100, 1 ) + + if ShouldPlaySound and volume > 0.2 and p:GetStartSize() > 13 and math.random(1,10) == 1 then + local pitch = math.Rand(95,105) * math.Clamp( 1.5 - volume * 0.9,0.5,1) + + if pitch < 58 then + sound.Play( "vehicles/airboat/pontoon_splash"..math.random(1,2)..".wav", startpos, 75, math.Rand(95,105), volume * 0.1, 0 ) + else + if Speed < 600 then + sound.Play( "ambient/water/water_splash"..math.random(1,3)..".wav", startpos, 75, pitch, volume * 0.1, 0 ) + end + end + end + + if not ShouldPlaySound then return end + + local emitter3D = Ent:GetParticleEmitter3D( Ent:GetPos() ) + + if not IsValid( emitter3D ) then return end + + local particle = emitter3D:Add("effects/splashwake1", startpos ) + + if not particle then return end + + local scale = math.Rand(0.5,2) + local size = p:GetEndSize() + local vsize = Vector(size,size,size) + + particle:SetStartSize( size * scale * 0.5 ) + particle:SetEndSize( size * scale ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetPos( startpos ) + particle:SetAngles( Angle(-90,math.Rand(-180,180),0) ) + particle:SetColor(VecCol.r,VecCol.g,VecCol.b) + particle:SetNextThink( CurTime() ) + particle:SetThinkFunction( function( pfx ) + + local startpos = pfx:GetPos() + local endpos = startpos - Vector(0,0,100) + + local trace = util.TraceHull( { + start = startpos, + endpos = endpos, + filter = Ent, + whitelist = true, + mins = -vsize, + maxs = vsize, + } ) + + if trace.Hit then pfx:SetDieTime( 0 ) return end + + pfx:SetNextThink( CurTime() ) + end ) + end ) + end +end + + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheeldust.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheeldust.lua new file mode 100644 index 0000000..43de2a0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheeldust.lua @@ -0,0 +1,183 @@ + +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +EFFECT.DustMat = { + "effects/lvs_base/particle_debris_01", + "effects/lvs_base/particle_debris_02", +} + +local SmokeMat = EFFECT.SmokeMat +local function MakeDustParticle( emitter, emitter3D, pos, vel, r, g, b ) + local particle = emitter:Add( SmokeMat[ math.random(1,#SmokeMat) ], pos ) + + if not particle then return end + + particle:SetVelocity( vel ) + particle:SetDieTime( 0.4 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 30 ) + particle:SetRollDelta( math.Rand(-6,6) ) + particle:SetColor( r, g, b ) + particle:SetGravity( Vector(0,0,-600) ) + particle:SetCollide( false ) + particle:SetNextThink( CurTime() ) + particle:SetThinkFunction( function( p ) + if not IsValid( emitter3D ) then return end + + p:SetNextThink( CurTime() + 0.05 ) + + local pos = p:GetPos() + local vel = p:GetVelocity() + + local traceData = { + start = pos, + endpos = pos + Vector(0,0,vel.z) * 0.06, + mask = MASK_SOLID_BRUSHONLY, + } + + local trace = util.TraceLine( traceData ) + + if not trace.Hit then return end + + p:SetEndSize( 0 ) + p:SetDieTime( 0 ) + + local pHit = emitter3D:Add( SmokeMat[ math.random(1,#SmokeMat) ], trace.HitPos + trace.HitNormal ) + pHit:SetStartSize( 15 ) + pHit:SetEndSize( 15 ) + pHit:SetDieTime( math.Rand(5,6) ) + pHit:SetStartAlpha( 50 ) + pHit:SetEndAlpha( 0 ) + pHit:SetColor( p:GetColor() ) + + local Ang = trace.HitNormal:Angle() + Ang:RotateAroundAxis( trace.HitNormal, math.Rand(-180,180) ) + + pHit:SetAngles( Ang ) + end ) +end + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local ent = data:GetEntity() + + if not IsValid( ent ) then return end + + local dir = data:GetNormal() + local scale = data:GetMagnitude() + local speed = ent:GetVelocity():LengthSqr() + local tooSlow = speed < 30000 + local tooFast = speed > 400000 + local underwater = data:GetFlags() == 1 + + local start = ent:GetPos() + local emitter = ent:GetParticleEmitter( start ) + local emitter3D = ent:GetParticleEmitter3D( start ) + + local VecCol + local LightColor = render.GetLightColor( pos + dir ) + + if underwater then + VecCol = Vector(1,1.2,1.4) * (0.06 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 + else + VecCol = Vector(0.3,0.25,0.15) * (0.1 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 + end + VecCol.x = math.min( VecCol.x, 255 ) + VecCol.y = math.min( VecCol.y, 255 ) + VecCol.z = math.min( VecCol.z, 255 ) + + local DieTime = math.Rand(0.8,1.6) + + if not tooSlow then + for i = 1, 5 do + local particle = emitter:Add( self.DustMat[ math.random(1,#self.DustMat) ] , pos ) + + if not particle then continue end + + particle:SetVelocity( (dir * 50 * i + VectorRand() * 25) * scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 * scale ) + particle:SetEndSize( 20 * i * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector(0,0,-600) * scale ) + particle:SetCollide( false ) + end + end + + for i = 1, 5 do + local particle = emitter:Add( underwater and "effects/splash4" or self.SmokeMat[ math.random(1,#self.SmokeMat) ] , pos ) + + if not particle then continue end + + particle:SetVelocity( (dir * 50 * i + VectorRand() * 40) * scale ) + particle:SetDieTime( (i / 8) * DieTime ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( underwarter and 150 or 255 ) + particle:SetStartSize( 10 * scale ) + particle:SetEndSize( 20 * i * scale ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector(0,0,-600) * scale ) + particle:SetCollide( false ) + + if underwater or tooFast then continue end + + particle:SetNextThink( CurTime() ) + particle:SetThinkFunction( function( p ) + if not IsValid( ent ) or not IsValid( emitter ) or not IsValid( emitter3D ) then return end + + p:SetNextThink( CurTime() + 0.05 ) + + local pos = p:GetPos() + local vel = p:GetVelocity() + local dir = vel:GetNormalized() + local speed = vel:Length() + + local traceData = { + start = pos, + endpos = pos + dir * speed * 0.06, + whitelist = true, + filter = ent, + } + local trace = util.TraceLine( traceData ) + + if not trace.Hit then return end + + if tooSlow then + p:SetEndSize( 0 ) + p:SetDieTime( 0 ) + end + + MakeDustParticle( emitter, emitter3D, trace.HitPos - trace.HitNormal * 10, trace.HitNormal * 20 + VectorRand() * 40, p:GetColor() ) + end ) + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheelsmoke.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheelsmoke.lua new file mode 100644 index 0000000..3817919 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheelsmoke.lua @@ -0,0 +1,56 @@ + +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local ent = data:GetEntity() + + if not IsValid( ent ) then return end + + local dir = data:GetNormal() + + local emitter = ent:GetParticleEmitter( ent:GetPos() ) + + local VecCol = (render.GetLightColor( pos + dir ) * 0.5 + Vector(0.3,0.3,0.3)) * 255 + + for i = 1, 2 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 50 ) + particle:SetDieTime( math.Rand(0.5,1.5) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 60 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,5) ) + particle:SetCollide( false ) + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheelwatersplash.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheelwatersplash.lua new file mode 100644 index 0000000..9c32fbf --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_physics_wheelwatersplash.lua @@ -0,0 +1,80 @@ + +EFFECT.WaterWake = Material("effects/splashwake1") + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + Vector(0,0,1) + local Ent = data:GetEntity() + self.Size = data:GetMagnitude() + + if not IsValid( Ent ) then return end + + self.LifeTime = 1 + self.DieTime = CurTime() + self.LifeTime + + local RainFX = data:GetFlags() == 1 + + if RainFX then + self.Size = self.Size * 2 + + else + self.Splash = { + Pos = Pos, + Mat = self.WaterWake, + RandomAng = math.random(0,360), + } + end + + local LightColor = render.GetLightColor( Pos ) + self.VecCol = Vector(1,1.2,1.4) * (0.06 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 + self.VecCol.x = math.min( self.VecCol.x, 255 ) + self.VecCol.y = math.min( self.VecCol.y, 255 ) + self.VecCol.z = math.min( self.VecCol.z, 255 ) + + local emitter = Ent:GetParticleEmitter( Ent:GetPos() ) + local Vel = Ent:GetVelocity():Length() + + for i = 1, 3 do + if emitter and emitter.Add then + local particle = emitter:Add( "effects/splash4", Pos + VectorRand() * self.Size * 0.1 ) + if not particle then continue end + + particle:SetVelocity( Vector(0,0,math.Clamp(Vel / 100,100,250)) ) + particle:SetDieTime( 0.25 + math.min(Vel / 500,0.2) ) + particle:SetAirResistance( 60 ) + particle:SetStartAlpha( RainFX and 5 or 150 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( self.Size * 0.2 ) + particle:SetEndSize( self.Size ) + particle:SetRollDelta( math.Rand(-1,1) * 5 ) + particle:SetColor( math.min( self.VecCol.r, 255 ), math.min( self.VecCol.g, 255 ), math.min( self.VecCol.b, 255 ) ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( false ) + end + end +end + + +function EFFECT:Think() + if CurTime() > self.DieTime then + return false + end + return true +end + +function EFFECT:Render() + if not self.Splash or not self.LifeTime or not self.VecCol then return end + + local Scale = 1 - (self.DieTime - CurTime()) / self.LifeTime + + local Alpha = math.max( 100 - 150 * Scale ^ 2, 0 ) + + if Alpha <= 0 then return end + + local Size = (self.Size + self.Size * Scale) * 1.5 + + cam.Start3D2D( self.Splash.Pos, Angle(0,0,0), 1 ) + surface.SetMaterial( self.Splash.Mat ) + surface.SetDrawColor( math.min( self.VecCol.r, 255 ), math.min( self.VecCol.g, 255 ), math.min( self.VecCol.b, 255 ), Alpha ) + surface.DrawTexturedRectRotated( 0, 0, Size, Size, self.Splash.RandomAng ) + cam.End3D2D() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_proton_explosion.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_proton_explosion.lua new file mode 100644 index 0000000..1204873 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_proton_explosion.lua @@ -0,0 +1,119 @@ + +local GlowMat = Material( "sprites/light_glow02_add" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + + self.LifeTime = 0.4 + self.DieTime = CurTime() + self.LifeTime + + local emitter = ParticleEmitter( self.Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0,30 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 800 ) + particle:SetDieTime( math.Rand(4,6) ) + particle:SetAirResistance( math.Rand(200,600) ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( math.Rand(30,60) ) + particle:SetEndSize( math.Rand(100,150) ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 50,50,50 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + end + + for i = 0, 20 do + local particle = emitter:Add( "sprites/light_glow02_add", self.Pos ) + + local vel = VectorRand() * 400 + + if particle then + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.4,0.8) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(24,48) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( 0,127,255 ) + particle:SetGravity( Vector(0,0,-600) ) + + particle:SetAirResistance( 0 ) + + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + end + end + + for i = 0, 40 do + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Pos ) + + if particle then + particle:SetVelocity( VectorRand(-1,1) * 500 ) + particle:SetDieTime( 0.14 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( math.Rand(30,60) ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + emitter:Finish() + + local Pos = self.Pos + local ply = LocalPlayer():GetViewEntity() + if IsValid( ply ) then + local delay = (Pos - ply:GetPos()):Length() / 13503.9 + if delay <= 0.11 then + sound.Play( "ambient/explosions/explode_9.wav", Pos, 85, 100, 1 - delay * 8 ) + end + + timer.Simple( delay, function() + sound.Play( "LVS.MISSILE_EXPLOSION", Pos ) + end ) + else + sound.Play( "LVS.MISSILE_EXPLOSION", Pos ) + end +end + +function EFFECT:Think() + if self.DieTime < CurTime() then return false end + + return true +end + +function EFFECT:Render() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + render.SetMaterial( GlowMat ) + render.DrawSprite( self.Pos, 400 * Scale, 400 * Scale, Color( 0, 127, 255, 255) ) + render.DrawSprite( self.Pos, 100 * Scale, 100 * Scale, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_proton_trail.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_proton_trail.lua new file mode 100644 index 0000000..0bbca52 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_proton_trail.lua @@ -0,0 +1,92 @@ +EFFECT.Offset = Vector(-8,0,0) + +local GlowMat = Material( "effects/select_ring" ) + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.OldPos = self.Entity:LocalToWorld( self.Offset ) + + self.Emitter = ParticleEmitter( self.Entity:LocalToWorld( self.OldPos ), false ) + end +end + +function EFFECT:doFX( pos ) + if not IsValid( self.Entity ) then return end + + if IsValid( self.Emitter ) then + local emitter = self.Emitter + + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), pos ) + if particle then + particle:SetVelocity( -self.Entity:GetForward() * math.Rand(250,800) + self.Entity:GetVelocity()) + particle:SetDieTime( 1 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 8 ) + particle:SetEndSize( 1 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 0,0,255 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( false ) + end + + local particle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), self.Entity:GetPos() ) + if particle then + particle:SetVelocity( -self.Entity:GetForward() * 200 + VectorRand() * 50 ) + particle:SetDieTime( 1 ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 6 ) + particle:SetEndSize( 2 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 0,0,255 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( false ) + end + end +end + +function EFFECT:Think() + if IsValid( self.Entity ) then + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.02 + + local oldpos = self.OldPos + local newpos = self.Entity:LocalToWorld( self.Offset ) + self:SetPos( newpos ) + + local Sub = (newpos - oldpos) + local Dir = Sub:GetNormalized() + local Len = Sub:Length() + + self.OldPos = newpos + + for i = 0, Len, 45 do + local pos = oldpos + Dir * i + + self:doFX( pos ) + end + end + + return true + end + + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end + + return false +end + +function EFFECT:Render() + local ent = self.Entity + local pos = ent:LocalToWorld( self.Offset ) + + render.SetMaterial( GlowMat ) + + render.DrawSprite( pos, 100, 100, Color( 0, 127, 255, 50 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_muzzle.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_muzzle.lua new file mode 100644 index 0000000..835228e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_muzzle.lua @@ -0,0 +1,40 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local Ent = data:GetEntity() + local Vel = Dir * 10 + + if IsValid( Ent ) then + Vel = Ent:GetVelocity() + end + + local emitter = ParticleEmitter( Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0, 12 do + local particle = emitter:Add( "effects/gunshipmuzzle", Pos + Dir * i * 0.7 * math.random(1,2) * 0.5 ) + local Size = 1 + + if not particle then continue end + + particle:SetVelocity( Dir * 800 + Vel ) + particle:SetDieTime( 0.05 ) + particle:SetStartAlpha( 255 * Size ) + particle:SetStartSize( math.max( math.random(10,24) - i * 0.5,0.1 ) * Size ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 255, 255, 255 ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_tracer.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_tracer.lua new file mode 100644 index 0000000..2374fe3 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_tracer.lua @@ -0,0 +1,29 @@ + +EFFECT.MatBeam = Material( "effects/gunshiptracer" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 500 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos + dir * len, endpos - dir * len, 12, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_tracer_large.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_tracer_large.lua new file mode 100644 index 0000000..fc738d4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_pulserifle_tracer_large.lua @@ -0,0 +1,29 @@ + +EFFECT.MatBeam = Material( "effects/gunshiptracer" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 700 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos + dir * len, endpos - dir * len, 24, 1, 0, Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_rotor_destruction.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_rotor_destruction.lua new file mode 100644 index 0000000..5ed4a16 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_rotor_destruction.lua @@ -0,0 +1,141 @@ + +EFFECT.BeamMaterial = Material( "particle/smokesprites_0003" ) +EFFECT.SmokeMaterials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Pos = data:GetOrigin() + self.Radius = data:GetMagnitude() + self.SpawnTime = CurTime() + + self:SetAngles( data:GetNormal():Angle() ) + + self:Debris() + self:Explosion() +end + + +function EFFECT:Debris() + local emitter = ParticleEmitter( self.Pos, false ) + + if not IsValid( emitter ) then return end + + for i = 0,30 do + local particle = emitter:Add( "effects/fleck_tile"..math.random(1,2), self.Pos ) + + local vel = (self:GetRight() * math.Rand(-1,1) + self:GetForward() * math.Rand(-1,1) + self:GetUp() * math.Rand(-0.25,0.25)):GetNormalized() * self.Radius * 5 + + if particle then + particle:SetVelocity( vel ) + particle:SetDieTime( 0.6 ) + particle:SetAirResistance( 25 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetRollDelta( math.Rand(-1,1) * 10 ) + particle:SetColor( 0,0,0 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0.3 ) + end + end + + emitter:Finish() +end + + +function EFFECT:Explosion() + local emitter = ParticleEmitter( self.Pos, false ) + + if not IsValid( emitter ) then return end + + local scale = 0.1 + self.Radius / 1000 + + for i = 0,10 do + local particle = emitter:Add( self.SmokeMaterials[ math.random(1, #self.SmokeMaterials ) ], self.Pos ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 1500 * scale ) + particle:SetDieTime( math.Rand(0.75,1.5) * scale ) + particle:SetAirResistance( math.Rand(200,600) ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(60,120) * scale ) + particle:SetEndSize( math.Rand(220,320) * scale ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 40,40,40 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + if (self.SpawnTime + 0.15) < CurTime() then return false end + + return true +end + +function EFFECT:Render() + if not self.SpawnTime then return end + + local pos = self:GetPos() + + render.SetMaterial( self.BeamMaterial ) + + local segmentdist = 360 / 30 + local overlap = 10 + local Mul = math.Clamp(self.SpawnTime + 0.15 - CurTime(),0,0.15) / 0.15 + + do + local Width = self.Radius / 2 + local Alpha = Mul * 255 + local radius = self.Radius * 0.5 * ((1 - Mul) ^ 5) + local AngOffset = Mul * 360 + + if Alpha > 0 then + for a = segmentdist, 360, segmentdist do + local Ang = a + AngOffset + local StartPos = self:LocalToWorld( Vector( math.cos( math.rad( -Ang - overlap ) ) * radius, -math.sin( math.rad( -Ang - overlap ) ) * radius, 0 ) ) + local EndPos = self:LocalToWorld( Vector( math.cos( math.rad( -Ang + overlap + segmentdist ) ) * radius, -math.sin( math.rad( -Ang + overlap + segmentdist ) ) * radius, 0 ) ) + + render.DrawBeam( StartPos, EndPos, Width, 0, 1, Color( 255, 255, 255, Alpha ) ) + end + end + end + + do + local Width = self.Radius / 2 + local Alpha = Mul * 255 + local radius = self.Radius * ((1 - Mul) ^ 5) + local AngOffset = Mul * 360 + + if Alpha > 0 then + for a = segmentdist, 360, segmentdist do + local Ang = a + AngOffset + local StartPos = self:LocalToWorld( Vector( math.cos( math.rad( -Ang - overlap ) ) * radius, -math.sin( math.rad( -Ang - overlap ) ) * radius, 0 ) ) + local EndPos = self:LocalToWorld( Vector( math.cos( math.rad( -Ang + overlap + segmentdist ) ) * radius, -math.sin( math.rad( -Ang + overlap + segmentdist ) ) * radius, 0 ) ) + + render.DrawBeam( StartPos, EndPos, Width, 0, 1, Color( 255, 255, 255, Alpha ) ) + end + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_shield_impact.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_shield_impact.lua new file mode 100644 index 0000000..e9642a4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_shield_impact.lua @@ -0,0 +1,85 @@ + +local LastImpact = 0 + +function EFFECT:Init( data ) + self.Ent = data:GetEntity() + self.Pos = data:GetOrigin() + + self.mat = Material( "sprites/light_glow02_add" ) + + local T = CurTime() + + self.LifeTime = 0.2 + self.DieTime = T + self.LifeTime + + local DontHurtEars = math.Clamp( T - LastImpact, 0.4, 1 ) ^ 2 + + LastImpact = T + + sound.Play( "lvs/shield_deflect.ogg", self.Pos, 120, 100, DontHurtEars ) + + self:Spark( self.Pos ) + + if IsValid( self.Ent ) then + self.Model = ClientsideModel( self.Ent:GetModel(), RENDERMODE_TRANSCOLOR ) + self.Model:SetMaterial("models/alyx/emptool_glow") + self.Model:SetColor( Color(200,220,255,255) ) + self.Model:SetParent( self.Ent, 0 ) + self.Model:SetMoveType( MOVETYPE_NONE ) + self.Model:SetLocalPos( Vector( 0, 0, 0 ) ) + self.Model:SetLocalAngles( Angle( 0, 0, 0 ) ) + self.Model:AddEffects( EF_BONEMERGE ) + end +end + +function EFFECT:Spark( pos ) + local emitter = ParticleEmitter( pos, false ) + + for i = 0, 20 do + local particle = emitter:Add( "sprites/rico1", pos ) + + local vel = VectorRand() * 500 + + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetAngles( vel:Angle() + Angle(0,90,0) ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetStartAlpha( math.Rand( 200, 255 ) ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(10,20) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(-100,100) ) + particle:SetRollDelta( math.Rand(-100,100) ) + particle:SetColor( 0, 127, 255 ) + + particle:SetAirResistance( 0 ) + end + + emitter:Finish() +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then + if IsValid( self.Model ) then + self.Model:Remove() + end + end + + if self.DieTime < CurTime() then + if IsValid( self.Model ) then + self.Model:Remove() + end + + return false + end + + return true +end + +function EFFECT:Render() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + render.SetMaterial( self.mat ) + render.DrawSprite( self.Pos, 800 * Scale, 800 * Scale, Color( 0, 127, 255, 255) ) + render.DrawSprite( self.Pos, 200 * Scale, 200 * Scale, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tire_blow.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tire_blow.lua new file mode 100644 index 0000000..927638e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tire_blow.lua @@ -0,0 +1,90 @@ + +EFFECT.SmokeMat = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + local Offset = Ent:GetPos() + local Low, High = Ent:WorldSpaceAABB() + local Vel = Ent:GetVelocity() + + local Radius = Ent:BoundingRadius() + + local NumParticles = Radius + NumParticles = NumParticles * 4 + + NumParticles = math.Clamp( NumParticles, 32, 256 ) + + local emitter = ParticleEmitter( Offset ) + + for i = 0, NumParticles do + local Pos = Vector( math.Rand( Low.x, High.x ), math.Rand( Low.y, High.y ), math.Rand( Low.z, High.z ) ) + local particle = emitter:Add( "effects/fleck_tile"..math.random(1,2), Pos ) + + if not particle then continue end + + particle:SetVelocity( ( Pos - Offset ) * 5 + Vel * 0.5 ) + particle:SetLifeTime( 0 ) + particle:SetDieTime( math.Rand( 0.5, 1 ) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(3,6) * Radius * 0.025 ) + particle:SetEndSize( 1 ) + particle:SetRoll( math.Rand( 0, 360 ) ) + particle:SetRollDelta( math.Rand(-10,10) ) + + particle:SetAirResistance( 25 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + particle:SetColor( 50, 50, 50 ) + particle:SetBounce( 0.3 ) + particle:SetLighting( true ) + end + + for i = 1, 2 do + local particle = emitter:Add( self.SmokeMat[ math.random(1,#self.SmokeMat) ] , Offset ) + + if not particle then continue end + + particle:SetVelocity( VectorRand() * 100 * Radius + Vel * 0.5 ) + particle:SetDieTime( math.Rand(0.2,0.6) ) + particle:SetAirResistance( 200 * Radius ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 0.5 * Radius ) + particle:SetEndSize( 2 * Radius ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( 255, 255, 255 ) + particle:SetGravity( Vector(0,0,600) ) + particle:SetCollide( false ) + particle:SetLighting( true ) + end + + emitter:Finish() + +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_autocannon.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_autocannon.lua new file mode 100644 index 0000000..282cf00 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_autocannon.lua @@ -0,0 +1,202 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +EFFECT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) + + self.emitter = ParticleEmitter( pos, false ) + + self.OldPos = pos + self.Dir = dir + + if not self.emitter then return end + + local VecCol = (render.GetLightColor( pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + for i = 0, 2 do + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], pos ) + + if not particle then continue end + + particle:SetVelocity( dir * 700 + VectorRand() * 200 ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( 250 ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 80 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,100) ) + particle:SetCollide( false ) + end + + for i = 0, math.random(1,12) do + local particle = self.emitter:Add( "sprites/rico1", pos ) + + if not particle then continue end + + particle:SetVelocity( dir * 2000 + VectorRand() * 50 ) + particle:SetDieTime( math.Rand(0.1,0.2) ) + particle:SetStartAlpha( 0 ) + particle:SetEndAlpha( 5 ) + particle:SetStartSize( math.Rand(0,0.5) ) + particle:SetEndSize( math.Rand(0,0.5) ) + particle:SetRollDelta( 100 ) + particle:SetAirResistance( 0 ) + particle:SetColor( 255, 200, 120 ) + end + + local trace = util.TraceLine( { + start = pos, + endpos = pos - Vector(0,0,500), + mask = MASK_SOLID_BRUSHONLY, + } ) + + if not trace or not trace.Hit then return end + + local VecCol = (render.GetLightColor( trace.HitPos + trace.HitNormal ) * 0.8 + Vector(0.17,0.15,0.1)) * 255 + for i = 1, 12 do + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], trace.HitPos ) + + if not particle then continue end + + local ang = i * 30 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + particle:SetVelocity( Vector(X,Y,0) * 1000 ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( 500 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 25 ) + particle:SetEndSize( 80 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,150) + self.Dir * 1000 ) + particle:SetCollide( false ) + end + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Intensity = 2 + local Ratio = math.min( 250 / (ViewEnt:GetPos() - trace.HitPos):Length(), 1 ) + + if Ratio < 0 then return end + + util.ScreenShake( trace.HitPos, Intensity * Ratio, 0.1, 0.5, 250 ) +end + +function EFFECT:Think() + local bullet = LVS:GetBullet( self.ID ) + + if not bullet then + if self.emitter then + self.emitter:Finish() + end + + local StartPos = self.OldPos + local EndPos = StartPos + self.Dir * 1000 + + local trace = util.TraceLine( { + start = StartPos, + endpos = EndPos, + } ) + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetStart( self.Dir ) + effectdata:SetEntity( trace.Entity ) + effectdata:SetNormal( trace.HitNormal ) + effectdata:SetSurfaceProp( trace.SurfaceProps ) + effectdata:SetMagnitude( 0.5 ) + util.Effect( "lvs_bullet_impact_ap", effectdata ) + + return false + end + + if not self.emitter then return true end + + local Pos = bullet:GetPos() + + self.Dir = bullet:GetDir() + + local Sub = self.OldPos - Pos + local Dist = Sub:Length() + local Dir = Sub:GetNormalized() + + local Vel = bullet.Velocity / 10 + + for i = 0, Dist, 100 do + local cur_pos = self.OldPos + Dir * i + + local VecCol = (render.GetLightColor( cur_pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], cur_pos ) + + if not particle then continue end + + particle:SetVelocity( -Dir * Vel + VectorRand() * 10 ) + particle:SetDieTime( math.Rand(0.05,1) ) + particle:SetAirResistance( 250 ) + particle:SetStartAlpha( 100 ) + particle:SetEndAlpha( 0 ) + + particle:SetStartSize( 0 ) + particle:SetEndSize( 15 ) + + particle:SetRollDelta( 1 ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetCollide( false ) + end + + self.OldPos = Pos + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 2000 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + + render.DrawBeam( endpos - dir * len, endpos + dir * len * 0.1, 10, 1, 0, Color( 100, 100, 100, 100 ) ) + render.DrawBeam( endpos - dir * len * 0.5, endpos + dir * len * 0.1, 5, 1, 0, Color( 255, 255, 255, 255 ) ) + + render.SetMaterial( self.MatSprite ) + render.DrawSprite( endpos, 400, 400, Color( 100, 100, 100, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_cannon.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_cannon.lua new file mode 100644 index 0000000..6a28dec --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_cannon.lua @@ -0,0 +1,187 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +EFFECT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) + + self.emitter = ParticleEmitter( pos, false ) + + self.OldPos = pos + self.Dir = dir + + if not self.emitter then return end + + local VecCol = (render.GetLightColor( pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + for i = 0,10 do + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], pos ) + + if not particle then continue end + + particle:SetVelocity( dir * 700 + VectorRand() * 200 ) + particle:SetDieTime( math.Rand(2,3) ) + particle:SetAirResistance( 250 ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 120 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,100) ) + particle:SetCollide( false ) + end + + local trace = util.TraceLine( { + start = pos, + endpos = pos - Vector(0,0,500), + mask = MASK_SOLID_BRUSHONLY, + } ) + + if not trace or not trace.Hit then return end + + local VecCol = (render.GetLightColor( trace.HitPos + trace.HitNormal ) * 0.8 + Vector(0.17,0.15,0.1)) * 255 + for i = 1,24 do + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], trace.HitPos ) + + if not particle then continue end + + local ang = i * 15 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + particle:SetVelocity( Vector(X,Y,0) * 2000 ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( 500 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 25 ) + particle:SetEndSize( 120 ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetGravity( Vector(0,0,150) + self.Dir * 2000 ) + particle:SetCollide( false ) + end + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Intensity = 8 + local Ratio = math.min( 250 / (ViewEnt:GetPos() - trace.HitPos):Length(), 1 ) + + if Ratio < 0 then return end + + util.ScreenShake( trace.HitPos, Intensity * Ratio, 0.1, 0.5, 250 ) +end + +function EFFECT:Think() + local bullet = LVS:GetBullet( self.ID ) + + if not bullet then + if self.emitter then + self.emitter:Finish() + end + + local StartPos = self.OldPos + local EndPos = StartPos + self.Dir * 1000 + + local trace = util.TraceLine( { + start = StartPos, + endpos = EndPos, + } ) + + if not trace.Hit then return false end + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetStart( self.Dir ) + effectdata:SetEntity( trace.Entity ) + effectdata:SetNormal( trace.HitNormal ) + effectdata:SetSurfaceProp( trace.SurfaceProps ) + effectdata:SetMagnitude( 1 ) + util.Effect( "lvs_bullet_impact_ap", effectdata ) + + return false + end + + if not self.emitter then return true end + + local Pos = bullet:GetPos() + + self.Dir = bullet:GetDir() + + local Sub = self.OldPos - Pos + local Dist = Sub:Length() + local Dir = Sub:GetNormalized() + + local Vel = bullet.Velocity / 10 + + for i = 0, Dist, 25 do + local cur_pos = self.OldPos + Dir * i + + local VecCol = (render.GetLightColor( cur_pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], cur_pos ) + + if not particle then continue end + particle:SetVelocity( -Dir * Vel + VectorRand() * 10 ) + particle:SetDieTime( math.Rand(0.05,1) ) + particle:SetAirResistance( 250 ) + particle:SetStartAlpha( 100 ) + particle:SetEndAlpha( 0 ) + + particle:SetStartSize( 0 ) + particle:SetEndSize( 30 ) + + particle:SetRollDelta( 1 ) + particle:SetColor( math.min( VecCol.r, 255 ), math.min( VecCol.g, 255 ), math.min( VecCol.b, 255 ) ) + particle:SetCollide( false ) + end + + self.OldPos = Pos + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 3000 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + + render.DrawBeam( endpos - dir * len, endpos + dir * len * 0.1, 32, 1, 0, Color( 100, 100, 100, 100 ) ) + render.DrawBeam( endpos - dir * len * 0.5, endpos + dir * len * 0.1, 16, 1, 0, Color( 255, 255, 255, 255 ) ) + + render.SetMaterial( self.MatSprite ) + render.DrawSprite( endpos, 250, 250, Color( 100, 100, 100, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_green.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_green.lua new file mode 100644 index 0000000..51bc08e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_green.lua @@ -0,0 +1,30 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 1600 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 3, 1, 0, Color( 225, 255, 225, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 10, 1, 0, Color( 150, 200, 150, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_missile.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_missile.lua new file mode 100644 index 0000000..b6d1ad2 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_missile.lua @@ -0,0 +1,128 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +EFFECT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) + + self.emitter = ParticleEmitter( pos, false ) + + self.OldPos = pos + self.Dir = dir +end + +function EFFECT:doFX( pos, curpos ) + if not IsValid( self.emitter ) then return end + + local particle = self.emitter:Add( self.MatSmoke[ math.random(1, #self.MatSmoke ) ], pos ) + if particle then + particle:SetGravity( Vector(0,0,100) + VectorRand() * 50 ) + particle:SetVelocity( -self.Dir * 200 ) + particle:SetAirResistance( 600 ) + particle:SetDieTime( math.Rand(1.5,2) ) + particle:SetStartAlpha( 50 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 60 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetRollDelta( math.Rand( -1, 1 ) ) + particle:SetColor(40,40,40) + particle:SetCollide( false ) + end + + local particle = self.emitter:Add( "particles/flamelet"..math.random(1,5), pos ) + if particle then + particle:SetVelocity( -self.Dir * math.Rand(250,800) + self.Dir * 1500 ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 8 ) + particle:SetEndSize( 1 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 0 ) ) + particle:SetCollide( false ) + end + + local particle = self.emitter:Add( "particles/flamelet"..math.random(1,5), curpos ) + if particle then + particle:SetVelocity( -self.Dir * 200 + VectorRand() * 50 ) + particle:SetDieTime( 0.25 ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 6 ) + particle:SetEndSize( 2 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 0 ) ) + particle:SetCollide( false ) + end +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then + if self.emitter then + self.emitter:Finish() + end + + return false + end + + if not self.emitter then return true end + + local T = CurTime() + + if (self.nextDFX or 0) <= T then + self.nextDFX = T + 0.02 + + local bullet = LVS:GetBullet( self.ID ) + + local Pos = bullet:GetPos() + + local Sub = self.OldPos - Pos + local Dist = Sub:Length() + local Dir = Sub:GetNormalized() + + for i = 0, Dist, 45 do + local cur_pos = self.OldPos + Dir * i + + self:doFX( cur_pos, Pos ) + end + + self.OldPos = Pos + end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local pos = bullet:GetPos() + + render.SetMaterial( self.MatSprite ) + render.DrawSprite( pos, 100, 100, Color( 255, 200, 150, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_orange.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_orange.lua new file mode 100644 index 0000000..7413751 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_orange.lua @@ -0,0 +1,30 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 1600 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 3, 1, 0, Color( 255, 255, 0, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 10, 1, 0, Color( 125, 50, 0, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_proton.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_proton.lua new file mode 100644 index 0000000..09398da --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_proton.lua @@ -0,0 +1,104 @@ + +EFFECT.GlowColor = Color( 0, 127, 255, 255 ) +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) + +EFFECT.MatSprite = Material( "effects/select_ring" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) + + self.emitter = ParticleEmitter( pos, false ) + + self.OldPos = pos + self.Dir = dir +end + +function EFFECT:doFX( pos, curpos ) + if not IsValid( self.emitter ) then return end + + local particle = self.emitter:Add( "particles/flamelet"..math.random(1,5), pos ) + if particle then + particle:SetVelocity( -self.Dir * math.Rand(250,800) + self.Dir * 5000 ) + particle:SetDieTime( math.Rand(0.2,0.4) ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 8 ) + particle:SetEndSize( 1 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 0,0,255 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( false ) + end + + local particle = self.emitter:Add( "particles/flamelet"..math.random(1,5), curpos ) + if particle then + particle:SetVelocity( self.Dir * 5000 + VectorRand() * 50 ) + particle:SetDieTime( 1 ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 6 ) + particle:SetEndSize( 2 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 0,0,255 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( false ) + end +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then + if self.emitter then + self.emitter:Finish() + end + + return false + end + + if not self.emitter then return true end + + local T = CurTime() + + if (self.nextDFX or 0) <= T then + self.nextDFX = T + 0.02 + + local bullet = LVS:GetBullet( self.ID ) + + local Pos = bullet:GetPos() + + local Sub = self.OldPos - Pos + local Dist = Sub:Length() + local Dir = Sub:GetNormalized() + + for i = 0, Dist, 45 do + local cur_pos = self.OldPos + Dir * i + + self:doFX( cur_pos, Pos ) + end + + self.OldPos = Pos + end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local pos = bullet:GetPos() + + render.SetMaterial( self.MatSprite ) + render.DrawSprite( pos, 100, 100, Color( 0, 127, 255, 50 ) ) + + render.SetMaterial( self.GlowMat ) + + for i = 0, 30 do + local Size = ((30 - i) / 30) ^ 2 * 128 + + render.DrawSprite( pos - self.Dir * i * 7, Size, Size, self.GlowColor ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_white.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_white.lua new file mode 100644 index 0000000..f0e6510 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_white.lua @@ -0,0 +1,30 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 1600 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 3, 1, 0, Color( 255, 255, 255, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 10, 1, 0, Color( 150, 150, 150, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_yellow.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_yellow.lua new file mode 100644 index 0000000..901deb4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_yellow.lua @@ -0,0 +1,30 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 1600 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 3, 1, 0, Color( 255, 255, 125, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 10, 1, 0, Color( 125, 80, 0, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_yellow_small.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_yellow_small.lua new file mode 100644 index 0000000..1e23ad8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_tracer_yellow_small.lua @@ -0,0 +1,30 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 500 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 4, 1, 0, Color( 255, 255, 125, 255 ) ) + render.DrawBeam( endpos - dir * len, endpos + dir * len, 8, 1, 0, Color( 125, 80, 0, 255 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_trailer_explosion.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_trailer_explosion.lua new file mode 100644 index 0000000..1313a42 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_trailer_explosion.lua @@ -0,0 +1,81 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + util.Effect( "cball_explode", effectdata, true, true ) + + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetNormal( Vector(0,0,1) ) + util.Effect( "manhacksparks", effectdata, true, true ) + + local emitter = ParticleEmitter( pos, false ) + + for i = 0,30 do + local particle = emitter:Add( "effects/fleck_tile"..math.random(1,2), pos ) + local vel = VectorRand() * math.Rand(150,200) + vel.z = math.Rand(200,250) + if not particle then continue end + + particle:SetVelocity( vel ) + particle:SetDieTime( math.Rand(2,6) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 2 ) + particle:SetEndSize( 2 ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetColor( 0,0,0 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0.3 ) + end + + for i = 0,4 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], pos ) + + local vel = VectorRand() * 200 + + if particle then + particle:SetVelocity( vel ) + particle:SetDieTime( math.Rand(2.5,5) ) + particle:SetAirResistance( 100 ) + particle:SetStartAlpha( 200 ) + particle:SetStartSize( 50 ) + particle:SetEndSize( 200 ) + particle:SetRoll( math.Rand(-5,5) ) + particle:SetColor( 10,10,10 ) + particle:SetGravity( Vector(0,0,20) ) + particle:SetCollide( false ) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_truck_exhaust.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_truck_exhaust.lua new file mode 100644 index 0000000..3118731 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_truck_exhaust.lua @@ -0,0 +1,84 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local Ent = data:GetEntity() + + if not IsValid( Ent ) or not isfunction( Ent.GetThrottle ) then return end + + local Engine = Ent:GetEngine() + + if not IsValid( Engine ) or not isfunction( Engine.GetRPM ) then return end + + local Vel = Ent:GetVelocity() + + local emitter = Ent:GetParticleEmitter( Pos ) + + if not IsValid( emitter ) then return end + + local throttle = math.min( Ent:GetThrottle() / 0.5, 1 ) + local clutch = Ent:GetQuickVar( "clutch" ) + local hasClutch = Ent:HasQuickVar( "clutch" ) + local ClutchActive = Engine:GetClutch() + if not clutch then + clutch = ClutchActive and 1 or 0 + end + if ClutchActive then + throttle = math.max( throttle - clutch, 0 ) + end + + local Scale = math.min( (math.max( Engine:GetRPM() - Ent.EngineIdleRPM, 0 ) / (Ent.EngineMaxRPM - Ent.EngineIdleRPM)) * (throttle ^ 2), 1 ) + + local temp = 0 + if Ent:HasQuickVar("temp") then + temp = math.Clamp( 1 - Ent:GetQuickVar( "temp" ) / 0.5, 0, 1 ) + end + + local Col = 50 + 205 * temp + + Col = Col - (Scale * Col) + + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + + if not particle then return end + + local invert = (1 - Dir.z) / 2 + + particle:SetVelocity( Vel + Dir * (100 + math.min( 800 * Scale, 300)) + VectorRand() * 50 * Scale + (Ent:GetVelocity() * 0.5 + Vector(0,0,Scale * 100)) * invert ) + particle:SetDieTime( math.max((1.4 - temp) - Ent:GetVelocity():LengthSqr() * 0.0001,0.2) + Scale * math.Rand(0.8,1.2) ) + particle:SetAirResistance( 400 ) + particle:SetStartAlpha( 100 - 50 * temp + Scale * 150 ) + particle:SetStartSize( 2 + (throttle + Scale) * 4 ) + particle:SetEndSize( 10 + 35 * (throttle + Scale) ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetRollDelta( math.Rand( -1, 1 ) * (2 - Scale * 1.75) ) + particle:SetColor( Col, Col, Col ) + particle:SetGravity( Vector( 0, 0, 10 + Scale * 300 ) + Ent:GetVelocity() * 4 ) + particle:SetCollide( true ) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_update.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_update.lua new file mode 100644 index 0000000..19fec66 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_update.lua @@ -0,0 +1,43 @@ + +function EFFECT:Init( data ) + self.Ent = data:GetEntity() + self.Pos = data:GetOrigin() + + local T = CurTime() + + self.LifeTime = 1 + self.DieTime = T + self.LifeTime + + if IsValid( self.Ent ) then + self.Model = ClientsideModel( self.Ent:GetModel(), RENDERMODE_TRANSCOLOR ) + self.Model:SetMaterial("models/alyx/emptool_glow") + self.Model:SetColor( Color(0,127,255,255) ) + self.Model:SetParent( self.Ent, 0 ) + self.Model:SetMoveType( MOVETYPE_NONE ) + self.Model:SetLocalPos( Vector( 0, 0, 0 ) ) + self.Model:SetLocalAngles( Angle( 0, 0, 0 ) ) + self.Model:AddEffects( EF_BONEMERGE ) + self.Model:SetModelScale( self.Ent:GetModelScale() ) + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then + if IsValid( self.Model ) then + self.Model:Remove() + end + end + + if self.DieTime < CurTime() then + if IsValid( self.Model ) then + self.Model:Remove() + end + + return false + end + + return true +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_upgrade.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_upgrade.lua new file mode 100644 index 0000000..cb296d5 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_upgrade.lua @@ -0,0 +1,43 @@ + +function EFFECT:Init( data ) + self.Ent = data:GetEntity() + self.Pos = data:GetOrigin() + + local T = CurTime() + + self.LifeTime = 1 + self.DieTime = T + self.LifeTime + + if IsValid( self.Ent ) then + self.Model = ClientsideModel( self.Ent:GetModel(), RENDERMODE_TRANSCOLOR ) + self.Model:SetMaterial("models/alyx/emptool_glow") + self.Model:SetColor( Color(255,200,50,255) ) + self.Model:SetParent( self.Ent, 0 ) + self.Model:SetMoveType( MOVETYPE_NONE ) + self.Model:SetLocalPos( Vector( 0, 0, 0 ) ) + self.Model:SetLocalAngles( Angle( 0, 0, 0 ) ) + self.Model:AddEffects( EF_BONEMERGE ) + self.Model:SetModelScale( self.Ent:GetModelScale() ) + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then + if IsValid( self.Model ) then + self.Model:Remove() + end + end + + if self.DieTime < CurTime() then + if IsValid( self.Model ) then + self.Model:Remove() + end + + return false + end + + return true +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_walker_stomp.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_walker_stomp.lua new file mode 100644 index 0000000..9450ff1 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_walker_stomp.lua @@ -0,0 +1,54 @@ +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + + local emitter = ParticleEmitter( pos, false ) + + for i = 1,12 do + local particle = emitter:Add( Materials[ math.random(1, #Materials ) ],pos ) + + if not particle then continue end + + local ang = i * 30 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + particle:SetVelocity( Vector(X,Y,0) * math.Rand(3000,4000) ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( math.Rand(3000,5000) ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( math.Rand(30,40) ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 60,60,60 ) + particle:SetGravity( VectorRand() * 200 + Vector(0,0,1000) ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_warpcannon_charge.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_warpcannon_charge.lua new file mode 100644 index 0000000..468e8bf --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_warpcannon_charge.lua @@ -0,0 +1,63 @@ + +local MuzzleFX = Material("sprites/strider_blackball") +local BeamFX = Material("sprites/bluelaser1") +local WarpFX = Material("particle/warp5_warp") + +function EFFECT:Init( data ) + self.Ent = data:GetEntity() + + if not IsValid( self.Ent ) then return end + + self.LifeTime = 2 + self.DieTime = CurTime() + self.LifeTime + + self.Ent:EmitSound( "NPC_Strider.Charge" ) + + self:SetRenderBoundsWS( self.Ent:GetPos(), self.Ent:GetPos() - self.Ent:GetUp() * 50000 ) +end + +function EFFECT:Think() + if (self.DieTime or 0) < CurTime() then return false end + + return true +end + +function EFFECT:Render() + if not IsValid( self.Ent ) then return end + + local Muzzle = self.Ent:GetAttachment( self.Ent:LookupAttachment( "bellygun" ) ) + + if not Muzzle then return end + + local T = CurTime() + local Delta = ((T + self.LifeTime) - self.DieTime) / self.LifeTime + + local trace = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + self.Ent:GetAimVector() * 50000, + mask = MASK_SOLID_BRUSHONLY + } ) + + self:SetRenderBoundsWS( Muzzle.Pos, trace.HitPos ) + + render.SetMaterial( MuzzleFX ) + render.DrawSprite( Muzzle.Pos, Delta * 32, Delta * 32, Color( 255, 255, 255, Delta * 255) ) + + render.SetMaterial( BeamFX ) + render.DrawBeam( Muzzle.Pos, trace.HitPos, (Delta ^ 2) * 64, 0, 1, Color( 255, 255, 255 ) ) + + render.SetMaterial( WarpFX ) + render.DrawSprite( Muzzle.Pos, (Delta ^ 2) * 256, (Delta ^ 2) * 256, Color( 255, 255, 255, 100) ) + + local dlight = DynamicLight( self:EntIndex() * 1337 ) + if dlight then + dlight.pos = Muzzle.Pos + dlight.r = 0 + dlight.g = 255 + dlight.b = 255 + dlight.brightness = Delta * 10 + dlight.Decay = 100 + dlight.Size = 128 + dlight.DieTime = CurTime() + 0.2 + end +end diff --git a/garrysmod/addons/lvs_base/lua/effects/lvs_warpcannon_fire.lua b/garrysmod/addons/lvs_base/lua/effects/lvs_warpcannon_fire.lua new file mode 100644 index 0000000..765d22e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/effects/lvs_warpcannon_fire.lua @@ -0,0 +1,76 @@ + +local MuzzleFX = Material("sprites/strider_blackball") +local BeamFX = Material("sprites/bluelaser1") +local WarpFX = Material("particle/warp5_warp") + +function EFFECT:Init( data ) + self.Ent = data:GetEntity() + + if not IsValid( self.Ent ) then return end + + self.LifeTime = 0.2 + self.DieTime = CurTime() + self.LifeTime + + self.Ent:EmitSound( "NPC_Strider.Shoot" ) + + self:SetRenderBoundsWS( self.Ent:GetPos(), self.Ent:GetPos() - self.Ent:GetUp() * 50000 ) +end + +function EFFECT:Think() + if (self.DieTime or 0) < CurTime() then + self:DoImpactEffect() + return false + end + + return true +end + +function EFFECT:DoImpactEffect() + if not IsValid( self.Ent ) then return end + + local Muzzle = self.Ent:GetAttachment( self.Ent:LookupAttachment( "bellygun" ) ) + + if not Muzzle then return end + + local trace = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + self.Ent:GetAimVector() * 50000, + mask = MASK_SOLID_BRUSHONLY + } ) + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos + trace.HitNormal ) + effectdata:SetRadius( 256 ) + effectdata:SetNormal( trace.HitNormal ) + util.Effect( "AR2Explosion", effectdata, true, true ) +end + +function EFFECT:Render() + if not IsValid( self.Ent ) then return end + + local Muzzle = self.Ent:GetAttachment( self.Ent:LookupAttachment( "bellygun" ) ) + + if not Muzzle then return end + + local T = CurTime() + local Delta = ((T + self.LifeTime) - self.DieTime) / self.LifeTime + + local trace = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + self.Ent:GetAimVector() * 50000, + mask = MASK_SOLID_BRUSHONLY + } ) + + self:SetRenderBoundsWS( Muzzle.Pos, trace.HitPos ) + + render.SetMaterial( BeamFX ) + render.DrawBeam( Muzzle.Pos, trace.HitPos, (Delta ^ 2) * 64, 0, 1, Color( 255, 255, 255 ) ) + + local Sub = trace.HitPos - Muzzle.Pos + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + local Scale = 512 - Delta * 512 + render.SetMaterial( WarpFX ) + render.DrawSprite( Muzzle.Pos + Dir * Dist * Delta, Scale, Scale, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_armor.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_armor.lua new file mode 100644 index 0000000..13dd8c9 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_armor.lua @@ -0,0 +1,258 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + + self:NetworkVar( "Float",0, "HP" ) + self:NetworkVar( "Float",1, "MaxHP" ) + self:NetworkVar( "Float",2, "IgnoreForce" ) + + self:NetworkVar( "Vector",0, "Mins" ) + self:NetworkVar( "Vector",1, "Maxs" ) + + self:NetworkVar( "Bool",0, "Destroyed" ) + + self:NetworkVar( "String",0, "Label" ) + + if SERVER then + self:SetMaxHP( 100 ) + self:SetHP( 100 ) + self:SetLabel( "Armor Plate" ) + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + end + + function ENT:Think() + return false + end + + function ENT:OnHealthChanged( dmginfo, old, new ) + if old == new then return end + end + + function ENT:OnRepaired() + end + + function ENT:OnDestroyed( dmginfo ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:TakeTransmittedDamage( dmginfo ) + local Force = dmginfo:GetDamageForce() + + local Damage = dmginfo:GetDamage() + local DamageForce = Force:Length() + local IsBlastDamage = dmginfo:IsDamageType( DMG_BLAST ) + + local CurHealth = self:GetHP() + + local pos = dmginfo:GetDamagePosition() + local dir = Force:GetNormalized() + + local base = self:GetBase() + + -- translate force value to armor penetration value is Force * 0.1 + -- mm to inch is * 0.0393701 + -- so correct value is * 0.00393701 + local pLength = DamageForce * 0.00393701 + + local TraceData = { + start = pos - dir * pLength, + endpos = pos + dir * pLength, + } + + local trace = util.TraceLine( TraceData ) + + -- parent stays the same + local parent = trace.Entity + local parentPos = trace.HitPos + local parentDir = trace.HitNormal + + -- only one extra iteration should be enough ... + if IsValid( trace.Entity ) and isfunction( trace.Entity.GetBase ) and trace.Entity:GetBase() == base then + + TraceData.filter = trace.Entity + + local FilteredTrace = util.TraceLine( TraceData ) + + if FilteredTrace.Hit then + trace = FilteredTrace + end + + trace.Entity = base + end + + local DotHitNormal = math.Clamp( trace.HitNormal:Dot( dir ) ,-1,1) + + local Armor = self:GetIgnoreForce() + local ArmorEffective = Armor / math.abs( DotHitNormal ) + + if math.abs( DotHitNormal ) > 0.9 then + ArmorEffective = Armor + end + + local DisableBounce = false + + local Inflictor = dmginfo:GetInflictor() + + if IsValid( Inflictor ) then + if Inflictor.DisableBallistics or Inflictor:IsNPC() or Inflictor:IsNextBot() then + DisableBounce = true + end + end + + if DamageForce <= ArmorEffective and not IsBlastDamage then + local T = CurTime() + + if trace.Entity ~= base then + self._NextBounce = T + 1 + + return false + end + + local Ax = math.acos( DotHitNormal ) + local HitAngle = 90 - (180 - math.deg( Ax )) + + if HitAngle > 30 then + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetNormal( -dir ) + util.Effect( "manhacksparks", effectdata, true, true ) + + self._NextBounce = T + 1 + + return false + end + + local NewDir = dir - trace.HitNormal * math.cos( Ax ) * 2 + + if (self._NextBounce or 0) > T or DisableBounce then + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetNormal( NewDir:GetNormalized() * 0.25 ) + util.Effect( "manhacksparks", effectdata, true, true ) + + return false + end + + self._NextBounce = T + 1 + + local hit_decal = ents.Create( "lvs_armor_bounce" ) + hit_decal:SetPos( trace.HitPos ) + hit_decal:SetAngles( NewDir:Angle() ) + hit_decal:Spawn() + hit_decal:Activate() + hit_decal:EmitSound("lvs/armor_rico"..math.random(1,6)..".wav", 95, 100, math.min( dmginfo:GetDamage() / 1000, 1 ) ) + + local PhysObj = hit_decal:GetPhysicsObject() + if not IsValid( PhysObj ) then return false end + + PhysObj:EnableDrag( false ) + PhysObj:SetVelocityInstantaneous( NewDir * 2000 + Vector(0,0,250) ) + PhysObj:SetAngleVelocityInstantaneous( VectorRand() * 250 ) + + return false + end + + local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() ) + + self:OnHealthChanged( dmginfo, CurHealth, NewHealth ) + self:SetHP( NewHealth ) + + if NewHealth <= 0 and not self:GetDestroyed() then + self:SetDestroyed( true ) + self:OnDestroyed( dmginfo ) + end + + local hit_decal = ents.Create( "lvs_armor_penetrate" ) + hit_decal:SetPos( parentPos + parentDir * 0.2 ) + hit_decal:SetAngles( parentDir:Angle() + Angle(90,0,0) ) + hit_decal:Spawn() + hit_decal:Activate() + hit_decal:SetParent( parent ) + + return true + end + + return +end + +function ENT:Initialize() +end + +function ENT:OnRemove() +end + +function ENT:Think() +end + + +function ENT:Draw() +end + +local function DrawText( pos, text, col ) + cam.Start2D() + local data2D = pos:ToScreen() + + if not data2D.visible then cam.End2D() return end + + local font = "TargetIDSmall" + + local x = data2D.x + local y = data2D.y + + draw.DrawText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), TEXT_ALIGN_CENTER ) + draw.DrawText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), TEXT_ALIGN_CENTER ) + draw.DrawText( text, font, x, y, col or color_white, TEXT_ALIGN_CENTER ) + cam.End2D() +end + +local LVS = LVS +local BoxMat = Material("models/wireframe") +local ColorSelect = Color(0,127,255,150) +local ColorNormal = Color(50,50,50,150) +local ColorTransBlack = Color(0,0,0,150) +local OutlineThickness = Vector(0.5,0.5,0.5) +local ColorText = Color(255,0,0,255) + +function ENT:DrawTranslucent() + if not LVS.DeveloperEnabled then return end + + local ply = LocalPlayer() + + if not IsValid( ply ) or ply:InVehicle() or not ply:KeyDown( IN_SPEED ) then return end + + local boxOrigin = self:GetPos() + local boxAngles = self:GetAngles() + local boxMins = self:GetMins() + local boxMaxs = self:GetMaxs() + + local HitPos, _, _ = util.IntersectRayWithOBB( ply:GetShootPos(), ply:GetAimVector() * 1000, boxOrigin, boxAngles, boxMins, boxMaxs ) + + local InRange = isvector( HitPos ) + + local Col = InRange and ColorSelect or ColorNormal + + render.SetColorMaterial() + render.DrawBox( boxOrigin, boxAngles, boxMins, boxMaxs, Col ) + render.DrawBox( boxOrigin, boxAngles, boxMaxs + OutlineThickness, boxMins - OutlineThickness, ColorTransBlack ) + + local boxCenter = (self:LocalToWorld( boxMins ) + self:LocalToWorld( boxMaxs )) * 0.5 + + if not InRange then return end + + DrawText( boxCenter, "Armor: "..(self:GetIgnoreForce() / 100).."mm\nHealth:"..self:GetHP().."/"..self:GetMaxHP(), ColorText ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_armor_bounce.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_armor_bounce.lua new file mode 100644 index 0000000..bdcbf33 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_armor_bounce.lua @@ -0,0 +1,124 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "88mm Round" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars - Items" + +ENT.Spawnable = false +ENT.AdminOnly = false + +ENT.LifeTime = 10 + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/misc/88mm_projectile.mdl" ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysicsInit( SOLID_VPHYSICS) + + self.DieTime = CurTime() + self.LifeTime + + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + + function ENT:Think() + if self.MarkForRemove then self:Remove() return false end + + self:NextThink( CurTime() + 0.1 ) + + if (self.DieTime or 0) > CurTime() then return true end + + self:Remove() + + return false + end + + + function ENT:PhysicsCollide( data, physobj ) + self.MarkForRemove = true + + local effectdata = EffectData() + effectdata:SetOrigin( data.HitPos ) + effectdata:SetNormal( -data.HitNormal ) + effectdata:SetMagnitude( 0.5 ) + util.Effect( "lvs_bullet_impact", effectdata ) + end + + return +end + +ENT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function ENT:Initialize() + self.DieTime = CurTime() + self.LifeTime + + self.emitter = ParticleEmitter( self:GetPos(), false ) +end + +function ENT:Smoke() + local T = CurTime() + + if (self.DieTime or 0) < T then return end + + if not IsValid( self.emitter ) then return end + + if (self.NextFX or 0) < T then + self.NextFX = T + 0.02 + + local Timed = 1 - (self.DieTime - T) / self.LifeTime + local Scale = math.max(math.min(2 - Timed * 2,1),0) + + local Pos = self:GetPos() + + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], Pos ) + + local VecCol = (render.GetLightColor( Pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + if particle then + particle:SetVelocity( VectorRand() * 10 ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetAirResistance( 100 ) + particle:SetStartAlpha( 100 * Scale ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( 20 ) + particle:SetRollDelta( 1 ) + particle:SetColor( VecCol.r, VecCol.g, VecCol.b ) + particle:SetGravity( Vector( 0, 0, 200 ) ) + particle:SetCollide( false ) + end + end +end + +function ENT:Think() + self:Smoke() +end + +function ENT:OnRemove() + if not self.emitter then return end + + self.emitter:Finish() +end + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_armor_penetrate.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_armor_penetrate.lua new file mode 100644 index 0000000..8924660 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_armor_penetrate.lua @@ -0,0 +1,149 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.LifeTime = 15 + +if SERVER then + local CountTotal = {} + + function ENT:Initialize() + CountTotal[ self:EntIndex() ] = true + + local Num = table.Count( CountTotal ) + + if (Num > 30 and math.random(1,2) == 1) or Num > 60 then + self:Remove() + + return + end + + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + self.DieTime = CurTime() + self.LifeTime + end + + function ENT:OnRemove() + CountTotal[ self:EntIndex() ] = nil + end + + function ENT:Think() + self:NextThink( CurTime() + 0.1 ) + + if not IsValid( self:GetParent() ) then self:Remove() return end + + if (self.DieTime or 0) > CurTime() then return true end + + self:Remove() + + return false + end + + return +end + +ENT.GlowMat1 = Material( "particle/particle_ring_wave_8" ) +ENT.GlowMat2 = Material( "sprites/light_glow02_add" ) +ENT.DecalMat = Material( "particle/particle_noisesphere" ) +ENT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +local CountTotal = {} + +function ENT:Initialize() + CountTotal[ self:EntIndex() ] = true + + self.RandomAng = math.random(0,360) + self.DieTime = CurTime() + self.LifeTime + + local Pos = self:GetPos() + local Dir = self:GetUp() + + self.emitter = ParticleEmitter( Pos, false ) + + self:EmitSound( "lvs/armor_pen_"..math.random(1,3)..".wav", 95 ) +end + +function ENT:Smoke() + local T = CurTime() + + if (self.DieTime or 0) < T then return end + + if not IsValid( self.emitter ) then return end + + if (self.NextFX or 0) < T then + self.NextFX = T + 0.2 + table.Count( CountTotal ) / 50 + + local particle = self.emitter:Add( self.MatSmoke[math.random(1,#self.MatSmoke)], self:GetPos() ) + + if particle then + particle:SetVelocity( self:GetUp() * 60 + VectorRand() * 30 ) + particle:SetDieTime( math.Rand(1.5,2) ) + particle:SetAirResistance( 100 ) + particle:SetStartAlpha( 30 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 0 ) + particle:SetEndSize( 60 ) + particle:SetRollDelta( math.Rand( -1, 1 ) ) + particle:SetColor( 50,50,50 ) + particle:SetGravity( Vector( 0, 0, 200 ) ) + particle:SetCollide( false ) + end + end +end + +function ENT:Think() + self:Smoke() +end + +function ENT:OnRemove() + CountTotal[ self:EntIndex() ] = nil + + if not IsValid(self.emitter) then return end + + self.emitter:Finish() +end + +function ENT:Draw() + local Timed = 1 - (self.DieTime - CurTime()) / self.LifeTime + local Scale = math.max(math.min(2 - Timed * 2,1),0) + + local Scale02 = math.max(Scale - 0.8,0) / 0.2 + + cam.Start3D2D( self:GetPos() + self:GetAngles():Up(), self:GetAngles(), 1 ) + surface.SetDrawColor( 255 * Scale02, (93 + 50 * Scale) * Scale02, (50 * Scale) * Scale02, (200 * Scale) * Scale02 ) + + surface.SetMaterial( self.GlowMat1 ) + surface.DrawTexturedRectRotated( 0, 0, 8 , 8 , self.RandomAng ) + + surface.SetMaterial( self.GlowMat2 ) + surface.DrawTexturedRectRotated( 0, 0, 16 , 16 , self.RandomAng ) + + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.SetMaterial( self.DecalMat ) + surface.DrawTexturedRectRotated( 0, 0, 16 , 16 , self.RandomAng ) + cam.End3D2D() +end + +function ENT:DrawTranslucent() + self:Draw() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_boneposeparemeter.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_boneposeparemeter.lua new file mode 100644 index 0000000..d09f570 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_boneposeparemeter.lua @@ -0,0 +1,30 @@ + +function ENT:CreateBonePoseParameter( name, bone, ang_min, ang_max, pos_min, pos_max ) + if not istable( self._BonePoseParameters ) then self._BonePoseParameters = {} end + + self._BonePoseParameters[ name ] = { + bone = (bone or -1), + ang_min = ang_min or angle_zero, + ang_max = ang_max or angle_zero, + pos_min = pos_min or vector_origin, + pos_max = pos_max or vector_origin, + } +end + +function ENT:SetBonePoseParameter( name, value ) + if name and string.StartsWith( name, "!" ) then + name = string.Replace( name, "!", "" ) + end + + local EntTable = self:GetTable() + + if not istable( EntTable._BonePoseParameters ) or not EntTable._BonePoseParameters[ name ] then return end + + local data = EntTable._BonePoseParameters[ name ] + + local ang = LerpAngle( value, data.ang_min, data.ang_max ) + local pos = LerpVector( value, data.pos_min, data.pos_max ) + + self:ManipulateBoneAngles( data.bone, ang ) + self:ManipulateBonePosition( data.bone, pos ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_effects.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_effects.lua new file mode 100644 index 0000000..323a362 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_effects.lua @@ -0,0 +1,142 @@ + +function ENT:StartWindSounds() + if not LVS.ShowEffects then return end + + self:StopWindSounds() + + if LocalPlayer():lvsGetVehicle() ~= self then return end + + local EntTable = self:GetTable() + + EntTable._WindSFX = CreateSound( self, "LVS.Physics.Wind" ) + EntTable._WindSFX:PlayEx(0,100) + + EntTable._WaterSFX = CreateSound( self, "LVS.Physics.Water" ) + EntTable._WaterSFX:PlayEx(0,100) +end + +function ENT:StopWindSounds() + local EntTable = self:GetTable() + + if EntTable._WindSFX then + EntTable._WindSFX:Stop() + EntTable._WindSFX = nil + end + + if EntTable._WaterSFX then + EntTable._WaterSFX:Stop() + EntTable._WaterSFX = nil + end +end + +ENT.DustEffectSurfaces = { + ["sand"] = true, + ["dirt"] = true, + ["grass"] = true, +} + +ENT.GroundEffectsMultiplier = 1 + +function ENT:DoVehicleFX() + local EntTable = self:GetTable() + + if EntTable.GroundEffectsMultiplier <= 0 or not LVS.ShowEffects then self:StopWindSounds() return end + + local Vel = self:GetVelocity():Length() * EntTable.GroundEffectsMultiplier + + if EntTable._WindSFX then EntTable._WindSFX:ChangeVolume( math.Clamp( (Vel - 1200) / 2800,0,1 ), 0.25 ) end + + if Vel < 1500 then + if EntTable._WaterSFX then EntTable._WaterSFX:ChangeVolume( 0, 0.25 ) end + + return + end + + if (EntTable.nextFX or 0) < CurTime() then + EntTable.nextFX = CurTime() + 0.05 + + local LCenter = self:OBBCenter() + LCenter.z = self:OBBMins().z + + local CenterPos = self:LocalToWorld( LCenter ) + + local trace = util.TraceLine( { + start = CenterPos + Vector(0,0,25), + endpos = CenterPos - Vector(0,0,450), + filter = self:GetCrosshairFilterEnts(), + } ) + + local traceWater = util.TraceLine( { + start = CenterPos + Vector(0,0,25), + endpos = CenterPos - Vector(0,0,450), + filter = self:GetCrosshairFilterEnts(), + mask = MASK_WATER, + } ) + + if EntTable._WaterSFX then EntTable._WaterSFX:ChangePitch( math.Clamp((Vel / 1000) * 50,80,150), 0.5 ) end + + if traceWater.Hit and trace.HitPos.z < traceWater.HitPos.z then + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.HitPos ) + effectdata:SetEntity( self ) + util.Effect( "lvs_physics_water", effectdata ) + + if EntTable._WaterSFX then EntTable._WaterSFX:ChangeVolume( 1 - math.Clamp(traceWater.Fraction,0,1), 0.5 ) end + else + if EntTable._WaterSFX then EntTable._WaterSFX:ChangeVolume( 0, 0.25 ) end + end + + if trace.Hit and EntTable.DustEffectSurfaces[ util.GetSurfacePropName( trace.SurfaceProps ) ] then + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetEntity( self ) + util.Effect( "lvs_physics_dust", effectdata ) + end + end +end + +function ENT:GetParticleEmitter( Pos ) + local EntTable = self:GetTable() + + local T = CurTime() + + if IsValid( EntTable.Emitter ) and (EntTable.EmitterTime or 0) > T then + return EntTable.Emitter + end + + self:StopEmitter() + + EntTable.Emitter = ParticleEmitter( Pos, false ) + EntTable.EmitterTime = T + 2 + + return EntTable.Emitter +end + +function ENT:StopEmitter() + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end +end + +function ENT:GetParticleEmitter3D( Pos ) + local EntTable = self:GetTable() + + local T = CurTime() + + if IsValid( EntTable.Emitter3D ) and (EntTable.EmitterTime3D or 0) > T then + return EntTable.Emitter3D + end + + self:StopEmitter3D() + + EntTable.Emitter3D = ParticleEmitter( Pos, true ) + EntTable.EmitterTime3D = T + 2 + + return EntTable.Emitter3D +end + +function ENT:StopEmitter3D() + if IsValid( self.Emitter3D ) then + self.Emitter3D:Finish() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_hud.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_hud.lua new file mode 100644 index 0000000..f0091da --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_hud.lua @@ -0,0 +1,253 @@ + +LVS:AddHudEditor( "VehicleHealth", 10, ScrH() - 85, 220, 75, 220, 75, "VEHICLE HEALTH", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintVehicleHealth then return end + + vehicle:LVSHudPaintVehicleHealth( X, Y, W, H, ScrX, ScrY, ply ) + end +) + +LVS:AddHudEditor( "VehicleInfo", ScrW() - 460, ScrH() - 85, 220, 75, 220, 75, "VEHICLE INFORMATION", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintInfoText then return end + + vehicle:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + end +) + +function ENT:LVSHudPaintVehicleHealth( X, Y, W, H, ScrX, ScrY, ply ) + draw.DrawText( "HEALTH ", "LVS_FONT", X + 102, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( math.Round( self:GetHP(), 0 ), "LVS_FONT_HUD_LARGE", X + 102, Y + 20, color_white, TEXT_ALIGN_LEFT ) +end + +ENT.VehicleIdentifierRange = 10000 + +function ENT:LVSHudPaintVehicleIdentifier( X, Y, In_Col ) + local HP = self:GetHP() + + surface.SetDrawColor( In_Col.r, In_Col.g, In_Col.b, In_Col.a ) + LVS:DrawDiamond( X + 1, Y + 1, 20, HP / self:GetMaxHP() ) + + if self:GetMaxShield() > 0 and HP > 0 then + surface.SetDrawColor( 200, 200, 255, In_Col.a ) + LVS:DrawDiamond( X + 1, Y + 1, 24, self:GetShield() / self:GetMaxShield() ) + end +end + +function ENT:LVSPreHudPaint( X, Y, ply ) + return true +end + +local zoom = 0 +local zoom_mat = Material( "vgui/zoom" ) +local zoom_switch = 0 +local zoom_blinder = 0 +local TargetZoom = 0 + +ENT.ZoomInSound = "weapons/sniper/sniper_zoomin.wav" +ENT.ZoomOutSound = "weapons/sniper/sniper_zoomout.wav" + +function ENT:GetZoom() + return TargetZoom +end + +function ENT:PaintZoom( X, Y, ply ) + TargetZoom = ply:lvsKeyDown( "ZOOM" ) and 1 or 0 + + zoom = zoom + (TargetZoom - zoom) * RealFrameTime() * 10 + + if self.OpticsEnable then + if self:GetOpticsEnabled() then + if zoom_switch ~= TargetZoom then + zoom_switch = TargetZoom + + zoom_blinder = 1 + + if TargetZoom == 1 then + surface.PlaySound( self.ZoomInSound ) + else + surface.PlaySound( self.ZoomOutSound ) + end + end + + zoom_blinder = zoom_blinder - zoom_blinder * RealFrameTime() * 5 + + surface.SetDrawColor( Color(0,0,0,255 * zoom_blinder) ) + surface.DrawRect( 0, 0, X, Y ) + + self.ZoomFov = self.OpticsFov + else + self.ZoomFov = nil + end + end + + X = X * 0.5 + Y = Y * 0.5 + + surface.SetDrawColor( Color(255,255,255,255 * zoom) ) + surface.SetMaterial(zoom_mat ) + surface.DrawTexturedRectRotated( X + X * 0.5, Y * 0.5, X, Y, 0 ) + surface.DrawTexturedRectRotated( X + X * 0.5, Y + Y * 0.5, Y, X, 270 ) + surface.DrawTexturedRectRotated( X * 0.5, Y * 0.5, Y, X, 90 ) + surface.DrawTexturedRectRotated( X * 0.5, Y + Y * 0.5, X, Y, 180 ) +end + +function ENT:LVSHudPaint( X, Y, ply ) + if not self:LVSPreHudPaint( X, Y, ply ) then return end + + self:PaintZoom( X, Y, ply ) +end + +function ENT:HurtMarker( intensity ) + LocalPlayer():EmitSound( "lvs/hit_receive"..math.random(1,2)..".wav", 75, math.random(95,105), 0.25 + intensity * 0.75, CHAN_STATIC ) + util.ScreenShake( Vector(0, 0, 0), 25 * intensity, 25 * intensity, 0.5, 1 ) +end + +function ENT:KillMarker() + self.LastKillMarker = CurTime() + 0.5 + + LocalPlayer():EmitSound( "lvs/hit_kill.wav", 85, 100, 0.4, CHAN_VOICE ) +end + +local LastMarker = 0 +function ENT:ArmorMarker( IsDamage ) + local T = CurTime() + + local DontHurtEars = math.Clamp( T - LastMarker, 0, 1 ) ^ 2 + + LastMarker = T + + local ArmorFailed = IsDamage and "takedamage" or "pen" + local Volume = IsDamage and (0.3 * DontHurtEars) or 1 + + LocalPlayer():EmitSound( "lvs/armor_"..ArmorFailed.."_"..math.random(1,3)..".wav", 85, math.random(95,105), Volume, CHAN_ITEM2 ) +end + +function ENT:HitMarker() + self.LastHitMarker = CurTime() + 0.15 + + LocalPlayer():EmitSound( "lvs/hit.wav", 85, math.random(95,105), 0.4, CHAN_ITEM ) +end + +function ENT:CritMarker() + self.LastCritMarker = CurTime() + 0.15 + + LocalPlayer():EmitSound( "lvs/hit_crit.wav", 85, math.random(95,105), 0.4, CHAN_ITEM2 ) +end + +function ENT:GetHitMarker() + return self.LastHitMarker or 0 +end + +function ENT:GetCritMarker() + return self.LastCritMarker or 0 +end + +function ENT:GetKillMarker() + return self.LastKillMarker or 0 +end + +function ENT:LVSPaintHitMarker( scr ) + local T = CurTime() + + local aV = math.cos( math.rad( math.max(((self:GetHitMarker() - T) / 0.15) * 360,0) ) ) + if aV ~= 1 then + local Start = 12 + (1 - aV) * 8 + local dst = 10 + + surface.SetDrawColor( 255, 255, 0, 255 ) + + surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start, scr.y + Start - dst ) + surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start - dst, scr.y + Start ) + + surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start, scr.y - Start + dst ) + surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start - dst, scr.y - Start ) + + surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start, scr.y + Start - dst ) + surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start + dst, scr.y + Start ) + + surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start, scr.y - Start + dst ) + surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start + dst, scr.y - Start ) + + scr.x = scr.x + 1 + scr.y = scr.y + 1 + + surface.SetDrawColor( 0, 0, 0, 80 ) + + surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start, scr.y + Start - dst ) + surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + Start - dst, scr.y + Start ) + + surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start, scr.y - Start + dst ) + surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + Start - dst, scr.y - Start ) + + surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start, scr.y + Start - dst ) + surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - Start + dst, scr.y + Start ) + + surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start, scr.y - Start + dst ) + surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - Start + dst, scr.y - Start ) + end + + local aV = math.sin( math.rad( math.max(((self:GetCritMarker() - T) / 0.15) * 180,0) ) ) + if aV > 0.01 then + local Start = 10 + aV * 40 + local End = 20 + aV * 45 + + surface.SetDrawColor( 255, 100, 0, 255 ) + surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + End, scr.y + End ) + surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - End, scr.y + End ) + surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + End, scr.y - End ) + surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - End, scr.y - End ) + + draw.NoTexture() + surface.DrawTexturedRectRotated( scr.x + Start, scr.y + Start, 3, 20, 45 ) + surface.DrawTexturedRectRotated( scr.x - Start, scr.y + Start, 20, 3, 45 ) + surface.DrawTexturedRectRotated( scr.x + Start, scr.y - Start, 20, 3, 45 ) + surface.DrawTexturedRectRotated( scr.x - Start, scr.y - Start, 3, 20, 45 ) + end + + local aV = math.sin( math.rad( math.sin( math.rad( math.max(((self:GetKillMarker() - T) / 0.2) * 90,0) ) ) * 90 ) ) + if aV > 0.01 then + surface.SetDrawColor( 255, 255, 255, 15 * (aV ^ 4) ) + surface.DrawRect( 0, 0, ScrW(), ScrH() ) + + local Start = 10 + aV * 40 + local End = 20 + aV * 45 + surface.SetDrawColor( 255, 0, 0, 255 ) + surface.DrawLine( scr.x + Start, scr.y + Start, scr.x + End, scr.y + End ) + surface.DrawLine( scr.x - Start, scr.y + Start, scr.x - End, scr.y + End ) + surface.DrawLine( scr.x + Start, scr.y - Start, scr.x + End, scr.y - End ) + surface.DrawLine( scr.x - Start, scr.y - Start, scr.x - End, scr.y - End ) + + draw.NoTexture() + surface.DrawTexturedRectRotated( scr.x + Start, scr.y + Start, 5, 20, 45 ) + surface.DrawTexturedRectRotated( scr.x - Start, scr.y + Start, 20, 5, 45 ) + surface.DrawTexturedRectRotated( scr.x + Start, scr.y - Start, 20, 5, 45 ) + surface.DrawTexturedRectRotated( scr.x - Start, scr.y - Start, 5, 20, 45 ) + end +end + +local Circles = { + [1] = {r = -1, col = Color(0,0,0,200)}, + [2] = {r = 0, col = Color(255,255,255,200)}, + [3] = {r = 1, col = Color(255,255,255,255)}, + [4] = {r = 2, col = Color(255,255,255,200)}, + [5] = {r = 3, col = Color(0,0,0,200)}, +} + +function ENT:LVSDrawCircle( X, Y, target_radius, value ) + local endang = 360 * value + + if endang == 0 then return end + + for i = 1, #Circles do + local data = Circles[ i ] + local radius = target_radius + data.r + local segmentdist = endang / ( math.pi * radius / 2 ) + + for a = 0, endang, segmentdist do + surface.SetDrawColor( data.col ) + + surface.DrawLine( X - math.sin( math.rad( a ) ) * radius, Y + math.cos( math.rad( a ) ) * radius, X - math.sin( math.rad( a + segmentdist ) ) * radius, Y + math.cos( math.rad( a + segmentdist ) ) * radius ) + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_init.lua new file mode 100644 index 0000000..24c4437 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_init.lua @@ -0,0 +1,224 @@ +include("shared.lua") +include("sh_weapons.lua") +include("sh_velocity_changer.lua") +include("sh_variables.lua") +include("cl_effects.lua") +include("cl_hud.lua") +include("cl_optics.lua") +include("cl_seatswitcher.lua") +include("cl_trailsystem.lua") +include("cl_boneposeparemeter.lua") + +local Zoom = 0 + +function ENT:LVSCalcFov( fov, ply ) + + local TargetZoom = ply:lvsKeyDown( "ZOOM" ) and 0 or 1 + + Zoom = Zoom + (TargetZoom - Zoom) * RealFrameTime() * 10 + + local newfov = fov * Zoom + (self.ZoomFov or 40) * (1 - Zoom) + + return newfov +end + +function ENT:LVSCalcView( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:PreDraw( flags ) + return true +end + +function ENT:PreDrawTranslucent( flags ) + return true +end + +function ENT:PostDraw( flags ) +end + +function ENT:PostDrawTranslucent( flags ) +end + +function ENT:Draw( flags ) + if self:PreDraw( flags ) then + if self.lvsLegacyDraw then + self:DrawModel() -- ugly, but required in order to fix old addons. Refract wont work on these. + else + self:DrawModel( flags ) + end + end + + self:PostDraw( flags ) +end + +function ENT:DrawTranslucent( flags ) + self:DrawTrail() + + if self:PreDrawTranslucent( flags ) then + self:DrawModel( flags ) + else + self.lvsLegacyDraw = true -- insert puke simley + end + + self:PostDrawTranslucent( flags ) +end + +function ENT:Initialize() + self:OnSpawn() + + if not istable( self.GibModels ) then return end + + for _, modelName in ipairs( self.GibModels ) do + util.PrecacheModel( modelName ) + end +end + +function ENT:OnSpawn() +end + +function ENT:OnFrameActive() +end + +function ENT:OnFrame() +end + +function ENT:OnEngineActiveChanged( Active ) +end + +function ENT:OnActiveChanged( Active ) +end + +ENT._oldActive = false +ENT._oldEnActive = false + +function ENT:HandleActive() + local EntTable = self:GetTable() + + local Active = self:GetActive() + local EngineActive = self:GetEngineActive() + local ActiveChanged = false + + if EntTable._oldActive ~= Active then + EntTable._oldActive = Active + EntTable:OnActiveChanged( Active ) + ActiveChanged = true + end + + if EntTable._oldEnActive ~= EngineActive then + EntTable._oldEnActive = EngineActive + self:OnEngineActiveChanged( EngineActive ) + ActiveChanged = true + end + + if ActiveChanged then + if Active or EngineActive then + self:StartWindSounds() + else + self:StopWindSounds() + end + end + + if Active or EngineActive then + self:DoVehicleFX() + end + + self:FlyByThink() + + return EngineActive +end + +function ENT:Think() + if not self:IsInitialized() then return end + + if self:HandleActive() then + self:OnFrameActive() + end + + self:HandleTrail() + self:OnFrame() +end + +function ENT:OnRemove() + self:StopEmitter() + self:StopEmitter3D() + self:StopWindSounds() + self:StopFlyBy() + self:StopDeathSound() + + self:OnRemoved() +end + +function ENT:OnRemoved() +end + +function ENT:CalcDoppler( Ent ) + if not IsValid( Ent ) then return 1 end + + if Ent:IsPlayer() then + local ViewEnt = Ent:GetViewEntity() + local Vehicle = Ent:lvsGetVehicle() + + if IsValid( Vehicle ) then + if Ent == ViewEnt then + Ent = Vehicle + end + else + if IsValid( ViewEnt ) then + Ent = ViewEnt + end + end + end + + local sVel = self:GetVelocity() + local oVel = Ent:GetVelocity() + + local SubVel = oVel - sVel + local SubPos = self:GetPos() - Ent:GetPos() + + local DirPos = SubPos:GetNormalized() + local DirVel = SubVel:GetNormalized() + + local A = math.acos( math.Clamp( DirVel:Dot( DirPos ) ,-1,1) ) + + return (1 + math.cos( A ) * SubVel:Length() / 13503.9) +end + +function ENT:GetCrosshairFilterEnts() + if not self:IsInitialized() or not LVS.MapDoneLoading then return { self } end -- wait for the server to be ready + + if not istable( self.CrosshairFilterEnts ) then + self.CrosshairFilterEnts = {self} + + timer.Simple(1, function() + if not IsValid( self ) then return end + + -- lets ask the server to build the filter for us because it has access to constraint.GetAllConstrainedEntities() + net.Start( "lvs_player_request_filter" ) + net.WriteEntity( self ) + net.SendToServer() + end) + end + + return self.CrosshairFilterEnts +end + +function ENT:FlyByThink() +end + +function ENT:StopFlyBy() +end + +function ENT:StopDeathSound() +end + +function ENT:OnDestroyed() +end + +net.Receive( "lvs_vehicle_destroy", function( len ) + local ent = net.ReadEntity() + + if not IsValid( ent ) or not isfunction( ent.OnDestroyed ) then return end + + ent:OnDestroyed() +end ) diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_optics.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_optics.lua new file mode 100644 index 0000000..0ef89ee --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_optics.lua @@ -0,0 +1,208 @@ + +ENT.OpticsFov = 30 +ENT.OpticsEnable = false +ENT.OpticsZoomOnly = true +ENT.OpticsFirstPerson = true +ENT.OpticsThirdPerson = true +ENT.OpticsPodIndex = { + [1] = true, +} + +ENT.OpticsCrosshairMaterial = Material( "vgui/circle" ) +ENT.OpticsCrosshairColor = Color(0,0,0,255) +ENT.OpticsCrosshairSize = 5 + +function ENT:PaintOpticsCrosshair( Pos2D ) + if not Pos2D.visible then return end + + local size = self.OpticsCrosshairSize + + surface.SetMaterial( self.OpticsCrosshairMaterial ) + surface.SetDrawColor( self.OpticsCrosshairColor ) + surface.DrawTexturedRect( Pos2D.x - size * 0.5, Pos2D.y - size * 0.5, size, size ) +end + +function ENT:CalcOpticsCrosshairDot( Pos2D ) + self:PaintOpticsCrosshair( Pos2D ) +end + +function ENT:GetOpticsEnabled() + local EntTable = self:GetTable() + + if not EntTable.OpticsEnable then return false end + + local ply = LocalPlayer() + + if not IsValid( ply ) then return false end + + local pod = ply:GetVehicle() + local PodIndex = pod:lvsGetPodIndex() + if pod == self:GetDriverSeat() then + PodIndex = 1 + end + + if EntTable.OpticsPodIndex[ PodIndex ] then + if pod:GetThirdPersonMode() then + return EntTable.OpticsThirdPerson + else + return EntTable.OpticsFirstPerson + end + end + + return false +end + +function ENT:UseOptics() + if self.OpticsZoomOnly and self:GetZoom() ~= 1 then return false end + + return self:GetOpticsEnabled() +end + +function ENT:PaintCrosshairCenter( Pos2D, Col ) + if self:UseOptics() then + if self.OpticsScreenCentered then + self:CalcOpticsCrosshairDot( Pos2D ) + + local ScreenCenter2D = { + x = ScrW() * 0.5, + y = ScrH() * 0.5, + visible = true, + } + + self:PaintOptics( ScreenCenter2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 1 ) + else + self:PaintOptics( Pos2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 1 ) + end + + return + end + + if not Col then + Col = Color( 255, 255, 255, 255 ) + end + + local Alpha = Col.a / 255 + local Shadow = Color( 0, 0, 0, 80 * Alpha ) + + surface.DrawCircle( Pos2D.x, Pos2D.y, 4, Shadow ) + surface.DrawCircle( Pos2D.x, Pos2D.y, 5, Col ) + surface.DrawCircle( Pos2D.x, Pos2D.y, 6, Shadow ) +end + +function ENT:PaintCrosshairOuter( Pos2D, Col ) + if self:UseOptics() then + if self.OpticsScreenCentered then + self:CalcOpticsCrosshairDot( Pos2D ) + + local ScreenCenter2D = { + x = ScrW() * 0.5, + y = ScrH() * 0.5, + visible = true, + } + + self:PaintOptics( ScreenCenter2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 2 ) + else + self:PaintOptics( Pos2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 2 ) + end + + return + end + + if not Col then + Col = Color( 255, 255, 255, 255 ) + end + + local Alpha = Col.a / 255 + local Shadow = Color( 0, 0, 0, 80 * Alpha ) + + surface.DrawCircle( Pos2D.x,Pos2D.y, 17, Shadow ) + surface.DrawCircle( Pos2D.x, Pos2D.y, 18, Col ) + + if LVS.AntiAliasingEnabled then + surface.DrawCircle( Pos2D.x, Pos2D.y, 19, Color( Col.r, Col.g, Col.b, 150 * Alpha ) ) + surface.DrawCircle( Pos2D.x, Pos2D.y, 20, Shadow ) + else + surface.DrawCircle( Pos2D.x, Pos2D.y, 19, Shadow ) + end +end + +function ENT:PaintCrosshairSquare( Pos2D, Col ) + if self:UseOptics() then + if self.OpticsScreenCentered then + self:CalcOpticsCrosshairDot( Pos2D ) + + local ScreenCenter2D = { + x = ScrW() * 0.5, + y = ScrH() * 0.5, + visible = true, + } + + self:PaintOptics( ScreenCenter2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 3 ) + else + self:PaintOptics( Pos2D, Col, LocalPlayer():GetVehicle():GetNWInt( "pPodIndex", -1 ), 3 ) + end + + return + end + + if not Col then + Col = Color( 255, 255, 255, 255 ) + end + + local X = Pos2D.x + 1 + local Y = Pos2D.y + 1 + + local Size = 20 + + surface.SetDrawColor( 0, 0, 0, 80 ) + surface.DrawLine( X - Size, Y + Size, X - Size * 0.5, Y + Size ) + surface.DrawLine( X + Size, Y + Size, X + Size * 0.5, Y + Size ) + surface.DrawLine( X - Size, Y + Size, X - Size, Y + Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size, Y - Size * 0.5 ) + surface.DrawLine( X + Size, Y + Size, X + Size, Y + Size * 0.5 ) + surface.DrawLine( X + Size, Y - Size, X + Size, Y - Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size * 0.5, Y - Size ) + surface.DrawLine( X + Size, Y - Size, X + Size * 0.5, Y - Size ) + + if Col then + surface.SetDrawColor( Col.r, Col.g, Col.b, Col.a ) + else + surface.SetDrawColor( 255, 255, 255, 255 ) + end + + X = Pos2D.x + Y = Pos2D.y + + surface.DrawLine( X - Size, Y + Size, X - Size * 0.5, Y + Size ) + surface.DrawLine( X + Size, Y + Size, X + Size * 0.5, Y + Size ) + surface.DrawLine( X - Size, Y + Size, X - Size, Y + Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size, Y - Size * 0.5 ) + surface.DrawLine( X + Size, Y + Size, X + Size, Y + Size * 0.5 ) + surface.DrawLine( X + Size, Y - Size, X + Size, Y - Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size * 0.5, Y - Size ) + surface.DrawLine( X + Size, Y - Size, X + Size * 0.5, Y - Size ) +end + +function ENT:DrawRotatedText( text, x, y, font, color, ang) + render.PushFilterMag( TEXFILTER.ANISOTROPIC ) + render.PushFilterMin( TEXFILTER.ANISOTROPIC ) + + local m = Matrix() + m:Translate( Vector( x, y, 0 ) ) + m:Rotate( Angle( 0, ang, 0 ) ) + + surface.SetFont( font ) + local w, h = surface.GetTextSize( text ) + + m:Translate( -Vector( w / 2, h / 2, 0 ) ) + + cam.PushModelMatrix( m ) + draw.DrawText( text, font, 0, 0, color ) + cam.PopModelMatrix() + + render.PopFilterMag() + render.PopFilterMin() +end + +function ENT:PaintOptics( Pos2D, Col, PodIndex, Type ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_seatswitcher.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_seatswitcher.lua new file mode 100644 index 0000000..0d24198 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_seatswitcher.lua @@ -0,0 +1,147 @@ + +ENT.IconVehicleLocked = Material( "lvs/locked.png" ) + +LVS:AddHudEditor( "SeatSwitcher", ScrW() - 360, 10, 350, 60, 350, 60, "SEAT SWITCHER", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintSeatSwitcher then return end + + vehicle:LVSHudPaintSeatSwitcher( X, Y, W, 30, ScrX, ScrY, ply ) + end +) + +function ENT:LVSHudPaintSeatSwitcher( X, Y, w, h, ScrX, ScrY, ply ) + local pSeats = table.Copy( self:GetPassengerSeats() ) + local SeatCount = table.Count( pSeats ) + + if SeatCount <= 0 then return end + + pSeats[0] = self:GetDriverSeat() + + draw.NoTexture() + + local HasAI = self:GetAI() + local HasAIGunners = self:GetAIGunners() + + local MySeat = ply:GetVehicle():lvsGetPodIndex() + + local Passengers = {} + for _, player in pairs( player.GetAll() ) do + if player:lvsGetVehicle() == self then + local Pod = player:GetVehicle() + Passengers[ Pod:lvsGetPodIndex() ] = player:GetName() + end + end + + if HasAI then + Passengers[1] = "[AI] "..self.PrintName + end + + if HasAIGunners then + for _, Pod in pairs( self:GetPassengerSeats() ) do + if IsValid( Pod:GetDriver() ) then continue end + + local weapon = Pod:lvsGetWeapon() + + if not IsValid( weapon ) then continue end + + Passengers[ weapon:GetPodIndex() ] = "[AI] Gunner" + end + end + + ply.SwitcherTime = ply.SwitcherTime or 0 + ply._lvsoldPassengers = ply._lvsoldPassengers or {} + + local Time = CurTime() + for k, v in pairs( Passengers ) do + if ply._lvsoldPassengers[k] ~= v then + ply._lvsoldPassengers[k] = v + ply.SwitcherTime = Time + 2 + end + end + for k, v in pairs( ply._lvsoldPassengers ) do + if not Passengers[k] then + ply._lvsoldPassengers[k] = nil + ply.SwitcherTime = Time + 2 + end + end + for _, v in pairs( LVS.pSwitchKeysInv ) do + if input.IsKeyDown(v) then + ply.SwitcherTime = Time + 2 + end + end + + local Hide = ply.SwitcherTime > Time + + ply.smHider = ply.smHider and (ply.smHider + ((Hide and 1 or 0) - ply.smHider) * RealFrameTime() * 15) or 0 + + local Alpha1 = 135 + 110 * ply.smHider + local HiderOffset = 270 * ply.smHider + local xPos = w - 35 + local yPos = Y - (SeatCount + 1) * 30 + h + 5 + + local SwapY = false + local SwapX = false + + local xHider = HiderOffset + + if X < (ScrX * 0.5 - w * 0.5) then + SwapX = true + xPos = 0 + xHider = 0 + end + + if Y < (ScrY * 0.5 - h * 0.5) then + SwapY = true + yPos = Y - h + end + + for _, Pod in pairs( pSeats ) do + if not IsValid( Pod ) then continue end + + local I = Pod:lvsGetPodIndex() + + if I <= 0 then continue end + + if not Hide and I > 10 then continue end + + if I == MySeat then + draw.RoundedBox(5, X + xPos - xHider, yPos + I * 30, 35 + HiderOffset, 25, Color(LVS.ThemeColor.r, LVS.ThemeColor.g, LVS.ThemeColor.b,100 + 50 * ply.smHider) ) + else + draw.RoundedBox(5, X + xPos - xHider, yPos + I * 30, 35 + HiderOffset, 25, Color(0,0,0,100 + 50 * ply.smHider) ) + end + + if Hide then + if Passengers[I] then + draw.DrawText( Passengers[I], "LVS_FONT_SWITCHER", X + 40 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_LEFT ) + else + draw.DrawText( "-", "LVS_FONT_SWITCHER", X + 40 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_LEFT ) + end + + draw.DrawText( "["..I.."]", "LVS_FONT_SWITCHER", X + 17 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_CENTER ) + else + if Passengers[I] then + draw.DrawText( "[^"..I.."]", "LVS_FONT_SWITCHER", X + 17 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_CENTER ) + else + draw.DrawText( "["..I.."]", "LVS_FONT_SWITCHER", X + 17 + xPos - xHider, yPos + I * 30 + 2.5, Color( 255, 255, 255, Alpha1 ), TEXT_ALIGN_CENTER ) + end + end + + if not self:GetlvsLockedStatus() then continue end + + local xLocker = SwapX and 35 + HiderOffset or -25 - HiderOffset + + if SwapY then + if I == 1 then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( self.IconVehicleLocked ) + surface.DrawTexturedRect( X + xPos + xLocker, yPos + I * 30, 25, 25 ) + end + else + if I == SeatCount then + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( self.IconVehicleLocked ) + surface.DrawTexturedRect( X + xPos + xLocker, yPos + I * 30, 25, 25 ) + end + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_trailsystem.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_trailsystem.lua new file mode 100644 index 0000000..5eb5942 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/cl_trailsystem.lua @@ -0,0 +1,147 @@ + +ENT.TrailMaterial = Material( "trails/smoke" ) +ENT.TrailRed = 255 +ENT.TrailGreen = 255 +ENT.TrailBlue = 255 +ENT.TrailAlpha = 100 + +function ENT:OnTrail( active, id ) +end + +function ENT:HandleTrail() + if not self.RegisteredTrailPositions then return end + + local FT = RealFrameTime() + + local pos = self:GetPos() + local vel = self:GetVelocity() + local vel_length = vel:Length() + + for id, data in pairs( self.RegisteredTrailPositions ) do + local cur_pos = self:LocalToWorld( data.pos ) + local cur_vel = (cur_pos - data.oldpos) / FT + + local cur_velL = math.abs( self:WorldToLocal( pos + cur_vel ).z ) + + if cur_velL > data.activation_speed and vel_length > data.min_flight_speed then + if not data.id then + data.id = self:StartTrail( data.pos, data.startsize, data.endsize, data.lifetime ) + self:OnTrail( true, data.id ) + end + else + if data.id then + self:OnTrail( false, data.id ) + self:FinishTrail( data.id ) + data.id = nil + end + end + + data.oldpos = cur_pos + end +end + +function ENT:RegisterTrail( Pos, StartSize, EndSize, LifeTime, min_flight_speed, activation_speed ) + if not istable( self.RegisteredTrailPositions ) then + self.RegisteredTrailPositions = {} + end + + local data = { + pos = Pos, + oldpos = self:LocalToWorld( Pos ), + startsize = StartSize, + endsize = EndSize, + lifetime = LifeTime, + min_flight_speed = min_flight_speed, + activation_speed = activation_speed, + } + + table.insert( self.RegisteredTrailPositions, data ) +end + +function ENT:StartTrail( Pos, StartSize, EndSize, LifeTime ) + if not LVS.ShowTraileffects then return end + + if not istable( self.TrailActive ) then + self.TrailActive = {} + end + + local ID = 1 + for _,_ in ipairs( self.TrailActive ) do + ID = ID + 1 + end + + self.TrailActive[ ID ] = { + lifetime = LifeTime, + start_size = StartSize, + end_size = EndSize, + pos = Pos, + active = true, + positions = {}, + } + + return ID +end + +function ENT:FinishTrail( ID ) + self.TrailActive[ ID ].active = false +end + +function ENT:DrawTrail() + local EntTable = self:GetTable() + + if not EntTable.TrailActive then return end + + local Time = CurTime() + + EntTable._NextTrail = EntTable._NextTrail or 0 + + local Set = EntTable._NextTrail < Time + + render.SetMaterial( EntTable.TrailMaterial ) + + for ID, data in pairs( EntTable.TrailActive ) do + + for pos_id, pos_data in pairs( data.positions ) do + if Time - pos_data.time > data.lifetime then + data.positions[ pos_id ] = nil + end + end + + if Set then + if data.active then + local cur_pos = { + time = Time, + pos = self:LocalToWorld( data.pos ), + } + + table.insert( data.positions, cur_pos ) + table.sort( data.positions, function( a, b ) return a.time > b.time end ) + end + end + + local num = #data.positions + + if num == 0 then + if not data.active then + EntTable.TrailActive[ ID ] = nil + end + + continue + end + + render.StartBeam( num ) + + for _, pos_data in ipairs( data.positions ) do + local Scale = (pos_data.time + data.lifetime - Time) / data.lifetime + local InvScale = 1 - Scale + + render.AddBeam( pos_data.pos, data.start_size * Scale + data.end_size * InvScale, pos_data.time * 50, Color( EntTable.TrailRed, EntTable.TrailGreen, EntTable.TrailBlue, EntTable.TrailAlpha * Scale ^ 2 ) ) + end + + render.EndBeam() + end + + if Set then + EntTable._NextTrail = Time + 0.025 + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/init.lua new file mode 100644 index 0000000..d3d4c77 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/init.lua @@ -0,0 +1,509 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "sh_weapons.lua" ) +AddCSLuaFile( "sh_velocity_changer.lua" ) +AddCSLuaFile( "sh_variables.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_effects.lua" ) +AddCSLuaFile( "cl_hud.lua" ) +AddCSLuaFile( "cl_optics.lua" ) +AddCSLuaFile( "cl_trailsystem.lua" ) +AddCSLuaFile( "cl_seatswitcher.lua" ) +AddCSLuaFile( "cl_boneposeparemeter.lua" ) +include("shared.lua") +include("sh_weapons.lua") +include("sh_velocity_changer.lua") +include("sh_variables.lua") +include("sv_ai.lua") +include("sv_cppi.lua") +include("sv_duping.lua") +include("sv_pod.lua") +include("sv_engine.lua") +include("sv_physics.lua") +include("sv_physics_damagesystem.lua") +include("sv_damagesystem.lua") +include("sv_shieldsystem.lua") +include("sv_doorsystem.lua") + +ENT.WaterLevelPreventStart = 1 +ENT.WaterLevelAutoStop = 2 +ENT.WaterLevelDestroyAI = 2 + +function ENT:SpawnFunction( ply, tr, ClassName ) + + if ply:InVehicle() then + local ent = ents.Create( ClassName ) + ent:StoreCPPI( ply ) + ent:SetPos( ply:GetPos() + Vector(0,0,100 + ent.SpawnNormalOffset) ) + ent:SetAngles( Angle(0, ply:EyeAngles().y + ent.SpawnAngleOffset, 0 ) ) + ent:Spawn() + ent:Activate() + + return ent + else + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:StoreCPPI( ply ) + ent:SetPos( tr.HitPos + tr.HitNormal * ent.SpawnNormalOffset ) + ent:SetAngles( Angle(0, ply:EyeAngles().y + ent.SpawnAngleOffset, 0 ) ) + ent:Spawn() + ent:Activate() + + return ent + end +end + +function ENT:Initialize() + self:SetModel( self.MDL ) + + self:PhysicsInit( SOLID_VPHYSICS, self.MassCenterOverride ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self:SetRenderMode( RENDERMODE_NORMAL ) + + -- this is so vj npcs can still see us + self:AddEFlags( EFL_DONTBLOCKLOS ) + + -- this is for our npc relationship system to work + self:AddFlags( FL_OBJECT ) + + local PObj = self:GetPhysicsObject() + + if not IsValid( PObj ) then + self:Remove() + + print("LVS: missing model. Vehicle terminated.") + + return + end + + PObj:SetMaterial( "default_silent" ) + PObj:EnableMotion( false ) + PObj:EnableDrag( false ) + + timer.Simple(0, function() + if not IsValid( self ) or not IsValid( PObj ) then print("LVS: ERROR couldn't initialize vehicle.") return end + + self:PostInitialize( PObj ) + end) + + if not istable( self.GibModels ) then return end + + for _, modelName in ipairs( self.GibModels ) do + util.PrecacheModel( modelName ) + end +end + +function ENT:PostInitialize( PObj ) + local SpawnSuccess, ErrorMsg = pcall( function() self:OnSpawn( PObj ) end ) + + if not SpawnSuccess then + ErrorNoHalt( "\n[ERROR] "..ErrorMsg.."\n\n" ) + end + + self:StartMotionController() + + self:AutoAI() + + self:CallOnRemove( "finish_weapons_on_delete", function( ent ) + ent:WeaponsFinish() + + for _, pod in pairs( ent:GetPassengerSeats() ) do + if not IsValid( pod ) then continue end + + local weapon = pod:lvsGetWeapon() + + if not IsValid( weapon ) or not weapon._activeWeapon then continue end + + local CurWeapon = self.WEAPONS[ weapon:GetPodIndex() ][ weapon._activeWeapon ] + + if not CurWeapon then continue end + + if CurWeapon.FinishAttack then + CurWeapon.FinishAttack( weapon ) + end + end + + ent:WeaponsOnRemove() + end) + + self:SetlvsReady( true ) + self:GetCrosshairFilterEnts() + + self:OnSpawnFinish( PObj ) +end + +function ENT:OnSpawnFinish( PObj ) + if GetConVar( "developer" ):GetInt() ~= 1 then + PObj:EnableMotion( true ) + end + + self:PhysWake() +end + +function ENT:OnSpawn( PObj ) +end + +function ENT:Think() + self:NextThink( CurTime() ) + + if not self:IsInitialized() then return true end + + self:HandleActive() + self:HandleStart() + self:PhysicsThink() + self:DamageThink() + self:WeaponsThink() + self:ShieldThink() + + if self:GetAI() then self:RunAI() end + + self:OnTick() + + return true +end + +function ENT:OnDriverChanged( Old, New, VehicleIsActive ) + self:OnPassengerChanged( Old, New, 1 ) +end + +function ENT:OnPassengerChanged( Old, New, PodIndex ) +end + +function ENT:OnSwitchSeat( ply, oldpod, newpod ) +end + +function ENT:OnTick() +end + +function ENT:OnRemoved() +end + +function ENT:OnRemove() + self:OnRemoved() +end + +function ENT:Lock() + for _, Handler in pairs( self:GetDoorHandlers() ) do + if not IsValid( Handler ) then continue end + + Handler:Close( ply ) + end + + if self:GetlvsLockedStatus() then return end + + self:SetlvsLockedStatus( true ) + self:EmitSound( "doors/latchlocked2.wav" ) +end + +function ENT:UnLock() + if not self:GetlvsLockedStatus() then return end + + self:SetlvsLockedStatus( false ) + self:EmitSound( "doors/latchunlocked1.wav" ) +end + +function ENT:IsUseAllowed( ply ) + if not IsValid( ply ) then return false end + + if (ply._lvsNextUse or 0) > CurTime() then return false end + + if self:GetlvsLockedStatus() or (LVS.TeamPassenger and ((self:GetAITEAM() ~= ply:lvsGetAITeam()) and ply:lvsGetAITeam() ~= 0 and self:GetAITEAM() ~= 0)) then + self:EmitSound( "doors/default_locked.wav" ) + + return false + end + + return true +end + +function ENT:Use( ply ) + if not self:IsUseAllowed( ply ) then return end + + if not istable( self._DoorHandlers ) then + self:SetPassenger( ply ) + + return + end + + if ply:GetMoveType() == MOVETYPE_NOCLIP then + ply._lvsNextExit = CurTime() + 0.5 + + self:SetPassenger( ply ) + + return + end + + if ply:KeyDown( IN_SPEED ) then return end + + local Handler = self:GetDoorHandler( ply ) + + if not IsValid( Handler ) then + if self:HasDoorSystem() and ply:GetMoveType() == MOVETYPE_WALK then + return + end + + self:SetPassenger( ply ) + + return + end + + local Pod = Handler:GetLinkedSeat() + + if not IsValid( Pod ) then Handler:Use( ply ) return end + + if not Handler:IsOpen() then Handler:Open( ply ) return end + + if Handler:IsOpen() then + Handler:Close( ply ) + else + Handler:OpenAndClose( ply ) + end + + if ply:KeyDown( IN_WALK ) then + + self:SetPassenger( ply ) + + return + end + + local DriverSeat = self:GetDriverSeat() + + if Pod ~= self:GetDriverSeat() then + if IsValid( Pod:GetDriver() ) then + self:SetPassenger( ply ) + else + ply:EnterVehicle( Pod ) + self:AlignView( ply ) + + hook.Run( "LVS.UpdateRelationship", self ) + end + + return + end + + if self:GetAI() then + self:SetPassenger( ply ) + + return + end + + if IsValid( Pod:GetDriver() ) then + self:SetPassenger( ply ) + + return + end + + if hook.Run( "LVS.CanPlayerDrive", ply, self ) ~= false then + ply:EnterVehicle( Pod ) + self:AlignView( ply ) + + hook.Run( "LVS.UpdateRelationship", self ) + else + hook.Run( "LVS.OnPlayerCannotDrive", ply, self ) + end +end + +function ENT:OnTakeDamage( dmginfo ) + self:CalcShieldDamage( dmginfo ) + self:CalcDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) + self:OnAITakeDamage( dmginfo ) + self:RemoveAllDecals() +end + +function ENT:OnMaintenance() +end + +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end + +function ENT:GetMissileOffset() + return self:OBBCenter() +end + +function ENT:RebuildCrosshairFilterEnts() + self.CrosshairFilterEnts = nil + + local CrosshairFilterEnts = table.Copy( self:GetCrosshairFilterEnts() ) + + for id, entity in pairs( CrosshairFilterEnts ) do + if not IsValid( entity ) or entity:GetNoDraw() then + CrosshairFilterEnts[ id ] = nil + end + end + + net.Start( "lvs_player_request_filter" ) + net.WriteEntity( self ) + net.WriteTable( CrosshairFilterEnts ) + net.Broadcast() +end + +function ENT:GetCrosshairFilterEnts() + if not self:IsInitialized() then return { self } end + + if not istable( self.CrosshairFilterEnts ) then + self.CrosshairFilterEnts = {} + + for _, Entity in pairs( constraint.GetAllConstrainedEntities( self ) ) do + if not IsValid( Entity ) then continue end + + table.insert( self.CrosshairFilterEnts , Entity ) + end + + for _, Parent in pairs( self.CrosshairFilterEnts ) do + for _, Child in pairs( Parent:GetChildren() ) do + if not IsValid( Child ) then continue end + + table.insert( self.CrosshairFilterEnts , Child ) + end + end + end + + return self.CrosshairFilterEnts +end + +function ENT:LVSFireBullet( data ) + data.Entity = self + data.Velocity = data.Velocity + self:GetVelocity():Length() + data.SrcEntity = self:WorldToLocal( data.Src ) + + LVS:FireBullet( data ) +end + +function ENT:AddSoundEmitter( pos, snd, snd_interior ) + local Emitter = ents.Create( "lvs_soundemitter" ) + + if not IsValid( Emitter ) then + self:Remove() + + print("LVS: Failed to create sound emitter entity. Vehicle terminated.") + + return + end + + Emitter:SetPos( self:LocalToWorld( pos ) ) + Emitter:SetAngles( self:GetAngles() ) + Emitter:Spawn() + Emitter:Activate() + Emitter:SetParent( self ) + Emitter:SetBase( self ) + + if snd and not snd_interior then + Emitter:SetSound( snd ) + Emitter:SetSoundInterior( snd ) + else + Emitter:SetSound( snd or "" ) + Emitter:SetSoundInterior( snd_interior ) + end + + self:DeleteOnRemove( Emitter ) + + self:TransferCPPI( Emitter ) + + return Emitter +end + +function ENT:AddFlameEmitter( target, attachment ) + if not IsValid( target ) then return end + + local FlameEmitter = ents.Create( "lvs_firestreamemitter" ) + FlameEmitter:AttachTo( target, attachment ) + FlameEmitter:Spawn() + FlameEmitter:Activate() + + return FlameEmitter +end + +function ENT:AddAmmoRack( pos, fxpos, ang, mins, maxs, target ) + local AmmoRack = ents.Create( "lvs_wheeldrive_ammorack" ) + + if not IsValid( AmmoRack ) then + self:Remove() + + print("LVS: Failed to create fueltank entity. Vehicle terminated.") + + return + end + + if not target then target = self end + + AmmoRack:SetPos( target:LocalToWorld( pos ) ) + AmmoRack:SetAngles( target:GetAngles() ) + AmmoRack:Spawn() + AmmoRack:Activate() + AmmoRack:SetParent( target ) + AmmoRack:SetBase( self ) + AmmoRack:SetEffectPosition( fxpos ) + + self:DeleteOnRemove( AmmoRack ) + + self:TransferCPPI( AmmoRack ) + + mins = mins or Vector(-30,-30,-30) + maxs = maxs or Vector(30,30,30) + + debugoverlay.BoxAngles( target:LocalToWorld( pos ), mins, maxs, target:LocalToWorldAngles( ang ), 15, Color( 255, 0, 0, 255 ) ) + + self:AddDS( { + pos = pos, + ang = ang, + mins = mins, + maxs = maxs, + entity = target, + Callback = function( tbl, ent, dmginfo ) + if not IsValid( AmmoRack ) then return end + + AmmoRack:TakeTransmittedDamage( dmginfo ) + + if AmmoRack:GetDestroyed() then return end + + local OriginalDamage = dmginfo:GetDamage() + + dmginfo:SetDamage( math.min( 2, OriginalDamage ) ) + end + } ) + + return AmmoRack +end + +function ENT:AddTrailerHitch( pos, hitchtype ) + if not hitchtype then + + hitchtype = LVS.HITCHTYPE_MALE + + end + + local TrailerHitch = ents.Create( "lvs_wheeldrive_trailerhitch" ) + + if not IsValid( TrailerHitch ) then + self:Remove() + + print("LVS: Failed to create trailerhitch entity. Vehicle terminated.") + + return + end + + TrailerHitch:SetPos( self:LocalToWorld( pos ) ) + TrailerHitch:SetAngles( self:GetAngles() ) + TrailerHitch:Spawn() + TrailerHitch:Activate() + TrailerHitch:SetParent( self ) + TrailerHitch:SetBase( self ) + TrailerHitch:SetHitchType( hitchtype ) + + self:TransferCPPI( TrailerHitch ) + + return TrailerHitch +end + +function ENT:OnCoupleChanged( targetVehicle, targetHitch, active ) +end + +function ENT:OnStartDrag( caller, activator ) +end + +function ENT:OnStopDrag( caller, activator ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_variables.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_variables.lua new file mode 100644 index 0000000..535fd6f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_variables.lua @@ -0,0 +1,40 @@ + +function ENT:HasQuickVar( name ) + name = "_smValue"..name + + return self[ name ] ~= nil +end + +function ENT:GetQuickVar( name ) + name = "_smValue"..name + + if not self[ name ] then return 0 end + + return self[ name ] +end + +if CLIENT then + function ENT:QuickLerp( name, target, rate ) + name = "_smValue"..name + + local EntTable = self:GetTable() + + if not EntTable[ name ] then EntTable[ name ] = 0 end + + EntTable[ name ] = EntTable[ name ] + (target - EntTable[ name ]) * math.min( RealFrameTime() * (rate or 10), 1 ) + + return EntTable[ name ] + end + + return +end + +function ENT:QuickLerp( name, target, rate ) + name = "_smValue"..name + + if not self[ name ] then self[ name ] = 0 end + + self[ name ] = self[ name ] + (target - self[ name ]) * math.min( FrameTime() * (rate or 10), 1 ) + + return self[ name ] +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_velocity_changer.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_velocity_changer.lua new file mode 100644 index 0000000..1c0d603 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_velocity_changer.lua @@ -0,0 +1,69 @@ + +if CLIENT then + hook.Add( "InitPostEntity", "!!!lvsUpdateMaxVelocity", function() + net.Start( "lvs_maxvelocity_updater" ) + net.SendToServer() + + hook.Remove( "InitPostEntity", "!!!lvsUpdateMaxVelocity" ) + end ) + + net.Receive( "lvs_maxvelocity_updater", function( len, ply ) + for _, data in pairs( net.ReadTable() ) do + local entity = data.ent + + if not IsValid( entity ) or not entity.LVS or not entity.MaxVelocity then continue end + + entity.MaxVelocity = data.vel + end + end ) + + return +end + +util.AddNetworkString( "lvs_maxvelocity_updater" ) + +local UpdatedVehicles = {} + +net.Receive( "lvs_maxvelocity_updater", function( len, ply ) + if ply._lvsAlreadyAskedForVelocityUpdate then return end + + ply._lvsAlreadyAskedForVelocityUpdate = true + + local TableSend = {} + + for id, data in pairs( UpdatedVehicles ) do + local entity = data.ent + + if IsValid( entity ) and entity.LVS and isfunction( entity.ChangeVelocity ) then + table.insert( TableSend, data ) + + continue + end + + UpdatedVehicles[ id ] = nil + end + + if #TableSend == 0 then return end + + net.Start( "lvs_maxvelocity_updater" ) + net.WriteTable( TableSend ) + net.Send( ply ) +end ) + +function ENT:ChangeVelocity( new ) + new = math.Clamp( new, 1, physenv.GetPerformanceSettings().MaxVelocity ) + + self.MaxVelocity = new + + timer.Simple(0, function() + if not IsValid( self ) then return end + + local data = { ent = self, vel = new } + + table.insert( UpdatedVehicles, data ) + + net.Start( "lvs_maxvelocity_updater" ) + net.WriteTable( {data} ) + net.Broadcast() + end) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_weapons.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_weapons.lua new file mode 100644 index 0000000..8f50743 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sh_weapons.lua @@ -0,0 +1,663 @@ + +ENT.WEAPONS = { + [1] = {}, +} + +function ENT:InitWeapons() +end + +function ENT:AddWeapon( weaponData, PodID ) + if not istable( weaponData ) then print("[LVS] couldn't register weapon") return end + + local data = table.Copy( weaponData ) + + if not PodID or PodID <= 1 then + PodID = 1 + end + + if not self.WEAPONS[ PodID ] then + self.WEAPONS[ PodID ] = {} + end + + local default = LVS:GetWeaponPreset( "DEFAULT" ) + + data.Icon = data.Icon or Material("lvs/weapons/bullet.png") + data.Ammo = data.Ammo or -1 + data.Delay = data.Delay or FrameTime() + + if isnumber( data.Clip ) and data.Clip > 0 then + data.HeatIsClip = true + + local ShootDelay = data.Delay + + local Clip = data.Clip + local ReloadSpeed = data.ReloadSpeed or 2 + + data.HeatRateUp = 1.00001 / (ShootDelay * Clip) + data.HeatRateDown = 1 / ReloadSpeed + data.OnReload = data.OnReload or default.OnReload + else + data.HeatRateUp = data.HeatRateUp or default.HeatRateUp + data.HeatRateDown = data.HeatRateDown or default.HeatRateDown + end + + data.Attack = data.Attack or default.Attack + data.StartAttack = data.StartAttack or default.StartAttack + data.FinishAttack = data.FinishAttack or default.FinishAttack + data.OnSelect = data.OnSelect or default.OnSelect + data.OnDeselect = data.OnDeselect or default.OnDeselect + data.OnThink = data.OnThink or default.OnThink + data.OnOverheat = data.OnOverheat or default.OnOverheat + data.OnRemove = data.OnRemove or default.OnRemove + data.UseableByAI = data.UseableByAI ~= false + + table.insert( self.WEAPONS[ PodID ], data ) +end + +function ENT:UpdateWeapon( PodID, WeaponID, weaponData ) + if not self.WEAPONS[ PodID ] then return end + + if not self.WEAPONS[ PodID ][ WeaponID ] then return end + + table.Merge( self.WEAPONS[ PodID ][ WeaponID ], weaponData ) +end + +function ENT:HasWeapon( ID ) + return istable( self.WEAPONS[1][ ID ] ) +end + +function ENT:AIHasWeapon( ID ) + local weapon = self.WEAPONS[1][ ID ] + if not istable( weapon ) then return false end + + return weapon.UseableByAI +end + +function ENT:GetActiveWeapon() + local SelectedID = self:GetSelectedWeapon() + local CurWeapon = self.WEAPONS[1][ SelectedID ] + + return CurWeapon, SelectedID +end + +function ENT:GetMaxAmmo() + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return -1 end + + return CurWeapon.Ammo or -1 +end + +function ENT:GetClip() + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return 0 end + + local HeatIncrement = (CurWeapon.HeatRateUp or 0.2) * math.max(CurWeapon.Delay or 0, FrameTime()) + + local Ammo = self:GetNWAmmo() + + if self:GetMaxAmmo() <= 0 and CurWeapon.Clip then + Ammo = CurWeapon.Clip + end + + return math.min( math.ceil( math.Round( (1 - self:GetNWHeat()) / HeatIncrement, 1 ) ), Ammo ) +end + +if SERVER then + function ENT:WeaponRestoreAmmo() + local AmmoIsSet = false + + for PodID, data in pairs( self.WEAPONS ) do + for id, weapon in pairs( data ) do + local MaxAmmo = weapon.Ammo or -1 + local CurAmmo = weapon._CurAmmo or -1 + + if CurAmmo == MaxAmmo then continue end + + self.WEAPONS[PodID][ id ]._CurAmmo = MaxAmmo + + AmmoIsSet = true + end + end + + if AmmoIsSet then + self:SetNWAmmo( self:GetAmmo() ) + + for _, pod in pairs( self:GetPassengerSeats() ) do + local weapon = pod:lvsGetWeapon() + + if not IsValid( weapon ) then continue end + + weapon:SetNWAmmo( weapon:GetAmmo() ) + end + end + + return AmmoIsSet + end + + function ENT:WeaponsOnRemove() + for _, data in pairs( self.WEAPONS ) do + for ID, Weapon in pairs( data ) do + if not Weapon.OnRemove then continue end + + Weapon.OnRemove( self ) + end + end + end + + function ENT:WeaponsFinish() + if not self._activeWeapon then return end + + local CurWeapon = self.WEAPONS[1][ self._activeWeapon ] + + if not CurWeapon then return end + + if CurWeapon.FinishAttack then + CurWeapon.FinishAttack( self ) + end + + self._activeWeapon = nil + self.OldAttack = false + end + + function ENT:GetAmmo() + if self:GetAI() then return self:GetMaxAmmo() end + + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return -1 end + + return CurWeapon._CurAmmo or self:GetMaxAmmo() + end + + function ENT:TakeAmmo( num ) + if self:GetMaxAmmo() <= 0 then return end + + local CurWeapon = self:GetActiveWeapon() + + CurWeapon._CurAmmo = math.max( self:GetAmmo() - (num or 1), 0 ) + + self:SetNWAmmo( CurWeapon._CurAmmo ) + end + + function ENT:GetHeat( weaponid ) + local CurWeapon + + if isnumber( weaponid ) and weaponid > 0 then + CurWeapon = self.WEAPONS[1][ weaponid ] + else + CurWeapon = self:GetActiveWeapon() + end + + if not CurWeapon then return 0 end + + return (CurWeapon._CurHeat or 0) + end + + function ENT:GetOverheated() + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return false end + + return CurWeapon.Overheated == true + end + + function ENT:SetOverheated( overheat ) + if self:GetOverheated() == overheat then return end + + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return end + + CurWeapon.Overheated = overheat + + self:SetNWOverheated( overheat ) + + if self:GetHeat() == 0 then return end + + if CurWeapon.OnOverheat then + CurWeapon.OnOverheat( self ) + end + end + + function ENT:SetHeat( heat ) + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return end + + heat = math.Clamp( heat, 0, 1 ) + + CurWeapon._CurHeat = heat + + if self:GetNWHeat() == heat then return end + + self:SetNWHeat( heat ) + end + + function ENT:CanAttack() + local CurWeapon = self:GetActiveWeapon() + + return (CurWeapon._NextFire or 0) < CurTime() + end + + function ENT:SetNextAttack( time ) + local CurWeapon = self:GetActiveWeapon() + + CurWeapon._NextFire = time + end + + function ENT:WeaponsShouldFire() + if self:GetAI() then return self._AIFireInput end + + local ply = self:GetDriver() + + if not IsValid( ply ) then return false end + + return ply:lvsKeyDown( "ATTACK" ) + end + + function ENT:WeaponsThink() + local EntTable = self:GetTable() + + local T = CurTime() + local FT = FrameTime() + local CurWeapon, SelectedID = self:GetActiveWeapon() + + for ID, Weapon in pairs( EntTable.WEAPONS[1] ) do + local IsActive = ID == SelectedID + + if Weapon.OnThink then Weapon.OnThink( self, IsActive ) end + + if IsActive then continue end + + if Weapon.HeatIsClip and not Weapon.Overheated and Weapon._CurHeat ~= 0 then + Weapon.Overheated = true + Weapon._CurHeat = 1 + + if Weapon.OnReload then Weapon.OnReload( self ) end + end + + -- cool all inactive weapons down + Weapon._CurHeat = Weapon._CurHeat and Weapon._CurHeat - math.min( Weapon._CurHeat, (Weapon.HeatRateDown or 0.25) * FT ) or 0 + end + + if not CurWeapon then return end + + local ShouldFire = self:WeaponsShouldFire() + local CurHeat = self:GetHeat() + + if self:GetOverheated() then + if CurHeat <= 0 then + self:SetOverheated( false ) + else + ShouldFire = false + end + else + if CurHeat >= 1 then + self:SetOverheated( true ) + ShouldFire = false + if CurWeapon.OnReload then CurWeapon.OnReload( self ) end + end + end + + if self:GetMaxAmmo() > 0 then + if self:GetAmmo() <= 0 then + ShouldFire = false + end + end + + if ShouldFire ~= EntTable.OldAttack then + EntTable.OldAttack = ShouldFire + + if ShouldFire then + if CurWeapon.StartAttack then + CurWeapon.StartAttack( self ) + end + EntTable._activeWeapon = SelectedID + else + self:WeaponsFinish() + end + end + + if ShouldFire then + if not self:CanAttack() then return end + + local ShootDelay = (CurWeapon.Delay or 0) + + self:SetNextAttack( T + ShootDelay ) + self:SetHeat( CurHeat + (CurWeapon.HeatRateUp or 0.2) * math.max(ShootDelay, FT) ) + + if not CurWeapon.Attack then return end + + if CurWeapon.Attack( self ) then + self:SetHeat( CurHeat - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) ) + self:SetNextAttack( T ) + end + + EntTable._lvsNextActiveWeaponCoolDown = T + 0.25 + else + if (EntTable._lvsNextActiveWeaponCoolDown or 0) > T then return end + + if CurWeapon.HeatIsClip and not CurWeapon.Overheated then + + self:SetHeat( self:GetHeat() ) + + return + end + + self:SetHeat( self:GetHeat() - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) ) + end + end + + function ENT:SelectWeapon( ID ) + if not isnumber( ID ) then return end + + if self.WEAPONS[1][ ID ] then + self:SetSelectedWeapon( ID ) + end + + local ply = self:GetDriver() + + if not IsValid( ply ) then return end + + net.Start( "lvs_select_weapon" ) + net.Send( ply ) + end + + function ENT:OnWeaponChanged( name, old, new) + if new == old then return end + + self:WeaponsFinish() + + local PrevWeapon = self.WEAPONS[1][ old ] + if PrevWeapon and PrevWeapon.OnDeselect then + PrevWeapon.OnDeselect( self, new ) + end + + local NextWeapon = self.WEAPONS[1][ new ] + if NextWeapon and NextWeapon.OnSelect then + NextWeapon.OnSelect( self, old ) + self:SetNWAmmo( NextWeapon._CurAmmo or NextWeapon.Ammo or -1 ) + self:SetNWOverheated( NextWeapon.Overheated == true ) + end + end + + return +end + +function ENT:DrawWeaponIcon( PodID, ID, x, y, width, height, IsSelected, IconColor ) +end + +function ENT:SelectWeapon( ID ) + if not isnumber( ID ) then return end + + net.Start( "lvs_select_weapon" ) + net.WriteInt( ID, 5 ) + net.WriteBool( false ) + net.SendToServer() +end + +function ENT:NextWeapon() + net.Start( "lvs_select_weapon" ) + net.WriteInt( 1, 5 ) + net.WriteBool( true ) + net.SendToServer() +end + +function ENT:PrevWeapon() + net.Start( "lvs_select_weapon" ) + net.WriteInt( -1, 5 ) + net.WriteBool( true ) + net.SendToServer() +end + +LVS:AddHudEditor( "WeaponSwitcher", ScrW() - 210, ScrH() - 165, 200, 68, 200, 68, "WEAPON SELECTOR", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintWeapons then return end + vehicle:LVSHudPaintWeapons( X, Y, W, H, ScrX, ScrY, ply ) + end +) + +LVS:AddHudEditor( "WeaponInfo", ScrW() - 230, ScrH() - 85, 220, 75, 220, 75, "WEAPON INFO", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintWeaponInfo then return end + + vehicle:LVSHudPaintWeaponInfo( X, Y, W, H, ScrX, ScrY, ply ) + end +) + +function ENT:GetAmmoID( ID ) + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local Base = ply:lvsGetWeaponHandler() + + if not IsValid( Base ) then return -1 end + + local selected = Base:GetSelectedWeapon() + local weapon = self.WEAPONS[ Base:GetPodIndex() ][ ID ] + + if ID == selected then + weapon._CurAmmo = Base:GetNWAmmo() + else + weapon._CurAmmo = weapon._CurAmmo or weapon.Ammo or -1 + end + + return weapon._CurAmmo +end + + +local Circles = { + [1] = {r = -1, col = Color(0,0,0,200)}, + [2] = {r = 0, col = Color(255,255,255,200)}, + [3] = {r = 1, col = Color(255,255,255,255)}, + [4] = {r = 2, col = Color(255,255,255,200)}, + [5] = {r = 3, col = Color(0,0,0,200)}, +} + +local function DrawCircle( X, Y, target_radius, heatvalue, overheated ) + local endang = 360 * heatvalue + + if endang == 0 then return end + + for i = 1, #Circles do + local data = Circles[ i ] + local radius = target_radius + data.r + local segmentdist = endang / ( math.pi * radius / 2 ) + + for a = 0, endang, segmentdist do + local r = data.col.r + local g = data.col.g * (1 - math.min(a / 270,1)) + local b = data.col.b * (1 - math.min(a / 90,1)) + + surface.SetDrawColor( r, g, b, data.col.a ) + + surface.DrawLine( X - math.sin( math.rad( a ) ) * radius, Y + math.cos( math.rad( a ) ) * radius, X - math.sin( math.rad( a + segmentdist ) ) * radius, Y + math.cos( math.rad( a + segmentdist ) ) * radius ) + end + end +end + +ENT.HeatMat = Material( "lvs/heat.png" ) +ENT.HeatIsClipMat = Material( "lvs/3d2dmats/refil.png" ) + +local color_white = color_white +local color_red = Color(255,0,0,255) + +function ENT:LVSHudPaintWeaponInfo( X, Y, w, h, ScrX, ScrY, ply ) + local Base = ply:lvsGetWeaponHandler() + + if not IsValid( Base ) then return end + + local ID = Base:GetSelectedWeapon() + + if not Base:HasWeapon( ID ) then return end + + local Weapon = Base:GetActiveWeapon() + local Heat = Base:GetNWHeat() + local OverHeated = Base:GetNWOverheated() + local Ammo = Base:GetNWAmmo() + + if Weapon and Weapon.HeatIsClip then + local Pod = ply:GetVehicle() + + if not IsValid( Pod ) then return end + + local PodID = Base:GetPodIndex() + + local FT = FrameTime() + local ShootDelay = math.max(Weapon.Delay or 0, FT) + local HeatIncrement = (Weapon.HeatRateUp or 0.2) * ShootDelay + + local Clip = Base:GetClip() + + if OverHeated then + Clip = 0 + + local hX = X + w - h * 0.5 + local hY = Y + h * 0.25 + h * 0.25 + + surface.SetMaterial( self.HeatIsClipMat ) + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawTexturedRectRotated( hX + 3, hY + 1, h, h, 0 ) + surface.SetDrawColor( 255, 0, 0, 255 ) + surface.DrawTexturedRectRotated( hX + 1, hY - 1, h, h, 0 ) + + DrawCircle( hX, hY, h * 0.35, Heat ) + end + + Ammo = Ammo - Clip + + local ColDyn = (Clip == 0 or OverHeated) and color_red or color_white + + draw.DrawText( "AMMO ", "LVS_FONT", X + 72, Y + 35, ColDyn, TEXT_ALIGN_RIGHT ) + + draw.DrawText( Clip, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, ColDyn, TEXT_ALIGN_LEFT ) + + if Base:GetMaxAmmo() <= 0 then return end + + local ColDyn2 = Ammo <= Weapon.Clip and color_red or color_white + + X = X + math.max( (#string.Explode( "", Clip ) - 1) * 18, 0 ) + + draw.DrawText( "/", "LVS_FONT_HUD_LARGE", X + 96, Y + 30, ColDyn2, TEXT_ALIGN_LEFT ) + + draw.DrawText( Ammo, "LVS_FONT", X + 110, Y + 40, ColDyn2, TEXT_ALIGN_LEFT ) + + return + end + + local hX = X + w - h * 0.5 + local hY = Y + h * 0.25 + h * 0.25 + local hAng = math.cos( CurTime() * 50 ) * 5 * (OverHeated and 1 or Heat ^ 2) + + surface.SetMaterial( self.HeatMat ) + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawTexturedRectRotated( hX + 4, hY + 1, h * 0.5, h * 0.5, hAng ) + + if OverHeated then + surface.SetDrawColor( 255, 0, 0, 255 ) + else + surface.SetDrawColor( 255, 255 * (1 - Heat), 255 * math.max(1 - Heat * 1.5,0), 255 ) + end + + surface.DrawTexturedRectRotated( hX + 2, hY - 1, h * 0.5, h * 0.5, hAng ) + + DrawCircle( hX, hY, h * 0.35, Heat ) + + if Base:GetMaxAmmo() <= 0 then return end + + draw.DrawText( "AMMO ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( Ammo, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT ) +end + +function ENT:LVSHudPaintWeapons( X, Y, w, h, ScrX, ScrY, ply ) + local EntTable = self:GetTable() + + local Base = ply:lvsGetWeaponHandler() + + if not IsValid( Base ) then return end + + local Pod = ply:GetVehicle() + + if not IsValid( Pod ) then return end + + local PodID = Base:GetPodIndex() + + local num = #self.WEAPONS[ PodID ] + + if num <= 1 then return end + + local CenterY = (Y + h * 0.5) + local CenterX = (X + w * 0.5) + + local FlatSelector = CenterX > ScrX * 0.333 and CenterX < ScrX * 0.666 + + local T = CurTime() + local FT = RealFrameTime() + + local gap = 4 + local SizeY = h - gap + + local Selected = Base:GetSelectedWeapon() + if Selected ~= EntTable._OldSelected then + EntTable._OldSelected = Selected + Pod._SelectActiveTime = T + 2 + end + + local tAlpha = (Pod._SelectActiveTime or 0) > T and 1 or 0 + local tAlphaRate = FT * 15 + + EntTable.smAlphaSW = EntTable.smAlphaSW and (EntTable.smAlphaSW + math.Clamp(tAlpha - EntTable.smAlphaSW,-tAlphaRate,tAlphaRate)) or 0 + + if EntTable.smAlphaSW > 0.95 then + EntTable._DisplaySelected = Selected + else + EntTable._DisplaySelected = EntTable._DisplaySelected or Selected + end + + local A255 = 255 * EntTable.smAlphaSW + local A150 = 150 * EntTable.smAlphaSW + + local Col = Color(0,0,0,A150) + local ColSelect = Color(255,255,255,A150) + + local SwapY = 0 + + if Y < (ScrY * 0.5 - h * 0.5) then + SwapY = 1 + end + + for ID = 1, num do + local IsSelected = EntTable._DisplaySelected == ID + local n = num - ID + local xPos = FlatSelector and X + (w + gap) * (ID - 1) - ((w + gap) * 0.5 * num - w * 0.5) or X + local yPos = FlatSelector and Y - h * math.min(SwapY,0) or Y - h * n + (num - 1) * h * SwapY + + draw.RoundedBox(5, xPos, yPos, w, SizeY, IsSelected and ColSelect or Col ) + + if IsSelected then + surface.SetDrawColor( 0, 0, 0, A255 ) + else + surface.SetDrawColor( 255, 255, 255, A255 ) + end + + if isbool( EntTable.WEAPONS[PodID][ID].Icon ) then + local col = IsSelected and Color(255,255,255,A255) or Color(0,0,0,A255) + self:DrawWeaponIcon( PodID, ID, xPos, yPos, SizeY * 2, SizeY, IsSelected, col ) + else + surface.SetMaterial( self.WEAPONS[PodID][ID].Icon ) + surface.DrawTexturedRect( xPos, yPos, SizeY * 2, SizeY ) + end + + local ammo = self:GetAmmoID( ID ) + + if ammo > -1 then + draw.DrawText( ammo, "LVS_FONT_HUD", xPos + w - 10, yPos + SizeY * 0.5 - 10, IsSelected and Color(0,0,0,A255) or Color(255,255,255,A255), TEXT_ALIGN_RIGHT ) + else + draw.DrawText( "O", "LVS_FONT_HUD", xPos + w - 19, yPos + SizeY * 0.5 - 10, IsSelected and Color(0,0,0,A255) or Color(255,255,255,A255), TEXT_ALIGN_RIGHT ) + draw.DrawText( "O", "LVS_FONT_HUD", xPos + w - 10, yPos + SizeY * 0.5 - 10, IsSelected and Color(0,0,0,A255) or Color(255,255,255,A255), TEXT_ALIGN_RIGHT ) + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/shared.lua new file mode 100644 index 0000000..92abf65 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/shared.lua @@ -0,0 +1,352 @@ +ENT.Type = "anim" + +ENT.PrintName = "LBaseEntity" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.VJ_ID_Destructible = true + +ENT.AutomaticFrameAdvance = true +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.Editable = true + +ENT.LVS = true + +ENT.MDL = "models/props_c17/trappropeller_engine.mdl" + +ENT.AITEAM = 0 + +ENT.MaxHealth = 100 +ENT.MaxShield = 0 + +ENT.SpawnNormalOffset = 15 +ENT.SpawnAngleOffset = 0 + +ENT.HitGroundLength = 10 + +ENT.lvsDisableZoom = true + +function ENT:AddDT( type, name, data ) + if not self.DTlist then self.DTlist = {} end + + if self.DTlist[ type ] then + self.DTlist[ type ] = self.DTlist[ type ] + 1 + else + self.DTlist[ type ] = 0 + end + + self:NetworkVar( type, self.DTlist[ type ], name, data ) +end + +function ENT:CreateBaseDT() + local InitWeaponsSuccess, ErrorMsg = pcall( function() self:InitWeapons() end ) + + if not InitWeaponsSuccess then + ErrorNoHalt( "\n[ERROR] "..ErrorMsg.."\n\n" ) + end + + self:AddDT( "Entity", "Driver" ) + self:AddDT( "Entity", "DriverSeat" ) + + self:AddDT( "Bool", "Active" ) + self:AddDT( "Bool", "EngineActive" ) + self:AddDT( "Bool", "AI", { KeyName = "aicontrolled", Edit = { type = "Boolean", order = 1, category = "AI"} } ) + + local ShowAIGunnerInMenu = false + + if istable( self.WEAPONS ) then + for id, _ in pairs( self.WEAPONS ) do + if id == 1 then continue end + + ShowAIGunnerInMenu = true + + break + end + end + + if ShowAIGunnerInMenu then + self:AddDT( "Bool", "AIGunners", { KeyName = "aigunners", Edit = { type = "Boolean", order = 2, category = "AI"} } ) + else + self:AddDT( "Bool", "AIGunners" ) + end + + self:AddDT( "Bool", "lvsLockedStatus" ) + self:AddDT( "Bool", "lvsReady" ) + self:AddDT( "Bool", "NWOverheated" ) + + self:AddDT( "Int", "AITEAM", { KeyName = "aiteam", Edit = { type = "Int", order = 2,min = 0, max = 3, category = "AI"} } ) + self:AddDT( "Int", "SelectedWeapon" ) + self:AddDT( "Int", "NWAmmo" ) + + self:AddDT( "Float", "HP", { KeyName = "health", Edit = { type = "Float", order = 2,min = 0, max = self.MaxHealth, category = "Misc"} } ) + self:AddDT( "Float", "Shield" ) + self:AddDT( "Float", "NWHeat" ) + + self:TurretSystemDT() + self:OnSetupDataTables() + + if SERVER then + self:NetworkVarNotify( "AI", self.OnToggleAI ) + self:NetworkVarNotify( "HP", self.PDSHealthValueChanged ) + self:NetworkVarNotify( "SelectedWeapon", self.OnWeaponChanged ) + + self:SetAITEAM( self.AITEAM ) + self:SetHP( self.MaxHealth ) + self:SetShield( self.MaxShield ) + self:SetSelectedWeapon( 1 ) + end +end + +function ENT:TurretSystemDT() +end + +function ENT:SetupDataTables() + self:CreateBaseDT() +end + +function ENT:OnSetupDataTables() +end + +function ENT:CalcMainActivity( ply ) +end + +function ENT:GetPlayerBoneManipulation( ply, PodID ) + return self.PlayerBoneManipulate[ PodID ] or {} +end + +function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed ) + ply:SetPlaybackRate( 1 ) + + if CLIENT then + GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + end + + return false +end + +function ENT:StartCommand( ply, cmd ) +end + +function ENT:HitGround() + local trace = util.TraceLine( { + start = self:LocalToWorld( self:OBBCenter() ), + endpos = self:LocalToWorld( Vector(0,0,self:OBBMins().z - self.HitGroundLength) ), + filter = self:GetCrosshairFilterEnts() + } ) + + return trace.Hit +end + +function ENT:Sign( n ) + if n > 0 then return 1 end + + if n < 0 then return -1 end + + return 0 +end + +function ENT:VectorSubtractNormal( Normal, Velocity ) + local VelForward = Velocity:GetNormalized() + + local Ax = math.acos( math.Clamp( Normal:Dot( VelForward ) ,-1,1) ) + + local Fx = math.cos( Ax ) * Velocity:Length() + + local NewVelocity = Velocity - Normal * math.abs( Fx ) + + return NewVelocity +end + +function ENT:VectorSplitNormal( Normal, Velocity ) + return math.cos( math.acos( math.Clamp( Normal:Dot( Velocity:GetNormalized() ) ,-1,1) ) ) * Velocity:Length() +end + +function ENT:AngleBetweenNormal( Dir1, Dir2 ) + return math.deg( math.acos( math.Clamp( Dir1:Dot( Dir2 ) ,-1,1) ) ) +end + +function ENT:GetMaxShield() + return self.MaxShield +end + +function ENT:GetShieldPercent() + return self:GetShield() / self:GetMaxShield() +end + +function ENT:GetMaxHP() + return self.MaxHealth +end + +function ENT:IsInitialized() + if not self.GetlvsReady then return false end -- in case this is called BEFORE setupdatatables + + return self:GetlvsReady() +end + +function ENT:GetWeaponHandler( num ) + if num == 1 then return self end + + local pod = self:GetPassengerSeat( num ) + + if not IsValid( pod ) then return NULL end + + return pod:lvsGetWeapon() +end + +function ENT:GetPassengerSeat( num ) + if num == 1 then + return self:GetDriverSeat() + else + for _, Pod in pairs( self:GetPassengerSeats() ) do + + if not IsValid( Pod ) then continue end + + local id = Pod:lvsGetPodIndex() + + if id == -1 then continue end + + if id == num then + return Pod + end + end + + return NULL + end +end + +function ENT:GetPassengerSeats() + if not self:IsInitialized() then return {} end + + if not istable( self.pSeats ) then + self.pSeats = {} + + local DriverSeat = self:GetDriverSeat() + + for _, v in pairs( self:GetChildren() ) do + if v ~= DriverSeat and v:GetClass():lower() == "prop_vehicle_prisoner_pod" then + table.insert( self.pSeats, v ) + end + end + end + + return self.pSeats +end + +function ENT:HasActiveSoundEmitters() + local active = false + + for _, emitter in ipairs( self:GetChildren() ) do + if emitter:GetClass() ~= "lvs_soundemitter" then continue end + + if not IsValid( emitter ) or not emitter.GetActive or not emitter.GetActiveVisible then continue end + + if emitter:GetActive() and emitter:GetActiveVisible() then + active = true + + break + end + end + + return active +end + +function ENT:GetPassenger( num ) + if num == 1 then + return self:GetDriver() + else + for _, Pod in pairs( self:GetPassengerSeats() ) do + + if not IsValid( Pod ) then + return NULL + end + + local id = Pod:lvsGetPodIndex() + + if id == -1 then continue end + + if id == num then + return Pod:GetDriver() + end + end + + return NULL + end +end + +function ENT:GetEveryone() + local plys = {} + + local Pilot = self:GetDriver() + if IsValid( Pilot ) then + table.insert( plys, Pilot ) + end + + for _, Pod in pairs( self:GetPassengerSeats() ) do + if not IsValid( Pod ) then continue end + + local ply = Pod:GetDriver() + + if not IsValid( ply ) then continue end + + table.insert( plys, ply ) + end + + return plys +end + +function ENT:GetPodIndex() + return 1 +end + +function ENT:PlayAnimation( animation, playbackrate ) + playbackrate = playbackrate or 1 + + local sequence = self:LookupSequence( animation ) + + self:ResetSequence( sequence ) + self:SetPlaybackRate( playbackrate ) + self:SetSequence( sequence ) +end + +function ENT:GetVehicle() + return self +end + +function ENT:GetVehicleType() + return "LBaseEntity" +end + +function ENT:GetBoneInfo( BoneName ) + local BoneID = self:LookupBone( BoneName ) + local numHitBoxSets = self:GetHitboxSetCount() + + if not BoneID then + goto SkipLoop + end + + for hboxset = 0, numHitBoxSets - 1 do + local numHitBoxes = self:GetHitBoxCount( hboxset ) + + for hitbox=0, numHitBoxes - 1 do + local bone = self:GetHitBoxBone( hitbox, hboxset ) + local name = self:GetBoneName( bone ) + + if BoneName ~= name then continue end + + local mins, maxs = self:GetHitBoxBounds( hitbox, hboxset ) + local pos, ang = self:GetBonePosition( BoneID ) + + return self:WorldToLocal( pos ), self:WorldToLocalAngles( ang ), mins, maxs + end + end + + :: SkipLoop :: + + return vector_origin, angle_zero, vector_origin, vector_origin +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_ai.lua new file mode 100644 index 0000000..d0efe4b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_ai.lua @@ -0,0 +1,230 @@ + +function ENT:RunAI() +end + +function ENT:AutoAI() + if not IsValid( self._OwnerEntLVS ) then return end + + if self._OwnerEntLVS:InVehicle() then + if self._OwnerEntLVS:IsAdmin() then + self:SetAI( true ) + end + end +end + +function ENT:OnCreateAI() +end + +function ENT:OnRemoveAI() +end + +function ENT:OnToggleAI( name, old, new ) + if new == old then return end + + if not self:IsInitialized() then + timer.Simple( FrameTime(), function() + if not IsValid( self ) then return end + + self:OnToggleAI( name, old, new ) + end ) + + return + end + + self:SetAIGunners( new ) + + if new == true then + local Driver = self:GetDriver() + + if IsValid( Driver ) then + Driver:ExitVehicle() + end + + self:SetActive( true ) + self:OnCreateAI() + + hook.Run( "LVS.UpdateRelationship", self ) + else + self:SetActive( false ) + self:OnRemoveAI() + end +end + +function ENT:OnAITakeDamage( dmginfo ) +end + +function ENT:AITargetInFront( ent, range ) + if not IsValid( ent ) then return false end + + if not range then range = 45 end + + if range >= 180 then return true end + + local DirToTarget = (ent:GetPos() - self:GetPos()):GetNormalized() + + local InFront = math.deg( math.acos( math.Clamp( self:GetForward():Dot( DirToTarget ) ,-1,1) ) ) < range + + return InFront +end + +function ENT:AICanSee( otherEnt ) + if not IsValid( otherEnt ) then return false end + + local PhysObj = otherEnt:GetPhysicsObject() + + if IsValid( PhysObj ) then + local trace = { + start = self:LocalToWorld( self:OBBCenter() ), + endpos = otherEnt:LocalToWorld( PhysObj:GetMassCenter() ), + filter = self:GetCrosshairFilterEnts(), + } + + return util.TraceLine( trace ).Entity == otherEnt + end + + local trace = { + start = self:LocalToWorld( self:OBBCenter() ), + endpos = otherEnt:LocalToWorld( otherEnt:OBBCenter() ), + filter = self:GetCrosshairFilterEnts(), + } + + return util.TraceLine( trace ).Entity == otherEnt +end + +function ENT:AIGetTarget( viewcone ) + if (self._lvsNextAICheck or 0) > CurTime() then return self._LastAITarget end + + self._lvsNextAICheck = CurTime() + 2 + + local MyPos = self:GetPos() + local MyTeam = self:GetAITEAM() + + if MyTeam == 0 then self._LastAITarget = NULL return NULL end + + local ClosestTarget = NULL + local TargetDistance = 60000 + + if not LVS.IgnorePlayers then + for _, ply in pairs( player.GetAll() ) do + if not ply:Alive() then continue end + + if ply:IsFlagSet( FL_NOTARGET ) then continue end + + local Dist = (ply:GetPos() - MyPos):Length() + + if Dist > TargetDistance then continue end + + local Veh = ply:lvsGetVehicle() + + if IsValid( Veh ) then + if self:AICanSee( Veh ) and Veh ~= self then + local HisTeam = Veh:GetAITEAM() + + if HisTeam == 0 then continue end + + if self.AISearchCone then + if not self:AITargetInFront( Veh, self.AISearchCone ) then continue end + end + + if HisTeam ~= MyTeam or HisTeam == 3 then + ClosestTarget = Veh + TargetDistance = Dist + end + end + else + local HisTeam = ply:lvsGetAITeam() + if not ply:IsLineOfSightClear( self ) or HisTeam == 0 then continue end + + if self.AISearchCone then + if not self:AITargetInFront( ply, self.AISearchCone ) then continue end + end + + if HisTeam ~= MyTeam or HisTeam == 3 then + ClosestTarget = ply + TargetDistance = Dist + end + end + end + end + + if not LVS.IgnoreNPCs then + for _, npc in pairs( LVS:GetNPCs() ) do + local HisTeam = LVS:GetNPCRelationship( npc:GetClass() ) + + if HisTeam == 0 or (HisTeam == MyTeam and HisTeam ~= 3) then continue end + + local Dist = (npc:GetPos() - MyPos):Length() + + if Dist > TargetDistance or not self:AICanSee( npc ) then continue end + + if self.AISearchCone then + if not self:AITargetInFront( npc, self.AISearchCone ) then continue end + end + + ClosestTarget = npc + TargetDistance = Dist + end + end + + for _, veh in pairs( LVS:GetVehicles() ) do + if veh:IsDestroyed() then continue end + + if veh == self then continue end + + local Dist = (veh:GetPos() - MyPos):Length() + + if Dist > TargetDistance or not self:AITargetInFront( veh, (viewcone or 100) ) then continue end + + local HisTeam = veh:GetAITEAM() + + if HisTeam == 0 then continue end + + if HisTeam == self:GetAITEAM() then + if HisTeam ~= 3 then continue end + end + + if self.AISearchCone then + if not self:AITargetInFront( veh, self.AISearchCone ) then continue end + end + + if self:AICanSee( veh ) then + ClosestTarget = veh + TargetDistance = Dist + end + end + + self._LastAITarget = ClosestTarget + + return ClosestTarget +end + +function ENT:IsEnemy( ent ) + if not IsValid( ent ) then return false end + + local HisTeam = 0 + + if ent:IsNPC() then + HisTeam = LVS:GetNPCRelationship( ent:GetClass() ) + end + + if ent:IsPlayer() then + if ent:IsFlagSet( FL_NOTARGET ) then return false end + + local veh = ent:lvsGetVehicle() + if IsValid( veh ) then + HisTeam = veh:GetAITEAM() + else + HisTeam = ent:lvsGetAITeam() + end + end + + if ent.LVS and ent.GetAITEAM then + HisTeam = ent:GetAITEAM() + end + + if HisTeam == 0 then return false end + + if HisTeam == 3 then return true end + + return HisTeam ~= self:GetAITEAM() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_cppi.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_cppi.lua new file mode 100644 index 0000000..bf59895 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_cppi.lua @@ -0,0 +1,18 @@ + +function ENT:StoreCPPI( owner ) + self._OwnerEntLVS = owner +end + +function ENT:TransferCPPI( target ) + if not IsEntity( target ) or not IsValid( target ) then return end + + if not CPPI then return end + + local Owner = self._OwnerEntLVS + + if not IsEntity( Owner ) then return end + + if IsValid( Owner ) then + target:CPPISetOwner( Owner ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_damagesystem.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_damagesystem.lua new file mode 100644 index 0000000..069f742 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_damagesystem.lua @@ -0,0 +1,426 @@ + +ENT._armorParts = {} +ENT._dmgParts = {} + +ENT.DSArmorDamageReduction = 0.1 +ENT.DSArmorDamageReductionType = DMG_BULLET + DMG_CLUB + +ENT.DSArmorIgnoreDamageType = DMG_SONIC +ENT.DSArmorIgnoreForce = 0 + +ENT.DSArmorBulletPenetrationAdd = 250 + +function ENT:AddDS( data ) + if not data then return end + + data.pos = data.pos or Vector(0,0,0) + data.ang = data.ang or Angle(0,0,0) + data.mins = data.mins or Vector(-1,-1,-1) + data.maxs = data.maxs or Vector(1,1,1) + data.entity = data.entity or self + data.Callback = data.Callback or function( tbl, ent, dmginfo ) end + + debugoverlay.BoxAngles( self:LocalToWorld( data.pos ), data.mins, data.maxs, data.entity:LocalToWorldAngles( data.ang ), 5, Color( 50, 0, 50, 150 ) ) + + table.insert( self._dmgParts, data ) +end + +function ENT:AddDSArmor( data ) + if not data then return end + + data.pos = data.pos or Vector(0,0,0) + data.ang = data.ang or Angle(0,0,0) + data.mins = data.mins or Vector(-1,-1,-1) + data.maxs = data.maxs or Vector(1,1,1) + data.entity = data.entity or self + data.Callback = data.Callback or function( tbl, ent, dmginfo ) end + + debugoverlay.BoxAngles( self:LocalToWorld( data.pos ), data.mins, data.maxs, data.entity:LocalToWorldAngles( data.ang ), 5, Color( 0, 50, 50, 150 ) ) + + table.insert( self._armorParts, data ) +end + +function ENT:CalcComponentDamage( dmginfo ) + local Len = self:BoundingRadius() + local dmgPos = dmginfo:GetDamagePosition() + local dmgDir = dmginfo:GetDamageForce():GetNormalized() + local dmgPenetration = dmgDir * self.DSArmorBulletPenetrationAdd + + debugoverlay.Line( dmgPos - dmgDir * self.DSArmorBulletPenetrationAdd, dmgPos + dmgPenetration, 4, Color( 0, 0, 255 ) ) + + local closestPart + local closestDist = Len * 2 + local HitDistance + + for index, part in ipairs( self._armorParts ) do + local target = part.entity + + if not IsValid( target ) then continue end + + local mins = part.mins + local maxs = part.maxs + local pos = target:LocalToWorld( part.pos ) + local ang = target:LocalToWorldAngles( part.ang ) + + local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( dmgPos, dmgPenetration, pos, ang, mins, maxs ) + + if HitPos then + debugoverlay.Cross( HitPos, 50, 4, Color( 255, 0, 255 ) ) + + local dist = (HitPos - dmgPos):Length() + + if closestDist > dist then + closestPart = part + closestDist = dist + HitDistance = (HitPos - dmgPos):Length() + end + end + end + + local lastPartDS + local closestPartDS + local closestDistDS = Len * 2 + for index, part in ipairs( self._dmgParts ) do + local target = part.entity + + if not IsValid( target ) then continue end + + local mins = part.mins + local maxs = part.maxs + local pos = target:LocalToWorld( part.pos ) + local ang = target:LocalToWorldAngles( part.ang ) + + local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( dmgPos, dmgPenetration, pos, ang, mins, maxs ) + + if HitPos and HitDistance then + lastPartDS = part + + if HitDistance < (HitPos - dmgPos):Length() then continue end + + closestPart = nil + closestDist = Len * 2 + end + + if not HitPos then continue end + + debugoverlay.Cross( HitPos, 50, 4, Color( 255, 0, 255 ) ) + + local dist = (HitPos - pos):Length() + + if closestDistDS > dist then + closestPartDS = part + closestDistDS = dist + end + end + + local Hit = false + for index, part in pairs( self._dmgParts ) do + local target = part.entity + + if not IsValid( target ) then continue end + + local mins = part.mins + local maxs = part.maxs + local pos = target:LocalToWorld( part.pos ) + local ang = target:LocalToWorldAngles( part.ang ) + + if part == closestPartDS then + Hit = true + part:Callback( self, dmginfo ) + debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 0, 0, 150 ) ) + end + end + + for index, part in pairs( self._armorParts ) do + local target = part.entity + + if not IsValid( target ) then continue end + + local mins = part.mins + local maxs = part.maxs + local pos = target:LocalToWorld( part.pos ) + local ang = target:LocalToWorldAngles( part.ang ) + + if part == closestPart then + if not part:Callback( self, dmginfo ) then + lastPartDS = nil + end + + debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 0, 150, 0, 150 ) ) + end + end + + if lastPartDS then + lastPartDS:Callback( self, dmginfo ) + + local target = lastPartDS.entity + + local mins = lastPartDS.mins + local maxs = lastPartDS.maxs + local pos = target:LocalToWorld( lastPartDS.pos ) + local ang = target:LocalToWorldAngles( lastPartDS.ang ) + + debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 0, 0, 150 ) ) + + Hit = false + end + + return Hit +end + +function ENT:CalcDamage( dmginfo ) + if dmginfo:IsDamageType( self.DSArmorIgnoreDamageType ) then return end + + if dmginfo:IsDamageType( self.DSArmorDamageReductionType ) then + if dmginfo:GetDamage() ~= 0 then + dmginfo:ScaleDamage( self.DSArmorDamageReduction ) + + dmginfo:SetDamage( math.max(dmginfo:GetDamage(),1) ) + end + end + + local IsFireDamage = dmginfo:IsDamageType( DMG_BURN ) + local IsCollisionDamage = dmginfo:GetDamageType() == (DMG_CRUSH + DMG_VEHICLE) + local CriticalHit = false + + if dmginfo:GetDamageForce():Length() < self.DSArmorIgnoreForce and not IsFireDamage then return end + + if not IsCollisionDamage then + CriticalHit = self:CalcComponentDamage( dmginfo ) + end + + local Damage = dmginfo:GetDamage() + + if Damage <= 0 then return end + + local CurHealth = self:GetHP() + + local NewHealth = math.Clamp( CurHealth - Damage, -self:GetMaxHP(), self:GetMaxHP() ) + + self:SetHP( NewHealth ) + + if self:IsDestroyed() then return end + + local Attacker = dmginfo:GetAttacker() + + if IsValid( Attacker ) and Attacker:IsPlayer() and not IsFireDamage then + net.Start( "lvs_hitmarker" ) + net.WriteBool( CriticalHit ) + net.Send( Attacker ) + end + + if Damage > 1 and not IsCollisionDamage and not IsFireDamage then + net.Start( "lvs_hurtmarker" ) + net.WriteFloat( math.min( Damage / 50, 1 ) ) + net.Send( self:GetEveryone() ) + end + + if NewHealth <= 0 then + self.FinalAttacker = dmginfo:GetAttacker() + self.FinalInflictor = dmginfo:GetInflictor() + + self:SetDestroyed( IsCollisionDamage ) + + self:ClearPDS() + + local Attacker = self.FinalAttacker + if IsValid( Attacker ) and Attacker:IsPlayer() then + net.Start( "lvs_killmarker" ) + net.Send( Attacker ) + end + + local ExplodeTime = self:PreExplode( math.Clamp((self:GetVelocity():Length() - 200) / 200,1.5,16) ) + + timer.Simple( ExplodeTime, function() + if not IsValid( self ) then return end + self:Explode() + end) + end +end + +function ENT:PreExplode( ExplodeTime ) + self:OnStartExplosion() + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return 0 end + + self:OnStartFireTrail( PhysObj, ExplodeTime ) + + return ExplodeTime +end + +function ENT:FindDS( PosToCheck, RadiusAdd ) + + if not isnumber( RadiusAdd ) then + RadiusAdd = 1 + end + + local closestPart + local closestDist = 50000 + + local ToCenter = (self:LocalToWorld( self:OBBCenter() ) - PosToCheck):GetNormalized() + + debugoverlay.Cross( PosToCheck, 50, 4, Color( 255, 255, 0 ) ) + + for _, tbl in ipairs( { self._armorParts, self._dmgParts } ) do + for index, part in ipairs( tbl ) do + local target = part.entity + + if not IsValid( target ) then continue end + + local mins = part.mins + local maxs = part.maxs + local pos = target:LocalToWorld( part.pos ) + local ang = target:LocalToWorldAngles( part.ang ) + + local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( PosToCheck, ToCenter * RadiusAdd, pos, ang, mins, maxs ) + + if HitPos then + local dist = (HitPos - PosToCheck):Length() + + if closestDist > dist then + closestPart = part + closestDist = dist + end + end + end + end + + if closestPart then + local target = closestPart.entity + local mins = closestPart.mins + local maxs = closestPart.maxs + local pos = target:LocalToWorld( closestPart.pos ) + local ang = target:LocalToWorldAngles( closestPart.ang ) + debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 255, 0, 150 ) ) + end + + return closestPart +end + +function ENT:DamageThink() + local EntTable = self:GetTable() + + if EntTable.MarkForDestruction then + self:Explode() + end + + if EntTable._pdsPartsAutoProgress then + if self:PDSThink( EntTable._pdsPartsAutoProgress ) then + EntTable._pdsPartsAutoProgress = nil + end + end + + if self:IsDestroyed() then + if self:GetVelocity():Length() < 800 then + self:Explode() + end + end +end + +function ENT:HurtPlayer( ply, dmg, attacker, inflictor ) + if not IsValid( ply ) then return end + + if not IsValid( attacker ) then + attacker = game.GetWorld() + end + + if not IsValid( inflictor ) then + inflictor = game.GetWorld() + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage( dmg ) + dmginfo:SetAttacker( attacker ) + dmginfo:SetInflictor( inflictor ) + dmginfo:SetDamageType( DMG_DIRECT ) + + ply:TakeDamageInfo( dmginfo ) +end + +function ENT:Explode() + if self.ExplodedAlready then return end + + self.ExplodedAlready = true + + local Driver = self:GetDriver() + + if IsValid( Driver ) then + self:HurtPlayer( Driver, Driver:Health() + Driver:Armor(), self.FinalAttacker, self.FinalInflictor ) + end + + if istable( self.pSeats ) then + for _, pSeat in pairs( self.pSeats ) do + if not IsValid( pSeat ) then continue end + + local psgr = pSeat:GetDriver() + if not IsValid( psgr ) then continue end + + self:HurtPlayer( psgr, psgr:Health() + psgr:Armor(), self.FinalAttacker, self.FinalInflictor ) + end + end + + self:OnFinishExplosion() + + self:Remove() +end + +function ENT:OnStartExplosion() + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + util.Effect( "lvs_explosion_nodebris", effectdata ) +end + +function ENT:OnFinishExplosion() + local ent = ents.Create( "lvs_destruction" ) + + if not IsValid( ent ) then return end + + ent:SetModel( self:GetModel() ) + ent:SetPos( self:GetPos() ) + ent:SetAngles( self:GetAngles() ) + ent.GibModels = self.GibModels + ent.Vel = self:GetVelocity() + ent:Spawn() + ent:Activate() +end + +function ENT:OnStartFireTrail( PhysObj, ExplodeTime ) + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetStart( PhysObj:GetMassCenter() ) + effectdata:SetEntity( self ) + effectdata:SetScale( (self.FireTrailScale or 1) ) + effectdata:SetMagnitude( ExplodeTime ) + util.Effect( "lvs_firetrail", effectdata ) +end + +function ENT:IsDestroyed() + return self.Destroyed == true +end + +function ENT:OnDestroyed() +end + +util.AddNetworkString( "lvs_vehicle_destroy" ) + +function ENT:SetDestroyed( SuppressOnDestroy ) + if self.Destroyed then return end + + self.Destroyed = true + + hook.Run( "LVS.OnVehicleDestroyed", self, self.FinalAttacker, self.FinalInflictor ) + + hook.Run( "LVS.UpdateRelationship", self ) + + if SuppressOnDestroy then return end + + self:OnDestroyed() + + net.Start("lvs_vehicle_destroy") + net.WriteEntity( self ) + net.SendPAS( self:GetPos() ) +end + +include("sv_damagesystem_armor.lua") diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_damagesystem_armor.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_damagesystem_armor.lua new file mode 100644 index 0000000..d37d50f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_damagesystem_armor.lua @@ -0,0 +1,105 @@ + +ENT.DSArmorBulletPenetrationType = DMG_AIRBOAT + DMG_SNIPER + +function ENT:AddArmor( pos, ang, mins, maxs, health, minforce, target ) + local Armor = ents.Create( "lvs_armor" ) + + if not IsValid( Armor ) then return end + + if not target then target = self end + + Armor:SetPos( target:LocalToWorld( pos ) ) + Armor:SetAngles( target:LocalToWorldAngles( ang ) ) + Armor:Spawn() + Armor:Activate() + Armor:SetParent( target ) + Armor:SetBase( self ) + Armor:SetMaxHP( health ) + Armor:SetHP( health ) + Armor:SetMins( mins ) + Armor:SetMaxs( maxs ) + + if isnumber( minforce ) then + Armor:SetIgnoreForce( minforce + self.DSArmorIgnoreForce ) + else + Armor:SetIgnoreForce( self.DSArmorIgnoreForce ) + end + + self:DeleteOnRemove( Armor ) + + self:TransferCPPI( Armor ) + + self:AddDSArmor( { + pos = pos, + ang = ang, + mins = mins, + maxs = maxs, + entity = target, + Callback = function( tbl, ent, dmginfo ) + if not IsValid( Armor ) or not dmginfo:IsDamageType( self.DSArmorBulletPenetrationType + DMG_BLAST ) then return true end + + local MaxHealth = self:GetMaxHP() + local MaxArmor = Armor:GetMaxHP() + local Damage = dmginfo:GetDamage() + + local ArmoredHealth = MaxHealth + MaxArmor + local NumShotsToKill = ArmoredHealth / Damage + + local ScaleDamage = math.Clamp( MaxHealth / (NumShotsToKill * Damage),0,1) + + local DidDamage = Armor:TakeTransmittedDamage( dmginfo ) + + if DidDamage then + if dmginfo:IsDamageType( DMG_PREVENT_PHYSICS_FORCE ) then + dmginfo:ScaleDamage( 0.05 ) + end + + local Attacker = dmginfo:GetAttacker() + + if IsValid( Attacker ) and Attacker:IsPlayer() then + local NonLethal = self:GetHP() > Damage * ScaleDamage + + if not ent._preventArmorMarker then + net.Start( "lvs_armormarker" ) + net.WriteBool( NonLethal ) + net.Send( Attacker ) + + if not NonLethal then + ent._preventArmorMarker = true + end + end + end + + dmginfo:ScaleDamage( ScaleDamage ) + else + dmginfo:ScaleDamage( 0 ) + end + + return true + end + } ) + + return Armor +end + +function ENT:OnArmorMaintenance() + local Repaired = false + + for _, part in pairs( self:GetCrosshairFilterEnts() ) do + if not IsValid( part ) then continue end + + if part:GetClass() ~= "lvs_armor" then continue end + + part:OnRepaired() + + if part:GetHP() ~= part:GetMaxHP() then + part:SetHP( part:GetMaxHP() ) + + if part:GetDestroyed() then part:SetDestroyed( false ) end + + Repaired = true + end + end + + return Repaired +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_doorsystem.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_doorsystem.lua new file mode 100644 index 0000000..cce58df --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_doorsystem.lua @@ -0,0 +1,108 @@ + +function ENT:AddDoorHandler( poseparameter, pos, ang, mins, maxs, openmins, openmaxs ) + if not isstring( poseparameter ) then return end + + if not isvector( pos ) or not isangle( ang ) or not isvector( mins ) or not isvector( maxs ) then + pos = vector_origin + ang = angle_zero + mins = self:OBBMins() + maxs = self:OBBMaxs() + end + + if not isvector( openmins ) then + openmins = mins + end + + if not isvector( openmaxs ) then + openmaxs = maxs + end + + local Handler = ents.Create( "lvs_base_doorhandler" ) + + if not IsValid( Handler ) then + return + end + + Handler:SetPos( self:LocalToWorld( pos ) ) + Handler:SetAngles( self:LocalToWorldAngles( ang ) ) + Handler:Spawn() + Handler:Activate() + Handler:SetParent( self ) + Handler:SetBase( self ) + Handler:SetMins( mins ) + Handler:SetMinsOpen( openmins ) + Handler:SetMinsClosed( mins ) + + Handler:SetMaxs( maxs ) + Handler:SetMaxsOpen( openmaxs ) + Handler:SetMaxsClosed( maxs ) + + Handler:SetPoseName( poseparameter ) + + self:DeleteOnRemove( Handler ) + + self:TransferCPPI( Handler ) + + if not istable( self._DoorHandlers ) then + self._DoorHandlers = {} + end + + table.insert( self._DoorHandlers, Handler ) + + return Handler +end + +function ENT:GetDoorHandler( ply ) + if not IsValid( ply ) or not istable( self._DoorHandlers ) then return NULL end + + local ShootPos = ply:GetShootPos() + local AimVector = ply:GetAimVector() + + local radius = 99999999999 + local target = NULL + + for _, doorHandler in pairs( self._DoorHandlers ) do + if not IsValid( doorHandler ) then continue end + + local boxOrigin = doorHandler:GetPos() + local boxAngles = doorHandler:GetAngles() + local boxMins = doorHandler:GetMins() + local boxMaxs = doorHandler:GetMaxs() + + local HitPos, _, _ = util.IntersectRayWithOBB( ShootPos, AimVector * doorHandler.UseRange, boxOrigin, boxAngles, boxMins, boxMaxs ) + + local InRange = isvector( HitPos ) + + if not InRange then continue end + + local dist = (ShootPos - HitPos):Length() + + if dist < radius then + target = doorHandler + radius = dist + end + end + + return target +end + +function ENT:GetDoorHandlers() + if istable( self._DoorHandlers ) then return self._DoorHandlers end + + return {} +end + +function ENT:HasDoorSystem() + local HasDoorSystem = false + + for _, Handler in pairs( self:GetDoorHandlers() ) do + if not IsValid( Handler ) then continue end + + if IsValid( Handler:GetLinkedSeat() ) then + HasDoorSystem = true + break + end + end + + return HasDoorSystem +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_duping.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_duping.lua new file mode 100644 index 0000000..8ad31dd --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_duping.lua @@ -0,0 +1,74 @@ + +-- attempt at fixing dupe support + +function ENT:PostEntityPaste( Player, Ent, CreatedEntities ) + + if isnumber( self._DuplicatorRestoreMaxHealthTo ) then + self.MaxHealth = self._DuplicatorRestoreMaxHealthTo + end + + if self.SetlvsReady then + self:SetlvsReady( false ) + end + + if self.SetActive then + self:SetActive( false ) + end + + if self.SetEngineActive then + self:SetEngineActive( false ) + end + + if not self.SetAI then return end + + if IsValid( Player ) and Player:IsAdmin() then return end + + self:SetAI( false ) +end + +function ENT:OnEntityCopyTableFinish( data ) + data.CrosshairFilterEnts = nil + data.pPodKeyIndex = nil + data.pSeats = nil + data.WEAPONS = nil + + -- everything with "_" at the start, usually temporary variables or variables used for timing. This will fix vehicles that are saved at a high curtime and then being spawned on a fresh server with low curtime + for id, _ in pairs( data ) do + if not string.StartsWith( id, "_" ) then continue end + + data[ id ] = nil + end + + data._DuplicatorRestoreMaxHealthTo = self:GetMaxHP() + + -- all functions need to go + for id, entry in pairs( data ) do + if not isfunction( entry ) then continue end + + data[ id ] = nil + end + + -- stuff below is things like constraints or DeleteOnRemove still referencing the old object. These need to go + data.OnDieFunctions = nil + data.Constraints = nil + data._children = nil +end + +hook.Add("AdvDupe_FinishPasting", "!!lvs_vehicle_dupefinished_initialize", function( data, entry ) + for _, v in pairs( data[ entry ].CreatedEntities ) do + + if not IsValid( v ) or not isentity( v ) or not v.LVS then continue end + + timer.Simple(1, function() + if not IsValid( v ) then return end + + if v.SetlvsReady and not v:GetlvsReady() then + v:SetlvsReady( true ) + end + + if v.RebuildCrosshairFilterEnts then + v:RebuildCrosshairFilterEnts() + end + end) + end +end ) diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_engine.lua new file mode 100644 index 0000000..7b94a46 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_engine.lua @@ -0,0 +1,51 @@ + +function ENT:HandleStart() + local Driver = self:GetDriver() + + if IsValid( Driver ) then + local KeyReload = Driver:lvsKeyDown( "ENGINE" ) + + if self.OldKeyReload ~= KeyReload then + self.OldKeyReload = KeyReload + + if KeyReload then + self:ToggleEngine() + end + end + end +end + +function ENT:ToggleEngine() + if self:GetEngineActive() then + self:StopEngine() + else + self:StartEngine() + end +end + +function ENT:IsEngineStartAllowed() + if hook.Run( "LVS.IsEngineStartAllowed", self ) == false then return false end + + if self:WaterLevel() > self.WaterLevelPreventStart then return false end + + return true +end + +function ENT:OnEngineActiveChanged( Active ) +end + +function ENT:StartEngine() + if self:GetEngineActive() or not self:IsEngineStartAllowed() then return end + + self:PhysWake() + + self:SetEngineActive( true ) + self:OnEngineActiveChanged( true ) +end + +function ENT:StopEngine() + if not self:GetEngineActive() then return end + + self:SetEngineActive( false ) + self:OnEngineActiveChanged( false ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics.lua new file mode 100644 index 0000000..55e062d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics.lua @@ -0,0 +1,177 @@ + +function ENT:GetWorldGravity() + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) or not PhysObj:IsGravityEnabled() then return 0 end + + return physenv.GetGravity():Length() +end + +function ENT:GetWorldUp() + local Gravity = physenv.GetGravity() + + if Gravity:Length() > 0 then + return -Gravity:GetNormalized() + else + return Vector(0,0,1) + end +end + +function ENT:PhysicsSimulate( phys, deltatime ) +end + +function ENT:PhysicsStopScape() + if self._lvsScrapeData then + if self._lvsScrapeData.sound then + self._lvsScrapeData.sound:Stop() + end + end + + self._lvsScrapeData = nil +end + +function ENT:PhysicsStartScrape( pos, dir ) + local startpos = self:LocalToWorld( pos ) + + local trace = util.TraceLine( { + start = startpos - dir * 5, + endpos = startpos + dir * 5, + filter = self:GetCrosshairFilterEnts() + } ) + + if trace.Hit then + local sound + + if self._lvsScrapeData and self._lvsScrapeData.sound then + sound = self._lvsScrapeData.sound + else + sound = CreateSound( self, "LVS.Physics.Scrape" ) + sound:PlayEx( 0, 90 + math.min( (self:GetVelocity():Length() / 2000) * 10,10) ) + end + + self._lvsScrapeData = { + dir = dir, + pos = pos, + sound = sound, + finishtime = CurTime() + 2, + } + + self:CallOnRemove( "stop_scraping", function( self ) + self:PhysicsStopScape() + end) + end +end + +function ENT:PhysicsThink() + local EntTable = self:GetTable() + + if not EntTable._lvsScrapeData then return end + + if EntTable._lvsScrapeData.finishtime < CurTime() then + self:PhysicsStopScape() + + return + end + + local startpos = self:LocalToWorld( EntTable._lvsScrapeData.pos ) + + local trace = util.TraceLine( { + start = startpos - EntTable._lvsScrapeData.dir, + endpos = startpos + EntTable._lvsScrapeData.dir * 5, + filter = self:GetCrosshairFilterEnts() + } ) + + local Vel = self:GetVelocity():Length() + + if trace.Hit and Vel > 25 then + local vol = math.min(math.max(Vel - 50,0) / 1000,1) + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos + trace.HitNormal ) + effectdata:SetNormal( trace.HitNormal ) + effectdata:SetMagnitude( vol ) + util.Effect( "lvs_physics_scrape", effectdata, true, true ) + + EntTable._lvsScrapeData.sound:ChangeVolume( vol, 0.1 ) + else + self:PhysicsStopScape() + end +end + +function ENT:TakeCollisionDamage( damage, attacker ) + if not IsValid( attacker ) then + attacker = game.GetWorld() + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage( damage ) + dmginfo:SetAttacker( attacker ) + dmginfo:SetInflictor( attacker ) + dmginfo:SetDamageType( DMG_CRUSH + DMG_VEHICLE ) -- this will communicate to the damage system to handle this kind of damage differently. + self:TakeDamageInfo( dmginfo ) +end + +function ENT:OnCollision( data, physobj ) + return false +end + +function ENT:OnSkyCollide( data, physobj ) + return true +end + +function ENT:PhysicsCollide( data, physobj ) + local HitEnt = data.HitEntity + + if not IsValid( HitEnt ) and util.GetSurfacePropName( data.TheirSurfaceProps ) == "default_silent" then + if self:OnSkyCollide( data, physobj ) then return end + end + + if self:IsDestroyed() then + self.MarkForDestruction = true + end + + if self:OnCollision( data, physobj ) then return end + + self:PhysicsStartScrape( self:WorldToLocal( data.HitPos ), data.HitNormal ) + + if IsValid( HitEnt ) then + if HitEnt:IsPlayer() or HitEnt:IsNPC() then + return + end + end + + if self:GetAI() and not self:IsPlayerHolding() then + if self:WaterLevel() >= self.WaterLevelDestroyAI then + self:SetDestroyed( true ) + self.MarkForDestruction = true + + return + end + end + + local VelDif = (data.OurOldVelocity - data.OurNewVelocity):Length() + + if VelDif > 60 and data.DeltaTime > 0.2 then + self:CalcPDS( data ) + + local effectdata = EffectData() + effectdata:SetOrigin( data.HitPos ) + effectdata:SetNormal( -data.HitNormal ) + util.Effect( "lvs_physics_impact", effectdata, true, true ) + + if VelDif > 1000 then + local damage = math.max( VelDif - 1000, 0 ) + + if not self:IsPlayerHolding() and damage > 0 then + self:EmitSound( "lvs/physics/impact_hard.wav", 85, 100 + math.random(-3,3), 1 ) + self:TakeCollisionDamage( VelDif, HitEnt ) + end + else + if VelDif > 800 then + self:EmitSound( "lvs/physics/impact_hard"..math.random(1,3)..".wav", 75, 100 + math.random(-3,3), 1 ) + else + self:EmitSound( "lvs/physics/impact_soft"..math.random(1,5)..".wav", 75, 100 + math.random(-6,6), math.min( VelDif / 600, 1 ) ) + end + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics_damagesystem.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics_damagesystem.lua new file mode 100644 index 0000000..9649478 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_physics_damagesystem.lua @@ -0,0 +1,267 @@ + +ENT._pdsParts = {} + +ENT.PDSDamageVelocity = 100 +ENT.PDSDamageMultiplier = 0.05 + +function ENT:ClearPDS() + if not istable( self._pdsParts ) then return end + + table.Empty( self._pdsParts ) +end + +function ENT:PDSHealthValueChanged( name, old, new) + if new == old then return end + + if not self:IsInitialized() or not istable( self._pdsParts ) or new ~= self:GetMaxHP() then return end + + self._pdsPartsAutoProgress = nil + + for _, part in pairs( self._pdsParts ) do + part:SetStage( 0 ) + + if not part._group or not part._subgroup then continue end + + self:SetBodygroup( part._group, part._subgroup ) + + part._group = nil + part._subgroup = nil + end +end + +local function DamagePart( ent, part, speed ) + if ent._pdsPartsAutoProgress and ent._pdsPartsAutoProgress.part == part then + ent._pdsPartsAutoProgress = nil + end + + if not speed then + speed = 0 + end + + local stage = part:GetStage() + 1 + + part:SetStage( stage ) + + local data = part:GetStageData() + + if isfunction( data.Callback ) then + data:Callback( ent, part, speed ) + end + + if istable( data.bodygroup ) then + for group, subgroup in pairs( data.bodygroup ) do + if not part._group or not part._subgroup then + part._group = group + part._subgroup = ent:GetBodygroup( group ) + end + + ent:SetBodygroup( group, subgroup ) + end + end + + if isstring( data.sound ) then + ent:EmitSound( data.sound, 75, 100, math.min(0.1 + speed / 700,1) ) + end + + if isnumber( data.maxvelocity ) then + ent._pdsPartsAutoProgress = { + part = part, + velocity = data.maxvelocity, + } + end + + if isstring( data.effect ) then + local effectdata = EffectData() + effectdata:SetOrigin( ent:LocalToWorld( part.pos ) ) + util.Effect( data.effect, effectdata, true, true ) + end + + if not istable( data.gib ) or not data.gib.mdl then return end + + timer.Simple(0, function() + if not IsValid( ent ) then return end + + local InvAttach = isstring( data.gib.target ) + + local pos + local ang + + if InvAttach then + pos = vector_origin + ang = angle_zero + else + if isvector( data.gib.pos ) and isangle( data.gib.ang ) then + pos = ent:LocalToWorld( data.gib.pos ) + ang = ent:LocalToWorldAngles( data.gib.ang ) + end + end + + local gib = ents.Create( "prop_physics" ) + gib:SetModel( data.gib.mdl ) + gib:SetPos( pos ) + gib:SetAngles( ang ) + gib:Spawn() + gib:Activate() + gib:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + gib:SetSkin( ent:GetSkin() ) + + if InvAttach then + local att = gib:GetAttachment( gib:LookupAttachment( data.gib.target ) ) + + if att then + local newpos = ent:LocalToWorld( -att.Pos ) + local newang = ent:LocalToWorldAngles( -att.Ang ) + + gib:SetPos( newpos ) + gib:SetAngles( newang ) + end + end + + gib:SetOwner( ent ) + gib:SetColor( ent:GetColor() ) + gib:SetRenderMode( RENDERMODE_TRANSALPHA ) + + ent:DeleteOnRemove( gib ) + ent:TransferCPPI( gib ) + + timer.Simple( 59.5, function() + if not IsValid( gib ) then return end + gib:SetRenderFX( kRenderFxFadeFast ) + end) + + timer.Simple( 60, function() + if not IsValid( gib ) then return end + gib:Remove() + end) + + local PhysObj = gib:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetVelocityInstantaneous( ent:GetVelocity() + Vector(0,0,250) ) + PhysObj:AddAngleVelocity( VectorRand() * 500 ) + end) +end + +function ENT:AddPDS( data ) + if not data then return end + + if self._pdsPartsID then + self._pdsPartsID = self._pdsPartsID + 1 + else + self._pdsPartsID = 1 + end + + data.pos = data.pos or Vector(0,0,0) + data.ang = data.ang or Angle(0,0,0) + data.mins = data.mins or Vector(-1,-1,-1) + data.maxs = data.maxs or Vector(1,1,1) + data.stages = data.stages or {} + data.GetStage = function( self ) + if not self._curstage then + self._curstage = 0 + end + + return self._curstage + end + data.SetStage = function( self, stage ) + self._curstage = stage + end + + data.GetStageData = function( self, stage ) + return self.stages[ self:GetStage() ] or {} + end + + debugoverlay.BoxAngles( self:LocalToWorld( data.pos ), data.mins, data.maxs, self:LocalToWorldAngles( data.ang ), 8, Color( 50, 50, 0, 150 ) ) + + self._pdsParts[ self._pdsPartsID ] = data + + if data.allow_damage then + local id = self._pdsPartsID + + self:AddDS( { + pos = data.pos, + ang = data.ang, + mins = data.mins, + maxs = data.maxs, + Callback = function( tbl, ent, dmginfo ) + if not IsValid( ent ) then return end + + local part = ent._pdsParts[ id ] + + if not part then return end + + DamagePart( ent, part, 1000 ) + end + } ) + end +end + +function ENT:FindPDS( PosToCheck, RadiusAdd ) + if not isnumber( RadiusAdd ) then + RadiusAdd = 1 + end + + if InfMap then + PosToCheck = InfMap.unlocalize_vector( PosToCheck, self.CHUNK_OFFSET ) + end + + local Parts = {} + + debugoverlay.Cross( PosToCheck, 50, 4, Color( 255, 255, 0 ) ) + + for index, part in ipairs( self._pdsParts ) do + local mins = part.mins + local maxs = part.maxs + local pos = self:LocalToWorld( part.pos ) + local ang = self:LocalToWorldAngles( part.ang ) + local dir = (pos - PosToCheck):GetNormalized() + + local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( PosToCheck, dir * RadiusAdd, pos, ang, mins, maxs ) + + if HitPos then + table.insert( Parts, part ) + end + end + + for _, closestPart in ipairs( Parts ) do + local mins = closestPart.mins + local maxs = closestPart.maxs + local pos = self:LocalToWorld( closestPart.pos ) + local ang = self:LocalToWorldAngles( closestPart.ang ) + debugoverlay.BoxAngles( pos, mins, maxs, ang, 1, Color( 255, 255, 0, 150 ) ) + end + + return Parts +end + +function ENT:CalcPDS( physdata ) + local VelDif = (physdata.OurOldVelocity - physdata.OurNewVelocity):Length() + + if VelDif < self.PDSDamageVelocity then return end + + local parts = self:FindPDS( physdata.HitPos, (VelDif - self.PDSDamageVelocity) * self.PDSDamageMultiplier ) + + if #parts == 0 then return end + + local HP = self:GetHP() + local MaxHP = self:GetMaxHP() + + if HP == MaxHP then + self:SetHP( math.max( MaxHP - 0.1, 1 ) ) + end + + for _, part in pairs( parts ) do + DamagePart( self, part, VelDif ) + end +end + +function ENT:PDSThink( data ) + local vel = self:GetVelocity():Length() + + if vel < data.velocity then return end + + DamagePart( self, data.part, vel ) + + return true +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_pod.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_pod.lua new file mode 100644 index 0000000..0e1455c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_pod.lua @@ -0,0 +1,199 @@ + +ENT.DriverActiveSound = "vehicles/atv_ammo_close.wav" +ENT.DriverInActiveSound = "vehicles/atv_ammo_open.wav" + +function ENT:AlignView( ply, SetZero ) + if not IsValid( ply ) then return end + + timer.Simple( 0, function() + if not IsValid( ply ) or not IsValid( self ) then return end + local Ang = Angle(0,90,0) + + if not SetZero then + Ang = self:GetAngles() + Ang.r = 0 + end + + ply:SetEyeAngles( Ang ) + end) +end + +function ENT:HandleActive() + local Pod = self:GetDriverSeat() + + if not IsValid( Pod ) then + self:SetActive( false ) + + return + end + + local Driver = Pod:GetDriver() + local Active = self:GetActive() + + if Driver ~= self:GetDriver() then + local NewDriver = Driver + local OldDriver = self:GetDriver() + local IsActive = IsValid( Driver ) + + self:SetDriver( Driver ) + self:SetActive( IsActive ) + + self:OnDriverChanged( OldDriver, NewDriver, IsActive ) + + if IsActive then + Driver:lvsBuildControls() + self:AlignView( Driver ) + + if self.DriverActiveSound then self:EmitSound( self.DriverActiveSound ) end + else + self:WeaponsFinish() + + if self.DriverInActiveSound then self:EmitSound( self.DriverInActiveSound ) end + end + end +end + +function ENT:SetPassenger( ply ) + if not IsValid( ply ) then return end + + local AI = self:GetAI() + local DriverSeat = self:GetDriverSeat() + local AllowedToBeDriver = hook.Run( "LVS.CanPlayerDrive", ply, self ) ~= false + + if IsValid( DriverSeat ) and not IsValid( DriverSeat:GetDriver() ) and not ply:KeyDown( IN_WALK ) and not AI and AllowedToBeDriver then + ply:EnterVehicle( DriverSeat ) + self:AlignView( ply ) + + hook.Run( "LVS.UpdateRelationship", self ) + else + local Seat = NULL + local Dist = 500000 + + for _, v in pairs( self:GetPassengerSeats() ) do + if not IsValid( v ) or IsValid( v:GetDriver() ) then continue end + if v:GetNWInt( "pPodIndex" ) == -1 then continue end + + local cDist = (v:GetPos() - ply:GetPos()):Length() + + if cDist < Dist then + Seat = v + Dist = cDist + end + end + + if IsValid( Seat ) then + ply:EnterVehicle( Seat ) + self:AlignView( ply, true ) + + hook.Run( "LVS.UpdateRelationship", self ) + else + if IsValid( DriverSeat ) then + if not IsValid( self:GetDriver() ) and not AI then + if AllowedToBeDriver then + ply:EnterVehicle( DriverSeat ) + self:AlignView( ply ) + + hook.Run( "LVS.UpdateRelationship", self ) + else + hook.Run( "LVS.OnPlayerCannotDrive", ply, self ) + end + end + end + end + end +end + +function ENT:AddDriverSeat( Pos, Ang ) + if IsValid( self:GetDriverSeat() ) then return self:GetDriverSeat() end + + local Pod = ents.Create( "prop_vehicle_prisoner_pod" ) + + if not IsValid( Pod ) then + self:Remove() + + print("LVS: Failed to create driverseat. Vehicle terminated.") + + return + else + self:SetDriverSeat( Pod ) + + local DSPhys = Pod:GetPhysicsObject() + + Pod:SetMoveType( MOVETYPE_NONE ) + Pod:SetModel( "models/nova/airboat_seat.mdl" ) + Pod:SetKeyValue( "vehiclescript","scripts/vehicles/prisoner_pod.txt" ) + Pod:SetKeyValue( "limitview", 0 ) + Pod:SetPos( self:LocalToWorld( Pos ) ) + Pod:SetAngles( self:LocalToWorldAngles( Ang ) ) + Pod:SetOwner( self ) + Pod:Spawn() + Pod:Activate() + Pod:SetParent( self ) + Pod:SetNotSolid( true ) + Pod:SetRenderMode( RENDERMODE_TRANSALPHA ) + Pod:DrawShadow( false ) + Pod.DoNotDuplicate = true + + Pod:lvsSetPodIndex( 1 ) + + Pod:PhysicsDestroy() + + debugoverlay.BoxAngles( Pod:GetPos(), Pod:OBBMins(), Pod:OBBMaxs(), Pod:GetAngles(), 5, Color( 255, 93, 0, 200 ) ) + + self:DeleteOnRemove( Pod ) + + self:TransferCPPI( Pod ) + end + + return Pod +end + +function ENT:AddPassengerSeat( Pos, Ang ) + if not isvector( Pos ) or not isangle( Ang ) then return NULL end + + local Pod = ents.Create( "prop_vehicle_prisoner_pod" ) + + if not IsValid( Pod ) then return NULL end + + Pod:SetMoveType( MOVETYPE_NONE ) + Pod:SetModel( "models/nova/airboat_seat.mdl" ) + Pod:SetKeyValue( "vehiclescript","scripts/vehicles/prisoner_pod.txt" ) + Pod:SetKeyValue( "limitview", 0 ) + Pod:SetPos( self:LocalToWorld( Pos ) ) + Pod:SetAngles( self:LocalToWorldAngles( Ang ) ) + Pod:SetOwner( self ) + Pod:Spawn() + Pod:Activate() + Pod:SetParent( self ) + Pod:SetNotSolid( true ) + Pod:SetRenderMode( RENDERMODE_TRANSALPHA ) + + debugoverlay.BoxAngles( Pod:GetPos(), Pod:OBBMins(), Pod:OBBMaxs(), Pod:GetAngles(), 5, Color( 100, 65, 127, 200 ) ) + + Pod:DrawShadow( false ) + Pod.DoNotDuplicate = true + + self.pPodKeyIndex = self.pPodKeyIndex and self.pPodKeyIndex + 1 or 2 + + if self.WEAPONS[ self.pPodKeyIndex ] then + local weapon = Pod:lvsAddWeapon( self.pPodKeyIndex ) + + if IsValid( weapon ) then + self:TransferCPPI( weapon ) + self:DeleteOnRemove( weapon ) + end + end + + Pod:lvsSetPodIndex( self.pPodKeyIndex ) + + Pod:PhysicsDestroy() + + self:DeleteOnRemove( Pod ) + self:TransferCPPI( Pod ) + + if not istable( self.pSeats ) then self.pSeats = {} end + + table.insert( self.pSeats, Pod ) + + return Pod +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_shieldsystem.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_shieldsystem.lua new file mode 100644 index 0000000..746bd80 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base/sv_shieldsystem.lua @@ -0,0 +1,80 @@ + +ENT.ShieldRechargeDelay = 5 +ENT.ShieldRechargeRate = 1 +ENT.ShieldBlockableTypes = { + [1] = DMG_BULLET, + [2] = DMG_AIRBOAT, + [3] = DMG_BUCKSHOT, + [4] = DMG_SNIPER, + [5] = DMG_BLAST, +} + +function ENT:CalcShieldDamage( dmginfo ) + local MaxShield = self:GetMaxShield() + + if MaxShield <= 0 then return end + + local DMG_ENUM = DMG_GENERIC + for _, ENUM in ipairs( self.ShieldBlockableTypes ) do + DMG_ENUM = DMG_ENUM + ENUM + end + + if not dmginfo:IsDamageType( DMG_ENUM ) then return end + + self:DelayNextShieldRecharge( self.ShieldRechargeDelay ) + + local DamageRemaining = self:TakeShieldDamage( dmginfo:GetDamage() ) + + dmginfo:SetDamage( DamageRemaining ) + + self:OnTakeShieldDamage( dmginfo ) +end + +function ENT:CanShieldRecharge() + return (self.NextShieldRecharge or 0) < CurTime() +end + +function ENT:DelayNextShieldRecharge( delay ) + self.NextShieldRecharge = CurTime() + delay +end + +function ENT:ShieldThink() + local MaxShield = self:GetMaxShield() + + if MaxShield <= 0 or self:GetShieldPercent() == 1 then return end + + if not self:CanShieldRecharge() then return end + + local Cur = self:GetShield() + local Rate = FrameTime() * 20 * self.ShieldRechargeRate + + self:SetShield( Cur + math.Clamp(MaxShield - Cur,-Rate,Rate) ) +end + +function ENT:TakeShieldDamage( damage ) + local cur = self:GetShield() + local sub = cur - damage + local new = math.Clamp( sub , 0, self:GetMaxShield() ) + + self:SetShield( new ) + + if sub < 0 then + return math.abs( sub ) + else + return 0 + end +end + +function ENT:OnTakeShieldDamage( dmginfo ) + if dmginfo:GetDamage() ~= 0 then return end + + local dmgNormal = -dmginfo:GetDamageForce():GetNormalized() + local dmgPos = dmginfo:GetDamagePosition() + + dmginfo:SetDamagePosition( dmgPos + dmgNormal * 250 * self:GetShieldPercent() ) + + local effectdata = EffectData() + effectdata:SetOrigin( dmginfo:GetDamagePosition() ) + effectdata:SetEntity( self ) + util.Effect( "lvs_shield_impact", effectdata ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_doorhandler.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_doorhandler.lua new file mode 100644 index 0000000..5719bec --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_doorhandler.lua @@ -0,0 +1,453 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.UseRange = 75 + +ENT._UseTargetAllowed = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Entity",1, "LinkedSeat" ) + + self:NetworkVar( "Bool",0, "Active" ) + + self:NetworkVar( "String",0, "PoseName" ) + + self:NetworkVar( "Vector",0, "Mins" ) + self:NetworkVar( "Vector",1, "Maxs" ) + + self:NetworkVar( "Float",0, "Rate" ) + self:NetworkVar( "Float",1, "RateExponent" ) + + self:NetworkVar( "Float",2, "PoseMin" ) + self:NetworkVar( "Float",3, "PoseMax" ) + + if SERVER then + self:SetRate( 10 ) + self:SetRateExponent( 2 ) + + self:SetPoseMax( 1 ) + end +end + +function ENT:IsServerSide() + local EntTable = self:GetTable() + + if isbool( EntTable._IsServerSide ) then return EntTable._IsServerSide end + + local PoseName = self:GetPoseName() + + if PoseName == "" then return false end + + local IsServerSide = string.StartsWith( PoseName, "^" ) + + EntTable._IsServerSide = IsServerSide + + return IsServerSide +end + +function ENT:IsOpen() + return self:GetActive() +end + +function ENT:InRange( ply, Range ) + local boxOrigin = self:GetPos() + local boxAngles = self:GetAngles() + local boxMins = self:GetMins() + local boxMaxs = self:GetMaxs() + + local HitPos, _, _ = util.IntersectRayWithOBB( ply:GetShootPos(), ply:GetAimVector() * Range, boxOrigin, boxAngles, boxMins, boxMaxs ) + + return isvector( HitPos ) +end + +if SERVER then + AccessorFunc(ENT, "soundopen", "SoundOpen", FORCE_STRING) + AccessorFunc(ENT, "soundclose", "SoundClose", FORCE_STRING) + + AccessorFunc(ENT, "maxsopen", "MaxsOpen", FORCE_VECTOR) + AccessorFunc(ENT, "minsopen", "MinsOpen", FORCE_VECTOR) + + AccessorFunc(ENT, "maxsclosed", "MaxsClosed", FORCE_VECTOR) + AccessorFunc(ENT, "minsclosed", "MinsClosed", FORCE_VECTOR) + + + util.AddNetworkString( "lvs_doorhandler_interact" ) + + net.Receive( "lvs_doorhandler_interact", function( length, ply ) + if not IsValid( ply ) then return end + + local ent = net.ReadEntity() + + if not IsValid( ent ) or not ent._UseTargetAllowed or not ent.UseRange or ply:InVehicle() then return end + + local Range = ent.UseRange * 2 + + if (ply:GetPos() - ent:GetPos()):Length() > Range then return end + + if not ent:InRange( ply, Range ) then return end + + ent:Use( ply, ply ) + end) + + function ENT:LinkToSeat( ent ) + if not IsValid( ent ) or not ent:IsVehicle() then + + ErrorNoHalt( "[LVS] Couldn't link seat to doorsystem. Entity expected, got "..tostring( ent ).."\n" ) + + return + end + + self:SetLinkedSeat( ent ) + end + + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:SetUseType( SIMPLE_USE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 15, 5, Color( 255, 223, 127 ) ) + end + + function ENT:Use( ply ) + if not IsValid( ply ) then return end + + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + if not Base:IsUseAllowed( ply ) then return end + + if self:IsOpen() then + self:Close( ply ) + else + self:Open( ply ) + end + end + + function ENT:OnOpen( ply ) + end + + function ENT:OnClosed( ply ) + end + + function ENT:OpenAndClose( ply ) + self:Open( ply ) + + self._PreventClosing = true + + timer.Simple(0.5, function() + if not IsValid( self ) then return end + + self:Close( ply ) + + self._PreventClosing = false + end ) + end + + function ENT:DisableOnBodyGroup( group, subgroup ) + self._BodyGroupDisable = group + self._BodySubGroupDisable = subgroup + end + + function ENT:IsBodyGroupDisabled() + if not self._BodyGroupDisable or not self._BodySubGroupDisable then return false end + + local base = self:GetBase() + + if not IsValid( base ) then return false end + + return base:GetBodygroup( self._BodyGroupDisable ) == self._BodySubGroupDisable + end + + function ENT:Open( ply ) + if self:IsOpen() then return end + + self:SetActive( true ) + self:SetMins( self:GetMinsOpen() ) + self:SetMaxs( self:GetMaxsOpen() ) + + if self:IsBodyGroupDisabled() then return end + + self:OnOpen( ply ) + + local snd = self:GetSoundOpen() + + if not snd then return end + + self:EmitSound( snd ) + end + + function ENT:Close( ply ) + if not self:IsOpen() then + if self:IsBodyGroupDisabled() then + self:Open( ply ) + end + + return + end + + if self:IsBodyGroupDisabled() then return end + + self:SetActive( false ) + self:SetMins( self:GetMinsClosed() ) + self:SetMaxs( self:GetMaxsClosed() ) + + self:OnClosed( ply ) + + local snd = self:GetSoundClose() + + if not snd then return end + + self:EmitSound( snd ) + end + + function ENT:OnDriverChanged( oldDriver, newDriver, pod ) + if self._PreventClosing then return end + + if IsValid( newDriver ) then + if self:IsOpen() then + self:Close( newDriver ) + end + else + timer.Simple( FrameTime() * 2, function() + if not IsValid( self ) or not IsValid( oldDriver ) or IsValid( self._Driver ) then return end + + if oldDriver:lvsGetVehicle() == self:GetBase() then return end + + if not self:IsOpen() then + self:OpenAndClose() + end + end ) + end + end + + function ENT:SetPoseParameterSV() + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + local Target = self:GetActive() and self:GetPoseMax() or self:GetPoseMin() + local poseName = self:GetPoseName() + + if poseName == "" then return end + + local EntTable = self:GetTable() + + EntTable.sm_pp = EntTable.sm_pp and EntTable.sm_pp + (Target - EntTable.sm_pp) * FrameTime() * self:GetRate() or 0 + + local value = EntTable.sm_pp ^ self:GetRateExponent() + + Base:SetPoseParameter( string.Replace(poseName, "^", ""), value ) + end + + function ENT:Think() + local LinkedSeat = self:GetLinkedSeat() + + if IsValid( LinkedSeat ) then + local Driver = LinkedSeat:GetDriver() + + if self._Driver ~= Driver then + + self:OnDriverChanged( self._Driver, Driver, LinkedSeat ) + + self._Driver = Driver + end + end + + if self:IsServerSide() then + self:SetPoseParameterSV() + + self:NextThink( CurTime() ) + else + self:NextThink( CurTime() + 0.25 ) + end + + return true + end + + function ENT:OnTakeDamage( dmginfo ) + end + + return +end + +function ENT:Initialize() +end + +function ENT:Think() + if self:IsServerSide() then return end + + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + local Target = self:GetActive() and self:GetPoseMax() or self:GetPoseMin() + local poseName = self:GetPoseName() + + if poseName == "" then return end + + local EntTable = self:GetTable() + + EntTable.sm_pp = EntTable.sm_pp and EntTable.sm_pp + (Target - EntTable.sm_pp) * RealFrameTime() * self:GetRate() or 0 + + local value = EntTable.sm_pp ^ self:GetRateExponent() + + if string.StartsWith( poseName, "!" ) then + Base:SetBonePoseParameter( poseName, value ) + else + Base:SetPoseParameter( poseName, value ) + end +end + +function ENT:OnRemove() +end + +function ENT:Draw() +end + +local LVS = LVS +ENT.ColorSelect = Color(127,255,127,150) +ENT.ColorNormal = Color(255,0,0,150) +ENT.ColorTransBlack = Color(0,0,0,150) +ENT.ColorBlack = Color(75,75,75,255) +ENT.OutlineThickness = Vector(0.5,0.5,0.5) + +local function DrawText( pos, text, align, col ) + if not align then align = TEXT_ALIGN_CENTER end + + cam.Start2D() + local data2D = pos:ToScreen() + + if not data2D.visible then cam.End2D() return end + + local font = "TargetIDSmall" + + local x = data2D.x + local y = data2D.y + + draw.DrawText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), align ) + draw.DrawText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), align ) + draw.DrawText( text, font, x, y, col or color_white, align ) + cam.End2D() +end + +local function bezier(p0, p1, p2, p3, t) + local e = p0 + t * (p1 - p0) + local f = p1 + t * (p2 - p1) + local g = p2 + t * (p3 - p2) + + local h = e + t * (f - e) + local i = f + t * (g - f) + + local p = h + t * (i - h) + + return p +end + +local function DrawBezier( startpos, midpos, endpos ) + cam.Start2D() + local p0 = LocalPlayer():GetPos() + local p1 = startpos + local p2 = midpos + local p3 = endpos + + local oldpos = p0:ToScreen() + + if not oldpos.visible then cam.End2D() return end + + local num = 100 + for i = 0, num do + local newpos = bezier(p0,p1,p2,p3, i / num):ToScreen() + + if not newpos.visible then + oldpos = newpos + continue + end + + surface.SetDrawColor( 255, 255, 255, math.abs( math.cos( -CurTime() * 5 + i * 0.05 ) ) * 255 ) + + surface.DrawLine( oldpos.x, oldpos.y, newpos.x, newpos.y ) + + oldpos = newpos + end + + cam.End2D() +end + +function ENT:DrawTranslucent() + local ply = LocalPlayer() + + if not IsValid( ply ) or ply:InVehicle() then return end + + local KeySprint = ply:KeyDown( IN_SPEED ) + local InRange = self:InRange( ply, self.UseRange ) + + if LVS.ShowDoorInfo and InRange then + local pos = (self:LocalToWorld( self:GetMins() ) + self:LocalToWorld( self:GetMaxs() )) * 0.5 + + local NameKeyUse = "["..(input.LookupBinding( "+use" ) or "+use is not bound to a key").."]" + local NameKeySprint = "["..(input.LookupBinding( "+speed" ) or "+speed is not bound to a key").."]" + + local boxOrigin = self:GetPos() + local boxAngles = self:GetAngles() + local boxMins = self:GetMins() + local boxMaxs = self:GetMaxs() + + if self:IsOpen() then + local LinkedSeat = self:GetLinkedSeat() + + if IsValid( LinkedSeat ) then + if not KeySprint then + DrawText( LinkedSeat:LocalToWorld( LinkedSeat:OBBCenter() ), NameKeyUse.." \n".."Enter ", TEXT_ALIGN_CENTER ) + DrawText( pos, " "..NameKeySprint.."\n".." Close", TEXT_ALIGN_CENTER, self.ColorBlack ) + DrawBezier( Vector(pos.x,pos.y,self:LocalToWorld( Vector(0,0,self:GetMaxs().z) ).z), Vector(pos.x,pos.y,self:LocalToWorld( Vector(0,0,self:GetMins().z) ).z), LinkedSeat:LocalToWorld( LinkedSeat:OBBCenter() - Vector(0,0,5) ) ) + else + DrawText( LinkedSeat:LocalToWorld( LinkedSeat:OBBCenter() ), NameKeySprint.." \n".."Enter ", TEXT_ALIGN_CENTER, self.ColorBlack ) + DrawText( pos, " "..NameKeyUse.."\n".." Close", TEXT_ALIGN_CENTER ) + end + else + DrawText( pos, NameKeyUse.."\n".."Close", TEXT_ALIGN_CENTER ) + end + else + DrawText( pos, NameKeyUse.."\n".."Open", TEXT_ALIGN_CENTER ) + end + end + + if not KeySprint then return end + + if InRange then + local EntTable = self:GetTable() + + local Use = ply:KeyDown( IN_USE ) + + if EntTable.old_Use ~= Use then + EntTable.old_Use = Use + + if Use then + net.Start( "lvs_doorhandler_interact" ) + net.WriteEntity( self ) + net.SendToServer() + end + end + end + + if not LVS.DeveloperEnabled then return end + + local boxOrigin = self:GetPos() + local boxAngles = self:GetAngles() + local boxMins = self:GetMins() + local boxMaxs = self:GetMaxs() + + local EntTable = self:GetTable() + + local Col = InRange and EntTable.ColorSelect or EntTable.ColorNormal + + render.SetColorMaterial() + render.DrawBox( boxOrigin, boxAngles, boxMins, boxMaxs, Col ) + render.DrawBox( boxOrigin, boxAngles, boxMaxs + EntTable.OutlineThickness, boxMins - EntTable.OutlineThickness, EntTable.ColorTransBlack ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_camera.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_camera.lua new file mode 100644 index 0000000..46f27cd --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_camera.lua @@ -0,0 +1,60 @@ + +function ENT:CalcViewOverride( ply, pos, angles, fov, pod ) + return pos, angles, fov +end + +function ENT:CalcViewDriver( ply, pos, angles, fov, pod ) + local view = {} + view.origin = pos + view.fov = fov + view.drawviewer = true + view.angles = ply:EyeAngles() + + if not pod:GetThirdPersonMode() then + + view.drawviewer = false + + return view + end + + local radius = 550 + radius = radius + radius * pod:GetCameraDistance() + + local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight()) + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod ) + local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod ) + + if self:GetDriverSeat() == pod then + return self:CalcViewDriver( ply, pos, angles, fov, pod ) + else + return self:CalcViewPassenger( ply, pos, angles, fov, pod ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_hud.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_hud.lua new file mode 100644 index 0000000..dc7d189 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_hud.lua @@ -0,0 +1,25 @@ + +ENT.IconEngine = Material( "lvs/engine.png" ) + +function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0) + draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT ) + + if ply ~= self:GetDriver() then return end + + local hX = X + W - H * 0.5 + local hY = Y + H * 0.25 + H * 0.25 + + surface.SetMaterial( self.IconEngine ) + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 ) + surface.SetDrawColor( color_white ) + surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 ) + + if not self:GetEngineActive() then + draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + self:LVSDrawCircle( hX, hY, H * 0.35, self:GetThrottle() ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_init.lua new file mode 100644 index 0000000..71e745a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/cl_init.lua @@ -0,0 +1,4 @@ +include("shared.lua") +include("sh_camera_eyetrace.lua") +include("cl_camera.lua") +include("cl_hud.lua") diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/init.lua new file mode 100644 index 0000000..07c7bba --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/init.lua @@ -0,0 +1,90 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_camera.lua" ) +AddCSLuaFile( "cl_hud.lua" ) +AddCSLuaFile( "sh_camera_eyetrace.lua" ) +include("shared.lua") +include("sv_controls.lua") +include("sv_components.lua") +include("sv_vehiclespecific.lua") +include("sh_camera_eyetrace.lua") +include("sv_ai.lua") + +DEFINE_BASECLASS( "lvs_base" ) + +function ENT:OnDriverChanged( Old, New, VehicleIsActive ) + if VehicleIsActive then + if not self:GetEngineActive() and self:IsEngineStartAllowed() then + self:SetEngineActive( true ) + end + + return + end + + self:SetEngineActive( false ) + self:SetMove( 0, 0 ) +end + +function ENT:StartEngine() + for _, wheel in pairs( self:GetWheels() ) do + if not IsValid( wheel ) then continue end + + wheel:PhysWake() + end + + BaseClass.StartEngine( self ) +end + +function ENT:PhysicsSimulate( phys, deltatime ) + if self:GetEngineActive() then phys:Wake() end + + local OnGroundMul = self:HitGround() and 1 or 0 + + local VelL = phys:WorldToLocal( phys:GetPos() + phys:GetVelocity() ) + + local InputMove = self:GetMove() + + self._smMove = self._smMove and (self._smMove + (Vector(InputMove.x,InputMove.y,0):GetNormalized() - self._smMove) * deltatime * self.ForceLinearRate * 10) or InputMove + + local MoveX = (self.MaxVelocityX + self.BoostAddVelocityX * InputMove.z) * self._smMove.x + local MoveY = (self.MaxVelocityY + self.BoostAddVelocityY * InputMove.z) * self._smMove.y + + local Ang = self:GetAngles() + + if not self:GetEngineActive() then + self:SetSteerTo( Ang.y ) + self.smY = Ang.y + end + + self.smY = self.smY and math.ApproachAngle( self.smY, self:GetSteerTo(), self.MaxTurnRate * deltatime * 100 ) or Ang.y + + local Steer = self:WorldToLocalAngles( Angle(Ang.p,self.smY,Ang.y) ).y + + local ForceLinear = ((Vector( MoveX, MoveY, 0 ) - Vector(VelL.x,VelL.y,0)) * self.ForceLinearMultiplier) * OnGroundMul * deltatime * 500 + local ForceAngle = (Vector(0,0,Steer) * self.ForceAngleMultiplier * 2 - phys:GetAngleVelocity() * self.ForceAngleDampingMultiplier) * OnGroundMul * deltatime * 600 + + return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, self:GetDisabled() and SIM_NOTHING or SIM_LOCAL_ACCELERATION ) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate ) + return ForceAngle, ForceLinear, simulate +end + +function ENT:IsEngineStartAllowed() + if hook.Run( "LVS.IsEngineStartAllowed", self ) == false then return false end + + if self:GetDisabled() then return false end + + if self:WaterLevel() > self.WaterLevelPreventStart then return false end + + return true +end + +function ENT:OnDisabled( name, old, new) + if new == old then return end + + if new then + if not self:GetEngineActive() then return end + self:SetEngineActive( false ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sh_camera_eyetrace.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sh_camera_eyetrace.lua new file mode 100644 index 0000000..453f365 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sh_camera_eyetrace.lua @@ -0,0 +1,42 @@ + +function ENT:GetEyeTrace( trace_forward ) + local startpos = self:LocalToWorld( self:OBBCenter() ) + + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + startpos = pod:LocalToWorld( pod:OBBCenter() ) + end + + local AimVector = trace_forward and self:GetForward() or self:GetAimVector() + + local data = { + start = startpos, + endpos = (startpos + AimVector * 50000), + filter = self:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( data ) + + return trace +end + +function ENT:GetAimVector() + if self:GetAI() then + return self:GetAIAimVector() + end + + local Driver = self:GetDriver() + + if not IsValid( Driver ) then return self:GetForward() end + + if SERVER then + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return Driver:EyeAngles():Forward() end + + return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward() + else + return Driver:EyeAngles():Forward() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/shared.lua new file mode 100644 index 0000000..505edd7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/shared.lua @@ -0,0 +1,72 @@ + +ENT.Base = "lvs_base" + +ENT.PrintName = "[LVS] Generic Fake Hover" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +ENT.ForceLinearMultiplier = 1 +ENT.ForceLinearRate = 1 + +ENT.MaxVelocityX = 300 +ENT.MaxVelocityY = 300 + +ENT.MaxTurnRate = 1 + +ENT.BoostAddVelocityX = 200 +ENT.BoostAddVelocityY = 200 + +ENT.GroundTraceHitWater = true +ENT.GroundTraceLength = 50 +ENT.GroundTraceHull = 100 + +ENT.DisableBallistics = true + +function ENT:SetupDataTables() + self:AddDT( "Vector", "AIAimVector" ) + self:AddDT( "Bool", "Disabled" ) + + if SERVER then + self:NetworkVarNotify( "Disabled", self.OnDisabled ) + end + + self:CreateBaseDT() +end + +function ENT:HitGround() + local data = { + start = self:LocalToWorld( self:OBBCenter() ), + endpos = self:LocalToWorld( Vector(0,0,self:OBBMins().z - self.GroundTraceLength) ), + mins = Vector( -self.GroundTraceHull, -self.GroundTraceHull, 0 ), + maxs = Vector( self.GroundTraceHull, self.GroundTraceHull, 0 ), + filter = self:GetCrosshairFilterEnts() + } + + local trace = util.TraceHull( data ) + + data.mask = MASK_WATER + + local traceWater = util.TraceHull( data ) + + return ((trace.Hit or (traceWater.Hit and self.GroundTraceHitWater)) and not trace.HitSky) +end + +function ENT:GetThrottle() + return math.min( self:GetVelocity():Length() / math.abs( self.MaxVelocityX + self.BoostAddVelocityX, self.MaxVelocityY + self.BoostAddVelocityY ), 1 ) +end + +function ENT:GetMaxThrottle() + return 1 +end + +function ENT:GetThrustStrenght() + return 0 +end + +function ENT:GetVehicleType() + return "fakehover" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_ai.lua new file mode 100644 index 0000000..648a0dc --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_ai.lua @@ -0,0 +1,125 @@ + +function ENT:OnCreateAI() + self:StartEngine() +end + +function ENT:OnRemoveAI() + self:StopEngine() +end + +function ENT:RunAI() + local RangerLength = 25000 + + local Target = self:AIGetTarget( 180 ) + + local StartPos = self:LocalToWorld( self:OBBCenter() ) + + local TraceFilter = self:GetCrosshairFilterEnts() + + local Front = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } ) + local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,15,0) ):Forward() * RangerLength } ) + local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-15,0) ):Forward() * RangerLength } ) + local FrontLeft1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,60,0) ):Forward() * RangerLength } ) + local FrontRight1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-60,0) ):Forward() * RangerLength } ) + local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,85,0) ):Forward() * RangerLength } ) + local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-85,0) ):Forward() * RangerLength } ) + + local TargetPos = (Front.HitPos + FrontLeft.HitPos + FrontRight.HitPos + FrontLeft1.HitPos + FrontRight1.HitPos + FrontLeft2.HitPos + FrontRight2.HitPos) / 7 + + self._AIFireInput = false + + if IsValid( self:GetHardLockTarget() ) then + TargetPos = self:GetHardLockTarget():GetPos() + if self:AITargetInFront( self:GetHardLockTarget(), 65 ) then + self._AIFireInput = true + end + else + if IsValid( Target ) then + TargetPos = Target:LocalToWorld( Target:OBBCenter() ) + + if self:AITargetInFront( Target, 65 ) then + self._AIFireInput = true + end + end + end + + if self._AIFireInput then + local CurHeat = self:GetNWHeat() + local CurWeapon = self:GetSelectedWeapon() + + if CurWeapon > 2 then + self:AISelectWeapon( 1 ) + else + if CurHeat > 0.9 then + if CurWeapon == 1 and self:AIHasWeapon( 2 ) then + self:AISelectWeapon( 2 ) + + elseif CurWeapon == 2 then + self:AISelectWeapon( 1 ) + end + else + if CurHeat == 0 and math.cos( CurTime() ) > 0 then + self:AISelectWeapon( 1 ) + end + end + end + end + + local DistToTarget = (TargetPos - self:GetPos()):Length() + local LocalMove = self:WorldToLocal( TargetPos ) + + if DistToTarget < 1000 then + LocalMove.x = -1 + end + + if DistToTarget > 800 and DistToTarget < 1200 then + LocalMove.y = math.sin( CurTime() * 1.5 + self:EntIndex() * 1337 ) * 10 + end + + self:SetMove( LocalMove.x, LocalMove.y ) + + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return end + + local AimVector = (TargetPos - pod:LocalToWorld( Vector(0,0,33) )):GetNormalized() + + self:SetAIAimVector( AimVector ) + + self:SetSteerTo( AimVector:Angle().y ) +end + +function ENT:OnAITakeDamage( dmginfo ) + local attacker = dmginfo:GetAttacker() + + if not IsValid( attacker ) then return end + + if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then + self:SetHardLockTarget( attacker ) + end +end + +function ENT:AISelectWeapon( ID ) + if ID == self:GetSelectedWeapon() then return end + + local T = CurTime() + + if (self._nextAISwitchWeapon or 0) > T then return end + + self._nextAISwitchWeapon = T + 1 + + self:SelectWeapon( ID ) +end + +function ENT:SetHardLockTarget( target ) + if not self:IsEnemy( target ) then return end + + self._HardLockTarget = target + self._HardLockTime = CurTime() + 4 +end + +function ENT:GetHardLockTarget() + if (self._HardLockTime or 0) < CurTime() then return NULL end + + return self._HardLockTarget +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_components.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_components.lua new file mode 100644 index 0000000..77ec570 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_components.lua @@ -0,0 +1,106 @@ + +ENT._WheelEnts = {} + +function ENT:GetWheels() + for id, ent in pairs( self._WheelEnts ) do + if IsValid( ent ) then continue end + + self._WheelEnts[ id ] = nil + end + + return self._WheelEnts +end + +function ENT:AddWheel( pos, radius, mass, buoyancyratio, brakeforce ) + if not isvector( pos ) or not isnumber( radius ) or not isnumber( mass ) then return end + + local wheel = ents.Create( "lvs_fakehover_wheel" ) + + if not IsValid( wheel ) then + self:Remove() + + print("LVS: Failed to initialize wheel. Vehicle terminated.") + + return + end + + local WheelPos = self:LocalToWorld( pos ) + local CenterPos = self:LocalToWorld( self:OBBCenter() ) + + debugoverlay.Sphere( WheelPos, radius, 5, Color(150,150,150), true ) + debugoverlay.Line( CenterPos, WheelPos, 5, Color(150,150,150), true ) + + wheel:SetPos( WheelPos ) + wheel:SetAngles( self:LocalToWorldAngles( Angle(0,90,0) ) ) + wheel:Spawn() + wheel:Activate() + wheel:SetBase( self ) + wheel:SetNoDraw( true ) + wheel:DrawShadow( false ) + wheel.DoNotDuplicate = true + wheel:Define( + { + radius = radius, + mass = mass, + buoyancyratio = buoyancyratio or 0, + } + ) + + table.insert( self._WheelEnts, wheel ) + + local PhysObj = wheel:GetPhysicsObject() + if not IsValid( PhysObj ) then + self:Remove() + + print("LVS: Failed to initialize wheel phys model. Vehicle terminated.") + return + end + + if PhysObj:GetMaterial() ~= "gmod_silent" then + self:Remove() + + print("LVS: Failed to initialize physprop material on wheel. Vehicle terminated.") + + return + end + + self:DeleteOnRemove( wheel ) + self:TransferCPPI( wheel ) + + local ballsocket = constraint.AdvBallsocket(wheel, self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -1, -1, -1, 1, 1, 1, 0, 0, 0, 0, 1) + ballsocket.DoNotDuplicate = true + self:TransferCPPI( ballsocket ) + + local nocollide = constraint.NoCollide( wheel, self, 0, 0 ) + nocollide.DoNotDuplicate = true + self:TransferCPPI( nocollide ) + + PhysObj:EnableMotion( true ) + + return wheel +end + +function ENT:AddEngineSound( pos ) + local EngineSND = ents.Create( "lvs_fakehover_soundemitter" ) + + if not IsValid( EngineSND ) then + self:Remove() + + print("LVS: Failed to create engine sound entity. Vehicle terminated.") + + return + end + + EngineSND:SetPos( self:LocalToWorld( pos ) ) + EngineSND:SetAngles( self:GetAngles() ) + EngineSND:Spawn() + EngineSND:Activate() + EngineSND:SetParent( self ) + EngineSND:SetBase( self ) + + self:DeleteOnRemove( EngineSND ) + + self:TransferCPPI( EngineSND ) + + return EngineSND +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_controls.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_controls.lua new file mode 100644 index 0000000..df41287 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_controls.lua @@ -0,0 +1,65 @@ + +function ENT:StartCommand( ply, cmd ) + if self:GetDriver() ~= ply then return end + + local KeyJump = ply:lvsKeyDown( "VSPEC" ) + + if self._lvsOldKeyJump ~= KeyJump then + self._lvsOldKeyJump = KeyJump + + if KeyJump then + self:ToggleVehicleSpecific() + end + end + + local Forward = cmd:KeyDown( IN_FORWARD ) + local Back = cmd:KeyDown( IN_BACK ) + local Left = cmd:KeyDown( IN_MOVELEFT ) + local Right = cmd:KeyDown( IN_MOVERIGHT ) + local Boost = cmd:KeyDown( IN_SPEED ) + + local X = (Forward and 1 or 0) - (Back and 1 or 0) + local Y = (Left and 1 or 0) - (Right and 1 or 0) + + self:SetMove( X, Y, Boost ) + + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return end + + if ply:lvsKeyDown( "FREELOOK" ) then + self:SetSteerTo( self:GetAngles().y) + + return + end + + self:SetSteerTo( pod:WorldToLocalAngles( ply:EyeAngles() ).y ) +end + +function ENT:SetSteerTo( Steer ) + if not isnumber( Steer ) then return end + + self._steer = Steer +end + +function ENT:GetSteerTo() + if not self:GetEngineActive() then return self:GetAngles().y end + + return (self._steer or self:GetAngles().y) +end + +function ENT:SetMove( X, Y, Boost ) + if not isnumber( X ) or not isnumber( Y ) then return end + + X = math.Clamp( X, -1, 1 ) + Y = math.Clamp( Y, -1, 1 ) + Z = Boost and 1 or 0 + + self._move = Vector( X, Y, Z ) +end + +function ENT:GetMove() + if not self:GetEngineActive() then return Vector(0,0,0) end + + return (self._move or Vector(0,0,0)) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_vehiclespecific.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_vehiclespecific.lua new file mode 100644 index 0000000..746e146 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fakehover/sv_vehiclespecific.lua @@ -0,0 +1,25 @@ + +function ENT:ToggleVehicleSpecific() + self._VSPEC = not self._VSPEC + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:EnableVehicleSpecific() + if self._VSPEC then return end + + self._VSPEC = true + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:DisableVehicleSpecific() + if not self._VSPEC then return end + + self._VSPEC = false + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:OnVehicleSpecificToggled( IsActive ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_camera.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_camera.lua new file mode 100644 index 0000000..b9dc127 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_camera.lua @@ -0,0 +1,189 @@ + +ENT._lvsSmoothFreeLook = 0 + +function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod ) + local ViewPosL = pod:WorldToLocal( pos ) + + local view = {} + view.fov = fov + view.drawviewer = true + view.angles = self:GetAngles() + + local FreeLook = ply:lvsKeyDown( "FREELOOK" ) + local Zoom = ply:lvsKeyDown( "ZOOM" ) + + if not pod:GetThirdPersonMode() then + + if FreeLook then + view.angles = pod:LocalToWorldAngles( ply:EyeAngles() ) + + self._lvsSmoothFreeLook = 1 + self._lvsSmoothFreeLookAngles = self:WorldToLocalAngles( view.angles ) + else + if self._lvsSmoothFreeLook and self._lvsSmoothFreeLookAngles then + + view.angles = self:LocalToWorldAngles( self._lvsSmoothFreeLookAngles * self._lvsSmoothFreeLook ) + + if self._lvsSmoothFreeLook <= 0 then + self._lvsSmoothFreeLookAngles = nil + self._lvsSmoothFreeLook = nil + else + self._lvsSmoothFreeLook = self._lvsSmoothFreeLook - self._lvsSmoothFreeLook * RealFrameTime() * 8 + end + end + end + + local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local Dividor = math.abs( velL.x ) + local SideForce = math.Clamp( velL.y / Dividor, -1, 1) + local UpForce = math.Clamp( velL.z / Dividor, -1, 1) + + local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1)) + if Zoom then + ViewPunch = Vector(0,0,0) + end + + pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0) + pod._lerpPos = pos + + view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5 + view.angles.p = view.angles.p - pod._lerpPosOffset.z * 0.1 + view.angles.y = view.angles.y + pod._lerpPosOffset.y * 0.1 + view.drawviewer = false + + return view + end + + pod._lerpPos = pod._lerpPos or self:GetPos() + + local radius = 550 + radius = radius + radius * pod:GetCameraDistance() + + if FreeLook then + local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local SideForce = math.Clamp(velL.y / 10,-250,250) + local UpForce = math.Clamp(velL.z / 10,-250,250) + + pod._lerpPosL = pod._lerpPosL and (pod._lerpPosL + (Vector(radius, SideForce,150 + radius * 0.1 + radius * pod:GetCameraHeight() + UpForce) - pod._lerpPosL) * RealFrameTime() * 12) or Vector(0,0,0) + pod._lerpPos = self:LocalToWorld( pod._lerpPosL ) + + view.origin = pod._lerpPos + view.angles = self:LocalToWorldAngles( Angle(0,180,0) ) + else + local TargetPos = self:LocalToWorld( Vector(500,0,150 + radius * 0.1 + radius * pod:GetCameraHeight()) ) + + local Sub = TargetPos - pod._lerpPos + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + local DesiredPos = TargetPos - self:GetForward() * (300 + radius) - Dir * 100 + + pod._lerpPos = pod._lerpPos + (DesiredPos - pod._lerpPos) * RealFrameTime() * (Zoom and 30 or 12) + pod._lerpPosL = self:WorldToLocal( pod._lerpPos ) + + local vel = self:GetVelocity() + + view.origin = pod._lerpPos + view.angles = self:GetAngles() + end + + view.origin = view.origin + ViewPosL + + return view +end + +function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod ) + local cvarFocus = math.Clamp( LVS.cvarCamFocus:GetFloat() , -1, 1 ) + + self._lvsSmoothFreeLook = self._lvsSmoothFreeLook + ((ply:lvsKeyDown( "FREELOOK" ) and 0 or 1) - self._lvsSmoothFreeLook) * RealFrameTime() * 10 + + local Zoom = ply:lvsKeyDown( "ZOOM" ) + + local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local Dividor = math.abs( velL.x ) + local SideForce = math.Clamp( velL.y / Dividor, -1, 1) + local UpForce = math.Clamp( velL.z / Dividor, -1, 1) + + local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1)) + if Zoom then + ViewPunch = Vector(0,0,0) + end + + pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0) + pod._lerpPos = pos + + local view = {} + view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5 + view.fov = fov + view.drawviewer = true + view.angles = (self:GetForward() * (1 + cvarFocus) * self._lvsSmoothFreeLook * 0.8 + ply:EyeAngles():Forward() * math.max(1 - cvarFocus, 1 - self._lvsSmoothFreeLook)):Angle() + + if cvarFocus >= 1 then + view.angles = LerpAngle( self._lvsSmoothFreeLook, ply:EyeAngles(), self:GetAngles() ) + else + view.angles.r = 0 + end + + if not pod:GetThirdPersonMode() then + + view.drawviewer = false + + return view + end + + local radius = 550 + radius = radius + radius * pod:GetCameraDistance() + + local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight()) + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view +end + +function ENT:CalcViewOverride( ply, pos, angles, fov, pod ) + return pos, angles, fov +end + +function ENT:CalcViewDriver( ply, pos, angles, fov, pod ) + if ply:lvsMouseAim() then + return self:CalcViewMouseAim( ply, pos, angles, fov, pod ) + else + return self:CalcViewDirectInput( ply, pos, angles, fov, pod ) + end +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod ) + local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod ) + + if self:GetDriverSeat() == pod then + return self:CalcViewDriver( ply, pos, angles, fov, pod ) + else + return self:CalcViewPassenger( ply, pos, angles, fov, pod ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_deathsound.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_deathsound.lua new file mode 100644 index 0000000..f2da67d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_deathsound.lua @@ -0,0 +1,17 @@ + +function ENT:OnDestroyed() + if not self.DeathSound then return end + + if self:GetVelocity():Length() <= self.MaxVelocity * 0.5 then return end + + self._sndDeath = CreateSound( self, self.DeathSound ) + self._sndDeath:SetSoundLevel( 125 ) + self._sndDeath:PlayEx( 1, 50 + 50 * self:CalcDoppler( LocalPlayer() ) ) +end + +function ENT:StopDeathSound() + if not self._sndDeath then return end + + self._sndDeath:Stop() +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_flyby.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_flyby.lua new file mode 100644 index 0000000..77043c8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_flyby.lua @@ -0,0 +1,63 @@ + +ENT.FlyByAdvance = 0 + +function ENT:FlyByThink() + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local EntTable = self:GetTable() + + if ply:lvsGetVehicle() == self then EntTable.OldApproaching = false return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Time = CurTime() + + if (EntTable._nextflyby or 0) > Time then return end + + EntTable._nextflyby = Time + 0.1 + + local Vel = self:GetVelocity() + + if self:GetThrottle() <= 0.75 or Vel:Length() <= EntTable.MaxVelocity * 0.75 then return end + + local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance + local ToPlayer = Sub:GetNormalized() + local VelDir = Vel:GetNormalized() + + local ApproachAngle = math.deg( math.acos( math.Clamp( ToPlayer:Dot( VelDir ) ,-1,1) ) ) + + local Approaching = ApproachAngle < 80 + + if Approaching ~= EntTable.OldApproaching then + EntTable.OldApproaching = Approaching + + if Approaching then + self:StopFlyBy() + else + self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) ) + end + end +end + +function ENT:OnFlyBy( Pitch ) + if not self.FlyBySound then return end + + local EntTable = self:GetTable() + + EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound ) + EntTable.flybysnd:SetSoundLevel( 95 ) + EntTable.flybysnd:PlayEx( 1, Pitch ) +end + +function ENT:StopFlyBy() + local EntTable = self:GetTable() + + if not EntTable.flybysnd then return end + + EntTable.flybysnd:Stop() + EntTable.flybysnd = nil +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_hud.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_hud.lua new file mode 100644 index 0000000..9ca215a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_hud.lua @@ -0,0 +1,98 @@ +ENT.IconEngine = Material( "lvs/engine.png" ) + +function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0) + draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT ) + + if ply ~= self:GetDriver() then return end + + local Throttle = self:GetThrottle() + local Col = Throttle <= 1 and color_white or Color(0,0,0,255) + local hX = X + W - H * 0.5 + local hY = Y + H * 0.25 + H * 0.25 + + surface.SetMaterial( self.IconEngine ) + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 ) + surface.SetDrawColor( color_white ) + surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 ) + + if not self:GetEngineActive() then + draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + self:LVSDrawCircle( hX, hY, H * 0.35, math.min( Throttle, 1 ) ) + + if Throttle > 1 then + draw.SimpleText( "+"..math.Round((Throttle - 1) * 100,0).."%" , "LVS_FONT", hX, hY, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end +end + +function ENT:LVSPreHudPaint( X, Y, ply ) + return true +end + +function ENT:LVSHudPaint( X, Y, ply ) + if not self:LVSPreHudPaint( X, Y, ply ) then return end + + if ply ~= self:GetDriver() then return end + + local HitPlane = self:GetEyeTrace( true ).HitPos:ToScreen() + local HitPilot = self:GetEyeTrace().HitPos:ToScreen() + + local MouseAim = ply:lvsMouseAim() + + if self:IsDrawingReflectorSight() then + self:DrawReflectorSight( HitPlane ) + + if MouseAim then + local LineVisible = false + + if not ply:lvsKeyDown( "FREELOOK" ) then + LineVisible = self:LVSHudPaintMouseAim( HitPlane, HitPilot ) + end + + if LineVisible then + self:PaintCrosshairOuter( HitPilot ) + end + end + + self:LVSPaintHitMarker( HitPilot ) + + return + end + + self:PaintCrosshairCenter( HitPlane ) + self:PaintCrosshairOuter( HitPilot ) + + if MouseAim and not ply:lvsKeyDown( "FREELOOK" ) then + self:LVSHudPaintMouseAim( HitPlane, HitPilot ) + end + + self:LVSPaintHitMarker( HitPilot ) +end + +function ENT:LVSHudPaintDirectInput( Pos2D ) + self:PaintCrosshairCenter( Pos2D ) + self:PaintCrosshairOuter( Pos2D ) +end + +function ENT:LVSHudPaintMouseAim( HitPlane, HitPilot ) + local Sub = Vector(HitPilot.x,HitPilot.y,0) - Vector(HitPlane.x,HitPlane.y,0) + local Len = Sub:Length() + + if Len <= 20 then return false end + + local Dir = Sub:GetNormalized() + + surface.SetDrawColor( 255, 255, 255, 100 ) + surface.DrawLine( HitPlane.x + Dir.x * 5, HitPlane.y + Dir.y * 5, HitPilot.x - Dir.x * 20, HitPilot.y- Dir.y * 20 ) + + -- shadow + surface.SetDrawColor( 0, 0, 0, 50 ) + surface.DrawLine( HitPlane.x + Dir.x * 5 + 1, HitPlane.y + Dir.y * 5 + 1, HitPilot.x - Dir.x * 20+ 1, HitPilot.y- Dir.y * 20 + 1 ) + + return true +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_init.lua new file mode 100644 index 0000000..720d6dc --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_init.lua @@ -0,0 +1,74 @@ +include("shared.lua") +include("cl_camera.lua") +include("sh_camera_eyetrace.lua") +include("cl_hud.lua") +include("cl_flyby.lua") +include("cl_deathsound.lua") +include("cl_reflectorsight.lua") + +local ExhaustSprite = Material( "effects/muzzleflash2" ) + +function ENT:DoExhaustFX() + local EntTable = self:GetTable() + + local Throttle = self:GetThrottle() + + local OffsetMagnitude = (8 + 4 * Throttle) + + local T = CurTime() + local FT = RealFrameTime() + + local HP = self:GetHP() + local MaxHP = self:GetMaxHP() + + if HP <= 0 then return end + + render.SetMaterial( ExhaustSprite ) + + local ShouldDoEffect = false + + if (EntTable.NextFX or 0) < T then + EntTable.NextFX = T + 0.05 + (1 - Throttle) / 10 + + ShouldDoEffect = true + end + + for id, data in pairs( EntTable.ExhaustPositions ) do + if not EntTable.ExhaustPositions[ id ].PosOffset then + EntTable.ExhaustPositions[ id ].PosOffset = 0 + end + + if not EntTable.ExhaustPositions[ id ].NextFX then + EntTable.ExhaustPositions[ id ].NextFX = 0 + end + + local Pos = self:LocalToWorld( data.pos ) + local Dir = self:LocalToWorldAngles( data.ang ):Up() + + self.ExhaustPositions[ id ].PosOffset = EntTable.ExhaustPositions[ id ].PosOffset + FT * (8 + 4 * Throttle) + + if ShouldDoEffect then + if math.random(0,1) == 1 then + EntTable.ExhaustPositions[ id ].PosOffset = 0 + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( Dir ) + effectdata:SetMagnitude( Throttle ) + effectdata:SetEntity( self ) + if HP > MaxHP * 0.25 then + util.Effect( "lvs_exhaust", effectdata ) + else + util.Effect( "lvs_exhaust_fire", effectdata ) + end + end + end + + if EntTable.ExhaustPositions[ id ].PosOffset > 1 or Throttle < 0.5 then continue end + + local Size = math.min( 10 * (1 - EntTable.ExhaustPositions[ id ].PosOffset ) ^ 2, 5 + 5 * Throttle ) + + render.SetMaterial( ExhaustSprite ) + render.DrawSprite( Pos + Dir * EntTable.ExhaustPositions[ id ].PosOffset * (5 + 5 * Throttle), Size, Size, color_white ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_reflectorsight.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_reflectorsight.lua new file mode 100644 index 0000000..286b57a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/cl_reflectorsight.lua @@ -0,0 +1,95 @@ + +ENT.ReflectorSight = false +ENT.ReflectorSightPos = vector_origin +ENT.ReflectorSightColor = color_white +ENT.ReflectorSightColorBG = color_black +ENT.ReflectorSightMaterial = Material("lvs/sights/german.png") +ENT.ReflectorSightMaterialRes = 128 +ENT.ReflectorSightHeight = 3 +ENT.ReflectorSightWidth = 1.5 +ENT.ReflectorSightGlow = false +ENT.ReflectorSightGlowMaterial = Material( "sprites/light_glow02_add" ) +ENT.ReflectorSightGlowMaterialRes = 600 +ENT.ReflectorSightGlowColor = color_white + +function ENT:PaintReflectorSight( Pos2D, Ang, Origin2D ) + if self.ReflectorSightGlow then + surface.SetDrawColor( self.ReflectorSightGlowColor ) + surface.SetMaterial( self.ReflectorSightGlowMaterial ) + surface.DrawTexturedRectRotated( Pos2D.x, Pos2D.y, self.ReflectorSightGlowMaterialRes, self.ReflectorSightGlowMaterialRes, -Ang ) + end + + surface.SetDrawColor( self.ReflectorSightColor ) + surface.SetMaterial( self.ReflectorSightMaterial ) + surface.DrawTexturedRectRotated( Pos2D.x, Pos2D.y, self.ReflectorSightMaterialRes, self.ReflectorSightMaterialRes, -Ang ) +end + +function ENT:IsDrawingReflectorSight() + if not self.ReflectorSight then return false end + + local Pod = self:GetDriverSeat() + + if not IsValid( Pod ) then return false end + + return not Pod:GetThirdPersonMode() +end + +function ENT:DrawReflectorSight( Pos2D ) + local Pos = self:LocalToWorld( self.ReflectorSightPos ) + local Up = self:GetUp() + local Right = self:GetRight() + + local Width = self.ReflectorSightWidth + local Height = self.ReflectorSightHeight + + local TopLeft = (Pos + Up * Height - Right * Width):ToScreen() + local TopRight = (Pos + Up * Height + Right * Width):ToScreen() + local BottomLeft = (Pos - Right * Width):ToScreen() + local BottomRight = (Pos + Right * Width):ToScreen() + + Pos = Pos:ToScreen() + + if not Pos.visible then return end + + local poly = { + { x = TopLeft.x, y = TopLeft.y }, + { x = TopRight.x, y = TopRight.y }, + { x = BottomRight.x, y = BottomRight.y }, + { x = BottomLeft.x, y = BottomLeft.y }, + } + + local Ang = 0 + + if TopLeft.x < TopRight.x then + Ang = (Vector( TopLeft.x, 0, TopLeft.y ) - Vector( TopRight.x, 0, TopRight.y )):Angle().p + else + Ang = (Vector( TopRight.x, 0, TopRight.y ) - Vector( TopLeft.x, 0, TopLeft.y )):Angle().p - 180 + end + + draw.NoTexture() + surface.SetDrawColor( self.ReflectorSightColorBG ) + surface.DrawPoly( poly ) + + render.SetStencilWriteMask( 0xFF ) + render.SetStencilTestMask( 0xFF ) + render.SetStencilReferenceValue( 0 ) + render.SetStencilPassOperation( STENCIL_KEEP ) + render.SetStencilZFailOperation( STENCIL_KEEP ) + render.ClearStencil() + + render.SetStencilEnable( true ) + render.SetStencilReferenceValue( 1 ) + render.SetStencilCompareFunction( STENCIL_NEVER ) + render.SetStencilFailOperation( STENCIL_REPLACE ) + + draw.NoTexture() + surface.SetDrawColor( color_white ) + surface.DrawPoly( poly ) + + render.SetStencilCompareFunction( STENCIL_EQUAL ) + render.SetStencilFailOperation( STENCIL_KEEP ) + + self:PaintReflectorSight( Pos2D, Ang, Pos ) + + render.SetStencilEnable( false ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/init.lua new file mode 100644 index 0000000..a6feb34 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/init.lua @@ -0,0 +1,189 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_camera.lua" ) +AddCSLuaFile( "sh_camera_eyetrace.lua" ) +AddCSLuaFile( "cl_hud.lua" ) +AddCSLuaFile( "cl_flyby.lua" ) +AddCSLuaFile( "cl_deathsound.lua" ) +AddCSLuaFile( "cl_reflectorsight.lua" ) +include("shared.lua") +include("sv_wheels.lua") +include("sv_landinggear.lua") +include("sv_components.lua") +include("sv_ai.lua") +include("sv_mouseaim.lua") +include("sh_camera_eyetrace.lua") + +function ENT:OnCreateAI() + self:StartEngine() + self.COL_GROUP_OLD = self:GetCollisionGroup() + self:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ) +end + +function ENT:OnRemoveAI() + self:StopEngine() + self:SetCollisionGroup( self.COL_GROUP_OLD or COLLISION_GROUP_NONE ) +end + +function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement ) + local LocalAngles = self:WorldToLocalAngles( TargetAngle ) + + if self:GetAI() then self:SetAIAimVector( LocalAngles:Forward() ) end + + local LocalAngPitch = LocalAngles.p + local LocalAngYaw = LocalAngles.y + local LocalAngRoll = LocalAngles.r + + local TargetForward = TargetAngle:Forward() + local Forward = self:GetForward() + + local AngDiff = math.deg( math.acos( math.Clamp( Forward:Dot( TargetForward ) ,-1,1) ) ) + + local WingFinFadeOut = math.max( (90 - AngDiff ) / 90, 0 ) + local RudderFadeOut = math.min( math.max( (120 - AngDiff ) / 120, 0 ) * 3, 1 ) + + local AngVel = self:GetPhysicsObject():GetAngleVelocity() + + local SmoothPitch = math.Clamp( math.Clamp(AngVel.y / 100,-0.25,0.25) / math.abs( LocalAngPitch ), -1, 1 ) + local SmoothYaw = math.Clamp( math.Clamp(AngVel.z / 100,-0.25,0.25) / math.abs( LocalAngYaw ), -1, 1 ) + + local Pitch = math.Clamp( -LocalAngPitch / 10 + SmoothPitch, -1, 1 ) + local Yaw = math.Clamp( -LocalAngYaw / 2 + SmoothYaw,-1,1) * RudderFadeOut + local Roll = math.Clamp( (-math.Clamp(LocalAngYaw * 16,-90,90) + LocalAngRoll * RudderFadeOut * 0.75) * WingFinFadeOut / 180 , -1 , 1 ) + + if FreeMovement then + Roll = math.Clamp( -LocalAngYaw * WingFinFadeOut / 180 , -1 , 1 ) + end + + if OverridePitch and OverridePitch ~= 0 then + Pitch = OverridePitch + end + + if OverrideYaw and OverrideYaw ~= 0 then + Yaw = OverrideYaw + end + + if OverrideRoll and OverrideRoll ~= 0 then + Roll = OverrideRoll + end + + self:SetSteer( Vector( math.Clamp(Roll * 1.25,-1,1), math.Clamp(-Pitch * 1.25,-1,1), -Yaw) ) +end + +function ENT:CalcAero( phys, deltatime, EntTable ) + if not EntTable then + EntTable = self:GetTable() + end + + -- mouse aim needs to run at high speed. + if self:GetAI() then + if EntTable._lvsAITargetAng then + self:ApproachTargetAngle( EntTable._lvsAITargetAng ) + end + else + local ply = self:GetDriver() + if IsValid( ply ) and ply:lvsMouseAim() then + self:PlayerMouseAim( ply ) + end + end + + local WorldGravity = self:GetWorldGravity() + local WorldUp = self:GetWorldUp() + local Steer = self:GetSteer() + + local Stability, InvStability, ForwardVelocity = self:GetStability() + + local Forward = self:GetForward() + local Left = -self:GetRight() + local Up = self:GetUp() + + local Vel = self:GetVelocity() + local VelForward = Vel:GetNormalized() + + local PitchPull = math.max( (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90, 0 ) + local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90 + + local GravMul = (WorldGravity / 600) * 0.25 + + -- crash behavior + if self:IsDestroyed() then + Steer = phys:GetAngleVelocity() / 200 + + PitchPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90 + + GravMul = WorldGravity / 600 + end + + local GravityPitch = math.abs( PitchPull ) ^ 1.25 * self:Sign( PitchPull ) * GravMul * EntTable.GravityTurnRatePitch + local GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * GravMul * EntTable.GravityTurnRateYaw + + local StallMul = math.min( (-math.min(Vel.z + EntTable.StallVelocity,0) / 100) * EntTable.StallForceMultiplier, EntTable.StallForceMax ) + + local StallPitch = 0 + local StallYaw = 0 + + if StallMul > 0 then + if InvStability < 1 then + StallPitch = PitchPull* GravMul * StallMul + StallYaw = YawPull * GravMul * StallMul + else + local StallPitchDir = self:Sign( math.deg( math.acos( math.Clamp( -VelForward:Dot( self:LocalToWorldAngles( Angle(-10,0,0) ):Up() ) ,-1,1) ) ) - 90 ) + local StallYawDir = self:Sign( math.deg( math.acos( math.Clamp( -VelForward:Dot( Left ) ,-1,1) ) ) - 90 ) + + local StallPitchPull = ((90 - math.abs( math.deg( math.acos( math.Clamp( -WorldUp:Dot( Up ) ,-1,1) ) ) - 90 )) / 90) * StallPitchDir + local StallYawPull = ((90 - math.abs( math.deg( math.acos( math.Clamp( -WorldUp:Dot( Left ) ,-1,1) ) ) - 90 )) / 90) * StallYawDir * 0.5 + + StallPitch = StallPitchPull * GravMul * StallMul + StallYaw = StallYawPull * GravMul * StallMul + end + end + + local Pitch = math.Clamp(Steer.y - GravityPitch,-1,1) * EntTable.TurnRatePitch * 3 * Stability - StallPitch * InvStability + local Yaw = math.Clamp(Steer.z * 4 + GravityYaw,-1,1) * EntTable.TurnRateYaw * Stability + StallYaw * InvStability + local Roll = math.Clamp(Steer.x * 1.5,-1,1) * EntTable.TurnRateRoll * 12 * Stability + + self:HandleLandingGear( deltatime ) + self:SetWheelSteer( Steer.z * EntTable.WheelSteerAngle ) + + local VelL = self:WorldToLocal( self:GetPos() + Vel ) + + local SlipMul = 1 - math.Clamp( math.max( math.abs( VelL.x ) - EntTable.MaxPerfVelocity, 0 ) / math.max(EntTable.MaxVelocity - EntTable.MaxPerfVelocity, 0 ),0,1) + + local MulZ = (math.max( math.deg( math.acos( math.Clamp( VelForward:Dot( Forward ) ,-1,1) ) ) - EntTable.MaxSlipAnglePitch * SlipMul * math.abs( Steer.y ), 0 ) / 90) * 0.3 + local MulY = (math.max( math.abs( math.deg( math.acos( math.Clamp( VelForward:Dot( Left ) ,-1,1) ) ) - 90 ) - EntTable.MaxSlipAngleYaw * SlipMul * math.abs( Steer.z ), 0 ) / 90) * 0.15 + + local Lift = -math.min( (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 180,0) * (WorldGravity / (1 / deltatime)) + + return Vector(0, -VelL.y * MulY, Lift - VelL.z * MulZ ) * Stability, Vector( Roll, Pitch, Yaw ) +end + +function ENT:OnSkyCollide( data, PhysObj ) + + local NewVelocity = self:VectorSubtractNormal( data.HitNormal, data.OurOldVelocity ) - data.HitNormal * math.Clamp(self:GetThrustStrenght() * self.MaxThrust,250,800) + + PhysObj:SetVelocityInstantaneous( NewVelocity ) + PhysObj:SetAngleVelocityInstantaneous( data.OurOldAngularVelocity ) + + self:FreezeStability() + + return true +end + +function ENT:PhysicsSimulate( phys, deltatime ) + local EntTable = self:GetTable() + + local Aero, Torque = self:CalcAero( phys, deltatime, EntTable ) + + if self:GetEngineActive() then phys:Wake() end + + local Thrust = math.max( self:GetThrustStrenght(), 0 ) * EntTable.MaxThrust * 100 + + local ForceLinear = (Aero * 10000 * EntTable.ForceLinearMultiplier + Vector(Thrust,0,0)) * deltatime + local ForceAngle = (Torque * 25 * EntTable.ForceAngleMultiplier - phys:GetAngleVelocity() * 1.5 * EntTable.ForceAngleDampingMultiplier) * deltatime * 250 + + return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_LOCAL_ACCELERATION ) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate ) + return ForceAngle, ForceLinear, simulate +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sh_camera_eyetrace.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sh_camera_eyetrace.lua new file mode 100644 index 0000000..fb7166a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sh_camera_eyetrace.lua @@ -0,0 +1,58 @@ + +function ENT:GetEyeTrace( trace_forward ) + local startpos = self:LocalToWorld( self:OBBCenter() ) + + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + startpos = pod:LocalToWorld( pod:OBBCenter() ) + end + + local AimVector = trace_forward and self:GetForward() or self:GetAimVector() + + local data = { + start = startpos, + endpos = (startpos + AimVector * 50000), + filter = self:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( data ) + + return trace +end + +function ENT:GetAimVector() + if self:GetAI() then + return self:GetAIAimVector() + end + + local Driver = self:GetDriver() + + if not IsValid( Driver ) then return self:GetForward() end + + if not Driver:lvsMouseAim() then + if Driver:lvsKeyDown( "FREELOOK" ) then + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return Driver:EyeAngles():Forward() end + + if pod:GetThirdPersonMode() then + return -self:GetForward() + else + return Driver:GetAimVector() + end + else + return self:GetForward() + end + end + + if SERVER then + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return Driver:EyeAngles():Forward() end + + return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward() + else + return Driver:EyeAngles():Forward() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/shared.lua new file mode 100644 index 0000000..7ed00fc --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/shared.lua @@ -0,0 +1,182 @@ + +ENT.Base = "lvs_base" + +ENT.PrintName = "[LVS] Base Fighter Plane" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MaxVelocity = 2500 +ENT.MaxPerfVelocity = 1500 +ENT.MaxThrust = 250 + +ENT.ThrottleRateUp = 0.6 +ENT.ThrottleRateDown = 0.3 + +ENT.TurnRatePitch = 1 +ENT.TurnRateYaw = 1 +ENT.TurnRateRoll = 1 + +ENT.GravityTurnRatePitch = 1 +ENT.GravityTurnRateYaw = 1 + +ENT.ForceLinearMultiplier = 1 + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +ENT.MaxSlipAnglePitch = 20 +ENT.MaxSlipAngleYaw = 10 + +ENT.StallVelocity = 150 +ENT.StallForceMultiplier = 4 +ENT.StallForceMax = 40 + +function ENT:SetupDataTables() + self:CreateBaseDT() + + self:AddDT( "Vector", "Steer" ) + self:AddDT( "Vector", "AIAimVector" ) + self:AddDT( "Float", "NWThrottle" ) + self:AddDT( "Float", "MaxThrottle" ) + self:AddDT( "Float", "LandingGear" ) + + if SERVER then + self:SetLandingGear( 1 ) + self:SetMaxThrottle( 1 ) + end +end + +function ENT:PlayerDirectInput( ply, cmd ) + local Pod = self:GetDriverSeat() + + local Delta = FrameTime() + + local KeyLeft = ply:lvsKeyDown( "-ROLL" ) + local KeyRight = ply:lvsKeyDown( "+ROLL" ) + local KeyPitchUp = ply:lvsKeyDown( "+PITCH" ) + local KeyPitchDown = ply:lvsKeyDown( "-PITCH" ) + local KeyRollRight = ply:lvsKeyDown( "+YAW" ) + local KeyRollLeft = ply:lvsKeyDown( "-YAW" ) + + local MouseX = cmd:GetMouseX() + local MouseY = cmd:GetMouseY() + + if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then + MouseX = 0 + MouseY = 0 + else + ply:SetEyeAngles( Angle(0,90,0) ) + end + + local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity() + + if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end + if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end + if KeyRollRight or KeyRollLeft then + local NewX = (KeyRollRight and 10 or 0) - (KeyRollLeft and 10 or 0) + + MouseX = (NewX / SensX) * ReturnDelta + end + + local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 ) + + local Cur = self:GetSteer() + + local Rate = Delta * 3 * ReturnDelta + + local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0) + + local Target = New + Input * Delta * 0.8 + + local Fx = math.Clamp( Target.x, -1, 1 ) + local Fy = math.Clamp( Target.y, -1, 1 ) + + local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0) + local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3) + + local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1) + + self:SetSteer( F ) +end + +function ENT:CalcThrottle( ply, cmd ) + if CLIENT then return end + + local Delta = FrameTime() + + local ThrottleUp = ply:lvsKeyDown( "+THROTTLE" ) and self.ThrottleRateUp or 0 + local ThrottleDown = ply:lvsKeyDown( "-THROTTLE" ) and -self.ThrottleRateDown or 0 + + local Throttle = (ThrottleUp + ThrottleDown) * Delta + + self:SetThrottle( self:GetThrottle() + Throttle ) +end + +function ENT:SetThrottle( NewThrottle ) + if self:GetEngineActive() then + self:SetNWThrottle( math.Clamp(NewThrottle,0,self:GetMaxThrottle()) ) + else + self:SetNWThrottle( 0 ) + end +end + +function ENT:GetThrottle() + if self:GetEngineActive() then + return self:GetNWThrottle() + else + return 0 + end +end + +function ENT:StartCommand( ply, cmd ) + if self:GetDriver() ~= ply then return end + + if SERVER and not self.WheelAutoRetract then + local KeyJump = ply:lvsKeyDown( "VSPEC" ) + + if self._lvsOldKeyJump ~= KeyJump then + self._lvsOldKeyJump = KeyJump + if KeyJump then + self:ToggleLandingGear() + self:PhysWake() + end + end + end + + if not ply:lvsMouseAim() then + self:PlayerDirectInput( ply, cmd ) + end + + self:CalcThrottle( ply, cmd ) +end + +function ENT:FreezeStability() + self._StabilityFrozen = CurTime() + 2 +end + +function ENT:GetStability() + if (self._StabilityFrozen or 0) > CurTime() then + return 1, 0, self.MaxPerfVelocity + end + + local ForwardVelocity = self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x + + local Stability = math.Clamp(ForwardVelocity / self.MaxPerfVelocity,0,1) ^ 2 + local InvStability = 1 - Stability + + return Stability, InvStability, ForwardVelocity +end + +function ENT:GetThrustStrenght() + local ForwardVelocity = self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x + + return (self.MaxVelocity - ForwardVelocity) * self:GetThrottle() / self.MaxVelocity +end + +function ENT:GetVehicleType() + return "plane" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_ai.lua new file mode 100644 index 0000000..14e806d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_ai.lua @@ -0,0 +1,203 @@ + +function ENT:OnCreateAI() + self:StartEngine() +end + +function ENT:OnRemoveAI() + self:StopEngine() +end + +function ENT:RunAI() + local RangerLength = 15000 + local mySpeed = self:GetVelocity():Length() + local MinDist = 600 + mySpeed + + local StartPos = self:LocalToWorld( self:OBBCenter() ) + + local TraceFilter = self:GetCrosshairFilterEnts() + + local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,20,0) ):Forward() * RangerLength } ) + local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-20,0) ):Forward() * RangerLength } ) + + local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,65,0) ):Forward() * RangerLength } ) + local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,-65,0) ):Forward() * RangerLength } ) + + local FrontLeft3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,65,0) ):Forward() * RangerLength } ) + local FrontRight3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,-65,0) ):Forward() * RangerLength } ) + + local FrontUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-20,0,0) ):Forward() * RangerLength } ) + local FrontDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(20,0,0) ):Forward() * RangerLength } ) + + local TraceForward = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } ) + local TraceDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,-RangerLength) } ) + local TraceUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,RangerLength) } ) + + local cAvoid = Vector(0,0,0) + + local myRadius = self:BoundingRadius() + local myPos = self:GetPos() + local myDir = self:GetForward() + for _, v in pairs( LVS:GetVehicles() ) do + if v == self then continue end + + local theirRadius = v:BoundingRadius() + local Sub = (myPos - v:GetPos()) + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + if Dist < (theirRadius + myRadius + 200) then + if math.deg( math.acos( math.Clamp( myDir:Dot( -Dir ) ,-1,1) ) ) < 90 then + cAvoid = cAvoid + Dir * (theirRadius + myRadius + 500) + end + end + end + + local FLp = FrontLeft.HitPos + FrontLeft.HitNormal * MinDist + cAvoid * 8 + local FRp = FrontRight.HitPos + FrontRight.HitNormal * MinDist + cAvoid * 8 + + local FL2p = FrontLeft2.HitPos + FrontLeft2.HitNormal * MinDist + local FR2p = FrontRight2.HitPos + FrontRight2.HitNormal * MinDist + + local FL3p = FrontLeft3.HitPos + FrontLeft3.HitNormal * MinDist + local FR3p = FrontRight3.HitPos + FrontRight3.HitNormal * MinDist + + local FUp = FrontUp.HitPos + FrontUp.HitNormal * MinDist + local FDp = FrontDown.HitPos + FrontDown.HitNormal * MinDist + + local Up = TraceUp.HitPos + TraceUp.HitNormal * MinDist + local Dp = TraceDown.HitPos + TraceDown.HitNormal * MinDist + + local TargetPos = (FLp+FRp+FL2p+FR2p+FL3p+FR3p+FUp+FDp+Up+Dp) / 10 + + local alt = (StartPos - TraceDown.HitPos):Length() + local ceiling = (StartPos - TraceUp.HitPos):Length() + + local WallDist = (StartPos - TraceForward.HitPos):Length() + + local Throttle = math.min( WallDist / mySpeed, 1 ) + + self._AIFireInput = false + + if alt < 600 or ceiling < 600 or WallDist < (MinDist * 3 * (math.deg( math.acos( math.Clamp( Vector(0,0,1):Dot( myDir ) ,-1,1) ) ) / 180) ^ 2) then + if ceiling < 600 then + Throttle = 0 + else + Throttle = 1 + + if self:HitGround() then + TargetPos.z = StartPos.z + 750 + else + if self:GetStability() < 0.5 then + TargetPos.z = StartPos.z + 1500 + end + end + end + else + if self:GetStability() < 0.5 then + TargetPos.z = StartPos.z + 600 + else + if IsValid( self:GetHardLockTarget() ) then + TargetPos = self:GetHardLockTarget():GetPos() + cAvoid * 8 + else + if alt > mySpeed then + local Target = self._LastAITarget + + if not IsValid( self._LastAITarget ) or not self:AITargetInFront( self._LastAITarget, 135 ) or not self:AICanSee( self._LastAITarget ) then + Target = self:AIGetTarget() + end + + if IsValid( Target ) then + if self:AITargetInFront( Target, 65 ) then + local T = CurTime() + self:EntIndex() * 1337 + TargetPos = Target:GetPos() + cAvoid * 8 + Vector(0,0, math.sin( T * 5 ) * 500 ) + Target:GetVelocity() * math.abs( math.cos( T * 13.37 ) ) * 5 + + Throttle = math.min( (StartPos - TargetPos):Length() / mySpeed, 1 ) + + local tr = util.TraceHull( { + start = StartPos, + endpos = (StartPos + self:GetForward() * 50000), + mins = Vector( -50, -50, -50 ), + maxs = Vector( 50, 50, 50 ), + filter = TraceFilter + } ) + + local CanShoot = (IsValid( tr.Entity ) and tr.Entity.LVS and tr.Entity.GetAITEAM) and (tr.Entity:GetAITEAM() ~= self:GetAITEAM() or tr.Entity:GetAITEAM() == 0) or true + + if CanShoot and self:AITargetInFront( Target, 22 ) then + local CurHeat = self:GetNWHeat() + local CurWeapon = self:GetSelectedWeapon() + + if CurWeapon > 2 then + self:AISelectWeapon( 1 ) + else + if CurHeat > 0.9 then + if CurWeapon == 1 and self:AIHasWeapon( 2 ) then + self:AISelectWeapon( 2 ) + + elseif CurWeapon == 2 then + self:AISelectWeapon( 1 ) + end + else + if CurHeat == 0 and math.cos( T ) > 0 then + self:AISelectWeapon( 1 ) + end + end + end + + self._AIFireInput = true + end + else + self:AISelectWeapon( 1 ) + + if alt > 6000 and self:AITargetInFront( Target, 90 ) then + TargetPos = Target:GetPos() + end + end + end + else + TargetPos.z = StartPos.z + 2000 + end + end + end + self:RaiseLandingGear() + end + + self:SetThrottle( Throttle ) + + self.smTargetPos = self.smTargetPos and self.smTargetPos + (TargetPos - self.smTargetPos) * FrameTime() or self:GetPos() + + self._lvsAITargetAng = (self.smTargetPos - self:GetPos()):GetNormalized():Angle() +end + +function ENT:AISelectWeapon( ID ) + if ID == self:GetSelectedWeapon() then return end + + local T = CurTime() + + if (self._nextAISwitchWeapon or 0) > T then return end + + self._nextAISwitchWeapon = T + math.random(3,6) + + self:SelectWeapon( ID ) +end + +function ENT:OnAITakeDamage( dmginfo ) + local attacker = dmginfo:GetAttacker() + + if not IsValid( attacker ) then return end + + if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then + self:SetHardLockTarget( attacker ) + end +end + +function ENT:SetHardLockTarget( target ) + self._HardLockTarget = target + self._HardLockTime = CurTime() + 4 +end + +function ENT:GetHardLockTarget() + if (self._HardLockTime or 0) < CurTime() then return NULL end + + return self._HardLockTarget +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_components.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_components.lua new file mode 100644 index 0000000..6e86d97 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_components.lua @@ -0,0 +1,100 @@ +function ENT:AddThruster( pos ) + local Thruster = ents.Create( "lvs_fighterplane_thruster" ) + + if not IsValid( Thruster ) then + self:Remove() + + print("LVS: Failed to create thruster entity. Vehicle terminated.") + + return + end + + Thruster:SetPos( self:LocalToWorld( pos ) ) + Thruster:SetAngles( self:GetAngles() ) + Thruster:Spawn() + Thruster:Activate() + Thruster:SetParent( self ) + Thruster:SetBase( self ) + + self:DeleteOnRemove( Thruster ) + + self:TransferCPPI( Thruster ) + + return Thruster +end + +function ENT:AddEngine( pos ) + local Engine = ents.Create( "lvs_fighterplane_engine" ) + + if not IsValid( Engine ) then + self:Remove() + + print("LVS: Failed to create engine entity. Vehicle terminated.") + + return + end + + Engine:SetPos( self:LocalToWorld( pos ) ) + Engine:SetAngles( self:GetAngles() ) + Engine:Spawn() + Engine:Activate() + Engine:SetParent( self ) + Engine:SetBase( self ) + + self:DeleteOnRemove( Engine ) + + self:TransferCPPI( Engine ) + + self:AddDS( { + pos = pos, + ang = Angle(0,0,0), + mins = Vector(-40,-20,-30), + maxs = Vector(40,20,30), + Callback = function( tbl, ent, dmginfo ) + dmginfo:ScaleDamage( 2.5 ) + end + } ) + + return Engine +end + +function ENT:AddRotor( pos ) + local Rotor = ents.Create( "lvs_fighterplane_rotor" ) + + if not IsValid( Rotor ) then + self:Remove() + + print("LVS: Failed to create rotor entity. Vehicle terminated.") + + return + end + + Rotor:SetPos( self:LocalToWorld( pos ) ) + Rotor:SetAngles( self:GetAngles() ) + Rotor:Spawn() + Rotor:Activate() + Rotor:SetParent( self ) + Rotor:SetBase( self ) + + if self:BoundingRadius() >= 600 then + Rotor:SetSound("lvs/vehicles/generic/bomber_propeller.wav") + Rotor:SetSoundStrain("lvs/vehicles/generic/bomber_propeller_strain.wav") + end + + self:DeleteOnRemove( Rotor ) + + self:TransferCPPI( Rotor ) + + return Rotor +end + +function ENT:AddExhaust( pos, ang ) + if not istable( self.ExhaustPositions ) then self.ExhaustPositions = {} end + + local Exhaust = { + pos = pos, + ang = ang, + } + + table.insert( self.ExhaustPositions, Exhaust ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_landinggear.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_landinggear.lua new file mode 100644 index 0000000..aa92fba --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_landinggear.lua @@ -0,0 +1,67 @@ + +function ENT:HandleLandingGear( Rate ) + local EnableBrakes = self:GetThrottle() <= 0 + + local Cur = self:GetLandingGear() + + if self.WheelAutoRetract then + if self:HitGround() then + self.LandingGearUp = false + else + self.LandingGearUp = self:GetStability() > 0.4 + end + end + + local New = Cur + math.Clamp((self.LandingGearUp and 0 or 1) - Cur,-Rate,Rate) + + local SetValue = Cur ~= New + + if SetValue then + self:SetLandingGear( New ) + end + + for _, data in pairs( self:GetWheels() ) do + local wheel = data.entity + local mass = data.mass + local physobj = data.physobj + + if not IsValid( wheel ) or not IsValid( physobj ) then continue end + + wheel:SetBrakes( EnableBrakes ) + + if not SetValue then continue end + + physobj:SetMass( 1 + (mass - 1) * New ^ 4 ) + end +end + +function ENT:ToggleLandingGear() + if self.WheelAutoRetract then return end + + self.LandingGearUp = not self.LandingGearUp + + self:OnLandingGearToggled( self.LandingGearUp ) +end + +function ENT:RaiseLandingGear() + if self.WheelAutoRetract then return end + + if not self.LandingGearUp then + self.LandingGearUp = true + + self:OnLandingGearToggled( self.LandingGearUp ) + end +end + +function ENT:DeployLandingGear() + if self.WheelAutoRetract then return end + + if self.LandingGearUp then + self.LandingGearUp = false + + self:OnLandingGearToggled( self.LandingGearUp ) + end +end + +function ENT:OnLandingGearToggled( IsDeployed ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_mouseaim.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_mouseaim.lua new file mode 100644 index 0000000..e4f140b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_mouseaim.lua @@ -0,0 +1,45 @@ + +function ENT:PlayerMouseAim( ply ) + local Pod = self:GetDriverSeat() + + local PitchUp = ply:lvsKeyDown( "+PITCH" ) + local PitchDown = ply:lvsKeyDown( "-PITCH" ) + local YawRight = ply:lvsKeyDown( "+YAW" ) + local YawLeft = ply:lvsKeyDown( "-YAW" ) + local RollRight = ply:lvsKeyDown( "+ROLL" ) + local RollLeft = ply:lvsKeyDown( "-ROLL" ) + + local FreeLook = ply:lvsKeyDown( "FREELOOK" ) + + local EyeAngles = Pod:WorldToLocalAngles( ply:EyeAngles() ) + + if FreeLook then + if isangle( self.StoredEyeAngles ) then + EyeAngles = self.StoredEyeAngles + end + else + self.StoredEyeAngles = EyeAngles + end + + local OverridePitch = 0 + local OverrideYaw = 0 + local OverrideRoll = (RollRight and 1 or 0) - (RollLeft and 1 or 0) + + if PitchUp or PitchDown then + EyeAngles = self:GetAngles() + + self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0) + + OverridePitch = (PitchUp and 1 or 0) - (PitchDown and 1 or 0) + end + + if YawRight or YawLeft then + EyeAngles = self:GetAngles() + + self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0) + + OverrideYaw = (YawRight and 1 or 0) - (YawLeft and 1 or 0) + end + + self:ApproachTargetAngle( EyeAngles, OverridePitch, OverrideYaw, OverrideRoll, FreeLook ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_wheels.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_wheels.lua new file mode 100644 index 0000000..277ea7c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_fighterplane/sv_wheels.lua @@ -0,0 +1,202 @@ + +ENT.WheelSteerAngle = 45 + +function ENT:AddWheelSteeringPlate( rear ) + if rear then + if IsValid( self._lvsSteerPlateRear ) then + return self._lvsSteerPlateRear + end + else + if IsValid( self._lvsSteerPlate ) then + return self._lvsSteerPlate + end + end + + local SteerMaster = ents.Create( "prop_physics" ) + + if not IsValid( SteerMaster ) then + self:Remove() + + print("LVS: Failed to initialize steering plate. Vehicle terminated.") + + return + end + + SteerMaster:SetModel( "models/hunter/plates/plate025x025.mdl" ) + SteerMaster:SetPos( self:GetPos() ) + SteerMaster:SetAngles( Angle(0,90,0) ) + SteerMaster:Spawn() + SteerMaster:Activate() + + local PhysObj = SteerMaster:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:EnableMotion( false ) + else + self:Remove() + + print("LVS: Failed to initialize steering plate. Vehicle terminated.") + + return + end + + SteerMaster:SetOwner( self ) + SteerMaster:DrawShadow( false ) + SteerMaster:SetNotSolid( true ) + SteerMaster:SetNoDraw( true ) + SteerMaster.DoNotDuplicate = true + self:DeleteOnRemove( SteerMaster ) + self:TransferCPPI( SteerMaster ) + + if rear then + self._lvsSteerPlateRear = SteerMaster + else + self._lvsSteerPlate = SteerMaster + end + + return SteerMaster +end + +function ENT:SetWheelSteer( SteerAngle ) + if IsValid( self._lvsSteerPlate ) then + local PhysObj = self._lvsSteerPlate:GetPhysicsObject() + + if IsValid( PhysObj ) then + if PhysObj:IsMotionEnabled() then + PhysObj:EnableMotion( false ) + end + end + + self._lvsSteerPlate:SetAngles( self:LocalToWorldAngles( Angle(0,math.Clamp(SteerAngle,-self.WheelSteerAngle,self.WheelSteerAngle),0) ) ) + end + + if not IsValid( self._lvsSteerPlateRear ) then return end + + local PhysObj = self._lvsSteerPlateRear:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + if PhysObj:IsMotionEnabled() then + PhysObj:EnableMotion( false ) + end + + self._lvsSteerPlateRear:SetAngles( self:LocalToWorldAngles( Angle(0,math.Clamp(-SteerAngle,-self.WheelSteerAngle,self.WheelSteerAngle),0) ) ) +end + +function ENT:GetWheels() + if not istable( self._lvsWheels ) then self._lvsWheels = {} end + + return self._lvsWheels +end + +function ENT:AddWheel( pos, radius, mass, type ) + if not isvector( pos ) or not isnumber( radius ) or not isnumber( mass ) then return end + + if not type then + type = LVS.WHEEL_BRAKE + end + + local wheel = ents.Create( "lvs_fighterplane_wheel" ) + + if not IsValid( wheel ) then + self:Remove() + + print("LVS: Failed to initialize wheel. Vehicle terminated.") + + return + end + + local WheelPos = self:LocalToWorld( pos ) + local CenterPos = self:LocalToWorld( self:OBBCenter() ) + + debugoverlay.Sphere( WheelPos, radius, 5, Color(150,150,150), true ) + debugoverlay.Line( CenterPos, WheelPos, 5, Color(150,150,150), true ) + + wheel:SetPos( WheelPos ) + wheel:SetAngles( self:LocalToWorldAngles( Angle(0,90,0) ) ) + wheel:Spawn() + wheel:Activate() + wheel:SetBase( self ) + wheel:Define( + { + physmat = "jeeptire", + radius = radius, + mass = mass, + brake = type == LVS.WHEEL_BRAKE, + } + ) + + local PhysObj = wheel:GetPhysicsObject() + if not IsValid( PhysObj ) then + self:Remove() + + print("LVS: Failed to initialize wheel phys model. Vehicle terminated.") + return + end + + PhysObj:EnableMotion( false ) + + self:DeleteOnRemove( wheel ) + self:TransferCPPI( wheel ) + + if type == LVS.WHEEL_STEER_NONE then + local ballsocket = constraint.AdvBallsocket(wheel, self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -180, -180, 180, 180, 180, 0, 0, 0, 0, 1) + ballsocket.DoNotDuplicate = true + self:TransferCPPI( ballsocket ) + end + + if type == LVS.WHEEL_BRAKE then + local axis = constraint.Axis( wheel, self, 0, 0, PhysObj:GetMassCenter(), wheel:GetPos(), 0, 0, 0, 0, Vector(1,0,0) , false ) + axis.DoNotDuplicate = true + self:TransferCPPI( axis ) + + wheel:SetBrakes( true ) + end + + if type == LVS.WHEEL_STEER_FRONT then + wheel:SetAngles( Angle(0,0,0) ) + + local SteerMaster = self:AddWheelSteeringPlate( false ) + + local ballsocket1 = constraint.AdvBallsocket(wheel, SteerMaster,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -0.01, -0.01, 180, 0.01, 0.01, 0, 0, 0, 1, 0) + ballsocket1.DoNotDuplicate = true + self:TransferCPPI( ballsocket1 ) + + local ballsocket2 = constraint.AdvBallsocket(wheel,self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -180, -180, 180, 180, 180, 0, 0, 0, 0, 0) + ballsocket2.DoNotDuplicate = true + self:TransferCPPI( ballsocket2 ) + end + + if type == LVS.WHEEL_STEER_REAR then + wheel:SetAngles( Angle(0,0,0) ) + + local SteerMaster = self:AddWheelSteeringPlate( true ) + + + local ballsocket1 = constraint.AdvBallsocket(wheel, SteerMaster,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -0.01, -0.01, 180, 0.01, 0.01, 0, 0, 0, 1, 0) + ballsocket1.DoNotDuplicate = true + self:TransferCPPI( ballsocket1 ) + + local ballsocket2 = constraint.AdvBallsocket(wheel,self,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -180, -180, -180, 180, 180, 180, 0, 0, 0, 0, 0) + ballsocket2.DoNotDuplicate = true + self:TransferCPPI( ballsocket2 ) + end + + local nocollide = constraint.NoCollide( wheel, self, 0, 0 ) + nocollide.DoNotDuplicate = true + self:TransferCPPI( nocollide ) + + PhysObj:EnableMotion( true ) + PhysObj:EnableDrag( false ) + + local WheelData = { + entity = wheel, + physobj = PhysObj, + mass = mass, + } + + if not istable( self._lvsWheels ) then self._lvsWheels = {} end + + table.insert( self._lvsWheels, WheelData ) + + return wheel +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/cl_init.lua new file mode 100644 index 0000000..9301b50 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/cl_init.lua @@ -0,0 +1,75 @@ +include("shared.lua") + +function ENT:Think() +end + +function ENT:OnRemove() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end + +function ENT:GetAimVector() + if self:GetAI() then + return self:GetNWAimVector() + end + + local Driver = self:GetDriver() + + if IsValid( Driver ) then + if self._AimVectorUnlocked then + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward() + end + end + + return Driver:GetAimVector() + else + return self:GetForward() + end +end + +function ENT:LVSPaintHitMarker( scr ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + Base:LVSPaintHitMarker( scr ) +end + +function ENT:LVSDrawCircle( X, Y, target_radius, value ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + Base:LVSDrawCircle( X, Y, target_radius, value ) +end + +function ENT:PaintCrosshairCenter( Pos2D, Col ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + Base:PaintCrosshairCenter( Pos2D, Col ) +end + +function ENT:PaintCrosshairOuter( Pos2D, Col ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + Base:PaintCrosshairOuter( Pos2D, Col ) +end + +function ENT:PaintCrosshairSquare( Pos2D, Col ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + Base:PaintCrosshairSquare( Pos2D, Col ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/init.lua new file mode 100644 index 0000000..8b1052e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/init.lua @@ -0,0 +1,327 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") +include("sv_ai.lua") + +function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) +end + +function ENT:Think() + self:HandleActive() + self:WeaponsThink() + + if self:GetAI() then + self:RunAI() + end + + self:NextThink( CurTime() ) + + return true +end + +function ENT:HandleActive() + local Pod = self:GetDriverSeat() + + if not IsValid( Pod ) then + return + end + + local Driver = Pod:GetDriver() + + if Driver ~= self:GetDriver() then + local NewDriver = Driver + local OldDriver = self:GetDriver() + + self:SetDriver( Driver ) + + local Base = self:GetVehicle() + + if IsValid( Base ) then + Base:OnPassengerChanged( OldDriver, NewDriver, Pod:GetNWInt( "pPodIndex", -1 ) ) + end + + if IsValid( Driver ) then + Driver:lvsBuildControls() + else + self:WeaponsFinish() + end + end +end + +function ENT:OnRemove() +end + +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end + +function ENT:WeaponsFinish() + if not self._activeWeapon then return end + + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + local CurWeapon = Base.WEAPONS[ self:GetPodIndex() ][ self._activeWeapon ] + + if not CurWeapon then return end + + if CurWeapon.FinishAttack then + CurWeapon.FinishAttack( self ) + end + + self._activeWeapon = nil + self.OldAttack = false +end + +function ENT:GetAmmo() + if self:GetAI() then return self:GetMaxAmmo() end + + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return -1 end + + return CurWeapon._CurAmmo or self:GetMaxAmmo() +end + +function ENT:TakeAmmo( num ) + if self:GetMaxAmmo() <= 0 then return end + + local CurWeapon = self:GetActiveWeapon() + + CurWeapon._CurAmmo = math.max( self:GetAmmo() - (num or 1), 0 ) + + self:SetNWAmmo( CurWeapon._CurAmmo ) +end + +function ENT:GetHeat( weaponid ) + local CurWeapon + + if isnumber( weaponid ) and weaponid > 0 then + local Base = self:GetVehicle() + + if IsValid( Base ) then + CurWeapon = Base.WEAPONS[ self:GetPodIndex() ][ weaponid ] + else + CurWeapon = self:GetActiveWeapon() + end + else + CurWeapon = self:GetActiveWeapon() + end + + if not CurWeapon then return 0 end + + return (CurWeapon._CurHeat or 0) +end + +function ENT:GetOverheated() + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return false end + + return CurWeapon.Overheated == true +end + +function ENT:SetOverheated( overheat ) + if self:GetOverheated() == overheat then return end + + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return end + + CurWeapon.Overheated = overheat + + self:SetNWOverheated( overheat ) + + if self:GetHeat() == 0 then return end + + if CurWeapon.OnOverheat then + CurWeapon.OnOverheat( self ) + end +end + +function ENT:SetHeat( heat ) + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return end + + heat = math.Clamp( heat, 0, 1 ) + + CurWeapon._CurHeat = heat + + if self:GetNWHeat() == heat then return end + + self:SetNWHeat( heat ) +end + +function ENT:CanAttack() + local CurWeapon = self:GetActiveWeapon() + + return (CurWeapon._NextFire or 0) < CurTime() +end + +function ENT:SetNextAttack( time ) + local CurWeapon = self:GetActiveWeapon() + + CurWeapon._NextFire = time +end + +function ENT:WeaponsShouldFire() + if self:GetAI() then return self._AIFireInput end + + local ply = self:GetDriver() + + if not IsValid( ply ) then return false end + + return ply:lvsKeyDown( "ATTACK" ) +end + +function ENT:WeaponsThink() + local T = CurTime() + local FT = FrameTime() + local CurWeapon, SelectedID = self:GetActiveWeapon() + + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + for ID, Weapon in pairs( Base.WEAPONS[ self:GetPodIndex() ] ) do + local IsActive = ID == SelectedID + if Weapon.OnThink then Weapon.OnThink( self, IsActive ) end + + if IsActive then continue end + + if Weapon.HeatIsClip and not Weapon.Overheated and Weapon._CurHeat ~= 0 then + Weapon.Overheated = true + Weapon._CurHeat = 1 + + if Weapon.OnReload then Weapon.OnReload( self ) end + end + + -- cool all inactive weapons down + Weapon._CurHeat = Weapon._CurHeat and Weapon._CurHeat - math.min( Weapon._CurHeat, (Weapon.HeatRateDown or 0.25) * FT ) or 0 + end + + if not CurWeapon then return end + + local ShouldFire = self:WeaponsShouldFire() + local CurHeat = self:GetHeat() + + if self:GetOverheated() then + if CurHeat <= 0 then + self:SetOverheated( false ) + else + ShouldFire = false + end + else + if CurHeat >= 1 then + self:SetOverheated( true ) + ShouldFire = false + if CurWeapon.OnReload then CurWeapon.OnReload( self ) end + end + end + + if self:GetMaxAmmo() > 0 then + if self:GetAmmo() <= 0 then + ShouldFire = false + end + end + + if ShouldFire ~= self.OldAttack then + self.OldAttack = ShouldFire + + if ShouldFire then + if CurWeapon.StartAttack then + CurWeapon.StartAttack( self ) + end + self._activeWeapon = SelectedID + else + self:WeaponsFinish() + end + end + + if ShouldFire then + if not self:CanAttack() then return end + + local ShootDelay = (CurWeapon.Delay or 0) + + self:SetNextAttack( T + ShootDelay ) + self:SetHeat( CurHeat + (CurWeapon.HeatRateUp or 0.2) * math.max(ShootDelay, FT) ) + + if not CurWeapon.Attack then return end + + if CurWeapon.Attack( self ) then + self:SetHeat( CurHeat - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) ) + self:SetNextAttack( T ) + end + + self._lvsNextActiveWeaponCoolDown = T + 0.25 + else + if (self._lvsNextActiveWeaponCoolDown or 0) > T then return end + + if CurWeapon.HeatIsClip and not CurWeapon.Overheated then + + self:SetHeat( self:GetHeat() ) + + return + end + + self:SetHeat( self:GetHeat() - math.min( self:GetHeat(), (CurWeapon.HeatRateDown or 0.25) * FT ) ) + end +end + +function ENT:SelectWeapon( ID ) + if not isnumber( ID ) then return end + + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + if Base.WEAPONS[ self:GetPodIndex() ][ ID ] then + self:SetSelectedWeapon( ID ) + end + + local ply = self:GetDriver() + + if not IsValid( ply ) then return end + + net.Start( "lvs_select_weapon" ) + net.Send( ply ) +end + +function ENT:OnWeaponChanged( name, old, new) + if new == old then return end + + self:WeaponsFinish() + + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + local PrevWeapon = Base.WEAPONS[ self:GetPodIndex() ][ old ] + if PrevWeapon and PrevWeapon.OnDeselect then + PrevWeapon.OnDeselect( self ) + end + + local NextWeapon = Base.WEAPONS[ self:GetPodIndex() ][ new ] + if NextWeapon and NextWeapon.OnSelect then + NextWeapon.OnSelect( self ) + self:SetNWAmmo( NextWeapon._CurAmmo or NextWeapon.Ammo or -1 ) + self:SetNWOverheated( NextWeapon.Overheated == true ) + end +end + +function ENT:LVSFireBullet( data ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return end + + data.Entity = Base + data.Velocity = data.Velocity + self:GetVelocity():Length() + data.SrcEntity = Base:WorldToLocal( data.Src ) + + LVS:FireBullet( data ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/shared.lua new file mode 100644 index 0000000..4048444 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/shared.lua @@ -0,0 +1,174 @@ +ENT.Type = "anim" + +ENT.PrintName = "LBaseGunner" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT.LVS_GUNNER = true +ENT.VectorNull = Vector(0,0,0) + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Driver" ) + self:NetworkVar( "Entity",1, "DriverSeat" ) + + self:NetworkVar( "Int", 0, "PodIndex") + self:NetworkVar( "Int", 1, "NWAmmo") + self:NetworkVar( "Int", 2, "SelectedWeapon" ) + + self:NetworkVar( "Float", 0, "NWHeat" ) + + self:NetworkVar( "Bool", 0, "NWOverheated" ) + + self:NetworkVar( "Vector", 0, "NWAimVector" ) + + if SERVER then + self:NetworkVarNotify( "SelectedWeapon", self.OnWeaponChanged ) + end +end + +function ENT:UnlockAimVector() + self._AimVectorUnlocked = true +end + +function ENT:LockAimVector() + self._AimVectorUnlocked = nil +end + +function ENT:GetEyeTrace() + local startpos = self:GetPos() + + local trace = util.TraceLine( { + start = startpos, + endpos = (startpos + self:GetAimVector() * 50000), + filter = self:GetCrosshairFilterEnts() + } ) + + return trace +end + +function ENT:GetAI() + if IsValid( self:GetDriver() ) then return false end + + local veh = self:GetVehicle() + + if not IsValid( veh ) then return false end + + return veh:GetAIGunners() +end + +function ENT:GetAITEAM() + local Base = self:GetVehicle() + + if not IsValid( Base ) then return 0 end + + return Base:GetAITEAM() +end + +function ENT:GetVehicle() + local Pod = self:GetParent() + + if not IsValid( Pod ) then return NULL end + + return Pod:GetParent() +end + +function ENT:HasWeapon( ID ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return false end + + return istable( Base.WEAPONS[ self:GetPodIndex() ][ ID ] ) +end + +function ENT:AIHasWeapon( ID ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return false end + + local weapon = Base.WEAPONS[ self:GetPodIndex() ][ ID ] + + if not istable( weapon ) then return false end + + return weapon.UseableByAI +end + +function ENT:GetActiveWeapon() + local SelectedID = self:GetSelectedWeapon() + + local Base = self:GetVehicle() + + if not IsValid( Base ) then return {}, SelectedID end + + local CurWeapon = Base.WEAPONS[ self:GetPodIndex() ][ SelectedID ] + + return CurWeapon, SelectedID +end + +function ENT:GetMaxAmmo() + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return -1 end + + return CurWeapon.Ammo or -1 +end + +function ENT:GetClip() + local CurWeapon = self:GetActiveWeapon() + + if not CurWeapon then return 0 end + + local HeatIncrement = (CurWeapon.HeatRateUp or 0.2) * math.max(CurWeapon.Delay or 0, FrameTime()) + + local Ammo = self:GetNWAmmo() + + if self:GetMaxAmmo() <= 0 and CurWeapon.Clip then + Ammo = CurWeapon.Clip + end + + return math.min( math.ceil( math.Round( (1 - self:GetNWHeat()) / HeatIncrement, 1 ) ), Ammo ) +end + +function ENT:GetCrosshairFilterEnts() + local Base = self:GetVehicle() + + if not IsValid( Base ) then return {} end + + return Base:GetCrosshairFilterEnts() +end + +function ENT:Sign( n ) + if n > 0 then return 1 end + + if n < 0 then return -1 end + + return 0 +end + +function ENT:VectorSubtractNormal( Normal, Velocity ) + local VelForward = Velocity:GetNormalized() + + local Ax = math.acos( math.Clamp( Normal:Dot( VelForward ) ,-1,1) ) + + local Fx = math.cos( Ax ) * Velocity:Length() + + local NewVelocity = Velocity - Normal * math.abs( Fx ) + + return NewVelocity +end + +function ENT:VectorSplitNormal( Normal, Velocity ) + return math.cos( math.acos( math.Clamp( Normal:Dot( Velocity:GetNormalized() ) ,-1,1) ) ) * Velocity:Length() +end + +function ENT:AngleBetweenNormal( Dir1, Dir2 ) + return math.deg( math.acos( math.Clamp( Dir1:Dot( Dir2 ) ,-1,1) ) ) +end + +function ENT:GetVehicleType() + return "LBaseGunner" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/sv_ai.lua new file mode 100644 index 0000000..22385fe --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_gunner/sv_ai.lua @@ -0,0 +1,98 @@ + +function ENT:GetAimVector() + if self:GetAI() then + local Dir = self._ai_look_dir or self.VectorNull + + self:SetNWAimVector( Dir ) + + return Dir + end + + local Driver = self:GetDriver() + + if IsValid( Driver ) then + if self._AimVectorUnlocked then + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward() + end + end + + return Driver:GetAimVector() + else + return self:GetForward() + end +end + +function ENT:RunAI() + local EntTable = self:GetTable() + + local Target = self:AIGetTarget( EntTable ) + + if not IsValid( Target ) then + EntTable._ai_look_dir = self:GetForward() + EntTable._AIFireInput = false + + return + end + + local TargetPos = Target:GetPos() + + if EntTable._AIFireInput then + local T = CurTime() * 0.5 + self:EntIndex() + local X = math.cos( T ) * 32 + local Y = math.sin( T ) * 32 + local Z = math.sin( math.cos( T / 0.5 ) * math.pi ) * 32 + TargetPos = Target:LocalToWorld( Target:OBBCenter() + Vector(X,Y,Z) ) + end + + EntTable._ai_look_dir = (TargetPos - self:GetPos()):GetNormalized() + + local StartPos = self:GetPos() + + local trace = util.TraceHull( { + start = StartPos, + endpos = (StartPos + EntTable._ai_look_dir * 50000), + mins = Vector( -50, -50, -50 ), + maxs = Vector( 50, 50, 50 ), + filter = self:GetCrosshairFilterEnts() + } ) + + if not self:AIHasWeapon( self:GetSelectedWeapon() ) then + EntTable._AIFireInput = false + + return + end + + if IsValid( trace.Entity ) and trace.Entity.GetAITEAM then + EntTable._AIFireInput = (trace.Entity:GetAITEAM() ~= self:GetAITEAM() or trace.Entity:GetAITEAM() == 0) + else + EntTable._AIFireInput = true + end +end + +function ENT:AIGetTarget( EntTable ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return NULL end + + if Base:GetAI() then + return Base:AIGetTarget() + end + + if not isnumber( EntTable.ViewConeAdd ) then + EntTable.ViewConeAdd = math.min( 100 + math.abs( Base:WorldToLocalAngles( self:GetAngles() ).y ), 360 ) + end + + return Base:AIGetTarget( EntTable.ViewConeAdd ) +end + +function ENT:AITargetInFront( ent, range ) + local Base = self:GetVehicle() + + if not IsValid( Base ) then return NULL end + + return Base:AITargetInFront( ent, range ) +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_camera.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_camera.lua new file mode 100644 index 0000000..a2af229 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_camera.lua @@ -0,0 +1,158 @@ + +ENT._lvsSmoothFreeLook = 0 + +function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod ) + local ViewPosL = pod:WorldToLocal( pos ) + + local view = {} + view.fov = fov + view.drawviewer = true + view.angles = self:GetAngles() + + local FreeLook = ply:lvsKeyDown( "FREELOOK" ) + local Zoom = ply:lvsKeyDown( "ZOOM" ) + + if not pod:GetThirdPersonMode() then + + if FreeLook then + view.angles = pod:LocalToWorldAngles( ply:EyeAngles() ) + end + + local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local Dividor = math.abs( velL.x ) + local SideForce = math.Clamp( velL.y / Dividor, -1, 1) + local UpForce = math.Clamp( velL.z / Dividor, -1, 1) + + local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1)) + if Zoom then + ViewPunch = Vector(0,0,0) + end + + pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0) + pod._lerpPos = pos + + view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5 + view.angles.p = view.angles.p - pod._lerpPosOffset.z * 0.1 + view.angles.y = view.angles.y + pod._lerpPosOffset.y * 0.1 + view.drawviewer = false + + return view + end + + pod._lerpPos = pod._lerpPos or self:GetPos() + + local radius = 550 + radius = radius + radius * pod:GetCameraDistance() + + if FreeLook then + local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local SideForce = math.Clamp(velL.y / 10,-250,250) + local UpForce = math.Clamp(velL.z / 10,-250,250) + + pod._lerpPosL = pod._lerpPosL and (pod._lerpPosL + (Vector(radius, SideForce,150 + radius * 0.1 + radius * pod:GetCameraHeight() + UpForce) - pod._lerpPosL) * RealFrameTime() * 12) or Vector(0,0,0) + pod._lerpPos = self:LocalToWorld( pod._lerpPosL ) + + view.origin = pod._lerpPos + view.angles = self:LocalToWorldAngles( Angle(0,180,0) ) + else + local TargetPos = self:LocalToWorld( Vector(500,0,150 + radius * 0.1 + radius * pod:GetCameraHeight()) ) + + local Sub = TargetPos - pod._lerpPos + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + local DesiredPos = TargetPos - self:GetForward() * (300 + radius) - Dir * 100 + + pod._lerpPos = pod._lerpPos + (DesiredPos - pod._lerpPos) * RealFrameTime() * (Zoom and 30 or 12) + pod._lerpPosL = self:WorldToLocal( pod._lerpPos ) + + local vel = self:GetVelocity() + + view.origin = pod._lerpPos + view.angles = self:GetAngles() + end + + view.origin = view.origin + ViewPosL + + return view +end + +function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod ) + local cvarFocus = math.Clamp( LVS.cvarCamFocus:GetFloat() , -1, 1 ) + + self._lvsSmoothFreeLook = self._lvsSmoothFreeLook + ((ply:lvsKeyDown( "FREELOOK" ) and 0 or 1) - self._lvsSmoothFreeLook) * RealFrameTime() * 10 + + local view = {} + view.origin = pos + view.fov = fov + view.drawviewer = true + view.angles = (self:GetForward() * (1 + cvarFocus) * self._lvsSmoothFreeLook * 0.8 + ply:EyeAngles():Forward() * math.max(1 - cvarFocus, 1 - self._lvsSmoothFreeLook)):Angle() + + if cvarFocus >= 1 then + view.angles = LerpAngle( self._lvsSmoothFreeLook, ply:EyeAngles(), self:GetAngles() ) + else + view.angles.r = 0 + end + + if not pod:GetThirdPersonMode() then + + view.drawviewer = false + + return view + end + + local radius = 550 + radius = radius + radius * pod:GetCameraDistance() + + local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight()) + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view +end + +function ENT:CalcViewOverride( ply, pos, angles, fov, pod ) + return pos, angles, fov +end + +function ENT:CalcViewDriver( ply, pos, angles, fov, pod ) + if ply:lvsMouseAim() then + return self:CalcViewMouseAim( ply, pos, angles, fov, pod ) + else + return self:CalcViewDirectInput( ply, pos, angles, fov, pod ) + end +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod ) + local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod ) + + if self:GetDriverSeat() == pod then + return self:CalcViewDriver( ply, pos, angles, fov, pod ) + else + return self:CalcViewPassenger( ply, pos, angles, fov, pod ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_flyby.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_flyby.lua new file mode 100644 index 0000000..77043c8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_flyby.lua @@ -0,0 +1,63 @@ + +ENT.FlyByAdvance = 0 + +function ENT:FlyByThink() + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local EntTable = self:GetTable() + + if ply:lvsGetVehicle() == self then EntTable.OldApproaching = false return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Time = CurTime() + + if (EntTable._nextflyby or 0) > Time then return end + + EntTable._nextflyby = Time + 0.1 + + local Vel = self:GetVelocity() + + if self:GetThrottle() <= 0.75 or Vel:Length() <= EntTable.MaxVelocity * 0.75 then return end + + local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance + local ToPlayer = Sub:GetNormalized() + local VelDir = Vel:GetNormalized() + + local ApproachAngle = math.deg( math.acos( math.Clamp( ToPlayer:Dot( VelDir ) ,-1,1) ) ) + + local Approaching = ApproachAngle < 80 + + if Approaching ~= EntTable.OldApproaching then + EntTable.OldApproaching = Approaching + + if Approaching then + self:StopFlyBy() + else + self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) ) + end + end +end + +function ENT:OnFlyBy( Pitch ) + if not self.FlyBySound then return end + + local EntTable = self:GetTable() + + EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound ) + EntTable.flybysnd:SetSoundLevel( 95 ) + EntTable.flybysnd:PlayEx( 1, Pitch ) +end + +function ENT:StopFlyBy() + local EntTable = self:GetTable() + + if not EntTable.flybysnd then return end + + EntTable.flybysnd:Stop() + EntTable.flybysnd = nil +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_hud.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_hud.lua new file mode 100644 index 0000000..34333c6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_hud.lua @@ -0,0 +1,168 @@ +ENT.IconEngine = Material( "lvs/engine.png" ) + +function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0) + draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT ) + + if ply ~= self:GetDriver() then return end + + local hX = X + W - H * 0.5 + local hY = Y + H * 0.25 + H * 0.25 + + surface.SetMaterial( self.IconEngine ) + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 ) + surface.SetDrawColor( color_white ) + surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 ) + + if not self:GetEngineActive() then + draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + self:LVSDrawCircle( hX, hY, H * 0.35, self:GetThrustPercent() ) +end + +function ENT:LVSPreHudPaint( X, Y, ply ) + return true +end + +ENT.Hud = true +ENT.HudThirdPerson = false +ENT.HudGradient = Material("gui/center_gradient") +ENT.HudColor = Color(255,255,255) + +function ENT:PaintHeliFlightInfo( X, Y, ply, Pos2D ) + local Roll = self:GetAngles().r + + surface.SetDrawColor(0,0,0,40) + surface.SetMaterial( self.HudGradient ) + surface.DrawTexturedRect( Pos2D.x - 270, Pos2D.y - 10, 140, 20 ) + surface.DrawTexturedRect( Pos2D.x + 130, Pos2D.y - 10, 140, 20 ) + + local X = math.cos( math.rad( Roll ) ) + local Y = math.sin( math.rad( Roll ) ) + + surface.SetDrawColor( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 ) + surface.DrawLine( Pos2D.x + X * 50, Pos2D.y + Y * 50, Pos2D.x + X * 125, Pos2D.y + Y * 125 ) + surface.DrawLine( Pos2D.x - X * 50, Pos2D.y - Y * 50, Pos2D.x - X * 125, Pos2D.y - Y * 125 ) + + surface.DrawLine( Pos2D.x + 125, Pos2D.y, Pos2D.x + 130, Pos2D.y + 5 ) + surface.DrawLine( Pos2D.x + 125, Pos2D.y, Pos2D.x + 130, Pos2D.y - 5 ) + surface.DrawLine( Pos2D.x - 125, Pos2D.y, Pos2D.x - 130, Pos2D.y + 5 ) + surface.DrawLine( Pos2D.x - 125, Pos2D.y, Pos2D.x - 130, Pos2D.y - 5 ) + + surface.SetDrawColor( 0, 0, 0, 80 ) + surface.DrawLine( Pos2D.x + X * 50 + 1, Pos2D.y + Y * 50 + 1, Pos2D.x + X * 125 + 1, Pos2D.y + Y * 125 + 1 ) + surface.DrawLine( Pos2D.x - X * 50 + 1, Pos2D.y - Y * 50 + 1, Pos2D.x - X * 125 + 1, Pos2D.y - Y * 125 + 1 ) + + surface.DrawLine( Pos2D.x + 126, Pos2D.y + 1, Pos2D.x + 131, Pos2D.y + 6 ) + surface.DrawLine( Pos2D.x + 126, Pos2D.y + 1, Pos2D.x + 131, Pos2D.y - 4 ) + surface.DrawLine( Pos2D.x - 126, Pos2D.y + 1, Pos2D.x - 129, Pos2D.y + 6 ) + surface.DrawLine( Pos2D.x - 126, Pos2D.y + 1, Pos2D.x - 129, Pos2D.y - 4 ) + + local X = math.cos( math.rad( Roll + 45 ) ) + local Y = math.sin( math.rad( Roll + 45 ) ) + surface.DrawLine( Pos2D.x + X * 30 - 1, Pos2D.y + Y * 30 + 1, Pos2D.x + X * 60 - 1, Pos2D.y + Y * 60 + 1 ) + local X = math.cos( math.rad( Roll + 135 ) ) + local Y = math.sin( math.rad( Roll + 135 ) ) + surface.DrawLine( Pos2D.x + X * 30 + 1, Pos2D.y + Y * 30 + 1, Pos2D.x + X * 60 + 1, Pos2D.y + Y * 60 + 1 ) + + surface.SetDrawColor( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 ) + local X = math.cos( math.rad( Roll + 45 ) ) + local Y = math.sin( math.rad( Roll + 45 ) ) + surface.DrawLine( Pos2D.x + X * 30, Pos2D.y + Y * 30, Pos2D.x + X * 60, Pos2D.y + Y * 60 ) + local X = math.cos( math.rad( Roll + 135 ) ) + local Y = math.sin( math.rad( Roll + 135 ) ) + surface.DrawLine( Pos2D.x + X * 30, Pos2D.y + Y * 30, Pos2D.x + X * 60, Pos2D.y + Y * 60 ) + + local Pitch = -self:GetAngles().p + + surface.DrawLine( Pos2D.x - 220, Pos2D.y, Pos2D.x - 180, Pos2D.y ) + surface.DrawLine( Pos2D.x + 220, Pos2D.y, Pos2D.x + 180, Pos2D.y ) + surface.SetDrawColor( 0, 0, 0, 80 ) + surface.DrawLine( Pos2D.x - 220, Pos2D.y + 1, Pos2D.x - 180, Pos2D.y + 1 ) + surface.DrawLine( Pos2D.x + 220, Pos2D.y + 1, Pos2D.x + 180, Pos2D.y + 1 ) + + draw.DrawText( math.Round( Pitch, 2 ), "LVS_FONT_PANEL", Pos2D.x - 175, Pos2D.y - 7, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 ), TEXT_ALIGN_LEFT ) + draw.DrawText( math.Round( Pitch, 2 ), "LVS_FONT_PANEL", Pos2D.x + 175, Pos2D.y - 7, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, 255 ), TEXT_ALIGN_RIGHT ) + + for i = -180, 180 do + local Y = -i * 10 + Pitch * 10 + + local absN = math.abs( i ) + + local IsTen = absN == math.Round( absN / 10, 0 ) * 10 + + local SizeX = IsTen and 20 or 10 + + local Alpha = 255 - (math.min( math.abs( Y ) / 200,1) ^ 2) * 255 + + if Alpha <= 0 then continue end + + surface.SetDrawColor( self.HudColor.r, self.HudColor.g, self.HudColor.b, Alpha * 0.75 ) + surface.DrawLine(Pos2D.x - 200 - SizeX, Pos2D.y + Y, Pos2D.x - 200, Pos2D.y + Y ) + surface.DrawLine(Pos2D.x + 200 + SizeX, Pos2D.y + Y, Pos2D.x + 200, Pos2D.y + Y ) + surface.SetDrawColor( 0, 0, 0, Alpha * 0.25 ) + surface.DrawLine(Pos2D.x - 200 - SizeX, Pos2D.y + Y + 1, Pos2D.x - 200, Pos2D.y + Y + 1 ) + surface.DrawLine(Pos2D.x + 200 + SizeX, Pos2D.y + Y + 1, Pos2D.x + 200, Pos2D.y + Y + 1) + + if not IsTen then continue end + + draw.DrawText( i, "LVS_FONT_HUD", Pos2D.x - 225, Pos2D.y + Y - 10, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, Alpha * 0.5 ), TEXT_ALIGN_RIGHT ) + draw.DrawText( i, "LVS_FONT_HUD", Pos2D.x + 225, Pos2D.y + Y - 10, Color( self.HudColor.r, self.HudColor.g, self.HudColor.b, Alpha * 0.5 ), TEXT_ALIGN_LEFT ) + end +end + +function ENT:LVSHudPaint( X, Y, ply ) + if not self:LVSPreHudPaint( X, Y, ply ) then return end + + if ply ~= self:GetDriver() then return end + + local HitPlane = self:GetEyeTrace( true ).HitPos:ToScreen() + local HitPilot = self:GetEyeTrace().HitPos:ToScreen() + + local pod = ply:GetVehicle() + + if self.Hud then + if not pod:GetThirdPersonMode() then + self:PaintHeliFlightInfo( X, Y, ply, HitPilot ) + end + end + + if self.HudThirdPerson then + if pod:GetThirdPersonMode() then + self:PaintHeliFlightInfo( X, Y, ply, HitPilot ) + end + end + + self:PaintCrosshairCenter( HitPlane ) + self:PaintCrosshairOuter( HitPilot ) + + if ply:lvsMouseAim() and not ply:lvsKeyDown( "FREELOOK" ) then + self:LVSHudPaintMouseAim( HitPlane, HitPilot ) + end + + self:LVSPaintHitMarker( HitPilot ) +end + +function ENT:LVSHudPaintDirectInput( Pos2D ) + self:PaintCrosshairCenter( Pos2D ) + self:PaintCrosshairOuter( Pos2D ) +end + +function ENT:LVSHudPaintMouseAim( HitPlane, HitPilot ) + local Sub = Vector(HitPilot.x,HitPilot.y,0) - Vector(HitPlane.x,HitPlane.y,0) + local Len = Sub:Length() + local Dir = Sub:GetNormalized() + + surface.SetDrawColor( 255, 255, 255, 100 ) + if Len > 20 then + surface.DrawLine( HitPlane.x + Dir.x * 5, HitPlane.y + Dir.y * 5, HitPilot.x - Dir.x * 20, HitPilot.y- Dir.y * 20 ) + + -- shadow + surface.SetDrawColor( 0, 0, 0, 50 ) + surface.DrawLine( HitPlane.x + Dir.x * 5 + 1, HitPlane.y + Dir.y * 5 + 1, HitPilot.x - Dir.x * 20+ 1, HitPilot.y- Dir.y * 20 + 1 ) + end +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_init.lua new file mode 100644 index 0000000..5fbc77f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/cl_init.lua @@ -0,0 +1,5 @@ +include("shared.lua") +include("cl_camera.lua") +include("sh_camera_eyetrace.lua") +include("cl_hud.lua") +include("cl_flyby.lua") diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/init.lua new file mode 100644 index 0000000..23167a8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/init.lua @@ -0,0 +1,210 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_camera.lua" ) +AddCSLuaFile( "sh_camera_eyetrace.lua" ) +AddCSLuaFile( "cl_hud.lua" ) +AddCSLuaFile( "cl_flyby.lua" ) +include("shared.lua") +include("sv_ai.lua") +include("sv_mouseaim.lua") +include("sv_components.lua") +include("sv_engine.lua") +include("sv_vehiclespecific.lua") +include("sv_damage_extension.lua") +include("sh_camera_eyetrace.lua") + +function ENT:OnCreateAI() + self:StartEngine() + self.COL_GROUP_OLD = self:GetCollisionGroup() + self:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ) +end + +function ENT:OnRemoveAI() + self:StopEngine() + self:SetCollisionGroup( self.COL_GROUP_OLD or COLLISION_GROUP_NONE ) +end + +function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement, phys, deltatime ) + if not IsValid( phys ) then + phys = self:GetPhysicsObject() + end + + if not deltatime then + deltatime = FrameTime() + end + + local LocalAngles = self:WorldToLocalAngles( TargetAngle ) + + local LocalAngPitch = LocalAngles.p + local LocalAngYaw = LocalAngles.y + local LocalAngRoll = LocalAngles.r + + local TargetForward = TargetAngle:Forward() + local Forward = self:GetForward() + + local Ang = self:GetAngles() + local AngVel = phys:GetAngleVelocity() + + local SmoothPitch = math.Clamp( math.Clamp(AngVel.y / 50,-0.25,0.25) / math.abs( LocalAngPitch ), -1, 1 ) + local SmoothYaw = math.Clamp( math.Clamp(AngVel.z / 50,-0.25,0.25) / math.abs( LocalAngYaw ), -1, 1 ) + + local VelL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local Pitch = math.Clamp(-LocalAngPitch / 10 + SmoothPitch,-1,1) + local Yaw = math.Clamp(-LocalAngYaw + SmoothYaw,-1,1) + local Roll = math.Clamp(LocalAngRoll / 100 + Yaw * 0.25 + math.Clamp(VelL.y / math.abs( VelL.x ) / 10,-0.25,0.25),-1,1) + + if OverrideRoll ~= 0 then + Roll = math.Clamp( self:WorldToLocalAngles( Angle( Ang.p, Ang.y, OverrideRoll * 60 ) ).r / 40,-1,1) + end + + self.Roll = Roll + + if OverridePitch and OverridePitch ~= 0 then + Pitch = OverridePitch + end + + if OverrideYaw and OverrideYaw ~= 0 then + Yaw = OverrideYaw + end + + self:SetSteer( Vector( Roll, -Pitch, -Yaw) ) +end + +function ENT:OnSkyCollide( data, PhysObj ) + + local NewVelocity = self:VectorSubtractNormal( data.HitNormal, data.OurOldVelocity ) - data.HitNormal * 50 + + PhysObj:SetVelocityInstantaneous( NewVelocity ) + PhysObj:SetAngleVelocityInstantaneous( data.OurOldAngularVelocity ) + + return true +end + +function ENT:PhysicsSimulate( phys, deltatime ) + if self:GetEngineActive() then phys:Wake() end + + local EntTable = self:GetTable() + + local WorldGravity = self:GetWorldGravity() + local WorldUp = self:GetWorldUp() + + local Up = self:GetUp() + local Left = -self:GetRight() + + local Mul = self:GetThrottle() + local InputThrust = math.min( self:GetThrust() , 0 ) * EntTable.ThrustDown + math.max( self:GetThrust(), 0 ) * EntTable.ThrustUp + + if self:HitGround() and InputThrust <= 0 then + Mul = 0 + end + + -- mouse aim needs to run at high speed. + if self:GetAI() then + self:CalcAIMove( phys, deltatime ) + else + local ply = self:GetDriver() + if IsValid( ply ) and ply:lvsMouseAim() then + self:PlayerMouseAim( ply, phys, deltatime ) + end + end + + local Steer = self:GetSteer() + + local Vel = phys:GetVelocity() + local VelL = phys:WorldToLocal( phys:GetPos() + Vel ) + + local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90 + + local GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * (WorldGravity / 100) * (math.min( Vector(VelL.x,VelL.y,0):Length() / EntTable.MaxVelocity,1) ^ 2) + + local Pitch = math.Clamp(Steer.y,-1,1) * EntTable.TurnRatePitch + local Yaw = math.Clamp(Steer.z + GravityYaw * 0.25 * EntTable.GravityTurnRateYaw,-1,1) * EntTable.TurnRateYaw * 60 + local Roll = math.Clamp(Steer.x,-1,1) * 1.5 * EntTable.TurnRateRoll + + local Ang = self:GetAngles() + + local FadeMul = (1 - math.max( (45 - self:AngleBetweenNormal( WorldUp, Up )) / 45,0)) ^ 2 + local ThrustMul = math.Clamp( 1 - (Vel:Length() / EntTable.MaxVelocity) * FadeMul, 0, 1 ) + + local Thrust = self:LocalToWorldAngles( Angle(Pitch,0,Roll) ):Up() * (WorldGravity + InputThrust * 500 * ThrustMul) * Mul + + local Force, ForceAng = phys:CalculateForceOffset( Thrust, phys:LocalToWorld( phys:GetMassCenter() ) + self:GetUp() * 1000 ) + + local ForceLinear = (Force - Vel * 0.15 * EntTable.ForceLinearDampingMultiplier) * Mul + local ForceAngle = (ForceAng + (Vector(0,0,Yaw) - phys:GetAngleVelocity() * 1.5 * EntTable.ForceAngleDampingMultiplier) * deltatime * 250) * Mul + + if EntTable._SteerOverride then + ForceAngle.z = (EntTable._SteerOverrideMove * math.max( self:GetThrust() * 2, 1 ) * 100 - phys:GetAngleVelocity().z) * Mul + end + + return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_GLOBAL_ACCELERATION ) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate ) + return ForceAngle, ForceLinear, simulate +end + +function ENT:ApproachThrust( New, Delta ) + if not Delta then + Delta = FrameTime() + end + + local Cur = self:GetThrust() + + self:SetThrust( Cur + (New - Cur) * Delta * self.ThrustRate * 2.5 ) +end + +function ENT:CalcThrust( KeyUp, KeyDown, Delta ) + if self:HitGround() and not KeyUp then + self:ApproachThrust( -1, Delta ) + self.Roll = self:GetAngles().r + + return + end + + local Up = KeyUp and 1 or 0 + local Down = KeyDown and -1 or 0 + + self:ApproachThrust( Up + Down, Delta ) +end + +function ENT:CalcHover( InputLeft, InputRight, InputUp, InputDown, ThrustUp, ThrustDown, PhysObj, deltatime ) + if not IsValid( PhysObj ) then + PhysObj = self:GetPhysicsObject() + end + + local VelL = PhysObj:WorldToLocal( PhysObj:GetPos() + PhysObj:GetVelocity() ) + local AngVel = PhysObj:GetAngleVelocity() + + local KeyLeft = InputLeft and 60 or 0 + local KeyRight = InputRight and 60 or 0 + local KeyPitchUp = InputUp and 60 or 0 + local KeyPitchDown = InputDown and 60 or 0 + + local Pitch = KeyPitchDown - KeyPitchUp + local Roll = KeyRight - KeyLeft + + if (Pitch + Roll) == 0 then + Pitch = math.Clamp(-VelL.x / 200,-1,1) * 60 + Roll = math.Clamp(VelL.y / 250,-1,1) * 60 + end + + local Ang = self:GetAngles() + + local Steer = self:GetSteer() + Steer.x = math.Clamp( Roll - Ang.r - AngVel.x,-1,1) + Steer.y = math.Clamp( Pitch - Ang.p - AngVel.y,-1,1) + + self:SetSteer( Steer ) + + self.Roll = Ang.r + + if ThrustUp or ThrustDown then + self:CalcThrust( ThrustUp, ThrustDown, deltatime ) + + return + end + + self:ApproachThrust( math.Clamp(-VelL.z / 100,-1,1), deltatime ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sh_camera_eyetrace.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sh_camera_eyetrace.lua new file mode 100644 index 0000000..fb7166a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sh_camera_eyetrace.lua @@ -0,0 +1,58 @@ + +function ENT:GetEyeTrace( trace_forward ) + local startpos = self:LocalToWorld( self:OBBCenter() ) + + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + startpos = pod:LocalToWorld( pod:OBBCenter() ) + end + + local AimVector = trace_forward and self:GetForward() or self:GetAimVector() + + local data = { + start = startpos, + endpos = (startpos + AimVector * 50000), + filter = self:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( data ) + + return trace +end + +function ENT:GetAimVector() + if self:GetAI() then + return self:GetAIAimVector() + end + + local Driver = self:GetDriver() + + if not IsValid( Driver ) then return self:GetForward() end + + if not Driver:lvsMouseAim() then + if Driver:lvsKeyDown( "FREELOOK" ) then + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return Driver:EyeAngles():Forward() end + + if pod:GetThirdPersonMode() then + return -self:GetForward() + else + return Driver:GetAimVector() + end + else + return self:GetForward() + end + end + + if SERVER then + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return Driver:EyeAngles():Forward() end + + return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward() + else + return Driver:EyeAngles():Forward() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/shared.lua new file mode 100644 index 0000000..49c7ca8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/shared.lua @@ -0,0 +1,153 @@ + +ENT.Base = "lvs_base" + +ENT.PrintName = "[LVS] Base Helicopter" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MaxVelocity = 2150 + +ENT.ThrustUp = 1 +ENT.ThrustDown = 0.8 +ENT.ThrustRate = 1 + +ENT.ThrottleRateUp = 0.2 +ENT.ThrottleRateDown = 0.2 + +ENT.TurnRatePitch = 1 +ENT.TurnRateYaw = 1 +ENT.TurnRateRoll = 1 + +ENT.GravityTurnRateYaw = 1 + +ENT.ForceLinearDampingMultiplier = 1.5 + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +function ENT:SetupDataTables() + self:CreateBaseDT() + + self:AddDT( "Vector", "Steer" ) + self:AddDT( "Vector", "AIAimVector" ) + self:AddDT( "Float", "Throttle" ) + self:AddDT( "Float", "NWThrust" ) +end + +function ENT:PlayerDirectInput( ply, cmd ) + local Pod = self:GetDriverSeat() + + local Delta = FrameTime() + + local KeyLeft = ply:lvsKeyDown( "-ROLL_HELI" ) + local KeyRight = ply:lvsKeyDown( "+ROLL_HELI" ) + local KeyPitchUp = ply:lvsKeyDown( "+PITCH_HELI" ) + local KeyPitchDown = ply:lvsKeyDown( "-PITCH_HELI" ) + local KeyRollRight = ply:lvsKeyDown( "+YAW_HELI" ) + local KeyRollLeft = ply:lvsKeyDown( "-YAW_HELI" ) + + local MouseX = cmd:GetMouseX() + local MouseY = cmd:GetMouseY() + + if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then + MouseX = 0 + MouseY = 0 + else + ply:SetEyeAngles( Angle(0,90,0) ) + end + + local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity() + + if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end + if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end + if KeyRollRight or KeyRollLeft then + local NewX = (KeyRollRight and 10 or 0) - (KeyRollLeft and 10 or 0) + + MouseX = (NewX / SensX) * ReturnDelta + end + + local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 ) + + local Cur = self:GetSteer() + + local Rate = Delta * 3 * ReturnDelta + + local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0) + + local Target = New + Input * Delta * 0.8 + + local Fx = math.Clamp( Target.x, -1, 1 ) + local Fy = math.Clamp( Target.y, -1, 1 ) + + local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0) + local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3) + + local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1) + + self:SetSteer( F ) + + if CLIENT then return end + + if ply:lvsKeyDown( "HELI_HOVER" ) then + self:CalcHover( ply:lvsKeyDown( "-YAW_HELI" ), ply:lvsKeyDown( "+YAW_HELI" ), KeyPitchUp, KeyPitchDown, ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ) ) + + self.ResetSteer = true + + else + if self.ResetSteer then + self.ResetSteer = nil + + self:SetSteer( Vector(0,0,0) ) + end + + self:CalcThrust( ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ) ) + end +end + +function ENT:StartCommand( ply, cmd ) + if self:GetDriver() ~= ply then return end + + if SERVER then + local KeyJump = ply:lvsKeyDown( "VSPEC" ) + + if self._lvsOldKeyJump ~= KeyJump then + self._lvsOldKeyJump = KeyJump + + if KeyJump then + self:ToggleVehicleSpecific() + end + end + end + + if not ply:lvsMouseAim() then + self:PlayerDirectInput( ply, cmd ) + end +end + +function ENT:SetThrust( New ) + if self:GetEngineActive() then + self:SetNWThrust( math.Clamp(New,-1,1) ) + else + self:SetNWThrust( 0 ) + end +end + +function ENT:GetThrust() + return self:GetNWThrust() +end + +function ENT:GetThrustPercent() + return math.Clamp(0.5 * self:GetThrottle() + self:GetThrust() * 0.5,0,1) +end + +function ENT:GetThrustStrenght() + return (1 - (self:GetVelocity():Length() / self.MaxVelocity)) * self:GetThrustPercent() +end + +function ENT:GetVehicleType() + return "helicopter" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_ai.lua new file mode 100644 index 0000000..3e3fc8c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_ai.lua @@ -0,0 +1,200 @@ + +function ENT:OnCreateAI() + self:StartEngine() +end + +function ENT:OnRemoveAI() + self:StopEngine() +end + +function ENT:RunAI() + local RangerLength = 15000 + + local mySpeed = self:GetVelocity():Length() + local myRadius = self:BoundingRadius() + local myPos = self:GetPos() + local myDir = self:GetForward() + + local MinDist = 600 + mySpeed + local StartPos = self:LocalToWorld( self:OBBCenter() ) + + local TraceFilter = self:GetCrosshairFilterEnts() + + local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,20,0) ):Forward() * RangerLength } ) + local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-20,0) ):Forward() * RangerLength } ) + + local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,65,0) ):Forward() * RangerLength } ) + local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,-65,0) ):Forward() * RangerLength } ) + + local FrontLeft3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,65,0) ):Forward() * RangerLength } ) + local FrontRight3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,-65,0) ):Forward() * RangerLength } ) + + local FrontUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-20,0,0) ):Forward() * RangerLength } ) + local FrontDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(20,0,0) ):Forward() * RangerLength } ) + + local TraceForward = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } ) + local TraceDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,-RangerLength) } ) + local TraceUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,RangerLength) } ) + + local cAvoid = Vector(0,0,0) + + for _, v in pairs( LVS:GetVehicles() ) do + if v == self then continue end + + local theirRadius = v:BoundingRadius() + local Sub = (myPos - v:GetPos()) + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + if Dist < (theirRadius + myRadius + 200) then + if math.deg( math.acos( math.Clamp( myDir:Dot( -Dir ) ,-1,1) ) ) < 90 then + cAvoid = cAvoid + Dir * (theirRadius + myRadius + 500) + end + end + end + + local FLp = FrontLeft.HitPos + FrontLeft.HitNormal * MinDist + cAvoid * 8 + local FRp = FrontRight.HitPos + FrontRight.HitNormal * MinDist + cAvoid * 8 + + local FL2p = FrontLeft2.HitPos + FrontLeft2.HitNormal * MinDist + local FR2p = FrontRight2.HitPos + FrontRight2.HitNormal * MinDist + + local FL3p = FrontLeft3.HitPos + FrontLeft3.HitNormal * MinDist + local FR3p = FrontRight3.HitPos + FrontRight3.HitNormal * MinDist + + local FUp = FrontUp.HitPos + FrontUp.HitNormal * MinDist + local FDp = FrontDown.HitPos + FrontDown.HitNormal * MinDist + + local Up = TraceUp.HitPos + TraceUp.HitNormal * MinDist + local Dp = TraceDown.HitPos + TraceDown.HitNormal * MinDist + + local TargetPos = (FLp+FRp+FL2p+FR2p+FL3p+FR3p+FUp+FDp+Up+Dp) / 10 + + local alt = (StartPos - TraceDown.HitPos):Length() + local ceiling = (StartPos - TraceUp.HitPos):Length() + + self._AIFireInput = false + + local Target = self:AIGetTarget() + + if alt < 600 or ceiling < 600 then + if ceiling < 600 then + TargetPos.z = StartPos.z - 2000 + else + TargetPos.z = StartPos.z + 2000 + end + else + if IsValid( self:GetHardLockTarget() ) then + TargetPos = self:GetHardLockTarget():GetPos() + cAvoid * 8 + else + if IsValid( Target ) then + local HisRadius = Target:BoundingRadius() + local HisPos = Target:GetPos() + Vector(0,0,600) + + TargetPos = HisPos + (myPos - HisPos):GetNormalized() * (myRadius + HisRadius + 500) + cAvoid * 8 + end + end + end + + self._lvsTargetPos = TargetPos + + if IsValid( Target ) then + TargetPos = Target:LocalToWorld( Target:OBBCenter() ) + + if self:AITargetInFront( Target, 65 ) then + local tr = self:GetEyeTrace() + + if (IsValid( tr.Entity ) and tr.Entity.LVS and tr.Entity.GetAITEAM) and (tr.Entity:GetAITEAM() ~= self:GetAITEAM() or tr.Entity:GetAITEAM() == 0) or true then + local CurHeat = self:GetNWHeat() + local CurWeapon = self:GetSelectedWeapon() + + if CurWeapon > 2 then + self:AISelectWeapon( 1 ) + else + if CurHeat > 0.9 then + if CurWeapon == 1 and self:AIHasWeapon( 2 ) then + self:AISelectWeapon( 2 ) + + elseif CurWeapon == 2 then + self:AISelectWeapon( 1 ) + end + else + if CurHeat == 0 and math.cos( CurTime() ) > 0 then + self:AISelectWeapon( 1 ) + end + end + end + + self._AIFireInput = true + end + else + self:AISelectWeapon( 1 ) + end + end + + self:SetAIAimVector( (TargetPos - myPos):GetNormalized() ) +end + +function ENT:CalcAIMove( PhysObj, deltatime ) + if not self._lvsTargetPos then return end + + local StartPos = self:LocalToWorld( self:OBBCenter() ) + + local Target = self:AIGetTarget() + + local TargetPos = self._lvsTargetPos + + local VelL = PhysObj:WorldToLocal( PhysObj:GetPos() + PhysObj:GetVelocity() ) + local AngVel = PhysObj:GetAngleVelocity() + + local LPos = self:WorldToLocal( TargetPos ) + + local Pitch = math.Clamp(LPos.x * 0.01 - VelL.x * 0.01,-1,1) * 40 + local Yaw = ((IsValid( Target ) and Target:GetPos() or TargetPos) - StartPos):Angle().y + local Roll = math.Clamp(VelL.y * 0.01,-1,1) * 40 + + local Ang = self:GetAngles() + + local Steer = self:GetSteer() + Steer.x = math.Clamp( Roll - Ang.r - AngVel.x,-1,1) + Steer.y = math.Clamp( Pitch - Ang.p - AngVel.y,-1,1) + Steer.z = math.Clamp( self:WorldToLocalAngles( Angle(0,Yaw,0) ).y / 10,-1,1) + + self:SetSteer( Steer ) + self:SetThrust( math.Clamp( LPos.z - VelL.z,-1,1) ) +end + +function ENT:AISelectWeapon( ID ) + if ID == self:GetSelectedWeapon() then return end + + local T = CurTime() + + if (self._nextAISwitchWeapon or 0) > T then return end + + self._nextAISwitchWeapon = T + math.random(3,6) + + self:SelectWeapon( ID ) +end + +function ENT:OnAITakeDamage( dmginfo ) + local attacker = dmginfo:GetAttacker() + + if not IsValid( attacker ) then return end + + if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then + self:SetHardLockTarget( attacker ) + end +end + +function ENT:SetHardLockTarget( target ) + if not self:IsEnemy( target ) then return end + + self._HardLockTarget = target + self._HardLockTime = CurTime() + 4 +end + +function ENT:GetHardLockTarget() + if (self._HardLockTime or 0) < CurTime() then return NULL end + + return self._HardLockTarget +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_components.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_components.lua new file mode 100644 index 0000000..6fa6194 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_components.lua @@ -0,0 +1,54 @@ + +function ENT:AddEngineSound( pos ) + local Engine = ents.Create( "lvs_helicopter_engine" ) + + if not IsValid( Engine ) then + self:Remove() + + print("LVS: Failed to create engine entity. Vehicle terminated.") + + return + end + + Engine:SetPos( self:LocalToWorld( pos ) ) + Engine:SetAngles( self:GetAngles() ) + Engine:Spawn() + Engine:Activate() + Engine:SetParent( self ) + Engine:SetBase( self ) + + self:DeleteOnRemove( Engine ) + + self:TransferCPPI( Engine ) + + return Engine +end + +function ENT:AddRotor( pos, ang, radius, speed ) + if not pos or not ang or not radius or not speed then return end + + local Rotor = ents.Create( "lvs_helicopter_rotor" ) + + if not IsValid( Rotor ) then + self:Remove() + + print("LVS: Failed to create rotor entity. Vehicle terminated.") + + return + end + + Rotor:SetPos( self:LocalToWorld( pos ) ) + Rotor:SetAngles( self:LocalToWorldAngles( ang ) ) + Rotor:Spawn() + Rotor:Activate() + Rotor:SetParent( self ) + Rotor:SetBase( self ) + Rotor:SetRadius( radius ) + Rotor:SetSpeed( speed ) + + self:DeleteOnRemove( Rotor ) + + self:TransferCPPI( Rotor ) + + return Rotor +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_damage_extension.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_damage_extension.lua new file mode 100644 index 0000000..ad26d06 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_damage_extension.lua @@ -0,0 +1,41 @@ + +function ENT:StartDestroyTimer() + if self._DestroyTimerStarted then return end + + self._DestroyTimerStarted = true + + timer.Simple( self:GetAI() and 5 or 60, function() + if not IsValid( self ) then return end + + self.MarkForDestruction = true + end ) +end + +function ENT:DestroySteering( movevalue ) + if self._SteerOverride then return end + + self._SteerOverride = true + self._SteerOverrideMove = (movevalue or 1) + self:StartDestroyTimer() +end + +function ENT:DestroyEngine() + if self._EngineDestroyed then return end + + self._EngineDestroyed = true + + self:TurnOffEngine() + + self:SetThrottle( 0 ) + self:SetThrust( 0 ) + + self:StartDestroyTimer() +end + +function ENT:IsSteeringDestroyed() + return self._SteerOverride == true +end + +function ENT:IsEngineDestroyed() + return self._EngineDestroyed == true +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_engine.lua new file mode 100644 index 0000000..465aabe --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_engine.lua @@ -0,0 +1,83 @@ + +function ENT:CalcThrottle() + if not self:GetEngineActive() then + + if self:GetThrottle() ~= 0 then self:SetThrottle( 0 ) end + + return + end + + local Delta = FrameTime() + + local Cur = self:GetThrottle() + local New = self._StopEngine and 0 or 1 + + if self:IsDestroyed() then New = 0 end + + if Cur == New and New == 0 then self:TurnOffEngine() return end + + self:SetThrottle( Cur + math.Clamp( (New - Cur), -self.ThrottleRateDown * Delta, self.ThrottleRateUp * Delta ) ) +end + +function ENT:HandleStart() + local Driver = self:GetDriver() + + if IsValid( Driver ) then + local KeyReload = Driver:lvsKeyDown( "ENGINE" ) + + if self.OldKeyReload ~= KeyReload then + self.OldKeyReload = KeyReload + + if KeyReload then + self:ToggleEngine() + end + end + end + + self:CalcThrottle() +end + +function ENT:ToggleEngine() + if self:GetEngineActive() and not self._StopEngine then + self:StopEngine() + else + self:StartEngine() + end +end + +function ENT:StartEngine() + if not self:IsEngineStartAllowed() then return end + if self._EngineDestroyed then return end + + if self:GetEngineActive() then + self._StopEngine = nil + + return + end + + self:PhysWake() + + self:SetEngineActive( true ) + self:OnEngineActiveChanged( true ) + + self._StopEngine = nil +end + +function ENT:StopEngine() + if self._StopEngine then return end + + self._StopEngine = true + self:OnEngineActiveChanged( false ) + + if self:WaterLevel() >= self.WaterLevelAutoStop then + self:TurnOffEngine() + end +end + +function ENT:TurnOffEngine() + if not self:GetEngineActive() then return end + + self:SetEngineActive( false ) + + self._StopEngine = nil +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_mouseaim.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_mouseaim.lua new file mode 100644 index 0000000..15f5ecd --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_mouseaim.lua @@ -0,0 +1,60 @@ + +function ENT:PlayerMouseAim( ply, phys, deltatime ) + local Pod = self:GetDriverSeat() + + local PitchUp = ply:lvsKeyDown( "+PITCH_HELI" ) + local PitchDown = ply:lvsKeyDown( "-PITCH_HELI" ) + local YawRight = ply:lvsKeyDown( "+YAW_HELI" ) + local YawLeft = ply:lvsKeyDown( "-YAW_HELI" ) + local RollRight = ply:lvsKeyDown( "+ROLL_HELI" ) + local RollLeft = ply:lvsKeyDown( "-ROLL_HELI" ) + + local FreeLook = ply:lvsKeyDown( "FREELOOK" ) + + local EyeAngles = Pod:WorldToLocalAngles( ply:EyeAngles() ) + + if FreeLook then + if isangle( self.StoredEyeAngles ) then + EyeAngles = self.StoredEyeAngles + end + else + self.StoredEyeAngles = EyeAngles + end + + local OverridePitch = 0 + local OverrideYaw = 0 + local OverrideRoll = (RollRight and 1 or 0) - (RollLeft and 1 or 0) + + if PitchUp or PitchDown then + EyeAngles = self:GetAngles() + + self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0) + + OverridePitch = (PitchUp and 1 or 0) - (PitchDown and 1 or 0) + end + + if YawRight or YawLeft then + EyeAngles = self:GetAngles() + + self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0) + + OverrideYaw = (YawRight and 1 or 0) - (YawLeft and 1 or 0) + end + + self:ApproachTargetAngle( EyeAngles, OverridePitch, OverrideYaw, OverrideRoll, FreeLook, phys, deltatime ) + + if ply:lvsKeyDown( "HELI_HOVER" ) then + self:CalcHover( RollLeft, RollRight, PitchUp, PitchDown, ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ), phys, deltatime ) + + self.ResetSteer = true + + else + if self.ResetSteer then + self.ResetSteer = nil + + self:SetSteer( Vector(0,0,0) ) + end + + self:CalcThrust( ply:lvsKeyDown( "+THRUST_HELI" ), ply:lvsKeyDown( "-THRUST_HELI" ), deltatime ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_vehiclespecific.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_vehiclespecific.lua new file mode 100644 index 0000000..746e146 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_helicopter/sv_vehiclespecific.lua @@ -0,0 +1,25 @@ + +function ENT:ToggleVehicleSpecific() + self._VSPEC = not self._VSPEC + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:EnableVehicleSpecific() + if self._VSPEC then return end + + self._VSPEC = true + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:DisableVehicleSpecific() + if not self._VSPEC then return end + + self._VSPEC = false + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:OnVehicleSpecificToggled( IsActive ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/cl_init.lua new file mode 100644 index 0000000..886e44d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/cl_init.lua @@ -0,0 +1,64 @@ +include("shared.lua") + +local up = Vector(0,0,1) +local down = Vector(0,0,-1) + +function ENT:DoVehicleFX() + local EntTable = self:GetTable() + + local Vel = self:GetVelocity():Length() + + if EntTable._WindSFX then + EntTable._WindSFX:ChangeVolume( math.Clamp( (Vel * EntTable.GroundEffectsMultiplier - 1200) / 2800,0,1 ), 0.25 ) + end + + local T = CurTime() + + if (EntTable.nextFX or 0) < T then + EntTable.nextFX = T + 0.01 + + self:DoAdvancedWaterEffects( EntTable, Vel ) + end +end + +function ENT:DoAdvancedWaterEffects( EntTable, Vel ) + local pos = self:LocalToWorld( self:OBBCenter() ) + + local traceSky = util.TraceLine( { + start = pos, + endpos = pos + up * 50000, + filter = self:GetCrosshairFilterEnts() + } ) + + local traceWater = util.TraceLine( { + start = traceSky.HitPos, + endpos = pos + down * 50000, + filter = self:GetCrosshairFilterEnts(), + mask = MASK_WATER + } ) + + local traceSoil = util.TraceLine( { + start = traceSky.HitPos, + endpos = pos + down * 50000, + filter = self:GetCrosshairFilterEnts(), + mask = MASK_ALL + } ) + + if traceSoil.HitPos.z > traceWater.HitPos.z then + if EntTable._WaterSFX then + EntTable._WaterSFX:ChangeVolume( 0, 0.25 ) + end + + return + end + + if EntTable._WaterSFX then + EntTable._WaterSFX:ChangeVolume( math.min(Vel / EntTable.MaxVelocity,1) ^ 2, 0.25 ) + EntTable._WaterSFX:ChangePitch( math.Clamp((Vel / EntTable.MaxVelocity) * 50,80,150), 0.5 ) + end + + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.HitPos ) + effectdata:SetEntity( self ) + util.Effect( "lvs_physics_water_advanced", effectdata ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/init.lua new file mode 100644 index 0000000..b334787 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/init.lua @@ -0,0 +1,167 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") +include("sv_controls.lua") +include("sv_components.lua") + +function ENT:EnableManualTransmission() + return false +end + +function ENT:DisableManualTransmission() + return false +end + +function ENT:SpawnFunction( ply, tr, ClassName ) + + local startpos = ply:GetShootPos() + local endpos = startpos + ply:GetAimVector() * 10000 + + local waterTrace = util.TraceLine( { + start = startpos, + endpos = endpos, + mask = MASK_WATER, + filter = ply + } ) + + if waterTrace.Hit and ((waterTrace.HitPos - startpos):Length() < (tr.HitPos - startpos):Length()) then + tr = waterTrace + end + + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:StoreCPPI( ply ) + ent:SetPos( tr.HitPos + tr.HitNormal * ent.SpawnNormalOffset ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + return ent +end + +function ENT:RunAI() +end + +function ENT:GetEnginePos() + local Engine = self:GetEngine() + + if IsValid( Engine ) then return Engine:GetPos() end + + return self:LocalToWorld( self:OBBCenter() ) +end + +local up = Vector(0,0,1) +local down = Vector(0,0,-1) +function ENT:PhysicsSimulate( phys, deltatime ) + + if self:GetEngineActive() then phys:Wake() end + + local EntTable = self:GetTable() + + local pos = self:GetEnginePos() + + local traceSky = util.TraceLine( { + start = pos, + endpos = pos + up * 50000, + filter = self:GetCrosshairFilterEnts() + } ) + + local traceData = { + start = traceSky.HitPos, + endpos = pos + down * 50000, + filter = self:GetCrosshairFilterEnts() + } + + pos = phys:LocalToWorld( phys:GetMassCenter() ) + + local traceSoil = util.TraceLine( traceData ) + traceData.mask = MASK_WATER + local traceWater = util.TraceLine( traceData ) + + local EngineInWater = traceWater.Hit + local Engine = self:GetEngine() + if IsValid( Engine ) then + traceData.start = Engine:GetPos() + traceData.endpos = Engine:LocalToWorld( Vector(0,0,-EntTable.EngineSplashDistance) ) + + EngineInWater = util.TraceLine( traceData ).Hit + end + + if not EngineInWater then return vector_origin, vector_origin, SIM_NOTHING end + + local BuoyancyForce = math.min( math.max( traceWater.HitPos.z - pos.z + EntTable.FloatHeight, 0 ), 10 ) + + local Grav = physenv.GetGravity() + local Vel = phys:GetVelocity() + local AngVel = phys:GetAngleVelocity() + + local mul = BuoyancyForce / 10 + local invmul = math.Clamp( 1 - mul, 0, 1 ) + + local Force = (-Grav + Vector(0,0,-Vel.z * invmul * EntTable.FloatForce)) * mul + local ForcePos = pos + self:GetUp() * BuoyancyForce + + local ForceLinear, ForceAngle = phys:CalculateForceOffset( Force, ForcePos ) + + ForceAngle = (ForceAngle - AngVel * invmul * 2) * 15 * EntTable.ForceAngleMultiplier + + local VelL = self:WorldToLocal( self:GetPos() + Vel ) + + local Thrust = self:GetThrust() + local ThrustStrength = math.abs( self:GetThrustStrenght() ) + + local SteerInput = self:GetSteer() + local Steer = SteerInput * math.Clamp( ThrustStrength + math.min(math.abs( VelL.x ) / math.min( EntTable.MaxVelocity, EntTable.MaxVelocityReverse ), 1 ), 0, 1 ) + + local Brake = self:GetBrake() + + if Brake > 0 then + Steer = Steer * -Brake + end + + local Pitch = -(math.max( math.cos( CurTime() * EntTable.FloatWaveFrequency + self:EntIndex() * 1337 ), 0 ) * VelL.x * 0.25 * EntTable.FloatWaveIntensity + Thrust * 0.25 * math.Clamp( VelL.x / EntTable.MaxVelocity,0,1) * EntTable.FloatThrottleIntensity) + local Yaw = Steer * EntTable.TurnForceYaw + local Roll = - AngVel.x * 5 - Steer * EntTable.TurnForceRoll + + if math.abs( SteerInput ) < 1 and BuoyancyForce > 0 and not self:IsPlayerHolding() then + Yaw = Yaw - AngVel.z * math.max( ((1 - math.abs( SteerInput )) ^ 2) * 100 - 100 * ThrustStrength, 1 ) + end + + ForceLinear:Add( self:GetForward() * Thrust + self:GetRight() * VelL.y ) + ForceAngle:Add( Vector(Roll,Pitch,Yaw) ) + + local FloatExp = math.max( self:GetUp().z, 0 ) ^ EntTable.FloatExponent + + ForceLinear:Mul( FloatExp ) + ForceAngle:Mul( FloatExp ) + + return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_GLOBAL_ACCELERATION ) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate ) + return ForceAngle, ForceLinear, simulate +end + +function ENT:ApproachTargetAngle( TargetAngle ) + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return end + + local ang = pod:GetAngles() + ang:RotateAroundAxis( self:GetUp(), 90 ) + + local Forward = ang:Right() + local View = pod:WorldToLocalAngles( TargetAngle ):Forward() + + local Reversed = false + if self:AngleBetweenNormal( View, ang:Forward() ) < 90 then + Reversed = self:GetReverse() + end + + local LocalAngSteer = (self:AngleBetweenNormal( View, ang:Right() ) - 90) / self.MouseSteerAngle + + local Steer = (math.min( math.abs( LocalAngSteer ), 1 ) ^ self.MouseSteerExponent * self:Sign( LocalAngSteer )) + + self:SetSteer( Steer ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/shared.lua new file mode 100644 index 0000000..8d1ff6f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/shared.lua @@ -0,0 +1,101 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "[LVS] Base Boat" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MaxHealth = 600 +ENT.MaxHealthEngine = 50 +ENT.MaxHealthFuelTank = 10 + +ENT.EngineIdleRPM = 1000 +ENT.EngineMaxRPM = 6000 + +ENT.EngineSplash = false +ENT.EngineSplashStartSize = 50 +ENT.EngineSplashEndSize = 200 +ENT.EngineSplashVelocity = 500 +ENT.EngineSplashVelocityRandomAdd = 200 +ENT.EngineSplashThrowAngle = 0 +ENT.EngineSplashDistance = 100 + +ENT.DeleteOnExplode = true + +ENT.lvsAllowEngineTool = false +ENT.lvsShowInSpawner = false + +ENT.AllowSuperCharger = false +ENT.AllowTurbo = false + +ENT.FloatHeight = 0 +ENT.FloatForce = 20 +ENT.FloatWaveFrequency = 5 +ENT.FloatExponent = 2 +ENT.FloatWaveIntensity = 1 + +ENT.FloatThrottleIntensity = 1 + +ENT.TurnRate = 5 +ENT.TurnForceYaw = 600 +ENT.TurnForceRoll = 400 + +ENT.MaxThrust = 1000 + +ENT.MaxVelocity = 1000 +ENT.MaxVelocityReverse = 350 + +ENT.MinVelocityAutoBrake = 200 + +ENT.ForceLinearMultiplier = 1 +ENT.ForceAngleMultiplier = 1 + +function ENT:GetVehicleType() + return "boat" +end + +function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed ) + ply:SetPlaybackRate( 1 ) + + if CLIENT then + if ply == self:GetDriver() then + ply:SetPoseParameter( "vehicle_steer", -self:GetSteer() ) + ply:InvalidateBoneCache() + end + + GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + end + + return false +end + +function ENT:GetThrust() + return self:GetThrustStrenght() * self.MaxThrust +end + +function ENT:GetThrustStrenght() + local EntTable = self:GetTable() + + local VelL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local DesiredVelocity = EntTable.MaxVelocity * self:GetThrottle() - EntTable.MaxVelocityReverse * self:GetBrake() + + if DesiredVelocity == 0 and math.abs( VelL.x ) > EntTable.MinVelocityAutoBrake then + return 0 + end + + return math.Clamp((DesiredVelocity - VelL.x) / EntTable.MaxVelocity,-1,1) +end + +function ENT:GetGear() + return -1 +end + +function ENT:IsManualTransmission() + return false +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/sv_components.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/sv_components.lua new file mode 100644 index 0000000..e6a5397 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/sv_components.lua @@ -0,0 +1,55 @@ + +function ENT:AddEngine( pos, ang, mins, maxs ) + if IsValid( self:GetEngine() ) then return end + + ang = ang or angle_zero + mins = mins or Vector(-10,-10,-10) + maxs = maxs or Vector(10,10,10) + + local Engine = ents.Create( "lvs_powerboat_engine" ) + + if not IsValid( Engine ) then + self:Remove() + + print("LVS: Failed to create engine entity. Vehicle terminated.") + + return + end + + Engine:SetPos( self:LocalToWorld( pos ) ) + Engine:SetAngles( self:LocalToWorldAngles( ang ) ) + Engine:Spawn() + Engine:Activate() + Engine:SetParent( self ) + Engine:SetBase( self ) + Engine:SetMaxHP( self.MaxHealthEngine ) + Engine:SetHP( self.MaxHealthEngine ) + + self:SetEngine( Engine ) + + self:DeleteOnRemove( Engine ) + + self:TransferCPPI( Engine ) + + debugoverlay.BoxAngles( self:LocalToWorld( pos ), mins, maxs, self:LocalToWorldAngles( ang ), 15, Color( 0, 255, 255, 255 ) ) + + self:AddDS( { + pos = pos, + ang = ang, + mins = mins, + maxs = maxs, + Callback = function( tbl, ent, dmginfo ) + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + Engine:TakeTransmittedDamage( dmginfo ) + + if not Engine:GetDestroyed() then + dmginfo:ScaleDamage( 0.25 ) + end + end + } ) + + return Engine +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/sv_controls.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/sv_controls.lua new file mode 100644 index 0000000..30cf08f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_powerboat/sv_controls.lua @@ -0,0 +1,46 @@ + +function ENT:CalcMouseSteer( ply ) + self:ApproachTargetAngle( ply:EyeAngles() ) +end + +function ENT:CalcSteer( ply, Steer ) + local KeyLeft = ply:lvsKeyDown( "CAR_STEER_LEFT" ) + local KeyRight = ply:lvsKeyDown( "CAR_STEER_RIGHT" ) + + local Steer = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0) + + self:LerpSteer( Steer ) +end + +function ENT:LerpSteer( Steer ) + local Rate = FrameTime() * self.TurnRate + local Cur = self:GetSteer() + local New = Cur + math.Clamp(Steer - Cur,-Rate,Rate) + + self:SetSteer( New ) +end + +function ENT:CalcThrottle( ply ) + local KeyThrottle = ply:lvsKeyDown( "CAR_THROTTLE" ) + local KeyBrake = ply:lvsKeyDown( "CAR_BRAKE" ) + + if KeyBrake then + KeyThrottle = false + end + + local ThrottleValue = ply:lvsKeyDown( "CAR_THROTTLE_MOD" ) and self:GetMaxThrottle() or 0.5 + local Throttle = KeyThrottle and ThrottleValue or 0 + local Brake = KeyBrake and ThrottleValue or 0 + + self:LerpThrottle( Throttle ) + self:LerpBrake( Brake ) +end + +function ENT:CalcHandbrake( ply ) +end + +function ENT:CalcTransmission( ply, T ) +end + +function ENT:OnHandbrakeActiveChanged( Active ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/init.lua new file mode 100644 index 0000000..dd2927e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/init.lua @@ -0,0 +1,106 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement ) + + if self:GetAI() then self:SetAIAimVector( self:WorldToLocalAngles( TargetAngle ):Forward() ) end + + local Ang = self:GetAngles() + + local AngY = self:WorldToLocalAngles( Angle(TargetAngle.p,TargetAngle.y,Ang.r ) ) + local AngP = self:WorldToLocalAngles( Angle(TargetAngle.p,Ang.y,Ang.r ) ) + + local LocalAngPitch = AngP.p + local LocalAngYaw = AngY.y + + local TargetForward = TargetAngle:Forward() + local Forward = self:GetForward() + + local AngVel = self:GetPhysicsObject():GetAngleVelocity() + + local Pitch = math.Clamp( -LocalAngPitch / 22 * math.Clamp( math.abs( AngY.y ) / 0.9,1,10), -1, 1 ) + math.Clamp(AngVel.y / 100,-0.25,0.25) / math.abs( LocalAngPitch ) + local Yaw = math.Clamp( -LocalAngYaw / 22,-1,1) + math.Clamp(AngVel.z / 100,-0.25,0.25) / math.abs( LocalAngYaw ) + + if OverridePitch and OverridePitch ~= 0 then + Pitch = OverridePitch + end + + if OverrideYaw and OverrideYaw ~= 0 then + Yaw = OverrideYaw + end + + self:SetSteer( Vector( math.Clamp(Yaw,-1,1), -math.Clamp(Pitch,-1,1), 0) ) +end + +function ENT:CalcAero( phys, deltatime ) + if self:GetAI() then + if self._lvsAITargetAng then + self:ApproachTargetAngle( self._lvsAITargetAng ) + end + end + + local Steer = self:GetSteer() + + local Forward = self:GetForward() + local Left = -self:GetRight() + + local Vel = self:GetVelocity() + local VelForward = Vel:GetNormalized() + + local GravityPitch = 0 + local GravityYaw = 0 + + -- crash bebehavior + if self:IsDestroyed() then + local WorldGravity = self:GetWorldGravity() + local WorldUp = self:GetWorldUp() + + local Up = self:GetUp() + + Steer = phys:GetAngleVelocity() / 200 + + local PitchPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90 + local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90 + + local GravMul = WorldGravity / 600 + + GravityPitch = math.abs( PitchPull ) ^ 1.25 * self:Sign( PitchPull ) * GravMul + GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * GravMul + + if not phys:IsGravityEnabled() then + phys:EnableGravity( true ) + end + end + + local Ang = self:GetAngles() + + Steer.y = math.max(math.abs( Steer.y ) - math.min( math.abs( Ang.p / 90 ), 1 ) ^ 3,0) * self:Sign( Steer.y ) + + if Ang.p > self.MaxPitch and Steer.y > 0 then + Steer.y = 0 + end + + if Ang.p < -self.MaxPitch and Steer.y < 0 then + Steer.y = 0 + end + + local Pitch = math.Clamp(Steer.y - GravityPitch,-1,1) * self.TurnRatePitch * 4 + local Yaw = math.Clamp(-Steer.x + GravityYaw,-1,1) * self.TurnRateYaw * 4 + + local VtolMove = self:GetVtolMove() + + local RollMul = (math.abs(self:AngleBetweenNormal( Vector(0,0,1) , self:GetForward() ) - 90) / 90) ^ 2 + local TargetRoll = (Steer.x * math.min( self:GetThrottle() ^ 2, 1 ) - (VtolMove.y / self.ThrustVtol) * 0.25) * 45 * (1 - RollMul) + + local Roll = math.Clamp( self:WorldToLocalAngles( Angle( 0, Ang.y, TargetRoll ) ).r / 90,-1,1) * self.TurnRateRoll * 16 + + local VelL = self:WorldToLocal( self:GetPos() + Vel ) + + local MulZ = (math.max( math.deg( math.acos( math.Clamp( VelForward:Dot( Forward ) ,-1,1) ) ) - math.abs( Steer.y ), 0 ) / 90) * 0.3 + local MulY = (math.max( math.abs( math.deg( math.acos( math.Clamp( VelForward:Dot( Left ) ,-1,1) ) ) - 90 ) - math.abs( Steer.z ), 0 ) / 90) * 0.15 + + local Move = Vector( (VtolMove.x < 0) and -math.min(VelL.x * 0.15,0) or 0, -VelL.y * MulY, -VelL.z * MulZ ) + VtolMove + + return Move, Vector( Roll, Pitch, Yaw ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/shared.lua new file mode 100644 index 0000000..1a9f294 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_repulsorlift/shared.lua @@ -0,0 +1,126 @@ + +ENT.Base = "lvs_base_starfighter" + +ENT.PrintName = "[LVS] Base Gunship" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.ThrustVtol = 30 +ENT.ThrustRateVtol = 2 + +ENT.MaxPitch = 60 + +ENT.DisableBallistics = true + +function ENT:CalcVtolThrottle( ply, cmd ) + local Delta = FrameTime() + + local ThrottleZero = self:GetThrottle() <= 0 + + local VtolX = ThrottleZero and (ply:lvsKeyDown( "-VTOL_X_SF" ) and -1 or 0) or 0 + local VtolY = ((ply:lvsKeyDown( "+VTOL_Y_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Y_SF" ) and 1 or 0)) + + if ply:lvsMouseAim() then + VtolY = math.Clamp( VtolY + ((ply:lvsKeyDown( "-ROLL_SF" ) and 1 or 0) - (ply:lvsKeyDown( "+ROLL_SF" ) and 1 or 0)), -1 , 1) + end + + VtolY = VtolY * math.max( 1 - self:GetThrottle() ^ 2, 0 ) + local VtolZ = ((ply:lvsKeyDown( "+VTOL_Z_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Z_SF" ) and 1 or 0)) + + local DesiredVtol = Vector(VtolX,VtolY,VtolZ) + local NewVtolMove = self:GetNWVtolMove() + (DesiredVtol - self:GetNWVtolMove()) * self.ThrustRateVtol * Delta + + if not ThrottleZero or self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x > 100 then + NewVtolMove.x = 0 + end + + self:SetVtolMove( NewVtolMove ) +end + +function ENT:GetVtolMove() + if self:GetEngineActive() and not self:GetAI() then + return self:GetNWVtolMove() * self.ThrustVtol + else + return Vector(0,0,0) + end +end + +function ENT:PlayerDirectInput( ply, cmd ) + local Pod = self:GetDriverSeat() + + local Delta = FrameTime() + + local KeyLeft = ply:lvsKeyDown( "-ROLL_SF" ) + local KeyRight = ply:lvsKeyDown( "+ROLL_SF" ) + local KeyPitchUp = ply:lvsKeyDown( "+PITCH_SF" ) + local KeyPitchDown = ply:lvsKeyDown( "-PITCH_SF" ) + + local MouseX = cmd:GetMouseX() + local MouseY = cmd:GetMouseY() + + if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then + MouseX = 0 + MouseY = 0 + else + ply:SetEyeAngles( Angle(0,90,0) ) + end + + local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity() + + if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end + if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end + if KeyLeft then MouseX = -(10 / SensX) * ReturnDelta end + if KeyRight then MouseX = (10 / SensX) * ReturnDelta end + + local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 ) + + local Cur = self:GetSteer() + + local Rate = Delta * 3 * ReturnDelta + + local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0) + + local Target = New + Input * Delta * 0.8 + + local Fx = math.Clamp( Target.x, -1, 1 ) + local Fy = math.Clamp( Target.y, -1, 1 ) + + local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0) + local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3) + + local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1) + + self:SetSteer( F ) +end + +function ENT:StartCommand( ply, cmd ) + if self:GetDriver() ~= ply then return end + + if SERVER then + local KeyJump = ply:lvsKeyDown( "VSPEC" ) + + if self._lvsOldKeyJump ~= KeyJump then + self._lvsOldKeyJump = KeyJump + + if KeyJump then + self:ToggleVehicleSpecific() + end + end + end + + if ply:lvsMouseAim() then + if SERVER then + self:PlayerMouseAim( ply, cmd ) + end + else + self:PlayerDirectInput( ply, cmd ) + end + + self:CalcThrottle( ply, cmd ) + self:CalcVtolThrottle( ply, cmd ) +end + +function ENT:GetVehicleType() + return "repulsorlift" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_camera.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_camera.lua new file mode 100644 index 0000000..a2af229 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_camera.lua @@ -0,0 +1,158 @@ + +ENT._lvsSmoothFreeLook = 0 + +function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod ) + local ViewPosL = pod:WorldToLocal( pos ) + + local view = {} + view.fov = fov + view.drawviewer = true + view.angles = self:GetAngles() + + local FreeLook = ply:lvsKeyDown( "FREELOOK" ) + local Zoom = ply:lvsKeyDown( "ZOOM" ) + + if not pod:GetThirdPersonMode() then + + if FreeLook then + view.angles = pod:LocalToWorldAngles( ply:EyeAngles() ) + end + + local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local Dividor = math.abs( velL.x ) + local SideForce = math.Clamp( velL.y / Dividor, -1, 1) + local UpForce = math.Clamp( velL.z / Dividor, -1, 1) + + local ViewPunch = Vector(0,math.Clamp(SideForce * 10,-1,1),math.Clamp(UpForce * 10,-1,1)) + if Zoom then + ViewPunch = Vector(0,0,0) + end + + pod._lerpPosOffset = pod._lerpPosOffset and pod._lerpPosOffset + (ViewPunch - pod._lerpPosOffset) * RealFrameTime() * 5 or Vector(0,0,0) + pod._lerpPos = pos + + view.origin = pos + pod:GetForward() * -pod._lerpPosOffset.y * 0.5 + pod:GetUp() * pod._lerpPosOffset.z * 0.5 + view.angles.p = view.angles.p - pod._lerpPosOffset.z * 0.1 + view.angles.y = view.angles.y + pod._lerpPosOffset.y * 0.1 + view.drawviewer = false + + return view + end + + pod._lerpPos = pod._lerpPos or self:GetPos() + + local radius = 550 + radius = radius + radius * pod:GetCameraDistance() + + if FreeLook then + local velL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + local SideForce = math.Clamp(velL.y / 10,-250,250) + local UpForce = math.Clamp(velL.z / 10,-250,250) + + pod._lerpPosL = pod._lerpPosL and (pod._lerpPosL + (Vector(radius, SideForce,150 + radius * 0.1 + radius * pod:GetCameraHeight() + UpForce) - pod._lerpPosL) * RealFrameTime() * 12) or Vector(0,0,0) + pod._lerpPos = self:LocalToWorld( pod._lerpPosL ) + + view.origin = pod._lerpPos + view.angles = self:LocalToWorldAngles( Angle(0,180,0) ) + else + local TargetPos = self:LocalToWorld( Vector(500,0,150 + radius * 0.1 + radius * pod:GetCameraHeight()) ) + + local Sub = TargetPos - pod._lerpPos + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + local DesiredPos = TargetPos - self:GetForward() * (300 + radius) - Dir * 100 + + pod._lerpPos = pod._lerpPos + (DesiredPos - pod._lerpPos) * RealFrameTime() * (Zoom and 30 or 12) + pod._lerpPosL = self:WorldToLocal( pod._lerpPos ) + + local vel = self:GetVelocity() + + view.origin = pod._lerpPos + view.angles = self:GetAngles() + end + + view.origin = view.origin + ViewPosL + + return view +end + +function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod ) + local cvarFocus = math.Clamp( LVS.cvarCamFocus:GetFloat() , -1, 1 ) + + self._lvsSmoothFreeLook = self._lvsSmoothFreeLook + ((ply:lvsKeyDown( "FREELOOK" ) and 0 or 1) - self._lvsSmoothFreeLook) * RealFrameTime() * 10 + + local view = {} + view.origin = pos + view.fov = fov + view.drawviewer = true + view.angles = (self:GetForward() * (1 + cvarFocus) * self._lvsSmoothFreeLook * 0.8 + ply:EyeAngles():Forward() * math.max(1 - cvarFocus, 1 - self._lvsSmoothFreeLook)):Angle() + + if cvarFocus >= 1 then + view.angles = LerpAngle( self._lvsSmoothFreeLook, ply:EyeAngles(), self:GetAngles() ) + else + view.angles.r = 0 + end + + if not pod:GetThirdPersonMode() then + + view.drawviewer = false + + return view + end + + local radius = 550 + radius = radius + radius * pod:GetCameraDistance() + + local TargetOrigin = view.origin - view.angles:Forward() * radius + view.angles:Up() * (radius * 0.2 + radius * pod:GetCameraHeight()) + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view +end + +function ENT:CalcViewOverride( ply, pos, angles, fov, pod ) + return pos, angles, fov +end + +function ENT:CalcViewDriver( ply, pos, angles, fov, pod ) + if ply:lvsMouseAim() then + return self:CalcViewMouseAim( ply, pos, angles, fov, pod ) + else + return self:CalcViewDirectInput( ply, pos, angles, fov, pod ) + end +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod ) + local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod ) + + if self:GetDriverSeat() == pod then + return self:CalcViewDriver( ply, pos, angles, fov, pod ) + else + return self:CalcViewPassenger( ply, pos, angles, fov, pod ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_deathsound.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_deathsound.lua new file mode 100644 index 0000000..f2da67d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_deathsound.lua @@ -0,0 +1,17 @@ + +function ENT:OnDestroyed() + if not self.DeathSound then return end + + if self:GetVelocity():Length() <= self.MaxVelocity * 0.5 then return end + + self._sndDeath = CreateSound( self, self.DeathSound ) + self._sndDeath:SetSoundLevel( 125 ) + self._sndDeath:PlayEx( 1, 50 + 50 * self:CalcDoppler( LocalPlayer() ) ) +end + +function ENT:StopDeathSound() + if not self._sndDeath then return end + + self._sndDeath:Stop() +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_flyby.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_flyby.lua new file mode 100644 index 0000000..bf48499 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_flyby.lua @@ -0,0 +1,63 @@ + +ENT.FlyByAdvance = 0 + +function ENT:FlyByThink() + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local EntTable = self:GetTable() + + if ply:lvsGetVehicle() == self then self.OldApproaching = false return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Time = CurTime() + + if (EntTable._nextflyby or 0) > Time then return end + + EntTable._nextflyby = Time + 0.1 + + local Vel = self:GetVelocity() + + if self:GetThrottle() <= 0.75 or Vel:Length() <= EntTable.MaxVelocity * 0.75 then return end + + local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance + local ToPlayer = Sub:GetNormalized() + local VelDir = Vel:GetNormalized() + + local ApproachAngle = math.deg( math.acos( math.Clamp( ToPlayer:Dot( VelDir ) ,-1,1) ) ) + + local Approaching = ApproachAngle < 80 + + if Approaching ~= EntTable.OldApproaching then + EntTable.OldApproaching = Approaching + + if Approaching then + self:StopFlyBy() + else + self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) ) + end + end +end + +function ENT:OnFlyBy( Pitch ) + if not self.FlyBySound then return end + + local EntTable = self:GetTable() + + EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound ) + EntTable.flybysnd:SetSoundLevel( 95 ) + EntTable.flybysnd:PlayEx( 1, Pitch ) +end + +function ENT:StopFlyBy() + local EntTable = self:GetTable() + + if not EntTable.flybysnd then return end + + EntTable.flybysnd:Stop() + EntTable.flybysnd = nil +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_hud.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_hud.lua new file mode 100644 index 0000000..06ef5c8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_hud.lua @@ -0,0 +1,72 @@ +ENT.IconEngine = Material( "lvs/engine.png" ) + +function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + local speed = math.Round( LVS:GetUnitValue( self:GetVelocity():Length() ) ,0) + draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT ) + + if ply ~= self:GetDriver() then return end + + local Throttle = self:GetThrottle() + local Col = Throttle <= 1 and color_white or Color(0,0,0,255) + local hX = X + W - H * 0.5 + local hY = Y + H * 0.25 + H * 0.25 + + surface.SetMaterial( self.IconEngine ) + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 ) + surface.SetDrawColor( color_white ) + surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 ) + + if not self:GetEngineActive() then + draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + self:LVSDrawCircle( hX, hY, H * 0.35, math.min( Throttle, 1 ) ) + + if Throttle > 1 then + draw.SimpleText( "+"..math.Round((Throttle - 1) * 100,0).."%" , "LVS_FONT", hX, hY, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end +end + +function ENT:LVSPreHudPaint( X, Y, ply ) + return true +end + +function ENT:LVSHudPaint( X, Y, ply ) + if not self:LVSPreHudPaint( X, Y, ply ) then return end + + if ply ~= self:GetDriver() then return end + + local HitPlane = self:GetEyeTrace( true ).HitPos:ToScreen() + local HitPilot = self:GetEyeTrace().HitPos:ToScreen() + + self:PaintCrosshairCenter( HitPlane ) + self:PaintCrosshairOuter( HitPilot ) + + if ply:lvsMouseAim() and not ply:lvsKeyDown( "FREELOOK" ) then + self:LVSHudPaintMouseAim( HitPlane, HitPilot ) + end + + self:LVSPaintHitMarker( HitPilot ) +end + +function ENT:LVSHudPaintDirectInput( Pos2D ) + self:PaintCrosshairCenter( Pos2D ) + self:PaintCrosshairOuter( Pos2D ) +end + +function ENT:LVSHudPaintMouseAim( HitPlane, HitPilot ) + local Sub = Vector(HitPilot.x,HitPilot.y,0) - Vector(HitPlane.x,HitPlane.y,0) + local Len = Sub:Length() + local Dir = Sub:GetNormalized() + + surface.SetDrawColor( 255, 255, 255, 100 ) + if Len > 20 then + surface.DrawLine( HitPlane.x + Dir.x * 5, HitPlane.y + Dir.y * 5, HitPilot.x - Dir.x * 20, HitPilot.y- Dir.y * 20 ) + + -- shadow + surface.SetDrawColor( 0, 0, 0, 50 ) + surface.DrawLine( HitPlane.x + Dir.x * 5 + 1, HitPlane.y + Dir.y * 5 + 1, HitPilot.x - Dir.x * 20+ 1, HitPilot.y- Dir.y * 20 + 1 ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_init.lua new file mode 100644 index 0000000..759a5e9 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/cl_init.lua @@ -0,0 +1,86 @@ +include("shared.lua") +include("cl_camera.lua") +include("sh_camera_eyetrace.lua") +include("cl_hud.lua") +include("cl_flyby.lua") +include("cl_deathsound.lua") + +ENT.TrailAlpha = 25 + +DEFINE_BASECLASS( "lvs_base" ) + +function ENT:Think() + BaseClass.Think( self ) + + self.EFxScale = self.EFxScale and (self.EFxScale - self.EFxScale * RealFrameTime()) or 0 + + self:CalcOnThrottle() +end + +function ENT:CalcOnThrottle() + if not self:GetEngineActive() then + self._oldOnTHR = nil + + return + end + + local Throttle = self:GetThrottle() + + if self._oldOnTHR ~= Throttle then + if self._oldOnTHR == 0 and Throttle > 0 then + self._IsAccelerating = true + end + + if Throttle > (self._oldOnTHR or 0) then + self._IsAccelerating = true + else + self._IsAccelerating = false + end + + if self._oldOnTHR == 1 then + self:StopBoost() + end + + self._oldOnTHR = Throttle + end + + if self._oldAccelerating ~= self._IsAccelerating then + self._oldAccelerating = self._IsAccelerating + + if not self._IsAccelerating then return end + + self:StartBoost() + end +end + +function ENT:StartBoost() + local T = CurTime() + + if (self._NextSND or 0) > T then return end + + self._NextSND = T + 1 + + self.EFxScale = 100 + + self:OnStartBoost() +end + +function ENT:StopBoost() + local T = CurTime() + + if (self._NextSND or 0) > T then return end + + self._NextSND = T + 1 + + self:OnStopBoost() +end + +function ENT:GetBoost() + return (self.EFxScale or 0) +end + +function ENT:OnStartBoost() +end + +function ENT:OnStopBoost() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/init.lua new file mode 100644 index 0000000..cc8f9f6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/init.lua @@ -0,0 +1,210 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_camera.lua" ) +AddCSLuaFile( "sh_camera_eyetrace.lua" ) +AddCSLuaFile( "cl_hud.lua" ) +AddCSLuaFile( "cl_flyby.lua" ) +AddCSLuaFile( "cl_deathsound.lua" ) +include("shared.lua") +include("sv_ai.lua") +include("sv_mouseaim.lua") +include("sv_components.lua") +include("sv_vehiclespecific.lua") +include("sh_camera_eyetrace.lua") + +DEFINE_BASECLASS( "lvs_base" ) + +function ENT:OnDriverChanged( Old, New, VehicleIsActive ) + + if not VehicleIsActive and self:GetThrottle() == 0 then + self:SetSteer( vector_origin ) + self:SetVtolMove( vector_origin ) + end + + self:OnPassengerChanged( Old, New, 1 ) +end + +function ENT:StartEngine() + if self:GetEngineActive() or not self:IsEngineStartAllowed() then return end + + self:GetPhysicsObject():EnableGravity( false ) + + BaseClass.StartEngine( self ) +end + +function ENT:StopEngine() + if not self:GetEngineActive() then return end + + self:GetPhysicsObject():EnableGravity( true ) + + BaseClass.StopEngine( self ) +end + +function ENT:OnCreateAI() + self:StartEngine() + self.COL_GROUP_OLD = self:GetCollisionGroup() + self:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ) +end + +function ENT:OnRemoveAI() + self:StopEngine() + self:SetCollisionGroup( self.COL_GROUP_OLD or COLLISION_GROUP_NONE ) +end + +function ENT:ApproachTargetAngle( TargetAngle, OverridePitch, OverrideYaw, OverrideRoll, FreeMovement ) + local LocalAngles = self:WorldToLocalAngles( TargetAngle ) + + if self:GetAI() then self:SetAIAimVector( LocalAngles:Forward() ) end + + local LocalAngPitch = LocalAngles.p + local LocalAngYaw = LocalAngles.y + local LocalAngRoll = LocalAngles.r + + local TargetForward = TargetAngle:Forward() + local Forward = self:GetForward() + + local AngDiff = math.deg( math.acos( math.Clamp( Forward:Dot( TargetForward ) ,-1,1) ) ) + + local WingFinFadeOut = math.max( (90 - AngDiff ) / 90, 0 ) + local RudderFadeOut = math.min( math.max( (120 - AngDiff ) / 120, 0 ) * 3, 1 ) + + local AngVel = self:GetPhysicsObject():GetAngleVelocity() + + local SmoothPitch = math.Clamp( math.Clamp(AngVel.y / 100,-0.25,0.25) / math.abs( LocalAngPitch ), -1, 1 ) + local SmoothYaw = math.Clamp( math.Clamp(AngVel.z / 100,-0.25,0.25) / math.abs( LocalAngYaw ), -1, 1 ) + + local Pitch = math.Clamp( -LocalAngPitch / 10 + SmoothPitch, -1, 1 ) + local Yaw = math.Clamp( -LocalAngYaw / 2 + SmoothYaw,-1,1) * RudderFadeOut + local Roll = math.Clamp( (-math.Clamp(LocalAngYaw * 16 * self:GetThrottle(),-90,90) + LocalAngRoll * RudderFadeOut * 0.75) * WingFinFadeOut / 180 , -1 , 1 ) + + if FreeMovement then + Roll = math.Clamp( -LocalAngYaw * WingFinFadeOut / 180 , -1 , 1 ) + end + + if OverridePitch and OverridePitch ~= 0 then + Pitch = OverridePitch + end + + if OverrideYaw and OverrideYaw ~= 0 then + Yaw = OverrideYaw + end + + if OverrideRoll and OverrideRoll ~= 0 then + Roll = OverrideRoll + end + + self:SetSteer( Vector( math.Clamp(Roll * 1.25,-1,1), math.Clamp(-Pitch * 1.25,-1,1), -Yaw) ) +end + +function ENT:CalcAero( phys, deltatime, EntTable ) + if not EntTable then + EntTable = self:GetTable() + end + + -- mouse aim needs to run at high speed. + if self:GetAI() then + if EntTable._lvsAITargetAng then + self:ApproachTargetAngle( EntTable._lvsAITargetAng ) + end + else + local ply = self:GetDriver() + if IsValid( ply ) and ply:lvsMouseAim() then + self:PlayerMouseAim( ply ) + end + end + + local Steer = self:GetSteer() + + local Forward = self:GetForward() + local Left = -self:GetRight() + + local Vel = self:GetVelocity() + local VelForward = Vel:GetNormalized() + + local GravityPitch = 0 + local GravityYaw = 0 + + -- crash bebehavior + if self:IsDestroyed() then + local WorldGravity = self:GetWorldGravity() + local WorldUp = self:GetWorldUp() + + local Up = self:GetUp() + + Steer = phys:GetAngleVelocity() / 200 + + local PitchPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Up ) ,-1,1) ) ) - 90) / 90 + local YawPull = (math.deg( math.acos( math.Clamp( WorldUp:Dot( Left ) ,-1,1) ) ) - 90) / 90 + + local GravMul = WorldGravity / 600 + + GravityPitch = math.abs( PitchPull ) ^ 1.25 * self:Sign( PitchPull ) * GravMul + GravityYaw = math.abs( YawPull ) ^ 1.25 * self:Sign( YawPull ) * GravMul + + if not phys:IsGravityEnabled() then + phys:EnableGravity( true ) + end + end + + local Pitch = math.Clamp(Steer.y - GravityPitch,-1,1) * EntTable.TurnRatePitch * 3 + local Yaw = math.Clamp(Steer.z * 4 + GravityYaw,-1,1) * EntTable.TurnRateYaw + local Roll = math.Clamp(Steer.x * 1.5,-1,1) * EntTable.TurnRateRoll * 12 + + local VelL = self:WorldToLocal( self:GetPos() + Vel ) + + local MulZ = (math.max( math.deg( math.acos( math.Clamp( VelForward:Dot( Forward ) ,-1,1) ) ) - math.abs( Steer.y ), 0 ) / 90) * 0.3 + local MulY = (math.max( math.abs( math.deg( math.acos( math.Clamp( VelForward:Dot( Left ) ,-1,1) ) ) - 90 ) - math.abs( Steer.z ), 0 ) / 90) * 0.15 + + local VtolMove = self:GetVtolMove() + + local Move = Vector( (VtolMove.x < 0) and -math.min(VelL.x * 0.15,0) or 0, -VelL.y * MulY, -VelL.z * MulZ ) + VtolMove + + return Move, Vector( Roll, Pitch, Yaw ) +end + +function ENT:OnSkyCollide( data, PhysObj ) + local NewVelocity = self:VectorSubtractNormal( data.HitNormal, data.OurOldVelocity ) - data.HitNormal * math.Clamp(self:GetThrustStrenght() * self.MaxThrust,250,800) + + PhysObj:SetVelocityInstantaneous( NewVelocity ) + PhysObj:SetAngleVelocityInstantaneous( data.OurOldAngularVelocity ) + + return true +end + +function ENT:PhysicsSimulate( phys, deltatime ) + if self:GetEngineActive() then + phys:Wake() + else + return vector_origin, vector_origin, SIM_NOTHING + end + + local EntTable = self:GetTable() + + local Aero, Torque = self:CalcAero( phys, deltatime, EntTable ) + + local Thrust = self:GetThrustStrenght() * EntTable.MaxThrust * 100 + + if self:IsDestroyed() then + Thrust = math.max( Thrust, 0 ) -- dont allow braking, but allow accelerating while destroyed + end + + local ForceLinear = (Aero * 10000 * EntTable.ForceLinearMultiplier + Vector(Thrust,0,0)) * deltatime + local ForceAngle = (Torque * 25 * EntTable.ForceAngleMultiplier - phys:GetAngleVelocity() * 1.5 * EntTable.ForceAngleDampingMultiplier) * deltatime * 250 + + return self:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, SIM_LOCAL_ACCELERATION ) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, ForceLinear, phys, deltatime, simulate ) + return ForceAngle, ForceLinear, simulate +end + +function ENT:OnMaintenance() + for _, Engine in pairs( self:GetEngines() ) do + if not IsValid( Engine ) then continue end + + if not Engine.SetHP or not Engine.GetMaxHP or not Engine.SetDestroyed then continue end + + Engine:SetHP( Engine:GetMaxHP() ) + Engine:SetDestroyed( false ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sh_camera_eyetrace.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sh_camera_eyetrace.lua new file mode 100644 index 0000000..fb7166a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sh_camera_eyetrace.lua @@ -0,0 +1,58 @@ + +function ENT:GetEyeTrace( trace_forward ) + local startpos = self:LocalToWorld( self:OBBCenter() ) + + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + startpos = pod:LocalToWorld( pod:OBBCenter() ) + end + + local AimVector = trace_forward and self:GetForward() or self:GetAimVector() + + local data = { + start = startpos, + endpos = (startpos + AimVector * 50000), + filter = self:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( data ) + + return trace +end + +function ENT:GetAimVector() + if self:GetAI() then + return self:GetAIAimVector() + end + + local Driver = self:GetDriver() + + if not IsValid( Driver ) then return self:GetForward() end + + if not Driver:lvsMouseAim() then + if Driver:lvsKeyDown( "FREELOOK" ) then + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return Driver:EyeAngles():Forward() end + + if pod:GetThirdPersonMode() then + return -self:GetForward() + else + return Driver:GetAimVector() + end + else + return self:GetForward() + end + end + + if SERVER then + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return Driver:EyeAngles():Forward() end + + return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward() + else + return Driver:EyeAngles():Forward() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/shared.lua new file mode 100644 index 0000000..bc37b0c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/shared.lua @@ -0,0 +1,194 @@ + +ENT.Base = "lvs_base" + +ENT.PrintName = "[LVS] Base Starfighter" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MaxVelocity = 3000 +ENT.MaxThrust = 3000 + +ENT.ThrustVtol = 55 +ENT.ThrustRateVtol = 3 + +ENT.ThrottleRateUp = 0.6 +ENT.ThrottleRateDown = 0.6 + +ENT.TurnRatePitch = 1 +ENT.TurnRateYaw = 1 +ENT.TurnRateRoll = 1 + +ENT.ForceLinearMultiplier = 1 + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +ENT.DisableBallistics = true + +function ENT:SetupDataTables() + self:AddDT( "Vector", "Steer" ) + self:AddDT( "Vector", "AIAimVector" ) + self:AddDT( "Vector", "NWVtolMove" ) + self:AddDT( "Float", "NWThrottle" ) + self:AddDT( "Float", "MaxThrottle" ) + + if SERVER then + self:SetMaxThrottle( 1 ) + end + + self:CreateBaseDT() +end + +function ENT:PlayerDirectInput( ply, cmd ) + local Pod = self:GetDriverSeat() + + local Delta = FrameTime() + + local KeyLeft = ply:lvsKeyDown( "-ROLL_SF" ) + local KeyRight = ply:lvsKeyDown( "+ROLL_SF" ) + local KeyPitchUp = ply:lvsKeyDown( "+PITCH_SF" ) + local KeyPitchDown = ply:lvsKeyDown( "-PITCH_SF" ) + local KeyRollRight = ply:lvsKeyDown( "+YAW_SF" ) + local KeyRollLeft = ply:lvsKeyDown( "-YAW_SF" ) + + local MouseX = cmd:GetMouseX() + local MouseY = cmd:GetMouseY() + + if ply:lvsKeyDown( "FREELOOK" ) and not Pod:GetThirdPersonMode() then + MouseX = 0 + MouseY = 0 + else + ply:SetEyeAngles( Angle(0,90,0) ) + end + + local SensX, SensY, ReturnDelta = ply:lvsMouseSensitivity() + + if KeyPitchDown then MouseY = (10 / SensY) * ReturnDelta end + if KeyPitchUp then MouseY = -(10 / SensY) * ReturnDelta end + if KeyRollRight or KeyRollLeft then + local NewX = (KeyRollRight and 10 or 0) - (KeyRollLeft and 10 or 0) + + MouseX = (NewX / SensX) * ReturnDelta + end + + local Input = Vector( MouseX * 0.4 * SensX, MouseY * SensY, 0 ) + + local Cur = self:GetSteer() + + local Rate = Delta * 3 * ReturnDelta + + local New = Vector(Cur.x, Cur.y, 0) - Vector( math.Clamp(Cur.x * Delta * 5 * ReturnDelta,-Rate,Rate), math.Clamp(Cur.y * Delta * 5 * ReturnDelta,-Rate,Rate), 0) + + local Target = New + Input * Delta * 0.8 + + local Fx = math.Clamp( Target.x, -1, 1 ) + local Fy = math.Clamp( Target.y, -1, 1 ) + + local TargetFz = (KeyLeft and 1 or 0) - (KeyRight and 1 or 0) + local Fz = Cur.z + math.Clamp(TargetFz - Cur.z,-Rate * 3,Rate * 3) + + local F = Cur + (Vector( Fx, Fy, Fz ) - Cur) * math.min(Delta * 100,1) + + self:SetSteer( F ) +end + +function ENT:CalcThrottle( ply, cmd ) + if CLIENT then return end + + local Delta = FrameTime() + + local ThrottleUp = ply:lvsKeyDown( "+THRUST_SF" ) and self.ThrottleRateUp or 0 + local ThrottleDown = ply:lvsKeyDown( "-THRUST_SF" ) and -self.ThrottleRateDown or 0 + + local Throttle = (ThrottleUp + ThrottleDown) * Delta + + self:SetThrottle( self:GetThrottle() + Throttle ) +end + +function ENT:CalcVtolThrottle( ply, cmd ) + local Delta = FrameTime() + + local ThrottleZero = self:GetThrottle() <= 0 + + local VtolX = ThrottleZero and (ply:lvsKeyDown( "-VTOL_X_SF" ) and -1 or 0) or 0 + local VtolY = ((ply:lvsKeyDown( "+VTOL_Y_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Y_SF" ) and 1 or 0)) + local VtolZ = ((ply:lvsKeyDown( "+VTOL_Z_SF" ) and 1 or 0) - (ply:lvsKeyDown( "-VTOL_Z_SF" ) and 1 or 0)) + + local DesiredVtol = Vector(VtolX,VtolY,VtolZ) + local NewVtolMove = self:GetNWVtolMove() + (DesiredVtol - self:GetNWVtolMove()) * self.ThrustRateVtol * Delta + + if not ThrottleZero or self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x > 100 then + NewVtolMove.x = 0 + end + + self:SetVtolMove( NewVtolMove ) +end + +function ENT:SetVtolMove( NewMove ) + if self:GetEngineActive() then + self:SetNWVtolMove( NewMove ) + else + self:SetNWVtolMove( Vector(0,0,0) ) + end +end + +function ENT:SetThrottle( NewThrottle ) + if self:GetEngineActive() then + self:SetNWThrottle( math.Clamp(NewThrottle,0,self:GetMaxThrottle()) ) + else + self:SetNWThrottle( 0 ) + end +end + +function ENT:GetThrottle() + if self:GetEngineActive() then + return self:GetNWThrottle() + else + return 0 + end +end + +function ENT:GetVtolMove() + if self:GetEngineActive() and not self:GetAI() then + return self:GetNWVtolMove() * self.ThrustVtol * (1 - math.min( self:GetThrottle(), 1 )) + else + return Vector(0,0,0) + end +end + +function ENT:StartCommand( ply, cmd ) + if self:GetDriver() ~= ply then return end + + if SERVER then + local KeyJump = ply:lvsKeyDown( "VSPEC" ) + + if self._lvsOldKeyJump ~= KeyJump then + self._lvsOldKeyJump = KeyJump + + if KeyJump then + self:ToggleVehicleSpecific() + end + end + end + + if not ply:lvsMouseAim() then + self:PlayerDirectInput( ply, cmd ) + end + + self:CalcThrottle( ply, cmd ) + self:CalcVtolThrottle( ply, cmd ) +end + +function ENT:GetThrustStrenght() + local ForwardVelocity = self:WorldToLocal( self:GetPos() + self:GetVelocity() ).x + + return (self.MaxVelocity * self:GetThrottle() - ForwardVelocity) / self.MaxVelocity +end + +function ENT:GetVehicleType() + return "starfighter" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_ai.lua new file mode 100644 index 0000000..2f2db04 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_ai.lua @@ -0,0 +1,192 @@ + +function ENT:OnCreateAI() + self:StartEngine() +end + +function ENT:OnRemoveAI() + self:StopEngine() +end + +function ENT:RunAI() + local RangerLength = 15000 + local mySpeed = self:GetVelocity():Length() + local MinDist = 600 + mySpeed + + local StartPos = self:LocalToWorld( self:OBBCenter() ) + + local TraceFilter = self:GetCrosshairFilterEnts() + + local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,20,0) ):Forward() * RangerLength } ) + local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(0,-20,0) ):Forward() * RangerLength } ) + + local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,65,0) ):Forward() * RangerLength } ) + local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(25,-65,0) ):Forward() * RangerLength } ) + + local FrontLeft3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,65,0) ):Forward() * RangerLength } ) + local FrontRight3 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-25,-65,0) ):Forward() * RangerLength } ) + + local FrontUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(-20,0,0) ):Forward() * RangerLength } ) + local FrontDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:LocalToWorldAngles( Angle(20,0,0) ):Forward() * RangerLength } ) + + local TraceForward = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + self:GetForward() * RangerLength } ) + local TraceDown = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,-RangerLength) } ) + local TraceUp = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Vector(0,0,RangerLength) } ) + + local cAvoid = Vector(0,0,0) + + local myRadius = self:BoundingRadius() + local myPos = self:GetPos() + local myDir = self:GetForward() + for _, v in pairs( LVS:GetVehicles() ) do + if v == self then continue end + + local theirRadius = v:BoundingRadius() + local Sub = (myPos - v:GetPos()) + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + if Dist < (theirRadius + myRadius + 200) then + if math.deg( math.acos( math.Clamp( myDir:Dot( -Dir ) ,-1,1) ) ) < 90 then + cAvoid = cAvoid + Dir * (theirRadius + myRadius + 500) + end + end + end + + local FLp = FrontLeft.HitPos + FrontLeft.HitNormal * MinDist + cAvoid * 8 + local FRp = FrontRight.HitPos + FrontRight.HitNormal * MinDist + cAvoid * 8 + + local FL2p = FrontLeft2.HitPos + FrontLeft2.HitNormal * MinDist + local FR2p = FrontRight2.HitPos + FrontRight2.HitNormal * MinDist + + local FL3p = FrontLeft3.HitPos + FrontLeft3.HitNormal * MinDist + local FR3p = FrontRight3.HitPos + FrontRight3.HitNormal * MinDist + + local FUp = FrontUp.HitPos + FrontUp.HitNormal * MinDist + local FDp = FrontDown.HitPos + FrontDown.HitNormal * MinDist + + local Up = TraceUp.HitPos + TraceUp.HitNormal * MinDist + local Dp = TraceDown.HitPos + TraceDown.HitNormal * MinDist + + local TargetPos = (FLp+FRp+FL2p+FR2p+FL3p+FR3p+FUp+FDp+Up+Dp) / 10 + + local alt = (StartPos - TraceDown.HitPos):Length() + local ceiling = (StartPos - TraceUp.HitPos):Length() + + local WallDist = (StartPos - TraceForward.HitPos):Length() + + local Throttle = math.min( WallDist / mySpeed, 1 ) + + self._AIFireInput = false + + if alt < 600 or ceiling < 600 or WallDist < (MinDist * 3 * (math.deg( math.acos( math.Clamp( Vector(0,0,1):Dot( myDir ) ,-1,1) ) ) / 180) ^ 2) then + Throttle = 1 + + if self:HitGround() then + TargetPos.z = StartPos.z + 750 + end + else + if IsValid( self:GetHardLockTarget() ) then + TargetPos = self:GetHardLockTarget():GetPos() + cAvoid * 8 + else + if alt > mySpeed then + local Target = self._LastAITarget + + if not IsValid( self._LastAITarget ) or not self:AITargetInFront( self._LastAITarget, 135 ) or not self:AICanSee( self._LastAITarget ) then + Target = self:AIGetTarget() + end + + if IsValid( Target ) then + if self:AITargetInFront( Target, 65 ) then + local T = CurTime() + self:EntIndex() * 1337 + TargetPos = Target:GetPos() + cAvoid * 8 + Vector(0,0, math.sin( T * 5 ) * 500 ) + Target:GetVelocity() * math.abs( math.cos( T * 13.37 ) ) * 5 + + Throttle = math.min( (StartPos - TargetPos):Length() / mySpeed, 1 ) + + local tr = util.TraceHull( { + start = StartPos, + endpos = (StartPos + self:GetForward() * 50000), + mins = Vector( -50, -50, -50 ), + maxs = Vector( 50, 50, 50 ), + filter = TraceFilter + } ) + + local CanShoot = (IsValid( tr.Entity ) and tr.Entity.LVS and tr.Entity.GetAITEAM) and (tr.Entity:GetAITEAM() ~= self:GetAITEAM() or tr.Entity:GetAITEAM() == 0) or true + + if CanShoot and self:AITargetInFront( Target, 22 ) then + local CurHeat = self:GetNWHeat() + local CurWeapon = self:GetSelectedWeapon() + + if CurWeapon > 2 then + self:AISelectWeapon( 1 ) + else + if CurHeat > 0.9 then + if CurWeapon == 1 and self:AIHasWeapon( 2 ) then + self:AISelectWeapon( 2 ) + + elseif CurWeapon == 2 then + self:AISelectWeapon( 1 ) + end + else + if CurHeat == 0 and math.cos( T ) > 0 then + self:AISelectWeapon( 1 ) + end + end + end + + self._AIFireInput = true + end + else + self:AISelectWeapon( 1 ) + + if alt > 6000 and self:AITargetInFront( Target, 90 ) then + TargetPos = Target:GetPos() + end + end + end + else + TargetPos.z = StartPos.z + 2000 + + self:EnableVehicleSpecific() + end + end + end + + self:SetThrottle( Throttle ) + + self.smTargetPos = self.smTargetPos and self.smTargetPos + (TargetPos - self.smTargetPos) * FrameTime() or self:GetPos() + + self._lvsAITargetAng = (self.smTargetPos - self:GetPos()):GetNormalized():Angle() +end + +function ENT:AISelectWeapon( ID ) + if ID == self:GetSelectedWeapon() then return end + + local T = CurTime() + + if (self._nextAISwitchWeapon or 0) > T then return end + + self._nextAISwitchWeapon = T + math.random(3,6) + + self:SelectWeapon( ID ) +end + +function ENT:OnAITakeDamage( dmginfo ) + local attacker = dmginfo:GetAttacker() + + if not IsValid( attacker ) then return end + + if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then + self:SetHardLockTarget( attacker ) + end +end + +function ENT:SetHardLockTarget( target ) + self._HardLockTarget = target + self._HardLockTime = CurTime() + 4 +end + +function ENT:GetHardLockTarget() + if (self._HardLockTime or 0) < CurTime() then return NULL end + + return self._HardLockTarget +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_components.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_components.lua new file mode 100644 index 0000000..9e72dbe --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_components.lua @@ -0,0 +1,78 @@ + +function ENT:AddEngine( pos, ang, mins, maxs, health ) + local Engine = ents.Create( "lvs_starfighter_engine" ) + + if not IsValid( Engine ) then + self:Remove() + + print("LVS: Failed to create engine entity. Vehicle terminated.") + + return + end + + Engine:SetPos( self:LocalToWorld( pos ) ) + Engine:SetAngles( self:GetAngles() ) + Engine:Spawn() + Engine:Activate() + Engine:SetParent( self ) + Engine:SetBase( self ) + + if not health then + health = self.MaxHealth / 8 + end + + Engine:SetMaxHP( health ) + Engine:SetHP( health ) + self:DeleteOnRemove( Engine ) + self:TransferCPPI( Engine ) + + self:AddDS( { + pos = pos, + ang = (ang or Angle(0,0,0)), + mins = (mins or Vector(-40,-20,-30)), + maxs = (maxs or Vector(40,20,30)), + Callback = function( tbl, ent, dmginfo ) + if dmginfo:GetDamage() <= 0 then return end + + Engine:TakeDamageInfo( dmginfo ) + end + } ) + + + if not istable( self._lvsEngines ) then + self._lvsEngines = {} + end + + table.insert( self._lvsEngines, Engine ) + + return Engine +end + +function ENT:GetEngines() + return self._lvsEngines or {} +end + +function ENT:AddEngineSound( pos ) + local EngineSND = ents.Create( "lvs_starfighter_soundemitter" ) + + if not IsValid( EngineSND ) then + self:Remove() + + print("LVS: Failed to create engine sound entity. Vehicle terminated.") + + return + end + + EngineSND:SetPos( self:LocalToWorld( pos ) ) + EngineSND:SetAngles( self:GetAngles() ) + EngineSND:Spawn() + EngineSND:Activate() + EngineSND:SetParent( self ) + EngineSND:SetBase( self ) + + self:DeleteOnRemove( EngineSND ) + + self:TransferCPPI( EngineSND ) + + return EngineSND +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_mouseaim.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_mouseaim.lua new file mode 100644 index 0000000..7be477d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_mouseaim.lua @@ -0,0 +1,45 @@ + +function ENT:PlayerMouseAim( ply ) + local Pod = self:GetDriverSeat() + + local PitchUp = ply:lvsKeyDown( "+PITCH_SF" ) + local PitchDown = ply:lvsKeyDown( "-PITCH_SF" ) + local YawRight = ply:lvsKeyDown( "+YAW_SF" ) + local YawLeft = ply:lvsKeyDown( "-YAW_SF" ) + local RollRight = ply:lvsKeyDown( "+ROLL_SF" ) + local RollLeft = ply:lvsKeyDown( "-ROLL_SF" ) + + local FreeLook = ply:lvsKeyDown( "FREELOOK" ) + + local EyeAngles = Pod:WorldToLocalAngles( ply:EyeAngles() ) + + if FreeLook then + if isangle( self.StoredEyeAngles ) then + EyeAngles = self.StoredEyeAngles + end + else + self.StoredEyeAngles = EyeAngles + end + + local OverridePitch = 0 + local OverrideYaw = 0 + local OverrideRoll = (RollRight and 1 or 0) - (RollLeft and 1 or 0) + + if PitchUp or PitchDown then + EyeAngles = self:GetAngles() + + self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0) + + OverridePitch = (PitchUp and 1 or 0) - (PitchDown and 1 or 0) + end + + if YawRight or YawLeft then + EyeAngles = self:GetAngles() + + self.StoredEyeAngles = Angle(EyeAngles.p,EyeAngles.y,0) + + OverrideYaw = (YawRight and 1 or 0) - (YawLeft and 1 or 0) + end + + self:ApproachTargetAngle( EyeAngles, OverridePitch, OverrideYaw, OverrideRoll, FreeLook ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_vehiclespecific.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_vehiclespecific.lua new file mode 100644 index 0000000..746e146 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_starfighter/sv_vehiclespecific.lua @@ -0,0 +1,25 @@ + +function ENT:ToggleVehicleSpecific() + self._VSPEC = not self._VSPEC + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:EnableVehicleSpecific() + if self._VSPEC then return end + + self._VSPEC = true + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:DisableVehicleSpecific() + if not self._VSPEC then return end + + self._VSPEC = false + + self:OnVehicleSpecificToggled( self._VSPEC ) +end + +function ENT:OnVehicleSpecificToggled( IsActive ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_camera.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_camera.lua new file mode 100644 index 0000000..429fbf4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_camera.lua @@ -0,0 +1,75 @@ + +function ENT:CalcViewOverride( ply, pos, angles, fov, pod ) + return pos, angles, fov +end + +function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:CalcViewDriver( ply, pos, angles, fov, pod ) + pos = pos + pod:GetUp() * 7 - pod:GetRight() * 11 + + if ply:lvsMouseAim() then + angles = ply:EyeAngles() + + return self:CalcViewMouseAim( ply, pos, angles, fov, pod ) + else + return self:CalcViewDirectInput( ply, pos, angles, fov, pod ) + end +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod ) + local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod ) + + local new_fov = math.min( fov + self:CalcViewPunch( ply, pos, angles, fov, pod ), 180 ) + + if self:GetDriverSeat() == pod then + return self:CalcViewDriver( ply, pos, angles, new_fov, pod ) + else + return self:CalcViewPassenger( ply, pos, angles, new_fov, pod ) + end +end + +function ENT:SuppressViewPunch( time ) + self._viewpunch_supressed_time = CurTime() + (time or 0.2) +end + +function ENT:IsViewPunchSuppressed() + return (self._viewpunch_supressed_time or 0) > CurTime() +end + +function ENT:CalcViewPunch( ply, pos, angles, fov, pod ) + local Vel = self:GetVelocity() + local VelLength = Vel:Length() + local VelPercentMaxSpeed = math.min( VelLength / 1000, 1 ) + + if ply:lvsMouseAim() then + angles = ply:EyeAngles() + end + + local direction = (90 - self:AngleBetweenNormal( angles:Forward(), Vel:GetNormalized() )) / 90 + + local FovValue = math.min( VelPercentMaxSpeed ^ 2 * 100, 15 ) + + local Throttle = self:GetThrottle() + local Brake = self:GetBrake() + + if self:IsViewPunchSuppressed() then + self._viewpunch_fov = self._viewpunch_fov and self._viewpunch_fov + (-VelPercentMaxSpeed * FovValue - self._viewpunch_fov) * RealFrameTime() or 0 + else + local newFov =(1 - VelPercentMaxSpeed) * Throttle * FovValue - VelPercentMaxSpeed * Brake * FovValue + + self._viewpunch_fov = self._viewpunch_fov and self._viewpunch_fov + (newFov - self._viewpunch_fov) * RealFrameTime() * 10 or 0 + end + + return self._viewpunch_fov * (90 - self:AngleBetweenNormal( angles:Forward(), Vel:GetNormalized() )) / 90 +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_exhausteffects.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_exhausteffects.lua new file mode 100644 index 0000000..e95cef4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_exhausteffects.lua @@ -0,0 +1,112 @@ + +function ENT:IsBackFireEnabled() + if not isfunction( self.GetBackfire ) then return false end + + return self:GetBackfire() +end + +function ENT:DoExhaustFX( Magnitude ) + for _, data in ipairs( self.ExhaustPositions ) do + if data.bodygroup then + if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end + end + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( data.pos ) ) + effectdata:SetNormal( self:LocalToWorldAngles( data.ang ):Forward() ) + effectdata:SetMagnitude( Magnitude ) + effectdata:SetEntity( self ) + util.Effect( data.effect or "lvs_exhaust", effectdata ) + end +end + +function ENT:ExhaustEffectsThink() + if not self:IsBackFireEnabled() then return end + + local Throttle = self:GetThrottle() + + if self._backfireTHR ~= Throttle then + self._backfireTHR = Throttle + + if Throttle ~= 0 then return end + + self:CalcExhaustPop() + end +end + +function ENT:CalcExhaustPop() + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + local RPM = Engine:GetRPM() + + local num = (Engine:GetRPM() / 500) + + local Throttle = self:GetThrottle() + + if Throttle > 0 and Throttle < 0.6 then return end + + if Throttle ~= 0 or (not IsValid( self:GetTurbo() ) and not IsValid( self:GetCompressor() )) then num = 0 end + + for i = 0, num do + timer.Simple( self.TransShiftSpeed + i * 0.1 , function() + if not IsValid( self ) then return end + + if i > 0 and self:GetThrottle() ~= 0 then return end + + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + local RPM = Engine:GetRPM() + + if RPM < self.EngineMaxRPM * 0.6 then return end + + if i == 0 then + self:DoExhaustPop( LVS.EngineVolume ) + else + self:DoExhaustPop( 0.75 * LVS.EngineVolume ) + end + end ) + end +end + +function ENT:DoExhaustPop( volume ) + if not istable( self.ExhaustPositions ) then return end + + for _, data in ipairs( self.ExhaustPositions ) do + if data.bodygroup then + if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end + end + + timer.Simple( math.Rand(0,0.2), function() + local effectdata = EffectData() + effectdata:SetOrigin( data.pos ) + effectdata:SetAngles( data.ang ) + effectdata:SetEntity( self ) + effectdata:SetMagnitude( volume or 1 ) + util.Effect( "lvs_carexhaust_pop", effectdata ) + end ) + end +end + +function ENT:DoExhaustBackFire() + if not istable( self.ExhaustPositions ) then return end + + for _, data in ipairs( self.ExhaustPositions ) do + if data.bodygroup then + if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end + end + + if math.random( 1, math.floor( #self.ExhaustPositions * 0.75 ) ) ~= 1 then continue end + + timer.Simple( math.Rand(0.5,1), function() + local effectdata = EffectData() + effectdata:SetOrigin( data.pos ) + effectdata:SetAngles( data.ang ) + effectdata:SetEntity( self ) + util.Effect( "lvs_carexhaust_backfire", effectdata ) + end ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_flyby.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_flyby.lua new file mode 100644 index 0000000..b662553 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_flyby.lua @@ -0,0 +1,72 @@ + +ENT.FlyByVelocity = 500 +ENT.FlyByMinThrottle = 0 +ENT.FlyByAdvance = 1 +ENT.FlyBySound = "lvs/vehicles/generic/car_flyby.wav" + +function ENT:FlyByThink() + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local veh = ply:lvsGetVehicle() + + local EntTable = self:GetTable() + + if veh == self then EntTable.OldApproaching = false return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + if IsValid( veh ) and ViewEnt == ply then + ViewEnt = veh + end + + local Time = CurTime() + + if (EntTable._nextflyby or 0) > Time then return end + + EntTable._nextflyby = Time + 0.1 + + local Vel = self:GetVelocity() + + if self:GetThrottle() <= EntTable.FlyByMinThrottle or Vel:Length() <= EntTable.FlyByVelocity then return end + + local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance + local ToPlayer = Sub:GetNormalized() + local VelDir = Vel:GetNormalized() + + local ApproachAngle = self:AngleBetweenNormal( ToPlayer, VelDir ) + + local Approaching = ApproachAngle < 80 + + if Approaching ~= EntTable.OldApproaching then + EntTable.OldApproaching = Approaching + + if Approaching then + self:StopFlyBy() + else + self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) ) + end + end +end + +function ENT:OnFlyBy( Pitch ) + if not self.FlyBySound then return end + + local EntTable = self:GetTable() + + EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound ) + EntTable.flybysnd:SetSoundLevel( 95 ) + EntTable.flybysnd:PlayEx( 1, Pitch ) +end + +function ENT:StopFlyBy() + local EntTable = self:GetTable() + + if not EntTable.flybysnd then return end + + EntTable.flybysnd:Stop() + EntTable.flybysnd = nil +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_hud.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_hud.lua new file mode 100644 index 0000000..b2d8e01 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_hud.lua @@ -0,0 +1,237 @@ + +include("cl_hud_speedometer.lua") + +ENT.IconEngine = Material( "lvs/engine.png" ) +ENT.IconFuel = Material( "lvs/fuel.png" ) + +local WaveScale = 0 +local WaveMaterial = Material( "effects/select_ring" ) +local oldThrottleActive = false +local oldReverse = false +local oldGear = -1 + +function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + if self:GetRacingHud() then return end + + local EntTable = self:GetTable() + + local T = CurTime() + + if (EntTable._nextRefreshVel or 0) < T then + EntTable._nextRefreshVel = T + 0.1 + EntTable._refreshVel = self:GetVelocity():Length() + end + + local speed = math.Round( LVS:GetUnitValue( EntTable._refreshVel or 0 ) , 0 ) + draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT ) + + if ply ~= self:GetDriver() then return end + + local Throttle = self:GetThrottle() + local Col = Throttle <= 1 and color_white or Color(0,0,0,255) + local hX = X + W - H * 0.5 + local hY = Y + H * 0.25 + H * 0.25 + + local fueltank = self:GetFuelTank() + + if IsValid( fueltank ) and fueltank:GetFuel() <= 0 then + surface.SetMaterial( EntTable.IconFuel ) + else + surface.SetMaterial( EntTable.IconEngine ) + end + + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 ) + surface.SetDrawColor( color_white ) + surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 ) + + if not self:GetEngineActive() then + draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + oldThrottleActive = false + + local Reverse = self:GetReverse() + + if oldReverse ~= Reverse then + oldReverse = Reverse + + WaveScale = 1 + end + + local IsManual = self:IsManualTransmission() + local Gear = self:GetGear() + + if oldGear ~= Gear then + oldGear = Gear + + WaveScale = 1 + end + + if WaveScale > 0 then + WaveScale = math.max( WaveScale - RealFrameTime() * 2, 0 ) + + local WaveRadius = (1 - WaveScale) * H * 1.5 + + surface.SetDrawColor( 0, 127, 255, 255 * WaveScale ^ 2 ) + surface.SetMaterial( WaveMaterial ) + + surface.DrawTexturedRectRotated( hX, hY, WaveRadius, WaveRadius, 0 ) + + if not Reverse and not IsManual then + draw.SimpleText( "D" , "LVS_FONT", hX, hY, Color(0,0,0,math.min(800 * WaveScale ^ 2,255)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + end + + if IsManual then + draw.SimpleText( (Reverse and -1 or 1) * Gear , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + if Reverse then + draw.SimpleText( "R" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + end + end + + self:LVSDrawCircle( hX, hY, H * 0.35, math.min( Throttle, 1 ) ) + + if Throttle > 1 then + draw.SimpleText( "+"..math.Round((Throttle - 1) * 100,0).."%" , "LVS_FONT", hX, hY, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end +end + +LVS:AddHudEditor( "CarMenu", ScrW() - 690, ScrH() - 85, 220, 75, 220, 75, "CAR MENU", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintCarMenu then return end + vehicle:LVSHudPaintCarMenu( X, Y, W, H, ScrX, ScrY, ply ) + end +) +local function DrawTexturedRect( X, Y, size, selected ) + local oz = 2 + + surface.SetDrawColor( 0, 0, 0, 150 ) + surface.DrawTexturedRectRotated( X + oz, Y + oz, size, size, 0 ) + + if IsColor( selected ) then + surface.SetDrawColor( selected.r, selected.g, selected.b, selected.a ) + else + if selected then + surface.SetDrawColor( 255, 255, 255, 255 ) + else + surface.SetDrawColor( 150, 150, 150, 150 ) + end + end + + surface.DrawTexturedRectRotated( X, Y, size, size, 0 ) +end + +ENT.CarMenuSiren = Material( "lvs/carmenu_siren.png" ) +ENT.CarMenuHighbeam = Material( "lvs/carmenu_highbeam.png" ) +ENT.CarMenuLowbeam = Material( "lvs/carmenu_lowbeam.png" ) +ENT.CarMenuDisable = Material( "lvs/carmenu_cross.png" ) +ENT.CarMenuFog = Material( "lvs/carmenu_fog.png" ) +ENT.CarMenuHazard = Material( "lvs/carmenu_hazard.png" ) +ENT.CarMenuLeft = Material( "lvs/carmenu_turnleft.png" ) +ENT.CarMenuRight = Material( "lvs/carmenu_turnRight.png" ) + +function ENT:LVSHudPaintCarMenu( X, Y, w, h, ScrX, ScrY, ply ) + if self:GetDriver() ~= ply then return end + + local MenuOpen = ply:lvsKeyDown( "CAR_MENU" ) and self:HasTurnSignals() + + local EntTable = self:GetTable() + + if MenuOpen then + if ply:lvsKeyDown( "CAR_BRAKE" ) then + EntTable._SelectedMode = 3 + end + if ply:lvsKeyDown( "CAR_THROTTLE" ) then + EntTable._SelectedMode = 0 + end + if ply:lvsKeyDown( "CAR_STEER_LEFT" ) then + EntTable._SelectedMode = 1 + end + if ply:lvsKeyDown( "CAR_STEER_RIGHT" ) then + EntTable._SelectedMode = 2 + end + + if EntTable._oldSelectedMode ~= EntTable._SelectedMode then + EntTable._oldSelectedMode = EntTable._SelectedMode + + self:EmitSound("buttons/lightswitch2.wav",75,120,0.25) + end + else + if EntTable._oldSelectedMode and isnumber( EntTable._SelectedMode ) then + self:EmitSound("buttons/lightswitch2.wav",75,100,0.25) + + net.Start( "lvs_car_turnsignal" ) + net.WriteInt( EntTable._SelectedMode, 4 ) + net.SendToServer() + + EntTable._SelectedMode = 0 + EntTable._oldSelectedMode = nil + end + + local size = 32 + local dist = 5 + + local cX = X + w * 0.5 + local cY = Y + h - size * 0.5 - dist + + local LightsHandler = self:GetLightsHandler() + + if IsValid( LightsHandler ) then + if LightsHandler:GetActive() then + if LightsHandler:GetHighActive() then + surface.SetMaterial( self.CarMenuHighbeam ) + DrawTexturedRect( cX, cY, size, Color(0,255,255,255) ) + else + surface.SetMaterial( self.CarMenuLowbeam ) + DrawTexturedRect( cX, cY, size, Color(0,255,0,255) ) + end + end + + if LightsHandler:GetFogActive() then + surface.SetMaterial( self.CarMenuFog ) + DrawTexturedRect( cX, cY - (size + dist), size, Color(255,100,0,255) ) + end + end + + local TurnMode = self:GetTurnMode() + + if TurnMode == 0 then return end + + local Alpha = self:GetTurnFlasher() and 255 or 0 + + if TurnMode == 1 or TurnMode == 3 then + surface.SetMaterial( EntTable.CarMenuLeft ) + DrawTexturedRect( cX - (size + dist), cY, size, Color(0,255,157,Alpha) ) + end + + if TurnMode == 2 or TurnMode == 3 then + surface.SetMaterial( EntTable.CarMenuRight ) + DrawTexturedRect( cX + (size + dist), cY, size, Color(0,255,157,Alpha) ) + end + + return + end + + local SelectedThing = EntTable._SelectedMode or 0 + + local size = 32 + local dist = 5 + + local cX = X + w * 0.5 + local cY = Y + h - size * 0.5 - dist + + surface.SetMaterial( EntTable.CarMenuDisable ) + DrawTexturedRect( cX, cY - (size + dist), size, SelectedThing == 0 ) + + surface.SetMaterial( EntTable.CarMenuLeft ) + DrawTexturedRect( cX - (size + dist), cY, size, SelectedThing == 1 ) + + surface.SetMaterial( EntTable.CarMenuRight) + DrawTexturedRect( cX + (size + dist), cY, size, SelectedThing == 2 ) + + surface.SetMaterial( EntTable.CarMenuHazard ) + DrawTexturedRect( cX, cY, size, SelectedThing == 3 ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_hud_speedometer.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_hud_speedometer.lua new file mode 100644 index 0000000..0c8e20f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_hud_speedometer.lua @@ -0,0 +1,403 @@ + +LVS:AddHudEditor( "Tachometer", ScrW() - 530, ScrH() - 250, 300, 220, 300, 220, "TACH", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintTach or not vehicle.GetRacingHud then return end + + vehicle:LVSHudPaintTach( X, Y, W, H, ScrX, ScrY, ply ) + end +) + +local THE_FONT = { + font = "Verdana", + extended = false, + size = 100, + weight = 2000, + blursize = 0, + scanlines = 0, + antialias = false, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} +surface.CreateFont( "LVS_TACHOMETER", THE_FONT ) + +local circles = include("includes/circles/circles.lua") + +local Center = 650 + +local startAngleSpeedo = 180 +local endAngleSpeedo = 375 + +local startAngleTach = 165 +local endAngleTach = 360 + +local Ring = circles.New( CIRCLE_OUTLINED, 300, 0, 0, 60 ) +Ring:SetMaterial( true ) + +local Circle = circles.New( CIRCLE_OUTLINED, 625, 0, 0, 230 ) +Circle:SetX( Center ) +Circle:SetY( Center ) +Circle:SetMaterial( true ) + +local RingOuter = circles.New( CIRCLE_OUTLINED, 645, 0, 0, 35 ) +RingOuter:SetX( Center ) +RingOuter:SetY( Center ) +RingOuter:SetMaterial( true ) + +local RingInner = circles.New( CIRCLE_OUTLINED, 640, 0, 0, 25 ) +RingInner:SetX( Center ) +RingInner:SetY( Center ) +RingInner:SetMaterial( true ) + +local RingFrame = circles.New( CIRCLE_OUTLINED, 390, 0, 0, 10 ) +RingFrame:SetX( Center ) +RingFrame:SetY( Center ) +RingFrame:SetMaterial( true ) + +local RingFrameOuter = circles.New( CIRCLE_OUTLINED, 395, 0, 0, 20 ) +RingFrameOuter:SetX( Center ) +RingFrameOuter:SetY( Center ) +RingFrameOuter:SetMaterial( true ) + +local RingOuterRedline = circles.New( CIRCLE_OUTLINED, 645, 0, 0, 20 ) +RingOuterRedline:SetX( Center ) +RingOuterRedline:SetY( Center ) +RingOuterRedline:SetMaterial( true ) + +local RingInnerRedline = circles.New( CIRCLE_OUTLINED, 640, 0, 0, 10 ) +RingInnerRedline:SetX( Center ) +RingInnerRedline:SetY( Center ) +RingInnerRedline:SetMaterial( true ) + +local VehicleTach = {} + +function ENT:GetBakedTachMaterial( MaxRPM ) + local Class = self:GetClass() + + if VehicleTach[ Class ] then return VehicleTach[ Class ] end + + local TachRange = endAngleTach - startAngleTach + + local Steps = math.ceil(MaxRPM / 1000) + local AngleStep = TachRange / Steps + local AngleRedline = startAngleTach + (TachRange / MaxRPM) * self.EngineMaxRPM + + local tachRT = GetRenderTarget( "lvs_tach_"..Class, Center * 2, Center * 2 ) + + local old = DisableClipping( true ) + + render.OverrideAlphaWriteEnable( true, true ) + + render.PushRenderTarget( tachRT ) + + cam.Start2D() + render.ClearDepth() + render.Clear( 0, 0, 0, 0 ) + + surface.SetDrawColor( Color( 0, 0, 0, 150 ) ) + + Circle:SetStartAngle( startAngleTach ) + Circle:SetEndAngle( endAngleTach ) + Circle() + + surface.SetDrawColor( Color( 0, 0, 0, 200 ) ) + + RingOuter:SetStartAngle( startAngleTach ) + RingOuter:SetEndAngle( AngleRedline ) + RingOuter() + + RingOuterRedline:SetStartAngle( AngleRedline ) + RingOuterRedline:SetEndAngle( endAngleTach ) + RingOuterRedline() + + RingFrameOuter:SetStartAngle( startAngleTach ) + RingFrameOuter:SetEndAngle( endAngleTach ) + RingFrameOuter() + + surface.SetDrawColor( color_white ) + + for i = 0, Steps do + local Ang = AngleStep * i + startAngleTach + + local AngX = math.cos( math.rad( Ang ) ) + local AngY = math.sin( math.rad( Ang ) ) + + local StartX = Center + AngX * 554 + local StartY = Center + AngY * 554 + + local EndX = Center + AngX * 635 + local EndY = Center + AngY * 635 + + if Ang > AngleRedline then + surface.SetDrawColor( Color(255,0,0,255) ) + else + surface.SetDrawColor( color_white ) + end + + draw.NoTexture() + surface.DrawTexturedRectRotated( (StartX + EndX) * 0.5, (StartY + EndY) * 0.5, 90, 15, -Ang ) + + local TextX = Center + AngX * 485 + local TextY = Center + AngY * 485 + + if Ang > AngleRedline then + draw.SimpleText( i, "LVS_TACHOMETER", TextX, TextY, Color(255,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + draw.SimpleText( i, "LVS_TACHOMETER", TextX, TextY, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + end + + for i = 1, Steps do + local Start = AngleStep * i + startAngleTach + + for n = 1, 9 do + local Ang = Start - (AngleStep / 10) * n + + if Ang > AngleRedline then + surface.SetDrawColor( Color(150,0,0,255) ) + else + surface.SetDrawColor( Color(150,150,150,255) ) + end + + local AngX = math.cos( math.rad( Ang ) ) + local AngY = math.sin( math.rad( Ang ) ) + + local StartX = Center + AngX * 575 + local StartY = Center + AngY * 575 + + local EndX = Center + AngX * 635 + local EndY = Center + AngY * 635 + + draw.NoTexture() + surface.DrawTexturedRectRotated( (StartX + EndX) * 0.5, (StartY + EndY) * 0.5, 60, 5, -Ang ) + end + end + + surface.SetDrawColor( color_white ) + + RingInner:SetStartAngle( startAngleTach ) + RingInner:SetEndAngle( AngleRedline ) + RingInner() + + RingFrame:SetStartAngle( startAngleTach ) + RingFrame:SetEndAngle( endAngleTach ) + RingFrame() + + surface.SetDrawColor( Color(255,0,0,255) ) + + RingInnerRedline:SetStartAngle( AngleRedline ) + RingInnerRedline:SetEndAngle( endAngleTach ) + RingInnerRedline() + + cam.End2D() + + render.OverrideAlphaWriteEnable( false ) + + render.PopRenderTarget() + + local Mat = CreateMaterial( "lvs_tach_"..Class.."_mat", "UnlitGeneric", { ["$basetexture"] = tachRT:GetName(), ["$translucent"] = 1, ["$vertexcolor"] = 1 } ) + + VehicleTach[ Class ] = Mat + + DisableClipping( old ) + + return Mat +end + +local TachNeedleColor = Color(255,0,0,255) +local TachNeedleRadiusInner = 90 +local TachNeedleRadiusOuter = 145 +local TachNeedleBlurTime = 0.1 +local TachNeedles = {} +local CurRPM = 0 +local CurSpeed = 0 + +function ENT:LVSHudPaintTach( X, Y, w, h, ScrX, ScrY, ply ) + if ply ~= self:GetDriver() then return end + + if not self:GetRacingHud() then return end + + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + local Delta = (Engine:GetRPM() - CurRPM) * RealFrameTime() * 20 + + CurRPM = CurRPM + Delta + + local EntTable = self:GetTable() + + local MaxRPM = EntTable.EngineMaxRPM + 3000 + + local Ang = startAngleTach + (endAngleTach - startAngleTach) * (CurRPM / MaxRPM) + + local T = CurTime() + + local FuelTank = self:GetFuelTank() + + local UsesFuel = IsValid( FuelTank ) + + if self:GetEngineActive() then + local Gear = self:GetGear() + + local printGear = Gear + + if Gear == -1 then + printGear = self:GetReverse() and "R" or "D" + else + if self:GetReverse() then + printGear = "-"..Gear + end + end + + draw.DrawText( printGear, "LVS_FONT_HUD_HUMONGOUS", X + w * 0.5, Y + w * 0.23, color_white, TEXT_ALIGN_CENTER ) + else + surface.SetMaterial( EntTable.IconEngine ) + if UsesFuel and FuelTank:GetFuel() <= 0 then + surface.SetMaterial( EntTable.IconFuel ) + end + + surface.SetDrawColor( Color(255,0,0, math.abs( math.cos( T * 5 ) ) * 255 ) ) + surface.DrawTexturedRectRotated( X + w * 0.5 + 2, Y + w * 0.35 - 1, w * 0.15, w * 0.15, 0 ) + end + + if (EntTable._nextRefreshVel or 0) < T then + EntTable._nextRefreshVel = T + 0.1 + EntTable._refreshVel = self:GetVelocity():Length() + end + + local speed = math.Round( LVS:GetUnitValue( EntTable._refreshVel or 0 ) , 0 ) + draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + w * 0.81, Y + w * 0.6, color_white, TEXT_ALIGN_LEFT ) + draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + w * 0.81 - 5, Y + w * 0.6, color_white, TEXT_ALIGN_RIGHT ) + + -- fuel, oil, coolant + local barlength = w * 0.2 + if UsesFuel then + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1, barlength + 2, 7 ) + + local col = LVS.FUELTYPES[ FuelTank:GetFuelType() ].color + surface.SetDrawColor( Color(col.r,col.g,col.b,255) ) + surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5, barlength * FuelTank:GetFuel(), 5 ) + + draw.DrawText( "fuel", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 - 5, Color(255,150,0,255), TEXT_ALIGN_LEFT ) + end + + if self:HasQuickVar( "oil" ) then + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1 + 10, barlength + 2, 7 ) + surface.SetDrawColor( 80, 80, 80, 255 ) + surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5 + 10, barlength * math.min(self:GetQuickVar( "oil" ),1), 5 ) + draw.DrawText( "oil pressure", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 + 5, Color(0, 0, 0, 255), TEXT_ALIGN_LEFT ) + end + + if self:HasQuickVar( "temp" ) then + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1 + 20, barlength + 2, 7 ) + surface.SetDrawColor( 0, 127, 255, 255 ) + surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5 + 20, barlength * math.min(self:GetQuickVar( "temp" ),1), 5 ) + draw.DrawText( "coolant temp", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 + 15, Color(0, 0, 255, 255), TEXT_ALIGN_LEFT ) + end + + -- brake, clutch, throttle bar + local throttle = self:GetThrottle() + local clutch = self:GetQuickVar( "clutch" ) + local hasClutch = self:HasQuickVar( "clutch" ) + local brake = self:GetBrake() + local engine = self:GetEngine() + if IsValid( engine ) then + local ClutchActive = engine:GetClutch() + + if not clutch then + clutch = ClutchActive and 1 or 0 + end + + if ClutchActive then + throttle = math.max( throttle - clutch, 0 ) + end + end + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawRect( X + w * 0.3 - 1, Y + w * 0.4 - 1, 7, barlength + 2 ) + surface.DrawRect( X + w * 0.3 + 10 - 1, Y + w * 0.4 - 1, 7, barlength + 2 ) + if hasClutch then surface.DrawRect( X + w * 0.3 - 10 - 1, Y + w * 0.4 - 1, 7, barlength + 2 ) end + surface.SetDrawColor( 255, 255, 255, 255 ) + if hasClutch then + local cllength = barlength * clutch + surface.DrawRect( X + w * 0.3 - 10, Y + w * 0.4 + barlength - cllength, 5, cllength ) + end + local brlength = barlength * brake + surface.DrawRect( X + w * 0.3, Y + w * 0.4 + barlength - brlength, 5, brlength ) + local thrlength = barlength * throttle + surface.DrawRect( X + w * 0.3 + 10, Y + w * 0.4 + barlength - thrlength, 5, thrlength ) + if hasClutch then draw.DrawText( "c", "LVS_FONT_PANEL", X + w * 0.3 - 7, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER ) end + draw.DrawText( "b", "LVS_FONT_PANEL", X + w * 0.3 + 3, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER ) + draw.DrawText( "t", "LVS_FONT_PANEL", X + w * 0.3 + 13, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER ) + + + local TachRange = endAngleTach - startAngleTach + local AngleRedline = startAngleTach + (TachRange / MaxRPM) * EntTable.EngineMaxRPM + Ring:SetX( X + w * 0.5 ) + Ring:SetY( Y + w * 0.5 ) + Ring:SetRadius( w * 0.49 ) + Ring:SetOutlineWidth( w * 0.04 ) + Ring:SetStartAngle( startAngleTach ) + Ring:SetEndAngle( math.min( Ang, AngleRedline ) ) + Ring() + + if Ang > AngleRedline then + surface.SetDrawColor( 255, 0, 0, 255 ) + Ring:SetStartAngle( AngleRedline ) + Ring:SetEndAngle( math.min( Ang, endAngleTach ) ) + Ring() + end + + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( self:GetBakedTachMaterial( MaxRPM ) ) + surface.DrawTexturedRect( X, Y, w, w ) + + local CenterX = X + w * 0.5 + local CenterY = Y + w * 0.5 + + local T = CurTime() + + local AngX = math.cos( math.rad( Ang ) ) + local AngY = math.sin( math.rad( Ang ) ) + + if math.abs( Delta ) > 1 then + local data = { + StartX = (CenterX + AngX * TachNeedleRadiusInner), + StartY = (CenterY + AngY * TachNeedleRadiusInner), + EndX = (CenterX + AngX * TachNeedleRadiusOuter), + EndY = (CenterY + AngY * TachNeedleRadiusOuter), + Time = T + TachNeedleBlurTime + } + + table.insert( TachNeedles, data ) + else + local StartX = CenterX + AngX * TachNeedleRadiusInner + local StartY = CenterY + AngY * TachNeedleRadiusInner + local EndX = CenterX + AngX * TachNeedleRadiusOuter + local EndY = CenterY + AngY * TachNeedleRadiusOuter + + surface.SetDrawColor( TachNeedleColor ) + surface.DrawLine( StartX, StartY, EndX, EndY ) + end + + for index, data in pairs( TachNeedles ) do + if data.Time < T then + TachNeedles[ index ] = nil + + continue + end + + local Brightness = (data.Time - T) / TachNeedleBlurTime + + surface.SetDrawColor( Color( TachNeedleColor.r * Brightness, TachNeedleColor.g * Brightness, TachNeedleColor.b * Brightness, TachNeedleColor.a * Brightness ^ 2 ) ) + surface.DrawLine( data.StartX, data.StartY, data.EndX, data.EndY ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_init.lua new file mode 100644 index 0000000..8f9592d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_init.lua @@ -0,0 +1,192 @@ +include("shared.lua") +include("sh_animations.lua") +include("sh_camera_eyetrace.lua") +include("cl_flyby.lua") +include("cl_tiresounds.lua") +include("cl_camera.lua") +include("cl_hud.lua") +include("cl_scrolltexture.lua") +include("cl_exhausteffects.lua") + +DEFINE_BASECLASS( "lvs_base" ) + +function ENT:CreateSubMaterial( SubMaterialID, name ) + if not SubMaterialID then return end + + local mat = self:GetMaterials()[ SubMaterialID + 1 ] + + if not mat then return end + + local string_data = file.Read( "materials/"..mat..".vmt", "GAME" ) + + if not string_data then return end + + return CreateMaterial( name, "VertexLitGeneric", util.KeyValuesToTable( string_data ) ) +end + +function ENT:CalcPoseParameters() + local steer = self:GetSteer() / self:GetMaxSteerAngle() + + local kmh = math.Round( self:GetVelocity():Length() * 0.09144, 0 ) + + local lights = self:GetLightsHandler() + local ammeter = 0.5 + + local rpm = 0 + local oil = 0.25 + + local gear = 1 + local clutch = 0 + + local throttle = self:GetThrottle() + + local engine = self:GetEngine() + local engineActive = self:GetEngineActive() + + local fuel = 1 + local fueltank = self:GetFuelTank() + + local handbrake = self:QuickLerp( "handbrake", self:GetNWHandBrake() and 1 or 0 ) + + local temperature = 0 + + if IsValid( engine ) then + local EngineHealthFraction = engine:GetHP() / engine:GetMaxHP() + + rpm = self:QuickLerp( "rpm", engine:GetRPM() ) + gear = engine:GetGear() + oil = self:QuickLerp( "oil", engineActive and math.min( (EngineHealthFraction ^ 2) * 0.2 + (rpm / self.EngineMaxRPM) * 1.25 - (math.max( rpm - self.EngineMaxRPM, 0 ) / 2000) * 1.6, 1 ) or 0, 2 ) ^ 2 + + local ClutchActive = engine:GetClutch() + + clutch = self:QuickLerp( "clutch", ClutchActive and 1 or 0 ) + + if ClutchActive then + throttle = math.max( throttle - clutch, 0 ) + end + + temperature = self:QuickLerp( "temp", self:QuickLerp( "base_temp", engineActive and 0.5 or 0, 0.025 + throttle * 0.1 ) + (1 - EngineHealthFraction) ^ 2 * 1.25, 0.5 ) + else + temperature = self:QuickLerp( "temp", self:QuickLerp( "base_temp", engineActive and 0.5 or 0, 0.025 + throttle * 0.1 ) + (1 - self:GetHP() / self:GetMaxHP()) ^ 2 * 1.25, 0.5 ) + end + + if IsValid( lights ) then + local Available = 0.5 + (rpm / self.EngineMaxRPM) * 0.25 + + local Use1 = lights:GetActive() and 0.1 or 0 + local Use2 = lights:GetHighActive() and 0.15 or 0 + local Use3 = lights:GetFogActive() and 0.05 or 0 + local Use4 = (self:GetTurnMode() ~= 0 and self:GetTurnFlasher()) and 0.03 or 0 + + ammeter = self:QuickLerp( "ammeter", math.max( Available - Use1 - Use2 - Use3 - Use4, 0 ), math.Rand(1,10) ) + end + + if IsValid( fueltank ) then + fuel = self:QuickLerp( "fuel", fueltank:GetFuel() ) + end + + self:UpdatePoseParameters( steer, self:QuickLerp( "kmh", kmh ), rpm, throttle, self:GetBrake(), handbrake, clutch, (self:GetReverse() and -gear or gear), temperature, fuel, oil, ammeter ) + self:InvalidateBoneCache() +end + +function ENT:Think() + if not self:IsInitialized() then return end + + BaseClass.Think( self ) + + self:TireSoundThink() + self:ExhaustEffectsThink() + + if isfunction( self.UpdatePoseParameters ) then + self:CalcPoseParameters() + else + self:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() ) + self:InvalidateBoneCache() + end + end + +function ENT:OnRemove() + self:TireSoundRemove() + + BaseClass.OnRemove( self ) +end + +function ENT:PostDrawTranslucent() + local Handler = self:GetLightsHandler() + + if not IsValid( Handler ) or not istable( self.Lights ) then return end + + Handler:RenderLights( self, self.Lights ) +end + +function ENT:OnEngineStallBroken() + for i = 0,math.random(3,6) do + timer.Simple( math.Rand(0,1.5) , function() + if not IsValid( self ) then return end + + self:DoExhaustBackFire() + end ) + end +end + +function ENT:OnChangeGear( oldGear, newGear ) + local HP = self:GetHP() + local MaxHP = self:GetMaxHP() + + local Engine = self:GetEngine() + local EngineHP = 0 + local EngineMaxHP = 0 + + if IsValid( Engine ) then + EngineHP = Engine:GetHP() + EngineMaxHP = Engine:GetMaxHP() + end + + local Damaged = HP < MaxHP * 0.5 + local EngineDamaged = EngineHP < EngineMaxHP * 0.5 + + if (Damaged or EngineDamaged) then + if oldGear > newGear then + if Damaged then + self:EmitSound( "lvs/vehicles/generic/gear_grind"..math.random(1,6)..".ogg", 75, math.Rand(70,100), 0.25 ) + self:DoExhaustBackFire() + end + else + if EngineDamaged then + self:DoExhaustBackFire() + end + end + else + self:EmitSound( self.TransShiftSound, 75 ) + + if self:IsBackFireEnabled() then + self:CalcExhaustPop() + end + end + + self:SuppressViewPunch( self.TransShiftSpeed ) +end + +function ENT:GetTurnFlasher() + return math.cos( CurTime() * 8 + self:EntIndex() * 1337 ) > 0 +end + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/generic/engine_start1.wav", 75, 100, LVS.EngineVolume ) + else + self:EmitSound( "vehicles/jetski/jetski_off.wav", 75, 100, LVS.EngineVolume ) + end +end + +function ENT:GetWheels() + local wheels = {} + + for _, ent in pairs( self:GetCrosshairFilterEnts() ) do + if not IsValid( ent ) or ent:GetClass() ~= "lvs_wheeldrive_wheel" then continue end + + table.insert( wheels, ent ) + end + + return wheels +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_scrolltexture.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_scrolltexture.lua new file mode 100644 index 0000000..c900182 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_scrolltexture.lua @@ -0,0 +1,83 @@ + +ENT.ScrollTextureData = { + ["$alphatest"] = "1", + ["$translate"] = "[0.0 0.0 0.0]", + ["$colorfix"] = "{255 255 255}", + ["Proxies"] = { + ["TextureTransform"] = { + ["translateVar"] = "$translate", + ["centerVar"] = "$center", + ["resultVar"] = "$basetexturetransform", + }, + ["Equals"] = { + ["srcVar1"] = "$colorfix", + ["resultVar"] = "$color", + } + } +} + +function ENT:GetRotationDelta( name, rot ) + name = "_deltaAng"..name + + if not self[ name ] then self[ name ] = Angle(0,0,0) end + + local ang = Angle(0,0,rot) + local cur = ang:Right() + local old = self[ name ]:Up() + + local delta = self:AngleBetweenNormal( cur, old ) - 90 + + self[ name ] = ang + + return delta +end + +function ENT:CalcScroll( name, rot ) + local delta = self:GetRotationDelta( name, rot ) + + name = "_deltaScroll"..name + + if not self[ name ] then self[ name ] = 0 end + + self[ name ] = self[ name ] + delta + + if self[ name ] > 32768 then + self[ name ] = self[ name ] - 32768 + end + + if self[ name ] < -32768 then + self[ name ] = self[ name ] + 32768 + end + + return self[ name ] +end + +function ENT:ScrollTexture( name, material, pos ) + if not isstring( name ) or not isstring( material ) or not isvector( pos ) then return "" end + + local id = self:EntIndex() + local class = self:GetClass() + + local EntTable = self:GetTable() + + local texture_name = class.."_["..id.."]_"..name + + if istable( EntTable._StoredScrollTextures ) then + if EntTable._StoredScrollTextures[ texture_name ] then + self._StoredScrollTextures[ texture_name ]:SetVector("$translate", pos ) + + return "!"..texture_name + end + else + EntTable._StoredScrollTextures = {} + end + + local data = table.Copy( EntTable.ScrollTextureData ) + data["$basetexture"] = material + + local mat = CreateMaterial(texture_name, "VertexLitGeneric", data ) + + EntTable._StoredScrollTextures[ texture_name ] = mat + + return "!"..texture_name +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_tiresounds.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_tiresounds.lua new file mode 100644 index 0000000..97f7064 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/cl_tiresounds.lua @@ -0,0 +1,84 @@ + +ENT.TireSoundFade = 0.15 +ENT.TireSoundTypes = { + ["roll"] = "lvs/vehicles/generic/wheel_roll.wav", + ["roll_racing"] = "lvs/vehicles/generic/wheel_roll.wav", + ["roll_dirt"] = "lvs/vehicles/generic/wheel_roll_dirt.wav", + ["roll_wet"] = "lvs/vehicles/generic/wheel_roll_wet.wav", + ["roll_damaged"] = "lvs/wheel_damaged_loop.wav", + ["skid"] = "lvs/vehicles/generic/wheel_skid.wav", + ["skid_racing"] = "lvs/vehicles/generic/wheel_skid_racing.wav", + ["skid_dirt"] = "lvs/vehicles/generic/wheel_skid_dirt.wav", + ["skid_wet"] = "lvs/vehicles/generic/wheel_skid_wet.wav", + ["tire_damage_layer"] = "lvs/wheel_destroyed_loop.wav", +} +ENT.TireSoundLevelSkid = 85 +ENT.TireSoundLevelRoll = 75 + +function ENT:TireSoundRemove() + for snd, _ in pairs( self.TireSoundTypes ) do + self:StopTireSound( snd ) + end +end + +function ENT:TireSoundThink() + for snd, _ in pairs( self.TireSoundTypes ) do + local T = self:GetTireSoundTime( snd ) + + if T > 0 then + local speed = self:GetVelocity():Length() + + local sound = self:StartTireSound( snd ) + + if string.StartsWith( snd, "skid" ) or snd == "tire_damage_layer" then + local vel = speed + speed = math.max( math.abs( self:GetWheelVelocity() ) - vel, 0 ) * 5 + vel + end + + local volume = math.min(speed / 1000,1) ^ 2 * T + local pitch = 100 + math.Clamp((speed - 400) / 200,0,155) + + sound:ChangeVolume( volume, 0 ) + sound:ChangePitch( pitch, 0.5 ) + else + self:StopTireSound( snd ) + end + end +end + +function ENT:DoTireSound( snd ) + if not istable( self._TireSounds ) then + self._TireSounds = {} + end + + self._TireSounds[ snd ] = CurTime() + self.TireSoundFade +end + +function ENT:GetTireSoundTime( snd ) + if not istable( self._TireSounds ) or not self._TireSounds[ snd ] then return 0 end + + return math.max(self._TireSounds[ snd ] - CurTime(),0) / self.TireSoundFade +end + +function ENT:StartTireSound( snd ) + if not self.TireSoundTypes[ snd ] or not istable( self._ActiveTireSounds ) then + self._ActiveTireSounds = {} + end + + if self._ActiveTireSounds[ snd ] then return self._ActiveTireSounds[ snd ] end + + local sound = CreateSound( self, self.TireSoundTypes[ snd ] ) + sound:SetSoundLevel( string.StartsWith( snd, "skid" ) and self.TireSoundLevelSkid or self.TireSoundLevelRoll ) + sound:PlayEx(0,100) + + self._ActiveTireSounds[ snd ] = sound + + return sound +end + +function ENT:StopTireSound( snd ) + if not istable( self._ActiveTireSounds ) or not self._ActiveTireSounds[ snd ] then return end + + self._ActiveTireSounds[ snd ]:Stop() + self._ActiveTireSounds[ snd ] = nil +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/init.lua new file mode 100644 index 0000000..04b8e7e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/init.lua @@ -0,0 +1,534 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_flyby.lua" ) +AddCSLuaFile( "cl_camera.lua" ) +AddCSLuaFile( "cl_hud.lua" ) +AddCSLuaFile( "cl_hud_speedometer.lua" ) +AddCSLuaFile( "cl_tiresounds.lua" ) +AddCSLuaFile( "cl_scrolltexture.lua" ) +AddCSLuaFile( "cl_exhausteffects.lua" ) +AddCSLuaFile( "sh_animations.lua" ) +AddCSLuaFile( "sh_camera_eyetrace.lua" ) +include("shared.lua") +include("sh_animations.lua") +include("sv_controls.lua") +include("sv_controls_handbrake.lua") +include("sv_components.lua") +include("sv_ai.lua") +include("sv_riggedwheels.lua") +include("sv_wheelsystem.lua") +include("sv_damage.lua") +include("sv_pivotsteer.lua") +include("sv_manualtransmission.lua") +include("sv_engine.lua") +include("sv_hydraulics.lua") +include("sh_camera_eyetrace.lua") + +ENT.DriverActiveSound = "common/null.wav" +ENT.DriverInActiveSound = "common/null.wav" + +DEFINE_BASECLASS( "lvs_base" ) + +local function SetMinimumAngularVelocityTo( new ) + local tbl = physenv.GetPerformanceSettings() + + if tbl.MaxAngularVelocity < new then + local OldAngVel = tbl.MaxAngularVelocity + + tbl.MaxAngularVelocity = new + physenv.SetPerformanceSettings( tbl ) + + print("[LVS-Cars] Wheels require higher MaxAngularVelocity to perform correctly! Increasing! "..OldAngVel.." =>"..new) + end +end + +local function IsServerOK( class ) + + if GetConVar( "gmod_physiterations" ):GetInt() ~= 4 then + RunConsoleCommand("gmod_physiterations", "4") + + return false + end + + return true +end + +function ENT:TracksCreate( PObj ) +end + +local function DontDuplicatePaintSheme( ply, ent, data ) + ent.RandomColor = nil + + if not duplicator or not duplicator.StoreEntityModifier then return end + + duplicator.StoreEntityModifier( ent, "lvsVehiclePaintSheme", data ) +end + +if duplicator and duplicator.RegisterEntityModifier then + duplicator.RegisterEntityModifier( "lvsVehiclePaintSheme", DontDuplicatePaintSheme ) +end + +function ENT:PostInitialize( PObj ) + + self:TracksCreate( PObj ) + + if not IsServerOK( self:GetClass() ) then + self:Remove() + print("[LVS] ERROR COULDN'T INITIALIZE VEHICLE!") + end + + if istable( self.Lights ) then + self:AddLights() + end + + if istable( self.RandomColor ) then + local data = self.RandomColor[ math.random( #self.RandomColor ) ] + + if IsColor( data ) then + self:SetColor( data ) + else + self:SetSkin( data.Skin or 0 ) + self:SetColor( data.Color or color_white ) + + if istable( data.Wheels ) then + self._WheelSkin = data.Wheels.Skin or 0 + self._WheelColor = data.Wheels.Color or color_white + end + + if istable( data.BodyGroups ) then + for id, subgroup in pairs( data.BodyGroups ) do + self:SetBodygroup( id, subgroup ) + end + end + end + + DontDuplicatePaintSheme( NULL, self, {} ) + end + + BaseClass.PostInitialize( self, PObj ) + + if isstring( self.HornSound ) and isvector( self.HornPos ) and #self.WEAPONS[1] == 0 then + if IsValid( self.HornSND ) then self.HornSND:Remove() end + + self.HornSND = self:AddSoundEmitter( self.HornPos or vector_origin, self.HornSound, self.HornSoundInterior ) + self.HornSND:SetSoundLevel( 75 ) + self.HornSND:SetDoppler( true ) + end + + if istable( self.SirenSound ) then + if IsValid( self.SirenSND ) then self.SirenSND:Remove() end + + self.SirenSND = self:AddSoundEmitter( self.SirenPos or vector_origin, "common/null.wav" ) + self.SirenSND:SetSoundLevel( 75 ) + self.SirenSND:SetDoppler( true ) + end + + PObj:SetMass( self.PhysicsMass * self.PhysicsWeightScale ) + PObj:EnableDrag( false ) + PObj:SetInertia( self.PhysicsInertia * self.PhysicsWeightScale ) + + SetMinimumAngularVelocityTo( 24000 ) + + if BRANCH == "dev" or BRANCH == "x86-64" then + for _, wheel in pairs( self:GetWheels() ) do + if not IsValid( wheel ) then continue end + + wheel:SetLightingOriginEntity( self ) + end + end + + self:EnableHandbrake() +end + +function ENT:AlignView( ply ) + if not IsValid( ply ) then return end + + timer.Simple( 0, function() + if not IsValid( ply ) or not IsValid( self ) then return end + + local Ang = Angle(0,90,0) + + local pod = ply:GetVehicle() + local MouseAim = ply:lvsMouseAim() and self:GetDriver() == ply + + if MouseAim and IsValid( pod ) then + Ang = pod:LocalToWorldAngles( Angle(0,90,0) ) + Ang.r = 0 + end + + ply:SetEyeAngles( Ang ) + end) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, phys, deltatime, simulate ) + + if not self:GetRacingTires() then + return ForceAngle, vector_origin, simulate + end + + local EntTable = self:GetTable() + + local WheelSideForce = EntTable.WheelSideForce * EntTable.ForceLinearMultiplierRacingTires + local ForceLinear = Vector(0,0,0) + + for id, wheel in pairs( self:GetWheels() ) do + if wheel:IsHandbrakeActive() or not wheel:PhysicsOnGround() then continue end + + local AxleAng = wheel:GetDirectionAngle() + + local Forward = AxleAng:Forward() + local Right = AxleAng:Right() + local Up = AxleAng:Up() + + local wheelPos = wheel:GetPos() + local wheelVel = phys:GetVelocityAtPoint( wheelPos ) + local wheelRadius = wheel:GetRadius() + + local Slip = math.Clamp(1 - self:AngleBetweenNormal( Forward, wheelVel:GetNormalized() ) / 90,0,1) + + local ForwardVel = self:VectorSplitNormal( Forward, wheelVel ) + + Force = -Right * self:VectorSplitNormal( Right, wheelVel ) * WheelSideForce * Slip + local wSideForce, wAngSideForce = phys:CalculateVelocityOffset( Force, wheelPos ) + + ForceAngle:Add( Vector(0,0,wAngSideForce.z) ) + ForceLinear:Add( wSideForce ) + end + + return ForceAngle, ForceLinear, simulate +end + +function ENT:PhysicsSimulate( phys, deltatime ) + + if self:GetEngineActive() then phys:Wake() end + + local ent = phys:GetEntity() + + if ent == self then + local Vel = 0 + + for _, wheel in pairs( self:GetWheels() ) do + if wheel:GetTorqueFactor() <= 0 then continue end + + local wheelVel = wheel:RPMToVel( math.abs( wheel:GetRPM() or 0 ) ) + + if wheelVel > Vel then + Vel = wheelVel + end + end + + self:SetWheelVelocity( Vel ) + + if not self:StabilityAssist() or not self:WheelsOnGround() then return self:PhysicsSimulateOverride( Vector(0,0,0), phys, deltatime, SIM_NOTHING ) end + + local ForceAngle = Vector(0,0, math.deg( -phys:GetAngleVelocity().z ) * math.min( phys:GetVelocity():Length() / self.PhysicsDampingSpeed, 1 ) * self.ForceAngleMultiplier ) + + return self:PhysicsSimulateOverride( ForceAngle, phys, deltatime, SIM_GLOBAL_ACCELERATION ) + end + + if not self:AlignWheel( ent ) or self:IsDestroyed() then self:EnableHandbrake() return vector_origin, vector_origin, SIM_NOTHING end + + local WheelTable = ent:GetTable() + local EntTable = self:GetTable() + + if ent:IsHandbrakeActive() then + if WheelTable.SetRPM then + ent:SetRPM( 0 ) + end + + return vector_origin, vector_origin, SIM_NOTHING + end + + local T = CurTime() + + if (WheelTable._NextSimulate or 0) < T or not WheelTable.Simulate then + WheelTable._NextSimulate = T + ((self:PivotSteer() or self:GetBrake() > 0) and EntTable.WheelTickIntervalBraking or EntTable.WheelTickInterval) + + WheelTable.Force, WheelTable.ForceAng, WheelTable.Simulate = self:SimulateRotatingWheel( ent, EntTable, WheelTable, phys, deltatime ) + end + + return WheelTable.Force, WheelTable.ForceAng, WheelTable.Simulate +end + +function ENT:SimulateRotatingWheel( ent, EntTable, WheelTable, phys, deltatime ) + local RotationAxis = ent:GetRotationAxis() + + local curRPM = self:VectorSplitNormal( RotationAxis, phys:GetAngleVelocity() ) / 6 + + local Throttle = self:GetThrottle() + + ent:SetRPM( curRPM ) + + local ForceAngle = vector_origin + local ForceLinear = Vector(0,0,0) + + local TorqueFactor = ent:GetTorqueFactor() + + local IsBraking = self:GetBrake() > 0 + local IsBrakingWheel = (TorqueFactor * Throttle) <= 0.99 + + if IsBraking and IsBrakingWheel then + if ent:IsRotationLocked() then + ForceAngle = vector_origin + else + local ForwardVel = self:VectorSplitNormal( ent:GetDirectionAngle():Forward(), phys:GetVelocity() ) + + local targetRPM = ent:VelToRPM( ForwardVel ) * 0.5 + + if math.abs( curRPM ) < EntTable.WheelBrakeLockupRPM then + ent:LockRotation() + else + if (ForwardVel > 0 and targetRPM > 0) or (ForwardVel < 0 and targetRPM < 0) then + ForceAngle = RotationAxis * math.Clamp( (targetRPM - curRPM) / 100,-1,1) * math.deg( EntTable.WheelBrakeForce ) * ent:GetBrakeFactor() * self:GetBrake() + end + end + end + else + if math.abs( curRPM ) < EntTable.WheelBrakeLockupRPM and Throttle == 0 then + ent:LockRotation() + else + if ent:IsRotationLocked() then + ent:ReleaseRotation() + end + end + + if TorqueFactor > 0 and Throttle > 0 then + local engineTorque = self:GetEngineTorque() + + local targetVelocity = self:GetTargetVelocity() + + local targetRPM = ent:VelToRPM( targetVelocity ) + + local targetRPMabs = math.abs( targetRPM ) + + local powerRPM = targetRPMabs * EntTable.EngineCurve + + local powerCurve = (powerRPM + math.max( targetRPMabs - powerRPM,0) - math.max(math.abs(curRPM) - powerRPM,0)) / targetRPMabs * self:Sign( targetRPM - curRPM ) + + local Torque = powerCurve * engineTorque * TorqueFactor * Throttle + + local BoostRPM = 0 + + if self:GetReverse() then + Torque = math.min( Torque, 0 ) + + BoostRPM = ent:VelToRPM( EntTable.MaxVelocityReverse / EntTable.TransGearsReverse ) * 0.5 + else + Torque = math.max( Torque, 0 ) + + BoostRPM = ent:VelToRPM( EntTable.MaxVelocity / EntTable.TransGears ) * 0.5 + end + + local BoostMul = math.max( EntTable.EngineCurveBoostLow, 0 ) + local BoostStart = 1 + BoostMul + + local TorqueBoost = BoostStart - (math.min( math.max( math.abs( curRPM ) - BoostRPM, 0 ), BoostRPM) / BoostRPM) * BoostMul + + local Forward = ent:GetDirectionAngle():Forward() + + local curVelocity = self:VectorSplitNormal( Forward, phys:GetVelocity() ) + + if targetVelocity >= 0 then + if curVelocity < targetVelocity then + ForceAngle = RotationAxis * Torque * TorqueBoost + end + else + if curVelocity > targetVelocity then + ForceAngle = RotationAxis * Torque * TorqueBoost + end + end + + if self:PivotSteer() then + local RotationDirection = ent:GetWheelType() * self:GetPivotSteer() + + if EntTable.PivotSteerByBrake and RotationDirection < 0 then + ent:LockRotation( true ) + + return vector_origin, vector_origin, SIM_NOTHING + end + + powerCurve = math.Clamp((EntTable.PivotSteerWheelRPM * RotationDirection - curRPM) / EntTable.PivotSteerWheelRPM,-1,1) + + Torque = powerCurve * engineTorque * TorqueFactor * Throttle * 2 * EntTable.PivotSteerTorqueMul + + ForceAngle = RotationAxis * Torque + end + end + end + + if not self:StabilityAssist() or not self:WheelsOnGround() then return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION end + + local Vel = phys:GetVelocity() + + local ForwardAngle = ent:GetDirectionAngle() + + local Forward = ForwardAngle:Forward() + local Right = ForwardAngle:Right() + + local Fy = self:VectorSplitNormal( Right, Vel ) + local Fx = self:VectorSplitNormal( Forward, Vel ) + + if TorqueFactor >= 1 then + local VelX = math.abs( Fx ) + local VelY = math.abs( Fy ) + + if VelY > VelX * 0.1 then + if VelX > EntTable.FastSteerActiveVelocity then + if VelY < VelX * 0.6 then + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION + end + else + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION + end + end + end + + if IsBraking and not IsBrakingWheel then + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION + end + + ForceLinear:Add( -self:GetUp() * EntTable.WheelDownForce * TorqueFactor ) + + if not self:GetRacingTires() then + ForceLinear:Add( -Right * math.Clamp(Fy * 5 * math.min( math.abs( Fx ) / 500, 1 ),-EntTable.WheelSideForce,EntTable.WheelSideForce) * EntTable.ForceLinearMultiplier ) + end + + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION +end + +function ENT:SteerTo( TargetValue, MaxSteer ) + local Cur = self:GetSteer() / MaxSteer + + local Diff = TargetValue - Cur + + local Returning = (Diff > 0 and Cur < 0) or (Diff < 0 and Cur > 0) + + local Rate = FrameTime() * (Returning and self.SteerReturnSpeed or self.SteerSpeed) + + local New = (Cur + math.Clamp(Diff,-Rate,Rate)) + + self:SetSteer( New * MaxSteer ) + + if New == 0 or self:GetEngineActive() then return end + + for _, wheel in pairs( self:GetWheels() ) do + if not IsValid( wheel ) then continue end + + wheel:PhysWake() + end +end + +function ENT:OnDriverEnterVehicle( ply ) +end + +function ENT:OnDriverExitVehicle( ply ) +end + +function ENT:OnDriverChanged( Old, New, VehicleIsActive ) + self:OnPassengerChanged( Old, New, 1 ) + + if VehicleIsActive then + + self:OnDriverEnterVehicle( New ) + + return + end + + self:OnDriverExitVehicle( Old ) + self:SetThrottle( 0 ) + + if self:GetBrake() > 0 then + self:SetBrake( 0 ) + self:EnableHandbrake() + self:StopEngine() + self:SetTurnMode( 0 ) + + local LightsHandler = self:GetLightsHandler() + + if IsValid( LightsHandler ) then + LightsHandler:SetActive( false ) + LightsHandler:SetHighActive( false ) + LightsHandler:SetFogActive( false ) + end + else + if not self:GetEngineActive() then + self:SetBrake( 0 ) + self:EnableHandbrake() + end + end + + self:SetReverse( false ) + + self:StopSiren() + + if self:GetSirenMode() > 0 then + self:SetSirenMode( 0 ) + end + + if IsValid( self.HornSND ) then + self.HornSND:Stop() + end +end + +function ENT:OnRefueled() + local FuelTank = self:GetFuelTank() + + if not IsValid( FuelTank ) then return end + + FuelTank:EmitSound( "vehicles/jetski/jetski_no_gas_start.wav" ) +end + +function ENT:OnMaintenance() + local FuelTank = self:GetFuelTank() + local Engine = self:GetEngine() + + if IsValid( Engine ) then + Engine:SetHP( Engine:GetMaxHP() ) + Engine:SetDestroyed( false ) + end + + if IsValid( FuelTank ) then + FuelTank:ExtinguishAndRepair() + + if FuelTank:GetFuel() ~= 1 then + FuelTank:SetFuel( 1 ) + + self:OnRefueled() + end + end + + for _, wheel in pairs( self:GetWheels() ) do + if not IsValid( wheel ) then continue end + + wheel:SetHP( wheel:GetMaxHP() ) + end +end + +function ENT:OnSuperCharged( enable ) +end + +function ENT:OnTurboCharged( enable ) +end + +function ENT:ApproachTargetAngle( TargetAngle ) + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return end + + local ang = pod:GetAngles() + ang:RotateAroundAxis( self:GetUp(), 90 ) + + local Forward = ang:Right() + local View = pod:WorldToLocalAngles( TargetAngle ):Forward() + + local Reversed = false + if self:AngleBetweenNormal( View, ang:Forward() ) < 90 then + Reversed = self:GetReverse() + end + + local LocalAngSteer = (self:AngleBetweenNormal( View, ang:Right() ) - 90) / self.MouseSteerAngle + + local Steer = (math.min( math.abs( LocalAngSteer ), 1 ) ^ self.MouseSteerExponent * self:Sign( LocalAngSteer )) + + self:SteerTo( Reversed and Steer or -Steer, self:GetMaxSteerAngle() ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sh_animations.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sh_animations.lua new file mode 100644 index 0000000..1e6fd34 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sh_animations.lua @@ -0,0 +1,37 @@ + +function ENT:CalcMainActivityPassenger( ply ) +end + +function ENT:CalcMainActivity( ply ) + if ply ~= self:GetDriver() then return self:CalcMainActivityPassenger( ply ) end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + ply.CalcIdeal = ACT_STAND + ply.CalcSeqOverride = ply:LookupSequence( "drive_jeep" ) + + return ply.CalcIdeal, ply.CalcSeqOverride +end + +function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed ) + ply:SetPlaybackRate( 1 ) + + if CLIENT then + if ply == self:GetDriver() then + ply:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() ) + ply:InvalidateBoneCache() + end + + GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + end + + return false +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sh_camera_eyetrace.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sh_camera_eyetrace.lua new file mode 100644 index 0000000..822cc9a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sh_camera_eyetrace.lua @@ -0,0 +1,50 @@ + +function ENT:GetEyeTrace( trace_forward ) + local startpos = self:LocalToWorld( self:OBBCenter() ) + + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + startpos = pod:LocalToWorld( pod:OBBCenter() ) + end + + local AimVector = trace_forward and self:GetForward() or self:GetAimVector() + + local data = { + start = startpos, + endpos = (startpos + AimVector * 50000), + filter = self:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( data ) + + return trace +end + +function ENT:GetAimVector() + if self:GetAI() then + return self:GetAIAimVector() + end + + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return self:GetForward() end + + local Driver = self:GetDriver() + + if not IsValid( Driver ) then return pod:GetForward() end + + if Driver:lvsMouseAim() then + if SERVER then + return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward() + else + return Driver:EyeAngles():Forward() + end + else + if SERVER then + return Driver:EyeAngles():Forward() + else + return pod:LocalToWorldAngles( Driver:EyeAngles() ):Forward() + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/shared.lua new file mode 100644 index 0000000..3e6a307 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/shared.lua @@ -0,0 +1,345 @@ + +ENT.Base = "lvs_base" + +ENT.PrintName = "[LVS] Wheeldrive Base" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MaxHealth = 600 +ENT.MaxHealthEngine = 50 +ENT.MaxHealthFuelTank = 10 + +ENT.MaxVelocity = 1400 +ENT.MaxVelocityReverse = 700 + +ENT.EngineCurve = 0.65 +ENT.EngineCurveBoostLow = 1 +ENT.EngineTorque = 350 +ENT.EngineIdleRPM = 1000 +ENT.EngineMaxRPM = 6000 + +ENT.ThrottleRate = 3.5 +ENT.BrakeRate = 3.5 + +ENT.ForceLinearMultiplier = 1 +ENT.ForceLinearMultiplierRacingTires = 1.15 +ENT.ForceAngleMultiplier = 0.5 + +ENT.TransGears = 4 +ENT.TransGearsReverse = 1 +ENT.TransMinGearHoldTime = 1 +ENT.TransShiftSpeed = 0.3 +ENT.TransShiftTorqueFactor = 0 +ENT.TransWobble = 40 +ENT.TransWobbleTime = 1.5 +ENT.TransWobbleFrequencyMultiplier = 1 +ENT.TransShiftSound = "lvs/vehicles/generic/gear_shift.wav" + +ENT.SteerSpeed = 3 +ENT.SteerReturnSpeed = 10 + +ENT.FastSteerActiveVelocity = 500 +ENT.FastSteerAngleClamp = 10 +ENT.FastSteerDeactivationDriftAngle = 7 + +ENT.SteerAssistDeadZoneAngle = 1 +ENT.SteerAssistMaxAngle = 15 +ENT.SteerAssistExponent = 1.5 +ENT.SteerAssistMultiplier = 3 + +ENT.MouseSteerAngle = 20 +ENT.MouseSteerExponent = 2 + +ENT.PhysicsWeightScale = 1 +ENT.PhysicsMass = 1000 +ENT.PhysicsInertia = Vector(1500,1500,750) +ENT.PhysicsDampingSpeed = 4000 +ENT.PhysicsDampingForward = true +ENT.PhysicsDampingReverse = false + +ENT.WheelTickInterval = 0.2 +ENT.WheelTickIntervalBraking = 0.02 + +ENT.WheelPhysicsMass = 100 +ENT.WheelPhysicsInertia = Vector(10,8,10) +ENT.WheelPhysicsTireHeight = 4 +ENT.WheelPhysicsMaterials = { + [0] = "friction_00", -- 0 + [1] = "friction_10", -- 0.1 + [2] = "friction_25", -- 0.25 + [3] = "rubber", -- 0.8 + [4] = "rubber", + [5] = "rubber", + [6] = "rubber", + [7] = "rubber", + [8] = "rubber", + [9] = "rubber", + [10] = "jeeptire", -- 1.337 -- i don't believe friction in havok can go above 1, however other settings such as bouncyness and elasticity are affected by it as it seems. We use jeeptire as default even tho it technically isn't the "best" choice, but rather the most common one + [11] = "jalopytire", -- 1.337 + [12] = "phx_tire_normal", -- 3 +} + +ENT.AutoReverseVelocity = 50 + +ENT.WheelBrakeLockupRPM = 20 + +ENT.WheelBrakeForce = 400 + +ENT.WheelSideForce = 800 +ENT.WheelDownForce = 500 + +ENT.AllowSuperCharger = true +ENT.SuperChargerVolume = 0.6 +ENT.SuperChargerSound = "lvs/vehicles/generic/supercharger_loop.wav" +ENT.SuperChargerVisible = true + +ENT.AllowTurbo = true +ENT.TurboVolume = 0.6 +ENT.TurboSound = "lvs/vehicles/generic/turbo_loop.wav" +ENT.TurboBlowOff = {"lvs/vehicles/generic/turbo_blowoff1.wav","lvs/vehicles/generic/turbo_blowoff2.wav"} + +ENT.DeleteOnExplode = false + +ENT.lvsAllowEngineTool = true +ENT.lvsShowInSpawner = false + +function ENT:SetupDataTables() + self:CreateBaseDT() + + self:AddDT( "Float", "Steer" ) + self:AddDT( "Float", "Throttle" ) + self:AddDT( "Float", "MaxThrottle" ) + self:AddDT( "Float", "Brake" ) + + self:AddDT( "Float", "NWMaxSteer" ) + self:AddDT( "Float", "WheelVelocity" ) + + self:AddDT( "Int", "NWGear" ) + self:AddDT( "Int", "TurnMode" ) + self:AddDT( "Int", "SirenMode" ) + + self:AddDT( "Bool", "Reverse" ) + self:AddDT( "Bool", "NWHandBrake" ) + + self:AddDT( "Bool", "RacingHud" ) + self:AddDT( "Bool", "RacingTires" ) + self:AddDT( "Bool", "Backfire" ) + + self:AddDT( "Entity", "Engine" ) + self:AddDT( "Entity", "FuelTank" ) + self:AddDT( "Entity", "LightsHandler" ) + self:AddDT( "Entity", "Turbo" ) + self:AddDT( "Entity", "Compressor" ) + + self:AddDT( "Vector", "AIAimVector" ) + + self:TrackSystemDT() + + if SERVER then + self:SetMaxThrottle( 1 ) + self:SetSirenMode( -1 ) + end +end + +function ENT:TrackSystemDT() +end + +function ENT:StabilityAssist() + if self:GetReverse() then + return self.PhysicsDampingReverse + end + + return self.PhysicsDampingForward +end + +function ENT:GetMaxSteerAngle() + if CLIENT then return self:GetNWMaxSteer() end + + local EntTable = self:GetTable() + + if EntTable._WheelMaxSteerAngle then return EntTable._WheelMaxSteerAngle end + + local Cur = 0 + + for _, Axle in pairs( EntTable._WheelAxleData ) do + if not Axle.SteerAngle then continue end + + if Axle.SteerAngle > Cur then + Cur = Axle.SteerAngle + end + end + + EntTable._WheelMaxSteerAngle = Cur + + self:SetNWMaxSteer( Cur ) + + return Cur +end + +function ENT:GetTargetVelocity() + local Reverse = self:GetReverse() + + if self:IsManualTransmission() then + local Gear = self:GetGear() + local EntTable = self:GetTable() + + local NumGears = Reverse and EntTable.TransGearsReverse or EntTable.TransGears + local MaxVelocity = Reverse and EntTable.MaxVelocityReverse or EntTable.MaxVelocity + + local GearedVelocity = math.min( (MaxVelocity / NumGears) * (Gear + 1), MaxVelocity ) + + return GearedVelocity * (Reverse and -1 or 1) + end + + if Reverse then + return -self.MaxVelocityReverse + end + + return self.MaxVelocity +end + +function ENT:HasHighBeams() + local EntTable = self:GetTable() + + if isbool( EntTable._HasHighBeams ) then return EntTable._HasHighBeams end + + if not istable( EntTable.Lights ) then return false end + + local HasHigh = false + + for _, data in pairs( EntTable.Lights ) do + if not istable( data ) then continue end + + for id, typedata in pairs( data ) do + if id == "Trigger" and typedata == "high" then + HasHigh = true + + break + end + end + end + + EntTable._HasHighBeams = HasHigh + + return HasHigh +end + +function ENT:HasFogLights() + local EntTable = self:GetTable() + + if isbool( EntTable._HasFogLights ) then return EntTable._HasFogLights end + + if not istable( EntTable.Lights ) then return false end + + local HasFog = false + + for _, data in pairs( EntTable.Lights ) do + if not istable( data ) then continue end + + for id, typedata in pairs( data ) do + if id == "Trigger" and typedata == "fog" then + HasFog = true + + break + end + end + end + + EntTable._HasFogLights = HasFog + + return HasFog +end + +function ENT:HasTurnSignals() + local EntTable = self:GetTable() + + if isbool( EntTable._HasTurnSignals ) then return EntTable._HasTurnSignals end + + if not istable( EntTable.Lights ) then return false end + + local HasTurnSignals = false + + for _, data in pairs( EntTable.Lights ) do + if not istable( data ) then continue end + + for id, typedata in pairs( data ) do + if id == "Trigger" and (typedata == "turnleft" or typedata == "turnright" or typedata == "main+brake+turnleft" or typedata == "main+brake+turnright") then + HasTurnSignals = true + + break + end + end + end + + EntTable._HasTurnSignals = HasTurnSignals + + return HasTurnSignals +end + +function ENT:GetGear() + local Gear = self:GetNWGear() + + if Gear <= 0 then + return -1 + end + + if self:GetReverse() then + return math.Clamp( Gear, 1, self.TransGearsReverse ) + end + + return math.Clamp( Gear, 1, self.TransGears ) +end + +function ENT:IsManualTransmission() + return self:GetNWGear() > 0 +end + +function ENT:BodygroupIsValid( name, groups ) + if not name or not istable( groups ) then return false end + + local EntTable = self:GetTable() + + local id = -1 + + if EntTable._StoredBodyGroups then + if EntTable._StoredBodyGroups[ name ] then + id = EntTable._StoredBodyGroups[ name ] + end + else + EntTable._StoredBodyGroups = {} + end + + if id == -1 then + for _, data in pairs( self:GetBodyGroups() ) do + if data.name == name then + id = data.id + + break + end + end + end + + if id == -1 then return false end + + EntTable._StoredBodyGroups[ name ] = id + + local cur = self:GetBodygroup( id ) + + for _, active in pairs( groups ) do + if cur == active then return true end + end + + return false +end + +function ENT:GetWheelUp() + return self:GetUp() +end + +function ENT:GetVehicleType() + return "car" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_ai.lua new file mode 100644 index 0000000..4a9828d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_ai.lua @@ -0,0 +1,254 @@ + +function ENT:OnCreateAI() + self:DisableManualTransmission() + + self:StartEngine() +end + +function ENT:OnRemoveAI() + self:StopEngine() +end + +function ENT:AIGetMovementTarget() + local Pod = self:GetDriverSeat() + + if not IsValid( Pod ) then return (self._MovmentTarget or self:GetPos()) end + + return (self._MovmentTarget or Pod:LocalToWorld( Pod:OBBCenter() + Vector(0,100,0))), (self._MovementDistance or 500) +end + +function ENT:AISetMovementTarget( pos, dist ) + self._MovmentTarget = pos + self._MovementDistance = (dist or 500) +end + +local DontChase = { + ["starfighter"] = true, + ["repulsorlift"] = true, + ["plane"] = true, + ["helicopter"] = true, +} + +function ENT:RunAI() + local Pod = self:GetDriverSeat() + + if not IsValid( Pod ) then self:SetAI( false ) return end + + local RangerLength = 25000 + + local Target = self:AIGetTarget( 180 ) + + local StartPos = Pod:LocalToWorld( Pod:OBBCenter() ) + + local GotoPos, GotoDist = self:AIGetMovementTarget() + + local TargetPos = GotoPos + + local T = CurTime() + + local IsTargetValid = IsValid( Target ) + + if IsTargetValid then + if self:AIHasWeapon( 1 ) then + if (self._LastGotoPos or 0) > T then + GotoPos = self._OldGotoPos + else + local Pos = Target:GetPos() + local Sub = self:GetPos() - Pos + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + + GotoPos = Pos + Dir * math.min( 2000, Dist ) + + self._LastGotoPos = T + math.random(4,8) + self._OldGotoPos = GotoPos + end + else + GotoPos = Target:GetPos() + end + + else + local TraceFilter = self:GetCrosshairFilterEnts() + + local Front = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Pod:GetForward() * RangerLength } ) + local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,15,0) ):Right() * RangerLength } ) + local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,-15,0) ):Right() * RangerLength } ) + local FrontLeft1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,60,0) ):Right() * RangerLength } ) + local FrontRight1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,-60,0) ):Right() * RangerLength } ) + local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,85,0) ):Right() * RangerLength } ) + local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,-85,0) ):Right() * RangerLength } ) + + local traceWater = util.TraceLine( { + start = Front.HitPos, + endpos = Front.HitPos - Vector(0,0,50000), + filter = self:GetCrosshairFilterEnts(), + mask = MASK_WATER + } ) + + if traceWater.Hit then + Front.HitPos = StartPos + end + + GotoPos = (Front.HitPos + FrontLeft.HitPos + FrontRight.HitPos + FrontLeft1.HitPos + FrontRight1.HitPos + FrontLeft2.HitPos + FrontRight2.HitPos) / 7 + + if not self:GetEngineActive() then + local Engine = self:GetEngine() + + if IsValid( Engine ) then + if not Engine:GetDestroyed() then + self:StartEngine() + end + else + self:StartEngine() + end + end + + if self:GetReverse() then + if Front.Fraction < 0.03 then + GotoPos = StartPos - Pod:GetForward() * 1000 + end + else + if Front.Fraction < 0.01 then + GotoPos = StartPos - Pod:GetForward() * 1000 + end + end + end + + local TargetPosLocal = Pod:WorldToLocal( GotoPos ) + local Throttle = math.min( math.max( TargetPosLocal:Length() - GotoDist, 0 ) / 10, 1 ) + + self:PhysWake() + + local PivotSteer = self.PivotSteerEnable + local DontMove = PivotSteer and Throttle == 0 + + if PivotSteer and IsTargetValid and Target.LVS and Target.GetVehicleType then + if DontChase[ Target:GetVehicleType() ] then + DontMove = true + end + end + + if DontMove then + local ang = self:GetAngles() + ang.y = Pod:GetAngles().y + 90 + + local View = self:GetAimVector() + + local LocalAngSteer = math.Clamp( (self:AngleBetweenNormal( View, ang:Right() ) - 90) / 10,-1,1) + + Throttle = math.abs( LocalAngSteer ) ^ 2 + + if Throttle < 0.1 then + Throttle = 0 + LocalAngSteer = 0 + end + + self:SetSteer( 0 ) + self:SetPivotSteer( -LocalAngSteer ) + self:LerpThrottle( Throttle ) + self:LerpBrake( 0 ) + else + self:SetPivotSteer( 0 ) + + if self:IsLegalInput() then + self:LerpThrottle( Throttle ) + + if Throttle == 0 then + self:LerpBrake( 1 ) + else + self:LerpBrake( 0 ) + end + else + self:LerpThrottle( 0 ) + self:LerpBrake( Throttle ) + end + + self:SetReverse( TargetPosLocal.y < 0 ) + + self:ApproachTargetAngle( Pod:LocalToWorldAngles( (GotoPos - self:GetPos()):Angle() ) ) + end + + self:ReleaseHandbrake() + + self._AIFireInput = false + + if IsValid( self:GetHardLockTarget() ) then + Target = self:GetHardLockTarget() + + TargetPos = Target:LocalToWorld( Target:OBBCenter() ) + + self._AIFireInput = true + else + if IsValid( Target ) then + local PhysObj = Target:GetPhysicsObject() + if IsValid( PhysObj ) then + TargetPos = Target:LocalToWorld( PhysObj:GetMassCenter() ) + else + TargetPos = Target:LocalToWorld( Target:OBBCenter() ) + end + + if self:AIHasWeapon( 1 ) or self:AIHasWeapon( 2 ) then + self._AIFireInput = true + end + + local CurHeat = self:GetNWHeat() + local CurWeapon = self:GetSelectedWeapon() + + if CurWeapon > 2 then + self:AISelectWeapon( 1 ) + else + if CurHeat < 0.9 then + if CurWeapon == 1 and self:AIHasWeapon( 2 ) then + self:AISelectWeapon( 2 ) + + elseif CurWeapon == 2 then + self:AISelectWeapon( 1 ) + end + else + if CurHeat == 0 and math.cos( CurTime() ) > 0 then + self:AISelectWeapon( 1 ) + end + end + end + end + end + + T = T + self:EntIndex() * 1.337 + + self:SetAIAimVector( (TargetPos + Vector(0,math.sin( T * 0.5 ) * 30,math.cos( T * 2 ) * 30) - StartPos):GetNormalized() ) +end + +function ENT:OnAITakeDamage( dmginfo ) + local attacker = dmginfo:GetAttacker() + + if not IsValid( attacker ) then return end + + if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then + self:SetHardLockTarget( attacker ) + end +end + +function ENT:SetHardLockTarget( target ) + if not self:IsEnemy( target ) then return end + + self._HardLockTarget = target + self._HardLockTime = CurTime() + 4 +end + +function ENT:GetHardLockTarget() + if (self._HardLockTime or 0) < CurTime() then return NULL end + + return self._HardLockTarget +end + +function ENT:AISelectWeapon( ID ) + if ID == self:GetSelectedWeapon() then return end + + local T = CurTime() + + if (self._nextAISwitchWeapon or 0) > T then return end + + self._nextAISwitchWeapon = T + math.random(3,6) + + self:SelectWeapon( ID ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_components.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_components.lua new file mode 100644 index 0000000..dbb54b6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_components.lua @@ -0,0 +1,211 @@ + +function ENT:AddEngine( pos, ang, mins, maxs ) + if IsValid( self:GetEngine() ) then return end + + ang = ang or angle_zero + mins = mins or Vector(-10,-10,-10) + maxs = maxs or Vector(10,10,10) + + local Engine = ents.Create( "lvs_wheeldrive_engine" ) + + if not IsValid( Engine ) then + self:Remove() + + print("LVS: Failed to create engine entity. Vehicle terminated.") + + return + end + + Engine:SetPos( self:LocalToWorld( pos ) ) + Engine:SetAngles( self:LocalToWorldAngles( ang ) ) + Engine:Spawn() + Engine:Activate() + Engine:SetParent( self ) + Engine:SetBase( self ) + Engine:SetMaxHP( self.MaxHealthEngine ) + Engine:SetHP( self.MaxHealthEngine ) + + self:SetEngine( Engine ) + + self:DeleteOnRemove( Engine ) + + self:TransferCPPI( Engine ) + + debugoverlay.BoxAngles( self:LocalToWorld( pos ), mins, maxs, self:LocalToWorldAngles( ang ), 15, Color( 0, 255, 255, 255 ) ) + + self:AddDS( { + pos = pos, + ang = ang, + mins = mins, + maxs = maxs, + Callback = function( tbl, ent, dmginfo ) + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + Engine:TakeTransmittedDamage( dmginfo ) + + if not Engine:GetDestroyed() then + dmginfo:ScaleDamage( 0.25 ) + end + end + } ) + + return Engine +end + +function ENT:AddFuelTank( pos, ang, tanksize, fueltype, mins, maxs ) + if IsValid( self:GetFuelTank() ) then return end + + local FuelTank = ents.Create( "lvs_wheeldrive_fueltank" ) + + if not IsValid( FuelTank ) then + self:Remove() + + print("LVS: Failed to create fueltank entity. Vehicle terminated.") + + return + end + + FuelTank:SetPos( self:LocalToWorld( pos ) ) + FuelTank:SetAngles( self:GetAngles() ) + FuelTank:Spawn() + FuelTank:Activate() + FuelTank:SetParent( self ) + FuelTank:SetBase( self ) + FuelTank:SetSize( tanksize or 600 ) + FuelTank:SetFuelType( fueltype or 0 ) + FuelTank:SetMaxHP( self.MaxHealthFuelTank ) + FuelTank:SetHP( self.MaxHealthFuelTank ) + + self:SetFuelTank( FuelTank ) + + self:DeleteOnRemove( FuelTank ) + + self:TransferCPPI( FuelTank ) + + mins = mins or Vector(-15,-15,-5) + maxs = maxs or Vector(15,15,5) + + debugoverlay.BoxAngles( self:LocalToWorld( pos ), mins, maxs, self:LocalToWorldAngles( ang ), 15, Color( 255, 255, 0, 255 ) ) + + self:AddDS( { + pos = pos, + ang = ang, + mins = mins, + maxs = maxs, + Callback = function( tbl, ent, dmginfo ) + if not IsValid( FuelTank ) then return end + + FuelTank:TakeTransmittedDamage( dmginfo ) + + if not FuelTank:GetDestroyed() then + dmginfo:ScaleDamage( 0.25 ) + end + end + } ) + + return FuelTank +end + +function ENT:AddLights() + if IsValid( self:GetLightsHandler() ) then return end + + local LightHandler = ents.Create( "lvs_wheeldrive_lighthandler" ) + + if not IsValid( LightHandler ) then return end + + LightHandler:SetPos( self:LocalToWorld( self:OBBCenter() ) ) + LightHandler:SetAngles( self:GetAngles() ) + LightHandler:Spawn() + LightHandler:Activate() + LightHandler:SetParent( self ) + LightHandler:SetBase( self ) + + self:DeleteOnRemove( LightHandler ) + + self:TransferCPPI( LightHandler ) + + self:SetLightsHandler( LightHandler ) +end + +function ENT:AddTurboCharger() + local Ent = self:GetTurbo() + + if IsValid( Ent ) then return Ent end + + local Turbo = ents.Create( "lvs_item_turbo" ) + + if not IsValid( Turbo ) then + self:Remove() + + print("LVS: Failed to create turbocharger entity. Vehicle terminated.") + + return + end + + Turbo:SetPos( self:GetPos() ) + Turbo:SetAngles( self:GetAngles() ) + Turbo:Spawn() + Turbo:Activate() + Turbo:LinkTo( self ) + + self:TransferCPPI( Turbo ) + + return Turbo +end + +function ENT:AddSuperCharger() + local Ent = self:GetCompressor() + + if IsValid( Ent ) then return Ent end + + local SuperCharger = ents.Create( "lvs_item_compressor" ) + + if not IsValid( SuperCharger ) then + self:Remove() + + print("LVS: Failed to create supercharger entity. Vehicle terminated.") + + return + end + + SuperCharger:SetPos( self:GetPos() ) + SuperCharger:SetAngles( self:GetAngles() ) + SuperCharger:Spawn() + SuperCharger:Activate() + SuperCharger:LinkTo( self ) + + self:TransferCPPI( SuperCharger ) + + return SuperCharger +end + +function ENT:AddDriverViewPort( pos, ang, mins, maxs ) + self:AddDS( { + pos = pos, + ang = ang, + mins = mins, + maxs = maxs, + Callback = function( tbl, ent, dmginfo ) + if dmginfo:GetDamage() <= 0 then return end + + local ply = self:GetDriver() + + if IsValid( ply ) then + ply:EmitSound("lvs/hitdriver"..math.random(1,2)..".wav",120) + self:HurtPlayer( ply, dmginfo:GetDamage(), dmginfo:GetAttacker(), dmginfo:GetInflictor() ) + end + + dmginfo:ScaleDamage( 0 ) + end + } ) +end + +function ENT:AddTuningExhaust() + self:SetBackfire( true ) +end + +function ENT:AddRacingTires() + self:SetRacingTires( true ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_controls.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_controls.lua new file mode 100644 index 0000000..3d78d53 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_controls.lua @@ -0,0 +1,454 @@ + +function ENT:CalcMouseSteer( ply ) + self:ApproachTargetAngle( ply:EyeAngles() ) +end + +function ENT:CalcSteer( ply ) + local KeyLeft = ply:lvsKeyDown( "CAR_STEER_LEFT" ) + local KeyRight = ply:lvsKeyDown( "CAR_STEER_RIGHT" ) + + local MaxSteer = self:GetMaxSteerAngle() + + local Vel = self:GetVelocity() + + local TargetValue = (KeyRight and 1 or 0) - (KeyLeft and 1 or 0) + + local EntTable = self:GetTable() + + if Vel:Length() > EntTable.FastSteerActiveVelocity then + local Forward = self:GetForward() + local Right = self:GetRight() + + local Axle = self:GetAxleData( 1 ) + + if Axle then + local Ang = self:LocalToWorldAngles( self:GetAxleData( 1 ).ForwardAngle ) + + Forward = Ang:Forward() + Right = Ang:Right() + end + + local VelNormal = Vel:GetNormalized() + + local DriftAngle = self:AngleBetweenNormal( Forward, VelNormal ) + + if self:GetRacingTires() or self:GetBrake() >= 1 then + if math.abs( self:GetSteer() ) < EntTable.FastSteerAngleClamp then + MaxSteer = math.min( MaxSteer, EntTable.FastSteerAngleClamp ) + end + else + if DriftAngle < EntTable.FastSteerDeactivationDriftAngle then + MaxSteer = math.min( MaxSteer, EntTable.FastSteerAngleClamp ) + end + end + + if not KeyLeft and not KeyRight then + local Cur = self:GetSteer() / MaxSteer + + local MaxHelpAng = math.min( MaxSteer, EntTable.SteerAssistMaxAngle ) + + local Ang = self:AngleBetweenNormal( Right, VelNormal ) - 90 + local HelpAng = ((math.abs( Ang ) / 90) ^ EntTable.SteerAssistExponent) * 90 * self:Sign( Ang ) + + TargetValue = math.Clamp( -HelpAng * EntTable.SteerAssistMultiplier,-MaxHelpAng,MaxHelpAng) / MaxSteer + end + end + + self:SteerTo( TargetValue, MaxSteer ) +end + +function ENT:IsLegalInput() + local EntTable = self:GetTable() + + if not EntTable.ForwardAngle then return true end + + local MinSpeed = math.min(EntTable.MaxVelocity,EntTable.MaxVelocityReverse) + + local ForwardVel = self:Sign( math.Round( self:VectorSplitNormal( self:LocalToWorldAngles( EntTable.ForwardAngle ):Forward(), self:GetVelocity() ) / MinSpeed, 0 ) ) + local DesiredVel = self:GetReverse() and -1 or 1 + + return ForwardVel == DesiredVel * math.abs( ForwardVel ) +end + +function ENT:LerpThrottle( Throttle ) + if not self:GetEngineActive() then self:SetThrottle( 0 ) return end + + local FT = FrameTime() + + local RateUp = self.ThrottleRate * FT + local RateDn = 3.5 * FT + + local Cur = self:GetThrottle() + local New = Cur + math.Clamp(Throttle - Cur,-RateDn,RateUp) + + self:SetThrottle( New ) +end + +function ENT:LerpBrake( Brake ) + local FT = FrameTime() + + local RateUp = self.BrakeRate * FT + local RateDn = 3.5 * FT + + local Cur = self:GetBrake() + local New = Cur + math.Clamp(Brake - Cur,-RateDn,RateUp) + + self:SetBrake( New ) +end + +function ENT:CalcThrottle( ply ) + local KeyThrottle = ply:lvsKeyDown( "CAR_THROTTLE" ) + local KeyBrakes = ply:lvsKeyDown( "CAR_BRAKE" ) + + if self:GetReverse() and not self:IsManualTransmission() then + KeyThrottle = ply:lvsKeyDown( "CAR_BRAKE" ) + KeyBrakes = ply:lvsKeyDown( "CAR_THROTTLE" ) + end + + local ThrottleValue = ply:lvsKeyDown( "CAR_THROTTLE_MOD" ) and self:GetMaxThrottle() or 0.5 + local Throttle = KeyThrottle and ThrottleValue or 0 + + if not self:IsLegalInput() then + self:LerpThrottle( 0 ) + self:LerpBrake( (KeyThrottle or KeyBrakes) and 1 or 0 ) + + return + end + + self:LerpThrottle( Throttle ) + self:LerpBrake( KeyBrakes and 1 or 0 ) +end + +function ENT:CalcHandbrake( ply ) + if ply:lvsKeyDown( "CAR_HANDBRAKE" ) then + self:EnableHandbrake() + else + self:ReleaseHandbrake() + end +end + +function ENT:CalcTransmission( ply, T ) + local EntTable = self:GetTable() + + if not EntTable.ForwardAngle or self:IsManualTransmission() then + local ShiftUp = ply:lvsKeyDown( "CAR_SHIFT_UP" ) + local ShiftDn = ply:lvsKeyDown( "CAR_SHIFT_DN" ) + + self:CalcManualTransmission( ply, EntTable, ShiftUp, ShiftDn ) + + local Reverse = self:GetReverse() + + if Reverse ~= EntTable._oldKeyReverse then + EntTable._oldKeyReverse = Reverse + + self:EmitSound( EntTable.TransShiftSound, 75 ) + end + + return + end + + local ForwardVelocity = self:VectorSplitNormal( self:LocalToWorldAngles( EntTable.ForwardAngle ):Forward(), self:GetVelocity() ) + + local KeyForward = ply:lvsKeyDown( "CAR_THROTTLE" ) + local KeyBackward = ply:lvsKeyDown( "CAR_BRAKE" ) + + local ReverseVelocity = EntTable.AutoReverseVelocity + + if KeyForward and KeyBackward then return end + + if not KeyForward and not KeyBackward then + if ForwardVelocity > ReverseVelocity then + self:SetReverse( false ) + end + + if ForwardVelocity < -ReverseVelocity then + self:SetReverse( true ) + end + + return + end + + if KeyForward and ForwardVelocity > -ReverseVelocity then + self:SetReverse( false ) + end + + if KeyBackward and ForwardVelocity < ReverseVelocity then + + if not EntTable._toggleReverse then + EntTable._toggleReverse = true + + EntTable._KeyBackTime = T + 0.4 + end + + if (EntTable._KeyBackTime or 0) < T then + self:SetReverse( true ) + end + else + EntTable._toggleReverse = nil + end + + local Reverse = self:GetReverse() + + if Reverse ~= EntTable._oldKeyReverse then + EntTable._oldKeyReverse = Reverse + + self:EmitSound( EntTable.TransShiftSound, 75 ) + end +end + +function ENT:CalcLights( ply, T ) + local LightsHandler = self:GetLightsHandler() + + if not IsValid( LightsHandler ) then return end + + local lights = ply:lvsKeyDown( "CAR_LIGHTS_TOGGLE" ) + + local EntTable = self:GetTable() + + if EntTable._lights ~= lights then + EntTable._lights = lights + + if lights then + EntTable._LightsUnpressTime = T + else + EntTable._LightsUnpressTime = nil + end + end + + if EntTable._lights and (T - EntTable._LightsUnpressTime) > 0.4 then + lights = false + end + + if lights ~= EntTable._oldlights then + if not isbool( EntTable._oldlights ) then EntTable._oldlights = lights return end + + if lights then + EntTable._LightsPressedTime = T + else + if LightsHandler:GetActive() then + if self:HasHighBeams() then + if (T - (EntTable._LightsPressedTime or 0)) >= 0.4 then + LightsHandler:SetActive( false ) + LightsHandler:SetHighActive( false ) + LightsHandler:SetFogActive( false ) + + self:EmitSound( "items/flashlight1.wav", 75, 100, 0.25 ) + else + LightsHandler:SetHighActive( not LightsHandler:GetHighActive() ) + + self:EmitSound( "buttons/lightswitch2.wav", 75, 80, 0.25) + end + else + LightsHandler:SetActive( false ) + LightsHandler:SetHighActive( false ) + LightsHandler:SetFogActive( false ) + + self:EmitSound( "items/flashlight1.wav", 75, 100, 0.25 ) + end + else + self:EmitSound( "items/flashlight1.wav", 75, 100, 0.25 ) + + if self:HasFogLights() and (T - (EntTable._LightsPressedTime or T)) >= 0.4 then + LightsHandler:SetFogActive( not LightsHandler:GetFogActive() ) + else + LightsHandler:SetActive( true ) + end + end + end + + EntTable._oldlights = lights + end +end + +function ENT:StartCommand( ply, cmd ) + if self:GetDriver() ~= ply then return end + + local EntTable = self:GetTable() + + self:SetRoadkillAttacker( ply ) + + if ply:lvsKeyDown( "CAR_MENU" ) then + self:LerpBrake( 0 ) + self:LerpThrottle( 0 ) + + return + end + + self:UpdateHydraulics( ply, cmd ) + + if ply:lvsMouseAim() then + if ply:lvsKeyDown( "FREELOOK" ) or ply:lvsKeyDown( "CAR_STEER_LEFT" ) or ply:lvsKeyDown( "CAR_STEER_RIGHT" ) then + self:CalcSteer( ply ) + else + self:CalcMouseSteer( ply ) + end + else + self:CalcSteer( ply ) + end + + if EntTable.PivotSteerEnable then + self:CalcPivotSteer( ply ) + + if self:PivotSteer() then + self:LerpBrake( 0 ) + else + self:CalcThrottle( ply ) + end + else + self:CalcThrottle( ply ) + end + + local T = CurTime() + + if (EntTable._nextCalcCMD or 0) > T then return end + + EntTable._nextCalcCMD = T + FrameTime() - 1e-4 + + self:CalcHandbrake( ply ) + self:CalcTransmission( ply, T ) + self:CalcLights( ply, T ) + self:CalcSiren( ply, T ) +end + +function ENT:CalcSiren( ply, T ) + local mode = self:GetSirenMode() + local horn = ply:lvsKeyDown( "ATTACK" ) and not ply:lvsKeyDown( "ZOOM" ) + + local EntTable = self:GetTable() + + if EntTable.HornSound and IsValid( EntTable.HornSND ) then + if horn and mode <= 0 then + EntTable.HornSND:Play() + else + EntTable.HornSND:Stop() + end + end + + if istable( EntTable.SirenSound ) and IsValid( EntTable.SirenSND ) then + local siren = ply:lvsKeyDown( "CAR_SIREN" ) + + if EntTable._siren ~= siren then + EntTable._siren = siren + + if siren then + EntTable._sirenUnpressTime = T + else + EntTable._sirenUnpressTime = nil + end + end + + if EntTable._siren and (T - EntTable._sirenUnpressTime) > 0.4 then + siren = false + end + + if siren ~= EntTable._oldsiren then + if not isbool( EntTable._oldsiren ) then EntTable._oldsiren = siren return end + + if siren then + EntTable._SirenPressedTime = T + else + if (T - (EntTable._SirenPressedTime or 0)) >= 0.4 then + if mode >= 0 then + self:SetSirenMode( -1 ) + self:StopSiren() + else + self:SetSirenMode( 0 ) + end + else + self:StartSiren( horn, true ) + end + end + + EntTable._oldsiren = siren + else + if horn ~= EntTable._OldKeyHorn then + EntTable._OldKeyHorn = horn + + if horn then + self:StartSiren( true, false ) + else + self:StartSiren( false, false ) + end + end + end + end +end + +function ENT:SetSirenSound( sound ) + if sound then + if self._PreventSiren then return end + + self._PreventSiren = true + + self.SirenSND:Stop() + self.SirenSND:SetSound( sound ) + self.SirenSND:SetSoundInterior( sound ) + + timer.Simple( 0.1, function() + if not IsValid( self.SirenSND ) then return end + + self.SirenSND:Play() + + self._PreventSiren = false + end ) + else + self:StopSiren() + end +end + +function ENT:StartSiren( horn, incr ) + local EntTable = self:GetTable() + + local Mode = self:GetSirenMode() + local Max = #EntTable.SirenSound + + local Next = Mode + + if incr then + Next = Next + 1 + + if Mode <= -1 or Next > Max then + Next = 1 + end + + self:SetSirenMode( Next ) + end + + if not EntTable.SirenSound[ Next ] then return end + + if horn then + if not EntTable.SirenSound[ Next ].horn then + + self:SetSirenMode( 0 ) + + return + end + + self:SetSirenSound( EntTable.SirenSound[ Next ].horn ) + else + if not EntTable.SirenSound[ Next ].siren then + + self:SetSirenMode( 0 ) + + return + end + + self:SetSirenSound( EntTable.SirenSound[ Next ].siren ) + end +end + +function ENT:StopSiren() + if not IsValid( self.SirenSND ) then return end + + self.SirenSND:Stop() +end + +function ENT:SetRoadkillAttacker( ply ) + local T = CurTime() + + if (self._nextSetAttacker or 0) > T then return end + + self._nextSetAttacker = T + 1 + + self:SetPhysicsAttacker( ply, 1.1 ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_controls_handbrake.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_controls_handbrake.lua new file mode 100644 index 0000000..12dc59e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_controls_handbrake.lua @@ -0,0 +1,55 @@ + +function ENT:EnableHandbrake() + if self:IsHandbrakeActive() then return end + + self:SetNWHandBrake( true ) + + self._HandbrakeEnabled = true + + for _, Wheel in pairs( self:GetWheels() ) do + if not self:GetAxleData( Wheel:GetAxle() ).UseHandbrake then continue end + + Wheel:SetHandbrake( true ) + end + + self:OnHandbrakeActiveChanged( true ) +end + +function ENT:ReleaseHandbrake() + if not self:IsHandbrakeActive() then return end + + self:SetNWHandBrake( false ) + + self._HandbrakeEnabled = nil + + for _, Wheel in pairs( self:GetWheels() ) do + if not self:GetAxleData( Wheel:GetAxle() ).UseHandbrake then continue end + + Wheel:SetHandbrake( false ) + end + + self:OnHandbrakeActiveChanged( false ) +end + +function ENT:SetHandbrake( enable ) + if enable then + + self:EnableHandbrake() + + return + end + + self:ReleaseHandbrake() +end + +function ENT:IsHandbrakeActive() + return self._HandbrakeEnabled == true +end + +function ENT:OnHandbrakeActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/generic/handbrake_on.wav", 75, 100, 0.25 ) + else + self:EmitSound( "lvs/vehicles/generic/handbrake_off.wav", 75, 100, 0.25 ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_damage.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_damage.lua new file mode 100644 index 0000000..0c2326f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_damage.lua @@ -0,0 +1,139 @@ + +ENT.FireTrailScale = 0.35 +ENT.DSArmorBulletPenetrationAdd = 50 + +DEFINE_BASECLASS( "lvs_base" ) + +function ENT:OnTakeDamage( dmginfo ) + self.LastAttacker = dmginfo:GetAttacker() + self.LastInflictor = dmginfo:GetInflictor() + + BaseClass.OnTakeDamage( self, dmginfo ) +end + +function ENT:TakeCollisionDamage( damage, attacker ) + if not IsValid( attacker ) then + attacker = game.GetWorld() + end + + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + local dmginfo = DamageInfo() + dmginfo:SetDamage( (math.min(damage / 4000,1) ^ 2) * 200 ) + dmginfo:SetAttacker( attacker ) + dmginfo:SetInflictor( attacker ) + dmginfo:SetDamageType( DMG_CRUSH + DMG_VEHICLE ) + + Engine:TakeTransmittedDamage( dmginfo ) +end + +function ENT:Explode() + if self.ExplodedAlready then return end + + self.ExplodedAlready = true + + local Driver = self:GetDriver() + + if IsValid( Driver ) then + self:HurtPlayer( Driver, 1000, self.FinalAttacker, self.FinalInflictor ) + end + + if istable( self.pSeats ) then + for _, pSeat in pairs( self.pSeats ) do + if not IsValid( pSeat ) then continue end + + local psgr = pSeat:GetDriver() + if not IsValid( psgr ) then continue end + + self:HurtPlayer( psgr, 1000, self.FinalAttacker, self.FinalInflictor ) + end + end + + self:OnFinishExplosion() + + if self.DeleteOnExplode or self.SpawnedByAISpawner then + + self:Remove() + + return + end + + if self.MDL_DESTROYED then + local numpp = self:GetNumPoseParameters() - 1 + local pps = {} + + for i = 0, numpp do + local sPose = self:GetPoseParameterName( i ) + + pps[ sPose ] = self:GetPoseParameter( sPose ) + end + + self:SetModel( self.MDL_DESTROYED ) + self:PhysicsDestroy() + self:PhysicsInit( SOLID_VPHYSICS ) + + for pName, pValue in pairs( pps ) do + self:SetPoseParameter(pName, pValue) + end + else + for id, group in pairs( self:GetBodyGroups() ) do + for subid, subgroup in pairs( group.submodels ) do + if subgroup == "" or string.lower( subgroup ) == "empty" then + self:SetBodygroup( id - 1, subid ) + end + end + end + end + + for _, ent in pairs( self:GetCrosshairFilterEnts() ) do + if not IsValid( ent ) or ent == self then continue end + + ent:Remove() + end + + for _, ent in pairs( self:GetChildren() ) do + if not IsValid( ent ) then continue end + + ent:Remove() + end + + self:SetDriver( NULL ) + + self:RemoveWeapons() + + self:StopMotionController() + + self.DoNotDuplicate = true + + self:OnExploded() +end + +function ENT:RemoveWeapons() + self:WeaponsFinish() + + for _, pod in pairs( self:GetPassengerSeats() ) do + local weapon = pod:lvsGetWeapon() + + if not IsValid( weapon ) then continue end + + weapon:WeaponsFinish() + end + + self:WeaponsOnRemove() + + for id, _ in pairs( self.WEAPONS ) do + self.WEAPONS[ id ] = {} + end +end + +function ENT:OnExploded() + self:Ignite( 30 ) + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetVelocity( self:GetVelocity() + Vector(math.random(-5,5),math.random(-5,5),math.random(150,250)) ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_engine.lua new file mode 100644 index 0000000..4d43c53 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_engine.lua @@ -0,0 +1,149 @@ + +DEFINE_BASECLASS( "lvs_base" ) + +function ENT:IsEngineStartAllowed() + if hook.Run( "LVS.IsEngineStartAllowed", self ) == false then return false end + + if self:WaterLevel() > self.WaterLevelPreventStart then return false end + + local FuelTank = self:GetFuelTank() + + if IsValid( FuelTank ) and FuelTank:GetFuel() <= 0 then return false end + + local Engine = self:GetEngine() + + if IsValid( Engine ) and Engine:GetDestroyed() then + Engine:EmitSound( "lvs/vehicles/generic/gear_grind"..math.random(1,6)..".ogg", 75, math.Rand(70,100), 0.25 ) + + return false + end + + return true +end + +function ENT:StartEngine() + for _, wheel in pairs( self:GetWheels() ) do + if not IsValid( wheel ) then continue end + + wheel:PhysWake() + end + + BaseClass.StartEngine( self ) +end + +function ENT:OnEngineStalled() + timer.Simple(math.Rand(0.8,1.6), function() + if not IsValid( self ) or self:GetEngineActive() then return end + + self:StartEngine() + end) +end + +function ENT:StallEngine() + self:StopEngine() + + if self:GetNWGear() ~= -1 then + self:SetNWGear( 1 ) + end + + self:OnEngineStalled() +end + +function ENT:ShutDownEngine() + if not self:GetEngineActive() then return end + + self:SetThrottle( 0 ) + self:StopEngine() +end + +function ENT:GetEngineTorque() + local EntTable = self:GetTable() + + local T = CurTime() + + if self:IsManualTransmission() then + local Gear = self:GetGear() + + local NumGears = Reverse and EntTable.TransGearsReverse or EntTable.TransGears + local MaxVelocity = Reverse and EntTable.MaxVelocityReverse or EntTable.MaxVelocity + + local PitchValue = MaxVelocity / NumGears + + local Vel = self:GetVelocity():Length() + + local preRatio = math.Clamp(Vel / (PitchValue * (Gear - 1)),0,1) + local Ratio = math.Clamp( 2 - math.max( Vel - PitchValue * (Gear - 1), 0 ) / PitchValue, 0, 1 ) + + local RatioIdeal = math.min( Ratio, preRatio ) + + if Gear < NumGears and Ratio < 0.5 and not EntTable.EngineRevLimited then + local engine = self:GetEngine() + + if IsValid( engine ) and Ratio < (0.5 * self:GetThrottle()) then + local dmginfo = DamageInfo() + dmginfo:SetDamage( 1 ) + dmginfo:SetAttacker( self ) + dmginfo:SetInflictor( self ) + dmginfo:SetDamageType( DMG_DIRECT ) + engine:TakeTransmittedDamage( dmginfo ) + end + end + + if preRatio <= 0.05 and Vel < PitchValue and Gear > 1 then + self:SetNWGear( 1 ) + end + + if EntTable.TransShiftSpeed > 0.5 then + if EntTable._OldTorqueShiftGear ~= Gear then + if (EntTable._OldTorqueShiftGear or 0) < Gear then + EntTable._TorqueShiftDelayTime = T + math.max( EntTable.TransShiftSpeed - 0.5, 0 ) + end + + EntTable._OldTorqueShiftGear = Gear + end + end + + if (EntTable._TorqueShiftDelayTime or 0) > T then return 0 end + + return math.deg( self.EngineTorque ) * RatioIdeal + end + + if EntTable.TransShiftSpeed > 0.5 and (EntTable._OldTorqueHoldGear or 0) < T then + local Reverse = self:GetReverse() + local vehVel = self:GetVelocity():Length() + local wheelVel = self:GetWheelVelocity() + + local NumGears = EntTable.TransGears + local MaxGear = Reverse and EntTable.TransGearsReverse or NumGears + + local PitchValue = EntTable.MaxVelocity / NumGears + + local DesiredGear = 1 + + local VelocityGeared = vehVel + + while (VelocityGeared > PitchValue) and DesiredGear< NumGears do + VelocityGeared = VelocityGeared - PitchValue + + DesiredGear = DesiredGear + 1 + end + + if EntTable._OldTorqueShiftGear ~= DesiredGear then + if not EntTable._OldTorqueShiftGear then + EntTable._OldTorqueShiftGear = DesiredGear + else + if (EntTable._OldTorqueShiftGear or 0) < DesiredGear then + EntTable._TorqueShiftDelayTime = T + EntTable.TransShiftSpeed + end + + EntTable._OldTorqueHoldGear = T + EntTable.TransShiftSpeed + EntTable.TransMinGearHoldTime + + EntTable._OldTorqueShiftGear = DesiredGear + end + end + end + + if (EntTable._TorqueShiftDelayTime or 0) > T then return math.deg( EntTable.EngineTorque ) * EntTable.TransShiftTorqueFactor end + + return math.deg( EntTable.EngineTorque ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_hydraulics.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_hydraulics.lua new file mode 100644 index 0000000..f36484f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_hydraulics.lua @@ -0,0 +1,105 @@ + +function ENT:UpdateHydraulics( ply, cmd ) + if not self._HydraulicControlers then return end + + local all = ply:lvsKeyDown( "CAR_HYDRAULIC" ) + + if self._HydToggleAll ~= all then + self._HydToggleAll = all + + if all then + self._HydToggleHeight = not self._HydToggleHeight + end + end + + local HeightAll = self._HydToggleHeight and 1 or 0 + local Invert = self._HydToggleHeight and -1 or 1 + + local FRONT = ply:lvsKeyDown( "CAR_HYDRAULIC_FRONT" ) + local REAR = ply:lvsKeyDown( "CAR_HYDRAULIC_REAR" ) + local LEFT = ply:lvsKeyDown( "CAR_HYDRAULIC_LEFT" ) + local RIGHT = ply:lvsKeyDown( "CAR_HYDRAULIC_RIGHT" ) + + local FL = (FRONT or LEFT) and Invert or 0 + local FR = (FRONT or RIGHT) and Invert or 0 + local RL = (REAR or LEFT) and Invert or 0 + local RR = (REAR or RIGHT) and Invert or 0 + + local HeightType = { + [""] = HeightAll, + ["fl"] = HeightAll + FL, + ["fr"] = HeightAll + FR, + ["rl"] = HeightAll + RL, + ["rr"] = HeightAll + RR, + } + + local Rate = FrameTime() * 10 + + for _, control in ipairs( self._HydraulicControlers ) do + local curHeight = control:GetHeight() + local desHeight = HeightType[ control:GetType() ] + + if curHeight == desHeight then control:OnFinish() continue end + + control:SetHeight( curHeight + math.Clamp(desHeight - curHeight,-Rate,Rate) ) + end +end + +local HYD = {} +HYD.__index = HYD +function HYD:Initialize() +end +function HYD:GetHeight() + if not IsValid( self._WheelEntity ) then return 0 end + + return self._WheelEntity:GetSuspensionHeight() +end +function HYD:SetHeight( new ) + if not IsValid( self._WheelEntity ) then return end + + self:OnStart() + + self._WheelEntity:SetSuspensionHeight( new ) +end +function HYD:OnStart() + if self.IsUpdatingHeight then return end + + self.IsUpdatingHeight = true + + if not IsValid( self._BaseEntity ) then return end + + if self:GetHeight() > 0.5 then + self._BaseEntity:EmitSound("lvs/vehicles/generic/vehicle_hydraulic_down.ogg", 75, 100, 0.5, CHAN_WEAPON) + else + self._BaseEntity:EmitSound("lvs/vehicles/generic/vehicle_hydraulic_up.ogg", 75, 100, 0.5, CHAN_WEAPON) + end +end +function HYD:OnFinish() + if not self.IsUpdatingHeight then return end + + self.IsUpdatingHeight = nil + + if not IsValid( self._WheelEntity ) then return end + + self._WheelEntity:EmitSound("lvs/vehicles/generic/vehicle_hydraulic_collide"..math.random(1,2)..".ogg", 75, 100, 0.5) +end +function HYD:GetType() + return self._WheelType +end + +function ENT:CreateHydraulicControler( type, wheel ) + if not istable( self._HydraulicControlers ) then + self._HydraulicControlers = {} + end + + local controller = {} + + setmetatable( controller, HYD ) + + controller._BaseEntity = self + controller._WheelEntity = wheel + controller._WheelType = type or "" + controller:Initialize() + + table.insert( self._HydraulicControlers, controller ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_manualtransmission.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_manualtransmission.lua new file mode 100644 index 0000000..5a630d2 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_manualtransmission.lua @@ -0,0 +1,72 @@ +function ENT:EnableManualTransmission() + self:SetReverse( false ) + self:SetNWGear( 1 ) +end + +function ENT:DisableManualTransmission() + self:SetNWGear( -1 ) +end + +function ENT:CalcManualTransmission( ply, EntTable, ShiftUp, ShiftDn ) + if ShiftUp ~= EntTable._oldShiftUp then + EntTable._oldShiftUp = ShiftUp + + if ShiftUp then + self:ShiftUp() + end + end + + if ShiftDn ~= EntTable._oldShiftDn then + EntTable._oldShiftDn = ShiftDn + + if ShiftDn then + self:ShiftDown() + end + end +end + +function ENT:OnShiftUp() +end + +function ENT:OnShiftDown() +end + +function ENT:ShiftUp() + if self:OnShiftUp() == false then return end + + local Reverse = self:GetReverse() + + if Reverse then + local NextGear = self:GetNWGear() - 1 + + self:SetNWGear( math.max( NextGear, 1 ) ) + + if NextGear <= 0 then + self:SetReverse( false ) + end + + return + end + + self:SetNWGear( math.min( self:GetNWGear() + 1, self.TransGears ) ) +end + +function ENT:ShiftDown() + if self:OnShiftDown() == false then return end + + local Reverse = self:GetReverse() + + if Reverse then + self:SetNWGear( math.min( self:GetNWGear() + 1, self.TransGearsReverse ) ) + + return + end + + local NextGear = self:GetNWGear() - 1 + + self:SetNWGear( math.max( NextGear, 1 ) ) + + if NextGear <= 0 then + self:SetReverse( true ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_pivotsteer.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_pivotsteer.lua new file mode 100644 index 0000000..60aa2f7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_pivotsteer.lua @@ -0,0 +1,58 @@ + +ENT.PivotSteerEnable = false +ENT.PivotSteerByBrake = true +ENT.PivotSteerWheelRPM = 40 +ENT.PivotSteerTorqueMul = 2 + +function ENT:GetPivotSteer() + return self._PivotSteer or 0 +end + +function ENT:SetPivotSteer( new ) + self._PivotSteer = new +end + +function ENT:PivotSteer() + if not self.PivotSteerEnable then return false end + + return (self._PivotSteer or 0) ~= 0 +end + +function ENT:CalcPivotSteer( ply ) + local KeyLeft = ply:lvsKeyDown( "CAR_STEER_LEFT" ) + local KeyRight = ply:lvsKeyDown( "CAR_STEER_RIGHT" ) + local KeyThrottle = ply:lvsKeyDown( "CAR_THROTTLE" ) + local KeyBrake = ply:lvsKeyDown( "CAR_BRAKE" ) + + local ShouldSteer = (KeyLeft or KeyRight) and not KeyBrake and not KeyThrottle + + local Throttle = self:GetThrottle() + + if self._oldShouldSteer ~= ShouldSteer then + self._oldShouldSteer = ShouldSteer + + if ShouldSteer then + self._ShouldSteer = true + end + end + + if ShouldSteer then + self._PivotSteer = (KeyRight and 1 or 0) - (KeyLeft and 1 or 0) + self:SetSteer( 0 ) + end + + if not ShouldSteer and self._ShouldSteer then + self:LerpThrottle( 0 ) + + if Throttle <= 0 then + self._ShouldSteer = nil + self._PivotSteer = 0 + end + + return + end + + if not self._ShouldSteer then return end + + self:LerpThrottle( (KeyRight or KeyLeft) and 1 or 0 ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_riggedwheels.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_riggedwheels.lua new file mode 100644 index 0000000..066050d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_riggedwheels.lua @@ -0,0 +1,99 @@ + +local function SetAll( ent, n ) + if not IsValid( ent ) then return end + + ent:SetPoseParameter("vehicle_wheel_fl_height",n) + ent:SetPoseParameter("vehicle_wheel_fr_height",n) + ent:SetPoseParameter("vehicle_wheel_rl_height",n) + ent:SetPoseParameter("vehicle_wheel_rr_height",n) +end + +function ENT:CreateRigControler( name, wheelEntity, min, max ) + local RigHandler = ents.Create( "lvs_wheeldrive_righandler" ) + + if not IsValid( RigHandler ) then + self:Remove() + + print("LVS: Failed to create righandler entity. Vehicle terminated.") + + return + end + + RigHandler:SetPos( self:GetPos() ) + RigHandler:SetAngles( self:GetAngles() ) + RigHandler:Spawn() + RigHandler:Activate() + RigHandler:SetParent( self ) + RigHandler:SetBase( self ) + RigHandler:SetPose0( min ) + RigHandler:SetPose1( max ) + RigHandler:SetWheel( wheelEntity ) + RigHandler:SetNameID( name ) + + self:DeleteOnRemove( RigHandler ) + + self:TransferCPPI( RigHandler ) + + return RigHandler +end + +function ENT:AddWheelsUsingRig( FrontRadius, RearRadius, data ) + if not istable( data ) then data = {} end + + local Body = ents.Create( "prop_dynamic" ) + Body:SetModel( self:GetModel() ) + Body:SetPos( self:GetPos() ) + Body:SetAngles( self:GetAngles() ) + Body:SetMoveType( MOVETYPE_NONE ) + Body:Spawn() + Body:Activate() + Body:SetColor( Color(255,255,255,0) ) + Body:SetRenderMode( RENDERMODE_TRANSCOLOR ) + + SetAll( Body, 0 ) + + SafeRemoveEntityDelayed( Body, 0.3 ) + + local id_fl = Body:LookupAttachment( "wheel_fl" ) + local id_fr = Body:LookupAttachment( "wheel_fr" ) + local id_rl = Body:LookupAttachment( "wheel_rl" ) + local id_rr = Body:LookupAttachment( "wheel_rr" ) + + local ForwardAngle = angle_zero + + if not isnumber( FrontRadius ) or not isnumber( RearRadius ) or id_fl == 0 or id_fr == 0 or id_rl == 0 or id_rr == 0 then return NULL, NULL, NULL, NULL, ForwardAngle end + + local pFL0 = Body:WorldToLocal( Body:GetAttachment( id_fl ).Pos ) + local pFR0 = Body:WorldToLocal( Body:GetAttachment( id_fr ).Pos ) + local pRL0 = Body:WorldToLocal( Body:GetAttachment( id_rl ).Pos ) + local pRR0 = Body:WorldToLocal( Body:GetAttachment( id_rr ).Pos ) + + local ForwardAngle = ((pFL0 + pFR0) / 2 - (pRL0 + pRR0) / 2):Angle() + ForwardAngle.p = 0 + ForwardAngle.y = math.Round( ForwardAngle.y, 0 ) + ForwardAngle.r = 0 + ForwardAngle:Normalize() + + local FL = self:AddWheel( { hide = (not isstring( data.mdl_fl )), pos = pFL0, radius = FrontRadius, mdl = data.mdl_fl, mdl_ang = data.mdl_ang_fl } ) + local FR = self:AddWheel( { hide = (not isstring( data.mdl_fr )), pos = pFR0, radius = FrontRadius, mdl = data.mdl_fr, mdl_ang = data.mdl_ang_fr } ) + local RL = self:AddWheel( { hide = (not isstring( data.mdl_rl )), pos = pRL0, radius = RearRadius, mdl = data.mdl_rl, mdl_ang = data.mdl_ang_rl } ) + local RR = self:AddWheel( { hide = (not isstring( data.mdl_rr )), pos = pRR0, radius = RearRadius, mdl = data.mdl_rr, mdl_ang = data.mdl_ang_rr } ) + + SetAll( Body, 1 ) + + timer.Simple( 0.15, function() + if not IsValid( self ) or not IsValid( Body ) then return end + + local pFL1 = Body:WorldToLocal( Body:GetAttachment( id_fl ).Pos ) + local pFR1 = Body:WorldToLocal( Body:GetAttachment( id_fr ).Pos ) + local pRL1 = Body:WorldToLocal( Body:GetAttachment( id_rl ).Pos ) + local pRR1 = Body:WorldToLocal( Body:GetAttachment( id_rr ).Pos ) + + self:CreateRigControler( "fl", FL, pFL0.z, pFL1.z ) + self:CreateRigControler( "fr", FR, pFR0.z, pFR1.z ) + self:CreateRigControler( "rl", RL, pRL0.z, pRL1.z ) + self:CreateRigControler( "rr", RR, pRR0.z, pRR1.z ) + end ) + + return FL, FR, RL, RR, ForwardAngle +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_wheelsystem.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_wheelsystem.lua new file mode 100644 index 0000000..6b1b1eb --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive/sv_wheelsystem.lua @@ -0,0 +1,451 @@ +ENT._WheelEnts = {} +ENT._WheelAxleID = 0 +ENT._WheelAxleData = {} + +function ENT:ClearWheels() + for _, ent in pairs( self:GetWheels() ) do + ent:Remove() + end + + table.Empty( self._WheelEnts ) + table.Empty( self._WheelAxleData ) + + self._WheelAxleID = 0 +end + +function ENT:GetWheels() + local EntTable = self:GetTable() + + for id, ent in pairs( EntTable._WheelEnts ) do + if IsValid( ent ) then continue end + + EntTable._WheelEnts[ id ] = nil + end + + return EntTable._WheelEnts +end + +function ENT:GetAxleData( ID ) + local EntTable = self:GetTable() + + if not EntTable._WheelAxleData[ ID ] then return {} end + + return EntTable._WheelAxleData[ ID ] +end + +function ENT:CreateSteerMaster( TargetEntity ) + if not IsValid( TargetEntity ) then return end + + local Master = ents.Create( "lvs_wheeldrive_steerhandler" ) + + if not IsValid( Master ) then + self:Remove() + + print("LVS: Failed to create steermaster entity. Vehicle terminated.") + + return + end + + Master:SetPos( TargetEntity:GetPos() ) + Master:SetAngles( Angle(0,90,0) ) + Master:Spawn() + Master:Activate() + + self:DeleteOnRemove( Master ) + self:TransferCPPI( Master ) + + return Master +end + +function ENT:AddWheel( data ) + if not istable( data ) or not isvector( data.pos ) then return end + + local Wheel = ents.Create( "lvs_wheeldrive_wheel" ) + + if not IsValid( Wheel ) then + self:Remove() + + print("LVS: Failed to create wheel entity. Vehicle terminated.") + + return + end + + Wheel:SetModel( data.mdl or "models/props_vehicles/tire001c_car.mdl" ) + Wheel:SetPos( self:LocalToWorld( data.pos ) ) + Wheel:SetAngles( Angle(0,0,0) ) + Wheel:Spawn() + Wheel:Activate() + + Wheel:SetBase( self ) + + Wheel:SetAlignmentAngle( data.mdl_ang or Angle(0,0,0) ) + + Wheel:SetHideModel( data.hide == true ) + + Wheel:lvsMakeSpherical( data.radius or -1 ) + + Wheel:SetWidth( data.width or 4 ) + + Wheel:SetCamber( data.camber or 0 ) + Wheel:SetCaster( data.caster or 0 ) + Wheel:SetToe( data.toe or 0 ) + Wheel:CheckAlignment() + Wheel:SetWheelType( data.wheeltype ) + + if isnumber( data.MaxHealth ) then + Wheel:SetMaxHP( data.MaxHealth ) + Wheel:SetHP( data.MaxHealth ) + end + + if isnumber( data.DSArmorIgnoreForce ) then + Wheel.DSArmorIgnoreForce = data.DSArmorIgnoreForce + end + + self:DeleteOnRemove( Wheel ) + self:TransferCPPI( Wheel ) + + local PhysObj = Wheel:GetPhysicsObject() + + if not IsValid( PhysObj ) then + self:Remove() + + print("LVS: Failed to create wheel physics. Vehicle terminated.") + + return + end + + PhysObj:SetMass( self.WheelPhysicsMass * self.PhysicsWeightScale ) + PhysObj:SetInertia( self.WheelPhysicsInertia * self.PhysicsWeightScale ) + PhysObj:EnableDrag( false ) + PhysObj:EnableMotion( false ) + + local nocollide_constraint = constraint.NoCollide(self,Wheel,0,0) + nocollide_constraint.DoNotDuplicate = true + + debugoverlay.Line( self:GetPos(), self:LocalToWorld( data.pos ), 5, Color(150,150,150), true ) + + table.insert( self._WheelEnts, Wheel ) + + local Master = self:CreateSteerMaster( Wheel ) + + local Lock = 0.0001 + + local B1 = constraint.AdvBallsocket( Wheel,Master,0,0,vector_origin,vector_origin,0,0,-180,-Lock,-Lock,180,Lock,Lock,0,0,0,1,1) + B1.DoNotDuplicate = true + + local B2 = constraint.AdvBallsocket( Master,Wheel,0,0,vector_origin,vector_origin,0,0,-180,Lock,Lock,180,-Lock,-Lock,0,0,0,1,1) + B2.DoNotDuplicate = true + + local expectedMaxRPM = math.max( self.MaxVelocity, self.MaxVelocityReverse ) * 60 / math.pi / (Wheel:GetRadius() * 2) + + if expectedMaxRPM > 800 then + local B3 = constraint.AdvBallsocket( Wheel,Master,0,0,vector_origin,vector_origin,0,0,-180,Lock,Lock,180,-Lock,-Lock,0,0,0,1,1) + B3.DoNotDuplicate = true + + local B4 = constraint.AdvBallsocket( Master,Wheel,0,0,vector_origin,vector_origin,0,0,-180,-Lock,-Lock,180,Lock,Lock,0,0,0,1,1) + B4.DoNotDuplicate = true + end + + if expectedMaxRPM > 2150 then + local possibleMaxVelocity = (2150 * (math.pi * (Wheel:GetRadius() * 2))) / 60 + + self.MaxVelocity = math.min( self.MaxVelocity, possibleMaxVelocity ) + self.MaxVelocityReverse = math.min( self.MaxVelocityReverse, possibleMaxVelocity ) + + print("[LVS] - peripheral speed out of range! clamping!" ) + end + + Wheel:SetMaster( Master ) + + timer.Simple(0, function() + if not IsValid( self ) or not IsValid( Wheel ) or not IsValid( PhysObj ) then return end + + Master:SetAngles( self:GetAngles() ) + Wheel:SetAngles( self:LocalToWorldAngles( Angle(0,-90,0) ) ) + + self:AddToMotionController( PhysObj ) + + PhysObj:EnableMotion( true ) + end ) + + if isnumber( self._WheelSkin ) then Wheel:SetSkin( self._WheelSkin ) end + + if IsColor( self._WheelColor ) then Wheel:SetColor( self._WheelColor ) end + + return Wheel +end + +function ENT:DefineAxle( data ) + if not istable( data ) then print("LVS: couldn't define axle: no axle data") return end + + if not istable( data.Axle ) or not istable( data.Wheels ) or not istable( data.Suspension ) then print("LVS: couldn't define axle: no axle/wheel/suspension data") return end + + self._WheelAxleID = self._WheelAxleID + 1 + + -- defaults + if self.ForcedForwardAngle then + data.Axle.ForwardAngle = self.ForcedForwardAngle + else + data.Axle.ForwardAngle = data.Axle.ForwardAngle or Angle(0,0,0) + end + + data.Axle.SteerType = data.Axle.SteerType or LVS.WHEEL_STEER_NONE + data.Axle.SteerAngle = data.Axle.SteerAngle or 20 + data.Axle.TorqueFactor = data.Axle.TorqueFactor or 1 + data.Axle.BrakeFactor = data.Axle.BrakeFactor or 1 + data.Axle.UseHandbrake = data.Axle.UseHandbrake == true + + if not self.ForwardAngle then self.ForwardAngle = data.Axle.ForwardAngle end + + data.Suspension.Height = data.Suspension.Height or 20 + data.Suspension.MaxTravel = data.Suspension.MaxTravel or data.Suspension.Height + data.Suspension.ControlArmLength = data.Suspension.ControlArmLength or 25 + data.Suspension.SpringConstant = data.Suspension.SpringConstant or 20000 + data.Suspension.SpringDamping = data.Suspension.SpringDamping or 2000 + data.Suspension.SpringRelativeDamping = data.Suspension.SpringRelativeDamping or 2000 + + local AxleCenter = Vector(0,0,0) + for _, Wheel in ipairs( data.Wheels ) do + if not IsEntity( Wheel ) then print("LVS: !ERROR!, given wheel is not a entity!") return end + + AxleCenter = AxleCenter + Wheel:GetPos() + + if not Wheel.SetAxle then continue end + + Wheel:SetAxle( self._WheelAxleID ) + end + AxleCenter = AxleCenter / #data.Wheels + + debugoverlay.Text( AxleCenter, "Axle "..self._WheelAxleID.." Center ", 5, true ) + debugoverlay.Cross( AxleCenter, 5, 5, Color( 255, 0, 0 ), true ) + debugoverlay.Line( AxleCenter, AxleCenter + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * 25, 5, Color(255,0,0), true ) + debugoverlay.Text( AxleCenter + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * 25, "Axle "..self._WheelAxleID.." Forward", 5, true ) + + data.Axle.CenterPos = self:WorldToLocal( AxleCenter ) + + self._WheelAxleData[ self._WheelAxleID ] = { + ForwardAngle = data.Axle.ForwardAngle, + AxleCenter = data.Axle.CenterPos, + SteerType = data.Axle.SteerType, + SteerAngle = data.Axle.SteerAngle, + TorqueFactor = data.Axle.TorqueFactor, + BrakeFactor = data.Axle.BrakeFactor, + UseHandbrake = data.Axle.UseHandbrake, + } + + for id, Wheel in ipairs( data.Wheels ) do + local Elastic = self:CreateSuspension( Wheel, AxleCenter, self:LocalToWorldAngles( data.Axle.ForwardAngle ), data.Suspension ) + + Wheel.SuspensionConstraintElastic = Elastic + + debugoverlay.Line( AxleCenter, Wheel:GetPos(), 5, Color(150,0,0), true ) + debugoverlay.Text( Wheel:GetPos(), "Axle "..self._WheelAxleID.." Wheel "..id, 5, true ) + + local AngleStep = 15 + for ang = 15, 360, AngleStep do + if not Wheel.GetRadius then continue end + + local radius = Wheel:GetRadius() + local X1 = math.cos( math.rad( ang ) ) * radius + local Y1 = math.sin( math.rad( ang ) ) * radius + + local X2 = math.cos( math.rad( ang + AngleStep ) ) * radius + local Y2 = math.sin( math.rad( ang + AngleStep ) ) * radius + + local P1 = Wheel:GetPos() + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Up() * Y1 + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * X1 + local P2 = Wheel:GetPos() + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Up() * Y2 + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * X2 + + debugoverlay.Line( P1, P2, 5, Color( 150, 150, 150 ), true ) + end + + -- nocollide them with each other + for i = id, #data.Wheels do + local Ent = data.Wheels[ i ] + if Ent == Wheel then continue end + + local nocollide_constraint = constraint.NoCollide(Ent,Wheel,0,0) + nocollide_constraint.DoNotDuplicate = true + end + end + + return self._WheelAxleData[ self._WheelAxleID ] +end + +function ENT:CreateSuspension( Wheel, CenterPos, DirectionAngle, data ) + if not IsValid( Wheel ) or not IsEntity( Wheel ) then return end + + local height = data.Height + local maxtravel = data.MaxTravel + local constant = data.SpringConstant + local damping = data.SpringDamping + local rdamping = data.SpringRelativeDamping + + local LimiterLength = 60 + local LimiterRopeLength = math.sqrt( maxtravel ^ 2 + LimiterLength ^ 2 ) + + local Pos = Wheel:GetPos() + + local PosL, _ = WorldToLocal( Pos, DirectionAngle, CenterPos, DirectionAngle ) + + local Forward = DirectionAngle:Forward() + local Right = DirectionAngle:Right() * (PosL.y > 0 and 1 or -1) + local Up = DirectionAngle:Up() + + local RopeSize = 0 + local RopeLength = data.ControlArmLength + + if height == 0 or maxtravel == 0 or RopeLength == 0 then + local ballsocket = constraint.Ballsocket( self, Wheel, 0, 0, Vector(0,0,0), 0, 0, 1 ) + ballsocket.DoNotDuplicate = true + + return + end + + local P1 = Pos + Forward * RopeLength * 0.5 + Right * RopeLength + local P2 = Pos + local Rope1 = constraint.Rope(self, Wheel,0,0,self:WorldToLocal( P1 ), Vector(0,0,0), Vector(RopeLength * 0.5,RopeLength,0):Length(), 0, 0, RopeSize,"cable/cable2", true ) + Rope1.DoNotDuplicate = true + debugoverlay.Line( P1, P2, 5, Color(0,255,0), true ) + + P1 = Pos - Forward * RopeLength * 0.5 + Right * RopeLength + local Rope2 = constraint.Rope(self, Wheel,0,0,self:WorldToLocal( P1 ), Vector(0,0,0), Vector(RopeLength * 0.5,RopeLength,0):Length(), 0, 0, RopeSize,"cable/cable2", true ) + Rope2.DoNotDuplicate = true + debugoverlay.Line( P1, P2, 5, Color(0,255,0), true ) + + local Offset = Up * height + + Wheel:SetPos( Pos - Offset ) + + local Limiter = constraint.Rope(self,Wheel,0,0,self:WorldToLocal( Pos - Up * height * 0.5 - Right * LimiterLength), Vector(0,0,0),LimiterRopeLength, 0, 0, RopeSize,"cable/cable2", false ) + Limiter.DoNotDuplicate = true + + P1 = Wheel:GetPos() + Up * (height * 2 + maxtravel * 2) + + local Elastic = constraint.Elastic( Wheel, self, 0, 0, Vector(0,0,0), self:WorldToLocal( P1 ), constant, damping, rdamping,"cable/cable2", RopeSize, false ) + Elastic.DoNotDuplicate = true + debugoverlay.SweptBox( P1, P2,- Vector(0,1,1), Vector(0,1,1), (P1 - P2):Angle(), 5, Color( 255, 255, 0 ), true ) + + Wheel:SetPos( Pos ) + + return Elastic +end + +function ENT:AlignWheel( Wheel ) + if not IsValid( Wheel ) then return false end + + local Master = Wheel.MasterEntity + + if not IsValid( Master ) then + if not isfunction( Wheel.GetMaster ) then return false end + + Master = Wheel:GetMaster() + + Wheel.MasterEntity = Master + + if IsValid( Master ) then + Wheel.MasterPhysObj = Master:GetPhysicsObject() + end + + return false + end + + local PhysObj = Wheel.MasterPhysObj + + if not IsValid( PhysObj ) then Wheel:Remove() return false end + + local Steer = self:GetSteer() + + if PhysObj:IsMotionEnabled() then PhysObj:EnableMotion( false ) return false end + + if not Master.lvsValidAxleData then + local ID = Wheel:GetAxle() + + if ID then + local Axle = self:GetAxleData( ID ) + + Master.AxleCenter = Axle.AxleCenter + Master.ForwardAngle = Axle.ForwardAngle or angle_zero + Master.SteerAngle = Axle.SteerAngle or 0 + Master.SteerType = Axle.SteerType or LVS.WHEEL_STEER_NONE + + Master.lvsValidAxleData = true + + if Axle.SteerType == LVS.WHEEL_STEER_ACKERMANN then + if not self._AckermannCenter then + local AxleCenter = vector_origin + local NumAxles = 0 + + for _, data in pairs( self._WheelAxleData ) do + if data.SteerType and data.SteerType ~= LVS.WHEEL_STEER_NONE then continue end + + AxleCenter = AxleCenter + data.AxleCenter + NumAxles = NumAxles + 1 + end + + self._AckermannCenter = AxleCenter / NumAxles + end + + local Dist = (self._AckermannCenter - Axle.AxleCenter):Length() + + if not self._AckermannDist or Dist > self._AckermannDist then + self._AckermannDist = Dist + end + end + end + + return false + end + + local AxleAng = self:LocalToWorldAngles( Master.ForwardAngle ) + + if Wheel.CamberCasterToe then + AxleAng:RotateAroundAxis( AxleAng:Right(), Wheel:GetCaster() ) + AxleAng:RotateAroundAxis( AxleAng:Forward(), Wheel:GetCamber() ) + AxleAng:RotateAroundAxis( AxleAng:Up(), Wheel:GetToe() ) + end + + local SteerType = Master.SteerType + + if SteerType <= LVS.WHEEL_STEER_NONE then Master:SetAngles( AxleAng ) return true end + + if SteerType == LVS.WHEEL_STEER_ACKERMANN then + if Steer ~= 0 then + local EntTable = self:GetTable() + + local AxleCenter = self:LocalToWorld( Master.AxleCenter ) + local RotCenter = self:LocalToWorld( EntTable._AckermannCenter ) + local RotPoint = self:LocalToWorld( EntTable._AckermannCenter + Master.ForwardAngle:Right() * (EntTable._AckermannDist / math.tan( math.rad( Steer ) )) ) + + local A = (AxleCenter - RotCenter):Length() + local C = (Wheel:GetPos() - RotPoint):Length() + + local Invert = ((self:VectorSplitNormal( Master.ForwardAngle:Forward(), Master.AxleCenter - EntTable._AckermannCenter ) < 0) and -1 or 1) * self:Sign( -Steer ) + + local Ang = (90 - math.deg( math.acos( A / C ) )) * Invert + + AxleAng:RotateAroundAxis( AxleAng:Up(), Ang ) + end + else + AxleAng:RotateAroundAxis( AxleAng:Up(), math.Clamp((SteerType == LVS.WHEEL_STEER_FRONT) and -Steer or Steer,-Master.SteerAngle,Master.SteerAngle) ) + end + + Master:SetAngles( AxleAng ) + + return true +end + +function ENT:WheelsOnGround() + + for _, ent in pairs( self:GetWheels() ) do + if not IsValid( ent ) then continue end + + if ent:PhysicsOnGround() then + return true + end + end + + return false +end + +function ENT:OnWheelCollision( data, physobj ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/cl_init.lua new file mode 100644 index 0000000..a5fa314 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/cl_init.lua @@ -0,0 +1,22 @@ +include("shared.lua") + +function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + local kmh = math.Round(self:GetVelocity():Length() * 0.09144,0) + + draw.DrawText( "km/h ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT ) + draw.DrawText( kmh, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT ) +end + +-- kill engine sounds +function ENT:OnEngineActiveChanged( Active ) +end + +-- kill flyby system +function ENT:FlyByThink() +end + +function ENT:OnFlyBy( Pitch ) +end + +function ENT:StopFlyBy() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/init.lua new file mode 100644 index 0000000..66c0dd6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/init.lua @@ -0,0 +1,248 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") +include("sv_ai.lua") +include("sv_kill_functions.lua") + +function ENT:EnableManualTransmission() + return false +end + +function ENT:DisableManualTransmission() + return false +end + +DEFINE_BASECLASS( "lvs_base_wheeldrive" ) + +function ENT:HandleActive() + local Pod = self:GetDriverSeat() + + if IsValid( Pod ) then + BaseClass.HandleActive( self ) + + return + end + + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) then self:SetActive( false ) return end + + self:SetActive( InputTarget:GetActive() ) +end + +function ENT:OnTick() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) then return end + + local InputLightsHandler = InputTarget:GetLightsHandler() + local LightsHandler = self:GetLightsHandler() + + if not IsValid( InputLightsHandler ) or not IsValid( LightsHandler ) then return end + + LightsHandler:SetActive( InputLightsHandler:GetActive() ) + LightsHandler:SetHighActive( InputLightsHandler:GetHighActive() ) + LightsHandler:SetFogActive( InputLightsHandler:GetFogActive() ) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, phys, deltatime, simulate ) + return ForceAngle, vector_origin, simulate +end + +function ENT:PhysicsSimulate( phys, deltatime ) + local ent = phys:GetEntity() + + if ent == self then + if not self:StabilityAssist() or not self:WheelsOnGround() then return vector_origin, vector_origin, SIM_NOTHING end + + local ForceAngle = Vector(0,0, math.deg( -phys:GetAngleVelocity().z ) * math.min( phys:GetVelocity():Length() / self.PhysicsDampingSpeed, 1 ) * self.ForceAngleMultiplier ) + + return self:PhysicsSimulateOverride( ForceAngle, phys, deltatime, SIM_GLOBAL_ACCELERATION ) + end + + return self:SimulateRotatingWheel( ent, phys, deltatime ) +end + +function ENT:SimulateRotatingWheel( ent, phys, deltatime ) + local RotationAxis = ent:GetRotationAxis() + + local curRPM = self:VectorSplitNormal( RotationAxis, phys:GetAngleVelocity() ) / 6 + + ent:SetRPM( curRPM ) + + if not self:AlignWheel( ent ) or ent:IsHandbrakeActive() then + + local HandBrake = self:GetNWHandBrake() + + if HandBrake then + if not ent:IsRotationLocked() then + ent:LockRotation() + end + + ent:SetRPM( 0 ) + else + if ent:IsRotationLocked() then + ent:ReleaseRotation() + end + end + + return vector_origin, vector_origin, SIM_NOTHING + end + + if self:GetBrake() > 0 and not ent:IsRotationLocked() then + local ForwardVel = self:VectorSplitNormal( ent:GetDirectionAngle():Forward(), phys:GetVelocity() ) + + local targetRPM = ent:VelToRPM( ForwardVel ) * 0.5 + + if math.abs( curRPM ) < self.WheelBrakeLockupRPM then + ent:LockRotation() + else + if (ForwardVel > 0 and targetRPM > 0) or (ForwardVel < 0 and targetRPM < 0) then + ForceAngle = RotationAxis * math.Clamp( (targetRPM - curRPM) / 100,-1,1) * math.deg( self.WheelBrakeForce ) * ent:GetBrakeFactor() * self:GetBrake() + end + end + + return ForceAngle, vector_origin, SIM_GLOBAL_ACCELERATION + end + + if ent:IsRotationLocked() then + ent:ReleaseRotation() + end + + return vector_origin, vector_origin, SIM_NOTHING +end + +function ENT:OnCoupleChanged( targetVehicle, targetHitch, active ) + + if IsValid( targetHitch ) and targetHitch:GetHitchType() == LVS.HITCHTYPE_FEMALE then return end + + if active then + self:OnCoupled( targetVehicle, targetHitch ) + + self:SetInputTarget( targetVehicle ) + + if not IsValid( targetHitch ) then return end + + targetHitch:EmitSound("doors/door_metal_medium_open1.wav") + else + self:OnDecoupled( targetVehicle, targetHitch ) + + self:SetInputTarget( NULL ) + + if not IsValid( targetHitch ) then return end + + targetHitch:EmitSound("buttons/lever8.wav") + + local LightsHandler = self:GetLightsHandler() + + if not IsValid( LightsHandler ) then return end + + LightsHandler:SetActive( false ) + LightsHandler:SetHighActive( false ) + LightsHandler:SetFogActive( false ) + end +end + +function ENT:OnCoupled( targetVehicle, targetHitch ) +end + +function ENT:OnDecoupled( targetVehicle, targetHitch ) +end + +function ENT:OnStartDrag( caller, activator ) +end + +function ENT:OnStopDrag( caller, activator ) +end + +function ENT:OnStartExplosion() +end + +function ENT:OnFinishExplosion() + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( self:OBBCenter() ) ) + util.Effect( "lvs_trailer_explosion", effectdata, true, true ) + + self:EmitSound("physics/metal/metal_box_break"..math.random(1,2)..".wav",75,100,1) + + self:SpawnGibs() +end + +local gibs = { + "models/gibs/manhack_gib01.mdl", + "models/gibs/manhack_gib02.mdl", + "models/gibs/manhack_gib03.mdl", + "models/gibs/manhack_gib04.mdl", + "models/props_c17/canisterchunk01a.mdl", + "models/props_c17/canisterchunk01d.mdl", + "models/props_c17/oildrumchunk01a.mdl", + "models/props_c17/oildrumchunk01b.mdl", + "models/props_c17/oildrumchunk01c.mdl", + "models/props_c17/oildrumchunk01d.mdl", + "models/props_c17/oildrumchunk01e.mdl", +} + +function ENT:SpawnGibs() + local pos = self:LocalToWorld( self:OBBCenter() ) + local ang = self:GetAngles() + + self.GibModels = istable( self.GibModels ) and self.GibModels or gibs + + for _, v in pairs( self.GibModels ) do + local ent = ents.Create( "prop_physics" ) + + if not IsValid( ent ) then continue end + + ent:SetPos( pos ) + ent:SetAngles( ang ) + ent:SetModel( v ) + ent:Spawn() + ent:Activate() + ent:SetRenderMode( RENDERMODE_TRANSALPHA ) + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + local PhysObj = ent:GetPhysicsObject() + + if IsValid( PhysObj ) then + PhysObj:SetVelocityInstantaneous( Vector( math.Rand(-1,1), math.Rand(-1,1), 1.5 ):GetNormalized() * math.random(250,400) ) + PhysObj:AddAngleVelocity( VectorRand() * 500 ) + PhysObj:EnableDrag( false ) + end + + timer.Simple( 4.5, function() + if not IsValid( ent ) then return end + + ent:SetRenderFX( kRenderFxFadeFast ) + end) + + timer.Simple( 5, function() + if not IsValid( ent ) then return end + + ent:Remove() + end) + end +end + +function ENT:OnStartFireTrail( PhysObj, ExplodeTime ) +end + +function ENT:OnExploded() + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetVelocity( self:GetVelocity() + Vector(math.random(-5,5),math.random(-5,5),math.random(150,250)) ) +end + +function ENT:OnDriverChanged( Old, New, VehicleIsActive ) + self:OnPassengerChanged( Old, New, 1 ) + + if VehicleIsActive then + + self:OnDriverEnterVehicle( New ) + + return + end + + self:OnDriverExitVehicle( Old ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/shared.lua new file mode 100644 index 0000000..23f4171 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/shared.lua @@ -0,0 +1,157 @@ + +ENT.Base = "lvs_base_wheeldrive" +DEFINE_BASECLASS( "lvs_base_wheeldrive" ) + +ENT.PrintName = "[LVS] Wheeldrive Trailer" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.DeleteOnExplode = true + +ENT.lvsAllowEngineTool = false +ENT.lvsShowInSpawner = false + +ENT.AllowSuperCharger = false +ENT.AllowTurbo = false + +ENT.PhysicsDampingSpeed = 1000 +ENT.PhysicsDampingForward = true +ENT.PhysicsDampingReverse = true + +function ENT:AddDT( type, name, data ) + if name == "EngineActive" then return end + + BaseClass.AddDT( self, type, name, data ) +end + +function ENT:SetupDataTables() + self:CreateBaseDT() + + self:AddDT( "Entity", "InputTarget" ) + self:AddDT( "Entity", "LightsHandler" ) + self:AddDT( "Vector", "AIAimVector" ) + + self:TurretSystemDT() + self:TrackSystemDT() +end + +function ENT:GetVehicleType() + return "LBaseTrailer" +end + +function ENT:StartCommand( ply, cmd ) +end + +function ENT:SetNWHandBrake() +end + +function ENT:GetGear() + return -1 +end + +function ENT:GetWheelVelocity() + return self:GetVelocity():Length() +end + +function ENT:GetRacingTires() + return false +end + +function ENT:IsManualTransmission() + return false +end + +function ENT:SetThrottle() +end + +function ENT:SetReverse() +end + +function ENT:SetEngineActive() +end + +function ENT:GetEngineActive() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetEngineActive then return false end + + return InputTarget:GetEngineActive() +end + +function ENT:GetEngine() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetEngine then return NULL end + + return InputTarget:GetEngine() +end + +function ENT:GetFuelTank() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetFuelTank then return NULL end + + return InputTarget:GetFuelTank() +end + +function ENT:GetThrottle() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetThrottle then return 0 end + + return InputTarget:GetThrottle() +end + +function ENT:GetSteer() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetSteer then return 0 end + + return InputTarget:GetSteer() +end + +function ENT:GetNWMaxSteer() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetNWMaxSteer then return 1 end + + return InputTarget:GetNWMaxSteer() +end + +function ENT:GetTurnMode() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetTurnMode then return 0 end + + return InputTarget:GetTurnMode() +end + +function ENT:GetReverse() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetReverse then return false end + + return InputTarget:GetReverse() +end + +function ENT:GetNWHandBrake() + local ApplyBrakes = not IsValid( self:GetInputTarget() ) + + if ApplyBrakes and self._HandBrakeForceDisabled then + return false + end + + return ApplyBrakes +end + +function ENT:GetBrake() + local InputTarget = self:GetInputTarget() + + if not IsValid( InputTarget ) or not InputTarget.GetBrake then return 0 end + + return InputTarget:GetBrake() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/sv_ai.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/sv_ai.lua new file mode 100644 index 0000000..c30bee7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/sv_ai.lua @@ -0,0 +1,65 @@ + +ENT.AISearchCone = 180 + +function ENT:OnCreateAI() +end + +function ENT:OnRemoveAI() +end + +function ENT:RunAI() + local Target = self:AIGetTarget( self.AISearchCone ) + + local StartPos = self:LocalToWorld( self:OBBCenter() ) + + local TargetPos = StartPos + self:GetForward() * 150 + + if IsValid( Target ) then + TargetPos = Target:GetPos() + end + + self._AIFireInput = false + + if IsValid( self:GetHardLockTarget() ) then + Target = self:GetHardLockTarget() + + TargetPos = Target:LocalToWorld( Target:OBBCenter() ) + + self._AIFireInput = true + else + if IsValid( Target ) then + local PhysObj = Target:GetPhysicsObject() + if IsValid( PhysObj ) then + TargetPos = Target:LocalToWorld( PhysObj:GetMassCenter() ) + else + TargetPos = Target:LocalToWorld( Target:OBBCenter() ) + end + + if self:AIHasWeapon( 1 ) or self:AIHasWeapon( 2 ) then + self._AIFireInput = true + end + + local CurHeat = self:GetNWHeat() + local CurWeapon = self:GetSelectedWeapon() + + if CurWeapon > 2 then + self:AISelectWeapon( 1 ) + else + if Target.LVS and CurHeat < 0.9 then + if CurWeapon == 1 and self:AIHasWeapon( 2 ) then + self:AISelectWeapon( 2 ) + + elseif CurWeapon == 2 then + self:AISelectWeapon( 1 ) + end + else + if CurHeat == 0 and math.cos( CurTime() ) > 0 then + self:AISelectWeapon( 1 ) + end + end + end + end + end + + self:SetAIAimVector( (TargetPos - StartPos):GetNormalized() ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/sv_kill_functions.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/sv_kill_functions.lua new file mode 100644 index 0000000..8d19722 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_base_wheeldrive_trailer/sv_kill_functions.lua @@ -0,0 +1,29 @@ +-- kill engine system + +function ENT:IsEngineStartAllowed() + return false +end + +function ENT:OnEngineActiveChanged( Active ) +end + +function ENT:StartEngine() +end + +function ENT:StopEngine() +end + +function ENT:ToggleEngine() +end + +function ENT:HandleStart() +end + +function ENT:AddEngine() +end + +function ENT:AddTurboCharger() +end + +function ENT:AddSuperCharger() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/cl_init.lua new file mode 100644 index 0000000..86b7f96 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/cl_init.lua @@ -0,0 +1,103 @@ +include("shared.lua") + +local lerp_to_ragdoll = 0 +local freezeangles = Angle(0,0,0) + +function ENT:StartCommand( ply, cmd ) + local LocalSpeed = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + + ply:SetAbsVelocity( LocalSpeed ) +end + +function ENT:CalcViewOverride( ply, pos, angles, fov, pod ) + if ply:GetNWBool( "lvs_camera_follow_ragdoll", false ) then + local ragdoll = ply:GetRagdollEntity() + + if IsValid( ragdoll ) then + lerp_to_ragdoll = math.min( lerp_to_ragdoll + FrameTime() * 2, 1 ) + + local eyeang = ply:EyeAngles() - Angle(0,90,0) + + local newpos = LerpVector( lerp_to_ragdoll, pos, ragdoll:GetPos() ) + local newang = LerpAngle( lerp_to_ragdoll, freezeangles, freezeangles + eyeang ) + + return newpos, newang, fov + end + end + + lerp_to_ragdoll = 0 + freezeangles = angles + + return pos, angles, fov +end + +function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod ) + local roll = angles.r + + angles.r = math.max( math.abs( roll ) - 30, 0 ) * (angles.r > 0 and 1.5 or -1.5) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + local roll = angles.r + + angles.r = math.max( math.abs( roll ) - 30, 0 ) * (angles.r > 0 and 1.5 or -1.5) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +local angle_zero = Angle(0,0,0) + +function ENT:GetPlayerBoneManipulation( ply, PodID ) + if PodID ~= 1 then return self.PlayerBoneManipulate[ PodID ] or {} end + + local TargetValue = self:ShouldPutFootDown() and 1 or 0 + + local Rate = math.min( FrameTime() * 4, 1 ) + + ply._smlvsBikerFoot = ply._smlvsBikerFoot and (ply._smlvsBikerFoot + (TargetValue - ply._smlvsBikerFoot) * Rate) or 0 + + local CurValue = ply._smlvsBikerFoot ^ 2 + + local Pose = table.Copy( self.PlayerBoneManipulate[ PodID ] or {} ) + + local BoneManip = self:GetEngineActive() and self.DriverBoneManipulateIdle or self.DriverBoneManipulateParked + + for bone, EndAngle in pairs( BoneManip or {} ) do + local StartAngle = Pose[ bone ] or angle_zero + + Pose[ bone ] = LerpAngle( CurValue, StartAngle, EndAngle ) + end + + if self.DriverBoneManipulateKickStart and ply._KickStartValue then + ply._KickStartValue = math.max( ply._KickStartValue - Rate, 0 ) + + local Start = self.DriverBoneManipulateKickStart.Start + local End = self.DriverBoneManipulateKickStart.End + + for bone, EndAngle in pairs( End ) do + local StartAngle = Start[ bone ] or angle_zero + + Pose[ bone ] = LerpAngle( ply._KickStartValue, StartAngle, EndAngle ) + end + + if ply._KickStartValue == 0 then ply._KickStartValue = nil end + end + + return Pose or {} +end + +function ENT:GetKickStarter() + local Driver = self:GetDriver() + + if not IsValid( Driver ) then return 0 end + + return math.sin( (Driver._KickStartValue or 0) * math.pi ) ^ 2 +end + +net.Receive( "lvs_kickstart_network" , function( len ) + local ply = net.ReadEntity() + + if not IsValid( ply ) then return end + + ply._KickStartValue = 1 +end ) diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/init.lua new file mode 100644 index 0000000..3dd55d7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/init.lua @@ -0,0 +1,222 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +-- code relies on this forward angle... +ENT.ForcedForwardAngle = Angle(0,0,0) + +ENT.TippingForceMul = 1 + +ENT.LeanAngleIdle = -10 +ENT.LeanAnglePark = -10 + +function ENT:ApproachTargetAngle( TargetAngle ) + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return end + + local ang = self:GetAngles() + + local Forward = ang:Right() + local View = pod:WorldToLocalAngles( TargetAngle ):Forward() + + local Reversed = false + if self:AngleBetweenNormal( View, ang:Forward() ) < 90 then + Reversed = self:GetReverse() + end + + local LocalAngSteer = (self:AngleBetweenNormal( View, ang:Right() ) - 90) / self.MouseSteerAngle + + local Steer = (math.min( math.abs( LocalAngSteer ), 1 ) ^ self.MouseSteerExponent * self:Sign( LocalAngSteer )) + + self:SteerTo( Reversed and Steer or -Steer, self:GetMaxSteerAngle() ) +end + +function ENT:PhysicsSimulateOverride( ForceAngle, phys, deltatime, simulate ) + local EntTable = self:GetTable() + + if EntTable._IsDismounted then + + local Pod = self:GetDriverSeat() + + if IsValid( Pod ) then + local z = math.max( self:GetUp().z, 0 ) + + local Gravity = self:GetWorldUp() * self:GetWorldGravity() * phys:GetMass() * deltatime + phys:ApplyForceCenter( Gravity * 1.5 * EntTable.TippingForceMul * z ) + phys:ApplyForceOffset( -Gravity * 3 * EntTable.TippingForceMul, Pod:GetPos() ) + end + + return vector_origin, vector_origin, SIM_NOTHING + end + + local Steer = self:GetSteer() + + local VelL = self:WorldToLocal( self:GetPos() + phys:GetVelocity() ) + + local ShouldIdle = self:ShouldPutFootDown() + + if ShouldIdle then + Steer = self:GetEngineActive() and EntTable.LeanAngleIdle or EntTable.LeanAnglePark + VelL.x = EntTable.MaxVelocity + else + local SpeedMul = math.Clamp( 1 - VelL.x / EntTable.MaxVelocity, 0, 1 ) ^ 2 + + ForceAngle.y = (math.Clamp( VelL.x * self:GetBrake() * EntTable.PhysicsRollMul, -EntTable.WheelBrakeForce, EntTable.WheelBrakeForce ) - self:GetThrottle() * self:GetEngineTorque() * 0.1 * SpeedMul) * EntTable.PhysicsPitchInvertForceMul + end + + local Mul = (self:GetUp().z > 0.5 and 1 or 0) * 50 * (math.min( math.abs( VelL.x ) / EntTable.PhysicsWheelGyroSpeed, 1 ) ^ 2) * EntTable.PhysicsWheelGyroMul + local Diff = (Steer - self:GetAngles().r) + + local ForceLinear = Vector(0,0,0) + ForceAngle.x = (Diff * 2.5 * EntTable.PhysicsRollMul - phys:GetAngleVelocity().x * EntTable.PhysicsDampingRollMul) * Mul + + if ShouldIdle and math.abs( Diff ) > 1 then + simulate = SIM_GLOBAL_ACCELERATION + end + + if self:GetRacingTires() and self:WheelsOnGround() then + local WheelSideForce = EntTable.WheelSideForce * EntTable.ForceLinearMultiplier + for id, wheel in pairs( self:GetWheels() ) do + if wheel:IsHandbrakeActive() then continue end + + local AxleAng = wheel:GetDirectionAngle() + + local Forward = AxleAng:Forward() + local Right = AxleAng:Right() + local Up = AxleAng:Up() + + local wheelPos = wheel:GetPos() + local wheelVel = phys:GetVelocityAtPoint( wheelPos ) + local wheelRadius = wheel:GetRadius() + + local ForwardVel = self:VectorSplitNormal( Forward, wheelVel ) + + Force = -Right * self:VectorSplitNormal( Right, wheelVel ) * WheelSideForce + local wSideForce, wAngSideForce = phys:CalculateVelocityOffset( Force, wheelPos ) + + ForceAngle:Add( wAngSideForce ) + ForceLinear:Add( wSideForce ) + end + end + + return ForceAngle, vector_origin, simulate +end + +function ENT:CalcDismount( data, physobj ) + if self._IsDismounted then return end + + self._IsDismounted = true + + self:PhysicsCollide( data, physobj ) + + if self:GetEngineActive() then + self:StopEngine() + end + + local LocalSpeed = self:WorldToLocal( self:GetPos() + data.OurOldVelocity ) + + for _, ply in pairs( self:GetEveryone() ) do + if ply:GetNoDraw() then continue end + + local EnablePartDrawing = false + + if pac then + local Pod = ply:GetVehicle() + + if IsValid( Pod ) and not Pod.HidePlayer then + EnablePartDrawing = true + pac.TogglePartDrawing( ply, 0 ) + end + end + + ply:SetNoDraw( true ) + ply:SetAbsVelocity( LocalSpeed ) + ply:CreateRagdoll() + ply:SetNWBool( "lvs_camera_follow_ragdoll", true ) + ply:lvsSetInputDisabled( true ) + + timer.Simple( math.Rand(3.5,4.5), function() + if not IsValid( ply ) then return end + + if EnablePartDrawing then + pac.TogglePartDrawing( ply, 1 ) + end + + ply:SetNoDraw( false ) + ply:SetNWBool( "lvs_camera_follow_ragdoll", false) + ply:lvsSetInputDisabled( false ) + + local ragdoll = ply:GetRagdollEntity() + + if not IsValid( ragdoll ) then return end + + ragdoll:Remove() + end) + end + + timer.Simple(3, function() + if not IsValid( self ) then return end + + self._IsDismounted = nil + end) +end + +function ENT:OnWheelCollision( data, physobj ) + local Speed = math.abs(data.OurOldVelocity:Length() - data.OurNewVelocity:Length()) + + if Speed < 200 then return end + + local ent = physobj:GetEntity() + + local pos, ang = WorldToLocal( data.HitPos, angle_zero, ent:GetPos(), ent:GetDirectionAngle() ) + local radius = ent:GetRadius() - 2 + + if Speed > 300 then + if math.abs( pos.y ) > radius and self:GetUp().z < 0.5 then + self:CalcDismount( data, physobj ) + end + end + + if math.abs( pos.x ) < radius or pos.z < -1 then return end + + self:CalcDismount( data, physobj ) +end + +util.AddNetworkString( "lvs_kickstart_network" ) + +function ENT:ToggleEngine() + if self:GetEngineActive() then + self:StopEngine() + + self._KickStartAttemt = 0 + else + if self.KickStarter and not self:GetAI() then + local T = CurTime() + + if (self._KickStartTime or 0) > T then return end + + self:EmitSound( self.KickStarterSound, 70, 100, 0.5 ) + + if not self._KickStartAttemt or ((T - (self.KickStarterStart or 0)) > (self.KickStarterAttemptsInSeconds or 0)) then + self._KickStartAttemt = 0 + self.KickStarterStart = T + end + + net.Start( "lvs_kickstart_network" ) + net.WriteEntity( self:GetDriver() ) + net.Broadcast() + + self._KickStartAttemt = self._KickStartAttemt + 1 + self._KickStartTime = T + self.KickStarterMinDelay + + if self._KickStartAttemt >= math.random( self.KickStarterMinAttempts, self.KickStarterMaxAttempts ) then + self._KickStartAttemt = nil + + self:StartEngine() + end + else + self:StartEngine() + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/shared.lua new file mode 100644 index 0000000..f1fbd11 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_bike_wheeldrive/shared.lua @@ -0,0 +1,88 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "[LVS] Wheeldrive Bike" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MaxVelocity = 1250 +ENT.MaxVelocityReverse = 100 + +ENT.EngineCurve = 0.4 +ENT.EngineTorque = 250 + +ENT.TransGears = 4 +ENT.TransGearsReverse = 1 + +ENT.PhysicsMass = 250 +ENT.PhysicsWeightScale = 0.5 +ENT.PhysicsInertia = Vector(400,400,200) + +ENT.ForceAngleMultiplier = 0.5 + +ENT.PhysicsPitchInvertForceMul = 0.5 + +ENT.PhysicsDampingSpeed = 500 +ENT.PhysicsDampingForward = true +ENT.PhysicsDampingReverse = false + +ENT.PhysicsRollMul = 1 +ENT.PhysicsDampingRollMul = 1 +ENT.PhysicsWheelGyroMul = 1 +ENT.PhysicsWheelGyroSpeed = 400 + +ENT.WheelPhysicsMass = 250 +ENT.WheelPhysicsInertia = Vector(5,4,5) + +ENT.WheelSideForce = 800 +ENT.WheelDownForce = 1000 + +ENT.KickStarter = true +ENT.KickStarterSound = "lvs/vehicles/bmw_r75/moped_crank.wav" +ENT.KickStarterMinAttempts = 2 +ENT.KickStarterMaxAttempts = 4 +ENT.KickStarterAttemptsInSeconds = 5 +ENT.KickStarterMinDelay = 0.5 + +ENT.FastSteerAngleClamp = 15 + +ENT.WheelTickInterval = 0.06 +ENT.WheelTickIntervalBraking = 0.02 + +function ENT:ShouldPutFootDown() + return self:GetNWHandBrake() or self:GetVelocity():Length() < 20 +end + +function ENT:CalcMainActivity( ply ) + if ply ~= self:GetDriver() then return self:CalcMainActivityPassenger( ply ) end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + ply.CalcIdeal = ACT_STAND + ply.CalcSeqOverride = ply:LookupSequence( "drive_airboat" ) + + return ply.CalcIdeal, ply.CalcSeqOverride +end + +function ENT:GetWheelUp() + return self:GetUp() * math.Clamp( 1 + math.abs( self:GetSteer() / 10 ), 1, 1.5 ) +end + +function ENT:GetVehicleType() + return "bike" +end + +function ENT:GravGunPickupAllowed( ply ) + return false +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_bomb.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_bomb.lua new file mode 100644 index 0000000..2200ba0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_bomb.lua @@ -0,0 +1,348 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.ExplosionEffect = "lvs_explosion_bomb" + +ENT.lvsProjectile = true +ENT.VJ_ID_Danger = true + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Active" ) + self:NetworkVar( "Bool", 1, "MaskSolid" ) + + self:NetworkVar( "Vector", 0, "Speed" ) +end + +if SERVER then + util.AddNetworkString( "lvs_bomb_hud" ) + + function ENT:SetEntityFilter( filter ) + if not istable( filter ) then return end + + self._FilterEnts = {} + + for _, ent in pairs( filter ) do + self._FilterEnts[ ent ] = true + end + end + function ENT:GetEntityFilter() + return self._FilterEnts or {} + end + function ENT:SetDamage( num ) self._dmg = num end + function ENT:SetForce( num ) self._force = num end + function ENT:SetThrust( num ) self._thrust = num end + function ENT:SetRadius( num ) self._radius = num end + function ENT:SetAttacker( ent ) + self._attacker = ent + + if not IsValid( ent ) or not ent:IsPlayer() then return end + + net.Start( "lvs_bomb_hud", true ) + net.WriteEntity( self ) + net.Send( ent ) + end + + function ENT:GetAttacker() return self._attacker or NULL end + function ENT:GetDamage() return (self._dmg or 2000) end + function ENT:GetForce() return (self._force or 8000) end + function ENT:GetRadius() return (self._radius or 400) end + + function ENT:Initialize() + self:SetModel( "models/props_phx/ww2bomb.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Enable() + if self.IsEnabled then return end + + local Parent = self:GetParent() + + if IsValid( Parent ) then + self:SetOwner( Parent ) + self:SetParent( NULL ) + end + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self:PhysWake() + + timer.Simple(1, function() + if not IsValid( self ) then return end + + self:SetCollisionGroup( COLLISION_GROUP_NONE ) + end ) + + self.IsEnabled = true + + local pObj = self:GetPhysicsObject() + + if not IsValid( pObj ) then + self:Remove() + + print("LVS: missing model. Missile terminated.") + + return + end + + pObj:SetMass( 500 ) + pObj:EnableGravity( false ) + pObj:EnableMotion( true ) + pObj:EnableDrag( false ) + pObj:SetVelocityInstantaneous( self:GetSpeed() ) + + self:SetTrigger( true ) + + self:StartMotionController() + + self:PhysWake() + + self.SpawnTime = CurTime() + + self:SetActive( true ) + end + + function ENT:PhysicsSimulate( phys, deltatime ) + phys:Wake() + + local ForceLinear = physenv.GetGravity() + + local Pos = self:GetPos() + local TargetPos = Pos + self:GetVelocity() + + local AngForce = -self:WorldToLocalAngles( (TargetPos - Pos):Angle() ) + + local ForceAngle = (Vector(AngForce.r,-AngForce.p,-AngForce.y) * 10 - phys:GetAngleVelocity() * 5 ) * 250 * deltatime + + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION + end + + function ENT:Think() + local T = CurTime() + + self:NextThink( T ) + + self:UpdateTrajectory() + + if not self.SpawnTime then return true end + + if (self.SpawnTime + 12) < T then + self:Detonate() + end + + return true + end + + function ENT:UpdateTrajectory() + local base = self:GetParent() + + if not IsValid( base ) then return end + + self:SetSpeed( base:GetVelocity() ) + end + + ENT.IgnoreCollisionGroup = { + [COLLISION_GROUP_NONE] = true, + [COLLISION_GROUP_WORLD] = true, + [COLLISION_GROUP_IN_VEHICLE] = true + } + + function ENT:StartTouch( entity ) + if entity == self:GetAttacker() then return end + + if istable( self._FilterEnts ) and self._FilterEnts[ entity ] then return end + + if entity.GetCollisionGroup and self.IgnoreCollisionGroup[ entity:GetCollisionGroup() ] then return end + + if entity.lvsProjectile then return end + + self:Detonate( entity ) + end + + function ENT:EndTouch( entity ) + end + + function ENT:Touch( entity ) + end + + function ENT:PhysicsCollide( data ) + if istable( self._FilterEnts ) and self._FilterEnts[ data.HitEntity ] then return end + + self:Detonate( data.HitEntity ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Detonate( target ) + if not self.IsEnabled or self.IsDetonated then return end + + self.IsDetonated = true + + local Pos = self:GetPos() + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + util.Effect( self.ExplosionEffect, effectdata, true, true ) + + local attacker = self:GetAttacker() + + LVS:BlastDamage( Pos, self:GetForward(), IsValid( attacker ) and attacker or game.GetWorld(), self, self:GetDamage(), DMG_BLAST, self:GetRadius(), self:GetForce() ) + + SafeRemoveEntityDelayed( self, FrameTime() ) + end + + return +end + +function ENT:Enable() + if self.IsEnabled then return end + + self.IsEnabled = true + + self.snd = CreateSound(self, "lvs/weapons/bomb_whistle_loop.wav") + self.snd:SetSoundLevel( 110 ) + self.snd:PlayEx(0,150) +end + +function ENT:CalcDoppler() + local Ent = LocalPlayer() + + local ViewEnt = Ent:GetViewEntity() + + if Ent:lvsGetVehicle() == self then + if ViewEnt == Ent then + Ent = self + else + Ent = ViewEnt + end + else + Ent = ViewEnt + end + + local sVel = self:GetVelocity() + local oVel = Ent:GetVelocity() + + local SubVel = oVel - sVel + local SubPos = self:GetPos() - Ent:GetPos() + + local DirPos = SubPos:GetNormalized() + local DirVel = SubVel:GetNormalized() + + local A = math.acos( math.Clamp( DirVel:Dot( DirPos ) ,-1,1) ) + + return (1 + math.cos( A ) * SubVel:Length() / 13503.9) +end + +function ENT:Think() + if self.snd then + self.snd:ChangePitch( 100 * self:CalcDoppler(), 1 ) + self.snd:ChangeVolume(math.Clamp(-(self:GetVelocity().z + 1000) / 3000,0,1), 2) + end + + if self.IsEnabled then return end + + if self:GetActive() then + self:Enable() + end +end + +function ENT:Draw() + local T = CurTime() + + if not self:GetActive() then + self._PreventDrawTime = T + 0.1 + return + end + + if (self._PreventDrawTime or 0) > T then return end + + self:DrawModel() +end + +function ENT:SoundStop() + if self.snd then + self.snd:Stop() + end +end + +function ENT:OnRemove() + self:SoundStop() +end + +local color_red = Color(255,0,0,255) +local color_red_blocked = Color(100,0,0,255) +local HudTargets = {} +hook.Add( "HUDPaint", "!!!!lvs_bomb_hud", function() + for ID, _ in pairs( HudTargets ) do + local Missile = Entity( ID ) + + if not IsValid( Missile ) or Missile:GetActive() then + HudTargets[ ID ] = nil + + continue + end + + local Grav = physenv.GetGravity() + local FT = 0.05 + local MissilePos = Missile:GetPos() + local Pos = MissilePos + local Vel = Missile:GetSpeed() + + local LastColor = color_red + local Mask = Missile.GetMaskSolid and (Missile:GetMaskSolid() and MASK_SOLID or MASK_SOLID_BRUSHONLY) or MASK_SOLID_BRUSHONLY + + cam.Start3D() + local Iteration = 0 + while Iteration < 1000 do + Iteration = Iteration + 1 + + Vel = Vel + Grav * FT + + local StartPos = Pos + local EndPos = Pos + Vel * FT + + local trace = util.TraceLine( { + start = StartPos, + endpos = EndPos, + mask = Mask, + } ) + + local traceVisible = util.TraceLine( { + start = MissilePos, + endpos = StartPos, + mask = Mask, + } ) + + LastColor = traceVisible.Hit and color_red_blocked or color_red + + render.DrawLine( StartPos, EndPos, LastColor ) + + Pos = EndPos + + if trace.Hit then + break + end + end + cam.End3D() + + local TargetPos = Pos:ToScreen() + + if not TargetPos.visible then continue end + + surface.DrawCircle( TargetPos.x, TargetPos.y, 20, LastColor ) + end +end ) + +net.Receive( "lvs_bomb_hud", function( len ) + local ent = net.ReadEntity() + + if not IsValid( ent ) then return end + + HudTargets[ ent:EntIndex() ] = true +end ) diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_concussionmissile.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_concussionmissile.lua new file mode 100644 index 0000000..6d3c0dc --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_concussionmissile.lua @@ -0,0 +1,62 @@ +AddCSLuaFile() + +ENT.Base = "lvs_protontorpedo" + +ENT.Type = "anim" + +ENT.PrintName = "Concussion Missile" +ENT.Author = "Luna" +ENT.Information = "sprengt dir ein zweites arschloch" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = true + +ENT.ExplosionEffect = "lvs_concussion_explosion" +ENT.GlowColor = Color( 255, 40, 100, 255 ) + +if SERVER then + function ENT:GetDamage() return + (self._dmg or 400) + end + + function ENT:GetRadius() + return (self._radius or 150) + end + + return +end + +ENT.GlowMat = Material( "sprites/light_glow02_add" ) + +function ENT:Enable() + if self.IsEnabled then return end + + self.IsEnabled = true + + self.snd = CreateSound(self, "npc/combine_gunship/gunship_crashing1.wav") + self.snd:SetSoundLevel( 80 ) + self.snd:Play() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( self ) + util.Effect( "lvs_concussion_trail", effectdata ) +end + +function ENT:Draw() + if not self:GetActive() then return end + + self:DrawModel() + + render.SetMaterial( self.GlowMat ) + + local pos = self:GetPos() + local dir = self:GetForward() + + for i = 0, 30 do + local Size = ((30 - i) / 30) ^ 2 * 128 + + render.DrawSprite( pos - dir * i * 7, Size, Size, self.GlowColor ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_destruction.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_destruction.lua new file mode 100644 index 0000000..0e93c18 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_destruction.lua @@ -0,0 +1,111 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +local gibs = { + "models/gibs/manhack_gib01.mdl", + "models/gibs/manhack_gib02.mdl", + "models/gibs/manhack_gib03.mdl", + "models/gibs/manhack_gib04.mdl", + "models/props_c17/canisterchunk01a.mdl", + "models/props_c17/canisterchunk01d.mdl", + "models/props_c17/oildrumchunk01a.mdl", + "models/props_c17/oildrumchunk01b.mdl", + "models/props_c17/oildrumchunk01c.mdl", + "models/props_c17/oildrumchunk01d.mdl", + "models/props_c17/oildrumchunk01e.mdl", +} + +for _, modelName in ipairs( gibs ) do + util.PrecacheModel( modelName ) +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + + self.Vel = isvector( self.Vel ) and self.Vel or Vector(0,0,0) + + local fxPos = self:LocalToWorld( self:OBBCenter() ) + + local effectdata = EffectData() + effectdata:SetOrigin( fxPos ) + util.Effect( "lvs_explosion", effectdata, true, true ) + + self.GibModels = istable( self.GibModels ) and self.GibModels or gibs + + self.Gibs = {} + self.DieTime = CurTime() + 5 + + local Speed = self.Vel:Length() + + for _, v in pairs( self.GibModels ) do + local ent = ents.Create( "prop_physics" ) + + if not IsValid( ent ) then continue end + + table.insert( self.Gibs, ent ) + + ent:SetPos( self:GetPos() ) + ent:SetAngles( self:GetAngles() ) + ent:SetModel( v ) + ent:Spawn() + ent:Activate() + ent:SetRenderMode( RENDERMODE_TRANSALPHA ) + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + local PhysObj = ent:GetPhysicsObject() + if IsValid( PhysObj ) then + if Speed <= 250 then + local GibDir = Vector( math.Rand(-1,1), math.Rand(-1,1), 1.5 ):GetNormalized() + PhysObj:SetVelocityInstantaneous( GibDir * math.random(800,1300) ) + else + PhysObj:SetVelocityInstantaneous( VectorRand() * math.max(300,self.Vel:Length() / 3) + self.Vel ) + end + + PhysObj:AddAngleVelocity( VectorRand() * 500 ) + PhysObj:EnableDrag( false ) + + local effectdata = EffectData() + effectdata:SetOrigin( fxPos ) + effectdata:SetStart( PhysObj:GetMassCenter() ) + effectdata:SetEntity( ent ) + effectdata:SetScale( math.Rand(0.3,0.7) ) + effectdata:SetMagnitude( math.Rand(0.5,2.5) ) + util.Effect( "lvs_firetrail", effectdata ) + end + + timer.Simple( 4.5 + math.Rand(0,0.5), function() + if not IsValid( ent ) then return end + + ent:SetRenderMode( RENDERMODE_TRANSCOLOR ) + ent:SetRenderFX( kRenderFxFadeFast ) + end ) + end + end + + function ENT:Think() + if self.DieTime < CurTime() then + self:Remove() + end + + self:NextThink( CurTime() + 1 ) + + return true + end + + function ENT:OnRemove() + if istable( self.Gibs ) then + for _, v in pairs( self.Gibs ) do + if IsValid( v ) then + v:Remove() + end + end + end + end +else + function ENT:Draw() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_fakehover_soundemitter.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_fakehover_soundemitter.lua new file mode 100644 index 0000000..a2e08ca --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_fakehover_soundemitter.lua @@ -0,0 +1,71 @@ +AddCSLuaFile() + +ENT.Base = "lvs_starfighter_soundemitter" + +if SERVER then return end + +function ENT:HandleEngineSounds( vehicle ) + local ply = LocalPlayer() + local pod = ply:GetVehicle() + local Throttle = vehicle:GetThrottle() + local Doppler = vehicle:CalcDoppler( ply ) + + local DrivingMe = ply:lvsGetVehicle() == vehicle + + local VolumeSetNow = false + + local FirstPerson = false + if IsValid( pod ) then + local ThirdPerson = pod:GetThirdPersonMode() + + if ThirdPerson ~= self._lvsoldTP then + self._lvsoldTP = ThirdPerson + VolumeSetNow = DrivingMe + end + + FirstPerson = DrivingMe and not ThirdPerson + end + + if DrivingMe ~= self._lvsoldDrivingMe then + VolumeSetNow = true + self._lvsoldDrivingMe = DrivingMe + end + + for id, sound in pairs( self._ActiveSounds ) do + if not sound then continue end + + local data = self.EngineSounds[ id ] + + local Pitch = math.Clamp( data.Pitch + Throttle * data.PitchMul, data.PitchMin, data.PitchMax ) + local PitchMul = data.UseDoppler and Doppler or 1 + + local InActive = Throttle > data.FadeOut or Throttle < data.FadeIn + if data.FadeOut >= 1 and Throttle > 1 then + InActive = false + end + + local Volume = InActive and 0 or LVS.EngineVolume + + if data.VolumeMin and data.VolumeMax and not InActive then + Volume = math.max(Throttle - data.VolumeMin,0) / (1 - data.VolumeMin) * data.VolumeMax * LVS.EngineVolume + end + + if istable( sound ) then + sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + if sound.int then sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), 0.2 ) end + + local fadespeed = VolumeSetNow and 0 or data.FadeSpeed + + if FirstPerson then + sound.ext:ChangeVolume( 0, 0 ) + if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end + else + sound.ext:ChangeVolume( Volume, fadespeed ) + if sound.int then sound.int:ChangeVolume( 0, 0 ) end + end + else + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + sound:ChangeVolume( Volume, data.FadeSpeed ) + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_fakehover_wheel.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_fakehover_wheel.lua new file mode 100644 index 0000000..4fa9272 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_fakehover_wheel.lua @@ -0,0 +1,122 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._lvsNoPhysgunInteraction = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Float",0, "fxRadius" ) +end + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/props_vehicles/tire001c_car.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:DrawShadow( false ) + + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + + -- this is so vj npcs can still see us + self:AddEFlags( EFL_DONTBLOCKLOS ) + + self:SetCollisionGroup( COLLISION_GROUP_PASSABLE_DOOR ) + end + + function ENT:Define( data ) + self:SetfxRadius( data.radius ) + self:PhysicsInitSphere( data.radius, "gmod_silent" ) + + local VectorNull = Vector(0,0,0) + + self:SetCollisionBounds( VectorNull, VectorNull ) + + local PhysObj = self:GetPhysicsObject() + + if IsValid( PhysObj ) then + PhysObj:EnableDrag( false ) + PhysObj:EnableMotion( false ) + PhysObj:SetMass( data.mass ) + PhysObj:SetBuoyancyRatio( data.buoyancyratio ) + end + end + + function ENT:SetPhysics( enable ) + if enable then + if self.PhysicsEnabled then return end + + self:GetPhysicsObject():SetMaterial("jeeptire") + self.PhysicsEnabled = true + else + if self.PhysicsEnabled == false then return end + + self:GetPhysicsObject():SetMaterial("friction_00") + self.PhysicsEnabled = false + end + end + + function ENT:CheckPhysics() + local base = self:GetBase() + + if not IsValid( base ) then return end + + if not base:GetEngineActive() then + self:SetPhysics( true ) + + self:NextThink( CurTime() + 0.25 ) + + return + end + + self:NextThink( CurTime() + 0.1 ) + + local Ang = base:GetAngles() + local steer = math.abs( base:WorldToLocalAngles( Angle(Ang.p,base:GetSteerTo(),Ang.r) ).y ) + local move = base:GetMove() + local speed = base:GetVelocity():LengthSqr() + + local enable = (math.abs( move.x ) + math.abs( move.y )) < 0.001 and steer < 3 and speed < 600 + + self:SetPhysics( enable ) + end + + function ENT:Think() + self:CheckPhysics() + + return true + end + + function ENT:OnTakeDamage( dmginfo ) + if dmginfo:IsDamageType( DMG_BLAST ) then return end + + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:TakeDamageInfo( dmginfo ) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_NEVER + end + + return +end + +function ENT:Initialize() +end + +function ENT:Think() +end + +function ENT:Draw() +end + +function ENT:OnRemove() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_engine.lua new file mode 100644 index 0000000..441d54d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_engine.lua @@ -0,0 +1,319 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) +end + +if SERVER then + util.AddNetworkString( "lvsplane_exhautcompatibility" ) + + net.Receive( "lvsplane_exhautcompatibility", function( len, ply ) + local ent = net.ReadEntity() + + if not IsValid( ent ) or not ent.LVS or not ent.ExhaustPositions then return end + + net.Start( "lvsplane_exhautcompatibility") + net.WriteEntity( ent ) + net.WriteTable( ent.ExhaustPositions ) + net.Send( ply ) + end ) + + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 50, 5, Color( 0, 255, 255 ) ) + end + + function ENT:CheckWater( Base ) + if bit.band( util.PointContents( self:GetPos() ), CONTENTS_WATER ) ~= CONTENTS_WATER then + if self.CountWater then + self.CountWater = nil + end + + return + end + + if Base.WaterLevelAutoStop > 3 then return end + + self.CountWater = (self.CountWater or 0) + 1 + + if self.CountWater < 4 then return end + + Base:StopEngine() + end + + function ENT:Think() + + local Base = self:GetBase() + + if IsValid( Base ) and Base:GetEngineActive() then + self:CheckWater( Base ) + end + + self:NextThink( CurTime() + 1 ) + + return true + end + + function ENT:OnTakeDamage( dmginfo ) + end + + return +end + +ENT._oldEnActive = false +ENT._ActiveSounds = {} + +function ENT:Initialize() +end + +function ENT:StopSounds() + for id, sound in pairs( self._ActiveSounds ) do + if istable( sound ) then + for _, snd in pairs( sound ) do + if snd then + snd:Stop() + end + end + else + sound:Stop() + end + self._ActiveSounds[ id ] = nil + end +end + +function ENT:HandleEngineSounds( vehicle ) + local ply = LocalPlayer() + local pod = ply:GetVehicle() + local Throttle = vehicle:GetThrottle() - vehicle:GetThrustStrenght() * vehicle:GetThrottle() * 0.5 + local Doppler = vehicle:CalcDoppler( ply ) + + local DrivingMe = ply:lvsGetVehicle() == vehicle + + local VolumeSetNow = false + + local FirstPerson = false + if IsValid( pod ) then + local ThirdPerson = pod:GetThirdPersonMode() + + if ThirdPerson ~= self._lvsoldTP then + self._lvsoldTP = ThirdPerson + VolumeSetNow = DrivingMe + end + + FirstPerson = DrivingMe and not ThirdPerson + end + + if DrivingMe ~= self._lvsoldDrivingMe then + self._lvsoldDrivingMe = DrivingMe + + self:StopSounds() + + self._oldEnActive = nil + + return + end + + local FT = RealFrameTime() + + self._smTHR = self._smTHR and self._smTHR + (Throttle - self._smTHR) * FT or 0 + + local HasActiveSoundEmitters = false + + if DrivingMe then + HasActiveSoundEmitters = vehicle:HasActiveSoundEmitters() + end + + for id, sound in pairs( self._ActiveSounds ) do + if not sound then continue end + + local data = self.EngineSounds[ id ] + + local Pitch = math.Clamp( data.Pitch + self._smTHR * data.PitchMul, data.PitchMin, data.PitchMax ) + local PitchMul = data.UseDoppler and Doppler or 1 + + local InActive = self._smTHR > data.FadeOut or self._smTHR < data.FadeIn + if data.FadeOut >= 1 and self._smTHR > 1 then + InActive = false + end + + local Volume = InActive and 0 or LVS.EngineVolume + + if data.VolumeMin and data.VolumeMax and not InActive then + Volume = math.max(self._smTHR - data.VolumeMin,0) / (1 - data.VolumeMin) * data.VolumeMax * LVS.EngineVolume + end + + if istable( sound ) then + sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + if sound.int then sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), 0.2 ) end + + local fadespeed = VolumeSetNow and 0 or data.FadeSpeed + + if FirstPerson then + sound.ext:ChangeVolume( 0, 0 ) + + if HasActiveSoundEmitters then + Volume = Volume * 0.25 + fadespeed = fadespeed * 0.5 + end + + if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end + else + if HasActiveSoundEmitters then + Volume = Volume * 0.75 + fadespeed = fadespeed * 0.5 + end + + sound.ext:ChangeVolume( Volume, fadespeed ) + + if sound.int then sound.int:ChangeVolume( 0, 0 ) end + end + else + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + sound:ChangeVolume( Volume, data.FadeSpeed ) + end + end +end + +function ENT:OnEngineActiveChanged( Active ) + if not Active then self:StopSounds() return end + + local ply = LocalPlayer() + local ViewPos = ply:GetViewEntity():GetPos() + local veh = ply:lvsGetVehicle() + + local Base = self:GetBase() + + local DrivingMe = veh == Base or ((self:GetPos() - ViewPos):LengthSqr() < 600000 and not Base:GetAI()) + + for id, data in pairs( self.EngineSounds ) do + if not isstring( data.sound ) then continue end + + self.EngineSounds[ id ].Pitch = data.Pitch or 80 + self.EngineSounds[ id ].PitchMin = data.PitchMin or 0 + self.EngineSounds[ id ].PitchMax = data.PitchMax or 255 + self.EngineSounds[ id ].PitchMul = data.PitchMul or 100 + self.EngineSounds[ id ].UseDoppler = data.UseDoppler ~= false + self.EngineSounds[ id ].FadeIn = data.FadeIn or 0 + self.EngineSounds[ id ].FadeOut = data.FadeOut or 1 + self.EngineSounds[ id ].FadeSpeed = data.FadeSpeed or 1.5 + self.EngineSounds[ id ].SoundLevel = data.SoundLevel or 85 + + if data.sound_int and data.sound_int ~= data.sound and DrivingMe then + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + if data.sound_int == "" then + self._ActiveSounds[ id ] = { + ext = sound, + int = false, + } + else + local sound_interior = CreateSound( self, data.sound_int ) + sound_interior:SetSoundLevel( data.SoundLevel ) + sound_interior:PlayEx(0,100) + + self._ActiveSounds[ id ] = { + ext = sound, + int = sound_interior, + } + end + else + if DrivingMe or data.SoundLevel >= 90 then + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + self._ActiveSounds[ id ] = sound + end + end + end +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + self:DamageFX( vehicle ) + + if not self.EngineSounds then + self.EngineSounds = vehicle.EngineSounds + + return + end + + local EngineActive = vehicle:GetEngineActive() + + if self._oldEnActive ~= EngineActive then + self._oldEnActive = EngineActive + self:OnEngineActiveChanged( EngineActive ) + end + + if EngineActive then + self:HandleEngineSounds( vehicle ) + else + self._smTHR = 0 + end +end + +function ENT:OnRemove() + self:StopSounds() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) or not vehicle:GetEngineActive() then return end + + if not istable( vehicle.ExhaustPositions ) then + vehicle.ExhaustPositions = {} + + net.Start("lvsplane_exhautcompatibility") + net.WriteEntity( vehicle ) + net.SendToServer() + end + + if #vehicle.ExhaustPositions == 0 then return end + + vehicle:DoExhaustFX() +end + +function ENT:DamageFX( vehicle ) + local T = CurTime() + local HP = vehicle:GetHP() + local MaxHP = vehicle:GetMaxHP() + + if HP <= 0 or HP > MaxHP * 0.5 or (self.nextDFX or 0) > T then return end + + self.nextDFX = T + 0.05 + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( vehicle ) + util.Effect( "lvs_engine_blacksmoke", effectdata ) +end + +net.Receive( "lvsplane_exhautcompatibility", function( len ) + local ent = net.ReadEntity() + + if not IsValid( ent ) then return end + + ent.ExhaustPositions = net.ReadTable() +end ) + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_rotor.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_rotor.lua new file mode 100644 index 0000000..4f5b950 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_rotor.lua @@ -0,0 +1,140 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "String", 1, "Sound" ) + self:NetworkVar( "String", 2, "SoundStrain" ) + + if SERVER then + self:SetSound("lvs/vehicles/generic/propeller.wav") + self:SetSoundStrain("lvs/vehicles/generic/propeller_strain.wav") + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 50, 5, Color( 255, 0, 255 ) ) + end + + function ENT:Think() + return false + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + return +end + +ENT._oldActive = false + +function ENT:Initialize() +end + +function ENT:StopSounds() + if self._RotorSound1 then + self._RotorSound1:Stop() + self._RotorSound1 = nil + end + if self._RotorSound2 then + self._RotorSound2:Stop() + self._RotorSound2 = nil + end +end + +function ENT:OnActiveChanged( Active ) + if not Active then self:StopSounds() return end + + self:StopSounds() + + self._RotorSound1 = CreateSound( self, self:GetSound() ) + self._RotorSound1:SetSoundLevel( 70 ) + self._RotorSound1:PlayEx(0,100) + + self._RotorSound2 = CreateSound( self, self:GetSoundStrain() ) + self._RotorSound2:SetSoundLevel( 140 ) + self._RotorSound2:PlayEx(0,100) +end + +function ENT:HandleSounds( vehicle, rpm, throttle ) + + local rotor_load = vehicle:GetThrustStrenght() + + local mul = math.max( rpm - 470, 0 ) / 330 + + local volume = mul * 0.6 + rotor_load * 0.4 * throttle + + local pitch = 80 + mul * 30 - rotor_load * 20 * throttle + + local pitch2 = 100 - rotor_load * 50 * throttle + local volume2 = (math.max(-rotor_load,0) * mul * throttle ^ 2) * (1 - math.min( math.max( pitch2 - 100, 0 ) / 80, 1 ) ) + + local fadespeed = 2 + + local ply = LocalPlayer() + if IsValid( ply ) and ply:lvsGetVehicle() == vehicle then + local pod = ply:GetVehicle() + if not pod:GetThirdPersonMode() then + if vehicle:HasActiveSoundEmitters() then + volume = volume * 0.25 + volume2 = volume2 * 0.25 + fadespeed = 0.5 + end + end + end + + if self._RotorSound1 then + self._RotorSound1:ChangeVolume( volume, fadespeed ) + self._RotorSound1:ChangePitch( pitch, 0.5 ) + end + + if self._RotorSound2 then + self._RotorSound2:ChangeVolume( volume2, 0.5 ) + self._RotorSound2:ChangePitch( math.min( pitch2, 160), 0.1 ) + end +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + local Active = vehicle:GetEngineActive() and LocalPlayer():lvsGetVehicle() == vehicle + + if self._oldActive ~= Active then + self._oldActive = Active + self:OnActiveChanged( Active ) + end + + local Throttle = vehicle:GetThrottle() + + local TargetRPM = vehicle:GetEngineActive() and (300 + Throttle * 500) or 0 + + vehicle.RotorRPM = vehicle.RotorRPM and vehicle.RotorRPM + (TargetRPM - vehicle.RotorRPM) * RealFrameTime() or 0 + + if Active then + self:HandleSounds( vehicle, vehicle.RotorRPM, Throttle ) + end +end + +function ENT:OnRemove() + self:StopSounds() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_thruster.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_thruster.lua new file mode 100644 index 0000000..3954bcf --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_thruster.lua @@ -0,0 +1,100 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "String", 1, "Sound" ) + + if SERVER then + self:SetSound("^lvs/vehicles/generic/afterburner.wav") + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 50, 5, Color( 255, 0, 255 ) ) + end + + function ENT:Think() + return false + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + return +end + +ENT._oldActive = false + +function ENT:Initialize() +end + +function ENT:StopSounds() + if not self._ThrusterSound then return end + + self._ThrusterSound:Stop() + self._ThrusterSound = nil +end + +function ENT:OnActiveChanged( Active ) + if not Active then self:StopSounds() return end + + self:StopSounds() + + self._ThrusterSound = CreateSound( self, self:GetSound() ) + self._ThrusterSound:SetSoundLevel( 90 ) + self._ThrusterSound:PlayEx(0,100) +end + +function ENT:HandleSounds( vehicle, throttle ) + + local thrust = vehicle:GetThrustStrenght() + + if not self._ThrusterSound then return end + + local volume = throttle * 0.5 + math.Clamp( thrust * 0.5, 0, 1 ) + + self._ThrusterSound:ChangeVolume( volume, 0.5 ) + self._ThrusterSound:ChangePitch( 100 + thrust * 20, 0.5 ) +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + local Active = vehicle:GetEngineActive() + + if self._oldActive ~= Active then + self._oldActive = Active + self:OnActiveChanged( Active ) + end + + local Throttle = vehicle:GetThrottle() + + if Active then + self:HandleSounds( vehicle, Throttle ) + end +end + +function ENT:OnRemove() + self:StopSounds() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_wheel.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_wheel.lua new file mode 100644 index 0000000..850b63b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_fighterplane_wheel.lua @@ -0,0 +1,119 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true +ENT._lvsNoPhysgunInteraction = true + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/props_vehicles/tire001c_car.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:DrawShadow( false ) + + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + + self:SetCollisionGroup( COLLISION_GROUP_PASSABLE_DOOR ) + end + + function ENT:SetBrakes( active ) + if not self._CanUseBrakes then + active = false + end + + if active ~= self._BrakesActive then + self._BrakesActive = active + + timer.Simple(0, function() + if not IsValid( self ) then return end + + if active then + self:StartMotionController() + else + self:StopMotionController() + end + end) + end + end + + function ENT:SetBrakeForce( force ) + self._BrakeForce = force + end + + function ENT:GetBrakeForce() + return (self._BrakeForce or 50) + end + + function ENT:Define( data ) + local bbox = Vector(data.radius,data.radius,data.radius) + + self:PhysicsInitSphere( data.radius, data.physmat ) + self:SetCollisionBounds( -bbox, bbox ) + + local PhysObj = self:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:SetMass( data.mass ) + end + + self._CanUseBrakes = data.brake + end + + function ENT:PhysicsSimulate( phys, deltatime ) + local BrakeForce = Vector( -phys:GetAngleVelocity().x, 0, 0 ) * self:GetBrakeForce() + + return BrakeForce, Vector(0,0,0), SIM_LOCAL_ACCELERATION + end + + function ENT:SetBase( ent ) + self._baseEnt = ent + end + + function ENT:GetBase() + return self._baseEnt + end + + function ENT:Use( ply ) + end + + function ENT:Think() + return false + end + + function ENT:OnRemove() + end + + function ENT:PhysicsCollide( data, physobj ) + end + + function ENT:OnTakeDamage( dmginfo ) + if dmginfo:IsDamageType( DMG_BLAST ) then return end + + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:TakeDamageInfo( dmginfo ) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_NEVER + end + + return +end + +function ENT:Initialize() +end + +function ENT:Think() +end + +function ENT:Draw() +end + +function ENT:OnRemove() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_fire.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_fire.lua new file mode 100644 index 0000000..22c96fb --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_fire.lua @@ -0,0 +1,311 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Fire" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminOnly = false + +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:SetupDataTables() + self:NetworkVar( "Entity", 0, "Emitter" ) + self:NetworkVar( "Float", 0, "LifeTime" ) + self:NetworkVar( "Float", 1, "DieTime" ) + + if SERVER then + self:SetLifeTime( math.Rand(8,12) ) + end +end + +function ENT:GetSize() + return math.min( (self:GetDieTime() - CurTime()) / self:GetLifeTime() * 10, 1) ^ 2 +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( tr.HitNormal:Angle() + Angle(90,0,0) ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:SetDamage( num ) self._dmg = num end + function ENT:SetAttacker( ent ) self._attacker = ent end + + function ENT:GetAttacker() return self._attacker or NULL end + function ENT:GetDamage() return (self._dmg or 3) end + + function ENT:SendDamage( victim, pos ) + if not IsValid( victim ) then return end + + if victim:IsPlayer() and victim:InVehicle() and victim:GetCollisionGroup() ~= COLLISION_GROUP_PLAYER then return end + + local attacker = self:GetAttacker() + + local dmg = DamageInfo() + dmg:SetDamage( self:GetDamage() ) + dmg:SetAttacker( IsValid( attacker ) and attacker or game.GetWorld() ) + dmg:SetInflictor( self ) + dmg:SetDamageType( DMG_BURN + DMG_PREVENT_PHYSICS_FORCE ) + dmg:SetDamagePosition( pos or vector_origin ) + victim:TakeDamageInfo( dmg ) + end + + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + + self:SetDieTime( CurTime() + self:GetLifeTime() ) + + if self:WaterLevel() > 0 then self:Remove() end + end + + function ENT:Think() + local T = CurTime() + + self:NextThink( T ) + + if T > self:GetDieTime() then + self:Remove() + end + + local EntTable = self:GetTable() + + if (EntTable._NextDamage or 0) < T then + EntTable._NextDamage = T + 0.5 + + local Size = self:GetSize() + local startpos = self:LocalToWorld( Vector(0,0,1) ) + local endpos = self:LocalToWorld( Vector(0,0,Size * 120) ) + + local maxs = Vector(80,80,0) * Size + local mins = Vector(-80,-80,0) * Size + + local trace = util.TraceHull( { + start = startpos, + endpos = endpos, + maxs = maxs, + mins = mins, + filter = self, + } ) + + if not trace.Hit or not IsValid( trace.Entity ) then return true end + + self:SendDamage( trace.Entity, trace.HitPos, 1 ) + end + + return true + end + + function ENT:PhysicsCollide( data, physobj ) + end + + function ENT:OnRemove() + end + + return +end + +local GlowMat = Material( "sprites/light_glow02_add" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function ENT:Initialize() + self.snd = CreateSound(self, "ambient/fire/fire_small_loop"..math.random(1,2)..".wav") + self.snd:SetSoundLevel( 60 ) + self.snd:Play() +end + +function ENT:Draw( flags ) +end + +function ENT:DrawTranslucent( flags ) + local Size = self:GetSize() * 200 + + render.SetMaterial( GlowMat ) + render.DrawSprite( self:GetPos(), Size, Size, Color( 255, 150, 75, 255) ) +end + +function ENT:SoundStop() + if self.snd then + self.snd:Stop() + end +end + +function ENT:OnRemove() + self:SoundStop() + self:StopEmitter() + self:StopEmitter3D() +end + +function ENT:GetParticleEmitter( Pos ) + local EntTable = self:GetTable() + + local T = CurTime() + + if IsValid( EntTable.Emitter ) and (EntTable.EmitterTime or 0) > T then + return EntTable.Emitter + end + + self:StopEmitter() + + EntTable.Emitter = ParticleEmitter( Pos, false ) + EntTable.EmitterTime = T + 2 + + return EntTable.Emitter +end + +function ENT:EmitFire() + local Pos = self:GetPos() + local Dir = self:GetUp() + + local emitter = self:GetParticleEmitter( Pos ) + local emitter3D = self:GetParticleEmitter3D( Pos ) + + if not IsValid( emitter ) or not IsValid( emitter3D ) then return end + + local particle = emitter3D:Add( "effects/lvs_base/flamelet"..math.random(1,5), Pos ) + + local Size = self:GetSize() + + if particle then + particle:SetStartSize( 20 * Size ) + particle:SetEndSize( 60 * Size ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetRollDelta( math.Rand(-2,2) ) + particle:SetAngles( Dir:Angle() ) + end + + local fparticle = emitter:Add( "effects/lvs_base/fire", Pos ) + if fparticle then + fparticle:SetVelocity( (VectorRand() * 30 + Dir * 100) * Size ) + fparticle:SetDieTime( math.Rand(0.6,0.8) ) + fparticle:SetAirResistance( 0 ) + + fparticle:SetStartAlpha( 255 ) + fparticle:SetEndAlpha( 255 ) + + fparticle:SetStartSize( 40 * Size ) + fparticle:SetEndSize( 0 ) + + fparticle:SetRollDelta( math.Rand(-2,2) ) + fparticle:SetColor( 255,255,255 ) + fparticle:SetGravity( Vector( 0, 0, 100 ) * Size ) + fparticle:SetCollide( false ) + end + + local fparticle = emitter:Add( "effects/lvs_base/flamelet"..math.random(1,5), Pos ) + if fparticle then + fparticle:SetVelocity( VectorRand() * 25 * Size ) + fparticle:SetDieTime( math.Rand(0.4,0.8) ) + fparticle:SetStartAlpha( 150 ) + fparticle:SetEndAlpha( 0 ) + fparticle:SetStartSize( 0 ) + fparticle:SetEndSize( math.Rand(60,80) * Size ) + fparticle:SetColor( 255, 255, 255 ) + fparticle:SetGravity( Vector(0,0,100) * Size ) + fparticle:SetRollDelta( math.Rand(-2,2) ) + fparticle:SetAirResistance( 0 ) + end + + for i = 0, 6 do + local eparticle = emitter:Add( "effects/fire_embers"..math.random(1,2), Pos ) + + if not eparticle then continue end + + eparticle:SetVelocity( VectorRand() * 400 * Size ) + eparticle:SetDieTime( math.Rand(0.4,0.6) ) + eparticle:SetStartAlpha( 255 ) + eparticle:SetEndAlpha( 0 ) + eparticle:SetStartSize( 20 * Size ) + eparticle:SetEndSize( 0 ) + eparticle:SetColor( 255, 255, 255 ) + eparticle:SetGravity( Vector(0,0,600) * Size ) + eparticle:SetRollDelta( math.Rand(-8,8) ) + eparticle:SetAirResistance( 300 * Size ) + end + + if math.random(1,3) ~= 1 then return end + + local sparticle = emitter:Add( Materials[ math.random(1, #Materials ) ], Pos ) + if sparticle then + sparticle:SetVelocity( Dir * 400 * Size ) + sparticle:SetDieTime( math.Rand(2,4) ) + sparticle:SetAirResistance( 500 ) + sparticle:SetStartAlpha( 125 ) + sparticle:SetStartSize( 0 ) + sparticle:SetEndSize( 200 * Size ) + sparticle:SetRoll( math.Rand(-3,3) ) + sparticle:SetRollDelta( math.Rand(-1,1) ) + sparticle:SetColor( 0, 0, 0 ) + sparticle:SetGravity( Vector( 0, 0, 800 ) * Size ) + sparticle:SetCollide( false ) + end +end + +function ENT:Think() + self:EmitFire() + + self:SetNextClientThink( CurTime() + math.Rand(0.01,0.4) ) + + return true +end + +function ENT:GetParticleEmitter3D( Pos ) + local EntTable = self:GetTable() + + local T = CurTime() + + if IsValid( EntTable.Emitter3D ) and (EntTable.EmitterTime3D or 0) > T then + return EntTable.Emitter3D + end + + self:StopEmitter3D() + + EntTable.Emitter3D = ParticleEmitter( Pos, true ) + EntTable.EmitterTime3D = T + 2 + + return EntTable.Emitter3D +end + + +function ENT:StopEmitter() + if not IsValid( self.Emitter ) then return end + + self.Emitter:Finish() +end + +function ENT:StopEmitter3D() + if not IsValid( self.Emitter3D ) then return end + + self.Emitter3D:Finish() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_firestreamemitter.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_firestreamemitter.lua new file mode 100644 index 0000000..12c9e10 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_firestreamemitter.lua @@ -0,0 +1,370 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Flamethrower" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "FlameVelocity" ) + self:NetworkVar( "Float", 1, "FlameLifeTime" ) + self:NetworkVar( "Float", 2, "FlameSize" ) + + self:NetworkVar( "Bool", 0, "Active" ) + self:NetworkVar( "Float", 3, "ActiveTime" ) + + self:NetworkVar( "String", 0, "TargetAttachment" ) + self:NetworkVar( "Entity", 0, "Target" ) + + if SERVER then + self:SetFlameLifeTime( 1.5 ) + self:SetFlameVelocity( 1000 ) + self:SetFlameSize( 80 ) + end +end + +function ENT:GetTargetVelocity() + local Target = self:GetTarget() + + if not IsValid( Target ) then return vector_origin end + + return Target:GetVelocity() +end + +function ENT:GetPosition() + local Pos = self:GetPos() + local Dir = self:GetForward() + + local Target = self:GetTarget() + local Attachment = self:GetTargetAttachment() + + if IsValid( Target ) and Attachment ~= "" then + local ID = Target:LookupAttachment( Attachment ) + local Muzzle = Target:GetAttachment( ID ) + Pos = Muzzle.Pos + Dir = Muzzle.Ang:Forward() + end + + return Pos, Dir +end + +local Grav = Vector(0,0,-600) +local Res = 0.05 +function ENT:FindTargets() + local Pos, Dir = self:GetPosition() + + local FlameSize = self:GetFlameSize() * 2 + local FlameVel = self:GetFlameVelocity() + local FlameLifeTime = self:GetFlameLifeTime() + + local Vel = Dir * FlameVel + + local trace + local Dist = 0 + local MaxDist = FlameVel * FlameLifeTime + + local targets = {} + + while Dist < (MaxDist * math.min( (CurTime() - self:GetActiveTime()) / FlameLifeTime, 1 )) do + Vel = Vel + Grav * Res + + local StartPos = Pos + local EndPos = Pos + Vel * Res + + Dist = Dist + (StartPos - EndPos):Length() + + local FlameRadius = FlameSize * (Dist / MaxDist) + local FlameHull = Vector( FlameRadius, FlameRadius, FlameRadius ) + + local traceData = { + start = StartPos, + endpos = EndPos, + mins = -FlameHull, + maxs = FlameHull, + filter = self, + } + trace = util.TraceLine( traceData ) + + --debugoverlay.Sphere( (StartPos + EndPos) * 0.5, FlameRadius, 0.05) + + Pos = EndPos + + local traceFilter = { self } + + for i = 1, 10 do + traceData.filter = traceFilter + + local hullTrace = util.TraceHull( traceData ) + + if not hullTrace.Hit or not IsValid( hullTrace.Entity ) then break end + + table.insert( traceFilter, hullTrace.Entity ) + + targets[ hullTrace.Entity:EntIndex() ] = hullTrace.HitPos + end + + if trace.Hit then + if IsValid( trace.Entity ) then + targets[ trace.Entity:EntIndex() ] = trace.HitPos + end + + for _, ent in ipairs( ents.FindInSphere( trace.HitPos, FlameSize ) ) do + targets[ ent:EntIndex() ] = ent:GetPos() + end + + break + end + end + + self.TraceResult = trace + + return targets +end + +if SERVER then + ENT.FlameStartSound = "lvs/weapons/flame_start.wav" + ENT.FlameStopSound = "lvs/weapons/flame_end.wav" + ENT.FlameLoopSound = "lvs/weapons/flame_loop.wav" + + function ENT:SetFlameStartSound( snd ) + if not isstring( snd ) then snd = "common/null.wav" end + + self.FlameStartSound = snd + end + + function ENT:SetFlameStopSound( snd ) + if not isstring( snd ) then snd = "common/null.wav" end + + self.FlameStopSound = snd + end + + function ENT:SetFlameLoopSound( snd ) + if not isstring( snd ) then snd = "common/null.wav" end + + self.FlameLoopSound = snd + end + + function ENT:SetDamage( num ) self._dmg = num end + function ENT:SetAttacker( ent ) self._attacker = ent end + + function ENT:GetAttacker() return self._attacker or NULL end + function ENT:GetDamage() return (self._dmg or 75) end + + function ENT:SetEntityFilter( filter ) + if not istable( filter ) then return end + + self._FilterEnts = {} + + for _, ent in pairs( filter ) do + self._FilterEnts[ ent ] = true + end + end + function ENT:GetEntityFilter() + return self._FilterEnts or {} + end + + function ENT:SetActiveDelay( num ) + self._activationdelay = num + end + function ENT:GetActiveDelay() + return (self._activationdelay or 0.5) + end + + function ENT:AttachTo( target, attachment ) + if not IsValid( target ) or IsValid( self:GetTarget() ) then return end + + self:SetPos( target:GetPos() ) + self:SetAngles( target:GetAngles() ) + self:SetParent( target ) + self:SetTarget( target ) + self:SetTargetAttachment( attachment or "" ) + + if not target.GetCrosshairFilterEnts then return end + + timer.Simple(1, function() + if not IsValid( self ) or not IsValid( target ) then return end + + self:SetEntityFilter( target:GetCrosshairFilterEnts() ) + end) + end + + function ENT:Enable() + if self:GetActive() then return end + + self:SetActive( true ) + self:HandleActive() + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( self:OBBCenter() ) ) + effectdata:SetEntity( self ) + effectdata:SetMagnitude( self:GetActiveDelay() ) + util.Effect( "lvs_flamestream_start", effectdata ) + + self:EmitSound( self.FlameStartSound ) + end + + function ENT:Disable() + if not self:GetActive() then return end + + self:SetActive( false ) + self:HandleActive() + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( self:OBBCenter() ) ) + effectdata:SetEntity( self ) + effectdata:SetMagnitude( self:GetActiveDelay() ) + util.Effect( "lvs_flamestream_finish", effectdata ) + + self:EmitSound( self.FlameStopSound ) + + if not self._snd then return end + + self:StopLoopingSound( self._snd ) + end + + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + end + + function ENT:HandleActive() + local T = CurTime() + local Delay = self:GetActiveDelay() + + if not self:GetActive() then + if self._IsActive then + self._IsActive = nil + self._IsFlameActive = nil + end + + self:NextThink( T + Delay ) + + return + end + + if not self._IsActive then + self._IsActive = true + + if self._LastFlameActive and self._LastFlameActive > (T - Delay) then + self:NextThink( T ) + else + self:NextThink( T + Delay ) + end + + return + end + + self._LastFlameActive = T + + if not self._IsFlameActive then + self._IsFlameActive = true + self:SetActiveTime( CurTime() ) + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( self:OBBCenter() ) ) + effectdata:SetEntity( self ) + effectdata:SetMagnitude( Delay ) + util.Effect( "lvs_flamestream_start", effectdata ) + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( self:OBBCenter() ) ) + effectdata:SetEntity( self ) + util.Effect( "lvs_flamestream", effectdata ) + + self._snd = self:StartLoopingSound( self.FlameLoopSound ) + end + + self:NextThink( T ) + end + + function ENT:SendDamage( victim, pos, damage ) + if not IsValid( victim ) then return end + + if victim:GetClass() == "prop_vehicle_prisoner_pod" then return end + + if victim:IsPlayer() and victim:InVehicle() and victim:GetCollisionGroup() ~= COLLISION_GROUP_PLAYER then return end + + local attacker = self:GetAttacker() + if not damage then damage = self:GetDamage() * FrameTime() end + + local dmg = DamageInfo() + dmg:SetDamage( damage ) + dmg:SetAttacker( IsValid( attacker ) and attacker or game.GetWorld() ) + dmg:SetInflictor( self:GetTarget() ) + dmg:SetDamageType( DMG_BURN + DMG_PREVENT_PHYSICS_FORCE ) + dmg:SetDamagePosition( pos or vector_origin ) + victim:TakeDamageInfo( dmg ) + end + + function ENT:HandleDamage() + local T = CurTime() + local filter = self:GetEntityFilter() + + for entid, pos in pairs( self:FindTargets() ) do + local ent = Entity( entid ) + + if not IsValid( ent ) or filter[ ent ] then continue end + + self:SendDamage( ent, pos ) + end + + local trace = self.TraceResult + + if not trace or not trace.Hit then return end + + local LastPos = self._LastHitPos or vector_origin + local LastTime = self._LastHitTime or 0 + + local Dist = (LastPos - trace.HitPos):Length() + + if Dist > self:GetFlameSize() * 0.4 then + self._LastHitPos = trace.HitPos + self._LastHitTime = T + self._HasSpawnedFire = nil + else + if (self._LastHitTime + 0.1) < T and not self._HasSpawnedFire then + self._HasSpawnedFire = true + + local IsProp = trace.Entity:GetMoveType() == MOVETYPE_VPHYSICS + + if not trace.HitWorld and not IsProp then return end + + local fire = ents.Create("lvs_fire") + fire:SetPos( trace.HitPos ) + fire:SetAngles( trace.HitNormal:Angle() + Angle(90,0,0) ) + fire:SetEmitter( self ) + fire:SetAttacker( self:GetAttacker() ) + if IsProp then + fire:SetParent( trace.Entity ) + end + fire:Spawn() + fire:Activate() + end + end + end + + function ENT:Think() + + self:HandleActive() + + if self:GetActive() then + self:HandleDamage() + end + + return true + end + + return +end + +function ENT:Draw( flags ) +end + +function ENT:Think() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_combine_bomb.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_combine_bomb.lua new file mode 100644 index 0000000..5fd7d1b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_combine_bomb.lua @@ -0,0 +1,127 @@ +AddCSLuaFile() + +ENT.Base = "lvs_bomb" +DEFINE_BASECLASS( "lvs_bomb" ) + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT.ExplosionEffect = "lvs_explosion_small" + +function ENT:Initialize() + self:SetModel("models/Combine_Helicopter/helicopter_bomb01.mdl") + self:SetMoveType( MOVETYPE_NONE ) + self:SetSkin( 1 ) +end + +if SERVER then + function ENT:OnTakeDamage( dmginfo ) + if self.IsTimerStarted then + self:Detonate() + else + self:StartDetonationTimer() + end + end + + function ENT:Think() + local T = CurTime() + + self:NextThink( T ) + + self:UpdateTrajectory() + + if not self.SpawnTime then return true end + + if (self.SpawnTime + 30) < T then + if self.IsTimerStarted then + self:Detonate() + else + self:Destroy() + end + end + + return true + end + + function ENT:StartDetonationTimer() + if self.IsTimerStarted then return end + + self.IsTimerStarted = true + + self.snd = CreateSound( self, "npc/attack_helicopter/aheli_mine_seek_loop1.wav" ) + self.snd:PlayEx( 0, 100 ) + self.snd:ChangeVolume(1, 0.5 ) + + self:SetSkin( 0 ) + + timer.Simple(3, function() + if not IsValid( self ) or not self.snd then return end + + self.snd:ChangePitch(160, 1 ) + + self:SetSkin( 1 ) + end ) + + timer.Simple(3.5, function() + if not IsValid( self ) then return end + + self:SetSkin( 0 ) + end ) + + timer.Simple(3.75, function() + if not IsValid( self ) then return end + + self:SetSkin( 1 ) + end ) + + timer.Simple(4, function() + if not IsValid( self ) then return end + + self:Detonate() + end ) + end + + function ENT:PhysicsCollide( data ) + if istable( self._FilterEnts ) and self._FilterEnts[ data.HitEntity ] then return end + + if IsValid( data.HitEntity ) then + self:Detonate() + else + self:StartDetonationTimer() + end + end + + function ENT:Destroy() + if self.IsDestroyed then return end + + self.IsDestroyed = true + + local mdl = ents.Create( "prop_physics" ) + mdl:SetModel( self:GetModel() ) + mdl:SetPos( self:GetPos() ) + mdl:SetAngles( self:GetAngles() ) + mdl:Spawn() + mdl:Fire("break") + + self:Remove() + end + + function ENT:OnRemove() + if self.snd then + self.snd:Stop() + self.snd = nil + end + + BaseClass.OnRemove( self ) + end + + return +end + +function ENT:Enable() + if self.IsEnabled then return end + + self.IsEnabled = true +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_engine.lua new file mode 100644 index 0000000..1139935 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_engine.lua @@ -0,0 +1,255 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 50, 5, Color( 0, 255, 255 ) ) + end + + function ENT:CheckWater( Base ) + if bit.band( util.PointContents( self:GetPos() ), CONTENTS_WATER ) ~= CONTENTS_WATER then + if self.CountWater then + self.CountWater = nil + end + + return + end + + if Base.WaterLevelAutoStop > 3 then return end + + self.CountWater = (self.CountWater or 0) + 1 + + if self.CountWater < 4 then return end + + Base:StopEngine() + end + + function ENT:Think() + + local Base = self:GetBase() + + if IsValid( Base ) and Base:GetEngineActive() then + self:CheckWater( Base ) + end + + self:NextThink( CurTime() + 1 ) + + return true + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + return +end + +ENT._oldEnActive = false +ENT._ActiveSounds = {} + +function ENT:Initialize() +end + +function ENT:StopSounds() + for id, sound in pairs( self._ActiveSounds ) do + if istable( sound ) then + for _, snd in pairs( sound ) do + if snd then + snd:Stop() + end + end + else + sound:Stop() + end + self._ActiveSounds[ id ] = nil + end +end + +function ENT:HandleEngineSounds( vehicle ) + local ply = LocalPlayer() + local pod = ply:GetVehicle() + local Throttle = vehicle:GetThrottle() + local Volume = (0.6 + math.max( vehicle:GetThrustStrenght(), 0 ) * 0.4) * Throttle + local Doppler = vehicle:CalcDoppler( ply ) + + local DrivingMe = ply:lvsGetVehicle() == vehicle + + local VolumeSetNow = false + + local FirstPerson = false + if IsValid( pod ) then + local ThirdPerson = pod:GetThirdPersonMode() + + if ThirdPerson ~= self._lvsoldTP then + self._lvsoldTP = ThirdPerson + VolumeSetNow = DrivingMe + end + + FirstPerson = DrivingMe and not ThirdPerson + end + + if DrivingMe ~= self._lvsoldDrivingMe then + self._lvsoldDrivingMe = DrivingMe + + self:StopSounds() + + self._oldEnActive = nil + + return + end + + local FT = RealFrameTime() + + local THR = (0.8 + (math.max( vehicle:GetVelocity():Length() / vehicle.MaxVelocity, 1 ) - vehicle:GetThrustStrenght()) * 0.2) * Throttle + + for id, sound in pairs( self._ActiveSounds ) do + if not sound then continue end + + local data = self.EngineSounds[ id ] + + local Pitch = math.Clamp( data.Pitch + THR * data.PitchMul, data.PitchMin, data.PitchMax ) + local PitchMul = data.UseDoppler and Doppler or 1 + + local Volume = InActive and 0 or math.min( Volume * LVS.EngineVolume * 1.25, 1 ) + + if istable( sound ) then + sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + if sound.int then sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), 0.2 ) end + + local fadespeed = VolumeSetNow and 0 or 0.1 + + if FirstPerson then + sound.ext:ChangeVolume( 0, 0 ) + + if vehicle:HasActiveSoundEmitters() then + Volume = Volume * 0.25 + fadespeed = fadespeed * 0.5 + end + + if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end + else + sound.ext:ChangeVolume( Volume, fadespeed ) + if sound.int then sound.int:ChangeVolume( 0, 0 ) end + end + else + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.1 ) + sound:ChangeVolume( Volume, data.FadeSpeed ) + end + end +end + +function ENT:OnEngineActiveChanged( Active ) + if not Active then self:StopSounds() return end + + local ply = LocalPlayer() + local DrivingMe = ply:lvsGetVehicle() == self:GetBase() + + for id, data in pairs( self.EngineSounds ) do + if not isstring( data.sound ) then continue end + + self.EngineSounds[ id ].Pitch = data.Pitch or 0 + self.EngineSounds[ id ].PitchMin = data.PitchMin or 0 + self.EngineSounds[ id ].PitchMax = data.PitchMax or 255 + self.EngineSounds[ id ].PitchMul = data.PitchMul or 100 + self.EngineSounds[ id ].Volume = data.Volume or 1 + self.EngineSounds[ id ].VolumeMin = data.VolumeMin or 0 + self.EngineSounds[ id ].VolumeMax = data.VolumeMax or 1 + self.EngineSounds[ id ].SoundLevel = data.SoundLevel or 100 + self.EngineSounds[ id ].UseDoppler = data.UseDoppler ~= false + + if data.sound_int and data.sound_int ~= data.sound and DrivingMe then + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + if data.sound_int == "" then + self._ActiveSounds[ id ] = { + ext = sound, + int = false, + } + else + local sound_interior = CreateSound( self, data.sound_int ) + sound_interior:SetSoundLevel( data.SoundLevel ) + sound_interior:PlayEx(0,100) + + self._ActiveSounds[ id ] = { + ext = sound, + int = sound_interior, + } + end + else + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + self._ActiveSounds[ id ] = sound + end + end +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + self:DamageFX( vehicle ) + + if not self.EngineSounds then + self.EngineSounds = vehicle.EngineSounds + + return + end + + local EngineActive = vehicle:GetEngineActive() + + if self._oldEnActive ~= EngineActive then + self._oldEnActive = EngineActive + self:OnEngineActiveChanged( EngineActive ) + end + + if EngineActive then + self:HandleEngineSounds( vehicle ) + end +end + +function ENT:OnRemove() + self:StopSounds() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end + +function ENT:DamageFX( vehicle ) + local T = CurTime() + local HP = vehicle:GetHP() + local MaxHP = vehicle:GetMaxHP() + + if HP <= 0 or HP > MaxHP * 0.5 or (self.nextDFX or 0) > T then return end + + self.nextDFX = T + 0.05 + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( vehicle ) + util.Effect( "lvs_engine_blacksmoke", effectdata ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_rotor.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_rotor.lua new file mode 100644 index 0000000..becff4c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_helicopter_rotor.lua @@ -0,0 +1,267 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +ENT.MinRotorEffectSize = 199 + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Float",0, "NWRadius" ) + self:NetworkVar( "Int",0, "Speed" ) + self:NetworkVar( "Int",1, "HP" ) + self:NetworkVar( "Bool",0, "Disabled" ) + + if SERVER then + self:SetSpeed( 4000 ) + self:SetHP( 10 ) + end +end + +function ENT:SetRotorEffects( new ) + self._fxShowAlways = new +end + +function ENT:GetRotorEffects() + if isbool( self._fxShowAlways ) then + return self._fxShowAlways == true + end + + return (self:GetRadius() > self.MinRotorEffectSize) +end + +function ENT:GetRadius() + return self:GetNWRadius() +end + +function ENT:SetRadius( radius ) + self:SetNWRadius( radius ) + + local AngleStep = 15 + + for ang = 15, 360, AngleStep do + local X1 = math.cos( math.rad( ang ) ) * radius + local Y1 = math.sin( math.rad( ang ) ) * radius + + local X2 = math.cos( math.rad( ang + AngleStep ) ) * radius + local Y2 = math.sin( math.rad( ang + AngleStep ) ) * radius + + debugoverlay.Line( self:LocalToWorld( Vector(X1,Y1,0) ), self:LocalToWorld( Vector(X2,Y2,0) ), 5, Color( 255, 0, 255 ) ) + end +end + +function ENT:CheckRotorClearance() + if self:GetDisabled() then self:DeleteRotorWash() return end + + local base = self:GetBase() + + if not IsValid( base ) then self:DeleteRotorWash() return end + + if not base:GetEngineActive() then self:DeleteRotorWash() return end + + local Radius = self:GetRadius() + + if base:GetThrottle() > 0.5 and self:GetRotorEffects() then + self:CreateRotorWash() + else + self:DeleteRotorWash() + end + + local pos = self:GetPos() + + local FT = FrameTime() + + self.Yaw = self.Yaw and self.Yaw + FT * self:GetSpeed() * base:GetThrottle() or 0 + + if self.Yaw >= 360 then + self.Yaw = self.Yaw - 360 + end + if self.Yaw <= -360 then + self.Yaw = self.Yaw + 360 + end + + local dir = self:LocalToWorldAngles( Angle(0,self.Yaw,0) ):Forward() + + local tr = util.TraceLine( { + start = pos, + endpos = (pos + dir * Radius), + filter = base:GetCrosshairFilterEnts(), + mask = MASK_SOLID_BRUSHONLY, + } ) + + if SERVER then + self.RotorHitCount = self.RotorHitCount or 0 + + local Hit = base._SteerOverride and tr.Hit or (tr.Hit and not tr.HitSky) + + if Hit then + self.RotorHit = true + + self.RotorHitCount = self.RotorHitCount + 1 + else + self.RotorHit = false + + self.RotorHitCount = math.max(self.RotorHitCount - 1 * FT,0) + end + + if self.RotorHitCount > self:GetHP() then + self:Destroy() + end + else + if tr.Hit and not tr.HitSky then + self.RotorHit = true + else + self.RotorHit = false + end + end + + if self.RotorHit ~= self.oldRotorHit then + if not isbool( self.oldRotorHit ) then self.oldRotorHit = self.RotorHit return end + + if self.RotorHit then + self:OnStartCollide( base, tr.HitPos, tr.HitNormal ) + else + self:OnFinishCollide( base, tr.HitPos, tr.HitNormal ) + end + + self.oldRotorHit = self.RotorHit + end +end + +function ENT:GetVehicle() + return self:GetBase() +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 50, 5, Color( 255, 0, 255 ) ) + end + + function ENT:Destroy() + if self:GetDisabled() then return end + + self:SetDisabled( true ) + self:OnDestroyed( self:GetBase() ) + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetNormal( self:GetForward() ) + effectdata:SetMagnitude( self:GetRadius() ) + util.Effect( "lvs_rotor_destruction", effectdata, true, true ) + + self:DeleteRotorWash() + end + + function ENT:OnDestroyed( base ) + self:EmitSound( "physics/metal/metal_box_break2.wav" ) + end + + function ENT:OnFinishCollide( base, Pos, Dir, Fraction ) + end + + function ENT:OnStartCollide( base, Pos, Dir, Fraction ) + end + + function ENT:OnTakeDamage( dmginfo ) + if dmginfo:IsDamageType( DMG_BLAST + DMG_DISSOLVE ) then return end + + local damage = dmginfo:GetDamage() + + self.RotorHitCount = (self.RotorHitCount or 0) + damage + + if self.RotorHitCount > self:GetHP() then + self:Destroy() + end + end + + function ENT:Think() + self:NextThink( CurTime() ) + + self:CheckRotorClearance() + + return true + end + + function ENT:OnRemove() + self:DeleteRotorWash() + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + function ENT:CreateRotorWash() + if IsValid( self.RotorWashEnt ) then return end + + local RotorWash = ents.Create( "env_rotorwash_emitter" ) + + if not IsValid( RotorWash ) then return end + + RotorWash:SetPos( self:GetPos() ) + RotorWash:SetAngles( self:GetAngles() ) + RotorWash:Spawn() + RotorWash:Activate() + RotorWash:SetParent( self ) + RotorWash.DoNotDuplicate = true + + self:DeleteOnRemove( RotorWash ) + + self.RotorWashEnt = RotorWash + + local Base = self:GetBase() + + if IsValid( Base ) then + Base:TransferCPPI( RotorWash ) + end + end + + function ENT:DeleteRotorWash() + if not IsValid( self.RotorWashEnt ) then return end + + self.RotorWashEnt:Remove() + end + + return +end + +function ENT:CreateRotorWash() +end + +function ENT:DeleteRotorWash() +end + +function ENT:OnStartCollide( base, Pos, Dir ) + local effectdata = EffectData() + effectdata:SetOrigin( Pos + Dir ) + effectdata:SetNormal( Dir ) + util.Effect( "stunstickimpact", effectdata, true, true ) + + self:EmitSound( "physics/metal/metal_computer_impact_bullet"..math.random(1,3)..".wav", 75, 80 + base:GetThrottle() * 20, 0.4 ) +end + +function ENT:OnFinishCollide( base, Pos, Dir ) + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( -self:LocalToWorldAngles( Angle(0,self.Yaw,0) ):Right() * 2 * base:Sign( self:GetSpeed() ) ) + util.Effect( "manhacksparks", effectdata, true, true ) + + self:EmitSound( "ambient/materials/roust_crash"..math.random(1,2)..".wav", 75, 90 + base:GetThrottle() * 20, 0.2 ) +end + +function ENT:Think() + self:CheckRotorClearance() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_ammo.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_ammo.lua new file mode 100644 index 0000000..0244547 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_ammo.lua @@ -0,0 +1,151 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Ammo" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/misc/88mm_shell.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:PhysWake() + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + end + + function ENT:Think() + if self.MarkForRemove then + self:Remove() + + return false + end + + self:NextThink( CurTime() + 0.1 ) + + return true + end + + function ENT:AddSingleRound( entity ) + local AmmoIsSet = false + + for PodID, data in pairs( entity.WEAPONS ) do + for id, weapon in pairs( data ) do + local MaxAmmo = weapon.Ammo or -1 + local CurAmmo = weapon._CurAmmo or MaxAmmo + + if CurAmmo == MaxAmmo then continue end + + entity.WEAPONS[PodID][ id ]._CurAmmo = math.min( CurAmmo + 1, MaxAmmo ) + + AmmoIsSet = true + end + end + + if AmmoIsSet then + entity:SetNWAmmo( entity:GetAmmo() ) + + for _, pod in pairs( entity:GetPassengerSeats() ) do + local weapon = pod:lvsGetWeapon() + + if not IsValid( weapon ) then continue end + + weapon:SetNWAmmo( weapon:GetAmmo() ) + end + end + + return AmmoIsSet + end + + function ENT:Refil( entity ) + if self.MarkForRemove then return end + + if not IsValid( entity ) then return end + + if not entity.LVS then return end + + if self:AddSingleRound( entity ) then + entity:OnMaintenance() + + entity:EmitSound("items/ammo_pickup.wav") + + self.MarkForRemove = true + end + end + + function ENT:ShootBullet( attacker ) + if self.BeenFired then return end + + self.BeenFired = true + + local hit_decal = ents.Create( "lvs_armor_bounce" ) + hit_decal:SetPos( self:GetPos() ) + hit_decal:SetAngles( self:GetAngles() ) + hit_decal:Spawn() + hit_decal:Activate() + hit_decal:EmitSound("ambient/explosions/explode_4.wav", 75, 120, 1) + hit_decal:SetCollisionGroup( COLLISION_GROUP_NONE ) + + if IsValid( attacker ) then + hit_decal:SetPhysicsAttacker( attacker, 10 ) + end + + local PhysObj = hit_decal:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:SetMass( 50 ) + PhysObj:EnableDrag( false ) + PhysObj:SetVelocityInstantaneous( self:GetForward() * 4000 ) + PhysObj:SetAngleVelocityInstantaneous( VectorRand() * 250 ) + end + + self:SetModel("models/misc/88mm_casing.mdl") + end + + function ENT:PhysicsCollide( data, physobj ) + if data.Speed > 60 and data.DeltaTime > 0.2 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + if VelDif > 700 then + self:ShootBullet() + end + end + + self:Refil( data.HitEntity ) + end + + function ENT:OnTakeDamage( dmginfo ) + self:ShootBullet( dmginfo:GetAttacker() ) + end +end + +if CLIENT then + function ENT:Draw() + self:DrawModel() + end + + function ENT:OnRemove() + end + + function ENT:Think() + end +end + +function ENT:GetCrosshairFilterEnts() + return {self} +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_ammocrate.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_ammocrate.lua new file mode 100644 index 0000000..73fe1c0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_ammocrate.lua @@ -0,0 +1,71 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Ammo Crate" +ENT.Information = "Single-Use Ammo Refil Item" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/items/item_item_crate.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:PhysWake() + end + + function ENT:Think() + return false + end + + function ENT:Refil( entity ) + if self.MarkForRemove then return end + + if not IsValid( entity ) then return end + + if not entity.LVS then return end + + if entity:WeaponRestoreAmmo() then + entity:EmitSound("items/ammo_pickup.wav") + + self.MarkForRemove = true + + SafeRemoveEntityDelayed( self, 0 ) + end + end + + function ENT:PhysicsCollide( data, physobj ) + self:Refil( data.HitEntity ) + end + + function ENT:OnTakeDamage( dmginfo ) + end +end + +if CLIENT then + function ENT:Draw( flags ) + self:DrawModel( flags ) + end + + function ENT:OnRemove() + end + + function ENT:Think() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_compressor.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_compressor.lua new file mode 100644 index 0000000..1ed4d2d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_compressor.lua @@ -0,0 +1,158 @@ +AddCSLuaFile() + +ENT.Base = "lvs_wheeldrive_engine_mod" + +ENT.PrintName = "Supercharger" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" +ENT.Information = "Edit Properties to change Torque and Power Curve" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +if SERVER then + function ENT:Initialize() + self:SetModel("models/diggercars/dodge_charger/blower_animated.mdl") + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:CanLink( ent ) + if not ent.AllowSuperCharger or IsValid( ent:GetCompressor() ) then return false end + + return true + end + + local function SaveCompressor( ply, ent, data ) + if not duplicator or not duplicator.StoreEntityModifier then return end + + timer.Simple( 0.2, function() + if not IsValid( ent ) or not isfunction( ent.AddSuperCharger ) then return end + + local compressor = ent:AddSuperCharger() + if IsValid( compressor ) then + if data.Curve then compressor:SetEngineCurve( data.Curve ) end + if data.Torque then compressor:SetEngineTorque( data.Torque ) end + end + end ) + + duplicator.StoreEntityModifier( ent, "lvsCarCompressor", data ) + end + + if duplicator and duplicator.RegisterEntityModifier then + duplicator.RegisterEntityModifier( "lvsCarCompressor", SaveCompressor ) + end + + function ENT:OnLinked( ent ) + ent:OnSuperCharged( true ) + ent:SetCompressor( self ) + + if not self.PlaySound then return end + + ent:EmitSound("lvs/equip_blower.ogg") + end + + function ENT:OnUnLinked( ent ) + ent:OnSuperCharged( false ) + + if not duplicator or not duplicator.ClearEntityModifier then return end + + duplicator.ClearEntityModifier( ent, "lvsCarCompressor" ) + end + + function ENT:OnVehicleUpdated( ent ) + if not duplicator or not duplicator.ClearEntityModifier or not duplicator.StoreEntityModifier then return end + + duplicator.ClearEntityModifier( ent, "lvsCarCompressor" ) + local data = { + Curve = self:GetEngineCurve(), + Torque = self:GetEngineTorque(), + } + duplicator.StoreEntityModifier( ent, "lvsCarCompressor", data ) + end + + return +end + +function ENT:OnEngineActiveChanged( Active, soundname ) + if Active then + self:StartSounds( soundname ) + else + self:StopSounds() + end +end + +function ENT:StartSounds( soundname ) + if self.snd then return end + + self.snd = CreateSound( self, soundname ) + self.snd:PlayEx(0,100) +end + +function ENT:StopSounds() + if not self.snd then return end + + self.snd:Stop() + self.snd = nil +end + +function ENT:HandleSounds( vehicle, engine ) + if not self.snd then return end + + local throttle = engine:GetClutch() and 0 or vehicle:GetThrottle() + local volume = (0.2 + math.max( math.sin( math.rad( ((engine:GetRPM() - vehicle.EngineIdleRPM) / (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM)) * 90 ) ), 0 ) * 0.8) * throttle * vehicle.SuperChargerVolume + local pitch = engine:GetRPM() / vehicle.EngineMaxRPM + + local ply = LocalPlayer() + local doppler = vehicle:CalcDoppler( ply ) + + self._smBoost = self._smBoost and self._smBoost + (volume - self._smBoost) * FrameTime() * 5 or 0 + + self.snd:ChangeVolume( volume * engine:GetEngineVolume() ) + self.snd:ChangePitch( (60 + pitch * 85) * doppler ) +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + local EngineActive = vehicle:GetEngineActive() + + if self._oldEnActive ~= EngineActive then + self._oldEnActive = EngineActive + + self:OnEngineActiveChanged( EngineActive, vehicle.SuperChargerSound ) + end + + if EngineActive then + local engine = vehicle:GetEngine() + + if not IsValid( engine ) then return end + + self:SetPoseParameter( "throttle_pedal", math.max( vehicle:GetThrottle() - (engine:GetClutch() and 1 or 0), 0 ) ) + self:InvalidateBoneCache() + + self:HandleSounds( vehicle, engine ) + end +end + +function ENT:OnRemove() + self:StopSounds() +end + +function ENT:Draw( flags ) + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then + self:DrawModel( flags ) + + return + end + + if not vehicle.SuperChargerVisible then return end + + self:DrawModel( flags ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_exhaust.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_exhaust.lua new file mode 100644 index 0000000..cd75c31 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_exhaust.lua @@ -0,0 +1,74 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Tuning Exhaust" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/props_vehicles/carparts_muffler01a.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:Think() + return false + end + + function ENT:PhysicsCollide( data ) + if self.MarkForRemove then return end + + local ent = data.HitEntity + + if not IsValid( ent ) or not ent.LVS or not isfunction( ent.SetBackfire ) then return end + + ent:SetBackfire( not ent:GetBackfire() ) + + local ply = self:GetCreator() + + if ent:GetBackfire() then + ent:EmitSound("common/wpn_hudoff.wav") + + if IsValid( ply ) then + ply:ChatPrint( "Tuning Exhaust Added" ) + end + else + ent:EmitSound("common/wpn_denyselect.wav") + + if IsValid( ply ) then + ply:ChatPrint( "Tuning Exhaust Removed" ) + end + end + + self.MarkForRemove = true + + SafeRemoveEntityDelayed( self, 0 ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + +else + function ENT:Draw( flags ) + self:DrawModel( flags ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_explosive.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_explosive.lua new file mode 100644 index 0000000..673de72 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_explosive.lua @@ -0,0 +1,110 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Explosive" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminOnly = false + +if SERVER then + function ENT:SetDamage( num ) self._dmg = num end + function ENT:SetRadius( num ) self._radius = num end + function ENT:SetAttacker( ent ) self._attacker = ent end + + function ENT:GetAttacker() return self._attacker or NULL end + function ENT:GetDamage() return (self._dmg or 250) end + function ENT:GetRadius() return (self._radius or 250) end + + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/Items/grenadeAmmo.mdl" ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + + self.TrailEntity = util.SpriteTrail( self, 0, Color(120,120,120,120), false, 5, 40, 0.2, 1 / ( 15 + 1 ) * 0.5, "trails/smoke" ) + end + + function ENT:Think() + self:NextThink( CurTime() ) + + if self.Active then + self:Detonate() + end + + return true + end + + function ENT:Detonate() + if self.IsExploded then return end + + self.IsExploded = true + + local Pos = self:GetPos() + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( Vector(0,0,1) ) + effectdata:SetMagnitude( 1 ) + + if self:WaterLevel() >= 2 then + util.Effect( "WaterSurfaceExplosion", effectdata, true, true ) + else + util.Effect( "lvs_defence_explosion", effectdata ) + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage( self:GetDamage() ) + dmginfo:SetAttacker( IsValid( self:GetAttacker() ) and self:GetAttacker() or self ) + dmginfo:SetDamageType( DMG_SONIC ) + dmginfo:SetInflictor( self ) + dmginfo:SetDamagePosition( Pos ) + + util.BlastDamageInfo( dmginfo, Pos, self:GetRadius() ) + + self:Remove() + end + + function ENT:PhysicsCollide( data, physobj ) + self.Active = true + + if data.Speed > 60 and data.DeltaTime > 0.2 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + if VelDif > 200 then + self:EmitSound( "Grenade.ImpactHard" ) + else + self:EmitSound( "Grenade.ImpactSoft" ) + end + + physobj:SetVelocity( data.OurOldVelocity * 0.5 ) + end + end +else + function ENT:Draw() + self:DrawModel() + end + + function ENT:Think() + return false + end + + function ENT:OnRemove() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_gauge.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_gauge.lua new file mode 100644 index 0000000..e2fddaf --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_gauge.lua @@ -0,0 +1,74 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Gauge" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/diggercars/tacho.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:Think() + return false + end + + function ENT:PhysicsCollide( data ) + if self.MarkForRemove then return end + + local ent = data.HitEntity + + if not IsValid( ent ) or not ent.LVS or not isfunction( ent.SetRacingHud ) then return end + + ent:SetRacingHud( not ent:GetRacingHud() ) + + local ply = self:GetCreator() + + if ent:GetRacingHud() then + ent:EmitSound("common/wpn_hudoff.wav") + + if IsValid( ply ) then + ply:ChatPrint( "Gauge Added" ) + end + else + ent:EmitSound("common/wpn_denyselect.wav") + + if IsValid( ply ) then + ply:ChatPrint( "Gauge Removed" ) + end + end + + self.MarkForRemove = true + + SafeRemoveEntityDelayed( self, 0 ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + +else + function ENT:Draw( flags ) + self:DrawModel( flags ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_gear.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_gear.lua new file mode 100644 index 0000000..7f5988b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_gear.lua @@ -0,0 +1,105 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "TopSpeed Upgrade" +ENT.Author = "Luna" +ENT.Category = "[LVS]" +ENT.Information = "Edit Properties to change Max Speed" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.Editable = true + +ENT.PhysicsSounds = true + +function ENT:SetupDataTables() + self:NetworkVar( "Float",0, "MaxSpeed", { KeyName = "maxspeed", Edit = { type = "Float", order = 1,min = 1, max = 1000, category = "Upgrade Settings"} } ) + + if SERVER then + + self:SetMaxSpeed( 300 ) + end +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + local function SaveVelocity( ply, ent, data ) + if not duplicator or not duplicator.StoreEntityModifier then return end + + if not IsValid( ent ) or not isfunction( ent.ChangeVelocity ) then return end + + ent:ChangeVelocity( data.MaxVelocity ) + + duplicator.StoreEntityModifier( ent, "lvsSaveVelocity", data ) + end + + if duplicator and duplicator.RegisterEntityModifier then + duplicator.RegisterEntityModifier( "lvsSaveVelocity", SaveVelocity ) + end + + function ENT:Initialize() + self:SetModel( "models/props_wasteland/gear01.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:Think() + return false + end + + function ENT:PhysicsCollide( data ) + if self.MarkForRemove then return end + + local ent = data.HitEntity + + if not IsValid( ent ) or not ent.LVS or not ent.MaxVelocity or not isfunction( ent.ChangeVelocity ) then return end + + local MaxVelocity = self:GetMaxSpeed() * (1 / 0.09144) + + local ply = self:GetCreator() + + if ent.MaxVelocity < MaxVelocity then + ent:EmitSound("ambient/machines/spinup.wav") + + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_upgrade", effectdata ) + else + ent:EmitSound("ambient/machines/spindown.wav") + + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_downgrade", effectdata ) + end + + ent:ChangeVelocity( MaxVelocity ) + + duplicator.StoreEntityModifier( ent, "lvsSaveVelocity", { MaxVelocity = MaxVelocity } ) + + self.MarkForRemove = true + + SafeRemoveEntityDelayed( self, 0 ) + end + + function ENT:OnTakeDamage( dmginfo ) + end +else + function ENT:Draw( flags ) + self:DrawModel( flags ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_jerrycan.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_jerrycan.lua new file mode 100644 index 0000000..a034b0d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_jerrycan.lua @@ -0,0 +1,390 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Jerry Can (Petrol)" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.AutomaticFrameAdvance = true + +ENT.FuelAmount = 500 -- seconds +ENT.FuelType = LVS.FUELTYPE_PETROL + +ENT.lvsGasStationFillSpeed = 0.05 +ENT.lvsGasStationRefillMe = true + +ENT.PhysicsSounds = true + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Active" ) + self:NetworkVar( "Float", 0, "Fuel" ) + self:NetworkVar( "Entity",0, "User" ) + + if SERVER then + self:SetFuel( 1 ) + end +end + +function ENT:IsOpen() + return self:GetActive() +end + +function ENT:IsUpright() + local Up = self:GetUp() + + return Up.z > 0.5 +end + +function ENT:GetFuelType() + return self.FuelType +end + +function ENT:GetSize() + return (self.FuelAmount * LVS.FuelScale) +end + +function ENT:GetFuelType() + return self.FuelType +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/misc/fuel_can.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + end + + function ENT:OnRefueled() + self:EmitSound( "vehicles/jetski/jetski_no_gas_start.wav" ) + end + + function ENT:TakeFuel( Need ) + local Fuel = self:GetFuel() + local Size = self:GetSize() + local Available = math.min( Size * Fuel, Size* self.lvsGasStationFillSpeed ) + local Give = math.min( Need, Available ) + + self:SetFuel( Fuel - Give / Size ) + + return Give + end + + function ENT:giveSWEP( ply ) + self:EmitSound("common/wpn_select.wav") + + ply:SetSuppressPickupNotices( true ) + ply:Give( "weapon_lvsfuelfiller" ) + ply:SetSuppressPickupNotices( false ) + + ply:SelectWeapon( "weapon_lvsfuelfiller" ) + self:SetUser( ply ) + + local SWEP = ply:GetWeapon( "weapon_lvsfuelfiller" ) + + if not IsValid( SWEP ) then return end + + SWEP:SetFuelType( self:GetFuelType() ) + SWEP:SetCallbackTarget( self ) + end + + function ENT:removeSWEP( ply ) + if ply:HasWeapon( "weapon_lvsfuelfiller" ) then + ply:StripWeapon( "weapon_lvsfuelfiller" ) + ply:SwitchToDefaultWeapon() + end + self:SetUser( NULL ) + end + + function ENT:checkSWEP( ply ) + if not ply:Alive() or ply:InVehicle() then + + self:removeSWEP( ply ) + + return + end + + local weapon = ply:GetActiveWeapon() + + if not IsValid( weapon ) or weapon:GetClass() ~= "weapon_lvsfuelfiller" then + self:removeSWEP( ply ) + + return + end + + if (ply:GetPos() - self:GetPos()):LengthSqr() < 150000 then return end + + self:removeSWEP( ply ) + end + + function ENT:Think() + if self:IsOpen() and not self:IsUpright() then + local amount = FrameTime() * 0.25 + + self:SetFuel( math.max( self:GetFuel() - amount, 0 ) ) + end + + local ply = self:GetUser() + local T = CurTime() + + if IsValid( ply ) then + self:checkSWEP( ply ) + end + + self:NextThink( T ) + + return true + end + + function ENT:Use( ply ) + if not IsValid( ply ) or not ply:IsPlayer() then return end + + local Active = self:GetActive() + local User = self:GetUser() + + if IsValid( User ) and User == ply then + self:removeSWEP( ply ) + self:PlayAnimation( "close" ) + self:SetActive( false ) + + return + end + + if Active then + if ply:HasWeapon("weapon_lvsfuelfiller") or ply:KeyDown( IN_WALK ) or ply:KeyDown( IN_SPEED ) then + self:PlayAnimation( "close" ) + self:SetActive( false ) + else + if not IsValid( User ) then + self:giveSWEP( ply ) + end + end + + return + end + + self:SetActive ( true ) + self:PlayAnimation( "open" ) + self:EmitSound("buttons/lever7.wav") + end + + function ENT:OnRemove() + local User = self:GetUser() + + if not IsValid( User ) then return end + + self:removeSWEP( User ) + end + + function ENT:PhysicsCollide( data, physobj ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:PlayAnimation( animation, playbackrate ) + playbackrate = playbackrate or 1 + + local sequence = self:LookupSequence( animation ) + + self:ResetSequence( sequence ) + self:SetPlaybackRate( playbackrate ) + self:SetSequence( sequence ) + end +end + +if CLIENT then + ENT.FrameMat = Material( "lvs/3d2dmats/frame.png" ) + ENT.RefuelMat = Material( "lvs/3d2dmats/refuel.png" ) + + function ENT:Draw() + self:DrawModel() + self:DrawCable() + + local ply = LocalPlayer() + local Pos = self:GetPos() + + if not IsValid( ply ) then return end + + if ply:HasWeapon("weapon_lvsfuelfiller") then return end + + if (ply:GetPos() - Pos):LengthSqr() > 5000000 then return end + + local data = LVS.FUELTYPES[ self.FuelType ] + local Text = data.name + local IconColor = Color( data.color.x, data.color.y, data.color.z, 255 ) + + for i = -1, 1, 2 do + cam.Start3D2D( self:LocalToWorld( Vector(0,4 * i,0) ), self:LocalToWorldAngles( Angle(0,90 + 90 * i,90) ), 0.1 ) + surface.SetDrawColor( IconColor ) + + surface.SetMaterial( self.FrameMat ) + surface.DrawTexturedRect( -50, -50, 100, 100 ) + + surface.SetMaterial( self.RefuelMat ) + surface.DrawTexturedRect( -50, -50, 100, 100 ) + + draw.SimpleText( Text, "LVS_FONT", 0, 75, IconColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + + draw.SimpleText( math.Round( self:GetFuel() * 100, 0 ).."%", "LVS_FONT", 0, 95, IconColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + cam.End3D2D() + end + end + + local cable = Material( "cable/cable2" ) + local function bezier(p0, p1, p2, p3, t) + local e = p0 + t * (p1 - p0) + local f = p1 + t * (p2 - p1) + local g = p2 + t * (p3 - p2) + + local h = e + t * (f - e) + local i = f + t * (g - f) + + local p = h + t * (i - h) + + return p + end + + function ENT:DrawCable() + local plyL = LocalPlayer() + + if not IsValid( plyL ) then return end + + if plyL:GetPos():DistToSqr( self:GetPos() ) > 350000 then return end + + local ply = self:GetUser() + + if not IsValid( ply ) then return end + + local pos = self:LocalToWorld( Vector(10,0,45) ) + local ang = self:LocalToWorldAngles( Angle(0,90,90) ) + + local startPos = self:LocalToWorld( Vector(7,0,5) ) + local p2 = self:LocalToWorld( Vector(8,0,40) ) + local p3 + local endPos + + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + endPos = (attachment.Pos + attachment.Ang:Forward() * -3 + attachment.Ang:Right() * 2 + attachment.Ang:Up() * -3.5) + p3 = endPos + attachment.Ang:Right() * 5 - attachment.Ang:Up() * 20 + + render.StartBeam( 15 ) + render.SetMaterial( cable ) + + for i = 0,15 do + local pos = bezier(startPos, p2, p3, endPos, i / 14) + + local Col = (render.GetLightColor( pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + render.AddBeam( pos, 1, 0, Color(Col.r,Col.g,Col.b,255) ) + end + + render.EndBeam() + end + + function ENT:OnRemove() + self:StopPour() + end + + function ENT:StartPour() + if self.snd then return end + + self.snd = CreateSound( self, "lvs/jerrycan_use.wav" ) + self.snd:PlayEx(0.5,80) + self.snd:ChangePitch(120,3) + end + + function ENT:StopPour() + if not self.snd then return end + + self.snd:Stop() + self.snd = nil + end + + function ENT:DoEffect() + local Up = self:GetUp() + local Pos = self:LocalToWorld( Vector(7.19,-0.01,10.46) ) + + local emitter = ParticleEmitter( Pos, false ) + local particle = emitter:Add( "effects/slime1", Pos ) + + if particle then + particle:SetVelocity( Up * math.abs( Up.z ) * 100 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetDieTime( 2 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 1.5 ) + particle:SetEndSize( 1.5 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 240,200,0,255 ) + particle:SetCollide( true ) + particle:SetCollideCallback( function( part, hitpos, hitnormal ) + local effectdata = EffectData() + effectdata:SetOrigin( hitpos ) + effectdata:SetNormal( hitnormal * 2 ) + effectdata:SetMagnitude( 0.2 ) + effectdata:SetScale( 0.2 ) + effectdata:SetRadius( 0.2 ) + util.Effect( "StriderBlood", effectdata ) + + sound.Play( "ambient/water/water_spray"..math.random(1,3)..".wav", hitpos, 55, math.Rand(95,105), 0.5 ) + + particle:SetDieTime( 0 ) + + if not IsValid( self ) then return false end + + if not self.LastPos then self.LastPos = hitpos end + + if (self.LastPos - hitpos):Length() < 10 then + return + end + + self.LastPos = hitpos + + util.Decal( "BeerSplash", hitpos + hitnormal * 2, hitpos - hitnormal * 2 ) + end ) + end + + emitter:Finish() + end + + function ENT:Think() + self:SetNextClientThink( CurTime() + 0.02 ) + + if self:GetFuel() <= 0 then self:StopPour() return end + + local T = CurTime() + + if not self:IsOpen() or self:IsUpright() then + + self:StopPour() + + return true + end + + self:StartPour() + + self:DoEffect() + + return true + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_jerrycan_diesel.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_jerrycan_diesel.lua new file mode 100644 index 0000000..9ac9a5f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_jerrycan_diesel.lua @@ -0,0 +1,26 @@ +AddCSLuaFile() + +ENT.Base = "lvs_item_jerrycan" +DEFINE_BASECLASS( "lvs_item_jerrycan" ) + +ENT.PrintName = "Jerry Can (Diesel)" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.AutomaticFrameAdvance = true + +ENT.FuelAmount = 120 -- seconds +ENT.FuelType = LVS.FUELTYPE_DIESEL + +ENT.lvsGasStationFillSpeed = 0.05 +ENT.lvsGasStationRefillMe = true + +if SERVER then + function ENT:Initialize() + BaseClass.Initialize( self ) + self:SetSkin( 1 ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_mine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_mine.lua new file mode 100644 index 0000000..81ed3be --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_mine.lua @@ -0,0 +1,164 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Mine" +ENT.Author = "Blu-x92" +ENT.Information = "Immobilize Tanks" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +if SERVER then + function ENT:SetDamage( num ) self._dmg = num end + function ENT:SetForce( num ) self._force = num end + function ENT:SetRadius( num ) self._radius = num end + function ENT:SetAttacker( ent ) self._attacker = ent end + + function ENT:GetAttacker() return self._attacker or NULL end + function ENT:GetDamage() return (self._dmg or 2000) end + function ENT:GetForce() return (self._force or 8000) end + function ENT:GetRadius() return (self._radius or 150) end + + function ENT:SpawnFunction( ply, tr, ClassName ) + + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent.Attacker = ply + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:Spawn() + ent:Activate() + + return ent + + end + + function ENT:Initialize() + self:SetModel( "models/blu/lvsmine.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + self:DrawShadow( false ) + + self.First = true + end + + function ENT:Use( ply ) + end + + function ENT:Detonate( Pos ) + if self.IsExploded then return end + + self.IsExploded = true + + if not isvector( Pos ) then Pos = self:GetPos() end + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( Vector(0,0,1) ) + effectdata:SetMagnitude( 1 ) + util.Effect( "lvs_bullet_impact_explosive", effectdata, true, true ) + + local attacker = self:GetAttacker() + + LVS:BlastDamage( Pos, Vector(0,0,1), IsValid( attacker ) and attacker or game.GetWorld(), self, self:GetDamage(), DMG_BLAST, self:GetRadius(), self:GetForce() ) + + SafeRemoveEntityDelayed( self, FrameTime() ) + end + + function ENT:Think() + local PhysObj = self:GetPhysicsObject() + + if IsValid( PhysObj ) and PhysObj:IsMotionEnabled() then + if PhysObj:IsAsleep() then + PhysObj:EnableMotion( false ) + end + end + + self:NextThink( CurTime() + 1 ) + + return true + end + + function ENT:OnRemove() + end + + function ENT:PhysicsCollide( data, PhysObj ) + local HitEnt = data.HitEntity + + if self.First then + self.First = nil + self.IgnoreEnt = HitEnt + end + + if HitEnt == self.IgnoreEnt then + if data.Speed > 60 and data.DeltaTime > 0.1 then + self:EmitSound( "weapon.ImpactHard" ) + end + + return + end + + PhysObj:SetVelocity( data.OurOldVelocity * 0.5 ) + + if not IsValid( HitEnt ) or HitEnt:IsWorld() then + if data.Speed > 60 and data.DeltaTime > 0.1 then + self:EmitSound( "weapon.ImpactHard" ) + end + + return + end + + if not HitEnt:IsPlayer() and HitEnt:GetClass() ~= self:GetClass() then + self:Detonate( data.HitPos ) + else + if data.Speed > 60 and data.DeltaTime > 0.1 then + self:EmitSound( "weapon.ImpactHard" ) + end + end + end + + function ENT:OnTakeDamage( dmginfo ) + self:Detonate() + end +else + function ENT:Draw( flags ) + local ply = LocalPlayer() + + if IsValid( ply ) then + if not ply:InVehicle() then + self:DrawModel( flags ) + + return + end + + local ViewEnt = ply:GetViewEntity() + + if IsValid( ViewEnt ) then + ply = ViewEnt + end + else + return + end + + local OldPos = self:GetPos() + + local Dist = math.min( (ply:GetPos() - self:GetPos()):LengthSqr() / 50000, 4.5 ) + + self:SetPos( self:LocalToWorld( Vector(0,0,-Dist) ) ) + self:DrawModel( flags ) + self:SetPos( OldPos ) + end + + function ENT:Think() + return false + end + + function ENT:OnRemove() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_racingtires.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_racingtires.lua new file mode 100644 index 0000000..2942a8f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_racingtires.lua @@ -0,0 +1,90 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Racing Tires" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/diggercars/tires.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:Think() + return false + end + + function ENT:PhysicsCollide( data ) + if self.MarkForRemove then return end + + local ent = data.HitEntity + + if not IsValid( ent ) or not ent.LVS or not isfunction( ent.SetRacingTires ) or ent.PivotSteerEnable then return end + + ent:SetRacingTires( not ent:GetRacingTires() ) + + local ply = self:GetCreator() + + ent:EmitSound("npc/dog/dog_servo6.wav") + + if ent:GetRacingTires() then + if IsValid( ply ) then + ply:ChatPrint( "Racing Tires Mounted" ) + end + + for _, wheel in pairs( ent:GetWheels() ) do + if not IsValid( wheel ) then continue end + + local effectdata = EffectData() + effectdata:SetOrigin( wheel:GetPos() ) + effectdata:SetEntity( wheel ) + util.Effect( "lvs_upgrade", effectdata ) + end + else + if IsValid( ply ) then + ply:ChatPrint( "Racing Tires Removed" ) + end + + for _, wheel in pairs( ent:GetWheels() ) do + if not IsValid( wheel ) then continue end + + local effectdata = EffectData() + effectdata:SetOrigin( wheel:GetPos() ) + effectdata:SetEntity( wheel ) + util.Effect( "lvs_downgrade", effectdata ) + end + end + + self.MarkForRemove = true + + SafeRemoveEntityDelayed( self, 0 ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + +else + function ENT:Draw( flags ) + self:DrawModel( flags ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_refuel.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_refuel.lua new file mode 100644 index 0000000..5c715bd --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_refuel.lua @@ -0,0 +1,265 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Gas Station" +ENT.Author = "Luna" +ENT.Information = "Refills fuel tanks" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.Editable = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "User" ) + self:NetworkVar( "Int",0, "FuelType", { KeyName = "fueltype", Edit = { type = "Int", order = 1,min = 0, max = #LVS.FUELTYPES, category = "Settings"} } ) + + if SERVER then + self:SetFuelType( LVS.FUELTYPE_PETROL ) + end +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Initialize() + self:SetModel( "models/props_wasteland/gaspump001a.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:EnableMotion( false ) + end + + function ENT:giveSWEP( ply ) + self:EmitSound("common/wpn_select.wav") + + ply:SetSuppressPickupNotices( true ) + ply:Give( "weapon_lvsfuelfiller" ) + ply:SetSuppressPickupNotices( false ) + + ply:SelectWeapon( "weapon_lvsfuelfiller" ) + self:SetUser( ply ) + + local SWEP = ply:GetWeapon( "weapon_lvsfuelfiller" ) + + if not IsValid( SWEP ) then return end + + SWEP:SetFuelType( self:GetFuelType() ) + SWEP:SetCallbackTarget( self ) + end + + function ENT:removeSWEP( ply ) + if ply:HasWeapon( "weapon_lvsfuelfiller" ) then + ply:StripWeapon( "weapon_lvsfuelfiller" ) + ply:SwitchToDefaultWeapon() + end + self:SetUser( NULL ) + end + + function ENT:checkSWEP( ply ) + if not ply:Alive() or ply:InVehicle() then + + self:removeSWEP( ply ) + + return + end + + local weapon = ply:GetActiveWeapon() + + if not IsValid( weapon ) or weapon:GetClass() ~= "weapon_lvsfuelfiller" then + self:removeSWEP( ply ) + + return + end + + if (ply:GetPos() - self:GetPos()):LengthSqr() < 150000 then return end + + self:removeSWEP( ply ) + end + + function ENT:Think() + local ply = self:GetUser() + local T = CurTime() + + if IsValid( ply ) then + self:checkSWEP( ply ) + + self:NextThink( T ) + else + self:NextThink( T + 0.5 ) + end + + return true + end + + function ENT:Use( ply ) + if not IsValid( ply ) or not ply:IsPlayer() then return end + + local User = self:GetUser() + + if IsValid( User ) then + if User == ply then + self:removeSWEP( ply ) + end + else + if ply:HasWeapon("weapon_lvsfuelfiller") then return end + + self:giveSWEP( ply ) + end + end + + function ENT:OnRemove() + local User = self:GetUser() + + if not IsValid( User ) then return end + + self:removeSWEP( User ) + end +end + +if CLIENT then + function ENT:CreatePumpEnt() + if IsValid( self.PumpEnt ) then return self.PumpEnt end + + self.PumpEnt = ents.CreateClientProp() + self.PumpEnt:SetModel( "models/props_equipment/gas_pump_p13.mdl" ) + self.PumpEnt:SetPos( self:LocalToWorld( Vector(-0.2,-14.6,45.7) ) ) + self.PumpEnt:SetAngles( self:LocalToWorldAngles( Angle(-0.3,92.3,-0.1) ) ) + self.PumpEnt:Spawn() + self.PumpEnt:Activate() + self.PumpEnt:SetParent( self ) + + return self.PumpEnt + end + + function ENT:RemovePumpEnt() + if not IsValid( self.PumpEnt ) then return end + + self.PumpEnt:Remove() + end + + function ENT:Think() + local PumpEnt = self:CreatePumpEnt() + + local ShouldDraw = IsValid( self:GetUser() ) + local Draw = PumpEnt:GetNoDraw() + + if Draw ~= ShouldDraw then + PumpEnt:SetNoDraw( ShouldDraw ) + end + end + + local cable = Material( "cable/cable2" ) + local function bezier(p0, p1, p2, p3, t) + local e = p0 + t * (p1 - p0) + local f = p1 + t * (p2 - p1) + local g = p2 + t * (p3 - p2) + + local h = e + t * (f - e) + local i = f + t * (g - f) + + local p = h + t * (i - h) + + return p + end + + ENT.FrameMat = Material( "lvs/3d2dmats/frame.png" ) + ENT.RefuelMat = Material( "lvs/3d2dmats/refuel.png" ) + + function ENT:Draw() + self:DrawModel() + self:DrawCable() + + local ply = LocalPlayer() + local Pos = self:GetPos() + + if not IsValid( ply ) then return end + + if (ply:GetPos() - Pos):LengthSqr() > 5000000 then return end + + local data = LVS.FUELTYPES[ self:GetFuelType() ] + local Text = data.name + local IconColor = Color( data.color.x, data.color.y, data.color.z, 255 ) + + cam.Start3D2D( self:LocalToWorld( Vector(10,0,45) ), self:LocalToWorldAngles( Angle(0,90,90) ), 0.1 ) + draw.NoTexture() + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawRect( -150, -120, 300, 240 ) + + surface.SetDrawColor( IconColor ) + + surface.SetMaterial( self.FrameMat ) + surface.DrawTexturedRect( -50, -50, 100, 100 ) + + surface.SetMaterial( self.RefuelMat ) + surface.DrawTexturedRect( -50, -50, 100, 100 ) + + draw.SimpleText( Text, "LVS_FONT", 0, 75, IconColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + cam.End3D2D() + end + + function ENT:DrawCable() + local plyL = LocalPlayer() + + if not IsValid( plyL ) then return end + + if plyL:GetPos():DistToSqr( self:GetPos() ) > 350000 then return end + + local pos = self:LocalToWorld( Vector(10,0,45) ) + local ang = self:LocalToWorldAngles( Angle(0,90,90) ) + local ply = self:GetUser() + + local startPos = self:LocalToWorld( Vector(0.06,-17.77,55.48) ) + local p2 = self:LocalToWorld( Vector(8,-17.77,30) ) + local p3 + local endPos + + if IsValid( ply ) then + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + endPos = (attachment.Pos + attachment.Ang:Forward() * -3 + attachment.Ang:Right() * 2 + attachment.Ang:Up() * -3.5) + p3 = endPos + attachment.Ang:Right() * 5 - attachment.Ang:Up() * 20 + else + p3 = self:LocalToWorld( Vector(0,-20,30) ) + endPos = self:LocalToWorld( Vector(0.06,-20.3,37) ) + end + + render.StartBeam( 15 ) + render.SetMaterial( cable ) + + for i = 0,15 do + local pos = bezier(startPos, p2, p3, endPos, i / 14) + + local Col = (render.GetLightColor( pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + render.AddBeam( pos, 1, 0, Color(Col.r,Col.g,Col.b,255) ) + end + + render.EndBeam() + end + + function ENT:OnRemove() + self:RemovePumpEnt() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_shell.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_shell.lua new file mode 100644 index 0000000..428e88d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_shell.lua @@ -0,0 +1,62 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +if SERVER then + ENT.MDL = "models/props_debris/shellcasing_10.mdl" + ENT.CollisionSounds = { + "lvs/vehicles/pak40/shell_impact1.wav", + "lvs/vehicles/pak40/shell_impact2.wav" + } + + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + ENT.LifeTime = 30 + + function ENT:Initialize() + self:SetModel( self.MDL ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:PhysWake() + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + + self.DieTime = CurTime() + self.LifeTime + + timer.Simple( self.LifeTime - 0.5, function() + if not IsValid( self ) then return end + + self:SetRenderFX( kRenderFxFadeFast ) + end) + end + + function ENT:Think() + if self.DieTime < CurTime() then + self:Remove() + end + + self:NextThink( CurTime() + 1 ) + + return true + end + + function ENT:PhysicsCollide( data, physobj ) + if data.Speed > 30 and data.DeltaTime > 0.2 then + self:EmitSound( self.CollisionSounds[ math.random(1,#self.CollisionSounds) ] ) + end + end + + return +end + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_smoke.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_smoke.lua new file mode 100644 index 0000000..964caaf --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_smoke.lua @@ -0,0 +1,196 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Smoke" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminOnly = false + +function ENT:SetupDataTables() + self:NetworkVar( "Bool",0, "Active" ) + + self:NetworkVar( "Float",0, "Radius" ) + + self:NetworkVar( "Float",1, "LifeTime" ) + + if SERVER then + self:SetLifeTime( 30 ) + self:SetRadius( 1000 ) + end +end + +function ENT:GetMins() + local Radius = self:GetRadius() + + return Vector(-Radius,-Radius,-Radius) +end + +function ENT:GetMaxs() + local Radius = self:GetRadius() + + return Vector(Radius,Radius,Radius) +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/Items/grenadeAmmo.mdl" ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + + self.TrailEntity = util.SpriteTrail( self, 0, Color(120,120,120,120), false, 5, 40, 0.2, 1 / ( 15 + 1 ) * 0.5, "trails/smoke" ) + end + + function ENT:Think() + local T = CurTime() + + self:NextThink( T + 1 ) + + if not self.RemoveTime then return true end + + if self.RemoveTime < T then + self:Remove() + end + + return true + end + + function ENT:Enable() + if self:GetActive() then return end + + self.RemoveTime = CurTime() + self:GetLifeTime() + + self:SetActive( true ) + + self:EmitSound("weapons/flaregun/fire.wav", 65, 100, 0.5) + + if IsValid( self.TrailEntity ) then + self.TrailEntity:Remove() + end + end + + function ENT:PhysicsCollide( data, physobj ) + self:Enable() + + if data.Speed > 60 and data.DeltaTime > 0.2 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + if VelDif > 200 then + self:EmitSound( "Grenade.ImpactHard" ) + else + self:EmitSound( "Grenade.ImpactSoft" ) + end + + physobj:SetVelocity( data.OurOldVelocity * 0.5 ) + end + end +else + function ENT:Draw() + self:DrawModel() + end + + function ENT:StartSound() + if self.snd then return self.snd end + + self.snd = CreateSound( self, "weapons/flaregun/burn.wav" ) + self.snd:PlayEx(1,100) + + return self.snd + end + + function ENT:Think() + local T = CurTime() + + if not self:GetActive() then self.DieTime = T + self:GetLifeTime() return end + + local volume = ((self.DieTime or 0) - T) / self:GetLifeTime() + local snd = self:StartSound() + snd:ChangeVolume( volume, 0.5 ) + + self.RemovedEnts = self.RemovedEnts or {} + + local plyPos = LocalPlayer():GetPos() + local pos = self:GetPos() + + if (plyPos - pos):Length() < self:GetRadius() then + for id, ent in pairs( LVS:GetVehicles() ) do + LVS:GetVehicles()[ id ] = nil + table.insert( self.RemovedEnts, ent ) + end + else + local Mins = self:GetMins() + local Maxs = self:GetMaxs() + + for id, ent in pairs( LVS:GetVehicles() ) do + local pDelta = ent:GetPos() - plyPos + local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( plyPos, pDelta, pos, angle_zero, Mins, Maxs ) + + if HitPos then + LVS:GetVehicles()[ id ] = nil + + table.insert( self.RemovedEnts, ent ) + end + end + + for id, ent in pairs( self.RemovedEnts ) do + if not IsValid( ent ) then + self.RemovedEnts[ id ] = nil + + continue + end + + local pDelta = ent:GetPos() - plyPos + local HitPos, HitNormal, Fraction = util.IntersectRayWithOBB( plyPos, pDelta, pos, angle_zero, Mins, Maxs ) + + if not HitPos then + self.RemovedEnts[ id ] = nil + table.insert( LVS:GetVehicles(), ent ) + end + end + end + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + util.Effect( "lvs_defence_smoke", effectdata, true, true ) + + self:SetNextClientThink( T + 0.2 ) + + return true + end + + function ENT:OnRemove() + self.RemovedEnts = self.RemovedEnts or {} + + for id, ent in pairs( self.RemovedEnts ) do + self.RemovedEnts[ id ] = nil + + if not IsValid( ent ) then + continue + end + + table.insert( LVS:GetVehicles(), ent ) + end + + if not self.snd then return end + + self.snd:Stop() + self.snd = nil + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_spikestrip.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_spikestrip.lua new file mode 100644 index 0000000..9d1c03b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_spikestrip.lua @@ -0,0 +1,161 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Spike Strip" +ENT.Author = "Luna" +ENT.Information = "Pops Tires" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SetAttacker( ent ) self._attacker = ent end + function ENT:GetAttacker() return self._attacker or self end + + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:Spawn() + ent:Activate() + ent:SetAttacker( ply ) + + return ent + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Initialize() + self:SetModel( "models/diggercars/shared/spikestrip_static.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetTrigger( true ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + end + + function ENT:UpdateFold() + end + + function ENT:Think() + local PhysObj = self:GetPhysicsObject() + + if IsValid( PhysObj ) and PhysObj:IsMotionEnabled() then + if PhysObj:IsAsleep() then + PhysObj:EnableMotion( false ) + end + end + + self:UpdateFold() + + self:NextThink( CurTime() ) + + return true + end + + function ENT:Use( ply ) + end + + function ENT:OnRemove() + end + + function ENT:PhysicsCollide( data, physobj ) + end + + function ENT:StartTouch( entity ) + end + + function ENT:EndTouch( entity ) + end + + function ENT:Touch( entity ) + if not IsValid( entity ) or entity:GetClass() ~= "lvs_wheeldrive_wheel" then return end + + local Destroy = entity:GetVelocity():Length() > 200 + + if not Destroy then + for i = 1,3 do + local L = self:LookupAttachment( "l"..i ) + local R = self:LookupAttachment( "r"..i ) + + local attL = self:GetAttachment( L ) + local attR = self:GetAttachment( R ) + + if not attL or not attR then continue end + + local trace = util.TraceLine( { + start = attL.Pos, + endpos = attR.Pos, + filter = entity, + whitelist = true, + } ) + + if trace.Hit then + Destroy = true + + break + end + end + end + + if not Destroy then return end + + local T = CurTime() + + if (entity._LastSpikeStripPop or 0) > T then return end + + entity._LastSpikeStripPop = T + 2 + + local dmginfo = DamageInfo() + dmginfo:SetDamage( entity:GetHP() ) + dmginfo:SetAttacker( self:GetAttacker() ) + dmginfo:SetDamageType( DMG_PREVENT_PHYSICS_FORCE ) + + entity:TakeDamageInfo( dmginfo ) + + local base = entity:GetBase() + + if not IsValid( base ) then return end + + local PhysObj = base:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:ApplyTorqueCenter( Vector(0,0, PhysObj:GetVelocity():Length() * 100 * (PhysObj:GetAngleVelocity().z > 0 and 1 or -1) ) ) + end + + return +end + +function ENT:Think() +end + +function ENT:Draw( flags ) + self:DrawModel( flags ) +end + +local spritemat = Material( "sprites/light_glow02_add" ) +local spritecolor = Color( 255, 93, 0, 255) + +function ENT:DrawTranslucent( flags ) + self:DrawModel( flags ) + + local size1 = 16 * (math.abs( math.cos( CurTime() * 4 ) ) ^ 10) + local size2 = 16 * (math.abs( math.sin( CurTime() * 4 ) ) ^ 10) + + render.SetMaterial( spritemat ) + render.DrawSprite( self:LocalToWorld( Vector(8,-116.5,10) ), size1, size1, spritecolor ) + render.DrawSprite( self:LocalToWorld( Vector(-7,-116.5,10) ), size2, size2, spritecolor ) + render.DrawSprite( self:LocalToWorld( Vector(8,118,10) ), size2, size2, spritecolor ) + render.DrawSprite( self:LocalToWorld( Vector(-7,118,10) ), size1, size1, spritecolor ) +end + +function ENT:OnRemove() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_spikestrip_foldable.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_spikestrip_foldable.lua new file mode 100644 index 0000000..562bd17 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_spikestrip_foldable.lua @@ -0,0 +1,72 @@ +AddCSLuaFile() + +ENT.Base = "lvs_item_spikestrip" + +ENT.AutomaticFrameAdvance = true + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/diggercars/shared/spikestrip_fold.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetTrigger( true ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + self:SetUseType( SIMPLE_USE ) + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:EnableDrag( false ) + end + + function ENT:UpdateFold() + if not self._StartFold then return end + + if not self._poseValue then + self._poseValue = 0 + + self:EmitSound("buttons/lever4.wav") + end + + if self._poseValue >= 1 then return end + + self._poseValue = math.min( self._poseValue + FrameTime(), 1 ) + + self:SetPoseParameter( "fold", self._poseValue ) + end + + function ENT:PhysicsCollide( data, physobj ) + self._StartFold = true + end + + function ENT:Use( ply ) + if not IsValid( ply ) or not ply:IsPlayer() then return end + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + if PhysObj:IsMotionEnabled() then return end + + if ply:HasWeapon("weapon_lvsspikestrip") then return end + + ply:EmitSound("items/ammo_pickup.wav") + ply:Give("weapon_lvsspikestrip") + ply:SelectWeapon("weapon_lvsspikestrip") + + self:Remove() + end + + return +end + +function ENT:Draw( flags ) + self:DrawModel( flags ) +end + +function ENT:DrawTranslucent( flags ) + self:DrawModel( flags ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_transmission_automatic.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_transmission_automatic.lua new file mode 100644 index 0000000..21499cd --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_transmission_automatic.lua @@ -0,0 +1,64 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Transmission - Automatic" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/diggercars/auto.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:Think() + return false + end + + function ENT:PhysicsCollide( data ) + if self.MarkForRemove then return end + + local ent = data.HitEntity + + if not IsValid( ent ) or not ent.LVS or not isfunction( ent.DisableManualTransmission ) then return end + + if isfunction( ent.IsManualTransmission ) and not ent:IsManualTransmission() then return end + + if ent:DisableManualTransmission() ~= false then + ent:EmitSound("npc/dog/dog_rollover_servos1.wav") + + self.MarkForRemove = true + + ent:DisableManualTransmission() + + SafeRemoveEntityDelayed( self, 0 ) + end + end + + function ENT:OnTakeDamage( dmginfo ) + end + +else + function ENT:Draw( flags ) + self:DrawModel( flags ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_transmission_manual.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_transmission_manual.lua new file mode 100644 index 0000000..1766fb5 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_transmission_manual.lua @@ -0,0 +1,62 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Transmission - Manual" +ENT.Author = "Luna" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.PhysicsSounds = true + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/diggercars/manual.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:Think() + return false + end + + function ENT:PhysicsCollide( data ) + if self.MarkForRemove then return end + + local ent = data.HitEntity + + if not IsValid( ent ) or not ent.LVS or not isfunction( ent.EnableManualTransmission ) then return end + + if isfunction( ent.IsManualTransmission ) and ent:IsManualTransmission() then return end + + if ent:EnableManualTransmission() ~= false then + ent:EmitSound("npc/dog/dog_rollover_servos1.wav") + + self.MarkForRemove = true + + SafeRemoveEntityDelayed( self, 0 ) + end + end + + function ENT:OnTakeDamage( dmginfo ) + end + +else + function ENT:Draw( flags ) + self:DrawModel( flags ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_item_turbo.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_item_turbo.lua new file mode 100644 index 0000000..3c1baee --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_item_turbo.lua @@ -0,0 +1,168 @@ +AddCSLuaFile() + +ENT.Base = "lvs_wheeldrive_engine_mod" + +ENT.PrintName = "Turbo" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" +ENT.Information = "Edit Properties to change Torque and Power Curve" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +if SERVER then + function ENT:Initialize() + self:SetModel("models/diggercars/dodge_charger/turbo.mdl") + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:PhysWake() + end + + function ENT:OnLinked( ent ) + ent:OnTurboCharged( true ) + ent:SetTurbo( self ) + + if not self.PlaySound then return end + + ent:EmitSound("lvs/equip_turbo.ogg") + end + + function ENT:OnUnLinked( ent ) + ent:OnTurboCharged( false ) + + if not duplicator or not duplicator.ClearEntityModifier then return end + + duplicator.ClearEntityModifier( ent, "lvsCarTurbo" ) + end + + function ENT:CanLink( ent ) + if not ent.AllowTurbo or IsValid( ent:GetTurbo() ) then return false end + + return true + end + + local function SaveTurbo( ply, ent, data ) + if not duplicator or not duplicator.StoreEntityModifier then return end + + timer.Simple( 0.2, function() + if not IsValid( ent ) or not isfunction( ent.AddTurboCharger ) then return end + + local turbo = ent:AddTurboCharger() + if IsValid( turbo ) then + if data.Curve then turbo:SetEngineCurve( data.Curve ) end + if data.Torque then turbo:SetEngineTorque( data.Torque ) end + end + end ) + + duplicator.StoreEntityModifier( ent, "lvsCarTurbo", data ) + end + + if duplicator and duplicator.RegisterEntityModifier then + duplicator.RegisterEntityModifier( "lvsCarTurbo", SaveTurbo ) + end + + function ENT:OnVehicleUpdated( ent ) + if not duplicator or not duplicator.ClearEntityModifier or not duplicator.StoreEntityModifier then return end + + duplicator.ClearEntityModifier( ent, "lvsCarTurbo" ) + local data = { + Curve = self:GetEngineCurve(), + Torque = self:GetEngineTorque(), + } + duplicator.StoreEntityModifier( ent, "lvsCarTurbo", data ) + end + + return +end + +function ENT:OnEngineActiveChanged( Active, soundname ) + if Active then + self:StartSounds( soundname ) + else + self:StopSounds() + end +end + +function ENT:StartSounds( soundname ) + if self.snd then return end + + self.snd = CreateSound( self, soundname ) + self.snd:PlayEx(0,100) +end + +function ENT:StopSounds() + if not self.snd then return end + + self.snd:Stop() + self.snd = nil +end + +function ENT:HandleSounds( vehicle, engine ) + if not self.snd then return end + + if not self.TurboRPM then + self.TurboRPM = 0 + end + + local FT = FrameTime() + + local throttle = engine:GetClutch() and 0 or vehicle:GetThrottle() + + local volume = math.Clamp(((self.TurboRPM - 300) / 300),0,1) * vehicle.TurboVolume + local pitch = math.min(self.TurboRPM / 3,150) + + if throttle == 0 and (self.TurboRPM > 350) then + if istable( vehicle.TurboBlowOff ) then + self:EmitSound( vehicle.TurboBlowOff[ math.random( 1, #vehicle.TurboBlowOff ) ], 75, 100, volume * LVS.EngineVolume ) + else + self:EmitSound( vehicle.TurboBlowOff, 75, 100, volume * LVS.EngineVolume ) + end + self.TurboRPM = 0 + end + + local rpm = engine:GetRPM() + local maxRPM = vehicle.EngineMaxRPM + + local ply = LocalPlayer() + local doppler = vehicle:CalcDoppler( ply ) + + self.TurboRPM = self.TurboRPM + math.Clamp(math.min(rpm / maxRPM,1) * 600 * (0.75 + 0.25 * throttle) - self.TurboRPM,-100 * FT,500 * FT) + + self._smBoost = self._smBoost and self._smBoost + (math.min( (self.TurboRPM or 0) / 400, 1 ) - self._smBoost) * FT * 10 or 0 + + self.snd:ChangeVolume( volume * engine:GetEngineVolume() ) + self.snd:ChangePitch( pitch * doppler ) +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + local EngineActive = vehicle:GetEngineActive() + + if self._oldEnActive ~= EngineActive then + self._oldEnActive = EngineActive + + self:OnEngineActiveChanged( EngineActive, vehicle.TurboSound ) + end + + if EngineActive then + local engine = vehicle:GetEngine() + + if not IsValid( engine ) then return end + + self:HandleSounds( vehicle, engine ) + end +end + +function ENT:OnRemove() + self:StopSounds() +end + +function ENT:Draw( flags ) + if IsValid( self:GetBase() ) then return end + + self:DrawModel( flags ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_missile.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_missile.lua new file mode 100644 index 0000000..d08045c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_missile.lua @@ -0,0 +1,432 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Missile" +ENT.Author = "Luna" +ENT.Information = "LVS Missile" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = true + +ENT.ExplosionEffect = "lvs_explosion_small" + +ENT.lvsProjectile = true +ENT.VJ_ID_Danger = true + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Active" ) + self:NetworkVar( "Entity", 0, "NWTarget" ) +end + +if SERVER then + util.AddNetworkString( "lvs_missile_hud" ) + + function ENT:GetAvailableTargets() + local targets = { + [1] = player.GetAll(), + [2] = LVS:GetVehicles(), + [3] = LVS:GetNPCs(), + } + + return targets + end + + function ENT:FindTarget( pos, forward, cone_ang, cone_len ) + local targets = self:GetAvailableTargets() + + local Attacker = self:GetAttacker() + local Parent = self:GetParent() + local Owner = self:GetOwner() + local Target = NULL + local DistToTarget = 0 + + for _, tbl in ipairs( targets ) do + for _, ent in pairs( tbl ) do + if not IsValid( ent ) or ent == Parent or ent == Owner or Target == ent or Attacker == ent then continue end + + local pos_ent = ent:GetPos() + local dir = (pos_ent - pos):GetNormalized() + local ang = math.deg( math.acos( math.Clamp( forward:Dot( dir ) ,-1,1) ) ) + + if ang > cone_ang then continue end + + local dist, _, _ = util.DistanceToLine( pos, pos + forward * cone_len, pos_ent ) + + if not IsValid( Target ) then + Target = ent + DistToTarget = dist + + continue + end + + if dist < DistToTarget then + Target = ent + DistToTarget = dist + end + end + end + + self:SetTarget( Target ) + + local ply = self:GetAttacker() + + if not IsValid( ply ) or not ply:IsPlayer() then return end + + net.Start( "lvs_missile_hud", true ) + net.WriteEntity( self ) + net.Send( ply ) + end + + function ENT:SetEntityFilter( filter ) + if not istable( filter ) then return end + + self._FilterEnts = {} + + for _, ent in pairs( filter ) do + self._FilterEnts[ ent ] = true + end + end + function ENT:SetTarget( ent ) self:SetNWTarget( ent ) end + function ENT:SetDamage( num ) self._dmg = num end + function ENT:SetForce( num ) self._force = num end + function ENT:SetThrust( num ) self._thrust = num end + function ENT:SetSpeed( num ) self._speed = num end + function ENT:SetTurnSpeed( num ) self._turnspeed = num end + function ENT:SetRadius( num ) self._radius = num end + function ENT:SetAttacker( ent ) self._attacker = ent end + + function ENT:GetAttacker() return self._attacker or NULL end + function ENT:GetDamage() return (self._dmg or 100) end + function ENT:GetForce() return (self._force or 4000) end + function ENT:GetRadius() return (self._radius or 250) end + function ENT:GetSpeed() return (self._speed or 4000) end + function ENT:GetTurnSpeed() return (self._turnspeed or 1) * 100 end + function ENT:GetThrust() return (self._thrust or 500) end + function ENT:GetTarget() + if IsValid( self:GetNWTarget() ) then + local Pos = self:GetPos() + local tPos = self:GetTargetPos() + + local Sub = tPos - Pos + local Len = Sub:Length() + local Dir = Sub:GetNormalized() + local Forward = self:GetForward() + + local AngToTarget = math.deg( math.acos( math.Clamp( Forward:Dot( Dir ) ,-1,1) ) ) + + local LooseAng = math.min( Len / 100, 90 ) + + if AngToTarget > LooseAng then + self:SetNWTarget( NULL ) + end + end + + return self:GetNWTarget() + end + function ENT:GetTargetPos() + local Target = self:GetNWTarget() + + if not IsValid( Target ) then return Vector(0,0,0) end + + if isfunction( Target.GetMissileOffset ) then + return Target:LocalToWorld( Target:GetMissileOffset() ) + end + + return Target:GetPos() + end + + function ENT:SpawnFunction( ply, tr, ClassName ) + + local ent = ents.Create( ClassName ) + ent:SetPos( ply:GetShootPos() ) + ent:SetAngles( ply:EyeAngles() ) + ent:Spawn() + ent:Activate() + ent:SetAttacker( ply ) + ent:Enable() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/weapons/w_missile_launch.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + end + + function ENT:Enable() + if self.IsEnabled then return end + + local Parent = self:GetParent() + + if IsValid( Parent ) then + self:SetOwner( Parent ) + self:SetParent( NULL ) + end + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetCollisionGroup( COLLISION_GROUP_NONE ) + self:PhysWake() + + self.IsEnabled = true + + local pObj = self:GetPhysicsObject() + + if not IsValid( pObj ) then + self:Remove() + + print("LVS: missing model. Missile terminated.") + + return + end + + pObj:SetMass( 1 ) + pObj:EnableGravity( false ) + pObj:EnableMotion( true ) + pObj:EnableDrag( false ) + + self:SetTrigger( true ) + + self:StartMotionController() + + self:PhysWake() + + self.SpawnTime = CurTime() + + self:SetActive( true ) + end + + function ENT:PhysicsSimulate( phys, deltatime ) + phys:Wake() + + local Thrust = self:GetThrust() + local Speed = self:GetSpeed() + local Pos = self:GetPos() + local velL = self:WorldToLocal( Pos + self:GetVelocity() ) + + local ForceLinear = (Vector( Speed * Thrust,0,0) - velL) * deltatime + + local Target = self:GetTarget() + + if not IsValid( Target ) then + return (-phys:GetAngleVelocity() * 250 * deltatime), ForceLinear, SIM_LOCAL_ACCELERATION + end + + local AngForce = -self:WorldToLocalAngles( (self:GetTargetPos() - Pos):Angle() ) + + local ForceAngle = (Vector(AngForce.r,-AngForce.p,-AngForce.y) * self:GetTurnSpeed() - phys:GetAngleVelocity() * 5 ) * 250 * deltatime + + return ForceAngle, ForceLinear, SIM_LOCAL_ACCELERATION + end + + function ENT:Think() + local T = CurTime() + + self:NextThink( T + 1 ) + + if not self.SpawnTime then return true end + + if (self.SpawnTime + 12) < T then + self:Detonate() + end + + return true + end + + ENT.IgnoreCollisionGroup = { + [COLLISION_GROUP_NONE] = true, + [COLLISION_GROUP_WORLD] = true, + [COLLISION_GROUP_INTERACTIVE_DEBRIS] = true, + } + + function ENT:StartTouch( entity ) + if entity == self:GetAttacker() then return end + + if istable( self._FilterEnts ) and self._FilterEnts[ entity ] then return end + + if entity.GetCollisionGroup and self.IgnoreCollisionGroup[ entity:GetCollisionGroup() ] then return end + + if entity.lvsProjectile then return end + + self:Detonate( entity ) + end + + function ENT:EndTouch( entity ) + end + + function ENT:Touch( entity ) + end + + function ENT:PhysicsCollide( data ) + if istable( self._FilterEnts ) and self._FilterEnts[ data.HitEntity ] then return end + + self:Detonate( data.HitEntity ) + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Detonate( target ) + if not self.IsEnabled or self.IsDetonated then return end + + self.IsDetonated = true + + local Pos = self:GetPos() + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + util.Effect( self.ExplosionEffect, effectdata, true, true ) + + local attacker = self:GetAttacker() + + LVS:BlastDamage( Pos, self:GetForward(), IsValid( attacker ) and attacker or game.GetWorld(), self, self:GetDamage(), DMG_BLAST, self:GetRadius(), self:GetForce() ) + + SafeRemoveEntityDelayed( self, FrameTime() ) + end +else + function ENT:Initialize() + end + + function ENT:Enable() + if self.IsEnabled then return end + + self.IsEnabled = true + + self.snd = CreateSound(self, "weapons/rpg/rocket1.wav") + self.snd:SetSoundLevel( 80 ) + self.snd:Play() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( self ) + util.Effect( "lvs_missiletrail", effectdata ) + end + + function ENT:CalcDoppler() + local Ent = LocalPlayer() + + local ViewEnt = Ent:GetViewEntity() + + if Ent:lvsGetVehicle() == self then + if ViewEnt == Ent then + Ent = self + else + Ent = ViewEnt + end + else + Ent = ViewEnt + end + + local sVel = self:GetVelocity() + local oVel = Ent:GetVelocity() + + local SubVel = oVel - sVel + local SubPos = self:GetPos() - Ent:GetPos() + + local DirPos = SubPos:GetNormalized() + local DirVel = SubVel:GetNormalized() + + local A = math.acos( math.Clamp( DirVel:Dot( DirPos ) ,-1,1) ) + + return (1 + math.cos( A ) * SubVel:Length() / 13503.9) + end + + function ENT:Draw() + if not self:GetActive() then return end + + self:DrawModel() + end + + function ENT:Think() + if self.snd then + self.snd:ChangePitch( 100 * self:CalcDoppler() ) + end + + if self.IsEnabled then return end + + if self:GetActive() then + self:Enable() + end + end + + function ENT:SoundStop() + if self.snd then + self.snd:Stop() + end + end + + function ENT:OnRemove() + self:SoundStop() + end + + local function DrawDiamond( X, Y, radius, angoffset ) + angoffset = angoffset or 0 + + local segmentdist = 90 + local radius2 = radius + 1 + + for ang = 0, 360, segmentdist do + local a = ang + angoffset + surface.DrawLine( X + math.cos( math.rad( a ) ) * radius, Y - math.sin( math.rad( a ) ) * radius, X + math.cos( math.rad( a + segmentdist ) ) * radius, Y - math.sin( math.rad( a + segmentdist ) ) * radius ) + surface.DrawLine( X + math.cos( math.rad( a ) ) * radius2, Y - math.sin( math.rad( a ) ) * radius2, X + math.cos( math.rad( a + segmentdist ) ) * radius2, Y - math.sin( math.rad( a + segmentdist ) ) * radius2 ) + end + end + + local color_red = Color(255,0,0,255) + local HudTargets = {} + hook.Add( "HUDPaint", "!!!!lvs_missile_hud", function() + local T = CurTime() + + local Index = 0 + + surface.SetDrawColor( 255, 0, 0, 255 ) + + for ID, _ in pairs( HudTargets ) do + local Missile = Entity( ID ) + + if not IsValid( Missile ) then + HudTargets[ ID ] = nil + + continue + end + + local Target = Missile:GetNWTarget() + + if not IsValid( Target ) then + HudTargets[ ID ] = nil + + continue + end + + local MissilePos = Missile:GetPos():ToScreen() + local TargetPos = Target:LocalToWorld( Target:OBBCenter() ):ToScreen() + + Index = Index + 1 + + if not TargetPos.visible then continue end + + DrawDiamond( TargetPos.x, TargetPos.y, 40, ID * 1337 - T * 100 ) + + draw.DrawText("LOCK", "LVS_FONT", TargetPos.x + 20, TargetPos.y + 20, color_red, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + + if not MissilePos.visible then continue end + + DrawDiamond( MissilePos.x, MissilePos.y, 16, ID * 1337 - T * 100 ) + draw.DrawText( Index, "LVS_FONT", MissilePos.x + 10, MissilePos.y + 10, color_red, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + + surface.DrawLine( MissilePos.x, MissilePos.y, TargetPos.x, TargetPos.y ) + end + end ) + + net.Receive( "lvs_missile_hud", function( len ) + local ent = net.ReadEntity() + + if not IsValid( ent ) then return end + + HudTargets[ ent:EntIndex() ] = true + end ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_powerboat_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_powerboat_engine.lua new file mode 100644 index 0000000..a77814e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_powerboat_engine.lua @@ -0,0 +1,332 @@ +AddCSLuaFile() + +ENT.Base = "lvs_wheeldrive_engine" +DEFINE_BASECLASS( "lvs_wheeldrive_engine" ) + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +ENT.RenderGroup = RENDERGROUP_BOTH + +if SERVER then + function ENT:Think() + return false + end + + return +end + +ENT._oldEnActive = false +ENT._ActiveSounds = {} + +function ENT:StopSounds() + for id, sound in pairs( self._ActiveSounds ) do + if istable( sound ) then + for _, snd in pairs( sound ) do + if snd then + snd:Stop() + end + end + else + sound:Stop() + end + self._ActiveSounds[ id ] = nil + end +end + +function ENT:HandleEngineSounds( vehicle ) + local ply = LocalPlayer() + local pod = ply:GetVehicle() + local Throttle = (vehicle:GetThrottle() - vehicle:GetThrustStrenght() * vehicle:GetThrottle() * 0.5) + vehicle:GetBrake() + local Doppler = vehicle:CalcDoppler( ply ) + + local DrivingMe = ply:lvsGetVehicle() == vehicle + + local VolumeSetNow = false + + local FirstPerson = false + if IsValid( pod ) then + local ThirdPerson = pod:GetThirdPersonMode() + + if ThirdPerson ~= self._lvsoldTP then + self._lvsoldTP = ThirdPerson + VolumeSetNow = DrivingMe + end + + FirstPerson = DrivingMe and not ThirdPerson + end + + if DrivingMe ~= self._lvsoldDrivingMe then + self._lvsoldDrivingMe = DrivingMe + + self:StopSounds() + + self._oldEnActive = nil + + return + end + + local FT = RealFrameTime() + + self._smTHR = self._smTHR and self._smTHR + (Throttle - self._smTHR) * FT or 0 + + local HasActiveSoundEmitters = false + + if DrivingMe then + HasActiveSoundEmitters = vehicle:HasActiveSoundEmitters() + end + + local rpmSet = false + + for id, sound in pairs( self._ActiveSounds ) do + if not sound then continue end + + local data = self.EngineSounds[ id ] + + local Pitch = math.Clamp( data.Pitch + self._smTHR * data.PitchMul, data.PitchMin, data.PitchMax ) + local PitchMul = data.UseDoppler and Doppler or 1 + + local InActive = self._smTHR > data.FadeOut or self._smTHR < data.FadeIn + if data.FadeOut >= 1 and self._smTHR > 1 then + InActive = false + end + + local Volume = InActive and 0 or LVS.EngineVolume + + if data.VolumeMin and data.VolumeMax and not InActive then + Volume = math.max(self._smTHR - data.VolumeMin,0) / (1 - data.VolumeMin) * data.VolumeMax * LVS.EngineVolume + end + + if istable( sound ) then + sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + if sound.int then sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), 0.2 ) end + + local fadespeed = VolumeSetNow and 0 or data.FadeSpeed + + if FirstPerson then + sound.ext:ChangeVolume( 0, 0 ) + + if HasActiveSoundEmitters then + Volume = Volume * 0.25 + fadespeed = fadespeed * 0.5 + end + + if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end + else + if HasActiveSoundEmitters then + Volume = Volume * 0.75 + fadespeed = fadespeed * 0.5 + end + + sound.ext:ChangeVolume( Volume, fadespeed ) + + if sound.int then sound.int:ChangeVolume( 0, 0 ) end + end + else + if data.FadeInRestart then + if Volume == 0 then + if sound:GetVolume() == 0 and sound:IsPlaying() then + sound:Stop() + else + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + sound:ChangeVolume( Volume, data.FadeSpeed ) + end + else + if sound:IsPlaying() then + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + sound:ChangeVolume( Volume, data.FadeSpeed ) + else + sound:PlayEx( Volume, math.Clamp( Pitch * PitchMul, 0, 255 ) ) + end + end + else + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + sound:ChangeVolume( Volume, data.FadeSpeed ) + end + + if not rpmSet then + rpmSet = true + + self:SetRPM( vehicle.EngineIdleRPM + ((sound:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) ) + end + end + end +end + +function ENT:OnEngineActiveChanged( Active ) + if not Active then self:StopSounds() return end + + local ply = LocalPlayer() + local ViewPos = ply:GetViewEntity():GetPos() + local veh = ply:lvsGetVehicle() + + local Base = self:GetBase() + + for id, data in pairs( self.EngineSounds ) do + if not isstring( data.sound ) then continue end + + self.EngineSounds[ id ].Pitch = data.Pitch or 80 + self.EngineSounds[ id ].PitchMin = data.PitchMin or 0 + self.EngineSounds[ id ].PitchMax = data.PitchMax or 255 + self.EngineSounds[ id ].PitchMul = data.PitchMul or 100 + self.EngineSounds[ id ].UseDoppler = data.UseDoppler ~= false + self.EngineSounds[ id ].FadeIn = data.FadeIn or 0 + self.EngineSounds[ id ].FadeOut = data.FadeOut or 1 + self.EngineSounds[ id ].FadeSpeed = data.FadeSpeed or 1.5 + self.EngineSounds[ id ].SoundLevel = data.SoundLevel or 85 + + if data.sound_int and data.sound_int ~= data.sound then + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + if data.sound_int == "" then + self._ActiveSounds[ id ] = { + ext = sound, + int = false, + } + else + local sound_interior = CreateSound( self, data.sound_int ) + sound_interior:SetSoundLevel( data.SoundLevel ) + sound_interior:PlayEx(0,100) + + self._ActiveSounds[ id ] = { + ext = sound, + int = sound_interior, + } + end + else + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + self._ActiveSounds[ id ] = sound + end + end +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + self:DamageFX( vehicle ) + self:EngineFX( vehicle ) + + if not self.EngineSounds then + self.EngineSounds = vehicle.EngineSounds + + return + end + + local EngineActive = vehicle:GetEngineActive() + + if self._oldEnActive ~= EngineActive then + self._oldEnActive = EngineActive + self:OnEngineActiveChanged( EngineActive ) + end + + if EngineActive then + self:HandleEngineSounds( vehicle ) + self:ExhaustFX( vehicle ) + else + self._smTHR = 0 + end +end + +function ENT:EngineFX( vehicle ) + if not vehicle:GetEngineActive() then return end + + local EntTable = vehicle:GetTable() + + if not EntTable.EngineSplash then return end + + local T = CurTime() + + if (EntTable.nextPropFX or 0) > T then return end + + local throttle = (self:GetRPM() / EntTable.EngineMaxRPM) + + EntTable.nextPropFX = T + math.max(0.2 - throttle,0.05) + + local startpos = self:GetPos() + local endpos = self:LocalToWorld( Vector(0,0,-EntTable.EngineSplashDistance) ) + + local traceWater = util.TraceLine( { + start = startpos, + endpos = endpos, + mask = MASK_WATER, + filter = vehicle:GetCrosshairFilterEnts() + } ) + + if not traceWater.Hit then return end + + local pos = traceWater.HitPos + + local emitter = vehicle:GetParticleEmitter( pos ) + + if not IsValid( emitter ) then return end + + local LightColor = render.GetLightColor( pos ) + local VecCol = Vector(1,1.2,1.4) * (0.06 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 + VecCol.x = math.min( VecCol.x, 255 ) + VecCol.y = math.min( VecCol.y, 255 ) + VecCol.z = math.min( VecCol.z, 255 ) + + local particle = emitter:Add( "effects/splash4", pos ) + + local dir = self:LocalToWorldAngles( Angle(EntTable.EngineSplashThrowAngle,180 - vehicle:GetSteer() * 30,0) ):Forward() + + local vel = (VectorRand() * EntTable.EngineSplashVelocityRandomAdd + dir * EntTable.EngineSplashVelocity) * throttle + + particle:SetVelocity( vel ) + particle:SetDieTime( 1 ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 255 ) + + particle:SetStartSize( EntTable.EngineSplashStartSize * throttle ) + particle:SetEndSize( EntTable.EngineSplashEndSize * throttle ) + + particle:SetRoll( math.Rand(-5,5) ) + particle:SetColor(VecCol.r,VecCol.g,VecCol.b) + particle:SetGravity( Vector(0,0,-600) ) + particle:SetCollide( false ) + particle:SetNextThink( T ) + particle:SetThinkFunction( function( p ) + p:SetNextThink( CurTime() ) + + local fxpos = p:GetPos() + + if fxpos.z > pos.z then return end + + p:SetDieTime( 0 ) + + local startpos = Vector(fxpos.x,fxpos.y,pos.z + 1) + + if not IsValid( vehicle ) then return end + + local emitter3D = vehicle:GetParticleEmitter3D( vehicle:GetPos() ) + + if not IsValid( emitter3D ) then return end + + local particle = emitter3D:Add("effects/splashwake1", startpos ) + + if not particle then return end + + local scale = math.Rand(0.5,2) + local size = p:GetEndSize() + local vsize = Vector(size,size,size) + + particle:SetStartSize( size * scale * 0.5 ) + particle:SetEndSize( size * scale ) + particle:SetDieTime( math.Rand(0.5,1) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetPos( startpos ) + particle:SetAngles( Angle(-90,math.Rand(-180,180),0) ) + particle:SetColor(VecCol.r,VecCol.g,VecCol.b) + end ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_protontorpedo.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_protontorpedo.lua new file mode 100644 index 0000000..3d349fe --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_protontorpedo.lua @@ -0,0 +1,62 @@ +AddCSLuaFile() + +ENT.Base = "lvs_missile" + +ENT.Type = "anim" + +ENT.PrintName = "Proton Torpedo" +ENT.Author = "Luna" +ENT.Information = "geht ab wie'n zpfchen" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = true + +ENT.ExplosionEffect = "lvs_proton_explosion" +ENT.GlowColor = Color( 0, 127, 255, 255 ) + +if SERVER then + function ENT:GetDamage() return + (self._dmg or 400) + end + + function ENT:GetRadius() + return (self._radius or 150) + end + + return +end + +ENT.GlowMat = Material( "sprites/light_glow02_add" ) + +function ENT:Enable() + if self.IsEnabled then return end + + self.IsEnabled = true + + self.snd = CreateSound(self, "npc/combine_gunship/gunship_crashing1.wav") + self.snd:SetSoundLevel( 80 ) + self.snd:Play() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( self ) + util.Effect( "lvs_proton_trail", effectdata ) +end + +function ENT:Draw() + if not self:GetActive() then return end + + self:DrawModel() + + render.SetMaterial( self.GlowMat ) + + local pos = self:GetPos() + local dir = self:GetForward() + + for i = 0, 30 do + local Size = ((30 - i) / 30) ^ 2 * 128 + + render.DrawSprite( pos - dir * i * 7, Size, Size, self.GlowColor ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_soundemitter.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_soundemitter.lua new file mode 100644 index 0000000..2d8921e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_soundemitter.lua @@ -0,0 +1,218 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + + self:NetworkVar( "Bool",0, "Active" ) + self:NetworkVar( "Bool",1, "ActiveVisible" ) + self:NetworkVar( "Bool",2, "Doppler" ) + + self:NetworkVar( "String",1, "Sound") + self:NetworkVar( "String",2, "SoundInterior") + + self:NetworkVar( "Int",0, "SoundLevel" ) + + if SERVER then + self:SetSoundLevel( 110 ) + self:SetActiveVisible( true ) + end +end + +if SERVER then + util.AddNetworkString( "lvs_soundemitter_playonce" ) + + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 50, 5, Color( 150, 150, 150 ) ) + end + + function ENT:Think() + return false + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + function ENT:PlayOnce( pitch, volume ) + net.Start( "lvs_soundemitter_playonce", true ) + net.WriteEntity( self ) + net.WriteInt( pitch or 100, 9 ) + net.WriteFloat( volume or 1 ) + net.SendPVS( self:GetPos() ) + end + + function ENT:Play() + self:SetActive( true ) + end + + function ENT:Stop() + self:SetActive( false ) + end + + return +end + +net.Receive( "lvs_soundemitter_playonce", function( len ) + local ent = net.ReadEntity() + + if not IsValid( ent ) or not ent.PlayOnce then return end + + ent:PlayOnce( net.ReadInt( 9 ), net.ReadFloat() ) +end ) + +function ENT:PlayOnce( pitch, volume ) + local ply = LocalPlayer() + local veh = ply:lvsGetVehicle() + + local snd = self:GetSound() + local snd_int = self:GetSoundInterior() + + if snd == snd_int then self:EmitSound( snd, self:GetSoundLevel(), pitch, volume, CHAN_WEAPON ) return end + + if IsValid( veh ) and veh == self:GetBase() and ply:GetViewEntity() == ply then + local pod = ply:GetVehicle() + + if IsValid( pod ) then + if pod:GetThirdPersonMode() then + self:EmitSound( snd, self:GetSoundLevel(), pitch, volume, CHAN_WEAPON ) + else + self:EmitSound( snd_int, self:GetSoundLevel(), pitch, volume, CHAN_WEAPON ) + end + else + self:EmitSound( snd, self:GetSoundLevel(), pitch, volume, CHAN_WEAPON ) + end + else + self:EmitSound( snd, self:GetSoundLevel(), pitch, volume, CHAN_WEAPON ) + end +end + +function ENT:Initialize() +end + +function ENT:RemoveSounds() + if self.snd then + self.snd:Stop() + self.snd = nil + end + + if self.snd_int then + self.snd_int:Stop() + self.snd_int = nil + end +end + +function ENT:HandleSounds() + local ply = LocalPlayer() + local veh = ply:lvsGetVehicle() + local base = self:GetBase() + + if self:GetDoppler() and IsValid( base ) then + local Doppler = base:CalcDoppler( ply ) + + if self.snd then self.snd:ChangePitch( 100 * Doppler, 0.5 ) end + if self.snd_int then self.snd_int:ChangePitch( 100 * Doppler, 0.5 ) end + end + + if not self.snd_int then return end + + if IsValid( veh ) and veh == base and ply:GetViewEntity() == ply then + local pod = ply:GetVehicle() + + if IsValid( pod ) then + if pod:GetThirdPersonMode() then + if self.snd then self.snd:ChangeVolume( 1 ) end + if self.snd_int then self.snd_int:ChangeVolume( 0 ) end + else + if self.snd then self.snd:ChangeVolume( 0 ) end + if self.snd_int then self.snd_int:ChangeVolume( 1 ) end + end + else + if self.snd then self.snd:ChangeVolume( 1 ) end + if self.snd_int then self.snd_int:ChangeVolume( 0 ) end + end + else + if self.snd then self.snd:ChangeVolume( 1 ) end + if self.snd_int then self.snd_int:ChangeVolume( 0 ) end + end +end + +function ENT:StartSounds() + local snd = self:GetSound() + local snd_int = self:GetSoundInterior() + + if snd ~= "" then + self.snd = CreateSound( self, snd ) + self.snd:SetSoundLevel( self:GetSoundLevel() ) + self.snd:PlayEx(0,100) + end + + if snd == snd_int or snd_int == "" or LocalPlayer():lvsGetVehicle() ~= self:GetBase() then + if self.snd then self.snd:ChangeVolume( 1, 0 ) end + + return + end + + self.snd_int = CreateSound( self, snd_int ) + self.snd_int:SetSoundLevel( self:GetSoundLevel() ) + self.snd_int:PlayEx(0,100) +end + +function ENT:StopSounds() + self.NextActive = CurTime() + 0.12 + + if self.snd then + self.snd:ChangeVolume( 0, 0.1 ) + end + + if self.snd_int then + self.snd_int:ChangeVolume( 0, 0.1 ) + end + + timer.Simple(0.11, function() + if not IsValid( self ) then return end + self:RemoveSounds() + end) +end + +function ENT:OnActiveChanged( Active ) + if Active then + self:StartSounds() + else + self:StopSounds() + end +end + +ENT._oldActive = false +function ENT:Think() + local Active = self:GetActive() and (self.NextActive or 0) < CurTime() + + if self._oldActive ~= Active then + self._oldActive = Active + self:OnActiveChanged( Active ) + end + + if Active then + self:HandleSounds() + end +end + +function ENT:OnRemove() + self:RemoveSounds() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_starfighter_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_starfighter_engine.lua new file mode 100644 index 0000000..5833086 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_starfighter_engine.lua @@ -0,0 +1,125 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Float",1, "HP" ) + self:NetworkVar( "Float",2, "MaxHP" ) + + self:NetworkVar( "Bool",0, "Destroyed" ) + + if SERVER then + self:SetMaxHP( 100 ) + self:SetHP( 100 ) + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 50, 5, Color( 0, 255, 255 ) ) + end + + function ENT:Think() + local T = CurTime() + local vehicle = self:GetBase() + + if not self:GetDestroyed() or not IsValid( vehicle ) or not vehicle:GetEngineActive() then self:NextThink( T + 1 ) return true end + + local PhysObj = vehicle:GetPhysicsObject() + + local Pos = self:GetPos() + local Len = vehicle:WorldToLocal( Pos ):Length() + + PhysObj:ApplyForceOffset( -vehicle:GetVelocity() * (PhysObj:GetMass() / Len) * FrameTime() * 50, Pos ) + + self:NextThink( T ) + + return true + end + + function ENT:OnTakeDamage( dmginfo ) + if self:GetDestroyed() then return end + + local Damage = dmginfo:GetDamage() + + if Damage <= 0 then return end + + local CurHealth = self:GetHP() + + local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() ) + + self:SetHP( NewHealth ) + + if NewHealth <= 0 then + self:SetDestroyed( true ) + end + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + return +end + +function ENT:Initialize() +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + self:DamageFX( vehicle ) +end + +function ENT:OnRemove() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end + +function ENT:DamageFX( vehicle ) + local T = CurTime() + local HP = vehicle:GetHP() + local MaxHP = vehicle:GetMaxHP() + + if HP <= 0 then return end + + if (self.nextDFX or 0) > T then return end + + self.nextDFX = T + 0.05 + + local Destroyed = self:GetDestroyed() + + if Destroyed or HP < MaxHP * 0.5 then + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( vehicle ) + util.Effect( "lvs_engine_blacksmoke", effectdata ) + end + + if not Destroyed then return end + + if HP < MaxHP * 0.5 then + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetNormal( -self:GetForward() ) + effectdata:SetMagnitude( 2 ) + effectdata:SetEntity( vehicle ) + util.Effect( "lvs_exhaust_fire", effectdata ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_starfighter_soundemitter.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_starfighter_soundemitter.lua new file mode 100644 index 0000000..e8447a8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_starfighter_soundemitter.lua @@ -0,0 +1,246 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +ENT._LVS = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 40, 5, Color( 255, 255, 255 ) ) + end + + function ENT:CheckWater( Base ) + if bit.band( util.PointContents( self:GetPos() ), CONTENTS_WATER ) ~= CONTENTS_WATER then + if self.CountWater then + self.CountWater = nil + end + + return + end + + if Base.WaterLevelAutoStop > 3 then return end + + self.CountWater = (self.CountWater or 0) + 1 + + if self.CountWater < 4 then return end + + Base:StopEngine() + end + + function ENT:Think() + + local Base = self:GetBase() + + if IsValid( Base ) and Base:GetEngineActive() then + self:CheckWater( Base ) + end + + self:NextThink( CurTime() + 1 ) + + return true + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + return +end + +ENT._oldEnActive = false +ENT._ActiveSounds = {} + +function ENT:Initialize() +end + +function ENT:StopSounds() + for id, sound in pairs( self._ActiveSounds ) do + if istable( sound ) then + for _, snd in pairs( sound ) do + if snd then + snd:Stop() + end + end + else + sound:Stop() + end + self._ActiveSounds[ id ] = nil + end +end + +function ENT:HandleEngineSounds( vehicle ) + local ply = LocalPlayer() + local pod = ply:GetVehicle() + local Throttle = vehicle:GetThrottle() - vehicle:GetThrustStrenght() * vehicle:GetThrottle() * 0.5 + local Doppler = vehicle:CalcDoppler( ply ) + + local DrivingMe = ply:lvsGetVehicle() == vehicle + + local FirstPerson = false + if IsValid( pod ) then + local ThirdPerson = pod:GetThirdPersonMode() + + if ThirdPerson ~= self._lvsoldTP then + self._lvsoldTP = ThirdPerson + VolumeSetNow = DrivingMe + end + + FirstPerson = DrivingMe and not ThirdPerson + end + + if DrivingMe ~= self._lvsoldDrivingMe then + self._lvsoldDrivingMe = DrivingMe + + self:StopSounds() + + self._oldEnActive = nil + + return + end + + local FT = RealFrameTime() + + self._smTHR = self._smTHR and self._smTHR + (Throttle - self._smTHR) * FT or 0 + + for id, sound in pairs( self._ActiveSounds ) do + if not sound then continue end + + local data = self.EngineSounds[ id ] + + local Pitch = math.Clamp( data.Pitch + self._smTHR * data.PitchMul, data.PitchMin, data.PitchMax ) + local PitchMul = data.UseDoppler and Doppler or 1 + + local InActive = self._smTHR > data.FadeOut or self._smTHR < data.FadeIn + if data.FadeOut >= 1 and self._smTHR > 1 then + InActive = false + end + + local Volume = InActive and 0 or LVS.EngineVolume + + if data.VolumeMin and data.VolumeMax and not InActive then + Volume = math.max(self._smTHR - data.VolumeMin,0) / (1 - data.VolumeMin) * data.VolumeMax * LVS.EngineVolume + end + + if istable( sound ) then + sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + if sound.int then sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), 0.2 ) end + + local fadespeed = VolumeSetNow and 0 or data.FadeSpeed + + if FirstPerson then + sound.ext:ChangeVolume( 0, 0 ) + + if vehicle:HasActiveSoundEmitters() then + Volume = Volume * 0.25 + fadespeed = fadespeed * 0.5 + end + + if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end + else + sound.ext:ChangeVolume( Volume, fadespeed ) + if sound.int then sound.int:ChangeVolume( 0, 0 ) end + end + else + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) + sound:ChangeVolume( Volume, data.FadeSpeed ) + end + end +end + +function ENT:OnEngineActiveChanged( Active ) + if not Active then self:StopSounds() return end + + local ply = LocalPlayer() + local DrivingMe = ply:lvsGetVehicle() == self:GetBase() + + for id, data in pairs( self.EngineSounds ) do + if not isstring( data.sound ) then continue end + + self.EngineSounds[ id ].Pitch = data.Pitch or 80 + self.EngineSounds[ id ].PitchMin = data.PitchMin or 0 + self.EngineSounds[ id ].PitchMax = data.PitchMax or 255 + self.EngineSounds[ id ].PitchMul = data.PitchMul or 100 + self.EngineSounds[ id ].UseDoppler = data.UseDoppler ~= false + self.EngineSounds[ id ].FadeIn = data.FadeIn or 0 + self.EngineSounds[ id ].FadeOut = data.FadeOut or 1 + self.EngineSounds[ id ].FadeSpeed = data.FadeSpeed or 1.5 + self.EngineSounds[ id ].SoundLevel = data.SoundLevel or 85 + + if data.sound_int and data.sound_int ~= data.sound and DrivingMe then + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + if data.sound_int == "" then + self._ActiveSounds[ id ] = { + ext = sound, + int = false, + } + else + local sound_interior = CreateSound( self, data.sound_int ) + sound_interior:SetSoundLevel( data.SoundLevel ) + sound_interior:PlayEx(0,100) + + self._ActiveSounds[ id ] = { + ext = sound, + int = sound_interior, + } + end + else + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + self._ActiveSounds[ id ] = sound + end + end +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + if not self.EngineSounds then + self.EngineSounds = vehicle.EngineSounds + + return + end + + local EngineActive = vehicle:GetEngineActive() + + if self._oldEnActive ~= EngineActive then + self._oldEnActive = EngineActive + self:OnEngineActiveChanged( EngineActive ) + end + + if EngineActive then + self:HandleEngineSounds( vehicle ) + else + self._smTHR = 0 + end +end + +function ENT:OnRemove() + self:StopSounds() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/cl_init.lua new file mode 100644 index 0000000..24d360a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/cl_init.lua @@ -0,0 +1,202 @@ +include("shared.lua") + +ENT.TrackSystemEnable = false +ENT.TrackHull = Vector(1,1,1) +ENT.TrackData = {} + +function ENT:CalcTrackScrollTexture() + local EntTable = self:GetTable() + + if not EntTable.TrackSystemEnable then return end + + local DriveWheelFL = self:GetTrackDriveWheelLeft() + if IsValid( DriveWheelFL ) then + local rotation = self:WorldToLocalAngles( DriveWheelFL:GetAngles() ).r + local scroll = self:CalcScroll( "scroll_left", rotation ) + + self:SetPoseParameter(EntTable.TrackPoseParameterLeft, scroll * EntTable.TrackPoseParameterLeftMul ) + self:SetSubMaterial( EntTable.TrackLeftSubMaterialID, self:ScrollTexture( "left", EntTable.TrackScrollTexture, EntTable.TrackLeftSubMaterialMul * scroll ) ) + end + + local DriveWheelFR = self:GetTrackDriveWheelRight() + if IsValid( DriveWheelFR ) then + local rotation = self:WorldToLocalAngles( DriveWheelFR:GetAngles() ).r + local scroll = self:CalcScroll( "scroll_right", rotation ) + + self:SetPoseParameter(EntTable.TrackPoseParameterRight, scroll * EntTable.TrackPoseParameterRightMul ) + self:SetSubMaterial( EntTable.TrackRightSubMaterialID, self:ScrollTexture( "right", EntTable.TrackScrollTexture, EntTable.TrackRightSubMaterialMul * scroll ) ) + end +end + +local WorldUp = Vector(0,0,1) + +function ENT:CalcTracks() + local EntTable = self:GetTable() + + if self:GetHP() <= 0 then + if EntTable._ResetSubMaterials then + EntTable._ResetSubMaterials = nil + for i = 0, 128 do + self:SetSubMaterial( i ) + end + end + + return + end + + EntTable._ResetSubMaterials = true + + local T = CurTime() + + if (EntTable._NextCalcTracks or 0) < T then + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local ViewEnt = ply:GetViewEntity() + if IsValid( ViewEnt ) then + ply = ViewEnt + end + + local Delay = math.min( (self:GetPos() - ply:GetPos()):LengthSqr() * 0.00000005, 1 ) + + EntTable._NextCalcTracks = T + Delay + + local Mul = math.max( Delay / RealFrameTime(), 1 ) + + local TrackHull = EntTable.TrackHull * (math.max( WorldUp:Dot( self:GetUp() ), 0 ) ^ 2) + + EntTable._TrackPoseParameters = {} + + for _, data in pairs( EntTable.TrackData ) do + if not istable( data.Attachment ) or not istable( data.PoseParameter ) then continue end + if not isstring( data.PoseParameter.name ) then continue end + + local att = self:GetAttachment( self:LookupAttachment( data.Attachment.name ) ) + + if not att then continue end + + local traceLength = data.Attachment.traceLength or 100 + local toGroundDistance = data.Attachment.toGroundDistance or 20 + + local trace = util.TraceHull( { + start = att.Pos, + endpos = att.Pos - self:GetUp() * traceLength, + filter = self:GetCrosshairFilterEnts(), + mins = -TrackHull, + maxs = TrackHull, + } ) + + local Rate = data.PoseParameter.lerpSpeed or 25 + local Dist = (att.Pos - trace.HitPos):Length() + EntTable.TrackHull.z - toGroundDistance + + local RangeMul = data.PoseParameter.rangeMultiplier or 1 + + if data.IsBonePP == nil then + data.IsBonePP = string.StartsWith( data.PoseParameter.name, "!" ) + + continue + + end + + EntTable._TrackPoseParameters[ data.PoseParameter.name ] = {} + EntTable._TrackPoseParameters[ data.PoseParameter.name ].IsBonePP = data.IsBonePP + + if data.IsBonePP then + EntTable._TrackPoseParameters[ data.PoseParameter.name ].Pose = math.Clamp( self:QuickLerp( data.PoseParameter.name, Dist * RangeMul, Rate * Mul ) / (data.PoseParameter.range or 10), 0 , 1 ) + else + EntTable._TrackPoseParameters[ data.PoseParameter.name ].Pose = self:QuickLerp( data.PoseParameter.name, Dist * RangeMul, Rate * Mul ) + end + end + end + + if not EntTable._TrackPoseParameters then return end + + for name, data in pairs( EntTable._TrackPoseParameters ) do + if data.IsBonePP then + self:SetBonePoseParameter( name, data.Pose ) + + continue + end + self:SetPoseParameter( name, data.Pose ) + end + + self:CalcTrackScrollTexture() +end + +DEFINE_BASECLASS( "lvs_base_wheeldrive" ) + +function ENT:Think() + if not self:IsInitialized() then return end + + self:CalcTracks() + + BaseClass.Think( self ) + end + +ENT.TrackSounds = "lvs/vehicles/sherman/tracks_loop.wav" + +ENT.TireSoundTypes = { + ["skid"] = "common/null.wav", + ["skid_dirt"] = "lvs/vehicles/generic/wheel_skid_dirt.wav", + ["skid_wet"] = "common/null.wav", + ["tracks_damage_layer"] = "lvs/tracks_damaged_loop.wav", + ["tire_damage_layer"] = "lvs/wheel_destroyed_loop.wav", +} + +function ENT:TireSoundThink() + for snd, _ in pairs( self.TireSoundTypes ) do + local T = self:GetTireSoundTime( snd ) + + if T > 0 then + local speed = self:GetVelocity():Length() + + local sound = self:StartTireSound( snd ) + + if string.StartsWith( snd, "skid" ) or snd == "tire_damage_layer" then + local vel = speed + speed = math.max( math.abs( self:GetWheelVelocity() ) - vel, 0 ) * 5 + vel + end + + local volume = math.min(speed / math.max( self.MaxVelocity, self.MaxVelocityReverse ),1) ^ 2 * T + local pitch = 100 + math.Clamp((speed - 400) / 200,0,155) + + if snd == "tracks_damage_layer" then + volume = math.min( speed / 100, 1 ) * T + end + + sound:ChangeVolume( volume, 0 ) + sound:ChangePitch( pitch, 0.5 ) + else + self:StopTireSound( snd ) + end + end +end + +function ENT:DoTireSound( snd ) + if not istable( self._TireSounds ) then + self._TireSounds = {} + end + + if string.StartsWith( snd, "roll" ) then + snd = "roll" + end + + self._TireSounds[ snd ] = CurTime() + self.TireSoundFade +end + +function ENT:StartTireSound( snd ) + if not self.TireSoundTypes[ snd ] or not istable( self._ActiveTireSounds ) then + self._ActiveTireSounds = {} + end + + if self._ActiveTireSounds[ snd ] then return self._ActiveTireSounds[ snd ] end + + local sound = CreateSound( self, (snd == "roll") and self.TrackSounds or self.TireSoundTypes[ snd ] ) + sound:SetSoundLevel( string.StartsWith( snd, "skid" ) and self.TireSoundLevelSkid or self.TireSoundLevelRoll ) + sound:PlayEx(0,100) + + self._ActiveTireSounds[ snd ] = sound + + return sound +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/init.lua new file mode 100644 index 0000000..f06304c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/init.lua @@ -0,0 +1,13 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") +include("sv_tracksystem.lua") + +AddCSLuaFile( "modules/cl_tankview.lua" ) +AddCSLuaFile( "modules/cl_attachable_playermodels.lua" ) +AddCSLuaFile( "modules/sh_turret.lua" ) +AddCSLuaFile( "modules/sh_turret_ballistics.lua" ) +AddCSLuaFile( "modules/sh_turret_splitsound.lua" ) + +ENT.DSArmorDamageReductionType = DMG_CLUB +ENT.DSArmorIgnoreDamageType = DMG_BULLET + DMG_SONIC + DMG_ENERGYBEAM diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/cl_attachable_playermodels.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/cl_attachable_playermodels.lua new file mode 100644 index 0000000..7342980 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/cl_attachable_playermodels.lua @@ -0,0 +1,43 @@ +if SERVER then return end + +function ENT:GetPlayerModel( name ) + if not istable( self._PlayerModels ) then return end + + return self._PlayerModels[ name ] +end + +function ENT:RemovePlayerModel( name ) + if not istable( self._PlayerModels ) then return end + + for id, model in pairs( self._PlayerModels ) do + if name and id ~= name then continue end + + if not IsValid( model ) then continue end + + model:Remove() + end +end + +function ENT:CreatePlayerModel( ply, name ) + if not isstring( name ) then return end + + if not istable( self._PlayerModels ) then + self._PlayerModels = {} + end + + if IsValid( self._PlayerModels[ name ] ) then return self._PlayerModels[ name ] end + + local model = ClientsideModel( ply:GetModel() ) + model:SetNoDraw( true ) + + model.GetPlayerColor = function() return ply:GetPlayerColor() end + model:SetSkin( ply:GetSkin() ) + + self._PlayerModels[ name ] = model + + return model +end + +function ENT:OnRemoved() + self:RemovePlayerModel() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/cl_tankview.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/cl_tankview.lua new file mode 100644 index 0000000..55dafb0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/cl_tankview.lua @@ -0,0 +1,74 @@ +if SERVER then return end + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + return pos, angles, fov +end + +function ENT:CalcTankView( ply, original_pos, original_ang, original_fov, pod ) + local pos, angles, fov = self:TankViewOverride( ply, original_pos, original_ang, original_fov, pod ) + + local view = {} + view.origin = pos + view.angles = angles + view.fov = fov + view.drawviewer = false + + if not pod:GetThirdPersonMode() then return view end + + local mn = self:OBBMins() + local mx = self:OBBMaxs() + local radius = ( mn - mx ):Length() + local radius = radius + radius * pod:GetCameraDistance() + + local clamped_angles = pod:WorldToLocalAngles( angles ) + clamped_angles.p = math.max( clamped_angles.p, -20 ) + clamped_angles = pod:LocalToWorldAngles( clamped_angles ) + + local StartPos = pos + local EndPos = StartPos - clamped_angles:Forward() * radius + clamped_angles:Up() * (radius * pod:GetCameraHeight()) + + local WallOffset = 4 + + local tr = util.TraceHull( { + start = StartPos, + endpos = EndPos, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.angles = angles + Angle(5,0,0) + view.origin = tr.HitPos + pod:GetUp() * 65 + view.drawviewer = true + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view +end + +function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod ) + if not pod:GetThirdPersonMode() then + angles = pod:LocalToWorldAngles( ply:EyeAngles() ) + end + + return self:CalcTankView( ply, pos, angles, fov, pod ) +end + +function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod ) + return self:CalcTankView( ply, pos, angles, fov, pod ) +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + if not pod:GetThirdPersonMode() then + angles = pod:LocalToWorldAngles( ply:EyeAngles() ) + end + + return self:CalcTankView( ply, pos, angles, fov, pod ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret.lua new file mode 100644 index 0000000..60b7ad0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret.lua @@ -0,0 +1,354 @@ +ENT.TurretPodIndex = 1 -- 1 = driver + +ENT.TurretAimRate = 25 + +ENT.TurretRotationSound = "vehicles/tank_turret_loop1.wav" +ENT.TurretRotationSoundDamaged = "lvs/turret_damaged_loop.wav" + +ENT.TurretFakeBarrel = false +ENT.TurretFakeBarrelRotationCenter = vector_origin + +ENT.TurretPitchPoseParameterName = "turret_pitch" +ENT.TurretPitchMin = -15 +ENT.TurretPitchMax = 15 +ENT.TurretPitchMul = 1 +ENT.TurretPitchOffset = 0 +ENT.TurretPitchEnableCentering = true + +ENT.TurretYawPoseParameterName = "turret_yaw" +ENT.TurretYawMul = 1 +ENT.TurretYawOffset = 0 +ENT.TurretYawEnableCentering = true + +ENT.TurretRateDestroyedMul = 0.25 + +function ENT:TurretSystemDT() + self:AddDT( "Bool", "TurretForceCenter" ) + self:AddDT( "Bool", "NWTurretEnabled" ) + self:AddDT( "Bool", "NWTurretDestroyed" ) + self:AddDT( "Bool", "TurretDamaged" ) + self:AddDT( "Entity", "NWTurretArmor" ) + + if SERVER then + self:SetTurretEnabled( true ) + self:SetTurretPitch( self.TurretPitchOffset ) + self:SetTurretYaw( self.TurretYawOffset ) + end +end + +function ENT:SetTurretDestroyed( new ) + self:SetNWTurretDestroyed( new ) + self:SetTurretDamaged( new ) +end + +function ENT:GetTurretDestroyed( new ) + return self:GetNWTurretDestroyed() +end + +function ENT:SetTurretEnabled( new ) + self:SetNWTurretEnabled( new ) +end + +function ENT:SetTurretArmor( TurretArmor ) + self:SetNWTurretArmor( TurretArmor ) + + if CLIENT then return end + + TurretArmor.OnDestroyed = function( ent, dmginfo ) + if not IsValid( self ) then return end + + self:SetTurretDestroyed( true ) + end + + TurretArmor.OnRepaired = function( ent ) + if not IsValid( self ) then return end + + self:SetTurretDestroyed( false ) + end + + TurretArmor.OnHealthChanged = function( ent, dmginfo, old, new ) + if new >= old then return end + + self:SetTurretDamaged( true ) + end +end + +function ENT:GetTurretArmor() + return self:GetNWTurretArmor() +end + +function ENT:GetTurretEnabled() + if self:GetTurretDestroyed() then return false end + + return self:GetNWTurretEnabled() +end + +function ENT:SetTurretPitch( num ) + self._turretPitch = num +end + +function ENT:SetTurretYaw( num ) + self._turretYaw = num +end + +function ENT:GetTurretPitch() + return (self._turretPitch or self.TurretPitchOffset) +end + +function ENT:GetTurretYaw() + return (self._turretYaw or self.TurretYawOffset) +end + +if CLIENT then + function ENT:UpdatePoseParameters( steer, speed_kmh, engine_rpm, throttle, brake, handbrake, clutch, gear, temperature, fuel, oil, ammeter ) + self:CalcTurret() + end + + function ENT:CalcTurret() + local pod = self:GetPassengerSeat( self.TurretPodIndex ) + + if not IsValid( pod ) then return end + + local plyL = LocalPlayer() + local ply = pod:GetDriver() + + if ply ~= plyL then return end + + self:AimTurret() + end + + net.Receive( "lvs_turret_sync_other", function( len ) + local veh = net.ReadEntity() + + if not IsValid( veh ) then return end + + local Pitch = net.ReadFloat() + local Yaw = net.ReadFloat() + + if isfunction( veh.SetTurretPitch ) then + veh:SetTurretPitch( Pitch ) + end + + if isfunction( veh.SetTurretYaw ) then + veh:SetTurretYaw( Yaw ) + end + end ) +else + util.AddNetworkString( "lvs_turret_sync_other" ) + + function ENT:OnPassengerChanged( Old, New, PodIndex ) + if PodIndex ~= self.TurretPodIndex then return end + + if IsValid( New ) then return end + + net.Start( "lvs_turret_sync_other" ) + net.WriteEntity( self ) + net.WriteFloat( self:GetTurretPitch() ) + net.WriteFloat( self:GetTurretYaw() ) + net.Broadcast() + end + + function ENT:CalcTurretSound( Pitch, Yaw, AimRate ) + local DeltaPitch = Pitch - self:GetTurretPitch() + local DeltaYaw = Yaw - self:GetTurretYaw() + + local PitchVolume = math.abs( DeltaPitch ) / AimRate + local YawVolume = math.abs( DeltaYaw ) / AimRate + + local PlayPitch = PitchVolume > 0.95 + local PlayYaw = YawVolume > 0.95 + + local TurretArmor = self:GetTurretArmor() + local Destroyed = self:GetTurretDamaged() + + if Destroyed and (PlayPitch or PlayYaw) and IsValid( TurretArmor ) then + local T = CurTime() + + if (self._NextTurDMGfx or 0) < T then + self._NextTurDMGfx = T + 0.1 + + local effectdata = EffectData() + effectdata:SetOrigin( TurretArmor:LocalToWorld( Vector(0,0,TurretArmor:GetMins().z) ) ) + effectdata:SetNormal( self:GetUp() ) + util.Effect( "lvs_physics_turretscraping", effectdata, true, true ) + end + end + + if PlayPitch or PlayYaw then + self:DoTurretSound() + end + + local T = self:GetTurretSoundTime() + + if T > 0 then + local volume = math.max( PitchVolume, YawVolume ) + local pitch = 90 + 10 * (1 - volume) + + if Destroyed then + local sound = self:StartTurretSoundDMG() + + pitch = pitch * self.TurretRateDestroyedMul + + sound:ChangeVolume( volume * 0.25, 0.25 ) + end + + local sound = self:StartTurretSound() + + sound:ChangeVolume( volume * 0.25, 0.25 ) + sound:ChangePitch( pitch, 0.25 ) + else + self:StopTurretSound() + self:StopTurretSoundDMG() + end + end + + function ENT:DoTurretSound() + if not self._TurretSound then self._TurretSound = 0 end + + self._TurretSound = CurTime() + 1.1 + end + + function ENT:GetTurretSoundTime() + if not self._TurretSound then return 0 end + + return math.max(self._TurretSound - CurTime(),0) / 1 + end + + function ENT:StopTurretSound() + if not self._turretSND then return end + + self._turretSND:Stop() + self._turretSND = nil + end + + function ENT:StartTurretSoundDMG() + if self._turretSNDdmg then return self._turretSNDdmg end + + self._turretSNDdmg = CreateSound( self, self.TurretRotationSoundDamaged ) + self._turretSNDdmg:PlayEx(0.5, 100) + + return self._turretSNDdmg + end + + function ENT:StopTurretSoundDMG() + if not self._turretSNDdmg then return end + + self._turretSNDdmg:Stop() + self._turretSNDdmg = nil + end + + function ENT:StartTurretSound() + if self._turretSND then return self._turretSND end + + self._turretSND = CreateSound( self, self.TurretRotationSound ) + self._turretSND:PlayEx(0,100) + + return self._turretSND + end + + function ENT:OnRemoved() + self:StopTurretSound() + self:StopTurretSoundDMG() + end + + function ENT:OnTick() + self:AimTurret() + end + + function ENT:CreateTurretPhysics( data ) + if not isstring( data.follow ) or not isstring( data.mdl ) then return NULL end + + local idFollow = self:LookupAttachment( data.follow ) + + local attFollow = self:GetAttachment( idFollow ) + + if not attFollow then return NULL end + + local Follower = ents.Create( "lvs_wheeldrive_attachment_follower" ) + + if not IsValid( Follower ) then return NULL end + + local Master = ents.Create( "lvs_wheeldrive_steerhandler" ) + + if not IsValid( Master ) then Follower:Remove() return NULL end + + Master:SetPos( attFollow.Pos ) + Master:SetAngles( attFollow.Ang ) + Master:Spawn() + Master:Activate() + self:DeleteOnRemove( Master ) + self:TransferCPPI( Master ) + + Follower:SetModel( data.mdl ) + Follower:SetPos( attFollow.Pos ) + Follower:SetAngles( self:GetAngles() ) + Follower:Spawn() + Follower:Activate() + Follower:SetBase( self ) + Follower:SetFollowAttachment( idFollow ) + Follower:SetMaster( Master ) + self:TransferCPPI( Follower ) + self:DeleteOnRemove( Follower ) + + local B1 = constraint.Ballsocket( Follower, self, 0, 0, self:WorldToLocal( attFollow.Pos ), 0, 0, 1 ) + B1.DoNotDuplicate = true + + local Lock = 0.0001 + local B2 = constraint.AdvBallsocket( Follower,Master,0,0,vector_origin,vector_origin,0,0,-Lock,-Lock,-Lock,Lock,Lock,Lock,0,0,0,1,1) + B2.DoNotDuplicate = true + + return Follower + end +end + +function ENT:IsTurretEnabled() + if self:GetHP() <= 0 then return false end + + if not self:GetTurretEnabled() then return false end + + return IsValid( self:GetPassenger( self.TurretPodIndex ) ) or self:GetAI() +end + +function ENT:AimTurret() + if not self:IsTurretEnabled() then if SERVER then self:StopTurretSound() self:StopTurretSoundDMG() end return end + + local EntTable = self:GetTable() + + local weapon = self:GetWeaponHandler( EntTable.TurretPodIndex ) + + if not IsValid( weapon ) then return end + + local AimAngles = self:WorldToLocalAngles( weapon:GetAimVector():Angle() ) + + if EntTable.TurretFakeBarrel then + AimAngles = self:WorldToLocalAngles( (self:LocalToWorld( EntTable.TurretFakeBarrelRotationCenter ) - weapon:GetEyeTrace().HitPos):Angle() ) + end + + local AimRate = EntTable.TurretAimRate * FrameTime() + + if self:GetTurretDamaged() then + AimRate = AimRate * EntTable.TurretRateDestroyedMul + end + + if self:GetTurretForceCenter() then + if EntTable.TurretPitchEnableCentering then AimAngles.p = EntTable.TurretPitchOffset end + if EntTable.TurretYawEnableCentering then AimAngles.y = EntTable.TurretYawOffset end + end + + local Pitch = math.Clamp( math.ApproachAngle( self:GetTurretPitch(), AimAngles.p, AimRate ), EntTable.TurretPitchMin, EntTable.TurretPitchMax ) + local Yaw = math.ApproachAngle( self:GetTurretYaw(), AimAngles.y, AimRate ) + + if EntTable.TurretYawMin and EntTable.TurretYawMax then + Yaw = math.Clamp( Yaw, EntTable.TurretYawMin, EntTable.TurretYawMax ) + end + + if SERVER then + self:CalcTurretSound( Pitch, Yaw, AimRate ) + end + + self:SetTurretPitch( Pitch ) + self:SetTurretYaw( Yaw ) + + self:SetPoseParameter(EntTable.TurretPitchPoseParameterName, EntTable.TurretPitchOffset + self:GetTurretPitch() * EntTable.TurretPitchMul ) + self:SetPoseParameter(EntTable.TurretYawPoseParameterName, EntTable.TurretYawOffset + self:GetTurretYaw() * EntTable.TurretYawMul ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret_ballistics.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret_ballistics.lua new file mode 100644 index 0000000..d5df20b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret_ballistics.lua @@ -0,0 +1,371 @@ + +ENT.TurretBallisticsPredicted = true +ENT.TurretBallisticsUpright = 0.6 +ENT.TurretBallisticsProjectileVelocity = 10000 +ENT.TurretBallisticsMuzzleAttachment = "muzzle" +ENT.TurretBallisticsViewAttachment = "sight" + +ENT.OpticsZoomOnly = true +ENT.OpticsScreenCentered = true + +function ENT:TurretSystemDT() + self:AddDT( "Bool", "TurretForceCenter" ) + self:AddDT( "Bool", "NWTurretEnabled" ) + self:AddDT( "Bool", "NWTurretDestroyed" ) + self:AddDT( "Bool", "TurretDamaged" ) + self:AddDT( "Entity", "NWTurretArmor" ) + + self:AddDT( "Float", "TurretCompensation" ) + self:AddDT( "Float", "NWTurretPitch" ) + self:AddDT( "Float", "NWTurretYaw" ) + + if SERVER then + self:SetTurretEnabled( true ) + self:SetTurretPitch( self.TurretPitchOffset ) + self:SetTurretYaw( self.TurretYawOffset ) + end +end + +function ENT:SetTurretPitch( num ) + self:SetNWTurretPitch( num ) +end + +function ENT:SetTurretYaw( num ) + self:SetNWTurretYaw( num ) +end + +function ENT:GetTurretPitch() + return self:GetNWTurretPitch() +end + +function ENT:GetTurretYaw() + return self:GetNWTurretYaw() +end + +function ENT:GetTurretViewOrigin() + local ID = self:LookupAttachment( self.TurretBallisticsViewAttachment ) + + local Att = self:GetAttachment( ID ) + + if not Att then return self:GetPos(), false end + + return Att.Pos, true +end + +if SERVER then + util.AddNetworkString( "lvs_turret_ballistics_synchronous" ) + + local function GetTurretEyeTrace( base, weapon ) + local startpos, found = base:GetTurretViewOrigin() + + if not found then return weapon:GetEyeTrace() end + + local pod = weapon:GetDriverSeat() + + if IsValid( pod ) and pod:GetThirdPersonMode() then + if weapon == base then + startpos = pod:LocalToWorld( pod:OBBCenter() ) + else + startpos = weapon:GetPos() + end + end + + local data = { + start = startpos, + endpos = (startpos + weapon:GetAimVector() * 50000), + filter = base:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( data ) + + return trace + end + + function ENT:CalcTurretAngles( EntTable ) + local weapon = self:GetWeaponHandler( EntTable.TurretPodIndex ) + + if not IsValid( weapon ) then return angle_zero end + + local UpZ = self:GetUp().z + + if UpZ < EntTable.TurretBallisticsUpright then return self:WorldToLocalAngles( weapon:GetAimVector():Angle() ) end + + local pod = weapon:GetDriverSeat() + + if IsValid( pod ) then + local ply = weapon:GetDriver() + + local ForceNoCompensation = false + + if IsValid( ply ) then + if self.OpticsZoomOnly and not ply:lvsKeyDown( "ZOOM" ) then + ForceNoCompensation = true + end + + if ply ~= weapon._LastBallisticsSendTo then + weapon._LastBallisticsSendTo = ply + + local velocity = EntTable.TurretBallisticsProjectileVelocity + local muzzle = EntTable.TurretBallisticsMuzzleAttachment + local sight = EntTable.TurretBallisticsViewAttachment + + self:TurretUpdateBallistics( velocity, muzzle, sight ) + end + + if pod:GetThirdPersonMode() or ForceNoCompensation then + return self:WorldToLocalAngles( weapon:GetAimVector():Angle() ) + end + else + if not weapon:GetAI() then + return self:WorldToLocalAngles( weapon:GetAimVector():Angle() ) + end + end + end + + local ID = self:LookupAttachment( EntTable.TurretBallisticsMuzzleAttachment ) + + local Muzzle = self:GetAttachment( ID ) + + if not Muzzle then return self:WorldToLocalAngles( weapon:GetAimVector():Angle() ) end + + local MuzzlePos = Muzzle.Pos + local MuzzleDir = Muzzle.Ang:Forward() + local MuzzleAng = MuzzleDir:Angle() + + local AimPos = GetTurretEyeTrace( self, weapon ).HitPos + + local StartPos = MuzzlePos + + local ProjectileVelocity = EntTable.TurretBallisticsProjectileVelocity + local Dist = (AimPos - MuzzlePos):Length() + + local OffsetPredicted = vector_origin + + if EntTable.TurretBallisticsPredicted then + OffsetPredicted = physenv.GetGravity() * ((Dist / ProjectileVelocity) ^ 2) + end + + local EndPos = AimPos - OffsetPredicted + + local Dir = (EndPos - StartPos):GetNormalized() + + local Pos, Ang = WorldToLocal( Muzzle.Pos, Dir:Angle(), Muzzle.Pos, MuzzleAng ) + + -- more body pitch/roll = more inaccurate. If Z up get smaller, turret must align more conservative to not overshoot + local TurretSmoothing = math.abs( UpZ ) + + self:SetTurretCompensation( OffsetPredicted.z ) + + return Angle( self:GetTurretPitch() + Ang.p * TurretSmoothing, self:GetTurretYaw() + Ang.y * TurretSmoothing, 0 ) + end + +else + ENT.IconTurret = Material( "lvs/turret.png" ) + ENT.IconTurretBody = Material( "lvs/turret_body.png" ) + ENT.IconTurretBarrel = Material( "lvs/turret_barrel.png" ) + ENT.IconTurretRing = Material( "lvs/turret_ring.png" ) + ENT.TurretColorMain = color_white + ENT.TurretColorShadow = Color(0,0,0,200) + ENT.TurretColorDamaged = Color(255,0,0,255) + + LVS:AddHudEditor( "Turret Info", ScrW() * 0.5 - 75, ScrH() - 110, 150, 100, 150, 100, "TURRETINFO", + function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply ) + if not vehicle.LVSHudPaintTurretInfo then return end + + vehicle:LVSHudPaintTurretInfo( X + W * 0.5 - H * 0.5, Y, H, H, ScrX, ScrY, ply ) + end + ) + + function ENT:LVSHudPaintTurretInfo( X, Y, W, H, ScrX, ScrY, ply ) + local pod = ply:GetVehicle() + + if not IsValid( pod ) or pod:lvsGetPodIndex() ~= self.TurretPodIndex then return end + + local EntTable = self:GetTable() + + local _, viewangles = ply:lvsGetView() + + local turret_yaw = self:GetTurretYaw() + + local yaw_body = - ply:GetVehicle():WorldToLocalAngles( viewangles ).y + 90 + local yaw_turret = turret_yaw + yaw_body + + local IconSize = W * 0.75 + + surface.SetDrawColor( EntTable.TurretColorShadow ) + surface.SetMaterial( EntTable.IconTurretBody ) + surface.DrawTexturedRectRotated( X + W * 0.5 + 2, Y + H * 0.5 + 2, IconSize, IconSize, yaw_body ) + + + local BodyColor = EntTable.TurretColorMain + + if self.GetWheels then + for _, wheel in pairs( self:GetWheels() ) do + if not wheel:GetDamaged() then continue end + + BodyColor = EntTable.TurretColorDamaged + + break + end + end + + surface.SetDrawColor( BodyColor ) + surface.SetMaterial( EntTable.IconTurretBody ) + surface.DrawTexturedRectRotated( X + W * 0.5, Y + H * 0.5, IconSize, IconSize, yaw_body ) + + surface.SetDrawColor( EntTable.TurretColorShadow ) + surface.SetMaterial( EntTable.IconTurret ) + surface.DrawTexturedRectRotated( X + W * 0.5 + 2, Y + H * 0.5 + 2, IconSize, IconSize, yaw_turret ) + + surface.SetDrawColor( EntTable.TurretColorMain ) + surface.SetMaterial( EntTable.IconTurretBarrel ) + surface.DrawTexturedRectRotated( X + W * 0.5, Y + H * 0.5, IconSize, IconSize, yaw_turret ) + + if self:GetTurretDamaged() then surface.SetDrawColor( EntTable.TurretColorDamaged ) end + surface.SetMaterial( EntTable.IconTurretRing ) + surface.DrawTexturedRectRotated( X + W * 0.5, Y + H * 0.5, IconSize, IconSize, yaw_turret ) + end + + net.Receive( "lvs_turret_ballistics_synchronous", function( len ) + local vehicle = net.ReadEntity() + + local velocity = net.ReadFloat() + local muzzle = net.ReadString() + local sight = net.ReadString() + + if not IsValid( vehicle ) then return end + + if velocity == 0 then velocity = nil end + if muzzle == "" then muzzle = nil end + if sight == "" then sight = nil end + + vehicle:TurretUpdateBallistics( velocity, muzzle, sight ) + end ) + + function ENT:CalcOpticsCrosshairDot( Pos2D ) + local ID = self:LookupAttachment( self.TurretBallisticsMuzzleAttachment ) + + local Muzzle = self:GetAttachment( ID ) + + if not Muzzle then return end + + local MuzzlePos = Muzzle.Pos + + local Pos = MuzzlePos + local LastPos = MuzzlePos + local StartPos = MuzzlePos + local StartDirection = Muzzle.Ang:Forward() + local Velocity = self.TurretBallisticsProjectileVelocity + + local Gravity = vector_origin + + if self.TurretBallisticsPredicted then + Gravity = physenv.GetGravity() + end + + cam.Start3D() + local Iteration = 0 + while Iteration < 1000 do + Iteration = Iteration + 1 + + local TimeAlive = Iteration / 200 + + local EndPos = StartPos + StartDirection * TimeAlive * Velocity + Gravity * (TimeAlive ^ 2) + + Pos = EndPos + + local trace = util.TraceLine( { + start = LastPos, + endpos = EndPos, + mask = MASK_SOLID, + } ) + + LastPos = EndPos + + if trace.Hit then + Pos = trace.HitPos + + break + end + end + cam.End3D() + + self:PaintOpticsCrosshair( Pos:ToScreen() ) + end +end + +function ENT:TurretUpdateBallistics( newvelocity, newmuzzle, newsight ) + if newvelocity then + self.TurretBallisticsProjectileVelocity = newvelocity + end + + if newmuzzle then + self.TurretBallisticsMuzzleAttachment = newmuzzle + end + + if newsight then + self.TurretBallisticsViewAttachment = newsight + end + + if CLIENT then return end + + local ply = self:GetPassenger( self.TurretPodIndex ) + + if not IsValid( ply ) then return end + + net.Start( "lvs_turret_ballistics_synchronous" ) + net.WriteEntity( self ) + net.WriteFloat( newvelocity or 0 ) + net.WriteString( newmuzzle or "" ) + net.WriteString( newsight or "" ) + net.Send( ply ) +end + +function ENT:AimTurret() + if not self:IsTurretEnabled() then if SERVER then self:StopTurretSound() self:StopTurretSoundDMG() end return end + + local EntTable = self:GetTable() + + if SERVER then + local AimAngles = self:CalcTurretAngles( EntTable ) + + local AimRate = EntTable.TurretAimRate * FrameTime() + + if self:GetTurretDamaged() then + AimRate = AimRate * EntTable.TurretRateDestroyedMul + end + + if self:GetTurretForceCenter() then + if EntTable.TurretPitchEnableCentering then AimAngles.p = EntTable.TurretPitchOffset end + if EntTable.TurretYawEnableCentering then AimAngles.y = EntTable.TurretYawOffset end + end + + local Pitch = math.Clamp( math.ApproachAngle( self:GetTurretPitch(), AimAngles.p, AimRate ), EntTable.TurretPitchMin, EntTable.TurretPitchMax ) + local Yaw = math.ApproachAngle( self:GetTurretYaw(), AimAngles.y, AimRate ) + + if EntTable.TurretYawMin and EntTable.TurretYawMax then + Yaw = math.Clamp( Yaw, EntTable.TurretYawMin, EntTable.TurretYawMax ) + end + + self:CalcTurretSound( Pitch, Yaw, AimRate ) + + self:SetTurretPitch( Pitch ) + self:SetTurretYaw( Yaw ) + + self:SetPoseParameter(EntTable.TurretPitchPoseParameterName, EntTable.TurretPitchOffset + self:GetTurretPitch() * EntTable.TurretPitchMul ) + self:SetPoseParameter(EntTable.TurretYawPoseParameterName, EntTable.TurretYawOffset + self:GetTurretYaw() * EntTable.TurretYawMul ) + + return + end + + local Rate = math.min( FrameTime() * EntTable.TurretAimRate, 1 ) + + local TargetPitch = EntTable.TurretPitchOffset + self:GetTurretPitch() * EntTable.TurretPitchMul + local TargetYaw = EntTable.TurretYawOffset + self:GetTurretYaw() * EntTable.TurretYawMul + + EntTable._turretPitch = EntTable._turretPitch and EntTable._turretPitch + (TargetPitch - EntTable._turretPitch) * Rate or EntTable.TurretPitchOffset + EntTable._turretYaw = EntTable._turretYaw and EntTable._turretYaw + (TargetYaw - EntTable._turretYaw) * Rate or EntTable.TurretYawOffset + + self:SetPoseParameter(EntTable.TurretPitchPoseParameterName, EntTable._turretPitch ) + self:SetPoseParameter(EntTable.TurretYawPoseParameterName, EntTable._turretYaw ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret_splitsound.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret_splitsound.lua new file mode 100644 index 0000000..ed7c083 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/modules/sh_turret_splitsound.lua @@ -0,0 +1,155 @@ + +include("sh_turret.lua") + +ENT.TurretRotationSound = "vehicles/tank_turret_loop1.wav" + +ENT.TurretElevationSound = "vehicles/tank_turret_loop2.wav" + +if CLIENT then return end + +function ENT:CalcTurretSound( Pitch, Yaw, AimRate ) + local DeltaPitch = Pitch - self:GetTurretPitch() + local DeltaYaw = Yaw - self:GetTurretYaw() + + local PitchVolume = math.abs( DeltaPitch ) / AimRate + local YawVolume = math.abs( DeltaYaw ) / AimRate + + local PlayPitch = PitchVolume > 0.95 + local PlayYaw = YawVolume > 0.95 + + local TurretArmor = self:GetTurretArmor() + local Destroyed = self:GetTurretDestroyed() + + if Destroyed and (PlayPitch or PlayYaw) and IsValid( TurretArmor ) then + local T = CurTime() + + if (self._NextTurDMGfx or 0) < T then + self._NextTurDMGfx = T + 0.1 + + local effectdata = EffectData() + effectdata:SetOrigin( TurretArmor:GetPos() ) + effectdata:SetNormal( self:GetUp() ) + effectdata:SetRadius( 0 ) + util.Effect( "cball_bounce", effectdata, true, true ) + end + end + + if Destroyed and (PlayPitch or PlayYaw) then + self:StartTurretSoundDMG() + else + self:StopTurretSoundDMG() + end + + if PlayPitch then + self:DoElevationSound() + end + + if PlayYaw then + self:DoRotationSound() + end + + if self:GetRotationSoundTime() > 0 then + local sound = self:StartRotationSound() + local volume = YawVolume + local pitch = 90 + 10 * (1 - volume) + + sound:ChangeVolume( volume * 0.25, 0.25 ) + sound:ChangePitch( pitch, 0.25 ) + else + self:StopRotationSound() + end + + if self:GetElevationSoundTime() > 0 then + local sound = self:StartElevationSound() + local volume = PitchVolume + local pitch = 90 + 10 * (1 - volume) + + sound:ChangeVolume( volume * 0.25, 0.25 ) + sound:ChangePitch( pitch, 0.25 ) + else + self:StopElevationSound() + end +end + +function ENT:DoRotationSound() + if not self._RotationSound then self._RotationSound = 0 end + + self._RotationSound = CurTime() + 1.1 +end + +function ENT:DoElevationSound() + if not self._ElevationSound then self._ElevationSound = 0 end + + self._ElevationSound = CurTime() + 1.1 +end + +function ENT:GetRotationSoundTime() + if not self._RotationSound then return 0 end + + return math.max(self._RotationSound - CurTime(),0) / 1 +end + +function ENT:GetElevationSoundTime() + if not self._ElevationSound then return 0 end + + return math.max(self._ElevationSound - CurTime(),0) / 1 +end + +function ENT:StopRotationSound() + if not self._turretRotSND then return end + + self._turretRotSND:Stop() + self._turretRotSND = nil +end + +function ENT:StopElevationSound() + if not self._turretElevSND then return end + + self._turretElevSND:Stop() + self._turretElevSND = nil +end + +function ENT:StartTurretSoundDMG() + if self._turretSNDdmg then return self._turretSNDdmg end + + self._turretSNDdmg = CreateSound( self, self.TurretRotationSoundDamaged ) + self._turretSNDdmg:PlayEx(0.5, 100) + + return self._turretSNDdmg +end + +function ENT:StopTurretSoundDMG() + if not self._turretSNDdmg then return end + + self._turretSNDdmg:Stop() + self._turretSNDdmg = nil +end + +function ENT:StartRotationSound() + if self._turretRotSND then return self._turretRotSND end + + self._turretRotSND = CreateSound( self, self.TurretRotationSound ) + self._turretRotSND:PlayEx(0,100) + + return self._turretRotSND +end + +function ENT:StartElevationSound() + if self._turretElevSND then return self._turretElevSND end + + self._turretElevSND = CreateSound( self, self.TurretElevationSound ) + self._turretElevSND:PlayEx(0,100) + + return self._turretElevSND +end + +function ENT:StopTurretSound() + self:StopElevationSound() + self:StopRotationSound() + + self:StopTurretSoundDMG() +end + +function ENT:OnRemoved() + self:StopTurretSound() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/shared.lua new file mode 100644 index 0000000..fec777a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/shared.lua @@ -0,0 +1,22 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "[LVS] Wheeldrive Tank" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MaxHealthEngine = 400 +ENT.MaxHealthFuelTank = 100 + +function ENT:TrackSystemDT() + self:AddDT( "Entity", "TrackDriveWheelLeft" ) + self:AddDT( "Entity", "TrackDriveWheelRight" ) +end + +function ENT:GetVehicleType() + return "tank" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/sv_tracksystem.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/sv_tracksystem.lua new file mode 100644 index 0000000..fa3db19 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_tank_wheeldrive/sv_tracksystem.lua @@ -0,0 +1,171 @@ + +function ENT:OnLeftTrackRepaired() +end + +function ENT:OnRightTrackRepaired() +end + +function ENT:OnLeftTrackDestroyed() +end + +function ENT:OnRightTrackDestroyed() +end + +function ENT:GetTrackPhysics() + return self._TrackPhysics +end + +function ENT:CreateTrackPhysics( mdl ) + if IsValid( self._TrackPhysics ) then return self._TrackPhysics end + + if not isstring( mdl ) then return NULL end + + local TrackPhysics = ents.Create( "lvs_wheeldrive_trackphysics" ) + + if not IsValid( TrackPhysics ) then return NULL end + + TrackPhysics:SetModel( mdl ) + TrackPhysics:SetPos( self:GetPos() ) + TrackPhysics:SetAngles( self:GetAngles() ) + TrackPhysics:Spawn() + TrackPhysics:Activate() + TrackPhysics:SetBase( self ) + self:TransferCPPI( TrackPhysics ) + self:DeleteOnRemove( TrackPhysics ) + + self._TrackPhysics = TrackPhysics + + local weld_constraint = constraint.Weld( TrackPhysics, self, 0, 0 ) + weld_constraint.DoNotDuplicate = true + + return TrackPhysics +end + +function ENT:CreateWheelChain( wheels ) + if not istable( wheels ) then return end + + local Lock = 0.0001 + local VectorNull = Vector(0,0,0) + + for _, wheel in pairs( wheels ) do + if not IsValid( wheel ) then continue end + + wheel:SetWheelChainMode( true ) + wheel:SetWidth( 0 ) + wheel:SetCollisionBounds( VectorNull, VectorNull ) + end + + for i = 2, #wheels do + local prev = wheels[ i - 1 ] + local cur = wheels[ i ] + + if not IsValid( cur ) or not IsValid( prev ) then continue end + + local B = constraint.AdvBallsocket(prev,cur,0,0,vector_origin,vector_origin,0,0,-Lock,-180,-180,Lock,180,180,0,0,0,1,1) + B.DoNotDuplicate = true + + local Rope = constraint.Rope(prev,cur,0,0,vector_origin,vector_origin,(prev:GetPos() - cur:GetPos()):Length(), 0, 0, 0,"cable/cable2", false) + Rope.DoNotDuplicate = true + end + + local WheelChain = {} + + WheelChain.OnDestroyed = function( ent ) + if not IsValid( ent ) or ent._tracksDestroyed then return end + + ent._tracksDestroyed = true + + self:OnTrackDestroyed( ent.wheeltype ) + self:OnHandleTrackGib( ent.wheeltype, true ) + + for _, wheel in pairs( wheels ) do + if not IsValid( wheel ) then continue end + + wheel:Destroy() + end + end + + WheelChain.OnRepaired = function( ent ) + for _, wheel in pairs( wheels ) do + if not IsValid( wheel ) then continue end + + wheel:Repair() + end + + if not IsValid( ent ) or not ent._tracksDestroyed then return end + + ent._tracksDestroyed = nil + + self:OnTrackRepaired( ent.wheeltype ) + self:OnHandleTrackGib( ent.wheeltype, false ) + end + + WheelChain.OnHealthChanged = function( ent, dmginfo, old, new ) + if new >= old then return end + + for _, wheel in pairs( wheels ) do + if not IsValid( wheel ) then continue end + + wheel:SetDamaged( true ) + end + end + + return WheelChain +end + +function ENT:SetTrackArmor( Armor, WheelChain ) + if not IsValid( Armor ) then return end + + Armor.OnDestroyed = WheelChain.OnDestroyed + Armor.OnRepaired = WheelChain.OnRepaired + Armor.OnHealthChanged = WheelChain.OnHealthChanged + Armor:SetLabel( "Tracks" ) +end + +function ENT:OnTrackRepaired( wheeltype ) + if wheeltype == LVS.WHEELTYPE_LEFT then + self:OnLeftTrackRepaired() + end + + if wheeltype == LVS.WHEELTYPE_RIGHT then + self:OnRightTrackRepaired() + end +end + +function ENT:OnTrackDestroyed( wheeltype ) + if wheeltype == LVS.WHEELTYPE_LEFT then + self:OnLeftTrackDestroyed() + end + + if wheeltype == LVS.WHEELTYPE_RIGHT then + self:OnRightTrackDestroyed() + end +end + +function ENT:SetTrackArmorLeft( Armor, WheelChain ) + + Armor.wheeltype = LVS.WHEELTYPE_LEFT + + self:SetTrackArmor( Armor, WheelChain ) +end + +function ENT:SetTrackArmorRight( Armor, WheelChain ) + + Armor.wheeltype = LVS.WHEELTYPE_RIGHT + + self:SetTrackArmor( Armor, WheelChain ) +end + +function ENT:OnHandleTrackGib( wheeltype, destroy ) + local TrackPhys = self:GetTrackPhysics() + + if not IsValid( TrackPhys ) then return end + + if destroy then + TrackPhys:SpawnGib( wheeltype ) + + return + end + + TrackPhys:ClearGib() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_attached_playermodels.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_attached_playermodels.lua new file mode 100644 index 0000000..4342aef --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_attached_playermodels.lua @@ -0,0 +1,33 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_attachable_playermodels.lua") + +function ENT:DrawDriver() + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then self:RemovePlayerModel( "driver" ) return end + + local plyL = LocalPlayer() + local ply = pod:GetDriver() + + if not IsValid( ply ) or (ply == plyL and not pod:GetThirdPersonMode()) then self:RemovePlayerModel( "driver" ) return end + + local ID = self:LookupAttachment( "seat" ) + local Att = self:GetAttachment( ID ) + + if not Att then self:RemovePlayerModel( "driver" ) return end + + local Pos,Ang = LocalToWorld( Vector(0,0,0), Angle(180,-20,-90), Att.Pos, Att.Ang ) + + local model = self:CreatePlayerModel( ply, "driver" ) + + model:SetSequence( "drive_airboat" ) + model:SetRenderOrigin( Pos ) + model:SetRenderAngles( Ang ) + model:DrawModel() +end + +function ENT:PreDraw() + self:DrawDriver() + + return true +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_init.lua new file mode 100644 index 0000000..b95e902 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_init.lua @@ -0,0 +1,4 @@ +include("shared.lua") +include("cl_tankview.lua") +include("sh_turret.lua") +include("cl_attached_playermodels.lua") diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_tankview.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_tankview.lua new file mode 100644 index 0000000..dc37144 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/cl_tankview.lua @@ -0,0 +1,20 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + if ply == self:GetDriver() and not pod:GetThirdPersonMode() then + local ID1 = self:LookupAttachment( "seat" ) + local ID2 = self:LookupAttachment( "muzzle" ) + + local Att1 = self:GetAttachment( ID1 ) + local Att2 = self:GetAttachment( ID2 ) + + if Att1 and Att2 then + local dir = Att2.Ang:Right() + pos = Att1.Pos - Att1.Ang:Right() * 27 + dir * 1.5 + end + + end + + return pos, angles, fov +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/init.lua new file mode 100644 index 0000000..04bb504 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/init.lua @@ -0,0 +1,18 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_tankview.lua" ) +AddCSLuaFile( "cl_attached_playermodels.lua" ) +AddCSLuaFile( "sh_turret.lua" ) +include("shared.lua") +include("sh_turret.lua") + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(0,0,18), Angle(0,-90,0) ) + DriverSeat.HidePlayer = true + + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurret = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/222/cannon_fire.wav", "lvs/vehicles/222/cannon_fire_interior.wav" ) + self.SNDTurret:SetSoundLevel( 95 ) + self.SNDTurret:SetParent( self, ID ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/sh_turret.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/sh_turret.lua new file mode 100644 index 0000000..8e05cb3 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/sh_turret.lua @@ -0,0 +1,32 @@ + +include("entities/lvs_tank_wheeldrive/modules/sh_turret.lua") + +ENT.TurretFakeBarrel = true +ENT.TurretFakeBarrelRotationCenter = Vector(0,0,40) + +ENT.TurretAimRate = 80 + +ENT.TurretRotationSound = "common/null.wav" + +ENT.TurretPitchPoseParameterName = "cannon_pitch" +ENT.TurretPitchMin = -30 +ENT.TurretPitchMax = 90 +ENT.TurretPitchMul = 1 +ENT.TurretPitchOffset = 0 + +ENT.TurretYawPoseParameterName = "cannon_yaw" +ENT.TurretYawMul = -1 +ENT.TurretYawOffset = 180 + +function ENT:TurretInRange() + local ID = self:LookupAttachment( "muzzle" ) + + local Muzzle = self:GetAttachment( ID ) + + if not Muzzle then return true end + + local Dir1 = Muzzle.Ang:Forward() + local Dir2 = self:GetAimVector() + + return self:AngleBetweenNormal( Dir1, Dir2 ) < 5 +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/shared.lua new file mode 100644 index 0000000..d72096d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flak/shared.lua @@ -0,0 +1,106 @@ + +ENT.Base = "lvs_base_wheeldrive_trailer" + +ENT.PrintName = "FlaK 38" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.VehicleCategory = "Artillery" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/blu/flak38.mdl" + +ENT.GibModels = { + "models/blu/flak_db.mdl", + "models/blu/flak_d1.mdl", + "models/blu/flak_d2.mdl", + "models/blu/flak_d3.mdl", + "models/blu/flak_d4.mdl", + "models/blu/flak_d5.mdl", + "models/blu/flak_d6.mdl", +} + +ENT.lvsShowInSpawner = false + +ENT.AITEAM = 1 + +ENT.PhysicsWeightScale = 1 +ENT.PhysicsMass = 450 +ENT.PhysicsInertia = Vector(475,452,162) +ENT.PhysicsDampingSpeed = 4000 +ENT.PhysicsDampingForward = false +ENT.PhysicsDampingReverse = false + +ENT.MaxHealth = 400 + +function ENT:InitWeapons() + local weapon = {} + weapon.Icon = Material("lvs/weapons/flak_he.png") + weapon.Ammo = 1500 + weapon.Delay = 0.25 + weapon.HeatRateUp = 0.25 + weapon.HeatRateDown = 0.5 + weapon.Attack = function( ent ) + + if not ent:TurretInRange() then + return true + end + + local ID = ent:LookupAttachment( "muzzle" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local Pos = Muzzle.Pos + local Dir = (ent:GetEyeTrace().HitPos - Pos):GetNormalized() + + local bullet = {} + bullet.Src = Pos + bullet.Dir = Dir + bullet.Spread = Vector(0,0,0) + bullet.TracerName = "lvs_tracer_autocannon" + bullet.Force = 3900 + bullet.HullSize = 50 * math.max( Dir.z, 0 ) + bullet.Damage = 40 + bullet.EnableBallistics = true + bullet.SplashDamage = 20 + bullet.SplashDamageRadius = 100 + bullet.SplashDamageEffect = "lvs_defence_explosion" + bullet.SplashDamageType = DMG_SONIC + bullet.Velocity = 50000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:PlayAnimation( "fire" ) + + ent:TakeAmmo( 1 ) + + if not IsValid( ent.SNDTurret ) then return end + + ent.SNDTurret:PlayOnce( 100 + math.cos( CurTime() + ent:EntIndex() * 1337 ) * 5 + math.Rand(-1,1), 1 ) + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/vehicles/222/cannon_overheat.wav") + end + weapon.HudPaint = function( ent, X, Y, ply ) + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = ent:TurretInRange() and Color(255,255,255,255) or Color(255,0,0,255) + + ent:PaintCrosshairCenter( Pos2D, Col ) + ent:PaintCrosshairSquare( Pos2D, Col ) + ent:LVSPaintHitMarker( Pos2D ) + end + self:AddWeapon( weapon ) +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/cl_init.lua new file mode 100644 index 0000000..8a21896 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/cl_init.lua @@ -0,0 +1,16 @@ +include("shared.lua") + +function ENT:LVSHudPaintVehicleIdentifier( X, Y, In_Col, target_ent ) +end + +function ENT:UpdatePoseParameters( steer, speed_kmh, engine_rpm, throttle, brake, handbrake, clutch, gear, temperature, fuel, oil, ammeter ) + local Prongs = self:GetProng() + + local T = CurTime() + + if Prongs then self._ProngTime = T + 0.25 end + + local ProngsActive = (self._ProngTime or 0) > T + + self:SetPoseParameter( "fold", self:QuickLerp( "prong", (ProngsActive and 1 or 0), 10 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/init.lua new file mode 100644 index 0000000..91d3dc9 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/init.lua @@ -0,0 +1,173 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + local WheelModel = "models/blu/carriage_wheel.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + SteerAngle = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(3.41,33.5,2), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + + self:AddWheel( { + pos = Vector(3.41,-33.5,2), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + + } ), + }, + Suspension = { + Height = 0, + MaxTravel = 0, + ControlArmLength = 0, + }, + } ) + + self:AddTrailerHitch( Vector(-86.5,0,18), LVS.HITCHTYPE_FEMALE ) + + local SupportEnt = ents.Create( "prop_physics" ) + + if not IsValid( SupportEnt ) then return end + + SupportEnt:SetModel( "models/props_junk/PopCan01a.mdl" ) + SupportEnt:SetPos( self:LocalToWorld( Vector(-57,0,-13) ) ) + SupportEnt:SetAngles( self:GetAngles() ) + SupportEnt:Spawn() + SupportEnt:Activate() + SupportEnt:PhysicsInitSphere( 5, "default_silent" ) + SupportEnt:SetNoDraw( true ) + SupportEnt:SetCollisionGroup( COLLISION_GROUP_PASSABLE_DOOR ) + SupportEnt.DoNotDuplicate = true + self:DeleteOnRemove( SupportEnt ) + SupportEnt:SetOwner( self ) + + constraint.Weld( self, SupportEnt, 0, 0, 0, false, false ) + + self.SupportEnt = SupportEnt:GetPhysicsObject() + + if not IsValid( self.SupportEnt ) then return end + + self.SupportEnt:SetMass( 250 ) +end + +function ENT:OnCoupled( targetVehicle, targetHitch ) + timer.Simple(0.2, function() + if not IsValid( self ) or not IsValid( self._MountEnt ) then return end + + self._MountEnt:RebuildCrosshairFilterEnts() + end) + + self:SetProng( true ) + + if not IsValid( self.SupportEnt ) then return end + self.SupportEnt:SetMass( 1 ) +end + +function ENT:OnDecoupled( targetVehicle, targetHitch ) + timer.Simple(0.2, function() + if not IsValid( self ) or not IsValid( self._MountEnt ) then return end + + self._MountEnt:RebuildCrosshairFilterEnts() + end) + + self:SetProng( false ) + + if not IsValid( self.SupportEnt ) then return end + self.SupportEnt:SetMass( 250 ) +end + +function ENT:OnStartDrag( caller, activator ) + self:SetProng( true ) + + if not IsValid( self.SupportEnt ) then return end + self.SupportEnt:SetMass( 1 ) +end + +function ENT:OnStopDrag( caller, activator ) + self:SetProng( false ) + + if not IsValid( self.SupportEnt ) then return end + self.SupportEnt:SetMass( 250 ) +end + +function ENT:Mount( ent ) + if IsValid( self._MountEnt ) or ent._IsMounted then return end + + if ent:IsPlayerHolding() then return end + + ent:SetOwner( self ) + ent:SetPos( self:GetPos() ) + ent:SetAngles( self:GetAngles() ) + + ent._MountOriginalCollision = ent:GetCollisionGroup() + self._MountEnt = ent + ent._IsMounted = true + + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + self._MountConstraint = constraint.Weld( ent, self, 0, 0, 0, false, false ) + + ent:RebuildCrosshairFilterEnts() +end + +function ENT:Dismount() + if not IsValid( self._MountEnt ) or not IsValid( self._MountConstraint ) then return end + + self._MountConstraint:Remove() + + self._MountEnt._IsMounted = nil + + local ent = self._MountEnt + + timer.Simple(1, function() + if not IsValid( ent ) then return end + + ent:SetOwner( NULL ) + + if ent._MountOriginalCollision then + ent:SetCollisionGroup( ent._MountOriginalCollision ) + + ent._MountOriginalCollision = nil + end + + end) + + self._MountEnt.CrosshairFilterEnts = nil + + self._MountEnt = nil +end + +function ENT:OnCollision( data, physobj ) + local ent = data.HitEntity + + if not IsValid( ent ) or ent:GetClass() ~= "lvs_trailer_flak" then return end + + timer.Simple(0, function() + if not IsValid( self ) or not IsValid( ent ) then return end + + self:Mount( ent ) + end) +end + +function ENT:OnTick() + if not IsValid( self._MountEnt ) or not self._MountEnt:IsPlayerHolding() then return end + + self:Dismount() +end + +function ENT:Use( ply ) + if not IsValid( self._MountEnt ) then return end + + self:Dismount() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/shared.lua new file mode 100644 index 0000000..3a00de7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_flaktrailer/shared.lua @@ -0,0 +1,45 @@ + +ENT.Base = "lvs_base_wheeldrive_trailer" + +ENT.PrintName = "FlaK Trailer" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.VehicleCategory = "Artillery" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/blu/flakcarriage.mdl" + +ENT.AITEAM = 0 + +ENT.MaxHealth = 200 + +ENT.DSArmorIgnoreForce = 1000 + +ENT.ForceAngleMultiplier = 2 + +ENT.lvsShowInSpawner = false + +function ENT:OnSetupDataTables() + self:AddDT( "Bool", "Prong" ) +end + +ENT.GibModels = { + "models/blu/carriage_wheel.mdl", + "models/blu/carriage_wheel.mdl", + "models/gibs/manhack_gib01.mdl", + "models/gibs/manhack_gib02.mdl", + "models/gibs/manhack_gib03.mdl", + "models/gibs/manhack_gib04.mdl", + "models/props_c17/canisterchunk01a.mdl", + "models/props_c17/canisterchunk01d.mdl", + "models/blu/carriage_d1.mdl", + "models/blu/carriage_d2.mdl", + "models/blu/carriage_d3.mdl", + "models/blu/carriage_d4.mdl", + "models/blu/carriage_d5.mdl", + "models/blu/carriage_d6.mdl", +} diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/cl_init.lua new file mode 100644 index 0000000..7961518 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/cl_init.lua @@ -0,0 +1,30 @@ +include("shared.lua") +include("sh_turret.lua") +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") +include("cl_optics.lua") + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + if ply == self:GetDriver() then + if pod:GetThirdPersonMode() then + pos = self:LocalToWorld( Vector(35,0,40) ) + else + local vieworigin, found = self:GetTurretViewOrigin() + + if found then pos = vieworigin end + end + end + + return pos, angles, fov +end + +function ENT:UpdatePoseParameters( steer, speed_kmh, engine_rpm, throttle, brake, handbrake, clutch, gear, temperature, fuel, oil, ammeter ) + local Prongs = self:GetProngs() + + local T = CurTime() + + if Prongs then self._ProngTime = T + 1 end + + local ProngsActive = (self._ProngTime or 0) > T + + self:SetPoseParameter( "prong", self:QuickLerp( "prong", (ProngsActive and 1 or 0), 10 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/cl_optics.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/cl_optics.lua new file mode 100644 index 0000000..b200b55 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/cl_optics.lua @@ -0,0 +1,133 @@ + +ENT.OpticsFov = 30 +ENT.OpticsEnable = true +ENT.OpticsZoomOnly = true +ENT.OpticsFirstPerson = true +ENT.OpticsThirdPerson = false +ENT.OpticsPodIndex = { + [1] = true, +} + +ENT.OpticsProjectileSize = 7.5 + +local RotationOffset = 0 +local circle = Material( "lvs/circle_hollow.png" ) +local tri1 = Material( "lvs/triangle1.png" ) +local tri2 = Material( "lvs/triangle2.png" ) +local pointer = Material( "gui/point.png" ) +local scope = Material( "lvs/scope_viewblocked.png" ) + +function ENT:PaintOpticsCrosshair( Pos2D ) + surface.SetDrawColor( 255, 255, 255, 5 ) + surface.SetMaterial( tri1 ) + surface.DrawTexturedRect( Pos2D.x - 17, Pos2D.y - 1, 32, 32 ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - 16, Pos2D.y, 32, 32 ) + + for i = -3, 3, 1 do + if i == 0 then continue end + + surface.SetMaterial( tri2 ) + surface.SetDrawColor( 255, 255, 255, 5 ) + surface.DrawTexturedRect( Pos2D.x - 11 + i * 32, Pos2D.y - 1, 20, 20 ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - 10 + i * 32, Pos2D.y, 20, 20 ) + end + + local ScrH = ScrH() + + local Y = Pos2D.y + 64 + local height = ScrH - Y + + surface.SetDrawColor( 0, 0, 0, 100 ) + surface.DrawRect( Pos2D.x - 2, Y, 4, height ) +end + +ENT.OpticsCrosshairMaterial = Material( "lvs/circle_filled.png" ) +ENT.OpticsCrosshairColor = Color(0,0,0,150) +ENT.OpticsCrosshairSize = 4 + +function ENT:PaintOptics( Pos2D, Col, PodIndex, Type ) + + if Type == 1 then + self:DrawRotatedText( "MG", Pos2D.x + 30, Pos2D.y + 30, "LVS_FONT_PANEL", Color(0,0,0,220), 0) + else + self:DrawRotatedText( Type == 3 and "HE" or "AP", Pos2D.x + 30, Pos2D.y + 30, "LVS_FONT_PANEL", Color(0,0,0,220), 0) + end + + local size = self.OpticsCrosshairSize + + surface.SetMaterial( self.OpticsCrosshairMaterial ) + surface.SetDrawColor( self.OpticsCrosshairColor ) + surface.DrawTexturedRect( Pos2D.x - size * 0.5, Pos2D.y - size * 0.5, size, size ) + + local ScrW = ScrW() + local ScrH = ScrH() + + surface.SetDrawColor( 0, 0, 0, 200 ) + + local TargetOffset = 0 + + if OldTargetOffset ~= TargetOffset then + OldTargetOffset = TargetOffset + surface.PlaySound( "lvs/optics.wav" ) + end + + RotationOffset = RotationOffset + (TargetOffset + math.max( self:GetTurretCompensation() / 15, -130 ) - RotationOffset) * RealFrameTime() * 8 + + local R = ScrH * 0.5 - 64 + local R0 = R + 30 + local R1 = R - 8 + local R2 = R - 23 + local R3 = R - 30 + local R4 = R - 18 + + for i = 0, 40 do + local ang = -90 + (180 / 40) * i + RotationOffset + + local x = math.cos( math.rad( ang ) ) + local y = math.sin( math.rad( ang ) ) + + if i == 2 then + self:DrawRotatedText( self.OpticsProjectileSize, Pos2D.x + x * R0, Pos2D.y + y * R0, "LVS_FONT", Color(0,0,0,200), 90 + ang) + end + if i == 3 then + self:DrawRotatedText( "cm", Pos2D.x + x * R0, Pos2D.y + y * R0, "LVS_FONT", Color(0,0,0,200), 90 + ang) + end + if i == 5 then + self:DrawRotatedText( "Pzgr", Pos2D.x + x * R0, Pos2D.y + y * R0, "LVS_FONT", Color(0,0,0,200), 90 + ang) + end + + surface.SetMaterial( circle ) + surface.DrawTexturedRectRotated( Pos2D.x + x * R, Pos2D.y + y * R, 16, 16, 0 ) + + surface.DrawLine( Pos2D.x + x * R1, Pos2D.y + y * R1, Pos2D.x + x * R2, Pos2D.y + y * R2 ) + + self:DrawRotatedText( i, Pos2D.x + x * R3, Pos2D.y + y * R3, "LVS_FONT_PANEL", Color(0,0,0,255), ang + 90) + + if i == 40 then continue end + + local ang = - 90 + (180 / 40) * (i + 0.5) + RotationOffset + + local x = math.cos( math.rad( ang ) ) + local y = math.sin( math.rad( ang ) ) + + surface.DrawLine( Pos2D.x + x * R1, Pos2D.y + y * R1, Pos2D.x + x * R4, Pos2D.y + y * R4 ) + end + + surface.SetDrawColor( 0, 0, 0, 100 ) + surface.SetMaterial( pointer ) + surface.DrawTexturedRect( Pos2D.x - 16, 0, 32, 64 ) + + local diameter = ScrH + 64 + local radius = diameter * 0.5 + + surface.SetMaterial( scope ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - radius, Pos2D.y - radius, diameter, diameter ) + + -- black bar left + right + surface.DrawRect( 0, 0, Pos2D.x - radius, ScrH ) + surface.DrawRect( Pos2D.x + radius, 0, Pos2D.x - radius, ScrH ) + +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/init.lua new file mode 100644 index 0000000..0772cdc --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/init.lua @@ -0,0 +1,171 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "sh_turret.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_optics.lua" ) +include("shared.lua") +include("sh_turret.lua") + +ENT.AISearchCone = 30 + +function ENT:OnSpawn( PObj ) + self:AddDriverSeat( Vector(0,15,-5), Angle(0,-90,0) ) + + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurret = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/pak40/cannon_fire.wav", "lvs/vehicles/pak40/cannon_fire.wav" ) + self.SNDTurret:SetSoundLevel( 95 ) + self.SNDTurret:SetParent( self, ID ) + + local WheelModel = "models/blu/pak40_wheel.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + SteerAngle = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(47.2,31,19), + mdl = WheelModel, + mdl_ang = Angle(0,90,0), + } ), + + self:AddWheel( { + pos = Vector(47.2,-31,19), + mdl = WheelModel, + mdl_ang = Angle(0,-90,0), + + } ), + }, + Suspension = { + Height = 0, + MaxTravel = 0, + ControlArmLength = 0, + }, + } ) + + self:AddTrailerHitch( Vector(-98,0,2), LVS.HITCHTYPE_FEMALE ) +end + +function ENT:OnTick() + self:AimTurret() +end + +function ENT:OnCollision( data, physobj ) + if self:WorldToLocal( data.HitPos ).z < 19 then return true end -- dont detect collision when the lower part of the model touches the ground + + return false +end + +function ENT:OnCoupled( targetVehicle, targetHitch ) + self:SetProngs( true ) + + timer.Simple( 0.2, function() + if not IsValid( self ) then return end + + self:RebuildCrosshairFilterEnts() + end ) +end + +function ENT:OnDecoupled( targetVehicle, targetHitch ) + self:SetProngs( false ) + + timer.Simple( 0.2, function() + if not IsValid( self ) then return end + + self:RebuildCrosshairFilterEnts() + end ) +end + +function ENT:OnStartDrag( caller, activator ) + self:SetProngs( true ) +end + +function ENT:OnStopDrag( caller, activator ) + self:SetProngs( false ) +end + +function ENT:SpawnShell() + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + + if not Muzzle then return end + + local Shell = ents.Create( "lvs_item_shell" ) + + if not IsValid( Shell ) then return end + + Shell.MDL = "models/props_debris/shellcasing_08.mdl" + Shell.CollisionSounds = { + "lvs/vehicles/pak40/shell_impact1.wav", + "lvs/vehicles/pak40/shell_impact2.wav" + } + + Shell:SetPos( Muzzle.Pos - Muzzle.Ang:Forward() * 140 ) + Shell:SetAngles( Muzzle.Ang + Angle(90,0,0) ) + Shell:Spawn() + Shell:Activate() + Shell:SetOwner( self ) + + local PhysObj = Shell:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetVelocityInstantaneous( Shell:GetRight() * 250 - Shell:GetUp() * 20 ) + PhysObj:SetAngleVelocityInstantaneous( Vector(0,0,180) ) +end + +function ENT:DoReloadSequence( delay ) + if self._ReloadActive then return end + + self._ReloadActive = true + + self:SetBodygroup(1, 1) + + timer.Simple(delay, function() + if not IsValid( self ) then return end + + self:PlayAnimation("breach") + + self:EmitSound("lvs/vehicles/pak40/cannon_unload.wav", 75, 100, 0.5, CHAN_WEAPON ) + + timer.Simple(0.3, function() + if not IsValid( self ) then return end + self:SpawnShell() + end) + end) + + timer.Simple(2, function() + if not IsValid( self ) then return end + + self:PlayAnimation("reload") + + self:EmitSound("lvs/vehicles/pak40/cannon_reload.wav", 75, 100, 1, CHAN_WEAPON ) + + timer.Simple(0.1, function() + if not IsValid( self ) then return end + self:SetBodygroup(1, 0) + self._ReloadActive = nil + end ) + end ) +end + +function ENT:DoAttackSequence() + if not IsValid( self.SNDTurret ) then return end + + self.SNDTurret:PlayOnce( 100 + math.cos( CurTime() + self:EntIndex() * 1337 ) * 5 + math.Rand(-1,1), 1 ) + + self:PlayAnimation("fire") + + self:DoReloadSequence( 1 ) +end + +function ENT:OnDriverEnterVehicle( ply ) + ply:SetCollisionGroup(COLLISION_GROUP_PLAYER) +end + +function ENT:OnDriverExitVehicle( ply ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/sh_turret.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/sh_turret.lua new file mode 100644 index 0000000..f1263d5 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/sh_turret.lua @@ -0,0 +1,23 @@ + +include("entities/lvs_tank_wheeldrive/modules/sh_turret.lua") +include("entities/lvs_tank_wheeldrive/modules/sh_turret_ballistics.lua") + +ENT.TurretBallisticsProjectileVelocity = ENT.ProjectileVelocityArmorPiercing +ENT.TurretBallisticsMuzzleAttachment = "muzzle" +ENT.TurretBallisticsViewAttachment = "sight" + +ENT.TurretAimRate = 15 + +ENT.TurretRotationSound = "common/null.wav" + +ENT.TurretPitchPoseParameterName = "cannon_pitch" +ENT.TurretPitchMin = -10 +ENT.TurretPitchMax = 6 +ENT.TurretPitchMul = -0.75 +ENT.TurretPitchOffset = -2 + +ENT.TurretYawPoseParameterName = "cannon_yaw" +ENT.TurretYawMin = -10 +ENT.TurretYawMax = 10 +ENT.TurretYawMul = -1 +ENT.TurretYawOffset = 0 diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/shared.lua new file mode 100644 index 0000000..565a136 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_trailer_pak40/shared.lua @@ -0,0 +1,172 @@ + +ENT.Base = "lvs_base_wheeldrive_trailer" + +ENT.PrintName = "PaK 40" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.VehicleCategory = "Artillery" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/blu/pak40.mdl" + +ENT.AITEAM = 1 + +ENT.WheelPhysicsMass = 350 +ENT.WheelPhysicsInertia = Vector(10,8,10) +ENT.WheelPhysicsTireHeight = 0 -- tire height 0 = doesnt use tires + +ENT.CannonArmorPenetration = 14500 + +-- ballistics +ENT.ProjectileVelocityHighExplosive = 13000 +ENT.ProjectileVelocityArmorPiercing = 16000 + +ENT.lvsShowInSpawner = false + +ENT.MaxHealth = 800 + +ENT.DSArmorIgnoreForce = 1000 + +ENT.GibModels = { + "models/blu/pak_d1.mdl", + "models/blu/pak_d2.mdl", + "models/blu/pak_d3.mdl", + "models/blu/pak_d4.mdl", + "models/blu/pak_d5.mdl", + "models/blu/pak_d6.mdl", + "models/blu/pak40_wheel.mdl", + "models/blu/pak40_wheel.mdl", + "models/gibs/manhack_gib01.mdl", + "models/gibs/manhack_gib02.mdl", + "models/gibs/manhack_gib03.mdl", + "models/gibs/manhack_gib04.mdl", +} + +function ENT:OnSetupDataTables() + self:AddDT( "Bool", "Prongs" ) + self:AddDT( "Bool", "UseHighExplosive" ) +end + +function ENT:CalcMainActivity( ply ) + if ply ~= self:GetDriver() then return self:CalcMainActivityPassenger( ply ) end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + ply.CalcIdeal = ACT_CROUCHIDLE + ply.CalcSeqOverride = ply:LookupSequence( "cidle_knife" ) + + return ply.CalcIdeal, ply.CalcSeqOverride +end + +function ENT:InitWeapons() + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = true + weapon.Ammo = 100 + weapon.Delay = 3 + weapon.HeatRateUp = 1 + weapon.HeatRateDown = 0.3 + weapon.OnThink = function( ent ) + local ply = ent:GetDriver() + + if not IsValid( ply ) then return end + + local SwitchType = ply:lvsKeyDown( "CAR_SWAP_AMMO" ) + + if ent._oldSwitchType ~= SwitchType then + ent._oldSwitchType = SwitchType + + if SwitchType then + ent:SetUseHighExplosive( not ent:GetUseHighExplosive() ) + ent:DoReloadSequence( 0 ) + ent:SetHeat( 1 ) + ent:SetOverheated( true ) + + if ent:GetUseHighExplosive() then + ent:TurretUpdateBallistics( ent.ProjectileVelocityHighExplosive ) + else + ent:TurretUpdateBallistics( ent.ProjectileVelocityArmorPiercing ) + end + end + end + end + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "muzzle" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = Muzzle.Ang:Forward() + bullet.Spread = Vector(0,0,0) + bullet.EnableBallistics = true + + if ent:GetUseHighExplosive() then + bullet.Force = 500 + bullet.HullSize = 15 + bullet.Damage = 250 + bullet.SplashDamage = 750 + bullet.SplashDamageRadius = 200 + bullet.SplashDamageEffect = "lvs_bullet_impact_explosive" + bullet.SplashDamageType = DMG_BLAST + bullet.Velocity = ent.ProjectileVelocityHighExplosive + else + bullet.Force = ent.CannonArmorPenetration + bullet.HullSize = 0 + bullet.Damage = 1000 + bullet.Velocity = ent.ProjectileVelocityArmorPiercing + end + + bullet.TracerName = "lvs_tracer_cannon" + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + ent:DoAttackSequence() + end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "muzzle" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + if ent:GetUseHighExplosive() then + ent:PaintCrosshairSquare( MuzzlePos2D, COLOR_WHITE ) + else + ent:PaintCrosshairOuter( MuzzlePos2D, COLOR_WHITE ) + end + + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + self:AddWeapon( weapon ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_turret.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_turret.lua new file mode 100644 index 0000000..f74f03e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_turret.lua @@ -0,0 +1,209 @@ +AddCSLuaFile() + +DEFINE_BASECLASS( "base_wire_entity" ) + +ENT.PrintName = "Projectile Turret" +ENT.WireDebugName = "Projectile Turret" + +ENT.Author = "Blu-x92" +ENT.Information = "Projectile Turret" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.Editable = true + +ENT.TracerOptions = { + ["LaserBlue"] = "lvs_laser_blue", + ["LaserRed"] = "lvs_laser_red", + ["LaserGreen"] = "lvs_laser_green", + ["TracerGreen"] = "lvs_tracer_green", + ["TracerOrange"] = "lvs_tracer_orange", + ["TracerWhite"] = "lvs_tracer_white", + ["TracerYellow"] = "lvs_tracer_yellow", + ["AutoCannon"] = "lvs_tracer_autocannon", + ["Cannon"] = "lvs_tracer_cannon", +} + +ENT.SplashTypeOptions = { + Shrapnel = "lvs_bullet_impact", + Explosive = "lvs_bullet_impact_explosive" +} + +function ENT:SetupDataTables() + local TracerOptions = {} + + for id, name in pairs( self.TracerOptions ) do + if not file.Exists( "effects/"..name..".lua", "LUA" ) then continue end + + TracerOptions[ id ] = name + end + + self:NetworkVar( "Float",1, "ShootDelay", { KeyName = "Shoot Delay", Edit = { type = "Float", order = 1,min = 0, max = 2, category = "Options"} } ) + self:NetworkVar( "Float",2, "Damage", { KeyName = "Damage", Edit = { type = "Float", order = 2,min = 0, max = 1000, category = "Options"} } ) + self:NetworkVar( "Float",3, "Speed", { KeyName = "Speed", Edit = { type = "Float", order = 3,min = 10000, max = 100000, category = "Options"} } ) + self:NetworkVar( "Float",4, "Size", { KeyName = "Size", Edit = { type = "Float", order = 4,min = 0, max = 50, category = "Options"} } ) + self:NetworkVar( "Float",5, "Spread", { KeyName = "Spread", Edit = { type = "Float", order = 5,min = 0, max = 1, category = "Options"} } ) + self:NetworkVar( "Float",6, "Penetration", { KeyName = "Armor Penetration (mm)", Edit = { type = "Float", order = 6,min = 0, max = 500, category = "Options"} } ) + self:NetworkVar( "Float",7, "SplashDamage", { KeyName = "Splash Damage", Edit = { type = "Float", order = 7,min = 0, max = 1000, category = "Options"} } ) + self:NetworkVar( "Float",8, "SplashDamageRadius", { KeyName = "Splash Damage Radius", Edit = { type = "Float", order = 8,min = 0, max = 750, category = "Options"} } ) + + self:NetworkVar( "String", 1, "SplashDamageType", { KeyName = "Splash Damage Type", Edit = { type = "Combo", order = 9,values = self.SplashTypeOptions,category = "Options"} } ) + + self:NetworkVar( "String", 2, "Tracer", { KeyName = "Tracer", Edit = { type = "Combo", order = 10,values = TracerOptions,category = "Options"} } ) + + if SERVER then + self:SetShootDelay( 0.05 ) + self:SetSpeed( 30000 ) + self:SetDamage( 15 ) + self:SetTracer( "lvs_tracer_orange" ) + self:SetSplashDamageType( "lvs_bullet_impact" ) + end +end + +if CLIENT then + function ENT:GetCrosshairFilterEnts() + if not istable( self.CrosshairFilterEnts ) then + self.CrosshairFilterEnts = {self} + + -- lets ask the server to build the filter for us because it has access to constraint.GetAllConstrainedEntities() + net.Start( "lvs_player_request_filter" ) + net.WriteEntity( self ) + net.SendToServer() + end + + return self.CrosshairFilterEnts + end + + return +end + +function ENT:GetCrosshairFilterEnts() + if not istable( self.CrosshairFilterEnts ) then + self.CrosshairFilterEnts = {} + + for _, Entity in pairs( constraint.GetAllConstrainedEntities( self ) ) do + if not IsValid( Entity ) then continue end + + table.insert( self.CrosshairFilterEnts , Entity ) + end + + for _, Parent in pairs( self.CrosshairFilterEnts ) do + for _, Child in pairs( Parent:GetChildren() ) do + if not IsValid( Child ) then continue end + + table.insert( self.CrosshairFilterEnts , Child ) + end + end + end + + return self.CrosshairFilterEnts +end + +function ENT:SpawnFunction( ply, tr, ClassName ) + + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent.Attacker = ply + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + +end + +function ENT:TriggerInput( name, value ) + if name == "Fire" then + self.TriggerFire = value >= 1 + end +end + +function ENT:Initialize() + self:SetModel( "models/props_junk/PopCan01a.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + self:PhysWake() + + self.Inputs = WireLib.CreateInputs( self,{"Fire"} ) +end + +function ENT:SetNextShoot( time ) + self.NextShoot = time +end + +function ENT:CanShoot() + if not self.TriggerFire then return false end + + self.NextShoot = self.NextShoot or 0 + + return self.NextShoot < CurTime() +end + +local IsCannon = { + ["lvs_tracer_autocannon"] = 0.25, + ["lvs_tracer_cannon"] = 1, +} + +function ENT:Shoot() + if not self:CanShoot() then return end + + local Tracer = self:GetTracer() + + local bullet = {} + bullet.Src = self:GetPos() + bullet.Dir = self:GetUp() + bullet.Spread = Vector(self:GetSpread(),self:GetSpread(),self:GetSpread()) + bullet.TracerName = Tracer + bullet.Force = self:GetPenetration() * 100 + bullet.HullSize = self:GetSize() + bullet.Damage = self:GetDamage() + bullet.Velocity = self:GetVelocity():Length() + self:GetSpeed() + + if IsCannon[ Tracer ] then + self:SetShootDelay( math.max( self:GetShootDelay(), IsCannon[ Tracer ] ) ) + end + + local SplashDamage = self:GetSplashDamage() + local SplashDamageRadius = self:GetSplashDamageRadius() + + if SplashDamage ~= 0 and SplashDamageRadius ~= 0 then + bullet.SplashDamage = SplashDamage + bullet.SplashDamageRadius = SplashDamageRadius + + local SplashEffect = self:GetSplashDamageType() + local BlastDamage = SplashEffect == "lvs_bullet_impact_explosive" + + bullet.SplashDamageEffect = SplashEffect + bullet.SplashDamageType = BlastDamage and DMG_BLAST or DMG_SONIC + + if BlastDamage then + self:SetShootDelay( math.max( self:GetShootDelay(), 0.5 ) ) + end + end + + bullet.Attacker = IsValid( self.Attacker ) and self.Attacker or self + + bullet.Entity = self + bullet.SrcEntity = vector_origin + + LVS:FireBullet( bullet ) + + self:SetNextShoot( CurTime() + self:GetShootDelay() ) +end + +function ENT:Think() + + self.BaseClass.Think( self ) + + self:Shoot() + + self:NextThink( CurTime() ) + + return true +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_air_refil.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_air_refil.lua new file mode 100644 index 0000000..cd4168f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_air_refil.lua @@ -0,0 +1,183 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Ammo Refil Balloon" +ENT.Author = "Luna" +ENT.Information = "Refils Ammo on Vehicles" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.RenderGroup = RENDERGROUP_BOTH + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Initialize() + self:SetModel( "models/balloons/hot_airballoon.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:DrawShadow( false ) + self:SetTrigger( true ) + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + local pObj = self:GetPhysicsObject() + + if not IsValid( pObj ) then + self:Remove() + + print("LVS: missing model. Balloon terminated.") + + return + end + + pObj:SetMass( 1000 ) + pObj:EnableMotion( true ) + pObj:EnableDrag( false ) + + self:StartMotionController() + + self:PhysWake() + end + + function ENT:PhysicsSimulate( phys, deltatime ) + phys:Wake() + + local StartPos = self:LocalToWorld( self:OBBCenter() ) + local traceUp = util.TraceLine( { + start = StartPos, + endpos = StartPos + Vector(0,0,50000), + filter = self, + mask = MASK_SOLID + } ) + local traceDown = util.TraceLine( { + start = StartPos, + endpos = StartPos - Vector(0,0,50000), + filter = self, + mask = MASK_SOLID + } ) + + local Force = (traceUp.HitPos + traceDown.HitPos) * 0.5 - StartPos + + local ForceLinear, ForceAngle = phys:CalculateForceOffset( Force, phys:LocalToWorld( phys:GetMassCenter() + Vector(0,0,1) ) ) + + ForceLinear = ForceLinear - phys:GetVelocity() + ForceAngle = ForceAngle - phys:GetAngleVelocity() + + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION + end + + function ENT:Refil( entity ) + if not IsValid( entity ) then return end + + if not entity.LVS then return end + + if entity:WeaponRestoreAmmo() then + entity:EmitSound("items/ammo_pickup.wav") + end + + entity:OnMaintenance() + end + + function ENT:StartTouch( entity ) + self:Refil( entity ) + end + + function ENT:EndTouch( entity ) + self:Refil( entity ) + end + + function ENT:Touch( entity ) + end + + function ENT:Think() + return false + end +end + +if CLIENT then + local WhiteList = { + ["weapon_physgun"] = true, + ["weapon_physcannon"] = true, + ["gmod_tool"] = true, + } + + local SpriteColor = Color( 255, 150, 0, 255 ) + local mat = Material( "models/wireframe" ) + local FrameMat = Material( "lvs/3d2dmats/frame.png" ) + local RepairMat = Material( "lvs/3d2dmats/refil.png" ) + + function ENT:Draw() + end + + function ENT:DrawTranslucent() + local ply = LocalPlayer() + local Small = false + + if IsValid( ply ) and not IsValid( ply:lvsGetVehicle() ) then + self:DrawModel() + + Small = true + + if GetConVarNumber( "cl_draweffectrings" ) == 0 then return end + + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + + if not IsValid( wep ) then return end + + local weapon_name = wep:GetClass() + + if not WhiteList[ weapon_name ] then + return + end + end + + local Pos = self:LocalToWorld( self:OBBCenter() ) + + if Small then + for i = 0, 180, 180 do + cam.Start3D2D( Pos, self:LocalToWorldAngles( Angle(0,i,90) ), 1 ) + surface.SetDrawColor( 255, 150, 0, 255 ) + + surface.SetMaterial( FrameMat ) + surface.DrawTexturedRect( -512, -512, 1024, 1024 ) + + surface.SetMaterial( RepairMat ) + surface.DrawTexturedRect( -256, 0, 512, 512 ) + cam.End3D2D() + end + else + for i = 0, 180, 180 do + cam.Start3D2D( Pos, self:LocalToWorldAngles( Angle(0,i,90) ), 0.75 ) + surface.SetDrawColor( 255, 150, 0, 255 ) + + surface.SetMaterial( FrameMat ) + surface.DrawTexturedRect( -512, -512, 1024, 1024 ) + + surface.SetMaterial( RepairMat ) + surface.DrawTexturedRect( -512, -512, 1024, 1024 ) + cam.End3D2D() + end + end + end + + function ENT:OnRemove() + if IsValid( self._RepairMDL ) then + self._RepairMDL:Remove() + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_repair.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_repair.lua new file mode 100644 index 0000000..5c3422f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_repair.lua @@ -0,0 +1,131 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Maintenance Station" +ENT.Author = "Luna" +ENT.Information = "Repairs Vehicles" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Initialize() + self:SetModel( "models/props_vehicles/generatortrailer01.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:DrawShadow( false ) + self:SetTrigger( true ) + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + + function ENT:Refil( entity ) + if not IsValid( entity ) then return end + + if not entity.LVS then return end + + local Repaired = false + + if entity:GetHP() ~= entity:GetMaxHP() then + entity:SetHP( entity:GetMaxHP() ) + + Repaired = true + end + + if entity:OnArmorMaintenance() then + Repaired = true + end + + if Repaired then + entity:EmitSound("npc/dog/dog_servo2.wav") + end + + if entity:WeaponRestoreAmmo() then + entity:EmitSound("items/ammo_pickup.wav") + end + + entity:OnMaintenance() + end + + function ENT:StartTouch( entity ) + self:Refil( entity ) + end + + function ENT:EndTouch( entity ) + self:Refil( entity ) + end + + function ENT:Touch( entity ) + end + + function ENT:Think() + return false + end +end + +if CLIENT then + local WhiteList = { + ["weapon_physgun"] = true, + ["weapon_physcannon"] = true, + ["gmod_tool"] = true, + } + + local mat = Material( "models/wireframe" ) + local FrameMat = Material( "lvs/3d2dmats/frame.png" ) + local RepairMat = Material( "lvs/3d2dmats/repair.png" ) + function ENT:Draw() + local ply = LocalPlayer() + local Small = false + + if IsValid( ply ) and not IsValid( ply:lvsGetVehicle() ) then + self:DrawModel() + + Small = true + + if GetConVarNumber( "cl_draweffectrings" ) == 0 then return end + + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + + if not IsValid( wep ) then return end + + local weapon_name = wep:GetClass() + + if not WhiteList[ weapon_name ] then + return + end + end + + local Pos = self:GetPos() + + for i = 0, 180, 180 do + cam.Start3D2D( self:LocalToWorld( Vector(0,0, self:OBBMins().z + 2 ) ), self:LocalToWorldAngles( Angle(i,90,0) ), 0.25 ) + surface.SetDrawColor( 255, 150, 0, 255 ) + + surface.SetMaterial( FrameMat ) + surface.DrawTexturedRect( -512, -512, 1024, 1024 ) + + surface.SetMaterial( RepairMat ) + if Small then + surface.DrawTexturedRect( -256, 0, 512, 512 ) + else + surface.DrawTexturedRect( -512, -512, 1024, 1024 ) + end + cam.End3D2D() + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_spammer.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_spammer.lua new file mode 100644 index 0000000..b0d1538 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_vehicle_spammer.lua @@ -0,0 +1,338 @@ + +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "AI Vehicle Spammer" +ENT.Author = "Luna" +ENT.Information = "AI Vehicle Spawner. Spammer in the hands of a Minge." +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.Editable = true + +function ENT:SetupDataTables() + local AllSents = scripted_ents.GetList() + local SpawnOptions = {} + + for _, v in pairs( AllSents ) do + if not v or not istable( v.t ) or not v.t.Spawnable then continue end + + if v.t.Base and v.t.Base:lower() == "lvs_base_wheeldrive" and not v.t.lvsShowInSpawner then continue end + + if v.t.lvsShowInSpawner == false then continue end + + if v.t.lvsShowInSpawner or (v.t.Base and (string.StartWith( v.t.Base:lower(), "lvs_base" ) or string.StartWith( v.t.Base:lower(), "lunasflightschool" ))) then + if v.t.Category and v.t.PrintName then + local nicename = v.t.Category.." - "..v.t.PrintName + if not table.HasValue( SpawnOptions, nicename ) then + SpawnOptions[nicename] = v.t.ClassName + end + end + end + end + + self:NetworkVar( "String",0, "Type", { KeyName = "Vehicle Type",Edit = { type = "Combo", order = 1,values = SpawnOptions,category = "Vehicle-Options"} } ) + self:NetworkVar( "Int",3, "TeamOverride", { KeyName = "AI Team", Edit = { type = "Int", order = 4,min = -1, max = 3, category = "Vehicle-Options"} } ) + self:NetworkVar( "Int",4, "RespawnTime", { KeyName = "spawntime", Edit = { type = "Int", order = 5,min = 1, max = 120, category = "Vehicle-Options"} } ) + self:NetworkVar( "Int",5, "Amount", { KeyName = "amount", Edit = { type = "Int", order = 6,min = 1, max = 10, category = "Vehicle-Options"} } ) + self:NetworkVar( "Int",6, "SpawnWithSkin", { KeyName = "spawnwithskin", Edit = { type = "Int", order = 8,min = 0, max = 16, category = "Vehicle-Options"} } ) + self:NetworkVar( "Int",7, "SpawnWithHealth", { KeyName = "spawnwithhealth", Edit = { type = "Int", order = 9,min = 0, max = 50000, category = "Vehicle-Options"} } ) + self:NetworkVar( "Int",8, "SpawnWithShield", { KeyName = "spawnwithshield", Edit = { type = "Int", order = 10,min = 0, max = 50000, category = "Vehicle-Options"} } ) + + self:NetworkVar( "Int",10, "SelfDestructAfterAmount", { KeyName = "selfdestructafteramount", Edit = { type = "Int", order = 22,min = 0, max = 100, category = "Spawner-Options"} } ) + self:NetworkVar( "Bool",2, "MasterSwitch" ) + + if SERVER then + self:NetworkVarNotify( "Type", self.OnTypeChanged ) + + self:SetRespawnTime( 2 ) + self:SetAmount( 1 ) + self:SetSelfDestructAfterAmount( 0 ) + self:SetSpawnWithHealth( 0 ) + self:SetSpawnWithShield( 0 ) + self:SetTeamOverride( -1 ) + end +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 1 ) + ent:Spawn() + ent:Activate() + + return ent + + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Initialize() + self:SetModel( "models/hunter/plates/plate8x8.mdl" ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self:DrawShadow( false ) + + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + self.NextSpawn = 0 + end + + function ENT:Use( ply ) + if not IsValid( ply ) then return end + + if not IsValid( self.Defusor ) then + self.Defusor = ply + self.DefuseTime = CurTime() + end + end + + function ENT:Think() + if IsValid( self.Defusor ) and isnumber( self.DefuseTime ) then + if self.Defusor:KeyDown( IN_USE ) then + if CurTime() - self.DefuseTime > 1 then + self:SetMasterSwitch( not self:GetMasterSwitch() ) + + for k, v in pairs( ents.FindByClass( "lvs_vehicle_spammer" ) ) do + if v ~= self and IsValid( v ) then + v:SetMasterSwitch( self:GetMasterSwitch() ) + end + end + + if self:GetMasterSwitch() then + self.Defusor:PrintMessage( HUD_PRINTTALK, "ALL AI-Spawners Enabled") + else + self.Defusor:PrintMessage( HUD_PRINTTALK, "ALL AI-Spawners Disabled") + end + + self.Defusor = nil + end + else + self:SetMasterSwitch( not self:GetMasterSwitch() ) + + if self:GetMasterSwitch() then + self.Defusor:PrintMessage( HUD_PRINTTALK, "AI-Spawner Enabled") + else + self.Defusor:PrintMessage( HUD_PRINTTALK, "AI-Spawner Disabled") + end + + self.Defusor = nil + end + end + + if not self:GetMasterSwitch() then return end + + self.spawnedvehicles = self.spawnedvehicles or {} + + if self.ShouldSpawn then + if self.NextSpawn < CurTime() then + + self.ShouldSpawn = false + + local pos = self:LocalToWorld( Vector( 0, 0, 150 ) ) + local ang = self:LocalToWorldAngles( Angle( 0, 90, 0 ) ) + + local Type = self:GetType() + + if Type ~= "" then + local spawnedvehicle = ents.Create( Type ) + + if IsValid( spawnedvehicle ) then + if spawnedvehicle.SpawnNormalOffsetSpawner then + spawnedvehicle:SetPos( self:LocalToWorld( Vector(0,0,spawnedvehicle.SpawnNormalOffsetSpawner) ) ) + else + spawnedvehicle:SetPos( pos + Vector(0,0,spawnedvehicle.SpawnNormalOffset or 0) ) + end + spawnedvehicle:SetAngles( ang ) + spawnedvehicle:Spawn() + spawnedvehicle:Activate() + spawnedvehicle:SetAI( true ) + spawnedvehicle:SetSkin( self:GetSpawnWithSkin() ) + spawnedvehicle.SpawnedByAISpawner = true + + if self:GetTeamOverride() >= 0 then + spawnedvehicle:SetAITEAM( self:GetTeamOverride() ) + end + + if self:GetSpawnWithHealth() > 0 then + spawnedvehicle.MaxHealth = self:GetSpawnWithHealth() + spawnedvehicle:SetHP( self:GetSpawnWithHealth() ) + end + + if self:GetSpawnWithShield() > 0 then + spawnedvehicle.MaxShield = self:GetSpawnWithShield() + spawnedvehicle:SetShield( self:GetSpawnWithShield() ) + end + + if spawnedvehicle.LFS and not spawnedvehicle.DontPushMePlease then + local PhysObj = spawnedvehicle:GetPhysicsObject() + + if IsValid( PhysObj ) then + PhysObj:SetVelocityInstantaneous( -self:GetRight() * 1000 ) + end + end + + table.insert( self.spawnedvehicles, spawnedvehicle ) + + if self:GetSelfDestructAfterAmount() > 0 then + self.RemoverCount = isnumber( self.RemoverCount ) and self.RemoverCount + 1 or 1 + + if self.RemoverCount >= self:GetSelfDestructAfterAmount() then + self:Remove() + end + end + end + end + end + else + local AmountSpawned = 0 + for k,v in pairs( self.spawnedvehicles ) do + if IsValid( v ) then + AmountSpawned = AmountSpawned + 1 + else + self.spawnedvehicles[k] = nil + end + end + + if AmountSpawned < self:GetAmount() then + self.ShouldSpawn = true + self.NextSpawn = CurTime() + self:GetRespawnTime() + end + end + + self:NextThink( CurTime() ) + + return true + end +end + +if CLIENT then + local WhiteList = { + ["weapon_physgun"] = true, + ["weapon_physcannon"] = true, + ["gmod_tool"] = true, + } + + local TutorialDone = false + local mat = Material( "models/wireframe" ) + local FrameMat = Material( "lvs/3d2dmats/frame.png" ) + local ArrowMat = Material( "lvs/3d2dmats/arrow.png" ) + + function ENT:Draw() + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + if TutorialDone then + if GetConVarNumber( "cl_draweffectrings" ) == 0 then return end + + local wep = ply:GetActiveWeapon() + + if not IsValid( wep ) then return end + + local weapon_name = wep:GetClass() + + if not WhiteList[ weapon_name ] then + return + end + else + local wep = ply:GetActiveWeapon() + + if not IsValid( wep ) then return end + + local weapon_name = wep:GetClass() + + if not WhiteList[ weapon_name ] then + if weapon_name == "gmod_camera" then return end + + local Trace = ply:GetEyeTrace() + if Trace.Entity ~= self or (ply:GetShootPos() - Trace.HitPos):Length() > 800 then return end + end + end + + local Pos = self:GetPos() + local R = 190 + render.SetMaterial( mat ) + render.DrawBox( Pos, self:GetAngles(), Vector(-R,-R,0), Vector(R,R,200), color_white ) + + for i = 0, 180, 180 do + cam.Start3D2D( Pos, self:LocalToWorldAngles( Angle(i,0,0) ), 0.185 ) + if self:GetMasterSwitch() then + local T4 = CurTime() * 4 + + local OY = math.cos( T4 ) + local A = math.max( math.sin( T4 ), 0 ) + + surface.SetMaterial( ArrowMat ) + + if self:GetType() == "" then + surface.SetDrawColor( 255, 0, 0, A * 255 ) + surface.DrawTexturedRect( -512, -512 + OY * 512, 1024, 1024 ) + + surface.SetDrawColor( 255, 0, 0, math.abs( math.cos( T4 ) ) ^ 2 * 255 ) + else + surface.SetDrawColor( 0, 127, 255, A * 255 ) + surface.DrawTexturedRect( -512, -512 + OY * 512, 1024, 1024 ) + + surface.SetDrawColor( 0, 127, 255, 255 ) + end + else + surface.SetDrawColor( 255, 0, 0, 255 ) + + surface.SetMaterial( ArrowMat ) + surface.DrawTexturedRect( -512, -512, 1024, 1024 ) + end + + surface.SetMaterial( FrameMat ) + surface.DrawTexturedRect( -1024, -1024, 2048, 2048 ) + cam.End3D2D() + end + end + + hook.Add( "HUDPaint", "!!!!!!!11111lvsvehiclespammer_tutorial", function() + if TutorialDone then + hook.Remove( "HUDPaint", "!!!!!!!11111lvsvehiclespammer_tutorial" ) + end + + local ply = LocalPlayer() + + if ply:InVehicle() then return end + + local trace = ply:GetEyeTrace() + local Dist = (ply:GetShootPos() - trace.HitPos):Length() + + if Dist > 800 then return end + + local Ent = trace.Entity + + if not IsValid( Ent ) then return end + + if Ent:GetClass() ~= "lvs_vehicle_spammer" then return end + + local pos = Ent:GetPos() + local scr = pos:ToScreen() + local Alpha = 255 + + if Ent:GetType() == "" then + draw.SimpleText( "Hold C => Right Click on me => Edit Properties => Choose a Type", "LVS_FONT", scr.x, scr.y - 10, Color(255,255,255,Alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + if not Ent:GetMasterSwitch() then + local Key = input.LookupBinding( "+use" ) + if not isstring( Key ) then Key = "+use is not bound to a key" end + + draw.SimpleText( "Now press ["..Key.."] to enable!", "LVS_FONT", scr.x, scr.y - 10, Color(255,255,255,Alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( "or hold ["..Key.."] to enable globally!", "LVS_FONT", scr.x, scr.y + 10, Color(255,255,255,Alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + TutorialDone = true + end + end + end ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_component.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_component.lua new file mode 100644 index 0000000..ce4fc31 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_component.lua @@ -0,0 +1,64 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.AutomaticFrameAdvance = true +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) +end + +if SERVER then + function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + end + + function ENT:Think() + return false + end + + function ENT:OnTakeDamage( dmginfo ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:OnTakeDamage( dmginfo ) + end + + function ENT:PhysicsCollide( data, phys ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:PhysicsCollide( data, phys ) + end + + function ENT:Use( ply ) + if (ply._lvsNextUse or 0) > CurTime() then return end + + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:Use( ply ) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + return +end + +function ENT:Draw() + self:DrawModel() +end + +function ENT:Think() +end + +function ENT:OnRemove() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/cl_init.lua new file mode 100644 index 0000000..aef95f1 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/cl_init.lua @@ -0,0 +1,2 @@ +include("shared.lua") +include("sh_camera_eyetrace.lua") diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/init.lua new file mode 100644 index 0000000..4f639ed --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/init.lua @@ -0,0 +1,93 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "sh_camera_eyetrace.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") +include("sh_camera_eyetrace.lua") + +function ENT:ToggleGravity( PhysObj, Enable ) + if PhysObj:IsGravityEnabled() ~= Enable then + PhysObj:EnableGravity( Enable ) + end +end + +function ENT:TransformNormal( ent, Normal ) + return Normal +end + +function ENT:GetAlignment( ent, phys ) + return ent:GetForward(), ent:GetRight() +end + +function ENT:GetMoveXY( ent, phys ) + return 0, 0 +end + +function ENT:GetSteer( ent, phys ) + return 0 +end + +function ENT:GetHoverHeight( ent, phys ) + return self.HoverHeight +end + +function ENT:PhysicsSimulate( phys, deltatime ) + phys:Wake() + + if not self:GetEngineActive() then + + self:ToggleGravity( phys, true ) + + return + end + + local base = phys:GetEntity() + local vel = phys:GetVelocity() + local velL = phys:WorldToLocal( phys:GetPos() + vel ) + + local masscenter = phys:LocalToWorld( phys:GetMassCenter() ) + + local forward, right = self:GetAlignment( base, phys ) + local up = base:GetUp() + + local tracedata = { + start = masscenter, + endpos = masscenter - up * self.HoverTraceLength, + mins = Vector( -self.HoverHullRadius, -self.HoverHullRadius, 0 ), + maxs = Vector( self.HoverHullRadius, self.HoverHullRadius, 0 ), + filter = function( entity ) + if self:GetCrosshairFilterLookup()[ entity:EntIndex() ] or entity:IsPlayer() or entity:IsNPC() or entity:IsVehicle() or self.HoverCollisionFilter[ entity:GetCollisionGroup() ] then + return false + end + + return true + end, + } + + local trace = util.TraceHull( tracedata ) + local traceLine = util.TraceLine( tracedata ) + + local OnGround = (trace.Hit or traceLine.hit) and not trace.HitSky and not traceLine.HitSky + + self:ToggleGravity( phys, not OnGround ) + + local Pos = trace.HitPos + if traceLine.Hit then + Pos = traceLine.HitPos + end + + local CurDist = (Pos - masscenter):Length() + + local X, Y = self:GetMoveXY( base, phys ) + local Z = ((self:GetHoverHeight( base, phys ) - CurDist) * 3 - velL.z * 0.5) + + local Normal = self:TransformNormal( base, trace.HitNormal ) + local Pitch = self:AngleBetweenNormal( Normal, forward ) - 90 + local Roll = self:AngleBetweenNormal( Normal, right ) - 90 + + local ForceLinear = Vector(X,Y,Z) * 2000 * deltatime * self.ForceLinearMultiplier + local ForceAngle = ( Vector(-Roll,-Pitch, self:GetSteer( base, phys ) ) * 12 * self.ForceAngleMultiplier - phys:GetAngleVelocity() * self.ForceAngleDampingMultiplier) * 400 * deltatime + + local SIMULATE = OnGround and SIM_LOCAL_ACCELERATION or SIM_NOTHING + + return ForceAngle, ForceLinear, SIMULATE +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/sh_camera_eyetrace.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/sh_camera_eyetrace.lua new file mode 100644 index 0000000..c7beca4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/sh_camera_eyetrace.lua @@ -0,0 +1,42 @@ + +function ENT:GetEyeTrace( trace_forward ) + local startpos = self:LocalToWorld( self:OBBCenter() ) + + local pod = self:GetDriverSeat() + + if IsValid( pod ) then + startpos = pod:LocalToWorld( pod:OBBCenter() ) + end + + local AimVector = trace_forward and self:GetForward() or self:GetAimVector() + + local data = { + start = startpos, + endpos = (startpos + AimVector * 50000), + filter = self:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( data ) + + return trace +end + +function ENT:GetAimVector() + if self:GetAI() then + return self:GetForward() + end + + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then return self:GetForward() end + + local Driver = self:GetDriver() + + if not IsValid( Driver ) then return pod:GetForward() end + + if SERVER then + return Driver:EyeAngles():Forward() + else + return pod:LocalToWorldAngles( Driver:EyeAngles() ):Forward() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/shared.lua new file mode 100644 index 0000000..0bd52d9 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_walker_atte_hoverscript/shared.lua @@ -0,0 +1,47 @@ + +ENT.Base = "lvs_base" + +ENT.PrintName = "[LVS] Generic Hover" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS]" + +ENT.ForceLinearMultiplier = 1 + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +ENT.HoverHeight = 140 +ENT.HoverTraceLength = 200 +ENT.HoverHullRadius = 20 + +ENT.HoverCollisionFilter = { + [COLLISION_GROUP_DEBRIS] = true, + [COLLISION_GROUP_DEBRIS_TRIGGER] = true, + [COLLISION_GROUP_PLAYER] = true, + [COLLISION_GROUP_WEAPON] = true, + [COLLISION_GROUP_VEHICLE_CLIP] = true, + [COLLISION_GROUP_WORLD] = true, +} + +ENT.DisableBallistics = true + +function ENT:SetupDataTables() + self:CreateBaseDT() +end + +function ENT:GetCrosshairFilterLookup() + if self._EntityLookUp then return self._EntityLookUp end + + self._EntityLookUp = {} + + for _, ent in pairs( self:GetCrosshairFilterEnts() ) do + self._EntityLookUp[ ent:EntIndex() ] = true + end + + return self._EntityLookUp +end + +function ENT:GetVehicleType() + return "walker" +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_ammorack.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_ammorack.lua new file mode 100644 index 0000000..8f883da --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_ammorack.lua @@ -0,0 +1,161 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + + self:NetworkVar( "Float",0, "HP" ) + self:NetworkVar( "Float",1, "MaxHP" ) + + self:NetworkVar( "Bool",0, "Destroyed" ) + + self:NetworkVar( "Vector",0, "EffectPosition" ) + + if SERVER then + self:SetMaxHP( 100 ) + self:SetHP( 100 ) + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + end + + function ENT:Think() + self:NextThink( CurTime() + 1 ) + + if self:GetDestroyed() then + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + local dmg = DamageInfo() + dmg:SetDamage( 100 ) + dmg:SetAttacker( IsValid( Base.LastAttacker ) and Base.LastAttacker or game.GetWorld() ) + dmg:SetInflictor( IsValid( Base.LastInflictor ) and Base.LastInflictor or game.GetWorld() ) + dmg:SetDamageType( DMG_BURN ) + Base:TakeDamageInfo( dmg ) + end + + return true + end + + function ENT:TakeTransmittedDamage( dmginfo ) + if self:GetDestroyed() then return end + + local Damage = dmginfo:GetDamage() + + if Damage <= 0 then return end + + local CurHealth = self:GetHP() + + local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() ) + + self:SetHP( NewHealth ) + + if NewHealth <= 0 then + self:SetDestroyed( true ) + + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + Base:Lock() + + for _, ply in pairs( Base:GetEveryone() ) do + Base:HurtPlayer( ply, ply:Health() + ply:Armor(), dmginfo:GetAttacker(), dmginfo:GetInflictor() ) + end + end + end + + function ENT:OnTakeDamage( dmginfo ) + if not dmginfo:IsDamageType( DMG_BURN ) then return end + + dmginfo:ScaleDamage( 0.5 ) + + self:TakeTransmittedDamage( dmginfo ) + end + + return +end + +function ENT:Initialize() +end + +function ENT:RemoveFireSound() + if self.FireBurnSND then + self.FireBurnSND:Stop() + self.FireBurnSND = nil + end + + self.ShouldStopFire = nil +end + +function ENT:StopFireSound() + if self.ShouldStopFire or not self.FireBurnSND then return end + + self.ShouldStopFire = true + + self:EmitSound("ambient/fire/mtov_flame2.wav") + + self.FireBurnSND:ChangeVolume( 0, 0.5 ) + + timer.Simple( 1, function() + if not IsValid( self ) then return end + + self:RemoveFireSound() + end ) +end + +function ENT:StartFireSound() + if self.ShouldStopFire or self.FireBurnSND then return end + + self.FireBurnSND = CreateSound( self, "lvs/ammo_fire_loop.wav" ) + self.FireBurnSND:SetSoundLevel( 85 ) + + self.FireBurnSND:PlayEx(0,100) + + self.FireBurnSND:ChangeVolume( 1, 1 ) + + self:EmitSound("lvs/ammo_fire.wav") + + self.StartFireTime = CurTime() +end + +function ENT:OnRemove() + self:RemoveFireSound() +end + +function ENT:Draw() +end + +function ENT:Think() + local T = CurTime() + + self:SetNextClientThink( T + 0.05 ) + + if not self:GetDestroyed() then + self:StopFireSound() + + return true + end + + self:StartFireSound() + + local Scale = math.min( (T - (self.StartFireTime or T)) / 2, 1 ) + + local Base = self:GetBase() + + local effectdata = EffectData() + effectdata:SetOrigin( Base:LocalToWorld( self:GetEffectPosition() ) ) + effectdata:SetEntity( Base ) + effectdata:SetMagnitude( Scale ) + util.Effect( "lvs_ammorack_fire", effectdata ) + + return true +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_attachment_follower.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_attachment_follower.lua new file mode 100644 index 0000000..cb8f11b --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_attachment_follower.lua @@ -0,0 +1,187 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Entity",1, "Master" ) +end + +function ENT:GetMins() + return self:OBBMins() +end + +function ENT:GetMaxs() + return self:OBBMaxs() +end + +if SERVER then + function ENT:SetFollowAttachment( id ) + self._attidFollow = id + end + + function ENT:Initialize() + self:SetUseType( SIMPLE_USE ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + + -- this is so vj npcs can still see us + self:AddEFlags( EFL_DONTBLOCKLOS ) + + self:DrawShadow( false ) + + self:SetMaterial( "models/wireframe" ) + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetMass( 1 ) + PhysObj:EnableDrag( false ) + PhysObj:EnableGravity( false ) + PhysObj:EnableMotion( true ) + + timer.Simple( 0, function() + if not IsValid( self ) or not self.GetBase then return end + + local Base = self:GetBase() + + if not IsValid( Base ) or not Base.GetWheels then return end + + for _, Wheel in pairs( Base:GetWheels() ) do + if not IsValid( Wheel ) then continue end + + local nocollide_constraint = constraint.NoCollide(self,Wheel,0,0) + nocollide_constraint.DoNotDuplicate = true + end + end ) + end + + function ENT:Think() + local T = CurTime() + + self:NextThink( T ) + + local Base = self:GetBase() + local Master = self:GetMaster() + + if not self._attidFollow or not IsValid( Base ) or not IsValid( Master ) then return true end + + local PhysObj = Master:GetPhysicsObject() + + if not IsValid( PhysObj ) then return true end + + if PhysObj:IsMotionEnabled() then PhysObj:EnableMotion( false ) end + + local att = Base:GetAttachment( self._attidFollow ) + + if not att then self:NextThink( T + 1 ) return true end + + local OldAng = Master:GetAngles() + local NewAng = att.Ang + + if OldAng ~= NewAng then + Master:SetAngles( att.Ang ) + self:PhysWake() + end + + return true + end + + function ENT:OnHealthChanged( dmginfo, old, new ) + end + + function ENT:OnRepaired() + end + + function ENT:OnDestroyed( dmginfo ) + end + + function ENT:OnTakeDamage( dmginfo ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + local OldTotalHealth = 0 + local NewTotalHealth = 0 + local CallDestroyed = false + + local children = self:GetChildren() + + for _, entity in pairs( children ) do + if entity:GetClass() ~= "lvs_armor" then continue end + + OldTotalHealth = OldTotalHealth + entity:GetHP() + + if entity._IsRepairFunctionTagged then continue end + + entity._IsRepairFunctionTagged = true + + local OldOnRepaired = entity.OnRepaired + + entity.OnRepaired = function( ent ) + if IsValid( self ) then + self:OnRepaired() + end + + OldOnRepaired( ent ) + end + end + + base:OnTakeDamage( dmginfo ) + + for _, entity in pairs( children ) do + if entity:GetClass() ~= "lvs_armor" then continue end + + local HP = entity:GetHP() + + NewTotalHealth = NewTotalHealth + HP + + if HP > 0 then continue end + + CallDestroyed = true + end + + self:OnHealthChanged( dmginfo, OldTotalHealth, NewTotalHealth ) + + if CallDestroyed then + self:OnDestroyed( dmginfo ) + end + end + + function ENT:PhysicsCollide( data, phys ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:PhysicsCollide( data, phys ) + end + + function ENT:Use( ply ) + if (ply._lvsNextUse or 0) > CurTime() then return end + + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:Use( ply ) + end + + function ENT:OnRemove() + end + + return +end + +function ENT:Initialize() +end + +function ENT:OnRemove() +end + +function ENT:Draw() + if not LVS.DeveloperEnabled then return end + + self:DrawModel() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_destruction.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_destruction.lua new file mode 100644 index 0000000..9858dc0 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_destruction.lua @@ -0,0 +1,79 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +if SERVER then + function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + + self.Vel = isvector( self.Vel ) and self.Vel or Vector(0,0,0) + + local fxPos = self:LocalToWorld( self:OBBCenter() ) + + local effectdata = EffectData() + effectdata:SetOrigin( fxPos ) + util.Effect( "lvs_explosion_bomb", effectdata ) + + self.Gibs = {} + + if not istable( self.GibModels ) then return end + + local Speed = self.Vel:Length() + + for _, v in pairs( self.GibModels ) do + local ent = ents.Create( "prop_physics" ) + + if not IsValid( ent ) then continue end + + table.insert( self.Gibs, ent ) + + ent:SetPos( self:GetPos() ) + ent:SetAngles( self:GetAngles() ) + ent:SetModel( v ) + ent:Spawn() + ent:Activate() + ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + + local PhysObj = ent:GetPhysicsObject() + if IsValid( PhysObj ) then + if Speed <= 250 then + local GibDir = Vector( math.Rand(-1,1), math.Rand(-1,1), 1.5 ):GetNormalized() + PhysObj:SetVelocityInstantaneous( GibDir * math.random(800,1300) ) + else + PhysObj:SetVelocityInstantaneous( VectorRand() * math.max(300,self.Vel:Length() / 3) + self.Vel ) + end + + PhysObj:AddAngleVelocity( VectorRand() * 500 ) + PhysObj:EnableDrag( false ) + + local effectdata = EffectData() + effectdata:SetOrigin( fxPos ) + effectdata:SetStart( PhysObj:GetMassCenter() ) + effectdata:SetEntity( ent ) + effectdata:SetScale( math.Rand(0.3,0.7) ) + effectdata:SetMagnitude( math.Rand(0.5,2.5) ) + util.Effect( "lvs_firetrail", effectdata ) + end + end + end + + function ENT:Think() + return false + end + + function ENT:OnRemove() + if istable( self.Gibs ) then + for _, v in pairs( self.Gibs ) do + if IsValid( v ) then + v:Remove() + end + end + end + end +else + function ENT:Draw() + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine.lua new file mode 100644 index 0000000..4ef41f8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine.lua @@ -0,0 +1,640 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +ENT._LVS = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Entity",1, "DoorHandler" ) + + self:NetworkVar( "Float",1, "HP" ) + self:NetworkVar( "Float",2, "MaxHP" ) + + self:NetworkVar( "Bool",0, "Destroyed" ) + + if SERVER then + self:SetMaxHP( 100 ) + self:SetHP( 100 ) + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + end + + function ENT:CheckWater( Base ) + local EntTable = self:GetTable() + + if bit.band( util.PointContents( self:GetPos() ), CONTENTS_WATER ) ~= CONTENTS_WATER then + if EntTable.CountWater then + EntTable.CountWater = nil + end + + return + end + + if Base.WaterLevelAutoStop > 3 then return end + + EntTable.CountWater = (EntTable.CountWater or 0) + 1 + + if EntTable.CountWater < 4 then return end + + Base:StopEngine() + end + + function ENT:Think() + + local Base = self:GetBase() + + if IsValid( Base ) and Base:GetEngineActive() then + self:CheckWater( Base ) + end + + self:NextThink( CurTime() + 1 ) + + return true + end + + function ENT:OnDestroyed() + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + util.Effect( "lvs_trailer_explosion", effectdata, true, true ) + + self:EmitSound("physics/metal/metal_box_break"..math.random(1,2)..".wav",75,100,1) + + local base = self:GetBase() + + if not IsValid( base ) then return end + + net.Start( "lvs_car_break" ) + net.WriteEntity( base ) + net.Broadcast() + + if base:GetEngineActive() then + self:EmitSound("npc/manhack/bat_away.wav",75,100,0.5) + + timer.Simple(1, function() + if not IsValid( self ) then return end + self:EmitSound("npc/manhack/gib.wav",75,90,1) + end) + end + + base:ShutDownEngine() + end + + function ENT:TakeTransmittedDamage( dmginfo ) + if self:GetDestroyed() then return end + + local Damage = dmginfo:GetDamage() + + if Damage <= 0 then return end + + local CurHealth = self:GetHP() + + local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() ) + + self:SetHP( NewHealth ) + + if NewHealth <= 0 then + self:SetDestroyed( true ) + + self:OnDestroyed() + end + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS + end + + function ENT:OnRemove() + local base = self:GetBase() + + if not IsValid( base ) or base.ExplodedAlready then return end + + base:SetMaxThrottle( 1 ) + end + + return +end + +ENT._oldEnActive = false +ENT._ActiveSounds = {} + +function ENT:Initialize() +end + +function ENT:StopSounds() + for id, sound in pairs( self._ActiveSounds ) do + if istable( sound ) then + for _, snd in pairs( sound ) do + if snd then + snd:Stop() + end + end + else + sound:Stop() + end + self._ActiveSounds[ id ] = nil + end +end + +function ENT:OnEngineActiveChanged( Active ) + if not Active then self:StopSounds() return end + + for id, data in pairs( self.EngineSounds ) do + if not isstring( data.sound ) then continue end + + self.EngineSounds[ id ].Pitch = data.Pitch or 100 + self.EngineSounds[ id ].PitchMul = data.PitchMul or 100 + self.EngineSounds[ id ].Volume = data.Volume or 1 + self.EngineSounds[ id ].SoundType = data.SoundType or LVS.SOUNDTYPE_NONE + self.EngineSounds[ id ].UseDoppler = data.UseDoppler ~= false + self.EngineSounds[ id ].SoundLevel = data.SoundLevel or 85 + + if data.sound_int and data.sound_int ~= data.sound then + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + if data.sound_int == "" then + self._ActiveSounds[ id ] = { + ext = sound, + int = false, + } + else + local sound_interior = CreateSound( self, data.sound_int ) + sound_interior:SetSoundLevel( data.SoundLevel ) + sound_interior:PlayEx(0,100) + + self._ActiveSounds[ id ] = { + ext = sound, + int = sound_interior, + } + end + else + local sound = CreateSound( self, data.sound ) + sound:SetSoundLevel( data.SoundLevel ) + sound:PlayEx(0,100) + + self._ActiveSounds[ id ] = sound + end + end +end + +function ENT:SetGear( newgear ) + self._CurGear = newgear +end + +function ENT:GetGear() + return (self._CurGear or 1) +end + +function ENT:SetRPM( rpm ) + self._CurRPM = rpm +end + +function ENT:GetRPM() + local base = self:GetBase() + + if not IsValid( base ) or not base:GetEngineActive() then return 0 end + + return math.abs(self._CurRPM or 0) +end + +function ENT:GetClutch() + return self._ClutchActive == true +end + +function ENT:SetEngineVolume( volume ) + self._engineVolume = volume + + return volume +end + +function ENT:GetEngineVolume() + return (self._engineVolume or 0) +end + +function ENT:HandleEngineSounds( vehicle ) + local ply = LocalPlayer() + local pod = ply:GetVehicle() + local Throttle = vehicle:GetThrottle() + local MaxThrottle = vehicle:GetMaxThrottle() + local Doppler = vehicle:CalcDoppler( ply ) + + local EntTable = self:GetTable() + + local DrivingMe = ply:lvsGetVehicle() == vehicle + + local IsManualTransmission = vehicle:IsManualTransmission() + + local VolumeSetNow = false + + local FirstPerson = false + if IsValid( pod ) then + local ThirdPerson = pod:GetThirdPersonMode() + + if ThirdPerson ~= EntTable._lvsoldTP then + EntTable._lvsoldTP = ThirdPerson + VolumeSetNow = DrivingMe + end + + FirstPerson = DrivingMe and not ThirdPerson + end + + if DrivingMe ~= EntTable._lvsoldDrivingMe then + EntTable._lvsoldDrivingMe = DrivingMe + + self:StopSounds() + + EntTable._oldEnActive = nil + + return + end + + local FT = RealFrameTime() + local T = CurTime() + + local Reverse = vehicle:GetReverse() + local vehVel = vehicle:GetVelocity():Length() + local wheelVel = vehicle:GetWheelVelocity() + + local IsHandBraking = wheelVel == 0 and vehicle:GetNWHandBrake() + + local Vel = 0 + local Wobble = 0 + + if vehVel / wheelVel <= 0.8 then + Vel = wheelVel + Wobble = -1 + else + Vel = vehVel + end + + local NumGears = vehicle.TransGears + local MaxGear = Reverse and vehicle.TransGearsReverse or NumGears + + local VolumeValue = self:SetEngineVolume( LVS.EngineVolume ) + local PitchValue = vehicle.MaxVelocity / NumGears + + local DesiredGear = 1 + + local subGeared = vehVel - (EntTable._smVelGeared or 0) + local VelocityGeared = vehVel + + if IsHandBraking then + VelocityGeared = PitchValue * Throttle + Vel = VelocityGeared + end + + --[[ workaround ]]-- TODO: Fix it properly + if vehicle:Sign( subGeared ) < 0 then + self._smVelGeared = (EntTable._smVelGeared or 0) + subGeared * FT * 5 + VelocityGeared = EntTable._smVelGeared + else + EntTable._smVelGeared = VelocityGeared + end + --[[ workaround ]]-- + + + while (VelocityGeared > PitchValue) and DesiredGear< NumGears do + VelocityGeared = VelocityGeared - PitchValue + + DesiredGear = DesiredGear + 1 + end + + if IsManualTransmission then + EntTable._NextShift = 0 + + if IsHandBraking then + DesiredGear = 1 + else + DesiredGear = vehicle:GetGear() + end + else + DesiredGear = math.Clamp( DesiredGear, 1, MaxGear ) + end + + local CurrentGear = math.Clamp(self:GetGear(),1,NumGears) + + local RatioThrottle = 0.5 + (Throttle ^ 2) * 0.5 + + local RatioPitch = math.max(Vel - (CurrentGear - 1) * PitchValue,0) + + if (not IsManualTransmission or IsHandBraking or vehicle.EngineRevLimited) then + RatioPitch = math.min( PitchValue, RatioPitch ) + end + + local preRatio = math.Clamp(Vel / (PitchValue * (CurrentGear - 1)),0,1) + local Ratio = (RatioPitch / PitchValue) * RatioThrottle + + if CurrentGear ~= DesiredGear then + if (EntTable._NextShift or 0) < T then + EntTable._NextShift = T + vehicle.TransMinGearHoldTime + + if CurrentGear < DesiredGear then + EntTable._ShiftTime = T + vehicle.TransShiftSpeed + EntTable._WobbleTime = T + vehicle.TransWobbleTime + end + + vehicle:OnChangeGear( CurrentGear, DesiredGear ) + + self:SetGear( DesiredGear ) + end + end + + if Throttle > 0.5 then + local FullThrottle = Throttle >= 0.99 + + if EntTable._oldFullThrottle ~= FullThrottle then + EntTable._oldFullThrottle = FullThrottle + + if FullThrottle then + EntTable._WobbleTime = T + vehicle.TransWobbleTime + end + end + + if Wobble == 0 then + local Mul = math.Clamp( (EntTable._WobbleTime or 0) - T, 0, 1 ) + + Wobble = (math.cos( T * (20 + CurrentGear * 10) * vehicle.TransWobbleFrequencyMultiplier ) * math.max(1 - Ratio,0) * vehicle.TransWobble * math.max(1 - vehicle:AngleBetweenNormal( vehicle:GetUp(), Vector(0,0,1) ) / 5,0) ^ 2) * Mul + end + end + + local FadeSpeed = 0.15 + local PlayIdleSound = CurrentGear == 1 and Throttle == 0 and Ratio < 0.5 + local rpmSet = false + local rpmRate = PlayIdleSound and 1 or 5 + + if IsManualTransmission and (self:GetRPM() < vehicle.EngineIdleRPM or EntTable.ForcedIdle) then + if EntTable.ForcedIdle then + self:SetRPM( vehicle.EngineIdleRPM ) + PlayIdleSound = true + rpmRate = 1 + EntTable._ClutchActive = true + + if Ratio > 0 or Throttle > 0 then + EntTable.ForcedIdle = nil + end + else + if Ratio == 0 and Throttle == 0 then + EntTable.ForcedIdle = true + end + end + end + + EntTable._smIdleVolume = EntTable._smIdleVolume and EntTable._smIdleVolume + ((PlayIdleSound and 1 or 0) - EntTable._smIdleVolume) * FT or 0 + EntTable._smRPMVolume = EntTable._smRPMVolume and EntTable._smRPMVolume + ((PlayIdleSound and 0 or 1) - EntTable._smRPMVolume) * FT * rpmRate or 0 + + if (EntTable._ShiftTime or 0) > T or PlayIdleSound then + PitchAdd = 0 + Ratio = 0 + Wobble = 0 + Throttle = 0 + FadeSpeed = PlayIdleSound and 0.25 or 3 + EntTable._ClutchActive = true + else + EntTable._ClutchActive = false + end + + if IsManualTransmission and IsHandBraking then + EntTable._ClutchActive = true + end + + if not EntTable.EnginePitchStep then + EntTable.EnginePitchStep = math.Clamp(vehicle.EngineMaxRPM / 10000, 0.6, 0.9) + + return + end + + for id, sound in pairs( EntTable._ActiveSounds ) do + if not sound then continue end + + local data = EntTable.EngineSounds[ id ] + + local Vol03 = data.Volume * 0.3 + local Vol02 = data.Volume * 0.2 + + local Volume = (Vol02 + Vol03 * Ratio + (Vol02 * Ratio + Vol03) * Throttle) * VolumeValue + + local PitchAdd = CurrentGear * (data.PitchMul / NumGears * EntTable.EnginePitchStep) * MaxThrottle + + local Pitch = data.Pitch + PitchAdd + (data.PitchMul - PitchAdd) * Ratio + Wobble + local PitchMul = data.UseDoppler and Doppler or 1 + + if IsManualTransmission and Ratio == 0 and preRatio < 1 and not PlayIdleSound then + Pitch = (PitchAdd / CurrentGear) * (1 - preRatio) + (data.Pitch + PitchAdd) * preRatio + Wobble + end + + local SoundType = data.SoundType + + if SoundType ~= LVS.SOUNDTYPE_ALL then + Volume = Volume * EntTable._smRPMVolume + + if SoundType == LVS.SOUNDTYPE_IDLE_ONLY then + Volume = EntTable._smIdleVolume * data.Volume * VolumeValue + Pitch = data.Pitch + data.PitchMul * Ratio + end + + if SoundType == LVS.SOUNDTYPE_REV_UP then + Volume = Throttle == 0 and 0 or Volume + end + + if SoundType == LVS.SOUNDTYPE_REV_DOWN then + Volume = Throttle == 0 and Volume or 0 + end + end + + if istable( sound ) then + sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), FadeSpeed ) + + if sound.int then + sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), FadeSpeed ) + end + + local fadespeed = VolumeSetNow and 0 or 0.15 + + if FirstPerson then + sound.ext:ChangeVolume( 0, 0 ) + + if vehicle:HasActiveSoundEmitters() then + Volume = Volume * 0.25 + fadespeed = fadespeed * 0.5 + end + + if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end + else + sound.ext:ChangeVolume( Volume, fadespeed ) + if sound.int then sound.int:ChangeVolume( 0, 0 ) end + end + else + sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), FadeSpeed ) + sound:ChangeVolume( Volume, 0.15 ) + end + + if rpmSet then continue end + + if PlayIdleSound then self:SetRPM( vehicle.EngineIdleRPM ) rpmSet = true continue end + + if data.SoundType == LVS.SOUNDTYPE_IDLE_ONLY then continue end + + if istable( sound ) then + if sound.int then + rpmSet = true + self:SetRPM( vehicle.EngineIdleRPM + ((sound.int:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) ) + else + if not sound.ext then continue end + + rpmSet = true + self:SetRPM( vehicle.EngineIdleRPM + ((sound.ext:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) ) + end + else + rpmSet = true + self:SetRPM( vehicle.EngineIdleRPM + ((sound:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) ) + end + end +end + +function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + local EntTable = self:GetTable() + + self:DamageFX( vehicle ) + + if not EntTable.EngineSounds then + EntTable.EngineSounds = vehicle.EngineSounds + + return + end + + local EngineActive = vehicle:GetEngineActive() + + if EntTable._oldEnActive ~= EngineActive then + EntTable._oldEnActive = EngineActive + + self:OnEngineActiveChanged( EngineActive ) + end + + if EngineActive then + self:HandleEngineSounds( vehicle ) + self:ExhaustFX( vehicle ) + end +end + +function ENT:RemoveFireSound() + if self.FireBurnSND then + self.FireBurnSND:Stop() + self.FireBurnSND = nil + end + + self.ShouldStopFire = nil +end + +function ENT:StopFireSound() + if self.ShouldStopFire or not self.FireBurnSND then return end + + self.ShouldStopFire = true + + self:EmitSound("ambient/fire/mtov_flame2.wav") + + self.FireBurnSND:ChangeVolume( 0, 0.5 ) + + timer.Simple( 1, function() + if not IsValid( self ) then return end + + self:RemoveFireSound() + end ) +end + +function ENT:StartFireSound() + if self.ShouldStopFire or self.FireBurnSND then return end + + self.FireBurnSND = CreateSound( self, "ambient/fire/firebig.wav" ) + self.FireBurnSND:PlayEx(0,100) + self.FireBurnSND:ChangeVolume( LVS.EngineVolume, 1 ) + + self:EmitSound("ambient/fire/ignite.wav") +end + +function ENT:OnRemove() + self:StopSounds() + self:RemoveFireSound() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end + +function ENT:ExhaustFX( vehicle ) + if not istable( vehicle.ExhaustPositions ) then return end + + local T = CurTime() + + if (self.nextEFX or 0) > T then return end + + self.nextEFX = T + 0.1 + + vehicle:DoExhaustFX( (self:GetRPM() / vehicle.EngineMaxRPM) * 0.5 + 0.5 * vehicle:GetThrottle() ) +end + +function ENT:DamageFX( vehicle ) + local T = CurTime() + local HP = self:GetHP() + local MaxHP = self:GetMaxHP() + + local EntTable = self:GetTable() + + if HP >= MaxHP * 0.5 then self:StopFireSound() return end + + if (EntTable.nextDFX or 0) > T then return end + + EntTable.nextDFX = T + 0.05 + + if self:GetDestroyed() then + if not EntTable._FireStopTime then + EntTable._FireStopTime = T + math.random(20,40) + end + + if EntTable ._FireStopTime < T then + self:StopFireSound() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( vehicle ) + util.Effect( "lvs_carengine_blacksmoke", effectdata ) + + return + end + + self:StartFireSound() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( vehicle ) + util.Effect( "lvs_carengine_fire", effectdata ) + else + EntTable._FireStopTime = nil + + self:StopFireSound() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( vehicle ) + effectdata:SetMagnitude( math.max(HP,0) / (MaxHP * 0.5) ) + util.Effect( "lvs_carengine_smoke", effectdata ) + end +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine_mod.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine_mod.lua new file mode 100644 index 0000000..6cbdd58 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine_mod.lua @@ -0,0 +1,184 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT._LVS = true + +ENT.Editable = true + +ENT.PhysicsSounds = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + + self:NetworkVar( "Float",0, "EngineCurve", { KeyName = "addpower", Edit = { type = "Float", order = 1,min = 0, max = 0.5, category = "Upgrade Settings"} } ) + self:NetworkVar( "Int",1, "EngineTorque", { KeyName = "addtorque", Edit = { type = "Int", order = 2,min = 0, max = 100, category = "Upgrade Settings"} } ) + + if SERVER then + self:SetEngineCurve( 0.25 ) + self:SetEngineTorque( 50 ) + + self:NetworkVarNotify( "EngineCurve", self.OnEngineCurveChanged ) + self:NetworkVarNotify( "EngineTorque", self.OnEngineTorqueChanged ) + end +end + +function ENT:GetBoost() + if not self._smBoost then return 0 end + + return self._smBoost +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + ent.PlaySound = true + ent:SetEngineTorque( 15 ) + + return ent + end + + function ENT:Initialize() + end + + function ENT:Think() + return false + end + + function ENT:CanLink( ent ) + return true + end + + function ENT:OnLinked( ent ) + end + + function ENT:OnUnLinked( ent ) + end + + function ENT:OnVehicleUpdated() + end + + local function ResetEngine( ply, ent, data ) + if not duplicator or not duplicator.StoreEntityModifier then return end + + if data.Curve then ent.EngineCurve = data.Curve end + if data.Torque then ent.EngineTorque = data.Torque end + + duplicator.StoreEntityModifier( ent, "lvsCarResetEngine", data ) + end + + if duplicator and duplicator.RegisterEntityModifier then + duplicator.RegisterEntityModifier( "lvsCarResetEngine", ResetEngine ) + end + + function ENT:LinkTo( ent ) + if not IsValid( ent ) or not ent.LVS or not self:CanLink( ent ) then return end + + local engine = ent:GetEngine() + + if not IsValid( engine ) then return end + + self.DoNotDuplicate = true + + self:PhysicsDestroy() + + self:SetSolid( SOLID_NONE ) + self:SetMoveType( MOVETYPE_NONE ) + + self:SetPos( engine:GetPos() ) + self:SetAngles( engine:GetAngles() ) + + self:SetParent( engine ) + + self:SetBase( ent ) + + if not ent.OriginalTorque or not ent.OriginalCurve then + ent.OriginalTorque = ent.EngineTorque + ent.OriginalCurve = ent.EngineCurve + + local data = { + Curve = ent.OriginalCurve, + Torque = ent.OriginalTorque, + } + + duplicator.StoreEntityModifier( ent, "lvsCarResetEngine", data ) + end + + ent.EngineCurve = ent.EngineCurve + self:GetEngineCurve() + ent.EngineTorque = ent.EngineTorque + self:GetEngineTorque() + + self:OnVehicleUpdated( ent ) + self:OnLinked( ent ) + end + + function ENT:PhysicsCollide( data ) + if self.HasTouched then return end + + self.HasTouched = true + + timer.Simple(0, function() + if not IsValid( self ) then return end + + self.HasTouched = nil + + self:LinkTo( data.HitEntity ) + end) + end + + function ENT:OnRemove() + local base = self:GetBase() + + if not IsValid( base ) or base.ExplodedAlready then return end + + base.EngineCurve = base.EngineCurve - self:GetEngineCurve() + base.EngineTorque = base.EngineTorque - self:GetEngineTorque() + + self:OnVehicleUpdated( base ) + + self:OnUnLinked( base ) + end + + function ENT:OnEngineCurveChanged( name, old, new ) + if old == new then return end + + local ent = self:GetBase() + + if not IsValid( ent ) then return end + + ent.EngineCurve = ent.EngineCurve - old + new + + self:OnVehicleUpdated( ent ) + end + + function ENT:OnEngineTorqueChanged( name, old, new ) + if old == new then return end + + local ent = self:GetBase() + + if not IsValid( ent ) then return end + + ent.EngineTorque = ent.EngineTorque - old + new + + self:OnVehicleUpdated( ent ) + end + + return +end + +function ENT:Initialize() +end + +function ENT:Think() +end + +function ENT:OnRemove() +end + +function ENT:Draw( flags ) + self:DrawModel( flags ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine_swapped.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine_swapped.lua new file mode 100644 index 0000000..cca47b8 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_engine_swapped.lua @@ -0,0 +1,66 @@ +AddCSLuaFile() + +ENT.Base = "lvs_wheeldrive_engine" +DEFINE_BASECLASS( "lvs_wheeldrive_engine" ) + +ENT.DoNotDuplicate = true + +ENT._LVS = true + +ENT.lvsEngine = true + +if SERVER then + util.AddNetworkString( "lvs_engine_swap" ) + + net.Receive("lvs_engine_swap", function( len, ply ) + local ent = net.ReadEntity() + + if not IsValid( ent ) or not ent.lvsEngine then return end + + if not istable( ent.EngineSounds ) then return end + + net.Start("lvs_engine_swap") + net.WriteEntity( ent ) + net.WriteTable( ent.EngineSounds ) + net.Send( ply ) + end) +else + net.Receive("lvs_engine_swap", function( len ) + local ent = net.ReadEntity() + + if not IsValid( ent ) then return end + + ent.EngineSounds = net.ReadTable() + end) + + function ENT:Think() + local vehicle = self:GetBase() + + if not IsValid( vehicle ) then return end + + self:DamageFX( vehicle ) + + if not self.EngineSounds then + self.EngineSounds = {} + + net.Start("lvs_engine_swap") + net.WriteEntity( self ) + net.SendToServer() + + return + end + + local EngineActive = vehicle:GetEngineActive() + + if self._oldEnActive ~= EngineActive then + self._oldEnActive = EngineActive + + self:OnEngineActiveChanged( EngineActive ) + end + + if EngineActive then + self:HandleEngineSounds( vehicle ) + self:ExhaustFX( vehicle ) + end + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_fueltank.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_fueltank.lua new file mode 100644 index 0000000..120a2cc --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_fueltank.lua @@ -0,0 +1,191 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Entity",1, "DoorHandler" ) + + self:NetworkVar( "Float",0, "Fuel" ) + self:NetworkVar( "Float",1, "NWSize" ) + self:NetworkVar( "Float",2, "HP" ) + self:NetworkVar( "Float",3, "MaxHP" ) + + self:NetworkVar( "Int",0, "FuelType" ) + + self:NetworkVar( "Bool",0, "Destroyed" ) + + if SERVER then + self:SetMaxHP( 100 ) + self:SetHP( 100 ) + self:SetFuel( 1 ) + self:NetworkVarNotify( "Fuel", self.OnFuelChanged ) + end +end + +function ENT:SetSize( num ) + self:SetNWSize( num ) +end + +function ENT:GetSize() + return self:GetNWSize() * LVS.FuelScale +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 20, 5, Color( 255, 93, 0 ) ) + end + + function ENT:ExtinguishAndRepair() + self:SetHP( self:GetMaxHP() ) + self:SetDestroyed( false ) + end + + function ENT:Think() + self:NextThink( CurTime() + 1 ) + + if self:GetDestroyed() then + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + if self:GetFuel() > 0 then + local dmg = DamageInfo() + dmg:SetDamage( 10 ) + dmg:SetAttacker( IsValid( Base.LastAttacker ) and Base.LastAttacker or game.GetWorld() ) + dmg:SetInflictor( IsValid( Base.LastInflictor ) and Base.LastInflictor or game.GetWorld() ) + dmg:SetDamageType( DMG_BURN ) + Base:TakeDamageInfo( dmg ) + + self:SetFuel( math.max( self:GetFuel() - 0.05, 0 ) ) + else + self:SetDestroyed( false ) + end + else + local base = self:GetBase() + + if IsValid( base ) and base:GetEngineActive() then + self:SetFuel( math.max( self:GetFuel() - (1 / self:GetSize()) * base:GetThrottle() ^ 2, 0 ) ) + end + end + + return true + end + + function ENT:TakeTransmittedDamage( dmginfo ) + if self:GetDestroyed() then return end + + local Damage = dmginfo:GetDamage() + + if Damage <= 0 then return end + + local CurHealth = self:GetHP() + + local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() ) + + self:SetHP( NewHealth ) + + if NewHealth <= 0 then + self:SetDestroyed( true ) + end + end + + function ENT:OnTakeDamage( dmginfo ) + if not dmginfo:IsDamageType( DMG_BURN ) then return end + + self:TakeTransmittedDamage( dmginfo ) + end + + function ENT:OnFuelChanged( name, old, new) + if new == old then return end + + if new <= 0 then + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:ShutDownEngine() + + local engine = base:GetEngine() + + if not IsValid( engine ) then return end + + engine:EmitSound("vehicles/jetski/jetski_off.wav") + end + end + + return +end + +function ENT:Initialize() +end + +function ENT:RemoveFireSound() + if self.FireBurnSND then + self.FireBurnSND:Stop() + self.FireBurnSND = nil + end + + self.ShouldStopFire = nil +end + +function ENT:StopFireSound() + if self.ShouldStopFire or not self.FireBurnSND then return end + + self.ShouldStopFire = true + + self:EmitSound("ambient/fire/mtov_flame2.wav") + + self.FireBurnSND:ChangeVolume( 0, 0.5 ) + + timer.Simple( 1, function() + if not IsValid( self ) then return end + + self:RemoveFireSound() + end ) +end + +function ENT:StartFireSound() + if self.ShouldStopFire or self.FireBurnSND then return end + + self.FireBurnSND = CreateSound( self, "ambient/fire/firebig.wav" ) + self.FireBurnSND:PlayEx(0,100) + self.FireBurnSND:ChangeVolume( LVS.EngineVolume, 1 ) + + self:EmitSound("ambient/fire/gascan_ignite1.wav") +end + +function ENT:OnRemove() + self:RemoveFireSound() +end + +function ENT:Draw() +end + +function ENT:Think() + self:SetNextClientThink( CurTime() + 0.05 ) + + self:DamageFX() + + return true +end + +function ENT:DamageFX() + if not self:GetDestroyed() or self:GetFuel() <= 0 then + self:StopFireSound() + + return + end + + self:StartFireSound() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( self:GetBase() ) + util.Effect( "lvs_carfueltank_fire", effectdata ) +end + diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_lighthandler.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_lighthandler.lua new file mode 100644 index 0000000..b13dd20 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_lighthandler.lua @@ -0,0 +1,725 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Entity",1, "DoorHandler" ) + + self:NetworkVar( "Bool",0, "Active" ) + self:NetworkVar( "Bool",1, "HighActive" ) + self:NetworkVar( "Bool",2, "FogActive" ) + + if SERVER then + self:NetworkVarNotify( "Active", self.OnActiveChanged ) + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + end + + function ENT:OnActiveChanged( name, old, new) + if new == old then return end + + local DoorHandler = self:GetDoorHandler() + + if not IsValid( DoorHandler ) then return end + + if new then + if not DoorHandler:IsOpen() then + DoorHandler:Open() + end + else + if DoorHandler:IsOpen() then + DoorHandler:Close() + end + end + end + + function ENT:Think() + local DoorHandler = self:GetDoorHandler() + + if not IsValid( DoorHandler ) then + self:NextThink( CurTime() + 1 ) + else + self:NextThink( CurTime() ) + + if self:GetActive() and not DoorHandler:IsOpen() then + DoorHandler:Open() + end + end + + return true + end + + function ENT:OnTakeDamage( dmginfo ) + end + + return +end + +function ENT:Initialize() + local base = self:GetBase() + + if not IsValid( base ) or not isfunction( base.IsInitialized ) or not base:IsInitialized() then + + timer.Simple( 1, function() + if not IsValid( self ) then return end + + self:Initialize() + end ) + + return + end + + if not istable( base.Lights ) then return end + + self:InitializeLights( base, base.Lights ) + + for typeid, typedata in pairs( base.Lights ) do + if not typedata.Siren or not typedata.Trigger then continue end + + if not istable( self._TriggerList ) then + self._TriggerList = {} + end + + local data = {} + data.pattern = {} + + for id, n in ipairs( string.Explode( "", typedata.Trigger ) ) do + data.pattern[ id ] = tonumber( n or 0 ) + end + + data.cur = 1 + data.max = #data.pattern + + self._TriggerList[ typedata.Trigger ] = data + end +end + +function ENT:InitializeLights( base, data ) + + if not istable( data ) then return end + + local materials = base:GetMaterials() + + for typeid, typedata in pairs( data ) do + if not typedata.Trigger then + data[typeid] = nil + end + + if typedata.SubMaterialID then + if isstring( typedata.SubMaterialID ) then + local ID = table.KeyFromValue( materials, typedata.SubMaterialID ) + + if isnumber( ID ) then + data[typeid].SubMaterialID = ID - 1 + end + end + + data[typeid].SubMaterial = self:CreateSubMaterial( typedata.SubMaterialID, typedata.Trigger ) + data[typeid].SubMaterialBrightness = typedata.SubMaterialBrightness or 1 + end + + if typedata.Sprites then + for lightsid, lightsdata in pairs( typedata.Sprites ) do + data[typeid].Sprites[ lightsid ].PixVis = util.GetPixelVisibleHandle() + + if isstring( lightsdata.pos ) then + data[typeid].Sprites[ lightsid ].att = base:LookupAttachment( lightsdata.pos ) + else + data[typeid].Sprites[ lightsid ].pos = isvector( lightsdata.pos ) and lightsdata.pos or vector_origin + end + + data[typeid].Sprites[ lightsid ].mat = isstring( lightsdata.mat ) and Material( lightsdata.mat ) or Material( "sprites/light_ignorez" ) + data[typeid].Sprites[ lightsid ].width = lightsdata.width or 50 + data[typeid].Sprites[ lightsid ].height = lightsdata.height or 50 + data[typeid].Sprites[ lightsid ].colorR = lightsdata.colorR or 255 + data[typeid].Sprites[ lightsid ].colorG = lightsdata.colorG or 255 + data[typeid].Sprites[ lightsid ].colorB = lightsdata.colorB or 255 + data[typeid].Sprites[ lightsid ].colorA = lightsdata.colorA or 255 + end + end + + if typedata.ProjectedTextures then + for projid, projdata in pairs( typedata.ProjectedTextures ) do + if typedata.Trigger == "high" then + data[typeid].ProjectedTextures[ projid ].PixVis = util.GetPixelVisibleHandle() + end + + if isstring( projdata.pos ) then + local attachID = base:LookupAttachment( projdata.pos ) + + data[typeid].ProjectedTextures[ projid ].att = attachID + + + -- detect if the attachment is upside down + local att = base:GetAttachment( attachID ) + if att then + if base:WorldToLocalAngles( att.Ang ):Up().z < -0.9 then + data[typeid].ProjectedTextures[ projid ].FixRotation = true + end + end + else + data[typeid].ProjectedTextures[ projid ].pos = projdata.pos or vector_origin + data[typeid].ProjectedTextures[ projid ].ang = projdata.ang or angle_zero + end + + data[typeid].ProjectedTextures[ projid ].mat = projdata.mat or (typedata.Trigger == "high" and "effects/flashlight/soft" or "effects/lvs/car_projectedtexture") + data[typeid].ProjectedTextures[ projid ].farz = projdata.farz or (typedata.Trigger == "high" and 4000 or 1000) + data[typeid].ProjectedTextures[ projid ].nearz = projdata.nearz or 65 + data[typeid].ProjectedTextures[ projid ].fov = projdata.fov or (typedata.Trigger == "high" and 40 or 60) + data[typeid].ProjectedTextures[ projid ].colorR = projdata.colorR or 255 + data[typeid].ProjectedTextures[ projid ].colorG = projdata.colorG or 255 + data[typeid].ProjectedTextures[ projid ].colorB = projdata.colorB or 255 + data[typeid].ProjectedTextures[ projid ].colorA = projdata.colorA or 255 + data[typeid].ProjectedTextures[ projid ].color = Color( projdata.colorR or 255, projdata.colorG or 255, projdata.colorB or 255 ) + data[typeid].ProjectedTextures[ projid ].brightness = projdata.brightness or (typedata.Trigger == "high" and 5 or 5) + data[typeid].ProjectedTextures[ projid ].shadows = projdata.shadows == true + end + end + + if typedata.DynamicLights then + for dLightid, dLightdata in pairs( typedata.DynamicLights ) do + + if isstring( dLightdata.pos ) then + data[typeid].DynamicLights[ dLightid ].att = base:LookupAttachment( dLightdata.pos ) + else + data[typeid].DynamicLights[ dLightid ].pos = isvector( dLightdata.pos ) and dLightdata.pos or vector_origin + end + + data[typeid].DynamicLights[ dLightid ].colorR = dLightdata.colorR or 255 + data[typeid].DynamicLights[ dLightid ].colorG = dLightdata.colorG or 255 + data[typeid].DynamicLights[ dLightid ].colorB = dLightdata.colorB or 255 + data[typeid].DynamicLights[ dLightid ].brightness = dLightdata.brightness or 0.1 + data[typeid].DynamicLights[ dLightid ].decay = dLightdata.decay or 1000 + data[typeid].DynamicLights[ dLightid ].size = dLightdata.size or 128 + data[typeid].DynamicLights[ dLightid ].lifetime = dLightdata.lifetime or 0.1 + end + end + end + + self.Enabled = true +end + +function ENT:CreateSubMaterial( SubMaterialID, name ) + local base = self:GetBase() + + if not IsValid( base ) or not isnumber( SubMaterialID ) then return end + + local mat = base:GetMaterials()[ SubMaterialID + 1 ] + + if not mat then return end + + local string_data = file.Read( "materials/"..mat..".vmt", "GAME" ) + + if not string_data then return end + + return CreateMaterial( name..SubMaterialID..base:GetClass()..base:EntIndex(), "VertexLitGeneric", util.KeyValuesToTable( string_data ) ) +end + +function ENT:ResetSubMaterials() + local base = self:GetBase() + + if not IsValid( base ) then return end + + local data = base.Lights + + if not istable( data ) then return end + + for typeid, typedata in pairs( data ) do + if not typedata.SubMaterialID or not typedata.SubMaterial then continue end + + base:SetSubMaterial(typedata.SubMaterialID, "") + end +end + +function ENT:CreateProjectedTexture( id, mat, col, brightness, shadows, nearz, farz, fov ) + if not mat then return end + + local thelamp = ProjectedTexture() + thelamp:SetTexture( mat ) + thelamp:SetColor( col ) + thelamp:SetBrightness( brightness ) + thelamp:SetEnableShadows( shadows ) + thelamp:SetNearZ( nearz ) + thelamp:SetFarZ( farz ) + thelamp:SetFOV( fov ) + + if istable( self._ProjectedTextures ) then + if IsValid( self._ProjectedTextures[ id ] ) then + self._ProjectedTextures[ id ]:Remove() + self._ProjectedTextures[ id ] = nil + end + else + self._ProjectedTextures = {} + end + + self._ProjectedTextures[ id ] = thelamp + + return thelamp +end + +function ENT:GetProjectedTexture( id ) + if not id or not istable( self._ProjectedTextures ) then return end + + return self._ProjectedTextures[ id ] +end + +function ENT:RemoveProjectedTexture( id ) + if not id or not istable( self._ProjectedTextures ) then return end + + if IsValid( self._ProjectedTextures[ id ] ) then + self._ProjectedTextures[ id ]:Remove() + self._ProjectedTextures[ id ] = nil + end +end + +function ENT:ClearProjectedTextures() + if not istable( self._ProjectedTextures ) then return end + + for id, proj in pairs( self._ProjectedTextures ) do + if IsValid( proj ) then + proj:Remove() + end + + self._ProjectedTextures[ id ] = nil + end +end + +local function DistanceMul( ent ) + local dist = (LocalPlayer():GetPos() - ent:GetPos()):LengthSqr() / 10000000 + + return math.max( 1 - dist, 0 ) +end + +function ENT:LightsThink( base ) + local EntID = base:EntIndex() + local Class = base:GetClass() + local data = base.Lights + + if not istable( data ) then return end + + local brightness = DistanceMul( self ) + + for typeid, typedata in pairs( data ) do + local mul = self:GetTypeActivator( typedata.Trigger ) * brightness + local active = mul > 0.01 + + if typedata.Trigger == "main" then + if self:GetHighActive() then + active = false + end + end + + if typedata.ProjectedTextures then + for projid, projdata in pairs( typedata.ProjectedTextures ) do + local id = typeid.."-"..projid + + local proj = self:GetProjectedTexture( id ) + local proj_active = active + + if proj_active and istable( projdata.bodygroup ) then + if not base:BodygroupIsValid( projdata.bodygroup.name, projdata.bodygroup.active ) then + proj_active = false + end + end + + if IsValid( proj ) then + if proj_active then + proj:SetBrightness( projdata.brightness * mul ) + + if projdata.att then + local att = base:GetAttachment( projdata.att ) + + if att then + proj:SetPos( att.Pos ) + + -- its upside down... + if projdata.FixRotation then + att.Ang:RotateAroundAxis( att.Ang:Forward(), 180 ) + end + + proj:SetAngles( att.Ang ) + end + else + if not isvector( projdata.pos ) then self:InitializeLights( base ) break end + + proj:SetPos( base:LocalToWorld( projdata.pos ) ) + proj:SetAngles( base:LocalToWorldAngles( projdata.ang ) ) + end + + proj:Update() + else + self:RemoveProjectedTexture( id ) + end + else + if proj_active then + self:CreateProjectedTexture( id, projdata.mat, projdata.color, projdata.brightness, projdata.shadows, projdata.nearz, projdata.farz, projdata.fov ) + else + self:RemoveProjectedTexture( id ) + end + end + end + end + + if typedata.DynamicLights then + for dLightid, dLightdata in pairs( typedata.DynamicLights ) do + if not active then continue end + + local dlight = DynamicLight( self:EntIndex() * 1000 + dLightid ) + + if not dlight then continue end + + local pos + + if dLightdata.att then + local att = base:GetAttachment( dLightdata.att ) + + if not att then + dlight.pos = vector_origin + else + dlight.pos = att.Pos + end + else + if not isvector( dLightdata.pos ) then self:InitializeLights( base ) break end + + dlight.pos = base:LocalToWorld( dLightdata.pos ) + end + + dlight.r = dLightdata.colorR + dlight.g = dLightdata.colorG + dlight.b = dLightdata.colorB + dlight.brightness = dLightdata.brightness + dlight.decay = dLightdata.decay + dlight.size = dLightdata.size + dlight.dietime = CurTime() + dLightdata.lifetime + end + end + + if not isnumber( typedata.SubMaterialID ) or not typedata.SubMaterial then continue end + + typedata.SubMaterial:SetFloat("$detailblendfactor", mul * typedata.SubMaterialBrightness ) + + if typedata.SubMaterialValue ~= active then + data[typeid].SubMaterialValue = active + base:SetSubMaterial(typedata.SubMaterialID, "!"..typedata.Trigger..typedata.SubMaterialID..Class..EntID) + end + end +end + +function ENT:LerpActivator( name, target, rate ) + name = "_sm"..name + + if not self[ name ] then self[ name ] = 0 end + + if not rate then rate = 10 end + + self[ name ] = self[ name ] + (target - self[ name ]) * rate + + return self[ name ] +end + +function ENT:GetTypeActivator( name ) + if not self[ "_sm"..name ] then return 0 end + + return self[ "_sm"..name ] ^ 2 +end + +local Left = { + [1] = true, + [3] = true, +} +local Right = { + [2] = true, + [3] = true, +} + +function ENT:CalcTypeActivators( base ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + local main = self:GetActive() and 1 or 0 + local high = self:GetHighActive() and 1 or 0 + local fog = self:GetFogActive() and 1 or 0 + local brake = base:GetBrake() > 0 and 1 or 0 + local reverse = base:GetReverse() and 1 or 0 + + local engineActive = base:GetEngineActive() and 1 or 0 + + local Flasher = base:GetTurnFlasher() + local TurnMode = base:GetTurnMode() + + local turnleft = (Left[ TurnMode ] and Flasher) and 1 or 0 + local turnright = (Right[ TurnMode ] and Flasher) and 1 or 0 + + local Rate = RealFrameTime() * 10 + + self:LerpActivator( "active", engineActive, Rate ) + self:LerpActivator( "fog", fog, Rate ) + self:LerpActivator( "brake", brake, Rate ) + self:LerpActivator( "reverse", reverse, Rate ) + self:LerpActivator( "turnleft", turnleft, Rate * 2 ) + self:LerpActivator( "turnright", turnright, Rate * 2 ) + + local DoorHandler = self:GetDoorHandler() + if IsValid( DoorHandler ) then + main = (DoorHandler.sm_pp or 0) >= 0.5 and main or 0 + high = (DoorHandler.sm_pp or 0) >= 0.5 and high or 0 + end + + self:LerpActivator( "main", main, Rate ) + + if Left[ TurnMode ] then + if main >= 0.5 then + self:LerpActivator( "main+brake+turnleft", main * 0.8 + turnleft * 1.5, Rate ) + else + self:LerpActivator( "main+brake+turnleft", turnleft, Rate ) + end + else + if main >= 0.5 then + self:LerpActivator( "main+brake+turnleft", main * 0.8 + brake * 1.5, Rate ) + else + self:LerpActivator( "main+brake+turnleft", brake, Rate ) + end + end + + if Right[ TurnMode ] then + if main >= 0.5 then + self:LerpActivator( "main+brake+turnright", main * 0.8 + turnright * 1.5, Rate ) + else + self:LerpActivator( "main+brake+turnright", turnright, Rate ) + end + else + if main >= 0.5 then + self:LerpActivator( "main+brake+turnright", main * 0.8 + brake * 1.5, Rate ) + else + self:LerpActivator( "main+brake+turnright", brake, Rate ) + end + end + + if main >= 0.5 then + self:LerpActivator( "main+brake", main * 0.8 + brake * 1.2, Rate ) + else + self:LerpActivator( "main+brake", brake, Rate ) + end + + self:LerpActivator( "main+high", main + high, Rate ) + + self:LerpActivator( "main+fog", main + fog, Rate ) + + self:LerpActivator( "high", high, Rate ) + + if not istable( self._TriggerList ) then return end + + local RateSiren = math.min( Rate * 50, 1 ) + + if base:GetSirenMode() < 0 then + for id, data in pairs( self._TriggerList ) do + self:LerpActivator( id, 0, RateSiren ) + end + + return + end + + for id, data in pairs( self._TriggerList ) do + self:LerpActivator( id, data.pattern[ data.cur ], RateSiren ) + end + + local T = CurTime() + self:EntIndex() * 1337 + + if (self._calcNext or 0) > T then return end + + self._calcNext = T + 0.1 + + for id, data in pairs( self._TriggerList ) do + data.cur = data.cur + 1 + + if data.cur > data.max then + data.cur = 1 + end + end +end + +ENT.LensFlare1 = Material( "effects/lvs/car_lensflare" ) +ENT.LensFlare2 = Material( "sprites/light_ignorez" ) +ENT.LightMaterial = Material( "effects/lvs/car_spotlight" ) +function ENT:GetAmbientLight( base ) + local T = CurTime() + local FT = RealFrameTime() + local ply = LocalPlayer() + + if not IsValid( ply ) then return 0, vector_origin end + + local plyPos = ply:GetShootPos() + + local ViewEnt = ply:GetViewEntity() + + if IsValid( ViewEnt ) and ViewEnt ~= ply then + plyPos = ViewEnt:GetPos() + end + + local EntTable = self:GetTable() + + if (EntTable._NextLightCheck or 0) > T then return (EntTable._AmbientLightMul or 0), plyPos end + + local LightVeh = render.GetLightColor( base:LocalToWorld( base:OBBCenter() ) ) + local LightPlayer = render.GetLightColor( plyPos ) + local AmbientLightMul = (1 - math.min( LightVeh:Dot( LightPlayer ) * 200, 1 )) ^ 2 + + EntTable._NextLightCheck = T + FT + + if not EntTable._AmbientLightMul then + EntTable._AmbientLightMul = 0 + end + + EntTable._AmbientLightMul = EntTable._AmbientLightMul and EntTable._AmbientLightMul + (AmbientLightMul - EntTable._AmbientLightMul) * FT or 0 + + return EntTable._AmbientLightMul, plyPos +end + +local DoMagic = { + ["main"] = true, + ["high"] = true, + ["main+high"] = true, +} + +function ENT:RenderLights( base, data ) + if not self.Enabled then return end + + local brightness = 1 - DistanceMul( self ) + + for _, typedata in pairs( data ) do + + local mul = self:GetTypeActivator( typedata.Trigger ) + + if mul < 0.01 then continue end + + if typedata.ProjectedTextures and DoMagic[ typedata.Trigger ] then + for projid, projdata in pairs( typedata.ProjectedTextures ) do + if istable( projdata.bodygroup ) then + if not base:BodygroupIsValid( projdata.bodygroup.name, projdata.bodygroup.active ) then continue end + end + + local pos + local dir + + if projdata.att then + local att = base:GetAttachment( projdata.att ) + + if not att then continue end + + pos = att.Pos + dir = att.Ang:Forward() + + else + pos = base:LocalToWorld( projdata.pos ) + dir = base:LocalToWorldAngles( projdata.ang ):Forward() + end + + if not projdata.colorR or not projdata.colorG or not projdata.colorB or not projdata.brightness then self:InitializeLights( base ) break end + + local L = 100 + 700 * brightness + local W = 50 + 100 * brightness + + render.SetMaterial( self.LightMaterial ) + render.DrawBeam( pos, pos + dir * L, W, -0.01, 0.99, Color( projdata.colorR * mul, projdata.colorG * mul, projdata.colorB * mul, projdata.brightness ) ) + + if not projdata.PixVis then continue end + + local AmbientLightMul, plyPos = self:GetAmbientLight( base ) + + local visible = util.PixelVisible( pos, 1, projdata.PixVis ) * mul + + if not visible or visible == 0 then continue end + + local aimdir = (plyPos - pos):GetNormalized() + + local ang = base:AngleBetweenNormal( dir, aimdir ) + + if ang < 20 then + local Alpha = 1 - (ang / 20) * 255 + render.SetMaterial( self.LensFlare2 ) + render.DrawSprite( pos, 512, 512, Color( projdata.colorR * mul * AmbientLightMul, projdata.colorG * mul * AmbientLightMul, projdata.colorB * mul * AmbientLightMul, Alpha * visible) ) + end + if ang < 10 then + local RGB = 30 * AmbientLightMul * mul + local Scale = 1 - (ang / 10) + local Alpha = Scale * 255 * math.Rand(0.8,1.2) * visible + local ScaleX = 1024 * math.Rand(0.98,1.02) + local ScaleY = 1024 * math.Rand(0.98,1.02) + + render.SetMaterial( self.LensFlare1 ) + render.DrawSprite( pos, ScaleX, ScaleY, Color(RGB,RGB,RGB,Alpha) ) + end + end + end + + if not typedata.Sprites then continue end + + for id, lightsdata in pairs( typedata.Sprites ) do + if not lightsdata.PixVis then + continue + end + + if istable( lightsdata.bodygroup ) then + if not base:BodygroupIsValid( lightsdata.bodygroup.name, lightsdata.bodygroup.active ) then continue end + end + + local pos + + if lightsdata.att then + local att = base:GetAttachment( lightsdata.att ) + + if not att then continue end + + pos = att.Pos + else + if not isvector( lightsdata.pos ) then self:InitializeLights( base ) break end + + pos = base:LocalToWorld( lightsdata.pos ) + end + + local visible = util.PixelVisible( pos, 2, lightsdata.PixVis ) + + if not visible then continue end + + if visible <= 0.1 then continue end + + if isstring( lightsdata.mat ) then self:InitializeLights( base ) break end + + render.SetMaterial( lightsdata.mat ) + render.DrawSprite( pos, lightsdata.width, lightsdata.height , Color(lightsdata.colorR,lightsdata.colorG,lightsdata.colorB,lightsdata.colorA*mul*visible^2) ) + end + end +end + +function ENT:Think() + local base = self:GetBase() + + if not IsValid( base ) or not self.Enabled then + self:SetNextClientThink( CurTime() + 1 ) + + return true + end + + self:CalcTypeActivators( base ) + self:LightsThink( base ) +end + +function ENT:OnRemove() + self:ClearProjectedTextures() + self:ResetSubMaterials() +end + +function ENT:Draw() +end + +function ENT:DrawTranslucent() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_righandler.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_righandler.lua new file mode 100644 index 0000000..7c3504e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_righandler.lua @@ -0,0 +1,52 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + + self:NetworkVar( "Entity",1, "Wheel" ) + + self:NetworkVar( "Float",0, "Pose0" ) + self:NetworkVar( "Float",1, "Pose1" ) + + self:NetworkVar( "String",0, "NameID" ) +end + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/dav0r/hoverball.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + end + + function ENT:Think() + return false + end +end + +if CLIENT then + function ENT:Draw() + end + + function ENT:OnRemove() + end + + function ENT:Think() + local Base = self:GetBase() + local Wheel = self:GetWheel() + + if not IsValid( Base ) or not IsValid( Wheel ) then return end + + local id = self:GetNameID() + local rotation = -self:WorldToLocalAngles( Wheel:GetAngles() ).r + + local zpos = Base:WorldToLocal( Wheel:GetPos() ).z + + if Wheel:GetNWDamaged() then zpos = zpos - Base.WheelPhysicsTireHeight end + + Base:SetPoseParameter("vehicle_wheel_"..id.."_spin",rotation) + Base:SetPoseParameter("vehicle_wheel_"..id.."_height",math.Remap( zpos, self:GetPose0(), self:GetPose1(), 0, 1)) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_steerhandler.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_steerhandler.lua new file mode 100644 index 0000000..b7b0305 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_steerhandler.lua @@ -0,0 +1,47 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/dav0r/hoverball.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:AddFlags( FL_OBJECT ) + + local PObj = self:GetPhysicsObject() + + if not IsValid( PObj ) then + self:Remove() + + return + end + + PObj:SetMass( 1 ) + PObj:EnableMotion( false ) + PObj:EnableDrag( false ) + + self:SetNotSolid( true ) + self:SetNoDraw( true ) + end + + function ENT:Think() + return false + end + + function ENT:UpdateTransmitState() + return TRANSMIT_NEVER + end + + return +end + +function ENT:Think() + return false +end + +function ENT:Draw() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_trackphysics.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_trackphysics.lua new file mode 100644 index 0000000..5cf4962 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_trackphysics.lua @@ -0,0 +1,201 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) +end + +if SERVER then + function ENT:AddToGibList( ent ) + if not istable( self._GibList ) then self._GibList = {} end + + table.insert( self._GibList, ent ) + end + + function ENT:GetGibList() + return self._GibList or {} + end + + function ENT:SpawnGib( LeftOrRight ) + local base = self:GetBase() + + local options = { + [LVS.WHEELTYPE_LEFT] = "left", + [LVS.WHEELTYPE_RIGHT] = "right" + } + + local side = options[ LeftOrRight ] + + if not side or not IsValid( base ) or not istable( base.TrackGibs ) or not base.TrackGibs[ side ] then return end + + local vel = self:GetVelocity() + + for _, data in pairs( base.TrackGibs[ side ] ) do + local IsRagdoll = util.IsValidRagdoll( data.mdl ) + + local class = IsRagdoll and "prop_ragdoll" or "prop_physics" + + local ent = ents.Create( class ) + + if not IsValid( ent ) then continue end + + ent:SetModel( data.mdl ) + ent:SetPos( self:LocalToWorld( data.pos ) ) + ent:SetAngles( self:LocalToWorldAngles( data.ang ) ) + ent:Spawn() + ent:Activate() + ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + + ent:EmitSound("lvs/tracks_break"..math.random(1,3)..".wav") + + if IsRagdoll then + for i = 1, (ent:GetPhysicsObjectCount() - 1) do + local bone = ent:GetPhysicsObjectNum( i ) + + if not IsValid( bone ) then continue end + + bone:AddVelocity( vel ) + end + else + local PhysObj = ent:GetPhysicsObject() + + if IsValid( PhysObj ) then + PhysObj:AddVelocity( vel ) + end + end + + self:DeleteOnRemove( ent ) + + self:AddToGibList( ent ) + + timer.Simple( 59.5, function() + if not IsValid( ent ) then return end + + ent:SetRenderMode( RENDERMODE_TRANSCOLOR ) + ent:SetRenderFX( kRenderFxFadeFast ) + end ) + + timer.Simple( 60, function() + if not IsValid( ent ) then return end + + ent:Remove() + end ) + end + end + + function ENT:ClearGib() + local Gibs = self:GetGibList() + + for _, ent in pairs( Gibs ) do + if not IsValid( ent ) then continue end + + ent:Remove() + end + + table.Empty( Gibs ) + end + + function ENT:Initialize() + self:SetUseType( SIMPLE_USE ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + + -- this is so vj npcs can still see us + self:AddEFlags( EFL_DONTBLOCKLOS ) + + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self:DrawShadow( false ) + + self:SetMaterial( "models/wireframe" ) + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetMass( 1 ) + PhysObj:EnableDrag( false ) + PhysObj:EnableGravity( false ) + PhysObj:EnableMotion( true ) + PhysObj:SetMaterial( "friction_00" ) + end + + function ENT:Think() + return false + end + + function ENT:OnTakeDamage( dmginfo ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + local Force = dmginfo:GetDamageForce() + local fLength = Force:Length() + + -- translate force value to armor penetration value is Force * 0.1 + -- mm to inch is * 0.0393701 + -- so correct value is * 0.00393701 + local pLength = fLength * 0.00393701 + + local startpos = dmginfo:GetDamagePosition() + local endpos = startpos + Force:GetNormalized() * pLength + + local trace = util.TraceLine( { + start = startpos, + endpos = endpos, + whitelist = true, + ignoreworld = true, + filter = base, + } ) + + if trace.Hit then + dmginfo:SetDamagePosition( trace.HitPos ) + else + dmginfo:SetDamageType( dmginfo:GetDamageType() + DMG_PREVENT_PHYSICS_FORCE ) + end + + base:OnTakeDamage( dmginfo ) + end + + function ENT:PhysicsCollide( data, physobj ) + local HitEntity = data.HitEntity + local HitObject = data.HitObject + + if not IsValid( HitEntity ) or not IsValid( HitObject ) then return end + + physobj:SetVelocityInstantaneous( data.OurOldVelocity ) + + if HitObject:IsMotionEnabled() and (HitEntity:GetClass() == "prop_ragdoll" or HitEntity:GetCollisionGroup() == COLLISION_GROUP_WEAPON) then + HitObject:SetVelocityInstantaneous( data.OurOldVelocity * 2 ) + end + end + + function ENT:Use( ply ) + if (ply._lvsNextUse or 0) > CurTime() then return end + + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:Use( ply ) + end + + function ENT:OnRemove() + self:ClearGib() + end + + return +end + +function ENT:Draw() + if not LVS.DeveloperEnabled then return end + + self:DrawModel() +end + +function ENT:Think() +end + +function ENT:OnRemove() +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_trailerhitch.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_trailerhitch.lua new file mode 100644 index 0000000..9fa119a --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_trailerhitch.lua @@ -0,0 +1,548 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +ENT.RenderGroup = RENDERGROUP_BOTH + +local IgnoreDistance = 200 +local GrabDistance = 150 +local HookupDistance = 32 + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Entity",1, "TargetBase" ) + self:NetworkVar( "Entity",2, "DragTarget" ) + self:NetworkVar( "Int",0, "HitchType" ) + + if SERVER then + self:SetHitchType( LVS.HITCHTYPE_NONE or -1 ) + end +end + +if SERVER then + util.AddNetworkString( "lvs_trailerhitch" ) + + net.Receive( "lvs_trailerhitch", function( len, ply ) + local ent = net.ReadEntity() + + if not IsValid( ent ) or not isfunction( ent.StartDrag ) then return end + + if ent.IsLinkInProgress then return end + + if IsValid( ent:GetTargetBase() ) then + ent:Decouple() + else + ent:StartDrag( ply ) + ent._HandBrakeForceDisabled = true + end + end ) + + function ENT:StartDrag( ply ) + if IsValid( self.GrabEnt ) or IsValid( ply._HitchGrabEnt ) then return end + + if self:GetHitchType() ~= LVS.HITCHTYPE_FEMALE then return end + + local base = self:GetBase() + + if not IsValid( ply ) or not ply:Alive() or ply:InVehicle() or ply:GetObserverMode() ~= OBS_MODE_NONE or not ply:KeyDown( IN_WALK ) or (ply:GetShootPos() - self:GetPos()):Length() > GrabDistance or not IsValid( base ) then return end + + ply:SprintDisable() + + self.GrabEnt = ents.Create( "prop_physics" ) + + ply._HitchGrabEnt = self.GrabEnt + + if not IsValid( self.GrabEnt ) then return end + + self.GrabEnt:SetModel( "models/Combine_Helicopter/helicopter_bomb01.mdl" ) + self.GrabEnt:SetPos( self:GetPos() ) + self.GrabEnt:SetAngles( self:GetAngles() ) + self.GrabEnt:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self.GrabEnt:Spawn() + self.GrabEnt:Activate() + self.GrabEnt:SetNoDraw( true ) + self.GrabEnt.DoNotDuplicate = true + self:DeleteOnRemove( self.GrabEnt ) + + self:SetDragTarget( ply ) + + local PhysObj = self.GrabEnt:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetMass( 50000 ) + PhysObj:EnableMotion( false ) + + constraint.Ballsocket( base, self.GrabEnt, 0, 0, vector_origin, 0, 0, 1 ) + + self.GrabEnt:SetSolid( SOLID_NONE ) + + base:OnStartDrag( self, ply ) + base._HandBrakeForceDisabled = true + + base._DragOriginalCollisionGroup = base:GetCollisionGroup() + base:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + if base.GetWheels then + for _, wheel in pairs( base:GetWheels() ) do + if not IsValid( wheel ) then continue end + + wheel._DragOriginalCollisionGroup = wheel:GetCollisionGroup() + wheel:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + end + + self:NextThink( CurTime() ) + end + + function ENT:StopDrag() + if IsValid( self.GrabEnt ) then + self.GrabEnt:Remove() + end + + local ply = self:GetDragTarget() + + if IsValid( ply ) then + ply:SprintEnable() + end + + local base = self:GetBase() + + if IsValid( base ) then + + base:OnStopDrag( self, ply ) + base._HandBrakeForceDisabled = nil + + if IsValid( ply ) then base:SetPhysicsAttacker( ply ) end + + if base._DragOriginalCollisionGroup then + base:SetCollisionGroup( base._DragOriginalCollisionGroup ) + base._DragOriginalCollisionGroup = nil + end + + if base.GetWheels then + for _, wheel in pairs( base:GetWheels() ) do + if not IsValid( wheel ) then continue end + + if IsValid( ply ) then wheel:SetPhysicsAttacker( ply ) end + + if wheel._DragOriginalCollisionGroup then + wheel:SetCollisionGroup( wheel._DragOriginalCollisionGroup ) + wheel._DragOriginalCollisionGroup = nil + end + end + end + end + + self:SetDragTarget( NULL ) + + for _, ent in ipairs( ents.FindByClass( "lvs_wheeldrive_trailerhitch" ) ) do + if ent:GetHitchType() ~= LVS.HITCHTYPE_MALE then continue end + + local dist = (self:GetPos() - ent:GetPos()):Length() + + if dist > HookupDistance then continue end + + self:CoupleTo( ent ) + + break + end + end + + function ENT:Drag( ply ) + if not IsValid( self.GrabEnt ) or ply:InVehicle() or not ply:KeyDown( IN_WALK ) or not ply:Alive() or ply:GetObserverMode() ~= OBS_MODE_NONE then + self:StopDrag() + + return + end + + if not self.GrabEnt.TargetAngle then + self.GrabEnt.TargetAngle = ply:EyeAngles().y + end + + local TargetAngle = ply:EyeAngles() + + self.GrabEnt.TargetAngle = math.ApproachAngle( self.GrabEnt.TargetAngle, TargetAngle.y, FrameTime() * 500 ) + + TargetAngle.p = math.max( TargetAngle.p, -15 ) + + TargetAngle.y = self.GrabEnt.TargetAngle + + local TargetPos = ply:GetShootPos() + TargetAngle:Forward() * 80 + + if (self:GetPos() - TargetPos):Length() > GrabDistance then self:StopDrag() return end + + self.GrabEnt:SetPos( TargetPos ) + + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:PhysWake() + + if base.WheelsOnGround then + if base:WheelsOnGround() then return end + else + if base:OnGround() then return end + end + + local PhysObj = base:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetAngleVelocity( PhysObj:GetAngleVelocity() * 0.8 ) + PhysObj:SetVelocity( PhysObj:GetVelocity() * 0.8 ) + end + + function ENT:Initialize() + self:SetSolid( SOLID_NONE ) + self:SetMoveType( MOVETYPE_NONE ) + self:DrawShadow( false ) + end + + function ENT:Decouple() + local TargetBase = self:GetTargetBase() + + self:SetTargetBase( NULL ) + + if not IsValid( self.HitchConstraint ) then return end + + local base = self:GetBase() + + if IsValid( base ) then + base:OnCoupleChanged( TargetBase, self.HitchTarget, false ) + TargetBase:OnCoupleChanged( self:GetBase(), self, false ) + end + + self.HitchConstraint:Remove() + + self.HitchTarget = nil + end + + function ENT:CoupleTo( target ) + if not IsValid( target ) or IsValid( target.HitchConstraint ) or IsValid( self.HitchConstraint ) then return end + + local base = self:GetBase() + + if self.IsLinkInProgress or not IsValid( base ) or IsValid( self.PosEnt ) then return end + + self.IsLinkInProgress = true + + if self:GetHitchType() ~= LVS.HITCHTYPE_FEMALE or target:GetHitchType() ~= LVS.HITCHTYPE_MALE then self.IsLinkInProgress = nil return end + + self.PosEnt = ents.Create( "prop_physics" ) + + if not IsValid( self.PosEnt ) then self.IsLinkInProgress = nil return end + + self.PosEnt:SetModel( "models/Combine_Helicopter/helicopter_bomb01.mdl" ) + self.PosEnt:SetPos( self:GetPos() ) + self.PosEnt:SetAngles( self:GetAngles() ) + self.PosEnt:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self.PosEnt:Spawn() + self.PosEnt:Activate() + self.PosEnt:SetNoDraw( true ) + self.PosEnt.DoNotDuplicate = true + self:DeleteOnRemove( self.PosEnt ) + + local PhysObj = self.PosEnt:GetPhysicsObject() + + if not IsValid( PhysObj ) then self.IsLinkInProgress = nil return end + + PhysObj:SetMass( 50000 ) + PhysObj:EnableMotion( false ) + + constraint.Ballsocket( base, self.PosEnt, 0, 0, vector_origin, 0, 0, 1 ) + + local targetBase = target:GetBase() + + base:OnCoupleChanged( targetBase, target, true ) + targetBase:OnCoupleChanged( self:GetBase(), self, true ) + + self.PosEnt:SetSolid( SOLID_NONE ) + + timer.Simple( 0, function() + if not IsValid( self.PosEnt ) then + self.IsLinkInProgress = nil + + return + end + + if not IsValid( target ) or not IsValid( targetBase ) then + self.PosEnt:Remove() + + self.IsLinkInProgress = nil + + return + end + + self.PosEnt:SetPos( target:GetPos() ) + + constraint.Weld( self.PosEnt, targetBase, 0, 0, 0, false, false ) + + timer.Simple( 0.25, function() + if not IsValid( base ) or not IsValid( targetBase ) or not IsValid( self.PosEnt ) then self.IsLinkInProgress = nil return end + + self.HitchTarget = target + self.HitchConstraint = constraint.Ballsocket( base, targetBase, 0, 0, targetBase:WorldToLocal( self.PosEnt:GetPos() ), 0, 0, 1 ) + + target.HitchConstraint = self.HitchConstraint + + self:SetTargetBase( targetBase ) + + self.PosEnt:Remove() + + self.IsLinkInProgress = nil + end ) + end ) + end + + function ENT:Think() + + local ply = self:GetDragTarget() + + if IsValid( ply ) then + self:Drag( ply ) + + self:NextThink( CurTime() ) + else + self:NextThink( CurTime() + 9999 ) + end + + return true + end + + function ENT:OnRemove() + self:StopDrag() + end + + return +end + +local HitchEnts = {} + +function ENT:Initialize() + table.insert( HitchEnts, self ) +end + +function ENT:OnRemove() + for id, e in pairs( HitchEnts ) do + if IsValid( e ) then continue end + + HitchEnts[ id ] = nil + end +end + +function ENT:Draw() +end + +local function DrawDiamond( X, Y, radius ) + local segmentdist = 90 + local radius2 = radius + 1 + + for a = 0, 360, segmentdist do + surface.DrawLine( X + math.cos( math.rad( a ) ) * radius, Y - math.sin( math.rad( a ) ) * radius, X + math.cos( math.rad( a + segmentdist ) ) * radius, Y - math.sin( math.rad( a + segmentdist ) ) * radius ) + surface.DrawLine( X + math.cos( math.rad( a ) ) * radius2, Y - math.sin( math.rad( a ) ) * radius2, X + math.cos( math.rad( a + segmentdist ) ) * radius2, Y - math.sin( math.rad( a + segmentdist ) ) * radius2 ) + end +end + +local function DrawText( x, y, text, col ) + local font = "TargetIDSmall" + draw.SimpleText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( text, font, x, y, col or color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) +end + +local circle = Material( "vgui/circle" ) +local radius = 6 +local Col = Color(255,191,0,255) + +local boxMins = Vector(-5,-5,-5) +local boxMaxs = Vector(5,5,5) + +function ENT:DrawInfoCoupled( ply ) + local boxOrigin = self:GetPos() + local scr = boxOrigin:ToScreen() + + if not scr.visible then return end + + local shootPos = ply:GetShootPos() + + local boxAngles = self:GetAngles() + + if (boxOrigin - shootPos):Length() > 250 then return end + + local HitPos, _, _ = util.IntersectRayWithOBB( shootPos, ply:GetAimVector() * GrabDistance, boxOrigin, boxAngles, boxMins, boxMaxs ) + + local X = scr.x + local Y = scr.y + + cam.Start2D() + if HitPos then + surface.SetDrawColor( 255, 255, 255, 255 ) + + local Key = input.LookupBinding( "+walk" ) + + if not isstring( Key ) then Key = "[+walk not bound]" end + + DrawText( X, Y + 20, "press "..Key.." to decouple!",Color(255,255,255,255) ) + + local KeyUse = ply:KeyDown( IN_WALK ) + + if self.OldKeyUse ~= KeyUse then + self.OldKeyUse = KeyUse + + if KeyUse then + net.Start( "lvs_trailerhitch" ) + net.WriteEntity( self ) + net.SendToServer() + end + end + else + surface.SetDrawColor( Col.r, Col.g, Col.b, Col.a ) + end + + DrawDiamond( X, Y, radius ) + surface.SetDrawColor( 0, 0, 0, 80 ) + DrawDiamond( X + 1, Y + 1, radius ) + cam.End2D() +end + +function ENT:DrawInfo( ply ) + local boxOrigin = self:GetPos() + local scr = boxOrigin:ToScreen() + + if not scr.visible then return end + + local shootPos = ply:GetShootPos() + + local boxAngles = self:GetAngles() + + if (boxOrigin - shootPos):Length() > 250 then return end + + local HitPos, _, _ = util.IntersectRayWithOBB( shootPos, ply:GetAimVector() * GrabDistance, boxOrigin, boxAngles, boxMins, boxMaxs ) + + local X = scr.x + local Y = scr.y + + local DragTarget = self:GetDragTarget() + local IsBeingDragged = IsValid( DragTarget ) + local HasTarget = false + + if IsBeingDragged then + cam.Start2D() + + for id, ent in pairs( HitchEnts ) do + if ent == self then continue end + + if not IsValid( ent ) or ent:GetHitchType() ~= LVS.HITCHTYPE_MALE then continue end + + local tpos = ent:GetPos() + + local dist = (tpos - boxOrigin):Length() + + if dist > IgnoreDistance then continue end + + local tscr = tpos:ToScreen() + + if not tscr.visible then continue end + + local tX = tscr.x + local tY = tscr.y + + if dist < HookupDistance and IsBeingDragged then + HasTarget = true + end + + surface.SetMaterial( circle ) + surface.SetDrawColor( 0, 0, 0, 80 ) + surface.DrawTexturedRect( tX - radius * 0.5 + 1, tY - radius * 0.5 + 1, radius, radius ) + + if HasTarget then + surface.SetDrawColor( 0, 255, 0, 255 ) + else + surface.SetDrawColor( Col.r, Col.g, Col.b, Col.a ) + end + + surface.DrawTexturedRect( tX - radius * 0.5, tY - radius * 0.5, radius, radius ) + + if not HasTarget then continue end + + surface.DrawLine( X, Y, tX, tY ) + + break + end + + local radiusB = 25 + math.cos( CurTime() * 10 ) * 2 + + if HasTarget then + surface.SetDrawColor( 0, 255, 0, 255 ) + else + surface.SetDrawColor( 255, 0, 0, 255 ) + end + + DrawDiamond( X, Y, radiusB ) + surface.SetDrawColor( 0, 0, 0, 80 ) + DrawDiamond( X + 1, Y + 1, radiusB ) + + if HasTarget then + DrawText( X, Y + 35, "release to couple",Color(0,255,0,255) ) + else + DrawText( X, Y + 35, "in use by "..DragTarget:GetName(),Color(255,0,0,255) ) + end + + cam.End2D() + + return + end + + cam.Start2D() + if HitPos then + surface.SetDrawColor( 255, 255, 255, 255 ) + + local Key = input.LookupBinding( "+walk" ) + + if not isstring( Key ) then Key = "[+walk not bound]" end + + DrawText( X, Y + 20, "hold "..Key.." to drag!",Color(255,255,255,255) ) + + local KeyUse = ply:KeyDown( IN_WALK ) + + if self.OldKeyUse ~= KeyUse then + self.OldKeyUse = KeyUse + + if KeyUse then + surface.PlaySound("common/wpn_select.wav") + + net.Start( "lvs_trailerhitch" ) + net.WriteEntity( self ) + net.SendToServer() + end + end + else + surface.SetDrawColor( Col.r, Col.g, Col.b, Col.a ) + end + + DrawDiamond( X, Y, radius ) + surface.SetDrawColor( 0, 0, 0, 80 ) + DrawDiamond( X + 1, Y + 1, radius ) + cam.End2D() +end + +function ENT:DrawTranslucent() + local ply = LocalPlayer() + + if not IsValid( ply ) or IsValid( ply:lvsGetVehicle() ) or self:GetHitchType() ~= LVS.HITCHTYPE_FEMALE then return end + + local wep = ply:GetActiveWeapon() + + if IsValid( wep ) and wep:GetClass() == "gmod_camera" then return end + + if IsValid( self:GetTargetBase() ) then + self:DrawInfoCoupled( ply ) + + return + end + + self:DrawInfo( ply ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_truckbase.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_truckbase.lua new file mode 100644 index 0000000..637be42 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_truckbase.lua @@ -0,0 +1,271 @@ + +AddCSLuaFile() + +ENT.Base = "lvs_base_wheeldrive" +DEFINE_BASECLASS( "lvs_base_wheeldrive" ) + +ENT.PrintName = "[LVS] Truck Base" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Trucks - Pack" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.WheelBrakeApplySound = "LVS.Brake.Apply" +ENT.WheelBrakeReleaseSound = "LVS.Brake.Release" + +ENT.EngineRevLimited = true +ENT.EngineIgnitionTime = 0 +ENT.EngineStartStopVolume = 1.0 +ENT.EngineStartSound = "common/null.wav" +ENT.EngineStopSound = "common/null.wav" + +ENT.TransShiftSound = "common/null.wav" + +if CLIENT then + ENT.TireSoundTypes = { + ["roll"] = "lvs/vehicles/generic/heavywheel_roll.wav", + ["roll_racing"] = "lvs/vehicles/generic/wheel_roll.wav", + ["roll_dirt"] = "lvs/vehicles/generic/heavywheel_roll_dirt.wav", + ["roll_wet"] = "lvs/vehicles/generic/wheel_roll_wet.wav", + ["roll_damaged"] = "lvs/wheel_damaged_loop.wav", + ["skid"] = "lvs/vehicles/generic/heavywheel_skid.wav", + ["skid_racing"] = "lvs/vehicles/generic/wheel_skid_racing.wav", + ["skid_dirt"] = "lvs/vehicles/generic/heavywheel_skid_dirt.wav", + ["skid_wet"] = "lvs/vehicles/generic/wheel_skid_wet.wav", + ["tire_damage_layer"] = "lvs/wheel_destroyed_loop.wav", + } + + function ENT:TireSoundThink() + for snd, _ in pairs( self.TireSoundTypes ) do + local T = self:GetTireSoundTime( snd ) + + if T > 0 then + local speed = self:GetVelocity():Length() + + local sound = self:StartTireSound( snd ) + + if string.StartsWith( snd, "skid" ) or snd == "tire_damage_layer" then + local vel = speed + speed = math.max( math.abs( self:GetWheelVelocity() ) - vel, 0 ) * 5 + vel + end + + local volume = math.min(speed / 400,1) ^ 2 * T + local pitch = 100 + math.Clamp((speed - 50) / 20,0,155) + + sound:ChangeVolume( volume, 0 ) + sound:ChangePitch( pitch ) + else + self:StopTireSound( snd ) + end + end + end + + function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply ) + BaseClass.LVSHudPaintInfoText( self, X, Y, W, H, ScrX, ScrY, ply ) + + if ply ~= self:GetDriver() then return end + + local Throttle = self:GetThrottle() + local MaxThrottle = self:GetMaxThrottle() + + if self:GetRacingHud() or MaxThrottle >= 0.99 then return end + + if MaxThrottle <= 0.51 then + MaxThrottle = math.min(Throttle,MaxThrottle) + end + + local hX = X + W - H * 0.5 + local hY = Y + H * 0.25 + H * 0.25 + local radius = H * 0.35 + + local rad1 = radius * 0.8 + local rad2 = radius * 1.2 + + local ang = math.rad( 92 + MaxThrottle * 360 ) + + surface.SetDrawColor( 255, 0, 0, 255 ) + + for i = -8,8 do + local printang = ang + math.rad( i * 0.35 ) + + local startX = hX + math.cos( printang ) * rad1 + local startY = hY + math.sin( printang ) * rad1 + local endX = hX + math.cos( printang ) * rad2 + local endY = hY + math.sin( printang ) * rad2 + + surface.DrawLine( startX, startY, endX, endY ) + end + end + + return +end + +function ENT:UpdateReverseSound() + local EntTable = self:GetTable() + + local ReverseSoundHandler = EntTable._ReverseSoundHandler + + if not IsValid( ReverseSoundHandler ) then return end + + local IsActive = ReverseSoundHandler:GetActive() + local ShouldActive = self:GetActive() and self:GetReverse() + + if ShouldActive then + if self:GetVelocity():LengthSqr() < 250 and self:GetThrottle() == 0 then + ShouldActive = false + end + end + + if IsActive == ShouldActive then return end + + ReverseSoundHandler:SetActive( ShouldActive ) +end + +function ENT:AddReverseSound( pos, snd, snd_interior ) + if IsValid( self._ReverseSoundHandler ) then return end + + if not snd then snd = "lvs/vehicles/generic/reverse_warning_beep.wav" end + + self._ReverseSoundHandler = self:AddSoundEmitter( pos, snd, snd_interior ) + self._ReverseSoundHandler:SetSoundLevel( 65 ) + + return self._ReverseSoundHandler +end + +function ENT:LerpBrake( Brake ) + local Old = self:GetBrake() + + BaseClass.LerpBrake( self, Brake ) + + local New = self:GetBrake() + + self:OnBrakeChanged( Old, New ) +end + +function ENT:OnBrakeChanged( Old, New ) + if Old == New then return end + + local BrakeActive = New ~= 0 + + if BrakeActive == self._OldBrakeActive then return end + + self._OldBrakeActive = BrakeActive + + if BrakeActive then + if self:GetVelocity():Length() > 100 then + self:EmitSound( self.WheelBrakeApplySound, 75, 100, 1 ) + end + + return + end + + self:EmitSound( self.WheelBrakeReleaseSound, 75, math.random(90,110), 1 ) +end + +function ENT:PostInitialize( PObj ) + BaseClass.PostInitialize( self, PObj ) + + self:OnCoupleChanged( nil, nil, false ) +end + +function ENT:OnCoupleChanged( targetVehicle, targetHitch, active ) + self.HitchIsHooked = active +end + +function ENT:GetEngineTorque() + local EntTable = self:GetTable() + + local MaxVelocity = EntTable.MaxVelocity + local Velocity = self:GetVelocity():Length() + local Geared = (MaxVelocity / EntTable.TransGears) * 0.5 + + local TargetValue = EntTable.HitchIsHooked and 1 or math.min( math.max( 1 - (math.max( Velocity - Geared, 0 ) / Geared) , 0.5 ) + math.max( (Velocity / MaxVelocity) ^ 2 - 0.5, 0 ), 1 ) + + if TargetValue ~= self:GetMaxThrottle() then + self:SetMaxThrottle( TargetValue ) + end + + return BaseClass.GetEngineTorque( self ) +end + +function ENT:OnHandbrakeActiveChanged( Active ) + if Active then + if self:GetVelocity():Length() > 100 then + self:EmitSound( self.WheelBrakeApplySound, 75, 100, 1 ) + self._AllowReleaseSound = true + end + else + if self._AllowReleaseSound then + self._AllowReleaseSound = nil + self:EmitSound( self.WheelBrakeReleaseSound, 75, math.random(90,110), 1 ) + end + end +end + +function ENT:HandleStart() + self:UpdateReverseSound() + + BaseClass.HandleStart( self ) + + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + local EntTable = self:GetTable() + + local ShouldStart = EntTable.DoEngineStart == true + + local T = CurTime() + + if EntTable.OldShouldStart ~= ShouldStart then + EntTable.OldShouldStart = ShouldStart + + if ShouldStart then + EntTable.EngineStartTime = T + EntTable.EngineIgnitionTime + + Engine:EmitSound( self.EngineStartSound, 75, 100, self.EngineStartStopVolume ) + end + end + + if not EntTable.EngineStartTime or EntTable.EngineStartTime > T then return end + + EntTable.EngineStartTime = nil + + if self:GetEngineActive() then return end + + self:StartEngine() +end + +function ENT:ToggleEngine() + local Engine = self:GetEngine() + + if not IsValid( Engine ) or Engine:GetDestroyed() then + + BaseClass.ToggleEngine( self ) + + return + end + + if self:GetEngineActive() then + self:StopEngine() + else + self.DoEngineStart = true + end +end + +function ENT:StopEngine() + BaseClass.StopEngine( self ) + + self.EngineStartTime = nil + + self.DoEngineStart = false + self.OldShouldStart = false + + local Engine = self:GetEngine() + + if not IsValid( Engine ) then return end + + Engine:EmitSound( self.EngineStopSound, 75, 100, self.EngineStartStopVolume ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_effects.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_effects.lua new file mode 100644 index 0000000..27abba5 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_effects.lua @@ -0,0 +1,243 @@ + +function ENT:StopWheelEffects() + if not self._DoingWheelFx then return end + + self._DoingWheelFx = nil + + self:FinishSkidmark() +end + +function ENT:StartWheelEffects( Base, trace, traceWater ) + self:DoWheelEffects( Base, trace, traceWater ) + + if self._DoingWheelFx then return end + + self._DoingWheelFx = true +end + +function ENT:DoWheelEffects( Base, trace, traceWater ) + if not trace.Hit then self:FinishSkidmark() return end + + local SurfacePropName = util.GetSurfacePropName( trace.SurfaceProps ) + local SkidValue = self:GetSkid() + + if traceWater.Hit then + local Scale = math.min( 0.3 + (SkidValue - 100) / 4000, 1 ) ^ 2 + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetEntity( Base ) + effectdata:SetNormal( trace.HitNormal ) + effectdata:SetMagnitude( Scale ) + effectdata:SetFlags( 1 ) + util.Effect( "lvs_physics_wheeldust", effectdata, true, true ) + + self:FinishSkidmark() + + return + end + + if self.SkidmarkSurfaces[ SurfacePropName ] then + local Scale = math.min( 0.3 + SkidValue / 4000, 1 ) ^ 2 + + if Scale > 0.2 then + self:StartSkidmark( trace.HitPos ) + self:CalcSkidmark( trace, Base:GetCrosshairFilterEnts() ) + else + self:FinishSkidmark() + end + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetEntity( Base ) + effectdata:SetNormal( trace.HitNormal ) + util.Effect( "lvs_physics_wheelsmoke", effectdata, true, true ) + else + self:FinishSkidmark() + end + + if not LVS.ShowEffects then return end + + if self.DustEffectSurfaces[ SurfacePropName ] then + local Scale = math.min( 0.3 + (SkidValue - 100) / 4000, 1 ) ^ 2 + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetEntity( Base ) + effectdata:SetNormal( trace.HitNormal ) + effectdata:SetMagnitude( Scale ) + effectdata:SetFlags( 0 ) + util.Effect( "lvs_physics_wheeldust", effectdata, true, true ) + end +end + +function ENT:DoWaterEffects( Base, traceWater, Pos ) + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.Fraction > 0.5 and traceWater.HitPos or Pos ) + effectdata:SetEntity( Base ) + effectdata:SetMagnitude( self:GetRadius() ) + effectdata:SetFlags( 0 ) + util.Effect( "lvs_physics_wheelwatersplash", effectdata ) +end + +function ENT:DoWheelChainEffects( Base, trace ) + if not LVS.ShowEffects then return end + + if not self.DustEffectSurfaces[ util.GetSurfacePropName( trace.SurfaceProps ) ] then return end + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetEntity( Base ) + effectdata:SetMagnitude( self:GetRadius() ) + effectdata:SetNormal( trace.HitNormal ) + util.Effect( "lvs_physics_trackdust", effectdata, true, true ) +end + +function ENT:CalcWheelEffects() + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + local T = CurTime() + local EntTable = self:GetTable() + + if (EntTable._NextWheelSound or 0) < T then + EntTable._NextWheelSound = T + 0.05 + + if EntTable._fxDelay ~= 1 and EntTable.TraceResult and EntTable.TraceResultWater then + self:CalcWheelSounds( Base, EntTable.TraceResult, EntTable.TraceResultWater ) + end + end + + if (EntTable._NextFx or 0) > T then return end + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local ViewEnt = ply:GetViewEntity() + if IsValid( ViewEnt ) then + ply = ViewEnt + end + + local Delay = 0.05 + + if self:GetWidth() <= 0 then + EntTable._fxDelay = math.min( Delay + (self:GetPos() - ply:GetPos()):LengthSqr() * 0.00000005, 1 ) + else + EntTable._fxDelay = math.min( Delay + (self:GetPos() - ply:GetPos()):LengthSqr() * 0.000000001, 1 ) + end + + EntTable._NextFx = T + EntTable._fxDelay + + local Radius = Base:GetWheelUp() * (self:GetRadius() + 1) + + local Vel = self:GetVelocity() + local Pos = self:GetPos() + Vel * 0.025 + + local StartPos = Pos + Radius + local EndPos = Pos - Radius + + local traceData = { + start = StartPos, + endpos = EndPos, + filter = Base:GetCrosshairFilterEnts(), + } + + local trace = util.TraceLine( traceData ) + + traceData.mask = MASK_WATER + + local traceWater = util.TraceLine( traceData ) + + EntTable.TraceResult = trace + EntTable.TraceResultWater = traceWater + + if traceWater.Hit and trace.HitPos.z < traceWater.HitPos.z then + if math.abs( self:GetRPM() ) > 25 then + self:DoWaterEffects( Base, traceWater, Pos ) + end + else + if self:GetWheelChainMode() and trace.Hit and math.abs( self:GetRPM() ) > 25 and Vel:LengthSqr() > 1500 then + self:DoWheelChainEffects( Base, trace ) + end + end + + if self:GetSlip() < 500 or EntTable._fxDelay > 0.1 then self:StopWheelEffects() return end + + self:StartWheelEffects( Base, trace, traceWater ) +end + +function ENT:CalcWheelSounds( Base, trace, traceWater ) + if not trace.Hit then return end + + local RPM = math.abs( self:GetRPM() ) + + if self:GetDamaged() and RPM > 30 then + if self:GetWheelChainMode() then + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetNormal( self:GetForward() ) + util.Effect( "lvs_physics_trackscraping", effectdata, true, true ) + + Base:DoTireSound( "tracks_damage_layer" ) + else + local Ang = self:GetForward():Angle() + Angle(10,0,0) + Ang:RotateAroundAxis( Base:GetUp(), -90 ) + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos + trace.HitNormal ) + effectdata:SetNormal( Ang:Forward() * math.min( self:GetSlip() / 10000, 3 ) * (self:GetRPM() > 0 and 1 or -1) ) + effectdata:SetMagnitude( 1 ) + util.Effect( "manhacksparks", effectdata, true, true ) + + Base:DoTireSound( "tire_damage_layer" ) + + return + end + end + + if not Base:GetEngineActive() and RPM < 50 then return end + + if traceWater.Hit then + Base:DoTireSound( "roll_wet" ) + + return + end + + local surface = self.DustEffectSurfaces[ util.GetSurfacePropName( trace.SurfaceProps ) ] and "_dirt" or "" + local snd_type = (self:GetSlip() > 500) and "skid" or "roll" + + if Base:GetRacingTires() and surface == "" then surface = "_racing" end + + if (istable( StormFox ) or istable( StormFox2 )) and surface ~= "_dirt" then + local Rain = false + + if StormFox then + Rain = StormFox.IsRaining() + end + + if StormFox2 then + Rain = StormFox2.Weather:IsRaining() + end + + if Rain then + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetEntity( Base ) + effectdata:SetMagnitude( self:BoundingRadius() ) + effectdata:SetFlags( 1 ) + util.Effect( "lvs_physics_wheelwatersplash", effectdata ) + + Base:DoTireSound( snd_type.."_wet" ) + + return + end + end + + if snd_type == "roll" and not self:GetWheelChainMode() and self:GetHP() ~= self:GetMaxHP() then + surface = "_damaged" + end + + Base:DoTireSound( snd_type..surface ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_init.lua new file mode 100644 index 0000000..5a63024 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_init.lua @@ -0,0 +1,117 @@ +include("shared.lua") +include("cl_effects.lua") +include("cl_skidmarks.lua") + +function ENT:Initialize() + local Mins, Maxs = self:GetRenderBounds() + + self:SetRenderBounds( Mins, Maxs, Vector( 50, 50, 50 ) ) + + self:DrawShadow( false ) +end + +if GravHull then + function ENT:DrawWheel( flags ) + self:SetAngles( self:LocalToWorldAngles( self:GetAlignmentAngle() ) ) -- GravHull overwrites SetRenderAngles, but SetAngles works too... + + self:DrawModel( flags ) + end +else + function ENT:DrawWheel( flags ) + self:SetRenderAngles( self:LocalToWorldAngles( self:GetAlignmentAngle() ) ) + + self:DrawModel( flags ) + end +end + +function ENT:DrawWheelBroken( flags ) + local base = self:GetBase() + + if not IsValid( base ) or not LVS.MapDoneLoading then + self:DrawModel( flags ) + + return + end + + -- Alternative method, tuning wheels... Workaround for diggers wheel pack. Flickers for some people... it is what it is + if self:GetBoneCount() > 1 then + local pos = self:GetPos() + + self:SetRenderOrigin( pos - base:GetUp() * base.WheelPhysicsTireHeight ) + self:DrawWheel( flags ) + self:SetRenderOrigin() + + return + end + + -- bone position method... more reliable and works on infmap, but doesnt work on diggers wheel pack + + self:SetupBones() + + local pos, ang = self:GetBonePosition( 0 ) + + if not pos then self:DrawModel( flags ) return end + + self:SetBonePosition( 0, pos - base:GetUp() * base.WheelPhysicsTireHeight, ang ) + + self:DrawWheel( flags ) + + self:SetBonePosition( 0, pos , ang ) +end + +function ENT:Draw( flags ) + if self:GetHideModel() then return end + + if self:GetNWDamaged() then + + self:DrawWheelBroken( flags ) + + return + end + + self:DrawWheel( flags ) +end + +function ENT:DrawTranslucent() + self:CalcWheelEffects() +end + +function ENT:Think() + self:CalcWheelSlip() + + self:SetNextClientThink( CurTime() + 0.1 ) + + return true +end + +function ENT:OnRemove() + self:StopWheelEffects() +end + +function ENT:CalcWheelSlip() + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + local Vel = self:GetVelocity() + local VelLength = Vel:Length() + + local rpmTheoretical = self:VelToRPM( VelLength ) + local rpm = math.abs( self:GetRPM() ) + + if rpm == 0 then + self._WheelSlip = rpmTheoretical + VelLength * 4 + self._WheelSkid = self._WheelSlip + else + self._WheelSlip = math.max( rpm - rpmTheoretical - 10, 0 ) ^ 2 + math.max( math.abs( Base:VectorSplitNormal( self:GetForward(), Vel * 4 ) ) - VelLength, 0 ) + self._WheelSkid = VelLength + self._WheelSlip + end +end + +function ENT:GetSlip() + return (self._WheelSlip or 0) +end + +function ENT:GetSkid() + return (self._WheelSkid or 0) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_skidmarks.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_skidmarks.lua new file mode 100644 index 0000000..6a61204 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/cl_skidmarks.lua @@ -0,0 +1,182 @@ + +ENT.SkidmarkMaterial = Material("sprites/lvs/skidmark") +ENT.SkidmarkMaterialDamaged = Material("sprites/lvs/skidmark_damaged") + +ENT.SkidmarkTraceAdd = Vector(0,0,10) +ENT.SkidmarkDelay = 0.05 +ENT.SkidmarkLifetime = 10 + +ENT.SkidmarkRed = 0 +ENT.SkidmarkGreen = 0 +ENT.SkidmarkBlue = 0 +ENT.SkidmarkAlpha = 150 + +ENT.SkidmarkSurfaces = { + [""] = true, + ["concrete"] = true, + ["plastic_barrel_buoyant"] = true, + ["phx_rubbertire2"] = true, + ["tile"] = true, + ["metal"] = true, + ["boulder"] = true, + ["default"] = true, +} + +ENT.DustEffectSurfaces = { + ["sand"] = true, + ["dirt"] = true, + ["grass"] = true, + ["antlionsand"] = true, + ["gravel"] = true, +} + +function ENT:GetSkidMarks() + if not istable( self._activeSkidMarks ) then + self._activeSkidMarks = {} + end + + return self._activeSkidMarks +end + +function ENT:StartSkidmark( pos ) + if self:GetWidth() <= 0 or self._SkidMarkID or not LVS.ShowTraileffects then return end + + local ID = 1 + for _,_ in ipairs( self:GetSkidMarks() ) do + ID = ID + 1 + end + + self._activeSkidMarks[ ID ] = { + active = true, + startpos = pos + self.SkidmarkTraceAdd, + delay = CurTime() + self.SkidmarkDelay, + damaged = self:GetNWDamaged(), + positions = {}, + } + + self._SkidMarkID = ID +end + +function ENT:FinishSkidmark() + if not self._SkidMarkID then return end + + self._activeSkidMarks[ self._SkidMarkID ].active = false + + self._SkidMarkID = nil +end + +function ENT:RemoveSkidmark( id ) + if not id then return end + + self._activeSkidMarks[ id ] = nil +end + +function ENT:CalcSkidmark( trace, Filter ) + local T = CurTime() + local CurActive = self:GetSkidMarks()[ self._SkidMarkID ] + + if not CurActive or not CurActive.active or CurActive.delay >= T then return end + + CurActive.delay = T + self.SkidmarkDelay + + local W = self:GetWidth() + + local cur = trace.HitPos + self.SkidmarkTraceAdd * 0.5 + + local prev = CurActive.positions[ #CurActive.positions ] + + if not prev then + local sub = cur - CurActive.startpos + + local L = sub:Length() * 0.5 + local C = (cur + CurActive.startpos) * 0.5 + + local Ang = sub:Angle() + local Forward = Ang:Right() + local Right = Ang:Forward() + + local p1 = C + Forward * W + Right * L + local p2 = C - Forward * W + Right * L + + local t1 = util.TraceLine( { start = p1, endpos = p1 - self.SkidmarkTraceAdd } ) + local t2 = util.TraceLine( { start = p2, endpos = p2 - self.SkidmarkTraceAdd } ) + + prev = { + px = CurActive.startpos, + p1 = t1.HitPos + t1.HitNormal, + p2 = t2.HitPos + t2.HitNormal, + lifetime = T + self.SkidmarkLifetime - self.SkidmarkDelay, + alpha = 0, + } + end + + local sub = cur - prev.px + + local L = sub:Length() * 0.5 + local C = (cur + prev.px) * 0.5 + + local Ang = sub:Angle() + local Forward = Ang:Right() + local Right = Ang:Forward() + + local p1 = C + Forward * W + Right * L + local p2 = C - Forward * W + Right * L + + local t1 = util.TraceLine( { start = p1, endpos = p1 - self.SkidmarkTraceAdd, filter = Filter, } ) + local t2 = util.TraceLine( { start = p2, endpos = p2 - self.SkidmarkTraceAdd, filter = Filter, } ) + + local nextID = #CurActive.positions + 1 + + CurActive.positions[ nextID ] = { + px = cur, + p1 = t1.HitPos + t1.HitNormal, + p2 = t2.HitPos + t2.HitNormal, + lifetime = T + self.SkidmarkLifetime, + alpha = math.min( nextID / 10, 1 ), + } +end + +function ENT:RenderSkidMarks() + local T = CurTime() + + for id, skidmark in pairs( self:GetSkidMarks() ) do + local prev + local AmountDrawn = 0 + + if skidmark.damaged then + render.SetMaterial( self.SkidmarkMaterialDamaged ) + else + render.SetMaterial( self.SkidmarkMaterial ) + end + + for markID, data in pairs( skidmark.positions ) do + if not prev then + + prev = data + + continue + end + + local Mul = math.max( data.lifetime - CurTime(), 0 ) / self.SkidmarkLifetime + + if Mul > 0 then + AmountDrawn = AmountDrawn + 1 + render.DrawQuad( data.p2, data.p1, prev.p1, prev.p2, Color( self.SkidmarkRed, self.SkidmarkGreen, self.SkidmarkBlue, math.min(255 * Mul * data.alpha,self.SkidmarkAlpha) ) ) + end + + prev = data + end + + if not skidmark.active and AmountDrawn == 0 then + self:RemoveSkidmark( id ) + end + end +end + +hook.Add( "PreDrawTranslucentRenderables", "!!!!lvs_skidmarks", function( bDepth, bSkybox ) + if bSkybox then return end + + for _, wheel in ipairs( ents.FindByClass("lvs_wheeldrive_wheel") ) do + wheel:RenderSkidMarks() + end +end) diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/init.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/init.lua new file mode 100644 index 0000000..92400a7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/init.lua @@ -0,0 +1,150 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_effects.lua" ) +AddCSLuaFile( "cl_skidmarks.lua" ) +include("shared.lua") +include("sv_axle.lua") +include("sv_brakes.lua") +include("sv_damage.lua") + +function ENT:GetWheelType() + return self._WheelType or LVS.WHEELTYPE_NONE +end + +function ENT:SetWheelType( wheel_type ) + self._WheelType = wheel_type +end + +function ENT:SetSuspensionHeight( newheight ) + newheight = newheight and math.Clamp( newheight, -1, 1 ) or 0 + + self._SuspensionHeightMultiplier = newheight + + if not IsValid( self.SuspensionConstraintElastic ) then return end + + local Length = self.SuspensionConstraintElastic:GetTable().length or 25 + + self.SuspensionConstraintElastic:Fire( "SetSpringLength", Length + Length * newheight ) +end + +function ENT:GetSuspensionHeight() + return self._SuspensionHeightMultiplier or 0 +end + +function ENT:SetSuspensionStiffness( new ) + new = new and math.Clamp( new, -1, 1 ) or 0 + + self._SuspensionStiffnessMultiplier = new + + if not IsValid( self.SuspensionConstraintElastic ) then return end + + local data = self.SuspensionConstraintElastic:GetTable() + local damping = data.damping or 2000 + local constant = data.constant or 20000 + + self.SuspensionConstraintElastic:Fire( "SetSpringConstant", constant + constant * new, 0 ) + self.SuspensionConstraintElastic:Fire( "SetSpringDamping", damping + damping * new, 0 ) +end + +function ENT:GetSuspensionStiffness() + return self._SuspensionStiffnessMultiplier or 0 +end + +function ENT:Initialize() + self:SetCollisionGroup( COLLISION_GROUP_PASSABLE_DOOR ) + + -- this is so vj npcs can still see us + self:AddEFlags( EFL_DONTBLOCKLOS ) +end + +function ENT:GravGunPickupAllowed( ply ) + return false +end + +function ENT:StartThink() + if self.AutomaticFrameAdvance then return end + + self.AutomaticFrameAdvance = true + + self.Think = function( self ) + self:NextThink( CurTime() ) + return true + end + + self:Think() +end + +function ENT:Think() + return false +end + +function ENT:OnRemove() + self:StopLeakAir() +end + +function ENT:lvsMakeSpherical( radius ) + if not radius or radius <= 0 then + radius = (self:OBBMaxs() - self:OBBMins()) * 0.5 + radius = math.max( radius.x, radius.y, radius.z ) + end + + self:PhysicsInitSphere( radius, "jeeptire" ) + + self:SetRadius( radius ) + + self:DrawShadow( not self:GetHideModel() ) +end + +function ENT:PhysicsMaterialUpdate( TargetValue ) + local base = self:GetBase() + local PhysObj = self:GetPhysicsObject() + + if not IsValid( base ) or not IsValid( PhysObj ) then return end + + local ListID = math.Clamp( math.Round( (TargetValue or 1) * 10, 0 ), 0, 12 ) + + PhysObj:SetMaterial( base.WheelPhysicsMaterials[ ListID ] ) +end + +function ENT:PhysicsOnGround() + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return false end + + local EntLoad,_ = PhysObj:GetStress() + + return EntLoad > 0 +end + +function ENT:PhysicsCollide( data, physobj ) + + if data.Speed > 150 and data.DeltaTime > 0.2 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + local Volume = math.min( math.abs( VelDif ) / 300 , 1 ) + + self:EmitSound( "lvs/vehicles/generic/suspension_hit_".. math.random(1,17) ..".ogg", 70, 100, Volume ^ 2 ) + end + + local HitEntity = data.HitEntity + local HitObject = data.HitObject + + if IsValid( HitEntity ) and IsValid( HitObject ) then + physobj:SetVelocityInstantaneous( data.OurOldVelocity ) + + if HitObject:IsMotionEnabled() and HitObject:GetMass() < physobj:GetMass() then + HitObject:SetVelocityInstantaneous( data.OurOldVelocity * 2 ) + end + end + + if math.abs(data.OurNewVelocity.z - data.OurOldVelocity.z) > 100 then + physobj:SetVelocityInstantaneous( data.OurOldVelocity ) + end + + local base = self:GetBase() + + if IsValid( base ) then + base:OnWheelCollision( data, physobj ) + end +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/shared.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/shared.lua new file mode 100644 index 0000000..cff1f8c --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/shared.lua @@ -0,0 +1,84 @@ + +ENT.PrintName = "Wheel" + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT._lvsRepairToolLabel = "Wheel" +ENT._lvsNoPhysgunInteraction = true + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "Radius") + self:NetworkVar( "Float", 1, "Width") + + self:NetworkVar( "Float", 2, "Camber" ) + self:NetworkVar( "Float", 3, "Caster" ) + self:NetworkVar( "Float", 4, "Toe" ) + + self:NetworkVar( "Float", 5, "RPM" ) + + self:NetworkVar( "Float", 6, "HP" ) + self:NetworkVar( "Float", 7, "MaxHP" ) + + self:NetworkVar( "Angle", 0, "AlignmentAngle" ) + + self:NetworkVar( "Entity", 0, "Base" ) + + self:NetworkVar( "Bool", 0, "HideModel" ) + self:NetworkVar( "Bool", 1, "Destroyed" ) + self:NetworkVar( "Bool", 2, "NWDamaged" ) + self:NetworkVar( "Bool", 3, "WheelChainMode" ) + + if SERVER then + self:SetMaxHP( 100 ) + self:SetHP( 100 ) + self:SetWidth( 3 ) + + self:NetworkVarNotify( "HP", self.HealthValueChanged ) + end +end + +function ENT:GetDamaged() + return self:GetNWDamaged() +end + +function ENT:VelToRPM( speed ) + if not speed then return 0 end + + return speed * 60 / math.pi / (self:GetRadius() * 2) +end + +function ENT:RPMToVel( rpm ) + if not rpm then return 0 end + + return (math.pi * rpm * self:GetRadius() * 2) / 60 +end + +function ENT:CheckAlignment() + self.CamberCasterToe = (math.abs( self:GetToe() ) + math.abs( self:GetCaster() ) + math.abs( self:GetCamber() )) ~= 0 + + if CLIENT then return end + + local SteerType = self:GetSteerType() + local Caster = self:GetCaster() + + local Camber = math.abs( self:GetCamber() ) + local CamberValue1 = (math.min( Camber, 15 ) / 15) * 0.3 + local CamberValue2 = (math.Clamp( Camber - 15, 0, 65 ) / 65) * 0.7 + + local CasterValue = (math.min( math.abs( Caster ), 15 ) / 15) * math.max( 1 - Camber / 10, 0 ) + + if SteerType == LVS.WHEEL_STEER_NONE then CasterValue = 0 end + + if SteerType == LVS.WHEEL_STEER_FRONT and Caster < 0 then CasterValue = 0 end + + if SteerType == LVS.WHEEL_STEER_REAR and Caster > 0 then CasterValue = 0 end + + local TractionValue = 1 - CamberValue1 - CamberValue2 + CasterValue + + self:PhysicsMaterialUpdate( TractionValue ) + + return TractionValue +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_axle.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_axle.lua new file mode 100644 index 0000000..031b318 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_axle.lua @@ -0,0 +1,58 @@ + +AccessorFunc(ENT, "axle", "Axle", FORCE_NUMBER) + +function ENT:SetMaster( master ) + self._Master = master +end + +function ENT:GetMaster() + return self._Master +end + +function ENT:GetDirectionAngle() + if not IsValid( self._Master ) then return angle_zero end + + return self._Master:GetAngles() +end + +function ENT:GetRotationAxis() + local WorldAngleDirection = -self:WorldToLocalAngles( self:GetDirectionAngle() ) + + return WorldAngleDirection:Right() +end + +function ENT:GetSteerType() + if self._steerType then return self._steerType end + + local base = self:GetBase() + + if not IsValid( base ) then return 0 end + + self._steerType = base:GetAxleData( self:GetAxle() ).SteerType + + return self._steerType +end + +function ENT:GetTorqueFactor() + if self._torqueFactor then return self._torqueFactor end + + local base = self:GetBase() + + if not IsValid( base ) then return 0 end + + self._torqueFactor = base:GetAxleData( self:GetAxle() ).TorqueFactor or 0 + + return self._torqueFactor +end + +function ENT:GetBrakeFactor() + if self._brakeFactor then return self._brakeFactor end + + local base = self:GetBase() + + if not IsValid( base ) then return 0 end + + self._brakeFactor = base:GetAxleData( self:GetAxle() ).BrakeFactor or 0 + + return self._brakeFactor +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_brakes.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_brakes.lua new file mode 100644 index 0000000..06c0ebb --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_brakes.lua @@ -0,0 +1,81 @@ + +function ENT:SetHandbrake( enable ) + if enable then + + self:EnableHandbrake() + + return + end + + self:ReleaseHandbrake() +end + +function ENT:IsHandbrakeActive() + return self._handbrakeActive == true +end + +function ENT:EnableHandbrake() + if self._handbrakeActive then return end + + self._handbrakeActive = true + + self:LockRotation() +end + +function ENT:ReleaseHandbrake() + if not self._handbrakeActive then return end + + self._handbrakeActive = nil + + self:ReleaseRotation() +end + +function ENT:LockRotation( TimedLock ) + + if TimedLock then + self._RotationLockTime = CurTime() + 0.15 + end + + if self:IsRotationLocked() then return end + + local Master = self:GetMaster() + + if not IsValid( Master ) then return end + + self.bsLock = constraint.AdvBallsocket(self,Master,0,0,vector_origin,vector_origin,0,0,-0.1,-0.1,-0.1,0.1,0.1,0.1,0,0,0,1,1) + self.bsLock.DoNotDuplicate = true + + local PhysObj = self:GetPhysicsObject() + + if self._OriginalMass or not IsValid( PhysObj ) then return end + + local Mass = PhysObj:GetMass() + + self._OriginalMass = Mass + + PhysObj:SetMass( Mass * 2 ) +end + +function ENT:ReleaseRotation() + if self._RotationLockTime then + if self._RotationLockTime > CurTime() then + return + end + end + + if not self:IsRotationLocked() then return end + + self.bsLock:Remove() + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) or not self._OriginalMass then return end + + PhysObj:SetMass( self._OriginalMass ) + + self._OriginalMass = nil +end + +function ENT:IsRotationLocked() + return IsValid( self.bsLock ) +end diff --git a/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_damage.lua b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_damage.lua new file mode 100644 index 0000000..969980e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/entities/lvs_wheeldrive_wheel/sv_damage.lua @@ -0,0 +1,216 @@ + +ENT.DSDamageAllowedType = DMG_SLASH + DMG_AIRBOAT + DMG_BULLET + DMG_SNIPER + DMG_BUCKSHOT + DMG_PREVENT_PHYSICS_FORCE +ENT.DSArmorIgnoreForce = 0 + +function ENT:OnTakeDamage( dmginfo ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + if self:GetWheelChainMode() then + if dmginfo:IsDamageType( DMG_PREVENT_PHYSICS_FORCE ) then return end + + base:OnTakeDamage( dmginfo ) + + return + end + + local MaxArmor = self:GetMaxHP() + local Damage = dmginfo:GetDamage() + + if not dmginfo:IsDamageType( DMG_PREVENT_PHYSICS_FORCE ) then + local IsFireDamage = dmginfo:IsDamageType( DMG_BURN ) + + if dmginfo:GetDamageForce():Length() < self.DSArmorIgnoreForce and not IsFireDamage then return end + + local MaxHealth = base:GetMaxHP() + + local ArmoredHealth = MaxHealth + MaxArmor + local NumShotsToKill = ArmoredHealth / Damage + + local ScaleDamage = math.Clamp( MaxHealth / (NumShotsToKill * Damage),0,1) + + dmginfo:ScaleDamage( ScaleDamage ) + + base:OnTakeDamage( dmginfo ) + end + + if not dmginfo:IsDamageType( self.DSDamageAllowedType ) then return end + + if not isnumber( base.WheelPhysicsTireHeight ) or base.WheelPhysicsTireHeight <= 0 then return end + + local CurHealth = self:GetHP() + + local NewHealth = math.Clamp( CurHealth - Damage, 0, MaxArmor ) + + self:SetHP( NewHealth ) + + if NewHealth > 0 then + self:StartLeakAir() + + return + end + + self:DestroyTire() +end + +function ENT:StartLeakAir() + if self._IsLeakingAir then return end + + self._IsLeakingAir = true + + local ID = "lvsLeakAir"..self:EntIndex() + + timer.Create( ID, 0.2, 0, function() + if not IsValid( self ) then timer.Remove( ID ) return end + + local dmg = DamageInfo() + dmg:SetDamage( 1 ) + dmg:SetAttacker( self ) + dmg:SetInflictor( self ) + dmg:SetDamageType( DMG_PREVENT_PHYSICS_FORCE ) + self:TakeDamageInfo( dmg ) + end) +end + +function ENT:StopLeakAir() + if not self._IsLeakingAir then return end + + local ID = "lvsLeakAir"..self:EntIndex() + + timer.Remove( ID ) + + self._IsLeakingAir = nil +end + +function ENT:HealthValueChanged( name, old, new) + if new == old or old > new or new ~= self:GetMaxHP() then return end + + self:RepairTire() +end + +function ENT:DestroyTire() + if self:GetNWDamaged() then return end + + self:SetNWDamaged( true ) + + self:StopLeakAir() + + local base = self:GetBase() + local PhysObj = self:GetPhysicsObject() + + if not IsValid( base ) or not IsValid( PhysObj ) then return end + + self._OldTirePhysProp = PhysObj:GetMaterial() + + PhysObj:SetMaterial( "glass" ) + + self:EmitSound("lvs/wheel_pop"..math.random(1,4)..".ogg") + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( self ) + util.Effect( "lvs_tire_blow", effectdata, true, true ) + + self._RestoreBodyGroups = {} + + for id, group in pairs( self:GetBodyGroups() ) do + for subid, subgroup in pairs( group.submodels ) do + if subgroup == "" or string.lower( subgroup ) == "empty" then + + local bodyGroupId = id - 1 + + self._RestoreBodyGroups[ bodyGroupId ] = self:GetBodygroup( bodyGroupId ) + + self:SetBodygroup( bodyGroupId, subid ) + end + end + end + + if not IsValid( self.SuspensionConstraintElastic ) then return end + + local Length = (self.SuspensionConstraintElastic:GetTable().length or 25) - base.WheelPhysicsTireHeight + + self.SuspensionConstraintElastic:Fire( "SetSpringLength", math.max( Length - base.WheelPhysicsTireHeight , 1 ) ) +end + +function ENT:RepairTire() + self:StopLeakAir() + + if not self._OldTirePhysProp then return end + + if istable( self._RestoreBodyGroups ) then + for bodyGroupId, subid in pairs( self._RestoreBodyGroups ) do + self:SetBodygroup( bodyGroupId, subid ) + end + + self._RestoreBodyGroups = nil + end + + self:SetNWDamaged( false ) + self:SetSuspensionHeight( self._SuspensionHeightMultiplier ) + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) or PhysObj:GetMaterial() ~= "glass" then + goto FinishRepairTire + end + + PhysObj:SetMaterial( self._OldTirePhysProp ) + + -- coders from the industry hate this, so we use it intentionally to assert dominance + :: FinishRepairTire :: + + self._OldTirePhysProp = nil +end + +function ENT:IsTireDestroyed() + return isstring( self._OldTirePhysProp ) +end + +function ENT:SetDamaged( new ) + if new == self:GetNWDamaged() then return end + + self:SetNWDamaged( new ) + + if new then + if not self._torqueFactor or self.old_torqueFactor then return end + + self.old_torqueFactor = self._torqueFactor + self._torqueFactor = self._torqueFactor * 0.25 + + return + end + + if not self.old_torqueFactor then return end + + self._torqueFactor = self.old_torqueFactor + self.old_torqueFactor = nil +end + +function ENT:Destroy() + if self:GetDestroyed() then return end + + self:SetDestroyed( true ) + self:SetDamaged( true ) + + local Master = self:GetMaster() + + if not IsValid( Master ) or IsValid( self.bsLockDMG ) then return end + + local Fric = 10 + + self.bsLockDMG = constraint.AdvBallsocket(self,Master,0,0,vector_origin,vector_origin,0,0,-180,-180,-180,180,180,180,Fric,Fric,Fric,1,1) + self.bsLockDMG.DoNotDuplicate = true +end + +function ENT:Repair() + self:SetHP( self:GetMaxHP() ) + + self:SetDestroyed( false ) + self:SetDamaged( false ) + + if IsValid( self.bsLockDMG ) then + self.bsLockDMG:Remove() + end +end diff --git a/garrysmod/addons/lvs_base/lua/includes/circles/circles.lua b/garrysmod/addons/lvs_base/lua/includes/circles/circles.lua new file mode 100644 index 0000000..bfd3728 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/includes/circles/circles.lua @@ -0,0 +1,495 @@ +if SERVER then return false end + +local _R = debug.getregistry() +if _R.Circles then return _R.Circles end + +local CIRCLE = {} +CIRCLE.__index = CIRCLE + +CIRCLE_FILLED = 0 +CIRCLE_OUTLINED = 1 +CIRCLE_BLURRED = 2 + +local New do + local err_number = "bad argument #%i to 'New' (number expected, got %s)" + + function New(t, r, x, y, ...) + assert(isnumber(t), string.format(err_number, 1, type(t))) + assert(isnumber(r), string.format(err_number, 2, type(r))) + assert(isnumber(x), string.format(err_number, 3, type(x))) + assert(isnumber(y), string.format(err_number, 4, type(y))) + + local circle = setmetatable({}, CIRCLE) + + circle:SetType(t) + circle:SetRadius(r) + circle:SetX(x) + circle:SetY(y) + + circle:SetVertices({Count = 0}) + + if t == CIRCLE_OUTLINED then + local outline_width = ... + assert(outline_width == nil or isnumber(outline_width), string.format(err_number, 5, type(outline_width))) + + circle:SetOutlineWidth(outline_width) + elseif t == CIRCLE_BLURRED then + local blur_layers, blur_density = ... + assert(blur_layers == nil or isnumber(blur_layers), string.format(err_number, 5, type(blur_layers))) + assert(blur_density == nil or isnumber(blur_density), string.format(err_number, 6, type(blur_density))) + + circle:SetBlurLayers(blur_layers) + circle:SetBlurDensity(blur_density) + end + + return circle + end +end + +local RotateVertices do + local err_table = "bad argument #1 to 'RotateVertices' (table expected, got %s)" + local err_number = "bad argument #%i to 'RotateVertices' (number expected, got %s)" + + function RotateVertices(vertices, ox, oy, rotation, rotate_uv) + assert(istable(vertices), string.format(err_table, type(vertices))) + assert(isnumber(ox), string.format(err_number, 2, type(ox))) + assert(isnumber(oy), string.format(err_number, 3, type(oy))) + assert(isnumber(rotation), string.format(err_number, 4, type(rotation))) + + local rotation = math.rad(rotation) + local c = math.cos(rotation) + local s = math.sin(rotation) + + for i = 1, vertices.Count or #vertices do + local vertex = vertices[i] + local vx, vy = vertex.x, vertex.y + + vx = vx - ox + vy = vy - oy + + vertex.x = ox + (vx * c - vy * s) + vertex.y = oy + (vx * s + vy * c) + + if rotate_uv == false then + local u, v = vertex.u, vertex.v + u, v = u - 0.5, v - 0.5 + + vertex.u = 0.5 + (u * c - v * s) + vertex.v = 0.5 + (u * s + v * c) + end + end + end +end + +local CalculateVertices do + local err_number = "bad argument #%i to 'CalculateVertices' (number expected, got %s)" + + function CalculateVertices(x, y, radius, rotation, start_angle, end_angle, distance, rotate_uv) + assert(isnumber(x), string.format(err_number, 1, type(x))) + assert(isnumber(y), string.format(err_number, 2, type(y))) + assert(isnumber(radius), string.format(err_number, 3, type(radius))) + assert(isnumber(rotation), string.format(err_number, 4, type(rotation))) + assert(isnumber(start_angle), string.format(err_number, 5, type(start_angle))) + assert(isnumber(end_angle), string.format(err_number, 6, type(end_angle))) + assert(isnumber(distance), string.format(err_number, 7, type(distance))) + + local vertices = {Count = 0} + local step = distance / radius + + local rad_start_angle = math.rad(start_angle) + local rad_end_angle = math.rad(end_angle) + local rad_rotation = math.rad(rotation) + + for a = rad_start_angle, rad_end_angle + step, step do + a = math.min(a, rad_end_angle) + + local c = math.cos(a + rad_rotation) + local s = math.sin(a + rad_rotation) + + local vertex = { + x = x + c * radius, + y = y + s * radius, + } + + if rotate_uv == false then + vertex.u = 0.5 + math.cos(a) / 2 + vertex.v = 0.5 + math.sin(a) / 2 + else + vertex.u = 0.5 + c / 2 + vertex.v = 0.5 + s / 2 + end + + vertices.Count = vertices.Count + 1 + vertices[vertices.Count] = vertex + end + + if end_angle - start_angle ~= 360 then + table.insert(vertices, 1, { + x = x, y = y, + u = 0.5, v = 0.5, + }) + + vertices.Count = vertices.Count + 1 + else + table.remove(vertices) + vertices.Count = vertices.Count - 1 + end + + return vertices + end +end + +function CIRCLE:__tostring() + return string.format("Circle: %p", self) +end + +function CIRCLE:Copy() + return table.Copy(self) +end + +function CIRCLE:IsValid() + return ( + not self.m_Dirty and + self.m_Vertices.Count >= 3 and + self.m_Radius >= 1 and + self.m_Distance >= 1 + ) +end + +function CIRCLE:Calculate() + local rotate_uv = self.m_RotateMaterial + + local radius = self.m_Radius + local x, y = self.m_X, self.m_Y + + local rotation = self.m_Rotation + local start_angle = self.m_StartAngle + local end_angle = self.m_EndAngle + + local distance = self.m_Distance + + assert(radius >= 1, string.format("circle radius should be >= 1 (%.4f)", radius)) + assert(distance >= 1, string.format("circle distance should be >= 1 (%.4f)", distance)) + + self:SetVertices(CalculateVertices(x, y, radius, rotation, start_angle, end_angle, distance, rotate_uv)) + + if self.m_Type == CIRCLE_OUTLINED then + local inner = self.m_ChildCircle or self:Copy() + local inner_r = radius - self.m_OutlineWidth + + inner:SetType(CIRCLE_FILLED) + + inner:SetPos(x, y) + inner:SetRadius(inner_r) + inner:SetRotation(rotation) + inner:SetAngles(start_angle, end_angle) + inner:SetDistance(distance) + + inner:SetColor(false) + inner:SetMaterial(false) + + inner:SetShouldRender(inner_r >= 1) + inner:SetDirty(inner.m_ShouldRender) + + self:SetShouldRender(inner_r < radius) + self:SetChildCircle(inner) + elseif self.m_ChildCircle then + self.m_ChildCircle = nil + end + + self:SetDirty(false) + + return self +end + +do + local blur = Material("pp/blurscreen") + + function CIRCLE:__call() + if self.m_Dirty then self:Calculate() end + + if not self:IsValid() then return false end + if not self.m_ShouldRender then return false end + + do + local col, mat = self.m_Color, self.m_Material + + if IsColor(col) then + if col.a <= 0 then return end + surface.SetDrawColor(col.r, col.g, col.b, col.a) + end + + if mat == true then + draw.NoTexture() + elseif TypeID(mat) == TYPE_MATERIAL then + surface.SetMaterial(mat) + end + end + + if self.m_Type == CIRCLE_OUTLINED then + render.ClearStencil() + + render.SetStencilEnable(true) + render.SetStencilTestMask(0xFF) + render.SetStencilWriteMask(0xFF) + render.SetStencilReferenceValue(0x01) + + render.SetStencilCompareFunction(STENCIL_NEVER) + render.SetStencilFailOperation(STENCIL_REPLACE) + render.SetStencilZFailOperation(STENCIL_REPLACE) + + self.m_ChildCircle() + + render.SetStencilCompareFunction(STENCIL_GREATER) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + + surface.DrawPoly(self.m_Vertices) + render.SetStencilEnable(false) + elseif self.m_Type == CIRCLE_BLURRED then + render.ClearStencil() + + render.SetStencilEnable(true) + render.SetStencilTestMask(0xFF) + render.SetStencilWriteMask(0xFF) + render.SetStencilReferenceValue(0x01) + + render.SetStencilCompareFunction(STENCIL_NEVER) + render.SetStencilFailOperation(STENCIL_REPLACE) + render.SetStencilZFailOperation(STENCIL_REPLACE) + + surface.DrawPoly(self.m_Vertices) + + render.SetStencilCompareFunction(STENCIL_LESSEQUAL) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + + surface.SetMaterial(blur) + + local sw, sh = ScrW(), ScrH() + + for i = 1, self.m_BlurLayers do + blur:SetFloat("$blur", (i / self.m_BlurLayers) * self.m_BlurDensity) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(0, 0, sw, sh) + end + render.SetStencilEnable(false) + else + surface.DrawPoly(self.m_Vertices) + end + + return true + end + + CIRCLE.Draw = CIRCLE.__call +end + +do + local err_number = "bad argument #%i to 'Translate' (number expected, got %s)" + + function CIRCLE:Translate(x, y) + assert(isnumber(x), string.format(err_number, 1, type(x))) + assert(isnumber(y), string.format(err_number, 2, type(y))) + + if x ~= 0 or y ~= 0 then + self.m_X = self.m_X + x + self.m_Y = self.m_Y + y + + if self:IsValid() then + for i = 1, self.m_Vertices.Count do + local vertex = self.m_Vertices[i] + + vertex.x = vertex.x + x + vertex.y = vertex.y + y + end + + if self.m_Type == CIRCLE_OUTLINED and self.m_ChildCircle then + self.m_ChildCircle:Translate(x, y) + end + end + end + + return self + end +end + +do + local err_number = "bad argument #1 to 'Scale' (number expected, got %s)" + + function CIRCLE:Scale(scale) + assert(isnumber(scale), string.format(err_number, type(scale))) + + if scale ~= 1 then + self.m_Radius = self.m_Radius * scale + + if self:IsValid() then + local x, y = self.m_X, self.m_Y + + for i = 1, self.m_Vertices.Count do + local vertex = self.m_Vertices[i] + + vertex.x = x + (vertex.x - x) * scale + vertex.y = y + (vertex.y - y) * scale + end + + if self.m_Type == CIRCLE_OUTLINED and self.m_ChildCircle then + self.m_ChildCircle:Scale(scale) + end + end + end + + return self + end +end + +do + local err_number = "bad argument #1 to 'Rotate' (number expected, got %s)" + + function CIRCLE:Rotate(rotation) + assert(isnumber(rotation), string.format(err_number, type(rotation))) + + if rotation ~= 0 then + self.m_Rotation = self.m_Rotation + rotation + + if self:IsValid() then + local x, y = self.m_X, self.m_Y + local vertices = self.m_Vertices + local rotate_uv = self.m_RotateMaterial + + RotateVertices(vertices, x, y, rotation, rotate_uv) + + if self.m_Type == CIRCLE_OUTLINED and self.m_ChildCircle then + self.m_ChildCircle:Rotate(rotation) + end + end + end + + return self + end +end + +do + local function AccessorFunc(name, default, dirty, callback) + local varname = "m_" .. name + + CIRCLE["Get" .. name] = function(self) + return self[varname] + end + + CIRCLE["Set" .. name] = function(self, value) + if default ~= nil and value == nil then + value = default + end + + if self[varname] ~= value then + if dirty then + self[dirty] = true + end + + if callback ~= nil then + local new = callback(self, self[varname], value) + value = new ~= nil and new or value + end + + self[varname] = value + end + + return self + end + + CIRCLE[varname] = default + end + + local function OffsetVerticesX(circle, old, new) + circle:Translate(new - old, 0) + + if circle.m_Type == CIRCLE_OUTLINED and circle.m_ChildCircle then + circle.m_ChildCircle:Translate(new - old, 0) + end + end + + local function OffsetVerticesY(circle, old, new) + circle:Translate(0, new - old) + + if circle.m_Type == CIRCLE_OUTLINED and circle.m_ChildCircle then + circle.m_ChildCircle:Translate(0, new - old) + end + end + + local function UpdateRotation(circle, old, new) + circle:Rotate(new - old) + + if circle.m_Type == CIRCLE_OUTLINED and circle.m_ChildCircle then + circle.m_ChildCircle:Rotate(new - old) + end + end + + -- These are set internally. Only use them if you know what you're doing. + AccessorFunc("Dirty", true) + AccessorFunc("Vertices", false) + AccessorFunc("ChildCircle", false) + AccessorFunc("ShouldRender", true) + + AccessorFunc("Color", false) -- The colour you want the circle to be. If set to false then surface.SetDrawColor's can be used. + AccessorFunc("Material", false) -- The material you want the circle to render. If set to false then surface.SetMaterial can be used. + AccessorFunc("RotateMaterial", true) -- Sets whether or not the circle's UV points should be rotated with the vertices. + + AccessorFunc("Type", CIRCLE_FILLED, "m_Dirty") -- The circle's type. + AccessorFunc("X", 0, false, OffsetVerticesX) -- The circle's X position relative to the top left of the screen. + AccessorFunc("Y", 0, false, OffsetVerticesY) -- The circle's Y position relative to the top left of the screen. + AccessorFunc("Radius", 8, "m_Dirty") -- The circle's radius. + AccessorFunc("Rotation", 0, false, UpdateRotation) -- The circle's rotation, measured in degrees. + AccessorFunc("StartAngle", 0, "m_Dirty") -- The circle's start angle, measured in degrees. + AccessorFunc("EndAngle", 360, "m_Dirty") -- The circle's end angle, measured in degrees. + AccessorFunc("Distance", 10, "m_Dirty") -- The maximum distance between each of the circle's vertices. This should typically be used for large circles in 3D2D. + + AccessorFunc("BlurLayers", 3) -- The circle's blur layers if Type is set to CIRCLE_BLURRED. + AccessorFunc("BlurDensity", 2) -- The circle's blur density if Type is set to CIRCLE_BLURRED. + AccessorFunc("OutlineWidth", 10, "m_Dirty") -- The circle's outline width if Type is set to CIRCLE_OUTLINED. + + function CIRCLE:SetPos(x, y) + x = tonumber(x) or self.m_X + y = tonumber(y) or self.m_Y + + if self:IsValid() then + self:Translate(x - self.m_X, y - self.m_Y) + else + self.m_X = x + self.m_Y = y + end + + return self + end + + function CIRCLE:SetAngles(s, e) + s = tonumber(s) or self.m_StartAngle + e = tonumber(e) or self.m_EndAngle + + self:SetDirty(self.m_Dirty or s ~= self.m_StartAngle or e ~= self.m_EndAngle) + + self.m_StartAngle = s + self.m_EndAngle = e + + return self + end + + function CIRCLE:GetPos() + return self.m_X, self.m_Y + end + + function CIRCLE:GetAngles() + return self.m_StartAngle, self.m_EndAngle + end +end + +_R.Circles = { + _MT = CIRCLE, + + New = New, + RotateVertices = RotateVertices, + CalculateVertices = CalculateVertices, +} + +return _R.Circles diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_camera.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_camera.lua new file mode 100644 index 0000000..92450aa --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_camera.lua @@ -0,0 +1,71 @@ + +function LVS:CalcView( vehicle, ply, pos, angles, fov, pod ) + local view = {} + view.origin = pos + view.angles = angles + view.fov = fov + view.drawviewer = false + + if not pod:GetThirdPersonMode() then return view end + + local mn = vehicle:OBBMins() + local mx = vehicle:OBBMaxs() + local radius = ( mn - mx ):Length() + local radius = radius + radius * pod:GetCameraDistance() + + local TargetOrigin = view.origin + ( view.angles:Forward() * -radius ) + view.angles:Up() * radius * pod:GetCameraHeight() + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + view.drawviewer = true + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view +end + +hook.Add( "CalcView", "!!!!LVS_calcview", function(ply, pos, angles, fov) + if ply:GetViewEntity() ~= ply then return end + + local pod = ply:GetVehicle() + local vehicle = ply:lvsGetVehicle() + + if not IsValid( pod ) or not IsValid( vehicle ) then return end + + local newfov = vehicle:LVSCalcFov( fov, ply ) + + local base = pod:lvsGetWeapon() + + if IsValid( base ) then + local weapon = base:GetActiveWeapon() + + if weapon and weapon.CalcView then + return ply:lvsSetView( weapon.CalcView( base, ply, pos, angles, newfov, pod ) ) + else + return ply:lvsSetView( vehicle:LVSCalcView( ply, pos, angles, newfov, pod ) ) + end + else + local weapon = vehicle:GetActiveWeapon() + + if weapon and weapon.CalcView then + return ply:lvsSetView( weapon.CalcView( vehicle, ply, pos, angles, newfov, pod ) ) + else + return ply:lvsSetView( vehicle:LVSCalcView( ply, pos, angles, newfov, pod ) ) + end + end +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_fonts.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_fonts.lua new file mode 100644 index 0000000..df11ee1 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_fonts.lua @@ -0,0 +1,44 @@ +local THE_FONT = { + font = "Verdana", + extended = false, + size = 14, + weight = 600, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = true, + additive = false, + outline = false, +} +surface.CreateFont( "LVS_VERSION", THE_FONT ) + +THE_FONT.extended = false +THE_FONT.size = 20 +THE_FONT.weight = 2000 +surface.CreateFont( "LVS_FONT", THE_FONT ) + +THE_FONT.size = 16 +surface.CreateFont( "LVS_FONT_SWITCHER", THE_FONT ) + +THE_FONT.font = "Arial" +THE_FONT.size = 14 +THE_FONT.weight = 1 +THE_FONT.shadow = false +surface.CreateFont( "LVS_FONT_PANEL", THE_FONT ) + +THE_FONT.size = 20 +THE_FONT.weight = 2000 +surface.CreateFont( "LVS_FONT_HUD", THE_FONT ) + +THE_FONT.size = 40 +THE_FONT.weight = 2000 +THE_FONT.shadow = true +surface.CreateFont( "LVS_FONT_HUD_LARGE", THE_FONT ) + +THE_FONT.size = 80 +surface.CreateFont( "LVS_FONT_HUD_HUMONGOUS", THE_FONT ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_hud.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_hud.lua new file mode 100644 index 0000000..53914a2 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_hud.lua @@ -0,0 +1,345 @@ + +--LVS.HudForceDefault = true + +LVS.HudEditors = LVS.HudEditors or {} +LVS.HudEditorsHide = {} + +local function ResetFrame( id ) + if not LVS.HudEditors[ id ] then return end + + LVS.HudEditors[ id ].w = LVS.HudEditors[ id ].DefaultWidth + LVS.HudEditors[ id ].h = LVS.HudEditors[ id ].DefaultHeight + LVS.HudEditors[ id ].X = LVS.HudEditors[ id ].DefaultX + LVS.HudEditors[ id ].Y = LVS.HudEditors[ id ].DefaultY + LVS.HudEditorsHide[ id ] = nil +end + +local function MakeFrame( id, X, Y, w, h, minw, minh, text ) + local Frame = vgui.Create("DFrame") + Frame:SetSize( w, h ) + Frame:SetPos( X, Y ) + Frame:SetTitle( text ) + Frame:SetScreenLock( true ) + Frame:MakePopup() + Frame:SetSizable( true ) + Frame:SetMinWidth( minw ) + Frame:SetMinHeight( minh ) + Frame.id = id + Frame.OnClose = function( self ) + ResetFrame( self.id ) + end + Frame.Paint = function(self, w, h ) + surface.SetDrawColor(0,0,0,150) + surface.DrawRect(0, 0, w, h) + + surface.SetDrawColor(80,80,80,255) + surface.DrawRect(0, 0, 2, h) + surface.DrawRect(w - 2, 0, 2, h) + surface.DrawRect(0, 0, w, 2) + surface.DrawRect(0, h - 2, w, 2) + + if not LVS.HudEditors[ self.id ] then return end + + local Width = self:GetWide() + local Height = self:GetTall() + + LVS.HudEditors[ self.id ].w = Width + LVS.HudEditors[ self.id ].h = Height + + LVS.HudEditors[ self.id ].X = math.min( self:GetX(), ScrW() - Width ) + LVS.HudEditors[ self.id ].Y = math.min( self:GetY(), ScrH() - Height ) + + if self:IsDragging() or input.IsMouseDown( MOUSE_LEFT ) then return end + + local Ratio = LVS.HudEditors[ self.id ].DefaultHeight / LVS.HudEditors[ self.id ].DefaultWidth + + if math.Round( Height / Width, 2 ) ~= math.Round( Ratio ,2 ) then + local NewHeight = Width * Ratio + + self:SetHeight( NewHeight ) + + LVS.HudEditors[ self.id ].h = NewHeight + end + end + + local DCheckbox = vgui.Create( "DCheckBoxLabel", Frame ) + DCheckbox:Dock( RIGHT ) + DCheckbox:DockMargin( 0, 0, 0, 0 ) + DCheckbox:SetText("Hide") + DCheckbox:SizeToContents() + DCheckbox.id = id + DCheckbox:SetChecked( LVS.HudEditorsHide[ id ] == true ) + DCheckbox.OnChange = function( self, bVal ) + if not self.id then return end + + if bVal then LVS.HudEditorsHide[ self.id ] = true return end + + LVS.HudEditorsHide[ self.id ] = nil + end + + LVS.HudEditors[ id ].Frame = Frame + + return Frame +end + +local ScreenWidth = ScrW() +local ScreenHeight = ScrH() + +local function SaveEditors() + if LVS.HudForceDefault then return end + + if ScreenWidth ~= ScrW() or ScreenHeight ~= ScrH() then return end -- player changed resolution while ingame... don't save because everything is fucked up now... + + local SaveString = "" + for id, data in pairs( LVS.HudEditors ) do + local w = data.w + local h = data.h + + local X = math.min( data.X / ScrW(), 1 ) + local Y = math.min( data.Y / ScrH(), 1 ) + + local hide = LVS.HudEditorsHide[ id ] and "?" or " " + + SaveString = SaveString..id.."~"..hide.."~"..w.."#"..h.."/"..X.."#"..Y.."\n" + end + + file.Write( "lvs_hud_settings.txt", SaveString ) +end + +local function LoadEditors() + if LVS.HudForceDefault then return end + + local LoadString = file.Read( "lvs_hud_settings.txt" ) + + if not LoadString then return end + + for _, garbage in pairs( string.Explode( "\n", LoadString ) ) do + local data1 = string.Explode( "~", garbage ) + + if not data1[3] then continue end + + local data2 = string.Explode( "/", data1[3] ) + + local size = string.Explode( "#", data2[1] ) + local pos = string.Explode( "#", data2[2] ) + + local ID = data1[1] + + if not LVS.HudEditors[ ID ] or not size[1] or not size[2] or not pos[1] or not pos[2] then continue end + + LVS.HudEditors[ ID ].w = math.max( LVS.HudEditors[ ID ].minw, size[1] ) + LVS.HudEditors[ ID ].h = math.max( LVS.HudEditors[ ID ].minh, size[2] ) + LVS.HudEditors[ ID ].X = math.min( pos[1] * ScrW(), ScrW() - size[1] ) + LVS.HudEditors[ ID ].Y = math.min( pos[2] * ScrH(), ScrH() - size[2] ) + + if data1[2] == "?" then + LVS.HudEditorsHide[ ID ] = true + end + end +end + +function LVS:AddHudEditor( id, X, Y, w, h, minw, minh, text, func ) + LVS.HudEditors[ id ] = { + DefaultX = X, + DefaultY = Y, + DefaultWidth = w, + DefaultHeight = h, + X = X, + Y = Y, + w = w, + h = h, + minw = minw, + minh = minh, + text = text, + func = func, + } +end + +hook.Add( "OnContextMenuOpen", "!!!!!LVS_hud", function() + if not IsValid( LocalPlayer():lvsGetVehicle() ) then return end + + if not GetConVar( "lvs_edit_hud" ):GetBool() then return end + + LVS:OpenEditors() + + return false +end ) + +hook.Add( "InitPostEntity", "!!!lvs_load_hud", function() + LoadEditors() +end ) + +function LVS:OpenEditors() + for id, editor in pairs( LVS.HudEditors ) do + if IsValid( editor.Frame ) then continue end + + MakeFrame( id, editor.X, editor.Y, editor.w, editor.h, editor.minw, editor.minh, editor.text ) + end + + local T = CurTime() + local ply = LocalPlayer() + local pod = ply:GetVehicle() + + ply.SwitcherTime = T + 9999 + + if not IsValid( pod ) then return end + + pod._SelectActiveTime = T + 9999 +end + +function LVS:CloseEditors() + SaveEditors() + + for id, editor in pairs( LVS.HudEditors ) do + if not IsValid( editor.Frame ) then continue end + editor.Frame:Remove() + end + + local T = CurTime() + local ply = LocalPlayer() + local pod = ply:GetVehicle() + + ply.SwitcherTime = T + + if not IsValid( pod ) then return end + + pod._SelectActiveTime = T +end + +hook.Add( "OnContextMenuClose", "!!!!!LVS_hud", function() + LVS:CloseEditors() +end ) + +function LVS:DrawDiamond( X, Y, radius, perc ) + if perc <= 0 then return end + + local segmentdist = 90 + + draw.NoTexture() + + for a = 90, 360, segmentdist do + local Xa = math.Round( math.sin( math.rad( -a ) ) * radius, 0 ) + local Ya = math.Round( math.cos( math.rad( -a ) ) * radius, 0 ) + + local C = math.sqrt( radius ^ 2 + radius ^ 2 ) + + if a == 90 then + C = C * math.min(math.max(perc - 0.75,0) / 0.25,1) + elseif a == 180 then + C = C * math.min(math.max(perc - 0.5,0) / 0.25,1) + elseif a == 270 then + C = C * math.min(math.max(perc - 0.25,0) / 0.25,1) + elseif a == 360 then + C = C * math.min(math.max(perc,0) / 0.25,1) + end + + if C > 0 then + local AxisMoveX = math.Round( math.sin( math.rad( -a + 135) ) * (C + 3) * 0.5, 0 ) + local AxisMoveY =math.Round( math.cos( math.rad( -a + 135) ) * (C + 3) * 0.5, 0 ) + + surface.DrawTexturedRectRotated(X - Xa - AxisMoveX, Y - Ya - AxisMoveY,3, math.ceil( C ), a - 45) + end + end +end + +local function PaintIdentifier( ent ) + if not LVS.ShowIdent or LVS:IsIndicatorForced() then return end + + local VehicleIdentifierRange = ent.VehicleIdentifierRange + local MyPos = ent:GetPos() + local MyTeam = ent:GetAITEAM() + + for _, v in pairs( LVS:GetVehicles() ) do + if not IsValid( v ) or v == ent then continue end + + local rPos = v:LocalToWorld( v:OBBCenter() ) + + local Pos = rPos:ToScreen() + local Dist = (MyPos - rPos):Length() + + if Dist > VehicleIdentifierRange or util.TraceLine( {start = ent:LocalToWorld( ent:OBBCenter() ),endpos = rPos,mask = MASK_NPCWORLDSTATIC,} ).Hit then continue end + + local Alpha = 255 * (1 - (Dist / VehicleIdentifierRange) ^ 2) + local Team = v:GetAITEAM() + local IndicatorColor = Color( 255, 0, 0, Alpha ) + + if Team == 0 then + if MyTeam == 0 then continue end + + IndicatorColor = Color( 0, 255, 0, Alpha ) + else + if Team == 1 or Team == 2 then + if Team ~= MyTeam and MyTeam ~= 0 then + IndicatorColor = Color( 255, 0, 0, Alpha ) + else + IndicatorColor = Color( 0, 127, 255, Alpha ) + end + end + end + + if Team > 3 then continue end + + v:LVSHudPaintVehicleIdentifier( Pos.x, Pos.y, IndicatorColor ) + end +end + +hook.Add( "HUDPaint", "!!!!!LVS_hud", function() + local ply = LocalPlayer() + + if ply:GetViewEntity() ~= ply then return end + + local Pod = ply:GetVehicle() + local Parent = ply:lvsGetVehicle() + + if not IsValid( Pod ) or not IsValid( Parent ) then + ply._lvsoldPassengers = {} + + return + end + + local X = ScrW() + local Y = ScrH() + + PaintIdentifier( Parent ) + Parent:LVSHudPaint( X, Y, ply ) + + local base = Pod:lvsGetWeapon() + if IsValid( base ) then + local weapon = base:GetActiveWeapon() + if weapon and weapon.HudPaint then + weapon.HudPaint( base, X, Y, ply ) + end + else + local weapon = Parent:GetActiveWeapon() + if ply == Parent:GetDriver() and weapon and weapon.HudPaint then + weapon.HudPaint( Parent, X, Y, ply ) + end + end + + for id, editor in pairs( LVS.HudEditors ) do + if LVS.HudEditorsHide[ id ] then continue end + + local ScaleX = editor.w / editor.DefaultWidth + local ScaleY = editor.h / editor.DefaultHeight + + local PosX = editor.X / ScaleX + local PosY = editor.Y / ScaleY + + local Width = editor.w / ScaleX + local Height = editor.h / ScaleY + + local ScrW = X / ScaleX + local ScrH = Y / ScaleY + + if ScaleX == 1 and ScaleY == 1 then + editor:func( Parent, PosX, PosY, Width, Height, ScrW, ScrH, ply ) + else + local m = Matrix() + m:Scale( Vector( ScaleX, ScaleY, 1 ) ) + + cam.PushModelMatrix( m ) + editor:func( Parent, PosX, PosY, Width, Height, ScrW, ScrH, ply ) + cam.PopModelMatrix() + end + end +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_menu.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_menu.lua new file mode 100644 index 0000000..14de454 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/cl_menu.lua @@ -0,0 +1,800 @@ + +local icon_load_version = Material("gui/html/refresh") + +local bgMat = Material( "lvs/controlpanel_bg.png" ) +local adminMat = Material( "icon16/shield.png" ) +local gradient_mat = Material( "gui/gradient" ) +local gradient_down = Material( "gui/gradient_down" ) + +local FrameSizeX = 600 +local FrameSizeY = 400 + +local function ClientSettings( Canvas ) + local TopPanel = vgui.Create( "DPanel", Canvas ) + TopPanel:SetSize( FrameSizeX, FrameSizeY * 0.35 ) + TopPanel.Paint = function( self, w, h ) + surface.SetDrawColor( 80, 80, 80, 255 ) + surface.SetMaterial( gradient_mat ) + surface.DrawTexturedRect( 1, 0, w, 1 ) + + draw.DrawText( "Mouse", "LVS_FONT", 4, 4, Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + end + TopPanel:Dock( BOTTOM ) + + local RightPanel = vgui.Create( "DPanel", Canvas ) + RightPanel:SetSize( FrameSizeX * 0.5, FrameSizeY ) + RightPanel.Paint = function( self, w, h ) + surface.SetDrawColor( 80, 80, 80, 255 ) + surface.SetMaterial( gradient_down ) + surface.DrawTexturedRect( 0, 0, 1, h ) + draw.DrawText( "Misc/Performance", "LVS_FONT", 4, 4, Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + end + RightPanel:Dock( RIGHT ) + + local RightPanelRight = vgui.Create( "DPanel", RightPanel ) + RightPanelRight:SetSize( FrameSizeX * 0.25, FrameSizeY ) + RightPanelRight.Paint = function() end + RightPanelRight:Dock( RIGHT ) + + local LeftPanel = vgui.Create( "DPanel", Canvas ) + LeftPanel:SetSize( FrameSizeX * 0.5, FrameSizeY ) + LeftPanel.Paint = function( self, w, h ) + draw.DrawText( "Preferences", "LVS_FONT", 4, 4, Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + end + LeftPanel:Dock( LEFT ) + + local CheckBoxPanel = vgui.Create( "DPanel", TopPanel ) + CheckBoxPanel:DockMargin( 0, 0, 0, 0 ) + CheckBoxPanel:SetSize( FrameSizeX, 55 ) + CheckBoxPanel.Paint = function() end + CheckBoxPanel:Dock( TOP ) + + if GetConVar( "lvs_mouseaim_type" ):GetInt() == 1 and not LVS:IsDirectInputForced() then + local CheckBoxType = vgui.Create( "DCheckBoxLabel", CheckBoxPanel ) + CheckBoxType:SetSize( FrameSizeX * 0.5, 55 ) + CheckBoxType:DockMargin( 16, 36, 0, 0 ) + CheckBoxType:Dock( LEFT ) + CheckBoxType:SetText( "Mouse-Aim for:" ) + CheckBoxType:SetConVar("lvs_mouseaim_type") + CheckBoxType.OnChange = function( self, bVal ) + if not isbool( self.first ) then self.first = true return end + timer.Simple(0.1, function() LVS:OpenMenu( true ) end ) + end + + local DScrollPanel = vgui.Create("DScrollPanel", CheckBoxPanel ) + DScrollPanel:SetSize( FrameSizeX * 0.25, 55 ) + DScrollPanel:DockMargin( 8, 0, 8, 0 ) + DScrollPanel:Dock( LEFT ) + + for _, vehicletype in pairs( LVS:GetVehicleTypes() ) do + local ScrollOption = vgui.Create( "DCheckBoxLabel", DScrollPanel ) + ScrollOption:SetText( vehicletype ) + ScrollOption:Dock( TOP ) + ScrollOption:DockMargin( 0, 0, 0, 5 ) + ScrollOption:SetConVar("lvs_mouseaim_type_"..vehicletype) + end + else + local CheckBox = vgui.Create( "DCheckBoxLabel", CheckBoxPanel ) + CheckBox:SetSize( FrameSizeX * 0.5, 55 ) + CheckBox:DockMargin( 16, 36, 0, 0 ) + CheckBox:Dock( LEFT ) + CheckBox:SetText( "Mouse-Aim Steering" ) + CheckBox:SetConVar("lvs_mouseaim") + CheckBox.OnChange = function( self, bVal ) + if not isbool( self.first ) then self.first = true return end + timer.Simple(0.1, function() LVS:OpenMenu( true ) end ) + end + if LVS:IsDirectInputForced() then + CheckBox:SetText( "[DISABLED] Use Mouse-Aim Steering" ) + CheckBox:SetDisabled( true ) + end + + if not LVS:IsDirectInputForced() then + local CheckBoxType = vgui.Create( "DCheckBoxLabel", CheckBoxPanel ) + CheckBoxType:SetSize( FrameSizeX * 0.5, 55 ) + CheckBoxType:DockMargin( 16, 36, 0, 0 ) + CheckBoxType:Dock( LEFT ) + CheckBoxType:SetText( "Edit Mouse-Aim per Type" ) + CheckBoxType:SetConVar("lvs_mouseaim_type") + CheckBoxType.OnChange = function( self, bVal ) + if not isbool( self.first ) then self.first = true return end + timer.Simple(0.1, function() LVS:OpenMenu( true ) end ) + end + end + end + + if GetConVar( "lvs_mouseaim" ):GetInt() == 0 or LVS:IsDirectInputForced() then + local L = vgui.Create( "DPanel", TopPanel ) + L:SetSize( FrameSizeX * 0.5, FrameSizeY ) + L.Paint = function() end + L:Dock( LEFT ) + + local R = vgui.Create( "DPanel", TopPanel ) + R:SetSize( FrameSizeX * 0.5, FrameSizeY ) + R.Paint = function() end + R:Dock( RIGHT ) + + local slider = vgui.Create( "DNumSlider", R ) + slider:DockMargin( 16, 4, 16, 4 ) + slider:Dock( TOP ) + slider:SetText( "Y Sensitivity" ) + slider:SetMin( -10 ) + slider:SetMax( 10 ) + slider:SetDecimals( 3 ) + slider:SetConVar( "lvs_sensitivity_y" ) + + local slider = vgui.Create( "DNumSlider", L ) + slider:DockMargin( 16, 4, 16, 4 ) + slider:Dock( TOP ) + slider:SetText( "X Sensitivity" ) + slider:SetMin( 0 ) + slider:SetMax( 10 ) + slider:SetDecimals( 3 ) + slider:SetConVar( "lvs_sensitivity_x" ) + + local slider = vgui.Create( "DNumSlider", L ) + slider:DockMargin( 16, 4, 16, 4 ) + slider:Dock( TOP ) + slider:SetText( "Return Delta" ) + slider:SetMin( 0 ) + slider:SetMax( 10 ) + slider:SetDecimals( 3 ) + slider:SetConVar( "lvs_return_delta" ) + else + local slider = vgui.Create( "DNumSlider", TopPanel ) + slider:DockMargin( 16, 4, 16, 4 ) + slider:Dock( TOP ) + slider:SetText( "Camera Focus" ) + slider:SetMin( -1 ) + slider:SetMax( 1 ) + slider:SetDecimals( 2 ) + slider:SetConVar( "lvs_camerafocus" ) + end + + local slider = vgui.Create( "DNumSlider", LeftPanel ) + slider:DockMargin( 16, 36, 16, 4 ) + slider:Dock( TOP ) + slider:SetText( "Engine Volume" ) + slider:SetMin( 0 ) + slider:SetMax( 1 ) + slider:SetDecimals( 2 ) + slider:SetConVar( "lvs_volume" ) + + local DLabel = vgui.Create("DLabel", LeftPanel ) + DLabel:SetPos( 16, 72 ) + DLabel:SetSize( 60, 20 ) + DLabel:SetText( "Speed Unit" ) + + local DComboBox = vgui.Create( "DComboBox", LeftPanel ) + DComboBox:SetPos( 130, 72 ) + DComboBox:SetSize( 160, 20 ) + DComboBox:SetValue( LVS.SpeedUnit ) + for unit, _ in pairs( LVS.SPEEDUNITS ) do + DComboBox:AddChoice( unit ) + end + DComboBox.OnSelect = function( self, index, value ) + RunConsoleCommand("lvs_speedunit", value) + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanel ) + CheckBox:DockMargin( 16, 43, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Show Team Identifier" ) + CheckBox:SetConVar("lvs_show_identifier") + if LVS:IsIndicatorForced() then + CheckBox:SetText( "[DISABLED] Team Identifier" ) + CheckBox:SetDisabled( true ) + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanel ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Show Hit/Kill Marker" ) + CheckBox:SetConVar("lvs_hitmarker") + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanel ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Enable HUD Editor" ) + CheckBox:SetConVar("lvs_edit_hud") + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanel ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Show Door Info" ) + CheckBox:SetConVar("lvs_show_doorinfo") + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanelRight ) + CheckBox:DockMargin( 16, 43, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Trail Effects" ) + CheckBox:SetConVar("lvs_show_traileffects") + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanelRight ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Wind/Dust FX/SFX" ) + CheckBox:SetConVar("lvs_show_effects") + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanelRight ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Scrape/Impact FX" ) + CheckBox:SetConVar("lvs_show_physicseffects") + + local CheckBox = vgui.Create( "DCheckBoxLabel", RightPanelRight ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Bullet near miss SFX" ) + CheckBox:SetConVar("lvs_bullet_nearmiss") +end + +local function ClientControls( Canvas ) + local TextHint = vgui.Create("DPanel", Canvas) + TextHint:DockMargin( 4, 20, 4, 2 ) + TextHint:SetText("") + TextHint:Dock( TOP ) + TextHint.Paint = function(self, w, h ) + draw.DrawText( "You need to re-enter the vehicle in order for the changes to take effect!", "LVS_FONT_PANEL", w * 0.5, -1, Color( 255, 50, 50, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local DScrollPanel = vgui.Create("DScrollPanel", Canvas) + DScrollPanel:DockMargin( 0, 0, 0, 24 ) + DScrollPanel:Dock( FILL ) + + for category, _ in pairs( LVS.KEYS_CATEGORIES ) do + local Header = vgui.Create("DPanel", DScrollPanel ) + Header:DockMargin( 0, 4, 4, 2 ) + Header:SetText("") + Header:Dock( TOP ) + Header.Paint = function(self, w, h ) + surface.SetMaterial( gradient_mat ) + surface.SetDrawColor( 80, 80, 80, 255 ) + surface.DrawTexturedRect( 0, 0, w, 1 ) + + draw.DrawText( category, "LVS_FONT", 4, 0, Color( 255, 255, 255, 255 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER ) + end + + for _, entry in pairs( LVS.KEYS_REGISTERED ) do + if entry.category ~= category then continue end + + local DPanel = vgui.Create( "DPanel", DScrollPanel ) + DPanel.Paint = function(self, w, h ) end + DPanel:DockMargin( 4, 2, 4, 2 ) + DPanel:SetSize( FrameSizeX, 25 ) + DPanel:Dock( TOP ) + + local ConVar = GetConVar( entry.cmd ) + + local DLabel = vgui.Create("DLabel", DPanel) + DLabel:DockMargin( 16, 0, 0, 0 ) + DLabel:SetText( entry.printname ) + DLabel:SetSize( FrameSizeX * 0.5, 32 ) + DLabel:Dock( LEFT ) + + local DBinder = vgui.Create("DBinder", DPanel) + DBinder:DockMargin( 0, 0, 0, 0 ) + DBinder:SetValue( ConVar:GetInt() ) + DBinder:SetSize( FrameSizeX * 0.5, 32 ) + DBinder:Dock( RIGHT ) + DBinder.ConVar = ConVar + DBinder.OnChange = function(self,iNum) + self.ConVar:SetInt(iNum) + + LocalPlayer():lvsBuildControls() + end + end + end + + local Header = vgui.Create("DPanel", DScrollPanel ) + Header:DockMargin( 0, 16, 0, 0 ) + Header:SetText("") + Header:Dock( TOP ) + Header.Paint = function(self, w, h ) + surface.SetMaterial( gradient_mat ) + surface.SetDrawColor( 80, 80, 80, 255 ) + surface.DrawTexturedRect( 0, 0, w, 1 ) + end + + local DButton = vgui.Create("DButton",DScrollPanel) + DButton:SetText("Reset") + DButton:DockMargin( 4, 0, 4, 4 ) + DButton:SetSize( FrameSizeX, 32 ) + DButton:Dock( TOP ) + DButton.DoClick = function() + surface.PlaySound( "buttons/button14.wav" ) + + for _, entry in pairs( LVS.KEYS_REGISTERED ) do + GetConVar( entry.cmd ):SetInt( entry.default ) + end + + LocalPlayer():lvsBuildControls() + + LVS:OpenClientControls() + end +end + +local function ServerSettings( Canvas ) + local slider = vgui.Create( "DNumSlider", Canvas ) + slider:DockMargin( 16, 32, 16, 4 ) + slider:Dock( TOP ) + slider:SetText( "Player Default AI-Team" ) + slider:SetMin( 0 ) + slider:SetMax( 3 ) + slider:SetDecimals( 0 ) + slider:SetConVar( "lvs_default_teams" ) + function slider:OnValueChanged( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_default_teams") + net.WriteString( tostring( math.Round(val,0) ) ) + net.SendToServer() + end + + local slider = vgui.Create( "DNumSlider", Canvas ) + slider:DockMargin( 16, 8, 16, 4 ) + slider:Dock( TOP ) + slider:SetText("Fuel tank size multiplier") + slider:SetMin( 0 ) + slider:SetMax( 10 ) + slider:SetDecimals( 2 ) + slider:SetConVar( "lvs_fuelscale" ) + function slider:OnValueChanged( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_fuelscale") + net.WriteString( tostring( val ) ) + net.SendToServer() + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", Canvas ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Freeze Player AI-Team" ) + CheckBox:SetValue( GetConVar( "lvs_freeze_teams" ):GetInt() ) + CheckBox:SizeToContents() + function CheckBox:OnChange( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_freeze_teams") + net.WriteString( tostring( val and 1 or 0 ) ) + net.SendToServer() + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", Canvas ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Only allow Players of matching AI-Team to enter Vehicles" ) + CheckBox:SetValue( GetConVar( "lvs_teampassenger" ):GetInt() ) + CheckBox:SizeToContents() + function CheckBox:OnChange( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_teampassenger") + net.WriteString( tostring( val and 1 or 0 ) ) + net.SendToServer() + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", Canvas ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "LVS-AI ignore NPC's" ) + CheckBox:SetValue( GetConVar( "lvs_ai_ignorenpcs" ):GetInt() ) + CheckBox:SizeToContents() + function CheckBox:OnChange( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_ai_ignorenpcs") + net.WriteString( tostring( val and 1 or 0 ) ) + net.SendToServer() + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", Canvas ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "LVS-AI ignore Players's" ) + CheckBox:SetValue( GetConVar( "lvs_ai_ignoreplayers" ):GetInt() ) + CheckBox:SizeToContents() + function CheckBox:OnChange( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_ai_ignoreplayers") + net.WriteString( tostring( val and 1 or 0 ) ) + net.SendToServer() + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", Canvas ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Disable Mouse Aim" ) + CheckBox:SetValue( GetConVar( "lvs_force_directinput" ):GetInt() ) + CheckBox:SizeToContents() + function CheckBox:OnChange( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_force_directinput") + net.WriteString( tostring( val and 1 or 0 ) ) + net.SendToServer() + end + + local CheckBox = vgui.Create( "DCheckBoxLabel", Canvas ) + CheckBox:DockMargin( 16, 16, 4, 4 ) + CheckBox:SetSize( FrameSizeX, 30 ) + CheckBox:Dock( TOP ) + CheckBox:SetText( "Hide Team Identifier" ) + CheckBox:SetValue( GetConVar( "lvs_force_forceindicator" ):GetInt() ) + CheckBox:SizeToContents() + function CheckBox:OnChange( val ) + net.Start("lvs_admin_setconvar") + net.WriteString("lvs_force_forceindicator") + net.WriteString( tostring( val and 1 or 0 ) ) + net.SendToServer() + end +end + +function LVS:OpenClientSettings() + if not IsValid( LVS.Frame ) then return end + + local BasePanel = LVS.Frame:CreatePanel() + + local DPanel = vgui.Create( "DPanel", BasePanel ) + DPanel.Paint = function(self, w, h ) end + DPanel:DockMargin( 0, 0, 0, 0 ) + DPanel:SetSize( FrameSizeX, 25 ) + DPanel:Dock( TOP ) + + local TabPanel = vgui.Create( "DPanel", BasePanel ) + TabPanel.Paint = function(self, w, h ) end + TabPanel:DockMargin( 0, 0, 0, 0 ) + TabPanel:SetSize( FrameSizeX, 25 ) + TabPanel:Dock( TOP ) + + local SettingsPanel = vgui.Create( "DPanel", TabPanel ) + SettingsPanel:DockMargin( 0, 0, 0, 0 ) + SettingsPanel:SetSize( FrameSizeX * 0.5, 32 ) + SettingsPanel:Dock( LEFT ) + SettingsPanel.Paint = function(self, w, h ) + draw.DrawText( "SETTINGS", "LVS_FONT", w * 0.5, 0, Color( 255, 255, 255, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local DButton = vgui.Create( "DButton", TabPanel ) + DButton:DockMargin( 0, 0, 0, 0 ) + DButton:SetText( "" ) + DButton:SetSize( FrameSizeX * 0.5, 32 ) + DButton:Dock( RIGHT ) + DButton.DoClick = function() + surface.PlaySound( "buttons/button14.wav" ) + LVS:OpenClientControls() + end + DButton.Paint = function(self, w, h ) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + + local Hovered = self:IsHovered() + + if Hovered then + surface.SetDrawColor( 120, 120, 120, 255 ) + else + surface.SetDrawColor( 80, 80, 80, 255 ) + end + + surface.DrawRect(1, 0, w-2, h-1) + + local Col = Hovered and Color( 255, 255, 255, 255 ) or Color( 150, 150, 150, 255 ) + draw.DrawText( "CONTROLS", "LVS_FONT", w * 0.5, 0, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local DButton = vgui.Create( "DButton", DPanel ) + DButton:DockMargin( 0, 0, 0, 0 ) + DButton:SetText( "" ) + DButton:SetSize( FrameSizeX * 0.5, 32 ) + DButton:Dock( RIGHT ) + DButton.DoClick = function() + if LocalPlayer():IsSuperAdmin() then + surface.PlaySound( "buttons/button14.wav" ) + LVS:OpenServerMenu() + else + surface.PlaySound( "buttons/button11.wav" ) + end + end + DButton.Paint = function(self, w, h ) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + + local Hovered = self:IsHovered() + + if Hovered then + draw.RoundedBox( 4, 1, 1, w-2, h-2, Color( 120, 120, 120, 255 ) ) + else + draw.RoundedBox( 4, 1, 1, w-2, h-2, Color( 80, 80, 80, 255 ) ) + end + + surface.SetDrawColor( 255, 255, 255, Hovered and 255 or 50 ) + surface.SetMaterial( adminMat ) + surface.DrawTexturedRect( 3, 2, 16, 16 ) + + local Col = Hovered and Color( 255, 255, 255, 255 ) or Color( 150, 150, 150, 255 ) + draw.DrawText( "SERVER", "LVS_FONT", w * 0.5, 0, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local ClientPanel = vgui.Create( "DPanel", DPanel ) + ClientPanel.Paint = function(self, w, h ) end + ClientPanel:DockMargin( 0, 0, 0, 0 ) + ClientPanel:SetSize( FrameSizeX * 0.5, 32 ) + ClientPanel:Dock( LEFT ) + ClientPanel.Paint = function(self, w, h ) + draw.DrawText( "CLIENT", "LVS_FONT", w * 0.5, 0, Color( 255, 255, 255, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local Canvas = vgui.Create( "DPanel", BasePanel ) + Canvas.Paint = function(self, w, h ) end + Canvas:DockMargin( 0, 0, 0, 0 ) + Canvas:SetSize( FrameSizeX, 25 ) + Canvas:Dock( FILL ) + + ClientSettings( Canvas ) +end + +function LVS:OpenClientControls() + if not IsValid( LVS.Frame ) then return end + + local BasePanel = LVS.Frame:CreatePanel() + + local DPanel = vgui.Create( "DPanel", BasePanel ) + DPanel.Paint = function(self, w, h ) end + DPanel:DockMargin( 0, 0, 0, 0 ) + DPanel:SetSize( FrameSizeX, 25 ) + DPanel:Dock( TOP ) + + local TabPanel = vgui.Create( "DPanel", BasePanel ) + TabPanel.Paint = function(self, w, h ) end + TabPanel:DockMargin( 0, 0, 0, 0 ) + TabPanel:SetSize( FrameSizeX, 25 ) + TabPanel:Dock( TOP ) + + local SettingsPanel = vgui.Create( "DPanel", TabPanel ) + SettingsPanel:DockMargin( 0, 0, 0, 0 ) + SettingsPanel:SetSize( FrameSizeX * 0.5, 32 ) + SettingsPanel:Dock( RIGHT ) + SettingsPanel.Paint = function(self, w, h ) + draw.DrawText( "CONTROLS", "LVS_FONT", w * 0.5, 0, Color( 255, 255, 255, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local DButton = vgui.Create( "DButton", TabPanel ) + DButton:DockMargin( 0, 0, 0, 0 ) + DButton:SetText( "" ) + DButton:SetSize( FrameSizeX * 0.5, 32 ) + DButton:Dock( LEFT ) + DButton.DoClick = function() + surface.PlaySound( "buttons/button14.wav" ) + LVS:OpenClientSettings() + end + DButton.Paint = function(self, w, h ) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + + local Hovered = self:IsHovered() + + if Hovered then + surface.SetDrawColor( 120, 120, 120, 255 ) + else + surface.SetDrawColor( 80, 80, 80, 255 ) + end + + surface.DrawRect(1, 1, w-2, h-2) + + local Col = Hovered and Color( 255, 255, 255, 255 ) or Color( 150, 150, 150, 255 ) + draw.DrawText( "SETTINGS", "LVS_FONT", w * 0.5, 0, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local DButton = vgui.Create( "DButton", DPanel ) + DButton:DockMargin( 0, 0, 0, 0 ) + DButton:SetText( "" ) + DButton:SetSize( FrameSizeX * 0.5, 32 ) + DButton:Dock( RIGHT ) + DButton.DoClick = function() + if LocalPlayer():IsSuperAdmin() then + surface.PlaySound( "buttons/button14.wav" ) + LVS:OpenServerMenu() + else + surface.PlaySound( "buttons/button11.wav" ) + end + end + DButton.Paint = function(self, w, h ) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + + local Hovered = self:IsHovered() + + if Hovered then + draw.RoundedBox( 4, 1, 1, w-2, h-2, Color( 120, 120, 120, 255 ) ) + else + draw.RoundedBox( 4, 1, 1, w-2, h-2, Color( 80, 80, 80, 255 ) ) + end + + surface.SetDrawColor( 255, 255, 255, Hovered and 255 or 50 ) + surface.SetMaterial( adminMat ) + surface.DrawTexturedRect( 3, 2, 16, 16 ) + + local Col = Hovered and Color( 255, 255, 255, 255 ) or Color( 150, 150, 150, 255 ) + draw.DrawText( "SERVER", "LVS_FONT", w * 0.5, 0, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local ClientPanel = vgui.Create( "DPanel", DPanel ) + ClientPanel.Paint = function(self, w, h ) end + ClientPanel:DockMargin( 0, 0, 0, 0 ) + ClientPanel:SetSize( FrameSizeX * 0.5, 32 ) + ClientPanel:Dock( LEFT ) + ClientPanel.Paint = function(self, w, h ) + draw.DrawText( "CLIENT", "LVS_FONT", w * 0.5, 0, Color( 255, 255, 255, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local Canvas = vgui.Create( "DPanel", BasePanel ) + Canvas.Paint = function(self, w, h ) end + Canvas:DockMargin( 0, 0, 0, 0 ) + Canvas:SetSize( FrameSizeX, 25 ) + Canvas:Dock( FILL ) + + ClientControls( Canvas ) +end + +function LVS:OpenServerMenu() + if not IsValid( LVS.Frame ) then return end + + local BasePanel = LVS.Frame:CreatePanel() + + local DPanel = vgui.Create( "DPanel", BasePanel ) + DPanel.Paint = function(self, w, h ) end + DPanel:DockMargin( 0, 0, 0, 0 ) + DPanel:SetSize( FrameSizeX, 25 ) + DPanel:Dock( TOP ) + + local ServerPanel = vgui.Create( "DPanel", DPanel ) + ServerPanel.Paint = function(self, w, h ) end + ServerPanel:DockMargin( 0, 0, 0, 0 ) + ServerPanel:SetSize( FrameSizeX * 0.5, 32 ) + ServerPanel:Dock( RIGHT ) + ServerPanel.Paint = function(self, w, h ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( adminMat ) + surface.DrawTexturedRect( 3, 2, 16, 16 ) + draw.DrawText( "SERVER", "LVS_FONT", w * 0.5, 0, Color( 255, 255, 255, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local DButton = vgui.Create( "DButton", DPanel ) + DButton:DockMargin( 0, 0, 0, 0 ) + DButton:SetText( "" ) + DButton:SetSize( FrameSizeX * 0.5, 32 ) + DButton:Dock( LEFT ) + DButton.DoClick = function() + surface.PlaySound( "buttons/button14.wav" ) + LVS:OpenClientSettings() + end + DButton.Paint = function(self, w, h ) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + + local Hovered = self:IsHovered() + + if Hovered then + draw.RoundedBox( 4, 1, 1, w-2, h-2, Color( 120, 120, 120, 255 ) ) + else + draw.RoundedBox( 4, 1, 1, w-2, h-2, Color( 80, 80, 80, 255 ) ) + end + + local Col = Hovered and Color( 255, 255, 255, 255 ) or Color( 150, 150, 150, 255 ) + draw.DrawText( "CLIENT", "LVS_FONT", w * 0.5, 0, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local Canvas = vgui.Create( "DPanel", BasePanel ) + Canvas.Paint = function(self, w, h ) end + Canvas:DockMargin( 0, 0, 0, 0 ) + Canvas:SetSize( FrameSizeX, 25 ) + Canvas:Dock( FILL ) + + ServerSettings( Canvas ) +end + +function LVS:CloseMenu() + if not IsValid( LVS.Frame ) then return end + + LVS.Frame:Close() + LVS.Frame = nil +end + +function LVS:OpenMenu( keep_position ) + local xPos + local yPos + + if IsValid( LVS.Frame ) then + if keep_position then + xPos = LVS.Frame:GetX() + yPos = LVS.Frame:GetY() + end + + LVS:CloseMenu() + end + + LVS.Frame = vgui.Create( "DFrame" ) + LVS.Frame:SetSize( FrameSizeX, FrameSizeY ) + LVS.Frame:SetTitle( "" ) + LVS.Frame:SetDraggable( true ) + LVS.Frame:SetScreenLock( true ) + LVS.Frame:MakePopup() + LVS.Frame:Center() + if keep_position and xPos and yPos then + LVS.Frame:SetPos( xPos, yPos ) + end + + LVS.Frame.Paint = function(self, w, h ) + draw.RoundedBox( 8, 0, 0, w, h, Color( 0, 0, 0, 255 ) ) + draw.RoundedBoxEx( 8, 1, 26, w-2, h-27, Color( 120, 120, 120, 255 ), false, false, true, true ) + draw.RoundedBoxEx( 8, 0, 0, w, 25, LVS.ThemeColor, true, true ) + + draw.SimpleText( "[LVS] - Control Panel ", "LVS_FONT", 5, 11, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER ) + + surface.SetDrawColor( 255, 255, 255, 50 ) + surface.SetMaterial( bgMat ) + surface.DrawTexturedRect( 0, -50, w, w ) + end + LVS.Frame.CreatePanel = function( self ) + + if IsValid( self.OldPanel ) then + self.OldPanel:Remove() + self.OldPanel = nil + end + + local DPanel = vgui.Create( "DPanel", LVS.Frame ) + DPanel:SetPos( 0, 25 ) + DPanel:SetSize( FrameSizeX, FrameSizeY - 25 ) + DPanel.Paint = function(self, w, h ) + local Col = Color( 255, 191, 0, 255 ) + + if LVS.VERSION_GITHUB == 0 then + surface.SetMaterial( icon_load_version ) + surface.SetDrawColor( Col ) + surface.DrawTexturedRectRotated( w - 14, h - 14, 16, 16, -CurTime() * 200 ) + + draw.SimpleText( "v"..LVS:GetVersion()..LVS.VERSION_TYPE, "LVS_VERSION", w - 23, h - 14, Col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER ) + + return + end + + local Current = LVS:GetVersion() + local Latest = LVS.VERSION_GITHUB + + local Pref = "v" + + if Current >= Latest and not LVS.VERSION_ADDONS_OUTDATED then + Col = Color(0,255,0,255) + else + Col = Color(255,0,0,255) + Pref = "OUTDATED v" + end + + draw.SimpleText( Pref..LVS:GetVersion()..LVS.VERSION_TYPE, "LVS_VERSION", w - 7, h - 14, Col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER ) + end + + self.OldPanel = DPanel + + return DPanel + end + + LVS:OpenClientSettings() +end + +list.Set( "DesktopWindows", "LVSMenu", { + title = "[LVS] Settings", + icon = "icon64/iconlvs.png", + init = function( icon, window ) + LVS:OpenMenu() + end +} ) + +concommand.Add( "lvs_openmenu", function( ply, cmd, args ) LVS:OpenMenu() end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_bulletsystem.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_bulletsystem.lua new file mode 100644 index 0000000..bd724d7 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_bulletsystem.lua @@ -0,0 +1,578 @@ +local LVS = LVS + +LVS._ActiveBullets = {} + +function LVS:RemoveBullet( index ) + LVS._ActiveBullets[ index ] = nil +end + +function LVS:GetBullet( index ) + if not LVS._ActiveBullets then return end + + return LVS._ActiveBullets[ index ] +end + +local NewBullet = {} +NewBullet.__index = NewBullet + +function NewBullet:SetPos( pos ) + self.curpos = pos +end + +function NewBullet:GetBulletIndex() + return self.bulletindex +end + +function NewBullet:Remove() + local index = self.bulletindex + + if SERVER then + -- prevents ghost bullets if the client fails to detect the hit + net.Start( "lvs_remove_bullet", true ) + net.WriteInt( index, 13 ) + net.SendPVS( self:GetPos() ) + end + + LVS:RemoveBullet( index ) +end + +function NewBullet:GetPos() + if not self.curpos then return self.Src end + + return self.curpos +end + +function NewBullet:SetGravity( new ) + self.Gravity = new +end + +function NewBullet:GetGravity() + return self.Gravity or vector_origin +end + +function NewBullet:GetDir() + return self.Dir or vector_origin +end + +function NewBullet:SetDir( newdir ) + self.Dir = newdir +end + +function NewBullet:GetTimeAlive() + return CurTime() - self.StartTime +end + +function NewBullet:GetSpawnTime() + if SERVER then + return self.StartTime + else + return math.min( self.StartTimeCL, CurTime() ) -- time when the bullet is received on client + end +end + +function NewBullet:GetLength() + return math.min((CurTime() - self:GetSpawnTime()) * 14,1) +end + +function NewBullet:HandleWaterImpact( traceStart, traceEnd, Filter ) + if self.HasHitWater then return end + + local traceWater = util.TraceLine( { + start = traceStart, + endpos = traceEnd, + filter = Filter, + mask = MASK_WATER, + } ) + + if not traceWater.Hit then return end + + self.HasHitWater = true + + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.HitPos ) + effectdata:SetScale( 10 + self.HullSize * 0.5 ) + effectdata:SetFlags( 2 ) + util.Effect( "WaterSplash", effectdata, true, true ) +end + +function NewBullet:HandleFlybySound( EarPos ) + if self.Muted or not LVS.EnableBulletNearmiss then return end + + local BulletPos = self:GetPos() + + local EarDist = (EarPos - BulletPos):LengthSqr() + + if self.OldEarDist and self.OldEarDist < EarDist then + + if EarDist < 250000 then + local effectdata = EffectData() + effectdata:SetOrigin( EarPos + (BulletPos - EarPos):GetNormalized() * 20 ) + effectdata:SetFlags( 2 ) + util.Effect( "TracerSound", effectdata ) + end + + self.Muted = true + end + + self.OldEarDist = EarDist +end + +function NewBullet:DoBulletFlight( TimeAlive ) + + local StartPos = self.Src + local StartDirection = self.StartDir + + local Velocity = self.Velocity + + local PosOffset + + -- startpos, direction and curtime of creation is networked to client. + -- the bullet position is simulated by doing startpos + dir * time * velocity + if self.EnableBallistics then + local PosTheoretical = StartDirection * TimeAlive * Velocity + + PosOffset = PosTheoretical + self:GetGravity() * (TimeAlive ^ 2) + + self:SetDir( (StartPos + PosOffset - StartPos):GetNormalized() ) + else + PosOffset = self.Dir * TimeAlive * Velocity + end + + if SERVER then + self:SetPos( StartPos + PosOffset ) + else + + -- "parent" the bullet to the vehicle for a very short time on client. This will give the illusion of the bullet not lagging behind even tho it is fired later on client + if IsValid( self.Entity ) and self.SrcEntity then + local mul = self:GetLength() + local inv = 1 - mul + + self:SetPos( StartPos * mul + self.Entity:LocalToWorld( self.SrcEntity ) * inv + PosOffset ) + + return + end + + -- if no parent detected, run same code as server + self:SetPos( StartPos + PosOffset ) + end +end + +function NewBullet:OnCollide( trace ) + if CLIENT then return end + + if trace.Entity == self.LastDamageTarget then return end + + local Attacker = (IsValid( self.Attacker ) and self.Attacker) or (IsValid( self.Entity ) and self.Entity) or game.GetWorld() + local Inflictor = (IsValid( self.Entity ) and self.Entity) or (IsValid( self.Attacker ) and self.Attacker) or game.GetWorld() + + local dmginfo = DamageInfo() + dmginfo:SetDamage( self.Damage ) + dmginfo:SetAttacker( Attacker ) + dmginfo:SetInflictor( Inflictor ) + dmginfo:SetDamageType( DMG_AIRBOAT ) + dmginfo:SetDamagePosition( trace.HitPos ) + + if self.Force1km then + local Mul = math.min( (self.Src - trace.HitPos):Length() / 39370, 1 ) + local invMul = math.max( 1 - Mul, 0 ) + + dmginfo:SetDamageForce( self.Dir * (self.Force * invMul + self.Force1km * Mul) ) + else + dmginfo:SetDamageForce( self.Dir * self.Force ) + end + + if self.Callback then + self.Callback( Attacker, trace, dmginfo ) + end + + if trace.Entity:GetClass() == "func_breakable_surf" then + -- this will cause the entire thing to just fall apart + dmginfo:SetDamageType( DMG_BLAST ) + end + + trace.Entity:DispatchTraceAttack( dmginfo, trace ) + + self.LastDamageTarget = trace.Entity +end + +function NewBullet:DoSplashDamage( trace ) + if not self.SplashDamage or not self.SplashDamageRadius then return false end + + if self.SplashDamageEffect ~= "" then + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetNormal( trace.HitWorld and trace.HitNormal or self.Dir ) + effectdata:SetMagnitude( self.SplashDamageRadius / 250 ) + util.Effect( self.SplashDamageEffect, effectdata ) + end + + local Attacker = (IsValid( self.Attacker ) and self.Attacker) or (IsValid( self.Entity ) and self.Entity) or game.GetWorld() + local Inflictor = (IsValid( self.Entity ) and self.Entity) or (IsValid( self.Attacker ) and self.Attacker) or game.GetWorld() + + LVS:BlastDamage( trace.HitPos, self.Dir, Attacker, Inflictor, self.SplashDamage, self.SplashDamageType, self.SplashDamageRadius, self.SplashDamageForce ) + + self.SplashDamage = nil + self.SplashDamageRadius = nil + self.SplashDamageEffect = nil + + return true +end + +function NewBullet:HandleCollision( traceStart, traceEnd, Filter ) + local TraceMask = self.HullSize <= 1 and MASK_SHOT_PORTAL or MASK_SHOT_HULL + + local traceLine + local traceHull + + if self.HullTraceResult then + traceHull = self.HullTraceResult + else + traceLine = util.TraceLine( { + start = traceStart, + endpos = traceEnd, + filter = Filter, + mask = TraceMask + } ) + + local trace = util.TraceHull( { + start = traceStart, + endpos = traceEnd, + filter = Filter, + mins = self.Mins, + maxs = self.Maxs, + mask = TraceMask, + ignoreworld = true + } ) + + if traceLine.Entity == trace.Entity and trace.Hit and traceLine.Hit then + trace = traceLine + end + + if trace.Hit then + self.HullTraceResult = trace + traceHull = trace + + self:OnCollide( trace ) + + if self:DoSplashDamage( trace ) then + self:Remove() + end + else + traceHull = { Hit = false } + end + end + + if not traceLine then + traceLine = util.TraceLine( { + start = traceStart, + endpos = traceEnd, + filter = Filter, + mask = TraceMask + } ) + end + + if not traceLine.Hit then + return + end + + self:OnCollide( traceLine ) + + self:DoSplashDamage( traceLine ) + + self:Remove() + + if SERVER then return end + + if not traceLine.HitSky then + local effectdata = EffectData() + effectdata:SetOrigin( traceLine.HitPos ) + effectdata:SetEntity( traceLine.Entity ) + effectdata:SetStart( traceStart ) + effectdata:SetNormal( traceLine.HitNormal ) + effectdata:SetSurfaceProp( traceLine.SurfaceProps ) + util.Effect( "Impact", effectdata ) + end +end + +local function GetEarPos() + if SERVER then return vector_origin end + + local EarPos + + local ply = LocalPlayer() + local ViewEnt = ply:GetViewEntity() + + if ViewEnt == ply then + if IsValid( ply:lvsGetVehicle() ) then + EarPos = ply:lvsGetView() + else + EarPos = ply:GetShootPos() + end + else + EarPos = ViewEnt:GetPos() + end + + return EarPos +end + +local function HandleBullets() + local T = CurTime() + local FT = FrameTime() + + local EarPos = GetEarPos() + + for id, bullet in pairs( LVS._ActiveBullets ) do + if bullet:GetSpawnTime() + 5 < T then -- destroy all bullets older than 5 seconds + bullet:Remove() + + continue + end + + local TimeAlive = bullet:GetTimeAlive() + + if TimeAlive < 0 then continue end -- CurTime() is predicted, this can be a negative number in some cases. + + local Filter = bullet.Filter + + local traceStart = bullet:GetPos() + bullet:DoBulletFlight( TimeAlive ) + local traceEnd = bullet:GetPos() + + if CLIENT then + --debugoverlay.Line( traceStart, traceEnd, Color( 255, 255, 255 ), true ) + + -- bullet flyby sounds + bullet:HandleFlybySound( EarPos ) + + -- bullet water impact effects + bullet:HandleWaterImpact( traceStart, traceEnd, Filter ) + end + + bullet:HandleCollision( traceStart, traceEnd, Filter ) + end +end + +local vector_one = Vector(1,1,1) + +local TracerIndex = 0 +local TracerToIndex = {} +local IndexToTracer = {} +if CLIENT then + hook.Add( "InitPostEntity", "lvs_bullet_sync", function() + net.Start( "lvs_bullet_tracer_lookup" ) + net.SendToServer() + + hook.Remove( "InitPostEntity", "lvs_bullet_sync" ) + end ) + + net.Receive( "lvs_bullet_tracer_lookup", function( length ) + local index = net.ReadInt( 32 ) + local name = net.ReadString() + + TracerToIndex[ name ] = index + IndexToTracer[ index ] = name + end) +else + function LVS:AddTracer( name ) + if TracerToIndex[ name ] then return TracerToIndex[ name ] end + + TracerIndex = TracerIndex + 1 + + net.Start( "lvs_bullet_tracer_lookup" ) + net.WriteInt( TracerIndex, 32 ) + net.WriteString( name ) + net.Broadcast() + + TracerToIndex[ name ] = TracerIndex + IndexToTracer[ TracerIndex ] = name + + return TracerIndex + end + + util.AddNetworkString( "lvs_bullet_tracer_lookup" ) + + net.Receive( "lvs_bullet_tracer_lookup", function( length, ply ) + if ply._lvsAlreadyAskedForTracers then return end + + ply._lvsAlreadyAskedForTracers = true + + if TracerIndex == 0 then return end + + for index, name in ipairs( IndexToTracer ) do + timer.Simple( index / 60, function() + if not IsValid( ply ) then return end + + net.Start( "lvs_bullet_tracer_lookup" ) + net.WriteInt( index, 32 ) + net.WriteString( name ) + net.Send( ply ) + end ) + end + end) +end + +if SERVER then + util.AddNetworkString( "lvs_fire_bullet" ) + util.AddNetworkString( "lvs_remove_bullet" ) + + hook.Add( "Tick", "!!!!lvs_bullet_handler", function( ply, ent ) -- from what i understand, think can "skip" on lag, while tick still simulates all steps + HandleBullets() + end ) + + local Index = 0 + local MaxIndex = 4094 -- this is the util.effect limit + + function LVS:FireBullet( data ) + + Index = Index + 1 + + if Index > MaxIndex then + Index = 1 + end + + LVS._ActiveBullets[ Index ] = nil + + local bullet = {} + + setmetatable( bullet, NewBullet ) + + bullet.TracerName = LVS:AddTracer( data.TracerName or "lvs_tracer_orange" ) + + bullet.Src = data.Src or vector_origin + bullet.Dir = (data.Dir + VectorRand() * (data.Spread or vector_origin) * 0.5):GetNormalized() + bullet.StartDir = bullet.Dir + bullet.Force = data.Force or 10 + + if data.Force1km then + bullet.Force1km = data.Force1km + end + + bullet.HullSize = data.HullSize or 5 + bullet.Mins = -vector_one * bullet.HullSize + bullet.Maxs = vector_one * bullet.HullSize + bullet.Velocity = math.Round( data.Velocity or 2500, 0 ) + bullet.Attacker = IsValid( data.Attacker ) and data.Attacker or (IsValid( data.Entity ) and data.Entity or game.GetWorld()) + bullet.Damage = data.Damage or 10 + bullet.Entity = data.Entity + if IsValid( bullet.Entity ) and bullet.Entity.GetCrosshairFilterEnts then + bullet.Filter = bullet.Entity:GetCrosshairFilterEnts() + else + bullet.Filter = bullet.Entity + end + bullet.SrcEntity = data.SrcEntity or vector_origin + bullet.Callback = data.Callback + bullet.SplashDamage = data.SplashDamage + bullet.SplashDamageForce = data.SplashDamageForce or 500 + bullet.SplashDamageRadius = data.SplashDamageRadius + bullet.SplashDamageEffect = data.SplashDamageEffect or "lvs_bullet_impact" + bullet.SplashDamageType = data.SplashDamageType or DMG_SONIC + bullet.StartTime = CurTime() + bullet.EnableBallistics = data.EnableBallistics == true + + if bullet.EnableBallistics then + bullet:SetGravity( physenv.GetGravity() ) + end + + if InfMap then + for _, ply in ipairs( player.GetAll() ) do + local NewPos = Vector( bullet.Src.x, bullet.Src.y, bullet.Src.z ) - InfMap.unlocalize_vector( Vector(), ply.CHUNK_OFFSET ) + + net.Start( "lvs_fire_bullet", true ) + net.WriteInt( Index, 13 ) + net.WriteInt( bullet.TracerName, 9 ) + net.WriteFloat( NewPos.x ) + net.WriteFloat( NewPos.y ) + net.WriteFloat( NewPos.z ) + net.WriteAngle( bullet.Dir:Angle() ) + net.WriteFloat( bullet.StartTime ) + net.WriteFloat( bullet.HullSize ) + net.WriteEntity( bullet.Entity ) + net.WriteVector( bullet.SrcEntity ) + net.WriteInt( bullet.Velocity, 19 ) + net.WriteBool( bullet.EnableBallistics ) + net.Send( ply ) + end + else + net.Start( "lvs_fire_bullet", true ) + net.WriteInt( Index, 13 ) + net.WriteInt( bullet.TracerName, 9 ) + net.WriteFloat( bullet.Src.x ) + net.WriteFloat( bullet.Src.y ) + net.WriteFloat( bullet.Src.z ) + net.WriteAngle( bullet.Dir:Angle() ) + net.WriteFloat( bullet.StartTime ) + net.WriteFloat( bullet.HullSize ) + net.WriteEntity( bullet.Entity ) + net.WriteVector( bullet.SrcEntity ) + net.WriteInt( bullet.Velocity, 19 ) + net.WriteBool( bullet.EnableBallistics ) + net.SendPVS( bullet.Src ) + end + + bullet.bulletindex = Index + LVS._ActiveBullets[ Index ] = bullet + end +else + net.Receive( "lvs_remove_bullet", function( length ) + LVS:RemoveBullet( net.ReadInt( 13 ) ) + end) + + net.Receive( "lvs_fire_bullet", function( length ) + local Index = net.ReadInt( 13 ) + + LVS._ActiveBullets[ Index ] = nil + + local bullet = {} + + setmetatable( bullet, NewBullet ) + + bullet.TracerName = IndexToTracer[ net.ReadInt( 9 ) ] or "lvs_tracer_orange" + bullet.Src = Vector(net.ReadFloat(),net.ReadFloat(),net.ReadFloat()) + bullet.Dir = net.ReadAngle():Forward() + bullet.StartDir = bullet.Dir + bullet.StartTime = net.ReadFloat() + bullet.HullSize = net.ReadFloat() + bullet.Mins = -vector_one * bullet.HullSize + bullet.Maxs = vector_one * bullet.HullSize + bullet.Entity = net.ReadEntity() + if IsValid( bullet.Entity ) and bullet.Entity.GetCrosshairFilterEnts then + bullet.Filter = bullet.Entity:GetCrosshairFilterEnts() + else + bullet.Filter = bullet.Entity + end + bullet.SrcEntity = net.ReadVector() + + if bullet.SrcEntity == vector_origin then + bullet.SrcEntity = nil + end + + bullet.Velocity = net.ReadInt( 19 ) + + bullet.EnableBallistics = net.ReadBool() + + if bullet.EnableBallistics then + bullet:SetGravity( physenv.GetGravity() ) + end + + bullet.StartTimeCL = CurTime() + RealFrameTime() + + local ply = LocalPlayer() + + if IsValid( ply ) then + bullet.Muted = bullet.Entity == ply:lvsGetVehicle() or bullet.Entity:GetOwner() == ply + end + + bullet.bulletindex = Index + LVS._ActiveBullets[ Index ] = bullet + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetMaterialIndex( Index ) + util.Effect( bullet.TracerName, effectdata ) + end ) + + hook.Add( "Think", "!!!!_lvs_bullet_think_cl", function() + HandleBullets() + end ) +end diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_convar.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_convar.lua new file mode 100644 index 0000000..42c737f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_convar.lua @@ -0,0 +1,134 @@ +-- 2022 and i still havent bothered creating a system that does this automatically + +LVS.cVar_FreezeTeams = CreateConVar( "lvs_freeze_teams", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"enable/disable auto ai-team switching" ) +LVS.FreezeTeams = LVS.cVar_FreezeTeams and LVS.cVar_FreezeTeams:GetBool() or false +cvars.AddChangeCallback( "lvs_freeze_teams", function( convar, oldValue, newValue ) + LVS.FreezeTeams = tonumber( newValue ) ~=0 +end, "lvs_freezeteams_callback" ) + +LVS.cVar_TeamPassenger = CreateConVar( "lvs_teampassenger", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"only allow players of matching ai-team to enter the vehicle? 1 = team only, 0 = everyone can enter" ) +LVS.TeamPassenger = LVS.cVar_TeamPassenger and LVS.cVar_TeamPassenger:GetBool() or false +cvars.AddChangeCallback( "lvs_teampassenger", function( convar, oldValue, newValue ) + LVS.TeamPassenger = tonumber( newValue ) ~= 0 +end, "lvs_teampassenger_callback" ) + +LVS.cVar_PlayerDefaultTeam = CreateConVar( "lvs_default_teams", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"set default player ai-team" ) +LVS.PlayerDefaultTeam = LVS.cVar_PlayerDefaultTeam and LVS.cVar_PlayerDefaultTeam:GetInt() or 0 +cvars.AddChangeCallback( "lvs_default_teams", function( convar, oldValue, newValue ) + LVS.PlayerDefaultTeam = math.Round( tonumber( newValue ), 0 ) +end, "lvs_defaultteam_callback" ) + +LVS.cVar_IgnoreNPCs = CreateConVar( "lvs_ai_ignorenpcs", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"should LVS-AI ignore NPCs?" ) +LVS.IgnoreNPCs = LVS.cVar_IgnoreNPCs and LVS.cVar_IgnoreNPCs:GetBool() or false +cvars.AddChangeCallback( "lvs_ai_ignoreplayers", function( convar, oldValue, newValue ) + LVS.IgnorePlayers = tonumber( newValue ) ~=0 +end) + +LVS.cVar_playerignore = CreateConVar( "lvs_ai_ignoreplayers", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"should LVS-AI ignore Players?" ) +LVS.IgnorePlayers = LVS.cVar_playerignore and LVS.cVar_playerignore:GetBool() or false +cvars.AddChangeCallback( "lvs_ai_ignorenpcs", function( convar, oldValue, newValue ) + LVS.IgnoreNPCs = tonumber( newValue ) ~=0 +end) + +LVS.cVar_FuelScale = CreateConVar( "lvs_fuelscale", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"Fuel tank size multiplier" ) +LVS.FuelScale = LVS.cVar_FuelScale and LVS.cVar_FuelScale:GetFloat() or 1 +cvars.AddChangeCallback( "lvs_fuelscale", function( convar, oldValue, newValue ) + LVS.FuelScale = tonumber( newValue ) +end ) + +if SERVER then + util.AddNetworkString( "lvs_admin_setconvar" ) + + net.Receive( "lvs_admin_setconvar", function( length, ply ) + if not IsValid( ply ) or not ply:IsSuperAdmin() then return end + + local ConVar = net.ReadString() + local Value = tonumber( net.ReadString() ) + + RunConsoleCommand( ConVar, Value ) + end) + + return +end + +CreateClientConVar( "lvs_mouseaim", 0, true, true) +CreateClientConVar( "lvs_mouseaim_type", 1, true, false) +CreateClientConVar( "lvs_edit_hud", 1, true, false) +CreateClientConVar( "lvs_sensitivity_x", 1, true, true) +CreateClientConVar( "lvs_sensitivity_y", 1, true, true) +CreateClientConVar( "lvs_return_delta", 2, true, true) + +LVS.cvarCamFocus = CreateClientConVar( "lvs_camerafocus", 0, true, false) + +local cvarSpeedUnit = CreateClientConVar( "lvs_speedunit", "km/h", true, false) +LVS.SpeedUnit = cvarSpeedUnit and cvarSpeedUnit:GetString() or "km/h" +cvars.AddChangeCallback( "lvs_speedunit", function( convar, oldValue, newValue ) + LVS.SpeedUnit = newValue +end) + +local cvarDoorInfo = CreateClientConVar( "lvs_show_doorinfo", 1, true, false) +LVS.ShowDoorInfo = cvarDoorInfo and cvarDoorInfo:GetBool() or false +cvars.AddChangeCallback( "lvs_show_doorinfo", function( convar, oldValue, newValue ) + LVS.ShowDoorInfo = tonumber( newValue ) ~=0 +end) + +local cvarVolume = CreateClientConVar( "lvs_volume", 0.5, true, false) +LVS.EngineVolume = cvarVolume and cvarVolume:GetFloat() or 0.5 +cvars.AddChangeCallback( "lvs_volume", function( convar, oldValue, newValue ) + LVS.EngineVolume = math.Clamp( tonumber( newValue ), 0, 1 ) +end) + +local cvarTrail = CreateClientConVar( "lvs_show_traileffects", 1, true, false) +LVS.ShowTraileffects = cvarTrail and cvarTrail:GetBool() or false +cvars.AddChangeCallback( "lvs_show_traileffects", function( convar, oldValue, newValue ) + LVS.ShowTraileffects = tonumber( newValue ) ~=0 +end) + +local cvarEffects = CreateClientConVar( "lvs_show_effects", 1, true, false) +LVS.ShowEffects = cvarEffects and cvarEffects:GetBool() or false +cvars.AddChangeCallback( "lvs_show_effects", function( convar, oldValue, newValue ) + LVS.ShowEffects = tonumber( newValue ) ~=0 +end) + +local cvarPhysEffects = CreateClientConVar( "lvs_show_physicseffects", 1, true, false) +LVS.ShowPhysicsEffects = cvarPhysEffects and cvarPhysEffects:GetBool() or false +cvars.AddChangeCallback( "lvs_show_physicseffects", function( convar, oldValue, newValue ) + LVS.ShowPhysicsEffects = tonumber( newValue ) ~=0 +end) + +local cvarShowIdent = CreateClientConVar( "lvs_show_identifier", 1, true, false) +LVS.ShowIdent = cvarShowIdent and cvarShowIdent:GetBool() or false +cvars.AddChangeCallback( "lvs_show_identifier", function( convar, oldValue, newValue ) + LVS.ShowIdent = tonumber( newValue ) ~=0 +end) + +local cvarHitMarker = CreateClientConVar( "lvs_hitmarker", 1, true, false) +LVS.ShowHitMarker = cvarHitMarker and cvarHitMarker:GetBool() or false +cvars.AddChangeCallback( "lvs_hitmarker", function( convar, oldValue, newValue ) + LVS.ShowHitMarker = tonumber( newValue ) ~=0 +end) + +local cvarAntiAlias = GetConVar( "mat_antialias" ) +LVS.AntiAliasingEnabled = cvarAntiAlias and (cvarAntiAlias:GetInt() > 3) or false +cvars.AddChangeCallback( "mat_antialias", function( convar, oldValue, newValue ) + LVS.AntiAliasingEnabled = tonumber( newValue ) > 3 +end) + +local cvarBulletSFX = CreateClientConVar( "lvs_bullet_nearmiss", 1, true, false) +LVS.EnableBulletNearmiss = cvarBulletSFX and cvarBulletSFX:GetBool() or false +cvars.AddChangeCallback( "lvs_bullet_nearmiss", function( convar, oldValue, newValue ) + LVS.EnableBulletNearmiss = tonumber( newValue ) ~=0 +end) + +local cvarDev = GetConVar( "developer" ) +LVS.DeveloperEnabled = cvarDev and (cvarDev:GetInt() >= 1) or false +cvars.AddChangeCallback( "developer", function( convar, oldValue, newValue ) + LVS.DeveloperEnabled = (tonumber( newValue ) or 0) >= 1 +end) + +cvars.AddChangeCallback( "lvs_mouseaim", function( convar, oldValue, newValue ) + LocalPlayer():lvsBuildControls() + + net.Start("lvs_toggle_mouseaim") + net.SendToServer() +end) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_damagenotify.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_damagenotify.lua new file mode 100644 index 0000000..2cb8233 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_damagenotify.lua @@ -0,0 +1,77 @@ + +if CLIENT then + net.Receive( "lvs_hurtmarker", function( len ) + if not LVS.ShowHitMarker then return end + + local ply = LocalPlayer() + + local vehicle = ply:lvsGetVehicle() + + if not IsValid( vehicle ) then return end + + vehicle:HurtMarker( net.ReadFloat() ) + end ) + + net.Receive( "lvs_hitmarker", function( len ) + if not LVS.ShowHitMarker then return end + + local ply = LocalPlayer() + + local vehicle = ply:lvsGetVehicle() + + local IsCrit = net.ReadBool() + + if not IsValid( vehicle ) then + hook.Run( "LVS:OnHudIndicator", ply, IsCrit and "crit" or "hit" ) + + return + end + + if IsCrit then + vehicle:CritMarker() + else + vehicle:HitMarker() + end + end ) + + net.Receive( "lvs_killmarker", function( len ) + if not LVS.ShowHitMarker then return end + + local ply = LocalPlayer() + + local vehicle = ply:lvsGetVehicle() + + if not IsValid( vehicle ) then + hook.Run( "LVS:OnHudIndicator", ply, "kill" ) + + return + end + + vehicle:KillMarker() + end ) + + net.Receive( "lvs_armormarker", function( len ) + if not LVS.ShowHitMarker then return end + + local ply = LocalPlayer() + + local vehicle = ply:lvsGetVehicle() + + local IsDamage = net.ReadBool() + + if not IsValid( vehicle ) then + hook.Run( "LVS:OnHudIndicator", ply, IsDamage and "armorcrit" or "armor" ) + + return + end + + vehicle:ArmorMarker( IsDamage ) + end ) + + return +end + +util.AddNetworkString( "lvs_hitmarker" ) +util.AddNetworkString( "lvs_hurtmarker" ) +util.AddNetworkString( "lvs_killmarker" ) +util.AddNetworkString( "lvs_armormarker" ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_entitytracker.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_entitytracker.lua new file mode 100644 index 0000000..524a9ee --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_entitytracker.lua @@ -0,0 +1,108 @@ +LVS.VehiclesStored = LVS.VehiclesStored or {} +LVS.NPCsStored = LVS.NPCsStored or {} + +function LVS:GetNPCs() + for index, ent in pairs( LVS.NPCsStored ) do + if not IsValid( ent ) then + LVS.NPCsStored[ index ] = nil + end + end + + return LVS.NPCsStored +end + +function LVS:GetVehicles() + for index, ent in pairs( LVS.VehiclesStored ) do + if not IsValid( ent ) then + LVS.VehiclesStored[ index ] = nil + end + end + + return LVS.VehiclesStored +end + +local Teams = { + ["npc_breen"] = 1, + ["npc_combine_s"] = 1, + ["npc_combinedropship"] = 1, + ["npc_combinegunship"] = 1, + ["npc_crabsynth"] = 1, + ["npc_cscanner"] = 1, + ["npc_helicopter"] = 1, + ["npc_manhack"] = 1, + ["npc_metropolice"] = 1, + ["npc_mortarsynth"] = 1, + ["npc_sniper"] = 1, + ["npc_stalker"] = 1, + ["npc_strider"] = 1, + ["npc_hunter"] = 1, + + ["monster_human_grunt"] = 1, + ["monster_human_assassin"] = 1, + ["monster_sentry"] = 1, + + ["npc_kleiner"] = 2, + ["npc_monk"] = 2, + ["npc_mossman"] = 2, + ["npc_vortigaunt"] = 2, + ["npc_alyx"] = 2, + ["npc_barney"] = 2, + ["npc_citizen"] = 2, + ["npc_dog"] = 2, + ["npc_eli"] = 2, + ["monster_scientist"] = 2, + ["monster_barney"] = 2, + + ["npc_zombine"] = 3, + ["npc_fastzombie"] = 3, + ["npc_headcrab"] = 3, + ["npc_headcrab_black"] = 3, + ["npc_headcrab_fast"] = 3, + ["npc_antlion"] = 3, + ["npc_antlionguard"] = 3, + ["npc_zombie"] = 3, + ["npc_zombie_torso"] = 3, + ["npc_poisonzombie"] = 3, + ["monster_alien_grunt"] = 3, + ["monster_alien_slave"] = 3, + ["monster_gargantua"] = 3, + ["monster_bullchicken"] = 3, + ["monster_headcrab"] = 3, + ["monster_babycrab"] = 3, + ["monster_zombie"] = 3, + ["monster_houndeye"] = 3, + ["monster_nihilanth"] = 3, + ["monster_bigmomma"] = 3, + ["monster_babycrab"] = 3, +} +function LVS:GetNPCRelationship( npc_class ) + return Teams[ npc_class ] or 0 +end + +hook.Add( "OnEntityCreated", "!!!!lvsEntitySorter", function( ent ) + timer.Simple( 2, function() + if not IsValid( ent ) then return end + + if isfunction( ent.IsNPC ) and ent:IsNPC() then + table.insert( LVS.NPCsStored, ent ) + + if SERVER then + hook.Run( "LVS.UpdateRelationship", ent ) + end + end + + if ent.LVS then + if CLIENT and ent.PrintName then + language.Add( ent:GetClass(), ent.PrintName) + end + + table.insert( LVS.VehiclesStored, ent ) + + if SERVER then + LVS:FixVelocity() + + hook.Run( "LVS.UpdateRelationship", ent ) + end + end + end ) +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_force_directinput.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_force_directinput.lua new file mode 100644 index 0000000..908d01e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_force_directinput.lua @@ -0,0 +1,56 @@ + +local cVar_forcedirect = CreateConVar( "lvs_force_directinput", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"Force Direct Input Steering Method?" ) +local cVar_forceindicator = CreateConVar( "lvs_force_forceindicator", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"Force Direct Input Steering Method?" ) + +function LVS:IsDirectInputForced() + return LVS.ForceDirectInput == true +end + +function LVS:IsIndicatorForced() + return LVS.ForceIndicator == true +end + +if SERVER then + util.AddNetworkString( "lvs_forced_input_getter" ) + + local function UpdateForcedSettings( ply ) + net.Start( "lvs_forced_input_getter" ) + + net.WriteBool( LVS:IsDirectInputForced() ) + net.WriteBool( LVS:IsIndicatorForced() ) + + if IsValid( ply ) then + net.Send( ply ) + else + net.Broadcast() + end + end + + LVS.ForceDirectInput = cVar_forcedirect and cVar_forcedirect:GetBool() or false + cvars.AddChangeCallback( "lvs_force_directinput", function( convar, oldValue, newValue ) + LVS.ForceDirectInput = tonumber( newValue ) ~=0 + + UpdateForcedSettings() + end) + + LVS.ForceIndicator = cVar_forceindicator and cVar_forceindicator:GetBool() or false + cvars.AddChangeCallback( "lvs_force_forceindicator", function( convar, oldValue, newValue ) + LVS.ForceIndicator = tonumber( newValue ) ~=0 + + UpdateForcedSettings() + end) + + net.Receive( "lvs_forced_input_getter", function( length, ply ) + UpdateForcedSettings( ply ) + end) +else + net.Receive( "lvs_forced_input_getter", function( length ) + LVS.ForceDirectInput = net.ReadBool() + LVS.ForceIndicator = net.ReadBool() + end ) + + hook.Add( "InitPostEntity", "!11!!!lvsIsPlayerReady", function() + net.Start( "lvs_forced_input_getter" ) + net.SendToServer() + end ) +end diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_hookers.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_hookers.lua new file mode 100644 index 0000000..d2fa9f6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_hookers.lua @@ -0,0 +1,303 @@ +hook.Add( "PhysgunPickup", "!!!!lvs_physgun_pickup", function( ply, ent ) + if ent._lvsNoPhysgunInteraction then return false end +end ) + +hook.Add( "InitPostEntity", "!!!lvsBullshitFixer", function() + timer.Simple(1, function() + LVS.MapDoneLoading = true + end) + + if SERVER then return end + + local Defaults = { + ["lvs_mouseaim_type_tank"] = 0, + ["lvs_mouseaim_type_car"] = 0, + ["lvs_mouseaim_type_repulsorlift"] = 1, + ["lvs_mouseaim_type_helicopter"] = 1, + ["lvs_mouseaim_type_plane"] = 1, + ["lvs_mouseaim_type_walker"] = 0, + ["lvs_mouseaim_type_starfighter"] = 1, + ["lvs_mouseaim_type_fakehover"] = 0, + } + + -- this needs to be here to make sure all sents are registered + for _, vehicletype in ipairs( LVS:GetVehicleTypes() ) do + local name = "lvs_mouseaim_type_"..vehicletype + local default = Defaults[ name ] or 0 + + CreateClientConVar( name, default, true, false) + end +end ) + +local function SetDistance( vehicle, ply ) + local iWheel = ply:GetCurrentCommand():GetMouseWheel() + + if iWheel == 0 or not vehicle.SetCameraDistance then return end + + local newdist = math.Clamp( vehicle:GetCameraDistance() - iWheel * 0.03 * ( 1.1 + vehicle:GetCameraDistance() ), -1, 10 ) + + vehicle:SetCameraDistance( newdist ) +end + +local function SetHeight( vehicle, ply ) + local iWheel = ply:GetCurrentCommand():GetMouseWheel() + + if iWheel == 0 or not vehicle.SetCameraHeight then return end + + local newdist = math.Clamp( vehicle:GetCameraHeight() - iWheel * 0.03 * ( 1.1 + vehicle:GetCameraHeight() ), -1, 10 ) + + vehicle:SetCameraHeight( newdist ) +end + +hook.Add( "VehicleMove", "!!!!lvs_vehiclemove", function( ply, vehicle, mv ) + if not ply.lvsGetVehicle then return end + + local veh = ply:lvsGetVehicle() + + if not IsValid( veh ) then return end + + if SERVER and ply:lvsKeyDown( "VIEWDIST" ) then + if ply:lvsKeyDown( "VIEWHEIGHT" ) then + SetHeight( vehicle, ply ) + else + SetDistance( vehicle, ply ) + end + end + + if CLIENT and not IsFirstTimePredicted() then return end + + local KeyThirdPerson = ply:lvsKeyDown("THIRDPERSON") + + if ply._lvsOldThirdPerson ~= KeyThirdPerson then + ply._lvsOldThirdPerson = KeyThirdPerson + + if KeyThirdPerson and vehicle.SetThirdPersonMode then + vehicle:SetThirdPersonMode( not vehicle:GetThirdPersonMode() ) + end + end + + return true +end ) + +hook.Add("CalcMainActivity", "!!!lvs_playeranimations", function(ply) + if not ply.lvsGetVehicle then return end + + local Ent = ply:lvsGetVehicle() + + if IsValid( Ent ) then + local A,B = Ent:CalcMainActivity( ply ) + + if A and B then + return A, B + end + end +end) + +hook.Add("UpdateAnimation", "!!!lvs_playeranimations", function( ply, velocity, maxseqgroundspeed ) + if not ply.lvsGetVehicle then return end + + local Ent = ply:lvsGetVehicle() + + if not IsValid( Ent ) then return end + + return Ent:UpdateAnimation( ply, velocity, maxseqgroundspeed ) +end) + +hook.Add( "StartCommand", "!!!!LVS_grab_command", function( ply, cmd ) + if not ply.lvsGetVehicle then return end + + local veh = ply:lvsGetVehicle() + + if not IsValid( veh ) then return end + + veh:StartCommand( ply, cmd ) +end ) + +hook.Add( "CanProperty", "!!!!lvsEditPropertiesDisabler", function( ply, property, ent ) + if ent.LVS and not ply:IsAdmin() and property == "editentity" then return false end +end ) + +LVS.ToolsDisable = { + ["rb655_easy_animation"] = true, + ["rb655_easy_bonemerge"] = true, + ["rb655_easy_inspector"] = true, +} +hook.Add( "CanTool", "!!!!lvsCanToolDisabler", function( ply, tr, toolname, tool, button ) + if LVS.ToolsDisable[ toolname ] and IsValid( tr.Entity ) and tr.Entity.LVS then return false end +end ) + +if CLIENT then + local hide = { + ["CHudHealth"] = true, + ["CHudBattery"] = true, + ["CHudAmmo"] = true, + ["CHudSecondaryAmmo"] = true, + } + local function HUDShouldDrawLVS( name ) + if hide[ name ] then return false end + end + + hook.Add( "LVS.PlayerEnteredVehicle", "!!!!lvs_player_enter", function( ply, veh ) + hook.Add( "HUDShouldDraw", "!!!!lvs_hidehud", HUDShouldDrawLVS ) + + if not IsValid( veh ) then return end + + local cvar = GetConVar( "lvs_mouseaim_type" ) + + if not cvar or cvar:GetInt() ~= 1 or not veh.GetVehicleType then return end + + local vehicletype = veh:GetVehicleType() + + local cvar_type = GetConVar( "lvs_mouseaim_type_"..vehicletype ) + local cvar_mouseaim = GetConVar( "lvs_mouseaim" ) + + if not cvar_type or not cvar_mouseaim then return end + + cvar_mouseaim:SetInt( cvar_type:GetInt() ) + end ) + + hook.Add( "LVS.PlayerLeaveVehicle", "!!!!lvs_player_exit", function( ply, veh ) + hook.Remove( "HUDShouldDraw", "!!!!lvs_hidehud" ) + end ) + + hook.Add( "InitPostEntity", "!!!lvs_infmap_velocity_fixer", function() + if not InfMap then + + hook.Remove( "InitPostEntity", "!!!lvs_infmap_velocity_fixer" ) + + return + end + + local meta = FindMetaTable( "Entity" ) + + if not InfMapOriginalGetVelocity then + InfMapOriginalGetVelocity = meta.GetVelocity + end + + function meta:GetVelocity() + local Velocity = InfMapOriginalGetVelocity( self ) + + local EntTable = self:GetTable() + + if not EntTable.LVS and not EntTable._lvsRepairToolLabel then return Velocity end + + local Speed = Velocity:LengthSqr() + + local T = CurTime() + + if Speed > 10 then + EntTable._infmapEntityVelocity = Velocity + EntTable._infmapEntityVelocityTime = T + 0.6 + else + if (EntTable._infmapEntityVelocityTime or 0) > T then + return EntTable._infmapEntityVelocity or vector_origin + end + end + + return Velocity + end + end ) + + return +end + +local DamageFix = { + ["npc_hunter"] = true, + ["npc_stalker"] = true, + ["npc_strider"] = true, + ["npc_combinegunship"] = true, + ["npc_helicopter"] = true, +} + +hook.Add( "EntityTakeDamage", "!!!_lvs_fix_vehicle_explosion_damage", function( target, dmginfo ) + if not target:IsPlayer() then + if target.LVS then + local attacker = dmginfo:GetAttacker() + + if IsValid( attacker ) and DamageFix[ attacker:GetClass() ] then + dmginfo:SetDamageType( DMG_AIRBOAT ) + dmginfo:SetDamageForce( dmginfo:GetDamageForce():GetNormalized() * 15000 ) + end + end + + return + end + + local veh = target:lvsGetVehicle() + + if not IsValid( veh ) or dmginfo:IsDamageType( DMG_DIRECT ) then return end + + if target:GetCollisionGroup() == COLLISION_GROUP_PLAYER then return end + + dmginfo:SetDamage( 0 ) +end ) + +hook.Add( "PlayerEnteredVehicle", "!!!!lvs_player_enter", function( ply, Pod ) + local veh = ply:lvsGetVehicle() + + if IsValid( veh ) then + net.Start( "lvs_player_enterexit" ) + net.WriteBool( true ) + net.WriteEntity( veh ) + net.Send( ply ) + + ply._lvsIsInVehicle = true + + if istable( veh.PlayerBoneManipulate ) then + local ID = Pod:lvsGetPodIndex() + local BoneManipulate = veh.PlayerBoneManipulate[ ID ] + + if BoneManipulate then + ply._lvsStopBoneManipOnExit = true + ply:lvsStartBoneManip() + end + end + + if LVS.FreezeTeams then + local nTeam = ply:lvsGetAITeam() + + if veh:GetAITEAM() ~= nTeam then + veh:SetAITEAM( nTeam ) + + ply:PrintMessage( HUD_PRINTTALK, "[LVS] This Vehicle's AI-Team has been updated to: "..(LVS.TEAMS[ nTeam ] or "") ) + end + end + end + + if not Pod.HidePlayer then return end + + ply:SetNoDraw( true ) + + if pac then pac.TogglePartDrawing( ply, 0 ) end +end ) + +hook.Add( "PlayerLeaveVehicle", "!!!!lvs_player_exit", function( ply, Pod ) + if ply._lvsIsInVehicle then + net.Start( "lvs_player_enterexit" ) + net.WriteBool( false ) + net.WriteEntity( Pod:lvsGetVehicle() ) + net.Send( ply ) + + ply._lvsIsInVehicle = nil + + if ply._lvsStopBoneManipOnExit then + ply._lvsStopBoneManipOnExit = nil + + ply:lvsStopBoneManip() + end + end + + if not Pod.HidePlayer then return end + + ply:SetNoDraw( false ) + + if pac then pac.TogglePartDrawing( ply, 1 ) end +end ) + +hook.Add( "PlayerDisconnected", "!!!!lvs_player_reset_bonemanip_client", function(ply) + if not ply._lvsStopBoneManipOnExit then return end + + ply._lvsStopBoneManipOnExit = nil + + ply:lvsStopBoneManip() +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_keybinding.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_keybinding.lua new file mode 100644 index 0000000..f39a21e --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_keybinding.lua @@ -0,0 +1,508 @@ + +hook.Add( "LVS:Initialize", "!!11lvs_default_keys", function() + local KEYS = { + { + name = "ATTACK", + category = "Armament", + name_menu = "Attack", + default = MOUSE_LEFT, + cmd = "lvs_lmb" + }, + { + name = "ZOOM", + category = "Armament", + name_menu = "Zoom", + default = MOUSE_RIGHT, + cmd = "lvs_rmb" + }, + { + name = "~SELECT~WEAPON#1", + category = "Armament", + name_menu = "Select Weapon 1", + cmd = "lvs_select_weapon1" + }, + { + name = "~SELECT~WEAPON#2", + category = "Armament", + name_menu = "Select Weapon 2", + cmd = "lvs_select_weapon2" + }, + { + name = "~SELECT~WEAPON#3", + category = "Armament", + name_menu = "Select Weapon 3", + cmd = "lvs_select_weapon3" + }, + { + name = "~SELECT~WEAPON#4", + category = "Armament", + name_menu = "Select Weapon 4", + cmd = "lvs_select_weapon4" + }, + --[[ only adding 4 because i dont want to bloat the menu. There can be added as many keys as neededed the system should figure it out by itself + { + name = "~SELECT~WEAPON#5", + category = "Armament", + name_menu = "Select Weapon 5", + cmd = "lvs_select_weapon5" + }, + ]] + { + name = "EXIT", + category = "Misc", + name_menu = "Exit Vehicle", + default = "+use", + cmd = "lvs_exit" + }, + { + name = "VIEWDIST", + category = "Misc", + name_menu = "Enable Mouse-Wheel Set-Camera-Distance", + default = MOUSE_MIDDLE, + cmd = "lvs_viewzoom" + }, + { + name = "VIEWHEIGHT", + category = "Misc", + name_menu = "Set-Camera-Distance => Set-Camera-Height", + default = "phys_swap", + cmd = "lvs_viewheight" + }, + { + name = "THIRDPERSON", + category = "Misc", + name_menu = "Toggle Thirdperson", + default = "+duck", + cmd = "lvs_thirdperson" + }, + { + name = "FREELOOK", + category = "Misc", + name_menu = "Freelook (Hold)", + default = "+walk", + cmd = "lvs_freelook" + }, + { + name = "ENGINE", + category = "Misc", + name_menu = "Toggle Engine", + default = "+reload", + cmd = "lvs_startengine" + }, + { + name = "VSPEC", + category = "Misc", + name_menu = "Toggle Vehicle-specific Function", + default = "+jump", + cmd = "lvs_special" + }, + } + + for _, v in pairs( KEYS ) do + LVS:AddKey( v.name, v.category, v.name_menu, v.cmd, v.default ) + end +end ) + +hook.Add( "LVS:Initialize", "[LVS] - Cars - Keys", function() + local KEYS = { + { + name = "CAR_THROTTLE", + category = "LVS-Car", + name_menu = "Throttle", + default = "+forward", + cmd = "lvs_car_throttle" + }, + { + name = "CAR_THROTTLE_MOD", + category = "LVS-Car", + name_menu = "Throttle Modifier", + default = "+speed", + cmd = "lvs_car_speed" + }, + { + name = "CAR_BRAKE", + category = "LVS-Car", + name_menu = "Brake", + default = "+back", + cmd = "lvs_car_brake" + }, + { + name = "CAR_HANDBRAKE", + category = "LVS-Car", + name_menu = "Handbrake", + default = "+jump", + cmd = "lvs_car_handbrake" + }, + { + name = "CAR_STEER_LEFT", + category = "LVS-Car", + name_menu = "Steer Left", + default = "+moveleft", + cmd = "lvs_car_turnleft" + }, + { + name = "CAR_STEER_RIGHT", + category = "LVS-Car", + name_menu = "Steer Right", + default = "+moveright", + cmd = "lvs_car_turnright" + }, + { + name = "CAR_LIGHTS_TOGGLE", + category = "LVS-Car", + name_menu = "Toggle Lights", + default = "phys_swap", + cmd = "lvs_car_toggle_lights" + }, + { + name = "CAR_MENU", + category = "LVS-Car", + name_menu = "Open Signal Menu", + default = "+zoom", + cmd = "lvs_car_menu" + }, + { + name = "CAR_SIREN", + category = "LVS-Car", + name_menu = "Open Siren Menu", + default = "phys_swap", + cmd = "lvs_car_siren" + }, + { + name = "CAR_SWAP_AMMO", + category = "LVS-Car", + name_menu = "Change Ammo Type", + default = "+walk", + cmd = "lvs_car_swap_ammo" + }, + { + name = "CAR_HYDRAULIC", + category = "LVS-Car", + name_menu = "Hydraulic", + default = KEY_PAD_5, + cmd = "lvs_hydraulic" + }, + { + name = "CAR_HYDRAULIC_FRONT", + category = "LVS-Car", + name_menu = "Hydraulic Front", + default = KEY_PAD_8, + cmd = "lvs_hydraulic_front" + }, + { + name = "CAR_HYDRAULIC_REAR", + category = "LVS-Car", + name_menu = "Hydraulic Rear", + default = KEY_PAD_2, + cmd = "lvs_hydraulic_rear" + }, + { + name = "CAR_HYDRAULIC_LEFT", + category = "LVS-Car", + name_menu = "Hydraulic Left", + default = KEY_PAD_4, + cmd = "lvs_hydraulic_left" + }, + { + name = "CAR_HYDRAULIC_RIGHT", + category = "LVS-Car", + name_menu = "Hydraulic Right", + default = KEY_PAD_6, + cmd = "lvs_hydraulic_right" + }, + { + name = "CAR_SHIFT_UP", + category = "LVS-Car", + name_menu = "Shift Up", + cmd = "lvs_car_shift_up" + }, + { + name = "CAR_SHIFT_DN", + category = "LVS-Car", + name_menu = "Shift Down", + cmd = "lvs_car_shift_dn" + }, + } + + for _, v in pairs( KEYS ) do + LVS:AddKey( v.name, v.category, v.name_menu, v.cmd, v.default ) + end +end ) + + +hook.Add( "LVS:Initialize", "[LVS] - Helicopter - Keys", function() + local KEYS = { + { + name = "+THRUST_HELI", + category = "LVS-Helicopter", + name_menu = "Throttle Increase", + default = "+forward", + cmd = "lvs_helicopter_throttle_up" + }, + { + name = "-THRUST_HELI", + category = "LVS-Helicopter", + name_menu = "Throttle Decrease", + default = "+back", + cmd = "lvs_helicopter_throttle_down" + }, + { + name = "+PITCH_HELI", + category = "LVS-Helicopter", + name_menu = "Pitch Up", + cmd = "lvs_helicopter_pitch_up" + }, + { + name = "-PITCH_HELI", + category = "LVS-Helicopter", + name_menu = "Pitch Down", + cmd = "lvs_helicopter_pitch_down" + }, + { + name = "-YAW_HELI", + category = "LVS-Helicopter", + name_menu = "Yaw Left [Roll in Direct Input]", + cmd = "lvs_helicopter_yaw_left" + }, + { + name = "+YAW_HELI", + category = "LVS-Helicopter", + name_menu = "Yaw Right [Roll in Direct Input]", + cmd = "lvs_helicopter_yaw_right" + }, + { + name = "-ROLL_HELI", + category = "LVS-Helicopter", + name_menu = "Roll Left [Yaw in Direct Input]", + default = "+moveleft", + cmd = "lvs_helicopter_roll_left" + }, + { + name = "+ROLL_HELI", + category = "LVS-Helicopter", + name_menu = "Roll Right [Yaw in Direct Input]", + default = "+moveright", + cmd = "lvs_helicopter_roll_right" + }, + { + name = "HELI_HOVER", + category = "LVS-Helicopter", + name_menu = "Hover", + default = "+speed", + cmd = "lvs_helicopter_hover" + }, + } + + for _, v in pairs( KEYS ) do + LVS:AddKey( v.name, v.category, v.name_menu, v.cmd, v.default ) + end +end ) + +hook.Add( "LVS:Initialize", "[LVS] - Planes - Keys", function() + local KEYS = { + { + name = "+THROTTLE", + category = "LVS-Plane", + name_menu = "Throttle Increase", + default = "+forward", + cmd = "lvs_plane_throttle_up" + }, + { + name = "-THROTTLE", + category = "LVS-Plane", + name_menu = "Throttle Decrease", + default = "+back", + cmd = "lvs_plane_throttle_down" + }, + { + name = "+PITCH", + category = "LVS-Plane", + name_menu = "Pitch Up", + default = "+speed", + cmd = "lvs_plane_pitch_up" + }, + { + name = "-PITCH", + category = "LVS-Plane", + name_menu = "Pitch Down", + cmd = "lvs_plane_pitch_down" + }, + { + name = "-YAW", + category = "LVS-Plane", + name_menu = "Yaw Left [Roll in Direct Input]", + cmd = "lvs_plane_yaw_left" + }, + { + name = "+YAW", + category = "LVS-Plane", + name_menu = "Yaw Right [Roll in Direct Input]", + cmd = "lvs_plane_yaw_right" + }, + { + name = "-ROLL", + category = "LVS-Plane", + name_menu = "Roll Left [Yaw in Direct Input]", + default = "+moveleft", + cmd = "lvs_plane_roll_left" + }, + { + name = "+ROLL", + category = "LVS-Plane", + name_menu = "Roll Right [Yaw in Direct Input]", + default = "+moveright", + cmd = "lvs_plane_roll_right" + }, + } + + for _, v in pairs( KEYS ) do + LVS:AddKey( v.name, v.category, v.name_menu, v.cmd, v.default ) + end +end ) + +hook.Add( "LVS:Initialize", "[LVS] - Star Wars - Keys", function() + local KEYS = { + { + name = "+THRUST_SF", + category = "LVS-Starfighter", + name_menu = "Thrust Increase", + default = "+forward", + cmd = "lvs_starfighter_throttle_up" + }, + { + name = "-THRUST_SF", + category = "LVS-Starfighter", + name_menu = "Thrust Decrease", + default = "+back", + cmd = "lvs_starfighter_throttle_down" + }, + { + name = "+PITCH_SF", + category = "LVS-Starfighter", + name_menu = "Pitch Up", + default = "+speed", + cmd = "lvs_starfighter_pitch_up" + }, + { + name = "-PITCH_SF", + category = "LVS-Starfighter", + name_menu = "Pitch Down", + cmd = "lvs_starfighter_pitch_down" + }, + { + name = "-YAW_SF", + category = "LVS-Starfighter", + name_menu = "Yaw Left [Roll in Direct Input]", + cmd = "lvs_starfighter_yaw_left" + }, + { + name = "+YAW_SF", + category = "LVS-Starfighter", + name_menu = "Yaw Right [Roll in Direct Input]", + cmd = "lvs_starfighter_yaw_right" + }, + { + name = "-ROLL_SF", + category = "LVS-Starfighter", + name_menu = "Roll Left [Yaw in Direct Input]", + default = "+moveleft", + cmd = "lvs_starfighter_roll_left" + }, + { + name = "+ROLL_SF", + category = "LVS-Starfighter", + name_menu = "Roll Right [Yaw in Direct Input]", + default = "+moveright", + cmd = "lvs_starfighter_roll_right" + }, + { + name = "+VTOL_Z_SF", + category = "LVS-Starfighter", + name_menu = "VTOL Up", + cmd = "lvs_starfighter_vtol_up" + }, + { + name = "-VTOL_Z_SF", + category = "LVS-Starfighter", + name_menu = "VTOL Down", + cmd = "lvs_starfighter_vtol_dn" + }, + { + name = "-VTOL_Y_SF", + category = "LVS-Starfighter", + name_menu = "VTOL Right", + cmd = "lvs_starfighter_vtol_right" + }, + { + name = "+VTOL_Y_SF", + category = "LVS-Starfighter", + name_menu = "VTOL Left", + cmd = "lvs_starfighter_vtol_left" + }, + { + name = "-VTOL_X_SF", + category = "LVS-Starfighter", + name_menu = "VTOL Reverse", + default = "+back", + cmd = "lvs_starfighter_vtol_reverse" + }, + } + + for _, v in pairs( KEYS ) do + LVS:AddKey( v.name, v.category, v.name_menu, v.cmd, v.default ) + end +end ) + +if SERVER then return end + +concommand.Add( "lvs_mouseaim_toggle", function( ply, cmd, args ) + local OldVar = GetConVar( "lvs_mouseaim" ):GetInt() + + if OldVar == 0 then + ply:PrintMessage( HUD_PRINTTALK, "[LVS] Mouse-Aim: Enabled" ) + RunConsoleCommand( "lvs_mouseaim", "1" ) + + else + ply:PrintMessage( HUD_PRINTTALK, "[LVS] Mouse-Aim: Disabled" ) + RunConsoleCommand( "lvs_mouseaim", "0" ) + end +end ) + +hook.Add( "PlayerBindPress", "!!!!_LVS_PlayerBindPress", function( ply, bind, pressed ) + if not ply.lvsGetVehicle then return end + + local vehicle = ply:lvsGetVehicle() + + if not IsValid( vehicle ) then return end + + if not ply:lvsKeyDown( "VIEWDIST" ) then + if string.find( bind, "invnext" ) then + vehicle:NextWeapon() + end + if string.find( bind, "invprev" ) then + vehicle:PrevWeapon() + end + end + + if string.find( bind, "+zoom" ) then + if vehicle.lvsDisableZoom then + return true + end + end +end ) + +hook.Add( "SpawnMenuOpen", "!!!lvs_spawnmenudisable", function() + local ply = LocalPlayer() + + if not ply._lvsDisableSpawnMenu or not IsValid( ply:lvsGetVehicle() ) then return end + + return false +end ) + +hook.Add( "ContextMenuOpen", "!!!lvs_contextmenudisable", function() + local ply = LocalPlayer() + + if not ply._lvsDisableContextMenu or not IsValid( ply:lvsGetVehicle() ) then return end + + return false +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_net.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_net.lua new file mode 100644 index 0000000..d48e870 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_net.lua @@ -0,0 +1,83 @@ + +if SERVER then + util.AddNetworkString( "lvs_player_request_filter" ) + util.AddNetworkString( "lvs_player_enterexit" ) + util.AddNetworkString( "lvs_toggle_mouseaim" ) + util.AddNetworkString( "lvs_car_turnsignal" ) + util.AddNetworkString( "lvs_car_break" ) + + net.Receive( "lvs_car_turnsignal", function( len, ply ) + if not IsValid( ply ) then return end + + local veh = ply:lvsGetVehicle() + + if not IsValid( veh ) or veh:GetDriver() ~= ply then return end + + veh:SetTurnMode( net.ReadInt( 4 ) ) + end ) + + net.Receive( "lvs_toggle_mouseaim", function( length, ply ) + ply:lvsBuildControls() + + local veh = ply:lvsGetVehicle() + + if not IsValid( veh ) then return end + + veh:AlignView( ply ) + end) + + net.Receive( "lvs_player_request_filter", function( length, ply ) + if not IsValid( ply ) then return end + + local ent = net.ReadEntity() + + if not IsValid( ent ) or not ent.GetCrosshairFilterEnts then return end -- TODO: Make this loop around and wait for ent.IsInitialized to exist and ent:IsInitialized() to return true + + local CrosshairFilterEnts = table.Copy( ent:GetCrosshairFilterEnts() ) + + for id, entity in pairs( CrosshairFilterEnts ) do + if not IsValid( entity ) or entity:GetNoDraw() then + CrosshairFilterEnts[ id ] = nil + end + end + + net.Start( "lvs_player_request_filter" ) + net.WriteEntity( ent ) + net.WriteTable( CrosshairFilterEnts ) + net.Send( ply ) + end) +else + net.Receive("lvs_car_break", function( len ) + local ent = net.ReadEntity() + + if not IsValid( ent ) or not isfunction( ent.OnEngineStallBroken ) then return end + + ent:OnEngineStallBroken() + end) + + net.Receive( "lvs_player_request_filter", function( length ) + local LVSent = net.ReadEntity() + + if not IsValid( LVSent ) then return end + + local Filter = {} + + for _, entity in pairs( net.ReadTable() ) do + if not IsValid( entity ) then continue end + table.insert( Filter, entity ) + end + + LVSent.CrosshairFilterEnts = Filter + end ) + + net.Receive( "lvs_player_enterexit", function( len ) + local Enable = net.ReadBool() + local Vehicle = net.ReadEntity() + + if Enable then + hook.Run( "LVS.PlayerEnteredVehicle", LocalPlayer(), Vehicle ) + else + hook.Run( "LVS.PlayerLeaveVehicle", LocalPlayer(), Vehicle ) + end + end ) +end diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_player.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_player.lua new file mode 100644 index 0000000..581f1a6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_player.lua @@ -0,0 +1,417 @@ +local meta = FindMetaTable( "Player" ) + +function meta:lvsGetAITeam() + return self:GetNWInt( "lvsAITeam", LVS.PlayerDefaultTeam ) +end + +function meta:lvsGetVehicle() + local Pod = self:GetVehicle() + + if not IsValid( Pod ) then return NULL end + + if Pod.LVSchecked then + + return Pod.LVSBaseEnt + + else + local Parent = Pod:GetParent() + + if not IsValid( Parent ) then return NULL end + + if not Parent.LVS then + Pod.LVSchecked = LVS.MapDoneLoading + Pod.LVSBaseEnt = NULL + + return NULL + end + + Pod.LVSchecked = LVS.MapDoneLoading + Pod.LVSBaseEnt = Parent + + return Parent + end +end + +function meta:lvsGetWeaponHandler() + local Pod = self:GetVehicle() + + if not IsValid( Pod ) then return NULL end + + local weapon = Pod:lvsGetWeapon() + + if IsValid( weapon ) then + return weapon + else + local veh = self:lvsGetVehicle() + + if not IsValid( veh ) then return NULL end + + if veh:GetDriver() == self then + return veh + else + return NULL + end + end +end + +function meta:lvsGetControls() + if not istable( self.LVS_BINDS ) then + self:lvsBuildControls() + end + + return self.LVS_BINDS +end + +function meta:lvsMouseAim() + if LVS:IsDirectInputForced() then + return false + end + + return self._lvsMouseAim +end + +function meta:lvsMouseSensitivity() + local X = self._lvsMouseX or 1 + local Y = self._lvsMouseY or 1 + local delta = self._lvsReturnDelta or 1 + + return X, Y, delta +end + +function meta:lvsBuildControls() + if istable( self.LVS_BINDS ) then + table.Empty( self.LVS_BINDS ) + end + + if SERVER then + self._lvsMouseAim = self:GetInfoNum( "lvs_mouseaim", 0 ) == 1 + + self.LVS_BINDS = table.Copy( LVS.KEYS_CATEGORIES ) + + for _,v in pairs( LVS.KEYS_REGISTERED ) do + if v.id == "~SKIP~" then continue end + + local ButtonID = self:GetInfoNum( v.cmd, 0 ) + + if not self.LVS_BINDS[v.category][ ButtonID ] then + self.LVS_BINDS[v.category][ ButtonID ] = {} + end + + table.insert( self.LVS_BINDS[v.category][ ButtonID ], v.id ) + end + + net.Start( "lvs_buildcontrols" ) + net.Send( self ) + + self._lvsMouseX = self:GetInfoNum( "lvs_sensitivity_x", 1 ) + self._lvsMouseY = self:GetInfoNum( "lvs_sensitivity_y", 1 ) + self._lvsReturnDelta = self:GetInfoNum( "lvs_return_delta", 1 ) + else + self._lvsMouseAim = GetConVar( "lvs_mouseaim" ):GetInt() == 1 + self._lvsMouseX = GetConVar( "lvs_sensitivity_x" ):GetFloat() + self._lvsMouseY = GetConVar( "lvs_sensitivity_y" ):GetFloat() + self._lvsReturnDelta = GetConVar( "lvs_return_delta" ):GetFloat() + + self.LVS_BINDS = {} + + local KeySpawnMenu = input.LookupBinding( "+menu" ) + if isstring( KeySpawnMenu ) then + KeySpawnMenu = input.GetKeyCode( KeySpawnMenu ) + end + + local KeyContextMenu = input.LookupBinding( "+menu_context" ) + if isstring( KeyContextMenu ) then + KeyContextMenu = input.GetKeyCode( KeyContextMenu ) + end + + self._lvsDisableSpawnMenu = nil + self._lvsDisableContextMenu = nil + + for _,v in pairs( LVS.KEYS_REGISTERED ) do + if v.id == "~SKIP~" then continue end + + local KeyCode = GetConVar( v.cmd ):GetInt() + + self.LVS_BINDS[ v.id ] = KeyCode + + if KeyCode == KeySpawnMenu then + self._lvsDisableSpawnMenu = true + end + if KeyCode == KeyContextMenu then + self._lvsDisableContextMenu = true + end + end + end +end + +local IS_MOUSE_ENUM = { + [MOUSE_LEFT] = true, + [MOUSE_RIGHT] = true, + [MOUSE_MIDDLE] = true, + [MOUSE_4] = true, + [MOUSE_5] = true, + [MOUSE_WHEEL_UP] = true, + [MOUSE_WHEEL_DOWN] = true, +} + +local function GetInput( ply, name ) + if SERVER then + if not ply._lvsKeyDown then + ply._lvsKeyDown = {} + end + + return ply._lvsKeyDown[ name ] == true + else + local Key = ply:lvsGetControls()[ name ] or 0 + + if IS_MOUSE_ENUM[ Key ] then + return input.IsMouseDown( Key ) + else + return input.IsKeyDown( Key ) + end + end +end + +function meta:lvsKeyDown( name ) + if not self:lvsGetInputEnabled() then return false end + + local Pressed = GetInput( self, name ) + local NewPressed = hook.Run( "LVS.PlayerKeyDown", self, name, Pressed ) + + if isbool( NewPressed ) then + return NewPressed + else + return Pressed + end +end + +function meta:lvsGetInputEnabled() + return (self._lvsKeyDisabler or 0) < CurTime() +end + +function meta:lvsSetInputDisabled( disable ) + if CLIENT then + net.Start( "lvs_buildcontrols" ) + net.WriteBool( disable ) + net.SendToServer() + end + + if disable then + self._lvsKeyDisabler = CurTime() + 120 + else + self._lvsKeyDisabler = CurTime() + 0.25 + end +end + +if CLIENT then + function meta:lvsSetView( view ) + self._lvsViewPos = view.origin or vector_origin + self._lvsViewAngles = view.angles or angle_zero + + return view + end + + function meta:lvsGetView() + local pos = self._lvsViewPos or vector_origin + local ang = self._lvsViewAngles or angle_zero + + return pos, ang + end + + net.Receive( "lvs_buildcontrols", function( len ) + local ply = LocalPlayer() + if not IsValid( ply ) then return end + ply:lvsBuildControls() + end ) + + local OldVisible = false + local function KeyBlocker() + local Visible = gui.IsGameUIVisible() or vgui.CursorVisible() + + if Visible ~= OldVisible then + OldVisible = Visible + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + if Visible then + ply:lvsSetInputDisabled( true ) + else + ply:lvsSetInputDisabled( false ) + end + end + end + + hook.Add( "LVS.PlayerEnteredVehicle", "!!!!!lvs_keyblocker_enable", function( ply, veh ) + hook.Add("PostDrawHUD", "!!!lvs_keyblocker", KeyBlocker ) + end ) + + hook.Add( "LVS.PlayerLeaveVehicle", "!!!!!lvs_keyblocker_disable", function( ply, veh ) + hook.Remove("PostDrawHUD", "!!!lvs_keyblocker" ) + end ) + + local players_bonemanip = {} + + local function StartBoneManip( id ) + players_bonemanip[ id ] = true + end + + local function StopBoneManip( id ) + if not players_bonemanip[ id ] then return end + + players_bonemanip[ id ] = nil + + local ply = Entity( id ) + + if not IsValid( ply ) then return end + + local angle_zero = Angle(0,0,0) + + for i = 0, (ply:GetBoneCount() - 1) do + ply:ManipulateBoneAngles( i, angle_zero ) + end + end + + net.Receive( "lvs_bonemanip", function( len ) + local entindex = net.ReadInt( 9 ) + local enable = net.ReadBool() + + if enable then + StartBoneManip( entindex ) + + return + end + + StopBoneManip( entindex ) + end ) + + hook.Add( "Think", "!!!!!lvs_player_bonemanip", function() + for EntID, _ in pairs( players_bonemanip ) do + local ply = Entity( EntID ) + + if not IsValid( ply ) or not ply:IsPlayer() then continue end + + local Pod = ply:GetVehicle() + local vehicle = ply:lvsGetVehicle() + + if not IsValid( Pod ) or not IsValid( vehicle ) then return end + + local BoneManipulate = vehicle:GetPlayerBoneManipulation( ply, Pod:lvsGetPodIndex() ) + + for name, ang in pairs( BoneManipulate ) do + local bone = ply:LookupBone( name ) + + if not bone then continue end + + ply:ManipulateBoneAngles( bone, ang ) + end + end + end ) + + return +end + +util.AddNetworkString( "lvs_buildcontrols" ) +util.AddNetworkString( "lvs_bonemanip" ) + +function meta:lvsStartBoneManip() + net.Start( "lvs_bonemanip" ) + net.WriteInt( self:EntIndex(), 9 ) + net.WriteBool( true ) + net.Broadcast() +end + +function meta:lvsStopBoneManip() + net.Start( "lvs_bonemanip" ) + net.WriteInt( self:EntIndex(), 9 ) + net.WriteBool( false ) + net.Broadcast() +end + +net.Receive( "lvs_buildcontrols", function( len, ply ) + if not IsValid( ply ) then return end + + ply:lvsSetInputDisabled( net.ReadBool() ) +end ) + +function meta:lvsSetInput( name, value ) + if not self._lvsKeyDown then + self._lvsKeyDown = {} + end + + self._lvsKeyDown[ name ] = value +end + +LVS.TEAMS = { + [0] = "FRIENDLY TO EVERYONE", + [1] = "Team 1", + [2] = "Team 2", + [3] = "HOSTILE TO EVERYONE", +} + +function meta:lvsSetAITeam( nTeam ) + nTeam = nTeam or LVS.PlayerDefaultTeam + + if self:lvsGetAITeam() ~= nTeam then + self:PrintMessage( HUD_PRINTTALK, "[LVS] Your AI-Team has been updated to: "..(LVS.TEAMS[ nTeam ] or "") ) + end + + self:SetNWInt( "lvsAITeam", nTeam ) +end + +hook.Add( "PlayerButtonUp", "!!!lvsButtonUp", function( ply, button ) + for _, KeyBind in pairs( ply:lvsGetControls() ) do + local KeyTBL = KeyBind[ button ] + + if not KeyTBL then continue end + + for _, KeyName in pairs( KeyTBL ) do + ply:lvsSetInput( KeyName, false ) + end + end +end ) + +hook.Add( "PlayerButtonDown", "!!!lvsButtonDown", function( ply, button ) + if not ply:lvsGetInputEnabled() then return end + + local vehicle = ply:lvsGetVehicle() + local vehValid = IsValid( vehicle ) + + for _, KeyBind in pairs( ply:lvsGetControls() ) do + local KeyTBL = KeyBind[ button ] + + if not KeyTBL then continue end + + for _, KeyName in pairs( KeyTBL ) do + ply:lvsSetInput( KeyName, true ) + + if not vehValid then continue end + + if string.StartWith( KeyName, "~SELECT~" ) then + local exp_string = string.Explode( "#", KeyName ) + local base = ply:lvsGetWeaponHandler() + + if exp_string[2] and IsValid( base ) then + base:SelectWeapon( tonumber( exp_string[2] ) ) + end + end + + if KeyName == "EXIT" then + if vehicle:GetDriver() == ply and vehicle:GetlvsLockedStatus() then vehicle:UnLock() end + + if vehicle:GetlvsLockedStatus() then continue end + + local T = CurTime() + + if (ply._lvsNextExit or 0) > T then continue end + + ply:ExitVehicle() + end + end + end +end ) + +hook.Add("CanExitVehicle","!!!lvsCanExitVehicle",function(vehicle,ply) + if IsValid( ply:lvsGetVehicle() ) then return false end +end) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_pod.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_pod.lua new file mode 100644 index 0000000..c503905 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_pod.lua @@ -0,0 +1,195 @@ + +local meta = FindMetaTable( "Vehicle" ) + +function meta:lvsGetVehicle() + if self.LVSchecked then + + return self.LVSBaseEnt + + else + local Parent = self:GetParent() + + if not IsValid( Parent ) then return NULL end + + if not Parent.LVS then + self.LVSchecked = LVS.MapDoneLoading + self.LVSBaseEnt = NULL + + return NULL + end + + self.LVSchecked = LVS.MapDoneLoading + self.LVSBaseEnt = Parent + + return Parent + end +end + +if CLIENT then + function meta:lvsGetPodIndex() + local id = self:GetNWInt( "pPodIndex", -1 ) + + if id ~= -1 then return id end + + -- code below is bandaid fix for ent:GetNWInt taking up to 5 minutes to update on client... + + local col = self:GetColor() + local id_by_color = col.r + + -- 255 or 0 is suspicous... + if id_by_color == 255 or id_by_color == 0 then return -1 end + + -- lets just assume its right... right? + if id_by_color == col.g and id_by_color == col.b then + return id_by_color + end + + return -1 + end + + function meta:GetCameraHeight() + if not self._lvsCamHeight then + self._lvsCamHeight = 0 + + net.Start("lvs_camera") + net.WriteEntity( self ) + net.SendToServer() + end + + return self._lvsCamHeight + end + + function meta:SetCameraHeight( newheight ) + self._lvsCamHeight = newheight + end + + function meta:lvsGetWeapon() + if self._lvsWeaponEntChecked then + return self._lvsWeaponEnt + end + + local found = false + + for _, ent in ipairs( self:GetChildren() ) do + if not ent.LVS_GUNNER then continue end + + self._lvsWeaponEntChecked = true + self._lvsWeaponEnt = ent + + found = true + + break + end + + return found and self._lvsWeaponEnt or NULL + end + + net.Receive( "lvs_select_weapon", function( length) + local ply = LocalPlayer() + local vehicle = ply:lvsGetVehicle() + + if not IsValid( vehicle ) or vehicle:GetDriver() ~= ply then return end + + vehicle._SelectActiveTime = CurTime() + 2 + end) + + + net.Receive( "lvs_camera", function( length, ply ) + local pod = net.ReadEntity() + + if not IsValid( pod ) then return end + + pod:SetCameraHeight( net.ReadFloat() ) + end) + + return +end + +function meta:lvsGetPodIndex() + return self:GetNWInt( "pPodIndex", -1 ) +end + +function meta:GetCameraHeight() + return (self._lvsCamHeight or 0) +end + +util.AddNetworkString( "lvs_select_weapon" ) +util.AddNetworkString( "lvs_camera" ) + +net.Receive( "lvs_select_weapon", function( length, ply ) + if not IsValid( ply ) then return end + + local ID = net.ReadInt( 5 ) + local Increment = net.ReadBool() + + local base = ply:lvsGetWeaponHandler() + + if not IsValid( base ) then return end + + if Increment then + base:SelectWeapon( base:GetSelectedWeapon() + ID ) + else + base:SelectWeapon( ID ) + end +end) + +net.Receive( "lvs_camera", function( length, ply ) + if not IsValid( ply ) then return end + + local pod = net.ReadEntity() + + if not IsValid( pod ) then return end + + net.Start("lvs_camera") + net.WriteEntity( pod ) + net.WriteFloat( pod:GetCameraHeight() ) + net.Send( ply ) +end) + +function meta:SetCameraHeight( newheight ) + self._lvsCamHeight = newheight + + net.Start("lvs_camera") + net.WriteEntity( self ) + net.WriteFloat( newheight ) + net.Broadcast() +end + +function meta:lvsAddWeapon( ID ) + if IsValid( self._lvsWeaponEnt ) then + return self._lvsWeaponEnt + end + + local weapon = ents.Create( "lvs_base_gunner" ) + + if not IsValid( weapon ) then return NULL end + + weapon:SetPos( self:LocalToWorld( Vector(0,0,33.182617) ) ) -- location exactly where ply:GetShootPos() is. This will make AI-Tracing easier. + weapon:SetAngles( self:LocalToWorldAngles( Angle(0,90,0) ) ) + weapon:SetOwner( self ) + weapon:Spawn() + weapon:Activate() + weapon:SetParent( self ) + weapon:SetPodIndex( ID ) + weapon:SetDriverSeat( self ) + + self._lvsWeaponEnt = weapon + + weapon:SetSelectedWeapon( 1 ) + + return weapon +end + +function meta:lvsGetWeapon() + return self._lvsWeaponEnt +end + +function meta:lvsSetPodIndex( index ) + -- garbage networking + self:SetNWInt( "pPodIndex", index ) + + self:SetMaterial( "null" ) + + -- more reliable networking, lol + self:SetColor( Color( index, index, index, 0 ) ) +end diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_soundscripts.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_soundscripts.lua new file mode 100644 index 0000000..ec50f2d --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/lvs_soundscripts.lua @@ -0,0 +1,194 @@ + +sound.Add( { + name = "LVS.Physics.Scrape", + channel = CHAN_STATIC, + level = 80, + sound = "lvs/physics/scrape_loop.wav" +} ) + +sound.Add( { + name = "LVS.Physics.Wind", + channel = CHAN_STATIC, + level = 140, + sound = "lvs/physics/wind_loop.wav", +} ) + +sound.Add( { + name = "LVS.Physics.Water", + channel = CHAN_STATIC, + level = 140, + sound = "lvs/physics/water_loop.wav", +} ) + +sound.Add( { + name = "LVS.DYNAMIC_EXPLOSION", + channel = CHAN_STATIC, + volume = 1.0, + level = 130, + pitch = {90, 110}, + sound = "^lvs/explosion_dist.wav" +} ) + +sound.Add( { + name = "LVS.MISSILE_EXPLOSION", + channel = CHAN_STATIC, + volume = 1.0, + level = 130, + pitch = {90, 120}, + sound = { + "ambient/levels/streetwar/city_battle17.wav", + "ambient/levels/streetwar/city_battle18.wav", + "ambient/levels/streetwar/city_battle19.wav", + } +} ) + +sound.Add( { + name = "LVS.BOMB_EXPLOSION_DYNAMIC", + channel = CHAN_STATIC, + volume = 1, + level = 135, + pitch = {90, 110}, + sound = { + "^lvs/explosions/dyn1.wav", + "^lvs/explosions/dyn2.wav", + "^lvs/explosions/dyn3.wav", + "^lvs/explosions/dyn4.wav", + } +} ) + +sound.Add( { + name = "LVS.BOMB_EXPLOSION", + channel = CHAN_STATIC, + volume = 1, + level = 75, + pitch = {90, 110}, + sound = { + "lvs/explosions/close1.wav", + "lvs/explosions/close2.wav", + "lvs/explosions/close3.wav", + "lvs/explosions/close4.wav", + } +} ) + +sound.Add( { + name = "LVS.BULLET_EXPLOSION_DYNAMIC", + channel = CHAN_STATIC, + volume = 1, + level = 135, + pitch = {90, 110}, + sound = { + "^lvs/explosions/med_dyn1.wav", + "^lvs/explosions/med_dyn2.wav", + "^lvs/explosions/med_dyn3.wav", + "^lvs/explosions/med_dyn4.wav", + } +} ) + +sound.Add( { + name = "LVS.BULLET_EXPLOSION", + channel = CHAN_STATIC, + volume = 1, + level = 75, + pitch = {90, 110}, + sound = { + "lvs/explosions/med_close1.wav", + "lvs/explosions/med_close2.wav", + "lvs/explosions/med_close3.wav", + "lvs/explosions/med_close4.wav", + } +} ) + +sound.Add( { + name = "LVS.EXPLOSION", + channel = CHAN_STATIC, + volume = 1.0, + level = 115, + pitch = {95, 115}, + sound = "lvs/explosion.wav" +} ) + +sound.Add( { + name = "LVS_MISSILE_FIRE", + channel = CHAN_WEAPON, + volume = 1.0, + level = 90, + sound = { + "^lvs/weapons/missile_1.wav", + "^lvs/weapons/missile_2.wav", + "^lvs/weapons/missile_3.wav", + "^lvs/weapons/missile_4.wav", + } +} ) + +sound.Add( { + name = "LVS_FIGHTERPLANE_CRASH", + channel = CHAN_STATIC, + volume = 1.0, + level = 128, + sound = { + "lvs/vehicles/generic/figher_crash1.wav", + "lvs/vehicles/generic/figher_crash2.wav", + } +} ) + +sound.Add( { + name = "LVS_BOMBERPLANE_CRASH", + channel = CHAN_STATIC, + volume = 1.0, + level = 128, + sound = { + "lvs/vehicles/generic/bomber_crash1.wav", + "lvs/vehicles/generic/bomber_crash2.wav", + "lvs/vehicles/generic/bomber_crash3.wav", + } +} ) + +sound.Add( { + name = "LVS.Brake.Release", + channel = CHAN_STATIC, + level = 75, + volume = 1, + sound = { + "lvs/vehicles/generic/pneumatic_brake_release_01.wav", + "lvs/vehicles/generic/pneumatic_brake_release_02.wav", + "lvs/vehicles/generic/pneumatic_brake_release_03.wav", + "lvs/vehicles/generic/pneumatic_brake_release_04.wav", + } +} ) + +sound.Add( { + name = "LVS.Brake.Apply", + channel = CHAN_STATIC, + level = 75, + volume = 1, + sound = "lvs/vehicles/generic/pneumatic_brake_pull.wav", +} ) + + +if CLIENT then + local SoundList = {} + + hook.Add( "EntityEmitSound", "!!!lvs_fps_rape_fixer", function( t ) + if not t.Entity.LVS and not t.Entity._LVS then return end + + local SoundFile = t.SoundName + + if SoundList[ SoundFile ] == true then + return true + + elseif SoundList[ SoundFile ] == false then + return false + + else + local File = string.Replace( SoundFile, "^", "" ) + + local Exists = file.Exists( "sound/"..File , "GAME" ) + + SoundList[ SoundFile ] = Exists + + if not Exists then + print("[LVS] '"..SoundFile.."' not found. Soundfile will not be played and is filtered for this game session to avoid fps issues.") + end + end + end ) +end diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_entityrelationship.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_entityrelationship.lua new file mode 100644 index 0000000..5dcd5f4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_entityrelationship.lua @@ -0,0 +1,69 @@ + +local function ApplyTeamRules( teamVeh, IsEnemy ) + if teamVeh == 0 then + IsEnemy = false + end + + if teamVeh == 3 then + IsEnemy = true + end + + return IsEnemy +end + +function LVS:SetNPCRelationship( NPC ) + for _, lvsVeh in pairs( LVS:GetVehicles() ) do + local teamVeh = lvsVeh:GetAITEAM() + + local IsEnemy = ApplyTeamRules( teamVeh, lvsVeh:IsEnemy( NPC ) ) + + local IsActive = (lvsVeh:GetAI() or #lvsVeh:GetEveryone() > 0) and not lvsVeh:IsDestroyed() + + if IsActive and IsEnemy then + NPC:AddEntityRelationship( lvsVeh, D_HT, 25 ) + NPC:UpdateEnemyMemory( lvsVeh, lvsVeh:GetPos() ) + else + local D_, _ = NPC:Disposition( lvsVeh ) + + if D_ ~= D_NU then + NPC:AddEntityRelationship( lvsVeh, D_NU ) + NPC:ClearEnemyMemory( lvsVeh ) + end + end + end +end + +function LVS:SetVehicleRelationship( lvsVeh ) + local teamVeh = lvsVeh:GetAITEAM() + + local Pos = lvsVeh:GetPos() + local IsActive = (lvsVeh:GetAI() or #lvsVeh:GetEveryone() > 0) and not lvsVeh:IsDestroyed() + + for _, NPC in pairs( LVS:GetNPCs() ) do + local IsEnemy = ApplyTeamRules( teamVeh, lvsVeh:IsEnemy( NPC ) ) + + if IsActive and IsEnemy then + NPC:AddEntityRelationship( lvsVeh, D_HT, 25 ) + NPC:UpdateEnemyMemory( lvsVeh, Pos ) + else + local D_, _ = NPC:Disposition( lvsVeh ) + + if D_ ~= D_NU then + NPC:AddEntityRelationship( lvsVeh, D_NU ) + NPC:ClearEnemyMemory( lvsVeh ) + end + end + end +end + +hook.Add( "LVS.UpdateRelationship", "!!!!lvsEntityRelationship", function( ent ) + timer.Simple(0.1, function() + if not IsValid( ent ) then return end + + if isfunction( ent.IsNPC ) and ent:IsNPC() then + LVS:SetNPCRelationship( ent ) + else + LVS:SetVehicleRelationship( ent ) + end + end) +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_exit.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_exit.lua new file mode 100644 index 0000000..df5b07f --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_exit.lua @@ -0,0 +1,154 @@ + +hook.Add( "PlayerUse", "!!!LVS_FIX_RE_ENTER", function( ply, ent ) + if ent.LVS and (ply._lvsNextUse or 0) > CurTime() then return false end +end ) + +hook.Add( "PlayerLeaveVehicle", "!!LVS_Exit", function( ply, Pod ) + if not ply:IsPlayer() or not IsValid( Pod ) then return end + + local Vehicle = Pod:lvsGetVehicle() + + if not IsValid( Vehicle ) then return end + + if not LVS.FreezeTeams then + ply:lvsSetAITeam( Vehicle:GetAITEAM() ) + end + + ply._lvsNextUse = CurTime() + 0.25 + + hook.Run( "LVS.UpdateRelationship", Vehicle ) + + local pos = Vehicle:LocalToWorld( Vehicle:OBBCenter() ) + local vel = Vehicle:GetVelocity() + local radius = Vehicle:BoundingRadius() + + local mins, maxs = ply:GetHull() + + local PosCenter = Pod:OBBCenter() + local StartPos = Pod:LocalToWorld( PosCenter ) + + local FilterPlayer = { ply } + local Filter = table.Copy( Vehicle:GetCrosshairFilterEnts() ) + table.insert( Filter, ply ) + + local zOffset = 15 + local ValidPositions = {} + + if isvector( Pod.ExitPos ) and Vehicle:GetUp().z > 0.9 then + local data = { + pos = Vehicle:LocalToWorld( Pod.ExitPos ), + dist = 1, + } + + table.insert( ValidPositions, data ) + end + + local LocalDesiredExitPosition = Vehicle:WorldToLocal( Pod:GetPos() ) + + if vel:Length() > (Pod.PlaceBehindVelocity or 100) then + LocalDesiredExitPosition.y = LocalDesiredExitPosition.y - radius + + local traceBehind = util.TraceLine( { + start = pos, + endpos = pos - vel:GetNormalized() * (radius + 50), + filter = Filter, + } ) + + local tracePlayer = util.TraceHull( { + start = traceBehind.HitPos + Vector(0,0,maxs.z + zOffset), + endpos = traceBehind.HitPos + Vector(0,0,zOffset), + maxs = Vector( maxs.x, maxs.y, 0 ), + mins = Vector( mins.x, mins.y, 0 ), + filter = FilterPlayer, + } ) + + if not tracePlayer.Hit and util.IsInWorld( tracePlayer.HitPos ) then + local data = { + pos = traceBehind.HitPos, + dist = 0, + } + + table.insert( ValidPositions, data ) + end + end + + local DesiredExitPosition = Pod:LocalToWorld( LocalDesiredExitPosition ) + + for ang = 0, 360, 15 do + local X = math.cos( math.rad( ang ) ) * radius + local Y = math.sin( math.rad( ang ) ) * radius + local Z = Pod:OBBCenter().z + + local EndPos = StartPos + Vector(X,Y,Z) + + local traceWall = util.TraceLine( {start = StartPos,endpos = EndPos,filter = Filter} ) + local traceVehicle = util.TraceLine( { + start = traceWall.HitPos, + endpos = StartPos, + filter = FilterPlayer, + } ) + + local CenterWallVehicle = (traceWall.HitPos + traceVehicle.HitPos) * 0.5 + + if traceWall.Hit or not util.IsInWorld( CenterWallVehicle ) then continue end + + local GoundPos = CenterWallVehicle - Vector(0,0,radius) + + local traceGround = util.TraceLine( {start = CenterWallVehicle,endpos = GoundPos,filter = Filter} ) + + if not traceGround.Hit or not util.IsInWorld( traceGround.HitPos ) then continue end + + local tracePlayerRoof = util.TraceHull( { + start = traceGround.HitPos + Vector(0,0,zOffset), + endpos = traceGround.HitPos + Vector(0,0,maxs.z + zOffset), + maxs = Vector( maxs.x, maxs.y, 0 ), + mins = Vector( mins.x, mins.y, 0 ), + filter = FilterPlayer, + } ) + + if tracePlayerRoof.Hit or not util.IsInWorld( tracePlayerRoof.HitPos ) then continue end + + local tracePlayer = util.TraceHull( { + start = traceGround.HitPos + Vector(0,0,maxs.z + zOffset), + endpos = traceGround.HitPos + Vector(0,0,zOffset), + maxs = Vector( maxs.x, maxs.y, 0 ), + mins = Vector( mins.x, mins.y, 0 ), + filter = FilterPlayer, + } ) + + if tracePlayer.Hit then continue end + + local traceBack = util.TraceLine( { + start = tracePlayer.HitPos + Vector(0,0,zOffset), + endpos = StartPos, + filter = FilterPlayer, + } ) + + local data = { + pos = tracePlayer.HitPos, + dist = (traceBack.HitPos - DesiredExitPosition):Length(), + } + + table.insert( ValidPositions, data ) + end + + local ExitPos + local ExitDist + + for _, data in pairs( ValidPositions ) do + if not ExitPos or not ExitDist or ExitDist > data.dist then + ExitPos = data.pos + ExitDist = data.dist + end + end + + -- all my plans failed, lets just let source do its thing + if not ExitPos then return end + + local ViewAngles = (StartPos - ExitPos):Angle() + ViewAngles.p = 0 + ViewAngles.r = 0 + + ply:SetPos( ExitPos ) + ply:SetEyeAngles( ViewAngles ) +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_switcher.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_switcher.lua new file mode 100644 index 0000000..bc127a6 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/autorun/sv_switcher.lua @@ -0,0 +1,77 @@ +hook.Add( "PlayerButtonDown", "!!!lvsSeatSwitcherButtonDown", function( ply, button ) + local vehicle = ply:lvsGetVehicle() + + if not IsValid( vehicle ) then return end + + local CurPod = ply:GetVehicle() + + if button == KEY_1 then + if ply == vehicle:GetDriver() then + if vehicle:GetlvsLockedStatus() then + vehicle:UnLock() + else + vehicle:Lock() + end + else + if IsValid( vehicle:GetDriver() ) then return end + + if vehicle:GetAI() then + vehicle:SetAI( false ) + vehicle:SetAIGunners( true ) + end + + if hook.Run( "LVS.CanPlayerDrive", ply, vehicle ) == false then + hook.Run( "LVS.OnPlayerCannotDrive", ply, vehicle ) + return + end + + ply:ExitVehicle() + + local DriverSeat = vehicle:GetDriverSeat() + + if not IsValid( DriverSeat ) then return end + + if hook.Run( "LVS.OnPlayerRequestSeatSwitch", ply, vehicle, CurPod, DriverSeat ) == false then return end + + timer.Simple( 0, function() + if not IsValid( vehicle ) or not IsValid( ply ) then return end + if IsValid( vehicle:GetDriver() ) or not IsValid( DriverSeat ) or vehicle:GetAI() then return end + + ply:EnterVehicle( DriverSeat ) + vehicle:AlignView( ply ) + vehicle:OnSwitchSeat( ply, CurPod, DriverSeat ) + end) + end + else + for _, Pod in pairs( vehicle:GetPassengerSeats() ) do + if not IsValid( Pod ) or Pod:GetNWInt( "pPodIndex", 3 ) ~= LVS.pSwitchKeys[ button ] then continue end + + local Driver = Pod:GetDriver() + + if IsValid( Driver ) then + if Driver == ply then + for _, DoorHandler in ipairs( vehicle:GetDoorHandlers() ) do + if DoorHandler:GetLinkedSeat() ~= Pod or not DoorHandler:IsOpen() then continue end + + DoorHandler:Close( ply ) + end + end + + continue + end + + if hook.Run( "LVS.OnPlayerRequestSeatSwitch", ply, vehicle, CurPod, Pod ) == false then continue end + + ply:ExitVehicle() + + timer.Simple( 0, function() + if not IsValid( Pod ) or not IsValid( ply ) then return end + if IsValid( Pod:GetDriver() ) then return end + + ply:EnterVehicle( Pod ) + vehicle:AlignView( ply, true ) + vehicle:OnSwitchSeat( ply, CurPod, Pod ) + end) + end + end +end ) diff --git a/garrysmod/addons/lvs_base/lua/lvs_framework/init.lua b/garrysmod/addons/lvs_base/lua/lvs_framework/init.lua new file mode 100644 index 0000000..e077b43 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/lvs_framework/init.lua @@ -0,0 +1,240 @@ + +local StartTime = SysTime() + +if SERVER then + AddCSLuaFile("includes/circles/circles.lua") +end + +local function FileIsEmpty( filename ) + if file.Size( filename, "LUA" ) <= 1 then -- this is suspicous + local data = file.Read( filename, "LUA" ) + + if data and string.len( data ) <= 1 then -- confirm its empty + + print("[LVS] - refusing to load '"..filename.."'! File is Empty!" ) + + return true + end + end + + return false +end + +for _, filename in pairs( file.Find("lvs_framework/autorun/*.lua", "LUA") ) do + if FileIsEmpty( "lvs_framework/autorun/"..filename ) then continue end + + if string.StartWith( filename, "sv_") then -- sv_ prefix only load serverside + if SERVER then + include("lvs_framework/autorun/"..filename) + end + + continue + end + + if string.StartWith( filename, "cl_") then -- cl_ prefix only load clientside + if SERVER then + AddCSLuaFile("lvs_framework/autorun/"..filename) + else + include("lvs_framework/autorun/"..filename) + end + + continue + end + + -- everything else is shared + if SERVER then + AddCSLuaFile("lvs_framework/autorun/"..filename) + end + include("lvs_framework/autorun/"..filename) +end + +hook.Run( "LVS:Initialize" ) + +print("[LVS] - initialized ["..math.Round((SysTime() - StartTime) * 1000,2).."ms]") + +if CLIENT then + hook.Add( "InitPostEntity", "!!!lvscheckupdates", function() + timer.Simple(20, function() + LVS.CheckUpdates() + + local convar = GetConVar( "no_error_hitboxes" ) + + if not convar then return end + + convar:SetBool( false ) + end) + end ) + + return +end + +resource.AddWorkshop("2912816023") + +local ValveWierdBlastDamageClass = { + ["npc_strider"] = true, -- takes 70 damage for each blast damage as constant value ... + ["npc_combinegunship"] = true, -- takes 44 damage as constant value ... + ["func_breakable_surf"] = true, -- this entity dont care about anything that isnt a trace attack or blast damage +} + +function LVS:BlastDamage( pos, forward, attacker, inflictor, damage, damagetype, radius, force ) + + local dmginfo = DamageInfo() + dmginfo:SetAttacker( attacker ) + dmginfo:SetInflictor( inflictor ) + dmginfo:SetDamage( damage ) + dmginfo:SetDamageType( damagetype == DMG_BLAST and DMG_SONIC or damagetype ) + + if damagetype ~= DMG_BLAST then + dmginfo:SetDamagePosition( pos ) + dmginfo:SetDamageForce( forward * force ) + + util.BlastDamageInfo( dmginfo, pos, radius ) + + return + end + + util.BlastDamageInfo( dmginfo, pos, radius ) + + local FragmentAngle = 10 + local NumFragments = 16 + local NumFragmentsMissed = 0 + + local RegisteredHits = {} + + local trace = util.TraceLine( { + start = pos, + endpos = pos - forward * radius, + filter = { attacker, inflictor }, + } ) + + local startpos = trace.HitPos + + for i = 1, NumFragments do + local ang = forward:Angle() + Angle( math.random(-FragmentAngle,FragmentAngle), math.random(-FragmentAngle,FragmentAngle), 0 ) + local dir = ang:Forward() + + local endpos = pos + dir * radius + + local trace = util.TraceLine( { + start = startpos, + endpos = endpos, + filter = { attacker, inflictor }, + } ) + + debugoverlay.Line( startpos, trace.HitPos, 10, Color( 255, 0, 0, 255 ), true ) + + if not trace.Hit then + NumFragmentsMissed = NumFragmentsMissed + 1 + + continue + end + + if not IsValid( trace.Entity ) then continue end + + if not RegisteredHits[ trace.Entity ] then + RegisteredHits[ trace.Entity ] = {} + end + + table.insert( RegisteredHits[ trace.Entity ], { + origin = trace.HitPos, + force = forward * force, + } ) + end + + local Hull = Vector(10,10,10) + + for _, ent in ipairs( ents.FindInSphere( pos, radius ) ) do + if not ent.LVS or ent == inflictor or ent == attacker then continue end + + local trace = util.TraceHull( { + start = pos, + endpos = ent:LocalToWorld( ent:OBBCenter() ), + mins = -Hull, + maxs = Hull, + whitelist = true, + ignoreworld = true, + filter = ent, + } ) + + debugoverlay.Line( pos, trace.HitPos, 10, Color( 255, 0, 0, 255 ), true ) + + NumFragments = NumFragments + 1 + + if not RegisteredHits[ ent ] then + RegisteredHits[ ent ] = {} + end + + table.insert( RegisteredHits[ ent ], { + origin = trace.HitPos, + force = forward * force, + } ) + end + + if NumFragmentsMissed == NumFragments then return end + + local DamageBoost = NumFragments / ( NumFragments - NumFragmentsMissed ) + + for ent, data in pairs( RegisteredHits ) do + local NumHits = #data + local AverageOrigin = vector_origin + local AverageForce = vector_origin + + for _, HitData in pairs( data ) do + AverageOrigin = AverageOrigin + HitData.origin + AverageForce = AverageForce + HitData.force + end + + AverageOrigin = AverageOrigin / NumHits + AverageForce = AverageForce / NumHits + + local TotalDamage = ( ( NumHits * DamageBoost ) / NumFragments ) * damage + + --debugoverlay.Cross( AverageOrigin, 50, 10, Color( 255, 0, 255 ) ) + + -- hack + if ValveWierdBlastDamageClass[ ent:GetClass() ] then + + util.BlastDamage( inflictor, attacker, pos, radius, damage ) + + continue + end + + local dmginfo = DamageInfo() + dmginfo:SetAttacker( attacker ) + dmginfo:SetInflictor( inflictor ) + dmginfo:SetDamage( TotalDamage ) + dmginfo:SetDamageForce( AverageForce ) + dmginfo:SetDamagePosition( AverageOrigin ) + dmginfo:SetDamageType( DMG_BLAST ) + + ent:TakeDamageInfo( dmginfo ) + end +end + +function LVS:FixVelocity() + local tbl = physenv.GetPerformanceSettings() + + if tbl.MaxVelocity < 4000 then + local OldVel = tbl.MaxVelocity + + tbl.MaxVelocity = 4000 + physenv.SetPerformanceSettings(tbl) + + print("[LVS] Low MaxVelocity detected! Increasing! "..OldVel.." => 4000") + end + + if tbl.MaxAngularVelocity < 7272 then + local OldAngVel = tbl.MaxAngularVelocity + + tbl.MaxAngularVelocity = 7272 + physenv.SetPerformanceSettings(tbl) + + print("[LVS] Low MaxAngularVelocity detected! Increasing! "..OldAngVel.." => 7272") + end +end + +hook.Add( "InitPostEntity", "!!!lvscheckupdates", function() + timer.Simple(20, function() + LVS.CheckUpdates() + end) +end ) diff --git a/garrysmod/addons/lvs_base/lua/weapons/weapon_lvsfuelfiller.lua b/garrysmod/addons/lvs_base/lua/weapons/weapon_lvsfuelfiller.lua new file mode 100644 index 0000000..b240dc4 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/weapons/weapon_lvsfuelfiller.lua @@ -0,0 +1,304 @@ +AddCSLuaFile() + +SWEP.Category = "[LVS]" +SWEP.Spawnable = false +SWEP.AdminSpawnable = false +SWEP.ViewModel = "models/weapons/c_fuelfillerlvs.mdl" +SWEP.WorldModel = "models/props_equipment/gas_pump_p13.mdl" +SWEP.UseHands = true + +SWEP.HoldType = "slam" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.RangeToCap = 24 +SWEP.HitDistance = 128 + +function SWEP:SetupDataTables() + self:NetworkVar( "Int",0, "FuelType" ) + self:NetworkVar( "Entity",0, "CallbackTarget" ) +end + +function SWEP:GetTank( entity ) + if entity.lvsGasStationRefillMe then + return entity + end + + if not entity.LVS or not entity.GetFuelTank then return NULL end + + return entity:GetFuelTank() +end + +function SWEP:GetCap( entity ) + if entity.lvsGasStationRefillMe then + return entity + end + + if not entity.LVS or not entity.GetFuelTank then return NULL end + + local FuelTank = entity:GetFuelTank() + + if not IsValid( FuelTank ) then return NULL end + + return FuelTank:GetDoorHandler() +end + +if CLIENT then + SWEP.PrintName = "Fuel Filler Pistol" + SWEP.Slot = 1 + SWEP.SlotPos = 3 + + SWEP.DrawWeaponInfoBox = false + + local FrameMat = Material( "lvs/3d2dmats/frame.png" ) + local RefuelMat = Material( "lvs/3d2dmats/refuel.png" ) + + function SWEP:DrawWeaponSelection( x, y, wide, tall, alpha ) + end + + function SWEP:DrawWorldModel() + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + local pos = attachment.Pos + attachment.Ang:Forward() * 6 + attachment.Ang:Right() * -1.5 + attachment.Ang:Up() * 2.2 + local ang = attachment.Ang + ang:RotateAroundAxis(attachment.Ang:Up(), 20) + ang:RotateAroundAxis(attachment.Ang:Right(), -30) + ang:RotateAroundAxis(attachment.Ang:Forward(), 0) + + self:SetRenderOrigin( pos ) + self:SetRenderAngles( ang ) + + self:DrawModel() + end + + local function DrawText( pos, text, col ) + local data2D = pos:ToScreen() + + if not data2D.visible then return end + + local font = "TargetIDSmall" + + local x = data2D.x + local y = data2D.y + draw.SimpleText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( text, font, x, y, col or color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + local function DrawIcon( pos, fueltype, fuelamount, visible ) + local data2D = pos:ToScreen() + + if not data2D.visible then return end + + local data = LVS.FUELTYPES[ fueltype ] + + if not istable( data ) then return end + + local x = data2D.x + local y = data2D.y + + local scale = visible and 2 or 1 + + if visible then + local IconColor = Color( data.color.x, data.color.y, data.color.z, 200 ) + local ScissorScale = 50 + local offset = ScissorScale * scale * fuelamount + local offset2 = ScissorScale * scale * (1 - fuelamount) + + surface.SetDrawColor( Color(0,0,0,200) ) + render.SetScissorRect( x - 40 * scale, y - ScissorScale * 0.5 * scale - offset, x + 40 * scale, y + ScissorScale * 0.5 * scale - offset, true ) + surface.SetMaterial( FrameMat ) + surface.DrawTexturedRect( x - 25 * scale, y - 25 * scale, 50 * scale, 50 * scale ) + surface.SetMaterial( RefuelMat ) + surface.DrawTexturedRect( x - 40 * scale, y - 40 * scale, 80 * scale, 80 * scale ) + + surface.SetDrawColor( IconColor ) + render.SetScissorRect( x - 40 * scale, y - ScissorScale * 0.5 * scale + offset2, x + 40 * scale, y + ScissorScale * 0.5 * scale + offset2, true ) + surface.SetMaterial( FrameMat ) + surface.DrawTexturedRect( x - 25 * scale, y - 25 * scale, 50 * scale, 50 * scale ) + surface.SetMaterial( RefuelMat ) + surface.DrawTexturedRect( x - 40 * scale, y - 40 * scale, 80 * scale, 80 * scale ) + render.SetScissorRect( 0,0,0,0,false ) + + draw.SimpleText( data.name, "LVS_FONT", x, y - 40 * scale, IconColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + local IconColor = Color( data.color.x, data.color.y, data.color.z, 100 ) + + surface.SetDrawColor( IconColor ) + + surface.SetMaterial( FrameMat ) + surface.DrawTexturedRect( x - 25 * scale, y - 25 * scale, 50 * scale, 50 * scale ) + surface.SetMaterial( RefuelMat ) + surface.DrawTexturedRect( x - 40 * scale, y - 40 * scale, 80 * scale, 80 * scale ) + end + end + + function SWEP:DrawHUD() + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local startpos = ply:GetShootPos() + local endpos = startpos + ply:GetAimVector() * self.HitDistance + + local trace = util.TraceLine( { + start = startpos , + endpos = endpos, + filter = ply, + mask = MASK_SHOT_HULL + } ) + + if not IsValid( trace.Entity ) then + trace = util.TraceHull( { + start = startpos , + endpos = endpos, + filter = ply, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + local FuelTank = self:GetTank( trace.Entity ) + local FuelCap = self:GetCap( trace.Entity ) + + if not IsValid( FuelTank ) then return end + + local pos = trace.HitPos + local fuelamount = FuelTank:GetFuel() + local fueltype = FuelTank:GetFuelType() + + if fueltype ~= self:GetFuelType() then + + local FuelName = LVS.FUELTYPES[ fueltype ].name or "" + + DrawText( trace.HitPos, "Incorrect Fuel Type. Requires: "..FuelName, Color(255,0,0,255) ) + + return + end + + if not IsValid( FuelCap ) then + DrawIcon( pos, fueltype, fuelamount, true ) + DrawText( pos, math.Round(fuelamount * 100,1).."%", Color(0,255,0,255) ) + + return + end + + if FuelCap:IsOpen() then + if (trace.HitPos - FuelCap:GetPos()):Length() > self.RangeToCap then + DrawIcon( FuelCap:GetPos(), fueltype, fuelamount, false ) + DrawText( pos, "Aim at Fuel Cap!", Color(255,255,0,255) ) + else + DrawIcon( FuelCap:GetPos(), fueltype, fuelamount, true ) + DrawText( pos, math.Round(fuelamount * 100,1).."%", Color(0,255,0,255) ) + end + + return + end + + local Key = input.LookupBinding( "+use" ) + + if not isstring( Key ) then Key = "[+use is not bound to a key]" end + + local pos = FuelCap:GetPos() + + DrawIcon( pos, fueltype, fuelamount, false ) + DrawText( pos, "Press "..Key.." to Open", Color(255,255,0,255) ) + end +end + +function SWEP:Initialize() + self:SetHoldType( self.HoldType ) +end + +function SWEP:PrimaryAttack() + + self:SetNextPrimaryFire( CurTime() + 0.5 ) + + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local startpos = ply:GetShootPos() + local endpos = startpos + ply:GetAimVector() * self.HitDistance + + local trace = util.TraceLine( { + start = startpos , + endpos = endpos, + filter = ply, + mask = MASK_SHOT_HULL + } ) + + if not IsValid( trace.Entity ) then + trace = util.TraceHull( { + start = startpos , + endpos = endpos, + filter = ply, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + self:Refuel( trace ) +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Refuel( trace ) + local entity = trace.Entity + + if CLIENT or not IsValid( entity ) then return end + + local FuelCap = self:GetCap( entity ) + local FuelTank = self:GetTank( entity ) + + if not IsValid( FuelTank ) then return end + + if FuelTank:GetFuelType() ~= self:GetFuelType() then return end + + if IsValid( FuelCap ) then + if not FuelCap:IsOpen() then return end + + if (trace.HitPos - FuelCap:GetPos()):Length() > self.RangeToCap then return end + end + + if FuelTank:GetFuel() == 1 then return end + + local Target = self:GetCallbackTarget() + + if FuelTank == Target then return end + + if IsValid( Target ) and Target.TakeFuel then + local Size = FuelTank:GetSize() + local Cur = FuelTank:GetFuel() + local Need = 1 - Cur + local Add = Target:TakeFuel( Need * Size ) + + if Add > 0 then + FuelTank:SetFuel( Cur + Add / Size ) + entity:OnRefueled() + end + + return + end + + FuelTank:SetFuel( math.min( FuelTank:GetFuel() + (entity.lvsGasStationFillSpeed or 0.05), 1 ) ) + entity:OnRefueled() +end diff --git a/garrysmod/addons/lvs_base/lua/weapons/weapon_lvsrepair.lua b/garrysmod/addons/lvs_base/lua/weapons/weapon_lvsrepair.lua new file mode 100644 index 0000000..83e1d47 --- /dev/null +++ b/garrysmod/addons/lvs_base/lua/weapons/weapon_lvsrepair.lua @@ -0,0 +1,375 @@ +AddCSLuaFile() + +SWEP.Category = "[LVS]" +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.ViewModel = "models/weapons/c_repairlvs.mdl" +SWEP.WorldModel = "models/weapons/w_repairlvs.mdl" +SWEP.UseHands = true + +SWEP.HoldType = "slam" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.MaxRange = 250 + +function SWEP:SetupDataTables() + self:NetworkVar( "Float",0, "FlameTime" ) +end + +function SWEP:GetLVS() + local ply = self:GetOwner() + + if not IsValid( ply ) then return NULL end + + local ent = ply:GetEyeTrace().Entity + + if not IsValid( ent ) then return NULL end + + if ent._lvsRepairToolLabel or ent.LVS then return ent end + + if not ent.GetBase then return NULL end + + ent = ent:GetBase() + + if IsValid( ent ) and ent.LVS then return ent end + + return NULL +end + +function SWEP:FindClosest() + local lvsEnt = self:GetLVS() + + if not IsValid( lvsEnt ) then return NULL end + + local ply = self:GetOwner() + + if ply:InVehicle() then return end + + local ShootPos = ply:GetShootPos() + local AimVector = ply:GetAimVector() + + local ClosestDist = self.MaxRange + local ClosestPiece = NULL + + local tableEnts = lvsEnt:GetChildren() + + if isfunction( lvsEnt.GetCrosshairFilterEnts ) then + tableEnts = lvsEnt:GetCrosshairFilterEnts() + end + + for _, target in pairs( tableEnts ) do + if not IsValid( target ) then continue end + + for _, entity in pairs( target:GetChildren() ) do + if entity:GetClass() ~= "lvs_armor" then continue end + + local boxOrigin = entity:GetPos() + local boxAngles = entity:GetAngles() + local boxMins = entity:GetMins() + local boxMaxs = entity:GetMaxs() + + local HitPos, _, _ = util.IntersectRayWithOBB( ShootPos, AimVector * 1000, boxOrigin, boxAngles, boxMins, boxMaxs ) + + if isvector( HitPos ) then + local Dist = (ShootPos - HitPos):Length() + + if Dist < ClosestDist then + ClosestDist = Dist + ClosestPiece = entity + end + end + end + end + + return ClosestPiece +end + +local function IsEngineMode( AimPos, Engine ) + if not IsValid( Engine ) then return false end + + if not isfunction( Engine.GetDoorHandler ) then return (AimPos - Engine:GetPos()):Length() < 25 end + + local DoorHandler = Engine:GetDoorHandler() + + if IsValid( DoorHandler ) then + if DoorHandler:IsOpen() then + return (AimPos - Engine:GetPos()):Length() < 50 + end + + return false + end + + return (AimPos - Engine:GetPos()):Length() < 25 +end + +if CLIENT then + SWEP.PrintName = "Repair Torch" + SWEP.Author = "Blu-x92" + + SWEP.Slot = 5 + SWEP.SlotPos = 1 + + SWEP.Purpose = "Repair Broken Armor" + SWEP.Instructions = "Primary to Repair\nHold Secondary to switch to Armor Repair Mode" + SWEP.DrawWeaponInfoBox = true + + SWEP.WepSelectIcon = surface.GetTextureID( "weapons/lvsrepair" ) + + local ColorSelect = Color(0,255,255,50) + local ColorText = Color(255,255,255,255) + + local function DrawText( pos, text, col ) + cam.Start2D() + local data2D = pos:ToScreen() + + if not data2D.visible then cam.End2D() return end + + local font = "TargetIDSmall" + + local x = data2D.x + local y = data2D.y + + draw.DrawText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), TEXT_ALIGN_CENTER ) + draw.DrawText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), TEXT_ALIGN_CENTER ) + draw.DrawText( text, font, x, y, col or color_white, TEXT_ALIGN_CENTER ) + cam.End2D() + end + + function SWEP:DrawEffects( weapon, ply ) + local ID = weapon:LookupAttachment( "muzzle" ) + + local Muzzle = weapon:GetAttachment( ID ) + + if not Muzzle then return end + + local T = CurTime() + + if self:GetFlameTime() < T or (self._NextFX1 or 0) > T then return end + + self._NextFX1 = T + 0.02 + + local effectdata = EffectData() + effectdata:SetOrigin( Muzzle.Pos ) + effectdata:SetAngles( Muzzle.Ang ) + effectdata:SetScale( 0.5 ) + util.Effect( "MuzzleEffect", effectdata, true, true ) + + if (self._NextFX2 or 0) > T then return end + + self._NextFX2 = T + 0.06 + + local trace = ply:GetEyeTrace() + local ShootPos = ply:GetShootPos() + + if (ShootPos - trace.HitPos):Length() > self.MaxRange then return end + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetNormal( trace.HitNormal * 0.15 ) + util.Effect( "manhacksparks", effectdata, true, true ) + + local dlight = DynamicLight( self:EntIndex() ) + + if not dlight then return end + + dlight.pos = (trace.HitPos + ShootPos) * 0.5 + dlight.r = 206 + dlight.g = 253 + dlight.b = 255 + dlight.brightness = 3 + dlight.decay = 1000 + dlight.size = 256 + dlight.dietime = CurTime() + 0.1 + end + + function SWEP:PostDrawViewModel( vm, weapon, ply ) + self:DrawEffects( vm, ply ) + end + + function SWEP:DrawWorldModel( flags ) + self:DrawModel( flags ) + self:DrawEffects( self, self:GetOwner() ) + end + + function SWEP:DrawHUD() + local ply = self:GetOwner() + + if not IsValid( ply ) or not ply:KeyDown( IN_ATTACK2 ) then + local lvsEnt = self:GetLVS() + local Pos = ply:GetEyeTrace().HitPos + + if IsValid( lvsEnt ) and (Pos - ply:GetShootPos()):Length() < self.MaxRange and not ply:InVehicle() then + local Label = lvsEnt._lvsRepairToolLabel or "Frame" + + if isfunction( lvsEnt.GetEngine ) then + local Engine = lvsEnt:GetEngine() + + local AimPos = ply:GetEyeTrace().HitPos + + local EngineMode = IsEngineMode( AimPos, Engine ) + + if IsValid( Engine ) and EngineMode then + DrawText( AimPos, "Engine\nHealth: "..math.Round(Engine:GetHP()).."/"..Engine:GetMaxHP(), ColorText ) + else + DrawText( AimPos, Label.."\nHealth: "..math.Round(lvsEnt:GetHP()).."/"..lvsEnt:GetMaxHP(), ColorText ) + end + else + DrawText( ply:GetEyeTrace().HitPos, Label.."\nHealth: "..math.Round(lvsEnt:GetHP()).."/"..lvsEnt:GetMaxHP(), ColorText ) + end + end + + return + end + + local Target = self:FindClosest() + + if IsValid( Target ) then + local boxOrigin = Target:GetPos() + local boxAngles = Target:GetAngles() + local boxMins = Target:GetMins() + local boxMaxs = Target:GetMaxs() + + cam.Start3D() + render.SetColorMaterial() + render.DrawBox( boxOrigin, boxAngles, boxMins, boxMaxs, ColorSelect ) + cam.End3D() + + DrawText( Target:LocalToWorld( (boxMins + boxMaxs) * 0.5 ), (Target:GetIgnoreForce() / 100).."mm "..Target:GetLabel().."\nHealth: "..math.Round(Target:GetHP()).."/"..Target:GetMaxHP(), ColorText ) + else + local Pos = ply:GetEyeTrace().HitPos + + if IsValid( self:GetLVS() ) and (Pos - ply:GetShootPos()):Length() < self.MaxRange and not ply:InVehicle() then + DrawText( Pos, "No Armor", ColorText ) + end + end + end +end + +function SWEP:Initialize() + self:SetHoldType( self.HoldType ) +end + +function SWEP:PrimaryAttack() + local T = CurTime() + + self:SetNextPrimaryFire( T + 0.15 ) + + self:SetFlameTime( T + 0.3 ) + + local EngineMode = false + local ArmorMode = true + local Target = self:FindClosest() + + local ply = self:GetOwner() + + if IsValid( ply ) and not ply:KeyDown( IN_ATTACK2 ) then + Target = self:GetLVS() + + if isfunction( Target.GetEngine ) then + local Engine = Target:GetEngine() + + local AimPos = ply:GetEyeTrace().HitPos + + EngineMode = IsEngineMode( AimPos, Engine ) + + if IsValid( Engine ) and EngineMode then + Target = Engine + end + end + + ArmorMode = false + end + + if not IsValid( Target ) then return end + + local HP = Target:GetHP() + local MaxHP = Target:GetMaxHP() + + if IsFirstTimePredicted() then + local trace = ply:GetEyeTrace() + + if HP ~= MaxHP then + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetNormal( trace.HitNormal ) + util.Effect( "stunstickimpact", effectdata, true, true ) + end + end + + if CLIENT then return end + + Target:SetHP( math.min( HP + 15, MaxHP ) ) + + if EngineMode and Target:GetDestroyed() then + Target:SetDestroyed( false ) + end + + if not ArmorMode then return end + + if Target:GetDestroyed() then Target:SetDestroyed( false ) end + + if HP < MaxHP then return end + + Target:OnRepaired() +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + local ply = self:GetOwner() + + if not IsValid( ply ) then self:StopSND() return end + + local PlaySound = self:GetFlameTime() >= CurTime() and (ply:GetShootPos() - ply:GetEyeTrace().HitPos):Length() < self.MaxRange + + if PlaySound then + self:PlaySND() + else + self:StopSND() + end +end + +function SWEP:StopSND() + if CLIENT then return end + + if not self._snd then return end + + self._snd:Stop() + self._snd = nil +end + +function SWEP:PlaySND() + if CLIENT then return end + + if self._snd then return end + + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + self._snd = CreateSound( ply, "lvs/weldingtorch_loop.wav" ) + self._snd:PlayEx(1, 70 ) +end + +function SWEP:OnRemove() + self:StopSND() +end + +function SWEP:OnDrop() + self:StopSND() +end + +function SWEP:Holster( wep ) + self:StopSND() + return true +end diff --git a/garrysmod/addons/lvs_cars/data_static/lvs/3027255911.txt b/garrysmod/addons/lvs_cars/data_static/lvs/3027255911.txt new file mode 100644 index 0000000..1e38ac3 --- /dev/null +++ b/garrysmod/addons/lvs_cars/data_static/lvs/3027255911.txt @@ -0,0 +1,3 @@ +name=Cars +version=1408 +url=https://raw.githubusercontent.com/SpaxscE/lvs_cars/main/data_static/lvs/3027255911.txt \ No newline at end of file diff --git a/garrysmod/addons/lvs_cars/lua/autorun/server/lvs_cars_addworkshop.lua b/garrysmod/addons/lvs_cars/lua/autorun/server/lvs_cars_addworkshop.lua new file mode 100644 index 0000000..9dd0114 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/autorun/server/lvs_cars_addworkshop.lua @@ -0,0 +1 @@ +resource.AddWorkshop("2922255745") -- LVS Cars diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/cl_init.lua new file mode 100644 index 0000000..173cfbd --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/cl_init.lua @@ -0,0 +1,16 @@ +include("shared.lua") + +function ENT:UpdatePoseParameters( steer ) + self:SetPoseParameter( "vehicle_steer", steer ) + self:SetPoseParameter( "kickstart", self:GetKickStarter() ) +end + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/bmw_r75/eng_start.wav", 75, 100, LVS.EngineVolume ) + + return + end + + self:EmitSound( "lvs/vehicles/bmw_r75/eng_stop.wav", 75, 100, LVS.EngineVolume ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/init.lua new file mode 100644 index 0000000..d45893e --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/init.lua @@ -0,0 +1,58 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +ENT.LeanAngleIdle = -10 +ENT.LeanAnglePark = 10 + +function ENT:OnSpawn( PObj ) + self:AddDriverSeat( Vector(-10,0,23), Angle(0,-90,0) ) + self:AddPassengerSeat( Vector(-25,0,24), Angle(0,-90,-5) ) + + self:AddEngine( Vector(14,0,10) ) + self:AddFuelTank( Vector(13.84,0,26.21), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL, Vector(-10,-5,-4),Vector(8,5,4) ) + + local WheelModel = "models/diggercars/bmw_r75/r75_wheel.mdl" + + local FWheel = self:AddWheel( { hide = true, pos = Vector(38,0,12), mdl = WheelModel, mdl_ang = Angle(0,0,0), width = 2 } ) + local RWheel = self:AddWheel( { hide = true, pos = Vector(-14,0,11), mdl = WheelModel, mdl_ang = Angle(0,0,0), width = 2 } ) + + self:CreateRigControler( "fl", FWheel, 5, 13 ) + self:CreateRigControler( "rl", RWheel, 5, 13 ) + + local FrontAxle = self:DefineAxle( { + Axle = { + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + }, + Wheels = { FWheel }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 10000, + SpringDamping = 800, + SpringRelativeDamping = 800, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { RWheel }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 10000, + SpringDamping = 800, + SpringRelativeDamping = 800, + }, + } ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/shared.lua new file mode 100644 index 0000000..e3e107b --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_bmw_r75/shared.lua @@ -0,0 +1,148 @@ + +ENT.Base = "lvs_bike_wheeldrive" + +ENT.PrintName = "BMW R75" +ENT.Author = "Digger" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Military" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/bmw_r75/r75_bike.mdl" + +ENT.AITEAM = 1 + +ENT.MaxHealth = 500 + +ENT.MaxVelocity = 1250 +ENT.MaxVelocityReverse = 100 + +ENT.EngineCurve = 0.4 +ENT.EngineTorque = 250 + +ENT.TransGears = 4 +ENT.TransGearsReverse = 1 + +ENT.lvsShowInSpawner = true + +ENT.KickStarter = true +ENT.KickStarterSound = "lvs/vehicles/bmw_r75/moped_crank.wav" +ENT.KickStarterMinAttempts = 2 +ENT.KickStarterMaxAttempts = 4 +ENT.KickStarterAttemptsInSeconds = 5 + +ENT.DriverBoneManipulateIdle = { + ["ValveBiped.Bip01_L_Thigh"] = Angle(-25,45,0), + ["ValveBiped.Bip01_L_Calf"] = Angle(0,-30,0), + ["ValveBiped.Bip01_L_Foot"] = Angle(0,0,0), +} + +ENT.DriverBoneManipulateParked = { + ["ValveBiped.Bip01_R_Thigh"] = Angle(25,45,0), + ["ValveBiped.Bip01_R_Calf"] = Angle(0,-30,0), + ["ValveBiped.Bip01_R_Foot"] = Angle(0,0,0), +} + +ENT.DriverBoneManipulateKickStart = { + Start = { + ["ValveBiped.Bip01_L_Thigh"] = Angle(-20,60,25), + ["ValveBiped.Bip01_L_Calf"] = Angle(0,0,0), + ["ValveBiped.Bip01_L_Foot"] = Angle(0,-10,0), + }, + End = { + ["ValveBiped.Bip01_L_Thigh"] = Angle(-20,-10,25), + ["ValveBiped.Bip01_L_Calf"] = Angle(0,70,0), + ["ValveBiped.Bip01_L_Foot"] = Angle(0,-10,0), + }, +} + +ENT.PlayerBoneManipulate = { + [1] = { + ["ValveBiped.Bip01_Pelvis"] = Angle(0,0,23), + + ["ValveBiped.Bip01_R_Thigh"] = Angle(14,10,-5), + ["ValveBiped.Bip01_L_Thigh"] = Angle(-14,10,5), + + ["ValveBiped.Bip01_R_Calf"] = Angle(0,40,0), + ["ValveBiped.Bip01_L_Calf"] = Angle(0,40,0), + + ["ValveBiped.Bip01_R_Foot"] = Angle(0,-20,0), + ["ValveBiped.Bip01_L_Foot"] = Angle(0,-20,0), + + ["ValveBiped.Bip01_R_UpperArm"] = Angle(10,25,0), + ["ValveBiped.Bip01_L_UpperArm"] = Angle(-5,25,0), + + ["ValveBiped.Bip01_R_Forearm"] = Angle(0,-10,0), + ["ValveBiped.Bip01_L_Forearm"] = Angle(0,-10,0), + }, + [2] = { + ["ValveBiped.Bip01_R_Thigh"] = Angle(14,10,0), + ["ValveBiped.Bip01_L_Thigh"] = Angle(-14,10,0), + }, +} + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/bmw_r75/eng_idle.wav", + Volume = 0.7, + Pitch = 85, + PitchMul = 50, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/bmw_r75/eng_loop.wav", + Volume = 1, + Pitch = 50, + PitchMul = 50, + SoundLevel = 75, + UseDoppler = true, + }, +} + +ENT.Lights = { + { + Trigger = "main+high", + SubMaterialID = 0, + Sprites = { + { pos = "lamp", colorB = 200, colorA = 150 }, + }, + }, + { + Trigger = "main", + Sprites = { + { pos = "back1", colorG = 0, colorB = 0, colorA = 150, width = 10, height = 10 }, + }, + ProjectedTextures = { + { pos = "lamp", ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + }, + }, + { + Trigger = "high", + ProjectedTextures = { + { pos = "lamp", ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + }, + }, + { + Trigger = "main", + SubMaterialID = 4, + }, + { + Trigger = "brake", + SubMaterialBrightness = 1, + Sprites = { + { pos = "back1", colorG = 0, colorB = 0, colorA = 150, width = 20, height = 20 }, + }, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-38.43,-5,16), + ang = Angle(0,180,0), + }, +} diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/cl_init.lua new file mode 100644 index 0000000..5a26ab4 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/cl_init.lua @@ -0,0 +1,29 @@ +include("shared.lua") +include("sh_tracks.lua") +include("sh_turret.lua") +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") + +function ENT:OnFrame() + local Heat = 0 + if self:GetSelectedWeapon() == 1 then + Heat = self:QuickLerp( "50cal_heat", self:GetNWHeat(), 10 ) + else + Heat = self:QuickLerp( "50cal_heat", 0, 0.2 ) + end + + local name = "halftrack_gunglow_"..self:EntIndex() + + if not self.TurretGlow then + self.TurretGlow = self:CreateSubMaterial( 4, name ) + + return + end + + if self._oldGunHeat ~= Heat then + self._oldGunHeat = Heat + + self.TurretGlow:SetFloat("$detailblendfactor", Heat ^ 7 ) + + self:SetSubMaterial(4, "!"..name) + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/init.lua new file mode 100644 index 0000000..af3556c --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/init.lua @@ -0,0 +1,86 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "sh_turret.lua" ) +AddCSLuaFile( "sh_tracks.lua" ) +include("shared.lua") +include("sh_turret.lua") +include("sh_tracks.lua") + +-- since this is based on a tank we need to reset these to default var values: +ENT.DSArmorDamageReductionType = DMG_BULLET + DMG_CLUB +ENT.DSArmorIgnoreDamageType = DMG_SONIC + + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(0,21,30), Angle(0,-90,0) ) + local PassengerSeat = self:AddPassengerSeat( Vector(15,-21,37), Angle(0,-90,10) ) + + self.HornSND = self:AddSoundEmitter( Vector(40,0,35), "lvs/horn3.wav" ) + self.HornSND:SetSoundLevel( 75 ) + self.HornSND:SetDoppler( true ) + + local DoorHandler = self:AddDoorHandler( "left_door", Vector(20,30,48), Angle(0,0,0), Vector(-17,-3,-12), Vector(20,6,12), Vector(-17,-15,-12), Vector(20,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) + DoorHandler:LinkToSeat( DriverSeat ) + + local DoorHandler = self:AddDoorHandler( "right_door", Vector(20,-30,48), Angle(0,180,0), Vector(-17,-3,-12), Vector(20,6,12), Vector(-17,-15,-12), Vector(20,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) + DoorHandler:LinkToSeat( PassengerSeat ) + + local DoorHandler = self:AddDoorHandler( "hatch", Vector(35,0,70), Angle(0,0,0), Vector(-10,-30,-10), Vector(10,30,10), Vector(-10,-30,-10), Vector(10,30,10) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_close.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_open.wav" ) + + local DoorHandler = self:AddDoorHandler( "trunk", Vector(-115,0,60), Angle(0,0,0), Vector(-1,-15,-15), Vector(1,15,15), Vector(-30,-15,-15), Vector(1,15,15) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) + + self:AddEngine( Vector(68,0,50) ) + self:AddFuelTank( Vector(55,0,18), Angle(0,0,0), 600, LVS.FUELTYPE_DIESEL ) + + self.SNDTurretMG = self:AddSoundEmitter( Vector(-63,0,85), "lvs/vehicles/halftrack/mc_loop.wav" ) + self.SNDTurretMG:SetSoundLevel( 95 ) + + local WheelModel = "models/diggercars/m5m16/m5_wheel.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0.5, + BrakeFactor = 1, + }, + Wheels = { + self:AddWheel( { + pos = Vector(81,-32,20), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + + self:AddWheel( { + pos = Vector(81,32,20), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + self:AddTrailerHitch( Vector(-121,0,25), LVS.HITCHTYPE_MALE ) +end + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/halftrack/engine_start.wav" ) + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/sh_tracks.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/sh_tracks.lua new file mode 100644 index 0000000..d4c635e --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/sh_tracks.lua @@ -0,0 +1,101 @@ + +if SERVER then + function ENT:TracksCreate( PObj ) + local WheelModel = "models/diggercars/m5m16/m5_wheel.mdl" + + local L1 = self:AddWheel( { hide = true, pos = Vector(-37,32,23), mdl = WheelModel } ) + local L2 = self:AddWheel( { hide = true, pos = Vector(-75,32,23), mdl = WheelModel } ) + local LeftWheelChain = self:CreateWheelChain( {L1, L2} ) + self:SetTrackDriveWheelLeft( L1 ) + + local R1 = self:AddWheel( { hide = true, pos = Vector(-37,-32,23), mdl = WheelModel, mdl_ang = Angle(0,180,0) } ) + local R2 = self:AddWheel( { hide = true, pos = Vector(-75,-32,23), mdl = WheelModel, mdl_ang = Angle(0,180,0) } ) + local RightWheelChain = self:CreateWheelChain( {R1, R2} ) + self:SetTrackDriveWheelRight( R1 ) + + local LeftTracksArmor = self:AddArmor( Vector(-55,30,20), Angle(0,0,0), Vector(-50,-10,-25), Vector(50,10,15), 200, 1000 ) + self:SetTrackArmorLeft( LeftTracksArmor, LeftWheelChain ) + + local RightTracksArmor = self:AddArmor( Vector(-55,-30,20), Angle(0,0,0), Vector(-50,-10,-25), Vector(50,10,15), 200, 1000 ) + self:SetTrackArmorRight( RightTracksArmor, RightWheelChain ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 0.5, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R1, L1, L2, R2 }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + end +else + ENT.TrackSystemEnable = true + + ENT.TrackScrollTexture = "models/diggercars/m5m16/m5_tracks_right" + ENT.ScrollTextureData = { + ["$bumpmap"] = "models/diggercars/shared/skin_nm", + ["$phong"] = "1", + ["$phongboost"] = "0.04", + ["$phongexponent"] = "3", + ["$phongfresnelranges"] = "[1 1 1]", + ["$translate"] = "[0.0 0.0 0.0]", + ["$colorfix"] = "{255 255 255}", + ["Proxies"] = { + ["TextureTransform"] = { + ["translateVar"] = "$translate", + ["centerVar"] = "$center", + ["resultVar"] = "$basetexturetransform", + }, + ["Equals"] = { + ["srcVar1"] = "$colorfix", + ["resultVar"] = "$color", + } + } + } + + ENT.TrackLeftSubMaterialID = 5 + ENT.TrackLeftSubMaterialMul = Vector(-0.0725,0,0) + + ENT.TrackRightSubMaterialID = 6 + ENT.TrackRightSubMaterialMul = Vector(-0.0725,0,0) + + ENT.TrackPoseParameterLeft = "spin_wheels_left" + ENT.TrackPoseParameterLeftMul = -2 + + ENT.TrackPoseParameterRight = "spin_wheels_right" + ENT.TrackPoseParameterRightMul = -2 + + ENT.TrackSounds = "lvs/vehicles/halftrack/tracks_loop.wav" + ENT.TrackHull = Vector(5,5,5) + ENT.TrackData = {} + + for i = 1, 5 do + for n = 0, 1 do + local LR = n == 0 and "l" or "r" + local LeftRight = n == 0 and "left" or "right" + local data = { + Attachment = { + name = "vehicle_suspension_"..LR.."_"..i, + toGroundDistance = 37, + traceLength = 150, + }, + PoseParameter = { + name = "suspension_"..LeftRight.."_"..i, + rangeMultiplier = -1.25, + lerpSpeed = 25, + } + } + table.insert( ENT.TrackData, data ) + end + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/sh_turret.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/sh_turret.lua new file mode 100644 index 0000000..9197a78 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/sh_turret.lua @@ -0,0 +1,39 @@ + +include("entities/lvs_tank_wheeldrive/modules/sh_turret.lua") + +ENT.TurretAimRate = 50 + +ENT.TurretFakeBarrel = true +ENT.TurretFakeBarrelRotationCenter = Vector(-63,0,85) + +ENT.TurretRotationSound = "vehicles/tank_turret_loop1.wav" + +ENT.TurretPitchPoseParameterName = "turret_pitch" +ENT.TurretPitchMin = -30 +ENT.TurretPitchMax = 40 +ENT.TurretPitchMul = 1 +ENT.TurretPitchOffset = -10 + +ENT.TurretYawPoseParameterName = "turret_yaw" +ENT.TurretYawMul = 1 +ENT.TurretYawOffset = 180 + +function ENT:TurretInRange() + local ID = self:LookupAttachment( "muzzle_1" ) + + local Muzzle = self:GetAttachment( ID ) + + if not Muzzle then return true end + + local Dir1 = Muzzle.Ang:Forward() + local Dir2 = self:GetAimVector() + + return self:AngleBetweenNormal( Dir1, Dir2 ) < 10 +end + +if CLIENT then + function ENT:UpdatePoseParameters( steer, speed_kmh, engine_rpm, throttle, brake, handbrake, clutch, gear, temperature, fuel, oil, ammeter ) + self:SetPoseParameter( "vehicle_steer", steer ) + self:CalcTurret() + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/shared.lua new file mode 100644 index 0000000..0790080 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodhalftrack_us/shared.lua @@ -0,0 +1,255 @@ + +ENT.Base = "lvs_tank_wheeldrive" + +ENT.PrintName = "Half-track" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Armored" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/m5m16/m5m16.mdl" + +ENT.AITEAM = 2 + +ENT.MaxHealth = 650 +ENT.MaxHealthEngine = 400 +ENT.MaxHealthFuelTank = 100 + +--damage system +ENT.CannonArmorPenetration = 2700 +ENT.CannonArmorPenetration1km = 1200 + +ENT.MaxVelocity = 700 +ENT.MaxVelocityReverse = 250 + +ENT.EngineCurve = 0 +ENT.EngineTorque = 175 + +ENT.TransGears = 3 +ENT.TransGearsReverse = 1 + +ENT.lvsShowInSpawner = true + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/halftrack/eng_idle_loop.wav", + Volume = 0.5, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/halftrack/eng_loop.wav", + Volume = 1, + Pitch = 50, + PitchMul = 100, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_REV_UP, + UseDoppler = true, + }, + { + sound = "lvs/vehicles/halftrack/eng_revdown_loop.wav", + Volume = 1, + Pitch = 50, + PitchMul = 100, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_REV_DOWN, + UseDoppler = true, + }, +} + +ENT.Lights = { + { + Trigger = "main", + SubMaterialID = 1, + Sprites = { + [1] = { + pos = Vector(91.57,29.43,50.18), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(91.57,-29.43,50.18), + colorB = 200, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(91.57,29.43,50.18), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(91.57,-29.43,50.18), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, + { + Trigger = "high", + SubMaterialID = 2, + Sprites = { + [1] = { + pos = Vector(96.81,19.99,49.67), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(96.81,-19.99,49.67), + colorB = 200, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(96.81,19.99,49.67), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(96.81,-19.99,49.67), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-49.71,-39.44,25.4), + ang = Angle(0,-142.92,-24.29), + }, +} + +function ENT:InitWeapons() + local weapon = {} + weapon.Icon = Material("lvs/weapons/bullet.png") + weapon.Ammo = 2000 + weapon.Delay = 0.02 + weapon.HeatRateUp = 0.25 + weapon.HeatRateDown = 0.15 + weapon.Attack = function( ent ) + if not ent:TurretInRange() then + if IsValid( ent.SNDTurretMG ) then + ent.SNDTurretMG:Stop() + end + + return true + end + + ent._MuzzleID = ent._MuzzleID and ent._MuzzleID + 1 or 1 + + if ent._MuzzleID > 4 then + ent._MuzzleID = 1 + end + + local ID = ent:LookupAttachment( "muzzle_"..ent._MuzzleID ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local Pos = Muzzle.Pos + local Dir = Muzzle.Ang:Forward() + + local bullet = {} + bullet.Src = Pos + bullet.Dir = (ent:GetEyeTrace().HitPos - Pos):GetNormalized() + bullet.Spread = Vector(0.03,0.03,0.03) + bullet.TracerName = "lvs_tracer_white" + bullet.Force = ent.CannonArmorPenetration + bullet.Force1km = ent.CannonArmorPenetration1km + bullet.EnableBallistics = true + bullet.HullSize = 1 + bullet.Damage = 35 + bullet.Velocity = 20000 + bullet.Attacker = ent:GetDriver() + bullet.Callback = function(att, tr, dmginfo) end + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + if not IsValid( ent.SNDTurretMG ) then return end + + ent.SNDTurretMG:Play() + end + weapon.StartAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + ent.SNDTurretMG:Play() + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + if not ent.SNDTurretMG:GetActive() then return end + + ent.SNDTurretMG:Stop() + ent.SNDTurretMG:EmitSound( "lvs/vehicles/halftrack/mc_lastshot.wav" ) + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/vehicles/222/cannon_overheat.wav") + end + weapon.HudPaint = function( ent, X, Y, ply ) + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = ent:TurretInRange() and Color(255,255,255,255) or Color(255,0,0,255) + + ent:PaintCrosshairCenter( Pos2D, Col ) + ent:PaintCrosshairOuter( Pos2D, Col ) + ent:LVSPaintHitMarker( Pos2D ) + end + self:AddWeapon( weapon ) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/horn.png") + weapon.Ammo = -1 + weapon.Delay = 0.5 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 0 + weapon.UseableByAI = false + weapon.Attack = function( ent ) end + weapon.StartAttack = function( ent ) + if not IsValid( ent.HornSND ) then return end + ent.HornSND:Play() + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent.HornSND ) then return end + ent.HornSND:Stop() + end + weapon.OnSelect = function( ent ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( false ) + end + end + weapon.OnDeselect = function( ent ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( true ) + end + end + weapon.OnThink = function( ent, active ) + ent:SetHeat( self.WEAPONS[1][ 1 ]._CurHeat or 0 ) + end + self:AddWeapon( weapon ) +end + diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/init.lua new file mode 100644 index 0000000..a8f277f --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/init.lua @@ -0,0 +1,109 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(-6.5,12,16.5), Angle(0,-95,-8) ) + local PassengerSeat = self:AddPassengerSeat( Vector(13,-12,22), Angle(0,-85,15) ) + local PassengerSeat1 = self:AddPassengerSeat( Vector(-22,12,22), Angle(0,-90,15) ) + local PassengerSeat2 = self:AddPassengerSeat( Vector(-22,-12,22), Angle(0,-90,15) ) + + local DoorHandler = self:AddDoorHandler( "left_door", Vector(10,21,35), Angle(0,0,0), Vector(-10,-3,-12), Vector(20,6,12), Vector(-10,-15,-12), Vector(20,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( DriverSeat ) + + local DoorHandler = self:AddDoorHandler( "right_door", Vector(20,-21,35), Angle(0,180,0), Vector(-10,-3,-12), Vector(20,6,12), Vector(-10,-15,-12), Vector(20,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( PassengerSeat ) + + local DoorHandler = self:AddDoorHandler( "rear_left_door", Vector(-20,21,35), Angle(0,0,0), Vector(-8,-3,-12), Vector(18,6,12), Vector(-8,-15,-12), Vector(18,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( PassengerSeat1 ) + + local DoorHandler = self:AddDoorHandler( "rear_right_door", Vector(-10,-21,35), Angle(0,180,0), Vector(-8,-3,-12), Vector(18,6,12), Vector(-8,-15,-12), Vector(18,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( PassengerSeat2 ) + + local DoorHandler = self:AddDoorHandler( "hatch", Vector(27.71,0,53), Angle(0,0,0), Vector(-5,-23,-5), Vector(5,23,7), Vector(-5,-23,-10), Vector(18,23,-3) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_close.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_open.wav" ) + + local FuelCap = self:AddDoorHandler( "fuel_cap", Vector(46.84,-15.13,45.95), Angle(0,90,-70), Vector(-2,-0.5,-2), Vector(2,2,2), Vector(-2,-3,-2), Vector(2,2,5) ) + FuelCap:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + FuelCap:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + + self:AddEngine( Vector(-56,0,37.5) ) + + local FuelTank = self:AddFuelTank( Vector(-57.06,0,18.92), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL ) + FuelTank:SetDoorHandler( FuelCap ) + + local WheelModel = "models/diggercars/kubel/kubelwagen_wheel.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + }, + Wheels = { + self:AddWheel( { + pos = Vector(53,-23,17.5), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(53,23,17.5), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(-46.5,-25,15.5), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(-46.5,25,15.5), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + self:AddTrailerHitch( Vector(-74,0,16.5), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/shared.lua new file mode 100644 index 0000000..f74d7ad --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen/shared.lua @@ -0,0 +1,98 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "Kuebelwagen" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Military" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/kubel/kubelwagen.mdl" + +ENT.AITEAM = 1 + +ENT.MaxVelocity = 1200 + +ENT.EngineTorque = 150 +ENT.EngineCurve = 0.25 + +ENT.TransGears = 4 +ENT.TransGearsReverse = 1 + +ENT.HornSound = "lvs/horn1.wav" +ENT.HornPos = Vector(40,0,35) + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/kuebelwagen/eng_idle_loop.wav", + Volume = 0.5, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/kuebelwagen/eng_loop.wav", + Volume = 1, + Pitch = 100, + PitchMul = 100, + SoundLevel = 75, + UseDoppler = true, + }, +} + +ENT.Lights = { + { + Trigger = "main", + Sprites = { + { pos = Vector(-71.74,20.47,40.6), colorG = 0, colorB = 0, colorA = 150 }, + { pos = Vector(-71.74,-20.47,40.6), colorG = 0, colorB = 0, colorA = 150 }, + }, + ProjectedTextures = { + { pos = Vector(70.57,25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + { pos = Vector(70.57,-25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + }, + }, + { + Trigger = "high", + ProjectedTextures = { + { pos = Vector(70.57,25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + { pos = Vector(70.57,-25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + }, + }, + { + Trigger = "main+high", + SubMaterialID = 1, + Sprites = { + { pos = Vector(70.57,25.1,33.49), colorB = 200, colorA = 150 }, + { pos = Vector(70.57,-25.1,33.49), colorB = 200, colorA = 150 }, + }, + }, + { + Trigger = "brake", + SubMaterialID = 2, + Sprites = { + { pos = Vector(-71.36,20.51,39.48), colorG = 0, colorB = 0, colorA = 150 }, + { pos = Vector(-71.36,-20.51,39.48), colorG = 0, colorB = 0, colorA = 150 }, + } + }, + { + Trigger = "fog", + SubMaterialID = 3, + Sprites = { + { pos = Vector(33.15,-25.63,48.61), colorB = 200, colorA = 150 }, + }, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-72.3,15.49,17.61), + ang = Angle(0,180,0), + }, +} diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/cl_init.lua new file mode 100644 index 0000000..3c2b7de --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/cl_init.lua @@ -0,0 +1,68 @@ +include("shared.lua") + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + + if pod ~= self:GetGunnerSeat() then + return LVS:CalcView( self, ply, pos, angles, fov, pod ) + end + + if pod:GetThirdPersonMode() then + local view = {} + view.fov = fov + view.drawviewer = true + + local mn = self:OBBMins() + local mx = self:OBBMaxs() + local radius = ( mn - mx ):Length() + local radius = radius + radius * pod:GetCameraDistance() + + local clamped_angles = pod:WorldToLocalAngles( angles ) + clamped_angles.p = math.max( clamped_angles.p, -20 ) + clamped_angles = pod:LocalToWorldAngles( clamped_angles ) + + local StartPos = pos + local EndPos = StartPos - clamped_angles:Forward() * radius + clamped_angles:Up() * (radius * pod:GetCameraHeight()) + + local WallOffset = 4 + + local tr = util.TraceHull( { + start = StartPos, + endpos = EndPos, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.angles = angles + Angle(5,0,0) + view.origin = tr.HitPos + pod:GetUp() * 65 + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view + end + + local ZoomAttach = self:GetAttachment( self:LookupAttachment( "zoom" ) ) + local EyeAttach = self:GetAttachment( self:LookupAttachment( "eye" ) ) + + if ZoomAttach and EyeAttach then + local ZOOM = ply:lvsKeyDown( "ZOOM" ) + + local TargetZoom = ZOOM and 1 or 0 + + pod.smZoom = pod.smZoom and (pod.smZoom + (TargetZoom - pod.smZoom) * RealFrameTime() * 12) or 0 + + local Zoom = pod.smZoom + local invZoom = 1 - Zoom + + pos = ZoomAttach.Pos * Zoom + EyeAttach.Pos * invZoom + end + + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/init.lua new file mode 100644 index 0000000..66978f4 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/init.lua @@ -0,0 +1,119 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(-6.5,12,16.5), Angle(0,-95,-8) ) + local PassengerSeat = self:AddPassengerSeat( Vector(13,-12,22), Angle(0,-85,15) ) + + local GunnerSeat = self:AddPassengerSeat( Vector(-30,0,18), Angle(0,-90,0) ) + self:SetGunnerSeat( GunnerSeat ) + + local PassengerSeat2 = self:AddPassengerSeat( Vector(-22,-12,22), Angle(0,-90,15) ) + + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMGf = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sdkfz250/mg_loop.wav" ) + self.SNDTurretMGf:SetSoundLevel( 95 ) + self.SNDTurretMGf:SetParent( self, ID ) + + local DoorHandler = self:AddDoorHandler( "left_door", Vector(10,21,35), Angle(0,0,0), Vector(-10,-3,-12), Vector(20,6,12), Vector(-10,-15,-12), Vector(20,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( DriverSeat ) + + local DoorHandler = self:AddDoorHandler( "right_door", Vector(20,-21,35), Angle(0,180,0), Vector(-10,-3,-12), Vector(20,6,12), Vector(-10,-15,-12), Vector(20,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( PassengerSeat ) + + local DoorHandler = self:AddDoorHandler( "rear_left_door", Vector(-20,21,35), Angle(0,0,0), Vector(-8,-3,-12), Vector(18,6,12), Vector(-8,-15,-12), Vector(18,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( GunnerSeat ) + + local DoorHandler = self:AddDoorHandler( "rear_right_door", Vector(-10,-21,35), Angle(0,180,0), Vector(-8,-3,-12), Vector(18,6,12), Vector(-8,-15,-12), Vector(18,30,12) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + DoorHandler:LinkToSeat( PassengerSeat2 ) + + local DoorHandler = self:AddDoorHandler( "hatch", Vector(27.71,0,53), Angle(0,0,0), Vector(-5,-23,-5), Vector(5,23,7), Vector(-5,-23,-10), Vector(18,23,-3) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_close.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_open.wav" ) + + local FuelCap = self:AddDoorHandler( "fuel_cap", Vector(46.84,-15.13,45.95), Angle(0,90,-70), Vector(-2,-0.5,-2), Vector(2,2,2), Vector(-2,-3,-2), Vector(2,2,5) ) + FuelCap:SetSoundOpen( "lvs/vehicles/generic/car_door_open.wav" ) + FuelCap:SetSoundClose( "lvs/vehicles/generic/car_door_close.wav" ) + + self:AddEngine( Vector(-56,0,37.5) ) + + local FuelTank = self:AddFuelTank( Vector(-57.06,0,18.92), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL ) + FuelTank:SetDoorHandler( FuelCap ) + + local WheelModel = "models/diggercars/kubel/kubelwagen_wheel.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + }, + Wheels = { + self:AddWheel( { + pos = Vector(53,-23,17.5), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(53,23,17.5), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(-46.5,-25,15.5), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(-46.5,25,15.5), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + self:AddTrailerHitch( Vector(-74,0,16.5), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/shared.lua new file mode 100644 index 0000000..efd0616 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodkuebelwagen_mg/shared.lua @@ -0,0 +1,303 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "Kuebelwagen MG34" +ENT.Author = "Digger" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Military" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/kubel/kubelwagen_mg.mdl" + +ENT.AITEAM = 1 + +ENT.MaxVelocity = 1200 + +ENT.EngineTorque = 150 +ENT.EngineCurve = 0.25 + +ENT.TransGears = 4 +ENT.TransGearsReverse = 1 + +ENT.HornSound = "lvs/horn1.wav" +ENT.HornPos = Vector(40,0,35) + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/kuebelwagen/eng_idle_loop.wav", + Volume = 0.5, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/kuebelwagen/eng_loop.wav", + Volume = 1, + Pitch = 100, + PitchMul = 100, + SoundLevel = 75, + UseDoppler = true, + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "GunnerSeat" ) +end + +ENT.Lights = { + { + Trigger = "main", + Sprites = { + { pos = Vector(-71.74,20.47,40.6), colorG = 0, colorB = 0, colorA = 150 }, + { pos = Vector(-71.74,-20.47,40.6), colorG = 0, colorB = 0, colorA = 150 }, + }, + ProjectedTextures = { + { pos = Vector(70.57,25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + { pos = Vector(70.57,-25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + }, + }, + { + Trigger = "high", + ProjectedTextures = { + { pos = Vector(70.57,25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + { pos = Vector(70.57,-25.1,33.49), ang = Angle(0,0,0), colorB = 200, colorA = 150, shadows = true }, + }, + }, + { + Trigger = "main+high", + SubMaterialID = 1, + Sprites = { + { pos = Vector(70.57,25.1,33.49), colorB = 200, colorA = 150 }, + { pos = Vector(70.57,-25.1,33.49), colorB = 200, colorA = 150 }, + }, + }, + { + Trigger = "brake", + SubMaterialID = 2, + Sprites = { + { pos = Vector(-71.36,20.51,39.48), colorG = 0, colorB = 0, colorA = 150 }, + { pos = Vector(-71.36,-20.51,39.48), colorG = 0, colorB = 0, colorA = 150 }, + } + }, + { + Trigger = "fog", + SubMaterialID = 3, + Sprites = { + { pos = Vector(33.15,-25.63,48.61), colorB = 200, colorA = 150 }, + }, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-72.3,15.49,17.61), + ang = Angle(0,180,0), + }, +} + +function ENT:InitWeapons() + self:AddGunnerWeapons() +end + +function ENT:GunnerInRange( Dir ) + local pod = self:GetGunnerSeat() + + if IsValid( pod ) and not pod:GetThirdPersonMode() then + local ply = pod:GetDriver() + + if IsValid( ply ) and ply:lvsKeyDown( "ZOOM" ) then + return true + end + end + + return self:AngleBetweenNormal( self:GetForward(), Dir ) < 40 +end + +function ENT:AddGunnerWeapons() + local COLOR_RED = Color(255,0,0,255) + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not base:GunnerInRange( ent:GetAimVector() ) then + + if not IsValid( base.SNDTurretMGf ) then return true end + + base.SNDTurretMGf:Stop() + + return true + end + + local Muzzle = base:GetAttachment( base:LookupAttachment( "muzzle" ) ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + + local ply = ent:GetDriver() + + if IsValid( ply ) and ply:lvsKeyDown( "ZOOM" ) then + local pod = ply:GetVehicle() + + if IsValid( pod ) and not pod:GetThirdPersonMode() then + bullet.Dir = Muzzle.Ang:Forward() + else + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + end + else + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + end + + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ply + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + base:PlayAnimation( "shot" ) + + if not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.StartAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.FinishAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Stop() + end + weapon.OnThink = function( ent, active ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not ent:GetAI() and not IsValid( ent:GetDriver() ) then + base:SetPoseParameter("f_pitch", 15 ) + base:SetPoseParameter("f_yaw", 0 ) + + return + end + + local Angles = base:WorldToLocalAngles( ent:GetAimVector():Angle() ) + Angles:Normalize() + + base:SetPoseParameter("f_yaw", -Angles.y ) + base:SetPoseParameter("f_pitch", -Angles.p ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = base:GunnerInRange( ent:GetAimVector() ) and COLOR_WHITE or COLOR_RED + + local pod = ply:GetVehicle() + + if not IsValid( pod ) then return end + + if not ply:lvsKeyDown( "ZOOM" ) or pod:GetThirdPersonMode() then + base:PaintCrosshairCenter( Pos2D, Col ) + end + + base:LVSPaintHitMarker( Pos2D ) + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/overheat.wav") + end + self:AddWeapon( weapon, 3 ) +end + + +function ENT:CalcMainActivityPassenger( ply ) + local GunnerSeat = self:GetGunnerSeat() + + if not IsValid( GunnerSeat ) then return end + + if GunnerSeat:GetDriver() ~= ply then return end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + ply.CalcIdeal = ACT_STAND + ply.CalcSeqOverride = ply:LookupSequence( "cwalk_revolver" ) + + return ply.CalcIdeal, ply.CalcSeqOverride +end + +function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed ) + ply:SetPlaybackRate( 1 ) + + if CLIENT then + local GunnerSeat = self:GetGunnerSeat() + + if ply == self:GetDriver() then + ply:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() ) + ply:InvalidateBoneCache() + end + + if IsValid( GunnerSeat ) and GunnerSeat:GetDriver() == ply then + local Pitch = math.Remap( self:GetPoseParameter( "f_pitch" ),0,1,-15,-5) + local Yaw = math.Remap( self:GetPoseParameter( "f_yaw" ),0,1,-25,25) + + ply:SetPoseParameter( "aim_pitch", Pitch * 1.5 ) + ply:SetPoseParameter( "aim_yaw", Yaw * 1.5 ) + + ply:SetPoseParameter( "head_pitch", -Pitch * 2 ) + ply:SetPoseParameter( "head_yaw", -Yaw * 3 ) + + ply:SetPoseParameter( "move_x", 0 ) + ply:SetPoseParameter( "move_y", 0 ) + + ply:InvalidateBoneCache() + end + + GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + end + + return false +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_init.lua new file mode 100644 index 0000000..f97a959 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_init.lua @@ -0,0 +1,38 @@ +include("shared.lua") +include("sh_tracks.lua") +include("sh_turret.lua") +include("cl_optics.lua") +include("cl_tankview.lua") + +local switch = Material("lvs/weapons/change_ammo.png") +local AP = Material("lvs/weapons/bullet_ap.png") +local HE = Material("lvs/weapons/tank_cannon.png") +function ENT:DrawWeaponIcon( PodID, ID, x, y, width, height, IsSelected, IconColor ) + local Icon = self:GetUseHighExplosive() and HE or AP + + surface.SetMaterial( Icon ) + surface.DrawTexturedRect( x, y, width, height ) + + local ply = LocalPlayer() + + if not IsValid( ply ) or self:GetSelectedWeapon() ~= 2 then return end + + surface.SetMaterial( switch ) + surface.DrawTexturedRect( x + width + 5, y + 7, 24, 24 ) + + local buttonCode = ply:lvsGetControls()[ "CAR_SWAP_AMMO" ] + + if not buttonCode then return end + + local KeyName = input.GetKeyName( buttonCode ) + + if not KeyName then return end + + draw.DrawText( KeyName, "DermaDefault", x + width + 17, y + height * 0.5 + 7, Color(0,0,0,IconColor.a), TEXT_ALIGN_CENTER ) +end + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/sherman/engine_start.wav", 75, 100, LVS.EngineVolume ) + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_optics.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_optics.lua new file mode 100644 index 0000000..edd3fc5 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_optics.lua @@ -0,0 +1,57 @@ + +ENT.OpticsFov = 30 +ENT.OpticsEnable = true +ENT.OpticsZoomOnly = true +ENT.OpticsFirstPerson = true +ENT.OpticsThirdPerson = false +ENT.OpticsPodIndex = { + [1] = true, +} + +ENT.OpticsCrosshairMaterial = Material( "lvs/circle_filled.png" ) +ENT.OpticsCrosshairColor = Color(0,0,0,150) +ENT.OpticsCrosshairSize = 4 + +local axis = Material( "lvs/axis.png" ) +local sight = Material( "lvs/shermansights.png" ) +local scope = Material( "lvs/scope_viewblocked.png" ) + +function ENT:PaintOpticsCrosshair( Pos2D ) + surface.SetMaterial( sight ) + surface.SetDrawColor( 0, 0, 0, 150 ) + surface.DrawTexturedRect( Pos2D.x - 210, Pos2D.y - 23, 420, 420 ) + + surface.SetMaterial( axis ) + surface.SetDrawColor( 255, 255, 255, 25 ) + surface.DrawTexturedRect( Pos2D.x - 7, Pos2D.y - 7, 16, 16 ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - 8, Pos2D.y - 8, 16, 16 ) +end + +function ENT:PaintOptics( Pos2D, Col, PodIndex, Type ) + local size = self.OpticsCrosshairSize + + surface.SetMaterial( self.OpticsCrosshairMaterial ) + surface.SetDrawColor( self.OpticsCrosshairColor ) + surface.DrawTexturedRect( Pos2D.x - size * 0.5, Pos2D.y - size * 0.5, size, size ) + + if Type == 1 then + self:DrawRotatedText( "MG", Pos2D.x + 30, Pos2D.y + 10, "LVS_FONT_PANEL", Color(0,0,0,220), 0) + else + self:DrawRotatedText( Type == 3 and "HE" or "AP", Pos2D.x + 30, Pos2D.y + 10, "LVS_FONT_PANEL", Color(0,0,0,220), 0) + end + + local ScrW = ScrW() + local ScrH = ScrH() + + local diameter = ScrH + 64 + local radius = diameter * 0.5 + + surface.SetMaterial( scope ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - radius, Pos2D.y - radius, diameter, diameter ) + + -- black bar left + right + surface.DrawRect( 0, 0, Pos2D.x - radius, ScrH ) + surface.DrawRect( Pos2D.x + radius, 0, Pos2D.x - radius, ScrH ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_tankview.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_tankview.lua new file mode 100644 index 0000000..611b3c5 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/cl_tankview.lua @@ -0,0 +1,12 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + if ply == self:GetDriver() and not pod:GetThirdPersonMode() then + local vieworigin, found = self:GetTurretViewOrigin() + + if found then pos = vieworigin end + end + + return pos, angles, fov +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/init.lua new file mode 100644 index 0000000..bef865e --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/init.lua @@ -0,0 +1,102 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "sh_tracks.lua" ) +AddCSLuaFile( "sh_turret.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_optics.lua" ) +AddCSLuaFile( "cl_tankview.lua" ) +include("shared.lua") +include("sh_tracks.lua") +include("sh_turret.lua") + +function ENT:OnSpawn( PObj ) + local ID = self:LookupAttachment( "machinegun" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMGf = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sherman/mg_loop.wav", "lvs/vehicles/sherman/mg_loop_interior.wav" ) + self.SNDTurretMGf:SetSoundLevel( 95 ) + self.SNDTurretMGf:SetParent( self, ID ) + + local ID = self:LookupAttachment( "turret_machinegun" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMG = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sherman/mg_loop.wav", "lvs/vehicles/sherman/mg_loop_interior.wav" ) + self.SNDTurretMG:SetSoundLevel( 95 ) + self.SNDTurretMG:SetParent( self, ID ) + + local ID = self:LookupAttachment( "turret_cannon" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurret = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sherman/cannon_fire.wav", "lvs/vehicles/sherman/cannon_fire.wav" ) + self.SNDTurret:SetSoundLevel( 95 ) + self.SNDTurret:SetParent( self, ID ) + + local DriverSeat = self:AddDriverSeat( Vector(0,0,60), Angle(0,-90,0) ) + DriverSeat.HidePlayer = true + + local GunnerSeat = self:AddPassengerSeat( Vector(88,-20,32), Angle(0,-90,0) ) + GunnerSeat.HidePlayer = true + self:SetGunnerSeat( GunnerSeat ) + + self:AddEngine( Vector(-79.66,0,70), Angle(0,180,0) ) + self:AddFuelTank( Vector(-75,0,40), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL, Vector(0,-20,-18),Vector(18,20,18) ) + + -- extra front plate left + self:AddArmor( Vector(82,20.5,65), Angle(-36,0,0), Vector(-1.2,-11,-12), Vector(1.2,11,12), 1200, self.FrontArmorExtra ) + -- extra front plate right + self:AddArmor( Vector(82,-20.5,65), Angle(-36,0,0), Vector(-1.2,-11,-12), Vector(1.2,11,12), 1200, self.FrontArmorExtra ) + + -- front upper wedge + self:AddArmor( Vector(70,0,66), Angle(0,0,0), Vector(-26,-48,-16), Vector(26,48,16), 1000, self.FrontArmor ) + + -- transmission rib left + self:AddArmor( Vector(102,12.6,35), Angle(0,0,0), Vector(-16,-1.5,-19), Vector(16,1.5,19), 1200, self.FrontArmorExtra ) + -- transmission rib right + self:AddArmor( Vector(102,-11.8,35), Angle(0,0,0), Vector(-16,-1.5,-19), Vector(16,1.5,19), 1200, self.FrontArmorExtra ) + + -- transmission + self:AddArmor( Vector(110,1,34), Angle(0,0,0), Vector(-15,-32,-18), Vector(15,32,18), 1000, self.FrontArmor ) + + -- extra ammo armor right front + self:AddArmor( Vector(50,-51,63), Angle(0,0,0), Vector(-14,-1,-9), Vector(14,1,9), 1200, self.FrontArmorExtra ) + -- extra ammo armor right rear + self:AddArmor( Vector(1,-51,63), Angle(0,0,0), Vector(-14,-1,-10), Vector(14,1,10), 1200, self.FrontArmorExtra ) + -- extra ammo armor left + self:AddArmor( Vector(50,51,63), Angle(0,0,0), Vector(-14,-1,-9), Vector(14,1,9), 1200, self.FrontArmorExtra ) + + -- rear down + self:AddArmor( Vector(-85,0,34), Angle(0,0,0), Vector(-6,-29,-18), Vector(6,29,18), 800, self.SideArmor ) + -- rear up + self:AddArmor( Vector(-105,0,63), Angle(0,0,0), Vector(-3,-48,-13), Vector(3,48,13), 800, self.SideArmor ) + -- rear top + self:AddArmor( Vector(-69,0,66), Angle(0,0,0), Vector(-33,-48,-10), Vector(33,48,10), 600, self.RoofArmor ) + + -- roof mid + self:AddArmor( Vector(4,0,72), Angle(0,0,0), Vector(-40,-48,-4), Vector(40,48,4), 600, self.RoofArmor ) + + -- turret neck + self:AddArmor( Vector(2,0,79), Angle(0,0,0), Vector(-42,-44,-3), Vector(42,44,3), 1200, self.FrontArmorExtra ) + + -- right up + self:AddArmor( Vector(-6,-49,63), Angle(0,0,0), Vector(-102,-1,-13), Vector(102,1,13), 800, self.SideArmor ) + -- left up + self:AddArmor( Vector(-6,49.5,63), Angle(0,0,0), Vector(-102,-1.5,-13), Vector(102,1.5,13), 800, self.SideArmor ) + + -- right down + self:AddArmor( Vector(2,-30,34), Angle(0,0,0), Vector(-93,-2,-18), Vector(93,2,18), 800, self.SideArmor ) + -- left down + self:AddArmor( Vector(2,31,34), Angle(0,0,0), Vector(-93,-1,-18), Vector(93,1,18), 800, self.SideArmor ) + + -- shelf + self:AddArmor( Vector(-29,0,51), Angle(0,0,0), Vector(-73,-48,-1), Vector(73,48,1), 600, self.RoofArmor ) + -- bottom + self:AddArmor( Vector(8,0,17), Angle(0,0,0), Vector(-87,-29,-1), Vector(87,29,1), 600, self.RoofArmor ) + + -- turret + local TurretArmor = self:AddArmor( Vector(2,0,95), Angle(0,0,0), Vector(-42,-44,-13), Vector(42,44,13), 1500, self.TurretArmor ) + TurretArmor:SetLabel( "Turret" ) + self:SetTurretArmor( TurretArmor ) + + -- ammo rack weakspot + self:AddAmmoRack( Vector(0,40,64), Vector(-5,0,70), Angle(0,0,0), Vector(-18,-6,-9), Vector(18,6,9) ) + self:AddAmmoRack( Vector(0,-40,64), Vector(-5,0,70), Angle(0,0,0), Vector(-18,-6,-9), Vector(18,6,9) ) + + -- trailer hitch + self:AddTrailerHitch( Vector(-100,0,24), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/sh_tracks.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/sh_tracks.lua new file mode 100644 index 0000000..adc743b --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/sh_tracks.lua @@ -0,0 +1,183 @@ + +if SERVER then + ENT.PivotSteerEnable = true + ENT.PivotSteerByBrake = true + ENT.PivotSteerWheelRPM = 40 + + ENT.TrackGibs = { + ["left"] = { + { + mdl = "models/blu/tanks/sherman_tracks_ragdoll.mdl", + pos = Vector(0,41.3,3), + ang = Angle(-90,-90,0), + }, + }, + ["right"] = { + { + mdl = "models/blu/tanks/sherman_tracks_ragdoll.mdl", + pos = Vector(0,-41.3,3), + ang = Angle(-90,-90,0), + }, + } + } + + function ENT:OnLeftTrackRepaired() + self:SetBodygroup(2,0) + end + + function ENT:OnLeftTrackDestroyed() + self:SetBodygroup(2,1) + end + + function ENT:OnRightTrackRepaired() + self:SetBodygroup(3,0) + end + + function ENT:OnRightTrackDestroyed() + self:SetBodygroup(3,1) + end + + function ENT:TracksCreate( PObj ) + self:CreateTrackPhysics( "models/blu/tanks/sherman_tracks_col.mdl" ) + + local WheelModel = "models/props_vehicles/tire001b_truck.mdl" + + local L1 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(100,42,55), mdl = WheelModel } ) + local L2 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(70,42,35), mdl = WheelModel } ) + local L3 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(35,42,40), mdl = WheelModel } ) + local L4 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(0,42,45), mdl = WheelModel } ) + local L5 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(-35,42,45), mdl = WheelModel } ) + local L6 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(-70,42,45), mdl = WheelModel } ) + local LeftWheelChain = self:CreateWheelChain( {L1, L2, L3, L4, L5, L6} ) + self:SetTrackDriveWheelLeft( L4 ) + + local R1 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(100,-42,55), mdl = WheelModel } ) + local R2 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(70,-42,35), mdl = WheelModel } ) + local R3 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(35,-42,40), mdl = WheelModel } ) + local R4 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(0,-42,45), mdl = WheelModel } ) + local R5 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(-35,-42,45), mdl = WheelModel } ) + local R6 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(-70,-42,45), mdl = WheelModel} ) + local RightWheelChain = self:CreateWheelChain( {R1, R2, R3, R4, R5, R6} ) + self:SetTrackDriveWheelRight( R4 ) + + local LeftTracksArmor = self:AddArmor( Vector(12,43,22), Angle(0,0,0), Vector(-121,-10,-28), Vector(121,10,28), 500, self.FrontArmor ) + self:SetTrackArmorLeft( LeftTracksArmor, LeftWheelChain ) + + local RightTracksArmor = self:AddArmor( Vector(12,-41,22), Angle(0,0,0), Vector(-121,-10,-28), Vector(121,10,28), 500, self.FrontArmor ) + self:SetTrackArmorRight( RightTracksArmor, RightWheelChain ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R1, L1, R2, L2 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R3, L3, L4, R4 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_REAR, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R5, L5, R6, L6 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + end +else + ENT.TrackSystemEnable = true + + ENT.TrackScrollTexture = "models/blu/track_sherman" + ENT.ScrollTextureData = { + ["$alphatest"] = "1", + ["$translate"] = "[0.0 0.0 0.0]", + ["$colorfix"] = "{255 255 255}", + ["Proxies"] = { + ["TextureTransform"] = { + ["translateVar"] = "$translate", + ["centerVar"] = "$center", + ["resultVar"] = "$basetexturetransform", + }, + ["Equals"] = { + ["srcVar1"] = "$colorfix", + ["resultVar"] = "$color", + } + } + } + + ENT.TrackLeftSubMaterialID = 1 + ENT.TrackLeftSubMaterialMul = Vector(0,0.0065,0) + + ENT.TrackRightSubMaterialID = 2 + ENT.TrackRightSubMaterialMul = Vector(0,0.0065,0) + + ENT.TrackPoseParameterLeft = "spin_wheels_left" + ENT.TrackPoseParameterLeftMul = -1.252 + + ENT.TrackPoseParameterRight = "spin_wheels_right" + ENT.TrackPoseParameterRightMul = -1.252 + + ENT.TrackSounds = "lvs/vehicles/sherman/tracks_loop.wav" + ENT.TrackHull = Vector(20,20,20) + ENT.TrackData = {} + for i = 1, 6 do + for n = 0, 1 do + local LR = n == 0 and "l" or "r" + local LeftRight = n == 0 and "left" or "right" + local data = { + Attachment = { + name = "vehicle_suspension_"..LR.."_"..i, + toGroundDistance = 41, + traceLength = 100, + }, + PoseParameter = { + name = "suspension_"..LeftRight.."_"..i, + rangeMultiplier = -1, + lerpSpeed = 25, + } + } + table.insert( ENT.TrackData, data ) + end + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/sh_turret.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/sh_turret.lua new file mode 100644 index 0000000..7df2548 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/sh_turret.lua @@ -0,0 +1,23 @@ + +include("entities/lvs_tank_wheeldrive/modules/sh_turret.lua") +include("entities/lvs_tank_wheeldrive/modules/sh_turret_ballistics.lua") + +ENT.TurretBallisticsPredicted = false -- sherman optics are not adjustable + +ENT.TurretBallisticsProjectileVelocity = ENT.ProjectileVelocityCoaxial +ENT.TurretBallisticsMuzzleAttachment = "turret_machinegun" +ENT.TurretBallisticsViewAttachment = "sight" + +ENT.TurretAimRate = 25 + +ENT.TurretRotationSound = "vehicles/tank_turret_loop1.wav" + +ENT.TurretPitchPoseParameterName = "turret_pitch" +ENT.TurretPitchMin = -15 +ENT.TurretPitchMax = 15 +ENT.TurretPitchMul = 1 +ENT.TurretPitchOffset = 0 + +ENT.TurretYawPoseParameterName = "turret_yaw" +ENT.TurretYawMul = 1 +ENT.TurretYawOffset = 0 diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/shared.lua new file mode 100644 index 0000000..acc1d8b --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman/shared.lua @@ -0,0 +1,433 @@ + +ENT.Base = "lvs_tank_wheeldrive" + +ENT.PrintName = "Sherman" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Tanks" +ENT.VehicleSubCategory = "Medium" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/blu/tanks/sherman_lvs.mdl" +ENT.MDL_DESTROYED = "models/blu/tanks/sherman_gib_1.mdl" + +ENT.GibModels = { + "models/blu/tanks/sherman_gib_2.mdl", + "models/blu/tanks/sherman_gib_3.mdl", + "models/blu/tanks/sherman_gib_4.mdl", + "models/blu/tanks/sherman_gib_6.mdl", + "models/blu/tanks/sherman_gib_7.mdl", +} + +ENT.AITEAM = 2 + +ENT.MaxHealth = 1200 + +--damage system +ENT.DSArmorIgnoreForce = 3000 +ENT.CannonArmorPenetration = 9200 +ENT.FrontArmor = 2000 +ENT.FrontArmorExtra = 4500 +ENT.SideArmor = 800 +ENT.TurretArmor = 3000 +ENT.RoofArmor = 100 + +-- ballistics +ENT.ProjectileVelocityCoaxial = 15000 +ENT.ProjectileVelocityHighExplosive = 13000 +ENT.ProjectileVelocityArmorPiercing = 16000 + +ENT.SteerSpeed = 1 +ENT.SteerReturnSpeed = 2 + +ENT.PhysicsWeightScale = 2 +ENT.PhysicsDampingSpeed = 1000 +ENT.PhysicsInertia = Vector(6000,6000,1500) + +ENT.MaxVelocity = 450 +ENT.MaxVelocityReverse = 150 + +ENT.EngineCurve = 0.1 +ENT.EngineTorque = 200 + +ENT.TransMinGearHoldTime = 0.1 +ENT.TransShiftSpeed = 0 + +ENT.TransGears = 3 +ENT.TransGearsReverse = 1 + +ENT.MouseSteerAngle = 45 + +ENT.lvsShowInSpawner = true + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/sherman/eng_idle_loop.wav", + Volume = 1, + Pitch = 70, + PitchMul = 30, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/sherman/eng_loop.wav", + Volume = 1, + Pitch = 20, + PitchMul = 90, + SoundLevel = 85, + SoundType = LVS.SOUNDTYPE_NONE, + UseDoppler = true, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-90.47,17.01,52.77), + ang = Angle(180,0,0) + }, + { + pos = Vector(-90.47,-17.01,52.77), + ang = Angle(180,0,0) + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "GunnerSeat" ) + self:AddDT( "Bool", "UseHighExplosive" ) +end + +function ENT:InitWeapons() + local COLOR_WHITE = Color(255,255,255,255) + + -- coaxial machinegun + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "turret_machinegun" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = Muzzle.Ang:Forward() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = ent.ProjectileVelocityCoaxial + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + end + weapon.StartAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + ent.SNDTurretMG:Play() + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + ent.SNDTurretMG:Stop() + end + weapon.OnOverheat = function( ent ) ent:EmitSound("lvs/overheat.wav") end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "turret_machinegun" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + ent:PaintCrosshairCenter( MuzzlePos2D, COLOR_WHITE ) + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + weapon.OnSelect = function( ent ) + ent:TurretUpdateBallistics( ent.ProjectileVelocityCoaxial, "turret_machinegun" ) + end + self:AddWeapon( weapon ) + + + + local weapon = {} + weapon.Icon = true + weapon.Ammo = 40 + weapon.Delay = 2.5 + weapon.HeatRateUp = 1 + weapon.HeatRateDown = 0.4 + weapon.OnThink = function( ent ) + if ent:GetSelectedWeapon() ~= 2 then return end + + local ply = ent:GetDriver() + + if not IsValid( ply ) then return end + + local SwitchType = ply:lvsKeyDown( "CAR_SWAP_AMMO" ) + + if ent._oldSwitchType ~= SwitchType then + ent._oldSwitchType = SwitchType + + if SwitchType then + ent:SetUseHighExplosive( not ent:GetUseHighExplosive() ) + ent:EmitSound("lvs/vehicles/sherman/cannon_unload.wav", 75, 100, 1, CHAN_WEAPON ) + ent:SetHeat( 1 ) + ent:SetOverheated( true ) + + if ent:GetUseHighExplosive() then + ent:TurretUpdateBallistics( ent.ProjectileVelocityHighExplosive ) + else + ent:TurretUpdateBallistics( ent.ProjectileVelocityArmorPiercing ) + end + end + end + end + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "turret_cannon" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = Muzzle.Ang:Forward() + bullet.Spread = Vector(0,0,0) + bullet.EnableBallistics = true + + if ent:GetUseHighExplosive() then + bullet.Force = 500 + bullet.HullSize = 15 + bullet.Damage = 250 + bullet.SplashDamage = 750 + bullet.SplashDamageRadius = 200 + bullet.SplashDamageEffect = "lvs_bullet_impact_explosive" + bullet.SplashDamageType = DMG_BLAST + bullet.Velocity = ent.ProjectileVelocityHighExplosive + else + bullet.Force = ent.CannonArmorPenetration + bullet.HullSize = 0 + bullet.Damage = 1000 + bullet.Velocity = ent.ProjectileVelocityArmorPiercing + end + + bullet.TracerName = "lvs_tracer_cannon" + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + local PhysObj = ent:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:ApplyForceOffset( -bullet.Dir * 150000, bullet.Src ) + end + + ent:TakeAmmo( 1 ) + + if not IsValid( ent.SNDTurret ) then return end + + ent.SNDTurret:PlayOnce( 100 + math.cos( CurTime() + ent:EntIndex() * 1337 ) * 5 + math.Rand(-1,1), 1 ) + + ent:EmitSound("lvs/vehicles/sherman/cannon_reload.wav", 75, 100, 1, CHAN_WEAPON ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "turret_cannon" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + if ent:GetUseHighExplosive() then + ent:PaintCrosshairSquare( MuzzlePos2D, COLOR_WHITE ) + else + ent:PaintCrosshairOuter( MuzzlePos2D, COLOR_WHITE ) + end + + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + weapon.OnSelect = function( ent ) + if ent:GetUseHighExplosive() then + ent:TurretUpdateBallistics( ent.ProjectileVelocityHighExplosive, "turret_cannon" ) + else + ent:TurretUpdateBallistics( ent.ProjectileVelocityArmorPiercing, "turret_cannon" ) + end + end + self:AddWeapon( weapon ) + + + -- smoke + local weapon = {} + weapon.Icon = Material("lvs/weapons/smoke_launcher.png") + weapon.Ammo = 3 + weapon.Delay = 1 + weapon.HeatRateUp = 1 + weapon.HeatRateDown = 0.05 + weapon.Attack = function( ent ) + ent:TakeAmmo( 1 ) + + local grenade = ents.Create( "lvs_item_smoke" ) + grenade:SetPos( ent:LocalToWorld( Vector(-92,0,51) ) ) + grenade:SetAngles( ent:GetAngles() ) + grenade:SetParent( ent ) + grenade:Spawn() + grenade:Activate() + grenade:Enable() + grenade:SetColor( Color(0,0,0,0) ) + end + self:AddWeapon( weapon ) + + + -- turret rotation disabler + local weapon = {} + weapon.Icon = Material("lvs/weapons/tank_noturret.png") + weapon.Ammo = -1 + weapon.Delay = 0 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 0 + weapon.OnSelect = function( ent, old, new ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( false ) + end + end + weapon.OnDeselect = function( ent, old, new ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( true ) + end + end + self:AddWeapon( weapon ) + + self:AddGunnerWeapons() +end + +function ENT:GunnerInRange( Dir ) + return self:AngleBetweenNormal( self:GetForward(), Dir ) < 60 +end + +function ENT:AddGunnerWeapons() + local COLOR_RED = Color(255,0,0,255) + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not base:GunnerInRange( ent:GetAimVector() ) then + + if not IsValid( base.SNDTurretMGf ) then return true end + + base.SNDTurretMGf:Stop() + + return true + end + + local ID = base:LookupAttachment( "machinegun" ) + + local Muzzle = base:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( Muzzle.Ang:Forward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + if not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.StartAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.FinishAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Stop() + end + weapon.OnThink = function( ent, active ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Angles = base:WorldToLocalAngles( ent:GetAimVector():Angle() ) + Angles:Normalize() + + base:SetPoseParameter("machinegun_yaw", Angles.y ) + base:SetPoseParameter("machinegun_pitch", Angles.p ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = base:GunnerInRange( ent:GetAimVector() ) and COLOR_WHITE or COLOR_RED + + base:PaintCrosshairCenter( Pos2D, Col ) + base:LVSPaintHitMarker( Pos2D ) + end + self:AddWeapon( weapon, 2 ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_init.lua new file mode 100644 index 0000000..242bfc8 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_init.lua @@ -0,0 +1,11 @@ +include("shared.lua") +include("sh_tracks.lua") +include("sh_turret.lua") +include("cl_optics.lua") +include("cl_tankview.lua") + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/sherman/engine_start.wav", 75, 100, LVS.EngineVolume ) + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_optics.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_optics.lua new file mode 100644 index 0000000..0f8f08d --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_optics.lua @@ -0,0 +1,51 @@ + +ENT.OpticsFov = 30 +ENT.OpticsEnable = true +ENT.OpticsZoomOnly = true +ENT.OpticsFirstPerson = true +ENT.OpticsThirdPerson = false +ENT.OpticsPodIndex = { + [1] = true, +} + +ENT.OpticsCrosshairMaterial = Material( "lvs/circle_filled.png" ) +ENT.OpticsCrosshairColor = Color(0,0,0,150) +ENT.OpticsCrosshairSize = 4 + +local axis = Material( "lvs/axis.png" ) +local sight = Material( "lvs/shermansights.png" ) +local scope = Material( "lvs/scope_viewblocked.png" ) + +function ENT:PaintOpticsCrosshair( Pos2D ) + surface.SetMaterial( sight ) + surface.SetDrawColor( 0, 0, 0, 150 ) + surface.DrawTexturedRect( Pos2D.x - 210, Pos2D.y - 23, 420, 420 ) + + surface.SetMaterial( axis ) + surface.SetDrawColor( 255, 255, 255, 25 ) + surface.DrawTexturedRect( Pos2D.x - 7, Pos2D.y - 7, 16, 16 ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - 8, Pos2D.y - 8, 16, 16 ) +end + +function ENT:PaintOptics( Pos2D, Col, PodIndex, Type ) + local size = self.OpticsCrosshairSize + + surface.SetMaterial( self.OpticsCrosshairMaterial ) + surface.SetDrawColor( self.OpticsCrosshairColor ) + surface.DrawTexturedRect( Pos2D.x - size * 0.5, Pos2D.y - size * 0.5, size, size ) + + local ScrW = ScrW() + local ScrH = ScrH() + + local diameter = ScrH + 64 + local radius = diameter * 0.5 + + surface.SetMaterial( scope ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - radius, Pos2D.y - radius, diameter, diameter ) + + -- black bar left + right + surface.DrawRect( 0, 0, Pos2D.x - radius, ScrH ) + surface.DrawRect( Pos2D.x + radius, 0, Pos2D.x - radius, ScrH ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_tankview.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_tankview.lua new file mode 100644 index 0000000..611b3c5 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/cl_tankview.lua @@ -0,0 +1,12 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + if ply == self:GetDriver() and not pod:GetThirdPersonMode() then + local vieworigin, found = self:GetTurretViewOrigin() + + if found then pos = vieworigin end + end + + return pos, angles, fov +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/init.lua new file mode 100644 index 0000000..25f2359 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/init.lua @@ -0,0 +1,101 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "sh_tracks.lua" ) +AddCSLuaFile( "sh_turret.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_optics.lua" ) +AddCSLuaFile( "cl_tankview.lua" ) +include("shared.lua") +include("sh_tracks.lua") +include("sh_turret.lua") + +function ENT:OnSpawn( PObj ) + local ID = self:LookupAttachment( "machinegun" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMGf = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sherman/mg_loop.wav", "lvs/vehicles/sherman/mg_loop_interior.wav" ) + self.SNDTurretMGf:SetSoundLevel( 95 ) + self.SNDTurretMGf:SetParent( self, ID ) + + self:SetBodygroup( 4, 1 ) + local FlameThrower = self:AddFlameEmitter( self, "muzzle_zippo" ) + --[[FlameThrower:SetFlameLifeTime( 1.5 ) + FlameThrower:SetFlameVelocity( 1000 ) + FlameThrower:SetFlameSize( 80 ) + FlameThrower:SetDamage( 100 ) + FlameThrower:SetFlameStartSound( "lvs/weapons/flame_start.wav" ) + FlameThrower:SetFlameStopSound( "lvs/weapons/flame_end.wav" ) + FlameThrower:SetFlameLoopSound( "lvs/weapons/flame_loop.wav" )]] + self.WPNFlameThrower = FlameThrower + + local DriverSeat = self:AddDriverSeat( Vector(0,0,60), Angle(0,-90,0) ) + DriverSeat.HidePlayer = true + + local GunnerSeat = self:AddPassengerSeat( Vector(88,-20,32), Angle(0,-90,0) ) + GunnerSeat.HidePlayer = true + self:SetGunnerSeat( GunnerSeat ) + + self:AddEngine( Vector(-79.66,0,70), Angle(0,180,0) ) + self:AddFuelTank( Vector(-75,0,40), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL, Vector(0,-20,-18),Vector(18,20,18) ) + + -- extra front plate left + self:AddArmor( Vector(82,20.5,65), Angle(-36,0,0), Vector(-1.2,-11,-12), Vector(1.2,11,12), 1200, self.FrontArmorExtra ) + -- extra front plate right + self:AddArmor( Vector(82,-20.5,65), Angle(-36,0,0), Vector(-1.2,-11,-12), Vector(1.2,11,12), 1200, self.FrontArmorExtra ) + + -- front upper wedge + self:AddArmor( Vector(70,0,66), Angle(0,0,0), Vector(-26,-48,-16), Vector(26,48,16), 1000, self.FrontArmor ) + + -- transmission rib left + self:AddArmor( Vector(102,12.6,35), Angle(0,0,0), Vector(-16,-1.5,-19), Vector(16,1.5,19), 1200, self.FrontArmorExtra ) + -- transmission rib right + self:AddArmor( Vector(102,-11.8,35), Angle(0,0,0), Vector(-16,-1.5,-19), Vector(16,1.5,19), 1200, self.FrontArmorExtra ) + + -- transmission + self:AddArmor( Vector(110,1,34), Angle(0,0,0), Vector(-15,-32,-18), Vector(15,32,18), 1000, self.FrontArmor ) + + -- extra ammo armor right front + self:AddArmor( Vector(50,-51,63), Angle(0,0,0), Vector(-14,-1,-9), Vector(14,1,9), 1200, self.FrontArmorExtra ) + -- extra ammo armor right rear + self:AddArmor( Vector(1,-51,63), Angle(0,0,0), Vector(-14,-1,-10), Vector(14,1,10), 1200, self.FrontArmorExtra ) + -- extra ammo armor left + self:AddArmor( Vector(50,51,63), Angle(0,0,0), Vector(-14,-1,-9), Vector(14,1,9), 1200, self.FrontArmorExtra ) + + -- rear down + self:AddArmor( Vector(-85,0,34), Angle(0,0,0), Vector(-6,-29,-18), Vector(6,29,18), 800, self.SideArmor ) + -- rear up + self:AddArmor( Vector(-105,0,63), Angle(0,0,0), Vector(-3,-48,-13), Vector(3,48,13), 800, self.SideArmor ) + -- rear top + self:AddArmor( Vector(-69,0,66), Angle(0,0,0), Vector(-33,-48,-10), Vector(33,48,10), 600, self.RoofArmor ) + + -- roof mid + self:AddArmor( Vector(4,0,72), Angle(0,0,0), Vector(-40,-48,-4), Vector(40,48,4), 600, self.RoofArmor ) + + -- turret neck + self:AddArmor( Vector(2,0,79), Angle(0,0,0), Vector(-42,-44,-3), Vector(42,44,3), 1200, self.FrontArmorExtra ) + + -- right up + self:AddArmor( Vector(-6,-49,63), Angle(0,0,0), Vector(-102,-1,-13), Vector(102,1,13), 800, self.SideArmor ) + -- left up + self:AddArmor( Vector(-6,49.5,63), Angle(0,0,0), Vector(-102,-1.5,-13), Vector(102,1.5,13), 800, self.SideArmor ) + + -- right down + self:AddArmor( Vector(2,-30,34), Angle(0,0,0), Vector(-93,-2,-18), Vector(93,2,18), 800, self.SideArmor ) + -- left down + self:AddArmor( Vector(2,31,34), Angle(0,0,0), Vector(-93,-1,-18), Vector(93,1,18), 800, self.SideArmor ) + + -- shelf + self:AddArmor( Vector(-29,0,51), Angle(0,0,0), Vector(-73,-48,-1), Vector(73,48,1), 600, self.RoofArmor ) + -- bottom + self:AddArmor( Vector(8,0,17), Angle(0,0,0), Vector(-87,-29,-1), Vector(87,29,1), 600, self.RoofArmor ) + + -- turret + local TurretArmor = self:AddArmor( Vector(2,0,95), Angle(0,0,0), Vector(-42,-44,-13), Vector(42,44,13), 1500, self.TurretArmor ) + TurretArmor:SetLabel( "Turret" ) + self:SetTurretArmor( TurretArmor ) + + -- ammo rack weakspot + self:AddAmmoRack( Vector(0,40,64), Vector(-5,0,70), Angle(0,0,0), Vector(-18,-6,-9), Vector(18,6,9) ) + self:AddAmmoRack( Vector(0,-40,64), Vector(-5,0,70), Angle(0,0,0), Vector(-18,-6,-9), Vector(18,6,9) ) + + -- trailer hitch + self:AddTrailerHitch( Vector(-100,0,24), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/sh_tracks.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/sh_tracks.lua new file mode 100644 index 0000000..adc743b --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/sh_tracks.lua @@ -0,0 +1,183 @@ + +if SERVER then + ENT.PivotSteerEnable = true + ENT.PivotSteerByBrake = true + ENT.PivotSteerWheelRPM = 40 + + ENT.TrackGibs = { + ["left"] = { + { + mdl = "models/blu/tanks/sherman_tracks_ragdoll.mdl", + pos = Vector(0,41.3,3), + ang = Angle(-90,-90,0), + }, + }, + ["right"] = { + { + mdl = "models/blu/tanks/sherman_tracks_ragdoll.mdl", + pos = Vector(0,-41.3,3), + ang = Angle(-90,-90,0), + }, + } + } + + function ENT:OnLeftTrackRepaired() + self:SetBodygroup(2,0) + end + + function ENT:OnLeftTrackDestroyed() + self:SetBodygroup(2,1) + end + + function ENT:OnRightTrackRepaired() + self:SetBodygroup(3,0) + end + + function ENT:OnRightTrackDestroyed() + self:SetBodygroup(3,1) + end + + function ENT:TracksCreate( PObj ) + self:CreateTrackPhysics( "models/blu/tanks/sherman_tracks_col.mdl" ) + + local WheelModel = "models/props_vehicles/tire001b_truck.mdl" + + local L1 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(100,42,55), mdl = WheelModel } ) + local L2 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(70,42,35), mdl = WheelModel } ) + local L3 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(35,42,40), mdl = WheelModel } ) + local L4 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(0,42,45), mdl = WheelModel } ) + local L5 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(-35,42,45), mdl = WheelModel } ) + local L6 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(-70,42,45), mdl = WheelModel } ) + local LeftWheelChain = self:CreateWheelChain( {L1, L2, L3, L4, L5, L6} ) + self:SetTrackDriveWheelLeft( L4 ) + + local R1 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(100,-42,55), mdl = WheelModel } ) + local R2 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(70,-42,35), mdl = WheelModel } ) + local R3 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(35,-42,40), mdl = WheelModel } ) + local R4 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(0,-42,45), mdl = WheelModel } ) + local R5 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(-35,-42,45), mdl = WheelModel } ) + local R6 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(-70,-42,45), mdl = WheelModel} ) + local RightWheelChain = self:CreateWheelChain( {R1, R2, R3, R4, R5, R6} ) + self:SetTrackDriveWheelRight( R4 ) + + local LeftTracksArmor = self:AddArmor( Vector(12,43,22), Angle(0,0,0), Vector(-121,-10,-28), Vector(121,10,28), 500, self.FrontArmor ) + self:SetTrackArmorLeft( LeftTracksArmor, LeftWheelChain ) + + local RightTracksArmor = self:AddArmor( Vector(12,-41,22), Angle(0,0,0), Vector(-121,-10,-28), Vector(121,10,28), 500, self.FrontArmor ) + self:SetTrackArmorRight( RightTracksArmor, RightWheelChain ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R1, L1, R2, L2 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R3, L3, L4, R4 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_REAR, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R5, L5, R6, L6 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + end +else + ENT.TrackSystemEnable = true + + ENT.TrackScrollTexture = "models/blu/track_sherman" + ENT.ScrollTextureData = { + ["$alphatest"] = "1", + ["$translate"] = "[0.0 0.0 0.0]", + ["$colorfix"] = "{255 255 255}", + ["Proxies"] = { + ["TextureTransform"] = { + ["translateVar"] = "$translate", + ["centerVar"] = "$center", + ["resultVar"] = "$basetexturetransform", + }, + ["Equals"] = { + ["srcVar1"] = "$colorfix", + ["resultVar"] = "$color", + } + } + } + + ENT.TrackLeftSubMaterialID = 1 + ENT.TrackLeftSubMaterialMul = Vector(0,0.0065,0) + + ENT.TrackRightSubMaterialID = 2 + ENT.TrackRightSubMaterialMul = Vector(0,0.0065,0) + + ENT.TrackPoseParameterLeft = "spin_wheels_left" + ENT.TrackPoseParameterLeftMul = -1.252 + + ENT.TrackPoseParameterRight = "spin_wheels_right" + ENT.TrackPoseParameterRightMul = -1.252 + + ENT.TrackSounds = "lvs/vehicles/sherman/tracks_loop.wav" + ENT.TrackHull = Vector(20,20,20) + ENT.TrackData = {} + for i = 1, 6 do + for n = 0, 1 do + local LR = n == 0 and "l" or "r" + local LeftRight = n == 0 and "left" or "right" + local data = { + Attachment = { + name = "vehicle_suspension_"..LR.."_"..i, + toGroundDistance = 41, + traceLength = 100, + }, + PoseParameter = { + name = "suspension_"..LeftRight.."_"..i, + rangeMultiplier = -1, + lerpSpeed = 25, + } + } + table.insert( ENT.TrackData, data ) + end + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/sh_turret.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/sh_turret.lua new file mode 100644 index 0000000..a230ca2 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/sh_turret.lua @@ -0,0 +1,23 @@ + +include("entities/lvs_tank_wheeldrive/modules/sh_turret.lua") +include("entities/lvs_tank_wheeldrive/modules/sh_turret_ballistics.lua") + +ENT.TurretBallisticsPredicted = false -- sherman optics are not adjustable + +ENT.TurretBallisticsProjectileVelocity = 10000 +ENT.TurretBallisticsMuzzleAttachment = "turret_machinegun" +ENT.TurretBallisticsViewAttachment = "turret_machinegun" + +ENT.TurretAimRate = 25 + +ENT.TurretRotationSound = "vehicles/tank_turret_loop1.wav" + +ENT.TurretPitchPoseParameterName = "turret_pitch" +ENT.TurretPitchMin = -15 +ENT.TurretPitchMax = 15 +ENT.TurretPitchMul = 1 +ENT.TurretPitchOffset = 0 + +ENT.TurretYawPoseParameterName = "turret_yaw" +ENT.TurretYawMul = 1 +ENT.TurretYawOffset = 0 diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/shared.lua new file mode 100644 index 0000000..d548dd6 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodsherman_zippo/shared.lua @@ -0,0 +1,308 @@ + +ENT.Base = "lvs_tank_wheeldrive" + +ENT.PrintName = "Sherman Zippo" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Tanks" +ENT.VehicleSubCategory = "Medium" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/blu/tanks/sherman_lvs.mdl" +ENT.MDL_DESTROYED = "models/blu/tanks/sherman_gib_1.mdl" + +ENT.GibModels = { + "models/blu/tanks/sherman_gib_2.mdl", + "models/blu/tanks/sherman_gib_3.mdl", + "models/blu/tanks/sherman_gib_4.mdl", + "models/blu/tanks/sherman_gib_6.mdl", + "models/blu/tanks/sherman_gib_7.mdl", +} + +ENT.AITEAM = 2 + +ENT.MaxHealth = 1200 + +--damage system +ENT.DSArmorIgnoreForce = 3000 +ENT.CannonArmorPenetration = 9200 +ENT.FrontArmor = 2000 +ENT.FrontArmorExtra = 4500 +ENT.SideArmor = 800 +ENT.TurretArmor = 3000 +ENT.RoofArmor = 100 + +ENT.SteerSpeed = 1 +ENT.SteerReturnSpeed = 2 + +ENT.PhysicsWeightScale = 2 +ENT.PhysicsDampingSpeed = 1000 +ENT.PhysicsInertia = Vector(6000,6000,1500) + +ENT.MaxVelocity = 450 +ENT.MaxVelocityReverse = 150 + +ENT.EngineCurve = 0.1 +ENT.EngineTorque = 200 + +ENT.TransMinGearHoldTime = 0.1 +ENT.TransShiftSpeed = 0 + +ENT.TransGears = 3 +ENT.TransGearsReverse = 1 + +ENT.MouseSteerAngle = 45 + +ENT.lvsShowInSpawner = true + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/sherman/eng_idle_loop.wav", + Volume = 1, + Pitch = 70, + PitchMul = 30, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/sherman/eng_loop.wav", + Volume = 1, + Pitch = 20, + PitchMul = 90, + SoundLevel = 85, + SoundType = LVS.SOUNDTYPE_NONE, + UseDoppler = true, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-90.47,17.01,52.77), + ang = Angle(180,0,0) + }, + { + pos = Vector(-90.47,-17.01,52.77), + ang = Angle(180,0,0) + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "GunnerSeat" ) +end + +function ENT:InitWeapons() + local COLOR_WHITE = Color(255,255,255,255) + + -- flamethrower + local weapon = {} + weapon.Icon = Material("lvs/weapons/flamethrower.png") + weapon.Ammo = 4000 + weapon.Delay = 0.05 + weapon.HeatRateUp = 0.25 + weapon.HeatRateDown = 0.5 + weapon.Attack = function( ent ) + ent:TakeAmmo( 1 ) + end + weapon.StartAttack = function( ent ) + if not IsValid( ent.WPNFlameThrower ) then return end + ent.WPNFlameThrower:SetAttacker( ent:GetDriver() ) + ent.WPNFlameThrower:Enable() + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent.WPNFlameThrower ) then return end + ent.WPNFlameThrower:Disable() + end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "turret_cannon" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + ent:PaintCrosshairOuter( MuzzlePos2D, COLOR_WHITE ) + + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/overheat.wav") + end + self:AddWeapon( weapon ) + + -- smoke + local weapon = {} + weapon.Icon = Material("lvs/weapons/smoke_launcher.png") + weapon.Ammo = 6 + weapon.Delay = 1 + weapon.HeatRateUp = 1 + weapon.HeatRateDown = 0.2 + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "turret_cannon" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + ent:EmitSound("lvs/vehicles/sherman/cannon_reload.wav", 75, 100, 1, CHAN_WEAPON ) + ent:EmitSound("lvs/smokegrenade.wav") + ent:TakeAmmo( 1 ) + + local grenade = ents.Create( "lvs_item_smoke" ) + grenade:SetPos( Muzzle.Pos ) + grenade:SetAngles( Muzzle.Ang ) + grenade:Spawn() + grenade:Activate() + grenade:GetPhysicsObject():SetVelocity( Muzzle.Ang:Forward() * 2000 ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "turret_cannon" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + ent:PaintCrosshairSquare( MuzzlePos2D, COLOR_WHITE ) + + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + self:AddWeapon( weapon ) + + -- turret rotation disabler + local weapon = {} + weapon.Icon = Material("lvs/weapons/tank_noturret.png") + weapon.Ammo = -1 + weapon.Delay = 0 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 0 + weapon.OnSelect = function( ent, old, new ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( false ) + end + end + weapon.OnDeselect = function( ent, old, new ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( true ) + end + end + self:AddWeapon( weapon ) + + self:AddGunnerWeapons() +end + +function ENT:GunnerInRange( Dir ) + return self:AngleBetweenNormal( self:GetForward(), Dir ) < 60 +end + +function ENT:AddGunnerWeapons() + local COLOR_RED = Color(255,0,0,255) + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not base:GunnerInRange( ent:GetAimVector() ) then + + if not IsValid( base.SNDTurretMGf ) then return true end + + base.SNDTurretMGf:Stop() + + return true + end + + local ID = base:LookupAttachment( "machinegun" ) + + local Muzzle = base:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( Muzzle.Ang:Forward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + if not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.StartAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.FinishAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Stop() + end + weapon.OnThink = function( ent, active ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Angles = base:WorldToLocalAngles( ent:GetAimVector():Angle() ) + Angles:Normalize() + + base:SetPoseParameter("machinegun_yaw", Angles.y ) + base:SetPoseParameter("machinegun_pitch", Angles.p ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = base:GunnerInRange( ent:GetAimVector() ) and COLOR_WHITE or COLOR_RED + + base:PaintCrosshairCenter( Pos2D, Col ) + base:LVSPaintHitMarker( Pos2D ) + end + self:AddWeapon( weapon, 2 ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_attached_playermodels.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_attached_playermodels.lua new file mode 100644 index 0000000..84c2351 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_attached_playermodels.lua @@ -0,0 +1,59 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_attachable_playermodels.lua") + +function ENT:DrawDriver() + local pod = self:GetDriverSeat() + + if not IsValid( pod ) then self:RemovePlayerModel( "driver" ) return end + + local plyL = LocalPlayer() + local ply = pod:GetDriver() + + if not IsValid( ply ) or (ply == plyL and not pod:GetThirdPersonMode()) then self:RemovePlayerModel( "driver" ) return end + + local ID = self:LookupAttachment( "seat1" ) + local Att = self:GetAttachment( ID ) + + if not Att then self:RemovePlayerModel( "driver" ) return end + + local Pos,Ang = LocalToWorld( Vector(10,-5,0), Angle(0,20,-90), Att.Pos, Att.Ang ) + + local model = self:CreatePlayerModel( ply, "driver" ) + + model:SetSequence( "sit" ) + model:SetRenderOrigin( Pos ) + model:SetRenderAngles( Ang ) + model:DrawModel() +end + +function ENT:DrawGunner() + local pod = self:GetGunnerSeat() + + if not IsValid( pod ) then self:RemovePlayerModel( "passenger" ) return end + + local plyL = LocalPlayer() + local ply = pod:GetDriver() + + if not IsValid( ply ) or (ply == plyL and not pod:GetThirdPersonMode()) then self:RemovePlayerModel( "passenger" ) return end + + local ID = self:LookupAttachment( "seat2" ) + local Att = self:GetAttachment( ID ) + + if not Att then self:RemovePlayerModel( "passenger" ) return end + + local Pos,Ang = LocalToWorld( Vector(10,-5,0), Angle(0,20,-90), Att.Pos, Att.Ang ) + + local model = self:CreatePlayerModel( ply, "passenger" ) + + model:SetSequence( "sit" ) + model:SetRenderOrigin( Pos ) + model:SetRenderAngles( Ang ) + model:DrawModel() +end + +function ENT:PreDraw() + self:DrawDriver() + self:DrawGunner() + + return true +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_init.lua new file mode 100644 index 0000000..a08ba40 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_init.lua @@ -0,0 +1,47 @@ +include("shared.lua") +include("sh_turret.lua") +include("cl_tankview.lua") +include("cl_attached_playermodels.lua") + +function ENT:OnFrame() + local Heat1 = 0 + if self:GetSelectedWeapon() == 2 then + Heat1 = self:QuickLerp( "cannon_heat", self:GetNWHeat(), 10 ) + else + Heat1 = self:QuickLerp( "cannon_heat", 0, 0.25 ) + end + local name = "spaehwagen_cannonglow_"..self:EntIndex() + if not self.TurretGlow1 then + self.TurretGlow1 = self:CreateSubMaterial( 3, name ) + + return + end + if self._oldGunHeat1 ~= Heat1 then + self._oldGunHeat1 = Heat1 + + self.TurretGlow1:SetFloat("$detailblendfactor", Heat1 ^ 7 ) + + self:SetSubMaterial(3, "!"..name) + end + + + local Heat2 = 0 + if self:GetSelectedWeapon() == 1 then + Heat2 = self:QuickLerp( "mg_heat", self:GetNWHeat(), 10 ) + else + Heat2 = self:QuickLerp( "mg_heat", 0, 0.25 ) + end + local name = "spaehwagen_mgglow_"..self:EntIndex() + if not self.TurretGlow2 then + self.TurretGlow2 = self:CreateSubMaterial( 2, name ) + + return + end + if self._oldGunHeat2 ~= Heat2 then + self._oldGunHeat2 = Heat2 + + self.TurretGlow2:SetFloat("$detailblendfactor", Heat2 ^ 7 ) + + self:SetSubMaterial(2, "!"..name) + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_tankview.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_tankview.lua new file mode 100644 index 0000000..9b4c17b --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/cl_tankview.lua @@ -0,0 +1,30 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + if ply == self:GetDriver() and not pod:GetThirdPersonMode() then + local ID = self:LookupAttachment( "seat1" ) + + local Muzzle = self:GetAttachment( ID ) + + if Muzzle then + pos = Muzzle.Pos - Muzzle.Ang:Right() * 25 + end + end + + return pos, angles, fov +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + if pod == self:GetGunnerSeat() and not pod:GetThirdPersonMode() then + local ID = self:LookupAttachment( "seat2" ) + + local Muzzle = self:GetAttachment( ID ) + + if Muzzle then + pos = Muzzle.Pos - Muzzle.Ang:Right() * 25 + end + end + + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/init.lua new file mode 100644 index 0000000..94bf071 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/init.lua @@ -0,0 +1,100 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_tankview.lua" ) +AddCSLuaFile( "cl_attached_playermodels.lua" ) +AddCSLuaFile( "sh_turret.lua" ) +include("shared.lua") +include("sh_turret.lua") + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(0,-15,30), Angle(0,0,0) ) + DriverSeat.HidePlayer = true + + local GunnerSeat = self:AddPassengerSeat( Vector(0,-15,30), Angle(0,0,0) ) + GunnerSeat.HidePlayer = true + self:SetGunnerSeat( GunnerSeat ) + + local ID = self:LookupAttachment( "muzzle_mg" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMG = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sherman/mg_loop.wav", "lvs/vehicles/sherman/mg_loop_interior.wav" ) + self.SNDTurretMG:SetSoundLevel( 95 ) + self.SNDTurretMG:SetParent( self, ID ) + + + local ID = self:LookupAttachment( "muzzle_turret" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurret = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/222/cannon_fire.wav", "lvs/vehicles/222/cannon_fire_interior.wav" ) + self.SNDTurret:SetSoundLevel( 95 ) + self.SNDTurret:SetParent( self, ID ) + + + self:AddEngine( Vector(0,-60,51), Angle(0,-90,0) ) + self:AddFuelTank( Vector(0,-63,25), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL ) + + local WheelModel = "models/diggercars/222/222_wheel.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,90,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 22, + TorqueFactor = 0.4, + BrakeFactor = 1, + }, + Wheels = { + self:AddWheel( { + pos = Vector(-35,70,18), + mdl = WheelModel, + mdl_ang = Angle(0,-90,0), + } ), + + self:AddWheel( { + pos = Vector(35,70,18), + mdl = WheelModel, + mdl_ang = Angle(0,90,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,90,0), + SteerType = LVS.WHEEL_STEER_REAR, + SteerAngle = 8, + TorqueFactor = 0.6, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(-35,-45,14), + mdl = WheelModel, + mdl_ang = Angle(0,-90,0), + } ), + + self:AddWheel( { + pos = Vector(35,-45,14), + mdl = WheelModel, + mdl_ang = Angle(0,90,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + self:AddTrailerHitch( Vector(0,-85,25), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/sh_turret.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/sh_turret.lua new file mode 100644 index 0000000..2534891 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/sh_turret.lua @@ -0,0 +1,16 @@ + +include("entities/lvs_tank_wheeldrive/modules/sh_turret.lua") + +ENT.TurretAimRate = 50 + +ENT.TurretRotationSound = "vehicles/tank_turret_loop1.wav" + +ENT.TurretPitchPoseParameterName = "turret_pitch" +ENT.TurretPitchMin = -20 +ENT.TurretPitchMax = 40 +ENT.TurretPitchMul = -1 +ENT.TurretPitchOffset = 0 + +ENT.TurretYawPoseParameterName = "turret_yaw" +ENT.TurretYawMul = -1 +ENT.TurretYawOffset = 90 diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/shared.lua new file mode 100644 index 0000000..d037fc5 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodspaehwagen/shared.lua @@ -0,0 +1,301 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "Panzerspaehwagen" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Armored" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/222/222.mdl" + +ENT.AITEAM = 1 + +ENT.MaxHealth = 750 +ENT.MaxHealthEngine = 400 +ENT.MaxHealthFuelTank = 100 + +--damage system +ENT.DSArmorIgnoreForce = 1200 +ENT.CannonArmorPenetration = 3900 + + +ENT.MaxVelocity = 1000 + +ENT.EngineCurve = 0.2 +ENT.EngineTorque = 200 + +ENT.TransGears = 5 +ENT.TransGearsReverse = 5 + +ENT.FastSteerAngleClamp = 5 +ENT.FastSteerDeactivationDriftAngle = 12 + +ENT.PhysicsWeightScale = 1.5 +ENT.PhysicsDampingForward = true +ENT.PhysicsDampingReverse = true + +ENT.lvsShowInSpawner = true + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/222/eng_idle_loop.wav", + Volume = 0.5, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/222/eng_loop.wav", + Volume = 1, + Pitch = 70, + PitchMul = 100, + SoundLevel = 75, + UseDoppler = true, + }, +} + +ENT.Lights = { + { + Trigger = "main", + SubMaterialID = 1, + Sprites = { + [1] = { + pos = Vector(-21.76,92.74,44.5), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(21.76,92.74,44.5), + colorB = 200, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(-21.76,92.74,44.5), + ang = Angle(0,90,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(21.76,92.74,44.5), + ang = Angle(0,90,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, + { + Trigger = "high", + Sprites = { + [1] = { + pos = Vector(-21.76,92.74,44.5), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(21.76,92.74,44.5), + colorB = 200, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(-21.76,92.74,44.5), + ang = Angle(0,90,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(21.76,92.74,44.5), + ang = Angle(0,90,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "GunnerSeat" ) +end + +function ENT:InitWeapons() + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "muzzle_mg" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = Muzzle.Ang:Forward() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + end + weapon.StartAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + ent.SNDTurretMG:Play() + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + ent.SNDTurretMG:Stop() + end + weapon.OnOverheat = function( ent ) ent:EmitSound("lvs/overheat.wav") end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "muzzle_mg" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + ent:PaintCrosshairCenter( MuzzlePos2D, COLOR_WHITE ) + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/overheat.wav") + end + self:AddWeapon( weapon ) + + + local weapon = {} + weapon.Icon = Material("lvs/weapons/bullet_ap.png") + weapon.Ammo = 250 + weapon.Delay = 0.25 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.2 + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "muzzle_turret" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = Muzzle.Ang:Forward() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_autocannon" + bullet.Force = ent.CannonArmorPenetration + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 100 + bullet.Velocity = 14000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:PlayAnimation( "turret_fire" ) + + local PhysObj = ent:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:ApplyForceOffset( -bullet.Dir * 10000, bullet.Src ) + end + + ent:TakeAmmo( 1 ) + + if not IsValid( ent.SNDTurret ) then return end + + ent.SNDTurret:PlayOnce( 100 + math.cos( CurTime() + ent:EntIndex() * 1337 ) * 5 + math.Rand(-1,1), 1 ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "muzzle_turret" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + ent:PaintCrosshairOuter( MuzzlePos2D, COLOR_WHITE ) + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/vehicles/222/cannon_overheat.wav") + end + self:AddWeapon( weapon ) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/tank_noturret.png") + weapon.Ammo = -1 + weapon.Delay = 0 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 0 + weapon.OnSelect = function( ent ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( false ) + end + end + weapon.OnDeselect = function( ent ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( true ) + end + end + self:AddWeapon( weapon ) +end + + +ENT.ExhaustPositions = { + { + pos = Vector(-20.98,-56.09,17.55), + ang = Angle(90,0,0), + }, + { + pos = Vector(20.98,-56.09,17.55), + ang = Angle(90,0,0), + }, +} diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_init.lua new file mode 100644 index 0000000..fcf9427 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_init.lua @@ -0,0 +1,41 @@ +include("shared.lua") +include("sh_tracks.lua") +include("sh_turret.lua") +include("cl_optics.lua") +include("cl_tankview.lua") + +local switch = Material("lvs/weapons/change_ammo.png") +local AP = Material("lvs/weapons/bullet_ap.png") +local HE = Material("lvs/weapons/tank_cannon.png") +function ENT:DrawWeaponIcon( PodID, ID, x, y, width, height, IsSelected, IconColor ) + local Icon = self:GetUseHighExplosive() and HE or AP + + surface.SetMaterial( Icon ) + surface.DrawTexturedRect( x, y, width, height ) + + local ply = LocalPlayer() + + if not IsValid( ply ) or self:GetSelectedWeapon() ~= 2 then return end + + surface.SetMaterial( switch ) + surface.DrawTexturedRect( x + width + 5, y + 7, 24, 24 ) + + local buttonCode = ply:lvsGetControls()[ "CAR_SWAP_AMMO" ] + + if not buttonCode then return end + + local KeyName = input.GetKeyName( buttonCode ) + + if not KeyName then return end + + draw.DrawText( KeyName, "DermaDefault", x + width + 17, y + height * 0.5 + 7, Color(0,0,0,IconColor.a), TEXT_ALIGN_CENTER ) +end + + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/tiger/engine_start.wav", 75, 100, LVS.EngineVolume ) + else + self:EmitSound( "lvs/vehicles/tiger/engine_stop.wav", 75, 100, LVS.EngineVolume ) + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_optics.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_optics.lua new file mode 100644 index 0000000..88998f3 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_optics.lua @@ -0,0 +1,152 @@ + +ENT.OpticsFov = 30 +ENT.OpticsEnable = true +ENT.OpticsZoomOnly = true +ENT.OpticsFirstPerson = true +ENT.OpticsThirdPerson = false +ENT.OpticsPodIndex = { + [1] = true, +} + +ENT.OpticsCrosshairMaterial = Material( "lvs/circle_filled.png" ) +ENT.OpticsCrosshairColor = Color(0,0,0,150) +ENT.OpticsCrosshairSize = 4 + +ENT.OpticsProjectileSize = 7.5 + +local OldTargetOffset = 0 +local RotationOffset = 0 +local circle = Material( "lvs/circle_hollow.png" ) +local tri1 = Material( "lvs/triangle1.png" ) +local tri2 = Material( "lvs/triangle2.png" ) +local pointer = Material( "gui/point.png" ) +local scope = Material( "lvs/scope_viewblocked.png" ) + +function ENT:PaintOpticsCrosshair( Pos2D ) + surface.SetDrawColor( 255, 255, 255, 5 ) + surface.SetMaterial( tri1 ) + surface.DrawTexturedRect( Pos2D.x - 17, Pos2D.y - 1, 32, 32 ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - 16, Pos2D.y, 32, 32 ) + + for i = -3, 3, 1 do + if i == 0 then continue end + + surface.SetMaterial( tri2 ) + surface.SetDrawColor( 255, 255, 255, 5 ) + surface.DrawTexturedRect( Pos2D.x - 11 + i * 32, Pos2D.y - 1, 20, 20 ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - 10 + i * 32, Pos2D.y, 20, 20 ) + end + + local ScrH = ScrH() + + local Y = Pos2D.y + 64 + local height = ScrH - Y + + surface.SetDrawColor( 0, 0, 0, 100 ) + surface.DrawRect( Pos2D.x - 2, Y, 4, height ) +end + +function ENT:PaintOptics( Pos2D, Col, PodIndex, Type ) + + if Type == 1 then + self:DrawRotatedText( "MG", Pos2D.x + 30, Pos2D.y + 30, "LVS_FONT_PANEL", Color(0,0,0,220), 0) + else + self:DrawRotatedText( Type == 3 and "HE" or "AP", Pos2D.x + 30, Pos2D.y + 30, "LVS_FONT_PANEL", Color(0,0,0,220), 0) + end + + local size = self.OpticsCrosshairSize + + surface.SetMaterial( self.OpticsCrosshairMaterial ) + surface.SetDrawColor( self.OpticsCrosshairColor ) + surface.DrawTexturedRect( Pos2D.x - size * 0.5, Pos2D.y - size * 0.5, size, size ) + + local ScrW = ScrW() + local ScrH = ScrH() + + surface.SetDrawColor( 0, 0, 0, 200 ) + + local TargetOffset = self:GetSelectedWeapon() == 1 and 150 or 0 + + if OldTargetOffset ~= TargetOffset then + OldTargetOffset = TargetOffset + surface.PlaySound( "lvs/optics.wav" ) + end + + RotationOffset = RotationOffset + (TargetOffset + math.max( self:GetTurretCompensation() / 15, -130 ) - RotationOffset) * RealFrameTime() * 8 + + local R = ScrH * 0.5 - 64 + local R0 = R + 30 + local R1 = R - 8 + local R2 = R - 23 + local R3 = R - 30 + local R4 = R - 18 + + for i = 0, 40 do + local ang = -90 + (180 / 40) * i + RotationOffset + + local x = math.cos( math.rad( ang ) ) + local y = math.sin( math.rad( ang ) ) + + if i == 2 then + self:DrawRotatedText( self.OpticsProjectileSize, Pos2D.x + x * R0, Pos2D.y + y * R0, "LVS_FONT", Color(0,0,0,200), 90 + ang) + end + if i == 3 then + self:DrawRotatedText( "cm", Pos2D.x + x * R0, Pos2D.y + y * R0, "LVS_FONT", Color(0,0,0,200), 90 + ang) + end + if i == 5 then + self:DrawRotatedText( "Pzgr", Pos2D.x + x * R0, Pos2D.y + y * R0, "LVS_FONT", Color(0,0,0,200), 90 + ang) + end + + surface.SetMaterial( circle ) + surface.DrawTexturedRectRotated( Pos2D.x + x * R, Pos2D.y + y * R, 16, 16, 0 ) + + surface.DrawLine( Pos2D.x + x * R1, Pos2D.y + y * R1, Pos2D.x + x * R2, Pos2D.y + y * R2 ) + + self:DrawRotatedText( i, Pos2D.x + x * R3, Pos2D.y + y * R3, "LVS_FONT_PANEL", Color(0,0,0,255), ang + 90) + + if i == 40 then continue end + + local ang = - 90 + (180 / 40) * (i + 0.5) + RotationOffset + + local x = math.cos( math.rad( ang ) ) + local y = math.sin( math.rad( ang ) ) + + surface.DrawLine( Pos2D.x + x * R1, Pos2D.y + y * R1, Pos2D.x + x * R4, Pos2D.y + y * R4 ) + end + + for i = 0, 13 do + local ang = 120 + (120 / 13) * i + RotationOffset + + local x = math.cos( math.rad( ang ) ) + local y = math.sin( math.rad( ang ) ) + + if i == 1 then + self:DrawRotatedText( "MG", Pos2D.x + x * R0, Pos2D.y + y * R0, "LVS_FONT", Color(0,0,0,200), 90 + ang) + end + + surface.SetMaterial( circle ) + surface.DrawTexturedRectRotated( Pos2D.x + x * R, Pos2D.y + y * R, 16, 16, 0 ) + + surface.DrawLine( Pos2D.x + x * R1, Pos2D.y + y * R1, Pos2D.x + x * R2, Pos2D.y + y * R2 ) + + self:DrawRotatedText( i, Pos2D.x + x * R3, Pos2D.y + y * R3, "LVS_FONT_PANEL", Color(0,0,0,255), ang + 90) + end + + surface.SetDrawColor( 0, 0, 0, 100 ) + surface.SetMaterial( pointer ) + surface.DrawTexturedRect( Pos2D.x - 16, 0, 32, 64 ) + + local diameter = ScrH + 64 + local radius = diameter * 0.5 + + surface.SetMaterial( scope ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawTexturedRect( Pos2D.x - radius, Pos2D.y - radius, diameter, diameter ) + + -- black bar left + right + surface.DrawRect( 0, 0, Pos2D.x - radius, ScrH ) + surface.DrawRect( Pos2D.x + radius, 0, Pos2D.x - radius, ScrH ) +end + diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_tankview.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_tankview.lua new file mode 100644 index 0000000..611b3c5 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/cl_tankview.lua @@ -0,0 +1,12 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + if ply == self:GetDriver() and not pod:GetThirdPersonMode() then + local vieworigin, found = self:GetTurretViewOrigin() + + if found then pos = vieworigin end + end + + return pos, angles, fov +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/init.lua new file mode 100644 index 0000000..628fbc9 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/init.lua @@ -0,0 +1,86 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "sh_tracks.lua" ) +AddCSLuaFile( "sh_turret.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_optics.lua" ) +AddCSLuaFile( "cl_tankview.lua" ) +include("shared.lua") +include("sh_tracks.lua") +include("sh_turret.lua") + +function ENT:OnSpawn( PObj ) + local ID = self:LookupAttachment( "muzzle_machinegun" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMGf = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sherman/mg_loop.wav", "lvs/vehicles/sherman/mg_loop_interior.wav" ) + self.SNDTurretMGf:SetSoundLevel( 95 ) + self.SNDTurretMGf:SetParent( self, ID ) + + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurret = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/tiger/cannon_fire.wav", "lvs/vehicles/tiger/cannon_fire.wav" ) + self.SNDTurret:SetSoundLevel( 95 ) + self.SNDTurret:SetParent( self, ID ) + + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMG = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos - Muzzle.Ang:Up() * 140 - Muzzle.Ang:Forward() * 15 ), "lvs/vehicles/sherman/mg_loop.wav", "lvs/vehicles/sherman/mg_loop_interior.wav" ) + self.SNDTurretMG:SetSoundLevel( 95 ) + self.SNDTurretMG:SetParent( self, ID ) + + local DriverSeat = self:AddDriverSeat( Vector(0,0,60), Angle(0,-90,0) ) + DriverSeat.HidePlayer = true + + local GunnerSeat = self:AddPassengerSeat( Vector(103,-24,41), Angle(0,-90,0) ) + GunnerSeat.HidePlayer = true + self:SetGunnerSeat( GunnerSeat ) + + self:AddEngine( Vector(-79.66,0,72.21), Angle(0,180,0) ) + self:AddFuelTank( Vector(-80,0,60), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL, Vector(-12,-50,-12),Vector(12,50,0) ) + + -- turret + local TurretArmor = self:AddArmor( Vector(0,0,88), Angle(0,0,0), Vector(-52,-52,-18), Vector(52,52,18), 4000, self.TurretArmor ) + TurretArmor:SetLabel( "Turret" ) + self:SetTurretArmor( TurretArmor ) + + -- front upper plate + self:AddArmor( Vector(105,0,62), Angle(0,0,0), Vector(-8,-65,-12), Vector(8,65,12), 4000, self.FrontArmor ) + + -- front mid plate + self:AddArmor( Vector(122,0,49), Angle(8,0,0), Vector(-10,-37,-1), Vector(10,37,1), 1200, self.RearArmor ) + + -- front lower plate + self:AddArmor( Vector(127,0,35), Angle(-67,0,0), Vector(-15,-37,-1), Vector(15,37,1), 4000, self.FrontArmor ) + + -- front bottom plate + self:AddArmor( Vector(111,0,18), Angle(-22,0,0), Vector(-11,-37,-1), Vector(11,37,1), 1200, self.RearArmor ) + + --left up + self:AddArmor( Vector(-8,64,56), Angle(0,0,0), Vector(-105,-1,-18), Vector(105,1,18), 1600, self.SideArmor ) + --right up + self:AddArmor( Vector(-8,-64,56), Angle(0,0,0), Vector(-105,-1,-18), Vector(105,1,18), 1600, self.SideArmor ) + + --left down + self:AddArmor( Vector(12,37,35), Angle(0,0,0), Vector(-120,-1,-22), Vector(120,1,22), 1200, self.RearArmor ) + --right down + self:AddArmor( Vector(12,-37,35), Angle(0,0,0), Vector(-120,-1,-22), Vector(120,1,22), 1200, self.RearArmor ) + + -- rear + self:AddArmor( Vector(-105,0,42), Angle(0,0,0), Vector(-8,-65,-29), Vector(8,65,29), 1600, self.SideArmor ) + + --top + self:AddArmor( Vector(0,0,69), Angle(0,0,0), Vector(-97,-63,-1), Vector(97,63,1), 500, self.RoofArmor ) + --bottom + self:AddArmor( Vector(2,0,14), Angle(0,0,0), Vector(-99,-36,-1), Vector(99,36,1), 500, self.RoofArmor ) + + -- hole + self:AddArmor( Vector(82,0,52), Angle(0,0,0), Vector(-15,-63,-2), Vector(15,63,2), 500, self.RoofArmor ) + + -- ammo rack weakspot + self:AddAmmoRack( Vector(0,50,55), Vector(0,0,65), Angle(0,0,0), Vector(-54,-12,-6), Vector(54,12,6) ) + self:AddAmmoRack( Vector(0,-50,55), Vector(0,0,65), Angle(0,0,0), Vector(-54,-12,-6), Vector(54,12,6) ) + self:AddAmmoRack( Vector(0,30,30), Vector(0,0,65), Angle(0,0,0), Vector(-30,-6,-12), Vector(30,6,12) ) + self:AddAmmoRack( Vector(0,-30,30), Vector(0,0,65), Angle(0,0,0), Vector(-30,-6,-12), Vector(30,6,12) ) + + -- trailer hitch + self:AddTrailerHitch( Vector(-112,0,22), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/sh_tracks.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/sh_tracks.lua new file mode 100644 index 0000000..685a07d --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/sh_tracks.lua @@ -0,0 +1,189 @@ + +if SERVER then + ENT.PivotSteerEnable = true + ENT.PivotSteerByBrake = false + ENT.PivotSteerWheelRPM = 25 + + ENT.TrackGibs = { + ["left"] = { + { + mdl = "models/blu/tanks/tiger_tracks_ragdoll.mdl", + pos = Vector(0,54.7,0), + ang = Angle(-90,-90,0), + }, + }, + ["right"] = { + { + mdl = "models/blu/tanks/tiger_tracks_ragdoll.mdl", + pos = Vector(0,-54.7,0), + ang = Angle(-90,-90,0), + }, + } + } + + function ENT:OnLeftTrackRepaired() + self:SetBodygroup(3,0) + end + + function ENT:OnLeftTrackDestroyed() + self:SetBodygroup(3,1) + end + + function ENT:OnRightTrackRepaired() + self:SetBodygroup(4,0) + end + + function ENT:OnRightTrackDestroyed() + self:SetBodygroup(4,1) + end + + function ENT:TracksCreate( PObj ) + self:CreateTrackPhysics( "models/blu/tanks/tiger_tracks_col.mdl" ) + + local WheelModel = "models/props_vehicles/tire001b_truck.mdl" + + local L1 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(115,55,45), mdl = WheelModel } ) + local L2 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(75,55,25), mdl = WheelModel } ) + local L3 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(35,55,35), mdl = WheelModel } ) + local L4 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(-5,55,35), mdl = WheelModel } ) + local L5 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(-45,55,35), mdl = WheelModel } ) + local L6 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_LEFT, pos = Vector(-85,55,35), mdl = WheelModel } ) + local LeftWheelChain = self:CreateWheelChain( {L1, L2, L3, L4, L5, L6} ) + self:SetTrackDriveWheelLeft( L4 ) + + local R1 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(115,-55,45), mdl = WheelModel } ) + local R2 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(75,-55,25), mdl = WheelModel } ) + local R3 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(35,-55,35), mdl = WheelModel } ) + local R4 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(-5,-55,35), mdl = WheelModel } ) + local R5 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(-45,-55,35), mdl = WheelModel } ) + local R6 = self:AddWheel( { hide = true, wheeltype = LVS.WHEELTYPE_RIGHT, pos = Vector(-85,-55,35), mdl = WheelModel} ) + local RightWheelChain = self:CreateWheelChain( {R1, R2, R3, R4, R5, R6} ) + self:SetTrackDriveWheelRight( R4 ) + + local LeftTracksArmor = self:AddArmor( Vector(11,55,8), Angle(0,0,0), Vector(-123,-17,-42), Vector(123,17,42), 700, self.FrontArmor ) + self:SetTrackArmorLeft( LeftTracksArmor, LeftWheelChain ) + + local RightTracksArmor = self:AddArmor( Vector(11,-55,8), Angle(0,0,0), Vector(-123,-17,-42), Vector(123,17,42), 700, self.FrontArmor ) + self:SetTrackArmorRight( RightTracksArmor, RightWheelChain ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R1, L1, R2, L2 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R3, L3, L4, R4 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_REAR, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R5, L5, R6, L6 }, + Suspension = { + Height = 20, + MaxTravel = 15, + ControlArmLength = 150, + SpringConstant = 20000, + SpringDamping = 1000, + SpringRelativeDamping = 2000, + }, + } ) + end + +else + + ENT.TrackSystemEnable = true + + ENT.TrackScrollTexture = "models/blu/tiger/track" + ENT.ScrollTextureData = { + ["$bumpmap"] = "models/blu/tiger/track_nm", + ["$phong"] = "1", + ["$phongboost"] = "0.02", + ["$phongexponent"] = "3", + ["$phongfresnelranges"] = "[1 1 1]", + ["$translate"] = "[0.0 0.0 0.0]", + ["$colorfix"] = "{255 255 255}", + ["Proxies"] = { + ["TextureTransform"] = { + ["translateVar"] = "$translate", + ["centerVar"] = "$center", + ["resultVar"] = "$basetexturetransform", + }, + ["Equals"] = { + ["srcVar1"] = "$colorfix", + ["resultVar"] = "$color", + } + } + } + + ENT.TrackLeftSubMaterialID = 1 + ENT.TrackLeftSubMaterialMul = Vector(0,-0.075,0) + + ENT.TrackRightSubMaterialID = 2 + ENT.TrackRightSubMaterialMul = Vector(0,-0.075,0) + + ENT.TrackPoseParameterLeft = "spin_wheels_left" + ENT.TrackPoseParameterLeftMul = -1.252 + + ENT.TrackPoseParameterRight = "spin_wheels_right" + ENT.TrackPoseParameterRightMul = -1.252 + + ENT.TrackSounds = "lvs/vehicles/tiger/tracks_loop.wav" + ENT.TrackHull = Vector(20,20,20) + ENT.TrackData = {} + for i = 1, 8 do + for n = 0, 1 do + local LR = n == 0 and "l" or "r" + local LeftRight = n == 0 and "left" or "right" + local data = { + Attachment = { + name = "vehicle_suspension_"..LR.."_"..i, + toGroundDistance = 40, + traceLength = 100, + }, + PoseParameter = { + name = "suspension_"..LeftRight.."_"..i, + rangeMultiplier = 1, + lerpSpeed = 25, + } + } + table.insert( ENT.TrackData, data ) + end + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/sh_turret.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/sh_turret.lua new file mode 100644 index 0000000..72bc5b9 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/sh_turret.lua @@ -0,0 +1,21 @@ + +include("entities/lvs_tank_wheeldrive/modules/sh_turret.lua") +include("entities/lvs_tank_wheeldrive/modules/sh_turret_ballistics.lua") + +ENT.TurretBallisticsProjectileVelocity = ENT.ProjectileVelocityCoaxial +ENT.TurretBallisticsMuzzleAttachment = "muzzle_coax" +ENT.TurretBallisticsViewAttachment = "sight" + +ENT.TurretAimRate = 15 + +ENT.TurretRotationSound = "vehicles/tank_turret_loop1.wav" + +ENT.TurretPitchPoseParameterName = "turret_pitch" +ENT.TurretPitchMin = -15 +ENT.TurretPitchMax = 15 +ENT.TurretPitchMul = 1 +ENT.TurretPitchOffset = 0 + +ENT.TurretYawPoseParameterName = "turret_yaw" +ENT.TurretYawMul = 1 +ENT.TurretYawOffset = 0 diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/shared.lua new file mode 100644 index 0000000..5c095d2 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodtiger/shared.lua @@ -0,0 +1,522 @@ + +ENT.Base = "lvs_tank_wheeldrive" + +ENT.PrintName = "Panzer VI Tiger" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Tanks" +ENT.VehicleSubCategory = "Heavy" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/blu/tanks/tiger_lvs.mdl" +ENT.MDL_DESTROYED = "models/blu/tanks/tiger_lvs_gib_1.mdl" + +ENT.GibModels = { + "models/blu/tanks/tiger_lvs_gib_2.mdl", + "models/blu/tanks/tiger_lvs_gib_3.mdl", + "models/blu/tanks/tiger_lvs_gib_4.mdl", +} + +ENT.AITEAM = 1 + +ENT.MaxHealth = 1500 + +--damage system +ENT.DSArmorIgnoreForce = 4000 +ENT.CannonArmorPenetration = 14500 +ENT.FrontArmor = 6000 +ENT.SideArmor = 4000 +ENT.TurretArmor = 6000 +ENT.RearArmor = 2000 +ENT.RoofArmor = 100 + + +-- ballistics +ENT.ProjectileVelocityCoaxial = 15000 +ENT.ProjectileVelocityHighExplosive = 13000 +ENT.ProjectileVelocityArmorPiercing = 16000 + +ENT.SteerSpeed = 1 +ENT.SteerReturnSpeed = 2 + +ENT.PhysicsWeightScale = 2 +ENT.PhysicsDampingSpeed = 1000 +ENT.PhysicsInertia = Vector(6000,6000,1500) + +ENT.MaxVelocity = 450 +ENT.MaxVelocityReverse = 150 + +ENT.EngineCurve = 0.1 +ENT.EngineTorque = 200 + +ENT.TransMinGearHoldTime = 0.1 +ENT.TransShiftSpeed = 0 + +ENT.TransGears = 3 +ENT.TransGearsReverse = 1 + +ENT.MouseSteerAngle = 45 + +ENT.lvsShowInSpawner = true + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/tiger/eng_idle_loop.wav", + Volume = 1, + Pitch = 70, + PitchMul = 30, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/tiger/eng_loop.wav", + Volume = 1, + Pitch = 30, + PitchMul = 100, + SoundLevel = 85, + SoundType = LVS.SOUNDTYPE_NONE, + UseDoppler = true, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-116.7,-16.67,68.88), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-116.7,16.67,68.88), + ang = Angle(-90,0,0), + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "GunnerSeat" ) + self:AddDT( "Bool", "UseHighExplosive" ) +end + +function ENT:InitWeapons() + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "muzzle_coax" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = Muzzle.Ang:Forward() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = ent.ProjectileVelocityCoaxial + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + end + weapon.StartAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + ent.SNDTurretMG:Play() + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent.SNDTurretMG ) then return end + ent.SNDTurretMG:Stop() + end + weapon.OnOverheat = function( ent ) ent:EmitSound("lvs/overheat.wav") end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "muzzle_coax" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local Start = Muzzle.Pos + + local traceTurret = util.TraceLine( { + start = Start, + endpos = Start + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + ent:PaintCrosshairCenter( MuzzlePos2D, COLOR_WHITE ) + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + weapon.OnSelect = function( ent ) + ent:TurretUpdateBallistics( ent.ProjectileVelocityCoaxial, "muzzle_coax" ) + end + self:AddWeapon( weapon ) + + + local weapon = {} + weapon.Icon = true + weapon.Ammo = 30 + weapon.Delay = 3.2 + weapon.HeatRateUp = 1 + weapon.HeatRateDown = 0.33 + weapon.OnThink = function( ent ) + if ent:GetSelectedWeapon() ~= 2 then return end + + local ply = ent:GetDriver() + + if not IsValid( ply ) then return end + + local SwitchType = ply:lvsKeyDown( "CAR_SWAP_AMMO" ) + + if ent._oldSwitchType ~= SwitchType then + ent._oldSwitchType = SwitchType + + if SwitchType then + ent:SetUseHighExplosive( not ent:GetUseHighExplosive() ) + ent:EmitSound("lvs/vehicles/tiger/cannon_unload.wav", 75, 100, 1, CHAN_WEAPON ) + ent:SetHeat( 1 ) + ent:SetOverheated( true ) + + if ent:GetUseHighExplosive() then + ent:TurretUpdateBallistics( ent.ProjectileVelocityHighExplosive ) + else + ent:TurretUpdateBallistics( ent.ProjectileVelocityArmorPiercing ) + end + end + end + end + weapon.Attack = function( ent ) + local ID = ent:LookupAttachment( "muzzle" ) + + local Muzzle = ent:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = Muzzle.Ang:Forward() + bullet.Spread = Vector(0,0,0) + bullet.EnableBallistics = true + + if ent:GetUseHighExplosive() then + bullet.Force = 500 + bullet.HullSize = 15 + bullet.Damage = 250 + bullet.SplashDamage = 1000 + bullet.SplashDamageRadius = 250 + bullet.SplashDamageEffect = "lvs_bullet_impact_explosive" + bullet.SplashDamageType = DMG_BLAST + bullet.Velocity = ent.ProjectileVelocityHighExplosive + else + bullet.Force = ent.CannonArmorPenetration + bullet.HullSize = 0 + bullet.Damage = 1250 + bullet.Velocity = ent.ProjectileVelocityArmorPiercing + end + + bullet.TracerName = "lvs_tracer_cannon" + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + local PhysObj = ent:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:ApplyForceOffset( -bullet.Dir * 150000, bullet.Src ) + end + + ent:TakeAmmo( 1 ) + + ent:PlayAnimation("shot") + + if not IsValid( ent.SNDTurret ) then return end + + ent.SNDTurret:PlayOnce( 100 + math.cos( CurTime() + ent:EntIndex() * 1337 ) * 5 + math.Rand(-1,1), 1 ) + + ent:EmitSound("lvs/vehicles/tiger/cannon_reload.wav", 75, 100, 1, CHAN_WEAPON ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local ID = ent:LookupAttachment( "muzzle" ) + + local Muzzle = ent:GetAttachment( ID ) + + if Muzzle then + local traceTurret = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + Muzzle.Ang:Forward() * 50000, + filter = ent:GetCrosshairFilterEnts() + } ) + + local MuzzlePos2D = traceTurret.HitPos:ToScreen() + + if ent:GetUseHighExplosive() then + ent:PaintCrosshairSquare( MuzzlePos2D, COLOR_WHITE ) + else + ent:PaintCrosshairOuter( MuzzlePos2D, COLOR_WHITE ) + end + + ent:LVSPaintHitMarker( MuzzlePos2D ) + end + end + weapon.OnSelect = function( ent ) + if ent:GetUseHighExplosive() then + ent:TurretUpdateBallistics( ent.ProjectileVelocityHighExplosive, "muzzle" ) + else + ent:TurretUpdateBallistics( ent.ProjectileVelocityArmorPiercing, "muzzle" ) + end + end + self:AddWeapon( weapon ) + + + local weapon = {} + weapon.Icon = Material("lvs/weapons/smoke_launcher.png") + weapon.Ammo = 3 + weapon.Delay = 1 + weapon.HeatRateUp = 1 + weapon.HeatRateDown = 0.05 + weapon.Attack = function( ent ) + ent:TakeAmmo( 1 ) + + local ID1 = ent:LookupAttachment( "muzzle_smoke_right" ) + local ID2 = ent:LookupAttachment( "muzzle_smoke_left" ) + + local Muzzle1 = ent:GetAttachment( ID1 ) + local Muzzle2 = ent:GetAttachment( ID2 ) + + if not Muzzle1 or not Muzzle2 then return end + + local Up = self:GetUp() + + ent:EmitSound("lvs/smokegrenade.wav") + + local Ang1 = Muzzle1.Ang + Ang1:RotateAroundAxis( Up, -5 ) + local grenade = ents.Create( "lvs_item_smoke" ) + grenade:SetPos( Muzzle1.Pos ) + grenade:SetAngles( Ang1 ) + grenade:Spawn() + grenade:Activate() + grenade:GetPhysicsObject():SetVelocity( Ang1:Forward() * 1000 ) + + local Ang2 = Muzzle2.Ang + Ang2:RotateAroundAxis( Up, 5 ) + local grenade = ents.Create( "lvs_item_smoke" ) + grenade:SetPos( Muzzle2.Pos ) + grenade:SetAngles( Ang2 ) + grenade:Spawn() + grenade:Activate() + grenade:GetPhysicsObject():SetVelocity( Ang2:Forward() * 1000 ) + + local Ang3 = Muzzle1.Ang + Ang3:RotateAroundAxis( Up, -15 ) + local grenade = ents.Create( "lvs_item_smoke" ) + grenade:SetPos( Muzzle1.Pos ) + grenade:SetAngles( Ang3 ) + grenade:Spawn() + grenade:Activate() + grenade:GetPhysicsObject():SetVelocity( Ang3:Forward() * 1000 ) + + + local Ang4 = Muzzle2.Ang + Ang4:RotateAroundAxis( Up, 15 ) + local grenade = ents.Create( "lvs_item_smoke" ) + grenade:SetPos( Muzzle2.Pos ) + grenade:SetAngles( Ang4 ) + grenade:Spawn() + grenade:Activate() + grenade:GetPhysicsObject():SetVelocity( Ang4:Forward() * 1000 ) + end + self:AddWeapon( weapon ) + + + local weapon = {} + weapon.Icon = Material("lvs/weapons/grenade_launcher.png") + weapon.Ammo = 1 + weapon.Delay = 1 + weapon.HeatRateUp = 1 + weapon.HeatRateDown = 1 + weapon.Attack = function( ent ) + ent:TakeAmmo( 1 ) + + ent:EmitSound("lvs/smokegrenade.wav") + + local nades = { + [1] = { + pos = Vector(82.52,61.27,74.75), + ang = Angle(-60,45,0), + }, + [2] = { + pos = Vector(82.52,-61.27,74.75), + ang = Angle(-60,-45,0), + }, + [3] = { + pos = Vector(-46.24,61.77,75.63), + ang = Angle(-60,90,0), + }, + [4] = { + pos = Vector(-46.24,-61.77,75.63), + ang = Angle(-60,-90,0), + }, + } + + for _, data in pairs( nades ) do + timer.Simple( math.Rand(0,0.2), function() + if not IsValid( ent ) then return end + + local pos = ent:LocalToWorld( data.pos ) + local ang = ent:LocalToWorldAngles( data.ang ) + + local grenade = ents.Create( "lvs_item_explosive" ) + grenade:SetPos( pos ) + grenade:SetAngles( ang ) + grenade:Spawn() + grenade:Activate() + grenade:SetAttacker( ent:GetDriver() ) + grenade:GetPhysicsObject():SetVelocity( ang:Forward() * 300 ) + end ) + end + end + self:AddWeapon( weapon ) + + + local weapon = {} + weapon.Icon = Material("lvs/weapons/tank_noturret.png") + weapon.Ammo = -1 + weapon.Delay = 0 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 0 + weapon.OnSelect = function( ent ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( false ) + end + end + weapon.OnDeselect = function( ent ) + if ent.SetTurretEnabled then + ent:SetTurretEnabled( true ) + end + end + self:AddWeapon( weapon ) + + self:AddGunnerWeapons() +end + +function ENT:GunnerInRange( Dir ) + return self:AngleBetweenNormal( self:GetForward(), Dir ) < 60 +end + +function ENT:AddGunnerWeapons() + local COLOR_RED = Color(255,0,0,255) + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not base:GunnerInRange( ent:GetAimVector() ) then + + if not IsValid( base.SNDTurretMGf ) then return true end + + base.SNDTurretMGf:Stop() + + return true + end + + local ID = base:LookupAttachment( "muzzle_machinegun" ) + + local Muzzle = base:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( Muzzle.Ang:Forward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + if not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.StartAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.FinishAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Stop() + end + weapon.OnThink = function( ent, active ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Angles = base:WorldToLocalAngles( ent:GetAimVector():Angle() ) + Angles:Normalize() + + base:SetPoseParameter("machinegun_yaw", -Angles.y ) + base:SetPoseParameter("machinegun_pitch", Angles.p ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = base:GunnerInRange( ent:GetAimVector() ) and COLOR_WHITE or COLOR_RED + + base:PaintCrosshairCenter( Pos2D, Col ) + base:LVSPaintHitMarker( Pos2D ) + end + self:AddWeapon( weapon, 2 ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/init.lua new file mode 100644 index 0000000..989f763 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/init.lua @@ -0,0 +1,79 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + self:AddDriverSeat( Vector(-29,17.5,21), Angle(0,-90,-10) ) + self:AddPassengerSeat( Vector(-11,-17.5,24), Angle(0,-90,10) ) + self:AddPassengerSeat( Vector(-45,0,24), Angle(0,-90,10) ) + + self:AddEngine( Vector(38,0,37) ) + self:AddFuelTank( Vector(-42.36,0,13.8), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL ) + + local WheelModel = "models/diggercars/willys/wh.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0.3, + BrakeFactor = 1, + }, + Wheels = { + self:AddWheel( { + pos = Vector(49.5,-27,16), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(49.5,27,16), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 0.7, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(-37,-27,12), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(-37,27,12), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + -- trailer hitch + self:AddTrailerHitch( Vector(-68,0,17), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/shared.lua new file mode 100644 index 0000000..023abff --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep/shared.lua @@ -0,0 +1,164 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "Willys Jeep" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Military" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/willys/willys.mdl" + +ENT.AITEAM = 2 + +ENT.MaxVelocity = 1200 + +ENT.EngineCurve = 0.25 +ENT.EngineTorque = 150 + +ENT.TransGears = 4 +ENT.TransGearsReverse = 1 + +ENT.HornSound = "lvs/horn2.wav" +ENT.HornPos = Vector(40,0,35) + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/willy/eng_idle_loop.wav", + Volume = 0.5, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/willy/eng_loop.wav", + Volume = 1, + Pitch = 50, + PitchMul = 100, + SoundLevel = 75, + UseDoppler = true, + }, +} + +ENT.Lights = { + { + Trigger = "main", + SubMaterialID = 0, + Sprites = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + colorB = 200, + colorA = 150, + }, + [3] = { + pos = Vector(-63.41,-20.49,21.1), + colorG = 0, + colorB = 0, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, + { + Trigger = "main", + SubMaterialID = 3, + }, + { + Trigger = "high", + Sprites = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + colorB = 200, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, + { + + Trigger = "brake", + SubMaterialID = 2, + Sprites = { + [1] = { + pos = Vector(-63.41,20.49,21.1), + colorG = 0, + colorB = 0, + colorA = 150, + }, + } + }, + { + Trigger = "fog", + SubMaterialID = 1, + Sprites = { + [1] = { + pos = Vector(61.03,14.6,28.6), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(61.03,-14.6,28.6), + colorB = 200, + colorA = 150, + }, + [3] = { + pos = Vector(53.09,26.85,35.88), + colorB = 200, + colorA = 150, + }, + }, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-59.32,13.07,12.77), + ang = Angle(0,180,0), + }, +} diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/cl_init.lua new file mode 100644 index 0000000..3c2b7de --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/cl_init.lua @@ -0,0 +1,68 @@ +include("shared.lua") + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + + if pod ~= self:GetGunnerSeat() then + return LVS:CalcView( self, ply, pos, angles, fov, pod ) + end + + if pod:GetThirdPersonMode() then + local view = {} + view.fov = fov + view.drawviewer = true + + local mn = self:OBBMins() + local mx = self:OBBMaxs() + local radius = ( mn - mx ):Length() + local radius = radius + radius * pod:GetCameraDistance() + + local clamped_angles = pod:WorldToLocalAngles( angles ) + clamped_angles.p = math.max( clamped_angles.p, -20 ) + clamped_angles = pod:LocalToWorldAngles( clamped_angles ) + + local StartPos = pos + local EndPos = StartPos - clamped_angles:Forward() * radius + clamped_angles:Up() * (radius * pod:GetCameraHeight()) + + local WallOffset = 4 + + local tr = util.TraceHull( { + start = StartPos, + endpos = EndPos, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "lvs_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.angles = angles + Angle(5,0,0) + view.origin = tr.HitPos + pod:GetUp() * 65 + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view + end + + local ZoomAttach = self:GetAttachment( self:LookupAttachment( "zoom" ) ) + local EyeAttach = self:GetAttachment( self:LookupAttachment( "eye" ) ) + + if ZoomAttach and EyeAttach then + local ZOOM = ply:lvsKeyDown( "ZOOM" ) + + local TargetZoom = ZOOM and 1 or 0 + + pod.smZoom = pod.smZoom and (pod.smZoom + (TargetZoom - pod.smZoom) * RealFrameTime() * 12) or 0 + + local Zoom = pod.smZoom + local invZoom = 1 - Zoom + + pos = ZoomAttach.Pos * Zoom + EyeAttach.Pos * invZoom + end + + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/init.lua new file mode 100644 index 0000000..c81f4f0 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/init.lua @@ -0,0 +1,96 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + self:AddDriverSeat( Vector(-29,17.5,21), Angle(0,-90,-10) ) + self:AddPassengerSeat( Vector(-11,-17.5,24), Angle(0,-90,10) ) + + local GunnerSeat = self:AddPassengerSeat( Vector(-55,0,29), Angle(0,-90,0) ) + self:SetGunnerSeat( GunnerSeat ) + + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMGf = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sherman/mg_loop.wav" ) + self.SNDTurretMGf:SetSoundLevel( 95 ) + self.SNDTurretMGf:SetParent( self, ID ) + + local DoorHandler = self:AddDoorHandler( "^mg_shot", Vector(0,0,0), Angle(0,0,0), Vector(0,0,0), Vector(0,0,0), Vector(0,0,0), Vector(0,0,0) ) + DoorHandler:SetRate( 20 ) + DoorHandler.PlayOnce = function( ent ) + ent:Open() + ent.sm_pp = 0 + end + + self.MgShot = DoorHandler + + self:AddEngine( Vector(38,0,37) ) + self:AddFuelTank( Vector(-42.36,0,13.8), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL ) + + local WheelModel = "models/diggercars/willys/wh.mdl" + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0.3, + BrakeFactor = 1, + }, + Wheels = { + self:AddWheel( { + pos = Vector(49.5,-27,16), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(49.5,27,16), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 0.7, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(-37,-27,12), + mdl = WheelModel, + mdl_ang = Angle(0,180,0), + } ), + + self:AddWheel( { + pos = Vector(-37,27,12), + mdl = WheelModel, + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + -- trailer hitch + self:AddTrailerHitch( Vector(-68,0,17), LVS.HITCHTYPE_MALE ) +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/shared.lua new file mode 100644 index 0000000..4162608 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_dodwillyjeep_mg/shared.lua @@ -0,0 +1,369 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "Willys Jeep M1919" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Military" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/willys/willys_mg.mdl" + +ENT.AITEAM = 2 + +ENT.MaxVelocity = 1200 + +ENT.EngineCurve = 0.25 +ENT.EngineTorque = 150 + +ENT.TransGears = 4 +ENT.TransGearsReverse = 1 + +ENT.HornSound = "lvs/horn2.wav" +ENT.HornPos = Vector(40,0,35) + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/willy/eng_idle_loop.wav", + Volume = 0.5, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/willy/eng_loop.wav", + Volume = 1, + Pitch = 50, + PitchMul = 100, + SoundLevel = 75, + UseDoppler = true, + }, +} + +ENT.Lights = { + { + Trigger = "main", + SubMaterialID = 0, + Sprites = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + colorB = 200, + colorA = 150, + }, + [3] = { + pos = Vector(-63.41,-20.49,21.1), + colorG = 0, + colorB = 0, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, + { + Trigger = "main", + SubMaterialID = 7, + }, + { + Trigger = "high", + Sprites = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + colorB = 200, + colorA = 150, + }, + }, + ProjectedTextures = { + [1] = { + pos = Vector(60.34,-17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + [2] = { + pos = Vector(60.34,17.52,34.46), + ang = Angle(0,0,0), + colorB = 200, + colorA = 150, + shadows = true, + }, + }, + }, + { + + Trigger = "brake", + SubMaterialID = 2, + Sprites = { + [1] = { + pos = Vector(-63.41,20.49,21.1), + colorG = 0, + colorB = 0, + colorA = 150, + }, + } + }, + { + Trigger = "fog", + SubMaterialID = 1, + Sprites = { + [1] = { + pos = Vector(61.03,14.6,28.6), + colorB = 200, + colorA = 150, + }, + [2] = { + pos = Vector(61.03,-14.6,28.6), + colorB = 200, + colorA = 150, + }, + [3] = { + pos = Vector(53.09,26.85,35.88), + colorB = 200, + colorA = 150, + }, + }, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(-59.32,13.07,12.77), + ang = Angle(0,180,0), + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "GunnerSeat" ) +end + +function ENT:InitWeapons() + self:AddGunnerWeapons() +end + +function ENT:GunnerInRange( Dir ) + local pod = self:GetGunnerSeat() + + if IsValid( pod ) and not pod:GetThirdPersonMode() then + local ply = pod:GetDriver() + + if IsValid( ply ) and ply:lvsKeyDown( "ZOOM" ) then + return true + end + end + + return self:AngleBetweenNormal( self:GetForward(), Dir ) < 50 +end + +function ENT:AddGunnerWeapons() + local COLOR_RED = Color(255,0,0,255) + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.12 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not base:GunnerInRange( ent:GetAimVector() ) then + + if not IsValid( base.SNDTurretMGf ) then return true end + + base.SNDTurretMGf:Stop() + + return true + end + + local Muzzle = base:GetAttachment( base:LookupAttachment( "muzzle" ) ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + + local ply = ent:GetDriver() + + if IsValid( ply ) and ply:lvsKeyDown( "ZOOM" ) then + local pod = ply:GetVehicle() + + if IsValid( pod ) and not pod:GetThirdPersonMode() then + bullet.Dir = Muzzle.Ang:Up() + else + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + end + else + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + end + + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ply + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( bullet.Dir ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + if not IsValid( base.MgShot ) or not IsValid( base.SNDTurretMGf ) then return end + + base.MgShot:PlayOnce() -- shoot anim + base.SNDTurretMGf:Play() -- sound + end + weapon.StartAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.FinishAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Stop() + end + weapon.OnThink = function( ent, active ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not ent:GetAI() and not IsValid( ent:GetDriver() ) then + base:SetPoseParameter("f_pitch", 15 ) + base:SetPoseParameter("f_yaw", 0 ) + + return + end + + local Angles = base:WorldToLocalAngles( ent:GetAimVector():Angle() ) + Angles:Normalize() + + base:SetPoseParameter("f_yaw", -Angles.y ) + base:SetPoseParameter("f_pitch", -Angles.p ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = base:GunnerInRange( ent:GetAimVector() ) and COLOR_WHITE or COLOR_RED + + local pod = ply:GetVehicle() + + if not IsValid( pod ) then return end + + if not ply:lvsKeyDown( "ZOOM" ) or pod:GetThirdPersonMode() then + base:PaintCrosshairCenter( Pos2D, Col ) + end + + base:LVSPaintHitMarker( Pos2D ) + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/overheat.wav") + end + self:AddWeapon( weapon, 3 ) +end + + +function ENT:CalcMainActivityPassenger( ply ) + local GunnerSeat = self:GetGunnerSeat() + + if not IsValid( GunnerSeat ) then return end + + if GunnerSeat:GetDriver() ~= ply then return end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + ply.CalcIdeal = ACT_STAND + ply.CalcSeqOverride = ply:LookupSequence( "cwalk_revolver" ) + + return ply.CalcIdeal, ply.CalcSeqOverride +end + +function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed ) + ply:SetPlaybackRate( 1 ) + + if CLIENT then + local GunnerSeat = self:GetGunnerSeat() + + if ply == self:GetDriver() then + ply:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() ) + ply:InvalidateBoneCache() + end + + if IsValid( GunnerSeat ) and GunnerSeat:GetDriver() == ply then + local Pitch = math.Remap( self:GetPoseParameter( "f_pitch" ),0,1,-15,10) + local Yaw = math.Remap( self:GetPoseParameter( "f_yaw" ),0,1,-5,5) + + ply:SetPoseParameter( "aim_pitch", Pitch * 1.5 ) + ply:SetPoseParameter( "aim_yaw", Yaw * 1.5 ) + + ply:SetPoseParameter( "head_pitch", -Pitch * 2 ) + ply:SetPoseParameter( "head_yaw", -Yaw * 3 ) + + ply:SetPoseParameter( "move_x", 0 ) + ply:SetPoseParameter( "move_y", 0 ) + + ply:InvalidateBoneCache() + end + + GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + end + + return false +end + diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/cl_init.lua new file mode 100644 index 0000000..9fcd259 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/cl_init.lua @@ -0,0 +1,9 @@ +include("shared.lua") +include("sh_tracks.lua") +include("cl_tankview.lua") + +function ENT:OnSpawn() + self:CreateBonePoseParameter( "hatch1", 59, Angle(0,0,0), Angle(0,60,0), Vector(0,0,0), Vector(0,0,0) ) + self:CreateBonePoseParameter( "hatch2", 60, Angle(0,0,0), Angle(0,60,0), Vector(0,0,0), Vector(0,0,0) ) +end + diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/cl_tankview.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/cl_tankview.lua new file mode 100644 index 0000000..b3680db --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/cl_tankview.lua @@ -0,0 +1,26 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_tankview.lua") + +function ENT:TankViewOverride( ply, pos, angles, fov, pod ) + if pod == self:GetFrontGunnerSeat() and not pod:GetThirdPersonMode() then + local ID = self:LookupAttachment( "f_eye" ) + + local Muzzle = self:GetAttachment( ID ) + + if Muzzle then + pos = Muzzle.Pos + end + end + + if pod == self:GetRearGunnerSeat() and not pod:GetThirdPersonMode() then + local ID = self:LookupAttachment( "r_eye" ) + + local Muzzle = self:GetAttachment( ID ) + + if Muzzle then + pos = Muzzle.Pos + end + end + + return pos, angles, fov +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/init.lua new file mode 100644 index 0000000..c05e3f9 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/init.lua @@ -0,0 +1,97 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "sh_tracks.lua" ) +AddCSLuaFile( "cl_tankview.lua" ) +include("shared.lua") +include("sh_tracks.lua") + +-- since this is based on a tank we need to reset these to default var values: +ENT.DSArmorDamageReductionType = DMG_BULLET + DMG_CLUB +ENT.DSArmorIgnoreDamageType = DMG_SONIC + + +function ENT:OnSpawn( PObj ) + + local ID = self:LookupAttachment( "f_muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMGf = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sdkfz250/mg_loop.wav" ) + self.SNDTurretMGf:SetSoundLevel( 95 ) + self.SNDTurretMGf:SetParent( self, ID ) + + local ID = self:LookupAttachment( "r_muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.SNDTurretMGt = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "lvs/vehicles/sdkfz250/mg_loop.wav" ) + self.SNDTurretMGt:SetSoundLevel( 95 ) + self.SNDTurretMGt:SetParent( self, ID ) + + local DriverSeat = self:AddDriverSeat( Vector(-10,16,15), Angle(0,-90,0) ) + local GunnerSeat = self:AddPassengerSeat( Vector(-30,0,25), Angle(0,-90,0) ) + local TopGunnerSeat = self:AddPassengerSeat( Vector(-35,0,23), Angle(0,90,0) ) + local PassengerSeat = self:AddPassengerSeat( Vector(0,-16,22), Angle(0,-90,10) ) + local PassengerSeat = self:AddPassengerSeat( Vector(-43,-14,24), Angle(0,-90,10) ) + local PassengerSeat = self:AddPassengerSeat( Vector(-50,14,32), Angle(0,180,0) ) + local PassengerSeat = self:AddPassengerSeat( Vector(-35,14,32), Angle(0,180,0) ) + local PassengerSeat = self:AddPassengerSeat( Vector(-20,14,32), Angle(0,180,0) ) + + self:SetFrontGunnerSeat( GunnerSeat ) + self:SetRearGunnerSeat( TopGunnerSeat ) + + local DoorHandler = self:AddDoorHandler( "trunk", Vector(-81.17,11.74,44.44), Angle(19,0,0), Vector(-1,-15,-15), Vector(1,15,15), Vector(-30,-15,-15), Vector(1,15,15) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) + DoorHandler:LinkToSeat( DriverSeat ) + + local DoorHandler = self:AddDoorHandler( "hatch", Vector(53.89,0.17,47.28), Angle(-80,0,0), Vector(-1,-15,-15), Vector(1,15,15), Vector(-30,-15,-15), Vector(1,15,15) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) + + local Engine = self:AddEngine( Vector(50,0,45) ) + Engine:SetDoorHandler( DoorHandler ) + + local DoorHandler = self:AddDoorHandler( "!hatch1", Vector(9.55,13.84,55.75), Angle(0,0,0), Vector(-3,-10,-3), Vector(3,10,3), Vector(-3,-10,-3), Vector(3,10,3) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) + + local DoorHandler = self:AddDoorHandler( "!hatch2", Vector(9.55,-13.84,55.75), Angle(0,0,0), Vector(-3,-10,-3), Vector(3,10,3), Vector(-3,-10,-3), Vector(3,10,3) ) + DoorHandler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) + DoorHandler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) + + self:AddFuelTank( Vector(55,0,18), Angle(0,0,0), 600, LVS.FUELTYPE_DIESEL ) + + self.SNDTurretMG = self:AddSoundEmitter( Vector(-63,0,85), "lvs/vehicles/halftrack/mc_loop.wav" ) + self.SNDTurretMG:SetSoundLevel( 95 ) + + local WheelModel = "models/diggercars/sdkfz250/250_wheel.mdl" + + local FLWheel = self:AddWheel( { pos = Vector(67,32,16.5), mdl = WheelModel, mdl_ang = Angle(0,0,0), width = 2 } ) + local FRWheel = self:AddWheel( { pos = Vector(67,-32,16.5), mdl = WheelModel, mdl_ang = Angle(0,180,0), width = 2} ) + + self:CreateRigControler( "fl", FLWheel, 10, 17.7 ) + self:CreateRigControler( "fr", FRWheel, 10, 17.7 ) + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0.5, + BrakeFactor = 1, + }, + Wheels = { FLWheel, FRWheel }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + self:AddTrailerHitch( Vector(-92.9,-0.04,23.19), LVS.HITCHTYPE_MALE ) +end + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/sdkfz250/engine_start.wav" ) + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/sh_tracks.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/sh_tracks.lua new file mode 100644 index 0000000..6f1826e --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/sh_tracks.lua @@ -0,0 +1,119 @@ + +if SERVER then + function ENT:OnLeftTrackRepaired() + self:SetBodygroup(1,0) + end + + function ENT:OnLeftTrackDestroyed() + self:SetBodygroup(1,1) + end + + function ENT:OnRightTrackRepaired() + self:SetBodygroup(2,0) + end + + function ENT:OnRightTrackDestroyed() + self:SetBodygroup(2,1) + end + + function ENT:TracksCreate( PObj ) + local WheelModel = "models/diggercars/sdkfz250/250_wheel.mdl" + + local L1 = self:AddWheel( { hide = true, pos = Vector(-0,32,20), mdl = WheelModel } ) + local L2 = self:AddWheel( { hide = true, pos = Vector(-30,32,20), mdl = WheelModel } ) + local L3 = self:AddWheel( { hide = true, pos = Vector(-60,32,20), mdl = WheelModel } ) + local LeftWheelChain = self:CreateWheelChain( {L1, L2, L3} ) + self:SetTrackDriveWheelLeft( L1 ) + + local R1 = self:AddWheel( { hide = true, pos = Vector(-0,-32,20), mdl = WheelModel, mdl_ang = Angle(0,180,0) } ) + local R2 = self:AddWheel( { hide = true, pos = Vector(-30,-32,20), mdl = WheelModel, mdl_ang = Angle(0,180,0) } ) + local R3 = self:AddWheel( { hide = true, pos = Vector(-60,-32,20), mdl = WheelModel, mdl_ang = Angle(0,180,0) } ) + local RightWheelChain = self:CreateWheelChain( {R1, R2, R3} ) + self:SetTrackDriveWheelRight( R1 ) + + local LeftTracksArmor = self:AddArmor( Vector(-25,30,20), Angle(0,0,0), Vector(-50,-10,-15), Vector(50,10,15), 200, 0 ) + self:SetTrackArmorLeft( LeftTracksArmor, LeftWheelChain ) + + local RightTracksArmor = self:AddArmor( Vector(-25,-30,20), Angle(0,0,0), Vector(-50,-10,-15), Vector(50,10,15), 200, 0 ) + self:SetTrackArmorRight( RightTracksArmor, RightWheelChain ) + + self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 0.5, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { R1, L1, L2, R2, R3, L3 }, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + end +else + ENT.TrackSystemEnable = true + + ENT.TrackScrollTexture = "models/diggercars/sdkfz250/tracks" + ENT.ScrollTextureData = { + ["$bumpmap"] = "models/diggercars/shared/skin_nm", + ["$phong"] = "1", + ["$phongboost"] = "0.04", + ["$phongexponent"] = "3", + ["$phongfresnelranges"] = "[1 1 1]", + ["$translate"] = "[0.0 0.0 0.0]", + ["$colorfix"] = "{255 255 255}", + ["Proxies"] = { + ["TextureTransform"] = { + ["translateVar"] = "$translate", + ["centerVar"] = "$center", + ["resultVar"] = "$basetexturetransform", + }, + ["Equals"] = { + ["srcVar1"] = "$colorfix", + ["resultVar"] = "$color", + } + } + } + + ENT.TrackLeftSubMaterialID = 4 + ENT.TrackLeftSubMaterialMul = Vector(0,0.0325,0) + + ENT.TrackRightSubMaterialID = 5 + ENT.TrackRightSubMaterialMul = Vector(0,0.0325,0) + + ENT.TrackPoseParameterLeft = "spin_wheels_left" + ENT.TrackPoseParameterLeftMul = -1.8 + + ENT.TrackPoseParameterRight = "spin_wheels_right" + ENT.TrackPoseParameterRightMul = -1.8 + + ENT.TrackSounds = "lvs/vehicles/halftrack/tracks_loop.wav" + ENT.TrackHull = Vector(5,5,5) + ENT.TrackData = {} + + for i = 1, 4 do + for n = 0, 1 do + local LR = n == 0 and "l" or "r" + local LeftRight = n == 0 and "left" or "right" + local data = { + Attachment = { + name = "vehicle_suspension_"..LR.."_"..i, + toGroundDistance = 29, + traceLength = 150, + }, + PoseParameter = { + name = "suspension_"..LeftRight.."_"..i, + rangeMultiplier = -1.25, + lerpSpeed = 25, + } + } + table.insert( ENT.TrackData, data ) + end + end +end diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/shared.lua new file mode 100644 index 0000000..117a60c --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_sdkfz250/shared.lua @@ -0,0 +1,397 @@ + +ENT.Base = "lvs_tank_wheeldrive" + +ENT.PrintName = "Schuetzenpanzerwagen" +ENT.Author = "Luna" +ENT.Information = "Luna's Vehicle Script" +ENT.Category = "[LVS] - Cars" + +ENT.VehicleCategory = "Cars" +ENT.VehicleSubCategory = "Armored" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/diggercars/sdkfz250/2501.mdl" +ENT.MDL_DESTROYED = "models/diggercars/sdkfz250/250_dead.mdl" + +ENT.AITEAM = 1 + +ENT.DSArmorIgnoreForce = 1000 + +ENT.MaxHealth = 650 +ENT.MaxHealthEngine = 400 +ENT.MaxHealthFuelTank = 100 + +--damage system +ENT.CannonArmorPenetration = 2700 + +ENT.MaxVelocity = 700 +ENT.MaxVelocityReverse = 250 + +ENT.EngineCurve = 0.7 +ENT.EngineTorque = 100 + +ENT.PhysicsWeightScale = 1.5 +ENT.PhysicsInertia = Vector(2500,2500,850) + +ENT.TransGears = 3 +ENT.TransGearsReverse = 1 + +ENT.lvsShowInSpawner = true + +ENT.HornSound = "lvs/horn1.wav" +ENT.HornPos = Vector(70,0,40) + +ENT.EngineSounds = { + { + sound = "lvs/vehicles/sdkfz250/eng_idle_loop.wav", + Volume = 0.5, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "lvs/vehicles/sdkfz250/eng_loop.wav", + Volume = 1, + Pitch = 20, + PitchMul = 100, + SoundLevel = 85, + SoundType = LVS.SOUNDTYPE_REV_UP, + UseDoppler = true, + }, + { + sound = "lvs/vehicles/sdkfz250/eng_revdown_loop.wav", + Volume = 1, + Pitch = 20, + PitchMul = 100, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_REV_DOWN, + UseDoppler = true, + }, +} + +ENT.ExhaustPositions = { + { + pos = Vector(35.31,39.22,26.35), + ang = Angle(0,90,0), + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "FrontGunnerSeat" ) + self:AddDT( "Entity", "RearGunnerSeat" ) + self:AddDT( "Bool", "UseHighExplosive" ) +end + +function ENT:InitWeapons() + self:AddGunnerWeapons() + self:AddTopGunnerWeapons() +end + + +function ENT:GunnerInRange( Dir ) + return self:AngleBetweenNormal( self:GetForward(), Dir ) < 35 +end + +function ENT:AddGunnerWeapons() + local COLOR_RED = Color(255,0,0,255) + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not base:GunnerInRange( ent:GetAimVector() ) then + + if not IsValid( base.SNDTurretMGf ) then return true end + + base.SNDTurretMGf:Stop() + + return true + end + + local ID = base:LookupAttachment( "f_muzzle" ) + + local Muzzle = base:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( Muzzle.Ang:Forward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + base:PlayAnimation( "shot_f" ) + + if not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.StartAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Play() + end + weapon.FinishAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGf ) then return end + + base.SNDTurretMGf:Stop() + end + weapon.OnThink = function( ent, active ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not ent:GetAI() and not IsValid( ent:GetDriver() ) then + base:SetPoseParameter("f_pitch", 15 ) + base:SetPoseParameter("f_yaw", 0 ) + + return + end + + local Angles = base:WorldToLocalAngles( ent:GetAimVector():Angle() ) + Angles:Normalize() + + base:SetPoseParameter("f_yaw", -Angles.y ) + base:SetPoseParameter("f_pitch", -Angles.p ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = base:GunnerInRange( ent:GetAimVector() ) and COLOR_WHITE or COLOR_RED + + base:PaintCrosshairCenter( Pos2D, Col ) + base:LVSPaintHitMarker( Pos2D ) + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/overheat.wav") + end + self:AddWeapon( weapon, 2 ) +end + +function ENT:TopGunnerInRange( ent ) + local AimPos = ent:GetEyeTrace().HitPos + local AimAng = (AimPos - self:LocalToWorld( Vector(-72.27,0.06,66.07) )):Angle() + + local _, Ang = WorldToLocal( AimPos, AimAng, Vector(-72.27,0,66), self:LocalToWorldAngles( Angle(0,180,0) ) ) + + return math.abs( Ang.y ) < 35 and Ang.p < 10 and Ang.p > -80 +end + +function ENT:AddTopGunnerWeapons() + local COLOR_RED = Color(255,0,0,255) + local COLOR_WHITE = Color(255,255,255,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not base:TopGunnerInRange( ent ) then + + if not IsValid( base.SNDTurretMGt ) then return true end + + base.SNDTurretMGt:Stop() + + return true + end + + local ID = base:LookupAttachment( "r_muzzle" ) + + local Muzzle = base:GetAttachment( ID ) + + if not Muzzle then return end + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = (ent:GetEyeTrace().HitPos - bullet.Src):GetNormalized() + bullet.Spread = Vector(0.01,0.01,0.01) + bullet.TracerName = "lvs_tracer_yellow_small" + bullet.Force = 10 + bullet.EnableBallistics = true + bullet.HullSize = 0 + bullet.Damage = 25 + bullet.Velocity = 15000 + bullet.Attacker = ent:GetDriver() + ent:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( bullet.Src ) + effectdata:SetNormal( Muzzle.Ang:Forward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_muzzle", effectdata ) + + ent:TakeAmmo( 1 ) + + base:PlayAnimation( "shot_r" ) + + if not IsValid( base.SNDTurretMGt ) then return end + + base.SNDTurretMGt:Play() + end + weapon.StartAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGt ) then return end + + base.SNDTurretMGt:Play() + end + weapon.FinishAttack = function( ent ) + local base = ent:GetVehicle() + + if not IsValid( base ) or not IsValid( base.SNDTurretMGt ) then return end + + base.SNDTurretMGt:Stop() + end + weapon.OnThink = function( ent, active ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + if not ent:GetAI() and not IsValid( ent:GetDriver() ) then + base:SetPoseParameter("r_pitch", 80 ) + base:SetPoseParameter("r_yaw", 0 ) + + return + end + + local AimPos = ent:GetEyeTrace().HitPos + local AimAng = (AimPos - base:LocalToWorld( Vector(-72.27,0.06,66.07) )):Angle() + + local Pos, Ang = WorldToLocal( AimPos, AimAng, Vector(-72.27,0,66), base:LocalToWorldAngles( Angle(0,180,0) ) ) + + base:SetPoseParameter("r_pitch", -Ang.p ) + base:SetPoseParameter("r_yaw", -Ang.y ) + end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetVehicle() + + if not IsValid( base ) then return end + + local Pos2D = ent:GetEyeTrace().HitPos:ToScreen() + + local Col = base:TopGunnerInRange( ent ) and COLOR_WHITE or COLOR_RED + + base:PaintCrosshairCenter( Pos2D, Col ) + base:LVSPaintHitMarker( Pos2D ) + end + weapon.OnOverheat = function( ent ) + ent:EmitSound("lvs/overheat.wav") + end + self:AddWeapon( weapon, 3 ) +end + + +function ENT:CalcMainActivityPassenger( ply ) + local FrontGunnerSeat = self:GetFrontGunnerSeat() + local RearGunnerSeat = self:GetRearGunnerSeat() + + if not IsValid( FrontGunnerSeat ) or not IsValid( RearGunnerSeat ) then return end + + if FrontGunnerSeat:GetDriver() ~= ply and RearGunnerSeat:GetDriver() ~= ply then return end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + ply.CalcIdeal = ACT_STAND + ply.CalcSeqOverride = ply:LookupSequence( "cwalk_revolver" ) + + return ply.CalcIdeal, ply.CalcSeqOverride +end + +function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed ) + ply:SetPlaybackRate( 1 ) + + if CLIENT then + local FrontGunnerSeat = self:GetFrontGunnerSeat() + local RearGunnerSeat = self:GetRearGunnerSeat() + + if ply == self:GetDriver() then + ply:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() ) + ply:InvalidateBoneCache() + end + + if IsValid( FrontGunnerSeat ) and FrontGunnerSeat:GetDriver() == ply then + local Pitch = math.Remap( self:GetPoseParameter( "f_pitch" ),0,1,-15,15) + local Yaw = math.Remap( self:GetPoseParameter( "f_yaw" ),0,1,-35,35) + + ply:SetPoseParameter( "aim_pitch", Pitch * 1.5 ) + ply:SetPoseParameter( "aim_yaw", Yaw * 1.5 ) + + ply:SetPoseParameter( "head_pitch", -Pitch * 2 ) + ply:SetPoseParameter( "head_yaw", -Yaw * 3 ) + + ply:SetPoseParameter( "move_x", 0 ) + ply:SetPoseParameter( "move_y", 0 ) + + ply:InvalidateBoneCache() + end + + if IsValid( RearGunnerSeat ) and RearGunnerSeat:GetDriver() == ply then + local Pitch = math.Remap( self:GetPoseParameter( "r_pitch" ),0,1,-15,15) + local Yaw = math.Remap( self:GetPoseParameter( "r_yaw" ),0,1,-35,35) + + ply:SetPoseParameter( "aim_pitch", Pitch * 3 - 10 ) + ply:SetPoseParameter( "aim_yaw", Yaw * 1.5 ) + + ply:SetPoseParameter( "head_pitch", -Pitch * 2 ) + ply:SetPoseParameter( "head_yaw", -Yaw * 3 ) + + ply:SetPoseParameter( "move_x", 0 ) + ply:SetPoseParameter( "move_y", 0 ) + + ply:InvalidateBoneCache() + end + + GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + end + + return false +end + diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/cl_init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/cl_init.lua new file mode 100644 index 0000000..494629d --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/cl_init.lua @@ -0,0 +1,67 @@ +include("shared.lua") + +function ENT:UpdatePoseParameters( steer, speed_kmh, engine_rpm, throttle, brake, handbrake, clutch, gear, temperature, fuel, oil, ammeter ) + self:SetPoseParameter( "vehicle_steer", steer ) -- keep default behavior + + --[[ add your gauges: + + self:SetPoseParameter( "tacho_gauge", engine_rpm / 8000 ) + self:SetPoseParameter( "temp_gauge", temperature ) + self:SetPoseParameter( "fuel_gauge", fuel ) + self:SetPoseParameter( "oil_gauge", oil ) + self:SetPoseParameter( "alt_gauge", ammeter ) + self:SetPoseParameter( "vehicle_gauge", speed_kmh / 240 ) + self:SetPoseParameter( "throttle_pedal", throttle ) + self:SetPoseParameter( "brake_pedal", brake ) + self:SetPoseParameter( "handbrake_pedal", handbrake ) + self:SetPoseParameter( "clutch_pedal", clutch ) + ]] + -- no need to call invalidatebonecache. Its called automatically after this function. +end + +--[[ + +function ENT:OnSpawn() +end + +-- use this instead of ENT:OnRemove +function ENT:OnRemoved() +end + +-- use this instead of ENT:Think() +function ENT:OnFrame() +end + +function ENT:LVSPreHudPaint( X, Y, ply ) + return true -- return false to prevent original hud paint from running +end + +-- called when the engine is turned on or off +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/generic/engine_start1.wav", 75, 100, LVS.EngineVolume ) + else + self:EmitSound( "vehicles/jetski/jetski_off.wav", 75, 100, LVS.EngineVolume ) + end +end + +-- called when either an ai is activated/deactivated or when a player is sitting/exiting the driver seat +function ENT:OnActiveChanged( Active ) +end + +function ENT:CalcViewOverride( ply, pos, angles, fov, pod ) + return pos, angles, fov +end + +function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + return LVS:CalcView( self, ply, pos, angles, fov, pod ) +end +]] diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/init.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/init.lua new file mode 100644 index 0000000..cb80c81 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/init.lua @@ -0,0 +1,348 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +--[[ +ENT.WaterLevelPreventStart = 1 -- at this water level engine can not start +ENT.WaterLevelAutoStop = 2 -- at this water level (on collision) the engine will stop +ENT.WaterLevelDestroyAI = 2 -- at this water level (on collision) the AI will self destruct +]] + +--ENT.PivotSteerEnable = false -- uncomment and set to "true" to enable pivot steering (tank steering on the spot) +--ENT.PivotSteerWheelRPM = 40 -- how fast the wheels rotate during pivot steer + +-- use this instead of ENT:Initialize() +function ENT:OnSpawn( PObj ) + --[[ basics ]] + self:AddDriverSeat( Vector(-10,0,-15), Angle(0,-90,0) ) -- self:AddDriverSeat( Position, Angle ) -- add a driver seat (max 1) + -- Pod.ExitPos = Vector(0,0,100) -- change exit position + -- Pod.HidePlayer = true -- should the player in this pod be invisible? + + -- local Pod = self:AddPassengerSeat( Position, Angle ) -- add a passenger seat (no limit) + -- Pod.ExitPos = Vector(0,0,100) -- change exit position + -- Pod.HidePlayer = true -- should the player in this pod be invisible? + + + --[[ engine sound / effects ]] + self:AddEngine( Vector(0,0,0) ) -- add a engine. This is used for sounds and effects and is required to get accurate RPM for the gauges. + --local Engine = self:AddEngine( vector_pos, angle_ang, mins, maxs ) + --Engine:SetDoorHandler( DoorHandler ) -- link it to a doorhandler as requirement for the repair tool + + --[[ fuel system ]] + -- self:AddFuelTank( pos, ang, tanksize, fueltype, mins, maxs ) -- adds a fuel tank. + --[[ + fueltypes: + LVS.FUELTYPE_PETROL + LVS.FUELTYPE_DIESEL + LVS.FUELTYPE_ELECTRIC + + tanksize is how many seconds@fullthrottle you can drive. Not in liter. + ]] + --Example: + self:AddFuelTank( Vector(0,0,0), Angle(0,0,0), 600, LVS.FUELTYPE_PETROL ) + + + --[[ damage system ]] + --[[ + -- The fuel tank internally registers a critical hitpoint that catches the vehicle on fire when damaged. You can use this same system to create your own damage behaviors like this: + self:AddDS( { + pos = Vector(0,0,0), + ang = Angle(0,0,0), + mins = Vector(-40,-20,-30), + maxs = Vector(40,20,30), + Callback = function( tbl, ent, dmginfo ) + --dmginfo:ScaleDamage( 15 ) -- this would scale damage *15 when this critical hitpoint has been hit + end + } ) + + -- you can also add armor spots using this method. If the bullet trace hits this box first, it will not hit the critical hit point: + self:AddDSArmor( { + pos = Vector(-70,0,35), + ang = Angle(0,0,0), + mins = Vector(-10,-40,-30), + maxs = Vector(10,40,30), + Callback = function( tbl, ent, dmginfo ) + -- armor also has a callback. You can set damage to 0 here for example: + dmginfo:ScaleDamage( 0 ) + end + } ) + + NOTE: !!DS parts are inactive while the vehicle has shield!! + + + + -- in addition to DS-parts LVS-Cars has a inbuild Armor system: + + self:AddArmor( pos, ang, mins, maxs, health, num_force_ignore ) + + -- num_force_ignore is the bullet force. Value here gets added to general immunity variable ENT.DSArmorIgnoreForce (see shared.lua) + ]] + + + + --[[ sound emitters ]] + -- self.SoundEmitter = self:AddSoundEmitter( Position, string_path_exterior_sound, string_path_interior_sound ) -- add a sound emitter + -- self.SoundEmitter:SetSoundLevel( 95 ) -- set sound level (95 is good for weapons) + + -- self.SoundEmitter:Play() -- start looping sound (use this in weapon start attack for example) + -- self.SoundEmitter:Stop() -- stop looping sound (use this in weapon stop attack for example) + + -- self.SoundEmitter:PlayOnce( pitch, volume ) -- or play a non-looped sound in weapon attack (do not use looped sound files with this, they will never stop) + + + --[[ items ]] + --self:AddTurboCharger() -- equip a turbo charger? + --self:AddSuperCharger() -- equip a super charger? + + + --[[ wheels ]] + --[[ + -- add a wheel: + local WheelEntity1 = self:AddWheel( { + -- radius = 12, -- (leave this commented-out to let the system figure this out by itself) + -- width = 3, -- tire witdh used for skidmarks + pos = Vector(0,0,0), + mdl = "path/to/model.mdl", + mdl_ang = Angle(0,0,0), -- use this to match model orientation with wheel rotation + + MaxHealth = 100, -- changes max health of wheel + DSArmorIgnoreForce = 0, -- changes the damage force that is needed to damage this wheel + + --camber = 0, -- camber alignment + --caster = 0, -- caster alignment + --toe = 0, -- toe alignment + + --hide = false, -- hide this wheel?, NOTE: if developer convar is set to 1 this will have no effect for debugging purposes. + + -- wheeltype = LVS.WHEELTYPE_NONE -- this is only used when ENT.PivotSteerEnable is set to true. It can be either LVS.WHEELTYPE_LEFT or LVS.WHEELTYPE_RIGHT depending on which direction you want it to spin + } ), + + !!NOTE!! + adding a wheel will not make the vehicle functional. It requires to be linked to an axle using self:DefineAxle() + ]] + + + --[[ axles ]] + --[[ + local Axle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), -- angle which is considered "forward" with this axle. Use this to fix misaligned body props. + + SteerType = STEER_TYPE, + -- STEER_TYPE: + -- LVS.WHEEL_STEER_NONE -- non steering wheel + -- LVS.WHEEL_STEER_FRONT -- a wheel that turns left when left key is pressed + -- LVS.WHEEL_STEER_REAR -- ..turns right when left key is pressed + + SteerAngle = 30, -- max steer angle, unused in steer type LVS.WHEEL_STEER_NONE + + TorqueFactor = 0.3, -- TorqueFactor is how much torque is applied to this axle. 1 = all power, 0 = no power. Ideally all Axis combined equal 1. + --So if you make front wheels have TorqueFactor 0.3 you set rear axle to 0.7 + --If Front Axle 0 set Rear Axle 1 ect.. you get the idea + + BrakeFactor = 1, -- how strong the brakes on this axle are. Just leave it at 1 + + --UseHandbrake = true, -- is this axle using the handbrake? + }, + + Wheels = {WheelEntity1, WheelEntity2, ... ect }, -- link wheels to this axle. Can be any amount in any order + + Suspension = { + Height = 6, -- suspension height. Ideally less than MaxTravel so the suspension doesnt always bump into its limiter. If it sags into the limiter you need more SpringConstant + MaxTravel = 7, -- limits the suspension travel. + ControlArmLength = 25, -- changes the size of the control arm. (changes the arc in which the axle is rotating) + SpringConstant = 20000, -- the strength of the spring. Max value is 50000 everything above has no effect. This is the reason you should NOT use realistic mass but instead change INERTIA ONLY to simulate heavier vehicles. + SpringDamping = 2000, -- damping of the spring, same as what you set in your elastic tool. + SpringRelativeDamping = 2000, -- relative damping of the spring, same as what you set in your elastic tool. If you dont know what it does just set it to the same as SpringDamping + }, + } ) + ]] + + -- example: + local WheelModel = "models/props_vehicles/tire001c_car.mdl" + + local WheelFrontLeft = self:AddWheel( { pos = Vector(60,30,-15), mdl = WheelModel } ) + local WheelFrontRight = self:AddWheel( { pos = Vector(60,-30,-15), mdl = WheelModel } ) + + local WheelRearLeft = self:AddWheel( {pos = Vector(-60,30,-15), mdl = WheelModel} ) + local WheelRearRight = self:AddWheel( {pos = Vector(-60,-30,-15), mdl = WheelModel} ) + + local SuspensionSettings = { + Height = 6, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + } + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0.3, + BrakeFactor = 1, + }, + Wheels = { WheelFrontLeft, WheelFrontRight }, + Suspension = SuspensionSettings, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 0.7, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { WheelRearLeft, WheelRearRight }, + Suspension = SuspensionSettings, + } ) + + -- example 2 (rear axle only). If this looks cleaner to you: + --[[ + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = Angle(0,0,0), + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 0.7, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = { + self:AddWheel( { + pos = Vector(-60,30,-15), + mdl = "models/props_vehicles/tire001c_car.mdl", + mdl_ang = Angle(0,0,0), + } ), + + self:AddWheel( { + pos = Vector(-60,-30,-15), + mdl = "models/props_vehicles/tire001c_car.mdl", + mdl_ang = Angle(0,0,0), + } ), + }, + Suspension = { + Height = 6, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + ) + ]] + + +-- example 3, prop_vehicle_jeep rigged model method using +--[[ + local FrontRadius = 15 + local RearRadius = 15 + local FL, FR, RL, RR, ForwardAngle = self:AddWheelsUsingRig( FrontRadius, RearRadius ) + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = ForwardAngle, + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + }, + Wheels = {FL,FR}, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = ForwardAngle, + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = {RL,RR}, + Suspension = { + Height = 15, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) +]] + +-- example 4, rigged wheels with visible prop wheels: +--[[ + local data = { + mdl_fr = "models/diggercars/dodge_charger/wh.mdl", + mdl_ang_fr = Angle(0,0,0), + mdl_fl = "models/diggercars/dodge_charger/wh.mdl", + mdl_ang_fl = Angle(0,180,0), + mdl_rl = "models/diggercars/dodge_charger/wh.mdl", + mdl_ang_rl = Angle(0,0,0), + mdl_rr = "models/diggercars/dodge_charger/wh.mdl", + mdl_ang_rr = Angle(0,180,0), + } + + local FrontRadius = 15 + local RearRadius = 15 + local FL, FR, RL, RR, ForwardAngle = self:AddWheelsUsingRig( FrontRadius, RearRadius, data ) + + local FrontAxle = self:DefineAxle( { + Axle = { + ForwardAngle = ForwardAngle, + SteerType = LVS.WHEEL_STEER_FRONT, + SteerAngle = 30, + TorqueFactor = 0, + BrakeFactor = 1, + }, + Wheels = {FL,FR}, + Suspension = { + Height = 10, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) + + local RearAxle = self:DefineAxle( { + Axle = { + ForwardAngle = ForwardAngle, + SteerType = LVS.WHEEL_STEER_NONE, + TorqueFactor = 1, + BrakeFactor = 1, + UseHandbrake = true, + }, + Wheels = {RL,RR}, + Suspension = { + Height = 15, + MaxTravel = 7, + ControlArmLength = 25, + SpringConstant = 20000, + SpringDamping = 2000, + SpringRelativeDamping = 2000, + }, + } ) +]] +end + +--[[ +function ENT:OnSuperCharged( enabled ) + -- called when supercharger is equipped/unequipped +end + +function ENT:OnTurboCharged( enabled ) + -- called when turbocharger is equipped/unequipped +end +]] diff --git a/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/shared.lua b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/shared.lua new file mode 100644 index 0000000..c1d5569 --- /dev/null +++ b/garrysmod/addons/lvs_cars/lua/entities/lvs_wheeldrive_template/shared.lua @@ -0,0 +1,362 @@ + +ENT.Base = "lvs_base_wheeldrive" + +ENT.PrintName = "template script" +ENT.Author = "*your name*" +ENT.Information = "" +ENT.Category = "[LVS] *your category*" + +ENT.Spawnable = false -- set to "true" to make it spawnable +ENT.AdminSpawnable = false + +ENT.SpawnNormalOffset = 40 -- spawn normal offset, raise to prevent spawning into the ground +--ENT.SpawnNormalOffsetSpawner = 0 -- offset for ai vehicle spawner + +ENT.MDL = "models/props_interiors/Furniture_Couch02a.mdl" +--ENT.MDL_DESTROYED = "models/props_interiors/Furniture_Couch02a.mdl" +--[[ +ENT.GibModels = { + "models/gibs/manhack_gib01.mdl", + "models/gibs/manhack_gib02.mdl", + "models/gibs/manhack_gib03.mdl", + "models/gibs/manhack_gib04.mdl", + "models/props_c17/canisterchunk01a.mdl", + "models/props_c17/canisterchunk01d.mdl", + "models/props_c17/oildrumchunk01a.mdl", + "models/props_c17/oildrumchunk01b.mdl", + "models/props_c17/oildrumchunk01c.mdl", + "models/props_c17/oildrumchunk01d.mdl", + "models/props_c17/oildrumchunk01e.mdl", +} +]] + +ENT.AITEAM = 1 +--[[ +TEAMS: + 0 = FRIENDLY TO EVERYONE + 1 = FRIENDLY TO TEAM 1 and 0 + 2 = FRIENDLY TO TEAM 2 and 0 + 3 = HOSTILE TO EVERYONE +]] + +ENT.MaxHealth = 400 -- max health +--ENT.MaxHealthEngine = 100 -- max health engine +--ENT.MaxHealthFuelTank = 100 -- max health fuel tank + +--ENT.DSArmorDamageReduction = 0.1 -- damage reduction multiplier. Damage is clamped to a minimum of 1 tho +--ENT.DSArmorDamageReductionType = DMG_BULLET + DMG_CLUB -- which damage type to damage reduce + +--ENT.DSArmorIgnoreDamageType = DMG_SONIC -- ignore this damage type completely +--ENT.DSArmorIgnoreForce = 1000 -- add general immunity against small firearms, 1000 = 10mm armor thickness +--ENT.DSArmorBulletPenetrationAdd = 250 -- changes how far bullets can cheat through the body to hit critical hitpoints and armor + + +--[[ +PLEASE READ: + Ideally you only need change: + + ENT.MaxVelocity -- to change top speed + ENT.EngineTorque -- to change acceleration speed + ENT.EngineIdleRPM -- optional: only used for rpm gauge. This will NOT change engine sound. + ENT.EngineMaxRPM -- optional: only used for rpm gauge. This will NOT change engine sound. + + ENT.TransGears -- in a sane range based on maxvelocity. Dont set 10 gears for a car that only does 10kmh this will sound like garbage. Ideally use a total of 3 - 6 gears + + I recommend keeping everything else at default settings. + (leave them commented-out or remove them from this script) +]] + +ENT.MaxVelocity = 1400 -- max velocity in forward direction in gmod-units/second +--ENT.MaxVelocityReverse = 700 -- max velocity in reverse + +--ENT.EngineCurve = 0.65 -- value goes from 0 to 1. Get into a car and type "developer 1" into the console to see the current engine curve +--ENT.EngineCurveBoostLow = 1 -- first gear torque boost multiplier +ENT.EngineTorque = 150 +ENT.EngineIdleRPM = 1000 +ENT.EngineMaxRPM = 6000 + +--ENT.ThrottleRate = 3.5 -- modify the throttle update rate, see it as the speed with which you push the pedal + +--ENT.ForceLinearMultiplier = 1 -- multiply all linear forces (such as downforce, wheel side force, ect) +--ENT.ForceAngleMultiplier = 0.5 -- multiply all angular forces such turn stability / inertia. Exception: Wheel/Engine torque. Those remain unchanged. + +ENT.TransGears = 4 -- amount of gears in forward direction. NOTE: the engine sound system calculates the gear ratios based on topspeed and amount of gears. This can not be changed. +--ENT.TransGearsReverse = 1 -- amount of gears in reverse direction +--ENT.TransMinGearHoldTime = 1 -- minimum time the vehicle should stay in a gear before allowing it to shift again. +--ENT.TransShiftSpeed = 0.3 -- in seconds. How fast the transmission handles a shift. The transmission mimics a manual shift by applying clutch, letting off throttle, releasing clutch and applying throttle again even tho it is automatic. +--ENT.TransWobble = 40 -- basically how much "play" is in the drivedrain. +--ENT.TransWobbleTime = 1.5 -- in seconds. How long after a shift or after applying throttle the engine will wobble up and down in rpm +--ENT.TransWobbleFrequencyMultiplier = 1 -- changes the frequency of the wobble +--ENT.TransShiftSound = "lvs/vehicles/generic/gear_shift.wav" change gear shift sound + +--ENT.SteerSpeed = 3 -- steer speed +--ENT.SteerReturnSpeed = 10 -- steer return speed to neutral steer + +--ENT.FastSteerActiveVelocity = 500 -- at which velocity the steering will clamp the steer angle +--ENT.FastSteerAngleClamp = 10 -- to which the steering angle is clamped to when speed is above ENT.FastSteerActiveVelocity +--ENT.FastSteerDeactivationDriftAngle = 7 -- allowed drift angle until ENT.FastSteerActiveVelocity is ignored and the steering becomes unclamped + +--ENT.SteerAssistDeadZoneAngle = 1 -- changes how much drift the counter steer system allows before interfering. 1 = 1 of drift without interfering +--ENT.SteerAssistMaxAngle = 15 -- max steering angle the counter steer system is allowed to help the player +--ENT.SteerAssistExponent = 1.5 -- an exponent to the counter steering curve. Just leave it at 1.5 +--ENT.SteerAssistMultiplier = 3 -- how "quick" the counter steer system is steering + +--ENT.MouseSteerAngle = 20 -- smaller value = more direct steer bigger value = smoother steer, just leave it at 20 +--ENT.MouseSteerExponent = 2 -- just leave it at 2. Fixes wobble. + +--ENT.PhysicsWeightScale = 1 -- this is the value you need to change in order to make a vehicle feel heavier. Just leave it at 1 unless you really need to change it +--ENT.PhysicsMass = 1000 -- do not mess with this unless you can balance everything yourself again. +--ENT.PhysicsInertia = Vector(1500,1500,750) -- do not mess with this unless you can balance everything yourself again. +--ENT.PhysicsDampingSpeed = 4000 -- do not mess with this unless you can balance everything yourself again. + +--ENT.PhysicsDampingForward = true -- internal physics damping to reduce wobble. Just keep it enabled in forward direction. +--ENT.PhysicsDampingReverse = false -- disabling this in reverse allows for a reverse 180 turn. If you want to go fast in reverse you should set this to true in order to get good stability + +--ENT.WheelPhysicsMass = 100 -- do not mess with this unless you can balance everything yourself again. +--ENT.WheelPhysicsInertia = Vector(10,8,10) -- do not mess with this unless you can balance everything yourself again. +--ENT.WheelPhysicsTireHeight = 4 -- changes the tire height. If tire is blown the wheel sink this amount into the ground. Set to 0 to disable tire damage +--[[ +-- physics friction lookup table. The default used one is 10, jeeptire +ENT.WheelPhysicsMaterials = { + [0] = "friction_00", -- 0 + [1] = "friction_10", -- 0.1 + [2] = "friction_25", -- 0.25 + [3] = "popcan", -- 0.3 + [4] = "glassbottle", -- 0.4 + [5] = "glass", -- 0.5 + [6] = "snow", -- 0.6 + [7] = "roller", -- 0.7 + [8] = "rubber", -- 0.8 + [9] = "slime", -- 0.9 + [10] = "jeeptire", -- 1.337 -- i don't believe friction in havok can go above 1, however other settings such as bouncyness and elasticity are affected by it as it seems. We use jeeptire as default even tho it technically isn't the "best" choice, but rather the most common one + [11] = "jalopytire", -- 1.337 + [12] = "phx_tire_normal", -- 3 +} +]] + +--ENT.AutoReverseVelocity = 50 -- below this velocity the transmission is allowed to automatically shift into reverse when holding the brake button + +--ENT.WheelBrakeLockupRPM = 20 -- below this wheel rpm it will engage the auto brake when the throttle is 0 + +--ENT.WheelBrakeForce = 400 -- how strong the brakes are. Just leave at 400. Allows for good braking while still allowing some turning. It has some build in ABS but it isnt perfect because even tho velocities say it isnt sliding the wheel will still visually slide in source... + +--ENT.WheelSideForce = 800 -- basically a sideways cheatforce that gives you better stability in turns. You shouldn't have to edit this. +--ENT.WheelDownForce = 500 -- wheels use jeeptire as physprop. To this a downward force is applied to increase traction. You shouldn't have to edit this. + +--ENT.AllowSuperCharger = true -- allow this vehicle to equip a supercharger? +--ENT.SuperChargerVolume = 1 -- change superchager sound volume +--ENT.SuperChargerSound = "lvs/vehicles/generic/supercharger_loop.wav" -- change supercharger sound file + +--ENT.AllowTurbo = true -- allow this vehilce to equip a turbocharger? +--ENT.TurboVolume = 1 -- change turbocharger sound volume +--ENT.TurboSound = "lvs/vehicles/generic/turbo_loop.wav" -- change turbo sound file +--ENT.TurboBlowOff = {"lvs/vehicles/generic/turbo_blowoff1.wav","lvs/vehicles/generic/turbo_blowoff1.wav"} -- change blowoff sound. If you only have one file you can just pass it as a string instead of a table. + +--ENT.DeleteOnExplode = false -- remove the vehicle when it explodes? + +--ENT.lvsAllowEngineTool = true -- alow the engine tool to be used on this vehicle? +--ENT.lvsShowInSpawner = false -- show this vehicle in vehicle spawner entity? + + --[[ +--ENT.RandomColor = {} -- table with colors to set on spawn + -- accepts colors and skin+color combo: + + -- example variant1: + ENT.RandomColor = { + Color(255,255,255), + Color(255,255,255), + Color(255,255,255), + Color(255,255,255), + Color(255,255,255), + Color(255,255,255), + } + + + -- example variant2: + ENT.RandomColor = { + { + Skin = 1, + Color = Color(255,255,255), + BodyGroups = { + [1] = 3, -- set bodygroup 1 to 3 + [5] = 7, -- set bodygroup 5 to 7 + }, + }, + { + Skin = 2, + Color = Color(255,255,255), + }, + { + Skin = 3, + Color = Color(255,255,255), + }, + { + Skin = 4, + Color = Color(255,255,255), + }, + { + Skin = 5, + Color = Color(255,255,255), + }, + { + Skin = 6, + Color = Color(255,255,255), + Wheels = { -- can also color wheels in this variant + Skin = 0, + Color = Color(255,255,0), + }, + }, + } + ]] + +--ENT.HornSound = "lvs/horn2.wav" add a horn sound +--ENT.HornSoundInterior = "lvs/horn2.wav" -- leave it commented out, that way it uses the same as ENT.HornSound +--ENT.HornPos = Vector(40,0,35) -- horn sound position + + +--[[weapons]] +function ENT:InitWeapons() + -- add a weapon: + + local weapon = {} + weapon.Icon = Material("lvs/weapons/bullet.png") + + -- overheat system: + weapon.Ammo = 1000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + + -- clip system example: + --[[ + weapon.Clip = 20 + weapon.Ammo = 60 + weapon.Delay = 0.1 + weapon.ReloadSpeed = 2 + weapon.OnReload = function( ent ) + ent:EmitSound("lvs/vehicles/sherman/cannon_reload.wav" ) + end + ]] + + weapon.Attack = function( ent ) + -- "ent" can be either the weapon handler or the vehicle(which has a integrated weapon handler) + -- "ent" is where ent:SetHeat, ent:GetHeat, ent:GetAmmo ect functions are called on. + -- for seat 1 (which is the driver), ent is equal to self (the vehicle), passenger seats usually return the weapon handler and not self. + -- if you want to be 100% sure to get the actual vehicle, just call ent:GetVehicle() it will always return the base vehicle. + + local bullet = {} + bullet.Src = ent:LocalToWorld( Vector(25,0,30) ) + bullet.Dir = ent:GetForward() + bullet.Spread = Vector( 0.015, 0.015, 0 ) + bullet.TracerName = "lvs_tracer_orange" + bullet.Force = 10 -- this divided by 100 = penetration in mm + --bullet.Force1km = 5 -- bullet force at 1km + --bullet.EnableBallistics = true -- enable ballistics? + bullet.HullSize = 15 + bullet.Damage = 10 + bullet.Velocity = 30000 + bullet.SplashDamage = 100 + bullet.SplashDamageRadius = 25 + --bullet.SplashDamageEffect = "lvs_bullet_impact" + --bullet.SplashDamageType = DMG_SONIC + --bullet.SplashDamageForce = 500 + bullet.Attacker = ent:GetDriver() + bullet.Callback = function(att, tr, dmginfo) end + + ent:LVSFireBullet( bullet ) + + ent:TakeAmmo( 1 ) + end + weapon.StartAttack = function( ent ) end + weapon.FinishAttack = function( ent ) end + weapon.OnSelect = function( ent ) end + weapon.OnDeselect = function( ent ) end + weapon.OnThink = function( ent, active ) end + weapon.OnOverheat = function( ent ) ent:EmitSound("lvs/overheat.wav") end + weapon.OnRemove = function( ent ) end + --[[ + weapon.CalcView = function( ent, ply, pos, angles, fov, pod ) + + -- build view yourself: + local view = {} + view.origin = pos + view.angles = angles + view.fov = fov + view.drawviewer = false + + return view + + --or use inbuild camera system: + --if pod:GetThirdPersonMode() then + -- pos = pos + ent:GetUp() * 100 -- move camera 100 units up in third person + --end + --return LVS:CalcView( ent, ply, pos, angles, fov, pod ) + end + ]] + --[[ + weapon.HudPaint = function( ent, X, Y, ply ) + -- hud paint that is only active when the weapon is selected + -- draw stuff like crosshair here + end + ]] + --self:AddWeapon( weapon ) -- add weapon to driver seat. Uncomment to make it useable. + + --self:AddWeapon( weapon, 2 ) -- this would register to weapon to seat 2 + --self:AddWeapon( weapon, 3 ) -- seat 3.. ect + +--[[ + -- or use presets (defined in "lvs_base\lua\lvs_framework\autorun\lvs_defaultweapons.lua"): + self.PosLMG = Vector(25,0,30) -- this is used internally as variable in LMG script + self.DirLMG = 0 -- this is used internally as variable in LMG script + self:AddWeapon( LVS:GetWeaponPreset( "LMG" ) ) +]] +end + +--[[ engine sounds ]] +-- valid SoundType's are: +-- LVS.SOUNDTYPE_IDLE_ONLY -- only plays in idle +-- LVS.SOUNDTYPE_NONE -- plays all the time except in idle +-- LVS.SOUNDTYPE_REV_UP -- plays when revving up +-- LVS.SOUNDTYPE_REV_DOWN -- plays when revving down +-- LVS.SOUNDTYPE_ALL -- plays all the time +ENT.EngineSounds = { + { + sound = "vehicles/apc/apc_idle1.wav", + Volume = 1, + Pitch = 85, + PitchMul = 25, + SoundLevel = 75, + SoundType = LVS.SOUNDTYPE_IDLE_ONLY, + }, + { + sound = "vehicles/airboat/fan_motor_fullthrottle_loop1.wav", + --sound_int = "path/to/interior/sound.wav", + Volume = 1, -- adjust volume + Pitch = 50, -- start pitch value + PitchMul = 100, -- value that gets added to Pitch at max engine rpm + SoundLevel = 75, -- if too quiet, adjust soundlevel. + SoundType = LVS.SOUNDTYPE_NONE, + UseDoppler = true, -- use doppler system? + }, +} + + +--[[ exhaust ]] +--[[ +ENT.ExhaustPositions = { + { + pos = Vector(-100.04,14.72,4.84), + ang = Angle(0,180,0), + }, + { + pos = Vector(-100.04,-14.72,4.84), + ang = Angle(0,180,0), + } +} +]] + + +--[[ lights ]] +ENT.Lights = {} +-- see: https://raw.githubusercontent.com/SpaxscE/lvs_cars/main/zzz_ENT_lights_info.lua +-- or https://discord.com/channels/1036581288653627412/1140195565368508427/1140195750207291403 diff --git a/garrysmod/addons/lvs_helicopter/data_static/lvs/2922255746.txt b/garrysmod/addons/lvs_helicopter/data_static/lvs/2922255746.txt new file mode 100644 index 0000000..b6a1692 --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/data_static/lvs/2922255746.txt @@ -0,0 +1,3 @@ +name=Helicopters +version=39 +url=https://raw.githubusercontent.com/SpaxscE/lvs_helicopters/main/data_static/lvs/2922255746.txt \ No newline at end of file diff --git a/garrysmod/addons/lvs_helicopter/lua/autorun/server/lvs_helicopters_addworkshop.lua b/garrysmod/addons/lvs_helicopter/lua/autorun/server/lvs_helicopters_addworkshop.lua new file mode 100644 index 0000000..78293f2 --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/autorun/server/lvs_helicopters_addworkshop.lua @@ -0,0 +1,2 @@ + +resource.AddWorkshop("2922255746") -- LVS Helicopters diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_gunship_body.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_gunship_body.lua new file mode 100644 index 0000000..be5729f --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_gunship_body.lua @@ -0,0 +1,65 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminOnly = false +ENT.DoNotDuplicate = true + +ENT.AutomaticFrameAdvance = true + +ENT._LVS = true + +function ENT:PlayAnimation( animation, playbackrate ) + playbackrate = playbackrate or 1 + + local sequence = self:LookupSequence( animation ) + + self:ResetSequence( sequence ) + self:SetPlaybackRate( playbackrate ) + self:SetSequence( sequence ) +end + +function ENT:GetAimVector() + local Parent = self:GetParent() + + if not IsValid( Parent ) or not Parent.GetAimVector then return -self:GetUp() end + + local Muzzle = self:GetAttachment( self:LookupAttachment( "bellygun" ) ) + + if not Muzzle then return -self:GetUp() end + + local AimAng = self:WorldToLocalAngles( (Parent:GetEyeTrace().HitPos - Muzzle.Pos):Angle() ) + + AimAng.p = math.Clamp( AimAng.p, 45, 90 ) + + return self:LocalToWorldAngles( AimAng ):Forward() +end + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/gunship.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:SetNoDraw( true ) + end + + function ENT:Think() + self:NextThink( CurTime() ) + + return true + end +else + function ENT:Initialize() + end + + function ENT:Draw() + self:DrawModel() + end + + function ENT:Think() + end + + function ENT:OnRemove() + end +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/cl_init.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/cl_init.lua new file mode 100644 index 0000000..493f2ef --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/cl_init.lua @@ -0,0 +1,109 @@ +include("shared.lua") + +function ENT:DamageFX() + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.05 + + local HP = self:GetHP() + local MaxHP = self:GetMaxHP() + + if HP > MaxHP * 0.25 then return end + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( Vector(-10,25,-10) ) ) + effectdata:SetNormal( self:GetUp() ) + effectdata:SetMagnitude( math.Rand(0.5,1.5) ) + effectdata:SetEntity( self ) + util.Effect( "lvs_exhaust_fire", effectdata ) + end +end + +function ENT:OnFrame() + self:AnimRotor() + self:AnimTail() + self:DamageFX() +end + +function ENT:RemoveLight() + if IsValid( self.projector ) then + self.projector:Remove() + self.projector = nil + end +end + +function ENT:OnRemoved() + self:RemoveLight() +end + +function ENT:AnimTail() + local Steer = self:GetSteer() + + local TargetValue = -(Steer.x + Steer.z * 2) * 10 + + self.sm_pp_rudder = self.sm_pp_rudder and (self.sm_pp_rudder + (TargetValue - self.sm_pp_rudder) * RealFrameTime() * 5) or 0 + + self:SetPoseParameter("rudder", self.sm_pp_rudder) + self:InvalidateBoneCache() +end + +function ENT:AnimRotor() + local RPM = self:GetThrottle() * 2500 + + self.RPM = self.RPM and (self.RPM + RPM * RealFrameTime() * 0.5) or 0 + + local Rot1 = Angle( -self.RPM,0,0) + Rot1:Normalize() + + local Rot2 = Angle(0,0,self.RPM) + Rot2:Normalize() + + self:ManipulateBoneAngles( 2, Rot1 ) + self:ManipulateBoneAngles( 5, Rot2 ) + self:ManipulateBoneAngles( 3, Rot2 ) +end + + +ENT.LightMaterial = Material( "effects/lvs/heli_spotlight" ) +ENT.GlowMaterial = Material( "sprites/light_glow02_add" ) + +function ENT:PreDrawTranslucent() + if not self:GetLightsEnabled() then + self:RemoveLight() + + return true + end + + local SpotLight = self:GetAttachment( self:LookupAttachment( "SpotLight" ) ) + + if not SpotLight then return true end + + if not IsValid( self.projector ) then + local thelamp = ProjectedTexture() + thelamp:SetBrightness( 5 ) + thelamp:SetTexture( "effects/flashlight/soft" ) + thelamp:SetColor( Color(255,255,255) ) + thelamp:SetEnableShadows( true ) + thelamp:SetFarZ( 2500 ) + thelamp:SetNearZ( 75 ) + thelamp:SetFOV( 60 ) + self.projector = thelamp + end + + local Dir = SpotLight.Ang:Forward() + + render.SetMaterial( self.GlowMaterial ) + render.DrawSprite( SpotLight.Pos + Dir * 5, 32, 32, Color( 255, 255, 255, 255) ) + + render.SetMaterial( self.LightMaterial ) + render.DrawBeam( SpotLight.Pos, SpotLight.Pos + Dir * 100, 32, 0, 0.99, Color( 100, 100, 100, 255) ) + + if IsValid( self.projector ) then + self.projector:SetPos( SpotLight.Pos ) + self.projector:SetAngles( SpotLight.Ang ) + self.projector:Update() + end + + return true +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/init.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/init.lua new file mode 100644 index 0000000..eb96c12 --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/init.lua @@ -0,0 +1,124 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(120,0,-40), Angle(0,-90,0) ) + DriverSeat:SetCameraDistance( 0.2 ) + + self:AddEngineSound( Vector(0,0,0) ) + + --self:AddRotor( pos, angle, radius, turn_speed_and_direction ) + self.Rotor = self:AddRotor( Vector(0,0,65), Angle(15,0,0), 310, -4000 ) + self.Rotor:SetRotorEffects( true ) + self.Rotor:SetHP( 50 ) + function self.Rotor:OnDestroyed( base ) + local ID = base:LookupBone( "Chopper.Rotor_Blur" ) + base:ManipulateBoneScale( ID, Vector(0,0,0) ) + + base:DestroyEngine() + + self:EmitSound( "physics/metal/metal_box_break2.wav" ) + end + + self.TailRotor = self:AddRotor( Vector(-218,4,-1.8), Angle(0,0,90), 25, -6000 ) + self.TailRotor:SetHP( 100 ) + function self.TailRotor:OnDestroyed( base ) + base:DestroySteering( -2.5 ) + base:SnapTailRotor() + + self:EmitSound( "physics/metal/metal_box_break2.wav" ) + end + + self:AddDS( { + pos = Vector(-218,4,-1.8), + ang = Angle(0,0,0), + mins = Vector(-45,-15,-40), + maxs = Vector(75,15,60), + Callback = function( tbl, ent, dmginfo ) + if dmginfo:GetDamage() <= 0 then return end + + ent.TailRotor:TakeDamageInfo( dmginfo ) + end + } ) + + local ID = self:LookupAttachment( "muzzle" ) + local Muzzle = self:GetAttachment( ID ) + self.weaponSND = self:AddSoundEmitter( self:WorldToLocal( Muzzle.Pos ), "npc/attack_helicopter/aheli_weapon_fire_loop3.wav", "npc/attack_helicopter/aheli_weapon_fire_loop3.wav" ) + self.weaponSND:SetSoundLevel( 110 ) + self.weaponSND:SetParent( self, ID ) +end + +function ENT:SnapTailRotor() + if self.TailDestroyed then return end + + self.TailDestroyed = true + + local ent = ents.Create( "prop_physics" ) + ent:SetModel( "models/gibs/helicopter_brokenpiece_05_tailfan.mdl" ) + ent:SetPos( self:LocalToWorld( Vector(-153.476349,1.618453,3.479492) ) ) + ent:SetAngles( self:GetAngles() ) + ent:Spawn() + ent:Activate() + ent:SetRenderMode( RENDERMODE_TRANSALPHA ) + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + self:DeleteOnRemove( ent ) + + local PhysObj = ent:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:SetVelocityInstantaneous( self:GetVelocity() ) + PhysObj:AddAngleVelocity( VectorRand() * 500 ) + PhysObj:EnableDrag( false ) + + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetStart( PhysObj:GetMassCenter() ) + effectdata:SetEntity( ent ) + effectdata:SetScale( 0.25 ) + effectdata:SetMagnitude( 5 ) + util.Effect( "lvs_firetrail", effectdata ) + + local BonesToScale = { + "Chopper.Tail", + "Chopper.Blade_Tail", + } + + for _, name in pairs( BonesToScale ) do + local ID = self:LookupBone( name ) + self:ManipulateBoneScale( ID, Vector(0,0,0) ) + end +end + +function ENT:GetMissileOffset() + return Vector(-60,0,0) +end + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/helicopter/start.wav" ) + end +end + +function ENT:OnCollision( data, physobj ) + if self:IsPlayerHolding() then return false end + + if data.Speed > 60 and data.DeltaTime > 0.2 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + if VelDif > 200 then + local part = self:FindDS( data.HitPos - data.OurOldVelocity:GetNormalized() * 25 ) + + if part then + local dmginfo = DamageInfo() + dmginfo:SetDamage( 200 ) + dmginfo:SetDamageType( DMG_CRUSH ) + part:Callback( self, dmginfo ) + end + end + end + + return false +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/shared.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/shared.lua new file mode 100644 index 0000000..374f73f --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combine/shared.lua @@ -0,0 +1,333 @@ + +ENT.Base = "lvs_base_helicopter" + +ENT.PrintName = "Combine Helicopter" +ENT.Author = "Luna" +ENT.Information = "Combine Attack Helicopter from Half Life 2 + Episodes" +ENT.Category = "[LVS] - Helicopters" + +ENT.VehicleCategory = "Helicopters" +ENT.VehicleSubCategory = "Combine" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.DisableBallistics = true + +ENT.MDL = "models/Combine_Helicopter.mdl" +ENT.GibModels = { + "models/gibs/helicopter_brokenpiece_01.mdl", + "models/gibs/helicopter_brokenpiece_02.mdl", + "models/gibs/helicopter_brokenpiece_03.mdl", + "models/gibs/helicopter_brokenpiece_06_body.mdl", + "models/gibs/helicopter_brokenpiece_04_cockpit.mdl", + "models/gibs/helicopter_brokenpiece_05_tailfan.mdl", +} + +ENT.AITEAM = 1 + +ENT.MaxHealth = 1600 + +ENT.MaxVelocity = 2150 + +ENT.ThrustUp = 1 +ENT.ThrustDown = 0.8 +ENT.ThrustRate = 1 + +ENT.ThrottleRateUp = 0.2 +ENT.ThrottleRateDown = 0.2 + +ENT.TurnRatePitch = 1 +ENT.TurnRateYaw = 1 +ENT.TurnRateRoll = 1 + +ENT.GravityTurnRateYaw = 2 + +ENT.ForceLinearDampingMultiplier = 1.5 + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +ENT.EngineSounds = { + { + sound = "^npc/attack_helicopter/aheli_rotor_loop1.wav", + --sound_int = "lvs/vehicles/helicopter/loop_interior.wav", + Pitch = 0, + PitchMin = 0, + PitchMax = 255, + PitchMul = 100, + Volume = 1, + VolumeMin = 0, + VolumeMax = 1, + SoundLevel = 125, + UseDoppler = true, + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Bool", "LightsEnabled" ) +end + +function ENT:GetAimAngles() + local Gun = self:GetAttachment( self:LookupAttachment( "gun" ) ) + + if not Gun then return end + + local trace = self:GetEyeTrace() + + local AimAngles = self:WorldToLocalAngles( (trace.HitPos - Gun.Pos):GetNormalized():Angle() ) + + return AimAngles +end + +function ENT:WeaponsInRange() + local AimAngles = self:GetAimAngles() + + return math.abs( AimAngles.y ) < 40 and AimAngles.p < 90 and AimAngles.p > -20 +end + +function ENT:SetPoseParameterTurret() + local AimAngles = self:GetAimAngles() + + self:SetPoseParameter("weapon_yaw", AimAngles.y ) + self:SetPoseParameter("weapon_pitch", -AimAngles.p ) +end + +function ENT:HandleShoot( FireInput, active ) + self.charge = self.charge or 0 + + if self.charging then + self.charge = math.min( self.charge + FrameTime() * 60, 100 ) + + if self.charge >= 100 then + self.charging = nil + end + else + if FireInput and self.charge > 0 then + self:ShootGun() + else + if FireInput then + self:ChargeGun() + else + self.charge = math.max(self.charge - FrameTime() * 120,0) + end + end + end + + local Fire = FireInput and active and self.charge > 0 and not self.charging + + if not IsValid( self.weaponSND ) then return end + + if self._oldFire ~= Fire then + self._oldFire = Fire + + if Fire then + if self.weaponSND.snd_chrg then + self.weaponSND.snd_chrg:Stop() + self.weaponSND.snd_chrg = nil + end + self.weaponSND:Play() + else + self.weaponSND:Stop() + end + end + + if not active then return end + + if Fire then + self:SetHeat( 1 - self.charge / 100 ) + else + self:SetHeat( math.Clamp(self.charge / 100,0,0.89) ) -- clamp to 0.89 so the ai doesnt detect it as overheating + end +end + +function ENT:ChargeGun() + self._doAttack = true + self.charging = true + + if not IsValid( self.weaponSND ) then return end + + self.weaponSND.snd_chrg = CreateSound( self, "NPC_AttackHelicopter.ChargeGun" ) + self.weaponSND.snd_chrg:Play() +end + +function ENT:FinishShoot() + self._doAttack = nil + self.charging = nil + + if not IsValid( self.weaponSND ) then return end + + self.weaponSND:Stop() + + if self.weaponSND.snd_chrg then + self.weaponSND.snd_chrg:Stop() + self.weaponSND.snd_chrg = nil + end +end + +function ENT:ShootGun() + local T = CurTime() + + if (self.NextFire or 0) > T then return end + + self.NextFire = T + 0.03 + + self.charge = self.charge - 0.9 + + local Muzzle = self:GetAttachment( self:LookupAttachment( "muzzle" ) ) + + if not Muzzle then return end + + local trace = self:GetEyeTrace() + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = (trace.HitPos - Muzzle.Pos):GetNormalized() + bullet.Spread = Vector(0.06,0.06,0.06) + bullet.TracerName = "lvs_pulserifle_tracer" + bullet.Force = 1000 + bullet.HullSize = 6 + bullet.Damage = 6 + bullet.Velocity = 15000 + bullet.Attacker = self:GetDriver() + bullet.Callback = function(att, tr, dmginfo) + local effectdata = EffectData() + effectdata:SetOrigin( tr.HitPos ) + effectdata:SetNormal( tr.HitNormal ) + util.Effect( "AR2Impact", effectdata, true, true ) + end + self:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( Muzzle.Pos ) + effectdata:SetNormal( Muzzle.Ang:Forward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_pulserifle_muzzle", effectdata ) +end + +function ENT:InitWeapons() + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = -1 + weapon.Delay = 0 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 0 + weapon.StartAttack = function( ent ) + ent:ChargeGun() + end + weapon.FinishAttack = function( ent ) + ent:FinishShoot() + end + weapon.Attack = function( ent ) + end + weapon.OnThink = function( ent, active ) + ent:SetPoseParameterTurret() + ent:HandleShoot( ent._doAttack and active and ent:WeaponsInRange(), active ) + end + weapon.OnSelect = function( ent ) ent:EmitSound("physics/metal/weapon_impact_soft3.wav") end + self:AddWeapon( weapon ) + + + local weapon = {} + weapon.Icon = Material("lvs/weapons/missile.png") + weapon.Ammo = 60 + weapon.Delay = 0.25 + weapon.HeatRateUp = 0.25 + weapon.HeatRateDown = 0.25 + weapon.Attack = function( ent ) + + ent.FireLeft = not ent.FireLeft + + local Driver = ent:GetDriver() + local Target = ent:GetEyeTrace().HitPos + + local projectile = ents.Create( "lvs_missile" ) + projectile:SetPos( ent:LocalToWorld( Vector(17.36,50.89 * (self.FireLeft and 1 or -1),-59.39) ) ) + projectile:SetAngles( ent:GetAngles() ) + projectile:SetParent( ent ) + projectile:Spawn() + projectile:Activate() + projectile.GetTarget = function( missile ) return missile end + projectile.GetTargetPos = function( missile ) + if missile.HasReachedTarget then + return missile:LocalToWorld( Vector(100,0,0) ) + end + + if (missile:GetPos() - Target):Length() < 100 then + missile.HasReachedTarget = true + end + return Target + end + projectile:SetAttacker( IsValid( Driver ) and Driver or self ) + projectile:SetEntityFilter( ent:GetCrosshairFilterEnts() ) + projectile:SetSpeed( ent:GetVelocity():Length() + 4000 ) + projectile:SetDamage( 400 ) + projectile:SetRadius( 150 ) + projectile:Enable() + projectile:EmitSound("npc/waste_scanner/grenade_fire.wav") + + ent:TakeAmmo() + end + weapon.OnSelect = function( ent ) + ent:EmitSound("weapons/shotgun/shotgun_cock.wav") + end + self:AddWeapon( weapon ) + + + local weapon = {} + weapon.Icon = Material("lvs/weapons/bomb.png") + weapon.UseableByAI = false + weapon.Ammo = 128 + weapon.Delay = 0.25 + weapon.HeatRateUp = -0.4 + weapon.HeatRateDown = 0.4 + weapon.StartAttack = function( ent ) + local Driver = ent:GetDriver() + + local projectile = ents.Create( "lvs_helicopter_combine_bomb" ) + projectile:SetPos( ent:LocalToWorld( Vector(-50,0,-25) ) ) + projectile:SetAngles( ent:GetAngles() ) + projectile:SetParent( ent ) + projectile:Spawn() + projectile:Activate() + projectile:SetAttacker( IsValid( Driver ) and Driver or ent ) + projectile:SetEntityFilter( ent:GetCrosshairFilterEnts() ) + projectile:SetSpeed( ent:GetVelocity() ) + projectile:SetDamage( 150 ) + projectile:SetRadius( 250 ) + + self._ProjectileEntity = projectile + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent._ProjectileEntity ) then return end + + ent._ProjectileEntity:Enable() + ent._ProjectileEntity:EmitSound("npc/attack_helicopter/aheli_mine_drop1.wav") + + ent:TakeAmmo() + + ent:SetHeat( ent:GetHeat() + 0.2 ) + + if ent:GetHeat() >= 1 then + ent:SetOverheated( true ) + end + end + self:AddWeapon( weapon ) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/light.png") + weapon.UseableByAI = false + weapon.Ammo = -1 + weapon.Delay = 0 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 1 + weapon.StartAttack = function( ent ) + if not ent.SetLightsEnabled then return end + + if ent:GetAI() then return end + + ent:SetLightsEnabled( not ent:GetLightsEnabled() ) + ent:EmitSound( "items/flashlight1.wav", 75, 105 ) + end + self:AddWeapon( weapon ) +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/cl_init.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/cl_init.lua new file mode 100644 index 0000000..6fa7ece --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/cl_init.lua @@ -0,0 +1,92 @@ +include("shared.lua") + +function ENT:PreDraw() + local Body = self:GetBody() + + if not IsValid( Body ) then return false end + + Body:DrawModel() + + return false +end + +function ENT:PreDrawTranslucent() + return false +end + +function ENT:DamageFX() + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.05 + + local HP = self:GetHP() + local MaxHP = self:GetMaxHP() + + if HP > MaxHP * 0.25 then return end + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( Vector(-60,0,-10) ) ) + effectdata:SetNormal( self:GetUp() ) + effectdata:SetMagnitude( math.Rand(0.5,1.5) ) + effectdata:SetEntity( self ) + util.Effect( "lvs_exhaust_fire", effectdata ) + end +end + +function ENT:OnFrame() + self:AnimRotor() + self:DamageFX() +end + +function ENT:AnimRotor() + local RPM = self:GetThrottle() * 2500 + + self.RPM = self.RPM and (self.RPM + RPM * RealFrameTime() * 0.5) or 0 + + local Rot = Angle( -self.RPM,0,0) + Rot:Normalize() + + local Body = self:GetBody() + + if not IsValid( Body ) then return end + + Body:ManipulateBoneAngles( 19, Rot ) +end + +function ENT:PaintCrosshairSquare( Pos2D, Col ) + local X = Pos2D.x + 1 + local Y = Pos2D.y + 1 + + local Size = 20 + + surface.SetDrawColor( 0, 0, 0, 80 ) + surface.DrawLine( X - Size, Y + Size, X - Size * 0.5, Y + Size ) + surface.DrawLine( X + Size, Y + Size, X + Size * 0.5, Y + Size ) + surface.DrawLine( X - Size, Y + Size, X - Size, Y + Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size, Y - Size * 0.5 ) + surface.DrawLine( X + Size, Y + Size, X + Size, Y + Size * 0.5 ) + surface.DrawLine( X + Size, Y - Size, X + Size, Y - Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size * 0.5, Y - Size ) + surface.DrawLine( X + Size, Y - Size, X + Size * 0.5, Y - Size ) + + if Col then + surface.SetDrawColor( Col.r, Col.g, Col.b, Col.a ) + else + surface.SetDrawColor( 255, 255, 255, 255 ) + end + + X = Pos2D.x + Y = Pos2D.y + + surface.DrawLine( X - Size, Y + Size, X - Size * 0.5, Y + Size ) + surface.DrawLine( X + Size, Y + Size, X + Size * 0.5, Y + Size ) + surface.DrawLine( X - Size, Y + Size, X - Size, Y + Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size, Y - Size * 0.5 ) + surface.DrawLine( X + Size, Y + Size, X + Size, Y + Size * 0.5 ) + surface.DrawLine( X + Size, Y - Size, X + Size, Y - Size * 0.5 ) + surface.DrawLine( X - Size, Y - Size, X - Size * 0.5, Y - Size ) + surface.DrawLine( X + Size, Y - Size, X + Size * 0.5, Y - Size ) + + self:PaintCrosshairCenter( Pos2D, Col ) +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/init.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/init.lua new file mode 100644 index 0000000..4a09739 --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/init.lua @@ -0,0 +1,160 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(200,0,0), Angle(0,-90,0) ) + DriverSeat:SetCameraDistance( 0.2 ) + DriverSeat.HidePlayer = true + + self:AddEngineSound( Vector(-133,0,55) ) + + self:DrawShadow( false ) + + local Body = ents.Create( "lvs_gunship_body" ) + Body:SetPos( self:GetPos() ) + Body:SetAngles( self:GetAngles() ) + Body:Spawn() + Body:Activate() + Body:SetParent( self ) + Body:SetSkin( 1 ) + self:DeleteOnRemove( Body ) + self:TransferCPPI( Body ) + self:SetBody( Body ) + + local Rotor = self:AddRotor( Vector(-133,0,55), Angle(0,0,0), 0, 4000 ) + Rotor:SetRotorEffects( true ) + + local ID = Body:LookupAttachment( "muzzle" ) + local Muzzle = Body:GetAttachment( ID ) + self.weaponSND = self:AddSoundEmitter( Body:WorldToLocal( Muzzle.Pos ), "npc/combine_gunship/gunship_weapon_fire_loop6.wav", "npc/combine_gunship/gunship_fire_loop1.wav" ) + self.weaponSND:SetSoundLevel( 110 ) + self.weaponSND:SetParent( Body, ID ) +end + +function ENT:SetRotor( PhysRot ) + local Body = self:GetBody() + + if not IsValid( Body ) then return end + + if self._oldPhysRot ~= PhysRot then + self._oldPhysTor = PhysRot + + if PhysRot then + Body:SetSkin( 1 ) + else + Body:SetSkin( 0 ) + end + end +end + +function ENT:OnTick() + local PhysRot = self:GetThrottle() < 0.85 + + if not self:IsEngineDestroyed() then + self:SetRotor( PhysRot ) + end + + self:AnimBody() + self:BodySounds() +end + +function ENT:BodySounds() + local T = CurTime() + + if (self._nextBodySND or 0) > T then return end + + self._nextBodySND = T + 2 + + local HP = self:GetHP() + + if self._oldHPsnd ~= HP then + if isnumber( self._oldHPsnd ) then + if self._oldHPsnd > HP and math.abs(self._oldHPsnd - HP) > 100 then + self:EmitSound("NPC_CombineGunship.Pain") + end + end + + self._oldHPsnd = HP + end + + local trace = self:GetEyeTrace() + + local SeeEnemy = IsValid( trace.Entity ) and trace.Entity ~= self._oldEnemySND and trace.Entity.LVS and self:IsEnemy( trace.Entity ) + + if SeeEnemy then + self._oldEnemySND = trace.Entity + self:EmitSound("NPC_CombineGunship.SeeEnemy") + end +end + +function ENT:AnimBody() + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + local Body = self:GetBody() + + if not IsValid( Body ) then return end + + local FT = FrameTime() + + local LocalAngles = self:WorldToLocalAngles( self:GetAimVector():Angle() ) + + local VelL = self:WorldToLocal( self:GetPos() + self:GetVelocity() ) + local AngVel = PhysObj:GetAngleVelocity() + local Steer = self:GetSteer() + + self._smLocalAngles = self._smLocalAngles and self._smLocalAngles + (LocalAngles - self._smLocalAngles) * FT * 4 or LocalAngles + self._smVelL = self._smVelL and self._smVelL + (VelL - self._smVelL) * FT * 10 or VelL + self._smAngVel = self._smAngVel and self._smAngVel + (AngVel - self._smAngVel) * FT * 10 or AngVel + self._smSteer = self._smSteer and self._smSteer + (Steer - self._smSteer) * FT * 0.2 or Steer + + Body:SetPoseParameter("flex_vert", self._smSteer.y * 10 + self._smLocalAngles.p * 0.5 ) + Body:SetPoseParameter("flex_horz", self._smAngVel.z * 0.25 - self._smSteer.x * 10 + self._smLocalAngles.y * 0.5 ) + Body:SetPoseParameter("fin_accel", self._smVelL.x * 0.0015 + self._smSteer.y * 2 + self._smVelL.z * 0.008 ) + Body:SetPoseParameter("fin_sway", -self._smVelL.y * 0.007 - self._smSteer.x * 5 ) + Body:SetPoseParameter("antenna_accel", self._smVelL.x * 0.005 ) + Body:SetPoseParameter("antenna_sway", -self._smVelL.y * 0.005 ) +end + +function ENT:FireBellyCannon() + local base = self:GetBody() + + if not IsValid( base ) then return end + + local Muzzle = base:GetAttachment( base:LookupAttachment( "bellygun" ) ) + + if not Muzzle then return end + + local traceTarget = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + base:GetAimVector() * 50000, + filter = self:GetCrosshairFilterEnts() + } ) + + local traceBrush = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + base:GetAimVector() * 50000, + mask = MASK_SOLID_BRUSHONLY, + } ) + + local Driver = self:GetDriver() + + local dmginfo = DamageInfo() + dmginfo:SetAttacker( IsValid( Driver ) and Driver or self ) + dmginfo:SetInflictor( self ) + dmginfo:SetDamage( 500 ) + dmginfo:SetDamageType( DMG_DISSOLVE ) + dmginfo:SetDamagePosition( traceBrush.HitPos ) + + util.BlastDamageInfo( dmginfo, traceBrush.HitPos, 500 ) + + local HitEnt = traceTarget.Entity + + if not IsValid( HitEnt ) then return end + + dmginfo:SetDamage( 5000 ) + + HitEnt:TakeDamageInfo( dmginfo ) +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/shared.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/shared.lua new file mode 100644 index 0000000..e0984da --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_combinegunship/shared.lua @@ -0,0 +1,247 @@ + +ENT.Base = "lvs_base_helicopter" + +ENT.PrintName = "Combine Gunship" +ENT.Author = "Luna" +ENT.Information = "Combine Synth Gunship from Half Life 2 + Episodes" +ENT.Category = "[LVS] - Helicopters" + +ENT.VehicleCategory = "Helicopters" +ENT.VehicleSubCategory = "Combine" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.DisableBallistics = true + +ENT.MDL = "models/blu/combine_gunship.mdl" +ENT.GibModels = { + "models/gibs/gunship_gibs_engine.mdl", + "models/gibs/gunship_gibs_eye.mdl", + "models/gibs/gunship_gibs_headsection.mdl", + "models/gibs/gunship_gibs_midsection.mdl", + "models/gibs/gunship_gibs_nosegun.mdl", + "models/gibs/gunship_gibs_sensorarray.mdl", + "models/gibs/gunship_gibs_tailsection.mdl", + "models/gibs/gunship_gibs_wing.mdl", +} + +ENT.AITEAM = 1 + +ENT.MaxHealth = 1600 + +ENT.MaxVelocity = 2150 + +ENT.ThrustUp = 1 +ENT.ThrustDown = 0.8 +ENT.ThrustRate = 1 + +ENT.ThrottleRateUp = 0.2 +ENT.ThrottleRateDown = 0.2 + +ENT.TurnRatePitch = 1 +ENT.TurnRateYaw = 1 +ENT.TurnRateRoll = 1 + +ENT.GravityTurnRateYaw = 2 + +ENT.ForceLinearDampingMultiplier = 1.5 + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +ENT.EngineSounds = { + { + sound = "^npc/combine_gunship/engine_whine_loop1.wav", + --sound_int = "lvs/vehicles/helicopter/loop_interior.wav", + Pitch = 0, + PitchMin = 0, + PitchMax = 255, + PitchMul = 100, + Volume = 1, + VolumeMin = 0, + VolumeMax = 1, + SoundLevel = 125, + UseDoppler = true, + }, + { + sound = "npc/combine_gunship/engine_rotor_loop1.wav", + Pitch = 0, + PitchMin = 0, + PitchMax = 255, + PitchMul = 100, + Volume = 1, + VolumeMin = 0, + VolumeMax = 1, + SoundLevel = 125, + UseDoppler = true, + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "Body" ) +end + +function ENT:GetAimAngles() + local Muzzle = self:GetAttachment( self:LookupAttachment( "muzzle" ) ) + + if not Muzzle then return end + + local trace = self:GetEyeTrace() + + local AimAngles = self:WorldToLocalAngles( (trace.HitPos - Muzzle.Pos):GetNormalized():Angle() ) + + return AimAngles +end + +function ENT:WeaponsInRange() + return self:AngleBetweenNormal( self:GetForward(), self:GetAimVector() ) < 75 +end + +function ENT:BellyInRange() + return self:AngleBetweenNormal( -self:GetUp(), self:GetAimVector() ) < 45 +end + +function ENT:InitWeapons() + local weapon = {} + weapon.Icon = Material("lvs/weapons/mg.png") + weapon.Ammo = 2000 + weapon.Delay = 0.1 + weapon.HeatRateUp = 0.2 + weapon.HeatRateDown = 0.25 + weapon.StartAttack = function( ent ) + if not IsValid( ent.weaponSND ) then return end + + ent.weaponSND:EmitSound("NPC_CombineGunship.CannonStartSound") + + self.ShouldPlaySND = true + end + weapon.FinishAttack = function( ent ) + if not IsValid( ent.weaponSND ) then return end + + self.ShouldPlaySND = false + + ent.weaponSND:Stop() + ent.weaponSND:EmitSound("NPC_CombineGunship.CannonStopSound") + end + weapon.Attack = function( ent ) + if not ent:WeaponsInRange() then + + ent.ShouldPlaySND = false + + return true + end + + ent.ShouldPlaySND = true + + local Body = ent:GetBody() + + if not IsValid( Body ) then return end + + local Muzzle = Body:GetAttachment( Body:LookupAttachment( "muzzle" ) ) + + if not Muzzle then return end + + local trace = ent:GetEyeTrace() + + local bullet = {} + bullet.Src = Muzzle.Pos + bullet.Dir = (trace.HitPos - Muzzle.Pos):GetNormalized() + bullet.Spread = Vector(0.02,0.02,0.02) + bullet.TracerName = "lvs_pulserifle_tracer_large" + bullet.Force = 2000 + bullet.HullSize = 6 + bullet.Damage = 18 + bullet.Velocity = 12000 + bullet.Attacker = self:GetDriver() + bullet.Callback = function(att, tr, dmginfo) + local effectdata = EffectData() + effectdata:SetOrigin( tr.HitPos + tr.HitNormal ) + effectdata:SetNormal( tr.HitNormal * 2 ) + effectdata:SetRadius( 10 ) + util.Effect( "cball_bounce", effectdata, true, true ) + end + self:LVSFireBullet( bullet ) + + local effectdata = EffectData() + effectdata:SetOrigin( Muzzle.Pos ) + effectdata:SetNormal( Muzzle.Ang:Forward() ) + effectdata:SetEntity( ent ) + util.Effect( "lvs_pulserifle_muzzle", effectdata ) + + ent:TakeAmmo() + end + weapon.OnThink = function( ent, active ) + if not IsValid( ent.weaponSND ) then return end + + local ShouldPlay = ent.ShouldPlaySND and active + + if ent._oldShouldPlaySND ~= ShouldPlay then + ent._oldShouldPlaySND = ShouldPlay + if ShouldPlay then + ent.weaponSND:Play() + else + ent.weaponSND:Stop() + end + end + end + weapon.OnSelect = function( ent ) ent:EmitSound("physics/metal/weapon_impact_soft3.wav") end + self:AddWeapon( weapon ) + + local color_red = Color(255,0,0,255) + + local weapon = {} + weapon.Icon = Material("lvs/weapons/warplaser.png") + weapon.Ammo = -1 + weapon.Delay = 4 + weapon.HeatRateUp = 0 + weapon.HeatRateDown = 0.05 + weapon.Attack = function( ent ) + if ent:GetAI() and not ent:BellyInRange() then return true end + + ent:SetHeat( 100 ) + ent:SetOverheated( true ) + + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity( ent:GetBody() ) + util.Effect( "lvs_warpcannon_charge", effectdata ) + + timer.Simple( 2, function() + if not IsValid( ent ) then return end + + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity( ent:GetBody() ) + util.Effect( "lvs_warpcannon_fire", effectdata ) + + timer.Simple( 0.2, function() + if not IsValid( ent ) then return end + + ent:FireBellyCannon() + end ) + end ) + end + weapon.OnSelect = function( ent ) ent:EmitSound("physics/metal/weapon_impact_soft3.wav") end + weapon.HudPaint = function( ent, X, Y, ply ) + local base = ent:GetBody() + + if not IsValid( base ) then return end + + local Muzzle = base:GetAttachment( base:LookupAttachment( "bellygun" ) ) + + if not Muzzle then return end + + local trace = util.TraceLine( { + start = Muzzle.Pos, + endpos = Muzzle.Pos + base:GetAimVector() * 50000, + mask = MASK_SOLID_BRUSHONLY + } ) + + local Pos2D = trace.HitPos:ToScreen() + + ent:PaintCrosshairSquare( Pos2D, ent:BellyInRange() and color_white or color_red ) + ent:LVSPaintHitMarker( Pos2D ) + end + self:AddWeapon( weapon ) +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/cl_init.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/cl_init.lua new file mode 100644 index 0000000..1cf54af --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/cl_init.lua @@ -0,0 +1,79 @@ +include("shared.lua") + +function ENT:CalcViewPassenger( ply, pos, angles, fov, pod ) + if pod == self:GetGunnerSeat() then return LVS:CalcView( self, ply, pos, angles, fov, pod ) end + + local view = {} + view.origin = pos + view.angles = angles + view.fov = fov + view.drawviewer = false + + local Pod = ply:GetVehicle() + + if not IsValid( Pod ) then return view end + + if not Pod:GetThirdPersonMode() then + Pod:SetThirdPersonMode( true ) + end + + local radius = 800 + + local TargetOrigin = self:LocalToWorld( Vector(0,0,50) ) - view.angles:Forward() * radius + view.angles:Up() * radius * 0.2 + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "player" ) and not e.LVS + + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view +end + +function ENT:DamageFX() + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.05 + + local HP = self:GetHP() + local MaxHP = self:GetMaxHP() + + if HP > MaxHP * 0.25 then return end + + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld( Vector(-10,0,80) ) ) + effectdata:SetNormal( self:GetUp() ) + effectdata:SetMagnitude( math.Rand(0.5,1.5) ) + effectdata:SetEntity( self ) + util.Effect( "lvs_exhaust_fire", effectdata ) + end +end + +function ENT:OnFrame() + self:AnimRotor() + self:DamageFX() +end + +function ENT:AnimRotor() + local RPM = self:GetThrottle() * 2500 + + self.RPM = self.RPM and (self.RPM + RPM * RealFrameTime() * 0.5) or 0 + + self:SetPoseParameter("rotor_spin", self.RPM ) + self:InvalidateBoneCache() +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/init.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/init.lua new file mode 100644 index 0000000..2f1c2d9 --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/init.lua @@ -0,0 +1,102 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + local DriverSeat = self:AddDriverSeat( Vector(85,-20,-7), Angle(0,-90,10) ) + DriverSeat:SetCameraDistance( 1 ) + + local PassengerSeats = { + { + pos = Vector(85,20,-7), + ang = Angle(0,-90,10) + }, + { + pos = Vector(30,20,0), + ang = Angle(0,-90,0) + }, + { + pos = Vector(30,-20,0), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-20,-20,0), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-20,20,0), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-70,-20,0), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-70,20,0), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-120,-20,0), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-120,20,0), + ang = Angle(0,-90,0) + }, + } + for num, v in pairs( PassengerSeats ) do + local Pod = self:AddPassengerSeat( v.pos, v.ang ) + + if num == 1 then + self:SetGunnerSeat( Pod ) + end + end + + self:AddEngineSound( Vector(40,0,10) ) + + --self:AddRotor( pos, angle, radius, turn_speed_and_direction ) + self.Rotor = self:AddRotor( Vector(-65,0,100), Angle(2,0,0), 390, -4000 ) + self.Rotor:SetRotorEffects( true ) + self.Rotor:SetHP( 50 ) + function self.Rotor:OnDestroyed( base ) + base:SetBodygroup( 1, 2 ) + base:DestroyEngine() + + self:EmitSound( "physics/metal/metal_box_break2.wav" ) + end + + self.TailRotor = self:AddRotor( Vector(-575.360840,31.147699,105.635742), Angle(0,0,90), 80, -6000 ) + self.TailRotor:SetHP( 50 ) + function self.TailRotor:OnDestroyed( base ) + base:SetBodygroup( 2, 2 ) + base:DestroySteering( 2.5 ) + + self:EmitSound( "physics/metal/metal_box_break2.wav" ) + end +end + +function ENT:SetRotor( PhysRot ) + self:SetBodygroup( 1, PhysRot and 0 or 1 ) +end + +function ENT:SetTailRotor( PhysRot ) + self:SetBodygroup( 2, PhysRot and 0 or 1 ) +end + +function ENT:OnTick() + local PhysRot = self:GetThrottle() < 0.85 + + if not self:IsSteeringDestroyed() then + self:SetTailRotor( PhysRot ) + end + + if not self:IsEngineDestroyed() then + self:SetRotor( PhysRot ) + end +end + +function ENT:OnEngineActiveChanged( Active ) + if Active then + self:EmitSound( "lvs/vehicles/helicopter/start.wav" ) + end +end diff --git a/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/shared.lua b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/shared.lua new file mode 100644 index 0000000..4ec233b --- /dev/null +++ b/garrysmod/addons/lvs_helicopter/lua/entities/lvs_helicopter_rebel/shared.lua @@ -0,0 +1,82 @@ + +ENT.Base = "lvs_base_helicopter" + +ENT.PrintName = "Rebel Helicopter" +ENT.Author = "Luna" +ENT.Information = "Transport Helicopter as seen in Half Life 2 Episode 2" +ENT.Category = "[LVS] - Helicopters" + +ENT.VehicleCategory = "Helicopters" +ENT.VehicleSubCategory = "Resistance" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/blu/helicopter.mdl" +ENT.GibModels = { + "models/gibs/helicopter_brokenpiece_01.mdl", + "models/gibs/helicopter_brokenpiece_02.mdl", + "models/gibs/helicopter_brokenpiece_03.mdl", + "models/combine_apc_destroyed_gib02.mdl", + "models/combine_apc_destroyed_gib04.mdl", + "models/combine_apc_destroyed_gib05.mdl", + "models/props_c17/trappropeller_engine.mdl", + "models/gibs/airboat_broken_engine.mdl", +} + +ENT.AITEAM = 2 + +ENT.MaxHealth = 3000 + +ENT.MaxVelocity = 1500 + +ENT.ThrustUp = 1 +ENT.ThrustDown = 0.8 +ENT.ThrustRate = 1 + +ENT.ThrottleRateUp = 0.2 +ENT.ThrottleRateDown = 0.2 + +ENT.TurnRatePitch = 0.75 +ENT.TurnRateYaw = 1 +ENT.TurnRateRoll = 0.75 + +ENT.GravityTurnRateYaw = 2 + +ENT.ForceLinearDampingMultiplier = 1.5 + +ENT.ForceAngleMultiplier = 1 +ENT.ForceAngleDampingMultiplier = 1 + +ENT.EngineSounds = { + { + sound = "^lvs/vehicles/helicopter/loop_near.wav", + sound_int = "lvs/vehicles/helicopter/loop_interior.wav", + Pitch = 0, + PitchMin = 0, + PitchMax = 255, + PitchMul = 100, + Volume = 1, + VolumeMin = 0, + VolumeMax = 1, + SoundLevel = 125, + UseDoppler = true, + }, + { + sound = "^lvs/vehicles/helicopter/loop_dist.wav", + sound_int = "", + Pitch = 0, + PitchMin = 0, + PitchMax = 255, + PitchMul = 100, + Volume = 1, + VolumeMin = 0, + VolumeMax = 1, + SoundLevel = 125, + UseDoppler = true, + }, +} + +function ENT:OnSetupDataTables() + self:AddDT( "Entity", "GunnerSeat" ) +end diff --git a/garrysmod/addons/module_adminmod/lua/autorun/sam.lua b/garrysmod/addons/module_adminmod/lua/autorun/sam.lua new file mode 100644 index 0000000..21b4f94 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/autorun/sam.lua @@ -0,0 +1,148 @@ +if SAM_LOADED then return end + +local version = tonumber("148") or 148 + +sam = {} +sam.config = {} +sam.language = {} +sam.player = {} +sam.ranks = {} +sam.permissions = {} +sam.author = "Srlion" +sam.version = version + +function sam.print(...) + MsgC( + Color(236, 240, 241), "(", + Color(244, 67, 54), "SAM", + Color(236, 240, 241), ") ", + Color(236, 240, 241), ... + ) Msg("\n") +end + +local FAILED = false +do + local types = { + sv_ = SERVER and include or function() end, + cl_ = SERVER and AddCSLuaFile or include, + sh_ = function(name) + if SERVER then + AddCSLuaFile(name) + end + return include(name) + end + } + + sam.load_file = function(name, type) + if FAILED then return end + + if type and not type:EndsWith("_") then + type = type .. "_" + end + + local func = types[type] or types[name:GetFileFromFilename():sub(1, 3)] or types["sh_"] + if func then + local rets = {func(name)} + if rets[1] == false then + FAILED = true + sam.print("Failed to load!") + end + return unpack(rets) + end + end +end +local load_file = sam.load_file + +sam.print("Loading...") + +load_file("sam/libs/sh_types.lua") +load_file("sam/libs/sh_pon.lua") +load_file("sam/libs/sh_mp.lua") +load_file("sam/libs/sh_netstream.lua") +load_file("sam/libs/sh_async_netstream.lua") +load_file("sam/libs/sh_globals.lua") +load_file("sam/libs/sql/sv_init.lua") +sam.Promise = load_file("sam/libs/sh_promises.lua") + +load_file("sam/sh_colors.lua") + +load_file("sam/sh_util.lua") +load_file("sam/sh_lang.lua") +load_file("sam/sv_sql.lua") +load_file("sam/sh_permissions.lua") + +load_file("sam/ranks/sh_ranks.lua") +load_file("sam/ranks/sv_ranks.lua") + +load_file("sam/config/sh_config.lua") +load_file("sam/config/sv_config.lua") +load_file("sam/config/cl_config.lua") + +load_file("sam/player/sh_player.lua") +load_file("sam/player/sh_nw_vars.lua") +load_file("sam/player/sv_player.lua") +load_file("sam/player/cl_player.lua") +load_file("sam/player/sv_ranks.lua") +load_file("sam/player/sv_auth.lua") +load_file("sam/player/sv_bans.lua") + +load_file("sam/command/sh_command.lua") +load_file("sam/command/sv_command.lua") +load_file("sam/command/cl_command.lua") + +for _, f in ipairs(file.Find("sam/command/arguments/*.lua", "LUA")) do + load_file("sam/command/arguments/" .. f, "sh") +end + +load_file("sam/sh_restrictions.lua") + +load_file("sam/menu/sh_init.lua") +load_file("sam/menu/cl_init.lua") + +load_file("sam/sh_motd.lua") + +local modules = file.Find("sam/modules/*.lua", "LUA") +for _, module in ipairs(modules) do + load_file("sam/modules/" .. module) +end + +load_file("sam/reports/cl_reports.lua") +load_file("sam/reports/sv_reports.lua") + +do + if SERVER then + hook.Add("SAM.LoadedConfig", "SAM.AdvertsMain", function(config) + if not config.Adverts then + sam.config.set("Adverts", {}) + end + end) + end + load_file("sam/cl_adverts.lua") +end + +if not FAILED then + sam.print("Loaded!") +end + +if SERVER then + local path = "sam/importers/" + + concommand.Add("sam_import", function(ply, _, args) + if IsValid(ply) then return end + + local admin_mod = args[1] + if not admin_mod then + sam.print("You need to provide an admin mod to import!") + return + end + + if not file.Exists(path .. admin_mod, "LUA") then + sam.print("There is no importer for '" .. admin_mod .. "'") + return + end + + CompileFile(path .. admin_mod .. "/main.lua")() + end, nil, nil, FCVAR_PROTECTED) +end + +SAM_LOADED = true diff --git a/garrysmod/addons/module_adminmod/lua/sam/cl_adverts.lua b/garrysmod/addons/module_adminmod/lua/sam/cl_adverts.lua new file mode 100644 index 0000000..063353e --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/cl_adverts.lua @@ -0,0 +1,126 @@ +if SAM_LOADED then return end + +local sam = sam +local config = sam.config + +local times = {} + +local entry_OnValueChange = function(s) + s:SetTall(s:GetNumLines() * (sam.SUI.Scale(16) --[[font size]] + 1) + 1 + 2) +end + +local entry_OnEnter = function(s) + local ads = config.get("Adverts") + local txt = s:GetText() + if txt == "" then + s:Remove() + if s.i then + table.remove(ads, s.i) + end + else + if txt == s.ad then return end + ads[s.i] = txt + s.ad = txt + end + config.set("Adverts", ads, true) +end + +local entry_OnKeyCodeTyped = function(s, code) + if code == KEY_ENTER then + s:old_OnKeyCodeTyped(code) + return true + else + return s:old_OnKeyCodeTyped(code) + end +end + +config.add_menu_setting("Adverts", function(body) + local adverts_body + + local adverts = body:Add("SAM.LabelPanel") + adverts:Dock(TOP) + adverts:DockMargin(8, 6, 8, 0) + adverts:SetLabel("Adverts\n- Random adverts print every 60 seconds\n- Timed adverts can be done like this: {1m} This advert prints every 1 minute") + + local add_advert = adverts:Add("SAM.Button") + add_advert:SetText("+") + add_advert:SetSize(25, 25) + + local zpos = 0 + local add_func = function(ad, ad_i) + zpos = zpos + 1 + + local entry = adverts_body:Add("SAM.TextEntry") + entry:SetPlaceholder("") + entry:SetMultiline(true) + entry:SetNoBar(true) + entry:Dock(TOP) + entry:DockMargin(8, 6, 8, 0) + entry:SetZPos(zpos) + entry.ad = ad + entry.no_scale = true + + if not sam.ispanel(ad) then + entry.i = ad_i + entry:SetValue(ad) + else + entry.i = #config.get("Adverts") + 1 + end + + entry.OnValueChange = entry_OnValueChange + entry.OnEnter = entry_OnEnter + entry.old_OnKeyCodeTyped = entry.OnKeyCodeTyped + entry.OnKeyCodeTyped = entry_OnKeyCodeTyped + end + add_advert:On("DoClick", add_func) + + adverts_body = body:Add("Panel") + adverts_body:Dock(TOP) + + function adverts_body:PerformLayout(w, h) + for k, v in ipairs(self:GetChildren()) do + entry_OnValueChange(v) + end + self:SizeToChildren(false, true) + end + + sam.config.hook({"Adverts"}, function() + if not IsValid(adverts_body) then return end + adverts_body:Clear() + + for k, v in ipairs(config.get("Adverts")) do + add_func(v, k) + end + end) +end) + +local random = {} + +timer.Create("SAM.Advert.RandomAdverts", 60, 0, function() + local ad = random[math.random(1, #random)] + if not ad then return end + sam.player.send_message(nil, ad) +end) + +sam.config.hook({"Adverts"}, function() + for i = #times, 1, -1 do + times[i] = nil + timer.Remove("SAM.Adverts." .. i) + end + + random = {} + for k, v in ipairs(config.get("Adverts")) do + if v:sub(1, 1) == "{" then + local time + time, v = v:match("(%b{}) *(.*)") + time = sam.parse_length(time) + if time then + timer.Create("SAM.Adverts." .. table.insert(times, true), time * 60, 0, function() + sam.player.send_message(nil, v) + end) + end + else + table.insert(random, v) + end + end +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/dropdown.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/dropdown.lua new file mode 100644 index 0000000..2ca137a --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/dropdown.lua @@ -0,0 +1,48 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +command.new_argument("dropdown") + :OnExecute(function(arg, input, ply, _, result) + if not arg.options or table.Empty(arg.options) then + ply:sam_send_message("no data", {S = "dropdown", S_2 = input}) + return + end + + table.insert(result, input) + end) + :Menu(function(set_result, body, buttons, arg) + local default = arg.hint or "select" + + local cbo = buttons:Add("SAM.ComboBox") + cbo:SetValue(default) + cbo:SetTall(25) + + function cbo:OnSelect(_, value) + set_result(value) + default = value + end + + function cbo:DoClick() + if self:IsMenuOpen() then + return self:CloseMenu() + end + + self:Clear() + self:SetValue(default) + + if not arg.options then + LocalPlayer():sam_send_message("dropdown has no options data") + return + end + + for k, v in pairs(arg.options) do + self:AddChoice(v) + end + + self:OpenMenu() + end + + return cbo + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/length.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/length.lua new file mode 100644 index 0000000..13f6f77 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/length.lua @@ -0,0 +1,62 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +local get_length = function(arg, input) + if (input == "" or input == nil) and arg.optional then + if arg.default ~= nil then + return arg.default + end + + return "" + end + + return sam.parse_length(input) +end + +command.new_argument("length") + :OnExecute(function(arg, input, ply, _, result, i) + local length = get_length(arg, input) + if length == "" then + result[i] = nil + elseif not length then + ply:sam_send_message("invalid", { + S = "length", S_2 = input + }) + return false + else + if arg.min and length ~= 0 then + length = math.max(length, arg.min) + end + + if arg.max then + if length == 0 then + length = arg.max + else + length = math.min(length, arg.max) + end + end + + result[i] = length + end + end) + + :Menu(function(set_result, body, buttons, argument) + local length_input = buttons:Add("SAM.TextEntry") + length_input:SetTall(25) + + length_input:SetCheck(function(new_limit) + new_limit = get_length(argument, new_limit) or nil + set_result(new_limit) + return new_limit or false + end) + + local hint = argument.hint or "length" + if argument.default then + hint = hint .. " = " .. tostring(argument.default) + end + + length_input:SetPlaceholder(hint) + return length_input + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/map.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/map.lua new file mode 100644 index 0000000..c0cc953 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/map.lua @@ -0,0 +1,50 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +command.new_argument("map") + :OnExecute(function(argument, input, ply, _, result) + local map_name = sam.is_valid_map(input) + if not map_name and not (argument.optional and input == "None") then + ply:sam_send_message("invalid", { + S = "map", S_2 = input + }) + return false + end + + table.insert(result, map_name) + end) + + :Menu(function(set_result, _, buttons, argument) + local maps = buttons:Add("SAM.ComboBox") + maps:SetTall(25) + + if argument.optional then + maps:AddChoice("None", nil, true) + end + + for _, map_name in ipairs(sam.get_global("Maps")) do + if not (argument.exclude_current and map_name == game.GetMap()) then + maps:AddChoice(map_name) + end + end + + function maps:OnSelect(_, value) + set_result(value) + end + + local value = argument.optional and "None" or maps:GetOptionText(1) + maps:SetValue(value) + maps:OnSelect(nil, value) + + return maps + end) + + :AutoComplete(function(_, result, name) + for _, map_name in ipairs(sam.get_global("Maps")) do + if map_name:lower():find(name, 1, true) then + table.insert(result, map_name) + end + end + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/number.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/number.lua new file mode 100644 index 0000000..bc0e24f --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/number.lua @@ -0,0 +1,67 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +local get_number = function(argument, input, gsub) + if (input == "" or input == nil) and argument.optional then + if argument.default ~= nil then + return argument.default + end + return "" + end + + local number = tonumber(input) + if gsub ~= false and not isnumber(number) then + number = tonumber(input:gsub("%D", ""), 10 /*gsub returns two args*/) + end + + return number +end + +command.new_argument("number") + :OnExecute(function(argument, input, ply, _, result, i) + local number = get_number(argument, input) + if number == "" then + result[i] = nil + elseif not number then + ply:sam_send_message("invalid", { + S = argument.hint or "number", S_2 = input + }) + return false + else + if argument.min then + number = math.max(number, argument.min) + end + + if argument.max then + number = math.min(number, argument.max) + end + + if argument.round then + number = math.Round(number) + end + + result[i] = number + end + end) + :Menu(function(set_result, body, buttons, argument) + local number_entry = buttons:Add("SAM.TextEntry") + number_entry:SetUpdateOnType(true) + number_entry:SetNumeric(true) + number_entry:SetTall(25) + + number_entry:SetCheck(function(number) + number = get_number(argument, number, false) + set_result(number) + return number or false + end) + + local hint = argument.hint or "number" + if argument.default then + hint = hint .. " = " .. tostring(argument.default) + end + number_entry:SetPlaceholder(hint) + + return number_entry + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/player.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/player.lua new file mode 100644 index 0000000..b3cee58 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/player.lua @@ -0,0 +1,446 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +local can_target_player = function(arg, admin, target, cmd, input) + if not IsValid(target) or not target:IsPlayer() or not target:sam_get_nwvar("is_authed") then + if input then + admin:sam_send_message("cant_find_target", { + S = input + }) + end + return false + end + + if not arg.allow_higher_target and not admin:CanTarget(target) then + if cmd then + admin:sam_send_message("cant_target_player", { + S = target:Name() + }) + end + return false + end + + if arg.cant_target_self and admin == target then + if cmd then + admin:sam_send_message("cant_target_self", { + S = cmd.name + }) + end + return false + end + + return true +end + +local check_text_match = function(text, ply) + if ply:Name():lower():find(text, 1, true) then return true end + if ply:sam_getrank():lower():find(text, 1, true) then return true end + if team.GetName(ply:Team()):lower():find(text, 1, true) then return true end + + if not ply:IsBot() then + return ply:SteamID():lower():find(text, 1, true) or ply:SteamID64():lower():find(text, 1, true) + end + + return false +end + +command.new_argument("player") + :OnExecute(function(arg, input, ply, cmd, result, n) + if input == nil and arg.optional then + if sam.isconsole(ply) then + ply:sam_send_message("cant_target_self", { + S = cmd.name + }) + return false + end + result[n] = {ply, admin = ply, input = input} + return + end + + local single_target = arg.single_target + local targets = {admin = ply, input = input} + + if input == "*" then + if single_target then + ply:sam_send_message("cant_target_multi_players") + return false + end + local players = player.GetAll() + for i = 1, #players do + local v = players[i] + if can_target_player(arg, ply, v) then + table.insert(targets, v) + end + end + elseif input:sub(1, 1) == "#" and not single_target then + local tmp = {} + for _, v in ipairs(input:sub(2):Trim():Split(",")) do + v = tonumber(v) + if not sam.isnumber(v) then continue end + local target = Entity(v) + if not tmp[target] and IsValid(target) and target:IsPlayer() then + tmp[target] = true + if can_target_player(arg, ply, target) then + table.insert(targets, target) + end + end + end + else + local target + if input == "^" then + target = ply + elseif input == "@" and not sam.isconsole(ply) then + target = ply:GetEyeTrace().Entity + elseif sam.is_steamid(input) then + target = player.GetBySteamID(input) + elseif sam.is_steamid64(input) then + target = player.GetBySteamID64(input) + elseif input:sub(1, 1) == "#" then + local index = input:sub(2):Trim() + index = tonumber(index) + + if not isnumber(index) then + ply:sam_send_message("invalid_id", { + S = input + }) + return false + end + + target = Entity(index) + + if not IsValid(target) or not target:IsPlayer() then + ply:sam_send_message("player_id_not_found", { + S = index + }) + return false + end + else + if input:sub(1, 1) == "%" and #input > 1 then + input = input:sub(2) + end + + target = sam.player.find_by_name(input) + if sam.istable(target) then + if single_target then + ply:sam_send_message("found_multi_players", {T = target}) + return false + else + for k, v in ipairs(target) do + if can_target_player(arg, ply, v) then + table.insert(targets, v) + end + end + goto _end + end + end + end + + if not can_target_player(arg, ply, target, cmd, input) then + return false + end + + table.insert(targets, target) + end + + ::_end:: + + if #targets == 0 then + ply:sam_send_message("cant_find_target", { + S = input + }) + return false + end + result[n] = targets + end) + + -- Do NOT ask me about this code at all please because I feel shit about it but I'm not gonna make + -- a file specially for this one + :Menu(function(set_result, body, buttons, argument, childs) + if body.ply_list then + local ply_list = body.ply_list + ply_list.argument = argument + ply_list.set_result = set_result + ply_list.multi_select = argument.single_target ~= true + + if argument.single_target == true and #ply_list:GetSelected() > 1 then + ply_list:ClearSelection() + end + + ply_list:OnRowSelected() + ply_list:GetParent():Show() + + return + end + + local SUI = sam.SUI + + local SetVisible = FindMetaTable("Panel").SetVisible + + local left_body = body:Add("SAM.Panel") + left_body:Dock(LEFT) + left_body:DockMargin(0, 0, 5, 0) + left_body:SetWide(0) + left_body.no_remove = true + left_body.no_change = "player" + + SetVisible(left_body, false) + left_body.SetVisible = function(s, visible) + if visible == s:IsVisible() or visible == s.visible_state then return end + + if visible then + SetVisible(s, true) + s:InvalidateLayout(true) + end + + s.visible_state = visible + s:Stop() + + s:SizeTo(visible and SUI.Scale(320) or 0, -1, 0.2, 0, 0, function() + SetVisible(s, visible) + s:InvalidateParent(true) + end) + end + left_body:Show() + + table.insert(childs, left_body) + + local ply_list = left_body:Add("SAM.ScrollPanel") + ply_list:Dock(FILL) + ply_list:Background(Color(34, 34, 34), 3) + ply_list.argument = argument + ply_list.set_result = set_result + ply_list.multi_select = argument.single_target ~= true + ply_list.Paint = function(s, w, h) + s:RoundedBox("Background", 3, 0, 0, w, h, SUI.GetColor("text_entry_bg")) + end + + local lines = {} + function ply_list:OnClickLine(line, clear) + local multi_select = ply_list.multi_select + if not multi_select and not clear then return end + + if multi_select and input.IsKeyDown(KEY_LCONTROL) then + if line.Selected then + line.Selected = false + self.main_selected_line = nil + self:OnRowSelected() + return + end + clear = false + end + + if multi_select and input.IsKeyDown(KEY_LSHIFT) then + local selected = self:GetSelectedLine() + if selected then + self.main_selected_line = self.main_selected_line or selected + + if clear then + self:ClearSelection() + end + + local first = math.min(self.main_selected_line.id, line.id) + local last = math.max(self.main_selected_line.id, line.id) + + for id = first, last do + local line_2 = lines[id] + local was_selected = line_2.Selected + + line_2.Selected = true + + if not was_selected then + self:OnRowSelected(line_2.id, line_2) + end + end + + return + end + end + + if not multi_select or clear then + self:ClearSelection() + end + + line.Selected = true + + self.main_selected_line = line + self:OnRowSelected(line.id, line) + end + + function ply_list:GetSelected() + local ret = {} + for _, v in ipairs(lines) do + if v.Selected then + table.insert(ret, v) + end + end + return ret + end + + function ply_list:GetSelectedLine() + for _, line in ipairs(lines) do + if line.Selected then return line end + end + end + + function ply_list:ClearSelection() + for _, line in ipairs(lines) do + line.Selected = false + end + self:OnRowSelected() + end + + function ply_list:OnRowSelected() + local plys = {} + for k, v in ipairs(ply_list:GetSelected()) do + plys[k] = v.ply:EntIndex() + end + if #plys == 0 then + self.set_result(nil) + else + self.set_result("#" .. table.concat(plys, ",")) + end + end + + function ply_list:OnRowRightClick(_, line) + local dmenu = vgui.Create("SAM.Menu") + dmenu:SetInternal(line) + + local name = line.ply:Name() + dmenu:AddOption("Copy Name", function() + SetClipboardText(name) + end) + + dmenu:AddSpacer() + + local steamid = line.ply:SteamID() + dmenu:AddOption("Copy SteamID", function() + SetClipboardText(steamid) + end) + + dmenu:AddOption("Copy SteamID64", function() + SetClipboardText(util.SteamIDTo64(steamid)) + end) + + dmenu:Open() + dmenu:SetPos(input.GetCursorPos()) + end + + local item_click = function(s) + ply_list:OnClickLine(s, true) + end + + local item_rightclick = function(s) + if not s.Selected then + ply_list:OnClickLine(s, true) + end + ply_list:OnRowRightClick(s.id, s) + end + + local item_cursor = function(s) + if input.IsMouseDown(MOUSE_LEFT) then + ply_list:OnClickLine(s) + end + end + + local added_players = {} + + local add_player = function(ply, i) + if can_target_player(ply_list.argument, LocalPlayer(), ply) then + local player_button = ply_list:Add("SAM.Button") + player_button:Dock(TOP) + player_button:DockMargin(0, 0, 0, 2) + player_button:DockPadding(4, 4, 4, 4) + player_button:SetContained(false) + player_button:SetText("") + player_button:SetZPos(i) + player_button.DoClick = item_click + player_button.DoRightClick = item_rightclick + player_button.OnCursorMoved = item_cursor + + local line = player_button:Add("SAM.PlayerLine") + line:SetMouseInputEnabled(false) + line:SetInfo({ + steamid = ply:IsBot() and "BOT" or ply:SteamID(), + name = ply:Name(), + rank = ply:sam_getrank() + }) + + player_button:InvalidateLayout(true) + player_button:SizeToChildren(false, true) + + player_button.ply = ply + player_button.line = line + player_button.id = table.insert(lines, player_button) + body.search_entry:OnValueChange() + + added_players[ply] = true + end + end + + ply_list:On("Think", function() + local players = player.GetAll() + for i = 1, #players do + local ply = players[i] + if not added_players[ply] then + add_player(ply, i) + end + end + + local argument = ply_list.argument + for i = 1, #lines do + local line = lines[i] + local ply = line.ply + + if not can_target_player(argument, LocalPlayer(), ply) then + line:Remove() + table.remove(lines, i) + added_players[ply] = nil + ply_list:OnRowSelected() + break + end + + line = line.line + line:SetName(ply:Name()) + line:SetRank(ply:sam_getrank()) + end + end) + + local search_entry = left_body:Add("SAM.TextEntry") + search_entry:Dock(TOP) + search_entry:DockMargin(0, 0, 0, 5) + search_entry:SetPlaceholder("Search... (name/steamid/rank/job)") + search_entry:SetBackground(Color(34, 34, 34)) + search_entry:SetTall(25) + search_entry:SetNoBar(true) + + function search_entry:OnValueChange(text) + if text == nil then + text = self:GetValue() + end + if text ~= "" then + ply_list:ClearSelection() + end + text = text:lower() + for i, line in ipairs(lines) do + local ply = line.ply + if IsValid(ply) then + line:SetVisible(check_text_match(text, ply)) + end + end + ply_list:GetCanvas():InvalidateLayout(true) + end + + body.ply_list = ply_list + body.search_entry = search_entry + end) + + :AutoComplete(function(arg, result, name) + local ply = LocalPlayer() + for k, v in ipairs(player.GetAll()) do + if can_target_player(arg, ply, v) and v:Name():lower():find(name, 1, true) then + table.insert(result, "%" .. v:Name()) + end + end + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/rank.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/rank.lua new file mode 100644 index 0000000..fdb1425 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/rank.lua @@ -0,0 +1,68 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +local is_good_rank = function(rank, arg, ply) + if arg.check and not arg.check(rank, ply) then + return false + end + return true +end + +command.new_argument("rank") + :OnExecute(function(arg, input, ply, _, result, i) + if not input and arg.optional then + result[i] = nil + return + end + + if not sam.ranks.is_rank(input) or not is_good_rank(input, arg, ply) then + ply:sam_send_message("invalid", { + S = arg.hint or "rank", S_2 = input + }) + return false + end + + result[i] = input + end) + + :Menu(function(set_result, body, buttons, arg) + local current_rank = arg.hint or "select rank" + + local ranks = buttons:Add("SAM.ComboBox") + ranks:SetValue(current_rank) + ranks:SetTall(25) + + function ranks:OnSelect(_, value) + set_result(value) + current_rank = value + end + + function ranks:DoClick() + if self:IsMenuOpen() then + return self:CloseMenu() + end + + self:Clear() + self:SetValue(current_rank) + + for rank_name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do + if is_good_rank(rank_name, arg, LocalPlayer()) then + self:AddChoice(rank_name) + end + end + + self:OpenMenu() + end + + return ranks + end) + + :AutoComplete(function(arg, result, name) + for rank_name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do + if rank_name:lower():find(name, 1, true) and is_good_rank(rank_name, arg, LocalPlayer()) then + table.insert(result, rank_name) + end + end + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/steamid.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/steamid.lua new file mode 100644 index 0000000..e3e0fc5 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/steamid.lua @@ -0,0 +1,111 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +local cached_ranks = {} +local targeting_offline = {} + +local check_steamid = function(steamid) + if not sam.is_steamid(steamid) then + if sam.is_steamid64(steamid) then + return util.SteamIDFrom64(steamid) + else + return nil + end + end + + return steamid +end + +local can_target_steamid_callback = function(data, promise) + local ply, steamid = promise.ply, promise.steamid + + if not data or sam.ranks.can_target(promise.rank, data.rank) then + promise:resolve({steamid}) + elseif IsValid(ply) then + ply:sam_send_message("cant_target_player", { + S = steamid + }) + end + + targeting_offline[ply] = nil + cached_ranks[steamid] = data ~= nil and data or false +end + +command.new_argument("steamid") + :OnExecute(function(argument, input, ply, _, result, i) + local steamid = check_steamid(input) + if not steamid then + ply:sam_send_message("invalid", { + S = "steamid/steamid64", S_2 = input + }) + return false + end + + if argument.allow_higher_target then + result[i] = steamid + return + end + + local promise = sam.Promise.new() + promise.ply = ply + promise.rank = ply:sam_getrank() + promise.steamid = steamid + + local target = player.GetBySteamID(steamid) + if sam.isconsole(ply) then + promise:resolve({steamid}) + elseif target then + if ply:CanTarget(target) then + promise:resolve({steamid, target}) + else + ply:sam_send_message("cant_target_player", { + S = steamid + }) + end + elseif cached_ranks[steamid] ~= nil then + can_target_steamid_callback(cached_ranks[steamid], promise) + else + targeting_offline[ply] = true + + sam.SQL.FQuery([[ + SELECT + `rank` + FROM + `sam_players` + WHERE + `steamid` = {1} + ]], {steamid}, can_target_steamid_callback, true, promise) + end + + result[i] = promise + end) + :Menu(function(set_result, body, buttons, argument) + local steamid_entry = buttons:Add("SAM.TextEntry") + steamid_entry:SetTall(25) + steamid_entry:SetUpdateOnType(true) + steamid_entry:SetPlaceholder("steamid/steamid64") + + steamid_entry:SetCheck(function(steamid) + steamid = check_steamid(steamid) + set_result(steamid) + return steamid or false + end) + + return steamid_entry + end) +:End() + +timer.Create("SAM.ClearCachedRanks", 60 * 2.5, 0, function() + table.Empty(cached_ranks) +end) + +hook.Add("SAM.ChangedSteamIDRank", "RemoveIfCached", function(steamid) + cached_ranks[steamid] = nil +end) + +hook.Add("SAM.CanRunCommand", "StopIfTargetingOffline", function(ply) + if targeting_offline[ply] then + return false + end +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/arguments/text.lua b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/text.lua new file mode 100644 index 0000000..29e8dbd --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/arguments/text.lua @@ -0,0 +1,60 @@ +if SAM_LOADED then return end + +local sam, command = sam, sam.command + +command.new_argument("text") + :OnExecute(function(argument, input, ply, _, result, i) + if sam.isstring(input) then + input = input:sub(1, 255) + end + + local invalid = false + if input == nil then + if not argument.optional then + invalid = true + end + elseif argument.check and not argument.check(input, ply) then + invalid = true + end + + if invalid then + ply:sam_send_message("invalid", { + S = argument.hint or "text", S_2 = input + }) + return false + end + + result[i] = input + end) + :Menu(function(set_result, body, buttons, argument) + local text_entry = buttons:Add("SAM.TextEntry") + text_entry:SetTall(25) + + local default = argument.default + text_entry:SetCheck(function(text) + local valid = true + if text == "" then + if default then + text = default + elseif not argument.optional then + valid = false + end + elseif argument.check and not argument.check(text, LocalPlayer()) then + valid = false + end + + set_result(valid and text or nil) + + return valid + end) + + local hint = argument.hint or "text" + if default then + hint = hint .. " = " .. tostring(default) + end + + text_entry:SetPlaceholder(hint) + + return text_entry + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/cl_command.lua b/garrysmod/addons/module_adminmod/lua/sam/command/cl_command.lua new file mode 100644 index 0000000..4b44968 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/cl_command.lua @@ -0,0 +1,118 @@ +if SAM_LOADED then return end + +local sam = sam +local command = sam.command + +local get_syntax = function(args, cmd_args, cmd_str) + for i = 1, #cmd_args do + cmd_str = cmd_str .. " " + + local cmd_arg = cmd_args[i] + local arg = args[i] + + if arg == "" then + arg = nil + end + + local optional = cmd_arg.optional + local c_1, c_2 = "<", ">" + if optional then + c_1, c_2 = "[", "]" + end + + cmd_str = cmd_str .. (arg and "\"" or c_1) + + cmd_str = cmd_str .. (arg or cmd_arg.hint or cmd_arg.name) + + if not arg then + local default = cmd_arg.default + if default then + cmd_str = cmd_str .. " = " .. tostring(default) + end + end + + cmd_str = cmd_str .. (arg and "\"" or c_2) + end + + return cmd_str +end + +-- +-- Auto Complete +-- +concommand.Add("sam", function(_, _, _, text) + LocalPlayer():ConCommand("sam_run " .. text) +end, function(_, text) + local ply = LocalPlayer() + local result = {} + + local new_arg = text:EndsWith(" ") + local args = sam.parse_args(text) + + local cmd_name = (args[1] or ""):lower() + local cmd = command.get_command(cmd_name) + + if not cmd or (#args == 1 and not new_arg) then + local commands = command.get_commands() + + for _, v in ipairs(commands) do + local name = v.name + if name:find(cmd_name, nil, true) and ply:HasPermission(name) then + table.insert(result, "sam " .. name) + end + end + + return result + end + + if not ply:HasPermission(cmd_name) then return end + + table.remove(args, 1) + + if new_arg then + local syntax = get_syntax(args, cmd.args, "sam " .. cmd.name) + if #args == 0 then + print(syntax) + end + table.insert(result, syntax) + return result + end + + local arg_index = new_arg and #args + 1 or #args + + local cmd_args = cmd.args + local cmd_args_n = #cmd_args + if cmd_args_n == 0 then return end + + if arg_index >= cmd_args_n then + arg_index = cmd_args_n + + if cmd.get_rest_args then + local arg = table.concat(args, " ", cmd_args_n) + if arg ~= "" then + args[cmd_args_n] = arg + for i = cmd_args_n + 1, #args do + args[i] = nil + end + end + end + end + + local arguments = command.get_arguments() + local cmd_arg = cmd_args[arg_index] + local func = arguments[cmd_arg.name].auto_complete + if func then + func(cmd_arg, result, args[arg_index] or "") + end + + local cmd_str = "sam " .. cmd_name .. " " + if arg_index - 1 > 0 then + cmd_str = cmd_str .. "\"" .. table.concat(args, "\" ", 1, arg_index - 1) .. "\" " + end + + for k, v in ipairs(result) do + result[k] = cmd_str .. "\"" .. v .. "\"" + end + + return result +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/sh_command.lua b/garrysmod/addons/module_adminmod/lua/sam/command/sh_command.lua new file mode 100644 index 0000000..b30ae3d --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/sh_command.lua @@ -0,0 +1,279 @@ +if SAM_LOADED then return end + +local sam = sam +local istable, isstring = sam.istable, sam.isstring + +local commands = {} +local arguments = {} + +do + local command = {} + + local current_category = "other" + function command.set_category(category) + if isstring(category) then + current_category = category + end + end + + function command.get_commands() + return commands + end + + function command.get_command(name) + for i = 1, #commands do + local cmd = commands[i] + if cmd.name == name then + return cmd, i + end + + local aliases = cmd.aliases + for i2 = 1, #aliases do + local alias = aliases[i2] + if alias == name then + return cmd, i + end + end + end + return false + end + + function command.remove_command(name) + local cmd, index = command.get_command(name) + if index then + table.remove(commands, index) + hook.Call("SAM.CommandRemoved", nil, cmd.name, cmd, index) + return index + end + return false + end + + function command.get_arguments() + return arguments + end + + do + local argument_methods = { + OnExecute = function(self, func) + if isfunction(func) and SERVER then + self.on_execute = func + end + return self + end, + Menu = function(self, func) + if isfunction(func) and CLIENT then + self.menu = func + end + return self + end, + AutoComplete = function(self, func) + if isfunction(func) and CLIENT then + self.auto_complete = func + end + return self + end, + End = function(self) + if SERVER then + arguments[self.name] = self.on_execute + else + arguments[self.name] = self + end + end + } + + local argument_meta = {__index = argument_methods} + function command.new_argument(name) + if isstring(name) then + return setmetatable({name = name}, argument_meta) + end + end + end + + if CLIENT then + function command.run_commands(to_run) + local time = 0 + for i = 1, #to_run do + timer.Simple(time, function() + RunConsoleCommand("sam", unpack(to_run[i])) + end) + time = time + 0.76 + end + end + end + + -- + -- Methods + -- + local Command_Methods = {} + local Command_meta = {__index = Command_Methods} + + function command.new(cmd) + if not isstring(cmd) then return end + + local new_command = setmetatable({}, Command_meta) + new_command.can_console_run = true + new_command.args = {} + new_command.name = cmd:lower() + new_command.aliases = {} + new_command.category = current_category + + return new_command + end + + local AddMethod = function(name, func) + Command_Methods[name] = func + end + + AddMethod("Aliases", function(self, ...) + for k, v in ipairs({...}) do + table.insert(self.aliases, v) + end + return self + end) + + AddMethod("AddArg", function(self, name, data) + if not isstring(name) then return end + if not istable(data) then + data = {} + end + data.name = name + table.insert(self.args, data) + return self + end) + + AddMethod("DisallowConsole", function(self, disallow) + self.can_console_run = isbool(disallow) and disallow or false + return self + end) + + AddMethod("SetCategory", function(self, category) + if isstring(category) then + self.category = category + end + return self + end) + + AddMethod("Help", function(self, help) + if isstring(help) then + self.help = sam.language.get(help) or help + end + return self + end) + + AddMethod("OnExecute", function(self, func) + if isfunction(func) and SERVER then + self.on_execute = func + end + return self + end) + + AddMethod("SetPermission", function(self, perm, default_rank) + if isstring(perm) then + self.permission = perm + self.default_rank = default_rank + end + return self + end) + + AddMethod("GetRestArgs", function(self, get) + if not isbool(get) then + get = true + end + self.get_rest_args = get + return self + end) + + AddMethod("MenuHide", function(self, should_hide) + if isbool(should_hide) then + self.menu_hide = should_hide + else + self.menu_hide = true + end + return self + end) + + AddMethod("DisableNotify", function(self, disable) + if isbool(disable) then + self.disable_notify = disable + else + self.disable_notify = true + end + return self + end) + + AddMethod("End", function(self) + local name = self.name + if SERVER and not self.on_execute then + sam.print("need an OnExecute function for the command!") + debug.Trace() + return + end + + if self.permission then + sam.permissions.add(self.permission, "Commands - " .. self.category, self.default_rank) + end + + local _, index = command.get_command(name) + if index then + commands[index] = self + hook.Call("SAM.CommandModified", nil, name, self, index) + else + hook.Call("SAM.CommandAdded", nil, name, self, table.insert(commands, self)) + end + end) + + AddMethod("GetRequiredArgs", function(self) + local required_args = {} + local args = self.args + for i = 1, #args do + local v = args[i] + if not v.optional then + table.insert(required_args, v) + end + end + return required_args + end) + + AddMethod("GetOptionalArgs", function(self) + local optional_args = {} + local args = self.args + for i = 1, #args do + local v = args[i] + if v.optional then + table.insert(optional_args, v) + end + end + return optional_args + end) + + AddMethod("ArgsToString", function(self, return_table) + local str_table = {} + local args = self.args + for i = 1, #self.args do + local v = args[i] + if not v.optional then + table.insert(str_table, "<" .. (v.hint or v.name) .. ">") + else + table.insert(str_table, "[" .. (v.hint or v.name) .. "]") + end + end + return return_table and str_table or table.concat(str_table, " ") + end) + + AddMethod("HasArg", function(self, arg) + local args = self.args + for i = 1, #self.args do + if args[i].name == arg then + return true + end + end + return false + end) + + command.add_method = AddMethod + + sam.command = command +end + +sam.__commands = commands +sam.__arguments = arguments diff --git a/garrysmod/addons/module_adminmod/lua/sam/command/sv_command.lua b/garrysmod/addons/module_adminmod/lua/sam/command/sv_command.lua new file mode 100644 index 0000000..7b1b941 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/command/sv_command.lua @@ -0,0 +1,176 @@ +if SAM_LOADED then return end + +local sam = sam +local command = sam.command + +local sub, match, lower = string.sub, string.match, string.lower +local WHITE, RED, BLUE, GREEN = Color(236, 240, 241), Color(244, 67, 54), Color(13, 130, 223), Color(0, 230, 64) + +local prefix +local send_syntax = function(ply, args, cmd_name, cmd_args, cmd_args_n, from_console) + local tbl = { + WHITE, + from_console and "sam " or prefix, + cmd_name + } + + for i = 1, cmd_args_n do + table.insert(tbl, " ") + + local cmd_arg = cmd_args[i] + local arg = args[i] + + if arg == "" then + arg = nil + end + + local optional = cmd_arg.optional + local c_1, c_2 = "<", ">" + if optional then + c_1, c_2 = "[", "]" + end + + table.insert(tbl, WHITE) + table.insert(tbl, arg and "\"" or c_1) + + table.insert(tbl, cmd_arg.optional and BLUE or RED) + table.insert(tbl, arg or cmd_arg.hint or cmd_arg.name) + + if not arg then + local default = cmd_arg.default + if default then + table.insert(tbl, WHITE) + table.insert(tbl, " = ") + + table.insert(tbl, GREEN) + table.insert(tbl, tostring(default)) + end + end + + table.insert(tbl, WHITE) + table.insert(tbl, arg and "\"" or c_2) + end + + sam.player.add_text(ply, unpack(tbl)) + return "" +end + +local run_command = function(ply, text, from_console) + local args = sam.parse_args(text) + local cmd_name = args[1] + if not cmd_name then return end + + cmd_name = lower(cmd_name) + + local cmd = command.get_command(cmd_name) + if not cmd then return end + + if not cmd.can_console_run and sam.isconsole(ply) then + ply:sam_send_message("cant_use_as_console", { + S = cmd_name + }) + return "" + end + + if cmd.permission and not sam.isconsole(ply) and not ply:HasPermission(cmd.permission) then + ply:sam_send_message("no_permission", { + S = cmd_name + }) + return "" + end + + local can_run = hook.Call("SAM.CanRunCommand", nil, ply, cmd_name, args, cmd) + if can_run == false then return "" end + + table.remove(args, 1) + + local cmd_args = cmd.args + local cmd_args_n = #cmd_args + + -- !kick srlion fuck off > !kick "srlion" "fuck off" + if cmd.get_rest_args then + local arg = table.concat(args, " ", cmd_args_n) + if arg ~= "" then + args[cmd_args_n] = arg + for i = cmd_args_n + 1, #args do + args[i] = nil + end + end + end + + -- we need to make sure that all required arguments are there + for i = 1, cmd_args_n do + if not cmd_args[i].optional then + local arg = args[i] + if arg == nil or arg == "" then + send_syntax(ply, args, cmd_name, cmd_args, cmd_args_n, from_console) + return "" + end + end + end + + local result = {} + local args_count = 0 + local arguments = command.get_arguments() + for i = 1, cmd_args_n do + local cmd_arg = cmd_args[i] + local arg = args[i] + + if arg == nil or arg == "" then + arg = cmd_arg.default + else + args_count = args_count + 1 + end + + if arguments[cmd_arg.name](cmd_arg, arg, ply, cmd, result, i) == false then + return "" + end + end + + cmd.on_execute(ply, unpack(result, 1, table.maxn(result))) + + if not cmd.disable_notify then + sam.print( + RED, ply:Name(), + WHITE, "(", + BLUE, ply:SteamID(), + WHITE, ") ran command '", + RED, cmd_name, + WHITE, + args_count > 0 + and "' with arguments: \"" .. table.concat(args, "\" \"") .. "\"" + or "'" + ) + end + + hook.Call("SAM.RanCommand", nil, ply, cmd_name, args, cmd, result) + + return "" +end + +hook.Add("PlayerSay", "SAM.Command.RunCommand", function(ply, text) + prefix = sub(text, 1, 1) + if prefix ~= "!" then return end + if match(sub(text, 2, 2), "%S") == nil then return end + + return run_command(ply, sub(text, 2), false) +end) + +local console_run_command = function(ply, _, _, text) + if match(sub(text, 2, 2), "%S") == nil then return end + + if not IsValid(ply) then + ply = sam.console + else + -- making it same as PlayerSay delay + -- https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/game/server/client.cpp#L747 + -- no delay for server console + if not ply:sam_check_cooldown("RunCommand", 0.66) then + return + end + end + + run_command(ply, text, true, false) +end +concommand.Add("sam", console_run_command) +concommand.Add("sam_run", console_run_command) -- for some dumb reason i cant make "sam" command clientside just for auto-complete diff --git a/garrysmod/addons/module_adminmod/lua/sam/config/cl_config.lua b/garrysmod/addons/module_adminmod/lua/sam/config/cl_config.lua new file mode 100644 index 0000000..240edd1 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/config/cl_config.lua @@ -0,0 +1,53 @@ +if SAM_LOADED then return end + +local sam = sam +local mp = sam.mp +local config = sam.config + +function config.set(key, value, force) + if not sam.isstring(key) then + error("invalid setting name") + end + + if not mp.packers[sam.type(value)] then + error("not supported value type") + end + + if not force and config.get(key) == value then return end + sam.netstream.Start("Config.Set", key, value) +end + +function config.get(key, default) + local value = sam.get_global("Config")[key] + if value ~= nil then + return value + end + return default +end + +local menu_settings = {} +function config.add_menu_setting(title, func) + local i = #menu_settings + 1 + for k, v in ipairs(menu_settings) do + if v.title == title then + i = k + break + end + end + menu_settings[i] = { + title = title, + func = func, + } +end + +function config.get_menu_settings() + return menu_settings +end + +hook.Add("SAM.ChangedGlobalVar", "SAM.CheckLoadedConfig", function(key, value) + if key == "Config" then + config.loaded = true + hook.Call("SAM.LoadedConfig", nil, value) + hook.Remove("SAM.ChangedGlobalVar", "SAM.CheckLoadedConfig") + end +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/config/sh_config.lua b/garrysmod/addons/module_adminmod/lua/sam/config/sh_config.lua new file mode 100644 index 0000000..a47a963 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/config/sh_config.lua @@ -0,0 +1,51 @@ +if SAM_LOADED then return end + +local sam = sam +local config = sam.config + +sam.permissions.add("manage_config", nil, "superadmin") + +local updates = {} +function config.hook(keys, func) + for i = #keys, 1, -1 do + keys[keys[i]] = true + keys[i] = nil + end + + local id = table.insert(updates, { + keys = keys, + func = func + }) + + if config.loaded then + func() + end + + return id +end + +function config.get_updated(key, default) + local setting = {} + config.hook({key}, function() + setting.value = config.get(key, default) + end) + return setting +end + +function config.remove_hook(key) + updates[key] = nil +end + +hook.Add("SAM.LoadedConfig", "RunHooks", function() + for k, v in pairs(updates) do + v.func() + end +end) + +hook.Add("SAM.UpdatedConfig", "RunHooks", function(key, value, old) + for k, v in pairs(updates) do + if v.keys[key] then + v.func(value, old) + end + end +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/config/sv_config.lua b/garrysmod/addons/module_adminmod/lua/sam/config/sv_config.lua new file mode 100644 index 0000000..d439cf3 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/config/sv_config.lua @@ -0,0 +1,99 @@ +if SAM_LOADED then return end + +local sam = sam +local SQL = sam.SQL +local mp = sam.mp +local config = sam.config + +local _config = {} + +function config.sync() + sam.set_global("Config", _config, true) +end + +local to_hex +do + local byte = string.byte + local gsub = string.gsub + local format = string.format + + local hex = function(c) + return format("%02X", byte(c)) + end + + function to_hex(text) + return (gsub(text, ".", hex)) + end +end + +function config.set(key, value, force) + if not sam.isstring(key) then + error("invalid setting name") + end + + if not mp.packers[sam.type(value)] then + error("not supported value type") + end + + local old = _config[key] + if not force and value == old then return end + + SQL.FQuery([[ + REPLACE INTO + `sam_config`( + `key`, + `value` + ) + VALUES + ({1}, X{2}) + ]], {key, to_hex(mp.pack(value))}) + + _config[key] = value + config.sync() + sam.hook_call("SAM.UpdatedConfig", key, value, old) +end + +function config.get(key, default) + local value = _config[key] + if value ~= nil then + return value + end + return default +end + +config.sync() + +hook.Add("SAM.DatabaseLoaded", "LoadConfig", function() + SQL.Query([[ + SELECT + * + FROM + `sam_config` + ]], function(sam_config) + for _, v in ipairs(sam_config) do + _config[v.key] = mp.unpack(v.value) + end + + config.loaded = true + config.sync() + hook.Call("SAM.LoadedConfig", nil, _config) + end):wait() +end) + +sam.netstream.Hook("Config.Set", function(ply, key, value) + config.set(key, value) + + value = tostring(value) + + if value == "" then + sam.player.send_message(nil, "{A} changed {S Blue} setting to: {S_2 Red}", { + A = ply, S = key, S_2 = "none" + }) + else + sam.player.send_message(nil, "{A} changed {S Blue} setting to: {S_2}", { + A = ply, S = key, S_2 = value + }) + end +end, function(ply) + return ply:HasPermission("manage_config") +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/importers/serverguard/main.lua b/garrysmod/addons/module_adminmod/lua/sam/importers/serverguard/main.lua new file mode 100644 index 0000000..f501569 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/importers/serverguard/main.lua @@ -0,0 +1,180 @@ +local sam, SQL = sam, sam.SQL + +local von = include("sam/importers/serverguard/sam_von.lua") +if not von then + return sam.print("Download the importer folder again from gmodstore.") +end + +local no_bans_msg = "No bans to import from serverguard." +local import_bans = function() + SQL.TableExists("serverguard_bans", function(exists) + if not exists then + return sam.print(no_bans_msg) + end + + SQL.Query([[ + DELETE FROM `sam_bans` + ]]) + + SQL.Query([[ + INSERT INTO + `sam_bans`( + `id`, + `steamid`, + `reason`, + `admin`, + `unban_date` + ) + SELECT + `id`, + `steam_id`, + `reason`, + 'Console', + `end_time` + FROM + `serverguard_bans` + ]], function() + sam.print("Imported bans from serverguard.") + end) + end) +end + +local no_ranks_msg = "No ranks to import from serverguard." +local import_ranks = function() + SQL.TableExists("serverguard_ranks", function(exists) + if not exists then + return sam.print(no_ranks_msg) + end + + SQL.Query([[ + SELECT + * + FROM + `serverguard_ranks` + ]], function(serverguard_ranks) + for _, v in pairs(serverguard_ranks) do + local name = v.unique_id + + if not sam.ranks.is_rank(name) then + sam.ranks.add_rank(name, "user", tonumber(v.immunity), tonumber(v.banlimit)) + end + + local data = sam.isstring(v.data) and util.JSONToTable(v.data) + local tools = sam.istable(data) and sam.istable(data.Restrictions) and sam.istable(data.Restrictions.Tools) and data.Restrictions.Tools + + if not tools then continue end + + for tool_name, value in pairs(tools) do + if value == false then + sam.ranks.take_permission(name, tool_name) + else + sam.ranks.give_permission(name, tool_name) + end + end + end + + sam.print("Imported ranks from serverguard.") + end) + end) +end + +local no_users_msg = "No users to import from serverguard." + +local import_expires = function(data) + if data and #data > 0 then + local began = false + for _, v in ipairs(data) do + local ply_data = v.data and von.deserialize(v.data) or {} + if not sam.isnumber(tonumber(ply_data.groupExpire)) then continue end + + if not began then + began = true + SQL.Begin() + end + + SQL.FQuery([[ + UPDATE + `sam_players` + SET + `expiry_date` = {1} + WHERE + `steamid` = {2} + ]], {tonumber(ply_data.groupExpire), v.steam_id}) + end + SQL.Commit(function() + sam.print("Imported users from serverguard.") + end) + else + sam.print("Imported users from serverguard.") + end +end + +local import_users = function() + SQL.TableExists("serverguard_users", function(exists) + if not exists then + return sam.print(no_users_msg) + end + + SQL.Query([[ + DELETE FROM `sam_players` + ]]) + + SQL.Query([[ + INSERT INTO + `sam_players`( + `id`, + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + ) + SELECT + `id`, + `steam_id`, + `name`, + `rank`, + 0, + `last_played`, + `last_played`, + 0 + FROM + `serverguard_users` + ]], function() + SQL.Query([[ + SELECT + `steam_id`, + `data` + FROM + `serverguard_users` + ]], import_expires) + end) + end) +end + +if not sam.ranks.ranks_loaded() then + return sam.print("Server is connecting to the Database, try again when it connects.") +end + +for k, v in ipairs(player.GetAll()) do + v:Kick("Importing data.") +end + +for k, v in pairs(sam.get_connected_players()) do + sam.player.kick_id(k, "Importing data.") +end + +hook.Add("CheckPassword", "SAM.ImportingData", function() + return false, "Importing data." +end) + +sam.print("Starting to import data from serverguard...") +timer.Simple(0, function() + import_bans() + import_ranks() + import_users() + + hook.Remove("CheckPassword", "SAM.ImportingData") +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/importers/serverguard/sam_von.lua b/garrysmod/addons/module_adminmod/lua/sam/importers/serverguard/sam_von.lua new file mode 100644 index 0000000..fd8f772 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/importers/serverguard/sam_von.lua @@ -0,0 +1,818 @@ +--[[ vON 1.3.4 + + Copyright 2012-2014 Alexandru-Mihai Maftei + aka Vercas + + GitHub Repository: + https://github.com/vercas/vON + + 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 (Vercas) if you publish your work based on (and/or using) this. + + If you modify the code for any purpose, the above obligations still apply. + If you make any interesting modifications, try forking the GitHub repository instead. + + Instead of copying this code over for sharing, rather use the link: + https://github.com/vercas/vON/blob/master/von.lua + + The author may not be held responsible for any damage or losses directly or indirectly caused by + the use of vON. + + If you disagree with the above, don't use the code. + +----------------------------------------------------------------------------------------------------------------------------- + + Thanks to the following people for their contribution: + - Divran Suggested improvements for making the code quicker. + Suggested an excellent new way of deserializing strings. + Lead me to finding an extreme flaw in string parsing. + - pennerlord Provided some performance tests to help me improve the code. + - Chessnut Reported bug with handling of nil values when deserializing array components. + + - People who contributed on the GitHub repository by reporting bugs, posting fixes, etc. + +----------------------------------------------------------------------------------------------------------------------------- + + The vanilla types supported in this release of vON are: + - table + - number + - boolean + - string + - nil + + The Garry's Mod-specific types supported in this release are: + - Vector + - Angle + + Entities: + - Entity + - Vehicle + - Weapon + - NPC + - Player + - NextBot + + These are the types one would normally serialize. + +----------------------------------------------------------------------------------------------------------------------------- + + New in this version: + - Fixed addition of extra entity types. I messed up really badly. +--]] + + + +local _deserialize, _serialize, _d_meta, _s_meta, d_findVariable, s_anyVariable +local sub, gsub, find, insert, concat, error, tonumber, tostring, type, next = string.sub, string.gsub, string.find, table.insert, table.concat, error, tonumber, tostring, type, next + + + +--[[ This section contains localized functions which (de)serialize + variables according to the types found. ]] + + + +-- This is kept away from the table for speed. +function d_findVariable(s, i, len, lastType, jobstate) + local i, c, typeRead, val = i or 1 + + -- Keep looping through the string. + while true do + -- Stop at the end. Throw an error. This function MUST NOT meet the end! + if i > len then + error("vON: Reached end of string, cannot form proper variable.") + end + + -- Cache the character. Nobody wants to look for the same character ten times. + c = sub(s, i, i) + + -- If it just read a type definition, then a variable HAS to come after it. + if typeRead then + -- Attempt to deserialize a variable of the freshly read type. + val, i = _deserialize[lastType](s, i, len, false, jobstate) + -- Return the value read, the index of the last processed character, and the type of the last read variable. + return val, i, lastType + + -- @ means nil. It should not even appear in the output string of the serializer. Nils are useless to store. + elseif c == "@" then + return nil, i, lastType + + -- $ means a table reference will follow - a number basically. + elseif c == "$" then + lastType = "table_reference" + typeRead = true + + -- n means a number will follow. Base 10... :C + elseif c == "n" then + lastType = "number" + typeRead = true + + -- b means boolean flags. + elseif c == "b" then + lastType = "boolean" + typeRead = true + + -- ' means the start of a string. + elseif c == "'" then + lastType = "string" + typeRead = true + + -- " means the start of a string prior to version 1.2.0. + elseif c == "\"" then + lastType = "oldstring" + typeRead = true + + -- { means the start of a table! + elseif c == "{" then + lastType = "table" + typeRead = true + + +--[[ Garry's Mod types go here ]] + + -- e means an entity ID will follow. + elseif c == "e" then + lastType = "Entity" + typeRead = true +--[[ + -- c means a vehicle ID will follow. + elseif c == "c" then + lastType = "Vehicle" + typeRead = true + + -- w means a weapon entity ID will follow. + elseif c == "w" then + lastType = "Weapon" + typeRead = true + + -- x means a NPC ID will follow. + elseif c == "x" then + lastType = "NPC" + typeRead = true +--]] + -- p means a player ID will follow. + -- Kept for backwards compatibility. + elseif c == "p" then + lastType = "Entity" + typeRead = true + + -- v means a vector will follow. 3 numbers. + elseif c == "v" then + lastType = "Vector" + typeRead = true + + -- a means an Euler angle will follow. 3 numbers. + elseif c == "a" then + lastType = "Angle" + typeRead = true + +--[[ Garry's Mod types end here ]] + + + -- If no type has been found, attempt to deserialize the last type read. + elseif lastType then + val, i = _deserialize[lastType](s, i, len, false, jobstate) + return val, i, lastType + + -- This will occur if the very first character in the vON code is wrong. + else + error("vON: Malformed data... Can't find a proper type definition. Char#" .. i .. ":" .. c) + end + + -- Move the pointer one step forward. + i = i + 1 + end +end + +-- This is kept away from the table for speed. +-- Yeah, ton of parameters. +function s_anyVariable(data, lastType, isNumeric, isKey, isLast, jobstate) + local tp = type(data) + + if jobstate[1] and jobstate[2][data] then + tp = "table_reference" + end + + -- Basically, if the type changes. + if lastType ~= tp then + -- Remember the new type. Caching the type is useless. + lastType = tp + + if _serialize[lastType] then + -- Return the serialized data and the (new) last type. + -- The second argument, which is true now, means that the data type was just changed. + return _serialize[lastType](data, true, isNumeric, isKey, isLast, false, jobstate), lastType + else + error("vON: No serializer defined for type \"" .. lastType .. "\"!") + end + end + + -- Otherwise, simply serialize the data. + return _serialize[lastType](data, false, isNumeric, isKey, isLast, false, jobstate), lastType +end + + + +--[[ This section contains the tables with the functions necessary + for decoding basic Lua data types. ]] + + + +_deserialize = { +-- Well, tables are very loose... +-- The first table doesn't have to begin and end with { and }. + ["table"] = function(s, i, len, unnecessaryEnd, jobstate) + local ret, numeric, i, c, lastType, val, ind, expectValue, key = {}, true, i or 1, nil, nil, nil, 1 + -- Locals, locals, locals, locals, locals, locals, locals, locals and locals. + + if sub(s, i, i) == "#" then + local e = find(s, "#", i + 2, true) + + if e then + local id = tonumber(sub(s, i + 1, e - 1)) + + if id then + if jobstate[1][id] and not jobstate[2] then + error("vON: There already is a table of reference #" .. id .. "! Missing an option maybe?") + end + + jobstate[1][id] = ret + + i = e + 1 + else + error("vON: Malformed table! Reference ID starting at char #" .. i .. " doesn't contain a number!") + end + else + error("vON: Malformed table! Cannot find end of reference ID start at char #" .. i .. "!") + end + end + + -- Keep looping. + while true do + -- Until it meets the end. + if i > len then + -- Yeah, if the end is unnecessary, it won't spit an error. The main chunk doesn't require an end, for example. + if unnecessaryEnd then + return ret, i + + -- Otherwise, the data has to be damaged. + else + error("vON: Reached end of string, incomplete table definition.") + end + end + + -- Cache the character. + c = sub(s, i, i) + --print(i, "table char:", c, tostring(unnecessaryEnd)) + + -- If it's the end of a table definition, return. + if c == "}" then + return ret, i + + -- If it's the component separator, switch to key:value pairs. + elseif c == "~" then + numeric = false + + elseif c == ";" then + -- Lol, nothing! + -- Remenant from numbers, for faster parsing. + + -- OK, now, if it's on the numeric component, simply add everything encountered. + elseif numeric then + -- Find a variable and it's value + val, i, lastType = d_findVariable(s, i, len, lastType, jobstate) + -- Add it to the table. + ret[ind] = val + + ind = ind + 1 + + -- Otherwise, if it's the key:value component... + else + -- If a value is expected... + if expectValue then + -- Read it. + val, i, lastType = d_findVariable(s, i, len, lastType, jobstate) + -- Add it? + ret[key] = val + -- Clean up. + expectValue, key = false, nil + + -- If it's the separator... + elseif c == ":" then + -- Expect a value next. + expectValue = true + + -- But, if there's a key read already... + elseif key then + -- Then this is malformed. + error("vON: Malformed table... Two keys declared successively? Char#" .. i .. ":" .. c) + + -- Otherwise the key will be read. + else + -- I love multi-return and multi-assignement. + key, i, lastType = d_findVariable(s, i, len, lastType, jobstate) + end + end + + i = i + 1 + end + + return nil, i + end, + +-- Just a number which points to a table. + ["table_reference"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + local n = tonumber(sub(s, i, a - 1)) + + if n then + return jobstate[1][n] or error("vON: Table reference does not point to a (yet) known table!"), a - 1 + else + error("vON: Table reference definition does not contain a valid number!") + end + end + + -- Using %D breaks identification of negative numbers. :( + + error("vON: Number definition started... Found no end.") + end, + + +-- Numbers are weakly defined. +-- The declaration is not very explicit. It'll do it's best to parse the number. +-- Has various endings: \n, }, ~, : and ;, some of which will force the table deserializer to go one char backwards. + ["number"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + return tonumber(sub(s, i, a - 1)) or error("vON: Number definition does not contain a valid number!"), a - 1 + end + + -- Using %D breaks identification of negative numbers. :( + + error("vON: Number definition started... Found no end.") + end, + + +-- A boolean is A SINGLE CHARACTER, either 1 for true or 0 for false. +-- Any other attempt at boolean declaration will result in a failure. + ["boolean"] = function(s, i, len, unnecessaryEnd, jobstate) + local c = sub(s,i,i) + -- Only one character is needed. + + -- If it's 1, then it's true + if c == "1" then + return true, i + + -- If it's 0, then it's false. + elseif c == "0" then + return false, i + end + + -- Any other supposely "boolean" is just a sign of malformed data. + error("vON: Invalid value on boolean type... Char#" .. i .. ": " .. c) + end, + + +-- Strings prior to 1.2.0 + ["oldstring"] = function(s, i, len, unnecessaryEnd, jobstate) + local res, i, a = "", i or 1 + -- Locals, locals, locals, locals + + while true do + a = find(s, "\"", i, true) + + if a then + if sub(s, a - 1, a - 1) == "\\" then + res = res .. sub(s, i, a - 2) .. "\"" + i = a + 1 + else + return res .. sub(s, i, a - 2), a + end + else + error("vON: Old string definition started... Found no end.") + end + end + end, + +-- Strings after 1.2.0 + ["string"] = function(s, i, len, unnecessaryEnd, jobstate) + local res, i, a = "", i or 1 + -- Locals, locals, locals, locals + + while true do + a = find(s, "\"", i, true) + + if a then + if sub(s, a - 1, a - 1) == "\\" then + res = res .. sub(s, i, a - 2) .. "\"" + i = a + 1 + else + return res .. sub(s, i, a - 1), a + end + else + error("vON: String definition started... Found no end.") + end + end + end, +} + + + +_serialize = { +-- Uh. Nothing to comment. +-- Ton of parameters. +-- Makes stuff faster than simply passing it around in locals. +-- table.concat works better than normal concatenations WITH LARGE-ISH STRINGS ONLY. + ["table"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + --print(string.format("data: %s; mustInitiate: %s; isKey: %s; isLast: %s; nice: %s; indent: %s; first: %s", tostring(data), tostring(mustInitiate), tostring(isKey), tostring(isLast), tostring(nice), tostring(indent), tostring(first))) + + local result, keyvals, len, keyvalsLen, keyvalsProgress, val, lastType, newIndent, indentString = {}, {}, #data, 0, 0 + -- Locals, locals, locals, locals, locals, locals, locals, locals, locals and locals. + + -- First thing to be done is separate the numeric and key:value components of the given table in two tables. + -- pairs(data) is slower than next, data as far as my tests tell me. + for k, v in next, data do + -- Skip the numeric keyz. + if type(k) ~= "number" or k < 1 or k > len or (k % 1 ~= 0) then -- k % 1 == 0 is, as proven by personal benchmarks, + keyvals[#keyvals + 1] = k -- the quickest way to check if a number is an integer. + end -- k % 1 ~= 0 is the fastest way to check if a number + end -- is NOT an integer. > is proven slower. + + keyvalsLen = #keyvals + + -- Main chunk - no initial character. + if not first then + result[#result + 1] = "{" + end + + if jobstate[1] and jobstate[1][data] then + if jobstate[2][data] then + error("vON: Table #" .. jobstate[1][data] .. " written twice..?") + end + + result[#result + 1] = "#" + result[#result + 1] = jobstate[1][data] + result[#result + 1] = "#" + + jobstate[2][data] = true + end + + -- Add numeric values. + if len > 0 then + for i = 1, len do + val, lastType = s_anyVariable(data[i], lastType, true, false, i == len and not first, jobstate) + result[#result + 1] = val + end + end + + -- If there are key:value pairs. + if keyvalsLen > 0 then + -- Insert delimiter. + result[#result + 1] = "~" + + -- Insert key:value pairs. + for _i = 1, keyvalsLen do + keyvalsProgress = keyvalsProgress + 1 + + val, lastType = s_anyVariable(keyvals[_i], lastType, false, true, false, jobstate) + + result[#result + 1] = val..":" + + val, lastType = s_anyVariable(data[keyvals[_i]], lastType, false, false, keyvalsProgress == keyvalsLen and not first, jobstate) + + result[#result + 1] = val + end + end + + -- Main chunk needs no ending character. + if not first then + result[#result + 1] = "}" + end + + return concat(result) + end, + +-- Number which points to table. + ["table_reference"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + data = jobstate[1][data] + + -- If a number hasn't been written before, add the type prefix. + if mustInitiate then + if isKey or isLast then + return "$"..data + else + return "$"..data..";" + end + end + + if isKey or isLast then + return data + else + return data..";" + end + end, + + +-- Normal concatenations is a lot faster with small strings than table.concat +-- Also, not so branched-ish. + ["number"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + -- If a number hasn't been written before, add the type prefix. + if mustInitiate then + if isKey or isLast then + return "n"..data + else + return "n"..data..";" + end + end + + if isKey or isLast then + return data + else + return data..";" + end + end, + + +-- I hope gsub is fast enough. + ["string"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + if sub(data, #data, #data) == "\\" then -- Hah, old strings fix this best. + return "\"" .. gsub(data, "\"", "\\\"") .. "v\"" + end + + return "'" .. gsub(data, "\"", "\\\"") .. "\"" + end, + + +-- Fastest. + ["boolean"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + -- Prefix if we must. + if mustInitiate then + if data then + return "b1" + else + return "b0" + end + end + + if data then + return "1" + else + return "0" + end + end, + + +-- Fastest. + ["nil"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + return "@" + end, +} + + + +--[[ This section handles additions necessary for Garry's Mod. ]] + + + +if gmod then -- Luckily, a specific table named after the game is present in Garry's Mod. + local Entity = Entity + + + + local extra_deserialize = { +-- Entities are stored simply by the ID. They're meant to be transfered, not stored anyway. +-- Exactly like a number definition, except it begins with "e". + ["Entity"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + return Entity(tonumber(sub(s, i, a - 1))), a - 1 + end + + error("vON: Entity ID definition started... Found no end.") + end, + + +-- A pair of 3 numbers separated by a comma (,). + ["Vector"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a, x, y, z = i or 1 + -- Locals, locals, locals, locals + + a = find(s, ",", i) + + if a then + x = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, ",", i) + + if a then + y = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, "[;:}~]", i) + + if a then + z = tonumber(sub(s, i, a - 1)) + end + + if x and y and z then + return Vector(x, y, z), a - 1 + end + + error("vON: Vector definition started... Found no end.") + end, + + +-- A pair of 3 numbers separated by a comma (,). + ["Angle"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a, p, y, r = i or 1 + -- Locals, locals, locals, locals + + a = find(s, ",", i) + + if a then + p = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, ",", i) + + if a then + y = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, "[;:}~]", i) + + if a then + r = tonumber(sub(s, i, a - 1)) + end + + if p and y and r then + return Angle(p, y, r), a - 1 + end + + error("vON: Angle definition started... Found no end.") + end, + } + + local extra_serialize = { +-- Same as numbers, except they start with "e" instead of "n". + ["Entity"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + data = data:EntIndex() + + if mustInitiate then + if isKey or isLast then + return "e"..data + else + return "e"..data..";" + end + end + + if isKey or isLast then + return data + else + return data..";" + end + end, + + +-- 3 numbers separated by a comma. + ["Vector"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + if mustInitiate then + if isKey or isLast then + return "v"..data.x..","..data.y..","..data.z + else + return "v"..data.x..","..data.y..","..data.z..";" + end + end + + if isKey or isLast then + return data.x..","..data.y..","..data.z + else + return data.x..","..data.y..","..data.z..";" + end + end, + + +-- 3 numbers separated by a comma. + ["Angle"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + if mustInitiate then + if isKey or isLast then + return "a"..data.p..","..data.y..","..data.r + else + return "a"..data.p..","..data.y..","..data.r..";" + end + end + + if isKey or isLast then + return data.p..","..data.y..","..data.r + else + return data.p..","..data.y..","..data.r..";" + end + end, + } + + for k, v in pairs(extra_serialize) do + _serialize[k] = v + end + + for k, v in pairs(extra_deserialize) do + _deserialize[k] = v + end + + local extraEntityTypes = { "Vehicle", "Weapon", "NPC", "Player", "NextBot" } + + for i = 1, #extraEntityTypes do + _serialize[extraEntityTypes[i]] = _serialize.Entity + end +end + + + +--[[ This section exposes the functions of the library. ]] + + + +local function checkTableForRecursion(tab, checked, assoc) + local id = checked.ID + + if not checked[tab] and not assoc[tab] then + assoc[tab] = id + checked.ID = id + 1 + else + checked[tab] = true + end + + for k, v in pairs(tab) do + if type(k) == "table" and not checked[k] then + checkTableForRecursion(k, checked, assoc) + end + + if type(v) == "table" and not checked[v] then + checkTableForRecursion(v, checked, assoc) + end + end +end + + + +local _s_table = _serialize.table +local _d_table = _deserialize.table + +_d_meta = { + __call = function(self, str, allowIdRewriting) + if type(str) == "string" then + return _d_table(str, nil, #str, true, {{}, allowIdRewriting}) + end + + error("vON: You must deserialize a string, not a "..type(str)) + end +} +_s_meta = { + __call = function(self, data, checkRecursion) + if type(data) == "table" then + if checkRecursion then + local assoc, checked = {}, {ID = 1} + + checkTableForRecursion(data, checked, assoc) + + return _s_table(data, nil, nil, nil, nil, true, {assoc, {}}) + end + + return _s_table(data, nil, nil, nil, nil, true, {false}) + end + + error("vON: You must serialize a table, not a "..type(data)) + end +} + + + +local von = { + version = "1.3.4", + versionNumber = 1003004, -- Reserving 3 digits per version component. + + deserialize = setmetatable(_deserialize,_d_meta), + serialize = setmetatable(_serialize,_s_meta) +} + + + +return von diff --git a/garrysmod/addons/module_adminmod/lua/sam/importers/ulx/main.lua b/garrysmod/addons/module_adminmod/lua/sam/importers/ulx/main.lua new file mode 100644 index 0000000..ff192c9 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/importers/ulx/main.lua @@ -0,0 +1,419 @@ +-- PLEASE READ +-- PLEASE READ +-- PLEASE READ +-- PLEASE READ + +-- Set the timing module to import times from when importing users +-- Supported modules: "utime" +-- If your timing module is not here then STOP and make a support ticket so I can add support for it before you import your users +local TIME_MODULE = "utime" -- Set this to nil if you don't want to import times + +-- PLEASE READ +-- PLEASE READ +-- PLEASE READ +-- PLEASE READ + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +local sam, SQL = sam, sam.SQL + +local ulib_unescape_backslash = function(s) + return s:gsub("\\\\", "\\") +end + +local ulib_explode = function(separator, str, limit) + local t = {} + local curpos = 1 + while true do -- We have a break in the loop + local newpos, endpos = str:find(separator, curpos) -- find the next separator in the string + if newpos ~= nil then -- if found then.. + table.insert(t, str:sub(curpos, newpos - 1)) -- Save it in our table. + curpos = endpos + 1 -- save just after where we found it for searching next time. + else + if limit and #t > limit then + return t -- Reached limit + end + table.insert(t, str:sub(curpos)) -- Save what's left in our array. + break + end + end + return t +end + +local ulib_split_args = function(args, start_token, end_token) + args = args:Trim() + local argv = {} + local curpos = 1 -- Our current position within the string + local in_quote = false -- Is the text we're currently processing in a quote? + start_token = start_token or "\"" + end_token = end_token or "\"" + local args_len = args:len() + + while in_quote or curpos <= args_len do + local quotepos = args:find(in_quote and end_token or start_token, curpos, true) + + -- The string up to the quote, the whole string if no quote was found + local prefix = args:sub(curpos, (quotepos or 0) - 1) + if not in_quote then + local trimmed = prefix:Trim() + if trimmed ~= "" then -- Something to be had from this... + local t = ulib_explode("%s+", trimmed) + table.Add(argv, t) + end + else + table.insert(argv, prefix) + end + + -- If a quote was found, reduce our position and note our state + if quotepos ~= nil then + curpos = quotepos + 1 + in_quote = not in_quote + else -- Otherwise we've processed the whole string now + break + end + end + + return argv, in_quote +end + +local ulib_parse_key_values = function(str, convert) + local lines = ulib_explode("\r?\n", str) + local parent_tables = {} -- Traces our way to root + local current_table, n = {}, 0 + local is_insert_last_op = false + local tmp_string = string.char(01, 02, 03) -- Replacement + + for i, line in ipairs(lines) do + local tokens = ulib_split_args((line:gsub("\\\"", tmp_string))) + + for i, token in ipairs(tokens) do + tokens[ i ] = ulib_unescape_backslash(token):gsub(tmp_string, "\"") + end + + local num_tokens = #tokens + + if num_tokens == 1 then + local token = tokens[ 1 ] + if token == "{" then + local new_table = {} + if is_insert_last_op then + current_table[ table.remove(current_table) ] = new_table + else + table.insert(current_table, new_table) + end + is_insert_last_op = false + table.insert(parent_tables, current_table) + current_table = new_table + + elseif token == "}" then + is_insert_last_op = false + current_table = table.remove(parent_tables) + if current_table == nil then + return nil, "Mismatched recursive tables on line " .. i + end + + else + is_insert_last_op = true + table.insert(current_table, tokens[ 1 ]) + end + + elseif num_tokens == 2 then + is_insert_last_op = false + if convert and tonumber(tokens[ 1 ]) then + tokens[ 1 ] = tonumber(tokens[ 1 ]) + end + + current_table[ tokens[ 1 ] ] = tokens[ 2 ] + + elseif num_tokens > 2 then + return nil, "Bad input on line " .. i + end + end + + if #parent_tables ~= 0 then + return nil, "Mismatched recursive tables" + end + + if convert and n == 1 and + type(current_table.Out) == "table" then -- If we caught a stupid garry-wrapper + + current_table = current_table.Out + end + + return current_table +end + +local ulib_remove_comment_header = function(data, comment_char) + comment_char = comment_char or ";" + local lines = ulib_explode("\r?\n", data) + local end_comment_line = 0 + for _, line in ipairs(lines) do + local trimmed = line:Trim() + if trimmed == "" or trimmed:sub(1, 1) == comment_char then + end_comment_line = end_comment_line + 1 + else + break + end + end + + local not_comment = table.concat(lines, "\n", end_comment_line + 1) + return not_comment:Trim() +end + +local no_bans_msg = "No bans to import from ulx." +local import_bans = function() + sam.print("Importing bans...") + + if not sql.TableExists("ulib_bans") then + return sam.print(no_bans_msg) + end + + local bans = sql.Query("SELECT `steamid`, `unban`, `reason`, `admin` FROM `ulib_bans`") + + if not sam.istable(bans) then + return sam.print(no_bans_msg) + end + + local bans_count = #bans + + if bans_count == 0 then + return sam.print(no_bans_msg) + end + + local began, imported_count = false, 0 + + for i = 1, bans_count do + local ban = bans[i] + local steamid = ban.steamid + + if sam.is_steamid64(steamid) then + steamid = util.SteamIDFrom64(ban.steamid) + end + + if sam.is_steamid(steamid) then + if not began then + began = true + SQL.Begin() + SQL.Query([[ + DELETE FROM `sam_bans` + ]]) + end + + local name, reason, admin, unban_date = ban.name, ban.reason, ban.admin, tonumber(ban.unban) + + if name == "NULL" then + name = "" + end + + if reason == "NULL" then + reason = "none" + end + + if sam.isstring(admin) and admin ~= "NULL" and admin ~= "(Console)" then + admin = admin:match("%(STEAM_%w:%w:%w*%)"):sub(2, -2) + else + admin = "Console" + end + + if not sam.isnumber(unban_date) or unban_date < 0 then + unban_date = 0 + end + + SQL.FQuery([[ + INSERT INTO + `sam_bans`( + `steamid`, + `reason`, + `admin`, + `unban_date` + ) + VALUES + ({1}, {2}, {3}, {4}) + ]], {steamid, reason, admin, unban_date}) + + imported_count = imported_count + 1 + end + end + + if began then + SQL.Commit(function() + sam.print("Imported " .. imported_count .. " ban(s).") + end) + else + sam.print(no_bans_msg) + end +end + +local groups_path = "ulib/groups.txt" +local no_ranks_msg = "No ranks to import from ulx." + +local imported_ranks + +local function add_rank(rank, info, ranks) + if not sam.ranks.is_rank(rank) then + local inherit = info.inherit_from + if inherit and ranks[inherit] and not sam.ranks.is_rank(inherit) then + add_rank(inherit, ranks[inherit], ranks) + end + imported_ranks = imported_ranks + 1 + sam.ranks.add_rank(rank, inherit) + end +end + +local import_ranks = function() + sam.print("Importing ranks...") + + if not file.Exists(groups_path, "DATA") then + return sam.print(no_ranks_msg) + end + + local ranks_text = file.Read(groups_path, "DATA") + + if not sam.isstring(ranks_text) then + return sam.print(no_ranks_msg) + end + + local ranks, err = ulib_parse_key_values(ulib_remove_comment_header(ranks_text, "/")) + if not ranks then + return sam.print("ULX ranks file was not formatted correctly, make a support ticket. (include the error message if there is one)" .. (err and (" | error: " .. err) or "")) + end + + imported_ranks = 0 + + for rank, info in pairs(ranks) do + if not sam.ranks.is_rank(rank) then + add_rank(rank, info, ranks) + end + end + + if imported_ranks == 0 then + sam.print(no_ranks_msg) + else + sam.print("Imported " .. imported_ranks .. " rank(s).") + end +end + +local users_path = "ulib/users.txt" +local no_users_msg = "No users to import from ulx." + +local import_users = function() + sam.print("Importing users...") + + if not file.Exists(users_path, "DATA") then + return sam.print(no_users_msg) + end + + local users_text = file.Read(users_path, "DATA") + + if not sam.isstring(users_text) then + return sam.print(no_users_msg) + end + + local users, err = ulib_parse_key_values(ulib_remove_comment_header(users_text, "/")) + if not users then + return sam.print("ULX users file was not formatted correctly, make a support ticket. (include the error message if there is one)" .. (err and (" | error: " .. err) or "")) + end + + local current_time = os.time() + local imported_users = 0 + + local times = false + + if TIME_MODULE == "utime" then + local utime = sql.Query("SELECT `player`, `lastvisit`, `totaltime` FROM `utime`") + if sam.istable(utime) then + times = {} + for i = 1, #utime do + local v = utime[i] + times[v.player] = v + end + end + end + + local began = false + + for steamid, info in pairs(users) do + if sam.is_steamid64(steamid) then + steamid = util.SteamIDFrom64(steamid) + end + + if sam.is_steamid(steamid) then + if not began then + began = true + SQL.Begin() + SQL.Query([[ + DELETE FROM `sam_players` + ]]) + end + + local first_join, last_join, play_time = current_time, current_time, 0 + + if times and TIME_MODULE == "utime" then + local utime_user = times[util.CRC("gm_" .. steamid .. "_gm")] + if utime_user then + last_join, play_time = utime_user.lastvisit, math.floor(utime_user.totaltime) + first_join = last_join + end + end + + local rank = info.group + if not rank or not sam.ranks.is_rank(rank) then + rank = "user" + end + + SQL.FQuery([[ + INSERT INTO + `sam_players`( + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + ) + VALUES + ({1}, {2}, {3}, {4}, {5}, {6}, {7}) + ]], {steamid, info.name or "", rank, 0, first_join, last_join, play_time}) + + imported_users = imported_users + 1 + end + end + + if began then + SQL.Commit(function() + sam.print("Imported " .. imported_users .. " user(s).") + end) + else + sam.print(no_users_msg) + end +end + +if not sam.ranks.ranks_loaded() then + return sam.print("Server is connecting to the Database, try again when it connects.") +end + +for k, v in ipairs(player.GetAll()) do + v:Kick("Importing data.") +end + +for k, v in pairs(sam.get_connected_players()) do + sam.player.kick_id(k, "Importing data.") +end + +hook.Add("CheckPassword", "SAM.ImportingData", function() + return false, "Importing data." +end) + +sam.print("Starting to import data from ulx...") +timer.Simple(0, function() + import_bans() + import_ranks() + import_users() + + hook.Remove("CheckPassword", "SAM.ImportingData") +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/importers/xadmin/main.lua b/garrysmod/addons/module_adminmod/lua/sam/importers/xadmin/main.lua new file mode 100644 index 0000000..47fee1e --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/importers/xadmin/main.lua @@ -0,0 +1,203 @@ +-- +-- CONFIG +-- + +local UsersTableName = "xadminusers" +local GroupsTableName = "xadmingroups" + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +local sam, SQL = sam, sam.SQL + +local no_bans_msg = "No bans to import from xadmin." +local import_bans = function() + SQL.TableExists("xadminbans", function(exists) + if not exists then + return sam.print(no_bans_msg) + end + + SQL.Query([[ + SELECT + `SteamID`, + `Admin`, + `Reason`, + `EndTime` + FROM + `xadminbans` + ]], function(xadmin_bans) + SQL.Begin() + + SQL.Query([[ + DELETE FROM `sam_bans` + ]]) + + local args, steamid, admin = {}, nil, nil + for _, v in ipairs(xadmin_bans) do + steamid, admin = util.SteamIDFrom64(v.SteamID), v.Admin + + if admin == "CONSOLE" then + admin = "Console" + else + admin = util.SteamIDFrom64(admin) + end + + args[1] = steamid + args[2] = v.Reason + args[3] = admin + args[4] = v.EndTime + + SQL.FQuery([[ + INSERT INTO + `sam_bans`( + `steamid`, + `reason`, + `admin`, + `unban_date` + ) + VALUES + ({1}, {2}, {3}, {4}) + ]], args) + end + + SQL.Commit(function() + sam.print("Imported bans from xadmin.") + end) + end) + end):wait() +end + +local get_rank = function(rank) + if rank == "Super Admin" then + return "superadmin" + elseif rank == "Admin" then + return "admin" + elseif rank == "Moderator" then + return "moderator" + elseif rank == "User" then + return "user" + end + return rank +end + +local no_ranks_msg = "No ranks to import from xadmin." +local import_ranks = function() + SQL.TableExists(GroupsTableName, function(exists) + if not exists then + return sam.print(no_ranks_msg) + end + + SQL.Query(([[ + SELECT + `Name`, + `Power` + FROM + `%s` + ]]):format(GroupsTableName), function(xadmin_ranks) + for _, v in ipairs(xadmin_ranks) do + local name = get_rank(v.Name) + + if not sam.ranks.is_rank(name) then + sam.ranks.add_rank(name, "user", tonumber(v.Power), 20160) + end + end + + sam.print("Imported ranks from xadmin.") + end):wait() + end):wait() +end + +local no_users_msg = "No users to import from xadmin." +local import_users = function() + SQL.TableExists(UsersTableName, function(exists) + if not exists then + return sam.print(no_users_msg) + end + + SQL.Query(([[ + SELECT + `%s`.`SteamID`, + `%s`.`AccessLevel`, + IFNULL(SUM(`time`), 0) `total` + FROM + `%s` + + LEFT OUTER JOIN `xadmintimetracking` `times` + on `%s`.`SteamID` = `times`.`SteamID` + + GROUP BY + `%s`.`SteamID` + ]]):format(UsersTableName, UsersTableName, UsersTableName, UsersTableName, UsersTableName), function(xadmin_players) + SQL.Begin() + + SQL.Query([[ + DELETE FROM `sam_players` + ]]) + + local args = {} + args[2] = "" + args[4] = 0 + args[5] = os.time() + args[6] = os.time() + + for k, v in ipairs(xadmin_players) do + args[1] = v.SteamID + + local rank = get_rank(v.AccessLevel) + if not sam.ranks.is_rank(rank) then + rank = "user" + end + + args[3] = rank + args[7] = v.total + + SQL.FQuery([[ + INSERT INTO + `sam_players`( + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + ) + VALUES + ({1}, {2}, {3}, {4}, {5}, {6}, {7}) + ]], args) + end + + SQL.Commit(function() + sam.print("Imported users from xadmin.") + end) + end):wait() + end):wait() +end + +if not sam.ranks.ranks_loaded() then + return sam.print("Server is connecting to the Database, try again when it connects.") +end + +for k, v in ipairs(player.GetAll()) do + v:Kick("Importing data.") +end + +for k, v in pairs(sam.get_connected_players()) do + sam.player.kick_id(k, "Importing data.") +end + +hook.Add("CheckPassword", "SAM.ImportingData", function() + return false, "Importing data." +end) + +sam.print("Starting to import data from xadmin...") +timer.Simple(0, function() + import_bans() + import_ranks() + import_users() + + hook.Remove("CheckPassword", "SAM.ImportingData") +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/message_pack/COPYRIGHT b/garrysmod/addons/module_adminmod/lua/sam/libs/message_pack/COPYRIGHT new file mode 100644 index 0000000..99b3454 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/message_pack/COPYRIGHT @@ -0,0 +1,30 @@ +lua-MessagePack License +-------------------------- + +lua-MessagePack is licensed under the terms of the MIT/X11 license reproduced below. + +=============================================================================== + +Copyright (C) 2012-2019 Francois Perrad. + +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. + +=============================================================================== + +(end of COPYRIGHT) diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/message_pack/sh_messagepack.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/message_pack/sh_messagepack.lua new file mode 100644 index 0000000..11a9519 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/message_pack/sh_messagepack.lua @@ -0,0 +1,864 @@ +if SAM_LOADED then return end +-- +-- lua-MessagePack : +-- +local SIZEOF_NUMBER = string.pack and #string.pack('n', 0.0) or 8 +local maxinteger +local mininteger +local assert = assert +local error = error +local pairs = pairs +local pcall = pcall +local setmetatable = setmetatable +local tostring = tostring +local char = string.char +local format = string.format +local floor = math.floor +local tointeger = floor +local frexp = math.frexp +local ldexp = math.ldexp +local huge = math.huge +local tconcat = table.concat +local type = sam.type +local isnumber = sam.isnumber +local _ENV = nil +local m = {} + +--[[ debug only +local function hexadump (s) + return (s:gsub('.', function (c) return format('%02X ', c:byte()) end)) +end +m.hexadump = hexadump +--]] +local function argerror(caller, narg, extramsg) + error("bad argument #" .. tostring(narg) .. " to " .. caller .. " (" .. extramsg .. ")") +end + +local function typeerror(caller, narg, arg, tname) + argerror(caller, narg, tname .. " expected, got " .. type(arg)) +end + +local function checktype(caller, narg, arg, tname) + if type(arg) ~= tname then + typeerror(caller, narg, arg, tname) + end +end + +local packers = setmetatable({}, { + __index = function(t, k) + if k == 1 then return end -- allows ipairs + error("pack '" .. k .. "' is unimplemented") + end +}) + +m.packers = packers + +packers["nil"] = function(buffer) + buffer[#buffer + 1] = char(0xC0) -- nil +end + +packers["boolean"] = function(buffer, bool) + if bool then + buffer[#buffer + 1] = char(0xC3) -- true + else + buffer[#buffer + 1] = char(0xC2) -- false + end +end + +packers["string_compat"] = function(buffer, str) + local n = #str + + if n <= 0x1F then + buffer[#buffer + 1] = char(0xA0 + n) -- fixstr + elseif n <= 0xFFFF then + buffer[#buffer + 1] = char(0xDA, floor(n / 0x100), n % 0x100) -- str16 + elseif n <= 4294967295.0 then + buffer[#buffer + 1] = char(0xDB, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- str32 + else + error"overflow in pack 'string_compat'" + end + + buffer[#buffer + 1] = str +end + +packers["_string"] = function(buffer, str) + local n = #str + + if n <= 0x1F then + buffer[#buffer + 1] = char(0xA0 + n) -- fixstr + elseif n <= 0xFF then + buffer[#buffer + 1] = char(0xD9, n) -- str8 + elseif n <= 0xFFFF then + buffer[#buffer + 1] = char(0xDA, floor(n / 0x100), n % 0x100) -- str16 + elseif n <= 4294967295.0 then + buffer[#buffer + 1] = char(0xDB, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- str32 + else + error("overflow in pack 'string'") + end + + buffer[#buffer + 1] = str +end + +packers["binary"] = function(buffer, str) + local n = #str + + if n <= 0xFF then + buffer[#buffer + 1] = char(0xC4, n) -- bin8 + elseif n <= 0xFFFF then + buffer[#buffer + 1] = char(0xC5, floor(n / 0x100), n % 0x100) -- bin16 + elseif n <= 4294967295.0 then + buffer[#buffer + 1] = char(0xC6, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- bin32 + else + error("overflow in pack 'binary'") + end + + buffer[#buffer + 1] = str +end + +local set_string = function(str) + if str == "string_compat" then + packers["string"] = packers["string_compat"] + elseif str == "string" then + packers["string"] = packers["_string"] + elseif str == "binary" then + packers["string"] = packers["binary"] + else + argerror("set_string", 1, "invalid option '" .. str .. "'") + end +end + +m.set_string = set_string + +packers["map"] = function(buffer, tbl, n) + if n <= 0x0F then + buffer[#buffer + 1] = char(0x80 + n) -- fixmap + elseif n <= 0xFFFF then + buffer[#buffer + 1] = char(0xDE, floor(n / 0x100), n % 0x100) -- map16 + elseif n <= 4294967295.0 then + buffer[#buffer + 1] = char(0xDF, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- map32 + else + error("overflow in pack 'map'") + end + + for k, v in pairs(tbl) do + packers[type(k)](buffer, k) + packers[type(v)](buffer, v) + end +end + +packers["array"] = function(buffer, tbl, n) + if n <= 0x0F then + buffer[#buffer + 1] = char(0x90 + n) -- fixarray + elseif n <= 0xFFFF then + buffer[#buffer + 1] = char(0xDC, floor(n / 0x100), n % 0x100) -- array16 + elseif n <= 4294967295.0 then + buffer[#buffer + 1] = char(0xDD, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- array32 + else + error("overflow in pack 'array'") + end + + for i = 1, n do + local v = tbl[i] + packers[type(v)](buffer, v) + end +end + +local set_array = function(array) + if array == "without_hole" then + packers["_table"] = function(buffer, tbl) + local is_map, n, max = false, 0, 0 + + for k in pairs(tbl) do + if isnumber(k) and k > 0 then + if k > max then + max = k + end + else + is_map = true + end + + n = n + 1 + end + + -- there are holes + if max ~= n then + is_map = true + end + + if is_map then + packers["map"](buffer, tbl, n) + else + packers["array"](buffer, tbl, n) + end + end + elseif array == "with_hole" then + packers["_table"] = function(buffer, tbl) + local is_map, n, max = false, 0, 0 + + for k in pairs(tbl) do + if isnumber(k) and k > 0 then + if k > max then + max = k + end + else + is_map = true + end + + n = n + 1 + end + + if is_map then + packers["map"](buffer, tbl, n) + else + packers["array"](buffer, tbl, max) + end + end + elseif array == "always_as_map" then + packers["_table"] = function(buffer, tbl) + local n = 0 + + for k in pairs(tbl) do + n = n + 1 + end + + packers["map"](buffer, tbl, n) + end + else + argerror("set_array", 1, "invalid option '" .. array .. "'") + end +end + +m.set_array = set_array + +packers["table"] = function(buffer, tbl) + packers["_table"](buffer, tbl) +end + +packers["unsigned"] = function(buffer, n) + if n >= 0 then + if n <= 0x7F then + buffer[#buffer + 1] = char(n) -- fixnum_pos + elseif n <= 0xFF then + buffer[#buffer + 1] = char(0xCC, n) -- uint8 + elseif n <= 0xFFFF then + buffer[#buffer + 1] = char(0xCD, floor(n / 0x100), n % 0x100) -- uint16 + elseif n <= 4294967295.0 then + buffer[#buffer + 1] = char(0xCE, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- uint32 + else + buffer[#buffer + 1] = char(0xCF, 0, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- uint64 -- only 53 bits from double + end + else + if n >= -0x20 then + buffer[#buffer + 1] = char(0x100 + n) -- fixnum_neg + elseif n >= -0x80 then + buffer[#buffer + 1] = char(0xD0, 0x100 + n) -- int8 + elseif n >= -0x8000 then + n = 0x10000 + n + buffer[#buffer + 1] = char(0xD1, floor(n / 0x100), n % 0x100) -- int16 + elseif n >= -0x80000000 then + n = 4294967296.0 + n + buffer[#buffer + 1] = char(0xD2, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int32 + else + buffer[#buffer + 1] = char(0xD3, 0xFF, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int64 -- only 53 bits from double + end + end +end + +packers["signed"] = function(buffer, n) + if n >= 0 then + if n <= 0x7F then + buffer[#buffer + 1] = char(n) -- fixnum_pos + elseif n <= 0x7FFF then + buffer[#buffer + 1] = char(0xD1, floor(n / 0x100), n % 0x100) -- int16 + elseif n <= 0x7FFFFFFF then + buffer[#buffer + 1] = char(0xD2, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int32 + else + buffer[#buffer + 1] = char(0xD3, 0, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int64 -- only 53 bits from double + end + else + if n >= -0x20 then + buffer[#buffer + 1] = char(0xE0 + 0x20 + n) -- fixnum_neg + elseif n >= -0x80 then + buffer[#buffer + 1] = char(0xD0, 0x100 + n) -- int8 + elseif n >= -0x8000 then + n = 0x10000 + n + buffer[#buffer + 1] = char(0xD1, floor(n / 0x100), n % 0x100) -- int16 + elseif n >= -0x80000000 then + n = 4294967296.0 + n + buffer[#buffer + 1] = char(0xD2, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int32 + else + buffer[#buffer + 1] = char(0xD3, 0xFF, floor(n / 0x1000000000000) % 0x100, floor(n / 0x10000000000) % 0x100, floor(n / 0x100000000) % 0x100, floor(n / 0x1000000) % 0x100, floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100) -- int64 -- only 53 bits from double + end + end +end + +local set_integer = function(integer) + if integer == "unsigned" then + packers["integer"] = packers["unsigned"] + elseif integer == "signed" then + packers["integer"] = packers["signed"] + else + argerror("set_integer", 1, "invalid option '" .. integer .. "'") + end +end + +m.set_integer = set_integer + +packers["float"] = function(buffer, n) + local sign = 0 + + if n < 0.0 then + sign = 0x80 + n = -n + end + + local mant, expo = frexp(n) + + if mant ~= mant then + buffer[#buffer + 1] = char(0xCA, 0xFF, 0x88, 0x00, 0x00) -- nan + elseif mant == huge or expo > 0x80 then + if sign == 0 then + buffer[#buffer + 1] = char(0xCA, 0x7F, 0x80, 0x00, 0x00) -- inf + else + buffer[#buffer + 1] = char(0xCA, 0xFF, 0x80, 0x00, 0x00) -- -inf + end + elseif (mant == 0.0 and expo == 0) or expo < -0x7E then + buffer[#buffer + 1] = char(0xCA, sign, 0x00, 0x00, 0x00) -- zero + else + expo = expo + 0x7E + mant = floor((mant * 2.0 - 1.0) * ldexp(0.5, 24)) + buffer[#buffer + 1] = char(0xCA, sign + floor(expo / 0x2), (expo % 0x2) * 0x80 + floor(mant / 0x10000), floor(mant / 0x100) % 0x100, mant % 0x100) + end +end + +packers["double"] = function(buffer, n) + local sign = 0 + + if n < 0.0 then + sign = 0x80 + n = -n + end + + local mant, expo = frexp(n) + + if mant ~= mant then + buffer[#buffer + 1] = char(0xCB, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- nan + elseif mant == huge or expo > 0x400 then + if sign == 0 then + buffer[#buffer + 1] = char(0xCB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- inf + else + buffer[#buffer + 1] = char(0xCB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- -inf + end + elseif (mant == 0.0 and expo == 0) or expo < -0x3FE then + buffer[#buffer + 1] = char(0xCB, sign, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) -- zero + else + expo = expo + 0x3FE + mant = floor((mant * 2.0 - 1.0) * ldexp(0.5, 53)) + buffer[#buffer + 1] = char(0xCB, sign + floor(expo / 0x10), (expo % 0x10) * 0x10 + floor(mant / 0x1000000000000), floor(mant / 0x10000000000) % 0x100, floor(mant / 0x100000000) % 0x100, floor(mant / 0x1000000) % 0x100, floor(mant / 0x10000) % 0x100, floor(mant / 0x100) % 0x100, mant % 0x100) + end +end + +local set_number = function(number) + if number == "float" then + packers["number"] = function(buffer, n) + if floor(n) == n and n < maxinteger and n > mininteger then + packers["integer"](buffer, n) + else + packers["float"](buffer, n) + end + end + elseif number == "double" then + packers["number"] = function(buffer, n) + if floor(n) == n and n < maxinteger and n > mininteger then + packers["integer"](buffer, n) + else + packers["double"](buffer, n) + end + end + else + argerror("set_number", 1, "invalid option '" .. number .. "'") + end +end + +m.set_number = set_number + +for k = 0, 4 do + local n = tointeger(2 ^ k) + local fixext = 0xD4 + k + + packers["fixext" .. tostring(n)] = function(buffer, tag, data) + assert(#data == n, "bad length for fixext" .. tostring(n)) + buffer[#buffer + 1] = char(fixext, tag < 0 and tag + 0x100 or tag) + buffer[#buffer + 1] = data + end +end + +packers["ext"] = function(buffer, tag, data) + local n = #data + + if n <= 0xFF then + buffer[#buffer + 1] = char(0xC7, n, tag < 0 and tag + 0x100 or tag) -- ext8 + elseif n <= 0xFFFF then + buffer[#buffer + 1] = char(0xC8, floor(n / 0x100), n % 0x100, tag < 0 and tag + 0x100 or tag) -- ext16 + elseif n <= 4294967295.0 then + buffer[#buffer + 1] = char(0xC9, floor(n / 0x1000000), floor(n / 0x10000) % 0x100, floor(n / 0x100) % 0x100, n % 0x100, tag < 0 and tag + 0x100 or tag) -- ext&32 + else + error("overflow in pack 'ext'") + end + + buffer[#buffer + 1] = data +end + +function m.pack(data) + local buffer = {} + packers[type(data)](buffer, data) + + return tconcat(buffer) +end + +local unpackers -- forward declaration + +local function unpack_cursor(c) + local s, i, j = c.s, c.i, c.j + + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + + local val = s:byte(i) + c.i = i + 1 + + return unpackers[val](c, val) +end + +m.unpack_cursor = unpack_cursor + +local function unpack_str(c, n) + local s, i, j = c.s, c.i, c.j + local e = i + n - 1 + + if e > j or n < 0 then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i + n - 1 + end + + c.i = i + n + + return s:sub(i, e) +end + +local function unpack_array(c, n) + local t = {} + + for i = 1, n do + t[i] = unpack_cursor(c) + end + + return t +end + +local function unpack_map(c, n) + local t = {} + + for i = 1, n do + local k = unpack_cursor(c) + local val = unpack_cursor(c) + + if k == nil or k ~= k then + k = m.sentinel + end + + if k ~= nil then + t[k] = val + end + end + + return t +end + +local function unpack_float(c) + local s, i, j = c.s, c.i, c.j + + if i + 3 > j then + c:underflow(i + 3) + s, i, j = c.s, c.i, c.j + end + + local b1, b2, b3, b4 = s:byte(i, i + 3) + local sign = b1 > 0x7F + local expo = (b1 % 0x80) * 0x2 + floor(b2 / 0x80) + local mant = ((b2 % 0x80) * 0x100 + b3) * 0x100 + b4 + + if sign then + sign = -1 + else + sign = 1 + end + + local n + + if mant == 0 and expo == 0 then + n = sign * 0.0 + elseif expo == 0xFF then + if mant == 0 then + n = sign * huge + else + n = 0.0 / 0.0 + end + else + n = sign * ldexp(1.0 + mant / 0x800000, expo - 0x7F) + end + + c.i = i + 4 + + return n +end + +local function unpack_double(c) + local s, i, j = c.s, c.i, c.j + + if i + 7 > j then + c:underflow(i + 7) + s, i, j = c.s, c.i, c.j + end + + local b1, b2, b3, b4, b5, b6, b7, b8 = s:byte(i, i + 7) + local sign = b1 > 0x7F + local expo = (b1 % 0x80) * 0x10 + floor(b2 / 0x10) + local mant = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 + + if sign then + sign = -1 + else + sign = 1 + end + + local n + + if mant == 0 and expo == 0 then + n = sign * 0.0 + elseif expo == 0x7FF then + if mant == 0 then + n = sign * huge + else + n = 0.0 / 0.0 + end + else + n = sign * ldexp(1.0 + mant / 4503599627370496.0, expo - 0x3FF) + end + + c.i = i + 8 + + return n +end + +local function unpack_uint8(c) + local s, i, j = c.s, c.i, c.j + + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + + local b1 = s:byte(i) + c.i = i + 1 + + return b1 +end + +local function unpack_uint16(c) + local s, i, j = c.s, c.i, c.j + + if i + 1 > j then + c:underflow(i + 1) + s, i, j = c.s, c.i, c.j + end + + local b1, b2 = s:byte(i, i + 1) + c.i = i + 2 + + return b1 * 0x100 + b2 +end + +local function unpack_uint32(c) + local s, i, j = c.s, c.i, c.j + + if i + 3 > j then + c:underflow(i + 3) + s, i, j = c.s, c.i, c.j + end + + local b1, b2, b3, b4 = s:byte(i, i + 3) + c.i = i + 4 + + return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4 +end + +local function unpack_uint64(c) + local s, i, j = c.s, c.i, c.j + + if i + 7 > j then + c:underflow(i + 7) + s, i, j = c.s, c.i, c.j + end + + local b1, b2, b3, b4, b5, b6, b7, b8 = s:byte(i, i + 7) + c.i = i + 8 + + return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 +end + +local function unpack_int8(c) + local s, i, j = c.s, c.i, c.j + + if i > j then + c:underflow(i) + s, i, j = c.s, c.i, c.j + end + + local b1 = s:byte(i) + c.i = i + 1 + + if b1 < 0x80 then + return b1 + else + return b1 - 0x100 + end +end + +local function unpack_int16(c) + local s, i, j = c.s, c.i, c.j + + if i + 1 > j then + c:underflow(i + 1) + s, i, j = c.s, c.i, c.j + end + + local b1, b2 = s:byte(i, i + 1) + c.i = i + 2 + + if b1 < 0x80 then + return b1 * 0x100 + b2 + else + return ((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) - 1 + end +end + +local function unpack_int32(c) + local s, i, j = c.s, c.i, c.j + + if i + 3 > j then + c:underflow(i + 3) + s, i, j = c.s, c.i, c.j + end + + local b1, b2, b3, b4 = s:byte(i, i + 3) + c.i = i + 4 + + if b1 < 0x80 then + return ((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4 + else + return ((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) - 1 + end +end + +local function unpack_int64(c) + local s, i, j = c.s, c.i, c.j + + if i + 7 > j then + c:underflow(i + 7) + s, i, j = c.s, c.i, c.j + end + + local b1, b2, b3, b4, b5, b6, b7, b8 = s:byte(i, i + 7) + c.i = i + 8 + + if b1 < 0x80 then + return ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 + else + return ((((((((b1 - 0xFF) * 0x100 + (b2 - 0xFF)) * 0x100 + (b3 - 0xFF)) * 0x100 + (b4 - 0xFF)) * 0x100 + (b5 - 0xFF)) * 0x100 + (b6 - 0xFF)) * 0x100 + (b7 - 0xFF)) * 0x100 + (b8 - 0xFF)) - 1 + end +end + +function m.build_ext(tag, data) + return nil +end + +local function unpack_ext(c, n, tag) + local s, i, j = c.s, c.i, c.j + local e = i + n - 1 + + if e > j or n < 0 then + c:underflow(e) + s, i, j = c.s, c.i, c.j + e = i + n - 1 + end + + c.i = i + n + + return m.build_ext(tag, s:sub(i, e)) +end + +local fn_1 = function(c, val) return val end +local fn_2 = function(c, val) return unpack_map(c, val % 0x10) end +local fn_3 = function(c, val) return unpack_array(c, val % 0x10) end +local fn_4 = function(c, val) return unpack_str(c, val % 0x20) end +local fn_5 = function(c, val) return val - 0x100 end + +unpackers = setmetatable({ + [0xC0] = function() return nil end, + [0xC2] = function() return false end, + [0xC3] = function() return true end, + [0xC4] = function(c) return unpack_str(c, unpack_uint8(c)) end, -- bin8 + [0xC5] = function(c) return unpack_str(c, unpack_uint16(c)) end, -- bin16 + [0xC6] = function(c) return unpack_str(c, unpack_uint32(c)) end, -- bin32 + [0xC7] = function(c) return unpack_ext(c, unpack_uint8(c), unpack_int8(c)) end, + [0xC8] = function(c) return unpack_ext(c, unpack_uint16(c), unpack_int8(c)) end, + [0xC9] = function(c) return unpack_ext(c, unpack_uint32(c), unpack_int8(c)) end, + [0xCA] = unpack_float, + [0xCB] = unpack_double, + [0xCC] = unpack_uint8, + [0xCD] = unpack_uint16, + [0xCE] = unpack_uint32, + [0xCF] = unpack_uint64, + [0xD0] = unpack_int8, + [0xD1] = unpack_int16, + [0xD2] = unpack_int32, + [0xD3] = unpack_int64, + [0xD4] = function(c) return unpack_ext(c, 1, unpack_int8(c)) end, + [0xD5] = function(c) return unpack_ext(c, 2, unpack_int8(c)) end, + [0xD6] = function(c) return unpack_ext(c, 4, unpack_int8(c)) end, + [0xD7] = function(c) return unpack_ext(c, 8, unpack_int8(c)) end, + [0xD8] = function(c) return unpack_ext(c, 16, unpack_int8(c)) end, + [0xD9] = function(c) return unpack_str(c, unpack_uint8(c)) end, + [0xDA] = function(c) return unpack_str(c, unpack_uint16(c)) end, + [0xDB] = function(c) return unpack_str(c, unpack_uint32(c)) end, + [0xDC] = function(c) return unpack_array(c, unpack_uint16(c)) end, + [0xDD] = function(c) return unpack_array(c, unpack_uint32(c)) end, + [0xDE] = function(c) return unpack_map(c, unpack_uint16(c)) end, + [0xDF] = function(c) return unpack_map(c, unpack_uint32(c)) end +}, { + __index = function(t, k) + if k < 0xC0 then + if k < 0x80 then + return fn_1 + elseif k < 0x90 then + return fn_2 + elseif k < 0xA0 then + return fn_3 + else + return fn_4 + end + elseif k > 0xDF then + return fn_5 + else + return function() + error("unpack '" .. format("%#x", k) .. "' is unimplemented") + end + end + end +}) + +local function cursor_string(str) + return { + s = str, + i = 1, + j = #str, + underflow = function() + error"missing bytes" + end + } +end + +local function cursor_loader(ld) + return { + s = '', + i = 1, + j = 0, + underflow = function(self, e) + self.s = self.s:sub(self.i) + e = e - self.i + 1 + self.i = 1 + self.j = 0 + + while e > self.j do + local chunk = ld() + + if not chunk then + error"missing bytes" + end + + self.s = self.s .. chunk + self.j = #self.s + end + end + } +end + +function m.unpack(s) + checktype("unpack", 1, s, "string") + local cursor = cursor_string(s) + local data = unpack_cursor(cursor) + + if cursor.i <= cursor.j then + error("extra bytes") + end + + return data +end + +function m.unpacker(src) + if type(src) == "string" then + local cursor = cursor_string(src) + + return function() + if cursor.i <= cursor.j then return cursor.i, unpack_cursor(cursor) end + end + elseif type(src) == "function" then + local cursor = cursor_loader(src) + + return function() + if cursor.i > cursor.j then + pcall(cursor.underflow, cursor, cursor.i) + end + + if cursor.i <= cursor.j then return true, unpack_cursor(cursor) end + end + else + argerror("unpacker", 1, "string or function expected, got " .. type(src)) + end +end + +set_string("string") +set_integer("unsigned") + +if SIZEOF_NUMBER == 4 then + maxinteger = 16777215 + mininteger = -maxinteger + m.small_lua = true + unpackers[0xCB] = nil -- double + unpackers[0xCF] = nil -- uint64 + unpackers[0xD3] = nil -- int64 + set_number("float") +else + maxinteger = 9007199254740991 + mininteger = -maxinteger + set_number("double") + + if SIZEOF_NUMBER > 8 then + m.long_double = true + end +end + +set_array("always_as_map") +m._VERSION = "0.5.2" +m._DESCRIPTION = "lua-MessagePack : a pure Lua implementation" +m._COPYRIGHT = "Copyright (c) 2012-2019 Francois Perrad" + +return m +-- +-- This library is licensed under the terms of the MIT/X11 license, +-- like Lua itself. +-- diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sh_async_netstream.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_async_netstream.lua new file mode 100644 index 0000000..767743d --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_async_netstream.lua @@ -0,0 +1,43 @@ +if SAM_LOADED then return end + +local netstream = sam.netstream +netstream.async = {} + +if SERVER then + local IsValid = IsValid + function netstream.async.Hook(name, fn, check) + netstream.Hook(name, function(ply, i, ...) + if not sam.isnumber(i) then return end + local res = function(...) + if IsValid(ply) then + netstream.Start(ply, name, i, ...) + end + end + fn(res, ply, ...) + end, check) + end +else + local count = 0 + local receivers = {} + + local hook_fn = function(i, ...) + local receiver = receivers[i] + if receiver[2] then + receiver[2]() + end + receiver[1]:resolve(...) + receivers[i] = nil + end + + function netstream.async.Start(name, func_to_call, ...) + local promise = sam.Promise.new() + count = count + 1 + receivers[count] = {promise, func_to_call} + netstream.Hook(name, hook_fn) + if func_to_call then + func_to_call() + end + netstream.Start(name, count, ...) + return promise + end +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sh_globals.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_globals.lua new file mode 100644 index 0000000..5c05994 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_globals.lua @@ -0,0 +1,72 @@ +if SAM_LOADED then return end + +local sam, netstream = sam, sam.netstream + +local globals + +if SERVER then + globals = {} + local order = {} + + local get_order_key = function(key) + for i = 1, #order do + if order[i] == key then + return i + end + end + end + + function sam.set_global(key, value, force) + if force or globals[key] ~= value then + globals[key] = value + + if value ~= nil then + if not get_order_key(key) then + table.insert(order, key) + end + else + local i = get_order_key(key) + if i then + table.remove(order, i) + end + end + + netstream.Start(nil, "SetGlobal", key, value) + end + end + + hook.Add("OnEntityCreated", "SAM.Globals", function(ent) + if ent:IsPlayer() and ent:IsValid() then + netstream.Start(ent, "SendGlobals", globals, order) + end + end) +end + +if CLIENT then + function sam.set_global(key, value) + if globals then + globals[key] = value + hook.Call("SAM.ChangedGlobalVar", nil, key, value) + end + end + netstream.Hook("SetGlobal", sam.set_global) + + netstream.Hook("SendGlobals", function(vars, order) + globals = vars + + for _, key in ipairs(order) do + hook.Call("SAM.ChangedGlobalVar", nil, key, vars[key]) + end + end) +end + +function sam.get_global(key, default) + if globals then + local value = globals[key] + if value ~= nil then + return value + end + end + + return default +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sh_mp.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_mp.lua new file mode 100644 index 0000000..4d53473 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_mp.lua @@ -0,0 +1,147 @@ +if SAM_LOADED then return end + +local sam = sam +local mp = sam.load_file("sam/libs/message_pack/sh_messagepack.lua") +local EXT_VECTOR = 1 +local EXT_ANGLE = 2 +local EXT_ENTITY = 3 +local EXT_PLAYER = 4 +local EXT_COLOR = 5 +local EXT_CONSOLE = 6 + +mp.packers["Entity"] = function(buffer, ent) + local buf = {} + mp.packers["number"](buf, ent:EntIndex()) + mp.packers["ext"](buffer, EXT_ENTITY, buf[1]) +end +mp.packers["Vehicle"] = mp.packers["Entity"] +mp.packers["Weapon"] = mp.packers["Entity"] +mp.packers["NPC"] = mp.packers["Entity"] +mp.packers["NextBot"] = mp.packers["Entity"] +mp.packers["PhysObj"] = mp.packers["Entity"] + +mp.packers["Player"] = function(buffer, ply) + local buf = {} + mp.packers["number"](buf, ply:UserID()) + mp.packers["ext"](buffer, EXT_PLAYER, buf[1]) +end + +local VECTOR = {} +mp.packers["Vector"] = function(buffer, vec) + VECTOR[1] = vec.x + VECTOR[2] = vec.y + VECTOR[3] = vec.z + + local buf = {} + mp.packers["_table"](buf, VECTOR) + mp.packers["ext"](buffer, EXT_VECTOR, table.concat(buf)) +end + +local ANGLE = {} +mp.packers["Angle"] = function(buffer, ang) + ANGLE[1] = ang.p + ANGLE[2] = ang.y + ANGLE[3] = ang.r + + local buf = {} + mp.packers["_table"](buf, ANGLE) + mp.packers["ext"](buffer, EXT_ANGLE, table.concat(buf)) +end + +local COLOR = {} +mp.packers["Color"] = function(buffer, col) + COLOR[1] = col.r + COLOR[2] = col.g + COLOR[3] = col.b + COLOR[4] = col.a + + local buf = {} + mp.packers["_table"](buf, COLOR) + mp.packers["ext"](buffer, EXT_COLOR, table.concat(buf)) +end + +mp.packers["console"] = function(buffer) + mp.packers["ext"](buffer, EXT_CONSOLE, "") +end + +local Entity = Entity +local _Player +local Color = Color +local Vector = Vector +local Angle = Angle +local unpackers = { + [EXT_ENTITY] = function(v) + return Entity(v) + end, + [EXT_PLAYER] = function(v) + return _Player(v) + end, + [EXT_VECTOR] = function(v) + return Vector(v[1], v[2], v[3]) + end, + [EXT_ANGLE] = function(v) + return Angle(v[1], v[2], v[3]) + end, + [EXT_COLOR] = function(v) + return Color(v[1], v[2], v[3], v[4]) + end, + [EXT_CONSOLE] = function(v) + return sam.console + end +} + +local Player = Player +if CLIENT then + local players = {} + + local Name = function(s) + return s.name + end + + _Player = function(id) + local ply = Player(id) + + if not IsValid(ply) then + local name = players[id] + if name then + return { + name = name, + Name = Name + } + end + end + + return ply + end + + hook.Add("OnEntityCreated", "SAM.GetPlayerName", function(ent) + if ent:IsPlayer() and ent:IsValid() then + ent.sam_userid = ent:UserID() -- userid is -1 in EntityRemoved????? + end + end) + + hook.Add("EntityRemoved", "SAM.GetPlayerName", function(ent) + if not ent:IsPlayer() then return end + + local id = ent.sam_userid + if not id then return end + + players[id] = ent:Name() + + timer.Simple(60, function() + if not IsValid(ent) then + players[id] = nil + end + end) + end) +else + _Player = Player +end + +mp.build_ext = function(tag, data) + local f = mp.unpacker(data) + local _, v = f() + return unpackers[tag](v) +end + +sam.mp = mp diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sh_netstream.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_netstream.lua new file mode 100644 index 0000000..43a617f --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_netstream.lua @@ -0,0 +1,167 @@ +if SAM_LOADED then return end + +--[[ + NetStream - 2.0.1 + https://github.com/alexgrist/NetStream/blob/master/netstream2.lua + + Alexander Grist-Hucker + http://www.revotech.org +]]-- + +--[[ + if SERVER then + netstream.Hook("Hi", function(ply, ...) -- Third argument is called to check if the player has permission to send the net message before decoding + print(...) + end, function(ply) + if not ply:IsAdmin() then + return false + end + end) + -- OR + netstream.Hook("Hi", function(ply, ...) + print(...) + end) + netstream.Start(Entity(1), "Hi", "a", 1, {}, true, false, nil, "!") -- First argument player or table of players or any other argument to send to all players + netstream.Start({Entity(1), Entity(2)}, "Hi", "a", 1, {}, true, false, nil, "!") + netstream.Start(nil, "Hi", "a", 1, {}, true, false, nil, "!") + end + if CLIENT then + netstream.Hook("Hi", function(...) + print(...) + end) + netstream.Start("Hi", "a", 1, {}, true, false, nil, "!") + end +]]-- + +-- Config + +local addonName = "SAM" +local mainTable = sam -- _G.netstream = netstream + +local mp = sam.mp + +-- + +local type = sam.type +local pcall = pcall +local unpack = unpack + +local net = net +local table_maxn = table.maxn + +local netStreamSend = addonName .. ".NetStreamDS.Sending" + +local netstream = {} +if istable(mainTable) then + mainTable.netstream = netstream +end + +local checks = {} +local receivers = {} + +local concat = table.concat +local pack = function(t, n) + local buffer = {} + mp.packers["array"](buffer, t, n) + return concat(buffer) +end + +if SERVER then + util.AddNetworkString(netStreamSend) + + -- local str_sub = string.sub + -- local function Split(str, buffer, result) + -- if not result then + -- result = {} + -- end + + -- if not buffer then + -- buffer = 32768 + -- end + + -- local len = #str + -- if len >= buffer then + -- result[#result + 1] = str_sub(str, 1, buffer - 1) + -- str = str_sub(str, buffer, len) + -- else + -- result[#result + 1] = str + -- return result + -- end + + -- return Split(str, buffer, result) + -- end + + local player_GetAll = player.GetAll + function netstream.Start(ply, name, ...) + local ply_type = type(ply) + if ply_type ~= "Player" and ply_type ~= "table" then + ply = player_GetAll() + end + + local encoded_data = pack({...}, select("#", ...)) + local length = #encoded_data + + net.Start(netStreamSend) + net.WriteString(name) + net.WriteUInt(length, 17) + net.WriteData(encoded_data, length) + net.Send(ply) + end + + function netstream.Hook(name, callback, check) + receivers[name] = callback + if type(check) == "function" then + checks[name] = check + end + end + + net.Receive(netStreamSend, function(_, ply) + local name = net.ReadString() + + local callback = receivers[name] + if not callback then return end + + local length = net.ReadUInt(17) + + local check = checks[name] + if check and check(ply, length) == false then return end + + local data = net.ReadData(length) + + local status + status, data = pcall(mp.unpack, data) + if not status or not sam.istable(data) then return end + + callback(ply, unpack(data, 1, table_maxn(data))) + end) +else + checks = nil + + function netstream.Start(name, ...) + local encoded_data = pack({...}, select("#", ...)) + local length = #encoded_data + + net.Start(netStreamSend) + net.WriteString(name) + net.WriteUInt(length, 17) + net.WriteData(encoded_data, length) + net.SendToServer() + end + + function netstream.Hook(name, callback) + receivers[name] = callback + end + + net.Receive(netStreamSend, function() + local callback = receivers[net.ReadString()] + if not callback then return end + + local length = net.ReadUInt(17) + local data = net.ReadData(length) + + data = mp.unpack(data) + callback(unpack(data, 1, table_maxn(data))) + end) +end + +return netstream diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sh_pon.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_pon.lua new file mode 100644 index 0000000..3a2b10e --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_pon.lua @@ -0,0 +1,448 @@ +if SAM_LOADED then return end + +--[[ + +DEVELOPMENTAL VERSION; + +VERSION 1.2.2 +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 pon, 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, Weapon +V 1.2.0 + - Added custom handling for k,v tables without any array component. +V 1.2.1 + - fixed deserialization bug. + +THANKS TO... + - VERCAS for the inspiration. +]] +local pon = {} +sam.pon = pon + +do + local type = sam.type + local IsColor = IsColor + local tonumber = tonumber + local format = string.format + local encode = {} + 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 + + local first = next(tbl, nil) + local predictedNumeric = 1 + + -- starts with a numeric dealio + if first == 1 then + output[#output + 1] = '{' + + for k, v in next, tbl do + if k == predictedNumeric then + predictedNumeric = predictedNumeric + 1 + local tv = type(v) + + 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 + elseif IsColor(v) then + self.Color(self, v, output, cache) + else + self[tv](self, v, output, cache) + end + else + break + end + end + + predictedNumeric = predictedNumeric - 1 + else + predictedNumeric = nil + end + + if predictedNumeric == nil then + output[#output + 1] = '[' -- no array component + else + output[#output + 1] = '~' -- array component came first so shit needs to happen + end + + for k, v in next, tbl, predictedNumeric do + local tk, tv = type(k), type(v) + if not self[tk] or not self[tv] then continue end + + -- WRITE 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 + elseif IsColor(v) then + self.Color(self, v, output, cache) + else + self[tk](self, k, output, cache) + end + + -- WRITE 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 + elseif IsColor(v) then + self.Color(self, v, output, cache) + else + self[tv](self, v, output, cache) + 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['PhysObj'] = encode['Entity'] + + encode['Color'] = function(self, val, output) + output[#output + 1] = ('C' .. val.r .. ',' .. val.g .. ',' .. val.b) .. (',' .. val.a .. ';') + end + + encode['console'] = function(self, val, output) + output[#output + 1] = 's' + end + + encode['nil'] = function(self, val, output) + 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 concat = table.concat + + function pon.encode(tbl) + local output = {nil, nil, nil, nil, nil, nil, nil, nil} + cacheSize = 0 + encode['table'](encode, tbl, output, {}) + + return concat(output) + 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) + 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) + 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) + 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) + 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) + 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) + 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 + + -- COLOR + decode['C'] = function(self, index, str) + local finish = find(str, ';', index, true) + local colStr = sub(str, index, finish - 1) + index = finish + 1 -- update the index. + local segs = Explode(',', colStr, false) + + return index, Color(segs[1], segs[2], segs[3], segs[4]) + end + + -- PLAYER + decode['P'] = function(self, index, str) + 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) return index + 1, nil end + -- SAM CONSOLE + decode['s'] = function(self, index) return index, sam.console end + + function pon.decode(data) + local _, res = decode[sub(data, 1, 1)](decode, 2, data, {}) + + return res + end +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sh_promises.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_promises.lua new file mode 100644 index 0000000..47ab05a --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_promises.lua @@ -0,0 +1,66 @@ +if SAM_LOADED then return end +-- not real promises, just really simple one + +local isfunction = sam and sam.isfunction or isfunction + +local null = {} + +local Promise = {} + +local PromiseMethods = {} +local Promise_meta = {__index = PromiseMethods} + +function Promise.new() + return setmetatable({ + value = null, + null = null + }, Promise_meta) +end + +function Promise.IsPromise(v) + return getmetatable(v) == Promise_meta +end + +function PromiseMethods:resolve(v) + if self.value ~= null then return end + if self.done_callback then + self.done_callback(v) + else + self.value = v + self.callback = 0 + end +end + +function PromiseMethods:reject(v) + if self.value ~= null then return end + if self.catch_callback then + self.catch_callback(v) + else + self.value = v + self.callback = 1 + end +end + +function PromiseMethods:done(func) + if isfunction(func) then + if self.value ~= null and self.callback == 0 then + func(self.value) + else + self.done_callback = func + end + end + return self +end + +function PromiseMethods:catch(func) + if isfunction(func) then + if self.value ~= null and self.callback == 1 then + func(self.value) + else + self.catch_callback = func + end + end + return self +end + +return Promise diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sh_types.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_types.lua new file mode 100644 index 0000000..e3d05b0 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sh_types.lua @@ -0,0 +1,84 @@ +if SAM_LOADED then return end + +-- https://gist.github.com/CapsAdmin/0d9c1e77d0fc22d910e182bfeb9812e5 +local getmetatable = getmetatable + +do + local types = { + ["string"] = "", + ["boolean"] = true, + ["number"] = 0, + ["function"] = function() end, + ["thread"] = coroutine.create(getmetatable), + ["Color"] = Color(0, 0, 0), + } + + for k, v in pairs(types) do + if not getmetatable(v) then + debug.setmetatable(v, {MetaName = k}) + else + getmetatable(v).MetaName = k + end + end +end + +function sam.type(value) + if value == nil then + return "nil" + end + local meta = getmetatable(value) + if meta then + meta = meta.MetaName + if meta then + return meta + end + end + return "table" +end + +do + local function add(name) + local new_name = name + if name == "bool" then + new_name = "boolean" + end + sam["is" .. name:lower()] = function(value) + local meta = getmetatable(value) + if meta and meta.MetaName == new_name then + return true + else + return false + end + end + end + + add("string") + add("number") + add("bool") + add("function") + + add("Angle") + add("Vector") + add("Panel") + add("Matrix") +end + +function sam.isentity(value) + local meta = getmetatable(value) + if meta then + if meta.MetaName == "Entity" then + return true + end + meta = meta.MetaBaseClass + if meta then + return meta.MetaName == "Entity" + end + end + return false +end +sam.IsEntity = sam.isentity + +local type = sam.type +function sam.istable(value) + return type(value) == "table" +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sql/databases/mysql.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sql/databases/mysql.lua new file mode 100644 index 0000000..6dba042 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sql/databases/mysql.lua @@ -0,0 +1,181 @@ +local _SQL = sam.SQL +local _error = _SQL.Error +local traceback = debug.traceback + +local _mysqloo, database = nil, nil + +local SQL = {} + +function SQL.Connect(callback, failed_callback, config) + if database then + local status = database:status() + if status == _mysqloo.DATABASE_CONNECTING or status == _mysqloo.DATABASE_CONNECTED then + return true + end + end + + _SQL.SetConnected(false) + + require("mysqloo") + + if not mysqloo then + _error("mysqloo module doesn't exist, get it from https://github.com/FredyH/MySQLOO") + return false + end + + _mysqloo = mysqloo + + database = _mysqloo.connect( + config.Host, + config.Username, + config.Password, + config.Database, + config.Port + ) + + function database.onConnected() + callback() + end + + function database.onConnectionFailed(_, error_text) + failed_callback(error_text) + end + + database:connect() + + return true +end + +-- +-- +-- +local transaction + +local add_transaction = function(query) + transaction:addQuery(database:query(query)) +end + +function SQL.Begin() + transaction = database:createTransaction() + return add_transaction +end + +function SQL.Commit(callback) + transaction.SQL_traceback = traceback("", 2) + + transaction.onSuccess = callback + transaction.onError = transaction_onError + + transaction:start() + + transaction = nil +end +-- +-- +-- + +-- +-- +-- +local on_query_success = function(query, data) + if query.SQL_first_row then + data = data[1] + end + query.SQL_callback(data, query.SQL_callback_obj) +end + +local on_query_fail = function(query, error_text) + local status = database:status() + + -- https://github.com/Kamshak/LibK/blob/master/lua/libk/server/sv_libk_database.lua#L129 + if status == _mysqloo.DATABASE_NOT_CONNECTED or status == _mysqloo.DATABASE_CONNECTING or error_text:find("Lost connection to MySQL server during query", 1, true) then + _SQL.SetConnected(false) + SQL.Query(query.SQL_query_string, query.SQL_callback, query.SQL_first_row, query.SQL_callback_obj) + else + -- 68e41a8b530e520644b0bfbfe1633cf8beb3fa37ef8e7f796881e9f2103af465 + _error("Query error: " .. error_text, query.SQL_traceback) + end +end + +function SQL.Query(query, callback, first_row, callback_obj) + local status = database:status() + if status == _mysqloo.DATABASE_NOT_CONNECTED or status == _mysqloo.DATABASE_INTERNAL_ERROR then + _SQL.Connect() + database:wait() + end + + local query_string = query + query = database:query(query) + + query.SQL_query_string = query_string + + if callback then + query.onSuccess = on_query_success + query.SQL_callback = callback + query.SQL_first_row = first_row + query.SQL_callback_obj = callback_obj + end + + query.SQL_traceback = traceback("", 2) + query.onError = on_query_fail + + query:start() + + return query +end + +-- local prepared_set_values = function(prepared_query, values) +-- for i = 1, #values do +-- local v = values[i] +-- local value_type = type(v) +-- if value_type == "string" then +-- prepared_query:setString(i, v) +-- elseif value_type == "number" then +-- prepared_query:setNumber(i, v) +-- else +-- error( +-- string.format( +-- "%s invalid type '%s' was passed to escape '%s'", +-- "(" .. SQL.GetAddonName() .. " | MySQL)", +-- value_type, +-- v +-- ) +-- ) +-- end +-- end +-- end + +-- function SQL.Prepare(query, callback, first_row, callback_obj) +-- local prepared_query = database:prepare(query) +-- prepared_query.SetValues = prepared_set_values + +-- if callback then +-- prepared_query.onSuccess = on_query_success +-- prepared_query.SQL_callback = callback +-- prepared_query.SQL_first_row = first_row +-- prepared_query.SQL_callback_obj = callback_obj +-- end + +-- prepared_query.SQL_traceback = traceback("", 2) +-- prepared_query.onError = on_query_fail + +-- return prepared_query +-- end + +-- +-- +-- + +function SQL.EscapeString(value, no_quotes) + if no_quotes then + return database:escape(value) + else + return "'" .. database:escape(value) .. "'" + end +end + +function SQL.TableExistsQuery(name) + return "SHOW TABLES LIKE " .. SQL.EscapeString(name) +end + +return SQL diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sql/databases/sqlite.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sql/databases/sqlite.lua new file mode 100644 index 0000000..3eec23e --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sql/databases/sqlite.lua @@ -0,0 +1,138 @@ +local _SQL = sam.SQL +local _error = sam.SQL.Error +local sql_query = sql.Query + +local SQL = {} + +function SQL.Connect(callback) + timer.Simple(0, callback) + return true +end + +-- +-- +-- +local transactions + +local add_transaction = function(query) + table.insert(transactions, query) +end + +function SQL.Begin() + transactions = {} + sql_query("BEGIN TRANSACTION") + return add_transaction +end + +function SQL.Commit(callback) + for i = 1, #transactions do + if sql_query(transactions[i]) == false then + sql_query("ROLLBACK TRANSACTION") + transactions = nil + _error("Transaction error: " .. sql.LastError()) + return + end + end + + transactions = nil + + sql_query("COMMIT TRANSACTION") + + if callback then + callback() + end +end +-- +-- +-- + +-- +-- +-- +local query_obj = { + wait = function() end -- mysqloo has query:wait() +} + +function SQL.Query(query, callback, first_row, callback_obj) + local data = sql_query(query) + if data == false then + _error("Query error: " .. sql.LastError()) + elseif callback then + if data == nil then + if not first_row then + data = {} + end + elseif first_row then + data = data[1] + end + + callback(data, callback_obj) + end + + return query_obj +end + +-- local concat = table.concat +-- local prepared_set_values = function(prepared_query, values) +-- for i = 1, prepared_query.args_n do +-- prepared_query[prepared_query[-i]] = _SQL.Escape(values[i]) +-- end +-- return concat(prepared_query, "", 1, prepared_query.n) +-- end + +-- local prepared_start = function() +-- end + +-- local sub, find = string.sub, string.find +-- function SQL.Prepare(query, callback, first_row, callback_obj) +-- local prepared_query = {} +-- prepared_query.wait = query_obj.wait +-- prepared_query.Start = prepared_start +-- prepared_query.SetValues = prepared_set_values + +-- local count, args_n = 0, 0 +-- local pos, start, _end = 0, nil, 0 +-- while true do +-- start, _end = find(query, "?", _end + 1, true) + +-- if not start then +-- break +-- end + +-- if pos ~= start then +-- count = count + 1; prepared_query[count] = sub(query, pos, start - 1) +-- end + +-- count = count + 1; prepared_query[count] = "NULL" +-- args_n = args_n - 1; prepared_query[args_n] = count + +-- pos = _end + 1 +-- end + +-- if pos <= #query then +-- count = count + 1; prepared_query[count] = sub(query, pos) +-- end + +-- prepared_query.n = count +-- prepared_query.args_n = abs(args_n) + +-- return prepared_query +-- end +-- +-- +-- + +local SQLStr = SQLStr +function SQL.EscapeString(value, no_quotes) + return SQLStr(value, no_quotes) +end + +function SQL.TableExistsQuery(name) + return "SELECT `name` FROM `sqlite_master` WHERE `name` = " .. SQL.EscapeString(name) .. " AND `type` = 'table'" +end + +-- +-- +-- + +return SQL diff --git a/garrysmod/addons/module_adminmod/lua/sam/libs/sql/sv_init.lua b/garrysmod/addons/module_adminmod/lua/sam/libs/sql/sv_init.lua new file mode 100644 index 0000000..dce0760 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/libs/sql/sv_init.lua @@ -0,0 +1,142 @@ +if SAM_LOADED then return end + +local format, isstring, istable, tonumber = string.format, sam.isstring, sam.istable, tonumber + +local config = {} + +local SQL, _SQL = {}, nil + +function SQL.Print(...) + MsgC( + Color(255, 255, 255), "(", + Color(244, 67, 54), SQL.GetAddonName(), + Color(255, 255, 255), " | ", + Color(244, 67, 54), SQL.IsMySQL() and "MySQL" or "SQLite", + Color(255, 255, 255), ") ", + ... + ) + Msg("\n") +end + +function SQL.Error(err, trace) + SQL.Print(err, trace or debug.traceback("", 2)) +end + +function SQL.Connect() + return _SQL.Connect(SQL.OnConnected, SQL.OnConnectionFailed, config) +end + +do + local in_transaction, old_query = false, nil + + function SQL.Begin() + if in_transaction then + return SQL.Error("transaction on going!") + end + in_transaction = true + + SQL.Query, old_query = _SQL.Begin(), SQL.Query + end + + function SQL.Commit(callback) + if not in_transaction then return end + + in_transaction = false + SQL.Query, old_query = old_query, nil + + return _SQL.Commit(callback) + end +end + +local gsub = string.gsub +function SQL.FQuery(query, args, callback, first_row, callback_obj) + query = gsub(query, "{(%d)(f?)}", function(i, no_escape) + return SQL.Escape(args[tonumber(i)], no_escape ~= "") + end) + + return SQL.Query(query, callback, first_row, callback_obj) +end + +do + local table_exists = function(data, callback) + callback(data and true or false) + end + + function SQL.TableExists(name, callback) + return SQL.Query(_SQL.TableExistsQuery(name), table_exists, true, callback) + end +end + +function SQL.IsMySQL() + return config.MySQL == true +end + +do + local connected = false + function SQL.IsConnected() + return connected + end + function SQL.SetConnected(is_connected) + connected = is_connected + end +end + +function SQL.Escape(value, no_quotes) + local value_type = type(value) + if value_type == "string" then + return _SQL.EscapeString(value, no_quotes) + elseif value_type == "number" then + return value + else + error( + format( + "%s invalid type '%s' was passed to escape '%s'", + "(" .. SQL.GetAddonName() .. " | " .. (SQL.IsMySQL() and "MySQL" or "SQLite") .. ")", + value_type, + value + ) + ) + end +end + +function SQL.OnConnected() + SQL.SetConnected(true) + hook.Call(SQL.GetAddonName() .. ".DatabaseConnected") +end + +function SQL.OnConnectionFailed(error_text) + SQL.Error("Failed to connect to the server: " .. error_text) + hook.Call(SQL.GetAddonName() .. ".DatabaseConnectionFailed", nil, error_text) +end + +function SQL.SetConfig(new_config) + if not istable(new_config) then return end + if new_config.MySQL == true then + for _, v in ipairs({"Host", "Username", "Password", "Database"}) do + if not isstring(new_config[v]) then + return SQL.Error( + format("config value for '%s' is invalid '%s' needs to be a string!", v, config[v]) + ) + end + end + new_config.Port = tonumber(new_config.Port) or 3306 + _SQL = sam.load_file("sam/libs/sql/databases/mysql.lua", "sv_") + else + _SQL = sam.load_file("sam/libs/sql/databases/sqlite.lua", "sv_") + end + + SQL.Query = _SQL.Query + config = new_config +end + +do + local addon_name = "NO NAME" + function SQL.SetAddonName(name) + addon_name = name + end + function SQL.GetAddonName() + return addon_name + end +end + +sam.SQL = SQL diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/cl_init.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/cl_init.lua new file mode 100644 index 0000000..4f29910 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/cl_init.lua @@ -0,0 +1,255 @@ +if SAM_LOADED then return end + +local vgui = vgui +local draw = draw + +local sam = sam +local sui = sui +local TDLib = sui.TDLib + +local config = sam.config + +do + local funcs = { + ["SAM.ComboBox"] = { + event = "OnSelect", + function(s, _, value) + config.set(s.config_key, value) + end + }, + ["SAM.TextEntry"] = { + event = "OnEnter", + function(s) + local v = s:GetText() + if s:GetNumeric() then + v = tonumber(v) + end + config.set(s.config_key, v) + end + }, + ["SAM.ToggleButton"] = { + event = "OnChange", + function(s, v) + config.set(s.config_key, v) + end + } + } + + sam.SUI = sam.SUI or sui.new("SAM", true, { + SetConfig = function(s, key, default) + s.config_key = key + + local i = config.hook({key}, function(value, old) + local v = config.get(key, default) + s:SetValue(v) + end) + + local t = funcs[s:GetName()] + s[t.event] = t[1] + + s:On("OnRemove", function() + config.remove_hook(i) + end) + end + }) +end + +local SUI = sam.SUI +local GetColor = SUI.GetColor + +sam.menu = {} + +local tabs = {} +function sam.menu.add_tab(icon, func, check, pos) + local tab = { + icon = icon, + func = func, + check = check, + pos = pos + } + for k, v in ipairs(tabs) do + if v.icon == icon then + tabs[k] = tab + return + end + end + table.insert(tabs, tab) +end + +function sam.menu.remove_tab(name) + for k, v in ipairs(tabs) do + if v.name == name then + table.remove(tabs, k) + break + end + end +end + +SAM_TAB_TITLE_FONT = SUI.CreateFont("TabTitle", "Roboto Bold", 22) +SAM_TAB_DESC_FONT = SUI.CreateFont("TabDesc", "Roboto Medium", 15) + +local MENU_LOADING = SUI.CreateFont("MenuLoading", "Roboto", 30) + +SUI.AddToTheme("Dark", { + frame = "#181818", + + scroll_panel = "#181818", + + menu_tabs_title = "#ffffff", + + --=-- + player_list_titles = "#f2f1ef", + + player_list_names = "#eeeeee", + player_list_names_2 = "#ff6347", + player_list_data = "#e8e8e8", + + player_list_rank = "#41b9ff", + player_list_console = "#00c853", + player_list_rank_text = "#2c3e50", + + player_list_steamid = "#a4a4a4", + --=-- + + --=-- + actions_button = Color(0, 0, 0, 0), + actions_button_hover = Color(200, 200, 200, 60), + + actions_button_icon = "#aaaaaa", + actions_button_icon_hover = "#ffffff", + --=-- + + --=-- + page_switch_bg = "#222222", + --=-- +}) + +SUI.SetTheme("Dark") + +function SUI.panels.Frame:Paint(w, h) + if GetColor("frame_blur") then + TDLib.BlurPanel(self) + end + + draw.RoundedBox(8, 0, 0, w, h, GetColor("frame")) +end + +function SUI.panels.Frame:HeaderPaint(w, h) + draw.RoundedBoxEx(8, 0, 0, w, h, GetColor("header"), true, true, false, false) + draw.RoundedBox(0, 0, h - 1, w, 1, GetColor("line")) +end + +do + function sam.menu.add_loading_panel(parent) + local is_loading = false + + local loading_panel = parent:Add("Panel") + loading_panel:SetVisible(false) + loading_panel:SetZPos(999999) + loading_panel:SetMouseInputEnabled(false) + + function loading_panel:Paint(w, h) + draw.RoundedBox(3, 0, 0, w, h, Color(50, 50, 50, 200)) + draw.SimpleText(string.rep(".", (CurTime() * 3) % 3), MENU_LOADING, w/2, h/2, Color(200, 200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + parent:SUI_TDLib() + parent:On("PerformLayout", function(s, w, h) + loading_panel:SetSize(w, h) + end) + + local first = true + local toggle_loading = function(bool) + if not IsValid(loading_panel) then return end + + is_loading = bool or not is_loading + if is_loading and not first then + loading_panel:SetVisible(is_loading and true or false) + loading_panel:SetMouseInputEnabled(is_loading) + else + timer.Simple(0.2, function() + if not IsValid(loading_panel) then return end + loading_panel:SetVisible(is_loading and true or false) + loading_panel:SetMouseInputEnabled(is_loading) + end) + end + + first = false + end + + return toggle_loading, function() + return is_loading + end + end +end + +local sam_menu +function sam.menu.open_menu() + if IsValid(sam_menu) then + return sam_menu:IsVisible() and sam_menu:Hide() or sam_menu:Show() + -- sam_menu:Remove() + end + + sam_menu = vgui.Create("SAM.Frame") + sam_menu:Center() + sam_menu:MakePopup() + sam_menu:SetTitle("SAM") + + sam_menu:AddAnimations(800, 600) + + sam_menu.close.DoClick = function() + sam_menu:Hide() + end + + local sheet = sam_menu:Add("SAM.ColumnSheet") + sheet:Dock(FILL) + sheet:InvalidateParent(true) + sheet:InvalidateLayout(true) + sheet.Paint = nil + + local tab_scroller = sheet.tab_scroller + tab_scroller:DockMargin(0, 1, 0, 1) + + function tab_scroller:Paint(w, h) + draw.RoundedBoxEx(8, 0, 0, w, h, GetColor("column_sheet_bar"), false, false, true, false) + end + + local sheets = {} + for _, v in SortedPairsByMemberValue(tabs, "pos") do + sheets[v.icon] = sheet:AddSheet(v.icon, v.func) + end + + tab_scroller = tab_scroller:GetCanvas() + sam_menu:On("Think", function() + for _, v in ipairs(tabs) do + local tab = sheets[v.icon] + if v.check and not v.check() then + if tab:IsVisible() then + tab:SetVisible(false) + if sheet:GetActiveTab() == tab then + sheet:SetActiveTab(sheet.tabs[1]) + end + tab_scroller:InvalidateLayout() + end + elseif not tab:IsVisible() then + tab:SetVisible(true) + tab_scroller:InvalidateLayout() + end + end + end) +end + +function sam.menu.get() + return sam_menu +end + +hook.Add("GUIMouseReleased", "SAM.CloseMenu", function(mouse_code) + local panel = vgui.GetHoveredPanel() + if mouse_code == MOUSE_LEFT and panel == vgui.GetWorldPanel() and IsValid(sam_menu) and sam_menu:HasHierarchicalFocus() then + sam_menu:Hide() + end +end) + +for _, f in ipairs(file.Find("sam/menu/tabs/*.lua", "LUA")) do + sam.load_file("sam/menu/tabs/" .. f, "sh") +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/sh_init.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/sh_init.lua new file mode 100644 index 0000000..f7d0546 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/sh_init.lua @@ -0,0 +1,24 @@ +if SAM_LOADED then return end + +require("sui") + +sam.command.new("menu") + :Help("Open admin mod menu") + :MenuHide() + :DisableNotify() + :OnExecute(function(ply) + sam.netstream.Start(ply, "OpenMenu") + end) +:End() + +if CLIENT then + sam.netstream.Hook("OpenMenu", function() + sam.menu.open_menu() + end) +end + +if SERVER then + for _, f in ipairs(file.Find("sam/menu/tabs/*.lua", "LUA")) do + sam.load_file("sam/menu/tabs/" .. f) + end +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/bans.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/bans.lua new file mode 100644 index 0000000..4344654 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/bans.lua @@ -0,0 +1,409 @@ +if SAM_LOADED then return end + +local sam = sam +local SQL = sam.SQL +local SUI = sam.SUI +local netstream = sam.netstream + +sam.permissions.add("manage_bans", nil, "superadmin") + +local get_pages_count = function(bans_count) + bans_count = bans_count / 35 + local i2 = math.floor(bans_count) + return bans_count ~= i2 and i2 + 1 or bans_count +end + +if SERVER then + local check = function(ply) + return ply:HasPermission("manage_bans") and ply:sam_check_cooldown("MenuManageBans", 0.1) + end + + local limit = 35 + + local get_page_count = function(res, callback, page, order_by, keyword) + local current_time = os.time() + local query = [[ + SELECT + COUNT(`steamid`) AS `count` + FROM + `sam_bans` + WHERE + (`unban_date` >= %d OR `unban_date` = 0)]] + + query = query:format(current_time) + + if keyword then + query = query .. " AND `steamid` LIKE " .. SQL.Escape("%" .. keyword .. "%") + end + + SQL.Query(query, callback, true, {res, page, order_by, keyword, current_time}) + end + + local resolve_promise = function(data, arguments) + local res = arguments[1] + arguments[1] = data + res(arguments) + end + + local get_bans = function(count_data, arguments) + local res, page, order_by, keyword, current_time = unpack(arguments) + local count = count_data.count + + local current_page + if page < 1 then + page, current_page = 1, 1 + end + + local pages_count = get_pages_count(count) + if page > pages_count then + page, current_page = pages_count, pages_count + end + + local query = [[ + SELECT + `sam_bans`.*, + IFNULL(`p1`.`name`, '') AS `name`, + IFNULL(`p2`.`name`, '') AS `admin_name` + FROM + `sam_bans` + LEFT OUTER JOIN + `sam_players` AS `p1` + ON + `sam_bans`.`steamid` = `p1`.`steamid` + LEFT OUTER JOIN + `sam_players` AS `p2` + ON + `sam_bans`.`admin` = `p2`.`steamid` + WHERE + (`sam_bans`.`unban_date` >= %d OR `sam_bans`.`unban_date` = 0)]] + + query = query:format(current_time) + + if keyword then + query = query .. " AND `sam_bans`.`steamid` LIKE " .. SQL.Escape("%" .. keyword .. "%") + end + + local offset = math.abs(limit * (page - 1)) + query = query .. ([[ + ORDER BY + `sam_bans`.`id` %s + LIMIT + %d OFFSET %d]]):format(order_by, limit, offset) + + SQL.Query(query, resolve_promise, nil, {res, count, current_page}) + end + + netstream.async.Hook("SAM.GetBans", function(res, ply, page, order_by, keyword) + if not isnumber(page) then return end + if order_by ~= "ASC" and order_by ~= "DESC" then return end + if keyword ~= nil and not sam.isstring(keyword) then return end + + get_page_count(res, get_bans, page, order_by, keyword) + end, check) + + return +end + +local GetColor = SUI.GetColor +local Line = sui.TDLib.LibClasses.Line + +local COLUMN_FONT = SUI.CreateFont("Column", "Roboto", 18) +local LINE_FONT = SUI.CreateFont("Line", "Roboto", 16) +local NEXT_FONT = SUI.CreateFont("NextButton", "Roboto", 18) + +sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/ban-user.png", function(column_sheet) + local refresh, pages + local current_page, current_order, keyword = nil, "DESC", nil + + local bans_body = column_sheet:Add("Panel") + bans_body:Dock(FILL) + bans_body:DockMargin(0, 1, 0, 0) + bans_body:DockPadding(10, 10, 10, 10) + + local toggle_loading, is_loading = sam.menu.add_loading_panel(bans_body) + + local title = bans_body:Add("SAM.Label") + title:Dock(TOP) + title:SetFont(SAM_TAB_TITLE_FONT) + title:SetText("Bans") + title:SetTextColor(GetColor("menu_tabs_title")) + title:SizeToContents() + + local total = bans_body:Add("SAM.Label") + total:Dock(TOP) + total:DockMargin(0, 6, 0, 0) + total:SetFont(SAM_TAB_DESC_FONT) + total:SetText("60 total bans") + total:SetTextColor(GetColor("menu_tabs_title")) + total:SetPos(10, SUI.Scale(40)) + total:SizeToContents() + + do + local container = bans_body:Add("SAM.Panel") + container:Dock(TOP) + container:DockMargin(0, 6, 10, 0) + container:SetTall(30) + + local sort_order = container:Add("SAM.ComboBox") + sort_order:Dock(RIGHT) + sort_order:SetWide(96) + sort_order:SetValue("Desc") + sort_order:AddChoice("Desc") + sort_order:AddChoice("Asc") + + function sort_order:OnSelect(_, value) + value = value:upper() + if current_order ~= value then + current_order = value + refresh() + end + end + + local search_entry = container:Add("SAM.TextEntry") + search_entry:Dock(LEFT) + search_entry:SetNoBar(true) + search_entry:SetPlaceholder("Search...") + search_entry:SetRadius(4) + search_entry:SetWide(220) + + function search_entry:OnEnter() + local value = self:GetValue() + if keyword ~= value then + keyword = value ~= "" and value or nil + refresh() + end + end + end + + Line(bans_body, nil, -5, 15, -5, 0) + + do + local columns = bans_body:Add("Panel") + columns:Dock(TOP) + columns:DockMargin(0, 10, 0, 0) + + local info = columns:Add("SAM.Label") + info:Dock(LEFT) + info:DockMargin(4, 0, 0, 0) + info:SetFont(COLUMN_FONT) + info:SetText("Player") + info:SetTextColor(GetColor("player_list_titles")) + info:SetWide(SUI.Scale(280) + SUI.Scale(34)) + info:SizeToContentsY(3) + + local time_left = columns:Add("SAM.Label") + time_left:Dock(LEFT) + time_left:DockMargin(-4, 0, 0, 0) + time_left:SetFont(COLUMN_FONT) + time_left:SetText("Time Left") + time_left:SetTextColor(GetColor("player_list_titles")) + time_left:SetWide(SUI.Scale(180)) + time_left:SizeToContentsY(3) + + local reason = columns:Add("SAM.Label") + reason:Dock(LEFT) + reason:DockMargin(-4, 0, 0, 0) + reason:SetFont(COLUMN_FONT) + reason:SetText("Reason") + reason:SetTextColor(GetColor("player_list_titles")) + reason:SetWide(SUI.Scale(280)) + reason:SizeToContentsY(3) + + columns:SizeToChildren(false, true) + end + + local body = bans_body:Add("SAM.ScrollPanel") + body:Dock(FILL) + body:DockMargin(0, 10, 0, 0) + body:SetVBarPadding(6) + + local set_data = function(data) + body:GetCanvas():Clear() + body.VBar.Scroll = 0 + + local bans, bans_count, current_page_2 = unpack(data) + total:SetText(bans_count .. " total bans") + + pages = get_pages_count(bans_count) + current_page.i = pages == 0 and 0 or current_page_2 or current_page.i + current_page:SetText(current_page.i .. "/" .. pages) + + body:Line() + + for k, v in ipairs(bans) do + local line = body:Add("SAM.PlayerLine") + line:DockMargin(0, 0, 0, 10) + + local name = v.name ~= "" and v.name or nil + local admin_name = v.admin_name ~= "" and v.admin_name or nil + line:SetInfo({ + steamid = v.steamid, + name = name, + rank = admin_name or (v.admin == "Console" and "Console"), + rank_bg = not admin_name and GetColor("player_list_console") + }) + + local unban_date = tonumber(v.unban_date) + local time_left = line:Add("SAM.Label") + time_left:Dock(LEFT) + time_left:DockMargin(-3, 0, 0, 0) + time_left:SetFont(LINE_FONT) + time_left:SetText(unban_date == 0 and "Never" or sam.reverse_parse_length((unban_date - os.time()) / 60)) + time_left:SetTextColor(GetColor("player_list_data")) + time_left:SetContentAlignment(4) + time_left:SetWide(SUI.Scale(180)) + + local reason = line:Add("SAM.Label") + reason:Dock(LEFT) + reason:DockMargin(4, 0, 0, 0) + reason:SetFont(LINE_FONT) + reason:SetText(v.reason) + reason:SetTextColor(GetColor("player_list_data")) + reason:SetContentAlignment(4) + reason:SetWrap(true) + reason:SetWide(SUI.Scale(200)) + + local old_tall = line.size + function reason:PerformLayout() + local _, h = self:GetTextSize() + if old_tall < h then + line:SetTall(h) + end + end + + local but = line:Actions() + but:On("DoClick", function() + local dmenu = vgui.Create("SAM.Menu") + dmenu:SetInternal(but) + if name then + dmenu:AddOption("Copy Name", function() + SetClipboardText(name) + end) + end + dmenu:AddOption("Copy SteamID", function() + SetClipboardText(v.steamid) + end) + dmenu:AddOption("Copy Reason", function() + SetClipboardText(v.reason) + end) + dmenu:AddOption("Copy Time Left", function() + SetClipboardText(time_left:GetText()) + end) + + if v.admin ~= "Console" then + dmenu:AddSpacer() + + if admin_name then + dmenu:AddOption("Copy Admin Name", function() + SetClipboardText(admin_name) + end) + end + + dmenu:AddOption("Copy Admin SteamID", function() + SetClipboardText(v.admin) + end) + end + + if LocalPlayer():HasPermission("unban") then + dmenu:AddSpacer() + dmenu:AddOption("Unban", function() + local user = name and ("%s (%s)"):format(name, v.steamid) or v.steamid + local querybox = vgui.Create("SAM.QueryBox") + querybox:SetWide(350) + querybox:SetTitle(user) + + local check = querybox:Add("SAM.Label") + check:SetText(sui.wrap_text("Are you sure that you want to unban\n" .. user, LINE_FONT, SUI.Scale(350))) + check:SetFont(LINE_FONT) + check:SizeToContents() + + querybox:Done() + querybox.save:SetEnabled(true) + querybox.save:SetText("UNBAN") + + querybox.save:SetContained(false) + querybox.save:SetColors(GetColor("query_box_cancel"), GetColor("query_box_cancel_text")) + + querybox.cancel:SetContained(true) + querybox.cancel:SetColors() + + querybox:SetCallback(function() + RunConsoleCommand("sam", "unban", v.steamid) + end) + end) + end + dmenu:Open() + end) + + body:Line() + end + + body:InvalidateLayout(true) + body:GetCanvas():InvalidateLayout(true) + end + + refresh = function() + if not is_loading() and LocalPlayer():HasPermission("manage_bans") then + local refresh_query = netstream.async.Start("SAM.GetBans", toggle_loading, current_page.i, current_order, keyword) + refresh_query:done(set_data) + end + end + + local bottom_panel = bans_body:Add("SAM.Panel") + bottom_panel:Dock(BOTTOM) + bottom_panel:DockMargin(0, 6, 0, 0) + bottom_panel:SetTall(30) + bottom_panel:Background(GetColor("page_switch_bg")) + + local previous_page = bottom_panel:Add("SAM.Button") + previous_page:Dock(LEFT) + previous_page:SetWide(30) + previous_page:SetText("<") + previous_page:SetFont(NEXT_FONT) + + previous_page:On("DoClick", function() + if current_page.i <= 1 then return end + + current_page.i = current_page.i - 1 + refresh() + end) + + current_page = bottom_panel:Add("SAM.Label") + current_page:Dock(FILL) + current_page:SetContentAlignment(5) + current_page:SetFont(SAM_TAB_DESC_FONT) + current_page:SetText("loading...") + current_page.i = 1 + + local next_page = bottom_panel:Add("SAM.Button") + next_page:Dock(RIGHT) + next_page:SetWide(30) + next_page:SetText(">") + next_page:SetFont(NEXT_FONT) + + next_page:On("DoClick", function() + if current_page.i == pages then return end + + current_page.i = current_page.i + 1 + refresh() + end) + + function bottom_panel:Think() + next_page:SetEnabled(current_page.i ~= pages) + previous_page:SetEnabled(current_page.i > 1) + end + + for k, v in ipairs({"SAM.BannedPlayer", "SAM.BannedSteamID", "SAM.EditedBan", "SAM.UnbannedSteamID"}) do + hook.Add(v, "SAM.MenuBans", function() + if IsValid(body) then + refresh() + end + end) + end + + refresh() + + return bans_body +end, function() + return LocalPlayer():HasPermission("manage_bans") +end, 4) diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/commands.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/commands.lua new file mode 100644 index 0000000..48f6903 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/commands.lua @@ -0,0 +1,223 @@ +if SAM_LOADED then return end +if SERVER then return end + +local sam = sam +local SUI = sam.SUI +local type = sam.type + +local Line = sui.TDLib.LibClasses.Line + +local COMMAND_HELP = SUI.CreateFont("CommandHelp", "Roboto", 14) +local COMMAND_RUN = SUI.CreateFont("CommandRun", "Roboto Medium", 14) + +sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/command_window.png", function(column_sheet) + local tab_body = column_sheet:Add("Panel") + tab_body:Dock(FILL) + tab_body:DockMargin(0, 1, 0, 0) + + do + local title = tab_body:Add("SAM.Label") + title:Dock(TOP) + title:DockMargin(10, 10, 0, 0) + title:SetFont(SAM_TAB_TITLE_FONT) + title:SetText("Commands") + title:SetTextColor(SUI.GetColor("menu_tabs_title")) + title:SizeToContents() + end + + local body = tab_body:Add("Panel") + body:Dock(FILL) + body:DockMargin(10, 5, 10, 10) + + Line(body) + + local left_body = body:Add("SAM.Panel") + left_body:Dock(LEFT) + left_body:SetWide(148) + + local search_entry = left_body:Add("SAM.TextEntry") + search_entry:Dock(TOP) + search_entry:SetNoBar(true) + search_entry:SetPlaceholder("Search...") + search_entry:SetRadius(4) + search_entry:SetTall(27) + + local category_list = left_body:Add("SAM.CollapseCategory") + category_list:Dock(FILL) + category_list:DockMargin(0, SUI.Scale(10), 0, 0) + + local canvas = category_list:GetCanvas() + + local commands_refresh = function() + if not IsValid(category_list) then return end + + canvas:Clear() + table.Empty(category_list.items) + table.Empty(category_list.categories) + + for k, v in ipairs(sam.command.get_commands()) do + if (v.permission and not LocalPlayer():HasPermission(v.permission)) or v.menu_hide then + continue + end + + local item = category_list:add_item(v.name, v.category) + item:InvalidateParent(true) + item.help = v.help + item.command = v + + item.names = {v.name:lower()} + for _, aliase in ipairs(v.aliases) do + table.insert(item.names, aliase:lower()) + end + end + end + commands_refresh() + + do + local hooks = { + "SAM.CommandAdded", "SAM.CommandModified", "SAM.CommandRemoved", + "SAM.RemovedPermission", + {"SAM.ChangedPlayerRank", func = function(ply, rank, old_rank) + if rank == old_rank then return end + if ply == LocalPlayer() then + commands_refresh() + end + end}, + { + "SAM.RankPermissionGiven", "SAM.RankPermissionTaken", "SAM.ChangedInheritRank", + func = function(rank) + if rank == LocalPlayer():sam_getrank() then + commands_refresh() + end + end + }, + { + "SAM.AddedPermission", "SAM.PermissionModified", + func = function(_, _, rank) + if rank == LocalPlayer():sam_getrank() then + commands_refresh() + end + end + } + } + for _, v in ipairs(hooks) do + if type(v) == "table" then + for _, v2 in ipairs(v) do + hook.Add(v2, "SAM.Menu.RefreshCommands", v.func) + end + else + hook.Add(v, "SAM.Menu.RefreshCommands", commands_refresh) + end + end + end + + function search_entry:OnValueChange(text) + category_list:Search(text:lower()) + end + + do + local line = Line(body, LEFT) + line:DockMargin(10, 0, 10, 0) + line:SetWide(1) + end + + local buttons = body:Add("SAM.ScrollPanel") + buttons:Dock(FILL) + + local childs = {} + local pos = 0 + buttons:GetCanvas():On("OnChildAdded", function(s, child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, 5) + child:SetAlpha(0) + child:SetVisible(false) + table.insert(childs, child) + + pos = pos + 1 + child:SetZPos(pos) + end) + + local run_command = buttons:Add("SAM.Button") + run_command:Dock(TOP) + run_command:SetTall(25) + run_command:SetFont(COMMAND_RUN) + run_command:SetZPos(100) + run_command:SetEnabled(false) + + run_command:On("DoClick", function(self) + LocalPlayer():ConCommand("sam\"" .. self:GetText() .. "\"\"" .. table.concat(self.input_arguments, "\"\"") .. "\"") + end) + + local help = buttons:Add("SAM.Label") + help:Dock(TOP) + help:SetFont(COMMAND_HELP) + help:SetZPos(101) + help:SetWrap(true) + help:SetAutoStretchVertical(true) + + sam.menu.get():On("OnKeyCodePressed", function(s, key_code) + if key_code == KEY_ENTER and IsValid(run_command) and run_command:IsEnabled() and run_command:IsMouseInputEnabled() and tab_body:IsVisible() then + run_command:DoClick() + end + end) + + function category_list:item_selected(item) + local arguments = sam.command.get_arguments() + local command = item.command + local command_arguments = command.args + local input_arguments = {} + + for i = #childs, 3, -1 do + local v = childs[i] + if not v.no_change or not command:HasArg(v.no_change) then + if v.no_remove ~= true then + v:Remove() + else + v:Hide() + end + end + end + + local NIL = {} + for _, v in ipairs(command_arguments) do + local func = arguments[v.name]["menu"] + if not func then continue end + + local i = table.insert(input_arguments, NIL) + local p = func(function(allow) + if not IsValid(run_command) then return end + input_arguments[i] = allow == nil and NIL or allow + for i_2 = 1, #input_arguments do + if input_arguments[i_2] == NIL then + run_command:SetEnabled(false) + return + end + end + run_command:SetEnabled(true) + end, body, buttons, v, childs) + if p then + p:AnimatedSetVisible(true) + end + end + + if #command_arguments == 0 then + run_command:SetEnabled(true) + end + + run_command:SetText(command.name) + run_command:AnimatedSetVisible(true) + run_command.input_arguments = input_arguments + + if command.help then + help:SetText(command.help) + help:AnimatedSetVisible(true) + help:SizeToContents() + else + help:AnimatedSetVisible(false) + end + + buttons:InvalidateLayout(true) + end + + return tab_body +end, nil, 1) diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config.lua new file mode 100644 index 0000000..caa8c85 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config.lua @@ -0,0 +1,97 @@ +if SAM_LOADED then return end + +local sam = sam +local config = sam.config + +local tabs = {} +if CLIENT then + function config.add_tab(name, func, check, pos) + local tab = { + name = name, + func = func, + check = check, + pos = pos + } + for k, v in ipairs(tabs) do + if v.name == name then + tabs[k] = tab + return + end + end + table.insert(tabs, tab) + end +end + +for _, f in ipairs(file.Find("sam/menu/tabs/config/*.lua", "LUA")) do + sam.load_file("sam/menu/tabs/config/" .. f, "cl_") +end + +if SERVER then return end + +local SUI = sam.SUI +local GetColor = SUI.GetColor +local Line = sui.TDLib.LibClasses.Line + +sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/config.png", function(column_sheet) + local tab_body = column_sheet:Add("Panel") + tab_body:Dock(FILL) + tab_body:DockMargin(0, 1, 0, 0) + + do + local title = tab_body:Add("SAM.Label") + title:Dock(TOP) + title:DockMargin(10, 10, 0, 0) + title:SetFont(SAM_TAB_TITLE_FONT) + title:SetText("Config") + title:SetTextColor(GetColor("menu_tabs_title")) + title:SizeToContents() + + local total = tab_body:Add("SAM.Label") + total:Dock(TOP) + total:DockMargin(10, 6, 0, 0) + total:SetFont(SAM_TAB_DESC_FONT) + total:SetText("Some settings may require a server restart") + total:SetTextColor(GetColor("menu_tabs_title")) + total:SetPos(10, SUI.Scale(40)) + total:SizeToContents() + end + + local body = tab_body:Add("Panel") + body:Dock(FILL) + body:DockMargin(10, 5, 10, 10) + + Line(body, nil, 0, 0, 0, 10) + + local sheet = body:Add("SAM.PropertySheet") + sheet:Dock(FILL) + sheet:InvalidateParent(true) + sheet:InvalidateLayout(true) + + local sheets = {} + for _, v in SortedPairsByMemberValue(tabs, "pos") do + sheets[v.name] = sheet:AddSheet(v.name, v.func) + end + + local tab_scroller = sheet.tab_scroller:GetCanvas() + function tab_body.Think() + for _, v in ipairs(tabs) do + local tab = sheets[v.name] + if v.check and not v.check() then + if tab:IsVisible() then + tab:SetVisible(false) + if sheet:GetActiveTab() == tab then + sheet:SetActiveTab(sheet.tabs[1]) + end + tab_scroller:InvalidateLayout() + end + elseif not tab:IsVisible() then + tab:SetVisible(true) + tab_scroller:InvalidateLayout() + end + end + end + + return tab_body +end, function() + return LocalPlayer():HasPermission("manage_config") +end, 5) diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config/reports.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config/reports.lua new file mode 100644 index 0000000..a05b511 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config/reports.lua @@ -0,0 +1,161 @@ +if SAM_LOADED then return end + +local sam = sam +local config = sam.config + +local not_empty = function(s) + return s and s ~= "" +end + +local number_entry = function(setting, config_key, default) + local entry = setting:Add("SAM.TextEntry") + entry:SetWide(50) + entry:SetPlaceholder("") + entry:SetBackground(Color(34, 34, 34)) + entry:SetNumeric(true) + entry:DisallowFloats() + entry:DisallowNegative() + entry:SetCheck(not_empty) + entry:SetConfig(config_key, default) + + return entry +end + +config.add_tab("Reports", function(parent) + local body = parent:Add("SAM.ScrollPanel") + body:Dock(FILL) + body:LineMargin(0, 6, 0, 0) + + local i = 0 + body:GetCanvas():On("OnChildAdded", function(s, child) + i = i + 1 + child:SetZPos(i) + + if not body.making_line then + body:Line() + end + end) + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("Enable") + setting:DockMargin(8, 6, 8, 0) + + local enable = setting:Add("SAM.ToggleButton") + enable:SetConfig("Reports", true) + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("Commands") + setting:DockMargin(8, 6, 8, 0) + + local entry = setting:Add("SAM.TextEntry") + entry:SetWide(200) + entry:SetNoBar(true) + entry:SetPlaceholder("") + entry:SetMultiline(true) + entry:SetConfig("Reports.Commands") + entry.no_scale = true + + function entry:OnValueChange() + self:SetTall(self:GetNumLines() * (sam.SUI.Scale(16) --[[font size]] + 1) + 1 + 2) + end + entry:OnValueChange() + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("Max Reports (Number of reports that can show on your screen)") + setting:DockMargin(8, 6, 8, 0) + + number_entry(setting, "Reports.MaxReports", 4) + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("Auto Close Time (Time to wait before automatically closing claimed reports)") + setting:DockMargin(8, 6, 8, 0) + + local entry = setting:Add("SAM.TextEntry") + entry:SetWide(70) + entry:SetNoBar(false) + entry:SetPlaceholder("") + entry:SetCheck(function(time) + time = sam.parse_length(time) + if not time then + return false + end + end) + entry:SetConfig("Reports.AutoCloseTime", "10m") + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("Always Show (Show the popups even if you are not on duty)") + setting:DockMargin(8, 6, 8, 0) + + local enable = setting:Add("SAM.ToggleButton") + enable:SetConfig("Reports.AlwaysShow", true) + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("On Duty Jobs") + setting:DockMargin(8, 6, 8, 0) + + local entry = setting:Add("SAM.TextEntry") + entry:SetWide(300) + entry:SetNoBar(true) + entry:SetPlaceholder("") + entry:SetMultiline(true) + entry:SetConfig("Reports.DutyJobs", "") + entry.no_scale = true + + function entry:OnValueChange() + self:SetTall(self:GetNumLines() * (sam.SUI.Scale(16) --[[font size]] + 1) + 1 + 2) + end + entry:OnValueChange() + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("Position") + setting:DockMargin(8, 6, 8, 0) + + local combo = setting:Add("SAM.ComboBox") + combo:SetWide(60) + combo:AddChoice("Left", nil, true) + combo:AddChoice("Right") + combo:SetConfig("Reports.Position", "Left") + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("X Padding") + setting:DockMargin(8, 6, 8, 0) + + number_entry(setting, "Reports.XPadding", 5) + end + + do + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel("Y Padding") + setting:DockMargin(8, 6, 8, 0) + + number_entry(setting, "Reports.YPadding", 5) + end + + return body +end, function() + return LocalPlayer():HasPermission("manage_config") +end, 2) diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config/server.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config/server.lua new file mode 100644 index 0000000..26c4508 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/config/server.lua @@ -0,0 +1,32 @@ +if SAM_LOADED then return end + +local sam = sam +local config = sam.config + +config.add_tab("Server", function(parent) + local server_body = parent:Add("SAM.ScrollPanel") + server_body:Dock(FILL) + server_body:LineMargin(0, 6, 0, 0) + + local i = 0 + server_body:GetCanvas():On("OnChildAdded", function(s, child) + i = i + 1 + child:SetZPos(i) + end) + + for k, v in ipairs(sam.config.get_menu_settings()) do + local panel = v.func(server_body) + if ispanel(panel) then + local setting = server_body:Add("SAM.LabelPanel") + setting:DockMargin(8, 6, 8, 0) + setting:SetLabel(v.title) + setting:SetPanel(panel) + end + + server_body:Line() + end + + return server_body +end, function() + return LocalPlayer():HasPermission("manage_config") +end, 1) diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/players.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/players.lua new file mode 100644 index 0000000..c672f3c --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/players.lua @@ -0,0 +1,452 @@ +if SAM_LOADED then return end + +local sam = sam +local SQL = sam.SQL +local SUI = sam.SUI +local netstream = sam.netstream + +sam.permissions.add("manage_players", nil, "superadmin") + +local get_pages_count = function(count) + count = count / 35 + local i2 = math.floor(count) + return count ~= i2 and i2 + 1 or count +end + +if SERVER then + local check = function(ply) + return ply:HasPermission("manage_players") and ply:sam_check_cooldown("MenuViewPlayers", 0.1) + end + + local limit = 35 + + local get_page_count = function(callback, res, page, column, order_by, sort_by, keyword) + local query = [[ + SELECT + COUNT(`steamid`) AS `count` + FROM + `sam_players`]] + if keyword then + if column == "steamid" and sam.is_steamid64(keyword) then + keyword = util.SteamIDFrom64(keyword) + end + + query = string.format("%s WHERE `%s` LIKE %s", query, column, SQL.Escape("%" .. keyword .. "%")) + end + SQL.Query(query, callback, true, {res, page, column, order_by, sort_by, keyword}) + end + + local valid_columns = { + steamid = true, + name = true, + rank = true + } + + local valid_sorts = { + id = true, + name = true, + rank = true, + play_time = true, + last_join = true + } + + local resolve_promise = function(data, arguments) + local res = arguments[1] + arguments[1] = data + res(arguments) + end + + local get_players = function(count_data, arguments) + local res, page, column, order_by, sort_by, keyword = unpack(arguments) + local count = count_data.count + + local current_page + if page < 1 then + page, current_page = 1, 1 + end + + local pages_count = get_pages_count(count) + if page > pages_count then + page, current_page = pages_count, pages_count + end + + local query = [[ + SELECT + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + FROM + `sam_players` + ]] + + local args = {} + + if keyword then + args[1] = column + args[2] = "%" .. keyword .. "%" + + query = query .. [[ + WHERE + `{1f}` LIKE {2} + ]] + end + + args[3] = sort_by + if order_by == "DESC" then + query = query .. [[ + ORDER BY `{3f}` DESC + ]] + else + query = query .. [[ + ORDER BY `{3f}` ASC + ]] + end + + args[4] = limit + args[5] = math.abs(limit * (page - 1)) + + query = query .. [[ + LIMIT {4} OFFSET {5} + ]] + + SQL.FQuery(query, args, resolve_promise, false, {res, count, current_page}) + end + + netstream.async.Hook("SAM.GetPlayers", function(res, ply, page, column, order_by, sort_by, keyword) + if not isnumber(page) then return end + if not valid_columns[column] then return end + if order_by ~= "ASC" and order_by ~= "DESC" then return end + if not valid_sorts[sort_by] then return end + if keyword ~= nil and not sam.isstring(keyword) then return end + + get_page_count(get_players, res, page, column, order_by, sort_by, keyword) + end, check) + + return +end + +local GetColor = SUI.GetColor +local Line = sui.TDLib.LibClasses.Line + +local COLUMN_FONT = SUI.CreateFont("Column", "Roboto", 18) +local LINE_FONT = SUI.CreateFont("Line", "Roboto", 16) +local NEXT_FONT = SUI.CreateFont("NextButton", "Roboto", 18) + +local button_click = function(s) + local v = s.v + + local dmenu = vgui.Create("SAM.Menu") + dmenu:SetInternal(s) + if v.name and v.name ~= "" then + dmenu:AddOption("Copy Name", function() + SetClipboardText(v.name) + end) + end + + dmenu:AddOption("Copy SteamID", function() + SetClipboardText(v.steamid) + end) + + dmenu:AddOption("Copy Rank", function() + SetClipboardText(v.rank) + end) + + dmenu:AddOption("Copy Play Time", function() + SetClipboardText(sam.reverse_parse_length(tonumber(v.play_time) / 60)) + end) + + dmenu:AddSpacer() + + dmenu:AddOption("Change Rank", function() + local querybox = vgui.Create("SAM.QueryBox") + querybox:SetTitle(string.format("Change rank for '%s'", v.name or v.steamid)) + querybox:SetWide(360) + + local ranks = querybox:Add("SAM.ComboBox") + ranks:SetTall(28) + + for rank_name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do + if v.rank ~= rank_name then + ranks:AddChoice(rank_name, nil, true) + end + end + + querybox:Done() + querybox.save:SetEnabled(true) + + querybox:SetCallback(function() + RunConsoleCommand("sam", "setrankid", v.steamid, ranks:GetValue()) + end) + end) + + dmenu:Open() +end + +sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/user.png", function(column_sheet) + local refresh, pages + local current_page, current_column, current_order, current_sort, keyword = nil, "steamid", "DESC", "id", nil + + local players_body = column_sheet:Add("Panel") + players_body:Dock(FILL) + players_body:DockMargin(0, 1, 0, 0) + players_body:DockPadding(10, 10, 10, 10) + + local toggle_loading, is_loading = sam.menu.add_loading_panel(players_body) + + local title = players_body:Add("SAM.Label") + title:Dock(TOP) + title:SetFont(SAM_TAB_TITLE_FONT) + title:SetText("Players") + title:SetTextColor(GetColor("menu_tabs_title")) + title:SizeToContents() + + local total = players_body:Add("SAM.Label") + total:Dock(TOP) + total:DockMargin(0, 6, 0, 0) + total:SetFont(SAM_TAB_DESC_FONT) + total:SetText("60 total players") + total:SetTextColor(GetColor("menu_tabs_title")) + total:SetPos(10, SUI.Scale(40)) + total:SizeToContents() + + local search_entry + do + local container = players_body:Add("SAM.Panel") + container:Dock(TOP) + container:DockMargin(0, 6, 10, 0) + container:SetTall(30) + + local sort_by = container:Add("SAM.ComboBox") + sort_by:Dock(RIGHT) + sort_by:DockMargin(4, 0, 0, 0) + sort_by:SetWide(106) + sort_by:SetValue("Sort By (ID)") + sort_by:AddChoice("ID") + sort_by:AddChoice("Name") + sort_by:AddChoice("Rank") + sort_by:AddChoice("Play Time") + + function sort_by:OnSelect(_, value) + value = value:lower():gsub(" ", "_") + if current_sort ~= value then + current_sort = value + refresh() + end + end + + local sort_order = container:Add("SAM.ComboBox") + sort_order:Dock(RIGHT) + sort_order:SetWide(96) + sort_order:SetValue("Desc") + sort_order:AddChoice("Desc") + sort_order:AddChoice("Asc") + + function sort_order:OnSelect(_, value) + value = value:upper() + if current_order ~= value then + current_order = value + refresh() + end + end + + local column = container:Add("SAM.ComboBox") + column:Dock(RIGHT) + column:DockMargin(0, 0, 4, 0) + column:SetWide(140) + + column:SetValue("Search (SteamID)") + column:AddChoice("SteamID") + column:AddChoice("Name") + column:AddChoice("Rank") + + function column:OnSelect(_, value) + value = value:lower() + if current_column ~= value then + current_column = value + refresh() + end + end + + search_entry = container:Add("SAM.TextEntry") + search_entry:Dock(LEFT) + search_entry:SetNoBar(true) + search_entry:SetPlaceholder("Search...") + search_entry:SetRadius(4) + search_entry:SetWide(220) + + function search_entry:OnEnter(no_refresh) + local value = self:GetValue() + if keyword ~= value then + keyword = value ~= "" and value or nil + if not no_refresh then + refresh() + end + end + end + end + + Line(players_body, nil, -5, SUI.Scale(15), -5, 0) + + do + local columns = players_body:Add("Panel") + columns:Dock(TOP) + columns:DockMargin(0, 10, 0, 0) + + local info = columns:Add("SAM.Label") + info:Dock(LEFT) + info:DockMargin(4, 0, 0, 0) + info:SetFont(COLUMN_FONT) + info:SetText("Player") + info:SetTextColor(GetColor("player_list_titles")) + info:SetWide(SUI.Scale(280) + SUI.Scale(34)) + info:SizeToContentsY(3) + + local play_time = columns:Add("SAM.Label") + play_time:Dock(LEFT) + play_time:DockMargin(-4, 0, 0, 0) + play_time:SetFont(COLUMN_FONT) + play_time:SetText("Play Time") + play_time:SetTextColor(GetColor("player_list_titles")) + play_time:SetWide(SUI.Scale(180)) + play_time:SizeToContentsY(3) + + local rank_expiry = columns:Add("SAM.Label") + rank_expiry:Dock(LEFT) + rank_expiry:DockMargin(-4, 0, 0, 0) + rank_expiry:SetFont(COLUMN_FONT) + rank_expiry:SetText("Rank Expiry") + rank_expiry:SetTextColor(GetColor("player_list_titles")) + rank_expiry:SetWide(SUI.Scale(280)) + rank_expiry:SizeToContentsY(3) + + columns:SizeToChildren(false, true) + end + + local body = players_body:Add("SAM.ScrollPanel") + body:Dock(FILL) + body:DockMargin(0, 10, 0, 0) + body:SetVBarPadding(6) + + local set_data = function(data) + body:GetCanvas():Clear() + body.VBar.Scroll = 0 + + local players, players_count, current_page_2 = unpack(data) + total:SetText(players_count .. " total players") + + pages = get_pages_count(players_count) + current_page.i = pages == 0 and 0 or current_page_2 or current_page.i + current_page:SetText(current_page.i .. "/" .. pages) + + body:Line() + + for k, v in ipairs(players) do + local line = body:Add("SAM.PlayerLine") + line:DockMargin(0, 0, 0, 10) + + local name = v.name ~= "" and v.name or nil + line:SetInfo({ + steamid = v.steamid, + name = name, + rank = v.rank + }) + + local play_time = line:Add("SAM.Label") + play_time:Dock(LEFT) + play_time:DockMargin(4, 0, 0, 0) + play_time:SetFont(LINE_FONT) + play_time:SetText(sam.reverse_parse_length(tonumber(v.play_time) / 60)) + play_time:SetTextColor(GetColor("player_list_data")) + play_time:SetContentAlignment(4) + play_time:SetWide(SUI.Scale(180)) + + local expiry_date = tonumber(v.expiry_date) + local rank_expiry = line:Add("SAM.Label") + rank_expiry:Dock(LEFT) + rank_expiry:DockMargin(-3, 0, 0, 0) + rank_expiry:SetFont(LINE_FONT) + rank_expiry:SetText(expiry_date == 0 and "Never" or sam.reverse_parse_length((expiry_date - os.time()) / 60)) + rank_expiry:SetTextColor(GetColor("player_list_data")) + rank_expiry:SetContentAlignment(4) + rank_expiry:SizeToContents() + + local but = line:Actions() + but.v = v + but:On("DoClick", button_click) + + body:Line() + end + end + + refresh = function() + if not is_loading() and LocalPlayer():HasPermission("manage_players") then + search_entry:OnEnter(true) + local refresh_query = netstream.async.Start("SAM.GetPlayers", toggle_loading, current_page.i, current_column, current_order, current_sort, keyword) + refresh_query:done(set_data) + end + end + + local bottom_panel = players_body:Add("SAM.Panel") + bottom_panel:Dock(BOTTOM) + bottom_panel:DockMargin(0, 6, 0, 0) + bottom_panel:SetTall(30) + bottom_panel:Background(GetColor("page_switch_bg")) + + local previous_page = bottom_panel:Add("SAM.Button") + previous_page:Dock(LEFT) + previous_page:SetWide(30) + previous_page:SetText("<") + previous_page:SetFont(NEXT_FONT) + + previous_page:On("DoClick", function() + if current_page.i <= 1 then return end + + current_page.i = current_page.i - 1 + refresh() + end) + + current_page = bottom_panel:Add("SAM.Label") + current_page:Dock(FILL) + current_page:SetContentAlignment(5) + current_page:SetFont(SAM_TAB_DESC_FONT) + current_page:SetText("loading...") + current_page.i = 1 + + local next_page = bottom_panel:Add("SAM.Button") + next_page:Dock(RIGHT) + next_page:SetWide(30) + next_page:SetText(">") + next_page:SetFont(NEXT_FONT) + + next_page:On("DoClick", function() + if current_page.i == pages then return end + + current_page.i = current_page.i + 1 + refresh() + end) + + function bottom_panel:Think() + next_page:SetEnabled(current_page.i ~= pages) + previous_page:SetEnabled(current_page.i > 1) + end + + do + local refresh_2 = function() + timer.Simple(1, refresh) + end + + for k, v in ipairs({"SAM.AuthedPlayer", "SAM.ChangedPlayerRank", "SAM.ChangedSteamIDRank"}) do + hook.Add(v, "SAM.MenuPlayers", refresh_2) + end + end + + refresh() + + return players_body +end, function() + return LocalPlayer():HasPermission("manage_players") +end, 2) diff --git a/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/ranks.lua b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/ranks.lua new file mode 100644 index 0000000..c2475a3 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/menu/tabs/ranks.lua @@ -0,0 +1,619 @@ +if SAM_LOADED then return end +if SERVER then return end + +local sam = sam +local SUI = sam.SUI + +local GetColor = SUI.GetColor +local Line = sui.TDLib.LibClasses.Line +local AnimatedSetVisible = sui.TDLib.LibClasses.AnimatedSetVisible + +local RANK_NAME = SUI.CreateFont("RankName", "Roboto Bold", 18) +local RANK_INFO = SUI.CreateFont("RankInfo", "Roboto Medium", 12) + +local CREATE_RANK = SUI.CreateFont("CreateRank", "Roboto Bold", 16, 200) +local RANK_TITLE = SUI.CreateFont("RankTitle", "Roboto Bold", 20) + +local rank_menu = function(rank, data) + local valid = sui.valid_options() + + local imm, banlim + if rank then + imm, banlim = data.immunity, data.ban_limit + end + + local edit_rank = vgui.Create("SAM.QueryBox") + edit_rank:SetWide(470) + edit_rank:SetTitle(rank and string.format("Edit Rank '%s'", rank) or "Create Rank") + + local new_name = rank + if not sam.ranks.is_default_rank(rank) then + local name = edit_rank:Add("SAM.LabelPanel") + name:SetLabel("Rank Name") + + local entry = name:Add("SAM.TextEntry") + entry:SetSize(210, 28) + entry:SetNoBar(false) + entry:SetPlaceholder("") + entry:SetValue(rank or "") + entry:SetCheck(function(_name) + new_name = _name + + if _name == rank then return end + if _name == "" or sam.ranks.is_rank(_name) then + return false + end + end) + + valid.Add(entry) + end + + local new_immunity = imm + do + local immunity = edit_rank:Add("SAM.LabelPanel") + immunity:SetLabel("Immunity (2~99)") + immunity:DockMargin(0, 5, 0, 0) + + local entry = immunity:Add("SAM.TextEntry") + entry:SetSize(210, 28) + entry:SetNumeric(true) + entry:DisallowFloats(true) + entry:DisallowNegative(true) + entry:SetPlaceholder("") + entry:SetValue(imm or "2") + entry:SetCheck(function(_immunity) + new_immunity = _immunity + + if _immunity == "" then + return false + end + + _immunity = tonumber(_immunity) + new_immunity = _immunity + if _immunity < 2 or _immunity > 99 then + return false + end + end) + + valid.Add(entry) + end + + local new_banlimit = banlim + do + local banlimit = edit_rank:Add("SAM.LabelPanel") + banlimit:SetLabel("Ban Limit (1y 1mo 1w 1d 1h 1m)") + banlimit:DockMargin(0, 5, 0, 0) + + local entry = banlimit:Add("SAM.TextEntry") + entry:SetSize(210, 28) + entry:SetNoBar(false) + entry:SetPlaceholder("") + entry:SetValue(banlim and sam.reverse_parse_length(banlim) or "2w") + entry:SetCheck(function(_banlimit) + new_banlimit = sam.parse_length(_banlimit) + if not new_banlimit and _banlimit ~= banlim then + return false + end + end) + + valid.Add(entry) + end + + local inherit = rank and sam.ranks.get_rank(rank).inherit or "user" + local new_inherit = inherit + do + local inherits_from = edit_rank:Add("SAM.LabelPanel") + inherits_from:SetLabel("Inherits From") + inherits_from:DockMargin(0, 5, 0, 0) + + local entry = inherits_from:Add("SAM.ComboBox") + entry:SetSize(210, 28) + entry:SetValue(inherit) + + for name in SortedPairsByMemberValue(sam.ranks.get_ranks(), "immunity", true) do + if name ~= rank and not sam.ranks.inherits_from(name, rank) then + entry:AddChoice(name) + end + end + + function entry:OnSelect(_, value) + new_inherit = value + end + end + + + edit_rank:Done() + edit_rank.save:SetEnabled(true) + edit_rank.save:SetText("SAVE") + + if rank then + edit_rank:SetCallback(function() + local to_run = {} + + if new_immunity ~= imm then + table.insert(to_run, {"changerankimmunity", rank, new_immunity}) + end + + if new_banlimit ~= banlim then + table.insert(to_run, {"changerankbanlimit", rank, new_banlimit}) + end + + if new_inherit ~= inherit then + table.insert(to_run, {"changeinherit", rank, new_inherit}) + end + + if new_name ~= rank then + table.insert(to_run, {"renamerank", rank, new_name}) + end + sam.command.run_commands(to_run) + end) + else + edit_rank:SetCallback(function() + RunConsoleCommand("sam", "addrank", new_name, new_inherit, new_immunity, new_banlimit) + end) + end + + function edit_rank.save:Think() + self:SetEnabled(valid.IsValid()) + end +end + +sam.menu.add_tab("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/military_rank.png", function(column_sheet) + local current_rank + + local parent = column_sheet:Add("Panel") + parent:Dock(FILL) + parent:DockMargin(0, 1, 0, 0) + + local title = parent:Add("SAM.Label") + title:Dock(TOP) + title:DockMargin(10, 10, 0, 0) + title:SetFont(SAM_TAB_TITLE_FONT) + title:SetText("Ranks") + title:SetTextColor(GetColor("menu_tabs_title")) + title:SizeToContents() + + local total = parent:Add("SAM.Label") + total:Dock(TOP) + total:DockMargin(10, 6, 0, 0) + total:SetFont(SAM_TAB_DESC_FONT) + total:SetText(table.Count(sam.ranks.get_ranks()) .. " total ranks") + total:SetTextColor(GetColor("menu_tabs_title")) + total:SizeToContents() + + local search_entry + do + local container = parent:Add("SAM.Panel") + container:Dock(TOP) + container:DockMargin(10, 6, 10, SUI.Scale(15)) + container:SetTall(30) + + search_entry = container:Add("SAM.TextEntry") + search_entry:Dock(LEFT) + search_entry:SetNoBar(true) + search_entry:SetPlaceholder("Search...") + search_entry:SetRadius(4) + search_entry:SetWide(220) + end + + local create_rank = parent:Add("SAM.Button") + create_rank:SetFont(CREATE_RANK) + create_rank:SetText("Create Rank") + create_rank:Dock(BOTTOM) + create_rank:DockMargin(10, 0, 10, 10) + + create_rank:On("DoClick", function() + rank_menu() + end) + + local right_body = parent:Add("Panel") + right_body:Dock(RIGHT) + right_body:DockMargin(0, 5, 10, 10) + right_body:SetWide(0) + right_body:SetZPos(-1) + + local rank_title = right_body:Add("SAM.Label") + rank_title:Dock(TOP) + rank_title:DockMargin(0, 0, 0, 5) + rank_title:SetFont(RANK_TITLE) + rank_title:SetTextColor(GetColor("menu_tabs_title")) + + local permissions_body = right_body:Add("SAM.CollapseCategory") + permissions_body:Dock(FILL) + permissions_body:GetCanvas():DockPadding(0, 0, 5, 0) + + local function refresh_access() + if not IsValid(current_rank) then return end + + for k, v in ipairs(permissions_body.items) do + AnimatedSetVisible(v.img, sam.ranks.has_permission(current_rank.name, v.name)) + end + end + + for k, v in ipairs({"SAM.ChangedInheritRank", "SAM.RankPermissionGiven", "SAM.RankPermissionTaken"}) do + hook.Add(v, "SAM.Menu.RefreshPermissions ", refresh_access) + end + + local function refresh_permissions() + permissions_body:GetCanvas():Clear() + table.Empty(permissions_body.items) + table.Empty(permissions_body.categories) + + local item_click = function(s) + local rank = current_rank.name + if not sam.ranks.has_permission(rank, s.name) then + RunConsoleCommand("sam", "givepermission", rank, s.name) + else + RunConsoleCommand("sam", "takepermission", rank, s.name) + end + end + + for k, v in ipairs(sam.permissions.get()) do + local item = permissions_body:add_item(v.name, v.category) + item:SetContentAlignment(4) + item:SetTextInset(6, 0) + item:SizeToContentsY(SUI.Scale(10)) + item:SetZPos(k) + item.name = v.name + item.DoClick = item_click + + local img = item:Add("SAM.Image") + img:Dock(RIGHT) + img:DockMargin(4, 4, 4, 4) + img:InvalidateParent(true) + img:SetWide(img:GetTall()) + img:SetImageColor(Color(52, 161, 224)) + img:SetImage("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/check_mark.png") + + item.img = img + end + end + + local limits_body + + do + local permissions_search = right_body:Add("SAM.TextEntry") + permissions_search:Dock(TOP) + permissions_search:DockMargin(0, 0, 5, 10) + permissions_search:SetNoBar(true) + permissions_search:SetPlaceholder("Search...") + permissions_search:SetRadius(4) + permissions_search:SetTall(30) + + function permissions_search:OnValueChange(text) + if limits_body and limits_body:IsVisible() then + local children = limits_body:GetCanvas():GetChildren() + for k, v in ipairs(children) do + v:AnimatedSetVisible(v.title:find(text, nil, true) ~= nil) + end + limits_body:InvalidateLayout(true) + else + permissions_body:Search(text:lower()) + end + end + + Line(right_body):SetZPos(2) + end + + local function load_limits() + if sam.limit_types then + if limits_body then return end + else + if limits_body then + limits_body:SetVisible(false) + permissions_body:AnimatedSetVisible(true) + limits_body:Remove() + limits_body = nil + end + return + end + + limits_body = right_body:Add("SAM.ScrollPanel") + limits_body:Dock(FILL) + limits_body:GetCanvas():DockPadding(0, 0, 5, 0) + limits_body:SetVisible(false) + + local item_enter = function(s) + if not IsValid(current_rank) then return end + + local rank = current_rank.name + + local limit = math.Clamp(s:GetValue(), -1, 1000) + if limit ~= sam.ranks.get_limit(rank, s.limit_type) then + RunConsoleCommand("sam", "changeranklimit", rank, s.limit_type, limit) + else + s:SetText(tostring(sam.ranks.get_limit(rank, s.limit_type))) + end + end + + local not_empty = function(s) + return s and s ~= "" + end + + local limit_values = {} + for k, v in ipairs(sam.limit_types) do + local immunity = limits_body:Add("SAM.LabelPanel") + immunity:SetLabel(v) + immunity:DockMargin(5, 0, 0, 5) + + local entry = immunity:Add("SAM.TextEntry") + entry:SetSize(60, 26) + entry:SetNumeric(true) + entry:DisallowFloats(true) + entry:SetPlaceholder("") + entry:SetCheck(not_empty) + entry.limit_type = v + entry.OnEnter = item_enter + + table.insert(limit_values, entry) + end + + function limits_body:Refresh() + if not IsValid(current_rank) then return end + + local rank = current_rank.name + for k, v in ipairs(limit_values) do + v:SetValue(tostring(sam.ranks.get_limit(rank, v.limit_type))) + end + end + + local right_current_rank = right_body:Add("SAM.Button") + right_current_rank:Dock(BOTTOM) + right_current_rank:DockMargin(0, 5, 0, 0) + right_current_rank:SetFont(CREATE_RANK) + right_current_rank:SetText("Switch to Limits") + right_current_rank:On("DoClick", function() + limits_body:AnimatedToggleVisible() + permissions_body:AnimatedToggleVisible() + + if permissions_body:AnimatedIsVisible() then + right_current_rank:SetText("Switch to Limits") + else + right_current_rank:SetText("Switch to Permissions") + end + end) + + limits_body:On("OnRemove", function() + right_current_rank:Remove() + end) + limits_body:Refresh() + end + + local function refresh_all() + timer.Create("SAM.Menu.Ranks.Refresh", 1, 1, function() + load_limits() + refresh_permissions() + refresh_access() + end) + end + + sam.config.hook({"Restrictions.Limits"}, refresh_all) + + for k, v in ipairs({"SAM.AddedPermission", "SAM.PermissionModified", "SAM.RemovedPermission"}) do + hook.Add(v, "SAM.Menu.RefreshPermissions", refresh_all) + end + + local body = parent:Add("SAM.ScrollPanel") + body:Dock(FILL) + body:DockMargin(10, 0, 5, 10) + body:SetVBarPadding(6) + + body:Line():SetZPos(-101) + + local select_rank = function(s) + if not IsValid(s) then + current_rank = nil + right_body:SizeTo(0, -1, 0.3) + return + end + + if IsValid(current_rank) then + current_rank.Selected = false + + if current_rank == s then + current_rank = nil + right_body:SizeTo(0, -1, 0.3) + return + end + end + + s.Selected = true + current_rank = s + refresh_access() + if limits_body then + limits_body:Refresh() + end + right_body:SizeTo(SUI.Scale(300), -1, 0.3) + + rank_title:SetText(s.name) + rank_title:SizeToContents() + end + + local ranks = {} + + function search_entry:OnValueChange() + local value = self:GetValue() + for k, v in pairs(ranks) do + local show = k:find(value, nil, true) + show = show ~= nil + v.line:AnimatedSetVisible(show) + v:GetParent():AnimatedSetVisible(show) + end + end + + local add_rank = function(rank_name, data) + if rank_name == "superadmin" then return end + if not IsValid(body) then return end + + local line = body:Add("SAM.Panel") + line:Dock(TOP) + line:DockMargin(0, 0, 0, 10) + line:SetTall(34) + line:SetZPos(-data.immunity) + line:InvalidateLayout(true) + + local container = line:Add("SAM.Button") + container:Dock(FILL) + container:DockMargin(0, 0, 5, 0) + container:DockPadding(5, 5, 0, 5) + container:SetText("") + container:SetContained(false) + container.name = rank_name + + ranks[rank_name] = container + + container:On("DoClick", select_rank) + + function container:DoRightClick() + rank_name = container.name + + if rank_name == "user" then return end + + local dmenu = vgui.Create("SAM.Menu") + dmenu:SetSize(w, h) + dmenu:SetInternal(container) + + dmenu:AddOption("Edit Rank", function() + rank_menu(rank_name, sam.ranks.get_rank(rank_name)) + end) + + if not sam.ranks.is_default_rank(rank_name) then + dmenu:AddSpacer() + + dmenu:AddOption("Remove Rank", function() + local remove_rank = vgui.Create("SAM.QueryBox") + remove_rank:SetWide(350) + + local check = remove_rank:Add("SAM.Label") + check:SetText("Are you sure that you want to remove '" .. rank_name .. "'?") + check:SetFont("SAMLine") + check:SetWrap(true) + check:SetAutoStretchVertical(true) + + remove_rank:Done() + remove_rank.save:SetEnabled(true) + remove_rank.save:SetText("REMOVE") + remove_rank.save:SetContained(false) + remove_rank.save:SetColors(GetColor("query_box_cancel"), GetColor("query_box_cancel_text")) + + remove_rank.cancel:SetContained(true) + remove_rank.cancel:SetColors() + + remove_rank:SetCallback(function() + RunConsoleCommand("sam", "removerank", rank_name) + end) + end) + end + + dmenu:Open() + dmenu:SetPos(input.GetCursorPos()) + end + + do + local name = container:Add("SAM.Label") + name:Dock(TOP) + name:DockMargin(0, 0, 0, 2) + name:SetTextColor(GetColor("player_list_names")) + name:SetFont(RANK_NAME) + name:SetText(rank_name) + name:SizeToContents() + + local immunity = container:Add("SAM.Label") + immunity:Dock(TOP) + immunity:SetTextColor(GetColor("player_list_steamid")) + immunity:SetFont(RANK_INFO) + immunity:SetText("Immunity: " .. data.immunity) + immunity:SizeToContents() + + local banlimit = container:Add("SAM.Label") + banlimit:Dock(TOP) + banlimit:SetTextColor(GetColor("player_list_steamid")) + banlimit:SetFont(RANK_INFO) + banlimit:SetText("Ban limit: " .. sam.reverse_parse_length(sam.parse_length(data.ban_limit))) + banlimit:SizeToContents() + + local inherit = container:Add("SAM.Label") + inherit:Dock(TOP) + inherit:SetTextColor(GetColor("player_list_steamid")) + inherit:SetFont(RANK_INFO) + inherit:SetText("Inherits from: " .. (sam.isstring(data.inherit) and data.inherit or "none")) + inherit:SizeToContents() + end + + container:InvalidateLayout(true) + container:SizeToChildren(false, true) + line:SizeToChildren(false, true) + + local _line = body:Line() + _line:SetZPos(-data.immunity) + + container.line = _line + container.data = data + end + + for rank_name, v in pairs(sam.ranks.get_ranks()) do + add_rank(rank_name, v) + end + + hook.Add("SAM.AddedRank", "SAM.RefreshRanksList", function(name, rank) + add_rank(name, rank) + end) + + hook.Add("SAM.RemovedRank", "SAM.RefreshRanksList", function(name) + local line = ranks[name] + if not IsValid(line) then return end + + line.line:Remove() + line:GetParent():Remove() + ranks[name] = nil + + if line == current_rank then + select_rank() + end + end) + + -- This is just better than caching panels for stuff that ain't gonna be called a lot + hook.Add("SAM.RankNameChanged", "SAM.RefreshRanksList", function(name, new_name) + local line = ranks[name] + if not IsValid(line) then return end + + -- if current_rank == name then + -- rank_name:SetText(new_name) + -- end + + line:GetChildren()[1]:SetText(new_name) + + ranks[new_name], ranks[name] = line, nil + line.name = new_name + end) + + hook.Add("SAM.RankImmunityChanged", "SAM.RefreshRanksList", function(name, immunity) + local line = ranks[name] + if not IsValid(line) then return end + + line:GetChildren()[2]:SetText("Immunity: " .. immunity) + line:GetParent():SetZPos(-immunity) + + -- SetZPos is kinda weird to deal with + line.line:SetZPos(-immunity + 1) + line.line:SetZPos(-immunity) + end) + + hook.Add("SAM.RankBanLimitChanged", "SAM.RefreshRanksList", function(name, new_limit) + local line = ranks[name] + if IsValid(line) then + line:GetChildren()[3]:SetText("Ban limit: " .. sam.reverse_parse_length(new_limit)) + end + end) + + hook.Add("SAM.ChangedInheritRank", "SAM.RefreshRanksList", function(name, new_inherit) + local line = ranks[name] + if IsValid(line) then + line:GetChildren()[4]:SetText("Inherits from: " .. new_inherit) + end + end) + + return parent +end, function() + return LocalPlayer():HasPermission("manage_ranks") +end, 3) diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/cami.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/cami.lua new file mode 100644 index 0000000..e29ad61 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/cami.lua @@ -0,0 +1,357 @@ +if SAM_LOADED then return end + +--[[ +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 = 20201130 + +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 + +--- @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" + }, + admin = { + Name = "admin", + Inherits = "user" + }, + superadmin = { + Name = "superadmin", + Inherits = "admin" + } +} + +--- 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) + 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/garrysmod/addons/module_adminmod/lua/sam/modules/chat.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/chat.lua new file mode 100644 index 0000000..e818d25 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/chat.lua @@ -0,0 +1,228 @@ +if SAM_LOADED then return end + +local sam, command, language = sam, sam.command, sam.language + +command.set_category("Chat") + +command.new("pm") + :SetPermission("pm", "user") + + :AddArg("player", {allow_higher_target = true, single_target = true, cant_target_self = true}) + :AddArg("text", {hint = "message", check = function(str) + return str:match("%S") ~= nil + end}) + + :GetRestArgs() + + :Help("pm_help") + + :OnExecute(function(ply, targets, message) + if ply:sam_get_pdata("unmute_time") then + return ply:sam_send_message("you_muted") + end + + local target = targets[1] + + ply:sam_send_message("pm_to", { + T = targets, V = message + }) + + if ply ~= target then + target:sam_send_message("pm_from", { + A = ply, V = message + }) + end + end) +:End() + +do + sam.permissions.add("see_admin_chat", nil, "admin") + + local reports_enabled = sam.config.get_updated("Reports", true) + command.new("asay") + :SetPermission("asay", "user") + + :AddArg("text", {hint = "message"}) + :GetRestArgs() + + :Help("asay_help") + + :OnExecute(function(ply, message) + if reports_enabled.value and not ply:HasPermission("see_admin_chat") then + local success, time = sam.player.report(ply, message) + if success == false then + ply:sam_send_message("You need to wait {S Red} seconds.", { + S = time + }) + else + ply:sam_send_message("to_admins", { + A = ply, V = message + }) + end + return + end + + local targets = {ply} + + local players = player.GetHumans() + for i = 1, #players do + local v = players[i] + if v:HasPermission("see_admin_chat") and v ~= ply then + table.insert(targets, v) + end + end + + sam.player.send_message(targets, "to_admins", { + A = ply, V = message + }) + end) + :End() + + if SERVER then + sam.hook_last("PlayerSay", "SAM.Chat.Asay", function(ply, text) + if text:sub(1, 1) == "@" then + ply:Say("!asay " .. text:sub(2)) + return "" + end + end) + end +end + +do + command.new("mute") + :SetPermission("mute", "admin") + + :AddArg("player") + :AddArg("length", {optional = true, default = 0, min = 0}) + :AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")}) + + :GetRestArgs() + + :Help("mute_help") + + :OnExecute(function(ply, targets, length, reason) + local current_time = SysTime() + + for i = 1, #targets do + local target = targets[i] + target:sam_set_pdata("unmute_time", length ~= 0 and (current_time + length * 60) or 0) + end + + sam.player.send_message(nil, "mute", { + A = ply, T = targets, V = sam.format_length(length), V_2 = reason + }) + end) + :End() + + command.new("unmute") + :SetPermission("unmute", "admin") + :AddArg("player", {optional = true}) + :Help("unmute_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i]:sam_set_pdata("unmute_time", nil) + end + + sam.player.send_message(nil, "unmute", { + A = ply, T = targets + }) + end) + :End() + + if SERVER then + sam.hook_first("PlayerSay", "SAM.Chat.Mute", function(ply, text) + local unmute_time = ply:sam_get_pdata("unmute_time") + if not unmute_time then return end + + if text:sub(1, 1) == "!" and text:sub(2, 2):match("%S") ~= nil then + local args = sam.parse_args(text:sub(2)) + + local cmd_name = args[1] + if not cmd_name then return end + + local cmd = command.get_command(cmd_name) + if cmd then + return + end + end + + if unmute_time == 0 or unmute_time > SysTime() then + return "" + else + ply:sam_set_pdata("unmute_time", nil) + end + end) + end +end + +do + command.new("gag") + :SetPermission("gag", "admin") + + :AddArg("player") + :AddArg("length", {optional = true, default = 0, min = 0}) + :AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")}) + + :GetRestArgs() + + :Help("gag_help") + + :OnExecute(function(ply, targets, length, reason) + for i = 1, #targets do + local target = targets[i] + target.sam_gagged = true + if length ~= 0 then + timer.Create("SAM.UnGag" .. target:SteamID64(), length * 60, 1, function() + RunConsoleCommand("sam", "ungag", "#" .. target:EntIndex()) + end) + end + end + + sam.player.send_message(nil, "gag", { + A = ply, T = targets, V = sam.format_length(length), V_2 = reason + }) + end) + :End() + + command.new("ungag") + :SetPermission("ungag", "admin") + + :AddArg("player", {optional = true}) + :Help("ungag_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + local target = targets[i] + target.sam_gagged = nil + timer.Remove("SAM.UnGag" .. target:SteamID64()) + end + + sam.player.send_message(nil, "ungag", { + A = ply, T = targets + }) + end) + :End() + + if SERVER then + hook.Add("PlayerCanHearPlayersVoice", "SAM.Chat.Gag", function(_, ply) + if ply.sam_gagged then + return false + end + end) + + hook.Add("PlayerInitialSpawn", "SAM.Gag", function(ply) + local gag_time = ply:sam_get_pdata("gagged") + if gag_time then + ply:sam_set_pdata("gagged", nil) + RunConsoleCommand("sam", "gag", "#" .. ply:EntIndex(), gag_time / 60, "LTAP") + end + end) + + hook.Add("PlayerDisconnected", "SAM.Gag", function(ply) + if ply.sam_gagged then + ply:sam_set_pdata("gagged", timer.TimeLeft("SAM.UnGag" .. ply:SteamID64()) or 0) + end + end) + end +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/darkrp.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/darkrp.lua new file mode 100644 index 0000000..a13be99 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/darkrp.lua @@ -0,0 +1,314 @@ +if SAM_LOADED then return end + +local add = not GAMEMODE and hook.Add or function(_, _, fn) + fn() +end + +add("PostGamemodeLoaded", "SAM.DarkRP", function() + if not DarkRP then return end + + local sam, command, language = sam, sam.command, sam.language + + command.set_category("DarkRP") + + command.new("arrest") + :SetPermission("arrest", "superadmin") + + :AddArg("player") + :AddArg("number", {hint = "time", optional = true, min = 0, default = 0, round = true}) + + :Help("arrest_help") + + :OnExecute(function(ply, targets, time) + if time == 0 then + time = math.huge + end + + for i = 1, #targets do + local v = targets[i] + if v:isArrested() then + v:unArrest() + end + v:arrest(time, ply) + end + + if time == math.huge then + sam.player.send_message(nil, "arrest", { + A = ply, T = targets + }) + else + sam.player.send_message(nil, "arrest2", { + A = ply, T = targets, V = time + }) + end + end) + :End() + + command.new("unarrest") + :SetPermission("unarrest", "superadmin") + + :AddArg("player", {optional = true}) + + :Help("unarrest_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i]:unArrest() + end + + sam.player.send_message(nil, "unarrest", { + A = ply, T = targets + }) + end) + :End() + + command.new("setmoney") + :SetPermission("setmoney", "superadmin") + + :AddArg("player", {single_target = true}) + :AddArg("number", {hint = "amount", min = 0, round = true}) + + :Help("setmoney_help") + + :OnExecute(function(ply, targets, amount) + local target = targets[1] + + amount = hook.Call("playerWalletChanged", GAMEMODE, target, amount - target:getDarkRPVar("money"), target:getDarkRPVar("money")) or amount + + DarkRP.storeMoney(target, amount) + target:setDarkRPVar("money", amount) + + sam.player.send_message(nil, "setmoney", { + A = ply, T = targets, V = amount + }) + end) + :End() + + command.new("addmoney") + :SetPermission("addmoney", "superadmin") + + :AddArg("player", {single_target = true}) + :AddArg("number", {hint = "amount", min = 0, round = true}) + + :Help("addmoney_help") + + :OnExecute(function(ply, targets, amount) + targets[1]:addMoney(amount) + + sam.player.send_message(nil, "addmoney", { + A = ply, T = targets, V = DarkRP.formatMoney(amount) + }) + end) + :End() + + command.new("selldoor") + :SetPermission("selldoor", "superadmin") + + :Help("selldoor_help") + + :OnExecute(function(ply) + local ent = ply:GetEyeTrace().Entity + if not IsValid(ent) or not ent.keysUnOwn then + return ply:sam_send_message("door_invalid") + end + local door_owner = ent:getDoorOwner() + if not IsValid(door_owner) then + return ply:sam_send_message("door_no_owner") + end + ent:keysUnOwn(ply) + + sam.player.send_message(nil, "selldoor", { + A = ply, T = {door_owner, admin = ply} + }) + end) + :End() + + command.new("sellall") + :SetPermission("sellall", "superadmin") + + :AddArg("player", {single_target = true}) + + :Help("sellall_help") + + :OnExecute(function(ply, targets, amount) + targets[1]:keysUnOwnAll() + + sam.player.send_message(nil, "sellall", { + A = ply, T = targets + }) + end) + :End() + + command.new("setjailpos") + :SetPermission("setjailpos", "superadmin") + + :Help("setjailpos_help") + + :OnExecute(function(ply) + DarkRP.storeJailPos(ply, false) + + sam.player.send_message(nil, "s_jail_pos", { + A = ply + }) + end) + :End() + + command.new("addjailpos") + :SetPermission("addjailpos", "superadmin") + + :Help("addjailpos_help") + + :OnExecute(function(ply) + DarkRP.storeJailPos(ply, true) + + sam.player.send_message(nil, "a_jail_pos", { + A = ply + }) + end) + :End() + + local RPExtraTeams = RPExtraTeams + local job_index = nil + + command.new("setjob") + :SetPermission("setjob", "admin") + + :AddArg("player") + :AddArg("text", {hint = "job", check = function(job) + job = job:lower() + + for i = 1, #RPExtraTeams do + local v = RPExtraTeams[i] + if v.name:lower() == job or v.command:lower() == job then + job_index = v.team + return true + end + end + + return false + end}) + + :Help("setjob_help") + + :OnExecute(function(ply, targets, job) + for i = 1, #targets do + targets[i]:changeTeam(job_index, true, true, true) + end + + sam.player.send_message(nil, "setjob", { + A = ply, T = targets, V = job + }) + end) + :End() + + do + local get_shipment = function(name) + local found, key = DarkRP.getShipmentByName(name) + if found then return found, key end + + name = name:lower() + + local shipments = CustomShipments + for i = 1, #shipments do + local shipment = shipments[i] + if shipment.entity == name then + return DarkRP.getShipmentByName(shipment.name) + end + end + + return false + end + + local place_entity = function(ent, tr, ply) + local ang = ply:EyeAngles() + ang.pitch = 0 + ang.yaw = ang.yaw - 90 + ang.roll = 0 + ent:SetAngles(ang) + + local flush_point = tr.HitPos - (tr.HitNormal * 512) + flush_point = ent:NearestPoint(flush_point) + flush_point = ent:GetPos() - flush_point + flush_point = tr.HitPos + flush_point + ent:SetPos(flush_point) + end + + command.new("shipment") + :SetPermission("shipment", "superadmin") + + :AddArg("text", {hint = "weapon", check = get_shipment}) + + :Help("shipment_help") + + :OnExecute(function(ply, weapon_name) + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + local tr = util.TraceLine(trace) + + local shipment_info, shipment_key = get_shipment(weapon_name) + + local crate = ents.Create(shipment_info.shipmentClass or "spawned_shipment") + crate.SID = ply.SID + + crate:Setowning_ent(ply) + crate:SetContents(shipment_key, shipment_info.amount) + + crate:SetPos(Vector(tr.HitPos.x, tr.HitPos.y, tr.HitPos.z)) + + crate.nodupe = true + crate.ammoadd = shipment_info.spareammo + crate.clip1 = shipment_info.clip1 + crate.clip2 = shipment_info.clip2 + + crate:Spawn() + crate:SetPlayer(ply) + + place_entity(crate, tr, ply) + + local phys = crate:GetPhysicsObject() + phys:Wake() + + if shipment_info.weight then + phys:SetMass(shipment_info.weight) + end + + sam.player.send_message(nil, "shipment", { + A = ply, V = weapon_name + }) + end) + :End() + end + + sam.command.new("forcename") + :SetPermission("forcename", "superadmin") + + :AddArg("player") + :AddArg("text", {hint = "name"}) + + :Help("forcename_help") + + :OnExecute(function(ply, targets, name) + local target = targets[1] + + DarkRP.retrieveRPNames(name, function(taken) + if not IsValid(target) then return end + + if taken then + ply:sam_send_message("forcename_taken", { + V = name + }) + return + end + + sam.player.send_message(nil, "forcename", { + A = ply, T = targets, V = name + }) + + DarkRP.storeRPName(target, name) + target:setDarkRPVar("rpname", name) + end) + end) + :End() +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/fun.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/fun.lua new file mode 100644 index 0000000..782aa58 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/fun.lua @@ -0,0 +1,644 @@ +if SAM_LOADED then return end + +local sam, command, language = sam, sam.command, sam.language + +command.set_category("Fun") + +do + local sounds = {} + for i = 1, 6 do + sounds[i] = "physics/body/body_medium_impact_hard" .. i .. ".wav" + end + + local slap = function(ply, damage, admin) + if not ply:Alive() or ply:sam_get_nwvar("frozen") then return end + ply:ExitVehicle() + + ply:SetVelocity(Vector(math.random(-100, 100), math.random(-100, 100), math.random(200, 400))) + ply:EmitSound(sounds[math.random(1, 6)], 60, math.random(80, 120)) + + if damage > 0 then + ply:TakeDamage(damage, admin, DMG_GENERIC) + end + end + + command.new("slap") + :SetPermission("slap", "admin") + + :AddArg("player") + :AddArg("number", {hint = "damage", round = true, optional = true, min = 0, default = 0}) + + :Help("slap_help") + + :OnExecute(function(ply, targets, damage) + for i = 1, #targets do + slap(targets[i], damage, ply) + end + + if damage > 0 then + sam.player.send_message(nil, "slap_damage", { + A = ply, T = targets, V = damage + }) + else + sam.player.send_message(nil, "slap", { + A = ply, T = targets + }) + end + end) + :End() +end + +command.new("slay") + :SetPermission("slay", "admin") + + :AddArg("player") + + :Help("slay_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + local v = targets[i] + if not v:sam_get_exclusive(ply) then + v:Kill() + end + end + + sam.player.send_message(nil, "slay", { + A = ply, T = targets + }) + end) +:End() + +command.new("hp") + :Aliases("sethp", "health", "sethealth") + + :SetPermission("hp", "admin") + + :AddArg("player") + :AddArg("number", {hint = "amount", min = 1, max = 2147483647, round = true, optional = true, default = 100}) + + :Help("hp_help") + + :OnExecute(function(ply, targets, amount) + for i = 1, #targets do + targets[i]:SetHealth(amount) + end + + sam.player.send_message(nil, "set_hp", { + A = ply, T = targets, V = amount + }) + end) +:End() + +command.new("armor") + :Aliases("setarmor") + + :SetPermission("armor", "admin") + + :AddArg("player") + :AddArg("number", {hint = "amount", min = 1, max = 2147483647, round = true, optional = true, default = 100}) + + :Help("armor_help") + + :OnExecute(function(ply, targets, amount) + for i = 1, #targets do + targets[i]:SetArmor(amount) + end + + sam.player.send_message(nil, "set_armor", { + A = ply, T = targets, V = amount + }) + end) +:End() + +command.new("ignite") + :SetPermission("ignite", "admin") + + :AddArg("player") + :AddArg("number", {hint = "seconds", optional = true, default = 60, round = true}) + + :Help("ignite_help") + + :OnExecute(function(ply, targets, length) + for i = 1, #targets do + local target = targets[i] + + if target:IsOnFire() then + target:Extinguish() + end + + target:Ignite(length) + end + + sam.player.send_message(nil, "ignite", { + A = ply, T = targets, V = length + }) + end) +:End() + +command.new("unignite") + :Aliases("extinguish") + + :SetPermission("ignite", "admin") + + :AddArg("player", {optional = true}) + + :Help("unignite_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i]:Extinguish() + end + + sam.player.send_message(nil, "unignite", { + A = ply, T = targets + }) + end) +:End() + +command.new("god") + :Aliases("invincible") + + :SetPermission("god", "admin") + + :AddArg("player", {optional = true}) + + :Help("god_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + local target = targets[i] + target:GodEnable() + target.sam_has_god_mode = true + end + + sam.player.send_message(nil, "god", { + A = ply, T = targets + }) + end) +:End() + +command.new("ungod") + :Aliases("uninvincible") + + :SetPermission("ungod", "admin") + + :AddArg("player", {optional = true}) + + :Help("ungod_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + local target = targets[i] + target:GodDisable() + target.sam_has_god_mode = nil + end + + sam.player.send_message(nil, "ungod", { + A = ply, T = targets + }) + end) +:End() + +do + command.new("freeze") + :SetPermission("freeze", "admin") + + :AddArg("player") + + :Help("freeze_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + local v = targets[i] + v:ExitVehicle() + if v:sam_get_nwvar("frozen") then + v:UnLock() + end + v:Lock() + v:sam_set_nwvar("frozen", true) + v:sam_set_exclusive("frozen") + end + + sam.player.send_message(nil, "freeze", { + A = ply, T = targets + }) + end) + :End() + + command.new("unfreeze") + :SetPermission("unfreeze", "admin") + + :AddArg("player", {optional = true}) + + :Help("unfreeze_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + local v = targets[i] + v:UnLock() + v:sam_set_nwvar("frozen", false) + v:sam_set_exclusive(nil) + end + + sam.player.send_message(nil, "unfreeze", { + A = ply, T = targets + }) + end) + :End() + + local disallow = function(ply) + if ply:sam_get_nwvar("frozen") then + return false + end + end + + for _, v in ipairs({"SAM.CanPlayerSpawn", "CanPlayerSuicide", "CanTool"}) do + hook.Add(v, "SAM.FreezePlayer." .. v, disallow) + end +end + +command.new("cloak") + :SetPermission("cloak", "admin") + + :AddArg("player", {optional = true}) + + :Help("cloak_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i]:sam_cloak() + end + + sam.player.send_message(nil, "cloak", { + A = ply, T = targets + }) + end) +:End() + +command.new("uncloak") + :SetPermission("uncloak", "admin") + + :AddArg("player", {optional = true}) + + :Help("uncloak_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i]:sam_uncloak() + end + + sam.player.send_message(nil, "uncloak", { + A = ply, T = targets + }) + end) +:End() + +do + local jail_props = { + Vector(0, 0, -5), Angle(90, 0, 0); + Vector(0, 0, 97), Angle(90, 0, 0); + + Vector(21, 31, 46), Angle(0, 90, 0); + Vector(21, -31, 46), Angle(0, 90, 0); + Vector(-21, 31, 46), Angle(0, 90, 0); + Vector(-21, -31, 46), Angle(0, 90, 0); + + Vector(-52, 0, 46), Angle(0, 0, 0); + Vector(52, 0, 46), Angle(0, 0, 0) + } + + local remove_jail = function(ply_jail_props) + for _, jail_prop in ipairs(ply_jail_props) do + if IsValid(jail_prop) then + jail_prop:Remove() + end + end + end + + local unjail = function(ply) + if not IsValid(ply) then return end + if not ply:sam_get_nwvar("jailed") then return end + + remove_jail(ply.sam_jail_props) + + ply.sam_jail_props = nil + ply.sam_jail_pos = nil + + ply:sam_set_nwvar("jailed", nil) + ply:sam_set_exclusive(nil) + + timer.Remove("SAM.Unjail." .. ply:SteamID()) + timer.Remove("SAM.Jail.Watch." .. ply:SteamID()) + end + + local return_false = function() + return false + end + + local function jail(ply, time) + if not IsValid(ply) then return end + if not isnumber(time) or time < 0 then + time = 0 + end + + if ply:sam_get_nwvar("frozen") then + RunConsoleCommand("sam", "unfreeze", "#" .. ply:EntIndex()) + end + + if not ply:sam_get_nwvar("jailed") or (not ply.sam_jail_props or not IsValid(ply.sam_jail_props[1])) then + ply:ExitVehicle() + ply:SetMoveType(MOVETYPE_WALK) + + ply.sam_jail_pos = ply:GetPos() + + ply:sam_set_nwvar("jailed", true) + ply:sam_set_exclusive("in jail") + + if ply.sam_jail_props then + for k, v in ipairs(ply.sam_jail_props) do + if IsValid(v) then + v:Remove() + end + end + end + + local ply_jail_props = {} + for i = 1, #jail_props, 2 do + local jail_prop = ents.Create("prop_physics") + jail_prop:SetModel("models/props_building_details/Storefront_Template001a_Bars.mdl") + jail_prop:SetPos(ply.sam_jail_pos + jail_props[i]) + jail_prop:SetAngles(jail_props[i + 1]) + jail_prop:SetMoveType(MOVETYPE_NONE) + jail_prop:Spawn() + jail_prop:GetPhysicsObject():EnableMotion(false) + jail_prop.CanTool = return_false + jail_prop.PhysgunPickup = return_false + jail_prop.jailWall = true + table.insert(ply_jail_props, jail_prop) + end + ply.sam_jail_props = ply_jail_props + end + + local steamid = ply:SteamID() + + if time == 0 then + timer.Remove("SAM.Unjail." .. steamid) + else + timer.Create("SAM.Unjail." .. steamid, time, 1, function() + if IsValid(ply) then + unjail(ply) + end + end) + end + + timer.Create("SAM.Jail.Watch." .. steamid, 0.5, 0, function() + if not IsValid(ply) then + return timer.Remove("SAM.Jail.Watch." .. steamid) + end + + if ply:GetPos():DistToSqr(ply.sam_jail_pos) > 4900 then + ply:SetPos(ply.sam_jail_pos) + end + + if not IsValid(ply.sam_jail_props[1]) then + jail(ply, timer.TimeLeft("SAM.Unjail." .. steamid) or 0) + end + end) + end + + command.new("jail") + :SetPermission("jail", "admin") + + :AddArg("player") + :AddArg("length", {optional = true, default = 0, min = 0}) + :AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")}) + + :GetRestArgs() + + :Help("jail_help") + + :OnExecute(function(ply, targets, length, reason) + for i = 1, #targets do + jail(targets[i], length * 60) + end + + sam.player.send_message(nil, "jail", { + A = ply, T = targets, V = sam.format_length(length), V_2 = reason + }) + end) + :End() + + command.new("unjail") + :SetPermission("unjail", "admin") + + :AddArg("player", {optional = true}) + + :Help("unjail_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + unjail(targets[i]) + end + + sam.player.send_message(nil, "unjail", { + A = ply, T = targets + }) + end) + :End() + + sam.hook_first("CanProperty", "SAM.Jail", function(_, property, ent) + if ent.jailWall and property == "remover" then + return false + end + end) + + if SERVER then + hook.Add("PlayerSpawn", "SAM.Jail", function(ply) + if ply:sam_get_nwvar("jailed") or ply:sam_get_pdata("jailed") then + if ply.sam_jail_pos then + ply:SetPos(ply.sam_jail_pos) + else + ply:SetPos(ply:sam_get_pdata("jail_pos")) + jail(ply, ply:sam_get_pdata("jail_time_left")) + + ply:sam_set_pdata("jailed", nil) + ply:sam_set_pdata("jail_pos", nil) + ply:sam_set_pdata("jail_time_left", nil) + end + end + end) + + hook.Add("PlayerEnteredVehicle", "SAM.Jail", function(ply) + if ply:sam_get_nwvar("jailed") then + ply:ExitVehicle() + end + end) + + hook.Add("PlayerDisconnected", "SAM.Jail", function(ply) + if ply:sam_get_nwvar("jailed") then + remove_jail(ply.sam_jail_props) + + ply:sam_set_pdata("jailed", true) + ply:sam_set_pdata("jail_pos", ply.sam_jail_pos) + ply:sam_set_pdata("jail_time_left", timer.TimeLeft("SAM.Unjail." .. ply:SteamID()) or 0) + + timer.Remove("SAM.Unjail." .. ply:SteamID()) + timer.Remove("SAM.Jail.Watch." .. ply:SteamID()) + end + end) + end + + local disallow = function(ply) + if ply:sam_get_nwvar("jailed") then + return false + end + end + + for _, v in ipairs({"PlayerNoClip", "SAM.CanPlayerSpawn", "CanPlayerEnterVehicle", "CanPlayerSuicide", "CanTool"}) do + hook.Add(v, "SAM.Jail", disallow) + end +end + +command.new("strip") + :SetPermission("strip", "admin") + + :AddArg("player") + + :Help("strip_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i]:StripWeapons() + end + + sam.player.send_message(nil, "strip", { + A = ply, T = targets + }) + end) +:End() + +command.new("respawn") + :SetPermission("respawn", "admin") + + :AddArg("player", {optional = true}) + + :Help("respawn_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i]:Spawn() + end + + sam.player.send_message(nil, "respawn", { + A = ply, T = targets + }) + end) +:End() + +command.new("setmodel") + :SetPermission("setmodel", "superadmin") + + :AddArg("player") + :AddArg("text", {hint = "model"}) + + :Help("setmodel_help") + + :OnExecute(function(ply, targets, model) + for i = 1, #targets do + targets[i]:SetModel(model) + end + + sam.player.send_message(nil, "setmodel", { + A = ply, T = targets, V = model + }) + end) +:End() + +command.new("giveammo") + :Aliases("ammo") + + :SetPermission("giveammo", "superadmin") + + :AddArg("player") + :AddArg("number", {hint = "amount", min = 0, max = 99999}) + + :Help("giveammo_help") + + :OnExecute(function(ply, targets, amount) + if amount == 0 then + amount = 99999 + end + + for i = 1, #targets do + local target = targets[i] + for _, wep in ipairs(target:GetWeapons()) do + if wep:GetPrimaryAmmoType() ~= -1 then + target:GiveAmmo(amount, wep:GetPrimaryAmmoType(), true) + end + + if wep:GetSecondaryAmmoType() ~= -1 then + target:GiveAmmo(amount, wep:GetSecondaryAmmoType(), true) + end + end + end + + sam.player.send_message(nil, "giveammo", { + A = ply, T = targets, V = amount + }) + end) +:End() + +do + command.new("scale") + :SetPermission("scale", "superadmin") + + :AddArg("player") + :AddArg("number", {hint = "amount", optional = true, min = 0, max = 2.5, default = 1}) + + :Help("scale_help") + + :OnExecute(function(ply, targets, amount) + for i = 1, #targets do + local v = targets[i] + v:SetModelScale(amount) + + -- https://github.com/carz1175/More-ULX-Commands/blob/9b142ee4247a84f16e2dc2ec71c879ab76e145d4/lua/ulx/modules/sh/extended.lua#L313 + v:SetViewOffset(Vector(0, 0, 64 * amount)) + v:SetViewOffsetDucked(Vector(0, 0, 28 * amount)) + + v.sam_scaled = true + end + + sam.player.send_message(nil, "scale", { + A = ply, T = targets, V = amount + }) + end) + :End() + + hook.Add("PlayerSpawn", "SAM.Scale", function(ply) + if ply.sam_scaled then + ply.sam_scaled = nil + ply:SetViewOffset(Vector(0, 0, 64)) + ply:SetViewOffsetDucked(Vector(0, 0, 28)) + end + end) +end + +sam.command.new("freezeprops") + :SetPermission("freezeprops", "admin") + :Help("freezeprops_help") + + :OnExecute(function(ply) + for _, prop in ipairs(ents.FindByClass("prop_physics")) do + local physics_obj = prop:GetPhysicsObject() + if IsValid(physics_obj) then + physics_obj:EnableMotion(false) + end + end + + sam.player.send_message(nil, "freezeprops", { + A = ply + }) + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/murder.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/murder.lua new file mode 100644 index 0000000..3e4c77d --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/murder.lua @@ -0,0 +1,214 @@ +if SAM_LOADED then return end + +local add = not GAMEMODE and hook.Add or function(_, _, fn) + fn() +end + +-- Thanks to https://github.com/boxama/addons/blob/master/addons/ULX_Murder/lua/ulx/modules/sh/murder.lua +add("PostGamemodeLoaded", "SAM.Murder", function() + if GAMEMODE.Author ~= "MechanicalMind" then return end + if not isstring(GAMEMODE.Version) or GAMEMODE.Version < "28" then return end + + local sam, command = sam, sam.command + + command.set_category("Murder") + + local autoslain_players = {} + + command.new("slaynr") + :SetPermission("slaynr", "admin") + + :AddArg("player") + :AddArg("number", {hint = "rounds", optional = true, default = 1, min = 1, max = 100, round = true}) + + :Help("Slays the target(s) at the beggining of the next round.") + + :OnExecute(function(ply, targets, rounds) + for i = 1, #targets do + local v = targets[i] + v.MurdererChance = 0 + + if not v:IsBot() then + autoslain_players[v:AccountID()] = rounds + end + end + + sam.player.send_message(nil, "{A} set {T} to be autoslain for {V} round(s)", { + A = ply, T = targets, V = rounds + }) + end) + :End() + + command.new("unslaynr") + :SetPermission("unslaynr", "admin") + + :AddArg("player") + + :Help("Remove target(s) autoslays.") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + local v = targets[i] + v.MurdererChance = 1 + + if not v:IsBot() then + autoslain_players[v:AccountID()] = nil + end + end + + sam.player.send_message(nil, "Removed all autoslays for {T} ", { + A = ply, T = targets + }) + end) + :End() + + hook.Add("OnStartRound", "SAM.Murder", function() + timer.Simple(3, function() + local players = team.GetPlayers(2) + local targets = {admin = sam.console} + for i = 1, #players do + local v = players[i] + if not v:IsBot() then continue end + + local slays = autoslain_players[v:AccountID()] + if not slays then continue end + + v:Kill() + + slays = slays - 1 + + targets[1] = v + sam.player.send_message(nil, "{A} autoslayed {T}, autoslays left: {V}.", { + A = sam.console, T = targets, V = slays + }) + + autoslain_players[v:AccountID()] = slays > 0 and slays or nil + end + end) + end) + + hook.Add("PlayerInitialSpawn", "SAM.Murder", function(ply) + if autoslain_players[ply:AccountID()] then + ply.MurdererChance = 0 + end + end) + + command.new("respawn") + :SetPermission("respawn", "admin") + + :AddArg("player", {single_target = true}) + + :Help("Respawn a target.") + + :OnExecute(function(ply, targets) + local target = targets[1] + + if target:Team() ~= 2 then + return ply:sam_add_text("You cannot respawn a spectator!") + end + + target:Spectate(OBS_MODE_NONE) + target:Spawn() + + sam.player.send_message(nil, "respawn", { + A = ply, T = targets + }) + end) + :End() + + local get_admins = function() + local admins = {} + + local players = player.GetHumans() + for i = 1, #players do + local v = players[i] + if v:IsAdmin() then + table.insert(admins, v) + end + end + + return admins + end + + command.new("givemagnum") + :SetPermission("givemagnum", "superadmin") + + :AddArg("player", {single_target = true, optional = true}) + + :Help("Give the target a magnum.") + + :OnExecute(function(ply, targets) + local target = targets[1] + + if target:Team() ~= 2 then + return ply:sam_add_text("You cannot give spectator a magnum!") + end + + target:Give("weapon_mu_magnum") + + sam.player.send_message(get_admins(), "{A} gave {T} a {V}", { + A = ply, T = targets, V = "magnum" + }) + end) + :End() + + command.new("giveknife") + :SetPermission("giveknife", "superadmin") + + :AddArg("player", {single_target = true, optional = true}) + + :Help("Give the target a knife.") + + :OnExecute(function(ply, targets) + local target = targets[1] + + if target:Team() ~= 2 then + return ply:sam_add_text("You cannot give spectator a knife!") + end + + target:Give("weapon_mu_knife") + + sam.player.send_message(get_admins(), "{A} gave {T} a {V}", { + A = ply, T = targets, V = "knife" + }) + end) + :End() + + command.new("forcemurderer") + :SetPermission("forcemurderer", "admin") + + :AddArg("player", {single_target = true, optional = true}) + + :Help("Force the target to me a murderer next round.") + + :OnExecute(function(ply, targets) + GAMEMODE.ForceNextMurderer = targets[1] + + sam.player.send_message(get_admins(), "{A} set {T} to be the Murderer next round!", { + A = ply, T = targets + }) + end) + :End() + + command.new("getmurderers") + :SetPermission("getmurderers", "admin") + + :Help("Print all murderers in chat.") + + :OnExecute(function(ply) + local murderers = {admin = ply} + + local players = team.GetPlayers(2) + for i = 1, #players do + local v = players[i] + if v:GetMurderer() then + table.insert(murderers, v) + end + end + + sam.player.send_message(ply, "Murderers are: {T}", { + T = murderers + }) + end) + :End() +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/support_cami.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/support_cami.lua new file mode 100644 index 0000000..50f7851 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/support_cami.lua @@ -0,0 +1,126 @@ +if SAM_LOADED then return end + +local ranks_loaded +if SERVER then + ranks_loaded = sam.ranks.ranks_loaded() +else + ranks_loaded = sam.ranks.get_ranks() ~= nil +end + +do + local load_ranks = function() + for name, rank in pairs(sam.ranks.get_ranks()) do + if not sam.ranks.is_default_rank(name) then + CAMI.RegisterUsergroup({Name = name, Inherits = rank.inherit}, "SAM") + end + end + end + + if ranks_loaded then + load_ranks() + else + hook.Add("SAM.LoadedRanks", "SAM.CAMI.LoadRanksToCAMI", load_ranks) + end +end + +hook.Add("SAM.AddedRank", "SAM.CAMI.AddedRank", function(name, rank) + if not sam.ranks.is_default_rank(name) then + CAMI.RegisterUsergroup({Name = name, Inherits = rank.inherit}, "SAM") + end +end) + +hook.Add("SAM.RemovedRank", "SAM.CAMI.RemovedRank", function(name) + CAMI.UnregisterUsergroup(name, "SAM") +end) + +hook.Add("SAM.RankNameChanged", "SAM.CAMI.RankNameChanged", function(old, new) + CAMI.UnregisterUsergroup(old, "SAM") + CAMI.RegisterUsergroup({Name = new, Inherits = sam.ranks.get_rank(new).inherit}, "SAM") +end) + +hook.Add("SAM.ChangedPlayerRank", "SAM.CAMI.ChangedPlayerRank", function(ply, new_rank, old_rank) + CAMI.SignalUserGroupChanged(ply, old_rank, new_rank, "SAM") +end) + +hook.Add("SAM.ChangedSteamIDRank", "SAM.CAMI.ChangedSteamIDRank", function(steamid, new_rank, old_rank) + CAMI.SignalSteamIDUserGroupChanged(steamid, old_rank, new_rank, "SAM") +end) + +---------------------------------------------------------------------------------------------------------------------------------------------------------- + +if SERVER then + do + local on_user_group_registered = function(rank, source) + if source ~= "SAM" then + sam.ranks.add_rank(rank.Name, sam.ranks.is_rank(rank.Inherits) and rank.Inherits or "user") + end + end + + local load_ranks = function() + for _, rank in pairs(CAMI.GetUsergroups()) do + on_user_group_registered(rank, "CAMI") + end + hook.Add("CAMI.OnUsergroupRegistered", "SAM.CAMI.OnUsergroupRegistered", on_user_group_registered) + end + + if ranks_loaded then + load_ranks() + else + hook.Add("SAM.LoadedRanks", "SAM.CAMI.LoadRanksFromCAMI", load_ranks) + end + end + + hook.Add("CAMI.OnUsergroupUnregistered", "SAM.CAMI.OnUsergroupUnregistered", function(rank, source) + if source ~= "SAM" then + sam.ranks.remove_rank(rank.Name) + end + end) + + hook.Add("CAMI.PlayerUsergroupChanged", "SAM.CAMI.PlayerUsergroupChanged", function(ply, _, new_rank, source) + if ply and IsValid(ply) and source ~= "SAM" then + sam.player.set_rank(ply, new_rank) + end + end) + + hook.Add("CAMI.SteamIDUsergroupChanged", "SAM.CAMI.SteamIDUsergroupChanged", function(steamid, _, new_rank, source) + if sam.is_steamid(steamid) and source ~= "SAM" then + sam.player.set_rank_id(steamid, new_rank) + end + end) +end + +do + local on_privilege_registered = function(privilege) + sam.permissions.add(privilege.Name, "CAMI", privilege.MinAccess) + end + + local load_privileges = function() + for _, privilege in pairs(CAMI.GetPrivileges()) do + on_privilege_registered(privilege) + end + hook.Add("CAMI.OnPrivilegeRegistered", "SAM.CAMI.OnPrivilegeRegistered", on_privilege_registered) + end + + if ranks_loaded then + load_privileges() + else + hook.Add("SAM.LoadedRanks", "SAM.CAMI.LoadPrivileges", load_privileges) + end +end + +hook.Add("CAMI.OnPrivilegeUnregistered", "SAM.CAMI.OnPrivilegeUnregistered", function(privilege) + sam.permissions.remove(privilege.Name) +end) + +hook.Add("CAMI.PlayerHasAccess", "SAM.CAMI.PlayerHasAccess", function(ply, privilege, callback, target) + if sam.type(ply) ~= "Player" then return end + + local has_permission = ply:HasPermission(privilege) + if sam.type(target) == "Player" then + callback(has_permission and ply:CanTarget(target)) + else + callback(has_permission) + end + + return true +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/teleport.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/teleport.lua new file mode 100644 index 0000000..b484d54 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/teleport.lua @@ -0,0 +1,188 @@ +if SAM_LOADED then return end + +local sam, command, language = sam, sam.command, sam.language + +command.set_category("Teleport") + +local find_empty_pos -- https://github.com/FPtje/DarkRP/blob/b147d6fa32799136665a9fd52d35c2fe87cf7f78/gamemode/modules/base/sv_util.lua#L149 +do + local is_empty = function(vector, ignore) + 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 ents_found = ents.FindInSphere(vector, 35) + for i = 1, #ents_found do + local v = ents_found[i] + if (v:IsNPC() or v:IsPlayer() or v:GetClass() == "prop_physics" or v.NotEmptyPos) and v ~= ignore then + return false + end + end + + return true + end + + local distance, step, area = 600, 30, Vector(16, 16, 64) + local north_vec, east_vec, up_vec = Vector(0, 0, 0), Vector(0, 0, 0), Vector(0, 0, 0) + + find_empty_pos = function(pos, ignore) + if is_empty(pos, ignore) and is_empty(pos + area, ignore) then + return pos + end + + for j = step, distance, step do + for i = -1, 1, 2 do + local k = j * i + + -- North/South + north_vec.x = k + if is_empty(pos + north_vec, ignore) and is_empty(pos + north_vec + area, ignore) then + return pos + north_vec + end + + -- East/West + east_vec.y = k + if is_empty(pos + east_vec, ignore) and is_empty(pos + east_vec + area, ignore) then + return pos + east_vec + end + + -- Up/Down + up_vec.z = k + if is_empty(pos + up_vec, ignore) and is_empty(pos + up_vec + area, ignore) then + return pos + up_vec + end + end + end + + return pos + end +end + +command.new("bring") + :DisallowConsole() + :SetPermission("bring", "admin") + + :AddArg("player", {cant_target_self = true}) + + :Help("bring_help") + + :OnExecute(function(ply, targets) + if not ply:Alive() then + return ply:sam_send_message("dead") + end + + if ply:InVehicle() then + return ply:sam_send_message("leave_car") + end + + if ply:sam_get_exclusive(ply) then + return ply:sam_send_message(ply:sam_get_exclusive(ply)) + end + + local teleported = {admin = ply} + local all = targets.input == "*" + + for i = 1, #targets do + local target = targets[i] + + if target:sam_get_exclusive(ply) then + if not all then + ply:sam_send_message(target:sam_get_exclusive(ply)) + end + continue + end + + if not target:Alive() then + target:Spawn() + end + + target.sam_tele_pos, target.sam_tele_ang = target:GetPos(), target:EyeAngles() + + target:ExitVehicle() + target:SetVelocity(Vector(0, 0, 0)) + target:SetPos(find_empty_pos(ply:GetPos(), target)) + target:SetEyeAngles((ply:EyePos() - target:EyePos()):Angle()) + + table.insert(teleported, target) + end + + if #teleported > 0 then + sam.player.send_message(nil, "bring", { + A = ply, T = teleported + }) + end + end) +:End() + +command.new("goto") + :DisallowConsole() + :SetPermission("goto", "admin") + + :AddArg("player", {single_target = true, allow_higher_target = true, cant_target_self = true}) + + :Help("goto_help") + + :OnExecute(function(ply, targets) + if ply:sam_get_exclusive(ply) then + return ply:sam_send_message(ply:sam_get_exclusive(ply)) + end + + if not ply:Alive() then + ply:Spawn() + end + + local target = targets[1] + ply.sam_tele_pos, ply.sam_tele_ang = ply:GetPos(), ply:EyeAngles() + + ply:ExitVehicle() + ply:SetVelocity(Vector(0, 0, 0)) + ply:SetPos(find_empty_pos(target:GetPos(), ply)) + ply:SetEyeAngles((target:EyePos() - ply:EyePos()):Angle()) + + sam.player.send_message(nil, "goto", { + A = ply, T = targets + }) + end) +:End() + +command.new("return") + :SetPermission("return", "admin") + + :AddArg("player", {single_target = true, optional = true}) + + :Help("return_help") + + :OnExecute(function(ply, targets) + local target = targets[1] + + local last_pos, last_ang = target.sam_tele_pos, target.sam_tele_ang + if not last_pos then + return sam.player.send_message(ply, "no_location", { + T = targets + }) + end + + if target:sam_get_exclusive(ply) then + return ply:sam_send_message(target:sam_get_exclusive(ply)) + end + + if not target:Alive() then + return ply:sam_send_message(target:Name() .. " is dead!") + end + + target:ExitVehicle() + target:SetVelocity(Vector(0, 0, 0)) + target:SetPos(last_pos) + target:SetEyeAngles(last_ang) + + target.sam_tele_pos, target.sam_tele_ang = nil, nil + + sam.player.send_message(nil, "returned", { + A = ply, T = targets + }) + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/ttt.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/ttt.lua new file mode 100644 index 0000000..a5a2027 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/ttt.lua @@ -0,0 +1,92 @@ +if SAM_LOADED then return end + +local run = function(fn) + if not GAMEMODE then + timer.Simple(0, fn) + else + fn() + end +end + +run(function() + if engine.ActiveGamemode() ~= "terrortown" then return end + + local sam, command, language = sam, sam.command, sam.language + + command.set_category("TTT") + + command.new("setslays") + :SetPermission("setslays", "admin") + + :AddArg("player", {single_target = true}) + :AddArg("number", {hint = "amount", optional = true, min = 1, default = 1, round = true}) + + :Help("setslays_help") + + :OnExecute(function(ply, targets, amount) + targets[1]:sam_set_pdata("slays_amount", amount) + + sam.player.send_message(nil, "setslays", { + A = ply, T = targets, V = amount + }) + end) + :End() + + command.new("removeslays") + :SetPermission("removeslays", "admin") + + :AddArg("player", {single_target = true}) + + :Help("removeslays_help") + + :OnExecute(function(ply, targets, amount) + local target = targets[1] + target:sam_set_pdata("slays_amount", nil) + target:SetForceSpec(false) + + sam.player.send_message(nil, "removeslays", { + A = ply, T = targets + }) + end) + :End() + + OldBeginRound = OldBeginRound or BeginRound + function BeginRound(...) + local players = player.GetAll() + for i = 1, #players do + local ply = players[i] + + local slays = ply:sam_get_pdata("slays_amount") + if not slays then continue end + + if not ply:IsSpec() then + ply:Kill() + end + + GAMEMODE:PlayerSpawnAsSpectator(ply) + + ply:SetTeam(TEAM_SPEC) + ply:SetForceSpec(true) + ply:Spawn() + + ply:SetRagdollSpec(false) -- dying will enable this, we don't want it here + + slays = slays - 1 + + if slays == 0 then + timer.Simple(0, function() + ply:SetForceSpec(false) + end) + ply:sam_set_pdata("slays_amount", nil) + else + ply:sam_set_pdata("slays_amount", slays) + end + + sam.player.send_message(nil, "setslays_slayed", { + T = {ply}, V = slays + }) + end + + return OldBeginRound(...) + end +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/user.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/user.lua new file mode 100644 index 0000000..5f59e0b --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/user.lua @@ -0,0 +1,263 @@ +if SAM_LOADED then return end + +local sam, command, language = sam, sam.command, sam.language + +command.set_category("User Management") + +command.new("setrank") + :Aliases("adduser", "changerank", "giverank") + + :SetPermission("setrank") + + :AddArg("player", {single_target = true}) + :AddArg("rank", {check = function(rank, ply) + return ply:CanTargetRank(rank) + end}) + :AddArg("length", {optional = true, default = 0}) + + :Help("setrank_help") + + :OnExecute(function(ply, targets, rank, length) + targets[1]:sam_set_rank(rank, length) + + sam.player.send_message(nil, "setrank", { + A = ply, T = targets, V = rank, V_2 = sam.format_length(length) + }) + end) +:End() + +command.new("setrankid") + :Aliases("adduserid", "changerankid", "giverankid") + + :SetPermission("setrankid") + + :AddArg("steamid") + :AddArg("rank", {check = function(rank, ply) + return ply:CanTargetRank(rank) + end}) + :AddArg("length", {optional = true, default = 0}) + + :Help("setrankid_help") + + :OnExecute(function(ply, promise, rank, length) + local a_name = ply:Name() + + promise:done(function(data) + local steamid, target = data[1], data[2] + if target then + target:sam_set_rank(rank, length) + + sam.player.send_message(nil, "setrank", { + A = ply, T = {target, admin = ply}, V = rank, V_2 = sam.format_length(length) + }) + else + sam.player.set_rank_id(steamid, rank, length) + + sam.player.send_message(nil, "setrank", { + A = a_name, T = steamid, V = rank, V_2 = sam.format_length(length) + }) + end + end) + end) +:End() + +command.new("addrank") + :SetPermission("manage_ranks") + + :AddArg("text", {hint = "rank name", check = function(rank) + return not sam.ranks.is_rank(rank) + end}) + :AddArg("rank", {hint = "inherit from"}) + :AddArg("number", {hint = "immunity", min = 2, max = 99, optional = true}) + :AddArg("length", {hint = "ban limit", optional = true}) + + :Help("addrank_help") + + :MenuHide() + + :OnExecute(function(ply, rank, inherit, immunity, ban_limit) + sam.ranks.add_rank(rank, inherit, immunity, ban_limit) + + sam.player.send_message(nil, "addrank", { + A = ply, V = rank + }) + end) +:End() + +command.new("removerank") + :SetPermission("manage_ranks") + + :AddArg("rank", {check = function(rank) + return not sam.ranks.is_default_rank(rank) + end}) + + :Help("removerank_help") + + :MenuHide() + + :OnExecute(function(ply, rank) + sam.ranks.remove_rank(rank) + + sam.player.send_message(nil, "removerank", { + A = ply, V = rank + }) + end) +:End() + +command.new("renamerank") + :SetPermission("manage_ranks") + + :AddArg("rank", {check = function(rank) + return not sam.ranks.is_default_rank(rank) + end}) + :AddArg("text", {hint = "new name", check = function(rank) + return not sam.ranks.is_rank(rank) + end}) + + :Help("renamerank_help") + + :MenuHide() + + :OnExecute(function(ply, rank, new_name) + sam.ranks.rename_rank(rank, new_name) + + sam.player.send_message(nil, "renamerank", { + A = ply, T = rank, V = new_name + }) + end) +:End() + +command.new("changeinherit") + :SetPermission("manage_ranks") + + :AddArg("rank", {check = function(rank) + return rank ~= "user" and rank ~= "superadmin" + end}) + :AddArg("rank", {hint = "inherits from"}) + + :Help("changeinherit_help") + + :MenuHide() + + :OnExecute(function(ply, rank, inherit) + if rank == inherit then return end + + sam.ranks.change_inherit(rank, inherit) + + sam.player.send_message(nil, "changeinherit", { + A = ply, T = rank, V = inherit + }) + end) +:End() + +command.new("changerankimmunity") + :SetPermission("manage_ranks") + + :AddArg("rank", {check = function(rank) + return rank ~= "user" and rank ~= "superadmin" + end}) + :AddArg("number", {hint = "new immunity", min = 2, max = 99}) + + :Help("changerankimmunity_help") + + :MenuHide() + + :OnExecute(function(ply, rank, new_immunity) + sam.ranks.change_immunity(rank, new_immunity) + + sam.player.send_message(nil, "rank_immunity", { + A = ply, T = rank, V = new_immunity + }) + end) +:End() + +command.new("changerankbanlimit") + :SetPermission("manage_ranks") + + :AddArg("rank", {check = function(rank) + return rank ~= "superadmin" + end}) + :AddArg("length") + + :Help("changerankbanlimit_help") + + :MenuHide() + + :OnExecute(function(ply, rank, new_limit) + sam.ranks.change_ban_limit(rank, new_limit) + + sam.player.send_message(nil, "rank_ban_limit", { + A = ply, T = rank, V = sam.format_length(new_limit) + }) + end) +:End() + +command.new("givepermission") + :SetPermission("manage_ranks") + + :AddArg("rank") + :AddArg("text", {hint = "permission"}) + + :Help("givepermission_help") + + :MenuHide() + + :OnExecute(function(ply, rank, permission) + if rank == "superadmin" then + return ply:sam_send_message("super_admin_access") + end + + sam.ranks.give_permission(rank, permission) + + sam.player.send_message(nil, "giveaccess", { + A = ply, V = permission, T = rank + }) + end) +:End() + +command.new("takepermission") + :SetPermission("manage_ranks") + + :AddArg("rank") + :AddArg("text", {hint = "permission"}) + + :Help("takepermission_help") + + :MenuHide() + + :OnExecute(function(ply, rank, permission) + if rank == "superadmin" then + return ply:sam_send_message("super_admin_access") + end + + sam.ranks.take_permission(rank, permission) + + sam.player.send_message(nil, "takeaccess", { + A = ply, V = permission, T = rank + }) + end) +:End() + +command.new("changeranklimit") + :SetPermission("manage_ranks") + + :AddArg("rank") + :AddArg("text", {hint = "limit"}) + :AddArg("number", {hint = "value"}) + + :Help("changeranklimit_help") + + :MenuHide() + + :OnExecute(function(ply, rank, limit, value) + if rank == "superadmin" then + return ply:sam_send_message("super_admin_access") + end + + sam.ranks.set_limit(rank, limit, value) + + sam.player.send_message(nil, "changeranklimit", { + A = ply, T = rank, V = limit, V_2 = value + }) + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/util.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/util.lua new file mode 100644 index 0000000..f8bc2e6 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/util.lua @@ -0,0 +1,569 @@ +if SAM_LOADED then return end + +local sam, command, language = sam, sam.command, sam.language + +command.set_category("Utility") + +command.new("map") + :SetPermission("map", "admin") + + :AddArg("map") + :AddArg("text", {hint = "gamemode", optional = true, check = sam.is_valid_gamemode}) + + :Help("map_help") + + :OnExecute(function(ply, map, gamemode) + if not gamemode then + sam.player.send_message(nil, "map_change", { + A = ply, V = map + }) + else + sam.player.send_message(nil, "map_change2", { + A = ply, V = map, V_2 = gamemode + }) + RunConsoleCommand("gamemode", gamemode) + end + + if #player.GetHumans() == 0 then + RunConsoleCommand("changelevel", map) + else + timer.Create("SAM.Command.Map", 10, 1, function() + RunConsoleCommand("changelevel", map) + end) + end + end) +:End() + +command.new("maprestart") + :SetPermission("maprestart", "admin") + + :Help("map_restart_help") + + :OnExecute(function(ply) + if #player.GetHumans() == 0 then + RunConsoleCommand("changelevel", game.GetMap()) + else + timer.Create("SAM.Command.MapRestart", 10, 1, function() + RunConsoleCommand("changelevel", game.GetMap()) + end) + + sam.player.send_message(nil, "map_restart", { + A = ply + }) + end + end) +:End() + +command.new("mapreset") + :SetPermission("mapreset", "admin") + + :Help("mapreset_help") + + :OnExecute(function(ply) + game.CleanUpMap() + + sam.player.send_message(nil, "mapreset", { + A = ply + }) + end) +:End() + +command.new("kick") + :SetPermission("kick", "admin") + + :AddArg("player", {single_target = true}) + :AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")}) + + :GetRestArgs() + + :Help("kick_help") + + :OnExecute(function(ply, targets, reason) + local target = targets[1] + target:Kick(reason) + + sam.player.send_message(nil, "kick", { + A = ply, T = target:Name(), V = reason + }) + end) +:End() + +command.new("ban") + :SetPermission("ban", "admin") + + :AddArg("player", {single_target = true}) + :AddArg("length", {optional = true, default = 0}) + :AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")}) + + :GetRestArgs() + + :Help("ban_help") + + :OnExecute(function(ply, targets, length, reason) + local target = targets[1] + if ply:GetBanLimit() ~= 0 then + if length == 0 then + length = ply:GetBanLimit() + else + length = math.Clamp(length, 1, ply:GetBanLimit()) + end + end + target:sam_ban(length, reason, ply:SteamID()) + + sam.player.send_message(nil, "ban", { + A = ply, T = target:Name(), V = sam.format_length(length), V_2 = reason + }) + end) +:End() + +command.new("banid") + :SetPermission("banid", "admin") + + :AddArg("steamid") + :AddArg("length", {optional = true, default = 0}) + :AddArg("text", {hint = "reason", optional = true, default = sam.language.get("default_reason")}) + + :GetRestArgs() + + :Help("banid_help") + + :OnExecute(function(ply, promise, length, reason) + local a_steamid, a_name, a_ban_limit = ply:SteamID(), ply:Name(), ply:GetBanLimit() + + promise:done(function(data) + local steamid, target = data[1], data[2] + + if a_ban_limit ~= 0 then + if length == 0 then + length = a_ban_limit + else + length = math.Clamp(length, 1, a_ban_limit) + end + end + + if target then + target:sam_ban(length, reason, a_steamid) + + sam.player.send_message(nil, "ban", { + A = a_name, T = target:Name(), V = sam.format_length(length), V_2 = reason + }) + else + sam.player.ban_id(steamid, length, reason, a_steamid) + + sam.player.send_message(nil, "banid", { + A = a_name, T = steamid, V = sam.format_length(length), V_2 = reason + }) + end + end) + end) +:End() + +command.new("unban") + :SetPermission("unban", "admin") + + :AddArg("steamid", {allow_higher_target = true}) + + :Help("unban_help") + + :OnExecute(function(ply, steamid, reason) + sam.player.unban(steamid, ply:SteamID()) + + sam.player.send_message(nil, "unban", { + A = ply, T = steamid + }) + end) +:End() + +do + command.new("noclip") + :SetPermission("noclip", "admin") + + :AddArg("player", {optional = true}) + + :Help("noclip_help") + + :OnExecute(function(ply, targets) + local id + for i = 1, #targets do + local v = targets[i] + v:SetMoveType(v:GetMoveType() == MOVETYPE_WALK and MOVETYPE_NOCLIP or MOVETYPE_WALK) + if v == ply then + id = i + end + end + + if id then + table.remove(targets, id) + if #targets == 0 then return end + end + + sam.player.send_message(nil, "noclip", { + A = ply, T = targets + }) + end) + :End() + + sam.permissions.add("can_noclip", nil, "admin") + + hook.Add("PlayerNoClip", "SAM.CanNoClip", function(ply) + if ply:HasPermission("can_noclip") then + return true + end + end) +end + +do + local config = sam.config + + sam.permissions.add("can_physgun_players", nil, "admin") + + if CLIENT then + local add_setting = function(body, title, key) + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel(title) + + local enable = setting:Add("SAM.ToggleButton") + enable:SetConfig(key, true) + + return setting + end + + config.add_menu_setting("Physgun", function(body) + local setting_body + + do + local p = add_setting(body, "Physgun (Enable/Disable all physgun features except picking up players)", "Physgun.Enabled") + p:DockMargin(8, 6, 8, 0) + end + + setting_body = body:Add("Panel") + setting_body:Dock(TOP) + setting_body:DockMargin(8, 6, 8, 0) + setting_body:DockPadding(8, 0, 8, 0) + + add_setting(setting_body, "No fall damage on drop", "Physgun.NoFallDamageOnDrop") + add_setting(setting_body, "Right click to freeze players", "Physgun.RightClickToFreeze") + add_setting(setting_body, "Reset Velocity to fix some issues when players fall", "Physgun.ResetVelocity") + + function setting_body:PerformLayout() + setting_body:SizeToChildren(false, true) + end + end) + end + + local freeze_player = function(ply) + if SERVER then + ply:Lock() + end + ply:SetMoveType(MOVETYPE_NONE) + ply:SetCollisionGroup(COLLISION_GROUP_WORLD) + end + + sam.hook_first("PhysgunPickup", "SAM.CanPhysgunPlayer", function(ply, target) + if sam.type(target) == "Player" and ply:HasPermission("can_physgun_players") and ply:CanTarget(target) then + freeze_player(target) + return true + end + end) + + local load_phygun_settings = function() + hook.Remove("PhysgunDrop", "SAM.PhysgunDrop") + hook.Remove("OnPlayerHitGround", "SAM.PhysgunDropOnPlayerHitGround") + + if config.get("Physgun.Enabled", true) == false then + return + end + + local right_click_to_freeze = config.get("Physgun.RightClickToFreeze", true) + local reset_velocity = config.get("Physgun.ResetVelocity", true) + hook.Add("PhysgunDrop", "SAM.PhysgunDrop", function(ply, target) + if sam.type(target) ~= "Player" then return end + + if right_click_to_freeze and ply:KeyPressed(IN_ATTACK2) then + freeze_player(target) + + if SERVER then + target:sam_set_nwvar("frozen", true) + target:sam_set_exclusive("frozen") + end + else + if reset_velocity then + target:SetLocalVelocity(Vector(0, 0, 0)) + end + + if SERVER then + target:UnLock() + target:sam_set_nwvar("frozen", false) + target:sam_set_exclusive(nil) + + if target.sam_has_god_mode then + target:GodEnable() + end + + target.sam_physgun_drop_was_frozen = not target:IsOnGround() + end + + target:SetMoveType(MOVETYPE_WALK) + target:SetCollisionGroup(COLLISION_GROUP_PLAYER) + end + end) + + if config.get("Physgun.NoFallDamageOnDrop", true) then + hook.Add("OnPlayerHitGround", "SAM.PhysgunDropOnPlayerHitGround", function(ply) + if ply.sam_physgun_drop_was_frozen then + ply.sam_physgun_drop_was_frozen = false + return true + end + end) + end + end + + config.hook({"Physgun.Enabled", "Physgun.RightClickToFreeze", "Physgun.ResetVelocity", "Physgun.NoFallDamageOnDrop"}, load_phygun_settings) +end + +do + command.new("cleardecals") + :SetPermission("cleardecals", "admin") + :Help("cleardecals_help") + + :OnExecute(function(ply) + sam.netstream.Start(nil, "cleardecals") + + sam.player.send_message(nil, "cleardecals", { + A = ply + }) + end) + :End() + + if CLIENT then + sam.netstream.Hook("cleardecals", function() + game.RemoveRagdolls() + RunConsoleCommand("r_cleardecals") + end) + end +end + +do + command.new("stopsound") + :SetPermission("stopsound", "admin") + :Help("stopsound_help") + + :OnExecute(function(ply) + sam.netstream.Start(nil, "stopsound") + + sam.player.send_message(nil, "stopsound", { + A = ply + }) + end) + :End() + + if CLIENT then + sam.netstream.Hook("stopsound", function() + RunConsoleCommand("stopsound") + end) + end +end + +command.new("exit") + :SetPermission("exit_vehicle", "admin") + + :AddArg("player", {single_target = true}) + + :Help("exit_vehicle_help") + + :OnExecute(function(ply, targets) + local target = targets[1] + + if not target:InVehicle() then + if ply == target then + return ply:sam_send_message("not_in_vehicle") + else + return ply:sam_send_message("not_in_vehicle2", { + S = target:Name() + }) + end + end + + target:ExitVehicle() + + sam.player.send_message(nil, "exit_vehicle", { + A = ply, T = targets + }) + end) +:End() + +command.new("time") + :SetPermission("time", "user") + + :AddArg("player", {single_target = true, optional = true}) + + :Help("time_help") + + :OnExecute(function(ply, targets) + if ply == targets[1] then + sam.player.send_message(ply, "time_your", { + V = sam.reverse_parse_length(targets[1]:sam_get_play_time() / 60) + }) + else + sam.player.send_message(ply, "time_player", { + T = targets, V = sam.reverse_parse_length(targets[1]:sam_get_play_time() / 60) + }) + end + end) +:End() + +command.new("admin") + :SetPermission("admin_mode", "admin") + + :Help("admin_help") + + :OnExecute(function(ply) + ply:sam_cloak() + ply:GodEnable() + ply:SetMoveType(MOVETYPE_NOCLIP) + end) +:End() + +command.new("unadmin") + :SetPermission("admin_mode", "admin") + + :Help("unadmin_help") + + :OnExecute(function(ply) + ply:sam_uncloak() + ply:GodDisable() + ply:SetMoveType(MOVETYPE_WALK) + end) +:End() + +do + command.new("buddha") + :SetPermission("buddha", "admin") + + :AddArg("player", {optional = true}) + + :Help("buddha_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i].sam_buddha = true + end + + sam.player.send_message(nil, "buddha", { + A = ply, T = targets + }) + end) + :End() + + command.new("unbuddha") + :SetPermission("buddha", "admin") + + :AddArg("player", {optional = true}) + + :Help("unbuddha_help") + + :OnExecute(function(ply, targets) + for i = 1, #targets do + targets[i].sam_buddha = nil + end + + sam.player.send_message(nil, "unbuddha", { + A = ply, T = targets + }) + end) + :End() + + if SERVER then + hook.Add("EntityTakeDamage", "SAM.BuddhaMode", function(ply, info) + if ply.sam_buddha and ply:Health() - info:GetDamage() <= 0 then + ply:SetHealth(1) + return true + end + end) + end +end + +command.new("give") + :SetPermission("give", "superadmin") + + :AddArg("player") + :AddArg("text", {hint = "weapon/entity"}) + + :Help("give_help") + + :OnExecute(function(ply, targets, weapon) + for i = 1, #targets do + targets[i]:Give(weapon) + end + + sam.player.send_message(nil, "give", { + A = ply, T = targets, V = weapon + }) + end) +:End() + +-- do +-- if CLIENT then +-- sam.netstream.Hook("GetFriends", function() +-- local friends = {} +-- local humans = player.GetHumans() +-- for i = 1, #humans do +-- local human = humans[i] +-- if human:GetFriendStatus() == "friend" then +-- table.insert(friends, human) +-- end +-- end +-- netstream.Start("GetFriends", friends) +-- end) +-- else +-- hook.Add("SAM.AuthedPlayer", "GetPlayerFriends", function(ply) +-- timer.Simple(0, function() +-- ply.sam_requesting_friends = true +-- netstream.Start(ply, "GetFriends") +-- end) +-- end) + +-- local invalid_friends = function(ply, friends, new_list) +-- if not sam.istable(friends) then return true end + +-- local count = #friends +-- local max_players = game.MaxPlayers() +-- for k, friend in pairs(friends) do +-- if not sam.isnumber(k) then return true end +-- if not sam.isentity(friend) then return true end +-- if k > max_players then return true end +-- if k > count then return true end + +-- if IsValid(friend) then +-- table.insert(new_list, friend) +-- end +-- end +-- end + +-- sam.netstream.Hook("GetFriends", function(ply, friends) +-- local new_list = {} +-- if invalid_friends(ply, friends, new_list) then +-- ply.sam_friends_invalid = true +-- return +-- end +-- ply.sam_friends = new_list +-- end, function() +-- return ply.sam_requesting_friends +-- end) +-- end + +-- command.new("friends") +-- :SetPermission("friends", "superadmin") + +-- :AddArg("player", {single_target = true}) + +-- :Help(language.get("friends_help")) + +-- :OnExecute(function(ply, targets) +-- local target = targets[1] +-- target.sam_friends_requests = target.sam_friends_requests or {} +-- target.sam_friends_requests[ply] = true +-- end) +-- :End() +-- end diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/utime.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/utime.lua new file mode 100644 index 0000000..eea2f3f --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/utime.lua @@ -0,0 +1,34 @@ +if SAM_LOADED then return end + +local PLAYER = FindMetaTable("Player") + +function PLAYER:GetUTime() + return self:sam_get_nwvar("TotalUTime") +end + +function PLAYER:SetUTime(time) + self:sam_set_nwvar("TotalUTime", time) +end + +function PLAYER:GetUTimeStart() + return self:sam_get_nwvar("UTimeStart") +end + +function PLAYER:SetUTimeStart(time) + self:sam_set_nwvar("UTimeStart", time) +end + +function PLAYER:GetUTimeSessionTime() + return CurTime() - self:GetUTimeStart() +end + +function PLAYER:GetUTimeTotalTime() + return self:GetUTime() + CurTime() - self:GetUTimeStart() +end + +if SERVER then + hook.Add("SAM.AuthedPlayer", "SAM.UTime", function(ply) + ply:SetUTime(ply:sam_get_play_time()) + ply:SetUTimeStart(CurTime()) + end) +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/modules/vote.lua b/garrysmod/addons/module_adminmod/lua/sam/modules/vote.lua new file mode 100644 index 0000000..8dd147b --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/modules/vote.lua @@ -0,0 +1,476 @@ +if SAM_LOADED then return end + +-- DONT EVER TALK TO ME ABOUT THIS CODE + +local sam, command = sam, sam.command + +command.set_category("Voting") + +local start_vote, end_vote +if SERVER then + local vote_on = false + local options, players_voted + + local shuffle = function(tbl) -- https://gist.github.com/Uradamus/10323382 + for i = #tbl, 2, -1 do + local j = math.random(i) + tbl[i], tbl[j] = tbl[j], tbl[i] + end + return tbl + end + + end_vote = function(ply, callback) + if not vote_on then + return ply:sam_add_text(color_white, "There is no vote to end.") + end + + vote_on = false + + sam.set_global("Vote", nil) + + if callback then + local tbl = {} + local total_count = 0 + + for i = 1, #options do + local count = sam.get_global("Votings" .. i) + total_count = total_count + count + table.insert(tbl, {i, count}) + sam.set_global("Votings" .. i, nil) + end + + if total_count == 0 then + return sam.player.add_text(nil, color_white, "The vote have been canceled because nobody voted.") + end + + table.sort(shuffle(tbl), function(a,b) return a[2] > b[2] end) + + local v = tbl[1] + local winner, count = v[1], v[2] + + callback(winner, options[winner], count, total_count) + else + for i = 1, #options do + sam.set_global("Votings" .. i, nil) + end + end + + options, players_voted = nil, nil + + timer.Remove("SAM.Vote") + end + + start_vote = function(ply, callback, title, ...) + if vote_on then + return ply:sam_add_text(color_white, "There is an active vote, wait for it to finish.") + end + + vote_on = true + + options, players_voted = {}, {} + + local args, n = {...}, select("#", ...) + for i = 1, n do + local v = args[i] + if v then + table.insert(options, v) + end + end + + sam.set_global("Vote", {title, options, CurTime()}) + + for k in ipairs(options) do + sam.set_global("Votings" .. k, 0) + end + + timer.Create("SAM.Vote", 25, 1, function() + end_vote(ply, callback) + end) + + return true + end + + sam.netstream.Hook("Vote", function(ply, index) + if not sam.isnumber(index) or index > 5 then return end + + local votings = sam.get_global("Votings" .. index) + if not votings then return end + + local old_index = players_voted[ply:AccountID()] + if old_index == index then return end + + if old_index then + sam.set_global("Votings" .. old_index, sam.get_global("Votings" .. old_index) - 1) + end + + sam.set_global("Votings" .. index, votings + 1) + + players_voted[ply:AccountID()] = index + end) +end + +if CLIENT then + local SUI = sam.SUI + -- https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/includes/extensions/client/player.lua + + local VOTING_TITLE = SUI.CreateFont("VotingTitle", "Roboto Bold", 15) + local VOTING_OPTION = SUI.CreateFont("VotingTitle", "Roboto Medium", 14) + + local bind_translation = {} + for i = 0, 9 do + bind_translation["slot" .. i] = i + end + + local voting_frame + + end_vote = function() + if IsValid(voting_frame) then + voting_frame:Remove() + end + hook.Remove("PlayerBindPress", "SAM.Voting") + hook.Remove("SAM.ChangedGlobalVar", "SAM.VotingCount") + end + + hook.Add("SAM.ChangedGlobalVar", "Voting", function(key, value) + if key ~= "Vote" then return end + + if not value then + end_vote() + return + end + + local title, options, start_time = value[1], value[2], value[3] + + sui.TDLib.Start() + + voting_frame = vgui.Create("EditablePanel") + voting_frame:SetSize(SUI.Scale(165), SUI.Scale(230)) + voting_frame:SetPos(5, ScrH() * 0.25) + voting_frame:DockPadding(4, 4, 4, 4) + voting_frame:Blur() + :Background(Color(30, 30, 30, 240)) + + local voting_title = voting_frame:Add("SAM.Label") + voting_title:Dock(TOP) + voting_title:SetFont(VOTING_TITLE) + voting_title:TextColor(Color(220, 220, 220)) + voting_title:SetText(title) + voting_title:SetWrap(true) + voting_title:SetAutoStretchVertical(true) + + local line = voting_frame:Add("SAM.Label") + line:Dock(TOP) + line:TextColor(Color(220, 220, 220)) + line:SetText("-") + + local options_added = {} + for i, v in ipairs(options) do + local option = voting_frame:Add("SAM.Label") + option:Dock(TOP) + option:SetFont(VOTING_OPTION) + option:TextColor(Color(220, 220, 220), true) + option:SetText(i .. ". " .. v .. " (0)") + option:SetWrap(true) + option:SetAutoStretchVertical(true) + + options_added[i] = option + end + + function voting_frame:Think() -- fucking gmod + self:SizeToChildren(false, true) + + local time_left = math.floor(25 - (CurTime() - start_time)) + if time_left <= 0 then + end_vote() + voting_frame.Think = nil + return + end + + voting_title:SetText(title .. " (" .. time_left .. ")") + end + + line = voting_frame:Add("SAM.Label") + line:Dock(TOP) + line:TextColor(Color(220, 220, 220)) + line:SetText("-") + + local option = voting_frame:Add("SAM.Label") + option:Dock(TOP) + option:SetFont(VOTING_OPTION) + option:TextColor(Color(220, 220, 220), true) + option:SetText("0. Close") + option:SetWrap(true) + option:SetAutoStretchVertical(true) + + sui.TDLib.End() + + local current_index + hook.Add("PlayerBindPress", "SAM.Voting", function(_, bind, down) + if not down then return end + + local index = bind_translation[bind] + if not index then return end + + if index == 0 then + end_vote() + return true + end + + if not options[index] then return true end + + if current_index then + options_added[current_index]:TextColor(Color(220, 220, 220), true) + end + + options_added[index]:TextColor(Color(65, 185, 255), true) + current_index = index + + sam.netstream.Start("Vote", index) + + return true + end) + + hook.Add("SAM.ChangedGlobalVar", "SAM.VotingCount", function(key2, count) + if key2:sub(1, 7) ~= "Votings" then return end + if not count then return end + + local index = tonumber(key2:sub(8)) + options_added[index]:SetText(index .. ". " .. options[index] .. " (" .. count .. ")") + end) + end) +end + +local vote_check = function(str) + return str:match("%S") ~= nil +end + +command.new("vote") + :SetPermission("vote", "admin") + + :AddArg("text", {hint = "title", check = vote_check}) + :AddArg("text", {hint = "option", check = vote_check}) + :AddArg("text", {hint = "option", check = vote_check}) + :AddArg("text", {hint = "option", optional = true, check = vote_check}) + :AddArg("text", {hint = "option", optional = true, check = vote_check}) + :AddArg("text", {hint = "option", optional = true, check = vote_check}) + + :Help("Start a vote!") + + :OnExecute(function(ply, title, ...) + local callback = function(_, option, count, total_count) + sam.player.send_message(nil, "Vote {V} for {V_2} has been passed. ({V_3}/{V_4})", { + V = title, V_2 = option, V_3 = count, V_4 = total_count + }) + end + + if start_vote(ply, callback, title, ...) then + sam.player.send_message(nil, "{A} started a vote with title {V}.", { + A = ply, V = title + }) + end + end) +:End() + +command.new("endvote") + :SetPermission("endvote", "admin") + + :Help("End current vote.") + + :OnExecute(function(ply) + end_vote(ply) + end) +:End() + +command.new("votekick") + :SetPermission("votekick", "admin") + + :AddArg("player", {single_target = true}) + :AddArg("text", {hint = "reason", optional = true}) + + :GetRestArgs() + + :Help("Start a vote to kick a player.") + + :OnExecute(function(ply, targets, reason) + local target = targets[1] + local target_name = target:Name() + + local callback = function(index, option, count, total_count) + if not IsValid(ply) then + sam.player.send_message(nil, "Vote was canceled because {T} left.", { + T = target_name + }) + return + end + + if index == 1 then + target:Kick("Vote was successful (" .. (reason or "none") .. ")") + + sam.player.send_message(nil, "Vote was successful, {T} has been kicked. ({V})", { + T = targets, V = reason + }) + else + sam.player.send_message(nil, "Vote was unsuccessful, {T} won't be kicked.", { + T = target_name + }) + end + end + + local title = "Kick " .. target_name .. "?" + if reason then + title = title .. " (" .. reason .. ")" + end + + if start_vote(ply, callback, title, "Yes", "No") then + if reason then + sam.player.send_message(nil, "{A} started a votekick against {T} ({V})", { + A = ply, T = targets, V = reason + }) + else + sam.player.send_message(nil, "{A} started a votekick against {T}", { + A = ply, T = targets + }) + end + end + end) +:End() + +command.new("voteban") + :SetPermission("voteban", "admin") + + :AddArg("player", {single_target = true}) + :AddArg("length", {optional = true, default = 60, min = 30, max = 120}) + :AddArg("text", {hint = "reason", optional = true}) + + :GetRestArgs() + + :Help("Start a vote to ban a player.") + + :OnExecute(function(ply, targets, length, reason) + local target = targets[1] + local target_steamid, target_name = target:SteamID(), target:Name() + local ply_steamid = ply:SteamID() + + local callback = function(index, option, count, total_count) + if index == 1 then + sam.player.ban_id(target_steamid, length, "Vote was successful (" .. (reason or "none") .. ")", ply_steamid) + + sam.player.send_message(nil, "Vote was successful, {T} has been banned. ({V})", { + T = target_name, V = reason + }) + else + sam.player.send_message(nil, "Vote was unsuccessful, {T} won't be banned.", { + T = target_name + }) + end + end + + local title = "Ban " .. target_name .. "?" + if reason then + title = title .. " (" .. reason .. ")" + end + + if start_vote(ply, callback, title, "Yes", "No") then + if reason then + sam.player.send_message(nil, "{A} started a voteban against {T} for {V} ({V_2})", { + A = ply, T = targets, V = sam.format_length(length), V_2 = reason + }) + else + sam.player.send_message(nil, "{A} started a voteban against {T} for {V}", { + A = ply, T = targets, V = sam.format_length(length) + }) + end + end + end) +:End() + +command.new("votemute") + :SetPermission("votemute", "admin") + + :AddArg("player", {single_target = true}) + :AddArg("text", {hint = "reason", optional = true}) + + :GetRestArgs() + + :Help("Start a vote to mute and gag a player.") + + :OnExecute(function(ply, targets, reason) + local _reason = reason and (" (" .. reason .. ")") or "" + + local target = targets[1] + local target_name = target:Name() + + local callback = function(index, option, count, total_count) + if not IsValid(target) then + sam.player.send_message(nil, "Vote was canceled because {T} left.", { + T = target_name + }) + return + end + + if index == 1 then + RunConsoleCommand("sam", "mute", "#" .. target:EntIndex(), 0, "votemute" .. _reason) + RunConsoleCommand("sam", "gag", "#" .. target:EntIndex(), 0, "votemute" .. _reason) + + sam.player.send_message(nil, "Vote was successful, {T} has been muted. ({V})", { + T = target_name, V = reason + }) + else + sam.player.send_message(nil, "Vote was unsuccessful, {T} won't be muted.", { + T = target_name + }) + end + end + + local title = "Mute " .. target_name .. "?" .. _reason + if start_vote(ply, callback, title, "Yes", "No") then + if reason then + sam.player.send_message(nil, "{A} started a votemute against {T} ({V}).", { + A = ply, T = targets, V = reason + }) + else + sam.player.send_message(nil, "{A} started a votemute against {T}.", { + A = ply, T = targets + }) + end + end + end) +:End() + +command.new("votemap") + :SetPermission("votemap", "admin") + + :AddArg("map", {exclude_current = true}) + :AddArg("map", {optional = true, exclude_current = true}) + :AddArg("map", {optional = true, exclude_current = true}) + + :GetRestArgs() + + :Help("Start a vote to change map.") + + :OnExecute(function(ply, ...) + local callback = function(_, option, count, total_count) + sam.player.send_message(nil, "Map vote for {V} has been passed. ({V_2}/{V_3})", { + V = option, V_2 = count, V_3 = total_count + }) + + if sam.is_valid_map(option) then + RunConsoleCommand("sam", "map", option) + end + end + + local args = {...} + for i = select("#", ...), 1, -1 do + if args[i] == "None" then + args[i] = nil + end + end + table.insert(args, "Extend Current Map") + + if start_vote(ply, callback, "Vote for the next map!", unpack(args)) then + sam.player.send_message(nil, "{A} started a map change vote.", { + A = ply + }) + end + end) +:End() diff --git a/garrysmod/addons/module_adminmod/lua/sam/player/cl_player.lua b/garrysmod/addons/module_adminmod/lua/sam/player/cl_player.lua new file mode 100644 index 0000000..2d50fb3 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/player/cl_player.lua @@ -0,0 +1,8 @@ +if SAM_LOADED then return end + +local sam = sam +local netstream = sam.netstream + +netstream.Hook("PlaySound", function(sound) + surface.PlaySound(sound) +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/player/sh_nw_vars.lua b/garrysmod/addons/module_adminmod/lua/sam/player/sh_nw_vars.lua new file mode 100644 index 0000000..87544c8 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/player/sh_nw_vars.lua @@ -0,0 +1,69 @@ +if SAM_LOADED then return end + +local netstream = sam.netstream + +local nwvars = {} + +if SERVER then + function sam.player.set_nwvar(ply, key, value, force) + local id = ply:EntIndex() + if force or nwvars[id][key] ~= value then + nwvars[id][key] = value + netstream.Start(nil, "SetNWVar", id, key, value) + end + end +end + +if CLIENT then + function sam.player.set_nwvar(ply, key, value) + local id_vars = nwvars[ply:EntIndex()] + id_vars[key] = value + end + + netstream.Hook("SetNWVar", function(id, key, value) + local id_vars = nwvars[id] + if id_vars == nil then + nwvars[id] = { + [key] = value + } + else + id_vars[key] = value + end + end) + + netstream.Hook("SendNWVars", function(vars) + nwvars = vars + end) + + netstream.Hook("RemoveNWVar", function(id) + nwvars[id] = nil + end) +end + +function sam.player.get_nwvar(ply, key, default) + local value = nwvars[ply:EntIndex()] + if value then + value = value[key] + if value ~= nil then + return value + end + end + return default +end + +if SERVER then + hook.Add("OnEntityCreated", "SAM.NWVars", function(ent) + if ent:IsPlayer() and ent:IsValid() then + nwvars[ent:EntIndex()] = {} + netstream.Start(ent, "SendNWVars", nwvars) + end + end) + + hook.Add("EntityRemoved", "SAM.NWVars", function(ent) + if ent:IsPlayer() then + local id = ent:EntIndex() + nwvars[id] = nil + netstream.Start(nil, "RemoveNWVar", id) + end + end) +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/player/sh_player.lua b/garrysmod/addons/module_adminmod/lua/sam/player/sh_player.lua new file mode 100644 index 0000000..bf35793 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/player/sh_player.lua @@ -0,0 +1,173 @@ +if SAM_LOADED then return end + +local sam = sam +local config = sam.config + +do + local _player = {} + sam.player = setmetatable(sam.player, { + __index = _player, + __newindex = function(_, k, v) + _player[k] = v + if sam.isfunction(v) and debug.getlocal(v, 1) == "ply" then + FindMetaTable("Player")["sam_" .. k] = v + sam.console["sam_" .. k] = v + end + end + }) +end + +function sam.player.find_by_name(name) + name = name:lower() + local current, players = nil, player.GetAll() + for i = 1, #players do + local ply = players[i] + local found = ply:Name():lower():find(name, 1, true) + if found then + if current then + if not sam.istable(current) then + current = {current, ply} + else + table.insert(current, ply) + end + else + current = ply + end + end + end + return current +end + +do + if CLIENT then + config.add_menu_setting("Chat Prefix (Leave empty for no prefix)", function() + local entry = vgui.Create("SAM.TextEntry") + entry:SetPlaceholder("") + entry:SetNoBar(true) + entry:SetConfig("ChatPrefix", "") + + return entry + end) + end + + function sam.player.send_message(ply, msg, tbl) + if SERVER then + if sam.isconsole(ply) then + local result = sam.format_message(msg, tbl) + sam.print(unpack(result, 1, result.__cnt)) + else + return sam.netstream.Start(ply, "send_message", msg, tbl) + end + else + local prefix_result = sam.format_message(config.get("ChatPrefix", "")) + local prefix_n = #prefix_result + + local result = sam.format_message(msg, tbl, prefix_result, prefix_n) + chat.AddText(unpack(result, 1, result.__cnt)) + end + end + + if SERVER then + function sam.player.add_text(ply, ...) + if sam.isconsole(ply) then + sam.print(...) + else + sam.netstream.Start(ply, "add_text", ...) + end + end + end + + if CLIENT then + sam.netstream.Hook("send_message", function(msg, tbl) + sam.player.send_message(nil, msg, tbl) + end) + + sam.netstream.Hook("add_text", function(...) + chat.AddText(...) + end) + end +end + +do + local PLAYER = FindMetaTable("Player") + + function PLAYER:IsAdmin() + return self:CheckGroup("admin") + end + + function PLAYER:IsSuperAdmin() + return self:CheckGroup("superadmin") + end + + local inherits_from = sam.ranks.inherits_from + function PLAYER:CheckGroup(name) + return inherits_from(self:sam_getrank(), name) + end + + local has_permission = sam.ranks.has_permission + function PLAYER:HasPermission(perm) + return has_permission(self:sam_getrank(), perm) + end + + local can_target = sam.ranks.can_target + function PLAYER:CanTarget(ply) + return can_target(self:sam_getrank(), ply:sam_getrank()) + end + + function PLAYER:CanTargetRank(rank) + return can_target(self:sam_getrank(), rank) + end + + local get_ban_limit = sam.ranks.get_ban_limit + function PLAYER:GetBanLimit(ply) + return get_ban_limit(self:sam_getrank()) + end + + function PLAYER:sam_get_play_time() + return self:sam_get_nwvar("play_time", 0) + self:sam_get_session_time() + end + + function PLAYER:sam_get_session_time() + return os.time() - self:sam_get_nwvar("join_time", 0) + end + + function PLAYER:sam_getrank() + return self:sam_get_nwvar("rank", "user") + end + + function PLAYER:sam_setrank(name) + return self:sam_set_nwvar("rank", name) + end + + if SERVER then + function PLAYER:Ban(length) + self:sam_ban(length) + end + + hook.Remove("PlayerInitialSpawn", "PlayerAuthSpawn") + end +end + +do + local set_cooldown = function(ply, name, time) + if not ply.sam_cool_downs then + ply.sam_cool_downs = {} + end + ply.sam_cool_downs[name] = SysTime() + time + return true + end + + function sam.player.check_cooldown(ply, name, time) + if not ply.sam_cool_downs or not ply.sam_cool_downs[name] then + return set_cooldown(ply, name, time) + end + + local current_time = SysTime() + local cool_down = ply.sam_cool_downs[name] + if cool_down > current_time then + return false, cool_down - current_time + else + return set_cooldown(ply, name, time) + end + end +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/player/sv_auth.lua b/garrysmod/addons/module_adminmod/lua/sam/player/sv_auth.lua new file mode 100644 index 0000000..1c99e4f --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/player/sv_auth.lua @@ -0,0 +1,129 @@ +if SAM_LOADED then return end + +local sam, SQL = sam, sam.SQL + +local auth_player = function(data, ply) + if not ply:IsValid() then return end + if ply:sam_get_nwvar("is_authed") then return end + + local steamid = ply:SteamID() + + local rank, expiry_date, play_time + local first_join = false + if data then + rank, expiry_date, play_time = data.rank, tonumber(data.expiry_date), tonumber(data.play_time) + + SQL.FQuery([[ + UPDATE + `sam_players` + SET + `name` = {1}, + `last_join` = {2} + WHERE + `steamid` = {3} + ]], {ply:Name(), os.time(), steamid}) + else + rank, expiry_date, play_time = "user", 0, 0 + first_join = true + + local time = os.time() + SQL.FQuery([[ + INSERT INTO + `sam_players`( + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + ) + VALUES + ({1}, {2}, {3}, {4}, {5}, {6}, {7}) + ]], {steamid, ply:Name(), rank, 0, time, time, 0}) + end + + ply:SetUserGroup(rank) + ply:sam_setrank(rank) + ply:sam_start_rank_timer(expiry_date) + + ply:sam_set_nwvar("join_time", os.time()) + ply:sam_set_nwvar("play_time", play_time) + ply:sam_set_nwvar("is_authed", true) + + hook.Call("SAM.AuthedPlayer", nil, ply, steamid, first_join) + + timer.Simple(0, function() + if IsValid(ply) then + sam.client_hook_call("SAM.AuthedPlayer", ply, steamid, first_join) + end + end) +end +hook.Add("PlayerInitialSpawn", "SAM.AuthPlayer", function(ply) + SQL.FQuery([[ + SELECT + `rank`, + `expiry_date`, + `play_time` + FROM + `sam_players` + WHERE + `steamid` = {1} + ]], {ply:SteamID()}, auth_player, true, ply) +end) +sam.player.auth = auth_player + +hook.Add("SAM.AuthedPlayer", "SetSuperadminToListenServer", function(ply) + if game.SinglePlayer() or ply:IsListenServerHost() then + ply:sam_set_rank("superadmin") + end +end) + +hook.Add("SAM.AuthedPlayer", "CheckIfFullyAuthenticated", function(ply) + timer.Simple(0, function() + if not IsValid(ply) then return end + if ply:IsBot() then return end + if not ply.IsFullyAuthenticated or ply:IsFullyAuthenticated() then return end + if game.SinglePlayer() or ply:IsListenServerHost() then return end + + ply:Kick("Your SteamID wasn't fully authenticated, try restarting steam.") + end) +end) + +do + local format = string.format + local floor = math.floor + local SysTime = SysTime + local last_save = SysTime() + + local save_play_time = function(ply) + if not ply:sam_get_nwvar("is_authed") then return end + + local query = format([[ + UPDATE + `sam_players` + SET + `play_time` = %u + WHERE + `steamid` = '%s' + ]], floor(ply:sam_get_play_time()), ply:SteamID()) + SQL.Query(query) + end + + hook.Add("Think", "SAM.Player.SaveTimes", function() + if SysTime() - last_save < 60 then return end + + SQL.Begin() + local players = player.GetHumans() + for i = 1, #players do + save_play_time(players[i]) + end + SQL.Commit() + + sam.hook_call("SAM.UpdatedPlayTimes") + + last_save = SysTime() + end) + + hook.Add("PlayerDisconnected", "SAM.Player.SaveTime", save_play_time) +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/player/sv_bans.lua b/garrysmod/addons/module_adminmod/lua/sam/player/sv_bans.lua new file mode 100644 index 0000000..faaac04 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/player/sv_bans.lua @@ -0,0 +1,281 @@ +if SAM_LOADED then return end + +local sam = sam +local SQL = sam.SQL + +local DEFAULT_REASON = sam.language.get("default_reason") + +local set_cached, get_cached do + local cached_bans = {} + function set_cached(k, v) + cached_bans[k] = v + end + function get_cached(k) + return cached_bans[k] + end + timer.Create("SAM.ClearCachedBans", 60 * 2.5, 0, function() + table.Empty(cached_bans) + end) +end + +function sam.format_ban_message(admin_name, admin_steamid, reason, unban_date) + unban_date = unban_date == 0 and "never" or sam.format_length((unban_date - os.time()) / 60) + + local message_tbl + if admin_name == "" then + message_tbl = sam.format_message("ban_message", { + S = admin_steamid, S_2 = reason, S_3 = unban_date + }) + else + message_tbl = sam.format_message("ban_message_2", { + S = admin_name, S_2 = admin_steamid, S_3 = reason, S_4 = unban_date + }) + end + + local message = "" + for i = 1, #message_tbl do + local v = message_tbl[i] + if sam.isstring(v) then + message = message .. v + end + end + + return message +end + +function sam.player.ban(ply, length, reason, admin_steamid) + if sam.type(ply) ~= "Player" or not ply:IsValid() then + error("invalid player") + end + + if ply.sam_is_banned then return end + + local unban_date + if not sam.isnumber(length) or length <= 0 then + unban_date = 0 + else + unban_date = (math.min(length, 31536000) * 60) + os.time() + end + + if not sam.isstring(reason) then + reason = DEFAULT_REASON + end + + if ply:IsBot() then -- you can't ban bots! + return ply:Kick(reason) + end + + if not sam.is_steamid(admin_steamid) then -- 6413d4d7dfc8ec7f575009cd275d89fd6990bac88e9161fe791b4cd3c7904c8b + admin_steamid = "Console" + end + + local steamid = ply:SteamID() + + SQL.FQuery([[ + INSERT INTO + `sam_bans`( + `steamid`, + `reason`, + `admin`, + `unban_date` + ) + VALUES + ({1}, {2}, {3}, {4}) + ]], {steamid, reason, admin_steamid, unban_date}) + + local admin_name = "" + do + local admin = player.GetBySteamID(admin_steamid) + if admin then + admin_name = admin:Name() + end + end + + ply.sam_is_banned = true + set_cached(steamid, nil) + sam.hook_call("SAM.BannedPlayer", ply, unban_date, reason, admin_steamid) + ply:Kick(sam.format_ban_message(admin_name, admin_steamid, reason, unban_date)) +end + +function sam.player.ban_id(steamid, length, reason, admin_steamid) + sam.is_steamid(steamid, true) + + do + local ply = player.GetBySteamID(steamid) + if ply then + return ply:sam_ban(length, reason, admin_steamid) + end + end + + local unban_date + if not sam.isnumber(length) or length <= 0 then + unban_date = 0 + else + unban_date = (math.min(length, 31536000) * 60) + os.time() + end + + if not sam.isstring(reason) then + reason = DEFAULT_REASON + end + + local query + if SQL.IsMySQL() then + query = [[ + INSERT INTO + `sam_bans`( + `steamid`, + `reason`, + `admin`, + `unban_date` + ) + VALUES + ({1}, {2}, {3}, {4}) ON DUPLICATE KEY + UPDATE + `reason` = VALUES(`reason`), + `admin` = VALUES(`admin`), + `unban_date` = VALUES(`unban_date`) + ]] + else + query = [[ + INSERT INTO + `sam_bans`( + `steamid`, + `reason`, + `admin`, + `unban_date` + ) + VALUES + ({1}, {2}, {3}, {4}) ON CONFLICT(`steamid`) DO + UPDATE SET + `reason` = excluded.`reason`, + `admin` = excluded.`admin`, + `unban_date` = excluded.`unban_date` + ]] + end + + SQL.FQuery(query, {steamid, reason, admin_steamid, unban_date}) + + local admin_name = "" + if sam.is_steamid(admin_steamid) then + local admin = player.GetBySteamID(admin_steamid) + if admin then + admin_name = admin:Name() + end + else + admin_steamid = "Console" + end + + set_cached(steamid, nil) + sam.hook_call("SAM.BannedSteamID", steamid, unban_date, reason, admin_steamid) + sam.player.kick_id(steamid, sam.format_ban_message(admin_name, admin_steamid, reason, unban_date)) +end + +function sam.player.unban(steamid, admin) + sam.is_steamid(steamid, true) + + if not sam.is_steamid(admin) then + admin = "Console" + end + + SQL.FQuery([[ + DELETE FROM + `sam_bans` + WHERE + `steamid` = {1} + ]], {steamid}) + + set_cached(steamid, false) + sam.hook_call("SAM.UnbannedSteamID", steamid, admin) +end + +local check_for_unban = function(steamid, ban_data, callback) + local to_return = ban_data + + local unban_date = tonumber(ban_data.unban_date) + if unban_date ~= 0 and os.time() >= unban_date then + to_return = false + sam.player.unban(steamid) + end + + if callback then + callback(to_return, steamid) + else + return to_return + end +end + +do + local query_callback = function(data, arguments) + local steamid, callback = arguments[1], arguments[2] + if data then + set_cached(steamid, data) + check_for_unban(steamid, data, callback) + else + set_cached(steamid, false) + callback(false, steamid) + end + end + + function sam.player.is_banned(steamid, callback) + sam.is_steamid(steamid, true) + + local ban_data = get_cached(steamid) + if ban_data then + check_for_unban(steamid, ban_data, callback) + elseif ban_data == false then + callback(false, steamid) + else + local query = [[ + SELECT + `sam_bans`.`steamid`, + `sam_bans`.`reason`, + `sam_bans`.`admin`, + `sam_bans`.`unban_date`, + IFNULL(`sam_players`.`name`, '') AS `admin_name` + FROM + `sam_bans` + LEFT OUTER JOIN + `sam_players` + ON + `sam_bans`.`admin` = `sam_players`.`steamid` + WHERE + `sam_bans`.`steamid` = ]] .. SQL.Escape(steamid) + + SQL.Query(query, query_callback, true, {steamid, callback}) + end + end +end + +do + local steamids = {} + + local query_callback = function(ban_data, steamid) + steamids[steamid] = nil + + if ban_data then + sam.player.kick_id(steamid, sam.format_ban_message(ban_data.admin_name, ban_data.admin, ban_data.reason, tonumber(ban_data.unban_date))) + end + end + + gameevent.Listen("player_connect") + hook.Add("player_connect", "SAM.CheckIfBanned", function(data) + local steamid = data.networkid + if data.bot == 0 and not steamids[steamid] then + steamids[steamid] = true + sam.player.is_banned(steamid, query_callback) + end + end) + + hook.Add("CheckPassword", "SAM.CheckIfBanned", function(steamid64) + local steamid = util.SteamIDFrom64(steamid64) + local ban_data = get_cached(steamid) + if not ban_data then return end + + ban_data = check_for_unban(steamid, ban_data) + if not ban_data then return end + + return false, sam.format_ban_message(ban_data.admin_name, ban_data.admin, ban_data.reason, tonumber(ban_data.unban_date)) + end) +end + +sam.player.ban_set_cached, sam.player.ban_get_cached = set_cached, get_cached diff --git a/garrysmod/addons/module_adminmod/lua/sam/player/sv_player.lua b/garrysmod/addons/module_adminmod/lua/sam/player/sv_player.lua new file mode 100644 index 0000000..96b9852 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/player/sv_player.lua @@ -0,0 +1,156 @@ +if SAM_LOADED then return end + +local sam = sam +local netstream = sam.netstream + +do + local connected_players = {} + + function sam.player.kick_id(id, reason) + reason = sam.isstring(reason) and reason or sam.language.get("default_reason") + reason = reason:sub(1, 400) + game.KickID(id, reason) + end + + function sam.player.is_connected(steamid) + return connected_players[steamid] and true or false + end + + function sam.get_connected_players() + return connected_players + end + + gameevent.Listen("player_connect") + hook.Add("player_connect", "SAM.ConnectedPlayers", function(data) + connected_players[data.networkid] = true + end) + + gameevent.Listen("player_disconnect") + hook.Add("player_disconnect", "SAM.ConnectedPlayers", function(data) + connected_players[data.networkid] = nil + end) +end + +function sam.player.set_exclusive(ply, reason) + ply.sam_exclusive_reason = reason +end + +function sam.player.get_exclusive(ply, admin) + local reason = ply.sam_exclusive_reason + if reason then + if ply == admin then + return "You are " .. reason .. "!" + else + return ply:Name() .. " is " .. reason .. "!" + end + end +end + +do + local hide_weapons = function(ply, should_hide) + for _, v in pairs(ply:GetWeapons()) do + v:SetNoDraw(should_hide) + end + + local physgun_beams = ents.FindByClassAndParent("physgun_beam", ply) + if physgun_beams then + for i = 1, #physgun_beams do + physgun_beams[i]:SetNoDraw(should_hide) + end + end + end + + local cloak = function(ply, should_cloak) + ply:SetNoDraw(should_cloak) + ply:DrawWorldModel(not should_cloak) + ply:SetRenderMode(should_cloak and RENDERMODE_TRANSALPHA or RENDERMODE_NORMAL) + ply:Fire("alpha", should_cloak and 0 or 255, 0) + ply:sam_set_nwvar("cloaked", should_cloak) + hide_weapons(ply, should_cloak) + end + + function sam.player.cloak(ply) + cloak(ply, true) + end + + function sam.player.uncloak(ply) + cloak(ply, false) + end + + hook.Add("PlayerSpawn", "SAM.CloakPlayer", function(ply) + if ply:sam_get_nwvar("cloaked") then + cloak(ply, true) + end + end) + + hook.Add("PlayerSwitchWeapon", "SAM.CloakPlayer", function(ply) + if ply:sam_get_nwvar("cloaked") then + timer.Create("SAM.HideWeapons" .. ply:SteamID(), 0, 1, function() + if IsValid(ply) and ply:sam_get_nwvar("cloaked") then + hide_weapons(ply, true) + end + end) + end + end) +end + +do + local call_hook = function(ply) + local can_spawn = hook.Call("SAM.CanPlayerSpawn", nil, ply) + if can_spawn ~= nil then + return can_spawn + end + end + + local spawn_hooks = { + "Effect", "NPC", + "Object", "Prop", + "Ragdoll", "SENT", + "SWEP", "Vehicle" + } + + for k, v in ipairs(spawn_hooks) do + hook.Add("PlayerSpawn" .. v, "SAM.CanPlayerSpawn", call_hook) + end +end + +do + local persistent_data = {} + + function sam.player.set_pdata(ply, key, value) + local ply_pdata = persistent_data[ply:AccountID()] + if ply_pdata then + ply_pdata[key] = value + else + persistent_data[ply:AccountID()] = { + [key] = value + } + end + end + + function sam.player.get_pdata(ply, key, default) + local ply_pdata = persistent_data[ply:AccountID()] + if ply_pdata then + local value = ply_pdata[key] + if value ~= nil then + return value + end + end + return default + end +end + +function sam.player.play_sound(ply, sound) + netstream.Start(ply, "PlaySound", sound) +end + +hook.Add("CharacterLoaded", "SAM.UnadminOnCharacterChange", function(character) + local ply = character:GetPlayer() + if ply:sam_get_nwvar("cloaked") then + ply:sam_uncloak() + ply:GodDisable() + ply:SetMoveType(MOVETYPE_WALK) + end +end) + +--[[/*1bfac981ef028da1ef0894f20e961940fe5f5c2d8e44559e60885dd1771352cc*/]] diff --git a/garrysmod/addons/module_adminmod/lua/sam/player/sv_ranks.lua b/garrysmod/addons/module_adminmod/lua/sam/player/sv_ranks.lua new file mode 100644 index 0000000..22159d4 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/player/sv_ranks.lua @@ -0,0 +1,205 @@ +if SAM_LOADED then return end + +local sam = sam +local SQL, Promise = sam.SQL, sam.Promise + +function sam.player.set_rank(ply, rank, length) + if sam.type(ply) ~= "Player" or not ply:IsValid() then + error("invalid player") + elseif not sam.ranks.is_rank(rank) then + error("invalid rank") + end + + if not sam.isnumber(length) or length < 0 then + length = 0 + end + + local expiry_date = 0 + if length ~= 0 then + if rank == "user" then + expiry_date = 0 + else + expiry_date = (math.min(length, 31536000) * 60) + os.time() + end + end + + ply:sam_start_rank_timer(expiry_date) + + SQL.FQuery([[ + UPDATE + `sam_players` + SET + `rank` = {1}, + `expiry_date` = {2} + WHERE + `steamid` = {3} + ]], {rank, expiry_date, ply:SteamID()}) + + local old_rank = ply:sam_getrank() + ply:SetUserGroup(rank) + ply:sam_setrank(rank) + sam.hook_call("SAM.ChangedPlayerRank", ply, rank, old_rank, expiry_date) +end + +do + local set_rank_id = function(player_data, arguments) + local old_rank = player_data and player_data.rank or false + local promise, steamid, rank, length = unpack(arguments, 1, 4) + + local expiry_date = 0 + if length ~= 0 then + if rank == "user" then + expiry_date = 0 + else + expiry_date = (math.min(length, 31536000) * 60) + os.time() + end + end + + local exists = true + if old_rank == false then + exists, old_rank = false, "user" + + local time = os.time() + SQL.FQuery([[ + INSERT INTO + `sam_players`( + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + ) + VALUES + ({1}, {2}, {3}, {4}, {5}, {6}, {7}) + ]], {steamid, "", rank, 0, time, time, 0}) + else + SQL.FQuery([[ + UPDATE + `sam_players` + SET + `rank` = {1}, + `expiry_date` = {2} + WHERE + `steamid` = {3} + ]], {rank, expiry_date, steamid}) + end + + promise:resolve() + sam.hook_call("SAM.ChangedSteamIDRank", steamid, rank, old_rank, expiry_date, exists) + end + + function sam.player.set_rank_id(steamid, rank, length) + sam.is_steamid(steamid, true) + + if not sam.ranks.is_rank(rank) then + error("invalid rank") + end + + local promise = Promise.new() + + do + local ply = player.GetBySteamID(steamid) + if ply then + promise:resolve(ply:sam_set_rank(rank, length)) + return promise + end + end + + if not sam.isnumber(length) or length < 0 then + length = 0 + end + + SQL.FQuery([[ + SELECT + `rank` + FROM + `sam_players` + WHERE + `steamid` = {1} + ]], {steamid}, set_rank_id, true, {promise, steamid, rank, length}) + + return promise + end +end + +do + local get_rank = function(data, callback) + if not data then + callback(false) + else + callback(data.rank) + end + end + + function sam.player.get_rank(steamid, callback) + sam.is_steamid(steamid, true) + + SQL.FQuery([[ + SELECT + `rank` + FROM + `sam_players` + WHERE + `steamid` = {1} + ]], {steamid}, get_rank, true, callback) + end +end + +do + local remove_rank_timer = function(ply) + timer.Remove("SAM.RankTimer." .. ply:SteamID()) + end + + function sam.player.start_rank_timer(ply, expiry_date) + ply.sam_rank_expirydate = expiry_date + if expiry_date == 0 then -- permanent rank + return remove_rank_timer(ply) + end + expiry_date = expiry_date - os.time() + timer.Create("SAM.RankTimer." .. ply:SteamID(), expiry_date, 1, function() + sam.player.send_message(nil, "rank_expired", { + T = {ply, admin = sam.console}, V = ply:sam_getrank() + }) + ply:sam_set_rank("user") + end) + end + + hook.Add("PlayerDisconnected", "SAM.RemoveRankTimer", remove_rank_timer) +end + +hook.Add("SAM.OnRankRemove", "ResetPlayerRank", function(name) + for _, ply in ipairs(player.GetAll()) do + if ply:sam_getrank() == name then + ply:sam_set_rank("user") + end + end + + SQL.FQuery([[ + UPDATE + `sam_players` + SET + `rank` = 'user', + `expiry_date` = 0 + WHERE + `rank` = {1} + ]], {name}) +end) + +hook.Add("SAM.RankNameChanged", "ChangePlayerRankName", function(old, new) + for _, ply in ipairs(player.GetAll()) do + if ply:sam_getrank() == old then + ply:sam_set_rank(new) + end + end + + SQL.FQuery([[ + UPDATE + `sam_players` + SET + `rank` = {1} + WHERE + `rank` = {2} + ]], {new, old}) +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/ranks/sh_ranks.lua b/garrysmod/addons/module_adminmod/lua/sam/ranks/sh_ranks.lua new file mode 100644 index 0000000..472c91a --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/ranks/sh_ranks.lua @@ -0,0 +1,114 @@ +if SAM_LOADED then return end + +SAM_IMMUNITY_SUPERADMIN = 100 +SAM_IMMUNITY_ADMIN = 50 +SAM_IMMUNITY_USER = 1 + +function sam.ranks.get_ranks() + return sam.get_global("Ranks") +end + +function sam.ranks.get_rank(rank) + local ranks = sam.ranks.get_ranks() + return ranks[rank] +end + +function sam.ranks.is_rank(rank) + if sam.ranks.get_rank(rank) then + return true + else + return false + end +end + +function sam.ranks.is_default_rank(rank) + return rank == "superadmin" or rank == "admin" or rank == "user" +end + +function sam.ranks.inherits_from(rank, rank_2) + if rank == rank_2 then + return true + end + + while true do + rank = sam.ranks.get_rank(rank) + + if rank then + local inherits_from = rank.inherit + if inherits_from == rank_2 then + return true + end + + rank = rank.inherit + else + return false + end + end +end + +function sam.ranks.has_permission(rank, permission) + while true do + if rank == "superadmin" then + return true + end + + rank = sam.ranks.get_rank(rank) + + if rank then + local rank_permission = rank.data.permissions[permission] + if rank_permission ~= nil then + return rank_permission + end + + rank = rank.inherit + else + return false + end + end +end + +function sam.ranks.get_limit(rank, limit_type) + while true do + if rank == "superadmin" then return -1 end + + rank = sam.ranks.get_rank(rank) + + if rank then + local limit = rank.data.limits[limit_type] + if limit ~= nil then + return limit + end + + rank = rank.inherit + else + return cvars.Number("sbox_max" .. limit_type, 0) + end + end +end + +function sam.ranks.get_immunity(rank) + rank = sam.ranks.get_rank(rank) + return rank and rank.immunity or false +end + +function sam.ranks.can_target(rank_1, rank_2) + rank_1, rank_2 = sam.ranks.get_rank(rank_1), sam.ranks.get_rank(rank_2) + if not rank_1 or not rank_2 then + return false + end + return rank_1.immunity >= rank_2.immunity +end + +function sam.ranks.get_ban_limit(rank) + rank = sam.ranks.get_rank(rank) + return rank and rank.ban_limit or false +end + +if CLIENT then + hook.Add("SAM.ChangedGlobalVar", "SAM.Ranks.CheckLoadedRanks", function(key, value) + if key == "Ranks" then + hook.Call("SAM.LoadedRanks", nil, value) + hook.Remove("SAM.ChangedGlobalVar", "SAM.Ranks.CheckLoadedRanks") + end + end) +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/ranks/sv_ranks.lua b/garrysmod/addons/module_adminmod/lua/sam/ranks/sv_ranks.lua new file mode 100644 index 0000000..c185141 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/ranks/sv_ranks.lua @@ -0,0 +1,438 @@ +if SAM_LOADED then return end + +local sam = sam +local SQL, util = sam.SQL, util + +local ranks = {} +local ranks_loaded = false + +function sam.ranks.sync() + sam.set_global("Ranks", ranks, true) +end + +function sam.ranks.add_rank(name, inherit, immunity, ban_limit) + if not sam.isstring(name) then + error("invalid rank name") + end + + if not ranks_loaded then + error("loading ranks") + end + + if ranks[name] then + return "rank_exists" + end + + if not sam.isstring(inherit) and name ~= "user" then + inherit = "user" + end + + local data = { + permissions = {}, + limits = {} + } + + if name ~= "user" then + local inherit_rank = ranks[inherit] + if not inherit_rank then + error("invalid rank to inherit from") + end + if not sam.isnumber(immunity) then + immunity = inherit_rank.immunity + end + if not sam.isnumber(ban_limit) then + ban_limit = inherit_rank.ban_limit + end + end + + if name == "superadmin" then + immunity = 100 + elseif name == "user" then + immunity = 1 + else + immunity = math.Clamp(immunity, 2, 99) + end + + ban_limit = math.Clamp(ban_limit, 0, 31536000) + + SQL.FQuery([[ + INSERT INTO + `sam_ranks`( + `name`, + `inherit`, + `immunity`, + `ban_limit`, + `data` + ) + VALUES + ({1}, {2}, {3}, {4}, {5}) + ]], {name, inherit or "NULL", immunity, ban_limit, util.TableToJSON(data)}) + + local rank = { + name = name, + inherit = inherit, + immunity = immunity, + ban_limit = ban_limit, + data = data + } + + ranks[name] = rank + sam.ranks.sync() + sam.hook_call("SAM.AddedRank", name, rank) +end + +function sam.ranks.remove_rank(name) + if not sam.isstring(name) then + error("invalid rank") + end + + if not ranks_loaded then + error("loading ranks") + end + + -- can't just remove default ranks!! + if sam.ranks.is_default_rank(name) then + return "default_rank" + end + + // 16761ecd632b58591221e90dc7c15ee4341e10d4c9ad23f8cdfed2cb35621623 + local rank = ranks[name] + if not rank then + error("invalid rank") + end + + SQL.FQuery([[ + DELETE FROM + `sam_ranks` + WHERE + `name` = {1} + ]], {name}) + + sam.hook_call("SAM.OnRankRemove", name, rank) + ranks[name] = nil + sam.ranks.sync() + sam.hook_call("SAM.RemovedRank", name) +end + +function sam.ranks.rename_rank(old, new) + if not sam.isstring(old) then + error("invalid old name") + end + + if not sam.isstring(new) then + error("invalid new name") + end + + if not ranks_loaded then + error("loading ranks") + end + + local old_rank = ranks[old] + if not old_rank then + error("invalid rank") + end + + if sam.ranks.is_default_rank(old) then + return "default_rank" + end + + if ranks[new] then + error("new rank name exists") + end + + old_rank.name = new + ranks[new], ranks[old] = ranks[old], nil + + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `name` = {1} + WHERE + `name` = {2} + ]], {new, old}) + + sam.ranks.sync() + sam.hook_call("SAM.RankNameChanged", old, new) +end + +function sam.ranks.change_inherit(name, inherit) + if not sam.isstring(name) then + error("invalid rank") + end + + if not sam.isstring(inherit) then + error("invalid rank to inherit from") + end + + if not ranks_loaded then + error("loading ranks") + end + + local rank = ranks[name] + if not rank then + error("invalid rank") + end + + if name == "user" or name == "superadmin" then return end + + if not ranks[inherit] then + error("invalid rank to inherit from") + end + + if name == inherit then + error("you can't inherit from the same rank") + end + + if rank.inherit == inherit then return end + + local old_inherit = rank.inherit + -- b56e1ee7d2141d19a33bdca2a77b8fe35d4e1fb515ad18b32b87e412572a45c2!!! + rank.inherit = inherit + + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `inherit` = {1} + WHERE + `name` = {2} + ]], {inherit, name}) + + sam.ranks.sync() + sam.hook_call("SAM.ChangedInheritRank", name, inherit, old_inherit) +end + +function sam.ranks.change_immunity(name, new_immunity) + if not sam.isstring(name) then + error("invalid rank") + end + + if not sam.isnumber(new_immunity) then + error("invalid immunity") + end + + if not ranks_loaded then + error("loading ranks") + end + + local rank = ranks[name] + if not rank then + error("invalid rank") + end + + if name == "user" or name == "superadmin" then return end + + new_immunity = math.Clamp(new_immunity, 2, 99) // 90219818fa6304647184770be8735a2c328bfdbdb6305fdf4fb8b8f42e1aa795!!! + + local old_immunity = rank.immunity + rank.immunity = new_immunity + + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `immunity` = {1} + WHERE + `name` = {2} + ]], {new_immunity, name}) + + sam.ranks.sync() + sam.hook_call("SAM.RankImmunityChanged", name, new_immunity, old_immunity) +end + +function sam.ranks.change_ban_limit(name, new_limit) + if not sam.isstring(name) then + error("invalid rank") + end + + if not sam.isnumber(new_limit) then + error("invalid ban limit") + end + + if not ranks_loaded then + error("loading ranks") + end + + local rank = ranks[name] + if not rank then + error("invalid rank") + end + + if name == "superadmin" then return end + + new_limit = math.Clamp(new_limit, 0, 31536000) + + local old_limit = rank.ban_limit + rank.ban_limit = new_limit + + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `ban_limit` = {1} + WHERE + `name` = {2} + ]], {new_limit, name}) + + sam.ranks.sync() + sam.hook_call("SAM.RankBanLimitChanged", name, new_limit, old_limit) +end + +function sam.ranks.give_permission(name, permission) + if not sam.isstring(name) then + error("invalid rank") + end + + if not sam.isstring(permission) then + error("invalid permission") + end + + if not ranks_loaded then + error("loading ranks") + end + + if name == "superadmin" then return end + + local rank = ranks[name] + if not rank then + error("invalid rank") + end + + local permissions = rank.data.permissions + if permissions[permission] then return end + + -- c13cd8686cda10dd1e759e5e63ebc9dc3b42f8d0096e3ae29c705789e9694762!!! + permissions[permission] = true + + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `data` = {1} + WHERE + `name` = {2} + ]], {util.TableToJSON(rank.data), name}) + + sam.ranks.sync() + sam.hook_call("SAM.RankPermissionGiven", name, permission) +end + +function sam.ranks.take_permission(name, permission) + if not sam.isstring(name) then + error("invalid rank") + end + + if not sam.isstring(permission) then + error("invalid permission") + end + + if not ranks_loaded then + error("loading ranks") + end + + if name == "superadmin" then return end + + local rank = ranks[name] + if not rank then + error("invalid rank") + end + + local permissions = rank.data.permissions + if permissions[permission] == false then return end + + permissions[permission] = false + + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `data` = {1} + WHERE + `name` = {2} + ]], {util.TableToJSON(rank.data), name}) + + sam.ranks.sync() + sam.hook_call("SAM.RankPermissionTaken", name, permission) +end + +function sam.ranks.set_limit(name, type, limit) + if not sam.isstring(name) then + error("invalid rank") + end + + if not sam.isstring(type) then + error("invalid limit type") + end + + if not sam.isnumber(limit) then + error("invalid limit value") + end + + if not ranks_loaded then + error("loading ranks") + end + + if name == "superadmin" then return end + + local rank = ranks[name] + if not rank then + error("invalid rank") + end + + limit = math.Clamp(limit, -1, 1000) + local limits = rank.data.limits + if limits[type] == limit then return end + + limits[type] = limit + + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `data` = {1} + WHERE + `name` = {2} + ]], {util.TableToJSON(rank.data), name}) + + sam.ranks.sync() + sam.hook_call("SAM.RankChangedLimit", name, type, limit) +end + +function sam.ranks.ranks_loaded() + return ranks_loaded +end + +sam.ranks.sync() + +hook.Add("SAM.DatabaseLoaded", "LoadRanks", function() + SQL.Query([[ + SELECT + * + FROM + `sam_ranks` + ]], function(sam_ranks) + for _, v in ipairs(sam_ranks) do + local name = v.name + ranks[name] = { + name = name, + inherit = name ~= "user" and v.inherit, + immunity = tonumber(v.immunity), + ban_limit = tonumber(v.ban_limit), + data = util.JSONToTable(v.data) + } + end + + ranks_loaded = true + + if #ranks < 3 then + sam.ranks.add_rank("user", nil, nil, 0) + sam.ranks.add_rank("admin", "user", SAM_IMMUNITY_ADMIN, 20160 --[[2 weeks]]) + sam.ranks.add_rank("superadmin", "admin", SAM_IMMUNITY_SUPERADMIN, 0) + /*5d1fc005a69d1d7b7fbb6984a9e4120cd20c61059d36f7303139e16aaf98d3db*/ + end + + sam.ranks.sync() + hook.Call("SAM.LoadedRanks", nil, ranks) + end):wait() +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/reports/cl_reports.lua b/garrysmod/addons/module_adminmod/lua/sam/reports/cl_reports.lua new file mode 100644 index 0000000..25b8938 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/reports/cl_reports.lua @@ -0,0 +1,452 @@ +if SAM_LOADED then return end + +local sam = sam +local netstream = sam.netstream +local SUI = sam.SUI + +local config = sam.config + +local Trim = string.Trim + +local muted_var = CreateClientConVar("sam_mute_reports", "0", false, false, "", 0, 1) + +local position = config.get_updated("Reports.Position", "Left") +local max_reports = config.get_updated("Reports.MaxReports", 4) +local always_show = config.get_updated("Reports.AlwaysShow", true) +local pad_x = config.get_updated("Reports.XPadding", 5) +local pad_y = config.get_updated("Reports.YPadding", 5) + +local duty_jobs = {} +config.hook({"Reports.DutyJobs"}, function() + local jobs = config.get("Reports.DutyJobs", ""):Split(",") + for i = #jobs, 1, -1 do + local v = Trim(jobs[i]) + if v ~= "" then + jobs[v] = true + end + jobs[i] = nil + end + duty_jobs = jobs +end) + +local commands = {} +config.hook({"Reports.Commands"}, function() + local cmds = config.get("Reports.Commands", ""):Split(",") + for i = 1, #cmds do + local v = Trim(cmds[i]) + if v ~= "" then + cmds[i] = { + name = v, + func = function(_, ply) + if IsValid(ply) then + RunConsoleCommand("sam", v, "#" .. ply:EntIndex()) + end + end + } + end + end + commands = cmds +end) + +local reports = {} +local queued_reports = {} + +local new_report, remove_report, check_queued, get_report, append_report + +get_report = function(ply, index) + for i = 1, #reports do + local v = reports[i] + local _ply = index and v.index or v.ply + if _ply == ply then return v end + end + + for i = 1, #queued_reports do + local v = queued_reports[i] + local _ply = index and v.index or v.ply + if _ply == ply then return v, i end + end +end + +remove_report = function(ply) + local report, delayed_i = get_report(ply) + + if delayed_i then + return table.remove(queued_reports, delayed_i) + end + + local panel = report.panel + panel:MoveToNewX(position.value == "Right" and ScrW() or -panel:GetWide(), function() + for i = report.pos + 1, #reports do + local v = reports[i] + v.pos = v.pos - 1 + v.panel:MoveToNewY(v.panel:GetY()) + end + + panel:Remove() + table.remove(reports, report.pos) + + check_queued() + end) +end + +check_queued = function() + while (max_reports.value - #reports > 0 and #queued_reports > 0) do + new_report(table.remove(queued_reports, 1)) + end +end + +append_report = function(ply, text) + local report, delayed = get_report(ply) + if delayed then + table.insert(report.comments, text) + else + report.panel:AddComment(text) + end +end + +new_report = function(report) + if #reports >= max_reports.value then + return table.insert(queued_reports, report) + end + + report.pos = table.insert(reports, report) + + local panel = vgui.Create("SAM.Report") + panel:SetReport(report) + + for k, v in ipairs(commands) do + panel:AddButton(v.name:gsub("^%l", string.upper), v.func) + end + + local claim = panel:AddButton("Claim", function(self, ply) + if panel:HasReport() then + return LocalPlayer():sam_send_message("You have an active case, close it first.") + end + + self.DoClick = function() + end + + local claim_query = netstream.async.Start("ClaimReport", nil, ply) + claim_query:done(function(claimed) + if not IsValid(panel) then return end + + if claimed then + panel:SetHasReport(ply) + + self:SetText("Close") + + self.background = Color(231, 76, 60, 200) + self.hover = Color(255, 255, 255, 25) + + panel:FixWide() + + for k, v in ipairs(panel:GetChildren()[3]:GetChildren()) do + v:SetDisabled(false) + v:SetCursor("hand") + end + + self.DoClick = function() + panel:Close() + end + else + panel:SetClaimed() + end + end) + end) + + panel.claim = claim + + claim:SetCursor("hand") + claim:SetDisabled(false) + + claim.background = Color(39, 174, 96, 200) + claim.hover = Color(255, 255, 255, 25) + + panel:FixWide() + + local x = pad_x.value + if position.value == "Right" then + x = (ScrW() - panel:GetWide()) - x + end + + panel:MoveToNewX(x) + panel:MoveToNewY(panel:GetY()) + + panel.new = true + for k, v in ipairs(report.comments) do + panel:AddComment(v) + end + panel.new = nil +end + +netstream.Hook("Report", function(ply, comment) + if not IsValid(ply) then return end + + if muted_var:GetBool() then return end + + local report = get_report(ply) + if not report then + report = { + ply = ply, + index = ply:EntIndex(), + comments = {comment} + } + + if not always_show.value and not duty_jobs[team.GetName(LocalPlayer():Team())] then + LocalPlayer():sam_send_message("({S Blue}) {S_2 Red}: {S_3}", { + S = "Report", S_2 = ply:Name(), S_3 = comment + }) + else + new_report(report) + end + else + append_report(ply, comment) + end +end) + +netstream.Hook("ReportClaimed", function(ply) + local report, delayed = get_report(ply) + if not report then return end + + if delayed then + table.remove(queued_reports, delayed) + else + report.panel:SetClaimed() + end +end) + +netstream.Hook("ReportClosed", function(index) + local report, delayed = get_report(index, true) + if not report then return end + + if delayed then + table.remove(queued_reports, delayed) + else + report.panel:SetClosed() + end +end) + +do + local REPORTS_HEADER = SUI.CreateFont("ReportHeader", "Roboto", 14, 540) + local REPORT_COMMENT = SUI.CreateFont("ReportComment", "Roboto", 13, 540) + local REPORT_BUTTONS = SUI.CreateFont("ReportButtons", "Roboto", 13, 550) + + local Panel = {} + + function Panel:Init() + sui.TDLib.Start() + + self:Blur() + :Background(Color(30, 30, 30, 240)) + + local p_w, p_h = SUI.Scale(300), SUI.Scale(125) + self:SetSize(p_w, p_h) + + local x = p_w * 2 + + if position.value == "Right" then + x = ScrW() + x + else + x = -x + end + + self:SetPos(x, -p_h) + + local top_panel = self:Add("Panel") + top_panel:Dock(TOP) + top_panel:SetTall(SUI.Scale(24)) + top_panel:Background(Color(60, 60, 60, 200)) + + local ply_name = top_panel:Add("DLabel") + ply_name:Dock(LEFT) + ply_name:DockMargin(5, 0, 0, 0) + ply_name:SetTextColor(Color(200, 200, 200)) + ply_name:SetFont(REPORTS_HEADER) + self.ply_name = ply_name + + local scroll = self:Add("SAM.ScrollPanel") + scroll:Dock(FILL) + scroll:DockMargin(5, 5, 5, 5) + scroll.Paint = nil + self.scroll = scroll + + local comment = scroll:Add("DLabel") + comment:Dock(TOP) + comment:SetText("") + comment:SetTextColor(Color(200, 200, 200)) + comment:SetFont(REPORT_COMMENT) + comment:SetMultiline(true) + comment:SetWrap(true) + comment:SetAutoStretchVertical(true) + self.comment = comment + + local bottom = self:Add("Panel") + bottom:Dock(BOTTOM) + bottom:SetTall(SUI.Scale(24)) + self.bottom = bottom + + sui.TDLib.End() + end + + function Panel:GetY() + return (self:GetTall() + 5) * (self.report.pos - 1) + pad_y.value + end + + function Panel:Close() + remove_report(self.report.ply) + end + + local change_state = function(self, text) + self.claim:SetText(text) + self.claim.DoClick = function() end + + self.claim:SUI_TDLib() + :Background(Color(41, 128, 185, 200)) + + timer.Simple(5, function() + if IsValid(self) then + self:Close() + end + end) + + if self:HasReport() == self.report.ply then + self:SetHasReport() + end + + self:FixWide() + end + + function Panel:SetClaimed() + change_state(self, "Case clamied!") + end + + function Panel:SetClosed() + change_state(self, "Case closed!") + end + + function Panel:SetReport(report) + surface.PlaySound("garrysmod/balloon_pop_cute.wav") + + report.panel = self + + self.report = report + self.ply_name:SetText(report.ply:Name()) + self.ply_name:SetWide(self:GetWide()) + end + + local disabled = Color(60, 60, 60, 200) + local click = Color(255, 255, 255, 30) + local button_paint = function(self, w, h) + draw.RoundedBox(0, 0, 0, w, h, self.background) + + if self:GetDisabled() then + draw.RoundedBox(0, 0, 0, w, h, disabled) + else + if self:IsHovered() then + draw.RoundedBox(0, 0, 0, w, h, self.hover) + end + + if self.Depressed then + draw.RoundedBox(0, 0, 0, w, h, click) + end + end + end + + local button_click = function(self) + self.cb(self, self.report.ply) + end + + local background = Color(60, 60, 60, 200) + local hover = Color(14, 134, 204, 100) + function Panel:AddButton(text, cb) + local button = self.bottom:Add("DButton") + button:Dock(LEFT) + button:SetText(text) + button:SetTextColor(Color(200, 200, 200)) + button:SetFont(REPORT_BUTTONS) + button:SetDisabled(true) + button:SetCursor("arrow") + + button.Paint = button_paint + button.DoClick = button_click + + button.background = background + button.hover = hover + + button.cb = cb + button.report = self.report + + return button + end + + function Panel:FixWide() + local wide = 0 + + for _, v in ipairs(self.bottom:GetChildren()) do + v:SizeToContents() + v:SetWide(v:GetWide() + 6) + wide = wide + v:GetWide() + end + + self:SetWide(wide) + + return wide + end + + function Panel:OnRemove() + local reporter = self:HasReport() + if reporter then + netstream.Start("CloseReport", reporter) + self:SetHasReport() + end + end + + function Panel:AddComment(text) + local comment = self.comment + + local old_text = comment:GetText() + if old_text ~= "" then + old_text = old_text .. "\n" + end + + if not self.new then + surface.PlaySound("ambient/water/drip4.wav") + end + + comment:SetText(old_text .. "- " .. text) + comment:SizeToContents() + + self.scroll:ScrollToBottom() + end + + function Panel:HasReport() + return LocalPlayer().sam_has_report + end + + function Panel:SetHasReport(v) + LocalPlayer().sam_has_report = v + end + + local new_animation = function(panel, name) + local new_name = "anim_" .. name + panel["MoveToNew" .. name:upper()] = function(self, new, cb) + if self[new_name] then + table.RemoveByValue(self.m_AnimList, self[new_name]) + end + + self[new_name] = self:NewAnimation(0.2, 0, -1, function() + self[new_name] = nil + if cb then cb() end + end) + + self[new_name].Think = function(_, _, frac) + self[name] = Lerp(frac, self[name], new) + end + end + end + + new_animation(Panel, "x") + new_animation(Panel, "y") + + vgui.Register("SAM.Report", Panel, "EditablePanel") +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/reports/sv_reports.lua b/garrysmod/addons/module_adminmod/lua/sam/reports/sv_reports.lua new file mode 100644 index 0000000..62c4161 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/reports/sv_reports.lua @@ -0,0 +1,163 @@ +if SAM_LOADED then return end + +local sam = sam +local netstream = sam.netstream + +local config = sam.config + +local auto_close +config.hook({"Reports.AutoCloseTime"}, function() + auto_close = sam.parse_length(config.get("Reports.AutoCloseTime", "10m")) * 60 +end) + +hook.Add("SAM.LoadedConfig", "SAM.ReportsMain", function(c) + if not c["Reports.Commands"] then + sam.config.set("Reports.Commands", "goto, bring, return") + end + + if not c["Reports.DutyJobs"] then + sam.config.set("Reports.DutyJobs", "Admin On Duty, Hobo, Medic") + end +end) + +local get_admins = function(ply) + local admins = {} + + local players = player.GetHumans() + for i = 1, #players do + local v = players[i] + if v:HasPermission("see_admin_chat") and v ~= ply then + table.insert(admins, v) + end + end + + return admins +end + +local remove_report_info = function(ply) + local admin = ply.sam_report_admin + + if IsValid(ply) then + ply.sam_has_report = nil + ply.sam_report_admin = nil + netstream.Start(get_admins(), "ReportClosed", ply:EntIndex()) + end + + if IsValid(admin) then + admin.sam_claimed_report = nil + end + + timer.Remove("SAM.Reports." .. ply:EntIndex()) +end + +function sam.player.report(ply, comment) + if not IsValid(ply) then + error("invalid player") + end + + local can_use, time = ply:sam_check_cooldown("NewReport", 4) + if can_use == false then return false, time < 1 and 1 or math.Round(time) end + + if not sam.isstring(comment) then + error("invalid comment") + end + + comment = comment:sub(1, 120) + + local admin = ply.sam_report_admin + if admin then + if IsValid(admin) then + return netstream.Start(admin, "Report", ply, comment) + else + remove_report_info(ply) + end + end + + ply.sam_has_report = true + netstream.Start(get_admins(), "Report", ply, comment) +end + +netstream.async.Hook("ClaimReport", function(res, ply, reporter) + if sam.type(reporter) ~= "Player" or not IsValid(reporter) or not reporter.sam_has_report then + return res(false) + end + + local admin = reporter.sam_report_admin + if not IsValid(admin) then + reporter.sam_report_admin, admin = nil, nil + end + + if admin and admin.sam_claimed_report then + return res(false) + end + + res(true) + + reporter.sam_report_admin = ply + ply.sam_claimed_report = true + + local admins = get_admins(ply) + netstream.Start(admins, "ReportClaimed", reporter) + table.insert(admins, ply) + sam.player.send_message(admins, "report_claimed", { + A = ply, T = {reporter, admin = ply} + }) + + timer.Create("SAM.Reports." .. reporter:EntIndex(), auto_close, 1, function() + remove_report_info(reporter) + + if IsValid(reporter) then + sam.player.send_message(reporter, "report_aclosed") + end + end) +end, function(ply) + return ply:HasPermission("see_admin_chat") +end) + +netstream.Hook("CloseReport", function(ply, reporter) + if sam.type(reporter) ~= "Player" or not IsValid(reporter) then return end + + if ply == reporter.sam_report_admin then + remove_report_info(reporter) + + if IsValid(reporter) then + sam.player.send_message(get_admins(), "report_closed", { + A = ply, T = {reporter, admin = ply} + }) + end + end +end, function(ply) + return ply:HasPermission("see_admin_chat") +end) + +hook.Add("PlayerDisconnected", "SAM.Reports", function(ply) + if ply.sam_has_report then + remove_report_info(ply) + end +end) + +local msgs = { + "Hey there I need some help", + "TP TO ME NOW", + "I JUST GOT RDM'D" +} +concommand.Add("sam_test_reports", function(ply) + if IsValid(ply) and not ply:IsSuperAdmin() then return end + + local bots = player.GetBots() + if #bots < 2 then + for i = 1, 2 - #bots do + RunConsoleCommand("bot") + end + end + + timer.Simple(1, function() + for k, v in ipairs(player.GetBots()) do + timer.Create("SAM.TestReports" .. k, k, 3, function() + if not IsValid(v) then return end + v:sam_set_rank("user") + v:Say("!asay srlion " .. table.Random(msgs)) + end) + end + end) +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/sh_colors.lua b/garrysmod/addons/module_adminmod/lua/sam/sh_colors.lua new file mode 100644 index 0000000..f3f4bd4 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/sh_colors.lua @@ -0,0 +1,19 @@ +if SAM_LOADED then return end + +local colors = { + Red = Color(244, 67, 54), + Blue = Color(13, 130, 223), + Green = Color(0, 230, 64), + White = Color(236, 240, 241), + Black = Color(10, 10, 10) +} + +function sam.get_color(name) + return colors[name] +end + +function sam.add_color(name, color) + if isstring(name) and IsColor(color) then + colors[name] = color + end +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/sh_lang.lua b/garrysmod/addons/module_adminmod/lua/sam/sh_lang.lua new file mode 100644 index 0000000..58c0398 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/sh_lang.lua @@ -0,0 +1,174 @@ +if SAM_LOADED then return end + +local lang = sam.load_file("sam_language.lua", "sh") + +local original = lang +if not isstring(lang) then + lang = "english" +end + +local lang_path = "sam_languages/" .. lang .. ".lua" + +if not file.Exists(lang_path, "LUA") then + lang_path = "sam_languages/english.lua" + if not file.Exists(lang_path, "LUA") then + -- maybe they deleted english lang???? + sam.print("SAM is broken!") + sam.print("Language '" .. tostring(original) .. "' doesn't exist and 'english' language file doesn't exist") + return false + else + sam.print("Language '" .. tostring(original) .. "' doesn't exist falling back to english") + end +end + +local Language = sam.load_file(lang_path, "sh_") + +local sub, find = string.sub, string.find + +local white_color = Color(236, 240, 241) + +do + local args = {} + function sam.add_message_argument(arg, func) + if isstring(arg) and isfunction(func) then + args[arg] = func + end + end + + local insert = function(t, v) + t.__cnt = t.__cnt + 1 + t[t.__cnt] = v + end + + function sam.format_message(msg, tbl, result, result_n) + msg = Language[msg] or msg + + result = result or {} + result.__cnt = result_n or 0 + + local pos = 0 + local start, _end, arg, arg2 = nil, 0, nil, nil + + while true do + start, _end, arg, arg2 = find(msg, "%{ *([%w_%#]+)([^%{}]-) *%}", _end) + if not start then break end + + if pos ~= start then + local txt = sub(msg, pos, start - 1) + if txt ~= "" then + insert(result, white_color) + insert(result, txt) + end + end + + local ma = args[sub(arg, 1, 1)] + if not ma then + insert(result, "{" .. arg .. " " .. arg2 .. "}") + else + ma(result, tbl and tbl[arg], arg, unpack(arg2:Trim():Split(" "))) + end + + pos = _end + 1 + end + + if pos <= #msg then + insert(result, white_color) + insert(result, sub(msg, pos)) + end + + return result + end + + /* + Admin + */ + sam.add_message_argument("A", function(result, admin) + if sam.isconsole(admin) then + -- we need to show that it's the real console!!!!! + insert(result, Color(236, 240, 241)) + insert(result, "*") + insert(result, Color(13, 130, 223)) + insert(result, "Console") + else + if sam.type(admin) == "Player" then + if CLIENT and LocalPlayer() == admin then + insert(result, Color(255, 215, 0)) + insert(result, sam.language.get("You")) + else + insert(result, Color(13, 130, 223)) + insert(result, admin:Name()) + end + else + insert(result, Color(13, 130, 223)) + insert(result, admin) + end + end + end) + + /* + Target(s) + */ + sam.add_message_argument("T", function(result, targets) + for k, v in ipairs(sam.get_targets_list(targets)) do + insert(result, v) + end + end) + + /* + Value(s) + */ + sam.add_message_argument("V", function(result, value) + insert(result, Color(0, 230, 64)) + insert(result, tostring(value)) + end) + + /* + Text(s) + */ + sam.add_message_argument("S", function(result, text, _, color) + insert(result, sam.get_color(color) or white_color) + insert(result, tostring(text)) + end) + + -- https://gist.github.com/fernandohenriques/12661bf250c8c2d8047188222cab7e28 + local hex_rgb = function(hex) + local r, g, b + if #hex == 4 then + r, g, b = tonumber(hex:sub(2, 2), 16) * 17, tonumber(hex:sub(3, 3), 16) * 17, tonumber(hex:sub(4, 4), 16) * 17 + else + r, g, b = tonumber(hex:sub(2, 3), 16), tonumber(hex:sub(4, 5), 16), tonumber(hex:sub(6, 7), 16) + end + + if not r or not g or not b then + return color_white + end + + return Color(r, g, b) + end + + /* + Colored Text(s) + */ + sam.add_message_argument("#", function(result, _, color, ...) + local text = table.concat({...}, " ") + insert(result, hex_rgb(color)) + insert(result, text) + end) +end + +function sam.get_message(msg) + msg = Language[msg] + if not msg then + return false + else + return {Color(236, 240, 241), msg} + end +end + +function sam.language.get(key) + return Language[key] +end + +function sam.language.Add(key, value) + Language[key] = value +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/sh_motd.lua b/garrysmod/addons/module_adminmod/lua/sam/sh_motd.lua new file mode 100644 index 0000000..7ff087a --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/sh_motd.lua @@ -0,0 +1,70 @@ +if SAM_LOADED then return end + +local sam = sam +local config = sam.config +local command = sam.command + +if CLIENT then + config.add_menu_setting("MOTD URL (Leave empty for no MOTD)", function() + local entry = vgui.Create("SAM.TextEntry") + entry:SetPlaceholder("") + entry:SetNoBar(true) + entry:SetConfig("MOTDURL", "") + + return entry + end) +end + +local motd +local load_motd = function() + local url = config.get("MOTDURL", "") + if url == "" then + command.remove_command("motd") + hook.Remove("HUDPaint", "SAM.OpenMOTD") + return + end + + if IsValid(motd) then + motd:Remove() + end + + command.set_category("Menus") + + command.new("motd") + :Help("Open MOTD menu") + :OnExecute(function(ply) + sam.netstream.Start(ply, "OpenMOTD") + end) + :End() + + if CLIENT then + function sam.menu.open_motd() + if IsValid(motd) then + motd:Remove() + end + + motd = vgui.Create("SAM.Frame") + motd:Dock(FILL) + motd:DockMargin(40, 40, 40, 40) + motd:MakePopup() + + function motd.close.DoClick() + motd:Remove() + end + + local html = motd:Add("DHTML") + html:Dock(FILL) + html:OpenURL(url) + end + + sam.netstream.Hook("OpenMOTD", function() + sam.menu.open_motd() + end) + + hook.Add("HUDPaint", "SAM.OpenMOTD", function() + sam.menu.open_motd() + hook.Remove("HUDPaint", "SAM.OpenMOTD") + end) + end +end +config.hook({"MOTDURL"}, load_motd) diff --git a/garrysmod/addons/module_adminmod/lua/sam/sh_permissions.lua b/garrysmod/addons/module_adminmod/lua/sam/sh_permissions.lua new file mode 100644 index 0000000..2e4885d --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/sh_permissions.lua @@ -0,0 +1,88 @@ +if SAM_LOADED then return end + +local permissions = {} + +local give_permission +if SERVER then + local permissions_to_add = {} + + give_permission = function(name, permission) + if sam.ranks.ranks_loaded() then + local rank = sam.ranks.get_rank(name) + if rank and rank.data.permissions[permission] == nil then + sam.ranks.give_permission(name, permission) + end + else + table.insert(permissions_to_add, {name, permission}) + end + end + + hook.Add("SAM.LoadedRanks", "SAM.Command.GivePermissions", function() + for k, v in ipairs(permissions_to_add) do + give_permission(v[1], v[2]) + end + end) +end + +local get_next_Other = function() + for i, v in ipairs(permissions) do + if v.category == "Other" then + return i + end + end + return #permissions + 1 +end + +function sam.permissions.add(permission, category, rank) + if not sam.isstring(category) then + category = "Other" + end + + local permission_data = { + name = permission, + category = category, + rank = rank, + value = value + } + + local index = sam.permissions.get_index(permission) + if not index then + if category ~= "Other" then + table.insert(permissions, get_next_Other(), permission_data) + else + table.insert(permissions, permission_data) + end + hook.Call("SAM.AddedPermission", nil, permission, category, rank, value) + else + permissions[index] = permission_data + hook.Call("SAM.PermissionModified", nil, permission, category, rank, value) + end + + if SERVER and rank then + give_permission(rank, permission) + end +end + +function sam.permissions.get_index(permission) + for i, v in ipairs(permissions) do + if v.name == permission then + return i + end + end +end + +function sam.permissions.remove(permission) + local index = sam.permissions.get_index(permission) + if index then + table.remove(permissions, index) + hook.Call("SAM.RemovedPermission", nil, permission) + end +end + +function sam.permissions.exists(permission) + return sam.permissions.get_index(permission) and true or false +end + +function sam.permissions.get() + return permissions +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/sh_restrictions.lua b/garrysmod/addons/module_adminmod/lua/sam/sh_restrictions.lua new file mode 100644 index 0000000..a8b2c24 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/sh_restrictions.lua @@ -0,0 +1,243 @@ +if SAM_LOADED then return end + +local loaded = false +local load_restrictions = function() + local sam = sam + local config = sam.config + local hook = hook + local SERVER = SERVER + + if CLIENT then + local add_setting = function(body, title, key) + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:SetLabel(title) + + local enable = setting:Add("SAM.ToggleButton") + enable:SetConfig(key, true) + + return setting + end + + config.add_menu_setting("Restrictions", function(body) + local setting = body:Add("SAM.LabelPanel") + setting:Dock(TOP) + setting:DockMargin(8, 6, 8, 0) + setting:SetLabel("Restrictions (Check these settings in ranks' permissions)") + + local setting_body = body:Add("Panel") + setting_body:Dock(TOP) + setting_body:DockMargin(8, 6, 8, 0) + setting_body:DockPadding(8, 0, 8, 0) + + add_setting(setting_body, "Tool (Eg. using button tool)", "Restrictions.Tool") + add_setting(setting_body, "Spawning (Eg. spawning props)", "Restrictions.Spawning") + add_setting(setting_body, "Limits (Eg. how many props can you spawn)", "Restrictions.Limits") + + function setting_body:PerformLayout() + setting_body:SizeToChildren(false, true) + end + end) + end + + local tools = weapons.GetStored("gmod_tool") + if sam.istable(tools) then + if config.get("Restrictions.Tool", true) then + for k, v in pairs(tools.Tool) do + sam.permissions.add(v.Mode, "Tools - " .. (v.Category or "Other"), "user") + end + + hook.Add("CanTool", "SAM.Module.Restrictions", function(ply, _, tool) + if (AdminPrivs and AdminPrivs[ply:GetUserGroup()]) or ply:GetUserGroup() == "ivent" then return true end + if not ply:HasPermission(tool) then + if CLIENT and sam.player.check_cooldown(ply, "ToolNoPermission", 0.1) then + ply:sam_send_message("You don't have permission to use this tool.") + end + return false + end + end) + else + for k, v in pairs(tools.Tool) do + sam.permissions.remove(v.Mode) + end + + hook.Remove("CanTool", "SAM.Module.Restrictions") + end + end + + sam.permissions.add("admin_weapons", "Spawning", "superadmin") + + local function no_permission(ply, name) + ply:sam_play_sound("buttons/button10.wav") + ply:sam_send_message("You don't have permission to spawn {S Blue}.", { + S = name + }) + end + + local spawning = { + PlayerSpawnProp = { + name = "props", + permission = "user", + call_gm = true, + }, + PlayerGiveSWEP = { + name = "give_weapons", + cb = function(_, ply, _, wep) + if wep.sam_AdminOnly and not ply:HasPermission("admin_weapons") then + no_permission(ply, "admin weapons") + return false + end + return true + end, + hook = sam.hook_first, + }, + PlayerSpawnSWEP = { + name = "spawn_weapons", + cb = function(_, ply, _, wep) + if wep.sam_AdminOnly and not ply:HasPermission("admin_weapons") then + no_permission(ply, "admin weapons") + return false + end + return true + end, + hook = sam.hook_first, + }, + PlayerSpawnSENT = { + name = "entities", + check_limit = "sents" + }, + PlayerSpawnNPC = { + name = "npcs", + check_limit = "npcs", + }, + PlayerSpawnVehicle = { + name = "vehicles", + check_limit = "vehicles", + }, + PlayerSpawnRagdoll = { + name = "ragdolls", + permission = "user", + } + } + + local override_lists = { + "Weapon", + "SpawnableEntities" + } + + local function LimitReachedProcess(ply, str) + if not IsValid(ply) then return true end + return ply:CheckLimit(str) + end + + local GAMEMODE = GAMEMODE + if config.get("Restrictions.Spawning", true) then + for k, v in pairs(spawning) do + local name = v + local permission = "superadmin" + local check + local check_limit + local hook = sam.hook_last + if istable(v) then + name = v.name + permission = v.permission or permission + if v.call_gm then + check = GAMEMODE[k] + elseif v.cb then + check = v.cb + end + hook = v.hook or hook + check_limit = v.check_limit + end + + sam.permissions.add(name, "Spawning", permission) + + if SERVER then + hook(k, "SAM.Spawning." .. k .. name, function(ply, ...) + if not (AdminPrivs and AdminPrivs[ply:GetUserGroup()]) and not ply:HasPermission(name) and ply:GetUserGroup() ~= "ivent" then + no_permission(ply, name) + return false + end + + if check_limit then + return LimitReachedProcess(ply, check_limit) + end + + if check then + return check(GAMEMODE, ply, ...) + end + + return true + end) + end + end + + for i = 1, #override_lists do + for k, v in pairs(list.GetForEdit(override_lists[i])) do + v.sam_AdminOnly = v.sam_AdminOnly or v.AdminOnly + v.AdminOnly = false + end + end + else + sam.permissions.add("admin_weapons") + + for k, v in pairs(spawning) do + sam.permissions.remove(istable(v) and v.name or v) + + if SERVER then + hook.Remove(k, "SAM.Spawning." .. k) + end + end + + for i = 1, #override_lists do + for k, v in pairs(list.GetForEdit(override_lists[i])) do + if v.sam_AdminOnly then + v.AdminOnly = v.sam_AdminOnly + end + end + end + end + + local PLAYER = FindMetaTable("Player") + if config.get("Restrictions.Limits", true) then + local get_limit = sam.ranks.get_limit + function PLAYER:GetLimit(limit_type) + return get_limit(self:sam_getrank(), limit_type) + end + + sam.hook_first("PlayerCheckLimit", "SAM.PlayerCheckLimit", function(ply, limit_type, count) + if ply:GetUserGroup() == "ivent" then return true end + local ply_limit = ply:GetLimit(limit_type) + if ply_limit < 0 then return true end + + if count > ply_limit - 1 then + return false + end + + return true + end) + + sam.limit_types = {} + for _, limit_type in SortedPairs(cleanup.GetTable(), true) do + local cvar = GetConVar("sbox_max" .. limit_type) + if cvar then + table.insert(sam.limit_types, limit_type) + end + end + else + sam.limit_types = nil + PLAYER.GetLimit = nil + hook.Remove("PlayerCheckLimit", "SAM.PlayerCheckLimit") + end + + if not loaded then + loaded = true + hook.Call("SAM.LoadedRestrictions") + end +end + +timer.Simple(5, function() + if GAMEMODE.IsSandboxDerived then + sam.config.hook({"Restrictions.Tool", "Restrictions.Spawning", "Restrictions.Limits"}, load_restrictions) + end +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam/sh_util.lua b/garrysmod/addons/module_adminmod/lua/sam/sh_util.lua new file mode 100644 index 0000000..b64d548 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/sh_util.lua @@ -0,0 +1,340 @@ +if SAM_LOADED then return end + +local sam = sam + +function sam.parse_args(str) + local args = {} + local tmp, in_quotes = "", false + for i = 1, #str do + local char = str:sub(i, i) + if char == "\"" then + -- i could use string.find to find the next double quotes but thats gonna be overkill + in_quotes = not in_quotes + if tmp ~= "" or not in_quotes then + args[#args + 1], tmp = tmp, "" + end + elseif char ~= " " or in_quotes then + tmp = tmp .. char + elseif tmp ~= "" then + args[#args + 1], tmp = tmp, "" + end + end + if tmp ~= "" then + args[#args + 1] = tmp + end + return args, #args +end + +function sam.get_targets_list(targets) + if sam.isstring(targets) then + return {Color(244, 67, 54), targets} + end + + local len = #targets + + if len == player.GetCount() and len > 1 then + return {Color(244, 67, 54), sam.language.get("Everyone")} + end + + local admin = targets.admin + local result = {} + local white = Color(236, 240, 241) + for i = 1, len do + local target = targets[i] + + if CLIENT and LocalPlayer() == target then + table.insert(result, Color(255, 215, 0)) + if admin ~= LocalPlayer() then + table.insert(result, sam.language.get("You")) + else + table.insert(result, sam.language.get("Yourself")) + end + elseif admin ~= target then + local name + if sam.isentity(target) and target.Name then + name = target:Name() + else + name = "Unknown" + table.insert(result, white) + table.insert(result, "*") + end + + table.insert(result, Color(244, 67, 54)) + table.insert(result, name) + else + table.insert(result, Color(255, 215, 0)) + table.insert(result, sam.language.get("Themself")) + end + + if i ~= len then + table.insert(result, white) + table.insert(result, ",") + end + end + + return result +end + +function sam.is_steamid(id, err) -- https://stackoverflow.com/questions/6724268/check-if-input-matches-steam-id-format + if sam.isstring(id) and id:match("^STEAM_[0-5]:[0-1]:[0-9]+$") ~= nil then + return true + else + return err and error("invalid steamid", 2) or false + end +end + +function sam.is_steamid64(id, err) + if sam.isstring(id) + and tonumber(id) + and id:sub(1, 7) == "7656119" + and (#id == 17 or #id == 18) then + return true + else + return err and error("invalid steamid64", 2) or false + end +end + +do + local console = {} + + do + local return_console = function() + return "Console" + end + for _, v in ipairs({"SteamID", "SteamID64", "Name", "Nick", "Name"}) do + console[v] = return_console + end + setmetatable(console, { + __tostring = return_console, + MetaName = "console" + }) + end + + function console.IsAdmin() + return true + end + + function console.IsSuperAdmin() + return true + end + + function console:IsUserGroup(name) + return name == "superadmin" + end + + function console.GetUserGroup() + return "superadmin" + end + + function console.sam_getrank() + return "superadmin" + end + + function console.HasPermission() + return true + end + + function console.CanTarget() + return true + end + + function console.CanTargetRank() + return true + end + + function console.GetBanLimit() + return 0 + end + + function console.SetUserGroup() + end + + function sam.isconsole(v) + return v == console + end + + sam.console = console +end + +do + local times = { + "year"; 525600, + "month"; 43800, + "week"; 10080, + "day"; 1440, + "hour"; 60, + "minute"; 1 + } + + for i = 1, #times, 2 do + times[i] = " " .. times[i] + end + + local floor = math.floor + function sam.format_length(mins) -- Thanks to this guide https://stackoverflow.com/a/21323783 + if mins <= 0 then + return "Indefinitely" + elseif mins <= 1 then + return "1 minute" + end + + local str = "" + for i = 1, #times, 2 do + local n1, n2 = times[i + 1] + n2, mins = floor(mins / n1), mins % n1 + + if n2 > 0 then + if str ~= "" then + if mins == 0 then + str = str .. " and " + else + str = str .. ", " + end + end + str = str .. n2 .. times[i] + if n2 > 1 then + str = str .. "s" + end + end + + if mins == 0 then + break + end + end + return str + end +end + +do + local times = { + m = 1, + h = 60, + d = 1440, + w = 10080, + mo = 43800, + y = 525600 + } + + function sam.parse_length(length) + local time, found = tonumber(length), false + if sam.isnumber(length) then + time, found = length, true + elseif time then + found = true + else + time = 0 + for t, u in length:gmatch("(%d+)(%a+)") do + u = times[u] + if u then + time = time + (u * t) + found = true + end + end + end + if not found then return false end + return math.Clamp(time, 0, 31536000) + end + + local times2 = {} + for k, v in SortedPairsByValue(times, true) do + table.insert(times2, k) + table.insert(times2, v) + end + + local floor = math.floor + function sam.reverse_parse_length(mins) -- Thanks to this guide https://stackoverflow.com/a/21323783 + if mins <= 0 then + return "0" + elseif mins <= 1 then + return "1m" + end + + local str = "" + for i = 1, #times2, 2 do + local n1, n2 = times2[i + 1] + n2, mins = floor(mins / n1), mins % n1 + + if n2 > 0 then + if str ~= "" then + str = str .. " " + end + str = str .. n2 .. times2[i] + end + + if mins == 0 then + break + end + end + return str + end +end + +do + if SERVER then + function sam.hook_call(event, ...) + hook.Call(event, nil, ...) + sam.netstream.Start(nil, "HookCall", event, ...) + end + + function sam.client_hook_call(event, ...) + sam.netstream.Start(nil, "HookCall", event, ...) + end + else + local function hook_call(event, ...) + hook.Call(event, nil, ...) + end + sam.netstream.Hook("HookCall", hook_call) + end +end + +if SERVER then + local maps = {} + + for k, v in ipairs(file.Find("maps/*.bsp", "GAME")) do + maps[k] = v:sub(1, -5):lower() + end + + sam.set_global("Maps", maps) +end + +function sam.is_valid_map(name) + local maps = sam.get_global("Maps") + if name:sub(-4) == ".bsp" then + name = name:sub(1, -5) + end + name = name:lower() + for i = 1, #maps do + if maps[i] == name then + return name + end + end + return false +end + +function sam.is_valid_gamemode(name) + name = name:lower() + local gamemodes = engine.GetGamemodes() + for i = 1, #gamemodes do + local gamemode = gamemodes[i] + if sam.isstring(gamemode.name) and gamemode.name:lower() == name then + return true + end + end + return false +end + +function sam.hook_first(event, name, func) + if HOOK_HIGH then + return hook.Add(event, name, func, HOOK_HIGH) + end + + return hook.Add(event, name, func) +end + +function sam.hook_last(event, name, func) + if HOOK_LOW then + return hook.Add(event, name, func, HOOK_LOW) + end + + return hook.Add(event, name, func) +end diff --git a/garrysmod/addons/module_adminmod/lua/sam/sv_sql.lua b/garrysmod/addons/module_adminmod/lua/sam/sv_sql.lua new file mode 100644 index 0000000..9a1b4a6 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam/sv_sql.lua @@ -0,0 +1,415 @@ +if SAM_LOADED then return end + +local SQL = sam.SQL +SQL.SetAddonName("SAM") + +if file.Exists("sam_sql_config.lua", "LUA") then + local config = sam.load_file("sam_sql_config.lua", "sv_") + if sam.istable(config) then + SQL.SetConfig(config) + end +end + +local current_version = 0 + +local versions = {} + +local add_version = function(version, fn) + table.insert(versions, {version, fn}) +end + +local update = function() + for _, v in ipairs(versions) do + local version = v[1] + if version > current_version then + v[2]() + end + end +end + +local check_updates = function() + -- SQL.Query([[DROP TABLE IF EXISTS `sam_players`]]) + -- SQL.Query([[DROP TABLE IF EXISTS `sam_bans`]]) + -- SQL.Query([[DROP TABLE IF EXISTS `sam_ranks`]]) + -- SQL.Query([[DROP TABLE IF EXISTS `sam_version`]]) + -- SQL.Query([[DROP TABLE IF EXISTS `sam_config`]]) + + SQL.TableExists("sam_version", function(exists) + if exists then + SQL.Query([[ + SELECT + `version` + FROM + `sam_version` + ]], function(version_data) + current_version = tonumber(version_data.version) + + if sam.version > current_version then + update() + + SQL.FQuery([[ + UPDATE + `sam_version` + SET + `version` = {1} + ]], {sam.version}):wait() + end + + hook.Call("SAM.DatabaseLoaded") + end, true):wait() + else + update() + + SQL.Query([[ + CREATE TABLE `sam_version`( + `version` SMALLINT(255) + ) + ]]) + + SQL.FQuery([[ + INSERT INTO + `sam_version`(`version`) + VALUES + ({1}) + ]], {sam.version}):wait() + + hook.Call("SAM.DatabaseLoaded") + end + end):wait() +end + +add_version(100, function() + local auto_increment = SQL.IsMySQL() and "AUTO_INCREMENT" or "" + + SQL.FQuery([[ + CREATE TABLE `sam_players`( + `id` INT PRIMARY KEY {1f}, + `steamid` VARCHAR(32), + `name` VARCHAR(255), + `rank` VARCHAR(30), + `expiry_date` INT UNSIGNED, + `first_join` INT UNSIGNED, + `last_join` INT UNSIGNED, + `play_time` MEDIUMINT UNSIGNED + ) + ]], {auto_increment}) + + SQL.FQuery([[ + CREATE TABLE `sam_bans`( + `id` INT PRIMARY KEY {1f}, + `steamid` VARCHAR(32), + `name` VARCHAR(255), + `reason` VARCHAR(255), + `admin` VARCHAR(32), + `unban_date` INT UNSIGNED + ) + ]], {auto_increment}) + + SQL.Query([[ + CREATE TABLE `sam_ranks`( + `name` VARCHAR(30), + `immunity` TINYINT UNSIGNED, + `ban_limit` INT UNSIGNED, + `data` TEXT + ) + ]]) +end) + +add_version(110, function() + SQL.Query([[ + ALTER TABLE `sam_ranks` + ADD `inherit` VARCHAR(30) + ]]) + + SQL.Query([[ + UPDATE + `sam_ranks` + SET + `inherit` = 'user' + WHERE + `name` != 'user' + ]]) + + if not SQL.IsMySQL() then + SQL.Query([[ + ALTER TABLE + `sam_players` RENAME TO `tmp_sam_players` + ]]) + + SQL.Query([[ + CREATE TABLE `sam_players`( + `id` INT PRIMARY KEY, + `steamid` VARCHAR(32), + `name` VARCHAR(255), + `rank` VARCHAR(30), + `expiry_date` INT UNSIGNED, + `first_join` INT UNSIGNED, + `last_join` INT UNSIGNED, + `play_time` INT UNSIGNED + ) + ]]) + + SQL.Query([[ + INSERT INTO + `sam_players`( + `id`, + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + ) + SELECT + `id`, + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` * 60 + FROM + `tmp_sam_players` + ]]) + + SQL.Query([[DROP TABLE `tmp_sam_players`]]) + else + SQL.Query([[ + ALTER TABLE + `sam_players` + MODIFY + `play_time` INT UNSIGNED + ]]) + + SQL.Query([[ + UPDATE `sam_players` + SET `play_time` = `play_time` * 60 + ]]) + end +end) + +add_version(112, function() + if not SQL.IsMySQL() then + SQL.Query([[ + ALTER TABLE + `sam_players` RENAME TO `tmp_sam_players` + ]]) + + SQL.Query([[ + ALTER TABLE + `sam_bans` RENAME TO `tmp_sam_bans` + ]]) + + SQL.Query([[ + CREATE TABLE `sam_players`( + `id` INTEGER PRIMARY KEY, + `steamid` VARCHAR(32), + `name` VARCHAR(255), + `rank` VARCHAR(30), + `expiry_date` INT UNSIGNED, + `first_join` INT UNSIGNED, + `last_join` INT UNSIGNED, + `play_time` INT UNSIGNED + ) + ]]) + + SQL.Query([[ + CREATE TABLE `sam_bans`( + `id` INTEGER PRIMARY KEY, + `steamid` VARCHAR(32), + `name` VARCHAR(255), + `reason` VARCHAR(255), + `admin` VARCHAR(32), + `unban_date` INT UNSIGNED + ) + ]]) + + SQL.Query([[ + INSERT INTO + `sam_players`( + `id`, + `steamid`, + `name`, + `rank`, + `expiry_date`, + `first_join`, + `last_join`, + `play_time` + ) + SELECT + * + FROM + `tmp_sam_players` + ]]) + + SQL.Query([[ + INSERT INTO + `sam_bans`( + `id`, + `steamid`, + `name`, + `reason`, + `admin`, + `unban_date` + ) + SELECT + * + FROM + `tmp_sam_bans` + ]]) + + SQL.Query([[DROP TABLE `tmp_sam_players`]]) + SQL.Query([[DROP TABLE `tmp_sam_bans`]]) + end +end) + +add_version(114, function() + SQL.Query([[ + SELECT + `name`, + `data` + FROM + `sam_ranks` + ]], function(ranks) + for k, v in ipairs(ranks) do + local name, permissions = v.name, v.data + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `data` = {1} + WHERE + `name` == {2} + ]], {sam.pon.encode({permissions = sam.pon.decode(permissions), limits = {}}), name}) + end + end):wait() +end) + +add_version(120, function() + if SQL.IsMySQL() then + SQL.Query([[ALTER TABLE `sam_bans` ADD UNIQUE (`steamid`)]]) + SQL.Query([[ALTER TABLE `sam_bans` DROP `name`]]) + else + SQL.Query([[ + ALTER TABLE + `sam_bans` RENAME TO `tmp_sam_bans` + ]]) + + SQL.Query([[ + CREATE TABLE `sam_bans`( + `id` INTEGER PRIMARY KEY, + `steamid` VARCHAR(32) UNIQUE, + `reason` VARCHAR(255), + `admin` VARCHAR(32), + `unban_date` INT UNSIGNED + ) + ]]) + + SQL.Query([[ + INSERT INTO + `sam_bans`( + `id`, + `steamid`, + `reason`, + `admin`, + `unban_date` + ) + SELECT + `id`, + `steamid`, + `reason`, + `admin`, + `unban_date` + FROM + `tmp_sam_bans` + ]]) + + SQL.Query([[DROP TABLE `tmp_sam_bans`]]) + end + + SQL.Query([[ + SELECT + `name`, + `data` + FROM + `sam_ranks` + ]], function(ranks) + for k, v in ipairs(ranks) do + SQL.FQuery([[ + UPDATE + `sam_ranks` + SET + `data` = {1} + WHERE + `name` = {2} + ]], {util.TableToJSON(sam.pon.decode(v.data)), v.name}) + end + end + ):wait() +end) + +add_version(135, function() + if not SQL.IsMySQL() then return end + SQL.Query([[ALTER TABLE `sam_ranks` MODIFY `name` BLOB;]]) +end) + +add_version(138, function() + SQL.Query([[ + CREATE TABLE `sam_config`( + `key` VARCHAR(40) UNIQUE, + `value` TEXT + ) + ]]) +end) + +add_version(141, function() + if not SQL.IsMySQL() then return end + SQL.Query([[ALTER TABLE `sam_config` MODIFY `value` BLOB;]]) +end) + +add_version(143, function() + SQL.Query([[DROP TABLE IF EXISTS `sam_config`]]) + SQL.Query([[ + CREATE TABLE `sam_config`( + `key` VARCHAR(40) UNIQUE, + `value` BLOB + ) + ]]) +end) + +add_version(147, function() + SQL.Query([[CREATE UNIQUE INDEX sam_players_steamid_index ON `sam_players` (`steamid`);]]) + SQL.Query([[CREATE INDEX sam_bans_steamid_index ON `sam_bans` (`steamid`);]]) +end) + +hook.Add("SAM.DatabaseConnected", "CheckUpdates", check_updates) + +hook.Add("SAM.DatabaseLoaded", "SAM.DatabaseLoaded", function() + sam.DatabaseLoaded = true + sam.print("Connected to database and loaded data successfully.") +end) + +SQL.Connect() + +timer.Create("SAM.CheckForUpdates", 60 * 20, 0, function() + http.Fetch("https://raw.githubusercontent.com/Srlion/SAM-Docs/master/version.txt", function(version, _, _, code) + if code ~= 200 then return end + + version = version:gsub("%s", "") + + if sam.version >= (tonumber(version) or 0) then return end + + sam.print(Color(255, 0, 0), "New update is available for SAM to download") + + local superadmins = {} + for k, v in ipairs(player.GetAll()) do + if v:IsSuperAdmin() then + table.insert(superadmins, v) + end + end + + sam.player.add_text(superadmins, Color(255, 0, 0), "New update is available for SAM to download") + end) +end) diff --git a/garrysmod/addons/module_adminmod/lua/sam_language.lua b/garrysmod/addons/module_adminmod/lua/sam_language.lua new file mode 100644 index 0000000..14868eb --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam_language.lua @@ -0,0 +1 @@ +return "russian" diff --git a/garrysmod/addons/module_adminmod/lua/sam_languages/english.lua b/garrysmod/addons/module_adminmod/lua/sam_languages/english.lua new file mode 100644 index 0000000..2ae3242 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam_languages/english.lua @@ -0,0 +1,281 @@ +return { + You = "You", + Yourself = "Yourself", + Themself = "Themself", + Everyone = "Everyone", + + cant_use_as_console = "You need to be a player to use {S Red} command!", + no_permission = "You don't have permission to use '{S Red}'!", + + cant_target_multi_players = "You can't target multiple players using this command!", + invalid_id = "Invalid id ({S Red})!", + cant_target_player = "You can't target {S Red}!", + cant_target_self = "You can't target your self using {S Red} command!", + player_id_not_found = "Player with id {S Red} is not found!", + found_multi_players = "Found multiple players: {T}!", + cant_find_target = "Can't find a player to target ({S Red})!", + + invalid = "Invalid {S} ({S_2 Red})", + default_reason = "none", + + menu_help = "Open admin mod menu.", + + -- Chat Commands + pm_to = "PM to {T}: {V}", + pm_from = "PM from {A}: {V}", + pm_help = "Send a personal message (PM) to a player.", + + to_admins = "{A} to admins: {V}", + asay_help = "Send a message to admins.", + + mute = "{A} muted {T} for {V}. ({V_2})", + mute_help = "Stop player(s) from sending messages in chat.", + + unmute = "{A} unmuted {T}.", + unmute_help = "Unmute player(s).", + + you_muted = "You are muted.", + + gag = "{A} gagged {T} for {V}. ({V_2})", + gag_help = "Stop player(s) from speaking.", + + ungag = "{A} ungagged {T}.", + ungag_help = "Ungag player(s).", + + -- Fun Commands + slap = "{A} slapped {T}.", + slap_damage = "{A} slapped {T} with {V} damage.", + slap_help = "Slap asses.", + + slay = "{A} slayed {T}.", + slay_help = "Slay player(s).", + + set_hp = "{A} set the hp for {T} to {V}.", + hp_help = "Set health for player(s).", + + set_armor = "{A} set the armor for {T} to {V}.", + armor_help = "Set armor for player(s).", + + ignite = "{A} ignited {T} for {V}.", + ignite_help = "Ignite player(s).", + + unignite = "{A} extinguished {T}.", + unignite_help = "Extinguish player(s).", + + god = "{A} enabled god mode for {T}.", + god_help = "Enable god mode for player(s).", + + ungod = "{A} disabled god mode for {T}.", + ungod_help = "Disable god mode for player(s).", + + freeze = "{A} froze {T}.", + freeze_help = "Freeze player(s).", + + unfreeze = "{A} unfroze {T}.", + unfreeze_help = "Unfreeze player(s).", + + cloak = "{A} cloaked {T}.", + cloak_help = "Cloak player(s).", + + uncloak = "{A} uncloaked {T}.", + uncloak_help = "Uncloak player(s).", + + jail = "{A} jailed {T} for {V}. ({V_2})", + jail_help = "Jail player(s).", + + unjail = "{A} unjailed {T}.", + unjail_help = "Unjail player(s).", + + strip = "{A} stripped weapons from {T}.", + strip_help = "Strip weapons from player(s).", + + respawn = "{A} respawned {T}.", + respawn_help = "Respawn player(s).", + + setmodel = "{A} set the model for {T} to {V}.", + setmodel_help = "Change player(s)'s model.", + + giveammo = "{A} gave {T} {V} ammo.", + giveammo_help = "Give player(s) ammo.", + + scale = "{A} set model scale for {T} to {V}.", + scale_help = "Scale player(s).", + + freezeprops = "{A} froze all props.", + freezeprops_help = "Freezes all props on the map.", + + -- Teleport Commands + dead = "You are dead!", + leave_car = "Leave the vehicle first!", + + bring = "{A} teleported {T}.", + bring_help = "Bring a player.", + + goto = "{A} teleported to {T}.", + goto_help = "Goto a player.", + + no_location = "No previous location to return {T} to.", + returned = "{A} returned {T}.", + return_help = "Return a player to where he was.", + + -- User Management Commands + setrank = "{A} set the rank for {T} to {V} for {V_2}.", + setrank_help = "Set a player's rank.", + setrankid_help = "Set a player's rank by his steamid/steamid64.", + + addrank = "{A} created a new rank {V}.", + addrank_help = "Create a new rank.", + + removerank = "{A} removed rank {V}.", + removerank_help = "Remove a rank.", + + super_admin_access = "superadmin has access to everything!", + + giveaccess = "{A} granted access {V} to {T}.", + givepermission_help = "Give permission to rank.", + + takeaccess = "{A} taken access {V} from {T}.", + takepermission_help = "Take permission from rank.", + + renamerank = "{A} renamed rank {T} to {V}.", + renamerank_help = "Rename rank.", + + changeinherit = "{A} changed the rank to inherit from for {T} to {V}.", + changeinherit_help = "Change the rank to inherit from.", + + rank_immunity = "{A} changed rank {T}'s immunity to {V}.", + changerankimmunity_help = "Change rank immunity.", + + rank_ban_limit = "{A} changed rank {T}'s ban limit to {V}.", + changerankbanlimit_help = "Change rank ban limit.", + + changeranklimit = "{A} changed {V} limit for {T} to {V_2}.", + changeranklimit_help = "Change rank limits.", + + -- Utility Commands + map_change = "{A} changing the map to {V} in 10 seconds.", + map_change2 = "{A} changing the map to {V} with gamemode {V_2} in 10 seconds.", + map_help = "Change current map and gamemode.", + + map_restart = "{A} restarting the map in 10 seconds.", + map_restart_help = "Restart current map.", + + mapreset = "{A} reset the map.", + mapreset_help = "Reset the map.", + + kick = "{A} kicked {T} Reason: {V}.", + kick_help = "Kick a player.", + + ban = "{A} banned {T} for {V} ({V_2}).", + ban_help = "Ban a player.", + + banid = "{A} banned ${T} for {V} ({V_2}).", + banid_help = "Ban a player using his steamid.", + + -- ban message when admin name doesn't exists + ban_message = [[ + + + You are banned by: {S} + + Reason: {S_2} + + You will be unbanned in: {S_3}]], + + -- ban message when admin name exists + ban_message_2 = [[ + + + You are banned by: {S} ({S_2}) + + Reason: {S_3} + + You will be unbanned in: {S_4}]], + + unban = "{A} unbanned {T}.", + unban_help = "Unban a player using his steamid.", + + noclip = "{A} has toggled noclip for {T}.", + noclip_help = "Toggle noclip on player(s).", + + cleardecals = "{A} cleared ragdolls and decals for all players.", + cleardecals_help = "Clear ragdolls and decals for all players.", + + stopsound = "{A} stopped all sounds.", + stopsound_help = "Stop all sounds for all players.", + + not_in_vehicle = "You are not in a vehicle!", + not_in_vehicle2 = "{S Blue} is not in a vehicle!", + exit_vehicle = "{A} forced {T} to get out from a vehicle.", + exit_vehicle_help = "Force a player out of a vehicle.", + + time_your = "Your total time is {V}.", + time_player = "{T} total time is {V}.", + time_help = "Check a player's time.", + + admin_help = "Activate admin mode.", + unadmin_help = "Deactivate admin mode.", + + buddha = "{A} enabled buddha mode for {T}.", + buddha_help = "Make player(s) godmoded when their health is 1.", + + unbuddha = "{A} disabled buddha mode for {T}.", + unbuddha_help = "Disable buddha mode for player(s).", + + give = "{A} gave {T} {V}.", + give_help = "Give player(s) weapon/entity", + + -- DarkRP Commands + arrest = "{A} arrested {T} forever.", + arrest2 = "{A} arrested {T} for {V} seconds.", + arrest_help = "Arrest player(s).", + + unarrest = "{A} unarrested {T}.", + unarrest_help = "Unarrest player(s).", + + setmoney = "{A} set money for {T} to {V}.", + setmoney_help = "Set money for a player.", + + addmoney = "{A} added {V} for {T}.", + addmoney_help = "Add money for a player.", + + door_invalid = "invalid door to sell.", + door_no_owner = "no one owns this door.", + + selldoor = "{A} sold a door/vehicle for {T}.", + selldoor_help = "Unown the door/vehicle you are looking at.", + + sellall = "{A} sold every door/vehicle for {T}.", + sellall_help = "Sell every door/vehicle owned for a player.", + + s_jail_pos = "{A} set a new jail position.", + setjailpos_help = "Resets all jail positions and sets a new one at your location.", + + a_jail_pos = "{A} added a new jail position.", + addjailpos_help = "Adds a jail position at your current location.", + + setjob = "{A} set {T}'s job to {V}.", + setjob_help = "Change a player's job.", + + shipment = "{A} spawned {V} shipment.", + shipment_help = "Spawn a shipment.", + + forcename = "{A} set the name for {T} to {V}.", + forcename_taken = "Name already taken. ({V})", + forcename_help = "Force name for a player.", + + report_claimed = "{A} claimed a report submitted by {T}.", + report_closed = "{A} closed a report submitted by {T}.", + report_aclosed = "Your report is closed. (Time expired)", + + rank_expired = "{V} rank for {T} expired.", + + -- TTT Commands + setslays = "{A} set amount of auto-slays for {T} to {V}.", + setslays_help = "Set amount of rounds to auto-slay a player for.", + + setslays_slayed = "{T} got auto-slayed, slays left: {V}.", + + removeslays = "{A} removed auto-slays for {T}.", + removeslays_help = "Remove auto-slays for a player." +} diff --git a/garrysmod/addons/module_adminmod/lua/sam_languages/russian.lua b/garrysmod/addons/module_adminmod/lua/sam_languages/russian.lua new file mode 100644 index 0000000..559fbb7 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam_languages/russian.lua @@ -0,0 +1,280 @@ +return { + You = "Вы", + Yourself = "Себя", + Themself = "Себя", + Everyone = "Все", + + cant_use_as_console = "Вы должны быть игроком, чтобы использовать команду {S Red}!", + no_permission = "У вас нет разрешения на использование '{S Red}'!", + + cant_target_multi_players = "Вы не можете выбирать несколько игроков этой командой!", + invalid_id = "Неверный идентификатор ({S Red})!", + cant_target_player = "Вы не можете выбрать {S Red}!", + cant_target_self = "Вы не можете нацелиться на самого себя с помощью {S Red}!", + player_id_not_found = "Игрок с id {S Red} не найден!", + found_multi_players = "Найдено несколько игроков: {T}!", + cant_find_target = "Не удалось найти игрока для цели ({S Red})!", + + invalid = "Неверный {S} ({S_2 Red})", + default_reason = "нет", + + menu_help = "Открыть меню админ-мода.", + + -- Chat Commands + pm_to = "ЛС для {T}: {V}", + pm_from = "ЛС от {A}: {V}", + pm_help = "Отправить личное сообщение (PM) игроку.", + + to_admins = "{A} для админов: {V}", + asay_help = "Отправить сообщение администраторам.", + + mute = "{A} отключил чат у {T} на {V}. ({V_2})", + mute_help = "Запретить игрокам отправлять сообщения в чат.", + + unmute = "{A} включил чат у {T}.", + unmute_help = "Включить чат игрокам.", + + you_muted = "Вам запретили писать в чат.", + + gag = "{A} заглушил {T} на {V}. ({V_2})", + gag_help = "Запретить игрокам голосовую связь.", + + ungag = "{A} включил голос для {T}.", + ungag_help = "Снять заглушение с игрока(ов).", + + -- Fun Commands + slap = "{A} шлепнул {T}.", + slap_damage = "{A} шлепнул {T} с уроном {V}.", + slap_help = "Шлепнуть игрока.", + + slay = "{A} убил {T}.", + slay_help = "Убить игрока(ов).", + + set_hp = "{A} установил здоровье {T} на {V}.", + hp_help = "Установить здоровье игроку(ам).", + + set_armor = "{A} установил броню {T} на {V}.", + armor_help = "Установить броню игроку(ам).", + + ignite = "{A} поджёг {T} на {V}.", + ignite_help = "Поджечь игрока(ов).", + + unignite = "{A} потушил {T}.", + unignite_help = "Потушить игрока(ов).", + + god = "{A} включил режим бога для {T}.", + god_help = "Включить бессмертие для игрока(ов).", + + ungod = "{A} выключил режим бога для {T}.", + ungod_help = "Выключить бессмертие для игрока(ов).", + + freeze = "{A} заморозил {T}.", + freeze_help = "Заморозить игрока(ов).", + + unfreeze = "{A} разморозил {T}.", + unfreeze_help = "Разморозить игрока(ов).", + + cloak = "{A} сделал {T} невидимым.", + cloak_help = "Сделать игрока(ов) невидимым.", + + uncloak = "{A} сделал {T} видимым.", + uncloak_help = "Вернуть видимость игрока(ов).", + + jail = "{A} посадил {T} в тюрьму на {V}. ({V_2})", + jail_help = "Посадить игрока(ов) в тюрьму.", + + unjail = "{A} выпустил {T} из тюрьмы.", + unjail_help = "Выпустить игрока(ов) из тюрьмы.", + + strip = "{A} лишил {T} оружия.", + strip_help = "Отобрать оружие у игрока(ов).", + + respawn = "{A} возродил {T}.", + respawn_help = "Воскресить игрока(ов).", + + setmodel = "{A} сменил модель {T} на {V}.", + setmodel_help = "Изменить модель игрока(ов).", + + giveammo = "{A} дал {T} {V} боеприпасов.", + giveammo_help = "Дать ammo игроку(ам).", + + scale = "{A} изменил масштаб модели {T} на {V}.", + scale_help = "Изменить масштаб игрока(ов).", + + freezeprops = "{A} заморозил все пропы.", + freezeprops_help = "Заморозить все пропы на карте.", + + -- Teleport Commands + dead = "Вы мертвы!", + leave_car = "Сначала выйдите из транспорта!", + + bring = "{A} телепортировал {T}.", + bring_help = "Притащить игрока к себе.", + + goto = "{A} телепортировался к {T}.", + goto_help = "Перейти к игроку.", + + no_location = "Нет предыдущей позиции для возвращения {T}.", + returned = "{A} вернул {T}.", + return_help = "Вернуть игрока на предыдущую позицию.", + + -- User Management Commands + setrank = "{A} установил ранг {T} на {V} на {V_2}.", + setrank_help = "Установить ранг игроку.", + setrankid_help = "Установить ранг по steamid/steamid64.", + + addrank = "{A} создал новый ранг {V}.", + addrank_help = "Создать новый ранг.", + + removerank = "{A} удалил ранг {V}.", + removerank_help = "Удалить ранг.", + + super_admin_access = "суперадмин имеет доступ ко всему!", + + giveaccess = "{A} дал доступ {V} {T}.", + givepermission_help = "Выдать разрешение рангу.", + + takeaccess = "{A} забрал доступ {V} у {T}.", + takepermission_help = "Отобрать разрешение у ранга.", + + renamerank = "{A} переименовал ранг {T} в {V}.", + renamerank_help = "Переименовать ранг.", + + changeinherit = "{A} изменил наследование ранга {T} на {V}.", + changeinherit_help = "Изменить, от какого ранга наследуется ранг.", + + rank_immunity = "{A} изменил иммунитет ранга {T} на {V}.", + changerankimmunity_help = "Изменить иммунитет ранга.", + + rank_ban_limit = "{A} изменил лимит банов ранга {T} на {V}.", + changerankbanlimit_help = "Изменить лимит банов ранга.", + + changeranklimit = "{A} изменил лимит {V} для {T} на {V_2}.", + changeranklimit_help = "Изменить лимиты ранга.", + + -- Utility Commands + map_change = "{A} сменит карту на {V} через 10 секунд.", + map_change2 = "{A} сменит карту на {V} с режимом {V_2} через 10 секунд.", + map_help = "Изменить текущую карту и режим игры.", + + map_restart = "{A} перезапустит карту через 10 секунд.", + map_restart_help = "Перезапустить текущую карту.", + + mapreset = "{A} сбросил карту.", + mapreset_help = "Сбросить карту.", + + kick = "{A} выгнал {T}. Причина: {V}.", + kick_help = "Выгнать игрока.", + + ban = "{A} забанил {T} на {V} ({V_2}).", + ban_help = "Забанить игрока.", + + banid = "{A} забанил ${T} на {V} ({V_2}).", + banid_help = "Забанить игрока по steamid.", + + -- ban message when admin name doesn't exists + ban_message = [[ + + + Вы забанены администратором: {S} + + Причина: {S_2} + + До разбана осталось: {S_3}]], + + -- ban message when admin name exists + ban_message_2 = [[ + + + Вы забанены: {S} ({S_2}) + + Причина: {S_3} + + До разбана осталось: {S_4}]], + + unban = "{A} разбанил {T}.", + unban_help = "Разбанить игрока по steamid.", + + noclip = "{A} переключил noclip для {T}.", + noclip_help = "Включить/выключить noclip у игрока(ов).", + + cleardecals = "{A} очистил трупы и следы для всех игроков.", + cleardecals_help = "Очистить трупы и следы на карте.", + + stopsound = "{A} остановил все звуки.", + stopsound_help = "Остановить все звуки для всех игроков.", + + not_in_vehicle = "Вы не в транспорте!", + not_in_vehicle2 = "{S Blue} не в транспорте!", + exit_vehicle = "{A} заставил {T} выйти из транспорта.", + exit_vehicle_help = "Принудительно вывести игрока из транспорта.", + + time_your = "Ваше общее время: {V}.", + time_player = "Общее время {T}: {V}.", time_help = "Проверить время игрока.", + + admin_help = "Включить режим администратора.", + unadmin_help = "Выключить режим администратора.", + + buddha = "{A} включил будда-режим для {T}.", + buddha_help = "Сделать игрока неубиваемым при 1 HP.", + + unbuddha = "{A} выключил будда-режим для {T}.", + unbuddha_help = "Выключить будда-режим.", + + give = "{A} дал {T} {V}.", + give_help = "Дать игроку(ам) предмет/сущность.", + + -- DarkRP Commands + arrest = "{A} арестовал {T} навсегда.", + arrest2 = "{A} арестовал {T} на {V} секунд.", + arrest_help = "Арестовать игрока(ов).", + + unarrest = "{A} освободил {T}.", + unarrest_help = "Освободить игрока из ареста.", + + setmoney = "{A} установил деньги {T} на {V}.", + setmoney_help = "Установить деньги игроку.", + + addmoney = "{A} добавил {V} {T}.", + addmoney_help = "Добавить деньги игроку.", + + door_invalid = "Неверная дверь для продажи.", + door_no_owner = "У этой двери нет владельца.", + + selldoor = "{A} продал дверь/транспорт для {T}.", + selldoor_help = "Продать дверь/транспорт, на который вы смотрите.", + + sellall = "{A} продал все двери/транспорты для {T}.", + sellall_help = "Продать все принадлежащие игроку двери/транспорты.", + + s_jail_pos = "{A} установил новую позицию тюрьмы.", + setjailpos_help = "Сбросить все позиции тюрьмы и установить новую в вашей точке.", + + a_jail_pos = "{A} добавил новую позицию тюрьмы.", + addjailpos_help = "Добавить позицию тюрьмы в текущей локации.", + + setjob = "{A} изменил работу {T} на {V}.", + setjob_help = "Изменить работу игрока.", + + shipment = "{A} создал шипмент {V}.", + shipment_help = "Создать шипмент.", + + forcename = "{A} установил имя {T} в {V}.", + forcename_taken = "Имя уже занято. ({V})", + forcename_help = "Принудительно задать имя игроку.", + + report_claimed = "{A} принял репорт, отправленный {T}.", + report_closed = "{A} закрыл репорт, отправленный {T}.", + report_aclosed = "Ваш репорт закрыт. (Истёкло время)", + + rank_expired = "Ранг {V} для {T} истёк.", + + -- TTT Commands + setslays = "{A} установил авто-ударов для {T} на {V}.", + setslays_help = "Установить число автоматических убийств для игрока.", + + setslays_slayed = "{T} был авто-убит, осталось попыток: {V}.", + + removeslays = "{A} убрал авто-убивания для {T}.", + removeslays_help = "Убрать авто-убивания для игрока." +} diff --git a/garrysmod/addons/module_adminmod/lua/sam_sql_config.lua b/garrysmod/addons/module_adminmod/lua/sam_sql_config.lua new file mode 100644 index 0000000..4fe2905 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sam_sql_config.lua @@ -0,0 +1,29 @@ +if SAM_LOADED then return end + +return { + -- + -- Enable MySQL/sqlite + -- Don't touch this if you don't know what it means + -- + MySQL = true, + + -- + -- MySQL host/ip + -- + Host = "77.91.76.196", + + -- + -- MySQL username + -- + Username = "u3_3OLj1JmZKT", + + -- + -- MySQL password + -- + Password = "R7zGPG!pzbnT^4jI18sw7da=", + + -- + -- MySQL database + -- + Database = "s3_ftvnu", +} diff --git a/garrysmod/addons/module_adminmod/lua/sui/vgui/sam_player_line.lua b/garrysmod/addons/module_adminmod/lua/sui/vgui/sam_player_line.lua new file mode 100644 index 0000000..f19d4f4 --- /dev/null +++ b/garrysmod/addons/module_adminmod/lua/sui/vgui/sam_player_line.lua @@ -0,0 +1,182 @@ +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local TDLib = sui.TDLib +local draw_material = sui.draw_material +local lerp_color = sui.lerp_color + +local GetColor = SUI.GetColor +local RoundedBox = TDLib.RoundedBox +local CircleAvatar = TDLib.LibClasses.CircleAvatar +local CircleClick2 = TDLib.LibClasses.CircleClick2 + +local PLAYER_LINE_NAME = SUI.CreateFont("PlayerLineName", "Roboto Bold", 17) +local PLAYER_LINE_RANK = SUI.CreateFont("PlayerLineRank", "Roboto Bold", 13) +local PLAYER_LINE_STEAMID = SUI.CreateFont("PlayerLineSteamID", "Roboto Medium", 12) + +local PANEL = {} + +function PANEL:Init() + local size = SUI.Scale(34) + + self:Dock(TOP) + self:SetTall(size) + + self.size = size +end + +local rank_Paint = function(s, w, h) + RoundedBox(s.rect, SUI.Scale(10), 0, 0, w, h, s.col) +end + +function PANEL:SetInfo(info) + local size = self.size + + local container + do + local w = SUI.Scale(280) + size + + local _container = self:Add("Panel") + _container:Dock(LEFT) + _container:SetMouseInputEnabled(false) + _container:SetWide(w) + + container = _container:Add("Panel") + container:SetSize(w, size) + + function _container:PerformLayout() + container:Center() + end + end + + do + local avatar = container:Add("Panel") + avatar:Dock(LEFT) + avatar:DockMargin(0, 0, 5, 0) + avatar:SetWide(size) + avatar:SetMouseInputEnabled(false) + CircleAvatar(avatar) + + avatar:SetSteamID(util.SteamIDTo64(info.steamid), size) + end + + do + local top_container = container:Add("Panel") + top_container:Dock(TOP) + top_container:DockMargin(0, 0, 0, 2) + + local name = top_container:Add("SAM.Label") + name:Dock(LEFT) + name:SetFont(PLAYER_LINE_NAME) + self.name = name + + local pname = info.name + if not pname or pname == "" then + name:SetTextColor(GetColor("player_list_names_2")) + self:SetName("N/A") + else + name:SetTextColor(GetColor("player_list_names")) + self:SetName(pname) + end + + if info.rank then + local rank_bg = top_container:Add("Panel") + rank_bg:Dock(LEFT) + rank_bg:DockMargin(5, 0, 0, 0) + + rank_bg.rect = {} + rank_bg.col = info.rank_bg or GetColor("player_list_rank") + rank_bg.Paint = rank_Paint + + local rank = rank_bg:Add("SAM.Label") + rank:Dock(FILL) + rank:DockMargin(SUI.Scale(8), 0, 0, 0) + rank:SetTextColor(GetColor("player_list_rank_text")) + rank:SetFont(PLAYER_LINE_RANK) + rank.bg = rank_bg + + self.rank = rank + self:SetRank(info.rank) + + rank_bg:SetSize(rank:GetTextSize() + SUI.Scale(8) * 2) + end + + top_container:SizeToChildren(true, true) + end + + local steamid = container:Add("SAM.Label") + steamid:Dock(TOP) + steamid:SetTextColor(GetColor("player_list_steamid")) + steamid:SetFont(PLAYER_LINE_STEAMID) + steamid:SetText(info.steamid) + steamid:SizeToContents() + steamid:SetAutoStretchVertical(true) + + self.container = container +end + +function PANEL:SetName(new_name) + local name = self.name + name:SetText(new_name) + name:SizeToContents() + if name:GetWide() > 160 then + name:SetWide(158) + end +end + +function PANEL:SetRank(new_rank) + local rank = self.rank + rank:SetText(new_rank) + rank:SizeToContents() + rank.bg:SetSize(rank:GetTextSize() + SUI.Scale(8) * 2) +end + +function PANEL:Actions() + local container + do + local size = self.size + + local _container = self:Add("Panel") + _container:Dock(RIGHT) + _container:SetWide(size) + + container = _container:Add("Panel") + container:SetSize(size, size) + + function _container:PerformLayout() + container:Center() + end + end + + local actions_button = container:Add("SAM.Button") + actions_button:SetText("") + actions_button:ClearPaint() + + function container:PerformLayout(w, h) + actions_button:SetSize(h, h) + actions_button:Center() + end + + local image = actions_button:Add("SAM.Image") + image:Dock(FILL) + image:SetImage("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sam/dots_verticle.png") + + local current_icon_color = Color(GetColor("actions_button_icon"):Unpack()) + function image:Draw(w, h) + if not h then return end + + if actions_button.Hovered then + lerp_color(current_icon_color, GetColor("actions_button_icon_hover")) + else + lerp_color(current_icon_color, GetColor("actions_button_icon")) + end + + draw_material(nil, w / 2, h / 2, SUI.ScaleEven(20), current_icon_color) + end + + CircleClick2(actions_button, Color(62, 62, 62), 10) + actions_button:Center() + + return actions_button +end + +sui.register("PlayerLine", PANEL, "Panel") diff --git a/garrysmod/addons/molotok/lua/entities/constructor_prop.lua b/garrysmod/addons/molotok/lua/entities/constructor_prop.lua new file mode 100644 index 0000000..401fde8 --- /dev/null +++ b/garrysmod/addons/molotok/lua/entities/constructor_prop.lua @@ -0,0 +1,46 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Constructor Prop" + +function ENT:Initialize() + if CLIENT then return end + + self:SetModel(self.Model or "models/props_c17/oildrum001.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + phys:SetMass(500) + end + + self.hp = self.InitialHealth or 10000 + + self:DrawShadow(true) + self:SetCollisionGroup(COLLISION_GROUP_NONE) +end + +function ENT:OnTakeDamage(dmg) + if not SERVER then return end + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + + self.hp = self.hp - dmg:GetDamage() + + if self.hp <= 0 then + self.Exploded = true + self:Remove() + end + + self:TakePhysicsDamage(dmg) +end + +if CLIENT then + function ENT:Draw() + self:DrawModel() + end +end diff --git a/garrysmod/addons/molotok/lua/entities/item_scrap1.lua b/garrysmod/addons/molotok/lua/entities/item_scrap1.lua new file mode 100644 index 0000000..3d44701 --- /dev/null +++ b/garrysmod/addons/molotok/lua/entities/item_scrap1.lua @@ -0,0 +1,177 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.PrintName = "Scrap" +ENT.Category = "Other" + +ENT.Spawnable = true +ENT.AdminOnly = false +ENT.DoNotDuplicate = false +ENT.AlreadyUsed = false + +if SERVER then + +util.AddNetworkString("DisplayScrap") + +function ENT:SpawnFunction(ply, tr, cls) + + if (!tr.Hit) then return end + + local SpawnPos = tr.HitPos + tr.HitNormal * 20 + local SpawnAng = ply:EyeAngles() + + local ent = ents.Create(cls) + + ent:SetPos(SpawnPos) + ent:SetAngles(SpawnAng) + ent:Spawn() + ent:Activate() + + return ent +end + +local mdltbl = { + "models/combine_turrets/floor_turret_gib1.mdl", + "models/combine_turrets/floor_turret_gib2.mdl", + "models/combine_turrets/floor_turret_gib3.mdl", + "models/combine_turrets/floor_turret_gib4.mdl", + "models/combine_turrets/floor_turret_gib5.mdl", + "models/gibs/manhack_gib01.mdl", + "models/gibs/manhack_gib02.mdl", + "models/gibs/manhack_gib04.mdl", + "models/props_c17/TrapPropeller_Lever.mdl", + "models/props_c17/utilityconnecter005.mdl", + "models/props_c17/utilityconnecter006.mdl", + "models/props_wasteland/gear01.mdl", + "models/props_wasteland/gear02.mdl" +} + +function ENT:Initialize() + + self:SetModel(table.Random(mdltbl)) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:DrawShadow(true) + + self:SetTrigger(true) + self:UseTriggerBounds(true,14) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetMass(25) + phys:SetMaterial( "metal" ) + end + + --SafeRemoveEntityDelayed( self, 30 ) +end + +function ENT:OnTakeDamage( damage ) + self:TakePhysicsDamage(damage) +end + +function ENT:PhysicsCollide(data, phys) + if data.DeltaTime > 0.2 then + if data.Speed > 250 then + self:EmitSound("debris/metal" .. math.random(1, 3) .. ".wav", 75, math.random(90,110), 0.5) + else + self:EmitSound("debris/metal" .. math.random(3, 6) .. ".wav", 75, math.random(90,110), 0.3) + end + end +end + +function ENT:StartTouch( activator ) + if activator:IsPlayer() then self:Use( activator ) end +end + +function ENT:Use(activator, caller) + if self.AlreadyUsed then return end + if activator:IsPlayer() then + activator:SetNWInt("Scrap",activator:GetNWInt("Scrap",0)+math.random(25,75)) + self:EmitSound("ui/item_metal_scrap_pickup.wav") + self:EmitSound("misc/scrap_sound/scrap_rebel.wav",75,math.random(95,105)) + self:Remove() + + net.Start("DisplayScrap") + net.WriteInt(1000,11) + net.Send(activator) + self.AlreadyUsed = true + end +end +end + +if CLIENT then + +function ENT:OnRemove() + local ed = EffectData() + ed:SetOrigin(self:GetPos()) + ed:SetEntity(self) + util.Effect( "entity_remove", ed, true, true ) +end + +net.Receive("DisplayScrap", function(len, ply) + LocalPlayer().UIScrapFade = net.ReadInt(11) +end) + +function ENT:BeingLookedAtByLocalPlayer() + local lp = LocalPlayer() + local trace = util.TraceHull( { + start = lp:GetShootPos(), + endpos = lp:GetShootPos() + lp:GetAimVector() * 200, + filter = lp, + mins = Vector( -3, -3, -3 ), + maxs = Vector( 3, 3, 3 ), + mask = MASK_SHOT, + } ) + + if trace.Entity ~= self then return false end + if trace.HitPos:Distance(LocalPlayer():GetShootPos()) > 200 then return false end + + return true +end + +local halos = {} +local halos_inv = {} + +function ENT:DrawEntityOutline() + if halos_inv[self] then return end + halos[#halos+1] = self + halos_inv[self] = true +end + +hook.Add("PreDrawHalos", "ScrapPickup", function() + if #halos == 0 then return end + halo.Add(halos, Color(255,255,255), 1, 1, 2, true, true) + halos = {} + halos_inv = {} +end) + +local Mat = Material( "sprites/light_ignorez" ) + +function ENT:Draw() + + self:DrawModel() + + if self:BeingLookedAtByLocalPlayer() then + if self.RenderGroup == RENDERGROUP_OPAQUE then + self.OldRenderGroup = self.RenderGroup + self.RenderGroup = RENDERGROUP_TRANSLUCENT + end + self:DrawEntityOutline() + self:DrawModel() + AddWorldTip( nil, "Scrap", nil, self:WorldSpaceCenter(), nil ) + else + if self.OldRenderGroup then + self.RenderGroup = self.OldRenderGroup + self.OldRenderGroup = nil + end + self:DrawModel() + render.SetMaterial( Mat ) render.DrawSprite( self:WorldSpaceCenter(), 32 +math.sin( SysTime()*2 )*4, 32 +math.sin( SysTime()*2 )*4, Color( 255, 255, 255, 192 ) ) + render.DrawSprite( self:WorldSpaceCenter(), 8 +math.cos( SysTime()*2 )*2, 64 +math.cos( SysTime()*2 )*4, Color( 255, 255, 255, 192 ) ) + end +end +end diff --git a/garrysmod/addons/molotok/lua/entities/item_scrapbox1.lua b/garrysmod/addons/molotok/lua/entities/item_scrapbox1.lua new file mode 100644 index 0000000..8a44e99 --- /dev/null +++ b/garrysmod/addons/molotok/lua/entities/item_scrapbox1.lua @@ -0,0 +1,143 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.PrintName = "Scrap Box" +ENT.Category = "Other" + +ENT.Spawnable = true +ENT.AdminOnly = false +ENT.DoNotDuplicate = false +ENT.AlreadyUsed = false + +function ENT:SetupDataTables() + self:NetworkVar( "Int", 0, "Scrap" ) +end + +function ENT:SpawnFunction(ply, tr, cls) + + if (!tr.Hit) then return end + + local SpawnPos = tr.HitPos + tr.HitNormal * 20 + local SpawnAng = ply:EyeAngles() + + local ent = ents.Create(cls) + + ent:SetPos(SpawnPos) + ent:SetAngles(SpawnAng) + ent:Spawn() + ent:Activate() + + return ent +end + +function ENT:Initialize() + if CLIENT then return end + self:SetModel("models/static/nmrih_tool_box_01.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:DrawShadow(true) + + self:SetTrigger(true) + self:UseTriggerBounds(true,14) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetMass(35) + phys:SetMaterial( "metal" ) + end + + self:SetScrap( 0 ) + --SafeRemoveEntityDelayed( self, 30 ) +end + +function ENT:OnTakeDamage( damage ) + self:TakePhysicsDamage(damage) +end + +function ENT:PhysicsCollide(data, phys) + if data.DeltaTime > 0.2 then + self:EmitSound(data.Speed > 250 and "Metal_Box.ImpactHard" or "Metal_Box.ImpactSoft") + end +end +function ENT:StartTouch( activator ) + if activator:IsPlayer() then self:Use( activator ) end +end +function ENT:Use(activator, caller) + if self.AlreadyUsed then return end + if activator:IsPlayer() then + activator:SetNWInt("Scrap",activator:GetNWInt("Scrap")+self:GetScrap()) + self:EmitSound("ui/item_metal_scrap_pickup.wav") + self:EmitSound("misc/scrap_sound/scrap_rebel.wav",75,math.random(95,105)) + self:Remove() + + net.Start("DisplayScrap") + net.WriteInt(1000,11) + net.Send(activator) + self.AlreadyUsed = true + end +end + +if CLIENT then + +function ENT:OnRemove() + local ed = EffectData() + ed:SetOrigin(self:GetPos()) + ed:SetEntity(self) + util.Effect( "entity_remove", ed, true, true ) +end + +function ENT:BeingLookedAtByLocalPlayer() + local lp = LocalPlayer() + local trace = util.TraceHull( { + start = lp:GetShootPos(), + endpos = lp:GetShootPos() + lp:GetAimVector() * 200, + filter = lp, + mins = Vector( -3, -3, -3 ), + maxs = Vector( 3, 3, 3 ), + mask = MASK_SHOT, + } ) + + if trace.Entity ~= self then return false end + if trace.HitPos:Distance(LocalPlayer():GetShootPos()) > 200 then return false end + + return true +end + +local halos = {} +local halos_inv = {} + +function ENT:DrawEntityOutline() + if halos_inv[self] then return end + halos[#halos+1] = self + halos_inv[self] = true +end + +local Mat = Material( "sprites/light_ignorez" ) + +function ENT:Draw() + self:DrawModel() + + if self:BeingLookedAtByLocalPlayer() then + if self.RenderGroup == RENDERGROUP_OPAQUE then + self.OldRenderGroup = self.RenderGroup + self.RenderGroup = RENDERGROUP_TRANSLUCENT + end + self:DrawEntityOutline() + self:DrawModel() + AddWorldTip( self:EntIndex(), "Scraps: "..self:GetScrap(), nil, self:WorldSpaceCenter(), nil ) + else + if self.OldRenderGroup then + self.RenderGroup = self.OldRenderGroup + self.OldRenderGroup = nil + end + self:DrawModel() + render.SetMaterial( Mat ) render.DrawSprite( self:WorldSpaceCenter(), 32 +math.sin( SysTime()*2 )*4, 32 +math.sin( SysTime()*2 )*4, Color( 255, 64, 92, 192 ) ) + render.DrawSprite( self:WorldSpaceCenter(), 8 +math.cos( SysTime()*2 )*2, 64 +math.cos( SysTime()*2 )*4, Color( 255, 64, 92, 192 ) ) + end +end +end diff --git a/garrysmod/addons/molotok/lua/entities/sent_construction.lua b/garrysmod/addons/molotok/lua/entities/sent_construction.lua new file mode 100644 index 0000000..1c5cd0f --- /dev/null +++ b/garrysmod/addons/molotok/lua/entities/sent_construction.lua @@ -0,0 +1,373 @@ +AddCSLuaFile() + +ENT.PrintName = "Construction" +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.Spawnable = false +ENT.AdminOnly = true +ENT.DoNotDuplicate = false +ENT._Seen = false +ENT._Lerp = 0 +ENT._Ler2 = 0 + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "State") + self:NetworkVar("Float", 0, "Progress") + self:NetworkVar("String", 0, "Nick") + self:NetworkVar("Entity", 0, "Owned") + self:NetworkVar("Entity", 1, "Player") + + if SERVER then + self:NetworkVarNotify("Progress", function(self, name, old, new) + if new >= 99.9 then + if self:GetState() == 2 then return end + self:SetState(2) + + if self.EntToBuild == "prop_physics" then + self.buildEnt = ents.Create("constructor_prop") + self.buildEnt:SetPos(self:GetPos() + vector_up*0.5) + self.buildEnt:SetAngles(self:GetAngles()) + + if self.EntModel then + self.buildEnt:SetModel(self.EntModel) + self.buildEnt.Model = self.EntModel + else + self.buildEnt:SetModel(self:GetModel()) + end + + self.buildEnt.InitialHealth = self.EntHealth or 10000 + self.buildEnt.PropName = self:GetNick() or "Constructor Prop" + + self.buildEnt:Spawn() + self.buildEnt:Activate() + self.buildEnt:SetNWEntity("SpikeOwner", self:GetPlayer()) + self.buildEnt.BuilderCost = self.EntToBuildCost or 10 + + local phys = self.buildEnt:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + + if self.EntToBuild == "sw_sania" then + timer.Simple(0, function() + if not IsValid(self.buildEnt) then return end + + local nearest + local dist = 200 + + for _, veh in ipairs(ents.FindInSphere(self:GetPos(), 200)) do + if veh.LVS then + local d = veh:GetPos():Distance(self:GetPos()) + if d < dist then + dist = d + nearest = veh + end + end + end + + if IsValid(nearest) then + local physTarget = nearest:GetBase() or nearest + + self.buildEnt:SetPos(nearest:GetPos() + Vector(0,0,60)) + self.buildEnt:SetAngles(nearest:GetAngles()) + + constraint.Weld(self.buildEnt, physTarget, 0, 0, 0, true) + + print("[REB] РЭБ приварена к LVS:", nearest) + else + print("[REB] LVS техника не найдена рядом") + end + end) + end + + undo.ReplaceEntity(self, self.buildEnt) + cleanup.ReplaceEntity(self, self.buildEnt) + + sound.Play("weapons/building.wav", self:GetPos() + vector_up*16, 80, 120) + self:Remove() + return + else + self.buildEnt = ents.Create(self.EntToBuild) + + if not IsValid(self.buildEnt) then + return + end + + self.buildEnt:SetPos(self:GetPos() + vector_up*0.5) + self.buildEnt:SetAngles(self:GetAngles()) + + self.buildEnt:SetKeyValue("Modification", 1) + + if self:GetNick() == "Rebel Turret" then + self.buildEnt:SetKeyValue("spawnflags", 512) + self.buildEnt:SetSaveValue("m_bHackedByAlyx", true) + end + + self.buildEnt:SetSaveValue("m_bPlacedByPlayer", true) + self.buildEnt:SetSaveValue("m_Efficiency", 2) + self.buildEnt:SetSaveValue("m_CurrentWeaponProficiency", 100) + + self.buildEnt:Spawn() + self.buildEnt:Activate() + self.buildEnt.BuilderCost = self.EntToBuildCost or 10 + + + local owner = self:GetOwned() or self:GetOwner() + self.buildEnt:SetNWEntity("SpikeOwner", owner) + + if self.buildEnt:GetClass():find("sent_ranged_healer") then + self.buildEnt:SetHealEverything(false) + self.buildEnt:SetHealRadius(256) + self.buildEnt:SetHealTeam(1001) + if owner and owner:IsPlayer() then + self.buildEnt:SetPlayer(owner) + end + self.buildEnt:SetReplenishArmor(false) + elseif self.buildEnt:GetClass():find("barricade") then + local phys = self.buildEnt:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + self.buildEnt:SetPos(self:GetPos() + vector_up*-5) + elseif self.buildEnt:GetClass():find("item_") then + self.buildEnt:PhysicsInit(SOLID_VPHYSICS) + if IsValid(self.MyWeldedEnt) then + constraint.Weld(self.buildEnt, self.MyWeldedEnt, 0, 0, 0, 0, true) + else + local phys = self.buildEnt:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + end + elseif self.buildEnt:GetClass() == "bouncingmortar" or + self.buildEnt:GetClass() == "ent_trappopeller_engine" then + local phys = self.buildEnt:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + self.buildEnt:SetAngles(angle_zero) + elseif self.buildEnt:GetClass() == "npc_turret_floor" then + self.buildEnt:SetNWBool("RebSentri", true) + if self:GetNick() == "Rebel Turret" then + local v = self.buildEnt + v.FriendsWithAllPlayerAllies = true + v.PlayerFriendly = true + v.VJ_NPC_Class = {"CLASS_REBEL"} + v.DisableMakingSelfEnemyToNPCs = true + + if math.random() < 0.33 then + v:SetSubMaterial(0, "models/combine_turrets/floor_turret/floor_turret_citizen") + v:SetMaterial("models/combine_turrets/floor_turret/floor_turret_citizen") + elseif math.random() > 0.33 then + v:SetSubMaterial(0, "models/combine_turrets/floor_turret/floor_turret_citizen2") + v:SetMaterial("models/combine_turrets/floor_turret/floor_turret_citizen2") + else + v:SetSubMaterial(0, "models/combine_turrets/floor_turret/floor_turret_citizen4") + v:SetMaterial("models/combine_turrets/floor_turret/floor_turret_citizen4") + end + end + elseif self.buildEnt:GetClass() == "combine_mine" then + local v = self.buildEnt + if math.random() < 0.33 then + v:SetSubMaterial(0, "Models/Combine_Mine/combine_mine_citizen") + v:SetMaterial("Models/Combine_Mine/combine_mine_citizen") + elseif math.random() > 0.33 then + v:SetSubMaterial(0, "Models/Combine_Mine/combine_mine_citizen"..math.random(2,3)) + v:SetMaterial("Models/Combine_Mine/combine_mine_citizen"..math.random(2,3)) + end + timer.Simple(1, function() + if IsValid(v) then + v:StopSound("npc/roller/mine/combine_mine_active_loop1.wav") + end + end) + end + + undo.ReplaceEntity(self, self.buildEnt) + cleanup.ReplaceEntity(self, self.buildEnt) + + sound.Play("weapons/building.wav", self:GetPos() + vector_up*16, 80, 120) + self:Remove() + return + end + else + self:SetColor(Color(255, 255, 255, math.Clamp(new/100*255, 32, 192))) + end + end) + + self:NetworkVarNotify("Owned", function(self, name, old, new) + if old == new then return end + self.Owner = new + self:SetOwner(new) + if NADMOD then + NADMOD.PlayerMakePropOwner(new, self) + end + end) + + self:NetworkVarNotify("Player", function(self, name, old, new) + if old == new then return end + self._Player = new + end) + end +end + +function ENT:GetPlayer() + if SERVER then + return self._Player or self:GetVar("Player") or self:GetOwner() + else + return self:GetVar("Player") or self:GetOwner() + end +end + +function ENT:SetPlayer(ply) + if not SERVER then return end + self._Player = ply + self:SetVar("Player", ply) +end + +function ENT:Initialize() + if CLIENT then return end + + if self.EntModel then + self:SetModel(self.EntModel) + else + self:SetModel("models/props_junk/wood_crate001a.mdl") + end + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self:DrawShadow(false) + self:SetCollisionGroup(COLLISION_GROUP_NONE) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + + self:SetProgress(0) + self:SetState(0) + self:SetNick("") +end + +function ENT:Use(ply) + if ply:IsPlayer() then + if self:IsPlayerHolding() then return end + ply:PickupObject(self) + end +end + +function ENT:OnTakeDamage( damage ) + self:TakePhysicsDamage(damage) +end + +function ENT:PhysicsCollide(data, phys) + if data.DeltaTime > 0.2 then + if data.Speed > 250 then + self:EmitSound("physics/metal/metal_box_impact_hard" .. math.random(1, 3) .. ".wav", 75, math.random(90,110), 0.5) + else + self:EmitSound("physics/metal/metal_box_impact_soft" .. math.random(1, 3) .. ".wav", 75, math.random(90,110), 0.2) + end + end +end + +hook.Add( "GetPreferredCarryAngles", "BuilderCarryAngs", function( ent ) + if ent:GetNWBool("RebSentri") then + return Angle( -10, 0, 0 ) + end +end) + +if SERVER then return end + +function ENT:OnRemove() + if self:GetState() > 0 then + local ed = EffectData() + ed:SetOrigin(self:GetPos()) + ed:SetEntity(self) + util.Effect( "entity_remove", ed, true, true ) + end +end + +function ENT:Think() + if self._Seen != self:BeingLookedAtByLocalPlayer() then + self._Seen = self:BeingLookedAtByLocalPlayer() self._Lerp = SysTime() +0.2 + end + self._Ler2 = Lerp( 0.1, self._Ler2, self:GetProgress() ) +end + +local glow = Material( "models/wireframe" ) +local Mat = Material( "particle/particle_ring_wave_addnofog" ) + +function ENT:Draw() + self:DrawModel( ) + + render.CullMode(MATERIAL_CULLMODE_CW) + self.DrawModel( self ) + render.CullMode(MATERIAL_CULLMODE_CCW) + + if self:GetState() == 0 then + render.SuppressEngineLighting( true ) + render.SetBlend( 0.25 ) render.MaterialOverride( glow ) + render.SetColorModulation( 1, 1, 0 ) + self:DrawModel() + render.SetBlend( 1 ) render.MaterialOverride() + render.SetColorModulation( 1, 1, 1 ) + render.SuppressEngineLighting( false ) + else + local pro = math.Clamp( 1 -self:GetProgress()/100, 0, 1 ) + render.SuppressEngineLighting( true ) + render.SetBlend( pro/4 ) render.MaterialOverride( glow ) + render.SetColorModulation( 1, 1, 1 ) + self:DrawModel() + render.SetBlend( 1 ) render.MaterialOverride() + render.SetColorModulation( 1, 1, 1 ) + render.SuppressEngineLighting( false ) + end + if self._Seen or self._Lerp > SysTime() then + local ler = math.Clamp( ( self._Lerp -SysTime() )/0.2, 0, 1 ) + if self._Seen then ler = 1-ler end + cam.Start2D() + local pos = self:WorldSpaceCenter():ToScreen() + local x, y, radius, seg, per = math.Round( pos.x ), math.Round( pos.y ), ler*40, 360, self._Ler2/100 + for i=1, 6 do surface.DrawCircle( x, y, radius -7 +i, Color( 0, 0, 0, 192*ler ) ) end + per = isnumber( per ) and per or 1 + local cir = {} + table.insert( cir, { x = x, y = y, u = 0.5, v = 0.5 } ) + for i = 0, seg do + if i > math.ceil( seg*per ) then break end + local a = math.rad( ( i/seg )*-360 +180 ) + 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 ) + surface.SetMaterial( Mat ) surface.SetDrawColor( 255, 200, 25, 255*ler ) + surface.DrawPoly( cir ) + draw.TextShadow( { + text = math.Clamp( math.Round( self._Ler2 ), 0, 100 ).."%", + pos = { x, y }, + font = "ScrapFont2", + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = Color( 255, 200, 25, ler*255 ) + }, 1, ler*255 ) + draw.TextShadow( { + text = self:GetNick(), + pos = { x, y -ler*52 }, + font = "TargetID", + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = Color( 255, 200, 25, ler*255 ) + }, 1, ler*255 ) + if IsValid( self:GetOwned() ) then + draw.TextShadow( { + text = "("..self:GetOwned():Nick()..")", + pos = { x, y +ler*52 }, + font = "TargetIDSmall", + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = Color( 255, 200, 25, ler*255 ) + }, 1, ler*255 ) + end + cam.End2D() + end +end diff --git a/garrysmod/addons/molotok/lua/weapons/weapon_builder.lua b/garrysmod/addons/molotok/lua/weapons/weapon_builder.lua new file mode 100644 index 0000000..1b15895 --- /dev/null +++ b/garrysmod/addons/molotok/lua/weapons/weapon_builder.lua @@ -0,0 +1,1161 @@ +AddCSLuaFile() + +if CLIENT then + if buildermenu then buildermenu:Remove() end + SWEP.BounceWeaponIcon = false + language.Add("weapon_builder", "Builder") +end + +if SERVER then + util.AddNetworkString("builder_hitprop") +end + +nextreload = 0 + +Builder_EntitiesTBL = { + ["sw_sania"] = { + cost = 40, + Name = "sw_sania", + PrintName = "РЭБ‑модуль", + mdl = "models/shtormer/sania.mdl", + lua = true, + attachToVehicle = true + } +} + +Builder_EntitiesNum = 0 +Builder_EntitiesLoaded = false + +timer.Simple(1, function() + local tab, ent = list.Get("SpawnableEntities"), scripted_ents.GetList() + local tab2 = list.Get("NPC") + + for k, v in pairs(Builder_EntitiesTBL) do + if v.Name == "prop_physics" then + Builder_EntitiesNum = Builder_EntitiesNum + 1 + continue + end + + if not istable(tab[v.Name]) and not istable(tab2[v.Name]) then + Builder_EntitiesTBL[k] = nil + continue + end + if v.lua and not istable(ent[v.Name]) then + Builder_EntitiesTBL[k] = nil + continue + end + Builder_EntitiesNum = Builder_EntitiesNum + 1 + end + + Builder_EntitiesLoaded = true +end) + +function BuilderIsCreature(ent) + return (ent:IsPlayer() or ent:IsNPC() or ent:IsNextBot() or ent:GetClass():find("prop_ragdoll")) +end + +SWEP.PrintName = "Builder" +SWEP.Slot = 5 +SWEP.SlotPos = 10 +SWEP.DrawAmmo = false +SWEP.HoldType = "melee" +SWEP.DrawCrosshair = false +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.ViewModelFOV = 65 +SWEP.ViewModelFlip = false +SWEP.UseHands = true + +SWEP.ViewModel = "models/weapons/c_hammer1.mdl" +SWEP.WorldModel = "models/weapons/w_hammer1.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 0.5 +SWEP.Primary.Force = 150 +SWEP.Primary.Damage = 30 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) + self:SetWeaponHoldType(self.HoldType) + + self.buildEnt = NULL + self.cooldwn = CurTime() + self.ToBuildCost = 10 + self.ToBuild = "combine_mine" + self.ToBuildmdl = "models/props_combine/combine_mine01.mdl" + self.ToBuildname = "Combine Mine" + + self.Idle = 0 + self.IdleTimer = CurTime() + 1 + + if CLIENT then + self.Ghost = nil + end +end + +function SWEP:Deploy() + self:SendWeaponAnim(ACT_VM_DRAW) + self:SetNextPrimaryFire(CurTime() + self:SequenceDuration()) + self:SetNextSecondaryFire(CurTime() + self:SequenceDuration()) + + self.buildEnt = NULL + self.cooldwn = CurTime() + + self.Idle = 0 + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self:NextThink(CurTime() + self:SequenceDuration()) + + if CLIENT then + if IsValid(self.Ghost) then self.Ghost:Remove() end + self.Ghost = nil + end + + return true +end + +function SWEP:Holster() + if CLIENT and IsValid(self.Ghost) then + self.Ghost:Remove() + self.Ghost = nil + end + return true +end + +function SWEP:OnRemove() + if CLIENT and IsValid(self.Ghost) then + self.Ghost:Remove() + self.Ghost = nil + end +end + +function SWEP:Think() + if SERVER then return end + + local ply = self.Owner + if not IsValid(ply) then return end + + local tr = util.TraceLine({ + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * 200, + filter = ply + }) + + if not IsValid(self.Ghost) or self.Ghost:GetModel() ~= self.ToBuildmdl then + if IsValid(self.Ghost) then self.Ghost:Remove() end + self.Ghost = ClientsideModel(self.ToBuildmdl, RENDERGROUP_TRANSLUCENT) + self.Ghost:SetRenderMode(RENDERMODE_TRANSCOLOR) + self.Ghost:SetColor(Color(255,255,255,80)) + end + + if IsValid(self.Ghost) then + self.Ghost:SetPos(tr.HitPos) + self.Ghost:SetAngles(Angle(0, ply:EyeAngles().y, 0)) + end +end +function SWEP:PrimaryAttack() + self.Owner:LagCompensation(true) + if self._nextBuild and self._nextBuild > CurTime() then + self.Owner:LagCompensation(false) + return + end + self._nextBuild = CurTime() + 0.3 + + local tr = util.TraceLine({ + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, + filter = self.Owner, + mask = MASK_SHOT_HULL, + }) + + if not IsValid(tr.Entity) then + tr = util.TraceHull({ + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, + filter = self.Owner, + mins = Vector(-16, -16, 0), + maxs = Vector(16, 16, 0), + mask = MASK_SHOT_HULL, + }) + end + + self.Owner:SetAnimation(PLAYER_ATTACK1) + + ---------------------------------------------------------------- + -- ОСОБЫЙ СЛУЧАЙ: постройка sw_sania напрямую (без sent_construction) + ---------------------------------------------------------------- + if SERVER and self.ToBuild == "sw_sania" then + self:SetNextPrimaryFire(CurTime() + 0.5) + + if self.Owner:GetNWInt("Scrap") < 300 then + self.Owner:PrintMessage(HUD_PRINTCENTER, "Not enough scrap (need 300)") + self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, 100) + self.Owner:LagCompensation(false) + goto do_anim -- чтобы всё равно сыграть анимацию + end + + self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") - 300) + + local hitPos = tr.HitPos + local hitNormal = tr.HitNormal + + local placePos = hitPos + hitNormal * 5 + local placeAng = hitNormal:Angle() + + local reb = ents.Create("sw_sania") + if IsValid(reb) then + reb:SetPos(placePos) + reb:SetAngles(placeAng) + reb:Spawn() + reb:Activate() + + timer.Simple(0, function() + if not IsValid(reb) then return end + + local veh + for _, e in ipairs(ents.FindInSphere(placePos, 200)) do + if IsValid(e) and (e.LVS or (e.GetBase and IsValid(e:GetBase()) and e:GetBase().LVS)) then + veh = e.LVS and e or e:GetBase() + break + end + end + + if IsValid(veh) then + local vphys = veh:GetPhysicsObject() + local rphys = reb:GetPhysicsObject() + + if IsValid(vphys) and IsValid(rphys) then + constraint.Weld(reb, veh, 0, 0, 0, true, false) + + timer.Create("REB_AutoRemove_" .. reb:EntIndex(), 1, 0, function() + if not IsValid(reb) then return end + if not IsValid(veh) then reb:Remove() end + end) + + self.Owner:EmitSound("weapons/building.wav", 80, 120) + return + end + end + + local phys = reb:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + phys:Sleep() + end + + self.Owner:EmitSound("weapons/building.wav", 80, 120) + end) + end + + goto do_anim + end + + ---------------------------------------------------------------- + -- ПОСТРОЙКА ЧЕРЕЗ sent_construction + ---------------------------------------------------------------- + if SERVER and tr.Hit then + local entData = Builder_EntitiesTBL[self.ToBuild] + if entData then + local existing = ents.FindInSphere(tr.HitPos, 25) + local hasConstruction = false + + for _, e in ipairs(existing) do + if IsValid(e) and e:GetClass() == "sent_construction" then + hasConstruction = true + break + end + end + + if hasConstruction then + self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 75, 100) + else + local build = ents.Create("sent_construction") + build:SetPos(tr.HitPos) + build:SetAngles(Angle(0, self.Owner:EyeAngles().y, 0)) + + build.EntToBuild = entData.Name + build.EntToBuildCost = entData.cost or 10 + build.EntModel = entData.mdl + build.EntHealth = entData.health or 10000 + build:SetNick(entData.PrintName) + build:SetOwned(self.Owner) + build:SetPlayer(self.Owner) + + build:Spawn() + build:Activate() + + local phys = build:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + phys:Sleep() + end + + self.Owner:EmitSound("weapons/building.wav", 80, 120) + end + end + end + + ---------------------------------------------------------------- + -- УДАР ПО УЖЕ СУЩЕСТВУЮЩЕМУ ОБЪЕКТУ + ---------------------------------------------------------------- + if SERVER and (IsValid(tr.Entity) or tr.Entity == Entity(0)) then + local v = tr.Entity + local entClass = v:GetClass() + + -- 1. Пытаемся понять, это ли наша постройка из Builder_EntitiesTBL + local is_built_entity = false + local entity_data = nil + + if IsValid(v) then + for _, entData in pairs(Builder_EntitiesTBL) do + local is_match = false + + -- обычные энтити (turrets, mines и т.п.) + if entData.Name == entClass then + is_match = true + -- наши фортификации, которые после строительства становятся constructor_prop с нужной моделью + elseif entClass == "constructor_prop" and entData.mdl == v:GetModel() then + is_match = true + -- NPC из таблицы + elseif list.Get("NPC")[entData.Name] and entData.Name == entClass then + is_match = true + end + + if is_match then + is_built_entity = true + entity_data = entData + break + end + end + end + + -- 2. Если это наша постройка — ломаем её и возвращаем половину цены + if is_built_entity and entity_data and entClass ~= "sent_construction" then + if not v.BuilderHealth then + v.BuilderHealth = entity_data.health or 500 + v.BuilderMaxHealth = v.BuilderHealth + v.BuilderScrapValue = (entity_data.cost or 0) * 5 -- полная цена в скрапе + end + + v.BuilderHealth = v.BuilderHealth - self.Primary.Damage + local hp = math.max(v.BuilderHealth, 0) + + self.Owner:PrintMessage( + HUD_PRINTCENTER, + "Ломаю постройку (" .. hp .. " HP)" + ) + + self.Owner:EmitSound("weapons/cbar_hit2.wav", 80, 100) + + net.Start("builder_hitprop") + net.WriteVector(tr.HitPos) + net.WriteInt(hp, 16) + net.Send(self.Owner) + + if v.BuilderHealth <= 0 then + local full_cost = v.BuilderScrapValue or ((entity_data.cost or 0) * 5) + local refund = math.floor(full_cost / 2) + + if refund > 0 then + self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + refund) + end + + v:Remove() + end + else + -- СТАРОЕ ПОВЕДЕНИЕ: урон/ремонт всего остального + if entClass ~= "sent_construction" then + if BuilderIsCreature(v) or v:Health() <= 0 or v:GetMaxHealth() <= 0 then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(self.Owner) + dmginfo:SetInflictor(self) + dmginfo:SetDamage(self.Primary.Damage) + dmginfo:SetDamageType(DMG_CLUB) + dmginfo:SetDamagePosition(self.Owner:GetShootPos()) + dmginfo:SetDamageForce(self.Owner:EyeAngles():Forward() * self.Primary.Damage * self.Primary.Force) + SuppressHostEvents(NULL) + v:DispatchTraceAttack(dmginfo, tr) + SuppressHostEvents(self.Owner) + + if v ~= Entity(0) and (not BuilderIsCreature(v) or not (v:GetClass():find("manhack") or v:GetClass():find("scanner") or v:GetClass():find("robot") or v:GetClass():find("turret") or v:GetClass():find("roller") or v:GetClass():find("sentry"))) then + self.Owner:EmitSound(")weapons/cbar_hitbod" .. math.random(1, 3) .. ".wav", 80, math.random(95, 100)) + else + self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100)) + end + else + if v:GetMaxHealth() * 10 <= v:Health() then + self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, math.random(95, 100)) + elseif self.Owner:GetNWInt("Scrap") < 5 then + self.Owner:PrintMessage(HUD_PRINTCENTER, "Not enough scrap") + self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, math.random(95, 100)) + else + v:SetHealth(math.min(v:GetMaxHealth() * 10, v:Health() + 5)) + self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") - 5) + self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100)) + end + end + end + + if entClass == "sent_construction" and self.Owner:GetNWInt("Scrap") >= 5 then + v:SetProgress(math.Round(v:GetProgress() + 100 / v.EntToBuildCost, 2)) + self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") - 5) + self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 80, math.random(95, 100)) + + local pos = tr.HitPos + timer.Simple(0, function() + local ef = EffectData() + ef:SetOrigin(pos) + util.Effect("builderef1", ef) + end) + elseif entClass == "sent_construction" then + self.Owner:PrintMessage(HUD_PRINTCENTER, "Not enough scrap") + self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, math.random(95, 100)) + end + end + + self.cooldwn = CurTime() + 2 + timer.Create("swing" .. self:EntIndex(), 2, 1, function() end) + else + self.cooldwn = CurTime() + 1 + end + + ::do_anim:: + + if tr.Hit then + self.Owner:ViewPunch(Angle(-1, -1, 0)) + self.Weapon:SendWeaponAnim(ACT_VM_HITCENTER) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + else + self.Owner:ViewPunch(Angle(1, 1, 0)) + self:EmitSound(")weapons/wrench_swing.wav", 80, math.random(95, 105)) + self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + end + + self.Idle = 0 + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Owner:LagCompensation(false) +end + +function SWEP:SecondaryAttack() + self.Owner:LagCompensation(true) + + if CLIENT then + local tr = util.TraceLine({ + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, + filter = self.Owner, + mask = MASK_SHOT_HULL, + }) + + if not IsValid(tr.Entity) then + tr = util.TraceHull({ + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, + filter = self.Owner, + mins = Vector(-16, -16, 0), + maxs = Vector(16, 16, 0), + mask = MASK_SHOT_HULL, + }) + end + + if tr.Hit then + local ef = EffectData() + ef:SetOrigin(tr.HitPos) + util.Effect("cball_bounce", ef) + end + end + + if SERVER then + local ply = self.Owner + local eyePos = ply:GetShootPos() + local eyeDir = ply:GetAimVector() + + local bestEnt + local bestDist = 200 + + for _, ent in ipairs(ents.FindByClass("sw_sania")) do + if not IsValid(ent) then continue end + + local toEnt = ent:GetPos() - eyePos + local dist = toEnt:Length() + if dist > 200 then continue end + + local dot = eyeDir:Dot(toEnt:GetNormalized()) + if dot < 0.8 then continue end + + if dist < bestDist then + bestDist = dist + bestEnt = ent + end + end + + if IsValid(bestEnt) then + ply:SetNWInt("Scrap", ply:GetNWInt("Scrap") + 150) + + bestEnt:Remove() + ply:EmitSound("buttons/button19.wav", 75, 90) + ply:LagCompensation(false) + return + end + end + + local tr = util.TraceLine({ + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, -- Дистанция постройки + filter = self.Owner, + mask = MASK_SHOT_HULL, + }) + + if not IsValid(tr.Entity) then + tr = util.TraceHull({ + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, -- Дистанция постройки + filter = self.Owner, + mins = Vector(-16, -16, 0), + maxs = Vector(16, 16, 0), + mask = MASK_SHOT_HULL, + }) + end + + self.Owner:SetAnimation(PLAYER_ATTACK1) + + if SERVER and (IsValid(tr.Entity) or tr.Entity == Entity(0)) then + local v = tr.Entity + local entClass = v:GetClass() + + local is_built_entity = false + local entity_data = nil + + for k, entData in pairs(Builder_EntitiesTBL) do + if entData.Name == entClass then + is_built_entity = true + entity_data = entData + break + elseif entClass == "constructor_prop" and entData.mdl == v:GetModel() then + is_built_entity = true + entity_data = entData + break + end + end + + -- если это наша постройка — ломаем и возвращаем половину стоимости + if is_built_entity and entClass ~= "sent_construction" then + + -- инициализация данных постройки (один раз) + if not v.BuilderHealth then + v.BuilderHealth = entity_data.health or 500 + v.BuilderMaxHealth = v.BuilderHealth + + local full_cost = (entity_data.cost or 0) * 5 + v.BuilderScrapValue = math.floor(full_cost / 2) -- половина цены + end + + -- наносим урон + v.BuilderHealth = v.BuilderHealth - self.Primary.Damage + local hp = math.max(v.BuilderHealth, 0) + + self.Owner:PrintMessage(HUD_PRINTCENTER, "Ломаю постройку (" .. hp .. " HP)") + self.Owner:EmitSound("weapons/cbar_hit2.wav", 80, 100) + + net.Start("builder_hitprop") + net.WriteVector(tr.HitPos) + net.WriteInt(hp, 16) + net.Send(self.Owner) + + -- уничтожение и возврат + if v.BuilderHealth <= 0 then + local refund = v.BuilderScrapValue or 0 + self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + refund) + v:Remove() + end + + return -- ВАЖНО: чтобы не выполнялся старый код ниже + end + + if entClass == "sent_construction" then + local spent_scrap = math.Round(v:GetProgress() / 100 * v.EntToBuildCost * 5) + self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + spent_scrap) + v:Remove() + self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100)) + else + local is_built_entity = false + local entity_data = nil + + for k, entData in pairs(Builder_EntitiesTBL) do + local is_match = false + + if entData.Name == entClass then + is_match = true + elseif entClass == "constructor_prop" and entData.mdl == v:GetModel() then + is_match = true + elseif list.Get("NPC")[entData.Name] and entData.Name == entClass then + is_match = true + end + + if is_match then + is_built_entity = true + entity_data = entData + break + end + end + + if is_built_entity and entity_data then + if not v.BuilderHealth then + v.BuilderHealth = entity_data.health or 500 + v.BuilderMaxHealth = v.BuilderHealth + + -- сразу считаем ВОЗВРАТ (половина цены из таблицы, с учётом *5) + local full_cost = (entity_data.cost or 0) * 5 + v.BuilderScrapValue = math.floor(full_cost / 2) + end + + v.BuilderHealth = v.BuilderHealth - self.Primary.Damage + local hp = math.max(v.BuilderHealth, 0) + + if SERVER then + self.Owner:PrintMessage( + HUD_PRINTCENTER, + "Ломаю постройку (" .. hp .. " HP)" + ) + + self.Owner:EmitSound("weapons/cbar_hit2.wav", 80, 100) + + net.Start("builder_hitprop") + net.WriteVector(tr.HitPos) + net.WriteInt(hp, 16) + net.Send(self.Owner) + + if v.BuilderHealth <= 0 then + local refund = v.BuilderScrapValue or 0 + self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + refund) + v:Remove() + end + end + else + local dmginfo = DamageInfo() + dmginfo:SetAttacker(self.Owner) + dmginfo:SetInflictor(self) + dmginfo:SetDamage(self.Primary.Damage) + dmginfo:SetDamageType(DMG_CLUB) + dmginfo:SetDamagePosition(self.Owner:GetShootPos()) + dmginfo:SetDamageForce(self.Owner:EyeAngles():Forward() * self.Primary.Damage * self.Primary.Force) + SuppressHostEvents(NULL) + v:DispatchTraceAttack(dmginfo, tr) + SuppressHostEvents(self.Owner) + + if BuilderIsCreature(v) then + if not (v:GetClass():find("manhack") or v:GetClass():find("scanner") or v:GetClass():find("robot") or v:GetClass():find("turret") or v:GetClass():find("roller") or v:GetClass():find("sentry")) then + self.Owner:EmitSound(")weapons/cbar_hitbod" .. math.random(1, 3) .. ".wav", 80, math.random(95, 100)) + else + self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100)) + end + else + self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100)) + end + end + end + end + + if tr.Hit then + self.Owner:ViewPunch(Angle(-1, -1, 0)) + self.Weapon:SendWeaponAnim(ACT_VM_HITCENTER) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + else + self.Owner:ViewPunch(Angle(1, 1, 0)) + self:EmitSound(")weapons/wrench_swing.wav", 80, math.random(95, 105)) + self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER) + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + end + + self.Idle = 0 + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Owner:LagCompensation(false) +end + +if CLIENT then + net.Receive("sendToBuilder", function() + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) then + if IsValid(wep.Ghost) then + wep.Ghost:Remove() + end + wep.Ghost = nil + end + end) + + net.Receive("builder_hitprop", function() + local hitPos = net.ReadVector() + local hp = net.ReadInt(16) + + local ef = EffectData() + ef:SetOrigin(hitPos) + util.Effect("cball_bounce", ef) + + local spark = EffectData() + spark:SetOrigin(hitPos) + util.Effect("ManhackSparks", spark) + + -- чат‑индикатор + chat.AddText( + Color(255, 200, 50), "[Молоток] ", + color_white, "Урон по постройке (" .. hp .. " HP)" + ) + end) +end + +if CLIENT then + net.Receive("builderMenu", function() + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == "weapon_builder" then + wep:Reload() + end + end) +end + +function SWEP:Reload() + if not self:IsValid() or not Builder_EntitiesLoaded then return false end + if CurTime() > nextreload then + nextreload = CurTime() + 1 + + if SERVER then + net.Start("builderMenu") + net.Send(self.Owner) + return + end + + local new = false + if not IsValid(buildermenu) then + new = true + buildermenu = vgui.Create("DFrame") + buildermenu:SetSize(ScrW() / 2.2, ScrH() / 1.5) + buildermenu:SetTitle("") + buildermenu:SetIcon("icon16/script_gear.png") + buildermenu:SetDraggable(false) + buildermenu:SetAlpha(1) + buildermenu:ShowCloseButton(false) + end + + local frame = buildermenu + frame:AlphaTo(255, 0.25) + frame:MakePopup() + frame:Center() + frame:Show() + frame.B_Close = false + frame:SetKeyboardInputEnabled(true) + frame:SetMouseInputEnabled(true) + frame.N_Scrap = 0 + + if not new then return end + + function frame:Paint(w, h) + if IsValid(LocalPlayer()) then + frame.N_Scrap = LocalPlayer():GetNWInt("Scrap") + end + + Derma_DrawBackgroundBlur(self, self.m_fCreateTime) + draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 200)) + surface.SetDrawColor(255, 255, 255, (150 + math.sin(RealTime() * 5.2) * 100) * .8) + surface.DrawOutlinedRect(0, 0, w, h) + surface.DrawOutlinedRect(0, 0, w, 25) + + draw.TextShadow({ + text = "Молоток [Металлолом:" .. frame.N_Scrap .. "]", + pos = {26, 12}, + font = "TargetID", + xalign = TEXT_ALIGN_LEFT, + yalign = TEXT_ALIGN_CENTER, + color = Color(255, 255, 255) + }, 1, 255) + end + + function frame:DoClose() + if frame.B_Close then return end + frame.B_Close = true + frame:AlphaTo(1, 0.25) + frame:SetKeyboardInputEnabled(false) + frame:SetMouseInputEnabled(false) + timer.Simple(0.25, function() + if IsValid(frame) and frame.B_Close then frame:Hide() end + end) + end + + local CloseButton = frame:Add("DButton") + local pax = CloseButton + pax:SetText("") + pax:SetPos(810, 3) + pax:SetSize(60, 18) + pax.B_Hover = false + + function pax:Paint(w, h) + draw.TextShadow({ + text = "Close", + pos = {w / 2, h / 2}, + font = "TargetID", + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = (pax.B_Hover and Color(255, 0, 0) or Color(255, 255, 255)) + }, 1, 255) + end + + function pax:DoClick() + frame:DoClose() + end + + function pax:OnCursorEntered() + pax.B_Hover = true + end + + function pax:OnCursorExited() + pax.B_Hover = false + end + + local PropPanel = vgui.Create("ContentContainer", frame) + PropPanel:SetTriggerSpawnlistChange(false) + PropPanel:Dock(FILL) + + local Categorised = {} + Categorised["Фортификации (" .. Builder_EntitiesNum .. ")"] = Builder_EntitiesTBL + + for CategoryName, v in SortedPairs(Categorised) do + local Header = vgui.Create("ContentHeader", PropPanel) + Header:SetText(CategoryName) + PropPanel:Add(Header) + + for k, WeaponTable in SortedPairsByMemberValue(v, "Name") do + if WeaponTable.PrintName ~= nil then + local icon = vgui.Create("ContentIcon", PropPanel) + icon.ClassName = k + + local iconpath = "entities/" .. WeaponTable.Name .. ".png" + if not file.Exists("materials/" .. iconpath, "game") then + iconpath = "vgui/entities/" .. WeaponTable.Name .. ".vmt" + end + + icon:SetMaterial(iconpath) + icon:SetName(WeaponTable.PrintName) + icon:SetToolTip("Cost: " .. (WeaponTable.cost and WeaponTable.cost * 5 or "???")) + + icon.DoClick = function() + frame:DoClose() + net.Start("sendToBuilder") + net.WriteString(icon.ClassName) + net.SendToServer() + end + + PropPanel:Add(icon) + end + end + end + end +end + +if CLIENT then + surface.CreateFont("ScrapFont", { + font = "Digital-7", + size = 50, + weight = 400, + scanlines = true, + antialias = true + }) + + surface.CreateFont("ScrapFont2", { + font = "Digital-7", + size = 36, + weight = 400, + scanlines = true, + antialias = true + }) + + local scrap = Material("icon/refined_metal.png") + + hook.Add("HUDPaint", "BuilderHud", function() + local fade = LocalPlayer().UIScrapFade or 0 + local negative = 0 + local w = 50 + local h = ScrH() / 3 + local wep = LocalPlayer():GetActiveWeapon() + + if IsValid(wep) and wep:GetClass() == "weapon_builder" then + fade = 200 + end + + if fade > negative then + local alpha = math.Clamp(fade, negative, 100) / 100 + if fade > negative then + LocalPlayer().UIScrapFade = math.Clamp(fade, negative, 400) - 1 + w = math.Clamp(fade, negative, 100) / 2 + end + + surface.SetDrawColor(255, 200, 25, (150 + math.sin(RealTime() * 5.2) * 100) * .8 * alpha) + surface.DrawOutlinedRect(w, h, 128, 128) + + surface.SetDrawColor(0, 0, 0, 128 * alpha) + surface.DrawRect(w, h, 128, 128) + + surface.SetDrawColor(255, 255, 255, 255 * alpha) + surface.SetMaterial(scrap) + surface.DrawTexturedRect(w, h - 20, 128, 128) + + draw.SimpleText( + tostring(math.Clamp(LocalPlayer():GetNWInt("Scrap", 0), 0, 99999)), + "ScrapFont", + w + 65, + h + 78, + Color(255, 200, 25, 255 * alpha), + 1, + 0 + ) + end + end) +end +if CLIENT then + local Mat = Material("sprites/light_ignorez") + + if true then + local nam = "builderef1" + local EFFECT = {} + + function EFFECT:Init(data) + local pos = data:GetOrigin() + self:SetRenderBounds(-Vector(32, 32, 32), Vector(32, 32, 32)) + self.Emitter = ParticleEmitter(pos) + + for i = 1, math.random(8, 16) do + local particle = self.Emitter:Add("effects/spark", pos) + particle:SetVelocity(VectorRand():GetNormalized() * math.random(48, 96)) + particle:SetLifeTime(0) + particle:SetDieTime(0.5) + local Siz = math.Rand(1, 3) + particle:SetStartSize(Siz) + particle:SetEndSize(0) + particle:SetStartLength(Siz * 2) + particle:SetEndLength(Siz) + particle:SetStartAlpha(128) + particle:SetEndAlpha(0) + particle:SetColor(255, 255, 128) + particle:SetLighting(false) + particle:SetCollide(true) + particle:SetGravity(Vector(0, 0, -128)) + particle:SetBounce(1) + end + + for i = 1, 4 do + local particle = self.Emitter:Add("particle/particle_smokegrenade1", pos) + if particle then + particle:SetVelocity(VectorRand():GetNormalized() * math.random(32, 64)) + particle:SetLifeTime(0) + particle:SetDieTime(math.Rand(0.5, 1)) + particle:SetStartAlpha(32) + particle:SetEndAlpha(0) + local Siz = math.Rand(12, 24) + particle:SetStartSize(Siz / 4) + particle:SetEndSize(Siz) + particle:SetRoll(math.random(0, 360)) + particle:SetColor(128, 128, 128) + particle:SetGravity(Vector(0, 0, math.random(32, 64))) + particle:SetAirResistance(256) + particle:SetCollide(true) + particle:SetBounce(1) + end + end + + local dlight = DynamicLight(0) + if dlight then + dlight.pos = pos + dlight.r = 255 + dlight.g = 255 + dlight.b = 128 + dlight.brightness = 1 + dlight.decay = 128 + dlight.size = 64 + dlight.dietime = CurTime() + 0.5 + end + + for i = 1, math.random(1, 4) do + local ef = EffectData() + ef:SetOrigin(pos) + util.Effect("builderef2", ef) + end + end + + function EFFECT:Think() return false end + function EFFECT:Render() end + + effects.Register(EFFECT, nam) + end + + if true then + local nam = "builderef2" + local EFFECT = {} + + function EFFECT:Init(data) + self.Entity:SetModel("models/gibs/metal_gib" .. math.random(1, 5) .. ".mdl") + self.Entity:PhysicsInit(SOLID_VPHYSICS) + self.Entity:SetRenderMode(RENDERMODE_TRANSCOLOR) + self.Entity:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self.Entity:SetModelScale(math.Rand(0.25, 0.5)) + self.Entity:SetRenderMode(RENDERMODE_TRANSCOLOR) + + local phys = self.Entity:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:EnableMotion(true) + phys:SetMaterial("gmod_silent") + phys:SetAngles(Angle(math.Rand(0, 360), math.Rand(0, 360), math.Rand(0, 360))) + local vel = VectorRand():GetNormalized() * math.Rand(64, 128) + vel = Vector(vel.x, vel.y, math.abs(vel.z)) + phys:SetVelocity(vel) + phys:Wake() + end + + self.LifeTime = CurTime() + math.Rand(0.5, 1) + self.LifeAlp = 255 + end + + function EFFECT:PhysicsCollide(data, physobj) end + + function EFFECT:Think() + if self.LifeTime < CurTime() then + self.LifeAlp = Lerp(0.05, self.LifeAlp, 0) + self.Entity:SetColor(Color(255, 255, 255, self.LifeAlp)) + if self.LifeAlp <= 1 then return false end + end + return true + end + + function EFFECT:Render() + self.Entity:DrawModel() + end + + effects.Register(EFFECT, nam) + end +end + +if SERVER then + util.AddNetworkString("sendToBuilder") + util.AddNetworkString("builderMenu") + util.AddNetworkString("builder_sync") + + net.Receive("sendToBuilder", function(len, ply) + local cls = net.ReadString() + if not istable(Builder_EntitiesTBL[cls]) then return end + + local weapon = ply:GetActiveWeapon() + if IsValid(weapon) and weapon:GetClass() == "weapon_builder" then + local tab = Builder_EntitiesTBL[cls] + + weapon.ToBuildCost = tab.cost or 10 + weapon.ToBuild = cls + weapon.ToBuildmdl = tab.mdl + weapon.ToBuildname = tab.PrintName + + if IsValid(weapon.buildEnt) then + weapon.buildEnt:Remove() + weapon.buildEnt = nil + end + + net.Start("builder_sync") + net.WriteString(cls) + net.Send(ply) + end + end) +end + +if CLIENT then + net.Receive("builder_sync", function() + local cls = net.ReadString() + local wep = LocalPlayer():GetActiveWeapon() + if not IsValid(wep) or wep:GetClass() ~= "weapon_builder" then return end + + local tab = Builder_EntitiesTBL[cls] + if not tab then return end + + wep.ToBuild = cls + wep.ToBuildmdl = tab.mdl + wep.ToBuildname = tab.PrintName + wep.ToBuildCost = tab.cost or 10 + + if IsValid(wep.Ghost) then + wep.Ghost:Remove() + end + wep.Ghost = nil + end) +end + + + hook.Add("PlayerSpawn", "BuilderForceResetScrap", function(ply) + ply:SetNWInt("Scrap", 500) + end) + +if CLIENT then + local EFFECT = {} + + function EFFECT:Init(data) + local pos = data:GetOrigin() + + self:SetRenderBounds(-Vector(32, 32, 32), Vector(32, 32, 32)) + self.Emitter = ParticleEmitter(pos) + + -- искры + for i = 1, math.random(8, 16) do + local particle = self.Emitter:Add("effects/spark", pos) + if not particle then continue end + + particle:SetVelocity(VectorRand():GetNormalized() * math.random(48, 96)) + particle:SetLifeTime(0) + particle:SetDieTime(0.5) + + local siz = math.Rand(1, 3) + particle:SetStartSize(siz) + particle:SetEndSize(0) + particle:SetStartLength(siz * 2) + particle:SetEndLength(siz) + + particle:SetStartAlpha(255) + particle:SetEndAlpha(0) + + particle:SetColor(255, 255, 128) + particle:SetLighting(false) + particle:SetCollide(true) + particle:SetGravity(Vector(0, 0, -128)) + particle:SetBounce(1) + end + + if self.Emitter then + self.Emitter:Finish() + end + end + + function EFFECT:Think() + return false + end + + function EFFECT:Render() + end + + effects.Register(EFFECT, "builderef1") +end + +hook.Add("PlayerDeath", "BuilderCancelConstructionOnDeath", function(ply) + for _, ent in ipairs(ents.FindByClass("sent_construction")) do + if ent:GetOwned() == ply or ent:GetPlayer() == ply then + ent:Remove() + end + end +end) + + hook.Add("OnNPCKilled", "ScrapDrop", function() end) + hook.Add("PlayerDeath", "ScrapDrop", function() end) diff --git a/garrysmod/addons/my_health_station/lua/entities/health_station/cl_init.lua b/garrysmod/addons/my_health_station/lua/entities/health_station/cl_init.lua new file mode 100644 index 0000000..e1ee42b --- /dev/null +++ b/garrysmod/addons/my_health_station/lua/entities/health_station/cl_init.lua @@ -0,0 +1,5 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/my_health_station/lua/entities/health_station/init.lua b/garrysmod/addons/my_health_station/lua/entities/health_station/init.lua new file mode 100644 index 0000000..684205c --- /dev/null +++ b/garrysmod/addons/my_health_station/lua/entities/health_station/init.lua @@ -0,0 +1,32 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/props_combine/health_charger001.mdl") + self:PhysicsInit(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:SetHealth(100) + self.MaxHealth = 100 +end + +function ENT:Use(activator, caller) + if activator:IsPlayer() then + if activator:Health() < activator:GetMaxHealth() then + activator:SetHealth(math.min(activator:Health() + 25, activator:GetMaxHealth())) + end + + if activator.IsBleeding then + activator:StopBleeding() + end + end +end diff --git a/garrysmod/addons/my_health_station/lua/entities/health_station/shared.lua b/garrysmod/addons/my_health_station/lua/entities/health_station/shared.lua new file mode 100644 index 0000000..d8aae03 --- /dev/null +++ b/garrysmod/addons/my_health_station/lua/entities/health_station/shared.lua @@ -0,0 +1,7 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" + +ENT.PrintName = "Health Station" +ENT.Author = "aboba" +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/other_sweps/lua/autorun/autorun_swep_nmrihconsumables.lua b/garrysmod/addons/other_sweps/lua/autorun/autorun_swep_nmrihconsumables.lua new file mode 100644 index 0000000..ce1d39a --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/autorun/autorun_swep_nmrihconsumables.lua @@ -0,0 +1,313 @@ +AddCSLuaFile() + +--[[ +----------------------------------------------------------------------------------------------------- +Convar Tables +----------------------------------------------------------------------------------------------------- +]] + +-- if CLIENT then + -- hook.Add("PopulateToolMenu", "ToolMenu_NMRIHConsumables", function() + -- spawnmenu.AddToolMenuOption("Options", "Craft_Pig", "ToolMenuOption_ConsumablesNMRIH", "NMRIH Consumables", "", "", function(panel) + -- panel:ClearControls() + -- panel:CheckBox("Enable Phalanx Regen?", "convar_consumablesnmrih_eregen", 1, 0, true) + -- panel:CheckBox("Enable Phalanx Cure?", "convar_consumablesnmrih_edelayinf", 1, 0, true) + -- panel:ControlHelp("Requires Infectious Mod to be mounted.") + -- panel:CheckBox("Enable Genetherapy Armor?", "convar_consumablesnmrih_earmor", 1, 0, true) + -- panel:CheckBox("Enable Genetherapy Cure?", "convar_consumablesnmrih_ecureinf", 1, 0, true) + -- panel:ControlHelp("Requires Infectious Mod to be mounted.") + -- end) + -- end) +-- end + +-- CreateConVar("convar_consumablesnmrih_eregen", "1", {FCVAR_ARCHIVE}, "Enable Phalanx Regen (0 - disabled, 1 - enabled)") +-- CreateConVar("convar_consumablesnmrih_edelayinf", "1", {FCVAR_ARCHIVE}, "Enable Phalanx Cure (0 - disabled, 1 - enabled)") +-- CreateConVar("convar_consumablesnmrih_earmor", "1", {FCVAR_ARCHIVE}, "Enable Phalanx Regen (0 - disabled, 1 - enabled)") +-- CreateConVar("convar_consumablesnmrih_ecureinf", "1", {FCVAR_ARCHIVE}, "Enable Phalanx Regen (0 - disabled, 1 - enabled)") + + +--[[ +----------------------------------------------------------------------------------------------------- +Ammo Tables +----------------------------------------------------------------------------------------------------- +]] + +game.AddAmmoType( { +name = "phalanx", +} ) + +game.AddAmmoType( { +name = "medkit", +} ) + +game.AddAmmoType( { +name = "genetherapy", +} ) + +game.AddAmmoType( { +name = "bandage", +} ) + +--[[ +----------------------------------------------------------------------------------------------------- +Sound Tables +----------------------------------------------------------------------------------------------------- +]] +sound.Add( { + name = "nmrih_consumables_shove", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/shove_01.wav", + "weapons/nmrih/items/shove_02.wav", + "weapons/nmrih/items/shove_03.wav", + "weapons/nmrih/items/shove_04.wav", + "weapons/nmrih/items/shove_05.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_genericfoley", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/clothes_generic_foley_01.wav", + "weapons/nmrih/items/clothes_generic_foley_02.wav", + "weapons/nmrih/items/clothes_generic_foley_03.wav", + "weapons/nmrih/items/clothes_generic_foley_04.wav", + "weapons/nmrih/items/clothes_generic_foley_05.wav", + } +} ) + +sound.Add( { + name = "nmrih_consumables_capremove", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/genetherapy/genetherapy_cap_remove_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_inject", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/genetherapy/genetherapy_inject_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_remove", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/genetherapy/genetherapy_click_01.wav", + } +} ) + +sound.Add( { + name = "nmrih_consumables_medkitopen", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/medkit_unzip_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_medpillsdraw", + channel = CHAN_ITEM, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/medpills_draw_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_medpillsopen", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/medpills_open_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_medpillsshake", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/medpills_shake_01.wav", + "weapons/nmrih/items/medkit/medpills_shake_02.wav", + "weapons/nmrih/items/medkit/medpills_shake_03.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_medshuffle", + channel = CHAN_ITEM, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/medkit_shuffle_01.wav", + "weapons/nmrih/items/medkit/medkit_shuffle_02.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_stichprep", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/stitching_prepare_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_medflesh", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/stitch_fleshy_01.wav", + "weapons/nmrih/items/medkit/stitch_fleshy_02.wav", + "weapons/nmrih/items/medkit/stitch_fleshy_03.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_medsnip", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/scissors_snip_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_medtape", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/medkit/tape_unravel_01.wav", + } +} ) + +sound.Add( { + name = "nmrih_consumables_pillsdraw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/phalanx/pills_draw_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_pillslid", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/phalanx/pills_lid_twist_01.wav", + "weapons/nmrih/items/phalanx/pills_lid_twist_02.wav", + "weapons/nmrih/items/phalanx/pills_lid_twist_03.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_pillslidfinal", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/phalanx/pills_lid_twist_open_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_pillsshake", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/phalanx/pills_shake_01.wav", + "weapons/nmrih/items/phalanx/pills_shake_02.wav", + "weapons/nmrih/items/phalanx/pills_shake_03.wav", + "weapons/nmrih/items/phalanx/pills_shake_04.wav", + "weapons/nmrih/items/phalanx/pills_shake_05.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_entermouth", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/phalanx/pills_enter_mouth_01.wav", + "weapons/nmrih/items/phalanx/pills_enter_mouth_02.wav", + "weapons/nmrih/items/phalanx/pills_enter_mouth_03.wav", + "weapons/nmrih/items/phalanx/pills_enter_mouth_04.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_pillsgulp", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/phalanx/gulp_01.wav", + "weapons/nmrih/items/phalanx/gulp_02.wav", + } +} ) + +sound.Add( { + name = "nmrih_consumables_bandageapply", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/bandage/bandage_apply_01.wav", + "weapons/nmrih/items/bandage/bandage_apply_02.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_bandageunravel1", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/bandage/bandage_unravel_01.wav", + } +} ) +sound.Add( { + name = "nmrih_consumables_bandageunravel2", + channel = CHAN_WEAPON, + volume = 1.0, + level = 55, + pitch = {95, 100}, + sound = { + "weapons/nmrih/items/bandage/bandage_unravel_02.wav", + } +} ) diff --git a/garrysmod/addons/other_sweps/lua/autorun/sh_eft_meds.lua b/garrysmod/addons/other_sweps/lua/autorun/sh_eft_meds.lua new file mode 100644 index 0000000..0b16cf3 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/autorun/sh_eft_meds.lua @@ -0,0 +1,645 @@ +AddCSLuaFile() + +--[[ +----------------------------------------------------------------------------------------------------- +Subcategories +----------------------------------------------------------------------------------------------------- +]] +-- list.Set("WeaponTreeIcons", "EFT", "logo16/logo_eftmedkit_16.png") +list.Set("WeaponTreeIcons", "EFT.Drugs", "logo16/logo_eftdrug_16.png") +list.Set("WeaponTreeIcons", "EFT.Stimulants", "logo16/logo_eftstim_16.png") +list.Set("WeaponTreeIcons", "EFT.Medkits", "logo16/logo_eftmedkit_16.png") +list.Set("WeaponTreeIcons", "EFT.Injury Treatment", "logo16/logo_eftinjury_16.png") + +list.Set("WeaponTreeThumbnails", "EFT.Drugs", "entities/weapon_eft_augmentin.png") +list.Set("WeaponTreeThumbnails", "EFT.Stimulants", "entities/weapon_eft_injectoradrenaline.png") +list.Set("WeaponTreeThumbnails", "EFT.Medkits", "entities/weapon_eft_afak.png") +list.Set("WeaponTreeThumbnails", "EFT.Injury Treatment", "entities/weapon_eft_surgicalkit.png") + +--[[ +----------------------------------------------------------------------------------------------------- +Particle Cache +----------------------------------------------------------------------------------------------------- +]] +-- if CLIENT then + -- game.AddParticles("particles/ep2/antlion_gib_02.pcf") + + -- PrecacheParticleSystem("antlion_gib_02_slime") + -- PrecacheParticleSystem("antlion_gib_02_juice") +-- end + +--[[ +----------------------------------------------------------------------------------------------------- +Convars +----------------------------------------------------------------------------------------------------- +]] + +-- if CLIENT then + -- CreateClientConVar("cl_eftmeds_quick", "0", true, false) +-- end + +--[[ +----------------------------------------------------------------------------------------------------- +Ammo Tables +----------------------------------------------------------------------------------------------------- +]] + +game.AddAmmoType( { +name = "carfirstaidkit", +} ) +game.AddAmmoType( { +name = "salewafirstaidkit", +} ) +game.AddAmmoType( { +name = "cattourniquet", +} ) +game.AddAmmoType( { +name = "augmentin", +} ) +game.AddAmmoType( { +name = "grizzlykit", +} ) +game.AddAmmoType( { +name = "surgicalkit", +} ) +game.AddAmmoType( { +name = "alusplint", +} ) +game.AddAmmoType( { +name = "afak", +} ) +game.AddAmmoType( { +name = "injectoradrenaline", +} ) +game.AddAmmoType( { +name = "morphine", +} ) +game.AddAmmoType( { +name = "l1", +} ) +game.AddAmmoType( { +name = "trimadol", +} ) +game.AddAmmoType( { +name = "propital", +} ) +game.AddAmmoType( { +name = "etg", +} ) +game.AddAmmoType( { +name = "tg12", +} ) +game.AddAmmoType( { +name = "anaglin", +} ) +game.AddAmmoType( { +name = "esmarch", +} ) + +--[[ +----------------------------------------------------------------------------------------------------- +Sound Tables +----------------------------------------------------------------------------------------------------- +]] + +---- + +----------------------------------------------------------------------------------------- Other + +--------------------------------------- Grizzly +sound.Add( { + name = "MedsGrizzly.Draw", + channel = CHAN_ITEM, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/grizzly/item_medkit_grizzly_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsGrizzly.Open", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/grizzly/item_medkit_grizzly_01_open.wav", + } +} ) +sound.Add( { + name = "MedsGrizzly.Take", + channel = CHAN_ITEM, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/grizzly/item_medkit_grizzly_02_medtake.wav", + } +} ) + +--------------------------------------- Bandage +sound.Add( { + name = "MedsBandage.Open", + channel = CHAN_ITEM, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/bandage/item_bandage_01_open.wav", + } +} ) +sound.Add( { + name = "MedsBandage.Take", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/bandage/item_bandage_02_bandagetake.wav", + } +} ) +sound.Add( { + name = "MedsBandage.Use", + channel = CHAN_WEAPON, + volume = 0.4, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/bandage/item_bandage_03_use.wav", + } +} ) +sound.Add( { + name = "MedsBandage.End", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/bandage/item_bandage_04_end.wav", + } +} ) + +--------------------------------------- Medkit +sound.Add( { + name = "MedsMedkit.Draw", + channel = CHAN_ITEM, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/medkit/item_medkit_ai_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsMedkit.Open", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/medkit/item_medkit_ai_01_open.wav", + } +} ) +sound.Add( { + name = "MedsMedkit.Take", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/medkit/item_medkit_ai_02_takesyringe.wav", + } +} ) +sound.Add( { + name = "MedsMedkit.Kolpa", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/medkit/item_medkit_ai_03_kolpachok.wav", + } +} ) +sound.Add( { + name = "MedsMedkit.Injection", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/medkit/item_medkit_ai_04_injection.wav", + } +} ) +sound.Add( { + name = "MedsMedkit.Throw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/medkit/item_medkit_ai_05_throwsyringe.wav", + } +} ) +sound.Add( { + name = "MedsMedkit.Putaway", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/medkit/item_medkit_ai_06_putaway.wav", + } +} ) + +--------------------------------------- Pills +sound.Add( { + name = "MedsBlister.Draw", + channel = CHAN_BODY, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/pills/item_pillsblister_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsBlister.Open", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 100}, + sound = { + "weapons/eft/pills/item_pillsblister_01_open.wav", + } +} ) +sound.Add( { + name = "MedsPillsBottle.Draw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 110}, + sound = { + "weapons/eft/pills/item_pillsbottle_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsPillsBottle.Open", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 100}, + sound = { + "weapons/eft/pills/item_pillsbottle_01_open.wav", + } +} ) +sound.Add( { + name = "MedsPillsBottle.Pilltake", + channel = CHAN_ITEM, + volume = 1.0, + level = 65, + pitch = {95, 100}, + sound = { + "weapons/eft/pills/item_pillsbottle_02_pilltake.wav", + } +} ) +sound.Add( { + name = "MedsPillsBottle.Use", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 100}, + sound = { + "weapons/eft/pills/item_pillsbottle_03_use.wav", + } +} ) +sound.Add( { + name = "MedsPillsBottle.Close", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 100}, + sound = { + "weapons/eft/pills/item_pillsbottle_04_close.wav", + } +} ) +sound.Add( { + name = "MedsPillsBottle.Putaway", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/pills/item_pillsbottle_05_putaway.wav", + } +} ) + +--------------------------------------- Salewa +sound.Add( { + name = "MedsSalewa.Draw", + channel = CHAN_ITEM, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/salewa/item_medkit_salewa_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsSalewa.Open", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/salewa/item_medkit_salewa_01_open.wav", + } +} ) +sound.Add( { + name = "MedsSalewa.Bandage", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/salewa/item_medkit_salewa_02_bandagetake.wav", + } +} ) +sound.Add( { + name = "MedsSalewa.Use", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/salewa/item_medkit_salewa_03_use.wav", + } +} ) +sound.Add( { + name = "MedsSalewa.End", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/salewa/item_medkit_salewa_04_end.wav", + } +} ) + +--------------------------------------- CAT +sound.Add( { + name = "MedsCat.Draw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/cat/item_cat_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsCat.Use", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/cat/item_cat_01_use.wav", + } +} ) +sound.Add( { + name = "MedsCat.Fasten", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/cat/item_cat_02_fasten.wav", + } +} ) +sound.Add( { + name = "MedsCat.Putaway", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/cat/item_cat_03_putaway.wav", + } +} ) + +--------------------------------------- Medkit +sound.Add( { + name = "MedsSurgical.Draw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.SicDraw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_01_scissors_draw.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.SciUse", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_02_scissors_use.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.SciPutaway", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_03_scissors_putaway.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.PickDraw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_04_picker_draw.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.PickUse", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_05_picker_use.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.PickPutaway", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_06_picker_putaway.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.StaplerDraw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_07_stapler_draw.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.StaperUse", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_08_stapler_use.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.StaplerPutaway", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_09_stapler_putaway.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.Close", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_10_close.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.Button1", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_11_close_button.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.Button2", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_12_close_button.wav", + } +} ) +sound.Add( { + name = "MedsSurgical.Putaway", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/surgicalkit/item_surgicalkit_13_putaway.wav", + } +} ) + +--------------------------------------- Splint +sound.Add( { + name = "MedsSplint.Start", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/splint/item_splint_00_start.wav", + } +} ) +sound.Add( { + name = "MedsSplint.Middle", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/splint/item_splint_01_middle.wav", + } +} ) +sound.Add( { + name = "MedsSplint.End", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/splint/item_splint_02_end.wav", + } +} ) + +--------------------------------------- Splint +sound.Add( { + name = "MedsInjector.Draw", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/injector/item_injector_00_draw.wav", + } +} ) +sound.Add( { + name = "MedsInjector.Open", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/injector/item_injector_01_kolpachok.wav", + } +} ) +sound.Add( { + name = "MedsInjector.Use", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/injector/item_injector_02_injection.wav", + } +} ) +sound.Add( { + name = "MedsInjector.End", + channel = CHAN_WEAPON, + volume = 1.0, + level = 65, + pitch = {95, 115}, + sound = { + "weapons/eft/injector/item_injector_03_putaway.wav", + } +} ) diff --git a/garrysmod/addons/other_sweps/lua/sef_addon/sef_eft_meds_effects.lua b/garrysmod/addons/other_sweps/lua/sef_addon/sef_eft_meds_effects.lua new file mode 100644 index 0000000..139ca83 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/sef_addon/sef_eft_meds_effects.lua @@ -0,0 +1,35 @@ +local AffectedEffects = { "Fatigue", "DarkVision", "Stunned" } +StatusEffects.OnPainkillers = { + Name = "On Painkillers", + Icon = "SEF_Icons/SEF_onpainkillers.png", + Desc = "You're on painkillers, some debuffs are delayed.", + Type = "BUFF", + EffectBegin = function(ent) + if not ent.GetTime then + ent.GetTime = {} + end + for _, effectName in ipairs(AffectedEffects) do + ent.GetTime[effectName] = 0 + end + ent.GetTimeOnPainkillers = 0 + ent:GetTimeLeft("OnPainkillers") + end, + Effect = function(ent, time) + for _, effectName in ipairs(AffectedEffects) do + if ent:HaveEffect(effectName) then + local remainingTime = ent:GetTimeLeft(effectName) + ent.GetTime[effectName] = remainingTime + ent.GetTime[effectName] + -- print(effectName .. " remaining time: " .. remainingTime) + ent:RemoveEffect(effectName) + end + end + end, + EffectEnd = function(ent) + -- print("Remaining time for DarkVision: " .. (ent.GetTime["DarkVision"] or "Not found")) + + for _, effectName in ipairs(AffectedEffects) do + if ent.GetTime[effectName] ~= 0 then + ent:ApplyEffect(effectName, ent.GetTime[effectName] - ent.GetTimeOnPainkillers) + end + end + end +} diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_afak.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_afak.lua new file mode 100644 index 0000000..9b08ad1 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_afak.lua @@ -0,0 +1,215 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_afak" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "AFAK First Aid Kit" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals up to 30HP +Cures Bleeding +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Medkits" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/afak/v_meds_afak.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/afak/w_meds_afak.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "afak" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 400 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 30 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_afak" +local ID_PRIMARYAMMO = "afak" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + if owner:HaveEffect("Bleeding") then + owner:RemoveEffect("Bleeding") + owner:RemoveAmmo(30, ID_PRIMARYAMMO) + end + end + + if INI_VIVO == true then + math.Round(OJSWounds:HealHP(owner, self.MaxHeal, VarHealHealth), 0) + if (OJSWounds:HasStatusWholeBody(owner, "Bleed")) then + OJSWounds:HealBleed(owner) + owner:RemoveAmmo(30, ID_PRIMARYAMMO) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(VarHealHealth, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IniAnimBandage = 1 + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + + timer.Simple(0.6, function() -- Audio Timer + if IsValid(self) and IsValid(self.Owner) then + if owner:GetActiveWeapon():GetClass() == ID_WEAPON then owner:EmitSound("MedsGrizzly.Open") end + end + end) +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if SERVER then + if self.IniHeal == 1 and self.IdleTimer <= CurTime() then -- Initialize Heal + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + self:Heal(owner) + end + + if self.IniAnimBandage == 1 and self.AnimTimes <= 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + if math.random(1,2) == 1 then + self:SendWeaponAnim(ACT_VM_RECOIL2) + else + self:SendWeaponAnim(ACT_VM_HITRIGHT) + end + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimBandage == 1 and self.AnimTimes == 2 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_RECOIL3) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end + end + + -- if SERVER then return end + -- if GetConVar("cl_eftmeds_quick"):GetBool() then + -- LocalPlayer():ConCommand("+attack") + -- if self.IniHeal == 1 then + -- print("hi") + -- end + -- end +end + + +function SWEP:Holster() + if CLIENT then LocalPlayer():ConCommand("-attack") end + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -4, 3) + local offsetAng = Angle(-0, -0, -180) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_alusplint.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_alusplint.lua new file mode 100644 index 0000000..d04cc63 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_alusplint.lua @@ -0,0 +1,179 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_alusplint" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Aluminium Splint" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Cures Fatigue +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Injury Treatment" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/alusplint/v_meds_alusplint.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/alusplint/w_meds_alusplint.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "alusplint" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 5 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_alusplint" +local ID_PRIMARYAMMO = "alusplint" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + owner:RemoveEffect("Fatigue") + end + + if INI_VIVO == true then + OJSWounds:HealFracture(owner) + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 + + timer.Simple(1.5, function() -- Audio Timer + if IsValid(self) and IsValid(self.Owner) then + if owner:GetActiveWeapon():GetClass() == "weapon_eft_alusplint" then owner:EmitSound("MedsSplint.Middle") end + end + end) +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(0, -5, -0) + local offsetAng = Angle(-90, 180, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_anaglin.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_anaglin.lua new file mode 100644 index 0000000..9afe7e0 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_anaglin.lua @@ -0,0 +1,191 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_alusplint" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Anaglin" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Gives On Painkillers, 95s +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Drugs" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/anaglin/v_meds_anaglin.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/anaglin/w_meds_anaglin.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "anaglin" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 4 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_anaglin" +local ID_PRIMARYAMMO = "anaglin" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + + OJSWounds:HealPain(owner) + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:ApplyEffect("OnPainkillers", 5) + end + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + end + self:Deploy() + end +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + owner:ApplyEffect("OnPainkillers", 95) + end + if INI_VIVO == true then + OJSWounds:AddStatus(owner,"Head","Painkillers", 95, 1) + for i=1,7 do + OJSWounds:HealPain(owner) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(4, -3, -2) + local offsetAng = Angle(-90, 180, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_augmentin.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_augmentin.lua new file mode 100644 index 0000000..af873b9 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_augmentin.lua @@ -0,0 +1,178 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_augmentin" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Augmentin Antibiotic's" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Gives Tenacity (20%) +Cures Dark Vision +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Drugs" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/augmentin/v_meds_augmentin.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/augmentin/w_meds_augmentin.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "augmentin" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 5 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_augmentin" +local ID_PRIMARYAMMO = "augmentin" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + owner:ApplyEffect("Tenacity", 155, 20) + owner:ApplyEffect("OnPainkillers", 155) + owner:RemoveEffect("DarkVision") + end + if INI_VIVO == true then + OJSWounds:AddStatus(owner,"Head","Painkillers", 155, 1) + for i=1,7 do + OJSWounds:HealPain(owner) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(0, -5, -0) + local offsetAng = Angle(-90, 180, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_automedkit.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_automedkit.lua new file mode 100644 index 0000000..309fabb --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_automedkit.lua @@ -0,0 +1,246 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_carfirstaidkit" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Car First Aid Kit" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals up to 35HP +Cures Bleeding +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Medkits" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/automedkit/v_meds_automedkit.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/automedkit/w_meds_automedkit.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "carfirstaidkit" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 220 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 35 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_automedkit" +local ID_PRIMARYAMMO = "carfirstaidkit" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.IniAnimPills = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + if owner:HaveEffect("Bleeding") then + owner:RemoveEffect("Bleeding") + owner:RemoveAmmo(50, ID_PRIMARYAMMO) + end + end + + if INI_VIVO == true then + math.Round(OJSWounds:HealHP(owner, self.MaxHeal, VarHealHealth), 0) + if (OJSWounds:HasStatusWholeBody(owner, "Bleed")) then + OJSWounds:HealBleed(owner) + owner:RemoveAmmo(50, ID_PRIMARYAMMO) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(VarHealHealth, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + + local RndAnim = math.random(1,3) + if (InitializeSEF == true and owner:HaveEffect("Bleeding")) or (INI_VIVO == true and (OJSWounds:HasStatusWholeBody(owner, "Bleed"))) then + self:SendWeaponAnim(ACT_VM_MISSRIGHT) + self.IniAnimBandage = 1 + elseif RndAnim == 1 then + self:SendWeaponAnim(ACT_VM_MISSRIGHT) + self.IniAnimBandage = 1 + elseif RndAnim == 2 then + self:SendWeaponAnim(ACT_VM_PULLBACK_HIGH) + self.IniAnimSyringe = 1 + else + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IniAnimPills = 1 + end + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + + timer.Simple(0.55, function() -- Audio Timer + if IsValid(self) and IsValid(self.Owner) then + if owner:GetActiveWeapon():GetClass() == "weapon_eft_automedkit" then owner:EmitSound("MedsGrizzly.Open") end + end + end) +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if SERVER then + if self.IniHeal == 1 and self.IdleTimer <= CurTime() then -- Initialize Heal + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.IniAnimPills = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + self:Heal(owner) + end + + if self.IniAnimBandage == 1 and self.AnimTimes <= 2 and self.IdleTimer <= CurTime() then -- Bandage Sequence + if math.random(1,2) == 1 then + self:SendWeaponAnim(ACT_VM_MISSRIGHT2) + else + self:SendWeaponAnim(ACT_VM_MISSCENTER) + end + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimBandage == 1 and self.AnimTimes == 3 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_MISSCENTER2) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end + + if self.IniAnimSyringe == 1 and self.AnimTimes <= 2 and self.IdleTimer <= CurTime() then -- Syringe Sequence + self:SendWeaponAnim(ACT_VM_PULLBACK) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimSyringe == 1 and self.AnimTimes == 3 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_PULLBACK_LOW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end + + if self.IniAnimPills == 1 and self.AnimTimes <= 1 and self.IdleTimer <= CurTime() then -- Syringe Sequence + if self.AnimTimes == 0 then + self:SendWeaponAnim(ACT_VM_RECOIL3) + else + self:SendWeaponAnim(ACT_VM_RECOIL2) + end + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimPills == 1 and self.AnimTimes == 2 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_PICKUP) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -5, 3) + local offsetAng = Angle(-0, 0, 180) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_cat.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_cat.lua new file mode 100644 index 0000000..64d9e2c --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_cat.lua @@ -0,0 +1,187 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_cat" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "CAT hemostatic tourniquet" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Cures Bleeding +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Injury Treatment" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/cat/v_meds_cat.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/cat/w_meds_cat.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "cattourniquet" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 90 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_cat" +local ID_PRIMARYAMMO = "cattourniquet" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.IniAnimPills = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + owner:RemoveEffect("Bleeding") + end + if INI_VIVO == true then + if (OJSWounds:HasStatusWholeBody(owner, "Bleed")) then + OJSWounds:HealBleed(owner) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(ID_PRIMARYAMMO) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniHeal == 1 and self.IdleTimer <= CurTime() then -- Initialize Heal + self.IniHeal = 0 + self.IniAnimBandage = 0 + self:Heal(owner) + end + + if self.IniAnimBandage == 1 and self.AnimTimes <= 0 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self:SendWeaponAnim(ACT_VM_RECOIL2) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimBandage == 1 and self.AnimTimes == 1 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_RECOIL3) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(0, -5, -0) + local offsetAng = Angle(-90, 180, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_grizzly.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_grizzly.lua new file mode 100644 index 0000000..c3cd3a2 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_grizzly.lua @@ -0,0 +1,245 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_grizzly" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Grizzly Medical Kit" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals up to 90HP +Cures Bleeding +Cures Concussion +Cures Fatigue +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Medkits" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/grizzly/v_meds_grizzly.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/grizzly/w_meds_grizzly.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "grizzlykit" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1800 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 90 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_grizzly" +local ID_PRIMARYAMMO = "grizzlykit" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.IniAnimPills = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + if owner:HaveEffect("Bleeding") then + owner:RemoveEffect("Bleeding") + owner:RemoveAmmo(40, ID_PRIMARYAMMO) + end + if owner:HaveEffect("Fatigue") then + owner:RemoveEffect("Fatigue") + owner:SoftRemoveEffect("Fatigue") + owner:RemoveAmmo(50, ID_PRIMARYAMMO) + end + owner:RemoveEffect("Concussion") + end + + if INI_VIVO == true then + math.Round(OJSWounds:HealHP(owner, self.MaxHeal, VarHealHealth), 0) + OJSWounds:HealPain(owner) + if (OJSWounds:HasStatusWholeBody(owner, "Bleed")) then + OJSWounds:HealBleed(owner) + owner:RemoveAmmo(40, ID_PRIMARYAMMO) + end + if (OJSWounds:HasStatusWholeBody(owner, "Fractured")) then + OJSWounds:HealFracture(owner) + owner:RemoveAmmo(50, ID_PRIMARYAMMO) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(VarHealHealth, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + local RndAnim = math.random(1,2) + if InitializeSEF == true and owner:HaveEffect("Bleeding") or (INI_VIVO == true and (OJSWounds:HasStatusWholeBody(owner, "Bleed"))) then + self:SendWeaponAnim(ACT_VM_MISSRIGHT) + self.IniAnimBandage = 1 + elseif RndAnim == 1 then + self:SendWeaponAnim(ACT_VM_MISSRIGHT) + self.IniAnimBandage = 1 + elseif RndAnim == 2 then + self:SendWeaponAnim(ACT_VM_PULLBACK_HIGH) + self.IniAnimSyringe = 1 + end + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + + timer.Simple(0.4, function() -- Audio Timer + if IsValid(self) and IsValid(self.Owner) then + if owner:GetActiveWeapon():GetClass() == "weapon_eft_grizzly" then owner:EmitSound("MedsGrizzly.Open") end + end + end) +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if SERVER then + if self.IniHeal == 1 and self.IdleTimer <= CurTime() then -- Initialize Heal + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + self:Heal(owner) + end + + if self.IniAnimBandage == 1 and self.AnimTimes <= 2 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self:SendWeaponAnim(ACT_VM_MISSRIGHT2) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimBandage == 1 and self.AnimTimes == 3 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_MISSCENTER2) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end + + if self.IniAnimSyringe == 1 and self.AnimTimes <= 0 and self.IdleTimer <= CurTime() then -- Syringe Sequence + self:SendWeaponAnim(ACT_VM_PULLBACK) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimSyringe == 1 and self.AnimTimes == 1 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_PICKUP) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimSyringe == 1 and self.AnimTimes == 2 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_PULLBACK) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimSyringe == 1 and self.AnimTimes == 3 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_PULLBACK_LOW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + self.IniHeal = 1 + end + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -7, 5) + local offsetAng = Angle(-0, -0, -180) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectoradrenaline.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectoradrenaline.lua new file mode 100644 index 0000000..fe0b307 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectoradrenaline.lua @@ -0,0 +1,177 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_eftadrenaline" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Adrenaline" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals 15HP over 15s +Gives Haste 65s +Gives Vulnerability 60s +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Stimulants" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/injector/v_meds_injector.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/injector/w_meds_injector.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "injectoradrenaline" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local ID_WEAPON = "weapon_eft_injectoradrenaline" +local ID_PRIMARYAMMO = "injectoradrenaline" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + else + INI_SEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + timer.Simple(0.05, function() + if IsValid(self) and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + vm:SetSkin(7) + end + end + end) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:ApplyEffect("Haste", 65, 40) + owner:ApplyEffect("Vuln", 60) + owner:ApplyEffect("Healing", 15, 1, 1) + end + + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + + end + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(7) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -1, 2) + local offsetAng = Angle(-180, 0, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectoretg.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectoretg.lua new file mode 100644 index 0000000..93784af --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectoretg.lua @@ -0,0 +1,177 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_eTG" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "eTG-Change Regenerative" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals 45HP over 90s +Removes 10HP +Cures Concussion +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Stimulants" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/injector/v_meds_injector.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/injector/w_meds_injector.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "etg" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local ID_WEAPON = "weapon_eft_injectoretg" +local ID_PRIMARYAMMO = "etg" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + else + INI_SEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + timer.Simple(0.05, function() + if IsValid(self) and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + vm:SetSkin(5) + end + end + end) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:ApplyEffect("Healing", 90, 1, 2) + owner:ApplyEffect("Wither", 5, 2, 1) + owner:RemoveEffect("Concussion") + end + + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + + end + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(5) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -1, 2) + local offsetAng = Angle(-180, 0, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectorl1.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectorl1.lua new file mode 100644 index 0000000..e08a3f5 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectorl1.lua @@ -0,0 +1,178 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_L1" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "L1 Norepinephrine" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Gives Tenacity 3s, 35% +Gives Exhausted 60s +Cures Dark Vision +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Stimulants" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/injector/v_meds_injector.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/injector/w_meds_injector.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "l1" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local ID_WEAPON = "weapon_eft_injectorl1" +local ID_PRIMARYAMMO = "l1" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + else + INI_SEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + timer.Simple(0.05, function() + if IsValid(self) and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + vm:SetSkin(4) + end + end + end) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:ApplyEffect("Exhaust", 93) + owner:ApplyEffect("Tenacity", 3, 35) + + owner:RemoveEffect("DarkVision") + end + + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + + end + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(4) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -1, 2) + local offsetAng = Angle(-180, 0, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectormorphine.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectormorphine.lua new file mode 100644 index 0000000..f6e4df9 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectormorphine.lua @@ -0,0 +1,179 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_morphine" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Morphine" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Gives Endurance 305s, 15% +Gives Hindered 120s, -30u +Cures Concussion +Cures Dark Vision +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Drugs" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/injector/v_meds_injector.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/injector/w_meds_injector.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "morphine" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local ID_WEAPON = "weapon_eft_injectormorphine" +local ID_PRIMARYAMMO = "morphine" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + else + INI_SEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + timer.Simple(0.05, function() + if IsValid(self) and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + vm:SetSkin(1) + end + end + end) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:ApplyEffect("Endurance", 305, 15) + owner:ApplyEffect("Hindered", 120, 30) + owner:RemoveEffect("Concussion") + owner:RemoveEffect("DarkVision") + end + + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + + end + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(1) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -1, 2) + local offsetAng = Angle(-180, 0, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectorpropital.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectorpropital.lua new file mode 100644 index 0000000..a42d852 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectorpropital.lua @@ -0,0 +1,175 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_propital" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Propital Regenerative Stimulant" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals 100HP over 300s +Gives Dark Vision 20s +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Stimulants" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/injector/v_meds_injector.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/injector/w_meds_injector.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "propital" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local ID_WEAPON = "weapon_eft_injectorpropital" +local ID_PRIMARYAMMO = "propital" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + else + INI_SEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + timer.Simple(0.05, function() + if IsValid(self) and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + vm:SetSkin(9) + end + end + end) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:ApplyEffect("Healing", 300, 1, 3) + owner:ApplyEffect("DarkVision", 20) + end + + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + + end + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(9) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -1, 2) + local offsetAng = Angle(-180, 0, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectortg12.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectortg12.lua new file mode 100644 index 0000000..8c2a77f --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_injectortg12.lua @@ -0,0 +1,175 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_TG12" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "xTG-12 Antidote" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Cures Poison +Gives Wither -5HP over 60s +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Stimulants" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/injector/v_meds_injector.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/injector/w_meds_injector.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "tg12" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local ID_WEAPON = "weapon_eft_injectortg12" +local ID_PRIMARYAMMO = "tg12" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + else + INI_SEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + timer.Simple(0.05, function() + if IsValid(self) and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + vm:SetSkin(13) + end + end + end) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:ApplyEffect("Wither", 60, 1, 12) + owner:RemoveEffect("Poison") + end + + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + + end + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(13) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(3, -1, 2) + local offsetAng = Angle(-180, 0, 0) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_salewa.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_salewa.lua new file mode 100644 index 0000000..067b153 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_salewa.lua @@ -0,0 +1,221 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_salewa" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Salewa First Aid Kit" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals up to 45HP +Cures Bleeding +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Medkits" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/salewa/v_meds_salewa.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/salewa/w_meds_salewa.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "salewafirstaidkit" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 400 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 45 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_salewa" +local ID_PRIMARYAMMO = "salewafirstaidkit" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.IniAnimPills = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + if owner:HaveEffect("Bleeding") then + owner:RemoveEffect("Bleeding") + owner:RemoveAmmo(45, ID_PRIMARYAMMO) + end + end + + if INI_VIVO == true then + math.Round(OJSWounds:HealHP(owner, self.MaxHeal, VarHealHealth), 0) + if (OJSWounds:HasStatusWholeBody(owner, "Bleed")) then + OJSWounds:HealBleed(owner) + owner:RemoveAmmo(45, ID_PRIMARYAMMO) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(VarHealHealth, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 or self.IniAnimSyringe == 1 or self.IniAnimPills == 1 then return end + + local RndAnim = math.random(1,2) + if (INI_SEF == true and owner:HaveEffect("Bleeding")) or (INI_VIVO == true and (OJSWounds:HasStatusWholeBody(owner, "Bleed"))) then + self:SendWeaponAnim(ACT_VM_MISSRIGHT) + self.IniAnimBandage = 1 + elseif RndAnim == 1 then + self:SendWeaponAnim(ACT_VM_MISSRIGHT) + self.IniAnimBandage = 1 + elseif RndAnim == 2 then + self:SendWeaponAnim(ACT_VM_PULLBACK_HIGH) + self.IniAnimSyringe = 1 + end + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if SERVER then + if self.IniHeal == 1 and self.IdleTimer <= CurTime() then -- Initialize Heal + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + self:Heal(owner) + end + + if self.IniAnimBandage == 1 and self.AnimTimes <= 2 and self.IdleTimer <= CurTime() then -- Bandage Sequence + if math.random(1,2) == 1 then + self:SendWeaponAnim(ACT_VM_MISSRIGHT2) + else + self:SendWeaponAnim(ACT_VM_MISSCENTER) + end + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimBandage == 1 and self.AnimTimes == 3 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_MISSCENTER2) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end + + if self.IniAnimSyringe == 1 and self.AnimTimes <= 6 and self.IdleTimer <= CurTime() then -- Syringe Sequence + self:SendWeaponAnim(ACT_VM_PULLBACK) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.AnimTimes = self.AnimTimes + 1 + elseif self.IniAnimSyringe == 1 and self.AnimTimes == 7 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_PULLBACK_LOW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniHeal = 1 + end + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(4, -4, 5) + local offsetAng = Angle(-0, -90, -180) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_surgicalkit.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_surgicalkit.lua new file mode 100644 index 0000000..1dd875b --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_eft_surgicalkit.lua @@ -0,0 +1,194 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/vgui_surgicalkit" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "CMS Surgical Kit" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Cures Broken +]] +SWEP.Category = "EFT" +SWEP.Category1 = "EFT" +SWEP.Category2 = "Injury Treatment" + +SWEP.ViewModelFOV = 85 +SWEP.ViewModel = "models/weapons/sweps/eft/surgicalkit/v_meds_surgicalkit.mdl" +SWEP.WorldModel = "models/weapons/sweps/eft/surgicalkit/w_meds_surgicalkit.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 5 +SWEP.SlotPos = 7 +SWEP.DrawAmmo = true + +SWEP.SwayScale = 0.15 +SWEP.BobScale = 0.75 + +SWEP.Primary.Ammo = "surgicalkit" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 5 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.MaxHeal = 0 + +local INI_SEF = false +local INI_VIVO = false +local ID_WEAPON = "weapon_eft_surgicalkit" +local ID_PRIMARYAMMO = "surgicalkit" + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + INI_SEF = true + end + + local FilePathVIVO = "lua/autorun/ojsshared.lua" + if file.Exists(FilePathVIVO, "GAME") then + INI_VIVO = true + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self.IniAnimBandage = 0 + self.IniAnimSyringe = 0 + self.IniAnimPills = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + + self:SendWeaponAnim(ACT_VM_IDLE) + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then + owner:StripWeapon(ID_WEAPON) + owner:SelectWeapon(owner:GetPreviousWeapon()) + end + return true +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + + if IsValid(self) then + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then -- Heal logic + if INI_SEF == true then + owner:RemoveEffect("Broken") + owner:RemoveEffect("Deathsdoorrecovery") + end + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + end + self:Deploy() + end +end + +function SWEP:Heal(owner) + local owner = self:GetOwner() + local VarMissingHealth = owner:GetMaxHealth() - owner:Health() + local VarGetHealth = math.min(VarMissingHealth, owner:GetAmmoCount(ID_PRIMARYAMMO)) + local VarHealHealth = math.min(VarGetHealth, self.MaxHeal) + + if IsValid(owner) and SERVER and owner:GetActiveWeapon():GetClass() == ID_WEAPON then + if INI_SEF == true then + owner:RemoveEffect("Broken") + owner:RemoveEffect("Deathsdoorrecovery") + end + if INI_VIVO == true then + if (OJSWounds:HasBlackoutWholeBody(owner)) then + OJSWounds:HealBlackout(owner) + end + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + VarHealHealth)) + end + owner:RemoveAmmo(1, ID_PRIMARYAMMO) + self:Deploy() + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + if self.IniAnimBandage == 1 then return end + + self:SendWeaponAnim(ACT_VM_RECOIL1) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.IniAnimBandage = 1 +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Think() + if self.IniAnimBandage == 1 and self.IdleTimer <= CurTime() then -- Bandage Sequence + self.IniAnimBandage = 0 + self.AnimTimes = 0 + self.IniHeal = 0 + self:Heal(owner) + end +end + +function SWEP:Holster() + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end + +if CLIENT then -- Worldmodel offset + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(0) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local owner = self:GetOwner() + + if (IsValid(owner)) then + local offsetVec = Vector(4, -1, -1) + local offsetAng = Angle(-90, 180, -90) + + local boneid = owner:LookupBone("ValveBiped.Bip01_R_Hand") -- Right Hand + if !boneid then return end + + local matrix = owner:GetBoneMatrix(boneid) + if !matrix then return end + + local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + + WorldModel:SetupBones() + else + + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + self:DrawModel() + end + + WorldModel:DrawModel() + + end +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_bandage.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_bandage.lua new file mode 100644 index 0000000..87c631f --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_bandage.lua @@ -0,0 +1,230 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/item_bandages" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Bandage" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals 20HP +]] +SWEP.Category = "NMRIH" + +SWEP.ViewModelFOV = 65 +SWEP.ViewModel = "models/weapons/nmrih/items/bandage/v_item_bandages.mdl" +SWEP.WorldModel = "models/weapons/nmrih/items/bandage/w_bandages.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 0 +SWEP.SlotPos = 5 +SWEP.DrawAmmo = true + +SWEP.Primary.Ammo = "bandage" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.HitDistance = 90 +SWEP.PrimaryCD = 1 +SWEP.PrimaryHitTime = 0.1 + +local HealAmount = 20 +local ArmorAmount = 0 +local InitializeSEF = false + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + InitializeSEF = true + else + InitializeSEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self:SendWeaponAnim(ACT_VM_DRAW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 0 + self.InitializeHealing = 0 + self.vmcamera = nil + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then owner:StripWeapon("weapon_nmrih_bandage") end -- Reminder + return true +end + +local function Heal(owner, weapon) + local activeWeapon = owner:GetActiveWeapon() + + if IsValid(weapon) then + if IsValid(owner) and SERVER and activeWeapon:GetClass() == "weapon_nmrih_bandage" then -- Reminder + + if InitializeSEF == true then + owner:ApplyEffect("Healing", 1, 20, 1) + owner:RemoveEffect("Bleeding") + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + HealAmount)) + owner:SetArmor(math.min(owner:GetMaxArmor(), owner:Armor() + ArmorAmount)) + end + owner:RemoveAmmo(1, "bandage") -- Reminder + -- owner:EmitSound("nil") + weapon:Deploy() + end + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:SetNextPrimaryFire(CurTime() + self.Owner:GetViewModel():SequenceDuration() + 0) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.InitializeHealing = 1 + self.Idle = 1 +end + +function SWEP:SecondaryAttack() + local owner = self:GetOwner() + local dmginfo = DamageInfo() + local tr = util.TraceLine( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mask = MASK_SHOT_HULL + } ) + if ( !IsValid( tr.Entity ) ) then + tr = util.TraceHull( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + if self.InitializeHealing == 1 then return end + + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + self:SendWeaponAnim(ACT_VM_HITCENTER) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self:SetNextSecondaryFire(CurTime() + 0.9) + + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + + timer.Create("TimerHitWhen", self.PrimaryHitTime, 1, function() -- Impact Timer + if SERVER then + if ( SERVER and IsValid( tr.Entity ) and ( tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:Health() > 0 ) ) then + dmginfo:SetDamage(10) + dmginfo:SetAttacker(self.Owner) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CRUSH) + tr.Entity:TakeDamageInfo(dmginfo) + end + end + end) +end + +function SWEP:Reload() + local owner = self:GetOwner() + local trace = owner:GetEyeTrace() + + if self.Give == 1 then return end + + if IsValid(trace.Entity) and trace.Entity:IsPlayer() then + local targetPlayer = trace.Entity + + self:SendWeaponAnim(ACT_VM_THROW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 1 + + owner:RemoveAmmo(1, "bandage") + + if CLIENT then return end + timer.Create("TimerNMRIHBandageGive", 1.2, 1, function() + if targetPlayer:HasWeapon("weapon_nmrih_bandage") then + targetPlayer:GiveAmmo( 1, "bandage") + else + targetPlayer:Give("weapon_nmrih_bandage") + end + -- print(owner:Nick() .. " gave a bandage to " .. targetPlayer:Nick()) + self:Deploy() + end) + end +end + +function SWEP:Think() + local owner = self.Owner + if self.InitializeHealing == 1 and self.IdleTimer <= CurTime() then + if IsValid(self) then + Heal(owner, self) + end + end + + if self.InitializeHealing == 1 then return end + if (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT) and owner:KeyDown(IN_SPEED)) then -- If Sprinting + if self.Sprint == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + self.Sprint = 1 + self.Walk = 0 + self.Idle = 0 + end + elseif (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT)) then -- If Walking + if self.Walk == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_IDLE_LOWERED) + self.Walk = 1 + self.Sprint = 0 + self.Idle = 0 + end + elseif self.Idle == 0 and self.IdleTimer <= CurTime() then -- Idle Sequence + self:SendWeaponAnim(ACT_VM_IDLE) + self.Idle = 1 + self.Sprint = 0 + self.Walk = 0 + end + + +end + +function SWEP:Holster() + timer.Remove("TimerHitWhen") + timer.Remove("TimerNMRIHBandageGive") + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_genetherapy.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_genetherapy.lua new file mode 100644 index 0000000..9365cd2 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_genetherapy.lua @@ -0,0 +1,227 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/item_gene_therapy" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Genetherapy" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +]] +SWEP.Category = "NMRIH" + +SWEP.ViewModelFOV = 65 +SWEP.ViewModel = "models/weapons/nmrih/items/genetherapy/v_item_genetherapy.mdl" +SWEP.WorldModel = "models/weapons/nmrih/items/genetherapy/w_genetherapy.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 0 +SWEP.SlotPos = 5 +SWEP.DrawAmmo = true + +SWEP.Primary.Ammo = "genetherapy" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.HitDistance = 90 +SWEP.PrimaryHitTime = 0.1 + +local HealAmount = 0 +local ArmorAmount = 50 +local InitializeSEF = false + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + InitializeSEF = true + else + InitializeSEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self:SendWeaponAnim(ACT_VM_DRAW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 0 + self.InitializeHealing = 0 + self.vmcamera = nil + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then owner:StripWeapon("weapon_nmrih_genetherapy") end -- Reminder + return true +end + +local function Heal(owner, weapon) + local activeWeapon = owner:GetActiveWeapon() + + if IsValid(weapon) then + if IsValid(owner) and SERVER and activeWeapon:GetClass() == "weapon_nmrih_genetherapy" then -- Reminder + + if InitializeSEF == true then + owner:ApplyEffect("Tenacity", math.huge) + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + HealAmount)) + owner:SetArmor(math.min(owner:GetMaxArmor(), owner:Armor() + ArmorAmount)) + end + owner:RemoveAmmo(1, "genetherapy") -- Reminder + -- owner:EmitSound("nil") + weapon:Deploy() + end + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:SetNextPrimaryFire(CurTime() + self.Owner:GetViewModel():SequenceDuration() + 0) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.InitializeHealing = 1 + self.Idle = 1 +end + +function SWEP:SecondaryAttack() + local owner = self:GetOwner() + local dmginfo = DamageInfo() + local tr = util.TraceLine( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mask = MASK_SHOT_HULL + } ) + if ( !IsValid( tr.Entity ) ) then + tr = util.TraceHull( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + if self.InitializeHealing == 1 then return end + + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + self:SendWeaponAnim(ACT_VM_HITCENTER) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self:SetNextSecondaryFire(CurTime() + 0.9) + + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + + timer.Create("TimerHitWhen", self.PrimaryHitTime, 1, function() -- Impact Timer + if SERVER then + if ( SERVER and IsValid( tr.Entity ) and ( tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:Health() > 0 ) ) then + dmginfo:SetDamage(10) + dmginfo:SetAttacker(self.Owner) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CRUSH) + tr.Entity:TakeDamageInfo(dmginfo) + end + end + end) +end + +function SWEP:Reload() + local owner = self:GetOwner() + local trace = owner:GetEyeTrace() + + if self.Give == 1 then return end + + if IsValid(trace.Entity) and trace.Entity:IsPlayer() then + local targetPlayer = trace.Entity + + self:SendWeaponAnim(ACT_VM_THROW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 1 + + owner:RemoveAmmo(1, "genetherapy") + + if CLIENT then return end + timer.Create("TimerNMRIHBandageGive", 1.2, 1, function() + if targetPlayer:HasWeapon("weapon_nmrih_genetherapy") then + targetPlayer:GiveAmmo( 1, "genetherapy") + else + targetPlayer:Give("weapon_nmrih_genetherapy") + end + -- print(owner:Nick() .. " gave a bandage to " .. targetPlayer:Nick()) + self:Deploy() + end) + end +end + +function SWEP:Think() + local owner = self.Owner + if self.InitializeHealing == 1 and self.IdleTimer <= CurTime() then + if IsValid(self) then + Heal(owner, self) + end + end + + if self.InitializeHealing == 1 then return end + if (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT) and owner:KeyDown(IN_SPEED)) then -- If Sprinting + if self.Sprint == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + self.Sprint = 1 + self.Walk = 0 + self.Idle = 0 + end + elseif (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT)) then -- If Walking + if self.Walk == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_IDLE_LOWERED) + self.Walk = 1 + self.Sprint = 0 + self.Idle = 0 + end + elseif self.Idle == 0 and self.IdleTimer <= CurTime() then -- Idle Sequence + self:SendWeaponAnim(ACT_VM_IDLE) + self.Idle = 1 + self.Sprint = 0 + self.Walk = 0 + end + + +end + +function SWEP:Holster() + timer.Remove("TimerHitWhen") + timer.Remove("TimerNMRIHBandageGive") + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_medkit.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_medkit.lua new file mode 100644 index 0000000..2c2e9c7 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_medkit.lua @@ -0,0 +1,228 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/item_first_aid" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "First Aid" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +Heals 100HP +]] +SWEP.Category = "NMRIH" + +SWEP.ViewModelFOV = 65 +SWEP.ViewModel = "models/weapons/nmrih/items/medkit/v_item_firstaid.mdl" +SWEP.WorldModel = "models/weapons/nmrih/items/medkit/w_firstaidkit.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 0 +SWEP.SlotPos = 5 +SWEP.DrawAmmo = true + +SWEP.Primary.Ammo = "medkit" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.HitDistance = 90 +SWEP.PrimaryCD = 1 +SWEP.PrimaryHitTime = 0.1 + +local HealAmount = 100 +local ArmorAmount = 0 +local InitializeSEF = false + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + InitializeSEF = true + else + InitializeSEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self:SendWeaponAnim(ACT_VM_DRAW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 0 + self.InitializeHealing = 0 + self.vmcamera = nil + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then owner:StripWeapon("weapon_nmrih_medkit") end -- Reminder + return true +end + +local function Heal(owner, weapon) + local activeWeapon = owner:GetActiveWeapon() + + if IsValid(weapon) then + if IsValid(owner) and SERVER and activeWeapon:GetClass() == "weapon_nmrih_medkit" then -- Reminder + + if InitializeSEF == true then + owner:ApplyEffect("Healing", 1, 100, 1) + owner:RemoveEffect("Bleeding") + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + HealAmount)) + owner:SetArmor(math.min(owner:GetMaxArmor(), owner:Armor() + ArmorAmount)) + end + owner:RemoveAmmo(1, "medkit") -- Reminder + -- owner:EmitSound("nil") + weapon:Deploy() + end + end +end + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:SetNextPrimaryFire(CurTime() + self.Owner:GetViewModel():SequenceDuration() + 0) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.InitializeHealing = 1 + self.Idle = 1 +end + +function SWEP:SecondaryAttack() + local owner = self:GetOwner() + local dmginfo = DamageInfo() + local tr = util.TraceLine( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mask = MASK_SHOT_HULL + } ) + if ( !IsValid( tr.Entity ) ) then + tr = util.TraceHull( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + if self.InitializeHealing == 1 then return end + + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + self:SendWeaponAnim(ACT_VM_HITCENTER) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self:SetNextSecondaryFire(CurTime() + 0.9) + + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + + timer.Create("TimerHitWhen", self.PrimaryHitTime, 1, function() -- Impact Timer + if SERVER then + if ( SERVER and IsValid( tr.Entity ) and ( tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:Health() > 0 ) ) then + dmginfo:SetDamage(10) + dmginfo:SetAttacker(self.Owner) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CRUSH) + tr.Entity:TakeDamageInfo(dmginfo) + end + end + end) +end + +function SWEP:Reload() + local owner = self:GetOwner() + local trace = owner:GetEyeTrace() + + if self.Give == 1 then return end + + if IsValid(trace.Entity) and trace.Entity:IsPlayer() then + local targetPlayer = trace.Entity + + self:SendWeaponAnim(ACT_VM_THROW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 1 + + owner:RemoveAmmo(1, "medkit") + + if CLIENT then return end + timer.Create("TimerNMRIHMedkitGive", 1.2, 1, function() + if targetPlayer:HasWeapon("weapon_nmrih_medkit") then + targetPlayer:GiveAmmo( 1, "medkit") + else + targetPlayer:Give("weapon_nmrih_medkit") + end + -- print(owner:Nick() .. " gave a bandage to " .. targetPlayer:Nick()) + self:Deploy() + end) + end +end + +function SWEP:Think() + local owner = self.Owner + if self.InitializeHealing == 1 and self.IdleTimer <= CurTime() then + if IsValid(self) then + Heal(owner, self) + end + end + + if self.InitializeHealing == 1 then return end + if (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT) and owner:KeyDown(IN_SPEED)) then -- If Sprinting + if self.Sprint == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + self.Sprint = 1 + self.Walk = 0 + self.Idle = 0 + end + elseif (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT)) then -- If Walking + if self.Walk == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_IDLE_LOWERED) + self.Walk = 1 + self.Sprint = 0 + self.Idle = 0 + end + elseif self.Idle == 0 and self.IdleTimer <= CurTime() then -- Idle Sequence + self:SendWeaponAnim(ACT_VM_IDLE) + self.Idle = 1 + self.Sprint = 0 + self.Walk = 0 + end +end + +function SWEP:Holster() + timer.Remove("TimerHitWhen") + timer.Remove("TimerNMRIHMedkitGive") + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end diff --git a/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_phalanx.lua b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_phalanx.lua new file mode 100644 index 0000000..13b7fe0 --- /dev/null +++ b/garrysmod/addons/other_sweps/lua/weapons/weapon_nmrih_phalanx.lua @@ -0,0 +1,241 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID( "vgui/hud/item_pills" ) + SWEP.BounceWeaponIcon = true + SWEP.DrawWeaponInfoBox = true +end + +SWEP.PrintName = "Phalanx" +SWEP.Author = "Craft_Pig" +SWEP.Purpose = +[[ +]] +SWEP.Category = "NMRIH" + +SWEP.ViewModelFOV = 65 +SWEP.ViewModel = "models/weapons/nmrih/items/phalanx/v_item_phalanx.mdl" +SWEP.WorldModel = "models/weapons/nmrih/items/phalanx/w_phalanx.mdl" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = true +SWEP.Slot = 0 +SWEP.SlotPos = 5 +SWEP.DrawAmmo = true + +SWEP.Primary.Ammo = "phalanx" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false + +SWEP.HitDistance = 90 +SWEP.PrimaryCD = 1 +SWEP.PrimaryHitTime = 0.1 + +local HealAmount = 1 +local ArmorAmount = 0 +local RegenAmount = 3 +local RegenDuration = 10 +local InitializeSEF = false + +function SWEP:Initialize() + self:SetHoldType("slam") + + local FilePathSEF = "lua/SEF/SEF_Functions.lua" + if file.Exists(FilePathSEF, "GAME") then + InitializeSEF = true + else + InitializeSEF = false + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + self:SendWeaponAnim(ACT_VM_DRAW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 0 + self.InitializeHealing = 0 + self.vmcamera = nil + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then owner:StripWeapon("weapon_nmrih_phalanx") end -- Reminder + return true +end + +local function Heal(owner, weapon) + local activeWeapon = owner:GetActiveWeapon() + + if IsValid(weapon) then + if IsValid(owner) and SERVER and activeWeapon:GetClass() == "weapon_nmrih_phalanx" then -- Reminder + + if InitializeSEF == true then + owner:ApplyEffect("Healing", 10, 1, 0.32) + owner:ApplyEffect("Tenacity", 10) + else + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + HealAmount)) + owner:SetArmor(math.min(owner:GetMaxArmor(), owner:Armor() + ArmorAmount)) + end + owner:RemoveAmmo(1, "phalanx") -- Reminder + -- owner:EmitSound("nil") + weapon:Deploy() + end + end +end + +local function Regenerate(owner, weapon) + timer.Create("TimerHealthRegenPainkillersSCPSL", 0.5, RegenDuration, function() + if IsValid(owner) and owner:Alive() then + owner:SetHealth(math.min(owner:Health() + RegenAmount, owner:GetMaxHealth())) + else + timer.Remove("TimerHealthRegenPainkillersSCPSL") + end + end) +end + + +function SWEP:PrimaryAttack() + local owner = self.Owner + + if owner:GetAmmoCount(self.Primary.Ammo) == 0 then return end + + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:SetNextPrimaryFire(CurTime() + self.Owner:GetViewModel():SequenceDuration() + 0) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.InitializeHealing = 1 + self.Idle = 1 +end + +function SWEP:SecondaryAttack() + local owner = self:GetOwner() + local dmginfo = DamageInfo() + local tr = util.TraceLine( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mask = MASK_SHOT_HULL + } ) + if ( !IsValid( tr.Entity ) ) then + tr = util.TraceHull( { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self.HitDistance, + filter = owner, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + if self.InitializeHealing == 1 then return end + + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + self:SendWeaponAnim(ACT_VM_HITCENTER) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self:SetNextSecondaryFire(CurTime() + 0.9) + + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + + timer.Create("TimerHitWhen", self.PrimaryHitTime, 1, function() -- Impact Timer + if SERVER then + if ( SERVER and IsValid( tr.Entity ) and ( tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:Health() > 0 ) ) then + dmginfo:SetDamage(10) + dmginfo:SetAttacker(self.Owner) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CRUSH) + tr.Entity:TakeDamageInfo(dmginfo) + end + end + end) +end + +function SWEP:Reload() + local owner = self:GetOwner() + local trace = owner:GetEyeTrace() + + if self.Give == 1 then return end + + if IsValid(trace.Entity) and trace.Entity:IsPlayer() then + local targetPlayer = trace.Entity + + self:SendWeaponAnim(ACT_VM_THROW) + self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration() + self.Idle = 0 + self.Sprint = 0 + self.Walk = 0 + self.Give = 1 + + owner:RemoveAmmo(1, "phalanx") + + if CLIENT then return end + timer.Create("TimerNMRIHPhalanxGive", 1.2, 1, function() + if targetPlayer:HasWeapon("weapon_nmrih_phalanx") then + targetPlayer:GiveAmmo( 1, "phalanx") + else + targetPlayer:Give("weapon_nmrih_phalanx") + end + -- print(owner:Nick() .. " gave a bandage to " .. targetPlayer:Nick()) + self:Deploy() + end) + end +end + +function SWEP:Think() + local owner = self.Owner + if self.InitializeHealing == 1 and self.IdleTimer <= CurTime() then + if IsValid(self) then + Heal(owner, self) + if InitializeSEF == false then Regenerate(owner, weapon) end + end + end + + if self.InitializeHealing == 1 then return end + if (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT) and owner:KeyDown(IN_SPEED)) then -- If Sprinting + if self.Sprint == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + self.Sprint = 1 + self.Walk = 0 + self.Idle = 0 + end + elseif (owner:KeyDown(IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT)) then -- If Walking + if self.Walk == 0 and self.IdleTimer <= CurTime() then + self:SendWeaponAnim(ACT_VM_IDLE_LOWERED) + self.Walk = 1 + self.Sprint = 0 + self.Idle = 0 + end + elseif self.Idle == 0 and self.IdleTimer <= CurTime() then -- Idle Sequence + self:SendWeaponAnim(ACT_VM_IDLE) + self.Idle = 1 + self.Sprint = 0 + self.Walk = 0 + end +end + +function SWEP:Holster() + timer.Remove("TimerHitWhen") + timer.Remove("TimerNMRIHPhalanxGive") + return true +end + +function SWEP:PostDrawViewModel( vm ) + local attachment = vm:GetAttachment(1) + if attachment then + self.vmcamera = vm:GetAngles() - attachment.Ang + else + self.vmcamera = Angle(0, 0, 0) + end +end + +function SWEP:CalcView( ply, pos, ang, fov ) + self.vmcamera = self.vmcamera or Angle(0, 0, 0) + return pos, ang + self.vmcamera, fov +end diff --git a/garrysmod/addons/permaprops/lua/autorun/client/cl_permaload.lua b/garrysmod/addons/permaprops/lua/autorun/client/cl_permaload.lua new file mode 100644 index 0000000..a7a2884 --- /dev/null +++ b/garrysmod/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("---------------------------------") diff --git a/garrysmod/addons/permaprops/lua/autorun/server/sv_permaload.lua b/garrysmod/addons/permaprops/lua/autorun/server/sv_permaload.lua new file mode 100644 index 0000000..1cb88b7 --- /dev/null +++ b/garrysmod/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("-------------------------------") diff --git a/garrysmod/addons/permaprops/lua/entities/pp_prop_effect.lua b/garrysmod/addons/permaprops/lua/entities/pp_prop_effect.lua new file mode 100644 index 0000000..d3011ad --- /dev/null +++ b/garrysmod/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/garrysmod/addons/permaprops/lua/permaprops/cl_menu.lua b/garrysmod/addons/permaprops/lua/permaprops/cl_menu.lua new file mode 100644 index 0000000..c43b887 --- /dev/null +++ b/garrysmod/addons/permaprops/lua/permaprops/cl_menu.lua @@ -0,0 +1,534 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +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 + + local AboutPanel = vgui.Create( "DPanel", Main ) + AboutPanel:SetPos( 190, 51 ) + AboutPanel:SetSize( 390, 275 ) + AboutPanel.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 + AboutPanel:Hide() + + local AboutLabel = vgui.Create("DLabel", AboutPanel) + AboutLabel:SetFont("pp_font") + AboutLabel:SetPos(140, 25) + AboutLabel:SetColor(Color(50, 50, 50, 255)) + AboutLabel:SetText("PermaProps 4.0") + AboutLabel:SizeToContents() + + local AboutLabel2 = vgui.Create("DLabel", AboutPanel) + AboutLabel2:SetFont("pp_font") + AboutLabel2:SetPos(30, 80) + AboutLabel2:SetColor(Color(50, 50, 50, 255)) + AboutLabel2:SetText("Author: Malboro\n\nContributor: Entoros | ARitz Cracker\n\n\n Special thanks to all donors !") + AboutLabel2:SizeToContents() + + local DonationsTxT = vgui.Create( "DButton", AboutPanel ) + DonationsTxT:SetText( " Donate " ) + DonationsTxT:SetFont("pp_font") + DonationsTxT:SetSize( 370, 30) + DonationsTxT:SetPos( 10, 240 ) + DonationsTxT:SetTextColor( Color( 50, 50, 50, 255 ) ) + DonationsTxT.DoClick = function() gui.OpenURL("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CJ5EUHFAQ7NLN") end + DonationsTxT.Paint = function(self) + surface.SetDrawColor(50, 50, 50, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + end + + local BAbout = vgui.Create("DButton", Main) + BAbout:SetText("About") + BAbout:SetFont("pp_font") + BAbout:SetSize(160, 50) + BAbout:SetPos(15, 159 + 115) + BAbout:SetTextColor( Color( 255, 255, 255, 255 ) ) + BAbout.PaintColor = Color(0, 0, 0, 0) + BAbout.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 + BAbout.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 + AboutPanel:Show() + PSelect = AboutPanel + + end + + if !file.Exists("permaprops_donate.txt", "DATA") then + + Derma_Query("Please don't Forget to Donate", "PermaProps 4.0", "Donate", function() gui.OpenURL("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CJ5EUHFAQ7NLN") end, "Cancel", function() file.Write("permaprops_donate.txt") end) + + end + +end +net.Receive("pp_open_menu", pp_open_menu) diff --git a/garrysmod/addons/permaprops/lua/permaprops/sv_lib.lua b/garrysmod/addons/permaprops/lua/permaprops/sv_lib.lua new file mode 100644 index 0000000..fef407a --- /dev/null +++ b/garrysmod/addons/permaprops/lua/permaprops/sv_lib.lua @@ -0,0 +1,312 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +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() + + 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 + + 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 ) + + 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 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 + + ent:SetRenderMode( RENDERMODE_TRANSALPHA ) + 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, 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 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/garrysmod/addons/permaprops/lua/permaprops/sv_menu.lua b/garrysmod/addons/permaprops/lua/permaprops/sv_menu.lua new file mode 100644 index 0000000..7809399 --- /dev/null +++ b/garrysmod/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["owner"] = { Physgun = true, Tool = true, Property = true, Save = true, Delete = true, Update = true, Menu = true, Permissions = true, Inherits = "admin", Custom = true } + -- PermaProps.Permissions["curator"] = { Physgun = true, Tool = true, Property = true, Save = true, Delete = true, Update = true, Menu = true, Permissions = true, Inherits = "admin", Custom = true } + -- PermaProps.Permissions["spec admin"] = { 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 = true, 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 +PermissionLoad() + +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/garrysmod/addons/permaprops/lua/permaprops/sv_permissions.lua b/garrysmod/addons/permaprops/lua/permaprops/sv_permissions.lua new file mode 100644 index 0000000..512f678 --- /dev/null +++ b/garrysmod/addons/permaprops/lua/permaprops/sv_permissions.lua @@ -0,0 +1,66 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ + 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) and tr.Entity.PermaProps then + + if tool == "permaprops" then + + return true + + end + + return PermaProps.HasPermission( ply, "Tool") + + 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) + +timer.Simple(5, function() hook.Remove("CanTool", "textScreensPreventTools") end) -- Fuck OFF +timer.Simple(5, function() hook.Remove("CanTool", "textscreenpreventtools") end) diff --git a/garrysmod/addons/permaprops/lua/permaprops/sv_specialfcn.lua b/garrysmod/addons/permaprops/lua/permaprops/sv_specialfcn.lua new file mode 100644 index 0000000..7d750e8 --- /dev/null +++ b/garrysmod/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) + + 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/garrysmod/addons/permaprops/lua/permaprops/sv_sql.lua b/garrysmod/addons/permaprops/lua/permaprops/sv_sql.lua new file mode 100644 index 0000000..a90d539 --- /dev/null +++ b/garrysmod/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 diff --git a/garrysmod/addons/permaprops/lua/weapons/gmod_tool/stools/permaprops.lua b/garrysmod/addons/permaprops/lua/weapons/gmod_tool/stools/permaprops.lua new file mode 100644 index 0000000..d2ee2ef --- /dev/null +++ b/garrysmod/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/garrysmod/addons/realistic_police_v1.7.8/lua/autorun/realistic_police_loader.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/autorun/realistic_police_loader.lua new file mode 100644 index 0000000..d59899c --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/autorun/realistic_police_loader.lua @@ -0,0 +1,67 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +if SERVER then + include("realistic_police/server/sv_rpt_function.lua") + include("realistic_police/server/sv_rpt_hook.lua") + include("realistic_police/server/sv_rpt_net.lua") + include("realistic_police/server/sv_rpt_tool.lua") +AddCSLuaFile("realistic_police/client/cl_rpt_main.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_fonts.lua") +AddCSLuaFile("realistic_police/client/cl_rpt_criminal.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_listreport.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_report.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_firefox.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_camera.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_license.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_cmd.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_fining.lua") + AddCSLuaFile("realistic_police/client/cl_rpt_handcuff.lua") + + AddCSLuaFile("realistic_police/client/cl_rpt_notify.lua") +AddCSLuaFile("realistic_police/languages/sh_language_en.lua") + AddCSLuaFile("realistic_police/languages/sh_language_tr.lua") + AddCSLuaFile("realistic_police/languages/sh_language_cn.lua") + AddCSLuaFile("realistic_police/languages/sh_language_fr.lua") + AddCSLuaFile("realistic_police/languages/sh_language_ptbr.lua") + AddCSLuaFile("realistic_police/languages/sh_language_ru.lua") + AddCSLuaFile("realistic_police/languages/sh_language_es.lua") + AddCSLuaFile("realistic_police/languages/sh_language_de.lua") + AddCSLuaFile("realistic_police/shared/sh_functions.lua") + AddCSLuaFile("realistic_police/sh_rpt_materials.lua") + AddCSLuaFile("realistic_police/sh_rpt_advanced.lua") + AddCSLuaFile("realistic_police/sh_rpt_config.lua") +else + include("realistic_police/client/cl_rpt_criminal.lua") + include("realistic_police/client/cl_rpt_listreport.lua") + include("realistic_police/client/cl_rpt_report.lua") + include("realistic_police/client/cl_rpt_firefox.lua") + include("realistic_police/client/cl_rpt_camera.lua") + include("realistic_police/client/cl_rpt_license.lua") + include("realistic_police/client/cl_rpt_cmd.lua") + include("realistic_police/client/cl_rpt_fining.lua") + include("realistic_police/client/cl_rpt_handcuff.lua") + + include("realistic_police/client/cl_rpt_notify.lua") +include("realistic_police/client/cl_rpt_main.lua") + include("realistic_police/client/cl_rpt_fonts.lua") +end + +include("realistic_police/languages/sh_language_en.lua") +include("realistic_police/languages/sh_language_tr.lua") +include("realistic_police/languages/sh_language_cn.lua") +include("realistic_police/languages/sh_language_fr.lua") +include("realistic_police/languages/sh_language_ru.lua") +include("realistic_police/languages/sh_language_es.lua") +include("realistic_police/languages/sh_language_ptbr.lua") +include("realistic_police/languages/sh_language_de.lua") +include("realistic_police/shared/sh_functions.lua") +include("realistic_police/sh_rpt_materials.lua") +include("realistic_police/sh_rpt_advanced.lua") +include("realistic_police/sh_rpt_config.lua") + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/darkrp_modules/realistic_police/sh_police.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/darkrp_modules/realistic_police/sh_police.lua new file mode 100644 index 0000000..d5ce2b4 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/darkrp_modules/realistic_police/sh_police.lua @@ -0,0 +1,51 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +if Realistic_Police.DefaultJob then + TEAM_CAMERAREPAIRER = DarkRP.createJob("Мастер по камерам", { + color = Color(252, 133, 0), + model = {"models/player/odessa.mdl"}, + description = [[Ваша задача — ремонтировать городские камеры наблюдения.]], + weapons = {"weapon_rpt_wrench"}, + command = "camerarepairer", + max = 2, + salary = 20, + admin = 0, + vote = false, + hasLicense = false + }) +end +DarkRP.createCategory{ + name = "Мастер по камерам", + categorises = "entities", + startExpanded = true, + color = Color(252, 133, 0), + canSee = function(ply) return true end, + sortOrder = 100 +} +-- a91f6942e11bb9a70ff8e3d7a0dcc737fd407c85d2d10ff95a1cfed59177e84a + +DarkRP.createEntity("Камера наблюдения", { + ent = "realistic_police_camera", + model = "models/wasted/wasted_kobralost_camera.mdl", + price = 500, + max = 10, + cmd = "realistic_police_camera", + --allowed = TEAM_CAMERAREPAIRER, + category = "Мастер по камерам" +}) + +DarkRP.createEntity("Экран наблюдения", { + ent = "realistic_police_screen", + model = "models/props/cs_office/tv_plasma.mdl", + price = 500, + max = 1, + cmd = "realistic_police_screen", + category = "Мастер по камерам" +}) +-- d4ec95c252515613de193db925956fba07d4ff50f580a4b42b7f507f9c716945 + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/cl_init.lua new file mode 100644 index 0000000..a93c4d7 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/cl_init.lua @@ -0,0 +1,23 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +include("shared.lua") +-- 76561198219964944 +-- a91f6942e11bb9a70ff8e3d7a0dcc737fd407c85d2d10ff95a1cfed59177e84a +function ENT:Draw() + self:DrawModel() + local pos = self:GetPos() + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Up(), 0) + ang:RotateAroundAxis(ang:Forward(), 85) + if LocalPlayer():GetPos():DistToSqr(self:GetPos()) < 200 ^ 2 then + cam.Start3D2D(pos + ang:Up()*0, Angle(0, LocalPlayer():EyeAngles().y-90, 90), 0.025) + draw.SimpleTextOutlined(Realistic_Police.BailerName, "rpt_font_22", -50, -3000, Realistic_Police.Colors["white"] , TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 0, Realistic_Police.Colors["white"] ); + cam.End3D2D() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/init.lua new file mode 100644 index 0000000..da4e644 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/init.lua @@ -0,0 +1,45 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +function ENT:Initialize() + self:SetModel("models/Humans/Group01/Female_02.mdl") + self:SetHullType(HULL_HUMAN) + self:SetHullSizeNormal() + self:SetNPCState(NPC_STATE_SCRIPT) + self:SetSolid(SOLID_BBOX) + self:SetUseType(SIMPLE_USE) +end +function ENT:Use(activator) + if Realistic_Police.CheckJailPos() then + RPTTableBailer = RPTTableBailer or {} + for k ,v in pairs(RPTTableBailer) do + if not IsValid(RPTTableBailer[k]["vEnt"]) or not timer.Exists("rpt_timerarrest"..RPTTableBailer[k]["vEnt"]:EntIndex()) then + print(RPTTableBailer[k]["vEnt"]) + RPTTableBailer[k] = nil + end + end +-- 76561198219964944 + + if #RPTTableBailer != 0 then + net.Start("RealisticPolice:HandCuff") + net.WriteString("Bailer") + net.WriteTable(RPTTableBailer) + net.Send(activator) + else + Realistic_Police.SendNotify(activator, Realistic_Police.GetSentence("noArrestedPlayer")) + end + else + Realistic_Police.SendNotify(activator, Realistic_Police.GetSentence("noJailPositions")) + end +end +function ENT:OnTakeDamage() + return 0 +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/shared.lua new file mode 100644 index 0000000..7e89894 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_bailer/shared.lua @@ -0,0 +1,15 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +ENT.Base = "base_ai" +ENT.Type = "ai" +ENT.PrintName = "Bailer" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = true +ENT.AdminSpawnable = true +-- 76561198219964944 +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/cl_init.lua new file mode 100644 index 0000000..9c92dea --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/cl_init.lua @@ -0,0 +1,19 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +include("shared.lua") +-- d4ec95c252515613de193db925956fba07d4ff50f580a4b42b7f507f9c716945 +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "RptCam" ) + self:NetworkVar( "String", 1, "RptRotate" ) +end + +function ENT:Draw() + self:DrawModel() +end +-- 55698bd11dd4b187c328ffc9200a97bcc7d15bb56979457ac0aeb39001de0521 + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/init.lua new file mode 100644 index 0000000..e6f433f --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/init.lua @@ -0,0 +1,68 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +ENT.AutomaticFrameAdvance = true +ENT.Editable = true +function ENT:Initialize() + self:SetModel( "models/wasted/wasted_kobralost_camera.mdl" ) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + local RealisticPolicePhys = self:GetPhysicsObject() + RealisticPolicePhys:EnableMotion(false) + RealisticPolicePhys:EnableGravity(true) + RealisticPolicePhys:Wake() + self.HealthEntity = Realistic_Police.CameraHealth + self.DestroyCam = false + self.RotateBack = false + self:SetRptRotate("nil") +end +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + if(self.HealthEntity <= 0) then return end + self.HealthEntity = self.HealthEntity - dmg:GetDamage() + if(self.HealthEntity <= 0) then + Realistic_Police.BrokeCamera(self) + end +end +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe + +function ENT:Think() + if self:GetRptRotate() != "nil" then + if self:GetRptRotate() == "Left" then + if not isnumber(self.RotateAngleY) then + self.RotateAngleY = 0 + end + if self.RotateAngleY < 50 then + self.RotateAngleY = self.RotateAngleY + 2 + self:ManipulateBoneAngles( 2, Angle(self.RotateAngleY,0,0) ) + end + end + if self:GetRptRotate() == "Right" then + if not isnumber(self.RotateAngleY) then + self.RotateAngleY = 0 + end + if self.RotateAngleY > -50 then + self.RotateAngleY = self.RotateAngleY - 2 + self:ManipulateBoneAngles( 2, Angle(self.RotateAngleY,0,0) ) + end + end + end +end + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "RptCam" ) + self:NetworkVar( "String", 1, "RptRotate" ) +end +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/shared.lua new file mode 100644 index 0000000..c371be0 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_camera/shared.lua @@ -0,0 +1,20 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "Camera" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = true +ENT.AdminSpawnable = true + + + + +-- 76561198219964937 +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/cl_init.lua new file mode 100644 index 0000000..3e81ef2 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/cl_init.lua @@ -0,0 +1,12 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +include("shared.lua") +function ENT:Draw() + self:DrawModel() +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/init.lua new file mode 100644 index 0000000..a78d5ad --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/init.lua @@ -0,0 +1,42 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +ENT.AutomaticFrameAdvance = true + +function ENT:Initialize() + self:SetModel( "models/props/sycreations/workstation.mdl" ) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + local RealisticPolicePhys = self:GetPhysicsObject() + if IsValid(RealisticPolicePhys) then + RealisticPolicePhys:EnableMotion(false) + RealisticPolicePhys:Wake() + end +end + +function ENT:Use(activator) + local job = Realistic_Police.GetPlayerJob(activator) + if (job == "ФСБ «Вымпел»" or Realistic_Police.HackerJob[job]) then + activator.AntiSpam = activator.AntiSpam or CurTime() + if activator.AntiSpam > CurTime() then return end + activator.AntiSpam = CurTime() + 1 + + net.Start("RealisticPolice:Open") + net.WriteString("OpenMainMenu") + net.WriteEntity(self) + net.WriteBool(false) + net.Send(activator) + else + Realistic_Police.SendNotify(activator, "Этот ПК только для сотрудников ФСБ!") + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/shared.lua new file mode 100644 index 0000000..41d6aa1 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer/shared.lua @@ -0,0 +1,17 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "Police Computer" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = true +ENT.AdminSpawnable = true + +-- dcb587233a6e567b06ae4da5655e3e9649694e7946f4d648e8f3181d8bc95b5f + +-- dcb587233a6e567b06ae4da5655e3e9649694e7946f4d648e8f3181d8bc95b5f diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/cl_init.lua new file mode 100644 index 0000000..82fb317 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/cl_init.lua @@ -0,0 +1,5 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/init.lua new file mode 100644 index 0000000..dfe2dce --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/init.lua @@ -0,0 +1,34 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +ENT.AutomaticFrameAdvance = true + +function ENT:Initialize() + self:SetModel( "models/props/sycreations/workstation.mdl" ) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + local RealisticPolicePhys = self:GetPhysicsObject() + if IsValid(RealisticPolicePhys) then + RealisticPolicePhys:EnableMotion(false) + RealisticPolicePhys:Wake() + end +end + +function ENT:Use(activator) + local job = Realistic_Police.GetPlayerJob(activator) + if (job == "СБУ" or Realistic_Police.HackerJob[job]) then + activator.AntiSpam = activator.AntiSpam or CurTime() + if activator.AntiSpam > CurTime() then return end + activator.AntiSpam = CurTime() + 1 + + net.Start("RealisticPolice:Open") + net.WriteString("OpenMainMenu") + net.WriteEntity(self) + net.WriteBool(false) + net.Send(activator) + else + Realistic_Police.SendNotify(activator, "Этот ПК только для сотрудников СБУ!") + end +end diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/shared.lua new file mode 100644 index 0000000..aadcb4a --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_computer_sbu/shared.lua @@ -0,0 +1,11 @@ +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "SBU Computer" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = true +ENT.AdminSpawnable = true + +function ENT:IsSBU() + return true +end diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/cl_init.lua new file mode 100644 index 0000000..943923e --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/cl_init.lua @@ -0,0 +1,23 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +include("shared.lua") +-- 55698bd11dd4b187c328ffc9200a97bcc7d15bb56979457ac0aeb39001de0521 +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe +function ENT:Draw() + self:DrawModel() + local pos = self:GetPos() + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Up(), 0) + ang:RotateAroundAxis(ang:Forward(), 85) + if LocalPlayer():GetPos():DistToSqr(self:GetPos()) < 200 ^ 2 then + cam.Start3D2D(pos + ang:Up()*0, Angle(0, LocalPlayer():EyeAngles().y-90, 90), 0.025) + draw.SimpleTextOutlined(Realistic_Police.JailerName, "rpt_font_22", -50, -3000, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 0, Realistic_Police.Colors["white"] ); + cam.End3D2D() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/init.lua new file mode 100644 index 0000000..c9aeec9 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/init.lua @@ -0,0 +1,39 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +-- 55698bd11dd4b187c328ffc9200a97bcc7d15bb56979457ac0aeb39001de0521 +function ENT:Initialize() + self:SetModel("models/Humans/Group01/Female_02.mdl") + self:SetHullType(HULL_HUMAN) + self:SetHullSizeNormal() + self:SetNPCState(NPC_STATE_SCRIPT) + self:SetSolid(SOLID_BBOX) + self:SetUseType(SIMPLE_USE) +end +function ENT:Use(activator) + if Realistic_Police.CheckJailPos() then + if IsValid(activator.WeaponRPT["Drag"]) && activator.WeaponRPT["Drag"]:IsPlayer() then + if Realistic_Police.CanCuff[Realistic_Police.GetPlayerJob(activator)] then + net.Start("RealisticPolice:HandCuff") + net.WriteString("Jailer") + net.WriteString(activator.WeaponRPT["Drag"]:GetName()) + net.Send(activator) + end + else + Realistic_Police.SendNotify(activator, Realistic_Police.GetSentence("needHoldPlayerToArrest")) + end + else + Realistic_Police.SendNotify(activator, Realistic_Police.GetSentence("noJailPositions")) + end +end +function ENT:OnTakeDamage() + return 0 +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/shared.lua new file mode 100644 index 0000000..111fa03 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailer/shared.lua @@ -0,0 +1,15 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +ENT.Base = "base_ai" +ENT.Type = "ai" +ENT.PrintName = "Jailer" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = true +ENT.AdminSpawnable = true +-- 76561198219964944 +-- 76561198219964937 diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/cl_init.lua new file mode 100644 index 0000000..7b32030 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/cl_init.lua @@ -0,0 +1,17 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +include("shared.lua") +-- 55698bd11dd4b187c328ffc9200a97bcc7d15bb56979457ac0aeb39001de0521 +-- a91f6942e11bb9a70ff8e3d7a0dcc737fd407c85d2d10ff95a1cfed59177e84a +function ENT:Draw() + self:DrawModel() + self:SetColor(Realistic_Police.Colors["green"]) + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetMaterial("models/wireframe") +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/init.lua new file mode 100644 index 0000000..827b500 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/init.lua @@ -0,0 +1,25 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +ENT.AutomaticFrameAdvance = true +-- dcb587233a6e567b06ae4da5655e3e9649694e7946f4d648e8f3181d8bc95b5f +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe +function ENT:Initialize() + self:SetModel( "models/hunter/blocks/cube05x05x05.mdl" ) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + local RealisticPolicePhys = self:GetPhysicsObject() + RealisticPolicePhys:EnableMotion(false) + RealisticPolicePhys:EnableGravity(true) + RealisticPolicePhys:Wake() +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/shared.lua new file mode 100644 index 0000000..85285ef --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_jailpos/shared.lua @@ -0,0 +1,18 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "Jail Pos" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +-- 76561198219964944 + +-- d4ec95c252515613de193db925956fba07d4ff50f580a4b42b7f507f9c716945 + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/cl_init.lua new file mode 100644 index 0000000..0bfde55 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/cl_init.lua @@ -0,0 +1,118 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +include("shared.lua") + +local function GetCameras() + local TableEnt = {} + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + if not IsValid(v:CPPIGetOwner()) then + if IsValid(v) then + table.insert(TableEnt, v) + end + end + end + return TableEnt +end + +function ENT:Draw() + if IsValid(self) && IsEntity(self) then + self:DrawModel() + local pos = self:GetPos() + self:GetAngles():Forward() * 6.1 + self:GetAngles():Up() * 37 + self:GetAngles():Right() * 29 + local ang = self:GetAngles() + ang:RotateAroundAxis( ang:Up(), -90 ) + ang:RotateAroundAxis( ang:Right(), 180 ) + ang:RotateAroundAxis( ang:Forward(), 270 ) + + cam.Start3D2D(pos, ang, 0.25) + if self:GetPos():DistToSqr(LocalPlayer():GetPos()) < 250^2 then + local cameras = GetCameras() + + if self.CreateMaterialRPT == nil && self.RenderTarget == nil then + self:CameraRender(cameras) + end + + if istable(cameras) then + if IsValid(cameras[self:GetNWInt("CameraId")]) then + if not cameras[self:GetNWInt("CameraId")]:GetRptCam() then + surface.SetDrawColor(Realistic_Police.Colors["white"]) + surface.SetMaterial(self.CreateMaterialRPT) + surface.DrawTexturedRect(0, 0, 227, 146) + else + draw.RoundedBox(0, 0, 0, 227, 146, Realistic_Police.Colors["black20"]) + draw.DrawText(Realistic_Police.GetSentence("noSignal"), "rpt_font_19", 113.5, 53, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER) + draw.DrawText(Realistic_Police.GetSentence("connectionProblem"), "rpt_font_19", 113.5, 73, Realistic_Police.Colors["red"], TEXT_ALIGN_CENTER) + end + end + end + else + if self.CreateMaterialRPT != nil && self.RenderTarget != nil then + hook.Remove( "HUDPaint", "RPTCamera:Render"..self:EntIndex() ) + self.CreateMaterialRPT = nil + self.RenderTarget = nil + end + end + cam.End3D2D() + end +end + +function ENT:CameraRender(camera) + self.RenderTarget = GetRenderTarget("rptcamera1"..self:EntIndex(), 500, 500, true) + self.CreateMaterialRPT = CreateMaterial("rptcamera1"..self:EntIndex(), "UnlitGeneric", {["$basetexture"] = self.RenderTarget,}) + + timer.Create("rptcamera1"..self:EntIndex(), Realistic_Police.CameraUpdateRate, 0, function() + if not IsValid(self) then + timer.Remove("rptcamera1"..self:EntIndex()) + return + end + + local cameraId = self:GetNWInt("CameraId") + + if not istable(camera) then return end + if not IsValid(self) then return end + if not isnumber(cameraId) then return end + if not IsValid(camera[cameraId]) then return end + + RPTPlayerDraw = false + + render.PushRenderTarget(self.RenderTarget) + cam.Start2D() + render.OverrideAlphaWriteEnable( true, true ) + + render.ClearDepth() + render.Clear( 0, 0, 0, 0 ) +local ang = camera[cameraId]:GetAngles() + ang:RotateAroundAxis(ang:Up(), -90) + local pos = camera[cameraId]:GetPos() + ang:Forward() * 25 + ang:Up() * 10 + + render.RenderView({ + x = 0, + y = 0, + w = 1920, + h = 1080, + origin = pos, + angles = ang, + viewid = 2, + }) +render.OverrideAlphaWriteEnable( false ) + cam.End2D() + render.PopRenderTarget() +-- d4ec95c252515613de193db925956fba07d4ff50f580a4b42b7f507f9c716945 + + if IsValid(self) then + if self.RenderTarget != nil then + self.CreateMaterialRPT:SetTexture( '$basetexture', self.RenderTarget ) + end + end + RPTPlayerDraw = false + end) +end +function ENT:Initialize() + self:CameraRender() +end + + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/init.lua new file mode 100644 index 0000000..ec94651 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/init.lua @@ -0,0 +1,54 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +ENT.AutomaticFrameAdvance = true +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe + +local function GetCameras() + local Tbl = {} + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + if not IsValid(v:CPPIGetOwner()) then + if IsValid(v) then + Tbl[#Tbl + 1] = v + end + end + end + return Tbl +end +-- 76561198219964944 + +function ENT:Initialize() + self:SetModel( "models/props/cs_office/tv_plasma.mdl" ) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetNWInt("CameraId", 1) +timer.Create("rpt_refresh_camera:"..self:EntIndex(), 3, 0, function() + if not IsValid(self) then return end + for k,v in pairs(ents.FindInSphere(self:GetPos(), 100)) do + if IsValid(v) && v:IsPlayer() then + v.RPTShowEntity = GetCameras()[self:GetNWInt("CameraId")] + end + end + end) +end + +function ENT:Use(activator) + local getCameras = GetCameras() + + if self:GetNWInt("CameraId") < #getCameras then + self:SetNWInt("CameraId", self:GetNWInt("CameraId") + 1) + else + self:SetNWInt("CameraId", 1) + end + activator.RPTShowEntity = getCameras[self:GetNWInt("CameraId")] +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/shared.lua new file mode 100644 index 0000000..87fd95a --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_policescreen/shared.lua @@ -0,0 +1,18 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "Police Screen" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = true +ENT.AdminSpawnable = true + +-- 76561198219964937 + +-- 76561198219964937 + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/cl_init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/cl_init.lua new file mode 100644 index 0000000..06d113a --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/cl_init.lua @@ -0,0 +1,109 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +include("shared.lua") + +local function GetCameras(Players) + local TableEnt = {} + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + if IsValid(v:CPPIGetOwner()) then + if v:CPPIGetOwner() == Players then + if IsValid(v) then + table.insert(TableEnt, v) + end + end + end + end + return TableEnt +end +function ENT:Draw() + if IsValid(self) && IsEntity(self) then + self:DrawModel() + local pos = self:GetPos() + self:GetAngles():Forward() * 6.1 + self:GetAngles():Up() * 37 + self:GetAngles():Right() * 29 + local ang = self:GetAngles() + ang:RotateAroundAxis( ang:Up(), -90 ) + ang:RotateAroundAxis( ang:Right(), 180 ) + ang:RotateAroundAxis( ang:Forward(), 270 ) + + cam.Start3D2D(pos, ang, 0.25) + if self:GetPos():DistToSqr(LocalPlayer():GetPos()) < 250^2 then + if self.CreateMaterialRPT == nil && self.RenderTarget == nil then + self:CameraRender() + end + if IsValid(self:CPPIGetOwner()) && self:CPPIGetOwner():IsPlayer() then + local cameras = GetCameras(self:CPPIGetOwner()) + if istable(cameras) then + if IsValid(cameras[self:GetNWInt("CameraId")]) then + if not cameras[self:GetNWInt("CameraId")]:GetRptCam() then + surface.SetDrawColor(Realistic_Police.Colors["white"]) + surface.SetMaterial( self.CreateMaterialRPT ) + surface.DrawTexturedRect( 0, 0, 227, 146 ) + else + draw.RoundedBox(0, 0, 0, 227, 146, Realistic_Police.Colors["black20"]) + draw.DrawText(Realistic_Police.GetSentence("noSignal"), "rpt_font_19", 113.5, 53, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER) + draw.DrawText(Realistic_Police.GetSentence("connectionProblem"), "rpt_font_19", 113.5, 73, Realistic_Police.Colors["red"], TEXT_ALIGN_CENTER) + end + end + end + end + else + if self.CreateMaterialRPT != nil && self.RenderTarget != nil then + hook.Remove( "HUDPaint", "RPTCamera:Render"..self:EntIndex() ) + self.CreateMaterialRPT = nil + self.RenderTarget = nil + end + end + cam.End3D2D() + end +end +function ENT:CameraRender() + self.RenderTarget = GetRenderTarget( "rptcamera1"..self:EntIndex(), 500, 500, false ) + self.CreateMaterialRPT = CreateMaterial( "rptcamera1"..self:EntIndex(), "UnlitGeneric", { ["$basetexture"] = self.RenderTarget, } ) +timer.Create("rptcamera1"..self:EntIndex(), Realistic_Police.CameraUpdateRate, 0, function() + if not IsValid(self) then + timer.Remove("rptcamera1"..self:EntIndex()) + return + end + + RPTPlayerDraw = true + render.PushRenderTarget( self.RenderTarget ) + render.ClearDepth() + if IsValid(self:CPPIGetOwner()) && self:CPPIGetOwner():IsPlayer() then + local cameras = GetCameras(self:CPPIGetOwner()) + if istable(cameras) then + if IsValid( cameras[self:GetNWInt("CameraId")] ) then + local Angs = cameras[self:GetNWInt("CameraId")]:GetAngles() + Angs:RotateAroundAxis(Angs:Up(), -90) +local pos = cameras[self:GetNWInt("CameraId")]:GetPos() + Angs:Forward() * 25 + Angs:Up() * 10 + + render.RenderView({ + x = 0, + y = 0, + w = 1920, + h = 1080, + origin = pos, + angles = Angs, + viewid = 2, + }) + end + end + end +render.UpdateScreenEffectTexture() + render.PopRenderTarget() + if IsValid(self) then + if self.RenderTarget != nil then + self.CreateMaterialRPT:SetTexture( '$basetexture', self.RenderTarget) + end + end + RPTPlayerDraw = false + end) +end + +function ENT:Initialize() + self:CameraRender() +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/init.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/init.lua new file mode 100644 index 0000000..6147d44 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/init.lua @@ -0,0 +1,53 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +ENT.AutomaticFrameAdvance = true +-- a91f6942e11bb9a70ff8e3d7a0dcc737fd407c85d2d10ff95a1cfed59177e84a + +local function GetCameras(Players) + local Tbl = {} + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + if IsValid(v:CPPIGetOwner()) then + if v:CPPIGetOwner() == Players then + if IsValid(v) then + Tbl[#Tbl + 1] = v + end + end + end + end + return Tbl +end +function ENT:Initialize() + self:SetModel( "models/props/cs_office/tv_plasma.mdl" ) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetNWInt("CameraId", 1) +timer.Create("rpt_refresh_camera:"..self:EntIndex(), 3, 0, function() + if not IsValid(self) then return end + for k,v in pairs(ents.FindInSphere(self:GetPos(), 100)) do + if IsValid(v) && v:IsPlayer() then + v.RPTShowEntity = GetCameras(v)[self:GetNWInt("CameraId")] + end + end + end) +end +function ENT:Use(activator) + if IsValid(self:CPPIGetOwner()) then + if self:GetNWInt("CameraId") < #GetCameras(self:CPPIGetOwner()) then + self:SetNWInt("CameraId", self:GetNWInt("CameraId") + 1) + else + self:SetNWInt("CameraId", 1) + end + activator.RPTShowEntity = GetCameras(self:CPPIGetOwner())[self:GetNWInt("CameraId")] + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/shared.lua new file mode 100644 index 0000000..397cd38 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/entities/realistic_police_screen/shared.lua @@ -0,0 +1,18 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +ENT.Base = "base_gmodentity" +ENT.Type = "anim" +ENT.PrintName = "Camera Screen" +ENT.Category = "Realistic Police" +ENT.Author = "Kobralost" +ENT.Spawnable = true +ENT.AdminSpawnable = true + + +-- 55698bd11dd4b187c328ffc9200a97bcc7d15bb56979457ac0aeb39001de0521 + +-- 76561198219964944 diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/gmodadminsuite/modules/logging/modules/rpt_compatibilty_blogs.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/gmodadminsuite/modules/logging/modules/rpt_compatibilty_blogs.lua new file mode 100644 index 0000000..0311c71 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/gmodadminsuite/modules/logging/modules/rpt_compatibilty_blogs.lua @@ -0,0 +1,69 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +local MODULE = GAS.Logging:MODULE() +MODULE.Category = "Realistic Police" +MODULE.Name = "Stungun" +MODULE.Colour = Color(44, 91, 240) + +MODULE:Hook("RealisticPolice:Tazz", "RealisticPolice:Tazz", function(ply, owner) + MODULE:Log(GAS.Logging:FormatPlayer(ply) .. " was tased by "..GAS.Logging:FormatPlayer(owner)) +end) + +GAS.Logging:AddModule(MODULE) + +----------------------------------------------------------------- + +local MODULE = GAS.Logging:MODULE() +MODULE.Category = "Realistic Police" +MODULE.Name = "Cuff / UnCuff" +MODULE.Colour = Color(44, 91, 240) + +MODULE:Hook("RealisticPolice:Cuffed", "RealisticPolice:Cuffed", function(ply, officer) + MODULE:Log(GAS.Logging:FormatPlayer(ply).. " was cuffed by "..GAS.Logging:FormatPlayer(officer)) +end) +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe + +MODULE:Hook("RealisticPolice:UnCuffed", "RealisticPolice:UnCuffed", function(ply, officer) + MODULE:Log(GAS.Logging:FormatPlayer(ply).. " was uncuffed by "..GAS.Logging:FormatPlayer(officer)) +end) + +GAS.Logging:AddModule(MODULE) + +----------------------------------------------------------------- + +local MODULE = GAS.Logging:MODULE() +MODULE.Category = "Realistic Police" +MODULE.Name = "Jail / UnJail" +MODULE.Colour = Color(44, 91, 240) + +MODULE:Hook("RealisticPolice:Jailed", "RealisticPolice:Jailed", function(ply, Time) + MODULE:Log(GAS.Logging:FormatPlayer(ply).. " was jailed for "..Time.." s") +end) + +MODULE:Hook("RealisticPolice:UnJailed", "RealisticPolice:UnJailed", function(ply, officer) + MODULE:Log(GAS.Logging:FormatPlayer(ply).. " was unjailed") +end) +-- 76561198219964944 + +GAS.Logging:AddModule(MODULE) + +----------------------------------------------------------------- + +local MODULE = GAS.Logging:MODULE() +MODULE.Category = "Realistic Police" +MODULE.Name = "Fining" +MODULE.Colour = Color(44, 91, 240) + +MODULE:Hook("RealisticPolice:FinePlayer", "RealisticPolice:FinePlayer", function(ply, rptEnt) + MODULE:Log(GAS.Logging:FormatPlayer(ply).. " send a fine to "..GAS.Logging:FormatPlayer(rptEnt)) +end) + +MODULE:Hook("RealisticPolice:FineVehicle", "RealisticPolice:FineVehicle", function(ply, officer) + MODULE:Log(GAS.Logging:FormatPlayer(ply).. " send a fine to a vehicle") +end) + +GAS.Logging:AddModule(MODULE) diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_camera.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_camera.lua new file mode 100644 index 0000000..b9a7df0 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_camera.lua @@ -0,0 +1,550 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +net.Receive("RealisticPolice:SecurityCamera", function() + local RPTListCamera = net.ReadTable() or {} + if not IsValid(RPTCFrame) then Realistic_Police.OpenCamera(RPTListCamera) end +end ) + +function Realistic_Police.Camera() + net.Start("RealisticPolice:SecurityCamera") + net.SendToServer() +end +function Realistic_Police.OpenCamera(RPTListCamera) + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 0 + local RPTTarget = 300 + local speed = 10 + local RPTId = 1 + + if IsValid(RPTCFrame) then RPTCFrame:Remove() end + + RPTCFrame = vgui.Create("DFrame", RPTFrame) + RPTCFrame:SetSize(RpRespX*0.682, RpRespY*0.675) + RPTCFrame:SetPos(RpRespX*0.1569, RpRespY*0.153) + RPTCFrame:ShowCloseButton(false) + RPTCFrame:SetDraggable(true) + RPTCFrame:SetTitle("") + RPTCFrame:SetScreenLock( true ) + RPTCFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w*0.708, h*0.72 ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( 0, 0, w*0.708, h*0.053 ) + + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w*0.708, h*0.72 ) + + draw.DrawText(Realistic_Police.GetSentence("cameraCenter"), "rpt_font_7", RpRespX*0.003, RpRespY*0.0038, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + function RPTCFrame:OnMousePressed() + RPTCFrame:RequestFocus() + local screenX, screenY = self:LocalToScreen( 0, 0 ) + if ( self.m_bSizable && gui.MouseX() > ( screenX + self:GetWide() - 20 ) && 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() && gui.MouseY() < ( screenY + 24 ) ) then + self.Dragging = { gui.MouseX() - self.x, gui.MouseY() - self.y } + self:MouseCapture( true ) + return + end + end + + local RPTScroll = vgui.Create("DScrollPanel", RPTCFrame) + RPTScroll:SetSize(RpRespX*0.12, RpRespY*0.44) + RPTScroll:SetPos(RpRespX*0.003, RpRespY*0.04) + RPTScroll.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(0,0) + + local DPanel = vgui.Create("DPanel", RPTCFrame) + DPanel:SetSize(RpRespX*0.355, RpRespY*0.44) + DPanel:SetPos(RpRespX*0.125, RpRespY*0.04) + DPanel.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) +surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + draw.DrawText(Realistic_Police.GetSentence("cameraSecurityOfCity"), "rpt_font_7", RpRespX*0.01, RpRespY*0.014, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + timer.Simple(0.2, function() + local DScroll2 = vgui.Create("DScrollPanel", DPanel) + DScroll2:SetSize(RpRespX*0.335, RpRespY*0.37) + DScroll2:SetPos(RpRespX*0.01, RpRespY*0.055) + DScroll2.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black49"] ) + surface.DrawRect( 0, 0, w, h ) + + local x,y = self:LocalToScreen(0, 0) + if #RPTListCamera != 0 then + local pos, ang = RPTListCamera[RPTId]:GetBonePosition(2) + if pos == RPTListCamera[RPTId]:GetPos() then + pos = RPTListCamera[RPTId]:GetBoneMatrix(2):GetTranslation() + end + local VectorBone = pos + local Angles = ang +if isangle(ang) then + Angles:RotateAroundAxis(Angles:Up(), -90) + Angles:RotateAroundAxis(Angles:Forward(), -270) + Angles:RotateAroundAxis(Angles:Right(), 90) + else + Angles = RPTListCamera[RPTId]:GetAngles() + Angles:RotateAroundAxis(Angles:Up(), -90) + end + + local Pos = RPTListCamera[RPTId]:GetPos() + Pos = Pos + Angles:Forward() * 25 + Angles:Up() * 10 + + if not RPTListCamera[RPTId]:GetRptCam() then + render.RenderView( { + origin = Pos, + angles = Angles, + x = x + 7.5, y = y + 7.5, + w = w - 15, h = h - 15 + } ) + if IsValid(DPanelBroke) then + DPanelBroke:Remove() + end + else + if not IsValid(DPanelBroke) then + DPanelBroke = vgui.Create("DPanel", DScroll2) + DPanelBroke:SetSize(RpRespX*0.335, RpRespY*0.37) + DPanelBroke:SetPos(0, 0) + DPanelBroke.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black20"] ) + surface.DrawRect( 0, 0, w, h ) + + draw.SimpleText(Realistic_Police.GetSentence("noSignal"), "rpt_font_12", DPanelBroke:GetWide()/2, DPanelBroke:GetTall()/2.5, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(Realistic_Police.GetSentence("connectionProblem"), "rpt_font_13", DPanelBroke:GetWide()/2, DPanelBroke:GetTall()/1.8, Realistic_Police.Colors["red"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + end + end + + local sbar = DScroll2:GetVBar() + sbar:SetSize(10,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + if istable(RPTListCamera) then + for k,v in pairs(RPTListCamera) do + local DButton1 = vgui.Create("DButton", RPTScroll) + DButton1:SetSize(0,RpRespY*0.05) + if isstring(v:GetNWString("rpt_name_camera")) && #v:GetNWString("rpt_name_camera") > 1 then + DButton1:SetText(v:GetNWString("rpt_name_camera")) + else + DButton1:SetText("Camera "..k) + end + DButton1:SetFont("rpt_font_7") + DButton1:SetTextColor(Realistic_Police.Colors["white"]) + DButton1:Dock(TOP) + DButton1:DockMargin(0,5,0,0) + DButton1.Paint = function(self,w,h) + if self:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["black25200"] ) + surface.DrawRect( 5, 0, w-10, h ) + else + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + end + end + DButton1.DoClick = function() + RPTId = k + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("ShowCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.SendToServer() + Realistic_Police.Clic() + end + end + end + + local DButtonLeft = vgui.Create("DButton", DScroll2) + DButtonLeft:SetSize(RpRespX*0.03, DScroll2:GetTall()*0.96) + DButtonLeft:SetPos(RpRespX*0.001, RpRespY*0.008) + DButtonLeft:SetText("〈") + DButtonLeft:SetFont("rpt_font_13") + DButtonLeft:SetTextColor(Realistic_Police.Colors["white200"]) + DButtonLeft.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black2510"] ) + surface.DrawRect( 5, 0, w-10, h ) + + if IsValid(RPTListCamera[RPTId]) then + if not self:IsHovered() then + if #RPTListCamera != 0 then + if IsValid(RPTListCamera[RPTId]) then + if RPTListCamera[RPTId]:GetRptRotate() != "nil" && RPTListCamera[RPTId]:GetRptRotate() != "Right" then + if input.IsMouseDown( MOUSE_LEFT ) then + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + end + end + end + end + end + end + DButtonLeft.OnMousePressed = function() + if IsValid(RPTListCamera[RPTId]) then + local BoneLookUp = RPTListCamera[RPTId]:LookupBone(RPTListCamera[RPTId]:GetBoneName(2)) + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("Left") + net.SendToServer() + end + end + DButtonLeft.OnMouseReleased = function() + if IsValid(RPTListCamera[RPTId]) then + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + end + + local DButtonRight = vgui.Create("DButton", DScroll2) + DButtonRight:SetSize(RpRespX*0.03, DScroll2:GetTall()*0.96) + DButtonRight:SetPos(RpRespX*0.304, RpRespY*0.008) + DButtonRight:SetText("〉") + DButtonRight:SetFont("rpt_font_13") + DButtonRight:SetTextColor(Realistic_Police.Colors["white200"]) + DButtonRight.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black2510"] ) + surface.DrawRect( 5, 0, w-10, h ) + + if not self:IsHovered() then + if IsValid(RPTListCamera[RPTId]) then + if #RPTListCamera != 0 then + if RPTListCamera[RPTId]:GetRptRotate() != "nil" && RPTListCamera[RPTId]:GetRptRotate() != "Left" then + if input.IsMouseDown( MOUSE_LEFT ) then + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + end + end + end + end + end + DButtonRight.OnMousePressed = function() + if IsValid(RPTListCamera[RPTId]) then + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("Right") + net.SendToServer() + end + end + DButtonRight.OnMouseReleased = function() + if RPTListCamera[RPTId] then + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + end + end ) + + local RPTResize = vgui.Create("DButton", RPTCFrame) + RPTResize:SetSize(RpRespX*0.016, RpRespY*0.026) + RPTResize:SetPos(RPTCFrame:GetWide()*0.667, RPTCFrame:GetTall()*0.075) + RPTResize:SetText("") + RPTResize.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["white200"] ) + surface.SetMaterial( Realistic_Police.Colors["Material8"] ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + RPTResize.DoClick = function() + Realistic_Police.Clic() + Realistic_Police.FulLScreenCamera(RPTListCamera) + --RPTCFrame:Remove() + end + + local RPTClose = vgui.Create("DButton", RPTCFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTCFrame:GetWide()*0.663, RPTCFrame:GetTall()*0.005) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTCFrame:Remove() + Realistic_Police.Clic() + end +end + +function Realistic_Police.FulLScreenCamera(RPTListCamera) + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 0 + local RPTTarget = 300 + local speed = 10 + local RPTId = 1 + if IsValid(RPTCFrame) then RPTCFrame:Remove() end + + RPTCFrame = vgui.Create("DFrame", RPTFrame) + RPTCFrame:SetSize(RpRespX*1, RpRespY*1) + RPTCFrame:ShowCloseButton(false) + RPTCFrame:SetDraggable(false) + RPTCFrame:SetTitle("") + RPTCFrame:SetScreenLock( true ) + RPTCFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black15200"].r, Realistic_Police.Colors["black15200"].g, Realistic_Police.Colors["black15200"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + draw.DrawText(Realistic_Police.GetSentence("cameraCenter"), "rpt_font_7", RpRespX*0.008, RpRespY*0.01, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + local RPTScroll = vgui.Create("DScrollPanel", RPTCFrame) + RPTScroll:SetSize(RpRespX*0.12, RpRespY*0.72) + RPTScroll:SetPos(RpRespX*0.01, RpRespY*0.04) + RPTScroll.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(0,0) + + local DPanel = vgui.Create("DPanel", RPTCFrame) + DPanel:SetSize(RpRespX*0.648, RpRespY*0.72) + DPanel:SetPos(RpRespX*0.137, RpRespY*0.04) + DPanel.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + net.Start("RealisticPolice:SecurityCamera") + net.SendToServer() + + timer.Simple(0.2, function() + local DScroll2 = vgui.Create("DScrollPanel", DPanel) + DScroll2:SetSize(RpRespX*0.639, RpRespY*0.703) + DScroll2:SetPos(RpRespX*0.005, RpRespY*0.008) + DScroll2.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black49"] ) + surface.DrawRect( 0, 0, w, h ) + + local x,y = self:LocalToScreen(0, 0) + if #RPTListCamera != 0 then + local pos, ang = RPTListCamera[RPTId]:GetBonePosition(2) + if pos == RPTListCamera[RPTId]:GetPos() then + pos = RPTListCamera[RPTId]:GetBoneMatrix(2):GetTranslation() + end + local VectorBone = pos + local Angles = ang + + if isangle(ang) then + Angles:RotateAroundAxis(Angles:Up(), -90) + Angles:RotateAroundAxis(Angles:Forward(), -270) + Angles:RotateAroundAxis(Angles:Right(), 90) + else + Angles = RPTListCamera[RPTId]:GetAngles() + Angles:RotateAroundAxis(Angles:Up(), -90) + end + + local Pos = RPTListCamera[RPTId]:GetPos() + Pos = Pos + Angles:Forward() * 25 + Angles:Up() * 10 + + if not RPTListCamera[RPTId]:GetRptCam() then + render.RenderView( { + origin = Pos, + angles = Angles, + x = x + 7.5, y = y + 7.5, + w = w - 15, h = h - 15 + } ) + if IsValid(DPanelBroke) then + DPanelBroke:Remove() + end + else + if not IsValid(DPanelBroke) then + DPanelBroke = vgui.Create("DPanel", DScroll2) + DPanelBroke:SetSize(RpRespX*0.639, RpRespY*0.703) + DPanelBroke:SetPos(0, 0) + DPanelBroke.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black20"] ) + surface.DrawRect( 0, 0, w, h ) + + draw.SimpleText(Realistic_Police.GetSentence("noSignal"), "rpt_font_12", DPanelBroke:GetWide()/2, DPanelBroke:GetTall()/2.5, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(Realistic_Police.GetSentence("connectionProblem"), "rpt_font_13", DPanelBroke:GetWide()/2, DPanelBroke:GetTall()/1.8, Realistic_Police.Colors["red"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + end + end + + local sbar = DScroll2:GetVBar() + sbar:SetSize(10,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + if istable(RPTListCamera) then + for k,v in pairs(RPTListCamera) do + local DButton1 = vgui.Create("DButton", RPTScroll) + DButton1:SetSize(0,RpRespY*0.05) + if isstring(v:GetNWString("rpt_name_camera")) && #v:GetNWString("rpt_name_camera") > 1 then + DButton1:SetText(v:GetNWString("rpt_name_camera")) + else + DButton1:SetText("Camera "..k) + end + DButton1:SetFont("rpt_font_7") + DButton1:SetTextColor(Realistic_Police.Colors["white"]) + DButton1:Dock(TOP) + DButton1:DockMargin(0,5,0,0) + DButton1.Paint = function(self,w,h) + if self:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["black25200"] ) + surface.DrawRect( 5, 0, w-10, h ) + else + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + end + end + DButton1.DoClick = function() + RPTId = k + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("ShowCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.SendToServer() + Realistic_Police.Clic() + end + end + end +-- 55698bd11dd4b187c328ffc9200a97bcc7d15bb56979457ac0aeb39001de0521 + + local DButtonLeft = vgui.Create("DButton", DScroll2) + DButtonLeft:SetSize(RpRespX*0.03, DScroll2:GetTall()*0.96) + DButtonLeft:SetPos(RpRespX*0.001, RpRespY*0.008) + DButtonLeft:SetText("〈") + DButtonLeft:SetFont("rpt_font_13") + DButtonLeft:SetTextColor(Realistic_Police.Colors["white"]) + DButtonLeft.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black2510"] ) + surface.DrawRect( 5, 0, w-10, h ) + if not self:IsHovered() then + if #RPTListCamera != 0 && IsValid(RPTListCamera[RPTId]) then + if RPTListCamera[RPTId]:GetRptRotate() != "nil" && RPTListCamera[RPTId]:GetRptRotate() != "Right" then + if input.IsMouseDown( MOUSE_LEFT ) then + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + end + end + end + end + DButtonLeft.OnMousePressed = function() + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("Left") + net.SendToServer() + end + DButtonLeft.OnMouseReleased = function() + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + + local DButtonRight = vgui.Create("DButton", DScroll2) + DButtonRight:SetSize(RpRespX*0.03, DScroll2:GetTall()*0.96) + DButtonRight:SetPos(RpRespX*0.605, RpRespY*0.008) + DButtonRight:SetText("〉") + DButtonRight:SetFont("rpt_font_13") + DButtonRight:SetTextColor(Realistic_Police.Colors["white"]) + DButtonRight.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black2510"] ) + surface.DrawRect( 5, 0, w-10, h ) + if not self:IsHovered() then + if #RPTListCamera != 0 && IsValid(RPTListCamera[RPTId]) then + if RPTListCamera[RPTId]:GetRptRotate() != "nil" && RPTListCamera[RPTId]:GetRptRotate() != "Left" then + if input.IsMouseDown( MOUSE_LEFT ) then + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + end + end + end + end + DButtonRight.OnMousePressed = function() + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("Right") + net.SendToServer() + end + DButtonRight.OnMouseReleased = function() + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("RotateCamera") + net.WriteEntity(RPTListCamera[RPTId]) + net.WriteString("nil") + net.SendToServer() + end + end ) + local RPTClose = vgui.Create("DButton", RPTCFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTCFrame:GetWide()*0.763, RPTCFrame:GetTall()*0.0052) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTCFrame:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_cmd.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_cmd.lua new file mode 100644 index 0000000..2cd1519 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_cmd.lua @@ -0,0 +1,222 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +local Sounds = { + [1] = "https://media.vocaroo.com/mp3/5nsksDxB4mJ", + [2] = "https://media.vocaroo.com/mp3/hPxWgG4BJTH", + [3] = "https://media.vocaroo.com/mp3/BgNxBllRoEE", +} +function Realistic_Police.HackKey() + sound.PlayURL( Sounds[math.random(1, #Sounds)], "", + function( station ) + if IsValid( station ) then + station:Play() + station:SetVolume(1) + end + end ) +end + +local ipNumbers = { + [1] = {min = 100, max = 999}, + [2] = {min = 100, max = 999}, + [3] = {min = 1, max = 30}, + [4] = {min = 1, max = 10} +} + +local function generateIP() + local ipAdress = "" + for _, v in ipairs(ipNumbers) do + if not isnumber(v.min) then continue end + if not isnumber(v.max) then continue end + ipAdress = ipAdress == "" and ipAdress..math.random(v.min, v.max) or ipAdress.."."..math.random(v.min, v.max) + end + return ipAdress +end + +function Realistic_Police.Cmd(Frame, Ent) + CoultDown = CoultDown or CurTime() + if CoultDown > CurTime() then return end + CoultDown = CurTime() + 1 + + local RpRespX, RpRespY = ScrW(), ScrH() + if not Realistic_Police.HackerJob[Realistic_Police.GetPlayerJob(LocalPlayer())] then RealisticPoliceNotify(Realistic_Police.GetSentence("cantDoThat")) return end + local RPTValue = 0 + local RPTTarget = 300 + local speed = 10 + local TextHack = "" + local TextColor = Realistic_Police.Colors["gray220"] + local CanWrite = true + local FinishHack = false + local ipAdress = generateIP() + local TableString = { + [1] = "" + } + local RandomWord = Realistic_Police.WordHack[math.random(1, #Realistic_Police.WordHack)] + local Progress = 1 + + local RPTCMDFrame = vgui.Create("DFrame", RPTFrame) + RPTCMDFrame:SetSize(RpRespX*0.4825, RpRespY*0.486) + RPTCMDFrame:ShowCloseButton(false) + RPTCMDFrame:SetDraggable(true) + RPTCMDFrame:SetTitle("") + RPTCMDFrame:Center() + RPTCMDFrame:SetKeyboardInputEnabled(true) + RPTCMDFrame:RequestFocus() + RPTCMDFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) +surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.074) + + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + local osName = Realistic_Police.GetSentence("osName") + draw.DrawText(osName.." CMD", "rpt_font_7", RpRespX*0.003, RpRespY*0.0038, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + + draw.DrawText(osName.." OS [Version 1.0] \n(c) "..osName.." Corporation 2020", "rpt_font_7", RpRespX*0.003, RpRespY*0.05, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + draw.DrawText("C:/Users/"..osName.."Corporation/cmd", "rpt_font_7", RpRespX*0.003, RpRespY*0.12, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + + for k,v in pairs(TableString) do + if k > 7 then table.remove(TableString, 1) end + if not FinishHack then + if TableString[k] == "" then + draw.DrawText("Write this Word "..RandomWord.." : "..TextHack, "rpt_font_7", RpRespX*0.003, (RpRespY*0.16) * (k*0.25) + RpRespY*0.12, TextColor, TEXT_ALIGN_LEFT) + elseif isstring(TableString[k]) then + draw.DrawText("Write this Word "..TableString[k].." : "..TableString[k], "rpt_font_7", RpRespX*0.003, (RpRespY*0.16) * (k*0.25) + RpRespY*0.12, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + elseif k != 7 then + if isstring(TableString[k]) then + draw.DrawText(TableString[k], "rpt_font_7", RpRespX*0.003, (RpRespY*0.16) * (k*0.25) + RpRespY*0.12, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + elseif k == 7 then + if isstring(TableString[k]) then + draw.DrawText(TableString[k], "rpt_font_7", RpRespX*0.003, (RpRespY*0.16) * (k*0.25) + RpRespY*0.12, TextColor, TEXT_ALIGN_LEFT) + end + end + end + end + function RPTCMDFrame:OnMousePressed() + RPTCMDFrame:RequestFocus() + local screenX, screenY = self:LocalToScreen( 0, 0 ) + if ( self.m_bSizable && gui.MouseX() > ( screenX + self:GetWide() - 20 ) && 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() && gui.MouseY() < ( screenY + 24 ) ) then + self.Dragging = { gui.MouseX() - self.x, gui.MouseY() - self.y } + self:MouseCapture( true ) + return + end + end +local RPTCMDFrameP = vgui.Create( "DProgress", RPTCMDFrame ) + RPTCMDFrameP:SetPos( 0, RpRespY*0.48 ) + RPTCMDFrameP:SetSize( RpRespX*0.4825, RpRespY*0.01 ) + RPTCMDFrameP:SetFraction( 0 ) + function RPTCMDFrame:OnKeyCodePressed( key ) + Realistic_Police.HackKey() + if CanWrite then + if input.GetKeyName( key ) != "BACKSPACE" && input.GetKeyName( key ) != "ENTER" then + if CanWrite then + if key >= 0 && key <= 36 or key == 59 then + TextHack = TextHack..input.GetKeyName( key ) + elseif key > 36 && key <= 46 then + local TableKey = { + [37] = 0, + [38] = 1, + [39] = 2, + [40] = 3, + [41] = 4, + [42] = 5, + [43] = 6, + [44] = 7, + [45] = 8, + [46] = 9, +} + TextHack = TextHack..TableKey[key] + end + end + elseif input.GetKeyName( key ) == "BACKSPACE" then + TextHack = string.sub(TextHack, 0, #TextHack-1) + elseif input.GetKeyName( key ) == "ENTER" then + if RandomWord == TextHack then + TextColor = Realistic_Police.Colors["green"] + CanWrite = false + surface.PlaySound( "UI/buttonclick.wav" ) + timer.Simple(0.5, function() + CanWrite = true + TextColor = Realistic_Police.Colors["gray220"] + TableString[#TableString] = TextHack + TableString[#TableString + 1] = "" + TextHack = "" + RPTCMDFrameP:SetFraction( Progress * ( 1 / Realistic_Police.WordCount) ) + Progress = Progress + 1 + TextHack = "" + RandomWord = Realistic_Police.WordHack[math.random(1, #Realistic_Police.WordHack)] + if Progress * 0.10 > Realistic_Police.WordCount*0.10 then + TableString = {} + local Pourcentage = "0" + FinishHack = true + timer.Create("rpt_access", 0.05, 101, function() + Pourcentage = Pourcentage + 1 + if Pourcentage != 101 then + TableString[#TableString + 1] = "$root@"..ipAdress.." - Hack Progress ...... "..Pourcentage.."%" + else + TableString[#TableString + 1] = "Access Granted to the Computer" + timer.Create("rpt_clicker", 0.5, 5, function() + if TextColor == Realistic_Police.Colors["gray220"] then + TextColor = Realistic_Police.Colors["green"] + else + TextColor = Realistic_Police.Colors["gray220"] + end + end ) + end + end ) + timer.Simple(8, function() + if IsValid(RPTCMDFrame) then + RPTCMDFrame:SlideUp(0.2) + net.Start("RealisticPolice:Open") + net.WriteEntity(Ent) + net.SendToServer() + end + end ) + end + end ) + else + if Progress > 1 then + Progress = Progress - 1 + RPTCMDFrameP:SetFraction( (Progress * ( 1 / Realistic_Police.WordCount)) - (1 * ( 1 / Realistic_Police.WordCount)) ) + end +surface.PlaySound( "buttons/combine_button1.wav" ) + TextColor = Realistic_Police.Colors["red"] + CanWrite = false + timer.Simple(0.5, function() + CanWrite = true + TextColor = Realistic_Police.Colors["gray220"] + TextHack = "" + end ) + end + end + end + end + + local RPTClose = vgui.Create("DButton", RPTCMDFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTCMDFrame:GetWide()*0.938, RPTCMDFrame:GetTall()*0.0068) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTCMDFrame:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_criminal.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_criminal.lua new file mode 100644 index 0000000..6658544 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_criminal.lua @@ -0,0 +1,443 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +local function CriminalRecord(ply) + net.Start("RealisticPolice:CriminalRecord") + net.WriteUInt(0, 10) + net.WriteString("SendRecord") + net.WriteEntity(ply) + net.SendToServer() +end + +local function ScrollCriminalRecord(p, RPTCriminalRecord) + local RpRespX, RpRespY = ScrW(), ScrH() + + if isstring(p:SteamID64()) then + DScroll2:Clear() + timer.Simple(0.5, function() + if istable(RPTCriminalRecord) then + for k,v in pairs(RPTCriminalRecord) do + local DPanel = vgui.Create("DPanel", DScroll2) + DPanel:SetSize(0,RpRespY*0.05 + ( (RpRespX * 0.01) * ( (string.len(v.Motif.." ("..v.Date..")")/44) - 1) )) + DPanel:Dock(TOP) + DPanel:DockMargin(0,5,0,0) + DPanel.Paint = function(self,w,h) + if self:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["black240"] ) + surface.DrawRect( 5, 0, w-10, h ) + else + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + end + end + + local descriptionLabel = vgui.Create("RichText", DPanel) + descriptionLabel:SetSize(RpRespY*0.45, RpRespY*0.05 + ( (RpRespX * 0.011) * ( (string.len(v.Motif.." ("..v.Date..")")/44) - 1) ) ) + descriptionLabel:SetPos(RpRespY*0.073, RpRespY*0.014) + descriptionLabel:SetVerticalScrollbarEnabled(false) + descriptionLabel:InsertColorChange(240, 240, 240, 255) + descriptionLabel:AppendText(v.Motif.." ("..v.Date..")") + function descriptionLabel:PerformLayout(w, h) + descriptionLabel:SetContentAlignment(5) + self:SetFontInternal("rpt_font_7") + end + + local DButton2 = vgui.Create("DButton", DPanel) + DButton2:SetSize(RpRespX*0.018,RpRespX*0.018) + DButton2:SetPos(RpRespX*0.3, RpRespY*0.01) + DButton2:SetText("") + DButton2.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["white240"] ) + surface.SetMaterial( Realistic_Police.Colors["Material1"] ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + DButton2.DoClick = function() + if Realistic_Police.JobDeleteRecord[Realistic_Police.GetPlayerJob(LocalPlayer())] then + if p != LocalPlayer() then + net.Start("RealisticPolice:CriminalRecord") + net.WriteUInt(k, 10) + net.WriteString("RemoveRecord") + net.WriteEntity(p) + net.SendToServer() + DPanel:Remove() + Realistic_Police.Clic() + else + RealisticPoliceNotify(Realistic_Police.GetSentence("cantDeleteSelfSanctions")) + end + else + RealisticPoliceNotify(Realistic_Police.GetSentence("cantDelete")) + end + end + end + end + end ) + end +end + +net.Receive("RealisticPolice:CriminalRecord", function() + local RPTNumber = net.ReadInt(32) + local RPTInformationDecompress = util.Decompress(net.ReadData(RPTNumber)) or {} + local RPTCriminalRecord = util.JSONToTable(RPTInformationDecompress) + local RPTEntity = net.ReadEntity() + + ScrollCriminalRecord(RPTEntity, RPTCriminalRecord) +end ) + +local antiSpamWanted = 0 +function Realistic_Police.CriminalRecord(Panel, RPTEntComputer) + local RpRespX, RpRespY = ScrW(), ScrH() + local TablePlayers = player.GetAll() + local VisiblePlayers = {} + local lp = LocalPlayer() + + local isSBUComputer = IsValid(RPTEntComputer) and RPTEntComputer.IsSBU and RPTEntComputer:IsSBU() + + for _, ply in ipairs(TablePlayers) do + local targetChar = ply.GetCharacter and ply:GetCharacter() + if not targetChar then continue end + + local faction = targetChar:GetFaction() + if isSBUComputer then + -- SBU computer only shows Ukraine players + if faction != (FACTION_UKRAINE or -1) then + continue + end + else + -- FSB computer (default) only shows non-Ukraine players + if faction == (FACTION_UKRAINE or -1) then + continue + end + end + table.insert(VisiblePlayers, ply) + end + + local RPTEntity = VisiblePlayers[1] or lp + local CrimeRecordName = IsValid(RPTEntity) and RPTEntity:Name() or "Нет данных" +local RPTValue = 0 + local RPTTarget = 300 + local speed = 10 + local RPTActivate = false + local RPTCriminalP = nil + + local RPTCFrame = vgui.Create("DFrame", RPTFrame) + RPTCFrame:SetSize(RpRespX*0.605, RpRespY*0.486) + RPTCFrame:SetPos(RpRespX*0.115, RpRespY*0.15 ) + RPTCFrame:ShowCloseButton(false) + RPTCFrame:SetDraggable(true) + RPTCFrame:SetTitle("") + RPTCFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.074 ) +surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + draw.DrawText(Realistic_Police.GetSentence("criminalRecord"), "rpt_font_7", RpRespX*0.003, RpRespY*0.0038, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + function RPTCFrame:OnMousePressed() + RPTCFrame:RequestFocus() + local screenX, screenY = self:LocalToScreen( 0, 0 ) + if ( self.m_bSizable && gui.MouseX() > ( screenX + self:GetWide() - 20 ) && 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() && gui.MouseY() < ( screenY + 24 ) ) then + self.Dragging = { gui.MouseX() - self.x, gui.MouseY() - self.y } + self:MouseCapture( true ) + return + end + end + + local RPTScroll = vgui.Create("DScrollPanel", RPTCFrame) + RPTScroll:SetSize(RpRespX*0.12, RpRespY*0.44) + RPTScroll:SetPos(RpRespX*0.003, RpRespY*0.04) + RPTScroll.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(0,0) + + local DPanel = vgui.Create("DPanel", RPTCFrame) + DPanel:SetSize(RpRespX*0.355, RpRespY*0.44) + DPanel:SetPos(RpRespX*0.125, RpRespY*0.04) + DPanel.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + draw.DrawText(Realistic_Police.GetSentence("recordOf").." : "..CrimeRecordName, "rpt_font_7", RpRespX*0.01, RpRespY*0.015, Realistic_Police.Colors["gray"], TEXT_ALIGN_LEFT) + end + + DScroll2 = vgui.Create("DScrollPanel", DPanel) + DScroll2:SetSize(RpRespX*0.335, RpRespY*0.37) + DScroll2:SetPos(RpRespX*0.01, RpRespY*0.055) + DScroll2.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black49"] ) + surface.DrawRect( 0, 0, w, h ) + end + local sbar = DScroll2:GetVBar() + sbar:SetSize(10,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + + local RPTPanel = vgui.Create("DPanel", RPTCFrame) + RPTPanel:SetSize(RpRespX*0.12, RpRespY*0.44) + RPTPanel:SetPos(RpRespX*0.482, RpRespY*0.04) + RPTPanel.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + + local PanelDModel = vgui.Create("DModelPanel", RPTPanel) + PanelDModel:SetSize(RpRespX*0.1, RpRespY*0.3) + PanelDModel:SetPos(RpRespX*0.01, RpRespY*0.04) + PanelDModel:SetModel(RPTEntity:GetModel()) + PanelDModel.LayoutEntity = function() end + PanelDModel:SetCamPos( Vector( 310, 10, 45 ) ) + PanelDModel:SetLookAt( Vector( 0, 0, 36 ) ) + PanelDModel:SetFOV(8) + + local BWanted = vgui.Create("DButton", RPTPanel) + BWanted:SetSize(RpRespX*0.115, RpRespY*0.03) + BWanted:SetPos(RpRespX*0.003, RPTPanel:GetTall()*0.82) + BWanted:SetText(Realistic_Police.GetPlayerJob(RPTEntity)) + BWanted:SetFont("rpt_font_7") + BWanted:SetTextColor(Realistic_Police.Colors["gray"]) + BWanted.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + BWanted.DoClick = function() end + + if not Realistic_Police.DisableWantedButton then + local BWarrant = vgui.Create("DButton", RPTPanel) + BWarrant:SetSize(RpRespX*0.115, RpRespY*0.03) + BWarrant:SetPos(RpRespX*0.003, RPTPanel:GetTall()*0.90) + BWarrant:SetText("Назначить Розыск") + BWarrant:SetFont("rpt_font_7") + BWarrant:SetTextColor(Realistic_Police.Colors["gray"]) + BWarrant.DoClick = function() + local curTime = CurTime() + + antiSpamWanted = antiSpamWanted or 0 + if antiSpamWanted > curTime then return end + antiSpamWanted = curTime + 2 -- Reduced spam delay + + local isSBU = IsValid(RPTEntComputer) and RPTEntComputer.IsSBU and RPTEntComputer:IsSBU() + local wantedVar = isSBU and "ix_wanted_sbu" or "ix_wanted_fsb" + local isWanted = RPTEntity:GetNWBool(wantedVar, false) + + net.Start("RealisticPolice:SetWantedStatus") + net.WriteEntity(RPTEntity) + net.WriteBool(not isWanted) + net.SendToServer() + end + BWarrant.Paint = function(self,w,h) + local isSBU = IsValid(RPTEntComputer) and RPTEntComputer.IsSBU and RPTEntComputer:IsSBU() + local wantedVar = isSBU and "ix_wanted_sbu" or "ix_wanted_fsb" + local isWanted = RPTEntity:GetNWBool(wantedVar, false) + + if isWanted then + BWarrant:SetText("Снять Розыск") + else + BWarrant:SetText("Назначить Розыск") + end + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + end + + CriminalRecord(RPTEntity) + + for k,v in ipairs(VisiblePlayers) do + + local DButton1 = vgui.Create("DButton", RPTScroll) + DButton1:SetSize(0,RpRespY*0.05) + DButton1:SetText(v:Name()) + DButton1:SetFont("rpt_font_7") + DButton1:Dock(TOP) + DButton1:DockMargin(0,5,0,0) + DButton1.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25150"] ) + surface.DrawRect( 5, 0, w, h ) + + local isSBU = IsValid(RPTEntComputer) and RPTEntComputer.IsSBU and RPTEntComputer:IsSBU() + local wantedVar = isSBU and "ix_wanted_sbu" or "ix_wanted_fsb" + local isWanted = v:GetNWBool(wantedVar, false) + + if isWanted then + DButton1:SetTextColor(Realistic_Police.Colors["red"]) + else + DButton1:SetTextColor(Realistic_Police.Colors["gray"]) + end + end + DButton1.DoClick = function() + DScroll2:Clear() + CriminalRecord(v) + Realistic_Police.Clic() + RPTEntity = v + PanelDModel:SetModel(v:GetModel()) + CrimeRecordName = v:Name() + BWanted:SetText(Realistic_Police.GetPlayerJob(v)) + end + end + + local BAdd = vgui.Create("DButton", DPanel) + BAdd:SetSize(RpRespX*0.06, RpRespY*0.03) + BAdd:SetPos(RpRespX*0.2877, RpRespY*0.0145) + BAdd:SetText(Realistic_Police.GetSentence("addUpperCase")) + BAdd:SetFont("rpt_font_7") + BAdd:SetTextColor(Realistic_Police.Colors["gray"]) + BAdd.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + end + BAdd.DoClick = function() + local PanelCreate = vgui.Create("DFrame", RPTBaseFrame) + PanelCreate:SetSize(RpRespX*0.2, RpRespY*0.22) + PanelCreate:Center() + PanelCreate:SetTitle("") + PanelCreate:ShowCloseButton(false) + PanelCreate.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + end +local PanelCD = vgui.Create("DComboBox", PanelCreate) + PanelCD:SetSize(RpRespX*0.18, RpRespY*0.05) + PanelCD:SetPos(RpRespX*0.01, RpRespY*0.03) + PanelCD:SetText(Realistic_Police.GetSentence("username")) + PanelCD:SetFont("rpt_font_7") + for k,v in pairs(player.GetAll()) do + PanelCD:AddChoice(v:Name(), v) + end + function PanelCD:OnSelect( index, text, data ) + RPTActivate = true + RPTCriminalP = data + Realistic_Police.Clic() + end +local PanelCT = vgui.Create("DTextEntry", PanelCreate) + PanelCT:SetSize(RpRespX*0.18, RpRespY*0.05) + PanelCT:SetPos(RpRespX*0.01, RpRespY*0.085) + PanelCT:SetText(" "..Realistic_Police.GetSentence("infractionReason")) + PanelCT:SetFont("rpt_font_7") + PanelCT:SetDrawLanguageID(false) + PanelCT.OnGetFocus = function(self) + if PanelCT:GetText() == " "..Realistic_Police.GetSentence("infractionReason") then + PanelCT:SetText("") + end + end + PanelCT.OnLoseFocus = function(self) + if PanelCT:GetText() == "" then + PanelCT:SetText(" "..Realistic_Police.GetSentence("infractionReason")) + end + end + PanelCT.AllowInput = function( self, stringValue ) + if string.len(PanelCT:GetValue()) > 1000 then + return true + end + end + local ButtonAdd = vgui.Create("DButton", PanelCreate) + ButtonAdd:SetSize(RpRespX*0.088, RpRespY*0.05) + ButtonAdd:SetPos(RpRespX*0.01, RpRespY*0.145) + ButtonAdd:SetText(Realistic_Police.GetSentence("create")) + ButtonAdd:SetFont("rpt_font_7") + ButtonAdd:SetTextColor(Realistic_Police.Colors["gray"]) + ButtonAdd.Paint = function(self,w,h) + if ButtonAdd:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawRect( 0, 0, w, h ) + else + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + end + end + ButtonAdd.DoClick = function() + if RPTActivate && IsValid(RPTCriminalP) then + if PanelCT:GetText() != " "..Realistic_Police.GetSentence("infractionReason") then + if Realistic_Police.JobEditRecord[Realistic_Police.GetPlayerJob(LocalPlayer())] then + net.Start("RealisticPolice:CriminalRecord") + net.WriteUInt(1, 10) + net.WriteString("AddRecord") + net.WriteEntity(RPTCriminalP) + net.WriteString(PanelCT:GetValue()) + net.SendToServer() + PanelCreate:Remove() + DScroll2:Clear() + CriminalRecord(RPTCriminalP) + Realistic_Police.Clic() + else + RealisticPoliceNotify(Realistic_Police.GetSentence("cantDoThat")) + end + else + surface.PlaySound( "buttons/combine_button1.wav" ) + end + else + surface.PlaySound( "buttons/combine_button1.wav" ) + end + end + + local ButtonDel = vgui.Create("DButton", PanelCreate) + ButtonDel:SetSize(RpRespX*0.0885, RpRespY*0.05) + ButtonDel:SetPos(RpRespX*0.101, RpRespY*0.145) + ButtonDel:SetText(Realistic_Police.GetSentence("cancel")) + ButtonDel:SetFont("rpt_font_7") + ButtonDel:SetTextColor(Realistic_Police.Colors["white"]) + ButtonDel.Paint = function(self,w,h) + if ButtonAdd:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawRect( 0, 0, w, h ) + else + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + end + end + ButtonDel.DoClick = function() + PanelCreate:Remove() + Realistic_Police.Clic() + end + Realistic_Police.Clic() + end + + local RPTClose = vgui.Create("DButton", RPTCFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTCFrame:GetWide()*0.95, RPTCFrame:GetTall()*0.0068) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTCFrame:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_fining.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_fining.lua new file mode 100644 index 0000000..515de56 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_fining.lua @@ -0,0 +1,367 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +local function GetAmountByName(String) + local Amount = 0 + for k,v in pairs(Realistic_Police.FiningPolice) do + if v.Name == String then + Amount = v.Price + end + end + return Amount +end + +local function RPTGetCategory(Bool) + local Table = {} + for k,v in pairs(Realistic_Police.FiningPolice) do + if v.Vehicle == Bool then + if not table.HasValue(Table, v.Category) then + table.insert(Table, v.Category) + end + end + end + return Table +end +local function RPTGetFineByCategory(String, Bool) + local Table = {} + for k,v in pairs(Realistic_Police.FiningPolice) do + if String == v.Category && Bool == v.Vehicle then + Table[#Table + 1] = v + end + end + return Table +end + +function Realistic_Police.OpenFiningMenu(Boolen) + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 0 + local RPTTarget = 300 + local speed = 10 + local RPTActivate = false + + if IsValid(RPTFFrame) then RPTFFrame:Remove() end + + RPTFFrame = vgui.Create("DFrame") + RPTFFrame:SetSize(RpRespX*0.28, RpRespY*0.602) + RPTFFrame:Center() + RPTFFrame:ShowCloseButton(false) + RPTFFrame:SetDraggable(false) + RPTFFrame:MakePopup() + RPTFFrame:SetTitle("") + RPTFFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.074 ) + + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawRect( RpRespX*0.005, RpRespY*0.04, w*0.97, h*0.918 ) + + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + draw.DrawText(Realistic_Police.GetSentence("fineBook"), "rpt_font_9", RpRespX*0.005, RpRespY*0.007, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end +local RPTScroll = vgui.Create("DScrollPanel", RPTFFrame) + RPTScroll:SetSize(RpRespX*0.27, RpRespY*0.496) + RPTScroll:SetPos(RpRespX*0.006, RpRespY*0.04) + RPTScroll.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawRect( 0, 0, w, h ) + end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(0,0) + + RPTCheckbox = {} + local TableFiningSytem = {} + local DButton1 = {} + local RPTMoney = 0 + for k,v in pairs(RPTGetCategory(Boolen)) do + DButton1[k] = vgui.Create("DButton", RPTScroll) + DButton1[k]:SetSize(0,RpRespY*0.05) + DButton1[k]:SetText("") + DButton1[k]:SetFont("rpt_font_7") + DButton1[k]:SetTextColor(Realistic_Police.Colors["white"]) + DButton1[k]:Dock(TOP) + DButton1[k].Extend = false + DButton1[k].FineName = v + DButton1[k]:DockMargin(0,5,0,0) + DButton1[k].Paint = function(self,w,h) + if self:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray100"]) + surface.DrawOutlinedRect( 5, 0, w-10, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray100"]) + surface.DrawOutlinedRect( 5, 0, w-10, RpRespY*0.05 ) + else + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray100"]) + surface.DrawOutlinedRect( 5, 0, w-10, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray100"]) + surface.DrawOutlinedRect( 5, 0, w-10, RpRespY*0.05 ) + end + draw.DrawText("Category : "..v, "rpt_font_7", DButton1[k]:GetWide()/2.05, RpRespY*0.013, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER) + end + local ParentDScrollPanel = {} + DButton1[k].DoClick = function() + if not DButton1[k].Extend then + DButton1[k]:SetSize(0,#RPTGetFineByCategory(DButton1[k].FineName, Boolen) * RpRespY*0.054 + RpRespY*0.06 ) + DButton1[k]:SlideDown(0.5) + DButton1[k].Extend = true + + ParentDScrollPanel[k] = vgui.Create("DScrollPanel", DButton1[k]) + ParentDScrollPanel[k]:SetPos(RpRespX*0.0034, RpRespY*0.051) + ParentDScrollPanel[k]:SetSize(RpRespX*0.262, #RPTGetFineByCategory(DButton1[k].FineName, Boolen) * RpRespY*0.054 + RpRespY*0.06 ) + ParentDScrollPanel[k]:SlideDown(0.5) + ParentDScrollPanel[k].Paint = function() end + ParentDScrollPanel[k]:GetVBar():SetSize(0,0) + + local DButtonScroll = {} + for id, v in pairs( RPTGetFineByCategory(DButton1[k].FineName, Boolen) ) do + DButtonScroll[id] = vgui.Create("DButton", ParentDScrollPanel[k]) + DButtonScroll[id]:SetSize(0,RpRespY*0.05) + DButtonScroll[id]:SetText("") + DButtonScroll[id]:SetFont("rpt_font_7") + DButtonScroll[id]:SetTextColor(Realistic_Police.Colors["white"]) + DButtonScroll[id]:Dock(TOP) + DButtonScroll[id]:DockMargin(3,4,0,0) + DButtonScroll[id].Paint = function(self,w,h) + if self:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, RpRespY*0.05 ) + else + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor(Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, RpRespY*0.05 ) + end + draw.DrawText(" "..(v.Name or ""), "rpt_font_7", DButtonScroll[id]:GetWide()/2.05, RpRespY*0.013, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER) + end + DButtonScroll[id].DoClick = function() + if not IsValid(RPTCheckbox[id]) then return end +if not table.HasValue(TableFiningSytem, v.Name) then + if #TableFiningSytem < Realistic_Police.MaxPenalty then + table.insert(TableFiningSytem, v.Name) + RPTCheckbox[id]:SetChecked(true) + else + table.RemoveByValue(TableFiningSytem, v.Name) + RPTCheckbox[id]:SetChecked(false) + RealisticPoliceNotify(Realistic_Police.GetSentence("richSanctions")) + end + else + table.RemoveByValue(TableFiningSytem, v.Name) + RPTCheckbox[id]:SetChecked(false) + end + RPTMoney = 0 + for k,v in pairs(Realistic_Police.FiningPolice) do + if table.HasValue(TableFiningSytem, v.Name) then + RPTMoney = RPTMoney + v.Price + end + end + end + + RPTCheckbox[id] = vgui.Create( "DCheckBox", DButtonScroll[id] ) + RPTCheckbox[id]:SetPos( RpRespX*0.01, RpRespY*0.02 ) + RPTCheckbox[id]:SetChecked( false ) + RPTCheckbox[id].OnChange = function(val) + if IsValid(RPTCheckbox[id]) then + if not table.HasValue(TableFiningSytem, v.Name) then + if #TableFiningSytem < Realistic_Police.MaxPenalty then + table.insert(TableFiningSytem, v.Name) + RPTCheckbox[id]:SetChecked(true) + else + table.RemoveByValue(TableFiningSytem, v.Name) + RPTCheckbox[id]:SetChecked(false) + RealisticPoliceNotify(Realistic_Police.GetSentence("richSanctions")) + end + else + table.RemoveByValue(TableFiningSytem, v.Name) + RPTCheckbox[id]:SetChecked(false) + end + end + + RPTMoney = 0 + for k,v in pairs(Realistic_Police.FiningPolice) do + if table.HasValue(TableFiningSytem, v.Name) then + RPTMoney = RPTMoney + v.Price + end + end + end + for _,name in pairs(TableFiningSytem) do + if name == v.Name then + if IsValid(RPTCheckbox[id]) then + RPTCheckbox[id]:SetChecked(true) + end + end + end + end + else + DButton1[k].Extend = false + DButton1[k]:SizeTo(DButton1[k]:GetWide(), RpRespY*0.05, 0.5, 0, -1, function() + ParentDScrollPanel[k]:Remove() + end ) + end + end + end + +local DButtonAccept = vgui.Create("DButton", RPTFFrame) + DButtonAccept:SetSize(RpRespX*0.271, RpRespY*0.05) + DButtonAccept:SetPos(RpRespX*0.005, RpRespY*0.5375) + DButtonAccept:SetText(Realistic_Police.GetSentence("confirm").." ( "..RPTMoney.." $ )") + DButtonAccept:SetFont("rpt_font_7") + DButtonAccept:SetTextColor(Realistic_Police.Colors["white"]) + DButtonAccept.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + DButtonAccept:SetText(Realistic_Police.GetSentence("confirm").." ( "..RPTMoney.." $ )") + end + DButtonAccept.DoClick = function() + if istable(TableFiningSytem) && #TableFiningSytem != 0 then + local StringToSend = "" + for k,v in pairs(TableFiningSytem) do + StringToSend = StringToSend.."§"..v + end + net.Start("RealisticPolice:FiningSystem") + net.WriteString("SendFine") + net.WriteString(StringToSend) + net.SendToServer() + RPTFFrame:SlideUp(0.7) + else + RealisticPoliceNotify(Realistic_Police.GetSentence("mustSelectPenality")) + end + end + + local RPTClose = vgui.Create("DButton", RPTFFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTFFrame:GetWide()*0.878, RPTFFrame:GetTall()*0.0068) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTFFrame:Remove() + Realistic_Police.Clic() + net.Start("RealisticPolice:FiningSystem") + net.WriteString("StopFine") + net.SendToServer() + end +end + +net.Receive("RealisticPolice:FiningSystem", function() + local Table = net.ReadTable() or {} + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 0 + local RPTTarget = 300 + local speed = 10 + local RPTActivate = false + local Amount = 0 + + for k,v in pairs(Realistic_Police.FiningPolice) do + if table.HasValue(Table, v.Name) then + Amount = Amount + v.Price + end + end + + if IsValid(RPTFFrame) then RPTFFrame:Remove() end + RPTFFrame = vgui.Create("DFrame") + RPTFFrame:SetSize(RpRespX*0.28, RpRespY*0.602) + RPTFFrame:Center() + RPTFFrame:ShowCloseButton(false) + RPTFFrame:SetDraggable(false) + RPTFFrame:SetTitle("") + RPTFFrame:MakePopup() + RPTFFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.074 ) + + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawRect( RpRespX*0.005, RpRespY*0.04, w*0.97, h*0.918 ) +surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + draw.DrawText(Realistic_Police.GetSentence("fineBook"), "rpt_font_9", RpRespX*0.005, RpRespY*0.007, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + local RPTScroll = vgui.Create("DScrollPanel", RPTFFrame) + RPTScroll:SetSize(RpRespX*0.27, RpRespY*0.496) + RPTScroll:SetPos(RpRespX*0.005, RpRespY*0.04) + RPTScroll.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawRect( 0, 0, w, h ) + end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(0,0) + + for k,v in pairs(Table) do + DButton1 = vgui.Create("DButton", RPTScroll) + DButton1:SetSize(0,RpRespY*0.05) + DButton1:SetText(v.." ("..DarkRP.formatMoney(GetAmountByName(v))..")") + DButton1:SetFont("rpt_font_7") + DButton1:SetTextColor(Realistic_Police.Colors["white"]) + DButton1:Dock(TOP) + DButton1:DockMargin(0,5,0,0) + DButton1.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 5, 0, w-10, h ) + end + end + + local DButtonBuy = vgui.Create("DButton", RPTFFrame) + DButtonBuy:SetSize(RpRespX*0.27, RpRespY*0.05) + DButtonBuy:SetPos(RpRespX*0.005, RpRespY*0.5375) + DButtonBuy:SetText(Realistic_Police.GetSentence("payFine").." : "..DarkRP.formatMoney(Amount)) + DButtonBuy:SetFont("rpt_font_7") + DButtonBuy:SetTextColor(Realistic_Police.Colors["white"]) + DButtonBuy.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["green"] ) + surface.DrawRect( 5, 0, w-10, h ) + + surface.SetDrawColor( Realistic_Police.Colors["green"] ) + surface.DrawOutlinedRect( 5, 0, w-10, h ) + end + DButtonBuy.DoClick = function() + if LocalPlayer():getDarkRPVar("money") >= Amount then + net.Start("RealisticPolice:FiningSystem") + net.WriteString("BuyFine") + net.SendToServer() + RPTFFrame:SlideUp(0.7) + else + RealisticPoliceNotify(Realistic_Police.GetSentence("cantAffordFine")) + end + end + + local RPTClose = vgui.Create("DButton", RPTFFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTFFrame:GetWide()*0.878, RPTFFrame:GetTall()*0.0068) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTFFrame:Remove() + Realistic_Police.Clic() + net.Start("RealisticPolice:FiningSystem") + net.WriteString("RefuseFine") + net.SendToServer() + end +end ) + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_firefox.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_firefox.lua new file mode 100644 index 0000000..f423e98 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_firefox.lua @@ -0,0 +1,48 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +function Realistic_Police.FireFox(RPTFrame) + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 100 + local RPTTarget = 300 + local speed = 4 +-- 76561198219964944 + + local RPTMenu = vgui.Create("DFrame", RPTFrame) + RPTMenu:SetSize(RpRespX*0.794, RpRespY*0.8) + RPTMenu:SetPos(0, 0) + RPTMenu:ShowCloseButton(false) + RPTMenu:SetDraggable(false) + RPTMenu:SetTitle("") + RPTMenu.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime(), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.05 ) + end + + local html = vgui.Create("DHTML", RPTMenu) + html:Dock(FILL) + html:OpenURL("https://www.google.com/") +-- 76561198219964937 +local RPTClose = vgui.Create("DButton", RPTMenu) + RPTClose:SetSize(RpRespX*0.0295, RpRespY*0.025) + RPTClose:SetPos(RPTMenu:GetWide()*0.96, RPTMenu:GetTall()*0.005) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTMenu:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_fonts.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_fonts.lua new file mode 100644 index 0000000..36065d3 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_fonts.lua @@ -0,0 +1,206 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +surface.CreateFont("rpt_font_1", { + font = "License Plate", + size = 30*(ScrH()/1080), + weight = 1000, + antialias = true, + extended = true, +}) + +surface.CreateFont("rpt_font_2", { + font = "License Plate", + size = 24*(ScrH()/1080), + weight = 1000, + antialias = true, + extended = true, +}) + +surface.CreateFont("rpt_font_3", { + font = "License Plate", + size = 30*(ScrH()/1080), + weight = 1000, + antialias = true, + extended = true, +}) + +surface.CreateFont("rpt_font_4", { + font = "License Plate", + size = 24*(ScrH()/1080), + weight = 1000, + antialias = true, + extended = true, +}) +surface.CreateFont("rpt_font_5", { + font = "Arial", + size = 16.8*(ScrH()/1080), + weight = 550, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_6", { + font = "Circular Std Medium", + size = 22.5*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_7", { + font = "Circular Std Medium", + size = ScrH()*0.021, + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_8", { + font = "Circular Std Medium", + size = 20*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_9", { + font = "Circular Std Medium", + size = 24*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_10", { + font = "Circular Std Medium", + size = 36*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) +surface.CreateFont("rpt_font_11", { + font = "Circular Std Medium", + size = 21*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_12", { + font = "Circular Std Medium", + size = 75*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_13", { + font = "Circular Std Medium", + size = 45*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_14", { + font = "Circular Std Medium", + size = 15*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_15", { + font = "Circular Std Medium", + size = 24*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_16", { + font = "Circular Std Medium", + size = 15*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_17", { + font = "Circular Std Medium", + size = 21*(ScrH()/1080), + weight = 550, + antialias = true, + extended = true +}) +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe + +surface.CreateFont("rpt_font_18", { + font = "Circular Std Medium", + size = 24*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true, +}) + +surface.CreateFont("rpt_font_19", { + font = "Circular Std Medium", + size = 18*(ScrH()/1080), + weight = 0, + antialias = true, + extended = true, +}) + +surface.CreateFont("rpt_font_20", { + font = "LEMON MILK", + size = 20*(ScrH()/1080), + weight = 800, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_21", { + font = "Poiret One", + size = 21*(ScrH()/1080), + weight = 800, + antialias = true, + extended = true +}) + +surface.CreateFont("rpt_font_22", { + font = "Circular Std Medium", + size = 180*(ScrH()/1080), + weight = 0, + blursize = 0, + scanlines = 0, + extended = true, + shadow = true, +}) + +surface.CreateFont("rpt_font_23", { + font = "Circular Std Medium", + size = 80*(ScrH()/1080), + weight = 0, + blursize = 0, + scanlines = 0, + extended = true, + shadow = true, +}) + +surface.CreateFont("rpt_notify_24", { + font = "Arial", + size = 100*(ScrH()/1080), + weight = 1000, + antialias = true, + extended = true, +}) + + + + + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_handcuff.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_handcuff.lua new file mode 100644 index 0000000..7d6918f --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_handcuff.lua @@ -0,0 +1,376 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +net.Receive("RealisticPolice:HandCuff", function() + local String = net.ReadString() + if String == "Jailer" then + -- Name of the Player to Jail + local NamePlayer = net.ReadString() + Realistic_Police.Jailer(NamePlayer) + elseif String == "Bailer" then + -- Open the menu of the bailer + local RPTTable = net.ReadTable() + Realistic_Police.Bailer(RPTTable) + elseif String == "Inspect" then + local Ent = net.ReadEntity() + Realistic_Police.CheckPlayer(Ent) + end +end) + +function Realistic_Police.Jailer(NamePlayer) + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 0 + local RPTTarget = 300 + local speed = 20 +local RPTLFrame = vgui.Create("DFrame") + RPTLFrame:SetSize(RpRespX*0.272, RpRespY*0.28) + RPTLFrame:Center() + RPTLFrame:ShowCloseButton(false) + RPTLFrame:MakePopup() + RPTLFrame:SetDraggable(false) + RPTLFrame:SetTitle("") + RPTLFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime(), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["gray60200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( w*0.003, 0, w*0.995, h*0.12 ) + + draw.DrawText(Realistic_Police.GetSentence("arrestMenu"), "rpt_font_7", RpRespX*0.004, RpRespY*0.005, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + local DermaNumSlider = vgui.Create( "DNumSlider", RPTLFrame ) + DermaNumSlider:SetPos( RpRespX*-0.16, RpRespY * 0.18 ) + DermaNumSlider:SetSize( RpRespX*0.42, RpRespY*0.01 ) + DermaNumSlider:SetText( "" ) + DermaNumSlider:SetMax( Realistic_Police.MaxDay ) + DermaNumSlider:SetDecimals( 1 ) + DermaNumSlider:SetDefaultValue ( 0 ) + DermaNumSlider:SetMin(1) + DermaNumSlider:SetValue(1) + DermaNumSlider.TextArea:SetVisible(false) + function DermaNumSlider.Slider:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray60"] ) + surface.DrawRect( 0, 0, w, h ) + end + function DermaNumSlider.Slider.Knob:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["darkblue"] ) + draw.NoTexture() + Realistic_Police.Circle( 0, RpRespY*0.007, 8, 1000 ) + end + DermaNumSlider.OnValueChanged = function( panel, value ) + DButtonAccept:SetText(Realistic_Police.GetSentence("confirm").." "..math.Round(value, 0).." "..Realistic_Police.GetSentence("years")) + end + + local RPTDPanel = vgui.Create("DPanel", RPTLFrame) + RPTDPanel:SetSize(RpRespX*0.251, RpRespY*0.05) + RPTDPanel:SetPos(RpRespX*0.01, RpRespY*0.05) + RPTDPanel:SetText(Realistic_Police.GetSentence("username2")) + RPTDPanel.Paint = function(self,w,h) + surface.SetDrawColor(Realistic_Police.Colors["black180"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["black180"] ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["darkblue"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + draw.DrawText(Realistic_Police.GetSentence("name").." : "..NamePlayer, "rpt_font_7", RpRespX*0.005, RpRespY*0.014, Realistic_Police.Colors["white"], TEXT_ALIGN_LEFT) + end + + local PanelCT = vgui.Create("DTextEntry", RPTLFrame) + PanelCT:SetSize(RpRespX*0.251, RpRespY*0.05) + PanelCT:SetPos(RpRespX*0.01, RpRespY*0.11) + PanelCT:SetText(" "..Realistic_Police.GetSentence("infractionReason")) + PanelCT:SetFont("rpt_font_7") + PanelCT:SetDrawLanguageID(false) + PanelCT.OnGetFocus = function(self) + if PanelCT:GetText() == " "..Realistic_Police.GetSentence("infractionReason") then + PanelCT:SetText("") + end + end + PanelCT.Paint = function(self,w,h) + surface.SetDrawColor(Realistic_Police.Colors["black"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["black180"] ) + surface.DrawRect( 0, 0, w, h ) + + self:DrawTextEntryText(Realistic_Police.Colors["white"], Realistic_Police.Colors["white"], Realistic_Police.Colors["white"]) + surface.SetDrawColor( Realistic_Police.Colors["darkblue"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + PanelCT.OnLoseFocus = function(self) + if PanelCT:GetText() == "" then + PanelCT:SetText(" "..Realistic_Police.GetSentence("infractionReason")) + end + end + PanelCT.AllowInput = function( self, stringValue ) + if string.len(PanelCT:GetValue()) >= 1000 then + return true + end + end + + DButtonAccept = vgui.Create("DButton", RPTLFrame) + DButtonAccept:SetSize(RpRespX*0.253, RpRespY*0.05) + DButtonAccept:SetPos(RpRespX*0.01, RpRespY*0.21) + DButtonAccept:SetText(Realistic_Police.GetSentence("confirm")) + DButtonAccept:SetFont("rpt_font_9") + DButtonAccept:SetTextColor(Realistic_Police.Colors["white"]) + DButtonAccept.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["green"] ) + surface.DrawRect( 5, 0, w-10, h ) + end + DButtonAccept.DoClick = function() + if PanelCT:GetText() != " "..Realistic_Police.GetSentence("infractionReason") then + net.Start("RealisticPolice:HandCuff") + net.WriteString("ArrestPlayer") + net.WriteUInt(DermaNumSlider:GetValue(), 10) + net.WriteString(PanelCT:GetText()) + net.SendToServer() + RPTLFrame:Remove() + end + end + + local RPTClose = vgui.Create("DButton", RPTLFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTLFrame:GetWide()*0.885, RPTLFrame:GetTall()*0.008) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTLFrame:Remove() + Realistic_Police.Clic() + end +end + +function Realistic_Police.Bailer(RPTTable) + local RpRespX, RpRespY = ScrW(), ScrH() +local RPTValue = 0 + local RPTTarget = 300 + local speed = 20 + + if IsValid(RPTLFrame) then RPTLFrame:Remove() end + RPTLFrame = vgui.Create("DFrame") + RPTLFrame:SetSize(RpRespX*0.2746, RpRespY*0.355) + RPTLFrame:Center() + RPTLFrame:ShowCloseButton(false) + RPTLFrame:SetDraggable(false) + RPTLFrame:SetTitle("") + RPTLFrame:MakePopup() + RPTLFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["gray60200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( w*0.003, 0, w*0.995, h*0.10 ) + + draw.DrawText(Realistic_Police.GetSentence("arrestedPlayersList"), "rpt_font_7", RpRespX*0.003, RpRespY*0.005, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + local RPTScroll = vgui.Create("DScrollPanel", RPTLFrame) + RPTScroll:SetSize(RpRespX*0.2695, RpRespY*0.31) + RPTScroll:SetPos(RpRespX*0.0029, RpRespY*0.04) + RPTScroll.Paint = function() end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(5,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + + for k,v in pairs(RPTTable) do + RPTDPanel2 = vgui.Create("DPanel", RPTScroll) + RPTDPanel2:SetSize(0, RpRespY*0.1) + RPTDPanel2:Dock(TOP) + RPTDPanel2:DockMargin(0, 0, 0, 5) + RPTDPanel2.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + draw.SimpleText(Realistic_Police.GetSentence("name").." : "..v.vName, "rpt_font_9", RpRespX*0.06, RpRespY * 0.012, Realistic_Police.Colors["gray220"], TEXT_ALIGN_LEFT) + draw.SimpleText(Realistic_Police.GetSentence("reason").." : "..v.vMotif, "rpt_font_9", RpRespX*0.06, RpRespY * 0.039, Realistic_Police.Colors["gray220"], TEXT_ALIGN_LEFT) + draw.SimpleText(Realistic_Police.GetSentence("price").." : "..DarkRP.formatMoney(v.vPrice), "rpt_font_9", RpRespX*0.06, RpRespY * 0.066, Realistic_Police.Colors["gray220"], TEXT_ALIGN_LEFT) + end + + local RPTPanelB = vgui.Create("DModelPanel", RPTDPanel2) + RPTPanelB:SetSize(RpRespX*0.05, RpRespX*0.05) + RPTPanelB:SetPos(RpRespX*0.003,RpRespY*0.007) + RPTPanelB.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + end + + local RPTModel = vgui.Create("DModelPanel", RPTDPanel2) + RPTModel:SetSize(RpRespX*0.05, RpRespX*0.05) + RPTModel:SetPos(RpRespX*0.003,RpRespY*0.007) + RPTModel:SetModel(v.vModel) + function RPTModel:LayoutEntity( Entity ) return end + local headpos = RPTModel.Entity:GetBonePosition(RPTModel.Entity:LookupBone("ValveBiped.Bip01_Head1")) + RPTModel:SetLookAt(headpos) + RPTModel:SetCamPos(headpos-Vector(-15, 0, 0)) +local RPTButtonPay = vgui.Create("DButton", RPTDPanel2) + RPTButtonPay:SetSize(RpRespX*0.05, RpRespX*0.04) + RPTButtonPay:SetPos(RpRespX*0.215, RpRespY * 0.015) + RPTButtonPay:SetText(Realistic_Police.GetSentence("pay")) + RPTButtonPay:SetTextColor(Realistic_Police.Colors["white"]) + RPTButtonPay:SetFont("rpt_font_10") + RPTButtonPay.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTButtonPay.DoClick = function() + net.Start("RealisticPolice:HandCuff") + net.WriteString("Bailer") + net.WriteEntity(v.vEnt) + net.WriteUInt(k, 10) + net.SendToServer() + RPTLFrame:Remove() + end + end + + local RPTClose = vgui.Create("DButton", RPTLFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTLFrame:GetWide()*0.885, RPTLFrame:GetTall()*0.008) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTLFrame:Remove() + Realistic_Police.Clic() + end +end + +function Realistic_Police.CheckPlayer(Player, Table) + if not Realistic_Police.CanConfiscateWeapon then return end + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 0 + local RPTTarget = 300 + local speed = 20 + + if IsValid(RPTLFrame) then RPTLFrame:Remove() end + RPTLFrame = vgui.Create("DFrame") + RPTLFrame:SetSize(RpRespX*0.2746, RpRespY*0.355) + RPTLFrame:Center() + RPTLFrame:ShowCloseButton(false) + RPTLFrame:SetDraggable(false) + RPTLFrame:SetTitle("") + RPTLFrame:MakePopup() + RPTLFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["gray60200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( w*0.003, 0, w*0.995, h*0.1 ) + + draw.DrawText(Realistic_Police.GetSentence("weaponsList"), "rpt_font_7", RpRespX*0.003, RpRespY*0.005, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + local RPTScroll = vgui.Create("DScrollPanel", RPTLFrame) + RPTScroll:SetSize(RpRespX*0.2695, RpRespY*0.31) + RPTScroll:SetPos(RpRespX*0.0029, RpRespY*0.04) + RPTScroll.Paint = function() end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(5,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + + local List = vgui.Create( "DIconLayout", RPTScroll ) + List:Dock( FILL ) + List:SetSpaceY( 5 ) + List:SetSpaceX( 5 ) + + local ListItem = {} + + for k, v in pairs(Player:GetWeapons()) do + if not IsValid(v) then continue end + + local mdl = v:GetModel() + local class = v:GetClass() + + if not Realistic_Police.CantConfiscate[class] then + local name = v:GetPrintName() +ListItem[k] = List:Add("SpawnIcon") + ListItem[k]:SetSize( RpRespX*0.087, RpRespX*0.087 ) + ListItem[k]:SetModel(mdl) + ListItem[k]:SetTooltip( false ) + ListItem[k].PaintOver = function(self, w,h) + surface.SetDrawColor(Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + draw.DrawText(name, "rpt_font_8", w/2, h*0.3, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local DButton1 = vgui.Create("DButton", ListItem[k]) + DButton1:SetSize(RpRespX*0.087, RpRespY*0.025) + DButton1:SetPos(0,RpRespY*0.129) + DButton1.PaintOver = function(self, w, h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + + + draw.DrawText(Realistic_Police.GetSentence("confiscate"), "rpt_font_7", w/2, h/8, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + DButton1.DoClick = function() + ListItem[k]:Remove() + + net.Start("RealisticPolice:HandCuff") + net.WriteString("StripWeapon") + net.WriteEntity(Player) + net.WriteString(class, 10) + net.SendToServer() +surface.PlaySound("UI/buttonclick.wav") + end + end + end + + local RPTClose = vgui.Create("DButton", RPTLFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTLFrame:GetWide()*0.885, RPTLFrame:GetTall()*0.008) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTLFrame:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_license.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_license.lua new file mode 100644 index 0000000..922ffb8 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_license.lua @@ -0,0 +1,201 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +function Realistic_Police.GetVehicleLicense(String) + local TableVehicle = {} + for k,v in pairs(ents.GetAll()) do + if string.StartWith(string.Replace(string.upper(v:GetNWString("rpt_plate")), " ", ""), string.Replace(string.upper(String), " ", "")) or string.Replace(v:GetNWString("rpt_plate"), " ", "") == string.Replace(string.upper(String), " ", "") then + local t = list.Get("Vehicles")[v:GetVehicleClass()] + + TableVehicle[#TableVehicle + 1] = { + ModelVehc = v:GetModel(), + NameVehc = t.Name, + Plate = v:GetNWString("rpt_plate"), + VOwner = v:CPPIGetOwner():Name(), + } + end + end + return TableVehicle +end +function Realistic_Police.License() + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTValue = 0 + local RPTTarget = 300 + local speed = 10 + + local RPTLFrame = vgui.Create("DFrame", RPTFrame) + RPTLFrame:SetSize(RpRespX*0.18285, RpRespY*0.1397) + RPTLFrame:Center() + RPTLFrame:ShowCloseButton(false) + RPTLFrame:SetDraggable(true) + RPTLFrame:SetTitle("") + RPTLFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15200"].r, Realistic_Police.Colors["black15200"].g, Realistic_Police.Colors["black15200"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.2 ) +surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + draw.DrawText(Realistic_Police.GetSentence("licenseplate"), "rpt_font_7", RpRespX*0.003, RpRespY*0.002, Color(220, 220, 220, RPTValue), TEXT_ALIGN_LEFT) + end + function RPTLFrame:OnMousePressed() + RPTLFrame:RequestFocus() + local screenX, screenY = self:LocalToScreen( 0, 0 ) + if ( self.m_bSizable && gui.MouseX() > ( screenX + self:GetWide() - 20 ) && 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() && gui.MouseY() < ( screenY + 24 ) ) then + self.Dragging = { gui.MouseX() - self.x, gui.MouseY() - self.y } + self:MouseCapture( true ) + return + end + end + + local RPTEntry = vgui.Create("DTextEntry", RPTLFrame) + RPTEntry:SetSize(RpRespX*0.18, RpRespY*0.05) + RPTEntry:SetPos(RpRespX*0.002, RpRespY*0.033) + RPTEntry:SetText(Realistic_Police.GetSentence("licenseplate")) + RPTEntry:SetFont("rpt_font_7") + RPTEntry:SetDrawLanguageID(false) + RPTEntry.OnGetFocus = function(self) RPTEntry:SetText("") end + RPTEntry.OnLoseFocus = function(self) + if RPTEntry:GetText() == "" then + RPTEntry:SetText(Realistic_Police.GetSentence("licenseplate")) + end + end + RPTEntry.AllowInput = function( self, stringValue ) + if string.len(RPTEntry:GetValue()) > 10 then + return true + end + end + + local RPTAccept = vgui.Create("DButton", RPTLFrame) + RPTAccept:SetSize(RpRespX*0.1795, RpRespY*0.05) + RPTAccept:SetPos(RpRespX*0.00207, RpRespY*0.086) + RPTAccept:SetText(Realistic_Police.GetSentence("search")) + RPTAccept:SetFont("rpt_font_7") + RPTAccept:SetTextColor(Realistic_Police.Colors["white"]) + RPTAccept.Paint = function(self,w,h) + if RPTAccept:IsHovered() then + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawRect( 0, 0, w, h ) + else + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + end + end + RPTAccept.DoClick = function() + if not IsValid(RPTEntry) then return end + + Realistic_Police.Clic() +-- 76561198219964937 + + local plate = Realistic_Police.GetVehicleLicense(RPTEntry:GetValue()) + if istable(plate) && #plate > 0 then + if IsValid(RPTLFrame) then RPTLFrame:Remove() end + + local RPTLFrame = vgui.Create("DFrame", RPTFrame) + RPTLFrame:SetSize(RpRespX*0.3, RpRespY*0.188) + RPTLFrame:Center() + RPTLFrame:ShowCloseButton(false) + RPTLFrame:SetDraggable(true) + RPTLFrame:SetTitle("") + RPTLFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15200"].r, Realistic_Police.Colors["black15200"].g, Realistic_Police.Colors["black15200"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.15 ) + + surface.SetDrawColor( Realistic_Police.Colors["black15200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + draw.DrawText(Realistic_Police.GetSentence("licenseplate"), "rpt_font_7", RpRespX*0.005, RpRespY*0.002, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + local DScroll = vgui.Create("DScrollPanel", RPTLFrame) + DScroll:SetSize(RpRespX*0.295, RpRespY*0.15) + DScroll:SetPos(RpRespX*0.003, RpRespY*0.033) + DScroll.Paint = function() end + local sbar = DScroll:GetVBar() + sbar:SetSize(10,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + for k,v in pairs(Realistic_Police.GetVehicleLicense(RPTEntry:GetValue())) do + local DPanel = vgui.Create("DPanel", DScroll) + DPanel:SetSize(0, RpRespY*0.15) + DPanel:Dock(TOP) + DPanel:DockMargin(0, 0, 0, 5) + DPanel.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) +surface.SetDrawColor( Realistic_Police.Colors["gray60"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + draw.DrawText("☑ "..v.VOwner, "rpt_font_11", RpRespX*0.18, RpRespY*0.04, Realistic_Police.Colors["white"], TEXT_ALIGN_LEFT) + draw.DrawText("☑ "..v.Plate, "rpt_font_11", RpRespX*0.18, RpRespY*0.07, Realistic_Police.Colors["white"], TEXT_ALIGN_LEFT) + draw.DrawText("☑ "..v.NameVehc, "rpt_font_11", RpRespX*0.18, RpRespY*0.1, Realistic_Police.Colors["white"], TEXT_ALIGN_LEFT) + end + + local RPTCarModel = vgui.Create( "DModelPanel", DPanel ) + RPTCarModel:SetPos( ScrW()*0.01, ScrH()*0.0298 ) + RPTCarModel:SetSize( ScrW()*0.15, ScrH()*0.13 ) + RPTCarModel:SetFOV(70) + RPTCarModel:SetCamPos(Vector(200, 0, 20)) + RPTCarModel:SetModel( v.ModelVehc ) + function RPTCarModel:LayoutEntity( ent ) end + + local RPTClose = vgui.Create("DButton", RPTLFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.0245) + RPTClose:SetPos(RPTLFrame:GetWide()*0.897, RPTLFrame:GetTall()*0.013) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTLFrame:Remove() + Realistic_Police.Clic() + end + end + else + RPTEntry:SetText(Realistic_Police.GetSentence("noVehiclesFound")) + timer.Simple(1, function() + if IsValid(RPTEntry) then + RPTEntry:SetText(Realistic_Police.GetSentence("licenseplate")) + end + end ) + end + end + + local RPTClose = vgui.Create("DButton", RPTLFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.0245) + RPTClose:SetPos(RPTLFrame:GetWide()*0.83, RPTLFrame:GetTall()*0.0175) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTLFrame:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_listreport.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_listreport.lua new file mode 100644 index 0000000..9a2fb05 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_listreport.lua @@ -0,0 +1,186 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +net.Receive("RealisticPolice:Report", function() + local RPTNumber = net.ReadInt(32) + local RPTInformationDecompress = util.Decompress(net.ReadData(RPTNumber)) + RPTTableListReport = util.JSONToTable(RPTInformationDecompress) or {} +end ) +-- dcb587233a6e567b06ae4da5655e3e9649694e7946f4d648e8f3181d8bc95b5f +function Realistic_Police.ListReport(RPTFrame, RPTEntComputer) + local RpRespX, RpRespY = ScrW(), ScrH() +local RPTValue = 0 + local RPTTarget = 300 + local speed = 20 + + local RPTLFrame = vgui.Create("DFrame", RPTFrame) + RPTLFrame:SetSize(RpRespX*0.2746, RpRespY*0.41) + RPTLFrame:Center() + RPTLFrame:ShowCloseButton(false) + RPTLFrame:SetDraggable(true) + RPTLFrame:SetTitle("") + RPTLFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) +surface.SetDrawColor( Realistic_Police.Colors["gray60200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( w*0.003, 0, w*0.995, h*0.085 ) + + draw.DrawText(Realistic_Police.GetSentence("username2"), "rpt_font_7", RpRespX*0.003, RpRespY*0.005, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + function RPTLFrame:OnMousePressed() + RPTLFrame:RequestFocus() + local screenX, screenY = self:LocalToScreen( 0, 0 ) + if ( self.m_bSizable && gui.MouseX() > ( screenX + self:GetWide() - 20 ) && 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() && gui.MouseY() < ( screenY + 24 ) ) then + self.Dragging = { gui.MouseX() - self.x, gui.MouseY() - self.y } + self:MouseCapture( true ) + return + end + end + + local RPTScroll = vgui.Create("DScrollPanel", RPTLFrame) + RPTScroll:SetSize(RpRespX*0.2695, RpRespY*0.31) + RPTScroll:SetPos(RpRespX*0.0029, RpRespY*0.095) + RPTScroll.Paint = function() end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(5,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + + local RPTComboBox = vgui.Create("DComboBox", RPTLFrame) + RPTComboBox:SetSize(RpRespX*0.269, RpRespY*0.05) + RPTComboBox:SetText(Realistic_Police.GetSentence("username2")) + RPTComboBox:SetFont("rpt_font_9") + RPTComboBox:SetTextColor(Realistic_Police.Colors["black"]) + RPTComboBox:SetPos(RpRespX*0.003, RpRespY*0.04) + local isSBUComputer = IsValid(RPTEntComputer) and RPTEntComputer.IsSBU and RPTEntComputer:IsSBU() + for k,v in pairs(player.GetAll()) do + local targetChar = v.GetCharacter and v:GetCharacter() + if targetChar then + local faction = targetChar:GetFaction() + if isSBUComputer then + if faction != (FACTION_UKRAINE or -1) then continue end + else + if faction == (FACTION_UKRAINE or -1) then continue end + end + end + RPTComboBox:AddChoice( v:Name(), v ) + end + RPTComboBox.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["gray240"] ) + surface.DrawRect( 0, 0, w, h ) + end + function RPTComboBox:OnSelect( index, text, data ) + + if IsValid(RPTScroll) then + RPTScroll:Clear() + end + + local SendPlayer = nil + if IsValid(data) then + SendPlayer = data + end + + net.Start("RealisticPolice:Report") + net.WriteUInt(1, 10) + net.WriteString("SendInformation") + net.WriteEntity(SendPlayer) + net.SendToServer() + + timer.Create("rptactualize", 0.2, 0, function() + if IsValid(RPTScroll) then + RPTScroll:Clear() + end + if IsValid(RPTScroll) then + if istable(RPTTableListReport) then + for k,v in pairs(RPTTableListReport) do + local RPTDPanel2 = vgui.Create("DPanel", RPTScroll) + RPTDPanel2:SetSize(0, RpRespY*0.1) + RPTDPanel2:Dock(TOP) + RPTDPanel2:DockMargin(0, 0, 0, 5) + RPTDPanel2.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + draw.SimpleText(Realistic_Police.GetSentence("prosecutor").." : "..v.RPTPolice, "rpt_font_9", RpRespX*0.06, RpRespY * 0.012, Realistic_Police.Colors["gray220"], TEXT_ALIGN_LEFT) + draw.SimpleText(Realistic_Police.GetSentence("accused").." : "..v.RPTCriminal, "rpt_font_9", RpRespX*0.06, RpRespY * 0.039, Realistic_Police.Colors["gray220"], TEXT_ALIGN_LEFT) + draw.SimpleText(Realistic_Police.GetSentence("date").." : "..v.RPTDate, "rpt_font_9", RpRespX*0.06, RpRespY * 0.066, Realistic_Police.Colors["gray220"], TEXT_ALIGN_LEFT) + end + + local RPTPanelB = vgui.Create("DModelPanel", RPTDPanel2) + RPTPanelB:SetSize(RpRespX*0.05, RpRespX*0.05) + RPTPanelB:SetPos(RpRespX*0.003,RpRespY*0.007) + RPTPanelB.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + if v.Model == "" then + draw.SimpleText("?", "rpt_font_13", RPTPanelB:GetWide()/2, RPTPanelB:GetTall()/2, Realistic_Police.Colors["white240"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + if v.Model != "" then + local RPTModel = vgui.Create("DModelPanel", RPTDPanel2) + RPTModel:SetSize(RpRespX*0.05, RpRespX*0.05) + RPTModel:SetPos(RpRespX*0.003,RpRespY*0.007) + RPTModel:SetModel(v.Model) + function RPTModel:LayoutEntity( Entity ) return end + if RPTModel.Entity:LookupBone("ValveBiped.Bip01_Head1") != nil then + local headpos = RPTModel.Entity:GetBonePosition(RPTModel.Entity:LookupBone("ValveBiped.Bip01_Head1")) + RPTModel:SetLookAt(headpos) + RPTModel:SetCamPos(headpos-Vector(-15, 0, 0)) + end + end + + local RPTButtonRead = vgui.Create("DButton", RPTDPanel2) + RPTButtonRead:SetSize(RpRespX*0.05, RpRespX*0.04) + RPTButtonRead:SetPos(RpRespX*0.215, RpRespY * 0.015) + RPTButtonRead:SetText(Realistic_Police.GetSentence("see")) + RPTButtonRead:SetTextColor(Realistic_Police.Colors["gray"]) + RPTButtonRead:SetFont("rpt_font_10") + RPTButtonRead.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTButtonRead.DoClick = function() + Realistic_Police.ReportMenu(RPTFrame, RPTTableListReport[k], SendPlayer, k) + Realistic_Police.Clic() + end + end + end + end + end ) + end + + local RPTClose = vgui.Create("DButton", RPTLFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTLFrame:GetWide()*0.885, RPTLFrame:GetTall()*0.008) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTLFrame:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_main.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_main.lua new file mode 100644 index 0000000..13c136c --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_main.lua @@ -0,0 +1,728 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +-- Information of the License Plate Position +net.Receive("RealisticPolice:SendInformation", function() + RPTInformationVehicle = {} + local Number = net.ReadInt(32) + local RPTInformationDecompress = util.Decompress(net.ReadData(Number)) or "" + local RPTTable = util.JSONToTable(RPTInformationDecompress) + + RPTInformationVehicle = Realistic_Police.BaseVehicles + + if istable(util.JSONToTable(RPTInformationDecompress)) then + table.Merge(RPTInformationVehicle, util.JSONToTable(RPTInformationDecompress)) + end +end ) + +local FontTable = {} +function Realistic_Police.Fonts(SizeId, fontName) + if table.HasValue(FontTable, "rpt_generate"..math.Round( SizeId, 0 )) then + return "rpt_generate"..math.Round( SizeId, 0 ) + end + local succ, err = pcall(function() + local font = "License Plate" + if isstring(fontName) then + font = fontName + end + + surface.CreateFont("rpt_generate"..math.Round( SizeId, 0 ), { + font = font, + size = math.Round( SizeId, 0 ), + weight = 800, + antialias = true, + extended = true, + }) + table.insert(FontTable, "rpt_generate"..math.Round( SizeId, 0 )) + end) + return succ and "rpt_generate"..math.Round( SizeId, 0 ) or "rpt_notify_24" +end + +function Realistic_Police.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 ) + 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 + +local function CreateNumSlider(id, x, y, panel, min, max) + local TableSlider = { + [1] = Realistic_Police.GetSentence("moveX"), + [2] = Realistic_Police.GetSentence("moveY"), + [3] = Realistic_Police.GetSentence("moveZ"), + [4] = Realistic_Police.GetSentence("rotateX"), + [5] = Realistic_Police.GetSentence("rotateY"), + [6] = Realistic_Police.GetSentence("rotateZ"), + [7] = Realistic_Police.GetSentence("width"), + [8] = Realistic_Police.GetSentence("height"), + } + + local W,H = ScrW(), ScrH() + DermaNumSlider = vgui.Create( "DNumSlider", panel ) + DermaNumSlider:SetPos( W*x, H*y ) + DermaNumSlider:SetSize( W*0.25, H*0.01 ) + DermaNumSlider:SetText( "" ) + DermaNumSlider:SetMin( min ) + DermaNumSlider:SetMax( max ) + DermaNumSlider:SetDecimals( 0 ) + DermaNumSlider:SetDefaultValue ( 1 ) + function DermaNumSlider.Slider:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black13"] ) + surface.DrawRect( 0, 0, w, h ) + end + function DermaNumSlider.Slider.Knob:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black"] ) + draw.NoTexture() + Realistic_Police.Circle( 0, H*0.007, 8, 1000 ) + end + DermaNumSlider.OnValueChanged = function( panel, value ) + if RPTStepID == 2 then + RPTPosition[id] = value + elseif RPTStepID == 3 then + RPTPosition2[id] = value + end + end + + local PosX, PosY = DermaNumSlider:GetPos() + if id > 0 && id < 4 or id == 7 then + local DLabel = vgui.Create( "DLabel", panel ) + DLabel:SetSize(W*0.3, H*0.02) + DLabel:SetPos( DermaNumSlider:GetWide()*0.052, PosY - H *0.03) + DLabel:SetText( TableSlider[id] ) + DLabel:SetFont("rpt_font_18") + elseif id != 7 then + local DLabel = vgui.Create( "DLabel", panel ) + DLabel:SetSize(W*0.3, H*0.02) + DLabel:SetPos( DermaNumSlider:GetWide()*0.66, PosY - H *0.03) + DLabel:SetText( TableSlider[id] ) + DLabel:SetFont("rpt_font_18") + end +end + +function Realistic_Police.Clic() + surface.PlaySound("rptsound2.mp3") +end + +local function RPTComputerView(ply, pos, angles, fov) + local view = {} + view.origin = pos + view.angles = angles + view.fov = fov + view.drawviewer = true + return view +end + +function Realistic_Police.OpenMenu(RPTEntComputer, BoolTablet) + Realistic_Police.CurrentComputer = RPTEntComputer + if IsValid(RPTBaseFrame) then RPTBaseFrame:Remove() end + local RpRespX, RpRespY = ScrW(), ScrH() + + local RPTAngle = RPTEntComputer:GetAngles() + Angle(-RPTEntComputer:GetAngles().pitch * 2, 180,-RPTEntComputer:GetAngles().roll *2) + local RPTAng = RPTEntComputer:GetAngles() + local RPTPos = RPTEntComputer:GetPos() + RPTAng:Up() * 19.5 + RPTAng:Right() * -1 + RPTAng:Forward() * 8 + local RPTStartTime = CurTime() + + local RPTValue = 300 + local RPTTarget = 200 + local speed = 40 + local Timer = 1.1 + + local Hours = os.date("%H:%M", os.time()) + hook.Add("CalcView", "RPTCalcView", function(ply, pos, angles, fov) + local FOV = 90 + if not BoolTablet then + if RPTInt != 0 then + RPTAngleOrigin = LerpVector( math.Clamp(CurTime()-RPTStartTime,0,1), LocalPlayer():EyePos(), RPTPos) + RPTAngleAngles = LerpAngle( math.Clamp(CurTime()-RPTStartTime,0,1), LocalPlayer():GetAngles(), RPTAngle) + FOV = 90 + else + RPTAngleOrigin = pos + RPTAngleAngles = angles + FOV = fov + end + return RPTComputerView(LocalPlayer(), RPTAngleOrigin, RPTAngleAngles, FOV) + end + end ) + + RPTBaseFrame = vgui.Create("DFrame") + RPTBaseFrame:SetSize(RpRespX, RpRespY) + RPTBaseFrame:MakePopup() + RPTBaseFrame:ShowCloseButton(false) + RPTBaseFrame:SetDraggable(false) + RPTBaseFrame:SetTitle("") + LocalPlayer():SetNoDraw(true) + RPTBaseFrame.Paint = function(self,w,h) + if BoolTablet then + surface.SetDrawColor( Realistic_Police.Colors["white"] ) + surface.SetMaterial( Realistic_Police.Colors["Material10"] ) + surface.DrawTexturedRect( RpRespX*0.0625, RpRespY*0.025, w*0.865, h*0.89 ) + end + end + if BoolTablet then + Timer = 0 + end + timer.Simple(Timer, function() + RPTFrame = vgui.Create("DPanel", RPTBaseFrame) + RPTFrame:SetSize(RpRespX*0.794, RpRespY*0.793) + RPTFrame:SetPos(RpRespX*0.1, RpRespY*0.06) + RPTFrame.Paint = function(self,w,h) + if not BoolTablet then + local isSBU = IsValid(RPTEntComputer) and RPTEntComputer.IsSBU and RPTEntComputer:IsSBU() + surface.SetDrawColor( Realistic_Police.Colors["white"] ) + surface.SetMaterial( isSBU and Realistic_Police.Colors["Material2_SBU"] or Realistic_Police.Colors["Material2"] ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + + surface.SetDrawColor( Realistic_Police.Colors["white200"] ) + surface.SetMaterial( Realistic_Police.Colors["Material3"] ) + surface.DrawTexturedRect( RpRespX*0.015, RpRespY*0.73, w*0.9713, h*0.0555 ) + draw.SimpleText(Hours, "rpt_font_15", RpRespX*0.764, RpRespY*0.738, Realistic_Police.Colors["white"], 1, 0) + end + + for k,v in pairs(Realistic_Police.Application) do + if k >= 99 then break end + local row = math.floor((k - 1)/7) + local rowmarge = row * RpRespY + local colum = k - (row * RpRespY*0.02) + local colmarge = (colum * 464) - 464 + + local Buttons = vgui.Create("DButton", RPTFrame) + Buttons:SetSize(RpRespX*0.047,RpRespX*0.047) + Buttons:SetPos(RpRespX*0.013 + rowmarge, RpRespY*0.092 * colum - RpRespY*0.08) + Buttons:SetText("") + Buttons.Paint = function(self,w,h) + if self:IsHovered() then + RPTValue = Lerp( speed * FrameTime( ), RPTValue, RPTTarget ) + surface.SetDrawColor( Realistic_Police.Colors["white"].r, Realistic_Police.Colors["white"].g, Realistic_Police.Colors["white"].b, RPTValue ) + surface.SetMaterial( v.Materials ) + surface.DrawTexturedRect( 5, 5, w - 15, h - 15 ) + else + surface.SetDrawColor( Realistic_Police.Colors["white"] ) + surface.SetMaterial( v.Materials ) + surface.DrawTexturedRect( 5, 5, w - 15, h - 15 ) + end + end + Buttons.DoClick = function() + Realistic_Police.Clic() + if Realistic_Police.OpenComputer[Realistic_Police.GetPlayerJob(LocalPlayer())] then + v.Function(RPTFrame, RPTEntComputer) + end + if Realistic_Police.HackerJob[Realistic_Police.GetPlayerJob(LocalPlayer())] && RPTEntComputer:GetNWBool("rpt_hack") or v.Type == "hacker" then + v.Function(RPTFrame, RPTEntComputer) + elseif not Realistic_Police.OpenComputer[Realistic_Police.GetPlayerJob(LocalPlayer())] then + RealisticPoliceNotify(Realistic_Police.GetSentence("computerMustBeHacked")) + end + end + + local appName = v.Name + if appName == "Терминал ФСБ" then + appName = Realistic_Police.GetSentence("terminalName") + end + + local DLabel = vgui.Create( "DLabel", RPTFrame ) + DLabel:SetSize(RpRespX*0.08, RpRespY*0.01) + DLabel:SetPos(-7 + rowmarge, RpRespY*0.092 * colum) + DLabel:SetText( appName ) + DLabel:SetFont("rpt_font_14") + DLabel:SetContentAlignment(5) + DLabel:SetTextColor(Realistic_Police.Colors["gray"]) + end + + local RPTClose = vgui.Create("DButton", RPTFrame) + RPTClose:SetSize(RpRespX*0.021,RpRespX*0.0199) + RPTClose:SetPos(RpRespX*0.018, RPTFrame:GetTall()*0.928) + RPTClose:SetText("") + RPTClose.DoClick = function() + RPTBaseFrame:Remove() + LocalPlayer():SetNoDraw(false) + Realistic_Police.Clic() + hook.Remove("CalcView", "RPTCalcView") + net.Start("RealisticPolice:SecurityCamera") + net.WriteString("DontShowCamera") + net.SendToServer() + end + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["white150"] ) + surface.SetMaterial( Realistic_Police.Colors["Material6"] ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + end ) +end + +net.Receive("RealisticPolice:SetupVehicle", function() + if not Realistic_Police.AdminRank[LocalPlayer():GetUserGroup()] then return end + local W,H = ScrW(), ScrH() + local Ent = net.ReadEntity() + local TableToSave = {} + + if IsValid(RPTMain) then RPTMain:Remove() end + RPTMain = vgui.Create("DFrame") + RPTMain:SetSize(W*0.3, H*0.36) + RPTMain:SetPos(W*0.35, H*0.65) + RPTMain:SetTitle("") + RPTMain:ShowCloseButton(false) + RPTMain.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black49"] ) + surface.DrawRect( 0, 0, w, h/1.95 ) + + surface.SetDrawColor( Realistic_Police.Colors["black49"] ) + surface.DrawRect( 0, h/1.90, w, h/5 ) + end + -- Right Sliders + CreateNumSlider(4, 0.06,0.05, RPTMain,-200, 200) + CreateNumSlider(5, 0.06, 0.10, RPTMain,-200, 200) + CreateNumSlider(6, 0.06, 0.15, RPTMain,-200, 200) + -- Left Sliders + CreateNumSlider(1,-0.09,0.05, RPTMain,-100, 100) + CreateNumSlider(2, -0.09, 0.10, RPTMain,-100, 100) + CreateNumSlider(3, -0.09, 0.15, RPTMain,-100, 100) + -- Back Sliders + CreateNumSlider(8, 0.06, 0.235, RPTMain, -200, 1000) + CreateNumSlider(7, -0.09, 0.235, RPTMain, -1200, 1200) + + local Save = vgui.Create("DButton", RPTMain) + Save:SetSize(W*0.3, H*0.07) + Save:SetPos(0,RPTMain:GetTall()*0.74) + Save:SetText(string.upper(Realistic_Police.GetSentence("save"))) + Save:SetFont("rpt_font_18") + Save:SetTextColor(Realistic_Police.Colors["white200"]) + Save.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["green"] ) + surface.DrawRect( 0, 0, w, h ) + end + Save.DoClick = function() + if not isvector(PosPlate) && not isangle(AngleEnt) then return end + local VectorEnt = PosPlate + Vector(RPTPosition[1],RPTPosition[2],RPTPosition[3]) + local AngleEnt = AngPlate + Angle(RPTPosition[4],RPTPosition[5],RPTPosition[6]) + local pos = Ent:WorldToLocal(VectorEnt) + + local VectorEnt2 = nil + local AngleEnt2 = nil + local pos2 = nil + if isvector(PosPlate2) && isangle(AngPlate2) then + VectorEnt2 = PosPlate2 + Vector(RPTPosition2[1],RPTPosition2[2],RPTPosition2[3]) + AngleEnt2 = AngPlate2 + Angle(RPTPosition2[4],RPTPosition2[5],RPTPosition2[6]) + pos2 = Ent:WorldToLocal(VectorEnt2) + end + + TableToSave[Ent:GetModel()] = { + Plate1 = { + PlateVector = pos, + PlateAngle = AngleEnt, + PlateSizeW = 1400 + RPTPosition[7], + PlateSizeH = 400 + RPTPosition[8], + }, + Plate2 = { + PlateVector = pos2, + PlateAngle = AngleEnt2, + PlateSizeW = 1400 + RPTPosition2[7], + PlateSizeH = 400 + RPTPosition2[8], + }, + } + if PosPlate2 == nil or AngPlate2 == nil then + TableToSave[Ent:GetModel()]["Plate2"] = nil + end + + net.Start("RealisticPolice:SetupVehicle") + net.WriteTable(TableToSave) + net.WriteEntity(Ent) + net.SendToServer() + + RPTMain:Remove() + PosPlate = nil + RPTToolSetup = false + RPTStepID = 1 + VectorEnt2 = nil + AngleEnt2 = nil + pos2 = nil + + RPTPosition = {} + RPTPosition2 = {} + Realistic_Police.Clic() + end +end ) +function Realistic_Police.PoliceTrunk() + local RespX, RespY = ScrW(), ScrH() + if IsValid(Frame) then Frame:Remove() end + if IsValid(RPTEGhostent) then RPTEGhostent:Remove() end + RealisticPoliceGhost = false + RealisticPoliceModel = nil + Frame = vgui.Create("DFrame") + Frame:SetSize(RespX*0.289, RespX*0.08) + Frame:MakePopup() + Frame:SetDraggable(false) + Frame:ShowCloseButton(false) + Frame:SetTitle("") + Frame:Center() + Frame.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["black25"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor( Realistic_Police.Colors["gray60200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + surface.SetDrawColor( Realistic_Police.Colors["black15"] ) + surface.DrawRect( 0, 0, w, h ) + draw.DrawText(Realistic_Police.GetSentence("policeCartrunk"), "rpt_font_7", RespX*0.003, RespY*0.002, Realistic_Police.Colors["gray220"], TEXT_ALIGN_LEFT) + end + + local DScroll = vgui.Create("DScrollPanel", Frame) + DScroll:SetSize(RespX*0.2788, RespY*0.096) + DScroll:SetPos(RespX*0.005, RespY*0.035) + DScroll.Paint = function() end + DScroll:GetVBar():SetSize(0,0) + + local List = vgui.Create( "DIconLayout", DScroll ) + List:Dock( FILL ) + List:SetSpaceY( 5 ) + List:SetSpaceX( 5 ) +for k,v in pairs(Realistic_Police.Trunk) do + local ListItem = List:Add( "SpawnIcon" ) + ListItem:SetSize( RespX*0.0541, RespX*0.0541 ) + ListItem:SetModel(k) + ListItem:SetTooltip( false ) + ListItem.DoClick = function() + RealisticPoliceGhost = true + RealisticPoliceModel = k + Frame:Remove() + RealisticPoliceNotify(Realistic_Police.GetSentence("spawnPropText")) + end + end + + local RPTClose = vgui.Create("DButton", Frame) + RPTClose:SetSize(RespX*0.03, RespY*0.025) + RPTClose:SetPos(Frame:GetWide()*0.89, Frame:GetTall()*0.008) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + LocalPlayer():SetNoDraw(false) + Frame:Remove() + Realistic_Police.Clic() + end +end + +local function RealisticPolicePlate(ent, pos, ang, String) + cam.Start3D2D( ent:LocalToWorld(pos), ent:LocalToWorldAngles(ang), 0.02 ) + if not IsValid(ent) then return end + if isstring(Realistic_Police.PlateVehicle[ent:GetVehicleClass()]) then + ent.Plate = Realistic_Police.PlateVehicle[ent:GetVehicleClass()] + else + ent.Plate = Realistic_Police.LangagePlate + end + if Realistic_Police.PlateConfig[ent.Plate]["Image"] != nil then + surface.SetDrawColor( Realistic_Police.Colors["gray240"] ) + surface.SetMaterial( Realistic_Police.PlateConfig[ent.Plate]["Image"] ) + surface.DrawTexturedRect( 0, 0, RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"], RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"] ) + end + local plateName = ent:GetNWString("rpt_plate") + if not Realistic_Police.PlateConfig[ent.Plate]["PlateText"] then + if plateName != "" then + local StringExplode = string.Explode(" ", plateName) or "" + if istable(StringExplode) && isstring(StringExplode[1]) && isstring(StringExplode[2]) && isstring(StringExplode[3]) then + plateName = StringExplode[1]..StringExplode[2]..StringExplode[3] + end + draw.SimpleText(plateName, Realistic_Police.Fonts(RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/6, (Realistic_Police.Lang == "ru" and "DermaDefault" or nil)),RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][1] - 5, RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/ Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][2]+5, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(plateName, Realistic_Police.Fonts(RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/6, (Realistic_Police.Lang == "ru" and "DermaDefault" or nil)),RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][1], RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/ Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][2], Realistic_Police.PlateConfig[ent.Plate]["TextColor"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + else + if plateName != "" then + local StringExplode = string.Explode(" ", plateName) or "" + if istable(StringExplode) && isstring(StringExplode[1]) && isstring(StringExplode[2]) && isstring(StringExplode[3]) then + plateName = StringExplode[1].."-"..StringExplode[2].."-"..StringExplode[3] + end + draw.SimpleText(plateName, Realistic_Police.Fonts(RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/6, (Realistic_Police.Lang == "ru" and "DermaDefault" or nil)),RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][1] - 5, RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/ Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][2]+5, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(plateName, Realistic_Police.Fonts(RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/6, (Realistic_Police.Lang == "ru" and "DermaDefault" or nil)),RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][1], RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/ Realistic_Police.PlateConfig[ent.Plate]["PlatePos"][2], Realistic_Police.PlateConfig[ent.Plate]["TextColor"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + if Realistic_Police.PlateConfig[ent.Plate]["Country"] != nil then + draw.SimpleText(Realistic_Police.PlateConfig[ent.Plate]["Country"], Realistic_Police.Fonts(RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/12), RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"] - RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/Realistic_Police.PlateConfig[ent.Plate]["CountryPos"][1], RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/Realistic_Police.PlateConfig[ent.Plate]["CountryPos"][2], Realistic_Police.PlateConfig[ent.Plate]["CountryColor"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + if Realistic_Police.PlateConfig[ent.Plate]["Department"] != nil then + draw.SimpleText(Realistic_Police.PlateConfig[ent.Plate]["Department"], Realistic_Police.Fonts(RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/13), RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/1.06, RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/1.4, Realistic_Police.Colors["gray"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + if Realistic_Police.PlateConfig[ent.Plate]["ImageServer"] != nil then + surface.SetDrawColor( Realistic_Police.Colors["gray"] ) + surface.SetMaterial( Realistic_Police.PlateConfig[ent.Plate]["ImageServer"] ) + surface.DrawTexturedRect( RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/1.104, RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/6, RPTInformationVehicle[ent:GetModel()][String]["PlateSizeW"]/14, RPTInformationVehicle[ent:GetModel()][String]["PlateSizeH"]/3 ) + end + cam.End3D2D() +end + +local VehicleTable = {} +hook.Add( "OnEntityCreated", "RPT:OnEntityCreated", function( ent ) + if IsValid(ent) && ent:IsVehicle() or ent:GetClass() == "gmod_sent_vehicle_fphysics_base" then + VehicleTable[#VehicleTable + 1] = ent + end +end ) + +hook.Add("PostDrawTranslucentRenderables", "RPT:LicensePlateD", function(bDepth, bSkybox) -- Draw Plate + if ( bSkybox ) then return end + for k, ent in pairs( VehicleTable ) do + if Realistic_Police.TrunkSystem && Realistic_Police.KeyTrunkHUD then + if IsValid(ent) && ent:IsVehicle() then + if ent:GetClass() == "prop_vehicle_jeep" or ent:GetClass() == "gmod_sent_vehicle_fphysics_base" then + if LocalPlayer():GetPos():DistToSqr(ent:LocalToWorld(Vector(0, - ent:OBBMaxs().y))) < 60000 && Realistic_Police.VehiclePoliceTrunk[ent:GetVehicleClass()] then + + cam.Start3D2D( istable(Realistic_Police.TrunkPosition[ent:GetVehicleClass()]) and ent:LocalToWorld(Vector(0 + Realistic_Police.TrunkPosition[ent:GetVehicleClass()]["Pos"].x, - ent:OBBMaxs().y + Realistic_Police.TrunkPosition[ent:GetVehicleClass()]["Pos"].y, ent:OBBMaxs().z*0.5 + Realistic_Police.TrunkPosition[ent:GetVehicleClass()]["Pos"].z)) or ent:LocalToWorld(Vector(0, - ent:OBBMaxs().y, ent:OBBMaxs().z*0.5)), ent:GetAngles() + Angle(0,0,90), 0.1 ) + surface.SetDrawColor( Realistic_Police.Colors["gray60200"] ) + draw.NoTexture() + Realistic_Police.Circle( 0, 0, 50, 1000 ) + surface.SetDrawColor( Realistic_Police.Colors["black20"] ) + draw.NoTexture() + Realistic_Police.Circle( 0, 0, 45, 1000 ) + draw.SimpleText("E", "rpt_font_23",0, 0, Color(240,240,240, LocalPlayer():GetPos():DistToSqr(ent:GetPos()) * 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + end + end + end + end + if Realistic_Police.PlateActivate then + if IsValid(ent) && ent:IsVehicle() then + if ent:GetPos():DistToSqr(LocalPlayer():GetPos()) < 3000000 then + if istable(RPTInformationVehicle) && istable(RPTInformationVehicle[ent:GetModel()]) then + if istable(RPTInformationVehicle[ent:GetModel()]["Plate1"]) && isvector(RPTInformationVehicle[ent:GetModel()]["Plate1"]["PlateVector"]) && isangle(RPTInformationVehicle[ent:GetModel()]["Plate1"]["PlateAngle"]) then + RealisticPolicePlate(ent, RPTInformationVehicle[ent:GetModel()]["Plate1"]["PlateVector"], RPTInformationVehicle[ent:GetModel()]["Plate1"]["PlateAngle"], "Plate1") + end + if istable(RPTInformationVehicle[ent:GetModel()]["Plate2"]) && isangle(RPTInformationVehicle[ent:GetModel()]["Plate2"]["PlateAngle"]) && isvector(RPTInformationVehicle[ent:GetModel()]["Plate2"]["PlateVector"]) then + RealisticPolicePlate(ent, RPTInformationVehicle[ent:GetModel()]["Plate2"]["PlateVector"], RPTInformationVehicle[ent:GetModel()]["Plate2"]["PlateAngle"], "Plate2") + end + end + end + else + VehicleTable[k] = nil + end + end + end +end ) + +hook.Add("Think", "RPT:Think", function() + -- Ghost entity for the trunk + if RealisticPoliceGhost then + if IsValid(RPTEGhostent) then RPTEGhostent:Remove() end + RPTEGhostent = ClientsideModel(RealisticPoliceModel, RENDERGROUP_OPAQUE) + RPTEGhostent:SetModel("") + RPTEGhostent:SetMaterial("models/wireframe") + RPTEGhostent:SetPos(LocalPlayer():GetEyeTrace().HitPos + Realistic_Police.Trunk[RealisticPoliceModel]["GhostPos"]) + RPTEGhostent:SetAngles(Angle(0,LocalPlayer():GetAngles().y + 90,0)) + RPTEGhostent:Spawn() + RPTEGhostent:Activate() + RPTEGhostent:SetRenderMode(RENDERMODE_TRANSALPHA) + end +end ) + +hook.Add("HUDPaint", "RealisticPolicesd:HUDPaint", function() + if Realistic_Police.CameraBrokeHud then + if Realistic_Police.CameraWorker[Realistic_Police.GetPlayerJob(LocalPlayer())] then + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + if v:GetRptCam() then + if not isvector(v:GetPos()) then return end + local tscreen = v:GetPos():ToScreen() + local sx, sy = surface.GetTextSize( "Crime Scene" ) + local dist = math.Round(v:GetPos():DistToSqr(LocalPlayer():GetPos())/(25600)) + if dist > 0.25 then + draw.SimpleText( dist.."m" , "rpt_font_2", tscreen.x, tscreen.y, Realistic_Police.Colors["white"], 1) + surface.SetDrawColor( Realistic_Police.Colors["white"].r, Realistic_Police.Colors["white"].g, Realistic_Police.Colors["white"].b, RPTValue ) + surface.SetMaterial( Realistic_Police.Colors["Material5"] ) + surface.DrawTexturedRect( tscreen.x - 12.5, tscreen.y - 23, 25, 25 ) + end + end + end + end + end + if IsValid(LocalPlayer():GetEyeTrace().Entity) then + if LocalPlayer():GetEyeTrace().Entity:GetClass() == "realistic_police_screen" or LocalPlayer():GetEyeTrace().Entity:GetClass() == "realistic_police_policescreen" then + if LocalPlayer():GetEyeTrace().Entity:GetPos():DistToSqr((LocalPlayer():GetPos())) < 10000 then + surface.SetDrawColor(Realistic_Police.Colors["white"]) + surface.SetMaterial( Realistic_Police.Colors["Material4"] ) + surface.DrawTexturedRect(ScrW()/2-25, ScrH()*0.9,50,50) + end + end + end +end) + +net.Receive("RealisticPolice:StunGun", function() + local Players = net.ReadEntity() + Players.TimerTazerStart = CurTime() + 5 + timer.Simple(0, function() + if IsValid(Players) && IsValid(Players:GetRagdollEntity()) then + Phys = Players:GetRagdollEntity():GetPhysicsObject() + + hook.Add("Think", "RPT:StunGunThink"..Players:EntIndex(), function() + if IsValid(Players) && Players:IsPlayer() then + if Players.TimerTazerStart > CurTime() then + if IsValid(Phys) then + Phys:ApplyForceOffset( Vector(math.random(-40,40),math.random(-40,40),0), Phys:GetPos() + Vector(0,0,50)) + end + else + hook.Remove("Think", "RPT:StunGunThink"..Players:EntIndex()) + end + end + end ) + end + end ) +end ) + +hook.Add( "PlayerButtonDown", "RPT:PlayerButtonDownTrunk", function( ply, button ) + if button == (Realistic_Police.KeyForOpenTrunk or KEY_E) then + if IsValid(RPTEGhostent) then + if isstring(RealisticPoliceModel) then + net.Start("RealisticPolice:PlaceProp") + net.WriteString(RealisticPoliceModel) + net.SendToServer() + end + RealisticPoliceGhost = false + RPTEGhostent:Remove() + end + + local ent = ply:GetEyeTrace().Entity + + if not IsValid(ent) or not ent:IsVehicle() or not Realistic_Police.TrunkSystem then return end + if not ent:GetClass() == "prop_vehicle_jeep" && not ent:GetClass() == "gmod_sent_vehicle_fphysics_base" then return end + if not Realistic_Police.VehiclePoliceTrunk[ent:GetVehicleClass()] or not Realistic_Police.CanOpenTrunk[Realistic_Police.GetPlayerJob(ply)] then return end + + if ply:GetPos():DistToSqr(Realistic_Police.TrunkPosition[ent:GetVehicleClass()] and Realistic_Police.TrunkPosition[ent:GetVehicleClass()]["Pos"] + ent:LocalToWorld(Vector(0, - ent:OBBMaxs().y, ent:OBBMaxs().z*0.5)) or ent:LocalToWorld(Vector(0, - ent:OBBMaxs().y, ent:OBBMaxs().z*0.5))) > 7500 then return end + Realistic_Police.PoliceTrunk() + end +end) + +net.Receive("RealisticPolice:Open", function() + local String = net.ReadString() + if String == "OpenFiningMenu" then + local Bool = net.ReadBool() + Realistic_Police.OpenFiningMenu(Bool) + elseif String == "OpenMainMenu" then + local Ent = net.ReadEntity() + local Bool = net.ReadBool() + Realistic_Police.OpenMenu(Ent, Bool) + elseif String == "OpenTrunk" then + -- Realistic_Police.PoliceTrunk() + elseif String == "Notify" then + local msg = net.ReadString() + RealisticPoliceNotify(msg) + end +end ) + +hook.Add("SetupMove", "RPT:SetupMove", function(ply, data) + local wep = ply:GetActiveWeapon() + if IsValid(wep) then + if wep:GetClass() == "weapon_rpt_surrender" or wep:GetClass() == "weapon_rpt_cuffed" then + data:SetMaxSpeed(80) + data:SetMaxClientSpeed(80) + end + end +end ) + +hook.Add("PlayerSwitchWeapon", "RPT:PlayerSwitchWeapon", function(ply, wp, wep) + local wep = ply:GetActiveWeapon() + if not IsValid(wep) then return end + + if wep:GetClass() == "weapon_rpt_surrender" or wep:GetClass() == "weapon_rpt_cuffed" or timer.Exists("rpt_animation"..ply:EntIndex()) then + return true + end +end) + +function Realistic_Police.NameCamera(CEntity) + local RpRespX, RpRespY = ScrW(), ScrH() +local RPTValue = 0 + local RPTTarget = 300 + local speed = 20 + + local RPTLFrame = vgui.Create("DFrame") + RPTLFrame:SetSize(RpRespX*0.272, RpRespY*0.2) + RPTLFrame:Center() + RPTLFrame:ShowCloseButton(false) + RPTLFrame:MakePopup() + RPTLFrame:SetDraggable(false) + RPTLFrame:SetTitle("") + RPTLFrame.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime(), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) +surface.SetDrawColor( Realistic_Police.Colors["gray60200"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) +surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( w*0.003, 0, w*0.995, h*0.15 ) + + draw.DrawText(Realistic_Police.GetSentence("cameraConfiguration"), "rpt_font_7", RpRespX*0.004, RpRespY*0.005, Color(Realistic_Police.Colors["gray220"].r, Realistic_Police.Colors["gray220"].g, Realistic_Police.Colors["gray220"].b, RPTValue), TEXT_ALIGN_LEFT) + end + + local PanelCT = vgui.Create("DTextEntry", RPTLFrame) + PanelCT:SetSize(RpRespX*0.251, RpRespY*0.05) + PanelCT:SetPos(RpRespX*0.01, RpRespY*0.05) + PanelCT:SetText(" "..Realistic_Police.GetSentence("cameraName")) + PanelCT:SetFont("rpt_font_7") + PanelCT:SetDrawLanguageID(false) + PanelCT.OnGetFocus = function(self) + if PanelCT:GetText() == " "..Realistic_Police.GetSentence("cameraName") then + PanelCT:SetText("") + end + end + PanelCT.Paint = function(self,w,h) + surface.SetDrawColor(Realistic_Police.Colors["black"]) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( Realistic_Police.Colors["black180"] ) + surface.DrawRect( 0, 0, w, h ) + + self:DrawTextEntryText(Realistic_Police.Colors["white"], Realistic_Police.Colors["white"], Realistic_Police.Colors["white"]) + surface.SetDrawColor( Realistic_Police.Colors["darkblue"] ) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + PanelCT.OnLoseFocus = function(self) + if PanelCT:GetText() == "" then + PanelCT:SetText(" "..Realistic_Police.GetSentence("infractionReason")) + end + end + PanelCT.AllowInput = function( self, stringValue ) + if string.len(PanelCT:GetValue()) > 18 then + return true + end + end + + DButtonAccept = vgui.Create("DButton", RPTLFrame) + DButtonAccept:SetSize(RpRespX*0.257, RpRespY*0.05) + DButtonAccept:SetPos(RpRespX*0.007, RpRespY*0.11) + DButtonAccept:SetText(Realistic_Police.GetSentence("confirm")) + DButtonAccept:SetFont("rpt_font_9") + DButtonAccept:SetTextColor(Realistic_Police.Colors["white"]) + DButtonAccept.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["green"] ) + surface.DrawRect( 5, 0, w-10, h ) + end + DButtonAccept.DoClick = function() + net.Start("RealisticPolice:NameCamera") + net.WriteEntity(CEntity) + net.WriteString(PanelCT:GetText()) + net.SendToServer() + RPTLFrame:Remove() + end + + local RPTClose = vgui.Create("DButton", RPTLFrame) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTLFrame:GetWide()*0.885, RPTLFrame:GetTall()*0.008) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTLFrame:Remove() + Realistic_Police.Clic() + end +end + +net.Receive("RealisticPolice:NameCamera", function() + local CEntity = net.ReadEntity() + Realistic_Police.NameCamera(CEntity) +end ) + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_notify.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_notify.lua new file mode 100644 index 0000000..8a28227 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_notify.lua @@ -0,0 +1,80 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police.Notify = Realistic_Police.Notify or {} +function RealisticPoliceNotify(msg, time) + if not isnumber(time) then time = 5 end + + if not Realistic_Police.UseDarkRPNotify then + Realistic_Police.Notify[#Realistic_Police.Notify + 1] = { + ["msg"] = msg, + ["time"] = CurTime() + (time or 5), + ["mat"] = Material("jail.png"), + } + else + notification.AddLegacy(msg, NOTIFY_GENERIC, time) + end +end + +function Realistic_Police.DrawNotification() + if Realistic_Police.Notify and #Realistic_Police.Notify > 0 then + for k,v in ipairs(Realistic_Police.Notify) do + surface.SetFont("rpt_font_20") + + local sizeText = surface.GetTextSize(v.msg) + + local RPS, RPTScrH = ScrW(), ScrH() + + if not isnumber(v.RLerp) then v.RLerp = -sizeText end + if not isnumber(v.RLerpY) then v.RLerpY = (RPTScrH*0.055*k)-RPTScrH*0.038 end + + if v.time > CurTime() then + v.RLerp = Lerp(FrameTime()*3, v.RLerp, RPS*0.02) + else + v.RLerp = Lerp(FrameTime()*3, v.RLerp, (-RPS*0.25 - sizeText)) + if v.RLerp < (-RPS*0.15 - sizeText) then + Realistic_Police.Notify[k] = nil + Realistic_Police.Notify = table.ClearKeys(Realistic_Police.Notify) + end + end + + v.RLerpY = Lerp(FrameTime()*8, v.RLerpY, (RPTScrH*0.055*k)-RPTScrH*0.038) +local posy = v.RLerpY + local incline = RPTScrH*0.055 + + local leftPart = { + {x = v.RLerp, y = posy}, + {x = v.RLerp + incline + sizeText + RPTScrH*0.043, y = posy}, + {x = v.RLerp + RPTScrH*0.043 + sizeText + RPTScrH*0.043, y = posy + RPTScrH*0.043}, + {x = v.RLerp, y = posy + RPTScrH*0.043}, + } +surface.SetDrawColor(Realistic_Police.Colors["darkblue"]) + draw.NoTexture() + surface.DrawPoly(leftPart) + + local rightPart = { + {x = v.RLerp, y = posy}, + {x = v.RLerp + incline, y = posy}, + {x = v.RLerp + RPTScrH*0.043, y = posy + RPTScrH*0.043}, + {x = v.RLerp, y = posy + RPTScrH*0.043}, + } + + surface.SetDrawColor(Realistic_Police.Colors["lightblue"]) + draw.NoTexture() + surface.DrawPoly(rightPart) + + surface.SetDrawColor(Realistic_Police.Colors["white"]) + surface.SetMaterial(v.mat) + surface.DrawTexturedRect(v.RLerp + RPS*0.006, v.RLerpY + RPTScrH*0.007, RPTScrH*0.027, RPTScrH*0.027) +draw.SimpleText(v.msg, "rpt_font_20", v.RLerp + RPS*0.037, v.RLerpY + RPTScrH*0.041/2, Realistic_Police.Colors["white"], TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end +end +hook.Add("HUDPaint", "Realistic_Police:DrawNotification", function() + Realistic_Police.DrawNotification() +end) + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_report.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_report.lua new file mode 100644 index 0000000..5d71136 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/client/cl_rpt_report.lua @@ -0,0 +1,249 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +function Realistic_Police.ReportMenu(RPTFrame, RPTable, RPTSendEntity, RPTIndex) + local RpRespX, RpRespY = ScrW(), ScrH() + local TimesDay = os.date("%d/%m/%Y", os.time()) + local RPTActivate = false + local RPTEntity = nil + + local RPTValue = 150 + local RPTTarget = 300 + local speed = 5 +local RPTMenu = vgui.Create("DFrame", RPTFrame) + RPTMenu:SetSize(RpRespX*0.794, RpRespY*0.8) + RPTMenu:SetPos(0, 0) + RPTMenu:ShowCloseButton(false) + RPTMenu:SetDraggable(false) + RPTMenu:SetTitle("") + RPTMenu.Paint = function(self,w,h) + RPTValue = Lerp( speed * FrameTime(), RPTValue, RPTTarget ) + surface.SetDrawColor( Color(Realistic_Police.Colors["black25"].r, Realistic_Police.Colors["black25"].g, Realistic_Police.Colors["black25"].b, RPTValue) ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetDrawColor( Color(Realistic_Police.Colors["black15"].r, Realistic_Police.Colors["black15"].g, Realistic_Police.Colors["black15"].b, RPTValue) ) + surface.DrawRect( 0, 0, w + 10, h*0.045 ) + end +local RPTScroll = vgui.Create("DScrollPanel", RPTMenu) + RPTScroll:SetSize(RpRespX*0.794, RpRespY*0.7) + RPTScroll:SetPos(0, RpRespY*0.1) + RPTScroll.Paint = function() end + local sbar = RPTScroll:GetVBar() + sbar:SetSize(10,0) + function sbar.btnUp:Paint() end + function sbar.btnDown:Paint() end + function sbar:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["black100"] ) + surface.DrawRect( 0, 0, w, h ) + end + function sbar.btnGrip:Paint(w, h) + surface.SetDrawColor( Realistic_Police.Colors["gray50"] ) + surface.DrawRect( 0, 0, w, h ) + end + + local RPTDPanel = vgui.Create("DFrame", RPTScroll) + RPTDPanel:SetSize(RpRespX*0.4, RpRespY*0.85) + RPTDPanel:SetPos(RpRespX*0.2, 0) + RPTDPanel:SetDraggable(false) + RPTDPanel:ShowCloseButton(false) + RPTDPanel.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["gray240"] ) + surface.DrawRect( 0, 0, w, h ) + end +local DLabel = vgui.Create("DLabel", RPTDPanel) + DLabel:SetSize(RpRespX*0.1,RpRespY*0.1) + DLabel:SetPos(RpRespX*0.03, 0) + DLabel:SetText(LocalPlayer():Name()) + DLabel:SetTextColor(Realistic_Police.Colors["black"]) + DLabel:SetFont("rpt_font_21") + + local DLabel2 = vgui.Create("DLabel", RPTDPanel) + DLabel2:SetSize(RpRespX*0.1,RpRespY*0.1) + DLabel2:SetPos(RpRespX*0.29, 0) + DLabel2:SetText(Realistic_Police.GetSentence("at").." "..TimesDay) + DLabel2:SetTextColor(Realistic_Police.Colors["black"]) + DLabel2:SetFont("rpt_font_21") + + local RPTText = vgui.Create("DTextEntry", RPTDPanel) + RPTText:SetSize(RpRespX*0.3, RpRespY*0.72) + RPTText:SetPos(RpRespX*0.05, RpRespY*0.1) + RPTText:SetMultiline(true) + RPTText:SetFont("rpt_font_20") + if not istable(RPTable) then + RPTText:SetValue(Realistic_Police.GetSentence("reportText")) + else + RPTText:SetValue(RPTable["RPTText"]) + end + RPTText:SetDrawLanguageID(false) + RPTText.Paint = function(self,w,h) + self:DrawTextEntryText(Realistic_Police.Colors["black"], Realistic_Police.Colors["black"], Realistic_Police.Colors["black"]) + end + RPTText.AllowInput = function( self, stringValue ) + if string.len(RPTText:GetValue()) > 1183 then + return true + end + end + RPTText.OnChange = function() + Realistic_Police.HackKey() + end + + local RPTSteamId = nil + local RPTIndexSave = 1 + local RPTDCombobox = vgui.Create("DComboBox", RPTMenu) + RPTDCombobox:SetSize(RpRespX*0.08, RpRespY*0.03) + RPTDCombobox:SetPos(RPTMenu:GetWide()*0.9, RpRespY*0.04) + RPTDCombobox:SetText(Realistic_Police.GetSentence("accusedName")) + RPTDCombobox:SetTextColor(Realistic_Police.Colors["black"]) + RPTDCombobox:SetFont("rpt_font_8") + RPTDCombobox.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["gray240"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTDCombobox:AddChoice( Realistic_Police.GetSentence("unknown") ) + local isSBUComputer = IsValid(RPTable) and RPTable.IsSBU and RPTable:IsSBU() + for k,v in pairs(player.GetAll()) do + if v != LocalPlayer() then + local targetChar = v.GetCharacter and v:GetCharacter() + if targetChar then + local faction = targetChar:GetFaction() + if isSBUComputer then + if faction != (FACTION_UKRAINE or -1) then continue end + else + if faction == (FACTION_UKRAINE or -1) then continue end + end + end + RPTDCombobox:AddChoice( v:Name(), v ) + end + end + function RPTDCombobox:OnSelect( index, text, data ) + RPTActivate = true + RPTIndexSave = index + Realistic_Police.Clic() + if text == Realistic_Police.GetSentence("unknown") then + RPTEntity = nil + else + RPTEntity = data + end + end + if istable(RPTable) then + RPTDCombobox:Clear() + RPTActivate = true + if IsValid(RPTSendEntity) then + RPTDCombobox:SetValue(RPTSendEntity:GetName()) + else + RPTDCombobox:SetValue(Realistic_Police.GetSentence("unknown")) + end + RPTSteamId = RPTable["RPTSteamID64"] + RPTIndexSave = RPTIndex + if text == Realistic_Police.GetSentence("unknown") then + RPTSteamId = "unknown" + end + end +local RPTButtonSave = vgui.Create("DButton", RPTMenu) + RPTButtonSave:SetSize(RpRespX*0.04, RpRespY*0.03) + RPTButtonSave:SetPos(RpRespX*0.004,RpRespY*0.04) + RPTButtonSave:SetText(Realistic_Police.GetSentence("save")) + RPTButtonSave:SetTextColor(Realistic_Police.Colors["gray200"]) + RPTButtonSave:SetFont("rpt_font_7") + RPTButtonSave.Paint = function() + if RPTButtonSave:IsHovered() then + RPTButtonSave:SetTextColor(Realistic_Police.Colors["gray150"]) + else + RPTButtonSave:SetTextColor(Realistic_Police.Colors["gray200"]) + end + end + RPTButtonSave.DoClick = function() + if Realistic_Police.JobEditReport[Realistic_Police.GetPlayerJob(LocalPlayer())] then + if IsValid(RPTEntity) then + if RPTEntity == LocalPlayer() then RealisticPoliceNotify(Realistic_Police.GetSentence("cantEditSelfSanctions")) return end + end + if RPTDCombobox:GetValue() == Realistic_Police.GetSentence("unknown") then + RPTEntity = nil + end + if RPTActivate then + if not istable(RPTable) then + net.Start("RealisticPolice:Report") + net.WriteUInt(1, 10) + net.WriteString("SaveReport") + net.WriteEntity(RPTEntity) + net.WriteString(RPTText:GetValue()) + net.SendToServer() + else + net.Start("RealisticPolice:Report") + net.WriteUInt(RPTIndexSave, 10) + net.WriteString("EditReport") + net.WriteEntity(RPTSendEntity) + net.WriteString(RPTText:GetValue()) + net.WriteString(RPTDCombobox:GetValue()) + net.SendToServer() + end + RPTMenu:Remove() + end + timer.Simple(0.4, function() + if IsValid(RPTScrollLReport) then + Realistic_Police.ActualizeReport(RPTScrollLReport) + end + end ) + Realistic_Police.Clic() + else + RealisticPoliceNotify(Realistic_Police.GetSentence("cantDoThat")) + end + end +local RPTButtonDelete = vgui.Create("DButton", RPTMenu) + RPTButtonDelete:SetSize(RpRespX*0.04, RpRespY*0.03) + RPTButtonDelete:SetPos(RpRespX*0.04,RpRespY*0.04) + RPTButtonDelete:SetText(Realistic_Police.GetSentence("delete")) + RPTButtonDelete:SetTextColor(Realistic_Police.Colors["gray200"]) + RPTButtonDelete:SetFont("rpt_font_7") + RPTButtonDelete.Paint = function() + if RPTButtonDelete:IsHovered() then + RPTButtonDelete:SetTextColor(Realistic_Police.Colors["gray150"]) + else + RPTButtonDelete:SetTextColor(Realistic_Police.Colors["gray200"]) + end + end + RPTButtonDelete.DoClick = function() + if Realistic_Police.JobDeleteReport[Realistic_Police.GetPlayerJob(LocalPlayer())] then + if IsValid(RPTEntity) then + if RPTEntity == LocalPlayer() then RealisticPoliceNotify(Realistic_Police.GetSentence("cantDeleteSelfSanctions")) return end + end + if istable(RPTable) then + if RPTDCombobox:GetValue() == Realistic_Police.GetSentence("unknown") then + RPTEntity = nil + end + net.Start("RealisticPolice:Report") + net.WriteUInt(RPTIndex, 10) + net.WriteString("RemoveReport") + net.WriteEntity(RPTSendEntity) + net.SendToServer() + end + timer.Simple(0.2, function() + if IsValid(RPTScrollLReport) then + Realistic_Police.ActualizeReport(RPTScrollLReport) + end + end ) + RPTMenu:Remove() + Realistic_Police.Clic() + else + RealisticPoliceNotify(Realistic_Police.GetSentence("cantDelete")) + end + end + + local RPTClose = vgui.Create("DButton", RPTMenu) + RPTClose:SetSize(RpRespX*0.03, RpRespY*0.028) + RPTClose:SetPos(RPTMenu:GetWide()*0.9605, RpRespY*0.00448) + RPTClose:SetText("") + RPTClose.Paint = function(self,w,h) + surface.SetDrawColor( Realistic_Police.Colors["red"] ) + surface.DrawRect( 0, 0, w, h ) + end + RPTClose.DoClick = function() + RPTMenu:Remove() + Realistic_Police.Clic() + end +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_cn.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_cn.lua new file mode 100644 index 0000000..b67dccb --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_cn.lua @@ -0,0 +1,114 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["cn"] = { + ["criminalRecord"] = "犯罪记录", + ["recordOf"] = "储物柜", + ["username"] = "姓名", + ["addUpperCase"] = "添加", + ["infractionReason"] = "罪行", + ["create"] = "创建", + ["cancel"] = "取消", + ["licenseplate"] = "车牌", + ["search"] = "搜索", + ["noVehiclesFound"] = "没有找到车辆", + ["reportText"] = [[检察官先生/女士, + + 本人(姓和名),生于(出生地出生日期),从事(职业),特此通知你对居住在(有关人员或公司的地址名称)的(姓和名/公司名称/组织名称/或X(如不详))进行(犯罪行为)投诉。 + + 截至(事件发生日期),本人确实是以下事件的受害者:(请尽可能准确地注明事件的详细情况以及发生地点、事件发生时间和参与犯罪的人员)。我随信附上可能对本案进一步审理有用的任何文件/照片/证据(请附上复印件,不要原件)。 + + 有几个人目睹了上述犯罪行为。他们是太太/先生(姓和名),住在(证人的地址),可以通过以下号码(证人的电话号码)联系。 + + 有几个人目睹了上述犯罪行为。他们是夫人/先生(姓和名)。 + + 检察官先生/女士,请接受我最美好的祝愿,谢谢你,祝你有愉快的一天。 + + 警务人员签名]], + ["unknown"] = "未知", + ["delete"] = "删除", + ["at"] = "该", + ["accusedName"] = "被告姓名", + ["save"] = "保存", + ["prosecutor"] = "检察官", + ["accused"] = "已确认", + ["date"] = "日期", + ["see"] = "看", + ["username2"] = "姓名", + ["allComplaints"] = "所有的投诉", + ["moveX"] = "X轴位移", + ["moveY"] = "Y轴位移", + ["moveZ"] = "Z轴位移", + ["rotateX"] = "X轴旋转", + ["rotateY"] = "Y轴旋转", + ["rotateZ"] = "Z轴旋转", + ["rotateYY"] = "Y轴旋转", + ["width"] = "宽度", + ["height"] = "高度", + ["configureLicenseplate"] = "选择车辆进行车牌配置。", + ["placeLicenseplate"] = "把(1)块车牌挂在你车辆的上", + ["placeLicenseplate2"] = "把(2)块车牌挂在你车辆的上", + ["undefined"] = "你没有正确的工作来访问computer。", + ["youWon"] = "你刚刚赢了", + ["youAddPenality"] = "你增加了一个处罚", + ["cameraCenter"] = "摄像中心", + ["cameraSecurityOfCity"] = "市区监控摄像头", + ["switchCamera"] = "按 使用 键更换摄像头", + ["cantDelete"] = "你不能删除它", + ["cantDoThat"] = "你不能这样做", + ["noSignal"] = "无信号", + ["connectionProblem"] = "连接错误", + ["computerMustBeHacked"] = "你必须入侵它才可以打开应用程序。", + ["cantAffordFine"] = "你没有足够的钱来支付罚款。", + ["payFine"] = "缴纳罚款", + ["confirm"] = "确认", + ["category"] = "类型", + ["fineBook"] = "罚款书", + ["noPayFine"] = "没有支付罚款", + ["personAlreadyHaveFine"] = "这个人已经有罚款了", + ["vehicleHaveFine"] = "这辆车已经有罚款了", + ["vehicleCantReceiveFine"] = "不能给这辆车罚款", + ["noGoodJob"] = "职业错误,不能增加罚单", + ["userCantReceiveFine"] = "你不能给这名玩家下罚单", + ["vehicleCantHaveFine"] = "你不能给这辆车下罚单", + ["richSanctions"] = "你可以增加更多的罚金", + ["mustSelectPenality"] = "你必须设定罚款金额", + ["cantAddFineToHimVehicle"] = "你不能给自己的车罚款", + ["noGoodJobToArrestuser"] = "职业错误,你不能逮捕这个人", + ["cantArrestPlayer"] = "你不能逮捕这个人", + ["needHoldPlayerToArrest"] = "你必须抓着玩家才能让他坐牢", + ["justPaid"] = "你刚付了钱", + ["cantAfford"] = "你没有足够的钱来做这件事", + ["arrestMenu"] = "逮捕菜单", + ["name"] = "名称", + ["price"] = "价格", + ["reason"] = "原因", + ["pay"] = "支付", + ["arrestedPlayersList"] = "被捕人员名单", + ["noArrestedPlayer"] = "没有玩家被捕", + ["noJailPositions"] = "没有设置监狱位置,请联系管理员", + ["weaponsList"] = "武器列表", + ["confiscate"] = "充公", + ["youSurrender"] = "你已投降", + ["youArrest"] = "你被逮捕了", + ["years"] = "年", + ["tooMuchEntities"] = "你有很多实体", + ["spawnPropText"] = "按[USE]键生成道具", + ["youUnarrestBy"] = "你被释放了", + ["youArrestBy"] = "你被逮捕了", + ["youHaveArrest"] = "逮捕你", + ["policeCartrunk"] = "警察箱", + ["cantDeleteSelfSanctions"] = "你不能删除你的惩罚", + ["cantEditSelfSanctions"] = "你不能编辑你的刑罚", + ["seconds"] = "秒", + ["cameraName"] = "相机名称", + ["cameraConfiguration"] = "相机配置" +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_de.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_de.lua new file mode 100644 index 0000000..6824197 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_de.lua @@ -0,0 +1,113 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["de"] = { + ["criminalRecord"] = "Strafregister", + ["recordOf"] = "Schließfach von", + ["username"] = "Name der Person", + ["addUpperCase"] = "ANZEIGE", + ["infractionReason"] = "Grund für die Straftat", + ["create"] = "Erstellen", + ["cancel"] = "Abbrechen", + ["licenseplate"] = "Nummernschild", + ["search"] = "Suche", + ["noVehiclesFound"] = "Keine Fahrzeuge gefunden", + ["reportText"] = [[Frau Staatsanwältin, Herr Staatsanwalt, + + Ich, der Unterzeichnete (Vor- und Nachname), geboren am (Geburtsdatum) am (Geburtsort), der den Beruf (Beruf) ausübt, teile Ihnen mit, dass ich Anzeige gegen (Vor- und Nachname / Name des Unternehmens / Name der Organisation / oder X, falls unbekannt), wohnhaft in (Name der Adresse der betreffenden Person oder des Unternehmens), wegen (Qualifikation der Straftat) erstatte. + + Am (Datum des Ereignisses) war ich tatsächlich Opfer des folgenden Sachverhalts: (geben Sie so genau wie möglich die Einzelheiten des Sachverhalts sowie den Ort, an dem er sich ereignet hat, die Uhrzeit des Ereignisses und die an der Zuwiderhandlung beteiligten Personen an). Ich füge diesem Schreiben alle Dokumente/Fotos/Beweise bei, die für den weiteren Verlauf des Falles nützlich sein könnten (bitte fügen Sie Kopien und keine Originale bei). + + Mehrere Personen waren Zeugen der oben genannten Straftat. Es handelt sich um Frau/Herr (Vor- und Nachname). + + Bitte nehmen Sie, sehr geehrte Frau, sehr geehrter Herr Staatsanwalt, den Ausdruck meiner besten Gefühle an. + + Unterschrift des Polizeibeamten]], + ["unknown"] = "Unbekannt", + ["delete"] = "Löschen", + ["at"] = "Die", + ["accusedName"] = "Angeklagter Name", + ["save"] = "Speichern", + ["prosecutor"] = "Staatsanwaltschaft", + ["accused"] = "Anerkannt", + ["date"] = "Datum", + ["see"] = "Schauen", + ["username2"] = "Name der Person", + ["allComplaints"] = "Alle Beschwerden", + ["moveX"] = "Verschiebung der X-Achse", + ["moveY"] = "Verschiebung der Y-Achse", + ["moveZ"] = "Verschiebung der Z-Achse", + ["rotateX"] = "Drehung der X-Achse", + ["rotateY"] = "Drehung der Y-Achse", + ["rotateZ"] = "Drehung der Z-Achse", + ["rotateYY"] = "Drehung der Y-Achse", + ["width"] = "Breite", + ["height"] = "Höhe", + ["configureLicenseplate"] = "Wählen Sie das Fahrzeug \n um das Nummernschild zu konfigurieren", + ["placeLicenseplate"] = "Bringen Sie das (1) Nummernschild \n Ihrem Fahrzeug an einem geeigneten Platz \n.", + ["placeLicenseplate2"] = "Bringen Sie das (2) Nummernschild \n Ihrem Fahrzeug an einem geeigneten Platz \n.", + ["undefined"] = "Sie haben nicht den richtigen Job, um auf den Computer zuzugreifen", + ["youWon"] = "Du hast gewonnen", + ["youAddPenality"] = "Sie haben eine Strafe hinzugefügt", + ["cameraCenter"] = "Kamera-Zentrum", + ["cameraSecurityOfCity"] = "Sicherheitskamera der Stadt", + ["switchCamera"] = "Drücken Sie USE um die Kamera zu wechseln", + ["cantDelete"] = "Sie können das nicht löschen", + ["cantDoThat"] = "Das kannst du nicht tun", + ["noSignal"] = "KEIN SIGNAL", + ["connectionProblem"] = "Problem mit der Verbindung", + ["computerMustBeHacked"] = "Der Computer muss gehackt werden, um eine Anwendung zu öffnen", + ["cantAffordFine"] = "Sie haben nicht genug Geld, um die Strafe zu bezahlen", + ["payFine"] = "Bezahlen Sie die Geldstrafe", + ["confirm"] = "BESTÄTIGEN", + ["category"] = "KATEGORIE", + ["fineBook"] = "Schönes Buch", + ["noPayFine"] = "Hat die Geldstrafe nicht bezahlt", + ["personAlreadyHaveFine"] = "Diese Person hat bereits eine Geldstrafe", + ["vehicleHaveFine"] = "Dieses Fahrzeug hat bereits eine Geldstrafe", + ["vehicleCantReceiveFine"] = "Dieses Fahrzeug kann keine Geldstrafe haben", + ["noGoodJob"] = "Sie haben nicht den richtigen Job, um eine Geldstrafe zu zahlen", + ["userCantReceiveFine"] = "Sie können diesem Spieler keine Geldstrafe auferlegen", + ["vehicleCantHaveFine"] = "Bei diesem Fahrzeug können keine Bußgelder erhoben werden.", + ["richSanctions"] = "Sie können weitere Strafen verhängen", + ["mustSelectPenality"] = "Sie müssen eine Strafe wählen", + ["cantAddFineToHimVehicle"] = "Sie können Ihr Fahrzeug nicht mit einem Bußgeld belegen", + ["noGoodJobToArrestuser"] = "Sie haben nicht den richtigen Job, um diese Person zu verhaften", + ["cantArrestPlayer"] = "Sie können diese Person nicht verhaften", + ["needHoldPlayerToArrest"] = "Du musst einen Spieler ins Gefängnis bringen", + ["justPaid"] = "Du hast gerade bezahlt", + ["cantAfford"] = "Sie haben nicht genug Geld für so etwas", + ["arrestMenu"] = "Menü Verhaftung", + ["name"] = "Name", + ["price"] = "Preis", + ["reason"] = "Motiv", + ["pay"] = "Bezahlen", + ["arrestedPlayersList"] = "Liste der Verhafteten", + ["noArrestedPlayer"] = "Es wurde kein Spieler verhaftet", + ["noJailPositions"] = "Es gibt keine Gefängnis position, kontaktiere einen Admin", + ["weaponsList"] = "Liste der Waffen", + ["confiscate"] = "KONFISZIEREN", + ["youSurrender"] = "Du gibst Auf", + ["youArrest"] = "Sie sind verhaftet", + ["years"] = "Jahre", + ["tooMuchEntities"] = "Sie haben zu viele Entities", + ["spawnPropText"] = "Drücken Sie [USE], um ein Prop zu erzeugen.", + ["youUnarrestBy"] = "Sie wurden freigelassen von", + ["youArrestBy"] = "Sie wurden verhaftet von", + ["youHaveArrest"] = "Sie haben verhaftet", + ["policeCartrunk"] = "Polizei Kofferraum", + ["cantDeleteSelfSanctions"] = "Du kannst deine Strafe nicht löschen", + ["cantEditSelfSanctions"] = "Sie können Ihre Strafe nicht bearbeiten", + ["seconds"] = "Sekunden", + ["cameraName"] = "Name der Kamera", + ["cameraConfiguration"] = "Kamera-Konfiguration", + ["press"] = "Druk op" +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_en.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_en.lua new file mode 100644 index 0000000..d6ece8f --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_en.lua @@ -0,0 +1,112 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["en"] = { + ["criminalRecord"] = "Criminal Record", + ["recordOf"] = "Locker of", + ["username"] = "Name of the Person", + ["addUpperCase"] = "ADD", + ["infractionReason"] = "Reason for the offence", + ["create"] = "Create", + ["cancel"] = "Cancel", + ["licenseplate"] = "License Plate", + ["search"] = "Search", + ["noVehiclesFound"] = "No vehicles found", + ["reportText"] = [[Madam, Mr. Prosecutor, + + I, the undersigned (name and surname), born on date of birth in place of birth and practicing the profession of (profession), inform you that I am filing a complaint against (name and surname / name of the company / name of the organization / or X if unknown), residing at (name the address of the person or company concerned) for (qualification of the offence). + + On (date of the event), I was indeed the victim of the following facts: (indicate as precisely as possible the details of the facts as well as the place where they occurred, the time of the event and the persons involved in the infraction). I enclose with this letter any documents / photos / evidence that may be useful in the future of this case (please attach copies and not originals). + + Several people witnessed the above-mentioned offence. They are Mrs/Mr (first and last names). + + Please accept, Madam, Mr. Prosecutor, the expression of my best feelings. + + Signature of the Police Officer]], + ["unknown"] = "Unknown", + ["delete"] = "Delete", + ["at"] = "The", + ["accusedName"] = "Accused Name", + ["save"] = "Save", + ["prosecutor"] = "Prosecutor", + ["accused"] = "Acknowledged", + ["date"] = "Date", + ["see"] = "See", + ["username2"] = "Name of the Person", + ["allComplaints"] = "All complaints", + ["moveX"] = "Displacement X-axis", + ["moveY"] = "Displacement Y-axis", + ["moveZ"] = "Displacement Z-axis", + ["rotateX"] = "Rotation X-axis", + ["rotateY"] = "Rotation Y-axis", + ["rotateZ"] = "Rotation Z-axis", + ["rotateYY"] = "Rotation Y-axis", + ["width"] = "Width", + ["height"] = "Height", + ["configureLicenseplate"] = "Select the vehicle \n for configure the \n license plate", + ["placeLicenseplate"] = "Place the (1) License \n Plate in the good place \n of your vehicle", + ["placeLicenseplate2"] = "Place the (2) License \n Plate in the good place \n of your vehicle", + ["undefined"] = "You don't have the right job to access on the computer", + ["youWon"] = "You just won", + ["youAddPenality"] = "You have added a penalty", + ["cameraCenter"] = "Camera Center", + ["cameraSecurityOfCity"] = "Security Camera of the City", + ["switchCamera"] = "Press Use for Change Camera", + ["cantDelete"] = "You can't delete this", + ["cantDoThat"] = "You can't do this", + ["noSignal"] = "NO SIGNAL", + ["connectionProblem"] = "Problem with the Connexion", + ["computerMustBeHacked"] = "The computer have to be hacked for open an application", + ["cantAffordFine"] = "You don't have enough money for pay the fine", + ["payFine"] = "Pay the Fine", + ["confirm"] = "CONFIRM", + ["category"] = "CATEGORY", + ["fineBook"] = "Fine Book", + ["noPayFine"] = "Did not pay the fine", + ["personAlreadyHaveFine"] = "This person have already a fine", + ["vehicleHaveFine"] = "This vehicle have already a fine", + ["vehicleCantReceiveFine"] = "This vehicle can't have a fine", + ["noGoodJob"] = "You don't have the right job for add a fine", + ["userCantReceiveFine"] = "You can't add a fine to this player", + ["vehicleCantHaveFine"] = "You can't add fine on this vehicle", + ["richSanctions"] = "You can add more penalty", + ["mustSelectPenality"] = "You have to select a penalty", + ["cantAddFineToHimVehicle"] = "You can't add a fine to your vehicle", + ["noGoodJobToArrestuser"] = "You don't have the right job to arrest this person", + ["cantArrestPlayer"] = "You can't arrest this person", + ["needHoldPlayerToArrest"] = "You must drag a player for jail someone", + ["justPaid"] = "You just paid", + ["cantAfford"] = "You don't have enought money for do this", + ["arrestMenu"] = "Arrest Menu", + ["name"] = "Name", + ["price"] = "Price", + ["reason"] = "Motif", + ["pay"] = "Pay", + ["arrestedPlayersList"] = "List of Arrested", + ["noArrestedPlayer"] = "There is no player arrested", + ["noJailPositions"] = "There is no jail position contact an administrator", + ["weaponsList"] = "List of Weapons", + ["confiscate"] = "CONFISCATE", + ["youSurrender"] = "You are Surrender", + ["youArrest"] = "You are Arrested", + ["years"] = "Years", + ["tooMuchEntities"] = "You have too many entities", + ["spawnPropText"] = "Press [USE] to spawn the prop", + ["youUnarrestBy"] = "You were released by", + ["youArrestBy"] = "You were arrested by", + ["youHaveArrest"] = "You arrested", + ["policeCartrunk"] = "Police Trunk", + ["cantDeleteSelfSanctions"] = "You can't delete your penalty", + ["cantEditSelfSanctions"] = "You can't edit your penalty", + ["seconds"] = "Seconds", + ["cameraName"] = "Name of the Camera", + ["cameraConfiguration"] = "Camera Configuration" +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_es.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_es.lua new file mode 100644 index 0000000..df17aa4 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_es.lua @@ -0,0 +1,107 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["es"] = { + ["criminalRecord"] = "Antecedentes penales", + ["recordOf"] = "Taquilla de", + ["username"] = "Nombre de la persona", + ["addUpperCase"] = "AÑADIR", + ["infractionReason"] = "Motivo de la infracción", + ["create"] = "Crear", + ["cancel"] = "Cancel", + ["licenseplate"] = "Placa de matrícula", + ["search"] = "Buscar", + ["noVehiclesFound"] = "No se han encontrado vehículos", + ["reportText"] = [[Señora, Señor Fiscal, + El abajo firmante (nombre y apellidos), nacido en fecha en lugar de nacimiento y ejerciendo la profesión de (profesión), le comunico que interpongo denuncia contra (nombre y apellidos / nombre de la empresa / nombre de la organización / o X si se desconoce), con domicilio en (indicar la dirección de la persona o empresa afectada) por (calificación del delito). + El (fecha del suceso), fui efectivamente víctima de los siguientes hechos: (indicar con la mayor precisión posible los detalles de los hechos, así como el lugar donde ocurrieron, la hora del suceso y las personas implicadas en la infracción). Adjunto a esta carta cualquier documento / foto / prueba que pueda ser útil en el futuro de este caso (por favor, adjunte copias y no originales). + Varias personas fueron testigos de la infracción mencionada. Se trata de la Sra./el Sr. (nombre y apellidos). + Le ruego que acepte, Señora, Señor Fiscal, la expresión de mis mejores sentimientos. + Firma del funcionario de policía]], + ["unknown"] = "Desconocido", + ["delete"] = "Eliminar", + ["at"] = "En", + ["accusedName"] = "Nombre del acusado", + ["save"] = "Guardar", + ["prosecutor"] = "Fiscal", + ["accused"] = "Reconocido", + ["date"] = "Fecha", + ["see"] = "Ver", + ["username2"] = "Nombre de la persona", + ["allComplaints"] = "Todas las denuncias", + ["moveX"] = "Desplazamiento Eje X", + ["moveY"] = "Desplazamiento Eje Y", + ["moveZ"] = "Desplazamiento Eje Z", + ["rotateX"] = "Rotación eje X", + ["rotateY"] = "Rotación eje Y", + ["rotateZ"] = "Rotación eje Z", + ["rotateYY"] = "Rotación eje Y", + ["width"] = "Anchura", + ["height"] = "Altura", + ["configureLicenseplate"] = "Seleccione el vehículo \n para configurar la \n matrícula", + ["placeLicenseplate"] = "Coloque la (1) placa \n de matrícula en el lugar adecuado \n de su vehículo.", + ["placeLicenseplate2"] = "Coloque la (2) placa \n de matrícula en el lugar adecuado \n de su vehículo.", + ["undefined"] = "No tienes el trabajo adecuado para acceder en el ordenador", + ["youWon"] = "Has ganado", + ["youAddPenality"] = "Ha añadido una sanción", + ["cameraCenter"] = "Centro de cámaras", + ["cameraSecurityOfCity"] = "Cámara de seguridad de la ciudad", + ["switchCamera"] = "Pulse Usar para cambiar de cámara", + ["cantDelete"] = "No puedes eliminar esto", + ["cantDoThat"] = "No puedes hacer esto", + ["noSignal"] = "SIN SEÑAL", + ["connectionProblem"] = "Problema con la conexión", + ["computerMustBeHacked"] = "Hay que piratear el ordenador para abrir una aplicación", + ["cantAffordFine"] = "No tienes suficiente dinero para pagar la multa", + ["payFine"] = "Pagar la multa", + ["confirm"] = "CONFIRMAR", + ["category"] = "CATEGORÍA", + ["fineBook"] = "Libro de multas", + ["noPayFine"] = "No pagó la multa", + ["personAlreadyHaveFine"] = "Esta persona ya tiene una multa", + ["vehicleHaveFine"] = "Este vehículo ya tiene una multa", + ["vehicleCantReceiveFine"] = "Este vehículo no puede tener una multa", + ["noGoodJob"] = "No tienes el trabajo adecuado para añadir una multa", + ["userCantReceiveFine"] = "No se puede añadir una multa a este jugador", + ["vehicleCantHaveFine"] = "No se puede añadir multa en este vehículo", + ["richSanctions"] = "Puede añadir más sanciones", + ["mustSelectPenality"] = "Tienes que seleccionar una sanción", + ["cantAddFineToHimVehicle"] = "No puedes añadir una multa a tu vehículo", + ["noGoodJobToArrestuser"] = "No tienes el trabajo adecuado para detener a esta persona", + ["cantArrestPlayer"] = "No puedes arrestar a esta persona", + ["needHoldPlayerToArrest"] = "Debes arrastrar al jugador para encarcelarlo", + ["justPaid"] = "Acabas de pagar", + ["cantAfford"] = "No tienes suficiente dinero para hacer esto", + ["arrestMenu"] = "Menú de arrestos", + ["name"] = "Nombre", + ["price"] = "Precio", + ["reason"] = "Motivo", + ["pay"] = "Pagar", + ["arrestedPlayersList"] = "Lista de arrestados", + ["noArrestedPlayer"] = "No hay ningún jugador detenido", + ["noJailPositions"] = "No hay ninguna celda colocada, contacta con un administrador", + ["weaponsList"] = "Lista de armas", + ["confiscate"] = "CONFISCAR", + ["youSurrender"] = "Te has rendido", + ["youArrest"] = "Te han arrestado", + ["years"] = "Años", + ["tooMuchEntities"] = "Tienes demasiadas entidades", + ["spawnPropText"] = "Pulsa [USAR] para generar un prop", + ["youUnarrestBy"] = "Has sido liberado por", + ["youArrestBy"] = "Has sido arrestado por", + ["youHaveArrest"] = "Estas arrestado", + ["policeCartrunk"] = "Maletero policial", + ["cantDeleteSelfSanctions"] = "No puedes eliminar tu sanción", + ["cantEditSelfSanctions"] = "No puedes editar tu sanción", + ["seconds"] = "Segundos", + ["cameraName"] = "Nombre de la cámara", + ["cameraConfiguration"] = "Configuración de la cámara" +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_fr.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_fr.lua new file mode 100644 index 0000000..3cc3afd --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_fr.lua @@ -0,0 +1,112 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["fr"] = { + ["criminalRecord"] = "Casier Judiciaire", + ["recordOf"] = "Casier de", + ["username"] = "Nom de la Personne", + ["addUpperCase"] = "AJOUTER", + ["infractionReason"] = "Motif de l'infraction", + ["create"] = "Créer", + ["cancel"] = "Annuler", + ["licenseplate"] = "Plaque D'immatriculation", + ["search"] = "Rechercher", + ["noVehiclesFound"] = "Aucun Véhicule Trouvé", + ["reportText"] = [[Madame, Monsieur le Procureur, + + Je soussigné(e) (nom et prénom), né(e) le date de naissance à lieu de naissance et exerçant la profession de (profession), vous informe porter plainte contre (nom et prénom / nom de l'entreprise / nom de l'organisation / ou X si personne non connue), résidant à (nommer l'adresse de la personne ou de l'entreprise concernée) pour (qualification de l'infraction). + + En date du (date de l'événement), j'ai en effet été victime des faits suivants : (indiquer le plus précisément possible le détail des faits ainsi que le lieu où ils se sont produits, l'heure de l'événement et les personnes impliquées dans l'infraction). Je joins à cette lettre les documents / photos / preuves qui pourraient se révéler utiles dans la suite de cette affaire (attention à joindre des copies et non des originaux). + + Plusieurs personnes ont été témoins de l'infraction mentionnée ci-dessus. Il s'agit de Madame / Monsieur (noms et prénoms). + + Je vous prie d'agréer, Madame, Monsieur le Procureur, l'expression de mes sentiments distingués. + + Signature du Policier]], + ["unknown"] = "Inconnu", + ["delete"] = "Supprimer", + ["at"] = "Le", + ["accusedName"] = "Nom de L'Accusé", + ["save"] = "Sauvegarder", + ["prosecutor"] = "Procureur", + ["accused"] = "Accusé", + ["date"] = "Date", + ["see"] = "Voir", + ["username2"] = "Nom de la Personne", + ["allComplaints"] = "Toutes les Plaintes", + ["moveX"] = "Déplacement Axe-X", + ["moveY"] = "Déplacement Axe-Y", + ["moveZ"] = "Déplacement Axe-Z", + ["rotateX"] = "Rotation Axe-X", + ["rotateY"] = "Rotation Axe-Y", + ["rotateZ"] = "Rotation Axe-Z", + ["rotateYY"] = "Rotation Axe-Y", + ["width"] = "Largeur", + ["height"] = "Hauteur", + ["configureLicenseplate"] = "Sélectioner le véhicule \n pour configurer la \n plaque d'imatriculation", + ["placeLicenseplate"] = "Placer la (1) Plaque \n D'imatriculation au bon endroit \n sur votre vehicule", + ["placeLicenseplate2"] = "Placer la (2) Plaque \n D'imatriculation au bon endroit \n sur votre vehicule", + ["undefined"] = "Vous n'avez pas le bon métier pour ouvrir l'ordinateur", + ["youWon"] = "Vous avez gagné", + ["youAddPenality"] = "Vous avez ajouté une pénalité", + ["cameraCenter"] = "Centrale des caméras", + ["cameraSecurityOfCity"] = "Caméras de Sécurité de la ville", + ["switchCamera"] = "Appuyer sur [UTILISER] pour changer de caméra", + ["cantDelete"] = "Vous ne pouvez pas supprimer ceci", + ["cantDoThat"] = "Vous ne pouvez pas faire cela", + ["noSignal"] = "AUCUN SIGNAL", + ["connectionProblem"] = "Problème de Connexion", + ["computerMustBeHacked"] = "L'ordinateur doit être hacké pour ouvrir une application", + ["cantAffordFine"] = "Vous n'avez pas assez d'argent pour payer l'amende", + ["payFine"] = "Payer l'Amende", + ["confirm"] = "CONFIRMER", + ["category"] = "CATEGORIE", + ["fineBook"] = "Carnet D'Amendes", + ["noPayFine"] = "N'a pas payé l'amende", + ["personAlreadyHaveFine"] = "Cette personne a déjà une amende", + ["vehicleHaveFine"] = "Ce vehicule a déjà une amende", + ["vehicleCantReceiveFine"] = "Ce vehicule ne peut pas recevoir d'amende", + ["noGoodJob"] = "Vous n'avez pas le bon métier pour ajouter une amende", + ["userCantReceiveFine"] = "Cette personne ne peut pas recevoir d'amende", + ["vehicleCantHaveFine"] = "Ce vehicule ne peut pas recevoir d'amende", + ["richSanctions"] = "Vous avez atteint la limite de sanction", + ["mustSelectPenality"] = "Vous devez sélectionner une pénalité", + ["cantAddFineToHimVehicle"] = "Vous ne pouvez pas ajouter une amende à votre vehicule", + ["noGoodJobToArrestuser"] = "Vous n'avez pas le bon métier pour arrêter cette personne", + ["cantArrestPlayer"] = "Vous ne pouvez pas arrêter cette personne", + ["needHoldPlayerToArrest"] = "Vous devez tenir une personne menottée pour la mettre en prison", + ["justPaid"] = "Vous venez de payer", + ["cantAfford"] = "Vous n'avez pas assez d'argent pour faire ceci", + ["arrestMenu"] = "Menu d'Arrestation", + ["name"] = "Nom", + ["price"] = "Prix", + ["reason"] = "Motif", + ["pay"] = "Payer", + ["arrestedPlayersList"] = "Liste des Prisonniers", + ["noArrestedPlayer"] = "Aucune personne n'est arrêtée", + ["noJailPositions"] = "Il n'y a aucune position de prison. Veuillez contacter un administrateur", + ["weaponsList"] = "Liste des Armes", + ["confiscate"] = "CONFISQUER", + ["youSurrender"] = "Vous vous rendez", + ["youArrest"] = "Vous êtes arrêté", + ["years"] = "Années", + ["tooMuchEntities"] = "Vous avez trop d'entités", + ["spawnPropText"] = "Appuyez sur [UTILISER] pour faire apparaître le prop", + ["youUnarrestBy"] = "Vous avez été libéré par", + ["youArrestBy"] = "Vous avez été arrêté par", + ["youHaveArrest"] = "Vous avez arrêté", + ["policeCartrunk"] = "Coffre de Police", + ["cantDeleteSelfSanctions"] = "Vous ne pouvez pas supprimer une de vos sanctions", + ["cantEditSelfSanctions"] = "Vous ne pouvez pas éditer une de vos sanctions", + ["seconds"] = "Secondes", + ["cameraName"] = "Nom de la Caméra", + ["cameraConfiguration"] = "Configuration des Caméras" +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_it.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_it.lua new file mode 100644 index 0000000..11f15b2 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_it.lua @@ -0,0 +1,112 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["it"] = { + ["criminalRecord"] = "Casellario Giudiziario", + ["recordOf"] = "Casellario di", + ["username"] = "Nome dell'Utente", + ["addUpperCase"] = "AGGIUNGERE", + ["infractionReason"] = "Motivo dell'Infrazione", + ["create"] = "Creare", + ["cancel"] = "Annullare", + ["licenseplate"] = "Targa", + ["search"] = "Cerca", + ["noVehiclesFound"] = "Nessun Veicolo Trovato", + ["reportText"] = [[Signore, Signora Procuratore, + + Io sottoscritto(a) (nome e cognome), nato(a) il data di nascita a luogo di nascita e che esercito la professione di (professione), informo di voler sporgere denuncia contro (nome e cognome / nome dell'azienda / nome dell'organizzazione / o X se persona sconosciuta), residente a (indirizzare della persona o dell'azienda coinvolta) per (qualifica dell'infrazione). + + In data (data dell'evento), sono stato vittima dei seguenti fatti: (descrivere il più precisamente possibile i dettagli dei fatti così come il luogo dove sono accaduti, l'ora dell'evento e le persone coinvolte nell'infrazione). Allego a questa lettera i documenti / foto / prove che potrebbero rivelarsi utili nel proseguo di questa causa (attenzione a allegare copie e non originali). + + Diverse persone hanno assistito all'infrazione sopra menzionata. Si tratta di Signora / Signore (nomi e cognomi). + + Prego di accettare, Signore, Signora Procuratore, l'espressione dei miei distinti saluti. + + Firma del Poliziotto]], + ["unknown"] = "Sconosciuto", + ["delete"] = "Elimina", + ["at"] = "Il", + ["accusedName"] = "Nome dell'Accusato", + ["save"] = "Salva", + ["prosecutor"] = "Procuratore", + ["accused"] = "Accusato", + ["date"] = "Data", + ["see"] = "Vedi", + ["username2"] = "Nome dell'Utente", + ["allComplaints"] = "Tutti i Reclami", + ["moveX"] = "Spostamento Asse-X", + ["moveY"] = "Spostamento Asse-Y", + ["moveZ"] = "Spostamento Asse-Z", + ["rotateX"] = "Rotazione Asse-X", + ["rotateY"] = "Rotazione Asse-Y", + ["rotateZ"] = "Rotazione Asse-Z", + ["rotateYY"] = "Rotazione Asse-Y", + ["width"] = "Larghezza", + ["height"] = "Altezza", + ["configureLicenseplate"] = "Seleziona il veicolo \n per configurare la \n targa", + ["placeLicenseplate"] = "Posiziona la (1) Targa \n nel posto giusto \n sul tuo veicolo", + ["placeLicenseplate2"] = "Posiziona la (2) Targa \n nel posto giusto \n sul tuo veicolo", + ["undefined"] = "Non hai il giusto mestiere per aprire il computer", + ["youWon"] = "Hai vinto", + ["youAddPenality"] = "Hai aggiunto una penalità", + ["cameraCenter"] = "Centrale delle telecamere", + ["cameraSecurityOfCity"] = "Telecamere di Sicurezza della città", + ["switchCamera"] = "Premi [UTILIZZARE] per cambiare telecamera", + ["cantDelete"] = "Non puoi eliminare questo", + ["cantDoThat"] = "Non puoi fare questo", + ["noSignal"] = "NESSUN SEGNALE", + ["connectionProblem"] = "Problema di Connessione", + ["computerMustBeHacked"] = "Il computer deve essere hackerato per aprire un'applicazione", + ["cantAffordFine"] = "Non hai abbastanza soldi per pagare la multa", + ["payFine"] = "Paga la Multa", + ["confirm"] = "CONFERMA", + ["category"] = "CATEGORIA", + ["fineBook"] = "Libretto delle Multe", + ["noPayFine"] = "Non ha pagato la multa", + ["personAlreadyHaveFine"] = "Questa persona ha già una multa", + ["vehicleHaveFine"] = "Questo veicolo ha già una multa", + ["vehicleCantReceiveFine"] = "Questo veicolo non può ricevere multe", + ["noGoodJob"] = "Non hai il mestiere giusto per aggiungere una multa", + ["userCantReceiveFine"] = "Questa persona non può ricevere multe", + ["vehicleCantHaveFine"] = "Questo veicolo non può avere multe", + ["richSanctions"] = "Hai raggiunto il limite di sanzioni", + ["mustSelectPenality"] = "Devi selezionare una penalità", + ["cantAddFineToHimVehicle"] = "Non puoi aggiungere una multa al tuo veicolo", + ["noGoodJobToArrestuser"] = "Non hai il mestiere giusto per arrestare questa persona", + ["cantArrestPlayer"] = "Non puoi arrestare questa persona", + ["needHoldPlayerToArrest"] = "Devi trattenere una persona ammanettata per metterla in prigione", + ["justPaid"] = "Hai appena pagato", + ["cantAfford"] = "Non hai abbastanza soldi per fare questo", + ["arrestMenu"] = "Menu di Arresto", + ["name"] = "Nome", + ["price"] = "Prezzo", + ["reason"] = "Motivo", + ["pay"] = "Paga", + ["arrestedPlayersList"] = "Lista dei Prigionieri", + ["noArrestedPlayer"] = "Nessuna persona è arrestata", + ["noJailPositions"] = "Non ci sono posizioni di prigione. Si prega di contattare un amministratore", + ["weaponsList"] = "Lista delle Armi", + ["confiscate"] = "CONFISCA", + ["youSurrender"] = "Ti arrendi", + ["youArrest"] = "Sei arrestato", + ["years"] = "Anni", + ["tooMuchEntities"] = "Hai troppe entità", + ["spawnPropText"] = "Premi su [UTILIZZARE] per far apparire il prop", + ["youUnarrestBy"] = "Sei stato liberato da", + ["youArrestBy"] = "Sei stato arrestato da", + ["youHaveArrest"] = "Hai arrestato", + ["policeCartrunk"] = "Baule della Polizia", + ["cantDeleteSelfSanctions"] = "Non puoi eliminare una delle tue sanzioni", + ["cantEditSelfSanctions"] = "Non puoi modificare una delle tue sanzioni", + ["seconds"] = "Secondi", + ["cameraName"] = "Nome della Camera", + ["cameraConfiguration"] = "Configurazione delle Camere" +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_ptbr.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_ptbr.lua new file mode 100644 index 0000000..a34a65a --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_ptbr.lua @@ -0,0 +1,113 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["ptbr"] = { + ["criminalRecord"] = "Registro criminal", + ["recordOf"] = "Armário de", + ["username"] = "Nome da Pessoa", + ["addUpperCase"] = "ADICIONAR", + ["infractionReason"] = "Motivo da ofensa", + ["create"] = "Criar", + ["cancel"] = "Cancelar", + ["licenseplate"] = "Placa de carro", + ["search"] = "Procurar", + ["noVehiclesFound"] = "Nenhum veículo encontrado", + ["reportText"] = [[Senhora, senhor promotor, + + Eu, abaixo assinado (nome e sobrenome), nascido na data de nascimento no local de nascimento e exercendo a profissão de (profissão), informo que estou apresentando reclamação contra (nome e sobrenome/nome da empresa/nome do organização / ou X se desconhecido), residente em (indicar o endereço da pessoa ou empresa em causa) para (qualificação da infracção). + + Em (data do facto), fui efectivamente vítima dos seguintes factos: (indicar com a maior precisão possível os detalhes dos factos, bem como o local onde ocorreram, a hora do facto e as pessoas envolvidas na infracção ). Anexo a esta carta quaisquer documentos/fotos/evidências que possam ser úteis no futuro deste caso (anexe cópias e não originais). + + Várias pessoas testemunharam o crime acima mencionado. Eles são Sra/Sr (nome e sobrenome). + + Por favor, aceite, Senhora, Senhor Promotor, a expressão dos meus melhores sentimentos. + + Assinatura do Policial]], + ["unknown"] = "Desconhecido", + ["delete"] = "Excluir", + ["at"] = "O", + ["accusedName"] = "Nome do Acusado", + ["save"] = "Salvar", + ["prosecutor"] = "Promotor", + ["accused"] = "Reconhecido", + ["date"] = "Data", + ["see"] = "Ver", + ["username2"] = "Nome da Pessoa", + ["allComplaints"] = "Todas as reclamações", + ["moveX"] = "Deslocamento Eixo X", + ["moveY"] = "Deslocamento Eixo Y", + ["moveZ"] = "Deslocamento Eixo Z", + ["rotateX"] = "Rotação Eixo X", + ["rotateY"] = "Rotação Eixo Y", + ["rotateZ"] = "Rotação Eixo Z", + ["rotateYY"] = "Rotação Eixo Y", + ["width"] = "Largura", + ["height"] = "Altura", + ["configureLicenseplate"] = "Selecione o veículo \n para configurar a \n licença da placa", + ["placeLicenseplate"] = "Coloque a (1) Licença \n Placa no lugar certo \n do seu veículo", + ["placeLicenseplate2"] = "Coloque a (2) Licença \n Placa no lugar certo \n do seu veículo", + ["undefined"] = "Você não tem o emprego certo para acessar o computador", + ["youWon"] = "Você acabou de ganhar", + ["youAddPenality"] = "Você adicionou uma penalidade", + ["cameraCenter"] = "Centro de Câmeras", + ["cameraSecurityOfCity"] = "Câmera de Segurança da Cidade", + ["switchCamera"] = "Pressione Usar para alterar a câmera", + ["cantDelete"] = "Você não pode excluir isso", + ["cantDoThat"] = "Você não pode fazer isso", + ["noSignal"] = "SEM SINAL", + ["connectionProblem"] = "Problema com na conexão", + ["computerMustBeHacked"] = "O computador precisa ser hackeado para abrir um aplicativo", + ["cantAffordFine"] = "Você não tem dinheiro suficiente para pagar a multa", + ["payFine"] = "Pague a multa", + ["confirm"] = "CONFIRMAR", + ["category"] = "CATEGORIA", + ["fineBook"] = "Livro fino", + ["noPayFine"] = "Não pagou a multa", + ["personAlreadyHaveFine"] = "Essa pessoa já possui uma multa", + ["vehicleHaveFine"] = "Este veículo já esta multado", + ["vehicleCantReceiveFine"] = "Este veículo não pode ter multa", + ["noGoodJob"] = "Você não tem o emprego certo para adicionar uma multa", + ["userCantReceiveFine"] = "Você não pode adicionar uma multa a este jogador", + ["vehicleCantHaveFine"] = "Você não pode adicionar uma multa neste veículo", + ["richSanctions"] = "Você pode adicionar mais penalidade", + ["mustSelectPenality"] = "Você tem que selecionar uma penalidade", + ["cantAddFineToHimVehicle"] = "Você não pode adicionar uma multa ao seu veículo", + ["noGoodJobToArrestuser"] = "Você não tem o emprego certo para prender essa pessoa", + ["cantArrestPlayer"] = "Você não pode prender essa pessoa", + ["needHoldPlayerToArrest"] = "Você deve segurar um jogador para poder prender alguém", + ["justPaid"] = "Você acabou de pagar", + ["cantAfford"] = "Você não tem dinheiro suficiente para fazer isso", + ["arrestMenu"] = "Menu de Prisão", + ["name"] = "Nome", + ["price"] = "Preço", + ["reason"] = "Motivo", + ["pay"] = "Pagar", + ["arrestedPlayersList"] = "Lista de presos", + ["noArrestedPlayer"] = "Não há nenhum jogador preso", + ["noJailPositions"] = "Não há uma posição na prisão, entre em contato com um administrador", + ["weaponsList"] = "Lista de Armas", + ["confiscate"] = "CONFISCAR", + ["youSurrender"] = "Você está se rendendo", + ["youArrest"] = "Você está preso", + ["years"] = "Anos", + ["tooMuchEntities"] = "Você tem muitas entidades", + ["spawnPropText"] = "Pressione [USE] para gerar o prop", + ["youUnarrestBy"] = "Você foi liberado por", + ["youArrestBy"] = "Você foi preso por", + ["youHaveArrest"] = "Você foi Preso", + ["policeCartrunk"] = "Porta-malas da Polícia", + ["cantDeleteSelfSanctions"] = "Você não pode excluir sua penalidade", + ["cantEditSelfSanctions"] = "Você não pode editar sua penalidade", + ["seconds"] = "Segundos", + ["cameraName"] = "Nome da câmera", + ["cameraConfiguration"] = "Configuração da câmera" +} +// PT-BR by: pop pic (https://www.gmodstore.com/users/poppic) + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_ru.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_ru.lua new file mode 100644 index 0000000..e811127 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_ru.lua @@ -0,0 +1,126 @@ +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["ru"] = { + ["criminalRecord"] = "База данных ФСБ (АСД)", + ["criminalRecord_SBU"] = "База данных СБУ", + ["recordOf"] = "Материалы дела на:", + ["username"] = "ФИО гражданина", + ["addUpperCase"] = "ВНЕСТИ", + ["infractionReason"] = "Статья/Правонарушение", + ["create"] = "Создать", + ["cancel"] = "Отмена", + ["licenseplate"] = "Регистрационный номер", + ["search"] = "Поиск по базе", + ["noVehiclesFound"] = "Транспорт не найден", + ["reportText"] = [[Начальнику Управления ФСБ России по г. Херсону + + Я, нижеподписавшийся (ФИО), (дата рождения) г.р., место рождения (место рождения), род занятий (должность/звание), уведомляю Вас о подаче официальной жалобы на (ФИО / Название организации / или 'Неизвестное лицо'), проживающего/расположенного по адресу (адрес) в связи с совершением (квалификация нарушения/преступления). + + (Дата происшествия) я стал свидетелем/жертвой следующих событий: (укажите детали фактов, место, время и лиц, причастных к нарушению). К заявлению прилагаю вещественные доказательства / фотоматериалы / документы, имеющие значение для дела. + + Свидетели происшествия: (ФИО свидетелей). + + Об ответственности за заведомо ложный донос по ст. 306 УК РФ предупрежден(а). + + __________________________ + Подпись сотрудника ФСБ]], + ["reportText_SBU"] = [[Начальнику Управления СБУ по г. Херсону + + Я, нижеподписавшийся (ФИО), (дата рождения) г.р., место рождения (место рождения), род занятий (должность/звание), уведомляю Вас о подаче официальной жалобы на (ФИО / Название организации / или 'Неизвестное лицо'), проживающего/расположенного по адресу (адрес) в связи с совершением (квалификация нарушения/преступления). + + (Дата происшествия) я стал свидетелем/жертвой следующих событий: (укажите детали фактов, место, время и лиц, причастных к нарушению). К заявлению прилагаю вещественные доказательства / фотоматериалы / документы, имеющие значение для дела. + + Свидетели происшествия: (ФИО свидетелей). + + Об ответственности за заведомо ложный донос по ст. 383 УК Украины предупрежден(а). + + __________________________ + Подпись сотрудника СБУ]], + ["unknown"] = "Личность не установлена", + ["delete"] = "Удалить", + ["at"] = " ", + ["accusedName"] = "Имя подозреваемого", + ["save"] = "Сохранить", + ["prosecutor"] = "Следователь", + ["accused"] = "Подозреваемый", + ["date"] = "Дата внесения", + ["see"] = "Просмотр", + ["username2"] = "ФИО объекта", + ["allComplaints"] = "Все материалы", + ["moveX"] = "Смещение по оси X", + ["moveY"] = "Смещение по оси Y", + ["moveZ"] = "Смещение по оси Z", + ["rotateX"] = "Вращение по оси X", + ["rotateY"] = "Вращение по оси Y", + ["rotateZ"] = "Вращение по оси Z", + ["rotateYY"] = "Вращение по оси Y", + ["width"] = "Ширина", + ["height"] = "Высота", + ["configureLicenseplate"] = "Выберите транспорт \n для настройки \n номерного знака", + ["placeLicenseplate"] = "Установите основной \n номер на корпусе \n транспортного средства", + ["placeLicenseplate2"] = "Установите дублирующий \n номер на корпусе \n транспортного средства", + ["undefined"] = "Доступ ограничен! Требуется авторизация в системе ФСБ", + ["undefined_SBU"] = "Доступ ограничен! Требуется авторизация в системе СБУ", + ["youWon"] = "Зачислено:", + ["youAddPenality"] = "Протокол составлен", + ["cameraCenter"] = "Узел защищенной связи", + ["cameraSecurityOfCity"] = "Оперативная камера наблюдения", + ["switchCamera"] = "Нажмите [ИСПОЛЬЗОВАТЬ] для переключения канала", + ["cantDelete"] = "Недостаточно прав для удаления", + ["cantDoThat"] = "Операция отклонена", + ["noSignal"] = "АБОНЕНТ ВНЕ СЕТИ", + ["connectionProblem"] = "Разрыв соединения", + ["computerMustBeHacked"] = "Требуется обход протоколов безопасности для доступа", + ["cantAffordFine"] = "Недостаточно средств для погашения штрафа", + ["payFine"] = "Погасить штраф", + ["confirm"] = "ПОДТВЕРДИТЬ", + ["category"] = "КАТЕГОРИЯ", + ["fineBook"] = "Бланк протокола", + ["noPayFine"] = "Задолженность не погашена", + ["personAlreadyHaveFine"] = "У объекта уже имеется действующее взыскание", + ["vehicleHaveFine"] = "Транспорт находится под арестом/штрафом", + ["vehicleCantReceiveFine"] = "Данный транспорт не подлежит штрафованию", + ["noGoodJob"] = "У вас нет полномочий ФСБ для выписки протоколов", + ["noGoodJob_SBU"] = "У вас нет полномочий СБУ для выписки протоколов", + ["userCantReceiveFine"] = "Объект обладает неприкосновенностью", + ["vehicleCantHaveFine"] = "Регистрация протокола на данный транспорт невозможна", + ["richSanctions"] = "Объект допускает дополнительные взыскания", + ["mustSelectPenality"] = "Выберите статью нарушения", + ["cantAddFineToHimVehicle"] = "Нельзя выписать штраф на служебный транспорт", + ["noGoodJobToArrestuser"] = "У вас нет полномочий проводить задержание", + ["cantArrestPlayer"] = "Задержание данного лица запрещено", + ["needHoldPlayerToArrest"] = "Необходимо конвоировать задержанного для водворения в ИТТ", + ["justPaid"] = "Штраф погашен:", + ["cantAfford"] = "Недостаточно средств", + ["arrestMenu"] = "Протокол задержания", + ["name"] = "Наименование", + ["price"] = "Сумма", + ["reason"] = "Основание", + ["pay"] = "Оплатить", + ["arrestedPlayersList"] = "Лица под стражей", + ["noArrestedPlayer"] = "Задержанные отсутствуют", + ["noJailPositions"] = "Точки ИТТ не найдены. Свяжитесь со штабом.", + ["weaponsList"] = "Изъятое имущество", + ["confiscate"] = "КОНФИСКОВАТЬ", + ["youSurrender"] = "Вы сложили оружие", + ["youArrest"] = "Вы задержаны сотрудниками ФСБ", + ["youArrest_SBU"] = "Вы задержаны сотрудниками СБУ", + ["years"] = "Суток", + ["tooMuchEntities"] = "Превышен лимит развертывания", + ["spawnPropText"] = "Нажмите [E] для развертывания спецсредства", + ["youUnarrestBy"] = "Вы освобождены распоряжением:", + ["youArrestBy"] = "Вы задержаны сотрудником:", + ["youHaveArrest"] = "Вы задержали объект", + ["policeCartrunk"] = "Багажник спецтранспорта", + ["cantDeleteSelfSanctions"] = "Удаление личных взысканий запрещено", + ["cantEditSelfSanctions"] = "Правка личного дела невозможна", + ["seconds"] = "сек.", + ["cameraName"] = "Позывной камеры", + ["cameraConfiguration"] = "Конфигурация канала связи", + ["press"] = "нажмите", + ["terminalName"] = "Терминал ФСБ", + ["terminalName_SBU"] = "Терминал СБУ", + ["osName"] = "АСД-ФСБ", + ["osName_SBU"] = "АСД-СБУ", +} diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_tr.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_tr.lua new file mode 100644 index 0000000..e06f4c3 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/languages/sh_language_tr.lua @@ -0,0 +1,114 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +Realistic_Police.Language = Realistic_Police.Language or {} + +Realistic_Police.Language["tr"] = { + ["criminalRecord"] = "Sabika Kaydi", + ["recordOf"] = "Dolap sahibi", + ["username"] = "Kisinin Ismi", + ["addUpperCase"] = "EKLE", + ["infractionReason"] = "Sucun nedeni", + ["create"] = "Olustur", + ["cancel"] = "Iptal Et", + ["licenseplate"] = "Plaka", + ["search"] = "Ara", + ["noVehiclesFound"] = "Hicbir arac bulunamadi", + ["reportText"] = [[Bayan, Bay. Savci, + + Ben asagida imzasi bulunan (Isim ve soy isim), (Dogum yeri ve dogum gunu) dogan ve meslegi (mesleginiz) olan bir sikayetciyim. Bu vesile ile (Isim ve Soyisim / sirket ismi / organizasyon ismi / veya bilinmiyorsa X), (sirketin veya sahisin adresi)'nde ikamet eden ve (sucun niteligi). + + (olayin tarihi) tarihinde gerceklesen bu olaydan sikayetciyim, Bu olayin kurbani oldugumu su sebeplerle gostereyim: (yasanan olayi tum detaylarla, mekan isimleri,saatiyle beraber her detayına kadar yazin). Bu davanin kisa surmesinde yardimci olan dokuman/fotograf/kanit ve benzeri dokumanlari suraya birakiyorum (lutfen fotograflari buraya yerlestiriniz). + + Asagida adi gecen birkac sahis olaya taniklik etmislerdir. Bunlar Bay/Bayan (tam adlari), (tanikin istikamet ettigi yer)'da istikamet ediyorlar ve su numaradan ulasilabilir (tanikin telefon numarasi). + + Asagida adi gecen birkac sahis olaya taniklik etmislerdir. Bunlar Bay / Bayan (isim ve soy isimleri). + + Lutfen bu davayi cozmenizi rica ediyorum, Bayan, Bay Savci, size sans dileyip tesekkur ediyorum, iyi gunler. + + Polis Memurunun Imzasi]], + ["unknown"] = "Bilinmeyen", + ["delete"] = "Sil", + ["at"] = "The", + ["accusedName"] = "Sanik Adi", + ["save"] = "Kaydet", + ["prosecutor"] = "Savci", + ["accused"] = "Taninan", + ["date"] = "Tarih", + ["see"] = "Goz At", + ["username2"] = "Kisinin ismi", + ["allComplaints"] = "Tum sikayetler", + ["moveX"] = "Yerlestirmenin X-ekseni", + ["moveY"] = "Yerlestirmenin Y-ekseni", + ["moveZ"] = "Yerlestirmenin Z-ekseni", + ["rotateX"] = "Donmenin X-ekseni", + ["rotateY"] = "Donmenin Y-ekseni", + ["rotateZ"] = "Donmenin Z-ekseni", + ["rotateYY"] = "Donmenin Y-ekseni", + ["width"] = "Genislik", + ["height"] = "Yukseklik", + ["configureLicenseplate"] = "Plakayi yapilandirmak \n icin bir arac seciniz", + ["placeLicenseplate"] = "Plakayi (1) aracin guzel \n ve gorunulebilir yerine \n yerlestirin", + ["placeLicenseplate2"] = "Plakayi (2) aracin guzel \n ve gorunulebilir yerine \n yerlestirin", + ["undefined"] = "Bilgisayara erişmek için doğru işiniz yok", + ["youWon"] = "Kazandin", + ["youAddPenality"] = "Bir ceza ekledin", + ["cameraCenter"] = "Kamera Merkezi", + ["cameraSecurityOfCity"] = "Sehirin guvenlik kamerasi", + ["switchCamera"] = "Kamerayi degistirmek icin kullan tusuna basin", + ["cantDelete"] = "Bunu silemezsin", + ["cantDoThat"] = "Bunu yapamazsin", + ["noSignal"] = "SINYAL YOK", + ["connectionProblem"] = "Baglantida bir problem var", + ["computerMustBeHacked"] = "Bir uygulamayi acmak icin bilgisayari hacklemen gerekir", + ["cantAffordFine"] = "Cezayi odemek icin yeterli paran yok", + ["payFine"] = "Cezayi Ode", + ["confirm"] = "ONAYLA", + ["category"] = "KATEGORI", + ["fineBook"] = "Ceza Defteri", + ["noPayFine"] = "Para Cezasini Odemedi", + ["personAlreadyHaveFine"] = "Bu sahis adina zaten bir para cezasi kesilmis durumda", + ["vehicleHaveFine"] = "Bu arac adina zaten bir para cezasi kesilmis durumda", + ["vehicleCantReceiveFine"] = "Bu araca ceza kesemezsiniz", + ["noGoodJob"] = "Ceza eklemek icin dogru meslekte degilsiniz", + ["userCantReceiveFine"] = "Bu oyuncuya para cezasi kesemezsin", + ["vehicleCantHaveFine"] = "Bu araca para cezasi kesemezsin", + ["richSanctions"] = "Daha fazla ceza kesebilirsin", + ["mustSelectPenality"] = "Bir ceza secmen lazim", + ["cantAddFineToHimVehicle"] = "Kendi aracina ceza kesemezsin", + ["noGoodJobToArrestuser"] = "Bu oyuncuyu tutuklamak icin dogru meslege sahip degilsin", + ["cantArrestPlayer"] = "Bu oyuncuyu tutuklayamazsin", + ["needHoldPlayerToArrest"] = "Birini hapise atmak icin suruklemen lazim", + ["justPaid"] = "Su kadar odedin", + ["cantAfford"] = "Bunu yapmaya yeterli paran yok", + ["arrestMenu"] = "Tutuklama Menusu", + ["name"] = "Isim", + ["price"] = "Ucret", + ["reason"] = "Motif", + ["pay"] = "Ode", + ["arrestedPlayersList"] = "Tutuklananlarin Listesi", + ["noArrestedPlayer"] = "Hicbir oyuncu tutuklanmamis", + ["noJailPositions"] = "Ayarlanmis herhangi bir hapis pozisyonu yok, bir admine ulasin", + ["weaponsList"] = "Silah Listesi", + ["confiscate"] = "EL KOY", + ["youSurrender"] = "Teslim Oldunuz", + ["youArrest"] = "Tutuklusunuz", + ["years"] = "Yil", + ["tooMuchEntities"] = "Birçok varlığa sahipsin", + ["spawnPropText"] = "Pervaneyi oluşturmak için [USE] tuşuna basın", + ["youUnarrestBy"] = "Tarafından serbest bırakıldınız", + ["youArrestBy"] = "Tarafından tutuklandın", + ["youHaveArrest"] = "Tutukladın", + ["policeCartrunk"] = "Polis Sandıgı", + ["cantDeleteSelfSanctions"] = "Penalitesini silemezsin", + ["cantEditSelfSanctions"] = "Penalitesini düzenleyemezsin", + ["seconds"] = "saniye", + ["cameraName"] = "Kamera Adı", + ["cameraConfiguration"] = "Kamera Yapılandırması" +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_function.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_function.lua new file mode 100644 index 0000000..d0e155b --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_function.lua @@ -0,0 +1,636 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} + +function Realistic_Police.GetSpawnPoint(ply) + local ent = hook.Run("PlayerSelectSpawn", ply, false) + + if ent && isfunction(ent.GetPos) then + return ent:GetPos() + elseif DarkRP then + local spawnVector = DarkRP.retrieveTeamSpawnPos(ply:Team()) or {} + + local randomSpawn = math.random(#spawnVector) + + if isvector(spawnVector[randomSpawn]) then + return spawnVector[randomSpawn] + end + end +end + +-- Send a notification to a player +function Realistic_Police.SendNotify(ply, msg) + if not IsValid(ply) or not ply:IsPlayer() then return end + + net.Start("RealisticPolice:Open") + net.WriteString("Notify") + net.WriteString(msg) + net.Send(ply) +end + +-- This function generate a random plate +function Realistic_Police.GeneratePlate(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + local String = Realistic_Police.Letters().." "..Realistic_Police.Number().." "..Realistic_Police.Letters() + + -- Check if the plate does not exist and check if the player don't have already a data + if not Realistic_Police.CheckExist(String) && not Realistic_Police.PlayerData(ply) then + RealisticPoliceTab[ply:SteamID()] = { + ["Plate"] = String, + ["CustomPlate"] = "", + } + file.Write("realistic_police/rpt_plate.txt", util.TableToJSON(RealisticPoliceTab, true)) + elseif not Realistic_Police.PlayerData(ply) then + Realistic_Police.GeneratePlate(ply) + end +end + +function Realistic_Police.ChangePlate(ply, plateName, syncVehc) + if not IsValid(ply) or not ply:IsPlayer() then return end + + local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + RealisticPoliceTab[ply:SteamID()] = { + ["Plate"] = plateName, + ["CustomPlate"] = "", + } + file.Write("realistic_police/rpt_plate.txt", util.TableToJSON(RealisticPoliceTab, true)) + + if IsValid(syncVehc) then Realistic_Police.SetInfoVehc(syncVehc) end +end + +-- Check if the Plate already exist +function Realistic_Police.CheckExist(Plate) + if not isstring(Plate) then return end + + local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + for k,v in pairs(RealisticPoliceTab) do + if v.Plate == Plate then + return true + end + end + return false +end + +-- Check if the player have already a license plate +function Realistic_Police.PlayerData(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + return istable(RealisticPoliceTab[ply:SteamID()]) +end + +-- Get the Plate of the player by his steamid +function Realistic_Police.GetPlate(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + if not istable(RealisticPoliceTab[ply:SteamID()]) then return end + + return RealisticPoliceTab[ply:SteamID()]["Plate"] +end + +-- Compress the Report table and Send the table to client +function Realistic_Police.SendReport(RPTSteamID64, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + local RealisticPoliceFil = file.Read("realistic_police/report/"..RPTSteamID64..".txt", "DATA") or "" + local CompressTable = util.Compress(RealisticPoliceFil) + + net.Start("RealisticPolice:Report") + net.WriteInt(CompressTable:len(), 32) + net.WriteData(CompressTable, CompressTable:len() ) + net.Send(ply) +end + +-- Compress the Criminal Record table and Send the table to client +function Realistic_Police.SendRecord(RPTSteamID64, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + if not IsValid(player.GetBySteamID64(RPTSteamID64)) or not player.GetBySteamID64(RPTSteamID64):IsPlayer() then return end + + local RealisticPoliceFil = file.Read("realistic_police/record/"..RPTSteamID64..".txt", "DATA") or "" + local CompressTable = util.Compress(RealisticPoliceFil) + + net.Start("RealisticPolice:CriminalRecord") + net.WriteInt(CompressTable:len(), 32) + net.WriteData(CompressTable, CompressTable:len()) + net.WriteEntity(player.GetBySteamID64(RPTSteamID64)) + net.Send(ply) +end + +-- Random letters for the license plate +function Realistic_Police.Letters() + return string.char( math.random(65,90 ))..string.char( math.random(65,90 )) +end + +-- Random numbers for the license plate +function Realistic_Police.Number() + return tostring(math.random(1, 9)..math.random(1, 9)..math.random(1, 9)) +end + +function Realistic_Police.CompatibilitiesATM(ply, plyMoney, amountToPay) + if not IsValid(ply) then return end + + local neededAmountFromBank = (amountToPay - plyMoney) + if neededAmountFromBank > 0 then + if CH_ATM && (CH_ATM.GetMoneyBankAccount(ply) >= neededAmountFromBank) then + return true + elseif GlorifiedBanking && (GlorifiedBanking.GetPlayerBalance(ply) >= neededAmountFromBank) then + return true + end + end +end + +function Realistic_Police.PayWithBank(ply, amountToPay) + if not IsValid(ply) then return end + + if CH_ATM then + CH_ATM.TakeMoneyFromBankAccount(ply, amountToPay) + elseif GlorifiedBanking then + GlorifiedBanking.RemovePlayerBalance(ply, amountToPay) + end +end + +function Realistic_Police.SetInfoVehc(ent) + -- Set the License plate of the player on the vehicle + if not IsValid(ent) or not ent:IsVehicle() && ent:GetClass() != "gmod_sent_vehicle_fphysics_base" then return end + + local owner = ent:GetOwner() + + if ent.CPPIGetOwner && not IsValid(owner) then + owner = ent:CPPIGetOwner() + end + + if not IsValid(owner) then return end + + ent:SetNWString("rpt_plate", Realistic_Police.GetPlate(owner)) +end + +-- Reset position of all bones for surrender and cuffed +function Realistic_Police.ResetBonePosition(Table, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + for k,v in pairs(Table) do + if isnumber(ply:LookupBone( k )) then + ply:ManipulateBoneAngles(ply:LookupBone( k ), Angle(0,0,0)) + end + end +end + +-- Check if the name of the fine is on the configuration +function Realistic_Police.CheckValidFine(String) + for k,v in pairs(Realistic_Police.FiningPolice) do + if v.Name == String then + return true + end + end +end +function Realistic_Police.AddCriminalRecord(ply, Table) + if not IsValid(ply) or not ply:IsPlayer() then return end + + local RealisticPoliceFil = file.Read("realistic_police/record/"..ply:SteamID64()..".txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + -- Check if RealisticPoliceTab contains more criminal record than the max + if #RealisticPoliceTab >= Realistic_Police.MaxCriminalRecord then + table.remove(RealisticPoliceTab, 1) + end + + RealisticPoliceTab[#RealisticPoliceTab + 1] = Table + file.Write("realistic_police/record/"..ply:SteamID64()..".txt", util.TableToJSON(RealisticPoliceTab, true)) +end + +local whitelistVar = { + ["Energy"] = true, +} +function Realistic_Police.SaveLoadInfo(ply, bool, info) + if bool then + -- Save the weapon of the player + local WeaponSave = {} + for k, v in pairs(ply:GetWeapons()) do + local class = v:GetClass() + + WeaponSave[class] = { + ["Clip1"] = v:Clip1(), + ["Clip2"] = v:Clip2(), + } + end + + -- Save some information of the player ( Weapon , Health & Armor ) + ply.RPTInfo = { + RArmor = ply:Armor(), + RWeapon = WeaponSave, + Ammo = ply:GetAmmo(), + RModel = info and ply:GetModel() or nil, + RHealth = ply:Health(), + RPos = info and ply:GetPos() or nil, + RDarkrpVar = info and table.Copy(ply.DarkRPVars) or nil, + } + else + -- SetPos the player + if isvector(ply.RPTInfo["RPos"]) then + ply:SetPos(ply.RPTInfo["RPos"]) + end + + -- Give back the armor of the player + ply:SetArmor((ply.RPTInfo["RArmor"] or 0)) + + -- Set the model of the player + if isstring(ply.RPTInfo["RModel"]) then + ply:SetModel(ply.RPTInfo["RModel"]) + end + + if istable(ply.RPTInfo["RDarkrpVar"]) then + for k,v in pairs(ply.RPTInfo["RDarkrpVar"]) do + if not whitelistVar[k] then continue end + + ply:setDarkRPVar(k, v) + end + end + + -- Give back the health of the player + ply:SetHealth((ply.RPTInfo["RHealth"] or 100)) + + -- Give back his weapons + ply:StripWeapons() + + for k,v in pairs(ply.RPTInfo["RWeapon"]) do + local wep = ply:Give(k, true) + + wep:SetClip1(v["Clip1"]) + wep:SetClip2(v["Clip2"]) + end + + for k,v in pairs(ply.RPTInfo["Ammo"]) do + ply:SetAmmo(v,k) + end + + ply.RPTInfo = nil + + if ply.WeaponRPT["Surrender"] then + ply:SelectWeapon("weapon_rpt_surrender") + end + + if ply.WeaponRPT["Cuff"] then + ply:SelectWeapon("weapon_rpt_cuffed") + end + end +end + +function Realistic_Police.Tazz(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + if timer.Exists("rpt_stungun_unfreeze"..ply:EntIndex()) then return end + + -- Save info of the player + Realistic_Police.SaveLoadInfo(ply, true, true) + + -- Create the Ragdoll Player + ply:CreateRagdoll() + timer.Simple(0, function() + local entSpectate = ply:GetRagdollEntity() + if not IsValid(entSpectate) then entSpectate = ply end + + ply:Spectate(OBS_MODE_CHASE) + ply:SpectateEntity(entSpectate) + ply:StripWeapons() + ply:StripAmmo() + end) +if IsValid(ply.WeaponRPT["DragedBy"]) then + ply.WeaponRPT["DragedBy"] = nil + end + + -- Send the animation of the corpse + timer.Create("rpt_stungun_send"..ply:EntIndex(), 1, 1, function() + if IsValid(ply) then + net.Start("RealisticPolice:StunGun") + net.WriteEntity(ply) + net.Broadcast() + end + end) + + -- Timer when the player is not stun + timer.Create("rpt_stungun"..ply:EntIndex(), 5, 1, function() + if IsValid(ply) then + + -- Unspectacte and Freeze player + ply:UnSpectate() + ply:Spawn() + + if IsValid(ply:GetRagdollEntity()) then + ply:GetRagdollEntity():Remove() + end + + Realistic_Police.SaveLoadInfo(ply) + + ply:Freeze(true) + + timer.Simple(1, function() + ply.RPTStunTable = {} + end) + + timer.Create("rpt_stungun_unfreeze"..ply:EntIndex(), 7, 1, function() + if IsValid(ply) then + ply:Freeze(false) + end + end) + end + end) +end + +function Realistic_Police.Cuff(ply, officer) + if not IsValid(ply) or not IsValid(officer) then return end + if ply:GetPos():DistToSqr(officer:GetPos()) > 15625 then return end + + if timer.Exists("rpt_timerarrest"..ply:EntIndex()) then + timer.Remove("rpt_timerarrest"..ply:EntIndex()) + end + + -- Check if the Police Man Can Cuff the player + if not Realistic_Police.CanCuff[Realistic_Police.GetPlayerJob(officer)] then + Realistic_Police.SendNotify(officer, Realistic_Police.GetSentence("noGoodJobToArrestuser")) + return + end + + -- Check if the player can be cuff or not + if Realistic_Police.CantBeCuff[Realistic_Police.GetPlayerJob(ply)] then + Realistic_Police.SendNotify(officer, Realistic_Police.GetSentence("cantArrestPlayer")) + return + end + + hook.Run("RealisticPolice:OnCuff", ply) + + if ply.WeaponRPT["Surrender"] then + ply:StripWeapon("weapon_rpt_surrender") + ply.WeaponRPT["Surrender"] = false +Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneSurrender, ply) + end + + local wep, oldWeapon = ply:GetActiveWeapon() + if IsValid(wep) then + oldWeapon = wep:GetClass() + end + + -- Cuff the Player + ply:Give("weapon_rpt_cuffed") + ply:SelectWeapon("weapon_rpt_cuffed") + + ply.WeaponRPT = { + Cuff = true, + EnterExit = true, + oldWeapon = oldWeapon, + } + + ply.WeaponRPT["processCuff"] = false + + ply:Freeze(false) + officer:Freeze(false) + + hook.Run("RealisticPolice:Cuffed", ply, officer ) +end + +function Realistic_Police.UnCuff(ply, officer) + if not IsValid(ply) then return end + if IsValid(officer) && ply:GetPos():DistToSqr(officer:GetPos()) > 15625 then return end + + if ply.WeaponRPT["Cuff"] then + + if timer.Exists("rpt_timerarrest"..ply:EntIndex()) && not Realistic_Police.UseDefaultArrest then + timer.Remove("rpt_timerarrest"..ply:EntIndex()) + end + + ply.WeaponRPT["Cuff"] = false + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneCuffed, ply) + + ply:Freeze(false) + + if IsValid(officer) then + officer:Freeze(false) + end + + ply:StripWeapon("weapon_rpt_cuffed") + + if isstring(ply.WeaponRPT["oldWeapon"]) && ply:HasWeapon(ply.WeaponRPT["oldWeapon"]) then + ply:SelectWeapon(ply.WeaponRPT["oldWeapon"]) + + ply.WeaponRPT["oldWeapon"] = nil + + if IsValid(ply.WeaponRPT["DragedBy"]) then + ply.WeaponRPT["DragedBy"].WeaponRPT["Drag"] = nil + ply.WeaponRPT["DragedBy"] = nil + end + end + + hook.Run("RealisticPolice:UnCuffed", ply, officer) + + if IsValid(officer) then + if istable(officer.WeaponRPT) && IsValid(officer.WeaponRPT["Drag"]) then + if officer.WeaponRPT["Drag"] == ply then + officer.WeaponRPT["Drag"] = nil + end + end + end + + ply.WeaponRPT["EnterExit"] = nil + end +end + +-- Check if there's a jail position. +function Realistic_Police.CheckJailPos() + local directory = file.Read("realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt", "DATA") or "" + local RptTable = util.JSONToTable(directory) or {} + + if #RptTable > 0 then + return true + end +end + +-- Find an empty jail place +function Realistic_Police.GetPlaceArrest() + local directory = file.Read("realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt", "DATA") or "" + local RptTable = util.JSONToTable(directory) or {} + + local RPTid = 1 + for k,v in pairs(ents.FindInSphere(RptTable[RPTid]["Pos"], 50)) do + if v:IsPlayer() then + RPTid = RPTid + 1 + end + end + return RptTable[RPTid]["Pos"] +end + +-- Arrest the player if he is cuffed +function Realistic_Police.ArresPlayer(ply, Time, officer) + if not IsValid(ply) or not ply:IsPlayer() then return end + + if Realistic_Police.UseDefaultArrest then + timer.Create("rpt_timerarrest"..ply:EntIndex(), Time, 1, function() end) + + hook.Run("RealisticPolice:Jailed", ply, Time, officer) + ply:SetNWInt("rpt_arrest_time", CurTime() + Time) + + Realistic_Police.UnCuff(ply, officer) + + ply:arrest(Time, "Arrested by the Jailer") + else + if timer.Exists("rpt_timerarrest"..ply:EntIndex()) then + timer.Remove("rpt_timerarrest"..ply:EntIndex()) + end + + if ply.WeaponRPT["Cuff"] then + ply:SetPos(Realistic_Police.GetPlaceArrest()) + ply.WeaponRPT["DragedBy"] = nil + + ply:SetNWInt("rpt_arrest_time", CurTime() + Time) +hook.Run("RealisticPolice:Jailed", ply, Time, officer) + -- Create the Jail Timer + timer.Create("rpt_timerarrest"..ply:EntIndex(), Time, 1, function() + if IsValid(ply) then + -- ply:StripWeapons() + Realistic_Police.UnArrest(ply, officer) + end + end) + end + end + + if Realistic_Police.SetModelWhenJail then + local randomModel = Realistic_Police.ModelWhenJail[math.random(1, #Realistic_Police.ModelWhenJail)] + + if isstring(randomModel) then + ply:SetModel(Realistic_Police.ModelWhenJail) + + ply.RPS = ply.RPS or {} + ply.RPS["oldModelBeforeArrest"] = ply:GetModel() + end + end +end + +function Realistic_Police.UnArrest(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + -- Check if the timer Exist + if timer.Exists("rpt_timerarrest"..ply:EntIndex()) then + -- Reset Variables / Bone / Speed of the player + ply:StripWeapon("weapon_rpt_cuffed") + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneCuffed, ply) + + -- UnCuff the Player ( For give weapons ) + ply.WeaponRPT["Cuff"] = false + + hook.Run("RealisticPolice:UnJailed", ply) + + ply.WeaponRPT = {} + + local spawnPoint = Realistic_Police.GetSpawnPoint(ply) + ply:SetPos(spawnPoint) + + -- Remove the arrest timer + timer.Remove("rpt_timerarrest"..ply:EntIndex()) + end + + if Realistic_Police.UseDefaultArrest then + if DarkRP then + ply:unArrest() + end + end + + ply.RPS = ply.RPS or {} + if isstring(ply.RPS["oldModelBeforeArrest"]) then + ply:SetModel(ply.RPS["oldModelBeforeArrest"]) + end +end + +function Realistic_Police.Drag(ply, officer) + if not IsValid(ply) or not IsValid(officer) then return end + if ply:GetPos():DistToSqr(officer:GetPos()) > 15625 then return end + + if ply.WeaponRPT["Cuff"] then + if not IsValid(ply.WeaponRPT["DragedBy"]) && not IsValid(officer.WeaponRPT["Drag"]) then + ply.WeaponRPT["DragedBy"] = officer + officer.WeaponRPT["Drag"] = ply + else + ply.WeaponRPT["DragedBy"] = nil + officer.WeaponRPT["Drag"] = nil + end + end +end + +function Realistic_Police.PlaceVehicle(officer, vehc) + local RPTPlace = nil + local DistancePlace = 1000 + + -- Get the Place with vcmod + if istable(vehc:VC_getSeatsAvailable()) then + for k,v in ipairs(table.Reverse(vehc:VC_getSeatsAvailable())) do + if v:GetPos():DistToSqr(officer:GetPos()) < DistancePlace ^ 2 then + if IsValid(v:GetDriver()) then continue end + RPTPlace = v + break + end + end + end + return RPTPlace +end + +function Realistic_Police.GetPoliceProps(ply) + local Count = 0 + for k,v in pairs(ents.FindByClass("prop_physics")) do + if v:CPPIGetOwner() == ply then + if istable(Realistic_Police.Trunk[v:GetModel()]) then + Count = Count + 1 + end + end + end + return Count +end + +function Realistic_Police.CountRepairMan() + local Count = 0 + for k,v in pairs(player.GetAll()) do + if Realistic_Police.CameraWorker[Realistic_Police.GetPlayerJob(v)] then + Count = Count + 1 + end + end + return Count +end + +function Realistic_Police.BrokeCamera(ent) + ent:ResetSequence("desactiv") + ent:SetSequence("desactiv") + ent:SetRptCam(true) + ent:SetRptRotate("nil") + ent.DestroyCam = true + + -- Restore the health and the animation of the camera + timer.Create("camera_respawn"..ent:EntIndex(), Realistic_Police.CameraRestart, 0, function() + if IsValid(ent) then + if ent:GetRptCam() then + if Realistic_Police.CountRepairMan() == 0 then + ent:ResetSequence("active") + ent:SetSequence("active") + ent:SetRptCam(false) + ent.HealthEntity = Realistic_Police.CameraHealth + ent.DestroyCam = false + timer.Remove("camera_respawn"..ent:EntIndex()) + end + else + timer.Remove("camera_respawn"..ent:EntIndex()) + end + end + end) +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_hook.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_hook.lua new file mode 100644 index 0000000..ea7a963 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_hook.lua @@ -0,0 +1,474 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} + +-- Initialize Folder ( Criminal Record / Report ) +hook.Add("InitPostEntity", "RPT:InitializeServer", function() + if not file.Exists("realistic_police", "DATA") then + file.CreateDir("realistic_police") + end + if not file.Exists("realistic_police/report", "DATA") then + file.CreateDir("realistic_police/report") + end + if not file.Exists("realistic_police/record", "DATA") then + file.CreateDir("realistic_police/record") + end + + -- Create a loop for broke sometime a camera + timer.Create("rpt_loop_camera", 400, 0, function() + + -- Check if count of camera Repairer is > 0 + if Realistic_Police.CameraBroke then + local RandomId = math.random(1, #ents.FindByClass("realistic_police_camera")) + if math.random(1, 2) == 1 then + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + if k == RandomId && not IsValid(v:CPPIGetOwner()) then + Realistic_Police.BrokeCamera(v) + end + end + end + end + end) +end) + +hook.Add("onLockpickCompleted", "RPT:onLockpickCompleted", function(Player, Success, CuffedP) + if IsValid(CuffedP) && CuffedP:IsPlayer() then + if istable(CuffedP.WeaponRPT) then + if CuffedP.WeaponRPT["Cuff"] then + if Success then + Realistic_Police.UnCuff(CuffedP, Player) + end + end + end + end +end) + +hook.Add("PlayerUse", "RPT:PlayerUse", function( ply, ent ) + if IsValid(ply) && ply:IsPlayer() then + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then + return false + end + end + end +end ) + +hook.Add("canLockpick", "RPT:canLockpick", function(Player, CuffedP, Trace) + if IsValid(CuffedP) && CuffedP:IsPlayer() then + if istable(CuffedP.WeaponRPT) then + if CuffedP.WeaponRPT["Cuff"] then + return true + end + end + end +end) + +hook.Add("lockpickTime", "RPT:lockpickTime", function(Player, Entity) + if IsValid(Entity) && Entity:IsPlayer() then + if istable(Entity.WeaponRPT) then + if Entity.WeaponRPT["Cuff"] then + return 10 + end + end + end +end) + +hook.Add("canArrest", "RPT:canArrest", function(Player, ArrestedPlayer) + if istable(ArrestedPlayer.WeaponRPT) then + if ArrestedPlayer.WeaponRPT["Cuff"] then + Realistic_Police.UnCuff(ArrestedPlayer, Player) + end + end +end) + +hook.Add("canUnarrest", "RPT:canUnarrest", function(Player, ArrestedPlayer) + if istable(ArrestedPlayer.WeaponRPT) then + if ArrestedPlayer.WeaponRPT["Cuff"] then + Realistic_Police.UnCuff(ArrestedPlayer, Player) + end + end +end) + +-- All hook which are desactivate when the player is Cuffed or Surrender + +hook.Add("PlayerSpawnProp", "RPT:PlayerSpawnProp", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("CanPlayerSuicide","RPT:CanPlayerSuicide",function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("PlayerCanPickupWeapon", "RPT:PlayerCanPickupWeapon", function(ply, wep) + if istable(ply.WeaponRPT) then + if wep:GetClass() != "weapon_rpt_surrender" && wep:GetClass() != "weapon_rpt_cuffed" && not ply.RPTInfo then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end + end +end) + +hook.Add("canGoAFK", "RPT:canGoAFK", function(ply, wep) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("canSleep", "RPT:canSleep", function(ply, wep) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("PlayerDroppedWeapon", "RPT:PlayerDroppedWeapon", function(ply, wep) + if istable(ply.RPTInfo) && ply.RPTInfo["RWeapon"] then + ply.RPTInfo["RWeapon"][wep:GetClass()] = nil + end +end) + +hook.Add("canDropWeapon", "RPT:canDropWeapon", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("playerCanChangeTeam", "RPT:playerCanChangeTeam", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("canBuyVehicle", "RPT:canBuyVehicle", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end ) + +hook.Add("canBuyShipment", "RPT:canBuyShipment", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("canBuyPistol", "RPT:canBuyPistol", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("canBuyAmmo", "RPT:canBuyAmmo", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("canBuyCustomEntity", "RPT:canBuyCustomEntity", function(ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("VC_canSwitchSeat", "RPT:VC_canSwitchSeat", function(ply, ent_from, ent_to) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end +end) + +hook.Add("CanExitVehicle", "RPT:CanExitVehicle", function(vehc, ply) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["EnterExit"] then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then + ply:EnterVehicle(vehc) + return false + end + end + end +end) + +hook.Add("PlayerSwitchWeapon", "RPT:PlayerSwitchWeapon", function(ply, oldWeapon, newWeapon) + local class = newWeapon:GetClass() + if class == "weapon_rpt_cuffed" or class == "weapon_rpt_surrender" then return end + + ply.WeaponRPT = ply.WeaponRPT or {} + if timer.Exists("rpt_animation"..ply:EntIndex()) or ply.WeaponRPT["processCuff"] then + return true + end + + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] or ply.WeaponRPT["processCuff"] then + return true + end + end +end) + +-------------------------------------------------------------------------------------- + +hook.Add("PlayerInitialSpawn", "RPT:PlayerInitialSpawn", function(ply) + timer.Simple(8, function() + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.WeaponRPT = {} + -- Generate a plate if the player doesn't have it + Realistic_Police.GeneratePlate(ply) + + -- Send the Table of Custom Vehicles ( Configurate by the owner of the server with the tool ) + local RealisticPoliceFil = file.Read("realistic_police/vehicles.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + local CompressTable = util.Compress(RealisticPoliceFil) + + net.Start("RealisticPolice:SendInformation") + net.WriteInt(CompressTable:len(), 32) + net.WriteData(CompressTable, CompressTable:len()) + net.Send(ply) + end) +end) + +hook.Add("OnEntityCreated", "RPT:OnEntityCreated", function(ent) + -- Set information of the plate on the vehicle + timer.Simple(2, function() + if IsValid(ent) then + Realistic_Police.SetInfoVehc(ent) + end + end) +end) + +-- Check if the Vehicle have a fine and the player can enter into the vehicle +hook.Add("CanPlayerEnterVehicle", "RPT:PlayerEnteredVehicle", function(ply, vehc) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Surrender"] then return false end +if istable(vehc.WeaponRPT) then + if istable(vehc.WeaponRPT["TableFine"]) && not ply.WeaponRPT["EnterVehc"] then + net.Start("RealisticPolice:FiningSystem") + net.WriteTable(vehc.WeaponRPT["TableFine"]) + net.Send(ply) + ply.WeaponRPT["Fine"] = vehc + return false + end + end + end + + -- Avoid change seat if you are surrender or handcuffed in a svmod seat + if SVMOD and ply:InVehicle() then + local currentVehicle = ply:GetVehicle() + if not IsValid(currentVehicle) then return end + + if currentVehicle:GetNW2Bool("SV_IsSeat") and istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then return false end + end + end +end) + +-- Set the plate on the vehicle when the player buy the vehicle +hook.Add("PlayerBuyVehicle", "RPT:PlayerBuyVehicle", function(ent) + timer.Simple(2, function() + if IsValid(ent) then + Realistic_Police.SetInfoVehc(ent) + end + end) +end) + +-- Add to the PVS Police Camera +hook.Add("SetupPlayerVisibility", "RPT:SetupPlayerVisibility", function(ply) + if ply.RPTShowEntity != nil && IsValid(ply.RPTShowEntity) then + AddOriginToPVS(ply.RPTShowEntity:GetPos()) + end +end) + +hook.Add( "PlayerButtonDown", "RPT:PlayerButtonDownS", function( ply, button ) + -- Open the Tablet on the vehicle + if button == Realistic_Police.KeyOpenTablet then + if ply:InVehicle() then + if IsValid(ply:GetVehicle()) then + if Realistic_Police.PoliceVehicle[ply:GetVehicle():GetVehicleClass()] then + if isbool(Realistic_Police.PoliceVehicle[ply:GetVehicle():GetVehicleClass()]) then + net.Start("RealisticPolice:Open") + net.WriteString("OpenMainMenu") + net.WriteEntity(ply:GetVehicle()) + net.WriteBool(true) + net.Send(ply) + end + end + end + if IsValid(ply:GetVehicle():GetParent()) then + if Realistic_Police.PoliceVehicle[ply:GetVehicle():GetParent():GetVehicleClass()] then + if isbool(Realistic_Police.PoliceVehicle[ply:GetVehicle():GetParent():GetVehicleClass()]) then + net.Start("RealisticPolice:Open") + net.WriteString("OpenMainMenu") + net.WriteEntity(ply:GetVehicle():GetParent()) + net.WriteBool(true) + net.Send(ply) + end + end + end + end + end + + if Realistic_Police.SurrenderActivate then + if istable(ply.WeaponRPT) then + -- Check if the player is not cuffed for surrender + if not ply.WeaponRPT["Cuff"] then +if button == Realistic_Police.SurrenderKey then + if ply:InVehicle() then return end + if not ply.WeaponRPT["Surrender"] then + hook.Run("RPT:Surrender", ply) + + timer.Create("rpt_surrender"..ply:EntIndex(), 0.5, 1, function() + if IsValid(ply) then + local wep, oldWeapon = ply:GetActiveWeapon() + if IsValid(wep) then + oldWeapon = wep:GetClass() + end + + ply.WeaponRPT["oldWeapon"] = oldWeapon + + ply:Give("weapon_rpt_surrender") + ply:SelectWeapon("weapon_rpt_surrender") + + ply.WeaponRPT["Surrender"] = true + end + end) + else + ply:StripWeapon("weapon_rpt_surrender") + ply.WeaponRPT["Surrender"] = false + + if isstring(ply.WeaponRPT["oldWeapon"]) && ply:HasWeapon(ply.WeaponRPT["oldWeapon"]) then + ply:SelectWeapon(ply.WeaponRPT["oldWeapon"]) + + ply.WeaponRPT["oldWeapon"] = nil + end + + -- Give back his weapons + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneSurrender, ply) + end + end + end + end + end +end) + +hook.Add( "PlayerButtonUp", "RPT:PlayerButtonUpS", function( ply, button ) + if istable(ply.WeaponRPT) then + if not ply.WeaponRPT["Cuff"] then + if button == Realistic_Police.SurrenderKey then + -- If timer of surrending exist then remove when the player up the button + if timer.Exists("rpt_surrender"..ply:EntIndex()) then + timer.Remove("rpt_surrender"..ply:EntIndex()) + end + end + end + end +end) +local CMoveData = FindMetaTable("CMoveData") +function CMoveData:RemoveKeys(ADLKey) + local ADLButtons = bit.band(self:GetButtons(), bit.bnot(ADLKey)) + self:SetButtons(ADLButtons) +end + +hook.Add("SetupMove", "RPT:Move", function(ply, data) + if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] or ply.WeaponRPT["Surrender"] then + data:SetMaxClientSpeed( 80 ) + if data:KeyDown(IN_JUMP) then + data:RemoveKeys(IN_JUMP) + end + end + -- this hook is the hook for drag the player + if IsValid(ply.WeaponRPT["DragedBy"]) then + if ply:GetPos():DistToSqr(ply.WeaponRPT["DragedBy"]:GetPos()) < 40000 then + if IsValid(ply.WeaponRPT["DragedBy"]) then + local VectorDrag = ply.WeaponRPT["DragedBy"]:GetPos() - ply:GetPos() + data:SetVelocity(Vector(VectorDrag.x*4, VectorDrag.y*4, -100)) + end + else + ply.WeaponRPT["DragedBy"] = nil + end + end + else + ply.WeaponRPT = {} + end +end) + +hook.Add("playerBoughtCustomEntity", "RPT:BoughtEntity", function(ply, enttbl, ent, price) + -- Set the owner of the Camera and the Screen + if ent:GetClass() == "realistic_police_camera" or ent:GetClass() == "realistic_police_screen" then + ent:CPPISetOwner(ply) + end +end ) + +-- Give ammo +hook.Add("WeaponEquip", "RPT:WeaponEquip", function(weapon, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + if weapon:GetClass() == "weapon_rpt_stungun" then + ply:GiveAmmo((Realistic_Police.StungunAmmo or 40), "Pistol", false) + end +end) +-- d4ec95c252515613de193db925956fba07d4ff50f580a4b42b7f507f9c716945 + +-- When the Police Man disconnect unfreez the player +hook.Add("PlayerDisconnected", "RPT:PlayerDisconnected", function(ply) + if IsValid(ply) then + if istable(ply.WeaponRPT) then + if IsValid(ply.WeaponRPT["Fine"]) && ply.WeaponRPT["Fine"]:IsPlayer() then + ply.WeaponRPT["Fine"]:Freeze(false) + end + end + end +end) + +hook.Add("PlayerDeath", "RPT:PlayerDeath", function(ply) + + if timer.Exists("rpt_stungun"..ply:EntIndex()) then + timer.Remove("rpt_stungun"..ply:EntIndex()) + end + + if not Realistic_Police.UseDefaultArrest then + if timer.Exists("rpt_timerarrest"..ply:EntIndex()) then + timer.Remove("rpt_timerarrest"..ply:EntIndex()) + end + else + Realistic_Police.UnCuff(ply) + end + + ply.WeaponRPT = {} + -- Reset Bone position + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneCuffed, ply) + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneSurrender, ply) + + for k,v in pairs(ents.GetAll()) do + if v:GetClass() == "realistic_police_camera" or v:GetClass() == "realistic_police_screen" then + if v:CPPIGetOwner() == ply then + v:Remove() + end + end + end +end) + +hook.Add("OnPlayerChangeTeam", "RPT:OnPlayerChangeTeam", function(ply) + + if timer.Exists("rpt_stungun"..ply:EntIndex()) then + timer.Remove("rpt_stungun"..ply:EntIndex()) + end + + ply.WeaponRPT = {} + -- Reset Bone position + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneCuffed, ply) + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneSurrender, ply) + + for k,v in pairs(ents.GetAll()) do + if v:GetClass() == "realistic_police_camera" or v:GetClass() == "realistic_police_screen" then + if v:CPPIGetOwner() == ply then + v:Remove() + end + end + end +end) + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_net.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_net.lua new file mode 100644 index 0000000..c7b7cbe --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_net.lua @@ -0,0 +1,768 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} + +util.AddNetworkString("RealisticPolice:Open") +util.AddNetworkString("RealisticPolice:Report") +util.AddNetworkString("RealisticPolice:CriminalRecord") +util.AddNetworkString("RealisticPolice:SecurityCamera") +util.AddNetworkString("RealisticPolice:NameCamera") +util.AddNetworkString("RealisticPolice:FiningSystem") +util.AddNetworkString("RealisticPolice:HandCuff") +util.AddNetworkString("RealisticPolice:StunGun") +util.AddNetworkString("RealisticPolice:SetupVehicle") +util.AddNetworkString("RealisticPolice:SendInformation") +util.AddNetworkString("RealisticPolice:UpdateInformation") +util.AddNetworkString("RealisticPolice:PlaceProp") +util.AddNetworkString("RealisticPolice:SetWantedStatus") + +-- Check if the player is near a computer / jailer or a police vehicle +local function CheckEnts(Player) + for k,v in pairs(ents.FindInSphere(Player:GetPos(), 600)) do + if IsValid(v) then + if v:GetClass() == "realistic_police_computer" or v:GetClass() == "realistic_police_computer_sbu" or v:GetClass() == "realistic_police_jailer" then + return true + end + end + if IsValid(v) && v:IsVehicle() then + if Realistic_Police.PoliceVehicle[v:GetVehicleClass()] then + return true + end + end + if IsValid(v:GetParent()) && v:GetParent():IsVehicle() then + if Realistic_Police.PoliceVehicle[v:GetParent():GetVehicleClass()] then + return true + end + end + end + return false +end + +-- Net For the setup a license plate on a vehicle +net.Receive("RealisticPolice:SetupVehicle", function(len,ply) + + -- Check if the Player is valid and have the correct rank for setup the vehicle + if not IsValid(ply) or not ply:IsPlayer() then return end + if not Realistic_Police.AdminRank[ply:GetUserGroup()] then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 0.1 + + -- This table contains the position , size , rotation of the plate + local Table = net.ReadTable() or {} + if not istable(Table) then return end + + -- This entity is the vehicle to configure + local EntityDelete = net.ReadEntity() + if not IsValid(EntityDelete) then return end + + local RealisticPoliceFil = file.Read("realistic_police/vehicles.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + -- Remove before position of the vehicle in the data + RealisticPoliceTab[EntityDelete:GetModel()] = nil + table.Merge(RealisticPoliceTab, Table) + + file.Write("realistic_police/vehicles.txt", util.TableToJSON(RealisticPoliceTab)) + + -- Here we send the table to the client for have the license plate information + timer.Simple(0.1, function() + + -- Compress the table for send information + local RealisticPoliceFil = file.Read("realistic_police/vehicles.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + local CompressTable = util.Compress(RealisticPoliceFil) + net.Start("RealisticPolice:SendInformation") + net.WriteInt(CompressTable:len(), 32) + net.WriteData(CompressTable, CompressTable:len() ) + net.Broadcast() + + -- Delete the vehicle preview + if IsValid(EntityDelete) then + EntityDelete:Remove() + end + end ) + +end ) + +net.Receive("RealisticPolice:Report", function(len, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 0.1 + + -- RPTInt = Int for delete / Edit + local RPTInt = net.ReadUInt(10) + + -- RPTString = Information of the action + local RPTString = net.ReadString() + if string.len(RPTString) > 30 then return end + + -- RPTSteamID64 = Steamid64 of the player + local RPTEntity = net.ReadEntity() + + -- RPTText = Text of the Report + local RPTText = net.ReadString() + if string.len(RPTText) > 1184 then return end + + local RPTSteamID64 = "" + + -- Check if it's a unknow report or a player report + local RPTName = "" + if IsValid(RPTEntity) && RPTEntity:IsPlayer() then + RPTSteamID64 = RPTEntity:SteamID64() + RPTName = RPTEntity:Name() + else + RPTSteamID64 = "unknown" + RPTName = Realistic_Police.GetSentence("unknown") + end +local RealisticPoliceFil = file.Read("realistic_police/report/"..RPTSteamID64..".txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + -- Create the table of the report for save it after + local TableReport = { + RPTText = RPTText, + RPTDate = os.date("%d/%m/%Y", os.time()), + RPTPolice = ply:Name(), + RPTCriminal = RPTName, + RPTSteamID64 = RPTSteamID64, + } + + -- Check if the player is near a Computer or a vehicle for add the report + if not CheckEnts(ply) then return end + + -- Save the report into the data + if RPTString == "SaveReport" then + + -- Check if the player have the job for add a report + if Realistic_Police.JobEditReport[Realistic_Police.GetPlayerJob(ply)] then + + -- Check if the Player is not the PoliceMan + if ply == RPTEntity then return end + + -- Check if RealisticPoliceTab contains more report than the max report + if #RealisticPoliceTab >= Realistic_Police.MaxReport then + table.remove(RealisticPoliceTab, 1) + end + + -- If the RPTSteamID64 is unknow then I set the model to "" + if RPTSteamID64 != "unknown" then + TableReport["Model"] = RPTEntity:GetModel() + else + TableReport["Model"] = "" + end + + RealisticPoliceTab[#RealisticPoliceTab + 1] = TableReport + file.Write("realistic_police/report/"..RPTSteamID64..".txt", util.TableToJSON(RealisticPoliceTab, true)) + + -- Send The table of the player to the client + Realistic_Police.SendReport(RPTSteamID64, ply) + end + + elseif RPTString == "SendInformation" then + + Realistic_Police.SendReport(RPTSteamID64, ply) + + elseif RPTString == "EditReport" then + + -- Check if the player can edit the report + if Realistic_Police.JobEditReport[Realistic_Police.GetPlayerJob(ply)] then + + -- Check if the Player is not the PoliceMan + if ply == RPTEntity then return end + + RealisticPoliceTab[RPTInt]["RPTText"] = TableReport["RPTText"] + file.Write("realistic_police/report/"..RPTSteamID64..".txt", util.TableToJSON(RealisticPoliceTab, true)) +Realistic_Police.SendReport(RPTSteamID64, ply) + end + + elseif RPTString == "RemoveReport" then + + -- Check if the player can remove the report + if Realistic_Police.JobDeleteReport[Realistic_Police.GetPlayerJob(ply)] then + + -- Check if the Player is not the PoliceMan + if ply == RPTEntity then return end + + table.remove(RealisticPoliceTab, RPTInt) + file.Write("realistic_police/report/"..RPTSteamID64..".txt", util.TableToJSON(RealisticPoliceTab, true)) + + Realistic_Police.SendReport(RPTSteamID64, ply) + end + + end +end) + +net.Receive("RealisticPolice:CriminalRecord", function(len, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 0.1 + + -- RPTInt = Int for delete a criminal record + local RPTInt = net.ReadUInt(10) + + -- RPTString = Information of the action + local RPTString = net.ReadString() + if string.len(RPTString) > 50 then return end + + local RPTEntity = net.ReadEntity() + if not IsValid(RPTEntity) then return end + + -- RPTText = Text of the criminal record + local RPTText = net.ReadString() + if string.len(RPTText) > 1050 then return end + + -- Create the table of the criminal record for save it after + local Table = { + Date = os.date("%d/%m/%Y", os.time()), + Motif = RPTText, + } +-- Check if the Policeman is near a Jail , A computer or a Vehicle for add a criminal record + if not IsValid(RPTEntity) or not CheckEnts(ply) then return end + + local RealisticPoliceFil = file.Read("realistic_police/record/"..RPTEntity:SteamID64()..".txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + if RPTString == "AddRecord" then + + if Realistic_Police.JobEditRecord[Realistic_Police.GetPlayerJob(ply)] then + + -- Check if the Player is not the PoliceMan + if ply == RPTEntity then return end + + -- Add a criminal Record + Realistic_Police.AddCriminalRecord(RPTEntity, Table) + + -- Send The table of the player to the client + Realistic_Police.SendRecord(RPTEntity:SteamID64(), ply) + end + + elseif RPTString == "SendRecord" then + + Realistic_Police.SendRecord(RPTEntity:SteamID64(), ply) + + elseif RPTString == "RemoveRecord" then + + if Realistic_Police.JobDeleteRecord[Realistic_Police.GetPlayerJob(ply)] then + + -- Check if the Player is not the PoliceMan + if ply == RPTEntity then return end + + RealisticPoliceTab[RPTInt] = nil + file.Write("realistic_police/record/"..RPTEntity:SteamID64()..".txt", util.TableToJSON(RealisticPoliceTab, true)) + + Realistic_Police.SendRecord(RPTEntity:SteamID64(), ply) + end + + end +end ) + +net.Receive("RealisticPolice:SecurityCamera", function(len, ply) -- Send Police Camera + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 0.1 + + -- RPTString = Information of the action + local RPTString = net.ReadString() + if string.len(RPTString) > 60 then return end + + -- RPTEnt = This entity is the camera which the player want to rotate + local RPTEnt = net.ReadEntity() + + -- RPTStringRotate = Rotate on the left or on the Right + local RPTStringRotate = net.ReadString() + if string.len(RPTStringRotate) > 30 then return end + + -- Check if the player is near a Computer or a vehicle + if not CheckEnts(ply) then return end + + if RPTString == "RotateCamera" then + + if not isentity(RPTEnt) or RPTEnt:GetClass() != "realistic_police_camera" then return end + RPTEnt:SetRptRotate(RPTStringRotate) + + elseif RPTString == "ShowCamera" then + + ply.RPTShowEntity = RPTEnt + + elseif RPTString == "DontShowCamera" then + + ply.RPTShowEntity = nil + + elseif RPTString != "RotateCamera" && RPTString != "ShowCamera" then + + -- Send all administrator camera to the client for the police + local TableToSend = {} + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + if v.RPTCam then + table.insert(TableToSend, v) + end + end + + -- Send the table to the client + net.Start("RealisticPolice:SecurityCamera") + net.WriteTable(TableToSend) + net.Send(ply) + end +end ) + +net.Receive("RealisticPolice:FiningSystem", function(len,ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 1 + + -- RPTString = Information of the action + local RPTString = net.ReadString() + + -- RPTStringTbl = All infraction send on this string + local RPTStringTbl = net.ReadString() + if #RPTStringTbl > 1000 then return end + + -- Convert the string to a table for use it + local FineTable = string.Explode("§", RPTStringTbl) + table.remove(FineTable, 1) + + local rptEnt = nil + if IsValid(ply.WeaponRPT["Fine"]) then + rptEnt = ply.WeaponRPT["Fine"] + end + if IsValid(rptEnt) then if not istable(rptEnt.WeaponRPT) then rptEnt.WeaponRPT = {} end end + + if RPTString == "SendFine" then + + -- Check if the Player have the correct job for add a fine + if Realistic_Police.JobCanAddFine[Realistic_Police.GetPlayerJob(ply)] then + if IsValid(rptEnt) then + + -- Check if the Player or the Vehicle is near the PoliceMan + if rptEnt:GetPos():DistToSqr(ply:GetPos()) > 40000 then return end + if #FineTable > Realistic_Police.MaxPenalty or #FineTable == 0 then return end + + -- Check if the Ent is a vehicle and check if the vehicle is owned by the Policeman + if rptEnt:IsVehicle() then + if rptEnt:CPPIGetOwner() == ply then Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("cantAddFineToHimVehicle")) return end + end + + -- Check if the player have already a fine or not + if rptEnt:IsPlayer() then + if isnumber(rptEnt.WeaponRPT["AmountFine"]) then + rptEnt:Freeze(false) + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("personAlreadyHaveFine")) + return + end + end + + -- Check if the vehicle have already a fine or not + if istable(rptEnt.WeaponRPT["TableFine"]) then + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("vehicleHaveFine")) + return + end + + -- Calculate the amount of the fine + local Amount = 0 + for k,v in pairs(Realistic_Police.FiningPolice) do + if table.HasValue(FineTable, v.Name) then + Amount = Amount + v.Price + end + end + + if rptEnt:IsPlayer() then + + -- Check if the Player Can have a fine or not + if not Realistic_Police.JobCantHaveFine[Realistic_Police.GetPlayerJob(rptEnt)] then + rptEnt.WeaponRPT["AmountFine"] = Amount + rptEnt.WeaponRPT["PlayerFiner"] = ply + net.Start("RealisticPolice:FiningSystem") + net.WriteTable(FineTable) + net.Send(rptEnt) + + hook.Run("RealisticPolice:FinePlayer", ply, rptEnt ) + else + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("userCantReceiveFine")) + end + rptEnt:Freeze(false) + + elseif rptEnt:IsVehicle() then + + -- Check if the vehicle can have a fine or not + if not Realistic_Police.VehicleCantHaveFine[rptEnt:GetClass()] then + rptEnt.WeaponRPT["AmountFine"] = Amount + rptEnt.WeaponRPT["TableFine"] = FineTable + rptEnt.WeaponRPT["PlayerFiner"] = ply + + hook.Run("RealisticPolice:FineVehicle", ply, rptEnt ) + else + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("vehicleCantReceiveFine")) + end + + end + end + + -- Play the sound/animation of the weapon + ply:GetActiveWeapon():EmitSound("rptfiningsound.mp3") + ply:GetActiveWeapon():SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + ply:GetActiveWeapon():SetNextPrimaryFire( CurTime() + ply:GetActiveWeapon():SequenceDuration() ) + + timer.Create("rpt_animation"..ply:EntIndex(), ply:GetActiveWeapon():SequenceDuration(), 1, function() + if IsValid(ply) && ply:IsPlayer() then + ply:GetActiveWeapon():SendWeaponAnim( ACT_VM_IDLE ) + end + end) + + ply.WeaponRPT["Fine"] = nil + else + -- Send the notification if the person can't add a fine + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("noGoodJob")) + end + + elseif RPTString == "BuyFine" then + + if ply:IsPlayer() && not IsValid(rptEnt) then + + -- Check if is valid amount for the fine + local amount = ply.WeaponRPT["AmountFine"] + if isnumber(amount) then + local playerMoney = ply:getDarkRPVar("money") + + -- Check if the Player can buy the fine + local buyedFine = false + + if ply:canAfford(amount) then + ply:addMoney(-amount) + + buyedFine = true + elseif Realistic_Police.CompatibilitiesATM(ply, playerMoney, amount) then + ply:addMoney(-playerMoney) + Realistic_Police.PayWithBank(ply, amount) + + buyedFine = true + else + if Realistic_Police.PlayerWanted then + ply:wanted(nil, Realistic_Police.GetSentence("noPayFine"), 120) + end + end + + if buyedFine == true then + -- Send Money & Notification to the Police Man and the person who receive the fine + if IsValid(ply.WeaponRPT["PlayerFiner"]) && ply.WeaponRPT["PlayerFiner"]:IsPlayer() then + ply.WeaponRPT["PlayerFiner"]:addMoney((amount*Realistic_Police.PourcentPay)/100) + Realistic_Police.SendNotify(ply.WeaponRPT["PlayerFiner"], Realistic_Police.GetSentence("youWon").." "..DarkRP.formatMoney((amount*Realistic_Police.PourcentPay)/100)) + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("justPaid").." "..DarkRP.formatMoney(amount)) + end + end + + -- Reset Variables of the player + ply.WeaponRPT["TableFine"] = nil + ply.WeaponRPT["PlayerFiner"] = nil + ply.WeaponRPT["AmountFine"] = nil + + ply:Freeze(false) + end + + elseif rptEnt:IsVehicle() then + + -- Check if is valid amount for the fine + local amount = rptEnt.WeaponRPT["AmountFine"] + if isnumber(amount) then + local buyedFine = false + + if ply:canAfford(amount) then + ply:addMoney(-amount) + + buyedFine = true + elseif Realistic_Police.CompatibilitiesATM(ply, playerMoney, amount) then + ply:addMoney(-playerMoney) + Realistic_Police.PayWithBank(ply, amount) + + buyedFine = true + end + + if buyedFine == true then + -- Send Money & Notification to the Police Man and the person who receive the fine + if IsValid(rptEnt.WeaponRPT["PlayerFiner"]) && rptEnt.WeaponRPT["PlayerFiner"]:IsPlayer() then + rptEnt.WeaponRPT["PlayerFiner"]:addMoney((rptEnt.WeaponRPT["AmountFine"]*Realistic_Police.PourcentPay)/100) + Realistic_Police.SendNotify(rptEnt.WeaponRPT["PlayerFiner"], Realistic_Police.GetSentence("youWon").." "..DarkRP.formatMoney((rptEnt.WeaponRPT["AmountFine"]*Realistic_Police.PourcentPay)/100)) + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("justPaid").." "..DarkRP.formatMoney(rptEnt.WeaponRPT["AmountFine"])) + end + + rptEnt.WeaponRPT["TableFine"] = nil + rptEnt.WeaponRPT["PlayerFiner"] = nil + rptEnt.WeaponRPT["AmountFine"] = nil + end + end + end + + elseif RPTString == "RefuseFine" then + + if isnumber(ply.WeaponRPT["AmountFine"]) then + ply:Freeze(false) + if Realistic_Police.PlayerWanted then + -- Check if the Fine is on a vehicle or a Player ( If the fine is on a vehicle and the player don't pay the fine he's not gonna be wanted ) + if not istable(ply.WeaponRPT["TableFine"]) then + if IsValid(ply.WeaponRPT["PlayerFiner"]) then + ply:wanted(ply.WeaponRPT["PlayerFiner"], Realistic_Police.GetSentence("noPayFine"), 120) + end + end + end + + -- Reset Amount and unfreeze the player also add a wanted + ply.WeaponRPT["PlayerFiner"] = nil + ply.WeaponRPT["AmountFine"] = nil + ply.WeaponRPT["Fine"] = nil + end + + elseif RPTString == "StopFine" then + + -- Stop the fine && reset variables + if not Realistic_Police.JobCanAddFine[Realistic_Police.GetPlayerJob(ply)] then return end + + if IsValid(rptEnt) && rptEnt:IsPlayer() then + rptEnt:Freeze(false) + + -- Reset Amount and unfreeze the player also add a wanted + ply.WeaponRPT["PlayerFiner"] = nil + ply.WeaponRPT["AmountFine"] = nil + ply.WeaponRPT["Fine"] = nil + end + end +end ) + +net.Receive("RealisticPolice:HandCuff", function(len, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 0.1 + + -- RPTString = Information of the action + local RPTString = net.ReadString() + + if RPTString == "ArrestPlayer" then + + -- RPTInt = Jail Time + local RPTInt = net.ReadUInt(10) + if RPTInt > Realistic_Police.MaxDay then return end + + -- RPTText = Why the player is arrested + local RPTText = net.ReadString() + if string.len(RPTText) > 1050 then return end + + if not Realistic_Police.CanCuff[Realistic_Police.GetPlayerJob(ply)] then return end + + -- Check if the Police Man drag a player and if the player is cuffed + if IsValid(ply.WeaponRPT["Drag"]) && ply.WeaponRPT["Drag"].WeaponRPT["Cuff"] then + + -- Check if RPTTableBailer is not a table + if not istable(RPTTableBailer) then + RPTTableBailer = {} + end + + -- Arrest the Player + Realistic_Police.SendNotify(ply.WeaponRPT["Drag"], Realistic_Police.GetSentence("youArrestBy").." "..ply:Name()) + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("youHaveArrest").." "..ply.WeaponRPT["Drag"]:Name()) + + -- Create The table to add on the Criminal Record + local Table = { + Date = os.date("%d/%m/%Y", os.time()), + Motif = RPTText, + } + + -- Add autaumatically a penalty on the criminal record + Realistic_Police.AddCriminalRecord(ply.WeaponRPT["Drag"], Table) + + -- Insert into the bailer table the player + table.insert(RPTTableBailer, { + vEnt = ply.WeaponRPT["Drag"], + vName = ply.WeaponRPT["Drag"]:Name(), + vMotif = RPTText, + vPrice = RPTInt*Realistic_Police.PriceDay, + vModel = ply.WeaponRPT["Drag"]:GetModel(), + }) + + Realistic_Police.ArresPlayer(ply.WeaponRPT["Drag"], RPTInt*Realistic_Police.DayEqual, ply) + ply.WeaponRPT["Drag"] = nil + end + + elseif RPTString == "Bailer" then + + local PlayerArrested = net.ReadEntity() + + -- RPTInt = Int of the bailer menu + local RPTInt = net.ReadUInt(10) + + -- Check if the player is valid + if IsValid(PlayerArrested) then + + -- Check if the Player can buy + if istable(RPTTableBailer[RPTInt]) && ply:canAfford(RPTTableBailer[RPTInt]["vPrice"]) then + + -- Remove the timer arrest and prisoner's release + if timer.Exists("rpt_timerarrest"..PlayerArrested:EntIndex()) then + ply:addMoney(-RPTTableBailer[RPTInt]["vPrice"]) + + Realistic_Police.UnArrest(PlayerArrested) + Realistic_Police.SendNotify(PlayerArrested, Realistic_Police.GetSentence("youUnarrestBy").." "..ply:Name()) + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("justPaid").." "..DarkRP.formatMoney(RPTTableBailer[RPTInt]["vPrice"])) + + local weapon = PlayerArrested:GetWeapon("weapon_rpt_cuffed") + if IsValid(weapon) then + PlayerArrested:StripWeapon("weapon_rpt_cuffed") + end + + PlayerArrested:SetNWInt("rpt_arrest_time", 0) + timer.Remove("rpt_timerarrest"..PlayerArrested:EntIndex()) + + RPTTableBailer[RPTInt] = nil + end + + else + -- Send a notification if the player don't have enought money + Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("cantAfford")) + end + end + + elseif RPTString == "StripWeapon" then + if not Realistic_Police.CanConfiscateWeapon then return end + + local PlayerArrested = net.ReadEntity() + local class = net.ReadString() + + if IsValid(PlayerArrested) && PlayerArrested:IsPlayer() then + if not Realistic_Police.CanCuff[Realistic_Police.GetPlayerJob(ply)] then return end + + -- Check if the player is near the Police Man + if PlayerArrested:GetPos():DistToSqr(ply:GetPos()) < 40000 && PlayerArrested.WeaponRPT["Cuff"] then + PlayerArrested:StripWeapon(class) + end + end + end +end ) + +net.Receive("RealisticPolice:PlaceProp", function(len, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 0.1 + + -- RPTString = String of the props into the table of the trunk + local RPTString = net.ReadString() + + -- Check if the Player can Open the trunk + if not Realistic_Police.CanOpenTrunk[Realistic_Police.GetPlayerJob(ply)] then return end + + -- Check if the props is on the table of the trunk + if not istable(Realistic_Police.Trunk[RPTString]) then return end + + if Realistic_Police.GetPoliceProps(ply) > Realistic_Police.MaxPropsTrunk then Realistic_Police.SendNotify(ply, Realistic_Police.GetSentence("tooMuchEntities")) return end + + -- Create The props + local RPTProps = ents.Create( "prop_physics" ) + RPTProps:SetModel( RPTString ) + RPTProps:SetPos( ply:GetEyeTrace().HitPos + Realistic_Police.Trunk[RPTString]["GhostPos"] ) + RPTProps:SetAngles(Angle(0,ply:GetAngles().y + 90,0)) + RPTProps:CPPISetOwner(ply) + RPTProps:Spawn() +-- Add the props to the undo list + undo.Create("prop") + undo.AddEntity(RPTProps) + undo.SetPlayer(ply) + undo.Finish() +end ) + +net.Receive("RealisticPolice:Open", function(len,ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply.RPTAntiSpam = ply.RPTAntiSpam or CurTime() + if ply.RPTAntiSpam > CurTime() then return end + ply.RPTAntiSpam = CurTime() + 1 + + local EntityC = net.ReadEntity() + if EntityC:GetClass() != "realistic_police_computer" && EntityC:GetClass() != "realistic_police_computer_sbu" && ply:GetPos():DistToSqr(EntityC:GetPos()) > 10000 then return end + + -- check if the player is near a computer or a vehicle + if not CheckEnts(ply) then return end + + -- Set the Boolen on the vehicle / the computer for the hack + if IsValid(EntityC) && (EntityC:GetClass() == "realistic_police_computer" or EntityC:GetClass() == "realistic_police_computer_sbu") or EntityC:IsVehicle() then + EntityC:SetNWBool("rpt_hack", true) + timer.Create("rpt_resolve"..EntityC:EntIndex(), Realistic_Police.ResolveHack, 1, function() + if IsValid(EntityC) then + EntityC:SetNWBool("rpt_hack", false) + end + end ) + end +end ) + +net.Receive("RealisticPolice:NameCamera", function(len, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + if not Realistic_Police.AdminRank[ply:GetUserGroup()] then return end + + local RPTEntity = net.ReadEntity() + if not IsValid(RPTEntity) then return end + + local String = net.ReadString() or "" + RPTEntity:SetNWString("rpt_name_camera", String) +end ) + +net.Receive("RealisticPolice:SetWantedStatus", function(len, ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + if not Realistic_Police.JobEditRecord[Realistic_Police.GetPlayerJob(ply)] then return end + + local target = net.ReadEntity() + local status = net.ReadBool() + + if not IsValid(target) or not target:IsPlayer() then return end + + local char = target:GetCharacter() + if not char then return end + + -- Определяем организацию на основе работы игрока + local job = Realistic_Police.GetPlayerJob(ply) + local org = "fsb" + if job == "СБУ" then + org = "sbu" + end + + char:SetData("wanted_" .. org, status) + target:SetNWBool("ix_wanted_" .. org, status) + + -- Синхронизируем общий статус ix_wanted для совместимости + target:SetNWBool("ix_wanted", target:GetNWBool("ix_wanted_fsb") or target:GetNWBool("ix_wanted_sbu")) + -- Также сохраняем в общее поле для военного билета + char:SetData("wanted", char:GetData("wanted_fsb", false) or char:GetData("wanted_sbu", false)) + + if status then + local orgName = (org == "sbu") and "СБУ" or "ФСБ" + Realistic_Police.SendNotify(ply, "Вы объявили " .. target:Name() .. " в розыск (" .. orgName .. ").") + else + local orgName = (org == "sbu") and "СБУ" or "ФСБ" + Realistic_Police.SendNotify(ply, "Вы сняли розыск (" .. orgName .. ") с " .. target:Name() .. ".") + end +end) + +-- Синхронизация статусов при загрузке персонажа +hook.Add("OnCharacterLoaded", "RealisticPolice:SyncWantedStatus", function(char) + local ply = char:GetPlayer() + if IsValid(ply) then + local w_fsb = char:GetData("wanted_fsb", false) + local w_sbu = char:GetData("wanted_sbu", false) + + ply:SetNWBool("ix_wanted_fsb", w_fsb) + ply:SetNWBool("ix_wanted_sbu", w_sbu) + ply:SetNWBool("ix_wanted", w_fsb or w_sbu) + end +end) + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_tool.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_tool.lua new file mode 100644 index 0000000..b99b1e5 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/server/sv_rpt_tool.lua @@ -0,0 +1,181 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} +-- Save Computer / Camera and police Screen on the map +function Realistic_Police.SaveEntity() + timer.Simple(1, function() + file.Delete("realistic_police/" .. game.GetMap() .. "_rpt_entities" .. ".txt") + local data = {} + for u, ent in pairs(ents.FindByClass("realistic_police_camera")) do + table.insert(data, { + Class = ent:GetClass(), + Pos = ent:GetPos(), + CName = ent:GetNWString("rpt_name_camera"), + GetAngle = ent:GetAngles() + }) + file.Write("realistic_police/" .. game.GetMap() .. "_rpt_entities" .. ".txt", util.TableToJSON(data)) + end + for u, ent in pairs(ents.FindByClass("realistic_police_computer")) do + table.insert(data, { + Class = ent:GetClass(), + Pos = ent:GetPos(), + GetAngle = ent:GetAngles() + }) + file.Write("realistic_police/" .. game.GetMap() .. "_rpt_entities" .. ".txt", util.TableToJSON(data)) + end + for u, ent in pairs(ents.FindByClass("realistic_police_policescreen")) do + table.insert(data, { + Class = ent:GetClass(), + Pos = ent:GetPos(), + GetAngle = ent:GetAngles() + }) + file.Write("realistic_police/" .. game.GetMap() .. "_rpt_entities" .. ".txt", util.TableToJSON(data)) + end + end) +end +function Realistic_Police.Load(String) + local directory = "realistic_police/" .. game.GetMap() .. String .. ".txt" + if file.Exists(directory, "DATA") then + local data = file.Read(directory, "DATA") + data = util.JSONToTable(data) + for k, v in pairs(data) do + local rpt_entity = ents.Create(v.Class) + rpt_entity:SetPos(v.Pos) + rpt_entity:SetAngles(v.GetAngle) + rpt_entity:Spawn() + rpt_entity.RPTCam = true + rpt_entity.HealthEntity = Realistic_Police.CameraHealth + if isstring(v.CName) then + rpt_entity:SetNWString("rpt_name_camera", v.CName) + end + rpt_entity.DestroyCam = false + rpt_entity.RotateBack = false + if v.Class == "realistic_police_camera" then + rpt_entity:SetRptRotate("nil") + end + local rpt_entityload = rpt_entity:GetPhysicsObject() + if (rpt_entityload:IsValid()) then + rpt_entityload:Wake() + rpt_entityload:EnableMotion(false) + end + end + end +end + +concommand.Add("rpt_save", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + Realistic_Police.SaveEntity() + end +end) +concommand.Add("rpt_cleaupentities", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + for u, ent in pairs(ents.FindByClass("realistic_police_camera")) do + ent:Remove() + end + end +end ) + +concommand.Add("rpt_removedata", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + if file.Exists("realistic_police/" .. game.GetMap() .. "_rpt_entities" .. ".txt", "DATA") then + file.Delete( "realistic_police/" .. game.GetMap() .. "_rpt_entities" .. ".txt" ) + concommand.Run(ply,"rpt_cleaupentities") + end + end +end) + +concommand.Add("rpt_reloadentities", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + concommand.Run(ply, "rpt_cleaupentities") + Realistic_Police.Load("_rpt_entities") + end +end ) +------------------------------------------------------------------------------------------------------------------------- + +function Realistic_Police.SaveJailEntity() + timer.Simple(1, function() + if #ents.FindByClass("realistic_police_jailer") >= 1 && #ents.FindByClass("realistic_police_bailer") >= 1 then + file.Delete("realistic_police/" .. game.GetMap() .. "_rpt_entities" .. ".txt") + end + local data = {} + for u, ent in pairs(ents.FindByClass("realistic_police_bailer")) do + table.insert(data, { + Class = ent:GetClass(), + Pos = ent:GetPos(), + GetAngle = ent:GetAngles() + }) + file.Write("realistic_police/" .. game.GetMap() .. "_rpt_jailents" .. ".txt", util.TableToJSON(data)) + end + for u, ent in pairs(ents.FindByClass("realistic_police_jailer")) do + table.insert(data, { + Class = ent:GetClass(), + Pos = ent:GetPos(), + GetAngle = ent:GetAngles() + }) + file.Write("realistic_police/" .. game.GetMap() .. "_rpt_jailents" .. ".txt", util.TableToJSON(data)) + end + if #ents.FindByClass("realistic_police_jailpos") >= 1 then + file.Delete("realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt") + end + local data = {} + for u, ent in pairs(ents.FindByClass("realistic_police_jailpos")) do + table.insert(data, { + Pos = ent:GetPos(), + }) + file.Write("realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt", util.TableToJSON(data)) + ent:Remove() + end + end) +end +concommand.Add("rpt_savejailpos", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + Realistic_Police.SaveJailEntity() + end +end) +concommand.Add("rpt_reloadjailent", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + concommand.Run(ply, "rpt_removejail") + Realistic_Police.Load("_rpt_jailents") + end +end ) + +concommand.Add("rpt_removedatajail", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + concommand.Run(ply, "rpt_removejail") + if file.Exists("realistic_police/" .. game.GetMap() .. "_rpt_jailents" .. ".txt", "DATA") then + file.Delete( "realistic_police/" .. game.GetMap() .. "_rpt_jailents" .. ".txt" ) + end + if file.Exists("realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt", "DATA") then + file.Delete( "realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt" ) + end + end +end ) + +concommand.Add("rpt_removejail", function(ply, cmd, args) + if Realistic_Police.AdminRank[ply:GetUserGroup()] then + for u, ent in pairs(ents.FindByClass("realistic_police_jailer")) do + ent:Remove() + end + for u, ent in pairs(ents.FindByClass("realistic_police_bailer")) do + ent:Remove() + end + end +end ) + +------------------------------------------------------------------------------------------------------------------------- + +hook.Add("InitPostEntity", "realistic_policeInit", function() + Realistic_Police.Load("_rpt_entities") + Realistic_Police.Load("_rpt_jailents") +end) + +hook.Add("PostCleanupMap", "realistic_policeLoad", function() + Realistic_Police.Load("_rpt_entities") + Realistic_Police.Load("_rpt_jailents") +end ) + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_advanced.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_advanced.lua new file mode 100644 index 0000000..75f92c3 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_advanced.lua @@ -0,0 +1,4427 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police.ManipulateBoneCuffed = { + ["ValveBiped.Bip01_R_UpperArm"] = Angle(-28,18,-21), + ["ValveBiped.Bip01_L_Hand"] = Angle(0,0,119), + ["ValveBiped.Bip01_L_Forearm"] = Angle(22.5,20,40), + ["ValveBiped.Bip01_L_UpperArm"] = Angle(15, 26, 0), + ["ValveBiped.Bip01_R_Forearm"] = Angle(0,47.5,0), + ["ValveBiped.Bip01_R_Hand"] = Angle(45,34,-15), + ["ValveBiped.Bip01_L_Finger01"] = Angle(0,50,0), + ["ValveBiped.Bip01_R_Finger0"] = Angle(10,2,0), + ["ValveBiped.Bip01_R_Finger1"] = Angle(-10,0,0), + ["ValveBiped.Bip01_R_Finger11"] = Angle(0,-40,0), + ["ValveBiped.Bip01_R_Finger12"] = Angle(0,-30,0) +} +-- 76561198219964944 +-- 76561198219964944 +Realistic_Police.ManipulateBoneSurrender = { + ["ValveBiped.Bip01_R_UpperArm"] = Angle(60,33,118), + ["ValveBiped.Bip01_L_Hand"] = Angle(-8,11,90), + ["ValveBiped.Bip01_L_Forearm"] = Angle(-25,-23,36), + ["ValveBiped.Bip01_R_Forearm"] = Angle(-22,1,15), + ["ValveBiped.Bip01_L_UpperArm"] = Angle(-67,-40,2), + ["ValveBiped.Bip01_R_Hand"] = Angle(30,42,-45), + ["ValveBiped.Bip01_L_Finger01"] = Angle(0,30,0), + ["ValveBiped.Bip01_L_Finger1"] = Angle(0,45,0), + ["ValveBiped.Bip01_L_Finger11"] = Angle(0,45,0), + ["ValveBiped.Bip01_L_Finger2"] = Angle(0,45,0), + ["ValveBiped.Bip01_L_Finger21"] = Angle(0,45,0), + ["ValveBiped.Bip01_L_Finger3"] = Angle(0,45,0), + ["ValveBiped.Bip01_L_Finger31"] = Angle(0,45,0), + ["ValveBiped.Bip01_L_Finger4"] = Angle(0,40,0), + ["ValveBiped.Bip01_L_Finger41"] = Angle(-10,30,0), + ["ValveBiped.Bip01_R_Finger0"] = Angle(0,-40,0), + ["ValveBiped.Bip01_R_Finger11"] = Angle(0,50,20), + ["ValveBiped.Bip01_R_Finger2"] = Angle(10,30,0), + ["ValveBiped.Bip01_R_Finger21"] = Angle(0,80,0), + ["ValveBiped.Bip01_R_Finger22"] = Angle(10,40,0), + ["ValveBiped.Bip01_R_Finger3"] = Angle(0,30,0), + ["ValveBiped.Bip01_R_Finger31"] = Angle(0,80,-0), + ["ValveBiped.Bip01_R_Finger32"] = Angle(0,80,-0), + ["ValveBiped.Bip01_R_Finger4"] = Angle(0,40,0), + ["ValveBiped.Bip01_R_Finger41"] = Angle(0,90,-20), + ["ValveBiped.Bip01_R_Finger42"] = Angle(0,80,-0), +} + +Realistic_Police.BaseVehicles = Realistic_Police.BaseVehicles or {} -- All TDM Cars & LW Cars +Realistic_Police.BaseVehicles = { + ["models/tdmcars/alfa_stradale.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 717.9905, + ["PlateSizeH"] = 329.2647, + ["PlateAngle"] = Angle(0, 0.125, 103.5), + ["PlateVector"] = Vector(-7.1562, -94.2812, 33.4375) + } + }, + ["models/tdmcars/chr_ptcruiser.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 786.3617, + ["PlateSizeH"] = 416.2717, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-7.75, -103.6875, 25.0625) + } + }, + ["models/lonewolfie/nis_skyline_r32.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1029.2994, + ["PlateSizeH"] = 266.879, + ["PlateAngle"] = Angle(0, 0, 107.1875), + ["PlateVector"] = Vector(-10.2812, -109.0312, 25.2812) + } + }, + ["models/tdmcars/gtaiv_airtug.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1395.8636, + ["PlateSizeH"] = 390.8299, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.0938, -47.375, 29.0625) + } + }, + ["models/lonewolfie/morgan_3wheeler.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 620.0, + ["PlateSizeH"] = 475.4545, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-6.3125, -74.3125, 26.2188) + } + }, + ["models/tdmcars/trailers/dump.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4062, -237, 73.8438) + } + }, + ["models/tdmcars/morg_aeross.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4688, -100.0625, 28.3438) + } + }, + ["models/tdmcars/s5.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 78.9688), + ["PlateVector"] = Vector(-13.875, -107.4375, 42.5938) + } + }, + ["models/lonewolfie/2000gtr_stock.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1082.8025, + ["PlateSizeH"] = 285.9873, + ["PlateAngle"] = Angle(0, 0, 100.3125), + ["PlateVector"] = Vector(-10.7812, -117.3438, 32.7812) + } + }, + ["models/tdmcars/audi_r8_spyder.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.625, 81.8125), + ["PlateVector"] = Vector(-13.3438, -103.4062, 39.2188) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -178.625, 90), + ["PlateVector"] = Vector(13.75, 109.3125, 30.875) + } + }, + ["models/lonewolfie/bently_pmcontinental.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1292.5387, + ["PlateSizeH"] = 379.5394, + ["PlateAngle"] = Angle(0.625, 2.0938, 75.9062), + ["PlateVector"] = Vector(-13.3125, -121.6562, 35.9375) + } + }, + ["models/tdmcars/hon_s2000.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 936.3636, + ["PlateSizeH"] = 491.8182, + ["PlateAngle"] = Angle(0, 3, 90), + ["PlateVector"] = Vector(-9.4688, -98.0938, 26.6562) + } + }, + ["models/lonewolfie/corvette_c7r.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-13.1875, -107.2812, 26.5) + } + }, + ["models/lonewolfie/renault_alpine_zar.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1230.9091, + ["PlateSizeH"] = 300.9091, + ["PlateAngle"] = Angle(0, -0.9062, 90), + ["PlateVector"] = Vector(-9.8438, -109.0938, 23.125) + } + }, + ["models/tdmcars/dod_challenger15.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 91.625), + ["PlateVector"] = Vector(-13.1875, -121.8125, 33.4688) + } + }, + ["models/tdmcars/emergency/lex_is300_jamesmay.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1120.1784, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-11.0625, -100, 42.0625) + } + }, + ["models/tdmcars/lambo_diablo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1416.3636, + ["PlateSizeH"] = 295.4545, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-13.6562, -106.4688, 32.9688) + } + }, + ["models/tdmcars/mit_eclipsegsx.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1307.2727, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-12.7812, -100.5312, 33.7188) + } + }, + ["models/tdmcars/vw_beetleconv.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 854.6412, + ["PlateSizeH"] = 387.7499, + ["PlateAngle"] = Angle(0, 0.0312, 70.1562), + ["PlateVector"] = Vector(-8.5625, -94.75, 26.875) + } + }, + ["models/lonewolfie/jaguar_xj220.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 373.8854, + ["PlateAngle"] = Angle(0, -0.125, 90), + ["PlateVector"] = Vector(-13.5, -119.8125, 24.0625) + } + }, + ["models/tdmcars/lam_gallardospyd.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1252.7273, + ["PlateSizeH"] = 295.4545, + ["PlateAngle"] = Angle(0, 3, 90), + ["PlateVector"] = Vector(-11.375, -108.1562, 24.0938) + } + }, + ["models/tdmcars/hsvw427_pol.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 79.125), + ["PlateVector"] = Vector(-13.9062, -114.25, 47.2812) + } + }, + ["models/tdmcars/mitsu_eclipgt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1012.7273, + ["PlateSizeH"] = 442.7273, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-10.5, -103.6875, 34.0938) + } + }, + ["models/lonewolfie/ford_mustang_whitegirl.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 845.8599, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 95.7188), + ["PlateVector"] = Vector(-8.5, -116.125, 29.25) + } + }, + ["models/tdmcars/nissan_silvias15.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 870.9091, + ["PlateSizeH"] = 382.7273, + ["PlateAngle"] = Angle(0, 0, 88.3438), + ["PlateVector"] = Vector(-8.1875, -105.0625, 25.2812) + } + }, + ["models/tdmcars/hud_hornet.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1001.8182, + ["PlateSizeH"] = 388.1818, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-10.4062, -127.875, 28.4062) + } + }, + ["models/tdmcars/trucks/scania_4x2.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.25, 90), + ["PlateVector"] = Vector(-14.25, -131, 39.3125) + } + }, + ["models/tdmcars/gmc_syclone.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 870.9091, + ["PlateSizeH"] = 344.5455, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-8.5312, -110.7188, 26.3438) + } + }, + ["models/tdmcars/vaux_astra12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4375, -105.125, 32.2812) + } + }, + ["models/tdmcars/landrover.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1274.8416, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.8125, 89.75), + ["PlateVector"] = Vector(-12.5, -116, 51.9062) + } + }, + ["models/tdmcars/sub_legacygt10.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1332.9492, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.5, 82.6562), + ["PlateVector"] = Vector(-13.1875, -108.7812, 41.7812) + } + }, + ["models/tdmcars/vw_camper65.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1107.2746, + ["PlateSizeH"] = 463.9351, + ["PlateAngle"] = Angle(0, 0.2188, 79.2188), + ["PlateVector"] = Vector(-11, -99.1875, 39.9375) + } + }, + ["models/lonewolfie/spyker_aileron.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.0312, -105.5312, 28.5) + } + }, + ["models/lonewolfie/suzuki_liana_glx.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1335.0318, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 86.5312), + ["PlateVector"] = Vector(-13.375, -101.25, 41.3438) + } + }, + ["models/tdmcars/gtav/zentorno.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 434.5455, + ["PlateSizeH"] = 257.2727, + ["PlateAngle"] = Angle(0, -3, 90), + ["PlateVector"] = Vector(-4.5625, -104.3125, 20.4688) + } + }, + ["models/lonewolfie/lam_countach.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1258.5987, + ["PlateSizeH"] = 301.2739, + ["PlateAngle"] = Angle(0, -2.125, 90), + ["PlateVector"] = Vector(-12.6875, -99.5625, 30.5938) + } + }, + ["models/tdmcars/civic_typer.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1296.3636, + ["PlateSizeH"] = 382.7273, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-12.625, -96.2812, 41.5312) + } + }, + ["models/tdmcars/minicooper_offroad.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1383.6364, + ["PlateSizeH"] = 300.9091, + ["PlateAngle"] = Angle(0, -3, 90), + ["PlateVector"] = Vector(-13.3125, -78.8438, 62) + } + }, + ["models/tdmcars/trailers/dolly.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0312, 90), + ["PlateVector"] = Vector(-14.0625, -60.1562, 38.25) + } + }, + ["models/lonewolfie/caterham_r500_superlight.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.7812, 81.5625), + ["PlateVector"] = Vector(-13.3125, -72.25, 25.5625) + } + }, + ["models/tdmcars/mini_coopers11.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1307.2727, + ["PlateSizeH"] = 333.6364, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4375, -82.7812, 40.625) + } + }, + ["models/tdmcars/tesla_models.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.5938, -121.875, 42.5938) + } + }, + ["models/tdmcars/crownvic_taxi.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-14.0625, -113.8125, 41.5938) + } + }, + ["models/lonewolfie/ferrari_365gts.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1090.4459, + ["PlateSizeH"] = 335.6688, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-10.7812, -107, 27.1562) + } + }, + ["models/lonewolfie/bugatti_veyron_grandsport.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 881.8182, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.0938, 0.9375, 85.4688), + ["PlateVector"] = Vector(-8.4688, -107.875, 32.4375) + } + }, + ["models/lonewolfie/uaz_3907_jaguar.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1198.5442, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-10.0938, -102.2812, 49.5938) + } + }, + ["models/lonewolfie/2000gtr_speedhunters.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 998.7261, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 95.7188), + ["PlateVector"] = Vector(-9.8125, -115.5625, 29.3438) + } + }, + ["models/tdmcars/aud_rs4avant.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 78.5312), + ["PlateVector"] = Vector(-13.7812, -110.125, 38.125) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 85.0625), + ["PlateVector"] = Vector(13.1875, 108.4688, 23.75) + } + }, + ["models/tdmcars/toy_mr2gt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 775.9468, + ["PlateSizeH"] = 299.9008, + ["PlateAngle"] = Angle(0, 1.0625, 90), + ["PlateVector"] = Vector(-7.8125, -98.2812, 25.375) + } + }, + ["models/tdmcars/trucks/scania_firetruck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 815.1163, + ["PlateSizeH"] = 299.3892, + ["PlateAngle"] = Angle(0, -0.3438, 90), + ["PlateVector"] = Vector(22.375, -163.875, 32.0938) + } + }, + ["models/lonewolfie/kamaz.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1268.3195, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-51.7812, -166.5938, 53) + } + }, + ["models/tdmcars/trucks/vol_fh1612_long.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.1562, 90), + ["PlateVector"] = Vector(-14.2188, -124.5312, 28.3125) + } + }, + ["models/tdmcars/maz_rx7.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 868.7898, + ["PlateSizeH"] = 385.3503, + ["PlateAngle"] = Angle(0, 0, 86.5312), + ["PlateVector"] = Vector(-8.5, -101.9375, 25.1875) + } + }, + ["models/tdmcars/courier_truck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 342.4242, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-14.9375, -158.25, 29.5625) + } + }, + ["models/tdmcars/trucks/freight_argosy.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9688, -169.7188, 35.4375) + } + }, + ["models/tdmcars/gallardo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1318.1818, + ["PlateSizeH"] = 311.8182, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.6562, -102.9062, 22.0625) + } + }, + ["models/lonewolfie/chev_tahoe_police.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(1.625, 0.8125, 94.9062), + ["PlateVector"] = Vector(-14.125, -114.2188, 51.9375) + } + }, + ["models/tdmcars/lambo_murcielagosv.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1.9375, 73), + ["PlateVector"] = Vector(-13.3125, -107.125, 33.1562) + } + }, + ["models/tdmcars/murcielago.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 442.7273, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-13.625, -108.875, 34.7188) + } + }, + ["models/lonewolfie/maz_miata_94.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 937.5796, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.9688), + ["PlateVector"] = Vector(-9.625, -91.5, 36.375) + } + }, + ["models/lonewolfie/for_country_squire.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 876.4331, + ["PlateSizeH"] = 370.0637, + ["PlateAngle"] = Angle(0, -1, 95.7188), + ["PlateVector"] = Vector(-8.375, -132.7188, 22.875) + } + }, + ["models/tdmcars/trucks/vol_fh1612_short.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9688, -116.3438, 32.0938) + } + }, + ["models/lonewolfie/ford_capri_rs3100.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1327.3885, + ["PlateSizeH"] = 370.0637, + ["PlateAngle"] = Angle(0, 0, 86.5312), + ["PlateVector"] = Vector(-13.0625, -103.8125, 35.9375) + } + }, + ["models/tdmcars/trucks/peterbilt_579_med.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2812, 90), + ["PlateVector"] = Vector(-14.1875, -175.1562, 56.6875) + } + }, + ["models/tdmcars/pon_fierogt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 881.8182, + ["PlateSizeH"] = 415.4545, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-9.0312, -97.8438, 22.875) + } + }, + ["models/tdmcars/emergency/for_crownvic.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 774.941, + ["PlateSizeH"] = 293.8112, + ["PlateAngle"] = Angle(0, -361, 81.7812), + ["PlateVector"] = Vector(-7.5938, -123.3438, 41.25) + } + }, + ["models/tdmcars/mas_quattroporte.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-12.4688, -125.6562, 30.625) + } + }, + ["models/lonewolfie/merc_sprinter_boxtruck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1281.5287, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.125, 90), + ["PlateVector"] = Vector(-12.6562, -127.4375, 24.8125) + } + }, + ["models/tdmcars/maz_furai.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.875, -107.125, 22.9375) + } + }, + ["models/tdmcars/pon_firetransam.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 870.9091, + ["PlateSizeH"] = 426.3636, + ["PlateAngle"] = Angle(0, 3, 75.25), + ["PlateVector"] = Vector(-8.4688, -112.5312, 37.25) + } + }, + ["models/tdmcars/gtav/police4.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1045.4545, + ["PlateSizeH"] = 360.9091, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-10, -116.4062, 39.9062) + } + }, + ["models/lonewolfie/jaguar_xfr_pol_und.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 393.6364, + ["PlateAngle"] = Angle(0, 1, 78.5312), + ["PlateVector"] = Vector(-14.375, -113.375, 44.625) + } + }, + ["models/lonewolfie/ford_rs200.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1434.3949, + ["PlateSizeH"] = 350.9554, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.125, -90.625, 35.125) + } + }, + ["models/tdmcars/fer_f430.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 88.3438), + ["PlateVector"] = Vector(-12.2812, -105.5, 30.6875) + } + }, + ["models/lonewolfie/volvo_s60_pol.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1667.2727, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-15.9688, -111.0625, 57.6562) + } + }, + ["models/lonewolfie/subaru_22b.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1189.8089, + ["PlateSizeH"] = 381.5287, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-11.7812, -105.0312, 23.2188) + } + }, + ["models/tdmcars/gtav/policeb.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 478.1818, + ["PlateSizeH"] = 219.0909, + ["PlateAngle"] = Angle(0, 3.9062, 90), + ["PlateVector"] = Vector(-4.8438, -54.6875, 27.6875) + } + }, + ["models/tdmcars/bug_veyronss.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 75.25), + ["PlateVector"] = Vector(-13.8438, -93.5, 34.125) + } + }, + ["models/tdmcars/emergency/chargersrt8.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1042.9289, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-10.6562, -120.875, 33.4062) + } + }, + ["models/tdmcars/dodgeram.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.8438, -122.75, 31.5625) + } + }, + ["models/lonewolfie/chev_suburban_pol_und.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 437.2727, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.25, -129.8125, 51.2188) + } + }, + ["models/tdmcars/emergency/for_taurus_13.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9062, -125.5938, 30.9688) + } + }, + ["models/lonewolfie/ferrari_ff.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 366.242, + ["PlateAngle"] = Angle(0, 0, 77.375), + ["PlateVector"] = Vector(-14, -117.1875, 33.375) + } + }, + ["models/tdmcars/chev_c10.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-13.625, -108.5938, 26.1562) + } + }, + ["models/tdmcars/wrangler.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.0312, -85.7812, 30.7812) + } + }, + ["models/tdmcars/for_raptor.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1187.2727, + ["PlateSizeH"] = 355.4545, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-10.6562, -131.125, 36.75) + } + }, + ["models/lonewolfie/polaris_6x6.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 794.5455, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 75.25), + ["PlateVector"] = Vector(-8.3438, -73.6562, 34.8125) + } + }, + ["models/tdmcars/cad_escalade.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1.625, 81.8125), + ["PlateVector"] = Vector(-14.4062, -123.7812, 47.8438) + } + }, + ["models/lonewolfie/subaru_brz.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 373.8854, + ["PlateAngle"] = Angle(0, -1, 77.375), + ["PlateVector"] = Vector(-13.9688, -100.25, 41.6562) + } + }, + ["models/lonewolfie/renault_alpine.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 914.5455, + ["PlateSizeH"] = 311.8182, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-9.0938, -95.3125, 24.25) + } + }, + ["models/tdmcars/lambo_miuracon.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 268.1818, + ["PlateAngle"] = Angle(0, -4, 90), + ["PlateVector"] = Vector(-14.3125, -103.9062, 30.4688) + } + }, + ["models/lonewolfie/chev_camaro_68.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-13.375, -108.8125, 35.4688) + } + }, + ["models/lonewolfie/chev_impala_09_taxi.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 920.8116, + ["PlateSizeH"] = 399.8423, + ["PlateAngle"] = Angle(0, 0.1875, 84.7188), + ["PlateVector"] = Vector(-9.0625, -119.8438, 31.9688) + } + }, + ["models/tdmcars/for_focussvt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1285.4545, + ["PlateSizeH"] = 355.4545, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-12.2812, -99.1875, 46.125) + } + }, + ["models/lonewolfie/jaguar_xfr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 77.375), + ["PlateVector"] = Vector(-14, -112.6875, 45.0625) + } + }, + ["models/lonewolfie/lykan_hypersport.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1182.1656, + ["PlateSizeH"] = 354.7771, + ["PlateAngle"] = Angle(0, 1, 77.375), + ["PlateVector"] = Vector(-12.4062, -102.9688, 29.1562) + } + }, + ["models/tdmcars/chev_corv_gsc.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4062, -103.25, 34.125) + } + }, + ["models/lonewolfie/ren_5turbo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 355.4545, + ["PlateAngle"] = Angle(0, 0, 65.4375), + ["PlateVector"] = Vector(-13.5, -80.5625, 38.4688) + } + }, + ["models/lonewolfie/shelby_glhs.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 81.9688), + ["PlateVector"] = Vector(-14.2812, -93.75, 36.875) + } + }, + ["models/lonewolfie/ford_capri_rs3100_police.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1372.7273, + ["PlateSizeH"] = 480.9091, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.2188, -104.8125, 36.75) + } + }, + ["models/lonewolfie/lada_2108.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1452.6433, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.6562, 90), + ["PlateVector"] = Vector(-14.5938, -88.6562, 38.2812) + } + }, + ["models/tdmcars/zondagr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 892.7273, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -3, 90), + ["PlateVector"] = Vector(5.8438, -107.1562, 18.7188) + } + }, + ["models/tdmcars/trailers/reefer3000r.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.125, -264.625, 53.375) + } + }, + ["models/tdmcars/lex_is300.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1122.6864, + ["PlateSizeH"] = 345.107, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-10.25, -99.6562, 41.375) + } + }, + ["models/tdmcars/gtav/turismor.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 696.3636, + ["PlateSizeH"] = 306.3636, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-6.1562, -106.0312, 20.4062) + } + }, + ["models/tdmcars/gmc_sierra.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.0625, -133.625, 48.9688) + } + }, + ["models/tdmcars/hummerh1_open.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 903.6364, + ["PlateSizeH"] = 344.5455, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-22.375, -112.4375, 35.0938) + } + }, + ["models/tdmcars/bmwm5e34.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 179, 90), + ["PlateVector"] = Vector(13.8125, 115, 24.4062) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-14.7812, -112.2812, 42.3438) + } + }, + ["models/tdmcars/fer_250gto.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 94.9062), + ["PlateVector"] = Vector(-14.0312, -102.2812, 27.875) + } + }, + ["models/tdmcars/del_dmc.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 85.0625), + ["PlateVector"] = Vector(-14.4688, -101.7812, 45.2188) + } + }, + ["models/tdmcars/mclaren_mp412cgt3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1472.6115, + ["PlateSizeH"] = 324.2038, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.375, -106.4375, 28.2812) + } + }, + ["models/tdmcars/cad_lmp.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.9688, -113.9375, 23.0312) + } + }, + ["models/tdmcars/reventon_roadster.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.1875, 0.25, 66.375), + ["PlateVector"] = Vector(-13.875, -111.1875, 35.6875) + } + }, + ["models/tdmcars/ast_db5.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 333.6364, + ["PlateAngle"] = Angle(0, -0.9062, 85.0625), + ["PlateVector"] = Vector(-13.9375, -106.5, 34.3438) + } + }, + ["models/tdmcars/hsvw427.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1203.6445, + ["PlateSizeH"] = 370.0093, + ["PlateAngle"] = Angle(0, 0, 77.5), + ["PlateVector"] = Vector(-11.4375, -114.375, 46.5938) + } + }, + ["models/tdmcars/emergency/mer_eclass.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1152.407, + ["PlateSizeH"] = 329.8089, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-11.5, -120.9062, 44.6875) + } + }, + ["models/tdmcars/auditt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1296.6116, + ["PlateSizeH"] = 324.2313, + ["PlateAngle"] = Angle(0.125, -0.5, 68.9688), + ["PlateVector"] = Vector(-9.7188, -90.9062, 33.1875) + } + }, + ["models/tdmcars/trailers/dolly2.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.9062, 80.625), + ["PlateVector"] = Vector(-14.4375, -9.5, 30.7188) + } + }, + ["models/lonewolfie/volvo_s60.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.25, -111, 58.375) + } + }, + ["models/tdmcars/bmw_1m.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1068.3395, + ["PlateSizeH"] = 359.4323, + ["PlateAngle"] = Angle(0, 0, 79.125), + ["PlateVector"] = Vector(-10.0312, -97.6875, 42.1562) + } + }, + ["models/tdmcars/242turbo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.1875, 0.0312, 90), + ["PlateVector"] = Vector(-14.1562, -107.1562, 31.9062) + } + }, + ["models/lonewolfie/daf_xfeuro6_4x2.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-12.75, -122.4375, 33.3438) + } + }, + ["models/tdmcars/ast_rapide.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-14.0312, -118.5, 35) + } + }, + ["models/lonewolfie/dodge_daytona.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1132.7273, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 104.7188), + ["PlateVector"] = Vector(-10.75, -143.5938, 34.375) + } + }, + ["models/lonewolfie/dodge_monaco.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 838.1818, + ["PlateSizeH"] = 300.9091, + ["PlateAngle"] = Angle(0, 0.3438, 88.3438), + ["PlateVector"] = Vector(-8.0625, -133.6875, 27.9062) + } + }, + ["models/lonewolfie/merc_sprinter_swb.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1396.1783, + ["PlateSizeH"] = 350.9554, + ["PlateAngle"] = Angle(0, -3, 88.8438), + ["PlateVector"] = Vector(-31.25, -111.3438, 28.3125) + } + }, + ["models/tdmcars/gtav/futo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 652.7273, + ["PlateSizeH"] = 284.5455, + ["PlateAngle"] = Angle(0, -2, 78.5312), + ["PlateVector"] = Vector(-6.3438, -84.5312, 32.0938) + } + }, + ["models/lonewolfie/smart_fortwo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 373.8854, + ["PlateAngle"] = Angle(0, 0, 86.5312), + ["PlateVector"] = Vector(-13.9688, -63.9062, 47.5312) + } + }, + ["models/tdmcars/dod_charger12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-13.8438, -125.5, 35.25) + } + }, + ["models/tdmcars/chargersrt8.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9062, -119, 33.5312) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -178, 90), + ["PlateVector"] = Vector(14.3438, 116.9375, 21.9688) + } + }, + ["models/tdmcars/trucks/peterbilt_579.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1434.2601, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2812, 90), + ["PlateVector"] = Vector(-14.125, -123.375, 51.5938) + } + }, + ["models/tdmcars/trailers/singleaxlebox.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.5938, -180.5, 54.0625) + } + }, + ["models/tdmcars/aston_v12vantage.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1265.2598, + ["PlateSizeH"] = 354.7094, + ["PlateAngle"] = Angle(0, 0, 80.75), + ["PlateVector"] = Vector(-12.5938, -109.4375, 27.75) + } + }, + ["models/lonewolfie/cad_eldorado_limo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 740.0, + ["PlateSizeH"] = 448.1818, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-7.75, -241.4688, 28.6875) + } + }, + ["models/lonewolfie/ford_f350_ambu.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 885.9261, + ["PlateSizeH"] = 570.4733, + ["PlateAngle"] = Angle(0, 0.2188, 90), + ["PlateVector"] = Vector(-9.6875, -170.5, 40.8438) + } + }, + ["models/lonewolfie/lan_delta_int.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 358.5987, + ["PlateAngle"] = Angle(0, 0, 75.0938), + ["PlateVector"] = Vector(-13.5312, -94.1562, 40.125) + } + }, + ["models/tdmcars/ktm_xbow.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.5938, -92.125, 30.125) + } + }, + ["models/lonewolfie/mclaren_f1_gtr97.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 104.7188), + ["PlateVector"] = Vector(-13.125, -122.125, 22.75) + } + }, + ["models/tdmcars/bmw_isetta.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1085.4801, + ["PlateSizeH"] = 251.3539, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-10.3438, -48.625, 22) + } + }, + ["models/lonewolfie/uaz_452.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-15.0625, -91.1562, 32.7812) + } + }, + ["models/tdmcars/lex_isf.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1234.7547, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2188, 88.3125), + ["PlateVector"] = Vector(-12.3438, -104.875, 42.75) + } + }, + ["models/tdmcars/jee_willys.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.25, -84.4688, 33.5) + } + }, + ["models/tdmcars/bmwm3e92.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-13.3438, -105.2812, 34.7812) + } + }, + ["models/tdmcars/ferrari250gt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-12.8438, -98.9688, 29.25) + } + }, + ["models/lonewolfie/bentley_arnage_t.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.5938, 0.0312, 72.375), + ["PlateVector"] = Vector(-14.0938, -113.0625, 40.0938) + } + }, + ["models/lonewolfie/dodge_charger_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1110.9091, + ["PlateSizeH"] = 371.8182, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-10.875, -127.5938, 32.75) + } + }, + ["models/tdmcars/mini_clubman.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1241.8182, + ["PlateSizeH"] = 311.8182, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-12.25, -91.625, 23.3125) + } + }, + ["models/tdmcars/mer_slr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 312.7389, + ["PlateAngle"] = Angle(0, 0, 72.7812), + ["PlateVector"] = Vector(-14.1562, -106.625, 43.3125) + } + }, + ["models/lonewolfie/subaru_impreza_2004.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1335.0318, + ["PlateSizeH"] = 381.5287, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.4688, -105.4062, 25.6875) + } + }, + ["models/lonewolfie/tvr_cerbera12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1437.1292, + ["PlateSizeH"] = 333.1158, + ["PlateAngle"] = Angle(0, 0.5625, 58.125), + ["PlateVector"] = Vector(-14.4062, -102.25, 30.25) + } + }, + ["models/tdmcars/chevelless.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-13.4688, -125.5312, 31.5) + } + }, + ["models/lonewolfie/tvr_sagaris.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 980.3453, + ["PlateSizeH"] = 273.6715, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-9.6875, -100.5625, 34.8438) + } + }, + ["models/tdmcars/bmwm5e60.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.4688, 92.9375), + ["PlateVector"] = Vector(-13.5938, -113.9688, 33.5938) + } + }, + ["models/lonewolfie/suzuki_kingquad.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 4.8438, 86.5312), + ["PlateVector"] = Vector(-14.0625, -41, 33.1875) + } + }, + ["models/lonewolfie/ren_meganers.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 72), + ["PlateVector"] = Vector(-13.7812, -103.3438, 31.1875) + } + }, + ["models/tdmcars/focusrs.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9062, -103.7188, 41.6562) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -179, 90), + ["PlateVector"] = Vector(15.7188, 101.75, 23.125) + } + }, + ["models/tdmcars/fer_enzo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 85.0625), + ["PlateVector"] = Vector(-12.6875, -113.3125, 30.5) + } + }, + ["models/tdmcars/gmc_sierralow.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1274.5455, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.2188, -129.5938, 41.8125) + } + }, + ["models/lonewolfie/dodge_charger_2015_undercover.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1064.9304, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5, 75.25), + ["PlateVector"] = Vector(-10.3125, -125.4375, 33.4062) + } + }, + ["models/lonewolfie/lam_huracan.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 107.1875), + ["PlateVector"] = Vector(-13.6875, -99.8438, 33.4375) + } + }, + ["models/tdmcars/bug_eb110.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1399.6793, + ["PlateSizeH"] = 356.0162, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.0625, -104.75, 36.6562) + } + }, + ["models/lonewolfie/chev_impala_09.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 839.5655, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 79.9375), + ["PlateVector"] = Vector(-8.0625, -119.0312, 31.9375) + } + }, + ["models/lonewolfie/dodge_viper.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1165.4545, + ["PlateSizeH"] = 355.4545, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-12, -103.7188, 43.7812) + } + }, + ["models/tdmcars/audi_r8_plus.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 72), + ["PlateVector"] = Vector(-13.2812, -101.1562, 37.875) + } + }, + ["models/lonewolfie/citroen_ds3_rally.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 76.3438), + ["PlateVector"] = Vector(-13.9062, -92.5, 24.5625) + } + }, + ["models/lonewolfie/dodge_monaco_police.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 838.1818, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-8.5312, -134.25, 30.25) + } + }, + ["models/tdmcars/mer_ml63.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.9375, 90), + ["PlateVector"] = Vector(-13.5625, -116.8438, 27.6562) + } + }, + ["models/lonewolfie/nissan_silvia_s14.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1403.8217, + ["PlateSizeH"] = 297.4522, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-14.0938, -112.7188, 24.9688) + } + }, + ["models/lonewolfie/trailer_medbox.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.625, -107.4688, 29.0625) + } + }, + ["models/tdmcars/hummerh1.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-14.3125, -112.9688, 37.5938) + } + }, + ["models/tdmcars/for_f350.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 178, 90), + ["PlateVector"] = Vector(13.75, 137.2188, 37.5) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-14.4375, -158.3125, 39.125) + } + }, + ["models/lonewolfie/lancia_037_stradale.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1335.0318, + ["PlateSizeH"] = 347.1338, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.3125, -95.0312, 22.9375) + } + }, + ["models/lonewolfie/porsche_911_rsr_74.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 355.4545, + ["PlateAngle"] = Angle(0, 0, 88.3438), + ["PlateVector"] = Vector(-16.5938, -109.4375, 22.25) + } + }, + ["models/tdmcars/cooper65.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1350.9091, + ["PlateSizeH"] = 300.9091, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.5625, -75.6875, 31.3438) + } + }, + ["models/tdmcars/nissan_gtr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1234.5207, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.625, 78.5312), + ["PlateVector"] = Vector(-12.75, -109.0312, 34.7188) + } + }, + ["models/tdmcars/cayenne.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-13.3125, -115.75, 29.75) + } + }, + ["models/lonewolfie/nissan_silvia_s14_wide.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1312.1019, + ["PlateSizeH"] = 343.3121, + ["PlateAngle"] = Angle(0, -0.4375, 100.3125), + ["PlateVector"] = Vector(-12.5312, -115.1875, 25.3125) + } + }, + ["models/tdmcars/scion_frs.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.7812, -100.5312, 44.4062) + } + }, + ["models/tdmcars/coltralliart.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 442.7273, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-12.375, -89.6875, 25.7812) + } + }, + ["models/lonewolfie/mercedes_actros_2014_4x2.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14, -122.75, 33.5) + } + }, + ["models/lonewolfie/jaguar_xfr_pol.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5, 80.1875), + ["PlateVector"] = Vector(-13.7188, -112.8125, 45.0938) + } + }, + ["models/tdmcars/trucks/gmc_c5500.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.3125, 90), + ["PlateVector"] = Vector(-14.125, -210.6562, 23.6875) + } + }, + ["models/tdmcars/noblem600.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1361.8182, + ["PlateSizeH"] = 284.5455, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-13.4688, -100.4375, 27.1875) + } + }, + ["models/lonewolfie/nissan_sileighty.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 876.4331, + ["PlateSizeH"] = 350.9554, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-8.8125, -109.625, 23.9062) + } + }, + ["models/tdmcars/chev_blazer.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1516.0184, + ["PlateSizeH"] = 354.5373, + ["PlateAngle"] = Angle(0, -0.5, 90), + ["PlateVector"] = Vector(-15.0625, -105.75, 30.3125) + } + }, + ["models/lonewolfie/ford_falcon_xb.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 960.5096, + ["PlateSizeH"] = 312.7389, + ["PlateAngle"] = Angle(0, 1, 81.9688), + ["PlateVector"] = Vector(-9.4688, -118.125, 26.8125) + } + }, + ["models/lonewolfie/honda_nsxr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 934.3685, + ["PlateSizeH"] = 356.4911, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-9.5625, -109.8438, 28.5625) + } + }, + ["models/tdmcars/chev_impala96.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 968.9642, + ["PlateSizeH"] = 429.2049, + ["PlateAngle"] = Angle(0, -0.0938, 85.1875), + ["PlateVector"] = Vector(-9.7812, -139.2188, 39.0938) + } + }, + ["models/lonewolfie/chev_suburban.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-14.4062, -134.9375, 32.125) + } + }, + ["models/lonewolfie/chev_suburban_pol.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1601.8182, + ["PlateSizeH"] = 410.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-15.2812, -129.2188, 50.3125) + } + }, + ["models/lonewolfie/fiat_595.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 960.5096, + ["PlateSizeH"] = 282.1656, + ["PlateAngle"] = Angle(0, 1, 68.1875), + ["PlateVector"] = Vector(-9.2188, -68.3438, 33.0625) + } + }, + ["models/lonewolfie/nissan_silvia_s14_works.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 98), + ["PlateVector"] = Vector(-13.6875, -115.3125, 26.625) + } + }, + ["models/tdmcars/landrover_defender.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1043.5937, + ["PlateSizeH"] = 357.3024, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-36.0312, -94.7812, 53.5312) + } + }, + ["models/tdmcars/maz_speed3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1281.5287, + ["PlateSizeH"] = 335.6688, + ["PlateAngle"] = Angle(0, 1, 93.4375), + ["PlateVector"] = Vector(-12.375, -109.0312, 26.2188) + } + }, + ["models/tdmcars/bmw_340i.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.5625, -108.875, 42.4375) + } + }, + ["models/lonewolfie/trailer_glass.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-15.5938, -221.0625, 35.5312) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-12.9688, -226.9375, 24.125) + } + }, + ["models/tdmcars/dod_ram_1500.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13, -128.7188, 34.0312) + } + }, + ["models/tdmcars/zondac12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 663.6364, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 3, 90), + ["PlateVector"] = Vector(-27.625, -101.4062, 18.2188) + } + }, + ["models/tdmcars/gt05.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-12.6875, -111.2812, 36.9062) + } + }, + ["models/lonewolfie/uaz_469b.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1.625, 90), + ["PlateVector"] = Vector(-30.7188, -80.3125, 40.4375) + } + }, + ["models/lonewolfie/ford_foxbody_stock.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 845.8599, + ["PlateSizeH"] = 400.6369, + ["PlateAngle"] = Angle(0, 1, 86.5312), + ["PlateVector"] = Vector(-8.5, -106.2188, 38.1562) + } + }, + ["models/tdmcars/rx8.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 373.8854, + ["PlateAngle"] = Angle(0, 1, 81.9688), + ["PlateVector"] = Vector(-14.0938, -104.1562, 32.625) + } + }, + ["models/lonewolfie/lam_reventon.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 381.5287, + ["PlateAngle"] = Angle(0, -2, 75.0938), + ["PlateVector"] = Vector(-14.0312, -102, 34.8125) + } + }, + ["models/tdmcars/emergency/mitsu_evox.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1365.8495, + ["PlateSizeH"] = 386.7, + ["PlateAngle"] = Angle(0, 0.0312, 90), + ["PlateVector"] = Vector(-13.6562, -105.125, 27.4688) + } + }, + ["models/tdmcars/bmw507.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9375, -104.9688, 31.2188) + } + }, + ["models/tdmcars/for_f100.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.6875, -112.7188, 37.8125) + } + }, + ["models/lonewolfie/uaz_31519.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.1562, -80.9062, 31.1562) + } + }, + ["models/tdmcars/gtav/gauntlet.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 685.4545, + ["PlateSizeH"] = 360.9091, + ["PlateAngle"] = Angle(0, 0, 111.25), + ["PlateVector"] = Vector(-6.8125, -114.625, 23.7812) + } + }, + ["models/tdmcars/mer_300slgull.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1059.8726, + ["PlateSizeH"] = 331.8471, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-10.5625, -110.7812, 22.7812) + } + }, + ["models/tdmcars/ford_transit.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1296.3636, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-13.3125, -112.0938, 44.6562) + } + }, + ["models/tdmcars/for_focus_rs16.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-13.6562, -100.6562, 49.2188) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(14.6562, 104.6562, 22.1562) + } + }, + ["models/tdmcars/fer_f12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-12.875, -110.0312, 36.3125) + } + }, + ["models/tdmcars/gtav/rebel.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 663.6364, + ["PlateSizeH"] = 355.4545, + ["PlateAngle"] = Angle(0, -177, 90), + ["PlateVector"] = Vector(6.0625, 107.2812, 41.5) + } + }, + ["models/lonewolfie/ferrari_458.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 70.5), + ["PlateVector"] = Vector(-14.0312, -109.8438, 32.6562) + } + }, + ["models/lonewolfie/astonmartin_cygnet.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1199.8871, + ["PlateSizeH"] = 379.115, + ["PlateAngle"] = Angle(0, -1.0938, 72.75), + ["PlateVector"] = Vector(-12.4375, -69.4688, 30.1562) + } + }, + ["models/lonewolfie/trailer_transporter.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5625, 90), + ["PlateVector"] = Vector(-14.6562, -276.5312, 30.1875) + } + }, + ["models/tdmcars/gtav/mesa3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 532.7273, + ["PlateSizeH"] = 279.0909, + ["PlateAngle"] = Angle(0, -3, 90), + ["PlateVector"] = Vector(24.875, -94.9688, 52.0625) + } + }, + ["models/tdmcars/dod_challenger70.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1136.1503, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0625, 109.75), + ["PlateVector"] = Vector(-10.2812, -119.1875, 30.8125) + } + }, + ["models/tdmcars/golfvr6_mk3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 948.4002, + ["PlateSizeH"] = 407.0997, + ["PlateAngle"] = Angle(0, 0.5938, 90), + ["PlateVector"] = Vector(-9.9062, -90.4688, 41.5312) + } + }, + ["models/lonewolfie/chev_nascar.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-12.75, -128.4375, 31.4062) + } + }, + ["models/tdmcars/gtav/patriot2.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1700.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-17.2812, -149.1875, 55.9688) + } + }, + ["models/tdmcars/vol_s60.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 379.83, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.25, -112.625, 16.4062) + } + }, + ["models/tdmcars/vw_golfr32.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1249.8568, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.3125, 90), + ["PlateVector"] = Vector(-12.1875, -98.1562, 27.7812) + } + }, + ["models/tdmcars/nis_370z.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 513.6364, + ["PlateAngle"] = Angle(0, -1, 78.5312), + ["PlateVector"] = Vector(-13.6562, -99.4688, 33.6875) + } + }, + ["models/tdmcars/bmwm1.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1401.8752, + ["PlateSizeH"] = 311.0037, + ["PlateAngle"] = Angle(0, 0, 68.5), + ["PlateVector"] = Vector(-13.7188, -101.7812, 36.6875) + } + }, + ["models/lonewolfie/trailer_profiliner.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.1562, -265.875, 32.7812) + } + }, + ["models/tdmcars/trailers/flatbed.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.4688, 90), + ["PlateVector"] = Vector(-14.1562, -296.1562, 50) + } + }, + ["models/tdmcars/mx5.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1304.4586, + ["PlateSizeH"] = 373.8854, + ["PlateAngle"] = Angle(0, 0, 77.375), + ["PlateVector"] = Vector(-12.875, -89.625, 37.1562) + } + }, + ["models/tdmcars/jag_ftype.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 78.5312), + ["PlateVector"] = Vector(-13.5312, -105.25, 40.125) + } + }, + ["models/lonewolfie/mer_c63_amg.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1327.3885, + ["PlateSizeH"] = 385.3503, + ["PlateAngle"] = Angle(0, 0, 75.0938), + ["PlateVector"] = Vector(-13.4062, -109.3125, 51.4062) + } + }, + ["models/tdmcars/mer_300sel.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 975.7962, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-9.5625, -119.5312, 31.9375) + } + }, + ["models/tdmcars/mclaren_p1.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 350.9554, + ["PlateAngle"] = Angle(0, 2, 98), + ["PlateVector"] = Vector(-13.5625, -103.4375, 22.2188) + } + }, + ["models/tdmcars/vw_sciroccor.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1039.6557, + ["PlateSizeH"] = 327.6685, + ["PlateAngle"] = Angle(0.125, 0.4375, 73.4688), + ["PlateVector"] = Vector(-10.875, -96.0938, 30.1562) + } + }, + ["models/tdmcars/landrover12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.6875, 90), + ["PlateVector"] = Vector(-14.0312, -116.7188, 53.8438) + } + }, + ["models/tdmcars/ford_coupe_40.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 78.5312), + ["PlateVector"] = Vector(-12.2188, -105.1562, 28.8438) + } + }, + ["models/tdmcars/nis_leaf.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1274.5455, + ["PlateSizeH"] = 284.5455, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-12.8438, -102.2188, 25.0312) + } + }, + ["models/lonewolfie/lotus_esprit_80.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1335.8376, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.25, 90), + ["PlateVector"] = Vector(-13.3438, -95.9375, 39.3438) + } + }, + ["models/tdmcars/trailers/car_trailer.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5, 90), + ["PlateVector"] = Vector(-12.875, -94.4375, 28.7188) + } + }, + ["models/lonewolfie/trailer_livestock.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.75, 90), + ["PlateVector"] = Vector(-13.4688, -246.5938, 31.9062) + } + }, + ["models/tdmcars/emergency/dod_charger12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 843.1667, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 85.8438), + ["PlateVector"] = Vector(-8.25, -125.25, 34.9688) + } + }, + ["models/tdmcars/kia_ceed.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.4688, 90), + ["PlateVector"] = Vector(-12.4062, -100.1562, 26.6875) + } + }, + ["models/lonewolfie/hummer_h1_tc.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1067.5159, + ["PlateSizeH"] = 457.9618, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-51.0625, -118.75, 42.25) + } + }, + ["models/lonewolfie/hot_twinmill.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 762.5186, + ["PlateSizeH"] = 283.7536, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-7.6875, -90.3438, 29) + } + }, + ["models/tdmcars/vol_xc90.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-13.9375, -118.1875, 26.0312) + } + }, + ["models/tdmcars/for_mustanggt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 838.1818, + ["PlateSizeH"] = 486.3636, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-7.9062, -114.7188, 32.0938) + } + }, + ["models/lonewolfie/merc_sprinter_openchassis.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1281.5287, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-12.625, -127.4375, 25.5625) + } + }, + ["models/lonewolfie/shelby_gt500kr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 838.2166, + ["PlateSizeH"] = 301.2739, + ["PlateAngle"] = Angle(0, 0, 91.125), + ["PlateVector"] = Vector(-8.2812, -113.0938, 28) + } + }, + ["models/tdmcars/trucks/scania_4x2_nojiggle.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.375, 90), + ["PlateVector"] = Vector(-14.9688, -131.0312, 38.9062) + } + }, + ["models/tdmcars/for_she_gt500.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 459.0909, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.3125, -113.625, 38.1562) + } + }, + ["models/lonewolfie/trailer_schmied.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.25, -163.9688, 69.25) + } + }, + ["models/tdmcars/bowler_exrs.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2188, 98.0312), + ["PlateVector"] = Vector(-13.4062, -106.1562, 31.875) + } + }, + ["models/lonewolfie/tvr_tuscans.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 903.6364, + ["PlateSizeH"] = 246.3636, + ["PlateAngle"] = Angle(0, -3, 90), + ["PlateVector"] = Vector(-8.3438, -99.6875, 23.2812) + } + }, + ["models/tdmcars/kia_fortekoup.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1209.0909, + ["PlateSizeH"] = 290.0, + ["PlateAngle"] = Angle(0, 3, 90), + ["PlateVector"] = Vector(-12.2188, -110.5312, 27.8125) + } + }, + ["models/tdmcars/skyline_r34.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 903.6364, + ["PlateSizeH"] = 426.3636, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-9.125, -107.1562, 27.9688) + } + }, + ["models/tdmcars/bus.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.0625, -269.125, 58.5625) + } + }, + ["models/tdmcars/350z.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 834.8114, + ["PlateSizeH"] = 420.6626, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-8.1875, -103.875, 31.2812) + } + }, + ["models/tdmcars/gmcvan.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 849.0909, + ["PlateSizeH"] = 400.5, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-7.6875, -111.9062, 23.6875) + } + }, + ["models/tdmcars/aud_s4.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -178, 90), + ["PlateVector"] = Vector(13.8125, 106.8438, 27.125) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 344.5455, + ["PlateAngle"] = Angle(0, 1, 88.3438), + ["PlateVector"] = Vector(-13.5, -109.9062, 41.9062) + } + }, + ["models/lonewolfie/trailer_truck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.3125, 90.125), + ["PlateVector"] = Vector(-13.9688, -254.25, 17.3438) + } + }, + ["models/tdmcars/mitsu_evox.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-13.125, -106.1562, 44.4375) + } + }, + ["models/tdmcars/gtav/police3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 718.1818, + ["PlateSizeH"] = 344.5455, + ["PlateAngle"] = Angle(0, 1, 68.7188), + ["PlateVector"] = Vector(-7.25, -111.5, 47.9688) + } + }, + ["models/tdmcars/fer_lafer.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-14.4375, -104.3125, 38.7188) + } + }, + ["models/tdmcars/fer_f50.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-13.75, -108.3438, 24.4375) + } + }, + ["models/tdmcars/chr_300c_12.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.625, 75.25), + ["PlateVector"] = Vector(-13.625, -117.6562, 32.5312) + } + }, + ["models/tdmcars/trucks/kenworth_t800.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-13.0938, -183.6562, 37.8438) + } + }, + ["models/tdmcars/jeep_wrangler_fnf.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 363.8354, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-11.4375, -113.9062, 41.6875) + } + }, + ["models/tdmcars/zen_st1.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1285.2635, + ["PlateSizeH"] = 319.7326, + ["PlateAngle"] = Angle(-0.2188, -0.8438, 75.8125), + ["PlateVector"] = Vector(-12.8438, -112.375, 25.5) + } + }, + ["models/tdmcars/jeep_wrangler88.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 750.9091, + ["PlateSizeH"] = 339.0909, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-37.0938, -81.0312, 41.4375) + } + }, + ["models/tdmcars/bmw_m3_gtr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1301.7263, + ["PlateSizeH"] = 371.4849, + ["PlateAngle"] = Angle(-0.5938, -0.2188, 77.4688), + ["PlateVector"] = Vector(-13.0938, -101.5, 42.4062) + } + }, + ["models/lonewolfie/nis_s13.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1036.9427, + ["PlateSizeH"] = 377.707, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-10.3438, -109.3438, 23.1875) + } + }, + ["models/tdmcars/trailers/reefer3000r_long.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-14.0625, -315.4062, 52.9688) + } + }, + ["models/lonewolfie/asc_kz1r.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 330.472, + ["PlateAngle"] = Angle(0.4375, 0.0625, 84.75), + ["PlateVector"] = Vector(-13.9688, -107.1875, 32.1875) + } + }, + ["models/lonewolfie/dodge_charger_2015_police.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1167.9345, + ["PlateSizeH"] = 331.3393, + ["PlateAngle"] = Angle(0, 0, 77.7812), + ["PlateVector"] = Vector(-11.8125, -125.5625, 32.875) + } + }, + ["models/tdmcars/jag_etype.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1176.3636, + ["PlateSizeH"] = 333.6364, + ["PlateAngle"] = Angle(0, 4, 90), + ["PlateVector"] = Vector(-10.4688, -103.8438, 28.0625) + } + }, + ["models/tdmcars/cit_2cv.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 338.3337, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.5938, -88.5625, 31.0938) + } + }, + ["models/lonewolfie/mercedes_actros_6x4.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -2.9062, 90), + ["PlateVector"] = Vector(-12.9375, -156.9062, 33.0312) + } + }, + ["models/tdmcars/citroen_c1.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180.9062, 90), + ["PlateVector"] = Vector(13.3125, 84.75, 26.5625) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4375, -82.2188, 21.9688) + } + }, + ["models/lonewolfie/austin_healey_3000.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.2188, -99.875, 23.5938) + } + }, + ["models/tdmcars/trailers/gooseneck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.125, 90), + ["PlateVector"] = Vector(-13.5625, -230.0938, 46.25) + } + }, + ["models/tdmcars/bmw_m6_13.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 78.5312), + ["PlateVector"] = Vector(-14.0625, -116.9062, 36.5625) + } + }, + ["models/lonewolfie/gmc_typhoon.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 838.2166, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-8.5, -97.0312, 26.2812) + } + }, + ["models/lonewolfie/subaru_impreza_2001.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1380.8917, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.7188, -107, 24.125) + } + }, + ["models/lonewolfie/mer_g65_6x6.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1411.465, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 91.125), + ["PlateVector"] = Vector(-14.1562, -161.375, 44.0625) + } + }, + ["models/lonewolfie/bentley_blower.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1025.2608, + ["PlateSizeH"] = 371.5492, + ["PlateAngle"] = Angle(0, -0.0625, 90), + ["PlateVector"] = Vector(-2.25, -87.4375, 26.5312) + } + }, + ["models/tdmcars/ferrari512tr.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 952.836, + ["PlateSizeH"] = 306.8675, + ["PlateAngle"] = Angle(0, 0, 100.5938), + ["PlateVector"] = Vector(-9.6562, -107.0938, 24.3438) + } + }, + ["models/tdmcars/golf_mk2.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.0625, 0.5625, 84.75), + ["PlateVector"] = Vector(-13.9375, -90.9375, 46.8125) + } + }, + ["models/lonewolfie/maz.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-13.125, -191.375, 68.125) + } + }, + ["models/tdmcars/gtav/utillitruck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 674.5455, + ["PlateSizeH"] = 388.1818, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(32.5625, -136.5625, 29.0312) + } + }, + ["models/lonewolfie/mer_g65.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1335.0318, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 86.5312), + ["PlateVector"] = Vector(-13.375, -96.9375, 38.125) + } + }, + ["models/lonewolfie/mercedes_slk55_amg.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1105.7325, + ["PlateSizeH"] = 297.4522, + ["PlateAngle"] = Angle(0, 0, 68.1875), + ["PlateVector"] = Vector(-10.9688, -90.875, 40.0625) + } + }, + ["models/lonewolfie/uaz_452_ambu.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-14.0625, -90.9688, 33.9375) + } + }, + ["models/tdmcars/spark.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9062, -85, 42.5) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -179, 90), + ["PlateVector"] = Vector(13.5312, 88.9375, 25.3438) + } + }, + ["models/tdmcars/bug_veyron.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 78.5312), + ["PlateVector"] = Vector(-13.0938, -108.4688, 34.4688) + } + }, + ["models/tdmcars/jeep_grandche.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-11.7188, -114.5312, 56.5) + } + }, + ["models/lonewolfie/ferrari_laferrari.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1052.2293, + ["PlateSizeH"] = 320.3822, + ["PlateAngle"] = Angle(0, 0, 65.9062), + ["PlateVector"] = Vector(-10.5938, -106.6875, 28.0625) + } + }, + ["models/tdmcars/chev_camzl1.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.7188, -112.5938, 37.0312) + } + }, + ["models/tdmcars/chev_stingray427.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 121.0625), + ["PlateVector"] = Vector(-13.8438, -107.25, 28.8438) + } + }, + ["models/tdmcars/mclaren_f1.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1220.3822, + ["PlateSizeH"] = 347.1338, + ["PlateAngle"] = Angle(0, 0, 93.4375), + ["PlateVector"] = Vector(-12.3125, -107.5625, 31.3438) + } + }, + ["models/tdmcars/audir8.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 68.7188), + ["PlateVector"] = Vector(-15.5938, -102.5312, 32.875) + } + }, + ["models/lonewolfie/trailer_panel.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-12.75, -258.5625, 40.9688) + } + }, + ["models/tdmcars/alfa_giulietta.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1152.3438, + ["PlateSizeH"] = 340.1955, + ["PlateAngle"] = Angle(0, 0, 77.6562), + ["PlateVector"] = Vector(-11.4375, -100.375, 29.7812) + } + }, + ["models/tdmcars/dod_ram_3500.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.8438, -155.375, -5.8438) + } + }, + ["models/lonewolfie/audi_r18.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.375, 90), + ["PlateVector"] = Vector(-12.7188, -109.875, 17.2812) + } + }, + ["models/tdmcars/dbs.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-14.0938, -106.75, 43.7188) + } + }, + ["models/tdmcars/fer_458spid.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 85.0625), + ["PlateVector"] = Vector(-13.125, -106.5312, 32.4375) + } + }, + ["models/lonewolfie/merc_sprinter_lwb.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1273.8854, + ["PlateSizeH"] = 328.0255, + ["PlateAngle"] = Angle(0, -3, 88.8438), + ["PlateVector"] = Vector(-30.3438, -155.3438, 27.9688) + } + }, + ["models/tdmcars/hon_civic97.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 497.2727, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-14.4062, -97.3438, 45.125) + } + }, + ["models/tdmcars/trailers/aerodynamic.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-12.8438, -254.5625, 45.7812) + } + }, + ["models/lonewolfie/mercedes_190e_evo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1335.0318, + ["PlateSizeH"] = 450.3185, + ["PlateAngle"] = Angle(0, 1, 75.0938), + ["PlateVector"] = Vector(-13.2188, -104.0312, 36.0312) + } + }, + ["models/tdmcars/hsvgts.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-12.9375, -121.6562, 28.875) + } + }, + ["models/lonewolfie/gmc_yukon.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 293.6306, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.625, -131.4062, 34.6562) + } + }, + ["models/tdmcars/hon_crxsir.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 827.2727, + ["PlateSizeH"] = 262.7273, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-8.2812, -86.625, 23.9375) + } + }, + ["models/lonewolfie/chev_tahoe.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 88.3125), + ["PlateVector"] = Vector(-13.9688, -112.9375, 51.4062) + } + }, + ["models/tdmcars/lam_miura_p400.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 729.0909, + ["PlateSizeH"] = 333.6364, + ["PlateAngle"] = Angle(0, 4, 90), + ["PlateVector"] = Vector(-7.5625, -106.0312, 31.6875) + } + }, + ["models/tdmcars/vol_xc70.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0625, 89.9375), + ["PlateVector"] = Vector(-13.5938, -119.9062, 18.7812) + } + }, + ["models/lonewolfie/polaris_4x4.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 696.3636, + ["PlateSizeH"] = 262.7273, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-6.4688, -41.5938, 33.3125) + } + }, + ["models/lonewolfie/fer_458_compe.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 438.8535, + ["PlateAngle"] = Angle(0, 0, 75.0938), + ["PlateVector"] = Vector(-13.5625, -111.7188, 35.0938) + } + }, + ["models/tdmcars/cit_c4.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 85.0625), + ["PlateVector"] = Vector(-14.3438, -99.3125, 29.1875) + } + }, + ["models/lonewolfie/thebigbooper.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.0625, -72.0938, 88.75) + } + }, + ["models/tdmcars/vw_golfgti_14.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1318.1818, + ["PlateSizeH"] = 361.7689, + ["PlateAngle"] = Angle(0, -0.6562, 90), + ["PlateVector"] = Vector(-13.125, -101.6562, 25.125) + } + }, + ["models/tdmcars/gtav/police.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 685.4545, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1.3438, 78.5312), + ["PlateVector"] = Vector(-6.5, -116.375, 39.5) + } + }, + ["models/tdmcars/vol_850r.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0312, 90), + ["PlateVector"] = Vector(-13.2188, -115.5, 26.0625) + } + }, + ["models/tdmcars/mas_ghibli.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1.25, 79.7812), + ["PlateVector"] = Vector(-13.375, -118.25, 43.375) + } + }, + ["models/tdmcars/chr_300c.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 78.5312), + ["PlateVector"] = Vector(-13.6875, -121.875, 33.5) + } + }, + ["models/tdmcars/69camaro.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 798.3468, + ["PlateSizeH"] = 343.6616, + ["PlateAngle"] = Angle(0, 0.0625, 112.5312), + ["PlateVector"] = Vector(-7.8438, -104.0312, 29.625) + } + }, + ["models/tdmcars/mini_coupe.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1296.3636, + ["PlateSizeH"] = 300.9091, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.625, -79.5938, 39.7188) + } + }, + ["models/tdmcars/trailers/logtrailer.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0312, 90), + ["PlateVector"] = Vector(-13.4688, -238.75, 80.0312) + } + }, + ["models/lonewolfie/nfs_mustanggt.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 838.1818, + ["PlateSizeH"] = 420.9091, + ["PlateAngle"] = Angle(0, 0, 81.8125), + ["PlateVector"] = Vector(-8.5625, -106.9375, 36.75) + } + }, + ["models/tdmcars/mitsu_evo8.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1514.5455, + ["PlateSizeH"] = 371.8182, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-15.3438, -103.4062, 24.8438) + } + }, + ["models/lonewolfie/ferrari_f40.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.875, -108.2812, 23.0625) + } + }, + ["models/lonewolfie/detomaso_pantera.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 787.5257, + ["PlateSizeH"] = 414.4941, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-7.8438, -101.5938, 33.0312) + } + }, + ["models/lonewolfie/hummer_h1_tc_offroad.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 937.7644, + ["PlateSizeH"] = 423.6171, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-50, -118.7812, 42.0312) + } + }, + ["models/lonewolfie/mitsu_evo_six.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.2812), + ["PlateVector"] = Vector(-12.8438, -103.375, 46.7188) + } + }, + ["models/lonewolfie/gmc_savana_news.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1178.2411, + ["PlateSizeH"] = 333.1506, + ["PlateAngle"] = Angle(0, -0.375, 90), + ["PlateVector"] = Vector(-11.125, -131.4375, 27.6875) + } + }, + ["models/tdmcars/courier_truck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -179, 90), + ["PlateVector"] = Vector(0.9375, 73.5625, 99.4375) + } + }, + ["models/lonewolfie/gmc_savana.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1266.1965, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.1562, -0.2812, 90), + ["PlateVector"] = Vector(-12.5625, -131.7188, 29.2812) + } + }, + ["models/tdmcars/gtav/camper_trailer.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 102.5938), + ["PlateVector"] = Vector(-14.1875, -214.2188, 37.1875) + } + }, + ["models/lonewolfie/detomaso_pantera.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 787.5257, + ["PlateSizeH"] = 414.4941, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-7.8438, -101.5938, 33.0312) + } + }, + ["models/tdmcars/gtav/tractor.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 662.4204, + ["PlateSizeH"] = 373.8854, + ["PlateAngle"] = Angle(0, 2, 86.5312), + ["PlateVector"] = Vector(-6.5312, -76.875, 24.375) + } + }, + ["models/lonewolfie/gmc_savana.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1266.1965, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.1562, -0.2812, 90), + ["PlateVector"] = Vector(-12.5625, -131.7188, 29.2812) + } + }, + ["models/lonewolfie/hummer_h1_tc_offroad.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 937.7644, + ["PlateSizeH"] = 423.6171, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-50, -118.7812, 42.0312) + } + }, + ["models/tdmcars/gtav/rebel.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 845.8599, + ["PlateSizeH"] = 301.2739, + ["PlateAngle"] = Angle(0, 0, 91.125), + ["PlateVector"] = Vector(-33.2188, -110.4062, 40.4062) + } + }, + ["models/tdmcars/gtav/gauntlet.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 853.5032, + ["PlateSizeH"] = 392.9936, + ["PlateAngle"] = Angle(0, 0, 109.4688), + ["PlateVector"] = Vector(-8.6562, -114.375, 24.25) + } + }, + ["models/tdmcars/gtav/nemesis.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 540.1274, + ["PlateSizeH"] = 270.7006, + ["PlateAngle"] = Angle(0, 0, 72.7812), + ["PlateVector"] = Vector(-5.2812, -35.3125, 38.2188) + } + }, + ["models/tdmcars/gtav/police3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 707.2727, + ["PlateSizeH"] = 360.9091, + ["PlateAngle"] = Angle(0, 1, 78.5312), + ["PlateVector"] = Vector(-6.8125, -112.5938, 48.8438) + } + }, + ["models/tdmcars/gtav/riot.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1029.2994, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-9.9688, -158.5, 37.0625) + } + }, + ["models/tdmcars/gtav/utillitruck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 838.2166, + ["PlateSizeH"] = 446.4968, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(30.625, -135.25, 29.6875) + } + }, + ["models/tdmcars/gtav/ambulance.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-11.8438, -173.7188, 26.5938) + } + }, + ["models/tdmcars/gtav/adder.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 783.6364, + ["PlateSizeH"] = 290.0, + ["PlateAngle"] = Angle(0, 0.3438, 75.25), + ["PlateVector"] = Vector(-7.5938, -92.5312, 28.75) + } + }, + ["models/tdmcars/gtav/bati.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 336.3636, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 58.9062), + ["PlateVector"] = Vector(-3.5, -41.625, 35.9062) + } + }, + ["models/tdmcars/gtav/camper.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 408.2803, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.8125, -207.1875, 48.7812) + } + }, + ["models/tdmcars/gtav/baletrailer.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 6, 90), + ["PlateVector"] = Vector(-14.875, -197.5625, 40.5) + } + }, + ["models/lonewolfie/gmc_savana_news.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1178.2411, + ["PlateSizeH"] = 333.1506, + ["PlateAngle"] = Angle(0, -0.375, 90), + ["PlateVector"] = Vector(-11.125, -131.4375, 27.6875) + } + }, + ["models/tdmcars/courier_truck.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -179, 90), + ["PlateVector"] = Vector(0.9375, 73.5625, 99.4375) + } + }, + ["models/tdmcars/gtav/patriot2.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9688, -144.0312, 55.9062) + } + }, + ["models/tdmcars/gtav/futo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 845.8599, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.9688), + ["PlateVector"] = Vector(-8.3125, -84.5, 32.875) + } + }, + ["models/tdmcars/gtav/mesa3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 609.0909, + ["PlateSizeH"] = 322.7273, + ["PlateAngle"] = Angle(0, 1, 94.9062), + ["PlateVector"] = Vector(23.7812, -94.5312, 52.2188) + } + }, + ["models/tdmcars/gtav/policeb.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 448.4076, + ["PlateSizeH"] = 270.7006, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-4.4062, -53.5625, 28.1875) + } + }, + ["models/tdmcars/gtav/zentorno.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 723.5669, + ["PlateSizeH"] = 259.2357, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-7.3438, -104.25, 21.4062) + } + }, + ["models/lonewolfie/mitsu_evo_six.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81.2812), + ["PlateVector"] = Vector(-12.8438, -103.375, 46.7188) + } + }, + ["models/tdmcars/gtav/bus.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 891.7197, + ["PlateSizeH"] = 354.7771, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-8.875, -292.125, 55.5312) + } + }, + ["models/lonewolfie/ferrari_f40.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.875, -108.2812, 23.0625) + } + }, + ["models/tdmcars/gtav/turismor.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 578.3439, + ["PlateSizeH"] = 320.3822, + ["PlateAngle"] = Angle(0, 0, 109.4688), + ["PlateVector"] = Vector(-5.5625, -106.0938, 21.7188) + } + }, + ["models/lonewolfie/cad_eldorado.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 730.5147, + ["PlateSizeH"] = 280.9912, + ["PlateAngle"] = Angle(0, 0.2812, 90), + ["PlateVector"] = Vector(-7.3125, -132.875, 26.5) + } + }, + ["models/lonewolfie/lotus_exiges_roadster.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.4062, 90), + ["PlateVector"] = Vector(-13.9062, -99.0312, 33.1562) + } + }, + ["models/lonewolfie/dodge_ram_1500_outdoorsman.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 805.4552, + ["PlateSizeH"] = 287.69, + ["PlateAngle"] = Angle(0, -0.25, 90), + ["PlateVector"] = Vector(-8.2188, -121.2812, 35.8125) + } + }, + ["models/lonewolfie/chev_impala_09_police.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 829.8935, + ["PlateSizeH"] = 449.5775, + ["PlateAngle"] = Angle(0, -0.5312, 80.2188), + ["PlateVector"] = Vector(-8.125, -119.4688, 32.4688) + } + }, + ["models/buggy.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4062, -100.2812, 36.3125) + } + }, + ["models/lonewolfie/uaz_31519_pol.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5625, 96.625), + ["PlateVector"] = Vector(-13.5625, -79.0625, 40.5) + } + }, + ["models/airboat.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-13.9375, -70.9688, 29.2812) + } + }, + ["models/lonewolfie/uaz_3170.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.125, 89.7812), + ["PlateVector"] = Vector(-13.875, -62.3438, 42.9375) + } + }, + ["models/lonewolfie/dacia_duster.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.5625, 70.625), + ["PlateVector"] = Vector(-14.0938, -106.6562, 45.5938) + } + }, + ["models/crsk_autos/hyundai/solaris_2010.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.2812, 90), + ["PlateVector"] = Vector(-13.8125, -114, 46.0625) + } + }, + ["models/crsk_autos/saab/900turbo_1989.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.4375, -107.875, 33) + } + }, + ["models/crsk_autos/w_motors/fenyr_supersport.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 113.2188), + ["PlateVector"] = Vector(14.1875, 110.5, 19.625) + } + }, + ["models/crsk_autos/tofas/kartal.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1317.6796, + ["PlateSizeH"] = 284.5269, + ["PlateAngle"] = Angle(0, -180, 92.4375), + ["PlateVector"] = Vector(13.2188, 97.3438, 23.75) + } + }, + ["models/crsk_autos/hyundai/solaris_2010_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 2, 90), + ["PlateVector"] = Vector(-13.7812, -114.0938, 45.9375) + } + }, + ["models/crsk_autos/skoda/rapid_2014.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.2812, 70.875), + ["PlateVector"] = Vector(-14.3125, -110, 45.2812) + } + }, + ["models/crsk_autos/bmw/alpina_b10.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1236.3636, + ["PlateSizeH"] = 318.1818, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-11.5625, -115.1562, 39.5625) + } + }, + ["models/crsk_autos/bmw/750i_e38_1995.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 924.0548, + ["PlateSizeH"] = 324.2424, + ["PlateAngle"] = Angle(0.125, -0.1562, 85.3125), + ["PlateVector"] = Vector(-9.3438, -122.375, 40.1562) + } + }, + ["models/crsk_autos/saab/93aero_2002.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.9375, -103.3438, 44.0312) + } + }, + ["models/crsk_autos/dodge/challenger_hellcat_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1062.6931, + ["PlateSizeH"] = 304.5184, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-10.75, -123.7812, 32.2188) + } + }, + ["models/crsk_autos/audi/rs6_avant_2016_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.4062, 76.7812), + ["PlateVector"] = Vector(-13.1875, -119.7188, 41.1875) + } + }, + ["models/crsk_autos/mercedes-benz/clk_gtr_amg_coupe_1998.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 90), + ["PlateVector"] = Vector(-13.6875, -106.625, 29.5938) + } + }, + ["models/crsk_autos/mercedes-benz/cls_c218.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 78.5312), + ["PlateVector"] = Vector(-13.875, -100.0938, 42.3438) + } + }, + ["models/crsk_autos/mitsubishi/lancer_evo_ix.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-14.2188, -99, 26.6875) + } + }, + ["models/crsk_autos/mercedes-benz/gt63s_coupe_amg_2018_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1414.4473, + ["PlateSizeH"] = 361.2371, + ["PlateAngle"] = Angle(0.3438, 0.0625, 82.1875), + ["PlateVector"] = Vector(-14.0625, -122.5312, 28.1875) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1212.8278, + ["PlateSizeH"] = 350.1005, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(12.25, 126, 18.875) + } + }, + ["models/crsk_autos/chevrolet/corvette_c1_1957.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 960.3454, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.4688, -0.3438, 89.5), + ["PlateVector"] = Vector(-9.6875, -113, 19.5312) + } + }, + ["models/crsk_autos/mclaren/720s_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2188, 90), + ["PlateVector"] = Vector(-13.875, -101.0625, 24.8438) + } + }, + ["models/crsk_autos/gaz/24_volga.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0938, 90), + ["PlateVector"] = Vector(-13.7812, -131.9062, 37.5) + } + }, + ["models/crsk_autos/rolls-royce/silverspiritmk3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2188, 88.6875), + ["PlateVector"] = Vector(-14.25, -130.2812, 38.0312) + } + }, + ["models/crsk_autos/gtasa/buffalo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 940.1491, + ["PlateSizeH"] = 348.4052, + ["PlateAngle"] = Angle(0, -0.4688, 74.6562), + ["PlateVector"] = Vector(-9.5, -104.9688, 40.7188) + } + }, + ["models/crsk_autos/volvo/xc90_t8_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1405.4545, + ["PlateSizeH"] = 290.0, + ["PlateAngle"] = Angle(0, -179.5938, 102.1875), + ["PlateVector"] = Vector(14.625, 92.9375, 30.7812) + } + }, + ["models/crsk_autos/toyota/mark_ii_jzx90_tourerv_1995.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 936.3636, + ["PlateSizeH"] = 322.7273, + ["PlateAngle"] = Angle(0.0938, -178.4688, 90), + ["PlateVector"] = Vector(9.7188, 95.7188, 26.2812) + } + }, + ["models/crsk_autos/alfaromeo/alfasud.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 372.4484, + ["PlateAngle"] = Angle(0, -0.2188, 101.5312), + ["PlateVector"] = Vector(-14.0625, -102.25, 40.9375) + } + }, + ["models/crsk_autos/kia/stinger_gt_2018.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 82.3125), + ["PlateVector"] = Vector(-13.375, -128.7812, 39.4688) + } + }, + ["models/crsk_autos/fiat/126p.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.5625, -72.6562, 31.0938) + } + }, + ["models/crsk_autos/audi/a4_quattro_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1356.6484, + ["PlateSizeH"] = 362.2359, + ["PlateAngle"] = Angle(0, -0.1562, 79.4062), + ["PlateVector"] = Vector(-13.9062, -109.0312, 40.875) + } + }, + ["models/crsk_autos/seat/leon_cupra_r_2003.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.375, 78.1562), + ["PlateVector"] = Vector(-14.2812, -105.4688, 25.5312) + } + }, + ["models/crsk_autos/paz/3205.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(14.4375, 155.875, 29.9688) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1263.4411, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-12.9375, -162.125, 59.3438) + } + }, + ["models/crsk_autos/rolls-royce/dawn_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0625, 85.0938), + ["PlateVector"] = Vector(-14.3438, -125.0312, 43.2812) + } + }, + ["models/crsk_autos/hyundai/solaris_2010_taxi.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.4688, 0.5, 89.6875), + ["PlateVector"] = Vector(-13.8125, -113.5625, 46.0312) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(14.3438, 90.1875, 23.7188) + } + }, + ["models/crsk_autos/toyota/crown_hybrid_athlete_s_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1262.0957, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 77.4688), + ["PlateVector"] = Vector(-12.4688, -118.5625, 43.9062) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.0625, -175.7188, 97.0938), + ["PlateVector"] = Vector(13.75, 123.0625, 23.5625) + } + }, + ["models/crsk_autos/lexus/lx570_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.3438, 90), + ["PlateVector"] = Vector(-14.6875, -134.8125, 44.5938) + } + }, + ["models/crsk_autos/chevrolet/elcamino_1973.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.1875, 64.4688), + ["PlateVector"] = Vector(-13.9062, -128.6562, 40.5) + } + }, + ["models/crsk_autos/cadillac/fleetwood_brougham_1985.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1333.1969, + ["PlateSizeH"] = 417.1394, + ["PlateAngle"] = Angle(0.5625, -0.25, 83.4062), + ["PlateVector"] = Vector(-13.5625, -156.0312, 41.9062) + } + }, + ["models/crsk_autos/chrysler/300_hurst_1970.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 878.9415, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.5312, 90), + ["PlateVector"] = Vector(-8.8125, -143.9375, 26.875) + } + }, + ["models/crsk_autos/saleen/s7_twinturbo.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.125, -102.125, 25.5938) + } + }, + ["models/crsk_autos/rolls-royce/silvercloud3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1298.3708, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 76.3438), + ["PlateVector"] = Vector(-12.8438, -124.6562, 37.375) + } + }, + ["models/crsk_autos/uaz/patriot_2014_investigation.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 87.2812), + ["PlateVector"] = Vector(-30.875, -121.2188, 42.8125) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1419.8592, + ["PlateSizeH"] = 327.4657, + ["PlateAngle"] = Angle(0, 179.875, 90), + ["PlateVector"] = Vector(14.1562, 97.2188, 39.1562) + } + }, + ["models/crsk_autos/mercedes-benz/500sl_1994.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1186.6543, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-11.5, -116.7188, 37.8125) + } + }, + ["models/crsk_autos/chevrolet/caprice_1993.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 988.86, + ["PlateSizeH"] = 435.4638, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-10.0312, -134.5625, 36.6562) + } + }, + ["models/crsk_autos/uaz/patriot_2014_police.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2812, 90), + ["PlateVector"] = Vector(-30.5312, -121.5, 42.9062) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 179.6562, 90), + ["PlateVector"] = Vector(14.125, 97.5938, 38.9062) + } + }, + ["models/crsk_autos/mercedes-benz/cklasse_w205_2014.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.1875, 80.75), + ["PlateVector"] = Vector(-14, -114.7188, 44.8125) + } + }, + ["models/crsk_autos/peugeot/308gti_2011.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2812, 90), + ["PlateVector"] = Vector(-13.875, -108.5312, 19.9688) + } + }, + ["models/crsk_autos/nissan/370z_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0625, 77.4375), + ["PlateVector"] = Vector(-14.1562, -102.2812, 32.9688) + } + }, + ["models/crsk_autos/honda/civic_typer_fk8_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.5938, -95.5625, 38.625) + } + }, + ["models/crsk_autos/cadillac/cts-v_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.4062, -119.625, 41.4375) + } + }, + ["models/crsk_autos/avtovaz/2170priora_2008.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.3438, -104.8125, 39.1875) + } + }, + ["models/crsk_autos/avtovaz/2109.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -179, 104.7188), + ["PlateVector"] = Vector(13.5625, 100.9688, 23.5625) + } + }, + ["models/crsk_autos/mercedes-benz/wolf.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1137.7241, + ["PlateSizeH"] = 338.7403, + ["PlateAngle"] = Angle(0, -0.2812, 90), + ["PlateVector"] = Vector(-11.4375, -89.0625, 46.9688) + } + }, + ["models/crsk_autos/skoda/octavia_mk3.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.3125, 76.5938), + ["PlateVector"] = Vector(-14.125, -120.7812, 39.9375) + } + }, + ["models/crsk_autos/avtovaz/vesta.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.125, -103.3125, 44.5938) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -178, 90), + ["PlateVector"] = Vector(12.375, 104.7188, 26.625) + } + }, + ["models/crsk_autos/jawa/350_634.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 545.946, + ["PlateSizeH"] = 327.4901, + ["PlateAngle"] = Angle(0, 1, 76.2812), + ["PlateVector"] = Vector(-5.4062, -46.1875, 25.4062) + } + }, + ["models/crsk_autos/mitsubishi/galante39a_1987.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-15.875, -102.2812, 36.875) + } + }, + ["models/crsk_autos/mercedes-benz/e500_w124_1994.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.5938, -107.5938, 39.875) + } + }, + ["models/crsk_autos/renault/magnum_iii_2005.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.0938, 179.875, 95.8438), + ["PlateVector"] = Vector(14.2812, 105.4062, 25.5625) + } + }, + ["models/crsk_autos/rolls-royce/wraith_2013.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 76.4375), + ["PlateVector"] = Vector(-14.1562, -117.6562, 43.1875) + } + }, + ["models/crsk_autos/volkswagen/karmann_ghia.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1143.6364, + ["PlateSizeH"] = 240.9091, + ["PlateAngle"] = Angle(0, 0, 97.4688), + ["PlateVector"] = Vector(-11.8438, -96.5, 26) + } + }, + ["models/crsk_autos/ford/focus_mk3_2012.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0625, 85.7812), + ["PlateVector"] = Vector(-13.7812, -116.2812, 43.9375) + } + }, + ["models/crsk_autos/honda/accord_2008.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.4375, -113.6875, 47.6562) + } + }, + ["models/crsk_autos/bmw/m135i_f21_2012.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5625, 90.8438), + ["PlateVector"] = Vector(-14.6562, -110.5312, 29.125) + } + }, + ["models/crsk_autos/maserati/alfieri_concept_2014.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.4688, 74.9062), + ["PlateVector"] = Vector(-14.5312, -116.75, 35.875) + } + }, + ["models/crsk_autos/toyota/camry_xv50_2016_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 179.2812, 91.8438), + ["PlateVector"] = Vector(14.1875, 116.4062, 24.25) + } + }, + ["models/crsk_autos/avtovaz/2107.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5, 90), + ["PlateVector"] = Vector(-14.0938, -90.4062, 33.5) + } + }, + ["models/crsk_autos/audi/s5_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.3125, 71.0312), + ["PlateVector"] = Vector(-14.25, -103.6875, 39.5) + } + }, + ["models/crsk_autos/mercedes-benz/c63s_amg_coupe_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 74.9688), + ["PlateVector"] = Vector(-13.4375, -117.7188, 28.8438) + } + }, + ["models/crsk_autos/honda/prelude_1996.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2812, 90), + ["PlateVector"] = Vector(-14.375, -106.625, 27.4062) + } + }, + ["models/crsk_autos/lamborghini/jalpa_1984.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2188, 90), + ["PlateVector"] = Vector(-14.1562, -105.1562, 18.9375) + } + }, + ["models/crsk_autos/toyota/camry_xv50_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.0938, 178.0938, 90.0312), + ["PlateVector"] = Vector(14.4375, 116.0312, 24.4062) + } + }, + ["models/crsk_autos/gmc/sierra_1500slt_2014.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.1562, -149.0625, 34.1875) + } + }, + ["models/crsk_autos/mercedes-benz/g63_amg_2019.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.4688, 90), + ["PlateVector"] = Vector(-13.75, -93.0938, 31.0312) + } + }, + ["models/crsk_autos/porsche/911_turbos_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 75.2812), + ["PlateVector"] = Vector(-14.1875, -108.2188, 28.125) + } + }, + ["models/crsk_autos/iveco/stralis_hi-way_2013.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(14.8438, 126.1875, 23.9375) + } + }, + ["models/crsk_autos/skoda/karoq_2018.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.0625, -118.7188, 43.3125) + } + }, + ["models/crsk_autos/peugeot/205_t16_1984.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.3438, 90), + ["PlateVector"] = Vector(-14.6562, -94.5625, 21.25) + } + }, + ["models/crsk_autos/tesla/model_x_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.6875, 86.6562), + ["PlateVector"] = Vector(-14.5, -145.25, 49.25) + } + }, + ["models/crsk_autos/scania/164l_580_2004.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(14.1562, 126.0625, 44.4062) + } + }, + ["models/crsk_autos/dodge/charger_srt_hellcat_2015_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.125, -125.375, 31.375) + } + }, + ["models/crsk_autos/honda/prelude_1980.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.1562, -97.8438, 38.375) + } + }, + ["models/crsk_autos/aston_martin/vantagev600_1998.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1458.2497, + ["PlateSizeH"] = 351.3857, + ["PlateAngle"] = Angle(0, 0.0625, 71.5625), + ["PlateVector"] = Vector(-14.5938, -124.9375, 44.6562) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1294.1509, + ["PlateSizeH"] = 313.5714, + ["PlateAngle"] = Angle(0, -180.2188, 87.5938), + ["PlateVector"] = Vector(12.9688, 102.9688, 25.2812) + } + }, + ["models/crsk_autos/rolls-royce/phantom_viii_2018.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1269.2988, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.6875, 84.5312), + ["PlateVector"] = Vector(-13.0938, -149.0312, 42.2812) + } + }, + ["models/crsk_autos/toyota/crown_hybrid_athlete_s_2016_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 827.2727, + ["PlateSizeH"] = 401.5679, + ["PlateAngle"] = Angle(0.2812, -180.0938, 90), + ["PlateVector"] = Vector(8.1875, 121.1875, 24.4688) + } + }, + ["models/crsk_autos/ford/crownvictoria_1994.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 448.4119, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.4688, -127.4375, 26.6875) + } + }, + ["models/crsk_autos/peugeot/406_1998.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.125, -110.1875, 29.0625) + } + }, + ["models/crsk_autos/bmw/z4_m40i_g29_2019.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 179, 90), + ["PlateVector"] = Vector(13.6562, 100.2812, 22.5938) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1310.6023, + ["PlateSizeH"] = 336.2354, + ["PlateAngle"] = Angle(0.2812, 0.6875, 83), + ["PlateVector"] = Vector(-12.9688, -98.25, 32.4062) + } + }, + ["models/crsk_autos/mercedes-benz/500se_w140_1992.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.7812, -112.5625, 41.4062) + } + }, + ["models/crsk_autos/honda/nsx_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.5, -100.2812, 23.6875) + } + }, + ["models/crsk_autos/ferrari/360stradale_2003.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5, 90), + ["PlateVector"] = Vector(-14.4062, -101.125, 27.9375) + } + }, + ["models/crsk_autos/bmw/x6m_f86_2015_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 75.7188), + ["PlateVector"] = Vector(-13.3438, -110.5, 53.9375) + } + }, + ["models/crsk_autos/ford/bronco_1982.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.5, -120.7188, 34.0625) + } + }, + ["models/crsk_autos/holden/commodore_ute_ss_2012.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.125, 82.6562), + ["PlateVector"] = Vector(-13.6562, -132.875, 38.0312) + } + }, + ["models/crsk_autos/bmw/z4_m40i_g29_2019_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1268.9202, + ["PlateSizeH"] = 403.7195, + ["PlateAngle"] = Angle(0, -0.6875, 88.0938), + ["PlateVector"] = Vector(-12.75, -98.75, 32.8125) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 180, 90), + ["PlateVector"] = Vector(13.9062, 100.625, 22.6875) + } + }, + ["models/crsk_autos/peugeot/406_taxi.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0938, 90), + ["PlateVector"] = Vector(-14.2812, -109.875, 29.9062) + } + }, + ["models/crsk_autos/avtovaz/2114_trafficpolice.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 90), + ["PlateVector"] = Vector(-14.0938, -81.1875, 33.1562) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1301.4344, + ["PlateSizeH"] = 303.2819, + ["PlateAngle"] = Angle(0, 179.6875, 90), + ["PlateVector"] = Vector(13.0938, 100, 24.7812) + } + }, + ["models/crsk_autos/tofas/dogan.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.5, -97.0625, 32.5) + } + }, + ["models/crsk_autos/ford/falcon_gtho_xy_1971.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-15.1562, -127.375, 23.9688) + } + }, + ["models/crsk_autos/toyota/chaservtourerx100_1998.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 782.9974, + ["PlateSizeH"] = 401.4582, + ["PlateAngle"] = Angle(0, -180, 90.1875), + ["PlateVector"] = Vector(8.1562, 97.3438, 19.0312) + } + }, + ["models/crsk_autos/porsche/911_gt2_993_1995.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.3125, 90), + ["PlateVector"] = Vector(-13.8125, -103.5312, 21.8125) + } + }, + ["models/crsk_autos/mercedes-benz/gl63_amg_2013.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.25, 90), + ["PlateVector"] = Vector(-14.4062, -133.9375, 47.9688) + } + }, + ["models/crsk_autos/zil/130_bortovoi.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-14.6562, -176.5, 50.375) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(15.125, 127.8438, 38.9375) + } + }, + ["models/crsk_autos/zaz/968m.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.25, 90), + ["PlateVector"] = Vector(-14.3125, -94.5625, 33.1562) + } + }, + ["models/crsk_autos/dodge/charger_srt_hellcat_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2812, 79.125), + ["PlateVector"] = Vector(-14.0625, -123.9375, 30.5938) + } + }, + ["models/crsk_autos/mercedes-benz/vito_panel_2014.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -2, 90), + ["PlateVector"] = Vector(-31, -121.0625, 42.6875) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180.2812, 90), + ["PlateVector"] = Vector(13.75, 116.0938, 24.4688) + } + }, + ["models/crsk_autos/avtovaz/2115_trafficpolice.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0625, 90), + ["PlateVector"] = Vector(-14.375, -93.9688, 21.2812) + }, + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 303.6733, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(14.0938, 100.1875, 25.1875) + } + }, + ["models/crsk_autos/smz/s-3d.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 532.7273, + ["PlateSizeH"] = 423.8357, + ["PlateAngle"] = Angle(0, -0.0938, 85.7812), + ["PlateVector"] = Vector(-5.2188, -63.375, 34.5) + } + }, + ["models/crsk_autos/toyota/landcruiser_200_2013.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 850.8904, + ["PlateSizeH"] = 235.7299, + ["PlateAngle"] = Angle(-0.3125, 179.25, 91.9062), + ["PlateVector"] = Vector(8.7812, 130.3125, 34.9688) + } + }, + ["models/crsk_autos/moskvich/412_1970.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.25, 90), + ["PlateVector"] = Vector(-14.4375, -89.0312, 31.6875) + } + }, + ["models/crsk_autos/mercedes-benz/gt63s_coupe_amg_2018.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.6562, -124.0625, 28.3438) + } + }, + ["models/crsk_autos/gtasa/tampa.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.5, 90), + ["PlateVector"] = Vector(-15.2812, -114.5625, 20.7812) + } + }, + ["models/crsk_autos/ford/ltd_lx_1986.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 801.9343, + ["PlateSizeH"] = 311.4339, + ["PlateAngle"] = Angle(0, -1.0938, 75.6875), + ["PlateVector"] = Vector(-8.0312, -129.4688, 35.5) + } + }, + ["models/crsk_autos/alfaromeo/montreal.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1297.3968, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.125, 90), + ["PlateVector"] = Vector(-13.0938, -102.3438, 35.4688) + } + }, + ["models/crsk_autos/audi/rs6_avant_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.125, 0.0312, 78), + ["PlateVector"] = Vector(-13.875, -120.5625, 41.375) + } + }, + ["models/crsk_autos/avtovaz/2101.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-13.4062, -95.0312, 33.6875) + } + }, + ["models/crsk_autos/mercedes-benz/g63_amg_2019_black.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14, -93, 31.25) + } + }, + ["models/crsk_autos/hyundai/solaris_2010_trafficpolice.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5, 81.7812), + ["PlateVector"] = Vector(-14.875, -112.375, 45.9375) + } + }, + ["models/crsk_autos/mercedes-benz/g500_2008.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.1562, -114.25, 27.6875) + } + }, + ["models/crsk_autos/subaru/wrx_sti_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.5, 87.0938), + ["PlateVector"] = Vector(-14.6875, -113.3438, 44.75) + } + }, + ["models/crsk_autos/skoda/octavia_mk1_1999.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -1, 90), + ["PlateVector"] = Vector(-14.5625, -124.4688, 42.4375) + } + }, + ["models/crsk_autos/daf/95xf_4x2_2003.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0.0938, 179.6875, 89.875), + ["PlateVector"] = Vector(14.6875, 125.6562, 26.375) + } + }, + ["models/crsk_autos/bmw/z4e89_2012.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.4375, 90), + ["PlateVector"] = Vector(-14.0312, -100.9375, 22.0312) + } + }, + ["models/crsk_autos/avtovaz/2114.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.875, -82.6562, 33.4688) + } + }, + ["models/crsk_autos/jeep/grandcherokee_srt_2014.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-14.8125, -116.125, 51.875) + } + }, + ["models/crsk_autos/skoda/fabia_mk1_2001.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.25, 80.75), + ["PlateVector"] = Vector(-14.1875, -88.8125, 41.75) + } + }, + ["models/crsk_autos/avtovaz/2113.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.0938, 90), + ["PlateVector"] = Vector(-13.8125, -81.2188, 33.1562) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1296.8569, + ["PlateSizeH"] = 278.0346, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(13.1875, 100.4375, 24.7188) + } + }, + ["models/crsk_autos/alfaromeo/giulia_quadrifoglio_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 79.7812), + ["PlateVector"] = Vector(-12.8438, -115.2188, 38.8125) + } + }, + ["models/crsk_autos/bmw/x6m_f86_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.4375, 78.4062), + ["PlateVector"] = Vector(-13.9688, -110.875, 54.1562) + } + }, + ["models/crsk_autos/aston_martin/db11_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.2188, -0.125, 87.2188), + ["PlateVector"] = Vector(-13.625, -109.0312, 24.4688) + } + }, + ["models/crsk_autos/ford/grantorino_1972.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 818.0583, + ["PlateSizeH"] = 338.6185, + ["PlateAngle"] = Angle(0, -0.0312, 90), + ["PlateVector"] = Vector(-8.4062, -133.0938, 28.7188) + } + }, + ["models/crsk_autos/bmw/7er_g11_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 82.0312), + ["PlateVector"] = Vector(-13.0312, -124.5938, 34.125) + } + }, + ["models/crsk_autos/toyota/landcruiser_200_2013_black.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(-0.3125, 1.25, 100.2188), + ["PlateVector"] = Vector(-13.9375, -111.8438, 47.0312) + }, + ["Plate1"] = { + ["PlateSizeW"] = 782.5969, + ["PlateSizeH"] = 234.918, + ["PlateAngle"] = Angle(0.0938, -175.9375, 90), + ["PlateVector"] = Vector(7.8438, 131.9688, 35) + } + }, + ["models/crsk_autos/avtovaz/2106.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.4688, 90), + ["PlateVector"] = Vector(-14.9375, -93.0625, 33.2812) + } + }, + ["models/crsk_autos/ford/bronco_1982_police.mdl"] = { + ["Plate2"] = { + ["PlateSizeW"] = 737.4595, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -180, 90), + ["PlateVector"] = Vector(7.6562, 100.9688, 34.6875) + }, + ["Plate1"] = { + ["PlateSizeW"] = 781.0237, + ["PlateSizeH"] = 424.0259, + ["PlateAngle"] = Angle(-0.0625, 0, 90), + ["PlateVector"] = Vector(-7.9062, -116.5625, 34.2188) + } + }, + ["models/crsk_autos/avtovaz/2115.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.4062, -95.6875, 21.4688) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1330.0076, + ["PlateSizeH"] = 301.1844, + ["PlateAngle"] = Angle(0, -180.0938, 90), + ["PlateVector"] = Vector(13.5312, 99.9062, 24.625) + } + }, + ["models/crsk_autos/ford/mustang_gt_2018.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.5625, -122.6875, 29.7812) + } + }, + ["models/crsk_autos/alfaromeo/8cspider.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.4688, 85.75), + ["PlateVector"] = Vector(-14.25, -106.375, 19.6562) + } + }, + ["models/crsk_autos/bmw/i8_2015.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.25, 72.625), + ["PlateVector"] = Vector(-14.6875, -125.6562, 28.5) + } + }, + ["models/crsk_autos/jaguar/fpace_2016.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 72.4688), + ["PlateVector"] = Vector(-14.25, -126.6875, 48.6875) + } + }, + ["models/crsk_autos/ferrari/812superfast_2017.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 70.7812), + ["PlateVector"] = Vector(-14.7812, -117.3438, 31.4375) + } + }, + ["models/crsk_autos/mercedes-benz/560sel_1985.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.2812, 80.6875), + ["PlateVector"] = Vector(-13.6562, -134.0312, 39.5312) + } + }, + ["models/crsk_autos/honda/integra_dc2_typer_1998.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.125, 90), + ["PlateVector"] = Vector(-14.2188, -107.8125, 26.5938) + } + }, + ["models/crsk_autos/mercedes-benz/e63amg_w212_facelift.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 84.75), + ["PlateVector"] = Vector(-14.25, -117.9062, 40.75) + } + }, + ["models/crsk_autos/peugeot/206rc_2003.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 281.8448, + ["PlateAngle"] = Angle(0, -0.0625, 80.1562), + ["PlateVector"] = Vector(-14, -84.7812, 36.8438) + } + }, + ["models/crsk_autos/zil/41047.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1230.9091, + ["PlateSizeH"] = 251.8182, + ["PlateAngle"] = Angle(0, -180.25, 98.3438), + ["PlateVector"] = Vector(12.7188, 124.5312, 24.9375) + } + }, + ["models/crsk_autos/ford/mustang_rtr_2018.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 1, 90), + ["PlateVector"] = Vector(-13.5312, -122.7188, 29.6562) + } + }, + ["models/crsk_autos/mercedes-benz/g500_short_2008.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0938, 90), + ["PlateVector"] = Vector(-14.5938, -93.7188, 26.875) + } + }, + ["models/crsk_autos/toyota/mark_ii_jzx90_grande_1992.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 932.3895, + ["PlateSizeH"] = 306.3636, + ["PlateAngle"] = Angle(-0.0312, -177.375, 90), + ["PlateVector"] = Vector(9.625, 95.7188, 26.125) + } + }, + ["models/crsk_autos/uaz/patriot_2014.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.2812, 87.3438), + ["PlateVector"] = Vector(-30.5625, -120.7188, 43.4375) + }, + ["Plate2"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 179.875, 90), + ["PlateVector"] = Vector(14.0938, 97.125, 38.0938) + } + }, + ["models/crsk_autos/ford/crownvictoria_1987.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, -0.4062, 74.5938), + ["PlateVector"] = Vector(-13.8125, -105.125, 35.5312) + } + }, + ["models/crsk_autos/peugeot/508_2011.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0.0938, 90), + ["PlateVector"] = Vector(-14.3438, -115.0312, 18.8125) + } + }, + ["models/crsk_autos/landrover/series_iia_stationwagon.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-28.25, -131.5312, 87.4688) + } + }, + ["models/crsk_autos/bmw/750li_f02_2009.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 81), + ["PlateVector"] = Vector(-13.7188, -119.7812, 40.1875) + } + }, + ["models/crsk_autos/apollo/intensa_emozione.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-13.4375, -128.875, 22.9688) + } + }, + ["models/crsk_autos/nissan/skyline_r32_gtr_custom.mdl"] = { + ["Plate1"] = { + ["PlateSizeW"] = 1400.0, + ["PlateSizeH"] = 400.0, + ["PlateAngle"] = Angle(0, 0, 90), + ["PlateVector"] = Vector(-14.8125, -111.0625, 27.9375) + } + } +} + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_config.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_config.lua new file mode 100644 index 0000000..93f7312 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_config.lua @@ -0,0 +1,519 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police = Realistic_Police or {} + +-- Helix Sync: Helper to get job name respecting units +function Realistic_Police.GetPlayerJob(ply) + if not IsValid(ply) then return "" end + local char = ply.GetCharacter and ply:GetCharacter() + if not char then return team.GetName(ply:Team()) end + + local faction = char:GetFaction() + local podrId = char:GetPodr() + + if faction == (FACTION_RUSSIAN or -1) then + if podrId == 6 then -- 6 is FSB Unit ID + return "ФСБ «Вымпел»" + end + elseif faction == (FACTION_UKRAINE or -1) then + if podrId == 5 then -- 5 is SBU Unit ID + return "СБУ" + end + end + + return team.GetName(ply:Team()) +end + +Realistic_Police.PlateConfig = Realistic_Police.PlateConfig or {} +Realistic_Police.Application = Realistic_Police.Application or {} +Realistic_Police.PlateVehicle = Realistic_Police.PlateVehicle or {} +Realistic_Police.FiningPolice = Realistic_Police.FiningPolice or {} +Realistic_Police.Trunk = Realistic_Police.Trunk or {} +Realistic_Police.TrunkPosition = Realistic_Police.TrunkPosition or {} + +----------------------------------------------------------------------------- +---------------------------- Main Configuration------------------------------ +----------------------------------------------------------------------------- + +Realistic_Police.Lang = "ru" -- You can choose fr , en , tr , cn + +Realistic_Police.DefaultJob = true -- Default Job Activate/Desactivate (Camera Repairer ) + +Realistic_Police.TrunkSystem = true -- Do you want to use the trunk system ? + +Realistic_Police.KeyOpenTablet = KEY_I -- Key for open the tablet into a vehicle + +Realistic_Police.WantedMessage = "Розыск ФСБ" -- Message when you wanted someone with the computer + +Realistic_Police.StungunAmmo = 40 -- Stungun ammo + +Realistic_Police.DisableWantedButton = false -- Do you want to disable wanted button + +Realistic_Police.CanConfiscateWeapon = true -- If the functionality for confiscate is activate or desactivate + +Realistic_Police.UseDefaultArrest = false -- use the default DarkRP arrest system + +Realistic_Police.UseDarkRPNotify = false -- if you want to use the darkrp notification + +Realistic_Police.CameraUpdateRate = 0.5 -- Update rate to optimise camera + +Realistic_Police.SetModelWhenJail = false -- Set the model when the player is in jail + +-- Set the model when the player is in jail +Realistic_Police.ModelWhenJail = { + "models/smalls_civilians/pack2/male/hoodie_jeans/male_01_hoodiejeans_pm.mdl", +} + +Realistic_Police.AdminRank = { -- Rank Admin + ["superadmin"] = true, + ["admin"] = true, +} + +Realistic_Police.OpenComputer = { -- Which job can open the computer + ["ФСБ «Вымпел»"] = true, + ["СБУ"] = true, +} + +Realistic_Police.PoliceVehicle = { -- Police Vehicle + ["sierratdm"] = true, + ["Chevrolet Tahoe - RAID"] = true, +} + +Realistic_Police.TrunkPosition["Chevrolet Tahoe - RAID"] = { + ["Pos"] = Vector(0,0,0), + ["Ang"] = Angle(0,0,0), +} + +----------------------------------------------------------------------------- +------------------------- Computer Configuration----------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.MaxReport = 5 -- Max report per persson + +Realistic_Police.MaxCriminalRecord = 30 -- Max Criminal Record per persson + +Realistic_Police.Application[1] = { -- Unique Id + ["Name"] = "Интернет", -- Name of the Application + ["Materials"] = Material("rpt_internet.png"), -- Material of the Application + ["Function"] = Realistic_Police.FireFox, -- Function Application + ["Type"] = "police", +} + +-- Removed Cameras app + + +Realistic_Police.Application[3] = { -- Unique Id + ["Name"] = "Криминальная база", -- Name of the Application + ["Materials"] = Material("rpt_law.png"), -- Material of the Application + ["Function"] = Realistic_Police.CriminalRecord, -- Function Application + ["Type"] = "police", +} +Realistic_Police.Application[4] = { -- Unique Id + ["Name"] = "Подача жалоб", -- Name of the Application + ["Materials"] = Material("rpt_cloud.png"), -- Material of the Application + ["Function"] = Realistic_Police.ReportMenu, -- Function application + ["Type"] = "police", +} + +Realistic_Police.Application[5] = { -- Unique Id + ["Name"] = "Список жалоб", -- Name of the Application + ["Materials"] = Material("rpt_documents.png"), -- Material of the Application + ["Function"] = Realistic_Police.ListReport, -- Function Application + ["Type"] = "police", +} + +-- Removed Plates app + + +Realistic_Police.Application[7] = { -- Unique Id + ["Name"] = "Терминал ФСБ", -- Name of the Application + ["Materials"] = Material("rpt_cmd.png"), -- Material of the Application + ["Function"] = Realistic_Police.Cmd, -- Function Application + ["Type"] = "hacker", +} + +----------------------------------------------------------------------------- +--------------------------- Plate Configuration------------------------------ +----------------------------------------------------------------------------- + +Realistic_Police.PlateActivate = true -- If Module plate is activate + +Realistic_Police.LangagePlate = "eu" -- You can choose eu or us + +Realistic_Police.PlateConfig["us"] = { + ["Image"] = Material("rpt_plate_us.png"), -- Background of the plate + ["ImageServer"] = nil, -- Image server or Image of the department + ["TextColor"] = Color(24, 55, 66), -- Color Text of the plate + ["Country"] = "ARIZONA", -- Country Name + ["CountryPos"] = {2, 5}, -- The pos of the text + ["CountryColor"] = Color(26, 134, 185), -- Color of the country text + ["Department"] = "", + ["PlatePos"] = {2, 1.5}, -- Plate Pos + ["PlateText"] = false, -- AABCDAA +} + +Realistic_Police.PlateConfig["eu"] = { + ["Image"] = Material("rpt_plate_eu.png"), -- Background of the plate + ["ImageServer"] = Material("rpt_department_eu.png"), -- Image server or Image of the department + ["TextColor"] = Color(0, 0, 0, 255), -- Color Text of the plate + ["Country"] = "F", -- Country Name + ["CountryPos"] = {1.065, 1.4}, -- The pos of the text + ["CountryColor"] = Color(255, 255, 255), -- Color of the country text + ["Department"] = "77", -- Department + ["PlatePos"] = {2, 2}, -- Plate Pos + ["PlateText"] = true, -- AA-BCD-AA +} + +Realistic_Police.PlateVehicle["crsk_alfaromeo_8cspider"] = "us" + +--Realistic_Police.PlateVehicle["class"] = "nameplate" + +----------------------------------------------------------------------------- +---------------------------- Trunk Configuration----------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.KeyForOpenTrunk = KEY_E -- https://wiki.facepunch.com/gmod/Enums/KEY + +Realistic_Police.KeyTrunkHUD = true -- Activate/desactivate the hud of the vehicle + +Realistic_Police.CanOpenTrunk = { + ["Civil Protection"] = true, + ["Civil Protection Chief"] = true, +} + +Realistic_Police.VehiclePoliceTrunk = { + ["Airboat"] = true, + ["Jeep"] = true, +} + +Realistic_Police.MaxPropsTrunk = 10 -- Max props trunk + +Realistic_Police.Trunk["models/props_wasteland/barricade002a.mdl"] = { + ["GhostPos"] = Vector(0,0,35), + ["GhostAngle"] = Vector(0,0,0), +} +Realistic_Police.Trunk["models/props_wasteland/barricade001a.mdl"] = { + ["GhostPos"] = Vector(0,0,30), + ["GhostAngle"] = Vector(0,0,0), +} + +Realistic_Police.Trunk["models/props_junk/TrafficCone001a.mdl"] = { + ["GhostPos"] = Vector(0,0,16), + ["GhostAngle"] = Vector(0,0,0), +} + +Realistic_Police.Trunk["models/props_c17/streetsign004f.mdl"] = { + ["GhostPos"] = Vector(0,0,12), + ["GhostAngle"] = Vector(0,0,0), +} + +Realistic_Police.Trunk["models/props_c17/streetsign001c.mdl"] = { + ["GhostPos"] = Vector(0,0,12), + ["GhostAngle"] = Vector(0,0,0), +} + +----------------------------------------------------------------------------- +-------------------------- HandCuff Configuration---------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.MaxDay = 10 -- Max Jail Day + +Realistic_Police.DayEqual = 60 -- 1 day = 60 Seconds + +Realistic_Police.PriceDay = 5000 -- Price to pay with the bailer per day + +Realistic_Police.JailerName = "Начальник тюрьмы" -- Jailer Name + +Realistic_Police.BailerName = "Сотрудник ФССП" -- Bailer Name + +Realistic_Police.SurrenderKey = KEY_T -- The key for surrender + +Realistic_Police.SurrenderInfoKey = "T" -- The Key + +Realistic_Police.SurrenderActivate = true + +Realistic_Police.CanCuff = { -- Job which can arrest someone + ["ФСБ «Вымпел»"] = true, + ["СБУ"] = true, +} + + +Realistic_Police.CantBeCuff = { -- Job which can't be cuff + ["Civil Protection"] = false, + ["Citizen"] = false, +} + +Realistic_Police.CantConfiscate = { -- Job which can't be cuff + ["gmod_tool"] = true, + ["weapon_physgun"] = true, + ["gmod_camera"] = true, + ["weapon_physcannon"] = true, + ["weapon_rpt_cuffed"] = true, +} + +----------------------------------------------------------------------------- +-------------------------- Stungun Configuration----------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.CantBeStun = { -- Job which can't be cuff + ["Civil Protection"] = true, + ["Citizen"] = false, +} + +----------------------------------------------------------------------------- +--------------------------- Camera Configuration----------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.CameraHealth = 50 -- Health of the Camera + +Realistic_Police.CameraRestart = 60 -- Camera restart when they don't have humans for repair +Realistic_Police.CameraRepairTimer = 10 -- Time to repair the camera 10s + +Realistic_Police.CameraBrokeHud = true -- If when a camera was broken the Camera Worker have a Popup on his screen + +Realistic_Police.CameraBroke = true -- if camera broke sometime when a camera repairer is present on the server + +Realistic_Police.CameraWorker = { -- Job which can repair the camera + ["Camera Repairer"] = true, + ["Citizen"] = false, +} + +Realistic_Police.CameraGiveMoney = 500 -- Money give when a player repair a camera + +----------------------------------------------------------------------------- +--------------------------- Report Configuration----------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.JobDeleteReport = { -- Which job can delete Report + ["ФСБ «Вымпел»"] = true, + ["СБУ"] = true, +} + + +Realistic_Police.JobEditReport = { -- Which job can create / edit report + ["ФСБ «Вымпел»"] = true, + ["СБУ"] = true, +} + + +----------------------------------------------------------------------------- +------------------------ Criminal Record Configuration ---------------------- +----------------------------------------------------------------------------- + +Realistic_Police.JobDeleteRecord = { -- Which job can delete Criminal Record + ["ФСБ «Вымпел»"] = true, + ["СБУ"] = true, +} + + +Realistic_Police.JobEditRecord = { -- Which job can create / edit Criminal Record + ["ФСБ «Вымпел»"] = true, + ["СБУ"] = true, +} + + +----------------------------------------------------------------------------- +---------------------------- Fining System ---------------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.PlayerWanted = true -- if the player is wanted when he doesn't pay the fine + +Realistic_Police.PourcentPay = 10 -- The amount pourcent which are give when the player pay the fine + +Realistic_Police.MaxPenalty = 2 -- Maxe Penalty on the same player + +Realistic_Police.JobCanAddFine = { -- Which job can add fine + ["ФСБ «Вымпел»"] = true, + ["СБУ"] = true, +} + + +Realistic_Police.JobCantHaveFine = { -- Which job can't receive fine + ["Civil Protection"] = true, + ["Civil Protection Chief"] = true, +} + +Realistic_Police.VehicleCantHaveFine = { -- Which vehicle can't receive fine + ["lam_reventon_lw"] = false, + ["sierratdm"] = true, +} + +Realistic_Police.FiningPolice[1] = { + ["Name"] = "Вуайеризм", -- Unique Name is require + ["Price"] = 1000, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[2] = { + ["Name"] = "Ложные вызовы", -- Unique Name is require + ["Price"] = 1500, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[3] = { + ["Name"] = "Сопротивление при аресте", -- Unique Name is require + ["Price"] = 2500, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[4] = { + ["Name"] = "Неподчинение требованиям", -- Unique Name is require + ["Price"] = 1000, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[5] = { + ["Name"] = "Пособничество преступнику", -- Unique Name is require + ["Price"] = 5000, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[6] = { + ["Name"] = "Оскорбления/Мат", -- Unique Name is require + ["Price"] = 500, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[7] = { + ["Name"] = "Проституция", -- Unique Name is require + ["Price"] = 1000, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[8] = { + ["Name"] = "Вандализм", -- Unique Name is require + ["Price"] = 2000, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[9] = { + ["Name"] = "Хранение каннабиса", -- Unique Name is require + ["Price"] = 2000, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[10] = { + ["Name"] = "Массовые беспорядки", -- Unique Name is require + ["Price"] = 2000, + ["Vehicle"] = false, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[11] = { + ["Name"] = "Езда с неисправными тормозами", -- Unique Name is require + ["Price"] = 500, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[12] = { + ["Name"] = "Езда с неисправным рулём", -- Unique Name is require + ["Price"] = 750, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[13] = { + ["Name"] = "Парковка в неположенном месте", -- Unique Name is require + ["Price"] = 1000, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[14] = { + ["Name"] = "Превышение скорости", -- Unique Name is require + ["Price"] = 250, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[15] = { + ["Name"] = "Побег с места ДТП", -- Unique Name is require + ["Price"] = 600, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[16] = { + ["Name"] = "Неисправные тормоза", -- Unique Name is require + ["Price"] = 1200, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[17] = { + ["Name"] = "Нарушение скоростного режима", -- Unique Name is require + ["Price"] = 1500, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[18] = { + ["Name"] = "Угон транспортного средства", -- Unique Name is require + ["Price"] = 700, + ["Vehicle"] = true, + ["Category"] = "General", +} + +Realistic_Police.FiningPolice[19] = { + ["Name"] = "Нарушение скоростного режима", -- Unique Name is require + ["Price"] = 160, + ["Vehicle"] = true, + ["Category"] = "General", +} + +----------------------------------------------------------------------------- +--------------------------- Hacking System ---------------------------------- +----------------------------------------------------------------------------- + +Realistic_Police.NameOs = "АСД-ФСБ" -- The name of the os + +Realistic_Police.ResolveHack = 120 -- Time which the computer will be repair + +Realistic_Police.WordCount = 10 -- How many word the people have to write for hack the computer + +Realistic_Police.HackerJob = { -- Which are not able to use the computer without hack the computer + ["Citizen"] = true, + ["Hobo"] = true, +} + +Realistic_Police.WordHack = { -- Random Word for hack the computer + "run.hack.exe", + "police.access.hack", + "rootip64", + "delete.password", + "password.breaker", + "run.database.sql", + "delete.access", + "recompil", + "connect.police.system", + "datacompil", + "username", + "mysqlbreaker", + "camera.exe", + "criminal.record.exe", + "deleteusergroup", + "license.plate.exe", + "cameracitizen.exe", + "loaddatapublic", + "internet.exe", + "reportmenu.exe", + "listreport.exe", +} + +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_materials.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_materials.lua new file mode 100644 index 0000000..a75f8ed --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/sh_rpt_materials.lua @@ -0,0 +1,62 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +Realistic_Police.Colors = Realistic_Police.Colors or {} + +Realistic_Police.Colors["white"] = Color( 255, 255, 255 ) +Realistic_Police.Colors["white250"] = Color( 255, 255, 255, 250 ) +Realistic_Police.Colors["white240"] = Color( 255, 255, 255, 240 ) +Realistic_Police.Colors["white200"] = Color( 255, 255, 255, 200 ) +Realistic_Police.Colors["white150"] = Color( 255, 255, 255, 150 ) +Realistic_Police.Colors["gray"] = Color( 230, 230, 230 ) +Realistic_Police.Colors["gray240"] = Color( 240, 240, 240 ) +Realistic_Police.Colors["gray220"] = Color( 220, 220, 220 ) +Realistic_Police.Colors["gray200"] = Color( 240, 240, 240, 200 ) +Realistic_Police.Colors["gray150"] = Color( 240, 240, 240, 150 ) +Realistic_Police.Colors["gray100"] = Color( 100, 100, 100, 255 ) +Realistic_Police.Colors["black"] = Color(0, 0, 0) +Realistic_Police.Colors["black240"] = Color(25, 28, 29, 240) +Realistic_Police.Colors["black100"] = Color(0, 0, 0, 100) +Realistic_Police.Colors["black49"] = Color(49, 49, 49, 240) +Realistic_Police.Colors["black25"] = Color(25, 28, 29) +Realistic_Police.Colors["black25200"] = Color(25, 28, 29, 200) +Realistic_Police.Colors["black25150"] = Color(25, 28, 29, 150) +Realistic_Police.Colors["black13"] = Color(13, 13, 13, 240) +Realistic_Police.Colors["black180"] = Color(0, 0, 0, 180) +Realistic_Police.Colors["black15"] = Color(13, 13, 15, 255) +Realistic_Police.Colors["black2510"] = Color(25, 28, 29, 10) +Realistic_Police.Colors["black49"] = Color(40, 40, 40, 240) +Realistic_Police.Colors["black49255"] = Color(40, 40, 40, 255) +Realistic_Police.Colors["black20"] = Color(20, 20, 20, 255) +Realistic_Police.Colors["black15200"] = Color(13, 13, 15, 200) +Realistic_Police.Colors["gray50"] = Color(50, 50, 50) +Realistic_Police.Colors["gray60"] = Color( 60, 60, 60, 255 ) +Realistic_Police.Colors["gray60200"] = Color( 60, 60, 60, 255 ) +Realistic_Police.Colors["green"] = Color(39, 174, 96, 240) +Realistic_Police.Colors["red"] = Color(232, 17, 35) +Realistic_Police.Colors["lightblue"] = Color(53,156,224) +Realistic_Police.Colors["darkblue"] = Color(34, 47, 62) +Realistic_Police.Colors["bluetool"] = Color(52, 73, 94) +Realistic_Police.Colors["blue41200"] = Color(41, 128, 185, 200) +Realistic_Police.Colors["black41200"] = Color(41,42,53,200) +Realistic_Police.Colors["black41200"] = Color(41,42,53,200) +Realistic_Police.Colors["white240"] = Color(255,255,255,210) +Realistic_Police.Colors["black18220"] = Color(18, 30, 42, 220) +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe + +Realistic_Police.Colors["Material1"] = Material("rpt_trash.png") +Realistic_Police.Colors["Material2"] = Material("rpt_background_v2.png", "smooth") +Realistic_Police.Colors["Material3"] = Material("rpt_toolbar.png") +Realistic_Police.Colors["Material4"] = Material("rpt_keybord.png") +Realistic_Police.Colors["Material5"] = Material("rpt_camerahud.png") +Realistic_Police.Colors["Material6"] = Material("rpt_turnoff.png") +Realistic_Police.Colors["Material7"] = Material("rpt_bell.png") +Realistic_Police.Colors["Material8"] = Material("rpt_resize.png") +Realistic_Police.Colors["Material9"] = Material("rpt_logo_police.png") +Realistic_Police.Colors["Material2_SBU"] = Material("rpt_background_v2_sbu.png", "smooth") +Realistic_Police.Colors["Material9_SBU"] = Material("rpt_logo_sbu.png") +Realistic_Police.Colors["Material10"] = Material("rpt_tablette_background.png") +-- 76561198219964937 diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/shared/sh_functions.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/shared/sh_functions.lua new file mode 100644 index 0000000..5c1250a --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/realistic_police/shared/sh_functions.lua @@ -0,0 +1,29 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + + +--[[ Make sure sentence exist and also langage exist]] +function Realistic_Police.GetSentence(string) + local result = "Lang Problem" + + local key = string + if CLIENT and IsValid(Realistic_Police.CurrentComputer) and Realistic_Police.CurrentComputer.IsSBU and Realistic_Police.CurrentComputer:IsSBU() then + if Realistic_Police.Language[Realistic_Police.Lang] and Realistic_Police.Language[Realistic_Police.Lang][string .. "_SBU"] then + key = string .. "_SBU" + end + end + + local sentence = istable(Realistic_Police.Language[Realistic_Police.Lang]) and Realistic_Police.Language[Realistic_Police.Lang][key] or "Lang Problem" + + if istable(Realistic_Police.Language[Realistic_Police.Lang]) and isstring(sentence) then + result = sentence + elseif istable(Realistic_Police.Language["en"]) and isstring(Realistic_Police.Language["en"][sentence]) then + result = Realistic_Police.Language["en"][sentence] + end + + return result +end + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_camera_tool.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_camera_tool.lua new file mode 100644 index 0000000..1680b66 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_camera_tool.lua @@ -0,0 +1,138 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile() +TOOL.Category = "Realistic Police" +TOOL.Name = "Camera-Setup" +TOOL.Author = "Kobralost" +RPTDeployTool = false + +if CLIENT then + TOOL.Information = { + { name = "left" }, + { name = "right" }, + } + language.Add("tool.realistic_police_camera_tool.name", "Camera Setup") + language.Add("tool.realistic_police_camera_tool.desc", "Create or modify Position of your Camera") + language.Add("tool.realistic_police_camera_tool.left", "Left-Click to place the camera in your server" ) + language.Add("tool.realistic_police_camera_tool.right", "Right-Click to delete the camera on your server" ) +end + +function TOOL:Deploy() + RPTDeployTool = true +end + +function TOOL:LeftClick(trace) + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.5 +local ply = self:GetOwner() + if SERVER then + if not IsValid( ply ) && not ply:IsPlayer() then return end + local trace = ply:GetEyeTrace() + local position = trace.HitPos + local angle = ply:GetAngles() +local rpt_createent = ents.Create( "realistic_police_camera" ) + rpt_createent:SetPos(position + Vector(0, 0, 0)) + rpt_createent:SetAngles(Angle(0, angle.Yaw - 90, 0)) + rpt_createent:Spawn() + rpt_createent:Activate() + rpt_createent.RPTCam = true +timer.Simple(0.1, function() + net.Start("RealisticPolice:NameCamera") + net.WriteEntity(rpt_createent) + net.Send(self:GetOwner()) + end ) + else + if not RPTDeployTool then RPTDeployTool = true end + end +end + +function TOOL:RightClick() + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.01 + + if SERVER then + local TraceEntity = self:GetOwner():GetEyeTrace().Entity + if IsValid(TraceEntity) && TraceEntity:GetClass() == "realistic_police_camera" then + TraceEntity:Remove() + else + if IsValid(ents.FindByClass("realistic_police_camera")[#ents.FindByClass("realistic_police_camera")]) then + ents.FindByClass("realistic_police_camera")[#ents.FindByClass("realistic_police_camera")]:Remove() + end + end + Realistic_Police.SaveEntity() + end +end + +function TOOL:CreateRPTEnt() + if CLIENT then + if IsValid(self.RPTEnt) then else + self.RPTEnt = ClientsideModel("models/wasted/wasted_kobralost_camera.mdl", RENDERGROUP_OPAQUE) + self.RPTEnt:SetModel("models/wasted/wasted_kobralost_camera.mdl") + self.RPTEnt:SetMaterial("models/wireframe") + self.RPTEnt:SetPos(Vector(0,0,0)) + self.RPTEnt:SetAngles(Angle(0,0,0)) + self.RPTEnt:Spawn() + self.RPTEnt:Activate() + self.RPTEnt.Ang = Angle(0,0,0) + self.RPTEnt:SetRenderMode(RENDERMODE_TRANSALPHA) + self.RPTEnt:SetColor(Realistic_Police.Colors["white"]) + end + end +end + +function TOOL:Think() + if IsValid(self.RPTEnt) then + ply = self:GetOwner() + trace = util.TraceLine(util.GetPlayerTrace(ply)) + ang = ply:GetAimVector():Angle() + Pos = Vector(trace.HitPos.X, trace.HitPos.Y, trace.HitPos.Z) + Ang = Angle(0, ang.Yaw - 90, 0) + self.RPTEnt.Ang + self.RPTEnt:SetPos(Pos) + self.RPTEnt:SetAngles(Ang) + else + self:CreateRPTEnt() + end +end + +function TOOL:Holster() + if IsValid(self.RPTEnt) then + self.RPTEnt:Remove() + end + RPTDeployTool = false +end +hook.Add("HUDPaint", "RPT:HUDPaint", function() + if RPTDeployTool then + for k,v in pairs(ents.FindByClass("realistic_police_camera")) do + local tscreen = v:GetPos():ToScreen() + draw.SimpleText( k, "rpt_font_2", tscreen.x, tscreen.y + 20, Realistic_Police.Colors["white"],1,1) + draw.SimpleText( "☑" , "rpt_font_2", tscreen.x, tscreen.y, Realistic_Police.Colors["white"],1,1) + end + end +end) + +function TOOL.BuildCPanel( CPanel ) + CPanel:AddControl("label", { + Text = "Save Realistic Police Entities" }) + CPanel:Button("Save Entities", "rpt_save") +CPanel:AddControl("label", { + Text = "Remove all Entities in The Data" }) + CPanel:Button("Remove Entities Data", "rpt_removedata") + + CPanel:AddControl("label", { + Text = "Remove all Entities in The Map" }) + CPanel:Button("Remove Entities Map", "rpt_cleaupentities") + + CPanel:AddControl("label", { + Text = "Reload all Entities in The Map" }) + CPanel:Button("Reload Entities Map", "rpt_reloadentities") +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_handcuff_tool.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_handcuff_tool.lua new file mode 100644 index 0000000..9b50077 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_handcuff_tool.lua @@ -0,0 +1,198 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile() +TOOL.Category = "Realistic Police" +TOOL.Name = "HandCuff-Setup" +TOOL.Author = "Kobralost" +TOOL.StepId = 1 + +if CLIENT then + TOOL.Information = { + { name = "left" }, + { name = "right" }, + { name = "use" }, + { name = "reload" }, + } + language.Add("tool.realistic_police_handcuff_tool.name", "HandCuff Setup") + language.Add("tool.realistic_police_handcuff_tool.desc", "Create or modify Position of Bailor, Jailor & Jail") + language.Add("tool.realistic_police_handcuff_tool.left", "Left-Click to place entities on the server" ) + language.Add("tool.realistic_police_handcuff_tool.right", "Right-Click to delete the last entity" ) + language.Add("tool.realistic_police_handcuff_tool.use", "USE to return to the previous step" ) + language.Add("tool.realistic_police_handcuff_tool.reload", "RELOAD to go to the next step" ) +end +-- a91f6942e11bb9a70ff8e3d7a0dcc737fd407c85d2d10ff95a1cfed59177e84a + +function TOOL:Deploy() + self.StepId = 1 +end + +-- Create the Entity ( Jailer , Bailer ) +function Realistic_Police.CreateEntityTool(Player, model, ent) + local angle = Player:GetAimVector():Angle() + local ang = Angle(0,angle.yaw + 180,0) + + local EntityT = ents.Create( ent ) + EntityT:SetModel(model) + if ent == "realistic_police_jailpos" then + EntityT:SetPos( Player:GetEyeTrace().HitPos + Vector(0,0,13) ) + else + EntityT:SetPos( Player:GetEyeTrace().HitPos + Vector(0,0,0) ) + end + EntityT:SetAngles(ang) + EntityT:Spawn() +end + +function TOOL:LeftClick(trace) + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.1 +if SERVER then + if self.StepId == 1 then + Realistic_Police.CreateEntityTool(self:GetOwner(), "models/Humans/Group01/Female_02.mdl", "realistic_police_jailer") + elseif self.StepId == 2 then + Realistic_Police.CreateEntityTool(self:GetOwner(), "models/Humans/Group01/Female_02.mdl", "realistic_police_bailer") + elseif self.StepId == 3 then + Realistic_Police.CreateEntityTool(self:GetOwner(), "models/hunter/blocks/cube05x05x05.mdl", "realistic_police_jailpos") + end + end +end + +function TOOL:RightClick() + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.1 + + if SERVER then + local Ent = self:GetOwner():GetEyeTrace().Entity + if IsValid(Ent) then + if Ent:GetClass() == "realistic_police_bailer" or Ent:GetClass() == "realistic_police_jailer" or Ent:GetClass() == "realistic_police_jailpos" then + Ent:Remove() + end + end + end +end +function TOOL:DrawToolScreen( w, h ) + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + if CLIENT then + surface.SetDrawColor( Realistic_Police.Colors["darkblue"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor( Color(52, 73, 94) ) + surface.DrawRect( 15, 20 + ( self.StepId * 70 ) - 70, w-30, 74 ) + surface.SetDrawColor( Realistic_Police.Colors["white"] ) + surface.DrawRect( 15, 20 + ( self.StepId * 70 ) - 70, w-30, 2 ) + surface.SetDrawColor( Realistic_Police.Colors["white"] ) + surface.DrawRect( 15, 20 + ( self.StepId * 70 ) + 5, w-30, 2 ) + draw.SimpleText( "Jailor NPC", "rpt_font_18", w / 2, h / 4.7, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( "Bailor NPC", "rpt_font_18", w / 2, h / 2, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( "Jail Position", "rpt_font_18", w / 2, h / 1.3, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end +end + +function TOOL:CreateGhostRPTEnt(Model) + if CLIENT then + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + if not IsValid(self.RPTGhostEnt) then + self.RPTGhostEnt = ClientsideModel(Model, RENDERGROUP_OPAQUE) + self.RPTGhostEnt:SetModel(Model) + self.RPTGhostEnt:SetMaterial("models/wireframe") + self.RPTGhostEnt:SetPos(Vector(0,0,0)) + self.RPTGhostEnt:SetAngles(self:GetOwner():GetAngles()) + self.RPTGhostEnt:Spawn() + self.RPTGhostEnt:Activate() + self.RPTGhostEnt.Ang = self:GetOwner():GetAngles() + + self.RPTGhostEnt:SetRenderMode(RENDERMODE_TRANSALPHA) + self.RPTGhostEnt:SetColor(Realistic_Police.Colors["white"]) + end + end +end + +function TOOL:Reload(trace) + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.1 + + if self.StepId != 3 then + self.StepId = self.StepId + 1 + if self.StepId == 1 or self.StepId == 2 then + if IsValid(self.RPTGhostEnt) then + self.RPTGhostEnt:Remove() + end + self:CreateGhostRPTEnt("models/Humans/Group01/Female_02.mdl") + else + if IsValid(self.RPTGhostEnt) then + self.RPTGhostEnt:Remove() + end + self:CreateGhostRPTEnt("models/hunter/blocks/cube05x05x05.mdl") + end + else + if IsValid(self.RPTGhostEnt) then + self.RPTGhostEnt:Remove() + end + self.StepId = 1 + end +end + +function TOOL:Think() + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + + if self.StepId >= 1 or self.StepId <= 3 then + if IsValid(self.RPTGhostEnt) then + ply = self:GetOwner() + trace = util.TraceLine(util.GetPlayerTrace(ply)) + angle = ply:GetAimVector():Angle() + ang = Angle(0,angle.yaw + 180,0) + if self.StepId != 3 then + Pos = Vector(trace.HitPos.X, trace.HitPos.Y, trace.HitPos.Z) + else + Pos = Vector(trace.HitPos.X, trace.HitPos.Y, trace.HitPos.Z + 13) + end + self.RPTGhostEnt:SetPos(Pos) + self.RPTGhostEnt:SetAngles(ang) + else + if self.StepId == 1 or self.StepId == 2 then + self:CreateGhostRPTEnt("models/Humans/Group01/Female_02.mdl") + else + self:CreateGhostRPTEnt("models/hunter/blocks/cube05x05x05.mdl") + end + end + end +end + +function TOOL:Holster() + if IsValid(self.RPTGhostEnt) then + if IsValid(self.RPTGhostEnt) then + self.RPTGhostEnt:Remove() + end + end +end + +function TOOL.BuildCPanel( CPanel ) +CPanel:AddControl("label", { + Text = "Save Jailer / Bailer / Jail Pos Entities" }) + CPanel:Button("Save Entities", "rpt_savejailpos") + + CPanel:AddControl("label", { + Text = "Remove all Handcuff Entities in The Data" }) + CPanel:Button("Remove Entities Data", "rpt_removedatajail") + + CPanel:AddControl("label", { + Text = "Remove all Handcuff Entities in The Map" }) + CPanel:Button("Remove Entities Map", "rpt_removejail") + + CPanel:AddControl("label", { + Text = "Reload all Handcuff Entities in The Map" }) + CPanel:Button("Reload Entities Map", "rpt_reloadjailent") +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_plate_tool.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_plate_tool.lua new file mode 100644 index 0000000..9fd6e9d --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/gmod_tool/stools/realistic_police_plate_tool.lua @@ -0,0 +1,324 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile() +TOOL.Category = "Realistic Police" +TOOL.Name = "Plate-Setup" +TOOL.Author = "Kobralost" +TOOL.StepID = 1 + +if CLIENT then + RPTStepID = 1 + TOOL.Information = { + { name = "left" }, + { name = "right" }, + { name = "use" }, + { name = "reload" }, + } + language.Add("tool.realistic_police_plate_tool.name", "License Plate Setup") + language.Add("tool.realistic_police_plate_tool.desc", "Create or modify License Plate in your server") + language.Add("tool.realistic_police_plate_tool.left", "Left-Click to select the vehicle & Place the License Plate" ) + language.Add("tool.realistic_police_plate_tool.use", "Use to return to the previous step and modify the first License Plate" ) + language.Add("tool.realistic_police_plate_tool.right", "Right-Click to delete the license plate of the vehicle" ) + language.Add("tool.realistic_police_plate_tool.reload", "Reload to go to the next step & Place two License Plate" ) +end +-- Reset Variables of the Plate ( Rotation , Size , Position) +local function ResetPosition() + RPTPosition = { + [1] = 0, + [2] = 0, + [3] = 0, + [4] = 0, + [5] = 0, + [6] = 0, + [7] = 0, + [8] = 0, + } + RPTPosition2 = { + [1] = 0, + [2] = 0, + [3] = 0, + [4] = 0, + [5] = 0, + [6] = 0, + [7] = 0, + [8] = 0, + } +end + +function TOOL:Deploy() + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + if SERVER then + self.StepID = 1 + elseif CLIENT then + RPTToolSetup = false + RPTStepID = 1 + RPTGhostActivate = true + ResetPosition() + end +end + +function TOOL:Reload() + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.1 +-- dcb587233a6e567b06ae4da5655e3e9649694e7946f4d648e8f3181d8bc95b5f + + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + if CLIENT then + if RPTStepID == 2 then + if PosPlate != nil then + RPTStepID = 3 + RPTGhostActivate = true + end + end + end +end + +hook.Add("KeyPress", "RealisticPolice:KeyPress", function(ply, key) + if ( key == IN_USE ) then + if CLIENT then + if IsValid(ply) && ply:IsPlayer() then + if Realistic_Police && Realistic_Police.AdminRank && Realistic_Police.AdminRank[ply:GetUserGroup()] then + if RPTStepID == 3 then + RPTStepID = 2 + RPTGhostActivate = true + end + end + end + end + end +end ) +function TOOL:RightClick() -- Remove the Plate + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.5 + + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + + if CLIENT then + if Realistic_Police.AdminRank[LocalPlayer():GetUserGroup()] then + if RPTStepID == 2 then + PosPlate = nil + RPTGhostActivate = true + end + if RPTStepID == 3 then + PosPlate2 = nil + RPTGhostActivate = true + end + end + elseif SERVER then + local EntityTrace = self:GetOwner():GetEyeTrace().Entity + local RealisticPoliceFil = file.Read("realistic_police/vehicles.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + + if istable(RealisticPoliceTab[EntityTrace:GetModel()]) then + if EntityTrace:IsVehicle() then + RealisticPoliceTab[EntityTrace:GetModel()] = nil + file.Write("realistic_police/vehicles.txt", util.TableToJSON(RealisticPoliceTab)) +timer.Simple(0.1, function() + local RealisticPoliceFil = file.Read("realistic_police/vehicles.txt", "DATA") or "" + local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} + local CompressTable = util.Compress(RealisticPoliceFil) + net.Start("RealisticPolice:SendInformation") + net.WriteInt(CompressTable:len(), 32) + net.WriteData(CompressTable, CompressTable:len() ) + net.Broadcast() + end ) + end + else + EntityTrace:Remove() + end + end +end + +function TOOL:Think() -- Debug + if SERVER then + if not IsValid(self.car_setup) then + if self.StepID > 1 then + net.Start("RealisticPolice:UpdateInformation") + net.Send(self:GetOwner()) + self.StepID = 1 + end + end + end +end + +if CLIENT then + net.Receive("RealisticPolice:UpdateInformation", function() + if IsValid(RPTMain) then RPTMain:Remove() end + RPTStepID = 1 + RPTToolSetup = false + PosPlate = nil + RPTGhostActivate = true + PosPlate2 = nil + RPTGhostActivate = true + ResetPosition() + end ) +end + +function TOOL:LeftClick(trace) -- Place the Plate + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + self:GetOwner().AntiSpam = self:GetOwner().AntiSpam or CurTime() + if self:GetOwner().AntiSpam > CurTime() then return end + self:GetOwner().AntiSpam = CurTime() + 0.3 + + if SERVER then + local EntityTrace = trace.Entity + if EntityTrace:IsVehicle() then + if IsValid(self.car_setup) then + self.car_setup:Remove() + self.StepID = 2 + end + end + if self.StepID == 2 then + timer.Simple(0.2, function() + if IsValid(self:GetOwner()) then + net.Start("RealisticPolice:SetupVehicle") + net.WriteEntity(self.car_setup) + net.Send(self:GetOwner()) + end + end ) + end + if self.StepID == 1 then + if IsValid(EntityTrace) then + local EntitiyLook = self:GetOwner():GetEyeTrace().Entity + self.car_setup = ents.Create( "prop_physics" ) + self.car_setup:SetModel( EntitiyLook:GetModel() ) + self.car_setup:SetPos( EntitiyLook:GetPos() ) + self.car_setup:SetAngles( Angle(0,0,0) ) + self.car_setup.VehicleSetupRPT = true + self.car_setup.Owner = self:GetOwner() + self.car_setup:Spawn() + local phys = self.car_setup:GetPhysicsObject() + if phys and phys:IsValid() then + phys:EnableMotion(false) + end + timer.Create("rpt_timer_setupvehicle"..self.car_setup:EntIndex(), 0.1, 0, function() + if not IsValid(self.car_setup) then timer.Remove("rpt_timer_setupvehicle") RPTToolSetup = false end + if IsValid(self.car_setup) then + self.car_setup:SetAngles( Angle(0,0,0) ) + local phys = self.car_setup:GetPhysicsObject() + if phys and phys:IsValid() then + phys:EnableMotion(false) + end + end + end ) + EntitiyLook:Remove() + self.StepID = 2 + end + end + elseif CLIENT then + ResetPosition() + local EntityTrace = trace.Entity + if RPTStepID == 2 then + if RPTToolSetup then + PosPlate = LocalPlayer():GetEyeTrace().HitPos + local ang = LocalPlayer():GetAimVector():Angle() + AngPlate = Angle(0, ang.Yaw + -90, 90):SnapTo( "y", 1 ) + RPTGhostActivate = false + end + end + if RPTStepID == 3 then + PosPlate2 = LocalPlayer():GetEyeTrace().HitPos + local ang = LocalPlayer():GetAimVector():Angle() + AngPlate2 = Angle(0, ang.Yaw + -90, 90):SnapTo( "y", 1 ) + RPTGhostActivate = false + end + if EntityTrace:IsVehicle() then + RPTGhostActivate = true + RPTToolSetup = true + RPTStepID = 2 + end + end +end + +function TOOL:Holster() + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + if CLIENT then + RPTToolSetup = false + RPTGhostActivate = false + PosPlate = nil + PosPlate2 = nil + if IsValid(RPTMain) then RPTMain:Remove() end + ResetPosition() + else + if IsValid(self.car_setup) then self.car_setup:Remove() end + end +end + +local toolGun = { + [1] = Realistic_Police.GetSentence("configureLicenseplate"), + [2] = Realistic_Police.GetSentence("placeLicenseplate"), + [3] = Realistic_Police.GetSentence("placeLicenseplate2"), +} + +function TOOL:DrawToolScreen( w, h ) + if not Realistic_Police.AdminRank[self:GetOwner():GetUserGroup()] then return end + if CLIENT then + surface.SetDrawColor( Realistic_Police.Colors["black"] ) + surface.DrawRect( 0, 0, w, h ) + surface.SetDrawColor( Realistic_Police.Colors["bluetool"] ) + surface.DrawRect( 15, 20, w-30, 70 ) + surface.SetDrawColor( Realistic_Police.Colors["white"] ) + surface.DrawRect( 15, 85 , w-30, 2 ) + surface.SetDrawColor( Realistic_Police.Colors["white"] ) + surface.DrawRect( 15, 25, w-30, 2 ) + draw.SimpleText("Step "..RPTStepID, "rpt_font_18", w/2, h/5,Realistic_Police.Colors["white"],TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) + draw.DrawText(toolGun[RPTStepID], "rpt_font_18", w/2, h/2.3,Realistic_Police.Colors["white"],TEXT_ALIGN_CENTER) + end +end + +local function RealisticPoliceGhost(pos, ang, RptTable) + cam.Start3D2D( pos, ang, 0.02 ) + if Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["Image"] != nil then + surface.SetDrawColor( Realistic_Police.Colors["gray"] ) + surface.SetMaterial( Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["Image"] ) + surface.DrawTexturedRect( 0, 0, 1400 + RptTable[7], 400 + RptTable[8]) + end + if Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["Country"] != nil then + local Country = Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["Country"] + draw.SimpleText(Country, Realistic_Police.Fonts((1400 + RptTable[7])/12 + RptTable[8]*0.05), (1400 + RptTable[7]) - (1400 + RptTable[7])/Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["CountryPos"][1], (400 + RptTable[8])/Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["CountryPos"][2], Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["CountryColor"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + if Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["Department"] != nil then + draw.SimpleText(Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["Department"], Realistic_Police.Fonts((1400 + RptTable[7])/13+ RptTable[8]*0.05), (1400 + RptTable[7])/1.06, (400 + RptTable[8])/1.4, Realistic_Police.Colors["gray"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + if not Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlateText"] then + draw.SimpleText("ABDS582D", Realistic_Police.Fonts((1400 + RptTable[7])/6 + RptTable[8]*0.05),(1400 + RptTable[7])/Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][1] - 5, (400 + RptTable[8])/ Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][2]+5, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("ABDS582D", Realistic_Police.Fonts((1400 + RptTable[7])/6 + RptTable[8]*0.05), (1400 + RptTable[7])/Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][1], (400 + RptTable[8])/ Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][2], Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["TextColor"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText("AA-123-BB", Realistic_Police.Fonts((1400 + RptTable[7])/6 + RptTable[8]*0.05),(1400 + RptTable[7])/Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][1] - 5, (400 + RptTable[8])/ Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][2]+5, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("AA-123-BB", Realistic_Police.Fonts((1400 + RptTable[7])/6 + RptTable[8]*0.05),(1400 + RptTable[7])/Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][1], (400 + RptTable[8])/ Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["PlatePos"][2], Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["TextColor"], TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + if Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["ImageServer"] != nil then + surface.SetDrawColor( Realistic_Police.Colors["gray"] ) + surface.SetMaterial( Realistic_Police.PlateConfig[Realistic_Police.LangagePlate]["ImageServer"] ) + surface.DrawTexturedRect( (1400 + RptTable[7])/1.104, (400 + RptTable[8])/6, (1400 + RptTable[7])/14,(400 + RptTable[8])/3 ) + end + cam.End3D2D() +end +if CLIENT then -- Preview of the Plate + hook.Add("PostDrawTranslucentRenderables", "RPT:LicensePlateTool", function() -- Draw Plate + if RPTToolSetup then + if PosPlate != nil then + RealisticPoliceGhost(PosPlate + Vector(RPTPosition[1],RPTPosition[2],RPTPosition[3]), AngPlate + Angle(RPTPosition[4],RPTPosition[5],RPTPosition[6]), RPTPosition) + end + if PosPlate == nil or PosPlate2 == nil then + if RPTGhostActivate then + local Pos = LocalPlayer():GetEyeTrace().HitPos + local ang = LocalPlayer():GetAimVector():Angle() + RPTAngSnap = Angle(0, ang.Yaw + -90, 90):SnapTo( "y", 1 ) + RealisticPoliceGhost(Pos, RPTAngSnap + Angle(RPTPosition[4],RPTPosition[5],RPTPosition[6]), RPTPosition) + end + end + if PosPlate2 != nil then + RealisticPoliceGhost(PosPlate2 + Vector(RPTPosition2[1],RPTPosition2[2],RPTPosition2[3]), AngPlate2 + Angle(RPTPosition2[4],RPTPosition2[5],RPTPosition2[6]), RPTPosition2) + end + end + end ) +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_cuffed/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_cuffed/shared.lua new file mode 100644 index 0000000..6d7f79f --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_cuffed/shared.lua @@ -0,0 +1,98 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile() + +SWEP.PrintName = "Cuffed" +SWEP.Author = "Kobralost" +SWEP.Purpose = "" + +SWEP.Slot = 2 +SWEP.SlotPos = 1 + +SWEP.Spawnable = false + +SWEP.Category = "Realistic Police" +SWEP.ViewModel = Model( "" ) +SWEP.WorldModel = Model("models/realistic_police/handcuffs/w_deploy_handcuffs.mdl") +SWEP.ViewModelFOV = 75 +SWEP.UseHands = false + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" + +SWEP.DrawAmmo = false + +SWEP.HitDistance = 125 + +SWEP.DarkRPCanLockpick = true +function SWEP:Deploy() + local Owner = self:GetOwner() + -- Manipulate the Bone of the Player + timer.Simple(0.2, function() + for k,v in pairs(Realistic_Police.ManipulateBoneCuffed) do + local bone = Owner:LookupBone(k) + if bone then + Owner:ManipulateBoneAngles(bone, v) + end + end + self:SetHoldType("passive") + end) +end +if CLIENT then + local WorldModel = ClientsideModel(SWEP.WorldModel) + + WorldModel:SetSkin(1) + WorldModel:SetNoDraw(true) + + function SWEP:DrawWorldModel() + local _Owner = self:GetOwner() + + if (IsValid(_Owner)) then + + local offsetVec = Vector(5, 1, -9) + local offsetAng = Angle(190, 140, -20) + + local boneid = _Owner:LookupBone("ValveBiped.Bip01_R_Hand") + if !boneid then return end + + local matrix = _Owner:GetBoneMatrix(boneid) + if !matrix then return end +local newPos, newAng = LocalToWorld(offsetVec, offsetAng, matrix:GetTranslation(), matrix:GetAngles()) + + WorldModel:SetPos(newPos) + WorldModel:SetAngles(newAng) + WorldModel:SetupBones() + else + WorldModel:SetPos(self:GetPos()) + WorldModel:SetAngles(self:GetAngles()) + end + + WorldModel:DrawModel() + end + WorldModel:SetModelScale( WorldModel:GetModelScale() * 1.3, 0 ) +end + +if CLIENT then + function SWEP:DrawHUD() + draw.SimpleTextOutlined(Realistic_Police.GetSentence("youArrest"),"rpt_font_9", ScrW()/2, ScrH()/15, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM,2, Realistic_Police.Colors["black"]) + if math.Round(LocalPlayer():GetNWInt("rpt_arrest_time") - CurTime()) > 0 then + draw.SimpleTextOutlined("[ "..math.Round(LocalPlayer():GetNWInt("rpt_arrest_time") - CurTime()).." ] - "..Realistic_Police.GetSentence("seconds"),"rpt_font_9", ScrW()/2, ScrH()/11, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM,2, Realistic_Police.Colors["black"]) + end + end +end + +function SWEP:CanPrimaryAttack() end +function SWEP:CanSecondaryAttack() end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_finebook/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_finebook/shared.lua new file mode 100644 index 0000000..34278d1 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_finebook/shared.lua @@ -0,0 +1,116 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile() + +SWEP.PrintName = "Книга штрафов" +SWEP.Author = "Kobralost" +SWEP.Purpose = "" +SWEP.Slot = 0 +SWEP.SlotPos = 4 + +SWEP.Spawnable = true +-- a91f6942e11bb9a70ff8e3d7a0dcc737fd407c85d2d10ff95a1cfed59177e84a + +SWEP.Category = "Realistic Police" +SWEP.ViewModel = Model( "models/realistic_police/finebook/c_notebook.mdl" ) +SWEP.WorldModel = Model("models/realistic_police/finebook/w_notebook.mdl") +SWEP.ViewModelFOV = 75 +SWEP.UseHands = true +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" +SWEP.DrawAmmo = false + +SWEP.HitDistance = 125 + +function SWEP:Deploy() + local Owner = self:GetOwner() + + self:SendWeaponAnim( ACT_VM_DRAW ) + self:SetNextPrimaryFire( CurTime() + self:SequenceDuration() ) + -- Play the idle animation next the primary attack + timer.Create("rpt_animation"..self:GetOwner():EntIndex(), self:SequenceDuration(), 1, function() + if IsValid(self) && IsValid(Owner) then + if Owner:GetActiveWeapon() == self then + self:SendWeaponAnim( ACT_VM_IDLE ) + end + end + end ) +end + +function SWEP:PrimaryAttack() + local Owner = self:GetOwner() + + Owner.AntiSpam = Owner.AntiSpam or CurTime() + if Owner.AntiSpam > CurTime() then return end + Owner.AntiSpam = CurTime() + 1 + + if SERVER then + Owner.WeaponRPT["Fine"] = Owner:GetEyeTrace().Entity + if IsValid(Owner.WeaponRPT["Fine"]) then + + -- Check if the entity is a vehicle or a player + if Owner.WeaponRPT["Fine"]:IsPlayer() or Owner.WeaponRPT["Fine"]:IsVehicle() then + + -- Check if the Police Man is near the entity + if Owner:GetPos():DistToSqr(Owner.WeaponRPT["Fine"]:GetPos()) < 140^2 then + + -- Check if the player can add a fine + if not Realistic_Police.JobCanAddFine[Realistic_Police.GetPlayerJob(Owner)] then + Realistic_Police.SendNotify(Owner, Realistic_Police.GetSentence("noGoodJob")) + return + end + + -- Check if the Player can have a fine + if Owner.WeaponRPT["Fine"]:IsPlayer() then + if Realistic_Police.JobCantHaveFine[Realistic_Police.GetPlayerJob(Owner.WeaponRPT["Fine"])] then + Realistic_Police.SendNotify(Owner, Realistic_Police.GetSentence("userCantReceiveFine")) + return + end + end + + -- Check if this type of vehicle can have a fine + if Owner.WeaponRPT["Fine"]:IsVehicle() then + if Realistic_Police.VehicleCantHaveFine[Owner.WeaponRPT["Fine"]:GetVehicleClass()] then + Realistic_Police.SendNotify(Owner, Realistic_Police.GetSentence("vehicleCantHaveFine")) + return + end + end + + -- Send information about the type of the entity if it's a player or a vehicle + local RPTCheck = nil + if Owner.WeaponRPT["Fine"]:IsPlayer() then + Owner.WeaponRPT["Fine"]:Freeze(true) + RPTCheck = false + else + RPTCheck = true + end + + -- Remove the Unfreeze timer of the player + if timer.Exists("rpt_stungun_unfreeze"..Owner.WeaponRPT["Fine"]:EntIndex()) then + timer.Remove("rpt_stungun_unfreeze"..Owner.WeaponRPT["Fine"]:EntIndex()) + end + + net.Start("RealisticPolice:Open") + net.WriteString("OpenFiningMenu") + net.WriteBool(RPTCheck) + net.Send(Owner) + end + end + end + end +end + +function SWEP:SecondaryAttack() end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_handcuff/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_handcuff/shared.lua new file mode 100644 index 0000000..1c779f2 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_handcuff/shared.lua @@ -0,0 +1,272 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + + +AddCSLuaFile() + +SWEP.PrintName = "Наручники" +SWEP.Author = "Kobralost" +SWEP.Purpose = "" + +SWEP.Slot = 0 +SWEP.SlotPos = 4 + +SWEP.Spawnable = true + +SWEP.Category = "Realistic Police" + +SWEP.ViewModel = Model("models/realistic_police/handcuffs/c_handcuffs.mdl") +SWEP.WorldModel = Model("models/realistic_police/handcuffs/w_handcuffs.mdl") +SWEP.ViewModelFOV = 80 +SWEP.UseHands = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" + +SWEP.DrawAmmo = false + +SWEP.HitDistance = 125 + +function SWEP:PrimaryAttack() + local Owner = self:GetOwner() + if not IsValid(Owner) then return end + + local ply = Owner:GetEyeTrace().Entity + if not IsValid(ply) then return end + + Owner.AntiSpam = Owner.AntiSpam or CurTime() + if Owner.AntiSpam > CurTime() then return end + Owner.AntiSpam = CurTime() + 1 + + if DarkRP and ply:IsPlayer() then + if ply.isArrested && ply:isArrested() then return end + end + + -- Check if the Police Man Can Cuff the player + if not Realistic_Police.CanCuff[Realistic_Police.GetPlayerJob(Owner)] then + if SERVER then + Realistic_Police.SendNotify(Owner, Realistic_Police.GetSentence("noGoodJobToArrestuser")) + end + return + end + + -- Check if the player can be cuff or not + if IsValid(ply) && ply:IsPlayer() then + if Realistic_Police.CantBeCuff[Realistic_Police.GetPlayerJob(ply)] then + if SERVER then + Realistic_Police.SendNotify(Owner, Realistic_Police.GetSentence("cantArrestPlayer")) + end + return + end + else + return + end + + if Owner:GetPos():DistToSqr(ply:GetPos()) > 20000 then return end + + if SERVER then + + if ply.WeaponRPT["Cuff"] == nil or not ply.WeaponRPT["Cuff"] then + + -- Freeze the Player and The police man + ply:Freeze(true) + Owner:Freeze(true) + ply.WeaponRPT["processCuff"] = true + + -- Remove the Unfreeze timer of the player + if timer.Exists("rpt_stungun_unfreeze"..ply:EntIndex()) then + timer.Remove("rpt_stungun_unfreeze"..ply:EntIndex()) + end + + timer.Simple(self:SequenceDuration(), function() + if IsValid(ply) && IsValid(Owner) then + Realistic_Police.Cuff(ply, Owner) + + if IsValid(ply) then ply:Freeze(false) end + if IsValid(Owner) then Owner:Freeze(false) end + end + end) + + -- Send the Weapon Animation and the weapon sound + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + self:SetNextPrimaryFire( CurTime() + 4 ) + + -- Play the idle animation next the primary attack + timer.Create("rpt_animation"..self:GetOwner():EntIndex(), self:SequenceDuration(), 1, function() + if IsValid(self) && IsValid(Owner) then + if Owner:GetActiveWeapon() == self then + self:SendWeaponAnim( ACT_VM_IDLE ) + end + end + end ) + + elseif ply.WeaponRPT["Cuff"] then + + -- Freeze the Player and The police man + ply:Freeze(true) + Owner:Freeze(true) + + -- Remove the Unfreeze timer of the player + if timer.Exists("rpt_stungun_unfreeze"..ply:EntIndex()) then + timer.Remove("rpt_stungun_unfreeze"..ply:EntIndex()) + end + + timer.Simple(self:SequenceDuration(), function() + if IsValid(ply) && IsValid(Owner) then + Realistic_Police.UnCuff(ply, Owner) +if IsValid(ply) then ply:Freeze(false) end + if IsValid(Owner) then Owner:Freeze(false) end + end + end ) +-- Send the Weapon Animation and the weapon sound + self:SendWeaponAnim( ACT_VM_SECONDARYATTACK ) + self:SetNextSecondaryFire( CurTime() + 4 ) + + -- Play the idle animation next the primary attack + timer.Create("rpt_animation"..self:GetOwner():EntIndex(), self:SequenceDuration(), 1, function() + if IsValid(self) && IsValid(Owner) then + if Owner:GetActiveWeapon() == self then + self:SendWeaponAnim( ACT_VM_IDLE ) + end + end + end ) + end + end + + self.Weapon:EmitSound("rpthandcuffleftclick.mp3") +end + +function SWEP:SecondaryAttack() + local Owner = self:GetOwner() + local ply = Owner:GetEyeTrace().Entity + + Owner.AntiSpam = Owner.AntiSpam or CurTime() + if Owner.AntiSpam > CurTime() then return end + Owner.AntiSpam = CurTime() + 1 + + if SERVER then + -- Check if the Player is Near the Police Man + if IsValid(ply) && IsValid(Owner) && not ply:IsVehicle() then +if istable(ply.WeaponRPT) then + if ply.WeaponRPT["Cuff"] then + Realistic_Police.Drag(ply, Owner) + end + end + + elseif ply:IsVehicle() then + if IsValid(Owner.WeaponRPT["Drag"]) then + Owner.WeaponRPT["Drag"].WeaponRPT["EnterExit"] = false + if SVMOD && SVMOD:GetAddonState() then + for i, seat in ipairs(ply.SV_Data.Seats) do + if not seat.Entity and i ~= 1 then + + local seat = ply:SV_CreateSeat(i) + Owner.WeaponRPT["Drag"]:EnterVehicle(seat) + end + end + elseif VC then + Owner.WeaponRPT["Drag"]:EnterVehicle(Realistic_Police.PlaceVehicle(Owner, ply)) + else + local tr = util.TraceLine( { + start = Owner:EyePos(), + endpos = Owner:EyePos() + Owner:EyeAngles():Forward() * 100, + }) + + for k, v in pairs(ents.FindInSphere(tr.HitPos, 75)) do + if not IsValid(v) or not v:IsVehicle() then continue end + + Owner.WeaponRPT["Drag"]:EnterVehicle(v) + end + end + Owner.WeaponRPT["Drag"].WeaponRPT["EnterExit"] = true + + -- Reset Drag Player + Owner.WeaponRPT["Drag"].WeaponRPT["DragedBy"] = nil + Owner.WeaponRPT["Drag"] = nil + else + if SVMOD && SVMOD:GetAddonState() then + local playerInVehicle = ply:SV_GetAllPlayers() +if istable(playerInVehicle) then + for k,v in pairs(playerInVehicle) do + if v.WeaponRPT["Cuff"] then + v.WeaponRPT["EnterExit"] = false + v:ExitVehicle() + v.WeaponRPT["EnterExit"] = true + end + end + end + elseif VC then + local playerInVehicle = ply:VC_getPlayers() +if istable(playerInVehicle) then + for k,v in pairs(playerInVehicle) do + if v.WeaponRPT["Cuff"] then + v.WeaponRPT["EnterExit"] = false + v:ExitVehicle() + v.WeaponRPT["EnterExit"] = true + end + end + end + else + local tr = util.TraceLine( { + start = Owner:EyePos(), + endpos = Owner:EyePos() + Owner:EyeAngles():Forward() * 100, + }) + + for k, v in pairs(ents.FindInSphere(tr.HitPos, 75)) do + if not IsValid(v) or not v:IsPlayer() then continue end + if v.WeaponRPT["Cuff"] then + v.WeaponRPT["EnterExit"] = false + v:ExitVehicle() + v.WeaponRPT["EnterExit"] = true + end + end + end + end + end + end +end + +function SWEP:Reload() + local Owner = self:GetOwner() + local ply = Owner:GetEyeTrace().Entity + + Owner.AntiSpam = Owner.AntiSpam or CurTime() + if Owner.AntiSpam > CurTime() then return end + Owner.AntiSpam = CurTime() + 1 + + if SERVER then + if IsValid(ply) && ply:IsPlayer() && ply.WeaponRPT && ply.WeaponRPT["Cuff"] then + -- Open the inspect menu for confiscate weapons + net.Start("RealisticPolice:HandCuff") + net.WriteString("Inspect") + net.WriteEntity(ply) + net.Send(Owner) + end + end +end + +function SWEP:Deploy() + local Owner = self:GetOwner() + self:SendWeaponAnim( ACT_VM_DRAW ) + self.Weapon:EmitSound("rpthandcuffdeploy.mp3") + -- Play the idle animation next the primary attack + timer.Create("rpt_animation"..self:GetOwner():EntIndex(), self:SequenceDuration(), 1, function() + if IsValid(self) && IsValid(Owner) then + if Owner:GetActiveWeapon() == self then + self:SendWeaponAnim( ACT_VM_IDLE ) + end + end + end ) +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_stungun/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_stungun/shared.lua new file mode 100644 index 0000000..2dbf23a --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_stungun/shared.lua @@ -0,0 +1,143 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile() +SWEP.PrintName = "Электрошокер" +SWEP.Author = "Kobralost" +SWEP.Purpose = "" + +SWEP.Slot = 0 +SWEP.SlotPos = 4 + +SWEP.Spawnable = true + +SWEP.Category = "Realistic Police" + +SWEP.ViewModel = Model("models/realistic_police/taser/c_taser.mdl") +SWEP.WorldModel = Model("models/realistic_police/taser/w_taser.mdl") +SWEP.ViewModelFOV = 60 +SWEP.UseHands = true + +SWEP.Base = "weapon_base" + +local ShootSound = Sound("Weapon_Pistol.Single") +SWEP.Primary.Damage = 1 +SWEP.Primary.TakeAmmo = 1 +SWEP.Primary.ClipSize = 1 +SWEP.Primary.Ammo = "Pistol" + +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Spread = 0.1 +SWEP.Primary.NumberofShots = 1 +SWEP.Primary.Automatic = false +SWEP.Primary.Recoil = .2 +SWEP.Primary.Delay = 0.1 +SWEP.Primary.Force = 100 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.DrawAmmo = true + +SWEP.HitDistance = 125 + +function SWEP:Deploy() + local Owner = self:GetOwner() + self:SendWeaponAnim( ACT_VM_DRAW ) + -- Play the idle animation next the primary attack + timer.Create("rpt_animation"..self:GetOwner():EntIndex(), self:SequenceDuration(), 1, function() + if IsValid(self) && IsValid(Owner) then + if Owner:GetActiveWeapon() == self then + self:SendWeaponAnim( ACT_VM_IDLE ) + end + end + end ) +end + +function SWEP:Trace() + local mins = Vector( -30, 0, 0 ) + local maxs = Vector( 30, 0, 0 ) + + local startpos = self.Owner:GetPos() + self.Owner:GetForward() * 40 + self.Owner:GetUp() * 60 + local dir = self.Owner:GetAngles():Forward() + local len = 500 + + local tr = util.TraceHull( { + start = startpos, + endpos = startpos + dir * len, + maxs = maxs, + mins = mins, + filter = self.Owner, + }) + + return tr +end +function SWEP:PrimaryAttack() + local Owner = self:GetOwner() + if ( !self:CanPrimaryAttack() ) then return end + + local bullet = {} + bullet.Num = self.Primary.NumberofShots + bullet.Src = self.Owner:GetShootPos() + bullet.Dir = self.Owner:GetAimVector() + bullet.Spread = Vector( self.Primary.Spread * 0.1 , self.Primary.Spread * 0.1, 0) + bullet.Tracer = 1 + bullet.Force = self.Primary.Force + bullet.Damage = self.Primary.Damage + bullet.AmmoType = self.Primary.Ammo + + local rnda = self.Primary.Recoil * -1 + local rndb = self.Primary.Recoil * math.random(-1, 1) + + self:ShootEffects() + + local tr = self:Trace() + local ply = tr.Entity +local tracepos = util.TraceLine(util.GetPlayerTrace( self.Owner )) + local effect = EffectData() + effect:SetOrigin( tracepos.HitPos ) + effect:SetStart( self.Owner:GetShootPos() ) + effect:SetAttachment( 1 ) + effect:SetEntity( self ) + util.Effect( "ToolTracer", effect ) +-- Take a bullet of primary ammo + self.Weapon:EmitSound("rptstungunshot2.mp3") + + if IsValid(ply) && ply:IsPlayer() then + -- Check if the player is near the player + if Owner:GetPos():DistToSqr(ply:GetPos()) < 600^2 then + if SERVER then + if not Realistic_Police.CantBeStun[Realistic_Police.GetPlayerJob(ply)] then + ply:EmitSound( "ambient/voices/m_scream1.wav" ) + Realistic_Police.Tazz(ply) + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneCuffed, ply) + Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneSurrender, ply) + ply:EmitSound("rptstungunmain.mp3") + + hook.Run("RealisticPolice:Tazz", ply, self:GetOwner() ) + end + end + end + end + self:TakePrimaryAmmo(self.Primary.TakeAmmo) + self:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) +end + +function SWEP:Reload() + local wep = self:GetOwner():GetActiveWeapon() + if ( !IsValid( wep ) ) then return -1 end + local ammo = self:GetOwner():GetAmmoCount( wep:GetPrimaryAmmoType() ) + + if self:Clip1() == 0 && ammo != 0 then + self.Weapon:EmitSound("rptstungunreload.mp3") + self.Weapon:DefaultReload( ACT_VM_RELOAD ); + end +end +function SWEP:SecondaryAttack() end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_surrender/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_surrender/shared.lua new file mode 100644 index 0000000..f630ac3 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_surrender/shared.lua @@ -0,0 +1,66 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +AddCSLuaFile() + +SWEP.PrintName = "Сдаться" +SWEP.Author = "Kobralost" +SWEP.Purpose = "" + +SWEP.Slot = 2 +SWEP.SlotPos = 1 + +SWEP.Spawnable = false +SWEP.Category = "Realistic Police" + +SWEP.ViewModel = Model( "models/weapons/sycreations/Kobralost/handsup.mdl" ) +SWEP.WorldModel = Model("") +SWEP.ViewModelFOV = 55 +SWEP.UseHands = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +-- d4ec95c252515613de193db925956fba07d4ff50f580a4b42b7f507f9c716945 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" + +SWEP.DrawAmmo = false + +SWEP.HitDistance = 125 +-- a91f6942e11bb9a70ff8e3d7a0dcc737fd407c85d2d10ff95a1cfed59177e84a + +function SWEP:Deploy() + local Owner = self:GetOwner() + -- Manipulate the Bone of the Player + timer.Simple(0.2, function() + if not IsValid(self) or not IsValid(Owner) then return end + + for k,v in pairs(Realistic_Police.ManipulateBoneSurrender) do + local bone = Owner:LookupBone(k) + if bone then + Owner:ManipulateBoneAngles(bone, v) + end + end + self:SetHoldType("passive") + Owner.RPTSurrender = true + end ) +end + +if CLIENT then + function SWEP:DrawHUD() + draw.SimpleTextOutlined(Realistic_Police.GetSentence("youSurrender").." ( Press "..Realistic_Police.SurrenderInfoKey.." )","rpt_font_9", ScrW()/2, ScrH()/15, Realistic_Police.Colors["white"], TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM,2, Realistic_Police.Colors["black"]) + end +end + +function SWEP:CanPrimaryAttack() end +function SWEP:CanSecondaryAttack() end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_wrench/shared.lua b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_wrench/shared.lua new file mode 100644 index 0000000..9adecc1 --- /dev/null +++ b/garrysmod/addons/realistic_police_v1.7.8/lua/weapons/weapon_rpt_wrench/shared.lua @@ -0,0 +1,112 @@ + +/* + Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee + Version: v1.7.8 (stable) +*/ + +-- 3aaf217dbda72c52d8532897f5c9000ac85eca339f30615d1f3f36b925d66cfe + +AddCSLuaFile() +SWEP.PrintName = "Гаечный ключ" +SWEP.Author = "Kobralost" +SWEP.Purpose = "" + +SWEP.Slot = 0 +SWEP.SlotPos = 4 + +SWEP.Spawnable = true + +SWEP.Category = "Realistic Police" + +SWEP.ViewModel = Model( "models/weapons/sycreations/Kobralost/v_ajustableWrench.mdl" ) +SWEP.WorldModel = Model( "models/weapons/sycreations/Kobralost/w_ajustableWrench.mdl" ) + +SWEP.ViewModelFOV = 55 +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 = true +SWEP.Secondary.Ammo = "none" + +SWEP.DrawAmmo = false +SWEP.HitDistance = 48 + +function SWEP:Initialize() + self:SetHoldType( "slam" ) +end + +function SWEP:PrimaryAttack() + if ( CLIENT ) then return end + local Owner = self:GetOwner() + local ply = Owner:GetEyeTrace().Entity + + -- Check if the entity trace is a camera + if IsValid(ply) && ply:GetClass() == "realistic_police_camera" then + + -- Check if the player is near the entity + if Owner:GetPos():DistToSqr(ply:GetPos()) < 125^2 then + + -- Check if the Camera is broke + if ply.DestroyCam then +-- Check if the Player have the correct job for repair the camera + if Realistic_Police.CameraWorker[Realistic_Police.GetPlayerJob(Owner)] then + + -- Play the Primary Attack animation + Owner:Freeze(true) + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + timer.Create("rpt_repair_camera2"..Owner:EntIndex(), 5, (Realistic_Police.CameraRepairTimer/5) - 1, function() + if IsValid(Owner) then + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + end + end) + + -- Create Movement on the camera of the player + local Punch = { 0.1, 0.5, 1, 1.2, 0.3, -0.5, -0.9, -1.6, -0.4, -0.8 } + timer.Create("rpt_repeat_anim"..Owner:EntIndex(), 1, Realistic_Police.CameraRepairTimer, function() + if IsValid(Owner) then + Owner:ViewPunch( Angle( table.Random(Punch), table.Random(Punch), table.Random(Punch) ) ) + end + end) + + -- Reset Rotation , Player Active Sequence and Reset Health of the camera + timer.Create("rpt_camera_repair"..Owner:EntIndex(), Realistic_Police.CameraRepairTimer, 1, function() + if IsValid(ply) then + -- Reset Camera Information + ply:ResetSequence("active") + ply:SetSequence("active") + ply.HealthEntity = Realistic_Police.CameraHealth + ply:SetRptCam(false) + ply.DestroyCam = false + + -- Unfreeze the Player && give the money + Owner:Freeze(false) + Owner:addMoney(Realistic_Police.CameraGiveMoney) + Realistic_Police.SendNotify(Owner, Realistic_Police.GetSentence("youWon").." "..DarkRP.formatMoney(Realistic_Police.CameraGiveMoney)) + end + end ) + end + end + end + end +end +function SWEP:SecondaryAttack() end + +function SWEP:Holster() + if SERVER then + self:GetOwner():Freeze(false) + timer.Remove("rpt_camera_repair"..self:GetOwner():EntIndex()) + timer.Remove("rpt_repair_camera2"..self:GetOwner():EntIndex()) + timer.Remove("rpt_repeat_anim"..self:GetOwner():EntIndex()) + self:SendWeaponAnim( ACT_VM_IDLE ) + end + return true +end + + diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.dx80.vtx new file mode 100644 index 0000000..931faad Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.dx90.vtx new file mode 100644 index 0000000..aa5b35e Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.mdl new file mode 100644 index 0000000..fa32e02 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.phy b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.phy new file mode 100644 index 0000000..22a281f Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.sw.vtx new file mode 100644 index 0000000..ac33bf6 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.vvd new file mode 100644 index 0000000..ade4285 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/props/sycreations/Workstation.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.dx80.vtx new file mode 100644 index 0000000..ea90a4f Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.dx90.vtx new file mode 100644 index 0000000..98f8611 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.mdl new file mode 100644 index 0000000..93bfe53 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.sw.vtx new file mode 100644 index 0000000..fc1aa37 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.vvd new file mode 100644 index 0000000..8ca0c7a Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/c_notebook.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.dx80.vtx new file mode 100644 index 0000000..4f99e34 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.dx90.vtx new file mode 100644 index 0000000..70deb5f Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.mdl new file mode 100644 index 0000000..f81dc19 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.phy b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.phy new file mode 100644 index 0000000..3ce8c6d Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.sw.vtx new file mode 100644 index 0000000..533b75e Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.vvd new file mode 100644 index 0000000..280fd4c Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/finebook/w_notebook.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.dx80.vtx new file mode 100644 index 0000000..102aeee Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.dx90.vtx new file mode 100644 index 0000000..d1eb5e3 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.mdl new file mode 100644 index 0000000..312cccc Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.sw.vtx new file mode 100644 index 0000000..b3e2fa0 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.vvd new file mode 100644 index 0000000..31dc1ce Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/c_handcuffs.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.dx80.vtx new file mode 100644 index 0000000..d6c450a Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.dx90.vtx new file mode 100644 index 0000000..e5d35e2 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.mdl new file mode 100644 index 0000000..f429f59 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.phy b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.phy new file mode 100644 index 0000000..2c600aa Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.sw.vtx new file mode 100644 index 0000000..a90ab13 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.vvd new file mode 100644 index 0000000..1655a5a Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_deploy_handcuffs.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.dx80.vtx new file mode 100644 index 0000000..04e01f6 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.dx90.vtx new file mode 100644 index 0000000..a2f47ed Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.mdl new file mode 100644 index 0000000..45fc0d2 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.phy b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.phy new file mode 100644 index 0000000..2667614 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.sw.vtx new file mode 100644 index 0000000..e253576 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.vvd new file mode 100644 index 0000000..f9ba6e8 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.dx80.vtx new file mode 100644 index 0000000..33b0aab Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.dx90.vtx new file mode 100644 index 0000000..ac5993c Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.mdl new file mode 100644 index 0000000..f9191ad Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.phy b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.phy new file mode 100644 index 0000000..8ef2e15 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.sw.vtx new file mode 100644 index 0000000..4d485ac Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.vvd new file mode 100644 index 0000000..1c57c52 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/handcuffs/w_handcuffs_b.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.dx80.vtx new file mode 100644 index 0000000..0a55630 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.dx90.vtx new file mode 100644 index 0000000..d898d6d Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.mdl new file mode 100644 index 0000000..a2171a3 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.sw.vtx new file mode 100644 index 0000000..d64b1aa Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.vvd new file mode 100644 index 0000000..6b448c5 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/c_taser.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.dx80.vtx new file mode 100644 index 0000000..2396578 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.dx90.vtx new file mode 100644 index 0000000..0174a35 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.mdl new file mode 100644 index 0000000..34959a0 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.phy b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.phy new file mode 100644 index 0000000..b327956 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.sw.vtx new file mode 100644 index 0000000..7b20564 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.vvd new file mode 100644 index 0000000..f0a3c71 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/realistic_police/taser/w_taser.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.dx80.vtx new file mode 100644 index 0000000..af26c1c Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.dx90.vtx new file mode 100644 index 0000000..ae84241 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.mdl new file mode 100644 index 0000000..bf232bd Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.phy b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.phy new file mode 100644 index 0000000..0ff55a4 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.sw.vtx new file mode 100644 index 0000000..cbecf48 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.vvd new file mode 100644 index 0000000..eea936f Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/wasted/wasted_kobralost_camera.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.dx80.vtx new file mode 100644 index 0000000..ac1abcf Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.dx90.vtx new file mode 100644 index 0000000..552ab01 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.mdl new file mode 100644 index 0000000..74bbc9c Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.sw.vtx new file mode 100644 index 0000000..5b849f5 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.vvd new file mode 100644 index 0000000..4c079ec Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/handsup.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.dx80.vtx new file mode 100644 index 0000000..5786c86 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.dx90.vtx new file mode 100644 index 0000000..1ddc1b0 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.mdl new file mode 100644 index 0000000..5b56207 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.sw.vtx new file mode 100644 index 0000000..eff647c Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.vvd new file mode 100644 index 0000000..943d9b8 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/v_ajustableWrench.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.dx80.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.dx80.vtx new file mode 100644 index 0000000..6002203 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.dx80.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.dx90.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.dx90.vtx new file mode 100644 index 0000000..6e725a3 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.dx90.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.mdl b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.mdl new file mode 100644 index 0000000..dfcd7e1 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.mdl differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.phy b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.phy new file mode 100644 index 0000000..30d84de Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.phy differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.sw.vtx b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.sw.vtx new file mode 100644 index 0000000..2c9a494 Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.sw.vtx differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.vvd b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.vvd new file mode 100644 index 0000000..a66df9b Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/models/weapons/sycreations/kobralost/w_ajustableWrench.vvd differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/resource/fonts/LicensePlate-j9eO.ttf b/garrysmod/addons/realistic_police_v1.7.8/resource/fonts/LicensePlate-j9eO.ttf new file mode 100644 index 0000000..85457ad Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/resource/fonts/LicensePlate-j9eO.ttf differ diff --git a/garrysmod/addons/realistic_police_v1.7.8/resource/fonts/PoiretOne-Regular.ttf b/garrysmod/addons/realistic_police_v1.7.8/resource/fonts/PoiretOne-Regular.ttf new file mode 100644 index 0000000..2da1bcf Binary files /dev/null and b/garrysmod/addons/realistic_police_v1.7.8/resource/fonts/PoiretOne-Regular.ttf differ diff --git a/garrysmod/addons/realistichandcuffs/lua/autorun/mrp_handcuffs_ext.lua b/garrysmod/addons/realistichandcuffs/lua/autorun/mrp_handcuffs_ext.lua new file mode 100644 index 0000000..5609b0c --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/autorun/mrp_handcuffs_ext.lua @@ -0,0 +1,876 @@ +-- lua/autorun/mrp_handcuffs_ext.lua +-- Realistic Handcuffs Extension for Military RP + +if SERVER then + util.AddNetworkString("MRP_Handcuffs_PerformJail") + util.AddNetworkString("RHC_Jailer_Menu") + util.AddNetworkString("MRP_Handcuffs_PerformUnjail") + util.AddNetworkString("MRP_Handcuffs_OpenInteract") + util.AddNetworkString("MRP_Handcuffs_DoInteract") + util.AddNetworkString("MRP_UpdateJailInfo") + + -- Data persistence for cells + local cellFilePath = "mrp_jailcells.txt" + MRP_JailCells = {RF = {}, UK = {}} + if file.Exists(cellFilePath, "DATA") then + local data = util.JSONToTable(file.Read(cellFilePath, "DATA") or "{}") or {} + MRP_JailCells.RF = data.RF or {} + MRP_JailCells.UK = data.UK or {} + end + + local function SaveCells() + file.Write(cellFilePath, util.TableToJSON(MRP_JailCells)) + end + + -- Helper to get player being looked at + local function GetTarget(ply) + local tr = ply:GetEyeTrace() + if IsValid(tr.Entity) and tr.Entity:IsPlayer() and tr.Entity:GetPos():DistToSqr(ply:GetPos()) < 100000 then + return tr.Entity + end + return nil + end + + -- Console commands as fallback for setting cells + concommand.Add("mrp_setcellrf", function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then return end + local cellNum = tonumber(args[1]) + if cellNum and cellNum >= 1 and cellNum <= 3 then + MRP_JailCells.RF[cellNum] = ply:GetPos() + SaveCells() + if IsValid(ply) then ply:ChatPrint("Позиция камеры РФ " .. cellNum .. " установлена на ваше местоположение.") else print("Set.") end + else + if IsValid(ply) then ply:ChatPrint("Использование: mrp_setcellrf [1-3] в консоли") end + end + end) + + concommand.Add("mrp_setcelluk", function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then return end + local cellNum = tonumber(args[1]) + if cellNum and cellNum >= 1 and cellNum <= 3 then + MRP_JailCells.UK[cellNum] = ply:GetPos() + SaveCells() + if IsValid(ply) then ply:ChatPrint("Позиция камеры УК " .. cellNum .. " установлена на ваше местоположение.") else print("Set.") end + else + if IsValid(ply) then ply:ChatPrint("Использование: mrp_setcelluk [1-3] в консоли") end + end + end) + + + + -- Chat commands + hook.Add("PlayerSay", "MRP_Handcuffs_ChatCommands", function(ply, text) + local args = string.Explode(" ", string.lower(text)) + local cmd = args[1] + + if cmd == "/setcellrf" then + if not ply:IsSuperAdmin() then return "" end + local cellNum = tonumber(args[2]) + if cellNum and cellNum >= 1 and cellNum <= 3 then + MRP_JailCells.RF[cellNum] = ply:GetPos() + SaveCells() + ply:ChatPrint("Позиция камеры РФ " .. cellNum .. " установлена на ваше местоположение.") + else + ply:ChatPrint("Использование: /setcellrf [1-3] (Смотрите на место или стойте там)") + end + return "" -- suppress chat + end + + if cmd == "/setcelluk" then + if not ply:IsSuperAdmin() then return "" end + local cellNum = tonumber(args[2]) + if cellNum and cellNum >= 1 and cellNum <= 3 then + MRP_JailCells.UK[cellNum] = ply:GetPos() + SaveCells() + ply:ChatPrint("Позиция камеры УК " .. cellNum .. " установлена на ваше местоположение.") + else + ply:ChatPrint("Использование: /setcelluk [1-3] (Смотрите на место или стойте там)") + end + return "" -- suppress chat + end + + local function HasPoliceAccess(p) + return true + end + + if cmd == "/gag" then + if not HasPoliceAccess(ply) then return "" end + local target = GetTarget(ply) + if target and target:GetNWBool("rhc_cuffed", false) then + local isGagged = target:GetNWBool("MRP_Gagged", false) + target:SetNWBool("MRP_Gagged", not isGagged) + ply:ChatPrint(target:Nick() .. " теперь " .. (not isGagged and "с кляпом" or "без кляпа") .. ".") + else + ply:ChatPrint("Вы должны смотреть на закованного игрока.") + end + return "" + elseif cmd == "/blind" then + if not HasPoliceAccess(ply) then return "" end + local target = GetTarget(ply) + if target and target:GetNWBool("rhc_cuffed", false) then + local isBlind = target:GetNWBool("MRP_Blindfolded", false) + target:SetNWBool("MRP_Blindfolded", not isBlind) + if not isBlind then + target:PrintMessage(HUD_PRINTCENTER, "Вам завязали глаза.") + end + ply:ChatPrint(target:Nick() .. " теперь " .. (not isBlind and "с завязанными глазами" or "без повязки на глазах") .. ".") + else + ply:ChatPrint("Вы должны смотреть на закованного игрока.") + end + return "" + end + end) + + -- Mute gagged players in voice + hook.Add("PlayerCanHearPlayersVoice", "MRP_Handcuffs_GagVoice", function(listener, talker) + if IsValid(talker) and talker:GetNWBool("MRP_Gagged", false) then + return false, false + end + end) + + -- Mute gagged players in text chat (Helix override) + hook.Add("PlayerSay", "MRP_Handcuffs_GagChat", function(ply, text) + if ply:GetNWBool("MRP_Gagged", false) then + ply:ChatPrint("Вы не можете говорить с кляпом во рту.") + return "" + end + end) + + -- Sync drag state for visuals + timer.Create("MRP_Handcuffs_SyncDrag", 0.5, 0, function() + for _, ply in ipairs(player.GetAll()) do + if ply.Dragging and IsValid(ply.Dragging) then + ply:SetNWEntity("MRP_DraggingTarget", ply.Dragging) + else + ply:SetNWEntity("MRP_DraggingTarget", NULL) + end + end + end) + + -- Interaction Menu and Move/Freeze logic + net.Receive("MRP_Handcuffs_DoInteract", function(len, ply) + -- Removing privilege check to allow anyone with weapon to interact + -- if ply.IsRHCWhitelisted and not ply:IsRHCWhitelisted() and not ply:IsAdmin() then return end + + local target = net.ReadEntity() + local action = net.ReadString() + if not IsValid(target) or not target:IsPlayer() or not target:GetNWBool("rhc_cuffed", false) then return end + if target:GetPos():DistToSqr(ply:GetPos()) > 100000 then return end + + if action == "gag" then + local isGagged = target:GetNWBool("MRP_Gagged", false) + target:SetNWBool("MRP_Gagged", not isGagged) + ply:ChatPrint(target:Nick() .. " теперь " .. (not isGagged and "с кляпом" or "без кляпа") .. ".") + elseif action == "blind" then + local isBlind = target:GetNWBool("MRP_Blindfolded", false) + target:SetNWBool("MRP_Blindfolded", not isBlind) + if not isBlind then target:PrintMessage(HUD_PRINTCENTER, "Вам завязали глаза.") end + ply:ChatPrint(target:Nick() .. " теперь " .. (not isBlind and "с завязанными глазами" or "без повязки на глазах") .. ".") + end + end) + + hook.Add("KeyPress", "MRP_Handcuffs_MoveBind", function(ply, key) + if key == IN_USE then + -- Removing privilege check to allow anyone with weapon to interact + -- if ply.IsRHCWhitelisted and not ply:IsRHCWhitelisted() and not ply:IsAdmin() then return end + + local target = GetTarget(ply) + if IsValid(target) and target:GetNWBool("rhc_cuffed", false) then + if ply:KeyDown(IN_WALK) then -- ALT + E + if target:GetMoveType() == MOVETYPE_NONE then + target:SetMoveType(MOVETYPE_WALK) + ply:ChatPrint("Разморожен " .. target:Nick()) + else + target:SetMoveType(MOVETYPE_NONE) + local trace = util.QuickTrace(ply:EyePos(), ply:GetAimVector() * 80, {ply, target}) + target:SetPos(trace.HitPos) + ply:ChatPrint("Заморожен и перемещен " .. target:Nick()) + end + else -- Just E + net.Start("MRP_Handcuffs_OpenInteract") + net.WriteEntity(target) + net.WriteBool(target:GetNWBool("MRP_Gagged", false)) + net.WriteBool(target:GetNWBool("MRP_Blindfolded", false)) + net.Send(ply) + end + end + end + end) + + -- Custom Jailer network receiver + net.Receive("MRP_Handcuffs_PerformJail", function(len, ply) + local dragged = net.ReadEntity() + local time = net.ReadUInt(32) -- up to 3600 + local reason = net.ReadString() + local cellIndex = net.ReadUInt(8) -- 1, 2, or 3 + + -- validate + if not ply.LastJailerNPC or not IsValid(ply.LastJailerNPC) then return end + if ply:GetPos():DistToSqr(ply.LastJailerNPC:GetPos()) > 60000 then return end + + local faction = ply.LastJailerFaction or "RF" + if not IsValid(dragged) or not dragged:IsPlayer() then return end + + -- Validate distance + if dragged:GetPos():DistToSqr(ply:GetPos()) > 60000 then + ply:ChatPrint("Заключенный слишком далеко!") + return + end + + time = math.Clamp(time, 1, 3600) + + -- Force teleport to custom cell + if cellIndex >= 1 and cellIndex <= 3 and MRP_JailCells[faction] and MRP_JailCells[faction][cellIndex] then + ply:ChatPrint("Игрок " .. dragged:Nick() .. " (" .. dragged:SteamID() .. ") посажен в камеру " .. faction .. " на " .. time .. " сек. Причина: " .. reason) + + -- Log the jailing + hook.Run("RHC_jailed", dragged, ply, time, reason) + + -- Keep them cuffed in jail (weapon limits already applied by standalone logic) + timer.Simple(0.2, function() + if IsValid(dragged) then + dragged:SetPos(MRP_JailCells[faction][cellIndex]) + + if dragged.getChar and dragged:getChar() then + dragged:getChar():setData("mrp_jail_time", os.time() + time) + dragged:getChar():setData("mrp_jail_faction", faction) + dragged:getChar():setData("mrp_jail_cell", cellIndex) + dragged:getChar():setData("mrp_jail_reason", reason) + dragged:getChar():setData("mrp_jailer_name", ply:Nick()) + dragged:getChar():setData("mrp_jailer_steam", ply:SteamID()) + + -- Sync to client + dragged:SetNWInt("MRP_JailTime", os.time() + time) + dragged:SetNWString("MRP_JailReason", reason) + dragged:SetNWString("MRP_JailerName", ply:Nick()) + dragged:SetNWString("MRP_JailerSteam", ply:SteamID()) + + net.Start("MRP_UpdateJailInfo") + net.WriteEntity(dragged) + net.WriteInt(os.time() + time) + net.WriteString(reason) + net.WriteString(ply:Nick()) + net.WriteString(ply:SteamID()) + net.Broadcast() + end + + -- Simple arrest unjail timer + local timerName = "MRP_Jail_" .. dragged:SteamID64() + timer.Create(timerName, time, 1, function() + if IsValid(dragged) then + dragged:Spawn() + -- Trigger uncuff manually + dragged:SetNWBool("rhc_cuffed", false) + dragged:SetNWBool("MRP_Gagged", false) + dragged:SetNWBool("MRP_Blindfolded", false) + + dragged:StripWeapon("weapon_r_cuffed") + + -- Explicitly reset bone angles + local CuffedBones = { + ["ValveBiped.Bip01_R_UpperArm"] = true, + ["ValveBiped.Bip01_L_UpperArm"] = true, + ["ValveBiped.Bip01_R_Forearm"] = true, + ["ValveBiped.Bip01_L_Forearm"] = true, + ["ValveBiped.Bip01_R_Hand"] = true, + ["ValveBiped.Bip01_L_Hand"] = true, + } + for boneName, _ in pairs(CuffedBones) do + local boneId = dragged:LookupBone(boneName) + if boneId then + dragged:ManipulateBoneAngles(boneId, Angle(0,0,0)) + end + end + + if dragged.RHC_SavedWeapons then + for _, class in ipairs(dragged.RHC_SavedWeapons) do + dragged:Give(class) + end + dragged.RHC_SavedWeapons = nil + end + + if dragged.RHC_OldWalk then + dragged:SetWalkSpeed(dragged.RHC_OldWalk) + dragged:SetRunSpeed(dragged.RHC_OldRun) + dragged.RHC_OldWalk = nil + dragged.RHC_OldRun = nil + end + + if dragged.getChar and dragged:getChar() then + dragged:getChar():setData("mrp_jail_time", nil) + dragged:getChar():setData("mrp_jail_faction", nil) + dragged:getChar():setData("mrp_jail_cell", nil) + dragged:getChar():setData("mrp_jail_reason", nil) + dragged:getChar():setData("mrp_jailer_name", nil) + dragged:getChar():setData("mrp_jailer_steam", nil) + + dragged:SetNWInt("MRP_JailTime", 0) + dragged:SetNWString("MRP_JailReason", "") + dragged:SetNWString("MRP_JailerName", "") + dragged:SetNWString("MRP_JailerSteam", "") + + net.Start("MRP_UpdateJailInfo") + net.WriteEntity(dragged) + net.WriteInt(0) + net.WriteString("") + net.WriteString("") + net.WriteString("") + net.Broadcast() + end + + if dragged.RHC_SavedWeapons then + for _, class in ipairs(dragged.RHC_SavedWeapons) do dragged:Give(class) end + dragged.RHC_SavedWeapons = nil + end + if dragged.RHC_OldWalk then + dragged:SetWalkSpeed(dragged.RHC_OldWalk) + dragged:SetRunSpeed(dragged.RHC_OldRun) + end + dragged:ChatPrint("Ваш срок заключения подошел к концу.") + end + end) + end + end) + + -- Drop drag + ply.Dragging = nil + dragged.DraggedBy = nil + ply:SetNWEntity("MRP_DraggingTarget", NULL) + else + ply:ChatPrint("Внимание: Позиция выбранной камеры не настроена! Администратор должен использовать /setcell" .. string.lower(faction) .. " " .. cellIndex) + end + end) + + + net.Receive("MRP_Handcuffs_PerformUnjail", function(len, ply) + local target = net.ReadEntity() + if not IsValid(target) or not target:IsPlayer() then return end + + -- validate + if not ply.LastJailerNPC or not IsValid(ply.LastJailerNPC) then return end + if ply:GetPos():DistToSqr(ply.LastJailerNPC:GetPos()) > 60000 then return end + + local timerName = "MRP_Jail_" .. target:SteamID64() + if timer.Exists(timerName) then + timer.Remove(timerName) + + target:SetNWBool("rhc_cuffed", false) + target:SetNWBool("MRP_Gagged", false) + target:SetNWBool("MRP_Blindfolded", false) + + target:StripWeapon("weapon_r_cuffed") + + -- Explicitly reset bone angles + local CuffedBones = { + ["ValveBiped.Bip01_R_UpperArm"] = true, + ["ValveBiped.Bip01_L_UpperArm"] = true, + ["ValveBiped.Bip01_R_Forearm"] = true, + ["ValveBiped.Bip01_L_Forearm"] = true, + ["ValveBiped.Bip01_R_Hand"] = true, + ["ValveBiped.Bip01_L_Hand"] = true, + } + for boneName, _ in pairs(CuffedBones) do + local boneId = target:LookupBone(boneName) + if boneId then + target:ManipulateBoneAngles(boneId, Angle(0,0,0)) + end + end + + if target.RHC_SavedWeapons then + for _, class in ipairs(target.RHC_SavedWeapons) do + target:Give(class) + end + target.RHC_SavedWeapons = nil + end + + if target.RHC_OldWalk then + target:SetWalkSpeed(target.RHC_OldWalk) + target:SetRunSpeed(target.RHC_OldRun) + target.RHC_OldWalk = nil + target.RHC_OldRun = nil + end + + if target.getChar and target:getChar() then + target:getChar():setData("mrp_jail_time", nil) + target:getChar():setData("mrp_jail_faction", nil) + target:getChar():setData("mrp_jail_cell", nil) + end + + -- Spawn the player naturally, just like after natural timeout + target:Spawn() + + target:ChatPrint("Вас досрочно освободил " .. ply:Nick()) + ply:ChatPrint("Вы освободили " .. target:Nick()) + else + ply:ChatPrint("Похоже, этот игрок не в тюрьме.") + end + end) + + -- Reconnect persistence + hook.Add("PlayerLoadedCharacter", "MRP_Handcuffs_LoadJail", function(ply, character, oldCharacter) + timer.Simple(1, function() + if not IsValid(ply) then return end + if not character or not character.getData then return end -- FIX + + local jailTime = character:getData("mrp_jail_time", 0) + local faction = character:getData("mrp_jail_faction", "RF") + local cellIndex = character:getData("mrp_jail_cell", 1) + + if jailTime > os.time() then + local remaining = jailTime - os.time() + + -- Ensure they're cuffed + ply:SetNWBool("rhc_cuffed", true) + ply:Give("weapon_r_cuffed") + + if MRP_JailCells[faction] and MRP_JailCells[faction][cellIndex] then + ply:SetPos(MRP_JailCells[faction][cellIndex]) + end + + ply:ChatPrint("Вы вернулись, чтобы отсидеть оставшиеся " .. remaining .. " секунд вашего срока.") + + -- Sync jail info to client + ply:SetNWInt("MRP_JailTime", jailTime) + ply:SetNWString("MRP_JailReason", character:getData("mrp_jail_reason", "Не указана")) + ply:SetNWString("MRP_JailerName", character:getData("mrp_jailer_name", "Неизвестен")) + ply:SetNWString("MRP_JailerSteam", character:getData("mrp_jailer_steam", "Неизвестен")) + + net.Start("MRP_UpdateJailInfo") + net.WriteEntity(ply) + net.WriteInt(jailTime) + net.WriteString(character:getData("mrp_jail_reason", "Не указана")) + net.WriteString(character:getData("mrp_jailer_name", "Неизвестен")) + net.WriteString(character:getData("mrp_jailer_steam", "Неизвестен")) + net.Broadcast() + + -- Re-apply timer + local timerName = "MRP_Jail_" .. ply:SteamID64() + timer.Create(timerName, remaining, 1, function() + if IsValid(ply) then + ply:SetNWBool("rhc_cuffed", false) + ply:SetNWBool("MRP_Gagged", false) + ply:SetNWBool("MRP_Blindfolded", false) + ply:StripWeapon("weapon_r_cuffed") + if ply.getChar and ply:getChar() then + ply:getChar():setData("mrp_jail_time", nil) + ply:getChar():setData("mrp_jail_faction", nil) + ply:getChar():setData("mrp_jail_cell", nil) + end + + -- Look for nearest appropriate jailer for un-jailing position + local classStr = (faction == "RF") and "rhc_jailer_rf" or "rhc_jailer_uk" + local npc = ents.FindByClass(classStr)[1] + if IsValid(npc) then + ply:Spawn() + end + + ply:ChatPrint("Ваш срок заключения подошел к концу.") + end + end) + else + -- Timed out while offline or was free + if character:getData("mrp_jail_time") then + character:setData("mrp_jail_time", nil) + character:setData("mrp_jail_faction", nil) + character:setData("mrp_jail_cell", nil) + character:setData("mrp_jail_reason", nil) + character:setData("mrp_jailer_name", nil) + character:setData("mrp_jailer_steam", nil) + + ply:SetNWInt("MRP_JailTime", 0) + ply:SetNWString("MRP_JailReason", "") + ply:SetNWString("MRP_JailerName", "") + ply:SetNWString("MRP_JailerSteam", "") + + net.Start("MRP_UpdateJailInfo") + net.WriteEntity(ply) + net.WriteInt(0) + net.WriteString("") + net.WriteString("") + net.WriteString("") + net.Broadcast() + end + end + end) + end) + + -- Reset cuff state on spawn if not jailed + hook.Add("PlayerSpawn", "MRP_Handcuffs_ResetOnSpawn", function(ply) + if ply:GetNWBool("rhc_cuffed", false) then + local character = ply.getChar and ply:getChar() + if not character or not character.getData or character:getData("mrp_jail_time", 0) <= os.time() then + -- Not jailed, reset cuff state + ply:SetNWBool("rhc_cuffed", false) + ply:SetNWBool("MRP_Gagged", false) + ply:SetNWBool("MRP_Blindfolded", false) + ply:StripWeapon("weapon_r_cuffed") + + -- Reset bones + for boneName, _ in pairs({ + ["ValveBiped.Bip01_R_UpperArm"] = true, + ["ValveBiped.Bip01_L_UpperArm"] = true, + ["ValveBiped.Bip01_R_Forearm"] = true, + ["ValveBiped.Bip01_L_Forearm"] = true, + ["ValveBiped.Bip01_R_Hand"] = true, + ["ValveBiped.Bip01_L_Hand"] = true, + }) do + local boneId = ply:LookupBone(boneName) + if boneId then + ply:ManipulateBoneAngles(boneId, Angle(0,0,0)) + end + end + + -- Restore weapons and speed if saved + if ply.RHC_SavedWeapons then + for _, class in ipairs(ply.RHC_SavedWeapons) do + ply:Give(class) + end + ply.RHC_SavedWeapons = nil + end + if ply.RHC_OldWalk then + ply:SetWalkSpeed(ply.RHC_OldWalk) + ply:SetRunSpeed(ply.RHC_OldRun) + ply.RHC_OldWalk = nil + ply.RHC_OldRun = nil + end + end + end + end) + +end + +if CLIENT then + local clientJailTime = 0 + local clientJailReason = "" + local clientJailerName = "" + local clientJailerSteam = "" + + net.Receive("MRP_UpdateJailInfo", function() + local ent = net.ReadEntity() + if ent == LocalPlayer() then + clientJailTime = net.ReadInt() + clientJailReason = net.ReadString() + clientJailerName = net.ReadString() + clientJailerSteam = net.ReadString() + end + end) + -- Blindfold Overlay + hook.Add("HUDPaint", "MRP_Handcuffs_BlindfoldHUD", function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + if ply:GetNWBool("MRP_Blindfolded", false) then + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, ScrW(), ScrH()) + draw.SimpleText("У вас завязаны глаза", "Trebuchet24", ScrW()/2, ScrH()/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end) + + -- Jail Time HUD + hook.Add("HUDPaint", "MRP_Handcuffs_JailHUD", function() + if clientJailTime > os.time() then + local remaining = clientJailTime - os.time() + local minutes = math.floor(remaining / 60) + local seconds = remaining % 60 + local timeStr = string.format("%02d:%02d", minutes, seconds) + + draw.SimpleTextOutlined("Осталось сидеть: " .. timeStr, "Trebuchet24", ScrW()/2, ScrH() - 150, Color(255, 255, 100, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, 2, Color(0, 0, 0, 255)) + + draw.SimpleTextOutlined("Причина: " .. clientJailReason, "Trebuchet18", ScrW()/2, ScrH() - 120, Color(255, 255, 100, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, 2, Color(0, 0, 0, 255)) + + draw.SimpleTextOutlined("Посадил: " .. clientJailerName .. " (" .. clientJailerSteam .. ")", "Trebuchet18", ScrW()/2, ScrH() - 90, Color(255, 255, 100, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, 2, Color(0, 0, 0, 255)) + end + end) + + -- Beam dragging visual + local matBeam = Material("cable/rope") + hook.Add("PostDrawTranslucentRenderables", "MRP_Handcuffs_DrawDrag", function() + for _, ply in ipairs(player.GetAll()) do + local target = ply:GetNWEntity("MRP_DraggingTarget") + if IsValid(target) then + local startPos = ply:GetPos() + Vector(0,0,40) + local endPos = target:GetPos() + Vector(0,0,40) + + local wep = ply:GetActiveWeapon() + if IsValid(wep) then + local vm = ply:GetViewModel() + if ply == LocalPlayer() and not ply:ShouldDrawLocalPlayer() and IsValid(vm) then + local attach = vm:GetAttachment(1) + if attach then startPos = attach.Pos end + else + local attach = wep:GetAttachment(1) + if attach then + startPos = attach.Pos + else + local bone = ply:LookupBone("ValveBiped.Bip01_R_Hand") + if bone then + local bPos = ply:GetBonePosition(bone) + if bPos then startPos = bPos end + end + end + end + else + local bone = ply:LookupBone("ValveBiped.Bip01_R_Hand") + if bone then + local bPos = ply:GetBonePosition(bone) + if bPos then startPos = bPos end + end + end + + local tBone = target:LookupBone("ValveBiped.Bip01_R_Hand") + if tBone then + local bPos = target:GetBonePosition(tBone) + if bPos then endPos = bPos end + end + + render.SetMaterial(matBeam) + render.DrawBeam(startPos, endPos, 2, 0, startPos:Distance(endPos) / 10, color_white) + end + end + end) + + -- Models + hook.Add("PostPlayerDraw", "MRP_Handcuffs_DrawModels", function(ply) + if not IsValid(ply) or not ply:Alive() then return end + + local isGagged = ply:GetNWBool("MRP_Gagged", false) + local isBlind = ply:GetNWBool("MRP_Blindfolded", false) + + if not isGagged and not isBlind then return end + + local bone = ply:LookupBone("ValveBiped.Bip01_Head1") + if not bone then return end + + local pos, ang = ply:GetBonePosition(bone) + if not pos or not ang then return end + + if isGagged then + if not IsValid(gagModel) then + gagModel = ClientsideModel("models/kidnappers_kit/tape gag cross/tapegagcross.mdl") + if IsValid(gagModel) then gagModel:SetNoDraw(true) end + end + + if IsValid(gagModel) then + local gagAng = ang * 1 + gagAng:RotateAroundAxis(gagAng:Right(), -92.67) + gagAng:RotateAroundAxis(gagAng:Up(), -85.54) + gagAng:RotateAroundAxis(gagAng:Forward(), 0) + + local offset = ang:Forward() * -59.4 + ang:Right() * 6.53 + ang:Up() * 2.77 + + gagModel:SetRenderOrigin(pos + offset) + gagModel:SetRenderAngles(gagAng) + gagModel:SetModelScale(1) + gagModel:DrawModel() + end + end + + if isBlind then + if not IsValid(blindModel) then + blindModel = ClientsideModel("models/kidnappers_kit/tape blindfold/tapeblindfold.mdl") + if IsValid(blindModel) then blindModel:SetNoDraw(true) end + end + + if IsValid(blindModel) then + local blindAng = ang * 1 + blindAng:RotateAroundAxis(blindAng:Right(), -90) + blindAng:RotateAroundAxis(blindAng:Up(), -89.11) + blindAng:RotateAroundAxis(blindAng:Forward(), 0) + + local offset = ang:Forward() * -49 + ang:Right() * 3.17 + ang:Up() * 0.4 + + blindModel:SetRenderOrigin(pos + offset) + blindModel:SetRenderAngles(blindAng) + blindModel:SetModelScale(0.92) + blindModel:DrawModel() + end + end + end) + + timer.Simple(2, function() + net.Receive("RHC_Jailer_Menu", function() + local dragged = net.ReadEntity() + local faction = net.ReadString() -- "RF" or "UK" + if not IsValid(dragged) then + chat.AddText(Color(255,0,0), "Рядом нет закованных игроков!") + return + end + + local factionTitle = (faction == "RF" or faction == "UK") and (" (" .. faction .. ")") or "" + + local frame = vgui.Create("DFrame") + frame:SetSize(400, 320) + frame:Center() + frame:SetTitle("Посадить игрока: " .. dragged:Nick() .. factionTitle) + frame:MakePopup() + frame.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(40, 40, 40, 240)) + draw.RoundedBox(4, 0, 0, w, 24, Color(30, 30, 30, 255)) + end + + local timeLabel = vgui.Create("DLabel", frame) + timeLabel:SetPos(20, 40) + timeLabel:SetText("Время (1-3600 сек):") + timeLabel:SizeToContents() + timeLabel:SetTextColor(color_white) + + local timeText = vgui.Create("DTextEntry", frame) + timeText:SetPos(20, 60) + timeText:SetSize(360, 25) + timeText:SetText("300") + timeText:SetNumeric(true) + + local reasonLabel = vgui.Create("DLabel", frame) + reasonLabel:SetPos(20, 100) + reasonLabel:SetText("Причина:") + reasonLabel:SizeToContents() + reasonLabel:SetTextColor(color_white) + + local reasonText = vgui.Create("DTextEntry", frame) + reasonText:SetPos(20, 120) + reasonText:SetSize(360, 25) + reasonText:SetText("Причина не указана") + + local cellLabel = vgui.Create("DLabel", frame) + cellLabel:SetPos(20, 160) + cellLabel:SetText("Выберите камеру:") + cellLabel:SizeToContents() + cellLabel:SetTextColor(color_white) + + local cellCombo = vgui.Create("DComboBox", frame) + cellCombo:SetPos(20, 180) + cellCombo:SetSize(360, 25) + cellCombo:SetValue("Камера 1") + cellCombo:AddChoice("Камера 1", 1) + cellCombo:AddChoice("Камера 2", 2) + cellCombo:AddChoice("Камера 3", 3) + + local submitBtn = vgui.Create("DButton", frame) + submitBtn:SetPos(20, 230) + submitBtn:SetSize(360, 40) + submitBtn:SetText("Посадить") + submitBtn:SetTextColor(color_white) + submitBtn.Paint = function(self, w, h) + local col = self:IsHovered() and Color(60, 120, 200) or Color(50, 100, 180) + draw.RoundedBox(4, 0, 0, w, h, col) + end + submitBtn.DoClick = function() + local time = tonumber(timeText:GetValue()) or 300 + local reason = reasonText:GetValue() + local _, cellIndex = cellCombo:GetSelected() + cellIndex = cellIndex or 1 + + net.Start("MRP_Handcuffs_PerformJail") + net.WriteEntity(dragged) + net.WriteUInt(time, 32) + net.WriteString(reason) + net.WriteUInt(cellIndex, 8) + net.SendToServer() + + frame:Close() + end + + -- Unjail functionality + local unjailBtn = vgui.Create("DButton", frame) + unjailBtn:SetPos(20, 275) + unjailBtn:SetSize(360, 25) + unjailBtn:SetText("Показать список заключенных для освобождения") + unjailBtn:SetTextColor(Color(255,100,100)) + unjailBtn.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(50, 20, 20)) + end + + unjailBtn.DoClick = function() + frame:Close() + + local uFrame = vgui.Create("DFrame") + uFrame:SetSize(300, 400) + uFrame:Center() + uFrame:SetTitle("Досрочное освобождение") + uFrame:MakePopup() + uFrame.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(40, 40, 40, 240)) + end + + local list = vgui.Create("DListView", uFrame) + list:Dock(FILL) + list:AddColumn("Заключенные игроки") + + for _, p in ipairs(player.GetAll()) do + if p:GetNWBool("rhc_cuffed", false) and not IsValid(p.DraggedBy) then + local line = list:AddLine(p:Nick()) + line.PlayerObj = p + end + end + + local relBtn = vgui.Create("DButton", uFrame) + relBtn:Dock(BOTTOM) + relBtn:SetTall(30) + relBtn:SetText("Освободить выбранного") + relBtn.DoClick = function() + local selected = list:GetSelectedLine() + if selected then + local plyObj = list:GetLine(selected).PlayerObj + if IsValid(plyObj) then + net.Start("MRP_Handcuffs_PerformUnjail") + net.WriteEntity(plyObj) + net.SendToServer() + end + uFrame:Close() + end + end + end + end) + end) + + net.Receive("MRP_Handcuffs_OpenInteract", function() + local target = net.ReadEntity() + local isGagged = net.ReadBool() + local isBlind = net.ReadBool() + + if not IsValid(target) then return end + + local frame = vgui.Create("DFrame") + frame:SetSize(250, 140) + frame:Center() + frame:SetTitle("Взаимодействие: " .. target:Nick()) + frame:MakePopup() + frame.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(40, 40, 40, 240)) + draw.RoundedBox(4, 0, 0, w, 24, Color(30, 30, 30, 255)) + end + + local gagBtn = vgui.Create("DButton", frame) + gagBtn:Dock(TOP) + gagBtn:DockMargin(10, 10, 10, 0) + gagBtn:SetTall(35) + gagBtn:SetText(isGagged and "Снять кляп" or "Вставить кляп") + gagBtn:SetTextColor(color_white) + gagBtn.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, self:IsHovered() and Color(60, 60, 60) or Color(80, 80, 80)) + end + gagBtn.DoClick = function() + net.Start("MRP_Handcuffs_DoInteract") + net.WriteEntity(target) + net.WriteString("gag") + net.SendToServer() + frame:Close() + end + + local blindBtn = vgui.Create("DButton", frame) + blindBtn:Dock(TOP) + blindBtn:DockMargin(10, 10, 10, 0) + blindBtn:SetTall(35) + blindBtn:SetText(isBlind and "Снять повязку" or "Надеть повязку") + blindBtn:SetTextColor(color_white) + blindBtn.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, self:IsHovered() and Color(60, 60, 60) or Color(80, 80, 80)) + end + blindBtn.DoClick = function() + net.Start("MRP_Handcuffs_DoInteract") + net.WriteEntity(target) + net.WriteString("blind") + net.SendToServer() + frame:Close() + end + end) +end diff --git a/garrysmod/addons/realistichandcuffs/lua/autorun/server/mrp_jailer_spawns.lua b/garrysmod/addons/realistichandcuffs/lua/autorun/server/mrp_jailer_spawns.lua new file mode 100644 index 0000000..e016c5f --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/autorun/server/mrp_jailer_spawns.lua @@ -0,0 +1,50 @@ +-- lua/autorun/server/mrp_jailer_spawns.lua +if not SERVER then return end + +local function SpawnJailers() + -- Remove existing jailers to prevent duplicates on lua refresh + for _, ent in ipairs(ents.FindByClass("rhc_jailer_rf")) do ent:Remove() end + for _, ent in ipairs(ents.FindByClass("rhc_jailer_uk")) do ent:Remove() end + + -- Spawn RF Jailer + local rf_jailer = ents.Create("rhc_jailer_rf") + if IsValid(rf_jailer) then + rf_jailer:SetPos(Vector(10651.541992, 10210.736328, 193.868973)) + rf_jailer:SetAngles(Angle(0, 90, 0)) + rf_jailer:Spawn() + + -- Drop to floor to be safe + local tr = util.TraceLine({ + start = rf_jailer:GetPos() + Vector(0,0,10), + endpos = rf_jailer:GetPos() - Vector(0,0,100), + filter = rf_jailer + }) + if tr.Hit then rf_jailer:SetPos(tr.HitPos) end + end + + -- Spawn UK Jailer + local uk_jailer = ents.Create("rhc_jailer_uk") + if IsValid(uk_jailer) then + uk_jailer:SetPos(Vector(-12438.240234, -9908.939453, 181.868973)) + uk_jailer:SetAngles(Angle(0, 180, 0)) + uk_jailer:Spawn() + + local tr = util.TraceLine({ + start = uk_jailer:GetPos() + Vector(0,0,10), + endpos = uk_jailer:GetPos() - Vector(0,0,100), + filter = uk_jailer + }) + if tr.Hit then uk_jailer:SetPos(tr.HitPos) end + end +end + +hook.Add("InitPostEntity", "MRP_SpawnHandcuffsJailers", function() + timer.Simple(5, SpawnJailers) -- Wait a bit to ensure map is fully loaded +end) + +-- Make it easy to respawn via command if accidentally deleted +concommand.Add("mrp_respawn_jailers", function(ply) + if IsValid(ply) and not ply:IsSuperAdmin() then return end + SpawnJailers() + if IsValid(ply) then ply:ChatPrint("NPC-тюремщики возрождены.") end +end) diff --git a/garrysmod/addons/realistichandcuffs/lua/autorun/server/rhc_arrest_logic.lua b/garrysmod/addons/realistichandcuffs/lua/autorun/server/rhc_arrest_logic.lua new file mode 100644 index 0000000..c4c0171 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/autorun/server/rhc_arrest_logic.lua @@ -0,0 +1 @@ +-- Cleared to fix "attempt to index global ENT" error diff --git a/garrysmod/addons/realistichandcuffs/lua/autorun/sh_loadrhandcuffs.lua b/garrysmod/addons/realistichandcuffs/lua/autorun/sh_loadrhandcuffs.lua new file mode 100644 index 0000000..d7e708b --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/autorun/sh_loadrhandcuffs.lua @@ -0,0 +1,7 @@ + +if SERVER then + -- Suppress missing file errors +end +if CLIENT then + -- Suppress missing file errors +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/cl_init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/cl_init.lua new file mode 100644 index 0000000..f12e48c --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/cl_init.lua @@ -0,0 +1,34 @@ +include('shared.lua') + +function ENT:Initialize() + +end + +function ENT:Think () +end + +local AttMat = Material("cable/cable") +function ENT:Draw() + local Player = self:GetOwningPlayer() + local AEnt = self:GetAttatchedEntity() + local APos = self:GetAttatchPosition() + if IsValid(AEnt) then + APos = AEnt:GetPos() + end + if IsValid(Player) then + local Bone = Player:LookupBone("ValveBiped.Bip01_R_Hand") + if Bone then + local Pos, Ang = Player:GetBonePosition(Bone) + local FPos = Pos + Ang:Forward() * 3.2 + Ang:Right() * 2 + Ang:Up() * -5 + + self:SetPos(FPos) + self:SetAngles(Ang) + + render.SetMaterial(AttMat) + render.DrawBeam(self:GetPos(), APos, 1, 0, 0, Color(255, 255, 255, 255)) + end + end +end + +function ENT:OnRemove( ) +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/init.lua new file mode 100644 index 0000000..2432ac6 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/init.lua @@ -0,0 +1,23 @@ +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) +include('shared.lua') + +function ENT:Initialize() + self.Entity:SetModel("models/tobadforyou/handcuffs.mdl") + self.Entity:PhysicsInit(SOLID_NONE) + self.Entity:SetMoveType(MOVETYPE_VPHYSICS) + self.Entity:SetSolid(SOLID_NONE) + self.Entity:SetUseType(SIMPLE_USE) +end + +function ENT:Use( activator, caller ) +end + +function ENT:Think() + +end + +function ENT:Touch(TouchEnt) + if self.Entity.Touched and self.Entity.Touched > CurTime() then return ; end + self.Entity.Touched = CurTime() + 1; +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/shared.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/shared.lua new file mode 100644 index 0000000..a7ed68f --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_attatch/shared.lua @@ -0,0 +1,16 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.Category = "ToBadForYou" + +ENT.Spawnable = false +ENT.PrintName = "Attatch Entity" +ENT.Author = "ToBadForYou" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "OwningPlayer") + self:NetworkVar("Entity", 1, "AttatchedEntity") + self:NetworkVar("Vector", 0, "AttatchPosition") +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/cl_init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/cl_init.lua new file mode 100644 index 0000000..6d08ceb --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/cl_init.lua @@ -0,0 +1,42 @@ +include('shared.lua') + +function ENT:Initialize () + local Data = RHandcuffsConfig.NPCData[self:GetClass()] + self.aps = Data.TextRotationSpeed + self.lastRot = CurTime() + self.curRot = 0 + + self.Font = Data.TextFont + self.Text = Data.Text + self.TextColor = Data.TextColor + self.TextBGColor = Data.TextBackgroundColor +end + +function ENT:Draw() + self.curRot = self.curRot + (self.aps * (CurTime() - self.lastRot)) + if (self.curRot > 360) then self.curRot = self.curRot - 360 end + self.lastRot = CurTime() + + local Maxs = self:LocalToWorld(self:OBBMaxs()) + local EntPos = self:GetPos() + local TextPos = Vector(EntPos.x,EntPos.y,Maxs.z+8) + local Text = self.Text + local Font = self.Font + surface.SetFont(Font) + local W,H = surface.GetTextSize(Text) + surface.SetDrawColor(self.TextBGColor) + + cam.Start3D2D(TextPos, Angle(180, self.curRot, -90), .1) + surface.DrawRect(-W/2,-H/2,W,H) + draw.SimpleText(Text, Font, 0, 0, self.TextColor, TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) + cam.End3D2D() + cam.Start3D2D(TextPos, Angle(180, self.curRot + 180, -90), .1) + draw.SimpleText(Text, Font, 0, 0, self.TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + + self:DrawModel() +end + +function ENT:OnRemove( ) +end + diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/init.lua new file mode 100644 index 0000000..bfd1ffa --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/init.lua @@ -0,0 +1,31 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include('shared.lua') + +function ENT:Initialize() + local Data = RHandcuffsConfig.NPCData[self:GetClass()] + self:SetModel(Data.Model) + self:SetSolid(SOLID_BBOX); + self:PhysicsInit(SOLID_BBOX); + self:SetMoveType(MOVETYPE_NONE); + self:DrawShadow(true); + self:SetUseType(SIMPLE_USE); + + self:SetFlexWeight( 10, 0 ) + self:ResetSequence(3) +end + +function ENT:Use(activator, caller) + if self.Touched and self.Touched > CurTime() then return ; end + self.Touched = CurTime() + 2; + + if (RHC_GetConf("BAIL_RestrictBailing") and activator:CanRHCBail()) or !RHC_GetConf("BAIL_RestrictBailing") then + net.Start("RHC_Bailer_Menu") + net.Send(activator) + else + TBFY_Notify(activator, 1, 4, RHC_GetLang("NotAllowedToUseBailer")) + end +end + +function ENT:Think() +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/shared.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/shared.lua new file mode 100644 index 0000000..3b28b19 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_bailer/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.Category = "ToBadForYou" + +ENT.Spawnable = false +ENT.PrintName = "Bailer NPC" +ENT.Author = "ToBadForYou" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.TBFYEnt = true diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/cl_init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/cl_init.lua new file mode 100644 index 0000000..de35a74 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/cl_init.lua @@ -0,0 +1,40 @@ +include('shared.lua') + +function ENT:Initialize () + self.aps = 50 + self.lastRot = CurTime() + self.curRot = 0 + + self.Font = "DermaDefault" + self.Text = "Jailer" + self.TextColor = Color(255,255,255,255) + self.TextBGColor = Color(0,0,0,200) +end + +function ENT:Draw() + self.curRot = self.curRot + (self.aps * (CurTime() - self.lastRot)) + if (self.curRot > 360) then self.curRot = self.curRot - 360 end + self.lastRot = CurTime() + + local Maxs = self:LocalToWorld(self:OBBMaxs()) + local EntPos = self:GetPos() + local TextPos = Vector(EntPos.x,EntPos.y,Maxs.z+8) + local Text = self.Text + local Font = self.Font + surface.SetFont(Font) + local W,H = surface.GetTextSize(Text) + surface.SetDrawColor(self.TextBGColor) + + cam.Start3D2D(TextPos, Angle(180, self.curRot, -90), .1) + surface.DrawRect(-W/2,-H/2,W+8,H+8) + draw.SimpleText(Text, Font, 0, 0, self.TextColor, TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) + cam.End3D2D() + cam.Start3D2D(TextPos, Angle(180, self.curRot + 180, -90), .1) + draw.SimpleText(Text, Font, 0, 0, self.TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + + self:DrawModel() +end + +function ENT:OnRemove( ) +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/init.lua new file mode 100644 index 0000000..43854fd --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/init.lua @@ -0,0 +1,38 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include('shared.lua') + +function ENT:Initialize() + self:SetModel("models/Barney.mdl") -- Standalone default model + self:SetSolid(SOLID_BBOX) + self:PhysicsInit(SOLID_BBOX) + self:SetMoveType(MOVETYPE_NONE) + self:DrawShadow(true) + self:SetUseType(SIMPLE_USE) + self:SetFlexWeight(10, 0) + self:ResetSequence(3) +end + +local function IsPolice(ply) + return true +end + +function ENT:Use(activator, caller) + if self.Touched and self.Touched > CurTime() then return end + self.Touched = CurTime() + 2 + + -- Removing specific job check to allow anyone to use the jailer + activator.LastJailerNPC = self + net.Start("RHC_Jailer_Menu") + net.WriteEntity(activator.Dragging) + net.WriteString("RF") + net.Send(activator) + -- The else block and restriction message are removed. + -- if not isAllowed then + -- activator:ChatPrint("Только сотрудники исполнительной власти могут использовать этого NPC.") + -- return + -- end +end + +function ENT:Think() +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/shared.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/shared.lua new file mode 100644 index 0000000..2111cc6 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.Category = "ToBadForYou" + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.PrintName = "Jailer NPC" +ENT.Author = "Military RP" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.TBFYEnt = true diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/cl_init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/cl_init.lua new file mode 100644 index 0000000..cc74e3f --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/cl_init.lua @@ -0,0 +1,5 @@ +include('shared.lua') + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/init.lua new file mode 100644 index 0000000..e6bcf2f --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/init.lua @@ -0,0 +1,55 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("RHC_Jailer_Menu") + +local RUSSIAN_FACTION = FACTION_RUSSIAN +local SPECIAL_PODR = 6 + +function ENT:Initialize() + self:SetModel("models/Barney.mdl") + self:SetSolid(SOLID_BBOX) + self:PhysicsInit(SOLID_BBOX) + self:SetMoveType(MOVETYPE_NONE) + self:DrawShadow(true) + self:SetUseType(SIMPLE_USE) + self:SetFlexWeight(10, 0) + self:ResetSequence(3) +end + +function ENT:Use(activator) + if self.Touched and self.Touched > CurTime() then return end + self.Touched = CurTime() + 1.5 + + local char = activator:GetCharacter() + if not char then return end + + -- Removing specific job check to allow anyone to use the jailer + -- if not isAllowed then + -- activator:ChatPrint("Только сотрудники исполнительной власти могут использовать этого NPC.") + -- return + -- end + + local targetPly = activator.Dragging + if not IsValid(targetPly) then + local closestDist = 40000 -- 200 units + for _, p in ipairs(player.GetAll()) do + if p ~= activator and p:GetNWBool("rhc_cuffed", false) then + local dist = p:GetPos():DistToSqr(activator:GetPos()) + if dist < closestDist then + closestDist = dist + targetPly = p + end + end + end + end + + activator.LastJailerNPC = self + activator.LastJailerFaction = "RF" + + net.Start("RHC_Jailer_Menu") + net.WriteEntity(targetPly) + net.WriteString("RF") + net.Send(activator) +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/shared.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/shared.lua new file mode 100644 index 0000000..72544f0 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_rf/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.Category = "ToBadForYou" + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.PrintName = "Jailer NPC (RF)" +ENT.Author = "Military RP" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.TBFYEnt = true diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/cl_init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/cl_init.lua new file mode 100644 index 0000000..cc74e3f --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/cl_init.lua @@ -0,0 +1,5 @@ +include('shared.lua') + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/init.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/init.lua new file mode 100644 index 0000000..ca7b4f3 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/init.lua @@ -0,0 +1,55 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("RHC_Jailer_Menu") + +local UKRAINE_FACTION = FACTION_UKRAINE +local SPECIAL_PODR = 6 + +function ENT:Initialize() + self:SetModel("models/Barney.mdl") + self:SetSolid(SOLID_BBOX) + self:PhysicsInit(SOLID_BBOX) + self:SetMoveType(MOVETYPE_NONE) + self:DrawShadow(true) + self:SetUseType(SIMPLE_USE) + self:SetFlexWeight(10, 0) + self:ResetSequence(3) +end + +function ENT:Use(activator) + if self.Touched and self.Touched > CurTime() then return end + self.Touched = CurTime() + 1.5 + + local char = activator:GetCharacter() + if not char then return end + + -- Removing specific job check to allow anyone to use the jailer + -- if not isAllowed then + -- activator:ChatPrint("Только сотрудники исполнительной власти могут использовать этого NPC.") + -- return + -- end + + local targetPly = activator.Dragging + if not IsValid(targetPly) then + local closestDist = 40000 -- 200 units + for _, p in ipairs(player.GetAll()) do + if p ~= activator and p:GetNWBool("rhc_cuffed", false) then + local dist = p:GetPos():DistToSqr(activator:GetPos()) + if dist < closestDist then + closestDist = dist + targetPly = p + end + end + end + end + + activator.LastJailerNPC = self + activator.LastJailerFaction = "UK" + + net.Start("RHC_Jailer_Menu") + net.WriteEntity(targetPly) + net.WriteString("UK") + net.Send(activator) +end diff --git a/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/shared.lua b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/shared.lua new file mode 100644 index 0000000..8542ba3 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/entities/rhc_jailer_uk/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.Category = "ToBadForYou" + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.PrintName = "Jailer NPC (UK)" +ENT.Author = "Military RP" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.TBFYEnt = true diff --git a/garrysmod/addons/realistichandcuffs/lua/gmodadminsuite/modules/logging/modules/addons/tbfy_rhandcuffs.lua b/garrysmod/addons/realistichandcuffs/lua/gmodadminsuite/modules/logging/modules/addons/tbfy_rhandcuffs.lua new file mode 100644 index 0000000..339118d --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/gmodadminsuite/modules/logging/modules/addons/tbfy_rhandcuffs.lua @@ -0,0 +1,27 @@ +local MODULE = GAS.Logging:MODULE() + +MODULE.Category = "ToBadForYou" +MODULE.Name = "Realistic Handcuffs" +MODULE.Colour = Color(0,0,255) + +MODULE:Hook("RHC_restrain","rhc_toggle_restrain",function(vic, handcuffer) + local LogText = "cuffed" + if !vic.Restrained then + LogText = "uncuffed" + end + MODULE:Log(GAS.Logging:FormatPlayer(handcuffer) .. " " .. LogText .. " " .. GAS.Logging:FormatPlayer(vic)) +end) + +MODULE:Hook("RHC_jailed","rhc_jailed_player",function(vic, jailer, time, reason) + MODULE:Log(GAS.Logging:FormatPlayer(jailer) .. " jailed " .. GAS.Logging:FormatPlayer(vic) .. " for " .. time .. " seconds, reason: " .. reason) +end) + +MODULE:Hook("RHC_confis_weapon","rhc_confis_w",function(vic, confis, wep) + MODULE:Log(GAS.Logging:FormatPlayer(confis) .. " confiscated a " .. wep .. " from " .. GAS.Logging:FormatPlayer(vic) .. ".") +end) + +MODULE:Hook("RHC_confis_item","rhc_confis_i",function(vic, confis, item) + MODULE:Log(GAS.Logging:FormatPlayer(confis) .. " confiscated a " .. item .. " from " .. GAS.Logging:FormatPlayer(vic) .. ".") +end) + +GAS.Logging:AddModule(MODULE) diff --git a/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/__category.lua b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/__category.lua new file mode 100644 index 0000000..f306886 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/__category.lua @@ -0,0 +1,23 @@ +--[[ + mLogs 2 (M4D Logs 2) + Created by M4D | http://m4d.one/ | http://steamcommunity.com/id/m4dhead | + Copyright © 2018 M4D.one All Rights Reserved + All 3rd party content is public domain or used with permission + M4D.one is the copyright holder of all code below. Do not distribute in any circumstances. +--]] + +mLogs.addCategory( + "Realistic Handcuff System", -- Name + "rhandcuff", + Color(0,0,255), -- Color + function() -- Check + return true + end, + true +) + +mLogs.addCategoryDefinitions("rhandcuff", { + cuffing = function(data) return mLogs.doLogReplace({"^player1", "^action", "^player2"},data) end, + jailing = function(data) return mLogs.doLogReplace({"^player1", "jailed", "^player2", "for", "^time", "seconds, reason:", "^reason"},data) end, + confiscation = function(data) return mLogs.doLogReplace({"^player1", "confiscated a", "^item", "from", "^player2"},data) end, +}) diff --git a/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/confiscation.lua b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/confiscation.lua new file mode 100644 index 0000000..26f3a2e --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/confiscation.lua @@ -0,0 +1,19 @@ +--[[ + mLogs 2 (M4D Logs 2) + Created by M4D | http://m4d.one/ | http://steamcommunity.com/id/m4dhead | + Copyright © 2018 M4D.one All Rights Reserved + All 3rd party content is public domain or used with permission + M4D.one is the copyright holder of all code below. Do not distribute in any circumstances. +--]] + +local category = "rhandcuff" + +mLogs.addLogger("Confiscation","confiscation",category) +mLogs.addHook("RHC_confis_weapon", category, function(vic,confis, wep) + if(not IsValid(vic) or not IsValid(confis))then return end + mLogs.log("confiscation", category, {player1=mLogs.logger.getPlayerData(confis),item=wep,player2=mLogs.logger.getPlayerData(vic),a=true}) +end) +mLogs.addHook("RHC_confis_item", category, function(vic,confis, item) + if(not IsValid(vic) or not IsValid(confis))then return end + mLogs.log("confiscation", category, {player1=mLogs.logger.getPlayerData(confis),item=item,player2=mLogs.logger.getPlayerData(vic),a=true}) +end) diff --git a/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/cuffing.lua b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/cuffing.lua new file mode 100644 index 0000000..906e7fb --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/cuffing.lua @@ -0,0 +1,20 @@ +--[[ + mLogs 2 (M4D Logs 2) + Created by M4D | http://m4d.one/ | http://steamcommunity.com/id/m4dhead | + Copyright © 2018 M4D.one All Rights Reserved + All 3rd party content is public domain or used with permission + M4D.one is the copyright holder of all code below. Do not distribute in any circumstances. +--]] + +local category = "rhandcuff" + +mLogs.addLogger("Cuffing","cuffing",category) +mLogs.addHook("RHC_restrain", category, function(vic,handcuffer) + if(not IsValid(vic) or not IsValid(handcuffer))then return end + local LogText = "cuffed" + if !vic.Restrained then + LogText = "uncuffed" + end + + mLogs.log("cuffing", category, {player1=mLogs.logger.getPlayerData(handcuffer),action=LogText,player2=mLogs.logger.getPlayerData(vic),a=true}) +end) diff --git a/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/jailing.lua b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/jailing.lua new file mode 100644 index 0000000..df0a340 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/mlogs/logger/loggers/tbfy_rhandcuffs/jailing.lua @@ -0,0 +1,15 @@ +--[[ + mLogs 2 (M4D Logs 2) + Created by M4D | http://m4d.one/ | http://steamcommunity.com/id/m4dhead | + Copyright © 2018 M4D.one All Rights Reserved + All 3rd party content is public domain or used with permission + M4D.one is the copyright holder of all code below. Do not distribute in any circumstances. +--]] + +local category = "rhandcuff" + +mLogs.addLogger("Jailing","jailing",category) +mLogs.addHook("RHC_jailed", category, function(vic, jailer, time, reason) + if(not IsValid(vic) or not IsValid(jailer))then return end + mLogs.log("jailing", category, {player1=mLogs.logger.getPlayerData(jailer),player2=mLogs.logger.getPlayerData(vic),time=time, reason=reason,a=true}) +end) diff --git a/garrysmod/addons/realistichandcuffs/lua/mrp_handcuffs/sv_logic.lua b/garrysmod/addons/realistichandcuffs/lua/mrp_handcuffs/sv_logic.lua new file mode 100644 index 0000000..9dd6546 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/mrp_handcuffs/sv_logic.lua @@ -0,0 +1 @@ +-- Cleared to fix syntax error "expected near end" diff --git a/garrysmod/addons/realistichandcuffs/lua/weapons/tbfy_surrendered/shared.lua b/garrysmod/addons/realistichandcuffs/lua/weapons/tbfy_surrendered/shared.lua new file mode 100644 index 0000000..05c82ff --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/weapons/tbfy_surrendered/shared.lua @@ -0,0 +1,51 @@ +if SERVER then + AddCSLuaFile("shared.lua") +end + +if CLIENT then + SWEP.PrintName = "Surrender" + SWEP.Slot = 2 + SWEP.SlotPos = 1 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +SWEP.Author = "ToBadForYou" +SWEP.Instructions = "" +SWEP.Contact = "" +SWEP.Purpose = "" + +SWEP.HoldType = "passive"; +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "passive" +SWEP.Category = "ToBadForYou" +SWEP.UID = 76561198187122039 + +SWEP.Spawnable = false +SWEP.AdminSpawnable = false +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("passive") end +function SWEP:CanPrimaryAttack() return false; end +function SWEP:SecondaryAttack() return false; end + +function SWEP:PreDrawViewModel(vm) + return true +end + +function SWEP:DrawWorldModel() +end + +if CLIENT then +function SWEP:DrawHUD() + draw.SimpleTextOutlined(RHC_GetLang("SurrenderedText"),"Trebuchet24",ScrW()/2,ScrH()/12,Color(255,255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM,2,Color(0,0,0,255)) +end +end diff --git a/garrysmod/addons/realistichandcuffs/lua/weapons/weapon_r_cuffed/shared.lua b/garrysmod/addons/realistichandcuffs/lua/weapons/weapon_r_cuffed/shared.lua new file mode 100644 index 0000000..ad9cdfc --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/weapons/weapon_r_cuffed/shared.lua @@ -0,0 +1,138 @@ +if SERVER then + AddCSLuaFile("shared.lua") +end + +if CLIENT then + SWEP.PrintName = "Cuffed" + SWEP.Slot = 2 + SWEP.SlotPos = 1 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +SWEP.Author = "Military RP" +SWEP.Instructions = "" +SWEP.Contact = "" +SWEP.Purpose = "" + +SWEP.HoldType = "passive" +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "passive" +SWEP.Category = "Military RP" + +SWEP.Spawnable = false +SWEP.AdminSpawnable = false + +SWEP.ViewModel = "models/tobadforyou/c_hand_cuffs.mdl" +SWEP.WorldModel = "models/tobadforyou/handcuffs.mdl" + +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 = "" + +local CuffedBones = { + ["ValveBiped.Bip01_R_UpperArm"] = Angle(-21.4, 21.4, 3.6), + ["ValveBiped.Bip01_L_UpperArm"] = Angle(30, 8.2, 27.8), + ["ValveBiped.Bip01_R_Forearm"] = Angle(0, 25, -10.7), + ["ValveBiped.Bip01_L_Forearm"] = Angle(-11.5, 31.1, -18), + ["ValveBiped.Bip01_R_Hand"] = Angle(14.3, -14.3, 60), + ["ValveBiped.Bip01_L_Hand"] = Angle(-11, 14.7, 70.4), +} + +function SWEP:Initialize() + self:SetHoldType("normal") +end +function SWEP:CanPrimaryAttack() return false end +function SWEP:SecondaryAttack() return false end +function SWEP:Reload() return false end + +function SWEP:Deploy() + self:SetHoldType("normal") + if SERVER then + local ply = self:GetOwner() + if IsValid(ply) then + for boneName, ang in pairs(CuffedBones) do + local boneId = ply:LookupBone(boneName) + if boneId then + ply:ManipulateBoneAngles(boneId, ang) + end + end + end + end + return true +end + +function SWEP:Holster() + if SERVER then + local ply = self:GetOwner() + if IsValid(ply) then + for boneName, _ in pairs(CuffedBones) do + local boneId = ply:LookupBone(boneName) + if boneId then + ply:ManipulateBoneAngles(boneId, Angle(0,0,0)) + end + end + end + end + return true +end + +function SWEP:OnRemove() + if SERVER then + local ply = self:GetOwner() + if IsValid(ply) then + for boneName, _ in pairs(CuffedBones) do + local boneId = ply:LookupBone(boneName) + if boneId then + ply:ManipulateBoneAngles(boneId, Angle(0,0,0)) + end + end + end + end +end + +if CLIENT then + function SWEP:PreDrawViewModel(vm) return true end + + function SWEP:DrawWorldModel() + local ply = self:GetOwner() + if not IsValid(ply) then return end + local boneindex = ply:LookupBone("ValveBiped.Bip01_L_Hand") + if boneindex then + local pos, ang = ply:GetBonePosition(boneindex) + if pos and ang then + -- Hardcoded tuned values + local cx, cy, cz = 0.9, 0.8, 4.8 + local cp, cyaw, cr = 7.1, -14.3, 96.2 + + local offset = ang:Forward() * cx + ang:Right() * cy + ang:Up() * cz + ang:RotateAroundAxis(ang:Right(), cp) + ang:RotateAroundAxis(ang:Up(), cyaw) + ang:RotateAroundAxis(ang:Forward(), cr) + self:SetRenderOrigin(pos + offset) + self:SetRenderAngles(ang) + self:DrawModel() + end + end + end +end + +if SERVER then + hook.Add("PlayerSwitchWeapon", "MRP_Handcuffs_PreventSwitch", function(ply, oldWep, newWep) + if ply:GetNWBool("rhc_cuffed", false) and newWep:GetClass() ~= "weapon_r_cuffed" then + return true -- Block switching weapons when cuffed + end + end) +end + +if CLIENT then + function SWEP:DrawHUD() + draw.SimpleTextOutlined("Вы закованы в наручники","Trebuchet24",ScrW()/2,ScrH() - 100,Color(255,100,100,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM,2,Color(0,0,0,255)) + end +end diff --git a/garrysmod/addons/realistichandcuffs/lua/weapons/weapon_r_handcuffs/shared.lua b/garrysmod/addons/realistichandcuffs/lua/weapons/weapon_r_handcuffs/shared.lua new file mode 100644 index 0000000..feab139 --- /dev/null +++ b/garrysmod/addons/realistichandcuffs/lua/weapons/weapon_r_handcuffs/shared.lua @@ -0,0 +1,379 @@ +if SERVER then + AddCSLuaFile("shared.lua") +end + +if CLIENT then + SWEP.PrintName = "Handcuffs" + SWEP.Slot = 2 + SWEP.SlotPos = 1 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +SWEP.Author = "ToBadForYou" +SWEP.Instructions = "Left Click: Restrain/Release.\nRight Click: Force Players out of vehicle.\nReload: Drag Cuffed Player." +SWEP.Contact = "" +SWEP.Purpose = "" + +SWEP.HoldType = "melee" +SWEP.UseHands = true + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "melee" +SWEP.Category = "Military RP" +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +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.PlayBackRate = 2 + +function SWEP:Initialize() + self:SetHoldType("melee") + self.ViewModel = "models/tobadforyou/c_hand_cuffs.mdl" + self.WorldModel = "models/tobadforyou/handcuffs.mdl" +end + +function SWEP:CanPrimaryAttack() return true end +function SWEP:CanSecondaryAttack() return true end + +function SWEP:PlayCuffSound(Time) + timer.Simple(Time, function() if IsValid(self) then self:EmitSound("npc/metro-police/gear2.wav") end end) + timer.Simple(Time+1, function() if IsValid(self) then self:EmitSound("npc/metro-police/gear2.wav") end end) +end + +local function IsPolice(ply) + return true +end + +local function RestrainPlayer(target, cop) + if not IsValid(target) or not target:IsPlayer() then return end + + target:SetNWBool("rhc_cuffed", true) + if SERVER then + target:EmitSound("npc/metro-police/gear2.wav") + + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if serverlogsPlugin then + serverlogsPlugin:AddLog("HANDCUFF_RESTRAIN", cop:Nick() .. " restrained " .. target:Nick(), cop, {target = target}) + end + + -- Save weapons + target.RHC_SavedWeapons = {} + for _, w in ipairs(target:GetWeapons()) do + table.insert(target.RHC_SavedWeapons, w:GetClass()) + end + + target:StripWeapons() + target:Give("weapon_r_cuffed") + target:SelectWeapon("weapon_r_cuffed") + + target.RHC_OldWalk = target:GetWalkSpeed() + target.RHC_OldRun = target:GetRunSpeed() + target:SetWalkSpeed(100) + target:SetRunSpeed(100) + end +end + +local CuffedBones = { + ["ValveBiped.Bip01_R_UpperArm"] = true, + ["ValveBiped.Bip01_L_UpperArm"] = true, + ["ValveBiped.Bip01_R_Forearm"] = true, + ["ValveBiped.Bip01_L_Forearm"] = true, + ["ValveBiped.Bip01_R_Hand"] = true, + ["ValveBiped.Bip01_L_Hand"] = true, +} + +local function UnrestrainPlayer(target, cop) + if not IsValid(target) or not target:IsPlayer() then return end + + target:SetNWBool("rhc_cuffed", false) + target:SetNWBool("MRP_Gagged", false) + target:SetNWBool("MRP_Blindfolded", false) + if SERVER then + target:EmitSound("npc/metro-police/gear2.wav") + + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if serverlogsPlugin then + serverlogsPlugin:AddLog("HANDCUFF_UNRESTRAIN", cop:Nick() .. " unrestrained " .. target:Nick(), cop, {target = target}) + end + + target:StripWeapon("weapon_r_cuffed") + + for boneName, _ in pairs(CuffedBones) do + local boneId = target:LookupBone(boneName) + if boneId then + target:ManipulateBoneAngles(boneId, Angle(0,0,0)) + end + end + + if target.RHC_SavedWeapons then + for _, class in ipairs(target.RHC_SavedWeapons) do + target:Give(class) + end + target.RHC_SavedWeapons = nil + end + + if target.RHC_OldWalk then + target:SetWalkSpeed(target.RHC_OldWalk) + target:SetRunSpeed(target.RHC_OldRun) + end + + if IsValid(target.DraggedBy) then + target.DraggedBy.Dragging = nil + target.DraggedBy = nil + end + end +end + +function SWEP:Think() + local target = self.AttemptToCuff + if IsValid(target) then + local vm = self.Owner:GetViewModel() + local ResetSeq = vm:LookupSequence("Reset") + + local TraceEnt = self.Owner:GetEyeTrace().Entity + local distance = target:GetPos():Distance(self.Owner:GetPos()) + + if not IsValid(TraceEnt) or TraceEnt ~= target or distance > 150 then + self.AttemptToCuff = nil + target.RHC_BeingCuffed = false + vm:SendViewModelMatchingSequence(ResetSeq) + target:Freeze(false) + elseif CurTime() >= self.AttemptCuffFinish then + if SERVER then + RestrainPlayer(target, self.Owner) + end + target.RHC_BeingCuffed = false + self.AttemptToCuff = nil + vm:SendViewModelMatchingSequence(ResetSeq) + target:Freeze(false) + end + end +end + +function SWEP:PrimaryAttack() + self.Weapon:SetNextPrimaryFire(CurTime() + 1.5) + + if not IsPolice(self.Owner) then + if SERVER then self.Owner:ChatPrint("Вам не разрешено использовать наручники.") end + return + end + + self.Weapon:EmitSound("npc/vort/claw_swing" .. math.random(1, 2) .. ".wav") + self.Owner:SetAnimation(PLAYER_ATTACK1) + + local target = self.Owner:GetEyeTrace().Entity + if not IsValid(target) or not target:IsPlayer() then return end + if self.Owner:EyePos():Distance(target:GetPos()) > 150 then return end + + -- Uncuff + if target:GetNWBool("rhc_cuffed", false) then + if SERVER then UnrestrainPlayer(target, self.Owner) end + return + end + + -- Begin Cuffing + self.AttemptToCuff = target + self.AttemptCuffStart = CurTime() + self.AttemptCuffFinish = CurTime() + 2 + target.RHC_BeingCuffed = true + + local vm = self.Owner:GetViewModel() + local DeploySeq = vm:LookupSequence("Deploy") + vm:SendViewModelMatchingSequence(DeploySeq) + vm:SetPlaybackRate(self.PlayBackRate) + + self:PlayCuffSound(0.3) + if SERVER then target:Freeze(true) end +end + +-- Dragging logic mapped to Reload +function SWEP:Reload() + if not IsFirstTimePredicted() then return end + if self.NextRPress and self.NextRPress > CurTime() then return end + self.NextRPress = CurTime() + 1 + if CLIENT then return end + + local target = self.Owner:GetEyeTrace().Entity + + -- Stop dragging + if IsValid(self.Owner.Dragging) then + self.Owner.Dragging.DraggedBy = nil + self.Owner.Dragging = nil + self.Owner:ChatPrint("Вы перестали тащить игрока.") + return + end + + -- Start dragging + if IsValid(target) and target:IsPlayer() and target:GetNWBool("rhc_cuffed", false) then + if self.Owner:EyePos():Distance(target:GetPos()) <= 150 then + self.Owner.Dragging = target + target.DraggedBy = self.Owner + self.Owner:ChatPrint("Вы теперь тащите " .. target:Nick()) + end + end +end + +function SWEP:SecondaryAttack() + -- Force out of vehicle + self.Weapon:SetNextSecondaryFire(CurTime() + 1) + if CLIENT then return end + if not IsPolice(self.Owner) then return end + + -- Helper function to find an empty seat + local function GetEmptySeat(veh) + if veh.IsDriveable and veh:IsDriveable() then + -- LVS Specific + if veh.GetDriver and not IsValid(veh:GetDriver()) then + local dSeat = veh:GetDriverSeat() + if IsValid(dSeat) then return dSeat end + end + if veh.GetPassengerSeats then + for _, seat in pairs(veh:GetPassengerSeats()) do + if IsValid(seat) and not IsValid(seat:GetDriver()) then + return seat + end + end + end + return nil + end + + -- Standard/Simfphys + if veh.GetDriver and not IsValid(veh:GetDriver()) then return veh end + if veh.pSeats then + for _, seat in pairs(veh.pSeats) do + if IsValid(seat) and not IsValid(seat:GetDriver()) then + return seat + end + end + end + return nil + end + + local target = self.Owner:GetEyeTrace().Entity + if not IsValid(target) then return end + if self.Owner:GetPos():Distance(target:GetPos()) > 300 then return end + + local isVeh = target:IsVehicle() or (target.GetClass and string.find(string.lower(target:GetClass()), "lvs")) + + if IsValid(self.Owner.Dragging) then + local dragged = self.Owner.Dragging + if isVeh then + local seat = GetEmptySeat(target) + if IsValid(seat) then + dragged:EnterVehicle(seat) + self.Owner.Dragging = nil + dragged.DraggedBy = nil + self.Owner:ChatPrint("Игрок посажен в транспорт.") + else + self.Owner:ChatPrint("Нет свободных мест.") + end + end + else + if isVeh then + local found = false + + -- LVS Specific + if target.IsDriveable and target:IsDriveable() then + local drv = target:GetDriver() + if IsValid(drv) and drv:GetNWBool("rhc_cuffed", false) then + drv:ExitVehicle() + found = true + end + + if target.GetPassengerSeats then + for _, seat in pairs(target:GetPassengerSeats()) do + local sDrv = IsValid(seat) and seat:GetDriver() + if IsValid(sDrv) and sDrv:GetNWBool("rhc_cuffed", false) then + sDrv:ExitVehicle() + found = true + end + end + end + else + -- Standard/Simfphys + local drv = target:GetDriver() + if IsValid(drv) and drv:GetNWBool("rhc_cuffed", false) then + drv:ExitVehicle() + found = true + end + if target.pSeats then + for _, seat in pairs(target.pSeats) do + local sDrv = IsValid(seat) and seat:GetDriver() + if IsValid(sDrv) and sDrv:GetNWBool("rhc_cuffed", false) then + sDrv:ExitVehicle() + found = true + end + end + end + end + + if found then + self.Owner:ChatPrint("Игрок(и) вытащен(ы) из транспорта.") + end + end + end +end + +if SERVER then + hook.Add("PlayerTick", "MRP_Handcuffs_DragLogic", function(ply) + local dragged = ply.Dragging + if IsValid(dragged) and dragged:IsPlayer() and dragged:GetNWBool("rhc_cuffed", false) then + local dist = ply:GetPos():Distance(dragged:GetPos()) + if dist > 300 then + ply.Dragging = nil + dragged.DraggedBy = nil + ply:ChatPrint("Таскание прекращено: слишком далеко.") + elseif dist > 100 then + local dir = (ply:GetPos() - dragged:GetPos()):GetNormalized() + dragged:SetVelocity(dir * (dist * 2)) + end + elseif dragged then + ply.Dragging = nil + if IsValid(dragged) then dragged.DraggedBy = nil end + end + end) + + hook.Add("PlayerDisconnected", "MRP_Handcuffs_DragCleanup", function(ply) + if IsValid(ply.DraggedBy) then ply.DraggedBy.Dragging = nil end + if IsValid(ply.Dragging) then ply.Dragging.DraggedBy = nil end + end) +end + +if CLIENT then + function SWEP:DrawWorldModel() + if not IsValid(self.Owner) then return end + local boneindex = self.Owner:LookupBone("ValveBiped.Bip01_R_Hand") + if boneindex then + local HPos, HAng = self.Owner:GetBonePosition(boneindex) + local offset = HAng:Right() * 0.5 + HAng:Forward() * 3.3 + HAng:RotateAroundAxis(HAng:Right(), 0) + HAng:RotateAroundAxis(HAng:Forward(), -90) + self:SetRenderOrigin(HPos + offset) + self:SetRenderAngles(HAng) + self:DrawModel() + end + end + + function SWEP:DrawHUD() + draw.SimpleText("ЛКМ: Надеть/Снять наручники", "default", ScrW()/2, 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 2, color_black) + draw.SimpleText("ПКМ: Посадить/Вытащить из транспорта", "default", ScrW()/2, 15, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 2, color_black) + draw.SimpleText("Перезарядка (R): Тащить закованного", "default", ScrW()/2, 25, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 2, color_black) + + local target = self.AttemptToCuff + if not IsValid(target) then return end + + local percent = math.Clamp((CurTime() - self.AttemptCuffStart) / (self.AttemptCuffFinish - self.AttemptCuffStart), 0, 1) + draw.SimpleText("Заковываем " .. target:Nick() .. " (" .. math.Round(percent * 100) .. "%)", "DermaLarge", ScrW()/2, ScrH()/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end diff --git a/garrysmod/addons/sania/lua/autorun/mur_drone_fx.lua b/garrysmod/addons/sania/lua/autorun/mur_drone_fx.lua new file mode 100644 index 0000000..b456a6d --- /dev/null +++ b/garrysmod/addons/sania/lua/autorun/mur_drone_fx.lua @@ -0,0 +1,49 @@ +if CLIENT then + +function DrawHeavySignalNoise(strength) + if not strength then return end + if strength > 0.9 then return end + + local sw, sh = ScrW(), ScrH() + + local noiseCount = (1 - strength) * 400 + for i = 1, noiseCount do + surface.SetDrawColor(255, 255, 255, math.random(10, 60)) + surface.DrawRect(math.random(0, sw), math.random(0, sh), 1, 1) + end + + local lineCount = (1 - strength) * 60 + for i = 1, lineCount do + local y = math.random(0, sh) + local w = math.random(sw * 0.2, sw) + local x = math.random(0, sw - w) + surface.SetDrawColor(255, 255, 255, math.random(20, 80)) + surface.DrawRect(x, y, w, math.random(1, 3)) + end + + if strength < 0.4 then + local shift = math.random(-8, 8) * (1 - strength) + surface.SetDrawColor(255, 255, 255, 40) + surface.DrawRect(shift, 0, sw, sh) + end + + if strength < 0.3 then + for i = 1, 10 do + local x = math.random(0, sw) + surface.SetDrawColor(255, 255, 255, math.random(30, 80)) + surface.DrawRect(x, 0, math.random(2, 6), sh) + end + end + + if strength < 0.2 then + local shake = (0.2 - strength) * 6 + surface.SetDrawColor(255, 255, 255, 25) + surface.DrawRect( + math.random(-shake, shake), + math.random(-shake, shake), + sw, sh + ) + end +end + +end diff --git a/garrysmod/addons/sania/lua/entities/sw_sania/cl_init.lua b/garrysmod/addons/sania/lua/entities/sw_sania/cl_init.lua new file mode 100644 index 0000000..ed17ae8 --- /dev/null +++ b/garrysmod/addons/sania/lua/entities/sw_sania/cl_init.lua @@ -0,0 +1,49 @@ + +include("shared.lua") +if CLIENT then +local jammat = Material( "models/shtormer/sania/jam.png" ) +local flag1 = false + + function ENT:Think() + if LocalPlayer():lvsGetVehicle():IsValid() then + if LocalPlayer():lvsGetVehicle():GetNWEntity("UAVControl"):IsValid() then + self:SetNWEntity("Jamming", LocalPlayer():lvsGetVehicle()) + jammer = ents.FindByClass( "sw_sania" ) + if jammer != nil then + for i =1, #jammer do + if jammer[i]:IsValid() then + if LocalPlayer():lvsGetVehicle():GetPos():Distance(jammer[i]:GetPos()) < jammer[i].JamDistance then + flag1 = true + end + end + end + if LocalPlayer():lvsGetVehicle():GetNWBool("Jammed") and !flag1 then + LocalPlayer():lvsGetVehicle():SetNWBool("Jammed", false) + flag1 = false + elseif flag1 then + LocalPlayer():lvsGetVehicle():SetNWBool("Jammed", true) + end + else + flag1 = false + end + end + if LocalPlayer():lvsGetVehicle():GetNWBool("Jammed") and LocalPlayer():lvsGetVehicle():GetPos():Distance(self:GetPos()) < self.JamDistance and self:IsValid() and LocalPlayer():lvsGetVehicle():IsValid() and self != NULL then + print(LocalPlayer():lvsGetVehicle():GetNWEntity("UAVControl"):IsValid()) + hook.Add( "HUDPaint", "uavjamsania", function() + if self != NULL and LocalPlayer():lvsGetVehicle() != NULL then + surface.SetDrawColor( 255, 255, 255, 255/(LocalPlayer():lvsGetVehicle():GetPos():Distance(self:GetPos())/self.FullJamDistance) ) -- Set the drawing color + surface.SetMaterial( jammat ) -- Use our cached material + surface.DrawTexturedRect( 0, 0, ScrW(), ScrH() ) -- Actually draw the rectangle + end + end ) + elseif !LocalPlayer():lvsGetVehicle():GetNWBool("Jammed") or !LocalPlayer():lvsGetVehicle():IsValid() or !self:IsValid() or self == NULL or LocalPlayer():lvsGetVehicle() == NULL or LocalPlayer():lvsGetVehicle():GetNWEntity("UAVControl"):IsValid() == NULL then + hook.Remove( "HUDPaint", "uavjamsania" ) + end + end + end + + function ENT:Draw() + self:DrawModel() + end + +end diff --git a/garrysmod/addons/sania/lua/entities/sw_sania/init.lua b/garrysmod/addons/sania/lua/entities/sw_sania/init.lua new file mode 100644 index 0000000..5ccad88 --- /dev/null +++ b/garrysmod/addons/sania/lua/entities/sw_sania/init.lua @@ -0,0 +1,158 @@ +AddCSLuaFile("shared.lua") +AddCSLuaFile("cl_init.lua") +include("shared.lua") +AddCSLuaFile("autorun/mur_drone_fx.lua") + +if SERVER then + +function ENT:Initialize() + self:SetModel("models/shtormer/sania.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetNWBool("JamEnabled", true) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then phys:Wake() end + + self.HP = self.HP or 500 +end + +function ENT:OnTakeDamage(dmginfo) + local dmg = dmginfo:GetDamage() + self.HP = self.HP - dmg + if self.HP <= 0 then + self:Remove() + end +end + +-- Глушение дронов +function ENT:JamDrone(drone) + if not IsValid(drone) then return end + + local dist = drone:GetPos():Distance(self:GetPos()) + local jammed = dist <= self.JamDistance + + drone:SetNWBool("Jammed", jammed) + + if jammed then + drone:SetNWFloat("JamStrength", 1 - dist / self.JamDistance) + else + drone:SetNWFloat("JamStrength", 0) + end +end + +hook.Add("KeyPress", "SaniaToggle", function(ply, key) + if key ~= IN_USE then return end + + local tr = ply:GetEyeTrace() + local ent = tr.Entity + + if not IsValid(ent) then return end + if ent:GetClass() ~= "sw_sania" then return end + if tr.HitPos:Distance(ply:GetPos()) > 150 then return end + + local newState = not ent:GetNWBool("JamEnabled") + ent:SetNWBool("JamEnabled", newState) + + if newState then + ply:ChatPrint("РЭБ: ВКЛЮЧЕНА") + ent:EmitSound("buttons/button3.wav", 75, 120) + else + ply:ChatPrint("РЭБ: ВЫКЛЮЧЕНА") + ent:EmitSound("buttons/button19.wav", 75, 80) + end +end) + + + +function ENT:Think() + local sph = ents.FindInSphere(self:GetPos(), self.JamDistance) + if not self:GetNWBool("JamEnabled") then + for drone, _ in pairs(self.JammedDrones or {}) do + if IsValid(drone) then + drone:SetNWBool("Jammed", false) + drone:SetNWFloat("JamStrength", 0) + end + end + self.JammedDrones = {} + self:NextThink(CurTime() + 0.1) + return true + end + self.JammedDrones = self.JammedDrones or {} + + -- временная таблица для отметки "кто сейчас в зоне" + local inZone = {} + + for i = 1, #sph do + local ent = sph[i] + if not IsValid(ent) then continue end + + local class = ent:GetClass() + + -- 1) Игрок в UAV + if ent:IsPlayer() then + local veh = ent:lvsGetVehicle() + if IsValid(veh) then + local uav = veh:GetNWEntity("UAVControl") + if IsValid(uav) then + local dist = veh:GetPos():Distance(self:GetPos()) + + if dist < self.JamDistance then + if not self._savedRates then + self._savedRates = { + pitch = veh.TurnRatePitch, + yaw = veh.TurnRateYaw, + roll = veh.TurnRateRoll + } + end + + local mul = math.max(0.1, dist / self.FullJamDistance) + + veh.TurnRatePitch = self._savedRates.pitch * mul + veh.TurnRateYaw = self._savedRates.yaw * mul + veh.TurnRateRoll = self._savedRates.roll * mul + else + if self._savedRates then + veh.TurnRatePitch = self._savedRates.pitch + veh.TurnRateYaw = self._savedRates.yaw + veh.TurnRateRoll = self._savedRates.roll + self._savedRates = nil + end + end + end + end + end + + -- 2) Дроны + if class == "mur_drone_entity" + or class == "mur_drone_grenade" + or class == "mur_drone_spy" then + + inZone[ent] = true + self.JammedDrones[ent] = true + + local dist = ent:GetPos():Distance(self:GetPos()) + ent:SetNWBool("Jammed", true) + ent:SetNWFloat("JamStrength", 1 - dist / self.JamDistance) + end + end + + for drone, _ in pairs(self.JammedDrones) do + if not IsValid(drone) then + self.JammedDrones[drone] = nil + continue + end + + if not inZone[drone] then + drone:SetNWBool("Jammed", false) + drone:SetNWFloat("JamStrength", 0) + self.JammedDrones[drone] = nil + end + end + + self:NextThink(CurTime() + 0.1) + return true +end + +end diff --git a/garrysmod/addons/sania/lua/entities/sw_sania/shared.lua b/garrysmod/addons/sania/lua/entities/sw_sania/shared.lua new file mode 100644 index 0000000..fd10885 --- /dev/null +++ b/garrysmod/addons/sania/lua/entities/sw_sania/shared.lua @@ -0,0 +1,15 @@ +ENT.Base = "base_gmodentity" +ENT.Type = "anim" + +ENT.PrintName = "Sania UAV Jammer" -- название энтити в меню Q +ENT.Author = "Shtormer" -- автор +ENT.Contact = "" -- контакты с автором +ENT.Information = "" -- описание энтити +ENT.Category = "SW Weapons Factory" -- категория в меню Q +ENT.Model = "models/shtormer/sania.mdl" +ENT.Spawnable = true -- разрешается спавнить через спавн-меню Q +ENT.AdminSpawnable = false -- разрешается спавнить только админам +ENT.JamDistance = 5000 +ENT.FullJamDistance = 2000 +ENT.HP = 500 +ENT.JamEnabled = true diff --git a/garrysmod/addons/schoolboard_addon/lua/entities/school_board/cl_init.lua b/garrysmod/addons/schoolboard_addon/lua/entities/school_board/cl_init.lua new file mode 100644 index 0000000..d01dda6 --- /dev/null +++ b/garrysmod/addons/schoolboard_addon/lua/entities/school_board/cl_init.lua @@ -0,0 +1,603 @@ +include("shared.lua") + +ENT.BoardRT = nil +ENT.BoardMat = nil +ENT.Elements = {} +ENT.ImageCache = {} + +function ENT:Initialize() + -- Создаем уникальный холст (RenderTarget) 1024x512 для рисования маркером + self.BoardRT = GetRenderTarget("SchoolBoardRT_" .. self:EntIndex(), 1024, 512) + self.BoardMat = CreateMaterial("SchoolBoardMat_" .. self:EntIndex(), "UnlitGeneric", { + ["$basetexture"] = self.BoardRT:GetName(), + ["$translucent"] = 1 + }) + + self.Elements = {} + self.ImageCache = {} + self.PositionOffset = Vector(3713.2, 4595.6, -200) -- Инициализируем смещение позиции + self.RotationOffset = Angle(0, -180, 90) -- Инициализируем смещение поворота + + -- Заливаем доску цветом белый + render.PushRenderTarget(self.BoardRT) + render.Clear(255, 255, 255, 255) + render.PopRenderTarget() + + -- Запрашиваем актуальный макет элементов (картинки, текст) + net.Start("SchoolBoard_RequestLayout") + net.WriteEntity(self) + net.SendToServer() +end + +local function GetBoardImage(ent, url) + if not url or url == "" then return nil end + if ent.ImageCache[url] then return ent.ImageCache[url] end + + ent.ImageCache[url] = Material("error") -- плейсхолдер пока грузится + + local fetchUrl = url + if string.match(fetchUrl, "^https?://imgur%.com/([^/]+)$") then + local id = string.match(fetchUrl, "^https?://imgur%.com/([^/]+)$") + fetchUrl = "https://i.imgur.com/" .. id .. ".png" + end + + http.Fetch(fetchUrl, function(body, length, headers, code) + if code == 200 then + local contentType = headers["Content-Type"] or headers["content-type"] or "" + if not string.find(string.lower(contentType), "image") then + chat.AddText(Color(255, 50, 50), "[Доска] Ссылка не является картинкой: ", url) + return + end + + local filename = "schoolboard_img_" .. util.CRC(url) .. ".png" + file.Write(filename, body) + ent.ImageCache[url] = Material("data/" .. filename, "noclamp smooth") + end + end) + return ent.ImageCache[url] +end + +net.Receive("SchoolBoard_SyncLayout", function() + local ent = net.ReadEntity() + local size = net.ReadUInt(32) + local compressed = net.ReadData(size) + + if IsValid(ent) and ent:GetClass() == "school_board" then + local json = util.Decompress(compressed) or "[]" + ent.Elements = util.JSONToTable(json) or {} + end +end) + +net.Receive("SchoolBoard_SyncPosition", function() + local ent = net.ReadEntity() + local offsetX = net.ReadFloat() + local offsetY = net.ReadFloat() + local offsetZ = net.ReadFloat() + + if IsValid(ent) and ent:GetClass() == "school_board" then + ent.PositionOffset = Vector(offsetX, offsetY, offsetZ) + end +end) + +net.Receive("SchoolBoard_SyncRotation", function() + local ent = net.ReadEntity() + local pitch = net.ReadFloat() + local yaw = net.ReadFloat() + local roll = net.ReadFloat() + + if IsValid(ent) and ent:GetClass() == "school_board" then + ent.RotationOffset = Angle(pitch, yaw, roll) + end +end) + +function ENT:GetBoardMetrics() + -- Фиксированный масштаб, который гарантированно влезает на доску + local scale = 0.088 + + local width = 93 + local height = 47 + + local realW = 1024 * scale + local realH = 512 * scale + + -- Центрируем холст 1024x512 относительно ширины 93 и высоты 47 + local offsetY = (width - realW) / 2 + local offsetZ = (height - realH) / 2 + + -- Исходные крайние точки из модели: Y = -46.5, Z = 23.5 + local startY = -46.5 + offsetY + local startZ = 23.5 - offsetZ + local frontX = 1.25 -- Чуть дальше 1.2, чтобы избежать z-fighting + + -- Смещение позиции применяем в локальных координатах пропа + local posOffset = self.PositionOffset or Vector(0, 0, 0) + local rotOffset = self.RotationOffset or Angle(0, 0, 270) + + -- Базовая позиция + смещение в локальных координатах пропа + local localPos = Vector(frontX + posOffset.x * 0.01, startY + posOffset.y * 0.01, startZ + posOffset.z * 0.01) + local pos = self:LocalToWorld(localPos) + local ang = self:LocalToWorldAngles(rotOffset) + + return pos, ang, scale, startY, startZ, frontX +end + +function ENT:Draw() + self:DrawModel() + + local pos, ang, scale, startY, startZ, frontX = self:GetBoardMetrics() + + self.BoardStartY = startY + self.BoardStartZ = startZ + self.BoardScale = scale + self.BoardFrontX = frontX + + cam.Start3D2D(pos, ang, scale) + -- Отрисовка слоя с маркерами (RenderTarget) + surface.SetMaterial(self.BoardMat) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(0, 0, 1024, 512) + + -- Отрисовка картинок и текста (native, четко) + for _, el in ipairs(self.Elements or {}) do + if el.type == "image" then + local mat = GetBoardImage(self, el.url) + if mat and mat:GetName() ~= "error" then + surface.SetMaterial(mat) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(el.x, el.y, el.w, el.h) + end + elseif el.type == "text" then + draw.DrawText(el.text, "DermaLarge", el.x, el.y, el.color or color_white, TEXT_ALIGN_LEFT) + end + end + cam.End3D2D() +end + +-- ========================================== +-- MENU / EDITOR +-- ========================================== +net.Receive("SchoolBoard_OpenMenu", function() + local ent = net.ReadEntity() + if not IsValid(ent) then return end + + local local_elements = table.Copy(ent.Elements or {}) + local selected_idx = nil + + local frame = vgui.Create("DFrame") + -- Делаем окно адаптивным под разрешение игрока + local fw, fh = ScrW() * 0.9, ScrH() * 0.9 + frame:SetSize(fw, fh) + frame:Center() + frame:SetTitle("Редактор Интерактивной Доски") + frame:MakePopup() + + -- ЛЕВАЯ ПАНЕЛЬ: Инструменты + local leftPanel = vgui.Create("DPanel", frame) + leftPanel:Dock(LEFT) + leftPanel:SetWide(math.max(200, fw * 0.15)) + leftPanel:DockMargin(5, 5, 5, 5) + + -- ПРАВАЯ ПАНЕЛЬ: Свойства выбранного элемента + local rightPanel = vgui.Create("DPanel", frame) + rightPanel:Dock(RIGHT) + rightPanel:SetWide(math.max(250, fw * 0.2)) + rightPanel:DockMargin(5, 5, 5, 5) + + -- ЦЕНТР: Холст + local centerPanel = vgui.Create("DPanel", frame) + centerPanel:Dock(FILL) + centerPanel:DockMargin(5, 5, 5, 5) + centerPanel.Paint = function(self, w, h) + surface.SetDrawColor(50, 50, 50, 255) + surface.DrawRect(0, 0, w, h) + end + + local titleHint = vgui.Create("DLabel", centerPanel) + titleHint:Dock(TOP) + titleHint:DockMargin(10, 10, 10, 0) + titleHint:SetFont("DermaDefaultBold") + titleHint:SetText("Холст 1024x512. Перетаскивайте элементы мышью.") + titleHint:SetTextColor(color_white) + titleHint:SetContentAlignment(5) + + local canvasCanvas = vgui.Create("DPanel", centerPanel) + canvasCanvas:Dock(FILL) + canvasCanvas:DockMargin(20, 10, 20, 20) + canvasCanvas.Paint = function() end -- transparent wrapper + + -- Сам холст + local canvasPanel = vgui.Create("DPanel", canvasCanvas) + canvasPanel:SetMouseInputEnabled(true) + + -- Вычисляем размеры холста, чтобы сохранить пропорции 2:1 + canvasCanvas.PerformLayout = function(self, w, h) + local ratio = 1024 / 512 + local cw, ch = w, w / ratio + if ch > h then + ch = h + cw = h * ratio + end + canvasPanel:SetSize(cw, ch) + canvasPanel:SetPos((w - cw) / 2, (h - ch) / 2) + end + + -- Отрисовка холста (с маппингом из 1024x512 в текущее разрешение cw x ch) + canvasPanel.Paint = function(self, w, h) + local scaleX = w / 1024 + local scaleY = h / 512 + + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawRect(0, 0, w, h) + + for i, el in ipairs(local_elements) do + local drawX = el.x * scaleX + local drawY = el.y * scaleY + local drawW = (el.w or 100) * scaleX + local drawH = (el.h or 30) * scaleY + + if el.type == "image" then + local mat = GetBoardImage(ent, el.url) + if mat and mat:GetName() ~= "error" then + surface.SetMaterial(mat) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(drawX, drawY, drawW, drawH) + else + surface.SetDrawColor(200, 200, 200, 255) + surface.DrawRect(drawX, drawY, drawW, drawH) + draw.SimpleText("Image", "DermaDefault", drawX + drawW/2, drawY + drawH/2, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + elseif el.type == "text" then + local m = Matrix() + m:Translate(Vector(drawX, drawY, 0)) + m:Scale(Vector(scaleX, scaleY, 1)) + + cam.PushModelMatrix(m) + draw.DrawText(el.text, "DermaLarge", 0, 0, el.color or color_white, TEXT_ALIGN_LEFT) + cam.PopModelMatrix() + end + + if i == selected_idx then + surface.SetDrawColor(255, 0, 0, 255) + surface.DrawOutlinedRect(drawX, drawY, drawW, drawH, 2) + end + end + end + + local dragging = false + local dragOffset = {x=0,y=0} + + canvasPanel.OnMousePressed = function(self, key) + if key == MOUSE_LEFT then + local mx, my = self:CursorPos() + local w, h = self:GetSize() + local scaleX = 1024 / w + local scaleY = 512 / h + + local realX = mx * scaleX + local realY = my * scaleY + + selected_idx = nil + for i = #local_elements, 1, -1 do + local el = local_elements[i] + local ew = el.w or 100 + local eh = el.h or 30 + if el.type == "text" then + surface.SetFont("DermaLarge") + ew, eh = surface.GetTextSize(el.text) + el.w = ew + el.h = eh + end + + if realX >= el.x and realX <= el.x + ew and realY >= el.y and realY <= el.y + eh then + selected_idx = i + dragging = true + dragOffset.x = realX - el.x + dragOffset.y = realY - el.y + break + end + end + UpdatePropertiesPanel() + end + end + canvasPanel.OnMouseReleased = function(self, key) + if key == MOUSE_LEFT then dragging = false end + end + canvasPanel.Think = function(self) + if dragging and selected_idx and local_elements[selected_idx] then + local mx, my = self:CursorPos() + local w, h = self:GetSize() + local scaleX = 1024 / w + local scaleY = 512 / h + + local realX = mx * scaleX + local realY = my * scaleY + + local_elements[selected_idx].x = realX - dragOffset.x + local_elements[selected_idx].y = realY - dragOffset.y + end + end + + local toolsPanel = vgui.Create("DScrollPanel", leftPanel) + toolsPanel:Dock(FILL) + + local lblTools = toolsPanel:Add("DLabel") + lblTools:Dock(TOP) + lblTools:DockMargin(5,5,5,10) + lblTools:SetText("Инструменты") + lblTools:SetFont("DermaDefaultBold") + lblTools:SetDark(true) + + local btnAddImg = toolsPanel:Add("DButton") + btnAddImg:Dock(TOP) + btnAddImg:DockMargin(5,0,5,5) + btnAddImg:SetText("Добавить Картинку") + btnAddImg.DoClick = function() + Derma_StringRequest("URL Картинки", "Введите прямую ссылку (.png/.jpg):", "", function(text) + table.insert(local_elements, {type="image", url=text, x=512-150, y=256-150, w=300, h=300}) + selected_idx = #local_elements + UpdatePropertiesPanel() + end) + end + + local btnAddText = toolsPanel:Add("DButton") + btnAddText:Dock(TOP) + btnAddText:DockMargin(5,0,5,15) + btnAddText:SetText("Добавить Текст") + btnAddText.DoClick = function() + table.insert(local_elements, {type="text", text="Новый текст", x=512-50, y=256-15, color=Color(255,255,255)}) + selected_idx = #local_elements + UpdatePropertiesPanel() + end + + local lblControl = toolsPanel:Add("DLabel") + lblControl:Dock(TOP) + lblControl:DockMargin(5,10,5,5) + lblControl:SetText("Управление доской") + lblControl:SetFont("DermaDefaultBold") + lblControl:SetDark(true) + + local btnSave = toolsPanel:Add("DButton") + btnSave:Dock(TOP) + btnSave:DockMargin(5,0,5,5) + btnSave:SetText("Сохранить на Доску") + btnSave:SetTall(35) + btnSave.DoClick = function() + -- Сохраняем элементы (текст и картинки) + local json = util.TableToJSON(local_elements) + local compressed = util.Compress(json) + net.Start("SchoolBoard_SaveLayout") + net.WriteEntity(ent) + net.WriteUInt(string.len(compressed), 32) + net.WriteData(compressed, string.len(compressed)) + net.SendToServer() + + chat.AddText(Color(0,255,0), "[Доска] Успешно сохранено!") + frame:Close() + end + + local btnClearDraw = toolsPanel:Add("DButton") + btnClearDraw:Dock(TOP) + btnClearDraw:DockMargin(5,5,5,15) + btnClearDraw:SetText("Стереть 3D-маркер") + btnClearDraw.DoClick = function() + net.Start("SchoolBoard_Clear_Req") + net.WriteEntity(ent) + net.SendToServer() + end + + local chalkLabel = toolsPanel:Add("DLabel") + chalkLabel:Dock(TOP) + chalkLabel:DockMargin(5,10,5,5) + chalkLabel:SetText("Цвет 3D-маркера:") + chalkLabel:SetDark(true) + + local chalkMixer = toolsPanel:Add("DColorMixer") + chalkMixer:Dock(TOP) + chalkMixer:SetTall(100) + chalkMixer:SetPalette(true) + chalkMixer:SetAlphaBar(false) + chalkMixer:SetWangs(true) + chalkMixer:SetColor(LocalPlayer().BoardChalkColor or Color(255,255,255)) + chalkMixer.ValueChanged = function(s, c) + LocalPlayer().BoardChalkColor = c + end + + local propsScroll = vgui.Create("DScrollPanel", rightPanel) + propsScroll:Dock(FILL) + + function UpdatePropertiesPanel() + propsScroll:Clear() + + local lblProps = propsScroll:Add("DLabel") + lblProps:Dock(TOP) + lblProps:DockMargin(5,5,5,10) + lblProps:SetText("Свойства элемента") + lblProps:SetFont("DermaDefaultBold") + lblProps:SetDark(true) + + if selected_idx and local_elements[selected_idx] then + local el = local_elements[selected_idx] + + local btnDel = propsScroll:Add("DButton") + btnDel:Dock(TOP) + btnDel:DockMargin(5,0,5,10) + btnDel:SetText("Удалить элемент") + btnDel:SetTextColor(Color(200, 50, 50)) + btnDel.DoClick = function() + table.remove(local_elements, selected_idx) + selected_idx = nil + UpdatePropertiesPanel() + end + + if el.type == "image" then + local wSlider = propsScroll:Add("DNumSlider") + wSlider:Dock(TOP) + wSlider:DockMargin(5,0,5,0) + wSlider:SetText("Ширина") + wSlider:SetMinMax(10, 1024) + wSlider:SetDecimals(0) + wSlider:SetValue(el.w) + wSlider.OnValueChanged = function(s, val) el.w = val end + + local hSlider = propsScroll:Add("DNumSlider") + hSlider:Dock(TOP) + hSlider:DockMargin(5,0,5,10) + hSlider:SetText("Высота") + hSlider:SetMinMax(10, 1024) + hSlider:SetDecimals(0) + hSlider:SetValue(el.h) + hSlider.OnValueChanged = function(s, val) el.h = val end + + elseif el.type == "text" then + local tEntry = propsScroll:Add("DTextEntry") + tEntry:Dock(TOP) + tEntry:DockMargin(5,0,5,10) + tEntry:SetValue(el.text) + tEntry.OnChange = function(s) el.text = s:GetValue() end + + local lblC = propsScroll:Add("DLabel") + lblC:Dock(TOP) + lblC:DockMargin(5,5,5,0) + lblC:SetText("Цвет текста:") + lblC:SetDark(true) + + local colMixer = propsScroll:Add("DColorMixer") + colMixer:Dock(TOP) + colMixer:SetTall(120) + colMixer:SetAlphaBar(false) + colMixer:DockMargin(5,0,5,10) + colMixer:SetColor(el.color or color_white) + colMixer.ValueChanged = function(s, c) el.color = c end + end + + local btnUp = propsScroll:Add("DButton") + btnUp:Dock(TOP) + btnUp:DockMargin(5,5,5,5) + btnUp:SetText("На передний план") + btnUp.DoClick = function() + local temp = table.remove(local_elements, selected_idx) + table.insert(local_elements, temp) + selected_idx = #local_elements + UpdatePropertiesPanel() + end + + local btnDown = propsScroll:Add("DButton") + btnDown:Dock(TOP) + btnDown:DockMargin(5,0,5,5) + btnDown:SetText("На задний план") + btnDown.DoClick = function() + local temp = table.remove(local_elements, selected_idx) + table.insert(local_elements, 1, temp) + selected_idx = 1 + UpdatePropertiesPanel() + end + else + local hint = propsScroll:Add("DLabel") + hint:Dock(TOP) + hint:DockMargin(5,0,5,0) + hint:SetText("Выберите элемент на холсте,\nчтобы изменить его свойства.") + hint:SetDark(true) + hint:SizeToContents() + end + end + UpdatePropertiesPanel() +end) + +-- ========================================== +-- CHALK DRAWING LOGIC (MARKER) +-- ========================================== +net.Receive("SchoolBoard_Clear_Do", function() + local ent = net.ReadEntity() + + if IsValid(ent) and ent.BoardRT then + render.PushRenderTarget(ent.BoardRT) + render.Clear(180, 140, 100, 255) + render.PopRenderTarget() + end +end) + +net.Receive("SchoolBoard_DrawLine", function() + local ent = net.ReadEntity() + local x1 = net.ReadFloat() + local y1 = net.ReadFloat() + local x2 = net.ReadFloat() + local y2 = net.ReadFloat() + local r = net.ReadUInt(8) + local g = net.ReadUInt(8) + local b = net.ReadUInt(8) + + if IsValid(ent) and ent.BoardRT then + render.PushRenderTarget(ent.BoardRT) + cam.Start2D() + surface.SetDrawColor(r, g, b, 255) + surface.DrawLine(x1, y1, x2, y2) + surface.DrawLine(x1 + 1, y1, x2 + 1, y2) + surface.DrawLine(x1, y1 + 1, x2, y2 + 1) + cam.End2D() + render.PopRenderTarget() + end +end) + +local lastDrawX, lastDrawY = nil, nil + +hook.Add("Think", "SchoolBoard_DrawLogic", function() + local ply = LocalPlayer() + + if not input.IsMouseDown(MOUSE_LEFT) then + lastDrawX = nil + lastDrawY = nil + return + end + + local tr = ply:GetEyeTrace() + local ent = tr.Entity + + if IsValid(ent) and ent:GetClass() == "school_board" and ent.BoardRT then + if ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then return end + if not ent.BoardScale then return end + + local obbPos = ent:WorldToLocal(tr.HitPos) + + -- Проверка: не даем рисовать с обратной стороны доски + if obbPos.x < ent.BoardFrontX - 2 then return end + + local drawX = (obbPos.y - ent.BoardStartY) / ent.BoardScale + local drawY = (ent.BoardStartZ - obbPos.z) / ent.BoardScale + + if drawX < 0 or drawX > 1024 or drawY < 0 or drawY > 512 then + lastDrawX = nil + lastDrawY = nil + return + end + + local chalkColor = ply.BoardChalkColor or Color(255, 255, 255) + + if lastDrawX and lastDrawY then + render.PushRenderTarget(ent.BoardRT) + cam.Start2D() + surface.SetDrawColor(chalkColor.r, chalkColor.g, chalkColor.b, 255) + surface.DrawLine(lastDrawX, lastDrawY, drawX, drawY) + surface.DrawLine(lastDrawX + 1, lastDrawY, drawX + 1, drawY) + surface.DrawLine(lastDrawX, lastDrawY + 1, drawX, drawY + 1) + cam.End2D() + render.PopRenderTarget() + + net.Start("SchoolBoard_DrawLine", true) + net.WriteEntity(ent) + net.WriteFloat(lastDrawX) + net.WriteFloat(lastDrawY) + net.WriteFloat(drawX) + net.WriteFloat(drawY) + net.WriteUInt(chalkColor.r, 8) + net.WriteUInt(chalkColor.g, 8) + net.WriteUInt(chalkColor.b, 8) + net.SendToServer() + end + + lastDrawX = drawX + lastDrawY = drawY + else + lastDrawX = nil + lastDrawY = nil + end +end) diff --git a/garrysmod/addons/schoolboard_addon/lua/entities/school_board/init.lua b/garrysmod/addons/schoolboard_addon/lua/entities/school_board/init.lua new file mode 100644 index 0000000..1749582 --- /dev/null +++ b/garrysmod/addons/schoolboard_addon/lua/entities/school_board/init.lua @@ -0,0 +1,274 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +-- Регистрация всех сетевых сообщений +util.AddNetworkString("SchoolBoard_OpenMenu") +util.AddNetworkString("SchoolBoard_SaveLayout") +util.AddNetworkString("SchoolBoard_SyncLayout") +util.AddNetworkString("SchoolBoard_RequestLayout") +util.AddNetworkString("SchoolBoard_Clear_Req") +util.AddNetworkString("SchoolBoard_Clear_Do") +util.AddNetworkString("SchoolBoard_DrawLine") +util.AddNetworkString("SchoolBoard_SavePosition") +util.AddNetworkString("SchoolBoard_SyncPosition") +util.AddNetworkString("SchoolBoard_SaveRotation") +util.AddNetworkString("SchoolBoard_SyncRotation") + + +-- helper: only SAM ranks superadmin/curator (or equivalent usergroups) can modify board +local function CanModifyBoard(ply) + if not IsValid(ply) or not ply:IsPlayer() then return false end + + -- standard checks + if ply:IsSuperAdmin() then return true end + if ply:GetUserGroup() == "curator" then return true end + + -- if SAM is present, consult its API as a fallback + if sam and sam.IsPlayerInRank then + if sam.IsPlayerInRank(ply, "superadmin") or sam.IsPlayerInRank(ply, "curator") then + return true + end + end + + return false +end + +-- append log entries to a file and console +local function LogBoardChange(ply, ent, action) + local name = IsValid(ply) and ply:Nick() or "Console" + local sid = IsValid(ply) and ply:SteamID() or "N/A" + local id = IsValid(ent) and ent:EntIndex() or 0 + local pos = IsValid(ent) and tostring(ent:GetPos()) or "unknown" + local msg = string.format("[%s] %s (%s) %s board #%d at %s\n", os.date(), name, sid, action, id, pos) + + print(msg) + file.Append("schoolboard_changes.txt", msg) +end + +-- console command for printing current log (admins only) +concommand.Add("schoolboard_printlog", function(ply, cmd, args) + if IsValid(ply) and not CanModifyBoard(ply) then + ply:ChatPrint("Нет доступа к логу доски.") + return + end + + local contents = file.Read("schoolboard_changes.txt", "DATA") or "" + if IsValid(ply) then + ply:PrintMessage(HUD_PRINTCONSOLE, contents) + ply:ChatPrint("Лог выведен в консоль.") + else + print(contents) + end +end) + +-- очистка лога доски (только для старшего админства) +concommand.Add("schoolboard_clearlog", function(ply, cmd, args) + if IsValid(ply) and not CanModifyBoard(ply) then + ply:ChatPrint("Нет доступа к управлению логом.") + return + end + + file.Write("schoolboard_changes.txt", "") + if IsValid(ply) then + ply:ChatPrint("Лог доски очищен.") + ply:PrintMessage(HUD_PRINTCONSOLE, "Лог доски был очищен.") + else + print("schoolboard_changes.txt was cleared by console") + end +end) + +function ENT:Initialize() + self:SetModel("models/props/cs_office/offcorkboarda.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) -- Позволяет нажимать 'E' на доску + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + + self.BoardLayoutStr = "[]" -- По умолчанию пустой массив JSON + self.PositionOffset = Vector(3713.2, 4595.6, -200) -- Смещение позиции доски в 3D пространстве + self.RotationOffset = Angle(0, -180, 90) -- Смещение поворота доски +end + +-- Открываем меню настройки, когда игрок нажимает 'E' +function ENT:Use(activator, caller) + if IsValid(activator) and activator:IsPlayer() then + if not CanModifyBoard(activator) then + activator:ChatPrint("Только superadmin/curator может редактировать доску.") + return + end + + net.Start("SchoolBoard_OpenMenu") + net.WriteEntity(self) + net.Send(activator) + end +end + +-- Клиент отправляет свой новый макет доски (массив картинок и текстов) +net.Receive("SchoolBoard_SaveLayout", function(len, ply) + local ent = net.ReadEntity() + + if len > 64000 then -- Ограничение размера пакета на всякий случай + return + end + + local layoutSize = net.ReadUInt(32) + local compressedData = net.ReadData(layoutSize) + + if IsValid(ent) and ent:GetClass() == "school_board" then + -- Разрешены только superadmin/curator (SAM ранги) или соответствующие usergroup + if not CanModifyBoard(ply) then + if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end + return + end + + -- Проверка расстояния, чтобы школьник с другого конца карты не менял доску + if ply:GetPos():DistToSqr(ent:GetPos()) > 500000 then return end + + ent.BoardLayoutStr = util.Decompress(compressedData) or "[]" + + -- логируем факт сохранения + LogBoardChange(ply, ent, "saved layout") + + -- Рассказываем всем остальным, что доска обновилась + net.Start("SchoolBoard_SyncLayout") + net.WriteEntity(ent) + net.WriteUInt(layoutSize, 32) + net.WriteData(compressedData, layoutSize) + net.Broadcast() + end +end) + +-- Когда игрок подключается или загружает сущность, он просит её данные +net.Receive("SchoolBoard_RequestLayout", function(len, ply) + local ent = net.ReadEntity() + + if IsValid(ent) and ent:GetClass() == "school_board" then + if not ent.BoardLayoutStr then ent.BoardLayoutStr = "[]" end + + local compressedData = util.Compress(ent.BoardLayoutStr) + if compressedData then + local layoutSize = string.len(compressedData) + + net.Start("SchoolBoard_SyncLayout") + net.WriteEntity(ent) + net.WriteUInt(layoutSize, 32) + net.WriteData(compressedData, layoutSize) + net.Send(ply) + end + end +end) + +-- Принимаем запрос на очистку от игрока и рассылаем всем +net.Receive("SchoolBoard_Clear_Req", function(len, ply) + local ent = net.ReadEntity() + + if IsValid(ent) and ent:GetClass() == "school_board" then + if not CanModifyBoard(ply) then + if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end + return + end + + LogBoardChange(ply, ent, "cleared board") + + net.Start("SchoolBoard_Clear_Do") + net.WriteEntity(ent) + net.Broadcast() + end +end) + +-- Принимаем координаты линии и цвет от рисующего и рассылаем остальным +net.Receive("SchoolBoard_DrawLine", function(len, ply) + local ent = net.ReadEntity() + local x1 = net.ReadFloat() + local y1 = net.ReadFloat() + local x2 = net.ReadFloat() + local y2 = net.ReadFloat() + local r = net.ReadUInt(8) + local g = net.ReadUInt(8) + local b = net.ReadUInt(8) + + if IsValid(ent) and ent:GetClass() == "school_board" then + -- ограничения на рисование маркером + if not CanModifyBoard(ply) then return end + + -- Проверяем, что игрок стоит рядом с доской + if ply:GetPos():DistToSqr(ent:GetPos()) > 50000 then return end + + net.Start("SchoolBoard_DrawLine", true) -- unreliable пакет + net.WriteEntity(ent) + net.WriteFloat(x1) + net.WriteFloat(y1) + net.WriteFloat(x2) + net.WriteFloat(y2) + net.WriteUInt(r, 8) + net.WriteUInt(g, 8) + net.WriteUInt(b, 8) + net.SendOmit(ply) -- Отправляем всем, кроме рисующего + end +end) + +-- Сохраняем позицию доски (смещение вектора) +net.Receive("SchoolBoard_SavePosition", function(len, ply) + local ent = net.ReadEntity() + local offsetX = net.ReadFloat() + local offsetY = net.ReadFloat() + local offsetZ = net.ReadFloat() + + if IsValid(ent) and ent:GetClass() == "school_board" then + if not CanModifyBoard(ply) then + if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end + return + end + -- Проверка расстояния + if ply:GetPos():DistToSqr(ent:GetPos()) > 500000 then return end + + ent.PositionOffset = Vector(offsetX, offsetY, offsetZ) + + -- логируем изменение позиции + LogBoardChange(ply, ent, "changed position") + + -- Синхронизируем позицию со всеми + net.Start("SchoolBoard_SyncPosition") + net.WriteEntity(ent) + net.WriteFloat(offsetX) + net.WriteFloat(offsetY) + net.WriteFloat(offsetZ) + net.Broadcast() + end +end) + +-- Сохраняем поворот доски (смещение угла) +net.Receive("SchoolBoard_SaveRotation", function(len, ply) + local ent = net.ReadEntity() + local pitch = net.ReadFloat() + local yaw = net.ReadFloat() + local roll = net.ReadFloat() + + if IsValid(ent) and ent:GetClass() == "school_board" then + if not CanModifyBoard(ply) then + if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end + return + end + -- Проверка расстояния + if ply:GetPos():DistToSqr(ent:GetPos()) > 500000 then return end + + ent.RotationOffset = Angle(pitch, yaw, roll) + + -- логируем изменение поворота + LogBoardChange(ply, ent, "changed rotation") + + -- Синхронизируем поворот со всеми + net.Start("SchoolBoard_SyncRotation") + net.WriteEntity(ent) + net.WriteFloat(pitch) + net.WriteFloat(yaw) + net.WriteFloat(roll) + net.Broadcast() + end +end) diff --git a/garrysmod/addons/schoolboard_addon/lua/entities/school_board/shared.lua b/garrysmod/addons/schoolboard_addon/lua/entities/school_board/shared.lua new file mode 100644 index 0000000..c0063c3 --- /dev/null +++ b/garrysmod/addons/schoolboard_addon/lua/entities/school_board/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Интерактивная доска" +ENT.Category = "Разное" +ENT.Author = "Welding" +ENT.Spawnable = true + +function ENT:SetupDataTables() + -- Больше не используем NetworkVar для одной картинки. + -- Доска будет хранить массив элементов через JSON. +end diff --git a/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/cl_init.lua b/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/cl_init.lua new file mode 100644 index 0000000..5f5d48b --- /dev/null +++ b/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/cl_init.lua @@ -0,0 +1,1879 @@ +include("shared.lua") + +-- === НАСТРОЙКА ИКОНОК, НАЗВАНИЙ И ОПИСАНИЙ (ARMA STYLE) === +local CUSTOM_ICONS_BG = { + ["shirt_and_jaket"] = { + default = Material("wardrobe_ui/body.png", "noclamp smooth"), + }, + ["helm"] = { + -- default = Material("wardrobe_ui/helm_icon.png", "noclamp smooth"), + } +} + +local CUSTOM_ICONS_SKIN = { + -- default = Material("wardrobe_ui/skin_bg.png", "noclamp smooth"), +} + +local CUSTOM_NAMES_BG = { + ["shirt_and_jaket"] = { + [0] = "Стандартная форма", + [1] = "Закатанные рукава", + }, + ["helm"] = { + [0] = "Без шлема", + } +} + +local CUSTOM_DESC_BG = { + ["shirt_and_jaket"] = { + [0] = "Полевая форма из плотного материала.\nОбеспечивает базовую маскировку.\n\n1.2kg", + [1] = "Облегченный вариант формы для жаркого климата.\nСнижает перегрев бойца.\n\n1.0kg", + }, + ["helm"] = { + [0] = "Отсутствие головного убора.\nНе предоставляет баллистической защиты.\n\n0.0kg", + } +} + +local CUSTOM_NAMES_SKIN = { + [0] = "Лесной камуфляж (Woodland)", + [1] = "Пустынный камуфляж (Desert)", +} + +local CUSTOM_DESC_SKIN = { + [0] = "Стандартный лесной паттерн для умеренного климата.\n\nКамуфляж", + [1] = "Песочный паттерн для засушливых регионов.\n\nКамуфляж", +} +-- ======================== + +local C_BG_D = Color(3, 5, 4) +local C_BG_M = Color(8, 12, 10) +local C_BG_L = Color(12, 18, 14) +local C_PRI = Color(27, 94, 32) +local C_PRI_H = Color(33, 110, 38) +local C_ACC = Color(56, 102, 35) +local C_ACC_H = Color(66, 120, 45) +local C_BDR = Color(46, 125, 50, 80) +local C_TXT_P = Color(165, 214, 167) +local C_GRAD = Color(27, 94, 32, 15) +local C_WARN = Color(150, 40, 40) +local C_WARN_H = Color(180, 50, 50) +local C_WHITE = Color(255, 255, 255, 200) + +local C_ARMA_TOOLTIP_BG = Color(35, 35, 35, 240) +local C_ARMA_SLOT = Color(40, 40, 40, 255) +local C_ARMA_SLOT_HOVER = Color(60, 60, 60, 255) +local C_ARMA_SLOT_EMPTY = Color(20, 20, 20, 150) +local C_ARMA_SEL = Color(194, 138, 74, 255) +local C_ARMA_TEXT = Color(180, 180, 180, 255) + +local bgMat = Material("materials/wardrobe_ui/fon.jpg", "noclamp smooth") + +-- ===================================================================== +-- ПАНЕЛЬ ОБЫЧНОГО ИГРОКА +-- ===================================================================== +net.Receive("SandboxWardrobeOpen", function() + local model = net.ReadString() + local bodygroups = net.ReadTable() + local availableSkins = net.ReadTable() + local currentBodygroups = net.ReadTable() + local currentSkin = net.ReadUInt(8) + local unlockedBodygroups = net.ReadTable() + local serverPresets = net.ReadTable() + + local scrW, scrH = ScrW(), ScrH() + local fW = math.Clamp(scrW * 0.62, 950, 1600) + local fH = math.Clamp(scrH * 0.62, 600, 1000) + + local leftW = 230 + local rightW = math.Clamp(fW * 0.38, 350, 550) + local midW = fW - leftW - rightW - 80 + local midX = 40 + leftW + local rightX = midX + midW + 20 + + local frame = vgui.Create("DFrame") + frame:SetSize(fW, fH) + frame:Center() + frame:SetTitle("") + frame:ShowCloseButton(false) + frame:MakePopup() + frame:SetBackgroundBlur(true) + frame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_GRAD) + surface.DrawRect(0, 0, w, 3) + draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false) + surface.SetDrawColor(C_BDR) + surface.DrawLine(0, 50, w, 50) + draw.SimpleText("ГАРДЕРОБ", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(C_ACC) + surface.DrawRect(20, 48, 30, 2) + surface.DrawRect(w-50, 48, 30, 2) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(frame:GetWide() - 40, 10) + closeBtn:SetSize(30, 30) + closeBtn:SetText("") + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ACC_H or C_ACC + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + closeBtn.DoClick = function() frame:Close() end + + local btnOffset = 45 + + local hintsPanel = vgui.Create("DPanel", frame) + hintsPanel:SetPos(20, 60) + hintsPanel.Expanded = true + hintsPanel.CurHeight = 305 + hintsPanel:SetSize(230, hintsPanel.CurHeight) + hintsPanel.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1) end + hintsPanel.Think = function(s) + local targetHeight = s.Expanded and 305 or 35 + if s.CurHeight ~= targetHeight then s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight); if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end; s:SetTall(math.Round(s.CurHeight)) end + end + + local hintsHeader = vgui.Create("DButton", hintsPanel) + hintsHeader:SetPos(0, 0); hintsHeader:SetSize(230, 35); hintsHeader:SetText("") + hintsHeader.Paint = function(s, w, h) + local col = s:IsHovered() and C_BG_L or C_BG_D + draw.RoundedBoxEx(6, 0, 0, w, h, col, true, true, not hintsPanel.Expanded, not hintsPanel.Expanded) + surface.SetDrawColor(C_ACC); surface.DrawRect(0, 0, 3, h) + draw.SimpleText("УПРАВЛЕНИЕ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(hintsPanel.Expanded and "▼" or "◀", "DermaDefault", w - 15, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + hintsHeader.DoClick = function(s) hintsPanel.Expanded = not hintsPanel.Expanded end + + local hintsContent = vgui.Create("DPanel", hintsPanel) + hintsContent:SetPos(0, 35); hintsContent:SetSize(230, 270); hintsContent.Paint = function() end + local binds = { {"ЛКМ / Перетаскивание", "Примерить одежду"}, {"ALT + ЛКМ", "Вращение камеры"}, {"SHIFT + ЛКМ", "Перемещение камеры"}, {"Колёсико мыши", "Приблизить / Отдалить"} } + local hy = 10 + for _, bind in ipairs(binds) do + local bindItem = vgui.Create("DPanel", hintsContent) + bindItem:SetPos(10, hy); bindItem:SetSize(210, 55) + bindItem.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1); draw.SimpleText(bind[1], "DermaDefaultBold", w/2, 16, C_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); draw.SimpleText(bind[2], "DermaDefault", w/2, 36, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end + hy = hy + 60 + end + + local presetsPanel = vgui.Create("DPanel", frame) + presetsPanel.Expanded = false + presetsPanel.CurHeight = 35 + presetsPanel:SetSize(230, presetsPanel.CurHeight) + + local presetsContent = vgui.Create("DScrollPanel", presetsPanel) + presetsContent:SetPos(0, 35) + + presetsPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + presetsPanel.Think = function(s) + local maxH = fH - hintsPanel.CurHeight - 80 + local targetHeight = s.Expanded and maxH or 35 + if s.CurHeight ~= targetHeight then + s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) + if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end + s:SetTall(math.Round(s.CurHeight)) + end + local targetY = hintsPanel:GetY() + hintsPanel:GetTall() + 10 + if s:GetY() ~= targetY then s:SetPos(20, targetY) end + + presetsContent:SetSize(230, s:GetTall() - 35) + end + + local presetsHeader = vgui.Create("DButton", presetsPanel) + presetsHeader:SetPos(0, 0); presetsHeader:SetSize(230, 35); presetsHeader:SetText("") + presetsHeader.Paint = function(s, w, h) + local col = s:IsHovered() and C_BG_L or C_BG_D + draw.RoundedBoxEx(6, 0, 0, w, h, col, true, true, not presetsPanel.Expanded, not presetsPanel.Expanded) + surface.SetDrawColor(C_ACC); surface.DrawRect(0, 0, 3, h); draw.SimpleText("СБОРКИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(presetsPanel.Expanded and "▼" or "◀", "DermaDefault", w - 15, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + presetsHeader.DoClick = function(s) presetsPanel.Expanded = not presetsPanel.Expanded end + + local allPresets = serverPresets or {} + if not allPresets[model] then allPresets[model] = {} end + + local function SavePresetsToServer() + net.Start("SandboxWardrobeSavePreset") + net.WriteTable(allPresets) + net.SendToServer() + end + + -- НОРМАЛИЗАЦИЯ ключей (util.JSONToTable превращает строковые числа обратно в number type) + local norm = {} + for k, v in pairs(allPresets[model]) do norm[tostring(k)] = v end + allPresets[model] = norm + + local py = 10 + for i = 1, 10 do + local slotPanel = vgui.Create("DPanel", presetsContent) + slotPanel:SetPos(10, py) + slotPanel:SetSize(200, 85) + slotPanel.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local pData = allPresets[model][i] or allPresets[model][tostring(i)] + local hasData = pData ~= nil + local defaultName = "Сборка " .. i + + local iconCell = vgui.Create("DPanel", slotPanel) + iconCell:SetPos(130, 12) + iconCell:SetSize(60, 60) + iconCell.Paint = function(s, w, h) + local curData = allPresets[model][tostring(i)] + local isFilled = curData ~= nil + + draw.RoundedBox(4, 0, 0, w, h, isFilled and C_ARMA_SLOT or C_ARMA_SLOT_EMPTY) + + if isFilled then + local mat = Material("wardrobe_ui/body.png", "noclamp smooth") + if mat and not mat:IsError() then + surface.SetDrawColor(255, 255, 255, 200) + surface.SetMaterial(mat) + surface.DrawTexturedRect(5, 5, w-10, h-10) + end + surface.SetDrawColor(C_PRI) + surface.DrawOutlinedRect(0, 0, w, h, 1) + else + draw.SimpleText("ПУСТО", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0,0,0,100)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + end + + local nameEntry = vgui.Create("DTextEntry", slotPanel) + nameEntry:SetPos(10, 12) + nameEntry:SetSize(110, 25) + nameEntry:SetText(hasData and (pData.name or defaultName) or defaultName) + nameEntry.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(s:IsEditing() and C_PRI or C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(C_WHITE, C_PRI, C_WHITE) + end + nameEntry.OnChange = function(s) + local curData = allPresets[model][tostring(i)] + if curData then + curData.name = s:GetText() + timer.Create("WardrobePresetSave_"..i, 0.5, 1, function() + if IsValid(frame) then SavePresetsToServer() end + end) + end + end + + local btnW = 33 + local btnY = 47 + + local loadBtn = vgui.Create("DButton", slotPanel) + loadBtn:SetPos(10, btnY) + loadBtn:SetSize(btnW, 25) + loadBtn:SetText("") + loadBtn.Paint = function(s, w, h) + if not allPresets[model][tostring(i)] then + draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) + draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + return + end + local col = s:IsHovered() and C_ACC_H or C_ACC + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + loadBtn.DoClick = function() + local curData = allPresets[model][tostring(i)] + if not curData then return end + for idx, val in pairs(curData.bgs) do currentBodygroups[tonumber(idx) or idx] = val end + if curData.skin then currentSkin = curData.skin end + surface.PlaySound("buttons/button15.wav") + end + + local saveBtn = vgui.Create("DButton", slotPanel) + saveBtn:SetPos(48, btnY) + saveBtn:SetSize(btnW, 25) + saveBtn:SetText("") + saveBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("СОХР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + saveBtn.DoClick = function() + local finalName = nameEntry:GetText() + if finalName == "" then finalName = defaultName end + local safeBGs = {} + for k, v in pairs(currentBodygroups) do safeBGs[tostring(k)] = v end + allPresets[model][tostring(i)] = { + name = finalName, + bgs = safeBGs, + skin = currentSkin + } + SavePresetsToServer() + surface.PlaySound("buttons/button15.wav") + end + + local delBtn = vgui.Create("DButton", slotPanel) + delBtn:SetPos(87, btnY) + delBtn:SetSize(btnW, 25) + delBtn:SetText("") + delBtn.Paint = function(s, w, h) + if not allPresets[model][tostring(i)] then + draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) + draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + return + end + local col = s:IsHovered() and C_WARN_H or C_WARN + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + delBtn.DoClick = function() + if allPresets[model][tostring(i)] or allPresets[model][i] then + allPresets[model][tostring(i)] = nil + allPresets[model][i] = nil + SavePresetsToServer() + nameEntry:SetText(defaultName) + surface.PlaySound("buttons/button15.wav") + end + end + + py = py + 95 + end + + local modelPanel = vgui.Create("DModelPanel", frame) + modelPanel:SetPos(midX, 60); modelPanel:SetSize(midW, fH - 140); modelPanel:SetModel(model) + modelPanel:Receiver("wardrobe_item", function(pnl, tbl, bDoDrop, command, x, y) + if bDoDrop then local droppedCell = tbl[1]; if IsValid(droppedCell) then if droppedCell.isBodygroup then currentBodygroups[droppedCell.bgIdx] = droppedCell.bgVal elseif droppedCell.isSkin then currentSkin = droppedCell.skinVal end; surface.PlaySound("UI/buttonclick.wav") end end + end) + + local oldPaint = modelPanel.Paint + modelPanel.Paint = function(s, w, h) + if bgMat and not bgMat:IsError() then surface.SetDrawColor(255, 255, 255, 255); surface.SetMaterial(bgMat); surface.DrawTexturedRect(0, 0, w, h) end + local mx, my = gui.MousePos(); local px, py = s:LocalToScreen(0, 0) + local isHoveredDrag = dragndrop.IsDragging() and (mx >= px and mx <= px + w and my >= py and my <= py + h) + if isHoveredDrag then surface.SetDrawColor(C_PRI); surface.DrawOutlinedRect(0, 0, w, h, 3); surface.SetDrawColor(C_GRAD); surface.DrawRect(0, 0, w, h) else surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1) end + oldPaint(s, w, h) + end + modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0), offset = Vector(0, 0, 0) }; modelPanel.curZoom = 60; modelPanel.curRot = Angle(0, 180, 0); modelPanel.curOffset = Vector(0, 0, 0) + modelPanel.LayoutEntity = function(s, ent) + if not s.bAnimSet then local seq = ent:LookupSequence("idle_subtle"); if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end; if seq > 0 then ent:SetSequence(seq) end; s.bAnimSet = true end + if (s.bDragging) then + local x, y = gui.MousePos(); local dx = x - s.lastX; local dy = y - s.lastY + if input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) then local right = s.curRot:Right(); local up = s.curRot:Up(); s.camAnims.offset = s.camAnims.offset - right * (dx * 0.15) + up * (dy * 0.15) elseif input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then s.camAnims.rot.yaw = s.camAnims.rot.yaw - dx * 0.8; s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch - dy * 0.8, -45, 45) end + s.lastX, s.lastY = x, y + end + local speed = FrameTime() * 10; s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom); s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot); s.curOffset = LerpVector(speed, s.curOffset, s.camAnims.offset) + local targetPos = ent:GetPos() + Vector(0, 0, 40) + s.curOffset; s:SetCamPos(targetPos - s.curRot:Forward() * s.curZoom); s:SetLookAng(s.curRot) + ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1); s.lastTick = RealTime() + end + + local function SetCameraAngle(angle) + if angle == "front" then modelPanel.camAnims.rot = Angle(0, 180, 0) elseif angle == "side" then modelPanel.camAnims.rot = Angle(0, 90, 0) elseif angle == "back" then modelPanel.camAnims.rot = Angle(0, 0, 0) end + modelPanel.camAnims.offset = Vector(0, 0, 0) + end + + modelPanel.OnMousePressed = function(s, code) if code == MOUSE_LEFT then local clickTime = SysTime(); if s.lastClickTime and (clickTime - s.lastClickTime) < 0.3 then s.camAnims.zoom = 60; s.camAnims.rot = Angle(0, 180, 0); s.camAnims.offset = Vector(0, 0, 0); s.lastClickTime = 0; return end; s.lastClickTime = clickTime; s.bDragging = true; s.lastX, s.lastY = gui.MousePos() end end + modelPanel.OnMouseReleased = function(s, code) if code == MOUSE_LEFT then s.bDragging = false end end + modelPanel.OnCursorExited = function(s) s.bDragging = false end + modelPanel.OnMouseWheeled = function(s, delta) s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 10, 120) end + timer.Simple(0.05, function() if IsValid(modelPanel) then SetCameraAngle("front") end end) + modelPanel.Think = function(s) local entity = s:GetEntity(); if IsValid(entity) then for idx, value in pairs(currentBodygroups) do if entity:GetBodygroup(idx) ~= value then entity:SetBodygroup(idx, value) end end; if entity:GetSkin() ~= currentSkin then entity:SetSkin(currentSkin) end end end + + local viewButtons = vgui.Create("DPanel", frame) + viewButtons:SetPos(midX, fH - 70); viewButtons:SetSize(midW, 50) + viewButtons.Paint = function(s, w, h) draw.RoundedBox(6, 0, 0, w, h, C_BG_M); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1) end + + local angles = {{"СПЕРЕДИ", "front"}, {"СБОКУ", "side"}, {"СЗАДИ", "back"}} + for k, v in ipairs(angles) do + local btn = vgui.Create("DButton", viewButtons) + btn.Paint = function(s, w, h) local col = s:IsHovered() and C_PRI_H or C_PRI; draw.RoundedBox(4, 0, 0, w, h, col); surface.SetDrawColor(C_BDR); surface.DrawOutlinedRect(0, 0, w, h, 1); draw.SimpleText(v[1], "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end + btn.DoClick = function() SetCameraAngle(v[2]) end + btn.PerformLayout = function(s, w, h) + local bs = (k == 2) and 20 or 10 + local bw = (viewButtons:GetWide() - 40) / 3 + btn:SetSize(bw, 30) + btn:SetPos(10 + (k-1)*(bw+10), 10) + end + btn:SetText("") + end + + local HoveredItemName = nil + local HoveredItemDesc = nil + + local tooltipPanel = vgui.Create("DPanel", frame) + tooltipPanel:SetSize(280, 100) + tooltipPanel:SetDrawOnTop(true) + tooltipPanel:SetMouseInputEnabled(false) + tooltipPanel:SetAlpha(0) + tooltipPanel.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, C_ARMA_TOOLTIP_BG) + surface.SetDrawColor(C_ARMA_SEL) + surface.DrawRect(0, 0, w, 3) + if HoveredItemName then draw.SimpleText(HoveredItemName, "DermaDefaultBold", 10, 12, C_ARMA_SEL, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) end + if HoveredItemDesc then draw.DrawText(HoveredItemDesc, "DermaDefault", 10, 35, C_ARMA_TEXT, TEXT_ALIGN_LEFT) end + end + tooltipPanel.Think = function(s) + if HoveredItemName then + s:SetAlpha(math.Clamp(s:GetAlpha() + FrameTime() * 1500, 0, 255)) + local mx, my = gui.MousePos() + local fx, fy = frame:GetPos() + s:SetPos(mx - fx + 15, my - fy + 15) + local _, lineCount = string.gsub(HoveredItemDesc or "", "\n", "") + s:SetTall(50 + (lineCount * 16)) + else + s:SetAlpha(math.Clamp(s:GetAlpha() - FrameTime() * 1500, 0, 255)) + end + end + + local settingsPanel = vgui.Create("DPanel", frame) + settingsPanel:SetPos(rightX, 60) + settingsPanel:SetSize(rightW, fH - 80) + settingsPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local settingsScroll = vgui.Create("DScrollPanel", settingsPanel) + settingsScroll:Dock(FILL) + settingsScroll:DockMargin(10, 10, 10, 55) + + local sbar = settingsScroll:GetVBar() + sbar:SetWide(4) + sbar.Paint = function() end + sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(100, 100, 100, 100)) end + sbar.btnUp.Paint = function() end + sbar.btnDown.Paint = function() end + + local baseCellSize = 108 + + local bgMainHeaderLabel = vgui.Create("DLabel", settingsScroll) + bgMainHeaderLabel:Dock(TOP) + bgMainHeaderLabel:DockMargin(22, 0, 30, 15) + bgMainHeaderLabel:SetTall(20) + bgMainHeaderLabel:SetText("РЕДАКТИРОВАНИЕ БОДИГРУПП") + bgMainHeaderLabel:SetFont("DermaDefaultBold") + bgMainHeaderLabel:SetTextColor(C_ARMA_SEL) + + if table.Count(bodygroups) > 0 then + local bgArray = {} + for idx, data in pairs(bodygroups) do table.insert(bgArray, data) end + table.sort(bgArray, function(a, b) return a.index < b.index end) + + for _, data in ipairs(bgArray) do + local idx = data.index + local bgName = string.lower(data.name) + + local allowedValues = {} + local blockedForModel = WARDROBE_BLOCKED_BGS[string.lower(model)] or {} + for i = 0, data.count - 1 do + local isBlocked = false + + if not (unlockedBodygroups[tostring(idx)] and unlockedBodygroups[tostring(idx)][tostring(i)]) then + for _, blockData in ipairs(blockedForModel) do + if blockData[1] == idx and blockData[2] == i then + if isfunction(blockData[3]) then + if not blockData[3](LocalPlayer()) then + isBlocked = true + end + else + isBlocked = true + end + break + end + end + end + + if not isBlocked then + table.insert(allowedValues, i) + end + end + + if #allowedValues == 0 then continue end + + local bgPanel = vgui.Create("DPanel", settingsScroll) + bgPanel:Dock(TOP) + bgPanel:DockMargin(10, 5, 30, 10) + bgPanel.Expanded = true + local panelExpandedHeight = 25 + baseCellSize + bgPanel.CurHeight = panelExpandedHeight + bgPanel:SetTall(panelExpandedHeight) + bgPanel.Paint = function(s, w, h) end + bgPanel.Think = function(s) + local targetHeight = s.Expanded and panelExpandedHeight or 25 + if s.CurHeight ~= targetHeight then + s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) + if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end + s:SetTall(math.Round(s.CurHeight)) + end + end + + local headerBtn = vgui.Create("DButton", bgPanel) + headerBtn:SetPos(22, 0) + headerBtn:SetSize(336, 25) + headerBtn:SetText("") + headerBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) + draw.SimpleText(string.upper(data.name), "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(bgPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + headerBtn.DoClick = function(s) + bgPanel.Expanded = not bgPanel.Expanded + surface.PlaySound("UI/buttonclick.wav") + end + + local carouselPanel = vgui.Create("DPanel", bgPanel) + carouselPanel:SetPos(22, 25) + carouselPanel:SetSize(336, baseCellSize) + carouselPanel.Paint = function() end + + local startScrollIdx = 0 + for k, v in ipairs(allowedValues) do + if v == (currentBodygroups[idx] or 0) then + startScrollIdx = k - 1 + break + end + end + + carouselPanel.TargetScroll = startScrollIdx + carouselPanel.CurScroll = carouselPanel.TargetScroll + + local leftBtn = vgui.Create("DButton", bgPanel) + leftBtn:SetPos(0, 25) + leftBtn:SetSize(16, baseCellSize) + leftBtn:SetText("") + leftBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local rightBtn = vgui.Create("DButton", bgPanel) + rightBtn:SetPos(364, 25) + rightBtn:SetSize(16, baseCellSize) + rightBtn:SetText("") + rightBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local maxScroll = math.max(0, #allowedValues - 1) + + leftBtn.DoClick = function() + carouselPanel.TargetScroll = math.max(0, carouselPanel.TargetScroll - 1) + surface.PlaySound("UI/buttonclick.wav") + end + + rightBtn.DoClick = function() + carouselPanel.TargetScroll = math.min(maxScroll, carouselPanel.TargetScroll + 1) + surface.PlaySound("UI/buttonclick.wav") + end + + local cells = {} + for scrollIdx, val in ipairs(allowedValues) do + local i = val + local cell = vgui.Create("DButton", carouselPanel) + cell:SetText("") + + cell.isBodygroup = true + cell.bgIdx = idx + cell.bgVal = i + cell:Droppable("wardrobe_item") + + cell.OnCursorEntered = function(s) + HoveredItemName = CUSTOM_NAMES_BG[bgName] and CUSTOM_NAMES_BG[bgName][i] or (string.upper(bgName) .. " #" .. i) + HoveredItemDesc = CUSTOM_DESC_BG[bgName] and CUSTOM_DESC_BG[bgName][i] or "Дополнительная экипировка.\n\n0.5kg" + end + cell.OnCursorExited = function(s) HoveredItemName = nil; HoveredItemDesc = nil end + + cell.DoClick = function(s) + currentBodygroups[idx] = i + carouselPanel.TargetScroll = scrollIdx - 1 + surface.PlaySound("UI/buttonclick.wav") + end + + cell.Paint = function(s, w, h) + local isSelected = (currentBodygroups[idx] or 0) == i + local bgColor = C_ARMA_SLOT + + if isSelected then bgColor = C_ARMA_SEL + elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end + + local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) + if s:IsDragging() then surface.SetDrawColor(255, 255, 255, 20); surface.DrawRect(0, 0, w, h); return end + + surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) + surface.DrawRect(0, 0, w, h) + + local iconData = CUSTOM_ICONS_BG[bgName] + local customIcon = iconData and (iconData[i] or iconData.default) + + if customIcon and not customIcon:IsError() then + surface.SetDrawColor(255, 255, 255, 255 * alphaMult) + surface.SetMaterial(customIcon) + surface.DrawTexturedRect(4, 4, w - 8, h - 8) + else + local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) + draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + surface.SetDrawColor(0, 0, 0, 120 * alphaMult) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + table.insert(cells, cell) + end + + carouselPanel.Think = function(s) + s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) + for i, cell in ipairs(cells) do + local index = i - 1 + local offset = index - s.CurScroll + local absOffset = math.abs(offset) + + cell.AbsOffset = absOffset + local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) + local size = baseCellSize * scale + local spacing = 65 + local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) + local y = (s:GetTall() / 2) - (size / 2) + + cell:SetSize(size, size) + cell:SetPos(x, y) + cell:SetZPos(100 - math.floor(absOffset * 10)) + end + end + end + end + + if #availableSkins > 1 then + local skinPanel = vgui.Create("DPanel", settingsScroll) + skinPanel:Dock(TOP) + skinPanel:DockMargin(10, 5, 30, 10) + skinPanel.Expanded = true + local panelExpandedHeight = 25 + baseCellSize + skinPanel.CurHeight = panelExpandedHeight + skinPanel:SetTall(panelExpandedHeight) + skinPanel.Paint = function(s, w, h) end + skinPanel.Think = function(s) + local targetHeight = s.Expanded and panelExpandedHeight or 25 + if s.CurHeight ~= targetHeight then + s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) + if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end + s:SetTall(math.Round(s.CurHeight)) + end + end + + local headerBtn = vgui.Create("DButton", skinPanel) + headerBtn:SetPos(22, 0) + headerBtn:SetSize(336, 25) + headerBtn:SetText("") + headerBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) + draw.SimpleText("ВАРИАНТ КАМУФЛЯЖА", "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(skinPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + headerBtn.DoClick = function(s) + skinPanel.Expanded = not skinPanel.Expanded + surface.PlaySound("UI/buttonclick.wav") + end + + local carouselPanel = vgui.Create("DPanel", skinPanel) + carouselPanel:SetPos(22, 25) + carouselPanel:SetSize(336, baseCellSize) + carouselPanel.Paint = function() end + carouselPanel.TargetScroll = currentSkin or 0 + carouselPanel.CurScroll = carouselPanel.TargetScroll + + local leftBtn = vgui.Create("DButton", skinPanel) + leftBtn:SetPos(0, 25); leftBtn:SetSize(16, baseCellSize); leftBtn:SetText("") + leftBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local rightBtn = vgui.Create("DButton", skinPanel) + rightBtn:SetPos(364, 25); rightBtn:SetSize(16, baseCellSize); rightBtn:SetText("") + rightBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_ARMA_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local maxScroll = math.max(0, #availableSkins - 1) + + leftBtn.DoClick = function() + carouselPanel.TargetScroll = math.max(0, carouselPanel.TargetScroll - 1) + surface.PlaySound("UI/buttonclick.wav") + end + + rightBtn.DoClick = function() + carouselPanel.TargetScroll = math.min(maxScroll, carouselPanel.TargetScroll + 1) + surface.PlaySound("UI/buttonclick.wav") + end + + local cells = {} + for i = 0, #availableSkins - 1 do + local cell = vgui.Create("DButton", carouselPanel) + cell:SetText("") + + cell.isSkin = true + cell.skinVal = i + cell:Droppable("wardrobe_item") + + cell.OnCursorEntered = function(s) HoveredItemName = CUSTOM_NAMES_SKIN[i] or ("Текстура #" .. i); HoveredItemDesc = CUSTOM_DESC_SKIN[i] or "Альтернативный паттерн экипировки." end + cell.OnCursorExited = function(s) HoveredItemName = nil; HoveredItemDesc = nil end + + cell.DoClick = function(s) + currentSkin = i + carouselPanel.TargetScroll = i + surface.PlaySound("UI/buttonclick.wav") + end + + cell.Paint = function(s, w, h) + local isSelected = currentSkin == i + local bgColor = C_ARMA_SLOT + + if isSelected then bgColor = C_ARMA_SEL elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end + + local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) + if s:IsDragging() then surface.SetDrawColor(255, 255, 255, 20); surface.DrawRect(0, 0, w, h); return end + + surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) + surface.DrawRect(0, 0, w, h) + + local customIcon = CUSTOM_ICONS_SKIN[i] or CUSTOM_ICONS_SKIN.default + if customIcon and not customIcon:IsError() then + surface.SetDrawColor(255, 255, 255, 255 * alphaMult) + surface.SetMaterial(customIcon) + surface.DrawTexturedRect(4, 4, w - 8, h - 8) + else + local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) + draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + surface.SetDrawColor(0, 0, 0, 120 * alphaMult) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + table.insert(cells, cell) + end + + carouselPanel.Think = function(s) + s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) + for i, cell in ipairs(cells) do + local index = i - 1 + local offset = index - s.CurScroll + local absOffset = math.abs(offset) + + cell.AbsOffset = absOffset + local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) + local size = baseCellSize * scale + local spacing = 65 + local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) + local y = (s:GetTall() / 2) - (size / 2) + + cell:SetSize(size, size) + cell:SetPos(x, y) + cell:SetZPos(100 - math.floor(absOffset * 10)) + end + end + end + + local applyBtn = vgui.Create("DButton", settingsPanel) + local wipeBtn = vgui.Create("DButton", settingsPanel) + + wipeBtn:SetSize(135, 35); wipeBtn:SetText("") + wipeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_WARN_H or C_WARN + draw.RoundedBox(8, 0, 0, w, h, C_BDR); draw.RoundedBox(8, 2, 2, w-4, h-4, col) + draw.SimpleText("СБРОСИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + wipeBtn.DoClick = function() net.Start("SandboxWardrobeApply"); net.WriteBool(true); net.SendToServer(); frame:Close() end + + applyBtn:SetSize(135, 35); applyBtn:SetText("") + applyBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(8, 0, 0, w, h, C_BDR); draw.RoundedBox(8, 2, 2, w-4, h-4, col); draw.RoundedBoxEx(8, 2, 2, w-4, h/2-2, C_GRAD, true, true, false, false) + draw.SimpleText("ПРИМЕНИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + applyBtn.DoClick = function() net.Start("SandboxWardrobeApply"); net.WriteBool(false); net.WriteTable(currentBodygroups); net.WriteUInt(currentSkin, 8); net.SendToServer(); frame:Close() end + + settingsPanel.PerformLayout = function(s, w, h) + applyBtn:SetPos(w - 135 - 10, h - 35 - 10) + wipeBtn:SetPos(w - 135*2 - 20, h - 35 - 10) + end +end) + +-- ===================================================================== +-- АДМИН ПАНЕЛЬ +-- ===================================================================== +net.Receive("SandboxWardrobeAdminOpen", function() + local unlockedData = net.ReadTable() + + local fW, fH = 800, 600 + local frame = vgui.Create("DFrame") + frame:SetSize(fW, fH) + frame:Center() + frame:SetTitle("") + frame:ShowCloseButton(false) + frame:MakePopup() + frame:SetBackgroundBlur(true) + frame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_GRAD) + surface.DrawRect(0, 0, w, 3) + draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false) + surface.SetDrawColor(C_BDR) + surface.DrawLine(0, 50, w, 50) + draw.SimpleText("АДМИН ПАНЕЛЬ (ДОСТУП)", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(C_ACC) + surface.DrawRect(20, 48, 30, 2) + surface.DrawRect(w-50, 48, 30, 2) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(frame:GetWide() - 40, 10) + closeBtn:SetSize(30, 30) + closeBtn:SetText("") + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ACC_H or C_ACC + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + closeBtn.DoClick = function() frame:Close() end + + local leftPanel = vgui.Create("DPanel", frame) + leftPanel:SetPos(20, 60) + leftPanel:SetSize(230, fH - 80) + leftPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local plyList = vgui.Create("DListView", leftPanel) + plyList:Dock(FILL) + plyList:DockMargin(10, 10, 10, 10) + plyList:AddColumn("Игрок") + plyList.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local function FormatLine(line) + for _, col in pairs(line.Columns) do + col:SetTextColor(C_WHITE) + col:SetFont("DermaDefaultBold") + end + line.Paint = function(s, w, h) + local col = C_BG_M + if s:IsSelected() then col = C_PRI + elseif s:IsHovered() then col = C_BG_L end + draw.RoundedBox(0, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + end + + local manualBtn = vgui.Create("DButton", leftPanel) + manualBtn:Dock(BOTTOM) + manualBtn:DockMargin(10, 0, 10, 10) + manualBtn:SetTall(30) + manualBtn:SetText("") + manualBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ДОБАВИТЬ ID", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + manualBtn.DoClick = function() + Derma_StringRequest("Оффлайн игрок", "SteamID64:", "", function(text) + if string.len(text) == 17 then + local line = plyList:AddLine("Оффлайн ("..text..")") + FormatLine(line) + line.sid = text + line.mdl = "models/cwz/characters/mason_pm.mdl" + end + end) + end + + local selectedSid = nil + + local rightPanel = vgui.Create("DPanel", frame) + rightPanel:SetPos(260, 60) + rightPanel:SetSize(fW - 280, fH - 80) + rightPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local topRow = vgui.Create("DPanel", rightPanel) + topRow:Dock(TOP) + topRow:SetTall(45) + topRow:DockMargin(10, 10, 10, 0) + topRow.Paint = function() end + + local modelEntry = vgui.Create("DTextEntry", topRow) + modelEntry:Dock(FILL) + modelEntry:DockMargin(0, 0, 10, 15) + modelEntry:SetPlaceholderText(" Путь к модели (напр. models/cwz/characters/mason_pm.mdl)") + modelEntry.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(C_WHITE, C_ACC, C_WHITE) + end + + local loadBtn = vgui.Create("DButton", topRow) + loadBtn:Dock(RIGHT) + loadBtn:SetWide(100) + loadBtn:DockMargin(0, 0, 0, 15) + loadBtn:SetText("") + loadBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ОБНОВИТЬ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local bgScroll = vgui.Create("DScrollPanel", rightPanel) + bgScroll:Dock(FILL) + bgScroll:DockMargin(10, 0, 10, 10) + + local sbar = bgScroll:GetVBar() + sbar:SetWide(4) + sbar.Paint = function() end + sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(100, 100, 100, 100)) end + sbar.btnUp.Paint = function() end + sbar.btnDown.Paint = function() end + + local function LoadModelBGs(modelSearch) + bgScroll:Clear() + if not modelSearch or modelSearch == "" then return end + + local sidData = unlockedData[selectedSid] or {} + local modelData = sidData[string.lower(modelSearch)] or {} + + local blockedForModel = WARDROBE_BLOCKED_BGS[string.lower(modelSearch)] + if not blockedForModel or table.IsEmpty(blockedForModel) then + local err = bgScroll:Add("DLabel") + err:SetText(" Для этой модели нет заблокированных бодигрупп в настройках.") + err:SetTextColor(C_WARN) + err:SetFont("DermaDefaultBold") + err:SizeToContents() + err:Dock(TOP) + return + end + + for _, blockData in ipairs(blockedForModel) do + local bg_id = blockData[1] + local bg_val = blockData[2] + local str_bg_id = tostring(bg_id) + local str_bg_val = tostring(bg_val) + local bg_name = blockData[4] or ("Бодигруппа " .. bg_id .. " Вариант " .. bg_val) + + local linePnl = vgui.Create("DPanel", bgScroll) + linePnl:Dock(TOP) + linePnl:SetTall(35) + linePnl:DockMargin(0, 5, 10, 5) + linePnl.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local lbl = vgui.Create("DLabel", linePnl) + lbl:Dock(LEFT) + lbl:DockMargin(10, 0, 0, 0) + lbl:SetWide(300) + lbl:SetText(bg_name) + lbl:SetFont("DermaDefaultBold") + lbl:SetTextColor(C_WHITE) + + local lockBtn = vgui.Create("DButton", linePnl) + lockBtn:Dock(RIGHT) + lockBtn:SetWide(100) + lockBtn:DockMargin(0, 5, 5, 5) + lockBtn:SetText("") + + local isUnlocked = (modelData[str_bg_id] and modelData[str_bg_id][str_bg_val]) and true or false + lockBtn.IsUnlocked = isUnlocked + + lockBtn.Paint = function(s, w, h) + local col = s.IsUnlocked and C_PRI or C_WARN + if s:IsHovered() then + col = s.IsUnlocked and C_PRI_H or C_WARN_H + end + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText(s.IsUnlocked and "ОТКРЫТ" or "ЗАКРЫТ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + lockBtn.DoClick = function(s) + s.IsUnlocked = not s.IsUnlocked + + unlockedData[selectedSid] = unlockedData[selectedSid] or {} + unlockedData[selectedSid][string.lower(modelSearch)] = unlockedData[selectedSid][string.lower(modelSearch)] or {} + unlockedData[selectedSid][string.lower(modelSearch)][str_bg_id] = unlockedData[selectedSid][string.lower(modelSearch)][str_bg_id] or {} + unlockedData[selectedSid][string.lower(modelSearch)][str_bg_id][str_bg_val] = s.IsUnlocked and true or nil + + net.Start("SandboxWardrobeAdminUpdate") + net.WriteString(selectedSid) + net.WriteString(modelSearch) + net.WriteUInt(bg_id, 8) + net.WriteUInt(bg_val, 8) + net.WriteBool(s.IsUnlocked) + net.SendToServer() + + surface.PlaySound("UI/buttonclick.wav") + end + end + end + + for _, ply in ipairs(player.GetAll()) do + local line = plyList:AddLine(ply:Nick()) + FormatLine(line) + line.sid = ply:SteamID64() + line.mdl = ply:GetModel() + end + + plyList.OnRowSelected = function(lst, index, pnl) + selectedSid = pnl.sid + modelEntry:SetText(pnl.mdl or "") + LoadModelBGs(pnl.mdl) + end + + loadBtn.DoClick = function() + if not selectedSid then return end + LoadModelBGs(modelEntry:GetText()) + end +end) + +-- ===================================================================== +-- ПАНЕЛЬ КОМАНДИРА +-- ===================================================================== + +local C_CMD = Color(40, 75, 120) +local C_CMD_H = Color(50, 90, 140) +local C_CMD_SEL = Color(70, 130, 180) + +net.Receive("SandboxWardrobeCommanderOpen", function() + local membersData = net.ReadTable() + + local scrW, scrH = ScrW(), ScrH() + local fW = math.Clamp(scrW * 0.65, 1000, 1600) + local fH = math.Clamp(scrH * 0.65, 650, 1000) + + local frame = vgui.Create("DFrame") + frame:SetSize(fW, fH) + frame:Center() + frame:SetTitle("") + frame:ShowCloseButton(false) + frame:MakePopup() + frame:SetBackgroundBlur(true) + frame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_GRAD) + surface.DrawRect(0, 0, w, 3) + draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false) + surface.SetDrawColor(C_BDR) + surface.DrawLine(0, 50, w, 50) + draw.SimpleText("ПАНЕЛЬ КОМАНДИРА", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(C_CMD) + surface.DrawRect(20, 48, 30, 2) + surface.DrawRect(w - 50, 48, 30, 2) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(frame:GetWide() - 40, 10) + closeBtn:SetSize(30, 30) + closeBtn:SetText("") + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ACC_H or C_ACC + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("X", "DermaLarge", w/2, h/2 - 2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + closeBtn.DoClick = function() frame:Close() end + + -- Предварительное объявление локальных функций для перестроения интерфейса + local selectedMember = nil + local cmdCurrentBodygroups = {} + local cmdCurrentSkin = 0 + local cmdModel = "" + local cmdBodygroups = {} + local cmdAvailableSkins = {} + + local RebuildSettings + local RebuildCommanderPresets + + -- === ЛЕВАЯ ПАНЕЛЬ: СПИСОК ПОДРАЗДЕЛЕНИЯ И СБОРКИ === + local leftW = 230 + local leftContainer = vgui.Create("DPanel", frame) + leftContainer:SetPos(20, 60) + leftContainer:SetSize(leftW, fH - 80) + leftContainer.Paint = function() end + + local squadPanel = vgui.Create("DPanel", leftContainer) + squadPanel:Dock(FILL) + squadPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local squadLabel = vgui.Create("DLabel", squadPanel) + squadLabel:Dock(TOP) + squadLabel:DockMargin(10, 10, 10, 5) + squadLabel:SetTall(20) + squadLabel:SetText("ПОДРАЗДЕЛЕНИЕ") + squadLabel:SetFont("DermaDefaultBold") + squadLabel:SetTextColor(C_CMD_SEL) + + local memberList = vgui.Create("DListView", squadPanel) + memberList:Dock(FILL) + memberList:DockMargin(10, 5, 10, 10) + memberList:AddColumn("Боец") + memberList.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local function FormatMemberLine(line) + for _, col in pairs(line.Columns) do + col:SetTextColor(C_WHITE) + col:SetFont("DermaDefaultBold") + end + line.Paint = function(s, w, h) + local col = C_BG_M + if s:IsSelected() then col = C_CMD + elseif s:IsHovered() then col = C_BG_L end + draw.RoundedBox(0, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + end + + if #membersData == 0 then + local emptyLbl = vgui.Create("DLabel", squadPanel) + emptyLbl:Dock(TOP) + emptyLbl:DockMargin(10, 10, 10, 0) + emptyLbl:SetTall(40) + emptyLbl:SetText("Нет бойцов\nв подразделении") + emptyLbl:SetFont("DermaDefault") + emptyLbl:SetTextColor(Color(120, 120, 120)) + emptyLbl:SetWrap(true) + end + + for _, data in ipairs(membersData) do + local line = memberList:AddLine(data.nick) + FormatMemberLine(line) + line.memberData = data + end + + -- === ПАНЕЛЬ СБОРОК КОМАНДИРА === + local presetsPanel = vgui.Create("DPanel", leftContainer) + presetsPanel:Dock(BOTTOM) + presetsPanel:DockMargin(0, 10, 0, 0) + presetsPanel.Expanded = false + presetsPanel.CurHeight = 35 + presetsPanel:SetTall(35) + presetsPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + presetsPanel.Think = function(s) + local maxH = fH - 80 - 150 -- Резервируем хотя бы 150px для списка бойцов + local targetHeight = s.Expanded and maxH or 35 + if s.CurHeight ~= targetHeight then + s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) + if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end + s:SetTall(math.Round(s.CurHeight)) + end + end + + local presetsHeader = vgui.Create("DButton", presetsPanel) + presetsHeader:Dock(TOP) + presetsHeader:SetTall(35) + presetsHeader:SetText("") + presetsHeader.Paint = function(s, w, h) + local col = s:IsHovered() and C_BG_L or C_BG_D + draw.RoundedBoxEx(6, 0, 0, w, h, col, true, true, not presetsPanel.Expanded, not presetsPanel.Expanded) + surface.SetDrawColor(C_CMD) + surface.DrawRect(0, 0, 3, h) + draw.SimpleText("СБОРКИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(presetsPanel.Expanded and "▼" or "◀", "DermaDefault", w - 15, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + presetsHeader.DoClick = function(s) presetsPanel.Expanded = not presetsPanel.Expanded; surface.PlaySound("UI/buttonclick.wav") end + + local presetsContent = vgui.Create("DScrollPanel", presetsPanel) + presetsContent:Dock(FILL) + + RebuildCommanderPresets = function() + presetsContent:Clear() + if not selectedMember or cmdModel == "" then return end + + local allPresets = {} + if file.Exists("wardrobe_presets.txt", "DATA") then allPresets = util.JSONToTable(file.Read("wardrobe_presets.txt", "DATA")) or {} end + if not allPresets[cmdModel] then allPresets[cmdModel] = {} end + + local py = 10 + for i = 1, 10 do + local slotPanel = vgui.Create("DPanel", presetsContent) + slotPanel:SetPos(10, py) + slotPanel:SetSize(200, 85) + slotPanel.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local pData = allPresets[cmdModel][i] or allPresets[cmdModel][tostring(i)] + local hasData = pData ~= nil + local defaultName = "Сборка " .. i + + local iconCell = vgui.Create("DPanel", slotPanel) + iconCell:SetPos(130, 12) + iconCell:SetSize(60, 60) + iconCell.Paint = function(s, w, h) + local curData = allPresets[cmdModel][tostring(i)] + local isFilled = curData ~= nil + + draw.RoundedBox(4, 0, 0, w, h, isFilled and C_ARMA_SLOT or C_ARMA_SLOT_EMPTY) + + if isFilled then + local mat = Material("wardrobe_ui/body.png", "noclamp smooth") + if mat and not mat:IsError() then + surface.SetDrawColor(255, 255, 255, 200) + surface.SetMaterial(mat) + surface.DrawTexturedRect(5, 5, w-10, h-10) + end + surface.SetDrawColor(C_CMD) + surface.DrawOutlinedRect(0, 0, w, h, 1) + else + draw.SimpleText("ПУСТО", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0,0,0,100)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + end + + local nameEntry = vgui.Create("DTextEntry", slotPanel) + nameEntry:SetPos(10, 12) + nameEntry:SetSize(110, 25) + nameEntry:SetText(hasData and (pData.name or defaultName) or defaultName) + nameEntry.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(s:IsEditing() and C_CMD or C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(C_WHITE, C_CMD, C_WHITE) + end + nameEntry.OnChange = function(s) + local curData = allPresets[cmdModel][tostring(i)] + if curData then + curData.name = s:GetText() + file.Write("wardrobe_presets.txt", util.TableToJSON(allPresets)) + end + end + + local btnW = 33 + local btnY = 47 + + local loadBtn = vgui.Create("DButton", slotPanel) + loadBtn:SetPos(10, btnY) + loadBtn:SetSize(btnW, 25) + loadBtn:SetText("") + loadBtn.Paint = function(s, w, h) + if not allPresets[cmdModel][tostring(i)] then + draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) + draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + return + end + local col = s:IsHovered() and C_CMD_H or C_CMD + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("ЗАГР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + loadBtn.DoClick = function() + local curData = allPresets[cmdModel][tostring(i)] + if not curData then return end + for idx, val in pairs(curData.bgs) do cmdCurrentBodygroups[tonumber(idx) or idx] = val end + if curData.skin then cmdCurrentSkin = curData.skin end + surface.PlaySound("buttons/button15.wav") + -- Обновляем правую панель чтобы отразить загруженный пресет + RebuildSettings() + end + + local saveBtn = vgui.Create("DButton", slotPanel) + saveBtn:SetPos(48, btnY) + saveBtn:SetSize(btnW, 25) + saveBtn:SetText("") + saveBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_CMD_H or C_CMD + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("СОХР", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + saveBtn.DoClick = function() + local finalName = nameEntry:GetText() + if finalName == "" then finalName = defaultName end + local safeBGs = {} + for k, v in pairs(cmdCurrentBodygroups) do safeBGs[tostring(k)] = v end + allPresets[cmdModel][tostring(i)] = { + name = finalName, + bgs = safeBGs, + skin = cmdCurrentSkin + } + file.Write("wardrobe_presets.txt", util.TableToJSON(allPresets)) + surface.PlaySound("buttons/button15.wav") + end + + local delBtn = vgui.Create("DButton", slotPanel) + delBtn:SetPos(87, btnY) + delBtn:SetSize(btnW, 25) + delBtn:SetText("") + delBtn.Paint = function(s, w, h) + if not allPresets[cmdModel][tostring(i)] then + draw.RoundedBox(4, 0, 0, w, h, Color(30, 30, 30, 150)) + draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + return + end + local col = s:IsHovered() and C_WARN_H or C_WARN + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("УДАЛ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + delBtn.DoClick = function() + if allPresets[cmdModel][tostring(i)] then + allPresets[cmdModel][tostring(i)] = nil + file.Write("wardrobe_presets.txt", util.TableToJSON(allPresets)) + nameEntry:SetText(defaultName) + surface.PlaySound("buttons/button15.wav") + end + end + + py = py + 95 + end + end + + -- === ЦЕНТРАЛЬНАЯ ПАНЕЛЬ: МОДЕЛЬ === + local rightW = math.Clamp(fW * 0.38, 350, 550) + local midW = fW - leftW - rightW - 80 + local midX = 40 + leftW + local rightX = midX + midW + 20 + + local modelPanel = vgui.Create("DModelPanel", frame) + modelPanel:SetPos(midX, 60) + modelPanel:SetSize(midW, fH - 140) + modelPanel.bModelReady = false + + local oldCmdPaint = modelPanel.Paint + modelPanel.Paint = function(s, w, h) + if bgMat and not bgMat:IsError() then + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(bgMat) + surface.DrawTexturedRect(0, 0, w, h) + end + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + if not selectedMember then + draw.SimpleText("Выберите бойца", "DermaLarge", w/2, h/2, Color(100, 100, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif s.bModelReady then + oldCmdPaint(s, w, h) + end + end + + modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0), offset = Vector(0, 0, 0) } + modelPanel.curZoom = 60 + modelPanel.curRot = Angle(0, 180, 0) + modelPanel.curOffset = Vector(0, 0, 0) + + modelPanel.LayoutEntity = function(s, ent) + if not s.bAnimSet then + local seq = ent:LookupSequence("idle_subtle") + if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end + if seq > 0 then ent:SetSequence(seq) end + s.bAnimSet = true + end + if s.bDragging then + local x, y = gui.MousePos() + local dx = x - s.lastX + local dy = y - s.lastY + if input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) then + local right = s.curRot:Right() + local up = s.curRot:Up() + s.camAnims.offset = s.camAnims.offset - right * (dx * 0.15) + up * (dy * 0.15) + elseif input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then + s.camAnims.rot.yaw = s.camAnims.rot.yaw - dx * 0.8 + s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch - dy * 0.8, -45, 45) + end + s.lastX, s.lastY = x, y + end + local speed = FrameTime() * 10 + s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom) + s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot) + s.curOffset = LerpVector(speed, s.curOffset, s.camAnims.offset) + local targetPos = ent:GetPos() + Vector(0, 0, 40) + s.curOffset + s:SetCamPos(targetPos - s.curRot:Forward() * s.curZoom) + s:SetLookAng(s.curRot) + ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1) + s.lastTick = RealTime() + end + + modelPanel.OnMousePressed = function(s, code) + if code == MOUSE_LEFT then + local clickTime = SysTime() + if s.lastClickTime and (clickTime - s.lastClickTime) < 0.3 then + s.camAnims.zoom = 60 + s.camAnims.rot = Angle(0, 180, 0) + s.camAnims.offset = Vector(0, 0, 0) + s.lastClickTime = 0 + return + end + s.lastClickTime = clickTime + s.bDragging = true + s.lastX, s.lastY = gui.MousePos() + end + end + modelPanel.OnMouseReleased = function(s, code) if code == MOUSE_LEFT then s.bDragging = false end end + modelPanel.OnCursorExited = function(s) s.bDragging = false end + modelPanel.OnMouseWheeled = function(s, delta) s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 10, 120) end + + modelPanel.Think = function(s) + local entity = s:GetEntity() + if IsValid(entity) then + for idx, value in pairs(cmdCurrentBodygroups) do + if entity:GetBodygroup(idx) ~= value then entity:SetBodygroup(idx, value) end + end + if entity:GetSkin() ~= cmdCurrentSkin then entity:SetSkin(cmdCurrentSkin) end + end + end + + -- === КНОПКИ РАКУРСОВ === + local viewButtons = vgui.Create("DPanel", frame) + viewButtons:SetPos(midX, fH - 70) + viewButtons:SetSize(midW, 50) + viewButtons.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local angles = {{"СПЕРЕДИ", "front"}, {"СБОКУ", "side"}, {"СЗАДИ", "back"}} + for k, v in ipairs(angles) do + local btn = vgui.Create("DButton", viewButtons) + btn:SetText("") + btn.Paint = function(s, w, h) + local col = s:IsHovered() and C_CMD_H or C_CMD + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText(v[1], "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + btn.DoClick = function() + if v[2] == "front" then modelPanel.camAnims.rot = Angle(0, 180, 0) + elseif v[2] == "side" then modelPanel.camAnims.rot = Angle(0, 90, 0) + elseif v[2] == "back" then modelPanel.camAnims.rot = Angle(0, 0, 0) end + modelPanel.camAnims.offset = Vector(0, 0, 0) + end + btn.PerformLayout = function(s, w, h) + local bw = (viewButtons:GetWide() - 40) / 3 + btn:SetSize(bw, 30) + btn:SetPos(10 + (k - 1) * (bw + 10), 10) + end + end + + -- === ПРАВАЯ ПАНЕЛЬ: НАСТРОЙКИ БОДИГРУПП И СКИНОВ === + local settingsPanel = vgui.Create("DPanel", frame) + settingsPanel:SetPos(rightX, 60) + settingsPanel:SetSize(rightW, fH - 80) + settingsPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local settingsScroll = vgui.Create("DScrollPanel", settingsPanel) + settingsScroll:Dock(FILL) + settingsScroll:DockMargin(10, 10, 10, 55) + + local sbar = settingsScroll:GetVBar() + sbar:SetWide(4) + sbar.Paint = function() end + sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, Color(100, 100, 100, 100)) end + sbar.btnUp.Paint = function() end + sbar.btnDown.Paint = function() end + + local baseCellSize = 108 + + RebuildSettings = function() + settingsScroll:Clear() + + if not selectedMember then + local lbl = vgui.Create("DLabel", settingsScroll) + lbl:Dock(TOP) + lbl:DockMargin(10, 20, 10, 0) + lbl:SetTall(30) + lbl:SetText("Выберите бойца из списка слева") + lbl:SetFont("DermaDefaultBold") + lbl:SetTextColor(Color(120, 120, 120)) + return + end + + local headerLabel = vgui.Create("DLabel", settingsScroll) + headerLabel:Dock(TOP) + headerLabel:DockMargin(22, 0, 30, 15) + headerLabel:SetTall(20) + headerLabel:SetText("ЭКИПИРОВКА: " .. string.upper(selectedMember.nick)) + headerLabel:SetFont("DermaDefaultBold") + headerLabel:SetTextColor(C_CMD_SEL) + + -- === БОДИГРУППЫ === + if table.Count(cmdBodygroups) > 0 then + local bgArray = {} + for idx, data in pairs(cmdBodygroups) do table.insert(bgArray, data) end + table.sort(bgArray, function(a, b) return a.index < b.index end) + + for _, data in ipairs(bgArray) do + local idx = data.index + local bgName = string.lower(data.name) + + local bgPanel = vgui.Create("DPanel", settingsScroll) + bgPanel:Dock(TOP) + bgPanel:DockMargin(10, 5, 30, 10) + bgPanel.Expanded = true + local panelExpandedHeight = 25 + baseCellSize + bgPanel.CurHeight = panelExpandedHeight + bgPanel:SetTall(panelExpandedHeight) + bgPanel.Paint = function() end + bgPanel.Think = function(s) + local targetHeight = s.Expanded and panelExpandedHeight or 25 + if s.CurHeight ~= targetHeight then + s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) + if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end + s:SetTall(math.Round(s.CurHeight)) + end + end + + local headerBtn = vgui.Create("DButton", bgPanel) + headerBtn:SetPos(22, 0) + headerBtn:SetSize(336, 25) + headerBtn:SetText("") + headerBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) + draw.SimpleText(string.upper(data.name), "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(bgPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + headerBtn.DoClick = function() bgPanel.Expanded = not bgPanel.Expanded; surface.PlaySound("UI/buttonclick.wav") end + + local carouselPanel = vgui.Create("DPanel", bgPanel) + carouselPanel:SetPos(22, 25) + carouselPanel:SetSize(336, baseCellSize) + carouselPanel.Paint = function() end + + local startIdx = 0 + for k2 = 0, data.count - 1 do + if k2 == (cmdCurrentBodygroups[idx] or 0) then startIdx = k2; break end + end + carouselPanel.TargetScroll = startIdx + carouselPanel.CurScroll = startIdx + + local leftArrow = vgui.Create("DButton", bgPanel) + leftArrow:SetPos(0, 25); leftArrow:SetSize(16, baseCellSize); leftArrow:SetText("") + leftArrow.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local rightArrow = vgui.Create("DButton", bgPanel) + rightArrow:SetPos(364, 25); rightArrow:SetSize(16, baseCellSize); rightArrow:SetText("") + rightArrow.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local maxScroll = math.max(0, data.count - 1) + leftArrow.DoClick = function() carouselPanel.TargetScroll = math.max(0, carouselPanel.TargetScroll - 1); surface.PlaySound("UI/buttonclick.wav") end + rightArrow.DoClick = function() carouselPanel.TargetScroll = math.min(maxScroll, carouselPanel.TargetScroll + 1); surface.PlaySound("UI/buttonclick.wav") end + + local cells = {} + for i = 0, data.count - 1 do + local cell = vgui.Create("DButton", carouselPanel) + cell:SetText("") + cell.DoClick = function() + cmdCurrentBodygroups[idx] = i + carouselPanel.TargetScroll = i + surface.PlaySound("UI/buttonclick.wav") + end + cell.Paint = function(s, w, h) + local isSelected = (cmdCurrentBodygroups[idx] or 0) == i + local bgColor = C_ARMA_SLOT + if isSelected then bgColor = C_CMD_SEL + elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end + local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) + surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) + surface.DrawRect(0, 0, w, h) + + local iconData = CUSTOM_ICONS_BG[bgName] + local customIcon = iconData and (iconData[i] or iconData.default) + if customIcon and not customIcon:IsError() then + surface.SetDrawColor(255, 255, 255, 255 * alphaMult) + surface.SetMaterial(customIcon) + surface.DrawTexturedRect(4, 4, w - 8, h - 8) + else + local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) + draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + surface.SetDrawColor(0, 0, 0, 120 * alphaMult) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + table.insert(cells, cell) + end + + carouselPanel.Think = function(s) + s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) + for ci, cell in ipairs(cells) do + local index = ci - 1 + local offset = index - s.CurScroll + local absOffset = math.abs(offset) + cell.AbsOffset = absOffset + local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) + local size = baseCellSize * scale + local spacing = 65 + local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) + local y = (s:GetTall() / 2) - (size / 2) + cell:SetSize(size, size) + cell:SetPos(x, y) + cell:SetZPos(100 - math.floor(absOffset * 10)) + end + end + end + end + + -- === СКИНЫ === + if #cmdAvailableSkins > 1 then + local skinPanel = vgui.Create("DPanel", settingsScroll) + skinPanel:Dock(TOP) + skinPanel:DockMargin(10, 5, 30, 10) + skinPanel.Expanded = true + local panelExpandedHeight = 25 + baseCellSize + skinPanel.CurHeight = panelExpandedHeight + skinPanel:SetTall(panelExpandedHeight) + skinPanel.Paint = function() end + skinPanel.Think = function(s) + local targetHeight = s.Expanded and panelExpandedHeight or 25 + if s.CurHeight ~= targetHeight then + s.CurHeight = Lerp(FrameTime() * 15, s.CurHeight, targetHeight) + if math.abs(s.CurHeight - targetHeight) < 0.5 then s.CurHeight = targetHeight end + s:SetTall(math.Round(s.CurHeight)) + end + end + + local skinHeader = vgui.Create("DButton", skinPanel) + skinHeader:SetPos(22, 0); skinHeader:SetSize(336, 25); skinHeader:SetText("") + skinHeader.Paint = function(s, w, h) + local col = s:IsHovered() and Color(255, 255, 255) or Color(200, 200, 200) + draw.SimpleText("ВАРИАНТ КАМУФЛЯЖА", "DermaDefaultBold", 0, h/2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(skinPanel.Expanded and "▼" or "◀", "DermaDefault", w, h/2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + skinHeader.DoClick = function() skinPanel.Expanded = not skinPanel.Expanded; surface.PlaySound("UI/buttonclick.wav") end + + local skinCarousel = vgui.Create("DPanel", skinPanel) + skinCarousel:SetPos(22, 25); skinCarousel:SetSize(336, baseCellSize) + skinCarousel.Paint = function() end + skinCarousel.TargetScroll = cmdCurrentSkin or 0 + skinCarousel.CurScroll = skinCarousel.TargetScroll + + -- ИСПРАВЛЕНА ОПЕЧАТКА ТУТ: + local skinLeft = vgui.Create("DButton", skinPanel) + skinLeft:SetPos(0, 25); skinLeft:SetSize(16, baseCellSize); skinLeft:SetText("") + skinLeft.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText("<", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local skinRight = vgui.Create("DButton", skinPanel) + skinRight:SetPos(364, 25); skinRight:SetSize(16, baseCellSize); skinRight:SetText("") + skinRight.Paint = function(s, w, h) + local col = s:IsHovered() and C_ARMA_SLOT_HOVER or C_ARMA_SLOT_EMPTY + draw.RoundedBox(0, 0, 0, w, h, col) + draw.SimpleText(">", "DermaDefaultBold", w/2, h/2, s:IsHovered() and C_CMD_SEL or Color(150, 150, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(Color(0, 0, 0, 120)); surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local skinMaxScroll = math.max(0, #cmdAvailableSkins - 1) + skinLeft.DoClick = function() skinCarousel.TargetScroll = math.max(0, skinCarousel.TargetScroll - 1); surface.PlaySound("UI/buttonclick.wav") end + skinRight.DoClick = function() skinCarousel.TargetScroll = math.min(skinMaxScroll, skinCarousel.TargetScroll + 1); surface.PlaySound("UI/buttonclick.wav") end + + local skinCells = {} + for i = 0, #cmdAvailableSkins - 1 do + local cell = vgui.Create("DButton", skinCarousel) + cell:SetText("") + cell.DoClick = function() + cmdCurrentSkin = i + skinCarousel.TargetScroll = i + surface.PlaySound("UI/buttonclick.wav") + end + cell.Paint = function(s, w, h) + local isSelected = cmdCurrentSkin == i + local bgColor = C_ARMA_SLOT + if isSelected then bgColor = C_CMD_SEL + elseif s:IsHovered() then bgColor = C_ARMA_SLOT_HOVER end + local alphaMult = math.Clamp(1 - (s.AbsOffset or 0) * 0.35, 0.2, 1) + surface.SetDrawColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a * alphaMult) + surface.DrawRect(0, 0, w, h) + local txtColor = isSelected and Color(40, 40, 40, 255 * alphaMult) or Color(150, 150, 150, 255 * alphaMult) + draw.SimpleText(tostring(i), "DermaLarge", w/2, h/2, txtColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(0, 0, 0, 120 * alphaMult) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + table.insert(skinCells, cell) + end + + skinCarousel.Think = function(s) + s.CurScroll = Lerp(FrameTime() * 12, s.CurScroll, s.TargetScroll) + for ci, cell in ipairs(skinCells) do + local index = ci - 1 + local offset = index - s.CurScroll + local absOffset = math.abs(offset) + cell.AbsOffset = absOffset + local scale = math.Clamp(1 - absOffset * 0.25, 0.5, 1) + local size = baseCellSize * scale + local spacing = 65 + local x = (s:GetWide() / 2) - (size / 2) + (offset * spacing) + local y = (s:GetTall() / 2) - (size / 2) + cell:SetSize(size, size) + cell:SetPos(x, y) + cell:SetZPos(100 - math.floor(absOffset * 10)) + end + end + end + end + + -- === КНОПКИ ПРИМЕНИТЬ / СБРОС === + local applyBtn = vgui.Create("DButton", settingsPanel) + local wipeBtn = vgui.Create("DButton", settingsPanel) + + wipeBtn:SetSize(135, 35); wipeBtn:SetText("") + wipeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_WARN_H or C_WARN + draw.RoundedBox(8, 0, 0, w, h, C_BDR) + draw.RoundedBox(8, 2, 2, w - 4, h - 4, col) + draw.SimpleText("СБРОСИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + wipeBtn.DoClick = function() + if not selectedMember then return end + net.Start("SandboxWardrobeCommanderApply") + net.WriteUInt(selectedMember.entIndex, 16) + net.WriteBool(true) + net.SendToServer() + surface.PlaySound("UI/buttonclick.wav") + end + + applyBtn:SetSize(135, 35); applyBtn:SetText("") + applyBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_CMD_H or C_CMD + draw.RoundedBox(8, 0, 0, w, h, C_BDR) + draw.RoundedBox(8, 2, 2, w - 4, h - 4, col) + draw.RoundedBoxEx(8, 2, 2, w - 4, h/2 - 2, C_GRAD, true, true, false, false) + draw.SimpleText("ПРИМЕНИТЬ", "DermaDefaultBold", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + applyBtn.DoClick = function() + if not selectedMember then return end + net.Start("SandboxWardrobeCommanderApply") + net.WriteUInt(selectedMember.entIndex, 16) + net.WriteBool(false) + net.WriteTable(cmdCurrentBodygroups) + net.WriteUInt(cmdCurrentSkin, 8) + net.SendToServer() + surface.PlaySound("UI/buttonclick.wav") + end + + settingsPanel.PerformLayout = function(s, w, h) + applyBtn:SetPos(w - 135 - 10, h - 35 - 10) + wipeBtn:SetPos(w - 135 * 2 - 20, h - 35 - 10) + end + + -- Инициальный rebuild + RebuildSettings() + RebuildCommanderPresets() + + -- === Выбор бойца === + memberList.OnRowSelected = function(lst, index, pnl) + selectedMember = pnl.memberData + if not selectedMember then return end + + cmdModel = selectedMember.model + cmdCurrentBodygroups = {} + cmdCurrentSkin = 0 + cmdBodygroups = {} + cmdAvailableSkins = {} + + -- Обновляем модель + modelPanel:SetModel(cmdModel) + modelPanel.bAnimSet = false + modelPanel.bModelReady = false + modelPanel.camAnims.zoom = 60 + modelPanel.camAnims.rot = Angle(0, 180, 0) + modelPanel.camAnims.offset = Vector(0, 0, 0) + + -- Задержка нужна чтобы DModelPanel успел обновить Entity после SetModel + timer.Simple(0.1, function() + if not IsValid(modelPanel) then return end + modelPanel.bModelReady = true + + local ent = modelPanel:GetEntity() + if IsValid(ent) then + for i = 0, ent:GetNumBodyGroups() - 1 do + local name = ent:GetBodygroupName(i) + local count = ent:GetBodygroupCount(i) + if count > 1 then + cmdBodygroups[i] = { name = name, count = count, index = i } + cmdCurrentBodygroups[i] = 0 + end + end + + local skinCount = ent:SkinCount() or 0 + for i = 0, skinCount - 1 do + table.insert(cmdAvailableSkins, i) + end + end + + -- Пробуем получить текущие бодигруппы от серверного игрока + local target = Entity(selectedMember.entIndex) + if IsValid(target) then + for idx in pairs(cmdBodygroups) do + cmdCurrentBodygroups[idx] = target:GetBodygroup(idx) + end + cmdCurrentSkin = target:GetSkin() + end + + RebuildSettings() + RebuildCommanderPresets() + end) + end +end) diff --git a/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/init.lua b/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/init.lua new file mode 100644 index 0000000..4aaf15a --- /dev/null +++ b/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/init.lua @@ -0,0 +1,351 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +resource.AddFile("materials/wardrobe_ui/body.png") +resource.AddFile("materials/wardrobe_ui/fon.png") + +util.AddNetworkString("SandboxWardrobeOpen") +util.AddNetworkString("SandboxWardrobeApply") +util.AddNetworkString("SandboxWardrobeAdminOpen") +util.AddNetworkString("SandboxWardrobeAdminUpdate") +util.AddNetworkString("SandboxWardrobeCommanderOpen") +util.AddNetworkString("SandboxWardrobeCommanderApply") +util.AddNetworkString("SandboxWardrobeSavePreset") + +if not file.Exists("wardrobe_presets", "DATA") then + file.CreateDir("wardrobe_presets") +end + +net.Receive("SandboxWardrobeSavePreset", function(len, ply) + if not IsValid(ply) then return end + local presets = net.ReadTable() + local steamID64 = ply:SteamID64() + if steamID64 then + local json = util.TableToJSON(presets, true) + if json then + file.Write("wardrobe_presets/" .. steamID64 .. ".txt", json) + end + end +end) + +-- ===================================================================== +-- ПСЕВДО-ФУНКЦИИ: Замените на свои реальные проверки! +-- ===================================================================== + +-- Проверяет, является ли игрок командиром подразделения. +-- Замените тело функции на свою логику, например: +-- return ply:GetNWBool("IsSquadLeader", false) +-- return ply:Team() == TEAM_COMMANDER +-- return ply.isCommander == true +local function IsCommander(ply) + -- ЗАГЛУШКА: Сейчас возвращает true для суперадминов. + -- Замените на реальную проверку! + return IsValid(ply) and ply:IsSuperAdmin() +end + +-- Возвращает таблицу игроков, которые находятся в подразделении этого командира. +-- Замените тело функции на свою логику, например: +-- return GetSquadByLeader(ply) +-- return team.GetPlayers(ply:Team()) +-- return ply:GetSquadMembers() +local function GetSquadMembers(commander) + -- ЗАГЛУШКА: Сейчас возвращает всех игроков на сервере кроме самого командира. + -- Замените на реальную логику получения списка подразделения! + local members = {} + for _, ply in ipairs(player.GetAll()) do + if ply ~= commander then + table.insert(members, ply) + end + end + return members +end + +-- ===================================================================== +-- СИСТЕМА РАЗБЛОКИРОВКИ БОДИГРУПП (АДМИН) +-- ===================================================================== + +local function SanitizeData(tbl, depth) + depth = depth or 1 + local out = {} + for k, v in pairs(tbl) do + local keyStr = tostring(k) + if depth == 1 then + if type(k) == "number" and k > 1e15 then continue end + if type(k) == "string" and string.find(keyStr, "e%+") then continue end + end + if type(v) == "table" then + out[keyStr] = SanitizeData(v, depth + 1) + else + out[keyStr] = v + end + end + return out +end + +function LoadWardrobeUnlocked() + if file.Exists("wardrobe_unlocked.txt", "DATA") then + local content = file.Read("wardrobe_unlocked.txt", "DATA") + local parsed = util.JSONToTable(content, true, true) + if parsed then + WARDROBE_UNLOCKED = SanitizeData(parsed) + else + WARDROBE_UNLOCKED = {} + end + else + WARDROBE_UNLOCKED = {} + end +end +LoadWardrobeUnlocked() + +function SaveWardrobeUnlocked() + local json = util.TableToJSON(WARDROBE_UNLOCKED, true) + if json then + file.Write("wardrobe_unlocked.txt", json) + end +end + +-- ===================================================================== +-- АДМИН ПАНЕЛЬ +-- ===================================================================== + +concommand.Add("wardrobe_admin", function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then + ply:ChatPrint("У вас нет прав (нужна группа superadmin).") + return + end + net.Start("SandboxWardrobeAdminOpen") + net.WriteTable(WARDROBE_UNLOCKED) + net.Send(ply) +end) + +net.Receive("SandboxWardrobeAdminUpdate", function(len, ply) + if not IsValid(ply) or not ply:IsSuperAdmin() then return end + + local sid64 = net.ReadString() + local model = string.lower(net.ReadString()) + local bg_id = tostring(net.ReadUInt(8)) + local bg_val = tostring(net.ReadUInt(8)) + local state = net.ReadBool() + + WARDROBE_UNLOCKED[sid64] = WARDROBE_UNLOCKED[sid64] or {} + WARDROBE_UNLOCKED[sid64][model] = WARDROBE_UNLOCKED[sid64][model] or {} + WARDROBE_UNLOCKED[sid64][model][bg_id] = WARDROBE_UNLOCKED[sid64][model][bg_id] or {} + + if state then + WARDROBE_UNLOCKED[sid64][model][bg_id][bg_val] = true + else + WARDROBE_UNLOCKED[sid64][model][bg_id][bg_val] = nil + if table.IsEmpty(WARDROBE_UNLOCKED[sid64][model][bg_id]) then WARDROBE_UNLOCKED[sid64][model][bg_id] = nil end + if table.IsEmpty(WARDROBE_UNLOCKED[sid64][model]) then WARDROBE_UNLOCKED[sid64][model] = nil end + if table.IsEmpty(WARDROBE_UNLOCKED[sid64]) then WARDROBE_UNLOCKED[sid64] = nil end + end + + SaveWardrobeUnlocked() +end) + +-- ===================================================================== +-- ПАНЕЛЬ КОМАНДИРА +-- ===================================================================== + +concommand.Add("wardrobe_commander", function(ply, cmd, args) + if not IsValid(ply) then return end + if not IsCommander(ply) then + ply:ChatPrint("Вы не являетесь командиром подразделения.") + return + end + + local members = GetSquadMembers(ply) + local membersData = {} + for _, member in ipairs(members) do + if IsValid(member) then + table.insert(membersData, { + nick = member:Nick(), + sid64 = member:SteamID64(), + model = member:GetModel(), + entIndex = member:EntIndex(), + }) + end + end + + net.Start("SandboxWardrobeCommanderOpen") + net.WriteTable(membersData) + net.Send(ply) +end) + +net.Receive("SandboxWardrobeCommanderApply", function(len, ply) + if not IsValid(ply) then return end + if not IsCommander(ply) then return end + + local targetEntIndex = net.ReadUInt(16) + local isWipe = net.ReadBool() + + local target = Entity(targetEntIndex) + if not IsValid(target) or not target:IsPlayer() then + ply:ChatPrint("Целевой игрок не найден.") + return + end + + -- Проверяем что целевой игрок находится в подразделении командира + local members = GetSquadMembers(ply) + local isMember = false + for _, member in ipairs(members) do + if member == target then + isMember = true + break + end + end + + if not isMember then + ply:ChatPrint("Этот игрок не в вашем подразделении.") + return + end + + if isWipe then + target:SetSkin(0) + local bgs = target:GetBodyGroups() + for _, bg in ipairs(bgs) do + target:SetBodygroup(bg.id, 0) + end + ply:ChatPrint("Сброшена экипировка для: " .. target:Nick()) + return + end + + local bodygroups = net.ReadTable() + local skin = net.ReadUInt(8) + + for idx, value in pairs(bodygroups) do + target:SetBodygroup(idx, value) + end + target:SetSkin(skin) + + ply:ChatPrint("Экипировка применена для: " .. target:Nick()) +end) + +-- ===================================================================== +-- ЛОГИКА ЭНТИТИ ГАРДЕРОБА +-- ===================================================================== + +function ENT:Initialize() + self:SetModel("models/props_wasteland/controlroom_storagecloset001a.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then phys:Wake() end + + self.playerCooldowns = {} +end + +function ENT:GetModelBodygroups(model) + local tempEnt = ents.Create("prop_dynamic") + if not IsValid(tempEnt) then return {} end + tempEnt:SetModel(model) + tempEnt:Spawn() + + local bodygroups = {} + for i = 0, tempEnt:GetNumBodyGroups() - 1 do + local name = tempEnt:GetBodygroupName(i) + local count = tempEnt:GetBodygroupCount(i) + if count > 1 then + bodygroups[i] = {name = name, count = count, index = i} + end + end + + tempEnt:Remove() + return bodygroups +end + +function ENT:GetModelSkinCount(model) + local tempEnt = ents.Create("prop_dynamic") + if not IsValid(tempEnt) then return 0 end + tempEnt:SetModel(model) + tempEnt:Spawn() + + local skinCount = tempEnt:SkinCount() or 0 + tempEnt:Remove() + return skinCount +end + +function ENT:Use(activator) + if not IsValid(activator) or not activator:IsPlayer() then return end + + local steamID = activator:SteamID() + local now = CurTime() + if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 2 then return end + + local model = activator:GetModel() + if not model then return end + + local bodygroups = self:GetModelBodygroups(model) + local skinCount = self:GetModelSkinCount(model) + + local availableBodygroups = {} + for idx, data in pairs(bodygroups) do + availableBodygroups[idx] = data + end + + local availableSkins = {} + for i = 0, skinCount - 1 do + table.insert(availableSkins, i) + end + + local currentBodygroups = {} + for idx in pairs(availableBodygroups) do + currentBodygroups[idx] = activator:GetBodygroup(idx) + end + + local currentSkin = activator:GetSkin() + + local steamID64 = activator:SteamID64() + local unlocked = {} + if steamID64 and WARDROBE_UNLOCKED[steamID64] and WARDROBE_UNLOCKED[steamID64][string.lower(model)] then + unlocked = WARDROBE_UNLOCKED[steamID64][string.lower(model)] + end + + local presets = {} + if steamID64 and file.Exists("wardrobe_presets/" .. steamID64 .. ".txt", "DATA") then + local content = file.Read("wardrobe_presets/" .. steamID64 .. ".txt", "DATA") + if content and content ~= "" then + presets = util.JSONToTable(content) or {} + end + end + + self.playerCooldowns[steamID] = now + + net.Start("SandboxWardrobeOpen") + net.WriteString(model) + net.WriteTable(availableBodygroups) + net.WriteTable(availableSkins) + net.WriteTable(currentBodygroups) + net.WriteUInt(currentSkin, 8) + net.WriteTable(unlocked) + net.WriteTable(presets) + net.Send(activator) +end + +net.Receive("SandboxWardrobeApply", function(len, client) + if not IsValid(client) then return end + + local isWipe = net.ReadBool() + + if isWipe then + client:SetSkin(0) + local bgs = client:GetBodyGroups() + for _, bg in ipairs(bgs) do + client:SetBodygroup(bg.id, 0) + end + return + end + + local bodygroups = net.ReadTable() + local skin = net.ReadUInt(8) + + for idx, value in pairs(bodygroups) do + client:SetBodygroup(idx, value) + end + + client:SetSkin(skin) +end) diff --git a/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/shared.lua b/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/shared.lua new file mode 100644 index 0000000..e1f77e5 --- /dev/null +++ b/garrysmod/addons/shkaf/lua/entities/wardrobe_ent/shared.lua @@ -0,0 +1,30 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Гардероб" +ENT.Author = "Server" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.Category = "Sandbox Арсенал" + +-- === НАСТРОЙКИ БЛОКИРОВКИ БОДИГРУПП === +-- Формат: ["путь/к/модели.mdl"] = { {id_бодигруппа, id_вариант, function(ply) return true end, "Название для админ панели"}, ... } +-- Внимание: путь к модели должен быть в нижнем регистре. +-- Если у бодигруппы указана функция, то она станет доступна, когда функция вернет true. Если функции нет — заблокирована для всех. +WARDROBE_BLOCKED_BGS = { + ["models/cwz/characters/mason_pm.mdl"] = { + {2, 1, function(ply) return false end, "Шлем (Вариант 1)"}, -- Пример: блокируем вариант 1 у бодигруппы 2 + }, +} + +-- ===================================================================== +-- ПСЕВДО-ФУНКЦИЯ: Является ли игрок командиром (для клиентской стороны). +-- Используется для показа кнопки "КОМАНДИР" в интерфейсе гардероба. +-- Замените тело функции на свою проверку, например: +-- return ply:GetNWBool("IsSquadLeader", false) +-- return ply:Team() == TEAM_COMMANDER +-- ===================================================================== +function IsWardrobeCommander(ply) + -- ЗАГЛУШКА: Сейчас возвращает true для суперадминов. + -- Замените на реальную проверку! + return IsValid(ply) and ply:IsSuperAdmin() +end diff --git a/garrysmod/addons/shkaf/materials/wardrobe_ui/body.png b/garrysmod/addons/shkaf/materials/wardrobe_ui/body.png new file mode 100644 index 0000000..b5c9eca Binary files /dev/null and b/garrysmod/addons/shkaf/materials/wardrobe_ui/body.png differ diff --git a/garrysmod/addons/shkaf/materials/wardrobe_ui/fon.jpg b/garrysmod/addons/shkaf/materials/wardrobe_ui/fon.jpg new file mode 100644 index 0000000..01d546c Binary files /dev/null and b/garrysmod/addons/shkaf/materials/wardrobe_ui/fon.jpg differ diff --git a/garrysmod/addons/shkaf/sound/door/close.wav b/garrysmod/addons/shkaf/sound/door/close.wav new file mode 100644 index 0000000..839342d Binary files /dev/null and b/garrysmod/addons/shkaf/sound/door/close.wav differ diff --git a/garrysmod/addons/shkaf/sound/door/open.wav b/garrysmod/addons/shkaf/sound/door/open.wav new file mode 100644 index 0000000..c8ba660 Binary files /dev/null and b/garrysmod/addons/shkaf/sound/door/open.wav differ diff --git a/garrysmod/addons/shreports/lua/autorun/autorun_reports.lua b/garrysmod/addons/shreports/lua/autorun/autorun_reports.lua new file mode 100644 index 0000000..56f9193 --- /dev/null +++ b/garrysmod/addons/shreports/lua/autorun/autorun_reports.lua @@ -0,0 +1,31 @@ +SH_REPORTS = {} + +include("reports/lib_easynet.lua") +include("reports/sh_main.lua") +include("reports_config.lua") + +IncludeCS("reports/language/" .. (SH_REPORTS.LanguageName or "english") .. ".lua") + +if (SERVER) then + AddCSLuaFile("reports/lib_loungeui.lua") + AddCSLuaFile("reports/lib_easynet.lua") + AddCSLuaFile("reports/sh_main.lua") + AddCSLuaFile("reports/cl_main.lua") + AddCSLuaFile("reports/cl_menu_main.lua") + AddCSLuaFile("reports/cl_menu_make.lua") + AddCSLuaFile("reports/cl_menu_performance.lua") + AddCSLuaFile("reports/cl_menu_rating.lua") + AddCSLuaFile("reports_config.lua") + + include("reports/lib_database.lua") + include("reports/sv_main.lua") +else + include("reports/lib_loungeui.lua") + include("reports/cl_main.lua") + include("reports/cl_menu_main.lua") + include("reports/cl_menu_make.lua") + include("reports/cl_menu_performance.lua") + include("reports/cl_menu_rating.lua") +end + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/cl_main.lua b/garrysmod/addons/shreports/lua/reports/cl_main.lua new file mode 100644 index 0000000..7af11e7 --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/cl_main.lua @@ -0,0 +1,155 @@ +if (!SH_REPORTS.ActiveReports) then + SH_REPORTS.ActiveReports = {} +end + +function SH_REPORTS:ReportCreated(data) + chat.AddText(self.Style.header, "[" .. self:L("reports") .. "] ", color_white, self:L("report_received", data.reporter_name, data.reported_name, self.ReportReasons[data.reason_id])) -- 76561198398853149 + + if (self.NewReportSound.enabled) then + surface.PlaySound(self.NewReportSound.path) + end + + self:MakeNotification(data) + self:MakePending(data) + + if (!self.ActiveReports) then + self.ActiveReports = {} + end + table.insert(self.ActiveReports, data) +end + +hook.Add("Think", "SH_REPORTS.Ready", function() + if (IsValid(LocalPlayer())) then + hook.Remove("Think", "SH_REPORTS.Ready") + easynet.SendToServer("SH_REPORTS.PlayerReady") + end +end) + +easynet.Callback("SH_REPORTS.SendList", function(data) + local pendings = {} + for _, report in pairs (SH_REPORTS.ActiveReports) do + if (IsValid(report.pending)) then + pendings[report.id] = report.pending + end + end + + SH_REPORTS.ServerTime = data.server_time + SH_REPORTS.ActiveReports = data.struct_reports + + for _, report in pairs (SH_REPORTS.ActiveReports) do + report.pending = pendings[report.id] + end + + SH_REPORTS:ShowReports() +end) + +easynet.Callback("SH_REPORTS.MinimizeReport", function(data) + if (IsValid(_SH_REPORTS_VIEW)) then + _SH_REPORTS_VIEW:Close() + end + + local report + for _, rep in pairs (SH_REPORTS.ActiveReports) do + if (rep.id == data.report_id) then + report = rep + break + end + end + + if (report) then + SH_REPORTS:MakeTab(report) + end +end) + +easynet.Callback("SH_REPORTS.ReportClosed", function(data) + for k, rep in pairs (SH_REPORTS.ActiveReports) do + if (rep.id == data.report_id) then + if (IsValid(rep.line)) then + rep.line:Close() + end + + if (IsValid(rep.pending)) then + rep.pending:Close() + end + + SH_REPORTS.ActiveReports[k] = nil + end + end + + if (IsValid(_SH_REPORTS_TAB) and _SH_REPORTS_TAB.id == data.report_id) then + _SH_REPORTS_TAB:Close() + end + + SH_REPORTS:ClosePendingPanel(data.report_id) +end) + +easynet.Callback("SH_REPORTS.ReportClaimed", function(data) + for _, rep in pairs (SH_REPORTS.ActiveReports) do + if (rep.id == data.report_id) then + rep.admin_id = data.admin_id + + if (IsValid(rep.line)) then + rep.line.claimed.avi:SetSteamID(data.admin_id) + rep.line.claimed.avi:SetVisible(true) + + local admin = player.GetBySteamID64(data.admin_id) + if (IsValid(admin)) then + rep.line.claimed.name:SetTextInset(32, 0) + rep.line.claimed.name:SetContentAlignment(4) + rep.line.claimed.name:SetText(admin:Nick()) + end + end + + if (IsValid(rep.pending)) then + rep.pending:Close() + end + + if (data.admin_id ~= LocalPlayer():SteamID64() and IsValid(rep.line) and IsValid(rep.line.delete)) then + rep.line.delete:Remove() + end + end + end + + SH_REPORTS:ClosePendingPanel(data.report_id) +end) + +easynet.Callback("SH_REPORTS.Notify", function(data) + -- do NOT do this + SH_REPORTS:Notify(SH_REPORTS:L(unpack(string.Explode("\t", data.msg))), nil, data.positive and SH_REPORTS.Style.success or SH_REPORTS.Style.failure) +end) + +easynet.Callback("SH_REPORTS.Chat", function(data) + chat.AddText(SH_REPORTS.Style.header, "[" .. SH_REPORTS:L("reports") .. "] ", color_white, data.msg) +end) + +easynet.Callback("SH_REPORTS.ReportCreated", function(data) + SH_REPORTS:ReportCreated(data) +end) + +easynet.Callback("SH_REPORTS.ReportsPending", function(data) + chat.AddText(SH_REPORTS.Style.header, "[" .. SH_REPORTS:L("reports") .. "] ", color_white, SH_REPORTS:L("there_are_x_reports_pending", data.num)) -- 76561198398853124 + + SH_REPORTS.ActiveReports = table.Copy(data.struct_reports) + + for _, report in pairs (SH_REPORTS.ActiveReports) do + SH_REPORTS:MakePending(report) + end +end) + +easynet.Callback("SH_REPORTS.AdminLeft", function(data) + for _, rep in pairs (SH_REPORTS.ActiveReports) do + if (rep.id == data.report_id) then + rep.admin_id = "" + + if (IsValid(rep.line)) then + rep.line.claimed.avi:SetVisible(false) + + rep.line.claimed.name:SetTextInset(0, 0) + rep.line.claimed.name:SetContentAlignment(5) + rep.line.claimed.name:SetText(SH_REPORTS:L("unclaimed")) + end + end + end +end) + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/cl_menu_main.lua b/garrysmod/addons/shreports/lua/reports/cl_menu_main.lua new file mode 100644 index 0000000..b742407 --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/cl_menu_main.lua @@ -0,0 +1,610 @@ +local function L(...) return SH_REPORTS:L(...) end + +local matBack = Material("shenesis/general/back.png") + +function SH_REPORTS:ShowReport(report) + if (IsValid(_SH_REPORTS_VIEW)) then + _SH_REPORTS_VIEW:Remove() + end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local m2 = m * 0.5 + local ss = self:GetScreenScale() + + local frame = self:MakeWindow(L"view_report") + frame:SetSize(500 * ss, 400 * ss) + frame:Center() + frame:MakePopup() + _SH_REPORTS_VIEW = frame + + frame:AddHeaderButton(matBack, function() + frame:Close() + self:ShowReports() + end) + + local body = vgui.Create("DPanel", frame) + body:SetDrawBackground(false) + body:DockPadding(m, m, m, m) + body:Dock(FILL) + + local players = vgui.Create("DPanel", body) + players:SetDrawBackground(false) + players:SetWide(frame:GetWide() - m * 2) + players:Dock(TOP) + + local lbl1 = self:QuickLabel(L"reporter", "{prefix}Large", styl.text, players) + lbl1:SetContentAlignment(7) + lbl1:SetTextInset(m2, m2) + lbl1:SetWide(frame:GetWide() * 0.5 - m2) + lbl1:Dock(LEFT) + lbl1:DockPadding(m2, lbl1:GetTall() + m * 1.5, m2, m2) + lbl1.Paint = function(me, w, h) + draw.RoundedBox(4, 0, 0, w, h, styl.inbg) + end + + local avi = self:Avatar(report.reporter_id, 32, lbl1) + avi:Dock(LEFT) + avi:DockMargin(0, 0, m2, 0) + + local nic = self:QuickButton(report.reporter_name, function() + SetClipboardText(report.reporter_name) + surface.PlaySound("common/bugreporter_succeeded.wav") + end, lbl1) + nic:SetContentAlignment(4) + nic:Dock(TOP) + + local s1 = util.SteamIDFrom64(report.reporter_id) + local steamid = self:QuickButton(s1, function() + SetClipboardText(s1) + surface.PlaySound("common/bugreporter_succeeded.wav") + end, lbl1) + steamid:SetContentAlignment(4) + steamid:Dock(TOP) + + local lbl = self:QuickLabel(L"reported_player", "{prefix}Large", styl.text, players) + lbl:SetContentAlignment(9) + lbl:SetTextInset(m2, m2) + lbl:Dock(FILL) + lbl:DockPadding(m2, lbl1:GetTall() + m * 1.5, m2, m2) + lbl.Paint = lbl1.Paint + + local avi = self:Avatar(report.reported_id, 32, lbl) + avi:Dock(RIGHT) + avi:DockMargin(m2, 0, 0, 0) + + local nic = self:QuickButton(report.reported_name, function() + SetClipboardText(report.reported_name) + surface.PlaySound("common/bugreporter_succeeded.wav") + end, lbl) + nic:SetContentAlignment(6) + nic:Dock(TOP) + nic.Think = function(me) + me:SetTextColor(IsValid(player.GetBySteamID64(report.reported_id)) and styl.text or styl.failure) + end + + local s2 = util.SteamIDFrom64(report.reported_id) + local steamid = self:QuickButton(s2, function() + SetClipboardText(s2) + surface.PlaySound("common/bugreporter_succeeded.wav") + end, lbl) + steamid:SetContentAlignment(6) + steamid:Dock(TOP) + + if (report.reported_id == "0") then + nic.Think = function() end + nic:Dock(FILL) + steamid:SetVisible(false) + avi:SetVisible(false) + end + + players:SetTall(lbl1:GetTall() + m * 2.5 + 32) + + local reason = self:QuickLabel(L("reason") .. ":", "{prefix}Medium", styl.text, body) + reason:Dock(TOP) + reason:DockMargin(0, m, 0, 0) + reason:DockPadding(reason:GetWide() + m2, 0, 0, 0) + + local r = self:QuickEntry(self.ReportReasons[report.reason_id], reason) + r:SetEnabled(false) + r:SetContentAlignment(6) + r:Dock(FILL) + + local comment = self:QuickLabel(L("comment") .. ":", "{prefix}Medium", styl.text, body) + comment:SetContentAlignment(7) + comment:Dock(FILL) + comment:DockMargin(0, m, 0, m2) + + local tx = self:QuickEntry("", comment) + tx:SetEnabled(false) + tx:SetMultiline(true) + tx:SetValue(report.comment) + tx:Dock(FILL) + tx:DockMargin(0, comment:GetTall() + m2, 0, 0) + + local actions = vgui.Create("DPanel", body) + actions:SetDrawBackground(false) + actions:Dock(BOTTOM) + + if (self:IsAdmin(LocalPlayer())) then + if (report.admin_id == "") then + if (self.ClaimNoTeleport) then + local claim = self:QuickButton(L"claim_report", function() + easynet.SendToServer("SH_REPORTS.Claim", {id = report.id}) + end, actions) + claim:Dock(LEFT) + else + local lbl = self:QuickLabel(L("claim_report") .. ":", "{prefix}Medium", styl.text, actions) + lbl:SetContentAlignment(4) + lbl:Dock(LEFT) + lbl:DockMargin(0, 0, m2, 0) + + local goto = self:QuickButton(L"goto", function() + if (IsValid(player.GetBySteamID64(report.reported_id))) then + local m = self:Menu() + m:AddOption("bring_reported_player"):SetMouseInputEnabled(false) + m:AddOption("yes", function() + easynet.SendToServer("SH_REPORTS.ClaimAndTeleport", {id = report.id, bring = false, bring_reported = true /* 76561198398853124 */}) + end) + m:AddOption("no", function() + easynet.SendToServer("SH_REPORTS.ClaimAndTeleport", {id = report.id, bring = false, bring_reported = false}) + end) + m:Open() + else + easynet.SendToServer("SH_REPORTS.ClaimAndTeleport", {id = report.id, bring = false, bring_reported = false}) + end + end, actions) + goto:Dock(LEFT) + + local bring = self:QuickButton(L"bring", function() + if (IsValid(player.GetBySteamID64(report.reported_id))) then + local m = self:Menu() + m:AddOption("bring_reported_player"):SetMouseInputEnabled(false) + m:AddOption("yes", function() + easynet.SendToServer("SH_REPORTS.ClaimAndTeleport", {id = report.id, bring = true, bring_reported = true}) + end) + m:AddOption("no", function() + easynet.SendToServer("SH_REPORTS.ClaimAndTeleport", {id = report.id, bring = true, bring_reported = false}) + end) + m:Open() + else + easynet.SendToServer("SH_REPORTS.ClaimAndTeleport", {id = report.id, bring = true, bring_reported = false}) + end + end, actions) + bring:Dock(LEFT) + bring:DockMargin(m2, 0, 0, 0) + end + + if (sitsys) then + local session = self:QuickButton(L"start_sit_session", function() + easynet.SendToServer("SH_REPORTS.ClaimAndCSit", {id = report.id}) + end, actions) + session:Dock(LEFT) + session:DockMargin(m2, 0, 0, 0) + end + else + local lbl = self:QuickLabel(L("claimed_by_x", ""), "{prefix}Medium", styl.text, actions) + lbl:SetContentAlignment(4) + lbl:Dock(LEFT) + lbl:DockMargin(0, 0, m2, 0) + + self:GetName(report.admin_id, function(nick) + if (IsValid(lbl)) then + lbl:SetText(L("claimed_by_x", nick)) + lbl:SizeToContents() + end + end) + end + end + + if (report.reporter_id == LocalPlayer():SteamID64()) or (report.admin_id == "" and self.CanDeleteWhenUnclaimed) or (report.admin_id == LocalPlayer():SteamID64() /* 76561198398853149 */) then + local close = self:QuickButton(L"close_report", function() + easynet.SendToServer("SH_REPORTS.CloseReport", {id = report.id}) + frame:Close() + end, actions, nil, self.Style.close_hover) + close:Dock(RIGHT) + end + + frame:SetAlpha(0) + frame:AlphaTo(255, 0.1) +end + +local matStats = Material("shenesis/reports/stats.png") +local matAdd = Material("shenesis/reports/add.png") + +function SH_REPORTS:ShowReports() + if (IsValid(_SH_REPORTS)) then + _SH_REPORTS:Remove() + end + if (IsValid(_SH_REPORTS_VIEW)) then + _SH_REPORTS_VIEW:Remove() + end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local ss = self:GetScreenScale() + + local delay = 0 + if (self.ServerTime) then + delay = self.ServerTime - os.time() + end + + local frame = self:MakeWindow(self:IsAdmin(LocalPlayer()) and L"report_list" or L"your_reports") + frame:SetSize(900 * ss, 600 * ss) + frame:Center() + frame:MakePopup() + _SH_REPORTS = frame + + if (self.UsergroupsPerformance[LocalPlayer():GetUserGroup()]) then + local btn = frame:AddHeaderButton(matStats, function() + easynet.SendToServer("SH_REPORTS.RequestPerfReports") + frame:Close() + end) + btn:SetToolTip(L"performance_reports") + end + if (!self:IsAdmin(LocalPlayer()) or self.StaffCanReport) then + local btn = frame:AddHeaderButton(matAdd, function() + self:ShowMakeReports() + frame:Close() + end) + btn:SetToolTip(L"new_report") + end + + local ilist = vgui.Create("DListView", frame) + ilist:SetSortable(false) + ilist:SetDrawBackground(false) + ilist:SetDataHeight(32) + ilist:Dock(FILL) + ilist:AddColumn(L"reporter") + ilist:AddColumn(L"reported_player") + ilist:AddColumn(L"reason") + ilist:AddColumn(L"waiting_time") + ilist:AddColumn(L"claimed") + ilist:AddColumn(L"actions") + self:PaintList(ilist) + + for _, report in SortedPairsByMemberValue (self.ActiveReports, "time", true) do + local reporter = vgui.Create("DPanel", frame) + reporter:SetDrawBackground(false) + + local avi = self:Avatar(report.reporter_id, 24, reporter) + avi:SetPos(4, 4) + + local name = self:QuickLabel(report.reporter_name, "{prefix}Medium", styl.text, reporter) + name:Dock(FILL) + name:SetTextInset(ilist:GetDataHeight(), 0) + + local reported = vgui.Create("DPanel", frame) + reported:SetDrawBackground(false) + + local avi = self:Avatar(report.reported_id, 24, reported) + avi:SetPos(4, 4) + + local name = self:QuickLabel(report.reported_name, "{prefix}Medium", styl.text, reported) + name:Dock(FILL) + name:SetTextInset(32, 0) + + if (report.reported_id ~= "0") then + name.Think = function(me) + me:SetTextColor(IsValid(player.GetBySteamID64(report.reported_id)) and styl.text or styl.failure) + end + else + avi:SetVisible(false) + name:SetContentAlignment(5) + name:SetTextInset(0, 0) + end + + local claimed = vgui.Create("DPanel", frame) + claimed:SetDrawBackground(false) + + local avi = self:Avatar("", 24, claimed) + avi:SetPos(4, 4) + claimed.avi = avi + + local name = self:QuickLabel(L"unclaimed", "{prefix}Medium", styl.text, claimed) + name:Dock(FILL) + name:SetTextInset(32, 0) + claimed.name = name + + if (report.admin_id ~= "") then + avi:SetSteamID(report.admin_id) + + self:GetName(report.admin_id, function(nick) + if (IsValid(name)) then + name:SetText(nick) + end + end) + else + avi:SetVisible(false) + name:SetContentAlignment(5) + name:SetTextInset(0, 0) + end + + local actions = vgui.Create("DPanel", frame) + actions:SetDrawBackground(false) + actions:SetTall(32) + actions:DockPadding(4, 4, 4, 4) + + local act_view = self:QuickButton(L"view", function() end, actions) + act_view:Dock(LEFT) + act_view:DockMargin(0, 0, 4, 0) + act_view.DoClick = function() + frame:Close() + self:ShowReport(report) + end + + local act_delete + if (report.admin_id == "" and self.CanDeleteWhenUnclaimed) or (report.admin_id == LocalPlayer():SteamID64()) then + act_delete = self:QuickButton(L"close_report", function() end, actions, nil, self.Style.close_hover /* 76561198398853124 */) + act_delete:Dock(LEFT) + act_delete.DoClick = function() + easynet.SendToServer("SH_REPORTS.CloseReport", {id = report.id}) + end + end + + local time = self:QuickLabel("", "{prefix}Medium", styl.text, frame) + time:SetContentAlignment(5) + time.Think = function(me) + if (!me.m_fNextRefresh or RealTime() >= me.m_fNextRefresh) then + me.m_fNextRefresh = RealTime() + 5 + me:SetText(self:FullFormatTime(os.time() + delay - report.time)) + end + end + + local line = ilist:AddLine(reporter, reported, self.ReportReasons[report.reason_id], time, claimed, actions) + -- line:SetSelectable(false) + line.claimed = claimed + line.delete = act_delete + line.Close = function(me) + me:AlphaTo(0, 0.2, nil, function() + if (!ilist.Lines[me:GetID()]) then + return end + + ilist:RemoveLine(me:GetID()) + end) + end + self:LineStyle(line) + + for _, rep in pairs (self.ActiveReports) do + if (rep.id == report.id) then + rep.line = line + end + end + end + + frame:SetAlpha(0) + frame:AlphaTo(255, 0.1) +end + +function SH_REPORTS:MakeTab(report) + if (IsValid(_SH_REPORTS_TAB)) then + _SH_REPORTS_TAB:Close() + end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local m2 = m * 0.5 + + local rep = vgui.Create("DButton") + rep:SetText("") + rep:SetSize(160, 32 + m) + rep:SetPos(ScrW() * 0.5, ScrH()) + rep:MoveToFront() + rep:DockPadding(m2, m2, m2, m2) + rep.Paint = function(me, w, h) + draw.RoundedBoxEx(4, 0, 0, w, h, styl.header, true, true, false, false) + end + rep.DoClick = function(me) + if (me.m_bClosing) then + return end + + self:ShowReport(report) + end + rep.Close = function(me) + if (me.m_bClosing) then + return end + + me.m_bClosing = true + me:Stop() + me:MoveTo(rep.x, ScrH(), 0.2, 0, -1, function() + me:Remove() + end) + end + rep.id = report.id + _SH_REPORTS_TAB = rep + + local avi = self:Avatar(report.reporter_id, 32, rep) + avi:SetMouseInputEnabled(false) + avi:Dock(LEFT) + avi:DockMargin(0, 0, m2, 0) + + local name = self:QuickLabel(L("report_of_x", report.reporter_name), "{prefix}Large", styl.text, rep) + name:Dock(FILL) + + rep:SetWide(name:GetWide() + avi:GetWide() + m * 1.5) + rep:CenterHorizontal() + rep:MoveTo(rep.x, ScrH() - rep:GetTall(), 0.2) +end + +function SH_REPORTS:MakeNotification(report) + if (IsValid(report.notif)) then + report.notif:Close() + end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local m2 = m * 0.5 + + local rep = vgui.Create("DButton") + rep:SetText("") + rep:SetSize(160, 32 + m) + rep:SetPos(ScrW() * 0.5, -rep:GetTall()) + rep:MoveToFront() + rep:DockPadding(m2, m2, m2, m2) + rep.Paint = function(me, w, h) + draw.RoundedBoxEx(4, 0, 0, w, h, styl.header, false, false, true, true) + end + rep.DoClick = function(me) + if (me.m_bClosing) then + return end + + self:ShowReport(report) + me:Close() + end + rep.Close = function(me) + if (me.m_bClosing) then + return end + + me.m_bClosing = true + me:Stop() + me:MoveTo(rep.x, -me:GetTall(), 0.2, 0, -1, function() + me:Remove() + end) + end + report.notif = rep + + local avi = self:Avatar(report.reporter_id, 32, rep) + avi:SetMouseInputEnabled(false) + avi:Dock(LEFT) + avi:DockMargin(0, 0, m2, 0) + + local name = self:QuickLabel(L("report_received", report.reporter_name, report.reported_name, self.ReportReasons[report.reason_id]), "{prefix}Large", /* 76561198398853149 */ styl.text, rep) + name:Dock(FILL) + + rep:SetWide(name:GetWide() + avi:GetWide() + m * 1.5) + rep:CenterHorizontal() + rep:MoveTo(rep.x, 0, 0.2, nil, nil, function() + rep:MoveTo(rep.x, -rep:GetTall(), 0.2, 7.5, nil, function() + rep:Remove() + end) + end) +end + +SH_REPORTS.PendingPanels = SH_REPORTS.PendingPanels or {} + +function SH_REPORTS:ClosePendingPanel(id) + local cleaned = {} + + -- Clean closed reports + for k, v in pairs (self.PendingPanels) do + if (!IsValid(v)) then + continue end + + local found = false + for _, rep in pairs (SH_REPORTS.ActiveReports) do + if (rep.id == v.m_iReportID) then + found = true + end + end + + if (!found) or (v.m_iReportID == id) then + v:Close() + continue + end + + table.insert(cleaned, v) + end + + self.PendingPanels = cleaned +end + +function SH_REPORTS:MakePending(report) + if (IsValid(report.pending)) then + report.pending:Remove() + end + + local num = 0 + for _, w in pairs (self.PendingPanels) do + if (IsValid(w) and !w.m_bClosing) then + num = num + 1 + end + end + + if (num >= self.PendingReportsDispNumber) then + return end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local hh = th * 0.66 + local m2, m3 = m * 0.5, m * 0.66 + local ss = self:GetScreenScale() + + local wnd = vgui.Create("DPanel") + wnd:SetSize(300 * ss, 112 * ss) + wnd:DockPadding(m3, hh + m3, m3, m3) + wnd.Paint = function(me, w, h) + draw.RoundedBoxEx(4, 0, 0, w, hh, styl.header, true, true, false, false) + draw.RoundedBoxEx(4, 0, hh, w, h - hh, styl.inbg, false, false, true, true) + draw.SimpleText("[" .. L"unclaimed" .. "] " .. L("report_of_x", report.reporter_name), "SH_REPORTS.MediumB", m2, hh * 0.5, styl.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + wnd.m_iReportID = report.id + report.pending = wnd + + local lbl = self:QuickLabel(L"reported_player" .. ":", "{prefix}Medium", styl.text, wnd) + lbl:Dock(TOP) + + local reported = self:QuickLabel(report.reported_name, "{prefix}Medium", styl.text, lbl) + reported:Dock(RIGHT) + + local lbl = self:QuickLabel(L"reason" .. ":", "{prefix}Medium", styl.text, wnd) + lbl:Dock(TOP) + lbl:DockMargin(0, m3, 0, 0) + + local reason = self:QuickLabel(self.ReportReasons[report.reason_id], "{prefix}Medium", styl.text, lbl) + reason:Dock(RIGHT) + + local buttons = vgui.Create("DPanel", wnd) + buttons:SetDrawBackground(false) + buttons:SetTall(20 * ss) + buttons:Dock(BOTTOM) + + local close = self:QuickButton("✕", function() + wnd:Close() + report.ignored = true + end, buttons) + close:SetWide(buttons:GetTall()) + close:Dock(LEFT) + close.m_Background = styl.close_hover + + local view = self:QuickButton(L"view", function() + self:ShowReport(report) + end, buttons) + view:Dock(RIGHT) + view.m_Background = styl.header + + local i = table.insert(self.PendingPanels, wnd) + wnd:SetPos(m, m + (i - 1) * wnd:GetTall() + (i - 1) * m) + wnd:SetAlpha(0) + wnd:AlphaTo(255, 0.1) + + wnd.Close = function(me) + if (me.m_bClosing) then + return end + + me.m_bClosing = true + me:AlphaTo(0, 0.1, 0, function() + local ma = #self.PendingPanels + table.RemoveByValue(self.PendingPanels, me) + me:Remove() + + local o = 0 + for j = i - 1, ma do + local w = self.PendingPanels[j] + if (IsValid(w) and w ~= me) then + w:Stop() + w:MoveTo(w.x, m + o * wnd:GetTall() + o * m, 0.2) + o = o + 1 + end + end + + -- Display any hidden panels + for _, r in pairs (self.ActiveReports) do + if (!IsValid(r.pending) and !r.ignored and r.admin_id == "") then + self:MakePending(r) + end + end + end) + end +end + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/cl_menu_make.lua b/garrysmod/addons/shreports/lua/reports/cl_menu_make.lua new file mode 100644 index 0000000..f9cb260 --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/cl_menu_make.lua @@ -0,0 +1,176 @@ +local function L(...) return SH_REPORTS:L(...) end + +local matBack = Material("shenesis/general/back.png") + +function SH_REPORTS:ShowMakeReports(c, d) + if (IsValid(_SH_REPORTS_MAKE)) then + _SH_REPORTS_MAKE:Remove() + end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local m2 = m * 0.5 + local ss = self:GetScreenScale() + + local frame = self:MakeWindow(L"new_report") + frame:SetSize(500 * ss, 500 * ss) + frame:Center() + frame:MakePopup() + _SH_REPORTS_MAKE = frame + + frame:AddHeaderButton(matBack, function() + frame:Close() + self:ShowReports() + end) + + local body = vgui.Create("DPanel", frame) + body:SetDrawBackground(false) + body:Dock(FILL) + body:DockMargin(m, m, m, m) + + local lbl = self:QuickLabel(L"reason" .. ":", "{prefix}Large", styl.text, body) + lbl:Dock(TOP) + + local reason = self:QuickComboBox(lbl) + reason:Dock(FILL) + reason:DockMargin(lbl:GetWide() + m, 0, 0, 0) + + for rid, r in pairs (self.ReportReasons) do + reason:AddChoice(r, rid) + end + + local lbl = self:QuickLabel(L"player_to_report" .. ":", "{prefix}Large", styl.text, body) + lbl:Dock(TOP) + lbl:DockMargin(0, m, 0, m) + + local target = self:QuickComboBox(lbl) + target:SetSortItems(false) + target:Dock(FILL) + target:DockMargin(lbl:GetWide() + m, 0, 0, 0) + + local toadd = {} + for _, ply in ipairs (player.GetAll()) do + if (ply == LocalPlayer()) then + continue end + + if (self:IsAdmin(ply) and !self.StaffCanBeReported) then + continue end + + table.insert(toadd, {nick = ply:Nick(), steamid = ply:SteamID64()}) + end + + for _, d in SortedPairsByMemberValue (toadd, "nick") do + target:AddChoice(d.nick, d.steamid) + end + + if (self.CanReportOther) then + target:AddChoice("​[" .. L"other" .. "]", "0") + end + + local p = vgui.Create("DPanel", body) + p:SetTall(64 * ss + m) + p:Dock(TOP) + p:DockPadding(m2, m2, m2, m2) + p.Paint = function(me, w, h) + draw.RoundedBox(4, 0, 0, w, h, styl.inbg) + end + + local pc = vgui.Create("DPanel", p) + pc:SetPaintedManually(true) + pc:SetDrawBackground(false) + pc:Dock(FILL) + + local avi = self:Avatar("", 64 * ss, pc) + avi:Dock(LEFT) + avi:DockMargin(0, 0, m2, 0) + + local nick = self:QuickLabel("", "{prefix}Large", styl.text, pc) + nick:Dock(TOP) + + local steamid = self:QuickLabel("", "{prefix}Medium", styl.text, pc) + steamid:Dock(TOP) + + local lbl = self:QuickLabel(L"comment" .. ":" /* 76561198398853149 */, "{prefix}Large", styl.text, body) + lbl:SetContentAlignment(7) + lbl:Dock(FILL) + lbl:DockMargin(0, m, 0, 0) + + local comment = self:QuickEntry("", lbl) + comment:SetValue(c or "") + comment:SetMultiline(true) + comment:Dock(FILL) + comment:DockMargin(0, lbl:GetTall() + m2, 0, 0) + + local btns = vgui.Create("DPanel", body) + btns:SetDrawBackground(false) + btns:Dock(BOTTOM) + btns:DockMargin(0, m, 0, 0) + + local submit = self:QuickButton(L"submit_report", function() + local name, steamid = target:GetSelected() + if (!steamid) then + self:Notify(L"select_player_first", nil, styl.failure, frame) + return + end + + local _, rid = reason:GetSelected() + if (!rid) then + self:Notify(L"select_reason_first", nil, styl.failure, frame) + return + end + + easynet.SendToServer("SH_REPORTS.NewReport", { + reported_name = name, + reported_id = steamid, + reason_id = rid, + comment = comment:GetValue():sub(1, self.MaxCommentLength), + }) + + frame:Close() + end, btns) + submit:Dock(RIGHT) + + -- cbs + if (d) then + reason.OnSelect = function(me, index, value, data) + local k = self.ReasonAutoTarget[value] + if (!k) then + return end + + local p = d["last" .. k] + if (IsValid(p)) then + local i + for k, v in pairs (target.Choices) do + if (v == p:Nick()) then + i = k + break + end + end + + if (i) then + target:ChooseOption(p:Nick(), i) + end + end + end + end + target.OnSelect = function(me, index, value, data) + pc:SetPaintedManually(false) + pc:SetAlpha(0) + pc:AlphaTo(255, 0.2) + + avi:SetVisible(data ~= "0") + avi:SetSteamID(data) + nick:SetText(value) + steamid:SetText(data ~= "0" and util.SteamIDFrom64(data) or "") + steamid:InvalidateParent(true) + end + + frame:SetAlpha(0) + frame:AlphaTo(255, 0.1) +end + +easynet.Callback("SH_REPORTS.QuickReport", function(data) + SH_REPORTS:ShowMakeReports(data.comment, data) +end) + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/cl_menu_performance.lua b/garrysmod/addons/shreports/lua/reports/cl_menu_performance.lua new file mode 100644 index 0000000..f2457df --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/cl_menu_performance.lua @@ -0,0 +1,415 @@ +local function L(...) return SH_REPORTS:L(...) end + +local matBack = Material("shenesis/general/back.png") + +function SH_REPORTS:ShowPerformanceReports() + if (IsValid(_SH_REPORTS_PERF)) then + _SH_REPORTS_PERF:Remove() + end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local m2 = m * 0.5 + local ss = self:GetScreenScale() + + local delay = 0 + if (self.ServerTime) then + delay = self.ServerTime - os.time() + end + + local curprep + + local frame = self:MakeWindow(L"performance_reports") + frame:SetSize(800 * ss, 600 * ss) + frame:Center() + frame:MakePopup() + _SH_REPORTS_PERF = frame + + frame:AddHeaderButton(matBack, function() + frame:Close() + self:ShowReports() + end) + + local sel = vgui.Create("DScrollPanel", frame) + sel:SetDrawBackground(false) + sel:SetWide(140 * ss) + sel:Dock(LEFT) + sel.Paint = function(me, w, h) + draw.RoundedBoxEx(4, 0, 0, w, h, styl.inbg, false, false, true, false) + end + self:PaintScroll(sel) + + local ilist_perf = vgui.Create("DListView", frame) + ilist_perf:SetVisible(false) + ilist_perf:SetSortable(false) + ilist_perf:SetDrawBackground(false) + ilist_perf:SetDataHeight(32) + ilist_perf:Dock(FILL) + ilist_perf:AddColumn(L"admin") + ilist_perf:AddColumn(L"num_claimed") + ilist_perf:AddColumn(L"num_closed") + ilist_perf:AddColumn(L"time_spent") + self:PaintList(ilist_perf) + + local ilist_rating = vgui.Create("DListView", frame) + ilist_rating:SetVisible(false) + ilist_rating:SetSortable(false) + ilist_rating:SetDrawBackground(false) + ilist_rating:SetDataHeight(32) + ilist_rating:Dock(FILL) + ilist_rating:AddColumn(L"admin") + ilist_rating:AddColumn(L"rating") + self:PaintList(ilist_rating) + + local ilist_history = vgui.Create("DListView", frame) + ilist_history:SetVisible(false) + ilist_history:SetSortable(false) + ilist_history:SetDrawBackground(false) + ilist_history:SetDataHeight(32) + ilist_history:Dock(FILL) + ilist_history:AddColumn(L"reporter") + ilist_history:AddColumn(L"reported_player") + ilist_history:AddColumn(L"reason") + ilist_history:AddColumn(L"admin") + ilist_history:AddColumn(L"rating") + -- ilist_history.Think = function(me) + -- local hover = vgui.GetHoveredPanel() + -- if (!IsValid(_SH_REPORTS_HIST_DETAILS)) then + -- if (!IsValid(hover) or !hover.m_HistoryReport) then + -- return end + + -- _SH_REPORTS_HIST_DETAILS = NULL + -- else + + -- end + -- end + self:PaintList(ilist_history) + + frame.ShowStaff = function(me, staffs) + if (!ilist_perf:IsVisible()) then + return end + + local i = 0 + for _, info in pairs (staffs) do + local user = vgui.Create("DPanel", frame) + user:SetDrawBackground(false) + + local avi = self:Avatar(info.steamid, 24, user) + avi:SetPos(4, 4) + + local name = self:QuickLabel("...", "{prefix}Medium", styl.text, user) + name:Dock(FILL) + name:SetTextInset(ilist_perf:GetDataHeight(), 0) + + self:GetName(info.steamid, function(nick) + if (IsValid(name)) then + name:SetText(nick) + end + end) + + local line = ilist_perf:AddLine(user, info.claimed, info.closed, self:FullFormatTime(info.timespent)) + line:SetAlpha(0) + line:AlphaTo(255, 0.1, 0.1 * i) + self:LineStyle(line) + + i = i + 1 + end + end + + frame.ShowRatings = function(me, ratings) + if (!ilist_rating:IsVisible()) then + return end + + ilist_rating:Clear() + + local i = 0 + for _, info in pairs (ratings) do + if (info.num == 0) then + continue end + + local frac = info.total / info.num / 5 + local tot = string.Comma(info.num) + local tx = " " .. math.Round(frac * 100) .. "% (" .. tot .. ")" + + local user = vgui.Create("DPanel", frame) + user:SetDrawBackground(false) + + local avi = self:Avatar(info.steamid, 24, user) + avi:SetPos(4, 4) + + local name = self:QuickLabel("...", "{prefix}Medium", styl.text, user) + name:Dock(FILL) + name:SetTextInset(ilist_rating:GetDataHeight(), 0) + + self:GetName(info.steamid, function(nick) + if (IsValid(name)) then + name:SetText(nick) + end + end) + + local stars = vgui.Create("DPanel", frame) + stars.Paint = function(me, w, h) + local _x, _y = me:LocalToScreen(0, 0) + + surface.SetFont("SH_REPORTS.Large") + local wi = surface.GetTextSize("★★★★★") + + surface.SetFont("SH_REPORTS.Medium") + local wi2 = surface.GetTextSize(tx) + + local wid = wi + wi2 + + draw.SimpleText("★★★★★", "SH_REPORTS.Large", w * 0.5 - wid * 0.5, h * 0.5, styl.inbg, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + render.SetScissorRect(_x, _y, _x + w * 0.5 - wid * 0.5 + wi * frac, _y + h, true) + draw.SimpleText("★★★★★", "SH_REPORTS.Large", w * 0.5 - wid * 0.5, h * 0.5, styl.rating, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + render.SetScissorRect(0, 0, 0, 0, false) + + draw.SimpleText(tx, "SH_REPORTS.Medium", w * 0.5 - wid * 0.5 + wi, h * 0.5, styl.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local line = ilist_rating:AddLine(user, stars) + line:SetAlpha(0) + line:AlphaTo(255, 0.1, 0.1 * i) + self:LineStyle(line) + + i = i + 1 + end + end + + frame.ShowHistory = function(me, history) + if (!ilist_history:IsVisible()) then + return end + + ilist_history:Clear() + + local i = 0 + for _, info in pairs (history) do + local reporter = vgui.Create("DPanel", frame) + reporter:SetDrawBackground(false) + + local avi = self:Avatar(info.reporter, 24, reporter) + avi:SetPos(4, 4) + + local name = self:QuickLabel("...", "{prefix}Medium", styl.text, reporter) + name:Dock(FILL) + name:SetTextInset(ilist_history:GetDataHeight(), 0) + + self:GetName(info.reporter, function(nick) + if (IsValid(name)) then + name:SetText(nick) + end + end) + + local reported = vgui.Create("DPanel", frame) + reported:SetDrawBackground(false) + + local avi = self:Avatar(info.reported, 24, reported) + avi:SetPos(4, 4) + + local name = self:QuickLabel("...", "{prefix}Medium", styl.text, reported) + name:Dock(FILL) + name:SetTextInset(ilist_history:GetDataHeight(), 0) + + if (info.reported == "0") then + avi:SetVisible(false) + name:SetText("[" .. L"other" .. "]") + name:SetTextInset(0, 0) + name:SetContentAlignment(5) + else + self:GetName(info.reported, function(nick) + if (IsValid(name)) then + name:SetText(nick) + end + end) + end + + local admin = vgui.Create("DPanel", frame) + admin:SetDrawBackground(false) + + local avi = self:Avatar(info.admin, 24, admin) + avi:SetPos(4, 4) + + local name = self:QuickLabel("...", "{prefix}Medium", styl.text, admin) + name:Dock(FILL) + name:SetTextInset(ilist_history:GetDataHeight(), 0) + + self:GetName(info.admin, function(nick) + if (IsValid(name)) then + name:SetText(nick) + end + end) + + local rating + if (info.rating > 0) then + local frac = info.rating / 5 + + rating = vgui.Create("DPanel", frame) + rating.Paint = function(me, w, h) + local _x, _y = me:LocalToScreen(0, 0) + + surface.SetFont("SH_REPORTS.Large") + local wi = surface.GetTextSize("★★★★★") + local wid = wi + + draw.SimpleText("★★★★★", "SH_REPORTS.Large", w * 0.5 - wid * 0.5, h * 0.5, styl.inbg, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + render.SetScissorRect(_x, _y, _x + w * 0.5 - wid * 0.5 + wi * frac, _y + h, true) + draw.SimpleText("★★★★★", "SH_REPORTS.Large", w * 0.5 - wid * 0.5, h * 0.5, styl.rating, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + render.SetScissorRect(0, 0, 0, 0, false) + end + end + + local line = ilist_history:AddLine(reporter, reported, info.reason, admin, info.rating > 0 and rating or L"none") + line:SetAlpha(0) + line:AlphaTo(255, 0.1, 0.1 * i) + line:SetToolTip(os.date(self.TimeFormat, info.date) .. "\n\n" .. L"waiting_time" .. ": " .. self:FullFormatTime(info.waiting_time) .. "\n\n" .. L"comment" .. ":\n" .. info.comment) + self:LineStyle(line) + + // HAAAAAAAAACKS + -- line.m_HistoryReport = info + + -- local function RecFix(s) + -- for _, v in pairs (s:GetChildren()) do + -- v.m_HistoryReport = info + -- v.m_HistoryLine = line + + -- RecFix(v) + -- end + -- end + + -- RecFix(line) + + i = i + 1 + end + end + + local function display_perf(start, prep) + if (curprep == start) then + return end + + curprep = start + frame.m_iID = prep.id + + ilist_perf:Clear() + ilist_perf:SetVisible(true) + ilist_rating:SetVisible(false) + ilist_history:SetVisible(false) + + local ds, de = os.date(self.DateFormat, start), os.date(self.DateFormat, prep.end_time) + + frame.m_Title:SetText(L"performance_reports" .. /* 76561198398853124 */ " (" .. ds .. " - " .. de .. ")") + frame.m_Title:SizeToContentsX() + + if (prep.staff) then + frame:ShowStaff(prep.staff) + else + easynet.SendToServer("SH_REPORTS.RequestPerfReportStaff", {id = prep.id}) + end + + self:Notify(L("displaying_perf_report_from_x_to_y", ds, de), 5, styl.success, frame) + end + + local btn_ratings = self:QuickButton(L"rating", function() + ilist_perf:SetVisible(false) + ilist_history:SetVisible(false) + ilist_rating:SetVisible(true) + ilist_rating:Clear() + + frame.m_Title:SetText(L"performance_reports") + frame.m_Title:SizeToContentsX() + + easynet.SendToServer("SH_REPORTS.RequestStaffRatings") + end, sel) + btn_ratings:SetContentAlignment(4) + btn_ratings:SetTextInset(m + 2, 0) + btn_ratings:Dock(TOP) + btn_ratings:SetTall(32 * ss) + btn_ratings.m_iRound = 0 + btn_ratings.PaintOver = function(me, w, h) + if (ilist_rating:IsVisible()) then + surface.SetDrawColor(styl.header) + surface.DrawRect(0, 0, 4, h) + end + end + + local btn_history = self:QuickButton(L"history", function() + ilist_perf:SetVisible(false) + ilist_rating:SetVisible(false) + ilist_history:SetVisible(true) + ilist_history:Clear() + + frame.m_Title:SetText(L"performance_reports") + frame.m_Title:SizeToContentsX() + + easynet.SendToServer("SH_REPORTS.RequestReportHistory") + end, sel) + btn_history:SetContentAlignment(4) + btn_history:SetTextInset(m + 2, 0) + btn_history:Dock(TOP) + btn_history:SetTall(32 * ss) + btn_history.m_iRound = 0 + btn_history.PaintOver = function(me, w, h) + if (ilist_history:IsVisible()) then + surface.SetDrawColor(styl.header) + surface.DrawRect(0, 0, 4, h) + end + end + + for _, prep in SortedPairs (self.CachedPerfReports, true) do + local btn = self:QuickButton(os.date(self.DateFormat, prep.start_time), function() + display_perf(prep.start_time, prep) + end, sel, nil, prep.end_time >= (os.time() + delay) and styl.success or styl.text) + btn:SetContentAlignment(4) + btn:SetTextInset(m + 2, 0) + btn:Dock(TOP) + btn:SetTall(32 * ss) + btn.m_iRound = 0 + btn.PaintOver = function(me, w, h) + if (curprep == prep.start_time and ilist_perf:IsVisible()) then + surface.SetDrawColor(styl.header) + surface.DrawRect(0, 0, 4, h) + end + end + end + + frame:SetAlpha(0) + frame:AlphaTo(255, 0.1) +end + +easynet.Callback("SH_REPORTS.SendPerfReports", function(data) + SH_REPORTS.CachedPerfReports = data.struct_perf_reports + SH_REPORTS:ShowPerformanceReports() +end) + +easynet.Callback("SH_REPORTS.SendPerfReportStaff", function(data) + if (!IsValid(_SH_REPORTS_PERF) or _SH_REPORTS_PERF.m_iID ~= data.id) then + return end + + _SH_REPORTS_PERF:ShowStaff(data.struct_perf_reports_staff) +end) + +easynet.Callback("SH_REPORTS.SendRatings", function(data) + if (!IsValid(_SH_REPORTS_PERF)) then + return end + + _SH_REPORTS_PERF:ShowRatings(data.struct_rating) +end) + +easynet.Callback("SH_REPORTS.SendHistoryList", function(data) + if (!IsValid(_SH_REPORTS_PERF)) then + return end + + local steamids = data.struct_history_steamids + for _, dat in pairs (data.struct_history_list) do + dat.reporter = steamids[dat.reporter_nid].steamid + dat.reported = steamids[dat.reported_nid].steamid + dat.admin = steamids[dat.admin_nid].steamid + + dat.reporter_nid = nil + dat.reported_nid = nil + dat.admin_nid = nil + end + + _SH_REPORTS_PERF:ShowHistory(data.struct_history_list) +end) + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/cl_menu_rating.lua b/garrysmod/addons/shreports/lua/reports/cl_menu_rating.lua new file mode 100644 index 0000000..5dd503e --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/cl_menu_rating.lua @@ -0,0 +1,71 @@ +local function L(...) return SH_REPORTS:L(...) end + +local matStar = Material("shenesis/reports/star.png", "noclamp smooth") + +function SH_REPORTS:ShowRating(report_id, admin_name) + if (IsValid(_SH_REPORTS_RATE)) then + _SH_REPORTS_RATE:Remove() + end + + local styl = self.Style + local th, m = self:GetPadding(), self:GetMargin() + local m2 = m * 0.5 + local ss = self:GetScreenScale() + + local cur_rate = 3 + local is = 64 * ss + + local frame = self:MakeWindow(L"rating") + frame:SetSize(1, 144 * ss + m * 2) + frame:MakePopup() + _SH_REPORTS_RATE = frame + + local stars = vgui.Create("DPanel", frame) + stars:SetDrawBackground(false) + stars:Dock(FILL) + stars:DockMargin(m, m, m, m) + + for i = 1, 5 do + local st = vgui.Create("DButton", stars) + st:SetToolTip(i .. "/" .. 5) + st:SetText("") + st:SetWide(64 * ss) + st:Dock(LEFT) + st:DockMargin(0, 0, m2, 0) + st.Paint = function(me, w, h) + if (!me.m_CurColor) then + me.m_CurColor = styl.inbg + else + me.m_CurColor = self:LerpColor(FrameTime() * 20, me.m_CurColor, cur_rate >= i and styl.rating or styl.inbg) + end + + surface.SetMaterial(matStar) + surface.SetDrawColor(me.m_CurColor) + surface.DrawTexturedRect(0, 0, w, h) + end + st.OnCursorEntered = function() + cur_rate = i + end + st.DoClick = function() + easynet.SendToServer("SH_REPORTS.RateAdmin", {report_id = report_id, rating = i}) + frame:Close() + end + end + + local lbl = self:QuickLabel(L("rate_question", admin_name), "{prefix}Large", styl.text, frame) + lbl:SetContentAlignment(5) + lbl:Dock(BOTTOM) + lbl:DockMargin(0, 0, 0, m) + + frame:SetWide(math.max(400 * ss, lbl:GetWide() + m * 2)) + frame:Center() + + local sp = math.ceil((frame:GetWide() - (64 * ss) * 5 - m * 4) * 0.5) + stars:DockPadding(sp, 0, sp, 0) +end + +easynet.Callback("SH_REPORTS.PromptRating", function(data) + SH_REPORTS:ShowRating(data.report_id, data.admin_name) +end) + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/language/english.lua b/garrysmod/addons/shreports/lua/reports/language/english.lua new file mode 100644 index 0000000..2ef450a --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/language/english.lua @@ -0,0 +1,65 @@ +SH_REPORTS.Language = { + reports = "Жалоба", + your_reports = "Ваша жалоба", + report_list = "Список жалоб", + reporter = "Подающий", + reported_player = "Обвиняемый", + reason = "Причина", + waiting_time = "Время ожидания", + claimed = "Взять?", + unclaimed = "Не взята", + + new_report = "Новая жалоба", + player_to_report = "Игрок, на которого жалоба", + comment = "Комментарий", + submit_report = "Отправить жалобу", + + view_report = "Посмотреть жалобы", + actions = "Действия", + goto = "К нему", + bring = "К себе", + bring_reported_player = "Телепортировать обвиняемого?", + yes = "Да", + no = "Нет", + claim_report = "Взять жалобу", + close_report = "Закрыть жалобу", + view = "Посмотреть", + start_sit_session = "Start sit session", + report_of_x = "%s's report", + claimed_by_x = "Взята %s", + other = "Другое", + none = "Нету", + + performance_reports = "Производительности", + displaying_perf_report_from_x_to_y = "Отображение отчета о производительности с %s по %s.", + admin = "Админ", + num_claimed = "Взято", + num_closed = "Закрыта", + time_spent = "Прошло времени", + rating = "Оценка", + rate_question = "Как бы вы оценили работу %s?", + rate_thanks = "Спасибо за оценку!", + rate_notification = "%s поставил вам оценку %s.", + history = "История", + + not_allowed_to_run_cmd = "Вам не разрешено использовать эту команду.", + report_submitted = "Ваша жалоба была отправлена. Ожидайте ответа от администратора", + report_limit_reached = "Вы достигли лимита жалоб, которые вы можете отправить!", + report_received = "Новая жалоба от %s против %s: %s", + report_claimed = "Жалоба взята!", + admin_claimed_your_report = "Администратор взял вашу жалобу!", + admin_has_disconnected = "Администратор разбирающий вашу жалобу вышел.", + report_closed = "Жалоба закрыта.", + your_report_was_closed = "Ваша жалоба была закрыта администратором.", + reporter_closed_report = "Игрок закрыл свою жалобу.", + report_already_claimed = "Эта жалоба уже взята кем-то.", + report_non_existent = "Эта жалоба не существует.", + player_has_no_reports = "У цели нету активных жалоб.", + cannot_report_self = "Вы не можете пожаловаться на самого себя.", + cannot_report_admin = "Вы не можете пожаловаться на администратора.", + cannot_report_as_admin = "Вы администратор, вы не можете подавать жалобы!", + claimed_report_still_active = "Вы уже имеете открытую жалобу.", + select_reason_first = "Выберите причину для жалобы.", + select_player_first = "Выберите игрока на которого вы жалуетесь..", + there_are_x_reports_pending = "Ожидается рассмотрения %d жалоб.", +} diff --git a/garrysmod/addons/shreports/lua/reports/language/french.lua b/garrysmod/addons/shreports/lua/reports/language/french.lua new file mode 100644 index 0000000..3111225 --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/language/french.lua @@ -0,0 +1,67 @@ +SH_REPORTS.Language = { + reports = "Signalement", + your_reports = "Vos signalements", + report_list = "Liste de signalements", + reporter = "Signaler", + reported_player = "Joueur signalé(e)", + reason = "Raison", + waiting_time = "Temps d'attente", + claimed = "Accepté ?", + unclaimed = "Non accepté", + + new_report = "Signaler un joueur", + player_to_report = "Joueur à signaler", + comment = "Commentaire", + submit_report = "Envoyer signalement", + + view_report = "Voir signalement", + actions = "Actions", + goto = "Aller à", + bring = "Ramener", + bring_reported_player = "Ramener le joueur signalé ?", + yes = "Oui", + no = "Non", + claim_report = "Accepter le signalement", + close_report = "Fermer le signalement", + view = "Voir", + start_sit_session = "Commencer une session isolée", + report_of_x = "Signalement de %s", + claimed_by_x = "Accepté par %s", + other = "Autre", + none = "Aucun", + + performance_reports = "Rapports de performance", + displaying_perf_report_from_x_to_y = "Affichage des rapports de performance de %s à %s.", -- 76561198398853149 + admin = "Admin", + num_claimed = "Acceptés", + num_closed = "Fermés", + time_spent = "Temps passé", + rating = "Note", + rate_question = "Comment noteriez-vous la performance de %s ?", + rate_thanks = "Merci pour votre note !", + rate_notification = "%s vous a donné une note de %s.", + history = "Historique", + + not_allowed_to_run_cmd = "Vous n'êtes pas autorisé à exécuter cette commande.", + report_submitted = "Signalement soumis. Veuillez attendre la réponse d'un admin.", + report_limit_reached = "Vous avez atteint la limite de signalements soumis !", + report_received = "Nouveau signalement de %s contre %s : %s", + report_claimed = "Signalement accepté !", + admin_claimed_your_report = "Un admin a accepté votre signalement !", + admin_has_disconnected = "L'admin en charge de votre signalement s'est déconnecté.", + report_closed = "Signalement fermé.", + your_report_was_closed = "Votre signalement a été fermé par un admin.", + reporter_closed_report = "Le signaleur a fermé son signalement.", + report_already_claimed = "Ce signalement a déjà été accepté.", + report_non_existent = "Ce signalement n'existe pas.", + player_has_no_reports = "La cible n'a pas de signalements actifs.", + cannot_report_self = "Vous ne pouvez pas signaler vous-même.", + cannot_report_admin = "Vous ne pouvez pas signaler un admin.", + cannot_report_as_admin = "Vous êtes un admin ; vous ne pouvez pas signaler de joueurs !", + claimed_report_still_active = "Vous avez déjà un rapport d'accepté ; finissez-le d'abord !", + select_reason_first = "Choisissez la raison de votre signalement.", + select_player_first = "Choisissez le joueur à signaler.", + there_are_x_reports_pending = "Il y a %d signalements en attente.", +} + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/language/german.lua b/garrysmod/addons/shreports/lua/reports/language/german.lua new file mode 100644 index 0000000..269c00b --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/language/german.lua @@ -0,0 +1,67 @@ +SH_REPORTS.Language = { + reports = "Meldungen", + your_reports = "Deine Meldungen", + report_list = "Meldungsliste", + reporter = "Melder", + reported_player = "Gemeldeter Spieler", + reason = "Grund", + waiting_time = "Wartezeit", + claimed = "Angenommen?", + unclaimed = "Nicht angenommen", + + new_report = "Neue Meldung", + player_to_report = "Täter", + comment = "Kommentar", + submit_report = "Meldung einreichen", + + view_report = "Meldung ansehen", + actions = "Aktionen", + goto = "Hingehen", + bring = "Herbringen", + bring_reported_player = "Den gemeldeten Spieler herbringen?", + yes = "Ja", + no = "Nein", + claim_report = "Meldung annehmen", + close_report = "Meldung schließen", + view = "Ansehen", + start_sit_session = "Sitzung starten", + report_of_x = "%ss Meldung", + claimed_by_x = "Angenommen von %s", + other = "Andere", + none = "Nichts", + + performance_reports = "Leistungsberichte", + displaying_perf_report_from_x_to_y = "Leistungsberichte von %s bis %s werden dargestellt.", + admin = "Admin", + num_claimed = "Angenommen", + num_closed = "Geschlossen", + time_spent = "Zeit verbracht", + rating = "Beliebtheit", + rate_question = "Wie würden Sie %ss Performance bewerten?", + rate_thanks = "Danke für Ihre Bewertung!", + rate_notification = "%s hat Ihnen eine %s Bewertung gegeben.", + history = "Chronikeinträge", + + not_allowed_to_run_cmd = "Sie haben nicht die Erlaubnis diesen Befehl auszuführen", + report_submitted = "Ihre Meldung wurde eingereicht. Bitte", + report_limit_reached = "Sie haben das Limit an Meldungen die sie erstellen können erreicht", + report_received = "Neue Meldung von %s gegen %s: %s", + report_claimed = "Meldung angenommen!", + admin_claimed_your_report = "Ein Admin hat Ihre Meldung angenommen!", + admin_has_disconnected = "Der Admin der Ihre Meldung bearbeitet hat den Server verlassen.", + report_closed = "Meldung geschlossen.", + your_report_was_closed = "Ihre Meldung wurde von einem Admin geschlossen.", + reporter_closed_report = "Der Melder hat seine Meldung geschlossen.", + report_already_claimed = "Diese Meldung wurde bereits angenommen.", + report_non_existent = "Diese Meldung existiert nicht.", + player_has_no_reports = "Spieler hat keine aktiven Meldungen.", + cannot_report_self = "Sie können sich nicht selbst melden.", + cannot_report_admin = "Sie können keinen Admin melden.", + cannot_report_as_admin = "Sie sind ein Admin; sie können keine Meldungen erstellen!", + claimed_report_still_active = "Sie haben eine Meldung bereits angenommen. Erledigen Sie diese zuerst!", + select_reason_first = "Wählen Sie einen Grund für Ihre Meldung aus.", + select_player_first = "Wählen Sie einen Spieler zum Melden aus.", + there_are_x_reports_pending = "Es sind %d Meldungen unerledigt.", +} + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/lib_database.lua b/garrysmod/addons/shreports/lua/reports/lib_database.lua new file mode 100644 index 0000000..09964dc --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/lib_database.lua @@ -0,0 +1,121 @@ +local base_table = SH_REPORTS +local prefix = "SH_REPORTS." + +base_table.DatabaseConfig = { + host = "localhost", + user = "root", + password = "", + database = "mysql", + port = 3306, +} + +function base_table:DBPrint(s) + local src = debug.getinfo(1) + local _, __, name = src.short_src:find("addons/(.-)/") + MsgC(Color(0, 200, 255), "[" .. name:upper() .. " DB] ", color_white, s, "\n") +end + +function base_table:ConnectToDatabase() + local dm = self.DatabaseMode + if (dm == "mysqloo") then + require("mysqloo") + + local cfg = self.DatabaseConfig + + self:DBPrint("Connecting to database") + + local db = mysqloo.connect(cfg.host, cfg.user, cfg.password, cfg.database, cfg.port) + db:setAutoReconnect(true) + db:setMultiStatements(true) + db.onConnected = function() + self:DBPrint("Connected to database!") + self.m_bConnectedToDB = true + + if (self.DatabaseConnected) then + self:DatabaseConnected() + end + end + db.onConnectionFailed = function(me, err) + self:DBPrint("Failed to connect to database: " .. err .. "\n") + print(err) + self.m_bConnectedToDB = false + end + db:connect() + _G[prefix .. "DB"] = db + else + self:DBPrint("Defaulting to sqlite") + self.m_bConnectedToDB = true + + if (self.DatabaseConnected) then + self:DatabaseConnected() + end + end +end + +function base_table:IsConnectedToDB() + return self.m_bConnectedToDB +end + +function base_table:Query(query, callback) + if (!self:IsConnectedToDB()) then + return end + + local dm = self.DatabaseMode + + callback = callback or function(q, ok, ret) end + + if (dm == "mysqloo") then + local q = _G[prefix .. "DB"]:query(query) + q.onSuccess = function(me, data) + _SH_QUERY_LAST_INSERT_ID = me:lastInsert() + callback(query, true, data) + _SH_QUERY_LAST_INSERT_ID = nil + end + q.onError = function(me, err, fq) + callback(query, false, err .. " (" .. fq .. ")") + self:DBPrint(err .. " (" .. fq .. ")") + end + q:start() + else + local d = sql.Query(query) + if (d ~= false) then + callback(query, true, d or {}) + else + callback(query, false, sql.LastError()) + print("sqlite error (" .. query .. "): " .. sql.LastError()) + end + end +end + +function base_table:Escape(s) + local dm = self.DatabaseMode + if (dm == "mysqloo") then + return _G[prefix .. "DB"]:escape(s) + else + return sql.SQLStr(s, true) + end +end + +function base_table:BetterQuery(query, args, callback) + for k, v in pairs (args) do + if (isstring(v)) then + v = self:Escape(v) + end + v = tostring(v) + + query = query:Replace("{" .. k .. "}", "'" .. v .. "'") + query = query:Replace("[" .. k .. "]", v) + end + + self:Query(query, callback) +end + +hook.Add("InitPostEntity", prefix .. "ConnectToDatabase", function() + base_table:ConnectToDatabase() +end) + +if (_G[prefix .. "DB"]) then + base_table.m_bConnectedToDB = _G[prefix .. "DB"]:status() == 0 +end + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/lib_easynet.lua b/garrysmod/addons/shreports/lua/reports/lib_easynet.lua new file mode 100644 index 0000000..ba25dde --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/lib_easynet.lua @@ -0,0 +1,143 @@ +AddCSLuaFile() + +EASYNET_STRING = 1 +EASYNET_FLOAT = 2 +EASYNET_BOOL = 3 +EASYNET_UINT8 = 4 +EASYNET_UINT16 = 5 +EASYNET_UINT32 = 6 +EASYNET_STRUCTURES = 7 +EASYNET_PLAYER = 8 + +module("easynet", package.seeall) + +local Structures = {} +local Creating + +function Start(id) + Creating = { + id = id, + nid = #Structures + 1, + args = {}, + } +end + +function Add(name, type) + Creating.args[name] = {id = table.Count(Creating.args) + 1, type = type} +end + +function Register() + local id = Creating.id + Structures[id] = table.Copy(Creating) + + if (SERVER) then + util.AddNetworkString(id) + end +end + +local function read(typ, name) + if (typ == EASYNET_STRING) then + return net.ReadString() + elseif (typ == EASYNET_FLOAT) then + return net.ReadFloat() + elseif (typ == EASYNET_BOOL) then + return net.ReadBool() + elseif (typ == EASYNET_UINT8) then + return net.ReadUInt(8) + elseif (typ == EASYNET_UINT16) then + return net.ReadUInt(16) + elseif (typ == EASYNET_UINT32) then + return net.ReadUInt(32) + elseif (typ == EASYNET_STRUCTURES) then + local t = {} + local struct = Structures[name] + + for i = 1, net.ReadUInt(16) do + t[i] = {} + + for n, arg in SortedPairsByMemberValue (struct.args, "id") do + t[i][n] = read(arg.type, n) + end + end + + return t + elseif (typ == EASYNET_PLAYER) then + return Player(net.ReadUInt(16)) + end +end + +function Callback(id, cb) + net.Receive(id, function(len, ply) + if (_EASYNET_DEBUG) then + print("[EasyNet][Rcv][" .. id .. "] " .. (len / 8) .. " bytes") + end + + local struct = Structures[id] + + local data = {} + for name, arg in SortedPairsByMemberValue (struct.args, "id") do + data[name] = read(arg.type, name) + end + + cb(data, ply) + end) +end + +local function write(val, typ, name) + if (typ == EASYNET_STRING) then + net.WriteString(val) + elseif (typ == EASYNET_FLOAT) then + net.WriteFloat(val) + elseif (typ == EASYNET_BOOL) then + net.WriteBool(val) + elseif (typ == EASYNET_UINT8) then + if (isstring(val)) then + val = tonumber(val) or 0 + end + + net.WriteUInt(val, 8) + elseif (typ == EASYNET_UINT16) then + net.WriteUInt(val, 16) + elseif (typ == EASYNET_UINT32) then + net.WriteUInt(val, 32) + elseif (typ == EASYNET_STRUCTURES) then + local struct = Structures[name] + + net.WriteUInt(table.Count(val), 16) + + for k, v in pairs (val) do + for n, arg in SortedPairsByMemberValue (struct.args, "id") do + write(v[n], arg.type, n) + end + end + elseif (typ == EASYNET_PLAYER) then + net.WriteUInt(IsValid(val) and val:UserID() or 0, 16) + end +end + +local function prepare(id, data) + local struct = Structures[id] + + net.Start(id) + for name, arg in SortedPairsByMemberValue (struct.args, "id") do + write(data[name], arg.type, name) + end + + if (_EASYNET_DEBUG) then + print("[EasyNet][Send][" .. id .. "] " .. net.BytesWritten() .. " bytes") + end +end + +if (SERVER) then + function Send(rec, id, data) + prepare(id, data) + net.Send(rec) + end +else + function SendToServer(id, data) + prepare(id, data) + net.SendToServer() + end +end + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/lib_loungeui.lua b/garrysmod/addons/shreports/lua/reports/lib_loungeui.lua new file mode 100644 index 0000000..b76b958 --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/lib_loungeui.lua @@ -0,0 +1,697 @@ + + +local base_table = SH_REPORTS +local font_prefix = "SH_REPORTS." + +-- +local matClose = Material("shenesis/general/close.png", "noclamp smooth") + +local function get_scale() + local sc = math.Clamp(ScrH() / 1080, 0.75, 1) + if (!th) then + th = 48 * sc + m = th * 0.25 + end + + return sc +end + +function base_table:L(s, ...) + return string.format(self.Language[s] or s, ...) +end + +function base_table:GetPadding() + return th +end + +function base_table:GetMargin() + return m +end + +function base_table:GetScreenScale() + return get_scale() +end + +function base_table:CreateFonts(scale) + local font = self.Font + local font_bold = self.FontBold + + local sizes = { + [12] = "Small", + [16] = "Medium", + [20] = "Large", + [24] = "Larger", + [32] = "Largest", + [200] = "3D", + } + + for s, n in pairs (sizes) do + surface.CreateFont(font_prefix .. n, {font = font, size = s * scale}) + surface.CreateFont(font_prefix .. n .. "B", {font = font_bold, size = s * scale}) + end +end + +hook.Add("InitPostEntity", font_prefix .. "CreateFonts", function() + base_table:CreateFonts(get_scale()) +end) + +function base_table:MakeWindow(title) + local scale = get_scale() + local styl = self.Style + + local pnl = vgui.Create("EditablePanel") + pnl.m_bDraggable = true + pnl.SetDraggable = function(me, b) + me.m_bDraggable = b + end + pnl.Paint = function(me, w, h) + if (me.m_fCreateTime) then + Derma_DrawBackgroundBlur(me, me.m_fCreateTime) + end + + draw.RoundedBox(4, 0, 0, w, h, styl.bg) + end + pnl.OnClose = function() end + pnl.Close = function(me) + if (me.m_bClosing) then + return end + + me.m_bClosing = true + me:AlphaTo(0, 0.1, 0, function() + me:Remove() + end) + me:OnClose() + end + + local header = vgui.Create("DPanel", pnl) + header:SetTall(th) + header:Dock(TOP) + header.Paint = function(me, w, h) + draw.RoundedBoxEx(4, 0, 0, w, h, styl.header, true, true, false, false) + end + header.Think = function(me) + if (me.Hovered and pnl.m_bDraggable) then + me:SetCursor("sizeall") + end + + local drag = me.m_Dragging + if (drag) then + local mx, my = math.Clamp(gui.MouseX(), 1, ScrW() - 1), math.Clamp(gui.MouseY(), 1, ScrH() - 1) + local x, y = mx - drag[1], my - drag[2] + + pnl:SetPos(x, y) + end + end + header.OnMousePressed = function(me) + if (pnl.m_bDraggable) then + me.m_Dragging = {gui.MouseX() - pnl.x, gui.MouseY() - pnl.y} + me:MouseCapture(true) + end + end + header.OnMouseReleased = function(me) + me.m_Dragging = nil + me:MouseCapture(false) + end + pnl.m_Header = header + + local titlelbl = self:QuickLabel(title, font_prefix .. "Larger", styl.text, header) + titlelbl:Dock(LEFT) + titlelbl:DockMargin(m, 0, 0, 0) + pnl.m_Title = titlelbl + + local close = vgui.Create("DButton", header) + close:SetText("") + close:SetWide(th) + close:Dock(RIGHT) + close.Paint = function(me, w, h) + if (me.Hovered) then + draw.RoundedBoxEx(4, 0, 0, w, h, styl.close_hover, false, true, false, false) + end + + if (me:IsDown()) then + draw.RoundedBoxEx(4, 0, 0, w, h, styl.hover, false, true, false, false) + end + + surface.SetDrawColor(me:IsDown() and styl.text_down or styl.text) + surface.SetMaterial(matClose) + surface.DrawTexturedRectRotated(w * 0.5, h * 0.5, 16 * scale, 16 * scale, 0) + end + close.DoClick = function(me) + pnl:Close() + end + pnl.m_Close = close + + pnl.AddHeaderButton = function(me, icon, callback) + local btn = vgui.Create("DButton", header) + btn:SetText("") + btn:SetWide(self:GetPadding()) + btn:Dock(RIGHT) + btn.Paint = function(me, w, h) + if (me.Hovered) then + surface.SetDrawColor(styl.hover) + surface.DrawRect(0, 0, w, h) + end + + surface.SetMaterial(icon) + surface.SetDrawColor(styl.text) + surface.DrawTexturedRect(w * 0.5 - 8, h * 0.5 - 8, 16, 16) + end + btn.DoClick = function() + callback() + end + + return btn + end + + return pnl +end + +function base_table:QuickLabel(t, f, c, p) + local l = vgui.Create("DLabel", p) + l:SetText(t) + l:SetFont(f:Replace("{prefix}", font_prefix)) + l:SetColor(c) + l:SizeToContents() + + return l +end + +function base_table:QuickButton(t, cb, p, f, c) + p:SetMouseInputEnabled(true) + + local styl = self.Style + + local b = vgui.Create("DButton", p) + b:SetText(t) + b:SetFont((f or "{prefix}Medium"):Replace("{prefix}", font_prefix)) + b:SetColor(c or styl.text) + b:SizeToContents() + b.DoClick = function(me) + cb(me) + end + b.Paint = function(me, w, h) + local r = me.m_iRound or 4 + + draw.RoundedBox(r, 0, 0, w, h, me.m_Background or styl.inbg) + + if (!me.m_bNoHover and me.Hovered) then + draw.RoundedBox(r, 0, 0, w, h, styl.hover) + end + + if (me.IsDown and me:IsDown()) then + draw.RoundedBox(r, 0, 0, w, h, styl.hover) + end + end + + return b +end + +function base_table:ButtonStyle(b, f, c) + local styl = self.Style + + b:SetFont((f or "{prefix}Medium"):Replace("{prefix}", font_prefix)) + b:SetContentAlignment(5) + b:SetColor(c or styl.text) + b.Paint = function(me, w, h) + local r = me.m_iRound or 4 + + draw.RoundedBox(r, 0, 0, w, h, me.m_Background or styl.inbg) + + if (!me.m_bNoHover and me.Hovered) then + draw.RoundedBox(r, 0, 0, w, h, styl.hover) + end + + if (me.IsDown and me:IsDown()) then + draw.RoundedBox(r, 0, 0, w, h, styl.hover) + end + end +end + +function base_table:LineStyle(l) + local styl = self.Style + + for _, v in pairs (l.Columns) do + if (v.SetFont) then + v:SetContentAlignment(5) + v:SetFont(font_prefix .. "Medium") + v:SetTextColor(styl.text) + end + end + + l.Paint = function(me, w, h) + if (!me:GetAltLine()) then + surface.SetAlphaMultiplier(math.min(me:GetAlpha() / 255, 0.5)) + surface.SetDrawColor(styl.inbg) + surface.DrawRect(0, 0, w, h) + surface.SetAlphaMultiplier(me:GetAlpha() / 255) + end + + if (me:IsSelectable() and me:IsLineSelected()) then + surface.SetDrawColor(styl.hover) + surface.DrawRect(0, 0, w, h) + elseif (me.Hovered or me:IsChildHovered()) then + surface.SetDrawColor(styl.hover2) + surface.DrawRect(0, 0, w, h) + end + end +end + +function base_table:QuickEntry(tx, parent) + parent:SetMouseInputEnabled(true) + parent:SetKeyboardInputEnabled(true) + + local styl = self.Style + + local entry = vgui.Create("DTextEntry", parent) + entry:SetText(tx or "") + entry:SetFont(font_prefix .. "Medium") + entry:SetDrawLanguageID(false) + entry:SetUpdateOnType(true) + entry:SetTextColor(styl.text) + entry:SetHighlightColor(styl.header) + entry:SetCursorColor(styl.text) + entry.Paint = function(me, w, h) + draw.RoundedBox(4, 0, 0, w, h, styl.textentry) + me:DrawTextEntryText(me:GetTextColor(), me:GetHighlightColor(), me:GetCursorColor()) + end + + return entry +end + +function base_table:QuickComboBox(parent) + parent:SetMouseInputEnabled(true) + + local combo = vgui.Create("DComboBox", parent) + combo.m_bNoHover = true + self:ButtonStyle(combo) + + combo.OldDoClick = combo.DoClick + combo.DoClick = function(me) + me:OldDoClick() + + if (IsValid(me.Menu)) then + for _, v in pairs (me.Menu:GetChildren()[1]:GetChildren()) do -- sdfdsfzz + self:ButtonStyle(v) + v.m_iRound = 0 + end + end + end + + return combo +end + +function base_table:PaintScroll(panel) + local styl = self.Style + + local scr = panel.VBar or panel:GetVBar() + scr.Paint = function(_, w, h) + draw.RoundedBox(4, 0, 0, w, h, /* 76561198398853149 styl.header */ styl.bg) + end + + scr.btnUp.Paint = function(_, w, h) + draw.RoundedBox(4, 2, 0, w - 4, h - 2, styl.inbg) + end + scr.btnDown.Paint = function(_, w, h) + draw.RoundedBox(4, 2, 2, w - 4, h - 2, styl.inbg) + end + + scr.btnGrip.Paint = function(me, w, h) + draw.RoundedBox(4, 2, 0, w - 4, h, styl.inbg) + + if (me.Hovered) then + draw.RoundedBox(4, 2, 0, w - 4, h, styl.hover2) + end + + if (me.Depressed) then + draw.RoundedBox(4, 2, 0, w - 4, h, styl.hover2) + end + end +end + +function base_table:PaintList(ilist) + for _, v in pairs (ilist.Columns) do + self:ButtonStyle(v.Header) + v.DraggerBar:SetVisible(false) + end + + self:PaintScroll(ilist) +end + +function base_table:StringRequest(title, text, callback) + local styl = self.Style + + if (IsValid(_LOUNGE_STRREQ)) then + _LOUNGE_STRREQ:Remove() + end + + local scale = get_scale() + local wi, he = 600 * scale, 160 * scale + + local cancel = vgui.Create("DPanel") + cancel:SetDrawBackground(false) + cancel:StretchToParent(0, 0, 0, 0) + cancel:MoveToFront() + cancel:MakePopup() + + local pnl = self:MakeWindow(title) + pnl:SetSize(wi, he) + pnl:Center() + pnl:MakePopup() + pnl.m_fCreateTime = SysTime() + _LOUNGE_STRREQ = pnl + + cancel.OnMouseReleased = function(me, mc) + if (mc == MOUSE_LEFT) then + pnl:Close() + end + end + cancel.Think = function(me) + if (!IsValid(pnl)) then + me:Remove() + end + end + + local body = vgui.Create("DPanel", pnl) + body:SetDrawBackground(false) + body:Dock(FILL) + body:DockPadding(m, m, m, m) + + local tx = self:QuickLabel(text, font_prefix .. "Large", styl.text, body) + tx:SetContentAlignment(5) + tx:SetWrap(tx:GetWide() > wi - m * 2) + tx:Dock(FILL) + + local apply = vgui.Create("DButton", body) + apply:SetText("OK") + apply:SetColor(styl.text) + apply:SetFont(font_prefix .. "Medium") + apply:Dock(BOTTOM) + apply.Paint = function(me, w, h) + draw.RoundedBox(4, 0, 0, w, h, styl.inbg) + + if (me.Hovered) then + draw.RoundedBox(4, 0, 0, w, h, styl.hover) + end + + if (me:IsDown()) then + draw.RoundedBox(4, 0, 0, w, h, styl.hover) + end + end + + local entry = vgui.Create("DTextEntry", body) + entry:RequestFocus() + entry:SetFont(font_prefix .. "Medium") + entry:SetDrawLanguageID(false) + entry:Dock(BOTTOM) + entry:DockMargin(0, m, 0, m) + entry.Paint = function(me, w, h) + draw.RoundedBox(4, 0, 0, w, h, styl.textentry) + me:DrawTextEntryText(me:GetTextColor(), me:GetHighlightColor(), me:GetCursorColor()) + end + entry.OnEnter = function() + apply:DoClick() + end + + apply.DoClick = function() + pnl:Close() + callback(entry:GetValue()) + end + + pnl.OnFocusChanged = function(me, gained) + if (!gained) then + timer.Simple(0, function() + if (!IsValid(me) or vgui.GetKeyboardFocus() == entry) then + return end + + me:Close() + end) + end + end + + pnl:SetWide(math.max(math.min(tx:GetWide() + m * 2, pnl:GetWide()), th * 2)) + pnl:CenterHorizontal() + + pnl:SetAlpha(0) + pnl:AlphaTo(255, 0.1) +end + +function base_table:Menu() + local styl = self.Style + + if (IsValid(_LOUNGE_MENU)) then + _LOUNGE_MENU:Remove() + end + + local cancel = vgui.Create("DPanel") + cancel:SetDrawBackground(false) + cancel:StretchToParent(0, 0, 0, 0) + cancel:MoveToFront() + cancel:MakePopup() + + local pnl = vgui.Create("DPanel") + pnl:SetDrawBackground(false) + pnl:SetSize(20, 1) + pnl.AddOption = function(me, text, callback) + surface.SetFont(font_prefix .. "MediumB") + local wi, he = surface.GetTextSize(text) + wi = wi + m * 2 + he = he + m + + me:SetWide(math.max(wi, me:GetWide())) + me:SetTall(pnl:GetTall() + he) + + local btn = vgui.Create("DButton", me) + btn:SetText(self:L(text)) + btn:SetFont(font_prefix .. "MediumB") + btn:SetColor(styl.text) + btn:Dock(TOP) + btn:SetSize(wi, he) + btn.Paint = function(me, w, h) + surface.SetDrawColor(styl.menu) + surface.DrawRect(0, 0, w, h) + + if (me.Hovered) then + surface.SetDrawColor(styl.hover) + surface.DrawRect(0, 0, w, h) + end + + if (me:IsDown()) then + surface.SetDrawColor(styl.hover) + surface.DrawRect(0, 0, w, h) + end + end + btn.DoClick = function(me) + if (callback) then + callback() + end + pnl:Close() + end + + return btn + end + pnl.Open = function(me) + me:SetPos(gui.MouseX(), math.min(math.max(0, ScrH() - me:GetTall()), gui.MouseY())) + me:MakePopup() + end + pnl.Close = function(me) + if (me.m_bClosing) then + return end + + me.m_bClosing = true + me:AlphaTo(0, 0.1, 0, function() + me:Remove() + end) + end + _LOUNGE_MENU = pnl + + cancel.OnMouseReleased = function(me, mc) + pnl:Close() + end + cancel.Think = function(me) + if (!IsValid(pnl)) then + me:Remove() + end + end + + return pnl +end + +function base_table:PanelPaint(name) + local styl = self.Style + local col = styl[name] or styl.bg + + return function(me, w, h) + draw.RoundedBox(4, 0, 0, w, h, col) + end +end + +// https://facepunch.com/showthread.php?t=1522945&p=50524545&viewfull=1#post50524545|76561198398853124 +local sin, cos, rad = math.sin, math.cos, math.rad +local rad0 = rad(0) +local function DrawCircle(x, y, radius, seg) + local cir = { + {x = x, y = y} + } + + for i = 0, seg do + local a = rad((i / seg) * -360) + table.insert(cir, {x = x + sin(a) * radius, y = y + cos(a) * radius}) + end + + table.insert(cir, {x = x + sin(rad0) * radius, y = y + cos(rad0) * radius}) + surface.DrawPoly(cir) +end + +function base_table:Avatar(ply, siz, par) + if (type(ply) == "Entity" and !IsValid(ply)) then + return end + + if (isnumber(ply)) then + ply = tostring(ply) + end + + siz = siz or 32 + local hsiz = siz * 0.5 + + local url = "http://steamcommunity.com/profiles/" .. (isstring(ply) and ply or ply:SteamID64() or "") + + par:SetMouseInputEnabled(true) + + local pnl = vgui.Create("DPanel", par) + pnl:SetSize(siz, siz) + pnl:SetDrawBackground(false) + pnl.Paint = function() end + + local av = vgui.Create("AvatarImage", pnl) + if (isstring(ply)) then + av:SetSteamID(ply, siz) + else + av:SetPlayer(ply, siz) + end + av:SetPaintedManually(true) + av:SetSize(siz, siz) + + local btn = vgui.Create("DButton", av) + btn:SetToolTip("Click here to view " .. (isstring(ply) and "their" or ply:Nick() .. "'s") .. " Steam Profile") + btn:SetText("") + btn:Dock(FILL) + btn.Paint = function() end + btn.DoClick = function(me) + gui.OpenURL(url) + end + + pnl.SetSteamID = function(me, s) + av:SetSteamID(s, siz) + url = "http://steamcommunity.com/profiles/" .. s + end + pnl.SetPlayer = function(me, p) + av:SetPlayer(p, siz) + url = "http://steamcommunity.com/profiles/" .. p:SteamID64() + end + pnl.Paint = function(me, w, h) + render.ClearStencil() + render.SetStencilEnable(true) + + render.SetStencilWriteMask(1) + render.SetStencilTestMask(1) + + render.SetStencilFailOperation(STENCILOPERATION_REPLACE) + render.SetStencilPassOperation(STENCILOPERATION_ZERO) + render.SetStencilZFailOperation(STENCILOPERATION_ZERO) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NEVER) + render.SetStencilReferenceValue(1) + + draw.NoTexture() + surface.SetDrawColor(color_black) + DrawCircle(hsiz, hsiz, hsiz, hsiz) + + render.SetStencilFailOperation(STENCILOPERATION_ZERO) + render.SetStencilPassOperation(STENCILOPERATION_REPLACE) + render.SetStencilZFailOperation(STENCILOPERATION_ZERO) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + render.SetStencilReferenceValue(1) + + av:PaintManual() + + render.SetStencilEnable(false) + render.ClearStencil() + end + + return pnl +end + +local c = {} +function base_table:GetName(sid, cb) + if (c[sid]) then + cb(c[sid]) + return + end + + for _, v in pairs (player.GetAll()) do + if (v:SteamID64() == sid) then + c[sid] = v:Nick() + cb(v:Nick()) + return + end + end + + steamworks.RequestPlayerInfo(sid) + timer.Simple(1, function() + local n = steamworks.GetPlayerName(sid) + c[sid] = n + cb(n) + end) +end + +function base_table:Notify(msg, dur, bg, parent) + if (IsValid(_SH_NOTIFY)) then + _SH_NOTIFY:Close() + end + + dur = dur or 3 + bg = bg or self.Style.header + + local fnt = font_prefix .. "Larger" + + local w, h = ScrW(), ScrH() + if (IsValid(parent)) then + w, h = parent:GetSize() + fnt = font_prefix .. "Large" + end + + local p = vgui.Create("DButton", parent) + p:MoveToFront() + p:SetText(self.Language[msg] or msg) + p:SetFont(fnt) + p:SetColor(self.Style.text) + p:SetSize(w, draw.GetFontHeight(fnt) + self:GetMargin() * 2) + p:AlignTop(h) + p.Paint = function(me, w, h) + surface.SetDrawColor(bg) + surface.DrawRect(0, 0, w, h) + end + p.Close = function(me) + if (me.m_bClosing) then + return end + + me.m_bClosing = true + me:Stop() + me:MoveTo(0, h, 0.2, 0, -1, function() + me:Remove() + end) + end + p.DoClick = p.Close + _SH_NOTIFY = p + + p:MoveTo(0, h - p:GetTall(), 0.2, 0, -1, function() + p:MoveTo(0, h, 0.2, dur, -1, function() + p:Remove() + end) + end) +end + +function base_table:LerpColor(frac, from, to) + return Color(Lerp(frac, from.r, to.r), Lerp(frac, from.g, to.g), Lerp(frac, from.b, to.b), Lerp(frac, from.a, to.a)) +end + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/sh_main.lua b/garrysmod/addons/shreports/lua/reports/sh_main.lua new file mode 100644 index 0000000..4bd5a69 --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/sh_main.lua @@ -0,0 +1,225 @@ +function SH_REPORTS:GetMidnight(offset) + return os.time() - tonumber(os.date("%H")) * 3600 - tonumber(os.date("%M")) * 60 - tonumber(os.date("%S")) + 86400 * (offset or /* 76561198398853124 */ 0) +end + +-- fresh from NEP +local d = { + [86400 * 31] = "mo", + [86400 * 7] = "w", + [86400] = "d", + [3600] = "h", + [60] = "min", + [1] = "s", +} +local c2 = {} +function SH_REPORTS:FullFormatTime(i) + if (c2[i]) then + return c2[i] + end + + local f = i + local t = {} + for ti, s in SortedPairs(d, true) do + local f = math.floor(i / ti) + if (f > 0) then + table.insert(t, f .. s) + i = i - f * ti + end + end + + t = table.concat(t, " ") + c2[f] = t + + return t +end + +function SH_REPORTS:IsAdmin(ply) + return self.Usergroups[ply:GetUserGroup()] ~= nil +end + +-- SERVER -> CLIENT +easynet.Start("SH_REPORTS.SendList") + easynet.Add("server_time", EASYNET_UINT32) + easynet.Add("struct_reports", EASYNET_STRUCTURES) +easynet.Register() + +easynet.Start("SH_REPORTS.Notify") + easynet.Add("msg", EASYNET_STRING) + easynet.Add("positive", EASYNET_BOOL) +easynet.Register() + +easynet.Start("SH_REPORTS.Chat") + easynet.Add("msg", EASYNET_STRING) +easynet.Register() + +easynet.Start("SH_REPORTS.ReportCreated") + easynet.Add("id", EASYNET_UINT32) + easynet.Add("reporter_id", EASYNET_STRING) + easynet.Add("reporter_name", EASYNET_STRING) + easynet.Add("reported_id", EASYNET_STRING) + easynet.Add("reported_name", EASYNET_STRING) + easynet.Add("reason_id", EASYNET_UINT8) + easynet.Add("comment", EASYNET_STRING) + easynet.Add("time", EASYNET_UINT32) + easynet.Add("admin_id", EASYNET_STRING) +easynet.Register() + +easynet.Start("SH_REPORTS.ReportClaimed") + easynet.Add("report_id", EASYNET_UINT8) + easynet.Add("admin_id", EASYNET_STRING) +easynet.Register() + +easynet.Start("SH_REPORTS.ReportClosed") + easynet.Add("report_id", EASYNET_UINT8) +easynet.Register() + +easynet.Start("SH_REPORTS.QuickReport") + easynet.Add("comment", EASYNET_STRING) + easynet.Add("lastkiller", EASYNET_PLAYER) + easynet.Add("lastarrester", EASYNET_PLAYER) +easynet.Register() + +easynet.Start("SH_REPORTS.MinimizeReport") + easynet.Add("report_id", EASYNET_UINT32) +easynet.Register() + +easynet.Start("SH_REPORTS.SendPerfReports") + easynet.Add("struct_perf_reports", EASYNET_STRUCTURES) +easynet.Register() + +easynet.Start("SH_REPORTS.SendPerfReportStaff") + easynet.Add("id", EASYNET_UINT32) + easynet.Add("struct_perf_reports_staff", EASYNET_STRUCTURES) +easynet.Register() + +easynet.Start("SH_REPORTS.ReportsPending") + easynet.Add("num", EASYNET_UINT16) + easynet.Add("struct_reports", EASYNET_STRUCTURES) +easynet.Register() + +easynet.Start("SH_REPORTS.AdminLeft") + easynet.Add("report_id", EASYNET_UINT32) +easynet.Register() + +easynet.Start("SH_REPORTS.PromptRating") + easynet.Add("report_id", EASYNET_UINT32) + easynet.Add("admin_name", EASYNET_STRING) +easynet.Register() + +easynet.Start("SH_REPORTS.SendRatings") + easynet.Add("struct_rating", EASYNET_STRUCTURES) +easynet.Register() + +easynet.Start("SH_REPORTS.SendHistoryList") + easynet.Add("struct_history_steamids", EASYNET_STRUCTURES) + easynet.Add("struct_history_list", EASYNET_STRUCTURES) +easynet.Register() + +easynet.Start("SH_REPORTS.SendReportValid") + easynet.Add("report_id", EASYNET_UINT32) + easynet.Add("valid", EASYNET_BOOL) +easynet.Register() + +-- CLIENT -> SERVER +easynet.Start("SH_REPORTS.PlayerReady") +easynet.Register() + +easynet.Start("SH_REPORTS.NewReport") + easynet.Add("reported_name", EASYNET_STRING) + easynet.Add("reported_id", EASYNET_STRING) + easynet.Add("reason_id", EASYNET_UINT8) + easynet.Add("comment", EASYNET_STRING) +easynet.Register() + +easynet.Start("SH_REPORTS.RequestList") +easynet.Register() + +easynet.Start("SH_REPORTS.ClaimAndTeleport") + easynet.Add("id", EASYNET_UINT32) + easynet.Add("bring", EASYNET_BOOL) + easynet.Add("bring_reported", EASYNET_BOOL) +easynet.Register() + +easynet.Start("SH_REPORTS.Claim") + easynet.Add("id", EASYNET_UINT32) +easynet.Register() + +easynet.Start("SH_REPORTS.ClaimAndCSit") + easynet.Add("id", EASYNET_UINT32) +easynet.Register() + +easynet.Start("SH_REPORTS.CloseReport") + easynet.Add("id", EASYNET_UINT32) +easynet.Register() + +easynet.Start("SH_REPORTS.RequestPerfReports") +easynet.Register() + +easynet.Start("SH_REPORTS.RequestPerfReportStaff" /* 76561198398853149 */) + easynet.Add("id", EASYNET_UINT32) +easynet.Register() + +easynet.Start("SH_REPORTS.RateAdmin") + easynet.Add("report_id", EASYNET_UINT32) + easynet.Add("rating", EASYNET_UINT8) +easynet.Register() + +easynet.Start("SH_REPORTS.RequestStaffRatings") +easynet.Register() + +easynet.Start("SH_REPORTS.RequestReportHistory") +easynet.Register() + +easynet.Start("SH_REPORTS.RequestReportValid") + easynet.Add("report_id", EASYNET_UINT32) +easynet.Register() + +-- STRUCTURES +easynet.Start("struct_reports") + easynet.Add("id", EASYNET_UINT32) + easynet.Add("reporter_id", EASYNET_STRING) + easynet.Add("reporter_name", EASYNET_STRING) + easynet.Add("reported_id", EASYNET_STRING) + easynet.Add("reported_name", EASYNET_STRING) + easynet.Add("reason_id", EASYNET_UINT8) + easynet.Add("comment", EASYNET_STRING) + easynet.Add("time", EASYNET_UINT32) + easynet.Add("admin_id", EASYNET_STRING) +easynet.Register() + +easynet.Start("struct_perf_reports") + easynet.Add("id", EASYNET_UINT32) + easynet.Add("start_time", EASYNET_UINT32) + easynet.Add("end_time", EASYNET_UINT32) +easynet.Register() + +easynet.Start("struct_perf_reports_staff") + easynet.Add("steamid", EASYNET_STRING) + easynet.Add("claimed", EASYNET_UINT16) + easynet.Add("closed", EASYNET_UINT16) + easynet.Add("timespent", EASYNET_UINT16) +easynet.Register() + +easynet.Start("struct_rating") + easynet.Add("steamid", EASYNET_STRING) + easynet.Add("total", EASYNET_UINT32) + easynet.Add("num", EASYNET_UINT16) +easynet.Register() + +easynet.Start("struct_history_steamids") + easynet.Add("steamid", EASYNET_STRING) +easynet.Register() + +easynet.Start("struct_history_list") + easynet.Add("report_id", EASYNET_UINT32) + easynet.Add("reporter_nid", EASYNET_UINT16) + easynet.Add("reported_nid", EASYNET_UINT16) + easynet.Add("reason", EASYNET_STRING) + easynet.Add("comment", EASYNET_STRING) + easynet.Add("rating", EASYNET_UINT8) + easynet.Add("date", EASYNET_UINT32) + easynet.Add("waiting_time", EASYNET_UINT16) + easynet.Add("admin_nid", EASYNET_UINT16) +easynet.Register() + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports/sv_main.lua b/garrysmod/addons/shreports/lua/reports/sv_main.lua new file mode 100644 index 0000000..96cacc3 --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports/sv_main.lua @@ -0,0 +1,973 @@ +if (!SH_REPORTS.ActiveReports) then + SH_REPORTS.ActiveReports = {} + SH_REPORTS.UniqueID = 0 + SH_REPORTS.InsertSQL = "INSERT IGNORE INTO" +end + +if (SH_REPORTS.UseWorkshop) then +-- resource.AddWorkshop("1141886968") +else + resource.AddFile("materials/shenesis/general/back.png") + resource.AddFile("materials/shenesis/general/close.png") + resource.AddFile("materials/shenesis/reports/add.png") + resource.AddFile("materials/shenesis/reports/stats.png") + resource.AddFile("materials/shenesis/reports/star.png") + resource.AddFile("resource/fonts/circular.ttf") + resource.AddFile("resource/fonts/circular_bold.ttf") +end + +function SH_REPORTS:DatabaseConnected() + if (self.DatabaseMode == "mysqloo") then + self:Query("SHOW TABLES LIKE 'sh_reports_performance'", function(q, ok, data) + if (!ok) or (data and table.Count(data) > 0) then + self:PostDatabaseConnected() + return + end + + self:Query([[ + CREATE TABLE `sh_reports_performance` ( + `steamid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `claimed` int(10) unsigned DEFAULT '0', + `closed` int(10) unsigned DEFAULT '0', + `timespent` int(10) unsigned DEFAULT '0', + `report_id` int(10) unsigned DEFAULT '0', + UNIQUE KEY `steamid_UNIQUE` (`steamid`,`report_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + CREATE TABLE `sh_reports_performance_reports` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `start_time` int(10) unsigned DEFAULT '0', + `end_time` int(10) unsigned DEFAULT '0', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + ]], function(q2, ok2, data2) + self:DBPrint("Creating sh_reports_performance and sh_reports_performance_reports: " .. tostring(ok2) .. " (" .. tostring(data2) /* 76561198398853149 */ .. ")") + self:PostDatabaseConnected() + end) + end) + + self:Query("SHOW TABLES LIKE 'sh_reports_performance_ratings'", function(q, ok, data) + if (!ok) or (data and table.Count(data) > 0) then + return end + + self:Query([[ + CREATE TABLE `sh_reports_performance_ratings` ( + `steamid` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `total` int(10) unsigned DEFAULT '0', + `num` int(10) unsigned DEFAULT '0', + PRIMARY KEY (`steamid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + ]], function(q2, ok2, data2) + self:DBPrint("Creating sh_reports_performance_ratings: " .. tostring(ok2) .. " (" .. tostring(data2) /* 76561198398853124 */ .. ")") + end) + end) + + self:Query("SHOW TABLES LIKE 'sh_reports_performance_history'", function(q, ok, data) + if (!ok) or (data and table.Count(data) > 0) then + return end + + self:Query([[ + CREATE TABLE `sh_reports_performance_history` ( + `id` int(10) unsigned NOT NULL, + `reporter` varchar(64) NOT NULL, + `reported` varchar(64) NOT NULL, + `reason` varchar(256), + `comment` varchar(2048), + `waiting_time` int(10) unsigned DEFAULT '0', + `date` int(10) unsigned DEFAULT '0', + `admin` varchar(64) NOT NULL, + `rating` int(10) unsigned DEFAULT '0', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + ]], function(q2, ok2, data2) + self:DBPrint("Creating sh_reports_performance_history: " .. tostring(ok2) .. " (" .. tostring(data2) .. ")") + end) + end) + else + local function CreateTable(name, query) + if (!sql.TableExists(name)) then + sql.Query([[ + CREATE TABLE `]] .. name .. [[` (]] .. query .. [[) + ]]) + + self:DBPrint("Creating " .. name .. ": " .. tostring(sql.TableExists(name))) + end + end + + CreateTable("sh_reports_performance", [[ + `steamid` varchar(64) NOT NULL, + `claimed` int(10) DEFAULT '0', + `closed` int(10) DEFAULT '0', + `timespent` int(10) DEFAULT '0', + `report_id` int(10) DEFAULT '0', + UNIQUE(steamid, report_id) ON CONFLICT IGNORE + ]]) + + CreateTable("sh_reports_performance_reports", [[ + `id` int(10) NOT NULL PRIMARY KEY, + `start_time` int(10) DEFAULT '0', + `end_time` int(10) DEFAULT '0' + ]]) + + CreateTable("sh_reports_performance_ratings", [[ + `steamid` varchar(64) NOT NULL PRIMARY KEY, + `total` int(10) DEFAULT '0', + `num` int(10) DEFAULT '0' + ]]) + + CreateTable("sh_reports_performance_history", [[ + `id` int(10) NOT NULL PRIMARY KEY, + `reporter` varchar(64) NOT NULL, + `reported` varchar(64) NOT NULL, + `reason` varchar(256), + `comment` varchar(2048), + `waiting_time` int(10) DEFAULT '0', + `date` int(10) DEFAULT '0', + `admin` varchar(64) NOT NULL, + `rating` int(5) DEFAULT '0' + ]]) + + self.InsertSQL = "INSERT OR IGNORE INTO" + self:PostDatabaseConnected() + end +end + +function SH_REPORTS:PostDatabaseConnected() + self:BetterQuery("SELECT * FROM sh_reports_performance_reports WHERE {time} < end_time ORDER BY id DESC", {time = os.time()}, function(q, ok, data) + if (!ok) then + return end + + if (data and #data > 0) then + local d = table.Copy(data[1]) + d.id = tonumber(d.id) + d.start_time = tonumber(d.start_time) + d.end_time = tonumber(d.end_time) + + self.CurrentPerfReport = d + self:DBPrint("Using performance report #" .. d.id .. ". It will last until " .. os.date(self.DateFormat, d.end_time) .. " 00:00.") + else + self:DBPrint("Creating new performance report as none were found.") + self:CreatePerformanceReport() + end + end) + + if (self.StorageExpiryTime > 0) then + self:BetterQuery("DELETE FROM sh_reports_performance_history WHERE {time} > date", {time = os.time() - self.StorageExpiryTime}) + end +end + +function SH_REPORTS:CreatePerformanceReport() + local days = 1 + if (self.PerformanceFrequency == "weekly") then + days = 7 - tonumber(os.date("%w")) + self.PerformanceWeekDay + elseif (self.PerformanceFrequency == "monthly") then + days = 31 + end + local mthen = self:GetMidnight(days) + + self:Query("SELECT id FROM sh_reports_performance_reports", function(q, ok, data) + if (!ok) then + return end + + local d = {id = table.Count(data) + 1, start_time = os.time(), end_time = mthen} + self.CurrentPerfReport = d + + self:BetterQuery([[ + INSERT INTO sh_reports_performance_reports (id, start_time, end_time) + VALUES ({id}, {start_time}, {end_time}) + ]], d) + + self:DBPrint("Created performance report #" .. d.id .. ". It will last until " .. os.date(self.DateFormat, mthen) .. " 00:00.") + end) + + self.CachedPerfReports = nil +end + +function SH_REPORTS:NewReport(ply, data) + if (self:IsAdmin(ply) and !self.StaffCanReport) then + self:Notify(ply, "cannot_report_as_admin", false) + return + end + + if (data.reporter_id == data.reported_id) then + self:Notify(ply, "cannot_report_self", false) + return + end + + local target = player.GetBySteamID64(data.reported_id) + if (IsValid(target) and self:IsAdmin(target) and !self.StaffCanBeReported) then + self:Notify(ply, "cannot_report_admin", false) + return + end + + local sid = ply:SteamID64() + if (table.Count(self:GetAllReports(sid)) >= self.MaxReportsPerPlayer) then + self:Notify(ply, "report_limit_reached", false) + return + end + + if (data.reported_id == "0" and !self.CanReportOther) then + return end + + self.UniqueID = self.UniqueID + 1 + data.id = self.UniqueID + self.ActiveReports[data.id] = table.Copy(data) + + self:Notify(ply, "report_submitted", true) + self:Log(ply:Nick() .. " <" .. ply:SteamID() .. "> reported [#" .. data.id .. "] " .. data.reported_name .. " <" .. util.SteamIDFrom64(data.reported_id /* 76561198398853124 */) .. "> for " .. self.ReportReasons[data.reason_id]) + + easynet.Send(self:GetStaff(), "SH_REPORTS.ReportCreated", data) +end + +function SH_REPORTS:PlayerSay(ply, str) + local text = str:Replace("!", "/"):lower():Trim() + + if (self.AdminCommands[text]) then + self:ShowReports(ply) + return "" + end + + if (self.ReportCommands[text]) then + if (!self:IsAdmin(ply) or self.StaffCanReport) then + easynet.Send(ply, "SH_REPORTS.QuickReport", {comment = "", lastkiller = ply.SH_LastKiller, lastarrester = ply.SH_LastArrester}) + else + self:Notify(ply, "cannot_report_as_admin", false) + end + + return "" + end + + if (self.EnableQuickReport and !self:IsAdmin(ply) and text:StartWith("@")) then + easynet.Send(ply, "SH_REPORTS.QuickReport", {comment = str:sub(2), lastkiller = ply.SH_LastKiller, lastarrester = ply.SH_LastArrester}) + return "" + end + + if (text == "/reportstats") then + if (self:IsAdmin(ply)) then + self:BetterQuery("SELECT * FROM sh_reports_performance WHERE steamid = {steamid}", {steamid = ply:SteamID64()}, function(q, ok, data) + if (!ok or !IsValid(ply)) then + return end + + local claimed = 0 + local closed = 0 + for _, d in pairs (data) do + claimed = claimed + tonumber(d.claimed) + closed = closed + tonumber(d.closed) + end + + ply:ChatPrint("Reports claimed: " .. string.Comma(claimed) .. " | Reports closed: " .. string.Comma(closed)) + end) + end + + return "" + end +end + +function SH_REPORTS:ShowReports(ply) + local tosend = {} + if (self:IsAdmin(ply)) then -- If admin, send all reports, if not only send own + tosend = self:GetAllReports() + else + tosend = self:GetAllReports(ply:SteamID64()) + end + + easynet.Send(ply, "SH_REPORTS.SendList", { + server_time = os.time(), + struct_reports = tosend, + }) +end + +local function SendPerfReports(ply, preps) + easynet.Send(ply, "SH_REPORTS.SendPerfReports", { + struct_perf_reports = preps + }) +end + +function SH_REPORTS:ShowPerformanceReports(ply) + if (!self.UsergroupsPerformance[ply:GetUserGroup()]) then + self:Notify(ply, "not_allowed_to_run_cmd", false) + return + end + + if (self.CachedPerfReports) then + SendPerfReports(ply, self.CachedPerfReports) + else + self:BetterQuery("SELECT * FROM sh_reports_performance_reports", {time = os.time()}, function(q, ok, data) + if (!ok or !IsValid(ply)) then + return end + + local d = {} + for k, v in pairs (data) do + d[tonumber(v.id)] = v + end + + self.CachedPerfReports = d + if (IsValid(ply)) then + SendPerfReports(ply, d) + end + end) + end +end + +function SH_REPORTS:RequestPerfReportStaff(ply, id) + if (!self.UsergroupsPerformance[ply:GetUserGroup()]) then + self:Notify(ply, "not_allowed_to_run_cmd", false) + return + end + + self:BetterQuery("SELECT steamid, claimed, closed, timespent FROM sh_reports_performance WHERE report_id = {id}" /* 76561198398853149 */, {id = id}, function(q, ok, data) + if (!ok or !IsValid(ply)) then + return end + + for k, v in pairs (data) do + v.claimed = tonumber(v.claimed) or 0 + v.closed = tonumber(v.closed) or 0 + v.timespent = tonumber(v.timespent) or 0 + end + + easynet.Send(ply, "SH_REPORTS.SendPerfReportStaff", { + id = id, + struct_perf_reports_staff = data + }) + end) +end + +function SH_REPORTS:RequestStaffRatings(ply) + if (!self.UsergroupsPerformance[ply:GetUserGroup()]) then + self:Notify(ply, "not_allowed_to_run_cmd", false) + return + end + + self:BetterQuery("SELECT steamid, num, total FROM sh_reports_performance_ratings" /* 76561198398853124 */, {}, function(q, ok, data) + if (!ok or !IsValid(ply)) then + return end + + easynet.Send(ply, "SH_REPORTS.SendRatings", { + struct_rating = data + }) + end) +end + +function SH_REPORTS:RequestReportHistory(ply) + if (!self.UsergroupsPerformance[ply:GetUserGroup()]) then + self:Notify(ply, "not_allowed_to_run_cmd", false) + return + end + + self:BetterQuery("SELECT * FROM sh_reports_performance_history", {}, function(q, ok, data) + if (!ok or !IsValid(ply)) then + return end + + -- Factorize the SteamID's to save on bytes + local t_steamids = {} + + local t = {} + for _, dat in pairs (data) do + t[tonumber(dat.id)] = dat + + t_steamids[dat.reporter] = true + t_steamids[dat.reported] = true + t_steamids[dat.admin] = true + end + + local steamids = {} + for steamid in pairs (t_steamids) do + t_steamids[steamid] = table.insert(steamids, {steamid = steamid}) + end + + local t_list = {} + for id, dat in pairs (t) do + table.insert(t_list, { + report_id = tonumber(dat.id), + reporter_nid = t_steamids[dat.reporter], + reported_nid = t_steamids[dat.reported], + reason = dat.reason, + comment = dat.comment, + rating = dat.rating, + date = dat.date, + waiting_time = dat.waiting_time, + admin_nid = t_steamids[dat.admin], + }) + end + + easynet.Send(ply, "SH_REPORTS.SendHistoryList", { + struct_history_steamids = steamids, + struct_history_list = t_list, + }) + end) +end + +function SH_REPORTS:ClaimReport(admin, report) + local sid = admin:SteamID64() + for _, rep in pairs (self:GetAllReports()) do + if (rep.admin_id == sid) then + self:Notify(admin, "claimed_report_still_active", false) + return false + end + end + + if (report.admin_id ~= "") then + return false + end + + report.claim_time = os.time() + report.admin_id = sid + self:Notify(player.GetBySteamID64(report.reporter_id), "admin_claimed_your_report", true) + + easynet.Send(admin, "SH_REPORTS.MinimizeReport", {report_id = report.id}) + easynet.Send(self:GetStaff(), "SH_REPORTS.ReportClaimed", {report_id = report.id, admin_id = report.admin_id}) + + if (self.CurrentPerfReport) and (!report.is_admin or self.AdminReportsCount) then + self:BetterQuery([[ + ]] .. self.InsertSQL .. [[ sh_reports_performance (steamid, report_id) + VALUES ({steamid}, {report_id}); + UPDATE sh_reports_performance SET claimed = claimed + 1 + WHERE steamid = {steamid} AND report_id = {report_id} + ]], {steamid = admin:SteamID64(), report_id = self.CurrentPerfReport.id}) + end + + return true +end + +function SH_REPORTS:ClaimAndTeleport(admin, id, bring, bring_reported) + if (!self:IsAdmin(admin)) then + self:Notify(admin, "not_allowed_to_run_cmd", false) + return + end + + local report = self:FindReport(id) + if (!report) then + self:Notify(admin, "report_non_existent", false) + return + end + + if (self.ClaimNoTeleport) then + return end + + local target = player.GetBySteamID64(report.reporter_id) + if (!IsValid(target)) then + return end + + if (!self:ClaimReport(admin, report)) then + return end + + admin.SH_PosBeforeReport = admin:GetPos() + target.SH_PosBeforeReport = target:GetPos() + + if (self.UseULXCommands) then + -- Bad idea? ULX sucks anyways + if (bring) then + ulx.bring(admin, {target}) + else + ulx.goto(admin, target) + end + + if (bring_reported) then + local reported = player.GetBySteamID64(report.reported_id) + if (IsValid(reported)) then + reported.SH_PosBeforeReport = reported:GetPos() + + if (bring) then + ulx.bring(admin, {reported}) + else + ulx.send(admin, reported, target) + end + end + end + else + local a, b = admin, target + if (bring) then + a, b = target, admin + end + + self:TeleportPlayer(a, b:GetPos()) + + if (bring_reported) then + local reported = player.GetBySteamID64(report.reported_id) + if (IsValid(reported)) then + reported.SH_PosBeforeReport = reported:GetPos() + + self:TeleportPlayer(reported, b:GetPos()) + end + end + end + + self:Log(admin:Nick() .. " <" .. admin:SteamID() .. "> claimed " .. target:Nick() .. "'s <" .. target:SteamID() .. "> report [#" .. id .. "]") +end + +function SH_REPORTS:ClaimAndCSit(admin, id) + if (!csitsystem) then + return end + + if (!self:IsAdmin(admin)) then + self:Notify(admin, "not_allowed_to_run_cmd", false) + return + end + + local report = self:FindReport(id) + if (!report) then + self:Notify(admin, "report_non_existent", false) + return + end + + local target = player.GetBySteamID64(report.reporter_id) + if (!IsValid(target)) then + return end + + if (!self:ClaimReport(admin, report)) then + return end + + admin.SH_PosBeforeReport = admin:GetPos() + target.SH_PosBeforeReport = target:GetPos() + + report.sit_id = csitsystem.HandOver(admin, target, player.GetBySteamID64(report.reported_id)) + + self:Log(admin:Nick() .. " <" .. admin:SteamID() .. "> claimed " .. target:Nick() .. "'s <" .. target:SteamID() .. "> report [#" .. id .. "] and started a sit") + -- self:CloseReport(admin, id) +end + +function SH_REPORTS:CloseReport(ply, id) + local report = self:FindReport(id) + if (!report) then + self:Notify(ply, "report_non_existent", false) + return + end + + local sid = ply:SteamID64() + if (self:IsAdmin(ply) and ((report.admin_id == "" and self.CanDeleteWhenUnclaimed) or report.admin_id == sid)) or (report.reporter_id == sid) then + self.ActiveReports[id] = nil + + self:Notify(ply, "report_closed", true) + easynet.Send(self:GetStaff(), "SH_REPORTS.ReportClosed", {report_id = id}) + + local target = player.GetBySteamID64(report.reporter_id) + local admin = player.GetBySteamID64(report.admin_id) + if (IsValid(target)) then + if (report.reporter_id ~= sid) then + self:Notify(target, "your_report_was_closed", true) + elseif (report.admin_id ~= "") then + if (IsValid(admin) and admin ~= ply) then + self:Notify(admin, "reporter_closed_report", true) + end + end + + easynet.Send(target, "SH_REPORTS.ReportClosed", {report_id = id}) + end + + if (!report.is_admin or self.AdminReportsCount) then + if (report.admin_id ~= "") then + local claim_time = os.time() - report.claim_time + + if (self.CurrentPerfReport) then + self:BetterQuery([[ + ]] .. self.InsertSQL .. [[ sh_reports_performance (steamid, report_id, timespent) + VALUES ({steamid}, {report_id}, {timespent}); + UPDATE sh_reports_performance SET closed = closed + 1, timespent = timespent + {timespent} + WHERE steamid = {steamid} AND report_id = {report_id} + ]], {steamid = report.admin_id, report_id = self.CurrentPerfReport.id, timespent = claim_time}) + end + + if (sid == report.reporter_id) then + self:Log(ply:Nick() .. " <" .. ply:SteamID() .. "> closed their own report [#" .. id .. "]") + else + if (IsValid(target) and self.AskRating) then + if (!target.SH_ReportsCompleted) then + target.SH_ReportsCompleted = {} + end + target.SH_ReportsCompleted[id] = ply:SteamID64() + + easynet.Send(target, "SH_REPORTS.PromptRating", {report_id = id, admin_name = ply:Nick()}) + end + + self:Log(ply:Nick() .. " <" .. ply:SteamID() .. "> closed the report [#" .. id .. "] from " .. report.reporter_name .. "<" .. util.SteamIDFrom64(report.reporter_id) .. ">") + end + elseif (self:IsAdmin(ply)) then + if (self.CurrentPerfReport) then + self:BetterQuery([[ + ]] .. self.InsertSQL .. [[ sh_reports_performance (steamid, report_id) + VALUES ({steamid}, {report_id}); + UPDATE sh_reports_performance SET closed = closed + 1 + WHERE steamid = {steamid} AND report_id = {report_id} + ]], {steamid = sid, report_id = self.CurrentPerfReport.id}) + end + + if (sid == report.reporter_id) then + self:Log(ply:Nick() .. " <" .. ply:SteamID() .. "> closed their own UNCLAIMED report [#" .. id .. "]") + else + self:Log(ply:Nick() .. " <" .. ply:SteamID() .. "> closed the UNCLAIMED report [#" .. id .. "] from " .. report.reporter_name .. "<" .. util.SteamIDFrom64(report.reporter_id) .. ">") + end + end + end + + if (report.admin_id ~= "") then + if (report.sit_id) then + csitsystem.EndSit(report.sit_id) + end + + if (self.TeleportPlayersBack) then + if (IsValid(target)) then + self:ReturnPlayer(target) + end + + local admin = player.GetBySteamID64(report.admin_id) + if (IsValid(admin)) then + self:ReturnPlayer(admin) + end + + local reported = player.GetBySteamID64(report.reported_id) + if (IsValid(reported)) then + self:ReturnPlayer(reported) + end + end + + if (self.StoreCompletedReports ~= "none") then + self:BetterQuery([[ + ]] .. self.InsertSQL .. [[ sh_reports_performance_history (id, reporter, reported, reason, comment, waiting_time, date, admin) + VALUES ({id}, {reporter}, {reported}, {reason}, {comment}, {waiting_time}, {date}, {admin}); + ]], {id = id, reporter = report.reporter_id, reported = report.reported_id, reason = self.ReportReasons[report.reason_id], comment = report.comment, waiting_time = os.time() - report.time, date = os.time(), admin = report.admin_id}) + end + end + else + self:Notify(ply, "not_allowed_to_run_cmd", false) + end +end + +function SH_REPORTS:RateAdmin(ply, report_id, rating) + if (!ply.SH_ReportsCompleted or !ply.SH_ReportsCompleted[report_id]) then + self:Notify(ply, "report_non_existent", false) + return + end + + local admin_id = ply.SH_ReportsCompleted[report_id] + rating = math.Clamp(rating, 1, 5) + + self:BetterQuery([[ + ]] .. self.InsertSQL .. [[ sh_reports_performance_ratings (steamid, total, num) + VALUES ({steamid}, 0, 0); + UPDATE sh_reports_performance_ratings SET total = total + {rating}, num = num + 1 + WHERE steamid = {steamid} + ]], {steamid = admin_id, rating = rating}) + + if (self.StoreCompletedReports ~= "none") then + self:BetterQuery([[ + UPDATE sh_reports_performance_history SET rating = {rating} + WHERE id = {id} + ]], {id = report_id, rating = rating}) + end + + if (self.NotifyRating) then + local admin = player.GetBySteamID64(admin_id) + if (IsValid(admin)) then + local rstr = "" + for i = 1, 5 do + rstr = rstr .. (rating >= i and "★" or "☆") + end + + self:Notify(admin, "rate_notification\t" .. ply:Nick() .. "\t" .. rstr, rating >= 3) + end + end + + ply.SH_ReportsCompleted[report_id] = nil + self:Notify(ply, "rate_thanks", true) +end + +function SH_REPORTS:PlayerReady(ply) + if (self.NotifyAdminsOnConnect and self:IsAdmin(ply)) then + local num = 0 + local pending = {} + for id, report in pairs (self:GetAllReports()) do + if (report.admin_id == "") then + num = num + 1 + table.insert(pending, report) + end + end + + if (num > 0) then + easynet.Send(ply, "SH_REPORTS.ReportsPending", {num = num, struct_reports = pending}) + end + end +end + +function SH_REPORTS:PlayerDisconnected(ply) + local sid = ply:SteamID64() + for id, report in pairs (self:GetAllReports()) do + if (report.reporter_id == sid) then + self.ActiveReports[id] = nil + easynet.Send(self:GetStaff(), "SH_REPORTS.ReportClosed", {report_id = id}) + + local admin = player.GetBySteamID64(report.admin_id) + if (IsValid(admin)) then + self:Notify(admin, "reporter_closed_report", false) + end + elseif (self:IsAdmin(ply) and report.admin_id == sid) then + report.admin_id = "" + easynet.Send(self:GetStaff(), "SH_REPORTS.AdminLeft", {report_id = id}) + + local reporter = player.GetBySteamID64(report.reporter_id) + if (IsValid(reporter)) then + easynet.Send(reporter, "SH_REPORTS.AdminLeft", {report_id = id}) + self:Notify(reporter, "admin_has_disconnected", false) + end + end + end +end + +function SH_REPORTS:ReturnPlayer(ply) + if (!ply.SH_PosBeforeReport) then + return end + + ply:SetPos(ply.SH_PosBeforeReport) + ply.SH_PosBeforeReport = nil +end + +function SH_REPORTS:MidnightCheck() + local perf = self.CurrentPerfReport + if (!perf) then + return end + + if (os.time() >= perf.end_time) then + self:DBPrint("Current performance report #" .. perf.id .. " expired, creating new one..") + self:CreatePerformanceReport() + end +end + +function SH_REPORTS:Notify(ply, msg, good) + easynet.Send(ply, "SH_REPORTS.Notify", {msg = msg, positive = good}) +end + +function SH_REPORTS:GetStaff(ply) + local t = {} + for _, v in ipairs (player.GetAll()) do + if (self:IsAdmin(v)) then + table.insert(t, v) + end + end + + return t +end + +local function CheckObstruction(ply, pos) + local t = { + start = pos, + endpos = pos + Vector(0, 0, 72), + mins = Vector(-16, -16, 0), + maxs = Vector(16, 16, 4), + filter = ply + } + + return bit.band(util.PointContents(pos), CONTENTS_SOLID) > 0 or util.TraceHull(t).Hit +end + +local coords = { + Vector(48, 0, 0), + Vector(-48, 0, 0), + Vector(0, 48, 0), + Vector(0, -48, 0), + Vector(48, 48, 0), + Vector(-48, 48, 0), + Vector(48, -48, 0), + Vector(-48, -48, 0), +} + +function SH_REPORTS:TeleportPlayer(ply, pos, exact) + if (!exact) then + if (CheckObstruction(ply, pos)) then + for _, c in ipairs (coords) do + if (!util.TraceLine({start = pos, endpos = pos + c, filter = ents.GetAll()}).Hit and !CheckObstruction(ply, pos + c /* 76561198398853124 */)) then + pos = pos + c + break + end + end + end + end + + ply.SH_PositionBeforeTeleport = ply:GetPos() + ply:SetPos(pos) +end + +function SH_REPORTS:FindReport(id) + return self.ActiveReports[id] +end + +function SH_REPORTS:GetAllReports(author) + local t = {} + for id, report in pairs (self.ActiveReports) do + if (author and report.reporter_id ~= author) then + continue end + + t[id] = report + end + + return t +end + +function SH_REPORTS:Log(s) + if (!self.UseServerLog) then + return end + + ServerLog(s .. "\n") +end + +local function GetPerformanceReport(date, preps) + for id, ps in pairs (preps) do + if (date >= ps[1] and date <= ps[2]) then + return id + end + end +end + +function SH_REPORTS:RebuildPerformance() + local preps = {} + self:BetterQuery("SELECT * FROM sh_reports_performance_reports", {}, function(q, ok, data) + for k, v in pairs (data) do + preps[tonumber(v.id)] = {tonumber(v.start_time), tonumber(v.end_time)} + end + + self:BetterQuery("SELECT * FROM sh_reports_performance_history", {}, function(q, ok, data) + local staffreps = {} + for _, rep in SortedPairsByMemberValue (data, "date") do + local admin = tostring(rep.admin) + staffreps[admin] = staffreps[admin] or {} + + table.insert(staffreps[admin], rep) + end + + local staffpreps = {} + for sid, reps in pairs (staffreps) do + staffpreps[sid] = {} + + for _, rep in pairs (reps) do + local prepid = GetPerformanceReport(tonumber(rep.date), preps) + staffpreps[sid][prepid] = staffpreps[sid][prepid] or {} + table.insert(staffpreps[sid][prepid], rep) + end + end + + for sid, preps in pairs (staffpreps) do + for prepid, reps in pairs (preps) do + local claimed, closed, timespent = 0, 0, 0 + for _, rep in pairs (reps) do + claimed = claimed + 1 + closed = closed + 1 + timespent = timespent + tonumber(rep.waiting_time) + end + + self:BetterQuery("SELECT * FROM sh_reports_performance WHERE steamid = {steamid} AND report_id = {prepid}", {steamid = sid, prepid = prepid}, function(q, ok, data) + if (data and data[1]) then + self:BetterQuery("UPDATE sh_reports_performance SET claimed = {claimed}, closed = {closed}, timespent = {timespent} WHERE steamid = {steamid} AND report_id = {prepid}", {claimed = claimed, closed = closed, timespent = timespent, steamid = sid, prepid = prepid}) + else + self:BetterQuery("INSERT INTO sh_reports_performance (steamid, claimed, closed, timespent, report_id) VALUES ({steamid}, {claimed}, {closed}, {timespent}, {prepid})", {claimed = claimed, closed = closed, timespent = timespent, steamid = sid, prepid = prepid}) + end + end) + end + end + end) + end) +end + +hook.Add("PlayerDisconnected", "SH_REPORTS.PlayerDisconnected", function(ply) + SH_REPORTS:PlayerDisconnected(ply) +end) + +hook.Add("PlayerSay", "SH_REPORTS.PlayerSay", function(ply, str) + local r = SH_REPORTS:PlayerSay(ply, str) + if (r) then + return r + end +end) + +hook.Add("DoPlayerDeath", "SH_REPORTS.DoPlayerDeath", function(ply, atk, dmginfo) + if (IsValid(atk) and atk:IsPlayer() and atk ~= ply) then + ply.SH_LastKiller = atk + end +end) + +hook.Add("playerArrested", "SH_REPORTS.playerArrested", function(ply, time, arrester) + if (IsValid(arrester) and arrester:IsPlayer() and arrester ~= ply) then + ply.SH_LastArrester = arrester + end +end) + +hook.Add("PlayerButtonDown", "SH_REPORTS.PlayerButtonDown", function(ply, btn) + if (!IsFirstTimePredicted()) then + return end + + if (btn == SH_REPORTS.ReportKey) then + if (!SH_REPORTS:IsAdmin(ply) or SH_REPORTS.StaffCanReport) then + easynet.Send(ply, "SH_REPORTS.QuickReport", {comment = "", lastkiller = ply.SH_LastKiller, lastarrester = ply.SH_LastArrester}) + else + SH_REPORTS:Notify(ply, "cannot_report_as_admin", false) + end + elseif (btn == SH_REPORTS.ReportsKey) then + SH_REPORTS:ShowReports(ply) + end +end) + +timer.Create("SH_REPORTS.MidnightCheck", 1, 0, function() + SH_REPORTS:MidnightCheck() +end) + +easynet.Callback("SH_REPORTS.NewReport", function(data, ply) + local report = data + data.reporter_name = ply:Nick() + data.reporter_id = ply:SteamID64() + data.time = os.time() + data.admin_id = "" + data.comment = data.comment:sub(1, SH_REPORTS.MaxCommentLength) + data.is_admin = SH_REPORTS:IsAdmin(ply) + + SH_REPORTS:NewReport(ply, data) +end) + +easynet.Callback("SH_REPORTS.Claim", function(data, ply) + if (!SH_REPORTS:IsAdmin(ply)) then + SH_REPORTS:Notify(ply, "not_allowed_to_run_cmd", false) + return + end + + local report = SH_REPORTS:FindReport(data.id) + if (!report) then + SH_REPORTS:Notify(ply, "report_non_existent", false) + return + end + + SH_REPORTS:ClaimReport(ply, report) +end) + +easynet.Callback("SH_REPORTS.ClaimAndTeleport", function(data, ply) + SH_REPORTS:ClaimAndTeleport(ply, data.id, data.bring, data.bring_reported) +end) + +easynet.Callback("SH_REPORTS.ClaimAndCSit", function(data, ply) + SH_REPORTS:ClaimAndCSit(ply, data.id) +end) + +easynet.Callback("SH_REPORTS.CloseReport", function(data, ply) + SH_REPORTS:CloseReport(ply, data.id) +end) + +easynet.Callback("SH_REPORTS.RequestPerfReports", function(data, ply) + SH_REPORTS:ShowPerformanceReports(ply) +end) + +easynet.Callback("SH_REPORTS.RequestPerfReportStaff", function(data, ply) + SH_REPORTS:RequestPerfReportStaff(ply, data.id) +end) + +easynet.Callback("SH_REPORTS.PlayerReady", function(data, ply) + SH_REPORTS:PlayerReady(ply) +end) + +easynet.Callback("SH_REPORTS.RateAdmin", function(data, ply) + SH_REPORTS:RateAdmin(ply, data.report_id, data.rating) +end) + +easynet.Callback("SH_REPORTS.RequestStaffRatings", function(data, ply) + SH_REPORTS:RequestStaffRatings(ply) +end) + +easynet.Callback("SH_REPORTS.RequestReportHistory", function(data, ply) + SH_REPORTS:RequestReportHistory(ply) +end) + +-- vk.com/urbanichka diff --git a/garrysmod/addons/shreports/lua/reports_config.lua b/garrysmod/addons/shreports/lua/reports_config.lua new file mode 100644 index 0000000..1a0969c --- /dev/null +++ b/garrysmod/addons/shreports/lua/reports_config.lua @@ -0,0 +1,221 @@ +/** +* General configuration +**/ + +-- Usergroups allowed to view/handle reports +SH_REPORTS.Usergroups = { + ["superadmin"] = true, + ["teh.admin"] = true, + ["projectteam"] = true, + ["curator"] = true, + ["disp"] = true, + ["sudo-curator"] = true, + ["assistant"] = true, + ["st.admin"] = true, + ["admin"] = true, + ["st.event"] = true, + ["event"] = true, +} + +-- Usergroups allowed to view performance reports +SH_REPORTS.UsergroupsPerformance = { + ["superadmin"] = true, + ["teh.admin"] = true, + ["projectteam"] = true, + ["curator"] = true, + ["disp"] = true, + ["sudo-curator"] = true, + ["assistant"] = true, +} + +-- Customize your report reasons here. +-- Try to keep them short as they appear in full in the reports list. +SH_REPORTS.ReportReasons = { + "Нарушение правил", + "Другое", + "Вопрос" +} + +-- How many reports can a player make? +SH_REPORTS.MaxReportsPerPlayer = 1 + +-- Play a sound to admins whenever a report is made? +SH_REPORTS.NewReportSound = { + enabled = true, + path = Sound("buttons/button16.wav"), +} + +-- Enable ServerLog support? Any actions related to reports will be ServerLog'd IN ENGLISH if true. +-- NOTE: ServerLogs are in English. +SH_REPORTS.UseServerLog = true + +-- Should admins be able to create reports? +SH_REPORTS.StaffCanReport = true + +-- Can players report admins? +SH_REPORTS.StaffCanBeReported = false + +-- Should admins be able to delete unclaimed reports? +SH_REPORTS.CanDeleteWhenUnclaimed = true + +-- Notify admins when they connect of any unclaimed reports? +SH_REPORTS.NotifyAdminsOnConnect = true + +-- Can players report "Other"? +-- Other is no player in particular; but players can make a report with Other if they want a sit or something. +SH_REPORTS.CanReportOther = true + +-- Use ULX commands for teleporting? (allows returning etc.) +SH_REPORTS.UseULXCommands = false + +-- Key binding to open the Make Report menu. +SH_REPORTS.ReportKey = KEY_F7 + +-- Key binding to open the Report List menu. +SH_REPORTS.ReportsKey = KEY_F8 + +-- Should players be asked for rating the admin after their report gets closed? +SH_REPORTS.AskRating = true + +-- Should admins know whenever a player rates them? +SH_REPORTS.NotifyRating = true + +-- Should players be teleported back to their position after their report gets closed? +SH_REPORTS.TeleportPlayersBack = true + +-- How many pending reports to show on admin's screen? +SH_REPORTS.PendingReportsDispNumber = 3 + +-- Allows admins to claim reports without teleporting? +-- If true, the Goto and Bring commands will be hidden. +SH_REPORTS.ClaimNoTeleport = false + +-- Use Steam Workshop for the custom content? +-- If false, custom content will be downloaded through FastDL. +SH_REPORTS.UseWorkshop = false + +/** +* Command configuration +**/ + +-- Chat commands which can open the View Reports menu (for admins) +-- ! are automatically replaced by / and inputs are made lowercase for convenience. +SH_REPORTS.AdminCommands = { + ["/reports"] = true, + ["/reportlist"] = true, +} + +-- Chat commands which can open the Make Report menu (for players) +-- ! are automatically replaced by / and inputs are made lowercase for convenience. +SH_REPORTS.ReportCommands = { + ["/report"] = true, + ["/rep"] = true, +} + +-- Enable quick reporting with @? +-- Typing "@this guy RDM'd me" would open the Make Report menu with the text as a comment. +-- Might conflict with add-ons relying on @ commands. +-- NOTE: Admins cannot use this feature. +SH_REPORTS.EnableQuickReport = true + +/** +* Performance reports configuration +**/ + +-- How should performance reports be saved? +-- Possible options: sqlite, mysqloo +-- mysqloo requires gmsv_mysqloo to be installed on your server. +-- You can configure MySQL credentials in reports/lib_database.lua +SH_REPORTS.DatabaseMode = "sqlite" + +-- What should be the frequency of performance reports? +-- Possible options: daily, weekly, monthly +SH_REPORTS.PerformanceFrequency = "weekly" + +-- If the above option is weekly, on what day of the week +-- should new performance reports be created? (always at midnight) +-- 0: Sunday +-- 1: Monday +-- 2: Tuesday +-- 3: Wednesday +-- 4: Thursday +-- 5: Friday +-- 6: Saturday +SH_REPORTS.PerformanceWeekDay = 1 + +-- Should reports created by admins count for the performance reports and ratings? +SH_REPORTS.AdminReportsCount = false + +/** +* Storage configuration +**/ + +-- Should reports closed by an admin be stored? +-- Useful if you want to see a past report, and what rating the admin got. +-- Possible options: none, sqlite, mysqloo +-- none disables this feature. +SH_REPORTS.StoreCompletedReports = "sqlite" + +-- Should reports be purged after some time? In seconds. +-- Purges are done on map start to avoid performance loss. +-- Set to 0 to make stored reports never expire. +-- Beware! Too many reports may prevent you from seeing the history properly due to large amounts of data to send. +SH_REPORTS.StorageExpiryTime = 86400 * 7 + +/** +* Advanced configuration +* Edit at your own risk! +**/ + +SH_REPORTS.MaxCommentLength = 2048 + +SH_REPORTS.DateFormat = "%Y/%m/%d" + +SH_REPORTS.TimeFormat = "%Y/%m/%d %H:%M:%S" + +-- When making a report with the "RDM" reason +-- it will automatically select the player who last killed you. +-- If you modify the report reasons above make sure to modify those here as well for convenience. +SH_REPORTS.ReasonAutoTarget = { + ["RDM"] = "killer", +} + +/** +* Theme configuration +**/ + +-- Font to use for normal text throughout the interface. +SH_REPORTS.Font = "Circular Std Medium" + +-- Font to use for bold text throughout the interface. +SH_REPORTS.FontBold = "Circular Std Bold" + +-- Color sheet. Only modify if you know what you're doing +SH_REPORTS.Style = { + header = Color(52, 152, 219, 255), + bg = Color(52, 73, 94, 255), + inbg = Color(44, 62, 80, 255), + + close_hover = Color(231, 76, 60, 255), + hover = Color(255, 255, 255, 10, 255), + hover2 = Color(255, 255, 255, 5, 255), + + text = Color(255, 255, 255, 255), + text_down = Color(0, 0, 0), + textentry = Color(44, 62, 80), + menu = Color(127, 140, 141), + + success = Color(46, 204, 113), + failure = Color(231, 76, 60), + rating = Color(241, 196, 15), +} + +/** +* Language configuration +**/ + +-- Various strings used throughout the add-on. +-- Available languages: english, french, german +-- To add your own language, see the reports/language folder +-- You may need to restart the map after changing the language! +SH_REPORTS.LanguageName = "english" diff --git a/garrysmod/addons/shreports/materials/shenesis/general/back.png b/garrysmod/addons/shreports/materials/shenesis/general/back.png new file mode 100644 index 0000000..a367591 Binary files /dev/null and b/garrysmod/addons/shreports/materials/shenesis/general/back.png differ diff --git a/garrysmod/addons/shreports/materials/shenesis/general/close.png b/garrysmod/addons/shreports/materials/shenesis/general/close.png new file mode 100644 index 0000000..bbc404f Binary files /dev/null and b/garrysmod/addons/shreports/materials/shenesis/general/close.png differ diff --git a/garrysmod/addons/shreports/materials/shenesis/reports/add.png b/garrysmod/addons/shreports/materials/shenesis/reports/add.png new file mode 100644 index 0000000..b11f126 Binary files /dev/null and b/garrysmod/addons/shreports/materials/shenesis/reports/add.png differ diff --git a/garrysmod/addons/shreports/materials/shenesis/reports/star.png b/garrysmod/addons/shreports/materials/shenesis/reports/star.png new file mode 100644 index 0000000..5d1ee62 Binary files /dev/null and b/garrysmod/addons/shreports/materials/shenesis/reports/star.png differ diff --git a/garrysmod/addons/shreports/materials/shenesis/reports/stats.png b/garrysmod/addons/shreports/materials/shenesis/reports/stats.png new file mode 100644 index 0000000..8e669d2 Binary files /dev/null and b/garrysmod/addons/shreports/materials/shenesis/reports/stats.png differ diff --git a/garrysmod/addons/shreports/resource/fonts/circular.ttf b/garrysmod/addons/shreports/resource/fonts/circular.ttf new file mode 100644 index 0000000..21b5600 Binary files /dev/null and b/garrysmod/addons/shreports/resource/fonts/circular.ttf differ diff --git a/garrysmod/addons/shreports/resource/fonts/circular_bold.ttf b/garrysmod/addons/shreports/resource/fonts/circular_bold.ttf new file mode 100644 index 0000000..8738b70 Binary files /dev/null and b/garrysmod/addons/shreports/resource/fonts/circular_bold.ttf differ diff --git a/garrysmod/addons/sui/addon.json b/garrysmod/addons/sui/addon.json new file mode 100644 index 0000000..175e6dd --- /dev/null +++ b/garrysmod/addons/sui/addon.json @@ -0,0 +1,9 @@ +{ + "title" : "SUI", + "type" : "ServerContent", + "tags" : ["fun", "cartoon"], + "ignore" : + [ + ".*" + ] +} \ No newline at end of file diff --git a/garrysmod/addons/sui/lua/includes/modules/sui.lua b/garrysmod/addons/sui/lua/includes/modules/sui.lua new file mode 100644 index 0000000..3567800 --- /dev/null +++ b/garrysmod/addons/sui/lua/includes/modules/sui.lua @@ -0,0 +1,39 @@ +if sui then return end + +AddCSLuaFile() + +sui = {} + +do + local wspace_chs = {} -- whitespace characters except a normal space " " + for k, v in ipairs({0x0c, 0x0a, 0x0d, 0x09, 0x0b}) do + wspace_chs[string.char(v)] = true + end + sui.wspace_chs = wspace_chs + + local cntrl_chs = {string.char(0x7f)} -- control characters + for i = 0x00, 0x1f do + cntrl_chs[string.char(i)] = true + end + sui.cntrl_chs = cntrl_chs +end + +if SERVER then + AddCSLuaFile("sui/libs/tdlib/cl_tdlib.lua") + AddCSLuaFile("sui/libs/bshadows.lua") + AddCSLuaFile("sui/libs/gif_loader.lua") + AddCSLuaFile("sui/libs/png_encoder.lua") + AddCSLuaFile("sui/libs/types.lua") + AddCSLuaFile("sui/cl_base.lua") +else + include("sui/libs/tdlib/cl_tdlib.lua") + include("sui/libs/bshadows.lua") + include("sui/libs/types.lua") + include("sui/cl_base.lua") +end + +if SERVER then + for _, f in ipairs(file.Find("sui/vgui/*.lua", "LUA")) do + AddCSLuaFile("sui/vgui/" .. f) + end +end diff --git a/garrysmod/addons/sui/lua/sui/cl_base.lua b/garrysmod/addons/sui/lua/sui/cl_base.lua new file mode 100644 index 0000000..1d3fdc3 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/cl_base.lua @@ -0,0 +1,930 @@ +local hook = hook +local bit = bit +local math = math + +local Color = Color +local ipairs = ipairs +local RealFrameTime = RealFrameTime + +local color_white = color_white +local color_black = color_black + +local sui = sui + +local isfunction = sui.isfunction +local isstring = sui.isstring + +local floor = math.floor + +function sui.scale(v) + return ScrH() * (v / 900) +end + +function sui.hex_rgb(hex) + hex = tonumber(hex:gsub("^([%w])([%w])([%w])$", "%1%1%2%2%3%3", 1), 16) + + return Color( + bit.band(bit.rshift(hex, 16), 0xFF), + bit.band(bit.rshift(hex, 8), 0xFF), + bit.band(hex, 0xFF) + ) +end + +function sui.rgb_hex(c) + return bit.tohex((c.r * 0x10000) + (c.g * 0x100) + c.b, 6) +end +local rgb_hex = sui.rgb_hex + +function sui.lerp_color(from, to) + local frac = RealFrameTime() * 10 + from.r = Lerp(frac, from.r, to.r) + from.g = Lerp(frac, from.g, to.g) + from.b = Lerp(frac, from.b, to.b) + from.a = Lerp(frac, from.a, to.a) +end + +do + local colors = { + ["41b9ff"] = Color(44, 62, 80), + ["00c853"] = Color(44, 62, 80), + ["181818"] = Color(242, 241, 239), + ["212121"] = Color(242, 241, 239), + } + + function sui.contrast_color(color) + local c = colors[rgb_hex(color)] + if c then return c end + + local luminance = (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255 + return luminance > 0.5 and color_black or color_white + end +end + +do + local SetDrawColor = surface.SetDrawColor + local SetMaterial = surface.SetMaterial + local DrawTexturedRectRotated = surface.DrawTexturedRectRotated + function sui.draw_material(mat, x, y, size, col, rot) + SetDrawColor(col) + + if x == -1 then + x = size / 2 + end + + if y == -1 then + y = size / 2 + end + + if mat then + SetMaterial(mat) + end + + DrawTexturedRectRotated(x, y, size, size, rot or 0) + end +end + +do + local hsv_t = { + [0] = function(v, p, q, t) + return v, t, p + end, + [1] = function(v, p, q, t) + return q, v, p + end, + [2] = function(v, p, q, t) + return p, v, t + end, + [3] = function(v, p, q, t) + return p, q, v + end, + [4] = function(v, p, q, t) + return t, p, v + end, + [5] = function(v, p, q, t) + return v, p, q + end + } + + function sui.hsv_to_rgb(h, s, v) + local i = floor(h * 6) + local f = h * 6 - i + + return hsv_t[i % 6]( + v * 255, -- v + (v * (1 - s)) * 255, -- p + (v * (1 - f * s)) * 255, -- q + (v * (1 - (1 - f) * s)) * 255 -- t + ) + end +end + +local Panel = FindMetaTable("Panel") +local SetSize = Panel.SetSize +local GetWide = Panel.GetWide +local GetTall = Panel.GetTall +function sui.scaling_functions(panel) + local scale_changed + local SUI = CURRENT_SUI + + local dock_top = function(s, h) + if not h then return end + + if not scale_changed then + s.real_h = h + end + + if not s.no_scale then + h = SUI.Scale(h) + end + + if GetTall(s) == h then return end + + SetSize(s, GetWide(s), h) + end + + local dock_right = function(s, w) + if not w then return end + + if not scale_changed then + s.real_w = w + end + + if not s.no_scale then + w = SUI.Scale(w) + end + + if GetWide(s) == w then return end + + SetSize(s, w, GetTall(s)) + end + + local size_changed = function(s, w, h) + if s.using_scale then return end + + s.using_scale = true + + local dock = s:GetDock() + + if dock ~= FILL then + if dock == NODOCK then + dock_top(s, h) + dock_right(s, w) + elseif dock == TOP or dock == BOTTOM then + dock_top(s, h) + else + dock_right(s, w) + end + end + + s.using_scale = nil + end + + local wide_changed = function(s, w) + size_changed(s, w) + end + + local tall_changed = function(s, h) + size_changed(s, nil, h) + end + + function panel:ScaleChanged() + scale_changed = true + size_changed(self, self.real_w, self.real_h) + scale_changed = nil + if self.OnScaleChange then + self:OnScaleChange() + end + end + + local on_remove = function(s) + SUI.RemoveScaleHook(s) + end + + function panel:ScaleInit() + self.SetSize = size_changed + self.SetWide = wide_changed + self.SetTall = tall_changed + SUI.OnScaleChanged(self, self.ScaleChanged) + self:On("OnRemove", on_remove) + end +end + +do + local utf8 = {} + + local str_rel_to_abs = function(str, v, str_n) + return v > 0 and v or math.max(str_n + v + 1, 1) + end + + local utf8_decode = function(str, start_pos, str_n) + start_pos = str_rel_to_abs(str, start_pos or 1, str_n) + + local b1 = str:byte(start_pos, start_pos) + if not b1 then return nil end + if b1 < 0x80 then return start_pos, start_pos, b1 end + if b1 > 0xF4 or b1 < 0xC2 then return nil end + + local cont_byte_count = b1 >= 0xF0 and 3 or b1 >= 0xE0 and 2 or b1 >= 0xC0 and 1 + local end_pos = start_pos + cont_byte_count + local code_point = 0 + + if str_n < end_pos then return nil end + + local bytes = {str:byte(start_pos + 1, end_pos)} + for i = 1, #bytes do + local b_x = bytes[i] + if bit.band(b_x, 0xC0) ~= 0x80 then return nil end + code_point = bit.bor(bit.lshift(code_point, 6), bit.band(b_x, 0x3F)) + b1 = bit.lshift(b1, 1) + end + + code_point = bit.bor(code_point, bit.lshift(bit.band(b1, 0x7F), cont_byte_count * 5)) + + return start_pos, end_pos, code_point + end + + local replacement = string.char(239, 191, 189) + + function utf8.force(str) + local end_pos = #str + if end_pos == 0 then return str, end_pos end + + local ret = "" + local cur_pos = 1 + + repeat + local seq_start_pos, seq_end_pos = utf8_decode(str, cur_pos, end_pos) + + if not seq_start_pos then + ret = ret .. replacement + cur_pos = cur_pos + 1 + else + ret = ret .. str:sub(seq_start_pos, seq_end_pos) + cur_pos = seq_end_pos + 1 + end + until cur_pos > end_pos + + return ret, #ret + end + + -- https://gist.github.com/gdeglin/4128882 + + local utf8_char_bytes = function(c) + if c > 0 and c <= 127 then + return 1 + elseif c >= 194 and c <= 223 then + return 2 + elseif c >= 224 and c <= 239 then + return 3 + elseif c >= 240 and c <= 244 then + return 4 + end + end + utf8.char_bytes = utf8_char_bytes + + function utf8.len(str) + local length = #str + + local len = 0 + + local pos = 1 + while pos <= length do + len = len + 1 + pos = pos + utf8_char_bytes(str:byte(pos)) + end + + return len + end + + function utf8.sub(str, i, j) + j = j or -1 + + if i == nil then return "" end + + local l = (i >= 0 and j >= 0) or utf8.len(str) + local start_char = (i >= 0) and i or l + i + 1 + local end_char = (j >= 0) and j or l + j + 1 + + if start_char > end_char then return "" end + + local pos = 1 + local length = #str + local len = 0 + + local start_byte, end_byte = 1, length + + while pos <= length do + len = len + 1 + + if len == start_char then + start_byte = pos + end + + pos = pos + utf8_char_bytes(str:byte(pos)) + + if len == end_char then + end_byte = pos - 1 + break + end + end + + return str:sub(start_byte, end_byte) + end + + sui.utf8 = utf8 +end + +-- +-- thanks falco! +-- https://github.com/FPtje/DarkRP/blob/4fd2c3c315427e79bb7624702cfaefe9ad26ac7e/gamemode/modules/base/cl_util.lua#L42 +-- +do + local utf8 = utf8 + local surface = surface + + local max_width, original_width, can_fix + + local fix_width = function() + if can_fix then + can_fix = false + max_width = original_width + end + end + + local char_wrap = function(text, remaining_width) + local total_width = 0 + + local new_text = "" + for char in text:gmatch(utf8.charpattern) do + total_width = total_width + surface.GetTextSize(char) + if total_width >= remaining_width then + total_width = surface.GetTextSize(char) + fix_width() + remaining_width = max_width + + new_text = new_text .. ("\n" .. char) + else + new_text = new_text .. char + end + end + + return new_text, total_width + end + + function sui.wrap_text(text, font, width, first_width) + text = sui.utf8.force(text) + + local total_width = 0 + can_fix = first_width and true or false + max_width, original_width = first_width or width, width + + surface.SetFont(font) + + local space_width = surface.GetTextSize(" ") + + text = text:gsub("(%s?[%S]*)", function(word) + local char = word:sub(1, 1) + if char == "\n" then + total_width = 0 + fix_width() + end + + local wordlen = surface.GetTextSize(word) + total_width = total_width + wordlen + + if wordlen >= max_width then + local split_word + split_word, total_width = char_wrap(word, max_width - (total_width - wordlen)) + return split_word + elseif total_width < max_width then + return word + end + + fix_width() + + total_width = wordlen + + if char == " " then + total_width = total_width - space_width + return "\n" .. word:sub(2) + end + + return "\n" .. word + end) + + return text + end +end + +function sui.register(classname, panel_table, parent_class) + sui.TDLib.Install(panel_table) + + if not panel_table.Add then + function panel_table:Add(pnl) + return vgui.Create(pnl, self) + end + end + + if not panel_table.NoOverrideClear and not panel_table.Clear then + function panel_table:Clear() + local children = self:GetChildren() + for i = 1, #children do + children[i]:Remove() + end + end + end + + local SUI = CURRENT_SUI + + for k, v in pairs(SUI.panels_funcs) do + panel_table[k] = v + end + + panel_table.SUI_GetColor = function(name) + return SUI.GetColor(name) + end + + SUI.panels[classname] = panel_table + + return vgui.Register(SUI.name .. "." .. classname, panel_table, parent_class) +end + +local Material; do + local C_Material, material_str = select(2, debug.getupvalue(_G.Material, 1)), "0001010" -- [["mips smooth"]] + Material = function(name) + return C_Material(name, material_str) + end +end +sui.Material = Material + +local function prepare_theme(theme) + for k, v in pairs(theme) do + if IsColor(v) then continue end + + if istable(v) then + prepare_theme(v) + elseif isstring(v) and v:sub(1, 1) == "#" then + theme[k] = sui.hex_rgb(v:sub(2)) + end + end +end + +function sui.new(addon_name, default_scaling, panels_funcs) + local SUI = { + name = addon_name, + panels = {}, + panels_funcs = panels_funcs or {} + } + + CURRENT_SUI = SUI + + do + local themes = table.Copy(sui.themes) + local current_theme_table + + function SUI.GetColor(color_name) + return current_theme_table[color_name] + end + + function SUI.SetTheme(theme_name) + SUI.current_theme = theme_name + current_theme_table = themes[theme_name] + hook.Call(addon_name .. ".ThemeChanged") + end + + function SUI.GetThemes() + return themes + end + + function SUI.AddToTheme(theme_name, tbl) + local theme = themes[theme_name] + for k, v in pairs(tbl) do + theme[k] = v + end + prepare_theme(theme) + end + + function SUI.RemoveTheme(theme_name) + themes[theme_name] = nil + if theme_name == SUI.current_theme then + SUI.SetTheme(next(themes)) + end + end + + function SUI.AddTheme(theme_name, tbl) + prepare_theme(tbl) + themes[theme_name] = tbl + end + + SUI.themes = themes + end + + local Scale + do + local scale = 1 + + if default_scaling then + SUI.Scale = sui.scale + else + function SUI.Scale(v) + return floor((v * scale) + 0.5) + end + end + Scale = SUI.Scale + + function SUI.ScaleEven(v) + v = Scale(v) + if v % 2 ~= 0 then + v = v + 1 + end + return v + end + + function SUI.SetScale(_scale) + if _scale == scale then return end + + scale = _scale + SUI.scale = _scale + + for k, v in pairs(SUI.fonts) do + SUI.CreateFont(k:sub(#addon_name + 1), v.font, v.size, v.weight) + end + + SUI.CallScaleChanged() + end + + local n = 0 + local keys = {} + local hooks = {} + _G[addon_name .. "_HOOKS"] = keys + _G[addon_name .. "_KEYS"] = hooks + _G[addon_name .. "_N"] = function() + return n + end + function SUI.OnScaleChanged(name, func) + if not isfunction(func) then + error("Invalid function?") + end + + if not name then + error("Invalid name?") + end + + if not isstring(name) then + local _func = func + func = function() + local isvalid = name.IsValid + if isvalid and isvalid(name) then + _func(name) + else + SUI.RemoveScaleHook(name, true) + end + end + end + + local pos = keys[name] + if pos then + hooks[pos + 1] = func + else + hooks[n + 1] = name + hooks[n + 2] = func + keys[name] = n + 1 + n = n + 2 + end + end + + function SUI.RemoveScaleHook(name, in_hook) + local pos = keys[name] + if not pos then return end + + if in_hook then + hooks[pos] = nil + hooks[pos + 1] = nil + else + local new_name = hooks[n - 1] + if new_name then + hooks[pos], hooks[pos + 1] = new_name, hooks[n] + hooks[n - 1], hooks[n] = nil, nil + keys[new_name] = pos + end + n = n - 2 + end + keys[name] = nil + end + + function SUI.CallScaleChanged() + if n == 0 then return end + + local i, c_n = 2, n + ::loop:: + local func = hooks[i] + if func then + func() + i = i + 2 + else + local _n, _i = c_n, i + if n ~= c_n then + _n = n + i = i + 2 + else + c_n = c_n - 2 + end + + local new_name = hooks[_n - 1] + if new_name then + hooks[_i - 1], hooks[_i] = new_name, hooks[_n] + hooks[_n - 1], hooks[_n] = nil, nil + keys[new_name] = _i - 1 + end + + n = n - 2 + end + + if i <= c_n then + goto loop + end + end + + function SUI.GetScale() + return scale + end + + SUI.scale = 1 + end + + do + local fonts = {} + + function SUI.CreateFont(font_name, font, size, weight) + font_name = addon_name .. font_name + + fonts[font_name] = fonts[font_name] or { + font = font, + size = size, + weight = weight + } + + surface.CreateFont(font_name, { + font = font, + size = Scale(size), + weight = weight, + extended = true + }) + + return font_name + end + + function SUI.GetFont(font_name) + return addon_name .. font_name + end + + function SUI.GetFontHeight(font_name) + local font = fonts[addon_name .. font_name] or fonts[font_name] + if not font then return 0 end + + return floor(Scale(font.size or 0)) + end + + SUI.fonts = fonts + end + + do + local materials = {} + + local delay = 0.008 + local next_run = UnPredictedCurTime() + + function SUI.Material(mat, allow_delay) + local _mat = materials[mat] + if _mat then return _mat end + + if allow_delay then + if UnPredictedCurTime() < next_run then return end + next_run = UnPredictedCurTime() + delay + end + + materials[mat] = Material(mat) + + return materials[mat] + end + + SUI.materials = materials + end + + SUI.SetTheme("Dark") + + for _, f in ipairs(file.Find("sui/vgui/sui_*.lua", "LUA")) do + include("sui/vgui/" .. f) + end + + for _, f in ipairs(file.Find(string.format("sui/vgui/%s_*.lua", addon_name:lower()), "LUA")) do + include("sui/vgui/" .. f) + end + + return SUI +end + +sui.themes = sui.themes or {} +function sui.add_theme(name, tbl) + prepare_theme(tbl) + sui.themes[name] = tbl +end + +function sui.valid_options() + local objs = {} + objs.IsValid = function() + local valid = true + for i = 1, #objs do + local obj = objs[i] + if obj:IsValid() and obj.valid == false then + valid = false + break + end + end + return valid + end + objs.Add = function(obj) + table.insert(objs, obj) + end + return objs +end + +do + local SURFACE = Color(31, 31, 31) + local PRIMARY = Color(65, 185, 255) + + local ON_SURFACE = Color(255, 255, 255) + local ON_SURFACE_HIGH_EMPHASIS = ColorAlpha(ON_SURFACE, 221) + local ON_SURFACE_MEDIUM_EMPHASIS = ColorAlpha(ON_SURFACE, 122) + local ON_SURFACE_DISABLED = ColorAlpha(ON_SURFACE, 97) + + local ON_PRIMARY = Color(60, 60, 60) + + sui.add_theme("Dark", { + frame = Color(18, 18, 18), + frame_blur = false, + + title = ON_SURFACE, + header = SURFACE, + + close = ON_SURFACE_MEDIUM_EMPHASIS, + close_hover = Color(255, 60, 60), + close_press = Color(255, 255, 255, 30), + + button = PRIMARY, + button_text = "#050709", + button_hover = ColorAlpha(ON_PRIMARY, 100), + button_click = ColorAlpha(ON_PRIMARY, 240), + button_disabled = Color(100, 100, 100), + button_disabled_text = "#bdbdbd", + + button2_hover = ColorAlpha(PRIMARY, 5), + button2_selected = ColorAlpha(PRIMARY, 15), + + scroll = ColorAlpha(PRIMARY, 97), + scroll_grip = PRIMARY, + + scroll_panel = Color(29, 29, 29), + scroll_panel_outline = false, + + text_entry_bg = Color(34, 34, 34), + text_entry_bar_color = Color(0, 0, 0, 0), + text_entry = ON_SURFACE_HIGH_EMPHASIS, + text_entry_2 = ON_SURFACE_MEDIUM_EMPHASIS, + text_entry_3 = PRIMARY, + + property_sheet_bg = Color(39, 39, 39), + property_sheet_tab = Color(150, 150, 150), + property_sheet_tab_click = Color(255, 255, 255, 30), + property_sheet_tab_active = PRIMARY, + + toggle_button = ON_SURFACE_DISABLED, + toggle_button_switch = ON_SURFACE_HIGH_EMPHASIS, + + toggle_button_active = ColorAlpha(PRIMARY, 65), + toggle_button_switch_active = PRIMARY, + + slider_knob = PRIMARY, + slider_track = ColorAlpha(PRIMARY, 65), + slider_hover = ColorAlpha(PRIMARY, 5), + slider_pressed = ColorAlpha(PRIMARY, 30), + + on_sheet = Color(43, 43, 43, 200), + on_sheet_hover = Color(200, 200, 200, 20), + + --=-- + query_box_bg = "#181818", + query_box_cancel = Color(244, 67, 54, 30), + query_box_cancel_text = "#f44336", + --=-- + + --=-- + menu = "#212121", + + menu_option = "#212121", + menu_option_text = "#bdbdbd", + menu_option_hover = "#3b3b3b", + menu_option_hover_text = "#fefefe", + + menu_spacer = "#303030", + --=-- + + line = "#303030", + + --=-- + column_sheet = "#263238", + column_sheet_bar = "#202020", + + column_sheet_tab = "#202020", + column_sheet_tab_hover = "#2e2e2e", + column_sheet_tab_active = "#383838", + + column_sheet_tab_icon = "#909090", + column_sheet_tab_icon_hover = "#f0f0f0", + column_sheet_tab_icon_active = "#34a1e0", + --=-- + + --=-- + collapse_category_header = "#272727", + collapse_category_header_hover = "#2a2a2a", + collapse_category_header_active = "#2e2e2e", + + collapse_category_header_text = "#aaaaaa", + collapse_category_header_text_hover = "#dcdcdc", + collapse_category_header_text_active = "#34A1E0", + + collapse_category_item = "#343434", + collapse_category_item_hover = "#464646", + collapse_category_item_active = "#535353", + + collapse_category_item_text = "#aaaaaa", + collapse_category_item_text_hover = "#dcdcdc", + collapse_category_item_text_active = "#ffffff", + --=-- + }) +end + +do + local PRIMARY = Color(65, 185, 255) + + local ON_PRIMARY = Color(220, 220, 220) + + sui.add_theme("Blur", { + frame = Color(30, 30, 30, 220), + frame_blur = true, + + title = Color(255, 255, 255), + header = Color(60, 60, 60, 200), + + close = Color(200, 200, 200), + close_hover = Color(255, 60, 60), + close_press = Color(255, 255, 255, 30), + + button = ColorAlpha(PRIMARY, 130), + button_text = ON_PRIMARY, + button_hover = Color(0, 0, 0, 30), + button_click = PRIMARY, + button_disabled = Color(100, 100, 100), + button_disabled_text = "#bdbdbd", + + button2_hover = ColorAlpha(PRIMARY, 5), + button2_selected = ColorAlpha(PRIMARY, 15), + + scroll = Color(0, 0, 0, 100), + scroll_grip = PRIMARY, + + scroll_panel = Color(255, 255, 255, 10), + scroll_panel_outline = false, + + text_entry_bg = Color(0, 0, 0, 0), + text_entry_bar_color = Color(200, 200, 200, 153), + text_entry = Color(240, 240, 240, 221), + text_entry_2 = Color(200, 200, 200, 153), + text_entry_3 = PRIMARY, + + property_sheet_bg = Color(60, 60, 60, 200), + property_sheet_tab = Color(150, 150, 150), + property_sheet_tab_click = Color(255, 255, 255, 40), + property_sheet_tab_active = PRIMARY, + + toggle_button = Color(244, 67, 54), + toggle_button_switch = Color(230, 230, 230), + + toggle_button_active = PRIMARY, + toggle_button_switch_active = Color(230, 230, 230), + + slider_knob = PRIMARY, + slider_track = ColorAlpha(PRIMARY, 100), + slider_hover = ColorAlpha(PRIMARY, 40), + slider_pressed = ColorAlpha(PRIMARY, 70), + + on_sheet = Color(60, 60, 60, 180), + on_sheet_hover = Color(30, 30, 30, 70), + + --=-- + query_box_bg = Color(0, 0, 0, 100), + query_box_cancel = Color(244, 67, 54, 30), + query_box_cancel_text = "#f44336", + --=-- + }) +end diff --git a/garrysmod/addons/sui/lua/sui/libs/bshadows.lua b/garrysmod/addons/sui/lua/sui/libs/bshadows.lua new file mode 100644 index 0000000..3e70d94 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/libs/bshadows.lua @@ -0,0 +1,94 @@ +local ScrW = ScrW +local ScrH = ScrH + +local sin = math.sin +local cos = math.cos +local rad = math.rad +local ceil = math.ceil + +local Start2D = cam.Start2D +local End2D = cam.End2D + +local PushRenderTarget = render.PushRenderTarget +local OverrideAlphaWriteEnable = render.OverrideAlphaWriteEnable +local Clear = render.Clear +local CopyRenderTargetToTexture = render.CopyRenderTargetToTexture +local BlurRenderTarget = render.BlurRenderTarget +local PopRenderTarget = render.PopRenderTarget +local SetMaterial = render.SetMaterial +local DrawScreenQuadEx = render.DrawScreenQuadEx +local DrawScreenQuad = render.DrawScreenQuad + +local RenderTarget, RenderTarget2 +local load_render_targets = function() + local w, h = ScrW(), ScrH() + RenderTarget = GetRenderTarget("sui_bshadows_original" .. w .. h, w, h) + RenderTarget2 = GetRenderTarget("sui_bshadows_shadow" .. w .. h, w, h) +end +load_render_targets() +hook.Add("OnScreenSizeChanged", "SUI.BShadows", load_render_targets) + +local ShadowMaterial = CreateMaterial("sui_bshadows", "UnlitGeneric", { + ["$translucent"] = 1, + ["$vertexalpha"] = 1, + ["alpha"] = 1 +}) + +local ShadowMaterialGrayscale = CreateMaterial("sui_bshadows_grayscale", "UnlitGeneric", { + ["$translucent"] = 1, + ["$vertexalpha"] = 1, + ["$alpha"] = 1, + ["$color"] = "0 0 0", + ["$color2"] = "0 0 0" +}) + +local SetTexture = ShadowMaterial.SetTexture + +local BSHADOWS = {} + +BSHADOWS.BeginShadow = function() + PushRenderTarget(RenderTarget) + + OverrideAlphaWriteEnable(true, true) + Clear(0, 0, 0, 0) + OverrideAlphaWriteEnable(false, false) + + Start2D() +end + +BSHADOWS.EndShadow = function(intensity, spread, blur, opacity, direction, distance, _shadowOnly) + opacity = opacity or 255 + direction = direction or 0 + distance = distance or 0 + + CopyRenderTargetToTexture(RenderTarget2) + + if blur > 0 then + OverrideAlphaWriteEnable(true, true) + BlurRenderTarget(RenderTarget2, spread, spread, blur) + OverrideAlphaWriteEnable(false, false) + end + + PopRenderTarget() + + SetTexture(ShadowMaterial, "$basetexture", RenderTarget) + SetTexture(ShadowMaterialGrayscale, "$basetexture", RenderTarget2) + + local xOffset = sin(rad(direction)) * distance + local yOffset = cos(rad(direction)) * distance + + SetMaterial(ShadowMaterialGrayscale) + for i = 1, ceil(intensity) do + DrawScreenQuadEx(xOffset, yOffset, ScrW(), ScrH()) + end + + if not _shadowOnly then + SetTexture(ShadowMaterial, "$basetexture", RenderTarget) + SetMaterial(ShadowMaterial) + DrawScreenQuad() + end + + End2D() +end + +sui.BSHADOWS = BSHADOWS diff --git a/garrysmod/addons/sui/lua/sui/libs/gif_loader.lua b/garrysmod/addons/sui/lua/sui/libs/gif_loader.lua new file mode 100644 index 0000000..5805085 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/libs/gif_loader.lua @@ -0,0 +1,432 @@ +local byte = string.byte +local sub = string.sub +local lshift = bit.lshift +local rshift = bit.rshift +local bor = bit.bor +local band = bit.band + +local GIFDecoder = {} +local GIFDecoderMethods = {} +local GIFDecoder_meta = {__index = GIFDecoderMethods} + +function GIFDecoder.new(buf) + local buf_n = #buf + local this = setmetatable({ + p = 1, + buf = buf + }, GIFDecoder_meta) + + local version = this:read(6) + assert(version == "GIF89a" or version == "GIF87a", "wrong file format") + + this.width = this:word() + this.height = this:word() + + local pf0 = this:byte() + local global_palette_flag = rshift(pf0, 7) + local num_global_colors_pow2 = band(pf0, 0x7) + local num_global_colors = lshift(1, num_global_colors_pow2 + 1) + this:skip(2) + + local global_palette_offset = nil + local global_palette_size = nil + + if global_palette_flag > 0 then + global_palette_offset = this.p + this.global_palette_offset = global_palette_offset + global_palette_size = num_global_colors + this:skip(num_global_colors * 3) + end + + local no_eof = true + + local frames = {} + + local delay = 0 + local transparent_index = nil + local disposal = 1 + + while no_eof and this.p <= buf_n do + local b = this:byte() + if b == 0x3b then + no_eof = false + elseif b == 0x2c then + local x, y, w, h = this:word(), this:word(), this:word(), this:word() + local pf2 = this:byte() + local local_palette_flag = rshift(pf2, 7) + local interlace_flag = band(rshift(pf2, 6), 1) + local num_local_colors_pow2 = band(pf2, 0x7) + local num_local_colors = lshift(1, num_local_colors_pow2 + 1) + local palette_offset = global_palette_offset + local palette_size = global_palette_size + local has_local_palette = false + if local_palette_flag ~= 0 then + has_local_palette = true + palette_offset = this.p + palette_size = num_local_colors + this:skip(num_local_colors * 3) + end + + local data_offset = this.p + + this:skip(1) + this:skip_eob() + + table.insert(frames, { + x = x, + y = y, + width = w, + height = h, + has_local_palette = has_local_palette, + palette_offset = palette_offset, + palette_size = palette_size, + data_offset = data_offset, + data_length = this.p - data_offset, + transparent_index = transparent_index, + interlaced = interlace_flag > 0, + delay = delay, + disposal = disposal + }) + elseif b == 0x21 then + local b2 = this:byte() + if b2 == 0xf9 then + local len, flags = this:bytes(2) + delay = this:word() + local transparent, terminator = this:bytes(2) + + assert(len == 4 and terminator == 0, "Invalid graphics extension block.") + + if flags % 2 == 1 then + transparent_index = transparent + else + transparent_index = nil + end + + disposal = math.floor(flags / 4) % 8 + elseif b2 == 0xff then + this:read(this:byte()) + this:skip_eob() + else + this:skip_eob() + end + end + end + + this.frames = frames + + return this +end + +function GIFDecoderMethods:skip(offset) + self.p = self.p + offset +end + +-- skip to end of block +function GIFDecoderMethods:skip_eob() + repeat + local size = self:byte() + self:skip(size) + until size == 0 +end + +function GIFDecoderMethods:byte() + local b = byte(self.buf, self.p) + self:skip(1) + return b +end + +function GIFDecoderMethods:bytes(len) + local _p = self.p + self:skip(len) + return byte(self.buf, _p, len + _p - 1) +end + +function GIFDecoderMethods:read(len) + local _p = self.p + self:skip(len) + return sub(self.buf, _p, len + _p - 1) +end + +function GIFDecoderMethods:word() + return bor(self:byte(), lshift(self:byte(), 8)) +end + +local GifReaderLZWOutputIndexStream = function(this, output, output_length) + local min_code_size = this:byte() + local clear_code = lshift(1, min_code_size) + local eoi_code = clear_code + 1 + local next_code = eoi_code + 1 + local cur_code_size = min_code_size + 1 + + local code_mask = lshift(1, cur_code_size) - 1 + local cur_shift = 0 + local cur = 0 + local op = 0 + + local subblock_size = this:byte() + + local code_table = {} + + local prev_code = nil + + while true do + while cur_shift < 16 do + if subblock_size == 0 then break end + + cur = bor(cur, lshift(this:byte(), cur_shift)) + cur_shift = cur_shift + 8 + + if subblock_size == 1 then + subblock_size = this:byte() + else + subblock_size = subblock_size - 1 + end + end + + if cur_shift < cur_code_size then break end + + local code = band(cur, code_mask) + cur = rshift(cur, cur_code_size) + cur_shift = cur_shift - cur_code_size + + if code == clear_code then + next_code = eoi_code + 1 + cur_code_size = min_code_size + 1 + code_mask = lshift(1, cur_code_size) - 1 + + prev_code = null + continue + elseif code == eoi_code then + break + end + + local chase_code = code < next_code and code or prev_code + local chase_length = 0 + local chase = chase_code + while chase > clear_code do + chase = rshift(code_table[chase], 8) + chase_length = chase_length + 1 + end + + local k = chase + local op_end = op + chase_length + (chase_code ~= code and 1 or 0) + if op_end > output_length then + Error("Warning, gif stream longer than expected.") + return + end + + output[op] = k; op = op + 1 + op = op + chase_length + + local b = op + + if chase_code ~= code then + output[op] = k; op = op + 1 + end + chase = chase_code + + while chase_length > 0 do + chase_length = chase_length - 1 + chase = code_table[chase] + b = b - 1 + output[b] = band(chase, 0xff) + + chase = rshift(chase, 8) + end + + if prev_code ~= nil and next_code < 4096 then + code_table[next_code] = bor(lshift(prev_code, 8), k) + next_code = next_code + 1 + + if next_code >= code_mask + 1 and cur_code_size < 12 then + cur_code_size = cur_code_size + 1 + code_mask = bor(lshift(code_mask, 1), 1) + end + end + + prev_code = code + end + + if op ~= output_length then + Error("Warning, gif stream shorter than expected.") + end + + return output +end + +function GIFDecoderMethods:decode_and_blit_frame_RGBA(frame_num, pixels) + local frame = self.frames[frame_num] + local num_pixels = frame.width * frame.height + local index_stream = {} + + self.p = frame.data_offset + GifReaderLZWOutputIndexStream(self, index_stream, num_pixels) + local palette_offset = frame.palette_offset + + local trans = frame.transparent_index + if trans == nil then + trans = 256 + end + + local width = self.width + local framewidth = frame.width + local framestride = width - framewidth + local xleft = framewidth + + local opbeg = (frame.y * width + frame.x) * 4 + + local opend = ((frame.y + frame.height) * width + frame.x) * 4 + local op = opbeg + local scanstride = framestride * 4 + + if frame.interlaced == true then + scanstride = scanstride + (width * 4 * 7) + end + + local interlaceskip = 8 + + local i = 0 + local buf = self.buf + while i < num_pixels do + local index = index_stream[i] + + if xleft == 0 then + op = op + scanstride + xleft = framewidth + + if op >= opend then + scanstride = + framestride * 4 + width * 4 * (interlaceskip - 1) + + op = + opbeg + + (framewidth + framestride) * lshift(interlaceskip, 1) + interlaceskip = rshift(interlaceskip, 1) + end + end + + if index ~= trans then + index = palette_offset + index * 3 + pixels[op + 0] = byte(buf, index) + pixels[op + 1] = byte(buf, index + 1) + pixels[op + 2] = byte(buf, index + 2) + pixels[op + 3] = 255 + end + + op = op + 4 + + xleft = xleft - 1 + i = i + 1 + end +end + +function GIFDecoderMethods:clear_frame(frame_num, pixels) + local frame = self.frames[frame_num] + + self.p = frame.data_offset + + local width = self.width + local framewidth = frame.width + local framestride = width - framewidth + local xleft = framewidth + + local opbeg = (frame.y * width + frame.x) * 4 + + local opend = ((frame.y + frame.height) * width + frame.x) * 4 + local op = opbeg + local scanstride = framestride * 4 + + if frame.interlaced == true then + scanstride = scanstride + (width * 4 * 7) + end + + local interlaceskip = 8 + + local i = 0 + local num_pixels = frame.width * frame.height + while i < num_pixels do + if xleft == 0 then + op = op + scanstride + xleft = framewidth + + if op >= opend then + scanstride = + framestride * 4 + width * 4 * (interlaceskip - 1) + + op = + opbeg + + (framewidth + framestride) * lshift(interlaceskip, 1) + interlaceskip = rshift(interlaceskip, 1) + end + end + + pixels[op + 0] = 0 + pixels[op + 1] = 0 + pixels[op + 2] = 0 + pixels[op + 3] = 0 + op = op + 4 + + xleft = xleft - 1 + i = i + 1 + end +end + +function GIFDecoderMethods:get_frames() + local num_pixels = self.width * self.height * 4 + 4 + local frames = {} + local numFrames = #self.frames + local last_frame + local restore_from + for i = 1, numFrames do + local frame = self.frames[i] + + local data = {} + + if last_frame then + local _data = last_frame.data + for k = 0, num_pixels do + data[k] = _data[k] + end + end + + if i > 1 then + local last_disposal = last_frame.disposal + if last_disposal == 3 then + if restore_from then + for k = 0, num_pixels do + data[k] = restore_from[k] + end + else + self:clear_frame(i - 1, data) + end + end + + if last_disposal == 2 then + self:clear_frame(i - 1, data) + end + end + + self:decode_and_blit_frame_RGBA(i, data) + + local delay = frame.delay + if delay < 2 then + delay = 10 + end + + local disposal = frame.disposal + last_frame = { + data = data, + delay = delay, + disposal = disposal + } + frames[i] = last_frame + + if disposal ~= 3 then + restore_from = data + end + end + + return frames +end + +return GIFDecoder.new diff --git a/garrysmod/addons/sui/lua/sui/libs/png_encoder.lua b/garrysmod/addons/sui/lua/sui/libs/png_encoder.lua new file mode 100644 index 0000000..191b19f --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/libs/png_encoder.lua @@ -0,0 +1,205 @@ +local string = string +local table = table +local bit = bit + +local char = string.char +local byte = string.byte + +local insert = table.insert +local concat = table.concat + +local bor = bit.bor +local bxor = bit.bxor +local band = bit.band +local bnot = bit.bnot +local lshift = bit.lshift +local rshift = bit.rshift + +local ceil = math.ceil + +local SIGNATURE = char(137, 80, 78, 71, 13, 10, 26, 10) + +local crc_table = {}; do + local n = 0 + while n < 256 do + local c = n + local k = 0 + while k < 8 do + if band(c, 1) ~= 0 then + c = bxor(0xedb88320, rshift(c, 1)) + else + c = rshift(c, 1) + end + k = k + 1 + end + crc_table[n + 1] = c + n = n + 1 + end +end + +local crc = function(buf) + local c = 0xffffffff + for i = 1, #buf do + c = bxor(crc_table[band(bxor(c, byte(buf, i)), 0xff) + 1], rshift(c, 8)) + end + return bxor(c, 0xffffffff) +end + +local dword_as_string = function(dword) + return char( + rshift(band(dword, 0xff000000), 24), + rshift(band(dword, 0x00ff0000), 16), + rshift(band(dword, 0x0000ff00), 8), + band(dword, 0x000000ff) + ) +end + +local create_chunk = function(type, data, length) + local CRC = crc(type .. data) + return concat({ + dword_as_string(length or #data), + type, + data, + dword_as_string(CRC) + }, "", 1, 4) +end + +local create_IHDR; do + local ARGS = ( + -- bit depth + char(8) .. + -- color type: 6=truecolor with alpha + char(6) .. + -- compression method: 0=deflate, only allowed value + char(0) .. + -- filtering: 0=adaptive, only allowed value + char(0) .. + -- interlacing: 0=none + char(0) + ) + + create_IHDR = function(w, h) + return create_chunk("IHDR", concat({ + dword_as_string(w), + dword_as_string(h), + ARGS + }, "", 1, 3), 13) + end +end + +local deflate_pack; do + local BASE = 6552 + local NMAX = 5552 + local adler32 = function(str) + local s1 = 1 + local s2 = 0 + local n = NMAX + + for i = 1, #str do + s1 = s1 + byte(str, i) + s2 = s2 + s1 + + n = n - 1 + if n == 0 then + s1 = s1 % BASE + s2 = s2 % BASE + n = NMAX + end + end + + s1 = s1 % BASE + s2 = s2 % BASE + + return bor(lshift(s2, 16), s1) + end + + local splitChunks = function(chunk, chunkSize) + local len = ceil(#chunk / chunkSize) + local ret = {} + for i = 1, len do + ret[i - 1] = chunk:sub(((i - 1) * chunkSize) + 1, chunkSize) + end + return ret + end + + deflate_pack = function(str) + local ret = {"\x78\x9c"} + + local chunks = splitChunks(str, 0xFFFF) + local len = #chunks + + local i = 0 + while i < (len + 1) do + local chunk = chunks[i] + local chunk_n = #chunk + + insert(ret, i < len and "\x00" or "\x01") + insert(ret, char(band(chunk_n, 0xff), band(rshift(chunk_n, 8), 0xff))) + insert(ret, char(band(bnot(chunk_n), 0xff), band(rshift(bnot(chunk_n), 8), 0xff))) + insert(ret, chunk) + i = i + 1 + end + + local t = adler32(str) + t = char( + band(rshift(t, 24), 0xff), + band(rshift(t, 16), 0xff), + band(rshift(t, 8), 0xff), + band(t, 0xff) + ) + + insert(ret, t) + + return concat(ret) + end +end + +local create_IDAT; do + local slice = function(a, s, e) + local ret, j = {}, 0 + for i = s, e - 1 do + ret[j] = char(band(a[i] or 0, 0xFF)) + j = j + 1 + end + return ret + end + + local array_split_chunks = function(w, h, array, chunkSize) + local ret = {} + local i = 0 + local len = ceil((w * h * 4 + 4) / chunkSize) + while i < len do + ret[i] = slice(array, i * chunkSize, (i + 1) * chunkSize) + i = i + 1 + end + return ret + end + + create_IDAT = function(w, h, chunk) + local scanlines = array_split_chunks(w, h, chunk, w * 4) + + local image_bytes = {} + for i = 0, #scanlines do + local scanline = scanlines[i] + insert(image_bytes, char(band(0, 0xFF))) + insert(image_bytes, concat(scanline, "", 0, #scanline)) + end + image_bytes = deflate_pack(concat(image_bytes)) + + return create_chunk("IDAT", image_bytes) + end +end + +local IEND = create_chunk("IEND", "", 0) +local to_return = {SIGNATURE, nil, nil, IEND} +local generate_png = function(w, h, chunk) + local IHDR = create_IHDR(w, h) + local IDAT = create_IDAT(w, h, chunk) + + to_return[2] = IHDR + to_return[3] = IDAT + + return concat(to_return, "", 1, 4) +end + +return generate_png diff --git a/garrysmod/addons/sui/lua/sui/libs/tdlib/cl_tdlib.lua b/garrysmod/addons/sui/lua/sui/libs/tdlib/cl_tdlib.lua new file mode 100644 index 0000000..b149305 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/libs/tdlib/cl_tdlib.lua @@ -0,0 +1,914 @@ +--[[ + Three's Derma Lib + Made by Threebow + + You are free to use this anywhere you like, or sell any addons + made using this, as long as I am properly accredited. +]] + +local pairs = pairs +local ipairs = ipairs +local Color = Color +local render = render +local SysTime = SysTime +local Lerp, RealFrameTime = Lerp, RealFrameTime +local RoundedBox, RoundedBoxEx, NoTexture = draw.RoundedBox, draw.RoundedBoxEx, draw.NoTexture +local SetDrawColor, DrawRect = surface.SetDrawColor, surface.DrawRect +local DrawPoly = surface.DrawPoly +local sui = sui + +local Panel = FindMetaTable("Panel") + +--[[ + Constants +]] +local BLUR = CreateMaterial("SUI.TDLib.Blur", "gmodscreenspace", { + ["$basetexture"] = "_rt_fullframefb", + ["$blur"] = (1 / 3) * 7, +}) + +local COL_WHITE_1 = Color(255, 255, 255) +local COL_WHITE_2 = Color(255, 255, 255, 30) + +--[[ + credits to http://slabode.exofire.net/circle_draw.shtml +]] +local calculate_circle do + local cos = math.cos + local sin = math.sin + local round = math.Round + local sqrt = math.sqrt + local pi = math.pi + calculate_circle = function(circle, x_centre, y_centre, r) + if circle.x == x_centre and circle.y == y_centre and circle.r == r then return end + + local step = (2 * pi) / round(6 * sqrt(r)) + + local i = 0 + for theta = 2 * pi, 0, -step do + local x = x_centre + r * cos(theta) + local y = y_centre - r * sin(theta) + i = i + 1 + circle[i] = { + x = x, + y = y + } + end + + for i = i + 1, #circle do + circle[i] = nil + end + + circle.x = x_centre + circle.y = y_centre + circle.r = r + end +end + +--[[ +void DrawArc(float cx, float cy, float r, float start_angle, float arc_angle, int num_segments) +{ + float theta = arc_angle / float(num_segments - 1);//theta is now calculated from the arc angle instead, the - 1 bit comes from the fact that the arc is open + + float tangetial_factor = tanf(theta); + + float radial_factor = cosf(theta); + + + float x = r * cosf(start_angle);//we now start at the start angle + float y = r * sinf(start_angle); + + glBegin(GL_LINE_STRIP);//since the arc is not a closed curve, this is a strip now + for(int ii = 0; ii < num_segments; ii++) + { + glVertex2f(x + cx, y + cy); + + float tx = -y; + float ty = x; + + x += tx * tangetial_factor; + y += ty * tangetial_factor; + + x *= radial_factor; + y *= radial_factor; + } + glEnd(); +} +]] + +local copy_color = function(color) + return Color(color.r, color.g, color.b, color.a) +end + +local color_alpha = function(color, a) + color.a = a + return color +end + +--[[ + Collection of various utilities +]] + +local TDLibUtil = {} + +function TDLibUtil.DrawCircle(circle, x, y, r, color) + calculate_circle(circle, x, y, r) + + SetDrawColor(color) + NoTexture() + DrawPoly(circle) +end +local DrawCircle = TDLibUtil.DrawCircle + +do + local SetMaterial = surface.SetMaterial + local UpdateScreenEffectTexture, DrawTexturedRect, SetScissorRect = render.UpdateScreenEffectTexture, surface.DrawTexturedRect, render.SetScissorRect + + local scrW, scrH = ScrW(), ScrH() + hook.Add("OnScreenSizeChanged", "SUI.TDLib", function() + scrW, scrH = ScrW(), ScrH() + end) + + function TDLibUtil.BlurPanel(s) + local x, y = s:LocalToScreen(0, 0) + + SetDrawColor(255, 255, 255) + SetMaterial(BLUR) + + for i = 1, 2 do + UpdateScreenEffectTexture() + DrawTexturedRect(x * -1, y * -1, scrW, scrH) + end + end + + function TDLibUtil.DrawBlur(x, y, w, h) + SetDrawColor(255, 255, 255) + SetMaterial(BLUR) + + SetScissorRect(x, y, x + w, y + h, true) + for i = 1, 2 do + UpdateScreenEffectTexture() + DrawTexturedRect(-1, -1, scrW, scrH) + end + SetScissorRect(0, 0, 0, 0, false) + end +end + +local LibClasses = {} + +do + local on_funcs = {} + + function LibClasses:On(name, func) + local old_func = self[name] + + if not old_func then + self[name] = func + return self + end + + local name_2 = name .. "_funcs" + + -- we gotta avoid creating 13535035 closures + if not on_funcs[name] then + on_funcs[name] = function(s, a1, a2, a3, a4) + local funcs = s[name_2] + local i, n = 0, #funcs + ::loop:: + i = i + 1 + if i <= n then + funcs[i](s, a1, a2, a3, a4) + goto loop + end + end + end + + if not self[name_2] then + self[name] = on_funcs[name] + self[name_2] = { + old_func, + func + } + else + table.insert(self[name_2], func) + end + + return self + end +end + +do + local UnPredictedCurTime = UnPredictedCurTime + + local transition_func = function(s) + local transitions = s.transitions + local i, n = 0, #transitions + ::loop:: + i = i + 1 + + if i <= n then + local v = transitions[i] + local name = v.name + local v2 = s[name] + if v.func(s) then + if v.start_0 then + v.start_1, v.start_0 = UnPredictedCurTime(), nil + end + + if v2 ~= 1 then + s[name] = Lerp((UnPredictedCurTime() - v.start_1) / v.time, v2, 1) + end + else + if v.start_1 then + v.start_0, v.start_1 = UnPredictedCurTime(), nil + end + + if v2 ~= 0 then + s[name] = Lerp((UnPredictedCurTime() - v.start_0) / v.time, v2, 0) + end + end + + goto loop + end + end + + function LibClasses:SetupTransition(name, time, func) + self[name] = 0 + + local transition = { + name = name, + time = time, + func = func, + start_0 = 0, + start_1 = 0, + } + + if self.transitions then + for k, v in ipairs(self.transitions) do + if v.name == name then + self.transitions[k] = transition + return self + end + end + table.insert(self.transitions, transition) + else + self.transitions = {transition} + self:On("Think", transition_func) + end + + return self + end +end + +function LibClasses:ClearPaint() + self.Paint = nil + self.Paint_funcs = nil + local SetPaintBackgroundEnabled = self.SetPaintBackgroundEnabled + if SetPaintBackgroundEnabled then + SetPaintBackgroundEnabled(self, false) + end + return self +end + +function LibClasses:RoundedBox(id, r, x, y, w, h, c) + self.colors = self.colors or {} + local colors = self.colors + + local id_c = colors[id] + if not id_c then + id_c = Color(c:Unpack()) + colors[id] = id_c + end + + sui.lerp_color(id_c, c) + RoundedBox(r, x, y, w, h, id_c) +end + +do + local SetFGColor = Panel.SetFGColor + + local set_color = function(s, col) + s.m_colText = col + SetFGColor(s, col.r, col.g, col.b, col.a) + end + + local paint = function(s) + local col = s.sui_textcolor + sui.lerp_color(col, s.new_col) + set_color(s, col) + end + + function LibClasses:TextColor(c, use_paint) + local col = self.sui_textcolor + if not col then + col = Color(c:Unpack()) + self.sui_textcolor = col + + if use_paint then + self:On("Paint", paint) + end + end + + if use_paint then + self.new_col = c + else + sui.lerp_color(col, c) + self:SetTextColor(col) + end + end +end + +do + local fade_hover_Paint = function(s, w, h) + if s.FadeHovers ~= 0 then + color_alpha(s.fadehover_color, s.fadehover_old_alpha * s.FadeHovers) + if s.fadehover_radius > 0 then + RoundedBox(s.fadehover_radius, 0, 0, w, h, s.fadehover_color) + else + SetDrawColor(s.fadehover_color) + DrawRect(0, 0, w, h) + end + end + end + + function LibClasses:FadeHover(color, time, radius, func) + color = copy_color(color or COL_WHITE_2) + self.fadehover_color = color + self.fadehover_radius = radius or 0 + self.fadehover_old_alpha = color.a + self:SetupTransition("FadeHovers", time or 0.8, func or TDLibUtil.HoverFunc) + self:On("Paint", fade_hover_Paint) + return self + end +end + +function LibClasses:BarHover(color, height, time) + color = color or COL_WHITE_1 + height = height or 2 + time = time or 1.6 + self:SetupTransition("BarHovers", time, TDLibUtil.HoverFunc) + self:On("Paint", function(s, w, h) + if s.BarHovers ~= 0 then + local bar = Round(w * s.BarHovers) + SetDrawColor(color) + DrawRect((w / 2) - (bar / 2), h - height, bar, height) + end + end) + return self +end + +do + local paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, s.SUI_GetColor("line")) + end + + function LibClasses:Line(dock, m1, m2, m3, m4) + self.making_line = true + + local line = self:Add("SAM.Panel") + line:Dock(dock or TOP) + + if self.line_margin then + line:DockMargin(unpack(self.line_margin)) + else + line:DockMargin(m1 or 0, m2 or 0, m3 or 0, m4 or 10) + end + + line.no_scale = true + line:SetTall(1) + line.Paint = paint + + self.making_line = false + return line + end + + function LibClasses:LineMargin(m1, m2, m3, m4) + self.line_margin = {m1 or 0, m2 or 0, m3 or 0, m4 or 0} + return self + end +end + +do + local background_Paint_1 = function(s) + s:SetBGColor(s.background_color) + end + + local background_Paint_2 = function(s, w, h) + RoundedBoxEx(s.background_radius, 0, 0, w, h, s.background_color, true, true, true, true) + end + + local background_Paint_3 = function(s, w, h) + RoundedBoxEx(s.background_radius, 0, 0, w, h, s.background_color, s.background_r_tl, s.background_r_tr, s.background_r_bl, s.background_r_br) + end + + function LibClasses:Background(color, radius, r_tl, r_tr, r_bl, r_br) + self.background_color = color + if isnumber(radius) and radius ~= 0 then + self.background_radius = radius + if isbool(r_tl) or isbool(r_tr) or isbool(r_bl) or isbool(r_br) then + self.background_r_tl = r_tl + self.background_r_tr = r_tr + self.background_r_bl = r_bl + self.background_r_br = r_br + self:On("Paint", background_Paint_3) + else + self:On("Paint", background_Paint_2) + end + else + self:SetPaintBackgroundEnabled(true) + self:On("ApplySchemeSettings", background_Paint_1) + self:On("PerformLayout", background_Paint_1) + end + return self + end +end + +function LibClasses:CircleClick(color, speed, target_radius) + self.circle_click_color = color or COL_WHITE_2 + + speed = speed or 5 + target_radius = isnumber(target_radius) and target_radius or false + + local radius, alpha, click_x, click_y = 0, -1, 0, 0 + local circle = {} + self:On("Paint", function(s, w) + if alpha >= 0 then + DrawCircle(circle, click_x, click_y, radius, ColorAlpha(self.circle_click_color, alpha)) + local frame_time = RealFrameTime() + radius, alpha = Lerp(frame_time * speed, radius, target_radius or w), Lerp(frame_time * speed, alpha, -1) + end + end) + self:On("DoClick", function() + click_x, click_y = self:CursorPos() + radius, alpha = 0, self.circle_click_color.a + end) + return self +end + +do + local min = math.min + function LibClasses:CircleClick2(color, speed, target_radius, start_radius) + color = color or COL_WHITE_2 + local _color = Color(color:Unpack()) + + speed = speed or 5 + target_radius = isnumber(target_radius) and target_radius or false + + local radius, alpha = 0, -1 + local circle = {} + self:On("Paint", function(s, w, h) + if alpha >= 0 then + _color.a = alpha + DrawCircle(circle, w / 2, h / 2, radius, _color) + + local frame_time = RealFrameTime() + radius, alpha = Lerp(frame_time * speed, radius, target_radius or min(w, h) / 2), Lerp(frame_time * speed, alpha, -1) + end + end) + + self:On("DoClick", function() + radius, alpha = start_radius or 0, color.a + end) + + return self + end +end + +-- https://github.com/Facepunch/garrysmod/pull/1520#issuecomment-410458090 +function LibClasses:Outline(color, width) + color = color or COL_WHITE_1 + width = width or 1 + self:On("Paint", function(s, w, h) + SetDrawColor(color) + DrawRect(0, 0, w, width) + DrawRect(0, h - width, w, width) + DrawRect(0, width, width, h - (width * 2)) + DrawRect(w - width, width, width, h - (width * 2)) + end) + return self +end + +function LibClasses:LinedCorners(color, len) + color = color or COL_WHITE_1 + len = len or 15 + self:On("Paint", function(s, w, h) + SetDrawColor(color) + DrawRect(0, 0, len, 1) + DrawRect(0, 1, 1, len - 1) + DrawRect(w - len, h - 1, len, 1) + DrawRect(w - 1, h - len, 1, len - 1) + end) + return self +end + +function LibClasses:SideBlock(color, size, side) + color = color or COL_WHITE_1 + size = size or 3 + side = side or LEFT + self:On("Paint", function(s, w, h) + SetDrawColor(color) + if side == LEFT then + DrawRect(0, 0, size, h) + elseif side == TOP then + DrawRect(0, 0, w, size) + elseif size == RIGHT then + DrawRect(w - size, 0, size, h) + elseif side == BOTTOM then + DrawRect(0, h - size, w, size) + end + end) + return self +end + +function LibClasses:Blur() + self:On("Paint", TDLibUtil.BlurPanel) + return self +end + +do + local STENCILOPERATION_REPLACE = STENCILOPERATION_REPLACE + local STENCILOPERATION_ZERO = STENCILOPERATION_ZERO + local STENCILCOMPARISONFUNCTION_NEVER = STENCILCOMPARISONFUNCTION_NEVER + local STENCILCOMPARISONFUNCTION_EQUAL = STENCILCOMPARISONFUNCTION_EQUAL + + local ClearStencil = render.ClearStencil + local SetStencilEnable = render.SetStencilEnable + local SetStencilWriteMask = render.SetStencilWriteMask + local SetStencilTestMask = render.SetStencilTestMask + local SetStencilFailOperation = render.SetStencilFailOperation + local SetStencilPassOperation = render.SetStencilPassOperation + local SetStencilZFailOperation = render.SetStencilZFailOperation + local SetStencilCompareFunction = render.SetStencilCompareFunction + local SetStencilReferenceValue = render.SetStencilReferenceValue + + local color_white = color_white + + local avatar_setplayer = function(s, ply, size) + s.avatar:SetPlayer(ply, size) + end + + local avatar_setsteamid = function(s, steamid, size) + s.avatar:SetSteamID(steamid, size) + end + + function LibClasses:CircleAvatar() + local avatar = self:Add("AvatarImage") + avatar:Dock(FILL) + avatar:SetPaintedManually(true) + self.avatar = avatar + self.SetSteamID = avatar_setsteamid + self.SetPlayer = avatar_setplayer + + local circle = {} + local PaintManual = avatar.PaintManual + self.Paint = function(s, w, h) + ClearStencil() + SetStencilEnable(true) + + SetStencilWriteMask(1) + SetStencilTestMask(1) + + SetStencilFailOperation(STENCILOPERATION_REPLACE) + SetStencilPassOperation(STENCILOPERATION_ZERO) + SetStencilZFailOperation(STENCILOPERATION_ZERO) + SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NEVER) + SetStencilReferenceValue(1) + + local a = w / 2 + DrawCircle(circle, a, a, a, color_white) + + SetStencilFailOperation(STENCILOPERATION_ZERO) + SetStencilPassOperation(STENCILOPERATION_REPLACE) + SetStencilZFailOperation(STENCILOPERATION_ZERO) + SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + SetStencilReferenceValue(1) + + PaintManual(avatar) + + SetStencilEnable(false) + end + return self + end +end + +do + function LibClasses:AnimationThinkInternal() + local systime = SysTime() + + if self.Term and self.Term <= systime then + self:Remove() + + return + end + + local m_AnimList = self.m_AnimList + if not m_AnimList then return end + + for i = #m_AnimList, 1, -1 do + local anim = m_AnimList[i] + if systime >= anim.StartTime then + local frac = math.TimeFraction(anim.StartTime, anim.EndTime, systime) + frac = math.Clamp(frac, 0, 1) + + local Think = anim.Think + if Think then + Think(anim, self, frac ^ (1.0 - (frac - 0.5))) + end + + if frac == 1 then + local OnEnd = anim.OnEnd + if OnEnd then + OnEnd(anim, self) + end + + m_AnimList[i] = nil + end + end + end + end + + local sort = function(a, b) + return a.EndTime > b.EndTime + end + + function LibClasses:NewAnimation(length, delay, ease, callback) + delay = delay or 0 + ease = ease or -1 + + if self.m_AnimQueue then + delay = delay + self:AnimTail() + self.m_AnimQueue = false + else + delay = delay + SysTime() + end + + local anim = { + StartTime = delay, + EndTime = delay + length, + Ease = ease, + OnEnd = callback + } + + self:SetAnimationEnabled(true) + + if self.m_AnimList == nil then + self.m_AnimList = {} + end + + table.insert(self.m_AnimList, anim) + table.sort(self.m_AnimList, sort) + + self.AnimationThink = self.AnimationThinkInternal + + return anim + end + + local MoveThink = function(anim, panel, frac) + if not anim.startx then + anim.startx = panel.x + anim.starty = panel.y + end + + local x = Lerp(frac, anim.startx, anim.x) + local y = Lerp(frac, anim.starty, anim.y) + panel:SetPos(x, y) + end + + function LibClasses:MoveTo(x, y, length, delay, ease, callback) + if self.x == x and self.y == y then return end + + local anim = self:NewAnimation(length, delay, ease, callback) + anim.x = x + anim.y = y + anim.Think = MoveThink + end + + local SetSize = Panel.SetSize + local SizeThink = function(anim, panel, frac) + if not anim.startw then + anim.startw, anim.starth = panel:GetSize() + end + + local w, h + if anim.sizew and anim.sizeh then + w = Lerp(frac, anim.startw, anim.w) + h = Lerp(frac, anim.starth, anim.h) + SetSize(panel, w, h) + elseif anim.sizew then + w = Lerp(frac, anim.startw, anim.w) + SetSize(panel, w, panel.starth) + else + h = Lerp(frac, anim.starth, anim.h) + SetSize(panel, panel.startw, h) + end + + if panel:GetDock() > 0 then + panel:InvalidateParent() + end + end + + function LibClasses:SizeTo(w, h, length, delay, ease, callback) + local anim = self:NewAnimation(length, delay, ease, callback) + + if w ~= -1 then + anim.sizew = true + end + + if h ~= -1 then + anim.sizeh = true + end + + anim.w, anim.h = w, h + anim.Think = SizeThink + + return anim + end + + local SetVisible = Panel.SetVisible + local IsVisible = Panel.IsVisible + + local is_visible = function(s) + local state = s.visible_state + if state ~= nil then + return state + else + return IsVisible(s) + end + end + + function LibClasses:AnimatedSetVisible(visible, cb) + if visible == is_visible(self) then + if cb then + cb() + end + return + end + + if visible then + SetVisible(self, true) + end + + self.visible_state = visible + self:Stop() + + self:AlphaTo(visible and 255 or 0, 0.2, 0, function() + SetVisible(self, visible) + self:InvalidateParent(true) + if cb then + cb() + end + end) + + self:InvalidateParent(true) + end + + function LibClasses:AnimatedToggleVisible() + self:AnimatedSetVisible(not is_visible(self)) + end + + function LibClasses:AnimatedIsVisible() + return is_visible(self) + end +end + +function Panel:SUI_TDLib() + for k, v in pairs(LibClasses) do + self[k] = v + end + return self +end + +TDLibUtil.Install = Panel.SUI_TDLib + +local count = 0 +TDLibUtil.Start = function() + count = count + 1 + for k, v in pairs(LibClasses) do + if not Panel["SUI_OLD" .. k] then + local old = Panel[k] + if old == nil then + old = v + end + Panel[k], Panel["SUI_OLD" .. k] = v, old + end + end +end + +TDLibUtil.End = function() + count = count - 1 + if count > 0 then return end + for k, v in pairs(LibClasses) do + local old = Panel["SUI_OLD" .. k] + if old == v then + Panel[k] = nil + else + Panel[k] = old + end + Panel["SUI_OLD" .. k] = nil + end +end + +TDLibUtil.HoverFunc = function(p) + return p:IsHovered() and not p:GetDisabled() +end + +TDLibUtil.DrawOutlinedBox = function(radius, x, y, w, h, bg, outline, thickness) + thickness = thickness or 2 + draw.RoundedBox(radius, x, y, w, h, outline) + draw.RoundedBox(radius, x + thickness, y + thickness, w - (thickness * 2), h - (thickness * 2), bg) +end + +do + local cos, sin, sqrt = math.cos, math.sin, math.sqrt + local clamp, floor = math.Clamp, math.floor + local min, max = math.min, math.max + + local calc_ellipse_points = function(rx, ry) + local points = sqrt(((rx * ry) / 2) * 6) + return max(points, 8) + end + + local M_PI = 3.14159265358979323846 + calc_rect = function(c, r, x, y, w, h) + if + (c.r == r) and + (c.x == x and c.y == y) and + (c.w == w and c.h == h) + then return end + + r = clamp(r, 0, min(w, h) / 2) + + local rx, ry = r, r + if w >= 0.02 then + rx = min(rx, w / 2.0 - 0.01) + end + if h >= 0.02 then + ry = min(ry, h / 2.0 - 0.01) + end + + local points = max(calc_ellipse_points(rx, ry) / 4, 1) + points = floor(points) + + local half_pi = M_PI / 2 + local angle_shift = half_pi / (points + 1) + + local phi = 0 + for i = 1, points + 2 do + c[i] = { + x = x + rx * (1 - cos(phi)), + y = y + ry * (1 - sin(phi)) + } + phi = phi + angle_shift + end + + phi = half_pi + for i = points + 3, 2 * (points + 2) do + c[i] = { + x = x + w - rx * (1 + cos(phi)), + y = y + ry * (1 - sin(phi)) + } + phi = phi + angle_shift + end + + phi = 2 * half_pi + for i = (2 * (points + 2)) + 1, 3 * (points + 2) do + c[i] = { + x = x + w - rx * (1 + cos(phi)), + y = y + h - ry * (1 + sin(phi)) + } + phi = phi + angle_shift + end + + phi = 3 * half_pi + for i = (3 * (points + 2)) + 1, 4 * (points + 2) do + c[i] = { + x = x + rx * (1 - cos(phi)), + y = y + h - ry * (1 + sin(phi)) + } + phi = phi + angle_shift + end + + local last = (points + 2) * 4 + 1 + c[last] = c[1] + + for i = last + 1, #c do + c[i] = nil + end + + c.r = r + c.x, c.y = x, y + c.w, c.h = w, h + end + + TDLibUtil.RoundedBox = function(c, r, x, y, w, h, color) + calc_rect(c, r, x, y, w, h) + + SetDrawColor(color) + NoTexture() + DrawPoly(c) + end +end + +TDLibUtil.LibClasses = LibClasses + +sui.TDLib = TDLibUtil diff --git a/garrysmod/addons/sui/lua/sui/libs/types.lua b/garrysmod/addons/sui/lua/sui/libs/types.lua new file mode 100644 index 0000000..5504e83 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/libs/types.lua @@ -0,0 +1,82 @@ +-- https://gist.github.com/CapsAdmin/0d9c1e77d0fc22d910e182bfeb9812e5 +local getmetatable = getmetatable + +do + local types = { + ["string"] = "", + ["boolean"] = true, + ["number"] = 0, + ["function"] = function() end, + ["thread"] = coroutine.create(getmetatable), + ["Color"] = Color(0, 0, 0), + } + + for k, v in pairs(types) do + if not getmetatable(v) then + debug.setmetatable(v, {MetaName = k}) + else + getmetatable(v).MetaName = k + end + end +end + +function sui.type(value) + if value == nil then + return "nil" + end + local meta = getmetatable(value) + if meta then + meta = meta.MetaName + if meta then + return meta + end + end + return "table" +end + +do + local function add(name) + local new_name = name + if name == "bool" then + new_name = "boolean" + end + sui["is" .. name:lower()] = function(value) + local meta = getmetatable(value) + if meta and meta.MetaName == new_name then + return true + else + return false + end + end + end + + add("string") + add("number") + add("bool") + add("function") + + add("Angle") + add("Vector") + add("Panel") + add("Matrix") +end + +function sui.isentity(value) + local meta = getmetatable(value) + if meta then + if meta.MetaName == "Entity" then + return true + end + meta = meta.MetaBaseClass + if meta then + return meta.MetaName == "Entity" + end + end + return false +end +sui.IsEntity = sui.isentity + +local type = sui.type +function sui.istable(value) + return type(value) == "table" +end diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_combobox.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_combobox.lua new file mode 100644 index 0000000..2d23a4d --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_combobox.lua @@ -0,0 +1,66 @@ +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local TEXT_FONT = SUI.CreateFont("ComboBox", "Roboto Regular", 16) + +local GetColor = SUI.GetColor +local draw_material = sui.draw_material + +local PANEL = {} + +PANEL.NoOverrideClear = true + +sui.scaling_functions(PANEL) + +function PANEL:Init() + self:ScaleInit() + self.DropButton:Remove() + self:SetFont(TEXT_FONT) + self:SetSize(34, 22) + self:SetIsMenu(true) + + local image = self:Add(NAME .. ".Image") + image:Dock(FILL) + image:SetImage("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sui/arrow.png") + image.Draw = self.Paint +end + +function PANEL:OpenMenu(pControlOpener) + if pControlOpener and pControlOpener == self.TextEntry then return end + if #self.Choices == 0 then return end + + if IsValid(self.Menu) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = vgui.Create(NAME .. ".Menu", self) + self.Menu:SetInternal(self) + + for k, v in ipairs(self.Choices) do + self.Menu:AddOption(v, function() + self:ChooseOption(v, k) + end) + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) +end + +function PANEL:Paint(w, h, from_image) + local text_color = GetColor("menu_option_hover_text") + + if from_image then + local size = SUI.ScaleEven(10) + draw_material(nil, w - (size / 2) - 6, h / 2, size, text_color) + else + local col = GetColor("menu") + self:RoundedBox("Background", 4, 0, 0, w, h, col) + self:SetTextColor(text_color) + end +end + +function PANEL:PerformLayout() +end + +sui.register("ComboBox", PANEL, "DComboBox") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_frame.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_frame.lua new file mode 100644 index 0000000..61eba52 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_frame.lua @@ -0,0 +1,316 @@ +local math = math +local gui = gui +local draw = draw +local surface = surface + +local ScrW = ScrW +local ScrH = ScrH +local IsValid = IsValid +local ipairs = ipairs + +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name +local TDLib = sui.TDLib + +local FRAME_FONT = SUI.CreateFont("Frame", "Roboto", 18) + +local Panel = FindMetaTable("Panel") + +local PANEL = {} + +AccessorFunc(PANEL, "m_bHeaderHeight", "HeaderHeight", FORCE_NUMBER) +AccessorFunc(PANEL, "m_bTitleFont", "TitleFont", FORCE_STRING) +AccessorFunc(PANEL, "m_bSizable", "Sizable", FORCE_BOOL) +AccessorFunc(PANEL, "m_iMinWidth", "MinWidth", FORCE_NUMBER) +AccessorFunc(PANEL, "m_iMinHeight", "MinHeight", FORCE_NUMBER) + +local header_Think = function(s) + local parent = s.parent + local sw, sh = ScrW(), ScrH() + + if s.dragging then + local x, y = input.GetCursorPos() + x, y = math.Clamp(x, 1, sw - 1), math.Clamp(y, 1, sh - 1) + x, y = x - s.dragging[1], y - s.dragging[2] + + parent:SetPos(x, y) + parent:InvalidateLayout(true) + parent:OnPosChanged() + else + local x, y, w, h = parent:GetBounds() + parent:SetPos(math.Clamp(x, 5, sw - w - 5), math.Clamp(y, 5, sh - h - 5)) + end +end + +local header_OnMousePressed = function(s) + local parent = s.parent + s.dragging = {gui.MouseX() - parent.x, gui.MouseY() - parent.y} + s:MouseCapture(true) +end + +local header_OnMouseReleased = function(s) + s.dragging = nil + s:MouseCapture(false) +end + +local title_SetBGColor = function(s, c) + s:SetVisible(c and true or false) +end + +local title_update_color = function(s) + s:SetTextColor(SUI.GetColor("title")) +end + +local close_DoClick = function(s) + s.frame:Remove() +end + +function PANEL:Init() + local header_buttons = {} + self.header_buttons = header_buttons + + self:Center() + self:SetHeaderHeight(28) + + local header = self:Add("PANEL") + header:Dock(TOP) + header.Paint = self.HeaderPaint + header:SetCursor("sizeall") + + header.parent = self + header.Think = header_Think + header.OnMousePressed = header_OnMousePressed + header.OnMouseReleased = header_OnMouseReleased + self.header = header + + local title = header:Add(NAME .. ".Label") + title:Dock(LEFT) + title:DockMargin(6, 2, 0, 2) + title:SetText("") + title:SetTextColor(SUI.GetColor("title")) + title:SizeToContents() + title.SetBGColor = title_SetBGColor + hook.Add(NAME .. ".ThemeChanged", title, title_update_color) + self.title = title + + self.close = self:AddHeaderButton("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sui/close.png", "close", close_DoClick) + self.close.frame = self + + self:SetSize(SUI.Scale(520), SUI.Scale(364)) + self:SetTitleFont(FRAME_FONT) + SUI.OnScaleChanged(self, self.ScaleChanged) + + function self:PerformLayout(w, h) + if IsValid(title) then + title:SizeToContents() + end + + if IsValid(header) then + header:SetTall(SUI.Scale(self:GetHeaderHeight())) + end + + for k, v in ipairs(header_buttons) do + if IsValid(v) then + v:SetWide(v:GetTall()) + local margin = SUI.Scale(4) + v.image:DockMargin(margin, margin, margin, margin) + end + end + end +end + +function PANEL:SetSize(w, h) + self.real_w, self.real_h = w, h + self:ScaleChanged() +end + +function PANEL:HeaderPaint(w, h) + draw.RoundedBoxEx(3, 0, 0, w, h, SUI.GetColor("header"), true, true) +end + +local SetSize = Panel.SetSize +PANEL.RealSetSize = SetSize +function PANEL:ScaleChanged() + if self.sizing then return end + + local new_w, new_h = SUI.Scale(self.real_w), SUI.Scale(self.real_h) + self.x, self.y = self.x + (self:GetWide() / 2 - new_w / 2), self.y + (self:GetTall() / 2 - new_h / 2) + SetSize(self, new_w, new_h) + self:InvalidateLayout(true) +end + +function PANEL:Paint(w, h) + if SUI.GetColor("frame_blur") then + TDLib.BlurPanel(self) + end + + self:RoundedBox("Background", 3, 0, 0, w, h, SUI.GetColor("frame")) +end + +function PANEL:SetTitleFont(font) + self.m_bTitleFont = font + self.title:SetFont(font) +end + +function PANEL:SetTitle(text) + self.title:SetText(text) + self.title:SizeToContents() +end + +function PANEL:AddHeaderButton(image_name, name, callback) + local button = self.header:Add("DButton") + button:SetText("") + button:Dock(RIGHT) + button:DockMargin(0, 2, #self.header:GetChildren() == 1 and 4 or 2, 2) + + local hover = name .. "_hover" + local press = name .. "_press" + local circle = {} + button.Paint = function(s, w, h) + if s:IsHovered() then + TDLib.DrawCircle(circle, w / 2, h / 2, w / 2, SUI.GetColor(hover)) + end + + if s.Depressed then + TDLib.DrawCircle(circle, w / 2, h / 2, w / 2, SUI.GetColor(press)) + end + end + button.DoClick = callback + + local image = button:Add(NAME .. ".Image") + image:Dock(FILL) + image:SetMouseInputEnabled(false) + image:SetImage(image_name) + + button.image = image + + table.insert(self.header_buttons, button) + + return button +end + +function PANEL:OnMousePressed(_, checking) + if not self.m_bSizable then return end + + local x, y = self:LocalToScreen(0, 0) + local w, h = self:GetSize() + if gui.MouseX() > (x + w - 20) and gui.MouseY() > (y + h - 20) then + if not checking then + self.sizing = {gui.MouseX() - w, gui.MouseY() - h} + self:MouseCapture(true) + end + + self:SetCursor("sizenwse") + + return + end + + if checking then + self:SetCursor("arrow") + end +end + +function PANEL:OnMouseReleased() + if not self.m_bSizable then return end + + self:MouseCapture(false) + SUI.CallScaleChanged() + self.sizing = nil +end + +function PANEL:Think() + if not self.m_bSizable then return end + + self:OnMousePressed(nil, true) + + if not self.sizing then return end + + local sw, sh = ScrW(), ScrH() + + local cx, cy = input.GetCursorPos() + local mousex = math.Clamp(cx, 1, sw - 1) + local mousey = math.Clamp(cy, 1, sh - 1) + + local x = mousex - self.sizing[1] + x = math.Clamp(x, self.m_iMinWidth, sw - 10) + + local y = mousey - self.sizing[2] + y = math.Clamp(y, self.m_iMinHeight, sh - 10) + + self.real_w, self.real_h = x, y + SetSize(self, x, y) + self:InvalidateLayout(true) + self:SetCursor("sizenwse") +end + +function PANEL:OnPosChanged() +end + +local SetVisible = Panel.SetVisible +local Remove = Panel.Remove + +local anim_speed = 0.2 + +local show = function(s) + local w, h = s.real_w, s.real_h + + if s.anim_scale then + w, h = SUI.Scale(w), SUI.Scale(h) + end + + SetVisible(s, true) + + SetSize(s, w * 1.1, h * 1.1) + s:Center() + + s:Stop() + s:SizeTo(w, h, anim_speed, 0, -1) + s:MoveTo((ScrW() / 2) - (w / 2), (ScrH() / 2) - (h / 2), anim_speed, 0, -1) + s:AlphaTo(255, anim_speed + 0.02, 0) + s:MakePopup() +end + +local remove = function(s, hide) + if not hide and not s:IsVisible() then + Remove(s) + return + end + + local w, h = s.real_w, s.real_h + + if s.anim_scale then + w, h = SUI.Scale(w), SUI.Scale(h) + end + + w, h = w * 1.1, h * 1.1 + + s:Stop() + s:SizeTo(w, h, anim_speed, 0, -1) + s:MoveTo((ScrW() / 2) - (w / 2), (ScrH() / 2) - (h / 2), anim_speed, 0, -1) + s:SetMouseInputEnabled(false) + s:SetKeyboardInputEnabled(false) + s:AlphaTo(0, anim_speed + 0.02, 0, function() + if hide then + SetVisible(s, false) + else + Remove(s) + end + end) +end + +local hide = function(s) + remove(s, true) +end + +function PANEL:AddAnimations(w, h, no_scale) + self.anim_scale = not no_scale + self.real_w, self.real_h = w, h + + self:SetAlpha(0) + show(self) + + self.Remove = remove + self.Hide = hide + self.Show = show +end + +sui.register("Frame", PANEL, "EditablePanel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_image.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_image.lua new file mode 100644 index 0000000..c20b19e --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_image.lua @@ -0,0 +1,324 @@ +local table = table +local file = file +local coroutine = coroutine +local surface = surface + +local UnPredictedCurTime = UnPredictedCurTime +local pairs = pairs + +local color_white = color_white + +local sui = sui +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local read_gif = include("sui/libs/gif_loader.lua") +local generate_png = include("sui/libs/png_encoder.lua") + +local images_path = (NAME .. "/images/"):lower() +file.CreateDir(images_path) + +local get_image_path = function(url) + return images_path .. (url:gsub("%W", "_") .. ".png") +end + +local Write = file.Write +local gif_to_png; do + local internal_gif_to_png = function(file_path, chunk) + local gif = read_gif(chunk) + local frames = gif:get_frames() + local w, h = gif.width, gif.height + + local path = file_path .. "/" + file.CreateDir(path) + + for frame_id = 1, #frames do + local frame = frames[frame_id] + local data = frame.data + local png = generate_png(w, h, data) + Write(("%s%d_%d.png"):format(path, frame_id, frame.delay), png) + coroutine.yield() + end + end + + local delay = 0.01 + local next_run = 0 + + local coroutines = {} + local callbacks = {} + gif_to_png = function(file_path, data, callback) + local co = coroutine.create(internal_gif_to_png) + local i = table.insert(coroutines, co) + callbacks[i] = callback + coroutine.resume(co, file_path, data) + next_run = UnPredictedCurTime() + end + + hook.Add("Think", NAME .. "ProcessGIFs", function() + local co = coroutines[1] + if not co then return end + if UnPredictedCurTime() < next_run then return end + + if coroutine.status(co) == "suspended" then + coroutine.resume(co) + else + callbacks[1]() + table.remove(coroutines, 1) + table.remove(callbacks, 1) + end + + next_run = UnPredictedCurTime() + delay + end) + + hook.Add(NAME .. "ImagesCleared", "ClearCoroutines", function() + table.Empty(coroutines) + table.Empty(callbacks) + end) +end + +local download_image, is_downloading_image; do + -- https://stackoverflow.com/questions/25959386/how-to-check-if-a-file-is-a-valid-image + local valid_images = { + ["\xff\xd8\xff"] = "jpeg", + ["\x89PNG\r\n\x1a\n"] = "png", + ["GIF87a"] = "gif", + ["GIF89a"] = "gif", + } + + local get_image_type = function(data) + for k, v in pairs(valid_images) do + if data:StartWith(k) then + return v + end + end + return false + end + + local downloading_images = {} + + local process_callbacks = function(url) + local callbacks = downloading_images[url] or {} + downloading_images[url] = nil + + for _, func in ipairs(callbacks) do + func() + end + end + + download_image = function(url, callback) + if downloading_images[url] then + table.insert(downloading_images[url], callback) + return + end + + downloading_images[url] = {callback} + + http.Fetch(url, function(data) + local image_type = get_image_type(data) + if not image_type then + downloading_images[url] = nil + return + end + + local image_path = get_image_path(url) + + if image_type == "gif" then + gif_to_png(image_path, data, function() + process_callbacks(url) + end) + else + file.Write(image_path, data) + process_callbacks(url) + end + end, function(err) + print("(SUI) Failed to download an image, error: " .. err) + downloading_images[url] = nil + end) + end + + is_downloading_image = function(url) + return downloading_images[url] ~= nil + end + + hook.Add(NAME .. "ImagesCleared", "ClearDownloadingImages", function() + table.Empty(downloading_images) + end) +end + +local images_panels = {} + +local PANEL = {} + +local err_mat = SUI.Material("error") + +function PANEL:Init() + self:SetMouseInputEnabled(false) + + self.minus = 0 + self.rotation = 0 + self.image = err_mat + self.image_col = color_white + + table.insert(images_panels, self) +end + +function PANEL:OnRemove() + for k, v in ipairs(images_panels) do + if v == self then + table.remove(images_panels, k) + return + end + end +end + +function PANEL:SetMinus(minus) + self.minus = minus +end + +function PANEL:SetRotation(rotation) + self.rotation = rotation +end + +function PANEL:SetImageColor(col) + self.image_col = col +end + +local cached_files = {} +local get_files = function(image_path) + local f = cached_files[image_path] + if f then return f end + + cached_files[image_path] = file.Find(image_path .. "/*", "DATA") + + return cached_files[image_path] +end + +function PANEL:SetImage(url) + self.image = err_mat + + self.pos = nil + self.delay = nil + + self.images = nil + self.delays = nil + self.url = url + + if url:sub(1, 4) ~= "http" then + self.image = SUI.Material(url) + return + end + + local image_path = get_image_path(url) + if not file.Exists(image_path, "DATA") or is_downloading_image(url) then + download_image(url, function() + if self:IsValid() then + self:SetImage(url) + end + end) + return + end + + local is_gif = file.IsDir(image_path, "DATA") + if is_gif then + local images = {} + local delays = {} + + local files = get_files(image_path) + for i = 1, #files do + local v = files[i] + local id, delay = v:match("(.*)_(.*)%.png") + id = tonumber(id) + local img_path = "../data/" .. image_path .. "/" .. v + images[id] = img_path + delays[id] = delay + end + + self.frame = 1 + self.delay = (UnPredictedCurTime() * 100) + delays[1] + + self.images = images + self.delays = delays + + self.max_images = #files + else + self.image = SUI.Material("../data/" .. image_path) + end +end + +local SetMaterial = surface.SetMaterial +function PANEL:PaintGIF(w, h, images) + local frame = self.frame + + -- SUI.Material() caches materials by default + local mat = SUI.Material(images[frame], true) + if not mat then + if frame > 1 then + mat = SUI.Material(images[frame - 1]) + else + mat = err_mat + end + + SetMaterial(mat) + + return + end + + SetMaterial(mat) + + local curtime = UnPredictedCurTime() * 100 + if curtime < self.delay then return end + frame = frame + 1 + if frame > self.max_images then + frame = 1 + end + + self.frame = frame + self.delay = curtime + self.delays[frame] +end + +local PaintGIF = PANEL.PaintGIF +local SetDrawColor = surface.SetDrawColor +local DrawTexturedRectRotated = surface.DrawTexturedRectRotated +function PANEL:Paint(w, h) + SetDrawColor(self.image_col) + + local images = self.images + if images then + PaintGIF(self, w, h, images) + else + SetMaterial(self.image) + end + + if self.Draw then + self:Draw(w, h, true) + else + local minus = self.minus + DrawTexturedRectRotated(w * 0.5, h * 0.5, w - minus, h - minus, self.rotation) + end +end + +sui.register("Image", PANEL, "PANEL") + +function SUI.ClearImages() + local files, dirs = file.Find(images_path .. "/*", "DATA") + for _, f in ipairs(files) do + file.Delete(images_path .. f) + end + + for _, d in ipairs(dirs) do + for _, f in ipairs(file.Find(images_path .. d .. "/*", "DATA")) do + file.Delete(images_path .. (d .. "/" .. f)) + end + file.Delete(images_path .. d) + end + + table.Empty(SUI.materials) + table.Empty(cached_files) + + hook.Call(NAME .. "ImagesCleared") + + for k, v in ipairs(images_panels) do + if v.url then + v:SetImage(v.url) + end + end +end diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_label.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_label.lua new file mode 100644 index 0000000..232e301 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_label.lua @@ -0,0 +1,210 @@ +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name +local MOUSE_LEFT = MOUSE_LEFT + +local SysTime = SysTime + +local PANEL = {} + +AccessorFunc(PANEL, "m_colText", "TextColor") +AccessorFunc(PANEL, "m_colTextStyle", "TextStyleColor") +AccessorFunc(PANEL, "m_FontName", "Font") + +AccessorFunc(PANEL, "m_bDoubleClicking", "DoubleClickingEnabled", FORCE_BOOL) +AccessorFunc(PANEL, "m_bAutoStretchVertical", "AutoStretchVertical", FORCE_BOOL) +AccessorFunc(PANEL, "m_bIsMenuComponent", "IsMenu", FORCE_BOOL) + +AccessorFunc(PANEL, "m_bBackground", "PaintBackground", FORCE_BOOL) +AccessorFunc(PANEL, "m_bBackground", "DrawBackground", FORCE_BOOL) +AccessorFunc(PANEL, "m_bDisabled", "Disabled", FORCE_BOOL) + +AccessorFunc(PANEL, "m_bIsToggle", "IsToggle", FORCE_BOOL) +AccessorFunc(PANEL, "m_bToggle", "Toggle", FORCE_BOOL) + +AccessorFunc(PANEL, "m_bBright", "Bright", FORCE_BOOL) +AccessorFunc(PANEL, "m_bDark", "Dark", FORCE_BOOL) +AccessorFunc(PANEL, "m_bHighlight", "Highlight", FORCE_BOOL) + +PANEL:SetIsToggle(false) +PANEL:SetToggle(false) +PANEL:SetDisabled(false) +PANEL:SetDoubleClickingEnabled(true) + +local Panel = FindMetaTable("Panel") +local SetMouseInputEnabled = Panel.SetMouseInputEnabled +local SetPaintBackgroundEnabled = Panel.SetPaintBackgroundEnabled +local SetPaintBorderEnabled = Panel.SetPaintBorderEnabled +local InvalidateLayout = Panel.InvalidateLayout +local SetFGColor = Panel.SetFGColor +function PANEL:Init() + SetMouseInputEnabled(self, false) + SetPaintBackgroundEnabled(self, false) + SetPaintBorderEnabled(self, false) +end + +function PANEL:AllowScale() + SUI.OnScaleChanged(self, self.ScaleChanged) +end + +function PANEL:ScaleChanged() + self:SizeToContents() +end + +function PANEL:SetFont(font) + if self.m_FontName == font then return end + + self.m_FontName = font + self:SetFontInternal(self.m_FontName) +end + +function PANEL:SetTextColor(col) + if self.m_colText == col then return end + + self.m_colText = col + SetFGColor(self, col.r, col.g, col.b, col.a) +end +PANEL.SetColor = PANEL.SetTextColor + +function PANEL:GetColor() + return self.m_colText or self.m_colTextStyle +end + +function PANEL:Toggle() + if not self:GetIsToggle() then return end + + self:SetToggle(not self:GetToggle()) + self:OnToggled(self:GetToggle()) +end + +function PANEL:SetDisabled(bDisabled) + self.m_bDisabled = bDisabled + InvalidateLayout(self) +end + +function PANEL:SetEnabled(bEnabled) + self:SetDisabled(not bEnabled) +end + +function PANEL:IsEnabled() + return not self:GetDisabled() +end + +function PANEL:ApplySchemeSettings() + local col = self:GetColor() + if not col then return end + + self:SetFGColor(col.r, col.g, col.b, col.a) +end + +function PANEL:AutoStretchVerticalThink() + self:SizeToContentsY() +end + +function PANEL:SetAutoStretchVertical(enable) + self.m_bAutoStretchVertical = enable + self.Think = enable and self.AutoStretchVerticalThink or nil +end + +function PANEL:OnCursorEntered() + InvalidateLayout(self, true) +end + +function PANEL:OnCursorExited() + InvalidateLayout(self, true) +end + +function PANEL:OnMousePressed(mousecode) + if self:GetDisabled() then return end + + if mousecode == MOUSE_LEFT and not dragndrop.IsDragging() and self.m_bDoubleClicking then + if self.LastClickTime and SysTime() - self.LastClickTime < 0.2 then + + self:DoDoubleClickInternal() + self:DoDoubleClick() + return + end + + self.LastClickTime = SysTime() + end + + if self:IsSelectable() and mousecode == MOUSE_LEFT and input.IsShiftDown() then + return self:StartBoxSelection() + end + + self:MouseCapture(true) + self.Depressed = true + self:OnDepressed() + InvalidateLayout(self, true) + + self:DragMousePress(mousecode) +end + +function PANEL:OnMouseReleased(mousecode) + self:MouseCapture(false) + + if self:GetDisabled() then return end + if not self.Depressed and dragndrop.m_DraggingMain ~= self then return end + + if self.Depressed then + self.Depressed = nil + self:OnReleased() + InvalidateLayout(self, true) + end + + if self:DragMouseRelease(mousecode) then return end + + if self:IsSelectable() and mousecode == MOUSE_LEFT then + local canvas = self:GetSelectionCanvas() + if canvas then + canvas:UnselectAll() + end + end + + if not self.Hovered then return end + + self.Depressed = true + + if mousecode == MOUSE_RIGHT then + self:DoRightClick() + end + + if mousecode == MOUSE_LEFT then + self:DoClickInternal() + self:DoClick() + end + + if mousecode == MOUSE_MIDDLE then + self:DoMiddleClick() + end + + self.Depressed = nil +end + +function PANEL:OnReleased() +end + +function PANEL:OnDepressed() +end + +function PANEL:OnToggled(bool) +end + +function PANEL:DoClick() + self:Toggle() +end + +function PANEL:DoRightClick() +end + +function PANEL:DoMiddleClick() +end + +function PANEL:DoClickInternal() +end + +function PANEL:DoDoubleClick() +end + +function PANEL:DoDoubleClickInternal() +end + +sui.register("Label", PANEL, "Label") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_label_panel.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_label_panel.lua new file mode 100644 index 0000000..a35d4de --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_label_panel.lua @@ -0,0 +1,71 @@ +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local LABEL_FONT = SUI.CreateFont("LabelPanel", "Roboto", 18) + +local PANEL = {} + +local add = function(s, c) + if IsValid(s.pnl) then + s.pnl:Remove() + end + + local pnl = vgui.Create(c, s) + s.pnl = pnl + + return pnl +end + +function PANEL:Init() + self.title = "" + + local label = self:Add(NAME .. ".Label") + label:Dock(LEFT) + self.label = label + + self:SetFont(LABEL_FONT) + + self:Dock(TOP) + self:InvalidateLayout(true) + self.Add = add +end + +function PANEL:SetPanel(pnl) + if IsValid(self.pnl) then + self.pnl:Remove() + end + + pnl:SetParent(self) + self.pnl = pnl +end + +function PANEL:SetLabel(lbl) + self.title = lbl + self:InvalidateLayout(true) +end + +function PANEL:SetFont(font) + self.font = font + self.label:SetFont(font) +end + +function PANEL:PerformLayout(w, h) + local label = self.label + local pnl = self.pnl + + local pnl_w, pnl_h = 0, 0 + if pnl then + pnl_w, pnl_h = pnl:GetSize() + end + + label:SetWide(w - pnl_w - 4) + label:SetText(sui.wrap_text(self.title, self.font, w - pnl_w - 4)) + + local _, _h = label:GetTextSize() + self:SetTall(math.max(_h, pnl_h)) + + if pnl then + pnl:SetPos(w - pnl_w, h / 2 - pnl_h / 2) + end +end + +sui.register("LabelPanel", PANEL, "PANEL") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_number_slider.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_number_slider.lua new file mode 100644 index 0000000..2f8932c --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_number_slider.lua @@ -0,0 +1,35 @@ +local surface = surface + +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local NUMBER_SLIDER_FONT = SUI.CreateFont("NumberSlider", "Roboto Regular", 14) + +local PANEL = {} + +sui.scaling_functions(PANEL) + +function PANEL:Init() + self:ScaleInit() + + local slider = vgui.Create(NAME .. ".Slider", self, "NumberSlider") + slider:Dock(FILL) + + self.slider = slider + + local label = self:Add(NAME .. ".Label") + label:Dock(RIGHT) + label:DockMargin(3, 0, 0, 0) + label:SetFont(NUMBER_SLIDER_FONT) + self.label = label + + function label:Think() + self:SetText(slider:GetValue()) + + self:SizeToContents() + end + + self:SetSize(100, 12) + self:InvalidateLayout(true) +end + +sui.register("NumberSlider", PANEL, "Panel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_panel.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_panel.lua new file mode 100644 index 0000000..54f71a9 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_panel.lua @@ -0,0 +1,9 @@ +local PANEL = {} + +sui.scaling_functions(PANEL) + +function PANEL:Init() + self:ScaleInit() +end + +sui.register("Panel", PANEL, "Panel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_property_sheet.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_property_sheet.lua new file mode 100644 index 0000000..fcfb292 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_property_sheet.lua @@ -0,0 +1,161 @@ +local draw = draw +local surface = surface +local vgui = vgui + +local TYPE_MATERIAL = TYPE_MATERIAL + +local RealFrameTime = RealFrameTime +local IsValid = IsValid +local Lerp = Lerp +local pairs = pairs +local TypeID = TypeID + +local TDLib_Classes = sui.TDLib.LibClasses +local TextColor = TDLib_Classes.TextColor +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local PROPERTY_SHEET_FONT = SUI.CreateFont("PropertySheet", "Roboto Regular", 18) + +local PANEL = {} + +AccessorFunc(PANEL, "m_FontName", "Font", FORCE_STRING) + +function PANEL:Init() + self.tabs = {} + + self:SetFont(PROPERTY_SHEET_FONT) + + local tab_scroller = self:Add("DHorizontalScroller") + tab_scroller:Dock(TOP) + + self.tabs_tall = 26 + self.tab_scroller = tab_scroller + + self:ScaleChanged() + SUI.OnScaleChanged(self, self.ScaleChanged) +end + +function PANEL:ScaleChanged() + self.tab_scroller:SetTall(SUI.Scale(self.tabs_tall)) + + for k, v in pairs(self.tab_scroller.Panels) do + if v:IsValid() then + if v.Material then + v:SetWide(self.tab_scroller:GetTall()) + else + v:SizeToContentsX() + end + end + end + + self:InvalidateLayout(true) +end + +function PANEL:Paint(w, h) + self:RoundedBox("Background", 1, 0, 0, w, self.tab_scroller:GetTall(), SUI.GetColor("property_sheet_bg")) +end + +function PANEL:PaintOver(w, h) + local active_tab = self:GetActiveTab() + if not IsValid(active_tab) then return end + + local tab_scroller = self.tab_scroller + local offset = tab_scroller:GetTall() - SUI.Scale(1) + + local x = active_tab:LocalToScreen(0) - self:LocalToScreen(0) + + if not self.activeTabX then + self.activeTabX = x + self.activeTabW = active_tab:GetWide() + end + + local delta = RealFrameTime() * 6 + if delta then + self.activeTabX = Lerp(delta, self.activeTabX, x) + self.activeTabW = Lerp(delta, self.activeTabW, active_tab:GetWide()) + end + + self:RoundedBox("Background2", 1, self.activeTabX, tab_scroller.y + offset, self.activeTabW, SUI.Scale(1), SUI.GetColor("property_sheet_tab_active")) +end + +local tab_Paint = function(s, w, h) + s.circle_click_color = SUI.GetColor("property_sheet_tab_click") + if s.property_sheet:GetActiveTab() == s then + TextColor(s, SUI.GetColor("property_sheet_tab_active")) + else + TextColor(s, SUI.GetColor("property_sheet_tab")) + end +end + +local tab_DoClick = function(s) + s.parent:SetActiveTab(s) +end + +local image_paint = function(s, w, h) + surface.SetDrawColor(color_white) + surface.SetMaterial(s.Material) + surface.DrawTexturedRectRotated(w * 0.5, h * 0.5, w - 10, h - 10, 0) +end + +function PANEL:AddSheet(name, load_func) + local tab = vgui.Create("DButton") + if TypeID(name) == TYPE_MATERIAL then + tab:SetText("") + tab.Material = name + tab.Paint = image_paint + tab:SetWide(self.tab_scroller:GetTall()) + else + tab:SetFont(self:GetFont()) + tab:SetText(name) + tab:SetTextInset(10, 0) + tab:SizeToContentsX() + + tab.Paint = tab_Paint + end + + tab.parent = self + tab.DoClick = tab_DoClick + + tab.load_func = load_func + tab.property_sheet = self + + tab.On = TDLib_Classes.On + TDLib_Classes.CircleClick(tab) + + self.tab_scroller:AddPanel(tab) + + if not self:GetActiveTab() then + self:SetActiveTab(tab) + end + + table.insert(self.tabs, tab) + + return tab +end + +function PANEL:GetActiveTab() + return self.active_tab +end + +function PANEL:SetActiveTab(new_tab) + if IsValid(new_tab) and not IsValid(new_tab.panel) then + local panel = new_tab.load_func(self) + panel:SetParent(self) + panel:SetVisible(false) + + panel.tab = new_tab + new_tab.panel = panel + end + + if self.active_tab and IsValid(self.active_tab.panel) then + self.active_tab.panel:SetVisible(false) + end + + if IsValid(new_tab) then + new_tab.panel:SetVisible(true) + end + + self.active_tab = new_tab +end + +sui.register("PropertySheet", PANEL, "EditablePanel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_query_box.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_query_box.lua new file mode 100644 index 0000000..3b6f37f --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_query_box.lua @@ -0,0 +1,130 @@ +local ScrW, ScrH = ScrW, ScrH +local DisableClipping = DisableClipping +local SetDrawColor = surface.SetDrawColor +local DrawRect = surface.DrawRect +local BlurPanel = sui.TDLib.BlurPanel +local lerp_color = sui.lerp_color + +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local GetColor = SUI.GetColor + +local PANEL = {} + +function PANEL:SetCallback(callback) + self.callback = callback +end + +function PANEL:Init() + self:SetSize(0, 0) + + local bottom = self:Add("Panel") + bottom:Dock(BOTTOM) + bottom:DockMargin(4, 10, 4, 4) + bottom:SetZPos(100) + + local save = bottom:Add(NAME .. ".Button") + save:SetText("SAVE") + save:Dock(RIGHT) + save:SetEnabled(false) + self.save = save + + function save.DoClick() + self.callback() + self:Remove() + end + + local cancel = bottom:Add(NAME .. ".Button") + cancel:Dock(RIGHT) + cancel:DockMargin(0, 0, 4, 0) + cancel:SetContained(false) + cancel:SetColors(GetColor("query_box_cancel"), GetColor("query_box_cancel_text")) + cancel:SetText("CANCEL") + self.cancel = cancel + + function cancel.DoClick() + self:Remove() + end + + bottom:SetSize(save:GetWide() * 2 + 4, SUI.Scale(30)) + + local body = self:Add("Panel") + body:Dock(FILL) + body:DockMargin(4, 4, 4, 4) + body:DockPadding(3, 3, 3, 3) + body:InvalidateLayout(true) + body:InvalidateParent(true) + + local added = 1 + function body.OnChildAdded(s, child) + added = added + 1 + child:Dock(TOP) + child:SetZPos(added) + child:InvalidateLayout(true) + s:InvalidateLayout(true) + end + self.body = body + + function self:Add(name) + return body:Add(name) + end + + local old_Paint = self.Paint + local trans = Color(0, 0, 0, 0) + local new_col = Color(70, 70, 70, 100) + function self:Paint(w, h) + lerp_color(trans, new_col) + + local x, y = self:LocalToScreen(0, 0) + DisableClipping(true) + BlurPanel(self) + SetDrawColor(trans) + DrawRect(x * -1, y * -1, ScrW(), ScrH()) + DisableClipping(false) + + old_Paint(self, w, h) + end +end + +function PANEL:ChildrenHeight() + local body = self.body + + self.header:InvalidateLayout(true) + local height = self.header:GetTall() + + body:InvalidateLayout(true) + self:InvalidateLayout(true) + height = height + select(2, body:ChildrenSize()) + + height = height + SUI.Scale(30) + 14 + 6 + + return height +end + +function PANEL:Paint(w, h) + if GetColor("frame_blur") then + BlurPanel(self) + end + + self:RoundedBox("Background", 8, 0, 0, w, h, GetColor("query_box_bg")) +end + +function PANEL:Done() + self:InvalidateChildren(true) + + self.size_to_children = function() + local h = self:ChildrenHeight() + self:RealSetSize(self:GetWide(), h) + self.real_h = h + end + + self:Center() + self:MakePopup() + self:DoModal(true) + + timer.Simple(0.08, function() + self:AddAnimations(self:GetWide(), self:ChildrenHeight(), true) + end) +end + +sui.register("QueryBox", PANEL, NAME .. ".Frame") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_scroll_panel.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_scroll_panel.lua new file mode 100644 index 0000000..540b4a3 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_scroll_panel.lua @@ -0,0 +1,198 @@ +local math = math +local table = table + +local pairs = pairs +local RealFrameTime = RealFrameTime + +local TDLib = sui.TDLib +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name +local RoundedBox = sui.TDLib.LibClasses.RoundedBox + +local Panel = {} + +AccessorFunc(Panel, "m_bFromBottom", "FromBottom", FORCE_BOOL) +AccessorFunc(Panel, "m_bVBarPadding", "VBarPadding", FORCE_NUMBER) + +Panel:SetVBarPadding(0) + +Panel.NoOverrideClear = true + +-- VBar +local starting_scroll_speed = 3 + +local vbar_OnMouseWheeled = function(s, delta) + s.scroll_speed = s.scroll_speed + (14 * RealFrameTime() --[[ slowly increase scroll speed ]]) + s:AddScroll(delta * -s.scroll_speed) +end + +-- default set scroll clamps amount +local vbar_SetScroll = function(s, amount) + if not s.Enabled then s.Scroll = 0 return end + + s.scroll_target = amount + s:InvalidateLayout() +end + +-- ¯\_(ツ)_/¯ https://github.com/Facepunch/garrysmod/blob/cd3d894288b847e3d081570129963d4089e36261/garrysmod/lua/vgui/dvscrollbar.lua#L234 +local vbar_OnCursorMoved = function(s, _, y) + if s.Dragging then + y = y - s.HoldPos + y = y / (s:GetTall() - s:GetWide() * 2 - s.btnGrip:GetTall()) + s.scroll_target = y * s.CanvasSize + end +end + +local vbar_Think = function(s) + local frame_time = RealFrameTime() * 14 + local scroll_target = s.scroll_target + + s.Scroll = Lerp(frame_time, s.Scroll, scroll_target) + + if not s.Dragging then + s.scroll_target = Lerp(frame_time, scroll_target, math.Clamp(scroll_target, 0, s.CanvasSize)) + end + + -- now start slowing it down!!! + s.scroll_speed = Lerp(frame_time / 14, s.scroll_speed, starting_scroll_speed) +end + +local vbar_Paint = function(s, w, h) + TDLib.RoundedBox(s.vertices, 3, 0, 0, w, h, SUI.GetColor("scroll")) +end + +local vbarGrip_Paint = function(s, w, h) + TDLib.RoundedBox(s.vertices, 3, 0, 0, w, h, SUI.GetColor("scroll_grip")) +end + +local vbar_PerformLayout = function(s, w, h) + local scroll = s:GetScroll() / s.CanvasSize + local bar_size = math.max(s:BarScale() * h, 10) + + local track = (h - bar_size) + 1 + scroll = scroll * track + + s.btnGrip.y = scroll + s.btnGrip:SetSize(w, bar_size) +end +-- + +function Panel:Init() + local canvas = self:GetCanvas() + canvas:SUI_TDLib() + + local children = {} + function canvas:OnChildAdded(child) + table.insert(children, child) + end + function canvas:OnChildRemoved(child) + for i = 1, #children do + local v = children[i] + if v == child then + table.remove(children, i) + return + end + end + end + canvas.GetChildren = function() + return children + end + canvas.children = children + + local vbar = self.VBar + vbar:SetHideButtons(true) + vbar.btnUp:SetVisible(false) + vbar.btnDown:SetVisible(false) + + vbar.vertices = {} + vbar.scroll_target = 0 + vbar.scroll_speed = starting_scroll_speed + + vbar.OnMouseWheeled = vbar_OnMouseWheeled + vbar.SetScroll = vbar_SetScroll + vbar.OnCursorMoved = vbar_OnCursorMoved + vbar.Think = vbar_Think + vbar.Paint = vbar_Paint + vbar.PerformLayout = vbar_PerformLayout + + vbar.btnGrip.vertices = {} + vbar.btnGrip.Paint = vbarGrip_Paint + + self:ScaleChanged() + SUI.OnScaleChanged(self, self.ScaleChanged) +end + +function Panel:OnChildAdded(child) + self:AddItem(child) + self:ChildAdded(child) +end + +function Panel:ChildAdded() +end + +function Panel:ScaleChanged() + local w = SUI.Scale(4) + + self.VBar:SetWide(w) + self.VBar.btnDown:SetSize(w, 0) + self.VBar.btnUp:SetSize(w, 0) +end + +function Panel:Paint(w, h) + local outline = SUI.GetColor("scroll_panel_outline") + if outline then + TDLib.DrawOutlinedBox(3, 0, 0, w, h, SUI.GetColor("scroll_panel"), outline, 1) + else + RoundedBox(self, "Background", 3, 0, 0, w, h, SUI.GetColor("scroll_panel")) + end +end + +function Panel:ScrollToBottom() + local vbar = self.VBar + for k, anim in pairs(vbar.m_AnimList or {}) do + anim:Think(vbar, 1) + vbar.m_AnimList[k] = nil + end + + self:InvalidateParent(true) + self:InvalidateChildren(true) + + vbar:SetScroll(vbar.CanvasSize) +end + +function Panel:PerformLayoutInternal(w, h) + w = w or self:GetWide() + h = h or self:GetTall() + + local canvas = self.pnlCanvas + + self:Rebuild() + + local vbar = self.VBar + vbar:SetUp(h, canvas:GetTall()) + + if vbar.Enabled then + w = w - vbar:GetWide() - self.m_bVBarPadding + end + + canvas:SetWide(w) + + self:Rebuild() +end + +function Panel:Think() + local canvas = self.pnlCanvas + + local vbar = self.VBar + if vbar.Enabled then + canvas.y = -vbar.Scroll + else + if self:GetFromBottom() then + canvas._y = Lerp(14 * RealFrameTime(), canvas._y or canvas.y, self:GetTall() - canvas:GetTall()) + else + canvas._y = Lerp(14 * RealFrameTime(), canvas._y or canvas.y, -vbar.Scroll) + end + canvas.y = canvas._y + end +end + +sui.register("ScrollPanel", Panel, "DScrollPanel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_slider.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_slider.lua new file mode 100644 index 0000000..7121f07 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_slider.lua @@ -0,0 +1,81 @@ +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name +local TDLib = sui.TDLib + +local Panel = {} + +sui.scaling_functions(Panel) + +AccessorFunc(Panel, "m_bValue", "Value", FORCE_NUMBER) +AccessorFunc(Panel, "m_bMin", "Min", FORCE_NUMBER) +AccessorFunc(Panel, "m_bMax", "Max", FORCE_NUMBER) +AccessorFunc(Panel, "m_bDecimals", "Decimals", FORCE_NUMBER) + +function Panel:Init() + self:ScaleInit() + + self:SetMin(0) + self:SetMax(10) + self:SetValue(1) + self:SetDecimals(1) + + self:SetSize(100, 12) + + self.rounded_box = {} + + self.Knob.circle = {} + self.Knob.Paint = self.KnobPaint + self:SetTrapInside(true) +end + +function Panel:SetMinMax(min, max) + self:SetMin(min) + self:SetMax(max) +end + +function Panel:TranslateValues(x, y) + self:SetValue(self:GetMin() + (x * self:GetRange())) + return self:GetFraction(), y +end + +function Panel:GetFraction() + return (self:GetValue() - self:GetMin()) / self:GetRange() +end + +function Panel:SetValue(val) + val = math.Clamp(val, self:GetMin(), self:GetMax()) + val = math.Round(val, self:GetDecimals()) + + self.m_bValue = val + self:SetSlideX((val - self:GetMin()) / self:GetRange()) + + self:OnValueChanged(val) +end + +function Panel:OnValueChanged(val) +end + +function Panel:GetRange() + return self:GetMax() - self:GetMin() +end + +function Panel:Paint(w, h) + local _h = SUI.Scale(2) + TDLib.RoundedBox(self.rounded_box, 3, 0, h / 2 - _h / 2, w, _h, SUI.GetColor("slider_track")) +end + +function Panel:KnobPaint(w, h) + if self.Depressed then + TDLib.DrawCircle(self.circle, w / 2, h / 2, h / 1.1, SUI.GetColor("slider_pressed")) + elseif self.Hovered then + TDLib.DrawCircle(self.circle, w / 2, h / 2, h / 1.1, SUI.GetColor("slider_hover")) + end + + TDLib.DrawCircle(self.circle, w / 2, h / 2, h / 2, SUI.GetColor("slider_knob")) +end + +function Panel:PerformLayout(w, h) + self.Knob:SetSize(SUI.Scale(12), SUI.Scale(12)) + DSlider.PerformLayout(self, w, h) +end + +sui.register("Slider", Panel, "DSlider") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_text_entry.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_text_entry.lua new file mode 100644 index 0000000..94fc96a --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_text_entry.lua @@ -0,0 +1,379 @@ +local sui = sui + +local surface = surface +local utf8 = sui.utf8 +local draw = draw +local math = math + +local IsValid = IsValid +local tostring = tostring +local tonumber = tonumber + +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local GetColor = SUI.GetColor +local TEXT_ENTRY_FONT = SUI.CreateFont("TextEntry", "Roboto Regular", 16) + +local Panel = {} + +sui.scaling_functions(Panel) + +AccessorFunc(Panel, "m_FontName", "Font", FORCE_STRING) +AccessorFunc(Panel, "m_Editable", "Editable", FORCE_BOOL) +AccessorFunc(Panel, "m_Placeholder", "Placeholder", FORCE_STRING) +AccessorFunc(Panel, "m_MaxChars", "MaxChars", FORCE_NUMBER) +AccessorFunc(Panel, "m_Numeric", "Numeric", FORCE_BOOL) +AccessorFunc(Panel, "m_NoBar", "NoBar", FORCE_BOOL) +AccessorFunc(Panel, "m_BarColor", "BarColor") +AccessorFunc(Panel, "m_Background", "Background") +AccessorFunc(Panel, "m_Radius", "Radius") +AccessorFunc(Panel, "m_NoEnter", "NoEnter") + +Panel:SetRadius(3) + +function Panel:Init() + self:ScaleInit() + + self:SetupTransition("TextEntryReady", 0.9, function() + return self:IsEditing() or self:GetBarColor() ~= nil + end) + + self:SetUpdateOnType(true) + self:SetCursor("beam") + self:SetFont(TEXT_ENTRY_FONT) + self:SetPlaceholder("Placeholder text") + + self:SetSize(200, 22) + + self.allowed_numeric_characters = "1234567890.-" + + self.history = {} + self.history_pos = 1 + self.can_use_history = true + + self:OnScaleChange() +end + +function Panel:SetCaretPos(pos) + DTextEntry.SetCaretPos(self, math.Clamp(pos, 0, utf8.len(self:GetText()))) +end + +function Panel:SetValue(value) + self:SetText(value) + self:OnValueChange(value) +end + +function Panel:AllowInput(ch) + if self:CheckNumeric(ch) then return true end + + if sui.wspace_chs[ch] or sui.cntrl_chs[ch] then + return true + end + + local max_chars = self:GetMaxChars() + if max_chars and #self:GetText() >= max_chars then + surface.PlaySound("resource/warning.wav") + return true + end +end + +function Panel:AddValue(v, i, j) + local original_text = self:GetText() + + local start + if i then + start = original_text:sub(1, i) + else + start = utf8.sub(original_text, 1, self:GetCaretPos()) + end + + local text = start .. v + local caret_pos = utf8.len(text) + + local _end + if j then + _end = original_text:sub(j) + else + _end = utf8.sub(original_text, utf8.len(start) + 1) + end + text = text .. _end + + local max_chars = self:GetMaxChars() + if max_chars then + text = text:sub(1, max_chars) + end + + self:SetValue(text) + self:SetCaretPos(caret_pos) +end + +function Panel:OnKeyCodeTyped(code) + if self.no_down then + self.no_down = nil + return + end + + if code == KEY_UP or code == KEY_DOWN then + if not self:UpdateFromHistory(code) then + return true + end + + local lines, caret_line = self:GetNumLines() + + if lines == 1 then + return true + end + + -- + -- this fixes a weird issue + -- make the text entry has at least 2 lines, go up then go down, you won't be able to go up again + -- + if code == KEY_DOWN and lines == caret_line + 1 then + self.no_down = true + gui.InternalKeyCodeTyped(KEY_DOWN) + end + end + + self:OnKeyCode(code) + + if code == KEY_ENTER then + if IsValid(self.Menu) then + self.Menu:Remove() + end + + if not self:GetNoEnter() then + self:FocusNext() + self:OnEnter() + end + end +end + +function Panel:DisallowFloats(disallow) + if not isbool(disallow) then + disallow = true + end + + if disallow then + self.allowed_numeric_characters = self.allowed_numeric_characters:gsub("%.", "", 1) + elseif not self.allowed_numeric_characters:find(".", 1, true) then + self.allowed_numeric_characters = self.allowed_numeric_characters .. "." + end +end + +function Panel:DisallowNegative(disallow) + if not isbool(disallow) then + disallow = true + end + + if disallow then + self.allowed_numeric_characters = self.allowed_numeric_characters:gsub("%-", "", 1) + elseif not self.allowed_numeric_characters:find("-", 1, true) then + self.allowed_numeric_characters = self.allowed_numeric_characters .. "-" + end +end + +function Panel:CheckNumeric(value) + if not self:GetNumeric() then return false end + + if not self.allowed_numeric_characters:find(value, 1, true) then + return true + end + + local new_value = "" + local current_value = tostring(self:GetText()) + + local caret_pos = self:GetCaretPos() + for i = 0, #current_value do + new_value = new_value .. current_value:sub(i, i) + if i == caret_pos then + new_value = new_value .. value + end + end + + if #current_value ~= 0 and not tonumber(new_value) then + return true + end + + return false +end + +function Panel:AddHistory(txt) + if not txt or txt == "" then return end + local history = self.history + if history[#history] ~= txt then + table.insert(history, txt) + end +end + +function Panel:UpdateFromHistory(code) + if not self.can_use_history then return end + + local lines, caret_line = self:GetNumLines() + + if code == KEY_UP then + if caret_line > 1 then return true end -- enable the caret to move up and down + + if self.history_pos <= 1 then return end + + self.history_pos = self.history_pos - 1 + elseif code == KEY_DOWN then + if caret_line ~= lines then return true end + + if self.history_pos >= #self.history then + self:SetValue("") + self:SetCaretPos(0) + self.history_pos = #self.history + 1 + return + end + + self.history_pos = self.history_pos + 1 + end + + local text = self.history[self.history_pos] + if not text then return end + + self:SetValue(text) + self:SetCaretPos(utf8.len(text)) +end + +function Panel:OnTextChanged() + self.history_pos = #self.history + 1 + + local text = self:GetText() + + self.can_use_history = text == "" and true or false + + if self:GetUpdateOnType() then + self:UpdateConvarValue() + self:OnValueChange(text) + end + + self:OnChange() +end + +function Panel:OnScaleChange() + self:InvalidateLayout() + self:InvalidateLayout(true) +end + +function Panel:Paint(w, h) + self:RoundedBox("Background", self:GetRadius(), 0, 0, w, h, GetColor("text_entry_bg") or self:GetBackground()) + + local text_entry_3 = GetColor("text_entry_3") + + if self:GetText() == "" then + local old_text = self:GetText() + self:SetText(self:GetPlaceholder()) + self:DrawTextEntryText(GetColor("text_entry_2"), text_entry_3, text_entry_3) + self:SetText(old_text) + else + self:DrawTextEntryText(GetColor("text_entry"), text_entry_3, text_entry_3) + end + + if not self:GetNoBar() then + local bar_color = self:GetBarColor() + + self:RoundedBox("Bar1", 0, 0, h - 1, w, 1, GetColor("text_entry_bar_color")) + + local bar = math.Round(w * self.TextEntryReady) + if bar > 0 then + self:RoundedBox("Bar2", 0, (w / 2) - (bar / 2), h - 1, bar, 1, bar_color or text_entry_3) + end + end +end + +-- https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/vgui2/vgui_controls/TextEntry.cpp#L969 +function Panel:GetNumLines(wide) + local num_lines = 1 + + wide = wide or self:GetWide() - 2 + + local vbar = self:GetChildren()[1] + if vbar then + wide = wide - vbar:GetWide() + end + + local char_width + local x = 3 + + local word_start_index = 1 + local word_start_len + local word_length = 0 + local has_word = false + local just_started_new_line = true + local word_started_on_new_line = true + + local start_char = 1 + + surface.SetFont(self:GetFont()) + + local i = start_char + local text, n = utf8.force(self:GetText()) + local caret_line = 0 + local caret_pos = self:GetCaretPos() + local caret_i = 1 + while i <= n do + local ch_len = utf8.char_bytes(text:byte(i)) + local ch = text:sub(i, i + ch_len - 1) + + if ch ~= " " then + if not has_word then + word_start_index = i + word_start_len = ch_len + has_word = true + word_started_on_new_line = just_started_new_line + word_length = 0 + end + else + has_word = false + end + + char_width = surface.GetTextSize(ch) + just_started_new_line = false + + if (x + char_width) >= wide then + x = 3 + + just_started_new_line = true + has_word = false + + if word_started_on_new_line then + num_lines = num_lines + 1 + else + num_lines = num_lines + 1 + i = (word_start_index + word_start_len) - ch_len + end + + word_length = 0 + end + + x = x + char_width + word_length = word_length + char_width + + if caret_i == caret_pos then + caret_line = num_lines + end + + i = i + ch_len + caret_i = caret_i + 1 + end + + return num_lines, caret_line +end + +function Panel:SetCheck(func, col) + function self:OnValueChange(text) + if func(text) == false then + self.valid = false + self:SetBarColor(GetColor("close_hover")) + self:SetNoEnter(true) + else + self.valid = true + self:SetBarColor(col) + self:SetNoEnter(false) + end + end + self:SetValue(self:GetText()) +end + +sui.register("TextEntry", Panel, "DTextEntry") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_threegrid.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_threegrid.lua new file mode 100644 index 0000000..168e252 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_threegrid.lua @@ -0,0 +1,89 @@ +local math = math +local table = table +local ipairs = ipairs + +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local Panel = {} + +AccessorFunc(Panel, "horizontalMargin", "HorizontalMargin", FORCE_NUMBER) +AccessorFunc(Panel, "verticalMargin", "VerticalMargin", FORCE_NUMBER) +AccessorFunc(Panel, "columns", "Columns", FORCE_NUMBER) +AccessorFunc(Panel, "Wide2", "Wide2", FORCE_NUMBER) + +function Panel:Init() + self:SetHorizontalMargin(0) + self:SetVerticalMargin(0) + self.Rows = {} + self.Cells = {} +end + +function Panel:AddCell(pnl) + local cols = self:GetColumns() + local idx = math.floor(#self.Cells / cols) + 1 + + local rows = self.Rows[idx] + if not rows then + rows = self:CreateRow() + self.Rows[idx] = rows + end + + local margin = self:GetHorizontalMargin() + + local dockl, dockt, _, dockb = pnl:GetDockMargin() + pnl:SetParent(rows) + pnl:Dock(LEFT) + pnl:DockMargin(dockl, dockt, #rows.Items + 1 < cols and self:GetHorizontalMargin() or 0, dockb) + pnl:SetWide(((self:GetWide2() or self:GetWide()) - margin * (cols - 1)) / cols) + + table.insert(rows.Items, pnl) + table.insert(self.Cells, pnl) + + self:CalculateRowHeight(rows) +end + +function Panel:CreateRow() + local row = self:Add("Panel") + row:Dock(TOP) + row:DockMargin(0, 0, 0, self:GetVerticalMargin()) + row.Items = {} + + return row +end + +function Panel:CalculateRowHeight(row) + local height = 0 + + for k, v in ipairs(row.Items) do + local _, t, _, b = v:GetDockMargin() + height = math.max(height, v:GetTall() + t + b) + end + + row:SetTall(height) +end + +function Panel:Skip() + local cell = vgui.Create("Panel") + self:AddCell(cell) +end + +function Panel:CalculateRowHeights() + for _, row in ipairs(self.Rows) do + self:CalculateRowHeight(row) + end +end + +function Panel:Clear() + for _, row in ipairs(self.Rows) do + for _, cell in ipairs(row.Items) do + cell:Remove() + end + + row:Remove() + end + + self.Cells, self.Rows = {}, {} +end + +Panel.OnRemove = Panel.Clear +sui.register("ThreeGrid", Panel, NAME .. ".ScrollPanel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_toggle_button.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_toggle_button.lua new file mode 100644 index 0000000..082fd62 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_toggle_button.lua @@ -0,0 +1,38 @@ +local Lerp = Lerp +local FrameTime = FrameTime + +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name +local TDLib = sui.TDLib + +local Panel = {} + +sui.scaling_functions(Panel) + +function Panel:Init() + self:ScaleInit() + + local rounded_box = {} + local switch_circle = {} + function self:Paint(w, h) + local is_checked = self:GetChecked() + + local _h = SUI.Scale(14) + TDLib.RoundedBox(rounded_box, _h, 0, h / 2 - _h / 2, w, _h, is_checked and SUI.GetColor("toggle_button_active") or SUI.GetColor("toggle_button")) + + local size = h - 2 + do + local pos = is_checked and (w - (size / 2)) or (h / 2 - 1) + if self.pos then + self.pos = Lerp(FrameTime() * 12, self.pos, pos) + else + self.pos = pos + end + end + + TDLib.DrawCircle(switch_circle, self.pos, h / 2, size / 2, is_checked and SUI.GetColor("toggle_button_switch_active") or SUI.GetColor("toggle_button_switch")) + end + + self:SetSize(34, 20) +end + +sui.register("ToggleButton", Panel, "DCheckBox") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_zbutton.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_zbutton.lua new file mode 100644 index 0000000..8946be9 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_zbutton.lua @@ -0,0 +1,137 @@ +local draw = draw +local render = render + +local TDLib = sui.TDLib +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local lerp_color = sui.lerp_color +local contrast_color = sui.contrast_color + +local BUTTON_FONT = SUI.CreateFont("Button", "Roboto Medium", 16) + +local color_white = color_white +local color_transparent = Color(0, 0, 0, 0) + +local PANEL = {} + +AccessorFunc(PANEL, "m_Background", "Background") +AccessorFunc(PANEL, "m_bContained", "Contained", FORCE_BOOL) + +sui.TDLib.Install(PANEL) +sui.scaling_functions(PANEL) + +PANEL:ClearPaint() +PANEL:SetContained(true) + +local Panel = FindMetaTable("Panel") +local SetMouseInputEnabled = Panel.SetMouseInputEnabled +local IsMouseInputEnabled = Panel.IsMouseInputEnabled +local SetCursor = Panel.SetCursor +local SetContentAlignment = Panel.SetContentAlignment +function PANEL:Init() + self:ScaleInit() + + self.vertices, self.vertices_2 = {}, {} + + SetMouseInputEnabled(self, true) + SetCursor(self, "hand") + SetContentAlignment(self, 5) + + self:SetSize(90, 30) + self:SetFont(BUTTON_FONT) + + self:CircleClick(nil, 7) + + self.OldPaint, self.Paint = self.Paint, self.Paint2 + + self.cur_col = Color(0, 0, 0, 0) +end + +function PANEL:SetEnabled(b) + SetMouseInputEnabled(self, b) +end + +function PANEL:IsEnabled() + return IsMouseInputEnabled(self) +end + +function PANEL:ContainedPaint(w, h) + local enabled = self:IsEnabled() + local col + if enabled then + col = self:GetBackground() or SUI.GetColor("button") + self:SetTextColor(SUI.GetColor("button_text")) + else + col = SUI.GetColor("button_disabled") + self:SetTextColor(SUI.GetColor("button_disabled_text")) + end + self:RoundedBox("Background", 4, 0, 0, w, h, col) + + if not enabled then return end + + self.circle_click_color = SUI.GetColor("button_click") + + if self.Hovered or self.Selected then + self:RoundedBox("Hover", 4, 0, 0, w, h, SUI.GetColor("button_hover")) + end +end + +function PANEL:SetColors(hover_color, text_color) + self.hover = hover_color + self.text_color = text_color +end + +function PANEL:Paint2(w, h) + if self:GetContained() then + self:ContainedPaint(w, h) + self:OldPaint(w, h) + return + end + + render.ClearStencil() + render.SetStencilEnable(true) + + render.SetStencilWriteMask(1) + render.SetStencilTestMask(1) + + render.SetStencilFailOperation(STENCILOPERATION_REPLACE) + render.SetStencilPassOperation(STENCILOPERATION_ZERO) + render.SetStencilZFailOperation(STENCILOPERATION_ZERO) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NEVER) + render.SetStencilReferenceValue(1) + + TDLib.RoundedBox(self.vertices, 4, 0, 0, w, h, color_white) + + render.SetStencilFailOperation(STENCILOPERATION_ZERO) + render.SetStencilPassOperation(STENCILOPERATION_REPLACE) + render.SetStencilZFailOperation(STENCILOPERATION_ZERO) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + render.SetStencilReferenceValue(1) + + local cur_col = self.cur_col + if self.Selected then + lerp_color(cur_col, SUI.GetColor("button2_selected")) + elseif self.Hovered then + lerp_color(cur_col, self.hover or SUI.GetColor("button2_hover")) + else + lerp_color(cur_col, color_transparent) + end + + TDLib.RoundedBox(self.vertices_2, 4, 0, 0, w, h, cur_col) + + if self.text_color then + self.circle_click_color = self.text_color + self:SetTextColor(self.text_color) + else + local col = contrast_color(cur_col) + self.circle_click_color = col + self:SetTextColor(col) + end + + self:OldPaint(w, h) + + render.SetStencilEnable(false) + render.ClearStencil() +end + +sui.register("Button", PANEL, NAME .. ".Label") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_zcollapse_category.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_zcollapse_category.lua new file mode 100644 index 0000000..bf64f3e --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_zcollapse_category.lua @@ -0,0 +1,244 @@ +local sui = sui + +local draw_material = sui.draw_material + +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local GetColor = SUI.GetColor + +local RoundedBox = sui.TDLib.LibClasses.RoundedBox +local TextColor = sui.TDLib.LibClasses.TextColor + +local TABS_FONT = SUI.CreateFont("CategoryListTabs", "Roboto Bold", 13) +local ITEMS_FONT = SUI.CreateFont("CategoryListItems", "Roboto Medium", 14) + +local Panel = {} + +local item_OnRemove = function(s) + local parent = s.parent + + local items = parent.items + for k, v in ipairs(items) do + if v == s then + table.remove(items, k) + break + end + end + + if #items == 0 then + local category = s.category + category:Remove() + parent.categories[category.name] = nil + end +end + +local item_DoClick = function(s) + local parent = s.parent + parent:select_item(s) +end + +function Panel:Init() + local categories = {} + local items = {} + + self.categories = categories + self.items = items + + self:SetVBarPadding(1) + + local get_category = function(name) + local category = categories[name] + if category then return category end + + local expanded = false + + category = self:Add("Panel") + category:Dock(TOP) + category:DockMargin(0, 0, 0, 3) + category.name = name + + local header = category:Add("DButton") + header:Dock(TOP) + header:DockMargin(0, 0, 0, 3) + header:SetFont(TABS_FONT) + header:SetContentAlignment(4) + header:SetTextInset(6, 0) + header:SetText(name) + header:SizeToContentsY(SUI.Scale(14)) + + local cur_col + local cur_col_text = Color(GetColor("collapse_category_header_text"):Unpack()) + function header:Paint(w, h) + if expanded then + cur_col = GetColor("collapse_category_header_active") + cur_col_text = GetColor("collapse_category_header_text_active") + elseif self.Hovered then + cur_col = GetColor("collapse_category_header_hover") + cur_col_text = GetColor("collapse_category_header_text_hover") + else + cur_col = GetColor("collapse_category_header") + cur_col_text = GetColor("collapse_category_header_text") + end + + RoundedBox(self, "Background", 3, 0, 0, w, h, cur_col) + TextColor(self, cur_col_text) + end + + local image = header:Add(NAME .. ".Image") + image:Dock(FILL) + image:SetImage("https://raw.githubusercontent.com/Srlion/Addons-Data/main/icons/sui/arrow.png") + + function image:Draw(w, h) + local size = SUI.ScaleEven(10) + draw_material(nil, w - (size / 2) - 6, h / 2, size, cur_col_text, expanded and 180) + end + + local current_h + function category.RefreshHeight() + local h + if expanded then + local _ + _, h = category:ChildrenSize() + if self.searching and h == header:GetTall() then + h = 0 + end + else + h = header:GetTall() + end + + if current_h == h then return end + + if h > 0 then + category:SetVisible(true) + end + + current_h = h + + category:Stop() + category:SizeTo(-1, h, 0.2, 0, -1, function() + if h == 0 then + category:SetVisible(false) + end + end) + end + + function category.SetExpanded(_, set_expanded) + if expanded == set_expanded then return end + + if sam.isbool(set_expanded) then + expanded = set_expanded + else + expanded = not expanded + end + + category.RefreshHeight() + + if expanded then + self:OnCategoryExpanded(category) + end + + self:InvalidateLayout(true) + end + header.DoClick = category.SetExpanded + + category:SetTall(header:GetTall()) + categories[name] = category + + return category + end + + function self:add_item(name, category_name) + local category = get_category(category_name) + + local item = category:Add("DButton") + item:Dock(TOP) + item:DockMargin(0, 0, 0, 3) + item:SetFont(ITEMS_FONT) + item:SetText(name) + item:SizeToContentsY(SUI.Scale(3 * 2)) + item.name = name + item.parent = self + item.category = category + + local cur_col + local cur_col_text = Color(GetColor("collapse_category_item_text"):Unpack()) + function item:Paint(w, h) + if self.selected then + cur_col = GetColor("collapse_category_item_active") + cur_col_text = GetColor("collapse_category_item_text_active") + elseif self.Hovered then + cur_col = GetColor("collapse_category_item_hover") + cur_col_text = GetColor("collapse_category_item_text_hover") + else + cur_col = GetColor("collapse_category_item") + cur_col_text = GetColor("collapse_category_item_text") + end + + RoundedBox(self, "Background", 4, 0, 0, w, h, cur_col) + TextColor(self, cur_col_text) + end + + item.DoClick = item_DoClick + item.OnRemove = item_OnRemove + + table.insert(items, item) + + return item + end +end + +function Panel:OnCategoryExpanded(category) +end + +function Panel:select_item(item) + if self.selected_item ~= item then + if IsValid(self.selected_item) then + self.selected_item.selected = false + end + item.selected = true + self.selected_item = item + self:item_selected(item) + end +end + +function Panel:item_selected() +end + +function Panel:Search(text, names) + local items = self.items + self.searching = true + for i = 1, #items do + local item = items[i] + local category = item.category + category:SetExpanded(true) + + if not names then + if item.name:find(text, nil, true) then + item:SetVisible(true) + else + item:SetVisible(false) + end + else + local found = false + for _, name in ipairs(item.names) do + if name:find(text, nil, true) then + found = true + item:SetVisible(true) + end + end + if not found then + item:SetVisible(false) + end + end + + if text == "" then + category:SetExpanded(false) + end + + category:RefreshHeight() + category:InvalidateLayout(true) + end + self.searching = false +end + +sui.register("CollapseCategory", Panel, NAME .. ".ScrollPanel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_zcolumn_sheet.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_zcolumn_sheet.lua new file mode 100644 index 0000000..1f7f65d --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_zcolumn_sheet.lua @@ -0,0 +1,132 @@ +local IsValid = IsValid + +local TDLib_Classes = sui.TDLib.LibClasses +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local GetColor = SUI.GetColor + +local Panel = {} + +function Panel:Init() + self.tabs = {} + + local tab_scroller = self:Add(NAME .. ".ScrollPanel") + tab_scroller:Dock(LEFT) + + function tab_scroller:Paint(w, h) + self:RoundedBox("Background", 1, 0, 0, w, h, GetColor("column_sheet_bar")) + end + + self.tabs_wide = 48 + self.tab_scroller = tab_scroller + + self:ScaleChanged() + SUI.OnScaleChanged(self, self.ScaleChanged) +end + +function Panel:ScaleChanged() + local tabs_wide = SUI.Scale(self.tabs_wide) + self.tab_scroller:SetWide(tabs_wide) + + self:InvalidateLayout(true) + + local tabs = self.tabs + for i = 1, #self.tabs do + tabs[i].img:SetMinus(SUI.Scale(20)) + end +end + +function Panel:Paint(w, h) + self:RoundedBox("Background", 1, 0, 0, w, h, GetColor("column_sheet")) +end + +local tab_DoClick = function(s) + s.parent:SetActiveTab(s) +end + +local tab_Paint = function(s, w, h) + local cur_col + if s.active then + cur_col = GetColor("column_sheet_tab_active") + elseif s.Hovered then + cur_col = GetColor("column_sheet_tab_hover") + else + cur_col = GetColor("column_sheet_tab") + end + + s:RoundedBox("Backgrounds", 0, 0, 0, w, h, cur_col) +end + +local tab_OnRemove = function(s) + table.RemoveByValue(s.parent.tabs, s) +end + +function Panel:AddSheet(mat, load_func) + local tab = self.tab_scroller:Add(NAME .. ".Button") + tab:Dock(TOP) + tab:SetText("") + tab:SetTall(self.tabs_wide) + + tab.On = TDLib_Classes.On + + tab.DoClick = tab_DoClick + tab.Paint = tab_Paint + tab:On("OnRemove", tab_OnRemove) + + tab.parent = self + tab.load_func = load_func + + local img = tab:Add(NAME .. ".Image") + img:Dock(FILL) + img:SetImage(mat) + img:SetMinus(SUI.Scale(20)) + + tab.img = img + + self.tab_scroller:AddItem(tab) + + if not self:GetActiveTab() then + self:SetActiveTab(tab) + end + + table.insert(self.tabs, tab) + + return tab +end + +function Panel:GetActiveTab() + return self.active_tab +end + +function Panel:SetActiveTab(new_tab) + if new_tab == self.active_tab then return end + + if not IsValid(new_tab.panel) then + local panel = new_tab.load_func(self) + panel:SetParent(self) + panel:SetVisible(false) + panel:SetAlpha(0) + + panel.tab = new_tab + new_tab.panel = panel + end + + local old_active_tab = self.active_tab + local delay = 0 + if old_active_tab and IsValid(old_active_tab.panel) then + old_active_tab.active = false + delay = 0.2 + old_active_tab.panel:AlphaTo(0, delay, 0, function(_, p) + if p:IsValid() then + p:SetVisible(false) + end + end) + end + + new_tab.active = true + new_tab.panel:SetVisible(true) + new_tab.panel:AlphaTo(255, 0.2, delay) + self.active_tab = new_tab +end + +sui.register("ColumnSheet", Panel, "EditablePanel") diff --git a/garrysmod/addons/sui/lua/sui/vgui/sui_zmenu.lua b/garrysmod/addons/sui/lua/sui/vgui/sui_zmenu.lua new file mode 100644 index 0000000..7e285a1 --- /dev/null +++ b/garrysmod/addons/sui/lua/sui/vgui/sui_zmenu.lua @@ -0,0 +1,136 @@ +local BSHADOWS = sui.BSHADOWS +local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name + +local GetColor = SUI.GetColor + +local RoundedBox = sui.TDLib.LibClasses.RoundedBox +local TextColor = sui.TDLib.LibClasses.TextColor + +local OPTION_FONT = SUI.CreateFont("MenuOption", "Roboto Medium", 15, 500) + +local PANEL = {} + +AccessorFunc(PANEL, "m_bIsMenuComponent", "IsMenu") +AccessorFunc(PANEL, "m_bDeleteSelf", "DeleteSelf") +AccessorFunc(PANEL, "m_iMinimumWidth", "MinimumWidth") +AccessorFunc(PANEL, "m_SetInternal", "Internal") + +PANEL:SetIsMenu(true) +PANEL:SetDeleteSelf(true) + +local pad = 4 +local max_height = 300 + +local PerformLayout = function(s) + local w, h = s:ChildrenSize() + if h > SUI.Scale(max_height) then + h = SUI.Scale(max_height) + end + s:SetSize(math.max(s:GetMinimumWidth(), w), h) +end + +function PANEL:Init() + self:GetCanvas():DockPadding(0, pad, 0, pad) + self:SetMinimumWidth(SUI.Scale(100)) + self:SetKeyboardInputEnabled(false) + self:SetTall(pad * 2) + self:SetAlpha(0) + self.tall = pad * 2 + RegisterDermaMenuForClose(self) + self:On("PerformLayoutInternal", PerformLayout) +end + +function PANEL:Paint(w, h) + local x, y = self:LocalToScreen() + + BSHADOWS.BeginShadow() + self:RoundedBox("Background", pad, x, y, w, h, GetColor("menu")) + BSHADOWS.EndShadow(1, 3, 3) + + self:MoveToFront() +end + +function PANEL:Open(x, y) + self:SizeToChildren(true, false) + + local w, h = self:GetSize() + if h > SUI.Scale(max_height) then + h = SUI.Scale(max_height) + end + + local internal = self:GetInternal() + internal:On("OnRemove", function() + self:Remove() + end) + if not x then + x, y = internal:LocalToScreen(0, 0) + y = y + (internal:GetTall() + 2) + end + + if y + h > ScrH() then + y = y - h + end + + if x + w > ScrW() then + x = x - w + end + + if y < 1 then + y = 1 + end + + if x < 1 then + x = 1 + end + + self:SetPos(x, y) + self:MakePopup() + self:AlphaTo(255, 0.23) + self:SetDrawOnTop(true) + self:MoveToFront() +end + +local option_OnMouseReleased = function(s, mousecode) + if s.Depressed and mousecode == MOUSE_LEFT then + CloseDermaMenus() + end + DButton.OnMouseReleased(s, mousecode) +end + +function PANEL:AddOption(str, callback) + local option = self:Add("DButton") + option:Dock(TOP) + option:SetFont(OPTION_FONT) + option:SetText(str) + option:SizeToContentsX(20) + option:SizeToContentsY(10) + option:InvalidateLayout(true) + option.OnMouseReleased = option_OnMouseReleased + + function option:Paint(w, h) + RoundedBox(self, "Background", 0, 0, 0, w, h, self.Hovered and GetColor("menu_option_hover") or GetColor("menu_option")) + TextColor(self, self.Hovered and GetColor("menu_option_hover_text") or GetColor("menu_option_text")) + end + + option.DoClick = callback + + self.tall = self.tall + option:GetTall() + self:SetTall(self.tall) + + return option +end + +function PANEL:AddSpacer() + local spacer = self:Add("Panel") + spacer:Dock(TOP) + spacer:DockMargin(0, 1, 0, 1) + spacer:SetTall(2) + + function spacer:Paint(w, h) + RoundedBox(self, "Background", 0, 0, 0, w, h, GetColor("menu_spacer")) + end + + spacer:InvalidateLayout(true) +end + +sui.register("Menu", PANEL, NAME .. ".ScrollPanel") diff --git a/garrysmod/addons/support_equipment/lua/autorun/client/v92_medikit_backpack.lua b/garrysmod/addons/support_equipment/lua/autorun/client/v92_medikit_backpack.lua new file mode 100644 index 0000000..944e5c0 --- /dev/null +++ b/garrysmod/addons/support_equipment/lua/autorun/client/v92_medikit_backpack.lua @@ -0,0 +1,120 @@ +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +local mdl = "models/v92/bf2/weapons/handheld/medikit_w.mdl" +local bags = {} + +-- Настраиваемые конвары для отладки +CreateClientConVar("v92_med_back_right", "3", true, false) +CreateClientConVar("v92_med_back_forward", "3", true, false) +CreateClientConVar("v92_med_back_up", "3", true, false) + +CreateClientConVar("v92_med_rot_right", "7", true, false) +CreateClientConVar("v92_med_rot_up", "0", true, false) +CreateClientConVar("v92_med_rot_forward", "90", true, false) + +CreateClientConVar("v92_med_bone", "ValveBiped.Bip01_Spine2", true, false) + +local function GetOffsets() + local cRight = GetConVar("v92_med_back_right") + local cForward = GetConVar("v92_med_back_forward") + local cUp = GetConVar("v92_med_back_up") + + local right = cRight and cRight:GetFloat() or 0 + local forward = cForward and cForward:GetFloat() or 0 + local up = cUp and cUp:GetFloat() or 0 + + return right, forward, up +end + +local function GetRotations() + local cR = GetConVar("v92_med_rot_right") + local cU = GetConVar("v92_med_rot_up") + local cF = GetConVar("v92_med_rot_forward") + + local r = cR and cR:GetFloat() or 0 + local u = cU and cU:GetFloat() or 0 + local f = cF and cF:GetFloat() or 0 + + return r, u, f +end + +local medikitRenderData = {} + +hook.Add("PostPlayerDraw", "V92_Medikit_Draw", function(ply) + if not IsValid(ply) or not ply:Alive() then return end + + -- Не рисуем на себе в первом лице + if ply == LocalPlayer() and not ply:ShouldDrawLocalPlayer() then + return + end + + local boneName = GetConVar("v92_med_bone"):GetString() or "ValveBiped.Bip01_Spine2" + + -- Проверяем наличие аптечки + local hasBag = false + for _, ent in ipairs(ents.FindByClass("v92_bf2_medikit_ent")) do + if ent:GetNWBool("V92_Medikit_IsAttached", false) + and ent:GetNWEntity("V92_Medikit_AttachedPlayer") == ply then + hasBag = true + break + end + end + + if not hasBag then + if IsValid(bags[ply]) then bags[ply]:Remove() end + bags[ply] = nil + return + end + + if not IsValid(bags[ply]) then + bags[ply] = ClientsideModel(mdl, RENDERGROUP_OPAQUE) + bags[ply]:SetNoDraw(true) + end + + local bag = bags[ply] + + ply:SetupBones() + + local bone = ply:LookupBone(boneName) + if not bone then return end + + local m = ply:GetBoneMatrix(bone) + if not m then return end + + local pos = m:GetTranslation() + local ang = m:GetAngles() + + -- Смещения + local rightOff, forwardOff, upOff = GetOffsets() + local rotRight, rotUp, rotForward = GetRotations() + + ang:RotateAroundAxis(ang:Right(), rotRight) + ang:RotateAroundAxis(ang:Up(), rotUp) + ang:RotateAroundAxis(ang:Forward(), rotForward) + + pos = pos + ang:Right() * rightOff + ang:Forward() * forwardOff + ang:Up() * upOff + + bag:SetPos(pos) + bag:SetAngles(ang) + bag:DrawModel() +end) + +hook.Add("PlayerDisconnected", "V92_Medikit_Cleanup", function(ply) + if bags[ply] then + bags[ply]:Remove() + bags[ply] = nil + end +end) + +hook.Add("PlayerDeath", "V92_Medikit_CleanupDeath", function(ply) + if bags[ply] then + bags[ply]:Remove() + bags[ply] = nil + end +end) +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/support_equipment/lua/autorun/sh_support_sweps_v92.lua b/garrysmod/addons/support_equipment/lua/autorun/sh_support_sweps_v92.lua new file mode 100644 index 0000000..62de9a2 --- /dev/null +++ b/garrysmod/addons/support_equipment/lua/autorun/sh_support_sweps_v92.lua @@ -0,0 +1,111 @@ +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +AddCSLuaFile() + +concommand.Add("VNT_Support_Torch_Toggle", function(ply,cmd,args) + bTorch = !bTorch + if bTorch then + + GetConVar("VNT_Support_Torch_Vanilla"):SetBool( 0 ) + ply:AllowFlashlight( true ) + print( "flashlight enabled" ) + + else + + GetConVar("VNT_Support_Torch_Vanilla"):SetBool( 1 ) + if SERVER then ply:Flashlight( false ) end + ply:AllowFlashlight( false ) + print( "flashlight disabled" ) + + end end , nil , "(TOGGLE) Toggle the requirement to use the flashlight weapon." , { FCVAR_REPLICATED, FCVAR_ARCHIVE } ) +if !ConVarExists("VNT_Support_Torch_Vanilla") then CreateConVar("VNT_Support_Torch_Vanilla" , 1 , { FCVAR_REPLICATED, FCVAR_ARCHIVE } , "INTERNAL VALUE, DO NOT USE DIRECTLY" , 0 , 1 ) end + +if !ConVarExists("VNT_Support_MineVehicle_PlaceDelay") then CreateConVar("VNT_Support_MineVehicle_PlaceDelay", 5 , { FCVAR_REPLICATED, FCVAR_ARCHIVE } , "(INT) Delay between uses of the Anti-Vehicle Mine" , 1 , 30 ) end +if !ConVarExists("VNT_Support_MineVehicle_Radius") then CreateConVar("VNT_Support_MineVehicle_Radius", 128 , { FCVAR_REPLICATED, FCVAR_ARCHIVE } , "(INT) Radius of the Anti-Vehicle Mine" , 5 , 128 ) end + +if !ConVarExists("VNT_Support_MinePersonnel_PlaceDelay") then CreateConVar("VNT_Support_MinePersonnel_PlaceDelay", 5 , { FCVAR_REPLICATED, FCVAR_ARCHIVE } , "(INT) Delay between uses of the Anti-Personnel Mine" , 1 , 30 ) end +if !ConVarExists("VNT_Support_MinePersonnel_Radius") then CreateConVar("VNT_Support_MinePersonnel_Radius", 128 , { FCVAR_REPLICATED, FCVAR_ARCHIVE } , "(INT) Radius of the Anti-Personnel Mine" , 5 , 128 ) end + +local function vntSupportSWepsOptions( Panel ) + + Panel:ClearControls() + + Panel:AddControl( "Header", { + Text = "Support Weapons" , + Description =[[Добро пожаловать на фт тим! + ]], + } ) + + ----------------------------------------------------- + ----------------------------------------------------- + -- Torch + ----------------------------------------------------- + ----------------------------------------------------- + + Panel:AddControl( "Checkbox", { + ["label"] = "Toggle Vanilla Flashlight", + ["Command"] = "VNT_Support_Torch_Toggle", + ["Type"] = "bool" + } ) + + Panel:AddControl( "Header", { + ["text"] = "M15 Anti-Vehicle Mines" , + ["Description"] =[[M15 Options + These control the M15 Anti-Vehicle mines. + ]], + } ) + + Panel:AddControl( "Slider", { + ["label"] = "Mine Place Delay", + ["Command"] = "VNT_Support_MineVehicle_PlaceDelay", + ["Type"] = "int", + ["min"] = "1", + ["max"] = "30" + } ) + + Panel:AddControl( "Slider", { + ["label"] = "Find Radius", + ["Command"] = "VNT_Support_MineVehicle_Radius", + ["Type"] = "int", + ["min"] = "5", + ["max"] = "128" + } ) + + Panel:AddControl( "Header", { + ["text"] = "M18 Anti-Personnel Mines" , + ["Description"] =[[M18 Options + These control the M18 Anti-Personnel mines. + ]], + } ) + + Panel:AddControl( "Slider", { + ["label"] = "Mine Place Delay", + ["Command"] = "VNT_Support_MinePersonnel_PlaceDelay", + ["Type"] = "int", + ["min"] = "1", + ["max"] = "30" + } ) + + Panel:AddControl( "Slider", { + ["label"] = "Find Radius", + ["Command"] = "VNT_Support_MinePersonnel_Radius", + ["Type"] = "int", + ["min"] = "5", + ["max"] = "128" + } ) + +end + +local function vntSupportSWepsIndex() spawnmenu.AddToolMenuOption( "Options", "V92", "Support Equipment", "Support Equipment", "", "", vntSupportSWepsOptions ) end +hook.Add( "PopulateToolMenu", "vntSupportSWepsIndex", vntSupportSWepsIndex ) + +if SERVER then + resource.AddWorkshop( "635210663" ) -- Support Equipment +end + +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/support_equipment/lua/effects/fx_explosion_movie.lua b/garrysmod/addons/support_equipment/lua/effects/fx_explosion_movie.lua new file mode 100644 index 0000000..79c107f --- /dev/null +++ b/garrysmod/addons/support_equipment/lua/effects/fx_explosion_movie.lua @@ -0,0 +1,949 @@ +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +AddCSLuaFile() + +//Sound,Impact +// 1 2 3 4 5 +//Dirt, Concrete, Metal, Glass, Flesh +// 1 2 3 4 5 6 7 8 9 +//Dust, Dirt, Sand, Metal, Smoke, Wood, Glass, Blood, YellowBlood +local mats={ + [MAT_ALIENFLESH] ={5,9}, + [MAT_ANTLION] ={5,9}, + [MAT_BLOODYFLESH] ={5,8}, + [45] ={5,8}, // Metrocop heads are a source glitch, they have no enumeration + [MAT_CLIP] ={3,5}, + [MAT_COMPUTER] ={4,5}, + [MAT_FLESH] ={5,8}, + [MAT_GRATE] ={3,4}, + [MAT_METAL] ={3,4}, + [MAT_PLASTIC] ={2,5}, + [MAT_SLOSH] ={5,5}, + [MAT_VENT] ={3,4}, + [MAT_FOLIAGE] ={1,5}, + [MAT_TILE] ={2,5}, + [MAT_CONCRETE] ={2,1}, + [MAT_DIRT] ={1,2}, + [MAT_SAND] ={1,3}, + [MAT_WOOD] ={2,6}, + [MAT_GLASS] ={4,7}, +} + + +function EFFECT:Init(data) +self.Entity = data:GetEntity() // Entity determines what is creating the dynamic light // + +self.Pos = data:GetOrigin() // Origin determines the global position of the effect // + +self.Scale = data:GetScale()*0.8 // Scale determines how large the effect is // +self.Radius = data:GetRadius() or 1 // Radius determines what type of effect to create, default is Concrete // + +self.DirVec = data:GetNormal() // Normal determines the direction of impact for the effect // +self.PenVec = data:GetStart() // PenVec determines the direction of the round for penetrations // +self.Particles = data:GetMagnitude() // Particles determines how many puffs to make, primarily for "trails" // +self.Angle = self.DirVec:Angle() // Angle is the angle of impact from Normal // +self.DebrizzlemyNizzle = 10+data:GetScale() // Debrizzle my Nizzle is how many "trails" to make // +self.Size = 5*self.Scale // Size is exclusively for the explosion "trails" size // + +self.Emitter = ParticleEmitter( self.Pos ) // Emitter must be there so you don't get an error // + + + + if self.Scale<2.1 then + sound.Play( "ambient/explosions/explode_" .. math.random(1, 4) .. ".wav", self.Pos, 100, 100 ) + else + sound.Play( "Explosion.Boom", self.Pos) + sound.Play( "ambient/explosions/explode_" .. math.random(1, 4) .. ".wav", self.Pos, 100, 100 ) + end + + + self.Mat=math.ceil(self.Radius) + + + + if mats[self.Mat][2]==1 then self:Dust() + elseif mats[self.Mat][2]==2 then self:Dirt() + elseif mats[self.Mat][2]==3 then self:Sand() + elseif mats[self.Mat][2]==4 then self:Metal() + elseif mats[self.Mat][2]==5 then self:Smoke() + elseif mats[self.Mat][2]==6 then self:Wood() + elseif mats[self.Mat][2]==7 then self:Glass() + elseif mats[self.Mat][2]==8 then self:Blood() + elseif mats[self.Mat][2]==9 then self:YellowBlood() + else self:Smoke() + end + +end + + function EFFECT:Dust() + + for i=1,5 do + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=1, 20*self.Scale do + local Dust = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*300)*self.Scale) ) + Dust:SetDieTime( math.Rand( 2 , 3 ) ) + Dust:SetStartAlpha( 230 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (50*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 150 ) + Dust:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Dust:SetColor( 80,80,80 ) + end + end + + for i=1, 15*self.Scale do + local Dust = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*400)*self.Scale) ) + Dust:SetDieTime( math.Rand( 1 , 5 )*self.Scale ) + Dust:SetStartAlpha( 50 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (80*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 250 ) + Dust:SetGravity( Vector( math.Rand( -200 , 200 ), math.Rand( -200 , 200 ), math.Rand( 10 , 100 ) ) ) + Dust:SetColor( 90,85,75 ) + end + end + + for i=1, 25*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(0,500)*self.Scale + VectorRand():GetNormalized() * math.random(0,400)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,10)*self.Scale) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 60,60,60 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + local Angle = self.DirVec:Angle() + + for i = 1, self.DebrizzlemyNizzle do /// This part makes the trailers /// + Angle:RotateAroundAxis(Angle:Forward(), (360/self.DebrizzlemyNizzle)) + local DustRing = Angle:Up() + local RanVec = self.DirVec*math.Rand(1, 5) + (DustRing*math.Rand(2, 5)) + + for k = 3, self.Particles do + local Rcolor = math.random(-20,20) + + local particle1 = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + particle1:SetVelocity((VectorRand():GetNormalized()*math.Rand(1, 2) * self.Size) + (RanVec*self.Size*k*3.5)) + particle1:SetDieTime( math.Rand( 0.5, 4 )*self.Scale ) + + particle1:SetStartAlpha( math.Rand( 90, 100 ) ) + particle1:SetEndAlpha(0) + particle1:SetGravity((VectorRand():GetNormalized()*math.Rand(5, 10)* self.Size) + Vector(0,0,-50)) + particle1:SetAirResistance( 200+self.Scale*20 ) + particle1:SetStartSize( (5*self.Size)-((k/self.Particles)*self.Size*3) ) + particle1:SetEndSize( (20*self.Size)-((k/self.Particles)*self.Size) ) + particle1:SetRoll( math.random( -500, 500 )/100 ) + + particle1:SetRollDelta( math.random( -0.5, 0.5 ) ) + particle1:SetColor( 90+Rcolor,87+Rcolor,80+Rcolor ) + end + end + end + +function EFFECT:Dirt() + + for i=1,5 do + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=1, 20*self.Scale do + local Dust = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*300)*self.Scale) ) + Dust:SetDieTime( math.Rand( 2 , 3 ) ) + Dust:SetStartAlpha( 230 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (50*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 150 ) + Dust:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Dust:SetColor( 90,83,68 ) + end + end + + for i=1, 15*self.Scale do + local Dust = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*400)*self.Scale) ) + Dust:SetDieTime( math.Rand( 1 , 5 )*self.Scale ) + Dust:SetStartAlpha( 50 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (80*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 250 ) + Dust:SetGravity( Vector( math.Rand( -200 , 200 ), math.Rand( -200 , 200 ), math.Rand( 10 , 100 ) ) ) + Dust:SetColor( 90,83,68 ) + end + end + + for i=1, 25*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(0,500)*self.Scale + VectorRand():GetNormalized() * math.random(0,400)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,10)*self.Scale) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 50,53,45 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + local Angle = self.DirVec:Angle() + + for i = 1, self.DebrizzlemyNizzle do /// This part makes the trailers /// + Angle:RotateAroundAxis(Angle:Forward(), (360/self.DebrizzlemyNizzle)) + local DustRing = Angle:Up() + local RanVec = self.DirVec*math.Rand(2, 6) + (DustRing*math.Rand(1, 4)) + + for k = 3, self.Particles do + local Rcolor = math.random(-20,20) + + local particle1 = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + particle1:SetVelocity((VectorRand():GetNormalized()*math.Rand(1, 2) * self.Size) + (RanVec*self.Size*k*3.5)) + particle1:SetDieTime( math.Rand( 0.5, 4 )*self.Scale ) + + particle1:SetStartAlpha( math.Rand( 90, 100 ) ) + particle1:SetEndAlpha(0) + particle1:SetGravity((VectorRand():GetNormalized()*math.Rand(5, 10)* self.Size) + Vector(0,0,-50)) + particle1:SetAirResistance( 200+self.Scale*20 ) + particle1:SetStartSize( (5*self.Size)-((k/self.Particles)*self.Size*3) ) + particle1:SetEndSize( (20*self.Size)-((k/self.Particles)*self.Size) ) + particle1:SetRoll( math.random( -500, 500 )/100 ) + + particle1:SetRollDelta( math.random( -0.5, 0.5 ) ) + particle1:SetColor( 90+Rcolor,83+Rcolor,68+Rcolor ) + end + end + end + + function EFFECT:Sand() + + for i=0, 45*self.Scale do // This is the main plume + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( self.DirVec * math.random( 50,1000*self.Scale) + VectorRand():GetNormalized()*300*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 5 )*self.Scale ) + Smoke:SetStartAlpha( math.Rand( 100, 120 ) ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 50*self.Scale ) + Smoke:SetEndSize( 120*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-1, 1) ) + Smoke:SetAirResistance( 200 ) + Smoke:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Smoke:SetColor( 90,83,68 ) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=0, 20*self.Scale do // This is the dirt kickup + local Dust = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,700)*self.Scale + VectorRand():GetNormalized()*250*self.Scale ) + Dust:SetDieTime( math.Rand( 0.5 , 1,5 ) ) + Dust:SetStartAlpha( 200 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( 60*self.Scale ) + Dust:SetEndSize( 90*self.Scale ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 200 ) + Dust:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Dust:SetColor( 90,83,68 ) + end + end + + for i=0, 25*self.Scale do // Chunkage + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(50,700)*self.Scale + VectorRand():GetNormalized() * math.random(0,500)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,8)*self.Scale ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 53,50,45 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + for i=0, 25*self.Scale do // Shrapnel + local Shrapnel = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos+self.DirVec ) + if (Shrapnel) then + Shrapnel:SetVelocity ( (self.DirVec*700*self.Scale) + (VectorRand():GetNormalized() * 1000*self.Scale) ) + Shrapnel:SetDieTime( math.random( 0.3, 0.5) * self.Scale ) + Shrapnel:SetStartAlpha( 255 ) + Shrapnel:SetEndAlpha( 0 ) + Shrapnel:SetStartSize( math.random(4,7)*self.Scale ) + Shrapnel:SetRoll( math.Rand(0, 360) ) + Shrapnel:SetRollDelta( math.Rand(-5, 5) ) + Shrapnel:SetAirResistance( 10 ) + Shrapnel:SetColor( 53,50,45 ) + Shrapnel:SetGravity( Vector( 0, 0, -600) ) + Shrapnel:SetCollide( true ) + Shrapnel:SetBounce( 0.8 ) + end + end + + for i=1,5 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.10 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=0, 10*self.Scale do + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( self.DirVec * math.random( 30,120*self.Scale) + VectorRand():GetNormalized() * math.random( 50,100*self.Scale) ) + Smoke:SetDieTime( math.Rand( 0.5 , 1 )*self.Scale ) + Smoke:SetStartAlpha( math.Rand( 80, 100 ) ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 10*self.Scale ) + Smoke:SetEndSize( 30*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 100 ) + Smoke:SetGravity( Vector( math.random(-20,20)*self.Scale, math.random(-20,20)*self.Scale, 250 ) ) + Smoke:SetColor( 90,83,68 ) + end + end + + + for i=0, 5*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 300,600*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 30, 40 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 150,150,150 ) + end + end + + + local Density = 40*self.Scale /// This part is for the dust ring /// + local Angle = self.DirVec:Angle() + for i=0, Density do + Angle:RotateAroundAxis(Angle:Forward(), (360/Density)) + local ShootVector = Angle:Up() + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( ShootVector * math.Rand(50,700*self.Scale) ) + Smoke:SetDieTime( math.Rand( 1 , 4 )*self.Scale ) + Smoke:SetStartAlpha( math.Rand( 90, 120 ) ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 40*self.Scale ) + Smoke:SetEndSize( 70*self.Scale ) + Smoke:SetRoll( math.Rand(0, 360) ) + Smoke:SetRollDelta( math.Rand(-1, 1) ) + Smoke:SetAirResistance( 200 ) + Smoke:SetGravity( Vector( math.Rand( -200 , 200 ), math.Rand( -200 , 200 ), math.Rand( 10 , 100 ) ) ) + Smoke:SetColor( 90,83,68 ) + end + end + end + + function EFFECT:Metal() + + for i=1,3 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=0, 20*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,1000*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 50, 70 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 120,120,120 ) + end + end + + for i=0, 30*self.Scale do + local Sparks = self.Emitter:Add( "effects/spark", self.Pos ) + if (Sparks) then + Sparks:SetVelocity( ((self.DirVec*0.75)+VectorRand()) * math.Rand(200, 600)*self.Scale ) + Sparks:SetDieTime( math.Rand(0.3, 1) ) + Sparks:SetStartAlpha( 255 ) + Sparks:SetStartSize( math.Rand(7, 15)*self.Scale ) + Sparks:SetEndSize( 0 ) + Sparks:SetRoll( math.Rand(0, 360) ) + Sparks:SetRollDelta( math.Rand(-5, 5) ) + Sparks:SetAirResistance( 20 ) + Sparks:SetGravity( Vector( 0, 0, -600 ) ) + end + end + + for i=0, 10*self.Scale do + local Sparks = self.Emitter:Add( "effects/yellowflare", self.Pos ) + if (Sparks) then + Sparks:SetVelocity( VectorRand() * math.Rand(200, 600)*self.Scale ) + Sparks:SetDieTime( math.Rand(1, 1.7) ) + Sparks:SetStartAlpha( 200 ) + Sparks:SetStartSize( math.Rand(10, 13)*self.Scale ) + Sparks:SetEndSize( 0 ) + Sparks:SetRoll( math.Rand(0, 360) ) + Sparks:SetRollDelta( math.Rand(-5, 5) ) + Sparks:SetAirResistance( 100 ) + Sparks:SetGravity( Vector( 0, 0, -60 ) ) + end + end + +end + + + function EFFECT:Smoke() + + for i=1,5 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=0, 20*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,1200*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 35, 50 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 120,120,120 ) + end + end + + + for i=1, 25*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_tile"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(100,400)*self.Scale + VectorRand():GetNormalized() * math.random(100,700)*self.Scale ) + Debris:SetDieTime( math.random( 1, 3) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,10)*self.Scale) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 70,70,70 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + local Angle = self.DirVec:Angle() + + for i = 1, self.DebrizzlemyNizzle do /// This part makes the trailers /// + Angle:RotateAroundAxis(Angle:Forward(), (360/self.DebrizzlemyNizzle)) + local DustRing = Angle:Up() + local RanVec = self.DirVec*math.Rand(1, 4) + (DustRing*math.Rand(3, 4)) + + for k = 3, self.Particles do + local Rcolor = math.random(-20,20) + + local particle1 = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + particle1:SetVelocity((VectorRand():GetNormalized()*math.Rand(1, 2) * self.Size) + (RanVec*self.Size*k*3.5)) + particle1:SetDieTime( math.Rand( 0, 3 )*self.Scale ) + + particle1:SetStartAlpha( math.Rand( 90, 100 ) ) + particle1:SetEndAlpha(0) + particle1:SetGravity((VectorRand():GetNormalized()*math.Rand(5, 10)* self.Size) + Vector(0,0,-50)) + particle1:SetAirResistance( 200+self.Scale*20 ) + particle1:SetStartSize( (5*self.Size)-((k/self.Particles)*self.Size*3) ) + particle1:SetEndSize( (20*self.Size)-((k/self.Particles)*self.Size) ) + particle1:SetRoll( math.random( -500, 500 )/100 ) + + particle1:SetRollDelta( math.random( -0.5, 0.5 ) ) + particle1:SetColor( 90+Rcolor,85+Rcolor,75+Rcolor ) + end + end +end + + function EFFECT:Wood() + + for i=1,5 do + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=0, 30*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,1000)*self.Scale ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 70, 90 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 90,85,75 ) + end + end + + for i=0, 20*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_wood"..math.random(1,2), self.Pos+self.DirVec ) + if (Debris) then + Debris:SetVelocity( self.DirVec * math.random(50,300)*self.Scale + VectorRand():GetNormalized() * math.random(200,600)*self.Scale ) + Debris:SetDieTime( math.random( 0.75, 2) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(10,15)*self.Scale ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 70 ) + Debris:SetColor( 90,85,75 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end +end + + function EFFECT:Glass() + + for i=1,5 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=0, 30*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_glass"..math.random(1,3), self.Pos ) + if (Debris) then + Debris:SetVelocity ( VectorRand():GetNormalized() * math.random(100,600)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2.5) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(3,7)*self.Scale ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-15, 15) ) + Debris:SetAirResistance( 50 ) + Debris:SetColor( 200,200,200 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + Debris:SetCollide( true ) + Debris:SetBounce( 0.5 ) + end + end + + + for i=0, 30*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,800*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 35, 50 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 150,150,150 ) + end + end + +end + + function EFFECT:Blood() + + for i=0, 30*self.Scale do // If you recieve over 50,000 joules of energy, you become red mist. + local Smoke = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(100,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 2 ) ) + Smoke:SetStartAlpha( 80 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(0, -200) ) ) + Smoke:SetColor( 70,35,35 ) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=0, 20*self.Scale do // Add some finer details.... + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(200,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 4 ) ) + Smoke:SetStartAlpha( 120 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(-50, -300) ) ) + Smoke:SetColor( 70,35,35 ) + end + end + + for i=1,5 do // Into the flash! + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=1, 20*self.Scale do // Chunkage NOT contained + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos-(self.DirVec*5) ) + if (Debris) then + Debris:SetVelocity ( VectorRand():GetNormalized() * 400*self.Scale ) + Debris:SetDieTime( math.random( 0.3, 0.6) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( 8 ) + Debris:SetEndSize( 9 ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 30 ) + Debris:SetColor( 70,35,35 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + Debris:SetCollide( true ) + Debris:SetBounce( 0.2 ) + end + end + +end + + function EFFECT:YellowBlood() + for i=0, 30*self.Scale do // If you recieve over 50,000 joules of energy, you become red mist. + local Smoke = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(100,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 2 ) ) + Smoke:SetStartAlpha( 80 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(0, -200) ) ) + Smoke:SetColor( 120,120,0 ) + end + end + + local Distort = self.Emitter:Add( "sprites/heatwave", self.Pos ) + if (Distort) then + Distort:SetVelocity( self.DirVec ) + Distort:SetAirResistance( 200 ) + Distort:SetDieTime( 0.1 ) + Distort:SetStartAlpha( 255 ) + Distort:SetEndAlpha( 0 ) + Distort:SetStartSize( self.Scale*600 ) + Distort:SetEndSize( 0 ) + Distort:SetRoll( math.Rand(180,480) ) + Distort:SetRollDelta( math.Rand(-1,1) ) + Distort:SetColor(255,255,255) + end + + for i=0, 20*self.Scale do // Add some finer details.... + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(200,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 4 ) ) + Smoke:SetStartAlpha( 120 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(-50, -300) ) ) + Smoke:SetColor( 120,120,0 ) + end + end + + for i=1,5 do // Into the flash! + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=1, 20*self.Scale do // Chunkage NOT contained + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos-(self.DirVec*5) ) + if (Debris) then + Debris:SetVelocity ( VectorRand():GetNormalized() * 400*self.Scale ) + Debris:SetDieTime( math.random( 0.3, 0.6) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( 8 ) + Debris:SetEndSize( 9 ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 30 ) + Debris:SetColor( 120,120,0 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + Debris:SetCollide( true ) + Debris:SetBounce( 0.2 ) + end + end +end + +function EFFECT:Think( ) return false end + +function EFFECT:Render() end +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/support_equipment/lua/entities/v92_bf2_ammokit_ent.lua b/garrysmod/addons/support_equipment/lua/entities/v92_bf2_ammokit_ent.lua new file mode 100644 index 0000000..2ed488c --- /dev/null +++ b/garrysmod/addons/support_equipment/lua/entities/v92_bf2_ammokit_ent.lua @@ -0,0 +1,111 @@ +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.Category = "Equipment" +ENT.PrintName = "BF2 Ammokit" +ENT.Author = "V92" +ENT.Spawnable = true +ENT.AdminOnly = true + +local DeployModel = Model("models/v92/bf2/weapons/handheld/ammokit_w.mdl") +local SupplySound = Sound( "BF2.Weapon.Handheld.Ammokit.Pickup" ) +ENT.SupplyAmount = 5 + +------------------------------------------------------------ +-- None of the shit under this concerns the client, +-- so we aren't even going to scope it to save resources. +------------------------------------------------------------ +if CLIENT then + + return + +end + +function ENT:Initialize() + + self:SetModel( DeployModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetCollisionGroup( COLLISION_GROUP_NONE ) + + local phys = self:GetPhysicsObject() + if phys and phys:IsValid() then phys:Wake() end + + util.PrecacheModel( DeployModel ) + util.PrecacheSound( SupplySound ) + + -- This is sloppy - but it works - so kiss my ass. + timer.Simple( 1.5 , function() + + if IsValid( self ) then + + -- Need to delay this or you eat it immediately on spawn, + -- which is an issue if you're trying to throw it to someone. + self:SetTrigger( true ) + + end + + end) + + -- I understand people might want to spawn a bunch of these as set dressing, + -- but honestly it becomes a clusterfuck very quickly. + -- How do I track how many a player has thrown? Max of 2? + timer.Simple( 300 , function() + + if IsValid( self ) then + + SafeRemoveEntity( self ) + + end + + end) + +end + +------------------------------------------------------------ +-- TOUCH! Patrick, don't touch. TOUCH! +------------------------------------------------------------ +function ENT:StartTouch( toucher ) + + -- The toucher isn't a player or NPC and the toucher is at/exceeding full health... + if !IsValid( toucher ) or !toucher:IsPlayer() or toucher:GetActiveWeapon():GetPrimaryAmmoType() == -1 then + + -- DE-NIED! + return false + + end + + local cw = toucher:GetActiveWeapon() + local need = cw:GetMaxClip1() + + if cw:Clip1() > -1 then + + toucher:GiveAmmo( need , cw:GetPrimaryAmmoType() , false ) + toucher:EmitSound( SupplySound ) + + self.SupplyAmount = self.SupplyAmount - 1 + + end + + -- Is the charge empty? + if self.SupplyAmount <= 0 then + + -- Fuck off to dev/null with your ass + self:Remove() + + end + + -- Don't let the function directly run again to prevent cascading use + return + +end +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/support_equipment/lua/entities/v92_bf2_medikit_ent.lua b/garrysmod/addons/support_equipment/lua/entities/v92_bf2_medikit_ent.lua new file mode 100644 index 0000000..d248212 --- /dev/null +++ b/garrysmod/addons/support_equipment/lua/entities/v92_bf2_medikit_ent.lua @@ -0,0 +1,250 @@ +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.Category = "Equipment" +ENT.PrintName = "BF2 Medikit" +ENT.Author = "V92" +ENT.Spawnable = true +ENT.AdminOnly = true + +local DeployModel = Model("models/v92/bf2/weapons/handheld/medikit_w.mdl") +local HealSound = Sound("BF2.Weapon.Handheld.Medikit.Pickup") +local AttachSound = Sound("items/ammo_pickup.wav") +local DetachSound = Sound("items/medshotno1.wav") + +ENT.MaxUses = 5 +ENT.HealPerUse = 25 + +if CLIENT then + function ENT:Draw() + return + end + return +end + +function ENT:Initialize() + self:SetModel(DeployModel) + self:SetNoDraw(false) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetMass(5) + end + + self.AttachedPlayer = nil + self.AttachmentBone = "ValveBiped.Bip01_Spine2" + self.IsAttached = false + self.HasInfiniteCharge = false + self.RemoveTimer = nil + + self.CurrentUses = self.MaxUses + + self:SetNWBool("V92_Medikit_IsAttached", false) + self:SetNWEntity("V92_Medikit_AttachedPlayer", NULL) + + util.PrecacheModel(DeployModel) + util.PrecacheSound(HealSound) + util.PrecacheSound(AttachSound) + util.PrecacheSound(DetachSound) + + self.RemoveTimer = timer.Simple(300, function() + if IsValid(self) and not self.IsAttached then + SafeRemoveEntity(self) + end + end) +end + +function ENT:AttachToPlayer(ply) + if not IsValid(ply) or not ply:IsPlayer() then return false end + + for _, ent in ipairs(ents.FindByClass("v92_bf2_medikit_ent")) do + if ent ~= self and IsValid(ent) and ent.IsAttached + and IsValid(ent.AttachedPlayer) and ent.AttachedPlayer == ply then + ent:DetachFromPlayer() + end + end + + self.AttachedPlayer = ply + self.IsAttached = true + self.HasInfiniteCharge = true + + self:SetNWBool("V92_Medikit_IsAttached", true) + self:SetNWEntity("V92_Medikit_AttachedPlayer", ply) + + if self.RemoveTimer then + timer.Remove(self.RemoveTimer) + self.RemoveTimer = nil + end + self:SetModel("") + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_NONE) + self:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE) + self:SetNoDraw(true) + ply:EmitSound(AttachSound) + self:SetOwner(ply) + + return true +end + +function ENT:DetachFromPlayer() + if not self.IsAttached then return end + + self:SetNWBool("V92_Medikit_IsAttached", false) + self:SetNWEntity("V92_Medikit_AttachedPlayer", NULL) + + if IsValid(self.AttachedPlayer) then + self.AttachedPlayer:EmitSound(DetachSound) + end + + self.HasInfiniteCharge = false + + timer.Simple(0.05, function() + if IsValid(self) then + self:SetModel(DeployModel) + self:SetNoDraw(false) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + timer.Simple(0, function() + if not IsValid(deployEnt) then return end + + local phys = deployEnt:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetMass(5) + phys:SetVelocity(owner:GetAimVector() * (owner:KeyDown(IN_USE) and 50 or 400)) + end + end) + + self.RemoveTimer = timer.Simple(300, function() + if IsValid(self) then + SafeRemoveEntity(self) + end + end) + end + end) + + self.IsAttached = false + self.AttachedPlayer = nil +end + +function ENT:Use(activator, caller, useType, value) + if not IsValid(self) then return end + if not IsValid(activator) or not activator:IsPlayer() then return end + + if self.IsAttached and IsValid(self.AttachedPlayer) then + local need = math.min(activator:GetMaxHealth() - activator:Health(), self.HealPerUse) + if need > 0 then + activator:SetHealth(math.min(activator:GetMaxHealth(), activator:Health() + need)) + activator:EmitSound(HealSound) + end + end +end + +function ENT:StartTouch(toucher) + if not IsValid(self) then return end + if not (toucher:IsPlayer() or toucher:IsNPC()) or toucher:Health() >= toucher:GetMaxHealth() then + return false + end + + if toucher:Health() < toucher:GetMaxHealth() then + local need = math.min(toucher:GetMaxHealth() - toucher:Health(), self.HealPerUse) + + if need > 0 then + if not (self.IsAttached and self.HasInfiniteCharge) then + self.CurrentUses = self.CurrentUses - 1 + end + + toucher:SetHealth(math.min(toucher:GetMaxHealth(), toucher:Health() + need)) + toucher:EmitSound(HealSound) + + if self.CurrentUses <= 0 and not (self.IsAttached and self.HasInfiniteCharge) then + self:Remove() + end + end + end + + return +end + +function ENT:OnRemove() + if self.RemoveTimer then + timer.Remove(self.RemoveTimer) + self.RemoveTimer = nil + end + + if self.IsAttached then + self.IsAttached = false + self.AttachedPlayer = nil + end +end + +function ENT:Think() + if self.IsAttached and (not IsValid(self.AttachedPlayer) or not self.AttachedPlayer:Alive()) then + self:DetachFromPlayer() + end + + self:NextThink(CurTime() + 0.5) + return true +end + +hook.Add("PlayerDeath", "V92_Medikit_PlayerDeath", function(victim) + for _, ent in ipairs(ents.FindByClass("v92_bf2_medikit_ent")) do + if IsValid(ent) and ent.IsAttached and IsValid(ent.AttachedPlayer) + and ent.AttachedPlayer == victim then + ent:DetachFromPlayer() + end + end +end) + +hook.Add("PlayerDisconnected", "V92_Medikit_PlayerDisconnected", function(ply) + for _, ent in ipairs(ents.FindByClass("v92_bf2_medikit_ent")) do + if IsValid(ent) and ent.IsAttached and IsValid(ent.AttachedPlayer) + and ent.AttachedPlayer == ply then + ent:DetachFromPlayer() + end + end +end) + +hook.Add("KeyPress", "V92_Medikit_TreatFromPlayer", function(ply, key) + if key ~= IN_USE then return end + if not IsValid(ply) or not ply:Alive() then return end + + local trace = ply:GetEyeTrace() + if not IsValid(trace.Entity) then return end + + local target = trace.Entity + + if IsValid(target) and target:IsPlayer() and target:Alive() and target ~= ply then + for _, medikit in ipairs(ents.FindByClass("v92_bf2_medikit_ent")) do + if IsValid(medikit) and medikit.IsAttached + and IsValid(medikit.AttachedPlayer) and medikit.AttachedPlayer == target then + + local need = math.min(ply:GetMaxHealth() - ply:Health(), 25) + + if need > 0 then + ply:SetHealth(math.min(ply:GetMaxHealth(), ply:Health() + need)) + ply:EmitSound(HealSound) + end + + break + end + end + end +end) +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/support_equipment/lua/weapons/v92_bf2_ammokit.lua b/garrysmod/addons/support_equipment/lua/weapons/v92_bf2_ammokit.lua new file mode 100644 index 0000000..f28026d --- /dev/null +++ b/garrysmod/addons/support_equipment/lua/weapons/v92_bf2_ammokit.lua @@ -0,0 +1,306 @@ +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +AddCSLuaFile() + +sound.Add({ ["name"] = "BF2.VO.English.Grunt.NoFilter.AmmoHere", + ["channel"] = CHAN_VOICE , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = { + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_ammohere.ogg" , + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_ammohere_alt.ogg" + } +}) + +sound.Add({ ["name"] = "BF2.VO.English.Grunt.NoFilter.OutOfAmmo", + ["channel"] = CHAN_VOICE , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = { + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_outofammo.ogg" , + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_outofammo_alt.ogg" + } +}) + +sound.Add({ ["name"] = "BF2.VO.English.Grunt.NoFilter.RequestAmmo", + ["channel"] = CHAN_VOICE , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = { + "v92/bf2/vo/english/grunt/no_filter/PLAYER_REQUEST_TM_requestammo.ogg" , + "v92/bf2/vo/english/grunt/no_filter/PLAYER_REQUEST_TM_requestammo_alt.ogg" + } +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Ammokit.Pickup", + ["channel"] = CHAN_ITEM , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = "v92/bf2/common/pick_up_ammopack.mp3" +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Ammokit.Deploy" , + ["channel"] = CHAN_BODY , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = { 95 , 105 }, + ["sound"] = "v92/bf2/weapons/handheld/ammokit/medikit_deploy.wav" +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Ammokit.Holster" , + ["channel"] = CHAN_BODY , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = { 95 , 105 } , + ["sound"] = "v92/bf2/weapons/handheld/ammokit/medikit_deploy.wav" +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Ammokit.Throw" , + ["channel"] = CHAN_BODY , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = { 95 , 105 } , + ["sound"] = "v92/bf2/weapons/handheld/ammokit/throw.wav" +}) + +if CLIENT then + + local strEntityName = "v92_bf2_ammokit" + local strInfoName = "Ammo Kit" + + SWEP.Category = "Utilities" + SWEP.PrintName = strInfoName + SWEP.Author = "V92" + SWEP.Contact = "Steam" + SWEP.Purpose = "Resupplying Nearby Units" + SWEP.Instructions = "Primary: Supply Target\nSecondary: Throw Consumable Kit\nUSE+Secondary: Drop Consumable Kit" + SWEP.Slot = 4 + SWEP.SlotPos = 32 + SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/" .. strEntityName ) + SWEP.ViewModelFOV = 67 + SWEP.BounceWeaponIcon = false + + language.Add( strEntityName, strInfoName ) + killicon.Add( strEntityName, "vgui/entities/".. strEntityName , Color( 255, 255, 255 ) ) + +end + +SWEP.Weight = 1 +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.UseHands = true +SWEP.ViewModel = Model("models/v92/bf2/weapons/handheld/ammokit_c.mdl") +SWEP.WorldModel = Model("models/v92/bf2/weapons/handheld/ammokit_w.mdl") + +SWEP.Primary.ClipSize = 100 +SWEP.Primary.DefaultClip = 100 +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.SupplyAmount = 25 +SWEP.MaxAmmo = 100 -- Maximum ammo + +local ThrowSound = Sound( "BF2.Weapon.Handheld.Ammokit.Throw" ) +local SupplySound = Sound( "BF2.Weapon.Handheld.Ammokit.Pickup" ) +local AmmoSoundVO = Sound( "BF2.VO.English.Grunt.NoFilter.AmmoHere" ) +local EmptySoundVO = Sound( "BF2.VO.English.Grunt.NoFilter.OutOfAmmo" ) +local RequestSoundVO = Sound( "BF2.VO.English.Grunt.NoFilter.RequestAmmo" ) +local DeployEntity = "v92_bf2_ammokit_ent" + +function SWEP:Initialize() + + self:SetHoldType( "slam" ) + + self.SupplyAmount = self.SupplyAmount or 25 + + if CLIENT then + + return + + end + + timer.Create( "ammokit_ammo" .. self:EntIndex() , 5 , 0 , function() + if ( self:Clip1() < self.MaxAmmo ) then self:SetClip1( math.min( self:Clip1() + 5 , self.MaxAmmo ) ) end + end ) + +end + +function SWEP:PrimaryAttack() + + if CLIENT then + + return + + end + + if self.Owner:IsPlayer() then + + self.Owner:LagCompensation( true ) + + end + + local tr = util.TraceLine( { + ["start"] = self.Owner:GetShootPos(), + ["endpos"] = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 64, + ["filter"] = self.Owner + } ) + + if self.Owner:IsPlayer() then + + self.Owner:LagCompensation( false ) + + end + + local ent = tr.Entity + + local need = 25 + + if IsValid( ent ) and self:Clip1() >= need then + + self:TakePrimaryAmmo( need ) + + local cw = ent:GetActiveWeapon() + if not IsValid(cw) then + self.Owner:EmitSound( EmptySoundVO ) + self:SetNextSecondaryFire( CurTime() + 3 ) + return + end + local ammoType = cw:GetPrimaryAmmoType() + if ammoType == "rpg_round" or ammoType == "PanzerFaust3 Rocket" then + -- Не давать патроны для ракетниц + self.Owner:EmitSound( EmptySoundVO ) + self:SetNextSecondaryFire( CurTime() + 3 ) + return + end + ent:GiveAmmo( cw:GetMaxClip1() , ammoType , false ) + ent:EmitSound( SupplySound ) + + if ent == tr.Entity then + + self.Owner:EmitSound( AmmoSoundVO ) + + end + + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + self:SetNextPrimaryFire( CurTime() + self:SequenceDuration() + 1 ) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + -- Even though the viewmodel has looping IDLE anim at all times, we need this to make fire animation work in multiplayer + -- timer.Create( "weapon_idle" .. self:EntIndex(), self:SequenceDuration(), 1, function() if ( IsValid( self ) ) then self:SendWeaponAnim( ACT_VM_IDLE ) end end ) + + else + + self.Owner:EmitSound( EmptySoundVO ) + + self:SetNextSecondaryFire( CurTime() + 3 ) + + end + +end + +function SWEP:SecondaryAttack() + + if CLIENT then + + return + + end + + local ent = self.Owner + + local need = 25 + + if IsValid( ent ) and self:Clip1() >= need then + + self:TakePrimaryAmmo( need ) + + ent:EmitSound( AmmoSoundVO ) + + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + local deployEnt = ents.Create( DeployEntity ) + deployEnt:SetPos( ent:EyePos() + ent:GetAimVector() * 16 + ent:GetRight() * 2 + ent:GetUp() * -3 ) + deployEnt:SetAngles( ent:EyeAngles() - Angle( 45 , 0 , 0 ) ) + deployEnt:SetOwner( self.Owner ) + deployEnt:Spawn() + + if deployEnt:GetPhysicsObject():IsValid() then + + deployEnt:GetPhysicsObject():SetMass( 5 ) + + if self.Owner:KeyDown( IN_USE ) then + deployEnt:GetPhysicsObject():SetVelocity( ent:GetAimVector() * 50 ) + else + deployEnt:GetPhysicsObject():SetVelocity( ent:GetAimVector() * 400 ) + end + + end + + self:SetNextSecondaryFire( CurTime() + self:SequenceDuration() + 1 ) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + -- timer.Create( "weapon_idle" .. self:EntIndex(), self:SequenceDuration(), 1, function() if ( IsValid( self ) ) then self:SendWeaponAnim( ACT_VM_IDLE ) end end ) + + else + + self.Owner:EmitSound( EmptySoundVO ) + + self:SetNextSecondaryFire( CurTime() + 3 ) + + end + +end + +function SWEP:OnRemove() + + timer.Stop( "ammokit_ammo" .. self:EntIndex() ) + -- timer.Stop( "weapon_idle" .. self:EntIndex() ) + +end + +function SWEP:Holster() + + -- timer.Stop( "weapon_idle" .. self:EntIndex() ) + + return true + +end + +function SWEP:CustomAmmoDisplay() + + self.AmmoDisplay = self.AmmoDisplay or {} + + self.AmmoDisplay.Draw = true -- draw the display? + + if self.Primary.ClipSize > 0 then + + self.AmmoDisplay.PrimaryClip = self:Clip1() -- amount in clip + self.AmmoDisplay.PrimaryAmmo = self:Ammo1() -- amount in reserve + + end + + if self.Secondary.ClipSize > 0 then + + self.AmmoDisplay.SecondaryAmmo = self:Ammo2() -- amount of secondary ammo + + end + + return self.AmmoDisplay -- return the table + +end +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/support_equipment/lua/weapons/v92_bf2_medikit.lua b/garrysmod/addons/support_equipment/lua/weapons/v92_bf2_medikit.lua new file mode 100644 index 0000000..62ddbe1 --- /dev/null +++ b/garrysmod/addons/support_equipment/lua/weapons/v92_bf2_medikit.lua @@ -0,0 +1,233 @@ +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- +AddCSLuaFile() + +sound.Add({ ["name"] = "BF2.VO.English.Grunt.NoFilter.HealHere", + ["channel"] = CHAN_VOICE , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = { + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_healhere.ogg" , + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_healhere_alt.ogg" + } +}) + +sound.Add({ ["name"] = "BF2.VO.English.Grunt.NoFilter.OutOfMedic", + ["channel"] = CHAN_VOICE , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = { + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_outofmedic.ogg" , + "v92/bf2/vo/english/grunt/no_filter/auto_moodgp_tm_outofmedic_alt.ogg" + } +}) + +sound.Add({ ["name"] = "BF2.VO.English.Grunt.NoFilter.RequestMedic", + ["channel"] = CHAN_VOICE , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = { + "v92/bf2/vo/english/grunt/no_filter/PLAYER_REQUEST_TM_requestmedic.ogg" , + "v92/bf2/vo/english/grunt/no_filter/PLAYER_REQUEST_TM_requestmedic_alt.ogg" + } +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Medikit.Pickup", + ["channel"] = CHAN_ITEM , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = 100 , + ["sound"] = "v92/bf2/common/pick_up_medipack.wav" +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Medikit.Deploy" , + ["channel"] = CHAN_BODY , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = { 95 , 105 }, + ["sound"] = "v92/bf2/weapons/handheld/medikit/medikit_deploy.wav" +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Medikit.Holster" , + ["channel"] = CHAN_BODY , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = { 95 , 105 } , + ["sound"] = "v92/bf2/weapons/handheld/medikit/medikit_deploy.wav" +}) + +sound.Add({ ["name"] = "BF2.Weapon.Handheld.Medikit.Throw" , + ["channel"] = CHAN_BODY , + ["volume"] = 1.0 , + ["level"] = 85 , + ["pitch"] = { 95 , 105 } , + ["sound"] = "v92/bf2/weapons/handheld/medikit/throw.wav" +}) + +if CLIENT then + + local strEntityName = "v92_bf2_medikit" + local strInfoName = "Medical Kit" + + SWEP.Category = "Utilities" + SWEP.PrintName = strInfoName + SWEP.Author = "V92" + SWEP.Contact = "Steam" + SWEP.Purpose = "Healing Nearby Units" + SWEP.Instructions = "Primary: Надеть/снять аптечку на себя\nUse+Primary: Вылечить себя\n" + SWEP.Slot = 4 + SWEP.SlotPos = 32 + SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/" .. strEntityName ) + SWEP.ViewModelFOV = 67 + SWEP.BounceWeaponIcon = false + + language.Add( strEntityName, strInfoName ) + killicon.Add( strEntityName, "vgui/entities/".. strEntityName , Color( 255, 255, 255 ) ) + +end + +SWEP.Weight = 1 +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.UseHands = true +SWEP.ViewModel = Model("models/v92/bf2/weapons/handheld/medikit_c.mdl") +SWEP.WorldModel = Model("models/v92/bf2/weapons/handheld/medikit_w.mdl") + +SWEP.Primary.ClipSize = 100 +SWEP.Primary.DefaultClip = 100 +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.HealAmount = GetConVarNumber("sk_healthkit") +SWEP.MaxAmmo = 100 + +local ThrowSound = Sound( "BF2.Weapon.Handheld.Medikit.Throw" ) +local HealSound = Sound( "BF2.Weapon.Handheld.Medikit.Pickup" ) +local HealSoundVO = Sound( "BF2.VO.English.Grunt.NoFilter.HealHere" ) +local EmptySoundVO = Sound( "BF2.VO.English.Grunt.NoFilter.OutOfMedic" ) +local RequestSoundVO = Sound( "BF2.VO.English.Grunt.NoFilter.RequestMedic" ) + +function SWEP:Initialize() + self:SetHoldType( "slam" ) + self.HealAmount = GetConVarNumber("sk_healthkit") + + if CLIENT then + return + end + + timer.Create( "medkit_ammo" .. self:EntIndex() , 10 , 0 , function() + if ( self:Clip1() < self.MaxAmmo ) then + self:SetClip1( math.min( self:Clip1() + 5 , self.MaxAmmo ) ) + end + end ) +end + +function SWEP:PrimaryAttack() + if CLIENT then return end + + local owner = self.Owner + if not IsValid(owner) then return end + + local medikitOnBack = nil + for _, ent in ipairs(ents.FindByClass("v92_bf2_medikit_ent")) do + if IsValid(ent) and ent.IsAttached and ent.AttachedPlayer == owner then + medikitOnBack = ent + break + end + end + + if medikitOnBack then + + if owner:KeyDown(IN_USE) then + owner:ChatPrint("[!] Нельзя лечить себя, пока аптечка на спине!") + self:SetNextPrimaryFire(CurTime() + 0.5) + return + end + + medikitOnBack:DetachFromPlayer() + self:SetNextPrimaryFire(CurTime() + 1) + return + end + + if owner:KeyDown(IN_USE) then + local need = GetConVarNumber("sk_healthkit") + need = math.min(owner:GetMaxHealth() - owner:Health(), self.HealAmount) + + if self:Clip1() >= need and owner:Health() < owner:GetMaxHealth() then + self:TakePrimaryAmmo(need) + owner:SetHealth(math.min(owner:GetMaxHealth(), owner:Health() + need)) + + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:SetNextPrimaryFire(CurTime() + self:SequenceDuration() + 0.5) + owner:SetAnimation(PLAYER_ATTACK1) + else + if need > self.HealAmount then + owner:EmitSound(EmptySoundVO) + end + self:SetNextPrimaryFire(CurTime() + 1) + end + + return + end + + local deployEnt = ents.Create("v92_bf2_medikit_ent") + if not IsValid(deployEnt) then + self:SetNextPrimaryFire(CurTime() + 0.5) + return + end + + deployEnt:SetPos(owner:GetPos() + Vector(0, 0, 50)) + deployEnt:SetAngles(owner:GetAngles()) + deployEnt:SetOwner(owner) + deployEnt:Spawn() + + if deployEnt.AttachToPlayer and deployEnt:AttachToPlayer(owner) then + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:SetNextPrimaryFire(CurTime() + self:SequenceDuration() + 0.5) + owner:SetAnimation(PLAYER_ATTACK1) + else + self:SetNextPrimaryFire(CurTime() + 0.5) + end +end + +function SWEP:SecondaryAttack() + return +end + +function SWEP:OnRemove() + timer.Stop( "medkit_ammo" .. self:EntIndex() ) +end + +function SWEP:Holster() + return true +end + +function SWEP:CustomAmmoDisplay() + self.AmmoDisplay = self.AmmoDisplay or {} + self.AmmoDisplay.Draw = true + + if self.Primary.ClipSize > 0 then + self.AmmoDisplay.PrimaryClip = self:Clip1() + self.AmmoDisplay.PrimaryAmmo = self:Ammo1() + end + + if self.Secondary.ClipSize > 0 then + self.AmmoDisplay.SecondaryAmmo = self:Ammo2() + end + + return self.AmmoDisplay +end +----------------------------------------- +-- MEDKIT_BACKPACK (MILITARY RP) +-- Собственность Олега Закона и Scripty +----------------------------------------- diff --git a/garrysmod/addons/swbombs/lua/autorun/client/sw_autorun_client_v3.lua b/garrysmod/addons/swbombs/lua/autorun/client/sw_autorun_client_v3.lua new file mode 100644 index 0000000..3ca52d3 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/autorun/client/sw_autorun_client_v3.lua @@ -0,0 +1,384 @@ +swv3 = swv3 or {} +local util = util +local pairs = pairs +local table = table +local istable = istable +local IsInWorld = util.IsInWorld +local TraceLine = util.TraceLine +local QuickTrace = util.QuickTrace +local Effect = util.Effect +local MASK_ALL = MASK_ALL +local tableinsert = table.insert +local IsValid = IsValid +local DMG_BLAST = DMG_BLAST +local soundSpeed = 18005.25*18005.25 -- 343m/s +local Level = { + [1] = { + 112, + 150, + 150, + }, + [2] = { + 112, + 150, + 150, + }, + [3] = { + 112, + 150, + 150, + }, +} +local BigExplosionSoundLevels = { + [1] = { + 150, + 150, + 150, + }, + [2] = { + 150, + 150, + 150, + }, + [3] = { + 150, + 150, + 150, + }, +} +local BigExplosionSounds = { +} + +net.Receive("swv3_net_sound_new",function() + local pos = net.ReadVector() + local tab = net.ReadTable() + + if not tab[1] then return end + + local e1 = tab[1] + local e2 = tab[2] or e1 + local e3 = tab[3] or e2 + + local div = math.Clamp(math.ceil(1),1,#Level) + local vol = 1 + local currange = 1000 / div + + local curRange_min = currange*5 + curRange_min = curRange_min*curRange_min + local curRange_mid = currange*14 + curRange_mid = curRange_mid * curRange_mid + local curRange_max = currange*40 + curRange_max = curRange_max * curRange_max + + local ply = LocalPlayer():GetViewEntity() + local plypos = ply:GetPos() + local distance = ply:GetPos():DistToSqr(pos) + + hook.Run("SWV3Explosion",e1,pos,distance,e2,e3) + + if distance <= curRange_min then + + if not LocalPlayer():InVehicle() then + util.ScreenShake(pos,10,75,2,math.sqrt(curRange_min)) + end + + if BigExplosionSounds[e1] then + sound.Play(e1,pos,BigExplosionSoundLevels[div][1],100,1 * vol) + else + sound.Play(e1,pos,Level[div][1],100,1 * vol) + end + + elseif distance <= curRange_mid then + timer.Simple(distance/soundSpeed,function() + if not LocalPlayer():InVehicle() then + util.ScreenShake(pos,3,75,1.5,math.sqrt(curRange_mid)) + end + + if BigExplosionSounds[e3] then + sound.Play(e2,pos,BigExplosionSoundLevels[div][2],100,1 * vol) + else + sound.Play(e2,pos,Level[div][2],100,0.5 * vol) + end + end) + else + timer.Simple(distance/soundSpeed,function() + if BigExplosionSounds[e2] then + sound.Play(e1,pos,BigExplosionSoundLevels[div][3],100,1 * vol) + else + sound.Play(e3,pos,Level[div][3],100,1 * vol) + end + end) + end +end) + +--Particles +game.AddParticles( "particles/Missile_Thrust.pcf") +game.AddParticles( "particles/Rocket_Thrust.pcf") +game.AddParticles( "particles/AAM_Thrust.pcf") +game.AddParticles( "particles/WP_trail.pcf") +game.AddParticles( "particles/sw_flare.pcf") +game.AddParticles( "particles/gb5_emp.pcf") +game.AddParticles( "particles/gwa_agm62.pcf") +game.AddParticles( "particles/gw_mark77.pcf") +game.AddParticles( "particles/gwa_m388.pcf") +game.AddParticles( "particles/gwa_t12.pcf") +game.AddParticles( "particles/gwa_blu1.pcf") +game.AddParticles( "particles/gwa_fab100.pcf") +game.AddParticles( "particles/gw_moab_gp100_pack.pcf") +game.AddParticles( "particles/gw_gbu12.pcf") +game.AddParticles( "particles/gw_mk84.pcf") +game.AddParticles( "particles/gw_m66.pcf") +game.AddParticles( "particles/gwa_blockbuster.pcf") +game.AddParticles( "particles/gwa_mk82a3.pcf") +game.AddParticles( "particles/gw_rocket_test.pcf") +game.AddParticles( "particles/gw_death.pcf") +game.AddParticles( "particles/gwa_mop.pcf") +game.AddParticles( "particles/gwa_sdb.pcf") +game.AddParticles( "particles/gwa_water_splode.pcf") +game.AddParticles( "particles/gwa_foab.pcf") +game.AddParticles( "particles/gwa_tallboy.pcf") +game.AddParticles( "particles/gwa_m61.pcf") +game.AddParticles( "particles/gwa_foab_new.pcf") +game.AddParticles( "particles/gwa_w48.pcf") +game.AddParticles( "particles/gwa_actual_tallboy.pcf") +game.AddParticles( "particles/gwa_grigori.pcf") +game.AddParticles( "particles/gwa_grandslam.pcf") +game.AddParticles( "particles/gwa_agm_norm.pcf") +game.AddParticles( "particles/gwa_fizzle.pcf") +game.AddParticles( "particles/gwa_littleboy_fizzle.pcf") +game.AddParticles( "particles/gwa_gas.pcf") +game.AddParticles( "particles/gwa_rds1.pcf") +game.AddParticles( "particles/gw_m66_new.pcf") +game.AddParticles( "particles/gw_dirty.pcf") +game.AddParticles( "particles/gw_b61.pcf") +game.AddParticles( "particles/gw_gbu12_new.pcf") +game.AddParticles( "particles/gw_mike.pcf") +game.AddParticles( "particles/gw_must.pcf") +game.AddParticles( "particles/gw_250lb.pcf") +game.AddParticles( "particles/gw_500lb.pcf") +game.AddParticles( "particles/gw_1000kg.pcf") +game.AddParticles( "particles/gw_bunker.pcf") +game.AddParticles( "particles/gw_2000lb.pcf") +game.AddParticles( "particles/gw_foabn.pcf") +game.AddParticles( "particles/gw_moabn.pcf") + +game.AddParticles( "particles/doi_explosion_fx.pcf") +game.AddParticles( "particles/doi_explosion_fx_b.pcf") +game.AddParticles( "particles/doi_explosion_fx_c.pcf") +game.AddParticles( "particles/doi_explosion_fx_grenade.pcf") +game.AddParticles( "particles/doi_explosion_fx_new.pcf") +game.AddParticles( "particles/doi_impact_fx.pcf" ) +game.AddParticles( "particles/doi_weapon_fx.pcf" ) +game.AddParticles( "particles/gb_water.pcf") +game.AddParticles( "particles/gb5_100lb.pcf") +game.AddParticles( "particles/gb5_500lb.pcf") +game.AddParticles( "particles/gb5_1000lb.pcf") +game.AddParticles( "particles/gb5_jdam.pcf") +game.AddParticles( "particles/gb5_large_explosion.pcf") +game.AddParticles( "particles/gb5_napalm.pcf") +game.AddParticles( "particles/gb5_light_bomb.pcf") +game.AddParticles( "particles/gb5_high_explosive_2.pcf") +game.AddParticles( "particles/gb5_high_explosive.pcf") +game.AddParticles( "particles/gb5_fireboom.pcf") +game.AddParticles( "particles/neuro_tank_ap.pcf") +game.AddParticles( "particles/ins_rockettrail.pcf") +game.AddParticles( "particles/ammo_cache_ins.pcf") +game.AddParticles( "particles/doi_rockettrail.pcf") +game.AddParticles( "particles/mnb_flamethrower.pcf") +game.AddParticles( "particles/impact_fx_ins.pcf" ) +game.AddParticles( "particles/environment_fx.pcf") +game.AddParticles( "particles/water_impact.pcf") +game.AddParticles( "particles/explosion_fx_ins.pcf") +game.AddParticles( "particles/weapon_fx_tracers.pcf" ) +game.AddParticles( "particles/weapon_fx_ins.pcf" ) +game.AddParticles( "particles/gred_particles.pcf" ) +game.AddParticles( "particles/fire_01.pcf" ) +game.AddParticles( "particles/doi_explosions_smoke.pcf" ) +game.AddParticles( "particles/explosion_fx_ins_b.pcf" ) +game.AddParticles( "particles/ins_smokegrenade.pcf" ) +game.AddParticles( "particles/ww1_gas.pcf" ) + +--Decals +game.AddDecal("scorch_1kg","decals/scorch_1kg") +game.AddDecal("scorch_25kg","decals/scorch_25kg") +game.AddDecal("scorch_50kg","decals/scorch_50kg") +game.AddDecal("scorch_100kg","decals/scorch_100kg") +game.AddDecal("scorch_250kg","decals/scorch_250kg") +game.AddDecal("scorch_500kg","decals/scorch_500kg") +game.AddDecal("scorch_1000kg","decals/scorch_1000kg") +game.AddDecal("scorch_1500kg","decals/scorch_1500kg") +game.AddDecal("scorch_3000kg","decals/scorch_3000kg") +game.AddDecal("scorch_5000kg","decals/scorch_5000kg") +game.AddDecal("scorch_9000kg","decals/scorch_9000kg") + +--Fonts +surface.CreateFont( "sw_digital", { + font = "DS-Digital", + extended = false, + size = 20, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) +surface.CreateFont("sw_hud", { + font = "Roboto", + size = 20, + weight = 550, + antialias = true, + scanlines = 2, + shadow = true +}) + +--Missile coordinates +swv3 = istable( swv3 ) and swv3 or {} +swv3.ThemeColor = Color(255,0,0,255) + +CreateClientConVar( "sw_coord_x", 0, true, true,"Missile target coordinate on X axis") +CreateClientConVar( "sw_coord_y", 0, true, true,"Missile target coordinate on Y axis") +CreateClientConVar( "sw_coord_z", 0, true, true,"Missile target coordinate on Z axis") +CreateClientConVar( "sw_launch_allowed", 0, true, true,"Allow missile launch") + +local FrameSizeX = 500 +local FrameSizeY = 190 + +function swv3:OpenCoordinatesMenu( keep_position ) + local xPos + local yPos + + if IsValid( swv3.Frame ) then + if keep_position then + xPos = swv3.Frame:GetX() + yPos = swv3.Frame:GetY() + end + + swv3.Frame:Close() + swv3.Frame = nil + end + + swv3.Frame = vgui.Create( "DFrame" ) + swv3.Frame:SetSize( FrameSizeX, FrameSizeY ) + swv3.Frame:SetTitle( "" ) + swv3.Frame:SetDraggable( true ) + swv3.Frame:SetScreenLock( true ) + swv3.Frame:MakePopup() + swv3.Frame:Center() + if keep_position and xPos and yPos then + swv3.Frame:SetPos( xPos, yPos ) + end + + local LCFrame = vgui.Create( "DPanel",swv3.Frame ) + LCFrame:SetPos(330,80) + LCFrame:SetSize( 150, 60 ) + + local XCoord = vgui.Create( "DLabel",LCFrame ) + local YCoord = vgui.Create( "DLabel",LCFrame ) + local ZCoord = vgui.Create( "DLabel",LCFrame ) + local LC = vgui.Create( "DLabel",LCFrame ) + local Status = vgui.Create( "DLabel",LCFrame ) + LC:SetPos(0,45) + LC:SetTextColor(Color(0,0,0)) + LC:SetText( "Status:" ) + + local LaunchButton = vgui.Create( "DButton", swv3.Frame ) + LaunchButton:SetPos( 330, 30 ) + LaunchButton:SetSize( 150, 30 ) + + local XFrame = vgui.Create( "DPanel",swv3.Frame ) + XFrame:SetPos(10,30) + XFrame:SetSize( 300, 40 ) + + local XLabel = vgui.Create( "DLabel",XFrame ) + XLabel:SetTextColor(Color(255,0,0)) + XLabel:SetText( "X coordinate" ) + + local XEntry = vgui.Create( "DTextEntry",XFrame) -- create the form as a child of frame + XEntry:SetNumeric(true) + XEntry:SetPos(0,15) + XEntry:Dock( BOTTOM ) + XEntry.OnEnter = function( self ) + local X = GetConVar("sw_coord_x") + X:SetInt(self:GetValue()) + end + + local YFrame = vgui.Create( "DPanel",swv3.Frame ) + YFrame:SetPos(10,80) + YFrame:SetSize( 300, 40 ) + + local YLabel = vgui.Create( "DLabel",YFrame ) + YLabel:SetTextColor(Color(255,0,0)) + YLabel:SetText( "Y coordinate" ) + + local YEntry = vgui.Create( "DTextEntry",YFrame) -- create the form as a child of frame + YEntry:SetNumeric(true) + YEntry:SetPos(0,15) + YEntry:Dock( BOTTOM ) + YEntry.OnEnter = function( self ) + local Y = GetConVar("sw_coord_y") + Y:SetInt(self:GetValue()) + end + + local ZFrame = vgui.Create( "DPanel",swv3.Frame ) + ZFrame:SetPos(10,130) + ZFrame:SetSize( 300, 40 ) + + local ZLabel = vgui.Create( "DLabel",ZFrame ) + ZLabel:SetTextColor(Color(255,0,0)) + ZLabel:SetText( "Z coordinate" ) + + local ZEntry = vgui.Create( "DTextEntry",ZFrame) -- create the form as a child of frame + ZEntry:SetNumeric(true) + ZEntry:SetPos(0,15) + ZEntry:Dock( BOTTOM ) + ZEntry.OnEnter = function( self ) + local Z = GetConVar("sw_coord_z") + Z:SetInt(self:GetValue()) + end + + swv3.Frame.Paint = function(self, w, h ) + draw.RoundedBox( 8, 0, 0, w, h, Color( 0, 0, 0, 255 ) ) + draw.RoundedBoxEx( 8, 1, 26, w-2, h-27, Color( 255, 255, 255, 255 ), false, false, true, true ) + draw.RoundedBoxEx( 8, 0, 0, w, 25, swv3.ThemeColor, true, true ) + + draw.SimpleText( "[SW Bombs V3] - Missile target coordinates ", "sw_hud", 5, 11, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER ) + XCoord:SetTextColor(Color(0,0,0)) + XCoord:SetText( "X:"..GetConVar("sw_coord_x"):GetInt() ) + + YCoord:SetPos(0,15) + YCoord:SetTextColor(Color(0,0,0)) + YCoord:SetText( "Y:"..GetConVar("sw_coord_y"):GetInt() ) + + ZCoord:SetPos(0,30) + ZCoord:SetTextColor(Color(0,0,0)) + ZCoord:SetText( "Z:"..GetConVar("sw_coord_z"):GetInt() ) + + Status:SetPos(40,45) + if GetConVar("sw_launch_allowed"):GetInt() == 1 then + LaunchButton:SetText( "Disallow launch" ) + LaunchButton.DoClick = function() + RunConsoleCommand( "sw_launch_allowed", 0 ) + end + + Status:SetTextColor(Color(0,255,0)) + Status:SetText( "Ready" ) + else + LaunchButton:SetText( "Allow launch" ) + LaunchButton.DoClick = function() + RunConsoleCommand( "sw_launch_allowed", 1 ) + end + + Status:SetTextColor(Color(255,0,0)) + Status:SetText( "Standby" ) + end + end +end + +concommand.Add( "sw_open_targeting_menu", function( ply, cmd, args ) swv3:OpenCoordinatesMenu() end ) diff --git a/garrysmod/addons/swbombs/lua/autorun/server/sw_autorun_server_v3.lua b/garrysmod/addons/swbombs/lua/autorun/server/sw_autorun_server_v3.lua new file mode 100644 index 0000000..1eae178 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/autorun/server/sw_autorun_server_v3.lua @@ -0,0 +1,191 @@ +swv3 = swv3 or {} +local util = util +local pairs = pairs +local table = table +local istable = istable +local IsInWorld = util.IsInWorld +local TraceLine = util.TraceLine +local QuickTrace = util.QuickTrace +local Effect = util.Effect +local MASK_ALL = MASK_ALL +local tableinsert = table.insert +local IsValid = IsValid +local DMG_BLAST = DMG_BLAST +local AddNetworkString = util.AddNetworkString +local tab = {} + +AddNetworkString("swv3_net_sound_new") + +swv3.CreateSound = function(pos,rsound,e1,e2,e3) + tab[1] = e1 or nil + tab[2] = e2 or nil + tab[3] = e3 or nil + + net.Start("swv3_net_sound_new") + net.WriteVector(pos) + net.WriteTable(tab) + net.Broadcast() +end + +hook.Remove( "PlayerLeaveVehicle", "!!LVS_Exit" ) + +hook.Add( "PlayerLeaveVehicle", "ExitUAV", function( ply, Pod ) + if not ply:IsPlayer() or not IsValid( Pod ) then return end + + local Vehicle = Pod:lvsGetVehicle() + + if not IsValid( Vehicle ) then return end + + if not LVS.FreezeTeams then + ply:lvsSetAITeam( Vehicle:GetAITEAM() ) + end + + ply._lvsNextUse = CurTime() + 0.25 + + hook.Run( "LVS.UpdateRelationship", Vehicle ) + + local pos = Vehicle:LocalToWorld( Vehicle:OBBCenter() ) + local vel = Vehicle:GetVelocity() + local radius = Vehicle:BoundingRadius() + + local mins, maxs = ply:GetHull() + + local PosCenter = Pod:OBBCenter() + local StartPos = Pod:LocalToWorld( PosCenter ) + + local FilterPlayer = { ply } + local Filter = table.Copy( Vehicle:GetCrosshairFilterEnts() ) + table.insert( Filter, ply ) + + local zOffset = 15 + local ValidPositions = {} + + if Vehicle.LVSUAV and IsValid(Vehicle:GetNWEntity("UAVControl")) then + local ExitPos = Vehicle:GetNWEntity("UAVControl"):LocalToWorld( Vector(-50,0,20) ) + + if not ExitPos then return end + + if util.IsInWorld( ExitPos ) then + ply:SetPos( ExitPos ) + ply:SetEyeAngles( (Pod:GetPos() - ExitPos):Angle() ) + end + else + if isvector( Pod.ExitPos ) and Vehicle:GetUp().z > 0.9 then + local data = { + pos = Vehicle:LocalToWorld( Pod.ExitPos ), + dist = 1, + } + + table.insert( ValidPositions, data ) + end + + local LocalDesiredExitPosition = Vehicle:WorldToLocal( Pod:GetPos() ) + + if vel:Length() > (Pod.PlaceBehindVelocity or 100) then + LocalDesiredExitPosition.y = LocalDesiredExitPosition.y - radius + + local traceBehind = util.TraceLine( { + start = pos, + endpos = pos - vel:GetNormalized() * (radius + 50), + filter = Filter, + } ) + + local tracePlayer = util.TraceHull( { + start = traceBehind.HitPos + Vector(0,0,maxs.z + zOffset), + endpos = traceBehind.HitPos + Vector(0,0,zOffset), + maxs = Vector( maxs.x, maxs.y, 0 ), + mins = Vector( mins.x, mins.y, 0 ), + filter = FilterPlayer, + } ) + + if not tracePlayer.Hit and util.IsInWorld( tracePlayer.HitPos ) then + local data = { + pos = traceBehind.HitPos, + dist = 0, + } + + table.insert( ValidPositions, data ) + end + end + + local DesiredExitPosition = Pod:LocalToWorld( LocalDesiredExitPosition ) + + for ang = 0, 360, 15 do + local X = math.cos( math.rad( ang ) ) * radius + local Y = math.sin( math.rad( ang ) ) * radius + local Z = Pod:OBBCenter().z + + local EndPos = StartPos + Vector(X,Y,Z) + + local traceWall = util.TraceLine( {start = StartPos,endpos = EndPos,filter = Filter} ) + local traceVehicle = util.TraceLine( { + start = traceWall.HitPos, + endpos = StartPos, + filter = FilterPlayer, + } ) + + local CenterWallVehicle = (traceWall.HitPos + traceVehicle.HitPos) * 0.5 + + if traceWall.Hit or not util.IsInWorld( CenterWallVehicle ) then continue end + + local GoundPos = CenterWallVehicle - Vector(0,0,radius) + + local traceGround = util.TraceLine( {start = CenterWallVehicle,endpos = GoundPos,filter = Filter} ) + + if not traceGround.Hit or not util.IsInWorld( traceGround.HitPos ) then continue end + + local tracePlayerRoof = util.TraceHull( { + start = traceGround.HitPos + Vector(0,0,zOffset), + endpos = traceGround.HitPos + Vector(0,0,maxs.z + zOffset), + maxs = Vector( maxs.x, maxs.y, 0 ), + mins = Vector( mins.x, mins.y, 0 ), + filter = FilterPlayer, + } ) + + if tracePlayerRoof.Hit or not util.IsInWorld( tracePlayerRoof.HitPos ) then continue end + + local tracePlayer = util.TraceHull( { + start = traceGround.HitPos + Vector(0,0,maxs.z + zOffset), + endpos = traceGround.HitPos + Vector(0,0,zOffset), + maxs = Vector( maxs.x, maxs.y, 0 ), + mins = Vector( mins.x, mins.y, 0 ), + filter = FilterPlayer, + } ) + + if tracePlayer.Hit then continue end + + local traceBack = util.TraceLine( { + start = tracePlayer.HitPos + Vector(0,0,zOffset), + endpos = StartPos, + filter = FilterPlayer, + } ) + + local data = { + pos = tracePlayer.HitPos, + dist = (traceBack.HitPos - DesiredExitPosition):Length(), + } + + table.insert( ValidPositions, data ) + end + + local ExitPos + local ExitDist + + for _, data in pairs( ValidPositions ) do + if not ExitPos or not ExitDist or ExitDist > data.dist then + ExitPos = data.pos + ExitDist = data.dist + end + end + + -- all my plans failed, lets just let source do its thing + if not ExitPos then return end + + local ViewAngles = (StartPos - ExitPos):Angle() + ViewAngles.p = 0 + ViewAngles.r = 0 + + ply:SetPos( ExitPos ) + ply:SetEyeAngles( ViewAngles ) + end +end ) diff --git a/garrysmod/addons/swbombs/lua/effects/lvs_haubitze_trail_improved.lua b/garrysmod/addons/swbombs/lua/effects/lvs_haubitze_trail_improved.lua new file mode 100644 index 0000000..783edd3 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/effects/lvs_haubitze_trail_improved.lua @@ -0,0 +1,76 @@ +local MatBeam = Material( "effects/lvs_base/spark" ) +local GlowMat = Material( "sprites/light_glow02_add" ) +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + self.Entity = data:GetEntity() + + if IsValid( self.Entity ) then + self.OldPos = self.Entity:GetPos() + + self.Emitter = ParticleEmitter( self.Entity:LocalToWorld( self.OldPos ), false ) + end +end + +function EFFECT:Think() + if IsValid( self.Entity ) then + self.nextDFX = self.nextDFX or 0 + + if self.nextDFX < CurTime() then + self.nextDFX = CurTime() + 0.02 + + local oldpos = self.OldPos + local newpos = self.Entity:GetPos() + self:SetPos( newpos ) + + local Sub = (newpos - oldpos) + local Dir = Sub:GetNormalized() + local Len = Sub:Length() + + self.OldPos = newpos + end + + return true + end + + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end + + return false +end + +function EFFECT:Render() + local ent = self.Entity + + if not IsValid( ent ) then return end + + local pos = ent:GetPos() + local dir = ent:GetForward() + + local len = 250 + + render.SetMaterial( MatBeam ) + render.DrawBeam( pos - dir * len, pos + dir * len * 0.1, 32, 1, 0, Color( 100, 100, 100, 100 ) ) + render.DrawBeam( pos - dir * len * 0.5, pos + dir * len * 0.1, 16, 1, 0, Color( 255, 255, 255, 255 ) ) + + render.SetMaterial( GlowMat ) + render.DrawSprite( pos, 250, 250, Color( 100, 100, 100, 255 ) ) +end diff --git a/garrysmod/addons/swbombs/lua/effects/lvs_tracer_cannon_improved.lua b/garrysmod/addons/swbombs/lua/effects/lvs_tracer_cannon_improved.lua new file mode 100644 index 0000000..c543bce --- /dev/null +++ b/garrysmod/addons/swbombs/lua/effects/lvs_tracer_cannon_improved.lua @@ -0,0 +1,116 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +EFFECT.MatSmoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) + + self.emitter = ParticleEmitter( pos, false ) + + self.OldPos = pos + self.Dir = dir + + + local trace = util.TraceLine( { + start = pos, + endpos = pos - Vector(0,0,500), + mask = MASK_SOLID_BRUSHONLY, + } ) + + local ply = LocalPlayer() + + if not IsValid( ply ) then return end + + local ViewEnt = ply:GetViewEntity() + + if not IsValid( ViewEnt ) then return end + + local Intensity = ply:InVehicle() and 5 or 50 + local Ratio = math.min( 250 / (ViewEnt:GetPos() - trace.HitPos):Length(), 1 ) + + if Ratio < 0 then return end + + util.ScreenShake( trace.HitPos, Intensity * Ratio, 0.1, 0.5, 250 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then + if self.emitter then + self.emitter:Finish() + end + + local StartPos = self.OldPos + local EndPos = StartPos + self.Dir * 1000 + + local trace = util.TraceLine( { + start = StartPos, + endpos = EndPos, + } ) + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetStart( self.Dir ) + effectdata:SetEntity( trace.Entity ) + effectdata:SetNormal( trace.HitNormal ) + effectdata:SetSurfaceProp( trace.SurfaceProps ) + util.Effect( "lvs_bullet_impact_ap", effectdata ) + + return false + end + + if not self.emitter then return true end + + local bullet = LVS:GetBullet( self.ID ) + + local Pos = bullet:GetPos() + + local Sub = self.OldPos - Pos + local Dist = Sub:Length() + local Dir = Sub:GetNormalized() + + + self.OldPos = Pos + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 3000 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + + render.DrawBeam( endpos - dir * len, endpos + dir * len * 0.1, 32, 1, 0, Color( 100, 100, 100, 100 ) ) + render.DrawBeam( endpos - dir * len * 0.5, endpos + dir * len * 0.1, 16, 1, 0, Color( 255, 255, 255, 255 ) ) + + render.SetMaterial( self.MatSprite ) + render.DrawSprite( endpos, 250, 250, Color( 100, 100, 100, 255 ) ) +end diff --git a/garrysmod/addons/swbombs/lua/effects/lvs_tracer_red.lua b/garrysmod/addons/swbombs/lua/effects/lvs_tracer_red.lua new file mode 100644 index 0000000..521fce8 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/effects/lvs_tracer_red.lua @@ -0,0 +1,35 @@ + +EFFECT.MatBeam = Material( "effects/lvs_base/spark" ) +EFFECT.MatSprite = Material( "sprites/light_glow02_add" ) + +function EFFECT:Init( data ) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.ID = data:GetMaterialIndex() + + self:SetRenderBoundsWS( pos, pos + dir * 50000 ) +end + +function EFFECT:Think() + if not LVS:GetBullet( self.ID ) then return false end + + return true +end + +function EFFECT:Render() + local bullet = LVS:GetBullet( self.ID ) + + local endpos = bullet:GetPos() + local dir = bullet:GetDir() + + local len = 3000 * bullet:GetLength() + + render.SetMaterial( self.MatBeam ) + + render.DrawBeam( endpos - dir * len, endpos + dir * len * 0.1, 10, 1, 0, Color( 255, 0, 0, 255 ) ) + render.DrawBeam( endpos - dir * len * 0.5, endpos + dir * len * 0.1, 5, 1, 0, Color( 255, 0, 0, 255 ) ) + + render.SetMaterial( self.MatSprite ) + render.DrawSprite( endpos, 400, 400, Color( 125, 0, 0, 255 ) ) +end diff --git a/garrysmod/addons/swbombs/lua/effects/sw_aircraftfueltank_fire.lua b/garrysmod/addons/swbombs/lua/effects/sw_aircraftfueltank_fire.lua new file mode 100644 index 0000000..2839aa9 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/effects/sw_aircraftfueltank_fire.lua @@ -0,0 +1,145 @@ + +EFFECT.GlowMat = Material( "sprites/light_glow02_add" ) +EFFECT.FireMat = { + [1] = Material( "effects/lvs_base/flamelet1" ), + [2] = Material( "effects/lvs_base/flamelet2" ), + [3] = Material( "effects/lvs_base/flamelet3" ), + [4] = Material( "effects/lvs_base/flamelet4" ), + [5] = Material( "effects/lvs_base/flamelet5" ), + [6] = Material( "effects/lvs_base/fire" ), +} + +EFFECT.SmokeMat = { + [1] = Material( "particle/smokesprites_0001" ), + [2] = Material( "particle/smokesprites_0002" ), + [3] = Material( "particle/smokesprites_0003" ), + [4] = Material( "particle/smokesprites_0004" ), + [5] = Material( "particle/smokesprites_0005" ), + [6] = Material( "particle/smokesprites_0006" ), + [7] = Material( "particle/smokesprites_0007" ), + [8] = Material( "particle/smokesprites_0008" ), + [9] = Material( "particle/smokesprites_0009" ), + [10] = Material( "particle/smokesprites_0010" ), + [11] = Material( "particle/smokesprites_0011" ), + [12] = Material( "particle/smokesprites_0012" ), + [13] = Material( "particle/smokesprites_0013" ), + [14] = Material( "particle/smokesprites_0014" ), + [15] = Material( "particle/smokesprites_0015" ), + [16] = Material( "particle/smokesprites_0016" ), +} + +EFFECT.Smoke = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + self.LifeTime = 1 + self.DieTime = CurTime() + self.LifeTime + + if not IsValid( Ent ) then return end + + self.Ent = Ent + self.Pos = Ent:WorldToLocal( Pos + VectorRand() * 20 ) + self.RandomSize = math.Rand( 1, 2 ) + self.Vel = self.Ent:GetVelocity() + + local emitter = Ent:GetParticleEmitter( self.Pos ) + + if not IsValid( emitter ) then return end + + for i = 1, 8 do + local particle = emitter:Add( self.Smoke[ math.random(1, #self.Smoke ) ], Pos ) + + local Dir = Angle(0,math.Rand(-180,180),0):Forward() + Dir.z = -0.5 + Dir:Normalize() + + if particle then + particle:SetVelocity( Dir * 250 ) + particle:SetDieTime( 0.5 + i * 0.01 ) + particle:SetAirResistance( 125 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 200 ) + particle:SetEndSize( 400 ) + particle:SetRoll( math.Rand(-1,1) * math.pi ) + particle:SetRollDelta( math.Rand(-1,1) * 3 ) + particle:SetColor( 0, 0, 0 ) + particle:SetGravity( Vector( 0, 0, 600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0 ) + end + end +end + +function EFFECT:Think() + if not IsValid( self.Ent ) then return false end + + if self.DieTime < CurTime() then return false end + + self:SetPos( self.Ent:LocalToWorld( self.Pos ) ) + + return true +end + +function EFFECT:Render() + if not IsValid( self.Ent ) or not self.Pos then return end + + self:RenderSmoke() + self:RenderFire() +end + +function EFFECT:RenderSmoke() + local Scale = (self.DieTime - CurTime()) / self.LifeTime + + local Pos = self.Ent:LocalToWorld( self.Pos ) + Vector(0,0,25) + + local InvScale = 1 - Scale + + local Num = #self.SmokeMat - math.Clamp(math.ceil( Scale * #self.SmokeMat ) - 1,0, #self.SmokeMat - 1) + + local C = 10 + 10 * self.RandomSize + local Size = (25 + 30 * InvScale) * self.RandomSize + local Offset = (self.Vel * InvScale ^ 2) * 0.5 + + render.SetMaterial( self.SmokeMat[ Num ] ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 80 * self.RandomSize) - Offset, Size, Size, Color( C, C, C, 200 * Scale) ) +end + +function EFFECT:RenderFire() + local Scale = (self.DieTime - 0.4 - CurTime()) / 0.6 + + if Scale < 0 then return end + + local Pos = self.Ent:LocalToWorld( self.Pos ) + + local InvScale = 1 - Scale + + render.SetMaterial( self.GlowMat ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 10), 100 * InvScale, 100 * InvScale, Color( 255, 150, 75, 255) ) + + local Num = #self.FireMat - math.Clamp(math.ceil( Scale * #self.FireMat ) - 1,0, #self.FireMat - 1) + + local Size = (10 + 20 * Scale) * self.RandomSize + local Offset = (self.Vel * InvScale ^ 2) * 0.025 + + render.SetMaterial( self.FireMat[ Num ] ) + render.DrawSprite( Pos + Vector(0,0,InvScale ^ 2 * 25) - Offset, Size, Size, Color( 255, 255, 255, 255) ) +end diff --git a/garrysmod/addons/swbombs/lua/effects/sw_practice_smoke_v3.lua b/garrysmod/addons/swbombs/lua/effects/sw_practice_smoke_v3.lua new file mode 100644 index 0000000..442a3af --- /dev/null +++ b/garrysmod/addons/swbombs/lua/effects/sw_practice_smoke_v3.lua @@ -0,0 +1,53 @@ +--DO NOT EDIT OR REUPLOAD THIS FILE + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + VectorRand() * 1 + + local emitter = ParticleEmitter( Pos, false ) + + if emitter then + local particle = emitter:Add( Materials[math.Round(math.Rand(1,table.Count( Materials )),0)], Pos ) + + if particle then + particle:SetVelocity( VectorRand() * 100 ) + particle:SetDieTime( math.random(10,15) ) + particle:SetAirResistance( 200 ) + particle:SetStartAlpha( 155 ) + particle:SetStartSize( 10 ) + particle:SetEndAlpha( 50 ) + particle:SetEndSize( math.Rand(250,300) ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor( data:GetColor() ) + particle:SetGravity( Vector( 0, 0, 500 ) ) + particle:SetCollide( false ) + end + + emitter:Finish() + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/swbombs/lua/effects/sw_torpedo_v3.lua b/garrysmod/addons/swbombs/lua/effects/sw_torpedo_v3.lua new file mode 100644 index 0000000..01e8131 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/effects/sw_torpedo_v3.lua @@ -0,0 +1,57 @@ + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + self.LifeTime = math.Rand(2,4) + self.DieTime = CurTime() + self.LifeTime + + self.Splash = { + Pos = Pos, + Mat = Material("effects/splashwake1"), + RandomAng = math.random(0,360), + } + + local emitter = Ent:GetParticleEmitter( Ent:GetPos() ) + + if emitter and emitter.Add then + local particle = emitter:Add( "effects/splash4", Pos + VectorRand(-10,10) - Vector(0,0,20) ) + if particle then + particle:SetVelocity( Vector(0,0,250) ) + particle:SetDieTime( 0.8 ) + particle:SetAirResistance( 60 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 25 ) + particle:SetEndSize( 50 ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( false ) + end + end +end + + +function EFFECT:Think() + if CurTime() > self.DieTime then + return false + end + return true +end + +function EFFECT:Render() + if self.Splash and self.LifeTime then + local Scale = (self.DieTime - self.LifeTime - CurTime()) / self.LifeTime + local S = 100 - Scale * 200 + local Alpha = 100 + 100 * Scale + + cam.Start3D2D( self.Splash.Pos + Vector(0,0,1), Angle(0,0,0), 1 ) + surface.SetMaterial( self.Splash.Mat ) + surface.SetDrawColor( 255, 255, 255 , Alpha ) + surface.DrawTexturedRectRotated( 0, 0, S , S, self.Splash.RandomAng ) + cam.End3D2D() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_aircraft_fueltank.lua b/garrysmod/addons/swbombs/lua/entities/sw_aircraft_fueltank.lua new file mode 100644 index 0000000..49dddf1 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_aircraft_fueltank.lua @@ -0,0 +1,176 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "Base" ) + self:NetworkVar( "Entity",1, "DoorHandler" ) + + self:NetworkVar( "Float",0, "Fuel" ) + self:NetworkVar( "Float",1, "Size" ) + self:NetworkVar( "Float",2, "HP" ) + self:NetworkVar( "Float",3, "MaxHP" ) + + self:NetworkVar( "Int",0, "FuelType" ) + + self:NetworkVar( "Bool",0, "Destroyed" ) + + if SERVER then + self:SetMaxHP( 100 ) + self:SetHP( 100 ) + self:SetFuel( 1 ) + self:NetworkVarNotify( "Fuel", self.OnFuelChanged ) + end +end + +if SERVER then + function ENT:Initialize() + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + debugoverlay.Cross( self:GetPos(), 20, 5, Color( 255, 93, 0 ) ) + end + + function ENT:ExtinguishAndRepair() + self:SetHP( self:GetMaxHP() ) + self:SetDestroyed( false ) + end + + function ENT:Think() + self:NextThink( CurTime() + 1 ) + + if self:GetDestroyed() then + local Base = self:GetBase() + + if not IsValid( Base ) then return end + + if self:GetFuel() > 0 then + local dmg = DamageInfo() + dmg:SetDamage( 100 ) + dmg:SetAttacker( IsValid( Base.LastAttacker ) and Base.LastAttacker or game.GetWorld() ) + dmg:SetInflictor( IsValid( Base.LastInflictor ) and Base.LastInflictor or game.GetWorld() ) + dmg:SetDamageType( DMG_BURN ) + Base:TakeDamageInfo( dmg ) + + self:SetFuel( math.max( self:GetFuel() - 0.05, 0 ) ) + else + self:SetDestroyed( false ) + end + else + local base = self:GetBase() + + if IsValid( base ) and base:GetEngineActive() then + self:SetFuel( math.max( self:GetFuel() - (1 / self:GetSize()) * base:GetThrottle() ^ 2, 0 ) ) + end + end + + return true + end + + function ENT:TakeTransmittedDamage( dmginfo ) + if self:GetDestroyed() then return end + + local Damage = dmginfo:GetDamage() + + if Damage <= 0 then return end + + local CurHealth = self:GetHP() + + local NewHealth = math.Clamp( CurHealth - Damage, 0, self:GetMaxHP() ) + + self:SetHP( NewHealth ) + + if NewHealth <= 0 then + self:SetDestroyed( true ) + end + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:OnFuelChanged( name, old, new) + if new == old then return end + + if new <= 0 then + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:StopEngine() + + base:EmitSound("vehicles/jetski/jetski_off.wav") + end + end + + return +end + +function ENT:Initialize() +end + +function ENT:RemoveFireSound() + if self.FireBurnSND then + self.FireBurnSND:Stop() + self.FireBurnSND = nil + end + + self.ShouldStopFire = nil +end + +function ENT:StopFireSound() + if self.ShouldStopFire or not self.FireBurnSND then return end + + self.ShouldStopFire = true + + self:EmitSound("ambient/fire/mtov_flame2.wav") + + self.FireBurnSND:ChangeVolume( 0, 0.5 ) + + timer.Simple( 1, function() + if not IsValid( self ) then return end + + self:RemoveFireSound() + end ) +end + +function ENT:StartFireSound() + if self.ShouldStopFire or self.FireBurnSND then return end + + self.FireBurnSND = CreateSound( self, "ambient/fire/firebig.wav" ) + self.FireBurnSND:PlayEx(0,100) + self.FireBurnSND:ChangeVolume( LVS.EngineVolume, 1 ) + + self:EmitSound("ambient/fire/gascan_ignite1.wav") +end + +function ENT:OnRemove() + self:RemoveFireSound() +end + +function ENT:Draw() +end + +function ENT:Think() + self:SetNextClientThink( CurTime() + 0.05 ) + + self:DamageFX() + + return true +end + +function ENT:DamageFX() + if not self:GetDestroyed() or self:GetFuel() <= 0 then + self:StopFireSound() + + return + end + + self:StartFireSound() + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + effectdata:SetEntity( self:GetBase() ) + util.Effect( "lvs_carfueltank_fire", effectdata ) +end + diff --git a/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/cl_init.lua b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/cl_init.lua new file mode 100644 index 0000000..d60f479 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/cl_init.lua @@ -0,0 +1,4 @@ +include("shared.lua") +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/init.lua b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/init.lua new file mode 100644 index 0000000..c02fe89 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/init.lua @@ -0,0 +1,301 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + ent.Owner = ply + return ent +end +function ENT:Initialize() + self:SetModel("models/sw/shared/airdrop_large.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(3) + self:SetVar(1, 22) + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:SetMass(5000) + end + self:SetBodygroup(1,1) + self.Broken = false + self.Smoking = false + self.SmokeAmount = 100 +end +function ENT:Think() + self:NextThink(CurTime()+0.05) + if self:GetVelocity():Length() >= 500 then + timer.Simple(1,function() + if IsValid(self) then + if self.Broken then return end + if self:GetOpen() == false and not self:IsPlayerHolding() then + self:SetOpen(true) + self:EmitSound("sw/misc/chute_1.wav") + self:SetBodygroup(1,0) + + self.Chute1 = ents.Create("prop_physics") + self.Chute1:SetModel("models/sw/shared/chute_2.mdl") + self.Chute1:SetPos(self:GetPos()+self:GetRight()*250+self:GetUp()*25) + self.Chute1:SetAngles(self:GetAngles()) + self.Chute1:Spawn() + self.Chute1:Activate() + self.Chute1.Owner=self.Owner + self.Chute1:PhysWake() + self.Chute1:GetPhysicsObject():EnableDrag(true) + self.Chute1:GetPhysicsObject():SetMass(100) + local Wire1 = constraint.Rope(self,self.Chute1,0,0,Vector(0,0,72),Vector(0,0,0),500,0,0,1 ) + + self.Chute2 = ents.Create("prop_physics") + self.Chute2:SetModel("models/sw/shared/chute_2.mdl") + self.Chute2:SetPos(self:GetPos()-self:GetRight()*250+self:GetUp()*25) + self.Chute2:SetAngles(self:GetAngles()) + self.Chute2:Spawn() + self.Chute2:Activate() + self.Chute2.Owner=self.Owner + self.Chute2:PhysWake() + self.Chute2:GetPhysicsObject():EnableDrag(true) + self.Chute2:GetPhysicsObject():SetMass(100) + local Wire2 = constraint.Rope(self,self.Chute2,0,0,Vector(0,0,72),Vector(0,0,0),500,0,0,1 ) + + self.Chute3 = ents.Create("prop_physics") + self.Chute3:SetModel("models/sw/shared/chute_2.mdl") + self.Chute3:SetPos(self:GetPos()+self:GetForward()*250+self:GetUp()*25) + self.Chute3:SetAngles(self:GetAngles()) + self.Chute3:Spawn() + self.Chute3:Activate() + self.Chute3.Owner=self.Owner + self.Chute3:PhysWake() + self.Chute3:GetPhysicsObject():EnableDrag(true) + self.Chute3:GetPhysicsObject():SetMass(100) + local Wire3 = constraint.Rope(self,self.Chute3,0,0,Vector(0,0,72),Vector(0,0,0),500,0,0,1 ) + + if IsValid(self.Chute1) then + self.Chute1:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*1) + end + if IsValid(self.Chute2) then + self.Chute2:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*1) + end + if IsValid(self.Chute3) then + self.Chute3:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*1) + end + end + end + end) + else + if self:GetVelocity():Length() <= 10 then + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + timer.Simple(1,function() + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + self:SetBodygroup(1,1) + if IsValid(self.Chute1) then + self.Chute1:Remove() + end + if IsValid(self.Chute2) then + self.Chute2:Remove() + end + if IsValid(self.Chute3) then + self.Chute3:Remove() + end + self:SetOpen(false) + self.Smoking = true + end + end + end) + end + end + end + end + + if self.Smoking then + if self.Broken then return end + if self.SmokeAmount >= 1 then + self.NextSmoke = self.NextSmoke or 0 + if self.NextSmoke < CurTime() then + self.NextSmoke = CurTime() + 0.01 + self.SmokeAmount = self.SmokeAmount - 1 + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos()+self:GetUp() * 10 ) + effectdata:SetColor(255,0,0,255) + util.Effect( "sw_practice_smoke_v3", effectdata ) + end + end + end + + if self:GetHP() <= 0 then + if !self.Broken then + self:SetBodygroup(2,math.random(1,9)) + self.Broken = true + end + end +end +function giveammo(ply) + local wep = ply:GetActiveWeapon() + local priAmmo = wep:GetPrimaryAmmoType() + local secAmmo = wep:GetSecondaryAmmoType() + local priMag = wep:GetMaxClip1() + local secMag = wep:GetMaxClip2() + if priAmmo == -1 and secAmmo == -1 then + return false + end + if priMag ~= -1 then + ply:GiveAmmo( (priMag*3), priAmmo ) + if secAmmo ~= -1 then + ply:GiveAmmo( 1, secAmmo ) + end + return true + elseif priMag == -1 then + ply:GiveAmmo( 1, priAmmo ) + return true + end + if priAmmo == -1 and secMag ~= -1 then + ply:GiveAmmo( 1, secAmmo ) + return true + end + return false +end +function ENT:Use( ply ) + if self.Broken then return end + self.Smoking = false + local tab = self:GetTable() + if tab[1] == 22 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 21 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,1) + end + elseif tab[1] == 20 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 19 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,2) + end + elseif tab[1] == 18 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 17 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,3) + end + elseif tab[1] == 16 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 15 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,4) + end + elseif tab[1] == 14 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 13 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,5) + end + elseif tab[1] == 12 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 11 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,6) + end + elseif tab[1] == 10 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 9 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,7) + end + elseif tab[1] == 8 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 7 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,8) + end + elseif tab[1] == 6 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 5 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,9) + end + elseif tab[1] == 4 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 3 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + self:SetBodygroup(2,10) + end + elseif tab[1] == 2 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 1 then + if giveammo(ply) == true then + self:SetBodygroup(2,11) + self.Broken = true + end + end +end + +--Damage +function ENT:PhysicsCollide( data ) + if data.Speed > 5 and data.DeltaTime > 0.1 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + if VelDif > 20 then + self:SetHP( self:GetHP() - VelDif*2 ) + end + end +end +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) + + local Damage = dmginfo:GetDamage() + local CurHealth = self:GetHP() + local NewHealth = CurHealth - Damage + local ShieldCanBlock = dmginfo:IsBulletDamage() or dmginfo:IsDamageType( DMG_AIRBOAT ) + + self:SetHP( NewHealth ) +end + +function ENT:OnRemove() + if IsValid(self.Chute1) then + self.Chute1:Remove() + end + if IsValid(self.Chute2) then + self.Chute2:Remove() + end + if IsValid(self.Chute3) then + self.Chute3:Remove() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/shared.lua b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/shared.lua new file mode 100644 index 0000000..979f339 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_large_v3/shared.lua @@ -0,0 +1,15 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" + +ENT.Category = "SW Bombs V3" +ENT.ClassName = "sw_airdrop_large_v3" + +ENT.PrintName = "Airdrop" + +ENT.Spawnable = true + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Open" ) + self:NetworkVar( "Float", 1, "HP") + self:SetHP(500) +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/cl_init.lua b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/cl_init.lua new file mode 100644 index 0000000..d60f479 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/cl_init.lua @@ -0,0 +1,4 @@ +include("shared.lua") +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/init.lua b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/init.lua new file mode 100644 index 0000000..f1dd0e2 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/init.lua @@ -0,0 +1,175 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + ent.Owner = ply + return ent +end +function ENT:Initialize() + self:SetModel("models/sw/shared/airdrop.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(3) + self:SetVar(1, 10) + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + end + self.Broken = false +end +function ENT:Think() + self:NextThink(CurTime()+0.05) + if self:GetVelocity():Length() >= 500 then + timer.Simple(1,function() + if IsValid(self) then + if self.Broken then return end + if self:GetOpen() == false and not self:IsPlayerHolding() then + self:SetOpen(true) + self:EmitSound("sw/misc/chute_1.wav") + self.Chute = ents.Create("prop_physics") + self.Chute:SetModel("models/sw/shared/chute_2.mdl") + self.Chute:SetPos(self:GetPos()+self:GetUp()*25) + self.Chute:SetAngles(self:GetAngles()) + self.Chute:Spawn() + self.Chute:Activate() + self.Chute.Owner=self.Owner + self.Chute:PhysWake() + self.Chute:GetPhysicsObject():EnableDrag(true) + self.Chute:GetPhysicsObject():SetMass(100) + local Wire = constraint.Rope(self,self.Chute,0,0,Vector(30,0,20),Vector(0,0,0),150,0,0,1 ) + if IsValid(self.Chute) then + self.Chute:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*1) + end + end + end + end) + else + if self:GetVelocity():Length() <= 10 then + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + timer.Simple(1,function() + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + if IsValid(self.Chute) then + self.Chute:Remove() + end + + self:SetOpen(false) + end + end + end) + end + end + end + end + + if self:GetHP() <= 0 then + if !self.Broken then + self.Broken = true + end + end +end +function giveammo(ply) + local wep = ply:GetActiveWeapon() + local priAmmo = wep:GetPrimaryAmmoType() + local secAmmo = wep:GetSecondaryAmmoType() + local priMag = wep:GetMaxClip1() + local secMag = wep:GetMaxClip2() + if priAmmo == -1 and secAmmo == -1 then + return false + end + if priMag ~= -1 then + ply:GiveAmmo( (priMag*2), priAmmo ) + if secAmmo ~= -1 then + ply:GiveAmmo( 1, secAmmo ) + end + return true + elseif priMag == -1 then + ply:GiveAmmo( 1, priAmmo ) + return true + end + if priAmmo == -1 and secMag ~= -1 then + ply:GiveAmmo( 1, secAmmo ) + return true + end + return false +end +function ENT:Use( ply ) + if self.Broken then return end + local tab = self:GetTable() + if tab[1] == 10 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 9 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 8 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 7 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 6 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 5 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 4 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 3 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 2 then + if giveammo(ply) == true then + tab[1] = tab[1] - 1 + end + elseif tab[1] == 1 then + if giveammo(ply) == true then + self:Remove() + end + end +end + +--Damage +function ENT:PhysicsCollide( data ) + if data.Speed > 5 and data.DeltaTime > 0.1 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + if VelDif > 50 then + self:SetHP( self:GetHP() - VelDif*2 ) + end + end +end +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) + + local Damage = dmginfo:GetDamage() + local CurHealth = self:GetHP() + local NewHealth = CurHealth - Damage + local ShieldCanBlock = dmginfo:IsBulletDamage() or dmginfo:IsDamageType( DMG_AIRBOAT ) + + self:SetHP( NewHealth ) +end + +function ENT:OnRemove() + if IsValid(self.Chute) then + self.Chute:Remove() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/shared.lua b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/shared.lua new file mode 100644 index 0000000..cf56864 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_airdrop_v3/shared.lua @@ -0,0 +1,14 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" + +ENT.Category = "SW Bombs V3" +ENT.ClassName = "sw_airdrop_v3" + +ENT.PrintName = "Care package" + +ENT.Spawnable = true +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Open" ) + self:NetworkVar( "Float", 1, "HP") + self:SetHP(250) +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_base_bomb_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_base_bomb_v3.lua new file mode 100644 index 0000000..711c939 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_base_bomb_v3.lua @@ -0,0 +1,601 @@ +AddCSLuaFile() + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "Bomb basescript V3" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/bombs/ao25m1.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_25kg" +ENT.AngEffect = true + +--Sounds +ENT.ArmSound = "sw/bomb/arm.wav" +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 500 +ENT.ImpactSpeed = 100 +ENT.ImpactDepth = 25 +ENT.Mass = 0 +ENT.Durability = 10 +ENT.DetonateHeight = 0 + +--Explosion +ENT.DamageType = DMG_BURN +ENT.ExplosionDamage = 10000 +ENT.ExplosionRadius = 300 +ENT.BlastRadius = 450 +ENT.FragDamage = 25 +ENT.FragRadius = 600 +ENT.FragCount = 100 + +--Guidance +ENT.DiveHeight = 1000 +ENT.Agility = 100 +ENT.HaveGuidance = false +ENT.GuidanceActive = false +ENT.LaserGuided = false +ENT.LaserCode = "view" + +function ENT:SetupDataTables() + self:NetworkVar("Bool",0,"Timed", {KeyName = "timed", Edit = { type = "Boolean", category = "Fuze"}}) + self:NetworkVar("Int",1,"Timer", { KeyName = "timer", Edit = { type = "Int", min = 0, max = 120, category = "Fuze"} } ) + self:NetworkVar("Bool",2,"AirDetonate", {KeyName = "airdetonate", Edit = { type = "Boolean", category = "Fuze"}}) + self:NetworkVar("Int",3,"DetonateHeight", { KeyName = "detonateheight", Edit = { type = "Int", min = 0, max = 2000, category = "Fuze"} } ) +end + +if SERVER then + local angle_reg = Angle(0,0,0) + local angle_dif = Angle(-90,0,0) + local Normal = Vector(1,1,0) + local Normal2 = Vector(0.1,1,1) + local trlength = Vector(0,0,9000) + local angle_zero = Angle() + local angle_1 = Angle(-90,0,0) + + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent + end + + function ENT:Initialize() + self:SetModel(self.Model) + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self:PhysWake() + local pObj = self:GetPhysicsObject() + pObj:SetMass( self.Mass ) + pObj:EnableGravity( false ) + pObj:EnableMotion( true ) + pObj:EnableDrag( false ) + self:StartMotionController() + self:PhysWake() + self.InGround = false + self.InEntity = false + self.Exploded = false + self.Armed = false + self.CurDurability = self.Durability + self.AirDetTicks = 0 + self.FreefallTicks=0 + self:OnSpawn() + end + + function ENT:OnSpawn() + end + + --Arming + function ENT:Use( activator, caller ) + if !self.Exploded and !self.Armed then + self:EmitSound(self.ArmSound) + self.Armed = true + end + end + + --Think + function ENT:Think() + self:NextThink(CurTime()) + if self.Armed then + if self:GetAirDetonate() then + if IsValid(self) and (self:GetVelocity():Length() > self.ImpactSpeed) and not self:IsPlayerHolding() then + self.AirDetTicks = self.AirDetTicks + 1 + if self.AirDetTicks >= 15 then + local tr = util.TraceLine({ + start = self:LocalToWorld(self:OBBCenter()), + endpos = self:LocalToWorld(self:OBBCenter()) - Vector(0,0,self:GetDetonateHeight()), + filter = self, + }) + if tr.Hit then + self.Exploded = true + self:Explode(self:GetPos()) + end + end + end + end + + if self.GuidanceActive then + self:LaserGuidance() + self:Guidance(self:GetPhysicsObject()) + end + end + if self.InGround or self.InEntity then + if self:IsPlayerHolding() then + self.InGround = false + self.InEntity = false + end + end + + self:OnTick() + return true + end + function ENT:OnTick() + end + + --Flight physics + function ENT:PhysicsSimulate( phys, deltatime ) + phys:Wake() + local ForceLinear, ForceAngle = phys:CalculateForceOffset( physenv.GetGravity(), phys:LocalToWorld( phys:GetMassCenter() + Vector(10,0,0) ) ) + if self.WingsOpen then + ForceAngle = ForceAngle - phys:GetAngleVelocity() * 30 + else + ForceAngle = ForceAngle - phys:GetAngleVelocity() * 5 + if not self.GuidanceActive then + phys:AddAngleVelocity(Vector(self:GetVelocity():Length()*0.01,0,0)) + end + end + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION + end + + --Guidance + function ENT:LaserGuidance() + if self.Armed and not (self.InGround or self.InEntity) then + if self.LaserGuided then + if IsValid(self.Launcher) then + local Parent = self.Launcher + local phys = self:GetPhysicsObject() + local ID = Parent:LookupAttachment( self.LaserCode ) + local Attachment = Parent:GetAttachment( ID ) + if Parent:GetAttachment( ID ) then + local TargetDir = Attachment.Ang:Forward() + local tr = util.TraceHull( { + start = Attachment.Pos, + endpos = (Attachment.Pos + TargetDir * 999999), + mins = Vector( -1, -1, -1 ), + maxs = Vector( 1, 1, 1 ), + filter = {self,Parent,Parent.wheel_C,Parent.wheel_R,Parent.wheel_L} + } ) + self.target = tr.Entity + self.targetOffset = tr.Entity:WorldToLocal(tr.HitPos) + end + end + end + end + end + function ENT:Guidance(ph) + if self.target != NULL then + if IsValid(self.target) or self.target:GetClass() == "worldspawn" then + if not (self.InGround or self.Armed or self.InEntity) then return end + local pos = self:GetPos() + local vel = self:WorldToLocal(pos+ph:GetVelocity())*0.4 + vel.x = 0 + local target = self.target:LocalToWorld(self.targetOffset) + local v = self:WorldToLocal(target + Vector(0,0,math.Clamp((pos*Normal):Distance(target*Normal)/5 - 50,0,self.DiveHeight or 0))):GetNormal() + if isnumber(self.Agility) then + v.y = math.Clamp(v.y*10,-1,1)*self.Agility + v.z = math.Clamp(v.z*10,-1,1)*self.Agility + else + v.y = math.Clamp(v.y*10,-1,1)*10 + v.z = math.Clamp(v.z*10,-1,1)*10 + end + ph:AddAngleVelocity( + ph:GetAngleVelocity()*-0.4 + + Vector(math.Rand(-1,1), math.Rand(-1,1), math.Rand(-1,1))*5 + + Vector(0,-vel.z,vel.y) + + Vector(0,-v.z,v.y) + ) + ph:AddVelocity(self:GetForward()*10 - self:LocalToWorld(vel*Normal2) + pos) + end + end + end + + --Taking damage + function ENT:OnTakeDamage(dmginfo) + if self.Exploded then return end + local inflictor = dmginfo:GetInflictor() + local attacker = dmginfo:GetAttacker() + if IsValid(inflictor) and (inflictor.IsGredBomb or inflictor.lvsProjectile or inflictor.SWBombV3 ) then return end + if IsValid(attacker) and (attacker.IsGredBomb or attacker.lvsProjectile or attacker.SWBombV3 ) then return end + self:TakePhysicsDamage(dmginfo) + self.CurDurability = self.CurDurability - dmginfo:GetDamage() + if self.Armed then + if self.CurDurability <= 0 then + self.Exploded = true + self:Explode(self:GetPos()) + end + else + self:EmitSound(self.ArmSound) + self.Armed = true + end + end + + --Explosion + function ENT:Explode(pos) + if !self.Exploded then return end + if !self.Armed then return end + pos = self:LocalToWorld(self:OBBCenter()) + if(self:WaterLevel() >= 1) then + local tr = util.TraceLine({ + start = pos, + endpos = pos + Vector(0,0,9000), + filter = self, + }) + local tr2 = util.TraceLine({ + start = tr.HitPos, + endpos = tr.HitPos - Vector(0,0,9000), + filter = self, + mask = MASK_WATER + CONTENTS_TRANSLUCENT, + }) + if tr2.Hit then + ParticleEffect(self.EffectWater, tr2.HitPos,(self.AngEffect and angle_dif or angle_reg), nil) + end + else + local tr = util.TraceLine({ + start = pos, + endpos = pos - Vector(0, 0, self.TraceLength), + filter = self, + }) + if tr.HitWorld then + ParticleEffect(self.Effect,pos,(self.AngEffect and angle_dif or angle_reg),nil) + else + ParticleEffect(self.EffectAir,pos,(self.AngEffect and angle_dif or angle_reg),nil) + end + end + debugoverlay.Sphere(pos,self.ExplosionRadius,5,Color(255,0,0,100),false) + debugoverlay.Sphere(pos,self.BlastRadius,5,Color(200,150,0,100),false) + debugoverlay.Sphere(pos,self.FragRadius,5,Color(200,200,0,100),false) + + --Deadly zone + if self.ExplosionRadius > 0 then + for k, v in pairs(ents.FindInSphere(pos,self.ExplosionRadius)) do + if v:IsValid() and not v.SWBombV3 then + local dmg = DamageInfo() + dmg:SetInflictor(self) + dmg:SetDamage(self.ExplosionDamage) + dmg:SetDamageType(self.DamageType) + dmg:SetAttacker((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()) + v:TakeDamageInfo(dmg) + end + end + end + + --Blast wave + if self.BlastRadius > 0 then + util.BlastDamage( self, ((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()), pos, self.BlastRadius,self.ExplosionDamage/2 ) + self:BlastDoors(self, pos, 10) + end + + --Frags + if self.FragCount > 0 then + self:Fragmentation(self,pos,10000,self.FragDamage,self.FragRadius,((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld())) + end + + swv3.CreateSound(pos,false,self.ExplosionSound,self.FarExplosionSound,self.DistExplosionSound) + timer.Simple(0,function() + if !IsValid(self) then return end + self:Remove() + end) + end + + --Impact + function ENT:PhysicsCollide( data, physobj ) + if self:GetVelocity():Length() >= self.ImpactSpeed then + local tr = util.TraceLine({ + start = self:LocalToWorld(self:OBBCenter()), + endpos =self:LocalToWorld(self:OBBCenter())+self:GetAngles():Forward()*self.TraceLength, + filter = self, + }) + if not (self.InGround or self.InEntity) then + if tr.Hit and tr.HitWorld and not tr.HitSky then + if self:WaterLevel() <= 0 then + self:EmitSound(self.ImpactSound) + self:EmitSound(self.DebrisSound) + else + self:EmitSound(self.WaterImpactSound) + end + self:SetPos(self:GetPos()+self:GetAngles():Forward()*(self.ImpactDepth)) + self:SetAngles(self:GetAngles()) + self:GetPhysicsObject():EnableMotion(false) + self.InGround = true + elseif tr.Hit and not tr.HitWorld and not tr.HitSky then + if IsValid(tr.Entity) then + if self:WaterLevel() <= 0 then + self:EmitSound(self.ImpactSound) + self:EmitSound(self.DebrisSound) + else + self:EmitSound(self.WaterImpactSound) + end + self:SetPos(self:GetPos()+self:GetAngles():Forward()*(self.ImpactDepth)) + self:SetAngles(self:GetAngles()) + self:SetParent(tr.Entity) + self.InEntity = true + end + end + end + + if self.Armed then + if self:GetTimed() then + timer.Simple(self:GetTimer(),function() + if IsValid(self) then + self.Exploded = true + self:Explode(self:GetPos()) + end + end) + else + self.Exploded = true + self:Explode(self:GetPos()) + end + else + self:EmitSound(self.ArmSound) + self.Armed = true + end + end + end + + --Door blasting + function ENT:IsDoor(ent) + local Class = ent:GetClass() + + return (Class == "prop_door") or (Class == "prop_door_rotating") or (Class == "func_door") or (Class == "func_door_rotating") + end + function ENT:BlastDoors(blaster, pos, power, range, ignoreVisChecks) + for k, door in pairs(ents.FindInSphere(pos, 40 * power * (range or 1))) do + if self:IsDoor(door) then + local proceed = ignoreVisChecks + + if not proceed then + local tr = util.QuickTrace(pos, door:LocalToWorld(door:OBBCenter()) - pos, blaster) + proceed = IsValid(tr.Entity) and (tr.Entity == door) + end + + if proceed then + self:BlastDoor(door, (door:LocalToWorld(door:OBBCenter()) - pos):GetNormalized() * 1000) + end + end + if door:GetClass() == "func_breakable_surf" then + door:Fire("Break") + end + end + end + function ENT:BlastDoor(ent, vel) + local Moddel, Pozishun, Ayngul, Muteeriul, Skin = ent:GetModel(), ent:GetPos(), ent:GetAngles(), ent:GetMaterial(), ent:GetSkin() + sound.Play("Wood_Crate.Break", Pozishun, 60, 100) + sound.Play("Wood_Furniture.Break", Pozishun, 60, 100) + ent:Fire("unlock", "", 0) + ent:Fire("open", "", 0) + ent:SetNoDraw(true) + ent:SetNotSolid(true) + + if Moddel and Pozishun and Ayngul then + local Replacement = ents.Create("prop_physics") + Replacement:SetModel(Moddel) + Replacement:SetPos(Pozishun + Vector(0, 0, 1)) + Replacement:SetAngles(Ayngul) + + if Muteeriul then + Replacement:SetMaterial(Muteeriul) + end + + if Skin then + Replacement:SetSkin(Skin) + end + + Replacement:SetModelScale(.9, 0) + Replacement:Spawn() + Replacement:Activate() + + if vel then + Replacement:GetPhysicsObject():SetVelocity(vel) + + timer.Simple(0, function() + if IsValid(Replacement) then + Replacement:GetPhysicsObject():ApplyForceCenter(vel * 100) + end + end) + end + + timer.Simple(3, function() + if IsValid(Replacement) then + Replacement:SetCollisionGroup(COLLISION_GROUP_WEAPON) + end + end) + + timer.Simple(30, function() + if IsValid(ent) then + ent:SetNotSolid(false) + ent:SetNoDraw(false) + end + + if IsValid(Replacement) then + Replacement:Remove() + end + end) + end + end + + --Frags + function ENT:Fragmentation(shooter, origin, fragNum, fragDmg, fragMaxDist, attacker, direction, spread, zReduction) + shooter = shooter or game.GetWorld() + zReduction = zReduction or 2 + + local Spred = Vector(0, 0, 0) + local BulletsFired, MaxBullets, disperseTime = 0, self.FragCount, .5 + + if fragNum >= 12000 then + disperseTime = 2 + elseif fragNum >= 6000 then + disperseTime = 1 + end + + for i = 1, fragNum do + timer.Simple((i / fragNum) * disperseTime, function() + local Dir + + if direction and spread then + Dir = Vector(direction.x, direction.y, direction.z) + Dir = Dir + VectorRand() * math.Rand(0, spread) + Dir:Normalize() + else + Dir = VectorRand() + end + + if zReduction then + Dir.z = Dir.z / zReduction + Dir:Normalize() + end + + local Tr = util.QuickTrace(origin, Dir * fragMaxDist, shooter) + + if Tr.Hit and not Tr.HitSky and not Tr.HitWorld and (BulletsFired < MaxBullets) then + local LowFrag = (Tr.Entity.IsVehicle and Tr.Entity:IsVehicle()) or Tr.Entity.LFS or Tr.Entity.LVS or Tr.Entity.EZlowFragPlease + + if (not LowFrag) or (LowFrag and math.random(1, 4) == 2) then + + local firer = (IsValid(shooter) and shooter) or game.GetWorld() + + firer:FireBullets({ + Attacker = attacker, + Damage = fragDmg, + Force = fragDmg / 8, + Num = 1, + Src = origin, + Tracer = 0, + Dir = Dir, + Spread = Spred, + AmmoType = "Buckshot" + }) + BulletsFired = BulletsFired + 1 + end + end + end) + end + end +end + +if CLIENT then + function ENT:Initialize() + self.snd = CreateSound(self, self.WhistleSound) + self.snd:SetSoundLevel( 110 ) + self.snd:PlayEx(0,150) + end + function ENT:CalcDoppler() + local Ent = LocalPlayer() + local ViewEnt = Ent:GetViewEntity() + + local sVel = self:GetVelocity() + local oVel = Ent:GetVelocity() + local SubVel = oVel - sVel + local SubPos = self:GetPos() - Ent:GetPos() + + local DirPos = SubPos:GetNormalized() + local DirVel = SubVel:GetNormalized() + local A = math.acos( math.Clamp( DirVel:Dot( DirPos ) ,-1,1) ) + return (1 + math.cos( A ) * SubVel:Length() / 13503.9) + end + function ENT:Think() + if self.snd then + self.snd:ChangePitch( 100 * self:CalcDoppler(), 1 ) + self.snd:ChangeVolume(math.Clamp(-(self:GetVelocity().z + 1000) / 3000,0,1), 2) + end + end + function ENT:Draw() + self:DrawModel() + end + function ENT:SoundStop() + if self.snd then + self.snd:Stop() + end + end + function ENT:OnRemove() + self:SoundStop() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_base_rocket_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_base_rocket_v3.lua new file mode 100644 index 0000000..dbf491c --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_base_rocket_v3.lua @@ -0,0 +1,949 @@ +AddCSLuaFile() + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +sound.Add( { + name = "SW_ROCKET_ENG_LOOP", + channel = CHAN_AUTO, + volume = 1.0, + level = 80, + sound = "sw/rocket/rocket_fly_loop.wav" +} ) + +--Main info +ENT.Type = "anim" +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "Rocket basescript V3" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3" +ENT.Editable = true +ENT.SWBombV3 = true +ENT.IsRocket = true + +--Visual +ENT.Model = "models/sw/rus/rockets/s1of.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_25kg" +ENT.AngEffect = true +ENT.RocketTrail = "Small_mis_thrust" +ENT.RocketBurnoutTrail = "Small_mis_burnout" +ENT.Tracer1Att = nil +ENT.Tracer2Att = nil +ENT.Engine1Att = nil +ENT.Engine2Att = nil + +--Sounds +ENT.ArmSound = "sw/bomb/arm.wav" +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) +ENT.EngineSound = "SW_ROCKET_ENG_LOOP" + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.DetonateHeight = 0 +ENT.MaxVelocity = 100 +ENT.FuelBurnoutTime = 1 +ENT.RotationalForce = 1000 + +--Explosion +ENT.DamageType = DMG_BURN +ENT.ExplosionDamage = 1000 +ENT.ExplosionRadius = 300 +ENT.BlastRadius = 450 +ENT.FragDamage = 25 +ENT.FragRadius = 600 +ENT.FragCount = 100 +ENT.ArmorPenetration = 1000 +ENT.PenetrationDamage = 1000 + +--Guidance +ENT.DiveHeight = 0 +ENT.Agility = 100 +ENT.HaveGuidance = false +ENT.GuidanceActive = false +ENT.LaserGuided = false +ENT.LaserCode = "view" +ENT.SpiralRate = -0.4 +ENT.SpiralRadius = 0 +ENT.IRunstable = false +ENT.IRradius = 0 + +function ENT:SetupDataTables() + self:NetworkVar("Bool",0,"AirDetonate", {KeyName = "airdetonate", Edit = { type = "Boolean", category = "Fuze"}}) + self:NetworkVar("Int",1,"DetonateHeight", { KeyName = "detonateheight", Edit = { type = "Int", min = 0, max = 2000, category = "Fuze"} } ) + self:NetworkVar("Int",2,"TracerScale") + self:NetworkVar("Bool",3,"RadioFuze", {KeyName = "radiofuze", Edit = { type = "Boolean", category = "Fuze"}}) + self:NetworkVar("Int",4,"RadioFuzeRadius", { KeyName = "radiofuzeradius", Edit = { type = "Int", min = 0, max = 5000, category = "Fuze"} }) +end + +if SERVER then + local angle_reg = Angle(0,0,0) + local angle_dif = Angle(-90,0,0) + local Normal = Vector(1,1,0) + local Normal2 = Vector(0.1,1,1) + local trlength = Vector(0,0,9000) + local angle_zero = Angle() + local angle_1 = Angle(-90,0,0) + + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent + end + + function ENT:Initialize() + self:SetModel(self.Model) + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetCollisionGroup( COLLISION_GROUP_NONE ) + self:PhysWake() + local pObj = self:GetPhysicsObject() + pObj:SetMass( self.Mass ) + pObj:EnableGravity( false ) + pObj:EnableMotion( true ) + pObj:EnableDrag( false ) + self:StartMotionController() + self:PhysWake() + self.InGround = false + self.InEntity = false + self.Exploded = false + self.Armed = false + self.CurDurability = self.Durability + self.AirDetTicks = 0 + self.MaxVelocityUnitsSquared = self.MaxVelocity and self:ConvertMetersToUnits(self.MaxVelocity^2) or nil + end + + --Arming + function ENT:Use( activator, caller ) + if !self.Exploded and !self.Armed then + self:EmitSound(self.ArmSound) + self.Armed = true + self:Launch() + end + end + function ENT:Launch() + if self.Exploded or self.Burnt or self.Fired then return end + + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + + local phys = self:GetPhysicsObject() + + if !IsValid(phys) then return end + + self.ImpactSpeed = 10 + + self.Fired = true + self:SetNWBool("Fired",true) + + constraint.RemoveAll(self) + + phys:Wake() + phys:EnableMotion(true) + + + self:InitLaunch(phys) + end + function ENT:InitLaunch(phys) + + self:EmitSound(self.StartSound) + util.ScreenShake( self:GetPos(), 1, 10, 1, 150+self.ExplosionRadius, true, nil ) + self:SetBodygroup(1,1) + + phys:AddAngleVelocity(Vector(self.RotationalForce or 0,0,0)) + + if self.RocketTrail then + timer.Simple(0,function() + if !IsValid(self) then return end + + if self.Engine1Att then + local ID = self:LookupAttachment( self.Engine1Att ) + ParticleEffectAttach(self.RocketTrail,PATTACH_POINT_FOLLOW,self,ID) + end + if self.Engine2Att then + local ID = self:LookupAttachment( self.Engine2Att ) + ParticleEffectAttach(self.RocketTrail,PATTACH_POINT_FOLLOW,self,ID) + end + if not self.Engine1Att and not self.Engine2Att then + ParticleEffectAttach(self.RocketTrail,PATTACH_ABSORIGIN_FOLLOW,self,1) + end + + if self.Tracer1Att then + if self.TracerEffect then + local ID = self:LookupAttachment( self.Tracer1Att ) + ParticleEffectAttach(self.TracerEffect,PATTACH_POINT_FOLLOW,self,ID) + end + end + if self.Tracer2Att then + if self.TracerEffect then + local ID = self:LookupAttachment( self.Tracer2Att ) + ParticleEffectAttach(self.TracerEffect,PATTACH_POINT_FOLLOW,self,ID) + end + end + end) + end + + if self.FuelBurnoutTime and self.FuelBurnoutTime != 0 then + timer.Simple(self.FuelBurnoutTime,function() + if !IsValid(self) then return end + + self.Burnt = true + self:SetNWBool("Burnt",true) + self:StopParticles() + self:StopSound(self.EngineSound) + self:StopSound(self.StartSound) + + if self.Engine1Att then + local ID = self:LookupAttachment( self.Engine1Att ) + ParticleEffectAttach(self.RocketBurnoutTrail,PATTACH_POINT_FOLLOW,self,ID) + end + if self.Engine2Att then + local ID = self:LookupAttachment( self.Engine2Att ) + ParticleEffectAttach(self.RocketBurnoutTrail,PATTACH_POINT_FOLLOW,self,ID) + end + if not self.Engine1Att and not self.Engine2Att then + ParticleEffectAttach(self.RocketBurnoutTrail,PATTACH_ABSORIGIN_FOLLOW,self,1) + end + if self.Tracer1Att then + if self.TracerEffect then + local ID = self:LookupAttachment( self.Tracer1Att ) + ParticleEffectAttach(self.TracerEffect,PATTACH_POINT_FOLLOW,self,ID) + end + end + if self.Tracer2Att then + if self.TracerEffect then + local ID = self:LookupAttachment( self.Tracer2Att ) + ParticleEffectAttach(self.TracerEffect,PATTACH_POINT_FOLLOW,self,ID) + end + end + end) + end + end + + --Think + function ENT:ConvertMetersToUnits(Meters) + return Meters / 0.01905 + end + function ENT:ConvertUnitsToMeters(Units) + return Units * 0.01905 + end + function ENT:Think() + self:NextThink(CurTime()) + if self:IsPlayerHolding() then + self:SetNWBool("IsPlayerHolding",true) + else + self:SetNWBool("IsPlayerHolding",false) + end + if self.Armed then + if self:GetAirDetonate() then + if IsValid(self) and (self:GetVelocity():Length() > self.ImpactSpeed) and not self:IsPlayerHolding() then + self.AirDetTicks = self.AirDetTicks + 1 + if self.AirDetTicks >= 50 then + local tr = util.TraceLine({ + start = self:LocalToWorld(self:OBBCenter()), + endpos = self:LocalToWorld(self:OBBCenter()) - Vector(0,0,self:GetDetonateHeight()), + filter = self, + }) + if tr.Hit then + self.Exploded = true + self:Explode(self:GetPos()) + end + end + end + end + if self.CruiseMode and self.GuidanceActive then + local ph = self:GetPhysicsObject() + local target = self.target:LocalToWorld(self.targetOffset) + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = (self:GetPos() + (self:GetAngles():Forward()*7500) - (self:GetAngles():Up()*2500) ), + filter = {self} + } ) + if tr.Hit then + local distance = self:GetPos():Distance(target) + if distance > 10000 then + ph:AddVelocity(self:GetAngles():Up()*500) + end + end + end + if self.GuidanceActive then + self:LaserGuidance() + self:Guidance(self:GetPhysicsObject()) + if IsValid(self.target) then + if self.WithOffset then + self.TargetForwardOffset = self:GetPos():Distance(self.target:GetPos())/(self.OffsetMultiplier or 2) + self.targetOffset = self.target:WorldToLocal(self.target:GetPos()+(self.target:GetForward()*self.TargetForwardOffset)) + end + end + if self.IRunstable == true then + for _, e in pairs(ents.FindInCone(self:GetPos(),self:GetForward(),1500,math.cos(math.rad(self.SeekerCone)))) do + if IsValid( e ) and e:GetClass() == "misc_flare" then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = e:GetPos(), + } ) + if tr.Hit and tr.Entity == e then + self.LaserGuided = false + self.target = e + end + end + if IsValid( e ) and e:GetClass() == "sw_flr" then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = e:GetPos(), + } ) + if tr.Hit and tr.Entity == e then + self.LaserGuided = false + self.target = e + end + end + if IsValid( e ) and e:GetClass() == "sw_flare_v3" then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = e:GetPos(), + } ) + if tr.Hit and tr.Entity == e then + self.LaserGuided = false + self.target = e + end + end + end + end + end + if self:GetRadioFuze() then + for _, e in pairs(ents.FindInSphere(self:GetPos(), self:GetRadioFuzeRadius())) do + if IsValid( e ) and e != self.Launcher then + if self.LaserGuided == true then + if e.IsRocket == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + if e.Fired == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + if e.Armed == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + if e.Burnt == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + if ((e.LFS and e:GetEngineActive()) or ((e:GetClass() == "lvs_helicopter_engine" or e:GetClass() == "lvs_fighterplane_engine") and e:GetBase():GetEngineActive()) or (e.isWacAircraft and e.active)) then + self.Exploded = true + self:Explode() + end + else + if ((e.LFS and e:GetEngineActive()) or ((e:GetClass() == "lvs_helicopter_engine" or e:GetClass() == "lvs_fighterplane_engine") and e:GetBase():GetEngineActive()) or (e.isWacAircraft and e.active)) then + self.Exploded = true + self:Explode() + end + + if e.IsRocket == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + if e.Fired == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + if e.Armed == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + if e.Burnt == true and e.Launcher != self.Launcher then + self.Exploded = true + self:Explode() + end + end + end + end + end + if self:GetNWBool("Fired") == true then + if not self.Burnt then + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + if self.MaxVelocityUnitsSquared and phys:GetVelocity():LengthSqr() < self.MaxVelocityUnitsSquared then + phys:ApplyForceCenter(self:GetForward() * 500000) + end + end + end + end + end + if self.InGround or self.InEntity then + if self:IsPlayerHolding() then + self.InGround = false + self.InEntity = false + end + end + + self:OnTick() + return true + end + function ENT:OnTick() + end + + --Flight physics + function ENT:PhysicsSimulate( phys, deltatime ) + if self.GuidanceActive or IsValid(self.target) then return end + phys:Wake() + local ForceLinear, ForceAngle = phys:CalculateForceOffset( physenv.GetGravity(), phys:LocalToWorld( phys:GetMassCenter() + Vector(10,0,0) ) ) + ForceAngle = ForceAngle - phys:GetAngleVelocity() * 30 + if self.RotationalForce and self.Fired then + phys:AddAngleVelocity(Vector(self.RotationalForce,0,0)) + end + return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION + end + + --Guidance + function ENT:LaserGuidance() + if self.Armed and not (self.InGround or self.InEntity) then + if self.LaserGuided then + if IsValid(self.Launcher) then + local Parent = self.Launcher + local phys = self:GetPhysicsObject() + local ID = Parent:LookupAttachment( self.LaserCode ) + local Attachment = Parent:GetAttachment( ID ) + if Parent:GetAttachment( ID ) then + local TargetDir = Attachment.Ang:Forward() + local tr = util.TraceLine( { + start = Attachment.Pos, + endpos = (Attachment.Pos + TargetDir * 999999), + filter = {self,Parent,Parent.wheel_C,Parent.wheel_R,Parent.wheel_L} + } ) + self.target = tr.Entity + self.targetOffset = tr.Entity:WorldToLocal(tr.HitPos) + phys:SetVelocity( self:GetVelocity() + self:GetAngles():Forward() * 50 ) + end + end + end + end + end + function ENT:Guidance(ph) + if self.target != NULL then + if IsValid(self.target) or self.target:GetClass() == "worldspawn" then + if not (self.InGround or self.Armed or self.InEntity) then return end + local pos = self:GetPos() + local vel = self:WorldToLocal(pos+ph:GetVelocity())*0.4 + vel.x = 0 + local target = self.target:LocalToWorld(self.targetOffset) + local v = self:WorldToLocal(target + Vector(0,0,math.Clamp((pos*Normal):Distance(target*Normal)/5 - 10,0,self.DiveHeight or 0))):GetNormal() + if isnumber(self.Agility) then + v.y = math.Clamp(v.y*10,-1,1)*self.Agility + v.z = math.Clamp(v.z*10,-1,1)*self.Agility + else + v.y = math.Clamp(v.y*10,-1,1)*10 + v.z = math.Clamp(v.z*10,-1,1)*10 + end + if self.Fired then + if self.SpiralRadius > 0 then + self.SpiralRadius = self.SpiralRadius - FrameTime()*(self.StabilisationTime or 50) + end + if self.SpiralRadius < 0 then + self.SpiralRadius = self.SpiralRadius + FrameTime()*(self.StabilisationTime or 50) + end + if self.SpiralRate > 0 then + self.SpiralRate = self.SpiralRate - FrameTime() + end + if self.SpiralRate < 0 then + self.SpiralRate = self.SpiralRate + FrameTime() + end + end + if self.SpiralRadnom then + ph:AddAngleVelocity( + ph:GetAngleVelocity()*self.SpiralRate + + Vector(math.Rand(-self.SpiralRadius,self.SpiralRadius), math.Rand(-self.SpiralRadius,self.SpiralRadius), math.Rand(-self.SpiralRadius,self.SpiralRadius)) + + Vector(0,-vel.z,vel.y) + + Vector(0,-v.z,v.y) + ) + else + ph:AddAngleVelocity( + ph:GetAngleVelocity()*self.SpiralRate + + Vector(self.SpiralRadius, self.SpiralRadius, self.SpiralRadius) + + Vector(0,-vel.z,vel.y) + + Vector(0,-v.z,v.y) + ) + end + ph:AddVelocity(self:GetForward() - self:LocalToWorld(vel*Normal2) + pos) + end + end + end + + --Taking damage + function ENT:OnTakeDamage(dmginfo) + if self.Exploded then return end + local inflictor = dmginfo:GetInflictor() + local attacker = dmginfo:GetAttacker() + if IsValid(inflictor) and (inflictor.IsGredBomb or inflictor.lvsProjectile or inflictor.SWBombV3 ) then return end + self:TakePhysicsDamage(dmginfo) + self.CurDurability = self.CurDurability - dmginfo:GetDamage() + if self.Armed then + if self.CurDurability <= 0 then + self.Exploded = true + self:Explode(self:GetPos()) + end + else + if !self.Armed and !self.Fired then + self:EmitSound(self.ArmSound) + self.Armed = true + self:Launch() + end + end + end + + --Explosion + function ENT:Explode(pos) + if !self.Exploded then return end + if !self.Armed then return end + pos = self:LocalToWorld(self:OBBCenter()) + if(self:WaterLevel() >= 1) then + local tr = util.TraceLine({ + start = pos, + endpos = pos + Vector(0,0,9000), + filter = self, + }) + local tr2 = util.TraceLine({ + start = tr.HitPos, + endpos = tr.HitPos - Vector(0,0,9000), + filter = self, + mask = MASK_WATER + CONTENTS_TRANSLUCENT, + }) + if tr2.Hit then + ParticleEffect(self.EffectWater, tr2.HitPos,(self.AngEffect and angle_dif or angle_reg), nil) + end + else + local tr = util.TraceLine({ + start = pos, + endpos = pos - Vector(0, 0, self.TraceLength), + filter = self, + }) + if tr.HitWorld then + ParticleEffect(self.Effect,pos,(self.AngEffect and angle_dif or angle_reg),nil) + else + ParticleEffect(self.EffectAir,pos,(self.AngEffect and angle_dif or angle_reg),nil) + end + end + + if self.HEAT then + local heat = {} + heat.Src = self:GetPos() + heat.Dir = self:GetAngles():Forward() + heat.Spread = Vector(0,0,0) + heat.Force = self.ArmorPenetration+math.Rand(-self.ArmorPenetration/10,self.ArmorPenetration/10) + heat.HullSize = self.HEATRadius or 0 + heat.Damage = self.PenetrationDamage+math.Rand(-self.PenetrationDamage/5,self.PenetrationDamage/5) + heat.Velocity = 10000 + heat.Attacker = self.Owner + LVS:FireBullet( heat ) + + --Blast wave + if self.ExplosionRadius > 0 then + util.BlastDamage( self, ((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()), pos, self.ExplosionRadius,self.ExplosionDamage ) + self:BlastDoors(self, pos, 10) + end + + --Frags + if self.FragCount > 0 then + self:Fragmentation(self,pos,10000,self.FragDamage,self.FragRadius,((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld())) + end + + debugoverlay.Text(pos+Vector(0,0,10),"Penetration: "..heat.Force,5) + debugoverlay.Text(pos-Vector(0,0,10),"Damage: "..heat.Damage,5) + debugoverlay.Sphere(pos,self.ExplosionRadius,5,Color(200,150,0,100),false) + debugoverlay.Sphere(pos,self.FragRadius,5,Color(200,200,0,100),false) + else + debugoverlay.Sphere(pos,self.ExplosionRadius,5,Color(255,0,0,100),false) + debugoverlay.Sphere(pos,self.BlastRadius,5,Color(200,150,0,100),false) + debugoverlay.Sphere(pos,self.FragRadius,5,Color(200,200,0,100),false) + + --Deadly zone + if self.ExplosionRadius > 0 then + for k, v in pairs(ents.FindInSphere(pos,self.ExplosionRadius)) do + if v:IsValid() and not v.SWBombV3 then + local dmg = DamageInfo() + dmg:SetInflictor(self) + dmg:SetDamage(self.ExplosionDamage) + dmg:SetDamageType(self.DamageType) + dmg:SetAttacker((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()) + v:TakeDamageInfo(dmg) + end + end + end + + --Blast wave + if self.BlastRadius > 0 then + util.BlastDamage( self, ((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()), pos, self.BlastRadius,self.ExplosionDamage/2 ) + self:BlastDoors(self, pos, 10) + end + + --Frags + if self.FragCount > 0 then + self:Fragmentation(self,pos,10000,self.FragDamage,self.FragRadius,((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld())) + end + end + + swv3.CreateSound(pos,false,self.ExplosionSound,self.FarExplosionSound,self.DistExplosionSound) + timer.Simple(0,function() + if !IsValid(self) then return end + self:Remove() + end) + end + + --Impact + function ENT:PhysicsCollide( data, physobj ) + if self:GetVelocity():Length() >= self.ImpactSpeed then + if self.Armed then + self.Exploded = true + self:Explode(self:GetPos()) + else + if !self.Armed and !self.Fired then + self:EmitSound(self.ArmSound) + self.Armed = true + self:Launch() + end + end + end + end + + --Door blasting + function ENT:IsDoor(ent) + local Class = ent:GetClass() + + return (Class == "prop_door") or (Class == "prop_door_rotating") or (Class == "func_door") or (Class == "func_door_rotating") + end + function ENT:BlastDoors(blaster, pos, power, range, ignoreVisChecks) + for k, door in pairs(ents.FindInSphere(pos, 40 * power * (range or 1))) do + if self:IsDoor(door) then + local proceed = ignoreVisChecks + + if not proceed then + local tr = util.QuickTrace(pos, door:LocalToWorld(door:OBBCenter()) - pos, blaster) + proceed = IsValid(tr.Entity) and (tr.Entity == door) + end + + if proceed then + self:BlastDoor(door, (door:LocalToWorld(door:OBBCenter()) - pos):GetNormalized() * 1000) + end + end + if door:GetClass() == "func_breakable_surf" then + door:Fire("Break") + end + end + end + function ENT:BlastDoor(ent, vel) + local Moddel, Pozishun, Ayngul, Muteeriul, Skin = ent:GetModel(), ent:GetPos(), ent:GetAngles(), ent:GetMaterial(), ent:GetSkin() + sound.Play("Wood_Crate.Break", Pozishun, 60, 100) + sound.Play("Wood_Furniture.Break", Pozishun, 60, 100) + ent:Fire("unlock", "", 0) + ent:Fire("open", "", 0) + ent:SetNoDraw(true) + ent:SetNotSolid(true) + + if Moddel and Pozishun and Ayngul then + local Replacement = ents.Create("prop_physics") + Replacement:SetModel(Moddel) + Replacement:SetPos(Pozishun + Vector(0, 0, 1)) + Replacement:SetAngles(Ayngul) + + if Muteeriul then + Replacement:SetMaterial(Muteeriul) + end + + if Skin then + Replacement:SetSkin(Skin) + end + + Replacement:SetModelScale(.9, 0) + Replacement:Spawn() + Replacement:Activate() + + if vel then + Replacement:GetPhysicsObject():SetVelocity(vel) + + timer.Simple(0, function() + if IsValid(Replacement) then + Replacement:GetPhysicsObject():ApplyForceCenter(vel * 100) + end + end) + end + + timer.Simple(3, function() + if IsValid(Replacement) then + Replacement:SetCollisionGroup(COLLISION_GROUP_WEAPON) + end + end) + + timer.Simple(30, function() + if IsValid(ent) then + ent:SetNotSolid(false) + ent:SetNoDraw(false) + end + + if IsValid(Replacement) then + Replacement:Remove() + end + end) + end + end + + --Frags + function ENT:Fragmentation(shooter, origin, fragNum, fragDmg, fragMaxDist, attacker, direction, spread, zReduction) + shooter = shooter or game.GetWorld() + zReduction = zReduction or 2 + + local Spred = Vector(0, 0, 0) + local BulletsFired, MaxBullets, disperseTime = 0, self.FragCount, .5 + + if fragNum >= 12000 then + disperseTime = 2 + elseif fragNum >= 6000 then + disperseTime = 1 + end + + for i = 1, fragNum do + timer.Simple((i / fragNum) * disperseTime, function() + local Dir + + if direction and spread then + Dir = Vector(direction.x, direction.y, direction.z) + Dir = Dir + VectorRand() * math.Rand(0, spread) + Dir:Normalize() + else + Dir = VectorRand() + end + + if zReduction then + Dir.z = Dir.z / zReduction + Dir:Normalize() + end + + local Tr = util.QuickTrace(origin, Dir * fragMaxDist, shooter) + + if Tr.Hit and not Tr.HitSky and not Tr.HitWorld and (BulletsFired < MaxBullets) then + local LowFrag = (Tr.Entity.IsVehicle and Tr.Entity:IsVehicle()) or Tr.Entity.LFS or Tr.Entity.LVS or Tr.Entity.EZlowFragPlease + + if (not LowFrag) or (LowFrag and math.random(1, 4) == 2) then + + local firer = (IsValid(shooter) and shooter) or game.GetWorld() + + firer:FireBullets({ + Attacker = attacker, + Damage = fragDmg, + Force = fragDmg / 8, + Num = 1, + Src = origin, + Tracer = 0, + Dir = Dir, + Spread = Spred, + AmmoType = "Buckshot" + }) + BulletsFired = BulletsFired + 1 + end + end + end) + end + end +end + +if CLIENT then + function ENT:Initialize() + self.snd = CreateSound(self, self.WhistleSound) + self.snd:SetSoundLevel( 110 ) + self.snd:PlayEx(0,150) + + self.EngSND = CreateSound( self, self.EngineSound ) + end + function ENT:CalcDoppler() + local Ent = LocalPlayer() + local ViewEnt = Ent:GetViewEntity() + + local sVel = self:GetVelocity() + local oVel = Ent:GetVelocity() + local SubVel = oVel - sVel + local SubPos = self:GetPos() - Ent:GetPos() + + local DirPos = SubPos:GetNormalized() + local DirVel = SubVel:GetNormalized() + local A = math.acos( math.Clamp( DirVel:Dot( DirPos ) ,-1,1) ) + return (1 + math.cos( A ) * SubVel:Length() / 13503.9) + end + function ENT:Think() + if self.snd then + self.snd:ChangePitch( 100 * self:CalcDoppler(), 1 ) + self.snd:ChangeVolume(math.Clamp(-(self:GetVelocity().z + 1000) / 3000,0,1), 2) + end + if self.Fired then + self.EngSND:Play() + end + if self:GetNWBool("Fired",false) and not self:GetNWBool("IsPlayerHolding",true) then + if self:GetTracerScale() then + if self.Tracer1Att and not self.TracerEffect then + self.Tracer = Material("sprites/animglow02") + self.Tracer:SetVector("$color", Vector(255,0,0)) + if IsValid(self.Emitter) then + local particle + for i = 0,1 do + local ID = self:LookupAttachment( self.Tracer1Att ) + local Attachment = self:GetAttachment( ID ) + particle = self.Emitter:Add(self.Tracer,Attachment.Pos) + if particle then + particle:SetVelocity(self:GetVelocity()) + particle:SetDieTime(0.015) + particle:SetAirResistance(0) + particle:SetStartAlpha(255) + particle:SetStartSize(self:GetTracerScale()*2) + particle:SetEndSize(self:GetTracerScale()*2) + particle:SetRoll(math.Rand(-1,1)) + particle:SetGravity(Vector(0,0,0)) + particle:SetCollide(false) + end + end + else + self.Emitter = ParticleEmitter(self:GetPos(),false) + end + end + if self.Tracer2Att and not self.TracerEffect then + self.Tracer = Material("sprites/animglow02") + self.Tracer:SetVector("$color", Vector(255,0,0)) + if IsValid(self.Emitter) then + local particle + for i = 0,1 do + local ID = self:LookupAttachment( self.Tracer2Att ) + local Attachment = self:GetAttachment( ID ) + particle = self.Emitter:Add(self.Tracer,Attachment.Pos) + if particle then + particle:SetVelocity(self:GetVelocity()) + particle:SetDieTime(0.015) + particle:SetAirResistance(0) + particle:SetStartAlpha(255) + particle:SetStartSize(self:GetTracerScale()*2) + particle:SetEndSize(self:GetTracerScale()*2) + particle:SetRoll(math.Rand(-1,1)) + particle:SetGravity(Vector(0,0,0)) + particle:SetCollide(false) + end + end + else + self.Emitter = ParticleEmitter(self:GetPos(),false) + end + end + if not self.Tracer1Att and not self.Tracer2Att then + self.Tracer = Material("sprites/animglow02") + self.Tracer:SetVector("$color", Vector(255,0,0)) + if IsValid(self.Emitter) then + local particle + for i = 0,1 do + particle = self.Emitter:Add(self.Tracer,self:GetPos()) + if particle then + particle:SetVelocity(self:GetVelocity()) + particle:SetDieTime(0.015) + particle:SetAirResistance(0) + particle:SetStartAlpha(255) + particle:SetStartSize(self:GetTracerScale()*2) + particle:SetEndSize(self:GetTracerScale()*2) + particle:SetRoll(math.Rand(-1,1)) + particle:SetGravity(Vector(0,0,0)) + particle:SetCollide(false) + end + end + else + self.Emitter = ParticleEmitter(self:GetPos(),false) + end + end + end + end + end + function ENT:Draw() + self:DrawModel() + end + function ENT:SoundStop() + if self.snd then + self.snd:Stop() + end + end + function ENT:OnRemove() + self:SoundStop() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_base_shell_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_base_shell_v3.lua new file mode 100644 index 0000000..edac28f --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_base_shell_v3.lua @@ -0,0 +1,108 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Shell basescript V3" +ENT.Author = "Shermann Wolf" +ENT.Category = "SW Bombs V3" + +ENT.Spawnable = false +ENT.AdminOnly = false + +if SERVER then + function ENT:SetDamage( num ) self._dmg = num end + function ENT:SetRadius( num ) self._radius = num end + function ENT:SetAttacker( ent ) self._attacker = ent end + + function ENT:GetAttacker() return self._attacker or NULL end + function ENT:GetDamage() return (self._dmg or 250) end + function ENT:GetRadius() return (self._radius or 250) end + + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal * 5 ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:Initialize() + self:SetModel( "models/Items/grenadeAmmo.mdl" ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + + self.TrailEntity = util.SpriteTrail( self, 0, Color(120,120,120,120), false, 5, 40, 0.2, 1 / ( 15 + 1 ) * 0.5, "trails/smoke" ) + end + + function ENT:Think() + self:NextThink( CurTime() ) + + if self.Active then + self:Detonate() + end + + return true + end + + function ENT:Detonate() + if self.IsExploded then return end + + self.IsExploded = true + + local Pos = self:GetPos() + + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + + if self:WaterLevel() >= 2 then + util.Effect( "WaterSurfaceExplosion", effectdata, true, true ) + else + util.Effect( "lvs_defence_explosion", effectdata ) + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage( self:GetDamage() ) + dmginfo:SetAttacker( IsValid( self:GetAttacker() ) and self:GetAttacker() or self ) + dmginfo:SetDamageType( DMG_BULLET ) + dmginfo:SetInflictor( self ) + dmginfo:SetDamagePosition( Pos ) + + util.BlastDamageInfo( dmginfo, Pos, self:GetRadius() ) + + self:Remove() + end + + function ENT:PhysicsCollide( data, physobj ) + self.Active = true + + if data.Speed > 60 and data.DeltaTime > 0.2 then + local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length() + + if VelDif > 200 then + self:EmitSound( "Grenade.ImpactHard" ) + else + self:EmitSound( "Grenade.ImpactSoft" ) + end + + physobj:SetVelocity( data.OurOldVelocity * 0.5 ) + end + end +else + function ENT:Draw() + self:DrawModel() + end + + function ENT:Think() + return false + end + + function ENT:OnRemove() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_base_torpedo_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_base_torpedo_v3.lua new file mode 100644 index 0000000..2d12d0e --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_base_torpedo_v3.lua @@ -0,0 +1,397 @@ +AddCSLuaFile() + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +sound.Add( { + name = "SW_TORP_ENG_LOOP", + channel = CHAN_AUTO, + volume = 1.0, + level = 70, + sound = "sw/torpedoes/torpedo_run.wav" +} ) + +--Main info +ENT.Type = "anim" +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "Torpedo basescript V3" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3" +ENT.SWBombV3 = true +ENT.IsRocket = true + +--Effects +ENT.Effect = "gw_250lb_explosion" +ENT.EffectAir = "gw_250lb_explosion" +ENT.EffectWater = "ins_water_explosion" + +--Sounds +ENT.ArmSound = "sw/bomb/arm.wav" +ENT.StartSound = "sw/torpedoes/torpedo_launch_1.wav" +ENT.EngineSound = "SW_TORP_ENG_LOOP" +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Visual +ENT.Model = "models/sw/rus/torpedoes/45_36.mdl" + +--Physics +ENT.ImpactSpeed = 500 +ENT.Mass = 100 +ENT.MaxVelocity = 1000 +ENT.WaterThrustForce = 1500 +ENT.RotationalForce = 0 +ENT.Buoyancy = 0.15 +ENT.FuelBurnoutTime = 20 +ENT.Agility = 100 + + +--Explosion +ENT.DamageType = DMG_BURN +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 585.9375 +ENT.BlastRadius = 878.90625 + +if SERVER then + local angle_reg = Angle(0,0,0) + local angle_dif = Angle(-90,0,0) + local Normal = Vector(1,1,0) + local Normal2 = Vector(0.1,1,1) + local trlength = Vector(0,0,9000) + local angle_zero = Angle() + local angle_1 = Angle(-90,0,0) + + --Spawn + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + return ent + end + function ENT:Initialize() + self:SetModel(self.Model) + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:PhysWake() + local pObj = self:GetPhysicsObject() + pObj:SetMass( self.Mass ) + pObj:EnableGravity( true ) + pObj:EnableMotion( true ) + pObj:EnableDrag( false ) + self.Armed = false + self.Fired = false + self.Exploded = false + self.MaxVelocityUnitsSquared = self.MaxVelocity and self:ConvertMetersToUnits(self.MaxVelocity^2) or nil + pObj:SetBuoyancyRatio(self.Buoyancy) + end + + --Arming + function ENT:Use( activator, caller ) + if !self.Exploded and !self.Armed then + self:EmitSound(self.ArmSound) + self.Armed = true + self:Launch() + end + end + function ENT:Launch() + if self.Exploded then return end + + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + + local phys = self:GetPhysicsObject() + + if !IsValid(phys) then return end + + self.ImpactSpeed = 10 + + self.Fired = true + self:SetNWBool("Fired",true) + + constraint.RemoveAll(self) + + phys:Wake() + phys:EnableMotion(true) + + self:InitLaunch(phys) + end + function ENT:InitLaunch(phys) + self:EmitSound(self.StartSound) + phys:AddAngleVelocity(Vector(self.RotationalForce or 0,0,0)) + if self.TorpedoTrail then + timer.Simple(0,function() + if !IsValid(self) then return end + + if self.Engine1Att then + local ID = self:LookupAttachment( self.Engine1Att ) + ParticleEffectAttach(self.TorpedoTrail,PATTACH_POINT_FOLLOW,self,ID) + end + if self.Engine2Att then + local ID = self:LookupAttachment( self.Engine2Att ) + ParticleEffectAttach(self.TorpedoTrail,PATTACH_POINT_FOLLOW,self,ID) + end + if not self.Engine1Att and not self.Engine2Att then + ParticleEffectAttach(self.TorpedoTrail,PATTACH_ABSORIGIN_FOLLOW,self,1) + end + end) + end + + if self.FuelBurnoutTime and self.FuelBurnoutTime != 0 then + timer.Simple(self.FuelBurnoutTime,function() + if !IsValid(self) then return end + self.Exploded = true + self:Explode(self:GetPos()) + end) + end + end + + --Move + function ENT:ConvertMetersToUnits(Meters) + return Meters / 0.01905 + end + function ENT:ConvertUnitsToMeters(Units) + return Units * 0.01905 + end + function ENT:Think() + if self.Fired then + if self:WaterLevel() >= 1 then + local VelForward = self:GetForward() + local VelForward_Norm = Vector(VelForward:GetNormalized().x,VelForward:GetNormalized().y,0) + if self.MaxVelocityUnitsSquared and self:GetPhysicsObject():GetVelocity():LengthSqr() < self.MaxVelocityUnitsSquared then + self:GetPhysicsObject():AddVelocity(VelForward_Norm*(self.WaterThrustForce)) + end + + if self.GuidanceActive then + if self.target != NULL then + local ph = self:GetPhysicsObject() + if IsValid(self.target) or self.target:GetClass() == "worldspawn" then + if not self.Armed or self.InEntity then return end + local pos = self:GetPos() + local vel = self:WorldToLocal(pos+ph:GetVelocity())*0.4 + vel.x = 0 + local target = self.target:LocalToWorld(self.targetOffset) + local v = self:WorldToLocal(target + Vector(0,0,math.Clamp((pos*Normal):Distance(target*Normal)/5 - 0,0,0 or 0))):GetNormal() + v.y = math.Clamp(v.y*10,-1,1)*(self.Agility or 10) + v.z = math.Clamp(v.z*10,-1,1)*(self.Agility or 10) + ph:AddAngleVelocity( + ph:GetAngleVelocity()*-0.1 + + Vector(0,0,0) + + Vector(0,-vel.z,vel.y) + + Vector(0,-v.z,v.y) + ) + ph:AddVelocity(VelForward_Norm - self:LocalToWorld(vel*Normal2) + pos) + end + end + end + end + else + if self.Armed then + if self:WaterLevel() >= 1 then + self:Launch() + end + end + end + end + + --Explosion + function ENT:Explode(pos) + if !self.Exploded then return end + if !self.Armed then return end + pos = self:LocalToWorld(self:OBBCenter()) + if(self:WaterLevel() >= 1) then + local tr = util.TraceLine({ + start = pos, + endpos = pos + Vector(0,0,90000), + filter = self, + }) + local tr2 = util.TraceLine({ + start = tr.HitPos, + endpos = tr.HitPos - Vector(0,0,90000), + filter = self, + mask = MASK_WATER + CONTENTS_TRANSLUCENT, + }) + if tr2.Hit then + ParticleEffect(self.EffectWater, tr2.HitPos,(self.AngEffect and angle_dif or angle_reg), nil) + end + else + local tr = util.TraceLine({ + start = pos, + endpos = pos - Vector(0, 0, self.TraceLength), + filter = self, + }) + if tr.HitWorld then + ParticleEffect(self.Effect,pos,(self.AngEffect and angle_dif or angle_reg),nil) + else + ParticleEffect(self.EffectAir,pos,(self.AngEffect and angle_dif or angle_reg),nil) + end + end + debugoverlay.Sphere(pos,self.ExplosionRadius,5,Color(255,0,0,100),false) + debugoverlay.Sphere(pos,self.BlastRadius,5,Color(200,150,0,100),false) + + --Deadly zone + if self.ExplosionRadius > 0 then + for k, v in pairs(ents.FindInSphere(pos,self.ExplosionRadius)) do + if v:IsValid() and not v.SWBombV3 then + local dmg = DamageInfo() + dmg:SetInflictor(self) + dmg:SetDamage(self.ExplosionDamage) + dmg:SetDamageType(self.DamageType) + dmg:SetAttacker((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()) + v:TakeDamageInfo(dmg) + end + end + end + + --Blast wave + if self.BlastRadius > 0 then + util.BlastDamage( self, ((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()), pos, self.BlastRadius,self.ExplosionDamage/2 ) + end + + if self:WaterLevel() >= 1 then + swv3.CreateSound(pos,false,self.WaterExplosionSound,self.WaterFarExplosionSound,self.DistExplosionSound) + else + swv3.CreateSound(pos,false,self.ExplosionSound,self.FarExplosionSound,self.DistExplosionSound) + end + timer.Simple(0,function() + if !IsValid(self) then return end + self:Remove() + end) + end + + --Impact + function ENT:PhysicsCollide( data, physobj ) + if self:GetVelocity():Length() >= self.ImpactSpeed then + if self:GetNWBool("Fired") == true then + self.Exploded = true + self:Explode(self:GetPos()) + else + if !self.Armed and !self.Fired then + self:EmitSound(self.ArmSound) + self.Armed = true + self:Launch() + end + end + end + end +end + +if CLIENT then + function ENT:Initialize() + self.EngSND = CreateSound( self, self.EngineSound ) + end + function ENT:Think() + if self:GetNWBool("Fired",false) then + if self:WaterLevel() >= 1 then + local traceWater = util.TraceLine( { + start = self:LocalToWorld(Vector(-25,0,20)), + endpos = self:LocalToWorld(Vector(-25,0,20)) - Vector(0,0,40), + filter = self, + mask = MASK_WATER, + } ) + + if traceWater.Hit then + local effectdata = EffectData() + effectdata:SetOrigin( traceWater.HitPos ) + effectdata:SetEntity( self ) + util.Effect( "sw_torpedo_v3", effectdata ) + end + + self.EngSND:Play() + else + if self.EngSND then + self.EngSND:Stop() + end + end + else + if self.EngSND then + self.EngSND:Stop() + end + end + end + function ENT:GetParticleEmitter( Pos ) + local EntTable = self:GetTable() + + local T = CurTime() + + if IsValid( EntTable.Emitter ) and (EntTable.EmitterTime or 0) > T then + return EntTable.Emitter + end + + self:StopEmitter() + + EntTable.Emitter = ParticleEmitter( Pos, false ) + EntTable.EmitterTime = T + 2 + + return EntTable.Emitter + end + function ENT:StopEmitter() + if IsValid( self.Emitter ) then + self.Emitter:Finish() + end + end + function ENT:Draw() + self:DrawModel() + end + function ENT:SoundStop() + if self.EngSND then + self.EngSND:Stop() + end + end + function ENT:OnRemove() + self:SoundStop() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab100_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab100_v3.lua new file mode 100644 index 0000000..0ef8931 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab100_v3.lua @@ -0,0 +1,118 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_sml_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_sml_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_sml_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_sml_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_sml_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_sml_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_sml_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_sml_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "ФАБ-100" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/bombs/fab100.mdl" +ENT.Effect = "ins_c4_explosion" +ENT.EffectAir = "ins_c4_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_100kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 50 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 25 +ENT.Mass = 100 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 468.75 +ENT.BlastRadius = 703.125 +ENT.FragDamage = 25 +ENT.FragRadius = 937.5 +ENT.FragCount = 0 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab250m62_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab250m62_v3.lua new file mode 100644 index 0000000..367eff2 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab250m62_v3.lua @@ -0,0 +1,118 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_med_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_med_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_med_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_med_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_med_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_med_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_med_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_med_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_med_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_med_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_med_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_med_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_med_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_med_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_med_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_med_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_med_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_med_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_med_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_med_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_med_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_med_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_med_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_med_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "ФАБ-250М62" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/bombs/fab250m62.mdl" +ENT.Effect = "gw_250lb_explosion" +ENT.EffectAir = "gw_250lb_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_250kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 60 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 40 +ENT.Mass = 250 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 585.9375 +ENT.BlastRadius = 878.90625 +ENT.FragDamage = 25 +ENT.FragRadius = 1171.875 +ENT.FragCount = 0 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab500m62_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab500m62_v3.lua new file mode 100644 index 0000000..adf90a0 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab500m62_v3.lua @@ -0,0 +1,118 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_hvy_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_hvy_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_hvy_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_hvy_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_hvy_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_hvy_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_hvy_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_hvy_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_hvy_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_hvy_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_hvy_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_hvy_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_hvy_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_hvy_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_hvy_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_hvy_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_hvy_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_hvy_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_hvy_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_hvy_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_hvy_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_hvy_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_hvy_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_hvy_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "ФАБ-500М62" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/bombs/fab500m62.mdl" +ENT.Effect = "gw_500lb_explosion" +ENT.EffectAir = "gw_500lb_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_500kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 65 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 40 +ENT.Mass = 500 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 732.421875 +ENT.BlastRadius = 1098.6328125 +ENT.FragDamage = 25 +ENT.FragRadius = 1464.84375 +ENT.FragCount = 0 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab50_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab50_v3.lua new file mode 100644 index 0000000..c959324 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_fab50_v3.lua @@ -0,0 +1,113 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "ФАБ-50" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/bombs/fab50.mdl" +ENT.Effect = "ins_rpg_explosion" +ENT.EffectAir = "ins_rpg_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_50kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 30 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 25 +ENT.Mass = 50 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 5000 +ENT.ExplosionRadius = 150 +ENT.BlastRadius = 562.5 +ENT.FragDamage = 25 +ENT.FragRadius = 750 +ENT.FragCount = 0 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_gbu12_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_gbu12_v3.lua new file mode 100644 index 0000000..9454c4a --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_gbu12_v3.lua @@ -0,0 +1,143 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_med_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_med_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_med_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_med_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_med_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_med_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_med_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_med_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_med_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_med_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_med_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_med_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_med_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_med_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_med_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_med_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_med_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_med_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_med_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_med_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_med_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_med_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_med_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_med_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "GBU-12" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | USA" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/usa/bombs/guided/gbu12.mdl" +ENT.Effect = "gw_250lb_explosion" +ENT.EffectAir = "gw_250lb_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_250kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 60 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 25 +ENT.Mass = 250 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 585.9375 +ENT.BlastRadius = 878.90625 +ENT.FragDamage = 25 +ENT.FragRadius = 1171.875 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.LaserGuided = true +ENT.LaserCode = "view" +ENT.Agility = 50 + +function ENT:OnTick() + if self.Armed then + local Phys=self:GetPhysicsObject() + local Vel=Phys:GetVelocity() + local Spd=Vel:Length() + if Spd > 150 and not self:IsPlayerHolding() and not constraint.HasConstraints(self) then + self.FreefallTicks=self.FreefallTicks+1 + if self.FreefallTicks >= 15 and not self.WingsOpen then + self.WingsOpen = true + self.ImpactSpeed = 50 + self.TraceLength = 150 + self:SetBodygroup(1,1) + end + else + self.FreefallTicks=0 + end + end +end + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_gbu39_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_gbu39_v3.lua new file mode 100644 index 0000000..b1aa1bd --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_gbu39_v3.lua @@ -0,0 +1,141 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_med_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_med_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_med_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_med_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_med_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_med_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_med_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_med_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_med_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_med_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_med_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_med_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_med_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_med_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_med_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_med_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_med_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_med_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_med_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_med_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_med_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_med_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_med_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_med_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "GBU-39" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | USA" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/usa/bombs/guided/gbu39.mdl" +ENT.Effect = "gw_250lb_explosion" +ENT.EffectAir = "gw_250lb_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_250kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 60 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 25 +ENT.Mass = 250 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 585.9375 +ENT.BlastRadius = 878.90625 +ENT.FragDamage = 25 +ENT.FragRadius = 1171.875 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 50 + +function ENT:OnTick() + if self.Armed then + local Phys=self:GetPhysicsObject() + local Vel=Phys:GetVelocity() + local Spd=Vel:Length() + if Spd > 150 and not self:IsPlayerHolding() and not constraint.HasConstraints(self) then + self.FreefallTicks=self.FreefallTicks+1 + if self.FreefallTicks >= 15 and not self.WingsOpen then + self.WingsOpen = true + self.ImpactSpeed = 50 + self.TraceLength = 150 + self:SetBodygroup(1,1) + end + else + self.FreefallTicks=0 + end + end +end + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_kab500kr_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_kab500kr_v3.lua new file mode 100644 index 0000000..9ac180d --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_kab500kr_v3.lua @@ -0,0 +1,122 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_hvy_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_hvy_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_hvy_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_hvy_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_hvy_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_hvy_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_hvy_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_hvy_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_hvy_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_hvy_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_hvy_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_hvy_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_hvy_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_hvy_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_hvy_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_hvy_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_hvy_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_hvy_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_hvy_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_hvy_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_hvy_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_hvy_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_hvy_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_hvy_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "КАБ-500КР" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/bombs/guided/kab500kr.mdl" +ENT.Effect = "gw_500lb_explosion" +ENT.EffectAir = "gw_500lb_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_500kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 65 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 40 +ENT.Mass = 500 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 732.421875 +ENT.BlastRadius = 1098.6328125 +ENT.FragDamage = 25 +ENT.FragRadius = 1464.84375 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 50 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_bomb_mk82_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_bomb_mk82_v3.lua new file mode 100644 index 0000000..6d0aa96 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_bomb_mk82_v3.lua @@ -0,0 +1,118 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_med_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_med_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_med_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_med_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_med_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_med_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_med_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_med_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_med_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_med_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_med_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_med_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_med_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_med_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_med_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_med_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_med_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_med_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_med_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_med_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_med_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_med_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_med_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_med_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "MK82" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | USA" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/usa/bombs/mk82.mdl" +ENT.Effect = "gw_250lb_explosion" +ENT.EffectAir = "gw_250lb_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_250kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 60 +ENT.ImpactSpeed = 150 +ENT.ImpactDepth = 25 +ENT.Mass = 250 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 585.9375 +ENT.BlastRadius = 878.90625 +ENT.FragDamage = 25 +ENT.FragRadius = 1171.875 +ENT.FragCount = 0 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_flare_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_flare_v3.lua new file mode 100644 index 0000000..26822b4 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_flare_v3.lua @@ -0,0 +1,173 @@ +AddCSLuaFile() +ENT.Type = "anim" +ENT.SWBombV3 = true +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.PrintName = "Flare" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.Model = "models/sw/shared/flare.mdl" + +function ENT:Initialize() + self:SetModel( self.Model) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + local pObj = self:GetPhysicsObject() + pObj:SetMass( 1 ) + pObj:EnableGravity( true ) + pObj:EnableMotion( true ) + pObj:EnableDrag( true ) + pObj:SetDragCoefficient(0) + pObj:Wake() + timer.Simple(3,function() + if IsValid(self) then + self:Remove() + end + end) +end +function ENT:Think() + for k,v in pairs( ents.FindInSphere( self:GetPos(), math.random(1500,2000) ) ) do + if( IsValid( v ) && IsValid( v.Target ) ) && !string.EndsWith(tostring(v.Target), "gtav_cm_flare]") then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + } ) + if tr.Hit and tr.Entity == v then + v.Target = self + if IsFirstTimePredicted() then + timer.Simple(2,function() + if IsValid(v) then + v:Detonate() + end + end) + end + end + end + if ( IsValid(v) ) and v.IsRocket and (v.JDAM or v.GuidanceActive) then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + } ) + if tr.Hit and tr.Entity == v then + self:SetNWEntity("TarRocket",v) + v.target = (self) + if v.lg == true then + v.lg = false + v.target = (self) + end + if v.LaserGuided == true then + v.LaserGuided = false + v.target = (self) + end + end + end + if ( IsValid(v) ) and v:GetClass() == "lunasflightschool_missile" and IsValid(v:GetLockOn()) then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + } ) + if tr.Hit and tr.Entity == v then + v:SetLockOn( self ) + if IsFirstTimePredicted() then + timer.Simple(4,function() + if IsValid(v) then + v:Detonate() + end + end) + end + end + end + if ( IsValid(v) ) and v:GetClass() == "lvs_missile" and IsValid(v:GetNWTarget()) then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + } ) + if tr.Hit and tr.Entity == v then + v:SetNWTarget( self ) + end + end + if ( IsValid(v) ) and v:GetClass() == "dronesrewrite_rocketbig" then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + } ) + if tr.Hit and tr.Entity == v then + v.Enemy = self + if IsFirstTimePredicted() then + timer.Simple(1,function() + if IsValid(v) then + v:Boom() + end + end) + end + end + end + if ( IsValid(v) ) and v:GetClass() == "rpg_missile" then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + } ) + if tr.Hit and tr.Entity == v then + local d = DamageInfo() + d:SetDamage( 100 ) + d:SetAttacker(self) + d:SetDamageType( DMG_MISSILEDEFENSE ) + v:TakeDamageInfo( d ) + end + end + end +end + +if CLIENT then + local emitter = ParticleEmitter(Vector(0, 0, 0)) + function ENT:Initialize() + self.lifetime = RealTime() + self.cooltime = CurTime() + end + function ENT:Draw() + self:DrawModel() + end + function ENT:Think() + local dist = 0 + if (self.cooltime < CurTime()) then + local smoke = emitter:Add("effects/smoke_a", self:GetPos() + self:GetForward()*-dist) + smoke:SetVelocity(self:GetForward()*-10) + smoke:SetDieTime(math.Rand(1,3.5)) + smoke:SetStartAlpha(150) + smoke:SetEndAlpha(0) + smoke:SetStartSize(90) + smoke:SetEndSize(30) + smoke:SetRoll(math.Rand(180,480)) + smoke:SetRollDelta(math.Rand(-4,2)) + smoke:SetGravity( Vector( 0, math.random(1,90), math.random(151,355) ) ) + smoke:SetColor( 135,135, 135 ) + smoke:SetAirResistance(50) + local fire = emitter:Add("effects/yellowflare", self:GetPos() + self:GetForward()*-dist) + fire:SetVelocity(self:GetForward()*-10) + fire:SetDieTime(math.Rand(.25,.35)) + fire:SetStartAlpha(250) + fire:SetEndAlpha(250) + fire:SetStartSize(150) + fire:SetEndSize(50) + fire:SetAirResistance(150) + fire:SetRoll(math.Rand(180,480)) + fire:SetRollDelta(math.Rand(-3,3)) + fire:SetColor(220,150,0) + self.cooltime = CurTime() + .0001 + end + end +end +function ENT:OnRemove() + if IsValid(self:GetNWEntity("TarRocket")) then + self:GetNWEntity("TarRocket").JDAM = false + self:GetNWEntity("TarRocket").GuidanceActive = false + end +end +function ENT:PhysicsUpdate() +end +function ENT:PhysicsCollide() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_heli_wheel_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_heli_wheel_v3.lua new file mode 100644 index 0000000..ed9bfba --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_heli_wheel_v3.lua @@ -0,0 +1,108 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/props_vehicles/tire001c_car.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:DrawShadow( false ) + self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ) + end + + function ENT:SetBrakes( active ) + if not self._CanUseBrakes then + actuve = false + end + + if active ~= self._BrakesActive then + self._BrakesActive = active + + if active then + self:StartMotionController() + else + self:StopMotionController() + end + end + end + + function ENT:SetBrakeForce( force ) + self._BrakeForce = force + end + + function ENT:GetBrakeForce() + return (self._BrakeForce or 60) + end + + function ENT:Define( data ) + local bbox = Vector(data.radius,data.radius,data.radius) + + self:PhysicsInitSphere( data.radius, data.physmat ) + self:SetCollisionBounds( -bbox, bbox ) + + local PhysObj = self:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:SetMass( data.mass ) + PhysObj:Wake() + PhysObj:SetBuoyancyRatio(0) + PhysObj:EnableGravity( false ) + end + + self._CanUseBrakes = data.brake + end + + function ENT:PhysicsSimulate( phys, deltatime ) + local BrakeForce = Vector( -phys:GetAngleVelocity().x, 0, 0 ) * self:GetBrakeForce() + + return BrakeForce, Vector(0,0,0), SIM_LOCAL_ACCELERATION + end + + function ENT:SetBase( ent ) + self._baseEnt = ent + end + + function ENT:GetBase() + return self._baseEnt + end + + function ENT:Use( ply ) + end + + function ENT:Think() + return false + end + + function ENT:OnRemove() + end + + function ENT:PhysicsCollide( data, physobj ) + end + + function ENT:OnTakeDamage( dmginfo ) + local base = self:GetBase() + + if not IsValid( base ) then return end + + base:TakeDamageInfo( dmginfo ) + end + + return +end + +function ENT:Initialize() +end + +function ENT:Think() +end + +function ENT:Draw() +end + +function ENT:OnRemove() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_item_refuel.lua b/garrysmod/addons/swbombs/lua/entities/sw_item_refuel.lua new file mode 100644 index 0000000..b8c4b14 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_item_refuel.lua @@ -0,0 +1,248 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "Aircraft refueler" +ENT.Author = "Luna" +ENT.Information = "Refills aircraft fuel tanks" +ENT.Category = "[LVS]" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.Editable = false + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "User" ) +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:Spawn() + ent:Activate() + + return ent + end + + function ENT:OnTakeDamage( dmginfo ) + end + + function ENT:Initialize() + self:SetModel( "models/props_wasteland/gaspump001a.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + local PhysObj = self:GetPhysicsObject() + + if not IsValid( PhysObj ) then return end + + PhysObj:EnableMotion( false ) + end + + function ENT:giveSWEP( ply ) + self:EmitSound("common/wpn_select.wav") + + ply:SetSuppressPickupNotices( true ) + ply:Give( "weapon_lvsaircraftfuelfiller" ) + ply:SetSuppressPickupNotices( false ) + + ply:SelectWeapon( "weapon_lvsaircraftfuelfiller" ) + self:SetUser( ply ) + + local SWEP = ply:GetWeapon( "weapon_lvsaircraftfuelfiller" ) + + if not IsValid( SWEP ) then return end + end + + function ENT:removeSWEP( ply ) + if ply:HasWeapon( "weapon_lvsaircraftfuelfiller" ) then + ply:StripWeapon( "weapon_lvsaircraftfuelfiller" ) + ply:SwitchToDefaultWeapon() + end + self:SetUser( NULL ) + end + + function ENT:checkSWEP( ply ) + if not ply:Alive() or ply:InVehicle() then + + self:removeSWEP( ply ) + + return + end + + local weapon = ply:GetActiveWeapon() + + if not IsValid( weapon ) or weapon:GetClass() ~= "weapon_lvsaircraftfuelfiller" then + self:removeSWEP( ply ) + + return + end + + if (ply:GetPos() - self:GetPos()):LengthSqr() < 150000 then return end + + self:removeSWEP( ply ) + end + + function ENT:Think() + local ply = self:GetUser() + local T = CurTime() + + if IsValid( ply ) then + self:checkSWEP( ply ) + + self:NextThink( T ) + else + self:NextThink( T + 0.5 ) + end + + return true + end + + function ENT:Use( ply ) + if not IsValid( ply ) or not ply:IsPlayer() then return end + + local User = self:GetUser() + + if IsValid( User ) then + if User == ply then + self:removeSWEP( ply ) + end + else + self:giveSWEP( ply ) + end + end + + function ENT:OnRemove() + local User = self:GetUser() + + if not IsValid( User ) then return end + + self:removeSWEP( User ) + end +end + +if CLIENT then + function ENT:CreatePumpEnt() + if IsValid( self.PumpEnt ) then return self.PumpEnt end + + self.PumpEnt = ents.CreateClientProp() + self.PumpEnt:SetModel( "models/props_equipment/gas_pump_p13.mdl" ) + self.PumpEnt:SetPos( self:LocalToWorld( Vector(-0.2,-14.6,45.7) ) ) + self.PumpEnt:SetAngles( self:LocalToWorldAngles( Angle(-0.3,92.3,-0.1) ) ) + self.PumpEnt:Spawn() + self.PumpEnt:Activate() + self.PumpEnt:SetParent( self ) + + return self.PumpEnt + end + + function ENT:RemovePumpEnt() + if not IsValid( self.PumpEnt ) then return end + + self.PumpEnt:Remove() + end + + function ENT:Think() + local PumpEnt = self:CreatePumpEnt() + + local ShouldDraw = IsValid( self:GetUser() ) + local Draw = PumpEnt:GetNoDraw() + + if Draw ~= ShouldDraw then + PumpEnt:SetNoDraw( ShouldDraw ) + end + end + + local cable = Material( "cable/cable2" ) + local function bezier(p0, p1, p2, p3, t) + local e = p0 + t * (p1 - p0) + local f = p1 + t * (p2 - p1) + local g = p2 + t * (p3 - p2) + + local h = e + t * (f - e) + local i = f + t * (g - f) + + local p = h + t * (i - h) + + return p + end + + ENT.FrameMat = Material( "lvs/3d2dmats/frame.png" ) + ENT.RefuelMat = Material( "lvs/3d2dmats/refuel.png" ) + + + function ENT:Draw() + self:DrawModel() + self:DrawCable() + + local ply = LocalPlayer() + local Pos = self:GetPos() + + if (ply:GetPos() - Pos):LengthSqr() > 5000000 then return end + + local IconColor = Color( 0, 255, 0, 255 ) + + cam.Start3D2D( self:LocalToWorld( Vector(10,0,45) ), self:LocalToWorldAngles( Angle(0,90,90) ), 0.1 ) + draw.NoTexture() + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawRect( -150, -120, 300, 240 ) + + surface.SetDrawColor( IconColor ) + + surface.SetMaterial( self.FrameMat ) + surface.DrawTexturedRect( -50, -50, 100, 100 ) + + surface.SetMaterial( self.RefuelMat ) + surface.DrawTexturedRect( -50, -50, 100, 100 ) + + draw.SimpleText( "Aircraft fuel", "LVS_FONT", 0, 75, IconColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + cam.End3D2D() + end + + function ENT:DrawCable() + if LocalPlayer():GetPos():DistToSqr( self:GetPos() ) > 350000 then return end + + local pos = self:LocalToWorld( Vector(10,0,45) ) + local ang = self:LocalToWorldAngles( Angle(0,90,90) ) + local ply = self:GetUser() + + local startPos = self:LocalToWorld( Vector(0.06,-17.77,55.48) ) + local p2 = self:LocalToWorld( Vector(8,-17.77,30) ) + local p3 + local endPos + + if IsValid( ply ) then + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + endPos = (attachment.Pos + attachment.Ang:Forward() * -3 + attachment.Ang:Right() * 2 + attachment.Ang:Up() * -3.5) + p3 = endPos + attachment.Ang:Right() * 5 - attachment.Ang:Up() * 20 + else + p3 = self:LocalToWorld( Vector(0,-20,30) ) + endPos = self:LocalToWorld( Vector(0.06,-20.3,37) ) + end + + render.StartBeam( 15 ) + render.SetMaterial( cable ) + + for i = 0,15 do + local pos = bezier(startPos, p2, p3, endPos, i / 14) + + local Col = (render.GetLightColor( pos ) * 0.8 + Vector(0.2,0.2,0.2)) * 255 + + render.AddBeam( pos, 1, 0, Color(Col.r,Col.g,Col.b,255) ) + end + + render.EndBeam() + end + + function ENT:OnRemove() + self:RemovePumpEnt() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_mine_tm62_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_mine_tm62_v3.lua new file mode 100644 index 0000000..daf47ad --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_mine_tm62_v3.lua @@ -0,0 +1,178 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_bomb_v3" ) + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_sml_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_sml_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_sml_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_sml_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_sml_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_sml_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_sml_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_sml_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "ТМ-62" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/bombs/tm62.mdl" +ENT.Effect = "ins_rpg_explosion" +ENT.EffectAir = "ins_rpg_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Decal = "scorch_1kg" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil + +--Physics +ENT.TraceLength = 50 +ENT.ImpactSpeed = 25 +ENT.ImpactDepth = 2 +ENT.Mass = 1 +ENT.Durability = 100 + +--Explosion +ENT.ExplosionDamage = 1000 +ENT.ExplosionRadius = 50 +ENT.BlastRadius = 200 +ENT.FragDamage = 150 +ENT.FragRadius = 300 +ENT.FragCount = 0 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end + +function ENT:Initialize() + self:SetModel(self.Model) + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetCollisionGroup( COLLISION_GROUP_NONE ) + self:PhysWake() + local pObj = self:GetPhysicsObject() + pObj:SetMass( self.Mass ) + pObj:EnableGravity( false ) + pObj:EnableMotion( true ) + pObj:EnableDrag( false ) + self:StartMotionController() + self:PhysWake() + self.InGround = false + self.InEntity = false + self.Exploded = false + self.Buried = false + self.Armed = false + self.CurDurability = self.Durability +end + +function ENT:PhysicsCollide( data, physobj ) +end + +function ENT:OnTick() + + if self.Armed and not self.Used then + timer.Simple(3, function() + if IsValid(self) then + self:SetPos(self:GetPos()-self:GetUp()*2) + self:GetPhysicsObject():EnableMotion(false) + self.Buried = true + end + end) + self.Used = true + end + + if self.Armed and self.Buried then + for k, v in pairs(ents.FindInSphere(self:LocalToWorld(self:OBBCenter()),self.ExplosionRadius/2)) do + if v:IsValid() and v:IsVehicle() then + timer.Simple(0.5,function() + if IsValid(self) then + self.Exploded = true + self:Explode(self:GetPos()) + end + end) + end + end + end +end + +function ENT:Touch( ent ) + if self.Armed and self.Buried then + if ent:IsVehicle() then + self.Exploded = true + self:Explode(self:GetPos()) + end + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/cl_init.lua b/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/cl_init.lua new file mode 100644 index 0000000..6207b7d --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/cl_init.lua @@ -0,0 +1,4 @@ +include("shared.lua") +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/init.lua b/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/init.lua new file mode 100644 index 0000000..bb7e8c5 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/init.lua @@ -0,0 +1,188 @@ +AddCSLuaFile("shared.lua") +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + ent.Owner = ply + return ent +end +function ENT:Initialize() + self:SetModel("models/sw/shared/backpack.mdl") + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:AddFlags( FL_OBJECT ) + local PObj = self:GetPhysicsObject() + if not IsValid( PObj ) then + self:Remove() + print("SW Bombs V2: Missing model. Entity terminated.") + return + end + self:PhysWake() +end + +function ENT:Think() + self:NextThink(CurTime()+0.05) + if self:GetAutoMode() then + if self:GetVelocity():Length() >= 500 then + if self:GetOpen() == false and not self:IsPlayerHolding() then + self:SetOpen(true) + self:EmitSound("sw/misc/chute_1.wav") + self.Chute1 = ents.Create("prop_physics") + self.Chute1:SetModel("models/sw/shared/chute_2.mdl") + self.Chute1:SetPos(self:GetPos()+self:GetRight()*250+self:GetUp()*25) + self.Chute1:SetAngles(self:GetAngles()) + self.Chute1:Spawn() + self.Chute1:Activate() + self.Chute1.Owner=self.Owner + self.Chute1:PhysWake() + self.Chute1:GetPhysicsObject():EnableDrag(true) + self.Chute1:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute1:GetPhysicsObject():SetMass(100) + local Wire1 = constraint.Rope(self,self.Chute1,0,0,Vector(0,0,0),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + + self.Chute2 = ents.Create("prop_physics") + self.Chute2:SetModel("models/sw/shared/chute_2.mdl") + self.Chute2:SetPos(self:GetPos()-self:GetRight()*250+self:GetUp()*25) + self.Chute2:SetAngles(self:GetAngles()) + self.Chute2:Spawn() + self.Chute2:Activate() + self.Chute2.Owner=self.Owner + self.Chute2:PhysWake() + self.Chute2:GetPhysicsObject():EnableDrag(true) + self.Chute2:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute2:GetPhysicsObject():SetMass(100) + local Wire2 = constraint.Rope(self,self.Chute2,0,0,Vector(0,0,0),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + + self.Chute3 = ents.Create("prop_physics") + self.Chute3:SetModel("models/sw/shared/chute_2.mdl") + self.Chute3:SetPos(self:GetPos()+self:GetForward()*250+self:GetUp()*25) + self.Chute3:SetAngles(self:GetAngles()) + self.Chute3:Spawn() + self.Chute3:Activate() + self.Chute3.Owner=self.Owner + self.Chute3:PhysWake() + self.Chute3:GetPhysicsObject():EnableDrag(true) + self.Chute3:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute3:GetPhysicsObject():SetMass(100) + local Wire3 = constraint.Rope(self,self.Chute3,0,0,Vector(0,0,0),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + end + else + if self:GetVelocity():Length() <= 10 then + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + timer.Simple(3,function() + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + if IsValid(self.Chute1) then + self.Chute1:Remove() + end + if IsValid(self.Chute2) then + self.Chute2:Remove() + end + if IsValid(self.Chute3) then + self.Chute3:Remove() + end + self:SetOpen(false) + end + end + end) + end + end + end + end + else + for _, e in pairs(ents.FindInSphere(self:GetPos(), 500)) do + if e == self.Owner then + if IsValid( e ) then + local Open = self:GetOpen() + OpenSwitch = self.Owner:KeyDown(IN_SCORE) + if self.OldOpenSwitch ~= OpenSwitch then + if OpenSwitch and Open == false then + self:SetOpen(true) + self:EmitSound("sw/misc/chute_1.wav") + self.Chute1 = ents.Create("prop_physics") + self.Chute1:SetModel("models/sw/shared/chute_2.mdl") + self.Chute1:SetPos(self:GetPos()+self:GetRight()*250+self:GetUp()*25) + self.Chute1:SetAngles(self:GetAngles()) + self.Chute1:Spawn() + self.Chute1:Activate() + self.Chute1.Owner=self.Owner + self.Chute1:PhysWake() + self.Chute1:GetPhysicsObject():EnableDrag(true) + self.Chute1:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute1:GetPhysicsObject():SetMass(100) + local Wire1 = constraint.Rope(self,self.Chute1,0,0,Vector(0,-15,10),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + + self.Chute2 = ents.Create("prop_physics") + self.Chute2:SetModel("models/sw/shared/chute_2.mdl") + self.Chute2:SetPos(self:GetPos()-self:GetRight()*250+self:GetUp()*25) + self.Chute2:SetAngles(self:GetAngles()) + self.Chute2:Spawn() + self.Chute2:Activate() + self.Chute2.Owner=self.Owner + self.Chute2:PhysWake() + self.Chute2:GetPhysicsObject():EnableDrag(true) + self.Chute2:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute2:GetPhysicsObject():SetMass(100) + local Wire2 = constraint.Rope(self,self.Chute2,0,0,Vector(0,-15,10),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + + self.Chute3 = ents.Create("prop_physics") + self.Chute3:SetModel("models/sw/shared/chute_2.mdl") + self.Chute3:SetPos(self:GetPos()+self:GetForward()*250+self:GetUp()*25) + self.Chute3:SetAngles(self:GetAngles()) + self.Chute3:Spawn() + self.Chute3:Activate() + self.Chute3.Owner=self.Owner + self.Chute3:PhysWake() + self.Chute3:GetPhysicsObject():EnableDrag(true) + self.Chute3:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute3:GetPhysicsObject():SetMass(100) + local Wire3 = constraint.Rope(self,self.Chute3,0,0,Vector(0,-15,10),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + elseif OpenSwitch and Open == true then + if IsValid(self.Chute1) then + self.Chute1:Remove() + end + if IsValid(self.Chute2) then + self.Chute2:Remove() + end + if IsValid(self.Chute3) then + self.Chute3:Remove() + end + self:SetOpen(false) + end + self.OldOpenSwitch = OpenSwitch + end + end + end + end + end + if IsValid(self.Chute1) then + self.Chute1:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + end + if IsValid(self.Chute2) then + self.Chute2:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + end + if IsValid(self.Chute3) then + self.Chute3:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + end + return true +end +function ENT:OnRemove() + if IsValid(self.Chute1) then + self.Chute1:Remove() + end + if IsValid(self.Chute2) then + self.Chute2:Remove() + end + if IsValid(self.Chute3) then + self.Chute3:Remove() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/shared.lua b/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/shared.lua new file mode 100644 index 0000000..32f6084 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_parachute_heavy_v3/shared.lua @@ -0,0 +1,16 @@ +ENT.Base = "base_anim" +ENT.Type = "anim" +ENT.SW_ENT = true +ENT.PrintName = "Parachute heavy" +ENT.Author = "Shermann Wolf" +ENT.Category = "SW Bombs V3" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Editable = true + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Open" ) + self:NetworkVar( "Int",1, "RopeLength", { KeyName = "ropelength", Edit = { type = "Int", order = 1,min = 0, max = 2500, category = "Misc"} } ) + self:NetworkVar( "Bool", 2, "AutoMode", { KeyName = "automode", Edit = { type = "Bool", category = "Misc"} } ) + self:SetRopeLength(300) +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/cl_init.lua b/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/cl_init.lua new file mode 100644 index 0000000..6207b7d --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/cl_init.lua @@ -0,0 +1,4 @@ +include("shared.lua") +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/init.lua b/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/init.lua new file mode 100644 index 0000000..e045884 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/init.lua @@ -0,0 +1,116 @@ +AddCSLuaFile("shared.lua") +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + ent.Owner = ply + return ent +end +function ENT:Initialize() + self:SetModel("models/sw/shared/backpack.mdl") + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:AddFlags( FL_OBJECT ) + local PObj = self:GetPhysicsObject() + if not IsValid( PObj ) then + self:Remove() + print("SW Bombs V3: Missing model. Entity terminated.") + return + end + self:PhysWake() +end + +function ENT:Think() + self:NextThink(CurTime()+0.05) + if self:GetAutoMode() then + if self:GetVelocity():Length() >= 500 then + if self:GetOpen() == false and not self:IsPlayerHolding() then + self:SetOpen(true) + self:EmitSound("sw/misc/chute_1.wav") + self.Chute = ents.Create("prop_physics") + self.Chute:SetModel("models/sw/shared/chute_1.mdl") + self.Chute:SetPos(self:GetPos()+self:GetUp()*25) + self.Chute:SetAngles(self:GetAngles()) + self.Chute:Spawn() + self.Chute:Activate() + self.Chute.Owner=self.Owner + self.Chute:PhysWake() + self.Chute:GetPhysicsObject():EnableDrag(true) + self.Chute:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute:GetPhysicsObject():SetMass(100) + local Wire = constraint.Rope(self,self.Chute,0,0,Vector(0,-15,10),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + if IsValid(self.Chute) then + self.Chute:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + end + end + else + if self:GetVelocity():Length() <= 10 then + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + timer.Simple(3,function() + if IsValid(self) then + if self:GetOpen() == true and not self:IsPlayerHolding() then + if IsValid(self.Chute) then + self.Chute:Remove() + end + self:SetOpen(false) + end + end + end) + end + end + end + end + else + for _, e in pairs(ents.FindInSphere(self:GetPos(), 500)) do + if e == self.Owner then + if IsValid( e ) then + local Open = self:GetOpen() + OpenSwitch = self.Owner:KeyDown(IN_SCORE) + if self.OldOpenSwitch ~= OpenSwitch then + if OpenSwitch and Open == false then + self:SetOpen(true) + self:EmitSound("sw/misc/chute_1.wav") + self.Chute = ents.Create("prop_physics") + self.Chute:SetModel("models/sw/shared/chute_1.mdl") + self.Chute:SetPos(self:GetPos()+self:GetUp()*25) + self.Chute:SetAngles(self:GetAngles()) + self.Chute:Spawn() + self.Chute:Activate() + self.Chute.Owner=self.Owner + self.Chute:PhysWake() + self.Chute:GetPhysicsObject():EnableDrag(true) + self.Chute:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + self.Chute:GetPhysicsObject():SetMass(100) + local Wire = constraint.Rope(self,self.Chute,0,0,Vector(0,-15,10),Vector(0,0,0),self:GetRopeLength(),0,0,1 ) + elseif OpenSwitch and Open == true then + if IsValid(self.Chute) then + self.Chute:Remove() + end + self:SetOpen(false) + end + self.OldOpenSwitch = OpenSwitch + end + end + end + end + end + if IsValid(self.Chute) then + self.Chute:GetPhysicsObject():SetDragCoefficient(self:GetVelocity():Length()*10) + end + return true +end + +function ENT:OnRemove() + if IsValid(self.Chute) then + self.Chute:Remove() + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/shared.lua b/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/shared.lua new file mode 100644 index 0000000..450b1a7 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_parachute_v3/shared.lua @@ -0,0 +1,16 @@ +ENT.Base = "base_anim" +ENT.Type = "anim" +ENT.SW_ENT = true +ENT.PrintName = "Parachute" +ENT.Author = "Shermann Wolf" +ENT.Category = "SW Bombs V3" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Editable = true + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Open" ) + self:NetworkVar( "Int",1, "RopeLength", { KeyName = "ropelength", Edit = { type = "Int", order = 1,min = 0, max = 2500, category = "Misc"} } ) + self:NetworkVar( "Bool", 2, "AutoMode", { KeyName = "automode", Edit = { type = "Bool", category = "Misc"} } ) + self:SetRopeLength(50) +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_4k40_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_4k40_v3.lua new file mode 100644 index 0000000..677de12 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_4k40_v3.lua @@ -0,0 +1,172 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_hvy_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_hvy_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_hvy_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_hvy_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_hvy_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_hvy_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_hvy_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_hvy_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_hvy_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_hvy_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_hvy_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_hvy_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_hvy_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_hvy_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_hvy_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_hvy_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_hvy_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_hvy_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_hvy_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_hvy_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_hvy_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_hvy_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_hvy_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_hvy_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "4К40" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/ssm/p15.mdl" +ENT.Effect = "mk84_air" +ENT.EffectAir = "mk84_explod" +ENT.EffectWater = "ins_water_explosion" +ENT.AngEffect = false +ENT.RocketTrail = "Big_mis_thrust" +ENT.RocketBurnoutTrail = "Big_mis_burnout" +ENT.Engine1Att = "engine_1" + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 250 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 500 +ENT.Mass = 100 +ENT.Durability = 500 +ENT.MaxVelocity = 320 +ENT.FuelBurnoutTime = 3 +ENT.RotationalForce = 0 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 915.52734375 +ENT.BlastRadius = 1373.291015625 +ENT.FragDamage = 25 +ENT.FragRadius = 1831.0546875 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 50 + +if SERVER then + function ENT:OnTick() + if self.Fired and self.GuidanceActive == false and self.target == nil then + for k, v in pairs( ents.FindInCone(self:GetPos(),self:GetForward(),5000,math.cos(math.rad(25))) ) do + if v:GetClass() == "lvs_wheeldrive_engine" or v.IsSimfphyscar or v:IsVehicle() then + if IsValid(v) then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + filter = function( ent ) + if ent == self then + return false + end + return true + end + } ) + debugoverlay.Line(self:GetPos(),v:GetPos(),1,Color(255,255,255),false) + if tr.Hit and tr.Entity == v then + self.GuidanceActive = true + self.DiveHeight = 100 + self.target = v + self.targetOffset = tr.Entity:WorldToLocal(tr.HitPos) + else + self.GuidanceActive = false + self.target = nil + self.DiveHeight = 0 + end + else + self.GuidanceActive = false + self.target = nil + self.DiveHeight = 0 + end + end + end + end + end + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m113_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m113_v3.lua new file mode 100644 index 0000000..906da62 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m113_v3.lua @@ -0,0 +1,128 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "9М113" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/atgm/9m113.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Engine1Att = "engine_1" +ENT.Engine2Att = "engine_2" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 240 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 70 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 140 +ENT.FragCount = 100 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + ent:SetTracerScale(5) + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m117_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m117_v3.lua new file mode 100644 index 0000000..b81c94e --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m117_v3.lua @@ -0,0 +1,127 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "9М117" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/atgm/9m117.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 375 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 10000 +ENT.ExplosionRadius = 70 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 140 +ENT.FragCount = 100 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + ent:SetTracerScale(5) + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m119_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m119_v3.lua new file mode 100644 index 0000000..ee443a2 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m119_v3.lua @@ -0,0 +1,134 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "9М119" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/atgm/9m119.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 800 +ENT.FuelBurnoutTime = 1.25 +ENT.RotationalForce = 0 + +--Explosion +ENT.ExplosionDamage = 300 +ENT.ExplosionRadius = 70 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 140 +ENT.FragCount = 0 + +ENT.HEAT = true +ENT.HEATRadius = 3 +ENT.ArmorPenetration = 80000 +ENT.PenetrationDamage = 4000 + +--Guidance +ENT.HaveGuidance = true +ENT.LaserGuided = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + ent:SetTracerScale(5) + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m127_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m127_v3.lua new file mode 100644 index 0000000..fdd1122 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m127_v3.lua @@ -0,0 +1,128 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "9М127" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/atgm/9m127.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Engine1Att = "engine_1" +ENT.Engine2Att = "engine_2" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 300 +ENT.Durability = 100 +ENT.MaxVelocity = 611 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 10000 +ENT.ExplosionRadius = 200 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 140 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 150 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + ent:SetTracerScale(5) + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m133_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m133_v3.lua new file mode 100644 index 0000000..4843cc9 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_9m133_v3.lua @@ -0,0 +1,128 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "9М133" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/atgm/9m133.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 250 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 3500 +ENT.ExplosionRadius = 70 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 140 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.LaserGuided = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + ent:SetTracerScale(5) + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_agm114_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_agm114_v3.lua new file mode 100644 index 0000000..442efee --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_agm114_v3.lua @@ -0,0 +1,127 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "AGM-114" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | USA" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/usa/missiles/agm/agm114.mdl" +ENT.Effect = "ins_c4_explosion" +ENT.EffectAir = "ins_c4_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 50 +ENT.Durability = 100 +ENT.MaxVelocity = 475 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 10000 +ENT.ExplosionRadius = 200 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 280 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_agm65_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_agm65_v3.lua new file mode 100644 index 0000000..eec714c --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_agm65_v3.lua @@ -0,0 +1,132 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "AGM-65" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | USA" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/usa/missiles/agm/agm65.mdl" +ENT.Effect = "ins_c4_explosion" +ENT.EffectAir = "ins_c4_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 306 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 300 +ENT.ExplosionRadius = 140 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 280 +ENT.FragCount = 0 + +ENT.HEAT = true +ENT.HEATRadius = 2 +ENT.ArmorPenetration = 83000 +ENT.PenetrationDamage = 2500 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_bgm71_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_bgm71_v3.lua new file mode 100644 index 0000000..0ce19ed --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_bgm71_v3.lua @@ -0,0 +1,128 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "BGM-71" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | USA" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/usa/missiles/atgm/bgm71.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.Engine1Att = "engine_1" +ENT.Engine2Att = "engine_2" +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 240 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 10000 +ENT.ExplosionRadius = 70 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 140 +ENT.FragCount = 100 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + ent:SetTracerScale(8) + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_grome1_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_grome1_v3.lua new file mode 100644 index 0000000..c730b00 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_grome1_v3.lua @@ -0,0 +1,171 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_hvy_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_hvy_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_hvy_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_hvy_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_hvy_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_hvy_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_hvy_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_hvy_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_hvy_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_hvy_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_hvy_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_hvy_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_hvy_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_hvy_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_hvy_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_hvy_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_hvy_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_hvy_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_hvy_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_hvy_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_hvy_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_hvy_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_hvy_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_hvy_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "ГРОМ-Э1" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/agm/9a7759.mdl" +ENT.Effect = "gw_250lb_explosion" +ENT.EffectAir = "gw_250lb_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.AngEffect = false +ENT.RocketTrail = "Med_mis_thrust" +ENT.RocketBurnoutTrail = "Med_mis_burnout" + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 250 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 500 +ENT.MaxVelocity = 300 +ENT.FuelBurnoutTime = 1.5 +ENT.RotationalForce = 0 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 732.421875 +ENT.BlastRadius = 1098.6328125 +ENT.FragDamage = 25 +ENT.FragRadius = 1464.84375 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 75 + +if SERVER then + function ENT:OnTick() + if self.Fired and self.GuidanceActive == false and self.target == nil then + for k, v in pairs( ents.FindInCone(self:GetPos(),self:GetForward(),2500,math.cos(math.rad(15))) ) do + if v:GetClass() == "lvs_wheeldrive_engine" or v.IsSimfphyscar or v:IsVehicle() then + if IsValid(v) then + local tr = util.TraceLine( { + start = self:GetPos(), + endpos = v:GetPos(), + filter = function( ent ) + if ent == self then + return false + end + return true + end + } ) + debugoverlay.Line(self:GetPos(),v:GetPos(),1,Color(255,255,255),false) + if tr.Hit and tr.Entity == v then + self.GuidanceActive = true + self.DiveHeight = 100 + self.target = v + self.targetOffset = tr.Entity:WorldToLocal(tr.HitPos) + else + self.GuidanceActive = false + self.target = nil + self.DiveHeight = 0 + end + else + self.GuidanceActive = false + self.target = nil + self.DiveHeight = 0 + end + end + end + end + end + function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent + end +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_hydra_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_hydra_v3.lua new file mode 100644 index 0000000..84786e0 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_hydra_v3.lua @@ -0,0 +1,62 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "Hydra 70" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | USA" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/usa/rockets/hydra70.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.ImpactSound = "sw/bomb/impact_1.wav" +ENT.DebrisSound = "sw/bomb/debris_1.wav" +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = "sw/explosion/exp_tny_1.wav" +ENT.FarExplosionSound = "sw/explosion/exp_sml_dst_1.wav" +ENT.DistExplosionSound = "sw/explosion/exp_sml_far_1.wav" +ENT.WaterExplosionSound = "sw/explosion/exp_trp_1.wav" +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 50 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 600 +ENT.FuelBurnoutTime = 1.3 + +--Explosion +ENT.ExplosionDamage = 1500 +ENT.ExplosionRadius = 70 +ENT.BlastRadius = 140 +ENT.FragDamage = 25 +ENT.FragRadius = 210 +ENT.FragCount = 0 diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_kh25ml_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_kh25ml_v3.lua new file mode 100644 index 0000000..cc1db80 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_kh25ml_v3.lua @@ -0,0 +1,136 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_sml_cls_1.wav" +ExpSnds[2] = "sw/explosion/exp_sml_cls_2.wav" +ExpSnds[3] = "sw/explosion/exp_sml_cls_3.wav" +ExpSnds[4] = "sw/explosion/exp_sml_cls_4.wav" +ExpSnds[5] = "sw/explosion/exp_sml_cls_5.wav" +ExpSnds[6] = "sw/explosion/exp_sml_cls_6.wav" +ExpSnds[7] = "sw/explosion/exp_sml_cls_7.wav" +ExpSnds[8] = "sw/explosion/exp_sml_cls_8.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "Х-25МЛ" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/missiles/agm/kh25ml.mdl" +ENT.Effect = "ins_c4_explosion" +ENT.EffectAir = "ins_c4_explosion" +ENT.EffectWater = "ins_water_explosion" +ENT.AngEffect = false +ENT.RocketTrail = "Med_mis_thrust" +ENT.RocketBurnoutTrail = "Med_mis_burnout" +ENT.Engine1Att = "engine_1" +ENT.Engine2Att = "engine_2" + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 250 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 500 +ENT.MaxVelocity = 870 +ENT.FuelBurnoutTime = 3 +ENT.RotationalForce = 0 + +--Explosion +ENT.ExplosionDamage = 15000 +ENT.ExplosionRadius = 468.75 +ENT.BlastRadius = 703.125 +ENT.FragDamage = 25 +ENT.FragRadius = 937.5 +ENT.FragCount = 0 + +--Guidance +ENT.HaveGuidance = true +ENT.Agility = 75 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_pg9v_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_pg9v_v3.lua new file mode 100644 index 0000000..c9c1083 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_pg9v_v3.lua @@ -0,0 +1,123 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "ПГ-9В" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/rockets/pg9v.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 100 +ENT.ImpactSpeed = 100 +ENT.ImpactDepth = 0 +ENT.Mass = 1 +ENT.Durability = 100 +ENT.MaxVelocity = 700 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 1000 +ENT.ExplosionRadius = 70 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 140 +ENT.FragCount = 0 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_s13_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_s13_v3.lua new file mode 100644 index 0000000..ba52325 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_s13_v3.lua @@ -0,0 +1,136 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "С-13" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/rockets/s13.mdl" +ENT.Effect = "ins_c4_explosion" +ENT.EffectAir = "ins_c4_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 250 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 530 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 10000 +ENT.ExplosionRadius = 200 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 250 +ENT.FragCount = 0 + +function ENT:OnTick() + if self.InGround or self.InEntity then + local tr = util.TraceLine({ + start = self:LocalToWorld(self:OBBCenter()), + endpos =self:LocalToWorld(self:OBBCenter())+self:GetAngles():Forward()*self.TraceLength, + filter = self, + }) + if not tr.Hit then + self:SetParent(NULL) + self:GetPhysicsObject():EnableMotion(true) + end + end +end +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_s13of_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_s13of_v3.lua new file mode 100644 index 0000000..c03a3f9 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_s13of_v3.lua @@ -0,0 +1,123 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local ImpSnds = {} +ImpSnds[1] = "sw/bomb/impact_1.wav" +ImpSnds[2] = "sw/bomb/impact_2.wav" +ImpSnds[3] = "sw/bomb/impact_3.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +local DbrSnds = {} +DbrSnds[1] = "sw/bomb/debris_1.wav" +DbrSnds[2] = "sw/bomb/debris_2.wav" +DbrSnds[3] = "sw/bomb/debris_3.wav" +DbrSnds[4] = "sw/bomb/debris_4.wav" + +local ExpSnds = {} +ExpSnds[1] = "sw/explosion/exp_tny_1.wav" +ExpSnds[2] = "sw/explosion/exp_tny_2.wav" +ExpSnds[3] = "sw/explosion/exp_tny_3.wav" + +local FarExpSnds = {} +FarExpSnds[1] = "sw/explosion/exp_sml_dst_1.wav" +FarExpSnds[2] = "sw/explosion/exp_sml_dst_2.wav" +FarExpSnds[3] = "sw/explosion/exp_sml_dst_3.wav" +FarExpSnds[4] = "sw/explosion/exp_sml_dst_4.wav" +FarExpSnds[5] = "sw/explosion/exp_sml_dst_5.wav" +FarExpSnds[6] = "sw/explosion/exp_sml_dst_6.wav" +FarExpSnds[7] = "sw/explosion/exp_sml_dst_7.wav" +FarExpSnds[8] = "sw/explosion/exp_sml_dst_8.wav" + +local DstExpSnds = {} +DstExpSnds[1] = "sw/explosion/exp_sml_far_1.wav" +DstExpSnds[2] = "sw/explosion/exp_sml_far_2.wav" +DstExpSnds[3] = "sw/explosion/exp_sml_far_3.wav" +DstExpSnds[4] = "sw/explosion/exp_sml_far_4.wav" +DstExpSnds[5] = "sw/explosion/exp_sml_far_5.wav" +DstExpSnds[6] = "sw/explosion/exp_sml_far_6.wav" +DstExpSnds[7] = "sw/explosion/exp_sml_far_7.wav" +DstExpSnds[8] = "sw/explosion/exp_sml_far_8.wav" + +local WtrExpSnds = {} +WtrExpSnds[1] = "sw/explosion/exp_trp_1.wav" +WtrExpSnds[2] = "sw/explosion/exp_trp_2.wav" +WtrExpSnds[3] = "sw/explosion/exp_trp_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "С-13ОФ" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/rockets/s13of.mdl" +ENT.Effect = "ins_c4_explosion" +ENT.EffectAir = "ins_c4_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.ImpactSound = table.Random(ImpSnds) +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.DebrisSound = table.Random(DbrSnds) +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = table.Random(ExpSnds) +ENT.FarExplosionSound = table.Random(FarExpSnds) +ENT.DistExplosionSound = table.Random(DstExpSnds) +ENT.WaterExplosionSound = table.Random(WtrExpSnds) +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 530 +ENT.FuelBurnoutTime = 1.25 + +--Explosion +ENT.ExplosionDamage = 3000 +ENT.ExplosionRadius = 125 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 250 +ENT.FragCount = 100 + +function ENT:SpawnFunction( ply, tr, ClassName ) + if not tr.Hit then return end + local ent = ents.Create( ClassName ) + ent:SetPos( tr.HitPos + tr.HitNormal ) + ent:SetAngles( Angle(0, ply:EyeAngles().y, 0 ) ) + ent:Spawn() + ent:Activate() + + ent.StartSound = table.Random(StartSnds) + ent.ImpactSound = table.Random(ImpSnds) + ent.WaterImpactSoundSound = table.Random(WtrImpSnds) + ent.DebrisSound = table.Random(DbrSnds) + ent.ExplosionSound = table.Random(ExpSnds) + ent.FarExplosionSound = table.Random(FarExpSnds) + ent.DistExplosionSound = table.Random(DstExpSnds) + ent.WaterExplosionSound = table.Random(WtrExpSnds) + + return ent +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_rocket_s8ko_v3.lua b/garrysmod/addons/swbombs/lua/entities/sw_rocket_s8ko_v3.lua new file mode 100644 index 0000000..b4ebce3 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_rocket_s8ko_v3.lua @@ -0,0 +1,62 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "sw_base_rocket_v3" ) + +local StartSnds = {} +StartSnds[1] = "sw/rocket/rocket_start_01.wav" +StartSnds[2] = "sw/rocket/rocket_start_02.wav" +StartSnds[3] = "sw/rocket/rocket_start_03.wav" +StartSnds[4] = "sw/rocket/rocket_start_04.wav" + +local WtrImpSnds = {} +WtrImpSnds[1] = "sw/bomb/impact_wtr_1.wav" +WtrImpSnds[2] = "sw/bomb/impact_wtr_2.wav" +WtrImpSnds[3] = "sw/bomb/impact_wtr_3.wav" + +--Main info +ENT.Type = "anim" +ENT.Spawnable = true +ENT.AdminSpawnable = false +ENT.AdminOnly = false +ENT.PrintName = "С-8КО" +ENT.Author = "Shermann Wolf" +ENT.Contact = "shermannwolf@gmail.com" +ENT.Category = "SW Bombs V3 | RUS" +ENT.Editable = true +ENT.SWBombV3 = true + +--Visual +ENT.Model = "models/sw/rus/rockets/s8.mdl" +ENT.Effect = "ins_m203_explosion" +ENT.EffectAir = "ins_m203_explosion" +ENT.EffectWater = "ins_water_explosion" + +ENT.AngEffect = true + +--Sounds +ENT.WaterImpactSoundSound = table.Random(WtrImpSnds) +ENT.ImpactSound = "sw/bomb/impact_1.wav" +ENT.DebrisSound = "sw/bomb/debris_1.wav" +ENT.WhistleSound = "sw/bomb/whistle.wav" +ENT.ExplosionSound = "sw/explosion/exp_tny_1.wav" +ENT.FarExplosionSound = "sw/explosion/exp_sml_dst_1.wav" +ENT.DistExplosionSound = "sw/explosion/exp_sml_far_1.wav" +ENT.WaterExplosionSound = "sw/explosion/exp_trp_1.wav" +ENT.WaterFarExplosionSound = nil +ENT.StartSound = table.Random(StartSnds) + +--Physics +ENT.TraceLength = 150 +ENT.ImpactSpeed = 500 +ENT.ImpactDepth = 0 +ENT.Mass = 100 +ENT.Durability = 100 +ENT.MaxVelocity = 700 +ENT.FuelBurnoutTime = 1.5 + +--Explosion +ENT.ExplosionDamage = 2500 +ENT.ExplosionRadius = 100 +ENT.BlastRadius = 0 +ENT.FragDamage = 25 +ENT.FragRadius = 270 +ENT.FragCount = 0 diff --git a/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/cl_init.lua b/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/cl_init.lua new file mode 100644 index 0000000..45c997c --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/cl_init.lua @@ -0,0 +1,3 @@ +include("shared.lua") +function ENT:LVSHudPaintVehicleIdentifier( X, Y, In_Col, target_ent ) +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/init.lua b/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/init.lua new file mode 100644 index 0000000..ac491e5 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/init.lua @@ -0,0 +1,92 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include("shared.lua") + +function ENT:Mount( ent ) + if IsValid( self._MountEnt ) or ent._IsMounted then return end + + if ent:IsPlayerHolding() then return end + local ID = self:LookupAttachment( "muzzle" ) + local Attachment = self:GetAttachment( ID ) + + ent:SetOwner( self ) + ent:SetPos( Attachment.Pos+Attachment.Ang:Up()*ent.CatapultPos or 0 ) + ent:SetAngles( Attachment.Ang ) + ent:SetlvsLockedStatus( true ) + ent:SetHP(ent:GetMaxHP()) + ent:WeaponRestoreAmmo() + ent:OnMaintenance() + + + ent._MountOriginalCollision = ent:GetCollisionGroup() + self._MountEnt = ent + ent._IsMounted = true + + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + self._MountConstraint = constraint.Weld( ent, self, 0, 0, 0, false, false ) + + ent:RebuildCrosshairFilterEnts() +end + +function ENT:Dismount() + if not IsValid( self._MountEnt ) or not IsValid( self._MountConstraint ) then return end + + self._MountConstraint:Remove() + + self._MountEnt._IsMounted = nil + + local ent = self._MountEnt + + timer.Simple(0.1, function() + if not IsValid( ent ) then return end + + ent:SetOwner( NULL ) + ent:SetlvsLockedStatus( true ) + if not ent:IsPlayerHolding() then + ent:SetEngineActive(true) + ent:GetPhysicsObject():AddVelocity(ent:GetUp()*150) + ent:GetPhysicsObject():AddVelocity(ent:GetForward()*1500) + end + end) + + timer.Simple(1,function() + if ent._MountOriginalCollision then + ent:SetCollisionGroup( ent._MountOriginalCollision ) + + ent._MountOriginalCollision = nil + end + + self._MountEnt.CrosshairFilterEnts = nil + + self._MountEnt = nil + end) +end + +function ENT:OnCollision( data, physobj ) + local ent = data.HitEntity + + if not IsValid( ent ) or not (ent.LVSUAV and ent.CatapultLaunchable) then return end + + timer.Simple(0, function() + if not IsValid( self ) or not IsValid( ent ) then return end + + self:Mount( ent ) + end) +end + +function ENT:OnTick() + if not IsValid( self._MountEnt ) or not self._MountEnt:IsPlayerHolding() then return end + + self:Dismount() +end + +function ENT:Use( ply ) + if not IsValid( self._MountEnt ) then return end + + self:Dismount() +end + +function ENT:OnRemove() + self:Dismount() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/shared.lua b/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/shared.lua new file mode 100644 index 0000000..0bae682 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_uav_catapult/shared.lua @@ -0,0 +1,13 @@ +ENT.Base = "lvs_base_wheeldrive_trailer" + +ENT.PrintName = "UAV Catapult" +ENT.Author = "Shermann Wolf" +ENT.Information = "" +ENT.Category = "SW Bombs V3" + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.MDL = "models/sw/shared/catapult.mdl" +ENT.AITEAM = 0 +ENT.MaxHealth = 200 diff --git a/garrysmod/addons/swbombs/lua/entities/sw_uav_control/cl_attached_playermodels.lua b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/cl_attached_playermodels.lua new file mode 100644 index 0000000..61c7ea4 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/cl_attached_playermodels.lua @@ -0,0 +1,30 @@ + +include("entities/lvs_tank_wheeldrive/modules/cl_attachable_playermodels.lua") + +function ENT:DrawDriver() + if IsValid(self:GetNWEntity("UAV")) then + local pod = self:GetNWEntity("UAV"):GetDriverSeat() + + if not IsValid( pod ) then self:RemovePlayerModel( "driver" ) return self:SetBodygroup(1,0) end + + local plyL = LocalPlayer() + local ply = pod:GetDriver() + + if not IsValid( ply ) then self:RemovePlayerModel( "driver" ) return self:SetBodygroup(1,0) end + + local model = self:CreatePlayerModel( ply, "driver" ) + model:SetSequence( "cidle_knife" ) + model:SetRenderOrigin( LocalToWorld(Vector(-30,0,-5),Angle(0,0,0),(self:LocalToWorld(Vector(0,0,0))),self:GetAngles()) ) + model:SetRenderAngles( self:LocalToWorldAngles(Angle(0,0,0)) ) + model:DrawModel() + + self:SetBodygroup(1,1) + end +end + + +function ENT:PreDraw() + self:DrawDriver() + + return true +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_uav_control/cl_init.lua b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/cl_init.lua new file mode 100644 index 0000000..cf1c07e --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/cl_init.lua @@ -0,0 +1,2 @@ +include("shared.lua") +include("cl_attached_playermodels.lua") diff --git a/garrysmod/addons/swbombs/lua/entities/sw_uav_control/init.lua b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/init.lua new file mode 100644 index 0000000..cc29ea4 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/init.lua @@ -0,0 +1,45 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_attached_playermodels.lua" ) +include("shared.lua") + +function ENT:OnSpawn( PObj ) + PObj:SetMass(100) + local DriverSeat = self:AddDriverSeat( Vector(-50,50,-5), Angle(0,-90,0) ) + DriverSeat.HidePlayer = true + DriverSeat.ExitPos = Vector(-20,0,20) +end + +function ENT:Use( ply ) + if IsValid(self:GetNWEntity("UAV")) then + if self:GetNWEntity("UAV"):GetAI() then return end + ply:EnterVehicle(self:GetNWEntity("UAV"):GetDriverSeat()) + self:GetNWEntity("UAV"):AlignView( ply ) + + if hook.Run( "LVS.CanPlayerDrive", ply, self ) ~= false then + ply:EnterVehicle( self:GetNWEntity("UAV"):GetDriverSeat() ) + self:GetNWEntity("UAV"):AlignView( ply ) + else + hook.Run( "LVS.OnPlayerCannotDrive", ply, self ) + end + end +end + +function ENT:Explode() + if self.ExplodedAlready then return end + + self.ExplodedAlready = true + + if IsValid(self:GetNWEntity("UAV")) then + local Driver = self:GetNWEntity("UAV"):GetDriver() + + if IsValid( Driver ) then + self:HurtPlayer( Driver, Driver:Health() + Driver:Armor(), self.FinalAttacker, self.FinalInflictor ) + end + self:GetNWEntity("UAV"):SetlvsLockedStatus(true) + end + + self:OnFinishExplosion() + + self:Remove() +end diff --git a/garrysmod/addons/swbombs/lua/entities/sw_uav_control/shared.lua b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/shared.lua new file mode 100644 index 0000000..a46e755 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/entities/sw_uav_control/shared.lua @@ -0,0 +1,34 @@ + +ENT.Base = "lvs_base_wheeldrive_trailer" + +ENT.PrintName = "[LVS] UAV Control" +ENT.Author = "Shermann Wolf" +ENT.Information = "" +ENT.Category = "[LVS]" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.MDL = "models/sw/shared/uav_control.mdl" + +ENT.AITEAM = 0 + +ENT.MaxHealth = 250 + +function ENT:CalcMainActivity( ply ) + if ply ~= self:GetDriver() then return self:CalcMainActivityPassenger( ply ) end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + ply.CalcIdeal = ACT_STAND + ply.CalcSeqOverride = ply:LookupSequence( "cidle_knife" ) + + return ply.CalcIdeal, ply.CalcSeqOverride +end diff --git a/garrysmod/addons/swbombs/lua/lvs_framework/autorun/sh_additional_keybinding.lua b/garrysmod/addons/swbombs/lua/lvs_framework/autorun/sh_additional_keybinding.lua new file mode 100644 index 0000000..dffa9ef --- /dev/null +++ b/garrysmod/addons/swbombs/lua/lvs_framework/autorun/sh_additional_keybinding.lua @@ -0,0 +1,38 @@ +hook.Add( "LVS:Initialize", "SW Controls keys", function() + local KEYS = { + { + name = "+ZOOM", + category = "Misc", + name_menu = "Cam zoom in", + cmd = "lvs_cam_zoom_in" + }, + { + name = "-ZOOM", + category = "Misc", + name_menu = "Cam zoom out", + cmd = "lvs_cam_zoom_out" + }, + { + name = "AIRBRAKE", + category = "Misc", + name_menu = "Airbrake", + cmd = "lvs_airbrake" + }, + { + name = "BAILOUT", + category = "Misc", + name_menu = "Bail out", + cmd = "lvs_bailout" + }, + { + name = "CHUTE", + category = "Misc", + name_menu = "Brake chute", + cmd = "lvs_brake_chute" + }, + } + + for _, v in pairs( KEYS ) do + LVS:AddKey( v.name, v.category, v.name_menu, v.cmd, v.default ) + end +end ) diff --git a/garrysmod/addons/swbombs/lua/weapons/gmod_tool/stools/sw_aamloadout_editor_v3.lua b/garrysmod/addons/swbombs/lua/weapons/gmod_tool/stools/sw_aamloadout_editor_v3.lua new file mode 100644 index 0000000..084d027 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/weapons/gmod_tool/stools/sw_aamloadout_editor_v3.lua @@ -0,0 +1,53 @@ + +TOOL.Category = "LFS/LVS" +TOOL.Name = "#tool.sw_aamloadout_editor_v3.name" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "aamloadout" ] = 0 + +if CLIENT then + language.Add( "tool.sw_aamloadout_editor_v3.name", "AAM Loadout Editor" ) + language.Add( "tool.sw_aamloadout_editor_v3.desc", "A tool used to set AAM loadout on LFS/LVS-Vehicles" ) + language.Add( "tool.sw_aamloadout_editor_v3.0", "Left click on a LFS/LVS-Vehicle to set AAM loadout,Right click to set 0 loadout." ) + language.Add( "tool.sw_aamloadout_editor_v3.1", "Left click on a LFS/LVS-Vehicle to set AAM loadout,Right click to set 0 loadout." ) + language.Add( "tool.sw_aamloadout_editor_v3.AAMloadout", "Loadout" ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + if not IsValid( ent ) or not (ent.LFS or ent.LVS) then return false end + if isnumber(ent.AAMLoadouts) then + ent:SetAAMLoadout( self:GetClientNumber( "aamloadout" ) ) + end + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + if not IsValid( ent ) or not (ent.LFS or ent.LVS) then return false end + if isnumber(ent.AAMLoadouts) then + ent:SetAAMLoadout(0) + end + return true +end + +function TOOL:Reload( trace ) +end + +function TOOL:Think() +end + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.sw_aamloadout_editor_v3.name", Description = "#tool.sw_aamloadout_editor_v3.desc" } ) + + panel:AddControl( "Slider", + { + Label = "#tool.sw_aamloadout_editor_v3.AAMloadout", + Type = "Int", + Min = "0", + Max = "10", + Command = "sw_aamloadout_editor_v3_aamloadout", + Help = false + }) +end diff --git a/garrysmod/addons/swbombs/lua/weapons/gmod_tool/stools/sw_loadout_editor_v3.lua b/garrysmod/addons/swbombs/lua/weapons/gmod_tool/stools/sw_loadout_editor_v3.lua new file mode 100644 index 0000000..b3c0e19 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/weapons/gmod_tool/stools/sw_loadout_editor_v3.lua @@ -0,0 +1,53 @@ + +TOOL.Category = "LFS/LVS" +TOOL.Name = "#tool.sw_loadout_editor_v3.name" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "loadout" ] = 0 + +if CLIENT then + language.Add( "tool.sw_loadout_editor_v3.name", "Loadout Editor" ) + language.Add( "tool.sw_loadout_editor_v3.desc", "A tool used to set loadout on LFS/LVS-Vehicles" ) + language.Add( "tool.sw_loadout_editor_v3.0", "Left click on a LFS/LVS-Vehicle to set loadout,Right click to set 0 loadout." ) + language.Add( "tool.sw_loadout_editor_v3.1", "Left click on a LFS/LVS-Vehicle to set loadout,Right click to set 0 loadout." ) + language.Add( "tool.sw_loadout_editor_v3.loadout", "Loadout" ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + if not IsValid( ent ) or not (ent.LFS or ent.LVS) then return false end + if isnumber(ent.Loadouts) then + ent:SetLoadout( self:GetClientNumber( "loadout" ) ) + end + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + if not IsValid( ent ) or not (ent.LFS or ent.LVS) then return false end + if isnumber(ent.Loadouts) then + ent:SetLoadout(0) + end + return true +end + +function TOOL:Reload( trace ) +end + +function TOOL:Think() +end + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.sw_loadout_editor_v3.name", Description = "#tool.sw_loadout_editor_v3.desc" } ) + + panel:AddControl( "Slider", + { + Label = "#tool.sw_loadout_editor_v3.loadout", + Type = "Int", + Min = "0", + Max = "50", + Command = "sw_loadout_editor_v3_loadout", + Help = false + }) +end diff --git a/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/cl_init.lua b/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/cl_init.lua new file mode 100644 index 0000000..61fdaf1 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/cl_init.lua @@ -0,0 +1,180 @@ +include("shared.lua") +local posX = { + 0.29, + 0.5, + 0.71, + 0.71, + 0.71, + 0.5, + 0.29, + 0.29, +} +local posY = { + 0.29, + 0.29, + 0.29, + 0.5, + 0.71, + 0.71, + 0.71, + 0.5, +} +local COLOR_GREEN = Color(0,255,0,255) +local COLOR_BLACK = Color(0,0,0,255) +local COLOR_WHITE = Color(255,255,255,255) +local COLOR_WHITE_HOVERED = Color(200,200,200,150) +local HOOK_ADDED = false +local LANGUAGE +local ints = { + [8] = 1, + [2] = 2, + [4] = 3, +} +local function DrawCircle( X, Y, radius ) + local segmentdist = 360 / ( 2 * math.pi * radius / 2 ) + for a = 0, 360, segmentdist do + surface.DrawLine( X + math.cos( math.rad( a ) ) * radius, Y - math.sin( math.rad( a ) ) * radius, X + math.cos( math.rad( a + segmentdist ) ) * radius, Y - math.sin( math.rad( a + segmentdist ) ) * radius ) + end +end +function SWEP:DrawHUD() + local X = ScrW() + local Y = ScrH() + if self.Zoom.Val == 0 then + local tr = self.Owner:GetEyeTrace() + if IsValid(tr.Entity) and tr.StartPos:DistToSqr(tr.HitPos) < self.MaxPairDistance then + if (tr.Entity.LVS or tr.Entity.LFS or (tr.Entity.SWBombV3 and tr.Entity.HaveGuidance)) and tr.Entity:GetNWBool("HaveDesignator") == false then + if !IsValid(self:GetNWEntity("PairedObj")) then + draw.DrawText("Click to pair designator","Trebuchet24",X*0.5,Y*0.25,color_white,TEXT_ALIGN_CENTER) + else + return + end + end + end + end + if IsValid(self:GetNWEntity("Projectile")) then + local Pos = self:GetNWVector("TarPos"):ToScreen() + surface.SetDrawColor( 255, 255, 255, self.Zoom.Val*255 ) + DrawCircle( Pos.x, Pos.y, 10 ) + local Pos2 = self:GetNWEntity("Projectile"):GetPos():ToScreen() + surface.SetDrawColor( 255, 255, 255, self.Zoom.Val*255 ) + surface.SetMaterial(Material("icons/mis.png")) + surface.DrawTexturedRect(Pos2.x,Pos2.y,25,25) + end + if IsValid(self:GetNWEntity("PairedObj")) and self:GetNWEntity("PairedObj").SWBombV3 then + local Pos = self:GetNWVector("TarPos"):ToScreen() + surface.SetDrawColor( 255, 255, 255, self.Zoom.Val*255 ) + DrawCircle( Pos.x, Pos.y, 10 ) + local Pos2 = self:GetNWEntity("PairedObj"):GetPos():ToScreen() + surface.SetDrawColor( 255, 255, 255, self.Zoom.Val*255 ) + surface.SetMaterial(Material("icons/mis.png")) + surface.DrawTexturedRect(Pos2.x,Pos2.y,25,25) + end + if IsValid(self:GetNWEntity("PairedObj")) and not self:GetNWEntity("PairedObj").SWBombV3 then + local Pos = self:GetNWEntity("PairedObj"):GetPos():ToScreen() + surface.SetDrawColor( 255, 255, 255, self.Zoom.Val*255 ) + surface.SetMaterial(Material("icons/plane.png")) + surface.DrawTexturedRect(Pos.x,Pos.y,25,25) + end + surface.SetDrawColor(0,0,0,self.Zoom.Val*255) + surface.SetTexture(surface.GetTextureID("models/sw/shared/sights/binoculars")) + surface.DrawTexturedRect(0,-(X-Y)/2,X,X) + local tr = self.Owner:GetEyeTrace() + local range = (math.ceil(100*(tr.StartPos:Distance(tr.HitPos)*0.024))/100) + if tr.HitSky then + range = "-" + else + range = range.."m" + end + surface.SetTextColor( 255, 255, 255, self.Zoom.Val*255 ) + surface.SetTextPos( (X*0.165), (Y/2) ) + surface.DrawText( "Range: "..range ) + surface.SetTextPos( (X*0.165), (Y/2) + 16) + surface.DrawText( "X: "..(math.ceil(100*tr.HitPos.x))/100) + surface.SetTextPos( (X*0.165), (Y/2) + 32) + surface.DrawText( "Y: "..(math.ceil(100*tr.HitPos.y)/100) ) + surface.SetTextPos( (X*0.165), (Y/2) + 48) + surface.DrawText( "Z: "..(math.ceil(100*tr.HitPos.z)/100) ) + + if self:GetNWBool("NVG") == true then + surface.SetTextPos( (X*0.165), (Y/2) + 64) + surface.DrawText( "NVG: ON" ) + local dlight = DynamicLight(self:EntIndex()) + dlight.r = self.Zoom.Val*255 + dlight.g = self.Zoom.Val*255 + dlight.b = self.Zoom.Val*255 + dlight.minlight = 0 + dlight.style = 0 + dlight.Brightness = self.Zoom.Val*0.1 + dlight.Pos = EyePos() + dlight.Size = self.Zoom.Val*2048 + dlight.Decay = self.Zoom.Val*10000 + dlight.DieTime = CurTime() + 0.1 + local mat_color = Material( "pp/colour" ) + render.UpdateScreenEffectTexture() + mat_color:SetTexture( "$fbtexture", render.GetScreenEffectTexture() ) + mat_color:SetFloat( "$pp_colour_addr", -self.Zoom.Val*255 ) + mat_color:SetFloat( "$pp_colour_addg", 0 ) + mat_color:SetFloat( "$pp_colour_addb", 0) + mat_color:SetFloat( "$pp_colour_mulr", 0 ) + mat_color:SetFloat( "$pp_colour_mulg", 0 ) + mat_color:SetFloat( "$pp_colour_mulb", 0 ) + mat_color:SetFloat( "$pp_colour_brightness", self.Zoom.Val*0.01 ) + mat_color:SetFloat( "$pp_colour_contrast", 1+self.Zoom.Val*5 ) + mat_color:SetFloat( "$pp_colour_colour", 1 ) + render.SetMaterial( mat_color ) + render.DrawScreenQuad() + DrawBloom(self.Zoom.Val*0.5,self.Zoom.Val*1,self.Zoom.Val*10,self.Zoom.Val*10,self.Zoom.Val*2,self.Zoom.Val*1,self.Zoom.Val*1,self.Zoom.Val*1,self.Zoom.Val*1) + else + surface.SetTextPos( (X*0.165), (Y/2) + 64) + surface.DrawText( "NVG: OFF" ) + end +end +function SWEP:CalcView(ply,pos,ang,fov) + return pos,ang,fov - (self.Zoom.Val * self.Zoom.FOV) +end +function SWEP:AdjustMouseSensitivity() + return self.Owner:KeyDown(IN_ATTACK2) and 0.1 or 1 +end +function SWEP:CalcViewModelView(ViewModel,OldEyePos) + if self.Zoom.Val > 0.8 then + return Vector(0,0,0) + else + return OldEyePos - Vector(0,0,1.3) + end +end +function SWEP:Think() + local keydown = self.Owner:KeyDown(IN_ATTACK2) + self.Zoom.Val = math.Clamp(self.Zoom.Val + (keydown and self.Zoom.Rate or -self.Zoom.Rate),0,1) + if keydown and not self.IsZooming then + self.IsZooming = true + self.IsUnZooming = false + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + elseif !keydown and not self.IsUnZooming and self.IsZooming then + self.IsZooming = false + self.IsUnZooming = true + self.Weapon:SendWeaponAnim(ACT_VM_SECONDARYATTACK) + end + if keydown then + local Zoom = self.Zoom.FOV + local ZoomSwitch = self.Owner:KeyDown(IN_SPEED) + if self.OldZoomSwitch ~= ZoomSwitch then + if ZoomSwitch and Zoom == 70 then + self.Zoom.FOV = 40 + elseif ZoomSwitch and Zoom == 40 then + self.Zoom.FOV = 70 + end + self.OldZoomSwitch = ZoomSwitch + end + local NVGSwitch = self.Owner:KeyDown(IN_ZOOM) + if self.OldNVGSwitch ~= NVGSwitch then + if NVGSwitch and self:GetNWBool("NVG") == true then + self:SetNWBool("NVG",false) + self:EmitSound("sw/misc/nv_off.wav") + elseif NVGSwitch and self:GetNWBool("NVG") == false then + self:SetNWBool("NVG",true) + self:EmitSound("sw/misc/nv_on.wav") + end + self.OldNVGSwitch = NVGSwitch + end + end +end diff --git a/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/init.lua b/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/init.lua new file mode 100644 index 0000000..1604830 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/init.lua @@ -0,0 +1,64 @@ +include("shared.lua") +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +function SWEP:PrimaryAttack() + if self.Owner:KeyDown(IN_ATTACK2) then + if IsValid(self:GetNWEntity("PairedObj")) then + if self:GetNWEntity("PairedObj").SWBombV3 and self:GetNWEntity("PairedObj").HaveGuidance then + local tr = self.Owner:GetEyeTrace() + self:GetNWEntity("PairedObj").GuidanceActive = true + self:GetNWEntity("PairedObj").target = tr.Entity + self:GetNWEntity("PairedObj").targetOffset = tr.Entity:WorldToLocal(tr.HitPos) + self:SetNWVector("TarPos", tr.HitPos ) + self:EmitSound("sw/misc/point.wav") + print(tr.HitPos) + end + if IsValid(self:GetNWEntity("PairedObj"):GetNWEntity("Missile")) then + if self:GetNWEntity("PairedObj"):GetNWEntity("Missile").SWBombV3 and self:GetNWEntity("PairedObj"):GetNWEntity("Missile").HaveGuidance then + local tr = self.Owner:GetEyeTrace() + self:SetNWEntity("Projectile",self:GetNWEntity("PairedObj"):GetNWEntity("Missile")) + self:GetNWEntity("PairedObj"):GetNWEntity("Missile").GuidanceActive = true + self:GetNWEntity("PairedObj"):GetNWEntity("Missile").LaserGuided = false + self:GetNWEntity("PairedObj"):GetNWEntity("Missile").target = tr.Entity + self:GetNWEntity("PairedObj"):GetNWEntity("Missile").targetOffset = tr.Entity:WorldToLocal(tr.HitPos) + self:SetNWVector("TarPos", tr.HitPos ) + self:EmitSound("sw/misc/point.wav") + print(tr.HitPos) + end + end + end + end + + if self.Owner:KeyDown(IN_ATTACK2) then return end + local tr = self.Owner:GetEyeTrace() + if IsValid(tr.Entity) and tr.StartPos:DistToSqr(tr.HitPos) < self.MaxPairDistance then + if ((tr.Entity.LVS or tr.Entity.LFS) and tr.Entity.Pairable ) or (tr.Entity.SWBombV3 and tr.Entity.HaveGuidance) then + if !IsValid(self:GetNWEntity("PairedObj")) then + tr.Entity:SetNWBool("HaveDesignator",true) + self:SetNWEntity("PairedObj",tr.Entity) + self:SetNWBool("Paired",true) + self.Owner:PrintMessage(HUD_PRINTCENTER,"You have paired target designator with:"..self:GetNWEntity("PairedObj").PrintName) + else + return + end + end + end +end +function SWEP:SecondaryAttack() +end +function SWEP:CanReload(ct) + return self.NextReload <= ct +end +function SWEP:Reload() + local ct = CurTime() + if not self:CanReload(ct) then return end + self.NextReload = ct + 0.3 +end +function SWEP:OnRemove() + if IsValid(self) and IsValid(self:GetNWEntity("PairedObj")) then + self:GetNWEntity("PairedObj"):SetNWBool("HaveDesignator",false) + self:SetNWEntity("PairedObj",nil) + self:SetNWBool("Paired",false) + end +end diff --git a/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/shared.lua b/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/shared.lua new file mode 100644 index 0000000..204591f --- /dev/null +++ b/garrysmod/addons/swbombs/lua/weapons/sw_laser_pointer_v3/shared.lua @@ -0,0 +1,98 @@ +AddCSLuaFile() +SWEP.Base = "weapon_base" +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.MaxPairDistance = 500^2 +SWEP.Category = "SW Weapons Factory" +SWEP.Author = "Shermann Wolf" +SWEP.Contact = "shermannwolf@gmail.com" +SWEP.Purpose = "" +SWEP.Instructions = "Right click on bomb/missile/aircraft to pair it with your laser pointer,and left click to point target for it" +SWEP.PrintName = "Laser target designator" +SWEP.WorldModel = "models/sw/shared/weapons/w_binoculars.mdl" +SWEP.ViewModel = "models/sw/shared/weapons/v_binoculars.mdl" +SWEP.TarPos = nil +SWEP.Primary = { + Ammo = "None", + ClipSize = -1, + DefaultClip = -1, + Automatic = false, + NextShot = 0, + FireRate = 0.00001 +} +SWEP.Secondary = SWEP.Primary +SWEP.NextReload = 0 +SWEP.DrawAmmo = false +SWEP.Zoom = {} +SWEP.Zoom.FOV = 70 +SWEP.Zoom.Rate = 0.035 +SWEP.Zoom.Val = 0 +SWEP.UseHands = true +local pairs = pairs +local IsValid = IsValid +local CLIENT = CLIENT +local SERVER = SERVER +local SINGLEPLAYER = game.SinglePlayer() +function SWEP:Deploy() + self.Weapon:SendWeaponAnim(ACT_VM_DRAW) + return true +end +function SWEP:Initialize() + self:SetNWEntity("PairedObj",nil) + self:SetNWEntity("Projectile",nil) + self:SetNWBool("Paired",false) + self:SetHoldType("camera") +end +function SWEP:Holster(wep) + return true +end +function SWEP:DrawHUD() + surface.SetDrawColor(0,0,0,self.Zoom.Val*255) + surface.SetTexture(surface.GetTextureID("models/sw/shared/sights/binoculars")) + local X = ScrW() + local Y = ScrH() + surface.DrawTexturedRect(0,-(X-Y)/2,X,X) +end +function SWEP:CalcViewModelView(ViewModel,OldEyePos) + if self.Zoom.Val > 0.8 then + return Vector(0,0,0) + else + return OldEyePos - Vector(0,0,1.3) + end +end +function SWEP:Think() + local keydown = self.Owner:KeyDown(IN_ATTACK2) + self.Zoom.Val = math.Clamp(self.Zoom.Val + (keydown and 0.1 or -0.1),0,1) + if keydown and not self.IsZooming then + self.IsZooming = true + self.IsUnZooming = false + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + elseif !keydown and not self.IsUnZooming and self.IsZooming then + self.IsZooming = false + self.IsUnZooming = true + self.Weapon:SendWeaponAnim(ACT_VM_SECONDARYATTACK) + end +end +function SWEP:CalcView(ply,pos,ang,fov) + fov = fov - (self.Zoom.Val*self.Zoom.FOV) + return pos,ang,fov +end +function SWEP:AdjustMouseSensitivity() + return self.Owner:KeyDown(IN_ATTACK2) and 0.1 or 1 +end +function SWEP:CanPrimaryAttack(ct) + return self.Primary.NextShot <= ct +end +function SWEP:PrimaryAttack() + local ct = CurTime() + if not self:CanPrimaryAttack(ct) then return end + self.Primary.NextShot = ct + self.Primary.FireRate +end +function SWEP:CanSecondaryAttack(ct) +end +function SWEP:SecondaryAttack() +end +function SWEP:CanReload(ct) +end +function SWEP:Reload() +end diff --git a/garrysmod/addons/swbombs/lua/weapons/weapon_lvsaircraftfuelfiller.lua b/garrysmod/addons/swbombs/lua/weapons/weapon_lvsaircraftfuelfiller.lua new file mode 100644 index 0000000..96d55a0 --- /dev/null +++ b/garrysmod/addons/swbombs/lua/weapons/weapon_lvsaircraftfuelfiller.lua @@ -0,0 +1,215 @@ +AddCSLuaFile() + +SWEP.Category = "[LVS]" +SWEP.Spawnable = false +SWEP.AdminSpawnable = false +SWEP.ViewModel = "models/weapons/c_fuelfillerlvs.mdl" +SWEP.WorldModel = "models/props_equipment/gas_pump_p13.mdl" +SWEP.UseHands = true + +SWEP.HoldType = "slam" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.RangeToCap = 24 +SWEP.HitDistance = 128 + +function SWEP:GetTank( entity ) + if entity.lvsGasStationRefillMe then + return entity + end + + if not entity.LVS or not entity.GetFuelTank then return NULL end + if not (entity:GetVehicleType() == "helicopter" or entity:GetVehicleType() == "plane") then return end + + return entity:GetFuelTank() +end + +function SWEP:GetCap( entity ) + if entity.lvsGasStationRefillMe then + return entity + end + + if not entity.LVS or not entity.GetFuelTank then return NULL end + if not (entity:GetVehicleType() == "helicopter" or entity:GetVehicleType() == "plane") then return end + + local FuelTank = entity:GetFuelTank() + + if not IsValid( FuelTank ) then return NULL end + + return FuelTank:GetDoorHandler() +end + +if CLIENT then + SWEP.PrintName = "Aircraft fuel Filler Pistol" + SWEP.Slot = 1 + SWEP.SlotPos = 3 + + SWEP.DrawWeaponInfoBox = false + + function SWEP:DrawWeaponSelection( x, y, wide, tall, alpha ) + end + + function SWEP:DrawWorldModel() + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + local pos = attachment.Pos + attachment.Ang:Forward() * 6 + attachment.Ang:Right() * -1.5 + attachment.Ang:Up() * 2.2 + local ang = attachment.Ang + ang:RotateAroundAxis(attachment.Ang:Up(), 20) + ang:RotateAroundAxis(attachment.Ang:Right(), -30) + ang:RotateAroundAxis(attachment.Ang:Forward(), 0) + + self:SetRenderOrigin( pos ) + self:SetRenderAngles( ang ) + + self:DrawModel() + end + + local function DrawText( pos, text, col ) + local data2D = pos:ToScreen() + + if not data2D.visible then return end + + local font = "TargetIDSmall" + + local x = data2D.x + local y = data2D.y + draw.SimpleText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + draw.SimpleText( text, font, x, y, col or color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + + function SWEP:DrawHUD() + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local startpos = ply:GetShootPos() + local endpos = startpos + ply:GetAimVector() * self.HitDistance + + local trace = util.TraceLine( { + start = startpos , + endpos = endpos, + filter = ply, + mask = MASK_SHOT_HULL + } ) + + if not IsValid( trace.Entity ) then + trace = util.TraceHull( { + start = startpos , + endpos = endpos, + filter = ply, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + local FuelTank = self:GetTank( trace.Entity ) + local FuelCap = self:GetCap( trace.Entity ) + + if not IsValid( FuelTank ) then return end + + if not IsValid( FuelCap ) then + DrawText( trace.HitPos, math.Round(FuelTank:GetFuel() * 100,1).."%", Color(0,255,0,255) ) + + return + end + + if FuelCap:IsOpen() then + if (trace.HitPos - FuelCap:GetPos()):Length() > self.RangeToCap then + DrawText( trace.HitPos, "Aim at Fuel Cap!", Color(255,255,0,255) ) + else + DrawText( trace.HitPos, math.Round(FuelTank:GetFuel() * 100,1).."%", Color(0,255,0,255) ) + end + + return + end + + local Key = input.LookupBinding( "+use" ) + + if not isstring( Key ) then Key = "[+use is not bound to a key]" end + + DrawText( FuelCap:GetPos(), "Press "..Key.." to Open", Color(255,255,0,255) ) + end +end + +function SWEP:Initialize() + self:SetHoldType( self.HoldType ) +end + +function SWEP:PrimaryAttack() + + self:SetNextPrimaryFire( CurTime() + 0.5 ) + + local ply = self:GetOwner() + + if not IsValid( ply ) then return end + + local startpos = ply:GetShootPos() + local endpos = startpos + ply:GetAimVector() * self.HitDistance + + local trace = util.TraceLine( { + start = startpos , + endpos = endpos, + filter = ply, + mask = MASK_SHOT_HULL + } ) + + if not IsValid( trace.Entity ) then + trace = util.TraceHull( { + start = startpos , + endpos = endpos, + filter = ply, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ), + mask = MASK_SHOT_HULL + } ) + end + + self:Refuel( trace ) +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Refuel( trace ) + local entity = trace.Entity + + if CLIENT or not IsValid( entity ) then return end + + local FuelCap = self:GetCap( entity ) + local FuelTank = self:GetTank( entity ) + + if not IsValid( FuelTank ) or FuelTank:GetDestroyed() then return end + + if FuelTank:GetBase():GetEngineActive() then + FuelTank:SetDestroyed(true) + end + + if IsValid( FuelCap ) then + if not FuelCap:IsOpen() then return end + + if (trace.HitPos - FuelCap:GetPos()):Length() > self.RangeToCap then return end + end + + if FuelTank:GetFuel() ~= 1 then + FuelTank:SetFuel( math.min( FuelTank:GetFuel() + (entity.lvsGasStationFillSpeed or 0.05), 1 ) ) + entity:OnRefueled() + end +end diff --git a/garrysmod/addons/tacrp/lua/autorun/sh_tacrp_load.lua b/garrysmod/addons/tacrp/lua/autorun/sh_tacrp_load.lua new file mode 100644 index 0000000..931c279 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/autorun/sh_tacrp_load.lua @@ -0,0 +1,40 @@ +AddCSLuaFile() + +TacRP = {} + +local searchdir = "tacrp/" + +local function includeFiles() + + for _, v in pairs(file.Find(searchdir .. "shared/*", "LUA")) do + include(searchdir .. "shared/" .. v) + AddCSLuaFile(searchdir .. "shared/" .. v) + end + + for _, v in pairs(file.Find(searchdir .. "client/*", "LUA")) do + AddCSLuaFile(searchdir .. "client/" .. v) + if CLIENT then + include(searchdir .. "client/" .. v) + end + end + + for _, v in pairs(file.Find(searchdir .. "client/vgui/*", "LUA")) do + AddCSLuaFile(searchdir .. "client/vgui/" .. v) + if CLIENT then + include(searchdir .. "client/vgui/" .. v) + end + end + + if SERVER then + for _, v in pairs(file.Find(searchdir .. "server/*", "LUA")) do + include(searchdir .. "server/" .. v) + end + end + +end + +includeFiles() + +searchdir = "tacrp_mod/" + +includeFiles() diff --git a/garrysmod/addons/tacrp/lua/autorun/swwf_launchers.lua b/garrysmod/addons/tacrp/lua/autorun/swwf_launchers.lua new file mode 100644 index 0000000..f24d83c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/autorun/swwf_launchers.lua @@ -0,0 +1,4 @@ +if CLIENT then + killicon.Add( "weapon_sw_fim92", "vgui/hud/weapon_sw_fim92", Color( 255, 255, 255, 255 ) ) + killicon.Add( "weapon_sw_9k38", "vgui/hud/weapon_sw_9k38", Color( 255, 255, 255, 255 ) ) +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_chargesmoke.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_chargesmoke.lua new file mode 100644 index 0000000..e654bd0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_chargesmoke.lua @@ -0,0 +1,75 @@ +function EFFECT:Init(data) + if CurTime() < 1 then self:Remove() return end + + self.TrailEnt = data:GetEntity() + if !IsValid(self.TrailEnt) then self:Remove() return end + + local pos = data:GetOrigin() + Vector(0, 0, 4) + local dir = data:GetNormal() + + local emitter = ParticleEmitter(pos) + local amt = 24 + + for i = 1, amt do + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir * math.Rand(-300, -100) + VectorRand() * 256 + Vector(0, 0, math.Rand(100, 300))) + smoke:SetGravity(Vector(0, 0, -300)) + smoke:SetStartAlpha(75) + smoke:SetEndAlpha(0) + smoke:SetStartSize(math.Rand(8, 12)) + smoke:SetEndSize(math.Rand(48, 64)) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(200) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(math.Rand(0.9, 1.25)) + end +end + +function EFFECT:Think() + if !IsValid(self.TrailEnt) or (self.TrailEnt:IsPlayer() and !self.TrailEnt:Alive()) or !self.TrailEnt:GetNWBool("TacRPChargeState") then + return false + end + return true +end + +function EFFECT:Render() + --if self.TrailEnt:IsOnGround() then + local pos = self.TrailEnt:GetPos() + Vector(math.Rand(-8, 8), math.Rand(-8, 8), 4) + + local emitter = ParticleEmitter(pos) + + local smoke = emitter:Add("particle/smokestack", pos) + if self.TrailEnt:IsOnGround() then + smoke:SetVelocity(self.TrailEnt:GetVelocity() * 0.2 + VectorRand() * 32 + Vector(0, 0, math.Rand(32, 64))) + smoke:SetGravity(Vector(0, 0, -128)) + + smoke:SetStartSize(math.Rand(8, 12)) + smoke:SetEndSize(math.Rand(48, 64)) + smoke:SetDieTime(math.Rand(0.8, 1)) + else + smoke:SetPos(pos + Vector(0, 0, 16)) + smoke:SetVelocity(VectorRand() * 32) + smoke:SetGravity(Vector(0, 0, -128)) + smoke:SetStartSize(16) + smoke:SetEndSize(32) + smoke:SetDieTime(math.Rand(0.4, 0.6)) + end + + smoke:SetStartAlpha(25) + smoke:SetEndAlpha(0) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(25) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + + emitter:Finish() + --end +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_confetti.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_confetti.lua new file mode 100644 index 0000000..851cdba --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_confetti.lua @@ -0,0 +1,66 @@ +function EFFECT:Init( data ) + + local vOffset = data:GetOrigin() + local dir = data:GetAngles() + local velocity = data:GetMagnitude() + local spread = data:GetScale() + + // sound.Play( "tacrp/kids_cheering.mp3", vOffset, 80, 100 ) + + local NumParticles = 100 + + local emitter = ParticleEmitter( vOffset, true ) + + local forward, up, right = dir:Forward(), dir:Up(), dir:Right() + + for i = 0, NumParticles do + + local oDir = Angle(dir) + local a = math.Rand(0, 360) + local angleRand = Angle(math.sin(a), math.cos(a), 0) + angleRand:Mul(spread * math.Rand(0, 45) * 1.4142135623730) + + oDir:RotateAroundAxis(right, angleRand.p) + oDir:RotateAroundAxis(up, angleRand.y) + oDir:RotateAroundAxis(forward, angleRand.r) + + local particle = emitter:Add( "particles/balloon_bit", vOffset + VectorRand(spread) ) + if ( particle ) then + + particle:SetVelocity( oDir:Forward() * math.Rand(0.5, 1) * velocity) + + particle:SetLifeTime( 0 ) + particle:SetDieTime( 10 ) + + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + + local Size = math.Rand( 1.5, 3 ) + particle:SetStartSize( Size ) + particle:SetEndSize( 0 ) + + particle:SetRoll( math.Rand( 0, 360 ) ) + particle:SetRollDelta( math.Rand( -2, 2 ) ) + + particle:SetAirResistance( 50 ) + particle:SetGravity( Vector( 0, 0, -75 ) ) + + particle:SetColor( math.Rand(50, 255), math.Rand(50, 255), math.Rand(50, 255) ) + + particle:SetCollide( true ) + + particle:SetAngleVelocity( Angle( math.Rand( -160, 160 ), math.Rand( -160, 160 ), math.Rand( -160, 160 ) ) ) + + particle:SetBounce( 0.7 ) + particle:SetLighting( true ) + end + end + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_dashsmoke.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_dashsmoke.lua new file mode 100644 index 0000000..f27953d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_dashsmoke.lua @@ -0,0 +1,84 @@ +function EFFECT:Init(data) + if CurTime() < 1 then self:Remove() return end + + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.EndTime = CurTime() + 0.25 + self.TrailEnt = data:GetEntity() + if !IsValid(self.TrailEnt) then self:Remove() return end + + local emitter = ParticleEmitter(pos) + local amt = 16 + + if IsValid(self.TrailEnt) and self.TrailEnt:IsOnGround() then + pos = pos + Vector(0, 0, 2) + + for i = 1, amt do + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir * -200 + VectorRand() * 128 + Vector(0, 0, math.Rand(50, 100))) + smoke:SetStartAlpha(200) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(24) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(150) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(0.5) + end + else + for i = 1, amt do + local _, a = LocalToWorld(Vector(), Angle((i / amt) * 360, 90, 0), pos, dir:Angle()) + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir * -50 + 150 * a:Up()) + smoke:SetStartAlpha(200) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(24) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(150) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(0.5) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + if CurTime() > self.EndTime or !IsValid(self.TrailEnt) or (self.TrailEnt:IsPlayer() and !self.TrailEnt:Alive()) then + return false + end + return true +end + +function EFFECT:Render() + local pos = self.TrailEnt:GetPos() + Vector( 0, 0, 1 ) + local emitter = ParticleEmitter(pos) + local d = math.Clamp((self.EndTime - CurTime()) / 0.15, 0, 1) ^ 2 + + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(VectorRand() * 4) + smoke:SetStartAlpha(d * 150) + smoke:SetEndAlpha(0) + smoke:SetStartSize(4) + smoke:SetEndSize(24) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(15) + smoke:SetCollide(false) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(0.25) + + emitter:Finish() +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_divesmoke.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_divesmoke.lua new file mode 100644 index 0000000..8a9a573 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_divesmoke.lua @@ -0,0 +1,78 @@ +function EFFECT:Init(data) + if CurTime() < 1 then self:Remove() return end + + local pos = data:GetOrigin() + local dir = data:GetNormal() + + self.EndTime = CurTime() + 0.2 + self.TrailEnt = data:GetEntity() + if !IsValid(self.TrailEnt) then self:Remove() return end + + local emitter = ParticleEmitter(pos) + + local tr = util.TraceLine({ + start = pos, + endpos = pos - Vector(0, 0, 2048), + mask = MASK_SOLID_BRUSHONLY + }) + + pos = pos - dir * 32 + local amt = 12 + math.ceil(tr.Fraction / 20) + + for i = 1, amt do + local _, a = LocalToWorld(Vector(), Angle((i / amt) * 360, 90, 0), pos, dir:Angle()) + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir * -(150 + tr.Fraction * 100) + (300 - tr.Fraction * 100) * a:Up()) + smoke:SetStartAlpha(20 + tr.Fraction * 50) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(24 + tr.Fraction * 16) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(50) + smoke:SetCollide(false) + smoke:SetBounce(0.2) + smoke:SetLighting(false) + smoke:SetDieTime(0.15 + tr.Fraction * 0.6) + end + + emitter:Finish() +end + +function EFFECT:Think() + if CurTime() > self.EndTime or !IsValid(self.TrailEnt) or (self.TrailEnt:IsPlayer() and !self.TrailEnt:Alive()) then + return false + end + return true +end + +function EFFECT:Render() + if self.TrailEnt:IsOnGround() then + local pos = self.TrailEnt:GetPos() + Vector(math.Rand(-8, 8), math.Rand(-8, 8), 4) + local dir = self.TrailEnt:GetVelocity():GetNormalized() + local vel = self.TrailEnt:GetVelocity():Length() + + local emitter = ParticleEmitter(pos) + + if engine.TickCount() % 2 == 0 then + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(VectorRand() * (50 + math.min(vel, 500) * 0.25) + dir * math.min(vel, 500) + Vector(0, 0, 200 - math.min(vel, 200))) + smoke:SetGravity(Vector(0, 0, -400)) + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + smoke:SetStartSize(4) + smoke:SetEndSize(32) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(25) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(math.Rand(0.75, 1)) + end + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_flare_explode.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_flare_explode.lua new file mode 100644 index 0000000..5fa62fc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_flare_explode.lua @@ -0,0 +1,92 @@ +EFFECT.Model = "models/Items/AR2_Grenade.mdl" + +function EFFECT:Init(data) + self:SetModel(self.Model) + local emitter = ParticleEmitter(data:GetOrigin()) + if not IsValid(emitter) then return end + for i = 1, 5 do + local smoke = emitter:Add("particle/smokestack", data:GetOrigin()) + smoke:SetVelocity(VectorRand() * 100) + smoke:SetGravity(Vector(math.Rand(-5, 5), math.Rand(-5, 5), 70)) + smoke:SetDieTime(math.Rand(5, 7)) + smoke:SetStartAlpha(40) + smoke:SetEndAlpha(0) + smoke:SetStartSize(math.Rand(20, 40)) + smoke:SetEndSize(60) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.5, 0.5)) + smoke:SetColor(175, 175, 175) + smoke:SetAirResistance(120) + smoke:SetPos(self:GetPos()) + smoke:SetLighting(false) + smoke:SetBounce(0.5) + smoke:SetCollide(true) + end + + for i = 1, 5 do + local smoke = emitter:Add("particle/smokestack", data:GetOrigin()) + smoke:SetVelocity(VectorRand() * 800) + smoke:SetGravity(Vector(math.Rand(-25, 25), math.Rand(-25, 25), -500)) + smoke:SetDieTime(math.Rand(0.6, 1)) + smoke:SetStartAlpha(100) + smoke:SetEndAlpha(0) + smoke:SetStartSize(80) + smoke:SetEndSize(250) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.5, 0.5)) + smoke:SetColor(100, 100, 100) + smoke:SetAirResistance(300) + smoke:SetPos(self:GetPos()) + smoke:SetLighting(false) + smoke:SetBounce(0.5) + smoke:SetCollide(true) + end + + for i = 1, 5 do + local fire = emitter:Add("effects/fire_cloud" .. math.random(1, 2), data:GetOrigin()) + fire:SetVelocity(VectorRand() * 1200 - Vector(0, 0, 100)) + fire:SetGravity(Vector(0, 0, 0)) + fire:SetDieTime(math.Rand(0.1, 0.25)) + fire:SetStartAlpha(150) + fire:SetEndAlpha(0) + fire:SetStartSize(math.Rand(15, 30)) + fire:SetEndSize(math.Rand(120, 160)) + fire:SetRoll(math.Rand(-180, 180)) + fire:SetRollDelta(math.Rand(-0.5, 0.5)) + fire:SetColor(200, 150, 50) + fire:SetAirResistance(300) + fire:SetPos(self:GetPos()) + fire:SetLighting(false) + fire:SetBounce(0.5) + fire:SetCollide(false) + end + + for i = 1, math.random(3, 5) do + local fire = emitter:Add("sprites/glow04_noz", data:GetOrigin()) + fire:SetVelocity(VectorRand() * 100 + Vector(0, 0, math.Rand(100, 300))) + fire:SetGravity(Vector(0, 0, -400)) + fire:SetDieTime(math.Rand(2, 3)) + fire:SetStartAlpha(200) + fire:SetEndAlpha(0) + fire:SetStartSize(math.Rand(3, 5)) + fire:SetEndSize(math.Rand(10, 20)) + fire:SetRoll(math.Rand(-180, 180)) + fire:SetRollDelta(math.Rand(-0.5, 0.5)) + fire:SetColor(255, 140, 80) + fire:SetAirResistance(2) + fire:SetPos(self:GetPos()) + fire:SetLighting(false) + fire:SetBounce(0.2) + fire:SetCollide(true) + end + emitter:Finish() + self:Remove() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() + return false +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_flashexplosion.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_flashexplosion.lua new file mode 100644 index 0000000..f16ef37 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_flashexplosion.lua @@ -0,0 +1,77 @@ +local images_smoke = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function TableRandomChoice(tbl) + return tbl[math.random(#tbl)] +end + +function EFFECT:Init(data) + self.Origin = data:GetOrigin() + + util.Decal("FadingScorch", self.Origin, self.Origin - Vector(0, 0, 16)) + + local emitter = ParticleEmitter( self.Origin + Vector( 0, 0, 16 ) ) + + for i = 0, 5 do + local particle = emitter:Add( TableRandomChoice(images_smoke) , self.Origin ) + local scol = math.Rand( 200, 225 ) + + particle:SetVelocity( 250 * VectorRand() ) + particle:SetDieTime( math.Rand(1.5, 5) ) + particle:SetStartAlpha( 50 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(100,200) ) + particle:SetEndSize( math.Rand(300,400) ) + particle:SetRoll( math.Rand(0,360) ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( scol,scol,scol ) + particle:SetAirResistance( 100 ) + particle:SetGravity( Vector( math.Rand(-30,30) ,math.Rand(-30,30),math.Rand(10,40)) ) + particle:SetLighting( true ) + particle:SetCollide( true ) + particle:SetBounce( 0.5 ) + end + + local particle = emitter:Add( "sprites/heatwave", self.Origin ) + particle:SetAirResistance( 0 ) + particle:SetDieTime( 1.5 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetStartSize( 250 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(180,480) ) + particle:SetRollDelta( math.Rand(-5,5) ) + particle:SetColor( 255, 255, 255 ) + + local fire = emitter:Add( "particle/fire", self.Origin ) + fire:SetAirResistance( 0 ) + fire:SetDieTime( 0.1 ) + fire:SetStartAlpha( 255 ) + fire:SetEndAlpha( 0 ) + fire:SetEndSize( 0 ) + fire:SetStartSize( data:GetRadius() ) + fire:SetRoll( math.Rand(180,480) ) + fire:SetRollDelta( math.Rand(-1,1) ) + fire:SetColor( 255, 255, 255 ) + + local light = DynamicLight(self:EntIndex()) + if (light) then + light.Pos = self.Origin + light.r = 255 + light.g = 255 + light.b = 255 + light.Brightness = 10 * data:GetScale() + light.Decay = 2500 + light.Size = data:GetRadius() + light.DieTime = CurTime() + 0.1 + end + + emitter:Finish() + +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_jumpsmoke.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_jumpsmoke.lua new file mode 100644 index 0000000..25eb01a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_jumpsmoke.lua @@ -0,0 +1,36 @@ +function EFFECT:Init(data) + local pos = data:GetOrigin() + local dir = data:GetNormal() + self.TrailEnt = data:GetEntity() + if !IsValid(self.TrailEnt) then self:Remove() return end + + local emitter = ParticleEmitter(pos) + local amt = 16 + + for i = 1, amt do + local _, a = LocalToWorld(Vector(0, 0, 2), Angle((i / amt) * 360, 90, 0), pos, dir:Angle()) + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(150 * a:Up()) + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(24) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(150) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(0.4) + end + + emitter:Finish() +end + +function EFFECT:Think() + return true +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_leapsmoke.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_leapsmoke.lua new file mode 100644 index 0000000..5954090 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_leapsmoke.lua @@ -0,0 +1,97 @@ +function EFFECT:Init(data) + local pos = data:GetOrigin() + local dir = data:GetNormal() + + local emitter = ParticleEmitter(pos) + local amt = 24 + + if IsValid(data:GetEntity()) and data:GetEntity():IsOnGround() and data:GetEntity():Crouching() then + pos = pos + Vector(0, 0, 4) + for i = 1, amt / 2 do + local _, a = LocalToWorld(Vector(), Angle((i / amt) * 360, 90, 0), pos, dir:Angle()) + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir * -50 + 300 * a:Up() + VectorRand() * 128) + smoke:SetGravity(Vector(0, 0, -200)) + smoke:SetStartAlpha(200) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(64) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(150) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(1) + end + + for i = 1, amt / 2 do + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir * math.Rand(400, 800) + VectorRand() * 128) + smoke:SetStartAlpha(200) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(32) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(150) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(0.75) + end + elseif IsValid(data:GetEntity()) and data:GetEntity():IsOnGround() then + pos = pos + Vector(0, 0, 2) + + local dir2 = Vector(dir) + dir2.z = 0 + dir2:Normalize() + + for i = 1, amt do + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir2 * math.Rand(-600, -200) + VectorRand() * 128 + Vector(0, 0, math.Rand(250, 400))) + smoke:SetGravity(Vector(0, 0, -300)) + smoke:SetStartAlpha(150) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(32) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(200) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(1) + end + else + for i = 1, amt do + local _, a = LocalToWorld(Vector(), Angle((i / amt) * 360, 90, 0), pos, dir:Angle()) + local smoke = emitter:Add("particle/smokestack", pos) + smoke:SetVelocity(dir * -50 + 150 * a:Up() + VectorRand() * 8) + smoke:SetStartAlpha(200) + smoke:SetEndAlpha(0) + smoke:SetStartSize(8) + smoke:SetEndSize(24) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.2, 0.2)) + smoke:SetColor(200, 200, 200) + smoke:SetAirResistance(150) + smoke:SetCollide(true) + smoke:SetBounce(0.2) + smoke:SetLighting(true) + smoke:SetDieTime(0.75) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_m202_explode.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_m202_explode.lua new file mode 100644 index 0000000..957ec4b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_m202_explode.lua @@ -0,0 +1,113 @@ +EFFECT.Model = "models/Items/AR2_Grenade.mdl" + +function EFFECT:Init(data) + self:SetModel(self.Model) + local emitter = ParticleEmitter(data:GetOrigin()) + if not IsValid(emitter) then return end + for i = 1, 10 do + local smoke = emitter:Add("particle/smokestack", data:GetOrigin()) + smoke:SetVelocity(VectorRand() * 100) + smoke:SetGravity(Vector(math.Rand(-5, 5), math.Rand(-5, 5), 70)) + smoke:SetDieTime(math.Rand(5, 7)) + smoke:SetStartAlpha(80) + smoke:SetEndAlpha(0) + smoke:SetStartSize(math.Rand(20, 40)) + smoke:SetEndSize(80) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.5, 0.5)) + smoke:SetColor(175, 175, 175) + smoke:SetAirResistance(120) + smoke:SetPos(self:GetPos()) + smoke:SetLighting(false) + smoke:SetBounce(0.5) + smoke:SetCollide(true) + end + + for i = 1, 10 do + local smoke = emitter:Add("particle/smokestack", data:GetOrigin()) + smoke:SetVelocity(VectorRand() * 800) + smoke:SetGravity(Vector(math.Rand(-25, 25), math.Rand(-25, 25), -500)) + smoke:SetDieTime(math.Rand(0.6, 1)) + smoke:SetStartAlpha(120) + smoke:SetEndAlpha(0) + smoke:SetStartSize(80) + smoke:SetEndSize(250) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-0.5, 0.5)) + smoke:SetColor(100, 100, 100) + smoke:SetAirResistance(300) + smoke:SetPos(self:GetPos()) + smoke:SetLighting(false) + smoke:SetBounce(0.5) + smoke:SetCollide(true) + end + + for i = 1, 10 do + local fire = emitter:Add("effects/fire_cloud" .. math.random(1, 2), data:GetOrigin()) + fire:SetVelocity(VectorRand() * 1200 - Vector(0, 0, 100)) + fire:SetGravity(Vector(0, 0, 0)) + fire:SetDieTime(math.Rand(0.1, 0.25)) + fire:SetStartAlpha(255) + fire:SetEndAlpha(0) + fire:SetStartSize(math.Rand(15, 30)) + fire:SetEndSize(math.Rand(180, 200)) + fire:SetRoll(math.Rand(-180, 180)) + fire:SetRollDelta(math.Rand(-0.5, 0.5)) + fire:SetColor(200, 200, 200) + fire:SetAirResistance(300) + fire:SetPos(self:GetPos()) + fire:SetLighting(false) + fire:SetBounce(0.5) + fire:SetCollide(false) + end + + for i = 1, 20 do + local fire = emitter:Add("effects/fire_embers" .. math.random(1, 3), data:GetOrigin()) + local v = VectorRand() * 1500 + v.z = v.z * 0.25 + fire:SetVelocity(v) + fire:SetGravity(Vector(0, 0, -750)) + fire:SetDieTime(math.Rand(1.2, 3)) + fire:SetStartAlpha(200) + fire:SetEndAlpha(0) + fire:SetStartSize(math.Rand(30, 50)) + fire:SetEndSize(math.Rand(40, 70)) + fire:SetRoll(math.Rand(-180, 180)) + fire:SetRollDelta(math.Rand(-0.5, 0.5)) + fire:SetColor(200, 200, 200) + fire:SetAirResistance(200) + fire:SetPos(self:GetPos()) + fire:SetLighting(false) + fire:SetBounce(0.5) + fire:SetCollide(true) + end + + for i = 1, math.random(6, 9) do + local fire = emitter:Add("sprites/glow04_noz", data:GetOrigin()) + fire:SetVelocity(VectorRand() * 100 + Vector(0, 0, math.Rand(300, 800))) + fire:SetGravity(Vector(0, 0, -400)) + fire:SetDieTime(math.Rand(2, 3)) + fire:SetStartAlpha(200) + fire:SetEndAlpha(0) + fire:SetStartSize(math.Rand(3, 5)) + fire:SetEndSize(math.Rand(10, 20)) + fire:SetRoll(math.Rand(-180, 180)) + fire:SetRollDelta(math.Rand(-0.5, 0.5)) + fire:SetColor(255, 180, 100) + fire:SetAirResistance(2) + fire:SetPos(self:GetPos()) + fire:SetLighting(false) + fire:SetBounce(0.2) + fire:SetCollide(true) + end + emitter:Finish() + self:Remove() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() + return false +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_muzzleeffect.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_muzzleeffect.lua new file mode 100644 index 0000000..46dc44c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_muzzleeffect.lua @@ -0,0 +1,65 @@ +function EFFECT:Init(data) + local wpn = data:GetEntity() + + if !IsValid(wpn) then self:Remove() return end + + if wpn:GetOwner() == LocalPlayer() and wpn:GetValue("ScopeHideWeapon") and wpn:IsInScope() then + self:Remove() + return + end + + local muzzle = TacRP.MuzzleEffects[data:GetFlags() or 1] or "muzzleflash_pistol" + if wpn.GetValue then + muzzle = wpn:GetValue("MuzzleEffect") + end + + local att = data:GetAttachment() or 1 + + local wm = false + + if (LocalPlayer():ShouldDrawLocalPlayer() or wpn.Owner != LocalPlayer()) then + wm = true + att = data:GetHitBox() + end + + local parent = wpn + + if !wm then + parent = LocalPlayer():GetViewModel() + end + + if wpn.GetMuzzleDevice then + parent = wpn:GetMuzzleDevice(wm) + else + parent = self + end + + -- if !IsValid(parent) then return end + + if muzzle then + if !istable(muzzle) then + muzzle = {muzzle} + end + + for _, muzzleeffect in ipairs(muzzle) do + local pcf = CreateParticleSystem(muz or parent, muzzleeffect, PATTACH_POINT_FOLLOW, att) + + if IsValid(pcf) then + pcf:StartEmission() + + if (muz or parent) != vm and !wm then + pcf:SetShouldDraw(false) + table.insert(wpn.MuzzPCFs, pcf) + end + end + end + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() + return false +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_nukeexplosion.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_nukeexplosion.lua new file mode 100644 index 0000000..127aaba --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_nukeexplosion.lua @@ -0,0 +1,130 @@ +local images_smoke = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function TableRandomChoice(tbl) + return tbl[math.random(#tbl)] +end + +local effecttime = 30 + +function EFFECT:Init(data) + self.Origin = data:GetOrigin() + + self.SpawnTime = CurTime() + + util.Decal("FadingScorch", self.Origin, self.Origin - Vector(0, 0, 16)) + + local emitter = ParticleEmitter( self.Origin + Vector( 0, 0, 16 ) ) + + // smoke cloud + + for i = 1,25 do + local particle = emitter:Add( TableRandomChoice(images_smoke) , self.Origin ) + local scol = math.Rand( 200, 225 ) + + particle:SetVelocity( 250 * VectorRand() ) + particle:SetDieTime( math.Rand(0.9, 1.1) * effecttime ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(300,400) ) + particle:SetEndSize( math.Rand(2000,2500) ) + particle:SetRoll( math.Rand(0,360) ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( scol,scol,scol ) + particle:SetAirResistance( 50 ) + particle:SetGravity( Vector( math.Rand(-250, 250), math.Rand(-250, 250), 500) ) + particle:SetLighting( false ) + end + + // stack + + for i = 1,25 do + local particle = emitter:Add( TableRandomChoice(images_smoke) , self.Origin ) + local scol = math.Rand( 200, 225 ) + + particle:SetVelocity( 250 * VectorRand() ) + particle:SetDieTime( math.Rand(0.9, 1.1) * effecttime ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(300,400) ) + particle:SetEndSize( math.Rand(1000,1500) ) + particle:SetRoll( math.Rand(0,360) ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( scol,scol,scol ) + particle:SetAirResistance( 50 ) + particle:SetGravity( Vector( math.Rand(-10, 10), math.Rand(-10, 10), math.Rand(0, 500)) ) + particle:SetLighting( false ) + end + + // wave + + local amt = 50 + + for i = 1, amt do + local particle = emitter:Add( TableRandomChoice(images_smoke) , self.Origin ) + local scol = math.Rand( 200, 225 ) + + particle:SetVelocity( VectorRand() * 8 + (Angle(0, i * (360 / amt), 0):Forward() * 750) ) + particle:SetDieTime( math.Rand(0.9, 1.1) * effecttime ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( math.Rand(300,400) ) + particle:SetEndSize( math.Rand(1000,1500) ) + particle:SetRoll( math.Rand(0,360) ) + particle:SetRollDelta( math.Rand(-1,1) ) + particle:SetColor( scol,scol,scol ) + particle:SetAirResistance( 5 ) + particle:SetLighting( false ) + particle:SetCollide( false ) + end + + local particle = emitter:Add( "sprites/heatwave", self.Origin ) + particle:SetAirResistance( 0 ) + particle:SetDieTime( effecttime ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetStartSize( 5000 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(180,480) ) + particle:SetRollDelta( math.Rand(-5,5) ) + particle:SetColor( 255, 255, 255 ) + + emitter:Finish() + + local light = DynamicLight(self:EntIndex()) + if (light) then + light.Pos = self.Origin + light.r = 255 + light.g = 255 + light.b = 255 + light.Brightness = 9 + light.Decay = 2500 + light.Size = 2048 + light.DieTime = CurTime() + 10 + end + +end + +function EFFECT:Think() + if (self.SpawnTime + effecttime) < CurTime() then + return false + else + return true + end +end + +local glaremat = Material("effects/ar2_altfire1b") + +function EFFECT:Render() + local d = (CurTime() - self.SpawnTime) / 15 + + d = 1 - d + + d = math.Clamp(d, 0, 1) + + cam.IgnoreZ(true) + + render.SetMaterial(glaremat) + render.DrawSprite(self.Origin, 15000 * d, 15000 * d, Color(255, 255, 255)) + + cam.IgnoreZ(false) +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_shelleffect.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_shelleffect.lua new file mode 100644 index 0000000..0ab5e3c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_shelleffect.lua @@ -0,0 +1,170 @@ +EFFECT.Type = 1 + +EFFECT.Pitch = 100 + +EFFECT.Model = "models/shells/shell_57.mdl" + +EFFECT.AlreadyPlayedSound = false +EFFECT.ShellTime = 0.5 +EFFECT.SpawnTime = 0 + +EFFECT.VMContext = true + +function EFFECT:Init(data) + + local att = data:GetAttachment() + local ent = data:GetEntity() + + self.Type = data:GetFlags() or self.Type + + local typetbl = TacRP.ShellTypes[self.Type] + + if !IsValid(ent) then self:Remove() return end + if !IsValid(ent:GetOwner()) then self:Remove() return end + + local origin, ang, dir + + if ent:GetOwner() == LocalPlayer() and ent:GetValue("ScopeHideWeapon") and ent:IsInScope() then + origin = EyePos() + + EyeAngles():Right() * ent.PassivePos.x + + EyeAngles():Forward() * ent.PassivePos.y + + EyeAngles():Up() * ent.PassivePos.z + ang = EyeAngles() + dir = ang:Right() -- not exactly correct but we can't rely on weapon model here + else + if LocalPlayer():ShouldDrawLocalPlayer() or ent:GetOwner() != LocalPlayer() then + mdl = ent + att = data:GetHitBox() + self.VMContext = false + else + mdl = LocalPlayer():GetViewModel() + table.insert(ent.ActiveEffects, self) + end + + if !IsValid(ent) then self:Remove() return end + if !mdl or !IsValid(mdl) then self:Remove() return end + if !mdl:GetAttachment(att) then self:Remove() return end + if !typetbl then return end + + origin = mdl:GetAttachment(att).Pos + ang = mdl:GetAttachment(att).Ang + + -- ang:RotateAroundAxis(ang:Up(), -90) + + -- ang:RotateAroundAxis(ang:Right(), (ent.ShellRotateAngle or Angle(0, 0, 0))[1]) + -- ang:RotateAroundAxis(ang:Up(), (ent.ShellRotateAngle or Angle(0, 0, 0))[2]) + -- ang:RotateAroundAxis(ang:Forward(), (ent.ShellRotateAngle or Angle(0, 0, 0))[3]) + + dir = ang:Forward() + + ang:RotateAroundAxis(ang:Forward(), 0) + ang:RotateAroundAxis(ang:Up(), 0) + end + + self:SetPos(origin) + self:SetModel(typetbl.Model) + self:SetModelScale(data:GetScale(), 0) + self:DrawShadow(true) + self:SetAngles(ang) + + self:SetNoDraw(true) + + self.Sounds = typetbl.Sounds + + local pb_vert = 2 + local pb_hor = 0.25 + + local mag = 150 + + self:PhysicsInitBox(Vector(-pb_vert,-pb_hor,-pb_hor), Vector(pb_vert,pb_hor,pb_hor)) + + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + + local phys = self:GetPhysicsObject() + + local plyvel = Vector(0, 0, 0) + + if IsValid(ent.Owner) then + plyvel = ent.Owner:GetAbsVelocity() + end + + phys:Wake() + phys:SetDamping(0, 0) + phys:SetMass(1) + phys:SetMaterial("gmod_silent") + + phys:SetVelocity((dir * mag * math.Rand(1, 2)) + plyvel) + + phys:AddAngleVelocity(VectorRand() * 100) + phys:AddAngleVelocity(ang:Up() * -2500 * math.Rand(0.75, 1.25)) + + local smoke = true + + if smoke and IsValid(mdl) then + local pcf = CreateParticleSystem(mdl, "port_smoke", PATTACH_POINT_FOLLOW, att) + + if IsValid(pcf) then + pcf:StartEmission() + end + + local smkpcf = CreateParticleSystem(self, "shellsmoke", PATTACH_ABSORIGIN_FOLLOW, 0) + + if IsValid(smkpcf) then + smkpcf:StartEmission() + end + + if self.VMContext then + table.insert(ent.PCFs, pcf) + table.insert(ent.PCFs, smkpcf) + + pcf:SetShouldDraw(false) + smkpcf:SetShouldDraw(false) + end + end + + self.SpawnTime = CurTime() +end + +function EFFECT:PhysicsCollide(colData) + if self.AlreadyPlayedSound then return end + local phys = self:GetPhysicsObject() + phys:SetVelocityInstantaneous(colData.HitNormal * -150) + + sound.Play(self.Sounds[math.random(#self.Sounds)], self:GetPos(), 65, 100, 1) + self:StopSound("Default.ImpactHard") + self.VMContext = false + self:SetNoDraw(false) + + self.AlreadyPlayedSound = true +end + +function EFFECT:Think() + if self:GetVelocity():Length() > 0 then self.SpawnTime = CurTime() end + self:StopSound("Default.ScrapeRough") + + if (self.SpawnTime + self.ShellTime) <= CurTime() then + if !IsValid(self) then return end + self:SetRenderFX( kRenderFxFadeFast ) + if (self.SpawnTime + self.ShellTime + 0.25) <= CurTime() then + if !IsValid(self:GetPhysicsObject()) then return end + self:GetPhysicsObject():EnableMotion(false) + if (self.SpawnTime + self.ShellTime + 0.5) <= CurTime() then + self:Remove() + return + end + end + end + return true +end + +function EFFECT:Render() + if !IsValid(self) then return end + + self:DrawModel() +end + +function EFFECT:DrawTranslucent() + if !IsValid(self) then return end + + self:DrawModel() +end diff --git a/garrysmod/addons/tacrp/lua/effects/tacrp_tracer.lua b/garrysmod/addons/tacrp/lua/effects/tacrp_tracer.lua new file mode 100644 index 0000000..adbb07f --- /dev/null +++ b/garrysmod/addons/tacrp/lua/effects/tacrp_tracer.lua @@ -0,0 +1,101 @@ +EFFECT.StartPos = Vector(0, 0, 0) +EFFECT.EndPos = Vector(0, 0, 0) +EFFECT.StartTime = 0 +EFFECT.LifeTime = 0.15 +EFFECT.LifeTime2 = 0.15 +EFFECT.DieTime = 0 +EFFECT.Color = Color(255, 255, 255) +EFFECT.Speed = 5000 + +local head = Material("particle/fire") +local tracer = Material("tacrp/tracer") + +function EFFECT:Init(data) + + local hit = data:GetOrigin() + local wep = data:GetEntity() + + if !IsValid(wep) then return end + local tacrp = wep.ArcticTacRP and wep.GetValue + + local start = data:GetStart() + if wep:GetOwner() == LocalPlayer() and tacrp and wep:GetValue("ScopeHideWeapon") and wep:IsInScope() then + start = EyePos() + + EyeAngles():Right() * wep.PassivePos.x + + EyeAngles():Forward() * wep.PassivePos.y + + EyeAngles():Up() * wep.PassivePos.z + elseif wep.GetTracerOrigin then + start = wep:GetTracerOrigin() + end + + if !start then + start = wep:GetPos() -- ??? + end + + local diff = hit - start + local dist = diff:Length() + + if !tacrp then + self.Speed = 15000 + elseif TacRP.ConVars["physbullet"]:GetBool() then + self.Speed = math.max(wep:GetValue("MuzzleVelocity") or data:GetScale(), 5000) + else + self.Speed = math.max(wep:GetValue("MuzzleVelocity") or data:GetScale(), dist / 0.4) + end + + self.LifeTime = dist / self.Speed + self.StartTime = UnPredictedCurTime() + self.DieTime = UnPredictedCurTime() + math.max(self.LifeTime, self.LifeTime2) + + self.StartPos = start + self.EndPos = hit + self.Dir = diff:GetNormalized() +end + +function EFFECT:Think() + return self.DieTime > UnPredictedCurTime() +end + +function EFFECT:Render() + + if !self.Dir then return end + + local d = (UnPredictedCurTime() - self.StartTime) / self.LifeTime + local startpos = self.StartPos + (d * 0.2 * (self.EndPos - self.StartPos)) + local endpos = self.StartPos + (d * (self.EndPos - self.StartPos)) + + --[[] + local col = LerpColor(d, self.Color, Color(0, 0, 0, 0)) + local col2 = LerpColor(d2, Color(255, 255, 255, 255), Color(0, 0, 0, 0)) + + render.SetMaterial(head) + render.DrawSprite(endpos, size * 3, size * 3, col) + + render.SetMaterial(tracer) + render.DrawBeam(endpos, startpos, size, 0, 1, col) + ]] + + local size = math.Clamp(math.log(EyePos():DistToSqr(endpos) - math.pow(256, 2)), 0, math.huge) + + local vel = self.Dir * self.Speed - LocalPlayer():GetVelocity() + + local dot = math.abs(EyeAngles():Forward():Dot(vel:GetNormalized())) + --dot = math.Clamp(((dot * dot) - 0.25) * 5, 0, 1) + local headsize = size * dot * 2 -- * math.min(EyePos():DistToSqr(pos) / math.pow(2500, 2), 1) + -- cam.Start3D() + + local col = Color(255, 225, 200) + -- local col = Color(255, 225, 200) + + render.SetMaterial(head) + render.DrawSprite(endpos, headsize, headsize, col) + + -- local tailpos = startpos + -- if (endpos - startpos):Length() > 512 then + -- tailpos = endpos - self.Dir * 512 + -- end + local tail = (self.Dir * math.min(self.Speed / 25, 512, (endpos - startpos):Length() - 64)) + + render.SetMaterial(tracer) + render.DrawBeam(endpos, endpos - tail, size * 0.75, 0, 1, col) +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo.lua new file mode 100644 index 0000000..63f8da6 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo.lua @@ -0,0 +1,173 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Ammo Pickup" +ENT.Category = "Tactical RP" + +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/ammoboxes/ammo_bag-1.mdl" +ENT.ModelOptions = nil + +ENT.InfiniteUse = false +ENT.OpeningAnim = false +ENT.NextUse = 0 +ENT.Open = false +ENT.MaxHealth = 0 + +function ENT:Initialize() + local model = self.Model + + if self.ModelOptions then + model = table.Random(self.ModelOptions) + end + + self:SetModel(model) + + if SERVER then + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(CONTINUOUS_USE) + self:PhysWake() + + if self.MaxHealth > 0 then + self:SetMaxHealth(self.MaxHealth) + self:SetHealth(self.MaxHealth) + end + + self:SetTrigger(true) -- Enables Touch() to be called even when not colliding + self:UseTriggerBounds(true, 24) + end +end + +local function ClampedGiveAmmo(ply, ammo, amt, clamp) + local count = ply:GetAmmoCount(ammo) + + if count >= clamp then + return false + elseif count + amt > clamp then + amt = math.max(clamp - count, 0) + end + + ply:GiveAmmo(amt, ammo) + + return true +end + +function ENT:ApplyAmmo(ply) + if self.NextUse > CurTime() then return end + + local wpn = ply:GetActiveWeapon() + + local ammotype = wpn:GetPrimaryAmmoType() + local clipsize = wpn:GetMaxClip1() + local supplyamount = clipsize * 1 + local max = clipsize * 6 + + local t2 + + if wpn.ArcticTacRP then + ammotype = wpn:GetAmmoType() + clipsize = wpn.ClipSize + max = (wpn:GetValue("SupplyAmmoAmount") or (clipsize * 6)) * (wpn:GetValue("SupplyLimit") or 1) + + if max <= 0 then + max = 1 + end + + if TacRP.ConVars["resupply_grenades"]:GetBool() then + local nade = wpn:GetGrenade() + + if nade.Ammo then + if !nade.AdminOnly or ply:IsAdmin() then + t2 = ClampedGiveAmmo(ply, nade.Ammo, 1, 3) + end + end + end + end + + local t = ClampedGiveAmmo(ply, ammotype, supplyamount, max) + + if t or t2 then + if self.OpeningAnim and !self.Open then + local seq = self:LookupSequence("open") + self:ResetSequence(seq) + self:EmitSound("items/ammocrate_open.wav") + + self.Open = true + end + + self.NextUse = CurTime() + 1 + + if !self.InfiniteUse then + self:Remove() + end + end +end + +ENT.CollisionSoundsHard = { + "physics/cardboard/cardboard_box_impact_hard1.wav", + "physics/cardboard/cardboard_box_impact_hard2.wav", + "physics/cardboard/cardboard_box_impact_hard3.wav", + "physics/cardboard/cardboard_box_impact_hard4.wav", + "physics/cardboard/cardboard_box_impact_hard5.wav", + "physics/cardboard/cardboard_box_impact_hard6.wav", + "physics/cardboard/cardboard_box_impact_hard7.wav", +} + +ENT.CollisionSoundsSoft = { + "physics/cardboard/cardboard_box_impact_soft1.wav", + "physics/cardboard/cardboard_box_impact_soft2.wav", + "physics/cardboard/cardboard_box_impact_soft3.wav", + "physics/cardboard/cardboard_box_impact_soft4.wav", + "physics/cardboard/cardboard_box_impact_soft5.wav", + "physics/cardboard/cardboard_box_impact_soft6.wav", + "physics/cardboard/cardboard_box_impact_soft7.wav", +} + +function ENT:PhysicsCollide(data) + if data.DeltaTime < 0.1 then return end + + if data.Speed > 25 then + self:EmitSound(self.CollisionSoundsHard[math.random(#self.CollisionSoundsHard)]) + else + self:EmitSound(self.CollisionSoundsSoft[math.random(#self.CollisionSoundsSoft)]) + end +end + +if SERVER then + + function ENT:Use(ply) + if !ply:IsPlayer() then return end + self:ApplyAmmo(ply) + end + + function ENT:Think() + if self.Open and (self.NextUse + 0.1) < CurTime() then + local seq = self:LookupSequence("close") + self:ResetSequence(seq) + self:EmitSound("items/ammocrate_close.wav") + + self.Open = false + end + + self:NextThink(CurTime()) + return true + end + +elseif CLIENT then + + function ENT:DrawTranslucent() + self:Draw() + end + + function ENT:Draw() + self:DrawModel() + end + +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_c4.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_c4.lua new file mode 100644 index 0000000..0a6792a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_c4.lua @@ -0,0 +1,15 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "C4 Charge (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/c4_charge-1.mdl" + +ENT.Ammo = "ti_c4" +ENT.GrenadeProxy = "tacrp_c4_detonator" diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_charge.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_charge.lua new file mode 100644 index 0000000..a6ce211 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_charge.lua @@ -0,0 +1,15 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Door Charge (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/door_charge-1.mdl" + +ENT.Ammo = "ti_charge" +ENT.GrenadeProxy = "tacrp_nade_charge" diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_crate.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_crate.lua new file mode 100644 index 0000000..dd906d4 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_crate.lua @@ -0,0 +1,28 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Ammo Crate" +ENT.Category = "Tactical RP" + +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/ammoboxes/ammo_box-2b.mdl" + +ENT.InfiniteUse = true +ENT.OpeningAnim = true + +ENT.AutomaticFrameAdvance = true + +ENT.CollisionSoundsHard = { + "physics/metal/metal_box_impact_hard1.wav", + "physics/metal/metal_box_impact_hard2.wav", + "physics/metal/metal_box_impact_hard3.wav", +} + +ENT.CollisionSoundsSoft = { + "physics/metal/metal_box_impact_soft1.wav", + "physics/metal/metal_box_impact_soft2.wav", + "physics/metal/metal_box_impact_soft3.wav", +} diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_crate_ttt.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_crate_ttt.lua new file mode 100644 index 0000000..526b30b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_crate_ttt.lua @@ -0,0 +1,286 @@ +if engine.ActiveGamemode() ~= "terrortown" then return end + +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.PrintName = "tacrp_ammocrate_name" + +ENT.Model = "models/weapons/tacint/ammoboxes/ammo_box-2b.mdl" +ENT.CanHavePrints = true +ENT.CanUseKey = true +ENT.MaxHealth = 250 + +ENT.MaxStored = 100 +ENT.RechargeRate = 1 +ENT.RechargeFreq = 2 + +ENT.AutomaticFrameAdvance = true + +ENT.CollisionSoundsHard = { + "physics/metal/metal_box_impact_hard1.wav", + "physics/metal/metal_box_impact_hard2.wav", + "physics/metal/metal_box_impact_hard3.wav", +} + +ENT.CollisionSoundsSoft = { + "physics/metal/metal_box_impact_soft1.wav", + "physics/metal/metal_box_impact_soft2.wav", + "physics/metal/metal_box_impact_soft3.wav", +} + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/frag_explode-1.wav", + "^TacRP/weapons/grenade/frag_explode-2.wav", + "^TacRP/weapons/grenade/frag_explode-3.wav", +} + +ENT.AmmoInfo = { + -- ammo per use, max, cost per use + ["357"] = {10, nil, 12}, -- 2 to fill + ["smg1"] = {30, nil, 10}, -- 2 to fill + ["pistol"] = {20, nil, 5}, -- 3 to fill + ["alyxgun"] = {12, nil, 6}, -- 3 to fill + ["buckshot"] = {8, nil, 8}, -- 3 to fill + + ["smg1_grenade"] = {1, 3, 34}, + ["rpg_round"] = {1, 2, 50}, + ["ti_sniper"] = {5, 10, 50}, +} + +if CLIENT then + + ENT.Icon = "entities/tacrp_ammo_crate.png" + + LANG.AddToLanguage("english", "tacrp_ammocrate_hint", "Press {usekey} to get ammo. Remaining charge: {num}") + LANG.AddToLanguage("english", "tacrp_ammocrate_broken", "Your Ammo Crate has been destroyed!") + LANG.AddToLanguage("english", "tacrp_ammocrate_subtitle", "Press {usekey} to receive ammo.") + LANG.AddToLanguage("english", "tacrp_ammocrate_charge", "Remaining charge: {charge}.") + LANG.AddToLanguage("english", "tacrp_ammocrate_empty", "No charge left") + + LANG.AddToLanguage("english", "tacrp_ammocrate_short_desc", "The ammo crate recharges over time") + + local TryT = LANG.TryTranslation + local ParT = LANG.GetParamTranslation + + ENT.TargetIDHint = { + name = "tacrp_ammocrate_name", + hint = "tacrp_ammocrate_hint", + fmt = function(ent, txt) + return ParT(txt, { + usekey = Key("+use", "USE"), + num = ent:GetStoredAmmo() or 0 + }) + end + } + + local key_params = { + usekey = Key("+use", "USE") + } + + -- TTT2 does this + hook.Add("TTTRenderEntityInfo", "tacrp_ammo_crate", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + if not IsValid(client) or not client:IsTerror() or not client:Alive() + or not IsValid(ent) or tData:GetEntityDistance() > 100 or ent:GetClass() ~= "tacrp_ammo_crate_ttt" then + return + end + + -- enable targetID rendering + tData:EnableText() + tData:EnableOutline() + tData:SetOutlineColor(client:GetRoleColor()) + + tData:SetTitle(TryT(ent.PrintName)) + tData:SetSubtitle(ParT("tacrp_ammocrate_subtitle", key_params)) + tData:SetKeyBinding("+use") + + local charge = ent:GetStoredAmmo() or 0 + + tData:AddDescriptionLine(TryT("tacrp_ammocrate_short_desc")) + + tData:AddDescriptionLine( + (charge > 0) and ParT("tacrp_ammocrate_charge", {charge = charge}) or TryT("tacrp_ammocrate_empty"), + (charge > 0) and roles.DETECTIVE.ltcolor or COLOR_ORANGE + ) + end) +end + +AccessorFuncDT(ENT, "StoredAmmo", "StoredAmmo") +AccessorFunc(ENT, "Placer", "Placer") + +function ENT:SetupDataTables() + self:DTVar("Int", 0, "StoredAmmo") +end + +function ENT:AddToStorage(amount) + self:SetStoredAmmo(math.min(self.MaxStored, self:GetStoredAmmo() + amount)) +end + +function ENT:TakeFromStorage(amount) + amount = math.min(amount, self:GetStoredAmmo()) + self:SetStoredAmmo(math.max(0, self:GetStoredAmmo() - amount)) + return amount +end + +function ENT:Initialize() + if SERVER then + self:SetModel(self.Model) + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(CONTINUOUS_USE) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:SetMass(150) + phys:Wake() + end + + if self.MaxHealth > 0 then + self:SetMaxHealth(self.MaxHealth) + self:SetHealth(self.MaxHealth) + end + + self:SetStoredAmmo(self.MaxStored) + + self:SetPlacer(nil) + + self.fingerprints = {} + end +end + +local function ClampedGiveAmmo(ply, ammo, amt, clamp) + local count = ply:GetAmmoCount(ammo) + + if count >= clamp then + return 0 + elseif count + amt > clamp then + amt = math.max(clamp - count, 0) + end + + return amt +end + +function ENT:ApplyAmmo(ply) + if (self.NextUse or 0) > CurTime() then return end + + local wpn = ply:GetActiveWeapon() + + local ammotype = string.lower(game.GetAmmoName(wpn:GetPrimaryAmmoType()) or "") + if !self.AmmoInfo[ammotype] then return end + + local max = self.AmmoInfo[ammotype][2] or wpn.Primary.ClipMax + local amt = self.AmmoInfo[ammotype][1] + amt = ClampedGiveAmmo(ply, ammotype, amt, max) -- amount we need + local cost = self.AmmoInfo[ammotype][3] * (amt / self.AmmoInfo[ammotype][1]) + local f = math.min(cost, self:GetStoredAmmo()) / cost -- fraction of cost we can afford + + amt = math.floor(amt * f) + + if amt > 0 then + self:TakeFromStorage(cost * f) + ply:GiveAmmo(amt, ammotype) + + if !self.Open then + local seq = self:LookupSequence("open") + self:ResetSequence(seq) + self:EmitSound("items/ammocrate_open.wav") + + self.Open = true + end + + self.NextUse = CurTime() + 1 + end +end + +function ENT:PhysicsCollide(data) + if data.DeltaTime < 0.1 then return end + + if data.Speed > 25 then + self:EmitSound(self.CollisionSoundsHard[math.random(#self.CollisionSoundsHard)]) + else + self:EmitSound(self.CollisionSoundsSoft[math.random(#self.CollisionSoundsSoft)]) + end +end + +if SERVER then + + function ENT:Use(ply) + if !ply:IsPlayer() then return end + self:ApplyAmmo(ply) + end + function ENT:Think() + if self.Open and (self.NextUse + 0.1) < CurTime() then + local seq = self:LookupSequence("close") + self:ResetSequence(seq) + self:EmitSound("items/ammocrate_close.wav") + + self.Open = false + end + + if (self.NextCharge or 0) < CurTime() then + self:AddToStorage(self.RechargeRate) + self.NextCharge = CurTime() + self.RechargeFreq + end + + self:NextThink(CurTime()) + return true + end + +elseif CLIENT then + + function ENT:DrawTranslucent() + self:Draw() + end + + function ENT:Draw() + self:DrawModel() + end + +end + +function ENT:OnTakeDamage(dmginfo) + if self.BOOM then return end + + self:TakePhysicsDamage(dmginfo) + self:SetHealth(self:Health() - dmginfo:GetDamage()) + local att = dmginfo:GetAttacker() + local placer = self:GetPlacer() + + if IsPlayer(att) then + DamageLog(Format("DMG: \t %s [%s] damaged ammo crate [%s] for %d dmg", att:Nick(), att:GetRoleString(), IsPlayer(placer) and placer:Nick() or "", dmginfo:GetDamage())) + end + + if self:Health() < 0 then + self:Remove() + util.EquipmentDestroyed(self:GetPos()) + + if IsValid(self:GetPlacer()) then + LANG.Msg(self:GetPlacer(), "tacrp_ammocrate_broken") + end + + self.BOOM = true + + local dmg = self:GetStoredAmmo() * 2.25 + 75 + + util.BlastDamage(self, dmginfo:GetAttacker(), self:GetPos(), 400, dmg) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound(table.Random(self.ExplodeSounds), 125, 90) + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_fire.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_fire.lua new file mode 100644 index 0000000..28d2079 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_fire.lua @@ -0,0 +1,16 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Thermite Grenade (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.Ammo = "ti_thermite" +ENT.Material = "models/tacint/weapons/w_models/smoke/thermite-1" +ENT.GrenadeProxy = "tacrp_nade_thermite" diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_flashbang.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_flashbang.lua new file mode 100644 index 0000000..608df18 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_flashbang.lua @@ -0,0 +1,15 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Flashbang (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/flashbang.mdl" + +ENT.Ammo = "ti_flashbang" +ENT.GrenadeProxy = "tacrp_nade_flashbang" diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_frag.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_frag.lua new file mode 100644 index 0000000..4e425b5 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_frag.lua @@ -0,0 +1,85 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Frag Grenade (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/frag.mdl" + +ENT.Ammo = "grenade" +ENT.GrenadeProxy = "tacrp_nade_frag" + +function ENT:Initialize() + self:SetModel(self.Model) + + if SERVER then + self:SetMaterial(self.Material or "") + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(CONTINUOUS_USE) + self:PhysWake() + + self:SetTrigger(true) -- Enables Touch() to be called even when not colliding + self:UseTriggerBounds(true, 24) + end +end + +function ENT:ApplyAmmo(ply) + if self.Used then return end + + self.Used = true + ply:GiveAmmo(1, self.Ammo) + self:Remove() +end + +ENT.CollisionSoundsHard = { + "physics/metal/weapon_impact_hard1.wav", + "physics/metal/weapon_impact_hard2.wav", + "physics/metal/weapon_impact_hard3.wav", +} + +ENT.CollisionSoundsSoft = { + "physics/metal/weapon_impact_soft1.wav", + "physics/metal/weapon_impact_soft2.wav", + "physics/metal/weapon_impact_soft3.wav", +} + +function ENT:PhysicsCollide(data) + if data.DeltaTime < 0.1 then return end + + if data.Speed > 25 then + self:EmitSound(self.CollisionSoundsHard[math.random(#self.CollisionSoundsHard)]) + else + self:EmitSound(self.CollisionSoundsSoft[math.random(#self.CollisionSoundsSoft)]) + end +end + +if SERVER then + + function ENT:Use(ply) + if !ply:IsPlayer() then return end + self:ApplyAmmo(ply) + if self.GrenadeProxy then + ply:Give(self.GrenadeProxy, true) + end + end + +elseif CLIENT then + + function ENT:DrawTranslucent() + self:Draw() + end + + function ENT:Draw() + self:DrawModel() + end + +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_gas.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_gas.lua new file mode 100644 index 0000000..3ed2036 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_gas.lua @@ -0,0 +1,16 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "CS Gas Grenade (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.Ammo = "ti_gas" +ENT.Material = "models/tacint/weapons/w_models/smoke/gas-1" +ENT.GrenadeProxy = "tacrp_nade_gas" diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_heal.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_heal.lua new file mode 100644 index 0000000..fa7dc7d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_heal.lua @@ -0,0 +1,16 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Medi-Smoke Canister (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.Ammo = "ti_heal" +ENT.Material = "models/tacint/weapons/w_models/smoke/heal-1" +ENT.GrenadeProxy = "tacrp_nade_heal" diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_nuke.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_nuke.lua new file mode 100644 index 0000000..823d161 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_nuke.lua @@ -0,0 +1,28 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Nuclear Device (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = true +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/props_misc/briefcase_bomb-1.mdl" + +ENT.Ammo = "ti_nuke" + +ENT.CollisionSoundsHard = { + "physics/metal/metal_box_impact_hard1.wav", + "physics/metal/metal_box_impact_hard2.wav", + "physics/metal/metal_box_impact_hard3.wav", +} + +ENT.CollisionSoundsSoft = { + "physics/metal/metal_box_impact_soft1.wav", + "physics/metal/metal_box_impact_soft2.wav", + "physics/metal/metal_box_impact_soft3.wav", +} + +ENT.GrenadeProxy = false diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_smoke.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_smoke.lua new file mode 100644 index 0000000..83427db --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_ammo_smoke.lua @@ -0,0 +1,15 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "tacrp_ammo_frag" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Smoke Grenade (Ammo)" +ENT.Category = "Tactical RP" + +ENT.AdminOnly = false +ENT.Spawnable = true +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.Ammo = "ti_smoke" +ENT.GrenadeProxy = "tacrp_nade_smoke" diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_att.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_att.lua new file mode 100644 index 0000000..83078fe --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_att.lua @@ -0,0 +1,167 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Attachment" +ENT.Category = "Tactical Intervention - Attachments" + +ENT.Spawnable = false +ENT.Model = "models/tacint/props_containers/supply_case-2.mdl" +ENT.AttToGive = nil + +function ENT:Initialize() + local model = self.Model + + self:SetModel(model) + + if SERVER then + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(SIMPLE_USE) + self:PhysWake() + + self:SetTrigger(true) -- Enables Touch() to be called even when not colliding + self:UseTriggerBounds(true, 24) + end +end + +function ENT:GiveAtt(ply) + if !self.AttToGive then return end + + if TacRP.ConVars["free_atts"]:GetBool() then + ply:PrintMessage(HUD_PRINTTALK, "All attachments are free! This is not necessary!") + return + end + + if TacRP.ConVars["lock_atts"]:GetBool() and TacRP:PlayerGetAtts(ply, self.AttToGive) > 0 then + ply:PrintMessage(HUD_PRINTTALK, "You already have this attachment!") + return + end + + local given = TacRP:PlayerGiveAtt(ply, self.AttToGive, 1) + if given then + TacRP:PlayerSendAttInv(ply) + self:EmitSound("TacRP/weapons/flashlight_on.wav") + self:Remove() + end +end + +ENT.CollisionSoundsHard = { + "physics/metal/metal_box_impact_hard1.wav", + "physics/metal/metal_box_impact_hard2.wav", + "physics/metal/metal_box_impact_hard3.wav", +} + +ENT.CollisionSoundsSoft = { + "physics/metal/metal_box_impact_soft1.wav", + "physics/metal/metal_box_impact_soft2.wav", + "physics/metal/metal_box_impact_soft3.wav", +} + +function ENT:PhysicsCollide(data) + if data.DeltaTime < 0.1 then return end + + if data.Speed > 40 then + self:EmitSound(self.CollisionSoundsHard[math.random(#self.CollisionSoundsHard)], 70, 100, data.Speed / 100) + else + self:EmitSound(self.CollisionSoundsSoft[math.random(#self.CollisionSoundsSoft)], 70, 100, data.Speed / 100) + end +end + +if SERVER then + + function ENT:Use(ply) + if !ply:IsPlayer() then return end + self:GiveAtt(ply) + end + +elseif CLIENT then + + function ENT:DrawTranslucent() + self:Draw() + end + + local font = "TacRP_Myriad_Pro_48_Unscaled" + local iw2 = 128 + + local drawit = function(self, p, a, alpha) + cam.Start3D2D(p, a, 0.05) + -- surface.SetFont("TacRP_LondonBetween_24_Unscaled") + + if !self.PrintLines then + self.PrintLines = TacRP.MultiLineText(TacRP:GetAttName(self.AttToGive), 328, font) + end + for j, text in ipairs(self.PrintLines or {}) do + draw.SimpleTextOutlined(text, font, 0, -40 + j * 40, Color(255, 255, 255, alpha * 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 2, Color(0, 0, 0, alpha * 75)) + end + + -- local w2 = surface.GetTextSize(self.PrintName) + + -- surface.SetTextPos(-w2 / 2, 0) + -- surface.SetTextColor(255, 255, 255, 255) + -- surface.DrawText(self.PrintName) + + surface.SetDrawColor(255, 255, 255, alpha * 255) + surface.SetMaterial(self.Icon or defaulticon) + surface.DrawTexturedRect(-iw2 / 2, -iw2 - 8, iw2, iw2) + cam.End3D2D() + end + + function ENT:Draw() + self:DrawModel() + local distsqr = (EyePos() - self:WorldSpaceCenter()):LengthSqr() + if distsqr <= 262144 then -- 512^2 + local a = math.Clamp(1 - (distsqr - 196608) / 65536, 0, 1) + local ang = self:GetAngles() + + ang:RotateAroundAxis(ang:Forward(), 180) + ang:RotateAroundAxis(ang:Right(), 90) + ang:RotateAroundAxis(ang:Up(), 90) + + local pos = self:GetPos() + + pos = pos + ang:Forward() * 0 + pos = pos + ang:Up() * 4.1 + pos = pos + ang:Right() * -8 + + drawit(self, pos, ang, a) + + -- cam.Start3D2D(pos, ang, 0.1) + -- surface.SetFont("TacRP_LondonBetween_24_Unscaled") + + -- local w = surface.GetTextSize(self.PrintName) + + -- surface.SetTextPos(-w / 2, 0) + -- surface.SetTextColor(255, 255, 255, 255) + -- surface.DrawText(self.PrintName) + + -- surface.SetDrawColor(255, 255, 255) + -- surface.SetMaterial(self.Icon or defaulticon) + -- local iw = 64 + -- surface.DrawTexturedRect(-iw / 2, -iw - 8, iw, iw) + -- cam.End3D2D() + + local ang2 = self:GetAngles() + + ang2:RotateAroundAxis(ang2:Forward(), 90) + ang2:RotateAroundAxis(ang2:Right(), -90) + ang2:RotateAroundAxis(ang2:Up(), 0) + + local pos2 = self:GetPos() + + pos2 = pos2 + ang2:Forward() * 0 + pos2 = pos2 + ang2:Up() * 4.1 + pos2 = pos2 + ang2:Right() * -8 + + drawit(self, pos2, ang2, a) + + + end + end + +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_bench.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_bench.lua new file mode 100644 index 0000000..8b589fb --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_bench.lua @@ -0,0 +1,68 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.PrintName = "Customization Bench" +ENT.Category = "Tactical RP" + +ENT.Spawnable = true +ENT.Model = "models/props_canal/winch02.mdl" + +function ENT:Initialize() + local model = self.Model + + self:SetModel(model) + + if SERVER then + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(SIMPLE_USE) + self:PhysWake() + end + + -- removal is handled when checking position + table.insert(TacRP.Benches, self) +end + +local font = "TacRP_Myriad_Pro_24_Unscaled" + +function ENT:DrawTranslucent() + + local distsqr = (EyePos() - self:WorldSpaceCenter()):LengthSqr() + if distsqr <= 262144 then -- 512^2 + local a = 1 --math.Clamp(1 - (distsqr - 196608) / 65536, 0, 1) + local ang = self:GetAngles() + + ang:RotateAroundAxis(ang:Forward(), 180) + ang:RotateAroundAxis(ang:Right(), 90) + ang:RotateAroundAxis(ang:Up(), 90) + + local pos = self:GetPos() + + pos = pos + ang:Forward() * 2.75 + pos = pos + ang:Up() * 9 + pos = pos + ang:Right() * -33 + + cam.Start3D2D(pos, ang, 0.075) + surface.SetFont(font) + local w = surface.GetTextSize(self.PrintName) + surface.SetTextPos(-w / 2, 0) + surface.SetTextColor(255, 255, 255) + surface.DrawText(self.PrintName) + if distsqr <= 16384 then + local t2 = "READY" + local w2 = surface.GetTextSize(t2) + surface.SetTextPos(-w2 / 2, 30) + surface.SetTextColor(100, 255, 100) + surface.DrawText(t2) + end + cam.End3D2D() + end +end + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_droppedmag.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_droppedmag.lua new file mode 100644 index 0000000..8c395d2 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_droppedmag.lua @@ -0,0 +1,143 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Dropped Magazine" +ENT.Category = "" + +ENT.Spawnable = false +ENT.Model = "" +ENT.FadeTime = 5 +ENT.CanHavePrints = true -- TTT + +ENT.ImpactSounds = { + ["pistol"] = { + "TacRP/weapons/drop_magazine_pistol-1.wav", + "TacRP/weapons/drop_magazine_pistol-2.wav", + "TacRP/weapons/drop_magazine_pistol-3.wav", + "TacRP/weapons/drop_magazine_pistol-4.wav", + "TacRP/weapons/drop_magazine_pistol-5.wav", + }, + ["metal"] = { + "TacRP/weapons/drop_magazine_metal-1.wav", + "TacRP/weapons/drop_magazine_metal-2.wav", + "TacRP/weapons/drop_magazine_metal-3.wav", + "TacRP/weapons/drop_magazine_metal-4.wav", + }, + ["plastic"] = { + "TacRP/weapons/drop_magazine_plastic-1.wav", + "TacRP/weapons/drop_magazine_plastic-2.wav" + }, + ["bullet"] = { + "player/pl_shell1.wav", + "player/pl_shell2.wav", + "player/pl_shell3.wav" + }, + ["shotgun"] = { + "weapons/fx/tink/shotgun_shell1.wav", + "weapons/fx/tink/shotgun_shell2.wav", + "weapons/fx/tink/shotgun_shell3.wav", + }, + ["spoon"] = { + "TacRP/weapons/grenade/spoon_bounce-1.wav", + "TacRP/weapons/grenade/spoon_bounce-2.wav", + "TacRP/weapons/grenade/spoon_bounce-3.wav", + } +} + +ENT.ImpactType = "pistol" + +ENT.AmmoType = nil +ENT.AmmoCount = nil + +function ENT:Initialize() + self:SetModel(self.Model) + + if SERVER then + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetUseType(SIMPLE_USE) + + self:PhysWake() + + local phys = self:GetPhysicsObject() + if !phys:IsValid() then + self:PhysicsInitBox(Vector(-1, -1, -1), Vector(1, 1, 1)) + end + end + + self.SpawnTime = CurTime() + if engine.ActiveGamemode() == "terrortown" and TacRP.ConVars["ttt_magazine_dna"]:GetBool() then + self.FadeTime = 600 + if SERVER then + self.fingerprints = {} + table.insert(self.fingerprints, self:GetOwner()) + end + end + + if self.AmmoType and self.AmmoCount then + self.FadeTime = math.max(self.FadeTime, 120) + self:SetOwner(NULL) -- Owner can't +USE their own entities + end +end + +function ENT:PhysicsCollide(colData, collider) + if colData.DeltaTime < 0.5 then return end + + local tbl = self.ImpactSounds[self.ImpactType] + + local snd = "" + + if tbl then + snd = table.Random(tbl) + end + + self:EmitSound(snd) +end + +function ENT:Think() + if !self.SpawnTime then + self.SpawnTime = CurTime() + end + + if SERVER and (self.SpawnTime + self.FadeTime) <= CurTime() then + + self:SetRenderFX( kRenderFxFadeFast ) + + if (self.SpawnTime + self.FadeTime + 1) <= CurTime() then + + if IsValid(self:GetPhysicsObject()) then + self:GetPhysicsObject():EnableMotion(false) + end + + if SERVER then + if (self.SpawnTime + self.FadeTime + 1.5) <= CurTime() then + self:Remove() + return + end + end + end + end +end + +function ENT:Use(ply) + if self.AmmoType and (self.AmmoCount or 0) > 0 and (self.SpawnTime + 0.5 <= CurTime()) then + local given = ply:GiveAmmo(self.AmmoCount, self.AmmoType) + self.AmmoCount = self.AmmoCount - given + if self.AmmoCount <= 0 then + self:Remove() + end + end +end + +function ENT:DrawTranslucent() + self:Draw() +end + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_fire_cloud.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_fire_cloud.lua new file mode 100644 index 0000000..ff9a53a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_fire_cloud.lua @@ -0,0 +1,309 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Fire Particle" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.Model = "models/Items/AR2_Grenade.mdl" + +ENT.FireTime = 8 + +ENT.Armed = false + +ENT.NextDamageTick = 0 + +ENT.Ticks = 0 + +AddCSLuaFile() + +function ENT:Initialize() + self.SpawnTime = CurTime() + + if SERVER then + self:SetModel( self.Model ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + local maxs = Vector(1, 1, 1) + local mins = -maxs + self:PhysicsInitBox(mins, maxs) + self:DrawShadow( false ) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:SetBuoyancyRatio(0) + end + + self:Detonate() + + -- self.FireTime = self.FireTime * math.Rand(0.8, 1.2) + -- self:SetNWFloat("FireTime", CurTime() + self.FireTime) + + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + end +end + +function ENT:Think() + if CLIENT then + local d = Lerp((self.SpawnTime + self.FireTime - CurTime()) / 8, 1, 0.000001) ^ 2 + + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex()) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 135 + self.Light.b = 0 + self.Light.Brightness = 5 + self.Light.Size = math.Clamp(TacRP.ConVars["thermite_radius"]:GetFloat(), 128, 512) * 1.5 + self.Light.DieTime = CurTime() + self.FireTime + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + local emitter = ParticleEmitter(self:GetPos()) + + if !self:IsValid() or self:WaterLevel() > 2 then return end + if !IsValid(emitter) then return end + + if self.Ticks % math.ceil(2 + d * 8) == 0 then + local fire = emitter:Add("particles/smokey", self:GetPos() + Vector(math.Rand(-32, 32), math.Rand(-32, 32), 0)) + fire:SetVelocity( (VectorRand() * 500) + (self:GetAngles():Up() * 300) ) + fire:SetGravity( Vector(0, 0, 1500) ) + fire:SetDieTime( math.Rand(0.5, 1) ) + fire:SetStartAlpha( 100 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 10 ) + fire:SetEndSize( 150 ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 250 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(0.75) + fire:SetNextThink( CurTime() + FrameTime() ) + fire:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 135, 0) + local col2 = Color(255, 255, 255) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + end + + if self.Ticks % math.ceil(1 + d * 6) == 0 then + local fire = emitter:Add("effects/spark", self:GetPos() + Vector(math.Rand(-32, 32), math.Rand(-32, 32), 0)) + fire:SetVelocity( VectorRand() * 750 ) + fire:SetGravity( Vector(math.Rand(-5, 5), math.Rand(-5, 5), -2000) ) + fire:SetDieTime( math.Rand(0.5, 1) ) + fire:SetStartAlpha( 255 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 5 ) + fire:SetEndSize( 0 ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 50 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(0.8) + fire.Ticks = 0 + end + + self.NextFlareTime = self.NextFlareTime or CurTime() + + if self.NextFlareTime <= CurTime() then + self.NextFlareTime = CurTime() + math.Rand(0.1, 0.5) + local fire = emitter:Add("sprites/orangeflare1", self:GetPos()) + fire:SetVelocity( VectorRand() * 750 ) + fire:SetGravity( Vector(math.Rand(-5, 5), math.Rand(-5, 5), -2000) ) + fire:SetDieTime( math.Rand(1, 2) ) + fire:SetStartAlpha( 255 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 50 ) + fire:SetEndSize( 0 ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 50 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(0.8) + fire.Ticks = 0 + fire:SetNextThink( CurTime() + FrameTime() ) + fire:SetThinkFunction( function(pa) + if !pa then return end + + local aemitter = ParticleEmitter(pa:GetPos()) + + local d = pa:GetLifeTime() / pa:GetDieTime() + + if !IsValid(aemitter) then return end + + if pa.Ticks % 5 == 0 then + local afire = aemitter:Add("particles/smokey", pa:GetPos()) + afire:SetVelocity( VectorRand() * 5 ) + afire:SetGravity( Vector(0, 0, 1500) ) + afire:SetDieTime( math.Rand(0.25, 0.5) * d ) + afire:SetStartAlpha( 255 ) + afire:SetEndAlpha( 0 ) + afire:SetStartSize( 5 * d ) + afire:SetEndSize( 20 ) + afire:SetRoll( math.Rand(-180, 180) ) + afire:SetRollDelta( math.Rand(-0.2,0.2) ) + afire:SetColor( 255, 255, 255 ) + afire:SetAirResistance( 150 ) + afire:SetPos( pa:GetPos() ) + afire:SetLighting( false ) + afire:SetCollide(true) + afire:SetBounce(0.9) + afire:SetNextThink( CurTime() + FrameTime() ) + afire:SetThinkFunction( function(apa) + if !apa then return end + local col1 = Color(255, 135, 0) + local col2 = Color(255, 255, 255) + + local col3 = col1 + local d2 = apa:GetLifeTime() / apa:GetDieTime() + col3.r = Lerp(d2, col1.r, col2.r) + col3.g = Lerp(d2, col1.g, col2.g) + col3.b = Lerp(d2, col1.b, col2.b) + + apa:SetColor(col3.r, col3.g, col3.b) + apa:SetNextThink( CurTime() + FrameTime() ) + end ) + end + + aemitter:Finish() + + pa.Ticks = pa.Ticks + 1 + + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + end + + emitter:Finish() + + self.Ticks = self.Ticks + 1 + else + + if !self:GetOwner():IsValid() then self:Remove() return end + + if self:GetVelocity():LengthSqr() <= 32 then + self:SetMoveType( MOVETYPE_NONE ) + end + + if self.NextDamageTick > CurTime() then return end + + if self:WaterLevel() > 2 then self:Remove() return end + + local dmg = DamageInfo() + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(Lerp((self.SpawnTime + self.FireTime - CurTime()) / self.FireTime, TacRP.ConVars["thermite_damage_max"]:GetFloat(), TacRP.ConVars["thermite_damage_min"]:GetFloat())) + dmg:SetInflictor(self) + dmg:SetAttacker(self:GetOwner()) + util.BlastDamageInfo(dmg, IsValid(self:GetParent()) and self:GetParent():GetPos() or self:GetPos(), TacRP.ConVars["thermite_radius"]:GetFloat()) + + if self.SpawnTime + self.FireTime <= CurTime() then self:Remove() return end + + self.NextDamageTick = CurTime() + 0.2 + self:NextThink(self.NextDamageTick) + return true + end +end + +function ENT:OnRemove() + if self.Light then + self.Light.dietime = CurTime() + 0.5 + self.Light.decay = 2000 + end + if !self.FireSound then return end + self.FireSound:Stop() +end + +function ENT:Detonate() + if !self:IsValid() then return end + + self.Armed = true + + if self.Order and self.Order != 1 then return end + + -- self.FireSound = CreateSound(self, "tacrp_extras/grenades/fire_loop_1.wav") + self.FireSound = CreateSound(self, "ambient/gas/steam2.wav") + self.FireSound:Play() + self.FireSound:ChangePitch(120) + + self.FireSound:ChangePitch(100, self.FireTime) + + timer.Simple(self.FireTime - 1, function() + if !IsValid(self) then return end + + self.FireSound:ChangeVolume(0, 1) + end) + + timer.Simple(self.FireTime, function() + if !IsValid(self) then return end + + self:Remove() + end) +end + +function ENT:Draw() + -- cam.Start3D() -- Start the 3D function so we can draw onto the screen. + -- render.SetMaterial( GetFireParticle() ) -- Tell render what material we want, in this case the flash from the gravgun + -- render.DrawSprite( self:GetPos(), math.random(200, 250), math.random(200, 250), Color(255, 255, 255) ) -- Draw the sprite in the middle of the map, at 16x16 in it's original colour with full alpha. + -- cam.End3D() +end + +local directfiredamage = { + ["npc_zombie"] = true, + ["npc_zombie_torso"] = true, + ["npc_fastzombie"] = true, + ["npc_fastzombie_torso"] = true, + ["npc_poisonzombie"] = true, + ["npc_zombine"] = true, + ["npc_headcrab"] = true, + ["npc_headcrab_fast"] = true, + ["npc_headcrab_black"] = true, + ["npc_headcrab_poison"] = true, +} + +hook.Add("EntityTakeDamage", "tacrp_fire_cloud", function(ent, dmginfo) + if IsValid(dmginfo:GetInflictor()) and dmginfo:GetInflictor():GetClass() == "tacrp_fire_cloud" and dmginfo:GetDamageType() == DMG_BURN then + if ent:IsNPC() then + if directfiredamage[ent:GetClass()] then + dmginfo:SetDamageType(DMG_SLOWBURN) -- DMG_BURN does not hurt HL2 zombies and instead turns them black. + elseif ent.Immune_Fire then + dmginfo:SetDamageType(DMG_DIRECT) -- Bitch + end + elseif !ent:IsNextBot() and !ent:IsPlayer() then + if ent:GetClass() == "prop_physics" then + dmginfo:SetDamageType(DMG_DIRECT) -- some props like to burn slowly against DMG_BURN or DMG_SLOWBURN. don't. + elseif ent:GetClass() == "tacrp_proj_nade_thermite" then + return true -- don't burn other thermite grenades + end + dmginfo:ScaleDamage(2) -- tremendous damage to props + end + dmginfo:SetDamageForce(Vector()) -- fire does not push things around. still applies to players, but that can't be helped. + end +end) + +hook.Add("PostEntityTakeDamage", "tacrp_fire_cloud", function(ent, dmginfo, took) + if took and IsValid(dmginfo:GetInflictor()) and dmginfo:GetInflictor():GetClass() == "tacrp_fire_cloud" and !ent:IsPlayer() then + ent:Ignite(math.Rand(3, 5)) + end +end) diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud.lua new file mode 100644 index 0000000..6fd16b6 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud.lua @@ -0,0 +1,219 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Flare Particle" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.Model = "models/Items/AR2_Grenade.mdl" + +ENT.FireTime = 15 + +ENT.Armed = false + +ENT.NextDamageTick = 0 + +ENT.Ticks = 0 + +ENT.FlareLength = 1 + +AddCSLuaFile() + +function ENT:Initialize() + self.SpawnTime = CurTime() + + if SERVER then + self:SetModel( self.Model ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + local maxs = Vector(2, 1, 1) + local mins = -maxs + self:PhysicsInitBox(mins, maxs) + self:DrawShadow( false ) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:SetBuoyancyRatio(0) + phys:SetDragCoefficient(5) + end + + self:Detonate() + + -- self.FireTime = self.FireTime * math.Rand(0.8, 1.2) + -- self:SetNWFloat("FireTime", CurTime() + self.FireTime) + else + local tr = util.TraceHull({ + start = self:GetPos() + Vector(0, 0, 12), + endpos = self:GetPos() + Vector(0, 0, 1024), + mask = MASK_SOLID_BRUSHONLY, + mins = Vector(-4, -4, -4), + maxs = Vector(4, 4, 4), + }) + self.FlareLength = math.Clamp(tr.Fraction ^ 2, 0.2, 1) + end +end + +function ENT:Think() + if CLIENT then + local d = Lerp((self.SpawnTime + self.FireTime - CurTime()) / 12, 1, 0.000001) ^ 2 + + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex()) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 75 + self.Light.b = 60 + self.Light.Brightness = 1.5 + self.Light.Size = 512 + self.Light.DieTime = CurTime() + self.FireTime + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + local emitter = ParticleEmitter(self:GetPos()) + + if !self:IsValid() or self:WaterLevel() > 2 then return end + if !IsValid(emitter) then return end + + if self.Ticks % math.ceil(2 + d * 8) == 0 then + local fire = emitter:Add("particles/smokey", self:GetPos() + Vector(math.Rand(-4, 4), math.Rand(-4, 4), 8)) + local wind = Vector(math.sin(CurTime() / 60), math.cos(CurTime() / 60), 0) * math.Rand(1000, 1400) + fire:SetVelocity( VectorRand() * 75 + self:GetVelocity() ) + fire:SetGravity( wind + Vector(0, 0, 2500) ) + fire:SetDieTime( self.FlareLength * math.Rand(2, 3) ) + fire:SetStartAlpha( 100 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 10 ) + fire:SetEndSize( Lerp(self.FlareLength, 48, 128) ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 300 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(1) + fire:SetNextThink( CurTime() + FrameTime() ) + fire:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 50, 25) + local col2 = Color(255, 155, 155) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + end + + if self.Ticks % math.ceil(6 + d * 10) == 0 then + local fire = emitter:Add("effects/spark", self:GetPos() + Vector(math.Rand(-4, 4), math.Rand(-4, 4), 0)) + fire:SetVelocity( VectorRand() * 300 + Vector(0, 0, 300) ) + fire:SetGravity( Vector(math.Rand(-5, 5), math.Rand(-5, 5), -2000) ) + fire:SetDieTime( math.Rand(0.5, 1) ) + fire:SetStartAlpha( 255 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 5 ) + fire:SetEndSize( 0 ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 50 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(0.8) + fire.Ticks = 0 + end + + emitter:Finish() + + self.Ticks = self.Ticks + 1 + else + + if !self:GetOwner():IsValid() then self:Remove() return end + + if self:GetVelocity():LengthSqr() <= 32 then + self:SetMoveType( MOVETYPE_NONE ) + end + + if self.NextDamageTick > CurTime() then return end + + if self:WaterLevel() > 2 then self:Remove() return end + + local dmg = DamageInfo() + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(15) + dmg:SetInflictor(self) + dmg:SetAttacker(self:GetOwner()) + util.BlastDamageInfo(dmg, IsValid(self:GetParent()) and self:GetParent():GetPos() or self:GetPos(), 96) + + if self.SpawnTime + self.FireTime <= CurTime() then self:Remove() return end + + self.NextDamageTick = CurTime() + 0.2 + self:NextThink(self.NextDamageTick) + return true + end +end + +function ENT:OnRemove() + if self.Light then + self.Light.dietime = CurTime() + 0.5 + self.Light.decay = 2000 + end + if !self.FireSound then return end + self.FireSound:Stop() +end + +function ENT:Detonate() + if !self:IsValid() then return end + + self.Armed = true + + if self.Order and self.Order != 1 then return end + + -- self.FireSound = CreateSound(self, "tacrp_extras/grenades/fire_loop_1.wav") + self.FireSound = CreateSound(self, "weapons/flaregun/burn.wav") + self.FireSound:SetSoundLevel(70) + self.FireSound:Play() + self.FireSound:ChangePitch(105) + + self.FireSound:ChangePitch(95, self.FireTime) + + timer.Simple(self.FireTime - 1, function() + if !IsValid(self) then return end + + self.FireSound:ChangeVolume(0, 1) + end) + + timer.Simple(self.FireTime, function() + if !IsValid(self) then return end + + self:Remove() + end) +end + +ENT.FlareColor = Color(255, 200, 200) +ENT.FlareSizeMin = 48 +ENT.FlareSizeMax = 64 + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + Vector(0, 0, 4), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:UpdateTransmitState() + if TacRP.ConVars["dynamiclight"]:GetBool() then + return TRANSMIT_ALWAYS + end + return TRANSMIT_PVS +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud_para.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud_para.lua new file mode 100644 index 0000000..f758ab5 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud_para.lua @@ -0,0 +1,233 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Flare Particle" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.Model = "models/Items/AR2_Grenade.mdl" + +ENT.FireTime = 15 + +ENT.Armed = false + +ENT.NextDamageTick = 0 + +ENT.Ticks = 0 + +ENT.FlareLength = 1 + +AddCSLuaFile() + +function ENT:Initialize() + self.SpawnTime = CurTime() + + if SERVER then + self:SetModel( self.Model ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + local maxs = Vector(2, 2, 2) + local mins = -maxs + self:PhysicsInitBox(mins, maxs) + self:DrawShadow( false ) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:SetBuoyancyRatio(0) + phys:SetDragCoefficient(4) + end + + self:Detonate() + end +end + +ENT.LastTraceTime = 0 +ENT.LastTracePos = nil + +function ENT:Think() + if CLIENT then + local d = Lerp((self.SpawnTime + self.FireTime - CurTime()) / 12, 1, 0.000001) ^ 2 + + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex()) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 200 + self.Light.b = 140 + self.Light.Brightness = 1 + self.Light.Size = 1500 + self.Light.DieTime = CurTime() + self.FireTime + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + -- fucking expensive + --[[] + if !self.ProjectedTexture then + self.ProjectedTexture = ProjectedTexture() + self.ProjectedTexture:SetTexture("effects/flashlight001") + self.ProjectedTexture:SetColor(Color(255, 200, 140)) + self.ProjectedTexture:SetNearZ(728) + self.ProjectedTexture:SetFarZ(4096) + self.ProjectedTexture:SetAngles(down) + self.ProjectedTexture:SetFOV(170) + self.ProjectedTexture:SetLightWorld(false) + self.ProjectedTexture:SetBrightness(0.75) + end + self.ProjectedTexture:SetPos(self:GetPos()) + self.ProjectedTexture:Update() + ]] + + local emitter = ParticleEmitter(self:GetPos()) + + if !self:IsValid() or self:WaterLevel() > 2 then return end + if !IsValid(emitter) then return end + + if self.Ticks % math.ceil(4 + d * 8) == 0 then + local fire = emitter:Add("particles/smokey", self:GetPos() + Vector(math.Rand(-4, 4), math.Rand(-4, 4), 8)) + fire:SetVelocity( VectorRand() * 8 ) + fire:SetGravity( Vector(0, 0, 100) ) + fire:SetDieTime( self.FlareLength * math.Rand(5, 6) ) + fire:SetStartAlpha( 20 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 12 ) + fire:SetEndSize( math.Rand(32, 48) ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 100 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(1) + fire:SetNextThink( CurTime() + FrameTime() ) + fire:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 200, 150) + local col2 = Color(255, 255, 225) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + end + + if self.Ticks % math.ceil(4 + d * 10) == 0 then + local fire = emitter:Add("effects/spark", self:GetPos()) + fire:SetVelocity( VectorRand() * 300 + Vector(0, 0, 300) ) + fire:SetGravity( Vector(math.Rand(-5, 5), math.Rand(-5, 5), -2000) ) + fire:SetDieTime( math.Rand(0.5, 1) ) + fire:SetStartAlpha( 255 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 5 ) + fire:SetEndSize( 0 ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 50 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(0.8) + fire.Ticks = 0 + end + + emitter:Finish() + + self.Ticks = self.Ticks + 1 + else + + if !self:GetOwner():IsValid() then self:Remove() return end + + if self.NextDamageTick > CurTime() then return end + + if self:WaterLevel() > 2 then self:Remove() return end + + local dmg = DamageInfo() + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(15) + dmg:SetInflictor(self) + dmg:SetAttacker(self:GetOwner()) + util.BlastDamageInfo(dmg, IsValid(self:GetParent()) and self:GetParent():GetPos() or self:GetPos(), 96) + + if self.SpawnTime + self.FireTime <= CurTime() then self:Remove() return end + + self.NextDamageTick = CurTime() + 0.2 + self:NextThink(self.NextDamageTick) + return true + end +end + +function ENT:OnRemove() + if self.Light then + self.Light.dietime = CurTime() + 0.5 + self.Light.decay = 800 + end + if IsValid(self.ProjectedTexture) then + self.ProjectedTexture:Remove() + end + if !self.FireSound then return end + self.FireSound:Stop() +end + +function ENT:Detonate() + if !self:IsValid() then return end + + self.Armed = true + + if self.Order and self.Order != 1 then return end + + -- self.FireSound = CreateSound(self, "tacrp_extras/grenades/fire_loop_1.wav") + self.FireSound = CreateSound(self, "weapons/flaregun/burn.wav") + self.FireSound:SetSoundLevel(70) + self.FireSound:Play() + self.FireSound:ChangePitch(105) + + self.FireSound:ChangePitch(95, self.FireTime) + + timer.Simple(self.FireTime - 1, function() + if !IsValid(self) then return end + + self.FireSound:ChangeVolume(0, 1) + end) + + timer.Simple(self.FireTime, function() + if !IsValid(self) then return end + + self:Remove() + end) +end + +ENT.FlareColor = Color(255, 240, 240) +ENT.FlareSizeMin = 64 +ENT.FlareSizeMax = 96 + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + Vector(0, 0, 4), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:PhysicsUpdate(phys) + if phys:IsGravityEnabled() and self:WaterLevel() <= 2 then + local v = phys:GetVelocity() + v.z = math.max(v.z, -70) + phys:SetVelocityInstantaneous(v) + end +end + +function ENT:UpdateTransmitState() + if TacRP.ConVars["dynamiclight"]:GetBool() then + return TRANSMIT_ALWAYS + end + return TRANSMIT_PVS +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud_signal.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud_signal.lua new file mode 100644 index 0000000..fc0a298 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_flare_cloud_signal.lua @@ -0,0 +1,219 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Flare Particle" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.Model = "models/Items/AR2_Grenade.mdl" + +ENT.FireTime = 60 -- Duration once landed + +ENT.Armed = false + +ENT.NextDamageTick = 0 + +ENT.Ticks = 0 + +ENT.FlareLength = 2 + +AddCSLuaFile() + +function ENT:Initialize() + self.SpawnTime = CurTime() + + if SERVER then + self:SetModel( self.Model ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + local maxs = Vector(2, 1, 1) + local mins = -maxs + self:PhysicsInitBox(mins, maxs) + self:DrawShadow( false ) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:SetBuoyancyRatio(0) + phys:SetDragCoefficient(5) + end + + self:Detonate() + + -- self.FireTime = self.FireTime * math.Rand(0.8, 1.2) + -- self:SetNWFloat("FireTime", CurTime() + self.FireTime) + else + local tr = util.TraceHull({ + start = self:GetPos() + Vector(0, 0, 12), + endpos = self:GetPos() + Vector(0, 0, 1024), + mask = MASK_SOLID_BRUSHONLY, + mins = Vector(-4, -4, -4), + maxs = Vector(4, 4, 4), + }) + self.FlareLength = math.Clamp(tr.Fraction ^ 2, 0.2, 1) + end +end + +function ENT:Think() + if CLIENT then + local d = Lerp((self.SpawnTime + self.FireTime - CurTime()) / 12, 1, 0.000001) ^ 2 + + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex()) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 75 + self.Light.b = 60 + self.Light.Brightness = 1.5 + self.Light.Size = 512 + self.Light.DieTime = CurTime() + self.FireTime + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + local emitter = ParticleEmitter(self:GetPos()) + + if !self:IsValid() or self:WaterLevel() > 2 then return end + if !IsValid(emitter) then return end + + if self.Ticks % math.ceil(2 + d * 8) == 0 then + local fire = emitter:Add("particles/smokey", self:GetPos() + Vector(math.Rand(-4, 4), math.Rand(-4, 4), 8)) + local wind = Vector(math.sin(CurTime() / 60), math.cos(CurTime() / 60), 0) * math.Rand(1000, 1400) + fire:SetVelocity( VectorRand() * 75 + self:GetVelocity() ) + fire:SetGravity( wind + Vector(0, 0, 2500) ) + fire:SetDieTime( self.FlareLength * math.Rand(8, 12) ) -- Flare Height + fire:SetStartAlpha( 100 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 10 ) + fire:SetEndSize( Lerp(self.FlareLength, 96, 256) ) -- Particle Size + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 300 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(1) + fire:SetNextThink( CurTime() + FrameTime() ) + fire:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 50, 25) + local col2 = Color(255, 155, 155) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + end + + if self.Ticks % math.ceil(6 + d * 10) == 0 then + local fire = emitter:Add("effects/spark", self:GetPos() + Vector(math.Rand(-4, 4), math.Rand(-4, 4), 0)) + fire:SetVelocity( VectorRand() * 300 + Vector(0, 0, 300) ) + fire:SetGravity( Vector(math.Rand(-5, 5), math.Rand(-5, 5), -2000) ) + fire:SetDieTime( math.Rand(0.5, 1) ) + fire:SetStartAlpha( 255 ) + fire:SetEndAlpha( 0 ) + fire:SetStartSize( 5 ) + fire:SetEndSize( 0 ) + fire:SetRoll( math.Rand(-180, 180) ) + fire:SetRollDelta( math.Rand(-0.2,0.2) ) + fire:SetColor( 255, 255, 255 ) + fire:SetAirResistance( 50 ) + fire:SetPos( self:GetPos() ) + fire:SetLighting( false ) + fire:SetCollide(true) + fire:SetBounce(0.8) + fire.Ticks = 0 + end + + emitter:Finish() + + self.Ticks = self.Ticks + 1 + else + + if !self:GetOwner():IsValid() then self:Remove() return end + + if self:GetVelocity():LengthSqr() <= 32 then + self:SetMoveType( MOVETYPE_NONE ) + end + + if self.NextDamageTick > CurTime() then return end + + if self:WaterLevel() > 2 then self:Remove() return end + + local dmg = DamageInfo() + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(15) + dmg:SetInflictor(self) + dmg:SetAttacker(self:GetOwner()) + util.BlastDamageInfo(dmg, IsValid(self:GetParent()) and self:GetParent():GetPos() or self:GetPos(), 96) + + if self.SpawnTime + self.FireTime <= CurTime() then self:Remove() return end + + self.NextDamageTick = CurTime() + 0.2 + self:NextThink(self.NextDamageTick) + return true + end +end + +function ENT:OnRemove() + if self.Light then + self.Light.dietime = CurTime() + 0.5 + self.Light.decay = 2000 + end + if !self.FireSound then return end + self.FireSound:Stop() +end + +function ENT:Detonate() + if !self:IsValid() then return end + + self.Armed = true + + if self.Order and self.Order != 1 then return end + + -- self.FireSound = CreateSound(self, "tacrp_extras/grenades/fire_loop_1.wav") + self.FireSound = CreateSound(self, "weapons/flaregun/burn.wav") + self.FireSound:SetSoundLevel(70) + self.FireSound:Play() + self.FireSound:ChangePitch(105) + + self.FireSound:ChangePitch(95, self.FireTime) + + timer.Simple(self.FireTime - 1, function() + if !IsValid(self) then return end + + self.FireSound:ChangeVolume(0, 1) + end) + + timer.Simple(self.FireTime, function() + if !IsValid(self) then return end + + self:Remove() + end) +end + +ENT.FlareColor = Color(255, 200, 200) +ENT.FlareSizeMin = 96 +ENT.FlareSizeMax = 128 + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + Vector(0, 0, 4), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:UpdateTransmitState() + if TacRP.ConVars["dynamiclight"]:GetBool() then + return TRANSMIT_ALWAYS + end + return TRANSMIT_PVS +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_gas_cloud.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_gas_cloud.lua new file mode 100644 index 0000000..97898d8 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_gas_cloud.lua @@ -0,0 +1,244 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Gas Cloud" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +ENT.Particles = nil +ENT.SmokeRadius = 256 +ENT.SmokeColor = Color(125, 150, 50) +ENT.BillowTime = 5 +ENT.Life = 15 + +AddCSLuaFile() + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/weapons/w_eq_smokegrenade_thrown.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + + sound.EmitHint(SOUND_DANGER, self:GetPos(), 328, self.Life, self) + else + local emitter = ParticleEmitter(self:GetPos()) + + self.Particles = {} + + local amt = 20 + + for i = 1, amt do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetVelocity( VectorRand() * 8 + (Angle(0, i * (360 / amt), 0):Forward() * 200) ) + smoke:SetStartAlpha( 0 ) + smoke:SetEndAlpha( 255 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( self.SmokeRadius ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-0.2,0.2) ) + smoke:SetColor( self.SmokeColor.r, self.SmokeColor.g, self.SmokeColor.b ) + smoke:SetAirResistance( 75 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 0.2 ) + smoke:SetLighting( false ) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.bt = CurTime() + self.BillowTime + smoke.dt = CurTime() + self.BillowTime + self.Life + smoke.ft = CurTime() + self.BillowTime + self.Life + math.Rand(2.5, 5) + smoke:SetDieTime(smoke.ft) + smoke.life = self.Life + smoke.billowed = false + smoke.radius = self.SmokeRadius + smoke:SetThinkFunction( function(pa) + if !pa then return end + + local prog = 1 + local alph = 0 + + if pa.ft < CurTime() then + return + elseif pa.dt < CurTime() then + local d = (CurTime() - pa.dt) / (pa.ft - pa.dt) + + alph = 1 - d + elseif pa.bt < CurTime() then + alph = 1 + else + local d = math.Clamp(pa:GetLifeTime() / (pa.bt - CurTime()), 0, 1) + + prog = (-d ^ 2) + (2 * d) + + alph = d + end + + pa:SetEndSize( pa.radius * prog ) + pa:SetStartSize( pa.radius * prog ) + + alph = math.Clamp(alph, 0, 1) + + pa:SetStartAlpha(50 * alph) + pa:SetEndAlpha(50 * alph) + + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + table.insert(self.Particles, smoke) + end + + emitter:Finish() + end + + self.dt = CurTime() + self.Life + self.BillowTime +end + + +function ENT:Think() + + if SERVER then + if !self:GetOwner():IsValid() then self:Remove() return end + + local o = self:GetOwner() + local origin = self:GetPos() + Vector(0, 0, 16) + + local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT + local threshold = ttt and 50 or 25 + + local dmg = DamageInfo() + dmg:SetAttacker(self:GetOwner()) + dmg:SetInflictor(self) + dmg:SetDamageType(DMG_NERVEGAS) + dmg:SetDamageForce(Vector(0, 0, 0)) + dmg:SetDamagePosition(self:GetPos()) + dmg:SetDamageCustom(1024) -- immersive death + for i, k in pairs(ents.FindInSphere(origin, 300)) do + if k:IsPlayer() or k:IsNPC() or k:IsNextBot() then + + if k:IsPlayer() then + local wep = k:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP and wep:GetValue("GasImmunity") then + continue + end + end + + local tr = util.TraceLine({ + start = origin, + endpos = k:EyePos() or k:WorldSpaceCenter(), + filter = self, + mask = MASK_SOLID_BRUSHONLY + }) + if tr.Fraction < 1 then continue end + + dmg:SetDamage(math.Rand(3, 6)) + + if k:IsPlayer() and TacRP.ConVars["gas_affectplayers"]:GetBool() then + k:TakeDamageInfo(dmg) + + local timername = "tacrp_gas_" .. k:EntIndex() + local reps = 6 + + if timer.Exists(timername) then + reps = math.Clamp(timer.RepsLeft(timername) + 3, reps, 20) + timer.Remove(timername) + end + timer.Create(timername, ttt and 5 or 1.5, reps, function() + if !IsValid(k) or !k:Alive() or (engine.ActiveGamemode() == "terrortown" and (GetRoundState() == ROUND_PREP or GetRoundState() == ROUND_POST)) then + timer.Remove(timername) + return + end + k:ScreenFade( SCREENFADE.IN, Color(125, 150, 50, 3), 0.1, 0 ) + if k:Health() > threshold then + local d = DamageInfo() + d:SetDamageType(DMG_NERVEGAS) + d:SetDamage(1) + d:SetInflictor(IsValid(self) and self or o) + d:SetAttacker(o) + d:SetDamageForce(k:GetForward()) + d:SetDamagePosition(k:GetPos()) + d:SetDamageCustom(1024) + k:TakeDamageInfo(d) + else + k:ViewPunch(Angle(math.Rand(-2, 2), 0, 0)) + end + if ttt or math.random() <= 0.333 then + k:EmitSound("ambient/voices/cough" .. math.random(1, 4) .. ".wav", 80, math.Rand(95, 105)) + end + end) + elseif k:IsNPC() and TacRP.ConVars["gas_affectnpcs"]:GetBool() and (k:GetHullType() == HULL_HUMAN or k:GetHullType() == HULL_WIDE_HUMAN) then + k:SetCurrentWeaponProficiency(WEAPON_PROFICIENCY_POOR) + local rng = math.random() + if rng <= 0.3 then + k:SetSchedule(SCHED_RUN_RANDOM) + elseif rng <= 0.8 then + k:SetSchedule(SCHED_COWER) + end + k:TakeDamageInfo(dmg) + elseif !k:IsNPC() and !k:IsPlayer() then + k:TakeDamageInfo(dmg) + end + end + end + + self:NextThink(CurTime() + 1) + + if self.dt < CurTime() then + SafeRemoveEntity(self) + end + end + + return true +end + +function ENT:Draw() + return false +end + +-- cs gas strips armor and will try not deal lethal damage +hook.Add("EntityTakeDamage", "tacrp_gas", function(ent, dmg) + if dmg:GetDamageType() == DMG_NERVEGAS and bit.band(dmg:GetDamageCustom(), 1024) == 1024 then + if ent:IsPlayer() then + local threshold = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT and 50 or 25 + local wep = ent:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP and wep:GetValue("GasImmunity") then + dmg:SetDamage(0) + elseif ent:Health() - dmg:GetDamage() <= threshold then + dmg:SetDamage(math.max(0, ent:Health() - threshold)) + end + if dmg:GetDamage() <= 0 then + ent.TacRPGassed = true + end + elseif ent:IsNPC() then + if ent:Health() <= dmg:GetDamage() then + ent:SetHealth(1) + dmg:SetDamage(0) + end + end + end +end) + +hook.Add("PostEntityTakeDamage", "tacrp_gas", function(ent, dmg, took) + if ent:IsPlayer() and dmg:GetDamageType() == DMG_NERVEGAS and bit.band(dmg:GetDamageCustom(), 1024) == 1024 then + ent:SetArmor(math.max(0, ent:Armor() - dmg:GetDamage())) + if ent.TacRPGassed or (dmg:GetDamage() > 0 and IsValid(dmg:GetInflictor()) and (dmg:GetInflictor():GetClass() == "tacrp_gas_cloud" or dmg:GetInflictor():GetClass() == "tacrp_smoke_cloud_ninja")) then + local distsqr = dmg:GetInflictor():GetPos():DistToSqr(ent:GetPos()) + if distsqr <= 350 * 350 then + ent:SetNWFloat("TacRPGasEnd", CurTime() + 10) + ent.TacRPGassed = nil + ent:ScreenFade( SCREENFADE.IN, Color(125, 150, 50, 100), 0.5, 0 ) + ent:ViewPunch(Angle(math.Rand(-1, 1), math.Rand(-1, 1), math.Rand(-1, 1))) + local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT + if ttt or math.random() <= 0.333 then + ent:EmitSound("ambient/voices/cough" .. math.random(1, 4) .. ".wav", 80, math.Rand(95, 105)) + end + end + end + end +end) diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_heal_cloud.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_heal_cloud.lua new file mode 100644 index 0000000..b118615 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_heal_cloud.lua @@ -0,0 +1,216 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Healing Gas Cloud" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +local SmokeColor = Color(125, 25, 125) + +ENT.Particles = nil +ENT.SmokeRadius = 256 +ENT.SmokeColor = SmokeColor +ENT.BillowTime = 2.5 +ENT.Life = 15 + +AddCSLuaFile() + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/weapons/w_eq_smokegrenade_thrown.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + else + local emitter = ParticleEmitter(self:GetPos()) + + self.Particles = {} + + local amt = 20 + + for i = 1, amt do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetVelocity( VectorRand() * 8 + (Angle(0, i * (360 / amt), 0):Forward() * 200) ) + smoke:SetStartAlpha( 0 ) + smoke:SetEndAlpha( 255 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( self.SmokeRadius ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-0.2,0.2) ) + smoke:SetColor( self.SmokeColor.r, self.SmokeColor.g, self.SmokeColor.b ) + smoke:SetAirResistance( 75 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 0.2 ) + smoke:SetLighting( false ) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.bt = CurTime() + self.BillowTime + smoke.dt = CurTime() + self.BillowTime + self.Life + smoke.ft = CurTime() + self.BillowTime + self.Life + math.Rand(2.5, 5) + smoke:SetDieTime(smoke.ft) + smoke.life = self.Life + smoke.billowed = false + smoke.radius = self.SmokeRadius / 2 + smoke.offset_r = math.Rand(0, 100) + smoke.offset_g = math.Rand(0, 100) + smoke.offset_b = math.Rand(0, 100) + smoke.pulsate_time = math.Rand(0.9, 3) + smoke:SetThinkFunction( function(pa) + if !pa then return end + if !self then return end + + local prog = 1 + local alph = 0 + + if pa.ft < CurTime() then + return + elseif pa.dt < CurTime() then + local d = (CurTime() - pa.dt) / (pa.ft - pa.dt) + + alph = 1 - d + elseif pa.bt < CurTime() then + alph = 1 + else + local d = math.Clamp(pa:GetLifeTime() / (pa.bt - CurTime()), 0, 1) + + prog = (-d ^ 2) + (2 * d) + + alph = d + end + + pa:SetColor( + SmokeColor.r + math.sin((CurTime() * pa.pulsate_time) + pa.offset_r) * 20, + SmokeColor.g + math.sin((CurTime() * pa.pulsate_time) + pa.offset_g) * 20, + SmokeColor.b + math.sin((CurTime() * pa.pulsate_time) + pa.offset_b) * 20 + ) + + pa:SetEndSize( pa.radius * prog ) + pa:SetStartSize( pa.radius * prog ) + + alph = math.Clamp(alph, 0, 1) + + pa:SetStartAlpha(35 * alph) + pa:SetEndAlpha(35 * alph) + + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + table.insert(self.Particles, smoke) + end + + emitter:Finish() + end + + self.dt = CurTime() + self.Life + self.BillowTime +end + +function ENT:Think() + + if SERVER then + if !self:GetOwner():IsValid() then self:Remove() return end + local origin = self:GetPos() + Vector(0, 0, 16) + + for i, k in pairs(ents.FindInSphere(origin, self.SmokeRadius)) do + if (k:IsPlayer() or k:IsNPC() or k:IsNextBot()) then + local tr = util.TraceLine({ + start = origin, + endpos = k:EyePos() or k:WorldSpaceCenter(), + filter = self, + mask = MASK_SOLID_BRUSHONLY + }) + if tr.Fraction < 1 then continue end + if k:IsPlayer() and (k.TacRPNextCanHealthGasTime or 0) <= CurTime() then + local ply = k + if ply:Health() < ply:GetMaxHealth() then + local amt = TacRP.ConVars["healnade_heal"]:GetInt() + local ret = {amt} + hook.Run("TacRP_MedkitHeal", self, self:GetOwner(), ply, ret) + amt = ret and ret[1] or amt + ply:SetHealth(math.min(ply:Health() + amt, ply:GetMaxHealth())) + elseif ply:Armor() > 0 and ply:Armor() <= ply:GetMaxArmor() and TacRP.ConVars["healnade_armor"]:GetInt() > 0 then + ply:SetArmor(math.min(ply:Armor() + TacRP.ConVars["healnade_armor"]:GetInt(), ply:GetMaxArmor())) + end + k.TacRPNextCanHealthGasTime = CurTime() + 0.19 + elseif !k:IsPlayer() and (k.TacRPNextCanHealthGasTime or 0) <= CurTime() then + if TacRP.EntityIsNecrotic(k) then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(self:GetOwner() or self) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_NERVEGAS) + dmginfo:SetDamage(TacRP.ConVars["healnade_damage"]:GetInt()) + k:TakeDamageInfo(dmginfo) + elseif k:Health() < k:GetMaxHealth() then + local amt = TacRP.ConVars["healnade_heal"]:GetInt() + local ret = {amt} + hook.Run("TacRP_MedkitHeal", self, self:GetOwner(), k, ret) + amt = ret and ret[1] or amt + k:SetHealth(math.min(k:Health() + amt, k:GetMaxHealth())) + end + k.TacRPNextCanHealthGasTime = CurTime() + 0.19 + end + end + end + + self:NextThink(CurTime() + 0.2) + + if self.dt < CurTime() then + SafeRemoveEntity(self) + end + else + if (self.NextEmitTime or 0) > CurTime() then return end + + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("effects/spark", self:GetPos()) + smoke:SetVelocity( Vector(math.Rand(-1, 1), math.Rand(-1, 1), math.Rand(0, 1)) * 300 ) + smoke:SetStartAlpha( 255 ) + smoke:SetEndAlpha( 0 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( 0 ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-32, 32) ) + smoke:SetColor( 255, 255, 255 ) + smoke:SetAirResistance( 100 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 1 ) + smoke:SetLighting( false ) + smoke:SetDieTime(2) + smoke:SetGravity(Vector(0, 0, -100)) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.hl2 = CurTime() + 1 + smoke:SetThinkFunction( function(pa) + if !pa then return end + + if pa.hl2 < CurTime() and !pa.alreadyhl then + pa:SetStartSize(8) + pa:SetEndSize(0) + pa:SetLifeTime(0) + pa:SetDieTime(math.Rand(0.25, 0.5)) + pa:SetVelocity(VectorRand() * 300) + pa:SetGravity(Vector(0, 0, -200)) + pa:SetAirResistance( 200 ) + pa.alreadyhl = true + else + pa:SetNextThink( CurTime() + FrameTime() ) + end + end ) + + emitter:Finish() + + self.NextEmitTime = CurTime() + 0.02 + end + + return true +end + +function ENT:Draw() + return false +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_heal_cloud_p2a1.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_heal_cloud_p2a1.lua new file mode 100644 index 0000000..a43f7c0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_heal_cloud_p2a1.lua @@ -0,0 +1,218 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Healing Gas Cloud" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +local SmokeColor = Color(125, 25, 125) + +ENT.Particles = nil +ENT.SmokeRadius = 128 +ENT.SmokeColor = SmokeColor +ENT.BillowTime = 0.5 +ENT.Life = 5 + +ENT.TacRPSmoke = true + +AddCSLuaFile() + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/weapons/w_eq_smokegrenade_thrown.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + else + local emitter = ParticleEmitter(self:GetPos()) + + self.Particles = {} + + local amt = 12 + + for i = 1, amt do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetVelocity( VectorRand() * 8 + (Angle(0, i * (360 / amt), 0):Forward() * 120) ) + smoke:SetStartAlpha( 0 ) + smoke:SetEndAlpha( 255 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( self.SmokeRadius ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-0.2,0.2) ) + smoke:SetColor( self.SmokeColor.r, self.SmokeColor.g, self.SmokeColor.b ) + smoke:SetAirResistance( 75 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 0.2 ) + smoke:SetLighting( false ) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.bt = CurTime() + self.BillowTime + smoke.dt = CurTime() + self.BillowTime + self.Life + smoke.ft = CurTime() + self.BillowTime + self.Life + math.Rand(2.5, 5) + smoke:SetDieTime(smoke.ft) + smoke.life = self.Life + smoke.billowed = false + smoke.radius = self.SmokeRadius / 2 + smoke.offset_r = math.Rand(0, 100) + smoke.offset_g = math.Rand(0, 100) + smoke.offset_b = math.Rand(0, 100) + smoke.pulsate_time = math.Rand(0.9, 3) + smoke:SetThinkFunction( function(pa) + if !pa then return end + if !self then return end + + local prog = 1 + local alph = 0 + + if pa.ft < CurTime() then + return + elseif pa.dt < CurTime() then + local d = (CurTime() - pa.dt) / (pa.ft - pa.dt) + + alph = 1 - d + elseif pa.bt < CurTime() then + alph = 1 + else + local d = math.Clamp(pa:GetLifeTime() / (pa.bt - CurTime()), 0, 1) + + prog = (-d ^ 2) + (2 * d) + + alph = d + end + + pa:SetColor( + SmokeColor.r + math.sin((CurTime() * pa.pulsate_time) + pa.offset_r) * 20, + SmokeColor.g + math.sin((CurTime() * pa.pulsate_time) + pa.offset_g) * 20, + SmokeColor.b + math.sin((CurTime() * pa.pulsate_time) + pa.offset_b) * 20 + ) + + pa:SetEndSize( pa.radius * prog ) + pa:SetStartSize( pa.radius * prog ) + + alph = math.Clamp(alph, 0, 1) + + pa:SetStartAlpha(35 * alph) + pa:SetEndAlpha(35 * alph) + + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + table.insert(self.Particles, smoke) + end + + emitter:Finish() + end + + self.dt = CurTime() + self.Life + self.BillowTime +end + +function ENT:Think() + + if SERVER then + if !self:GetOwner():IsValid() then self:Remove() return end + local origin = self:GetPos() + Vector(0, 0, 16) + + for i, k in pairs(ents.FindInSphere(origin, self.SmokeRadius)) do + if (k:IsPlayer() or k:IsNPC() or k:IsNextBot()) then + local tr = util.TraceLine({ + start = origin, + endpos = k:EyePos() or k:WorldSpaceCenter(), + filter = self, + mask = MASK_SOLID_BRUSHONLY + }) + if tr.Fraction < 1 then continue end + if k:IsPlayer() and (k.TacRPNextCanHealthGasTime or 0) <= CurTime() then + local ply = k + if ply:Health() < ply:GetMaxHealth() then + local amt = TacRP.ConVars["healnade_heal"]:GetInt() + local ret = {amt} + hook.Run("TacRP_MedkitHeal", self, self:GetOwner(), ply, ret) + amt = ret and ret[1] or amt + ply:SetHealth(math.min(ply:Health() + amt, ply:GetMaxHealth())) + elseif ply:Armor() > 0 and ply:Armor() <= ply:GetMaxArmor() and TacRP.ConVars["healnade_armor"]:GetInt() > 0 then + ply:SetArmor(math.min(ply:Armor() + TacRP.ConVars["healnade_armor"]:GetInt(), ply:GetMaxArmor())) + end + k.TacRPNextCanHealthGasTime = CurTime() + 0.74 + elseif !k:IsPlayer() and (k.TacRPNextCanHealthGasTime or 0) <= CurTime() then + if TacRP.EntityIsNecrotic(k) then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(self:GetOwner() or self) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_NERVEGAS) + dmginfo:SetDamage(TacRP.ConVars["healnade_damage"]:GetInt()) + k:TakeDamageInfo(dmginfo) + elseif k:Health() < k:GetMaxHealth() then + local amt = TacRP.ConVars["healnade_heal"]:GetInt() + local ret = {amt} + hook.Run("TacRP_MedkitHeal", self, self:GetOwner(), k, ret) + amt = ret and ret[1] or amt + k:SetHealth(math.min(k:Health() + amt, k:GetMaxHealth())) + end + k.TacRPNextCanHealthGasTime = CurTime() + 0.74 + end + end + end + + self:NextThink(CurTime() + 0.75) + + if self.dt < CurTime() then + SafeRemoveEntity(self) + end + else + if (self.NextEmitTime or 0) > CurTime() then return end + + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("effects/spark", self:GetPos()) + smoke:SetVelocity( Vector(math.Rand(-1, 1), math.Rand(-1, 1), math.Rand(0, 1)) * self.SmokeRadius ) + smoke:SetStartAlpha( 255 ) + smoke:SetEndAlpha( 0 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( 0 ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-32, 32) ) + smoke:SetColor( 255, 255, 255 ) + smoke:SetAirResistance( 100 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 1 ) + smoke:SetLighting( false ) + smoke:SetDieTime(2) + smoke:SetGravity(Vector(0, 0, -100)) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.hl2 = CurTime() + self.BillowTime + smoke:SetThinkFunction( function(pa) + if !pa then return end + + if pa.hl2 < CurTime() and !pa.alreadyhl then + pa:SetStartSize(4) + pa:SetEndSize(0) + pa:SetLifeTime(0) + pa:SetDieTime(math.Rand(0.25, 0.5)) + pa:SetVelocity(VectorRand() * 300) + pa:SetGravity(Vector(0, 0, -200)) + pa:SetAirResistance( 200 ) + pa.alreadyhl = true + else + pa:SetNextThink( CurTime() + FrameTime() ) + end + end ) + + emitter:Finish() + + self.NextEmitTime = CurTime() + 0.1 + end + + return true +end + +function ENT:Draw() + return false +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_nuke_cloud.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_nuke_cloud.lua new file mode 100644 index 0000000..4e760a5 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_nuke_cloud.lua @@ -0,0 +1,83 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Nuclear Radiation" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.Life = 10 + +ENT.MaxRadius = 7500 + +AddCSLuaFile() + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/weapons/w_eq_smokegrenade_thrown.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + end + + self.SpawnTime = CurTime() + self.dt = CurTime() + self.Life + + util.ScreenShake(self:GetPos(), 16, 30, self.Life, self.MaxRadius) + + if CLIENT then return end + + for i, k in pairs(ents.FindInSphere(self:GetPos(), self.MaxRadius)) do + if k:GetClass() == "func_breakable_surf" then + k:Fire("shatter", "", 0) + elseif k:GetClass() == "func_breakable" then + k:Fire("break", "", 0) + end + + local phys = k:GetPhysicsObject() + + if IsValid(phys) then + local vec = (k:GetPos() - self:GetPos()) * 500 + phys:ApplyForceCenter(vec) + end + end +end + +function ENT:RadiationAttack() + local d = (CurTime() - self.SpawnTime) / self.Life + local radius = self.MaxRadius * d + + local dmg = DamageInfo() + dmg:SetDamage((1 - d) * 5000) + dmg:SetDamageType(DMG_RADIATION) + dmg:SetDamagePosition(self:GetPos()) + dmg:SetAttacker(self:GetOwner() or self.Attacker) + dmg:SetInflictor(self) + + for i, k in pairs(ents.FindInSphere(self:GetPos(), radius)) do + if !IsValid(k) then continue end + constraint.RemoveAll(k) + k:TakeDamageInfo(dmg) + + end +end + +function ENT:Think() + if SERVER then + if !self:GetOwner():IsValid() then self:Remove() return end + + self:RadiationAttack() + + self:NextThink(CurTime() + 1) + + if self.dt < CurTime() then + SafeRemoveEntity(self) + end + end + + return true +end + +function ENT:Draw() + return false +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_3gl.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_3gl.lua new file mode 100644 index 0000000..33628f3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_3gl.lua @@ -0,0 +1,84 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm 3GL" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/40mm_explode-1.wav", + "^TacRP/weapons/grenade/40mm_explode-2.wav", + "^TacRP/weapons/grenade/40mm_explode-3.wav", +} + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() or self + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.5 or 1) + local dmg = 55 + + util.BlastDamage(self, attacker, self:GetPos(), 200, dmg * mult) + self:ImpactTraceAttack(ent, dmg * mult, 50) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("helicoptermegabomb", fx) + end + + self:EmitSound(table.Random(self.ExplodeSounds), 100, math.Rand(103, 110)) + + self:Remove() +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(20, 40)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(-self:GetAngles():Forward() * 400 + (VectorRand() * 10)) + + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.5, 1)) + + smoke:SetGravity(Vector(0, 0, 0)) + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_beanbag.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_beanbag.lua new file mode 100644 index 0000000..895e321 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_beanbag.lua @@ -0,0 +1,76 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm Baton" +ENT.Spawnable = false + +ENT.Model = "models/weapons/flare.mdl" +ENT.CollisionSphere = 1 + +ENT.IsRocket = false + +ENT.InstantFuse = false +ENT.RemoteFuse = false +ENT.ImpactFuse = true + +ENT.ExplodeOnDamage = false +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.ImpactDamage = 0 + +ENT.SmokeTrail = false + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Initialize() + BaseClass.Initialize(self) + if SERVER then + self:GetPhysicsObject():SetDragCoefficient(0) + end +end + +function ENT:Impact(data, collider) + if self.Impacted then return end + self.Impacted = true + + local tgt = data.HitEntity + local attacker = self.Attacker or self:GetOwner() or self + local d = data.OurOldVelocity:GetNormalized() + + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self) + dmg:SetDamage(500) + dmg:SetDamageType(DMG_CLUB) + dmg:SetDamagePosition(data.HitPos) + dmg:SetDamageForce(d * 5000) + + local atktr = util.TraceLine({ + start = self:GetPos(), + endpos = data.HitPos, + filter = self + }) + + -- TacRP.CancelBodyDamage(tgt, dmg, atktr.HitGroup) + tgt:DispatchTraceAttack(dmg, atktr) + + -- leave a bullet hole. Also may be able to hit things it can't collide with (like stuck C4) + self:FireBullets({ + Attacker = attacker, + Damage = 0, + Force = 1, + Distance = 4, + HullSize = 4, + Tracer = 0, + Dir = d, + Src = data.HitPos - d, + IgnoreEntity = self, + Callback = function(atk, tr, dmginfo) + dmginfo:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self) + end + }) + + self:Remove() + return true +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_gas.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_gas.lua new file mode 100644 index 0000000..eaa7b26 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_gas.lua @@ -0,0 +1,39 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm CS Gas" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + local attacker = self.Attacker or self:GetOwner() or self + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_gas_cloud" ) + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetOwner(attacker) + cloud:Spawn() + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_he.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_he.lua new file mode 100644 index 0000000..e3c239d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_he.lua @@ -0,0 +1,48 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm HE" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/frag_explode-1.wav", + "^TacRP/weapons/grenade/frag_explode-2.wav", + "^TacRP/weapons/grenade/frag_explode-3.wav", +} + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() or self + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + local dmg = 150 + + util.BlastDamage(self, attacker, self:GetPos(), 300, dmg * mult) + self:ImpactTraceAttack(ent, dmg * mult, 100) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound(table.Random(self.ExplodeSounds), 125) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_heal.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_heal.lua new file mode 100644 index 0000000..580c470 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_heal.lua @@ -0,0 +1,50 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm Medi-Smoke" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.BounceSounds = { + "TacRP/weapons/grenade/smoke_bounce-1.wav", + "TacRP/weapons/grenade/smoke_bounce-2.wav", + "TacRP/weapons/grenade/smoke_bounce-3.wav", + "TacRP/weapons/grenade/smoke_bounce-4.wav", +} + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + local attacker = self.Attacker or self:GetOwner() or self + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_heal_cloud" ) + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetOwner(attacker) + cloud:Spawn() + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_heat.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_heat.lua new file mode 100644 index 0000000..e7911dc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_heat.lua @@ -0,0 +1,54 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm HEAT" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/frag_explode-1.wav", + "^TacRP/weapons/grenade/frag_explode-2.wav", + "^TacRP/weapons/grenade/frag_explode-3.wav", +} + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + local attacker = self.Attacker or self:GetOwner() or self + + util.BlastDamage(self, attacker, self:GetPos(), 150, 25 * TacRP.ConVars["mult_damage_explosive"]:GetFloat()) + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_fire_cloud" ) + + if !IsValid(cloud) then return end + + local ang = self:GetAngles() + + ang:RotateAroundAxis(ang:Right(), 90) + + cloud:SetPos(self:GetPos()) + cloud:SetAngles(ang) + cloud:SetOwner(attacker) + cloud:Spawn() + + cloud:SetMoveType(MOVETYPE_NONE) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_impact.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_impact.lua new file mode 100644 index 0000000..63f2311 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_impact.lua @@ -0,0 +1,52 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm Beanbag" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.SmokeTrail = false +ENT.BounceSounds = { + "TacRP/weapons/grenade/flashbang_bounce-1.wav", + "TacRP/weapons/grenade/flashbang_bounce-2.wav", + "TacRP/weapons/grenade/flashbang_bounce-3.wav", +} + + +function ENT:Impact(data, collider) + self:EmitSound("weapons/rpg/shotdown.wav", 90, 115) + + if IsValid(data.HitEntity) then + local attacker = self.Attacker or self:GetOwner() or self + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(self) + dmg:SetDamage(Lerp((data.OurOldVelocity:Length() - 1000) / 4000, 0, 100)) + dmg:SetDamageType(DMG_CRUSH) + dmg:SetDamageForce(data.OurOldVelocity:GetNormalized() * 5000) + dmg:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmg) + end + + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + + SafeRemoveEntityDelayed(self, 3) + return true +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_lvg.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_lvg.lua new file mode 100644 index 0000000..767d149 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_lvg.lua @@ -0,0 +1,63 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm LVG" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0.3 + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/frag_explode-1.wav", + "^TacRP/weapons/grenade/frag_explode-2.wav", + "^TacRP/weapons/grenade/frag_explode-3.wav", +} + +ENT.SmokeTrail = true + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() or self + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(self) + dmg:SetDamageType(DMG_SONIC) + dmg:SetDamagePosition(self:GetPos()) + dmg:SetDamage(100 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), 300) + dmg:SetDamage(10) + util.BlastDamageInfo(dmg, self:GetPos(), 512) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + fx:SetRadius(512) + util.Effect("TacRP_flashexplosion", fx) + util.Effect("HelicopterMegaBomb", fx) + end + + TacRP.Flashbang(self, self:GetPos(), 1024, 2.5, 0.25, 1) + + self:EmitSound(table.Random(self.ExplodeSounds), 125, 110) + self:EmitSound("TacRP/weapons/grenade/flashbang_explode-1.wav", 125, 90, 0.8) + + self:Remove() +end + +function ENT:Impact() + self:EmitSound("weapons/rpg/shotdown.wav", 90, 115) +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_ratshot.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_ratshot.lua new file mode 100644 index 0000000..6ca2546 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_ratshot.lua @@ -0,0 +1,120 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm Ratshot" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.InstantFuse = false +ENT.RemoteFuse = false +ENT.ImpactFuse = true + +ENT.Delay = 0 + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/frag_explode-1.wav", + "^TacRP/weapons/grenade/frag_explode-2.wav", + "^TacRP/weapons/grenade/frag_explode-3.wav", +} + + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Initialize() + BaseClass.Initialize(self) + if SERVER then + self:SetTrigger(true) + self:UseTriggerBounds(true, 96) + end +end + +function ENT:StartTouch(ent) + if SERVER and ent ~= self:GetOwner() and (ent:IsNPC() or ent:IsPlayer() or ent:IsNextBot()) then + self:Detonate() + end +end + +function ENT:Detonate() + local dir = self:GetForward() + local attacker = self.Attacker or self:GetOwner() or self + local src = self:GetPos() + local fx = EffectData() + fx:SetOrigin(src) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + fx:SetMagnitude(4) + fx:SetScale(1.5) + fx:SetRadius(4) + fx:SetNormal(dir) + util.Effect("Sparks", fx) + util.Effect("HelicopterMegaBomb", fx) + end + + local mult = (self.NPCDamage and 0.25 or 1) * TacRP.ConVars["mult_damage_explosive"]:GetFloat() + util.BlastDamage(self, attacker, src, 328, 90 * mult) + + self:EmitSound(table.Random(self.ExplodeSounds), 115, 105) + self:EmitSound("physics/metal/metal_box_break1.wav", 100, 190, 0.5) + + self:Remove() +end + +ENT.SmokeTrail = true + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(20, 40)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetVelocity(-self:GetAngles():Forward() * 400 + (VectorRand() * 10)) + + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.5, 1)) + smoke:SetGravity(Vector(0, 0, 0)) + + local spark = emitter:Add("effects/spark", self:GetPos()) + + spark:SetStartAlpha(255) + spark:SetEndAlpha(255) + + spark:SetStartSize(4) + spark:SetEndSize(0) + + spark:SetRoll(math.Rand(-180, 180)) + spark:SetRollDelta(math.Rand(-1, 1)) + + spark:SetVelocity(-self:GetAngles():Forward() * 300 + (VectorRand() * 100)) + + spark:SetColor(255, 255, 255) + spark:SetLighting(false) + + spark:SetDieTime(math.Rand(0.1, 0.3)) + + spark:SetGravity(Vector(0, 0, 10)) + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_smoke.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_smoke.lua new file mode 100644 index 0000000..2f050ef --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_40mm_smoke.lua @@ -0,0 +1,48 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "40mm Smoke" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.BounceSounds = { + "TacRP/weapons/grenade/smoke_bounce-1.wav", + "TacRP/weapons/grenade/smoke_bounce-2.wav", + "TacRP/weapons/grenade/smoke_bounce-3.wav", + "TacRP/weapons/grenade/smoke_bounce-4.wav", +} + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_smoke_cloud" ) + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:Spawn() + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_ball.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_ball.lua new file mode 100644 index 0000000..e3927bc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_ball.lua @@ -0,0 +1,132 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Thrown Ball" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint_extras/w_baseball.mdl" --"models/weapons/w_models/w_baseball.mdl" -- TODO replace with hexed model + +ENT.IsRocket = false + +ENT.InstantFuse = false +ENT.RemoteFuse = false +ENT.ImpactFuse = true + +ENT.ExplodeOnDamage = false +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 + +ENT.SmokeTrail = false + +ENT.Damage = 25 + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Initialize() + BaseClass.Initialize(self) + if SERVER then + self:GetPhysicsObject():SetDragCoefficient(0) + self.StartPos = self:GetPos() + self.Trail = util.SpriteTrail(self, 0, color_white, true, 4, 0, 0.1, 2, "trails/tube") + end +end + +function ENT:StartTouch(ent) + if self.Impacted and (CurTime() - self.SpawnTime) > 0.05 and IsValid(ent) and ent:IsPlayer() and ent:GetNWFloat("TacRPScoutBall", 0) > CurTime() then + ent:SetNWFloat("TacRPScoutBall", 0) + SafeRemoveEntity(self) + end +end + +function ENT:PhysicsCollide(data, collider) + + if IsValid(data.HitEntity) and data.HitEntity:GetClass() == "func_breakable_surf" then + self:FireBullets({ + Attacker = self:GetOwner(), + Inflictor = self, + Damage = 0, + Distance = 32, + Tracer = 0, + Src = self:GetPos(), + Dir = data.OurOldVelocity:GetNormalized(), + }) + local pos, ang, vel = self:GetPos(), self:GetAngles(), data.OurOldVelocity + self:SetAngles(ang) + self:SetPos(pos) + self:GetPhysicsObject():SetVelocityInstantaneous(vel * 0.5) + return + end + + if self.Impacted then return end + self.Impacted = true + self:SetTrigger(true) + self:UseTriggerBounds(true, 8) + if IsValid(self.Trail) then + self.Trail:Remove() + end + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + local attacker = self.Attacker or self:GetOwner() or self + if IsValid(data.HitEntity) then + local d = data.OurOldVelocity:GetNormalized() + + local tgtpos = data.HitPos + local dist = (tgtpos - self.StartPos):Length() + self.Damage = Lerp(math.Clamp(dist / 1500, 0, 1) ^ 1.5, 15, 50) * (data.HitEntity:IsPlayer() and 1 or 1.5) + + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self) + dmg:SetDamage(self.Damage) + dmg:SetDamageType(DMG_SLASH) + dmg:SetDamageForce(d * 10000) + dmg:SetDamagePosition(data.HitPos) + + if dist > 200 then + data.HitEntity.TacRPBashSlow = true + end + + if data.HitEntity:IsPlayer() then + local wep = data.HitEntity:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP then + wep:SetBreath(0) + wep:SetOutOfBreath(true) + end + end + + if data.HitEntity:IsPlayer() or data.HitEntity:IsNPC() or data.HitEntity:IsNextBot() then + if dist >= 1500 then + data.HitEntity:EmitSound("tacrp/sandman/pl_impact_stun_range.wav", 100) + else + data.HitEntity:EmitSound("tacrp/sandman/pl_impact_stun.wav", 90) + end + else + data.HitEntity:EmitSound("tacrp/sandman/baseball_hitworld" .. math.random(1, 3) .. ".wav", 90) + end + + if data.HitEntity:IsNPC() or data.HitEntity:IsNextBot() then + data.HitEntity:SetVelocity(Vector(0, 0, 200)) + if data.HitEntity:IsNPC() then + data.HitEntity:SetSchedule(SCHED_FLINCH_PHYSICS) + end + end + + local atktr = util.TraceLine({ + start = self:GetPos(), + endpos = tgtpos, + filter = self + }) + + TacRP.CancelBodyDamage(data.HitEntity, dmg, atktr.HitGroup) + data.HitEntity:SetPhysicsAttacker(attacker, 3) + data.HitEntity:DispatchTraceAttack(dmg, atktr) + + self:SetOwner(nil) + else + data.HitEntity:EmitSound("tacrp/sandman/baseball_hitworld" .. math.random(1, 3) .. ".wav", 90) + end + + SafeRemoveEntityDelayed(self, 5) + -- self:GetPhysicsObject():SetVelocity(-data.HitNormal * data.OurNewVelocity:Length()) +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_base.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_base.lua new file mode 100644 index 0000000..113bffd --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_base.lua @@ -0,0 +1,618 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Base Projectile" +ENT.Category = "" + +ENT.Spawnable = false +ENT.Model = "" + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +ENT.Material = false // custom material + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.Sticky = false // projectile sticks on impact + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.TimeFuse = false // projectile will arm after this amount of time +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. +ENT.StickyFuse = false // projectile becomes timed after sticking. + +ENT.SafetyFuse = 0 // impact fuse hitting too early will not detonate + +ENT.RemoveOnImpact = false +ENT.ExplodeOnImpact = false +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false // projectile explodes when it enters water + +ENT.Defusable = false // press E on the projectile to defuse it +ENT.DefuseOnDamage = false + +ENT.ImpactDamage = 50 +ENT.ImpactDamageType = DMG_CRUSH + DMG_CLUB + +ENT.Delay = 0 // after being triggered and this amount of time has passed, the projectile will explode. + +ENT.SoundHint = false // Emit a sound hint so NPCs can react +ENT.SoundHintDelay = 0 // Delay after spawn +ENT.SoundHintRadius = 328 +ENT.SoundHintDuration = 1 + +ENT.Armed = false + +ENT.SmokeTrail = false // leaves trail of smoke +ENT.FlareColor = nil +ENT.FlareSizeMin = 200 +ENT.FlareSizeMax = 250 + + +// Guided projectile related +ENT.SteerDelay = 0 // Delay before steering logic kicks in +ENT.SteerSpeed = 60 // Turn rate in degrees per second +ENT.SteerBrake = 0 // Amount of speed to slow down by when turning +ENT.SeekerAngle = 180 // Angle difference (degrees) above which projectile loses target +ENT.SeekerExplodeRange = 256 // Distance to the target below which the missile will immediately explode +ENT.SeekerExplodeSnapPosition = true // When exploding on a seeked target, teleport to the entity's position for more damage +ENT.SeekerExplodeAngle = 180 // Angle tolerance (degrees) below which detonation can happen + +ENT.TopAttack = false // This missile will attack from the top +ENT.TopAttackHeight = 512 // Distance above target to top attack +ENT.TopAttackDistance = 128 // Distance from target to stop top attacking + +ENT.RocketLifetime = 30 // Rocket will cut after this time + +ENT.MinSpeed = 0 +ENT.MaxSpeed = 0 +ENT.Acceleration = 0 + +ENT.LeadTarget = false // account for target's velocity and distance +ENT.SuperSteerTime = 0 // Amount of time where turn rate is boosted +ENT.SuperSteerSpeed = 100 // Boosted turn rate in degrees per seconds + +ENT.NoReacquire = true // If target is lost, it cannot be tracked anymore +ENT.FlareRedirectChance = 0 + +ENT.LockOnEntity = NULL +ENT.TargetPos = nil + +ENT.AudioLoop = nil +ENT.BounceSounds = nil +ENT.CollisionSphere = nil +ENT.GunshipWorkaround = true + +// Tell LVS to not ricochet us +ENT.DisableBallistics = true + +ENT.TopAttacking = false +ENT.StartSuperSteerTime = 0 + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "Weapon") +end + +function ENT:Initialize() + if SERVER then + self:SetModel(self.Model) + self:SetMaterial(self.Material or "") + if self.CollisionSphere then + self:PhysicsInitSphere(self.CollisionSphere) + else + self:PhysicsInit(SOLID_VPHYSICS) + end + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + if self.Defusable then + self:SetUseType(SIMPLE_USE) + end + + local phys = self:GetPhysicsObject() + if !phys:IsValid() then + self:Remove() + return + end + + phys:EnableDrag(false) + phys:SetDragCoefficient(0) + phys:SetBuoyancyRatio(0) + phys:Wake() + + if self.IsRocket then + phys:EnableGravity(false) + end + + if self.TopAttack then + self.TopAttacking = true + end + + self:SwitchTarget(self.LockOnEntity) + end + + self.StartSuperSteerTime = CurTime() + self.SpawnTime = CurTime() + self.NextFlareRedirectTime = 0 + + self.NPCDamage = IsValid(self:GetOwner()) and self:GetOwner():IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() + + if self.AudioLoop then + self.LoopSound = CreateSound(self, self.AudioLoop) + self.LoopSound:Play() + end + + if self.InstantFuse then + self.ArmTime = CurTime() + self.Armed = true + end + + self:OnInitialize() +end + +function ENT:OnRemove() + if self.LoopSound then + self.LoopSound:Stop() + end +end + +function ENT:OnTakeDamage(dmg) + if self.Detonated then return end + + // self:TakePhysicsDamage(dmg) + + if self.ExplodeOnDamage then + if IsValid(self:GetOwner()) and IsValid(dmg:GetAttacker()) then self:SetOwner(dmg:GetAttacker()) + else self.Attacker = dmg:GetAttacker() or self.Attacker end + self:PreDetonate() + elseif self.DefuseOnDamage and dmg:GetDamageType() != DMG_BLAST then + self:EmitSound("physics/plastic/plastic_box_break" .. math.random(1, 2) .. ".wav", 70, math.Rand(95, 105)) + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + fx:SetNormal(self:GetAngles():Forward()) + fx:SetAngles(self:GetAngles()) + util.Effect("ManhackSparks", fx) + self.Detonated = true + self:Remove() + end +end + +function ENT:PhysicsCollide(data, collider) + if IsValid(data.HitEntity) and data.HitEntity:GetClass() == "func_breakable_surf" then + self:FireBullets({ + Attacker = self:GetOwner(), + Inflictor = self, + Damage = 0, + Distance = 32, + Tracer = 0, + Src = self:GetPos(), + Dir = data.OurOldVelocity:GetNormalized(), + }) + local pos, ang, vel = self:GetPos(), self:GetAngles(), data.OurOldVelocity + self:SetAngles(ang) + self:SetPos(pos) + self:GetPhysicsObject():SetVelocityInstantaneous(vel * 0.5) + return + end + + if (self.SafetyFuse or 0) > 0 and self.SpawnTime + self.SafetyFuse > CurTime() then + self:SafetyImpact(data, collider) + self:Remove() + return + elseif self.ImpactFuse then + if !self.Armed then + self.ArmTime = CurTime() + self.Armed = true + + if self:Impact(data, collider) then + return + end + end + + if self.Delay == 0 or self.ExplodeOnImpact then + self:PreDetonate(data.HitEntity) + end + elseif self.ImpactDamage > 0 and (self.NextImpactDamage or 0) < CurTime() and data.Speed >= 10 and IsValid(data.HitEntity) and (engine.ActiveGamemode() != "terrortown" or !data.HitEntity:IsPlayer()) then + local dmg = DamageInfo() + dmg:SetAttacker(IsValid(self:GetOwner()) and self:GetOwner() or self.Attacker) + dmg:SetInflictor(self) + dmg:SetDamage(self.ImpactDamage) + dmg:SetDamageType(self.ImpactDamageType) + dmg:SetDamageForce(data.OurOldVelocity) + dmg:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmg) + self.NextImpactDamage = CurTime() + 0.05 + elseif !self.ImpactFuse then + self:Impact(data, collider) + end + + if self.Sticky then + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetPos(data.HitPos) + + self:SetAngles((-data.HitNormal):Angle()) + + if data.HitEntity:IsWorld() or data.HitEntity:GetSolid() == SOLID_BSP then + self:SetMoveType(MOVETYPE_NONE) + self:SetPos(data.HitPos) + else + self:SetPos(data.HitPos) + self:SetParent(data.HitEntity) + end + + self:EmitSound("TacRP/weapons/plant_bomb.wav", 65) + + self.Attacker = self:GetOwner() + self:SetOwner(NULL) + + if self.StickyFuse and !self.Armed then + self.ArmTime = CurTime() + self.Armed = true + end + + self:Stuck() + else + if !self.Bounced then + self.Bounced = true + local dot = data.HitNormal:Dot(Vector(0, 0, 1)) + if dot < 0 then + self:GetPhysicsObject():SetVelocityInstantaneous(data.OurNewVelocity * (1 + dot * 0.5)) + end + end + end + + if data.DeltaTime < 0.1 then return end + if !self.BounceSounds then return end + + local s = table.Random(self.BounceSounds) + + self:EmitSound(s) +end + +function ENT:OnThink() +end + +function ENT:OnInitialize() +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(50, 75)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(-self:GetAngles():Forward() * 400 + (VectorRand() * 10)) + + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.75, 1.25)) + + smoke:SetGravity(Vector(0, 0, 0)) + + emitter:Finish() + end +end + +function ENT:PhysicsUpdate(phys) + if self.SpawnTime + self.RocketLifetime < CurTime() then return end + + if self.TargetPos and (self.SteerDelay + self.SpawnTime) <= CurTime() then + local v = phys:GetVelocity() + + local steer_amount = self.SteerSpeed * FrameTime() + if self.SuperSteerTime + self.StartSuperSteerTime > CurTime() then + steer_amount = self.SuperSteerSpeed * FrameTime() + end + + local dir = (self.TargetPos - self:GetPos()):GetNormalized() + local diff = math.deg(math.acos(dir:Dot(self:GetForward()))) + local turn_deg = math.min(diff, steer_amount) + + local newang = self:GetAngles() + newang:RotateAroundAxis(dir:Cross(self:GetForward()), -turn_deg) + + local brake = turn_deg / steer_amount * self.SteerBrake + + self:SetAngles(Angle(newang.p, newang.y, 0)) + phys:SetVelocityInstantaneous(self:GetForward() * math.Clamp(v:Length() + (self.Acceleration - brake) * FrameTime(), self.MinSpeed, self.MaxSpeed)) + elseif self.Acceleration > 0 then + phys:SetVelocityInstantaneous(self:GetForward() * math.Clamp(phys:GetVelocity():Length() + self.Acceleration * FrameTime(), self.MinSpeed, self.MaxSpeed)) + end +end + +local gunship = {["npc_combinegunship"] = true, ["npc_combinedropship"] = true, ["npc_helicopter"] = true} + +function ENT:DoTracking() + local target = self.LockOnEntity + if IsValid(target) then + if self.TopAttack and self.TopAttacking then + local xyvector = (target:WorldSpaceCenter() - self:GetPos()) + xyvector.z = 0 + local dist = xyvector:Length() + + self.TargetPos = target:WorldSpaceCenter() + Vector(0, 0, self.TopAttackHeight) + if self.LeadTarget then + local dist2 = (self.TargetPos - self:GetPos()):Length() + + local time = dist2 / self:GetVelocity():Length() + self.TargetPos = self.TargetPos + (target:GetVelocity() * time) + end + + if dist <= self.TopAttackDistance then + self.TopAttacking = false + self.StartSuperSteerTime = CurTime() + end + else + local dir = (target:WorldSpaceCenter() - self:GetPos()):GetNormalized() + local diff = math.deg(math.acos(dir:Dot(self:GetForward()))) + if diff <= self.SeekerAngle or self.SuperSteerTime + self.StartSuperSteerTime > CurTime() then + self.TargetPos = target:WorldSpaceCenter() + local dist = (self.TargetPos - self:GetPos()):Length() + if self.LeadTarget then + local time = dist / self:GetVelocity():Length() + self.TargetPos = self.TargetPos + (target:GetVelocity() * time) + end + + if self.FlareRedirectChance > 0 and self.NextFlareRedirectTime <= CurTime() and !TacRP.FlareEntities[target:GetClass()] then + local flares = ents.FindInSphere(self:GetPos(), 2048) + for k, v in pairs(flares) do + if (TacRP.FlareEntities[v:GetClass()] or v.IsTacRPFlare) and math.Rand(0, 1) <= self.FlareRedirectChance then + self:SwitchTarget(v) + break + end + end + self.NextFlareRedirectTime = CurTime() + 0.5 + end + + if self.SeekerExplodeRange > 0 and diff <= self.SeekerExplodeAngle + and self.SteerDelay + self.SpawnTime <= CurTime() + and dist < self.SeekerExplodeRange then + local tr = util.TraceLine({ + start = self:GetPos(), + endpos = target:GetPos(), + filter = self, + mask = MASK_SOLID, + }) + if self.SeekerExplodeSnapPosition then + self:SetPos(tr.HitPos) + end + self:PreDetonate(target) + end + elseif self.NoReacquire then + self.LockOnEntity = nil + self.TargetPos = nil + end + end + elseif (!IsValid(target) and self.NoReacquire) or target.UnTrackable then + self.LockOnEntity = nil + self.TargetPos = nil + end + + if self.GunshipWorkaround and (self.GunshipCheck or 0 < CurTime()) then + self.GunshipCheck = CurTime() + 0.33 + local tr = util.TraceLine({ + start = self:GetPos(), + endpos = self:GetPos() + (self:GetVelocity() * 6 * engine.TickInterval()), + filter = self, + mask = MASK_SHOT + }) + if IsValid(tr.Entity) and gunship[tr.Entity:GetClass()] then + self:SetPos(tr.HitPos) + self:PreDetonate(tr.Entity) + end + end +end + +function ENT:Think() + if !IsValid(self) or self:GetNoDraw() then return end + + if !self.SpawnTime then + self.SpawnTime = CurTime() + end + + if SERVER and self.SoundHint and CurTime() >= self.SpawnTime + self.SoundHintDelay then + self.SoundHint = false // only once + sound.EmitHint(SOUND_DANGER, self:GetPos(), self.SoundHintRadius, self.SoundHintDuration, self) + end + + if !self.Armed and isnumber(self.TimeFuse) and self.SpawnTime + self.TimeFuse < CurTime() then + self.ArmTime = CurTime() + self.Armed = true + end + + if self.Armed and self.ArmTime + self.Delay < CurTime() then + self:PreDetonate() + end + + if self.ExplodeUnderwater and self:WaterLevel() > 0 then + self:PreDetonate() + end + + if SERVER and self.SpawnTime + self.RocketLifetime > CurTime() then + self:DoTracking() + end + + self:DoSmokeTrail() + + self:OnThink() + + self:NextThink(CurTime()) + return true +end + +function ENT:Use(ply) + if !self.Defusable then return end + + self:EmitSound("TacRP/weapons/rifle_jingle-1.wav") + + if self.PickupAmmo then + ply:GiveAmmo(1, self.PickupAmmo, true) + end + + self:Remove() +end + +function ENT:RemoteDetonate() + self:EmitSound("TacRP/weapons/c4/relay_switch-1.wav") + + self.ArmTime = CurTime() + self.Armed = true +end + +function ENT:PreDetonate(ent) + if CLIENT then return end + + if !self.Detonated then + self.Detonated = true + + if !IsValid(self.Attacker) and !IsValid(self:GetOwner()) then self.Attacker = game.GetWorld() end + + self:Detonate(ent) + end +end + +function ENT:Detonate(ent) + // fill this in :) +end + +function ENT:Impact(data, collider) +end + +function ENT:SafetyImpact(data, collider) + local attacker = self.Attacker or self:GetOwner() + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + + if IsValid(data.HitEntity) then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(attacker) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(self.ImpactDamageType) + dmginfo:SetDamage(self.ImpactDamage * (self.NPCDamage and 0.25 or 1)) + dmginfo:SetDamageForce(data.OurOldVelocity * 20) + dmginfo:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmginfo) + end + + self:EmitSound("weapons/rpg/shotdown.wav", 80) + + if self:GetModel() == "models/weapons/tacint/rocket_deployed.mdl" then + for i = 1, 4 do + local prop = ents.Create("prop_physics") + prop:SetPos(self:GetPos()) + prop:SetAngles(self:GetAngles()) + prop:SetModel("models/weapons/tacint/rpg7_shrapnel_p" .. i .. ".mdl") + prop:Spawn() + prop:GetPhysicsObject():SetVelocityInstantaneous(data.OurNewVelocity * 0.5 + VectorRand() * 75) + prop:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + SafeRemoveEntityDelayed(prop, 3) + end + end +end + +function ENT:ImpactTraceAttack(ent, damage, pen) + if !IsValid(ent) then return end + if ent.LVS then + // LVS only does its penetration logic on FireBullets, so we must fire a bullet to trigger it + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) // The projectile blocks the penetration decal?! + self:FireBullets({ + Attacker = self.Attacker or self:GetOwner(), + Damage = damage, + Tracer = 0, + Src = self:GetPos(), + Dir = self:GetForward(), + HullSize = 16, + Distance = 128, + IgnoreEntity = self, + Callback = function(atk, btr, dmginfo) + dmginfo:SetDamageType(DMG_AIRBOAT + DMG_SNIPER) // LVS wants this + dmginfo:SetDamageForce(self:GetForward() * pen) // penetration strength + end, + }) + else + // This is way more consistent because the damage always lands + local tr = util.TraceHull({ + start = self:GetPos(), + endpos = self:GetPos() + self:GetForward() * 256, + filter = ent, + whitelist = true, + ignoreworld = true, + mask = MASK_ALL, + mins = Vector( -8, -8, -8 ), + maxs = Vector( 8, 8, 8 ), + }) + local dmginfo = DamageInfo() + dmginfo:SetAttacker(self.Attacker or self:GetOwner()) + dmginfo:SetInflictor(self) + dmginfo:SetDamagePosition(self:GetPos()) + dmginfo:SetDamageForce(self:GetForward() * pen) + dmginfo:SetDamageType(DMG_AIRBOAT + DMG_SNIPER) + dmginfo:SetDamage(damage) + ent:DispatchTraceAttack(dmginfo, tr, self:GetForward()) + end +end + +function ENT:Stuck() +end + +function ENT:DrawTranslucent() + self:Draw() +end + +local mat = Material("effects/ar2_altfire1b") + +function ENT:Draw() + if self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.05) > CurTime() then return end + + self:DrawModel() + + if self.FlareColor then + local mult = self.SafetyFuse and math.Clamp((CurTime() - (self.SpawnTime + self.SafetyFuse)) / self.SafetyFuse, 0.1, 1) or 1 + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + (self:GetAngles():Forward() * -16), mult * math.Rand(self.FlareSizeMin, self.FlareSizeMax), mult * math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) + end +end + +function ENT:SwitchTarget(target) + if IsValid(self.LockOnEntity) then + if isfunction(self.LockOnEntity.OnLaserLock) then + self.LockOnEntity:OnLaserLock(false) + end + end + + self.LockOnEntity = target + + if IsValid(self.LockOnEntity) then + if isfunction(self.LockOnEntity.OnLaserLock) then + self.LockOnEntity:OnLaserLock(true) + end + end +end + +hook.Add("EntityTakeDamage", "tacrp_proj_collision", function(ent, dmginfo) + if IsValid(dmginfo:GetInflictor()) + and scripted_ents.IsBasedOn(dmginfo:GetInflictor():GetClass(), "tacrp_proj_base") + and dmginfo:GetDamageType() == DMG_CRUSH then dmginfo:SetDamage(0) return true end +end) diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_breach_slug.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_breach_slug.lua new file mode 100644 index 0000000..157a842 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_breach_slug.lua @@ -0,0 +1,98 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Breaching Slug" +ENT.Spawnable = false + +ENT.Model = "models/weapons/flare.mdl" +ENT.CollisionSphere = 1 + +ENT.IsRocket = false + +ENT.InstantFuse = false +ENT.RemoteFuse = false +ENT.ImpactFuse = true + +ENT.ExplodeOnDamage = false +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.ImpactDamage = 0 + +ENT.SmokeTrail = false + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Initialize() + BaseClass.Initialize(self) + if SERVER then + self:GetPhysicsObject():SetDragCoefficient(5) + end +end + +function ENT:Impact(data, collider) + if self.Impacted then return end + self.Impacted = true + + local tgt = data.HitEntity + local attacker = self.Attacker or self:GetOwner() or self + local d = data.OurOldVelocity:GetNormalized() + if IsValid(tgt) and string.find(tgt:GetClass(), "door") then + -- if slug spent too much time in the air, it can only open doors, not breach them + local vel = d * math.max(0, (self.SpawnTime + 0.25 - CurTime()) / 0.25) ^ 0.75 * 3000 + for _, otherDoor in pairs(ents.FindInSphere(tgt:GetPos(), 72)) do + if tgt != otherDoor and otherDoor:GetClass() == tgt:GetClass() then + TacRP.DoorBust(otherDoor, vel, attacker) + break + end + end + TacRP.DoorBust(tgt, vel, attacker) + elseif IsValid(tgt) and tgt:GetClass() == "func_button" then + -- press buttons remotely :3 + tgt:Use(attacker, self) + end + + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self) + dmg:SetDamage(50) + dmg:SetDamageType(DMG_CLUB) + dmg:SetDamagePosition(data.HitPos) + dmg:SetDamageForce(d * 30000) + + local atktr = util.TraceLine({ + start = self:GetPos(), + endpos = data.HitPos, + filter = self + }) + + -- TacRP.CancelBodyDamage(tgt, dmg, atktr.HitGroup) + tgt:DispatchTraceAttack(dmg, atktr) + + -- leave a bullet hole. Also may be able to hit things it can't collide with (like stuck C4) + self:FireBullets({ + Attacker = attacker, + Damage = 0, + Force = 1, + Distance = 4, + HullSize = 4, + Tracer = 0, + Dir = d, + Src = data.HitPos - d, + IgnoreEntity = self, + Callback = function(atk, tr, dmginfo) + dmginfo:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self) + end + }) + + self:Remove() + return true +end + +hook.Add("PostEntityTakeDamage", "tacrp_proj_breach_slug", function(ent, dmg, took) + if took and IsValid(dmg:GetInflictor()) and ent:IsPlayer() + and dmg:GetInflictor():GetClass() == "tacrp_proj_breach_slug" + and (!IsValid(ent:GetActiveWeapon()) or !ent:GetActiveWeapon().ArcticTacRP or !ent:GetActiveWeapon():GetValue("StunResist")) then + ent:SetNWFloat("TacRPLastBashed", CurTime() + 3) + end +end) diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet.lua new file mode 100644 index 0000000..6562ab3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet.lua @@ -0,0 +1,165 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Gyrojet Round" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint_shark/projectiles/gyrojet_rocket.mdl" +ENT.CollisionSphere = 2 + +ENT.IsRocket = true + +ENT.InstantFuse = false +ENT.RemoteFuse = false +ENT.ImpactFuse = true + +ENT.ExplodeOnDamage = false +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.SafetyFuse = 0 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 128, 0, 100) +ENT.FlareLife = 0.5 + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/40mm_explode-1.wav", + "^TacRP/weapons/grenade/40mm_explode-2.wav", + "^TacRP/weapons/grenade/40mm_explode-3.wav", +} + +ENT.ImpactDamage = 0 + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +function ENT:MakeSmoke() + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(55) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(16, 24)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(-self:GetAngles():Forward() * 200 + (VectorRand() * 5) + + self:GetAngles():Right() * math.sin(CurTime() * math.pi * 16) * 32 + + self:GetAngles():Up() * math.cos(CurTime() * math.pi * 16) * 32) + + smoke:SetColor(255, 255, 255) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.4, 0.6)) + + smoke:SetGravity(Vector(0, 0, 0)) + + emitter:Finish() +end + +function ENT:Think() + if !IsValid(self) or self:GetNoDraw() then return end + + if CurTime() - self.SpawnTime >= self.FlareLife then + self.FlareColor = false + end + + if !self.SpawnTime then + self.SpawnTime = CurTime() + end + + if !self.Armed and isnumber(self.TimeFuse) and self.SpawnTime + self.TimeFuse < CurTime() then + self.ArmTime = CurTime() + self.Armed = true + end + + if self.Armed and self.ArmTime + self.Delay < CurTime() then + self:PreDetonate() + end + + if self.ExplodeUnderwater and self:WaterLevel() > 0 then + self:PreDetonate() + end + + if CLIENT and self.SmokeTrail and CurTime() - self.SpawnTime >= 0.1 then + self:MakeSmoke() + end + + self:OnThink() +end + +function ENT:Impact(data, collider) + + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("MetalSpark", fx) + + local attacker = self.Attacker or self:GetOwner() or self + local inflictor = attacker.GetWeapon and attacker:GetWeapon("tacrp_sd_gyrojet") + local dmg = inflictor and inflictor.GetValue and inflictor:GetValue("Damage_Max") or 75 + self:FireBullets({ + Attacker = attacker, + Inflictor = inflictor or self, + Force = 5, + Num = 1, + Tracer = 0, + Dir = data.OurOldVelocity:GetNormalized(), + Src = data.HitPos, + Damage = dmg, + HullSize = 2, + Callback = function(att, btr, dmginfo) + if IsValid(btr.Entity) then + if IsValid(inflictor) and inflictor.GetValue then + local range = (btr.HitPos - btr.StartPos):Length() + inflictor:AfterShotFunction(btr, dmginfo, range, inflictor:GetValue("Penetration"), {}) + end + else + -- Even though bullet did not hit, projectile did; just apply damage + if attacker:IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() then + dmginfo:ScaleDamage(0.25) + end + data.HitEntity:TakeDamageInfo(dmginfo) + end + + end + }) + + self:Remove() + return false +end + +function ENT:Detonate() + self:Remove() +end + +function ENT:PhysicsUpdate(phys) + local len = phys:GetVelocity():Length() + local f = math.Clamp(len / 4000, 0, 1) + phys:AddVelocity(self:GetForward() * Lerp(f, 10, 1)) +end + +local mat = Material("sprites/light_glow02_add") + +function ENT:Draw() + self:DrawModel() + + if self.FlareColor then + local s = Lerp(((CurTime() - self.SpawnTime) / self.FlareLife) ^ 0.5, 1, 0) + local s1, s2 = 32 * s, 64 * s + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + (self:GetAngles():Forward() * -4), math.Rand(s1, s2), math.Rand(s1, s2), self.FlareColor) + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_he.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_he.lua new file mode 100644 index 0000000..5e4e69b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_he.lua @@ -0,0 +1,39 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_gyrojet" +ENT.PrintName = "HE Gyrojet Round" +ENT.Spawnable = false + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 200, 128, 100) +ENT.FlareLife = 0.5 + + +function ENT:Impact(data, collider) + return false +end + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() or self + local inflictor = attacker.GetWeapon and attacker:GetWeapon("tacrp_sd_gyrojet") + local dmg = inflictor and inflictor:GetValue("Damage_Max") or 50 + if attacker:IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() then + dmg = dmg * 0.25 + end + + util.BlastDamage(self, attacker, self:GetPos(), 128, dmg) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + fx:SetNormal(self:GetForward()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("HelicopterMegaBomb", fx) + end + + self:EmitSound(table.Random(self.ExplodeSounds), 80, 110, 0.75) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_lv.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_lv.lua new file mode 100644 index 0000000..705828a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_lv.lua @@ -0,0 +1,41 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_gyrojet" +ENT.PrintName = "LV Gyrojet Round" +ENT.Spawnable = false + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 255, 200, 100) +ENT.FlareLife = 0.2 + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +function ENT:MakeSmoke() + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(45) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(2) + smoke:SetEndSize(math.Rand(8, 16)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(-self:GetAngles():Forward() * 150 + (VectorRand() * 5)) + + smoke:SetColor(255, 255, 255) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.2, 0.4)) + + smoke:SetGravity(Vector(0, 0, 0)) + + emitter:Finish() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_pipe.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_pipe.lua new file mode 100644 index 0000000..ec4e2f3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_pipe.lua @@ -0,0 +1,86 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_gyrojet" +ENT.PrintName = "Gyrojet Pipe Grenade" +ENT.Spawnable = false + +ENT.Model = "models/Items/AR2_Grenade.mdl" +ENT.CollisionSphere = false + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 200, 128, 100) +ENT.FlareLife = 0.1 + +ENT.IsRocket = false +ENT.InstantFuse = true +ENT.ImpactFuse = false +ENT.Delay = 1.5 + +ENT.AudioLoop = false + +function ENT:Impact(data, collider) + if IsValid(data.HitEntity) and (data.HitEntity:IsPlayer() or data.HitEntity:IsNPC() or data.HitEntity:IsNextBot()) then + self:Detonate() + end +end + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() or self + local inflictor = attacker.GetWeapon and attacker:GetWeapon("tacrp_sd_gyrojet") + local dmg = inflictor and inflictor.GetValue and inflictor:GetValue("Damage_Max") or 75 + if attacker:IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() then + dmg = dmg * 0.25 + end + + util.BlastDamage(self, attacker, self:GetPos(), 150, dmg) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + fx:SetNormal(self:GetForward()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("HelicopterMegaBomb", fx) + end + + self:EmitSound(table.Random(self.ExplodeSounds), 80, 110, 0.75) + + self:Remove() +end + +function ENT:PhysicsUpdate(phys) +end + + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +function ENT:MakeSmoke() + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(2) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(10, 12)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 25) + + smoke:SetColor(255, 255, 255) + smoke:SetLighting(false) + + smoke:SetDieTime(math.Rand(0.6, 0.8)) + + smoke:SetGravity(Vector(0, 0, 256)) + + emitter:Finish() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_ratshot.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_ratshot.lua new file mode 100644 index 0000000..0107ee6 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_gyrojet_ratshot.lua @@ -0,0 +1,56 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_gyrojet" +ENT.PrintName = "Ratshot Gyrojet Round" +ENT.Spawnable = false + +ENT.SmokeTrail = true +ENT.FlareColor = Color(155, 155, 255, 255) +ENT.FlareLife = 0.4 + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Initialize() + BaseClass.Initialize(self) + if SERVER then + self:SetTrigger(true) + self:UseTriggerBounds(true, 48) + end +end + +function ENT:StartTouch(ent) + if SERVER and ent ~= self:GetOwner() and (ent:IsNPC() or ent:IsPlayer() or ent:IsNextBot()) then + self:Detonate() + end +end + +function ENT:Detonate() + local dir = self:GetForward() + local attacker = self.Attacker or self:GetOwner() or self + local inflictor = attacker.GetWeapon and attacker:GetWeapon("tacrp_sd_gyrojet") + local damage = inflictor and inflictor:GetValue("Damage_Max") or 50 + if attacker:IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() then + damage = damage * 0.25 + end + local src = self:GetPos() -- + dir * 32 + local fx = EffectData() + fx:SetOrigin(src) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + fx:SetMagnitude(4) + fx:SetScale(4) + fx:SetRadius(8) + fx:SetNormal(dir) + util.Effect("Sparks", fx) + util.Effect("HelicopterMegaBomb", fx) + end + + util.BlastDamage(self, attacker, src, 200, damage) + + self:EmitSound(table.Random(self.ExplodeSounds), 90, 110, 0.75) + self:EmitSound("physics/metal/metal_box_break1.wav", 90, 175) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_knife.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_knife.lua new file mode 100644 index 0000000..73f6d5c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_knife.lua @@ -0,0 +1,257 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Thrown Knife" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/w_knife.mdl" + +ENT.IsRocket = false + +ENT.InstantFuse = false +ENT.RemoteFuse = false +ENT.ImpactFuse = true + +ENT.ExplodeOnDamage = false +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.ImpactDamage = 0 + +ENT.SmokeTrail = false +local path = "tacrp/weapons/knife/" +ENT.Sound_MeleeHit = { + path .. "/scrape_metal-1.wav", + path .. "/scrape_metal-2.wav", + path .. "/scrape_metal-3.wav", +} +ENT.Sound_MeleeHitBody = { + path .. "/flesh_hit-1.wav", + path .. "/flesh_hit-2.wav", + path .. "/flesh_hit-3.wav", + path .. "/flesh_hit-4.wav", + path .. "/flesh_hit-5.wav", +} + +ENT.Damage = 35 + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Initialize() + BaseClass.Initialize(self) + if SERVER then + self:GetPhysicsObject():SetDragCoefficient(2) + end +end + +function ENT:Impact(data, collider) + if self.Impacted then return end + self.Impacted = true + + local tgt = data.HitEntity + local attacker = self.Attacker or self:GetOwner() or self + if IsValid(tgt) then + local d = data.OurOldVelocity:GetNormalized() + + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self) + dmg:SetDamage(self.Damage) + dmg:SetDamageType(self.DamageType or DMG_SLASH) + dmg:SetDamageForce(d * 10000) + dmg:SetDamagePosition(data.HitPos) + + local tgtpos = data.HitPos + if (tgt:IsPlayer() or tgt:IsNPC() or tgt:IsNextBot()) then + if (tgt:GetNWFloat("TacRPLastBashed", 0) + 3 >= CurTime() + or (tgt:GetNWFloat("TacRPStunStart", 0) + tgt:GetNWFloat("TacRPStunDur", 0) >= CurTime())) then + dmg:ScaleDamage(1.5) + tgt:EmitSound("weapons/crossbow/bolt_skewer1.wav", 80, 110) + end + + -- Check if the knife is a headshot + -- Either the head is the closest bodygroup, or the direction is quite on point + local headpos = nil + local pos = data.HitPos + d * 8 + local hset = tgt:GetHitboxSet() + local hdot, bhg, bdist, hdist = 0, 0, math.huge, math.huge + for i = 0, tgt:GetHitBoxCount(hset) or 0 do + + local bone = tgt:GetHitBoxBone(i, hset) + local mtx = bone and tgt:GetBoneMatrix(bone) + if !mtx then continue end + local hpos = mtx:GetTranslation() + local dot = (hpos - data.HitPos):GetNormalized():Dot(d) + local dist = (hpos - pos):LengthSqr() + + if tgt:GetHitBoxHitGroup(i, hset) == HITGROUP_HEAD then + hdot = dot + hdist = dist + headpos = hpos + end + if dist < bdist then + bdist = dist + bhg = tgt:GetHitBoxHitGroup(i, hset) + tgtpos = hpos + end + end + + if bhg == HITGROUP_HEAD or (hdot >= 0.85 and hdist < 2500) then + dmg:ScaleDamage(2) + tgt:EmitSound("player/headshot" .. math.random(1, 2) .. ".wav", 80, 105) + tgtpos = headpos + end + + self:EmitSound(istable(self.Sound_MeleeHitBody) and self.Sound_MeleeHitBody[math.random(1, #self.Sound_MeleeHitBody)] or self.Sound_MeleeHitBody, 80, 110, 1) + -- self:EmitSound("tacrp/weapons/knife/flesh_hit-" .. math.random(1, 5) .. ".wav", 80, 110, 1) + + -- local ang = data.OurOldVelocity:Angle() + -- local fx = EffectData() + -- fx:SetStart(data.HitPos - d * 4) + -- fx:SetOrigin(data.HitPos) + -- fx:SetNormal(d) + -- fx:SetAngles(-ang) + -- fx:SetEntity(tgt) + -- fx:SetDamageType(DMG_SLASH) + -- fx:SetSurfaceProp(data.TheirSurfaceProps) + -- util.Effect("Impact", fx) + + else + dmg:SetDamageForce(d * 30000) + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + if SERVER then + self:EmitSound(istable(self.Sound_MeleeHit) and self.Sound_MeleeHit[math.random(1, #self.Sound_MeleeHit)] or self.Sound_MeleeHit, 80, 110, 1) + end + end + + -- tgt:TakeDamageInfo(dmg) + + local atktr = util.TraceLine({ + start = self:GetPos(), + endpos = tgtpos, + filter = self + }) + + TacRP.CancelBodyDamage(tgt, dmg, atktr.HitGroup) + tgt:DispatchTraceAttack(dmg, atktr) + else + -- leave a bullet hole. Also may be able to hit things it can't collide with (like stuck C4) + local ang = data.OurOldVelocity:Angle() + self:FireBullets({ + Attacker = attacker, + Damage = self.Damage, + Force = 1, + Distance = 4, + HullSize = 4, + Tracer = 0, + Dir = ang:Forward(), + Src = data.HitPos - ang:Forward(), + IgnoreEntity = self, + Callback = function(atk, tr, dmginfo) + dmginfo:SetInflictor(IsValid(self.Inflictor) and self.Inflictor or self) + if tr.HitSky then + SafeRemoveEntity(self) + else + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + if SERVER then + self:EmitSound(istable(self.Sound_MeleeHit) and self.Sound_MeleeHit[math.random(1, #self.Sound_MeleeHit)] or self.Sound_MeleeHit, 80, 110, 1) + end + end + end + }) + end + + -- self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + + if self.DamageType == DMG_SLASH and (tgt:IsWorld() or (IsValid(tgt) and tgt:GetPhysicsObject():IsValid())) then + local angles = data.OurOldVelocity:Angle() + angles:RotateAroundAxis(self:GetRight(), -90) + self:GetPhysicsObject():Sleep() + + timer.Simple(0.01, function() + if IsValid(self) and tgt:IsWorld() or (IsValid(tgt) and (!(tgt:IsNPC() or tgt:IsPlayer()) or tgt:Health() > 0)) then + if !tgt:IsWorld() then + self:SetSolid(SOLID_NONE) + end + self:SetMoveType(MOVETYPE_NONE) + + local f = {self, self:GetOwner()} + table.Add(f, tgt:GetChildren()) + local tr = util.TraceLine({ + start = data.HitPos - data.OurOldVelocity, + endpos = data.HitPos + data.OurOldVelocity, + filter = f, + mask = MASK_SOLID, + ignoreworld = true, + }) + + local bone = (tr.Entity == tgt) and tr.PhysicsBone == 0 + and tr.Entity:GetHitBoxBone(tr.HitBox, tr.Entity:GetHitboxSet()) + or tr.PhysicsBone or -1 + local matrix = tgt:GetBoneMatrix(bone) + if tr.Entity == tgt and matrix then + local bpos = matrix:GetTranslation() + local bang = matrix:GetAngles() + self:SetPos(data.HitPos) + self:FollowBone(tgt, bone) + local n_pos, n_ang = WorldToLocal(tr.HitPos + tr.HitNormal * self:GetModelRadius() * 0.5, angles, bpos, bang) + self:SetLocalPos(n_pos) + self:SetLocalAngles(n_ang) + debugoverlay.Cross(pos, 8, 5, Color(255, 0, 255), true) + else + self:SetAngles(angles) + self:SetPos(data.HitPos - data.OurOldVelocity:GetNormalized() * self:GetModelRadius() * 0.5) + if !tgt:IsWorld() then + self:SetParent(tgt) + end + end + elseif IsValid(self) then + self:GetPhysicsObject():SetVelocityInstantaneous(data.OurNewVelocity * 0.5) + self:GetPhysicsObject():SetAngleVelocityInstantaneous(data.OurOldAngularVelocity * 0.5) + end + end) + end + + timer.Simple(0.01, function() + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + if self.HasAmmo then + self:SetTrigger(true) + self:SetOwner(NULL) + end + end) + + timer.Simple(5, function() + if IsValid(self) then + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetRenderFX(kRenderFxFadeFast) + end + end) + SafeRemoveEntityDelayed(self, 7) + + return true +end + +function ENT:Touch(ent) + if self.HasAmmo and ent:IsPlayer() and (IsValid(self.Inflictor) and ent == self.Inflictor:GetOwner()) then + self.HasAmmo = false + ent:GiveAmmo(1, "xbowbolt") + self:Remove() + end +end + +hook.Add("PostEntityTakeDamage", "TacRP_KnifeProj", function(ent) + if (ent:IsPlayer() or ent:IsNPC()) and ent:Health() < 0 then + for _, proj in pairs(ent:GetChildren()) do + if proj:GetClass() == "tacrp_proj_knife" then proj:Remove() end + end + end +end) diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_ks23_flashbang.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_ks23_flashbang.lua new file mode 100644 index 0000000..c8302a8 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_ks23_flashbang.lua @@ -0,0 +1,49 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "KS-23 Flashbang" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true +ENT.ExplodeOnImpact = true + +ENT.Delay = 0.2 + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/flashbang_explode-1.wav", +} + +ENT.AudioLoop = "" + +ENT.SmokeTrail = false + +function ENT:Detonate() + // util.BlastDamage(self, self:GetOwner(), self:GetPos(), 150, 25) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + self:Remove() + return + else + fx:SetRadius(328) + util.Effect("TacRP_flashexplosion", fx) + end + + TacRP.Flashbang(self, self:GetPos(), 328, 1, 0.1, 0.3) + + self:EmitSound(table.Random(self.ExplodeSounds), 125) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202.lua new file mode 100644 index 0000000..0868654 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202.lua @@ -0,0 +1,85 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "M202 Rocket" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.SafetyFuse = 0.05 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 200, 150) + +ENT.Radius = 400 + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + local dmg = DamageInfo() + dmg:SetDamagePosition(self:GetPos()) + dmg:SetInflictor(self) + dmg:SetAttacker(attacker) + + // Apply a small instance of damage to ignite first, before doing the real damage + // This will ensure if the target dies it is on fire first (so it can ignite its ragdolls etc.) + dmg:SetDamageType(DMG_SLOWBURN) + dmg:SetDamage(5) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + dmg:SetDamageType(DMG_BLAST + DMG_BURN) + dmg:SetDamage(250 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + self:ImpactTraceAttack(ent, 100 * mult, 100) + + local decaltr = util.TraceLine({ + start = self:GetPos(), + endpos = self:GetPos() + self:GetForward() * 128, + filter = self, + mask = MASK_SOLID, + }) + util.Decal("Scorch", decaltr.StartPos, decaltr.HitPos - (decaltr.HitNormal * 16), self) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("tacrp_m202_explode", fx) + self:EmitSound("^weapons/explode5.wav", 100, 110) + self:EmitSound("^ambient/fire/ignite.wav", 110, 110) + end + + self:Remove() +end + +local burn = { + tacrp_proj_m202 = 12, + tacrp_proj_p2a1_flare = 4, + tacrp_proj_p2a1_paraflare = 4, + tacrp_proj_p2a1_incendiary = 8, +} + +hook.Add("PostEntityTakeDamage", "tacrp_pa_m202", function(ent, dmginfo, took) + local infl = dmginfo:GetInflictor() + if took and IsValid(infl) and burn[infl:GetClass()] and dmginfo:IsDamageType(DMG_SLOWBURN) then + local fr = Lerp(1 - (ent:GetPos():Distance(infl:GetPos())) / infl.Radius, 0.25, 1) + ent:Ignite(fr * burn[infl:GetClass()] * (ent:IsPlayer() and 1 or 2.5)) + end +end) diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_apers.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_apers.lua new file mode 100644 index 0000000..e77dfde --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_apers.lua @@ -0,0 +1,134 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "M202 Rocket (APERS)" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.SafetyFuse = 0.05 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(200, 200, 255) + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + + +DEFINE_BASECLASS(ENT.Base) + +function ENT:PhysicsCollide(data, collider) + if self:Impact(data, collider) then + return + end + + BaseClass.PhysicsCollide(self, data, collider) +end + +function ENT:Think() + if self.SpawnTime + self.SafetyFuse < CurTime() and (self.NextTraceTime or 0) < CurTime() then + self.NextTraceTime = CurTime() + 0.1 + + local dir = self:GetVelocity():GetNormalized() + local deg = 1 //math.Clamp(1.5 - dir:Cross(Vector(0, 0, -1)):Length(), 0.5, 1) + + local tr = util.TraceHull({ + start = self:GetPos(), + endpos = self:GetPos() + dir * (1024 * deg), + filter = self, + mins = Vector(-32, -32, -32), + maxs = Vector(32, 32, 32) + }) + if tr.Hit then + self:PreDetonate() + end + end + + BaseClass.Think(self) +end + + +function ENT:Detonate() + local dir = self:GetForward() + local attacker = self.Attacker or self:GetOwner() or self + local src = self:GetPos() - dir * 64 + local fx = EffectData() + fx:SetOrigin(src) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + fx:SetMagnitude(8) + fx:SetScale(2) + fx:SetRadius(8) + fx:SetNormal(dir) + util.Effect("Sparks", fx) + + local tr = util.TraceHull({ + start = src, + endpos = src + dir * 1024, + filter = self, + mins = Vector(-16, -16, -8), + maxs = Vector(16, 16, 8) + }) + fx:SetMagnitude(4) + fx:SetScale(1) + fx:SetRadius(2) + fx:SetNormal(dir) + for i = 1, math.floor(tr.Fraction * 6) do + fx:SetOrigin(tr.StartPos + tr.Normal * (i / 6) * 1024) + util.Effect("Sparks", fx) + end + end + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + + self:FireBullets({ + Attacker = attacker, + Damage = 5, + Force = 1, + Distance = 1024, + HullSize = 16, + Num = 48, + Tracer = 1, + Src = src, + Dir = dir, + Spread = Vector(1, 1, 0), + IgnoreEntity = self, + }) + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetDamageType(DMG_BULLET + DMG_BLAST) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:GetVelocity() * 100) + dmg:SetDamagePosition(src) + for _, ent in pairs(ents.FindInCone(src, dir, 1024, 0.707)) do + local tr = util.QuickTrace(src, ent:GetPos() - src, {self, ent}) + if tr.Fraction == 1 then + dmg:SetDamage(mult * 60 * math.Rand(0.75, 1) * Lerp((ent:GetPos():DistToSqr(src) / 1048576) ^ 0.5, 1, 0.25) * (self.NPCDamage and 0.5 or 1) * mult) + if !ent:IsOnGround() then dmg:ScaleDamage(1.5) end + ent:TakeDamageInfo(dmg) + end + end + + util.BlastDamage(self, attacker, src, 256, 50 * mult) + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125, 100) + self:EmitSound("physics/metal/metal_box_break1.wav", 100, 200) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_harpoon.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_harpoon.lua new file mode 100644 index 0000000..a544e38 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_harpoon.lua @@ -0,0 +1,205 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "M202 Harpoon" +ENT.Spawnable = false + +ENT.Model = "models/props_junk/harpoon002a.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.SmokeTrail = false + +local path = "tacrp/weapons/knife/" +ENT.Sound_MeleeHit = { + path .. "/scrape_metal-1.wav", + path .. "/scrape_metal-2.wav", + path .. "/scrape_metal-3.wav", +} +ENT.Sound_MeleeHitBody = { + path .. "/flesh_hit-1.wav", + path .. "/flesh_hit-2.wav", + path .. "/flesh_hit-3.wav", + path .. "/flesh_hit-4.wav", + path .. "/flesh_hit-5.wav", +} + +function ENT:OnInitialize() + if SERVER then + self:GetPhysicsObject():SetMass(25) + self:GetPhysicsObject():SetDragCoefficient(10) + self:Ignite(30) + end +end + +function ENT:Impact(data, collider) + local tgt = data.HitEntity + local attacker = self.Attacker or self:GetOwner() or self + local d = data.OurOldVelocity:GetNormalized() + local forward = data.OurOldVelocity:Dot(self:GetAngles():Forward()) + if forward <= 100 then return true end + if IsValid(tgt) then + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + + if IsValid(data.HitEntity) then + data.HitEntity:Ignite(math.Rand(5, 10), 128) + local dmginfo = DamageInfo() + dmginfo:SetAttacker(attacker) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CLUB) + dmginfo:SetDamage(self.NPCDamage and 25 or math.Clamp(forward / 70, 10, 200)) + dmginfo:SetDamageForce(data.OurOldVelocity) + dmginfo:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmginfo) + end + else + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + if SERVER then + self:EmitSound(istable(self.Sound_MeleeHit) and self.Sound_MeleeHit[math.random(1, #self.Sound_MeleeHit)] or self.Sound_MeleeHit, 80, 110, 1) + end + + -- leave a bullet hole. Also may be able to hit things it can't collide with (like stuck C4) + self:FireBullets({ + Attacker = attacker, + Damage = self.Damage, + Force = 1, + Distance = 4, + HullSize = 4, + Tracer = 0, + Dir = ang:Forward(), + Src = data.HitPos - ang:Forward(), + IgnoreEntity = self, + Callback = function(atk, tr, dmginfo) + dmginfo:SetDamageType(DMG_SLASH) + dmginfo:SetInflictor(attacker) + end + }) + end + + if tgt:IsWorld() or (IsValid(tgt) and tgt:GetPhysicsObject():IsValid()) then + local angles = data.OurOldVelocity:Angle() + self:GetPhysicsObject():Sleep() + + timer.Simple(0, function() + if tgt:IsWorld() or (IsValid(tgt) and (!(tgt:IsNPC() or tgt:IsPlayer()) or tgt:Health() > 0)) then + self:SetSolid(SOLID_NONE) + self:SetMoveType(MOVETYPE_NONE) + + local f = {self, self:GetOwner()} + table.Add(f, tgt:GetChildren()) + local tr = util.TraceLine({ + start = data.HitPos - data.OurOldVelocity, + endpos = data.HitPos + data.OurOldVelocity, + filter = f, + mask = MASK_SOLID, + ignoreworld = true, + }) + + local bone = (tr.Entity == tgt) and tr.PhysicsBone == 0 + and tr.Entity:GetHitBoxBone(tr.HitBox, tr.Entity:GetHitboxSet()) + or tr.PhysicsBone or -1 + local matrix = tgt:GetBoneMatrix(bone) + if tr.Entity == tgt and matrix then + local bpos = matrix:GetTranslation() + local bang = matrix:GetAngles() + self:SetPos(data.HitPos) + self:FollowBone(tgt, bone) + local n_pos, n_ang = WorldToLocal(tr.HitPos + tr.HitNormal * self:GetModelRadius() * 0.5, angles, bpos, bang) + self:SetLocalPos(n_pos) + self:SetLocalAngles(n_ang) + debugoverlay.Cross(pos, 8, 5, Color(255, 0, 255), true) + else + self:SetAngles(angles) + self:SetPos(data.HitPos - data.OurOldVelocity:GetNormalized() * self:GetModelRadius() * 0.5) + if !tgt:IsWorld() then + self:SetParent(tgt) + end + end + else + self:GetPhysicsObject():SetVelocity(data.OurOldVelocity * 0.75) + self:GetPhysicsObject():SetAngleVelocity(data.OurOldAngularVelocity) + self.Armed = false + end + end) + end + timer.Simple(5, function() + if IsValid(self) then + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetRenderFX(kRenderFxFadeFast) + end + end) + SafeRemoveEntityDelayed(self, 7) + + return true +end + +local g = Vector(0, 0, -9.81) +function ENT:PhysicsUpdate(phys) + if !self.Armed and phys:IsGravityEnabled() and self:WaterLevel() <= 2 then + local v = phys:GetVelocity() + self:SetAngles(v:Angle()) + phys:SetVelocityInstantaneous(v * 0.985 + g) + end + + -- local v = phys:GetVelocity() + -- self:SetAngles(v:Angle() + Angle(2, 0, 0)) + -- phys:SetVelocityInstantaneous(self:GetForward() * v:Length()) +end + +ENT.SmokeTrail = true +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and self:GetVelocity():Length() >= 100 then + local pos = self:GetPos() + self:GetUp() * 4 + local emitter = ParticleEmitter(pos) + local smoke = emitter:Add(GetSmokeImage(), pos) + + smoke:SetStartAlpha(100) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(2) + smoke:SetEndSize(math.Rand(16, 24)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetVelocity(VectorRand() * 8 + self:GetUp() * 16) + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.5, 0.75)) + + smoke:SetGravity(Vector(0, 0, 15)) + + local fire = emitter:Add("effects/fire_cloud" .. math.random(1, 2), pos) + fire:SetStartAlpha(150) + fire:SetEndAlpha(0) + fire:SetStartSize(math.Rand(2, 4)) + fire:SetEndSize(math.Rand(8, 16)) + fire:SetRoll(math.Rand(-180, 180)) + fire:SetRollDelta(math.Rand(-1, 1)) + fire:SetVelocity(VectorRand() * 16 + self:GetUp() * 16) + fire:SetLighting(false) + fire:SetDieTime(math.Rand(0.1, 0.3)) + fire:SetGravity(Vector(0, 0, 50)) + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_he.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_he.lua new file mode 100644 index 0000000..e23596c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_he.lua @@ -0,0 +1,47 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "M202 Rocket (HE)" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.SafetyFuse = 0.05 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 255, 200) + + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + util.BlastDamage(self, attacker, self:GetPos(), 350, 150 * mult) + self:ImpactTraceAttack(ent, 800 * mult, 13000) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_smoke.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_smoke.lua new file mode 100644 index 0000000..98ee444 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_m202_smoke.lua @@ -0,0 +1,44 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "M202 Rocket (Smoke)" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.SafetyFuse = 0.05 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 255, 255) + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_smoke_cloud" ) + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:Spawn() + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_c4.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_c4.lua new file mode 100644 index 0000000..5085535 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_c4.lua @@ -0,0 +1,91 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "C4" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/c4_charge-1.mdl" + +ENT.Sticky = true + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = true // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.Defusable = true +ENT.DefuseOnDamage = true + +ENT.ImpactDamage = 0 + +ENT.Delay = 0.5 + +ENT.PickupAmmo = "ti_c4" + +ENT.ExplodeSounds = { + "TacRP/weapons/breaching_charge-1.wav" +} + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() or self + + local dmg = TacRP.ConVars["c4_damage"]:GetFloat() + local rad = TacRP.ConVars["c4_radius"]:GetFloat() + local p = self:GetPos() + self:GetForward() * 8 + + util.BlastDamage(self, attacker, p, rad / 2, dmg) + util.BlastDamage(self, attacker, p, rad, dmg) + + local fx = EffectData() + fx:SetOrigin(p) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + + if rad >= 400 then + local count = 8 + for i = 1, count do + local tr = util.TraceLine({ + start = p, + endpos = p + Angle(0, i / count * 360, 0):Forward() * (rad - 200) * math.Rand(0.9, 1.1), + mask = MASK_SHOT, + filter = self, + }) + fx:SetOrigin(tr.HitPos) + util.Effect("HelicopterMegaBomb", fx) + end + end + end + + self:EmitSound(table.Random(self.ExplodeSounds), 125) + + self:SetParent(NULL) + for _, door in pairs(ents.FindInSphere(self:GetPos(), 256)) do + if IsValid(door) and string.find(door:GetClass(), "door") and !door.TacRP_DoorBusted then + local vel = (door:GetPos() - self:GetPos()):GetNormalized() * 200000 + for _, otherDoor in pairs(ents.FindInSphere(door:GetPos(), 72)) do + if door != otherDoor and otherDoor:GetClass() == door:GetClass() then + TacRP.DoorBust(otherDoor, vel, attacker) + break + end + end + TacRP.DoorBust(door, vel, attacker) + end + end + + self:Remove() +end + +function ENT:Stuck() + // you are already dead + if IsValid(self:GetParent()) and self:GetParent():IsPlayer() and !IsValid(self:GetParent().nadescream) then + self:GetParent().nadescream = self + self:GetParent():EmitSound("vo/npc/male01/ohno.wav") + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_charge.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_charge.lua new file mode 100644 index 0000000..12be9bc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_charge.lua @@ -0,0 +1,149 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "C4" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/door_charge-1.mdl" + +ENT.Sticky = true + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. +ENT.StickyFuse = true + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.Defusable = false +ENT.DefuseOnDamage = true + +ENT.ImpactDamage = 0 + +ENT.Delay = 2 + +ENT.PickupAmmo = "ti_charge" + +ENT.ExplodeSounds = { + "TacRP/weapons/breaching_charge-1.wav" +} + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "Weapon") + self:NetworkVar("Bool", 0, "Remote") + self:NetworkVar("Float", 0, "ArmTime") +end + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Initialize() + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() and IsValid(self:GetOwner():GetActiveWeapon()) and self:GetOwner():GetActiveWeapon():GetClass() == "tacrp_c4_detonator" then + self.StickyFuse = false + self.RemoteFuse = true + self.Delay = 0.5 + self.Defusable = true + self:SetRemote(true) + end + + BaseClass.Initialize(self) +end + +function ENT:Detonate() + local attacker = IsValid(self.Attacker) and self.Attacker or self:GetOwner() + + util.BlastDamage(self, attacker, self:GetPos(), TacRP.ConVars["charge_radius"]:GetFloat(), TacRP.ConVars["charge_damage"]:GetFloat()) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + fx:SetNormal(self:GetForward()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("HelicopterMegaBomb", fx) + end + + self:EmitSound(table.Random(self.ExplodeSounds), 110) + + local door = self:GetParent() + self:SetParent(NULL) + + if IsValid(door) and string.find(door:GetClass(), "door") then + local vel = self:GetForward() * -50000 + for _, otherDoor in pairs(ents.FindInSphere(door:GetPos(), 72)) do + if door != otherDoor and otherDoor:GetClass() == door:GetClass() then + TacRP.DoorBust(otherDoor, vel, attacker) + break + end + end + TacRP.DoorBust(door, vel, attacker) + end + + self:Remove() +end + +function ENT:Stuck() + + sound.EmitHint(SOUND_DANGER, self:GetPos(), 256, 2, self) + + self:SetArmTime(CurTime()) + if !self:GetRemote() then + local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT + self:EmitSound("weapons/c4/c4_beep1.wav", ttt and 60 or 80, 110) + timer.Create("breachbeep_" .. self:EntIndex(), 0.25, 7, function() + if !IsValid(self) then return end + self:EmitSound("weapons/c4/c4_beep1.wav", ttt and 60 or 80, 110) + end) + end + + // you are already dead + if IsValid(self:GetParent()) and self:GetParent():IsPlayer() and !IsValid(self:GetParent().nadescream) then + self:GetParent().nadescream = self + if self:GetRemote() then + self:GetParent():EmitSound("vo/npc/male01/ohno.wav") + else + self:GetParent():EmitSound("vo/npc/male01/no0" .. math.random(1, 2) .. ".wav") + end + end + + if VJ then + self.Zombies = {} + for _, x in ipairs(ents.FindInSphere(self:GetPos(), 512)) do + if x:IsNPC() and string.find(x:GetClass(),"npc_vj_l4d_com_") and x.Zombie_CanHearPipe == true and x.Zombie_NextPipBombT < CurTime() then + x.Zombie_NextPipBombT = CurTime() + 3 + table.insert(x.VJ_AddCertainEntityAsEnemy,self) + x:AddEntityRelationship(self, D_HT, 99) + x.MyEnemy = self + x:SetEnemy(self) + table.insert(self.Zombies, x) + end + end + end +end + +function ENT:OnThink() + if VJ and self.Zombies then + for _, v in ipairs(self.Zombies) do + if IsValid(v) then + v:SetLastPosition(self:GetPos()) + v:VJ_TASK_GOTO_LASTPOS() + end + end + end +end + +local clr_timed = Color(255, 0, 0) +local clr_remote = Color(0, 255, 0) + +local mat = Material("sprites/light_glow02_add") +function ENT:Draw() + self:DrawModel() + + if (self:GetRemote() or self:GetArmTime() > 0) and math.ceil((CurTime() - self:GetArmTime()) * (self:GetRemote() and 2 or 8)) % 2 == 1 then + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + self:GetAngles():Up() * 7.5 + self:GetAngles():Right() * -4.5 + self:GetAngles():Forward() * 2, 8, 8, self:GetRemote() and clr_remote or clr_timed) + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_flashbang.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_flashbang.lua new file mode 100644 index 0000000..9fb71a2 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_flashbang.lua @@ -0,0 +1,94 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Flash Grenade" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/flashbang.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.ImpactDamage = 1 + +ENT.Delay = 1.5 + +ENT.SoundHint = true +ENT.SoundHintDelay = 0.5 +ENT.SoundHintRadius = 728 +ENT.SoundHintDuration = 1 + +ENT.BounceSounds = { + "TacRP/weapons/grenade/flashbang_bounce-1.wav", + "TacRP/weapons/grenade/flashbang_bounce-2.wav", + "TacRP/weapons/grenade/flashbang_bounce-3.wav", +} + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/flashbang_explode-1.wav", +} + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() or self + + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(self) + dmg:SetDamageType(engine.ActiveGamemode() == "terrortown" and DMG_DIRECT or DMG_SONIC) + dmg:SetDamagePosition(self:GetPos()) + dmg:SetDamage(3) + util.BlastDamageInfo(dmg, self:GetPos(), 728) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + self:Remove() + return + else + fx:SetRadius(728) + util.Effect("TacRP_flashexplosion", fx) + end + + TacRP.Flashbang(self, self:GetPos(), 728, 3, 0.25, 0.5) + + self:EmitSound(table.Random(self.ExplodeSounds), 125) + + self:Remove() +end + +ENT.SmokeTrail = true +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and (self.Tick or 0) % 5 == 0 then + local pos = self:GetPos() + VectorRand() * 2 + local emitter = ParticleEmitter(pos) + + local smoke = emitter:Add("effects/spark", pos) + + smoke:SetVelocity( VectorRand() * 32 ) + smoke:SetStartAlpha( 255 ) + smoke:SetEndAlpha( 0 ) + smoke:SetStartSize( 2 ) + smoke:SetEndSize( 0 ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-32, 32) ) + smoke:SetColor( 255, 255, 255 ) + smoke:SetAirResistance( 125 ) + smoke:SetPos( pos ) + smoke:SetCollide( true ) + smoke:SetBounce( 1 ) + smoke:SetLighting( false ) + smoke:SetDieTime(math.Rand(2, 3)) + smoke:SetGravity(Vector(0, 0, 4)) + + emitter:Finish() + end + self.Tick = (self.Tick or 0) + 1 +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_frag.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_frag.lua new file mode 100644 index 0000000..5f7a4a3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_frag.lua @@ -0,0 +1,59 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Frag Grenade" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/frag.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.SoundHint = true +ENT.SoundHintDelay = 1 +ENT.SoundHintRadius = 512 +ENT.SoundHintDuration = 1 + +ENT.ImpactDamage = 1 + +ENT.Delay = 2 + +ENT.BounceSounds = { + "TacRP/weapons/grenade/frag_bounce-1.wav", + "TacRP/weapons/grenade/frag_bounce-2.wav", + "TacRP/weapons/grenade/frag_bounce-3.wav", +} + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/frag_explode-1.wav", + "^TacRP/weapons/grenade/frag_explode-2.wav", + "^TacRP/weapons/grenade/frag_explode-3.wav", +} + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() or self + + local dmg = TacRP.ConVars["frag_damage"]:GetFloat() + if self.ImpactFuse then dmg = dmg * 0.5 end + + util.BlastDamage(self, attacker, self:GetPos(), TacRP.ConVars["frag_radius"]:GetFloat(), dmg) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound(table.Random(self.ExplodeSounds), 125) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_gas.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_gas.lua new file mode 100644 index 0000000..3c87c1d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_gas.lua @@ -0,0 +1,90 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Gas Grenade" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.Material = "models/tacint/weapons/w_models/smoke/gas-1" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.SoundHint = true +ENT.SoundHintDelay = 1 +ENT.SoundHintRadius = 512 +ENT.SoundHintDuration = 1 + +ENT.ImpactDamage = 1 + +ENT.Delay = 2 + +ENT.BounceSounds = { + "TacRP/weapons/grenade/smoke_bounce-1.wav", + "TacRP/weapons/grenade/smoke_bounce-2.wav", + "TacRP/weapons/grenade/smoke_bounce-3.wav", + "TacRP/weapons/grenade/smoke_bounce-4.wav", +} + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + local attacker = self.Attacker or self:GetOwner() or self + + // util.BlastDamage(self, attacker, self:GetPos(), 300, 10) + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_gas_cloud" ) + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetOwner(attacker) + cloud:Spawn() + + self:Remove() +end + + +ENT.SmokeTrail = true +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail then + local pos = self:GetPos() + self:GetUp() * 4 + local emitter = ParticleEmitter(pos) + local smoke = emitter:Add(GetSmokeImage(), pos) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(2) + smoke:SetEndSize(math.Rand(16, 24)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetVelocity(VectorRand() * 8 + self:GetUp() * 16) + smoke:SetColor(125, 150, 50) + smoke:SetLighting(false) + + smoke:SetDieTime(math.Rand(0.5, 0.75)) + + smoke:SetGravity(Vector(0, 0, 15)) + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_heal.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_heal.lua new file mode 100644 index 0000000..53e3271 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_heal.lua @@ -0,0 +1,88 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Medi-Gas Canister" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.Material = "models/tacint/weapons/w_models/smoke/heal-1" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.ImpactDamage = 1 + +ENT.Delay = 5 + +ENT.BounceSounds = { + "TacRP/weapons/grenade/smoke_bounce-1.wav", + "TacRP/weapons/grenade/smoke_bounce-2.wav", + "TacRP/weapons/grenade/smoke_bounce-3.wav", + "TacRP/weapons/grenade/smoke_bounce-4.wav", +} + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + local attacker = self.Attacker or self:GetOwner() or self + + // util.BlastDamage(self, attacker, self:GetPos(), 300, 10) + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_heal_cloud" ) + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetOwner(attacker) + cloud:Spawn() + + self:Remove() +end + +ENT.SmokeTrail = true +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end +ENT.NextSmokeTime = 0 +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and self.NextSmokeTime < CurTime() then + local pos = self:GetPos() + self:GetUp() * 4 + local emitter = ParticleEmitter(pos) + local smoke = emitter:Add(GetSmokeImage(), pos) + + smoke:SetStartAlpha(75) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(16, 32)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetVelocity(VectorRand() * 8 + self:GetUp() * 42) + + smoke:SetColor(125, 25, 125) + smoke:SetLighting(false) + + smoke:SetDieTime(math.Rand(1, 1.5)) + + smoke:SetGravity(Vector(0, 0, 15)) + + emitter:Finish() + + self.NextSmokeTime = CurTime() + 0.025 + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_nuke.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_nuke.lua new file mode 100644 index 0000000..bbc8c09 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_nuke.lua @@ -0,0 +1,55 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Nuke" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/props_misc/briefcase_bomb-1.mdl" + +ENT.Sticky = false + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = true // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = true // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.Defusable = true +ENT.PickupAmmo = "ti_nuke" + +ENT.Delay = 0.5 + +ENT.ExplodeSounds = { + "ambient/explosions/explode_6.wav" +} + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() or self + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + self:Remove() + return + else + util.Effect("TacRP_nukeexplosion", fx) + end + + util.BlastDamage(self, attacker, self:GetPos(), 1024, 100000) + + local cloud = ents.Create( "TacRP_nuke_cloud" ) + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetOwner(attacker) + cloud:Spawn() + + self:EmitSound(table.Random(self.ExplodeSounds), 149) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_rock.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_rock.lua new file mode 100644 index 0000000..b932e98 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_rock.lua @@ -0,0 +1,324 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Rock" +ENT.Spawnable = false + +ENT.Model = "models/props_debris/concrete_chunk05g.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.Delay = 0 + +ENT.AllowFunny = true + +ENT.ExtraModels = { + "models/props_junk/PopCan01a.mdl", + "models/Gibs/HGIBS.mdl", + "models/props_junk/garbage_glassbottle003a.mdl", + "models/props_junk/garbage_glassbottle001a.mdl", + "models/props_junk/garbage_glassbottle002a.mdl", + "models/props_junk/garbage_metalcan001a.mdl", + "models/props_junk/garbage_metalcan002a.mdl", + "models/props_junk/GlassBottle01a.mdl", + "models/props_junk/garbage_coffeemug001a.mdl", + "models/props_junk/glassjug01.mdl", + "models/props_lab/jar01b.mdl", + "models/props_junk/watermelon01.mdl", + "models/props_junk/CinderBlock01a.mdl", + "models/props_junk/MetalBucket01a.mdl", + "models/props_junk/metal_paintcan001a.mdl", + "models/Gibs/Antlion_gib_Large_2.mdl", + "models/props_junk/garbage_plasticbottle003a.mdl", + "models/props_junk/garbage_plasticbottle002a.mdl", + "models/props_junk/garbage_plasticbottle001a.mdl", + "models/props_junk/terracotta01.mdl", + "models/props_junk/Shoe001a.mdl", + "models/props_lab/frame002a.mdl", + "models/props_lab/reciever01c.mdl", + "models/props_lab/box01a.mdl", + "models/props_junk/garbage_milkcarton001a.mdl", + "models/props_lab/cactus.mdl", + "models/props_lab/desklamp01.mdl", + "models/Gibs/HGIBS_spine.mdl", + "models/Gibs/wood_gib01a.mdl", + "models/Gibs/wood_gib01b.mdl", + "models/Gibs/wood_gib01c.mdl", + "models/Gibs/wood_gib01d.mdl", + "models/Gibs/wood_gib01e.mdl", + "models/props_c17/oildrum001_explosive.mdl", // won't actually explode :trolleg: + "models/props_interiors/SinkKitchen01a.mdl", + "models/props_borealis/door_wheel001a.mdl", + "models/hunter/blocks/cube025x025x025.mdl", + "models/food/burger.mdl", + "models/food/hotdog.mdl", + "models/lamps/torch.mdl", + "models/player/items/humans/top_hat.mdl", + "models/dav0r/camera.mdl", + "models/dav0r/tnt/tnt.mdl", + "models/maxofs2d/camera.mdl", + "models/maxofs2d/companion_doll.mdl", + "models/maxofs2d/cube_tool.mdl", + "models/maxofs2d/light_tubular.mdl", + "models/maxofs2d/lamp_flashlight.mdl", + "models/mechanics/gears/gear12x12.mdl", + "models/mechanics/various/211.mdl", + "models/props_phx/games/chess/white_rook.mdl", + "models/props_phx/games/chess/white_queen.mdl", + "models/props_phx/games/chess/white_pawn.mdl", + "models/props_phx/games/chess/white_knight.mdl", + "models/props_phx/games/chess/white_king.mdl", + "models/props_phx/games/chess/white_dama.mdl", + "models/props_phx/games/chess/black_rook.mdl", + "models/props_phx/games/chess/black_queen.mdl", + "models/props_phx/games/chess/black_pawn.mdl", + "models/props_phx/games/chess/black_knight.mdl", + "models/props_phx/games/chess/black_king.mdl", + "models/props_phx/games/chess/black_dama.mdl", + "models/props_phx/games/chess/black_bishop.mdl", + "models/props_phx/misc/egg.mdl", + "models/props_phx/misc/potato.mdl", + "models/props_phx/misc/potato_launcher_explosive.mdl", + "models/props_phx/misc/smallcannonball.mdl", + "models/props_phx/misc/soccerball.mdl", + "models/props_phx/misc/fender.mdl", + "models/props_combine/breenbust.mdl", + "models/props_combine/breenglobe.mdl", + "models/props_combine/combinebutton.mdl", + "models/props_combine/breenclock.mdl", + "models/props_interiors/pot01a.mdl", + "models/props_junk/garbage_bag001a.mdl", + "models/props_lab/harddrive02.mdl", + "models/props_lab/monitor02.mdl", // HAAAAAAX + "models/props_lab/clipboard.mdl", + "models/props_lab/tpplug.mdl", + "models/props_pipes/valvewheel002a.mdl", + "models/props_pipes/valve003.mdl", + "models/props_rooftop/sign_letter_m001.mdl", + "models/props_rooftop/sign_letter_f001b.mdl", + "models/props_rooftop/sign_letter_u001b.mdl", + "models/props_wasteland/speakercluster01a.mdl", + "models/props_wasteland/prison_toilet01.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/playgroundTick-tack-toe_block01a.mdl", + "models/props_c17/doll01.mdl", + "models/props_c17/BriefCase001a.mdl", + "models/props_c17/metalPot001a.mdl", + "models/props_c17/metalPot002a.mdl", + "models/props_canal/mattpipe.mdl", + "models/extras/info_speech.mdl", + "models/items/grenadeammo.mdl", // not a live one + "models/props_c17/light_cagelight02_on.mdl", + "models/props_c17/suitcase_passenger_physics.mdl", + "models/props_citizen_tech/guillotine001a_wheel01.mdl", + "models/props_interiors/bathtub01a.mdl", // now this is just absurd + "models/props_junk/garbage_takeoutcarton001a.mdl", + "models/props_junk/garbage_newspaper001a.mdl", + "models/props_junk/sawblade001a.mdl", + "models/props_lab/bewaredog.mdl", + "models/props_lab/huladoll.mdl", + "models/props_lab/powerbox02d.mdl", + "models/props_lab/powerbox02b.mdl", + "models/props_lab/powerbox02a.mdl", + "models/weapons/w_bugbait.mdl", + "models/weapons/w_alyx_gun.mdl", + "models/weapons/w_crowbar.mdl", + "models/weapons/w_smg1.mdl", + "models/weapons/w_shotgun.mdl", + "models/weapons/w_rocket_launcher.mdl", + "models/weapons/w_pistol.mdl", + "models/weapons/w_physics.mdl", + "models/weapons/w_irifle.mdl", + "models/weapons/w_357.mdl", + "models/weapons/w_package.mdl", + "models/weapons/w_pist_deagle.mdl", + "models/weapons/w_pist_elite_single.mdl", + "models/weapons/w_pist_glock18.mdl", + "models/weapons/w_rif_ak47.mdl", + "models/weapons/w_rif_m4a1.mdl", + "models/weapons/w_shot_m3super90.mdl", + "models/weapons/w_smg_mp5.mdl", + "models/weapons/w_snip_awp.mdl", + "models/weapons/w_crossbow.mdl", + "models/weapons/w_eq_defuser.mdl", + "models/weapons/w_toolgun.mdl", + "models/weapons/w_missile_closed.mdl", + "models/weapons/w_stunbaton.mdl", + "models/weapons/w_annabelle.mdl", + "models/roller.mdl", + "models/pigeon.mdl", + "models/headcrabclassic.mdl", + "models/headcrab.mdl", + "models/headcrabblack.mdl", + "models/crow.mdl", + "models/seagull.mdl", + "models/kleiner.mdl", + "models/gman.mdl", + "models/manhack.mdl", + + // CSS + "models/props/cs_militia/bottle01.mdl", + "models/props/cs_office/coffee_mug.mdl", + "models/props/cs_office/computer_keyboard.mdl", + "models/props/cs_office/water_bottle.mdl", + "models/props/cs_office/projector_remote.mdl", + "models/props/cs_office/phone.mdl", + "models/props/cs_italy/bananna_bunch.mdl", + "models/props/cs_italy/bananna.mdl", + "models/props/cs_italy/orange.mdl", + "models/props/de_tides/vending_turtle.mdl", +} + +function ENT:Initialize() + if SERVER then + if util.IsValidModel(TacRP.ConVars["rock_funny"]:GetString()) then + self:SetModel(TacRP.ConVars["rock_funny"]:GetString()) + elseif self.AllowFunny and math.random() <= TacRP.ConVars["rock_funny"]:GetFloat() then + local i = math.min(TacRP.ConVars["rock_funny"]:GetFloat() > 1 and TacRP.ConVars["rock_funny"]:GetInt() - 1 or math.random(1, #self.ExtraModels), #self.ExtraModels) + local mdl = self.ExtraModels[i] + self:SetModel(util.IsValidModel(mdl) and mdl or self.Model) + else + self:SetModel(self.Model) + end + + self:SetSkin(math.random(1, self:SkinCount())) + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + if self.Defusable then + self:SetUseType(SIMPLE_USE) + end + self:PhysWake() + + local phys = self:GetPhysicsObject() + if !phys:IsValid() then + self:Remove() + else + phys:SetDragCoefficient(0) + phys:SetMass(2) + end + + if self.IsRocket then + phys:EnableGravity(false) + end + end + + self.SpawnTime = CurTime() + + self.NPCDamage = IsValid(self:GetOwner()) and self:GetOwner():IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() + + if self.AudioLoop then + self.LoopSound = CreateSound(self, self.AudioLoop) + self.LoopSound:Play() + end + + if self.InstantFuse then + self.ArmTime = CurTime() + self.Armed = true + end +end + +function ENT:Impact(data, collider) + return true +end + + +function ENT:PhysicsCollide(data, collider) + local attacker = self.Attacker or self:GetOwner() or self + + if IsValid(data.HitEntity) and data.HitEntity:GetClass() == "func_breakable_surf" then + self:FireBullets({ + Attacker = attacker, + Inflictor = self, + Damage = 0, + Distance = 32, + Tracer = 0, + Src = self:GetPos(), + Dir = data.OurOldVelocity:GetNormalized(), + }) + local pos, ang, vel = self:GetPos(), self:GetAngles(), data.OurOldVelocity + self:SetAngles(ang) + self:SetPos(pos) + self:GetPhysicsObject():SetVelocityInstantaneous(vel * 0.5) + return + end + + local prop1 = util.GetSurfaceData(data.OurSurfaceProps) + local prop2 = util.GetSurfaceData(data.TheirSurfaceProps) + + + if IsValid(data.HitEntity) and (self.LastDamage or 0) + 0.25 < CurTime() then + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetInflictor(self) + dmg:SetDamage(Lerp((data.OurOldVelocity:Length() - 500) / 2500, 4, 35)) + if self:GetModel() != self.Model then + dmg:ScaleDamage(2) + end + dmg:SetDamageType(DMG_CRUSH + DMG_CLUB) + dmg:SetDamageForce(data.OurOldVelocity) + dmg:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmg) + self.LastDamage = CurTime() + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + elseif data.Speed >= 75 and (self.LastImpact or 0) + 0.1 < CurTime() then + self.LastImpact = CurTime() + self:EmitSound(self:GetModel() == self.Model and "physics/concrete/rock_impact_hard" .. math.random(1, 6) .. ".wav" or prop1.impactHardSound) + elseif data.Speed >= 25 and (self.LastImpact or 0) + 0.1 < CurTime() then + self.LastImpact = CurTime() + self:EmitSound(self:GetModel() == self.Model and "physics/concrete/rock_impact_soft" .. math.random(1, 3) .. ".wav" or prop1.impactSoftSound) + end + + if !self.FirstHit then + self.FirstHit = true + self:EmitSound(prop2.bulletImpactSound) + + if self:GetModel() == self.Model then + self:EmitSound("physics/concrete/concrete_break" .. math.random(2, 3) .. ".wav", 75, math.Rand(105, 110), 0.5) + SafeRemoveEntityDelayed(self, 3) + else + self:EmitSound(prop1.impactHardSound) + if util.IsValidRagdoll(self:GetModel()) then + local rag = ents.Create("prop_ragdoll") + rag:SetModel(self:GetModel()) + rag:SetPos(self:GetPos()) + rag:SetAngles(self:GetAngles()) + rag:Spawn() + rag:SetCollisionGroup(COLLISION_GROUP_WEAPON) + if IsValid(rag:GetPhysicsObject()) then + rag:GetPhysicsObject():ApplyForceOffset(data.HitPos, data.OurOldVelocity) + end + + SafeRemoveEntityDelayed(rag, 5) + self:Remove() + else + local gibs = self:PrecacheGibs() + if gibs > 0 then + self:GibBreakClient(data.OurNewVelocity * math.Rand(0.8, 1.2)) + // self:GibBreakServer( data.OurOldVelocity * 2 ) + self:Remove() + else + SafeRemoveEntityDelayed(self, 3) + end + end + end + + + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_smoke.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_smoke.lua new file mode 100644 index 0000000..58aa844 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_smoke.lua @@ -0,0 +1,79 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Smoke Grenade" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = false + +ENT.ImpactDamage = 1 + +ENT.Delay = 2 + +ENT.BounceSounds = { + "TacRP/weapons/grenade/smoke_bounce-1.wav", + "TacRP/weapons/grenade/smoke_bounce-2.wav", + "TacRP/weapons/grenade/smoke_bounce-3.wav", + "TacRP/weapons/grenade/smoke_bounce-4.wav", +} + +ENT.ExplodeSounds = { + "TacRP/weapons/grenade/smoke_explode-1.wav", +} + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + + self:EmitSound(table.Random(self.ExplodeSounds), 75) + + local cloud = ents.Create( "TacRP_smoke_cloud" ) + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:Spawn() + + self:Remove() +end + + +ENT.SmokeTrail = true +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail then + local pos = self:GetPos() + self:GetUp() * 4 + local emitter = ParticleEmitter(pos) + + local smoke = emitter:Add(GetSmokeImage(), pos) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(2) + smoke:SetEndSize(math.Rand(16, 24)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetVelocity(VectorRand() * 8 + self:GetUp() * 16) + smoke:SetColor(200, 200, 200) + smoke:SetLighting(false) + + smoke:SetDieTime(math.Rand(0.25, 0.5)) + + smoke:SetGravity(Vector(0, 0, 25)) + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_thermite.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_thermite.lua new file mode 100644 index 0000000..c1c5c41 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_nade_thermite.lua @@ -0,0 +1,113 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "Thermite Grenade" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/smoke.mdl" + +ENT.Material = "models/tacint/weapons/w_models/smoke/thermite-1" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = false // projectile explodes on impact. + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true +ENT.DefuseOnDamage = true + +ENT.ImpactDamage = 1 + +ENT.Delay = 3 + + +ENT.SoundHint = true +ENT.SoundHintDelay = 1.5 +ENT.SoundHintRadius = 328 +ENT.SoundHintDuration = 1.5 + +ENT.Sticky = true + +ENT.ExplodeSounds = { + "^TacRP/weapons/grenade/frag_explode-1.wav", + "^TacRP/weapons/grenade/frag_explode-2.wav", + "^TacRP/weapons/grenade/frag_explode-3.wav", +} + +function ENT:Detonate() + if self:WaterLevel() > 0 then self:Remove() return end + local attacker = self.Attacker or self:GetOwner() or self + + -- local dmg = 50 + -- if self.ImpactFuse then dmg = dmg * 0.5 end + -- util.BlastDamage(self, attacker, self:GetPos(), 350, dmg) + + self:EmitSound("ambient/fire/gascan_ignite1.wav", 80, 110) + + local cloud = ents.Create( "TacRP_fire_cloud" ) + + if !IsValid(cloud) then return end + + local t = 8 + if self.ImpactFuse then t = t * 0.5 end + + cloud.FireTime = t + cloud:SetPos(self:GetPos()) + cloud:SetAngles(self:GetAngles()) + cloud:SetOwner(attacker) + cloud:Spawn() + if IsValid(self:GetParent()) then + cloud:SetParent(self:GetParent()) + elseif self:GetMoveType() == MOVETYPE_NONE then + cloud:SetMoveType(MOVETYPE_NONE) + end + + self:Remove() +end + +ENT.NextDamageTick = 0 + +ENT.SmokeTrail = true +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail then + local pos = self:GetPos() + self:GetUp() * 4 + local emitter = ParticleEmitter(pos) + + local smoke = emitter:Add("particles/smokey", pos) + + smoke:SetStartAlpha(30) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(2) + smoke:SetEndSize(math.Rand(16, 24)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetVelocity(VectorRand() * 16 + Vector(0, 0, 64)) + smoke:SetColor(200, 200, 200) + smoke:SetLighting(false) + + smoke:SetDieTime(math.Rand(0.5, 1)) + smoke:SetGravity(Vector(0, 0, -100)) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 135, 0) + local col2 = Color(255, 255, 255) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_flare.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_flare.lua new file mode 100644 index 0000000..22ae9b4 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_flare.lua @@ -0,0 +1,232 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "P2A1 Signal Flare" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnImpact = true +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 8 +ENT.SafetyFuse = 0 + +ENT.ImpactDamage = 50 +ENT.ImpactDamageType = DMG_BURN + DMG_SLOWBURN + +ENT.AudioLoop = false + +ENT.Radius = 200 + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 200, 200) +ENT.FlareSizeMin = 16 +ENT.FlareSizeMax = 32 +ENT.Gravity = Vector(0, 0, 9.81 * 0.333333) + +function ENT:Initialize() + if SERVER then + self:SetModel(self.Model) + self:PhysicsInitBox(-Vector(3, 3, 3), Vector(3, 3, 3) ) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + + local phys = self:GetPhysicsObject() + if !phys:IsValid() then + self:Remove() + return + end + + phys:EnableDrag(false) + phys:SetDragCoefficient(0) + phys:SetMass(1) + phys:SetBuoyancyRatio(0) + phys:Wake() + end + + self.SpawnTime = CurTime() + self.NextFlareRedirectTime = 0 + + self.NPCDamage = IsValid(self:GetOwner()) and self:GetOwner():IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() + + if self.AudioLoop then + self.LoopSound = CreateSound(self, self.AudioLoop) + self.LoopSound:Play() + end + + if self.InstantFuse then + self.ArmTime = CurTime() + self.Armed = true + end +end + +function ENT:PhysicsUpdate(phys) + if phys:IsGravityEnabled() and self:WaterLevel() <= 2 then + local v = phys:GetVelocity() + phys:SetVelocityInstantaneous(v + self.Gravity) + end +end + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() or self + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + local dmg = DamageInfo() + dmg:SetDamagePosition(self:GetPos()) + dmg:SetInflictor(self) + dmg:SetAttacker(attacker) + + // Apply a small instance of damage to ignite first, before doing the real damage + // This will ensure if the target dies it is on fire first (so it can ignite its ragdolls etc.) + dmg:SetDamageType(DMG_SLOWBURN) + dmg:SetDamage(5) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(60 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + // TacRP.Flashbang(self, self:GetPos(), 512, 0.5, 0.1, 0) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("tacrp_flare_explode", fx) + self:EmitSound("^ambient/fire/ignite.wav", 80, 112) + end + + local cloud = ents.Create("tacrp_flare_cloud") + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetAngles(self:GetAngles()) + cloud:SetOwner(attacker) + timer.Simple(0, function() + cloud:Spawn() + if IsValid(self:GetParent()) then + cloud:SetParent(self:GetParent()) + elseif self:GetMoveType() == MOVETYPE_NONE then + cloud:SetMoveType(MOVETYPE_NONE) + else + cloud:GetPhysicsObject():SetVelocityInstantaneous(self:GetVelocity() * 0.5) + end + self:Remove() + end) +end + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex() + 1) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 75 + self.Light.b = 60 + self.Light.Brightness = 1 + self.Light.Size = 1024 + self.Light.DieTime = CurTime() + 8 + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + render.SetMaterial(mat) + render.DrawSprite(self:GetPos(), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:OnRemove() + if self.Light then + self.Light.Size = 1200 + self.Light.Brightness = 1.5 + self.Light.DieTime = CurTime() + 1 + self.Light.Decay = 2000 + end + if !self.FireSound then return end + self.FireSound:Stop() +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and !(self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.1) > CurTime()) then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("particles/smokey", self:GetPos()) + + smoke:SetStartAlpha(30) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(30, 40)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 4) + + smoke:SetColor(255, 50, 25) + smoke:SetLighting(false) + smoke:SetDieTime(math.Rand(3.5, 4.5)) + smoke:SetGravity(Vector(0, 0, -7)) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 50, 25) + local col2 = Color(255, 155, 155) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + emitter:Finish() + end +end + + +function ENT:SafetyImpact(data, collider) + local attacker = self.Attacker or self:GetOwner() + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("StunstickImpact", fx) + + if IsValid(data.HitEntity) then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(attacker) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CRUSH + DMG_SLOWBURN) + dmginfo:SetDamage(self.ImpactDamage * (self.NPCDamage and 0.25 or 1)) + dmginfo:SetDamageForce(data.OurOldVelocity * 20) + dmginfo:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmginfo) + end + + self:EmitSound("physics/plastic/plastic_barrel_impact_hard2.wav", 70, 110) +end + +function ENT:UpdateTransmitState() + if TacRP.ConVars["dynamiclight"]:GetBool() then + return TRANSMIT_ALWAYS + end + return TRANSMIT_PVS +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_flare_signal.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_flare_signal.lua new file mode 100644 index 0000000..d2f80fa --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_flare_signal.lua @@ -0,0 +1,232 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "P2A1 Signal Flare" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/grenade_40mm.mdl" + +ENT.IsRocket = false // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnImpact = true +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 10 +ENT.SafetyFuse = 0 + +ENT.ImpactDamage = 50 +ENT.ImpactDamageType = DMG_BURN + DMG_SLOWBURN + +ENT.AudioLoop = false + +ENT.Radius = 200 + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 200, 200) +ENT.FlareSizeMin = 320 +ENT.FlareSizeMax = 640 +ENT.Gravity = Vector(0, 0, 9.81 * 0.333333) + +function ENT:Initialize() + if SERVER then + self:SetModel(self.Model) + self:PhysicsInitBox(-Vector(3, 3, 3), Vector(3, 3, 3) ) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) + + local phys = self:GetPhysicsObject() + if !phys:IsValid() then + self:Remove() + return + end + + phys:EnableDrag(false) + phys:SetDragCoefficient(0) + phys:SetMass(1) + phys:SetBuoyancyRatio(0) + phys:Wake() + end + + self.SpawnTime = CurTime() + self.NextFlareRedirectTime = 0 + + self.NPCDamage = IsValid(self:GetOwner()) and self:GetOwner():IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() + + if self.AudioLoop then + self.LoopSound = CreateSound(self, self.AudioLoop) + self.LoopSound:Play() + end + + if self.InstantFuse then + self.ArmTime = CurTime() + self.Armed = true + end +end + +function ENT:PhysicsUpdate(phys) + if phys:IsGravityEnabled() and self:WaterLevel() <= 2 then + local v = phys:GetVelocity() + phys:SetVelocityInstantaneous(v + self.Gravity) + end +end + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() or self + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + local dmg = DamageInfo() + dmg:SetDamagePosition(self:GetPos()) + dmg:SetInflictor(self) + dmg:SetAttacker(attacker) + + // Apply a small instance of damage to ignite first, before doing the real damage + // This will ensure if the target dies it is on fire first (so it can ignite its ragdolls etc.) + dmg:SetDamageType(DMG_SLOWBURN) + dmg:SetDamage(5) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(60 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + // TacRP.Flashbang(self, self:GetPos(), 512, 0.5, 0.1, 0) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("tacrp_flare_explode", fx) + self:EmitSound("^ambient/fire/ignite.wav", 80, 112) + end + + local cloud = ents.Create("tacrp_flare_cloud_signal") + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetAngles(self:GetAngles()) + cloud:SetOwner(attacker) + timer.Simple(0, function() + cloud:Spawn() + if IsValid(self:GetParent()) then + cloud:SetParent(self:GetParent()) + elseif self:GetMoveType() == MOVETYPE_NONE then + cloud:SetMoveType(MOVETYPE_NONE) + else + cloud:GetPhysicsObject():SetVelocityInstantaneous(self:GetVelocity() * 0.5) + end + self:Remove() + end) +end + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex() + 1) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 75 + self.Light.b = 60 + self.Light.Brightness = 1 + self.Light.Size = 1024 + self.Light.DieTime = CurTime() + 8 + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + render.SetMaterial(mat) + render.DrawSprite(self:GetPos(), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:OnRemove() + if self.Light then + self.Light.Size = 1200 + self.Light.Brightness = 1.5 + self.Light.DieTime = CurTime() + 1 + self.Light.Decay = 2000 + end + if !self.FireSound then return end + self.FireSound:Stop() +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and !(self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.1) > CurTime()) then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("particles/smokey", self:GetPos()) + + smoke:SetStartAlpha(30) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(30, 40)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 4) + + smoke:SetColor(255, 50, 25) + smoke:SetLighting(false) + smoke:SetDieTime(math.Rand(3.5, 4.5)) + smoke:SetGravity(Vector(0, 0, -7)) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 50, 25) + local col2 = Color(255, 155, 155) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + emitter:Finish() + end +end + + +function ENT:SafetyImpact(data, collider) + local attacker = self.Attacker or self:GetOwner() + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("StunstickImpact", fx) + + if IsValid(data.HitEntity) then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(attacker) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CRUSH + DMG_SLOWBURN) + dmginfo:SetDamage(self.ImpactDamage * (self.NPCDamage and 0.25 or 1)) + dmginfo:SetDamageForce(data.OurOldVelocity * 20) + dmginfo:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmginfo) + end + + self:EmitSound("physics/plastic/plastic_barrel_impact_hard2.wav", 70, 110) +end + +function ENT:UpdateTransmitState() + if TacRP.ConVars["dynamiclight"]:GetBool() then + return TRANSMIT_ALWAYS + end + return TRANSMIT_PVS +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_heal.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_heal.lua new file mode 100644 index 0000000..9a49399 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_heal.lua @@ -0,0 +1,58 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_p2a1_flare" +ENT.PrintName = "P2A1 Medi-Smoke Flare" +ENT.Spawnable = false + +ENT.ImpactDamage = 0 + +ENT.SmokeTrail = true +ENT.FlareColor = Color(100, 50, 255) +ENT.FlareSizeMin = 16 +ENT.FlareSizeMax = 32 +ENT.Gravity = Vector(0, 0, 9.81 * 0.3333) + +function ENT:Detonate(ent) + + self:EmitSound("TacRP/weapons/grenade/smoke_explode-1.wav", 80, 108) + timer.Simple(0, function() + local cloud = ents.Create( "tacrp_heal_cloud_p2a1" ) + if !IsValid(cloud) then return end + cloud:SetPos(self:GetPos()) + cloud:SetOwner(self:GetOwner()) + cloud:Spawn() + self:Remove() + end) +end + + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + render.SetMaterial(mat) + render.DrawSprite(self:GetPos(), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and !(self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.1) > CurTime()) then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("particles/smokey", self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(25, 35)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 32) + smoke:SetColor(125, 25, 255) + smoke:SetLighting(false) + smoke:SetDieTime(math.Rand(0.3, 0.5)) + smoke:SetGravity(Vector(0, 0, 0)) + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_incendiary.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_incendiary.lua new file mode 100644 index 0000000..dbbb459 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_incendiary.lua @@ -0,0 +1,148 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_p2a1_flare" +ENT.PrintName = "P2A1 Incendiary Flare" +ENT.Spawnable = false + +ENT.Radius = 328 + +ENT.SafetyFuse = 0.3 + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 200, 100) +ENT.FlareSizeMin = 16 +ENT.FlareSizeMax = 32 +ENT.Gravity = Vector(0, 0, 9.81 * 0.3) + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() or self + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + local dmg = DamageInfo() + dmg:SetDamagePosition(self:GetPos()) + dmg:SetInflictor(self) + dmg:SetAttacker(attacker) + + // Apply a small instance of damage to ignite first, before doing the real damage + // This will ensure if the target dies it is on fire first (so it can ignite its ragdolls etc.) + dmg:SetDamageType(DMG_SLOWBURN) + dmg:SetDamage(5) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + dmg:SetDamageType(DMG_BURN) + dmg:SetDamage(60 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), self.Radius) + + // TacRP.Flashbang(self, self:GetPos(), 512, 0.5, 0.1, 0) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("tacrp_m202_explode", fx) + self:EmitSound("^ambient/fire/ignite.wav", 80, 108) + self:EmitSound("^weapons/explode5.wav", 80, 115, 0.8) + end + + self:Remove() +end + + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex() + 1) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 128 + self.Light.b = 50 + self.Light.Brightness = 1 + self.Light.Size = 328 + self.Light.DieTime = CurTime() + 30 + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + render.SetMaterial(mat) + render.DrawSprite(self:GetPos(), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:OnRemove() + if self.Light then + self.Light.Size = 728 + self.Light.Brightness = 1 + self.Light.DieTime = CurTime() + 4 + self.Light.Decay = 250 + end + if !self.FireSound then return end + self.FireSound:Stop() +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and !(self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.1) > CurTime()) then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("particles/smokey", self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(25, 35)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 32) + + smoke:SetColor(255, 200, 25) + smoke:SetLighting(false) + smoke:SetDieTime(math.Rand(0.3, 0.5)) + smoke:SetGravity(Vector(0, 0, 0)) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 128, 50) + local col2 = Color(220, 200, 180) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + emitter:Finish() + end +end + + +function ENT:SafetyImpact(data, collider) + local attacker = self.Attacker or self:GetOwner() + local ang = data.OurOldVelocity:Angle() + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("StunstickImpact", fx) + + if IsValid(data.HitEntity) then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(attacker) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(self.ImpactDamageType) + dmginfo:SetDamage(self.ImpactDamage * (self.NPCDamage and 0.25 or 1)) + dmginfo:SetDamageForce(data.OurOldVelocity * 20) + dmginfo:SetDamagePosition(data.HitPos) + data.HitEntity:TakeDamageInfo(dmginfo) + end + + self:EmitSound("physics/plastic/plastic_barrel_impact_hard2.wav", 70, 110) +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_paraflare.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_paraflare.lua new file mode 100644 index 0000000..7fc1e84 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_paraflare.lua @@ -0,0 +1,113 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_p2a1_flare" +ENT.PrintName = "P2A1 Parachute Flare" +ENT.Spawnable = false + +ENT.InstantFuse = true +ENT.RemoteFuse = false +ENT.ImpactFuse = false + +ENT.ExplodeOnDamage = false // projectile explodes when it takes damage. +ENT.ExplodeUnderwater = true + +ENT.Delay = 2 + +ENT.Radius = 328 + +ENT.FlareColor = Color(255, 240, 240) + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + self:EmitSound("tacint_extras/p2a1/confetti.wav", 80, 80) + end + + local cloud = ents.Create("tacrp_flare_cloud_para") + + if !IsValid(cloud) then return end + + cloud:SetPos(self:GetPos()) + cloud:SetAngles(self:GetAngles()) + cloud:SetOwner(attacker) + timer.Simple(0, function() + cloud:Spawn() + if IsValid(self:GetParent()) then + cloud:SetParent(self:GetParent()) + elseif self:GetMoveType() == MOVETYPE_NONE then + cloud:SetMoveType(MOVETYPE_NONE) + else + cloud:GetPhysicsObject():SetVelocityInstantaneous(self:GetVelocity() * 1 + Vector(math.Rand(-64, 64), math.Rand(-64, 64), 256)) + end + self:Remove() + end) +end + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + if !self.Light and TacRP.ConVars["dynamiclight"]:GetBool() then + self.Light = DynamicLight(self:EntIndex() + 1) + if (self.Light) then + self.Light.Pos = self:GetPos() + self.Light.r = 255 + self.Light.g = 200 + self.Light.b = 100 + self.Light.Brightness = 0.5 + self.Light.Size = 728 + self.Light.DieTime = CurTime() + 2 + end + elseif self.Light then + self.Light.Pos = self:GetPos() + end + + render.SetMaterial(mat) + render.DrawSprite(self:GetPos(), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and !(self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.1) > CurTime()) then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("particles/smokey", self:GetPos()) + + smoke:SetStartAlpha(30) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(30, 40)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 4) + + smoke:SetColor(255, 50, 25) + smoke:SetLighting(false) + smoke:SetDieTime(math.Rand(3.5, 4.5)) + smoke:SetGravity(Vector(0, 0, -7)) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke:SetThinkFunction( function(pa) + if !pa then return end + local col1 = Color(255, 200, 150) + local col2 = Color(255, 255, 225) + + local col3 = col1 + local d = pa:GetLifeTime() / pa:GetDieTime() + col3.r = Lerp(d, col1.r, col2.r) + col3.g = Lerp(d, col1.g, col2.g) + col3.b = Lerp(d, col1.b, col2.b) + + pa:SetColor(col3.r, col3.g, col3.b) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_smoke.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_smoke.lua new file mode 100644 index 0000000..3f96b3b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_p2a1_smoke.lua @@ -0,0 +1,59 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_p2a1_flare" +ENT.PrintName = "P2A1 Smoke Flare" +ENT.Spawnable = false + +ENT.ImpactDamage = 0 + +ENT.SmokeTrail = true +ENT.FlareColor = Color(255, 255, 255) +ENT.FlareSizeMin = 16 +ENT.FlareSizeMax = 32 +ENT.Gravity = Vector(0, 0, 9.81 * 0.3333) + +function ENT:Detonate(ent) + + self:EmitSound("TacRP/weapons/grenade/smoke_explode-1.wav", 80, 108) + timer.Simple(0, function() + local cloud = ents.Create( "tacrp_smoke_cloud_p2a1" ) + if !IsValid(cloud) then return end + cloud:SetPos(self:GetPos()) + cloud:SetOwner(self:GetOwner()) + cloud:Spawn() + self:Remove() + end) +end + + +local mat = Material("effects/ar2_altfire1b") +function ENT:Draw() + render.SetMaterial(mat) + render.DrawSprite(self:GetPos(), math.Rand(self.FlareSizeMin, self.FlareSizeMax), math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor) +end + +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and !(self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.1) > CurTime()) then + local emitter = ParticleEmitter(self:GetPos()) + + local smoke = emitter:Add("particles/smokey", self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(4) + smoke:SetEndSize(math.Rand(25, 35)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 32) + + smoke:SetColor(255, 255, 255) + smoke:SetLighting(false) + smoke:SetDieTime(math.Rand(0.3, 0.5)) + smoke:SetGravity(Vector(0, 0, 0)) + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7.lua new file mode 100644 index 0000000..9c7e503 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7.lua @@ -0,0 +1,46 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "RPG-7 Rocket" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.SafetyFuse = 0.075 +ENT.ImpactDamage = 150 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 255, 255) + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + util.BlastDamage(self, attacker, self:GetPos(), 350, 200 * mult) + self:ImpactTraceAttack(ent, 1000 * mult, 15000) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_harpoon.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_harpoon.lua new file mode 100644 index 0000000..61bc737 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_harpoon.lua @@ -0,0 +1,155 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "RPG7 Shovel" -- not actually a harpoon lol +ENT.Spawnable = false + +ENT.Model = "models/props_junk/Shovel01a.mdl" + +ENT.ImpactDamage = 0 + +ENT.InstantFuse = false +ENT.ImpactFuse = true + +ENT.SmokeTrail = false + +local path = "tacrp/weapons/knife/" +ENT.Sound_MeleeHit = { + "physics/metal/metal_box_impact_hard1.wav", + "physics/metal/metal_box_impact_hard2.wav", + "physics/metal/metal_box_impact_hard3.wav", +} +ENT.Sound_MeleeHitBody = { + path .. "/flesh_hit-1.wav", + path .. "/flesh_hit-2.wav", + path .. "/flesh_hit-3.wav", + path .. "/flesh_hit-4.wav", + path .. "/flesh_hit-5.wav", +} + +function ENT:OnInitialize() + if SERVER then + self:GetPhysicsObject():SetMass(50) + self:GetPhysicsObject():SetDragCoefficient(5) + end + self.Attacker = self.Attacker or self:GetOwner() +end + +function ENT:Impact(data, collider) + local tgt = data.HitEntity + local attacker = self.Attacker or (IsValid(self:GetOwner()) and self:GetOwner()) or self + local d = data.OurOldVelocity:GetNormalized() + local ang = data.OurOldVelocity:Angle() + + local speed = data.Speed + if speed <= 50 then return true end + + local dmg = self.NPCDamage and 75 or math.Clamp(speed / 15, 50, 200) + + if IsValid(tgt) then + local dmginfo = DamageInfo() + dmginfo:SetAttacker(attacker) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_CLUB) + dmginfo:SetDamage(dmg) + dmginfo:SetDamageForce(data.OurOldVelocity) + dmginfo:SetDamagePosition(data.HitPos) + tgt:TakeDamageInfo(dmginfo) + + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + else + local ang = data.OurOldVelocity:Angle() + + -- leave a bullet hole. Also may be able to hit things it can't collide with (like stuck C4) + self:FireBullets({ + Attacker = attacker, + Damage = dmg, + Force = 1, + Distance = 4, + HullSize = 4, + Tracer = 0, + Dir = ang:Forward(), + Src = data.HitPos - ang:Forward(), + IgnoreEntity = self, + Callback = function(atk, tr, dmginfo) + dmginfo:SetDamageType(DMG_CLUB) + dmginfo:SetInflictor(attacker) + if tr.HitSky then + SafeRemoveEntity(self) + else + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetNormal(-ang:Forward()) + fx:SetAngles(-ang) + util.Effect("ManhackSparks", fx) + if SERVER then + self:EmitSound(istable(self.Sound_MeleeHit) and self.Sound_MeleeHit[math.random(1, #self.Sound_MeleeHit)] or self.Sound_MeleeHit, 80, 110, 1) + end + end + end + }) + end + + self:GetPhysicsObject():SetVelocityInstantaneous(data.OurNewVelocity * 0.5) + + timer.Simple(0.01, function() + if IsValid(self) then + self:SetOwner(NULL) -- lol + self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + end + end) + timer.Simple(5, function() + if IsValid(self) then + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetRenderFX(kRenderFxFadeFast) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + end + end) + SafeRemoveEntityDelayed(self, 7) + + return true +end + +local g = Vector(0, 0, -9.81) +function ENT:PhysicsUpdate(phys) + if !self.Armed and phys:IsGravityEnabled() and self:WaterLevel() <= 2 then + local v = phys:GetVelocity() + local a = v:Angle() + a.p = a.p - 90 + self:SetAngles(a) + phys:SetVelocityInstantaneous(v * 0.985 + g) + end +end + +ENT.SmokeTrail = false +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end +function ENT:DoSmokeTrail() + if CLIENT and self.SmokeTrail and self:GetVelocity():Length() >= 100 then + local pos = self:GetPos() + local emitter = ParticleEmitter(pos) + local smoke = emitter:Add(GetSmokeImage(), pos) + + smoke:SetStartAlpha(25) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(2) + smoke:SetEndSize(math.Rand(16, 24)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetVelocity(VectorRand() * 8 + self:GetUp() * 16) + smoke:SetColor(255, 255, 255) + smoke:SetLighting(true) + smoke:SetDieTime(math.Rand(0.5, 0.75)) + smoke:SetGravity(Vector(0, 0, 15)) + emitter:Finish() + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_improvised.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_improvised.lua new file mode 100644 index 0000000..ab9d338 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_improvised.lua @@ -0,0 +1,253 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "RPG-7 Improvised Rocket" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.SafetyFuse = 0 +ENT.BoostTime = 5 +ENT.ImpactDamage = 150 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 255, 75) + +DEFINE_BASECLASS(ENT.Base) + +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "NoBooster") + self:NetworkVar("Entity", 0, "Weapon") +end + +function ENT:Initialize() + BaseClass.Initialize(self) + + if SERVER then + local phys = self:GetPhysicsObject() + local rng = math.random() + if rng <= 0.01 then + self:EmitSound("weapons/rpg/shotdown.wav", 80, 95) + local fx = EffectData() + fx:SetOrigin(self:GetPos() + self:GetForward() * 32) + fx:SetStart(Vector(math.Rand(0, 255), math.Rand(0, 255), math.Rand(0, 255))) + util.Effect("balloon_pop", fx) + self:GetOwner():EmitSound("tacrp/kids_cheering.mp3", 100, 100, 1) + SafeRemoveEntity(self) + elseif rng <= 0.25 then + self.BoostTime = math.Rand(0.5, 5) + + self:EmitSound("weapons/rpg/shotdown.wav", 80, 95) + + self:SetNoBooster(math.random() <= 0.2) + phys:EnableGravity(true) + + if self:GetNoBooster() then + phys:SetVelocityInstantaneous(self:GetOwner():GetVelocity() + self:GetForward() * math.Rand(25, 75) + self:GetUp() * math.Rand(75, 150)) + else + phys:SetVelocityInstantaneous(self:GetOwner():GetVelocity() + self:GetForward() * math.Rand(100, 500) + self:GetUp() * math.Rand(50, 200)) + end + phys:AddAngleVelocity(VectorRand() * 180) + else + self.BoostTime = math.Rand(1, 3) + phys:SetVelocityInstantaneous(self:GetForward() * math.Rand(3000, 6000)) + end + end +end + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() + + if math.random() <= 0.05 then + self:EmitSound("physics/metal/metal_barrel_impact_hard3.wav", 125, 115) + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + fx:SetMagnitude(4) + fx:SetScale(4) + fx:SetRadius(4) + fx:SetNormal(self:GetVelocity():GetNormalized()) + util.Effect("Sparks", fx) + + for i = 1, 4 do + local prop = ents.Create("prop_physics") + prop:SetPos(self:GetPos()) + prop:SetAngles(self:GetAngles()) + prop:SetModel("models/weapons/tacint/rpg7_shrapnel_p" .. i .. ".mdl") + prop:Spawn() + prop:GetPhysicsObject():SetVelocityInstantaneous(self:GetVelocity() * 0.5 + VectorRand() * 75) + prop:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + + SafeRemoveEntityDelayed(prop, 3) + end + + self:Remove() + return + end + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + if self.NPCDamage then + util.BlastDamage(self, attacker, self:GetPos(), 350, 100) + else + util.BlastDamage(self, attacker, self:GetPos(), 350, math.Rand(150, 250) * mult) + self:ImpactTraceAttack(ent, math.Rand(750, 1500) * mult, math.Rand(7500, 20000)) + end + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125) + + self:Remove() +end + +function ENT:PhysicsUpdate(phys) + if self:GetNoBooster() then return end + local len = phys:GetVelocity():Length() + local f = math.Clamp(len / 5000, 0, 1) + if phys:IsGravityEnabled() then + phys:AddVelocity(self:GetForward() * math.Rand(0, Lerp(f, 100, 10))) + phys:AddAngleVelocity(VectorRand() * Lerp(f, 8, 2)) + elseif self.SpawnTime < CurTime() and len < 500 then + phys:EnableGravity(true) + else + phys:AddVelocity(VectorRand() * Lerp(f, 5, 50) + self:GetForward() * Lerp(f, 10, 0)) + end +end + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +function ENT:Think() + if !IsValid(self) or self:GetNoDraw() then return end + + if !self.SpawnTime then + self.SpawnTime = CurTime() + end + + if !self.Armed and isnumber(self.TimeFuse) and self.SpawnTime + self.TimeFuse < CurTime() then + self.ArmTime = CurTime() + self.Armed = true + end + + if self.Armed and self.ArmTime + self.Delay < CurTime() then + self:PreDetonate() + end + + if SERVER and !self:GetNoBooster() and self.SpawnTime + self.BoostTime < CurTime() then + self:SetNoBooster(true) + self:GetPhysicsObject():EnableGravity(true) + end + + if self.LoopSound and self:GetNoBooster() then + self.LoopSound:Stop() + end + + if self.ExplodeUnderwater and self:WaterLevel() > 0 then + self:PreDetonate() + end + + if self.SmokeTrail and CLIENT then + local emitter = ParticleEmitter(self:GetPos()) + if !self:GetNoBooster() then + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(50, 75)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(-self:GetAngles():Forward() * 400 + (VectorRand() * 10)) + + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.75, 1.25)) + + smoke:SetGravity(Vector(0, 0, 0)) + + elseif !self.LastNoBooster then + + for i = 1, 10 do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + smoke:SetStartSize(25) + smoke:SetEndSize(math.Rand(100, 150)) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 200) + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + smoke:SetDieTime(math.Rand(0.75, 1.75)) + smoke:SetGravity(Vector(0, 0, -200)) + end + else + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(30) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(25, 50)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 10) + + smoke:SetColor(150, 150, 150) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.2, 0.3)) + + smoke:SetGravity(Vector(0, 0, 0)) + end + emitter:Finish() + + self.LastNoBooster = self:GetNoBooster() + + end + + + self:OnThink() +end + +local mat = Material("effects/ar2_altfire1b") + +function ENT:Draw() + self:DrawModel() + + if self.FlareColor and !self:GetNoBooster() then + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + (self:GetAngles():Forward() * -16), math.Rand(100, 150), math.Rand(100, 150), self.FlareColor) + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_mortar.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_mortar.lua new file mode 100644 index 0000000..debe6a9 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_mortar.lua @@ -0,0 +1,250 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "RPG-7 Mortar Rocket" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true + +ENT.InstantFuse = false +ENT.RemoteFuse = false +ENT.ImpactFuse = true + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.Delay = 0 +ENT.SafetyFuse = 0.7 +ENT.BoostTime = 0.3 +ENT.ImpactDamage = 150 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 50, 0) + +DEFINE_BASECLASS(ENT.Base) + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "Weapon") + self:NetworkVar("Bool", 0, "NoBooster") +end + +function ENT:Initialize() + BaseClass.Initialize(self) + + if SERVER then + -- self:SetAngles(self:GetAngles() + Angle(-5, 0, 0)) + local phys = self:GetPhysicsObject() + phys:SetMass(30) + phys:SetDragCoefficient(1) + phys:SetVelocity(self:GetForward() * 4000) + end +end + +function ENT:Detonate() + local attacker = self.Attacker or self:GetOwner() + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + + if self.NPCDamage then + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("HelicopterMegaBomb", fx) + end + util.BlastDamage(self, attacker, self:GetPos(), 512, 150 * mult) + else + if self.SpawnTime + self.SafetyFuse >= CurTime() then + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("HelicopterMegaBomb", fx) + end + util.BlastDamage(self, attacker, self:GetPos(), 256, 100 * mult) + else + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + self:EmitSound("^ambient/explosions/explode_3.wav", 100, 90, 0.75, CHAN_AUTO) + util.BlastDamage(self, attacker, self:GetPos(), 128, 500 * mult) + util.BlastDamage(self, attacker, self:GetPos(), 328, 120 * mult) + util.BlastDamage(self, attacker, self:GetPos(), 768, 80 * mult) + local count = 8 + for i = 1, count do + local tr = util.TraceLine({ + start = self:GetPos(), + endpos = self:GetPos() + Angle(0, i / count * 360, 0):Forward() * 328 * math.Rand(0.75, 1), + mask = MASK_SHOT, + filter = self, + }) + fx:SetOrigin(tr.HitPos) + util.Effect("HelicopterMegaBomb", fx) + end + end + end + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125, 95) + + self:Remove() +end + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +function ENT:Think() + if !IsValid(self) or self:GetNoDraw() then return end + + if !self.SpawnTime then + self.SpawnTime = CurTime() + end + + if !self.Armed and isnumber(self.TimeFuse) and self.SpawnTime + self.TimeFuse < CurTime() then + self.ArmTime = CurTime() + self.Armed = true + end + + if self.Armed and self.ArmTime + self.Delay < CurTime() then + self:PreDetonate() + end + + if SERVER and !self:GetNoBooster() and self.SpawnTime + self.BoostTime < CurTime() then + self:SetNoBooster(true) + self:GetPhysicsObject():EnableGravity(true) + self:GetPhysicsObject():SetVelocityInstantaneous(self:GetVelocity() * 0.5) + end + + if self.LoopSound and self:GetNoBooster() then + self.LoopSound:Stop() + end + + if self.ExplodeUnderwater and self:WaterLevel() > 0 then + self:PreDetonate() + end + + if self.SmokeTrail and CLIENT then + local emitter = ParticleEmitter(self:GetPos()) + if !self:GetNoBooster() then + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(50, 75)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(-self:GetAngles():Forward() * 400 + (VectorRand() * 10)) + + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.75, 1.25)) + + smoke:SetGravity(Vector(0, 0, 0)) + + elseif !self.LastNoBooster then + + for i = 1, 10 do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetStartAlpha(50) + smoke:SetEndAlpha(0) + smoke:SetStartSize(25) + smoke:SetEndSize(math.Rand(100, 150)) + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 200) + smoke:SetColor(200, 200, 200) + smoke:SetLighting(true) + smoke:SetDieTime(math.Rand(0.75, 1.75)) + smoke:SetGravity(Vector(0, 0, -200)) + end + else + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + + smoke:SetStartAlpha(30) + smoke:SetEndAlpha(0) + + smoke:SetStartSize(10) + smoke:SetEndSize(math.Rand(25, 50)) + + smoke:SetRoll(math.Rand(-180, 180)) + smoke:SetRollDelta(math.Rand(-1, 1)) + + smoke:SetPos(self:GetPos()) + smoke:SetVelocity(VectorRand() * 10) + + smoke:SetColor(150, 150, 150) + smoke:SetLighting(true) + + smoke:SetDieTime(math.Rand(0.2, 0.3)) + + smoke:SetGravity(Vector(0, 0, 0)) + end + + if CurTime() >= (self.SpawnTime + self.SafetyFuse) and !self.Sparked then + self.Sparked = true + for i = 1, 15 do + local fire = emitter:Add("effects/spark", self:GetPos()) + fire:SetVelocity(VectorRand() * 512 + self:GetVelocity() * 0.25) + fire:SetGravity(Vector(math.Rand(-5, 5), math.Rand(-5, 5), -1000)) + fire:SetDieTime(math.Rand(0.2, 0.4)) + fire:SetStartAlpha(255) + fire:SetEndAlpha(0) + fire:SetStartSize(8) + fire:SetEndSize(0) + fire:SetRoll(math.Rand(-180, 180)) + fire:SetRollDelta(math.Rand(-0.2, 0.2)) + fire:SetColor(255, 255, 255) + fire:SetAirResistance(50) + fire:SetLighting(false) + fire:SetCollide(true) + fire:SetBounce(0.8) + end + end + + emitter:Finish() + + self.LastNoBooster = self:GetNoBooster() + end + + self:OnThink() +end + +local g = Vector(0, 0, -9.81) +function ENT:PhysicsUpdate(phys) + if phys:IsGravityEnabled() then + local v = phys:GetVelocity() + self:SetAngles(v:Angle()) + phys:SetVelocityInstantaneous(v + g) + end + + -- local v = phys:GetVelocity() + -- self:SetAngles(v:Angle() + Angle(2, 0, 0)) + -- phys:SetVelocityInstantaneous(self:GetForward() * v:Length()) +end + +local mat = Material("effects/ar2_altfire1b") + +function ENT:Draw() + self:DrawModel() + + if self.FlareColor and !self:GetNoBooster() then + render.SetMaterial(mat) + render.DrawSprite(self:GetPos() + (self:GetAngles():Forward() * -16), math.Rand(100, 150), math.Rand(100, 150), self.FlareColor) + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_ratshot.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_ratshot.lua new file mode 100644 index 0000000..3bad211 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_rpg7_ratshot.lua @@ -0,0 +1,98 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "RPG-7 Ratshot" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = true // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. +ENT.TimeFuse = false + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.Delay = 0.3 +ENT.SafetyFuse = 0.3 +ENT.ImpactDamage = 150 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(75, 175, 255) + +function ENT:Detonate() + local dir = self:GetForward() + local attacker = self.Attacker or self:GetOwner() or self + local src = self:GetPos() - dir * 64 + local fx = EffectData() + fx:SetOrigin(src) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + fx:SetMagnitude(8) + fx:SetScale(2) + fx:SetRadius(8) + fx:SetNormal(dir) + util.Effect("Sparks", fx) + + local tr = util.TraceHull({ + start = src, + endpos = src + dir * 2048, + filter = self, + mins = Vector(-16, -16, -8), + maxs = Vector(16, 16, 8) + }) + fx:SetMagnitude(4) + fx:SetScale(1) + fx:SetRadius(2) + fx:SetNormal(dir) + for i = 1, math.floor(tr.Fraction * 6) do + fx:SetOrigin(tr.StartPos + tr.Normal * (i / 6) * 2048) + util.Effect("Sparks", fx) + end + end + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() * (self.NPCDamage and 0.25 or 1) + + self:FireBullets({ + Attacker = attacker, + Damage = 5, + Force = 1, + Distance = 2048, + HullSize = 16, + Num = 48, + Tracer = 1, + Src = src, + Dir = dir, + Spread = Vector(1, 1, 0), + IgnoreEntity = self, + }) + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetDamageType(DMG_BULLET + DMG_BLAST) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:GetVelocity() * 100) + dmg:SetDamagePosition(src) + for _, ent in pairs(ents.FindInCone(src, dir, 2048, 0.707)) do + local tr = util.QuickTrace(src, ent:GetPos() - src, {self, ent}) + if tr.Fraction == 1 then + dmg:SetDamage(130 * math.Rand(0.75, 1) * Lerp((ent:GetPos():DistToSqr(src) / 4194304) ^ 0.5, 1, 0.25) * mult) + if !ent:IsOnGround() then dmg:ScaleDamage(1.5) end + ent:TakeDamageInfo(dmg) + end + end + + util.BlastDamage(self, attacker, src, 256, 50 * mult) + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125, 100) + self:EmitSound("physics/metal/metal_box_break1.wav", 100, 200) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger.lua new file mode 100644 index 0000000..774eebe --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger.lua @@ -0,0 +1,73 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_base" +ENT.PrintName = "FIM-92 Missile" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.GunshipWorkaround = false + +ENT.Delay = 0 +ENT.SafetyFuse = 0.1 +ENT.ImpactDamage = 150 + +ENT.SteerSpeed = 120 +ENT.SeekerAngle = 55 + +ENT.LeadTarget = true +ENT.SuperSteerTime = 1 +ENT.SuperSteerSpeed = 240 + +ENT.MaxSpeed = 5000 +ENT.Acceleration = 4000 + +ENT.SteerDelay = 0.5 +ENT.FlareRedirectChance = 0.2 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 255, 255) + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() + local dir = self:GetForward() + local src = self:GetPos() - dir * 64 + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetDamageType(DMG_BLAST + DMG_AIRBOAT) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:GetVelocity() * 100) + dmg:SetDamagePosition(src) + dmg:SetDamage(100 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), 256) + self:ImpactTraceAttack(ent, 500 * mult, 100) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_4aam.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_4aam.lua new file mode 100644 index 0000000..a141a6a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_4aam.lua @@ -0,0 +1,71 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_stinger" +ENT.PrintName = "FIM-92 Missile (4AAM)" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = false +ENT.ExplodeUnderwater = true + +ENT.GunshipWorkaround = false + +ENT.FlareSizeMin = 150 +ENT.FlareSizeMax = 200 + +ENT.SafetyFuse = 0.1 +ENT.ImpactDamage = 150 + +ENT.SteerSpeed = 200 +ENT.SeekerAngle = 90 + +ENT.LeadTarget = true +ENT.SuperSteerTime = 0.5 +ENT.SuperSteerSpeed = -90 // yes this is intentionally negative + +ENT.MaxSpeed = 6000 +ENT.Acceleration = 2000 + +ENT.SteerDelay = 0 +ENT.FlareRedirectChance = 0.4 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 230, 200) + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() + local dir = self:GetForward() + local src = self:GetPos() - dir * 64 + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetDamageType(DMG_BLAST + DMG_AIRBOAT) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:GetVelocity() * 100) + dmg:SetDamagePosition(src) + dmg:SetDamage(75 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), 200) + self:ImpactTraceAttack(ent, 100 * mult, 100) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + util.Effect("HelicopterMegaBomb", fx) + + self:EmitSound("^tacrp/weapons/grenade/40mm_explode-" .. math.random(1, 3) .. ".wav", 115) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_apers.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_apers.lua new file mode 100644 index 0000000..8c6c477 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_apers.lua @@ -0,0 +1,123 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_stinger" +ENT.PrintName = "FIM-92 Missile (APERS)" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. +ENT.TimeFuse = false + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.GunshipWorkaround = false + +ENT.SafetyFuse = 0.1 +ENT.ImpactDamage = 150 + +ENT.SteerSpeed = 30 +ENT.SeekerAngle = 180 +ENT.SeekerExplodeRange = 728 +ENT.SeekerExplodeSnapPosition = false +ENT.SeekerExplodeAngle = 20 + +ENT.LeadTarget = true +ENT.SuperSteerTime = 1.5 +ENT.SuperSteerSpeed = 400 + +ENT.MaxSpeed = 2000 +ENT.Acceleration = 5000 + +ENT.SteerDelay = 0.5 +ENT.FlareRedirectChance = 0.5 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 255, 255) + +function ENT:OnInitialize() + if SERVER and IsValid(self.LockOnEntity) then + local dist = self.LockOnEntity:WorldSpaceCenter():Distance(self:GetPos()) + self.SteerDelay = math.Clamp(dist / 2000, 0.75, 3) + self.SuperSteerTime = self.SteerDelay + 0.5 + end +end + +function ENT:Detonate() + local dir = self:GetForward() + local attacker = self.Attacker or self:GetOwner() or self + local src = self:GetPos() - dir * 64 + local fx = EffectData() + fx:SetOrigin(src) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + fx:SetMagnitude(8) + fx:SetScale(2) + fx:SetRadius(8) + fx:SetNormal(dir) + util.Effect("Sparks", fx) + + local tr = util.TraceHull({ + start = src, + endpos = src + dir * 2048, + filter = self, + mins = Vector(-16, -16, -8), + maxs = Vector(16, 16, 8) + }) + fx:SetMagnitude(4) + fx:SetScale(1) + fx:SetRadius(2) + fx:SetNormal(dir) + for i = 1, math.floor(tr.Fraction * 6) do + fx:SetOrigin(tr.StartPos + tr.Normal * (i / 6) * 2048) + util.Effect("Sparks", fx) + end + end + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + + self:FireBullets({ + Attacker = attacker, + Damage = 5, + Force = 1, + Distance = 1024, + HullSize = 16, + Num = 48, + Tracer = 1, + Src = src, + Dir = dir, + Spread = Vector(0.5, 0.5, 0), + IgnoreEntity = self, + }) + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetDamageType(DMG_BUCKSHOT + DMG_BLAST) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:GetVelocity() * 100) + dmg:SetDamagePosition(src) + for _, ent in pairs(ents.FindInCone(src, dir, 1024, 0.707)) do + local tr = util.QuickTrace(src, ent:GetPos() - src, {self, ent}) + if tr.Fraction == 1 then + dmg:SetDamage(100 * math.Rand(0.75, 1) * Lerp((ent:GetPos():DistToSqr(src) / 1048576) ^ 0.5, 1, 0.25) * (self.NPCDamage and 0.5 or 1) * mult) + if !ent:IsOnGround() then dmg:ScaleDamage(1.5) end + ent:TakeDamageInfo(dmg) + end + end + + util.BlastDamage(self, attacker, src, 256, 50 * mult) + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125, 100) + self:EmitSound("physics/metal/metal_box_break1.wav", 100, 200) + + self:Remove() +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_qaam.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_qaam.lua new file mode 100644 index 0000000..54e8db4 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_qaam.lua @@ -0,0 +1,40 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_stinger" +ENT.PrintName = "FIM-92 Missile (QAAM)" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.GunshipWorkaround = false + +ENT.SafetyFuse = 0.1 +ENT.ImpactDamage = 150 + +ENT.SteerSpeed = 30 +ENT.SeekerAngle = 75 + +ENT.LeadTarget = true +ENT.SuperSteerTime = 3 +ENT.SuperSteerSpeed = 120 + +ENT.MaxSpeed = 8000 +ENT.Acceleration = 8000 + +ENT.SteerDelay = 0.3 +ENT.FlareRedirectChance = 0.35 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(175, 175, 255) diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_saam.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_saam.lua new file mode 100644 index 0000000..5cd1277 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_proj_stinger_saam.lua @@ -0,0 +1,81 @@ +AddCSLuaFile() + +ENT.Base = "tacrp_proj_stinger" +ENT.PrintName = "FIM-92 Missile (SAAM)" +ENT.Spawnable = false + +ENT.Model = "models/weapons/tacint/rocket_deployed.mdl" + +ENT.IsRocket = true // projectile has a booster and will not drop. + +ENT.InstantFuse = false // projectile is armed immediately after firing. +ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator. +ENT.ImpactFuse = true // projectile explodes on impact. + +ENT.ExplodeOnDamage = true +ENT.ExplodeUnderwater = true + +ENT.GunshipWorkaround = false + +ENT.SafetyFuse = 0.1 +ENT.ImpactDamage = 150 + +ENT.SteerSpeed = 60 +ENT.SeekerAngle = 55 + +ENT.LeadTarget = true +ENT.SuperSteerTime = 1 +ENT.SuperSteerSpeed = 150 + +ENT.MaxSpeed = 4500 +ENT.Acceleration = 2000 + +ENT.SteerDelay = 0.5 +ENT.FlareRedirectChance = 0.05 + +ENT.AudioLoop = "TacRP/weapons/rpg7/rocket_flight-1.wav" + +ENT.SmokeTrail = true + +ENT.FlareColor = Color(255, 255, 255) + +DEFINE_BASECLASS(ENT.Base) + +function ENT:Detonate(ent) + local attacker = self.Attacker or self:GetOwner() + local dir = self:GetForward() + local src = self:GetPos() - dir * 64 + + local mult = TacRP.ConVars["mult_damage_explosive"]:GetFloat() + local dmg = DamageInfo() + dmg:SetAttacker(attacker) + dmg:SetDamageType(DMG_BLAST + DMG_AIRBOAT) + dmg:SetInflictor(self) + dmg:SetDamageForce(self:GetVelocity()) + dmg:SetDamagePosition(src) + dmg:SetDamage(150 * mult) + util.BlastDamageInfo(dmg, self:GetPos(), 256) + self:ImpactTraceAttack(ent, 700 * mult, 100) + + local fx = EffectData() + fx:SetOrigin(self:GetPos()) + + if self:WaterLevel() > 0 then + util.Effect("WaterSurfaceExplosion", fx) + else + util.Effect("Explosion", fx) + end + + self:EmitSound("TacRP/weapons/rpg7/explode.wav", 125) + + self:Remove() +end + +function ENT:OnThink() + if IsValid(self:GetOwner()) and IsValid(self:GetOwner():GetActiveWeapon()) + and self:GetOwner():GetActiveWeapon().ArcticTacRP then + self.LockOnEntity = self:GetOwner():GetActiveWeapon():GetLockOnEntity() + else + self:SwitchTarget(nil) + end +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud.lua new file mode 100644 index 0000000..fd86bbb --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud.lua @@ -0,0 +1,193 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Smoke Cloud" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +ENT.TacRPSmoke = true +ENT.Particles = nil +ENT.SmokeRadius = 300 +ENT.SmokeColor = Color(150, 150, 150) +ENT.BillowTime = 1 +ENT.Life = 20 + +-- Cheap maths +ENT.SmokeRadiusSqr = ENT.SmokeRadius * ENT.SmokeRadius + +AddCSLuaFile() + +function ENT:Initialize() + local mins, maxs = Vector(-self.SmokeRadius / 2, -self.SmokeRadius / 2, -self.SmokeRadius / 2), Vector(self.SmokeRadius / 2, self.SmokeRadius / 2, self.SmokeRadius / 2) + if SERVER then + self:PhysicsInitSphere(self.SmokeRadius / 2) + self:SetCollisionBounds(mins, maxs) + self:SetMoveType( MOVETYPE_NONE ) + self:DrawShadow( false ) + else + + table.insert(TacRP.ClientSmokeCache, self) + + local emitter = ParticleEmitter(self:GetPos()) + + self.Particles = {} + + local amt = 20 + + for i = 1, amt do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetVelocity( VectorRand() * 8 + (Angle(0, i * (360 / amt), 0):Forward() * 250) ) + smoke:SetStartAlpha( 0 ) + smoke:SetEndAlpha( 255 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( self.SmokeRadius ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-0.2,0.2) ) + smoke:SetColor( self.SmokeColor.r, self.SmokeColor.g, self.SmokeColor.b ) + smoke:SetAirResistance( 75 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 0.2 ) + smoke:SetLighting( false ) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.bt = CurTime() + self.BillowTime + smoke.dt = CurTime() + self.BillowTime + self.Life + smoke.ft = CurTime() + self.BillowTime + self.Life + math.Rand(2.5, 5) + smoke:SetDieTime(smoke.ft) + smoke.life = self.Life + smoke.billowed = false + smoke.radius = self.SmokeRadius + smoke:SetThinkFunction( function(pa) + if !pa then return end + + local prog = 1 + local alph = 0 + + if pa.ft < CurTime() then + // pass + elseif pa.dt < CurTime() then + local d = (CurTime() - pa.dt) / (pa.ft - pa.dt) + + alph = 1 - d + elseif pa.bt < CurTime() then + alph = 1 + else + local d = math.Clamp(pa:GetLifeTime() / (pa.bt - CurTime()), 0, 1) + + prog = (-d ^ 2) + (2 * d) + + alph = d + end + + pa:SetEndSize( pa.radius * prog ) + pa:SetStartSize( pa.radius * prog ) + + pa:SetStartAlpha(255 * alph) + pa:SetEndAlpha(255 * alph) + + -- pa:SetLifeTime(pa:GetLifeTime() + FrameTime()) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + table.insert(self.Particles, smoke) + end + + emitter:Finish() + end + + if TacRP.ConVars["smoke_affectnpcs"]:GetBool() then + self:EnableCustomCollisions() + self:SetCustomCollisionCheck(true) + self:CollisionRulesChanged() + else + self:SetSolid(SOLID_NONE) + end + + self.dt = CurTime() + self.Life + self.BillowTime +end + +function ENT:TestCollision( startpos, delta, isbox, extents, mask ) + if (mask == MASK_BLOCKLOS or mask == MASK_BLOCKLOS_AND_NPCS) then + local len = delta:Length() + if len <= 200 then return false end -- NPCs can see very close + + local rad = self.SmokeRadiusSqr + local pos = self:GetPos() + local dir = delta:GetNormalized() + + -- Trace started within the smoke + if startpos:DistToSqr(pos) <= rad then + return { + HitPos = startpos, + Fraction = 0, + Normal = -dir, + } + end + + -- Find the closest point on the original trace to the smoke's origin point + local t = (pos - startpos):Dot(dir) + local p = startpos + t * dir + + -- If the point is within smoke radius, the trace is intersecting the smoke + if p:DistToSqr(pos) <= rad then + return { + HitPos = p, + Fraction = math.Clamp(t / len, 0, 0.95), + Normal = -dir, + } + end + else + return false + end +end + +hook.Add("ShouldCollide", "tacrp_smoke_cloud", function(ent1, ent2) + if ent1.TacRPSmoke and !ent2:IsNPC() then return false end + if ent2.TacRPSmoke and !ent1:IsNPC() then return false end +end) + +function ENT:Think() + + if SERVER then + if self.dt < CurTime() then + SafeRemoveEntity(self) + return + end + + --[[] + if !TacRP.ConVars["smoke_affectnpcs"]:GetBool() then return end + local targets = ents.FindInSphere(self:GetPos(), self.SmokeRadius) + for _, k in pairs(targets) do + if k:IsNPC() then + local ret = hook.Run("TacRP_StunNPC", k, self) + if ret then continue end + k:SetSchedule(SCHED_STANDOFF) + end + end + + self:NextThink(CurTime() + 0.5) + ]] + return true + end +end + +function ENT:OnRemove() + if CLIENT then + timer.Simple(0, function() + if !IsValid(self) then + table.RemoveByValue(TacRP.ClientSmokeCache, self) + end + end) + end +end + +function ENT:Draw() + return false +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud_ninja.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud_ninja.lua new file mode 100644 index 0000000..5ea3339 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud_ninja.lua @@ -0,0 +1,207 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "Smoke Cloud" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +ENT.TacRPSmoke = true +ENT.Particles = nil +ENT.SmokeRadius = 256 +ENT.SmokeColor = Color(220, 220, 220) +ENT.BillowTime = 0.5 +ENT.Life = 8 + +AddCSLuaFile() + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/weapons/w_eq_smokegrenade_thrown.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + else + + table.insert(TacRP.ClientSmokeCache, self) + + local emitter = ParticleEmitter(self:GetPos()) + + self.Particles = {} + + local amt = 20 + + for i = 1, amt do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetVelocity( VectorRand() * 8 + (Angle(0, i * (360 / amt), 0):Forward() * 220) ) + smoke:SetStartAlpha( 0 ) + smoke:SetEndAlpha( 255 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( self.SmokeRadius ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-0.2,0.2) ) + smoke:SetColor( self.SmokeColor.r, self.SmokeColor.g, self.SmokeColor.b ) + smoke:SetAirResistance( 75 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 0.2 ) + smoke:SetLighting( false ) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.bt = CurTime() + self.BillowTime + smoke.dt = CurTime() + self.BillowTime + self.Life + smoke.ft = CurTime() + self.BillowTime + self.Life + math.Rand(1, 3) + smoke:SetDieTime(smoke.ft) + smoke.life = self.Life + smoke.billowed = false + smoke.radius = self.SmokeRadius + smoke:SetThinkFunction( function(pa) + if !pa then return end + + local prog = 1 + local alph = 0 + + if pa.ft < CurTime() then + // pass + elseif pa.dt < CurTime() then + local d = (CurTime() - pa.dt) / (pa.ft - pa.dt) + + alph = 1 - d + elseif pa.bt < CurTime() then + alph = 1 + else + local d = math.Clamp(pa:GetLifeTime() / (pa.bt - CurTime()), 0, 1) + + prog = (-d ^ 2) + (2 * d) + + alph = d + end + + pa:SetEndSize( pa.radius * prog ) + pa:SetStartSize( pa.radius * prog ) + + pa:SetStartAlpha(255 * alph) + pa:SetEndAlpha(255 * alph) + + -- pa:SetLifeTime(pa:GetLifeTime() + FrameTime()) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + table.insert(self.Particles, smoke) + end + + emitter:Finish() + end + + self.dt = CurTime() + self.Life + self.BillowTime + 2 +end + +function ENT:Think() + + + if SERVER then + if !self:GetOwner():IsValid() then self:Remove() return end + + -- local o = self:GetOwner() + -- local origin = self:GetPos() + Vector(0, 0, 16) + + -- local dmg = DamageInfo() + -- dmg:SetAttacker(self:GetOwner()) + -- dmg:SetInflictor(self) + -- dmg:SetDamageType(DMG_NERVEGAS) + -- dmg:SetDamageForce(Vector(0, 0, 0)) + -- dmg:SetDamagePosition(self:GetPos()) + -- dmg:SetDamageCustom(1024) -- immersive death + + -- util.BlastDamageInfo(dmg, self:GetPos(), 300) + + -- for i, k in pairs(ents.FindInSphere(origin, 300)) do + -- if k == self:GetOwner() then continue end + + -- if k:IsPlayer() or k:IsNPC() or k:IsNextBot() then + -- local tr = util.TraceLine({ + -- start = origin, + -- endpos = k:EyePos() or k:WorldSpaceCenter(), + -- filter = self, + -- mask = MASK_SOLID_BRUSHONLY + -- }) + -- if tr.Fraction < 1 then continue end + -- local dist = (tr.HitPos - tr.StartPos):Length() + -- local delta = dist / 320 + + -- dmg:SetDamage(k:IsPlayer() and math.Rand(1, 3) or math.Rand(5, 15)) + + -- k:TakeDamageInfo(dmg) + + -- if k:IsPlayer() then + -- k:ScreenFade( SCREENFADE.IN, Color(150, 150, 50, 100), 2 * delta, 0 ) + + -- local timername = "tacrp_ninja_gas_" .. k:EntIndex() + -- local reps = 3 + + -- if timer.Exists(timername) then + -- reps = math.Clamp(timer.RepsLeft(timername) + 3, reps, 10) + -- timer.Remove(timername) + -- end + -- timer.Create(timername, 2, reps, function() + -- if !IsValid(k) or !k:Alive() then + -- timer.Remove(timername) + -- return + -- end + -- k:ScreenFade( SCREENFADE.IN, Color(150, 150, 50, 5), 0.1, 0 ) + -- if k:Health() > 1 then + -- local d = DamageInfo() + -- d:SetDamageType(DMG_NERVEGAS) + -- d:SetDamage(math.random(1, 2)) + -- d:SetInflictor(IsValid(self) and self or o) + -- d:SetAttacker(o) + -- d:SetDamageForce(k:GetForward()) + -- d:SetDamagePosition(k:GetPos()) + -- d:SetDamageCustom(1024) + -- k:TakeDamageInfo(d) + -- else + -- k:ViewPunch(Angle(math.Rand(-2, 2), 0, 0)) + -- end + -- if math.random() <= 0.3 then + -- k:EmitSound("ambient/voices/cough" .. math.random(1, 4) .. ".wav", 80, math.Rand(95, 105)) + -- end + -- end) + + -- if math.random() <= 0.3 then + -- k:EmitSound("ambient/voices/cough" .. math.random(1, 4) .. ".wav", 80, math.Rand(95, 105)) + -- end + -- elseif k:IsNPC() then + -- k:SetSchedule(SCHED_STANDOFF) + -- end + -- end + -- end + + -- self:NextThink(CurTime() + 1) + + if self.dt < CurTime() then + SafeRemoveEntity(self) + end + + return true + + end +end + +function ENT:OnRemove() + if CLIENT then + timer.Simple(0, function() + if !IsValid(self) then + table.RemoveByValue(TacRP.ClientSmokeCache, self) + end + end) + end +end + +function ENT:Draw() + return false +end diff --git a/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud_p2a1.lua b/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud_p2a1.lua new file mode 100644 index 0000000..b32056d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/entities/tacrp_smoke_cloud_p2a1.lua @@ -0,0 +1,100 @@ +ENT.Type = "anim" +ENT.Base = "tacrp_smoke_cloud" +ENT.PrintName = "Smoke Cloud" +ENT.Author = "" +ENT.Information = "" +ENT.Spawnable = false +ENT.AdminSpawnable = false + +local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"} + +local function GetSmokeImage() + return smokeimages[math.random(#smokeimages)] +end + +ENT.SmokeRadius = 150 +ENT.SmokeColor = Color(150, 150, 150) +ENT.BillowTime = 1 +ENT.Life = 12 + +AddCSLuaFile() + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/weapons/w_eq_smokegrenade_thrown.mdl" ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_NONE ) + self:DrawShadow( false ) + else + + table.insert(TacRP.ClientSmokeCache, self) + + local emitter = ParticleEmitter(self:GetPos()) + + self.Particles = {} + + local amt = 15 + + for i = 1, amt do + local smoke = emitter:Add(GetSmokeImage(), self:GetPos()) + smoke:SetVelocity( VectorRand() * 8 + (Angle(0, i * (360 / amt), 0):Forward() * 100) ) + smoke:SetStartAlpha( 0 ) + smoke:SetEndAlpha( 255 ) + smoke:SetStartSize( 0 ) + smoke:SetEndSize( self.SmokeRadius ) + smoke:SetRoll( math.Rand(-180, 180) ) + smoke:SetRollDelta( math.Rand(-0.2,0.2) ) + smoke:SetColor( self.SmokeColor.r, self.SmokeColor.g, self.SmokeColor.b ) + smoke:SetAirResistance( 75 ) + smoke:SetPos( self:GetPos() ) + smoke:SetCollide( true ) + smoke:SetBounce( 0.2 ) + smoke:SetLighting( false ) + smoke:SetNextThink( CurTime() + FrameTime() ) + smoke.bt = CurTime() + self.BillowTime + smoke.dt = CurTime() + self.BillowTime + self.Life + smoke.ft = CurTime() + self.BillowTime + self.Life + math.Rand(2.5, 5) + smoke:SetDieTime(smoke.ft) + smoke.life = self.Life + smoke.billowed = false + smoke.radius = self.SmokeRadius + smoke:SetThinkFunction( function(pa) + if !pa then return end + + local prog = 1 + local alph = 0 + + if pa.ft < CurTime() then + // pass + elseif pa.dt < CurTime() then + local d = (CurTime() - pa.dt) / (pa.ft - pa.dt) + + alph = 1 - d + elseif pa.bt < CurTime() then + alph = 1 + else + local d = math.Clamp(pa:GetLifeTime() / (pa.bt - CurTime()), 0, 1) + + prog = (-d ^ 2) + (2 * d) + + alph = d + end + + pa:SetEndSize( pa.radius * prog ) + pa:SetStartSize( pa.radius * prog ) + + pa:SetStartAlpha(255 * alph) + pa:SetEndAlpha(255 * alph) + + -- pa:SetLifeTime(pa:GetLifeTime() + FrameTime()) + pa:SetNextThink( CurTime() + FrameTime() ) + end ) + + table.insert(self.Particles, smoke) + end + + emitter:Finish() + end + + self.dt = CurTime() + self.Life + self.BillowTime + 2 +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_bind.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_bind.lua new file mode 100644 index 0000000..75e516a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_bind.lua @@ -0,0 +1,134 @@ +hook.Add("PlayerBindPress", "TacRP_Binds", function(ply, bind, pressed, code) + local wpn = ply:GetActiveWeapon() + + if !wpn or !IsValid(wpn) or !wpn.ArcticTacRP then return end + + -- if we don't block, TTT will do radio menu + if engine.ActiveGamemode() == "terrortown" and bind == "+zoom" and !LocalPlayer():KeyDown(IN_USE) then + ply.TacRPBlindFireDown = pressed + return true + end + + if bind == "+showscores" then + wpn.LastHintLife = CurTime() -- ping the hints + end + + if bind == "+menu_context" and !LocalPlayer():KeyDown(IN_USE) then + TacRP.KeyPressed_Customize = pressed + + return true + end + + if TacRP.ConVars["toggletactical"]:GetBool() and bind == "impulse 100" and wpn:GetValue("CanToggle") and (!GetConVar("mp_flashlight"):GetBool() or (TacRP.ConVars["flashlight_alt"]:GetBool() and ply:KeyDown(IN_WALK)) or (!TacRP.ConVars["flashlight_alt"]:GetBool() and !ply:KeyDown(IN_WALK))) then + TacRP.KeyPressed_Tactical = pressed + + return true + end +end) + +function TacRP.GetBind(binding) + local bind = input.LookupBinding(binding) + + if !bind then + if binding == "+grenade1" then + return string.upper(input.GetKeyName(TacRP.GRENADE1_Backup)) + elseif binding == "+grenade2" then + return string.upper(input.GetKeyName(TacRP.GRENADE2_Backup)) + end + + return "!" + end + + return string.upper(bind) +end + +function TacRP.GetBindKey(bind) + local key = input.LookupBinding(bind) + if !key then + if bind == "+grenade1" then + return string.upper(input.GetKeyName(TacRP.GRENADE1_Backup)) + elseif bind == "+grenade2" then + return string.upper(input.GetKeyName(TacRP.GRENADE2_Backup)) + end + + return bind + else + return string.upper(key) + end +end + +function TacRP.GetKeyIsBound(bind) + local key = input.LookupBinding(bind) + + if !key then + if bind == "+grenade1" then + return true + elseif bind == "+grenade2" then + return true + end + + return false + else + return true + end +end + +function TacRP.GetKey(bind) + local key = input.LookupBinding(bind) + + if !key then + if bind == "+grenade1" then + return TacRP.GRENADE1_Backup + elseif bind == "+grenade2" then + return TacRP.GRENADE2_Backup + end + + return false + else + return input.GetKeyCode(key) + end +end + +TacRP.KeyPressed_Melee = false + +concommand.Add("+tacrp_melee", function() + TacRP.KeyPressed_Melee = true +end) + +concommand.Add("-tacrp_melee", function() + TacRP.KeyPressed_Melee = false +end) + +TacRP.KeyPressed_Customize = false + +concommand.Add("+tacrp_customize", function() + TacRP.KeyPressed_Customize = true +end) + +concommand.Add("-tacrp_customize", function() + TacRP.KeyPressed_Customize = false +end) + +TacRP.KeyPressed_Tactical = false + +concommand.Add("+tacrp_tactical", function() + TacRP.KeyPressed_Tactical = true +end) + +concommand.Add("-tacrp_tactical", function() + TacRP.KeyPressed_Tactical = false +end) + +concommand.Add("tacrp_dev_listanims", function() + local wep = LocalPlayer():GetActiveWeapon() + if !wep then return end + local vm = LocalPlayer():GetViewModel() + if !vm then return end + local alist = vm:GetSequenceList() + + for i = 0, #alist do + MsgC(clr_b, i, " --- ") + MsgC(color_white, "\t", alist[i], "\n [") + MsgC(clr_r, "\t", vm:SequenceDuration(i), "\n") + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_darkrp.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_darkrp.lua new file mode 100644 index 0000000..84d1ed3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_darkrp.lua @@ -0,0 +1,136 @@ +local hold_tbl = {} +net.Receive("tacrp_spawnedwepatts", function() + local ent_index = net.ReadUInt(12) + hold_tbl[ent_index] = {} + local count = net.ReadUInt(4) + for i = 1, count do + hold_tbl[ent_index][net.ReadUInt(4)] = net.ReadUInt(TacRP.Attachments_Bits) + end + + if IsValid(Entity(ent_index)) then + Entity(ent_index).Attachments = hold_tbl[ent_index] + end +end) + +local function makeattmdl(self, atttbl, slottbl) + local model = atttbl.Model + + if atttbl.WorldModel then + model = atttbl.WorldModel + end + + local csmodel = ClientsideModel(model) + + if !IsValid(csmodel) then return end + + local scale = Matrix() + local vec = Vector(1, 1, 1) * (atttbl.Scale or 1) * (slottbl.WMScale or 1) + scale:Scale(vec) + csmodel:EnableMatrix("RenderMultiply", scale) + csmodel:SetNoDraw(true) + + local tbl = { + Model = csmodel, + Weapon = self + } + + table.insert(TacRP.CSModelPile, tbl) + + return csmodel +end + +hook.Add("onDrawSpawnedWeapon", "TacRP", function(ent) + local wep_tbl = weapons.Get(ent:GetWeaponClass()) + if !wep_tbl or !wep_tbl.ArcticTacRP then return end + + ent:DrawModel() + + if !ent.Attachments and hold_tbl[ent:EntIndex()] then + ent.Attachments = table.Copy(hold_tbl[ent:EntIndex()]) + hold_tbl[ent:EntIndex()] = nil + end + + local count = table.Count(ent.Attachments or {}) + + if count > 0 then + ent.TacRP_CSAttModels = ent.TacRP_CSAttModels or {} + for k, v in pairs(ent.Attachments) do + local atttbl = TacRP.Attachments[TacRP.Attachments_Index[v]] + local slottbl = wep_tbl.Attachments[k] + if !atttbl or !atttbl.Model then continue end + if !IsValid(ent.TacRP_CSAttModels[k]) then + ent.TacRP_CSAttModels[k] = makeattmdl(ent, atttbl, slottbl) + end + local model = ent.TacRP_CSAttModels[k] + + local offset_pos = slottbl.Pos_WM + local offset_ang = slottbl.Ang_WM + local bone = slottbl.WMBone or "ValveBiped.Bip01_R_Hand" + + if !bone then continue end + + local boneindex = ent:LookupBone(bone) + if !boneindex then continue end + + local bonemat = ent:GetBoneMatrix(boneindex) + if bonemat then + bpos = bonemat:GetTranslation() + bang = bonemat:GetAngles() + end + + local apos, aang + + apos = bpos + bang:Forward() * offset_pos.x + apos = apos + bang:Right() * offset_pos.y + apos = apos + bang:Up() * offset_pos.z + + aang = Angle() + aang:Set(bang) + + aang:RotateAroundAxis(aang:Right(), offset_ang.p) + aang:RotateAroundAxis(aang:Up(), offset_ang.y) + aang:RotateAroundAxis(aang:Forward(), offset_ang.r) + + local moffset = (atttbl.ModelOffset or Vector(0, 0, 0)) * (slottbl.VMScale or 1) + + apos = apos + aang:Forward() * moffset.x + apos = apos + aang:Right() * moffset.y + apos = apos + aang:Up() * moffset.z + + model:SetPos(apos) + model:SetAngles(aang) + model:SetRenderOrigin(apos) + model:SetRenderAngles(aang) + + model:DrawModel() + end + end + + -- if (EyePos() - ent:GetPos()):LengthSqr() <= 262144 then -- 512^2 + -- local ang = LocalPlayer():EyeAngles() + + -- ang:RotateAroundAxis(ang:Forward(), 180) + -- ang:RotateAroundAxis(ang:Right(), 90) + -- ang:RotateAroundAxis(ang:Up(), 90) + + -- cam.Start3D2D(ent:WorldSpaceCenter() + Vector(0, 0, (ent:OBBMaxs().z - ent:OBBMins().z) * 0.5 + 8) , ang, 0.1) + -- -- surface.SetFont("TacRP_LondonBetween_32_Unscaled") + -- surface.SetFont("TacRP_Myriad_Pro_32_Unscaled") + + -- local name = wep_tbl.PrintName .. (ent:Getamount() > 1 and (" ×" .. ent:Getamount()) or "") + -- local w = surface.GetTextSize(name) + -- surface.SetTextPos(-w / 2, 0) + -- surface.SetTextColor(255, 255, 255, 255) + -- surface.DrawText(name) + -- if count > 0 then + -- local str = count .. " Attachments" + -- local w2 = surface.GetTextSize(str) + -- surface.SetTextPos(-w2 / 2, 32) + -- surface.SetTextColor(255, 255, 255, 255) + -- surface.DrawText(str) + -- end + -- cam.End3D2D() + -- end + + return true +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_font.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_font.lua new file mode 100644 index 0000000..2255a8c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_font.lua @@ -0,0 +1,170 @@ +local hudscale = TacRP.ConVars["hudscale"] +function TacRP.SS(i) + return ScrW() / 640 * i * hudscale:GetFloat() +end +TacRP.HoloSS = (ScreenScaleH(1) / ScreenScale(1)) or 1 + +local sizes_to_make = { + 4, + 5, + 6, + 8, + 10, + 11, + 12, + 14, + 16, + 20, + 32 +} + +local italic_sizes_to_make = { + 6, + 8, +} + +local unscaled_sizes_to_make = { + 24, + 32, + 48 +} + +local fonts_to_make = { + "Myriad Pro", + "HD44780A00 5x8" +} + +-- TacRP_HD44780A00_5x8_32 + +local function generatefonts() + + for ind, font in ipairs(fonts_to_make) do + local fontname = string.Replace(font, " ", "_") + + if GetConVar("tacrp_font" .. ind) and GetConVar("tacrp_font" .. ind):GetString() ~= "" then + font = GetConVar("tacrp_font" .. ind):GetString() + else + font = TacRP:GetPhrase("font." .. ind) or font + end + + for _, i in pairs(sizes_to_make) do + + surface.CreateFont( "TacRP_" .. fontname .. "_" .. tostring(i), { + font = font, + size = TacRP.SS(i), + weight = 0, + antialias = true, + extended = true, -- Required for non-latin fonts + } ) + + surface.CreateFont( "TacRP_" .. fontname .. "_" .. tostring(i) .. "_Glow", { + font = font, + size = TacRP.SS(i), + weight = 0, + antialias = true, + blursize = 6, + extended = true, + } ) + end + + for _, i in pairs(italic_sizes_to_make) do + surface.CreateFont( "TacRP_" .. fontname .. "_" .. tostring(i) .. "_Italic", { + font = font, + size = TacRP.SS(i), + weight = 0, + antialias = true, + extended = true, + italic = true, + } ) + end + + for _, i in pairs(unscaled_sizes_to_make) do + + surface.CreateFont( "TacRP_" .. fontname .. "_" .. tostring(i) .. "_Unscaled", { + font = font, + size = i, + weight = 0, + antialias = true, + extended = true, + } ) + + end + end + +end + +generatefonts() + +function TacRP.Regen(full) + TacRP.HoloSS = (ScreenScaleH(1) / ScreenScale(1)) -- a little bodge to make sights scale, i'm not adding an extra hook just for this + if full then + generatefonts() + end +end + +hook.Add( "OnScreenSizeChanged", "TacRP.Regen", function() TacRP.Regen(true) end) +cvars.AddChangeCallback("tacrp_hudscale", function() TacRP.Regen(true) end, "tacrp_hudscale") + +function TacRP.MultiLineText(text, maxw, font) + local content = {} + local tline = "" + local x = 0 + surface.SetFont(font) + + local ts = surface.GetTextSize(" ") + + local newlined = string.Split(text, "\n") + + for _, line in ipairs(newlined) do + local words = string.Split(line, " ") + + for _, word in ipairs(words) do + local tx = surface.GetTextSize(word) + + if x + tx > maxw then + local dashi = string.find(word, "-") + if dashi and surface.GetTextSize(utf8.sub(word, 0, dashi)) <= maxw - x then + -- cut the word at the dash sign if possible + table.insert(content, tline .. utf8.sub(word, 0, dashi)) + tline = "" + x = 0 + word = utf8.sub(word, dashi + 1) + tx = surface.GetTextSize(word) + else + -- move whole word to new line + table.insert(content, tline) + tline = "" + x = 0 + + -- else + -- -- cut the word down from the middle + -- while x + tx > maxw do + -- local cut = "" + -- for i = 2, utf8.len(word) do + -- cut = utf8.sub(word, 0, -i) + -- tx = surface.GetTextSize(cut) + -- if x + tx < maxw then + -- table.insert(content, tline .. cut) + -- tline = "" + -- word = utf8.sub(word, utf8.len(word) - i + 2) + -- x = 0 + -- tx = surface.GetTextSize(word) + -- break + -- end + -- end + -- end + end + end + + tline = tline .. word .. " " + + x = x + tx + ts + end + + table.insert(content, tline) + tline = "" + x = 0 + end + + return content +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_garbage.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_garbage.lua new file mode 100644 index 0000000..bf13f11 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_garbage.lua @@ -0,0 +1,60 @@ +TacRP.CSModelPile = {} -- { {Model = NULL, Weapon = NULL} } +TacRP.FlashlightPile = {} + +function TacRP.CollectGarbage() + local removed = 0 + + local newpile = {} + + for _, k in pairs(TacRP.CSModelPile) do + if IsValid(k.Weapon) then + table.insert(newpile, k) + + continue + end + + SafeRemoveEntity(k.Model) + + removed = removed + 1 + end + + TacRP.CSModelPile = newpile + + if TacRP.Developer() and removed > 0 then + print("Removed " .. tostring(removed) .. " CSModels") + end +end + +hook.Add("PostCleanupMap", "TacRP.CleanGarbage", function() + TacRP.CollectGarbage() +end) + +timer.Create("TacRP CSModel Garbage Collector", 5, 0, TacRP.CollectGarbage) + +hook.Add("PostDrawEffects", "TacRP_CleanFlashlights", function() + local newflashlightpile = {} + + for _, k in pairs(TacRP.FlashlightPile) do + if IsValid(k.Weapon) and k.Weapon == LocalPlayer():GetActiveWeapon() then + table.insert(newflashlightpile, k) + + continue + end + + if k.ProjectedTexture and k.ProjectedTexture:IsValid() then + k.ProjectedTexture:Remove() + end + end + + TacRP.FlashlightPile = newflashlightpile + + local wpn = LocalPlayer():GetActiveWeapon() + + if !wpn then return end + if !IsValid(wpn) then return end + if !wpn.ArcticTacRP then return end + + if GetViewEntity() == LocalPlayer() then return end + + wpn:KillFlashlightsVM() +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_hooks.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_hooks.lua new file mode 100644 index 0000000..1866343 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_hooks.lua @@ -0,0 +1,210 @@ +hook.Add("PostPlayerDraw", "TacRP_Holster", function(ply, flags) + if !ply.TacRP_Holster or bit.band(flags, STUDIO_RENDER) != STUDIO_RENDER then return end + if ply == LocalPlayer() and !LocalPlayer():ShouldDrawLocalPlayer() then return end + + if !TacRP.ConVars["drawholsters"]:GetBool() or !TacRP.ConVars["visibleholster"]:GetBool() then return end + + ply.TacRP_HolsterModels = ply.TacRP_HolsterModels or {} + for i, v in ipairs(TacRP.HolsterBones) do + local wep = ply.TacRP_Holster[i] + if !IsValid(wep) or wep:GetOwner() != ply or wep == ply:GetActiveWeapon() or !wep:GetValue("HolsterVisible") then + SafeRemoveEntity(ply.TacRP_HolsterModels[i]) + ply.TacRP_HolsterModels[i] = nil + ply.TacRP_Holster[i] = nil + continue + end + + local bone = ply:LookupBone(v[1]) + local matrix = bone and ply:GetBoneMatrix(bone) + if !bone or !matrix then return end + + local fallback + local holstermodel = wep:GetValue("HolsterModel") or wep.WorldModel + if !util.IsValidModel(holstermodel) and v[3] then + holstermodel = v[3][1] + fallback = true + end + + if !ply.TacRP_HolsterModels[i] or !IsValid(ply.TacRP_HolsterModels[i]) + or ply.TacRP_HolsterModels[i]:GetModel() != holstermodel then + SafeRemoveEntity(ply.TacRP_HolsterModels[i]) + ply.TacRP_HolsterModels[i] = ClientsideModel(holstermodel, RENDERGROUP_OPAQUE) + ply.TacRP_HolsterModels[i]:SetNoDraw(true) + end + + local spos, sang = v[2], Angle() + if istable(v[2]) then + spos = v[2][1] + sang = v[2][2] + elseif v[2] == nil then + spos = Vector() + end + + local hpos, hang = wep:GetValue("HolsterPos"), wep:GetValue("HolsterAng") + if fallback then + hpos = v[3][2] + hang = v[3][3] + end + local off = spos + hpos + local rot = hang + sang + + local pos = matrix:GetTranslation() + local ang = matrix:GetAngles() + + pos = pos + ang:Right() * off.x + ang:Forward() * off.y + ang:Up() * off.z + ang:RotateAroundAxis(ang:Forward(), rot.p) + ang:RotateAroundAxis(ang:Up(), rot.y) + ang:RotateAroundAxis(ang:Right(), rot.r) + + debugoverlay.Axis(pos, ang, 8, FrameTime() * 2, true) + + model = ply.TacRP_HolsterModels[i] + model:SetPos(pos) + model:SetAngles(ang) + model:SetRenderOrigin(pos) + model:SetRenderAngles(ang) + model:DrawModel() + model:SetRenderOrigin() + model:SetRenderAngles() + + if !wep:GetValue("HolsterModel") then + wep:DoBodygroups(true, model) + wep:DrawCustomModel(true, ply.TacRP_HolsterModels[i]) + end + end +end) + +TacRP.ClientSmokeCache = {} + +hook.Add( "HUDDrawTargetID", "TacRP_FlashlightGlint", function() + local ply = LocalPlayer():GetEyeTrace().Entity + if !IsValid(ply) then return end + + -- Flashed + if LocalPlayer():GetNWFloat("TacRPStunStart", 0) + LocalPlayer():GetNWFloat("TacRPStunDur", 0) > CurTime() then return false end + + -- Flashlight glint + if ply:IsPlayer() and TacRP.ConVars["flashlight_blind"]:GetBool() then + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP and wep:GetValue("Flashlight") then + + local src, dir = wep:GetTracerOrigin(), wep:GetShootDir() + + local diff = EyePos() - src + + local dot = -dir:Forward():Dot(EyeAngles():Forward()) + local dot2 = dir:Forward():Dot(diff:GetNormalized()) + dot = math.max(0, (dot + dot2) / 2) ^ 1.5 + if dot > 0.707 then + local dist = 300 + local wep2 = LocalPlayer():GetActiveWeapon() + if IsValid(wep2) and wep2.ArcticTacRP and wep2:IsInScope() and wep2:GetValue("ScopeOverlay") then + dist = 3500 + end + if wep:GetTracerOrigin():Distance(EyePos()) <= dist then + return false + end + end + end + end + + + -- Smoke + for i, ent in ipairs(TacRP.ClientSmokeCache) do + if !IsValid(ent) or !ent.TacRPSmoke then table.remove(TacRP.ClientSmokeCache, i) continue end + local pos = ent:GetPos() + local rad = ent.SmokeRadius + + -- target is in smoke + if ply:WorldSpaceCenter():Distance(pos) <= rad then return false end + + local s = ply:WorldSpaceCenter() - EyePos() + local d = s:GetNormalized() + local v = pos - EyePos() + local t = v:Dot(d) + local p = EyePos() + t * d + + -- we are in smoke OR line of sight is intersecting smoke + if t > -rad and t < s:Length() and p:Distance(pos) <= rad then return false end + end +end) + +hook.Add("CreateMove", "TacRP_CreateMove", function(cmd) + local wpn = LocalPlayer():GetActiveWeapon() + + -- In TTT ScreenClicker isn't disabled for some reason + if TacRP.CursorEnabled and !LocalPlayer():Alive() then + TacRP.CursorEnabled = false + gui.EnableScreenClicker(false) + end + + if !IsValid(wpn) then return end + if !wpn.ArcticTacRP then return end + + if TacRP.ConVars["autoreload"]:GetBool() then + if wpn:GetMaxClip1() > 0 and wpn:Clip1() == 0 and (wpn:Ammo1() > 0 or wpn:GetInfiniteAmmo()) + and wpn:GetNextPrimaryFire() + 0.5 < CurTime() and wpn:ShouldAutoReload() then + local buttons = cmd:GetButtons() + + buttons = buttons + IN_RELOAD + + cmd:SetButtons(buttons) + end + end + + local is_typing = LocalPlayer():IsTyping() or gui.IsConsoleVisible() or gui.IsGameUIVisible() or vgui.GetKeyboardFocus() != nil + + local grenade1bind = input.LookupBinding("+grenade1") + + if !grenade1bind then + if input.IsKeyDown( KEY_G ) and !is_typing then + cmd:AddKey(IN_GRENADE1) + end + end + + local grenade2bind = input.LookupBinding("+grenade2") + + if !grenade2bind then + if input.IsKeyDown( KEY_H ) and !is_typing then + cmd:AddKey(IN_GRENADE2) + end + end + + if TacRP.KeyPressed_Melee then + cmd:AddKey(TacRP.IN_MELEE) + end + + if TacRP.KeyPressed_Customize then + cmd:AddKey(TacRP.IN_CUSTOMIZE) + end + + if TacRP.KeyPressed_Tactical then + cmd:AddKey(TacRP.IN_TACTICAL) + end + + local mult = TacRP.CalculateMaxMoveSpeed(LocalPlayer()) + + local basemove = Vector(cmd:GetForwardMove(), cmd:GetUpMove(), cmd:GetSideMove()) + local maxspeed = basemove:Length() + local movedir = basemove:GetNormalized() + + local finalmovedir = movedir * maxspeed * mult + + cmd:SetForwardMove(finalmovedir[1]) + cmd:SetUpMove(finalmovedir[2]) + cmd:SetSideMove(finalmovedir[3]) +end) + +hook.Add("PreRender", "TACRP_PreRender", function() + local wpn = LocalPlayer():GetActiveWeapon() + + if !wpn.ArcticTacRP then return end + + if wpn:GetValue("BlindFireCamera") then + wpn:DoRT() + end + + if wpn:GetValue("ThermalCamera") then + wpn:DoThermalRT() + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_hud.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_hud.lua new file mode 100644 index 0000000..aafb4f0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_hud.lua @@ -0,0 +1,149 @@ +local mat_corner_ul = Material("TacRP/hud/800corner_nobg1.png", "mips smooth") +local mat_corner_ur = Material("TacRP/hud/800corner_nobg2.png", "mips smooth") +local mat_corner_br = Material("TacRP/hud/800corner_nobg3.png", "mips smooth") +local mat_corner_bl = Material("TacRP/hud/800corner_nobg4.png", "mips smooth") + +function TacRP.DrawCorneredBox(x, y, w, h, col) + col = col or Color(255, 255, 255) + + surface.DrawRect(x, y, w, h) + + surface.SetDrawColor(col) + + local s = 8 + + surface.SetMaterial(mat_corner_ul) + surface.DrawTexturedRect(x, y, s, s) + + surface.SetMaterial(mat_corner_ur) + surface.DrawTexturedRect(x + w - s, y, s, s) + + surface.SetMaterial(mat_corner_bl) + surface.DrawTexturedRect(x, y + h - s, s, s) + + surface.SetMaterial(mat_corner_br) + surface.DrawTexturedRect(x + w - s, y + h - s, s, s) +end + +local hide = { + ["CHudHealth"] = true, + ["CHudBattery"] = true, + ["CHudAmmo"] = true, + ["CHudSecondaryAmmo"] = true, +} + +hook.Add("HUDShouldDraw", "TacRP_HideHUD", function(name) + if !IsValid(LocalPlayer()) then return end + local wpn = LocalPlayer():GetActiveWeapon() + + if IsValid(wpn) and wpn.ArcticTacRP and + ((TacRP.ConVars["drawhud"]:GetBool() and TacRP.ConVars["hud"]:GetBool()) or wpn:GetCustomize()) and hide[name] then + return false + end +end) + +local names = { + ["357"] = "Magnum Ammo", + ["smg1"] = "Carbine Ammo", + ["ar2"] = "Rifle Ammo", +} + +hook.Add("PreGamemodeLoaded", "TacRP_AmmoName", function() + if TacRP.ConVars["ammonames"]:GetBool() then + for k, v in pairs(names) do + language.Add(k .. "_ammo", TacRP:GetPhrase("ammo." .. k) or v) + end + end +end) + +local gaA = 0 +function TacRP.GetFOVAcc(spread) + local raw = isnumber(spread) + if !raw and spread:IsWeapon() then spread = spread:GetSpread() end + cam.Start3D() + local lool = ( EyePos() + ( EyeAngles():Forward() ) + ( spread * EyeAngles():Up() ) ):ToScreen() + cam.End3D() + + local gau = ( (ScrH() / 2) - lool.y ) + if raw then return gau end + + gaA = math.Approach(gaA, gau, (ScrH() / 2) * FrameTime() * 2) + + return gaA +end + +TacRP.PanelColors = { + ["bg"] = { + Color(0, 0, 0, 150), -- normal + Color(255, 255, 255, 255), -- hover + Color(150, 150, 150, 150), -- sel + Color(255, 255, 255, 255), -- sel, hover + }, + ["bg2"] = { + Color(0, 0, 0, 200), -- normal + Color(255, 255, 255, 255), -- hover + Color(150, 150, 150, 200), -- sel + Color(255, 255, 255, 255), -- sel, hover + }, + ["corner"] = { + Color(255, 255, 255), + Color(0, 0, 0), + Color(50, 50, 255), + Color(150, 150, 255), + }, + ["text"] = { + Color(255, 255, 255), + Color(0, 0, 0), + Color(255, 255, 255), + Color(0, 0, 0), + }, + ["text_glow"] = { + Color(255, 255, 255, 100), + Color(0, 0, 0, 100), + Color(255, 255, 255, 100), + Color(0, 0, 0, 100), + }, +} + +function TacRP.GetPanelColor(str, hvr, sel) + local i = 1 + if isnumber(hvr) or hvr == nil then + i = hvr or 1 + elseif sel then + i = hvr and 4 or 3 + elseif hvr then + i = 2 + end + + return TacRP.PanelColors[str][i] +end + +function TacRP.GetPanelColors(hvr, sel) + local i = 1 + if sel then + i = hvr and 4 or 3 + elseif hvr then + i = 2 + end + return TacRP.PanelColors["bg2"][i], TacRP.PanelColors["corner"][i], TacRP.PanelColors["text"][i] +end + +hook.Add("RenderScreenspaceEffects", "TacRP_Gas", function() + if LocalPlayer():GetNWFloat("TacRPGasEnd", 0) > CurTime() then + local delta = math.Clamp((LocalPlayer():GetNWFloat("TacRPGasEnd", 0) - CurTime()) / 2, 0, 1) ^ 1.5 + + DrawMaterialOverlay("effects/water_warp01", delta * 0.1) + DrawMotionBlur(0.5 * delta, 0.75 * delta, 0.01) + DrawColorModify({ + [ "$pp_colour_addr" ] = 0.125 * delta, + [ "$pp_colour_addg" ] = 0.2 * delta, + [ "$pp_colour_addb" ] = 0.05 * delta, + [ "$pp_colour_brightness" ] = -0.2 * delta, + [ "$pp_colour_contrast" ] = 1 - delta * 0.1, + [ "$pp_colour_colour" ] = 1 - delta * 0.75, + [ "$pp_colour_mulr" ] = 0, + [ "$pp_colour_mulg" ] = 0, + [ "$pp_colour_mulb" ] = 0, + }) + end +end ) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_killicons.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_killicons.lua new file mode 100644 index 0000000..1eba7e0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_killicons.lua @@ -0,0 +1,71 @@ +TacRPOldKilliconDraw = TacRPOldKilliconDraw or killicon.Draw +local killicons_cachednames = {} +local killicons_cachedicons = {} +local killiconmat = Material("HUD/killicons/default") + +TacRP.KillIconAlias = { + ["tacrp_proj_rpg7"] = "tacrp_rpg7", + ["tacrp_proj_rpg7_ratshot"] = "tacrp_rpg7", + + ["tacrp_proj_40mm_3gl"] = "tacrp_m320", + ["tacrp_proj_40mm_gas"] = "tacrp_m320", + ["tacrp_proj_40mm_he"] = "tacrp_m320", + ["tacrp_proj_40mm_heat"] = "tacrp_m320", + ["tacrp_proj_40mm_lvg"] = "tacrp_m320", + ["tacrp_proj_40mm_smoke"] = "tacrp_m320", + + ["tacrp_proj_ks23_flashbang"] = "tacrp_ks23", + + ["tacrp_proj_knife"] = "tacrp_knife", + + ["tacrp_gas_cloud"] = "tacrp_proj_nade_gas", -- also used by 40mm gas but whatever + ["tacrp_fire_cloud"] = "tacrp_proj_nade_thermite", + ["tacrp_nuke_cloud"] = "tacrp_proj_nade_nuke", +} + +TacRPNewKilliconDraw = function(x, y, name, alpha) + name = TacRP.KillIconAlias[name] or name + local wpn = weapons.Get(name) + + if tobool(killicons_cachednames[name]) == true then + local w, h + + -- nade icons are smaller + if killicons_cachednames[name] == 2 then + w, h = 48, 48 + else + w, h = 64, 64 + end + + x = x - w * 0.5 + y = y - h * 0.3 + + cam.Start2D() + + local selecticon = killicons_cachedicons[name] + + if !selecticon then -- not cached + local loadedmat = Material(wpn.IconOverride or "entities/" .. name .. ".png", "smooth mips") + killicons_cachedicons[name] = loadedmat + selecticon = loadedmat + end + + surface.SetDrawColor(255, 255, 255, alpha) + surface.SetMaterial(selecticon or killiconmat) + surface.DrawTexturedRect(x, y, w, h) + cam.End2D() + else + if killicons_cachednames[name] == nil then -- not cached yet, checking for tacrp + if TacRP.QuickNades_EntLookup[name] then + killicons_cachednames[name] = 2 + killicons_cachedicons[name] = TacRP.QuickNades[TacRP.QuickNades_EntLookup[name]].Icon or killiconmat + else + killicons_cachednames[name] = (weapons.Get(name) and weapons.Get(name).ArcticTacRP) or false + end -- weapons.get() will return nil for any hl2 base gun + else -- we know it is totally not tacrp gun + return TacRPOldKilliconDraw(x, y, name, alpha) + end + end +end + +killicon.Draw = TacRPNewKilliconDraw diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_net.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_net.lua new file mode 100644 index 0000000..956c1b6 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_net.lua @@ -0,0 +1,93 @@ +net.Receive("tacrp_networkweapon", function(len) + local wpn_index = net.ReadUInt(13) + local wpn = Entity(wpn_index) + + -- When the server immediately calls NetworkWeapon on a new weapon, + -- the client entity may not be valid or correct instantly. + -- (in SP, the entity will appear valid but the functions/variables will all be nil.) + if !IsValid(wpn) or !wpn.ArcticTacRP then + local tname = "wait" .. engine.TickCount() .. math.random(100, 999) + + -- Read bits now (can't do it in a timer) + -- WriteEntity always uses 13 bits, so we can derive amount of attachments + local ids = {} + for i = 1, (len - 13) / TacRP.Attachments_Bits do + table.insert(ids, net.ReadUInt(TacRP.Attachments_Bits)) + end + + -- Wait until entity properly exists to pass on attachment info. + -- Usually won't take more than 1 tick but ping may cause issues + timer.Create(tname, 0, 100, function() + local wpn = Entity(wpn_index) + if !IsValid(wpn) or !wpn.ArcticTacRP then return end + + wpn:ReceiveWeapon(ids) + wpn:UpdateHolster() + + timer.Remove(tname) + end) + else + wpn:ReceiveWeapon() + wpn:UpdateHolster() + end +end) + +net.Receive("TacRP_updateholster", function() + local ply = net.ReadEntity() + --local slot = net.ReadUInt(TacRP.HolsterNetBits) + local item = net.ReadEntity() + + if !IsValid(ply) or !IsValid(item) or !item.GetValue then return end + + local visible = item:GetValue("HolsterVisible") + local slot = item:GetValue("HolsterSlot") + + if visible and slot then + ply.TacRP_Holster = ply.TacRP_Holster or {} + if !IsValid(item) then + ply.TacRP_Holster[slot] = nil + else + ply.TacRP_Holster[slot] = item + end + end +end) + +net.Receive("tacrp_doorbust", function() + local door = net.ReadEntity() + if IsValid(door) then + local mins, maxs = door:GetCollisionBounds() + door:SetRenderBounds(mins, maxs, Vector(4, 4, 4)) + end +end) + +gameevent.Listen("player_spawn") +hook.Add("player_connect", "TacRP_Holster", function(userid) + local ply = Player(userid) + ply.TacRP_Holster = {} +end) + +net.Receive("tacrp_flashbang", function() + local time = net.ReadFloat() + + if time > 0 then + LocalPlayer():ScreenFade( SCREENFADE.IN, TacRP.ConVars["flash_dark"]:GetBool() and Color(0, 0, 0, 255) or Color(255, 255, 255, 255), math.min(time * 2, 2.5), time) + end + + if TacRP.ConVars["flash_dark"]:GetBool() then + LocalPlayer():SetDSP(32, time == 0) + else + LocalPlayer():SetDSP(37, time == 0) + end + +end) +net.Receive("tacrp_addshieldmodel", function(len, ply) + local wpn = net.ReadEntity() + local mdl = net.ReadEntity() + mdl.mmRHAe = net.ReadFloat() + mdl.TacRPShield = true + + if !IsValid(wpn) or !wpn.ArcticTacRP then return end + + wpn.Shields = wpn.Shields or {} + table.insert(wpn.Shields, mdl) +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_news.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_news.lua new file mode 100644 index 0000000..91a276c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_news.lua @@ -0,0 +1,1947 @@ +TacRP.News = { + --[[] + { + Title = "New Article", + Type = "Update", + Date = "2023-03-16", -- yyyy-mm-dd + Major = true, -- if unread, brings up page on startup + Summary = nil, -- text shown in the side tab + Hyperlink = nil, -- if set, loads the specified webpage instead of Contents. + Contents = "", -- main text, uses HTML + }, + ]] + { + Title = "New Newsletter System!", + Type = "Announcement", + Date = "2023-03-17", + Author = "8Z", + Major = true, + Summary = nil, + Contents = [[ +

Thank you for using Tactical RP, and welcome to our new newsletter!
+If you forgot you had TacRP installed, we're sorry for distrubing your gmod session, please don't unsubscribe

+ +

You can now catch up with updates, dev blogs, and more, all from the comfort of your own video game! A few posts (like this one) are embedded within the addon and will always be available, while others will be fetched remotely so you can get the hottest news without updating the addon.

+ +

Major updates and releases will be displayed on startup, while minor posts will bring up a reminder in the chat box. If you want to be only notified for major updates, toggle the checkbox above.

+ +

Despite the cold reception TacRP got at launch, some of you stuck with us (or at least tolerated its presence in the weapons tab), and we cannot thank you enough for your support. That's why we here at Garry's Mod Tactical Realism Industries™(trademark pending) are cooking up more good stuff for you to enjoy, including an expansion pack full of new weapons.

+ +

In the meantime, we hope you enjoy Tactical RP, and remember:
+No matter what Arctic might say, the RP in TacRP stands for Remix Pack, not Role Play! +
It is seriously kind of stupid to put roleplay in your weapon pack name. Is there even a DarkRP server out there that uses these guns? Don't they all just use M9K? Why do we even make new weapon packs if a decade-old weapon base with no c_hands is enough for them? Is humanity truly doomed? sussy imposter amogus??? skull emoji

+ +

Yours,
+8Z, the gmod hyperealist

+]] + }, + { + Title = "Interops Update", + Type = "Announcement", + Date = "2023-05-14", + Author = "8Z", + Major = true, + Summary = "Major update and expansion! It's here!!!", + Contents = [[ +

It's here!

+ +

Alongside the release of thirty-one new weapons in the Interops pack, a long list of changes have also dropped. They've been sneakily updated (sorry) but I've finally bothered to write the patch notes for them now.

+ +

Without being too verbose, here is a shortlist of all the changes up until this point.

+ +

Armor Penetration

+

Combat can get really awkward with HL2 armor absorbing 80% of all damage. Now, TacRP braves the unknown to become the second addon to ever challenge this status quo! (The first being, of course, my own Danger Zone Entities. #humblebrag)
+Every weapon has an Armor Piercing and Armor Shredding value. The former dictates how much damage is blocked by armor and the latter determines how much damage armor takes. +All weapons have naturally been balanced to use these numbers, so you can expect a tactical experience even with armor.

+

Of course, TacRP is fully compatible with Danger Zone Entities' armor, and will use the weapons' AP value for them.

+ +

TTT Support

+

It's a match made in heaven! TacRP will now natively support TTT, and using it is as easy as installing and opening the game. In addition to compatibility, a new set of stats have been made for the mode in particular so that the weapons' balance is similar to vanilla TTT.
+Several guns are added as buyable equipment, like the launchers, anti-materiel snipers, and the riot shield.
+There's also two buyable equipment, an attachment bench (if you restrict customization), and an ammo box.

+ +

Rebalance... Again

+

I just can't help it. The default "Tactical" balance mode has been rewritten to be less slow (considering reduced movement speed in most roleplay modes), and higher-tiered weapons are less overpowered, albeit still stronger on average.
+With the addition of new weapons to round out the set, some category changes have also been made.

+ +

Hints

+

You can do so many things with these guns it's hard to keep track. Now, you don't have to!
+Hints will show up reminding you what contextually relevant buttons you can press. It'll also nag you to bind your grenade input.

+ +

Stats Menu Rework

+

The stats menu finally has a scroll bar to scroll through all those numbers. They also now show the change in difference as a percentage.

+ +

Held Grenades

+

Relevant to TTT, you can now pull out grenades as weapons. Unfortunately I was not able to get custom viewmodels for them, so bear with the CSS placeholders for a bit.
+Also, if you have Danger Zone Entities, you can quickthrow and switch to Bump Mines.

+
+

Anyways, I hope all of you have as much fun with the Interops weapons as speedonerd and I had making them. It's a pleasure seeing these old models come to life again.
+Be sure to check out the Interops workshop page as well as Danger Zone Entities.

+ +

Yours,
+8Z, the gmod hyperealist

+]], + }, + { + Title = "Update 8", + Type = "Patch Notes", + Date = "2023-05-15", + Author = "8Z", + Major = false, + Summary = "Post release fixes because it keeps happening.", + Contents = [[ +

No update survives first contact. As is tradition, things immediately break when I push updates. Even if the version number is my lucky number.

+

In addition to some fixes, I've also added a few small but cool things, like the ability to pick up DNA from dropped magazines in TTT, and gas effects.

+
    +
  • Added: Overlay, motion blur and color modification when affected by gas directly (but not lingering damage). +
  • Added: (TTT) Dropped magazines (including grenade spoons) contain DNA of the dropper and no longer fade. +
  • Added: (TTT) Customization Bench allows you to freely customize (equivalent to free atts). +
  • Added: (TTT) Picking up a grenade will make the player select it as a quicknade if the current selection isn't valid. +
  • Balance: Sticky sounds (Breaching Charges, C4) and quickthrow sounds are less loud. +
  • Balance: (TTT) Breaching Charges have a bigger blast radius, slightly lower damage and much quieter beeps. +
  • Balance: Gas grenades no longer do damage when exploding. +
  • Balance: Flashbang blast and effect radius now 728 (previously 512 and 1024). +
  • Changed: (TTT) Random attachments will try to avoid duplicates and respect InvAtt. +
  • Fixed: Various issues with the Medkit. +
  • Fixed: Bodygroup related errors. +
  • Fixed: Quicknade and grenade weapons do not respect ttt_no_nade_throw_during_prep. +
  • Fixed: Gas grenade doesn't cause sway and coughing when below health threshold. +
  • Fixed: Attachments do not sync properly if weapon is out of PVS initially (such as spawning in TTT). +
+

I'm sure I've missed some more, so send any issues or suggestions my way!

+

Yours,
+8Z, the gmod hyperealist

]], + }, + { + Title = "Update 9", + Type = "Patch Notes", + Date = "2023-05-16", + Author = "8Z", + Major = false, + Summary = "The biggest changes tend to slip under the hood.", + Contents = [[ +

A humble update. Aside from a lot of internal changes, some systems have been reworked and a new ammo attachment for pistols is added.

+ +

Jamming Rework

+

Arctic added jamming for fun and neither of us really knew what to do with it. We didn't want to just slap it on all guns just because, but there also seemed to not be any good reason to put it anywhere. +In lieu of a better idea, I've elected to give the surplus attachments some dubious upside that at the very least may be interesting.

+ +

Furthermore, the chance to jam is now based on ammo type, with attachments adding a "Jam Factor" instead of modifying percentage chances directly. This should help prevent pistols, shotguns and snipers from jamming too infrequently compared to automatic high fire rate guns.

+ + +

Free Aim Rework

+

Free Aim was one of those features that nobody liked - admittedly, not even myself. But I couldn't not give it another chance.

+
    +
  • All weapons now have their own Free Aim Angle, typically between 2-9 degrees. Generally speaking, pistols, SMGs and stockless weapons have little free aim. +
  • Overall free aim value is much lower. Most rifles will have 4-5 degrees, and only cumbersome weapons like snipers and machine guns will have high values. +
  • Free aim point of aim moves across the screen slower. +
+ +

Mr. Worldwide

+

Ported over the localization system used in ARC9. As of now, there are no other languages, so if you are interested in contributing your language, please contact me or make a pull request!
+An additional benefit is that a lot of the attachments now use consistent wording due to switching to phrases.

+ +

Changelist

+
    +
  • Major Change: Free Aim rework. +
  • Major Change: Jamming rework. +
  • Added: Surplus Ammo and Surplus Bolt gained unique behavior. They still jam though. +
  • Added: Steel Core Rounds for pistols, basically the oppposite of Hollowpoints. +
  • Added: Localization system. Currently only Chinese Simplified is available. +
  • Rebalance: (Arcade) Most pistols are no longer lethal on headshot without Hollowpoints/Skullsplitter. +
  • Rebalance: Dual MTX has reduced spread and fires in runaway burst. +
  • Rebalance: Riot Shield can now be penetrated with sufficient Material Penetration (around 30). +
  • Rebalance: TMJ now gives flat penetration increase of 8" (generally lower than before). +
  • Rebalance: AS-50 and Hecate II got more penetration. +
  • Rebalance: Auto-Burst trigger no longer has downsides (it's now easy to get optimal ROF with improved buffering). +
  • Rebalance: Sniper rifles have lower hip spread penalty (to compensate for free aim). +
  • Rebalance: Juice up UMP45 with less recoil kick and spread. +
  • Rebalance: Burst trigger on semi-auto guns increase RPM further and no longer increase recoil spread. Burst is now runaway and semi mode is removed. +
  • Rebalance: Competition/Weighted trigger no longer change recoil kick. +
  • Changed: Adjusted thresholds and criteria for some ratings. +
  • Changed: Click buffering for burst fire now works during the whole burst and not just after the last shot. +
  • Changed: Renamed Penetration to Material Penetration, and Armor Piercing to Armor Penetration. +
  • Changed: Some descriptions for stats and ratings. +
  • Fixed: When both Free Aim and Sway are on, point of aim shakes intensely while recovering from recoil. +
  • Fixed: Safety desync causing gun able to shoot while apparently in safety. +
]], + }, + { + Title = "Update 10", + Type = "Patch Notes", + Date = "2023-06-14", + Author = "8Z", + Major = false, + Summary = "Minor performance and PVE Balance.", + Contents = [[ + +

Changelist

+
    +
  • Major Change: PvE balance mode. +
  • Changed: ConVars are now cached, which should increase performance. +
  • Changed: Hold type while aiming. +
  • Changed: Lasers now draw only a dot and not the beam. They are also more consistent. +
]], + }, + { + Title = "Update 11", + Type = "Patch Notes", + Date = "2023-06-27", + Author = "8Z", + Major = false, + Summary = "New customization menu, and more suppressors!", + Contents = [[ +

The customization menu is getting a little cramped with all the tactical customization going on. The new UI should be way more concise, hiding all the options behind a click but still allowing you to see all your current choices at a glance.

+ +

If you're not a fan, the legacy menu option can be toggled back with the checkbox in the bottom left.

+ +

Besides that, there's also 2 more suppressor options to make the Muzzle slot a little less lonely. Also, they're suppressors and not silencers now.

+ +

Changelist

+
    +
  • Major Change: Customization menu rework. +
  • Added: Support for DynaMetR's Customizable HL2 HUD. Secondary ammo will display the type and amount of the currently selected quicknade, and ammo pickup will also display correctly. +
  • Added: Compact Suppressor. Reduces spread, lower penalty compared to the Silencer (now Tactical Suppressor). +
  • Added: Weighted Suppressor. Increases range and reduces recoil, but worsens ADS and sprint time. +
  • Added: Akimbo variants for some attachments. +
  • Added: Icons for PS90 and HK94 (thanks speedonerd). +
  • Rebalance: Tactical Suppressor also reduces recoil spread penalty (5%). +
  • Rebalance: Akimbo trigger has weaker RPM penalty (10%). +
  • Rebalance: Reduced recoil spread on several assault rifles. +
  • Rebalance: Reduced recoil on the RPG-7 drastically. +
  • Changed: Adjusted shooting volume on some weapons for consistency. Pistol caliber weapons have ~110dB, carbines ~120dB, and the rest are ~130dB. +
  • Changed: Stat differences are now rounded to the whole percent (due to rounding errors in the values, the extra precision is meaningless). +
  • Fixed: Adjusted suppressor positions on some guns. +
  • Fixed: Akimbo pistols use last shot animation correctly. +
  • Fixed: Pre-emptively fix an upcoming issue in the June 2023 update related to loading server files in Singleplayer. +
]], + }, + { + Title = "Update 12", + Type = "Patch Notes", + Date = "2023-07-18", + Author = "8Z", + Major = false, + Summary = "AR Expansion, and recoil system overhaul.", + Contents = [[ + +

The AR Expansion Pack is out now! Get 5 new guns by clicking this (not a rickroll):

+ +

This update introduces changes to how recoil and spread works. Before, most "Recoil" stats were actually affecting spread, and Recoil Kick was the only actual recoil stat. These stats are now renamed to Bloom, and their behavior has changed as well.

+ +

Under the new system, instead of your gun magically becoming less accurate as you shoot, the recoil becomes stronger instead. If you wish to revert to old behavior, untick "Bloom Modifies Recoil" in the Mechanics setting page.

+ +

Recoil Stability is a new stat that makes recoil go more straight up. Previously, recoil could go anywhere within a 180 degree half circle - this is equivalent to 0% stability now. Certain guns, especially high recoil rifles, have increased stability to make their recoil manageable.

+ +

The intent of the overall change is to improve the viability of high recoil weapons and to make medium to long range combat more viable on non-sniper rifles.

+ +

Changelist

+
    +
  • Major Change: Alternate Recoil Mode. +
  • Major Change: Recoil Stability stat. +
  • Added: All weapons gain a 0.01 moving spread penalty. +
  • Added: You can now bind +tacrp_melee to any button to quick melee. E+LMB will still work. +
  • Added: ConVar to allow firing while sprinting, in case you're a boomer shooter enthuasist. +
  • Rebalance: Fold Stock and Adjust Stock reduce ADS and sprint time by 0.08/0.04 seconds instead of a percentage, and recoil is reduced. +
  • Changed: Some "Recoil" stats are now named Bloom. Their functionality remains identical. +
  • Fixed: Certain lower resolutions unable to display attachments properly. +
]], + }, + { + Title = "Update 13", + Type = "Patch Notes", + Date = "2023-07-24", + Author = "8Z", + Major = false, + Summary = "Small compatability additions and balance tweaks.", + Contents = [[ + +

The Special Delivery expansion is out - get 21 extra special weapons for TacRP today!:

+ +

This is a small update with mostly minor changes.

+ +

Changelist

+
    +
  • Added: Breach Charges will attract VJ L4D Common Infected like a pipe bomb. Why? Why not? +
  • Added: Expansion addons can now add shell ejections (used by Special Delivery for the Super X3) +
  • Rebalance: Buffed DSA-58. +
  • Rebalance: Some adjustments to akimbo attachments. +
  • Rebalance: Buffed burst trigger fire rate. +
  • Changed: When using Sliding Ability's slide, you aren't considered sprinting and can fire your gun. +
  • Fixed: Auto-Burst doesn't work with non-runaway bursts. +
]], + }, + { + Title = "Update 14", + Type = "Patch Notes", + Date = "2023-07-25", + Author = "8Z", + Major = false, + Summary = "Melee update.", + Contents = [[ + +

Even if you run out of bullets, you'll never run out of bullets. Brute Force, a melee weapon expansion for TacRP, is out now!:

+ +

Accompanying Brute Force is a list of updates to melee weapons, as well as a new perk.

+ +

Changelist

+
    +
  • Added: Melee Special attachment Scout. Gain a double jump and bat balls into others to slow them. +
  • Added: Melee bashes and delays have a small delay before the hit connects. (0.25s for bash, 0.15s for knives) +
  • Rebalance: Charge special use same meter as other mobility specials and charge the same rate. +
  • Rebalance: Melee weapons now have varied throwing damage, velocity, and mobility recharge time. +
  • Rebalance: Ninja can backhop regardless of whether the smoke bomb is ready. +
  • Rebalance: Knife throw headshot detection is slightly more generous. +
  • Rebalance: Knife throw does double damage on stunned/slowed targets, and no longer on mid-air targets. +
  • Rebalance: Slow total duration increased from 2s to 3s, fade start time unchanged. +
]], + }, + { + Title = "Update 15", + Type = "Patch Notes", + Date = "2023-08-06", + Author = "8Z", + Major = false, + Summary = "", + Contents = [[ +

With so many updates on the list, I'm starting to run out of quirky hooks to put in front of patch notes. Ah well, nobody reads this anyways.

+ +

A Twenty-Three Millimeter Shotgun

+

The old model was the first re-rigged weapon for TacRP and had quite a few flaws. It's been replaced with the model from FA:S2, complete with actual sounds, visible shells during reloading and cycling, and a slight buff.

+ +

In addition, the Zvezda flash shell has a new variant which only loads the top shell as a flashbang, for more practical applications.

+ +

Stat Menu Expansion

+

Number lovers rejoice, for there's been a heap of changes to the stat menu. The whole thing has been reorganized and spacers are added to group stats into (mostly) sensible categories. Furthermore, a couple of missing stats have now been added.

+ +

Smooth like Tactical Butter

+

Previously, leaving sprint was an awkward action that prevented you from doing what you want: aiming down sights. Not anymore! You can start aiming as soon as you let go of sprint; however, this will be slower than normal and you still cannot fire for the Sprint to Fire duration.

+ +

Shotguns can now also immediately cancel reloading to fire, making them more useful in a pinch.

+ +

Grenade View Models

+

Speedonerd very kindly rigged the grenades to CSS viewmodels so you don't have to pretend you're using our grenades anymore.

+ +
+ +

Changelist

+
    +
  • Major Change: Stat menu expansion. +
  • Major Change: New KS-23 Model and sounds (from FA:S 2). New KS-23 attachment: Top-loaded Zvezda shells. +
  • Added: Muzzle Light feature: brief flash of projected light when firing, like L4D2. Only visible on self, can be disabled. +
  • Added: You can now aim the weapon while exiting sprint state, albeit at a slower speed. +
  • Added: Shotgun reloading can be instantly cancelled (configurable, default on). +
  • Added: Akimbo pistols now use the correct worldmodel attachments and drop magazines from both hands. +
  • Added: Better effects system to allow for double ejects and muzzles on certain expansion guns. +
  • Added: Client config "Aiming Stops Sprinting" (default on). +
  • Added: "Lower Weapon While Airborne" config (default off), because bhopping is not tactical. +
  • Added: You can now reset all client configs in the menu with a button. +
  • Added: Grenade viewmodels rigged to CS:S animations by speedonerd. +
  • Added: Holding overhand throw for pulled out grenades increases throw velocity by up to 50% over 0.25s. +
  • Added: RPM Burst and Semi multipliers. +
  • Added: Flashbangs will now stun human type NPCs. +
  • Rebalance: Triggers now affect Recoil Stability. +
  • Rebalance: Burst trigger has increased RPM for burst fire mode only. +
  • Rebalance: Scope glint is slightly more visible and starts being visible closer. +
  • Rebalance: Frag grenades, C4s and Breach Charges do more damage. +
  • Rebalance: Buffed AMD-65, FB Beryl 96, HK94 and PS90. +
  • Rebalance: Mag Extender slows reload by 3-5% (weapon type dependent). +
  • Rebalance: Adjusted burst fire weapons to accomdate for RPM Burst multipliers. +
  • Changed: When "Allow Reload While Sprinting" is disabled, sprinting cancels reload (unless ammo is already replenished). +
  • Changed: Shuffled around some options in Mechanics menu. +
  • Changed: Adjusted thermite effects and removed initial explosion. Thermite now ignites non-players. +
  • Changed: Adjusted some rating rules. +
  • Fixed: Error when trying to pick up weapon in DarkRP caused by incorrect hook usage. +
  • Fixed: Should fix situation where removed smokes still block targetid. +
  • Fixed: Firing buffer does not trigger when weapon is locked in animation (shotgun reload finishing). +
  • Fixed: Weapon withs >100% AP deal more damage than normal and restores armor. +
]], + }, + { + Title = "Update 16", + Type = "Patch Notes", + Date = "2023-12-02", + Author = "8Z", + Major = true, + Summary = "A flurry of new features: Trivia, Recoil patterns, Bipods, Breath holding.", + Contents = [[ +

What is Tactical may never die. Is that how the saying goes? +
Here's an update that's been cooking for a while. We've got a bit of everything: cool new features that enhance gameplay, visual reworks to some old models, performance update, it's all here.

+ +

Shots 1-5: Clearly Missed

+

Not enough skill issue in your Garry's Mod weapon addon? Now, instead of complaining about random recoil spread, you're gonna have to complain about something else, cause it's no longer random.

+ +

Similar to CS:GO (I guess it's now CS2)'s implementation, weapon recoil will follow a consistent pattern, typically going upwards until reaching a "suppression threshold", after which recoil becomes mostly horizontal.

+ +

Best used with "Bloom Modifies Recoil". Guns that have low RPM (<100), are burst fire, or shoot entities do not use patterns.

+ +

I'll take "Useless Firearm Knowledge" for 500, Alex

+

Want to know more about all the guns we have? No? Damn, cause we wasted a lot of hours putting in trivia and credits for

+ +

Every. Single. Gun. There's like over a hundred. Yeah.

+ +

There's also now a credits page, so you know who to thank for the pretty models in all of our packs!

+ +

For additional realism, some guns have their names changed. The exception is the TGS-12, which we're almost certain Gooseman made up and is just a Bekas decked with aftermarket parts.

+ +

Pod means legs, Bi means two

+

The previous iteration of bipod was like pretending to use a bipod. It was not very fun. So I went ahead and implemented a proper bipod system.

+ +

Now, you can deploy bipod on any surface by aiming down sights when the prompt shows up. While deployed, you can enjoy significantly reduced bloom gain and recoil kick, zero sway and free aim, and reduced hip spread (based on peeking bonus).

+ +

AS50, MG4, Hecate and SA58 come with a bipod in the base pack. They are now innate and no longer take up your Accessory slot (the Bipod attachment is deprecated). Plus, the bipod will unfold while deployed and fold up when it's not. Neat!

+ +

Steady, Steady...

+

Sway is kinda annoying when you want to snipe. Now, you can hold your breath to stop swaying for a little bit, like most modern games.

+ +

The amount of breath meter drained is dependent on Scoped Sway. Iron sights and low magnification optics are easier to hold still with. Careful not to hold too long, as running low or out will increase sway significantly!

+ +

Facelifts

+

The Glock 17 model has been swapped out for the cooler one by Twinke Masta. +
The Hecate II now has its original scope and bipod models, and the animations have been improved a little.

+ +
+ +

Changelist

+
    +
  • Major Change: Trivia and Credits tab. +
  • Major Change: Bipod. +
  • Major Change: Hold Breath. +
  • Major Change: Recoil Patterns. +
  • Added: Glock 17 model replaced. +
  • Added: Hecate II model touch up - added scope and bipod from original model. +
  • Added: Bulk attachment files. This should speed up loading noticeably. +
  • Changed: Spread gauge is no longer FOV dependent. Outer (thin) circle is ~17MOA per tick and inner (thick) circle is ~83MOA per tick. +
  • Changed: Phrasing changes for Speed, Bloom and Duration stats. +
  • Changed: MAC-10 firing sounds. +
  • Changed: Adjusted some weapon names and descriptions. +
  • Changed: Magnum Pistols are now its own category. +
  • Changed: Newsletter now appears when a TacRP weapon is drawn, instead of on load. +
  • Rebalance: Significantly increased bloom recovery and reduced maximum bloom on most guns. +
  • Rebalance: Significantly reduced range on all weapons. +
  • Rebalance: Snipers now use Magnum Ammo (357) instead of Sniper Ammo (SniperPenetratedRound). +
  • Rebalance: Surplus attachments now reduce recoil. +
  • Rebalance: Mag Extenders slows down reload slightly. +
  • Rebalance: Buffs to Beryl and M1A. +
  • Rebalance: Sphinx 2000 is now a 3-round burst Machine Pistol (again). +
  • Rebalance: Increased P90 recoil. +
  • Fixed: Some worldmodels not having a folded stock bodygroup. +
  • Fixed: Firemode resetting when detaching attachment. +
  • Fixed: Minor alignment issues with optics. +
  • Fixed: Some TTT2 related issues. +
  • Fixed: Runaway Burst interactions with "Lower Weapon While Sprinting" off. +
]], + }, + { + Title = "U16 Expansions", + Type = "Patch Notes", + Date = "2023-12-02", + Author = "8Z", + Major = true, + Summary = "New expansion, new weapons, and many model touchups among the expanded TacRP family.", + Contents = [[ +

As TacRP grows richer in features, the expansion addons also receive additions and touchups. Previously only documented in the steam page, the patch notes for them are now being added to the newsletter for ease of access.

+ +
+ +

Iron Curtain (New!)

+

+The Soviet/AK themed expansion starring 9 weapons, including well-known AK variants, weapons descended from the Kalashnikov design and the SVD (which isn't actually an AK, sorry for the false advertising). +
    +
  • AEK-971 +
  • AK-12 +
  • AK-74 +
  • AK-74U +
  • AN-94 +
  • Galil ARM (LMG configuration) +
  • Galil Sniper (a.k.a. The Galatz) +
  • RK-95 +
  • SVD (we're really playing it loose with this one) +
+ +

InterOps

+ +
    +
  • Model fixes to MX4/CX4, Remington 870 SPMM, and M14 SOPMOD. +
  • Reworked SCAR-H model with fixes and new animations. +
  • Replaced Desert Eagle textures. +
+ +

Special Delivery

+ +
    +
  • New weapons: M1A1 Thompson, M1 Carbine, T/C G2 Contender. +
  • Replaced model for PP-19 Bizon. +
  • G3SG1 changed to G3A3, replaced model and reworked stats. +
  • Groza is now integrally suppressed, and has a different set of animations. +
  • Groza and FAMAS now have a rail mount for scopes. +
  • Added unique firing sound for De Lisle Carbine. +
  • Animation touch-ups across the board. Some animations will look significantly less jank now. +
+ +

ArmaLite Revolution

+ +
    +
  • New weapon: AR-15 Compact. +
+ +

Brute Force

+ +
    +
  • New weapons: The shovel and frying pan, for all you slapstick comedy fans. +
+ +]], + }, + { + Title = "Update 16 Hotfix", + Type = "Patch Notes", + Date = "2023-12-05", + Author = "8Z", + Major = false, + Summary = "i broke autosave sry", + Contents = [[ +

Changelist

+
    +
  • Fixed: Autosave desyncing in singleplayer, causing attachments to not show up. +
  • Fixed: Autosave not checking for slot validity or take from attachment inventory. +
  • Fixed: AUG procedural irons issue. +
  • Fixed: Exiting sprint disables shooting even if "Lower Weapon While Sprinting" is off. +
  • Changed: Quick melee can now be held down to fire immediately or continue meleeing. +
]], + }, + { + Title = "Update 17", + Type = "Patch Notes", + Date = "2023-12-24", + Author = "speedonerd", + Major = true, + Summary = "A holly jolly update", + Contents = [[ +

This is speedonerd speaking! I'm one of the other people working on this mess, mainly in the asset department (models, sounds, animations). +

We've got quite the update here, one that brought on more things than we initially anticipated adding, including a bunch of new configuration options and a total melee system rework. + +

They have guns...lots of guns...

+

NPC support now has a new dropdown menu, sorted by weapon type so you can easily find whatever weapon you're looking for. Within each category you can specify a certain weapon or have NPCs pick a random weapon from each quality tier. +

And for those who enjoy chaos, we've included an option for NPCs to spawn with any weapon randomly. Enjoy a random rebel with no sense of self-preservation facerocketing you! + +

drop me avp plz

+

There's now a dedicated button in the customization menu to drop your current weapon, with dropped weapons able to be picked up with your 'use' key. +

This synergizes with weapon slot restrictions, allowing you to swap weapons with any gun you find lying around with the press of a button. + +

It's like wading through molasses.

+

In case you don't like how a certain balancing scheme punishes movement, there are now convars for each of the movement penalties that let you turn them off individually. +

They can all be found under Movement in the Mechanics tab. + +

What happened to my freakin' car?!

+

Fighting against vehicles with TacRP will now be a lot more interesting thanks to some hackery and workarounds to make infantry-to-vehicle combat functional. +

TacRP now works with LVS' armor penetration system, allowing gunfire, launched grenades and rockets to pierce vehicle armor. +

Combine Hunter Choppers, infamous for their hardcoded immunity to everything but airboat gun damage, can now take damage from direct hits with rockets. + +

Now this...is a knife.

+

TacRP's melee system has been very superficial for a while now with little statistical difference between weapons (which is especially prominent if you have Brute Force installed) and were more goofy than they were actually viable. +

As such, we've remade the melee system with a host of new features, including actual weapon stats and a new attachment slot: Boost, which acts as a perk slot with various passive benefits from simple stat boosts to unique effects like regaining health. +

The base pack's knives and every weapon from Brute Force have been updated to use this new system. + +

You kids today don't know how to circlestrafe!

+

Do you yearn for the good old days, when shooters were fun because they didn't have depth or good game balance? I did for a brief period, and it compelled me to quickly integrate a new balance mode that removed ironsights and tightened up weapon spread. +

It didn't really pan out how I had hoped—TacRP is built around so many modern mechanics and systems that it's hard to make it "old school" without stripping away nearly all of them. +

Regardless, what I ended up with before abandoning it is still accessible via the console for preservation's sake, if you're interested. + +


+ +

Changelist

+
    +
  • Major Change: Melee attributes & boost perks. +
  • Major Change: NPC weapon dropdown with random roll option. +
  • Major Change: Weapon dropping and swapping. TacRP weapons will now require pressing +USE to pick up - this can be disabled. +
  • Added: New icons for Deadeye and Grenadier. +
  • Added: Unfinished Old School balance mode. Enter "tacrp_balance 4" in console to access. +
  • Added: CVars for toggling specific movement penalties. +
  • Added: Restored concealment accessory. Hides weapon when holstered. +
  • Added: Bipod hint will now draw with backup HUD. +
  • Added: 40mm grenade casing eject effect. +
  • Added: Dual MTX tactical attachment slot, attached to the right gun. +
  • Changed: Gunfire can now penetrate LVS vehicles. +
  • Changed: RPG rockets can now penetrate LVS vehicles and damage Combine Helicopter NPCs on a direct hit. +
  • Changed: RPG-7 & M320 moved to separate launchers weapon category. +
  • Changed: Added "Special Weapon" category for unorthodox weapons that don't fit any other category (currently only used by expansion weapons). +
  • Changed: New trigger category for 4-position FCG weapons (weapons with auto, burst & semi modes). Blocks the Forced Reset and Burst triggers. +
  • Rebalance: RPG rockets do more direct impact damage. +
  • Rebalance: Deadeye grants increased zoom distance (again). +
  • Fixed: Customization slot icon flash behavior causing errors. +
  • Fixed: Corner cam display being the incorrect aspect ratio. +
  • Fixed: Multiplayer attachment bodygroup networking issue. +
  • Fixed: Multiplayer hold breath meter jitteryness. +
  • Fixed: Multiplayer autosave playing deploy animation twice. +
+ +
+ +

For those wanting to follow TacRP, as well as anything else I or 8Z are working on, we now have our own Discord server! Join GMod Hyperrealism today! +

+ +]], + }, + { + Title = "U17 Expansions", + Type = "Patch Notes", + Date = "2023-12-24", + Author = "speedonerd", + Major = false, + Summary = "Touchups to existing packs.", + Contents = [[ +

Changelist

+
    +
  • Added: (ArmaLite Revolution) M16A1 refurbished bolt. +
  • Added: (Special Delivery) Double Defense ejected shells sound. +
  • Fixed: (Brute Force) M9 Bayonet misaligned worldmodel. +
  • Rebalance: (Brute Force) All weapons have been updated to make use of the new melee stat system. +
  • Changed: (InterOps) Replaced Five-Seven model and firing sounds. +
  • Changed: (InterOps) Colt SMG restricted to using Colt 3x20 scope only. +
  • Changed: (InterOps) China Lake revamp - Touched up animations, now ejects spent casings. +
  • Changed: (InterOps) Automag animation touch-ups. +
  • Changed: (Special Delivery) Gyrojet revamp - New firing sound, new animations, now in the "Special Weapon" category along with the Fat Mac. +
  • Changed: (Special Delivery) WA2000 animation improvements. +
  • Changed: (Special Delivery) Dual Berettas, USPs, 1911s & Deagles tactical attachment slot. +
]], + }, + { + Title = "Update 17 Hotfix", + Type = "Patch Notes", + Date = "2023-12-31", + Author = "8Z", + Major = false, + Summary = "Quick fix for NPC issue.", + Contents = [[ +

Changelist

+
    +
  • Fixed: Error when spawning NPC with default weapon. +
]], + }, + { + Title = "Update 18", + Type = "Patch Notes", + Date = "2024-01-07", + Author = "speedonerd", + Major = false, + Summary = "Small additions.", + Contents = [[ +

Small update with a few fixes to compliment the new expansion. + +

Changelist

+
    +
  • Added: News button in customization page. +
  • Added: Bind for HL2 flashlight when a tactical attachment is fitted (ALT + F). +
  • Added: Random any weapon option for NPCs. +
  • Changed: (TTT) Snipers now use 357 ammo instead of Deagle ammo. +
  • Changed: (TTT) New TTT2 sprint functionality. +
  • Fixed: (TTT) Quicknade convar errors in TTT2. +
  • Fixed: (TTT) General TTT2 networking improvements and fixes. +
]], + }, + { + Title = "Heavy Duty Release", + Type = "Expansion Release", + Date = "2024-01-07", + Author = "speedonerd", + Major = true, + Summary = "New expansion and a new weapon for InterOps", + Contents = [[ +

Yet another new expansion. Originally intended to release in time for Christmas, we missed the mark a bit but hope you'll enjoy it all the same. We've also pushed some extra goodies to existing expansions. + +

Heavy Duty (New!)

+

+The heavy weaponry expansion, sporting 10 new weapons including autoshotties, guided rockets and an 80's cinema icon. +
    +
  • AMT Hardballer +
  • Dual Hardballers +
  • CheyTac M200 Intervention +
  • Franchi SPAS-12 +
  • HK XM25 CDTE +
  • Holland & Holland Double Rifle +
  • Mk 153 SMAW +
  • Pancor Jackhammer +
  • SSK Industries .950 JDJ "Fat Mac" +
  • Taurus Raging Judge +
+ +

InterOps

+

+
    +
  • New weapon: SIG SG 550-2 SP. A semi auto-only, non-sniper SIG in the Sporter Carbines category. +
+ +

ArmaLite Revolution

+ +
    +
  • New weapon: KAC SR-25 EMR. A high-power, low-capacity AR-based marksman rifle. +
+ +
+ +

Changelist

+
    +
  • Changed: (InterOps) Updated XM8 LMG model. Added bipod and now has correct barrel length. +
  • Changed: (InterOps) SG 550-1 burst mode changed to automatic, redone stats. +
  • Changed: (Brute Force) Replaced crowbar and pipe wrench models with new ones from CSO2. +
  • Changed: (Iron Curtain) SVD stat tweaks. +
+ +
+ +

Have a tactical 2024, gamers!]], + }, + { + Title = "Update 19", + Type = "Patch Notes", + Date = "2024-04-01", + Author = "speedonerd", + Major = false, + Summary = "Not (quite) an April Fools update.", + Contents = [[ +

Hey all! It's been a while since our last Workshop release and things over on the dev front have started to slow, owing to some real-life obligations from both me and 8Z. That's not to say we don't have anything new to share, as we've cooked up a few things in the past 2 months that we'll be dropping with this update. + +

What is this, "balance," you speak of?

+

TacRP's various gameplay and balance settings have been, frankly, a total mess for a while. As we continued adding new tweakable options the menus started getting very cluttered, and as we continued adding new weapons we found that having to create 4 whole sets of stats for every single one became insanely arduous. +

A balance revamp was well overdue so we've gone and done just that. Instead of the Arcade-Tactical-TTT-PvE modes we had before that mainly changed TTK speeds and speed penalties, we've reduced this to three new balance modes: Tiered, Untiered and TTT. +

The main difference now is the utilization of the weapon tier system, with Tiered separating weapons into four tiers with higher tiers having better stats, and Untiered eliminating these tiers and making all weapons similar in stats. TTT mode still exists to cater to the specific gameplay flow of the mode. +

Instead of balance schemes controlling speed and handling penalties, things like melee and sighted slowdowns are togglable options independent of the balancing scheme, and damage can now be fine tuned per weapontype to increase or decrease TTK. Sliders for various other things like ADS speed, reload speed and recoil kick have also been added. +

These sliders and certain options that directly affect balance are under the Balance tab while mechanics that exist independent of balance scheme are under the Mechanics tab. + +

Dwarves don't use ironsights.

+

Old School mode has been reworked into Old School Scopes; a balance mechanic independent of any one scheme. Like before, it disables ironsights and tightens hipfire, but spread will now be increased with an optic fitted. The higher an optic's magnification, the higher your spread penalty. + +

More options, more fun!

+

Some new minor gameplay options have been added for extra spice. +

'Dump ammo in magazines' does exactly what it says on the tin: any ammo left in your magazine when you reload will be dropped onto the ground. It is not lost, however, as dropped magazines can be picked up by the player to recover the lost ammo. (This mechanic was not inspired by any particular democratic game and was entirely of our own invention) +

'Lower weapon while not aiming' forces weapons to always be lowered unless you're aiming them, similar to DarkRP. + +

Animator? I barely know her!

+

Normally I wouldn't write something for an animation overhaul but this one is a special case. The K98 Sporter from InterOps has always been controversial as it's an iconic old-world bolt action that's been bastardized to use a box mag. The real reason for this was TacInt had no suitable animation set for a weapon like a Mauser rifle and we had to settle for what we had. +

That was until recently when a friend of ours approached us with new animations for the rifle; the Hunting Rifle animations from Cry of Fear. As such, the K98 has been revamped with these new animations and no longer has that fugly Sako mag protruding out from it. Thanks Lazarus! +

Oh yeah, the Double Defense from Special Delivery received new animations too, so that's neat. + +


+ +

Changelog

+
    +
  • Added: (Base) HK45 Compact in Elite tier. +
  • Added: (Base) New balance scheme overhaul. +
  • Changed: (Base) RPG-7 projectiles have slightly shorter safety fuse. +
  • Changed: (InterOps) New Kar 98k animations from Cry of Fear. (Thanks Lazarus!) +
  • Changed: (InterOps) Rescaled FNC model. +
  • Changed: (InterOps) Revised Honey Badger model and sounds. +
  • Changed: (InterOps) New STAR-15 sounds. (Thanks rzenith!) +
  • Changed: (Special Delivery) New Double Defense animations. +
  • Changed: (Brute Force) New baseball bat animations. (Thanks Lazarus!) +
  • Fixed: (Heavy Metal) Fixed laser beam origin on Hardballer. +
  • Fixed: Some other stuff probably not worth documenting. +]], + }, + { + Title = "Update 21", + Type = "Patch Notes", + Date = "2024-06-12", + Author = "speedonerd, 8Z", + Major = true, + Summary = "Expanded attachments and networking improvements", + Contents = [[ +

    Howdy all! It's a little crazy to think about, but May 14th (14 - 5 - 2024) marks exactly one year since TacRP InterOps launched on the Workshop. Since then TacRP's weapon selection has gone from 70-something weapons to over 200, and a myriad of new features have been added. +

    It's been quite a ride, and we appreciate all the support and kind words we've continued to receive throughout this year of development. +

    We've been cooking up a pretty substantial update the past month or so with not one, but two massive content drops being developed concurrently. Unfortunately, they're still yet to be released, but we still have a decent-sized update to satiate you in the meantime. + +

    (8Z's Note: This news was written back when we were still not that far off from the anniversary date. Alas...) + +


    + +

    "They were actually known cheaters who were using a lag switch."

    +

    The base has received updates to networking that should finally eliminate pesky jitter and inconsistencies at high ping. +

    Movement code prediction has been almost completely rewritten and the breath holding mechanic should now actually work in multiplayer. Viewmodel jitter should also hopefully be a thing of the past. + +

    You Tactical?

    +

    The pool of tactical attachments has been expanded with a couple of new options and improvements to existing options. +

      +
    • Emergency Bullet: For when you're in a pinch and just need that one extra round that'll do 'em in. Press your tactical button to quickly chamber-load a single round. +
    • 2x Zoom: Gives every optic (even ironsights!) access to a 2x zoom level. Low power optics can now zoom in further and high-power sniper optics can now zoom out. +
    • Thermal Imager: You got this thing off AliExpress for about a dollar and it works, I guess. When peaking, a thermal overlay will highlight targets in red, though the rest of your vision is blurred. +
    • Combo Module: A 2-for-1 deal, now you can have a laser and a flashlight together. Does not blind targets like the standalone flashlight. +
    +

    Corner Cam has also been given a new night mode that can be toggled for low-light environments. + +

    Buzz off!

    +

    Added the FIM-92 Stinger to the base pack. This lock-on only launcher is suitable for shooting down aircraft, but can lock onto other things too.

    + +

    Sean's got the shotgun.

    +

    A slew of new ammotypes for shotguns have arrived. +

      +
    • Minishells: Smaller shells with less power, but you can fit more of them into your magazine. +
    • Dragon's Breath: Specialty shells that can ignite targets. +
    • Frag Shells: Explosive shells that deal splash damage in a small radius. +
    • Breaching Shells: Trade off damage and range for the ability to knock down doors. +
    • Buckshot Roulette: The shells enter the chamber in an unknown order... +
    + +

    Brake hard and fast.

    +

    Three new muzzle attachments, finally giving most weapons some attachable muzzle brakes. +

      +
    • Agressor Brake: Increases firing move speed by redirecting gasses away from the shooter. +
    • Breaching Brake: Sharp and pointy edges increase melee damage. +
    • Concussive Brake: Significantly reduces recoil kick at the cost of firing move speed. +
    + +

    Nanomachines, son.

    +

    A new grenade, the Medi-Smoke, has been added. This can of purple technology and probably drugs will restore your health as long as you stand in it!

    + +
    + +

    You might notice a few weapons have received model updates. These have been byproducts of the coming content drops, at least one of which should be expected to launch some time before the end of the summer.

    +

    Thank you all for your continued support, and stay tactical.

    + +
    + +

    Changelog

    +
      +
    • Added: (Base) FIM-92 Stinger with 4 ammo attachments. +
    • Added: (Base) Four new tactical attachments: Emergency Bullet, 2x Zoom, Thermal Imager, Combo Module. +
    • Added: (Base) Five new shotgun ammo types: Minishells, Dragon's Breath, Frag Shells, Breaching Shells, Buckshot Roulette. +
    • Added: (Base) Corner-Cam now has a night-vision mode, toggle with Tactical keybind. +
    • Added: (Base) Jammed weapons requires pressing reload key to unjam by default. +
    • Added: (Base) JHP ammo for rifles. +
    • Added: (Base) Healing Grenades. +
    • Added: (Base) Non-lethal grenades now have a distinctive trail. +
    • Added: (Base) Polish localization and backend l18n improvements. +
    • Changed: (Base) Reordered and redefined weapon subcategories. Anti-materiel rifles has been dissolved and Precision Rifles has been split up into Battle Rifles and Marksman Rifles. +
    • Changed: (All packs) Updated a few descriptions and added/changed some quotes. +
    • Changed: (InterOps) New RPK model. +
    • Changed: (Special Delivery) New PKM model. +
    • Fixed: (Base) Significant networking improvements across the board. +
    • Fixed: (InterOps) Fixed twisted wrists on SG 550 rifles (at last). +
    • Rebalance: (All packs) A lot of balance tweaks across the board. + +
    +]], + }, + { + Title = "Update 21 HotFix", + Type = "Patch Notes", + Date = "2024-06-14", + Author = "8Z", + Major = false, + Summary = "Some hotfixes", + Contents = [[ + +

    Changelog

    +
      +
    • Fixed: Value tier added NPC weapon selection. +
    • Fixed: Updated localization strings. +
    • Fixed: Fixed Stinger missile in multiplayer. +
    • Fixed: "Lower weapon when not aiming" option related fixes. +
    • Fixed: Sprint/Sight progress is now properly predicted in multiplayer. +
    • Fixed: Grenade material carrying over to other viewmodels if stripped (such as throwing last grenade). +
    +]], + }, + { + Title = "Update 22 / ExoOps Release", + Type = "Patch Notes", + Date = "2024-06-20", + Author = "8Z", + Major = true, + Summary = "New expansion release!", + Contents = [[ + +

    ExoOps (New!)

    +

    +The third major expansion pack featuring 38 weapons of all kinds. +
      +
    • Jericho 941 +
    • HK P7 +
    • Walther P99 +
    • SIG P210 +
    • S&W Mk 22 Mod. 0 "Hush Puppy" +
    • Star Megastar +
    • Browning Hi-Power +
    • Chiappa Rhino 20DS +
    • S&W Model 29 "Satan" +
    • Dueling Demons (Dual M29 Satan) +
    • Mauser M712 Schnellfeuer +
    • Beretta 93R +
    • Calico M950A +
    • SIG MPX +
    • HK MP5SD +
    • HK MP5K +
    • Magpul Masada +
    • Bushmaster ACR +
    • Colt Model 733 +
    • Enfield L85A2 +
    • HK G36C +
    • Beretta AR70 (AR70/223) +
    • FN F2000 +
    • SCAR-L +
    • AUG A1 +
    • Benelli M3 Super 90 +
    • HK CAWS +
    • SPAS-15 +
    • MG42 +
    • M60 +
    • Winchester M1873 +
    • FN FAL (Israeli Pattern) +
    • SIG Stgw 57 (SG 510) +
    • Howa Type 64 +
    • FN FAL (StG 58) +
    • SIG MCX SPEAR +
    • Steyr Scout +
    • HK PSG-1 +
    + +

    Changelog

    +
      +
    • Added: (Special Delivery) New Weapon: FAMAS G2. +
    • Added: Refurbished Bolt attachment. Only available for weapons that innately jam, will remove jam chance but decrease RPM and accuracy. +
    • Changed: Tweaked some stat and rating displays. +
    • Fixed: LVS damage type interactions. +
    • Rebalance: Minor changes to some weapons and attachments. +
    +]], + }, + { + Title = "Update 23", + Type = "Patch Notes", + Date = "2024-07-15", + Author = "8Z", + Major = true, + Summary = "Civvie guns and balance tweaks.", + Contents = [[ + +

    Citizens aren't supposed to have guns... or are they?

    +

    Some sporter weapons join the gang, including two returning members.

    + +The Arsenal SAM7SF is a new addition, kitbashed by Arctic and based on the AMD-65.
    +The Diemaco AR-15 and HK HK243 got a facelift by Fesiug and is now available in the spawnmenu again.
    +Additionally, a civilian/non-lethal version of the M320 has been added. Use it to fire beanbags at people.
    + +

    DEMOTION

    +

    Some guns have lowered grades to better round out representation across all the tiers. +Notably, some weapons in the base pack are now Value grade, a new lowest grade that was previously only in expansion weapons.

    + +

    (im)Practical Projectiles

    +

    The notorious RPG-7 shovel has now become reality. Launch dull working tools at your enemies!

    + +

    Grenade launchers can also now launch Medi-Smoke grenades.

    + +

    Changelist

    +
      +
    • Major Addition: New Weapons: Arsenal SAM7SF (Sporter AK/AMD), Diemaco AR-15, HK HK243, HK M320LE. +
    • Added: RPG-7 Shovel ammo, 40mm Medi-Smoke ammo. +
    • Added: Thirdperson holstering animation. +
    • Added: Grenade ammo entities will give their respective weapons on pickup. +
    • Added: Expanded Ammo Types option for a spicier ammo economy (ammo entities not included). +
    • Changed: Minor cosmetic and compression improvements to some weapon textures. +
    • Changed: Thrown melee projectiles will now disappear when hitting skybox. +
    • Fixed: Spread becoming a straight line when aiming very high or very low. +
    • Fixed: Various DarkRP spawned_weapon related interactions. +
    • Fixed: Quirky networking bug. +
    • Fixed: Stinger collision issue. +
    • Rebalance: MAC-10 -> MAC-11 (smaller caliber), and is now Value grade. +
    • Rebalance: Mini-14 is now Value grade. +
    • Rebalance: HK UMP45 is now Consumer grade. +
    • Rebalance: Buffed Heavy Bolt, Weighted Trigger, Tactical Bolt, Block. +
    • Rebalance: Ninja will now consume full meter on divekick. +
    • Rebalance: Automatic shotguns no longer use recoil patterns. +
    + +

    Expansion Changelist

    +
      +
    • Changed: LR-300 remodel, now uses the real handguard. +
    • Changed: Some melee weapons now use Lazarus's two-handed animations. +
    • Fixed: Rhino 20DS being too shiny. +
    • Rebalance: AR70, FAL, LR-300, FNC and MX4 are now Consumer tier. +
    • Rebalance: Groza is now Security tier. +
    • Rebalance: Nerfed M16A1 and Thompson. +
    +]], + }, + { + Title = "Update 24", + Type = "Patch Notes", + Date = "2024-08-03", + Author = "8Z", + Major = false, + Summary = "Fixes and more configurations.", + Contents = [[ + +

    Changelist

    +
      +
    • Added: ConVars for tweaking Medkit and Riot Shield behavior, in Equipment tab. +
    • Added: ConVars to disable door busting. +
    • Added: ConVar multipliers for Headshot Damage and Melee Weapon Damage. +
    • Added: USP 9mm conversion attachment (using HL2 sounds). +
    • Added: Quotes for various weapons. +
    • Added: Additional localization. +
    • Added: Credits for Medkit and Riot Shield animations. (thanks Arqu!) +
    • Changed: Reworked missile logic for the Stinger and SMAW. +
    • Changed: Melee Techinques are now free attachments. +
    • Changed: Removed chance to fail an unjam attempt. +
    • Changed: Disabled shotgun reversing their starting animation on non-empty reload. +
    • Changed: Rangefinder now displays range as a percentage on the point of aim. +
    • Changed: Thermal Imager renamed, no longer blurs when peeking. +
    • Changed: SAPHE can now damage Combine Helicopters and LVS vehicles. +
    • Changed: Riot Shield can't block melee attacks when attacking. +
    • Fixed: Improved projectile impact damage consistency. +
    • Fixed: 40mm Healing grenades not healing. +
    • Fixed: RMR being on guns it should not. +
    • Fixed: Missing accessory slot for some akimbos. +
    • Fixed: Missing sling and other accessory attachments on some guns. +
    • Rebalance: Tweaked explosive damage and radius. TTT gamemode no longer reduces explosive damage. +
    • Rebalance: Breaching Brake gives slight increase in recoil stability. +
    • Rebalance: Dual MTX minor damage buff and recoil increase. +
    • Rebalance: P250 minor damage increase. +
    • Rebalance: Grease bolt has more recoil, Tactical Bolt increases more mobility. +
    • Rebalance: Melee Throw now consumes crossbow bolt ammo. +
    • Rebalance: Various nerfs to melee Special attachments and Momentum. +
    + +

    Expansion Changelist

    +
      +
    • Added: SMAW Hummingbird rockets, XM25 flechettes and HEAT grenades. +
    • Changed: Tweaked Kar98k reload behavior. +
    • Changed: Intervention MLG sound now an ammo type. Does what you would expect. +
    • Changed: Improved Groza animations. +
    • Fixed: SMAW projectiles not obeying explosive damage scaling. +
    • Rebalance: Buff PSG-1, Amphibian, P210; nerf SR-25; tweaked Kar98k. +
    +]], + }, + { + Title = "Update 25", + Type = "Patch Notes", + Date = "2024-08-03", + Author = "8Z", + Major = false, + Summary = "Grenade configurations.", + Contents = [[ + +

    Changelist

    +
      +
    • Added: Client Authoratitive Hit Registration, to improve multiplayer hitreg. +
    • Added: ConVars for tweaking attributes of various grenades, in Equipment tab. +
    • Added: ConVar to disable magazine dropping. +
    • Changed: tacrp_hud will no longer disable minimal HUD. +
    • Changed: Thermite grenade and the thermite cloud will now stick to whatever it hits. +
    • Fixed: Flashbang will now properly stun all kinds of NPCs. +
    • Rebalance: Melee block meter consumption on hit is now based on your max health. +
    +]], + }, + { + Title = "Update 25 Patch 1", + Type = "Patch Notes", + Date = "2024-09-08", + Author = "8Z", + Major = false, + Summary = "", + Contents = [[ +

    Changelist

    +
      +
    • Changed: Melee block no longer flinches NPCs (this was causing issues on most SNPCS). +
    • Fixed: Missing casings on various pistols. +
    • Fixed: Client-authoratitive hitreg doing double damage on listen servers. +
    +]], + }, + { + Title = "Update 26", + Type = "Update", + Date = "2024-11-05", + Author = "speedonerd", + Major = false, + Summary = "A new weapon, a fun bonus and lots of visual touch-ups.", + Contents = [[ + +

    Greetings all. Unfortunately the long-awaited post-apocalypse pack still isn't finished, but rest assured we are still working on it and it will see the light of day eventually. +For now, enjoy a small-ish update that consists mostly of visual updates but also contains some fancy new content.

    + +

    SIG SG 510 "Black Shark" One-Off Release

    +

    +

    I made this in an afternoon on a whim; a customized SG 510 visually inspired by Battlefield Hardline's weird hybridized 510 and functionally inspired by the infamous "Rico Special" that was featured on Forgotten Weapons. +Give it a whirl! Apologies in advance for the blindness and potential motion sickness.

    + +

    Changelist:

    +
      +
    • Added: (Base) ConVar to disable quick grenades (but this will make us sad). +
    • Changed: (Base) Altered Stinger lock-on logic to be more performant. +
    • Changed: (All packs) Removed the RMR and/or OKP-7 from weapons where their sight pictures are blocked. +
    • Major Addition: (Heavy Duty) New Weapon: HK21E machine gun. This weapon was present in the addon's files but unfinished for quite a while. +
    + +

    Asset Updates

    +
      +
    • Changed: (Base) SG551 irons are removed when equipping an optic for a better sight picture. +
    • Changed: (Base) Added an optic rail to the Bekas. +
    • Changed: (Base) M320 LE now sports a yellow frame to distinguish it from its lethal counterpart. +
    • Changed: (InterOps) SG550-2 SP irons are removed when equipping an optic, same as the 551. +
    • Changed: (InterOps) Fixed clipping bolt and forend on the Chinalake. +
    • Changed: (Special Delivery) Added optic rails to the MP40 and Thompson. +
    • Changed: (Special Delivery) Revised both FAMAS variants' animations. +
    • Changed: (Special Delivery) Revised Gyrojet animations to be less obstructive when reloading. +
    • Changed: (Heavy Duty) Tweaked Intervention ironsights. +
    • Changed: (Exo Ops) Added an optic rail to the L85. +
    • Changed: (Exo Ops) Revised MCX SPEAR animations. +
    • Changed: (Exo Ops) Revised HK CAWS animations. +
    • Changed: (Exo Ops) Revised Winchester M1873 animations. They should be a tad more lively now. +
    • Fixed: (Exo Ops) Broken bipod bodygroup on MG42. +
    • Fixed: (Exo Ops) PSG-1 bolt not moving far enough back when reloading. +
    • Fixed: (Exo Ops) SG 510 now has its folded ironsights bodygroup (originally absent due to StudioMDL limitations) +
    +]] + }, + { + Title = "Update 27", + Type = "Update", + Date = "2024-11-12", + Author = "8Z", + Major = false, + Summary = "Spawnmenu tidy-up and more bonus guns.", + Contents = [[ + +

    A few misclaneous quality of life improvements, including tier highlighting and sorting in the spawnmenu.

    + +

    Also, over the past few days, speedonerd has been hard at work kitbashing together more custom guns for fun (but no profit). Expect a release announcement soon!

    + +

    Changelist:

    +
      +
    • Added: The spawnmenu will now highlight TacRP weapons' tiers when using tiered balance. +
    • Added: The spawnmenu sorts by tier first, then by name. Tooltips also show weapon description. +
    • Added: ConVars for toggling spawnmenu behavior. +
    • Added: "Stylish" perk, for mid-air trickshots and quickscopes. +
    • Added: AS-50 and Hecate has a bigger muzzle flash. +
    • Added: "Exotic" tier, reserved for bonus and custom weapons. +
    • Fixed: USP no longer has vaseline smeared all over it. +
    • Fixed: Casing ejections shouldn't be missing on any gun anymore (let us know). +
    • Rebalance: Nerfed barrel attachments and Subsonic ammo; minor tweaks to some perks. +
    +]] + }, + { + Title = "Update 28", + Type = "Update", + Date = "2024-11-29", + Author = "8Z", + Major = false, + Summary = "Small additions for bonus weapons.", + Contents = [[ + +

    Bonus Weapon Releases

    +

    A set of bonus one-off weapons has been released! These were customized to the tastes of some of our friends and have some cool features not present on base TacRP weapons. Give them a peek!

    + +

    MP5/10 "Zero Eight": A 10mm MP5 with Swordfish kit, prototype foregrip, and 50-round drum.

    +

    Hécate "Kingslayer": Hécate with suppressor, rangefinder scope, and fragmentation rounds.

    +

    USP "The Governor": USP Elite with compensator and high power rounds.

    +

    Desert Eagle "Arbiter": Fully automatic Desert Eagle carbine. Yes, really.

    +

    SR-25 "Symbiosis": Suppressed and rechambered in .338 Lapua, with an adjustable scope and kill tracker.

    + +

    Changelist:

    +
      +
    • Added: Some features to support bonus weapons. +
    • Fixed: Client authoriatitive damage not setting DMG_BULLET. +
    • Rebalance: Buffed SG551 and Hecate's bipod. +
    +]] + }, + { + Title = "Update 29 / Scavenger's Spoils Release", + Type = "Update", + Date = "2024-12-25", + Author = "8Z", + Major = true, + Summary = "New expansion release!", + Contents = [[ +

    Scavenger's Spoils (New!)

    +

    +The fourth major expansion, featuring 42 weapons themed around zombies, the apocalypse and a post-Soviet aesthetic. All you have out here is whatever you can get your hands on, comrade.
    + +

    Changelist:

    +
      +
    • Added: Various features to support Scavenger's Spoils. +
    • Added: Shotgun pellets now fire hull traces to make shots more consistent, like the HL2 shotgun. As a consequence, they are no longer affected by limb multipliers. +
    • Added: Reworked bullpup animations by speedonerd for the FAMAS and Groza. +
    • Fixed: Client authoriatitive damage not setting DMG_BULLET. +
    • Rebalance: Emergency Bullet will clear a jam when loading. +
    • Rebalance: Marksman Trigger now removes jam chance entirely. +
    • Rebalance: All shotguns have increased spread, reduced damage and significantly reduced range in keeping with the hull size change. I promise they're still good. +
    + +]] + }, + { + Title = "Update 30 / Localization Patch", + Type = "Update", + Date = "2025-01-10", + Author = "Moka, 8Z, & speedonerd", + Major = false, + Summary = "Full Localization Support", + Contents = [[ + +

    Full Localization

    +

    TacRP has been updated and received full localization support. This means that weapons (names, descriptions, trivia & credits), attachments (names, descriptions, pros & cons) and overall base content (stats, categories, etc.) can now be translated to any language Garry's Mod supports.

    +

    TacRP supports English, and it has been partially supporting Chinese and Polish for a long time now. Additionally, Swedish has been added as a translated language, courtesy of Moka.

    +

    All weapon packs that had localization files included in them (Special Delivery, Iron Curtain, InterOps, etc.) will be updated to have them removed, as all those strings are now included in the base.

    + +

    Changelist:

    +
      +
    • Added: (Base) Added a setting to enable Aim Assistance (Client / Server) +
    • Added: (Base) Added a setting to lower the mouse sensitivity when aiming (Client) +
    • Added: (Base) Added a setting to enable infinite ammo for launchers (Client) +
    • Added: (Base) Added a setting for the CS Gas and Flashbangs to affect players and/or NPCs (Equipment) +
    • Added: (Base) Added a setting to toggle dynamic lights (Server) +
    • Added: (Base) Spawnmenu subcategories weapon names and descriptions now use localized strings +
    • Changed: (Base) Tweaked the stats on the "Ratshot Rounds" attachment +
    • Changed: (Base) The Stinger can now lock onto Glide vehicles +
    • Changed: (Base) Fixed that weapons were not added to the Spawnmenu category correctly +
    • Changed: (Base) Attachment descriptions can now be two or more lines long and will scale accordingly +
    • Changed: (Base) NPCs now react to nearby grenades +
    • Changed: (Base) NPCs line of sight will be blocked by smoke grenades +
    • Changed: (Base) Altered the behavior of the Quicknade Radial Menu; grenade quantity is only shown when you have more than one, and the quantity is displayed below the grenade name; it is also localized +
    • Changed: (InterOps) Kar 98K given minor tweaks to its quickthrow animations +
    • Changed: (ExoOps) The HK CAWS has brand new animations +
    • Changed: (Scavenger's Spoils) Fixed that the StG 44 had missing credits, and that the scopes were misaligned +
    • Changed: (Scavenger's Spoils) Lowered the RPM on the M202 FLASH +
    • Changed: (Scavenger's Spoils) SMLE given minor tweaks to its quickthrow animations. +
    • The Mosin Nagant hasn't received these tweaks yet due to technical difficulties. Expect this to follow in a hotfix. +
    + +]] + }, + { + Title = "Update 30 Hotfix 1", + Type = "Patch Notes", + Date = "2025-01-11", + Author = "speedonerd", + Major = false, + Summary = "", + Contents = [[ + +

    Changelist:

    +
      +
    • Changed: (Base) Tweaked aim assist behavior. Certain weapons (namely launchers) will now be unaffected by the aim assist setting. +
    • Changed: (Multiple packs) Several localization errors fixed. +
    + +]] + }, + { + Title = "Update 30 Hotfix 2", + Type = "Patch Notes", + Date = "2025-01-13", + Author = "speedonerd", + Major = false, + Summary = "", + Contents = [[ + +

    Changelist:

    +
      +
    • Changed: (All packs) More localization tweaks. +
    • Changed: (Base) Description quotes are now always size 6 font, regardless of length. +
    • Fixed: (Base) Fixed viewmodels drawing on top of screen overlays (thanks Chen!). +
    • Fixed: (InterOps + Scavenger's Spoils) Fixed blindfire animation weirdness with toploader bolt-actions. +
    +]] + }, + { + Title = "Update 31", + Type = "Patch Notes", + Date = "2025-02-15", + Author = "8Z", + Major = false, + Summary = "Minor fixes and features.", + Contents = [[ +

    Changelist:

    +
      +
    • Added: Inverse Peeking client ConVar to make peeking the default aiming option. +
    • Changed: Shotgun pellets are now 50% hull traces and 50% non-hull traces, like the HL2 shotgun. +
    • Changed: Wallbang damage is significantly increased. +
    • Changed: Explosive Damage on guns no longer disable penetration. +
    • Changed: Developer-related visuals now require superadmin permissions to be visible. +
    • Fixed: Holosight scaling for unusual aspect ratios. +
    • Fixed: Client-authoratitive damage behaving weirdly in singleplayer. +
    • Fixed: Blindfire bone manipulation not working in singleplayer. +
    • Rebalance: Nerfed shotgun armor shredding. +
    +]] + }, + { + Title = "Update 32", + Type = "Patch Notes", + Date = "2025-05-20", + Author = "chen, speedonerd", + Major = false, + Summary = "Minor additions and fixes.", + Contents = [[ +

    Changelist:

    +
      +
    • Added: Global 10% recoil reduction for burst/semi fire modes. +
    • Added: (Base) Tactical Trigger, reducing recoil on burst/semi for weapons with multiple firemodes. +
    • Added: Synchronized third person animations for reloading, grenading, jam-clearing, and bashing. +
    • Added: Quotes for weapons that previously didnt have any. +
    • Changed: (Base) Ultima Ratio overhauled. Lower damage but higher magazine capacity. +
    • Changed: (Special Delivery) Gyrojet third person reload animation from "pistol" to "revolver" to accommodate for the update. +
    • Changed: (Exo Ops) SPAS-15 firing sound and minor animation tweaks. +
    • Changed: Kingslayer renamed to Kingbreaker. +
    • Changed: Black Shark model, texture and sound updates (trans rights). +
    • Changed: Altered some existing weapon quotes. +
    • Fixed: An invisible riot shield sticking to the player after dropping one. +
    • Fixed: Homing launchers breaking HUDs if the player hasn't been in first person. +
    • Fixed: "developer" convar causing a harmless error in multiplayer. +
    • Fixed: Some ammo types not having correctly localized names in customization menu because of incorrect captialization. +
    • Fixed: (Iron Curtain) Buggy firing animation on AEK-971. +
    • Rebalance: Buffed Dual Stage Trigger. +
    +]] + }, + { + Title = "Update 33", + Type = "Patch Notes", + Date = "2025-09-11", + Author = "speedonerd", + Major = true, + Summary = "Kept you waiting, huh?", + Contents = [[ + +

    A small-ish update with a couple of bugfixes and minor changes. This update cycle saw a lot of content development in the background but most of it was unfortunately scrapped due to a lack of motivation. While there's a possibility that some of it may resurface in the future, don't count on it. + +

    Every squad will be given one rifle. To share.

    +

    What hasn't been scrapped is a complete reanimation of the top-loaded bolt-action rifles (Kar 98k, Mosin-Nagant, Lee-Enfield) done by yours truly (speedonerd, in case you forgot), and a new attachment to allow these rifles to use stripper clips!

    + +

    Changelist:

    +
      +
    • Added: (Base) Support for custom ammotypes and quicknades. +
    • Added: (Base) ConVar to disable weapon jamming. +
    • Added: (Base) Melee weapons can have custom lunge sounds defined. +
    • Added: (Base) Stripper clip attachment for top-loaded bolt-action rifles (Kar98k, Mosin, Lee-Enfield). +
    • Fixed: (Base) Melee specials breaking if auto reload was enabled. +
    • Changed: (Base) Emergency Bullet and 2x Zoom are always free. +
    • Changed: (Base) Hecate can no longer melee bash. +
    • Changed: (Base) Shotgun extended magazine attachment category split into two separate categories to distinguish between tube magazines and detachable magazines. +
    • Changed: (InterOps + Scavenger's Spoils) New bespoke animations for top-loaded bolt-action rifles (Kar98k, Mosin, Lee-Enfield). +
    • Changed: (Heavy Duty) Small SPAS-12 model edit and unique semi-auto reload. +
    • Changed: (Exo Ops) Steyr Scout moved to Marksman Rifles. +
    • Changed: (Exo Ops) SPAS-15 animation overhaul. +
    +]] + }, + { + Title = "Update 33 Patch 1", + Type = "Patch Notes", + Date = "2025-09-30", + Author = "8Z", + Major = false, + Summary = "Hotfix for sound issues.", + Contents = [[ +

    Changelist:

    +
      +
    • Fixed: Missing sounds due to FireAnimationEvent behavior change in beta branch (thanks to chen for the fix). +
    +]] + }, + { + Title = "Update 34", + Type = "Patch Notes", + Date = "2025-12-08", + Author = "speedonerd", + Major = false, + Summary = "Minor fixes and changes.", + Contents = [[ +

    Changelist:

    +
      +
    • Added: (Scav. Spoils) Added Mad Minute bolt for Lee-Enfield. +
    • Changed: (Base) Improved flare detection for tracking projectiles and autoaim. +
    • Changed: (Base) Altered lowered irons dot to be a small two-prong reticle. +
    • Changed: (Base) Glock 17 model fixes. +
    • Fixed: Fixed explosive damage sometimes having a null attacker. +
    • Fixed: Fixed weapons that inherit their ammotype from another weapon not being able to pick up ammo in TTT. +
    +]] + }, +} +TacRP.NewsRead = TacRP.NewsRead or {} +TacRP.NewsFirstLoad = false +TacRP.NewsLoaded = nil +TacRP.NewsResult = "Uninitialized" +TacRP.NewsPopup = nil + +function TacRP.SaveReadData() + local tbl = {} + for k, v in pairs(TacRP.NewsRead) do + table.insert(tbl, k) + end + file.Write(TacRP.PresetPath .. "tacrp_news.txt", util.TableToJSON(tbl, false)) +end + +local date_pattern = "(%d+)-(%d+)-(%d+)" +function TacRP.LoadReadData() + local tbl = util.JSONToTable(file.Read(TacRP.PresetPath .. "tacrp_news.txt", "DATA") or "") or {} + TacRP.NewsRead = {} + + if #tbl == 0 then + TacRP.NewsFirstLoad = true + end + + for _, i in ipairs(tbl) do + TacRP.NewsRead[i] = true + end +end + +function TacRP.RemoveNewsPanel() + if TacRP.NewsPanel then + TacRP.NewsPanel:Close() + TacRP.NewsPanel = nil + end +end + +local function loadlocalandsort() + TacRP.NewsLoaded = TacRP.NewsLoaded or {} + + for k, v in pairs(TacRP.News) do + v.Key = k + table.insert(TacRP.NewsLoaded, v) + end + + table.sort(TacRP.NewsLoaded, function(a, b) + local a_y, a_m, a_d = string.match(a.Date, date_pattern) + local b_y, b_m, b_d = string.match(b.Date, date_pattern) + if !a_y or !a_m or !a_d then return true end + if !b_y or !b_y or !b_y then return false end + if (a_y > b_y) or (a_y == b_y and a_m > b_m) or (a_y == b_y and a_m == b_m and a_d > b_d) then + return true + end + return false + end) +end + +function TacRP.FetchNews(callback) + if TacRP.NewsLoaded then + if callback then + callback() + end + return + end + TacRP.NewsResult = "Fetching news..." + http.Fetch("https://theonly8z.github.io/tactical_realist/article_list", function(body, length, headers, code) + local _, body_start = string.find(body, "", nil, true) + local body_end = string.find(body, "", nil, true) + if body_start and body_end then + local json = string.sub(body, body_start + 1, body_end - 1) + local loaded = (util.JSONToTable(json) or {}).news + if not loaded then + loadlocalandsort() + if callback then + callback() + end + TacRP.NewsResult = "Fetch failed: " .. #TacRP.News .. " local (cannot parse)" + return + end + TacRP.NewsLoaded = {} + for i, v in pairs(loaded or {}) do + v.Key = v.Link + table.insert(TacRP.NewsLoaded, v) + end + loadlocalandsort() + + TacRP.NewsResult = "Fetch OK: " .. #loaded .. " remote, " .. #TacRP.News .. " local" + + if callback then + callback() + end + else + loadlocalandsort() + if callback then + callback() + end + TacRP.NewsResult = "Fetch failed: " .. #TacRP.News .. " local (cannot parse)" + end + end, function(message) + loadlocalandsort() + TacRP.NewsResult = "Fetch failed: " .. #TacRP.News .. " local (" .. message .. ")" + if callback then + callback() + end + end) +end +local fetchnews = TacRP.FetchNews + +local html_head = [[ + +]] + +local html_failed = [[ +

    Failed to load this page.

    +

    Link: {URL}

    +

    Error: {ERROR}

    +]] + +TacRP.NewsPanel = TacRP.NewsPanel or nil +function TacRP.CreateNewsPanel(open) + TacRP.RemoveNewsPanel() + + if TacRP.NewsFirstLoad then + TacRP.SaveReadData() + TacRP.NewsFirstLoad = false + end + + local is_workshop = nil + local last_update = nil + steamworks.FileInfo(2588031232, function(result) + last_update = os.date("%Y-%m-%d %H:%M", result.updated) + is_workshop = steamworks.ShouldMountAddon(2588031232) + end) + + local w_news, h_news = TacRP.SS(384), TacRP.SS(256) + + local h_bottom = TacRP.SS(8) + local h_title = TacRP.SS(24) + local w_list = TacRP.SS(96) + local h_entry = TacRP.SS(18) + + TacRP.NewsPanel = vgui.Create("DFrame") + TacRP.NewsPanel:SetTitle("") + TacRP.NewsPanel:SetSize(w_news, h_news) + TacRP.NewsPanel:ShowCloseButton(false) + TacRP.NewsPanel:MakePopup() + TacRP.NewsPanel:Center() + TacRP.NewsPanel:SetDeleteOnClose(true) + TacRP.NewsPanel:SetDraggable(false) + function TacRP.NewsPanel.Paint(self, w, h) + end + + local close = vgui.Create("DButton", TacRP.NewsPanel) + close:SetSize(TacRP.SS(8), TacRP.SS(8)) + close:SetPos(w_news - TacRP.SS(10), 0) + close:SetText("") + function close.Paint(self, w, h) + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(self:IsHovered() and !self:IsDown(), self:IsDown()) + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + draw.SimpleText("X", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + function close.DoClick() + TacRP.RemoveNewsPanel() + end + + local cvarbox = vgui.Create("DCheckBox", TacRP.NewsPanel) + cvarbox:SetSize(TacRP.SS(8), TacRP.SS(8)) + cvarbox:SetPos(TacRP.SS(2), 0) + cvarbox:SetText("") + cvarbox:SetConVar("tacrp_news_majoronly") + function cvarbox.Paint(self, w, h) + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(self:IsHovered(), self:GetChecked()) + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + if self:GetChecked() then + draw.SimpleText("O", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + local cvarback = vgui.Create("DPanel", TacRP.NewsPanel) + cvarback:SetPos(TacRP.SS(11), 0) + cvarback:SetSize(TacRP.SS(110), TacRP.SS(8)) + function cvarback.Paint(self, w, h) + surface.SetDrawColor(0, 0, 0, 100) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + draw.SimpleText("Only notify on major news/updates", "TacRP_Myriad_Pro_8", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local y_title = TacRP.SS(2) + local title = vgui.Create("DPanel", TacRP.NewsPanel) + title:SetSize(w_news, h_title) + title:Dock(TOP) + function title.Paint(self, w, h) + local c_bg = TacRP.GetPanelColor("bg2") + surface.SetDrawColor(c_bg) + surface.DrawRect(0, y_title, w, h - y_title) + TacRP.DrawCorneredBox(0, y_title, w, h - y_title) + draw.SimpleText("Tactical RP Newsletter", "TacRP_Myriad_Pro_20", w / 2, TacRP.SS(2), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + + local bottom = vgui.Create("DPanel", TacRP.NewsPanel) + bottom:SetSize(w_news, h_bottom) + bottom:Dock(BOTTOM) + function bottom.Paint(self, w, h) + local c_bg = TacRP.GetPanelColor("bg2") + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + local text + if is_workshop == true then + text = "Update " .. tostring(TacRP.Version) .. " | " .. "Workshop: " .. last_update + elseif is_workshop == false then + text = "Update " .. tostring(TacRP.Version) .. " | " .. "Workshop: Not Mounted" + end + + if text then + draw.SimpleText(text, "TacRP_Myriad_Pro_6", TacRP.SS(4), h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local branch = tostring(BRANCH) + if branch == "unknown" then branch = "main" end + + draw.SimpleText("GMod Branch: " .. branch, "TacRP_Myriad_Pro_6", w - TacRP.SS(4), h / 2, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + + draw.SimpleText(TacRP.NewsResult, "TacRP_Myriad_Pro_6", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local newspanel = vgui.Create("DScrollPanel", TacRP.NewsPanel) + newspanel:SetSize(w_list, h_news - h_title) + newspanel:DockMargin(0, TacRP.SS(2), 0, TacRP.SS(2)) + newspanel:Dock(LEFT) + function newspanel.Paint(self, w, h) + local c_bg = TacRP.GetPanelColor("bg") + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + end + local sbar = newspanel:GetVBar() + function sbar:Paint(w, h) + end + function sbar.btnUp:Paint(w, h) + local c_bg, c_txt = TacRP.GetPanelColor("bg2", self:IsHovered()), TacRP.GetPanelColor("text", self:IsHovered()) + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + draw.SimpleText("↑", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + function sbar.btnDown:Paint(w, h) + local c_bg, c_txt = TacRP.GetPanelColor("bg2", self:IsHovered()), TacRP.GetPanelColor("text", self:IsHovered()) surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + draw.SimpleText("↓", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + function sbar.btnGrip:Paint(w, h) + local c_bg, c_cnr = TacRP.GetPanelColor("bg2", self:IsHovered()), TacRP.GetPanelColor("corner", self:IsHovered()) surface.SetDrawColor(c_bg) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + end + + local newslist = vgui.Create("DIconLayout", newspanel) + newslist:Dock(FILL) + newslist:SetLayoutDir(TOP) + newslist:SetSpaceY(TacRP.SS(2)) + + local buttons = {} + function TacRP.NewsPanel.PopulateNews(self2) + newslist:Clear() + for i, v in ipairs(TacRP.NewsLoaded) do + local data = v + local btn = newslist:Add("DButton") + if data.Summary then + btn.TextLines = TacRP.MultiLineText(data.Summary, w_list - TacRP.SS(4), "TacRP_Myriad_Pro_6") + btn:SetSize(w_list, h_entry + #btn.TextLines * TacRP.SS(6.5)) + else + btn:SetSize(w_list, h_entry) + end + btn:SetText("") + btn.Index = i + function btn.DoClick(self) + TacRP.NewsPanel.Page:SetPageIndex(i) + end + function btn.Paint(self3, w, h) + local down = btn.Index == TacRP.NewsPanel.Index + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(self3:IsHovered() and !down, down) + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + + if !TacRP.NewsRead[data.Key] then + local c_glow = TacRP.GetPanelColor("text_glow", self3:IsHovered() and !down, down) + draw.SimpleText(data.Title, "TacRP_Myriad_Pro_8_Glow", TacRP.SS(2), TacRP.SS(2), c_glow, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + draw.SimpleText(data.Title, "TacRP_Myriad_Pro_8", TacRP.SS(2), TacRP.SS(2), c_txt, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(data.Date or "Unknown Date", "TacRP_Myriad_Pro_6", w - TacRP.SS(2) - 12, TacRP.SS(9.5), c_txt, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText((data.Type or "Article") .. ((data.Link or data.Hyperlink) and " (Web)" or ""), "TacRP_Myriad_Pro_6", TacRP.SS(2), TacRP.SS(9.5), c_txt, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + if btn.TextLines then + for j, text in ipairs(btn.TextLines) do + draw.SimpleText(text, "TacRP_Myriad_Pro_6", TacRP.SS(2), TacRP.SS(16) + (j - 1) * TacRP.SS(6.5), c_txt, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + end + end + buttons[i] = btn + end + newslist:SizeToChildren(false, true) + end + TacRP.NewsPanel:PopulateNews() + + local page = vgui.Create("DPanel", TacRP.NewsPanel) + TacRP.NewsPanel.Page = page + page:Dock(FILL) + page:DockMargin(TacRP.SS(2), TacRP.SS(2), 0, TacRP.SS(2)) + page:InvalidateParent(true) + + function page:SetPageIndex(i) + + self:Clear() + TacRP.NewsPanel.Index = i + local data = TacRP.NewsLoaded[TacRP.NewsPanel.Index or -1] + local ind = data.Key + + if !TacRP.NewsRead[ind] then + timer.Remove("tacrp_news") + timer.Create("tacrp_news", 0.5, 1, function() + if TacRP.NewsPanel and TacRP.NewsPanel.Index == i then + TacRP.NewsRead[ind] = true + TacRP.SaveReadData() + end + end) + end + + local pagelink = data.Hyperlink or data.Link + if pagelink then + self:SetSize(w_news - w_list - TacRP.SS(2), h_news - h_title - h_bottom - TacRP.SS(16)) + + local topbar = vgui.Create("DPanel", self) + topbar:Dock(TOP) + topbar:SetTall(TacRP.SS(8)) + function topbar.Paint(self2, w, h) + end + + local homebutton = vgui.Create("DButton", topbar) + homebutton:SetText("") + homebutton:Dock(LEFT) + homebutton:SetSize(TacRP.SS(16), TacRP.SS(8)) + homebutton:DockMargin(TacRP.SS(2), 0, TacRP.SS(2), 0) + homebutton:SetMouseInputEnabled(true) + function homebutton.Paint(self2, w, h) + local hover = self2:IsHovered() + local c_bg, c_txt = TacRP.GetPanelColor("bg", hover), TacRP.GetPanelColor("text", hover) + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, TacRP.GetPanelColor("corner", hover)) + draw.SimpleText("HOME", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + function homebutton.DoClick(self2) + homebutton.Page:OpenURL(pagelink) + end + + local linkbutton = vgui.Create("DButton", topbar) + if BRANCH == "unknown" then + linkbutton:SetText("") + linkbutton:Dock(FILL) + linkbutton:SetMouseInputEnabled(true) + linkbutton.Hyperlink = pagelink + function linkbutton.Paint(self2, w, h) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, Color(0, 0, 0, 255)) + local c = Color(50, 50, 255) + if self2:IsHovered() then + c = Color(100, 100, 255) + end + draw.SimpleText(self2.Hyperlink, "TacRP_Myriad_Pro_6", w / 2, h / 2, c, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + DisableClipping(true) + draw.SimpleText("Embedded browers do not work on your branch of GMod.", "TacRP_Myriad_Pro_8", w / 2, h + TacRP.SS(96), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("Click here to open the tab in the overlay.", "TacRP_Myriad_Pro_8", w / 2, h + TacRP.SS(106), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + DisableClipping(false) + end + function linkbutton.DoClick(self2) + gui.OpenURL(self2.Hyperlink) + end + else + linkbutton:SetText("") + linkbutton:Dock(FILL) + linkbutton:SetMouseInputEnabled(true) + linkbutton.Hyperlink = pagelink + function linkbutton.Paint(self2, w, h) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, Color(0, 0, 0, 255)) + local c = Color(50, 50, 255) + if self2:IsHovered() then + c = Color(100, 100, 255) + end + draw.SimpleText(self2.Hyperlink, "TacRP_Myriad_Pro_6", w / 2, h / 2, c, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + DisableClipping(true) + draw.SimpleText("If the page does not load, click the link at the top to open externally.", "TacRP_Myriad_Pro_8", w / 2, h + TacRP.SS(96), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + DisableClipping(false) + end + function linkbutton.DoClick(self2) + gui.OpenURL(self2.Hyperlink) + end + end + + + if BRANCH != "unknown" then + local html = vgui.Create("DHTML", self) + html:Dock(FILL) + html:OpenURL(pagelink) + function html.OnBeginLoadingDocument(self2, url) + linkbutton.Hyperlink = url + end + homebutton.Page = html + else + local html = vgui.Create("DButton", self) + html:SetText("") + html:Dock(FILL) + linkbutton.Hyperlink = pagelink + function html.Paint(self2) end + function html.DoClick(self2) + gui.OpenURL(linkbutton.Hyperlink) + end + end + else + local c_txt = TacRP.GetPanelColor("text") + + local page_title = vgui.Create("DLabel", self) + page_title:SetFont("TacRP_Myriad_Pro_20") + page_title:SetTextColor(c_txt) + page_title:SetText(data.Title) + page_title:SizeToContents() + page_title:DockMargin(TacRP.SS(4), TacRP.SS(2), 0, 0) + page_title:Dock(TOP) + + local page_subtitle = vgui.Create("DPanel", self) + page_subtitle:DockMargin(TacRP.SS(4), 0, TacRP.SS(4), TacRP.SS(2)) + page_subtitle:Dock(TOP) + page_subtitle:SetTall(TacRP.SS(14)) + page_subtitle.Paint = function(self2, w, h) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawLine(0, h - 1, w, h - 1) + end + + local page_type = vgui.Create("DLabel", page_subtitle) + page_type:SetFont("TacRP_Myriad_Pro_12") + page_type:SetTextColor(c_txt) + page_type:SetText((data.Type or "Article") .. (data.Author and (" • " .. data.Author) or "")) + page_type:SizeToContents() + page_type:Dock(LEFT) + + local page_date = vgui.Create("DLabel", page_subtitle) + page_date:SetFont("TacRP_Myriad_Pro_12") + page_date:SetTextColor(c_txt) + page_date:SetText(data.Date or "Unknown Date") + page_date:SizeToContents() + page_date:Dock(RIGHT) + + local html = vgui.Create("DHTML", self) + html:Dock(FILL) + local url = data.ContentSource --or data.Link + if url then + http.Fetch(url, function(body, length, headers, code) + local article_start = string.find(body, "]+>", nil, true) + local _, article_end = string.find(body, "", nil, true) + if article_start and article_end then + body = string.sub(body, article_start, article_end) + html:SetHTML(html_head .. "\n" .. body) + else + html:SetHTML(body) + end + end, function(message) + local body = html_failed + body = string.Replace(body, "{URL}", url) + body = string.Replace(body, "{ERROR}", message) + html:SetHTML(html_head .. "\n" .. body) + end) + else + html:SetHTML(html_head .. "\n" .. data.Contents) + html:SetAllowLua(true) + end + end + + self:InvalidateLayout(true) + + self:SizeToChildren(false, true) + end + + function page:Paint(w, h) + if !TacRP.NewsLoaded then return end + local data = TacRP.NewsLoaded[TacRP.NewsPanel.Index or -1] + if data and data.Hyperlink then return end + + local c_bg = TacRP.GetPanelColor("bg2") + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + end + + local chosen = buttons[isnumber(open) and open or 1] + if chosen then + chosen:DoClick() + end +end + +concommand.Add("tacrp_news", function() + fetchnews(function() + local ind, major + for i, v in ipairs(TacRP.NewsLoaded) do + if !TacRP.NewsRead[v.Key] and (!ind or !major) then + ind = i + major = v.Major + end + if TacRP.NewsFirstLoad then -- if no read articles exist, mark all as read! + TacRP.NewsRead[v.Key] = true + elseif major then break end + end + + TacRP.CreateNewsPanel(ind) + end) +end) + +concommand.Add("tacrp_news_reset", function() + file.Delete(TacRP.PresetPath .. "tacrp_news.txt") + TacRP.NewsRead = {} + TacRP.NewsLoaded = nil + TacRP.NewsFirstLoad = true + TacRP.NewsPopup = nil + TacRP.NewsResult = "Uninitialized" +end) + +local function notifynews() + TacRP.LoadReadData() + + fetchnews(function() + local ind, major + + for i, v in ipairs(TacRP.NewsLoaded) do + if !TacRP.NewsRead[v.Key] and (!ind or !major) then + ind = i + major = v.Major + end + if TacRP.NewsFirstLoad then -- if no read articles exist, mark all as read! + TacRP.NewsRead[v.Key] = true + elseif major then break end + -- if major then break end + end + + if ind then + if major and !TacRP.ConVars["news_majoronly"]:GetBool() then + TacRP.CreateNewsPanel(ind) + -- elseif major or !TacRP.ConVars["news_majoronly"]:GetBool() then + -- chat.AddText(color_white, "------------- Tactical RP -------------") + -- chat.AddText(color_white, "New " .. string.lower(TacRP.NewsLoaded[ind].Type or "article") .. " released!") + -- chat.AddText(color_white, "Use command 'tacrp_news' or type '/tacrp_news' to view it and suppress this message.") + -- chat.AddText(color_white, "---------------------------------------") + end + end + end) +end +concommand.Add("tacrp_news_check", notifynews) + +-- hook.Add("InitPostEntity", "tacrp_news", function() +-- timer.Simple(5, function() +-- if !TacRP.ConVars["checknews"]:GetBool() then return end +-- notifynews() +-- end) +-- end) + +hook.Add("OnPlayerChat", "tacrp_news", function(ply, txt) + if ply == LocalPlayer() and string.lower(txt) == "/tacrp_news" then + LocalPlayer():ConCommand("tacrp_news") + return true + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_subcategories.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_subcategories.lua new file mode 100644 index 0000000..2758c8f --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_subcategories.lua @@ -0,0 +1,373 @@ +local qualitymat = Material("tacrp/quality_overlay.png", "smooth") + +local quality_to_color = { + ["5Value"] = Color(255, 255, 255, 75), + ["4Consumer"] = Color(200, 255, 200, 75), + ["3Security"] = Color(50, 50, 200, 75), + ["2Operator"] = Color(200, 40, 200, 75), + ["1Elite"] = Color(255, 50, 50, 75), + ["0Exotic"] = Color(225, 225, 50, 75), +} + +local function DoGenericSpawnmenuRightclickMenu(self) + local menu = DermaMenu() + + menu:AddOption("#spawnmenu.menu.copy", function() + SetClipboardText(self:GetSpawnName()) + end):SetIcon("icon16/page_copy.png") + + if isfunction(self.OpenMenuExtra) then + self:OpenMenuExtra(menu) + end + + if not IsValid(self:GetParent()) or not self:GetParent().GetReadOnly or not self:GetParent():GetReadOnly() then + menu:AddSpacer() + + menu:AddOption("#spawnmenu.menu.delete", function() + self:Remove() + hook.Run("SpawnlistContentChanged") + end):SetIcon("icon16/bin_closed.png") + end + + menu:Open() +end + +local function AddWeaponToCategory( propPanel, ent ) + return spawnmenu.CreateContentIcon(ent.ScriptedEntityType or "weapon", propPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = ent.IconOverride or ("entities/" .. ent.ClassName .. ".png"), + admin = ent.AdminOnly + }) +end + +spawnmenu.AddContentType("tacrp_weapon", function(container, obj) + if not obj.material then return end + if not obj.nicename then return end + if not obj.spawnname then return end + local icon = vgui.Create("ContentIcon", container) + icon:SetContentType("weapon") + icon:SetSpawnName(obj.spawnname) + icon:SetName(obj.nicename) + icon:SetMaterial(obj.material) + icon:SetAdminOnly(obj.admin) + icon.Quality = obj.quality + icon:SetColor(Color(135, 206, 250, 255)) + -- Generate a nice tooltip with extra info. + local SWEPinfo = weapons.Get(obj.spawnname) + local toolTip = language.GetPhrase(obj.nicename) + + if not SWEPinfo then + SWEPinfo = list.Get("Weapon")[obj.spawnname] + end + + + if SWEPinfo then + toolTip = toolTip .. "\n" + + if SWEPinfo.Description and SWEPinfo.Description != "" then + toolTip = toolTip .. "\n" .. ( TacRP:GetPhrase("wep." .. SWEPinfo.ClassName .. ".desc") or SWEPinfo.Description ) + end + + if SWEPinfo.Description_Quote and SWEPinfo.Description_Quote != "" then + toolTip = toolTip .. "\n\n" .. ( TacRP:GetPhrase("wep." .. SWEPinfo.ClassName .. ".desc.quote") or SWEPinfo.Description_Quote ) + end + end + + icon:SetTooltip(toolTip) + + local highlight = TacRP.ConVars["spawnmenu_highlight"]:GetBool() and TacRP.UseTiers() + if highlight then + local oldpaint = icon.Paint + icon.Paint = function(self2, w, h) + surface.SetDrawColor(quality_to_color[self2.Quality] or Color(0, 0, 0, 0)) + surface.SetMaterial(qualitymat) + surface.DrawTexturedRect(0, 0, w, h) + oldpaint(self2, w, h) + end + end + + + icon.DoClick = function() + RunConsoleCommand("gm_giveswep", obj.spawnname) + surface.PlaySound("ui/buttonclickrelease.wav") + end + + icon.DoMiddleClick = function() + RunConsoleCommand("gm_spawnswep", obj.spawnname) + surface.PlaySound("ui/buttonclickrelease.wav") + end + + icon.OpenMenuExtra = function(self, menu) + menu:AddOption("#spawnmenu.menu.spawn_with_toolgun", function() + RunConsoleCommand("gmod_tool", "creator") + RunConsoleCommand("creator_type", "3") + RunConsoleCommand("creator_name", obj.spawnname) + end):SetIcon("icon16/brick_add.png") + + if self:GetIsNPCWeapon() then + local opt = menu:AddOption("#spawnmenu.menu.use_as_npc_gun", function() + RunConsoleCommand("gmod_npcweapon", self:GetSpawnName()) + end) + + if self:GetSpawnName() == GetConVarString("gmod_npcweapon") then + opt:SetIcon("icon16/monkey_tick.png") + else + opt:SetIcon("icon16/monkey.png") + end + end + end + + icon.OpenMenu = DoGenericSpawnmenuRightclickMenu + + if IsValid(container) then + container:Add(icon) + end + + return icon +end) + +hook.Add("PopulateWeapons", "zzz_TacRP_SubCategories", function(pnlContent, tree, anode) + local cvar = TacRP.ConVars["spawnmenu_subcats"]:GetInt() + -- if cvar == 0 then return end + + local sortbytiers = TacRP.ConVars["spawnmenu_sortbytiers"]:GetBool() + + timer.Simple(0, function() + -- Loop through the weapons and add them to the menu + local Weapons = list.Get("Weapon") + local Categorised = {} + local TacRPCats = {} + + -- Build into categories + subcategories + for k, weapon in pairs(Weapons) do + if not weapon.Spawnable then continue end + if not weapons.IsBasedOn(k, "tacrp_base") then continue end + -- Get the weapon category as a string + local Category = weapon.Category or "Other2" + local WepTable = weapons.Get(weapon.ClassName) + + if not isstring(Category) then + Category = tostring(Category) + end + + -- Get the weapon subcategory as a string + local SubCategory = "Other" + + if cvar == 2 then + if WepTable != nil and WepTable.SubCatTier != nil then + SubCategory = WepTable.SubCatTier + + if SubCategory == "9Special" then + SubCategory = WepTable.SubCatType + end + + if not isstring(SubCategory) then + SubCategory = tostring(SubCategory) + end + end + elseif cvar == 1 then + if WepTable != nil and WepTable.SubCatType != nil then + SubCategory = WepTable.SubCatType + + if not isstring(SubCategory) then + SubCategory = tostring(SubCategory) + end + end + else + SubCategory = "Other" + end + + local wep = weapons.Get(weapon.ClassName) + + weapon.Quality = wep.SubCatTier + + -- Insert it into our categorised table + Categorised[Category] = Categorised[Category] or {} + Categorised[Category][SubCategory] = Categorised[Category][SubCategory] or {} + table.insert(Categorised[Category][SubCategory], weapon) + TacRPCats[Category] = true + end + + -- Iterate through each category in the weapons table + for _, node in pairs(tree:Root():GetChildNodes()) do + if not TacRPCats[node:GetText()] then continue end + -- Get the subcategories registered in this category + local catSubcats = Categorised[node:GetText()] + if not catSubcats then continue end + + -- Overwrite the icon populate function with a custom one + node.DoPopulate = function(self) + -- If we've already populated it - forget it. + if self.PropPanel then return end + -- Create the container panel + self.PropPanel = vgui.Create("ContentContainer", pnlContent) + self.PropPanel:SetVisible(false) + self.PropPanel:SetTriggerSpawnlistChange(false) + + -- Iterate through the subcategories + for subcatName, subcatWeps in SortedPairs(catSubcats) do + -- Create the subcategory header, if more than one exists for this category + if table.Count(catSubcats) > 1 then + local label = vgui.Create("ContentHeader", container) + label:SetText(string.sub(TacRP:TryTranslate(subcatName), 2)) + self.PropPanel:Add(label) + end + + -- Create the clickable icon + if sortbytiers then + table.sort(subcatWeps, function(a, b) + if (a.Quality == b.Quality) then + return a.PrintName < b.PrintName + else + return a.Quality < b.Quality + end + end) + else + table.SortByMember(subcatWeps, "PrintName") + end + + for _, ent in ipairs(subcatWeps) do + spawnmenu.CreateContentIcon("tacrp_weapon", self.PropPanel, { + nicename = TacRP:GetPhrase("wep." .. ent.ClassName .. ".name.full") + or TacRP:GetPhrase("wep." .. ent.ClassName .. ".name") + or ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = ent.IconOverride or "entities/" .. ent.ClassName .. ".png", + admin = ent.AdminOnly, + quality = ent.Quality + }) + end + end + end + + -- If we click on the node populate it and switch to it. + node.DoClick = function(self) + self:DoPopulate() + pnlContent:SwitchPanel(self.PropPanel) + end + end + + -- Select the first node + local FirstNode = tree:Root():GetChildNode(0) + + if IsValid(FirstNode) then + FirstNode:InternalDoClick() + end + end) +end) + +local function BuildWeaponCategories() + local weapons = list.Get("Weapon") + local Categorised = {} + + -- Build into categories + for k, weapon in pairs(weapons) do + if not weapon.Spawnable then continue end + local Category = weapon.Category or "Other" + + if not isstring(Category) then + Category = tostring(Category) + end + + Categorised[Category] = Categorised[Category] or {} + table.insert(Categorised[Category], weapon) + end + + return Categorised +end + +local function AddCategory(tree, cat) + local CustomIcons = list.Get("ContentCategoryIcons") + -- Add a node to the tree + local node = tree:AddNode(cat, CustomIcons[cat] or "icon16/gun.png") + tree.Categories[cat] = node + + -- When we click on the node - populate it using this function + node.DoPopulate = function(self) + -- If we've already populated it - forget it. + if IsValid(self.PropPanel) then return end + -- Create the container panel + self.PropPanel = vgui.Create("ContentContainer", tree.pnlContent) + self.PropPanel:SetVisible(false) + self.PropPanel:SetTriggerSpawnlistChange(false) + local weps = BuildWeaponCategories()[cat] + + for k, ent in SortedPairsByMemberValue(weps, "PrintName") do + spawnmenu.CreateContentIcon(ent.ScriptedEntityType or "weapon", self.PropPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = ent.IconOverride or ("entities/" .. ent.ClassName .. ".png"), + admin = ent.AdminOnly + }) + end + end + + -- If we click on the node populate it and switch to it. + node.DoClick = function(self) + self:DoPopulate() + tree.pnlContent:SwitchPanel(self.PropPanel) + end + + node.OnRemove = function(self) + if IsValid(self.PropPanel) then + self.PropPanel:Remove() + end + end + + return node +end + +local function AutorefreshWeaponToSpawnmenu(weapon, name) + local swepTab = g_SpawnMenu.CreateMenu:GetCreationTab("#spawnmenu.category.weapons") + if not swepTab or not swepTab.ContentPanel or not IsValid(swepTab.Panel) then return end + local tree = swepTab.ContentPanel.ContentNavBar.Tree + if not tree.Categories then return end + local newCategory = weapon.Category or "Other" + + -- Remove from previous category.. + for cat, catPnl in pairs(tree.Categories) do + if not IsValid(catPnl.PropPanel) then continue end + + for _, icon in pairs(catPnl.PropPanel.IconList:GetChildren()) do + if icon:GetName() != "ContentIcon" then continue end + + if icon:GetSpawnName() == name then + local added = false + + if cat == newCategory then + -- We already have the new category, just readd the icon here + local newIcon = AddWeaponToCategory(catPnl.PropPanel, weapon) + newIcon:MoveToBefore(icon) + added = true + end + + icon:Remove() + if added then return end + end + end + -- Leave the empty categories, this only applies to devs anyway + end + + -- Weapon changed category... + if IsValid(tree.Categories[newCategory]) then + -- Only do this if it is already populated. + -- If not, the weapon will appear automatically when user clicks on the category + if IsValid(tree.Categories[newCategory].PropPanel) then + -- Just append it to the end, heck with the order + AddWeaponToCategory(tree.Categories[newCategory].PropPanel, weapon) + end + else + AddCategory(tree, newCategory) + end +end + +hook.Add("InitPostEntity", "TacRP_OverrideSpawnmenuReloadSWEP", function() + hook.Add("PreRegisterSWEP", "spawnmenu_reload_swep", function(weapon, name) + if not weapon.Spawnable or weapons.IsBasedOn(name, "tacrp_base") then return end + + timer.Simple(0, function() + AutorefreshWeaponToSpawnmenu(weapon, name) + end) + end) +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_ttt.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_ttt.lua new file mode 100644 index 0000000..421d12c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_ttt.lua @@ -0,0 +1,169 @@ +if engine.ActiveGamemode() != "terrortown" then return end + +if !TTT2 then + hook.Add("TTTSettingsTabs", "TacRP", function(dtabs) + + local padding = dtabs:GetPadding() * 2 + + local panellist = vgui.Create("DPanelList", dtabs) + panellist:StretchToParent(0,0,padding,0) + panellist:EnableVerticalScrollbar(true) + panellist:SetPadding(10) + panellist:SetSpacing(10) + + local dgui_pref = vgui.Create("DForm", panellist) + dgui_pref:SetName("Client - Preference") + dgui_pref:CheckBox("Toggle Aiming", "tacrp_toggleaim") + dgui_pref:CheckBox("Toggle Peeking", "tacrp_togglepeek") + dgui_pref:CheckBox("Auto Reload When Empty", "TacRP_autoreload") + dgui_pref:CheckBox("Flashbang Dark Mode", "tacrp_flash_dark") + dgui_pref:Help("In dark mode, flashbangs turn your screen black instead of white, and mutes audio intead of ringing.") + dgui_pref:CheckBox("Quiet Radar", "tacrp_radar_quiet") + dgui_pref:Help("This mutes your own radar sound for yourself only. Others can still hear your radar, and you can still hear others' radars.") + dgui_pref:NumSlider("HUD Scale", "tacrp_hudscale", 0.5, 1.5, 2) + dgui_pref:Help("HUD is already scaled to screen width; this slider may help ultrawide users or people with a vertical setup.") + + panellist:AddItem(dgui_pref) + + local dgui_ui = vgui.Create("DForm", panellist) + dgui_ui:SetName("Client - Interface") + dgui_ui:CheckBox("Quickthrow Radial Menu", "tacrp_nademenu") + dgui_ui:Help("When enabled, +grenade2 brings up a menu to select grenades. Otherwise it switches between them.") + dgui_ui:CheckBox("Quickthrow Menu Clicking", "tacrp_nademenu_click") + dgui_ui:Help("When enabled, left click and right click in the quickthrow menu performs an overhand and underhand throw of the highlighted grenade.") + dgui_ui:CheckBox("Blindfire Radial Menu", "tacrp_blindfiremenu") + dgui_ui:Help("When enabled, +zoom brings up a menu to change blindfire type. Otherwise it sets blindfire based on movement keys pressed.") + dgui_ui:CheckBox("Use Meters instead of HU", "tacrp_metricunit") + dgui_ui:CheckBox("Recoil Vignette", "tacrp_vignette") + dgui_ui:Help("Vignette intensity is based on amount of accumulated recoil.") + dgui_ui:CheckBox("Show Backup HUD", "tacrp_minhud") + dgui_ui:CheckBox("Show Control Hints", "tacrp_hints") + dgui_ui:CheckBox("Hints Always Active", "tacrp_hints_always") + panellist:AddItem(dgui_ui) + + local dgui_cl = vgui.Create("DForm", panellist) + dgui_cl:SetName("Client - Misc.") + dgui_cl:CheckBox("Near Walling", "tacrp_nearwall") + dgui_cl:Help("Pushes viewmodel back when the point of aim is in front of a wall. Purely visual effect, but may help when blindfiring.") + dgui_cl:CheckBox("Disable Suicide Mode", "tacrp_idunwannadie") + dgui_cl:Help("Hides the option to shoot yourself from the radial menu, and disables the SHIFT+ALT+B key combo.") + dgui_cl:CheckBox("Draw Holstered Weapons", "tacrp_drawholsters") + dgui_cl:CheckBox("True Laser Position", "tacrp_true_laser") + panellist:AddItem(dgui_cl) + + local dgui1 = vgui.Create("DForm", panellist) + dgui1:SetName("Server - Main") + dgui1:Help("This menu only works if you are the host of a listen server.") + + dgui1:CheckBox("Free Attachments", "tacrp_free_atts") + dgui1:CheckBox("Infinite Ammo", "tacrp_infiniteammo") + dgui1:CheckBox("Infinite Grenades", "tacrp_infinitegrenades") + dgui1:CheckBox("Magazines Leave DNA", "tacrp_ttt_magazine_dna") + dgui1:Help("Dropped magazines and grenade spoons don't disappear and contain user's DNA.") + + panellist:AddItem(dgui1) + + local dgui2 = vgui.Create("DForm", panellist) + dgui2:SetName("Server - Weapons & Attachments") + dgui2:Help("This menu only works if you are the host of a listen server.") + + dgui2:CheckBox("Free Attachments", "tacrp_free_atts") + dgui2:CheckBox("Unlocking Attachments", "tacrp_lock_atts") + dgui2:Help("Attachments aren't consumed on equip, like CW2.0.") + dgui2:CheckBox("Lose Attachments On Death", "tacrp_loseattsondie") + + dgui2:CheckBox("Replace Vanilla TTT Weapons", "tacrp_ttt_weapon_include") + dgui2:NumSlider("Replace Chance", "tacrp_ttt_weapon_replace", 0, 1, 2) + dgui2:NumSlider("Maximum Atts. On Weapon", "tacrp_ttt_atts_max", 0, 10, 0) + dgui2:NumSlider("Attachment Chance", "tacrp_ttt_atts_random", 0, 1, 2) + dgui2:NumSlider("Give Atts. On Spawn", "tacrp_ttt_atts_max", 0, 100, 0) + + dgui2:CheckBox("Allow Innocents Customization", "tacrp_ttt_cust_inno_allow") + dgui2:CheckBox("Allow Non-Innocents Customization", "tacrp_ttt_cust_role_allow") + dgui2:Help("If disabled, the respective roles cannot customize for any reason.") + + dgui2:CheckBox("Innocents Customization During Round", "tacrp_ttt_cust_inno_round") + dgui2:CheckBox("Non-Innocents Customization During Round", "tacrp_ttt_cust_role_round") + dgui2:Help("If disabled, customization is only allowed in pregame/postgame.") + + dgui2:CheckBox("Innocents Must Use Customization Bench", "tacrp_ttt_cust_inno_needbench") + dgui2:CheckBox("Non-Innocents Must Use Customization Bench", "tacrp_ttt_cust_role_needbench") + dgui2:Help("If set, customiation is only allowed when near a customization bench bought by Detectives/Traitors.") + + panellist:AddItem(dgui2) + + local dgui3 = vgui.Create("DForm", panellist) + dgui3:SetName("Server - Mechanics") + dgui1:CheckBox("Magazines Leave DNA", "tacrp_ttt_magazine_dna") + dgui1:Help("Dropped magazines and grenade spoons don't disappear and contain user's DNA.") + dgui3:CheckBox("Enable Sway", "tacrp_sway") + dgui3:CheckBox("Enable Free Aim", "tacrp_freeaim") + dgui3:CheckBox("Enable Penetration", "tacrp_penetration") + dgui3:CheckBox("Enable Physical Bullets", "tacrp_physbullet") + dgui3:Help("Bullets will be hitscan up to a certain range depending on muzzle velocity.") + dgui3:CheckBox("Enable Holstering", "tacrp_holster") + dgui3:Help("Play a holster animation before pulling out another weapon. If disabled, holstering is instant.") + dgui3:CheckBox("Enable Shotgun Patterns", "tacrp_fixedspread") + dgui3:Help("Shotgun pellets uses a pattern that covers the spread area for more consistency.") + dgui3:CheckBox("Enable Pattern Randomness", "tacrp_pelletspread") + dgui3:Help("Add random spread onto the pattern. Does not affect total spread. If disabled, shotgun patterns become completely static.") + dgui3:CheckBox("Enable Pattern Randomness", "tacrp_pelletspread") + dgui3:CheckBox("Custom Armor Penetration", "tacrp_armorpenetration") + dgui3:Help("Weapons use defined piercing and shredding stats to calculate damage when hitting players with HL2 suit armor, instead of using the standard 20% damage. This generally increases the weapons' effectiveness against armor.\nCompatible with Danger Zone Entities' armor.") + dgui3:CheckBox("Enable Scope Glint", "tacrp_glint") + dgui3:Help("Scopes show a visible glint. Glint size is dependent on angle of view, scope magnification and distance, and is bigger when zoomed in.") + dgui3:CheckBox("Enable Blinding Flashlights", "tacrp_flashlight_blind") + dgui3:Help("Flashlight glare will obscure vision based on distance and viewing angle. Effect is more significant on scopes. If disabled, glare sprite will be visible but not grow in size.") + dgui3:CheckBox("Allow Reload while Sprinting", "tacrp_sprint_reload") + + dgui3:CheckBox("Movement Penalty", "tacrp_penalty_move") + dgui3:ControlHelp("Penalty when weapon is up.\nDoes not apply in safety.") + dgui3:CheckBox("Firing Movement Penalty", "tacrp_penalty_firing") + dgui3:ControlHelp("Penalty from firing the weapon.") + dgui3:CheckBox("Aiming Movement Penalty", "tacrp_penalty_aiming") + dgui3:ControlHelp("Penalty while aiming the weapon.") + dgui3:CheckBox("Reload Movement Penalty", "tacrp_penalty_reload") + dgui3:ControlHelp("Penalty while reloading.") + dgui3:CheckBox("Melee Movement Penalty", "tacrp_penalty_melee") + dgui3:ControlHelp("Penalty from melee bashing.") + + panellist:AddItem(dgui3) + + dtabs:AddSheet("TacRP", panellist, "icon16/gun.png", false, false, "TacRP") + end) +end + +hook.Add("TTTRenderEntityInfo", "TacRP_TTT", function(tData) + local client = LocalPlayer() + local ent = tData:GetEntity() + + + if !IsValid(client) or !client:IsTerror() or !client:Alive() + or !IsValid(ent) or tData:GetEntityDistance() > 100 or !ent:IsWeapon() + or !ent.ArcticTacRP or ent.PrimaryGrenade then + return + end + + if tData:GetAmountDescriptionLines() > 0 then + tData:AddDescriptionLine() + end + + if ent.Attachments and ent:CountAttachments() > 0 then + tData:AddDescriptionLine(tostring(ent:CountAttachments()) .. " Attachments:", nil) + for i, v in pairs(ent.Attachments) do + local attName = v.Installed + local attTbl = TacRP.GetAttTable(attName) + if attTbl and v.PrintName and attTbl.PrintName then + local printName = TacRP:GetAttName(attName) + tData:AddDescriptionLine(printName, nil, {attTbl.Icon}) + end + end + end +end) + +hook.Add("TTTBodySearchPopulate", "TacRP", function(processed, raw) + if (weapons.Get(raw.wep or "") or {}).ArcticTacRP and bit.band(raw.dmg or 0, DMG_BUCKSHOT) != 0 then + processed.dmg.text = LANG.GetTranslation("tacrp_search_dmg_buckshot") + processed.dmg.img = "tacrp/ttt/kill_buckshot.png" + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/cl_util.lua b/garrysmod/addons/tacrp/lua/tacrp/client/cl_util.lua new file mode 100644 index 0000000..dfa4183 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/cl_util.lua @@ -0,0 +1,44 @@ +function TacRP.FormatViewModelAttachment(nFOV, vOrigin, bFrom) + local vEyePos = EyePos() + local aEyesRot = EyeAngles() + local vOffset = vOrigin - vEyePos + local vForward = aEyesRot:Forward() + local nViewX = math.tan(nFOV * math.pi / 360) + + if nViewX == 0 then + vForward:Mul(vForward:Dot(vOffset)) + vEyePos:Add(vForward) + + return vEyePos + end + + -- FIXME: LocalPlayer():GetFOV() should be replaced with EyeFOV() when it's binded + local nWorldX = math.tan(LocalPlayer():GetFOV() * math.pi / 360) + + if nWorldX == 0 then + vForward:Mul(vForward:Dot(vOffset)) + vEyePos:Add(vForward) + + return vEyePos + end + + local vRight = aEyesRot:Right() + local vUp = aEyesRot:Up() + + if bFrom then + local nFactor = nWorldX / nViewX + vRight:Mul(vRight:Dot(vOffset) * nFactor) + vUp:Mul(vUp:Dot(vOffset) * nFactor) + else + local nFactor = nViewX / nWorldX + vRight:Mul(vRight:Dot(vOffset) * nFactor) + vUp:Mul(vUp:Dot(vOffset) * nFactor) + end + + vForward:Mul(vForward:Dot(vOffset)) + vEyePos:Add(vRight) + vEyePos:Add(vUp) + vEyePos:Add(vForward) + + return vEyePos +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/vgui/tacrpattslot.lua b/garrysmod/addons/tacrp/lua/tacrp/client/vgui/tacrpattslot.lua new file mode 100644 index 0000000..41611f0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/vgui/tacrpattslot.lua @@ -0,0 +1,332 @@ +local PANEL = {} +AccessorFunc(PANEL, "ShortName", "ShortName", FORCE_STRING) +AccessorFunc(PANEL, "Weapon", "Weapon") +AccessorFunc(PANEL, "Slot", "Slot") + +AccessorFunc(PANEL, "IsMenu", "IsMenu") +AccessorFunc(PANEL, "SlotLayout", "SlotLayout") + +local flash_end = 0 +local lock = Material("tacrp/hud/mark_lock.png", "mips smooth") + +function PANEL:GetInstalled() + if self:GetIsMenu() and IsValid(self:GetWeapon()) and self:GetWeapon().Attachments then + local installed = self:GetWeapon().Attachments[self:GetSlot()].Installed + if installed == "" then return nil end + return installed + else + return self:GetShortName() + end +end + +function PANEL:Init() + + self:SetText("") + + self.Icon = vgui.Create("DImage", self) + self.Title = vgui.Create("DLabel", self) + + self.Title.Paint = function(self2, w, h) + if self2:GetTextColor() == Color(0, 0, 0) then + surface.SetDrawColor(255, 255, 255, 50) + surface.DrawRect(0, 0, w, h) + end + end + + self:SetMouseInputEnabled(true) +end + +function PANEL:PerformLayout(w, h) + + local atttbl = TacRP.GetAttTable(self:GetInstalled()) + local empty = self:GetInstalled() == nil + + self.Icon:Dock(FILL) + + if empty then + self.Icon:SetVisible(false) + self.Title:SetText(TacRP:GetPhrase("att.none") or "N/A") + else + self.Icon:SetVisible(true) + self.Icon:SetMaterial(atttbl.Icon) + self.Title:SetText(TacRP:GetAttName(self:GetInstalled())) + end + + self.Title:SetSize(w, ScreenScale(6)) + self.Title:SetFont("TacRP_Myriad_Pro_6") + self.Title:SizeToContentsX(8) + if self.Title:GetWide() >= w then + self.Title:SetWide(w) + end + self.Title:SetContentAlignment(2) + self.Title:SetPos(w / 2 - self.Title:GetWide() / 2, h - ScreenScale(6)) + +end + +function PANEL:GetColors() + + local wep = self:GetWeapon() + local att = self:GetShortName() + local hover = self:IsHovered() + local attached = IsValid(wep) and self:GetSlot() + and wep.Attachments[self:GetSlot()].Installed == att + self:SetDrawOnTop(hover) + + local col_bg = Color(0, 0, 0, 150) + local col_corner = Color(255, 255, 255) + local col_text = Color(255, 255, 255) + local col_image = Color(255, 255, 255) + + if (attached and !self:GetIsMenu()) or (self:GetIsMenu() and self:GetSlotLayout() and self:GetSlotLayout():GetActiveSlot() == self:GetSlot()) then + col_bg = Color(150, 150, 150, 150) + col_corner = Color(50, 50, 255) + col_text = Color(0, 0, 0) + col_image = Color(200, 200, 200) + if hover then + col_bg = Color(255, 255, 255) + col_corner = Color(150, 150, 255) + col_text = Color(0, 0, 0) + col_image = Color(255, 255, 255) + end + else + if self:GetIsMenu() or TacRP:PlayerGetAtts(LocalPlayer(), att) > 0 then + if hover then + col_bg = Color(255, 255, 255) + col_corner = Color(0, 0, 0) + col_text = Color(0, 0, 0) + col_image = Color(255, 255, 255) + end + else + if hover then + col_bg = Color(150, 150, 150) + col_corner = Color(25, 0, 0) + col_text = Color(0, 0, 0) + col_image = Color(255, 255, 255) + else + col_bg = Color(25, 20, 20, 150) + col_corner = Color(255, 0, 0) + col_text = Color(255, 0, 0) + col_image = Color(200, 200, 200) + end + end + end + + return col_bg, col_corner, col_text, col_image +end + +function PANEL:Paint(w, h) + + local wep = self:GetWeapon() + local att = self:GetShortName() + local atttbl = TacRP.GetAttTable(att) + local empty = self:GetInstalled() == nil + + if !IsValid(wep) then return end + + local hover = self:IsHovered() + self:SetDrawOnTop(hover) + local has = empty and 0 or TacRP:PlayerGetAtts(wep:GetOwner(), att) + + local col_bg, col_corner, col_text, col_image = self:GetColors() + + + surface.SetDrawColor(col_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, col_corner) + + self.Title:SetTextColor(col_text) + self.Icon:SetImageColor(col_image) + + if !self:GetIsMenu() and !TacRP.ConVars["lock_atts"]:GetBool() and !TacRP.ConVars["free_atts"]:GetBool() and !atttbl.Free then + local numtxt = has + if !TacRP.ConVars["free_atts"]:GetBool() and engine.ActiveGamemode() == "terrortown" and TacRP.ConVars["ttt_bench_freeatts"]:GetBool() and TacRP.NearBench(wep:GetOwner()) then + numtxt = "*" + end + + surface.SetFont("TacRP_Myriad_Pro_6") + surface.SetTextColor(col_text) + local nt_w = surface.GetTextSize(numtxt) + surface.SetTextPos(w - nt_w - TacRP.SS(2), TacRP.SS(1)) + surface.DrawText(numtxt) + end +end + +function PANEL:PaintOver(w, h) + -- thank u fesiug + if self:IsHovered() and self:GetInstalled() != nil then + + local wep = self:GetWeapon() + local att = self:GetInstalled() + local atttbl = TacRP.GetAttTable(att) + local attslot = wep.Attachments[self:GetSlot()] + + local todo = DisableClipping(true) + local col_bg = Color(0, 0, 0, 230) + local col_corner = Color(255, 255, 255) + local col_text = Color(255, 255, 255) + local col_text_pro = Color(0, 130, 0, 230) + local col_text_con = Color(130, 0, 0, 230) + local rx, ry = self:CursorPos() + rx = rx + TacRP.SS(12) + --ry = ry + TacRP.SS(16) + local gap = TacRP.SS(18) + local statjump = TacRP.SS(12) + + local bw, bh = TacRP.SS(160), TacRP.SS(18) + + local atttxt = TacRP:GetAttName(att, true) + if !self.DescCache then + self.DescCache = TacRP.MultiLineText(TacRP:GetAttDesc(att), bw, "TacRP_Myriad_Pro_6") + end + bh = bh + (#self.DescCache - 1) * TacRP.SS(5) + + local firstoff = bh + TacRP.SS(2) + local vertsize = firstoff + gap + TacRP.SS(4) + if atttbl.Pros then vertsize = vertsize + statjump * #atttbl.Pros end + if atttbl.Cons then vertsize = vertsize + statjump * #atttbl.Cons end + + if self:GetY() + ry >= ScrH() - vertsize then + ry = ry + (ScrH() - vertsize - (self:GetY() + ry)) + end + + surface.SetDrawColor(col_bg) + TacRP.DrawCorneredBox(rx, ry, bw, bh, col_corner) + + -- Att. Name + surface.SetTextColor(col_text) + surface.SetFont("TacRP_Myriad_Pro_10") + surface.SetTextPos(rx + TacRP.SS(2), ry + TacRP.SS(1)) + surface.DrawText(atttxt) + + -- Att. Description + surface.SetFont("TacRP_Myriad_Pro_6") + for i, k in ipairs(self.DescCache) do + surface.SetTextPos(rx + TacRP.SS(2), ry + TacRP.SS(1 + 8 + 2 + 5 * (i - 1))) + surface.DrawText(k) + end + + local bump = firstoff + local txjy = TacRP.SS(1) + local rump = TacRP.SS(9) + if atttbl.Pros then for aa, ab in ipairs(atttbl.Pros) do + surface.SetDrawColor(col_text_pro) + TacRP.DrawCorneredBox( rx, ry + bump, bw, TacRP.SS(10) ) + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(col_text) + surface.SetTextPos(rx + rump, ry + bump + txjy) + surface.DrawText(TacRP:TryTranslate(ab)) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextPos(rx + TacRP.SS(3), ry + bump + txjy) + surface.DrawText("+") + + bump = bump + statjump + statted = true + end end + if atttbl.Cons then for aa, ab in ipairs(atttbl.Cons) do + surface.SetDrawColor(col_text_con) + TacRP.DrawCorneredBox( rx, ry + bump, bw, TacRP.SS(10) ) + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(col_text) + surface.SetTextPos(rx + rump, ry + bump + txjy) + surface.DrawText(TacRP:TryTranslate(ab)) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextPos(rx + TacRP.SS(3.8), ry + bump + txjy) + surface.DrawText("-") + + bump = bump + statjump + statted = true + end end + + -- if statted then + -- surface.SetDrawColor(col_bg) + -- surface.DrawLine(rx, ry + gap, rx + bw, ry + gap) + -- end + + local can, reason = TacRP.CanCustomize(wep:GetOwner(), wep, att, attslot) + if !can then + reason = reason or "Restricted" + local reasonx, reasonw = rx + TacRP.SS(14), bw - TacRP.SS(14) + surface.SetDrawColor(25, 0, 0, 240) + TacRP.DrawCorneredBox(reasonx, ry + bump, reasonw, TacRP.SS(12), col_text_con) + surface.SetFont("TacRP_Myriad_Pro_12") + if flash_end > CurTime() then + local c = 255 - (flash_end - CurTime()) * 255 + surface.SetTextColor(255, c, c) + surface.SetDrawColor(255, c, c) + else + surface.SetTextColor(255, 255, 255) + surface.SetDrawColor(255, 255, 255) + end + + surface.SetMaterial(lock) + surface.DrawTexturedRect(rx, ry + bump, TacRP.SS(12), TacRP.SS(12)) + + local tw = surface.GetTextSize(reason) + surface.SetTextPos(reasonx + reasonw / 2 - tw / 2, ry + bump) + surface.DrawText(reason) + elseif engine.ActiveGamemode() == "terrortown" and TacRP.NearBench(wep:GetOwner()) then + local reasonx, reasonw = rx, bw + reason = "Free Customization At Bench" + surface.SetDrawColor(0, 0, 0, 200) + TacRP.DrawCorneredBox(reasonx, ry + bump, reasonw, TacRP.SS(10), col_text_con) + surface.SetFont("TacRP_Myriad_Pro_10") + surface.SetTextColor(255, 255, 255) + surface.SetDrawColor(255, 255, 255) + local tw = surface.GetTextSize(reason) + surface.SetTextPos(reasonx + reasonw / 2 - tw / 2, ry + bump) + surface.DrawText(reason) + end + + DisableClipping(todo) + + end +end + +function PANEL:Think() + if self:GetIsMenu() then + if self.LastInstalled and self.LastInstalled != self:GetInstalled() then + self:InvalidateLayout(true) + end + self.LastInstalled = self:GetInstalled() + end +end + +function PANEL:DoClick() + local wep = self:GetWeapon() + if !IsValid(wep) or wep:GetOwner() != LocalPlayer() then return end + local att = self:GetShortName() + local slot = self:GetSlot() + local attslot = wep.Attachments[slot] + + if self:GetIsMenu() then + if self:GetSlotLayout():GetActiveSlot() == slot then + self:GetSlotLayout():SetSlot(0) + else + self:GetSlotLayout():SetSlot(slot) + end + elseif attslot.Installed then + if attslot.Installed == att then + wep:Detach(slot) + else + wep:Detach(slot, true, true) + wep:Attach(slot, att) + end + else + wep:Attach(slot, att) + end +end +function PANEL:DoRightClick() + local wep = self:GetWeapon() + if !IsValid(wep) or wep:GetOwner() != LocalPlayer() then return end + local att = self:GetShortName() + local slot = self:GetSlot() + local attslot = wep.Attachments[slot] + + if attslot.Installed and attslot.Installed == att then + wep:Detach(slot) + end +end + +vgui.Register("TacRPAttSlot", PANEL, "DLabel") diff --git a/garrysmod/addons/tacrp/lua/tacrp/client/vgui/tacrpattslotlayout.lua b/garrysmod/addons/tacrp/lua/tacrp/client/vgui/tacrpattslotlayout.lua new file mode 100644 index 0000000..2d0ed2d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/client/vgui/tacrpattslotlayout.lua @@ -0,0 +1,70 @@ +local PANEL = {} +AccessorFunc(PANEL, "ActiveSlot", "ActiveSlot") +AccessorFunc(PANEL, "Weapon", "Weapon") +AccessorFunc(PANEL, "Scroll", "Scroll") + +function PANEL:LoadAttachments() + self:Clear() + + if (self:GetActiveSlot() or 0) <= 0 then + if self:GetScroll() then self:GetScroll():SetVisible(false) end + return + end + + local attslot = self:GetWeapon().Attachments[self:GetActiveSlot()] + local atts = TacRP.GetAttsForCats(attslot.Category or "", self:GetWeapon()) + + table.sort(atts, function(a, b) + a = a or "" + b = b or "" + + if a == "" or b == "" then return true end + + local atttbl_a = TacRP.GetAttTable(a) + local atttbl_b = TacRP.GetAttTable(b) + + local order_a = 0 + local order_b = 0 + + order_a = atttbl_a.SortOrder or order_a + order_b = atttbl_b.SortOrder or order_b + + if order_a == order_b then + return (atttbl_a.PrintName or "") < (atttbl_b.PrintName or "") + end + + return order_a < order_b + end) + + for i, att in pairs(atts) do + local slot_panel = self:Add("TacRPAttSlot") --vgui.Create("TacRPAttSlot", self) + slot_panel:SetShortName(att) + slot_panel:SetSlot(self:GetActiveSlot()) + slot_panel:SetWeapon(self:GetWeapon()) + slot_panel:SetSize(TacRP.SS(32), TacRP.SS(32)) + end + + -- self:InvalidateLayout(true) + + if self:GetScroll() then + self:GetScroll():SetVisible(true) + self:GetScroll():SetTall(math.min(ScrH() * 0.9, #atts * (TacRP.SS(32) + self:GetSpaceY()))) + self:GetScroll():CenterVertical() + self:GetScroll():GetVBar():SetScroll(0) + end + +end + +function PANEL:SetSlot(i) + self:SetActiveSlot(i) + self:LoadAttachments() + + self:GetWeapon().LastCustomizeSlot = i +end + +-- function PANEL:Paint(w, h) +-- surface.SetDrawColor(255, 0, 0) +-- surface.DrawRect(0, 0, w, h) +-- end + +vgui.Register("TacRPAttSlotLayout", PANEL, "DIconLayout") diff --git a/garrysmod/addons/tacrp/lua/tacrp/server/sv_convar.lua b/garrysmod/addons/tacrp/lua/tacrp/server/sv_convar.lua new file mode 100644 index 0000000..dee03a9 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/server/sv_convar.lua @@ -0,0 +1,30 @@ +if CLIENT then return end + +net.Receive("tacrp_sendconvar", function(len, ply) + local command = net.ReadString() + + if !ply:IsAdmin() then return end + if game.SinglePlayer() then return end + if string.sub(command, 1, 5) != "tacrp" then return end + + local cmds = string.Split(command, " ") + + local timername = "change" .. cmds[1] + + if timer.Exists(timername) then + timer.Remove(timername) + end + + local args = {} + for i, k in pairs(cmds) do + if k == " " then continue end + k = string.Trim(k, " ") + + table.insert(args, k) + end + + timer.Create(timername, 0.25, 1, function() + RunConsoleCommand(args[1], args[2]) + print("Changed " .. args[1] .. " to " .. args[2] .. ".") + end) +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/server/sv_damage.lua b/garrysmod/addons/tacrp/lua/tacrp/server/sv_damage.lua new file mode 100644 index 0000000..21421dd --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/server/sv_damage.lua @@ -0,0 +1,207 @@ +-- affects how much armor is reduced from damage +local armorbonus = 1.0 +-- affects what fraction of damage is converted to armor damage (1 means none) +-- local armorratio = 0.2 + +-- Simulate armor calculation +-- https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/game/server/player.cpp#L1061 +local function calcarmor(dmginfo, armor, flBonus, flRatio) + local old = GetConVar("player_old_armor"):GetBool() + if !flBonus then + flBonus = old and 0.5 or 1 + end + flRatio = flRatio or 0.2 + + local dmg = dmginfo:GetDamage() + local origdmg = dmg + -- if dmginfo:IsDamageType(DMG_BLAST) and !game.SinglePlayer() then + -- flBonus = flBonus * 2 + -- end + if armor > 0 then + local flNew = dmg * flRatio + local flArmor = math.max(0, dmg - flNew) * flBonus + + if !old and flArmor == 0 then + flArmor = 1 + -- flArmor = math.max(1, flArmor) + end + + if flArmor > armor then + flArmor = armor * (1 / flBonus) + flNew = dmg - flArmor + -- m_DmgSave = armor -- ? + armor = 0 + else + -- m_DmgSave = flArmor + armor = math.max(0, armor - flArmor) + end + + dmg = flNew + + if dmg > origdmg and armor > 0 then + armor = math.max(0, armor - (dmg - origdmg) * flBonus) + dmg = origdmg + end + end + return dmg, armor +end + +local bitflags_blockable = DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST +hook.Add("EntityTakeDamage", "Z_TacRP", function(ply, dmginfo) + if !TacRP.ConVars["armorpenetration"]:GetBool() then return end + if isfunction(GAMEMODE.HandlePlayerArmorReduction) then return end -- in dev branch right now + if !ply:IsPlayer() or dmginfo:IsFallDamage() or dmginfo:GetDamage() < 1 then return end + + -- if danger zone armor wants to handle it, don't do it + if DZ_ENTS and ply:Armor() > 0 and (GetConVar("dzents_armor_enabled"):GetInt() == 2 or (GetConVar("dzents_armor_enabled"):GetInt() == 1 and ply:DZ_ENTS_HasArmor())) then + return + end + + local wep = dmginfo:GetInflictor() + if wep:IsPlayer() then wep = wep:GetActiveWeapon() end + + if !IsValid(wep) or !wep.ArcticTacRP then return end + + -- do we even have armor? + if (engine.ActiveGamemode() == "terrortown" and !ply:HasEquipmentItem(EQUIP_ARMOR)) + or (engine.ActiveGamemode() ~= "terrortown" and ply:Armor() <= 0) then + return + end + + -- only handle these damage types + if bit.band(dmginfo:GetDamageType(), bitflags_blockable) == 0 then + return + end + + local ap = wep:GetValue("ArmorPenetration") + local ab = wep:GetValue("ArmorBonus") + + local healthdmg, newarmor = calcarmor(dmginfo, ply:Armor(), armorbonus * ab, ap) + -- print("WANT", ply:Health() - healthdmg, newarmor, "(" .. healthdmg .. " dmg, " .. (ply:Armor() - newarmor) .. " armor)") + ply.TacRPPendingArmor = newarmor + ply:SetArmor(0) -- don't let engine do armor calculation + dmginfo:SetDamage(healthdmg) +end) + +hook.Add("PostEntityTakeDamage", "TacRP", function(ply, dmginfo, took) + if !TacRP.ConVars["armorpenetration"]:GetBool() then return end + if isfunction(GAMEMODE.HandlePlayerArmorReduction) then return end + if !ply:IsPlayer() then return end + if ply.TacRPPendingArmor then + ply:SetArmor(ply.TacRPPendingArmor) + -- print("SET", ply:Armor()) + -- timer.Simple(0, function() + -- print("POST", ply:Health(), ply:Armor()) + -- end) + end + ply.TacRPPendingArmor = nil +end) + +hook.Add("DoPlayerDeath", "TacRP_DropGrenade", function(ply, attacker, dmginfo) + local wep = ply:GetActiveWeapon() + if !IsValid(wep) or !wep.ArcticTacRP or !wep:GetPrimedGrenade() then return end + local nade = wep:GetValue("PrimaryGrenade") and TacRP.QuickNades[wep:GetValue("PrimaryGrenade")] or wep:GetGrenade() + if nade then + local ent = nade.GrenadeEnt + local src = ply:EyePos() + local ang = ply:EyeAngles() + local rocket = ents.Create(ent or "") + + if !IsValid(rocket) then return end + + rocket:SetPos(src) + rocket:SetOwner(ply) + rocket:SetAngles(ang) + rocket:Spawn() + rocket:SetPhysicsAttacker(ply, 10) + + if TacRP.IsGrenadeInfiniteAmmo(nade) then + rocket.PickupAmmo = nil + rocket.WeaponClass = nil -- dz ents + end + + if wep:GetValue("QuickNadeTryImpact") and nade.CanSetImpact then + rocket.InstantFuse = false + rocket.Delay = 0 + rocket.Armed = false + rocket.ImpactFuse = true + end + + if nade.TTTTimer then + rocket:SetGravity(0.4) + rocket:SetFriction(0.2) + rocket:SetElasticity(0.45) + rocket:SetDetonateExact(CurTime() + nade.TTTTimer) + rocket:SetThrower(ply) + end + + local phys = rocket:GetPhysicsObject() + + if phys:IsValid() then + phys:ApplyForceCenter(ply:GetVelocity() + VectorRand() * 50 + Vector(0, 0, math.Rand(25, 50))) + phys:AddAngleVelocity(VectorRand() * 500) + end + + if nade.Spoon and TacRP.ConVars["dropmagazinemodel"]:GetBool() then + local mag = ents.Create("TacRP_droppedmag") + + if mag then + mag:SetPos(src) + mag:SetAngles(ang) + mag.Model = "models/weapons/tacint/flashbang_spoon.mdl" + mag.ImpactType = "spoon" + mag:SetOwner(ply) + mag:Spawn() + + local phys2 = mag:GetPhysicsObject() + + if IsValid(phys2) then + phys2:ApplyForceCenter(VectorRand() * 25) + phys2:AddAngleVelocity(Vector(math.Rand(-300, 300), math.Rand(-300, 300), math.Rand(-300, 300))) + end + end + end + end +end) + +hook.Add("HandlePlayerArmorReduction", "TacRP", function(ply, dmginfo) + if !TacRP.ConVars["armorpenetration"]:GetBool() then return end + if dmginfo:IsFallDamage() or dmginfo:GetDamage() < 1 then return end + + if DZ_ENTS and ply:Armor() > 0 and (GetConVar("dzents_armor_enabled"):GetInt() == 2 or (GetConVar("dzents_armor_enabled"):GetInt() == 1 and ply:DZ_ENTS_HasArmor())) then + return + end + + local wep = dmginfo:GetInflictor() + if wep:IsPlayer() then wep = wep:GetActiveWeapon() end + if !IsValid(wep) or !wep.ArcticTacRP then return end + + if ply:Armor() <= 0 or bit.band(dmginfo:GetDamageType(), DMG_FALL + DMG_DROWN + DMG_POISON + DMG_RADIATION) ~= 0 then return end + local flBonus = 1.0 * wep:GetValue("ArmorBonus") + local flRatio = wep:GetValue("ArmorPenetration") + + if GetConVar("player_old_armor"):GetBool() then + flBonus = 0.5 * wep:GetValue("ArmorBonus") + end + + local flNew = dmginfo:GetDamage() * flRatio + local flArmor = (dmginfo:GetDamage() - flNew) * flBonus + + if !GetConVar("player_old_armor"):GetBool() then + if flArmor < 1.0 then + flArmor = 1.0 + end + end + + 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) + + return true +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/server/sv_darkrp.lua b/garrysmod/addons/tacrp/lua/tacrp/server/sv_darkrp.lua new file mode 100644 index 0000000..dc9c041 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/server/sv_darkrp.lua @@ -0,0 +1,111 @@ +util.AddNetworkString("tacrp_spawnedwepatts") + +hook.Add("onDarkRPWeaponDropped", "TacRP", function(ply, ent, wep) + ent:SetCollisionGroup(COLLISION_GROUP_WEAPON) + if wep.ArcticTacRP and wep.Attachments then + local atts = {} + for k, v in pairs(wep.Attachments or {}) do + atts[k] = v.Installed and (TacRP.Attachments[v.Installed] or {}).ID + end + if table.Count(atts) > 0 then + net.Start("tacrp_spawnedwepatts") + net.WriteUInt(ent:EntIndex(), 12) -- ent won't exist on client when message arrives + net.WriteUInt(table.Count(atts), 4) + for k, v in pairs(atts) do + net.WriteUInt(k, 4) + net.WriteUInt(v, TacRP.Attachments_Bits) + end + net.Broadcast() + ent.Attachments = atts + end + end + if wep:GetNWBool("TacRP_PoliceBiocode", false) then + ent:SetNWBool("TacRP_PoliceBiocode", true) + ent.TacRP_PoliceBiocode = true -- NWVars aren't saved in pocket but this is?? But we also need client to know so it's awkward + end +end) + +hook.Add("PlayerPickupDarkRPWeapon", "TacRP", function(ply, ent, wep) + if wep.ArcticTacRP and wep.Attachments then + -- DarkRP will remove wep (created with ents.Create?) so we must make one ourselves here too + if ply:HasWeapon(wep:GetClass()) or ply:KeyDown(IN_WALK) then + ply:PickupObject(ent) + return true + end -- block duplicate pickups + + local class = wep:GetClass() + local biocoded = ent:GetNWBool("TacRP_PoliceBiocode") + wep:Remove() + wep = ply:Give(class, true) + wep.GaveDefaultAmmo = true -- did DefaultClip kill your father or something, arctic? + if biocoded then + wep:SetNWBool("TacRP_PoliceBiocode", true) + end + + if ent.Attachments then + for k, v in pairs(ent.Attachments) do + wep.Attachments[k].Installed = TacRP.Attachments_Index[v] + end + timer.Simple(0.1, function() if IsValid(wep) then wep:NetworkWeapon() end end) + end + + ent.Attachments = nil -- Don't duplicate attachments + + hook.Call("playerPickedUpWeapon", nil, ply, ent, wep) + ent:GivePlayerAmmo(ply, wep, false) + ent:DecreaseAmount() + + return true + end +end) + +hook.Add("onPocketItemDropped", "TacRP_DarkRP", function(wep, ent, item, id) + if ent:GetClass() ~= "spawned_weapon" then return end + if ent.TacRP_PoliceBiocode then + ent:SetNWBool("TacRP_PoliceBiocode", true) + end + local atts = ent.Attachments or {} + if table.Count(atts) > 0 then + net.Start("tacrp_spawnedwepatts") + net.WriteUInt(ent:EntIndex(), 12) -- ent won't exist on client when message arrives + net.WriteUInt(table.Count(atts), 4) + for k, v in pairs(atts) do + net.WriteUInt(k, 4) + net.WriteUInt(v, TacRP.Attachments_Bits) + end + net.Broadcast() + end +end) + +local function hack() + local spawned_weapon = (scripted_ents.GetStored("spawned_weapon") or {}).t + if spawned_weapon and not spawned_weapon.TacRP_Hack then + spawned_weapon.TacRP_Hack = true -- don't make this change multiple times! + local old = spawned_weapon.StartTouch + + function spawned_weapon:StartTouch(ent) + if ent.Attachments then return end + old(self, ent) + end + end + + if DarkRP then + hook.Add("PlayerLoadout", "TacRP_PoliceBiocode", function(ply) + if !TacRP.ConVars["rp_biocode_cp"]:GetBool() or !ply:isCP() then return end + timer.Simple(0.001, function() + if !IsValid(ply) then return end + local jobTable = ply:getJobTable() + for _, v in pairs(jobTable.weapons or {}) do + local wep = ply:GetWeapon(v) + if IsValid(wep) then + wep:SetNWBool("TacRP_PoliceBiocode", true) + wep.TacRP_PoliceBiocode = true + end + end + end) + end) + end +end +hook.Add("InitPostEntity", "TacRP_SpawnedWeaponHack", hack) +hack() + diff --git a/garrysmod/addons/tacrp/lua/tacrp/server/sv_door.lua b/garrysmod/addons/tacrp/lua/tacrp/server/sv_door.lua new file mode 100644 index 0000000..5cf690b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/server/sv_door.lua @@ -0,0 +1,80 @@ +function TacRP.DoorBust(ent, vel, attacker) + if !string.find(ent:GetClass(), "door") then return end + local cvar = TacRP.ConVars["doorbust"]:GetInt() + local t = TacRP.ConVars["doorbust_time"]:GetFloat() + + local oldSpeed = ent:GetInternalVariable("m_flSpeed") + ent:Fire("SetSpeed", tostring(oldSpeed * 10), 0) + ent:Fire("Open", "", 0) + ent:Fire("SetSpeed", oldSpeed, 0.3) + + -- I still can't figure out what exactly crashes on brush doors + if vel:Length() >= 1 and ent:GetPhysicsObject():IsValid() and cvar == 1 and ent:GetClass() == "prop_door_rotating" then + if cvar == 0 or ent.TacRP_DoorBusted then return end + ent.TacRP_DoorBusted = true + + -- Don't remove the door, that's a silly thing to do + ent:SetNoDraw(true) + ent:SetNotSolid(true) + ent.original_pos = ent:GetPos() + + -- Make a busted door prop and fling it + local prop = ents.Create("prop_physics") + prop:SetModel(ent:GetModel()) + prop:SetPos(ent:GetPos()) + prop:SetAngles(ent:GetAngles()) + prop:SetSkin(ent:GetSkin() or 0) + for i = 0, ent:GetNumBodyGroups() do + prop:SetBodygroup(i, ent:GetBodygroup(i) or 0) + end + prop:Spawn() + prop:SetPhysicsAttacker(attacker, 3) + prop:GetPhysicsObject():SetVelocityInstantaneous(vel) + + -- This is necessary to set the render bounds of func doors + timer.Simple(0, function() + + -- Shrink the door collision a little so it will slide through the door frame. Only do it to brush doors or the hl2 one in case some of them have custom collision + -- if prop:GetModel() == "models/props_c17/door01_left.mdl" or string.Left(prop:GetModel(), 1) == "*" then + -- local mins, maxs = prop:GetCollisionBounds() + -- prop:PhysicsInitBox(mins + Vector(2, 2, 2), maxs - Vector(2, 2, 2)) + -- end + + net.Start("tacrp_doorbust") + net.WriteEntity(prop) + net.Broadcast() + end) + + -- Make it not collide with players after a bit cause that's annoying + -- timer.Create("TacRP_DoorBust_" .. prop:EntIndex(), 2, 1, function() + -- if IsValid(prop) then + -- prop:SetCollisionGroup(COLLISION_GROUP_WEAPON) + -- local c = prop:GetColor() + -- prop:SetRenderMode(RENDERMODE_TRANSALPHA) + -- prop:SetColor(Color(c.r, c.g, c.b, c.a * 0.8)) + -- end + -- end) + + -- Reset it after a while + timer.Simple(1, function() + ent:SetPos(ent.original_pos - Vector(0, 0, 100000)) + end) + SafeRemoveEntityDelayed(prop, t) + timer.Create("TacRP_DoorBust_" .. ent:EntIndex(), t, 1, function() + if IsValid(ent) then + ent:SetNoDraw(false) + ent:SetNotSolid(false) + ent.TacRP_DoorBusted = false + ent:SetPos(ent.original_pos) + ent.original_pos = nil + end + end) + else + + timer.Create("TacRP_DoorBust_" .. ent:EntIndex(), 0.5, 1, function() + if IsValid(ent) then + ent.TacRP_DoorBusted = false + end + end) + end +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/server/sv_garbage.lua b/garrysmod/addons/tacrp/lua/tacrp/server/sv_garbage.lua new file mode 100644 index 0000000..d8bfed7 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/server/sv_garbage.lua @@ -0,0 +1,38 @@ +TacRP.ShieldPropPile = {} -- { {Model = NULL, Weapon = NULL} } + +local function SV_TacRP_CollectGarbage() + local removed = 0 + + local newpile = {} + + for _, k in pairs(TacRP.ShieldPropPile) do + if IsValid(k.Weapon) then + table.insert(newpile, k) + + continue + end + + SafeRemoveEntity(k.Model) + + removed = removed + 1 + end + + TacRP.ShieldPropPile = newpile + + if TacRP.Developer() and removed > 0 then + print("Removed " .. tostring(removed) .. " Shield Models") + end +end + +timer.Create("TacRP Shield Model Garbage Collector", 5, 0, SV_TacRP_CollectGarbage) + +hook.Add("PlayerDeath", "TacRP_DeathCleanup", function(ply, inflictor, attacker) + ply:SetNWFloat("TacRPGasEnd", 0) + ply:SetNWFloat("TacRPStunStart", 0) + ply:SetNWFloat("TacRPStunDur", 0) + + local timername = "tacrp_gas_" .. ply:EntIndex() + if timer.Exists(timername) then + timer.Remove(timername) + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/server/sv_net.lua b/garrysmod/addons/tacrp/lua/tacrp/server/sv_net.lua new file mode 100644 index 0000000..d554456 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/server/sv_net.lua @@ -0,0 +1,172 @@ +util.AddNetworkString("tacrp_toggleblindfire") +util.AddNetworkString("tacrp_togglecustomize") +util.AddNetworkString("tacrp_reloadatts") +util.AddNetworkString("tacrp_networkweapon") +util.AddNetworkString("tacrp_attach") +util.AddNetworkString("tacrp_receivepreset") +util.AddNetworkString("tacrp_sendattinv") +util.AddNetworkString("tacrp_sendbullet") +util.AddNetworkString("tacrp_sendconvar") + +util.AddNetworkString("tacrp_updateholster") +util.AddNetworkString("tacrp_clientdamage") +util.AddNetworkString("tacrp_container") +util.AddNetworkString("tacrp_toggletactical") +util.AddNetworkString("tacrp_doorbust") +util.AddNetworkString("tacrp_flashbang") +util.AddNetworkString("tacrp_togglenade") +util.AddNetworkString("tacrp_addshieldmodel") +util.AddNetworkString("tacrp_updateslot") +util.AddNetworkString("tacrp_givenadewep") +util.AddNetworkString("tacrp_reloadlangs") +util.AddNetworkString("tacrp_npcweapon") +util.AddNetworkString("tacrp_drop") + +net.Receive("tacrp_togglenade", function(len, ply) + local bf = net.ReadUInt(TacRP.QuickNades_Bits) + local throw = net.ReadBool() + local under = false + if throw then under = net.ReadBool() end + + local wpn = ply:GetActiveWeapon() + + if !wpn or !IsValid(wpn) or !wpn.ArcticTacRP then return end + + wpn:SelectGrenade(bf) + if throw then + wpn:PrimeGrenade() + wpn.GrenadeThrowOverride = under + end +end) + +net.Receive("tacrp_givenadewep", function(len, ply) + local bf = net.ReadUInt(TacRP.QuickNades_Bits) + local wpn = ply:GetActiveWeapon() + if !wpn or !IsValid(wpn) or !wpn.ArcticTacRP or !TacRP.AreTheGrenadeAnimsReadyYet then return end + + local nade = TacRP.QuickNades[TacRP.QuickNades_Index[bf]] + if !nade or !nade.GrenadeWep or !wpn:CheckGrenade(bf, true) then return end + + ply:Give(nade.GrenadeWep, true) +end) + +net.Receive("tacrp_toggleblindfire", function(len, ply) + local bf = net.ReadUInt(TacRP.BlindFireNetBits) + + local wpn = ply:GetActiveWeapon() + + if !wpn or !IsValid(wpn) or !wpn.ArcticTacRP then return end + + wpn:ToggleBlindFire(bf) +end) + +net.Receive("tacrp_togglecustomize", function(len, ply) + local bf = net.ReadBool() + + local wpn = ply:GetActiveWeapon() + + if !wpn or !IsValid(wpn) or !wpn.ArcticTacRP then return end + + wpn:ToggleCustomize(bf) +end) + +net.Receive("tacrp_toggletactical", function(len, ply) + local wpn = ply:GetActiveWeapon() + + if !wpn or !IsValid(wpn) or !wpn.ArcticTacRP or !wpn:GetValue("CanToggle") then return end + + wpn:SetTactical(!wpn:GetTactical()) +end) + +net.Receive("tacrp_networkweapon", function(len, ply) + local wpn = net.ReadEntity() + + if !wpn.ArcticTacRP then return end + + wpn:NetworkWeapon(ply) +end) + +net.Receive("tacrp_attach", function(len, ply) + local wpn = net.ReadEntity() + + local attach = net.ReadBool() + local slot = net.ReadUInt(8) + local attid = 0 + + if attach then + attid = net.ReadUInt(TacRP.Attachments_Bits) + end + + if ply:GetActiveWeapon() != wpn or !wpn.ArcticTacRP then return end + + if attach then + local att = TacRP.Attachments_Index[attid] + + wpn:Attach(slot, att, true) + else + wpn:Detach(slot, true) + end +end) + +net.Receive("tacrp_receivepreset", function(len, ply) + local wpn = net.ReadEntity() + + if !wpn.ArcticTacRP or wpn:GetOwner() != ply then return end + wpn:ReceivePreset() +end) + +function TacRP.DropWeapon(ply, wep) + if wep.ArcticTacRP and wep:GetValue("PrimaryGrenade") then + -- Grenades don't have a clip size. this would mean players can constantly generate and drop nade sweps that do nothing. + local nade = TacRP.QuickNades[wep:GetValue("PrimaryGrenade")] + if TacRP.IsGrenadeInfiniteAmmo(nade) then + return -- Disallow dropping nades when its infinite + elseif nade.Singleton then + if DarkRP then + local canDrop = hook.Call("canDropWeapon", GAMEMODE, ply, wep) + if !canDrop then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return "" + end + ply:DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_DROP) + ply:dropDRPWeapon(wep) + else + ply:DropWeapon(wep) + end + elseif nade.AmmoEnt and ply:GetAmmoCount(nade.Ammo) > 0 then + ply:RemoveAmmo(1, nade.Ammo) + local ent = ents.Create(nade.AmmoEnt) + ent:SetPos(ply:EyePos() - Vector(0, 0, 4)) + ent:SetAngles(AngleRand()) + ent:Spawn() + if IsValid(ent:GetPhysicsObject()) then + ent:GetPhysicsObject():SetVelocityInstantaneous(ply:EyeAngles():Forward() * 200) + end + if ply:GetAmmoCount(nade.Ammo) == 0 then + wep:Remove() + end + end + else + if DarkRP then + local canDrop = hook.Call("canDropWeapon", GAMEMODE, ply, wep) + if !canDrop then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return "" + end + ply:DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_DROP) + ply:dropDRPWeapon(wep) + --ply:DropWeapon(wep) + else + ply:DropWeapon(wep) + end + end +end + +net.Receive("tacrp_drop", function(len, ply) + if !TacRP.ConVars["allowdrop"]:GetBool() then return end + local wep = ply:GetActiveWeapon() + if !IsValid(wep) or !wep.ArcticTacRP then return end + if !ply:Alive() then return end + + TacRP.DropWeapon(ply, wep) +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/server/sv_util.lua b/garrysmod/addons/tacrp/lua/tacrp/server/sv_util.lua new file mode 100644 index 0000000..9aa162f --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/server/sv_util.lua @@ -0,0 +1,154 @@ +local badseqs = { + ["Unknown"] = true, -- no cower sequence + ["head_pitch"] = true, -- antlion guards + ["Walk_Neutral_South"] = true, -- l4d2 common inf +} + +function TacRP.Flashbang(ent, pos, radius, time_max, time_min, time_stunadd) + time_stunadd = time_stunadd or 0.5 + for _, k in ipairs(ents.FindInSphere(pos, radius)) do + if k:IsPlayer() and TacRP.ConVars["flash_affectplayers"]:GetBool() then + local dist = k:EyePos():Distance(pos) + local dp = (k:EyePos() - pos):Dot(k:EyeAngles():Forward()) + + local time = Lerp( dp, time_max, time_min ) + + time = Lerp( dist / radius, time, time_min ) + + local tr = util.QuickTrace(pos, k:EyePos() - pos, {k, ent}) + + if tr.Fraction < 1 then + time = 0 + else + local wep = k:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP and wep:GetValue("StunResist") then + time = math.sqrt(time) * 0.5 + time_stunadd = math.sqrt(time_stunadd) * 0.5 + end + + k:SetNWFloat("TacRPStunStart", CurTime()) + k:SetNWFloat("TacRPStunDur", time + time_stunadd) + end + + net.Start("tacrp_flashbang") + net.WriteFloat(time) + net.Send(k) + elseif k:IsNPC() and TacRP.ConVars["flash_affectnpcs"]:GetBool() and (k.TacRP_FlashEnd or 0) < CurTime() then + local ret = hook.Run("TacRP_StunNPC", k, ent) + if ret then continue end + local t = time_max + -- stun them if they have a good cower sequence. this doesn't affect npcs like antlion guards, manhacks etc. + if badseqs[k:GetSequenceName(ACT_COWER)] != true then + local tr = util.TraceLine({ + start = pos, + endpos = k:EyePos(), + mask = MASK_SOLID, + filter = {ent, k} + }) + if tr.Fraction == 1 then + k:SetSchedule(SCHED_COWER) + k:RestartGesture(ACT_COWER) + k:SetNPCState(NPC_STATE_NONE) + k.TacRP_FlashEnd = CurTime() + t - 0.01 + timer.Simple(t, function() + if IsValid(k) then + k:SetNPCState(NPC_STATE_ALERT) + end + end) + end + else + local tr = util.TraceLine({ + start = pos, + endpos = k:EyePos(), + mask = MASK_SOLID, + filter = {ent, k} + }) + if tr.Fraction == 1 then + k.TacRP_FlashEnd = CurTime() + t - 0.01 + timer.Create("tacrp_flash_" .. k:EntIndex(), 0.5, math.ceil(t / 0.5), function() + if IsValid(k) then k:SetSchedule(SCHED_STANDOFF) end + end) + k:SetSchedule(SCHED_STANDOFF) + end + end + end + end +end + +local cats = { + ["ClassName"] = true, + ["PrintName"] = true, + ["SubCatTier"] = function(wep) return string.sub(wep.SubCatTier, 1) end, + ["SubCatType"] = function(wep) return string.sub(wep.SubCatType, 1) end, + ["Damage_Max"] = true, + ["Damage_Min"] = true, + ["Raw DPS"] = function(wep) + local valfunc = wep.GetBaseValue + local bfm = wep:GetBestFiremode(true) + local rrpm = wep:GetRPM(true, bfm) + local erpm = rrpm + local pbd = valfunc(wep, "PostBurstDelay") + if bfm < 0 then + erpm = 60 / ((1 / (rrpm / 60)) + (pbd / -bfm)) + end + local num = valfunc(wep, "Num") + local dmg = math.max(valfunc(wep, "Damage_Max"), valfunc(wep, "Damage_Min")) + return math.Round(dmg * num * erpm / 60, 1) + end, + + ["Range_Max"] = true, + ["Range_Min"] = true, + + ["MuzzleVelocity"] = true, + ["ClipSize"] = true, + ["RPM"] = true, + + ["RecoilKick"] = true, + ["RecoilStability"] = true, + ["Spread"] = true, + ["RecoilSpreadPenalty"] = true, + ["HipFireSpreadPenalty"] = true, + + ["MoveSpeedMult"] = true, + ["ShootingSpeedMult"] = true, + ["SightedSpeedMult"] = true, + ["ReloadSpeedMult"] = true, + ["ReloadTime"] = function(wep) return math.Round(wep:GetReloadTime(true), 2) end, + + ["AimDownSightsTime"] = true, + ["SprintToFireTime"] = true, +} + +concommand.Add("tacrp_dev_dumpstats", function() + if !game.SinglePlayer() then return end + local str = "" + local str_l18n = "" + print("Dumping stats, this may lag a bit...") + -- headers + for k, v in pairs(cats) do + str = str .. k .. "," + end + str = string.sub(str, 0, -1) .. "\n" + timer.Simple(0.1, function() + local ply = Entity(1) + for _, class in SortedPairs(TacRP.GetWeaponList()) do + local wpn = ply:Give(class, true) + str_l18n = str_l18n .. "L[\"wep." .. class .. ".name\"] = [[" .. (wpn.PrintName or "") .. "]]" .. "\n" + str_l18n = str_l18n .. "L[\"wep." .. class .. ".desc\"] = [[" .. (wpn.Description or "") .. "]]" .. "\n" + str_l18n = str_l18n .. "L[\"wep." .. class .. ".desc.quote\"] = [[" .. (wpn.Description_Quote or "") .. "]]" .. "\n\n" + for k, v in pairs(cats) do + if v == true then + str = str .. (wpn[k] or "") .. "," + else + str = str .. v(wpn) .. "," + end + end + ply:StripWeapon(class) + str = string.sub(str, 0, -1) .. "\n" + end + file.Write("tacrp_dumpstats.csv", str) + file.Write("tacrp_l18n.txt", str_l18n) + + print("Dumped stats to tacrp_dumpstats.csv") + end) +end, nil, "Collects some stats and dumps them to a CSV file. Singleplayer only, may lag a bit.") diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/default.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/default.lua new file mode 100644 index 0000000..beeff50 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/default.lua @@ -0,0 +1,56 @@ +ATT.PrintName = "Holographic (1.5x)" + +ATT.AdminOnly = false +ATT.InvAtt = "" +ATT.Free = false +ATT.Ignore = true + +ATT.Icon = Material("") +ATT.Description = "Muzzle device that reduces audible report and spread." +ATT.Pros = {""} +ATT.Cons = {""} + +ATT.Model = "" +ATT.WorldModel = "" // optional +ATT.Scale = 1 +ATT.ModelOffset = Vector(0, 0, 0) + +ATT.Category = "" // can be "string" or {"list", "of", "strings"} + +// Return true to FORCE compatibility +// Return false to force deny compatibility +// Return nil to use default behavior +// Cats = Category of slot we're trying to mount to +ATT.Compatibility = function(wpn, cats) +end + +ATT.ActivateElements = {""} + +ATT.SortOrder = 0 + +// Sight stuff +// Allows a custom sight to be defined based on offset from the element's origin + +ATT.SightPos = Vector(0, 0, 0) +ATT.SightAng = Angle(0, 0, 0) + +// Stat modifications are completely automatically handled now. + +// You can do Mult_ before a stat, e.g.: +// Mult_RPM +// Mult_Recoil +// Mult_ScopedSpreadPenalty +// In order to multiply the value. + +// You can do Add_ in a similar way, which will add a value. + +// There is also Override_, which works similarly. +// Override_Priority_ will do what you would expect. + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.5 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.Mult_QuickScopeTime = 0.75 diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_charge.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_charge.lua new file mode 100644 index 0000000..26b758a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_charge.lua @@ -0,0 +1,668 @@ +ATT.PrintName = "Charge" +ATT.FullName = "demoknight tf2" +ATT.Icon = Material("entities/tacrp_att_melee_spec_charge.png", "mips smooth") +ATT.Description = "Advance with reckless abandon, and break some laws of physics too." +ATT.Pros = { "att.pro.melee_spec_charge1", "att.pro.melee_spec_charge2", "att.pro.melee_spec_charge3", "att.pro.melee_spec_charge4" } + +ATT.Category = {"melee_spec"} + +ATT.SortOrder = 5 + +ATT.MeleeCharge = true + +ATT.Override_NoBreathBar = true + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+reload"] = {so = 0.4, str = "hint.melee_charge"} + tbl["+walk/+reload"] = {so = 0.5, str = "hint.melee_charge.menu"} +end + +local followup = 0.75 + +local modecount = 3 +local modes = { + -- {speed, duration, turnrate, damagemult, resistance} + [0] = {700, 1.5, 240, 1, 0.25}, + [1] = {700, 1.5, 960, 0.75, 0}, + [2] = {800, 1.5, 90, 1.25, 0.5}, +} + +local stat_vel = 1 +local stat_dur = 2 +local stat_turn = 3 +local stat_dmg = 4 +local stat_res = 5 +local function chargestats(ply, i) + local mode = ply:GetNWInt("TacRPChargeMode", 0) + if modes[mode] then + if i then + return modes[mode][i] or 0 + else + return modes[mode] + end + else + return 0 + end +end + +local function ChargeFraction(ply) + local g = 0.25 + return 1 - math.Clamp((ply:GetNWFloat("TacRPChargeTime", 0) + chargestats(ply, stat_dur) - CurTime() - g) / (chargestats(ply, stat_dur) - g), 0, 1) +end + +local function wishspeedthreshold() + return 100 * GetConVar("sv_friction"):GetFloat() / GetConVar("sv_accelerate"):GetFloat() +end + +local function entercharge(ply) + ply:SetNWFloat("TacRPChargeStrength", 0) + ply:SetNWBool("TacRPChargeState", true) + ply.TacRPPrevColCheck = ply:GetCustomCollisionCheck() + ply:SetCustomCollisionCheck(true) + ply.TacRPPrevFlag = ply:IsEFlagSet(EFL_NO_DAMAGE_FORCES) + if ply:GetNWInt("TacRPChargeMode", 0) == 2 and !ply.TacRPPrevFlag then + ply:AddEFlags(EFL_NO_DAMAGE_FORCES) + end +end + +local function exitcharge(ply, nohit) + ply:SetNWFloat("TacRPChargeStrength", ChargeFraction(ply)) + ply:SetNWBool("TacRPChargeState", false) + ply:SetNWFloat("TacRPChargeTime", 0) + ply:SetNWFloat("TacRPChargeEnd", CurTime()) + + if IsValid(ply:GetNWEntity("TacRPChargeWeapon")) then + ply:GetNWEntity("TacRPChargeWeapon"):SetBreath(0) + end + + if !nohit then + ply:SetNWFloat("TacRPChargeFollowup", CurTime() + followup) + end + + ply:SetCustomCollisionCheck(ply.TacRPPrevColCheck) + if ply.TacRPPrevFlag then + ply:RemoveEFlags(EFL_NO_DAMAGE_FORCES) + end + ply.TacRPPrevColCheck = nil + ply.TacRPPrevFlag = nil +end + +local function incharge(ply) + return ply:Alive() and ply:GetNWBool("TacRPChargeState", false) +end + +local function activecharge(ply) + return ply:GetNWFloat("TacRPChargeTime", 0) + chargestats(ply, stat_dur) > CurTime() +end + +local function chargemelee(self) + if self:StillWaiting(false, true) then return end + + self:EmitSound(self:ChooseSound(self:GetValue("Sound_MeleeSwing")), 75, 100, 1) + + self:GetOwner():LagCompensation(true) + + local dmg = self:GetValue("MeleeDamage") + * (1 + self:GetMeleePerkDamage()) + * (1 + self:GetOwner():GetNWFloat("TacRPChargeStrength", 0) ^ 1.5 * 2) + local range = 200 + self:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE2) + self:PlayAnimation("melee2", 1, false, true) + + local filter = {self:GetOwner()} + + table.Add(filter, self.Shields) + + local start = self:GetOwner():GetShootPos() + local dir = self:GetOwner():GetAimVector() + local tr = util.TraceLine({ + start = start, + endpos = start + dir * range, + filter = filter, + mask = MASK_SHOT_HULL, + }) + + -- weapon_hl2mpbasebasebludgeon.cpp: do a hull trace if not hit + if tr.Fraction == 1 or !IsValid(tr.Entity) then + local dim = 32 + local pos2 = tr.HitPos - dir * (dim * 1.732) + local tr2 = util.TraceHull({ + start = start, + endpos = pos2, + filter = filter, + mask = MASK_SHOT_HULL, + mins = Vector(-dim, -dim, -dim), + maxs = Vector(dim, dim, dim) + }) + if tr2.Fraction < 1 and IsValid(tr2.Entity) then + local dot = (tr2.Entity:GetPos() - start):GetNormalized():Dot(dir) + if dot >= 0.5 then + tr = tr2 + end + end + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage(dmg) + dmginfo:SetDamageForce(dir * dmg * (1000 + 500 * self:GetMeleePerkDamage())) + dmginfo:SetDamagePosition(tr.HitPos) + dmginfo:SetDamageType(self:GetValue("MeleeDamageType")) + dmginfo:SetAttacker(self:GetOwner()) + dmginfo:SetInflictor(self) + + local t = self:GetValue("MeleeAttackTime") * 2 + + if tr.Fraction < 1 then + TacRP.CancelBodyDamage(tr.Entity, dmginfo, tr.HitGroup) + + if IsValid(tr.Entity) and (tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:IsNextBot()) then + self:EmitSound(self:ChooseSound(self:GetValue("Sound_MeleeHitBody")), 75, 100, 1, CHAN_ITEM) + + if self:GetValue("MeleeBackstab") then + local ang = math.NormalizeAngle(self:GetOwner():GetAngles().y - tr.Entity:GetAngles().y) + if ang <= 60 and ang >= -60 then + dmginfo:ScaleDamage(self:GetValue("MeleeBackstabMult")) + self:EmitSound("tacrp/riki_backstab.wav", 70, 100, 0.4) + end + end + else + self:EmitSound(self:ChooseSound(self:GetValue("Sound_MeleeHit")), 75, 100, 1, CHAN_ITEM) + end + + if IsValid(tr.Entity) and self:GetValue("MeleeSlow") then + tr.Entity.TacRPBashSlow = true + end + + if IsValid(tr.Entity) and !tr.HitWorld then + --tr.Entity:TakeDamageInfo(dmginfo) + tr.Entity:DispatchTraceAttack(dmginfo, tr) + end + end + + self:GetOwner():LagCompensation(false) + + self:SetLastMeleeTime(CurTime()) + self:SetNextSecondaryFire(CurTime() + t) + + self:SetTimer(0.5, function() + self:SetShouldHoldType() + end, "holdtype") +end + +ATT.Hook_PreShoot = function(wep) + if !IsFirstTimePredicted() then return end + local ply = wep:GetOwner() + if wep:StillWaiting() then return end + + if incharge(ply) then + exitcharge(ply, true) + ply:EmitSound("TacRP.Charge.End") + chargemelee(wep) + return true + elseif ply:GetNWFloat("TacRPChargeFollowup", 0) > CurTime() then + chargemelee(wep) + ply:SetNWFloat("TacRPChargeFollowup", 0) + return true + end +end + +ATT.Hook_PreReload = function(wep) + wep.LastHintLife = CurTime() + + if !(game.SinglePlayer() or IsFirstTimePredicted()) then return end + local ply = wep:GetOwner() + + if game.SinglePlayer() and CLIENT then + entercharge(ply) + return + end + + if !incharge(ply) and ply:KeyDown(IN_WALK) and ply:KeyPressed(IN_RELOAD) then + ply:SetNWInt("TacRPChargeMode", (ply:GetNWInt("TacRPChargeMode", 0) + 1) % 3) + return true + end + + if incharge(ply) or !ply:KeyPressed(IN_RELOAD) or ply:GetMoveType() != MOVETYPE_WALK + or wep:GetBreath() < 1 then return end + + wep:SetBreath(0) + -- ply:SetNWFloat("TacRPDashCharge", 0) + ply:SetNWFloat("TacRPChargeTime", CurTime()) + ply:SetNWEntity("TacRPChargeWeapon", wep) + + entercharge(ply) + + wep:SetHoldType("melee2") + + wep:SetTimer(chargestats(ply, stat_dur) + followup, function() + wep:SetShouldHoldType() + end, "holdtype") + + if SERVER then + ply:EmitSound("TacRP.Charge.Windup", 80) + end + + -- so client can draw the effect. blehhhh + if game.SinglePlayer() and SERVER then wep:CallOnClient("Reload") end + + local eff = EffectData() + eff:SetOrigin(ply:GetPos()) + eff:SetNormal(Angle(0, ply:GetAngles().y, 0):Forward()) + eff:SetEntity(ply) + util.Effect("tacrp_chargesmoke", eff) + + return true +end + +--[[] +ATT.Hook_PostThink = function(wep) + local ply = wep:GetOwner() + if (game.SinglePlayer() or IsFirstTimePredicted()) and !incharge(ply) then + ply:SetNWFloat("TacRPDashCharge", math.min(1, ply:GetNWFloat("TacRPDashCharge", 0) + FrameTime() / (wep:GetValue("MeleeDashChargeTime") or 7.5))) + end +end +]] + +function ATT.TacticalDraw(self) + local ply = self:GetOwner() + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(128) + local h = TacRP.SS(8) + + local x = (scrw - w) / 2 + local y = (scrh - h) * 7 / 8 + + surface.SetDrawColor(0, 0, 0, 100) + TacRP.DrawCorneredBox(x, y, w, h) + + x = x + TacRP.SS(1) + y = y + TacRP.SS(1) + w = w - TacRP.SS(2) + h = h - TacRP.SS(2) + + local chargin = incharge(ply) + + local curmode = ply:GetNWInt("TacRPChargeMode", 0) + if !chargin and ply:KeyDown(IN_WALK) then + + local w_mode = w / modecount - TacRP.SS(0.5) * (modecount - 1) + for i = 0, modecount - 1 do + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(curmode == i, curmode == i) + surface.SetDrawColor(c_bg) + TacRP.DrawCorneredBox(x + (i / modecount * w) + (i * TacRP.SS(0.5)), y - TacRP.SS(45), w_mode, TacRP.SS(6), c_cnr) + draw.SimpleText(TacRP:GetPhrase("hint.melee_charge." .. i .. ".name"), "TacRP_HD44780A00_5x8_4", x + (i / modecount * w) + (i * TacRP.SS(0.5)) + w_mode / 2, y - TacRP.SS(45 - 3), c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + surface.SetDrawColor(0, 0, 0, 240) + TacRP.DrawCorneredBox(x, y - TacRP.SS(38), w, TacRP.SS(24)) + + local desc = TacRP.MultiLineText(TacRP:GetPhrase("hint.melee_charge." .. curmode .. ".desc"), w - TacRP.SS(6), "TacRP_Myriad_Pro_6") + for j, text in ipairs(desc or {}) do + draw.SimpleText(text, "TacRP_Myriad_Pro_6", x + TacRP.SS(3), y - TacRP.SS(38 - 2) + (j - 1) * TacRP.SS(6.5), color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + end + + + local c = self:GetBreath() -- math.Clamp(ply:GetNWFloat("TacRPDashCharge", 0), 0, 1) + local d = 1 + + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(chargin, chargin) + surface.SetDrawColor(c_bg) + TacRP.DrawCorneredBox(x + w / 2 - w / 6, y - TacRP.SS(12), w / 3, TacRP.SS(9), c_cnr) + draw.SimpleText(TacRP:GetPhrase("hint.melee_charge." .. curmode .. ".name"), "TacRP_HD44780A00_5x8_6", x + w / 2, y - TacRP.SS(8), c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if chargin then + c = math.Clamp((ply:GetNWFloat("TacRPChargeTime", 0) + chargestats(ply, stat_dur) - CurTime()) / chargestats(ply, stat_dur), 0, 1) + d = 1 - ChargeFraction(ply) + surface.SetDrawColor(255, 255 * d ^ 0.5, 255 * d, 150) + elseif c < 1 then + surface.SetDrawColor(150, 150, 150, 150) + else + surface.SetDrawColor(255, 255, 255, 150) + end + + surface.DrawRect(x, y, w * c, h) + +end + +hook.Add("StartCommand", "TacRP_Charge", function(ply, cmd) + if incharge(ply) then + local ang = cmd:GetViewAngles() + + cmd:SetButtons(bit.band(cmd:GetButtons(), bit.bnot(IN_DUCK + IN_JUMP + IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT + IN_WALK + IN_SPEED))) + cmd:SetForwardMove(chargestats(ply, stat_vel)) + cmd:SetUpMove(0) + cmd:SetSideMove(0) + + ply.TacRPLastAngle = ply.TacRPLastAngle or ang + local max = FrameTime() * chargestats(ply, stat_turn) + cmd:SetViewAngles(Angle(ang.p, math.ApproachAngle(ply.TacRPLastAngle.y, ang.y, max), 0)) + + ply.TacRPLastAngle = cmd:GetViewAngles() + else + ply.TacRPLastAngle = nil + end +end) + +local function TracePlayerBBox(ply, pos0, pos1) + local bb1, bb2 = ply:GetCollisionBounds() + return util.TraceHull({ + start = pos0, + endpos = pos1, + mask = MASK_PLAYERSOLID, + collisiongroup = COLLISION_GROUP_PLAYER_MOVEMENT, + mins = bb1, + maxs = bb2, + filter = ply, + }) +end + +hook.Add("Move", "TacRP_Charge", function(ply, mv) + if incharge(ply) then + + if ply:GetMoveType() != MOVETYPE_WALK then + exitcharge(ply) + return + end + + local pos = mv:GetOrigin() + + -- naive implementation: always move in direction of aim. + -- does not allow for cool stuff like charge strafing + -- local v = mv:GetVelocity().z + -- local speed = mv:GetVelocity():Dot(ply:GetForward()) + -- mv:SetVelocity(ply:GetForward() * math.max(ply:GetRunSpeed() + (ply:IsOnGround() and 450 or 350), speed) + Vector(0, 0, v)) + + -- replicate demoknight charge mechanic + -- https://github.com/OthmanAba/TeamFortress2/blob/master/tf2_src/game/shared/tf/tf_gamemovement.cpp + local wishspeed = chargestats(ply, stat_vel) + local wishdir = ply:GetForward() + local flAccelerate = GetConVar("sv_accelerate"):GetFloat() + + -- Grounded friction is constantly trying to slow us down. Counteract it! + -- TF2 doesn't have to deal with it since it disables friction in-engine. We can't since I don't want to reimplement gamemovement.cpp + if ply:IsOnGround() and ply:WaterLevel() == 0 then + wishspeed = wishspeed / 0.88 -- approximate grounded friciton with magic number + if !game.SinglePlayer() then + wishspeed = wishspeed / 0.88 -- ??????????? + end + end + + -- if our wish speed is too low (attributes), we must increase acceleration or we'll never overcome friction + -- Reverse the basic friction calculation to find our required acceleration + if wishspeed < wishspeedthreshold() then + local flSpeed = mv:GetVelocity():Length() + local flControl = math.min(GetConVar("sv_stopspeed"):GetFloat(), flSpeed) + flAccelerate = (flControl * GetConVar("sv_friction"):GetFloat()) / wishspeed + 1 + end + + -- Accelerate() + -- https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/game/shared/gamemovement.cpp + + local currentspeed = mv:GetVelocity():Dot(wishdir) + local addspeed = wishspeed - currentspeed + + if addspeed > 0 then + accelspeed = math.min(addspeed, flAccelerate * FrameTime() * wishspeed) -- * player->m_surfaceFriction but it seems like it's 1 by default? + + mv:SetVelocity(mv:GetVelocity() + accelspeed * wishdir) + end + + -- The player will double dip on air acceleration since engine also applies its acceleration. + -- Stop engine behavior if we're in mid-air and not colliding. If we collide, let engine kick in. + if !ply:IsOnGround() and ply:WaterLevel() == 0 then + + -- since we are about to override the rest of engine movement code, apply gravity ourselves + mv:SetVelocity(mv:GetVelocity() + physenv.GetGravity() * FrameTime()) + end + + local dest = pos + mv:GetVelocity() * FrameTime() + local tr = TracePlayerBBox(ply, pos, dest) + -- If we made it all the way, then copy trace end as new player position. + if tr.Fraction == 1 then + if !ply:IsOnGround() and ply:WaterLevel() == 0 then + mv:SetOrigin(tr.HitPos) + -- mv:SetVelocity(mv:GetVelocity() - ply:GetBaseVelocity()) -- probably not relevant? + return true + end + else + if SERVER and IsValid(tr.Entity) then + -- use this trace in FinishMove so we can punt/hit whatever is in our way + ply.TacRPChargeTrace = tr + end + + -- Sliding along a wall seems to increase velocity beyond desired value. Clamp it if so + local v2d = mv:GetVelocity() + v2d.z = 0 + if v2d:Length2D() > wishspeed then + mv:SetVelocity(v2d:GetNormalized() * wishspeed + Vector(0, 0, mv:GetVelocity().z)) + end + end + end +end) + +local mins, maxs = Vector(-24, -24, 16), Vector(24, 24, 72) +hook.Add("FinishMove", "TacRP_Charge", function(ply, mv) + -- if true then return end + --if !IsFirstTimePredicted() then return end + if incharge(ply) then + local wep = ply:GetNWEntity("TacRPChargeWeapon") + local d = ChargeFraction(ply) + + -- https://github.com/OthmanAba/TeamFortress2/blob/1b81dded673d49adebf4d0958e52236ecc28a956/tf2_src/game/shared/tf/tf_gamemovement.cpp#L248 + -- some grace time to make point blank charges not awkward. + if ply:GetNWFloat("TacRPChargeTime", 0) + engine.TickInterval() * 5 < CurTime() then + local active = activecharge(ply) + local vel = mv:GetVelocity():Length() + + if !active or vel < chargestats(ply, stat_vel) * 0.4 or IsValid(ply.TacRPTryCollide) then + + local tr = ply.TacRPChargeTrace + local ent = ply.TacRPTryCollide + + if !tr and IsFirstTimePredicted() then + ply:LagCompensation(true) + tr = util.TraceHull({ + start = ply:GetPos(), + endpos = ply:GetPos() + ply:GetForward() * 48, + filter = {ply}, + mask = MASK_PLAYERSOLID, + mins = mins, + maxs = maxs, + ignoreworld = !active + }) + ply:LagCompensation(false) + debugoverlay.Box(tr.HitPos, mins, maxs, FrameTime() * 10, Color(255, 255, 255, 0)) + end + if !IsValid(ent) and tr then + ent = tr.Entity + end + + local grace = false + if IsValid(ent) and ent:GetOwner() != ply then + if SERVER then + local df = math.max(0.25, d) * (1 + wep:GetMeleePerkDamage()) + local dmgscale = chargestats(ply, stat_dmg) + local dmginfo = DamageInfo() + dmginfo:SetDamageForce(ply:GetForward() * 5000 * df) + dmginfo:SetDamagePosition(tr.HitPos) + dmginfo:SetDamageType(DMG_CLUB) + dmginfo:SetAttacker(ply) + dmginfo:SetInflictor(IsValid(wep) and wep or ply) + + local phys = ent:GetPhysicsObject() + if ent:IsPlayer() or ent:IsNPC() or ent:IsNextBot() then + dmginfo:SetDamage((IsValid(wep) and wep:GetValue("MeleeDamage") or 25) * df) + + ent:SetVelocity(ply:GetForward() * 500 * df + Vector(0, 0, 100 + 150 * d)) + ent:SetGroundEntity(NULL) + + -- we may be able to kill the target and go through them + grace = (ent.TacRPNextChargeHit or 0) <= CurTime() and ent:Health() <= dmginfo:GetDamage() * dmgscale + + if grace then + mv:SetVelocity(ply:GetForward() * chargestats(ply, stat_vel)) + elseif d >= 0.5 then + ply:EmitSound("TacRP.Charge.HitFlesh_Range", 80) + else + ply:EmitSound("TacRP.Charge.HitFlesh", 80) + end + elseif IsValid(phys) then + --phys:SetVelocityInstantaneous(ply:GetForward() * math.Rand(15000, 20000) * (d * 0.5 + 0.5) * ((1 / phys:GetMass()) ^ 0.5)) + + -- hitting a prop once will punt it, and we will not stop because of the prop for some time. + -- if we hit it again before the debounce, we probably failed to move it and will stop. + phys:ApplyForceOffset(ply:GetForward() * chargestats(ply, stat_vel) * (d * 0.5 + 0.5) * dmgscale * 10, ply:GetPos()) + + grace = phys:IsMotionEnabled() and ((ent.TacRPNextChargeHit or 0) <= CurTime() or (ply.TacRPGrace or 0) >= CurTime()) + if !grace then + ply:EmitSound("TacRP.Charge.HitWorld", 80) + elseif (ent.TacRPNextChargeHit or 0) <= CurTime() then + dmginfo:SetDamage(math.max(vel, chargestats(ply, stat_vel) * 0.5)) + dmginfo:SetDamageType(DMG_CLUB) + mv:SetVelocity(ply:GetForward() * chargestats(ply, stat_vel) * 0.75) + end + end + + if (ent.TacRPNextChargeHit or 0) <= CurTime() then + dmginfo:ScaleDamage(dmgscale) + ent:TakeDamageInfo(dmginfo) + end + end + elseif tr and tr.HitWorld then + ply:EmitSound("TacRP.Charge.HitWorld", 80) + end + -- In TF2, going below 300 velocity instantly cancels the charge. + -- However, this feels really bad if you're trying to cancel your momentum mid-air! + -- Also works poorly with props + if !active or (!grace and ((tr and tr.Hit) or (ply.TacRPChargeTrace and IsValid(ent) and (ent:IsNPC() or ent:IsPlayer() or ent:IsNextBot())))) then + exitcharge(ply) + + if !game.SinglePlayer() and CLIENT then + if IsValid(ent) and (ent:IsNPC() or ent:IsPlayer() or ent:IsNextBot()) then + if d >= 0.5 then + ply:EmitSound("TacRP.Charge.HitFlesh_Range", 80) + else + ply:EmitSound("TacRP.Charge.HitFlesh", 80) + end + else + ply:EmitSound("TacRP.Charge.HitWorld", 80) + end + end + + if tr.Hit then + util.ScreenShake(tr.HitPos, 20, 125, d * 1, 750) + else + ply:EmitSound("TacRP.Charge.End") -- interrupt sound + end + + if IsValid(wep) and wep.ArcticTacRP then + wep:SetShouldHoldType() + end + elseif grace and IsValid(ent) and (ent.TacRPNextChargeHit or 0) <= CurTime() then + if game.SinglePlayer() then + ent.TacRPNextChargeHit = CurTime() + 0.5 + ply.TacRPGrace = CurTime() + 0.3 + else + ent.TacRPNextChargeHit = CurTime() + 0.3 + ply.TacRPGrace = CurTime() + 0.15 + end + util.ScreenShake(tr.HitPos, 10, 125, 0.25, 750) + ply:EmitSound("physics/body/body_medium_impact_hard" .. math.random(1, 6) .. ".wav") + end + end + + ply.TacRPChargeTrace = nil + ply.TacRPTryCollide = nil + end + end +end) + +hook.Add("EntityTakeDamage", "TacRP_Charge", function(ply, dmginfo) + if !IsValid(dmginfo:GetAttacker()) or !ply:IsPlayer() then return end + local activewep = ply:GetActiveWeapon() + + if !incharge(ply) and ply:GetNWFloat("TacRPChargeEnd") + 0.25 < CurTime() then + if dmginfo:GetAttacker() == ply + and IsValid(activewep) and activewep.ArcticTacRP and activewep:GetValue("MeleeCharge") then + ply:SetVelocity(dmginfo:GetDamageForce() * 0.02) + dmginfo:ScaleDamage(0.5) + end + return + end + + -- immune to physics damage regardless of mode or held weapon + if dmginfo:IsDamageType(DMG_CRUSH) then + return true + end + + -- only get benefits if charging weapon is out + local wep = ply:GetNWEntity("TacRPChargeWeapon") + if !IsValid(wep) or wep != ply:GetActiveWeapon() then return end + + local resist = chargestats(ply, stat_res) + dmginfo:ScaleDamage(1 - resist) +end) + +hook.Add("PostEntityTakeDamage", "TacRP_Charge", function(ent, dmginfo, took) + -- local ply = dmginfo:GetAttacker() + -- local inflictor = dmginfo:GetInflictor() + --[[] + if took and IsValid(inflictor) and inflictor.ArcticTacRP and inflictor:GetValue("MeleeCharge") + and (ent:IsNPC() or ent:IsPlayer() or ent:IsNextBot()) and ent != ply + and ply:IsPlayer() and ply:GetNWInt("TacRPChargeMode", 0) == 1 + and incharge(ply) then + ply:SetNWFloat("TacRPDashCharge", math.min(1, ply:GetNWFloat("TacRPDashCharge", 0) + dmginfo:GetDamage() * (ent:IsPlayer() and 0.01 or 0.005))) + end + ]] + if took and dmginfo:GetAttacker() != ent and !dmginfo:IsFallDamage() and ent:IsPlayer() and incharge(ent) and ent:GetNWInt("TacRPChargeMode", 0) == 1 then + local dur = chargestats(ent, stat_dur) + local left = ent:GetNWFloat("TacRPChargeTime", 0) + dur - CurTime() - dmginfo:GetDamage() * 0.1 * dur + ent:SetNWFloat("TacRPChargeTime", CurTime() - dur + left) + if !incharge(ent) then + exitcharge(ent) + end + end +end) + +-- don't slide off stuff +-- blehhhhhhh +local col = { + [COLLISION_GROUP_NONE] = true, + [COLLISION_GROUP_INTERACTIVE_DEBRIS] = true, + [COLLISION_GROUP_INTERACTIVE] = true, + [COLLISION_GROUP_PLAYER] = true, + [COLLISION_GROUP_BREAKABLE_GLASS] = true, + [COLLISION_GROUP_VEHICLE] = true, + [COLLISION_GROUP_PLAYER_MOVEMENT] = true, + [COLLISION_GROUP_NPC] = true, +} +local mt = { + [MOVETYPE_PUSH] = true, + [MOVETYPE_NONE] = true, +} +local function touchy(ply, ent) + if IsValid(ply) and IsValid(ent) and ply:IsPlayer() and !ent:IsPlayer() and incharge(ply) and ent:GetOwner() != ply and ent:GetParent() != ply and ply:GetParent() != ent + and (!mt[ent:GetMoveType()] and col[ent:GetCollisionGroup()] and IsValid(ent:GetPhysicsObject()) and ent:GetPhysicsObject():IsMotionEnabled()) then + + local diff = ent:GetPos() - ply:GetPos() + if diff:Length() <= ent:BoundingRadius() + ply:BoundingRadius() + 8 then + local dot = diff:GetNormalized():Dot(ply:GetVelocity():GetNormalized()) + if dot >= 0.5 then + ply.TacRPTryCollide = ent + end + end + return true + end +end +hook.Add("ShouldCollide", "TacRP_Charge", function(ent1, ent2) + if IsValid(ent1.TacRPTryCollide) or IsValid(ent2.TacRPTryCollide) then return end + if touchy(ent1, ent2) then return end + if touchy(ent2, ent1) then return end +end) + +hook.Add("DoPlayerDeath", "TacRP_Charge", function(ply) + exitcharge(ply, true) +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_lunge.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_lunge.lua new file mode 100644 index 0000000..3f6e7da --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_lunge.lua @@ -0,0 +1,128 @@ +ATT.PrintName = "Frenzy" +ATT.Icon = Material("entities/tacrp_att_melee_spec_lunge.png", "mips smooth") +ATT.Description = "Close the distance and overwhelm your enemies." +ATT.Pros = { "att.pro.melee_spec_lunge1" } + +ATT.Category = {"melee_spec"} + +ATT.SortOrder = 3 + +-- ATT.Lifesteal = 1 / 4 + + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+reload"] = {so = 0.4, str = "hint.melee_lunge"} +end + +local chargeamt = 0.5 +ATT.Override_BreathSegmentSize = chargeamt + +local function getcharge(wep) + return wep:GetBreath() +end + +local function setcharge(wep, f) + wep:SetBreath(math.Clamp(f, 0, 1)) +end + +local leapsoundstatus = false +local leapsound = "" + +local function makesound(ent, pitch) + if TacRP.ShouldWeFunny() then + ent:EmitSound("tacrp/amongus_loud.mp3", 75, pitch) + else + if istable(leapsound) then + leapsound = table.Random(leapsound) + end + ent:EmitSound(leapsound, 75, pitch) + end +end + +ATT.Hook_PreReload = function(wep) + local ply = wep:GetOwner() + + if wep:GetValue("Sound_Lunge") then + leapsound = wep:GetValue("Sound_Lunge") + end + + if !ply:KeyPressed(IN_RELOAD) or ply:GetMoveType() == MOVETYPE_NOCLIP + or getcharge(wep) < chargeamt --ply:GetNWFloat("TacRPDashCharge", 0) < chargeamt + or (ply.TacRPNextLunge or 0) > CurTime() then return end + + ply.TacRPNextLunge = CurTime() + 1.5 + -- ply:SetNWFloat("TacRPDashCharge", ply:GetNWFloat("TacRPDashCharge", 0) - chargeamt) + ply:SetNWFloat("TacRPLastLeap", CurTime()) + setcharge(wep, getcharge(wep) - chargeamt) + + local ang = Angle(ply:GetAngles().p, ply:GetAngles().y, 0) + local vel + local mult = wep:GetMeleePerkSpeed() + + if ply:IsOnGround() then + ang.p = math.min(0, ang.p) + vel = ang:Forward() * mult * (400 + math.max(0, 400 - ang:Forward():Dot(ply:GetVelocity()))) + Vector(0, 0, 250) + if SERVER then + makesound(ply, 92) + end + else + local int = math.Clamp(ply:GetVelocity():Dot(ply:GetAngles():Forward()) ^ 0.9, 0, 500) + vel = ply:GetVelocity() * -1 + ply:GetAngles():Forward() * mult * (int + 600) + if SERVER then + makesound(ply, 110) + end + end + + ply:ViewPunch(Angle(-5, 0, 0)) + + ply:DoCustomAnimEvent(PLAYERANIMEVENT_JUMP, -1) + + ply:SetVelocity(vel) + + -- so client can draw the effect. blehhhh + if game.SinglePlayer() and SERVER then wep:CallOnClient("Reload") end + + local eff = EffectData() + eff:SetOrigin(ply:GetPos()) + eff:SetNormal(vel:GetNormalized()) + eff:SetEntity(ply) + util.Effect("tacrp_leapsmoke", eff) + + return true +end + +-- ATT.Hook_PostThink = function(wep) +-- local ply = wep:GetOwner() +-- if (game.SinglePlayer() or IsFirstTimePredicted()) and ply:GetNWFloat("TacRPLastLeap", 0) + 1 < CurTime() then +-- ply:SetNWFloat("TacRPDashCharge", math.min(1, ply:GetNWFloat("TacRPDashCharge", 0) + FrameTime() / (wep:GetValue("MeleeDashChargeTime") or 7.5))) +-- end +-- end + +--[[] +function ATT.TacticalDraw(self) + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(128) + local h = TacRP.SS(8) + + local x = (scrw - w) / 2 + local y = (scrh - h) * 7 / 8 + + surface.SetDrawColor(0, 0, 0, 100) + TacRP.DrawCorneredBox(x, y, w, h) + + x = x + TacRP.SS(1) + y = y + TacRP.SS(1) + w = w - TacRP.SS(2) + h = h - TacRP.SS(2) + + local c = math.Clamp(self:GetOwner():GetNWFloat("TacRPDashCharge", 0), 0, 1) + + surface.SetDrawColor(255, 255, 255, 100) + surface.DrawRect(x, y, w * c, h) + + surface.SetDrawColor(255, 255, 255, 200) + surface.DrawLine(x + w * chargeamt, y, x + w * chargeamt, y + h) +end +]] diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_nade.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_nade.lua new file mode 100644 index 0000000..0917ec4 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_nade.lua @@ -0,0 +1,26 @@ +ATT.PrintName = "Bomber" +ATT.FullName = "Bombardier" +ATT.Icon = Material("entities/tacrp_att_melee_spec_nade.png", "mips smooth") +ATT.Description = "Use jury rigged impact grenades to ruin someone's day." +ATT.Pros = { "att.pro.melee_spec_nade1", "att.pro.melee_spec_nade2" } +ATT.Cons = { "att.con.melee_spec_nade" } + +ATT.Category = {"melee_spec"} +ATT.Free = false + +ATT.SortOrder = 4 + +ATT.Hook_PreReload = function(self) + self.GrenadeMenuKey = IN_RELOAD + if game.SinglePlayer() and SERVER then + self:CallOnClient("Reload") + end + return true +end + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+reload"] = {so = 0.4, str = "hint.quicknade.menu"} +end + +ATT.QuickNadeTryImpact = true +-- ATT.Mult_QuickNadeTimeMult = 1.5 diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_ninja.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_ninja.lua new file mode 100644 index 0000000..0c2d369 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_ninja.lua @@ -0,0 +1,308 @@ +ATT.PrintName = "Ninja" +ATT.Icon = Material("entities/tacrp_att_melee_spec_ninja.png", "mips smooth") +ATT.Description = "Disrupt your enemies and strike with the element of surprise." +ATT.Pros = { "att.pro.melee_spec_ninja1", "att.pro.melee_spec_ninja2", "att.pro.melee_spec_ninja3", "att.pro.melee_spec_ninja4" } + +ATT.Category = {"melee_spec"} + +ATT.SortOrder = 2 + +ATT.NoFallDamage = false +ATT.SilentFootstep = true + +local cost = 1 / 5 +ATT.Override_BreathSegmentSize = cost + +local function getcharge(wep) + -- return ply:GetNWFloat("TacRPDashCharge", 0) + return wep:GetBreath() +end + +local function setcharge(wep, f) + -- ply:SetNWFloat("TacRPDashCharge", f) + wep:SetBreath(math.Clamp(f, 0, 1)) +end + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+reload"] = {so = 0.4, str = "hint.melee_ninja.palm"} + if self:GetOwner():IsOnGround() then + tbl["+duck/+reload"] = {so = 0.5, str = "hint.melee_ninja.backhop"} + else + tbl["+duck/+reload"] = {so = 0.5, str = "hint.melee_ninja.divekick"} + end +end + +local smokedelay = 10 + +local function makehitsound(ent) + if TacRP.ShouldWeFunny() then + ent:EmitSound("tacrp/slap.mp3", 75, 100) + else + ent:EmitSound("tacrp/weapons/melee_body_hit-" .. math.random(1, 5) .. ".wav", 75, 100, 1, CHAN_ITEM) + end +end + +-- local function makesmokesound(ent, pitch) +-- if TacRP.ShouldWeFunny() then +-- ent:EmitSound("tacrp/fart-with-reverb.mp3", 75, pitch) +-- else +-- ent:EmitSound("TacRP/weapons/grenade/smoke_explode-1.wav", 75, pitch) +-- end +-- end + +ATT.Hook_Recharge = function(wep) + if wep:GetOwner():GetNWBool("TacRPNinjaDive") then return true end +end + +ATT.Hook_PreReload = function(wep) + wep.LastHintLife = CurTime() + local ply = wep:GetOwner() + if !ply:KeyPressed(IN_RELOAD) then return end + + if ply:IsOnGround() and ply:Crouching() then + if ply:GetNWFloat("TacRPDiveTime", 0) + 1 > CurTime() or getcharge(wep) < cost * 1.5 then return true end + --[[] + if ply:GetNWFloat("TacRPNinjaSmoke", 0) <= CurTime() and SERVER then + makesmokesound(ply, 110) + local cloud = ents.Create( "tacrp_smoke_cloud_ninja" ) + if !IsValid(cloud) then return end + cloud:SetPos(ply:GetPos()) + cloud:SetOwner(ply) + cloud:Spawn() + ply:SetNWFloat("TacRPNinjaSmoke", CurTime() + smokedelay) + else + wep:EmitSound("npc/fast_zombie/claw_miss1.wav", 75, 105, 1) + end + ]] + setcharge(wep, getcharge(wep) - cost * 1.5) + wep:EmitSound("npc/fast_zombie/claw_miss1.wav", 75, 105, 1) + local ang = ply:GetAngles() + if ang.p >= -15 then + ang.p = math.Clamp(ang.p, 45, 180 - 45) + ply:SetVelocity(ang:Forward() * -math.max(100, 400 + 400 * wep:GetValue("MeleePerkAgi") - ply:GetVelocity():Length())) + end + ply:SetNWFloat("TacRPDiveTime", CurTime()) + elseif !ply:IsOnGround() and ply:Crouching() then + if ply:GetMoveType() != MOVETYPE_NOCLIP and !ply:GetNWBool("TacRPNinjaDive") and ply:GetNWFloat("TacRPDiveTime", 0) + 0.5 < CurTime() and ply:EyeAngles():Forward():Dot(Vector(0, 0, 1)) < -0.25 then + ply:SetNWBool("TacRPNinjaDive", true) + ply:SetNWFloat("TacRPDiveTime", CurTime()) + ply:SetNWVector("TacRPDiveDir", ply:EyeAngles():Forward() * Lerp(getcharge(wep), 30000, 100000) * Lerp(wep:GetValue("MeleePerkAgi"), 0.6, 1.4)) + wep:EmitSound("weapons/mortar/mortar_fire1.wav", 65, 120, 0.5) + setcharge(wep, 0) + end + elseif !wep:StillWaiting() and getcharge(wep) >= cost then + wep:SetNextSecondaryFire(CurTime() + 0.8) + wep:PlayAnimation("halt", 0.4, false, true) + -- wep:EmitSound("ambient/energy/weld2.wav", 75, 110, 0.5) + wep:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST) + wep:EmitSound("npc/fast_zombie/claw_miss1.wav", 75, 90, 1) + + local dmg = 20 + local range = 72 + local filter = {wep:GetOwner()} + + local start = wep:GetOwner():GetShootPos() + local dir = wep:GetOwner():GetAimVector() + local tr = util.TraceLine({ + start = start, + endpos = start + dir * range, + filter = filter, + mask = MASK_SHOT_HULL, + }) + + -- weapon_hl2mpbasebasebludgeon.cpp: do a hull trace if not hit + if tr.Fraction == 1 or !IsValid(tr.Entity) then + local dim = 48 + local pos2 = tr.HitPos - dir * (dim * 1.732) + local tr2 = util.TraceHull({ + start = start, + endpos = pos2, + filter = filter, + mask = MASK_SHOT_HULL, + mins = Vector(-dim, -dim, -dim), + maxs = Vector(dim, dim, dim) + }) + if tr2.Fraction < 1 and IsValid(tr2.Entity) then + local dot = (tr2.Entity:GetPos() - start):GetNormalized():Dot(dir) + if dot >= 0 then + tr = tr2 + end + end + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage(dmg) + dmginfo:SetDamageForce(dir * dmg * 1000) + dmginfo:SetDamagePosition(tr.HitPos) + dmginfo:SetDamageType(DMG_CLUB + DMG_SONIC) + dmginfo:SetAttacker(wep:GetOwner()) + dmginfo:SetInflictor(wep) + + if tr.Fraction < 1 then + + TacRP.CancelBodyDamage(tr.Entity, dmginfo, tr.HitGroup) + + if tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:IsNextBot() then + setcharge(wep, getcharge(wep) - cost) + makehitsound(wep) + tr.Entity.PalmPunched = true + else + local vel = ply:GetVelocity().z --:Length() + wep:EmitSound("tacrp/weapons/melee_hit-" .. math.random(1, 2) .. ".wav", 75, 100, 1, CHAN_ITEM) + -- if ply:IsOnGround() and math.abs(tr.HitNormal:Dot(Vector(0, 0, 1))) <= 0.25 and (tr.Normal:Dot(ply:GetAimVector())) >= 0.5 and vel <= 250 then + -- ply:SetVelocity(Vector(0, 0, Lerp(vel / 250, 500, 250))) + -- end + if math.abs(tr.HitNormal:Dot(Vector(0, 0, 1))) <= 0.25 and (tr.Normal:Dot(ply:GetAimVector())) >= 0.5 then + setcharge(wep, getcharge(wep) - cost) + ply:SetVelocity(Vector(0, 0, Lerp(vel / 200, 400, 200) * Lerp(wep:GetValue("MeleePerkAgi"), 0.75, 1.25))) + end + end + + if IsValid(tr.Entity) and !tr.HitWorld then + --tr.Entity:TakeDamageInfo(dmginfo) + tr.Entity:DispatchTraceAttack(dmginfo, tr) + end + + wep:SetNextSecondaryFire(CurTime() + 0.4) + end + + -- local dir = ply:EyeAngles():Forward() + -- for _, ent in pairs(ents.FindInSphere(ply:EyePos() + dir * 64, 256)) do + -- if ent == ply or !IsValid(ent:GetPhysicsObject()) then continue end + -- ent:GetPhysicsObject():SetVelocityInstantaneous(3000 * dir) + -- end + end + + return true +end + +--[[] +local smokeicon = Material("TacRP/grenades/smoke.png", "mips smooth") +function ATT.TacticalDraw(self) + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(16) + local h = TacRP.SS(16) + + local x = (scrw - w) / 2 + local y = (scrh - h) - TacRP.SS(8) + + surface.SetDrawColor(0, 0, 0, 200) + TacRP.DrawCorneredBox(x, y, w, h) + + local c = math.Clamp((self:GetOwner():GetNWFloat("TacRPNinjaSmoke", 0) - CurTime()) / smokedelay, 0, 1) + surface.SetDrawColor(150, 150, 150, 75) + surface.DrawRect(x, y + h * (1 - c), w, h * c) + + local clr = c > 0 and 200 or 255 + surface.SetDrawColor(clr, clr, clr, 255) + surface.SetMaterial(smokeicon) + surface.DrawTexturedRect(x, y, w, h) +end +]] + + +hook.Add("FinishMove", "TacRP_Ninja", function(ply, mv) + if ply:GetNWBool("TacRPNinjaDive") then + if ply:IsOnGround() and !ply.TacRPNinjaGroundTime then + ply.TacRPNinjaGroundTime = CurTime() + end + if (ply:IsOnGround() and ply.TacRPNinjaGroundTime + engine.TickInterval() < CurTime()) or !ply:Alive() or ply:GetMoveType() == MOVETYPE_NOCLIP then + ply:SetNWBool("TacRPNinjaDive", false) + mv:SetVelocity(mv:GetAngles():Forward() * mv:GetVelocity():Length() * 2) + ply.TacRPNinjaGroundTime = nil + elseif ply:GetNWFloat("TacRPDiveTime", 0) + 0.1 > CurTime() then + mv:SetVelocity(ply:GetNWVector("TacRPDiveDir") * FrameTime()) + + -- do it here to get around reload not called clientside in SP + if (ply.TacRPDiveEffect or 0) != ply:GetNWFloat("TacRPDiveTime", -1) then + ply.TacRPDiveEffect = CurTime() + local eff = EffectData() + eff:SetOrigin(ply:GetPos()) + eff:SetNormal(ply:GetNWVector("TacRPDiveDir", ply:EyeAngles():Forward())) + eff:SetEntity(ply) + util.Effect("tacrp_divesmoke", eff) + end + end + end +end) + +hook.Add("GetFallDamage", "TacRP_Ninja", function(ply, speed) + if ply:GetNWBool("TacRPNinjaDive") then + if ply:GetNWFloat("TacRPDiveTime", 0) + 0.2 <= CurTime() then + ply:EmitSound("physics/concrete/boulder_impact_hard" .. math.random(1, 4) .. ".wav") + local eff = EffectData() + eff:SetOrigin(ply:GetPos()) + eff:SetScale(128) + eff:SetEntity(ply) + util.Effect("ThumperDust", eff) + + local dmginfo = DamageInfo() + dmginfo:SetDamage(math.Clamp((speed - 300) / 30, 20, 70)) + dmginfo:SetDamageForce(Vector(0, 0, 3000)) + dmginfo:SetDamagePosition(ply:GetPos()) + dmginfo:SetDamageType(DMG_CRUSH) + dmginfo:SetAttacker(ply) + dmginfo:SetInflictor(ply) + + for _, ent in pairs(ents.FindInSphere(ply:GetPos(), 256)) do + if ent == ply or !IsValid(ent:GetPhysicsObject()) then continue end + if ent:IsPlayer() or ent:IsNPC() then + ent:SetVelocity(Vector(0, 0, math.Clamp(speed / 4, 250, 1000))) + else + ent:GetPhysicsObject():ApplyTorqueCenter(VectorRand() * speed ^ 0.5) + ent:GetPhysicsObject():ApplyForceOffset(Vector(0, 0, speed) * (ent:GetPhysicsObject():GetMass() ^ 0.5), ply:GetPos()) + end + ent:TakeDamageInfo(dmginfo) + end + + if IsValid(ply:GetGroundEntity()) and (ply:GetGroundEntity():IsPlayer() or ply:GetGroundEntity():IsNPC() or ply:GetGroundEntity():IsNextBot()) then + ply:SetVelocity(Vector(0, 0, 300)) + ply:GetGroundEntity().GoombaStomped = true + dmginfo:SetDamage(999) + dmginfo:SetDamageType(DMG_CRUSH + DMG_NEVERGIB) + dmginfo:SetDamageForce(Vector(0, 0, math.min(speed / 10, 500))) + dmginfo:SetDamagePosition(ply:GetPos()) + ply:GetGroundEntity():TakeDamageInfo(dmginfo) + ply:EmitSound("tacrp/mario_coin.wav", 80, 100, 0.2) + end + + ply:SetNWBool("TacRPNinjaDive", false) + ply.TacRPNinjaGroundTime = nil + end + return 0 + end +end) + +hook.Add("PlayerFootstep", "TacRP_Ninja", function(ply) + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP and wep:GetValue("SilentFootstep") then + return true + end +end) + +hook.Add("PostEntityTakeDamage", "TacRP_GoombaStomp", function(ent, dmginfo, took) + if took and ent.GoombaStomped == true and dmginfo:GetDamageType() == DMG_CRUSH + DMG_NEVERGIB then + if ent:Health() < 0 then + ent:EmitSound("tacrp/mario_death.wav", 100, 100, 0.5) + end + ent.GoombaStomped = false + end + + if took and ent.PalmPunched and dmginfo:GetDamageType() == DMG_CLUB + DMG_SONIC then + ent.PalmPunched = false + ent:SetNWFloat("TacRPLastBashed", CurTime()) + if ent:IsPlayer() then + net.Start("tacrp_flashbang") + net.WriteFloat(0.5) + net.Send(ent) + ent:SetVelocity(Vector(0, 0, 250)) + else + local dir = dmginfo:GetAttacker():EyeAngles():Forward() + dir.z = 0 + dir:Normalize() + ent:SetVelocity(dir * 500 + Vector(0, 0, 250)) + end + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_scout.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_scout.lua new file mode 100644 index 0000000..f5949e8 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_scout.lua @@ -0,0 +1,150 @@ +ATT.PrintName = "Scout" +ATT.FullName = "jerma tf2" +ATT.Icon = Material("entities/tacrp_att_melee_spec_scout.png", "mips smooth") +ATT.Description = "Grass grows, sun shines, birds fly, and brotha' - I hurt people." +ATT.Pros = { "att.pro.melee_spec_scout1", "att.pro.melee_spec_scout2", "att.pro.melee_spec_scout3" } + +ATT.Category = {"melee_spec"} + +ATT.SortOrder = 6 + +ATT.DoubleJump = true + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+reload"] = {so = 0.4, str = "Launch Ball"} +end + +local jumpcost = 1 / 5 +ATT.Override_BreathSegmentSize = jumpcost + + +local balldelay = 5 + +ATT.Hook_PreReload = function(wep) + local ply = wep:GetOwner() + if ply:GetNWFloat("TacRPScoutBall", 0) > CurTime() then return end + ply:SetNWFloat("TacRPScoutBall", CurTime() + balldelay) + + wep:SetNextPrimaryFire(CurTime() + 0.3) + wep:PlayAnimation("melee", 0.5, false, true) + wep:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE2) + + wep:EmitSound("tacrp/sandman/bat_baseball_hit" .. math.random(1, 2) .. ".wav") + + if SERVER then + local src, ang = ply:GetShootPos(), wep:GetShootDir() --+ Angle(-1, 0, 0) + local force = 2000 + local rocket = ents.Create("tacrp_proj_ball") + rocket.Inflictor = wep + rocket:SetPos(src) + rocket:SetOwner(ply) + rocket:SetAngles(ang) + rocket:Spawn() + rocket:SetPhysicsAttacker(ply, 10) + + local phys = rocket:GetPhysicsObject() + + if phys:IsValid() then + phys:AddVelocity(ang:Forward() * force) + phys:SetAngleVelocityInstantaneous(VectorRand() * 500) + end + end + + return true +end + +local ballicon = Material("TacRP/grenades/baseball.png", "mips smooth") +local lastc = 0 +function ATT.TacticalDraw(self) + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(16) + local h = TacRP.SS(16) + + local x = (scrw - w) / 2 + local y = (scrh - h) * 7 / 8 + + surface.SetDrawColor(0, 0, 0, 200) + TacRP.DrawCorneredBox(x, y, w, h) + + local c = math.Clamp((self:GetOwner():GetNWFloat("TacRPScoutBall", 0) - CurTime()) / balldelay, 0, 1) + surface.SetDrawColor(150, 150, 150, 75) + surface.DrawRect(x, y + h * (1 - c), w, h * c) + + if c == 0 and lastc > 0 then + LocalPlayer():EmitSound("tacrp/sandman/recharged.wav") + end + lastc = c + + local clr = c > 0 and 200 or 255 + surface.SetDrawColor(clr, clr, clr, 255) + surface.SetMaterial(ballicon) + surface.DrawTexturedRect(x, y, w, h) +end + +local function GetMoveVector(mv) + local ang = mv:GetAngles() + + local max_speed = mv:GetMaxSpeed() + + local forward = math.Clamp(mv:GetForwardSpeed(), -max_speed, max_speed) + local side = math.Clamp(mv:GetSideSpeed(), -max_speed, max_speed) + + local abs_xy_move = math.abs(forward) + math.abs(side) + + if abs_xy_move == 0 then + return Vector(0, 0, 0) + end + + local mul = max_speed / abs_xy_move + + local vec = Vector() + + vec:Add(ang:Forward() * forward) + vec:Add(ang:Right() * side) + + vec:Mul(mul) + + return vec +end +hook.Add("SetupMove", "tacrp_melee_spec_scout", function(ply, mv) + local wep = ply:GetActiveWeapon() + if !IsValid(wep) or !wep.ArcticTacRP or !wep:GetValue("DoubleJump") then return end + if ply:OnGround() or ply:GetMoveType() != MOVETYPE_WALK then + ply:SetNWBool("TacRPDoubleJump", true) + return + end + + if !mv:KeyPressed(IN_JUMP) then + return + end + + local add = Lerp(wep:GetValue("MeleePerkAgi"), 0.8, 1.4) + if IsFirstTimePredicted() then + if !ply:GetNWBool("TacRPDoubleJump") then + if wep:GetBreath() < jumpcost then return end + wep:SetBreath(wep:GetBreath() - jumpcost) + else + ply:SetNWBool("TacRPDoubleJump", false) + end + end + + local vel = GetMoveVector(mv) + + vel.z = ply:GetJumpPower() * add + + mv:SetVelocity(vel) + + ply:DoCustomAnimEvent(PLAYERANIMEVENT_JUMP , -1) + + local eff = EffectData() + eff:SetOrigin(ply:GetPos() + Vector(0, 0, 0)) + eff:SetNormal(Vector(0, 0, 1)) + eff:SetEntity(ply) + util.Effect("tacrp_jumpsmoke", eff) +end) + +ATT.Hook_Recharge = function(wep) + if !wep:GetOwner():GetNWBool("TacRPDoubleJump") then return true end +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_step.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_step.lua new file mode 100644 index 0000000..51094d5 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_spec_step.lua @@ -0,0 +1,143 @@ +ATT.PrintName = "Airdash" +ATT.Icon = Material("entities/tacrp_att_melee_spec_step.png", "mips smooth") +ATT.Description = "Mobility tool used by blood-fueled robots and transgender women." +ATT.Pros = { "att.pro.melee_spec_step1", "att.pro.melee_spec_step2" } + +ATT.Category = {"melee_spec"} + +ATT.SortOrder = 1 + +ATT.Airdash = true +ATT.NoFallDamage = false + +-- ATT.Add_MeleeRechargeRate = 0.5 + +local duration = 0.25 + +local cost = 1 / 3 +ATT.Override_BreathSegmentSize = cost + +local function makedashsound(ent, pitch) + if TacRP.ShouldWeFunny() then + ent:EmitSound("tacrp/vineboom.mp3", 75, pitch) + else + ent:EmitSound("player/suit_sprint.wav", 75, pitch) + + end +end + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+reload"] = {so = 0.4, str = "hint.melee_step"} +end + +local function getcharge(wep) + -- return ply:GetNWFloat("TacRPDashCharge", 0) + return wep:GetBreath() +end + +local function setcharge(wep, f) + -- ply:SetNWFloat("TacRPDashCharge", f) + wep:SetBreath(math.Clamp(f, 0, 1)) +end + +ATT.Hook_PreReload = function(wep) + local ply = wep:GetOwner() + + if ply:GetNWFloat("TacRPDashTime", -1) + 0.25 > CurTime() + or !ply:KeyPressed(IN_RELOAD) + or ply:GetMoveType() == MOVETYPE_NOCLIP + or getcharge(wep) < cost then return end + + setcharge(wep, getcharge(wep) - cost) + -- ply:SetNWFloat("TacRPDashCharge", ply:GetNWFloat("TacRPDashCharge", 0) - 1 / 3) + ply:SetNWVector("TacRPDashDir", Vector()) + ply:SetNWFloat("TacRPDashTime", CurTime()) + ply:SetNWFloat("TacRPDashSpeed", 1.5 + wep:GetValue("MeleePerkAgi") * 1) + ply:SetNWBool("TacRPDashFall", false) + + if SERVER then + makedashsound(ply, 95) + end + + return true +end + +-- ATT.Hook_PostThink = function(wep) +-- local ply = wep:GetOwner() +-- if IsFirstTimePredicted() and ply:KeyPressed(IN_DUCK) and !ply:IsOnGround() and !ply:GetNWBool("TacRPDashFall") then +-- local dot = ply:GetAimVector():Dot(Vector(0, 0, -1)) +-- if dot > 0.707 then +-- ply:SetVelocity(-Vector(0, 0, ply:GetVelocity().z + 500 + wep:GetValue("MeleePerkAgi") * 500)) +-- ply:SetNWBool("TacRPDashFall", true) +-- end +-- end +-- end + +hook.Add("SetupMove", "TacRP_Quickstep", function(ply, mv, cmd) + if !IsFirstTimePredicted() then return end + if ply:GetNWFloat("TacRPDashTime", -1) + duration > CurTime() then + if !ply.TacRPDashDir and !ply.TacRPDashCancel then + ply.TacRPDashDir = TacRP.GetCmdVector(cmd, true) + ply.TacRPDashStored = ply:GetVelocity():Length() + ply.TacRPDashCancel = nil + ply.TacRPDashPending = true + ply.TacRPDashGrounded = ply:IsOnGround() + + local f, s = cmd:GetForwardMove(), cmd:GetSideMove() + if math.abs(f + s) == 0 then f = 10000 end + + ply:ViewPunch(Angle(f / 2500, s / -5000, s / 2500)) + + ply:SetVelocity(ply.TacRPDashDir * ply:GetRunSpeed() * ply:GetNWFloat("TacRPDashSpeed", 4) * (ply:IsOnGround() and 3 or 1)) + + local eff = EffectData() + eff:SetOrigin(ply:GetPos()) + eff:SetNormal(ply.TacRPDashDir) + eff:SetEntity(ply) + util.Effect("tacrp_dashsmoke", eff) + end + + if ply.TacRPDashGrounded and ply.TacRPDashCancel == nil and cmd:KeyDown(IN_JUMP) then + -- ply:SetVelocity(ply.TacRPDashDir * ply:GetRunSpeed() * 1 + Vector(0, 0, 5 * ply:GetJumpPower())) + ply.TacRPDashGrounded = false + ply.TacRPDashCancel = CurTime() + ply:SetNWFloat("TacRPDashTime", -1) + end + elseif ply:GetNWFloat("TacRPDashTime", -1) + duration <= CurTime() then + if ply.TacRPDashCancel != nil and CurTime() - ply.TacRPDashCancel > 0 and !ply:IsOnGround() then + ply:SetVelocity(ply:GetVelocity():GetNegated() + ply.TacRPDashDir * ply:GetRunSpeed() * 2.5 + Vector(0, 0, 2 * ply:GetJumpPower())) + ply.TacRPDashCancel = nil + ply.TacRPDashDir = nil + elseif ply.TacRPDashDir and ply.TacRPDashCancel == nil then + ply.TacRPDashDir = nil + if !ply:IsOnGround() then + ply:SetVelocity(ply:GetVelocity():GetNegated() / 2) + end + -- elseif ply:IsOnGround() and ply:GetNWBool("TacRPDashFall") then + -- ply:SetNWBool("TacRPDashFall", false) + end + end +end) + +hook.Add("FinishMove", "TacRP_Quickstep", function(ply, mv) + if ply:GetNWFloat("TacRPDashTime", -1) + duration > CurTime() and ply.TacRPDashCancel == nil then + local v = mv:GetVelocity() + v.z = 0 + mv:SetVelocity(v) + end +end) + +hook.Add("EntityTakeDamage", "TacRP_Quickstep", function(ent, dmginfo) + if !ent:IsPlayer() or ent:GetNWFloat("TacRPDashTime", -1) + duration <= CurTime() then return end + ent:EmitSound("weapons/fx/nearmiss/bulletltor0" .. math.random(3, 4) .. ".wav") + local eff = EffectData() + eff:SetOrigin(dmginfo:GetDamagePosition()) + eff:SetNormal(-dmginfo:GetDamageForce():GetNormalized()) + util.Effect("StunstickImpact", eff) + return true +end) + +hook.Add("GetFallDamage", "TacRP_Quickstep", function(ply, speed) + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP and (wep:GetValue("NoFallDamage")) then return true end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_block.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_block.lua new file mode 100644 index 0000000..eb69ad7 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_block.lua @@ -0,0 +1,234 @@ +ATT.PrintName = "Guard" +ATT.FullName = "High Guard" + +ATT.Icon = Material("entities/tacrp_att_melee_tech_block.png", "mips smooth") +ATT.Description = "Defense is the best offense. It is, coinicidently, also the best defense." +ATT.Pros = { "att.pro.melee_tech_block1", "att.pro.melee_tech_block2" } + +ATT.Category = {"melee_tech"} + +ATT.SortOrder = 2 + +ATT.MeleeBlock = true +ATT.HeavyAttack = true + +ATT.Free = true + +local function hold(wep) + return wep:GetOwner():KeyDown(IN_ATTACK2) + and wep:GetHoldBreathAmount() > 0 + and wep:GetNextSecondaryFire() < CurTime() +end + + +--[[] +ATT.Hook_SecondaryAttack = function(wep) + -- if wep:StillWaiting() then return end + if wep:GetNWFloat("TacRPNextBlock", 0) > CurTime() then return end + wep:SetNextSecondaryFire(CurTime() + 0.25) + wep:SetNWFloat("TacRPNextBlock", CurTime() + 1) + wep:PlayAnimation("idle_defend", 1) + wep:SetHoldType("magic") + wep:SetNWFloat("TacRPKnifeParry", CurTime() + 0.5) + wep:EmitSound("tacrp/weapons/pistol_holster-" .. math.random(1, 4) .. ".wav", 75, 110) + + wep:SetNextIdle(CurTime() + 0.5) + if SERVER then + wep:SetTimer(0.75, function() + wep:SetShouldHoldType() + end, "BlockReset") + end +end +]] + +ATT.Hook_PreShoot = function(wep) + if wep:GetNWFloat("TacRPKnifeCounter", 0) > CurTime() then + wep:EmitSound("common/warning.wav", 70, 120, 1, CHAN_AUTO) + -- wep:SetHoldBreathAmount(math.max(0, wep:GetHoldBreathAmount() - 0.5)) + wep:Melee(true) + wep:SetOutOfBreath(false) + wep:SetNWFloat("TacRPKnifeCounter", 0) + return true + end + if wep:GetOutOfBreath() then return true end +end + +ATT.Hook_PostThink = function(wep) + local canhold = hold(wep) + + local ft = FrameTime() + + if !IsFirstTimePredicted() then return end + + + if !wep:GetOutOfBreath() then + if canhold and wep:GetHoldBreathAmount() > 0.05 then + -- wep:SetHoldBreathAmount(wep:GetHoldBreathAmount() - 0.1) + wep:SetOutOfBreath(true) + wep:SetHoldingBreath(false) + wep:SetNextIdle(math.huge) + wep:PlayAnimation("idle_defend", 1) + wep:SetHoldType("magic") + else + wep:SetHoldBreathAmount(math.min(1, wep:GetHoldBreathAmount() + FrameTime() * Lerp(wep:GetValue("MeleePerkInt"), 0.075, 0.2))) + end + else + if !canhold then + wep:SetOutOfBreath(false) + wep:PlayAnimation("idle", 1) + wep:SetShouldHoldType() + if wep:GetNWFloat("TacRPKnifeCounter", 0) < CurTime() then + wep:SetNextSecondaryFire(CurTime() + 0.5) + else + wep:SetNextSecondaryFire(CurTime() + 0.15) + end + else + wep:SetHoldBreathAmount(math.max(0, wep:GetHoldBreathAmount() - FrameTime() * 0.25)) + end + end + + -- cancel attack post swing into block since SecondaryAttack won't be called at all otherwise + -- if wep:GetOwner():KeyDown(IN_ATTACK2) and wep:GetNextSecondaryFire() - CurTime() <= 0.25 and wep:GetNWFloat("TacRPNextBlock", 0) <= CurTime() then + -- wep:SetNextSecondaryFire(CurTime()) + -- end +end + +local breath_a = 0 +local last = 1 +local lastt = 0 +function ATT.TacticalDraw(self) + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(48) + local h = TacRP.SS(4) + + local x = scrw / 2 + local y = scrh * 0.65 + TacRP.SS(6) + + if CurTime() > lastt + 1 then + breath_a = math.Approach(breath_a, 0, FrameTime() * 2) + elseif breath_a < 1 then + breath_a = math.Approach(breath_a, 1, FrameTime()) + end + local breath = self:GetHoldBreathAmount() + if last != breath then + lastt = CurTime() + last = breath + end + if breath_a == 0 then return end + + x = x - w / 2 + y = y - h / 2 + + surface.SetDrawColor(90, 90, 90, 200 * breath_a) + surface.DrawOutlinedRect(x - 1, y - 1, w + 2, h + 2, 1) + surface.SetDrawColor(0, 0, 0, 75 * breath_a) + surface.DrawRect(x, y, w, h) + + surface.SetDrawColor(255, 255, 255, 150 * breath_a) + + surface.DrawRect(x, y, w * breath, h) +end + +hook.Add("EntityTakeDamage", "TacRP_Block", function(ent, dmginfo) + if !IsValid(dmginfo:GetAttacker()) or !ent:IsPlayer() then return end + local wep = ent:GetActiveWeapon() + + if !IsValid(wep) or !wep.ArcticTacRP or !wep:GetValue("MeleeBlock") or !wep:GetOutOfBreath() then return end + if !(dmginfo:IsDamageType(DMG_GENERIC) or dmginfo:IsDamageType(DMG_CLUB) or dmginfo:IsDamageType(DMG_CRUSH) or dmginfo:IsDamageType(DMG_SLASH) or dmginfo:GetDamageType() == 0) then return end + -- if dmginfo:GetAttacker():GetPos():DistToSqr(ent:GetPos()) >= 22500 then return end + if (dmginfo:GetAttacker():GetPos() - ent:EyePos()):GetNormalized():Dot(ent:EyeAngles():Forward()) < 0.5 and ((dmginfo:GetDamagePosition() - ent:EyePos()):GetNormalized():Dot(ent:EyeAngles():Forward()) < 0.5) then return end + + -- get guard broken bitch + if ent.PalmPunched then + ent:SetActiveWeapon(NULL) + ent:DropWeapon(wep, dmginfo:GetAttacker():GetPos()) + return + end + + wep:SetHoldBreathAmount(wep:GetHoldBreathAmount() - math.Clamp(0.05 + dmginfo:GetDamage() / ent:GetMaxHealth(), 0.05, 0.5)) + + local ang = ent:EyeAngles() + local fx = EffectData() + fx:SetOrigin(ent:EyePos()) + fx:SetNormal(ang:Forward()) + fx:SetAngles(ang) + util.Effect("ManhackSparks", fx) + + ent:EmitSound("physics/metal/metal_solid_impact_hard5.wav", 90, math.Rand(105, 110)) + ent:ViewPunch(AngleRand(-1, 1) * (dmginfo:GetDamage() ^ 0.5)) + + wep:SetNWFloat("TacRPKnifeCounter", CurTime() + 0.6) + + local inflictor = dmginfo:GetInflictor() + timer.Simple(0, function() + if IsValid(inflictor) and inflictor:IsScripted() and scripted_ents.IsBasedOn(inflictor:GetClass(), "tacrp_proj_base") and IsValid(inflictor:GetPhysicsObject()) then + inflictor:GetPhysicsObject():SetVelocityInstantaneous(ent:EyeAngles():Forward() * 2000) + inflictor:SetOwner(ent) + end + end) + + if wep:GetHoldBreathAmount() <= 0 then + ent:SetActiveWeapon(NULL) + ent:DropWeapon(wep, dmginfo:GetAttacker():GetPos()) + ent:EmitSound("physics/metal/metal_box_break1.wav", 80, 110) + end + + return true +end) + +--[[ +hook.Add("EntityTakeDamage", "TacRP_Counter", function(ent, dmginfo) + if !IsValid(dmginfo:GetAttacker()) or !dmginfo:GetAttacker():IsPlayer() then return end + local wep = dmginfo:GetAttacker():GetActiveWeapon() + + if !IsValid(wep) or !wep.ArcticTacRP or wep:GetNWFloat("TacRPKnifeCounter", 0) < CurTime() or (dmginfo:GetInflictor() != wep and dmginfo:GetInflictor() != dmginfo:GetAttacker()) then return end + local dropwep = ent:IsPlayer() and ent:GetActiveWeapon() + + if ent.IsLambdaPlayer then + -- drop client weapon model; delay a tick so we don't duplicate on death + timer.Simple(0, function() + if !IsValid(ent) or ent:Health() < 0 then return end + if ent.l_DropWeaponOnDeath and !ent:IsWeaponMarkedNodraw() then + net.Start( "lambdaplayers_createclientsidedroppedweapon" ) + net.WriteEntity( ent:GetWeaponENT() ) + net.WriteEntity( ent ) + net.WriteVector( ent:GetPhysColor() ) + net.WriteString( ent:GetWeaponName() ) + net.WriteVector( dmginfo:GetDamageForce() ) + net.WriteVector( dmginfo:GetDamagePosition() ) + net.Broadcast() + end + end) + + local run = math.Rand(1, 3) + ent.l_WeaponUseCooldown = CurTime() + run + + -- wait a bit before swapping because client relies on the entity to set model + ent:GetWeaponENT():SetNoDraw(true) + ent:GetWeaponENT():DrawShadow(false) + ent:SetHasCustomDrawFunction(false) + ent:RetreatFrom(dmginfo:GetAttacker(), run) + timer.Simple(1, function() + if !IsValid(ent) or ent:Health() < 0 then return end + ent.l_Weapon = "none" -- holster function? never heard of 'em + ent:PreventWeaponSwitch(false) + ent:SwitchWeapon("none", true) + end) + timer.Simple(run, function() + if !IsValid(ent) or ent:Health() < 0 then return end + ent:AttackTarget(dmginfo:GetAttacker()) + end) + elseif !IsValid(dropwep) or dropwep:GetHoldType() == "fists" or dropwep:GetHoldType() == "normal" or string.find(dropwep:GetClass(), "fist") or string.find(dropwep:GetClass(), "unarmed") or string.find(dropwep:GetClass(), "hand") then + dmginfo:ScaleDamage(1.5) + else + ent:SetActiveWeapon(NULL) + ent:DropWeapon(dropwep, dmginfo:GetAttacker():GetPos()) + end +end) +]] + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+attack2"] = {so = 0.1, str = "hint.melee.block"} +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_heavy.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_heavy.lua new file mode 100644 index 0000000..9adac2b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_heavy.lua @@ -0,0 +1,48 @@ +ATT.PrintName = "Heavy" +ATT.FullName = "Heavy-handed" +ATT.Icon = Material("entities/tacrp_att_melee_tech_heavy.png", "mips smooth") +ATT.Description = "A classic counter-terrorist technique: Just smack them real hard." +ATT.Pros = { "att.pro.melee_tech_heavy1", "att.pro.melee_tech_heavy2" } + +ATT.Category = {"melee_tech"} + +ATT.SortOrder = 1 + +ATT.HeavyAttack = true + +ATT.Free = true + +ATT.MeleeBackstab = true +ATT.MeleeBackstabMult = 1.5 + +ATT.Hook_SecondaryAttack = function(self) + self:Melee(true) + + -- Cancel demoknight charge + local ply = self:GetOwner() + if ply:Alive() and ply:GetNWBool("TacRPChargeState", false) then + ply:SetNWBool("TacRPChargeState", false) + ply:SetNWFloat("TacRPChargeTime", 0) + ply:SetNWFloat("TacRPChargeEnd", CurTime()) + + if IsValid(ply:GetNWEntity("TacRPChargeWeapon")) then + ply:GetNWEntity("TacRPChargeWeapon"):SetBreath(0) + end + + ply:SetCustomCollisionCheck(ply.TacRPPrevColCheck) + if ply.TacRPPrevFlag then + ply:RemoveEFlags(EFL_NO_DAMAGE_FORCES) + end + ply.TacRPPrevColCheck = nil + ply.TacRPPrevFlag = nil + + ply:EmitSound("TacRP.Charge.End") + end + + return true +end + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+attack"] = {so = 0, str = "hint.melee.light"} + tbl["+attack2"] = {so = 0.1, str = "hint.melee.heavy"} +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_nade.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_nade.lua new file mode 100644 index 0000000..a2781cf --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_nade.lua @@ -0,0 +1,34 @@ +ATT.PrintName = "Grenade" +ATT.FullName = "Grenadier" +ATT.Icon = Material("entities/tacrp_att_melee_tech_nade.png", "mips smooth") +ATT.Description = "Always good to have something handy to throw." +ATT.Pros = { "att.pro.melee_tech_nade1", "att.pro.melee_tech_nade2", "att.pro.melee_tech_nade3" } + +ATT.Category = {"melee_tech"} + +ATT.SortOrder = 4 +ATT.InvAtt = "perk_throw" + +ATT.ThrowRocks = true +ATT.Mult_QuickNadeTimeMult = 0.65 + +ATT.Free = true + +ATT.Hook_SecondaryAttack = function(self) + self.GrenadeDownKey = IN_ATTACK2 + self:PrimeGrenade() +end + +--[[] +ATT.Hook_PreReload = function(self) + self.GrenadeMenuKey = IN_RELOAD + if game.SinglePlayer() and SERVER then + self:CallOnClient("Reload") + end + return true +end +]] + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+attack2"] = {so = 0.1, str = "hint.quicknade.throw"} +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_throw.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_throw.lua new file mode 100644 index 0000000..9857bd5 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts/melee_tech_throw.lua @@ -0,0 +1,103 @@ +ATT.PrintName = "Throw" +ATT.FullName = "Knife Throw" +ATT.Icon = Material("entities/tacrp_att_melee_tech_throw.png", "mips smooth") +ATT.Description = "Bar trick turned lethal." +ATT.Pros = { "att.pro.melee_tech_throw1", "att.pro.melee_tech_throw2", "att.pro.melee_tech_throw3", "att.pro.melee_tech_throw4" } + +ATT.Category = {"melee_tech"} + +ATT.SortOrder = 3 + +ATT.ThrowAttack = true + +ATT.Free = true + +ATT.Override_Ammo = "xbowbolt" + +ATT.Hook_SecondaryAttack = function(self) + + if self:StillWaiting() or self:GetNextSecondaryFire() > CurTime() then return end + + if !self:GetInfiniteAmmo() and self:GetOwner():GetAmmoCount("xbowbolt") <= 0 then return end + + local s = self:GetValue("MeleeAttackTime") * 3 * self:GetMeleePerkCooldown() + self:PlayAnimation("meleethrow", s, false, true) + --self:GetOwner():DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_THROW) + self:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE) + + self:EmitSound("weapons/iceaxe/iceaxe_swing1.wav", 75, 120, 1) + + self:SetTimer(0.15, function() + if CLIENT then return end + + local rocket = ents.Create("tacrp_proj_knife") + + if !IsValid(rocket) then return end + + local src, ang = self:GetOwner():GetShootPos(), self:GetShootDir() --+ Angle(-1, 0, 0) + local spread = 0 + local force = self:GetMeleePerkVelocity() + local dispersion = Angle(math.Rand(-1, 1), math.Rand(-1, 1), 0) + dispersion = dispersion * spread * 36 + + rocket.Model = self.ThrownKnifeModel or self.WorldModel + rocket.Damage = self:GetValue("MeleeDamage") * self:GetMeleePerkDamage() * self:GetConfigDamageMultiplier() + rocket.Inflictor = self + rocket.DamageType = self:GetValue("MeleeDamageType") + rocket.Sound_MeleeHit = istable(self.Sound_MeleeHit) and table.Copy(self.Sound_MeleeHit) or self.Sound_MeleeHit + rocket.Sound_MeleeHitBody = istable(self.Sound_MeleeHitBody) and table.Copy(self.Sound_MeleeHitBody) or self.Sound_MeleeHitBody + + rocket:SetPos(src) + rocket:SetOwner(self:GetOwner()) + rocket:SetAngles(ang + dispersion) + rocket:Spawn() + rocket:SetPhysicsAttacker(self:GetOwner(), 10) + rocket.HasAmmo = !self:GetInfiniteAmmo() + + local phys = rocket:GetPhysicsObject() + + if phys:IsValid() then + phys:ApplyForceCenter((ang + dispersion):Forward() * force + self:GetOwner():GetVelocity()) + phys:SetAngleVelocityInstantaneous(VectorRand() * 10 + Vector(0, 1800, 0)) + end + end) + + if !self:GetInfiniteAmmo() then + self:GetOwner():RemoveAmmo(1, "xbowbolt") + end + + local throwtimewait = math.max(0, s - 0.75) --self:GetValue("MeleeThrowTimeWait") + self:SetTimer(throwtimewait, function() + self:PlayAnimation("deploy", 1, false, true) + end) + + self:SetNextSecondaryFire(CurTime() + s) + + -- Cancel demoknight charge + local ply = self:GetOwner() + if ply:Alive() and ply:GetNWBool("TacRPChargeState", false) then + ply:SetNWBool("TacRPChargeState", false) + ply:SetNWFloat("TacRPChargeTime", 0) + ply:SetNWFloat("TacRPChargeEnd", CurTime()) + + if IsValid(ply:GetNWEntity("TacRPChargeWeapon")) then + ply:GetNWEntity("TacRPChargeWeapon"):SetBreath(0) + end + + ply:SetCustomCollisionCheck(ply.TacRPPrevColCheck) + if ply.TacRPPrevFlag then + ply:RemoveEFlags(EFL_NO_DAMAGE_FORCES) + end + ply.TacRPPrevColCheck = nil + ply.TacRPPrevFlag = nil + + ply:EmitSound("TacRP.Charge.End") + end + + return true +end + + +ATT.Hook_GetHintCapabilities = function(self, tbl) + tbl["+attack2"] = {so = 0.1, str = "hint.melee.throw"} +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/acc.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/acc.lua new file mode 100644 index 0000000..c97360a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/acc.lua @@ -0,0 +1,719 @@ +-- acc.lua + +local ATT = {} + +------------------------------ +-- #region acc_bipod +------------------------------ +ATT = {} +ATT.Ignore = true + +ATT.PrintName = "Bipod" +ATT.Icon = Material("entities/tacrp_att_acc_bipod.png", "mips smooth") +ATT.Description = "Foldable support that stabilizes the weapon when deployed." +ATT.Pros = {"stat.recoilcrouch", "stat.swaycrouch"} + +ATT.Category = "acc_bipod" + + +ATT.SortOrder = 5.5 + +ATT.Mult_RecoilMultCrouch = 0.2 +ATT.Mult_SwayCrouchMult = 0.15 + +ATT.InstalledElements = {"bipod"} + +TacRP.LoadAtt(ATT, "acc_bipod") +-- #endregion + +------------------------------ +-- #region acc_brace +------------------------------ +ATT = {} + +ATT.PrintName = "Pistol Brace" +ATT.Icon = Material("entities/tacrp_att_acc_brace.png", "mips smooth") +ATT.Description = "Turns your pistol into a rifle. The ATF is gonna get your ass." +ATT.Pros = {"rating.control", "rating.stability"} +ATT.Cons = {"rating.handling", "rating.maneuvering"} + +ATT.Category = "acc_brace" + +ATT.SortOrder = 3 + +ATT.Mult_RecoilKick = 0.3 +ATT.Mult_RecoilSpreadPenalty = 0.75 + +ATT.Mult_Sway = 0.75 +ATT.Add_ScopedSway = -0.2 +ATT.Mult_ScopedSway = 0.75 + +ATT.Add_AimDownSightsTime = 0.06 +ATT.Add_SprintToFireTime = 0.1 + +ATT.Add_HipFireSpreadPenalty = 0.0075 +ATT.Add_FreeAimMaxAngle = 0.75 + +ATT.Mult_DeployTimeMult = 1.5 +ATT.Mult_HolsterTimeMult = 1.5 + +TacRP.LoadAtt(ATT, "acc_brace") +-- #endregion + +------------------------------ +-- #region acc_cheekrest +------------------------------ +ATT = {} + +ATT.PrintName = "Cheek Rest" +ATT.Icon = Material("entities/tacrp_att_acc_cheekrest.png", "mips smooth") +ATT.Description = "Stabilizes your head while aiming down sights, reducing sway." +ATT.Pros = {"stat.scopedsway", "stat.bloomintensity"} + +ATT.Category = "acc_sling" + +ATT.SortOrder = 7 + +ATT.Mult_ScopedSway = 0.5 +ATT.Mult_RecoilSpreadPenalty = 0.95 + +TacRP.LoadAtt(ATT, "acc_cheekrest") +-- #endregion + +------------------------------ +-- #region acc_conceal +------------------------------ +ATT = {} + +ATT.PrintName = "Concealment" +ATT.Icon = Material("entities/tacrp_att_acc_conceal.png", "mips smooth") +ATT.Description = "Carry the weapon discretely, hiding it from view when not held." +ATT.Pros = {"att.procon.conceal"} + +ATT.Category = "acc_holster" + +ATT.SortOrder = 8 + +ATT.Override_HolsterVisible = false + +ATT.Ignore = false -- engine.ActiveGamemode() != "terrortown" + +TacRP.LoadAtt(ATT, "acc_conceal") +-- #endregion + +------------------------------ +-- #region acc_dual_ergo +------------------------------ +ATT = {} + +ATT.PrintName = "Ergo Grip" +ATT.FullName = "Ergonomic Grip" +ATT.Icon = Material("entities/tacrp_att_acc_ergo.png", "mips smooth") +ATT.Description = "Grooved grip makes it easier to move while shooting two guns." + +ATT.Category = "acc_dual" +ATT.InvAtt = "acc_ergo" + +ATT.SortOrder = 2 + +ATT.Pros = {"stat.shootingspeed"} +ATT.Add_ShootingSpeedMult = 0.08 + +TacRP.LoadAtt(ATT, "acc_dual_ergo") +-- #endregion + +------------------------------ +-- #region acc_dual_quickdraw +------------------------------ +ATT = {} + +ATT.PrintName = "Quickdraw" +ATT.FullName = "Quickdraw Holster" +ATT.Icon = Material("entities/tacrp_att_acc_quickdraw.png", "mips smooth") +ATT.Description = "A pair of strapless holster to quickly draw the weapons and hasten loading." +ATT.Pros = {"stat.deploytime", "stat.reloadtime"} + +ATT.Category = "acc_dual" +ATT.InvAtt = "acc_quickdraw" + +ATT.SortOrder = 4 + +ATT.Mult_DeployTimeMult = 0.75 +ATT.Mult_HolsterTimeMult = 0.5 + +ATT.Mult_ReloadTimeMult = 0.95 + +ATT.TryUnholster = true + +TacRP.LoadAtt(ATT, "acc_dual_quickdraw") +-- #endregion + +------------------------------ +-- #region acc_dual_skel +------------------------------ +ATT = {} + +ATT.PrintName = "Light Grip" +ATT.FullName = "Lightweight Grip" +ATT.Icon = Material("entities/tacrp_att_acc_skel.png", "mips smooth") +ATT.Description = "Skeletonized grip makes the guns lighter and easier to move around with." + +ATT.Category = "acc_dual" +ATT.InvAtt = "acc_skel" + +ATT.SortOrder = 2.1 + +ATT.Pros = {"stat.movespeed", "stat.reloadspeed"} +ATT.Add_MoveSpeedMult = 0.08 +ATT.Add_ReloadSpeedMult = 0.1 + +TacRP.LoadAtt(ATT, "acc_dual_skel") +-- #endregion + +------------------------------ +-- #region acc_duffelbag +------------------------------ +ATT = {} + +ATT.PrintName = "Gun Bag" +ATT.Icon = Material("entities/tacrp_dufflebag.png", "mips smooth") +ATT.Description = "Hide the gun in a bag so you don't cause mass panic." +ATT.Pros = {"Conceal weapon in bag"} + +ATT.Category = "acc_duffle" + +ATT.SortOrder = 8 + +ATT.Override_HolsterVisible = true +ATT.HolsterModel = "models/jessev92/payday2/item_bag_loot.mdl" +ATT.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +ATT.HolsterPos = Vector(7, -2, 0) +ATT.HolsterAng = Angle(10, 90, 90) + +ATT.Ignore = true + +TacRP.LoadAtt(ATT, "acc_duffelbag") +-- #endregion + +------------------------------ +-- #region acc_ergo +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_dual_ergo.name" +ATT.FullName = "att.acc_dual_ergo.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_ergo.png", "mips smooth") +ATT.Description = "Grooved grip makes aiming faster and moving while shooting easier." + +ATT.Category = "acc" + +ATT.SortOrder = 2 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Pros = {"stat.shootingspeed"} + + ATT.Add_ShootingSpeedMult = 0.15 +else + ATT.Pros = {"stat.shootingspeed", "stat.aimdownsights"} + + ATT.Add_ShootingSpeedMult = 0.08 + ATT.Mult_AimDownSightsTime = 0.85 +end + +TacRP.LoadAtt(ATT, "acc_ergo") +-- #endregion + +------------------------------ +-- #region acc_extendedbelt +------------------------------ +ATT = {} + +ATT.PrintName = "Box Extender" +ATT.Icon = Material("entities/tacrp_att_acc_extendedbelt.png", "mips smooth") +ATT.Description = "Increase ammo capacity for machine guns significantly." +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "extendedbelt" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 25 +ATT.Mult_ReloadTimeMult = 1.05 + +TacRP.LoadAtt(ATT, "acc_extendedbelt") +-- #endregion + +------------------------------ +-- #region acc_extmag_dual +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_dual.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} + +ATT.Category = "acc_extmag_dual" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 4 + +TacRP.LoadAtt(ATT, "acc_extmag_dual") +-- #endregion + +------------------------------ +-- #region acc_extmag_dual2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_dual.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} + +ATT.Category = "acc_extmag_dual2" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 2 + +TacRP.LoadAtt(ATT, "acc_extmag_dual2") +-- #endregion + +------------------------------ +-- #region acc_extmag_dualsmg +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_dual.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "acc_extmag_dualsmg" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 8 +ATT.Mult_ReloadTimeMult = 1.05 + +TacRP.LoadAtt(ATT, "acc_extmag_dualsmg") +-- #endregion + +------------------------------ +-- #region acc_extmag_pistol +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_pistol.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "acc_extmag_pistol" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 3 + +ATT.Mult_ReloadTimeMult = 1.03 + +TacRP.LoadAtt(ATT, "acc_extmag_pistol") +-- #endregion + +------------------------------ +-- #region acc_extmag_pistol2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_pistol.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "acc_extmag_pistol2" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 2 + +ATT.Mult_ReloadTimeMult = 1.03 + +TacRP.LoadAtt(ATT, "acc_extmag_pistol2") +-- #endregion + +------------------------------ +-- #region acc_extmag_rifle +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_rifle.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "perk_extendedmag" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 5 + +ATT.Mult_ReloadTimeMult = 1.05 + +TacRP.LoadAtt(ATT, "acc_extmag_rifle") +-- #endregion + +------------------------------ +-- #region acc_extmag_rifle2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_rifle2.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "acc_extmag_rifle2" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 4 + +ATT.Mult_ReloadTimeMult = 1.05 + +TacRP.LoadAtt(ATT, "acc_extmag_rifle2") +-- #endregion + +------------------------------ +-- #region acc_extmag_shotgun (Leaving the original as a backup for third-party weapons/weapon packs that may rely on it) +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_shotgun.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = {"acc_extmag_shotgun"} + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 2 + +ATT.Mult_ReloadTimeMult = 1.03 + +TacRP.LoadAtt(ATT, "acc_extmag_shotgun") +-- #endregion + +------------------------------ +-- #region acc_extmag_shotgun_mag +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_shotgun.png", "mips smooth") -- make new icon maybe? +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = {"acc_extmag_shotgun_mag"} + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 5 + +ATT.Mult_ReloadTimeMult = 1.05 + +TacRP.LoadAtt(ATT, "acc_extmag_shotgun_mag") +-- #endregion + +------------------------------ +-- #region acc_extmag_shotgun_tube +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag_shotgun_tube.name" +ATT.FullName = "att.acc_extmag_shotgun_tube.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_shotgun.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = {"acc_extmag_shotgun_tube"} + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 2 + +ATT.Mult_ReloadTimeMult = 1.03 + +TacRP.LoadAtt(ATT, "acc_extmag_shotgun_tube") +-- #endregion + +------------------------------ +-- #region acc_extmag_smg +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_smg.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "acc_extmag_smg" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 5 + +ATT.Mult_ReloadTimeMult = 1.05 + +TacRP.LoadAtt(ATT, "acc_extmag_smg") +-- #endregion + +------------------------------ +-- #region acc_extmag_sniper +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_sniper.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} +ATT.Cons = {"stat.reloadtime"} + +ATT.Category = "acc_extmag_sniper" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 2 + +ATT.Mult_ReloadTimeMult = 1.03 + +TacRP.LoadAtt(ATT, "acc_extmag_sniper") +-- #endregion + +------------------------------ +-- #region acc_foldstock +------------------------------ +ATT = {} + +ATT.PrintName = "Fold Stock" +ATT.Icon = Material("entities/tacrp_att_acc_foldstock.png", "mips smooth") +ATT.Description = "Keep stock folded, improving handling significantly at the cost of recoil." +ATT.Pros = {"rating.handling", "rating.maneuvering"} +ATT.Cons = {"stat.recoilkick", "stat.scopedsway"} + +ATT.Free = true + +ATT.Category = "acc_foldstock" + +ATT.SortOrder = 0.5 + +ATT.Mult_RecoilVisualKick = 2 + +ATT.Mult_SightedSpeedMult = 1.25 +ATT.Mult_HipFireSpreadPenalty = 0.7 + +ATT.Add_RecoilKick = 1 +ATT.Mult_RecoilKick = 1.5 + +-- ATT.Mult_SprintToFireTime = 0.75 +-- ATT.Mult_AimDownSightsTime = 0.75 +ATT.Add_SprintToFireTime = -0.08 +ATT.Add_AimDownSightsTime = -0.08 + +ATT.Add_ScopedSway = 0.1 +ATT.Mult_ScopedSway = 2 +ATT.Mult_Sway = 0.8 +ATT.Add_FreeAimMaxAngle = -1 + +ATT.InstalledElements = {"foldstock"} + +ATT.TryUnholster = true +ATT.Mult_HolsterTimeMult = 0.5 + +TacRP.LoadAtt(ATT, "acc_foldstock") +-- #endregion + +------------------------------ +-- #region acc_foldstock2 +------------------------------ +ATT = {} + +ATT.PrintName = "Adjust Stock" +ATT.Icon = Material("entities/tacrp_att_acc_foldstock.png", "mips smooth") +ATT.Description = "Shorten stock to improve handling somewhat at the cost of recoil." +ATT.Pros = {"rating.handling", "rating.maneuvering"} +ATT.Cons = {"stat.recoilkick", "stat.scopedsway"} + +ATT.Free = true + +ATT.Category = "acc_foldstock2" + +ATT.SortOrder = 0.5 + +ATT.Mult_RecoilVisualKick = 1.65 +ATT.Mult_SightedSpeedMult = 1.125 +ATT.Mult_HipFireSpreadPenalty = 0.85 + +ATT.Add_RecoilKick = 0.5 +ATT.Mult_RecoilKick = 1.25 + +-- ATT.Mult_SprintToFireTime = 0.85 +-- ATT.Mult_AimDownSightsTime = 0.85 +ATT.Add_SprintToFireTime = -0.04 +ATT.Add_AimDownSightsTime = -0.04 + +ATT.Add_ScopedSway = 0.1 +ATT.Mult_Sway = 0.9 +ATT.Add_FreeAimMaxAngle = -0.5 + +ATT.InstalledElements = {"foldstock"} + +ATT.TryUnholster = true +ATT.Mult_HolsterTimeMult = 0.75 + +TacRP.LoadAtt(ATT, "acc_foldstock2") +-- #endregion + +------------------------------ +-- #region acc_pad +------------------------------ +ATT = {} + +ATT.PrintName = "Recoil Pad" +ATT.Icon = Material("entities/tacrp_att_acc_pad.png", "mips smooth") +ATT.Description = "Rubber pad attached to the end of the stock." +ATT.Pros = {"stat.recoilkick"} + +ATT.Category = "acc_sling" + +ATT.SortOrder = 6 + +ATT.Mult_RecoilVisualKick = 0.9 + +ATT.Add_RecoilKick = -0.5 +ATT.Mult_RecoilKick = 0.95 + +TacRP.LoadAtt(ATT, "acc_pad") +-- #endregion + +------------------------------ +-- #region acc_quickdraw +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_dual_quickdraw.name" +ATT.FullName = "att.acc_dual_quickdraw.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_quickdraw.png", "mips smooth") +ATT.Description = "Strapless holster with magazine pouches for quick drawing and loading." +ATT.Pros = {"stat.deploytime", "stat.reloadtime"} + +ATT.Category = "acc_holster" + +ATT.SortOrder = 4 + +--ATT.Mult_DeployTimeMult = 0.6 +ATT.Mult_HolsterTimeMult = 0.5 +ATT.Mult_ReloadTimeMult = 0.925 + +ATT.TryUnholster = true + +TacRP.LoadAtt(ATT, "acc_quickdraw") +-- #endregion + +------------------------------ +-- #region acc_skel +------------------------------ +ATT = {} + +ATT.PrintName = "att.acc_dual_skel.name" +ATT.FullName = "att.acc_dual_skel.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_skel.png", "mips smooth") +ATT.Description = "Skeletonized grip makes the weapon faster to raise and keep raised." +ATT.Pros = {"Sighted Speed", "Sprint To Fire Time"} + +ATT.Category = "acc" + +ATT.SortOrder = 2.1 + + + +if engine.ActiveGamemode() == "terrortown" then + ATT.Pros = {"stat.sightedspeed"} + + ATT.Add_SightedSpeedMult = 0.12 +else + ATT.Pros = {"stat.sightedspeed", "stat.sprinttofire"} + + ATT.Add_SightedSpeedMult = 0.08 + ATT.Mult_SprintToFireTime = 0.85 +end + +TacRP.LoadAtt(ATT, "acc_skel") +-- #endregion + +------------------------------ +-- #region acc_sling +------------------------------ +ATT = {} + +ATT.PrintName = "Sling" +ATT.Icon = Material("entities/tacrp_att_acc_sling.png", "mips smooth") +ATT.Description = "Attach a strap to the weapon, making it easier to draw and reload." +ATT.Pros = {"stat.deploytime", "stat.reloadtime"} + +ATT.Category = {"acc_sling", "acc_slingonly"} + +ATT.SortOrder = 5 + +ATT.Mult_DeployTimeMult = 0.75 +ATT.Mult_HolsterTimeMult = 0.75 +ATT.Mult_ReloadTimeMult = 0.925 + +TacRP.LoadAtt(ATT, "acc_sling") +-- #endregion + diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/ak.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/ak.lua new file mode 100644 index 0000000..2bbd5c0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/ak.lua @@ -0,0 +1,206 @@ +local ATT = {} + +--------------------------------------------- +ATT = {} +ATT.PrintName = "Lightweight" +ATT.FullName = "AK-74 Lightweight Furniture" + +ATT.Icon = Material("entities/tacrp_att_acc_ak74_poly.png", "mips smooth") +ATT.Description = "Paratrooper configuration for increased handling and mobility." +ATT.Pros = {"stat.movespeed", "rating.maneuvering", "rating.handling"} +ATT.Cons = {"stat.scopedsway", "stat.recoil"} + +ATT.Category = "acc_ak74" + +ATT.SortOrder = 0.1 + +ATT.Add_MoveSpeedMult = 0.03 +ATT.Add_AimDownSightsTime = -0.05 +ATT.Add_SprintToFireTime = -0.03 + +ATT.Add_RecoilVisualKick = 0.15 +ATT.Add_RecoilKick = 1 +ATT.Add_RecoilStability = -0.05 +ATT.Add_RecoilSpreadPenalty = 0.0002 + +ATT.Add_FreeAimMaxAngle = -0.5 +ATT.Mult_HipFireSpreadPenalty = 0.8 +-- ATT.Add_Sway = -0.25 +ATT.Add_ScopedSway = 0.05 + +ATT.InstalledElements = {"polymer"} +ATT.Mult_DeployTimeMult = 0.65 +ATT.Mult_HolsterTimeMult = 0.5 + +TacRP.LoadAtt(ATT, "acc_ak74_poly") + +--------------------------------------------- +ATT = {} +ATT.PrintName = "7.62x39mm" +ATT.FullName = "AK-12 7.62x39mm Mod Kit" + +ATT.Icon = Material("entities/tacrp_att_ammo_ak12_762.png", "mips smooth") +ATT.Description = "Load a more powerful cartridge, increasing both damage and recoil." +ATT.Pros = {"stat.damage", "att.procon.armor"} +ATT.Cons = {"stat.rpm", "stat.recoil"} + +ATT.Category = "ammo_ak12" + +ATT.SortOrder = 0.1 + +ATT.Override_Ammo = "ar2" + +ATT.Mult_Damage_Max = 1.15 +ATT.Mult_Damage_Min = 1.25 +ATT.Mult_RPM = 700 / 800 + +ATT.Add_Penetration = 3 +ATT.Add_ArmorPenetration = 0.075 + +ATT.Add_RecoilVisualKick = 0.15 +ATT.Add_RecoilKick = 3 +ATT.Add_RecoilSpreadPenalty = 0.0012 + +ATT.Override_Sound_Shoot = "^tacint_extras/ak12/ak47_new-1.wav" + +TacRP.LoadAtt(ATT, "ammo_ak12_762") + +--------------------------------------------- +ATT = {} +ATT.PrintName = "Booster" +ATT.FullName = "6P26 Muzzle Booster" +ATT.Icon = Material("entities/tacrp_att_muzz_ak_booster.png", "mips smooth") +ATT.Description = "AK pattern muzzle device that increases rate of fire." +ATT.Pros = {"stat.rpm", "stat.muzzlevelocity"} +ATT.Cons = {"stat.recoil"} + +ATT.Model = "models/weapons/tacint_extras/addons/ak74u_booster.mdl" +ATT.Scale = 1.25 + +ATT.ModelOffset = Vector(-0.5, 0, 0.03) + +ATT.Category = {"muzz_ak", "muzzle"} + +ATT.SortOrder = 0.2 + +ATT.Mult_MuzzleVelocity = 1.1 +ATT.Mult_RPM = 1.05 +ATT.Add_RecoilStability = -0.1 +ATT.Add_RecoilKick = 0.5 + +ATT.Add_Pitch_Shoot = 2.5 + +TacRP.LoadAtt(ATT, "muzz_ak_booster") + +--------------------------------------------- +ATT = {} +ATT.PrintName = "Compensator" +ATT.FullName = "6P20 Compensator" +ATT.Icon = Material("entities/tacrp_att_muzz_ak_comp.png", "mips smooth") +ATT.Description = "AK pattern muzzle device that straightens recoil." +ATT.Pros = {"stat.recoilkick", "stat.recoilstability"} +ATT.Cons = {"stat.spread", "stat.bloomintensity"} + +ATT.Model = "models/weapons/tacint_extras/addons/ak74_comp.mdl" +ATT.Scale = 0.9 + +ATT.ModelOffset = Vector(-1.8, 0, 0.02) + +ATT.Category = {"muzz_ak", "muzzle"} + +ATT.SortOrder = 0.1 + +ATT.Add_RecoilStability = 0.05 +ATT.Mult_RecoilSpreadPenalty = 1.1 +ATT.Mult_RecoilKick = 0.95 +ATT.Mult_Spread = 1.25 + +TacRP.LoadAtt(ATT, "muzz_ak_comp") + +--------------------------------------------- +ATT = {} +ATT.PrintName = "PBS-5" +ATT.FullName = "PBS-5 Suppressor" +ATT.Icon = Material("entities/tacrp_att_muzz_supp_pbs.png", "mips smooth") +ATT.Description = "AK pattern suppressor improving recoil stability at cost of accuracy." +ATT.Pros = {"stat.vol_shoot", "stat.recoilstability"} +ATT.Cons = {"stat.spread", "stat.muzzlevelocity"} + +ATT.Model = "models/weapons/tacint_extras/addons/suppressor_pbs.mdl" +ATT.Scale = 1.5 + +ATT.ModelOffset = Vector(0.5, 0, 0) + +ATT.Category = "silencer" + +ATT.SortOrder = 8 + +ATT.Add_Vol_Shoot = -25 +ATT.Mult_MuzzleVelocity = 0.9 +ATT.Add_RecoilStability = 0.07 +ATT.Add_Spread = 0.0025 + +ATT.Add_Pitch_Shoot = -2.5 + +ATT.Silencer = true +ATT.Override_MuzzleEffect = "muzzleflash_suppressed" + +TacRP.LoadAtt(ATT, "muzz_supp_pbs") + +--------------------------------------------- +ATT = {} +ATT.PrintName = "Kobra" +ATT.Icon = Material("entities/tacrp_att_optic_ak_kobra.png", "mips smooth") +ATT.Description = "Russian dovetail reflex sight." +ATT.Pros = {"att.sight.1"} + +ATT.Category = {"optic_ak", "optic_ak2"} + +ATT.SortOrder = 1 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.1 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.Model = "models/weapons/tacint_extras/addons/ak_kobra.mdl" +ATT.Scale = 1.2 +ATT.ModelOffset = Vector(-2, -0.25, 0) + +ATT.SightPos = Vector(-0.175, -12, 1.75) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Holosight = Material("tacrp/hud/kobra.png", "additive") +ATT.Holosight:SetInt("$flags", 128) + +ATT.InstalledElements = {"akmount"} + +TacRP.LoadAtt(ATT, "optic_ak_kobra") + +--------------------------------------------- +ATT = {} +ATT.PrintName = "Nimrod 6x40" +ATT.Icon = Material("entities/tacrp_att_optic_galil_scope.png", "mips smooth") +ATT.Description = "Sniper optic designed to be mounted onto Galil rifles." +ATT.Pros = {"att.zoom.6"} + +ATT.Category = {"optic_galil"} + +ATT.SortOrder = 6 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/l96.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 6 + +ATT.SightPos = Vector(1.5, -10, -0.22) +ATT.SightAng = Angle(0, 0, 0) + +ATT.InstalledElements = {"scope"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "optic_galil") diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/ammo.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/ammo.lua new file mode 100644 index 0000000..ebe801e --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/ammo.lua @@ -0,0 +1,1696 @@ +-- ammo.lua + +local ATT = {} + +------------------------------ +-- #region ammo_40mm_3gl +------------------------------ +ATT = {} + +ATT.PrintName = "3GL" +ATT.FullName = "40mm Cluster Grenades" + +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_3gl.png", "mips smooth") +ATT.Description = "Three weak cluster grenades, fired at once." +ATT.Pros = {"att.procon.moreproj"} +ATT.Cons = {"stat.spread", "stat.muzzlevelocity"} + +ATT.Category = "ammo_40mm" + +ATT.SortOrder = 1 + +ATT.ShootEnt = "tacrp_proj_40mm_3gl" +ATT.Num = 3 + +ATT.InstalledElements = {"3gl"} + +ATT.Override_Damage_Max = 60 +ATT.Override_Damage_Min = 60 + +ATT.Override_Spread = 0.05 +ATT.Override_ShotgunPelletSpread = 0.025 +ATT.Override_MidAirSpreadPenalty = 0 +ATT.Override_HipFireSpreadPenalty = 0 + +ATT.Mult_ShootEntForce = 0.85 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_40mm_3gl") +-- #endregion + +------------------------------ +-- #region ammo_40mm_buck +------------------------------ +ATT = {} + +ATT.PrintName = "Buckshot" +ATT.FullName = "40mm Buckshot Grenades" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_buck.png", "mips smooth") +ATT.Description = "Flat-top grenade firing shotgun pellets. Weak due to low pressure curve." +ATT.Pros = {"att.procon.direct", "att.procon.doorbreach"} +ATT.Cons = {"att.procon.noexp"} + +ATT.Category = "ammo_40mm" + +ATT.Override_Ammo = "buckshot" -- extremely not realism + +ATT.SortOrder = 2 + +ATT.Override_ShootEnt = false + +ATT.NoRanger = false + +ATT.Override_Damage_Max = 9 +ATT.Override_Damage_Min = 2 +ATT.Override_Num = 18 +ATT.Override_Range_Min = 50 +ATT.Override_Range_Max = 1200 + +ATT.Override_Spread = 0.05 +ATT.Override_ShotgunPelletSpread = 0.05 + +ATT.Override_HipFireSpreadPenalty = 0 +ATT.Override_MidAirSpreadPenalty = 0 + +ATT.Override_MuzzleVelocity = 9500 + +ATT.Override_Sound_ShootAdd = "^TacRP/weapons/m4star10/fire-2.wav" +ATT.Override_Pitch_Shoot = 95 + +ATT.DoorBreach = true +ATT.DoorBreachThreshold = 120 + +ATT.InstalledElements = {"buck"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_40mm_buck") +-- #endregion + +------------------------------ +-- #region ammo_40mm_gas +------------------------------ +ATT = {} + +ATT.PrintName = "CS Gas" +ATT.FullName = "40mm CS Gas Grenades" + +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_lvg.png", "mips smooth") +ATT.Description = "Grenade containing crowd control chemicals that deal lingering damage." +ATT.Pros = {"att.procon.crowd"} +ATT.Cons = {"att.procon.noexp", "att.procon.nonlethal"} + +ATT.Category = {"ammo_40mm", "ammo_40mm_civ"} + +ATT.SortOrder = 3.5 + +ATT.ShootEnt = "tacrp_proj_40mm_gas" + +ATT.InstalledElements = {"lvg"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_40mm_gas") +-- #endregion + +------------------------------ +-- #region ammo_40mm_heat +------------------------------ +ATT = {} + +ATT.PrintName = "Flechette" +ATT.FullName = "40mm Flechette Grenades" + +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_heat.png", "mips smooth") +ATT.Description = "Flat-top grenade packing accurate flechette darts." +ATT.Pros = {"att.procon.direct", "stat.spread", "stat.armorpenetration"} +ATT.Cons = {"att.procon.noexp"} + +ATT.Category = "ammo_40mm" + +ATT.Override_Ammo = "buckshot" -- extremely not realism + +ATT.SortOrder = 2.5 + +ATT.Override_ShootEnt = false + +ATT.InstalledElements = {"buck"} --{"heat"} + +ATT.Override_NoRanger = false + +ATT.Override_Damage_Max = 14 +ATT.Override_Damage_Min = 6 +ATT.Override_Num = 8 +ATT.Override_Range_Min = 500 +ATT.Override_Range_Max = 2000 +ATT.Override_Penetration = 6 + +ATT.Override_ArmorPenetration = 0.8 + +ATT.Override_Spread = 0.015 +ATT.Override_ShotgunPelletSpread = 0.02 + +ATT.Override_HipFireSpreadPenalty = 0.03 +ATT.Override_MidAirSpreadPenalty = 0 + + +ATT.MuzzleVelocity = 15000 + +ATT.Override_Sound_ShootAdd = "^tacrp/weapons/m4star10/fire-2.wav" +ATT.Override_Pitch_Shoot = 110 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_40mm_heat") +-- #endregion + +------------------------------ +-- #region ammo_40mm_impact +------------------------------ +ATT = {} + +ATT.PrintName = "Dummy" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_smoke.png", "mips smooth") +ATT.Description = "" +ATT.Pros = {"Infinite ammo"} +ATT.Cons = {"Impact only"} + +ATT.Category = {"ammo_40mm", "ammo_40mm_civ"} + +ATT.ShootEnt = "tacrp_proj_40mm_impact" +ATT.Mult_ShootEntForce = 1 + +ATT.InfiniteAmmo = true + +ATT.InstalledElements = {"smoke"} + +ATT.Ignore = true + +TacRP.LoadAtt(ATT, "ammo_40mm_impact") +-- #endregion + +------------------------------ +-- #region ammo_40mm_lvg +------------------------------ +ATT = {} + +ATT.PrintName = "Concussion" --"LVG" +ATT.FullName = "40mm Concussion Grenades" + +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_concussion.png", "mips smooth") +ATT.Description = "Low velocity grenade made to incapacitate targets with indirect fire." +ATT.Pros = {"att.procon.detdelay", "att.procon.flash"} +ATT.Cons = {"stat.muzzlevelocity", "stat.damage"} + +ATT.Category = {"ammo_40mm", "ammo_40mm_civ"} + +ATT.SortOrder = 3 + +ATT.ShootEnt = "tacrp_proj_40mm_lvg" +ATT.Mult_ShootEntForce = 0.5 + +ATT.InstalledElements = {"lvg"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_40mm_lvg") +-- #endregion + +------------------------------ +-- #region ammo_40mm_ratshot +------------------------------ +ATT = {} + +ATT.PrintName = "Ratshot" +ATT.FullName = "40mm Ratshot Grenades" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_ratshot.png", "mips smooth") +ATT.Description = "For rodents of unbelievable size." +ATT.Pros = {"att.procon.radius", "att.procon.proxfuse"} +ATT.Cons = {"stat.damage", "stat.muzzlevelocity"} + +ATT.Category = "ammo_40mm" + +ATT.SortOrder = 2.9 + +ATT.Override_Damage_Max = 80 +ATT.Override_Damage_Min = 80 + +ATT.ShootEnt = "tacrp_proj_40mm_ratshot" +ATT.Mult_ShootEntForce = 0.75 + +ATT.InstalledElements = {"smoke"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true + ATT.Override_Damage_Max = 60 + ATT.Override_Damage_Min = 60 +end + +TacRP.LoadAtt(ATT, "ammo_40mm_ratshot") +-- #endregion + +------------------------------ +-- #region ammo_40mm_smoke +------------------------------ +ATT = {} + +ATT.PrintName = "Smoke" +ATT.FullName = "40mm Smoke Grenades" + +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_smoke.png", "mips smooth") +ATT.Description = "Grenade that produces a concealing smokescreen on impact." +ATT.Pros = {"att.procon.smoke"} +ATT.Cons = {"att.procon.noexp"} + +ATT.Category = {"ammo_40mm", "ammo_40mm_civ"} + +ATT.SortOrder = 4 + +ATT.ShootEnt = "tacrp_proj_40mm_smoke" + +ATT.InstalledElements = {"smoke"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_40mm_smoke") +-- #endregion + +------------------------------ +-- #region ammo_40mm_heal +------------------------------ +ATT = {} + +ATT.PrintName = "Medi-Smoke" +ATT.FullName = "40mm Medi-Smoke Grenades" + +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_smoke.png", "mips smooth") +ATT.Description = "Grenade that produces a cloud of restorative gas on impact." +ATT.Pros = {"att.procon.heal"} +ATT.Cons = {"att.procon.noexp"} + +ATT.Category = {"ammo_40mm", "ammo_40mm_civ"} + +ATT.SortOrder = 4.1 + +ATT.ShootEnt = "tacrp_proj_40mm_heal" + +ATT.InstalledElements = {"smoke"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_40mm_heal") +-- #endregion + +------------------------------ +-- #region ammo_amr_hv +------------------------------ +ATT = {} + +ATT.PrintName = "HV" +ATT.FullName = "High Velocity Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_match.png", "mips smooth") +ATT.Description = "Bullets with much higher velocity, but worsens overpenetration." +ATT.Pros = {"stat.range", "stat.muzzlevelocity"} +ATT.Cons = {"stat.damage_max"} + +ATT.Category = {"ammo_amr", "ammo_sniper"} + +ATT.SortOrder = 2.5 + +ATT.Mult_MuzzleVelocity = 1.5 + +ATT.Mult_Range_Max = 1.25 +ATT.Mult_Damage_Max = 0.85 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_amr_hv") +-- #endregion + +------------------------------ +-- #region ammo_amr_ratshot +------------------------------ +ATT = {} + +ATT.PrintName = "Ratshot" +ATT.FullName = "Ratshot Rounds" +ATT.Icon = Material("entities/tacrp_att_ammo_amr_ratshot.png", "mips smooth") +ATT.Description = "For rodents of unusual size." +ATT.Pros = {"att.procon.moreproj", "rating.maneuvering"} +ATT.Cons = {"stat.damage", "stat.range", "stat.spread"} + +ATT.Category = {"ammo_amr"} + +ATT.SortOrder = 5 + +ATT.Override_MuzzleVelocity = 9000 + +ATT.Override_Num = 16 +ATT.Override_Damage_Max = 7 +ATT.Override_Damage_Min = 1 +ATT.Override_Penetration = 1 + +ATT.Mult_Range_Min = 0.3 +ATT.Mult_Range_Max = 0.3 + +ATT.Mult_HipFireSpreadPenalty = 0.25 +ATT.Mult_MidAirSpreadPenalty = 0 +ATT.Mult_MovingSpreadPenalty = 0 + +ATT.Add_Spread = 0.02 +ATT.Add_ShotgunPelletSpread = 0.03 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_amr_ratshot") +-- #endregion + +------------------------------ +-- #region ammo_amr_saphe +------------------------------ +ATT = {} + +ATT.PrintName = "SAPHE" +ATT.FullName = "Semi-Armor-Piercing High-Explosive Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_saphe.png", "mips smooth") +ATT.Description = "Explosive rounds effective against both armor and personnel." +ATT.Pros = {"att.procon.explosive", "stat.penetration"} +ATT.Cons = {"stat.rpm", "stat.clipsize", "stat.muzzlevelocity"} + +ATT.Category = "ammo_amr" + +ATT.SortOrder = 4 + +ATT.ExplosiveEffect = "HelicopterMegaBomb" +ATT.ExplosiveDamage = 60 +ATT.ExplosiveRadius = 200 + +ATT.Add_Damage_Max = -60 +ATT.Add_Damage_Min = -60 + +ATT.Mult_MuzzleVelocity = 0.667 + +ATT.Mult_Penetration = 1.5 + +ATT.Mult_ClipSize = 0.51 + +ATT.Mult_RPM = 0.85 +ATT.Mult_ShootTimeMult = 1 / 0.85 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_amr_saphe") +-- #endregion + +------------------------------ +-- #region ammo_ks23_flashbang +------------------------------ +ATT = {} + +ATT.PrintName = "Zvezda" +ATT.FullName = "KS-23 Zvezda Flash Shells" +ATT.Icon = Material("entities/tacrp_att_ammo_ks23_flashbang.png", "mips smooth") +ATT.Description = "Flashbang shells that stun enemies, right from the barrel." +ATT.Pros = {"att.procon.flash"} +ATT.Cons = {"att.procon.timedfuse", "att.procon.nonlethal"} + +ATT.SortOrder = 1 +ATT.Category = "ammo_ks23" + +ATT.Override_MuzzleEffect = "muzzleflash_smg" + +ATT.ShootEnt = "tacrp_proj_ks23_flashbang" + +ATT.Num = 1 +ATT.ShootEntForce = 1200 + +TacRP.LoadAtt(ATT, "ammo_ks23_flashbang") +-- #endregion + +------------------------------ +-- #region ammo_ks23_flashbang_top +------------------------------ +ATT = {} + +ATT.PrintName = "Zvezda (Top)" +ATT.FullName = "KS-23 Zvezda Flash Shells (Top-loaded)" +ATT.Icon = Material("entities/tacrp_att_ammo_ks23_flashbang.png", "mips smooth") +ATT.Description = "Load the first round with flash rounds and the rest with standard shells." +ATT.Pros = {"att.procon.flash"} +ATT.Cons = {"att.procon.firstround"} + +ATT.SortOrder = 1.1 +ATT.Category = "ammo_ks23" +ATT.InvAtt = "ammo_ks23_flashbang" + +ATT.ShootEntForce = 1200 + +ATT.Func_Num = function(wep, modifiers) + if wep:Clip1() == wep:GetMaxClip1() then + modifiers.set = 1 + modifiers.prio = 10 + end +end +ATT.Func_ShootEnt = function(wep, modifiers) + if wep:Clip1() == wep:GetMaxClip1() then + modifiers.set = "tacrp_proj_ks23_flashbang" + modifiers.prio = 10 + end +end +ATT.Func_MuzzleEffect = function(wep, modifiers) + if wep:Clip1() == wep:GetMaxClip1() - 1 then + modifiers.set = "muzzleflash_smg" + modifiers.prio = 10 + end +end + +TacRP.LoadAtt(ATT, "ammo_ks23_flashbang_top") +-- #endregion + +------------------------------ +-- #region ammo_magnum +------------------------------ +ATT = {} + +ATT.PrintName = "+P" +ATT.FullName = "Overpressured Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_plusp.png", "mips smooth") +ATT.Description = "Bullets that maintain close range power better, but have higher recoil." +ATT.Pros = {"stat.range_min", "stat.muzzlevelocity"} +ATT.Cons = {"stat.recoilkick", "stat.spread"} + +ATT.Category = {"ammo_pistol", "ammo_rifle"} + +ATT.SortOrder = 5 + +ATT.Add_RecoilKick = 0.25 +ATT.Mult_RecoilKick = 1.15 +ATT.Mult_Spread = 1.25 +ATT.Mult_MuzzleVelocity = 1.25 +ATT.Add_Range_Min = 400 +-- ATT.Mult_Range_Min = 1.25 + +TacRP.LoadAtt(ATT, "ammo_magnum") +-- #endregion + +------------------------------ +-- #region ammo_pistol_ap +------------------------------ +ATT = {} + +ATT.PrintName = "Steel Core" +ATT.FullName = "Steel Core Rounds" + +ATT.Icon = Material("entities/tacrp_att_ammo_pistol_ap.png", "mips smooth") +ATT.Description = "Hardened bullets better penetrate armor, but destabilize ballistics." +ATT.Pros = {"att.procon.armor", "stat.penetration"} +ATT.Cons = {"stat.spread", "stat.muzzlevelocity", "stat.range"} + +ATT.Category = {"ammo_pistol", "ammo_pistol_sub"} + +ATT.SortOrder = 1.5 + +ATT.Add_Penetration = 5 +ATT.Add_ArmorPenetration = 0.1 +ATT.Add_ArmorBonus = 0.25 + +ATT.Mult_MuzzleVelocity = 0.85 +ATT.Mult_Range_Max = 0.85 +ATT.Mult_Spread = 1.25 + + +TacRP.LoadAtt(ATT, "ammo_pistol_ap") +-- #endregion + +------------------------------ +-- #region ammo_pistol_headshot +------------------------------ +ATT = {} + +ATT.PrintName = "Skullsplitter" +ATT.FullName = "Skullsplitter Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_skullsplitter.png", "mips smooth") +ATT.Description = "Specialized rounds that do more damage to vital body parts." +ATT.Pros = {"att.procon.head", "stat.spread"} +ATT.Cons = {"att.procon.limb", "stat.armorbonus"} + +ATT.Category = {"ammo_pistol", "ammo_pistol_sub"} + +ATT.SortOrder = 1.25 + +ATT.Mult_Spread = 0.85 +ATT.Mult_ArmorBonus = 0.5 + +ATT.Override_BodyDamageMultipliersExtra = { + [HITGROUP_HEAD] = 1.5, + [HITGROUP_LEFTARM] = 0.8, + [HITGROUP_RIGHTARM] = 0.8, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +TacRP.LoadAtt(ATT, "ammo_pistol_headshot") +-- #endregion + +------------------------------ +-- #region ammo_pistol_hollowpoints +------------------------------ +ATT = {} + +ATT.PrintName = "Hollow-points" +ATT.FullName = "Hollow-point Rounds" + +ATT.Icon = Material("entities/tacrp_att_acc_hollowpoints.png", "mips smooth") +ATT.Description = "Bullets that expand on hit, improving damage to flesh targets and limbs." +ATT.Pros = {"att.procon.chest", "att.procon.limb"} +ATT.Cons = {"att.procon.head", "att.procon.armor", "stat.penetration"} + +ATT.Category = {"ammo_pistol", "ammo_pistol_sub"} + +ATT.SortOrder = 1 + +ATT.Mult_Penetration = 0.2 +ATT.Mult_ArmorPenetration = 0.75 +ATT.Mult_ArmorBonus = 0.75 + +ATT.Override_BodyDamageMultipliersExtra = { + [HITGROUP_HEAD] = 0.75, + [HITGROUP_CHEST] = 1.15, + [HITGROUP_LEFTARM] = -1, + [HITGROUP_RIGHTARM] = -1, + [HITGROUP_LEFTLEG] = -1, + [HITGROUP_RIGHTLEG] = -1, + [HITGROUP_GEAR] = -1, +} + +TacRP.LoadAtt(ATT, "ammo_pistol_hollowpoints") +-- #endregion + + +------------------------------ +-- #region ammo_rifle_jhp +------------------------------ +ATT = {} + +ATT.PrintName = "JHP" +ATT.FullName = "Jacketed Hollow-point Rounds" +ATT.Icon = Material("entities/tacrp_att_ammo_rifle_jhp.png", "mips smooth") +ATT.Description = "Bullets that expand on hit, improving damage to flesh targets and limbs." +ATT.Pros = {"att.procon.chest", "att.procon.limb"} +ATT.Cons = {"stat.range", "att.procon.armor", "stat.penetration"} + +ATT.Category = {"ammo_rifle", "ammo_rifle_sub"} + +ATT.SortOrder = 1 + +ATT.Mult_Range_Min = 0.5 +ATT.Mult_Range_Max = 0.85 +ATT.Mult_Penetration = 0.2 +ATT.Mult_ArmorPenetration = 0.75 +ATT.Mult_ArmorBonus = 0.5 + +ATT.Override_BodyDamageMultipliersExtra = { + [HITGROUP_CHEST] = 1.25, + [HITGROUP_LEFTARM] = -1, + [HITGROUP_RIGHTARM] = -1, + [HITGROUP_LEFTLEG] = -1, + [HITGROUP_RIGHTLEG] = -1, + [HITGROUP_GEAR] = -1, +} + + +TacRP.LoadAtt(ATT, "ammo_rifle_jhp") +-- #endregion + +------------------------------ +-- #region ammo_pistol_match +------------------------------ +ATT = {} + +ATT.PrintName = "Match" +ATT.FullName = "Pistol Match Rounds" +ATT.Icon = Material("entities/tacrp_att_ammo_pistol_match.png", "mips smooth") +ATT.Description = "Bullets with improved range and accuracy." +ATT.Pros = {"stat.spread", "stat.range_max"} +ATT.Cons = {"stat.hipfirespread", "stat.peekpenalty"} + +ATT.Category = {"ammo_pistol", "ammo_pistol_sub"} + +ATT.SortOrder = 4.5 + +ATT.Mult_Spread = 0.667 +ATT.Add_Range_Max = 750 +ATT.Add_HipFireSpreadPenalty = 0.01 +ATT.Add_PeekPenaltyFraction = 0.05 + +TacRP.LoadAtt(ATT, "ammo_pistol_match") +-- #endregion + +------------------------------ +-- #region ammo_rifle_match +------------------------------ +ATT = {} + +ATT.PrintName = "Match" +ATT.FullName = "Rifle Match Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_match.png", "mips smooth") +ATT.Description = "Bullets with greatly improved accuracy." +ATT.Pros = {"stat.spread", "stat.muzzlevelocity", "stat.bloomintensity"} +ATT.Cons = {"stat.hipfirespread", "att.procon.limb"} + +ATT.Category = {"ammo_rifle", "ammo_rifle_sub"} + +ATT.SortOrder = 2 + +ATT.Mult_Spread = 0.25 +ATT.Mult_MuzzleVelocity = 1.5 +ATT.Mult_RecoilSpreadPenalty = 0.85 +ATT.Add_HipFireSpreadPenalty = 0.015 + +ATT.Override_BodyDamageMultipliersExtra = { + [HITGROUP_LEFTARM] = 0.95, + [HITGROUP_RIGHTARM] = 0.95, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.85, +} + +TacRP.LoadAtt(ATT, "ammo_rifle_match") +-- #endregion + +------------------------------ +-- #region ammo_roulette +------------------------------ +ATT = {} + +ATT.PrintName = "Roulette" +ATT.FullName = "Russian Roulette" +ATT.Icon = Material("entities/tacrp_att_acc_roulette.png", "mips smooth") +ATT.Description = "A lethal game of chance. Spin the cylinder while loaded to reset the odds." +ATT.Pros = {} +ATT.Cons = {"att.procon.onebullet"} +ATT.Category = {"ammo_roulette"} + +ATT.Free = true + +ATT.SortOrder = -1 + +--ATT.Mult_ShootChance = 1 / 6 +ATT.Override_ClipSize = 1 +ATT.Override_AmmoPerShot = 1 + +ATT.Hook_PreReload = function(wep) + if wep:StillWaiting(true) then return end + if wep:Clip1() < 1 then return end + if wep:Ammo1() <= 0 and !wep:GetInfiniteAmmo() then return end + + if SERVER then + wep:SetNWInt("TacRP_RouletteShot", math.random(1, wep:GetBaseValue("ClipSize"))) + wep:PlayAnimation("jam", 1, true, true) + wep:SetNextPrimaryFire(CurTime() + 1) + wep:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM) + end + return true +end + +ATT.Hook_EndReload = function(wep) + if SERVER then + wep:SetNWInt("TacRP_RouletteShot", math.random(1, wep:GetBaseValue("ClipSize"))) + end +end + +ATT.Hook_PreShoot = function(wep) + if SERVER and wep:GetNWInt("TacRP_RouletteShot", 0) == 0 then + wep:SetNWInt("TacRP_RouletteShot", math.random(1, wep:GetBaseValue("ClipSize"))) + end + + if wep:GetNWInt("TacRP_RouletteShot") != wep:GetNthShot() % wep:GetBaseValue("ClipSize") + 1 then + wep.Primary.Automatic = false + if wep:GetBlindFire() then + wep:PlayAnimation("blind_dryfire") + else + wep:PlayAnimation("dryfire") + end + wep:EmitSound(wep:GetValue("Sound_DryFire"), 75, 100, 1, CHAN_BODY) + wep:SetBurstCount(0) + wep:SetNthShot(wep:GetNthShot() + 1) + wep:SetNextPrimaryFire(CurTime() + (60 / wep:GetValue("RPM"))) + wep:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL) + + return true + end +end + +if engine.ActiveGamemode() == "terrortown" then + ATT.Pros = {"att.procon.explosive"} + ATT.ExplosiveEffect = "HelicopterMegaBomb" + ATT.ExplosiveDamage = 60 + ATT.ExplosiveRadius = 256 +end + +TacRP.LoadAtt(ATT, "ammo_roulette") +-- #endregion + +------------------------------ +-- #region ammo_rpg_improvised +------------------------------ +ATT = {} + +ATT.PrintName = "Improvised" +ATT.FullName = "RPG-7 Improvised Warhead" +ATT.Icon = Material("entities/tacrp_att_ammo_rpg_improvised.png", "mips smooth") +ATT.Description = "Straight from the bargain bin." +ATT.Pros = {"att.procon.refund", "att.procon.nosafety", "rating.mobility"} +ATT.Cons = {"att.procon.projrng", "att.procon.failrng"} + +ATT.Category = "ammo_rpg" + +ATT.SortOrder = 1 + +ATT.Override_ShootEnt = "tacrp_proj_rpg7_improvised" +ATT.Add_ShootingSpeedMult = 0.3 +ATT.Add_ReloadSpeedMult = 0.15 + +ATT.Override_ShootEntForce = 0 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +ATT.Hook_PostShootEnt = function(wep) + if CLIENT then return end + if wep:GetOwner():IsPlayer() and !wep:GetInfiniteAmmo() and math.random() <= 0.3 then + wep:GetOwner():GiveAmmo(math.random(1, wep:GetValue("AmmoPerShot")), wep:GetPrimaryAmmoType(), true) + end +end + +TacRP.LoadAtt(ATT, "ammo_rpg_improvised") +-- #endregion + +------------------------------ +-- #region ammo_rpg_mortar +------------------------------ +ATT = {} + +ATT.PrintName = "Mortar" +ATT.FullName = "RPG-7 Mortar Warhead" +ATT.Icon = Material("entities/tacrp_att_ammo_rpg_mortar.png", "mips smooth") +ATT.Description = "A mortar with a booster stuck to it, for \"indirect fire\". Needs time to prime." +ATT.Pros = {"att.procon.radius"} +ATT.Cons = {"att.procon.needprime"} + +ATT.Category = "ammo_rpg" + +ATT.SortOrder = 3 + +ATT.Override_ShootEnt = "tacrp_proj_rpg7_mortar" + +ATT.Override_ShootEntForce = 2500 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_rpg_mortar") +-- #endregion + +------------------------------ +-- #region ammo_rpg_ratshot +------------------------------ +ATT = {} + +ATT.PrintName = "Ratshot" +ATT.FullName = "RPG-7 Ratshot Warhead" +ATT.Icon = Material("entities/tacrp_att_ammo_rpg_ratshot.png", "mips smooth") +ATT.Description = "For rodents of unacceptable size." +ATT.Pros = {"att.procon.airburst"} +ATT.Cons = {"att.procon.timedfuse"} + +ATT.Category = "ammo_rpg" + +ATT.SortOrder = 2 + +ATT.Override_ShootEnt = "tacrp_proj_rpg7_ratshot" +ATT.Override_ShootEntForce = 1000 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_rpg_ratshot") +-- #endregion + +------------------------------ +-- #region ammo_rpg_harpoon +------------------------------ +ATT = {} + +ATT.PrintName = "Shovel" +ATT.FullName = "RPG-7 Shovel Warhead" +ATT.Icon = Material("entities/tacrp_att_ammo_rpg_improvised.png", "mips smooth") +ATT.Description = "Fire shovels, somehow. Either you're crazy, out of rockets, or both." +ATT.Pros = {"att.procon.shovel"} +ATT.Cons = {"att.procon.shovel"} + +ATT.Category = "ammo_rpg" +ATT.Free = true + +ATT.SortOrder = 9 + +ATT.Mult_HipFireSpreadPenalty = 0.5 + +ATT.Override_ShootEnt = "tacrp_proj_rpg7_harpoon" +ATT.Add_ShootingSpeedMult = 0.6 +ATT.Add_ReloadSpeedMult = 0.4 + +ATT.Add_RPM = 60 + +ATT.ReloadTimeMult = 0.9 + +ATT.Mult_ShootEntForce = 0.85 + +ATT.Override_Sound_Shoot = "weapons/crossbow/fire1.wav" + +ATT.Override_DefaultBodygroups = "02" +ATT.Override_DefaultWMBodygroups = "02" + +ATT.Ammo = "xbowbolt" + +ATT.Mult_ReloadTimeMult = 0.85 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_rpg_harpoon") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_bird +------------------------------ +ATT = {} + +ATT.PrintName = "Birdshot" +ATT.Icon = Material("entities/tacrp_att_acc_bird.png", "mips smooth") +ATT.Description = "Fire smaller pellets in a larger spread." +ATT.Pros = {"att.procon.moreproj", "stat.recoil"} +ATT.Cons = {"stat.spread"} + +ATT.Category = {"ammo_shotgun", "ammo_shotgun2"} + +ATT.SortOrder = 2 + +ATT.Add_ArmorPenetration = -0.1 + +ATT.Mult_Damage_Min = 0.55 +ATT.Mult_Damage_Max = 0.55 + +ATT.Mult_Num = 2 +ATT.Mult_RecoilKick = 0.85 + +-- ATT.Add_Spread = 0.02 +-- ATT.Add_ShotgunPelletSpread = 0.008 + +ATT.Mult_Spread = 1.75 +ATT.Mult_ShotgunPelletSpread = 1.75 + +TacRP.LoadAtt(ATT, "ammo_shotgun_bird") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_mag +------------------------------ +ATT = {} + +ATT.PrintName = "Magnum Buck" +ATT.FullName = "Magnum Buckshot" +ATT.Icon = Material("entities/tacrp_att_acc_magnum.png", "mips smooth") +ATT.Description = "High yield powder improves damage retention past point blank." +ATT.Pros = {"stat.range_min", "stat.muzzlevelocity"} +ATT.Cons = {"stat.recoil", "rating.mobility"} + +ATT.Category = {"ammo_shotgun", "ammo_shotgun2"} + +ATT.SortOrder = 3 + +ATT.Add_Range_Min = 250 + +ATT.Mult_RecoilKick = 1.5 +ATT.Mult_MuzzleVelocity = 1.5 + +ATT.Add_Spread = 0.005 +ATT.Add_ShotgunPelletSpread = 0.005 + +ATT.Mult_RecoilSpreadPenalty = 1.25 + +ATT.Mult_ShootingSpeedMult = 0.8 + +TacRP.LoadAtt(ATT, "ammo_shotgun_mag") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_slugs +------------------------------ +ATT = {} + +ATT.PrintName = "Slugs" +ATT.FullName = "Slug Shells" +ATT.Icon = Material("entities/tacrp_att_acc_slugs.png", "mips smooth") +ATT.Description = "Fire a single projectile for medium range shooting." +ATT.Pros = {"stat.spread", "stat.range"} +ATT.Cons = {"att.procon.1proj", "stat.hipfirespread"} + +ATT.Category = "ammo_shotgun" + +ATT.SortOrder = 4 + +ATT.Add_ArmorPenetration = 0.2 + +ATT.Mult_Damage_Min = 6 +ATT.Mult_Damage_Max = 6 + +ATT.Mult_Range_Max = 1.5 + +ATT.Num = 1 + +ATT.Mult_Spread = 0.2 +ATT.Mult_RecoilSpreadPenalty = 0.25 + +ATT.Add_HipFireSpreadPenalty = 0.025 + +ATT.Mult_MuzzleVelocity = 1.5 + +ATT.Override_MuzzleEffect = "muzzleflash_slug" + +ATT.Override_BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +TacRP.LoadAtt(ATT, "ammo_shotgun_slugs") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_slugs2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.ammo_shotgun_slugs.name" +ATT.FullName = "att.ammo_shotgun_slugs.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_slugs.png", "mips smooth") +ATT.Description = "att.ammo_shotgun_slugs.desc" +ATT.Pros = {"stat.spread", "stat.range"} +ATT.Cons = {"att.procon.1proj", "stat.hipfirespread"} + +ATT.Category = "ammo_shotgun2" +ATT.InvAtt = "ammo_shotgun_slugs" + +ATT.SortOrder = 4 + +ATT.Add_ArmorPenetration = 0.15 + +ATT.Mult_Damage_Min = 7 +ATT.Mult_Damage_Max = 4.5 + +ATT.Mult_Range_Min = 1.5 +ATT.Mult_Range_Max = 1.5 + +ATT.Num = 1 + +ATT.Mult_Spread = 0.2 +ATT.Mult_RecoilSpreadPenalty = 0.25 + +ATT.Add_HipFireSpreadPenalty = 0.025 + +ATT.Mult_MuzzleVelocity = 1.5 + +ATT.Override_MuzzleEffect = "muzzleflash_slug" + +ATT.Override_BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +TacRP.LoadAtt(ATT, "ammo_shotgun_slugs2") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_triple +------------------------------ +ATT = {} + +ATT.PrintName = "Triple Hit" +ATT.FullName = "Triple Hit Shells" +ATT.Icon = Material("entities/tacrp_att_acc_triple.png", "mips smooth") +ATT.Description = "Fire three projectiles for more accuracy." +ATT.Pros = {"stat.spread"} +ATT.Cons = {"att.procon.3proj", "stat.hipfirespread"} + +ATT.Category = "ammo_shotgun" + +ATT.SortOrder = 5 + +ATT.Add_ArmorPenetration = 0.1 + +ATT.Mult_Damage_Max = 2.5 +ATT.Mult_Damage_Min = 2.5 + +ATT.Num = 3 + +ATT.Mult_Spread = 0.33333 +ATT.Mult_ShotgunPelletSpread = 0.5 + +ATT.Mult_RecoilSpreadPenalty = 0.5 + +ATT.Add_HipFireSpreadPenalty = 0.01 + +ATT.Mult_MuzzleVelocity = 1.25 + +ATT.Override_MuzzleEffect = "muzzleflash_slug" + +ATT.Override_BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1.5, + [HITGROUP_CHEST] = 1.15, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +TacRP.LoadAtt(ATT, "ammo_shotgun_triple") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_triple2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.ammo_shotgun_triple.name" +ATT.FullName = "att.ammo_shotgun_triple.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_triple.png", "mips smooth") +ATT.Description = "att.ammo_shotgun_triple.desc" +ATT.Pros = {"stat.spread"} +ATT.Cons = {"att.procon.3proj", "stat.hipfirespread"} + +ATT.Category = "ammo_shotgun2" +ATT.InvAtt = "ammo_shotgun_triple" + +ATT.SortOrder = 5 + +ATT.Add_ArmorPenetration = 0.075 + +ATT.Mult_Damage_Max = 2 +ATT.Mult_Damage_Min = 2 + +ATT.Num = 3 + +ATT.Mult_Spread = 0.33333 +ATT.Mult_ShotgunPelletSpread = 0.5 + +ATT.Mult_RecoilSpreadPenalty = 0.5 + +ATT.Add_HipFireSpreadPenalty = 0.005 + +ATT.Mult_MuzzleVelocity = 1.25 + +ATT.Override_MuzzleEffect = "muzzleflash_slug" + +ATT.Override_BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1.5, + [HITGROUP_CHEST] = 1.15, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +TacRP.LoadAtt(ATT, "ammo_shotgun_triple2") +-- #endregion + +------------------------------ +-- #region ammo_subsonic +------------------------------ +ATT = {} + +ATT.PrintName = "Subsonic" +ATT.FullName = "Subsonic Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_subsonic.png", "mips smooth") +ATT.Description = "Bullets with reduced powder load." +ATT.Pros = {"att.procon.notracer", "stat.recoil"} +ATT.Cons = {"stat.muzzlevelocity", "stat.range_max"} + +ATT.Category = {"ammo_rifle", "ammo_pistol"} + +ATT.SortOrder = 2 + +ATT.Mult_RecoilKick = 0.85 +ATT.TracerNum = 0 +ATT.Mult_MuzzleVelocity = 0.75 +ATT.Mult_Vol_Shoot = 0.9 +ATT.Mult_Range_Max = 0.85 + +TacRP.LoadAtt(ATT, "ammo_subsonic") +-- #endregion + +------------------------------ +-- #region ammo_surplus +------------------------------ +ATT = {} + +ATT.PrintName = "Surplus" +ATT.FullName = "Surplus Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_surplus.png", "mips smooth") +ATT.Description = "Unreliable old ammo, yet you keep finding them everywhere." +ATT.Pros = {"att.procon.refund", "stat.recoil"} +ATT.Cons = {"att.procon.unreliable"} +ATT.Category = {"ammo_rifle", "ammo_sniper", "ammo_pistol", "ammo_amr", "ammo_shotgun", "ammo_shotgun2", "ammo_rifle_sub", "ammo_pistol_sub"} + +ATT.SortOrder = 999 + +-- ATT.Mult_SupplyLimit = 2 +-- ATT.Mult_ShootChance = 0.98 + +ATT.Mult_RecoilSpreadPenalty = 0.9 +ATT.Mult_RecoilKick = 0.85 + +ATT.Add_JamFactor = 0.2 +ATT.Add_ShootPitchVariance = 2 + +ATT.Hook_PostShoot = function(wep) + if CLIENT then return end + if wep:GetOwner():IsPlayer() and !wep:GetInfiniteAmmo() and math.random() <= 0.5 then + wep:GetOwner():GiveAmmo(math.random(1, wep:GetValue("AmmoPerShot")), wep:GetPrimaryAmmoType(), true) + end +end + +TacRP.LoadAtt(ATT, "ammo_surplus") +-- #endregion + +------------------------------ +-- #region ammo_tmj +------------------------------ +ATT = {} + +ATT.PrintName = "TMJ" +ATT.FullName = "Total Metal Jacket Rounds" +ATT.Icon = Material("entities/tacrp_att_acc_tmj.png", "mips smooth") +ATT.Description = "Bullets with improved penetration capability." +ATT.Pros = {"att.procon.armor", "stat.penetration"} +ATT.Cons = {"stat.recoilfirstshot", "stat.recoilmaximum"} +ATT.Category = {"ammo_rifle", "ammo_sniper", "ammo_amr", "ammo_rifle_sub"} + +ATT.SortOrder = 1.5 + +ATT.Add_Penetration = 10 +ATT.Add_RecoilFirstShotMult = 1 +ATT.Add_RecoilMaximum = 2 +ATT.Add_ArmorPenetration = 0.05 + +TacRP.LoadAtt(ATT, "ammo_tmj") +-- #endregion + + +------------------------------ +-- #region ammo_buckshot_roulette +------------------------------ +ATT = {} + +ATT.PrintName = "B. Roulette" +ATT.FullName = "Buckshot Roulette" +ATT.Icon = Material("entities/tacrp_att_ammo_buckshot_roulette.png", "mips smooth") +ATT.Description = "The shells enter the chamber in an unknown order." +ATT.Cons = {"att.procon.nopartialreloads"} +ATT.Category = "ammo_shotgun" + +ATT.SortOrder = -1 + +ATT.Free = true + +ATT.ShotgunFullCancel = true +ATT.Override_Firemodes = {1} +ATT.Override_Priority_Firemodes = 999 + +ATT.OnPresetLoad = function(wep) + if SERVER then + local blanks = math.random(1, wep:GetValue("ClipSize") - 1) + wep:SetNWInt("TacRP_RouletteBlanks", blanks) + + wep:GetOwner():PrintMessage(HUD_PRINTCENTER, tostring(wep:GetValue("ClipSize") - blanks) .. " LIVE, " .. tostring(blanks) .. " BLANK.") + end +end + +ATT.OnAttach = function(wep) + if SERVER then + wep:SetNWInt("TacRP_RouletteBlanks", 0) + wep:Unload() + end +end + +ATT.OnDetach = function(wep) + if SERVER then + wep:SetNWInt("TacRP_RouletteBlanks", 0) + wep:Unload() + end +end + +ATT.Hook_PreReload = function(wep) + if wep:StillWaiting(true) then return end + if wep:Ammo1() <= 0 and !wep:GetInfiniteAmmo() then return end + + if wep:Clip1() > 0 then return true end + + return +end + +ATT.Hook_EndReload = function(wep) + if SERVER then + local blanks = math.random(1, wep:Clip1() - 1) + wep:SetNWInt("TacRP_RouletteBlanks", blanks) + + wep:GetOwner():PrintMessage(HUD_PRINTCENTER, tostring(wep:Clip1() - blanks) .. " LIVE, " .. tostring(blanks) .. " BLANK.") + end +end + +ATT.Hook_InsertReload = function(wep) + if SERVER then + local blanks = math.random(1, wep:Clip1() - 1) + wep:SetNWInt("TacRP_RouletteBlanks", blanks) + end +end + +ATT.Hook_CancelReload = function(wep) + if SERVER then + local blanks = wep:GetNWInt("TacRP_RouletteBlanks") + + wep:GetOwner():PrintMessage(HUD_PRINTCENTER, tostring(wep:Clip1() - blanks) .. " LIVE, " .. tostring(blanks) .. " BLANK.") + end +end + +ATT.Hook_PreShoot = function(wep) + local chance = wep:GetNWInt("TacRP_RouletteBlanks") / wep:Clip1() + + if util.SharedRandom("tacRP_blankChance", 0, 1) <= chance then + if SERVER then + wep:SetNWInt("TacRP_RouletteBlanks", wep:GetNWInt("TacRP_RouletteBlanks") - 1) + end + + wep.Primary.Automatic = false + wep:PlayAnimation("jam") + wep:EmitSound(wep:GetValue("Sound_DryFire"), 75, 100, 1, CHAN_BODY) + wep:SetBurstCount(0) + wep:SetNthShot(wep:GetNthShot() + 1) + wep:SetNextPrimaryFire(CurTime() + math.max(60 / wep:GetValue("RPM"), 1)) + wep:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL) + + wep:TakePrimaryAmmo(wep:GetValue("AmmoPerShot")) + + return true + else + return + end +end + +TacRP.LoadAtt(ATT, "ammo_buckshotroulette") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_minishell +------------------------------ +ATT = {} + +ATT.PrintName = "Mini" +ATT.FullName = "Minishells" +ATT.Icon = Material("entities/tacrp_att_ammo_minishell.png", "mips smooth") +ATT.Description = "Short shells increase ammo capacity but don't hit as hard." +ATT.Pros = {"stat.clipsize", "stat.recoilkick"} +ATT.Cons = {"att.procon.lessproj"} + +ATT.Category = "ammo_shotgun" + +ATT.SortOrder = 1 + +ATT.Mult_Num = 0.6667 +ATT.Mult_RecoilKick = 0.85 +ATT.Mult_ClipSize = 1.5 + +ATT.Compatibility = function(wpn, cats) + if !wpn.ShotgunReload then return false end +end + +TacRP.LoadAtt(ATT, "ammo_shotgun_minishell") +-- #endregion + + +------------------------------ +-- #region ammo_shotgun_dragon +------------------------------ +ATT = {} + +ATT.PrintName = "Dragon" +ATT.FullName = "Dragon's Breath" +ATT.Icon = Material("entities/tacrp_att_ammo_dragonsbreath.png", "mips smooth") +ATT.Description = "Magnesium pellets set targets on fire, but have poor range and damage." +ATT.Pros = {"att.procon.incendiary"} +ATT.Cons = {"stat.damage", "stat.muzzlevelocity", "stat.spread"} + +ATT.Category = {"ammo_shotgun", "ammo_shotgun2"} + +ATT.SortOrder = 6 + +ATT.Mult_Damage_Max = 0.75 +ATT.Mult_Damage_Min = 0.5 +ATT.Mult_MuzzleVelocity = 0.75 +ATT.Add_Spread = 0.03 +ATT.Add_ShotgunPelletSpread = 0.03 + +ATT.Override_ExplosiveEffect = "ManhackSparks" +-- ATT.Add_ExplosiveDamage = 1 +-- ATT.Add_ExplosiveRadius = 16 + +ATT.Override_DamageType = DMG_BURN + +TacRP.LoadAtt(ATT, "ammo_shotgun_dragon") +-- #endregion + +------------------------------ +-- #region ammo_shotgun_frag +------------------------------ +ATT = {} + +ATT.PrintName = "Frag" +ATT.FullName = "High Explosive Fragmentation" +ATT.Icon = Material("entities/tacrp_att_ammo_frag12.png", "mips smooth") +ATT.Description = "Explosive slugs deal area damage, but don't expect too much from them." +ATT.Pros = {"att.procon.explosive"} +ATT.Cons = {"stat.damage", "stat.spread", "stat.muzzlevelocity"} + +ATT.Category = {"ammo_shotgun", "ammo_shotgun2"} + +ATT.SortOrder = 6 + +ATT.Override_Damage_Min = 5 +ATT.Override_Damage_Max = 5 + +ATT.Num = 1 + +ATT.Mult_Spread = 0.6 + +ATT.Add_HipFireSpreadPenalty = 0.025 + +ATT.Mult_MuzzleVelocity = 0.5 + +ATT.Override_MuzzleEffect = "muzzleflash_slug" + +ATT.Override_ExplosiveEffect = "HelicopterMegaBomb" +ATT.Add_ExplosiveDamage = 40 +ATT.Add_ExplosiveRadius = 150 + +TacRP.LoadAtt(ATT, "ammo_shotgun_frag") +-- #endregion + + +------------------------------ +-- #region ammo_shotgun_breach +------------------------------ +ATT = {} + +ATT.PrintName = "Breach (Top)" +ATT.FullName = "Breaching Shell (Top-loaded)" +ATT.Icon = Material("entities/tacrp_att_ammo_breaching.png", "mips smooth") +ATT.Description = "Load the first round with a specialized breaching slug." +ATT.Pros = {"att.procon.doorbreach"} +ATT.Cons = {"att.procon.firstround"} + +ATT.Category = {"ammo_shotgun", "ammo_shotgun2"} + +ATT.SortOrder = 10 + +ATT.ShootEntForce = 2000 + +ATT.Func_Num = function(wep, modifiers) + if wep:Clip1() == wep:GetMaxClip1() then + modifiers.set = 1 + modifiers.prio = 10 + end +end +ATT.Func_ShootEnt = function(wep, modifiers) + if wep:Clip1() == wep:GetMaxClip1() then + modifiers.set = "tacrp_proj_breach_slug" + modifiers.prio = 10 + end +end +ATT.Func_MuzzleEffect = function(wep, modifiers) + if wep:Clip1() == wep:GetMaxClip1() - 1 then + modifiers.set = "muzzleflash_smg" + modifiers.prio = 10 + end +end + +TacRP.LoadAtt(ATT, "ammo_shotgun_breach") +-- #endregion + + +------------------------------ +-- #region ammo_stinger_saam +------------------------------ +ATT = {} + +ATT.PrintName = "Semi-Active" +ATT.FullName = "Stinger Semi-Active Missile" +ATT.Icon = Material("entities/tacrp_att_ammo_stinger.png", "mips smooth") +ATT.Description = "Powerful missiles that lock rapidly but require constant guidance." +ATT.Pros = {"att.procon.locktime", "stat.damage"} +ATT.Cons = {"att.procon.semiactive"} + +ATT.Category = "ammo_stinger" + +ATT.SortOrder = 1 + +ATT.Override_ShootEnt = "tacrp_proj_stinger_saam" + +ATT.Override_LockOnTrackAngle = 10 + +ATT.Override_LockOnTime = 0.1 + +ATT.NoAutoReload = true + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_stinger_saam") +-- #endregion + +------------------------------ +-- #region ammo_stinger_qaam +------------------------------ +ATT = {} + +ATT.PrintName = "Agile" +ATT.FullName = "Stinger High Agility Missile" +ATT.Icon = Material("entities/tacrp_att_ammo_stinger_qaam.png", "mips smooth") +ATT.Description = "Highly maneuverable missile with a short range and long lock time." +ATT.Pros = {"att.procon.proj.turn"} +ATT.Cons = {"att.procon.locktime"} + +ATT.Category = "ammo_stinger" + +ATT.SortOrder = 2 + +ATT.Override_ShootEnt = "tacrp_proj_stinger_qaam" + +ATT.Override_LockOnRange = 16000 +ATT.Override_LockOnTime = 1.5 +ATT.Range_Max = 16000 +ATT.Range_Min = 1000 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_stinger_qaam") +-- #endregion + +------------------------------ +-- #region ammo_stinger_4aam +------------------------------ +ATT = {} + +ATT.PrintName = "Quad" +ATT.FullName = "Stinger 4x Missile" +ATT.Icon = Material("entities/tacrp_att_ammo_stinger_4aam.png", "mips smooth") +ATT.Description = "Guided cluster missiles maximize pressure to enemy pilots." +ATT.Pros = {"att.procon.moreproj", "att.procon.proj.turn"} +ATT.Cons = {"stat.damage", "stat.spread", "att.procon.armdelay"} + +ATT.Category = "ammo_stinger" + +ATT.SortOrder = 3 + +ATT.Override_ShootEnt = "tacrp_proj_stinger_4aam" + +ATT.Override_LockOnTrackAngle = 10 + +ATT.Override_Num = 4 +ATT.Override_Spread = 0.1 +ATT.Override_Damage_Max = 75 +ATT.Override_Damage_Min = 75 + +ATT.Hook_PreShoot = function(wep) + wep.QuadShootIndex = 0 +end + +ATT.Hook_PreShootEnt = function(wep, ent) + wep.QuadShootIndex = wep.QuadShootIndex + 1 +end + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_stinger_4aam") +-- #endregion + +------------------------------ +-- #region ammo_stinger_apers +------------------------------ +ATT = {} + +ATT.PrintName = "Killer Bee" +ATT.FullName = "Stinger Anti-Personnel Missile" +ATT.Icon = Material("entities/tacrp_att_ammo_stinger_apers.png", "mips smooth") +ATT.Description = "For rodents of unacceptable agility." +ATT.Pros = {"att.procon.radius", "att.procon.proxfuse"} +ATT.Cons = {"stat.damage", "att.procon.armdelay"} + +ATT.Category = "ammo_stinger" + +ATT.SortOrder = 4 + +ATT.Override_ShootEnt = "tacrp_proj_stinger_apers" + +ATT.Override_LockOnRange = 6000 +ATT.Override_LockOnTrackAngle = 35 +ATT.Override_LockOnTime = 0.5 + +ATT.Range_Max = 6000 +ATT.Range_Min = 1000 + +ATT.ShootOffsetAngle = Angle(-30, 0, 0) +ATT.ShootEntForce = 3000 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_stinger_apers") +-- #endregion + + +------------------------------ +-- #region ammo_usp_9mm +------------------------------ +ATT = {} + +ATT.PrintName = "9x19mm" +ATT.FullName = "USP 9x19mm Conversion" +ATT.Icon = Material("entities/tacrp_ex_usp.png", "mips smooth") +ATT.Description = "Fire a smaller caliber round with higher capacity and firerate." +ATT.Pros = {"stat.clipsize", "stat.rpm", "stat.recoil"} +ATT.Cons = {"stat.damage"} + +ATT.SortOrder = 0 + +ATT.Category = "ammo_usp" + +ATT.Override_Ammo_Expanded = "pistol" + +ATT.Mult_RPM = 1.15 + +ATT.Add_ClipSize = 3 +ATT.Mult_RecoilSpreadPenalty = 0.85 +ATT.Mult_RecoilKick = 0.5 + +ATT.Mult_Damage_Max = 24 / 28 +ATT.Mult_Damage_Min = 10 / 12 + +ATT.Override_Sound_Shoot = "weapons/pistol/pistol_fire2.wav" -- maximum gordon freeman +ATT.Override_Sound_Shoot_Silenced = "tacrp/weapons/p2000/p2000_fire_silenced-1.wav" +ATT.Override_ShootPitchVariance = 2 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_usp_9mm") +-- #endregion + + +-- R700 ammos +ATT = {} + +ATT.Ignore = true + +ATT.PrintName = ".300 Win Mag" +ATT.FullName = "R700 .300 Win Mag Mod Kit" +ATT.Icon = Material("entities/tacrp_att_ammo_300winmag.png", "mips smooth") +ATT.Description = "Magnum cartridge with higher close range damage." +ATT.Pros = {"stat.damage_max"} +ATT.Cons = {"stat.damage_min", "stat.recoilkick", "stat.rpm"} + +ATT.Category = "ammo_R700" +ATT.SortOrder = 0.5 + +ATT.Mult_Damage_Max = 1.25 +ATT.Mult_Damage_Min = 0.8 + +ATT.Mult_RPM = 1 / 1.25 +ATT.Mult_ShootTimeMult = 1.25 + +ATT.Add_RecoilKick = 10 + +ATT.Override_Ammo = "357" +ATT.Override_Ammo_Expanded = "ti_sniper" + +ATT.Override_Sound_Shoot = "tacrp/weapons/spr/awp1.wav" +ATT.Add_Pitch_Shoot = 10 + +TacRP.LoadAtt(ATT, "ammo_r700_300winmag") diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/bolt_trigger.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/bolt_trigger.lua new file mode 100644 index 0000000..97d68c6 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/bolt_trigger.lua @@ -0,0 +1,753 @@ +-- bolt_trigger.lua + +local ATT = {} + +------------------------------ +-- #region bolt_fine +------------------------------ +ATT = {} + +ATT.PrintName = "Refined" +ATT.FullName = "Refined Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_fine.png", "mips smooth") +ATT.Description = "A delicate bolt suitable for short bursts." +ATT.Pros = {"stat.recoildissipation"} +ATT.Cons = {"stat.recoilpershot"} + +ATT.Category = "bolt_automatic" + +ATT.SortOrder = 3 + +ATT.Mult_RecoilDissipationRate = 1.25 +-- ATT.Mult_RecoilSpreadPenalty = 1.175 +ATT.Mult_RecoilPerShot = 1.1 + +TacRP.LoadAtt(ATT, "bolt_fine") +-- #endregion + +------------------------------ +-- #region bolt_greased +------------------------------ +ATT = {} + +ATT.PrintName = "Greased" +ATT.FullName = "Greased Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_greased.png", "mips smooth") +ATT.Description = "Faster cycle speed but handling is worse." +ATT.Pros = {"stat.rpm"} +ATT.Cons = {"rating.mobility", "stat.recoil", "stat.muzzlevelocity"} + +ATT.Category = "bolt_manual" + +ATT.SortOrder = 1 + +ATT.Mult_RPM = 1.15 +ATT.Mult_ShootTimeMult = 1 / 1.15 + +ATT.Add_RecoilVisualKick = 1 +ATT.Mult_RecoilKick = 1.5 +ATT.Add_ShootingSpeedMult = -0.1 +ATT.Add_SightedSpeedMult = -0.05 + +ATT.Mult_MuzzleVelocity = 0.85 + +TacRP.LoadAtt(ATT, "bolt_greased") +-- #endregion + +------------------------------ +-- #region bolt_heavy +------------------------------ +ATT = {} + +ATT.PrintName = "Heavy" +ATT.FullName = "Heavy Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_heavy.png", "mips smooth") +ATT.Description = "Reduce recoil at the cost of fire rate." +ATT.Pros = {"stat.recoilkick", "stat.bloomintensity"} +ATT.Cons = {"stat.rpm"} + +ATT.Category = "bolt_automatic" + +ATT.SortOrder = 2 + +ATT.Mult_RPM = 0.85 +ATT.Mult_RecoilVisualKick = 0.7 +ATT.Mult_RecoilKick = 0.7 +ATT.Mult_RecoilSpreadPenalty = 0.9 + +TacRP.LoadAtt(ATT, "bolt_heavy") +-- #endregion + +------------------------------ +-- #region bolt_light +------------------------------ +ATT = {} + +ATT.PrintName = "Light" +ATT.FullName = "Light Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_light.png", "mips smooth") +ATT.Description = "Increase fire rate at the cost of recoil." +ATT.Pros = {"stat.rpm"} +ATT.Cons = {"stat.recoilkick", "stat.bloomintensity"} + +ATT.Category = "bolt_automatic" + +ATT.SortOrder = 1 + +ATT.Mult_RPM = 1.15 +ATT.Mult_RecoilKick = 1.25 +ATT.Mult_RecoilSpreadPenalty = 1.1 + +TacRP.LoadAtt(ATT, "bolt_light") +-- #endregion + +------------------------------ +-- #region bolt_rough +------------------------------ +ATT = {} + +ATT.PrintName = "Rugged" +ATT.FullName = "Rugged Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_rough.png", "mips smooth") +ATT.Description = "A durable bolt suitable for long bursts." +ATT.Pros = {"stat.recoilpershot"} +ATT.Cons = {"stat.recoildissipation"} + +ATT.Category = "bolt_automatic" + +ATT.SortOrder = 4 + +ATT.Mult_RecoilDissipationRate = 0.75 +-- ATT.Mult_RecoilSpreadPenalty = 0.825 +ATT.Mult_RecoilPerShot = 0.9 + +TacRP.LoadAtt(ATT, "bolt_rough") +-- #endregion + +------------------------------ +-- #region bolt_surplus +------------------------------ +ATT = {} + +ATT.PrintName = "Surplus" +ATT.FullName = "Surplus Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_surplus.png", "mips smooth") +ATT.Description = "Rust has eaten most of it away, but it still kinda works." +ATT.Pros = {"att.procon.surplusboost1", "stat.recoil"} +ATT.Cons = {"att.procon.surplusboost2", "att.procon.unreliable"} + +ATT.Category = {"bolt_automatic", "bolt_manual"} + +ATT.SortOrder = 9 + +ATT.Mult_RecoilSpreadPenalty = 0.8 +ATT.Mult_RecoilKick = 0.75 + +ATT.Add_JamFactor = 0.4 +-- ATT.Mult_ShootChance = 0.96 + +ATT.Hook_PostShoot = function(wep) + if CLIENT then return end + if (wep.TacRP_NextSurplusBoost or 0) < CurTime() and math.random() <= 0.5 then + wep:SetNWFloat("TacRP_SurplusBoost", CurTime() + math.Rand(0.15, 0.4)) + wep.TacRP_NextSurplusBoost = CurTime() + math.Rand(0.5, 2) + end +end + +ATT.Hook_PostJam = function(wep) + wep:SetNWFloat("TacRP_SurplusBoost", 0) +end + +ATT.Hook_PostThink = function(wep) + if wep:GetCurrentFiremode() != 1 and wep:GetNWFloat("TacRP_SurplusBoost", 0) >= CurTime() then + wep:PrimaryAttack() + end +end + +hook.Add("TacRP_Stat_RPM", "bolt_surplus", function(wep, data) + if wep:GetNWFloat("TacRP_SurplusBoost", 0) >= CurTime() then + data.mul = data.mul * 1.15 + end +end) + +hook.Add("TacRP_Stat_Pitch_Shoot", "bolt_surplus", function(wep, data) + if wep:GetNWFloat("TacRP_SurplusBoost", 0) >= CurTime() then + data.add = data.add + 7.5 + end +end) + +TacRP.LoadAtt(ATT, "bolt_surplus") +-- #endregion + +------------------------------ +-- #region bolt_tactical +------------------------------ +ATT = {} + +ATT.PrintName = "Tactical" +ATT.FullName = "Tactical Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_tactical.png", "mips smooth") +ATT.Description = "Heavier bolt trades cycling speed for superb control of the weapon." +ATT.Pros = {"stat.recoil", "stat.quickscope", "rating.mobility", "stat.muzzlevelocity"} +ATT.Cons = {"stat.rpm"} + +ATT.Category = "bolt_manual" + +ATT.SortOrder = 2 + +ATT.Mult_RPM = 0.85 +ATT.Mult_ShootTimeMult = 1 / 0.85 + +ATT.Add_ShootingSpeedMult = 0.2 +ATT.Add_SightedSpeedMult = 0.08 +ATT.Mult_RecoilKick = 0.4 +ATT.Mult_RecoilVisualKick = 0.5 + +ATT.Mult_MuzzleVelocity = 1.15 +ATT.Mult_QuickScopeSpreadPenalty = 0.5 +ATT.Mult_QuickScopeTime = 0.5 + + +TacRP.LoadAtt(ATT, "bolt_tactical") +-- #endregion + +------------------------------ +-- #region bolt_refurbished +------------------------------ +ATT = {} + +ATT.PrintName = "Refurbished" +ATT.FullName = "Refurbished Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_refurbished.png", "mips smooth") +ATT.Description = "Fix the gun's reliability problems with some armory tweaks." +ATT.Pros = {"att.procon.reliability"} +ATT.Cons = {"stat.rpm", "stat.spread"} + +ATT.Category = "bolt_jammable" + +ATT.SortOrder = 10 + +ATT.Override_JamFactor = 0 -- This intentionally does not prevent surplus ammo from jamming! +ATT.Add_RPM = -50 +ATT.Add_Spread = 0.003 + +ATT.Compatibility = function(wep) + if wep:GetBaseValue("JamFactor") == 0 then + return false + end +end + +TacRP.LoadAtt(ATT, "bolt_refurbished") +-- #endregion + +------------------------------ +-- #region trigger_akimbo +------------------------------ +ATT = {} + +ATT.PrintName = "Akimbo" +ATT.FullName = "Akimbo Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_akimbo.png", "mips smooth") +ATT.Description = "Let'em have it!" +ATT.Pros = {"att.procon.auto", "stat.recoilkick"} +ATT.Cons = {"stat.rpm"} + +ATT.Free = true +ATT.Ignore = true + +ATT.Category = "trigger_akimbo" + +ATT.SortOrder = 0.5 + +ATT.Override_Firemodes = {2} +ATT.Mult_RPM = 0.9 +ATT.Mult_RecoilKick = 0.75 + +TacRP.LoadAtt(ATT, "trigger_akimbo") +-- #endregion + +------------------------------ +-- #region trigger_burst +------------------------------ +ATT = {} + +ATT.PrintName = "Burst" +ATT.FullName = "Burst Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_burst.png", "mips smooth") +ATT.Description = "Trigger that sacrfices automatic fire for stability." +ATT.Pros = {"stat.rpm", "rating.control"} +ATT.Cons = {"att.procon.burst"} + +ATT.Category = {"trigger_auto"} + +ATT.SortOrder = 1.1 + +ATT.Add_PostBurstDelay = 0.15 +ATT.Add_RPMMultBurst = 0.25 +ATT.Override_Firemodes = {-3, 1} +ATT.Override_RunawayBurst = true + +ATT.Mult_RecoilSpreadPenalty = 0.75 +ATT.Mult_RecoilVisualKick = 0.85 +ATT.Mult_RecoilKick = 0.75 + +ATT.Mult_RecoilStability = 1.25 + +TacRP.LoadAtt(ATT, "trigger_burst") +-- #endregion + +------------------------------ +-- #region trigger_burst2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.trigger_burst.name" +ATT.FullName = "att.trigger_burst.name.full" +ATT.Icon = Material("entities/tacrp_att_trigger_burst.png", "mips smooth") +ATT.Description = "Trigger that emulates burst fire." +ATT.Pros = {"att.procon.burst"} +ATT.Cons = {"stat.recoilkick", "stat.recoilstability"} + +ATT.InvAtt = "trigger_burst" + +ATT.Category = {"trigger_semi"} + +ATT.SortOrder = 1.1 + +ATT.Override_Firemodes = {-3, 1} +ATT.Add_RPMMultBurst = 0.3 +ATT.Override_RunawayBurst = true +ATT.Add_PostBurstDelay = 0.22 + +ATT.Mult_RecoilKick = 1.25 +ATT.Mult_RecoilStability = 0.75 + +TacRP.LoadAtt(ATT, "trigger_burst2") +-- #endregion + +------------------------------ +-- #region trigger_burstauto +------------------------------ +ATT = {} + +ATT.PrintName = "Auto-Burst" +ATT.FullName = "Auto-Burst Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_burstauto.png", "mips smooth") +ATT.Description = "Trigger that allows continuous burst fire while held." +ATT.Pros = {"att.procon.autoburst"} +ATT.Cons = {} + +ATT.Category = {"trigger_burst", "trigger_burstauto", "trigger_4pos"} + +ATT.SortOrder = 4 + +ATT.AutoBurst = true +-- ATT.Add_PostBurstDelay = 0.025 +-- ATT.Add_RecoilResetTime = 0.03 + +TacRP.LoadAtt(ATT, "trigger_burstauto") +-- #endregion + +------------------------------ +-- #region trigger_comp +------------------------------ +ATT = {} + +ATT.PrintName = "Competition" +ATT.FullName = "Competition Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_comp.png", "mips smooth") +ATT.Description = "Lightweight trigger for sports shooting." +ATT.Pros = {"stat.recoilfirstshot", "stat.recoilstability"} +ATT.Cons = {"stat.recoilmaximum"} + +ATT.Category = {"trigger_semi", "trigger_auto", "trigger_burst", "trigger_akimbo", "trigger_revolver", "trigger", "trigger_4pos"} + +ATT.SortOrder = 2 + +ATT.Mult_RecoilFirstShotMult = 0.75 +ATT.Mult_RecoilMaximum = 1.5 +ATT.Add_RecoilStability = 0.1 + +TacRP.LoadAtt(ATT, "trigger_comp") +-- #endregion + +------------------------------ +-- #region trigger_comp2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.trigger_comp.name" +ATT.FullName = "att.trigger_comp.name.full" +ATT.Icon = Material("entities/tacrp_att_trigger_comp.png", "mips smooth") +ATT.Description = "Lightweight trigger that recovers from accuracy faster." +ATT.Pros = {"stat.recoildissipation", "stat.recoilstability"} +ATT.Cons = {"stat.shootingspeed"} + +ATT.InvAtt = "trigger_comp" + +ATT.Category = {"trigger_manual"} + +ATT.SortOrder = 2 + +ATT.Mult_RecoilDissipationRate = 1.25 +ATT.Mult_ShootingSpeedMult = 0.75 +ATT.Add_RecoilStability = 0.15 + +TacRP.LoadAtt(ATT, "trigger_comp2") +-- #endregion + +------------------------------ +-- #region trigger_frcd +------------------------------ +ATT = {} + +ATT.PrintName = "Forced Reset" +ATT.FullName = "Forced Reset Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_frcd.png", "mips smooth") +ATT.Description = "Trigger that emulates automatic fire but with poor performance." +ATT.Pros = {"att.procon.auto"} +ATT.Cons = {"stat.recoilkick", "stat.bloomintensity", "stat.recoilstability"} + +ATT.Category = "trigger_semi" + +ATT.SortOrder = 1 + +ATT.Override_Firemodes = {2} +ATT.Override_Firemode = 2 +ATT.Mult_RecoilKick = 1.25 +ATT.Mult_RecoilSpreadPenalty = 1.15 +ATT.Mult_RecoilStability = 0.5 + +TacRP.LoadAtt(ATT, "trigger_frcd") +-- #endregion + +------------------------------ +-- #region trigger_frcd2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.trigger_frcd.name" +ATT.FullName = "att.trigger_frcd.name.full" +ATT.Icon = Material("entities/tacrp_att_trigger_frcd.png", "mips smooth") +ATT.Description = "att.trigger_frcd.desc" +ATT.Pros = {"att.procon.auto"} +ATT.Cons = {"stat.recoilkick", "stat.bloomintensity", "stat.recoilstability"} + +ATT.InvAtt = "trigger_frcd" + +ATT.Category = "trigger_burst" + +ATT.SortOrder = 1 + +ATT.Override_Firemodes = {2} +ATT.Override_Firemode = 2 +ATT.Mult_RecoilKick = 1.25 +ATT.Mult_RecoilSpreadPenalty = 1.15 +ATT.Mult_RecoilStability = 0.75 + +TacRP.LoadAtt(ATT, "trigger_frcd2") +-- #endregion + +------------------------------ +-- #region trigger_hair +------------------------------ +ATT = {} + +ATT.PrintName = "Feather" +ATT.FullName = "Feather Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_hair.png", "mips smooth") +ATT.Description = "Very sensitive trigger for rapid semi-automatic fire." +ATT.Pros = {"stat.rpm"} +ATT.Cons = {"stat.recoilmaximum", "stat.recoilstability", "stat.hipfirespread"} + +ATT.Category = {"trigger_semi", "trigger_revolver"} + +ATT.SortOrder = 4 + +ATT.Mult_RPMMultSemi = 1.2 +ATT.Mult_RecoilMaximum = 1.25 +ATT.Mult_RecoilStability = 0.5 +ATT.Mult_HipFireSpreadPenalty = 1.15 + +TacRP.LoadAtt(ATT, "trigger_hair") +-- #endregion + +------------------------------ +-- #region trigger_hair_akimbo +------------------------------ +ATT = {} + +ATT.PrintName = "att.trigger_hair.name" +ATT.FullName = "att.trigger_hair.name.full" +ATT.Icon = Material("entities/tacrp_att_trigger_hair.png", "mips smooth") +ATT.Description = "Very sensitive trigger for rapid akimbo fire." +ATT.Pros = {"stat.rpm", "stat.postburstdelay"} +ATT.Cons = {"stat.recoilmaximum", "stat.recoil"} + +ATT.Category = {"trigger_akimbo"} +ATT.InvATt = "trigger_hair" + +ATT.SortOrder = 4 + +ATT.Mult_PostBurstDelay = 0.5 + +ATT.Mult_RPMMultSemi = 1.2 +ATT.Mult_RPMMultBurst = 1.2 +ATT.Mult_RecoilMaximum = 1.25 +ATT.Mult_RecoilStability = 0.5 +ATT.Mult_RecoilKick = 1.2 + +TacRP.LoadAtt(ATT, "trigger_hair_akimbo") + +------------------------------ +-- #region trigger_heavy +------------------------------ +ATT = {} + +ATT.PrintName = "Weighted" +ATT.FullName = "Weighted Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_heavy.png", "mips smooth") +ATT.Description = "Heavy trigger for sustained fire." +ATT.Pros = {"stat.recoilmaximum"} +ATT.Cons = {"stat.recoilfirstshot", "stat.recoilstability"} + +ATT.Category = {"trigger_semi", "trigger_auto", "trigger_burst", "trigger_akimbo", "trigger_revolver", "trigger", "trigger_4pos"} + +ATT.SortOrder = 3 + +ATT.Mult_RecoilFirstShotMult = 1.5 +ATT.Mult_RecoilMaximum = 0.75 +ATT.Add_RecoilStability = -0.1 + +TacRP.LoadAtt(ATT, "trigger_heavy") +-- #endregion + +------------------------------ +-- #region trigger_heavy2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.trigger_heavy.name" +ATT.FullName = "att.trigger_heavy.name.full" +ATT.Icon = Material("entities/tacrp_att_trigger_heavy.png", "mips smooth") +ATT.Description = "Heavy trigger that reduces mobility impact from shooting." +ATT.Pros = {"stat.shootingspeed"} +ATT.Cons = {"stat.recoildissipation", "stat.recoilstability"} + +ATT.InvAtt = "trigger_heavy" + +ATT.Category = {"trigger_manual"} + +ATT.SortOrder = 3 + +ATT.Mult_RecoilDissipationRate = 0.85 +ATT.Mult_ShootingSpeedMult = 1.25 +ATT.Add_RecoilStability = -0.15 + +TacRP.LoadAtt(ATT, "trigger_heavy2") +-- #endregion + +------------------------------ +-- #region trigger_semi +------------------------------ +ATT = {} + +ATT.PrintName = "Marksman" +ATT.FullName = "Marksman Trigger" + +ATT.Icon = Material("entities/tacrp_att_trigger_semi.png", "mips smooth") +ATT.Description = "Trigger that sacrfices automatic fire for precision." +ATT.Pros = {"stat.spread", "stat.recoil", "stat.rpm", "att.procon.reliability"} +ATT.Cons = {"att.procon.semi"} + +ATT.Category = {"trigger_auto", "trigger_burst", "trigger_4pos"} + +ATT.SortOrder = 1 + +ATT.Override_Firemodes = {1} +ATT.Mult_Spread = 0.3333 +ATT.Mult_ShotgunPelletSpread = 0.5 +ATT.Mult_RecoilSpreadPenalty = 0.75 +ATT.Mult_RecoilKick = 0.6 +ATT.Mult_RecoilVisualKick = 0.5 +ATT.Mult_RecoilStability = 1.25 +ATT.Add_RPM = 50 +ATT.Mult_JamFactor = 0 + +TacRP.LoadAtt(ATT, "trigger_semi") +-- #endregion + +------------------------------ +-- #region trigger_slam +------------------------------ +ATT = {} + +ATT.PrintName = "Slamfire" +ATT.FullName = "Slamfire Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_frcd.png", "mips smooth") +ATT.Description = "att.trigger_frcd.desc" +ATT.Pros = {"stat.rpm", "att.procon.auto"} +ATT.Cons = {"stat.spread", "rating.mobility"} + +ATT.Category = "trigger_pump" + +ATT.SortOrder = 1 + +ATT.Override_Firemodes = {2} +ATT.Mult_RecoilKick = 1.25 +ATT.Mult_RecoilSpreadPenalty = 1.25 +ATT.Add_RecoilMaximum = 0.5 + +ATT.Mult_RPM = 1.15 +ATT.Mult_ShootTimeMult = 1.25 +ATT.Mult_ShootingSpeedMult = 0.75 + +TacRP.LoadAtt(ATT, "trigger_slam") +-- #endregion + +------------------------------ +-- #region trigger_slam2 +------------------------------ +ATT = {} + +ATT.PrintName = "att.trigger_slam.name" +ATT.FullName = "att.trigger_slam.name.full" +ATT.Icon = Material("entities/tacrp_att_trigger_frcd.png", "mips smooth") +ATT.Description = "att.trigger_frcd.desc" +ATT.Pros = {"stat.rpm", "att.procon.auto"} +ATT.Cons = {"stat.spread", "rating.mobility"} + +ATT.Category = "trigger_pump2" +ATT.InvAtt = "trigger_slam" + +ATT.SortOrder = 1 + +ATT.Override_Firemodes = {2} +ATT.Mult_RecoilKick = 1.25 +ATT.Mult_RecoilSpreadPenalty = 1.25 +ATT.Add_RecoilMaximum = 0.5 + +ATT.Mult_RPM = 1.15 +ATT.Mult_ShootTimeMult = 1.4 +ATT.Mult_ShootingSpeedMult = 0.75 + +TacRP.LoadAtt(ATT, "trigger_slam2") +-- #endregion + +------------------------------ +-- #region trigger_straight +------------------------------ +ATT = {} + +ATT.PrintName = "Straight" +ATT.FullName = "Straight Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_straight.png", "mips smooth") +ATT.Description = "Narrow trigger with superior recoil performance." +ATT.Pros = {"stat.bloomintensity", "stat.recoildissipation"} +ATT.Cons = {"stat.recoilresettime", "stat.shootingspeed"} + +ATT.Category = {"trigger_auto", "trigger_straight", "trigger_4pos"} + +ATT.SortOrder = 5.5 + +ATT.Mult_RecoilDissipationRate = 1.15 +ATT.Mult_RecoilSpreadPenalty = 0.85 + +ATT.Add_RecoilResetTime = 0.075 +ATT.Add_ShootingSpeedMult = -0.08 + +TacRP.LoadAtt(ATT, "trigger_straight") +-- #endregion + +------------------------------ +-- #region trigger_wide +------------------------------ +ATT = {} + +ATT.PrintName = "Wide" +ATT.FullName = "Wide Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_wide.png", "mips smooth") +ATT.Description = "Large trigger assembly, easy to hold even in awkward positions." +ATT.Pros = {"stat.quickscope", "stat.peekpenalty", "stat.freeaimangle"} +ATT.Cons = {"stat.aimdownsights"} + +ATT.Category = {"trigger_revolver", "trigger_manual"} + +ATT.SortOrder = 5 + +ATT.Mult_PeekPenaltyFraction = 0.75 +ATT.Mult_QuickScopeSpreadPenalty = 0.75 +ATT.Mult_FreeAimMaxAngle = 0.85 +-- ATT.Mult_HipFireSpreadPenalty = 0.75 + +ATT.Add_AimDownSightsTime = 0.03 + +TacRP.LoadAtt(ATT, "trigger_wide") +-- #endregion + +------------------------------ +-- #region trigger_dualstage +------------------------------ +ATT = {} + +ATT.PrintName = "D. Stage" +ATT.FullName = "Dual Stage Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_dualstage.png", "mips smooth") +ATT.Description = "Trigger that reduces firerate while aiming for better control and accuracy." +ATT.Pros = {"att.procon.aimrecoil", "att.procon.aimspread"} +ATT.Cons = {"att.procon.aimrpm"} + +ATT.SortOrder = 10 + +ATT.Category = {"trigger_auto", "trigger_semi", "trigger_burst", "trigger_4pos"} + +ATT.Func_RPM = function(wep, data) + if wep:GetScopeLevel() > 0 and not wep:GetPeeking() then + data.mul = data.mul * 0.7 + end +end + +ATT.Func_RecoilKick = function(wep, data) + if wep:GetScopeLevel() > 0 and not wep:GetPeeking() then + data.mul = data.mul * 0.5 + end +end + +ATT.Func_RecoilPerShot = function(wep, data) + if wep:GetScopeLevel() > 0 and not wep:GetPeeking() then + data.mul = data.mul * 0.5 + end +end + +ATT.Func_Spread = function(wep, data) + if wep:GetScopeLevel() > 0 and not wep:GetPeeking() then + data.mul = data.mul * 0.5 + end +end + +TacRP.LoadAtt(ATT, "trigger_dualstage") +-- #endregion + + +------------------------------ +-- #region trigger_tactical +------------------------------ +ATT = {} + +ATT.PrintName = "Tactical" +ATT.FullName = "Tactical Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_burstauto.png", "mips smooth") +ATT.Description = "Trigger reducing recoil for burst and semi fire modes." +ATT.Pros = {"stat.recoilburst", "stat.recoilsemi"} +ATT.Cons = {"stat.postburstdelay"} + +ATT.Category = {"trigger_auto", "trigger_burstauto", "trigger_4pos"} + +ATT.SortOrder = 11 + +ATT.Add_RecoilMultBurst = -0.15 +ATT.Add_RecoilMultSemi = -0.2 +ATT.Add_PostBurstDelay = 0.02 + +TacRP.LoadAtt(ATT, "trigger_tactical") +-- #endregion diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/interops.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/interops.lua new file mode 100644 index 0000000..e758b26 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/interops.lua @@ -0,0 +1,476 @@ +-- interops.lua + +local ATT = {} + +------------------------------ +-- ammo_star15_300blk +------------------------------ +ATT = {} + +ATT.PrintName = ".300 BLK" +ATT.FullName = "ST AR-15 .300 AAC Blackout Mod Kit" +ATT.Icon = Material("entities/tacrp_att_ammo_star15_300blk.png", "mips smooth") +ATT.Description = "Modification to load lower velocity bullets with better CQB potential." +ATT.Pros = {"stat.damage_max"} +ATT.Cons = {"rating.range", "stat.muzzlevelocity", "att.procon.armor"} + +ATT.Category = "ammo_star15" +ATT.SortOrder = 0 + +ATT.InstalledElements = {"300blk"} + +ATT.Mult_Damage_Max = 1.2 +ATT.Mult_Range_Min = 0.5 +ATT.Mult_Range_Max = 0.75 +ATT.Mult_MuzzleVelocity = 0.8 +ATT.Add_Spread = 0.0007 +ATT.Mult_RecoilSpreadPenalty = 1.1 + +ATT.Mult_Penetration = 0.75 +ATT.Add_ArmorPenetration = -0.15 +ATT.Mult_ArmorBonus = 0.5 + +-- forgive me for fudging the dist sounds +ATT.Override_Sound_Shoot = "^tacint_extras/star15/fire-03.ogg" +ATT.Override_Sound_Shoot_Silenced = "tacint_extras/star15/fire_300blk-supp.ogg" + +ATT.Override_DropMagazineModel = "models/weapons/tacint_extras/magazines/star15_pmag.mdl" +ATT.Override_DropMagazineImpact = "plastic" + +TacRP.LoadAtt(ATT, "ammo_star15_300blk") + +------------------------------ +-- ammo_star15_50beo +------------------------------ +ATT = {} + +ATT.PrintName = ".50 BEO" +ATT.FullName = "ST AR-15 .50 Beowulf Mod Kit" +ATT.Icon = Material("entities/tacrp_att_ammo_star15_50beo.png", "mips smooth") +ATT.Description = "Modification to load low capacity, high power magnum rounds." +ATT.Pros = {"rating.lethality"} +ATT.Cons = {"rating.range", "rating.control", "att.procon.armor"} + +ATT.Category = "ammo_star15" +ATT.SortOrder = 0.5 + +ATT.InstalledElements = {"50beo"} + +ATT.Mult_Damage_Max = 2 +ATT.Mult_Damage_Min = 1.5 +ATT.Mult_Range_Min = 0.75 +ATT.Mult_Range_Max = 0.5 + +ATT.Mult_Penetration = 0.25 +ATT.Add_ArmorPenetration = -0.1 +ATT.Mult_ArmorBonus = 0.75 + +ATT.Mult_RPM = 0.6 +ATT.Mult_ClipSize = 1 / 2.4 + +ATT.Add_RecoilKick = 4 +ATT.Mult_RecoilSpreadPenalty = 1.75 +ATT.Add_RecoilResetTime = 0.075 +ATT.Mult_MuzzleVelocity = 0.6 +ATT.Add_RecoilPerShot = 0.6 + +ATT.Mult_ShootTimeMult = 1.5 + +ATT.Override_Sound_Shoot = "^tacint_extras/star15/fire_beo.ogg" +ATT.Override_Sound_Shoot_Silenced = "tacint_extras/star15/fire_beo-supp.ogg" + +TacRP.LoadAtt(ATT, "ammo_star15_50beo") + +------------------------------ +-- bolt_af2011_alt +------------------------------ +ATT = {} + +ATT.PrintName = "Alternating" +ATT.FullName = "AF2011-A1 Alternating Bolt" +ATT.Icon = Material("entities/tacrp_att_bolt_heavy.png", "mips smooth") +ATT.Description = "Malicious interpretation of the concept of \"double-stacked magazines\"." +ATT.Pros = {"stat.spread", "rating.control", "stat.rpm"} +ATT.Cons = {"att.procon.onebullet", "stat.recoilmaximum"} + +ATT.Category = "bolt_af2011" + +ATT.SortOrder = 0 + +-- ATT.Override_Firemodes = {1, -2} +-- ATT.Override_Firemodes_Priority = 0.5 +-- ATT.Override_RunawayBurst = true +-- ATT.Override_RunawayBurst_Priority = 0.5 +-- ATT.Override_PostBurstDelay = 0.1 +-- ATT.Override_PostBurstDelay_Priority = 0.5 + +-- ATT.Func_RPMMultBurst = function(wep, modifiers) +-- if wep:GetFiremodeAmount() == 2 and wep:GetValue("Firemodes")[2] == -2 then +-- modifiers.set = 1.5 +-- modifiers.prio = 10 +-- end +-- end + +ATT.Override_AmmoPerShot = 1 +ATT.Override_Num = 1 + +ATT.Mult_RPM = 1.75 + +ATT.Add_RecoilStability = 0.3 +ATT.Mult_RecoilKick = 0.4 +ATT.Mult_RecoilSpreadPenalty = 0.7 +ATT.Mult_Spread = 0.75 + +ATT.Add_RecoilMaximum = 2 + +ATT.Override_Sound_Shoot = "^tacint_shark/af2011/af2011a0-1.wav" +ATT.Add_Pitch_Shoot = 0 +ATT.Override_EffectsDoubled = false + +TacRP.LoadAtt(ATT, "bolt_af2011_alt") + +------------------------------ +-- muzz_comp_io_m14 +------------------------------ +ATT = {} + +ATT.PrintName = "att.muzz_pistol_comp.name" +ATT.Icon = Material("entities/tacrp_att_muzz_pistol_comp.png", "mips smooth") +ATT.Description = "att.muzz_comp_io_m14.desc" +ATT.Pros = {"stat.recoil", "stat.muzzlevelocity"} +ATT.Cons = {"stat.rpm"} + +ATT.Category = "comp_m14" +ATT.InvAtt = "muzz_pistol_comp" +ATT.SortOrder = 1 + +ATT.Mult_RecoilKick = 0.5 +ATT.Mult_RecoilSpreadPenalty = 0.8 +ATT.Mult_RPM = 450 / 600 +ATT.Mult_MuzzleVelocity = 1.1 + +ATT.InstalledElements = {"muzzle"} + +TacRP.LoadAtt(ATT, "muzz_comp_io_m14") + +------------------------------ +-- muzz_tec9_shroud +------------------------------ +ATT = {} + +ATT.PrintName = "Shroud" +ATT.FullName = "TEC-9 Barrel Shroud" +ATT.Icon = Material("entities/tacrp_att_muzz_tec9_shroud.png", "mips smooth") +ATT.Description = "Barrel extension improving performance at range." +ATT.Pros = {"rating.precision", "rating.control", "rating.range"} +ATT.Cons = {"stat.rpm", "rating.maneuvering"} + +ATT.Category = "muzz_tec9" + +ATT.Mult_RPM = 750 / 1000 +ATT.Mult_Spread = 0.6 +ATT.Mult_RecoilSpreadPenalty = 0.7 +ATT.Mult_Range_Max = 1.5 +ATT.Mult_Range_Min = 1.5 +ATT.Mult_RecoilKick = 0.85 +ATT.Mult_RecoilVisualKick = 0.75 +ATT.Add_HipFireSpreadPenalty = 0.0075 +ATT.Add_FreeAimMaxAngle = 0.5 + +ATT.InstalledElements = {"shroud"} +ATT.Override_QCA_Muzzle = 3 +ATT.Add_Pitch_Shoot = -5 + +TacRP.LoadAtt(ATT, "muzz_tec9_shroud") + +------------------------------ +-- optic_ak_pso1 +------------------------------ +ATT = {} + +ATT.PrintName = "att.optic_ak_pso1.name" +ATT.FullName = "att.optic_ak_pso1.name.full" +ATT.Icon = Material("entities/tacrp_att_optic_pso1.png", "mips smooth") +ATT.Description = "att.optic_ak_pso1.desc" +ATT.Pros = {"att.zoom.6"} + +ATT.Category = "optic_ak" + +ATT.SortOrder = 6 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/pso1.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 6 +ATT.Override_ScopeOverlaySize = 0.9 +ATT.Override_ScopeHideWeapon = true + +ATT.SightPos = Vector(0, -17, 1.75) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Model = "models/weapons/tacint_extras/addons/pso1.mdl" +ATT.Scale = 1.25 +ATT.ModelOffset = Vector(-1.5, 0, -0.75) + +ATT.InstalledElements = {"akmount"} + +TacRP.LoadAtt(ATT, "optic_ak_pso1") + +------------------------------ +-- optic_ar_colt +------------------------------ +ATT = {} + +ATT.PrintName = "att.optic_ar_colt.name" +ATT.FullName = "att.optic_ar_colt.name.full" +ATT.Icon = Material("entities/tacrp_att_optic_m16a2_colt.png", "mips smooth") +ATT.Description = "att.optic_ar_colt.desc" +ATT.Pros = {"att.zoom.3"} + +ATT.Category = "optic_ar" + +ATT.SortOrder = 3 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/coltscope.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 3 +ATT.Override_ScopeOverlaySize = 0.85 + +ATT.Model = "models/weapons/tacint_extras/addons/coltscope.mdl" +ATT.Scale = 1 +ATT.ModelOffset = Vector(0, 0, 0) + +ATT.InstalledElements = {"chmount"} + +TacRP.LoadAtt(ATT, "optic_ar_colt") + +------------------------------ +-- optic_k98_zf41 +------------------------------ +ATT = {} + +ATT.PrintName = "Zeiss" +ATT.FullName = "Zeiss 6x36 Scope" --not actually, it's the scope from the trg-42 +ATT.Icon = Material("entities/tacrp_att_optic_m16a2_colt.png", "mips smooth") +ATT.Description = "Medium-power sniper scope made specially for the Kar98k." +ATT.Pros = {"att.zoom.6"} + +ATT.Category = "optic_kar98" + +ATT.SortOrder = 6 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/coltscope.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 6 +ATT.Override_ScopeOverlaySize = 0.85 + +ATT.Scale = 1 +ATT.ModelOffset = Vector(0, 0, 0) + +ATT.InstalledElements = {"scope"} + +TacRP.LoadAtt(ATT, "optic_k98_zf42") + +------------------------------ +-- optic_m16a2_colt +------------------------------ +ATT = {} + +ATT.PrintName = "att.optic_ar_colt.name" +ATT.FullName = "att.optic_ar_colt.name.full" +ATT.Icon = Material("entities/tacrp_att_optic_m16a2_colt.png", "mips smooth") +ATT.Description = "att.optic_ar_colt.desc" +ATT.Pros = {"att.zoom.3"} + +ATT.Category = "optic_m16a2" +ATT.InvAtt = "optic_ar_colt" + +ATT.SortOrder = 3 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/coltscope.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 3 +ATT.Override_ScopeOverlaySize = 0.85 + +ATT.InstalledElements = {"coltscope"} + +TacRP.LoadAtt(ATT, "optic_m16a2_colt") + +------------------------------ +-- optic_pso1 +------------------------------ +ATT = {} + +ATT.PrintName = "att.optic_ak_pso1.name" +ATT.FullName = "att.optic_ak_pso1.name.full" +ATT.Icon = Material("entities/tacrp_att_optic_pso1.png", "mips smooth") +ATT.Description = "att.optic_ak_pso1.desc" +ATT.Pros = {"att.zoom.6"} + +ATT.Category = "optic_pso1" +ATT.InvAtt = "optic_ak_pso1" + +ATT.SortOrder = 6 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/pso1.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 6 +ATT.Override_ScopeOverlaySize = 0.9 +ATT.Override_ScopeHideWeapon = true + +ATT.InstalledElements = {"pso1"} + +TacRP.LoadAtt(ATT, "optic_pso1") + +------------------------------ +-- optic_xm8_4x +------------------------------ +ATT = {} + +ATT.PrintName = "4x" +ATT.FullName = "XM8 Integrated Scope (4x)" +ATT.Icon = Material("entities/tacrp_att_optic_acog.png", "mips smooth") +ATT.Description = "Medium range zoom setting with ACOG reticle." +ATT.Pros = {"att.zoom.4"} + +ATT.Category = "optic_xm8" + +ATT.SortOrder = 4 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/acog.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 4 +ATT.Override_ScopeOverlaySize = 0.85 + +ATT.Free = true + +TacRP.LoadAtt(ATT, "optic_xm8_4x") + +------------------------------ +-- optic_xm8_6x +------------------------------ +ATT = {} + +ATT.PrintName = "6x" +ATT.FullName = "XM8 Integrated Scope (6x)" +ATT.Icon = Material("entities/tacrp_att_optic_acog.png", "mips smooth") +ATT.Description = "Medium-long range zoom setting with Short Dot reticle." +ATT.Pros = {"att.zoom.6"} + +ATT.Category = "optic_xm8" + +ATT.SortOrder = 6 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/shortdot.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 6 +ATT.Override_ScopeOverlaySize = 1 + +ATT.Free = true + +TacRP.LoadAtt(ATT, "optic_xm8_6x") + +------------------------------ +-- optic_xm8_8x +------------------------------ +ATT = {} + +ATT.PrintName = "8x" +ATT.FullName = "XM8 Integrated Scope (8x)" +ATT.Icon = Material("entities/tacrp_att_optic_acog.png", "mips smooth") +ATT.Description = "Long range zoom setting with sniper reticle." +ATT.Pros = {"att.zoom.8"} + +ATT.Category = "optic_xm8" + +ATT.SortOrder = 8 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/sniper.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 8 +ATT.Override_ScopeOverlaySize = 0.8 + +ATT.Free = true + +TacRP.LoadAtt(ATT, "optic_xm8_8x") + +------------------------------ +-- trigger_vp70_auto +------------------------------ +ATT = {} + +ATT.PrintName = "Automatic" +ATT.FullName = "VP-70 Auto Sear Stock" +ATT.Icon = Material("entities/tacrp_att_trigger_vp70_auto.png", "smooth") +ATT.Description = "The engineers at H&K are frothing at their mouths as we speak." +ATT.Pros = {"att.procon.auto"} +ATT.Cons = {"stat.rpm", "rating.control"} + +ATT.Free = false + +ATT.Category = "trigger_vp70" + +ATT.SortOrder = 0.5 + +ATT.Override_Firemodes = {2, 1} +-- ATT.Mult_RPM = 0.778 +-- ATT.Override_RPM = 1200 + +ATT.Add_RecoilVisualKick = 1 +ATT.Add_RecoilKick = 3 + +ATT.Mult_ShootingSpeedMult = 0.85 +ATT.Add_HipFireSpreadPenalty = 0.005 +ATT.Add_RecoilMaximum = 3 + +TacRP.LoadAtt(ATT, "trigger_vp70_auto") + +------------------------------ +-- trigger_vp70_semi +------------------------------ +ATT = {} + +ATT.PrintName = "Stockless" +ATT.FullName = "VP-70 Remove Stock" +ATT.Icon = Material("entities/tacrp_att_trigger_vp70_semi.png", "smooth") +ATT.Description = "Removes burst fire capability, improving handling and mobility." +ATT.Pros = {"stat.damage", "rating.handling", "rating.mobility"} +ATT.Cons = {"att.procon.semi", "rating.stability"} + +ATT.Free = true + +ATT.Category = "trigger_vp70" + +ATT.SortOrder = 0 + +ATT.Add_Damage_Max = 3 +ATT.Add_Damage_Min = 3 + +ATT.Mult_ReloadTimeMult = 0.9 + +ATT.Override_Firemodes = {1} +ATT.Mult_RPM = 0.667 +ATT.Mult_RPMMultSemi = 0.7 +-- ATT.Override_RPM = 600 + +ATT.Add_RecoilKick = 2 +ATT.Add_SprintToFireTime = -0.05 +ATT.Add_AimDownSightsTime = -0.02 +ATT.Mult_MoveSpeedMult = 1.1 +ATT.Add_SightedSpeedMult = 0.15 +ATT.Add_ShootingSpeedMult = 0.25 +ATT.Add_ReloadTimeMult = -0.15 +ATT.Add_HipFireSpreadPenalty = -0.005 + +ATT.Add_RecoilSpreadPenalty = -0.0035 + +ATT.Add_ScopedSway = 0.3 +ATT.Add_Sway = -0.15 +ATT.Add_FreeAimMaxAngle = -1.5 + +ATT.InstalledElements = {"foldstock"} + +TacRP.LoadAtt(ATT, "trigger_vp70_semi") + diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/melee_boost.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/melee_boost.lua new file mode 100644 index 0000000..26a5896 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/melee_boost.lua @@ -0,0 +1,136 @@ +local ATT = {} + +ATT = {} + +ATT.PrintName = "Level Up" +ATT.Icon = Material("entities/tacrp_att_melee_boost_all.png", "mips smooth") +ATT.Description = "Small boost to all attributes." +ATT.Pros = {"stat.meleeperkstr", "stat.meleeperkagi", "stat.meleeperkint"} + +ATT.Category = "melee_boost" + +ATT.SortOrder = 1 + +ATT.Add_MeleePerkStr = 0.03 +ATT.Add_MeleePerkAgi = 0.03 +ATT.Add_MeleePerkInt = 0.03 + +TacRP.LoadAtt(ATT, "melee_boost_all") + +ATT = {} +ATT.PrintName = "Bulk Up" +ATT.Icon = Material("entities/tacrp_att_melee_boost_str.png", "mips smooth") +ATT.Description = "Increase Brawn significantly at the cost of other attributes." +ATT.Pros = {"stat.meleeperkstr"} +ATT.Cons = {"stat.meleeperkagi", "stat.meleeperkint"} + +ATT.Category = "melee_boost" + +ATT.SortOrder = 2 + +ATT.Add_MeleePerkStr = 0.2 +ATT.Add_MeleePerkAgi = -0.05 +ATT.Add_MeleePerkInt = -0.05 + +TacRP.LoadAtt(ATT, "melee_boost_str") + +ATT = {} +ATT.PrintName = "Catch Up" +ATT.Icon = Material("entities/tacrp_att_melee_boost_agi.png", "mips smooth") +ATT.Description = "Increase Dexterity significantly at the cost of other attributes." +ATT.Pros = {"stat.meleeperkagi"} +ATT.Cons = {"stat.meleeperkstr", "stat.meleeperkint"} + +ATT.Category = "melee_boost" + +ATT.SortOrder = 3 + +ATT.Add_MeleePerkAgi = 0.2 +ATT.Add_MeleePerkStr = -0.05 +ATT.Add_MeleePerkInt = -0.05 + +TacRP.LoadAtt(ATT, "melee_boost_agi") + +ATT = {} +ATT.PrintName = "Wise Up" +ATT.Icon = Material("entities/tacrp_att_melee_boost_int.png", "mips smooth") +ATT.Description = "Increase Strategy significantly at the cost of other attributes." +ATT.Pros = {"stat.meleeperkint"} +ATT.Cons = {"stat.meleeperkstr", "stat.meleeperkagi"} + +ATT.Category = "melee_boost" + +ATT.SortOrder = 4 + +ATT.Add_MeleePerkInt = 0.2 +ATT.Add_MeleePerkStr = -0.05 +ATT.Add_MeleePerkAgi = -0.05 + +TacRP.LoadAtt(ATT, "melee_boost_int") + + +ATT = {} +ATT.PrintName = "Lifestealer" +ATT.Icon = Material("entities/tacrp_att_melee_boost_lifesteal.png", "mips smooth") +ATT.Description = "Restore health by dealing damage." +ATT.Pros = {"stat.lifesteal"} +ATT.Cons = {"stat.meleeperkstr", "stat.meleeperkagi"} + +ATT.Category = "melee_boost" + +ATT.SortOrder = 10 + +ATT.Add_Lifesteal = 0.3 +ATT.Add_MeleePerkStr = -0.05 +ATT.Add_MeleePerkAgi = -0.05 + +TacRP.LoadAtt(ATT, "melee_boost_lifesteal") + +ATT = {} +ATT.PrintName = "Momentum" +ATT.Icon = Material("entities/tacrp_att_melee_boost_momentum.png", "mips smooth") +ATT.Description = "Restore perk charge by dealing damage." +ATT.Pros = {"stat.damagecharge"} +ATT.Cons = {"stat.meleeperkint"} + +ATT.Category = "melee_boost" + +ATT.SortOrder = 11 + +ATT.Add_DamageCharge = 0.003 +ATT.Add_MeleePerkInt = -0.08 + +TacRP.LoadAtt(ATT, "melee_boost_momentum") + +ATT = {} +ATT.PrintName = "Afterimage" +ATT.Icon = Material("entities/tacrp_att_melee_boost_afterimage.png", "mips smooth") +ATT.Description = "Swing your weapon in a flash, landing the attack instantly." +ATT.Pros = {"stat.meleedelay"} +ATT.Cons = {"stat.meleeattackmisstime"} + +ATT.Category = "melee_boost" + +ATT.SortOrder = 12 + +ATT.Override_MeleeDelay = 0 +ATT.Mult_MeleeAttackMissTime = 1.15 + +TacRP.LoadAtt(ATT, "melee_boost_afterimage") + +ATT = {} +ATT.PrintName = "Shock Trooper" +ATT.FullName = "Shock Trooper" +ATT.Icon = Material("entities/tacrp_att_acc_shock.png", "mips smooth") +ATT.Description = "Reduce impact of impairing effects while weapon is held." +ATT.Pros = {"att.procon.gasimmune", "att.procon.flashresist", "att.procon.stunresist"} + +ATT.Category = "melee_boost" +ATT.InvAtt = "perk_shock" + +ATT.SortOrder = 12 + +ATT.GasImmunity = true +ATT.StunResist = true + +TacRP.LoadAtt(ATT, "melee_boost_shock") diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/muzz.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/muzz.lua new file mode 100644 index 0000000..a59d761 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/muzz.lua @@ -0,0 +1,336 @@ +-- muzz.lua + +local ATT = {} + +------------------------------ +-- #region muzz_comp_mac10 +------------------------------ +ATT = {} + +ATT.PrintName = "att.muzz_pistol_comp.name" +ATT.Icon = Material("entities/tacrp_att_muzz_pistol_comp.png", "mips smooth") +ATT.Description = "att.muzz_pistol_comp.desc" +ATT.Pros = {"stat.recoil", "stat.spread", "stat.range"} +ATT.Cons = {"stat.rpm"} + +ATT.Category = "comp_mac10" +ATT.InvAtt = "muzz_pistol_comp" +ATT.SortOrder = 1 + +ATT.Mult_RecoilKick = 0.75 +ATT.Mult_RPM = 0.8 +ATT.Mult_RecoilSpreadPenalty = 0.65 +ATT.Mult_Spread = 0.5 +ATT.Mult_Range_Max = 1.5 +ATT.Mult_Range_Min = 1.5 + +ATT.InstalledElements = {"pistol_comp"} + +TacRP.LoadAtt(ATT, "muzz_comp_mac10") +-- #endregion + +------------------------------ +-- #region muzz_comp_usp +------------------------------ +ATT = {} + +ATT.PrintName = "att.muzz_pistol_comp.name" +ATT.Icon = Material("entities/tacrp_att_muzz_pistol_comp.png", "mips smooth") +ATT.Description = "att.muzz_pistol_comp.desc" +ATT.Pros = {"stat.recoil", "stat.spread", "stat.range_min"} +ATT.Cons = {"stat.rpm"} + +ATT.Category = "comp_usp" +ATT.InvAtt = "muzz_pistol_comp" +ATT.SortOrder = 1 + +ATT.Mult_RecoilKick = 0.5 +ATT.Mult_RPM = 0.9 +ATT.Mult_Spread = 0.75 +ATT.Mult_Range_Min = 1.5 + +ATT.InstalledElements = {"pistol_comp"} + +TacRP.LoadAtt(ATT, "muzz_comp_usp") +-- #endregion + +------------------------------ +-- #region muzz_hbar +------------------------------ +ATT = {} + +ATT.PrintName = "Heavy Barrel" +ATT.Icon = Material("entities/tacrp_att_muzz_hbar.png", "mips smooth") +ATT.Description = "Sturdy barrel with improved sway and recoil performance." +ATT.Pros = {"stat.scopedsway", "stat.recoil", "stat.range_min"} +ATT.Cons = {"stat.spread", "stat.range_max"} + +ATT.Category = {"silencer", "barrel"} + +ATT.SortOrder = 1 + +ATT.Add_Spread = 0.004 +ATT.Mult_Spread = 1.25 +ATT.Mult_Range_Min = 1.25 +ATT.Mult_Range_Max = 0.66667 +ATT.Mult_RecoilKick = 0.75 +ATT.Mult_ScopedSway = 0.5 +ATT.Mult_RecoilSpreadPenalty = 1.1 + +TacRP.LoadAtt(ATT, "muzz_hbar") +-- #endregion + +------------------------------ +-- #region muzz_lbar +------------------------------ +ATT = {} + +ATT.PrintName = "Light Barrel" +ATT.Icon = Material("entities/tacrp_att_muzz_lbar.png", "mips smooth") +ATT.Description = "Lightweight barrel more accurate and effective at long range." +ATT.Pros = {"stat.spread", "stat.range_max"} +ATT.Cons = {"stat.scopedsway", "stat.recoil", "stat.range_min"} + +ATT.Category = {"silencer", "barrel"} + +ATT.SortOrder = 1 + +ATT.Mult_Spread = 0.5 +ATT.Mult_Range_Min = 0.66667 +ATT.Mult_Range_Max = 1.25 +-- ATT.Mult_MuzzleVelocity = 1.25 +ATT.Mult_RecoilKick = 1.25 +ATT.Add_ScopedSway = 0.1 + +TacRP.LoadAtt(ATT, "muzz_lbar") +-- #endregion + +------------------------------ +-- #region muzz_pistol_comp +------------------------------ +ATT = {} + +ATT.PrintName = "Compensator" +ATT.Icon = Material("entities/tacrp_att_muzz_pistol_comp.png", "mips smooth") +ATT.Description = "Muzzle device which reduces recoil impact." +ATT.Pros = {"stat.recoil", "stat.spread", "stat.range_min"} +ATT.Cons = {"stat.rpm"} + +ATT.Model = "models/weapons/tacint/addons/pistol_comp.mdl" +ATT.Scale = 2 +ATT.ModelOffset = Vector(-17.75, 0, -3.5) + +ATT.Category = "pistol_muzzle" + +ATT.SortOrder = 1 + +ATT.Mult_RecoilKick = 0.5 +ATT.Mult_RPM = 0.9 +ATT.Mult_Spread = 0.75 +ATT.Mult_Range_Min = 1.5 + +TacRP.LoadAtt(ATT, "muzz_pistol_comp") +-- #endregion + +------------------------------ +-- #region muzz_silencer +------------------------------ +ATT = {} + +ATT.PrintName = "T. Suppressor" +ATT.FullName = "Tactical Suppressor" +ATT.Icon = Material("entities/tacrp_att_muzz_silencer.png", "mips smooth") +ATT.Description = "Balanced suppressor that reduces recoil and effective range." +ATT.Pros = {"stat.vol_shoot", "stat.recoil"} +ATT.Cons = {"stat.range", "stat.muzzlevelocity"} + +ATT.Model = "models/weapons/tacint/addons/silencer.mdl" +ATT.Scale = 0.35 + +ATT.Category = "silencer" + +ATT.SortOrder = 4 + +ATT.Add_Vol_Shoot = -25 +ATT.Mult_RecoilKick = 0.9 +ATT.Mult_RecoilSpreadPenalty = 0.95 +ATT.Mult_Range_Max = 0.8 +ATT.Mult_Range_Min = 0.8 +ATT.Mult_MuzzleVelocity = 0.85 + +ATT.Silencer = true +ATT.Override_MuzzleEffect = "muzzleflash_suppressed" + +TacRP.LoadAtt(ATT, "muzz_silencer") +-- #endregion + +------------------------------ +-- #region muzz_supp_compact +------------------------------ +ATT = {} + +ATT.PrintName = "C. Suppressor" +ATT.FullName = "Compact Suppressor" +ATT.Icon = Material("entities/tacrp_att_muzz_supp_compact.png", "mips smooth") +ATT.Description = "Short suppressor improving accuracy with low impact to effective range." +ATT.Pros = {"stat.vol_shoot", "stat.spread"} +ATT.Cons = {"stat.range", "stat.muzzlevelocity"} + +ATT.Model = "models/weapons/tacint_extras/addons/suppressor.mdl" +ATT.Scale = 1.4 + +ATT.ModelOffset = Vector(-0.05, 0, 0.05) + +ATT.Category = "silencer" + +ATT.SortOrder = 5 + +ATT.Add_Vol_Shoot = -20 +ATT.Mult_Spread = 0.8 +ATT.Mult_Range_Max = 0.9 +ATT.Mult_Range_Min = 0.9 +ATT.Mult_MuzzleVelocity = 0.9 + +ATT.Add_Pitch_Shoot = 7.5 + +ATT.Silencer = true +ATT.Override_MuzzleEffect = "muzzleflash_suppressed" + +TacRP.LoadAtt(ATT, "muzz_supp_compact") +-- #endregion + +------------------------------ +-- #region muzz_supp_weighted +------------------------------ +ATT = {} + +ATT.PrintName = "W. Suppressor" +ATT.FullName = "Weighted Suppressor" +ATT.Icon = Material("entities/tacrp_att_muzz_supp_weighted.png", "mips smooth") +ATT.Description = "Heavy suppressor with superior ballistics but worse handling." +ATT.Pros = {"stat.vol_shoot", "stat.range", "stat.recoil"} +ATT.Cons = {"rating.handling", "rating.maneuvering"} + +ATT.Model = "models/weapons/tacint_extras/addons/suppressor_salvo.mdl" +ATT.Scale = 1.5 + +ATT.ModelOffset = Vector(0.4, 0, -0.05) + +ATT.Category = "silencer" + +ATT.SortOrder = 6 + +ATT.Add_Vol_Shoot = -30 +ATT.Mult_RecoilKick = 0.75 +ATT.Mult_RecoilSpreadPenalty = 0.9 + +ATT.Mult_Range_Max = 1.15 +ATT.Mult_Range_Min = 1.15 + +ATT.Add_SprintToFireTime = 0.02 +ATT.Add_AimDownSightsTime = 0.03 + +ATT.Add_FreeAimMaxAngle = 0.5 +ATT.Add_Sway = 0.1 +ATT.Add_ScopedSway = 0.05 + +ATT.Add_Pitch_Shoot = -7.5 + +ATT.Silencer = true +ATT.Override_MuzzleEffect = "muzzleflash_suppressed" + +TacRP.LoadAtt(ATT, "muzz_supp_weighted") +-- #endregion + + +------------------------------ +-- #region muzz_brake_aggressor +------------------------------ +ATT = {} + +ATT.PrintName = "A. Brake" +ATT.FullName = "Aggressor Brake" +ATT.Icon = Material("entities/tacrp_att_muzz_brake_aggressor.png", "mips smooth") +ATT.Description = "Muzzle brake designed to redirect vented gases away from the shooter." +ATT.Pros = {"stat.shootingspeed"} +ATT.Cons = {"stat.recoilstability", "stat.spread", "stat.vol_shoot"} + +ATT.Model = "models/weapons/tacint_extras/addons/brake_aggressor.mdl" +ATT.Scale = 1.25 + +ATT.ModelOffset = Vector(-5.0, 0, 0) + +ATT.Category = {"silencer", "brake"} + +ATT.Add_ShootingSpeedMult = 0.05 +ATT.Mult_RecoilStability = 0.9 +ATT.Mult_Spread = 1.15 +ATT.Add_Vol_Shoot = 5 + +ATT.SortOrder = 100 + +TacRP.LoadAtt(ATT, "muzz_brake_aggressor") +-- #endregion + +------------------------------ +-- #region muzz_brake_breaching +------------------------------ +ATT = {} + +ATT.PrintName = "B. Brake" +ATT.FullName = "Breaching Brake" +ATT.Icon = Material("entities/tacrp_att_muzz_brake_breaching.png", "mips smooth") +ATT.Description = "Spiked muzzle brake designed for close combat." +ATT.Pros = {"stat.meleedamage", "stat.recoilstability"} +ATT.Cons = {"stat.spread"} + +ATT.Model = "models/weapons/tacint_extras/addons/brake_breacher.mdl" +ATT.Scale = 1.25 + +ATT.ModelOffset = Vector(-5.4, 0, 0) + +ATT.Category = {"silencer", "brake"} + +ATT.Mult_MeleeDamage = 1.4 +ATT.Mult_Spread = 1.25 +ATT.Mult_RecoilStability = 1.15 + +ATT.SortOrder = 101 + +TacRP.LoadAtt(ATT, "muzz_brake_breaching") +-- #endregion + +------------------------------ +-- #region muzz_brake_concussive +------------------------------ +ATT = {} + +ATT.PrintName = "C. Brake" +ATT.FullName = "Concussive Brake" +ATT.Icon = Material("entities/tacrp_att_muzz_brake_concussive.png", "mips smooth") +ATT.Description = "Viciously loud, uncomfortable muzzle brake for extreme recoil control." +ATT.Pros = {"stat.recoilkick"} +ATT.Cons = {"stat.shootingspeed", "stat.spread", "stat.vol_shoot"} + +ATT.Model = "models/weapons/tacint_extras/addons/brake_concussive.mdl" +ATT.Scale = 1.25 + +ATT.ModelOffset = Vector(-5.4, 0, 0) + +ATT.Category = {"silencer", "brake"} + +ATT.Mult_RecoilKick = 0.6 +-- ATT.Mult_RecoilStability = 1.1 + +ATT.Add_ShootingSpeedMult = -0.4 +ATT.Mult_Spread = 1.2 + +ATT.Add_Vol_Shoot = 15 +ATT.Mult_Pitch_Shoot = 0.9 + +ATT.SortOrder = 102 + +ATT.Override_MuzzleEffect = "muzzleflash_pistol_deagle" + +TacRP.LoadAtt(ATT, "muzz_brake_concussive") +-- #endregion diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/optic_tac.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/optic_tac.lua new file mode 100644 index 0000000..5ee8d89 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/optic_tac.lua @@ -0,0 +1,1175 @@ +-- optic_tac.lua + +local ATT = {} + +------------------------------ +-- #region optic_8x +------------------------------ +ATT = {} + +ATT.PrintName = "Telescopic" +ATT.Icon = Material("entities/tacrp_att_optic_8x.png", "mips smooth") +ATT.Description = "Long-range sniper optic." +ATT.Pros = {"att.zoom.8"} + +ATT.Model = "models/weapons/tacint/addons/scope.mdl" +ATT.Scale = 1 +ATT.ModelOffset = Vector(-1, 0, -0.35) + +ATT.Category = "optic_sniper" + +ATT.SortOrder = 8 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/sniper.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 8 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = true + +ATT.Override_ScopeOverlaySize = 0.8 + +TacRP.LoadAtt(ATT, "optic_8x") +-- #endregion + +------------------------------ +-- #region optic_acog +------------------------------ +ATT = {} + +ATT.PrintName = "ACOG" +ATT.Icon = Material("entities/tacrp_att_optic_acog.png", "mips smooth") +ATT.Description = "Medium range combat scope." +ATT.Pros = {"att.zoom.4"} + +ATT.Model = "models/weapons/tacint/addons/acog.mdl" +ATT.Scale = 0.3 +ATT.ModelOffset = Vector(-1, 0, 0.5) + +ATT.Category = "optic_medium" + +ATT.SortOrder = 4 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/acog.png", "mips smooth") +-- ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 4 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlaySize = 0.85 + +ATT.SightPos = Vector(0, -10, 1.01611) +ATT.SightAng = Angle(0, 0, 0) + +TacRP.LoadAtt(ATT, "optic_acog") +-- #endregion + +------------------------------ +-- #region optic_elcan +------------------------------ +ATT = {} + +ATT.PrintName = "ELCAN" +ATT.Icon = Material("entities/tacrp_att_optic_elcan.png", "mips smooth") +ATT.Description = "Low power combat scope." +ATT.Pros = {"att.zoom.3.4"} + +-- model: https://gamebanana.com/mods/210646 +-- scope texture: ins2 +ATT.Model = "models/weapons/tacint_extras/addons/elcan.mdl" +ATT.Scale = 1 +ATT.ModelOffset = Vector(-0.5, 0, -0.4) + +ATT.Category = "optic_medium" + +ATT.SortOrder = 3.4 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/elcan.png", "mips smooth") +-- ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 3.4 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = true + +ATT.SightPos = Vector(0, -10, 1.01611) +ATT.SightAng = Angle(0, 0, 0) + +TacRP.LoadAtt(ATT, "optic_elcan") +-- #endregion + +------------------------------ +-- #region optic_holographic +------------------------------ +ATT = {} + +ATT.PrintName = "Holographic" +ATT.Icon = Material("entities/tacrp_att_optic_holographic.png", "mips smooth") +ATT.Description = "Boxy optic to assist aiming at close range." +ATT.Pros = {"att.sight.1.5"} + +ATT.Model = "models/weapons/tacint/addons/holosight_hq.mdl" +ATT.Scale = 0.35 +ATT.ModelOffset = Vector(0, 0.05, 0) + +ATT.Category = {"optic_cqb", "optic_cqb_nookp7", "optic_cqb_verytall"} + +ATT.SortOrder = 1.5 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.5 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.SightPos = Vector(-0.05, -15, 1.1) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Holosight = Material("tacrp/hud/eotech.png", "additive") + +ATT.Holosight:SetInt("$flags", 128) + +TacRP.LoadAtt(ATT, "optic_holographic") +-- #endregion + +------------------------------ +-- #region optic_irons +------------------------------ +ATT = {} + +ATT.PrintName = "Iron Sights" +ATT.Icon = Material("entities/tacrp_att_optic_irons.png", "mips smooth") +ATT.Description = "Basic sights for added mobility." +ATT.Pros = {"rating.handling"} +ATT.Cons = {"att.procon.noscope"} + +ATT.Free = true + +ATT.InstalledElements = {"irons"} + +ATT.Category = "ironsights" + +ATT.SortOrder = 0 + +ATT.Override_ScopeHideWeapon = false +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.1 +ATT.Override_ScopeLevels = 1 + +ATT.Add_AimDownSightsTime = -0.03 +-- ATT.Add_SprintToFireTime = -0.03 + +TacRP.LoadAtt(ATT, "optic_irons") +-- #endregion + +------------------------------ +-- #region optic_irons_sniper +------------------------------ +ATT = {} + +ATT.PrintName = "att.optic_irons.name" +ATT.Icon = Material("entities/tacrp_att_optic_irons.png", "mips smooth") +ATT.Description = "Replace default scope for faster aim and better mobility." +ATT.Pros = {"rating.handling", "rating.mobility"} +ATT.Cons = {"att.procon.noscope"} + +ATT.Free = true + +ATT.InstalledElements = {"irons"} + +ATT.Category = "ironsights_sniper" + +ATT.SortOrder = 0 + +ATT.Override_ScopeHideWeapon = false +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.1 +ATT.Override_ScopeLevels = 1 + +ATT.Add_AimDownSightsTime = -0.05 +ATT.Add_SprintToFireTime = -0.05 +ATT.Add_SightedSpeedMult = 0.1 +ATT.Add_ShootingSpeedMult = 0.05 +ATT.Mult_HipFireSpreadPenalty = 0.75 + +TacRP.LoadAtt(ATT, "optic_irons_sniper") +-- #endregion + +------------------------------ +-- #region optic_okp7 +------------------------------ +ATT = {} + +ATT.PrintName = "OKP-7" +ATT.Icon = Material("entities/tacrp_att_optic_okp7.png", "mips smooth") +ATT.Description = "Low profile reflex sight with minimal zoom." +ATT.Pros = {"att.sight.1.25"} + +ATT.Model = "models/weapons/tacint/addons/okp7.mdl" + +ATT.Category = {"optic_cqb", "optic_okp7"} +ATT.Scale = 1.1 +ATT.ModelOffset = Vector(-2, 0, -0.55) + +ATT.SortOrder = 1.25 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.25 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.SightPos = Vector(0, -15, 1) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Holosight = Material("tacrp/hud/okp7.png", "smooth") + +ATT.Holosight:SetInt("$flags", 128) + +TacRP.LoadAtt(ATT, "optic_okp7") +-- #endregion + +------------------------------ +-- #region optic_rds2 +------------------------------ +ATT = {} + +ATT.PrintName = "Red Dot" +ATT.Icon = Material("entities/tacrp_att_optic_rds2.png", "mips smooth") +ATT.Description = "Open reflex sight with a clear view." +ATT.Pros = {"att.sight.1.25"} + +ATT.Model = "models/weapons/tacint/addons/rds2.mdl" +ATT.Scale = 1.1 +ATT.ModelOffset = Vector(-1.5, 0, -0.5) + +ATT.Category = {"optic_cqb", "optic_cqb_nookp7"} + +ATT.SortOrder = 1.25 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.25 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.SightPos = Vector(0, -15, 1.4) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Holosight = Material("tacrp/hud/rds.png", "additive") + +ATT.Holosight:SetInt("$flags", 128) + +TacRP.LoadAtt(ATT, "optic_rds2") +-- #endregion + +------------------------------ +-- #region optic_rds +------------------------------ +ATT = {} + +ATT.PrintName = "Aimpoint" +ATT.Icon = Material("entities/tacrp_att_optic_rds.png", "mips smooth") +ATT.Description = "Tube optic to assist aiming at close range." +ATT.Pros = {"att.sight.1.75"} + +ATT.Model = "models/weapons/tacint/addons/reddot_hq.mdl" +ATT.Scale = 0.35 +ATT.ModelOffset = Vector(0, 0, 1) + +ATT.Category = {"optic_cqb", "optic_cqb_nookp7", "optic_cqb_verytall"} + +ATT.SortOrder = 1.75 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.75 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.SightPos = Vector(0, -15, 0.1) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Holosight = Material("tacrp/hud/rds.png", "additive") + +ATT.Holosight:SetInt("$flags", 128) + +TacRP.LoadAtt(ATT, "optic_rds") +-- #endregion + +------------------------------ +-- #region optic_rmr +------------------------------ +ATT = {} + +ATT.PrintName = "RMR" +ATT.Icon = Material("entities/tacrp_att_optic_rmr.png", "mips smooth") +ATT.Description = "Low profile optic sight for pistols." +ATT.Pros = {"att.sight.1"} + +ATT.InstalledElements = {"optic_rmr"} + +ATT.Model = "models/weapons/tacint/addons/optic_rmr_hq.mdl" +ATT.Scale = 1 + +ATT.Category = "optic_pistol" + +ATT.SortOrder = 1 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.1 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.SightPos = Vector(0, -10, 0.520837) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Holosight = Material("tacrp/hud/rds.png", "additive") + +ATT.Holosight:SetInt("$flags", 128) + +TacRP.LoadAtt(ATT, "optic_rmr") +-- #endregion + +------------------------------ +-- #region optic_rmr_rifle +------------------------------ +ATT = {} + +ATT.PrintName = "RMR" +ATT.PrintName = "RMR" +ATT.Icon = Material("entities/tacrp_att_optic_rmr.png", "mips smooth") +ATT.Description = "Low profile optic sight." +ATT.Pros = {"att.sight.1"} + +ATT.Model = "models/weapons/tacint/addons/optic_rmr_hq.mdl" +ATT.Scale = 1 +ATT.ModelOffset = Vector(-1, -0, -0.4) + +ATT.Category = "optic_cqb" +ATT.InvAtt = "optic_rmr" + +ATT.SortOrder = 1 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.1 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = false + +ATT.SightPos = Vector(0, -13, 0.45) +ATT.SightAng = Angle(0, 0, 0) + +ATT.Holosight = Material("tacrp/hud/rds.png", "additive") + +ATT.Holosight:SetInt("$flags", 128) + +ATT.Compatibility = function(wpn, cats) -- Allows a weapon to have the OKP-7 but not the RMR + if wpn.NoRMR then return false end +end + +TacRP.LoadAtt(ATT, "optic_rmr_rifle") +-- #endregion + +------------------------------ +-- #region optic_shortdot +------------------------------ +ATT = {} + +ATT.PrintName = "Short Dot" +ATT.Icon = Material("entities/tacrp_att_optic_shortdot.png", "mips smooth") +ATT.Description = "Compact optic scope with decent magnification." +ATT.Pros = {"att.zoom.5"} + +-- model: gamebanana +-- scope texture: ins2 +ATT.Model = "models/weapons/tacint_extras/addons/schd.mdl" +ATT.Scale = 1.15 +ATT.ModelOffset = Vector(-1, 0, -0.45) + +ATT.Category = "optic_medium" + +ATT.SortOrder = 5 + +ATT.Override_Scope = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/shortdot.png", "mips smooth") +-- ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 5 +ATT.Override_ScopeLevels = 1 +ATT.Override_ScopeHideWeapon = true + +ATT.SightPos = Vector(0, -10, 1.01611) +ATT.SightAng = Angle(0, 0, 0) + +TacRP.LoadAtt(ATT, "optic_shortdot") +-- #endregion + +------------------------------ +-- #region tac_cornershot +------------------------------ +ATT = {} + +ATT.PrintName = "Corner-Cam" +ATT.Icon = Material("entities/tacrp_att_tac_cornershot.png", "mips smooth") +ATT.Description = "Displays point of aim while blindfiring." +ATT.Pros = {"att.procon.cornershot"} + +ATT.Model = "models/weapons/tacint/addons/cornershot_mounted.mdl" +ATT.Scale = 1 + +ATT.Category = "tactical" + +ATT.CanToggle = true +ATT.BlindFireCamera = true + +ATT.TacticalName = "hint.tac.cam_mode" + +TacRP.LoadAtt(ATT, "tac_cornershot") +-- #endregion + +------------------------------ +-- #region tac_dmic +------------------------------ +ATT = {} + +ATT.PrintName = "Radar" +ATT.Icon = Material("entities/tacrp_att_tac_dmic.png", "mips smooth") +ATT.Description = "Detects the position of nearby targets, but emits sound." +ATT.Pros = {"att.procon.dmic"} +ATT.Cons = {"att.procon.audible"} + +ATT.Model = "models/weapons/tacint/addons/dmic_mounted.mdl" +ATT.Scale = 1 + +ATT.Category = "tactical" + +ATT.Minimap = true +ATT.CanToggle = true + +ATT.TacticalName = "hint.tac.radar" + +local scantime = TacRP.ConVars["att_radartime"] +local lastradar = 0 +local cache_lastradarpositions +local mat_radar = Material("tacrp/hud/radar.png", "smooth") +local mat_radar_active = Material("tacrp/hud/radar_active.png", "mips smooth") +local mat_dot = Material("tacrp/hud/dot.png", "mips smooth") +local mat_tri = Material("tacrp/hud/triangle.png", "mips smooth") +function ATT.TacticalDraw(self) + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(100) + local h = TacRP.SS(100) + + local x = (scrw - w) / 2 + local y = (scrh - h) * 0.99 + + surface.SetMaterial(mat_radar) + surface.SetDrawColor(255, 255, 255, 100) + surface.DrawTexturedRect(x, y, w, h) + + local radarpositions = {} + + if lastradar + scantime:GetFloat() > CurTime() then + radarpositions = cache_lastradarpositions + else + local tbl = ents.FindInSphere(self:GetOwner():GetPos(), 50 / TacRP.HUToM) + + local i = 0 + for _, ent in ipairs(tbl) do + if !((ent:IsPlayer() and ent:Alive()) or (ent:IsNPC() and ent:Health() > 0) or ent:IsNextBot()) then continue end + if ent == self:GetOwner() then continue end + + local ang = self:GetOwner():EyeAngles() + + ang.y = ang.y + 90 + ang.p = 0 + ang.r = 0 + + local relpos = WorldToLocal(ent:GetPos(), Angle(0, 0, 0), self:GetOwner():GetPos(), ang) + + local read = { + x = -relpos.x, + y = relpos.y, + z = relpos.z, + } + + table.insert(radarpositions, read) + i = i + 1 + end + + lastradar = CurTime() + cache_lastradarpositions = radarpositions + + if !TacRP.ConVars["radar_quiet"]:GetBool() then + LocalPlayer():EmitSound("plats/elevbell1.wav", 60, 95 + math.min(i, 3) * 5, 0.1 + math.min(i, 3) * 0.05) + end + end + + surface.SetDrawColor(0, 0, 0, 255 * 2 * (1 - ((CurTime() - lastradar) / scantime:GetFloat()))) + surface.SetMaterial(mat_radar_active) + surface.DrawTexturedRect(x, y, w, h) + -- surface.SetDrawColor(255, 255, 255, 255) + + local ds = TacRP.SS(4) + + for _, dot in ipairs(radarpositions) do + local dx = x + (dot.x * TacRP.HUToM * w * (36 / 40) / 100) + (w / 2) + local dy = y + (dot.y * TacRP.HUToM * h * (36 / 40) / 100) + (h / 2) + + local gs = TacRP.SS(8) + + dx = math.Round(dx / (w / gs)) * (w / gs) + dy = math.Round(dy / (h / gs)) * (h / gs) + + dx = dx - TacRP.SS(0.5) + dy = dy - TacRP.SS(0.5) + + if math.abs(dot.z) > 128 then + surface.SetMaterial(mat_tri) + surface.DrawTexturedRectRotated(dx, dy, ds, ds, dot.z > 0 and 0 or 180) + else + surface.SetMaterial(mat_dot) + surface.DrawTexturedRect(dx - (ds / 2), dy - (ds / 2), ds, ds) + end + end +end + +function ATT.TacticalThink(self) + if IsValid(self:GetOwner()) and self:GetTactical() and (SERVER and !game.SinglePlayer()) and (self.NextRadarBeep or 0) < CurTime() then + self.NextRadarBeep = CurTime() + scantime:GetFloat() + local f = RecipientFilter() + f:AddPAS(self:GetPos()) + f:RemovePlayer(self:GetOwner()) + local s = CreateSound(self, "plats/elevbell1.wav", f) + s:SetSoundLevel(80) + s:PlayEx(0.2, 105) + end +end + +ATT.Compatibility = function(wpn, cats) -- Allows a weapon to have the OKP-7 but not the RMR + if wpn.RangefinderIntegral then return false end +end + +TacRP.LoadAtt(ATT, "tac_dmic") +-- #endregion + +------------------------------ +-- #region tac_flashlight +------------------------------ +ATT = {} + +ATT.PrintName = "Flashlight" +ATT.Icon = Material("entities/tacrp_att_tac_flashlight.png", "mips smooth") +ATT.Description = "Emits a strong beam of light, blinding anyone staring into it." +ATT.Pros = {"att.procon.flashlight", "att.procon.blind"} +ATT.Cons = {"att.procon.visible"} + +ATT.Model = "models/weapons/tacint/addons/flashlight_mounted.mdl" +ATT.Scale = 1 + +ATT.Category = "tactical" + +ATT.SortOrder = 1 + +ATT.Flashlight = true +ATT.Blinding = true +ATT.FlashlightFOV = 75 +ATT.FlashlightBrightness = 1.25 + +ATT.CanToggle = true + +ATT.TacticalName = "hint.tac.flashlight" + +TacRP.LoadAtt(ATT, "tac_flashlight") +-- #endregion + +------------------------------ +-- #region tac_laser +------------------------------ +ATT = {} + +ATT.PrintName = "Laser" +ATT.Icon = Material("entities/tacrp_att_tac_laser.png", "mips smooth") +ATT.Description = "Emits a narrow red beam and dot, indicating where the gun is pointed at." +ATT.Pros = {"att.procon.laser"} +ATT.Cons = {"att.procon.visible"} + +ATT.Model = "models/weapons/tacint/addons/laser_mounted.mdl" +ATT.Scale = 1 + +ATT.Category = "tactical" + +ATT.SortOrder = 1 + +ATT.Laser = true +ATT.CanToggle = true + +ATT.TacticalName = "hint.tac.laser" + +ATT.Override_LaserColor = Color(255, 0, 0) + +TacRP.LoadAtt(ATT, "tac_laser") +-- #endregion + + +------------------------------ +-- #region tac_combo +------------------------------ +ATT = {} + +ATT.FullName = "Laser-Light Combo" +ATT.PrintName = "Combo" +ATT.Icon = Material("entities/tacrp_att_tac_combo.png", "mips smooth") +ATT.Description = "Emits a green laser and flashlight. The light is too weak to blind others." +ATT.Pros = {"att.procon.laser", "att.procon.flashlight"} +ATT.Cons = {"att.procon.visible"} + +ATT.Model = "models/weapons/tacint_extras/addons/anpeq.mdl" +ATT.Scale = 0.6 +ATT.ModelAngleOffset = Angle(0, 0, 0) + +ATT.Category = "tactical" + +ATT.SortOrder = 2 + +ATT.Laser = true +ATT.Flashlight = true +ATT.FlashlightFOV = 60 +ATT.FlashlightBrightness = 0.75 +ATT.LaserPower = 1 +ATT.CanToggle = true + +ATT.TacticalName = "hint.tac.combo" + +ATT.Override_LaserColor = Color(0, 255, 0, 200) + +TacRP.LoadAtt(ATT, "tac_combo") +-- #endregion + +------------------------------ +-- #region tac_rangefinder +------------------------------ +ATT = {} + +ATT.PrintName = "Rangefinder" +ATT.Icon = Material("entities/tacrp_att_tac_rangefinder.png", "mips smooth") +ATT.Description = "Measures ballistic performance of the weapon." +ATT.Pros = {"att.procon.rf1", "att.procon.rf2"} + +ATT.Model = "models/weapons/tacint/addons/rangefinder_mounted.mdl" +ATT.Scale = 1 + +ATT.Category = "tactical" + +ATT.Rangefinder = true +ATT.CanToggle = true + +ATT.TacticalName = "hint.tac.rangefinder" + +local lastrangefinder = 0 +local rftime = 1 / 10 +local rawdist = 0 +local cached_txt +local cached_txt2 +local mat_rf = Material("tacrp/hud/rangefinder.png", "mips smooth") +function ATT.TacticalDraw(self) + local txt = "NO RTN" + local txt2 = "" + local txt3 = "" + local txt4 = "" + + if lastrangefinder + rftime < CurTime() then + local tr = util.TraceLine({ + start = self:GetMuzzleOrigin(), + endpos = self:GetMuzzleOrigin() + (self:GetShootDir():Forward() * 50000), + mask = MASK_OPAQUE_AND_NPCS, + filter = self:GetOwner() + }) + + rawdist = (tr.HitPos - tr.StartPos):Length() + local dist + if TacRP.ConVars["metricunit"]:GetBool() then + dist = math.min(math.Round(rawdist * TacRP.HUToM, 0), 99999) + txt = tostring(dist) .. "m" + else + dist = math.min(math.Round(rawdist, 0), 99999) + txt = tostring(dist) .. "HU" + end + + if TacRP.ConVars["physbullet"]:GetBool() then + -- Not totally accurate due to hitscan kicking in up close + local t = math.Round(rawdist / self:GetValue("MuzzleVelocity"), 2) + txt2 = tostring(math.Round(rawdist / self:GetValue("MuzzleVelocity"), 2)) .. "s" + if t > 0 and t < 1 then txt2 = string.sub(txt2, 2) end + else + -- Not totally accurate due to hitscan kicking in up close + if !TacRP.ConVars["metricunit"]:GetBool() then + txt2 = tostring(math.min(math.Round(rawdist * TacRP.HUToM, 0), 99999)) .. "m" + else + txt2 = tostring(math.min(math.Round(rawdist, 0), 99999)) .. "HU" + end + end + + local edmg = self:GetDamageAtRange(rawdist) + edmg = math.ceil(edmg) + + txt3 = tostring(edmg) .. "DMG" + + for _ = 0, 12 - string.len(txt3) - string.len(txt) do + txt = txt .. " " + end + + txt = txt .. txt3 + + local mult = self:GetBodyDamageMultipliers() --self:GetValue("BodyDamageMultipliers") + local min = math.min(unpack(mult)) + + if edmg * min >= 100 then + txt4 = "LETHAL" + elseif edmg * mult[HITGROUP_LEFTLEG] >= 100 then + txt4 = "LEGS" + elseif edmg * mult[HITGROUP_LEFTARM] >= 100 then + txt4 = "ARMS" + elseif edmg * mult[HITGROUP_STOMACH] >= 100 then + txt4 = "STMCH" + elseif edmg * mult[HITGROUP_CHEST] >= 100 then + txt4 = "CHEST" + elseif edmg * mult[HITGROUP_HEAD] >= 100 then + txt4 = "HEAD" + else + txt4 = tostring(math.ceil(100 / edmg)) .. (self:GetValue("Num") > 1 and "PTK" or "STK") + end + + for _ = 0, 12 - string.len(txt4) - string.len(txt2) do + txt2 = txt2 .. " " + end + + txt2 = txt2 .. txt4 + + cached_txt = txt + cached_txt2 = txt2 + lastrangefinder = CurTime() + else + txt = cached_txt + txt2 = cached_txt2 + end + + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(100) + local h = TacRP.SS(50) + + local x = (scrw - w) / 2 + local y = (scrh - h) * 5 / 6 + + surface.SetMaterial(mat_rf) + surface.SetDrawColor(255, 255, 255, 100) + surface.DrawTexturedRect(x, y, w, h) + + surface.SetFont("TacRP_HD44780A00_5x8_10") + -- local tw = surface.GetTextSize(txt) + surface.SetTextPos(x + TacRP.SS(3), y + TacRP.SS(12)) + surface.SetTextColor(0, 0, 0) + surface.DrawText(txt) + + -- local tw2 = surface.GetTextSize(txt2) + surface.SetTextPos(x + TacRP.SS(3), y + TacRP.SS(22)) + surface.SetTextColor(0, 0, 0) + surface.DrawText(txt2) +end + +local last_laze_time = 0 +-- local last_laze_dist = 0 +local laze_interval = 0.2 +local ccip_v = 0 +local dropalpha = 0 +local dropalpha2 = 0 +local frac = 0 +function ATT.TacticalCrosshair(self, x, y, spread, sway) + + if self:GetNextPrimaryFire() + 0.1 > CurTime() then + dropalpha2 = 0 + end + + if self:IsInScope() and (self:GetValue("ScopeOverlay") or !self:GetReloading()) then + dropalpha = math.Approach(dropalpha, self:GetSightAmount() ^ 2, FrameTime() * 1) + dropalpha2 = math.Approach(dropalpha2, dropalpha, FrameTime() * 1) + else + dropalpha = math.Approach(dropalpha, 0, FrameTime() * 10) + dropalpha2 = dropalpha + end + if dropalpha == 0 then return end + + frac = math.Clamp((rawdist - self:GetValue("Range_Min")) / (self:GetValue("Range_Max") - self:GetValue("Range_Min")), 0, 1) + if self:GetValue("Damage_Min") <= self:GetValue("Damage_Max") then frac = 1 - frac end + + -- surface.DrawCircle(x, y, 16, 255, 255, 255, dropalpha * 80) + surface.SetDrawColor(255, 255, 255, dropalpha * 150) + surface.DrawLine(x - 16, y, x + 16, y) + surface.DrawLine(x, y + 16, x, y - 16) + surface.SetFont("DermaDefault") + draw.SimpleText(math.Round(frac * 100) .. "%", "DermaDefault", x, y - 16, surface.GetDrawColor(), TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) + + if !TacRP.ConVars["physbullet"]:GetBool() then return end + + if last_laze_time + laze_interval <= CurTime() then + last_laze_time = CurTime() + local ccip = self:GetCCIP() + + if !ccip then + ccip_v = 0 + else + cam.Start3D(nil, nil, self.ViewModelFOV) + ccip_v = (ccip.HitPos:ToScreen().y - (ScrH() / 2)) * self:GetCorVal() + -- local localhp = mdl:WorldToLocal(ccip.HitPos) + -- local localpos = mdl:WorldToLocal(pos) + -- ccip_v = (localpos.z - localhp.z) + cam.End3D() + -- last_laze_dist = ccip.HitPos:Distance(self:GetMuzzleOrigin()) + end + end + + for i = 1, math.Round((ccip_v - 4) / 4) do + surface.DrawCircle(x, y + i * 4, 1, 255, 255, 255, dropalpha2 * 75) + end + + -- surface.DrawCircle(x, y + ccip_v, 6, 255, 255, 255, dropalpha * 120) + -- surface.DrawCircle(x, y + ccip_v, 8, 255, 255, 255, dropalpha * 120) + surface.SetDrawColor(255, 255, 255, dropalpha2 * 150) + surface.DrawLine(x - 7, y - 7 + ccip_v, x + 7, y + 7 + ccip_v) + surface.DrawLine(x - 7, y + 7 + ccip_v, x + 7, y - 7 + ccip_v) + + -- surface.DrawCircle(x, y, spread - 1, 255, 255, 255, circlealpha * 75) + -- surface.DrawCircle(x, y, spread + 1, 255, 255, 255, circlealpha * 75) +end + +ATT.TacticalCrosshairTruePos = true + +ATT.Compatibility = function(wpn, cats) -- Allows a weapon to have the OKP-7 but not the RMR + if wpn.RangefinderIntegral then return false end +end + +TacRP.LoadAtt(ATT, "tac_rangefinder") +-- #endregion + +------------------------------ +-- #region tac_spreadgauge +------------------------------ +ATT = {} + +ATT.PrintName = "Spread Gauge" +ATT.Icon = Material("entities/tacrp_att_tac_rangefinder.png", "mips smooth") +ATT.Description = "Measures weapon stability from sway and bloom." +ATT.Pros = {"att.procon.gauge1", "att.procon.gauge2"} + +ATT.Model = "models/weapons/tacint/addons/rangefinder_mounted.mdl" +ATT.Scale = 1 + +ATT.Category = "tactical" + +ATT.SpreadGauge = true +ATT.CanToggle = true + +ATT.TacticalName = "hint.tac.spread_gauge" + +local mat_spread = Material("tacrp/hud/spreadgauge.png", "smooth") +local mat_spread_fire = Material("tacrp/hud/spreadgauge_fire.png", "") +local mat_spread_gauge = Material("tacrp/hud/spreadgauge_gauge.png", "") +local mat_spread_text = Material("tacrp/hud/spreadgauge_text.png", "") +local mat_cone = Material("tacrp/hud/cone.png", "smooth") +local mat_cone_text = Material("tacrp/hud/cone_text.png", "") +function ATT.TacticalDraw(self) + local scrw = ScrW() + local scrh = ScrH() + + local w = TacRP.SS(60) + local h = TacRP.SS(30) + + local x = (scrw - w) / 2 + local y = (scrh - h) * 5.5 / 6 + + -- if self:GetSightDelta() > 0 then + -- y = y - self:GetSightDelta() ^ 0.5 * TacRP.SS(24) + -- end + + surface.SetMaterial(mat_spread) + surface.SetDrawColor(255, 255, 255, 100) + surface.DrawTexturedRect(x, y, w, h) + + local spread = math.Clamp(math.deg(self:GetSpread()) * 60, 0, 999.9) + local spread1 = math.floor(spread) + local spread2 = math.floor((spread - spread1) * 10) + local spread_txt1 = tostring(spread1) + surface.SetFont("TacRP_HD44780A00_5x8_6") + surface.SetTextColor(0, 0, 0) + surface.SetTextPos(x + TacRP.SS(22), y + TacRP.SS(2.5)) + if spread < 10 then + surface.SetTextColor(0, 0, 0, 100) + surface.DrawText("00") + surface.SetTextColor(0, 0, 0) + elseif spread < 100 then + surface.SetTextColor(0, 0, 0, 100) + surface.DrawText("0") + surface.SetTextColor(0, 0, 0) + end + surface.DrawText(spread_txt1) + surface.DrawText(".") + surface.DrawText(spread2) + + local recoil = self:GetRecoilAmount() + local recoil_pct = math.Round( recoil, 2 ) + local recoil_per = recoil / self:GetValue("RecoilMaximum") + surface.SetTextPos(x + TacRP.SS(22), y + TacRP.SS(11.5)) + surface.SetTextColor(0, 0, 0) + + if recoil_pct < 10 then + surface.SetTextColor(0, 0, 0, 100) + surface.DrawText("0") + surface.SetTextColor(0, 0, 0) + elseif recoil_per == 1 and math.sin(SysTime() * 60) > 0 then + surface.SetTextColor(0, 0, 0, 150) + end + surface.DrawText(recoil_pct) + + local bleh = math.ceil(recoil_pct) - recoil_pct + bleh = tostring(bleh) + if (recoil_per == 1 and math.sin(SysTime() * 60) > 0) then + surface.SetTextColor(0, 0, 0, 150) + else + surface.SetTextColor(0, 0, 0) + end + if #bleh == 1 then + surface.DrawText(".00") + elseif #bleh == 3 then + surface.SetTextColor(0, 0, 0) + surface.DrawText("0") + end + + local last_fire = math.Clamp((self:GetNextPrimaryFire() - CurTime()) / (60 / self:GetValue("RPM")), 0, 1) + surface.SetDrawColor(255, 255, 255, last_fire * 255) + surface.SetMaterial(mat_spread_fire) + surface.DrawTexturedRect(x, y, w, h) + + surface.SetDrawColor(255, 255, 255, math.abs(math.sin(SysTime())) * 200) + surface.SetMaterial(mat_spread_gauge) + surface.DrawTexturedRect(x, y, w, h) + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(mat_spread_text) + surface.DrawTexturedRect(x, y, w, h) + + local w_cone = TacRP.SS(40) + local x2 = (scrw - w_cone) / 2 + local y2 = y - w_cone - TacRP.SS(4) + + surface.SetMaterial(mat_cone) + surface.SetDrawColor(255, 255, 255, 100) + surface.DrawTexturedRect(x2, y2, w_cone, w_cone) + surface.SetMaterial(mat_cone_text) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(x2, y2, w_cone, w_cone) + + local acc_size = math.ceil(spread * 0.125) --math.max(TacRP.GetFOVAcc(self), 1) + local a = math.Clamp(1 - (acc_size - TacRP.SS(15)) / TacRP.SS(5), 0.05, 1) ^ 0.5 + surface.DrawCircle(x2 + w_cone / 2, y2 + w_cone / 2, math.min(TacRP.SS(16), acc_size), 0, 0, 0, a * 150) + surface.DrawCircle(x2 + w_cone / 2, y2 + w_cone / 2, math.min(TacRP.SS(16), acc_size) + 0.5, 0, 0, 0, a * 200) + surface.DrawCircle(x2 + w_cone / 2, y2 + w_cone / 2, math.min(TacRP.SS(16), acc_size) + 1, 0, 0, 0, a * 150) + + if spread < 101 then + acc_size = math.ceil(spread * 0.625) + surface.DrawCircle(x2 + w_cone / 2, y2 + w_cone / 2, acc_size, 0, 0, 0, a * 200) + end + + local fov_mult = LocalPlayer():GetFOV() / math.max(self.TacRPLastFOV or 90, 0.00001) + local fov_mult1 = math.floor(fov_mult) + local fov_mult2 = math.Round(fov_mult - math.floor(fov_mult), 1) * 10 + if fov_mult2 == 10 then fov_mult1 = fov_mult1 + 1 fov_mult2 = 0 end + + surface.SetFont("TacRP_HD44780A00_5x8_6") + surface.SetTextColor(0, 0, 0) + surface.SetTextPos(x2 + TacRP.SS(17), y2 + TacRP.SS(2)) + surface.DrawText(fov_mult1 .. "." .. fov_mult2 .. "x") + local sway_pct = math.Clamp(math.Round((self:IsSwayEnabled() and self:GetSwayAmount() or self:GetForcedSwayAmount()) * 100), 0, 999) + local sway_txt = sway_pct .. "%" + local sway_w = surface.GetTextSize("100%") -- same width per char so its ok + surface.SetTextPos(x2 + TacRP.SS(23) - sway_w, y2 + w_cone - TacRP.SS(8.5)) + if sway_pct < 10 then + surface.SetTextColor(0, 0, 0, 100) + surface.DrawText("00") + surface.SetTextColor(0, 0, 0) + elseif sway_pct < 100 then + surface.SetTextColor(0, 0, 0, 100) + surface.DrawText("0") + surface.SetTextColor(0, 0, 0) + end + surface.DrawText(sway_txt) +end + +local circlealpha = 0 +function ATT.TacticalCrosshair(self, x, y, spread, sway) + if self:IsInScope() and !self:GetReloading() then + circlealpha = math.Approach(circlealpha, self:GetSightAmount() ^ 2, FrameTime() * 2) + else + circlealpha = math.Approach(circlealpha, 0, FrameTime() * 10) + end + if circlealpha == 0 then return end + + surface.DrawCircle(x, y, spread - 1, 255, 255, 255, circlealpha * 100) + surface.DrawCircle(x, y, spread + 1, 255, 255, 255, circlealpha * 100) +end + +ATT.TacticalCrosshairTruePos = true + +ATT.Compatibility = function(wpn, cats) -- Allows a weapon to have the OKP-7 but not the RMR + if wpn.RangefinderIntegral then return false end +end + +TacRP.LoadAtt(ATT, "tac_spreadgauge") +-- #endregion + +------------------------------ +-- #region tac_magnifier +------------------------------ +ATT = {} + +ATT.PrintName = "2x Zoom" +ATT.FullName = "Variable Zoom Optic (2x)" +ATT.Icon = Material("entities/tacrp_att_tac_magnifier.png", "mips smooth") +ATT.Description = "Allows all optics to access a 2x zoom level, allowing them to zoom in or out." +ATT.Pros = {"att.procon.magnifier"} +ATT.Cons = {"att.procon.needscope"} + +ATT.Category = "tactical_zoom" + +ATT.SortOrder = 8 + +ATT.CanToggle = true + +ATT.Hook_ModifyMagnification = function(wep, data) + if wep:GetTactical() and wep:GetValue("Scope") and (wep:GetValue("ScopeOverlay") or wep:GetValue("Holosight")) then + return 2 + end +end + +ATT.Override_Sound_ToggleTactical = "tacrp/2xzoom.wav" + +ATT.TacticalName = "hint.tac.magnifier" + +ATT.Free = true + +TacRP.LoadAtt(ATT, "tac_magnifier") +-- #endregion + +------------------------------ +-- #region tac_bullet +------------------------------ +ATT = {} + +ATT.FullName = "Emergency Bullet" +ATT.PrintName = "Emrg. Bullet" +ATT.Icon = Material("entities/tacrp_att_tac_bullet.png", "mips smooth") +ATT.Description = "Press the tactical key to quickly load a single bullet for emergencies." +ATT.Pros = {"att.procon.bullet"} +ATT.Cons = {} + +ATT.Category = "tactical_ebullet" + +ATT.SortOrder = 9 + +ATT.Override_Sound_ToggleTactical = "" +ATT.CanToggle = true + +ATT.CustomTacticalHint = "hint.tac.load_one" + +ATT.Free = true + +ATT.Hook_ToggleTactical = function(wep) + if wep:GetMaxClip1() <= 2 then + wep:Reload(true) + return true + end + + if wep:GetReloading() then return end + if wep:StillWaiting() then return end + + if wep:GetCapacity() <= 0 then return end + + local loadamt = math.min(wep:GetCapacity() - wep:Clip1(), wep:GetValue("Akimbo") and 2 or wep:GetValue("AmmoPerShot")) + + if loadamt <= 0 then return end + if wep:Ammo1() < loadamt and !wep:GetInfiniteAmmo() then return end + + -- wep:SetNextPrimaryFire(CurTime() + 1) + local t = wep:PlayAnimation("jam", 0.667, true, true) + wep:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_RELOAD_LOOP, t * 1000) + -- wep:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RELOAD_PISTOL) + wep:RestoreClip(loadamt) + wep:DoBulletBodygroups() + wep:SetJammed(false) + + return true +end + +TacRP.LoadAtt(ATT, "tac_bullet") +-- #endregion + +------------------------------ +-- #region tac_thermal +------------------------------ +ATT = {} + +ATT.PrintName = "Thermal-Cam" +ATT.FullName = "ZUMQFY Thermal Imaging Device" +ATT.Icon = Material("entities/tacrp_att_tac_cornershot.png", "mips smooth") +ATT.Description = "Display a thermal overlay which fuses with the main view while peeking." +ATT.Pros = {"att.procon.thermal"} +ATT.Cons = {} --{"att.procon.blurpeek"} + +ATT.Model = "models/weapons/tacint/addons/cornershot_mounted.mdl" + +ATT.Category = "tactical" + +ATT.ThermalCamera = true +ATT.CanToggle = true + +ATT.TacticalName = "hint.tac.cam_mode" + +-- ATT.Hook_BlurScope = function(wep) +-- if wep:GetScopeLevel() > 0 and wep:GetPeeking() then +-- local d = wep:GetSightAmount() +-- return {d * 0.25, d} +-- end +-- end + +TacRP.LoadAtt(ATT, "tac_thermal") +-- #endregion + +------------------------------ +-- toploader_stripper_clip +------------------------------ +ATT = {} + +ATT.PrintName = "Clip" +ATT.FullName = "Stripper Clip" +ATT.Icon = Material("entities/tacrp_att_optic_stripperclip.png", "mips smooth") +ATT.Description = "Use stripper clips to improve reload speed." +ATT.Pros = {"Reloads all rounds at once"} +ATT.Cons = {"No single-round reloads", "Incompatible with optics"} + +ATT.Category = "stripper_clip" + +ATT.SortOrder = 0 + +ATT.ShotgunReload = false + +ATT.Hook_TranslateSequence = function(self, seq) + if seq == "reload" then + return {"reload_clip"} + end +end + +ATT.InstalledElements = {"optic_clip"} + +TacRP.LoadAtt(ATT, "toploader_stripper_clip") diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/pa.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/pa.lua new file mode 100644 index 0000000..acb9405 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/pa.lua @@ -0,0 +1,1108 @@ +local ATT + +ATT = {} + +ATT.PrintName = "4x" +ATT.Icon = Material("entities/tacrp_att_optic_8x.png", "mips smooth") +ATT.Description = "Reduced scope magnification." +ATT.Pros = {"att.zoom.4"} + +ATT.Free = true + +ATT.Category = "optic_sako85" + +ATT.SortOrder = 1 + +ATT.Override_ScopeFOV = 90 / 4 + +TacRP.LoadAtt(ATT, "optic_sako85_4x") + +ATT = {} + +ATT.PrintName = "8x" +ATT.Icon = Material("entities/tacrp_att_optic_8x.png", "mips smooth") +ATT.Description = "Increased scope magnification." +ATT.Pros = {"att.zoom.8"} + +ATT.Free = true + +ATT.Category = "optic_sako85" + +ATT.SortOrder = 2 + +ATT.Override_ScopeFOV = 90 / 8 + +TacRP.LoadAtt(ATT, "optic_sako85_8x") + +ATT = {} + +ATT.PrintName = "Iron Sights" +ATT.Icon = Material("entities/tacrp_att_optic_irons.png", "mips smooth") +ATT.Description = "Remove scope for faster aim and better mobility." +ATT.Pros = {"rating.handling", "rating.mobility"} +ATT.Cons = {"att.procon.noscope"} + +ATT.Free = true + +ATT.InstalledElements = {"irons"} + +ATT.Category = "optic_sako85" + +ATT.SortOrder = 0 + +ATT.Override_ScopeHideWeapon = false +ATT.Override_ScopeOverlay = false +ATT.Override_ScopeFOV = 90 / 1.25 + +ATT.Add_AimDownSightsTime = -0.05 +ATT.Add_SprintToFireTime = -0.04 +ATT.Add_SightedSpeedMult = 0.1 +ATT.Add_ShootingSpeedMult = 0.05 +ATT.Mult_HipFireSpreadPenalty = 0.75 + +TacRP.LoadAtt(ATT, "optic_sako85_irons") + +ATT = {} + +ATT.PrintName = ".222 Win" +ATT.FullName = "Sako 85 .222 Winchester Mod Kit" +ATT.Icon = Material("entities/tacrp_pa_sako85.png", "mips smooth") +ATT.Description = "Load intermediate rounds with reduced recoil and lethality." +ATT.Pros = {"stat.rpm", "stat.recoilkick", "stat.clipsize"} +ATT.Cons = {"stat.damage"} + +ATT.Category = "ammo_sako85" +ATT.Free = true + +ATT.SortOrder = 1 + +ATT.Override_Ammo = "smg1" +ATT.Override_Ammo_Expanded = "smg1" +ATT.Override_Sound_Shoot = "^tacint_extras/sako85/m16_fire_01.wav" + +ATT.Mult_RPM = 1.2 +ATT.Mult_ShootTimeMult = 1.15 / 1.2 + +ATT.Mult_Damage_Max = 0.85 +ATT.Mult_Damage_Min = 0.85 + +ATT.Mult_RecoilKick = 0.5 +ATT.Mult_Range_Max = 1.25 +ATT.Add_ClipSize = 1 -- not realism but who cares + +ATT.Override_BodyDamageMultipliersExtra = { + [HITGROUP_STOMACH] = 0.85, +} + +TacRP.LoadAtt(ATT, "ammo_sako85_222") + +ATT = {} + +ATT.PrintName = ".300 Win Mag" +ATT.FullName = "Sako 85 .300 Winchester Magnum Mod Kit" +ATT.Icon = Material("entities/tacrp_pa_sako85.png", "mips smooth") +ATT.Description = "Load magnum rounds for improved lethality." +ATT.Pros = {"stat.damage"} +ATT.Cons = {"stat.recoilkick", "stat.rpm", "stat.clipsize"} + +ATT.Category = "ammo_sako85" +ATT.Free = true + +ATT.SortOrder = 2 + +ATT.Override_Ammo = "357" +ATT.Override_Ammo_Expanded = "ti_rifle" + +ATT.Override_Sound_Shoot = "^tacint_extras/sako85/win1892_fire_01.wav" + +ATT.Mult_RPM = 0.9 +ATT.Mult_ShootTimeMult = 1.15 / 0.9 + +ATT.Mult_Damage_Max = 1.16 +ATT.Mult_Damage_Min = 1.16 + +ATT.Add_RecoilKick = 1 +ATT.Add_ClipSize = -1 + +ATT.Override_BodyDamageMultipliersExtra = { + [HITGROUP_STOMACH] = 0.9, +} + +TacRP.LoadAtt(ATT, "ammo_sako85_300mag") + +ATT = {} + +ATT.PrintName = "PU" +ATT.FullName = "SVT-40 PU 3.5x Scope" +ATT.Icon = Material("entities/tacrp_att_optic_svt_pu.png", "mips smooth") +ATT.Description = "Low power scope with specialized mount for the SVT-40." +ATT.Pros = {"att.zoom.3.5"} +ATT.Cons = {"stat.aimdownsights"} + +ATT.Category = {"optic_pu"} + +ATT.SortOrder = 5 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/pu.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 3.5 + +ATT.Add_AimDownSightsTime = 0.04 + +ATT.InstalledElements = {"scope"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "optic_svt_pu") + +------------------------------ +-- #region ammo_m202_smoke +------------------------------ +ATT = {} + +ATT.PrintName = "Smoke" +ATT.FullName = "M202 Smoke Rockets" +ATT.Icon = Material("entities/tacrp_att_ammo_m202.png", "mips smooth") +ATT.Description = "Rocket that produces a concealing smokescreen on impact." +ATT.Pros = {"att.procon.smoke"} +ATT.Cons = {"att.procon.noexp"} + +ATT.Category = "ammo_m202" + +ATT.ShootEnt = "tacrp_proj_m202_smoke" + +ATT.InstalledElements = {"smoke"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_m202_smoke") +-- #endregion + +------------------------------ +-- #region ammo_m202_apers +------------------------------ +ATT = {} + +ATT.PrintName = "Hornet" +ATT.FullName = "M202 Hornet Rockets" +ATT.Icon = Material("entities/tacrp_att_ammo_m202.png", "mips smooth") +ATT.Description = "Airburst fragmentation rockets for direct fire anti-personnel use." +ATT.Pros = {"att.procon.radius", "att.procon.proxfuse"} +ATT.Cons = {"stat.damage", "stat.muzzlevelocity"} + +ATT.Category = "ammo_m202" + +ATT.Override_Damage_Max = 60 +ATT.Override_Damage_Min = 60 + +ATT.ShootEnt = "tacrp_proj_m202_apers" +ATT.Mult_ShootEntForce = 0.75 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_m202_apers") +-- #endregion + +------------------------------ +-- #region ammo_m202_harpoon +------------------------------ +ATT = {} + +ATT.PrintName = "Harpoon" +ATT.FullName = "M202 Harpoon Rockets" +ATT.Icon = Material("entities/tacrp_att_ammo_m202.png", "mips smooth") +ATT.Description = "Launch fiery harpoons that do tremendous damage on impact." +ATT.Pros = {"stat.damage", "rating.mobility", "rating.precision"} +ATT.Cons = {"att.procon.noexp"} + +ATT.Category = "ammo_m202" + +ATT.Mult_Spread = 0.5 +ATT.Mult_RecoilSpreadPenalty = 0.65 + +ATT.Override_Damage_Max = 100 +ATT.Override_Damage_Min = 10 + +ATT.Override_Sound_Shoot = "weapons/crossbow/fire1.wav" + +ATT.Override_ShootingSpeedMult = 0.75 +ATT.Override_ReloadSpeedMult = 0.5 + +-- ATT.Override_Num = 1 + +ATT.Ammo = "xbowbolt" + +ATT.ShootEnt = "tacrp_proj_m202_harpoon" +-- ATT.Mult_ShootEntForce = 1.25 +ATT.Mult_Spread = 0.25 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_m202_harpoon") +-- #endregion + + +------------------------------ +-- #region ammo_m202_he +------------------------------ +ATT = {} + +ATT.PrintName = "HEAT" +ATT.FullName = "M202 High-Explosive Anti-Tank Rockets" +ATT.Icon = Material("entities/tacrp_att_ammo_m202.png", "mips smooth") +ATT.Description = "Rocket with an explosive charge." +ATT.Pros = {"att.procon.proj.direct"} +ATT.Cons = {"stat.rpm", "att.procon.radius"} + +ATT.Category = "ammo_m202" + +ATT.ShootEnt = "tacrp_proj_m202_he" + +ATT.Mult_RPM = 0.5 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_m202_he") +-- #endregion + +ATT = {} + +ATT.PrintName = "Sniper" +ATT.FullName = "Sniper Bolt" +ATT.Icon = Material("entities/tacrp_att_optic_irons.png", "mips smooth") +ATT.Description = "Use the sniper bolt without a scope, increasing accuracy." +ATT.Pros = {"stat.spread", "stat.zoom"} +ATT.Cons = {"stat.rpm"} + +ATT.Category = "optic_mosin" +ATT.Free = true + +ATT.SortOrder = 1 + +ATT.Override_ScopeFOV = 90 / 1.5 +ATT.Mult_Spread = 0.333 +ATT.Mult_RPM = 45 / 52 +ATT.Mult_ShootTimeMult = 1.15 + +ATT.Hook_TranslateSequence = function(self, seq) + if seq == "fire" then + return seq .. "_sniper" + elseif seq == "fire_iron" then + return seq .. "_sniper" + elseif seq == "deploy" then + return seq .. "_sniper" + elseif seq == "reload_start" then + return seq .. "_sniper" + elseif seq == "reload_finish" then + return seq .. "_sniper" + end +end + +ATT.InstalledElements = {"bolt"} + +TacRP.LoadAtt(ATT, "optic_mosin_irons") + + +ATT = {} + +ATT.PrintName = "PU" +ATT.FullName = "Mosin-Nagant PU 3.5x Scope" +ATT.Icon = Material("entities/tacrp_att_optic_mosin_pu.png", "mips smooth") +ATT.Description = "Side-mounted low power scope for the Mosin-Nagant." +ATT.Pros = {"stat.spread", "att.zoom.3.5"} +ATT.Cons = {"stat.rpm", "stat.aimdownsights"} + +ATT.Category = "optic_mosin" + +ATT.SortOrder = 3.5 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/pu.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 3.5 + +ATT.Mult_Spread = 0.333 +ATT.Add_AimDownSightsTime = 0.03 +ATT.Mult_RPM = 45 / 52 +ATT.Mult_ShootTimeMult = 1.15 + +ATT.Hook_TranslateSequence = function(self, seq) + if seq == "fire" then + return seq .. "_sniper" + elseif seq == "fire_iron" then + return seq .. "_sniper" + elseif seq == "deploy" then + return seq .. "_sniper" + elseif seq == "reload_start" then + return seq .. "_sniper" + elseif seq == "reload_finish" then + return seq .. "_sniper" + end +end + +ATT.InstalledElements = {"scope1", "bolt"} + +TacRP.LoadAtt(ATT, "optic_mosin_pu") + + +ATT = {} + +ATT.PrintName = "PEM" +ATT.FullName = "Mosin-Nagant 6x PEM Scope" +ATT.Icon = Material("entities/tacrp_att_optic_mosin_pem.png", "mips smooth") +ATT.Description = "Side-mounted sniper scope for the Mosin-Nagant." +ATT.Pros = {"stat.spread", "att.zoom.6"} +ATT.Cons = {"stat.rpm", "stat.aimdownsights"} + +ATT.Category = "optic_mosin" + +ATT.SortOrder = 6 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/pu.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 6 + +ATT.Mult_Spread = 0.333 +ATT.Add_AimDownSightsTime = 0.03 +ATT.Mult_RPM = 45 / 52 +ATT.Mult_ShootTimeMult = 1.15 + +ATT.Hook_TranslateSequence = function(self, seq) + if seq == "fire" then + return seq .. "_sniper" + elseif seq == "fire_iron" then + return seq .. "_sniper" + elseif seq == "deploy" then + return seq .. "_sniper" + elseif seq == "reload_start" then + return seq .. "_sniper" + elseif seq == "reload_finish" then + return seq .. "_sniper" + end +end + +ATT.InstalledElements = {"scope2", "bolt"} + +TacRP.LoadAtt(ATT, "optic_mosin_pem") + + +ATT = {} + +ATT.PrintName = "PE" +ATT.FullName = "Mosin-Nagant 4x PE Scope" +ATT.Icon = Material("entities/tacrp_att_optic_mosin_pe.png", "mips smooth") +ATT.Description = "Top-mounted medium range scope for the Mosin-Nagant." +ATT.Pros = {"stat.spread", "att.zoom.4"} +ATT.Cons = {"stat.rpm", "stat.aimdownsights"} + +ATT.Category = "optic_mosin" + +ATT.SortOrder = 4 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/pu.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 4 + +ATT.Mult_Spread = 0.333 +ATT.Add_AimDownSightsTime = 0.03 +ATT.Mult_RPM = 45 / 52 +ATT.Mult_ShootTimeMult = 1.15 + +ATT.Hook_TranslateSequence = function(self, seq) + if seq == "fire" then + return seq .. "_sniper" + elseif seq == "fire_iron" then + return seq .. "_sniper" + elseif seq == "deploy" then + return seq .. "_sniper" + elseif seq == "reload_start" then + return seq .. "_sniper" + elseif seq == "reload_finish" then + return seq .. "_sniper" + end +end + +ATT.InstalledElements = {"scope3", "bolt"} + +TacRP.LoadAtt(ATT, "optic_mosin_pe") + + +ATT = {} + +ATT.PrintName = "Bayonet" +ATT.FullName = "Mosin-Nagant Spike Bayonet" +ATT.Icon = Material("entities/tacrp_att_muzz_mosin_bayonet.png", "mips smooth") +ATT.Description = "For stabbing fascist scum." +ATT.Pros = {"stat.meleedamage", "stat.meleerange"} +ATT.Cons = {"stat.sightedspeed", "stat.scopedsway", "stat.meleeattacktime"} + +ATT.Category = "muzz_mosin" + +ATT.SortOrder = 999 + +ATT.Add_MeleeRange = 24 +ATT.Mult_MeleeDamage = 2 +ATT.Mult_MeleeAttackTime = 1.25 + +ATT.Mult_SightedSpeedMult = 0.85 +ATT.Add_ScopedSway = 0.1 + +ATT.Hook_TranslateSequence = function(self, seq) + if seq == "melee" then + return seq .. "_bayo" + end +end + +ATT.InstalledElements = {"bayonet"} + +ATT.Override_Sound_MeleeHit = "tacint_extras/mosin/melee_hitworld.ogg" +ATT.Override_Sound_MeleeHitBody = "tacint_extras/mosin/melee_hitbody.ogg" + +TacRP.LoadAtt(ATT, "muzz_mosin_bayonet") + + +ATT = {} + +ATT.PrintName = "SVU Supp." +ATT.FullName = "SVU Suppressor" +ATT.Icon = Material("entities/tacrp_att_muzz_svu_supp.png", "mips smooth") +ATT.Description = "Weapon-specific suppressor that boosts fire rate." +ATT.Pros = {"stat.rpm"} +ATT.Cons = {"stat.recoilstability", "rating.range", "rating.maneuvering"} + +ATT.InstalledElements = {"supp"} + +ATT.ModelOffset = Vector(0.4, 0, -0.05) + +ATT.Category = "muzz_svu" + +ATT.SortOrder = 1 + +ATT.Add_Vol_Shoot = -20 + +ATT.Mult_RPM = 1.15 + +ATT.Mult_RecoilStability = 0.66667 + +ATT.Mult_Range_Max = 0.66667 +ATT.Mult_Range_Min = 0.25 + +ATT.Add_FreeAimMaxAngle = 0.5 +ATT.Add_Sway = 0.1 +ATT.Add_ScopedSway = 0.05 + +ATT.Silencer = true +ATT.Override_MuzzleEffect = "muzzleflash_suppressed" + +TacRP.LoadAtt(ATT, "muzz_svu_supp") + + +ATT = {} + +ATT.PrintName = "Bayonet" +ATT.FullName = "SKS Folding Bayonet" +ATT.Icon = Material("entities/tacrp_att_muzz_sks_bayonet.png", "mips smooth") +ATT.Description = "For stabbing capitalist scum." +ATT.Pros = {"stat.meleedamage", "stat.meleerange"} +ATT.Cons = {"stat.sightedspeed", "stat.scopedsway", "stat.meleeattacktime"} + +ATT.Category = "muzz_sks" +ATT.Free = true + +ATT.SortOrder = 999 + +ATT.Add_MeleeRange = 24 +ATT.Mult_MeleeDamage = 2 +ATT.Mult_MeleeAttackTime = 1.25 + +ATT.Mult_SightedSpeedMult = 0.85 +ATT.Add_ScopedSway = 0.1 + +ATT.InstalledElements = {"bayonet"} + +ATT.Override_Sound_MeleeHit = "tacint_extras/mosin/melee_hitworld.ogg" +ATT.Override_Sound_MeleeHitBody = "tacint_extras/mosin/melee_hitbody.ogg" + +TacRP.LoadAtt(ATT, "muzz_sks_bayonet") + + +------------------------------ +-- #region tac_cz75_mag +------------------------------ +ATT = {} + +ATT.PrintName = "Backup Mag" +ATT.FullName = "CZ-75 Backup Magazine" +ATT.Icon = Material("entities/tacrp_att_tac_cz75_mag.png", "mips smooth") +ATT.Description = "An extra magazine mounted on the gun for peace of mind." +ATT.Pros = {"stat.reloadtime"} +ATT.Cons = {"stat.sightedspeed", "rating.stability"} + +ATT.InstalledElements = {"magazine"} + +ATT.Category = "tactical_cz75" +ATT.Free = true +ATT.SortOrder = 999 + +ATT.Mult_ReloadTimeMult = 0.95 +ATT.Mult_SightedSpeedMult = 0.9 +ATT.Add_Sway = 0.25 +ATT.Add_ScopedSway = 0.1 + +TacRP.LoadAtt(ATT, "tac_cz75_mag") + + +------------------------------ +-- #region barrel_coachgun_short +------------------------------ +ATT = {} + +ATT.PrintName = "Short" +ATT.FullName = "Coachgun Short Barrels" +ATT.Icon = Material("entities/tacrp_att_barrel_coachgun_short.png", "mips smooth") +ATT.Description = "Significantly shortened barrel for close range encounters." +ATT.Pros = {"stat.rpm", "rating.maneuvering", "rating.mobility"} +ATT.Cons = {"stat.spread", "stat.recoilkick", "stat.range"} + +ATT.InstalledElements = {"short"} + +ATT.Category = "barrel_coachgun" +ATT.Free = false +ATT.SortOrder = 999 + +ATT.Mult_MuzzleVelocity = 0.85 +ATT.Mult_RPM = 1.15 +ATT.Add_RecoilKick = 10 +ATT.Mult_RecoilKick = 1.25 +ATT.Add_RecoilVisualKick = 2 +ATT.Add_Spread = 0.03 +ATT.Add_ShotgunPelletSpread = 0.025 +ATT.Add_HipFireSpreadPenalty = -0.005 +ATT.Add_FreeAimMaxAngle = -2.5 +ATT.Add_AimDownSightsTime = -0.18 +ATT.Add_SprintToFireTime = -0.15 +ATT.Mult_DeployTimeMult = 0.75 +ATT.Mult_HolsterTimeMult = 0.75 +ATT.Add_MoveSpeedMult = 0.05 +ATT.Add_SightedSpeedMult = 0.15 +ATT.Add_ShootingSpeedMult = 0.2 +ATT.Mult_Range_Max = 0.5 + +ATT.Override_Sound_Shoot = "^tacint_extras/coachgun/coach_fire1.wav" + +TacRP.LoadAtt(ATT, "barrel_coachgun_short") + + +------------------------------ +-- #region muzz_pistol_comp +------------------------------ +ATT = {} + +ATT.PrintName = "att.muzz_pistol_comp.name" +ATT.Icon = Material("entities/tacrp_att_muzz_pistol_comp.png", "mips smooth") +ATT.Description = "att.muzz_pistol_comp.desc" +ATT.Pros = {"stat.recoil", "stat.spread", "stat.range_min"} +ATT.Cons = {"stat.rpm"} + +ATT.InvAtt = "muzz_pistol_comp" + +ATT.Category = "automag3_muzz" + +ATT.InstalledElements = {"comp"} + +ATT.SortOrder = 1 + +ATT.Mult_RecoilKick = 0.5 +ATT.Mult_RPM = 0.9 +ATT.Mult_Spread = 0.6 +ATT.Add_Range_Min = 500 + +TacRP.LoadAtt(ATT, "muzz_pistol_comp_automag3") + +ATT = {} + +ATT.PrintName = ".30 Carbine" +ATT.FullName = "AutoMag III .30 Carbine Mod Kit" +ATT.Icon = Material("entities/tacrp_att_ammo_automag3_30carbine.png", "mips smooth") +ATT.Description = "Load a carbine cartridge for improved accuracy and range." +ATT.Pros = {"stat.spread", "stat.range", "stat.muzzlevelocity"} +ATT.Cons = {"stat.rpm"} + +ATT.Category = "ammo_automag3" +ATT.Free = true + +ATT.SortOrder = 0 + +ATT.Override_Ammo = "smg1" +ATT.Override_Ammo_Expanded = "ti_pdw" + +ATT.Mult_RPM = 0.85 +ATT.Mult_ShootTimeMult = 1 / 0.85 + +ATT.Add_Spread = -0.004 + +ATT.Add_Range_Min = 500 +ATT.Add_Range_Max = 1500 +ATT.Mult_MuzzleVelocity = 1.5 + +TacRP.LoadAtt(ATT, "ammo_automag3_30carbine") + + +ATT = {} + +ATT.PrintName = "No. 32 Scope" +ATT.FullName = "Lee-Enfield No. 32 Telescope Sight" +ATT.Icon = Material("entities/tacrp_att_optic_mosin_pe.png", "mips smooth") +ATT.Description = "Top-mounted medium range scope for the Lee-Enfield." +ATT.Pros = {"stat.spread", "att.zoom.3.5"} +ATT.Cons = {"stat.reloadtime", "stat.aimdownsights"} + +ATT.Category = "optic_smle" + +ATT.SortOrder = 3.5 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/pu.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 3.5 + +ATT.Mult_Spread = 0.25 +ATT.Add_AimDownSightsTime = 0.03 +ATT.Mult_ReloadTimeMult = 1.1 + + +ATT.InstalledElements = {"scope"} + +TacRP.LoadAtt(ATT, "optic_smle_no32") + + +ATT = {} + +ATT.PrintName = "Mad" +ATT.FullName = "Mad Minute" +ATT.Icon = Material("entities/tacrp_att_bolt_greased.png", "mips smooth") +ATT.Description = "Use an old speed shooting technique to shoot at blazing speeds!" +ATT.Pros = {"stat.rpm"} +ATT.Cons = {"rating.mobility", "stat.recoil"} + +ATT.Category = "bolt_mad" + +ATT.SortOrder = 1 + +ATT.Mult_RPM = 2 +ATT.Mult_ShootTimeMult = 1 / 2 + +ATT.Add_RecoilVisualKick = 2 +ATT.Mult_RecoilKick = 1.25 +ATT.Add_ShootingSpeedMult = -0.2 +ATT.Add_SightedSpeedMult = -0.1 + +TacRP.LoadAtt(ATT, "bolt_mad") +-- #endregion + + +------------------------------ +-- Flare Pistol Ammo +------------------------------ + +ATT = {} + +ATT.PrintName = "Incendiary" +ATT.FullName = "P2A1 Incendiary Cartridges" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_concussion.png", "mips smooth") +ATT.Description = "Flares with a more powerful explosion but no illumination." +ATT.Pros = {"att.procon.radius", "att.procon.incendiary"} +ATT.Cons = {"att.procon.noflare", "stat.muzzlevelocity", "att.procon.armdelay"} + +ATT.Category = "ammo_p2a1" + +ATT.SortOrder = 0 +ATT.Override_ShootEnt = "tacrp_proj_p2a1_incendiary" +ATT.Mult_ShootEntForce = 0.7 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_incendiary") + + +ATT = {} + +ATT.PrintName = "Smoke" +ATT.FullName = "P2A1 Smoke Cartridges" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_smoke.png", "mips smooth") +ATT.Description = "Flares that creates a small smokescreen on impact." +ATT.Pros = {"att.procon.smoke"} +ATT.Cons = {"att.procon.noflare", "att.procon.nonlethal", "stat.muzzlevelocity"} + +ATT.Category = "ammo_p2a1" + +ATT.SortOrder = 1 +ATT.Override_ShootEnt = "tacrp_proj_p2a1_smoke" +ATT.Mult_ShootEntForce = 0.75 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_smoke") + + +ATT = {} + +ATT.PrintName = "Illumination" +ATT.FullName = "P2A1 Illumination Flare Cartridges" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_concussion.png", "mips smooth") +ATT.Description = "White flares with a mini-parachute, lighting up a large area while it falls." +ATT.Pros = {"att.procon.illumradius"} +ATT.Cons = {"att.procon.timedfuse", "att.procon.noexp"} + +ATT.Category = "ammo_p2a1" + +ATT.SortOrder = 0.1 +ATT.Override_ShootEnt = "tacrp_proj_p2a1_paraflare" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_para") + +ATT = {} + +ATT.PrintName = "Medi-Smoke" +ATT.FullName = "P2A1 Medi-Smoke Cartridges" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_smoke.png", "mips smooth") +ATT.Description = "Flares that produce a tiny amount of restorative gas on impact." +ATT.Pros = {"att.procon.heal"} +ATT.Cons = {"att.procon.noflare", "att.procon.nonlethal", "stat.muzzlevelocity"} + +ATT.Category = "ammo_p2a1" +ATT.InvAtt = "ammo_40mm_heal" + +ATT.SortOrder = 1.5 +ATT.Override_ShootEnt = "tacrp_proj_p2a1_heal" +ATT.Mult_ShootEntForce = 0.75 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_heal") + + +ATT = {} + +ATT.PrintName = "Magnum Buck" +ATT.FullName = "P2A1 Magnum Buck Shotshells" +ATT.Icon = Material("entities/tacrp_att_acc_magnum.png", "mips smooth") +ATT.Description = "Cram some shotshells into your flare gun for direct firepower." +ATT.Pros = {"att.procon.direct"} +ATT.Cons = {"stat.spread", "rating.mobility"} + +ATT.Category = "ammo_p2a1" +ATT.InvAtt = "ammo_shotgun_mag" + +ATT.SortOrder = 2 + +ATT.Override_Ammo = "buckshot" +ATT.Override_ShootEnt = false +ATT.Add_RecoilKick = 30 +ATT.Override_Spread = 0.06 +ATT.Override_ShotgunPelletSpread = 0.06 +ATT.Override_Damage_Max = 10 +ATT.Override_Damage_Min = 4 +ATT.Override_Range_Min = 150 +ATT.Override_Range_Max = 1000 +ATT.Override_NoRanger = false +ATT.Override_Num = 12 +ATT.Mult_ShootingSpeedMult = 0.5 + +ATT.Override_Sound_ShootAdd = "^tacrp/weapons/m4star10/fire-2.wav" +ATT.Override_MuzzleEffect = "muzzleflash_shotgun" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_buckshot") + +ATT = {} + +ATT.PrintName = "Birdshot" +ATT.FullName = "P2A1 Bird Shotshells" +ATT.Icon = Material("entities/tacrp_att_acc_bird.png", "mips smooth") +ATT.Description = "Cram some birdshells into your flare gun. Insane spread but hard to miss." +ATT.Pros = {"att.procon.direct", "att.procon.moreproj"} +ATT.Cons = {"stat.spread", "stat.damage", "rating.mobility"} + +ATT.Category = "ammo_p2a1" +ATT.InvAtt = "ammo_shotgun_bird" + +ATT.SortOrder = 3 + +ATT.Override_Ammo = "buckshot" +ATT.Override_ShootEnt = false +ATT.Add_RecoilKick = 18 +ATT.Override_Spread = 0.1 +ATT.Override_ShotgunPelletSpread = 0.08 +ATT.Override_Damage_Max = 5 +ATT.Override_Damage_Min = 2 +ATT.Override_Range_Min = 150 +ATT.Override_Range_Max = 1000 +ATT.Override_NoRanger = false +ATT.Override_Num = 24 +ATT.Mult_ShootingSpeedMult = 0.75 + +ATT.Override_Sound_ShootAdd = "^tacrp/weapons/m4star10/fire-2.wav" +ATT.Override_MuzzleEffect = "muzzleflash_shotgun" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_bird") + + +ATT = {} + +ATT.PrintName = "att.ammo_shotgun_slugs.name" +ATT.FullName = "P2A1 Slug Shotshells" +ATT.Icon = Material("entities/tacrp_att_acc_slugs.png", "mips smooth") +ATT.Description = "Cram slugs into your flare gun. Short barrel limits accuracy and range." +ATT.Pros = {"att.procon.direct", "stat.range"} +ATT.Cons = {"att.procon.1proj", "rating.mobility"} + +ATT.Category = "ammo_p2a1" +ATT.InvAtt = "ammo_shotgun_slugs" + +ATT.SortOrder = 4 + +ATT.Override_Ammo = "buckshot" +ATT.Override_ShootEnt = false +ATT.Add_RecoilKick = 18 +ATT.Override_Spread = 0.01 +ATT.Override_Damage_Max = 75 +ATT.Override_Damage_Min = 30 +ATT.Override_Range_Min = 300 +ATT.Override_Range_Max = 1200 +ATT.Override_NoRanger = false +ATT.Mult_ShootingSpeedMult = 0.5 +ATT.Mult_MuzzleVelocity = 1.25 +ATT.Add_HipFireSpreadPenalty = 0.015 + +ATT.Override_Sound_ShootAdd = "^tacrp/weapons/m4star10/fire-2.wav" +ATT.Override_MuzzleEffect = "muzzleflash_slug" + +ATT.Override_BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_slug") + + +ATT = {} + +ATT.PrintName = "Frag" +ATT.FullName = "P2A1 HE Frag Shotshells" +ATT.Icon = Material("entities/tacrp_att_ammo_frag12.png", "mips smooth") +ATT.Description = "Turn your flare gun into a knockoff grenade pistol." +ATT.Pros = {"att.procon.explosive", "att.procon.radius"} +ATT.Cons = {"att.procon.damage", "stat.muzzlevelocity"} + +ATT.Category = "ammo_p2a1" +ATT.InvAtt = "ammo_shotgun_frag" + +ATT.SortOrder = 5 + +ATT.Override_Ammo = "buckshot" +ATT.Override_ShootEnt = false +ATT.Add_RecoilKick = 18 +ATT.Override_Spread = 0.02 +ATT.Override_Damage_Max = 25 +ATT.Override_Damage_Min = 25 +ATT.Override_ExplosiveEffect = "HelicopterMegaBomb" +ATT.Add_ExplosiveDamage = 50 +ATT.Add_ExplosiveRadius = 256 +ATT.Override_NoRanger = false +ATT.Add_HipFireSpreadPenalty = 0.02 +ATT.Mult_MuzzleVelocity = 0.5 + +ATT.Override_Sound_ShootAdd = "^tacrp/weapons/m4star10/fire-2.wav" +ATT.Override_MuzzleEffect = "muzzleflash_slug" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_frag") + + +ATT = {} + +ATT.PrintName = "Zvezda" +ATT.FullName = "P2A1 Zvezda Flash Shotshells" +ATT.Icon = Material("entities/tacrp_att_ammo_ks23_flashbang.png", "mips smooth") +ATT.Description = "Flashbang dispenser in your pocket. Best used around corners." +ATT.Pros = {"att.procon.flash"} +ATT.Cons = {"att.procon.timedfuse", "att.procon.nonlethal"} + +ATT.SortOrder = 11 +ATT.Category = "ammo_p2a1" +ATT.InvAtt = "ammo_ks23_flashbang" +ATT.Override_Ammo = "buckshot" + +ATT.Override_ShootEnt = "tacrp_proj_ks23_flashbang" +ATT.Override_ShootEntForce = 1500 + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_flashbang") + + +ATT = {} + +ATT.PrintName = "Breach" +ATT.FullName = "P2A1 Breaching Shotshells" +ATT.Icon = Material("entities/tacrp_att_ammo_breaching.png", "mips smooth") +ATT.Description = "Load a specialized breaching slug for pocket door busting." +ATT.Pros = {"att.procon.doorbreach"} +ATT.Cons = {"att.procon.nonlethal"} + +ATT.Category = "ammo_p2a1" +ATT.InvAtt = "ammo_shotgun_breach" +ATT.Override_Ammo = "buckshot" + +ATT.SortOrder = 10 +ATT.Override_ShootEntForce = 2000 +ATT.Override_ShootEnt = "tacrp_proj_breach_slug" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_breach") + +ATT = {} + +ATT.PrintName = "Rocks" +ATT.FullName = "P2A1 Rock Shotshells" +ATT.Icon = Material("entities/tacrp_att_ammo_p2a1_rock.png", "mips smooth") +ATT.Description = "Neolithic buckshot." +ATT.Pros = {"att.procon.throwrocks"} +ATT.Cons = {"att.procon.throwrocks"} + +ATT.Category = "ammo_p2a1" +ATT.Free = true + +ATT.Override_Ammo = "buckshot" +ATT.SortOrder = 90 +ATT.Override_Spread = 0.04 +ATT.Override_ShotgunPelletSpread = 0.04 +ATT.Override_ShootEnt = "tacrp_proj_nade_rock" +ATT.Override_Num = 4 +ATT.Override_Damage_Max = 10 +ATT.Override_Damage_Min = 10 +ATT.Override_Sound_ShootAdd = "physics/concrete/boulder_impact_hard4.wav" +ATT.Mult_ShootEntForce = 1.5 + +-- ATT.Hook_PreShootEnt = function(wep, rocket) +-- rocket.AllowFunny = false +-- end +ATT.Hook_PostShootEnt = function(wep, rocket) + rocket:SetPhysicsAttacker(wep:GetOwner(), 10) + local phys = rocket:GetPhysicsObject() + if phys:IsValid() then + phys:AddAngleVelocity(VectorRand() * 1500) + end +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_rock") + + +ATT = {} + +ATT.PrintName = "Confetti" +ATT.FullName = "P2A1 Confetti Shotshells" +ATT.Icon = Material("entities/tacrp_att_ammo_p2a1_confetti.png", "mips smooth") +ATT.Description = "For celebrations. Yippie!" +ATT.Pros = {} +ATT.Cons = {"att.procon.nonlethal"} + +ATT.Category = "ammo_p2a1" +ATT.Free = true + +ATT.SortOrder = 100 +ATT.Override_Ammo = "buckshot" +ATT.Override_ShootEnt = false +ATT.Override_Num = 0 +ATT.Override_Damage_Max = 0 +ATT.Override_Damage_Min = 0 +ATT.Override_Sound_Shoot = "^tacint_extras/p2a1/confetti.wav" +ATT.Override_Vol_Shoot = 80 +ATT.Override_MuzzleEffect = false + +ATT.Hook_PostShoot = function(wep) + if IsFirstTimePredicted() then + local fx = EffectData() + fx:SetOrigin(wep:GetMuzzleOrigin() + wep:GetShootDir():Forward() * 32) + fx:SetAngles(wep:GetShootDir()) + fx:SetScale(0.6) + fx:SetMagnitude(500) + util.Effect("tacrp_confetti", fx) + wep:EmitSound("tacrp/kids_cheering.mp3", 80, 97, 0.8, CHAN_ITEM) + end + +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_confetti") + + +ATT = {} + +ATT.PrintName = "Signal" +ATT.FullName = "P2A1 Signal Flare Cartridges" +ATT.Icon = Material("entities/tacrp_att_ammo_40mm_concussion.png", "mips smooth") +ATT.Description = "A brighter, longer burning flare for distress signaling." +ATT.Pros = {"stat.muzzlevelocity"} +ATT.Cons = {"att.procon.noexp"} + +ATT.Category = "ammo_p2a1" + +ATT.SortOrder = 0.1 +ATT.Mult_ShootEntForce = 1.5 +ATT.Override_ShootEnt = "tacrp_proj_p2a1_flare_signal" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_p2a1_signal") diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/perk.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/perk.lua new file mode 100644 index 0000000..5395e86 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/perk.lua @@ -0,0 +1,187 @@ +-- perk.lua + +local ATT = {} + +------------------------------ +-- #region perk_aim +------------------------------ +ATT = {} + +ATT.PrintName = "Deadeye" +ATT.Icon = Material("entities/tacrp_att_acc_aim.png", "mips smooth") +ATT.Description = "Zooms in your aim and makes it easier to fire while sighted." +ATT.Pros = {"stat.zoom", "stat.scopedsway", "stat.movespread"} + +ATT.Category = "perk_shooting" + +ATT.SortOrder = 2 + +ATT.Mult_ScopeFOV = 0.75 +ATT.Mult_ScopedSway = 0.25 +ATT.Mult_MoveSpreadPenalty = 0.75 + +TacRP.LoadAtt(ATT, "perk_aim") +-- #endregion + +------------------------------ +-- #region perk_blindfire +------------------------------ +ATT = {} + +ATT.PrintName = "Point Shoot" +ATT.FullName = "Point Shooter" +ATT.Icon = Material("entities/tacrp_att_acc_blindfire.png", "mips smooth") +ATT.Description = "Improves blindfire and peeking." +ATT.Pros = {"stat.peekpenalty", "stat.blindfiresway", "stat.freeaimangle"} + +ATT.Category = {"perk"} + +ATT.SortOrder = 7 + +ATT.Mult_BlindFireSway = 0.25 +ATT.Mult_PeekPenaltyFraction = 0.66667 +ATT.Mult_FreeAimMaxAngle = 0.75 + +TacRP.LoadAtt(ATT, "perk_blindfire") +-- #endregion + +------------------------------ +-- #region perk_hipfire +------------------------------ +ATT = {} + +ATT.PrintName = "Rambo" +ATT.Icon = Material("entities/tacrp_att_acc_hipfire.png", "mips smooth") +ATT.Description = "Improves weapon accuracy while not aiming." +ATT.Pros = {"stat.sway", "stat.hipfirespread"} + +ATT.Category = "perk" + +ATT.SortOrder = 2 + +ATT.Mult_HipFireSpreadPenalty = 0.66667 +ATT.Mult_Sway = 0.66667 + +TacRP.LoadAtt(ATT, "perk_hipfire") +-- #endregion + +------------------------------ +-- #region perk_melee +------------------------------ +ATT = {} + +ATT.PrintName = "Smackdown" +ATT.Icon = Material("entities/tacrp_att_acc_melee.png", "mips smooth") +ATT.Description = "Improves melee damage, and slows struck targets." +ATT.Pros = {"stat.meleedamage", "att.procon.meleeslow"} + +ATT.Category = "perk_melee" + +ATT.SortOrder = 2 + +ATT.Mult_MeleeDamage = 35 / 25 +ATT.MeleeSlow = true + +TacRP.LoadAtt(ATT, "perk_melee") +-- #endregion + +------------------------------ +-- #region perk_reload +------------------------------ +ATT = {} + +ATT.PrintName = "Quickload" +ATT.Icon = Material("entities/tacrp_att_acc_reload.png", "mips smooth") +ATT.Description = "Improves reloading speed." +ATT.Pros = {"stat.reloadtime"} + +ATT.Category = "perk_reload" + +ATT.SortOrder = 2 + +ATT.Mult_ReloadTimeMult = 0.9 + +TacRP.LoadAtt(ATT, "perk_reload") +-- #endregion + +------------------------------ +-- #region perk_shock +------------------------------ +ATT = {} + +ATT.PrintName = "att.melee_boost_shock.name" +ATT.FullName = "att.melee_boost_shock.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_shock.png", "mips smooth") +ATT.Description = "att.melee_boost_shock.desc" +ATT.Pros = {"att.procon.gasimmune", "att.procon.flashresist", "att.procon.stunresist"} + +ATT.Category = {"perk", "perk_passive"} + +ATT.SortOrder = 8 + +ATT.GasImmunity = true +ATT.StunResist = true + +TacRP.LoadAtt(ATT, "perk_shock") +-- #endregion + +------------------------------ +-- #region perk_speed +------------------------------ +ATT = {} + +ATT.PrintName = "Agility" +ATT.Icon = Material("entities/tacrp_att_acc_speed.png", "mips smooth") +ATT.Description = "Improves weapon mobility, especially while reloading." +ATT.Pros = {"stat.movespeed", "stat.reloadspeed"} + +ATT.Category = "perk" + +ATT.SortOrder = 2 + +ATT.Add_MoveSpeedMult = 0.1 +ATT.Add_ReloadSpeedMult = 0.15 + +TacRP.LoadAtt(ATT, "perk_speed") +-- #endregion + +------------------------------ +-- #region perk_throw +------------------------------ +ATT = {} + +ATT.PrintName = "Grenadier" +ATT.Icon = Material("entities/tacrp_att_acc_grenade.png", "mips smooth") +ATT.Description = "Improves quickthrow, and adds the option to throw rocks." +ATT.Pros = {"att.procon.quickthrow", "att.procon.throwrocks"} + +ATT.Category = {"perk", "perk_throw"} + +ATT.SortOrder = 6 + +ATT.ThrowRocks = true +ATT.Mult_QuickNadeTimeMult = 0.5 + +TacRP.LoadAtt(ATT, "perk_throw") +-- #endregion + +------------------------------ +-- #region perk_mlg +------------------------------ +ATT = {} + +ATT.PrintName = "Stylish" +ATT.Icon = Material("entities/tacrp_att_acc_mlg.png", "mips smooth") +ATT.Description = "Improves quickscoping and accuracy while jumping or moving." +ATT.Pros = {"stat.quickscope", "stat.midairspread", "stat.movespread"} + +ATT.Category = "perk_shooting" + +ATT.SortOrder = 9 + +ATT.Mult_QuickScopeSpreadPenalty = 0.25 +ATT.Mult_MidAirSpreadPenalty = 0.25 +ATT.Mult_MoveSpreadPenalty = 0.75 + +TacRP.LoadAtt(ATT, "perk_mlg") +-- #endregion diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/sd.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/sd.lua new file mode 100644 index 0000000..0b4ea4c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/atts_bulk/sd.lua @@ -0,0 +1,387 @@ +local ATT = {} + +------------------------------------------------- +ATT = {} +ATT.PrintName = "att.acc_extmag.name" +ATT.FullName = "att.acc_extmag.name.full" +ATT.Icon = Material("entities/tacrp_att_acc_extmag_shotgun.png", "mips smooth") +ATT.Description = "att.acc_extmag.desc" +ATT.Pros = {"stat.clipsize"} + +ATT.Category = "acc_extmag_a5" + +ATT.InvAtt = "acc_extmag_rifle" + +ATT.SortOrder = 1 + +ATT.Add_ClipSize = 2 + +ATT.InstalledElements = {"extmag"} + +TacRP.LoadAtt(ATT, "acc_extmag_auto5") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "Marksman" +ATT.FullName = "Marksman Trigger" +ATT.Icon = Material("entities/tacrp_att_trigger_semi.png", "mips smooth") +ATT.Description = "Trigger that sacrfices automatic fire for precision." +ATT.Pros = {"stat.damage", "stat.spread", "rating.control"} +ATT.Cons = {"att.procon.semi", "stat.rpm"} + +ATT.InvAtt = "trigger_semi" +ATT.Category = {"trigger_dual_uzis"} + +ATT.SortOrder = 0.1 + +ATT.Override_Firemodes = {1} + +ATT.Add_RecoilStability = 0.3 +ATT.Mult_RecoilKick = 0.75 +ATT.Mult_RecoilSpreadPenalty = 0.85 +ATT.Mult_Spread = 0.75 + +ATT.Add_Damage_Max = 3 +ATT.Add_Damage_Min = 2 + +ATT.Mult_RPM = 0.75 + +TacRP.LoadAtt(ATT, "trigger_dual_uzis_semi") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "Spin" +ATT.FullName = "Revolver Spin" + +ATT.Icon = Material("entities/tacrp_sd_1858.png", "mips smooth") +ATT.Description = "wheeeeeeeeeeeee" +ATT.Pros = {"att.procon.yeehaw"} + +ATT.Category = "tac_1858" +ATT.SortOrder = 1 +ATT.Free = true + +ATT.Override_Sound_ToggleTactical = "" +ATT.CanToggle = true +ATT.CustomTacticalHint = "hint.tac.spin_revolver" + +ATT.Hook_ToggleTactical = function(wep) + if wep:StillWaiting() then return true end + wep:ScopeToggle(0) + wep:PlayAnimation("draw", 1, false, true) + wep:SetNextPrimaryFire(CurTime() + 0.25) + return true +end + +TacRP.LoadAtt(ATT, "tac_1858_spin") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "3.5x Scope" +ATT.FullName = "M1 Carbine 3.5x24 Scope" +ATT.Icon = Material("entities/tacrp_att_optic_carbinescope.png", "mips smooth") +ATT.Description = "Optical scope with specialized mount for the M1 Carbine." +ATT.Pros = {"att.zoom.3.5"} +ATT.Cons = {"stat.aimdownsights"} + +ATT.Category = "optic_m1" + +ATT.SortOrder = 3.5 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/l96.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 3.5 + +ATT.Add_AimDownSightsTime = 0.04 + +ATT.InstalledElements = {"scope"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "optic_m1_scope") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "4x Scope" +ATT.FullName = "De Lisle 4x24 Scope" +ATT.Icon = Material("entities/tacrp_att_optic_m16a2_colt.png", "mips smooth") +ATT.Description = "Optical scope with specialized mount for the De Lisle." +ATT.Pros = {"att.zoom.4"} +ATT.Cons = {"stat.aimdownsights"} + +ATT.Category = "optic_delisle" + +ATT.SortOrder = 0 + +ATT.Override_Scope = true +ATT.Override_ScopeHideWeapon = true +ATT.Override_ScopeOverlay = Material("tacrp/scopes/l96.png", "mips smooth") +ATT.Override_ScopeFOV = 90 / 4 + +ATT.Add_AimDownSightsTime = 0.04 + +ATT.InstalledElements = {"scope"} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "optic_delisle_scope") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "A. Suppressor" +ATT.FullName = "Assassin Suppressor" +ATT.Icon = Material("entities/tacrp_att_muzz_supp_assassin.png", "mips smooth") +ATT.Description = "Extended suppressor improving range significantly at cost of stability." +ATT.Pros = {"stat.vol_shoot", "stat.range", "stat.muzzlevelocity"} +ATT.Cons = {"stat.recoil", "rating.handling"} + +ATT.Model = "models/weapons/tacint_shark/addons/suppressor_assassin.mdl" +ATT.Scale = 1.4 + +ATT.ModelOffset = Vector(0, 0, 0.05) + +ATT.Category = "silencer" + +ATT.SortOrder = 7 + +ATT.Add_Vol_Shoot = -25 +ATT.Add_Range_Min = 250 +ATT.Mult_Range_Max = 1.5 +ATT.Mult_Range_Min = 1.5 +ATT.Add_MuzzleVelocity = 2000 +ATT.Mult_MuzzleVelocity = 1.1 +ATT.Mult_RecoilKick = 1.25 +ATT.Add_RecoilKick = 1 +ATT.Mult_RecoilDissipationRate = 0.75 + +ATT.Add_SprintToFireTime = 0.03 +ATT.Add_AimDownSightsTime = 0.03 +ATT.Add_DeployTimeMult = 0.25 + +ATT.Add_Pitch_Shoot = 5 + +ATT.Silencer = true +ATT.Override_MuzzleEffect = "muzzleflash_suppressed" + +TacRP.LoadAtt(ATT, "muzz_supp_assassin") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "Ratshot" +ATT.FullName = "13mm Ratshot Mini-Rockets" + +ATT.Icon = Material("entities/tacrp_att_ammo_gyrojet.png", "mips smooth") +ATT.Description = "Proximity fuse fragmentation mini-rockets. For rodents of unexpected size." +ATT.Pros = {"att.procon.airburst"} +ATT.Cons = {"stat.damage", "stat.muzzlevelocity", "stat.rpm"} + +ATT.Category = "ammo_gyrojet" + +ATT.SortOrder = 4 + +ATT.Mult_Damage_Max = 0.55 +ATT.Mult_Damage_Min = 0.55 + +ATT.Mult_ShootEntForce = 0.65 + +ATT.Mult_ShootTimeMult = 2.5 +ATT.Mult_RPM = 0.5 + +ATT.ShootEnt = "tacrp_proj_gyrojet_ratshot" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_gyrojet_ratshot") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "Pipe" +ATT.FullName = "15mm Boosted Pipe Grenades" + +ATT.Icon = Material("entities/tacrp_att_ammo_gyrojet.png", "mips smooth") +ATT.Description = "Heavy grenades with timed fuse. Direct hits detonate instantly." +ATT.Pros = {"att.procon.explosive"} +ATT.Cons = {"stat.muzzlevelocity", "stat.clipsize", "stat.rpm"} + +ATT.Category = "ammo_gyrojet" + +ATT.SortOrder = 3 + +ATT.Add_ClipSize = -2 + +ATT.Mult_ShootEntForce = 0.15 + +ATT.Mult_ShootTimeMult = 2.5 +ATT.Mult_RPM = 0.5 + +ATT.ShootEnt = "tacrp_proj_gyrojet_pipe" + +ATT.Mult_Damage_Max = 0.725 +ATT.Mult_Damage_Min = 0.725 + +ATT.Override_BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 1, + [HITGROUP_RIGHTLEG] = 1, + [HITGROUP_GEAR] = 1 +} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_gyrojet_pipe") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "LV" +ATT.FullName = "11mm Low Velocity Mini-Rockets" + +ATT.Icon = Material("entities/tacrp_att_ammo_gyrojet.png", "mips smooth") +ATT.Description = "Projectiles with reduced diameter and velocity, leaving a less visible trail." +ATT.Pros = {"att.procon.reliability", "stat.clipsize", "stat.rpm"} +ATT.Cons = {"stat.damage", "stat.muzzlevelocity"} + +ATT.Category = "ammo_gyrojet" + +ATT.SortOrder = 1 + +ATT.Mult_Damage_Max = 0.6 +ATT.Mult_Damage_Min = 0.6 +ATT.Mult_RPM = 1.25 + +ATT.Add_JamFactor = -0.25 + +ATT.Add_ClipSize = 4 +ATT.Mult_ShootEntForce = 0.85 + +ATT.ShootEnt = "tacrp_proj_gyrojet_lv" + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_gyrojet_lv") + +------------------------------------------------- +ATT = {} +ATT.PrintName = "HE" +ATT.FullName = "13mm High Explosive Mini-Rockets" + +ATT.Icon = Material("entities/tacrp_att_ammo_gyrojet.png", "mips smooth") +ATT.Description = "Projectile with a small explosive charge instead of a bullet head." +ATT.Pros = {"att.procon.explosive"} +ATT.Cons = {"stat.damage", "att.procon.unreliable", "stat.rpm", "stat.muzzlevelocity"} + +ATT.Category = "ammo_gyrojet" + +ATT.SortOrder = 2 + +ATT.Mult_ShootEntForce = 0.85 + +ATT.Mult_Damage_Max = 0.6 +ATT.Mult_Damage_Min = 0.6 + +ATT.Add_JamFactor = 0.5 + +ATT.Mult_ShootTimeMult = 2.5 +ATT.Mult_RPM = 0.5 + +ATT.ShootEnt = "tacrp_proj_gyrojet_he" + +ATT.Override_BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 1, + [HITGROUP_RIGHTLEG] = 1, + [HITGROUP_GEAR] = 1 +} + +if engine.ActiveGamemode() == "terrortown" then + ATT.Free = true +end + +TacRP.LoadAtt(ATT, "ammo_gyrojet_he") + +------------------------------------------------- +ATT = {} +ATT.PrintName = ".45 Colt" +ATT.FullName = "Remington 1858 .45 Colt Conversion" + +ATT.Icon = Material("entities/tacrp_att_ammo_1858_45colt.png", "mips smooth") +ATT.Description = "Cartridge conversion firing larger, more powerful, but less reliable rounds." +ATT.Pros = {"stat.damage", "stat.range_min", "att.procon.armor"} +ATT.Cons = {"rating.control", "att.procon.unreliable"} + +ATT.Category = "ammo_1858" + +ATT.SortOrder = 1 + +ATT.Add_ArmorPenetration = 0.15 + +ATT.Add_Range_Min = 200 +ATT.Add_RecoilVisualKick = 1 +ATT.Add_RecoilKick = 4 +ATT.Mult_RecoilStability = 0.25 +ATT.Mult_MuzzleVelocity = 1.25 + +ATT.Mult_Damage_Max = 1.25 +ATT.Mult_Damage_Min = 1.25 +ATT.Add_Damage_Min = 5 + +ATT.Add_JamFactor = 2 + +ATT.Add_Pitch_Shoot = 7 + +ATT.InstalledElements = {"cylinder"} + +ATT.Override_Sound_Shoot = "^tacint_shark/weapons/1858/nma_fire_45.wav" + +TacRP.LoadAtt(ATT, "ammo_1858_45colt") + +------------------------------------------------- +ATT = {} +ATT.PrintName = ".36 Percussion" +ATT.FullName = "Remington 1858 .36 Caliber Conversion" + +ATT.Icon = Material("entities/tacrp_att_ammo_1858_36perc.png", "mips smooth") +ATT.Description = "Caliber conversion firing smaller rounds with better range." +ATT.Pros = {"stat.range", "stat.recoil", "stat.rpm"} +ATT.Cons = {"stat.damage_max", "att.procon.armor"} + +ATT.Category = "ammo_1858" + +ATT.SortOrder = 2 +ATT.Ammo = "pistol" + +ATT.Mult_ArmorBonus = 0.5 + +ATT.Mult_Range_Max = 1.5 +ATT.Mult_Range_Min = 2 +ATT.Mult_Spread = 0.75 + +ATT.Mult_RecoilVisualKick = 0.75 +ATT.Mult_RecoilKick = 0.5 + +ATT.Mult_Damage_Max = 0.8 + +ATT.Mult_RPM = 1.15 +ATT.Mult_ShootTimeMult = 0.85 + +ATT.Add_Pitch_Shoot = -5 + +TacRP.LoadAtt(ATT, "ammo_1858_36perc") diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/items/_default.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/items/_default.lua new file mode 100644 index 0000000..1f665f4 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/items/_default.lua @@ -0,0 +1,5 @@ +ATT.Name = "Item" +ATT.Description = "Blah Blah Blah" + +ATT.Model = "models/error.mdl" +ATT.Weight = 1 diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_en.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_en.lua new file mode 100644 index 0000000..2f2baa9 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_en.lua @@ -0,0 +1,531 @@ +L = {} -- Русский язык + +L["font.1"] = "Myriad Pro" +L["font.2"] = "HD44780A00 5x8" + +L["tier.0"] = "5Ценность" +L["tier.1"] = "4Потребительский" +L["tier.2"] = "3Охранный" +L["tier.3"] = "2Оперативный" +L["tier.4"] = "1Элитный" +L["tier.5"] = "0Экзотический" + +L["tier.spec"] = "9Специальный" +L["type.sidearm"] = "1Пистолет" +L["type.magnum_pistol"] = "2Магнум" +L["type.machine_pistol"] = "3Пистолет-пулемёт" +L["type.dual_pistol"] = "3Парные пистолеты" +L["type.submachine_gun"] = "3Пистолет-пулемёт" +L["type.assault_rifle"] = "4Штурмовая винтовка" +L["type.machine_gun"] = "5Пулемёт" +L["type.shotgun"] = "5Дробовик" +L["type.sporter_carbine"] = "5Карабин" +L["type.battle_rifle"] = "5Боевая винтовка" +L["type.dmr"] = "6Снайперская винтовка" +L["type.sniper_rifle"] = "7Снайперская винтовка" +L["type.amr"] = "9Противоматериальная винтовка" +L["type.melee_sharp"] = "8Холодное оружие" +L["type.melee_blunt"] = "8Дробящее оружие" +L["type.equipment"] = "9Снаряжение" +L["type.throwable"] = "9Метательное" +L["type.launcher"] = "6Гранатомёт" +L["type.special_weapon"] = "7Спец. оружие" + +L["cust.rating"] = "Рейтинг" +L["cust.stats"] = "Характеристики" +L["cust.description"] = "ОПИСАНИЕ:" +L["cust.type_tier"] = "{type} класса {tier}" +L["cust.trivia"] = "Информация" +L["cust.credits"] = "Авторы" +L["cust.description2"] = "Описание" +L["cust.drop_wep"] = "Выбросить оружие" +L["cust.drop_nade"] = "Выбросить гранату" + +L["unit.hu"] = " ед." +L["unit.meter"] = "м" +L["unit.second"] = "с" +L["unit.persecond"] = "/с" +L["unit.mps"] = "м/с" +L["unit.damage"] = " урона" +L["unit.stk"] = " выстр." +L["unit.ptk"] = " дробь" + +L["slot.default"] = "Слот" +L["slot.optic"] = "Оптика" +L["slot.muzzle"] = "Дуло" +L["slot.tactical"] = "Тактическое" +L["slot.accessory"] = "Аксессуар" +L["slot.bolt"] = "Затвор" +L["slot.trigger"] = "Спуск" +L["slot.ammo"] = "Боеприпасы" +L["slot.perk"] = "Перк" +L["slot.melee_tech"] = "Техника" +L["slot.melee_spec"] = "Особое" +L["slot.melee_boost"] = "Усиление" + +L["ammo.ti_flashbang"] = "Светошумовые гранаты" +L["ammo.ti_thermite"] = "Термитные гранаты" +L["ammo.ti_smoke"] = "Дымовые гранаты" +L["ammo.ti_c4"] = "Заряды C4" +L["ammo.ti_gas"] = "Газовые гранаты" +L["ammo.ti_nuke"] = "Ядерные устройства" +L["ammo.ti_charge"] = "Подрывные заряды" +L["ammo.ti_sniper"] = "Противоматериальные патроны" +L["ammo.ti_heal"] = "Медицинский дым" +L["ammo.ti_pistol_light"] = "Лёгкие пистолетные патроны" +L["ammo.ti_pistol_heavy"] = "Тяжёлые пистолетные патроны" +L["ammo.ti_pdw"] = "Патроны PDW" +L["ammo.ti_rifle"] = "Винтовочные патроны" + +L["ammo.357"] = "Магнум" +L["ammo.smg1"] = "Карабин" +L["ammo.ar2"] = "Винтовка" + +L["rating.score"] = "(Оценка: {score}/{max})" +L["rating.lethality"] = "Летальность" +L["rating.lethality.desc"] = "Насколько легко и быстро оружие может убить цель.\nНе учитывает пробитие брони.\nЗависит от урона и скорострельности." +L["rating.suppression"] = "Подавление" +L["rating.suppression.desc"] = "Сколько урона оружие может нанести за длительный период.\nНе учитывает пробитие брони.\nЗависит от урона, скорострельности, магазина и скорости перезарядки." +L["rating.range"] = "Дальность" +L["rating.range.desc"] = "Насколько хорошо оружие сохраняет урон на дальних дистанциях.\nЗависит от минимальной и максимальной дальности." +L["rating.precision"] = "Точность" +L["rating.precision.desc"] = "Насколько точно оружие при одиночных выстрелах.\nЗависит от разброса и отдачи." +L["rating.control"] = "Контроль" +L["rating.control.desc"] = "Насколько управляема отдача при длительной стрельбе.\nЗависит от скорострельности и отдачи." +L["rating.handling"] = "Обращение" +L["rating.handling.desc"] = "Как быстро оружие готово к стрельбе после бега или прицеливания.\nЗависит от времени прицеливания и доставания." +L["rating.maneuvering"] = "Маневрирование" +L["rating.maneuvering.desc"] = "Насколько точно оружие без прицеливания.\nЗависит от разброса от бедра и раскачки." +L["rating.mobility"] = "Мобильность" +L["rating.mobility.desc"] = "Как быстро игрок может двигаться с этим оружием.\nЗависит от скорости передвижения." +L["rating.stability"] = "Стабильность" +L["rating.stability.desc"] = "Насколько сильно прицел движется.\nЗависит от раскачки." + +L["stat.table.stat"] = "ХАРАКТЕРИСТИКА" +L["stat.table.base"] = "БАЗА" +L["stat.table.curr"] = "ТЕКУЩ" +L["stat.table.diff"] = "РАЗН" + +L["stat.raw_dps"] = "Чистый DPS" +L["stat.raw_dps.desc"] = "Урон в секунду без падения урона на дистанции.\nНе учитывает множители урона по зонам." +L["stat.min_ttk"] = "Минимальное время убийства" +L["stat.min_ttk.desc"] = "Время убийства цели со 100 здоровья.\nНе учитывает падение урона и множители по зонам." +L["stat.armorpenetration"] = "Пробитие брони" +L["stat.armorpenetration.desc"] = "Множитель урона по здоровью при попадании в броню.\nЧем выше значение, тем меньше урона поглощает броня.\n100% = броня игнорируется полностью." +L["stat.armorbonus"] = "Разрушение брони" +L["stat.armorbonus.desc"] = "Множитель разрушения брони.\nПри высоком пробитии брони, разрушается меньше брони.\nПри 100% пробития урон брони не наносится." +L["stat.penetration"] = "Пробитие материалов" +L["stat.penetration.desc"] = "Количество металла, которое может пробить оружие.\nВ зависимости от материала глубина пробития меняется." +L["stat.spread"] = "Разброс" +L["stat.spread.desc"] = "Базовый разброс оружия." +L["stat.rpm"] = "Скорострельность" +L["stat.rpm.desc"] = "Скорость стрельбы в выстрелах в минуту." +L["stat.rpm_burst"] = "Скорострельность (очередь)" +L["stat.rpm_burst.desc"] = "Скорость стрельбы в режиме очереди." +L["stat.rpm_semi"] = "Скорострельность (одиночный)" +L["stat.rpm_semi.desc"] = "Скорость стрельбы в полуавтоматическом режиме." +L["stat.rpm_burst_peak"] = "Пиковая скорострельность" +L["stat.rpm_burst_peak.desc"] = "Максимальная скорострельность с учётом задержки." +L["stat.clipsize"] = "Магазин" +L["stat.clipsize.desc"] = "Количество патронов в магазине." +L["stat.sprinttofire"] = "Время из бега" +L["stat.sprinttofire.desc"] = "Время перехода от бега к стрельбе." +L["stat.aimdownsights"] = "Время прицеливания" +L["stat.aimdownsights.desc"] = "Время перехода к прицеливанию." +L["stat.muzzlevelocity"] = "Начальная скорость" +L["stat.muzzlevelocity.desc"] = "Скорость полёта пули.\nТакже определяет дистанцию хитскана." +L["stat.recoilkick"] = "Отдача" +L["stat.recoilkick.desc"] = "Сила отдачи, смещающей прицел." +L["stat.recoilstability"] = "Стабильность отдачи" +L["stat.recoilstability.desc"] = "Насколько предсказуема отдача.\nБольшее значение = более вертикальная отдача." +L["stat.recoilspread"] = "Разброс от отдачи" +L["stat.recoilspread.desc"] = "Дополнительный разброс за единицу накопления.\nБольшее значение = больше неточности при стрельбе." +L["stat.recoilspread2"] = "Усиление отдачи" +L["stat.recoilspread2.desc"] = "Дополнительная отдача за единицу накопления.\nБольшее значение = сильнее отдача при длительной стрельбе." +L["stat.recoildissipation"] = "Восстановление" +L["stat.recoildissipation.desc"] = "Скорость снижения накопления.\nБольшее значение = быстрее исчезает накопление." +L["stat.recoilresettime"] = "Задержка восстановления" +L["stat.recoilresettime.desc"] = "Задержка перед началом восстановления.\nБольшее значение = дольше ждать между выстрелами." +L["stat.recoilmaximum"] = "Максимальное накопление" +L["stat.recoilmaximum.desc"] = "Максимальное накопление при непрерывной стрельбе." +L["stat.recoilfirstshot"] = "Начальное накопление" +L["stat.recoilfirstshot.desc"] = "Множитель накопления для первого выстрела.\nПрименяется при нулевом накоплении." +L["stat.recoilpershot"] = "Накопление за выстрел" +L["stat.recoilpershot.desc"] = "Сколько накопления добавляется за выстрел." +L["stat.recoilcrouch"] = "Отдача в приседе" +L["stat.recoilcrouch.desc"] = "Множитель отдачи в приседе без движения." +L["stat.movespeed"] = "Скорость движения" +L["stat.movespeed.desc"] = "Множитель скорости с оружием в руках.\nНе замедляет при опущенном оружии." +L["stat.shootingspeed"] = "Скорость при стрельбе" +L["stat.shootingspeed.desc"] = "Множитель скорости при стрельбе.\nНакопление отдачи усиливает замедление." +L["stat.sightedspeed"] = "Скорость при прицеливании" +L["stat.sightedspeed.desc"] = "Множитель скорости при прицеливании." +L["stat.reloadspeed"] = "Скорость перезарядки" +L["stat.reloadspeed.desc"] = "Множитель скорости при перезарядке." +L["stat.meleespeed"] = "Скорость ближнего боя" +L["stat.meleespeed.desc"] = "Множитель скорости при атаке в ближнем бою." +L["stat.reloadtime"] = "Время перезарядки" +L["stat.reloadtime.desc"] = "Время, необходимое для перезарядки." +L["stat.deploytime"] = "Время доставания" +L["stat.deploytime.desc"] = "Время доставания оружия." +L["stat.holstertime"] = "Время убирания" +L["stat.holstertime.desc"] = "Время убирания оружия." +L["stat.sway"] = "Раскачка" +L["stat.sway.desc"] = "Раскачка от бедра. Влияет на направление стрельбы,\nне меняя направление прицеливания." +L["stat.scopedsway"] = "Раскачка при прицеливании" +L["stat.scopedsway.desc"] = "Раскачка при прицеливании. Влияет на направление стрельбы,\nне меняя направление прицеливания." +L["stat.swaycrouch"] = "Раскачка в приседе" +L["stat.swaycrouch.desc"] = "Множитель раскачки в приседе." +L["stat.midairspread"] = "Разброс в воздухе" +L["stat.midairspread.desc"] = "Неточность без опоры под ногами.\nПрименяется наполовину при плавании и на лестницах." +L["stat.hipfirespread"] = "Разброс от бедра" +L["stat.hipfirespread.desc"] = "Неточность при стрельбе от бедра." +L["stat.movespread"] = "Разброс при движении" +L["stat.movespread.desc"] = "Неточность при движении.\nИнтенсивность зависит от скорости." +L["stat.meleedamage"] = "Урон ближнего боя" +L["stat.meleedamage.desc"] = "Урон от атаки в ближнем бою." +L["stat.firemode"] = "Режим огня" +L["stat.firemode.desc"] = "Возможности стрельбы оружия." +L["stat.freeaimangle"] = "Угол свободного прицеливания" +L["stat.freeaimangle.desc"] = "Максимальное отклонение от направления прицеливания от бедра." +L["stat.shotstofail"] = "Выстрелов до отказа" +L["stat.shotstofail.desc"] = "Среднее количество выстрелов до заклинивания." +L["stat.recoilburst"] = "Отдача (очередь)" +L["stat.recoilburst.desc"] = "Множитель отдачи в режиме очереди." +L["stat.recoilsemi"] = "Отдача (одиночный)" +L["stat.recoilsemi.desc"] = "Множитель отдачи в полуавтоматическом режиме." + +L["stat.meleeperkstr"] = "Сила" +L["stat.meleeperkstr.desc"] = "Атрибут ближнего боя, влияющий на техники.\nВлияет на урон и отбрасывание." +L["stat.meleeperkagi"] = "Ловкость" +L["stat.meleeperkagi.desc"] = "Атрибут ближнего боя, влияющий на техники.\nВлияет на скорость движения и интервал атаки." +L["stat.meleeperkint"] = "Стратегия" +L["stat.meleeperkint.desc"] = "Атрибут ближнего боя, влияющий на техники.\nВлияет на скорость восстановления и силу снарядов." + +L["rating.meleeattacktime"] = "Скорость" +L["rating.meleeattacktime.desc"] = "Как быстро оружие может атаковать." +L["stat.damage.desc_melee"] = "Урон от основных атак." +L["stat.meleeattacktime"] = "Задержка атаки" +L["stat.meleeattacktime.desc"] = "Время между атаками при попадании." +L["stat.meleeattackmisstime"] = "Задержка промаха" +L["stat.meleeattackmisstime.desc"] = "Время между атаками при промахе." +L["stat.meleerange"] = "Дальность атаки" +L["stat.meleerange.desc"] = "Дальность атаки оружия." +L["stat.meleedelay"] = "Замах" +L["stat.meleedelay.desc"] = "Задержка между началом и попаданием атаки." + +L["stat.melee2damage"] = "Урон (Т)" +L["stat.melee2damage.desc"] = "Урон от тяжёлых атак." +L["stat.melee2attacktime"] = "Задержка атаки (Т)" +L["stat.melee2attacktime.desc"] = "Время между тяжёлыми атаками при попадании." +L["stat.melee2attackmisstime"] = "Задержка промаха (Т)" +L["stat.melee2attackmisstime.desc"] = "Время между тяжёлыми атаками при промахе." +L["stat.meleethrowdamage"] = "Урон (Бросок)" +L["stat.meleethrowdamage.desc"] = "Урон от брошенного оружия." +L["stat.meleethrowvelocity"] = "Скорость броска" +L["stat.meleethrowvelocity.desc"] = "Скорость полёта брошенного оружия." +L["stat.meleethrowtime"] = "Задержка броска" +L["stat.meleethrowtime.desc"] = "Время между бросками оружия." +L["stat.lifesteal"] = "Здоровье от урона" +L["stat.lifesteal.desc"] = "Доля нанесённого урона, превращаемая в здоровье." +L["stat.damagecharge"] = "Заряд от урона" +L["stat.damagecharge.desc"] = "Доля нанесённого урона, превращаемая в заряд." + +L["stat.damage"] = "Урон" +L["stat.damage.desc"] = "Урон на пулю на всех дистанциях." +L["stat.damage_max"] = "Урон вблизи" +L["stat.damage_max.desc"] = "Урон на пулю без падения урона." +L["stat.damage_min"] = "Урон вдали" +L["stat.damage_min.desc"] = "Урон на пулю при максимальном падении урона." +L["stat.explosivedamage"] = "Взрывной урон" +L["stat.explosivedamage.desc"] = "Урон от взрыва при попадании пули." +L["stat.explosiveradius"] = "Радиус взрыва" +L["stat.explosiveradius.desc"] = "Радиус взрыва при попадании пули." +L["stat.range_max"] = "Максимальная дальность" +L["stat.range_max.desc"] = "Дистанция максимального падения урона." +L["stat.range_min"] = "Минимальная дальность" +L["stat.range_min.desc"] = "Дистанция начала падения урона." +L["stat.postburstdelay"] = "Задержка очереди" +L["stat.postburstdelay.desc"] = "Время между очередями выстрелов." +L["stat.ammopershot"] = "Патронов за выстрел" +L["stat.ammopershot.desc"] = "Количество патронов за выстрел." +L["stat.num"] = "Количество пуль" +L["stat.num.desc"] = "Количество пуль за выстрел." +L["stat.peekpenalty"] = "Штраф выглядывания" +L["stat.peekpenalty.desc"] = "Разброс и раскачка при выглядывании." +L["stat.quickscope"] = "Штраф быстрого прицеливания" +L["stat.quickscope.desc"] = "Неточность при быстром прицеливании.\nШтраф исчезает за 0.2 секунды." +L["stat.vol_shoot"] = "Громкость" +L["stat.vol_shoot.desc"] = "Насколько слышен звук выстрела." +L["stat.damagemultnpc"] = "Урон по NPC" +L["stat.damagemultnpc.desc"] = "Урон по NPC и ботам умножается на это значение." + +L["stat.swaycrouch"] = "Раскачка в приседе" +L["stat.recoil"] = "Отдача" +L["stat.range"] = "Дальность" +L["stat.blindfiresway"] = "Раскачка вслепую" +L["stat.zoom"] = "Зум прицела" +L["stat.bloomintensity"] = "Интенсивность накопления" + +L["att.none"] = "Н/Д" + +L["att.procon.3proj"] = "Три снаряда" +L["att.procon.moreproj"] = "Больше снарядов" +L["att.procon.lessproj"] = "Меньше снарядов" +L["att.procon.1proj"] = "Один снаряд" +L["att.procon.noexp"] = "Без взрыва" +L["att.procon.direct"] = "Прямой огонь" +L["att.procon.doorbreach"] = "Взлом дверей" +L["att.procon.crowd"] = "Контроль толпы" +L["att.procon.nonlethal"] = "Несмертельное" +L["att.procon.detdelay"] = "Задержка детонации" +L["att.procon.flash"] = "Ослепление" +L["att.procon.airburst"] = "Воздушный взрыв" +L["att.procon.timedfuse"] = "Таймер взрыва" +L["att.procon.smoke"] = "Дымовая завеса" +L["att.procon.limb"] = "Урон по конечностям" +L["att.procon.head"] = "Урон в голову" +L["att.procon.chest"] = "Урон в торс" +L["att.procon.onebullet"] = "Одна пуля" +L["att.procon.armor"] = "Эффективность брони" +L["att.procon.nosafety"] = "Без предохранителя" +L["att.procon.radius"] = "Радиус взрыва" +L["att.procon.needprime"] = "Слабее без взведения" +L["att.procon.projrng"] = "Случайная траектория и урон" +L["att.procon.failrng"] = "Шанс отказа" +L["att.procon.notracer"] = "Скрытые трассеры" +L["att.procon.refund"] = "Высокий шанс возврата патронов" +L["att.procon.unreliable"] = "Ненадёжное" +L["att.procon.surplusboost1"] = "Иногда стреляет быстрее" +L["att.procon.surplusboost2"] = "Также неконтролируемо" +L["att.procon.meleeslow"] = "Замедление при ударе" +L["att.procon.gasimmune"] = "Иммунитет к газу" +L["att.procon.flashresist"] = "Сопротивление ослеплению" +L["att.procon.stunresist"] = "Сокращение оглушения" +L["att.procon.quickthrow"] = "Быстрый бросок" +L["att.procon.throwrocks"] = "Бросок камней" +L["att.procon.cornershot"] = "Обзор из-за угла" +L["att.procon.thermal"] = "Тепловизор при выглядывании" +L["att.procon.dmic"] = "Отображение врагов" +L["att.procon.audible"] = "Слышно другим" +L["att.procon.flashlight"] = "Освещение" +L["att.procon.blind"] = "Ослепление при взгляде" +L["att.procon.visible"] = "Видно другим" +L["att.procon.laser"] = "Указатель точки прицеливания" +L["att.procon.rf1"] = "Отображение дистанции и урона" +L["att.procon.rf2"] = "Визуализация падения пули" +L["att.procon.gauge1"] = "Отображение параметров" +L["att.procon.gauge2"] = "Визуализация разброса" +L["att.procon.auto"] = "Автоматический огонь" +L["att.procon.burst"] = "Очередь" +L["att.procon.semi"] = "Полуавтоматический" +L["att.procon.autoburst"] = "Автоматическая очередь" +L["att.procon.explosive"] = "Взрывное" +L["att.procon.reliability"] = "Надёжность" +L["att.procon.noscope"] = "Без прицела" +L["att.procon.conceal"] = "Скрытое в кобуре" +L["att.procon.armdelay"] = "Задержка взведения" +L["att.procon.proxfuse"] = "Датчик близости" +L["att.procon.magnifier"] = "Переменное увеличение" +L["att.procon.needscope"] = "Требуется оптика" +L["att.procon.bullet"] = "Запасная пуля" +L["att.procon.nopartialreloads"] = "Без частичной перезарядки" +L["att.procon.incendiary"] = "Поджог целей" +L["att.procon.blurpeek"] = "Размытие при выглядывании" +L["att.procon.aimrecoil"] = "Отдача (при прицеливании)" +L["att.procon.aimspread"] = "Разброс (при прицеливании)" +L["att.procon.aimrpm"] = "Скорострельность (при прицеливании)" +L["att.procon.firstround"] = "Первый выстрел - несмертельный снаряд" +L["att.procon.locktime"] = "Время захвата" +L["att.procon.semiactive"] = "Требуется постоянное наведение" +L["att.procon.proj.turn"] = "Маневренность снаряда" +L["att.procon.proj.speed"] = "Максимальная скорость снаряда" +L["att.procon.proj.direct"] = "Урон при попадании" +L["att.procon.shovel"] = "Стреляет лопатами" +L["att.procon.heal"] = "Лечебный эффект" +L["att.procon.infiniteammo"] = "Бесконечные патроны" + +L["att.sight.1"] = "Прицел 1x" +L["att.sight.1.25"] = "Прицел 1.25x" +L["att.sight.1.5"] = "Прицел 1.5x" +L["att.sight.1.75"] = "Прицел 1.75x" +L["att.zoom.2"] = "Зум 2x" +L["att.zoom.2.2"] = "Зум 2.2x" +L["att.zoom.3"] = "Зум 3x" +L["att.zoom.3.4"] = "Зум 3.4x" +L["att.zoom.3.5"] = "Зум 3.5x" +L["att.zoom.4"] = "Зум 4x" +L["att.zoom.5"] = "Зум 5x" +L["att.zoom.6"] = "Зум 6x" +L["att.zoom.8"] = "Зум 8x" +L["att.zoom.10"] = "Зум 10x" +L["att.zoom.12"] = "Зум 12x" + +L["spacer.damage"] = "Поражающий эффект" +L["spacer.damage.desc"] = "Характеристики урона и эффективной дальности." +L["spacer.action"] = "Действие" +L["spacer.action.desc"] = "Характеристики заряжания и перезарядки." +L["spacer.ballistics"] = "Баллистика" +L["spacer.ballistics.desc"] = "Характеристики траектории пули и пробития." +L["spacer.recoilbloom"] = "Отдача и накопление" +L["spacer.recoilbloom.desc"] = "Характеристики отдачи при длительной стрельбе." +L["spacer.mobility"] = "Мобильность" +L["spacer.mobility.desc"] = "Характеристики скорости передвижения." +L["spacer.maneuvering"] = "Маневрирование" +L["spacer.maneuvering.desc"] = "Характеристики без прицеливания." +L["spacer.handling"] = "Обращение" +L["spacer.handling.desc"] = "Характеристики готовности оружия." +L["spacer.sway"] = "Раскачка" +L["spacer.sway.desc"] = "Характеристики отклонения от точки прицеливания." +L["spacer.misc"] = "Разное" +L["spacer.misc.desc"] = "Прочие характеристики." + +L["trivia.year"] = "Год производства" +L["trivia.caliber"] = "Калибр" +L["trivia.manufacturer"] = "Производитель" +L["trivia.faction"] = "Фракция" +L["trivia.unknown"] = "Неизвестно" + +L["faction.coalition"] = "Коалиция" +L["faction.militia"] = "Милиция" +L["faction.neutral"] = "Нейтральные" + +L["menu.legacy"] = "Старое меню" + +L["hint.swap"] = "Заменить {weapon} на {weapon2}" +L["hint.pickup"] = "Подобрать {weapon}" +L["hint.unjam"] = "Устранить заклинивание" +L["hint.rp_biocode_cp"] = "Биокод - только для полиции" + +L["hint.melee"] = "Ближняя атака" +L["hint.melee.block"] = "Блок" +L["hint.melee.throw"] = "Бросок" +L["hint.melee.light"] = "Лёгкая атака" +L["hint.melee.heavy"] = "Тяжёлая атака" + +L["hint.peek.hold"] = "Выглянуть" +L["hint.peek.toggle"] = "Переключить выглядывание" +L["hint.hold_breath"] = "Задержать дыхание" +L["hint.customize"] = "Настроить" +L["hint.inspect"] = "Осмотреть" +L["hint.firemode"] = "Режим огня" +L["hint.safety.turn_off"] = "Снять с предохранителя" +L["hint.safety.turn_on"] = "Поставить на предохранитель" +L["hint.hl2_flashlight"] = "Фонарик костюма" +L["hint.toggle_tactical"] = "Переключить {1}" +L["hint.blindfire"] = "Стрельба вслепую" +L["hint.blindfire.menu"] = "Меню слепой стрельбы" +L["hint.blindfire.up"] = "Стрельба вверх" +L["hint.blindfire.left"] = "Стрельба влево" +L["hint.blindfire.right"] = "Стрельба вправо" +L["hint.blindfire.cancel"] = "Отменить слепую стрельбу" +L["hint.blindfire.kys"] = "Самоубийство" +L["hint.quicknade.over"] = "Бросок сверху" +L["hint.quicknade.under"] = "Бросок снизу" +L["hint.quicknade.throw"] = "Быстрый бросок" +L["hint.quicknade.pull_out"] = "Достать гранату" +L["hint.quicknade.menu"] = "Меню гранат" +L["hint.quicknade.next"] = "Следующая граната" +L["hint.quicknade.prev"] = "Предыдущая граната" +L["hint.ttt.radio"] = "TTT Радио" +L["hint.ttt.shop"] = "TTT Магазин" +L["hint.tac"] = "Тактическое" +L["hint.tac.radar"] = "Радар" +L["hint.tac.flashlight"] = "Фонарик" +L["hint.tac.laser"] = "Лазер" +L["hint.tac.combo"] = "Комбо" +L["hint.tac.rangefinder"] = "Дальномер" +L["hint.tac.spread_gauge"] = "Индикатор" +L["hint.tac.magnifier"] = "Увеличение" +L["hint.tac.cam_mode"] = "Режим камеры" +L["hint.tac.load_one"] = "Зарядить один патрон" + +L["hint.melee_lunge"] = "Выпад" +L["hint.melee_step"] = "Рывок в воздухе" +L["hint.melee_ninja.palm"] = "Удар ладонью" +L["hint.melee_ninja.backhop"] = "Прыжок назад" +L["hint.melee_ninja.divekick"] = "Удар в прыжке" + +L["hint.medkit.self"] = "Лечить себя" +L["hint.medkit.others"] = "Лечить других" + +L["hint.unlock"] = "Разблокировать" +L["hint.exitcustmenu"] = "Выйти из меню настройки" + +L["hint.nogrenades"] = "Нет гранат" + +L["hint.hold"] = "(Удерживать)" + +L["hint.shootself"] = "Выстрелить в себя" + +L["hint.melee_charge"] = "Рывок" +L["hint.melee_charge.menu"] = "Тип рывка" +L["hint.melee_charge.0.name"] = "Смелость" +L["hint.melee_charge.0.desc"] = "Стандартный режим со средним контролем поворота.\nТаран цели наносит урон и отбрасывание. Атака во время рывка отменяет его." +L["hint.melee_charge.1.name"] = "Мастерство" +L["hint.melee_charge.1.desc"] = "Значительно улучшенный контроль поворота.\nНизкая защита и урон тарана.\nПолучение урона сокращает длительность рывка." +L["hint.melee_charge.2.name"] = "Упорство" +L["hint.melee_charge.2.desc"] = "Увеличенная скорость рывка, сниженный контроль поворота.\nОчень высокая защита и урон тарана.\nИммунитет к отбрасыванию во время рывка." + +L["quicknade.fuse"] = "Взрыватель" +L["quicknade.ti_thermite.dettype"] = "Контактный" +L["quicknade.ti_flashbang.dettype"] = "2.5 сек" +L["quicknade.ti_c4.dettype"] = "Дистанционный" +L["quicknade.ti_smoke.dettype"] = "2 сек" +L["quicknade.ti_gas.dettype"] = "2 сек" +L["quicknade.ti_nuke.dettype"] = "10 сек" +L["quicknade.ti_charge.dettype"] = "Дистанционный" +L["quicknade.ti_heal.dettype"] = "Контактный" + +L["unknown"] = "Неизвестно" + +L["caliber.nato"] = "НАТО" +L["caliber.warpac"] = "Варшавский договор" + +L["caliber.22lr"] = ".22 LR" +L["caliber.9x18"] = "9×18мм" +L["caliber.9x19"] = "9×19мм" +L["caliber.380"] = ".380 ACP" +L["caliber.45acp"] = ".45 ACP" +L["caliber.10mm"] = "10мм Auto" +L["caliber.44mag"] = ".44 Magnum" +L["caliber.50ae"] = ".50 AE" +L["caliber.454"] = ".454 Casull" +L["caliber.500"] = ".500 S&W" +L["caliber.357"] = ".357 Magnum" +L["caliber.38"] = ".38 Special" +L["caliber.410"] = ".410 Bore" +L["caliber.57x28"] = "5.7×28мм" +L["caliber.46x30"] = "4.6×30мм" +L["caliber.20g"] = "20 Калибр" +L["caliber.12g"] = "12 Калибр" +L["caliber.10g"] = "10 Калибр" +L["caliber.4g"] = "4 Калибр" +L["caliber.25mm"] = "25мм" +L["caliber.303"] = ".303 British" +L["caliber.50bmg"] = ".50 BMG" +L["caliber.762x25"] = "7.62×25мм" +L["caliber.762x39"] = "7.62×39мм" +L["caliber.762x51"] = "7.62×51мм" +L["caliber.762x54"] = "7.62×54мм" +L["caliber.545x39"] = "5.45×39мм" +L["caliber.556x45"] = "5.56×45мм" +L["caliber.300blk"] = ".300 Blackout" +L["caliber.40mm"] = "40мм" +L["caliber.rocket"] = "Ракета" +L["caliber.rpg7"] = "РПГ-7" +L["caliber.at4"] = "AT4 HEDP" +L["caliber.grenade"] = "Граната" +L["caliber.grenade_frag"] = "Осколочная граната" +L["caliber.tack"] = "Кнопка" +L["caliber.rock"] = "Камень" +L["caliber.explosive"] = "Взрывчатка" +L["caliber.357sig"] = ".357 SIG" +L["caliber.9x39"] = "9×39мм" +L["caliber.6mmcreed"] = "6мм Creedmoor" +L["caliber.338"] = ".338 Lapua" +L["caliber.40sw"] = ".40 S&W" +L["caliber.22tcm"] = ".22 TCM" + +STL = {} +STL["unknown"] = "Неизвестно" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_pl.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_pl.lua new file mode 100644 index 0000000..8ff510d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_pl.lua @@ -0,0 +1,543 @@ +L = {} -- Polish Localization by meerfi + +L["font.1"] = "Myriad Pro" +L["font.2"] = "HD44780A00 5x8" + +-- L["tier.0"] = "5Value" +L["tier.1"] = "4Przemysłowa Jakość" +L["tier.2"] = "3Wojskowa Jakość" +L["tier.3"] = "2Poufny" +L["tier.4"] = "1Elitarny" +-- L["tier.5"] = "0Exotic" + +L["tier.spec"] = "9Specjalny" +L["type.sidearm"] = "1Boczna Broń" +L["type.magnum_pistol"] = "2Magnum Pistolet" +L["type.machine_pistol"] = "3Maszynowy Pistolet" +-- L["type.dual_pistol"] = "3Akimbos" +L["type.submachine_gun"] = "3Pistolet Maszynowy" +L["type.assault_rifle"] = "4Karabin Szturmowy" +L["type.machine_gun"] = "5Karabin Maszynowy" +L["type.shotgun"] = "5Strzelba" +L["type.sporter_carbine"] = "5Karabinek Sportowy" +-- L["type.battle_rifle"] = "5Battle Rifle" +-- L["type.dmr"] = "6Marksman Rifle" +L["type.sniper_rifle"] = "7Karabin Snajperski" +L["type.amr"] = "9Karabin Przeciwpancerny" +L["type.melee_sharp"] = "8Broń Sieczna" +L["type.melee_blunt"] = "8Broń Tępa" +L["type.equipment"] = "9Sprzęt" +L["type.throwable"] = "9Rzutka" +L["type.launcher"] = "6Wyrzutnia" +L["type.special_weapon"] = "7Broń Specjalna" + +L["cust.rating"] = "Ocena" +L["cust.stats"] = "Statystyki" +L["cust.description"] = "OPIS:" -- używane w tekście w liniach +L["cust.type_tier"] = "{tier} Klasa {type}" +L["cust.trivia"] = "Ciekawostki" +L["cust.credits"] = "Twórcy" +L["cust.description2"] = "Opis" -- używane jako tytuł +L["cust.drop_wep"] = "Upuść Broń" +L["cust.drop_nade"] = "Upuść Granat" + +L["unit.hu"] = " HU" -- jednostka młotka +L["unit.meter"] = "m" +L["unit.second"] = "s" +L["unit.persecond"] = "/s" +L["unit.mps"] = "m/s" -- metry na sekundę +L["unit.damage"] = " DMG" +L["unit.stk"] = " STK" -- strzały do zabicia +L["unit.ptk"] = " PTK" -- kulki do zabicia + +L["slot.default"] = "Miejsce" +L["slot.optic"] = "Optyczny" +L["slot.muzzle"] = "Wylot" +L["slot.tactical"] = "Taktyczny" +L["slot.accessory"] = "Dodatek" +L["slot.bolt"] = "Zamek" +L["slot.trigger"] = "Spust" +L["slot.ammo"] = "Amunicja" +L["slot.perk"] = "Atut" +L["slot.melee_tech"] = "Technika" +L["slot.melee_spec"] = "Specjalny" +-- L["slot.melee_boost"] = "Boost" + +L["ammo.ti_flashbang"] = "Granaty Błyskowe" +L["ammo.ti_thermite"] = "Granaty Zapalające" +L["ammo.ti_smoke"] = "Granaty Dymne" +L["ammo.ti_c4"] = "Ładunki C4" +L["ammo.ti_gas"] = "Granaty z Gazem CS" +L["ammo.ti_nuke"] = "Urządzenia Nuklearne" +L["ammo.ti_charge"] = "Ładunek Wtargnięcia" +L["ammo.ti_sniper"] = "Amunicja Przeciwmateriałowa" +-- L["ammo.ti_heal"] = "Medi-Smoke Canisters" +-- L["ammo.ti_pistol_light"] = "Light Pistol Ammo" +-- L["ammo.ti_pistol_heavy"] = "Heavy Pistol Ammo" +-- L["ammo.ti_pdw"] = "PDW Ammo" +-- L["ammo.ti_rifle"] = "Heavy Rifle Ammo" +-- L["ammo.ti_sniper"] = "Sniper Ammo" + +-- used when tacrp_ammonames 1 +-- L["ammo.357"] = "Magnum Ammo" +-- L["ammo.smg1"] = "Carbine Ammo" +-- L["ammo.ar2"] = "Rifle Ammo" + +L["rating.score"] = "(Wynik: {score}/{max})" +L["rating.lethality"] = "Śmiertelność" +L["rating.lethality.desc"] = "Jak łatwo i szybko broń może zlikwidować pojedynczy cel.\nNie uwzględnia penetracji pancerza.\nWpływa na to obrażenia i RPM." +L["rating.suppression"] = "Tłumienie" +L["rating.suppression.desc"] = "Ile obrażeń broń może zadać w dłuższym okresie czasu.\nNie uwzględnia penetracji pancerza.\nWpływa na to obrażenia, RPM, pojemność i czas przeładowania." +L["rating.range"] = "Zasięg" +L["rating.range.desc"] = "Jak dobrze broń zyskuje lub traci obrażenia na długich dystansach.\nWpływa na to minimalny zasięg, maksymalny zasięg i spadek obrażeń." +L["rating.precision"] = "Precyzja" +L["rating.precision.desc"] = "Jak celna jest broń podczas strzelania pojedynczymi strzałami lub krótkimi seriami.\nWpływa na to rozrzut i różne statystyki odrzutu." +L["rating.control"] = "Kontrola" +L["rating.control.desc"] = "Jak łatwo jest zarządzać odrzutem i rozrzutem broni podczas ciągłego ognia.\nWpływa na to RPM i różne statystyki odrzutu." +L["rating.handling"] = "Obsługa" +L["rating.handling.desc"] = "Jak szybko ta broń jest gotowa do użycia po biegu, celowaniu i rozkładaniu.\nWpływa na to czas celowania, czas przejścia od biegu do ognia i czas rozkładania." +L["rating.maneuvering"] = "Manewrowość" +L["rating.maneuvering.desc"] = "Jak celna jest broń podczas strzelania bez celowania.\nWpływa na to rozrzut podczas strzelania z biodra, rozrzut w powietrzu, chwieje i kąt swobodnego celowania." +L["rating.mobility"] = "Mobilność" +L["rating.mobility.desc"] = "Jak szybko użytkownik może się poruszać, używając tej broni.\nWpływa na to różne statystyki prędkości." +L["rating.stability"] = "Stabilność" +L["rating.stability.desc"] = "Jak bardzo celownik broni będzie się poruszał.\nWpływa na to różne statystyki chwiejące." + +L["stat.table.stat"] = "STATYSTYKA" +L["stat.table.base"] = "PODSTAWA" +L["stat.table.curr"] = "AKTUALNE" +L["stat.table.diff"] = "RÓŻNICA" + +L["stat.raw_dps"] = "Surowe DPS" +L["stat.raw_dps.desc"] = "Obliczona wydajność obrażeń broni bez spadku obrażeń.\nNie uwzględnia mnożników obrażeń grup trafienia ani penetracji pancerza." +L["stat.min_ttk"] = "Minimalne TTK" +L["stat.min_ttk.desc"] = "Obliczony czas potrzebny do zabicia celu od drugiego strzału.\nZakłada brak spadku obrażeń i cel ma 100 zdrowia.\nNie uwzględnia mnożników obrażeń grup trafienia." +L["stat.armorpenetration"] = "Penetracja Pancerza" +L["stat.armorpenetration.desc"] = "Mnożnik dla obrażeń zdrowia zadanych przy trafieniu w pancerz.\nWyższa wartość oznacza mniejsze obrażenia dla pancerza i więcej dla zdrowia.\nPrzy 100% pancerz jest całkowicie ignorowany. Przy 0% pancerz blokuje wszystkie obrażenia." +L["stat.armorbonus"] = "Niszczenie Pancerza" +L["stat.armorbonus.desc"] = "Mnożnik dla ilości zniszczonego pancerza.\nUwaga: Broń o wysokiej penetracji pancerza zniszczy mniej pancerza, ponieważ obrażenia idą bezpośrednio na zdrowie. Przy 100% penetracji pancerza nie będzie żadnych obrażeń pancerza." +L["stat.penetration"] = "Penetracja Materiału" +L["stat.penetration.desc"] = "Ilość metalu, przez który broń może strzelać.\nW zależności od rodzaju powierzchni, rzeczywista głębokość penetracji będzie się różnić." +L["stat.spread"] = "Rozrzut" +L["stat.spread.desc"] = "Podstawowy rozrzut broni." +L["stat.rpm"] = "Szybkostrzelność" +L["stat.rpm.desc"] = "Szybkostrzelność broni, w strzałach na minutę." +L["stat.rpm_burst"] = "Szybkostrzelność (w Serii)" +L["stat.rpm_burst.desc"] = "Szybkostrzelność broni w trybie ognia seriami, w strzałach na minutę." +L["stat.rpm_semi"] = "Szybkostrzelność (w Trybie Pojedynczym)" +L["stat.rpm_semi.desc"] = "Szybkostrzelność broni w trybie ognia pojedynczego, w strzałach na minutę." +L["stat.rpm_burst_peak"] = "Szybkostrzelność (Szczyt Serii)" +L["stat.rpm_burst_peak.desc"] = "Najwyższa osiągalna szybkostrzelność, uwzględniająca opóźnienie po serii, w strzałach na minutę." +L["stat.clipsize"] = "Pojemność magazynka" +L["stat.clipsize.desc"] = "Ilość amunicji, którą broń może pomieścić naraz." +L["stat.sprinttofire"] = "Strzał podczas biegu" +L["stat.sprinttofire.desc"] = "Czas potrzebny na przejście z pełnego biegu do możliwości oddania strzału." +L["stat.aimdownsights"] = "ADS z Celownika." +L["stat.aimdownsights.desc"] = "Czas potrzebny na przejście z celowania na oko do celowania z przyrządów." +L["stat.muzzlevelocity"] = "Prd. wylotowa pocisku" +L["stat.muzzlevelocity.desc"] = "Jak szybko pocisk przemieszcza się w świecie.\nKontroluje również próg odległości, poniżej którego pociski stają się natychmiastowe." +L["stat.recoilkick"] = "Odrzut" +L["stat.recoilkick.desc"] = "Siła \"odczuwalnego odrzutu\", który przesuwa twój punkt celowania." +L["stat.recoilstability"] = "Stabilność odrzutu" +L["stat.recoilstability.desc"] = "Jak przewidywalny jest odrzut.\nWiększa wartość oznacza, że odrzut staje się bardziej pionowy i mniej poziomy." +L["stat.recoilspread"] = "Bloom na rozrzut" +L["stat.recoilspread.desc"] = "Dodatkowy rozrzut na jednostkę bloom.\nWiększa wartość oznacza, że broń staje się bardziej niedokładna podczas długotrwałego ognia." +L["stat.recoilspread2"] = "Bloom na odrzut" +L["stat.recoilspread2.desc"] = "Dodatkowy odrzut na jednostkę bloom.\nWiększa wartość oznacza, że odrzut broni staje się silniejszy podczas długotrwałego ognia." +L["stat.recoildissipation"] = "Odzyskiwanie bloomu" +L["stat.recoildissipation.desc"] = "Szybkość, z jaką akumulowany bloom się rozprasza.\nWiększa wartość oznacza, że bloom znika szybciej po czasie resetu." +L["stat.recoilresettime"] = "Czas resetu bloomu" +L["stat.recoilresettime.desc"] = "Opóźnienie po strzale przed rozpoczęciem rozpraszania bloomu.\nWiększa wartość oznacza, że musisz czekać dłużej między strzałami, zanim bloom\nzacznie się zmniejszać." +L["stat.recoilmaximum"] = "Maksymalny bloom" +L["stat.recoilmaximum.desc"] = "Górny limit bloomu wywołanego ciągłym strzelaniem." +L["stat.recoilfirstshot"] = "Początkowy bloom" +L["stat.recoilfirstshot.desc"] = "Mnożnik bloomu podczas pierwszego strzału.\nDotyczy sytuacji, gdy nie ma akumulowanego bloomu." +L["stat.recoilpershot"] = "Zysk bloomu" +L["stat.recoilpershot.desc"] = "Ile bloomu jest dodawane za każdym strzałem." +L["stat.recoilcrouch"] = "Odrzut w Kucaniu" +L["stat.recoilcrouch.desc"] = "Mnożnik odrzutu podczas kucania i nieporuszania się." +L["stat.movespeed"] = "Prędkość poruszania się" +L["stat.movespeed.desc"] = "Mnożnik prędkości, gdy broń jest w pozycji gotowości.\nNie spowalnia, gdy broń jest zabezpieczona." +L["stat.shootingspeed"] = "Prd. podczas Strzału" +L["stat.shootingspeed.desc"] = "Mnożnik prędkości związany z oddawaniem strzałów z broni.\nAkumulujący się odrzut zwiększa intensywność spowolnienia." +L["stat.sightedspeed"] = "Prędkość podczas ADS" +L["stat.sightedspeed.desc"] = "Mnożnik prędkości podczas celowania bronią." +L["stat.reloadspeed"] = "Prd. przeładowania" +L["stat.reloadspeed.desc"] = "Mnożnik prędkości podczas przeładowywania." +L["stat.meleespeed"] = "Prd. podczas ataku wręcz" +L["stat.meleespeed.desc"] = "Mnożnik prędkości podczas atakowania wręcz." +L["stat.reloadtime"] = "Czas przeładowania" +L["stat.reloadtime.desc"] = "Ilość czasu wymagana do wykonania przeładowania." +L["stat.deploytime"] = "Czas wyjęcia" +L["stat.deploytime.desc"] = "Ilość czasu wymagana na wyjęcie broni." +L["stat.holstertime"] = "Czas schowania" +L["stat.holstertime.desc"] = "Ilość czasu wymagana na schowanie broni." +L["stat.sway"] = "Kołysanie" +L["stat.sway.desc"] = "Ilość chybienia podczas strzelania z biodra. Chybienie wpływa na kierunek twojego strzału\nbez zmiany kierunku celowania." +L["stat.scopedsway"] = "Kołysanie podczas ADS" +L["stat.scopedsway.desc"] = "Ilość chybienia podczas celowania. Chybienie wpływa na kierunek twojego strzału\nbez zmiany kierunku celowania." +L["stat.swaycrouch"] = "Chybienie w Kucaniu" +L["stat.swaycrouch.desc"] = "Mnożnik chybienia podczas kucania." +L["stat.midairspread"] = "Rozrzut w powietrzu" +L["stat.midairspread.desc"] = "Ilość niedokładności spowodowanej brakiem solidnej podłoża.\nStosowane z połowiczną intensywnością podczas pływania i wspinaczki po drabinach." +L["stat.hipfirespread"] = "Rozrzut strzelania z biodra" +L["stat.hipfirespread.desc"] = "Ilość niedokładności obecnej podczas strzelania z biodra." +L["stat.movespread"] = "Rozrzut podczas poruszania się" +L["stat.movespread.desc"] = "Ilość niedokładności obecnej podczas poruszania się.\nIntensywność jest związana z bieżącą prędkością." +L["stat.meleedamage"] = "Obrażenia wręcz" +L["stat.meleedamage.desc"] = "Obrażenia zadawane podczas atakowania wręcz." +L["stat.firemode"] = "Tryb strzelania" +L["stat.firemode.desc"] = "Zdolności strzelania broni." +L["stat.freeaimangle"] = "Kąt swobodnego celowania" +L["stat.freeaimangle.desc"] = "Maksymalna ilość odchylenia od kierunku celowania podczas strzelania z biodra." +L["stat.shotstofail"] = "Średnia liczba strzałów do awarii" +L["stat.shotstofail.desc"] = "Średnia liczba strzałów, które zostaną oddane przed zacięciem się broni." + +L["stat.meleeperkstr"] = "Moc" +L["stat.meleeperkstr.desc"] = "Atrybut ataku wręcz wpływający na Techniki i Specjały.\nWpływa na obrażenia od atrybutów i odrzut." +L["stat.meleeperkagi"] = "Zręczność" +L["stat.meleeperkagi.desc"] = "Atrybut ataku wręcz wpływający na Techniki i Specjały.\nWpływa na prędkość ruchu od atrybutów i odstęp między atakami." +L["stat.meleeperkint"] = "Strategia" +L["stat.meleeperkint.desc"] = "Atrybut ataku wręcz wpływający na Techniki i Specjały.\nWpływa na szybkość regeneracji atrybutów i siłę pocisku." + +L["rating.meleeattacktime"] = "Szybkość" +L["rating.meleeattacktime.desc"] = "Jak szybko broń może atakować." +L["stat.damage.desc_melee"] = "Obrażenia zadawane przez główne ataki." -- używane przy obrażeniach od ataku wręcz +L["stat.meleeattacktime"] = "Opóźnienie przy trafieniu" +L["stat.meleeattacktime.desc"] = "Ilość czasu między każdym atakiem, jeśli coś trafiło." +L["stat.meleeattackmisstime"] = "Opóźnienie przy chybieniu" +L["stat.meleeattackmisstime.desc"] = "Ilość czasu między każdym atakiem, jeśli nic nie trafiło." +L["stat.meleerange"] = "Zasięg ataku" +L["stat.meleerange.desc"] = "Jak daleko broń może sięgać w celu ataku." +L["stat.meleedelay"] = "Zamach" +L["stat.meleedelay.desc"] = "Opóźnienie między rozpoczęciem ataku a jego wykonaniem." + +L["stat.melee2damage"] = "Obrażenia (ciężkie)" +L["stat.melee2damage.desc"] = "Obrażenia zadawane przez ciężkie ataki." +L["stat.melee2attacktime"] = "Opóźnienie przy trafieniu (ciężkie)" +L["stat.melee2attacktime.desc"] = "Ilość czasu między każdym ciężkim atakiem, jeśli coś trafiło." +L["stat.melee2attackmisstime"] = "Opóźnienie przy chybieniu (ciężkie)" +L["stat.melee2attackmisstime.desc"] = "Ilość czasu między każdym ciężkim atakiem, jeśli nic nie trafiło." +L["stat.meleethrowdamage"] = "Obrażenia (rzut)" +L["stat.meleethrowdamage.desc"] = "Obrażenia zadawane przez broń wrzucaną." +L["stat.meleethrowvelocity"] = "Prędkość rzutu" +L["stat.meleethrowvelocity.desc"] = "Prędkość przemieszczania się broni wrzucanej." +L["stat.meleethrowtime"] = "Opóźnienie rzutu" +L["stat.meleethrowtime.desc"] = "Ilość czasu między każdym rzutem broni." +L["stat.lifesteal"] = "Zdrowie z obrażeń" +L["stat.lifesteal.desc"] = "Ułamek obrażeń zadanych, który jest zamieniany na zdrowie." +L["stat.damagecharge"] = "Ładunek z obrażeń" +L["stat.damagecharge.desc"] = "Ułamek obrażeń zadanych, który jest zamieniany na ładunek atrybutów." + +L["stat.damage"] = "Obrażenia" +L["stat.damage_max.desc"] = "Obrażenia na pocisk przy wszystkich zasięgach." +L["stat.damage_max"] = "Obr. na bliski zasięg" +L["stat.damage_max.desc"] = "Obrażenia na pocisk przy braku zmniejszenia zasięgu lub zwiększenia." +L["stat.damage_min"] = "Obr. na daleki zasięg" +L["stat.damage_min.desc"] = "Obrażenia na pocisk przy maksymalnym zmniejszeniu zasięgu lub zwiększeniu." +L["stat.explosivedamage"] = "Obrażenia eksplozywne" +L["stat.explosivedamage.desc"] = "Obrażenia zadane przez eksplozję przy uderzeniu pocisku." +L["stat.explosiveradius"] = "Promień eksplozji" +L["stat.explosiveradius.desc"] = "Promień rażenia eksplozji przy uderzeniu pocisku." +L["stat.range_max"] = "Maksymalny zasięg" +L["stat.range_max.desc"] = "Odległość, na której zmniejszenie zasięgu lub zwiększenie osiąga maksimum." +L["stat.range_min"] = "Minimalny zasięg" +L["stat.range_min.desc"] = "Odległość, na której zmniejszenie zasięgu lub zwiększenie się rozpoczyna." +L["stat.postburstdelay"] = "Opóźnienie po serii" +L["stat.postburstdelay.desc"] = "Czas potrzebny do zresetowania broni między seriami strzałów." +L["stat.ammopershot"] = "Amunicja na strzał" +L["stat.ammopershot.desc"] = "Ilość amunicji zużywanej na strzał." +L["stat.num"] = "Ilość pocisków" +L["stat.num.desc"] = "Ilość pocisków wystrzeliwanych na strzał." +L["stat.peekpenalty"] = "Kara za wyglądanie" +L["stat.peekpenalty.desc"] = "Ilość rozrzutu i chybienia podczas wyglądania." +L["stat.quickscope"] = "Kara za szybki ADS" +L["stat.quickscope.desc"] = "Ilość niedokładności podczas przełączania się w i z celowania.\nKara zanika w ciągu 0,2 sekundy." +L["stat.vol_shoot"] = "Głośność" +-- L["stat.vol_shoot.desc"] = "How audible the weapon's firing sound is." +-- L["stat.damagemultnpc"] = "NPC Damage" +-- L["stat.damagemultnpc.desc"] = "Non-melee damage against NPCs and Nextbots is multiplied by this value." + +-- not in stats page but attachments may use +L["stat.swaycrouch"] = "Chybienie w kucach" +L["stat.recoil"] = "Odrzut" +L["stat.range"] = "Zasięg" +L["stat.blindfiresway"] = "Chybienie przy strzelaniu z zasłony" +L["stat.zoom"] = "Powiększenie celu" +L["stat.bloomintensity"] = "Intensywność bloomu" + +-- L["att.none"] = "N/A" + +L["att.procon.3proj"] = "Trzy pociski" +L["att.procon.moreproj"] = "Więcej pocisków" +-- L["att.procon.lessproj"] = "Fewer projectiles" +L["att.procon.1proj"] = "Jeden pocisk" +L["att.procon.noexp"] = "Bez wybuchu" +L["att.procon.direct"] = "Bezpośredni ogień" +L["att.procon.doorbreach"] = "Może wyważyć drzwi" +L["att.procon.crowd"] = "Kontrola tłumu" +L["att.procon.nonlethal"] = "Nieśmiertelny" +L["att.procon.detdelay"] = "Opóźnione detonacje" +L["att.procon.flash"] = "Efekt granatu ogłuszającego" +L["att.procon.airburst"] = "Wybuch powietrzny" +L["att.procon.timedfuse"] = "Zapalnik czasowy" +L["att.procon.smoke"] = "Efekt zasłony dymnej" +L["att.procon.limb"] = "Obrażenia kończyn" +L["att.procon.head"] = "Obrażenia głowy" +L["att.procon.chest"] = "Obrażenia klatki piersiowej" +L["att.procon.onebullet"] = "Jeden pocisk" +L["att.procon.armor"] = "Skuteczność pancerza" +L["att.procon.nosafety"] = "Brak bezpiecznika" +L["att.procon.radius"] = "Promień rażenia" +L["att.procon.needprime"] = "Słaby, jeśli nie zainicjowany" +L["att.procon.projrng"] = "Losowa trajektoria i obrażenia" +L["att.procon.failrng"] = "Szansa na dramatyczne awarie" +L["att.procon.notracer"] = "Ukryte ślady pocisków" +L["att.procon.refund"] = "Wysoka szansa na zwrot amunicji" +L["att.procon.unreliable"] = "Niezawodność" +L["att.procon.surplusboost1"] = "Czasami strzela szybciej" +L["att.procon.surplusboost2"] = "Strzela także niekontrolowanie" +L["att.procon.meleeslow"] = "Spowalnia cel po uderzeniu" +L["att.procon.gasimmune"] = "Immunitet na gaz CS" +L["att.procon.flashresist"] = "Odporność na ogłuszenie świetlne" +L["att.procon.stunresist"] = "Zmniejszony czas ogłuszenia/zwolnienia" +L["att.procon.quickthrow"] = "Szybsze rzucać" +L["att.procon.throwrocks"] = "Rzuć kamieniami" +L["att.procon.cornershot"] = "Widzenie w zakręcie podczas strzelania z zasłony" +-- L["att.procon.thermal"] = "Thermal overlay when peeking" +L["att.procon.dmic"] = "Wyświetl pobliskie cele" +L["att.procon.audible"] = "Słyszalny dla innych" +L["att.procon.flashlight"] = "Oświetl obszar" +L["att.procon.blind"] = "Oślepia, gdy na nią spojrzeć" +L["att.procon.visible"] = "Widoczne dla innych" +L["att.procon.laser"] = "Podświetla cel" +L["att.procon.rf1"] = "Wyświetla odległość i obrażenia na odległość" +L["att.procon.rf2"] = "Wizualizacja spadku pocisku w celownikach" +L["att.procon.gauge1"] = "Wyświetla rozrzut, kołysanie i rozproszenie" +L["att.procon.gauge2"] = "Wizualizacja rozproszenia w celownikach" +L["att.procon.auto"] = "Automatyczny ogień" +L["att.procon.burst"] = "Ogień seriami" +L["att.procon.semi"] = "Ogień półautomatyczny" +L["att.procon.autoburst"] = "Automatyczne serie" +L["att.procon.explosive"] = "Wybuchowy" +L["att.procon.reliability"] = "Niezawodność" +L["att.procon.noscope"] = "Bez celownika" +L["att.procon.conceal"] = "Ukryj broń podczas schowania" +L["att.procon.armdelay"] = "Opóźnienie uzbrojenia" +L["att.procon.proxfuse"] = "Zapalnik zbliżeniowy" +-- L["att.procon.magnifier"] = "Variable Magnification" +-- L["att.procon.needscope"] = "Requires Optic" +-- L["att.procon.bullet"] = "Emergency Bullet" +-- L["att.procon.nopartialreloads"] = "No partial reloads" +-- L["att.procon.incendiary"] = "Ignite Targets" +-- L["att.procon.blurpeek"] = "Blur when peeking" +-- L["att.procon.aimrecoil"] = "Recoil (while aiming)" +-- L["att.procon.aimspread"] = "Spread (while aiming)" +-- L["att.procon.aimrpm"] = "Fire Rate (while aiming)" +-- L["att.procon.firstround"] = "First shot is a single less-lethal projectile" +-- L["att.procon.locktime"] = "Lock-on time" +-- L["att.procon.semiactive"] = "Constant guidance required" +-- L["att.procon.proj.turn"] = "Projectile Maneuverability" +-- L["att.procon.proj.speed"] = "Projectile Top Speed" +-- L["att.procon.proj.direct"] = "Impact Damage" +-- L["att.procon.shovel"] = "Shoots shovels" +-- L["att.procon.heal"] = "Healing effect" +-- L["att.procon.infiniteammo"] = "Infinite Ammo" + +L["att.sight.1"] = "Celownik 1x" +L["att.sight.1.25"] = "Celownik 1.25x" +L["att.sight.1.5"] = "Celownik 1.5x" +L["att.sight.1.75"] = "Celownik 1.75x" +L["att.zoom.2"] = "Powiększenie 2x" +L["att.zoom.2.2"] = "Powiększenie 2.2x" +L["att.zoom.3"] = "Powiększenie 3x" +L["att.zoom.3.4"] = "Powiększenie 3.4x" +L["att.zoom.3.5"] = "Powiększenie 3.5x" +L["att.zoom.4"] = "Powiększenie 4x" +L["att.zoom.5"] = "Powiększenie 5x" +L["att.zoom.6"] = "Powiększenie 6x" +L["att.zoom.8"] = "Powiększenie 8x" +L["att.zoom.10"] = "Powiększenie 10x" +L["att.zoom.12"] = "Powiększenie 12x" + +L["spacer.damage"] = "Efekt końcowy" +L["spacer.damage.desc"] = "Statystyki związane z wyjściowym efektem obrażeń broni i jej zasięgiem skutecznym." +L["spacer.action"] = "Akcja" +L["spacer.action.desc"] = "Statystyki związane z ładowaniem i przeładowywaniem broni." +L["spacer.ballistics"] = "Balistyka" +L["spacer.ballistics.desc"] = "Statystyki związane z trajektorią pocisku broni i jego wpływem na materiał." +L["spacer.recoilbloom"] = "Odrzut i rozkwit" +L["spacer.recoilbloom.desc"] = "Statystyki związane z odrzutem i niestabilnością broni podczas długotrwałego ognia." +L["spacer.mobility"] = "Mobilność" +L["spacer.mobility.desc"] = "Statystyki związane z prędkością poruszania się podczas korzystania z broni." +L["spacer.maneuvering"] = "Manewrowanie" +L["spacer.maneuvering.desc"] = "Statystyki związane z wydajnością broni podczas niecelowania." +L["spacer.handling"] = "Obsługa" +L["spacer.handling.desc"] = "Statystyki związane z czasem gotowości broni do strzału." +L["spacer.sway"] = "Kołysanie" +L["spacer.sway.desc"] = "Statystyki związane z odchyleniem broni od celu." +L["spacer.misc"] = "Różne" +L["spacer.misc.desc"] = "Statystyki, które nie pasują do żadnej głównej kategorii." + +L["trivia.year"] = "Rok produkcji" +L["trivia.caliber"] = "Kaliber" +L["trivia.manufacturer"] = "Producent" +L["trivia.faction"] = "Frakcja" +L["trivia.unknown"] = "Nieznane" + +L["faction.coalition"] = "Sojusz" -- "Kontrterrorystów" +L["faction.militia"] = "Milicja" -- "Terrorystów" +L["faction.neutral"] = "Bezstronny" -- niezwiązany z frakcją + +-- L["menu.legacy"] = "Legacy Menu" + +L["hint.swap"] = "Zamień na {weapon}" +L["hint.pickup"] = "Podnieś {weapon}" +-- L["hint.unjam"] = "Unjam Weapon" +-- L["hint.rp_biocode_cp"] = "Biocoded - Police Use Only" + +-- L["hint.melee"] = "Melee Attack" +-- L["hint.melee.block"] = "Block" +-- L["hint.melee.throw"] = "Melee Throw" +-- L["hint.melee.light"] = "Light Attack" +-- L["hint.melee.heavy"] = "Heavy Attack" + +-- L["hint.peek.hold"] = "Peek" +-- L["hint.peek.toggle"] = "Toggle Peek" +-- L["hint.hold_breath"] = "Hold Breath" +-- L["hint.customize"] = "Customize" +-- L["hint.inspect"] = "Inspect" +-- L["hint.firemode"] = "Firemode" +-- L["hint.safety.turn_off"] = "Disable Safety" +-- L["hint.safety.turn_on"] = "Enable Safety" +-- L["hint.hl2_flashlight"] = "Suit Flashlight" +-- L["hint.toggle_tactical"] = "Toggle {1}" +-- L["hint.blindfire"] = "Blindfire" +-- L["hint.blindfire.menu"] = "Blindfire Menu" +-- L["hint.blindfire.up"] = "Blindfire Up" +-- L["hint.blindfire.left"] = "Blindfire Left" +-- L["hint.blindfire.right"] = "Blindfire Right" +-- L["hint.blindfire.cancel"] = "Cancel Blindfire" +-- L["hint.blindfire.kys"] = "Commit Suicide" +-- L["hint.quicknade.over"] = "Overhand Throw" +-- L["hint.quicknade.under"] = "Underhand Throw" +-- L["hint.quicknade.throw"] = "Quickthrow" +-- L["hint.quicknade.pull_out"] = "Pull Out Grenade" +-- L["hint.quicknade.menu"] = "Quickthrow Menu" +-- L["hint.quicknade.next"] = "Next Grenade" +-- L["hint.quicknade.prev"] = "Previous Grenade" +-- L["hint.ttt.radio"] = "TTT Radio" +-- L["hint.ttt.shop"] = "TTT Shop" +-- L["hint.tac"] = "Tactical" -- default value +-- L["hint.tac.radar"] = "Radar" +-- L["hint.tac.flashlight"] = "Flashlight" +-- L["hint.tac.laser"] = "Laser" +-- L["hint.tac.combo"] = "Combo" +-- L["hint.tac.rangefinder"] = "Ranger" +-- L["hint.tac.spread_gauge"] = "Gauge" +-- L["hint.tac.magnifier"] = "Magnifier" +-- L["hint.tac.cam_mode"] = "Cam Mode" -- Used for both Corner-Cam and Thermal Imager +-- L["hint.tac.load_one"] = "Load One Round" + +-- L["hint.melee_lunge"] = "Lunge" +-- L["hint.melee_step"] = "Airdash" +-- L["hint.melee_ninja.palm"] = "Palm Strike" +-- L["hint.melee_ninja.backhop"] = "Backhop" +-- L["hint.melee_ninja.divekick"] = "Divekick" + +-- L["hint.medkit.self"] = "Heal Self" +-- L["hint.medkit.others"] = "Heal Others" + +-- L["hint.unlock"] = "Unlock" +-- L["hint.exitcustmenu"] = "Exit the Customization Menu" + +-- L["hint.nogrenades"] = "No Grenades Available" + +-- L["hint.hold"] = "(Hold)" + +-- L["hint.shootself"] = "Shoot Yourself" + +-- For demoknight charge attachment +-- L["hint.melee_charge"] = "Charge" +-- L["hint.melee_charge.menu"] = "Charge Type" +-- L["hint.melee_charge.0.name"] = "Bravery" +-- L["hint.melee_charge.0.desc"] = "Standard mode with average turn control.\nBash into a target to deal damage and knockback. Attacking during the charge cancels it." +-- L["hint.melee_charge.1.name"] = "Mastery" +-- L["hint.melee_charge.1.desc"] = "Significantly increased turn control.\nLow damage resistance and bash damage.\nTaking damage shortens charge duration." +-- L["hint.melee_charge.2.name"] = "Tenacity" +-- L["hint.melee_charge.2.desc"] = "Increased charge speed, reduced turn control.\nVery high damage resistance and bash damage.\nGains knockback immunity during charge." + +-- Weapon calibers +-- L["caliber.556x45"] = "5.56×45mm" +-- L["caliber.762x39"] = "7.62×39mm" +-- L["caliber.762x51"] = "7.62×51mm" +-- L["caliber.50bmg"] = ".50 BMG" +-- L["caliber.16g"] = "16 Gauge" +-- L["caliber.12g"] = "12 Gauge" +-- L["caliber.40mmg"] = "40mm Grenades" +-- L["caliber.9x19"] = "9×19mm" +-- L["caliber.57x28"] = "FN 5.7×28mm" +-- L["caliber.45acp"] = ".45 ACP" +-- L["caliber.380acp"] = ".380 ACP" +-- L["caliber.homing"] = "Infrared Homing Missile" +-- L["caliber.40sw"] = ".40 S&W" +-- L["caliber.23x75"] = "23×75mmR" +-- L["caliber.223r"] = ".223 Remington" +-- L["caliber.46x30"] = "HK 4.6×30mm" +-- L["caliber.357"] = ".357 Magnum" +-- L["caliber.357sig"] = ".357 SIG" +-- L["caliber.6mm"] = "6mm Whisper" +-- L["caliber.40mmr"] = "40mm Rockets" +-- L["caliber.32acp"] = ".32 ACP" +-- L["caliber.308"] = ".308 Winchester" +-- L["caliber.65mm"] = "6.5mm Creedmoor" + +-- L["caliber.44amp"] = ".44 AMP" +-- L["caliber.40x46"] = "40×46mm" +-- L["caliber.50ae"] = ".50 AE" +-- L["caliber.792x57"] = "7.92×57mm Mauser" +-- L["caliber.500sw"] = ".500 S&W Magnum" +-- L["caliber.22lr"] = ".22 LR" +-- L["caliber.38special"] = ".38 Special" +-- L["caliber.338"] = ".338 Lapua Magnum" +-- L["caliber.9x39"] = "9x39mm" + +-- L["caliber.44p"] = ".44 Percussion" +-- L["caliber.300blk"] = ".300 Blackout" +-- L["caliber.9x18"] = "9×18mm" +-- L["caliber.4570"] = ".45-70 Govt." +-- L["caliber.9x21"] = "9x21mm IMI" +-- L["caliber.762x54"] = "7.62×54mmR" +-- L["caliber.762x25"] = "7.62×25mm Tokarev" +-- L["caliber.300winmag"] = ".300 Win Mag" + +-- L["caliber.545x39"] = "5.45×39mm" + +-- L["caliber.410b"] = ".410 Bore" +-- L["caliber.408ct"] = ".408 Cheyenne Tactical" +-- L["caliber.950jdj"] = ".950 JDJ" +-- L["caliber.600ne"] = ".600 Nitro Express" +-- L["caliber.83mmr"] = "83mm Rockets" +-- L["caliber.25x40"] = "25×40mm" + +-- L["caliber.44m"] = ".44 Magnum" +-- L["caliber.763x25"] = "7.63×25mm Mauser" +-- L["caliber.277sig"] = ".277 SIG Fury" +-- L["caliber.10auto"] = "10mm Auto" +-- L["caliber.75x55"] = "7.5×55mm" +-- L["caliber.44-40"] = ".44-40 WCF" + +-- L["caliber.20g"] = "20 Gauge" +-- L["caliber.9wm"] = "9mm Win Mag" +-- L["caliber.44s"] = ".44 Special" +-- L["caliber.303b"] = ".303 British" +-- L["caliber.66mmr"] = "66mm Rockets" +-- L["caliber.25mmf"] = "25mm Flares" +-- L["caliber.270w"] = ".270 Winchester" + +-- L["caliber.45s"] = ".45 Super" + +-- L["unknown"] = "Unknown" + +-- Depreciated Strings (No longer in use, or use is altered) +-- L["type.precision_rifle"] = "Karabin Precyzyjny" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_ru.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_ru.lua new file mode 100644 index 0000000..2f2baa9 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_ru.lua @@ -0,0 +1,531 @@ +L = {} -- Русский язык + +L["font.1"] = "Myriad Pro" +L["font.2"] = "HD44780A00 5x8" + +L["tier.0"] = "5Ценность" +L["tier.1"] = "4Потребительский" +L["tier.2"] = "3Охранный" +L["tier.3"] = "2Оперативный" +L["tier.4"] = "1Элитный" +L["tier.5"] = "0Экзотический" + +L["tier.spec"] = "9Специальный" +L["type.sidearm"] = "1Пистолет" +L["type.magnum_pistol"] = "2Магнум" +L["type.machine_pistol"] = "3Пистолет-пулемёт" +L["type.dual_pistol"] = "3Парные пистолеты" +L["type.submachine_gun"] = "3Пистолет-пулемёт" +L["type.assault_rifle"] = "4Штурмовая винтовка" +L["type.machine_gun"] = "5Пулемёт" +L["type.shotgun"] = "5Дробовик" +L["type.sporter_carbine"] = "5Карабин" +L["type.battle_rifle"] = "5Боевая винтовка" +L["type.dmr"] = "6Снайперская винтовка" +L["type.sniper_rifle"] = "7Снайперская винтовка" +L["type.amr"] = "9Противоматериальная винтовка" +L["type.melee_sharp"] = "8Холодное оружие" +L["type.melee_blunt"] = "8Дробящее оружие" +L["type.equipment"] = "9Снаряжение" +L["type.throwable"] = "9Метательное" +L["type.launcher"] = "6Гранатомёт" +L["type.special_weapon"] = "7Спец. оружие" + +L["cust.rating"] = "Рейтинг" +L["cust.stats"] = "Характеристики" +L["cust.description"] = "ОПИСАНИЕ:" +L["cust.type_tier"] = "{type} класса {tier}" +L["cust.trivia"] = "Информация" +L["cust.credits"] = "Авторы" +L["cust.description2"] = "Описание" +L["cust.drop_wep"] = "Выбросить оружие" +L["cust.drop_nade"] = "Выбросить гранату" + +L["unit.hu"] = " ед." +L["unit.meter"] = "м" +L["unit.second"] = "с" +L["unit.persecond"] = "/с" +L["unit.mps"] = "м/с" +L["unit.damage"] = " урона" +L["unit.stk"] = " выстр." +L["unit.ptk"] = " дробь" + +L["slot.default"] = "Слот" +L["slot.optic"] = "Оптика" +L["slot.muzzle"] = "Дуло" +L["slot.tactical"] = "Тактическое" +L["slot.accessory"] = "Аксессуар" +L["slot.bolt"] = "Затвор" +L["slot.trigger"] = "Спуск" +L["slot.ammo"] = "Боеприпасы" +L["slot.perk"] = "Перк" +L["slot.melee_tech"] = "Техника" +L["slot.melee_spec"] = "Особое" +L["slot.melee_boost"] = "Усиление" + +L["ammo.ti_flashbang"] = "Светошумовые гранаты" +L["ammo.ti_thermite"] = "Термитные гранаты" +L["ammo.ti_smoke"] = "Дымовые гранаты" +L["ammo.ti_c4"] = "Заряды C4" +L["ammo.ti_gas"] = "Газовые гранаты" +L["ammo.ti_nuke"] = "Ядерные устройства" +L["ammo.ti_charge"] = "Подрывные заряды" +L["ammo.ti_sniper"] = "Противоматериальные патроны" +L["ammo.ti_heal"] = "Медицинский дым" +L["ammo.ti_pistol_light"] = "Лёгкие пистолетные патроны" +L["ammo.ti_pistol_heavy"] = "Тяжёлые пистолетные патроны" +L["ammo.ti_pdw"] = "Патроны PDW" +L["ammo.ti_rifle"] = "Винтовочные патроны" + +L["ammo.357"] = "Магнум" +L["ammo.smg1"] = "Карабин" +L["ammo.ar2"] = "Винтовка" + +L["rating.score"] = "(Оценка: {score}/{max})" +L["rating.lethality"] = "Летальность" +L["rating.lethality.desc"] = "Насколько легко и быстро оружие может убить цель.\nНе учитывает пробитие брони.\nЗависит от урона и скорострельности." +L["rating.suppression"] = "Подавление" +L["rating.suppression.desc"] = "Сколько урона оружие может нанести за длительный период.\nНе учитывает пробитие брони.\nЗависит от урона, скорострельности, магазина и скорости перезарядки." +L["rating.range"] = "Дальность" +L["rating.range.desc"] = "Насколько хорошо оружие сохраняет урон на дальних дистанциях.\nЗависит от минимальной и максимальной дальности." +L["rating.precision"] = "Точность" +L["rating.precision.desc"] = "Насколько точно оружие при одиночных выстрелах.\nЗависит от разброса и отдачи." +L["rating.control"] = "Контроль" +L["rating.control.desc"] = "Насколько управляема отдача при длительной стрельбе.\nЗависит от скорострельности и отдачи." +L["rating.handling"] = "Обращение" +L["rating.handling.desc"] = "Как быстро оружие готово к стрельбе после бега или прицеливания.\nЗависит от времени прицеливания и доставания." +L["rating.maneuvering"] = "Маневрирование" +L["rating.maneuvering.desc"] = "Насколько точно оружие без прицеливания.\nЗависит от разброса от бедра и раскачки." +L["rating.mobility"] = "Мобильность" +L["rating.mobility.desc"] = "Как быстро игрок может двигаться с этим оружием.\nЗависит от скорости передвижения." +L["rating.stability"] = "Стабильность" +L["rating.stability.desc"] = "Насколько сильно прицел движется.\nЗависит от раскачки." + +L["stat.table.stat"] = "ХАРАКТЕРИСТИКА" +L["stat.table.base"] = "БАЗА" +L["stat.table.curr"] = "ТЕКУЩ" +L["stat.table.diff"] = "РАЗН" + +L["stat.raw_dps"] = "Чистый DPS" +L["stat.raw_dps.desc"] = "Урон в секунду без падения урона на дистанции.\nНе учитывает множители урона по зонам." +L["stat.min_ttk"] = "Минимальное время убийства" +L["stat.min_ttk.desc"] = "Время убийства цели со 100 здоровья.\nНе учитывает падение урона и множители по зонам." +L["stat.armorpenetration"] = "Пробитие брони" +L["stat.armorpenetration.desc"] = "Множитель урона по здоровью при попадании в броню.\nЧем выше значение, тем меньше урона поглощает броня.\n100% = броня игнорируется полностью." +L["stat.armorbonus"] = "Разрушение брони" +L["stat.armorbonus.desc"] = "Множитель разрушения брони.\nПри высоком пробитии брони, разрушается меньше брони.\nПри 100% пробития урон брони не наносится." +L["stat.penetration"] = "Пробитие материалов" +L["stat.penetration.desc"] = "Количество металла, которое может пробить оружие.\nВ зависимости от материала глубина пробития меняется." +L["stat.spread"] = "Разброс" +L["stat.spread.desc"] = "Базовый разброс оружия." +L["stat.rpm"] = "Скорострельность" +L["stat.rpm.desc"] = "Скорость стрельбы в выстрелах в минуту." +L["stat.rpm_burst"] = "Скорострельность (очередь)" +L["stat.rpm_burst.desc"] = "Скорость стрельбы в режиме очереди." +L["stat.rpm_semi"] = "Скорострельность (одиночный)" +L["stat.rpm_semi.desc"] = "Скорость стрельбы в полуавтоматическом режиме." +L["stat.rpm_burst_peak"] = "Пиковая скорострельность" +L["stat.rpm_burst_peak.desc"] = "Максимальная скорострельность с учётом задержки." +L["stat.clipsize"] = "Магазин" +L["stat.clipsize.desc"] = "Количество патронов в магазине." +L["stat.sprinttofire"] = "Время из бега" +L["stat.sprinttofire.desc"] = "Время перехода от бега к стрельбе." +L["stat.aimdownsights"] = "Время прицеливания" +L["stat.aimdownsights.desc"] = "Время перехода к прицеливанию." +L["stat.muzzlevelocity"] = "Начальная скорость" +L["stat.muzzlevelocity.desc"] = "Скорость полёта пули.\nТакже определяет дистанцию хитскана." +L["stat.recoilkick"] = "Отдача" +L["stat.recoilkick.desc"] = "Сила отдачи, смещающей прицел." +L["stat.recoilstability"] = "Стабильность отдачи" +L["stat.recoilstability.desc"] = "Насколько предсказуема отдача.\nБольшее значение = более вертикальная отдача." +L["stat.recoilspread"] = "Разброс от отдачи" +L["stat.recoilspread.desc"] = "Дополнительный разброс за единицу накопления.\nБольшее значение = больше неточности при стрельбе." +L["stat.recoilspread2"] = "Усиление отдачи" +L["stat.recoilspread2.desc"] = "Дополнительная отдача за единицу накопления.\nБольшее значение = сильнее отдача при длительной стрельбе." +L["stat.recoildissipation"] = "Восстановление" +L["stat.recoildissipation.desc"] = "Скорость снижения накопления.\nБольшее значение = быстрее исчезает накопление." +L["stat.recoilresettime"] = "Задержка восстановления" +L["stat.recoilresettime.desc"] = "Задержка перед началом восстановления.\nБольшее значение = дольше ждать между выстрелами." +L["stat.recoilmaximum"] = "Максимальное накопление" +L["stat.recoilmaximum.desc"] = "Максимальное накопление при непрерывной стрельбе." +L["stat.recoilfirstshot"] = "Начальное накопление" +L["stat.recoilfirstshot.desc"] = "Множитель накопления для первого выстрела.\nПрименяется при нулевом накоплении." +L["stat.recoilpershot"] = "Накопление за выстрел" +L["stat.recoilpershot.desc"] = "Сколько накопления добавляется за выстрел." +L["stat.recoilcrouch"] = "Отдача в приседе" +L["stat.recoilcrouch.desc"] = "Множитель отдачи в приседе без движения." +L["stat.movespeed"] = "Скорость движения" +L["stat.movespeed.desc"] = "Множитель скорости с оружием в руках.\nНе замедляет при опущенном оружии." +L["stat.shootingspeed"] = "Скорость при стрельбе" +L["stat.shootingspeed.desc"] = "Множитель скорости при стрельбе.\nНакопление отдачи усиливает замедление." +L["stat.sightedspeed"] = "Скорость при прицеливании" +L["stat.sightedspeed.desc"] = "Множитель скорости при прицеливании." +L["stat.reloadspeed"] = "Скорость перезарядки" +L["stat.reloadspeed.desc"] = "Множитель скорости при перезарядке." +L["stat.meleespeed"] = "Скорость ближнего боя" +L["stat.meleespeed.desc"] = "Множитель скорости при атаке в ближнем бою." +L["stat.reloadtime"] = "Время перезарядки" +L["stat.reloadtime.desc"] = "Время, необходимое для перезарядки." +L["stat.deploytime"] = "Время доставания" +L["stat.deploytime.desc"] = "Время доставания оружия." +L["stat.holstertime"] = "Время убирания" +L["stat.holstertime.desc"] = "Время убирания оружия." +L["stat.sway"] = "Раскачка" +L["stat.sway.desc"] = "Раскачка от бедра. Влияет на направление стрельбы,\nне меняя направление прицеливания." +L["stat.scopedsway"] = "Раскачка при прицеливании" +L["stat.scopedsway.desc"] = "Раскачка при прицеливании. Влияет на направление стрельбы,\nне меняя направление прицеливания." +L["stat.swaycrouch"] = "Раскачка в приседе" +L["stat.swaycrouch.desc"] = "Множитель раскачки в приседе." +L["stat.midairspread"] = "Разброс в воздухе" +L["stat.midairspread.desc"] = "Неточность без опоры под ногами.\nПрименяется наполовину при плавании и на лестницах." +L["stat.hipfirespread"] = "Разброс от бедра" +L["stat.hipfirespread.desc"] = "Неточность при стрельбе от бедра." +L["stat.movespread"] = "Разброс при движении" +L["stat.movespread.desc"] = "Неточность при движении.\nИнтенсивность зависит от скорости." +L["stat.meleedamage"] = "Урон ближнего боя" +L["stat.meleedamage.desc"] = "Урон от атаки в ближнем бою." +L["stat.firemode"] = "Режим огня" +L["stat.firemode.desc"] = "Возможности стрельбы оружия." +L["stat.freeaimangle"] = "Угол свободного прицеливания" +L["stat.freeaimangle.desc"] = "Максимальное отклонение от направления прицеливания от бедра." +L["stat.shotstofail"] = "Выстрелов до отказа" +L["stat.shotstofail.desc"] = "Среднее количество выстрелов до заклинивания." +L["stat.recoilburst"] = "Отдача (очередь)" +L["stat.recoilburst.desc"] = "Множитель отдачи в режиме очереди." +L["stat.recoilsemi"] = "Отдача (одиночный)" +L["stat.recoilsemi.desc"] = "Множитель отдачи в полуавтоматическом режиме." + +L["stat.meleeperkstr"] = "Сила" +L["stat.meleeperkstr.desc"] = "Атрибут ближнего боя, влияющий на техники.\nВлияет на урон и отбрасывание." +L["stat.meleeperkagi"] = "Ловкость" +L["stat.meleeperkagi.desc"] = "Атрибут ближнего боя, влияющий на техники.\nВлияет на скорость движения и интервал атаки." +L["stat.meleeperkint"] = "Стратегия" +L["stat.meleeperkint.desc"] = "Атрибут ближнего боя, влияющий на техники.\nВлияет на скорость восстановления и силу снарядов." + +L["rating.meleeattacktime"] = "Скорость" +L["rating.meleeattacktime.desc"] = "Как быстро оружие может атаковать." +L["stat.damage.desc_melee"] = "Урон от основных атак." +L["stat.meleeattacktime"] = "Задержка атаки" +L["stat.meleeattacktime.desc"] = "Время между атаками при попадании." +L["stat.meleeattackmisstime"] = "Задержка промаха" +L["stat.meleeattackmisstime.desc"] = "Время между атаками при промахе." +L["stat.meleerange"] = "Дальность атаки" +L["stat.meleerange.desc"] = "Дальность атаки оружия." +L["stat.meleedelay"] = "Замах" +L["stat.meleedelay.desc"] = "Задержка между началом и попаданием атаки." + +L["stat.melee2damage"] = "Урон (Т)" +L["stat.melee2damage.desc"] = "Урон от тяжёлых атак." +L["stat.melee2attacktime"] = "Задержка атаки (Т)" +L["stat.melee2attacktime.desc"] = "Время между тяжёлыми атаками при попадании." +L["stat.melee2attackmisstime"] = "Задержка промаха (Т)" +L["stat.melee2attackmisstime.desc"] = "Время между тяжёлыми атаками при промахе." +L["stat.meleethrowdamage"] = "Урон (Бросок)" +L["stat.meleethrowdamage.desc"] = "Урон от брошенного оружия." +L["stat.meleethrowvelocity"] = "Скорость броска" +L["stat.meleethrowvelocity.desc"] = "Скорость полёта брошенного оружия." +L["stat.meleethrowtime"] = "Задержка броска" +L["stat.meleethrowtime.desc"] = "Время между бросками оружия." +L["stat.lifesteal"] = "Здоровье от урона" +L["stat.lifesteal.desc"] = "Доля нанесённого урона, превращаемая в здоровье." +L["stat.damagecharge"] = "Заряд от урона" +L["stat.damagecharge.desc"] = "Доля нанесённого урона, превращаемая в заряд." + +L["stat.damage"] = "Урон" +L["stat.damage.desc"] = "Урон на пулю на всех дистанциях." +L["stat.damage_max"] = "Урон вблизи" +L["stat.damage_max.desc"] = "Урон на пулю без падения урона." +L["stat.damage_min"] = "Урон вдали" +L["stat.damage_min.desc"] = "Урон на пулю при максимальном падении урона." +L["stat.explosivedamage"] = "Взрывной урон" +L["stat.explosivedamage.desc"] = "Урон от взрыва при попадании пули." +L["stat.explosiveradius"] = "Радиус взрыва" +L["stat.explosiveradius.desc"] = "Радиус взрыва при попадании пули." +L["stat.range_max"] = "Максимальная дальность" +L["stat.range_max.desc"] = "Дистанция максимального падения урона." +L["stat.range_min"] = "Минимальная дальность" +L["stat.range_min.desc"] = "Дистанция начала падения урона." +L["stat.postburstdelay"] = "Задержка очереди" +L["stat.postburstdelay.desc"] = "Время между очередями выстрелов." +L["stat.ammopershot"] = "Патронов за выстрел" +L["stat.ammopershot.desc"] = "Количество патронов за выстрел." +L["stat.num"] = "Количество пуль" +L["stat.num.desc"] = "Количество пуль за выстрел." +L["stat.peekpenalty"] = "Штраф выглядывания" +L["stat.peekpenalty.desc"] = "Разброс и раскачка при выглядывании." +L["stat.quickscope"] = "Штраф быстрого прицеливания" +L["stat.quickscope.desc"] = "Неточность при быстром прицеливании.\nШтраф исчезает за 0.2 секунды." +L["stat.vol_shoot"] = "Громкость" +L["stat.vol_shoot.desc"] = "Насколько слышен звук выстрела." +L["stat.damagemultnpc"] = "Урон по NPC" +L["stat.damagemultnpc.desc"] = "Урон по NPC и ботам умножается на это значение." + +L["stat.swaycrouch"] = "Раскачка в приседе" +L["stat.recoil"] = "Отдача" +L["stat.range"] = "Дальность" +L["stat.blindfiresway"] = "Раскачка вслепую" +L["stat.zoom"] = "Зум прицела" +L["stat.bloomintensity"] = "Интенсивность накопления" + +L["att.none"] = "Н/Д" + +L["att.procon.3proj"] = "Три снаряда" +L["att.procon.moreproj"] = "Больше снарядов" +L["att.procon.lessproj"] = "Меньше снарядов" +L["att.procon.1proj"] = "Один снаряд" +L["att.procon.noexp"] = "Без взрыва" +L["att.procon.direct"] = "Прямой огонь" +L["att.procon.doorbreach"] = "Взлом дверей" +L["att.procon.crowd"] = "Контроль толпы" +L["att.procon.nonlethal"] = "Несмертельное" +L["att.procon.detdelay"] = "Задержка детонации" +L["att.procon.flash"] = "Ослепление" +L["att.procon.airburst"] = "Воздушный взрыв" +L["att.procon.timedfuse"] = "Таймер взрыва" +L["att.procon.smoke"] = "Дымовая завеса" +L["att.procon.limb"] = "Урон по конечностям" +L["att.procon.head"] = "Урон в голову" +L["att.procon.chest"] = "Урон в торс" +L["att.procon.onebullet"] = "Одна пуля" +L["att.procon.armor"] = "Эффективность брони" +L["att.procon.nosafety"] = "Без предохранителя" +L["att.procon.radius"] = "Радиус взрыва" +L["att.procon.needprime"] = "Слабее без взведения" +L["att.procon.projrng"] = "Случайная траектория и урон" +L["att.procon.failrng"] = "Шанс отказа" +L["att.procon.notracer"] = "Скрытые трассеры" +L["att.procon.refund"] = "Высокий шанс возврата патронов" +L["att.procon.unreliable"] = "Ненадёжное" +L["att.procon.surplusboost1"] = "Иногда стреляет быстрее" +L["att.procon.surplusboost2"] = "Также неконтролируемо" +L["att.procon.meleeslow"] = "Замедление при ударе" +L["att.procon.gasimmune"] = "Иммунитет к газу" +L["att.procon.flashresist"] = "Сопротивление ослеплению" +L["att.procon.stunresist"] = "Сокращение оглушения" +L["att.procon.quickthrow"] = "Быстрый бросок" +L["att.procon.throwrocks"] = "Бросок камней" +L["att.procon.cornershot"] = "Обзор из-за угла" +L["att.procon.thermal"] = "Тепловизор при выглядывании" +L["att.procon.dmic"] = "Отображение врагов" +L["att.procon.audible"] = "Слышно другим" +L["att.procon.flashlight"] = "Освещение" +L["att.procon.blind"] = "Ослепление при взгляде" +L["att.procon.visible"] = "Видно другим" +L["att.procon.laser"] = "Указатель точки прицеливания" +L["att.procon.rf1"] = "Отображение дистанции и урона" +L["att.procon.rf2"] = "Визуализация падения пули" +L["att.procon.gauge1"] = "Отображение параметров" +L["att.procon.gauge2"] = "Визуализация разброса" +L["att.procon.auto"] = "Автоматический огонь" +L["att.procon.burst"] = "Очередь" +L["att.procon.semi"] = "Полуавтоматический" +L["att.procon.autoburst"] = "Автоматическая очередь" +L["att.procon.explosive"] = "Взрывное" +L["att.procon.reliability"] = "Надёжность" +L["att.procon.noscope"] = "Без прицела" +L["att.procon.conceal"] = "Скрытое в кобуре" +L["att.procon.armdelay"] = "Задержка взведения" +L["att.procon.proxfuse"] = "Датчик близости" +L["att.procon.magnifier"] = "Переменное увеличение" +L["att.procon.needscope"] = "Требуется оптика" +L["att.procon.bullet"] = "Запасная пуля" +L["att.procon.nopartialreloads"] = "Без частичной перезарядки" +L["att.procon.incendiary"] = "Поджог целей" +L["att.procon.blurpeek"] = "Размытие при выглядывании" +L["att.procon.aimrecoil"] = "Отдача (при прицеливании)" +L["att.procon.aimspread"] = "Разброс (при прицеливании)" +L["att.procon.aimrpm"] = "Скорострельность (при прицеливании)" +L["att.procon.firstround"] = "Первый выстрел - несмертельный снаряд" +L["att.procon.locktime"] = "Время захвата" +L["att.procon.semiactive"] = "Требуется постоянное наведение" +L["att.procon.proj.turn"] = "Маневренность снаряда" +L["att.procon.proj.speed"] = "Максимальная скорость снаряда" +L["att.procon.proj.direct"] = "Урон при попадании" +L["att.procon.shovel"] = "Стреляет лопатами" +L["att.procon.heal"] = "Лечебный эффект" +L["att.procon.infiniteammo"] = "Бесконечные патроны" + +L["att.sight.1"] = "Прицел 1x" +L["att.sight.1.25"] = "Прицел 1.25x" +L["att.sight.1.5"] = "Прицел 1.5x" +L["att.sight.1.75"] = "Прицел 1.75x" +L["att.zoom.2"] = "Зум 2x" +L["att.zoom.2.2"] = "Зум 2.2x" +L["att.zoom.3"] = "Зум 3x" +L["att.zoom.3.4"] = "Зум 3.4x" +L["att.zoom.3.5"] = "Зум 3.5x" +L["att.zoom.4"] = "Зум 4x" +L["att.zoom.5"] = "Зум 5x" +L["att.zoom.6"] = "Зум 6x" +L["att.zoom.8"] = "Зум 8x" +L["att.zoom.10"] = "Зум 10x" +L["att.zoom.12"] = "Зум 12x" + +L["spacer.damage"] = "Поражающий эффект" +L["spacer.damage.desc"] = "Характеристики урона и эффективной дальности." +L["spacer.action"] = "Действие" +L["spacer.action.desc"] = "Характеристики заряжания и перезарядки." +L["spacer.ballistics"] = "Баллистика" +L["spacer.ballistics.desc"] = "Характеристики траектории пули и пробития." +L["spacer.recoilbloom"] = "Отдача и накопление" +L["spacer.recoilbloom.desc"] = "Характеристики отдачи при длительной стрельбе." +L["spacer.mobility"] = "Мобильность" +L["spacer.mobility.desc"] = "Характеристики скорости передвижения." +L["spacer.maneuvering"] = "Маневрирование" +L["spacer.maneuvering.desc"] = "Характеристики без прицеливания." +L["spacer.handling"] = "Обращение" +L["spacer.handling.desc"] = "Характеристики готовности оружия." +L["spacer.sway"] = "Раскачка" +L["spacer.sway.desc"] = "Характеристики отклонения от точки прицеливания." +L["spacer.misc"] = "Разное" +L["spacer.misc.desc"] = "Прочие характеристики." + +L["trivia.year"] = "Год производства" +L["trivia.caliber"] = "Калибр" +L["trivia.manufacturer"] = "Производитель" +L["trivia.faction"] = "Фракция" +L["trivia.unknown"] = "Неизвестно" + +L["faction.coalition"] = "Коалиция" +L["faction.militia"] = "Милиция" +L["faction.neutral"] = "Нейтральные" + +L["menu.legacy"] = "Старое меню" + +L["hint.swap"] = "Заменить {weapon} на {weapon2}" +L["hint.pickup"] = "Подобрать {weapon}" +L["hint.unjam"] = "Устранить заклинивание" +L["hint.rp_biocode_cp"] = "Биокод - только для полиции" + +L["hint.melee"] = "Ближняя атака" +L["hint.melee.block"] = "Блок" +L["hint.melee.throw"] = "Бросок" +L["hint.melee.light"] = "Лёгкая атака" +L["hint.melee.heavy"] = "Тяжёлая атака" + +L["hint.peek.hold"] = "Выглянуть" +L["hint.peek.toggle"] = "Переключить выглядывание" +L["hint.hold_breath"] = "Задержать дыхание" +L["hint.customize"] = "Настроить" +L["hint.inspect"] = "Осмотреть" +L["hint.firemode"] = "Режим огня" +L["hint.safety.turn_off"] = "Снять с предохранителя" +L["hint.safety.turn_on"] = "Поставить на предохранитель" +L["hint.hl2_flashlight"] = "Фонарик костюма" +L["hint.toggle_tactical"] = "Переключить {1}" +L["hint.blindfire"] = "Стрельба вслепую" +L["hint.blindfire.menu"] = "Меню слепой стрельбы" +L["hint.blindfire.up"] = "Стрельба вверх" +L["hint.blindfire.left"] = "Стрельба влево" +L["hint.blindfire.right"] = "Стрельба вправо" +L["hint.blindfire.cancel"] = "Отменить слепую стрельбу" +L["hint.blindfire.kys"] = "Самоубийство" +L["hint.quicknade.over"] = "Бросок сверху" +L["hint.quicknade.under"] = "Бросок снизу" +L["hint.quicknade.throw"] = "Быстрый бросок" +L["hint.quicknade.pull_out"] = "Достать гранату" +L["hint.quicknade.menu"] = "Меню гранат" +L["hint.quicknade.next"] = "Следующая граната" +L["hint.quicknade.prev"] = "Предыдущая граната" +L["hint.ttt.radio"] = "TTT Радио" +L["hint.ttt.shop"] = "TTT Магазин" +L["hint.tac"] = "Тактическое" +L["hint.tac.radar"] = "Радар" +L["hint.tac.flashlight"] = "Фонарик" +L["hint.tac.laser"] = "Лазер" +L["hint.tac.combo"] = "Комбо" +L["hint.tac.rangefinder"] = "Дальномер" +L["hint.tac.spread_gauge"] = "Индикатор" +L["hint.tac.magnifier"] = "Увеличение" +L["hint.tac.cam_mode"] = "Режим камеры" +L["hint.tac.load_one"] = "Зарядить один патрон" + +L["hint.melee_lunge"] = "Выпад" +L["hint.melee_step"] = "Рывок в воздухе" +L["hint.melee_ninja.palm"] = "Удар ладонью" +L["hint.melee_ninja.backhop"] = "Прыжок назад" +L["hint.melee_ninja.divekick"] = "Удар в прыжке" + +L["hint.medkit.self"] = "Лечить себя" +L["hint.medkit.others"] = "Лечить других" + +L["hint.unlock"] = "Разблокировать" +L["hint.exitcustmenu"] = "Выйти из меню настройки" + +L["hint.nogrenades"] = "Нет гранат" + +L["hint.hold"] = "(Удерживать)" + +L["hint.shootself"] = "Выстрелить в себя" + +L["hint.melee_charge"] = "Рывок" +L["hint.melee_charge.menu"] = "Тип рывка" +L["hint.melee_charge.0.name"] = "Смелость" +L["hint.melee_charge.0.desc"] = "Стандартный режим со средним контролем поворота.\nТаран цели наносит урон и отбрасывание. Атака во время рывка отменяет его." +L["hint.melee_charge.1.name"] = "Мастерство" +L["hint.melee_charge.1.desc"] = "Значительно улучшенный контроль поворота.\nНизкая защита и урон тарана.\nПолучение урона сокращает длительность рывка." +L["hint.melee_charge.2.name"] = "Упорство" +L["hint.melee_charge.2.desc"] = "Увеличенная скорость рывка, сниженный контроль поворота.\nОчень высокая защита и урон тарана.\nИммунитет к отбрасыванию во время рывка." + +L["quicknade.fuse"] = "Взрыватель" +L["quicknade.ti_thermite.dettype"] = "Контактный" +L["quicknade.ti_flashbang.dettype"] = "2.5 сек" +L["quicknade.ti_c4.dettype"] = "Дистанционный" +L["quicknade.ti_smoke.dettype"] = "2 сек" +L["quicknade.ti_gas.dettype"] = "2 сек" +L["quicknade.ti_nuke.dettype"] = "10 сек" +L["quicknade.ti_charge.dettype"] = "Дистанционный" +L["quicknade.ti_heal.dettype"] = "Контактный" + +L["unknown"] = "Неизвестно" + +L["caliber.nato"] = "НАТО" +L["caliber.warpac"] = "Варшавский договор" + +L["caliber.22lr"] = ".22 LR" +L["caliber.9x18"] = "9×18мм" +L["caliber.9x19"] = "9×19мм" +L["caliber.380"] = ".380 ACP" +L["caliber.45acp"] = ".45 ACP" +L["caliber.10mm"] = "10мм Auto" +L["caliber.44mag"] = ".44 Magnum" +L["caliber.50ae"] = ".50 AE" +L["caliber.454"] = ".454 Casull" +L["caliber.500"] = ".500 S&W" +L["caliber.357"] = ".357 Magnum" +L["caliber.38"] = ".38 Special" +L["caliber.410"] = ".410 Bore" +L["caliber.57x28"] = "5.7×28мм" +L["caliber.46x30"] = "4.6×30мм" +L["caliber.20g"] = "20 Калибр" +L["caliber.12g"] = "12 Калибр" +L["caliber.10g"] = "10 Калибр" +L["caliber.4g"] = "4 Калибр" +L["caliber.25mm"] = "25мм" +L["caliber.303"] = ".303 British" +L["caliber.50bmg"] = ".50 BMG" +L["caliber.762x25"] = "7.62×25мм" +L["caliber.762x39"] = "7.62×39мм" +L["caliber.762x51"] = "7.62×51мм" +L["caliber.762x54"] = "7.62×54мм" +L["caliber.545x39"] = "5.45×39мм" +L["caliber.556x45"] = "5.56×45мм" +L["caliber.300blk"] = ".300 Blackout" +L["caliber.40mm"] = "40мм" +L["caliber.rocket"] = "Ракета" +L["caliber.rpg7"] = "РПГ-7" +L["caliber.at4"] = "AT4 HEDP" +L["caliber.grenade"] = "Граната" +L["caliber.grenade_frag"] = "Осколочная граната" +L["caliber.tack"] = "Кнопка" +L["caliber.rock"] = "Камень" +L["caliber.explosive"] = "Взрывчатка" +L["caliber.357sig"] = ".357 SIG" +L["caliber.9x39"] = "9×39мм" +L["caliber.6mmcreed"] = "6мм Creedmoor" +L["caliber.338"] = ".338 Lapua" +L["caliber.40sw"] = ".40 S&W" +L["caliber.22tcm"] = ".22 TCM" + +STL = {} +STL["unknown"] = "Неизвестно" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_sv-se.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_sv-se.lua new file mode 100644 index 0000000..47a31f0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_sv-se.lua @@ -0,0 +1,540 @@ +L = {} -- Swedish by Moka + +L["font.1"] = "Myriad Pro" +L["font.2"] = "HD44780A00 5x8" + +L["tier.0"] = "5Prisvärd" +L["tier.1"] = "4Konsumentklass" +L["tier.2"] = "3Säkerhetsklass" +L["tier.3"] = "2Operatörsklass" +L["tier.4"] = "1Elitklass" +L["tier.5"] = "0Exotisk" + +L["tier.spec"] = "9Special" +L["type.sidearm"] = "1Pistol" +L["type.magnum_pistol"] = "2Magnum-Pistol" +L["type.machine_pistol"] = "3Automatpistol" +L["type.dual_pistol"] = "3Akimbo-Vapen" +L["type.submachine_gun"] = "3Kulsprutepistol" +L["type.assault_rifle"] = "4Automatkarbin" +L["type.machine_gun"] = "5Kulspruta" +L["type.shotgun"] = "5Hagelbössa" +L["type.sporter_carbine"] = "5Sportgevär" +L["type.battle_rifle"] = "5Stridsgevär" +L["type.dmr"] = "6DMR" +L["type.sniper_rifle"] = "7Krypskyttegevär" +L["type.amr"] = "9Antimateriellgevär" +L["type.melee_sharp"] = "8Vasst Närstridsvapen" +L["type.melee_blunt"] = "8Trubbigt Närstridsvapen" +L["type.equipment"] = "9Utrustning" +L["type.throwable"] = "9Kastbart" +L["type.launcher"] = "6Kastare" +L["type.special_weapon"] = "7Specialvapen" + +L["cust.rating"] = "Rang" +L["cust.stats"] = "Stats" +L["cust.description"] = "BESKRIVNING:" -- used in-line in text boxes +L["cust.type_tier"] = "{type} ({tier})" +L["cust.trivia"] = "Trivia" +L["cust.credits"] = "Författare" +L["cust.description2"] = "Beskrivning" -- used as title +L["cust.drop_wep"] = "Släpp Vapen" +L["cust.drop_nade"] = "Släpp Granat" + +L["unit.hu"] = " HU" -- hammer unit +L["unit.meter"] = "m" +L["unit.second"] = "s" +L["unit.persecond"] = "/s" +L["unit.mps"] = "m/s" -- meters per second +L["unit.damage"] = " SKD" +L["unit.stk"] = " SAD" -- shots to kill +L["unit.ptk"] = " SAD" -- pellets to kill + +L["slot.default"] = "Plats" +L["slot.optic"] = "Sikte" +L["slot.muzzle"] = "Mynning" +L["slot.tactical"] = "Taktisk" +L["slot.accessory"] = "Tillbehör" +L["slot.bolt"] = "Slutstycke" +L["slot.trigger"] = "Avtryckare" +L["slot.ammo"] = "Ammo" +L["slot.perk"] = "Färdighet" +L["slot.melee_tech"] = "Teknik" +L["slot.melee_spec"] = "Special" +L["slot.melee_boost"] = "Boost" + +L["ammo.ti_flashbang"] = "Chockgranater" +L["ammo.ti_thermite"] = "Termitgranater" +L["ammo.ti_smoke"] = "Rökgranater" +L["ammo.ti_c4"] = "C4-Sprängladdningar" +L["ammo.ti_gas"] = "Tårgasgranater" +L["ammo.ti_nuke"] = "Nukleär Enhet" +L["ammo.ti_charge"] = "Brytladdning" +L["ammo.ti_sniper"] = "Antimateriell Ammo" +L["ammo.ti_heal"] = "Medi-Rökgranater" +L["ammo.ti_pistol_light"] = "Lätt Pistol Ammo" +L["ammo.ti_pistol_heavy"] = "Tung Pistol Ammo" +L["ammo.ti_pdw"] = "PDW Ammo" +L["ammo.ti_rifle"] = "Tung Gevärsammo" +L["ammo.ti_sniper"] = "PSG-Ammo" + +-- used when tacrp_ammonames 1 +L["ammo.357"] = "Magnum Ammo" +L["ammo.smg1"] = "Karbinammo" +L["ammo.ar2"] = "Gevärsammo" + +L["rating.score"] = "(Poäng: {score}/{max})" +L["rating.lethality"] = "Dödlighet" +L["rating.lethality.desc"] = "Hur lätt och snabbt vapnet kan ta ner ett mål.\nAnser inte rustning penetration.\nPåverkas av Skada och Eldhastighet." +L["rating.suppression"] = "Täckning" +L["rating.suppression.desc"] = "Hur mycket skada vapnet kan dela ut över en längre stund.\nAnser inte rustning penetration.\nPåverkas av Skada, Eldhastighet, Kapacitet och Omladdnings Tid." +L["rating.range"] = "Räckvidd" +L["rating.range.desc"] = "Hur lätt vapnet får eller tappar skada över längre distanser.\nPåverkas av Minimum Räckvidd, Maximal Räckvidd, och skadeavsläpp." +L["rating.precision"] = "Träffsäkerhet" +L["rating.precision.desc"] = "Hur träffsäker vapnet är när du skjuter enstaka skott eller korta salvor.\nPåverkas av Spridning och diverse Rekyl statistik." +L["rating.control"] = "Kontroll" +L["rating.control.desc"] = "Hur hanterlig vapnets rekyl och spridning är under kontinuerlig eld.\nPåverkas av Eldhastighet och diverse Rekyl statistik." +L["rating.handling"] = "Hantering" +L["rating.handling.desc"] = "Hur snabbt vapnet blir redo från spring, sikte och när den tas fram.\nPåverkas av Siktetid, Spring-till-Eld Tid och Ta Fram Tid." +L["rating.maneuvering"] = "Manövrering" +L["rating.maneuvering.desc"] = "Hur träffsäker vapnet är utan att sikta.\nPåverkas av Höftspridning, I Luft Spridning, Svängning och Fri Vy Vinkel." +L["rating.mobility"] = "Rörlighet" +L["rating.mobility.desc"] = "Hur snabbt användaren kan förflytta sig med vapnet.\nPåverkas av diverse Hastighet statistik." +L["rating.stability"] = "Stabilitet" +L["rating.stability.desc"] = "Hur mycket vapnets siktevy förflyttar sig.\nPåverkas av diverse Svängning statistik." + +L["stat.table.stat"] = "STAT" +L["stat.table.base"] = "BAS" +L["stat.table.curr"] = "NUV." +L["stat.table.diff"] = "SKILL." + +L["stat.raw_dps"] = "Rå SPM" +L["stat.raw_dps.desc"] = "Vapnets uträknad skada utan avsläpp.\nAnser inte träffgrupp skada multiplicerade eller rustning penetration." +L["stat.min_ttk"] = "Minimum TAD" +L["stat.min_ttk.desc"] = "Uträknad tid som krävs för att döda en måltavla från och med andra skottet.\nAnser inte skadeavsläpp och att målet har 100 livspoäng.\nAnser inte träffgrupp skade multiplicerare." +L["stat.armorpenetration"] = "Rustning Penetration" +L["stat.armorpenetration.desc"] = "Multiplicerare för livspoäng skada gjord när du träffar rustning.\nHögre värden betyder mindre skada till rustning och mer till liv.\n100% rustning ignoreras helt. 0% rustning blockerar all skada." +L["stat.armorbonus"] = "Rustning Strimlande" +L["stat.armorbonus.desc"] = "Multiplicerare för hur mycket extra rustning som förstörs.\nNotis: Höga RP-vapen strimlar mindre rustning tack vare att skadan\ngår direkt till livet. Vid 100% RP så görs ingen skada till rustningen." +L["stat.penetration"] = "Materialpenetration" +L["stat.penetration.desc"] = "Hur mycket metall vapnet kan skjuta genom.\nBeroende på ytans typ så kan penetrationens djup variera." +L["stat.spread"] = "Spridning" +L["stat.spread.desc"] = "Vapnets bas spridning." +L["stat.rpm"] = "Eldhastighet" +L["stat.rpm.desc"] = "Vapnets eldhastighet. Räknas i skott i minuten." +L["stat.rpm_burst"] = "Eldhastighet (Salvo)" +L["stat.rpm_burst.desc"] = "Vapnets eldhastighet i salvoläge, i SM." +L["stat.rpm_semi"] = "Eldhastighet (Enkel)" +L["stat.rpm_semi.desc"] = "Vapnets eldhastighet i enkelskottsläge, i SM." +L["stat.rpm_burst_peak"] = "Eldhastighet (Topp S.)" +L["stat.rpm_burst_peak.desc"] = "Högsta eldhastigheten som kan nås, inklusive efter salvo fördröjningen,\ni SM." +L["stat.clipsize"] = "Kapacitet" +L["stat.clipsize.desc"] = "Antal ammunition vapnet kan hålla på en gång." +L["stat.sprinttofire"] = "Spring-till-Eld" +L["stat.sprinttofire.desc"] = "Tid som krävs för att gå från att springa till att kunna skjuta." +L["stat.aimdownsights"] = "Siktetid" +L["stat.aimdownsights.desc"] = "Tid som krävs för att gå från höften till siktet." +L["stat.muzzlevelocity"] = "Mynningshastighet" +L["stat.muzzlevelocity.desc"] = "Hur snabbt projektilen förflyttar sig i världen.\nKontrollerar också avståndströskeln innan skott blir \"hitscan\"." +L["stat.recoilkick"] = "Rekylkick" +L["stat.recoilkick.desc"] = "Styrkan av \"känd rekyl\" som förflyttar din vy." +L["stat.recoilstability"] = "Rekylstabilitet" +L["stat.recoilstability.desc"] = "Hur förutsägbar rekylkicken är.\nHögre värden betyder att rekylen blir mer vertikal och mindre horisontal." +L["stat.recoilspread"] = "Blom till Spridning" +L["stat.recoilspread.desc"] = "Ytterlig spridning per enhet av blom.\nHögre värden betyder att vapnet blir mindre träffsäker under kontinuerlig eld." +L["stat.recoilspread2"] = "Blom till Rekyl" +L["stat.recoilspread2.desc"] = "Ytterlig rekylkick per enhet av blom.\nHögre värden betyder att vapnets rekyl blir starkare under kontinuerlig eld." +L["stat.recoildissipation"] = "Blom Återhämtning" +L["stat.recoildissipation.desc"] = "Hur snabbt samlad blom försvinner.\nHögre värden betyder att blom försvinner snabbare efter\nåterställningstiden." +L["stat.recoilresettime"] = "Blom Nedkylning" +L["stat.recoilresettime.desc"] = "Fördröjning efter eld innan blom börjar försvinna.\nHögre värden betyder att du måste vänta längre mellan skott\ninnan blom börjar försvinna." +L["stat.recoilmaximum"] = "Maximal Blom" +L["stat.recoilmaximum.desc"] = "Översta blom gränsen som orsakas av kontinuerlig eld." +L["stat.recoilfirstshot"] = "Initial Blom" +L["stat.recoilfirstshot.desc"] = "Blom multiplicerare när du skjuter första skottet.\nTillämpas när det inte finns någon samlad blom." +L["stat.recoilpershot"] = "Mottagen Blom" +L["stat.recoilpershot.desc"] = "Hur mycket blom som läggs till per skott." +L["stat.recoilcrouch"] = "Rekyl (Hukad)" +L["stat.recoilcrouch.desc"] = "Multiplicerad rekyl när du är hukad och inte förflyttar dig." +L["stat.movespeed"] = "Rörelsehastighet" +L["stat.movespeed.desc"] = "Multiplicerad rörelsehastighet när vapnet hålls uppe.\nSänks inte när vapnet är säkrat." +L["stat.shootingspeed"] = "Rörelseh. under Eld" +L["stat.shootingspeed.desc"] = "Multiplicerad hastighet under eld.\nSamlad rekyl ökar hastighetsstyrkan." +L["stat.sightedspeed"] = "Rörelseh. i Siktet" +L["stat.sightedspeed.desc"] = "Multiplicerad hastighet i siktet." +L["stat.reloadspeed"] = "Rörelseh. Omladd." +L["stat.reloadspeed.desc"] = "Multiplicerad hastighet under omladdning." +L["stat.meleespeed"] = "Rörelseh. Närstrid" +L["stat.meleespeed.desc"] = "Multiplicerad hastighet när du anfaller i närstrid." +L["stat.reloadtime"] = "Omladdningstid" +L["stat.reloadtime.desc"] = "Hur lång tid som krävs för att utföra en omladdning." +L["stat.deploytime"] = "Ta Fram Tid" +L["stat.deploytime.desc"] = "Hur lång tid som krävs för att ta fram vapnet." +L["stat.holstertime"] = "Hölster Tid" +L["stat.holstertime.desc"] = "Hur lång tid som krävs för att lägga undan vapnet." +L["stat.sway"] = "Svängning" +L["stat.sway.desc"] = "Hur mycket svängning från höften.\nSvängning påverkar din vy under eld utan att ändra din siktevy." +L["stat.scopedsway"] = "Svängning i Siktet" +L["stat.scopedsway.desc"] = "Hur mycket svängning i siktet.\nSvängning påverkar din vy under eld utan att ändra din siktevy." +L["stat.swaycrouch"] = "Svängning (Hukad)" +L["stat.swaycrouch.desc"] = "Multiplicerad svängning när du är hukad." +L["stat.midairspread"] = "Spridning i Luften" +L["stat.midairspread.desc"] = "Hur mycket träffsäkerhet som förloras när du inte står på marken.\nHalvt tillämpad när du simmar och klättrar stegar." +L["stat.hipfirespread"] = "Spridning från Höften" +L["stat.hipfirespread.desc"] = "Hur mycket träffsäkerhet som finns under eld från höften." +L["stat.movespread"] = "Spridning under Rörelse" +L["stat.movespread.desc"] = "Hur mycket träffsäkerhet som finns under rörelse.\nStyrkan relaterad till nuvarande hastighet." +L["stat.meleedamage"] = "Närstridsskada" +L["stat.meleedamage.desc"] = "Skada som delas ut när du anfaller i närstrid." +L["stat.firemode"] = "Eldlägen" +L["stat.firemode.desc"] = "Vapnets eldlägesförmåga." +L["stat.freeaimangle"] = "Fri Vy Vinkel" +L["stat.freeaimangle.desc"] = "Hur mycket maximal avvikelse från siktepunkten under eld från höften." +L["stat.shotstofail"] = "Elaka Skott till Avbrott" +L["stat.shotstofail.desc"] = "Genomsnitt antal skott som skjuts innan vapnet får avbrott." + +L["stat.meleeperkstr"] = "Styrka" +L["stat.meleeperkstr.desc"] = "Närstridsattribut påverkar Tekniker och Specialer.\nPåverkar färdighetens skada och tillbakatryck." +L["stat.meleeperkagi"] = "Smidighet" +L["stat.meleeperkagi.desc"] = "Närstridsattribut påverkar Tekniker och Specialer.\nPåverkar färdighetens rörelsekraft och anfallstid." +L["stat.meleeperkint"] = "Strategi" +L["stat.meleeperkint.desc"] = "Närstridsattribut påverkar Tekniker och Specialer.\nPåverkar färdighetens laddtid och projektilstyrka." + +L["rating.meleeattacktime"] = "Snabbhet" +L["rating.meleeattacktime.desc"] = "Hur snabbt vapnet kan anfalla." +L["stat.damage.desc_melee"] = "Skada gjord från Primära Anfall" +L["stat.meleeattacktime"] = "Närstrids. Fördröjning" +L["stat.meleeattacktime.desc"] = "Hur lång tid mellan varje anfall om den träffar något." +L["stat.meleeattackmisstime"] = "Närstrids. Miss Förd." +L["stat.meleeattackmisstime.desc"] = "Hur lång tid mellan varje anfall om den inte träffar något." +L["stat.meleerange"] = "Närstridsräckvidd" +L["stat.meleerange.desc"] = "Hur långt vapnet kan nå med dess anfall." +L["stat.meleedelay"] = "Närstridstid" +L["stat.meleedelay.desc"] = "Fördröjning mellan att ett anfall påbörjas och landar." + +L["stat.melee2damage"] = "Skada (T)" +L["stat.melee2damage.desc"] = "Skada som görs av tunga anfall." +L["stat.melee2attacktime"] = "Närstrids. Fördröj. (T)" +L["stat.melee2attacktime.desc"] = "Hur lång tid mellan varje tungt anfall om den träffar något." +L["stat.melee2attackmisstime"] = "Närstrids. Miss F. (T)" +L["stat.melee2attackmisstime.desc"] = "Hur lång tid mellan varje tungt anfall om den inte träffar något." +L["stat.meleethrowdamage"] = "Skada (Kast)" +L["stat.meleethrowdamage.desc"] = "Skada som görs av kastade vapen." +L["stat.meleethrowvelocity"] = "Kast Hastighet" +L["stat.meleethrowvelocity.desc"] = "Kasthastighet på kastade vapen." +L["stat.meleethrowtime"] = "Kast Fördröjning" +L["stat.meleethrowtime.desc"] = "Hur lång tid mellan varje kastat vapen." +L["stat.lifesteal"] = "Liv från Skada" +L["stat.lifesteal.desc"] = "Bråkdel av skadan som görs som byts till livspoäng." +L["stat.damagecharge"] = "Laddning från Skada" +L["stat.damagecharge.desc"] = "Bråkdel av skadan som görs som byts till färdighetsladdning." + +L["stat.damage"] = "Skada" +L["stat.damage.desc"] = "Skada per skott vid all räckvidd." +L["stat.damage_max"] = "Skada på Nära Distans" +L["stat.damage_max.desc"] = "Skada per skott utan räckvidd avsläpp eller upprampning." +L["stat.damage_min"] = "Skada på Lång Distans" +L["stat.damage_min.desc"] = "Skada per skott vid maximal räckvidd avsläpp eller upprampning." +L["stat.explosivedamage"] = "Explosiv Skada" +L["stat.explosivedamage.desc"] = "Skada som görs av explosioner vid skotträff." +L["stat.explosiveradius"] = "Explosiv Radie" +L["stat.explosiveradius.desc"] = "Explosionens sprängradie vid skotträff." +L["stat.range_max"] = "Maximal Räckvidd" +L["stat.range_max.desc"] = "Distans där räckvidd avsläpp eller upprampning är högst." +L["stat.range_min"] = "Minimum Räckvidd" +L["stat.range_min.desc"] = "Distans där räckvidd avsläpp eller upprampning påbörjar." +L["stat.postburstdelay"] = "Salvo Fördröjning" +L["stat.postburstdelay.desc"] = "Tid som krävs för att vapnet ska återställas efter varje salvo." +L["stat.ammopershot"] = "Ammo per Skott" +L["stat.ammopershot.desc"] = "Antal ammo som används per skott." +L["stat.num"] = "Skottantal" +L["stat.num.desc"] = "Antal skott som skjuts per patron." +L["stat.peekpenalty"] = "Kik Straff" +L["stat.peekpenalty.desc"] = "Antal spridning från höften och svängning som tillämpas när du kikar." +L["stat.quickscope"] = "\"Quickscope\" Straff" +L["stat.quickscope.desc"] = "Hur mycket träffsäkerhet som förloras när du går in och ur siktet.\nBestraffningen försvinner efter 0,2 sekunder." +L["stat.vol_shoot"] = "Högljuddhet" +L["stat.vol_shoot.desc"] = "Hur högljudd vapnets skottljud är." +L["stat.damagemultnpc"] = "NPC Skada" +L["stat.damagemultnpc.desc"] = "Icke-närstridsskada mot NPC:er och Nextbots multipliceras med detta värde." + +-- not in stats page but attachments may use +L["stat.swaycrouch"] = "Svängning (Hukad)" +L["stat.recoil"] = "Rekyl" +L["stat.range"] = "Räckvidd" +L["stat.blindfiresway"] = "Svängning (Blind Eld)" +L["stat.zoom"] = "Siktezoom" +L["stat.bloomintensity"] = "Blom Styrka" + +L["att.none"] = "Inget" + +L["att.procon.3proj"] = "Tre projektiler" +L["att.procon.moreproj"] = "Fler projektiler" +L["att.procon.lessproj"] = "Färre projektiler" +L["att.procon.1proj"] = "En projektil" +L["att.procon.noexp"] = "Ingen explosion" +L["att.procon.direct"] = "Direkt eld" +L["att.procon.doorbreach"] = "Kan bryta ner dörrar" +L["att.procon.crowd"] = "Publikkontroll" +L["att.procon.nonlethal"] = "Icke-dödlig" +L["att.procon.detdelay"] = "Fördröjd sprängning" +L["att.procon.flash"] = "Chockgranateffekt" +L["att.procon.airburst"] = "Sprängs i luften" +L["att.procon.timedfuse"] = "Tidsinställd säkring" +L["att.procon.smoke"] = "Rökskärmseffekt" +L["att.procon.limb"] = "Skada på lem" +L["att.procon.head"] = "Skada till huvudet" +L["att.procon.chest"] = "Skada i brösten" +L["att.procon.onebullet"] = "En patron" +L["att.procon.armor"] = "Rustning effektivitet" +L["att.procon.nosafety"] = "Ingen säkringstid" +L["att.procon.radius"] = "Sprängradie" +L["att.procon.needprime"] = "Svag om säkrad" +L["att.procon.projrng"] = "Slumpmässig bana och skada" +L["att.procon.failrng"] = "Chans för dramatiskt fel" +L["att.procon.notracer"] = "Gömda Spårljus" +L["att.procon.refund"] = "Hög chans att få tillbaka ammo" +L["att.procon.unreliable"] = "Opålitlig" +L["att.procon.surplusboost1"] = "Skjuter ibland snabbare" +L["att.procon.surplusboost2"] = "Skjuter också okontrollerbart" +L["att.procon.meleeslow"] = "Sakta ned mål vid närstridsträff" +L["att.procon.gasimmune"] = "Immun mot Tårgas" +L["att.procon.flashresist"] = "Motstånd mot Chockgranater" +L["att.procon.stunresist"] = "Sänkt chocktid" +L["att.procon.quickthrow"] = "Snabbare Snabbkast" +L["att.procon.throwrocks"] = "Kasta Stenar" +L["att.procon.cornershot"] = "Hörnvy under kikande eld" +L["att.procon.thermal"] = "Värmeseende överlägg under kikande" +L["att.procon.dmic"] = "Visa nära måltavlor" +L["att.procon.audible"] = "Hörbart av andra" +L["att.procon.flashlight"] = "Light up area" +L["att.procon.blind"] = "Bländer när man tittar" +L["att.procon.visible"] = "Synlig av andra" +L["att.procon.laser"] = "Visar siktepunkten" +L["att.procon.rf1"] = "Visar distans och skada vid räckvidd" +L["att.procon.rf2"] = "Skadeavsläpp visualisering i siktet" +L["att.procon.gauge1"] = "Visa blom, svängning och spridning" +L["att.procon.gauge2"] = "Spridning visualisering i siktet" +L["att.procon.auto"] = "Automateld" +L["att.procon.burst"] = "Salvoeld" +L["att.procon.semi"] = "Halvautomateld" +L["att.procon.autoburst"] = "Automatsalvo" +L["att.procon.explosive"] = "Explosiv" +L["att.procon.reliability"] = "Pålitlighet" +L["att.procon.noscope"] = "Inget Sikte" +L["att.procon.conceal"] = "Göm vapnet när den är undanplockad" +L["att.procon.armdelay"] = "Säkringsfördröjning" +L["att.procon.proxfuse"] = "Närhetssäkring" +L["att.procon.magnifier"] = "Varierad Magnifiering" +L["att.procon.needscope"] = "Kräver Sikte" +L["att.procon.bullet"] = "Akut Patron" +L["att.procon.nopartialreloads"] = "Inga delvisa omladdningar" +L["att.procon.incendiary"] = "Tänder fyr på mål" +L["att.procon.blurpeek"] = "Sudd under kikande" +L["att.procon.aimrecoil"] = "Rekyl (i Siktet)" +L["att.procon.aimspread"] = "Spridning (i Siktet)" +L["att.procon.aimrpm"] = "Eldhastighet (i Siktet)" +L["att.procon.firstround"] = "Första skottet skjuter mindre dödlig projektil" +L["att.procon.locktime"] = "Låstid" +L["att.procon.semiactive"] = "Kräver konstant guidande" +L["att.procon.proj.turn"] = "Projektil Manövrering" +L["att.procon.proj.speed"] = "Projektil Toppfart" +L["att.procon.proj.direct"] = "Träffskada" +L["att.procon.shovel"] = "Skjuter spadar" +L["att.procon.heal"] = "Healande effekt" +L["att.procon.infiniteammo"] = "Oändlig Ammo" + +L["att.sight.1"] = "1x Sikte" +L["att.sight.1.25"] = "1,25x Sikte" +L["att.sight.1.5"] = "1,5x Sikte" +L["att.sight.1.75"] = "1,75x Sikte" +L["att.zoom.2"] = "2x Zoom" +L["att.zoom.2.2"] = "2,2x Zoom" +L["att.zoom.3"] = "3x Zoom" +L["att.zoom.3.4"] = "3,4x Zoom" +L["att.zoom.3.5"] = "3,5x Zoom" +L["att.zoom.4"] = "4x Zoom" +L["att.zoom.5"] = "5x Zoom" +L["att.zoom.6"] = "6x Zoom" +L["att.zoom.8"] = "8x Zoom" +L["att.zoom.10"] = "10x Zoom" +L["att.zoom.12"] = "12x Zoom" + +L["spacer.damage"] = "Terminal Effekt" +L["spacer.damage.desc"] = "Statistik som handlar om vapnets skada och effektiv räckvidd." +L["spacer.action"] = "Åtgärd" +L["spacer.action.desc"] = "Statistik som handlar om vapnets laddning." +L["spacer.ballistics"] = "Ballistics" +L["spacer.ballistics.desc"] = "Statistik som handlar om vapnets skottbana och effekt på material." +L["spacer.recoilbloom"] = "Rekyl & Blom" +L["spacer.recoilbloom.desc"] = "Statistik som handlar om vapnets kick och ostabilitet under kontinuerlig eld." +L["spacer.mobility"] = "Rörlighet" +L["spacer.mobility.desc"] = "Statistik som handlar om rörelsehastighet när vapnet används." +L["spacer.maneuvering"] = "Manövrering" +L["spacer.maneuvering.desc"] = "Statistik som handlar om vapnets prestanda utan att sikta." +L["spacer.handling"] = "Hantering" +L["spacer.handling.desc"] = "Statistik som handlar om hur snabbt vapnet blir redo." +L["spacer.sway"] = "Svängning" +L["spacer.sway.desc"] = "Statistik som handlar om vapnets avvikelse från siktepunkten." +L["spacer.misc"] = "Annat" +L["spacer.misc.desc"] = "Statistik som inte passar i någon större kategori." + +L["trivia.year"] = "Produktionsår" +L["trivia.caliber"] = "Kaliber" +L["trivia.manufacturer"] = "Tillverkare" +L["trivia.faction"] = "Lag" +L["trivia.unknown"] = "Okänd" + +L["faction.coalition"] = "Koalition" -- "Counter-Terrorists" +L["faction.militia"] = "Milis" -- "Terrorists" +L["faction.neutral"] = "Alliansfri" -- non-faction specific + +L["menu.legacy"] = "Gammaldags Meny" + +L["hint.swap"] = "Byt {weapon} mot {weapon2}" +L["hint.pickup"] = "Plocka upp {weapon}" +L["hint.unjam"] = "Fixa Eldavbrott" +L["hint.rp_biocode_cp"] = "Biokodad - Endast För Polis" + +L["hint.melee"] = "Närstridsanfall" +L["hint.melee.block"] = "Blockera" +L["hint.melee.throw"] = "Närstridskast" +L["hint.melee.light"] = "Lätt Anfall" +L["hint.melee.heavy"] = "Tungt Anfall" + +L["hint.peek.hold"] = "Kika" +L["hint.peek.toggle"] = "Växla Kika" +L["hint.hold_breath"] = "Håll Andan" +L["hint.customize"] = "Anpassa" +L["hint.inspect"] = "Inspektera" +L["hint.firemode"] = "Eldläge" +L["hint.safety.turn_off"] = "Avaktivera Säkring" +L["hint.safety.turn_on"] = "Aktivera Säkring" +L["hint.hl2_flashlight"] = "Dräkt Ficklampa" +L["hint.toggle_tactical"] = "Växla {1}" +L["hint.blindfire"] = "Blindeld" +L["hint.blindfire.menu"] = "Blindeld Meny" +L["hint.blindfire.up"] = "Blindeld Upp" +L["hint.blindfire.left"] = "Blindeld Vänster" +L["hint.blindfire.right"] = "Blindeld Höger" +L["hint.blindfire.cancel"] = "Avbryt Blindeld" +L["hint.blindfire.kys"] = "Begå Självmord" +L["hint.quicknade.over"] = "Överhand Kast" +L["hint.quicknade.under"] = "Underhand Kast" +L["hint.quicknade.throw"] = "Snabbkast" +L["hint.quicknade.pull_out"] = "Ta fram Granat" +L["hint.quicknade.menu"] = "Snabbkast Meny" +L["hint.quicknade.next"] = "Nästa Granat" +L["hint.quicknade.prev"] = "Föregående Granat" +L["hint.ttt.radio"] = "TTT-Radio" +L["hint.ttt.shop"] = "TTT-Butik" +L["hint.tac"] = "Taktisk" -- default value +L["hint.tac.radar"] = "Radar" +L["hint.tac.flashlight"] = "Ficklampa" +L["hint.tac.laser"] = "Laser" +L["hint.tac.combo"] = "Kombo" +L["hint.tac.rangefinder"] = "Distansräk." +L["hint.tac.spread_gauge"] = "Mätare" +L["hint.tac.magnifier"] = "Magnifierare" +L["hint.tac.cam_mode"] = "Kam. Läge" -- Used for both Corner-Cam and Thermal Imager +L["hint.tac.load_one"] = "Ladda Ett Skott" + +L["hint.melee_lunge"] = "Longera" +L["hint.melee_step"] = "Rus i Luften" +L["hint.melee_ninja.palm"] = "Palmslag" +L["hint.melee_ninja.backhop"] = "Tillbakahopp" +L["hint.melee_ninja.divekick"] = "Dykspark" + +L["hint.medkit.self"] = "Heala Själv" +L["hint.medkit.others"] = "Heala Andra" + +L["hint.unlock"] = "Lås Upp" +L["hint.exitcustmenu"] = "Stäng Anpassningsmenyn" + +L["hint.nogrenades"] = "Inga Granater Tillgängliga" + +L["hint.hold"] = "(Håll)" + +L["hint.shootself"] = "Skjut Dig Själv" + +-- For demoknight charge attachment +L["hint.melee_charge"] = "Anfall" +L["hint.melee_charge.menu"] = "Anfallsläge" +L["hint.melee_charge.0.name"] = "Modighet" +L["hint.melee_charge.0.desc"] = "Standardläge med genomsnitt vridningskontroll.\nAnfall in i en måltavla för att göra skada och tillbakatryck. Om du attackerar under anfallet så avbryts den." +L["hint.melee_charge.1.name"] = "Mästare" +L["hint.melee_charge.1.desc"] = "Drastiskt ökad vridningskontroll.\nLågt skademotstånd och anfallsskada.\nAnfallstiden sänks om du tar skada." +L["hint.melee_charge.2.name"] = "Envishet" +L["hint.melee_charge.2.desc"] = "Ökad anfallshastighet, sänkt vridningskontroll.\nVäldigt högt skademotstånd och anfallsskada.\nImmun mot tillbakatryck under anfall." + +-- Weapon calibers +L["caliber.556x45"] = "5,56 × 45 mm" +L["caliber.762x39"] = "7,62 × 39 mm" +L["caliber.762x51"] = "7,62 × 51 mm" +L["caliber.50bmg"] = ".50 BMG" +L["caliber.16g"] = "Kaliber 16" +L["caliber.12g"] = "Kaliber 12" +L["caliber.40mmg"] = "40 mm Granater" +L["caliber.9x19"] = "9 × 19 mm" +L["caliber.57x28"] = "FN 5,7 × 28 mm" +L["caliber.45acp"] = ".45 ACP" +L["caliber.380acp"] = ".380 ACP" +L["caliber.homing"] = "Infraröd Sökande Robot" +L["caliber.40sw"] = ".40 S&W" +L["caliber.23x75"] = "23 × 75 mmR" +L["caliber.223r"] = ".223 Remington" +L["caliber.46x30"] = "HK 4,6 × 30mm" +L["caliber.357"] = ".357 Magnum" +L["caliber.357sig"] = ".357 SIG" +L["caliber.6mm"] = "6 mm Whisper" +L["caliber.40mmr"] = "40 mm Granater" +L["caliber.32acp"] = ".32 ACP" +L["caliber.308"] = ".308 Winchester" +L["caliber.65mm"] = "6,5 mm Creedmoor" + +L["caliber.44amp"] = ".44 AMP" +L["caliber.40x46"] = "40 × 46 mm" +L["caliber.50ae"] = ".50 AE" +L["caliber.792x57"] = "7,92 × 57 mm Mauser" +L["caliber.500sw"] = ".500 S&W Magnum" +L["caliber.22lr"] = ".22 LR" +L["caliber.38special"] = ".38 Special" +L["caliber.338"] = ".338 Lapua Magnum" +L["caliber.9x39"] = "9 × 39 mm" + +L["caliber.44p"] = ".44 Tändhatt" +L["caliber.300blk"] = ".300 Blackout" +L["caliber.9x18"] = "9 × 18 mm" +L["caliber.4570"] = ".45-70 Govt." +L["caliber.9x21"] = "9 × 21 mm IMI" +L["caliber.762x54"] = "7,62 × 54 mmR" +L["caliber.762x25"] = "7,62 × 25 mm Tokarev" +L["caliber.300winmag"] = ".300 Win Mag" + +L["caliber.545x39"] = "5,45 × 39 mm" + +L["caliber.410b"] = ".410 Bore" +L["caliber.408ct"] = ".408 Cheyenne Tactical" +L["caliber.950jdj"] = ".950 JDJ" +L["caliber.600ne"] = ".600 Nitro Express" +L["caliber.83mmr"] = "83 mm Robotar" +L["caliber.25x40"] = "25 × 40 mm" + +L["caliber.44m"] = ".44 Magnum" +L["caliber.763x25"] = "7,63 × 25 mm Mauser" +L["caliber.277sig"] = ".277 SIG Fury" +L["caliber.10auto"] = "10 mm Auto" +L["caliber.75x55"] = "7,5 × 55 mm" +L["caliber.44-40"] = ".44-40 WCF" + +L["caliber.20g"] = "Kaliber 20" +L["caliber.9wm"] = "9 mm Win Mag" +L["caliber.44s"] = ".44 Special" +L["caliber.303b"] = ".303 Britiskt" +L["caliber.66mmr"] = "66 mm Raketer" +L["caliber.25mmf"] = "25 mm Signalskott" +L["caliber.270w"] = ".270 Winchester" + +L["caliber.45s"] = ".45 Super" + +L["unknown"] = "Okänd" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_zh-cn.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_zh-cn.lua new file mode 100644 index 0000000..e5d0942 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/base_zh-cn.lua @@ -0,0 +1,545 @@ +L = {} -- Traditional Chinese + +L["font.1"] = "Microsoft YaHei" +-- L["font.2"] = "HD44780A00 5x8" + +L["tier.0"] = "5下等" +L["tier.1"] = "4零售" +L["tier.2"] = "3警备" +L["tier.3"] = "2特遣" +L["tier.4"] = "1精英" +L["tier.5"] = "0特异" + +L["tier.spec"] = "9特殊" +L["type.sidearm"] = "1手枪" +L["type.magnum_pistol"] = "2马格南手枪" +L["type.machine_pistol"] = "3自动手枪" +L["type.dual_pistol"] = "3双枪" +L["type.submachine_gun"] = "3冲锋枪" +L["type.assault_rifle"] = "4突击步枪" +L["type.machine_gun"] = "5机枪" +L["type.shotgun"] = "5霰弹枪" +L["type.sporter_carbine"] = "5民用枪械" +L["type.battle_rifle"] = "5战斗步枪" +L["type.dmr"] = "6射手步枪" +L["type.sniper_rifle"] = "7狙击枪" +-- L["type.amr"] = "9Anti-Materiel Rifle" +L["type.melee_sharp"] = "8刀刃近战武器" +L["type.melee_blunt"] = "8钝头近战武器" +L["type.equipment"] = "9装备" +L["type.throwable"] = "9投掷物" +L["type.launcher"] = "6发射器" +L["type.special_weapon"] = "7特殊武器" + +L["cust.rating"] = "评分" +L["cust.stats"] = "属性" +L["cust.description"] = "简介:" +L["cust.type_tier"] = "{tier}{type}" +L["cust.trivia"] = "细节" +L["cust.credits"] = "作者" +L["cust.description2"] = "简介" +L["cust.drop_wep"] = "丢弃武器" +L["cust.drop_nade"] = "丢弃投掷物" + +L["unit.hu"] = " HU" +L["unit.meter"] = "m" +L["unit.second"] = "s" +L["unit.persecond"] = "/s" +L["unit.mps"] = "m/s" -- meters per second +L["unit.damage"] = "伤害" +L["unit.stk"] = "发致命" -- shots to kill +L["unit.ptk"] = "颗致命" -- pellets to kill + +L["slot.default"] = "槽位" +L["slot.optic"] = "瞄具" +L["slot.muzzle"] = "枪口" +L["slot.tactical"] = "战术" +L["slot.accessory"] = "附件" +L["slot.bolt"] = "枪栓" +L["slot.trigger"] = "扳机" +L["slot.ammo"] = "弹种" +L["slot.perk"] = "专长" +L["slot.melee_tech"] = "手法" +L["slot.melee_spec"] = "特技" +-- L["slot.melee_boost"] = "Boost" + +L["ammo.ti_flashbang"] = "闪光弹" +L["ammo.ti_thermite"] = "燃烧弹" +L["ammo.ti_smoke"] = "烟雾弹" +L["ammo.ti_c4"] = "C4炸药" +L["ammo.ti_gas"] = "毒气弹" +L["ammo.ti_nuke"] = "核装置" +L["ammo.ti_charge"] = "破门炸药" +L["ammo.ti_sniper"] = "反器材弹药" +L["ammo.ti_heal"] = "治疗弹" +-- L["ammo.ti_pistol_light"] = "Light Pistol Ammo" +-- L["ammo.ti_pistol_heavy"] = "Heavy Pistol Ammo" +-- L["ammo.ti_pdw"] = "PDW Ammo" +-- L["ammo.ti_rifle"] = "Heavy Rifle Ammo" +-- L["ammo.ti_sniper"] = "Sniper Ammo" + +-- used when tacrp_ammonames 1 +L["ammo.357"] = "马格南弹药" +L["ammo.smg1"] = "卡宾枪弹药" +L["ammo.ar2"] = "步枪弹药" + +L["rating.score"] = "(得分: {score}/{max})" +L["rating.lethality"] = "爆发火力" +L["rating.lethality.desc"] = "武器快速消灭单个目标的能力。\n相关属性:伤害,射速。" +L["rating.suppression"] = "持续火力" +L["rating.suppression.desc"] = "武器持续输出伤害的能力。\n相关属性:伤害,射速,容量,装填耗时。" +L["rating.range"] = "有效射程" +L["rating.range.desc"] = "武器随距离增减保持伤害的能力。\n相关属性:射程,伤害衰落。" +L["rating.precision"] = "精准度" +L["rating.precision.desc"] = "武器单发或短点射时保持精准的能力。\n相关属性:散射,后坐力。" +L["rating.control"] = "可控度" +L["rating.control.desc"] = "武器持续开火时控制后坐力和保持精准的能力。\n相关属性:射速,后坐力。" +L["rating.handling"] = "手感" +L["rating.handling.desc"] = "武器快速进入准备状态的能力。\n相关属性:瞄准耗时,举起耗时,切换耗时。" +L["rating.maneuvering"] = "机动性" +L["rating.maneuvering.desc"] = "武器不瞄准时保持精准的能力。\n相关属性:腰射散射,空中散射,自由准心,摇晃。" +L["rating.mobility"] = "总体移速" +L["rating.mobility.desc"] = "使用武器时保持移动速度的能力。\n相关属性:各类移动属性。" +L["rating.stability"] = "稳定性" +L["rating.stability.desc"] = "武器瞄准点的随机移动程度。\n相关属性:各类摇晃属性。" + +L["stat.table.stat"] = "属性名" +L["stat.table.base"] = "基础值" +L["stat.table.curr"] = "当前值" +L["stat.table.diff"] = "差别" + +L["stat.raw_dps"] = "每秒伤害(理想值)" +L["stat.raw_dps.desc"] = "推算得出的,武器每秒能打出的最高伤害。\n不计算头部和肢体伤害倍率。" +L["stat.min_ttk"] = "击杀耗时(理想值)" +L["stat.min_ttk.desc"] = "推算得出的,武器击杀目标需要的最低时间。\n假设目标无护甲,生命值等同于你的生命上限。\n不计算头部和肢体伤害倍率。" +L["stat.armorpenetration"] = "护甲穿透率" +L["stat.armorpenetration.desc"] = "击中护甲时对人体伤害的倍数。\n更高的值表示对护甲的伤害更少,对人体的伤害更大。\n在100%时,护甲会被完全忽略。在0%时,护甲会阻挡所有伤害。" +L["stat.armorbonus"] = "护甲粉碎率" +L["stat.armorbonus.desc"] = "击中护甲时对护甲伤害的倍数。\n高穿透武器的大部分伤害将直接作用于人体\n如果穿透力为100%,将不会对护甲造成伤害。" +L["stat.penetration"] = "物体穿透度" +L["stat.penetration.desc"] = "该武器可射穿的金属厚度。\n根据材质类型,实际穿透厚度会有所不同。" +L["stat.spread"] = "扩散" +L["stat.spread.desc"] = "枪械的基础准确度。" +L["stat.rpm"] = "射速" +L["stat.rpm.desc"] = "枪械的射速,以每分钟子弹量为单位。" +L["stat.rpm_burst"] = "射速(点射)" +L["stat.rpm_burst.desc"] = "点射模式下武器的射速。" +L["stat.rpm_semi"] = "射速(半自动)" +L["stat.rpm_semi.desc"] = "半自动模式下武器的射速。" +L["stat.rpm_burst_peak"] = "射速(点射极限)" +L["stat.rpm_burst_peak.desc"] = "点射模式下的平均射速(算上开火间隔)。" +L["stat.clipsize"] = "载弹量" +L["stat.clipsize.desc"] = "枪械一次可容纳的弹药量。" +L["stat.sprinttofire"] = "急停时间" +L["stat.sprinttofire.desc"] = "玩家从冲刺移动到能够正常开火所需的时间。" +L["stat.aimdownsights"] = "开镜时间" +L["stat.aimdownsights.desc"] = "玩家从腰射到开镜所需的时间。" +L["stat.muzzlevelocity"] = "子弹初速" +L["stat.muzzlevelocity.desc"] = "子弹在游戏中的飞行速度。\n还可以控制射程值,低于这个值,子弹就会变成命中率。" +L["stat.recoilkick"] = "后坐力强度" +L["stat.recoilkick.desc"] = "移动视角位置的后坐力强度。" +L["stat.recoilstability"] = "后坐力稳定性" +L["stat.recoilstability.desc"] = "后坐力的可控能力。\n稳定性 100% 时后坐力完全垂直。\n稳定性 0% 时后坐力方向为 180° 半圆。" +L["stat.recoilspread"] = "散射强度" +L["stat.recoilspread.desc"] = "每单位散射导致的不准确度。\n较大的数值意味着枪械会在连续射击时变得更不准确。" +L["stat.recoilspread2"] = "散射强度" +L["stat.recoilspread2.desc"] = "每单位散射导致的额外后坐力。\n较大的数值意味着枪械会在连续射击时后坐力变高。" +L["stat.recoildissipation"] = "散射复位速率" +L["stat.recoildissipation.desc"] = "积累后坐力释放速度。\n较大的数值意味着后坐力在复位时间后影响更快消失。" +L["stat.recoilresettime"] = "散射复位延迟" +L["stat.recoilresettime.desc"] = "后坐力开始消散前的延迟时间。\n较大的数值意味着你必须在两次射击之间要等待更长时间,后坐力影响才会开始复位。" +L["stat.recoilmaximum"] = "最大散射" +L["stat.recoilmaximum.desc"] = "连续射击造成的后坐力的上限。" +L["stat.recoilfirstshot"] = "首枪散射倍率" +L["stat.recoilfirstshot.desc"] = "连续射击时第一枪的后坐力倍率。在所有后坐力复位后重置。\n不影响视觉后坐力。" +L["stat.recoilpershot"] = "散射增速" +L["stat.recoilpershot.desc"] = "每一枪增加多少单位的后坐力,通常是1。\n较大的数值会更快地增加不准确度,也需要更长的时间来恢复。" +L["stat.recoilcrouch"] = "蹲伏后坐力率" +L["stat.recoilcrouch.desc"] = "蹲下不动时的后坐力倍率。" +L["stat.movespeed"] = "移动速度" +L["stat.movespeed.desc"] = "当枪械被举起时的移速倍率。当武器处于保险状态时将不会减速。" +L["stat.shootingspeed"] = "射击时移动速度" +L["stat.shootingspeed.desc"] = "射击时的移速倍率。\n后坐力的积累会增加减速强度。" +L["stat.sightedspeed"] = "开镜时移动速度" +L["stat.sightedspeed.desc"] = "开镜时的移速倍率。" +L["stat.reloadspeed"] = "换弹时移动速度" +L["stat.reloadspeed.desc"] = "换弹时的移速倍率。" +L["stat.meleespeed"] = "近战时移动速度" +L["stat.meleespeed.desc"] = "近战攻击时的移速倍率。" +L["stat.reloadtime"] = "换弹时间" +L["stat.reloadtime.desc"] = "换弹所需的时间。" +L["stat.deploytime"] = "切枪时间" +L["stat.deploytime.desc"] = "切换枪械所需的时间。" +L["stat.holstertime"] = "收枪时间" +L["stat.holstertime.desc"] = "放下枪械所需的时间。" +L["stat.sway"] = "晃动" +L["stat.sway.desc"] = "腰射时的武器晃动。\n晃动会影响你的枪械射击方向,但不会改变你的准星方向。" +L["stat.scopedsway"] = "瞄准晃动" +L["stat.scopedsway.desc"] = "瞄准时的武器晃动。\n散射会影响你的枪械射击方向,但不会改变你的准星方向。" +L["stat.swaycrouch"] = "蹲伏晃动" +L["stat.swaycrouch.desc"] = "蹲下不动时的武器晃动。\n散射会影响你的枪械射击方向,但不会改变你的准星方向。" +L["stat.midairspread"] = "跳射散射" +L["stat.midairspread.desc"] = "在空中的额外散射。" +L["stat.hipfirespread"] = "腰射散射" +L["stat.hipfirespread.desc"] = "不瞄准时武器的额外散射。" +L["stat.movespread"] = "移动散射" +L["stat.movespread.desc"] = "移动时武器的额外散射.\n散射和移动速度有关联。" +L["stat.meleedamage"] = "近战伤害" +L["stat.meleedamage.desc"] = "近战攻击造成的伤害。" +L["stat.firemode"] = "射击模式" +L["stat.firemode.desc"] = "枪械的射击模式。" +L["stat.freeaimangle"] = "自由瞄准角度" +L["stat.freeaimangle.desc"] = "腰射时与瞄准方向的最大偏差量。" +L["stat.shotstofail"] = "平均卡壳率" +L["stat.shotstofail.desc"] = "在枪械卡壳之前的能正常击发的平均击发次数。" + +-- L["stat.meleeperkstr"] = "Brawn" +-- L["stat.meleeperkstr.desc"] = "Melee attribute influencing Techniques and Specials.\nAffects perk damage and knockback." +-- L["stat.meleeperkagi"] = "Dexterity" +-- L["stat.meleeperkagi.desc"] = "Melee attribute influencing Techniques and Specials.\nAffects perk movement power and attack interval." +-- L["stat.meleeperkint"] = "Strategy" +-- L["stat.meleeperkint.desc"] = "Melee attribute influencing Techniques and Specials.\nAffects perk recharge speed and projectile force." + +-- L["rating.meleeattacktime"] = "Haste" +-- L["rating.meleeattacktime.desc"] = "How quickly the weapon can attack." +-- L["stat.damage.desc_melee"] = "Damage dealt by primary attacks." -- used on melee damage +-- L["stat.meleeattacktime"] = "Melee Attack Delay" +-- L["stat.meleeattacktime.desc"] = "Amount of time between each attack if it hit something." +-- L["stat.meleeattackmisstime"] = "Melee Miss Delay" +-- L["stat.meleeattackmisstime.desc"] = "Amount of time between each attack if it didn't hit anything." +-- L["stat.meleerange"] = "Melee Range" +-- L["stat.meleerange.desc"] = "How far away the weapon can reach for an attack." +-- L["stat.meleedelay"] = "Melee Windup" +-- L["stat.meleedelay.desc"] = "Delay between an attack starting and landing." + +-- L["stat.melee2damage"] = "Damage (H)" +-- L["stat.melee2damage.desc"] = "Damage dealt by heavy attacks." +-- L["stat.melee2attacktime"] = "Melee Attack Delay (H)" +-- L["stat.melee2attacktime.desc"] = "Amount of time between each heavy attack if it hit something." +-- L["stat.melee2attackmisstime"] = "Melee Miss Delay (H)" +-- L["stat.melee2attackmisstime.desc"] = "Amount of time between each heavy attack if it didn't hit anything." +-- L["stat.meleethrowdamage"] = "Damage (Throw)" +-- L["stat.meleethrowdamage.desc"] = "Damage dealt by thrown weapons." +-- L["stat.meleethrowvelocity"] = "Throw Velocity" +-- L["stat.meleethrowvelocity.desc"] = "Travel speed of thrown weapons." +-- L["stat.meleethrowtime"] = "Throw Delay" +-- L["stat.meleethrowtime.desc"] = "Amount of time between each weapon throw." +-- L["stat.lifesteal"] = "Health From Damage" +-- L["stat.lifesteal.desc"] = "Fraction of damage dealt that is converted into health." +-- L["stat.damagecharge"] = "Charge From Damage" +-- L["stat.damagecharge.desc"] = "Fraction of damage dealt that is converted into perk charge." + +L["stat.damage"] = "伤害" +L["stat.damage.desc"] = "武器所有距离下的伤害。" +L["stat.damage_max"] = "近距离伤害" +L["stat.damage_max.desc"] = "最低射程下的武器伤害。" +L["stat.damage_min"] = "远距离伤害" +L["stat.damage_min.desc"] = "极限射程下的武器伤害。" +L["stat.explosivedamage"] = "爆炸伤害" +L["stat.explosivedamage.desc"] = "子弹爆炸时造成的额外伤害。" +L["stat.explosiveradius"] = "爆炸范围" +L["stat.explosiveradius.desc"] = "子弹爆炸时的有效范围。" +L["stat.range_max"] = "最大射程" +L["stat.range_max.desc"] = "武器极限射程的距离。" +L["stat.range_min"] = "最小射程" +L["stat.range_min.desc"] = "武器不受射程影响的距离。" +L["stat.postburstdelay"] = "点射延迟" +L["stat.postburstdelay.desc"] = "武器点射后需要等待的时间。" +L["stat.ammopershot"] = "弹药消耗" +L["stat.ammopershot.desc"] = "每次开火使用的弹药量。" +L["stat.num"] = "子弹量" +L["stat.num.desc"] = "开火时射出的子弹数量。" +L["stat.peekpenalty"] = "半瞄准效率" +L["stat.peekpenalty.desc"] = "半瞄准时附加的腰射散射和摇晃强弱。" +L["stat.quickscope"] = "快瞄散射" +L["stat.quickscope.desc"] = "切换瞄准时的临时散射。\n散射在 0.2 瞄内逐渐消失。" +L["stat.vol_shoot"] = "击发噪音" +-- L["stat.vol_shoot.desc"] = "How audible the weapon's firing sound is." +L["stat.damagemultnpc"] = "NPC 伤害倍率" +L["stat.damagemultnpc.desc"] = "武器对非玩家角色的伤害倍率(不包括近战)。" + +-- not in stats page but attachments may use +L["stat.swaycrouch"] = "蹲伏扩散" +L["stat.recoil"] = "后坐力" +L["stat.range"] = "射程" +L["stat.blindfiresway"] = "盲射扩散" +L["stat.zoom"] = "瞄准变焦" +L["stat.bloomintensity"] = "散射强度" + +-- L["att.none"] = "N/A" + +L["att.procon.3proj"] = "三颗弹头" +L["att.procon.moreproj"] = "更多弹头" +L["att.procon.lessproj"] = "更少弹头" +L["att.procon.1proj"] = "一颗弹头" +L["att.procon.noexp"] = "无爆炸" +L["att.procon.direct"] = "直射开火" +L["att.procon.doorbreach"] = "可破门" +L["att.procon.crowd"] = "范围控制" +L["att.procon.nonlethal"] = "非致命" +L["att.procon.detdelay"] = "延迟引爆" +L["att.procon.flash"] = "闪光眩晕效果" +L["att.procon.airburst"] = "空中爆破" +L["att.procon.timedfuse"] = "定时引爆" +L["att.procon.smoke"] = "烟雾弹效果" +L["att.procon.limb"] = "肢体伤害" +L["att.procon.head"] = "爆头伤害" +L["att.procon.chest"] = "上身伤害" +L["att.procon.onebullet"] = "一颗子弹" +L["att.procon.armor"] = "对甲效果" +L["att.procon.nosafety"] = "无安全距离" +L["att.procon.radius"] = "爆炸范围" +L["att.procon.needprime"] = "未起爆时很弱" +L["att.procon.projrng"] = "弹道和伤害带有随机性" +L["att.procon.failrng"] = "有概率发生戏剧性意外" +L["att.procon.notracer"] = "隐藏弹道" +L["att.procon.refund"] = "大概率返还弹药" +L["att.procon.unreliable"] = "不可靠" +L["att.procon.surplusboost1"] = "偶尔提升射速" +L["att.procon.surplusboost2"] = "提升射速时扳机失控" +L["att.procon.meleeslow"] = "减速近战击中的目标" +L["att.procon.gasimmune"] = "免疫毒气伤害" +L["att.procon.flashresist"] = "提升闪光弹抗性" +L["att.procon.stunresist"] = "减少眩晕/减速持续时间" +L["att.procon.quickthrow"] = "更快的快速投掷" +L["att.procon.throwrocks"] = "投掷石块" +L["att.procon.cornershot"] = "盲射时显示枪口视角" +L["att.procon.thermal"] = "半瞄准时开启热成像显示" +L["att.procon.dmic"] = "显示附近的目标" +L["att.procon.audible"] = "发出声响" +L["att.procon.flashlight"] = "照明前方区域" +L["att.procon.blind"] = "致盲正在看它的人" +L["att.procon.visible"] = "对他人可见" +L["att.procon.laser"] = "突出瞄准点" +L["att.procon.rf1"] = "显示射程的距离和伤害" +L["att.procon.rf2"] = "在开镜时可视化子弹落点" +L["att.procon.gauge1"] = "显示后坐力、水平后坐力和扩散" +L["att.procon.gauge2"] = "在开镜时可视化扩散" +L["att.procon.auto"] = "全自动射击" +L["att.procon.burst"] = "三连发射击" +L["att.procon.semi"] = "半自动射击" +L["att.procon.autoburst"] = "全自动点射" +L["att.procon.explosive"] = "爆炸效果" +L["att.procon.reliability"] = "可靠度" +L["att.procon.noscope"] = "无瞄具" +L["att.procon.conceal"] = "武器收起时不可见" +L["att.procon.armdelay"] = "起爆延迟" +L["att.procon.proxfuse"] = "近炸引信" +L["att.procon.magnifier"] = "可变焦缩放" +L["att.procon.needscope"] = "需要瞄具才有效" +L["att.procon.bullet"] = "紧急装填" +L["att.procon.nopartialreloads"] = "无法半装填" +L["att.procon.incendiary"] = "点燃目标" +L["att.procon.blurpeek"] = "半瞄准时背景模糊" +L["att.procon.aimrecoil"] = "后坐力 (瞄准时)" +L["att.procon.aimspread"] = "精准度 (瞄准时)" +L["att.procon.aimrpm"] = "射速 (瞄准时)" +L["att.procon.firstround"] = "首发射出非致命弹头" +L["att.procon.locktime"] = "锁定时间" +L["att.procon.semiactive"] = "需要保持锁定" +L["att.procon.proj.turn"] = "飞弹机动性" +L["att.procon.proj.speed"] = "飞弹最高速度" +L["att.procon.proj.direct"] = "直击伤害" +L["att.procon.shovel"] = "发射铲子" +L["att.procon.heal"] = "治疗效果" +-- L["att.procon.infiniteammo"] = "Infinite Ammo" + +L["att.sight.1"] = "1倍缩放" +L["att.sight.1.25"] = "1.25倍缩放" +L["att.sight.1.5"] = "1.5倍缩放" +L["att.sight.1.75"] = "1.75倍缩放" +L["att.zoom.2"] = "2倍变焦" +L["att.zoom.2.2"] = "2.2倍变焦" +L["att.zoom.3"] = "3倍变焦" +L["att.zoom.3.4"] = "3.4倍变焦" +L["att.zoom.3.5"] = "3.5倍变焦" +L["att.zoom.4"] = "4倍变焦" +L["att.zoom.5"] = "5倍变焦" +L["att.zoom.6"] = "6倍变焦" +L["att.zoom.8"] = "8倍变焦" +L["att.zoom.10"] = "10倍变焦" +L["att.zoom.12"] = "12倍变焦" + +L["spacer.damage"] = "命中效果" +L["spacer.damage.desc"] = "有关武器伤害和射程的属性。" +L["spacer.action"] = "机能" +L["spacer.action.desc"] = "有关武器装填和上膛的属性。" +L["spacer.ballistics"] = "弹道" +L["spacer.ballistics.desc"] = "有关武器弹道和对材料伤害的属性。" +L["spacer.recoilbloom"] = "后坐力" +L["spacer.recoilbloom.desc"] = "有关武器后坐力以及连续开火的属性。" +L["spacer.mobility"] = "移速" +L["spacer.mobility.desc"] = "有关使用武器时移动速度的属性。" +L["spacer.maneuvering"] = "机动性" +L["spacer.maneuvering.desc"] = "有关武器不瞄准时命中难易的属性。" +L["spacer.handling"] = "操作速度" +L["spacer.handling.desc"] = "有关武器操作快慢的属性。" +L["spacer.sway"] = "晃动" +L["spacer.sway.desc"] = "有关武器晃动的属性。" +L["spacer.misc"] = "其它" +L["spacer.misc.desc"] = "不隶属与其他类别的属性。" + +L["trivia.year"] = "生产年份" +L["trivia.caliber"] = "口径" +L["trivia.manufacturer"] = "生产商" +L["trivia.faction"] = "派系" +L["trivia.unknown"] = "未知" + +L["faction.coalition"] = "联盟" -- "Counter-Terrorists" +L["faction.militia"] = "民兵" -- "Terrorists" +L["faction.neutral"] = "中立" -- non-faction specific + +-- L["menu.legacy"] = "Legacy Menu" + +L["hint.swap"] = "和 {weapon} 切换" +L["hint.pickup"] = "捡起 {weapon}" +L["hint.unjam"] = "解除卡弹" +L["hint.rp_biocode_cp"] = "警察专用" + +L["hint.melee"] = "近战攻击" +L["hint.melee.block"] = "格挡" +L["hint.melee.throw"] = "近战投掷" +L["hint.melee.light"] = "近战轻击" +L["hint.melee.heavy"] = "近战重击" + +L["hint.peek.hold"] = "半瞄准" +L["hint.peek.toggle"] = "切换半瞄准" +L["hint.hold_breath"] = "屏住呼吸" +L["hint.customize"] = "自定义武器" +L["hint.inspect"] = "检视武器" +L["hint.firemode"] = "切换火控" +L["hint.safety.turn_off"] = "解除保险" +L["hint.safety.turn_on"] = "开启保险" +L["hint.hl2_flashlight"] = "默认手电筒" +L["hint.toggle_tactical"] = "切换 {1}" +L["hint.blindfire"] = "盲射" +L["hint.blindfire.menu"] = "盲射菜单" +L["hint.blindfire.up"] = "向上盲射" +L["hint.blindfire.left"] = "向左盲射" +L["hint.blindfire.right"] = "向右盲射" +L["hint.blindfire.cancel"] = "解除盲射" +L["hint.blindfire.kys"] = "自杀" +L["hint.quicknade.over"] = "正手投掷" +L["hint.quicknade.under"] = "反手投掷" +L["hint.quicknade.throw"] = "快捷投掷" +L["hint.quicknade.pull_out"] = "切出手雷" +L["hint.quicknade.menu"] = "快捷投掷菜单" +L["hint.quicknade.next"] = "下个手雷" +L["hint.quicknade.prev"] = "上个手雷" +L["hint.ttt.radio"] = "TTT 无线电" +L["hint.ttt.shop"] = "TTT 商店" +L["hint.tac"] = "战术配件" -- default value +L["hint.tac.radar"] = "雷达" +L["hint.tac.flashlight"] = "手电筒" +L["hint.tac.laser"] = "镭射" +L["hint.tac.combo"] = "光瞄组合" +L["hint.tac.rangefinder"] = "测距仪" +L["hint.tac.spread_gauge"] = "扩散仪" +L["hint.tac.magnifier"] = "缩放" +L["hint.tac.cam_mode"] = "镜头模式" -- Used for both Corner-Cam and Thermal Imager +L["hint.tac.load_one"] = "装填单发子弹" + +L["hint.melee_lunge"] = "飞扑" +L["hint.melee_step"] = "快步" +L["hint.melee_ninja.palm"] = "掌击" +L["hint.melee_ninja.backhop"] = "后跳" +L["hint.melee_ninja.divekick"] = "飞踢" + +L["hint.medkit.self"] = "治疗自己" +L["hint.medkit.others"] = "治疗他人" + +-- L["hint.unlock"] = "Unlock" +-- L["hint.exitcustmenu"] = "Exit the Customization Menu" + +-- L["hint.nogrenades"] = "No Grenades Available" + +-- L["hint.hold"] = "(Hold)" + +-- L["hint.shootself"] = "Shoot Yourself" + +-- For demoknight charge attachment +L["hint.melee_charge"] = "冲锋" +L["hint.melee_charge.menu"] = "冲锋模式" +L["hint.melee_charge.0.name"] = "勇性" +L["hint.melee_charge.0.desc"] = "默认模式,转向速度平均。\n撞击目标造成伤害并击退。冲锋时攻击会取消冲刺。" +L["hint.melee_charge.1.name"] = "灵性" +L["hint.melee_charge.1.desc"] = "转向速度极高的冲刺模式。\n撞击强度和伤害抗性较低。\n冲刺时受伤会减短冲刺速度。" +L["hint.melee_charge.2.name"] = "韧性" +L["hint.melee_charge.2.desc"] = "冲刺速度更高,转向速度很低。\n撞击强度和伤害抗性较高。\n冲刺时免疫击退。" + +-- Weapon calibers +-- L["caliber.556x45"] = "5.56×45mm" +-- L["caliber.762x39"] = "7.62×39mm" +-- L["caliber.762x51"] = "7.62×51mm" +-- L["caliber.50bmg"] = ".50 BMG" +-- L["caliber.16g"] = "16 Gauge" +-- L["caliber.12g"] = "12 Gauge" +-- L["caliber.40mmg"] = "40mm Grenades" +-- L["caliber.9x19"] = "9×19mm" +-- L["caliber.57x28"] = "FN 5.7×28mm" +-- L["caliber.45acp"] = ".45 ACP" +-- L["caliber.380acp"] = ".380 ACP" +-- L["caliber.homing"] = "Infrared Homing Missile" +-- L["caliber.40sw"] = ".40 S&W" +-- L["caliber.23x75"] = "23×75mmR" +-- L["caliber.223r"] = ".223 Remington" +-- L["caliber.46x30"] = "HK 4.6×30mm" +-- L["caliber.357"] = ".357 Magnum" +-- L["caliber.357sig"] = ".357 SIG" +-- L["caliber.6mm"] = "6mm Whisper" +-- L["caliber.40mmr"] = "40mm Rockets" +-- L["caliber.32acp"] = ".32 ACP" +-- L["caliber.308"] = ".308 Winchester" +-- L["caliber.65mm"] = "6.5mm Creedmoor" + +-- L["caliber.44amp"] = ".44 AMP" +-- L["caliber.40x46"] = "40×46mm" +-- L["caliber.50ae"] = ".50 AE" +-- L["caliber.792x57"] = "7.92×57mm Mauser" +-- L["caliber.500sw"] = ".500 S&W Magnum" +-- L["caliber.22lr"] = ".22 LR" +-- L["caliber.38special"] = ".38 Special" +-- L["caliber.338"] = ".338 Lapua Magnum" +-- L["caliber.9x39"] = "9x39mm" + +-- L["caliber.44p"] = ".44 Percussion" +-- L["caliber.300blk"] = ".300 Blackout" +-- L["caliber.9x18"] = "9×18mm" +-- L["caliber.4570"] = ".45-70 Govt." +-- L["caliber.9x21"] = "9x21mm IMI" +-- L["caliber.762x54"] = "7.62×54mmR" +-- L["caliber.762x25"] = "7.62×25mm Tokarev" +-- L["caliber.300winmag"] = ".300 Win Mag" + +-- L["caliber.545x39"] = "5.45×39mm" + +-- L["caliber.410b"] = ".410 Bore" +-- L["caliber.408ct"] = ".408 Cheyenne Tactical" +-- L["caliber.950jdj"] = ".950 JDJ" +-- L["caliber.600ne"] = ".600 Nitro Express" +-- L["caliber.83mmr"] = "83mm Rockets" +-- L["caliber.25x40"] = "25×40mm" + +-- L["caliber.44m"] = ".44 Magnum" +-- L["caliber.763x25"] = "7.63×25mm Mauser" +-- L["caliber.277sig"] = ".277 SIG Fury" +-- L["caliber.10auto"] = "10mm Auto" +-- L["caliber.75x55"] = "7.5×55mm" +-- L["caliber.44-40"] = ".44-40 WCF" + +-- L["caliber.20g"] = "20 Gauge" +-- L["caliber.9wm"] = "9mm Win Mag" +-- L["caliber.44s"] = ".44 Special" +-- L["caliber.303b"] = ".303 British" +-- L["caliber.66mmr"] = "66mm Rockets" +-- L["caliber.25mmf"] = "25mm Flares" +-- L["caliber.270w"] = ".270 Winchester" + +-- L["caliber.45s"] = ".45 Super" + +-- L["unknown"] = "Unknown" + +-- Depreciated Strings (No longer in use, or use is altered) +-- L["type.precision_rifle"] = "精准步枪" +-- L["type.melee_weapon"] = "近战武器" +-- L["stat.peekpenalty"] = "精准腰射精度" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_en.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_en.lua new file mode 100644 index 0000000..5419125 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_en.lua @@ -0,0 +1,92 @@ +L = {} -- Русский язык - Контент + +--[[ +Для локализации модификаций (замените optic_acog на класс своей модификации): +L["att.optic_acog.name"] = "Название модификации" +L["att.optic_acog.name.full"] = "Полное название модификации" +L["att.optic_acog.desc"] = "Описание модификации" + +Для локализации оружия (замените tacrp_vertec на класс своего оружия): +L["wep.tacrp_vertec.name.full"] = "Полное название оружия" +L["wep.tacrp_vertec.name"] = "Название оружия" +L["wep.tacrp_vertec.desc"] = "Описание оружия" +L["wep.tacrp_vertec.desc.quote"] = "Цитата оружия" -- курсивная строка внизу +L["wep.tacrp_vertec.trivia.manufacturer"] = "Производитель" +L["wep.tacrp_vertec.trivia.caliber"] = "Калибр" +L["wep.tacrp_vertec.trivia.year"] = "Год" +L["wep.tacrp_vertec.credits"] = "Авторы" +]] + +///////////////////// -- [[ TacRP Base ]] -- +-- Быстрые гранаты +L["quicknade.fuse"] = "ВЗРЫВАТЕЛЬ:" + +L["quicknade.frag.name.full"] = "Осколочная граната" +L["quicknade.frag.name"] = "ОСКОЛ." +L["quicknade.frag.dettype"] = "Таймер: 2с" +L["quicknade.frag.desc"] = "Стандартная взрывная граната, разбрасывающая осколки в среднем радиусе.\n\nОбычно не смертельна сама по себе, но может ранить цели или вытеснить их из укрытия." + +L["quicknade.flash.name.full"] = "Светошумовая граната" +L["quicknade.flash.name"] = "СВЕТО." +L["quicknade.flash.dettype"] = "Таймер: 1.5с" +L["quicknade.flash.desc"] = "Излучает яркую вспышку и оглушительный хлопок, дезориентирующий цели.\n\nЗамедляет поражённые цели и наносит незначительный урон в большом радиусе." + +L["quicknade.smoke.name.full"] = "Дымовая граната" +L["quicknade.smoke.name"] = "ДЫМОВ." +L["quicknade.smoke.dettype"] = "Таймер: 2с" +L["quicknade.smoke.desc"] = "Создаёт маскирующее облако дыма, которое длится около 20 секунд.\n\nНе наносит урона вообще и обычно используется для прикрытия наступления или затруднения линии видимости." + +L["quicknade.gas.name.full"] = "Газовая граната" +L["quicknade.gas.name"] = "ГАЗ" +L["quicknade.gas.dettype"] = "Таймер: 2с" +L["quicknade.gas.desc"] = "Выпускает облако слезоточивого газа, которое длится около 15 секунд.\n\nВсе, кто попадут внутрь, получают несмертельный длительный урон и испытывают трудности с удержанием оружия.\n\nЭто химическое оружие, запрещённое Женевской конвенцией." + +L["quicknade.fire.name.full"] = "Термитная граната" +L["quicknade.fire.name"] = "ОГОНЬ" +L["quicknade.fire.dettype"] = "Таймер: 3с" +L["quicknade.fire.desc"] = "Прилипает к целям и интенсивно горит около 8 секунд, нанося урон в малом радиусе.\n\nХотя термит обычно используется для прожигания материалов, он также полезен для блокировки зон." + +L["quicknade.c4.name.full"] = "Заряд C4" +L["quicknade.c4.name"] = "C4" +L["quicknade.c4.dettype"] = "Дистанционно" +L["quicknade.c4.desc"] = "Брикет мощной взрывчатки, который можно подорвать детонатором дистанционно.\n\nC4 удивительно инертен, но сигнальное устройство можно снять или уничтожить, обезвредив заряд." + +L["quicknade.nuke.name.full"] = "Ядерное устройство" +L["quicknade.nuke.name"] = "ЯДЕРН." +L["quicknade.nuke.dettype"] = "Дистанционно" +L["quicknade.nuke.desc"] = "Микроядерная бомба размером с портфель, которую можно подорвать детонатором дистанционно.\n\nЕё взрывной результат не нуждается в описании." + +L["quicknade.breach.name.full"] = "Взрывной заряд" +L["quicknade.breach.name"] = "ВЗЛОМ" +L["quicknade.breach.dettype"] = "Таймер: 2с / Дистанционно" +L["quicknade.breach.desc"] = "Кумулятивный заряд, созданный для пробивания дверей и слабых стен.\n\nМаленький радиус взрыва, но уничтожит любую дверь, к которой прикреплён, и ранит цели с другой стороны ударной волной.\n\nПри удержании детонатора заряд настроен на дистанционный подрыв." + +L["quicknade.heal.name.full"] = "Медицинский дым" +L["quicknade.heal.name"] = "ЛЕЧЕН." +L["quicknade.heal.dettype"] = "Таймер: 5с" +L["quicknade.heal.desc"] = "Выпускает облако восстанавливающего газа примерно на 15 секунд.\n\nМедицинские наниты восстанавливают здоровье при вдыхании. Если получатели здоровы, их броня может быть отремонтирована или перезаряжена.\n\nИмеет противоположный эффект на нежить, нанося урон вместо лечения." + +L["quicknade.rock.name.full"] = "Камень" +L["quicknade.rock.name"] = "КАМЕНЬ" +L["quicknade.rock.dettype"] = "Тупая травма" +L["quicknade.rock.desc"] = "Возможно, первое оружие, когда-либо использованное людьми.\n\nИспользуйте в крайнем случае, для древних казней или жестоких розыгрышей.\n\nНасколько вы находчивы, нет предела тому, что ещё можно достать из штанов в крайнем случае..." + +L["quicknade.bump.name.full"] = "Отбрасывающая мина" +L["quicknade.bump.name"] = "ОТБРОС" +L["quicknade.bump.dettype"] = "Нажимная пластина" +L["quicknade.bump.desc"] = "Намагниченная мина, прилипающая к поверхностям.\n\nСоздаёт безвредный взрыв, отталкивающий всё вокруг, раня цели, отправляя их в стены или пол.\n\nМожет использоваться для запуска себя очень далеко, но не забудьте парашют." + +L["quicknade.t-smk.name.full"] = "Дымовая граната" +L["quicknade.t-smk.name"] = "T-ДЫМ" +L["quicknade.t-smk.dettype"] = "Таймер: 2с" +L["quicknade.t-smk.desc"] = "Террористическая дымовая граната.\n\nСоздаёт дымовую завесу." + +L["quicknade.t-dcb.name.full"] = "Дискомбобулятор" +L["quicknade.t-dcb.name"] = "T-ДСК" +L["quicknade.t-dcb.dettype"] = "Таймер: 3с" +L["quicknade.t-dcb.desc"] = "Террористическая граната сотрясения.\n\nНе наносит урона, но создаёт взрыв, притягивающий объекты и отбрасывающий игроков." + +L["quicknade.t-inc.name.full"] = "Зажигательная граната" +L["quicknade.t-inc.name"] = "T-ЗАЖ" +L["quicknade.t-inc.dettype"] = "Таймер: 2с" +L["quicknade.t-inc.desc"] = "Террористическая зажигательная граната.\n\nВзрывается с небольшим уроном и разжигает огонь в зоне." diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_pl.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_pl.lua new file mode 100644 index 0000000..96c902b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_pl.lua @@ -0,0 +1,2913 @@ +L = {} -- Polish Content Strings by meerfi + +///////////////////// -- [[ TacRP Base ]] -- +-- QuickNades +-- L["quicknade.fuse"] = "FUSE:" + +-- L["quicknade.frag.name.full"] = "Frag Grenade" +-- L["quicknade.frag.name"] = "FRAG" +-- L["quicknade.frag.dettype"] = "Timed: 2s" +-- L["quicknade.frag.desc"] = "Standard explosive grenade spraying shrapnel in a medium radius.\n\nTypically not lethal by itself, but can wound targets or flush them out of cover." + +-- L["quicknade.flash.name.full"] = "Flashbang" +-- L["quicknade.flash.name"] = "FLASH" +-- L["quicknade.flash.dettype"] = "Timed: 1.5s" +-- L["quicknade.flash.desc"] = "Emits a bright flash and deafening bang that disorients targets (hence its name).\n\nSlows affected targets, and deals minor damage in a large radius." + +-- L["quicknade.smoke.name.full"] = "Smoke Grenade" +-- L["quicknade.smoke.name"] = "SMOKE" +-- L["quicknade.smoke.dettype"] = "Timed: 2s" +-- L["quicknade.smoke.desc"] = "Emits a concealing cloud of smoke that lasts about 20 seconds.\n\nDeals no damage whatsoever, and is commonly used to cover an advance or to obscure a line of sight." + +-- L["quicknade.gas.name.full"] = "CS Gas Grenade" +-- L["quicknade.gas.name"] = "GAS" +-- L["quicknade.gas.dettype"] = "Timed: 2s" +-- L["quicknade.gas.desc"] = "Emits a cloud of tear gas that lasts about 15 seconds.\n\nAnyone caught within will take non-lethal lingering damage and have trouble keeping their weapon steady.\n\nIt is a chemical weapon banned by the Geneva Convention and is ABSOLUTELY NOT FART GAS." + +-- L["quicknade.fire.name.full"] = "Thermite Grenade" +-- L["quicknade.fire.name"] = "FIRE" +-- L["quicknade.fire.dettype"] = "Timed: 3s" +-- L["quicknade.fire.desc"] = "Sticks to targets and burns intensely for about 8 seconds, dealing damage within a small radius.\n\nWhile thermite is typically used to burn through materiel, it is also useful for area denial." + +-- L["quicknade.c4.name.full"] = "C4 Charge" +-- L["quicknade.c4.name"] = "C4" +-- L["quicknade.c4.dettype"] = "Remote" +-- L["quicknade.c4.desc"] = "A brick of powerful explosives that can be touched off by a detonator remotely.\n\nC4 is remarkably inert, but the signalling device can be removed or destroyed, defusing the charge." + +-- L["quicknade.nuke.name.full"] = "Nuclear Device" +-- L["quicknade.nuke.name"] = "NUKE" +-- L["quicknade.nuke.dettype"] = "Remote" +-- L["quicknade.nuke.desc"] = "Briefcase-sized micro nuclear bomb that can be touched off by a detonator remotely.\n\nIts explosive outcome needs no description." + +-- L["quicknade.breach.name.full"] = "Breaching Charge" +-- L["quicknade.breach.name"] = "BREACH" +-- L["quicknade.breach.dettype"] = "Timed: 2s / Remote" +-- L["quicknade.breach.desc"] = "Shaped charge made to bust through doors and weak walls.\n\nSmall blast radius, but will destroy any door it is attached to and hurt targets on the other side with its shockwave.\n\nWhen holding a detonator, the charge is configured to detonate remotely." + +-- L["quicknade.heal.name.full"] = "Medi-Smoke Can" +-- L["quicknade.heal.name"] = "HEAL" +-- L["quicknade.heal.dettype"] = "Timed: 5s" +-- L["quicknade.heal.desc"] = "Emits a cloud of restorative gas for about 15 seconds.\n\nMedical nanites restores health when inhaled. If recipients are healthy, any armor they have can be repaired or recharged.\n\nHas the opposite effect on necrotics, dealing damage instead." + +-- L["quicknade.rock.name.full"] = "Rock" +-- L["quicknade.rock.name"] = "ROCK" +-- L["quicknade.rock.dettype"] = "Blunt Trauma" +-- L["quicknade.rock.desc"] = "Possibly the first weapon ever used by humans.\n\nUse as last resort, for ancient capital punishments, or for violent pranks.\n\nResourceful as you are, there's no telling what else you can pull out of your pants in a pinch..." + +-- L["quicknade.bump.name.full"] = "Bump Mine" +-- L["quicknade.bump.name"] = "BUMP" +-- L["quicknade.bump.dettype"] = "Pressure Plate" +-- L["quicknade.bump.desc"] = "Magnetized mine that sticks to surfaces.\n\nCreates a harmless explosion that pushes everything away, hurting targets by sending them into walls or floors.\n\nCan be used to launch yourself very far, but remember to bring a parachute." + +-- L["quicknade.t-smk.name.full"] = "Smoke Grenade" +-- L["quicknade.t-smk.name"] = "T-SMK" +-- L["quicknade.t-smk.dettype"] = "Timed: 2s" +-- L["quicknade.t-smk.desc"] = "Terrorist-issue smoke grenade.\n\nCreates a smokescreen." + +-- L["quicknade.t-dcb.name.full"] = "Discombobulator" +-- L["quicknade.t-dcb.name"] = "T-DCB" +-- L["quicknade.t-dcb.dettype"] = "Timed: 3s" +-- L["quicknade.t-dcb.desc"] = "Terrorist-issue concussion grenade.\n\nDoes no damage, but creates a blast that pulls props in and pulls players out." + +-- L["quicknade.t-inc.name.full"] = "Incendiary Grenade" +-- L["quicknade.t-inc.name"] = "T-INC" +-- L["quicknade.t-inc.dettype"] = "Timed: 2s" +-- L["quicknade.t-inc.desc"] = "Terrorist-issue incendiary grenade.\n\nExplodes with minor damage, and starts fires in an area." + +-- Weapons +local ws = "tacrp_" +local w = ws .. "ak47" +-- L["wep." .. w .. ".name.full"] = "FB Beryl 96" +-- L["wep." .. w .. ".name"] = "Beryl 96" +L["wep." .. w .. ".desc"] = "Łatwy w obsłudze karabin o niskiej szybkostrzelności i niskim odrzucie." +L["wep." .. w .. ".desc.quote"] = "Mimo wyglądu, to nie jest AK." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FB \"Łucznik\" Radom" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "amd65" +-- L["wep." .. w .. ".name"] = "AMD-65" +L["wep." .. w .. ".desc"] = "Węgierski klon AK z integralnym chwytem i kolbą drutową. Wysokie obrażenia, ale mniejszy zasięg niż większość karabinów." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Fegyver- és Gépgyár" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "ar15" +-- L["wep." .. w .. ".name.full"] = "Diemaco AR-15" +-- L["wep." .. w .. ".name"] = "AR-15" +L["wep." .. w .. ".desc"] = "Customized semi-automatic model of an ubiquitous American rifle. Uses reduced capacity magazines." +L["wep." .. w .. ".desc.quote"] = "\"One of the most beloved and most vilified rifles in the country.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt Canada" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "as50" +-- L["wep." .. w .. ".name.full"] = "AI AS50" +-- L["wep." .. w .. ".name"] = "AS50" +L["wep." .. w .. ".desc"] = "Półautomatyczny karabin przeciwpancerny, który może łatwo zdziesiątkować każdą osobę na dowolnym dystansie. \nDomyślnie wyposażony w lunetę 12x. \nZdecydowanie za ciężki do machania, więc tłuczenie jest wykluczone." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Accuracy International" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "aug" +-- L["wep." .. w .. ".name.full"] = "Steyr AUG A2" +-- L["wep." .. w .. ".name"] = "AUG A2" +L["wep." .. w .. ".desc"] = "Karabin bullpup z serią strzałów o dużej pojemności magazynka i świetnej obsłudze." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "bekas" +-- L["wep." .. w .. ".name.full"] = "Molot Bekas-16M" +-- L["wep." .. w .. ".name"] = "Bekas-16M" +L["wep." .. w .. ".desc"] = "Celny strzelba myśliwska z niską szybkostrzelnością. \nOgraniczona skuteczność przeciwko pancerzowi." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Molot" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "c4_detonator" +-- L["wep." .. w .. ".name"] = "C4 Detonator" +L["wep." .. w .. ".desc"] = "Device for touching off C4 charges or other types of remote explosives." +-- -- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "civ_amd65" +-- L["wep." .. w .. ".name.full"] = "Arsenal SAM7SF" +-- L["wep." .. w .. ".name"] = "SAM7SF" +L["wep." .. w .. ".desc"] = "American semi-automatic AK pattern rifle, customized with an AR-15 style stock and Hungarian handguard.\nUses reduced capacity magazines." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arsenal Inc" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention \nModel Edit: Arctic" + +w = ws .. "civ_g36k" +-- L["wep." .. w .. ".name.full"] = "HK HK243" +-- L["wep." .. w .. ".name"] = "HK243" +L["wep." .. w .. ".desc"] = "Semi-automatic model of an iconic polymer rifle.\nUses reduced capacity magazines." +L["wep." .. w .. ".desc.quote"] = "\"Weapons of war do not belong on our streets!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "civ_m320" +-- L["wep." .. w .. ".name.full"] = "HK M320LE" +-- L["wep." .. w .. ".name"] = "M320LE" +L["wep." .. w .. ".desc"] = "Law Enforcement version of the M320 sanctioned for less-lethal munitions. Fires beanbag rounds that incapacitate on direct hit." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "civ_mp5" +-- L["wep." .. w .. ".name.full"] = "HK HK94" +-- L["wep." .. w .. ".name"] = "HK94" +L["wep." .. w .. ".desc"] = "Półautomatyczny model legendarnego pistoletu maszynowego. \nWykorzystuje magazynki o zmniejszonej pojemności." +L["wep." .. w .. ".desc.quote"] = "Frequently seen standing in for its military counterpart." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention \nModel Edit: speedonerd \n(it was soooo hard lol)" + +w = ws .. "civ_p90" +-- L["wep." .. w .. ".name.full"] = "FN PS90" +-- L["wep." .. w .. ".name"] = "PS90" +L["wep." .. w .. ".desc"] = "Półautomatyczna wariacja futurystycznego PDW. \nWykorzystuje magazynki o zmniejszonej pojemności." +L["wep." .. w .. ".desc.quote"] = "\"This is a weapon of terror. It's made to intimidate the enemy.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention \nModel Edit: speedonerd \n(it was soooo hard lol)" + +w = ws .. "dsa58" +-- L["wep." .. w .. ".name.full"] = "DSA SA58 OSW" +-- L["wep." .. w .. ".name"] = "SA58" +L["wep." .. w .. ".desc"] = "Karabin bojowy o wolnej szybkostrzelności, ale bardzo wysokim uszkodzeniu i penetracji pancerza. Posiada chwyt, który zapewnia pewną stabilność, jeśli jest rozwinięty." +L["wep." .. w .. ".desc.quote"] = "\"Zamknij się, zegarze i załaduj.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "DS Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "ex_ak47" +-- L["wep." .. w .. ".name"] = "AK-47" +L["wep." .. w .. ".desc"] = "Ikoniczny radziecki karabin szturmowy. Solidna i prosta konstrukcja, która zainspirowała niezliczone klony i pochodne." +L["wep." .. w .. ".desc.quote"] = "Esencjonalna broń złoczyńców." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, PoisonHeadcrab, Steelbeast \nTextures: Millenia, IppE, FxDarkloki, Pete \nSounds: CC5, modderfreak, .exe \nAnimations: Tactical Intervention" + +w = ws .. "ex_glock" +-- L["wep." .. w .. ".name"] = "Glock 17" +L["wep." .. w .. ".desc"] = "Polimerowy pistolet z większą niż standardowa pojemnością magazynka i szybkim tempem strzałów." +L["wep." .. w .. ".desc.quote"] = "Nie wykrywa się na lotniskowych wykrywaczach metalu." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Glock Ges.m.b.H" +-- L["wep." .. w .. ".credits"] = "Assets: Twinke Masta \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "ex_hecate" +-- L["wep." .. w .. ".name.full"] = "PGM Hécate II" +-- L["wep." .. w .. ".name"] = "Hécate II" +L["wep." .. w .. ".desc"] = "Ciężki karabin przeciwmateriałowy, który może zabić jednym strzałem. Wyposażony fabrycznie w lunetę 12x. Na tyle lekki, by go używać jako broń do walki wręcz." +L["wep." .. w .. ".desc.quote"] = "\"Testowany przez Gun Runner, zatwierdzony przez NCR.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +-- L["wep." .. w .. ".credits"] = "Assets: Toby Burnside \nSource: Fallout 4: New Vegas Project" + +w = ws .. "ex_hk45c" +-- L["wep." .. w .. ".name.full"] = "HK HK45 Compact" +-- L["wep." .. w .. ".name"] = "HK45C" +L["wep." .. w .. ".desc"] = "Nowoczesny pistolet o dużej sile ognia w poręcznym opakowaniu." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: B0t, Red Crayon \nTextures: IppE, Grall19 \nSounds: DMG, xLongWayHome, Leeroy Newman \nAnimations: Tactical Intervention" + +w = ws .. "ex_m4a1" +-- L["wep." .. w .. ".name.full"] = "Colt M4A1" +-- L["wep." .. w .. ".name"] = "M4A1" +L["wep." .. w .. ".desc"] = "Prawdziwa amerykańska klasyka z wysoką szybkostrzelnością i zrównoważonymi osiągami." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Twinke Masta, DMG \nSounds: Teh Strelok \nAnimations: Tactical Intervention" + +w = ws .. "ex_m1911" +-- L["wep." .. w .. ".name.full"] = "Colt M1911" +-- L["wep." .. w .. ".name"] = "M1911" +L["wep." .. w .. ".desc"] = "Nadmiarowy pistolet z ery przed taktycznymi dodatkami i optyką pistoletową, ale nadal mocno uderza." +L["wep." .. w .. ".desc.quote"] = "\"Hasta la vista, baby!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Twinke Masta, DMG \nSounds: xLongWayHome, Strelok, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "ex_mac10" +-- L["wep." .. w .. ".name.full"] = "Ingram MAC-11" +-- L["wep." .. w .. ".name"] = "MAC-11" +L["wep." .. w .. ".desc"] = "Karabin, który najlepiej sprawdza się w punktowym strzelaniu na oślep." +L["wep." .. w .. ".desc.quote"] = "\"Daj mi ten cholerny pistolet, Tre!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Military Armament Corporation" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Enron \nSounds: Vunsunta, Erick F \nAnimations: Tactical Intervention" + +w = ws .. "ex_mp9" +-- L["wep." .. w .. ".name.full"] = "BT MP9" +-- L["wep." .. w .. ".name"] = "MP9" +L["wep." .. w .. ".desc"] = "Kompaktowy pistolet maszynowy z polimeru, oferujący dużą siłę ognia w małym opakowaniu." +L["wep." .. w .. ".desc.quote"] = "\"Twoja prawa ręka odpada?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Brügger & Thomet" +-- L["wep." .. w .. ".credits"] = "Assets: Logger & The Expert \nSource: Gamebanana" + +w = ws .. "ex_stinger" +-- L["wep." .. w .. ".name.full"] = "FIM-92 Stinger" +-- L["wep." .. w .. ".name"] = "Stinger" +L["wep." .. w .. ".desc"] = "Kierowany wyrzutnik pocisków z funkcją namierzania. Wymaga namierzenia, aby strzelić. Trafienie nie jest gwarantowane." +L["wep." .. w .. ".desc.quote"] = "\"Rogacz przyparty do muru jest bardziej niebezpieczny niż szakal!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Raytheon Missiles and Defense" +-- L["wep." .. w .. ".credits"] = "Assets: Modern Warfare 2 \nAnimations: Tactical Intervention" + +w = ws .. "ex_ump45" +-- L["wep." .. w .. ".name.full"] = "HK UMP45" +-- L["wep." .. w .. ".name"] = "UMP45" +L["wep." .. w .. ".desc"] = "Kanciasty pistolet maszynowy zaprojektowany z myślą o niskich kosztach produkcji. \nWysokie obrażenia z bliska, ale niska skuteczność na zasięg i niska szybkostrzelność." +L["wep." .. w .. ".desc.quote"] = "\"Co to za pistolety koktajlowe?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Hellspike, Logger & Cyper \nSource: Gamebanana" + +w = ws .. "ex_usp" +-- L["wep." .. w .. ".name.full"] = "HK USP" +-- L["wep." .. w .. ".name"] = "USP" +L["wep." .. w .. ".desc"] = "Taktyczny pistolet o dobrej sile rażenia i zasięgu jak na jego pojemność." +L["wep." .. w .. ".desc.quote"] = "Broń z wyboru dla wolnych ludzi." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Thanez, Racer445 \nTextures: Thanez, fxdarkloki \nSounds: Vunsunta, BlitzBoaR \nAnimations: Tactical Intervention" + +w = ws .. "fp6" +-- L["wep." .. w .. ".name.full"] = "HK FABARM FP6" +-- L["wep." .. w .. ".name"] = "FABARM FP6" +L["wep." .. w .. ".desc"] = "Strzelba bojowa o wysokiej szybkostrzelności i pojemności." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FABARM S.p.A." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "g36k" +-- L["wep." .. w .. ".name.full"] = "HK G36K" +-- L["wep." .. w .. ".name"] = "G36K" +L["wep." .. w .. ".desc"] = "Karabin szturmowy o wysokiej prędkości wylotowej. Dobrze nadaje się do średniego zasięgu ognia ciągłego. \nWyposażony w celownik 2x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "gsr1911" +-- L["wep." .. w .. ".name.full"] = "SIG 1911 TACOPS" +-- L["wep." .. w .. ".name"] = "SIG 1911" +L["wep." .. w .. ".desc"] = "Pistolet o wysokich obrażeniach, ale niskim zasięgu i pojemności. Ewolucja taktyczna, albo jak niektórzy by powiedzieli, dewolucja klasyki." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer, Inc." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "hk417" +-- L["wep." .. w .. ".name.full"] = "HK HK417" +-- L["wep." .. w .. ".name"] = "HK417" +L["wep." .. w .. ".desc"] = "Karabin bojowy o doskonałych obrażeniach, szybkostrzelności i precyzji. Zdolny do ognia automatycznego, chociaż jest bardzo niestabilny." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "k1a" +-- L["wep." .. w .. ".name.full"] = "Daewoo K1A" +-- L["wep." .. w .. ".name"] = "K1A" +L["wep." .. w .. ".desc"] = "Karabin serii z minimalnym odrzutem i dobrą celnością strzałów z biodra." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Daewoo Precision" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "knife" +L["wep." .. w .. ".name"] = "Nóż składany" +L["wep." .. w .. ".desc"] = "Wielofunkcyjny nóż składany, chociaż większość zastosowań wiąże się z kłuciem kogoś." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "knife2" +L["wep." .. w .. ".name"] = "Nóż Jackal" +L["wep." .. w .. ".desc"] = "Bardzo ostro wyglądający nóż. Lekkie, częściowo szkieletyzowane ostrze sprawia, że jest szybszy do machania, ale zadaje mniej obrażeń." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention (Unused)" + +w = ws .. "ks23" +-- L["wep." .. w .. ".name"] = "KS-23" +L["wep." .. w .. ".desc"] = "Wykonana z recyklingowanych luf do karabinów lotniczych, ta ciężka strzelba strzela pociskami o dwukrotnie większej średnicy niż typowe śrutówki i może łatwo rozerwać na kawałki wszystko, do czego jest mniej więcej skierowana. Może wyważyć drzwi." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source" + +w = ws .. "m1" +-- L["wep." .. w .. ".name.full"] = "Ruger Mini-14" +-- L["wep." .. w .. ".name"] = "Mini-14" +L["wep." .. w .. ".desc"] = "Lekki karabin bez kolby ani montażu optyki. \nDobra celność strzałów z biodra wśród karabinów, ale zasięg jest niski." +L["wep." .. w .. ".desc.quote"] = "\"Ten z pistoletem mówi prawdę.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m4" +-- L["wep." .. w .. ".name.full"] = "Diemaco C8A1" +-- L["wep." .. w .. ".name"] = "C8A1" +L["wep." .. w .. ".desc"] = "Bliski kuzyn klasycznego amerykańskiego karabinu o wolniejszym, ale bardziej kontrolowanym ogniu." +L["wep." .. w .. ".desc.quote"] = "Zielony i bardzo, bardzo zły." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt Canada" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m4star10" +-- L["wep." .. w .. ".name.full"] = "Benelli M4 Super 90" +-- L["wep." .. w .. ".name"] = "M4 Super 90" +L["wep." .. w .. ".desc"] = "Półautomatyczna strzelba o bardzo wysokim wyjściu obrażeń. Przeładowywanie może być kłopotliwe." +L["wep." .. w .. ".desc.quote"] = "Nie ma nic, czego siedem śrutówek 12 Gauge nie mogłoby rozwiązać." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Benelli Armi S.p.A." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m14" +-- L["wep." .. w .. ".name.full"] = "Springfield M1A" +-- L["wep." .. w .. ".name"] = "M1A" +L["wep." .. w .. ".desc"] = "Półautomatyczny karabin z śmiertelnym strzałem w głowę. \nDomyślnie wyposażony w lunetę 6x." +L["wep." .. w .. ".desc.quote"] = "\"This my rifle! There are many like it, but this one is mine!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m320" +-- L["wep." .. w .. ".name.full"] = "HK M320" +-- L["wep." .. w .. ".name"] = "M320" +L["wep." .. w .. ".desc"] = "Granatnik zdolny do strzelania różnymi pociskami." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "medkit" +L["wep." .. w .. ".name"] = "Apteczka pierwszej pomocy" +L["wep." .. w .. ".desc"] = "Kompaktowy zestaw środków medycznych do leczenia ran. \nMoże leczyć siebie, ale jest bardziej skuteczny, gdy jest używany na innych. \nZaopatrzenie regeneruje się z czasem." +L["wep." .. w .. ".desc.quote"] = "\"Keep still, let me patch you up.\"" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Left 4 Dead 2 \nAnimations: Arqu" + +w = ws .. "mg4" +-- L["wep." .. w .. ".name.full"] = "HK MG4" +-- L["wep." .. w .. ".name"] = "MG4" +L["wep." .. w .. ".desc"] = "Karabin maszynowy o ogromnej sile ognia, ale trudny do użycia bez rozstawionego dwójnogu." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mp5" +-- L["wep." .. w .. ".name.full"] = "HK MP5A3" +-- L["wep." .. w .. ".name"] = "MP5A3" +L["wep." .. w .. ".desc"] = "Zrównoważony pistolet maszynowy znany ze swojej precyzji." +L["wep." .. w .. ".desc.quote"] = "\"Teraz mam karabin maszynowy. Ho, ho, ho.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mp7" +-- L["wep." .. w .. ".name.full"] = "HK MP7" +-- L["wep." .. w .. ".name"] = "MP7" +L["wep." .. w .. ".desc"] = "PDW o doskonałej obsłudze i skuteczności na bliskim zasięgu. Amunicja o wysokiej prędkości zachowuje skuteczność na dystansie i łatwo przebija pancerz." +L["wep." .. w .. ".desc.quote"] = "\"Zapomniałeś przeładować, cholerny!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mr96" +-- L["wep." .. w .. ".name.full"] = "Manurhin MR96" +-- L["wep." .. w .. ".name"] = "MR96" +L["wep." .. w .. ".desc"] = "Rewolwer magnum o dobrym prowadzeniu i sile zatrzymania. Precyzyjny, ale trudny do szybkiego strzelania." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Chapuis Armes" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mtx_dual" +-- L["wep." .. w .. ".name"] = "Dual MTX" +L["wep." .. w .. ".desc"] = "Rozrzutna para kompaktowych pistoletów o dużej pojemności, wysokim uszkodzeniu i wysokiej jakości. \nPrzy takiej sile ognia, kto potrzebuje celowania." +L["wep." .. w .. ".desc.quote"] = "With firepower like this, who needs aiming?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Detonics Defense" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "nade_charge" +L["wep." .. w .. ".name.full"] = "Ładunek Wyważający" + +w = ws .. "nade_flashbang" +L["wep." .. w .. ".name.full"] = "Granat Błyskowy" + +w = ws .. "nade_frag" +L["wep." .. w .. ".name.full"] = "Granat odłamkowy" + +w = ws .. "nade_gas" +L["wep." .. w .. ".name.full"] = "Granat z gazem CS" + +w = ws .. "nade_heal" +L["wep." .. w .. ".name.full"] = "Puszka z Medi-Dymem" + +w = ws .. "nade_smoke" +L["wep." .. w .. ".name.full"] = "Granat Dymny" + +w = ws .. "nade_thermite" +L["wep." .. w .. ".name"] = "Granat termiczny" + +w = ws .. "p90" +-- L["wep." .. w .. ".name.full"] = "FN P90" +-- L["wep." .. w .. ".name"] = "P90" +L["wep." .. w .. ".desc"] = "Bullpup PDW z hojnym magazynkiem ładowanym od góry i kontrolowanym rozrzutem. Pociski o wysokiej prędkości zachowują skuteczność na odległość i łatwo przebijają pancerz." +L["wep." .. w .. ".desc.quote"] = "\"To jest broń wojenna, stworzona do zabijania twojego wroga.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "p250" +-- L["wep." .. w .. ".name.full"] = "SIG P250" +-- L["wep." .. w .. ".name"] = "P250" +L["wep." .. w .. ".desc"] = "Potężny pistolet, który rezygnuje z pojemności na rzecz siły rażenia i precyzji." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer, Inc." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "p2000" +-- L["wep." .. w .. ".name.full"] = "HK P2000" +-- L["wep." .. w .. ".name"] = "P2000" +L["wep." .. w .. ".desc"] = "Dobrze zbalansowany, typowy pistolet policyjny." +L["wep." .. w .. ".desc.quote"] = "\"Naprzód! Naprzód! Naprzód!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "pdw" +-- L["wep." .. w .. ".name"] = "KAC PDW" +L["wep." .. w .. ".desc"] = "Karabin kaliberu subcompact PDW. Idealne połączenie karabinu i pistoletu maszynowego." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "riot_shield" +L["wep." .. w .. ".name"] = "Tarcza Antyterrorystyczna" +L["wep." .. w .. ".desc"] = "Lekka tarcza. Pomimo plastikowo wyglądającego rdzenia, jest w stanie zatrzymać prawie wszystkie pociski kal. karabinowego. \nMożliwość sprintu i ataku wręcz bez narażania bezpieczeństwa użytkownika, ale nieco spowalnia prędkość ruchu." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Tactical Intervention \nAnimations: Arqu" + +w = ws .. "rpg7" +-- L["wep." .. w .. ".name"] = "RPG-7" +L["wep." .. w .. ".desc"] = "Radziecka wyrzutnia rakiet z potężnym wybuchem. \nBezpiecznik zapobiega detonacjom z bliska." +-- L["wep." .. w .. ".desc.quote"] = "If you hear someone screaming its name, duck for cover." +-- L["wep." .. w .. ".trivia.manufacturer"] = "NPO Bazalt" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "sg551" +-- L["wep." .. w .. ".name.full"] = "SIG SG 551" +-- L["wep." .. w .. ".name"] = "SG 551" +L["wep." .. w .. ".desc"] = "Karabin szturmowy o doskonałej wydajności, zrównoważony mniejszą pojemnością magazynka." +L["wep." .. w .. ".desc.quote"] = "\"Bez pytań, bez odpowiedzi. To jest nasz biznes.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "skorpion" +-- L["wep." .. w .. ".name.full"] = "Skorpion vz. 61" +-- L["wep." .. w .. ".name"] = "Skorpion" +L["wep." .. w .. ".desc"] = "Lekki pistolet maszynowy o dobrym zasięgu, odrzucie i rozrzucie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "sphinx" +-- L["wep." .. w .. ".name.full"] = "Sphinx 2000" +-- L["wep." .. w .. ".name"] = "Sphinx" +L["wep." .. w .. ".desc"] = "Pistolet premium zmodyfikowany do strzału 3-rundowego. Wysoka szybkostrzelność, ale długi opóźnienie między seriami." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sphinx Systems" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "spr" +-- L["wep." .. w .. ".name.full"] = "Remington M700 SPS" +-- L["wep." .. w .. ".name"] = "R700 SPS" +L["wep." .. w .. ".desc"] = "Karabin myśliwski średniego zasięgu o szybkim cyklu strzału. \nDomyślnie wyposażony w lunetę 6x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "superv" +-- L["wep." .. w .. ".name.full"] = "Kriss Vector" +-- L["wep." .. w .. ".name"] = "Vector" +L["wep." .. w .. ".desc"] = "SMG na krótki zasięg o bardzo wysokiej szybkostrzelności i praktycznie braku odrzutu. Niska penetracja pancerza, ale może go szybko przegryźć." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kriss USA, Inc." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "tgs12" +-- L["wep." .. w .. ".name.full"] = "Tomahawk Matador" +-- L["wep." .. w .. ".name"] = "Matador" +L["wep." .. w .. ".desc"] = "Strzelba z krótką lufą i chwyt pistoletowy. Wysoka mobilność i odrzut, najbardziej skuteczna na krótkim dystansie." +L["wep." .. w .. ".desc.quote"] = "For years, its true identity was a mystery." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tomahawk Shotguns" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "uratio" +-- L["wep." .. w .. ".name.full"] = "PGM Ultima Ratio" +-- L["wep." .. w .. ".name"] = "Ultima Ratio" +L["wep." .. w .. ".desc"] = "Lekki karabin snajperski o dobrej sile rażenia i dużej mobilności. \nWyposażony w domyślnie 10-krotny celownik." +-- L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "uzi" +-- L["wep." .. w .. ".name.full"] = "IMI Uzi Pro" +-- L["wep." .. w .. ".name"] = "Uzi Pro" +L["wep." .. w .. ".desc"] = "Zrównoważony pistolet maszynowy z kontrolowalną szybkostrzelnością." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "vertec" +-- L["wep." .. w .. ".name.full"] = "Beretta 92FS Vertec" +-- L["wep." .. w .. ".name"] = "92FS Vertec" +L["wep." .. w .. ".desc"] = "Włoski pistolet o ponadprzeciętnym zasięgu i celności." +L["wep." .. w .. ".desc.quote"] = "\"Yippie ki-yay, matko jedna!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "xd45" +-- L["wep." .. w .. ".name.full"] = "Springfield XD-45" +-- L["wep." .. w .. ".name"] = "XD-45" +L["wep." .. w .. ".desc"] = "Automatyczny pistolet maszynowy o niesamowitej sile na krótkim dystansie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +-- Attachments (Non-Bulk) +-- L["att.melee_spec_charge.name.full"] = "demoknight tf2" +-- L["att.melee_spec_charge.name"] = "Charge" +-- L["att.melee_spec_charge.desc"] = "Advance with reckless abandon, and break some laws of physics too." +-- L["att.pro.melee_spec_charge1"] = "RELOAD: Charge" +-- L["att.pro.melee_spec_charge2"] = "WALK + RELOAD: Select Charge Mode" +-- L["att.pro.melee_spec_charge3"] = "Reduced Damage while Charging" +-- L["att.pro.melee_spec_charge4"] = "Reduced Self-Damage" + +-- L["att.melee_spec_lunge.name"] = "Frency" +-- L["att.melee_spec_lunge.desc"] = "Close the distance and overwhelm your enemies." +-- L["att.pro.melee_spec_lunge1"] = "RELOAD: Lunge in Aim Direction" + +-- L["att.melee_spec_nade.name.full"] = "Bombardier" +-- L["att.melee_spec_nade.name"] = "Bomber" +-- L["att.melee_spec_nade.desc"] = "Use jury-rigged impact grenades to ruin someone's day." +-- L["att.pro.melee_spec_nade1"] = "RELOAD: Toggle Grenades" +-- L["att.pro.melee_spec_nade2"] = "Grenades Explode on Impact" +-- L["att.con.melee_spec_nade"] = "Grenade Damage" + +-- L["att.melee_spec_ninja.name"] = "Ninja" +-- L["att.melee_spec_ninja.desc"] = "Disrupt your enemies and strike with the element of surprise." +-- L["att.pro.melee_spec_ninja1"] = "RELOAD: Palm Strike / Climb" +-- L["att.pro.melee_spec_ninja2"] = "RELOAD: (Mid-Air + Crouch): Dive Kick" +-- L["att.pro.melee_spec_ninja3"] = "RELOAD: (Crouch): Backhop" +-- L["att.pro.melee_spec_ninja4"] = "Silent Footsteps" + +-- L["att.melee_spec_scout.name.full"] = "jerma tf2" +-- L["att.melee_spec_scout.name"] = "Scout" +-- L["att.melee_spec_scout.desc"] = "Grass grows, sun shines, birds fly, and brotha' - I hurt people." +-- L["att.pro.melee_spec_scout1"] = "RELOAD: Launch Ball" +-- L["att.pro.melee_spec_scout2"] = "Ball Damage Scales with Distance" +-- L["att.pro.melee_spec_scout3"] = "Ball Applies Slow on Hit" +-- L["att.pro.melee_spec_scout4"] = "Multi Jump" + +-- L["att.melee_spec_step.name"] = "Airdash" +-- L["att.melee_spec_step.desc"] = "Mobility tool used by blood-fueled robots and transgender women." +-- L["att.pro.melee_spec_step1"] = "RELOAD: Dash in Movement Direction" +-- L["att.pro.melee_spec_step2"] = "Invulnerable while Dashing" + +-- L["att.melee_tech_block.name.full"] = "High Guard" +-- L["att.melee_tech_block.name"] = "Guard" +-- L["att.melee_tech_block.desc"] = "Defense is the best offense. It is, coincidentally, also the best defense." +-- L["att.pro.melee_tech_block1"] = "ALT-FIRE: Block Melee Attacks or Projectiles" +-- L["att.pro.melee_tech_block2"] = "Heavy Counterattack After Successful Block" + +-- L["att.melee_tech_heavy.name.full"] = "Heavy-handed" +-- L["att.melee_tech_heavy.name"] = "Heavy" +-- L["att.melee_tech_heavy.desc"] = "A classic counter-terrorist technique: Just smack them real hard." +-- L["att.pro.melee_tech_heavy1"] = "ALT-FIRE: Heavy Attack" +-- L["att.pro.melee_tech_heavy2"] = "Backstab Damage" + +-- L["att.melee_tech_nade.name.full"] = "Grenadier" +-- L["att.melee_tech_nade.name"] = "Grenade" +-- L["att.melee_tech_nade.desc"] = "Always good to have something handy to throw." +-- L["att.pro.melee_tech_nade1"] = "ALT-FIRE: Quickthrow" +-- L["att.pro.melee_tech_nade2"] = "Faster Quickthrow" +-- L["att.pro.melee_tech_nade3"] = "Throw Rocks" + +-- L["att.melee_tech_throw.name.full"] = "Knife Throw" +-- L["att.melee_tech_throw.name"] = "Throw" +-- L["att.melee_tech_throw.desc"] = "Bar trick turned lethal." +-- L["att.pro.melee_tech_throw1"] = "ALT-FIRE: Throw Weapon" +-- L["att.pro.melee_tech_throw2"] = "Consumes Crossbow Bolt Ammo" +-- L["att.pro.melee_tech_throw3"] = "Bonus Headshot Damage" +-- L["att.pro.melee_tech_throw4"] = "Bonus Damage on Slowed/Stunned Targets" + +-- Attachments (acc) +-- L["att.acc_bipod.name"] = "Bipod" +-- L["att.acc_bipod.desc"] = "Foldable support that stabilizes the weapon when deployed." + +-- L["att.acc_brace.name"] = "Pistol Brace" +-- L["att.acc_brace.desc"] = "Turns your pistol into a rifle. The ATF is gonna get your ass." + +-- L["att.acc_cheekrest.name"] = "Cheek Rest" +-- L["att.acc_cheekrest.desc"] = "Stabilizes your head while aiming down sights, reducing sway." + +-- L["att.acc_dual_ergo.name.full"] = "Ergonomic Grip" +-- L["att.acc_dual_ergo.name"] = "Ergo Grip" +-- L["att.acc_dual_ergo.desc"] = "Grooved grip makes it easier to move while shooting two guns." + +-- L["att.acc_dual_quickdraw.name.full"] = "Quickdraw Holster" +-- L["att.acc_dual_quickdraw.name"] = "Quickdraw" +-- L["att.acc_dual_quickdraw.desc"] = "A pair of strapless holster to quickly draw the weapons and hasten loading." + +-- L["att.acc_dual_skel.name.full"] = "Lightweight Grip" +-- L["att.acc_dual_skel.name"] = "Light Grip" +-- L["att.acc_dual_skel.desc"] = "Skeletonized grip makes the guns lighter and easier to move around with." + +-- L["att.acc_duffelbag.name"] = "Gun Bag" +-- L["att.acc_duffelbag.desc"] = "Hide the gun in a bag so you don't cause mass panic." + +-- L["att.acc_ergo.desc"] = "Grooved grip makes aiming faster and moving while shooting easier." + +-- L["att.acc_extendedbelt.name.full"] = "Box Extender" +-- L["att.acc_extendedbelt.name"] = "Box Ext." +-- L["att.acc_extendedbelt.desc"] = "Increase ammo capacity for machine guns significantly." + +L["att.acc_extmag.name.full"] = "Rozszerzony magazynek" +L["att.acc_extmag.name"] = "Rozszerzacz magazynka" +L["att.acc_extmag.desc"] = "Niewielkie zwiększenie pojemności broni." + +-- L["att.acc_extmag_shotgun_tube.name.full"] = "Tube Extender" +-- L["att.acc_extmag_shotgun_tube.name"] = "Tube Ext." +-- L["att.acc_extmag_shotgun_tube.desc"] = "Slightly increase weapon capacity." + +-- L["att.acc_foldstock.name"] = "Fold Stock" +-- L["att.acc_foldstock.desc"] = "Keep stock folded, improving handling significantly at the cost of recoil." + +-- L["att.acc_foldstock2.name"] = "Adjust Stock" +-- L["att.acc_foldstock2.desc"] = "Shorten stock to improve handling somewhat at the cost of recoil." + +-- L["att.acc_pad.name"] = "Recoil Pad" +-- L["att.acc_pad.desc"] = "Rubber pad attached to the end of the stock." + +-- L["att.acc_quickdraw.desc"] = "Strapless holster with magazine pouches for quick drawing and loading." + +-- L["att.acc_skel.desc"] = "Skeletonized grip makes the weapon faster to raise and keep raised." + +-- L["att.acc_sling.name"] = "Sling" +-- L["att.acc_sling.desc"] = "Attach a strap to the weapon, making it easier to draw and reload." + +-- Attachments (ammo) +-- L["att.ammo_40mm_3gl.name.full"] = "40mm Cluster Grenades" +-- L["att.ammo_40mm_3gl.name"] = "3GL" +-- L["att.ammo_40mm_3gl.desc"] = "Three weak cluster grenades, fired at once." + +-- L["att.ammo_40mm_buck.name.full"] = "40mm Buckshot Grenades" +-- L["att.ammo_40mm_buck.name"] = "Buckshot" +-- L["att.ammo_40mm_buck.desc"] = "Flat-top grenade firing shotgun pellets. Weak due to low pressure curve." + +-- L["att.ammo_40mm_gas.name.full"] = "40mm CS Gas Grenades" +-- L["att.ammo_40mm_gas.name"] = "CS Gas" +-- L["att.ammo_40mm_gas.desc"] = "Grenade containing crowd control chemicals that deal lingering damage." + +-- L["att.ammo_40mm_heat.name.full"] = "40mm Flechette Grenades" +-- L["att.ammo_40mm_heat.name"] = "Flechette" +-- L["att.ammo_40mm_heat.desc"] = "Flat-top grenade packing accurate flechette darts." + +-- L["att.ammo_40mm_lvg.name.full"] = "40mm Concussion Grenades" +-- L["att.ammo_40mm_lvg.name"] = "Concussion" +-- L["att.ammo_40mm_lvg.desc"] = "Low velocity grenade made to incapacitate targets with indirect fire." + +-- L["att.ammo_40mm_ratshot.name.full"] = "40mm Ratshot Grenades" +-- L["att.ammo_40mm_ratshot.name"] = "Ratshot" +-- L["att.ammo_40mm_ratshot.desc"] = "For rodents of unbelievable size." + +-- L["att.ammo_40mm_smoke.name.full"] = "40mm Smoke Grenades" +-- L["att.ammo_40mm_smoke.name"] = "Smoke" +-- L["att.ammo_40mm_smoke.desc"] = "Grenade that produces a concealing smokescreen on impact." + +-- L["att.ammo_40mm_heal.name.full"] = "40mm Medi-Smoke Grenades" +-- L["att.ammo_40mm_heal.name"] = "Medi-Smoke" +-- L["att.ammo_40mm_heal.desc"] = "Grenade that produces a cloud of restorative gas on impact." + +-- L["att.ammo_amr_hv.name.full"] = "High Velocity Rounds" +-- L["att.ammo_amr_hv.name"] = "HV" +-- L["att.ammo_amr_hv.desc"] = "Bullets with much higher velocity, but worsens overpenetration." + +-- L["att.ammo_amr_ratshot.name.full"] = "Ratshot Rounds" +-- L["att.ammo_amr_ratshot.name"] = "Ratshot" +-- L["att.ammo_amr_ratshot.desc"] = "For rodents of unusual size." + +-- L["att.ammo_amr_saphe.name.full"] = "Semi-Armor Piercing High-Explosive Rounds" +-- L["att.ammo_amr_saphe.name"] = "SAPHE" +-- L["att.ammo_amr_saphe.desc"] = "Explosive rounds effective against both armor and personnel." + +-- L["att.ammo_ks23_flashbang.name.full"] = "KS-23 Zvezda Flash Shells" +-- L["att.ammo_ks23_flashbang.name"] = "Zvezda" +-- L["att.ammo_ks23_flashbang.desc"] = "Flashbang shells that stun enemies, right from the barrel." + +-- L["att.ammo_ks23_flashbang_top.name.full"] = "KS-23 Zvezda Flash Shells (Top-Loaded)" +-- L["att.ammo_ks23_flashbang_top.name"] = "Zvezda (T)" +-- L["att.ammo_ks23_flashbang_top.desc"] = "Load the first round with flash rounds and the rest with standard shells." + +-- L["att.ammo_magnum.name.full"] = "Overpressured Rounds" +-- L["att.ammo_magnum.name"] = "+P" +-- L["att.ammo_magnum.desc"] = "Bullets that maintain close range power better, but have higher recoil." + +-- L["att.ammo_pistol_ap.name.full"] = "Steel Core Rounds" +-- L["att.ammo_pistol_ap.name"] = "Steel Core" +-- L["att.ammo_pistol_ap.desc"] = "Hardened bullets better penetrate armor, but destabilize ballistics." + +-- L["att.ammo_pistol_headshot.name.full"] = "Skullsplitter Rounds" +-- L["att.ammo_pistol_headshot.name"] = "Skullsplitter" +-- L["att.ammo_pistol_headshot.desc"] = "Specialized rounds that do more damage to vital body parts." + +-- L["att.ammo_pistol_hollowpoints.name.full"] = "Hollow-point Rounds" +-- L["att.ammo_pistol_hollowpoints.name"] = "HP" +-- L["att.ammo_pistol_hollowpoints.desc"] = "Bullets that expand on hit, improving damage to flesh targets and limbs." + +-- L["att.ammo_rifle_jhp.name.full"] = "Jacketed Hollow-point Rounds" +-- L["att.ammo_rifle_jhp.name"] = "JHP" +-- L["att.ammo_rifle_jhp.desc"] = "Bullets with much higher velocity, but worsens overpenetration." + +-- L["att.ammo_pistol_match.name.full"] = "Pistol Match Rounds" +-- L["att.ammo_pistol_match.name"] = "Match" +-- L["att.ammo_pistol_match.desc"] = "Bullets with improved range and accuracy." + +-- L["att.ammo_rifle_match.name.full"] = "Rifle Match Rounds" +-- L["att.ammo_rifle_match.name"] = "Match" +-- L["att.ammo_rifle_match.desc"] = "Bullets with greatly improved accuracy." + +-- L["att.ammo_roulette.name.full"] = "Russian Roulette" +-- L["att.ammo_roulette.name"] = "Roulette" +-- L["att.ammo_roulette.desc"] = "A lethal game of chance. Spin the cylinder while loaded to reset the odds." + +-- L["att.ammo_rpg_improvised.name.full"] = "RPG-7 Improvised Warhead" +-- L["att.ammo_rpg_improvised.name"] = "Improvised" +-- L["att.ammo_rpg_improvised.desc"] = "Straight from the bargain bin." + +-- L["att.ammo_rpg_mortar.name.full"] = "RPG-7 Mortar Warhead" +-- L["att.ammo_rpg_mortar.name"] = "Mortar" +-- L["att.ammo_rpg_mortar.desc"] = "A mortar with a booster stuck to it, for \"indirect fire\". Needs time to prime." + +-- L["att.ammo_rpg_ratshot.name.full"] = "RPG-7 Ratshot Warhead" +-- L["att.ammo_rpg_ratshot.name"] = "Ratshot" +-- L["att.ammo_rpg_ratshot.desc"] = "For rodents of unacceptable size." + +-- L["att.ammo_rpg_harpoon.name.full"] = "RPG-7 Shovel Warhead" +-- L["att.ammo_rpg_harpoon.name"] = "Shovel" +-- L["att.ammo_rpg_harpoon.desc"] = "Fire shovels, somehow. Either you're crazy, out of rockets, or both." + +-- L["att.ammo_shotgun_bird.name"] = "Birdshot" +-- L["att.ammo_shotgun_bird.desc"] = "Fire smaller pellets in a larger spread." + +-- L["att.ammo_shotgun_mag.name.full"] = "Magnum Buckshot" +-- L["att.ammo_shotgun_mag.name"] = "Magnum" +-- L["att.ammo_shotgun_mag.desc"] = "High yield powder improves damage retention past point blank." + +L["att.ammo_shotgun_slugs.name.full"] = "Śrut" +L["att.ammo_shotgun_slugs.name"] = "Śrut" +L["att.ammo_shotgun_slugs.desc"] = "Wypuszcza pojedynczy pocisk do strzelania na średnim zasięgu." + +L["att.ammo_shotgun_triple.name.full"] = "Śrut potrójnego trafu" +L["att.ammo_shotgun_triple.name"] = "Potrójny traf" +L["att.ammo_shotgun_triple.desc"] = "Wypuszcza trzy pociski dla większej celności." + +-- L["att.ammo_subsonic.name.full"] = "Subsonic Rounds" +-- L["att.ammo_subsonic.name"] = "Subsonic" +-- L["att.ammo_subsonic.desc"] = "Bullets with reduced powder load." + +-- L["att.ammo_surplus.name.full"] = "Surplus Rounds" +-- L["att.ammo_surplus.name"] = "Surplus" +-- L["att.ammo_surplus.desc"] = "Unreliable old ammo, yet you keep finding them everywhere." + +-- L["att.ammo_tmj.name.full"] = "Total Metal Jacket Rounds" +-- L["att.ammo_tmj.name"] = "TMJ" +-- L["att.ammo_tmj.desc"] = "Bullets with improved penetration capability." + +-- L["att.ammo_buckshotroulette.name.full"] = "Buckshot Roulette" +-- L["att.ammo_buckshotroulette.name"] = "B. Roulette" +-- L["att.ammo_buckshotroulette.desc"] = "The shells enter the chamber in an unknown order." + +-- L["att.ammo_shotgun_minishell.name.full"] = "Minishells" +-- L["att.ammo_shotgun_minishell.name"] = "Minis" +-- L["att.ammo_shotgun_minishell.desc"] = "Short shells increase ammo capacity but don't hit as hard." + +-- L["att.ammo_shotgun_dragon.name.full"] = "Dragon's Breath" +-- L["att.ammo_shotgun_dragon.name"] = "Dragon" +-- L["att.ammo_shotgun_dragon.desc"] = "Magnesium pellets set targets on fire, but have poor range and damage." + +-- L["att.ammo_shotgun_frag.name.full"] = "High-Explosive Fragmentation Shells" +-- L["att.ammo_shotgun_frag.name"] = "Frag" +-- L["att.ammo_shotgun_frag.desc"] = "Explosive slugs deal area damage, but don't expect too much from them." + +-- L["att.ammo_shotgun_breach.name.full"] = "Breaching Shells (Top-Loaded)" +-- L["att.ammo_shotgun_breach.name"] = "Breach (T)" +-- L["att.ammo_shotgun_breach.desc"] = "Load the first round with a specialized breaching slug." + +-- L["att.ammo_stinger_saam.name.full"] = "FIM-92 Stinger Semi-Active Missile" +-- L["att.ammo_stinger_saam.name"] = "Semi-Active" +-- L["att.ammo_stinger_saam.desc"] = "Powerful missiles that lock rapidly but require constant guidance." + +-- L["att.ammo_stinger_qaam.name.full"] = "FIM-92 Stinger High Agility Missile" +-- L["att.ammo_stinger_qaam.name"] = "Agile" +-- L["att.ammo_stinger_qaam.desc"] = "Highly maneuverable missile with a short range and long lock time." + +-- L["att.ammo_stinger_4aam.name.full"] = "FIM-92 Stinger Quad Missiles" +-- L["att.ammo_stinger_4aam.name"] = "4x" +-- L["att.ammo_stinger_4aam.desc"] = "Guided cluster missiles maximize pressure to enemy pilots." + +-- L["att.ammo_stinger_apers.name.full"] = "FIM-92 Stinger Anti-Personnel Missiles" +-- L["att.ammo_stinger_apers.name"] = "Killer Bee" +-- L["att.ammo_stinger_apers.desc"] = "For rodents of unacceptable agility." + +-- L["att.ammo_usp_9mm.name.full"] = "HK USP 9×19mm Conversion" +-- L["att.ammo_usp_9mm.name"] = "9×19mm" +-- L["att.ammo_usp_9mm.desc"] = "Fire a smaller caliber round with higher capacity and firerate." + +-- Attachments (bolt_trigger) +-- L["att.bolt_fine.name.full"] = "Refined Bolt" +-- L["att.bolt_fine.name"] = "Refined" +-- L["att.bolt_fine.desc"] = "A delicate bolt suitable for short bursts." + +-- L["att.bolt_greased.name.full"] = "Greased Bolt" +-- L["att.bolt_greased.name"] = "Greased" +-- L["att.bolt_greased.desc"] = "Faster cycle speed but handling is worse." + +-- L["att.bolt_heavy.name.full"] = "Heavy Bolt" +-- L["att.bolt_heavy.name"] = "Heavy" +-- L["att.bolt_heavy.desc"] = "Reduce recoil at the cost of fire rate." + +-- L["att.bolt_light.name.full"] = "Light Bolt" +-- L["att.bolt_light.name"] = "Light" +-- L["att.bolt_light.desc"] = "Increase fire rate at the cost of recoil." + +-- L["att.bolt_rough.name.full"] = "Rugged Bolt" +-- L["att.bolt_rough.name"] = "Rugged" +-- L["att.bolt_rough.desc"] = "A durable bolt suitable for long bursts." + +-- L["att.bolt_surplus.name.full"] = "Surplus Bolt" +-- L["att.bolt_surplus.name"] = "Surplus" +-- L["att.bolt_surplus.desc"] = "Rust has eaten most of it away, but it still kinda works." + +-- L["att.bolt_tactical.name.full"] = "Tactical Bolt" +-- L["att.bolt_tactical.name"] = "Tactical" +-- L["att.bolt_tactical.desc"] = "Heavier bolt trades cycling speed for superb control of the weapon." + +-- L["att.bolt_refurbished.name.full"] = "Refurbished Bolt" +-- L["att.bolt_refurbished.name"] = "Refurbished" +-- L["att.bolt_refurbished.desc"] = "Fix the gun's reliability problems with some armory tweaks." + +-- L["att.trigger_akimbo.name.full"] = "Akimbo Trigger" +-- L["att.trigger_akimbo.name"] = "Akimbo" +-- L["att.trigger_akimbo.desc"] = "Let'em have it!" + +-- L["att.trigger_burst.name.full"] = "Burst Trigger" +-- L["att.trigger_burst.name"] = "Burst" +-- L["att.trigger_burst.desc"] = "Trigger that sacrfices automatic fire for stability." + +-- L["att.trigger_burst2.desc"] = "Trigger that emulates burst fire." + +-- L["att.trigger_burstauto.name.full"] = "Auto-Burst Trigger" +-- L["att.trigger_burstauto.name"] = "Auto-Burst" +-- L["att.trigger_burstauto.desc"] = "Trigger that allows continuous burst fire while held." + +-- L["att.trigger_comp.name.full"] = "Competition Trigger" +-- L["att.trigger_comp.name"] = "Competition" +-- L["att.trigger_comp.desc"] = "Lightweight trigger for sports shooting." + +-- L["att.trigger_comp2.desc"] = "Lightweight trigger that recovers from accuracy faster." + +-- L["att.trigger_frcd.name.full"] = "Forced Reset Trigger" +-- L["att.trigger_frcd.name"] = "Forced Reset" +-- L["att.trigger_frcd.desc"] = "Trigger that emulates automatic fire but with poor performance." + +-- L["att.trigger_hair.name.full"] = "Feather Trigger" +-- L["att.trigger_hair.name"] = "Feather" +-- L["att.trigger_hair.desc"] = "Very sensitive trigger for rapid semi-automatic fire." + +-- L["att.trigger_hair_akimbo.desc"] = "Very sensitive trigger for rapid akimbo fire." + +-- L["att.trigger_heavy.name.full"] = "Weighted Trigger" +-- L["att.trigger_heavy.name"] = "Weighted" +-- L["att.trigger_heavy.desc"] = "Heavy trigger for sustained fire." + +-- L["att.trigger_heavy2.desc"] = "Heavy trigger that reduces mobility impact from shooting." + +-- L["att.trigger_semi.name.full"] = "Marksman Trigger" +-- L["att.trigger_semi.name"] = "Marksman" +-- L["att.trigger_semi.desc"] = "Trigger that sacrfices automatic fire for precision." + +-- L["att.trigger_slam.name.full"] = "Slamfire Trigger" +-- L["att.trigger_slam.name"] = "Slamfire" + +-- L["att.trigger_straight.name.full"] = "Straight Trigger" +-- L["att.trigger_straight.name"] = "Straight" +-- L["att.trigger_straight.desc"] = "Narrow trigger with superior recoil performance." + +-- L["att.trigger_wide.name.full"] = "Akimbo Trigger" +-- L["att.trigger_wide.name"] = "Akimbo" +-- L["att.trigger_wide.desc"] = "Large trigger assembly, easy to hold even in awkward positions." + +-- L["att.trigger_dualstage.name.full"] = "Dual Stage Trigger" +-- L["att.trigger_dualstage.name"] = "D. Stage" +-- L["att.trigger_dualstage.desc"] = "Trigger that reduces firerate while aiming for better control and accuracy." + +-- Attachments (melee_boost) +-- L["att.melee_boost_all.name"] = "Level Up" +-- L["att.melee_boost_all.desc"] = "Small boost to all attributes." + +-- L["att.melee_boost_str.name"] = "Bulk Up" +-- L["att.melee_boost_str.desc"] = "Increase Brawn significantly at the cost of other attributes." + +-- L["att.melee_boost_agi.name"] = "Catch Up" +-- L["att.melee_boost_agi.desc"] = "Increase Dexterity significantly at the cost of other attributes." + +-- L["att.melee_boost_int.name"] = "Wise Up" +-- L["att.melee_boost_int.desc"] = "Increase Strategy significantly at the cost of other attributes." + +-- L["att.melee_boost_lifesteal.name"] = "Lifestealer" +-- L["att.melee_boost_lifesteal.desc"] = "Restore health by dealing damage." + +-- L["att.melee_boost_momentum.name"] = "Momentum" +-- L["att.melee_boost_momentum.desc"] = "Restore perk charge by dealing damage." + +-- L["att.melee_boost_afterimage.name"] = "Afterimage" +-- L["att.melee_boost_afterimage.desc"] = "Swing your weapon in a flash, landing the attack instantly." + +-- L["att.melee_boost_shock.name.full"] = "Shock Trooper" +-- L["att.melee_boost_shock.name"] = "S. Trooper" +-- L["att.melee_boost_shock.desc"] = "Reduce impact of impairing effects while weapon is held." + +-- Attachments (muzz) +-- L["att.muzz_hbar.name"] = "Heavy Barrel" +-- L["att.muzz_hbar.desc"] = "Sturdy barrel with improved sway and recoil performance." + +-- L["att.muzz_lbar.name"] = "Light Barrel" +-- L["att.muzz_lbar.desc"] = "Lightweight barrel more accurate and effective at long range." + +L["att.muzz_pistol_comp.name"] = "Kompensator" +L["att.muzz_pistol_comp.desc"] = "Urządzenie wylotowe, które zmniejsza wpływ odrzutu." + +-- L["att.muzz_supp_compact.name.full"] = "Compact Suppressor" +-- L["att.muzz_supp_compact.name"] = "C. Suppressor" +-- L["att.muzz_supp_compact.desc"] = "Short suppressor improving accuracy with low impact to effective range." + +-- L["att.muzz_silencer.name.full"] = "Tactical Suppressor" +-- L["att.muzz_silencer.name"] = "T. Suppressor" +-- L["att.muzz_silencer.desc"] = "Balanced suppressor that reduces recoil and effective range." + +-- L["att.muzz_supp_weighted.name.full"] = "Weighted Suppressor" +-- L["att.muzz_supp_weighted.name"] = "W. Suppressor" +-- L["att.muzz_supp_weighted.desc"] = "Heavy suppressor with superior ballistics but worse handling." + +-- L["att.muzz_brake_aggressor.name.full"] = "Aggressor Brake" +-- L["att.muzz_brake_aggressor.name"] = "A. Brake" +-- L["att.muzz_brake_aggressor.desc"] = "Muzzle brake designed to redirect vented gases away from the shooter." + +-- L["att.muzz_brake_breaching.name.full"] = "Breaching Brake" +-- L["att.muzz_brake_breaching.name"] = "B. Brake" +-- L["att.muzz_brake_breaching.desc"] = "Spiked muzzle brake designed for close combat." + +-- L["att.muzz_brake_concussive.name.full"] = "Concussive Brake" +-- L["att.muzz_brake_concussive.name"] = "C. Brake" +-- L["att.muzz_brake_concussive.desc"] = "Viciously loud, uncomfortable muzzle brake for extreme recoil control." + +-- Attachments (optic_tac) +-- L["att.optic_8x.name.full"] = "Telescopic Scope" +-- L["att.optic_8x.name"] = "Telescopic" +-- L["att.optic_8x.desc"] = "Long-range sniper optic." + +-- L["att.optic_acog.name.full"] = "ACOG Scope" +-- L["att.optic_acog.name"] = "ACOG" +-- L["att.optic_acog.desc"] = "Medium range combat scope." + +-- L["att.optic_elcan.name.full"] = "ELCAN Scope" +-- L["att.optic_elcan.name"] = "ELCAN" +-- L["att.optic_elcan.desc"] = "Low power combat scope." + +-- L["att.optic_holographic.name.full"] = "Holographic Sight" +-- L["att.optic_holographic.name"] = "Holographic" +-- L["att.optic_holographic.desc"] = "Boxy optic to assist aiming at close range." + +-- L["att.optic_irons.name"] = "Iron Sights" +-- L["att.optic_irons.desc"] = "Basic sights for added mobility." + +-- L["att.optic_irons_sniper.desc"] = "Replace default scope for faster aim and better mobility." + +-- L["att.optic_okp7.name"] = "OKP-7" +-- L["att.optic_okp7.desc"] = "Low profile reflex sight with minimal zoom." + +-- L["att.optic_rds2.name.full"] = "Red Dot Sight" +-- L["att.optic_rds2.name"] = "Red Dot" +-- L["att.optic_rds2.desc"] = "Open reflex sight with a clear view." + +-- L["att.optic_rds.name"] = "Aimpoint" +-- L["att.optic_rds.desc"] = "Tube optic to assist aiming at close range." + +-- L["att.optic_rmr.name"] = "RMR" +-- L["att.optic_rmr.desc"] = "Low profile optic sight for pistols." + +-- L["att.optic_rmr_rifle.desc"] = "Low profile optic sight." + +-- L["att.optic_shortdot.name"] = "Short Dot" +-- L["att.optic_shortdot.desc"] = "Compact optic scope with decent magnification." + +-- L["att.tac_cornershot.name"] = "Corner-Cam" +-- L["att.tac_cornershot.desc"] = "Displays point of aim while blindfiring." + +-- L["att.tac_dmic.name"] = "Radar" +-- L["att.tac_dmic.desc"] = "Detects the position of nearby targets, but emits sound." + +-- L["att.tac_flashlight.name"] = "Flashlight" +-- L["att.tac_flashlight.desc"] = "Emits a strong beam of light, blinding anyone staring into it." + +-- L["att.tac_laser.name"] = "Laser" +-- L["att.tac_laser.desc"] = "Emits a narrow red beam and dot, indicating where the gun is pointed at." + +-- L["att.tac_combo.name.full"] = "Laser-Light Combo" +-- L["att.tac_combo.name"] = "Combo" +-- L["att.tac_combo.desc"] = "Emits a green laser and flashlight. The light is too weak to blind others." + +-- L["att.tac_rangefinder.name"] = "Rangefinder" +-- L["att.tac_rangefinder.desc"] = "Measures ballistic performance of the weapon." + +-- L["att.tac_spreadgauge.name"] = "Spread Gauge" +-- L["att.tac_spreadgauge.desc"] = "Measures weapon stability from sway and bloom." + +-- L["att.tac_magnifier.name"] = "Variable Zoom Optic (2x)" +-- L["att.tac_magnifier.name"] = "2x Zoom" +-- L["att.tac_magnifier.desc"] = "Allows all optics to access a 2x zoom level, allowing them to zoom in or out." + +-- L["att.tac_bullet.name.full"] = "Emergency Bullet" +-- L["att.tac_bullet.name"] = "Emrg. Bullet" +-- L["att.tac_bullet.desc"] = "Press the tactical key to quickly load a single bullet for emergencies." + +-- L["att.tac_thermal.name.full"] = "ZUMQFY Thermal Imaging Device" +-- L["att.tac_thermal.name"] = "Thermal-Cam" +-- L["att.tac_thermal.desc"] = "Display a thermal overlay which fuses with the main view while peeking." + +-- Attachments (perk) +-- L["att.perk_aim.name"] = "Deadeye" +-- L["att.perk_aim.desc"] = "Zooms in your aim and makes it easier to fire while sighted." + +-- L["att.perk_blindfire.name.full"] = "Point Shooter" +-- L["att.perk_blindfire.name"] = "Point Shoot" +-- L["att.perk_blindfire.desc"] = "Improves blindfire and peeking." + +-- L["att.perk_hipfire.name"] = "Rambo" +-- L["att.perk_hipfire.desc"] = "Improves weapon accuracy while not aiming." + +-- L["att.perk_melee.name"] = "Smackdown" +-- L["att.perk_melee.desc"] = "Improves melee damage, and slows struck targets." + +-- L["att.perk_reload.name"] = "Quickload" +-- L["att.perk_reload.desc"] = "Improves reloading speed." + +-- L["att.perk_speed.name"] = "Agility" +-- L["att.perk_speed.desc"] = "Improves weapon mobility, especially while reloading." + +-- L["att.perk_throw.name"] = "Grenadier" +-- L["att.perk_throw.desc"] = "Improves quickthrow, and adds the option to throw rocks." + +-- L["att.perk_mlg.name"] = "Stylish" +-- L["att.perk_mlg.desc"] = "Improves quickscoping and accuracy while jumping or moving." + +///////////////////// -- [[ InterOps Weapons ]] -- +-- Weapons +ws = "tacrp_io_" +w = ws .. "870" +-- L["wep." .. w .. ".name.full"] = "Remington 870 SPMM" +-- L["wep." .. w .. ".name"] = "R870 SPMM" +L["wep." .. w .. ".desc"] = "Strzelba \"Marine Magnum\" pokryta niklem. Słaba obsługa, ale ma dużą pojemność i moc ognia." +L["wep." .. w .. ".desc.quote"] = "\"JEST ZŁOTO W TYCH WZGÓRZACH!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Millenia \nSounds: Vunsunta, xLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "ab10" +-- L["wep." .. w .. ".name.full"] = "Intratec AB-10" +-- L["wep." .. w .. ".name"] = "AB-10" +L["wep." .. w .. ".desc"] = "Półautomatyczny model \"After Ban\" TEC-9 z krótką, nietokowaną lufą. Duży magazynek, ale niewiarygodny." +L["wep." .. w .. ".desc.quote"] = "\"Kto potrzebuje broni szturmowej?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Intratec" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Payday 2 \nSounds: The_AntiPirate \nAnimations: Tactical Intervention" + +w = ws .. "af2011" +-- L["wep." .. w .. ".name.full"] = "AF2011-A1" +-- L["wep." .. w .. ".name"] = "AF2011" +L["wep." .. w .. ".desc"] = "W zasadzie dwa połączone pistolety M1911, ta egzotyczna abominacja strzela dwiema kulami przy każdym pociągnięciu spustu." +L["wep." .. w .. ".desc.quote"] = "\"Jeśli 1911 jest tak dobry, dlaczego nie ma 1911 2?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arsenal Firearms" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "automag" +-- L["wep." .. w .. ".name.full"] = "Auto Mag Pistol" +-- L["wep." .. w .. ".name"] = "Auto Mag" +L["wep." .. w .. ".desc"] = "Wysoce precyzyjny pistolet magnum. Świetne prowadzenie dzięki swojemu rozmiarowi, ale może montować tylko optykę pistoletową." +L["wep." .. w .. ".desc.quote"] = "Przeklęty design, który zdołał większość swoich producentów." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Auto Mag Corporation" +-- L["wep." .. w .. ".credits"] = "Model: RedRogueXIII \nTextures/Sounds: Futon \nAnimations: Tactical Intervention" + +w = ws .. "chinalake" +-- L["wep." .. w .. ".name.full"] = "China Lake Launcher" +-- L["wep." .. w .. ".name"] = "China Lake" +L["wep." .. w .. ".desc"] = "Ciężki granatnik o działaniu pompowym z wysoką prędkością początkową, ale słabą obsługą." +L["wep." .. w .. ".desc.quote"] = "Tylko 59 takich kiedykolwiek istniało. Skąd masz ten jeden?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "NAWS China Lake" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "coltsmg" +-- L["wep." .. w .. ".name.full"] = "Colt 9mm SMG" +-- L["wep." .. w .. ".name"] = "Colt SMG" +L["wep." .. w .. ".desc"] = "SMG na platformie AR z ogniem seryjnym. Doskonała kontrola odrzutu, dobre obrażenia i zasięg, ale ograniczone opcje optyczne. Faworyzowany przez Departament Energii." +L["wep." .. w .. ".desc.quote"] = "Głośnik nie jest w zestawie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Assets: Twinke Masta & Kimono \nSounds: Strelok & New World Interactive \nAnimations: Tactical Intervention" + +w = ws .. "cx4" +-- L["wep." .. w .. ".name.full"] = "Beretta CX4 Storm" +-- L["wep." .. w .. ".name"] = "CX4 Storm" +L["wep." .. w .. ".desc"] = "Półautomatyczna karabinek pistoletowy o dobrym zasięgu. Przeciętna penetracja pancerza, ale duża rama sprawia, że broń jest bardzo stabilna." +L["wep." .. w .. ".desc.quote"] = "\"Jak powszechnie wiadomo, w arsenale pewnego lidera PMC.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "degala" +-- L["wep." .. w .. ".name.full"] = "Desert Eagle" +-- L["wep." .. w .. ".name"] = "Deagle" +L["wep." .. w .. ".desc"] = "Imponujący pistolet magnum, tak ikoniczny, jak to tylko możliwe.\nPotężny i o dużej pojemności, ale odrzut jest trudny do opanowania." +L["wep." .. w .. ".desc.quote"] = "\"Słyszysz to, Mr. Anderson?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +-- L["wep." .. w .. ".credits"] = "Model: Vashts1985 \nTextures: Racer445 \nSounds:Vunsunta, XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "fiveseven" +-- L["wep." .. w .. ".name.full"] = "FN Five-seveN" +-- L["wep." .. w .. ".name"] = "Five-seveN" +L["wep." .. w .. ".desc"] = "Masywny pistolet kal. PDW o doskonałej pojemności.\nPociski o wysokiej prędkości zachowują skuteczność na dystansie i łatwo przebijają pancerz." +L["wep." .. w .. ".desc.quote"] = "Czy to fretka biega wokół?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2, edited by speedonerd \nSounds: Vunsunta, Counter-Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "fnc" +-- L["wep." .. w .. ".name.full"] = "FN FNC Para" +-- L["wep." .. w .. ".name"] = "FNC Para" +L["wep." .. w .. ".desc"] = "Lekki karabin szturmowy z wysoką precyzją strzałów z biodra i mobilnością, ale niskim zasięgiem i słabą penetracją pancerza." +L["wep." .. w .. ".desc.quote"] = "\"Mówię, co myślę i robię, co mówię.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Twinke Masta, the_tub, Xero \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "glock18" +-- L["wep." .. w .. ".name"] = "Glock 18C" +L["wep." .. w .. ".desc"] = "Pistolet maszynowy o wysokiej szybkostrzelności i mobilności." +L["wep." .. w .. ".desc.quote"] = "\"Sooner or later, you'll have to jump.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Glock Ges.m.b.H" +-- L["wep." .. w .. ".credits"] = "Model: Hav0k101 \nTextures: el maestro de graffiti \nSounds: BlitzBoaR, Lorn, Ghost597879, Zeven II \nAnimations: Tactical Intervention" + +w = ws .. "k98" +-- L["wep." .. w .. ".name.full"] = "Karabiner 98k" +-- L["wep." .. w .. ".name"] = "Kar98k" +L["wep." .. w .. ".desc"] = "Zabytkowy karabin z zamkiem czterotaktowym o trwałej konstrukcji. Potężny z bliska, ale zasadniczo przestarzały na współczesnym polu bitwy." +L["wep." .. w .. ".desc.quote"] = "\"Chcesz totalnej wojny?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Mauser" +-- L["wep." .. w .. ".credits"] = "Model: Day of Defeat: Source, edited by 8Z \nTextures: Cafe Rev., rascal, 5hifty \nSounds: rzen1th \nAnimations Cry of Fear, Lazarus" + +w = ws .. "k98_varmint" +-- L["wep." .. w .. ".name"] = "Varmint Rifle" +L["wep." .. w .. ".desc"] = "Karabin powtarzalny oparty na mechanizmie Mausera. Akceptuje wszechobecne magazynki AR-15. Lekki, łatwy w obsłudze i ma dużą pojemność, ale obrażenia są niskie." +L["wep." .. w .. ".desc.quote"] = "Dla gryzoni... niepozornych rozmiarów." +-- L["wep." .. w .. ".trivia.manufacturer"] = "All-American Firearms" +-- L["wep." .. w .. ".credits"] = "Model: Day of Defeat: Source, edited by 8Z \nTextures: 5hifty \nSounds: rzen1th \nAnimations: Tactical Intervention" + +w = ws .. "m14" +-- L["wep." .. w .. ".name"] = "M14 SOPMOD" +L["wep." .. w .. ".desc"] = "Zmodernizowany karabin z krótką lufą o poprawionej mobilności i szybkostrzelności automatycznej, ale z dużym odrzutem.\nWyposażony w celownik 6x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Troy Industries" +-- L["wep." .. w .. ".credits"] = "Model: General Tso & Twinke Masta \nTextures: Thanez, Twinke Masta \nSounds: Lakedown, teh strelok & Futon \nAnimations: Tactical Intervention" + +w = ws .. "m16a2" +-- L["wep." .. w .. ".name.full"] = "Colt M16A2" +-- L["wep." .. w .. ".name"] = "M16A2" +L["wep." .. w .. ".desc"] = "Stary karabin szturmowy z ograniczonymi opcjami optyki. Mimo to, jego stabilny odrzut i wysokie obrażenia sprawiają, że jest niezawodny na średnim dystansie." +L["wep." .. w .. ".desc.quote"] = "\"Strzelaj czysto, obserwuj tło.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, SureShot, vagrant \nTextures: Stoke, modderfreak \nSounds: Vunsunta, Navaro \nAnimations: Tactical Intervention" + +w = ws .. "m500" +-- L["wep." .. w .. ".name.full"] = "SW Model 500" +-- L["wep." .. w .. ".name"] = "M500" +L["wep." .. w .. ".desc"] = "Masywny rewolwer o długiej lufie wystrzeliwujący potężny nabój magnum, uznawany za najpotężniejszy pistolet produkcyjny na świecie." +L["wep." .. w .. ".desc.quote"] = "\"Wy, idioci, zrujnowaliście pięcioletnie śledztwo!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nOriginally ported to CS 1.6 by GR_Lucia \nSounds: Ghost597879, MSKyuuni & Zira \nAnimations: Tactical Intervention" + +w = ws .. "mx4" +-- L["wep." .. w .. ".name.full"] = "Beretta MX4 Storm" +-- L["wep." .. w .. ".name"] = "MX4 Storm" +L["wep." .. w .. ".desc"] = "Wojskowy karabinek pistoletowy o wysokiej szybkostrzelności.\nPrzeciętna penetracja pancerza, ale duża rama sprawia, że broń jest dość stabilna." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "p226" +-- L["wep." .. w .. ".name.full"] = "SIG P226" +-- L["wep." .. w .. ".name"] = "P226" +L["wep." .. w .. ".desc"] = "Pistolet z doskonałym zasięgiem i precyzją, ale niskimi obrażeniami ogólnymi." +L["wep." .. w .. ".desc.quote"] = "\"Poprawny termin to 'laski', proszę pana.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Model: SoulSlayer \nTextures: Thanez \nSounds: Anders, DMG, FxDarkloki, & Thanez \nAnimations: Tactical Intervention" + +w = ws .. "rpk" +-- L["wep." .. w .. ".name"] = "RPK" +L["wep." .. w .. ".desc"] = "Lekki karabin maszynowy pochodzący od karabinu piechoty. Wysoka siła rażenia i dobry odrzut, ale słaba mobilność i rozrzut." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Molot" +-- L["wep." .. w .. ".credits"] = "Assets: Call To Arms \nAnimations: Tactical Intervention" + +w = ws .. "ruger" +-- L["wep." .. w .. ".name.full"] = "AWC Amphibian Ruger" +-- L["wep." .. w .. ".name"] = "Amphibian" +L["wep." .. w .. ".desc"] = "Pistolet małego kalibru wyposażony w zintegrowany tłumik. Prawie zerowy odrzut ze względu na słabe naboje." +L["wep." .. w .. ".desc.quote"] = "\"Ciesz się, że nigdy nie miałeś wyboru.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +-- L["wep." .. w .. ".credits"] = "Model: The Lama \nTextures: The Miller \nAnimations: Tactical Intervention" + +w = ws .. "saiga" +-- L["wep." .. w .. ".name"] = "Saiga-12K" +L["wep." .. w .. ".desc"] = "Strzelba o dużej pojemności zasilana z magazynka pudełkowego. Ciasny rozrzut i wysoka szybkostrzelność, ale obrażenia są stosunkowo niskie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Assets: Battlefield 3 \nAnimations: Tactical Intervention" + +w = ws .. "scarh" +-- L["wep." .. w .. ".name.full"] = "FN SCAR-H CQC" +-- L["wep." .. w .. ".name"] = "SCAR-H" +L["wep." .. w .. ".desc"] = "Kompaktowy, bardzo mobilny karabin bojowy z szybką obsługą." +L["wep." .. w .. ".desc.quote"] = "\"Sand Bravo, odczytujemy 70 celów w twoim sektorze.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN America" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2, edited by speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "sg550" +-- L["wep." .. w .. ".name.full"] = "SIG SG 550-1 Sniper" +-- L["wep." .. w .. ".name"] = "SG 550-1" +L["wep." .. w .. ".desc"] = "Karabin wyborowy kaliberu karabinu z szybkim ogniem automatycznym. Łatwy do kontroli w krótkich seriach i ma wysoką penetrację pancerza. Domyślnie wyposażony w lunetę 6x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c & Twinke Masta \nTextures: Twinke Masta \nSounds: Farion, Treyarch & Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "sg550r" +-- L["wep." .. w .. ".name.full"] = "SIG SG550-2 SP" +-- L["wep." .. w .. ".name"] = "SG 550-2" +L["wep." .. w .. ".desc"] = "Długi karabin przekształcony na półautomatyczny dla rynków cywilnych. Łatwy w obsłudze i ma wysoką penetrację pancerza." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c & Twinke Masta \nTextures: Twinke Masta \nSounds: Farion, Treyarch & Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "sl8" +-- L["wep." .. w .. ".name.full"] = "HK SL8" +-- L["wep." .. w .. ".name"] = "SL8" +L["wep." .. w .. ".desc"] = "Półautomatyczna wersja G36 przeznaczona do precyzyjnego strzelania. Niska szybkostrzelność, ale kontrola odrzutu jest doskonała.\nWyposażony w celownik 2x, ale nie ma opcji celownika mechanicznego." +-- L["wep." .. w .. ".desc.quote"] = "\"Used to be a cop myself, only for one day though.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c \nTextures: Twinke Masta \nSounds: KingFriday \nAnimations: Tactical Intervention" + +w = ws .. "star15" +-- L["wep." .. w .. ".name.full"] = "Spikes Tactical AR-15" +-- L["wep." .. w .. ".name"] = "ST AR-15" +L["wep." .. w .. ".desc"] = "Poprawiony karabin AR-15 z myślą o precyzyjnym strzelaniu. \nZawsze wybór samotników na ścieżce zemsty, twój los będzie hałaśliwym pogrzebem... i cichym pożegnaniem. Przynieś detonator." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Spikes Tactical" +-- L["wep." .. w .. ".credits"] = "Assets: carl ruins everything, Leon-DLL, Mira + various sources \nSounds: Insurgency, rzen1th & Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "t850" +-- L["wep." .. w .. ".name.full"] = "Taurus 850 Ultralite" +-- L["wep." .. w .. ".name"] = "T850" +L["wep." .. w .. ".desc"] = "Rewolwer z krótką lufą o kompaktowych wymiarach. Skuteczność gwałtownie spada poza zasięg punktowy." +-- L["wep." .. w .. ".desc.quote"] = "Cardio is free when you're chasing gangsters." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Taurus" +-- L["wep." .. w .. ".credits"] = "Model: Fearfisch \nTextures: Millenia \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "tec9" +-- L["wep." .. w .. ".name.full"] = "Intratec TEC-9" +-- L["wep." .. w .. ".name"] = "TEC-9" +L["wep." .. w .. ".desc"] = "Pistolet maszynowy o złej sławie ze względu na łatwość konwersji na pełny automat, a następnie kryminalne użycie." +L["wep." .. w .. ".desc.quote"] = "\"Klient ma zawsze rację.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Intratec" +-- L["wep." .. w .. ".credits"] = "Model & Texture: Payday 2 \nSounds: The_AntiPirate \nAnimations: Tactical Intervention" + +w = ws .. "trg42" +-- L["wep." .. w .. ".name.full"] = "Sako TRG-42" +-- L["wep." .. w .. ".name"] = "TRG-42" +L["wep." .. w .. ".desc"] = "Karabin snajperski Magnum o przyzwoitej obsłudze i mobilności. \nPotężny, ale wolno się przeładowuje i nie jest bardzo stabilny. \nDomyślnie wyposażony w lunetę 12x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +-- L["wep." .. w .. ".credits"] = "Model: Darkstorn \nTextures: SilentAssassin12 \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "usc" +-- L["wep." .. w .. ".name.full"] = "HK USC" +-- L["wep." .. w .. ".name"] = "USC" +L["wep." .. w .. ".desc"] = "Półautomatyczna wersja karabinka UMP. Używa magazynków o niskiej pojemności, ale jego długa lufa i zespół chwytu zapewniają doskonałe osiągi na średnim dystansie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Twinke Masta, xLongWayHome, Stoke, Teh Snake & Millenia etc. \nAnimations: Tactical Intervention" + +w = ws .. "val" +-- L["wep." .. w .. ".name"] = "AS Val" +L["wep." .. w .. ".desc"] = "Karabin z integralnym tłumikiem o wysokim wyjściu obrażeń i precyzji, ale słabo radzi sobie przy długich seriach." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model & Textures: S.T.A.L.K.E.R. \nSounds: S.T.A.L.K.E.R. & Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "vp70" +-- L["wep." .. w .. ".name.full"] = "HK VP70" +-- L["wep." .. w .. ".name"] = "VP70" +L["wep." .. w .. ".desc"] = "Pistolet polimerowy z innowacyjnym kolbą-holsterem, umożliwiającym niezwykle szybki ogień seriami." +-- L["wep." .. w .. ".desc.quote"] = "\"Where's everyone going? Bingo?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model & Textures: KnechtRuprecht \nSounds: Strelok & xLongWayHome \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "vss" +-- L["wep." .. w .. ".name.full"] = "VSS Vintorez" +-- L["wep." .. w .. ".name"] = "Vintorez" +L["wep." .. w .. ".desc"] = "Karabin wyborowy z integralnym tłumikiem o wysokiej szybkostrzelności i niskim odrzucie, ale słabo radzi sobie w długich seriach. \nDomyślnie wyposażony w lunetę 6x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model: Ettubrutesbro, Millenia & Twinke Masta \nTextures: Millenia \nSounds: S.T.A.L.K.E.R. & Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "xm8car" +-- L["wep." .. w .. ".name.full"] = "HK XM8 Compact" +-- L["wep." .. w .. ".name"] = "XM8 Compact" +L["wep." .. w .. ".desc"] = "Eksperymentalny karabinek wielozadaniowy. Łatwy w użyciu, ale z niskimi obrażeniami. \nWyposażony w regulowany, zintegrowany celownik 2-8x." +-- L["wep." .. w .. ".desc.quote"] = "\"Who loves spaghetti?!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: End Of Days \nTextures: Copkiller, Twinke Masta & Wangchung \nAnimations: Tactical Intervention" + +w = ws .. "xm8lmg" +-- L["wep." .. w .. ".name.full"] = "HK XM8 LMG" +-- L["wep." .. w .. ".name"] = "XM8 LMG" +L["wep." .. w .. ".desc"] = "Eksperymentalny karabin wielozadaniowy w konfiguracji MG. Lekki, o dużej pojemności i niskim odrzucie, ale słaba moc. \nPosiada regulowany wbudowany celownik 2-8x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: End Of Days \nTextures: Copkiller, Twinke Masta & Wangchung \nAnimations: Tactical Intervention" + +-- Attachments (interops) +-- L["att.ammo_star15_300blk.name.full"] = "ST AR-15 .300 AC Blackout Mod Kit" +-- L["att.ammo_star15_300blk.name"] = ".300 BLK" +-- L["att.ammo_star15_300blk.desc"] = "Modification to load lower velocity bullets with better CQB potential." + +-- L["att.ammo_star15_50beo.name.full"] = "ST AR-15 .50 Beowulf Mod Kit" +-- L["att.ammo_star15_50beo.name"] = ".50 BEO" +-- L["att.ammo_star15_50beo.desc"] = "Modification to load low capacity, high power magnum rounds." + +-- L["att.bolt_af2011_alt.name.full"] = "AF2011-A1 Alternating Bolt" +-- L["att.bolt_af2011_alt.name"] = "Alternating" +-- L["att.bolt_af2011_alt.desc"] = "Malicious interpretation of the concept of \"double-stacked magazines\"." + +-- L["att.muzz_comp_io_m14.desc"] = "att.muzz_pistol_comp.desc" + +-- L["att.muzz_tec9_shroud.name.full"] = "TEC-9 Barrel Shroud" +-- L["att.muzz_tec9_shroud.name"] = "Shroud" +-- L["att.muzz_tec9_shroud.desc"] = "Barrel extension improving performance at range." + +-- L["att.optic_ak_pso1.name.full"] = "PSO-1 Scope" +-- L["att.optic_ak_pso1.name"] = "PSO-1" +-- L["att.optic_ak_pso1.desc"] = "Russian dovetail scope with medium-long range magnification." + +-- L["att.optic_ar_colt.name.full"] = "Colt 3x20 Scope" +-- L["att.optic_ar_colt.name"] = "Colt 3x20" +-- L["att.optic_ar_colt.desc"] = "Low power optical scope mounted on AR pattern carry handles." + +-- L["att.optic_k98_zf41.name.full"] = "Zeiss 6x38 Scope" +-- L["att.optic_k98_zf41.name"] = "Zeiss" +-- L["att.optic_k98_zf41.desc"] = "Medium-power sniper scope made specially for the Kar98k." + +-- L["att.optic_xm8_4x.name.full"] = "XM8 Integrated Scope (4x)" +-- L["att.optic_xm8_4x.name"] = "4x" +-- L["att.optic_xm8_4x.desc"] = "Medium range zoom setting with ACOG reticle." + +-- L["att.optic_xm8_6x.name.full"] = "XM8 Integrated Scope (6x)" +-- L["att.optic_xm8_6x.name"] = "6x" +-- L["att.optic_xm8_6x.desc"] = "Medium-long range zoom setting with Short Dot reticle." + +-- L["att.optic_xm8_8x.name.full"] = "XM8 Integrated Scope (8x)" +-- L["att.optic_xm8_8x.name"] = "8x" +-- L["att.optic_xm8_8x.desc"] = "Long range zoom setting with sniper reticle." + +-- L["att.trigger_vp70_auto.name.full"] = "VP-70 Auto Sear Stock" +-- L["att.trigger_vp70_auto.name"] = "Automatic" +-- L["att.trigger_vp70_auto.desc"] = "The engineers at H&K are frothing at their mouths as we speak." + +-- L["att.trigger_vp70_semi.name.full"] = "VP-70 Remove Stock" +-- L["att.trigger_vp70_semi.name"] = "Stockless" +-- L["att.trigger_vp70_semi.desc"] = "Removes burst fire capability, improving handling and mobility." + +///////////////////// -- [[ ArmaLite Revolution ]] -- +-- Weapons +ws = "tacrp_ar_" +w = ws .. "ar15pistol" +-- L["wep." .. w .. ".name"] = "AR-15 Compact" +L["wep." .. w .. ".desc"] = "Bezkolbowy, bardzo krótki karabin AR-15. \nPrawnie pistolet, wystarczająco lekki, aby funkcjonować jako broń boczna, ale niestabilny i nieprecyzyjny bez formy karabinu." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Ultra-Tac" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Mateusz Woliński, Jordan Whincup \nMagazine: Twinke Masta \nSounds: Teh Strelok, Vunsunta, xLongWayHome, CS:O2 \nAnimations: Tactical Intervention" + +w = ws .. "gilboa" +-- L["wep." .. w .. ".name.full"] = "Gilboa DBR Snake" +-- L["wep." .. w .. ".name"] = "Gilboa DBR" +L["wep." .. w .. ".desc"] = "Unikalny karabinek AR z podwójnym lufą. Dwa razy większa śmiertelność niż jedna lufa, ale konstrukcja jest masywna i nieprecyzyjna. \nZ oczywistych powodów nie może przyjmować osprzętu do lufy." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Silver Shadow" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "hk416" +-- L["wep." .. w .. ".name.full"] = "HK HK416" +-- L["wep." .. w .. ".name"] = "HK416" +L["wep." .. w .. ".desc"] = "Elegancki czarny karabin stworzony jako konkurent dla AR-15. Precyzyjny i o niskim odrzucie kosztem pewnej masy." +L["wep." .. w .. ".desc.quote"] = "Elitarna broń jak ta to wszystko, czego potrzebujesz." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, B0T, SoulSlayer \nTextures: Acid Snake, el maestro de graffiti, Antman \nSounds: Vunsunta, xLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "lr300" +-- L["wep." .. w .. ".name.full"] = "Z-M LR-300" +-- L["wep." .. w .. ".name"] = "LR-300" +L["wep." .. w .. ".desc"] = "Pochodna AR \"Light Rifle\" z modyfikowanym systemem gazowym. Oferuje wysoką mobilność i szybkostrzelność, ale stabilność jest poniżej standardu. \n\"300\" oznacza maksymalny zasięg skuteczny karabinu w metrach." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Z-M Weapons" +-- L["wep." .. w .. ".credits"] = "Model: TheLama \nTextures: Wannabe \nSounds: NightmareMutant \nAnimations: Tactical Intervention" + +w = ws .. "m16a1" +-- L["wep." .. w .. ".name.full"] = "Colt M16A1" +-- L["wep." .. w .. ".name"] = "M16A1" +-- L["wep." .. w .. ".desc"] = "An antique rifle recovered from the rice fields. \nBoasts high firing rate and good range, though don't expect this bucket of rust to run without a hitch or two." +-- L["wep." .. w .. ".desc.quote"] = "Welcome to the jungle." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c \nTextures: Millenia \nCompile: The_AntiPirate \nAnimations: Tactical Intervention" + +w = ws .. "m16a4" +-- L["wep." .. w .. ".name.full"] = "Colt M16A4" +-- L["wep." .. w .. ".name"] = "M16A4" +L["wep." .. w .. ".desc"] = "Nowoczesny karabin piechoty z nowoczesnymi usprawnieniami, takimi jak górna szyna i chwyt RIS. Wszechstronna broń piechoty o dobrym skutecznym zasięgu." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, SureShot, vagrant \nTextures: Stoke, modderfreak \nSounds: Vunsunta, Navaro \nAnimations: Tactical Intervention" + +w = ws .. "sr25" +-- L["wep." .. w .. ".name.full"] = "KAC SR-25 EMR" +-- L["wep." .. w .. ".name"] = "SR-25" +L["wep." .. w .. ".desc"] = "\"Karabin Dostosowany\" stworzony do precyzyjnego strzelania. \nNiska pojemność, ale doskonała wydajność. \nWyposażony w celownik 10x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "vltor" +-- L["wep." .. w .. ".name"] = "VLTOR SBR" +L["wep." .. w .. ".desc"] = "Karabinek na platformie AR z unikalną osłoną na rękojeść w stylu piggyback. Doskonały na bliskie dystanse, bez utraty wydajności na średnich dystansach." +L["wep." .. w .. ".desc.quote"] = "\"Pospiesz się! Idź do Whiskey Hotel!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "VLTOR" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: Teh Strelok \nAnimations: Tactical Intervention" + +-- Attachments (ar) +-- L["att.bolt_gilboa_alt.name.full"] = "Gilboa DBR Alternating Bolt" +-- L["att.bolt_gilboa_alt.name"] = "Alternating" +-- L["att.bolt_gilboa_alt.desc"] = "Separated bolts that are able to fire alternatingly, somehow." + +-- L["att.muzz_sr25.name.full"] = "SR-25 Suppressor Shroud" +-- L["att.muzz_sr25.name"] = "SR-25 Supp." +-- L["att.muzz_sr25.desc"] = "Unique suppressor shroud that improves ballistics but lowers fire rate." + +///////////////////// -- [[ Special Delivery ]] -- +-- Weapons +ws = "tacrp_sd_" +w = ws .. "1022" +-- L["wep." .. w .. ".name.full"] = "Ruger 10/22" +-- L["wep." .. w .. ".name"] = "10/22" +L["wep." .. w .. ".desc"] = "Ultra lekki karabin do strzelania. Wysoce precyzyjny i łatwy w obsłudze, ale jest ledwo śmiertelny, chyba że zdobędzie strzał w głowę." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +-- L["wep." .. w .. ".credits"] = "Assets: No More Room in Hell \nAnimations: Tactical Intervention" + +w = ws .. "1858" +-- L["wep." .. w .. ".name.full"] = "Remington 1858 Army" +-- L["wep." .. w .. ".name"] = "Army" +L["wep." .. w .. ".desc"] = "Zabytkowy rewolwer na kapiszony o dużej mocy na krótkim dystansie, ale bardzo wolny w strzelaniu. Odpowiedni do kowbojskiego roleplayu." +L["wep." .. w .. ".desc.quote"] = "\"Podaj whiskey!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Nimrod Hempel (Fistful of Frags) \nAnimations: CyloWalker \nQuickthrow & melee animations: speedonerd" + +w = ws .. "aac_hb" +-- L["wep." .. w .. ".name.full"] = "AAC Honey Badger" +-- L["wep." .. w .. ".name"] = "Honey Badger" +L["wep." .. w .. ".desc"] = "Lekki karabin szturmowy z integralnym tłumikiem. Mocny w walce wręcz i nie ma widocznego śladu, ale ma słabe osiągi na dystansie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "AAC" +-- L["wep." .. w .. ".credits"] = "Model: Hyper \nAnimations: Tactical Intervention" + +w = ws .. "bizon" +-- L["wep." .. w .. ".name.full"] = "PP-19 Bizon" +-- L["wep." .. w .. ".name"] = "Bizon" +L["wep." .. w .. ".desc"] = "SMG pochodzące z AK z dużym pojemnym magazynkiem spiralnym. Dość słaby, ale łatwy do kontrolowania i obsługi." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhmash" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Milo \nSounds: Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "contender" +-- L["wep." .. w .. ".name.full"] = "T/C G2 Contender" +-- L["wep." .. w .. ".name"] = "Contender" +L["wep." .. w .. ".desc"] = "Jednostrzałowy pistolet myśliwski. Precyzyjny, kompaktowy i śmiertelny, więc lepiej, żeby ta jedna runda się liczyła. \nDomyślnie wyposażony w lunetę 6x." +L["wep." .. w .. ".desc.quote"] = "\"Wiesz co mnie denerwuje? Dwie grupy ludzi...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Thompson/Center" +-- L["wep." .. w .. ".credits"] = "Model: kriboez, Doktor haus \nTextures: cR45h, syncing, tenoyl, Ultimately \nSounds: Doktor haus \nAnimations: 8Z, speedonerd" + +w = ws .. "db" +-- L["wep." .. w .. ".name.full"] = "Stoeger Double Defense" +-- L["wep." .. w .. ".name"] = "Double Defense" +L["wep." .. w .. ".desc"] = "Nowoczesna krótka strzelba dwulufowa. Łatwa w obsłudze, niezawodna i śmiertelna w walce wręcz." +L["wep." .. w .. ".desc.quote"] = "\"Eat leaden death, demon.\"" --To be translated +-- L["wep." .. w .. ".trivia.manufacturer"] = "Stoeger" +-- L["wep." .. w .. ".credits"] = "Model: Counter-Strike: Online 2 \nSounds: Navaro & Vunsunta \nAnimations: speedonerd & 8Z" + +w = ws .. "delisle" +L["wep." .. w .. ".name.full"] = "Karabin De Lisle" +-- L["wep." .. w .. ".name"] = "De Lisle" +L["wep." .. w .. ".desc"] = "Karabin samopowtarzalny kaliberu pistoletowego z wbudowanym tłumikiem. Jedna z najcichszych broni palnych kiedykolwiek wykonanych, jego pociski subsoniczne nie mają smug, ale poruszają się powoli." +L["wep." .. w .. ".desc.quote"] = "Wystrzelony do Tamizy, nikt go nie usłyszał." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sterling Armaments" +-- L["wep." .. w .. ".credits"] = "Model: RedRougeXIII \nTextures: Storm (lovingly fixed by Unselles) \nAnimations: Tactical Intervention" + +w = ws .. "dual_1911" +-- L["wep." .. w .. ".name"] = "Dueling Wyverns" +L["wep." .. w .. ".desc"] = "Para ozdobnych, złotych pistoletów M1911 z wyrytymi wizerunkami wiwern. Mocno uderzają, ale ich niska pojemność może być problematyczna." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Model: Schmung \nTextures: Millenia \nAnimations: Tactical Intervention" + +w = ws .. "dual_degala" +-- L["wep." .. w .. ".name"] = "Dual Eagles" +L["wep." .. w .. ".desc"] = "Para ozdobnych, złotych Desert Eagles, jakby jeden nie wystarczył. Podwójna szalona moc obalająca, podwójny szalony odrzut." +L["wep." .. w .. ".desc.quote"] = "\"Moja krew... na ich rękach.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c & Stoke \nTextures: The_Tub & Stoke \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "dual_ppk" +L["wep." .. w .. ".name"] = "Podwójni agenci" +L["wep." .. w .. ".desc"] = "Para tłumionych pistoletów PPK. Szybkie i precyzyjne, ale niska pojemność i przeciętne obrażenia wymagają ostrego oka i dyscypliny spustu." +L["wep." .. w .. ".desc.quote"] = "Lepiej nie wybieraj Oddjoba." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: HK & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "dual_usp" +L["wep." .. w .. ".name"] = "Podwójne mecze" +L["wep." .. w .. ".desc"] = "Para pistoletów zdobytych od pary martwych metrocops. Przyzwoite obrażenia i pojemność, ale ciężkie i wolne do strzelania." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Silvio Dante & Twinke Masta \nTextures: LoneWolf \nAnimations: Tactical Intervention" + +w = ws .. "dual_uzis" +L["wep." .. w .. ".name"] = "Podwójne Uzis" +L["wep." .. w .. ".desc"] = "Para pełnoautomatycznych Micro Uzis. Nie wiem, jak oczekujesz trafić cokolwiek z tym wyposażeniem, ale rób, jak uważasz." +L["wep." .. w .. ".desc.quote"] = "\"Uderzę cię tyloma prawami, że będziesz błagać o lewe!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Model: BrainBread 2 \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "dualies" +-- L["wep." .. w .. ".name"] = "Dueling Dragons" +L["wep." .. w .. ".desc"] = "Para spersonalizowanych pistoletów z dwukolorowym wykończeniem i smokami na uchwytach. Szybka obsługa i przyzwoita kontrola odrzutu, ale niska moc obalająca." +L["wep." .. w .. ".desc.quote"] = "\"Puściłem palec ze spustu i to było koniec.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Model: Spydr \nTextures: NCFurious \nAnimations: Tactical Intervention" + +w = ws .. "famas" +-- L["wep." .. w .. ".name"] = "FAMAS F1" +L["wep." .. w .. ".desc"] = "Bullpup z ogniem serii o wysokiej szybkostrzelności i świetnej celności, ale ograniczony przez poniżej standardową pojemność magazynka i dość intensywny odrzut. \nMa dwójnóg z tajemniczych francuskich powodów." +-- L["wep." .. w .. ".trivia.manufacturer"] = "GIAT Industries" +-- L["wep." .. w .. ".credits"] = "Model: SnipaMasta \nTextures: SnipaMasta, Fnuxray \nAnimations: speedonerd" + +w = ws .. "famas_g2" +-- L["wep." .. w .. ".name"] = "FAMAS G2" +-- L["wep." .. w .. ".desc"] = "Bullpup rifle with a blazing fast firerate, used by the French Navy. While capable of automatic fire, using burst-fire or the built-in bipod is recommended." +-- L["wep." .. w .. ".desc.quote"] = "\"God's got a sense of humor, alright.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "GIAT Industries" +-- L["wep." .. w .. ".credits"] = "Model: SnipaMasta \nTextures: SnipaMasta, Fnuxray \nAnimations: speedonerd" + +w = ws .. "g3" +-- L["wep." .. w .. ".name.full"] = "HK G3A3" +-- L["wep." .. w .. ".name"] = "G3A3" +L["wep." .. w .. ".desc"] = "Precyzyjny ciężki karabin bojowy z dość znośnym trybem ognia automatycznego, ale wolnym obsługiwaniem." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nSounds: Nightmare Mutant & FA:S2 \nAnimations: Tactical Intervention" + +w = ws .. "groza" +L["wep." .. w .. ".name.full"] = "OTs-14 \"Groza-4\"" +-- L["wep." .. w .. ".name"] = "Groza-4" +L["wep." .. w .. ".desc"] = "Niejasny karabin bullpup wykonany z przekonfigurowanego AK. Słaby, ale ma świetne prowadzenie i stabilność. \nTłumik nie jest integralny, ale działa jako chwyt przedni." +L["wep." .. w .. ".desc.quote"] = "\"Znikaj stąd, stalkerze.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "TsKIB SOO" +-- L["wep." .. w .. ".credits"] = "Model: Teh Snake, edited by speedonerd \nTextures: Teh Snake \nSounds: Teh Snake & speedonerd \nAnimations: speedonerd" + +w = ws .. "gyrojet" +L["wep." .. w .. ".name.full"] = "MBA Gyrojet MkI" +-- L["wep." .. w .. ".name"] = "Gyrojet" +L["wep." .. w .. ".desc"] = "Niejasny eksperymentalny pistolet strzelający samonapędzającymi się minirakietami. Obecnie głównie przedmiot kolekcjonerski, amunicja jest potężna, ale niewiarygodna." +L["wep." .. w .. ".desc.quote"] = "\"Z czyjej prywatnej kolekcji to ukradłeś?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "MBAssociates" +-- L["wep." .. w .. ".credits"] = "Model & Textures: RedRougeXIII \nSounds: speedonerd, Tactical Intervention \nAnimations: speedonerd" + +w = ws .. "m1carbine" +-- L["wep." .. w .. ".name"] = "M1 Carbine" +L["wep." .. w .. ".desc"] = "Karabin samopowtarzalny z okresu II wojny światowej. Przeznaczony jako broń obronna dla wojsk wsparcia, jest precyzyjny i lekki, ale ma przeciętną moc." +-- L["wep." .. w .. ".desc.quote"] = "\"Flash.\" \"Thunder.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "General Motors" +-- L["wep." .. w .. ".credits"] = "Model & Textures: KnechtRuprecht \nSounds: NightmareMutant \nAnimations: Tactical Intervention" + +w = ws .. "mp40" +-- L["wep." .. w .. ".name.full"] = "Steyr MP40" +-- L["wep." .. w .. ".name"] = "MP40" +L["wep." .. w .. ".desc"] = "SMG z okresu II wojny światowej o niskiej szybkostrzelności i diabelskim profilu odrzutu. Pomimo swojego wieku, nadal pojawia się w wielu strefach wojennych." +L["wep." .. w .. ".desc.quote"] = "\"Hans, twoja kawa jest okropna. Nie będę tego pił.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr-Mannlicher" +-- L["wep." .. w .. ".credits"] = "Model: Soul-Slayer \nTextures: Kimono \nSounds: Futon & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "pkm" +-- L["wep." .. w .. ".name"] = "PKM" +L["wep." .. w .. ".desc"] = "Uniwersalny karabin maszynowy zdolny do intensywnego ognia zaporowego. Wysoka pojemność i obrażenia, ale jest bardzo, bardzo masywny." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Degtyaryov Plant" +-- L["wep." .. w .. ".credits"] = "Assets: Call to Arms \nSounds: NightmareMutant & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "ppk" +-- L["wep." .. w .. ".name.full"] = "Walther PPK" +-- L["wep." .. w .. ".name"] = "PPK" +L["wep." .. w .. ".desc"] = "Kompaktowy, niskopojemnościowy pistolet kieszonkowy, słynny dzięki filmom. Najlepiej nadaje się do obrony na bliskim dystansie." +L["wep." .. w .. ".desc.quote"] = "\"Szokujące. Absolutnie szokujące.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: HK & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "superx3" +-- L["wep." .. w .. ".name.full"] = "Winchester Super X3" +-- L["wep." .. w .. ".name"] = "Super X3" +L["wep." .. w .. ".desc"] = "Cywilna strzelba sportowa, zaprojektowana z myślą o wydajności. Długa lufa i sportowy czok zapewniają dobrą kontrolę i zasięg, ale słabe prowadzenie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Winchester Repeating Arms" +-- L["wep." .. w .. ".credits"] = "Model, Textures & Sound: No More Room in Hell \nAnimations: Tactical Intervention" + +w = ws .. "thompson" +-- L["wep." .. w .. ".name.full"] = "M1A1 Thompson" +-- L["wep." .. w .. ".name"] = "Thompson" +L["wep." .. w .. ".desc"] = "Pistolet maszynowy z czasów II wojny światowej z solidnymi drewnianymi elementami. Imponująca siła ognia na bliskim dystansie, ale znacznie cięższy niż powinien być." +-- L["wep." .. w .. ".desc.quote"] = "\"Eat lead, jerries!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Auto-Ordnance Company" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "tt33" +-- L["wep." .. w .. ".name.full"] = "Tokarev TT-33" +-- L["wep." .. w .. ".name"] = "TT-33" +L["wep." .. w .. ".desc"] = "Antyczny pistolet zza Żelaznej Kurtyny. Amunicja ma zaskakująco duży cios, ale jest niepewna." +-- L["wep." .. w .. ".desc.quote"] = "\"Perhaps you would prefer to avoid the red tape?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model: Mr.Rifleman \nTextures: BuLL5H1T & Mr.Rifleman \nSounds: NightmareMutant \nAnimations: Tactical Intervention" + +w = ws .. "vz58" +-- L["wep." .. w .. ".name.full"] = "Sa vz. 58" +-- L["wep." .. w .. ".name"] = "vz. 58" +L["wep." .. w .. ".desc"] = "Karabin szturmowy o wysokim uszkodzeniu z doskonałymi możliwościami przebijania pancerza, przekształcony na półautomatyczny dla rynków cywilnych." +L["wep." .. w .. ".desc.quote"] = "Pomimo wyglądu, to na pewno nie jest AK." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Model, Textures & Sounds: No More Room in Hell \nAnimations: Tactical Intervention" + +w = ws .. "wa2000" +-- L["wep." .. w .. ".name.full"] = "Walther WA 2000" +-- L["wep." .. w .. ".name"] = "WA 2000" +L["wep." .. w .. ".desc"] = "Elegancki karabin snajperski bullpup o wysokim uszkodzeniu i wysokiej szybkostrzelności. \nDomyślnie wyposażony w lunetę 12x." +L["wep." .. w .. ".desc.quote"] = "\"Imiona są dla przyjaciół, więc nie potrzebuję jednego.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nOriginally ported to CS 1.6 by GR_Lucia \nSounds: HK & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +-- Attachments (sd) +-- L["att.trigger_dual_uzis_semi.name.full"] = "att.trigger_semi.name.full" +-- L["att.trigger_dual_uzis_semi.name"] = "att.trigger_semi.name" +-- L["att.trigger_dual_uzis_semi.desc"] = "att.trigger_semi.desc" + +-- L["att.tac_1858_spin.name.full"] = "Revolver Spin" +-- L["att.tac_1858_spin.name"] = "Spin" +-- L["att.tac_1858_spin.desc"] = "wheeeeeeeeeeeee" + +-- L["att.optic_m1_scope.name.full"] = "M1 Carbine 3.5x24 Scope" +-- L["att.optic_m1_scope.name"] = "3.5x Scope" +-- L["att.optic_m1_scope.desc"] = "Optical scope with specialized mount for the M1 Carbine." + +-- L["att.optic_delisle_scope.name.full"] = "De Lisle 4x24 Scope" +-- L["att.optic_delisle_scope.name"] = "4x Scope" +-- L["att.optic_delisle_scope.desc"] = "Optical scope with specialized mount for the De Lisle." + +-- L["att.muzz_supp_assassin.name.full"] = "Assassin Suppressor" +-- L["att.muzz_supp_assassin.name"] = "A. Suppressor" +-- L["att.muzz_supp_assassin.desc"] = "Extended suppressor improving range significantly at cost of stability." + +-- L["att.ammo_gyrojet_ratshot.name.full"] = "13mm Ratshot Mini-Rockets" +-- L["att.ammo_gyrojet_ratshot.name"] = "Ratshot" +-- L["att.ammo_gyrojet_ratshot.desc"] = "Proximity fuse fragmentation mini-rockets. For rodents of unexpected size." + +-- L["att.ammo_gyrojet_pipe.name.full"] = "15mm Boosted Pipe Grenades" +-- L["att.ammo_gyrojet_pipe.name"] = "Pipe" +-- L["att.ammo_gyrojet_pipe.desc"] = "Heavy grenades with timed fuse. Direct hits detonate instantly." + +-- L["att.ammo_gyrojet_lv.name.full"] = "11mm Low Velocity Mini-Rockets" +-- L["att.ammo_gyrojet_lv.name"] = "LV" +-- L["att.ammo_gyrojet_lv.desc"] = "Projectiles with reduced diameter and velocity, leaving a less visible trail." + +-- L["att.ammo_gyrojet_he.name.full"] = "13mm High-Explosive Mini-Rockets" +-- L["att.ammo_gyrojet_he.name"] = "HE" +-- L["att.ammo_gyrojet_he.desc"] = "Projectile with a small explosive charge instead of a bullet head." + +-- L["att.ammo_1858_45colt.name.full"] = "Remington 1858 .45 Colt Conversion" +-- L["att.ammo_1858_45colt.name"] = ".45 Colt" +-- L["att.ammo_1858_45colt.desc"] = "Cartridge conversion firing larger, more powerful, but less reliable rounds." + +-- L["att.ammo_1858_36perc.name.full"] = "Remington 1858 .36 Caliber Conversion" +-- L["att.ammo_1858_36perc.name"] = ".36 Percussion" +-- L["att.ammo_1858_36perc.desc"] = "Caliber conversion firing smaller rounds with better range." + +-- L["att.procon.yeehaw"] = "yeeeeeeeeeeeeehawwwwwwwwww" + +///////////////////// -- [[ Brute Force ]] -- +-- Weapons +ws = "tacrp_m_" +w = ws .. "bamaslama" +-- L["wep." .. w .. ".name"] = "Alabama Slammer" +L["wep." .. w .. ".desc"] = "Nóż bowie nazwany na cześć koktajlu, z ząbkowanym tylnym ostrzem i mocowaniem bagnetu." +L["wep." .. w .. ".desc.quote"] = "\"Czy powiedziałem, że możesz to zrobić?\"" +-- L["wep." .. w .. ".credits"] = "Model: Havok101 \nTextures: Millenia" + +w = ws .. "bat" +-- L["wep." .. w .. ".name.full"] = "Loisville Slugger TPX" +-- L["wep." .. w .. ".name"] = "Slugger" +L["wep." .. w .. ".desc"] = "Aluminiowa pałka bejsbolowa, dobra do bijania home runów lub łamania czaszek." +L["wep." .. w .. ".desc.quote"] = "\"Pop quiz! Ile czasu zajmuje zabicie debila?\"" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Yogensia \nAnimations: Lazarus" + +w = ws .. "bayonet" +-- L["wep." .. w .. ".name"] = "M9 Phrobis III" +L["wep." .. w .. ".desc"] = "Standardowy wojskowy nóż, który można zamontować na broni jako bagnet. Nic nie stoi na przeszkodzie, aby używać go jak zwykłego noża." +-- L["wep." .. w .. ".credits"] = "Assets: BrainBread 2" + +w = ws .. "boina" +-- L["wep." .. w .. ".name"] = "Cudeman Boina Verde" +L["wep." .. w .. ".desc"] = "Solidny, duży nóż do przetrwania. \"Boina verde\" to po hiszpańsku \"zielony beret\", próba zareklamowania noża jako wojskowego." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Shortez" + +w = ws .. "cleaver" +L["wep." .. w .. ".name"] = "Siekać mięso" +L["wep." .. w .. ".desc"] = "Duże, solidne ostrze do siekania mięsa, czy to mięsa zwierzęcego, czy ludzkiego. Ma wrodzone Wampiryzm." +-- L["wep." .. w .. ".credits"] = "Assets: Warface" + +w = ws .. "crowbar" +L["wep." .. w .. ".name"] = "Łom" +L["wep." .. w .. ".desc"] = "Wszechstronne narzędzie użytkowe przeznaczone do wyważania rzeczy; skrzynki, sejfy, drzwi i ludzie!" +L["wep." .. w .. ".desc.quote"] = "\"Myślę, że upuściłeś to z powrotem w Black Mesa.\"" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2" + +w = ws .. "css" +-- L["wep." .. w .. ".name"] = "The Classic" +L["wep." .. w .. ".desc"] = "Klasyczny nóż z lat 90., zanim modne wzory i absurdalne ceny stały się normą." +L["wep." .. w .. ".desc.quote"] = "\"Dobra, ruszajmy!\"" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Source" + +w = ws .. "fasthawk" +-- L["wep." .. w .. ".name"] = "SOG Fasthawk" +L["wep." .. w .. ".desc"] = "Zmodernizowany tomahawk zaprojektowany do walki. Aerodynamiczny design pozwala na dalekie rzuty." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Yogensia" + +w = ws .. "gerber" +-- L["wep." .. w .. ".name.full"] = "Gerber LMF Infantry" +-- L["wep." .. w .. ".name"] = "LMF Infantry" +L["wep." .. w .. ".desc"] = "Kompaktowy nóż do sytuacji przetrwania, takich jak patroszenie ryb, rzeźbienie drewna lub patroszenie i rzeźbienie wroga." +-- L["wep." .. w .. ".credits"] = "Model: Macaroane \nTextures: Blackfire" + +w = ws .. "glock" +-- L["wep." .. w .. ".name.full"] = "Glock Feldmesser 78" +-- L["wep." .. w .. ".name"] = "Feldmesser 78" +L["wep." .. w .. ".desc"] = "Solidny nóż polowy z mocnym chwytem, zaprojektowany do użytku w austriackim Jagdkommando." +L["wep." .. w .. ".desc.quote"] = "Tak, to ten sam Glock, który produkuje pistolety." +-- L["wep." .. w .. ".credits"] = "Model: HellSpike \nTextures: Dr. Hubbler" + +w = ws .. "hamma" +L["wep." .. w .. ".name"] = "Młotek" +L["wep." .. w .. ".desc"] = "Powszechne narzędzie rzemieślnika do wbijania gwoździ w drewno lub mocowania przedmiotów na miejscu." +L["wep." .. w .. ".desc.quote"] = "Kiedy masz młotek, wszystko wygląda jak gwóźdź..." +-- L["wep." .. w .. ".credits"] = "Model: FearFisch & sHiBaN \nTextures: Meltdown, sHiBaN & Kitteh" + +w = ws .. "harpoon" +-- L["wep." .. w .. ".name.full"] = "Extrema Ratio Harpoon" +-- L["wep." .. w .. ".name"] = "Harpoon" +L["wep." .. w .. ".desc"] = "Cienki nóż z ząbkami na tylnej krawędzi dla głębszej penetracji." +-- L["wep." .. w .. ".credits"] = "Model: Warfrog \nTextures: kannoe" + +w = ws .. "heathawk" +-- L["wep." .. w .. ".name"] = "Heat Hawk" +L["wep." .. w .. ".desc"] = "Broń MS w kształcie topora przeskalowana do ludzkiego rozmiaru. \nOryginał wykorzystuje nadgrzewane ostrze, które przecina metal jak masło, ale ta replika ma tylko świecącą naklejkę przymocowaną do metalowego ostrza." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Amouro" + +w = ws .. "incorp" +L["wep." .. w .. ".name"] = "Nóż Viper" +L["wep." .. w .. ".desc"] = "Efektowny nóż składany z premium wykończeniem ze stali nierdzewnej i rękojeścią z drewna." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Teh Snake" + +w = ws .. "kitchen" +L["wep." .. w .. ".name"] = "Nóż Kuchenny" +L["wep." .. w .. ".desc"] = "Zwykły, prosty nóż kuchenny. Łatwy do zdobycia i odpowiednio śmiertelny, noże takie jak te są popularne wśród gangów ulicznych i psychopatów. Posiada wbudowane Kradzież Życia." +L["wep." .. w .. ".desc.quote"] = "\"Hej, kolego, masz licencję na ten nóż?\"" +-- L["wep." .. w .. ".credits"] = "Model: Kitteh \nTextures: Rochenback" + +w = ws .. "knife3" +L["wep." .. w .. ".name"] = "Nóż Task Force" +L["wep." .. w .. ".desc"] = "Duży nóż z ząbkowanym ostrzem i zakrzywioną rękojeścią, trochę przesadzony dla tego, co ma być nożem wojskowym." +L["wep." .. w .. ".desc.quote"] = "\"MAMO, WEŹ APARAT!!\"" +-- L["wep." .. w .. ".credits"] = "Model: Syncing \nTextures: Boba Fett" + +w = ws .. "kukri" +-- L["wep." .. w .. ".name"] = "Kukri" +L["wep." .. w .. ".desc"] = "Krótki nóż z zakrzywionym ostrzem, rzekomo zaprojektowany tak, aby zaczepiał o cele i zadawał maksymalne obrażenia." +L["wep." .. w .. ".desc.quote"] = "\"Profesjonaliści mają standardy.\"" +-- L["wep." .. w .. ".credits"] = "Model: Loyen \nTextures: Pain_Agent" + +w = ws .. "machete" +-- L["wep." .. w .. ".name"] = "Machete" +L["wep." .. w .. ".desc"] = "Wielofunkcyjny nóż maczety, chociaż większość zastosowań obejmuje kogoś kłującego." +-- L["wep." .. w .. ".credits"] = "Assets: BrainBread 2" + +w = ws .. "pan" +L["wep." .. w .. ".name"] = "Patelnia" +L["wep." .. w .. ".desc"] = "Naczynie do gotowania na kuchence wykonane z solidnego żeliwa. Stało się ikoniczną bronią dzięki slapstickowi i kreskówkom." +-- L["wep." .. w .. ".credits"] = "Assets: Left 4 Dead 2" + +w = ws .. "pipe" +L["wep." .. w .. ".name"] = "Rura Ołowiana" +L["wep." .. w .. ".desc"] = "Solidna ołowiana rura wodociągowa. Mimo że nie została zaprojektowana do zadawania jakiejkolwiek szkody, uderzenie nią powoduje dość dużo szkód." +-- L["wep." .. w .. ".desc.quote"] = "\"OH SHIT!! Too late!!\"" +-- L["wep." .. w .. ".credits"] = "Assets: No More Room in Hell" + +w = ws .. "rambo" +-- L["wep." .. w .. ".name"] = "Bowie Knife" +-- L["wep." .. w .. ".desc"] = "A classic knife design made specifically for dueling, back when knife fights were a form of entertainment. \nRemains popular due to its association with overly-masculine 80's action movie stars." +-- L["wep." .. w .. ".credits"] = "Model: fallschirmjager \nTextures: HellSpike & EinHain" + +w = ws .. "shovel" +L["wep." .. w .. ".name"] = "Łopata" +L["wep." .. w .. ".desc"] = "Stara łopata wojskowa, zaprojektowana do szybkiego kopania okopów. Świetnie sprawdza się jako prymitywna broń biała, mająca zarówno tępe oblicze, jak i ostry brzeg." +L["wep." .. w .. ".desc.quote"] = "\"Robale!\"" +-- L["wep." .. w .. ".credits"] = "Assets: Day of Defeat: Source" + +w = ws .. "tonfa" +L["wep." .. w .. ".name"] = "Pałka policyjna" +L["wep." .. w .. ".desc"] = "Specjalistyczna pałka policyjna z ukośnym uchwytem do alternatywnego chwytu. Przydatna do tłumienia zamieszek w upadających \"demokratycznych\" krajach." +-- L["wep." .. w .. ".credits"] = "Assets: Left 4 Dead 2" + +w = ws .. "tracker" +L["wep." .. w .. ".name"] = "Nóż Tracker" +L["wep." .. w .. ".desc"] = "Duży nóż do flaków zaprojektowany do wielu zastosowań łowieckich, jednym z nich jest, wygodnie, rozcinanie ludzkich celów." +-- L["wep." .. w .. ".credits"] = "Model: Cartman \nTextures: Henron & Fxdarkloki" + +w = ws .. "wiimote" +-- L["wep." .. w .. ".name.full"] = "Nintendo Wii Remote" +-- L["wep." .. w .. ".name"] = "Wii Remote" +-- L["wep." .. w .. ".desc"] = "An iconic game controller with revolutionary motion controls. \nCareful! Neglecting to wear the wrist strap could lead to injury." +-- L["wep." .. w .. ".desc.quote"] = "You're Out!" -- Wii Sports +-- L["wep." .. w .. ".credits"] = "Model & Textures: Mr. Pickle" + +w = ws .. "wrench" +L["wep." .. w .. ".name"] = "Klucz do rur" +L["wep." .. w .. ".desc"] = "Solidny klucz do dokręcania rur wodnych i gazowych. Konstrukcja z całkowicie żelazna sprawia, że jest to dość tępa broń." +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2" + +///////////////////// -- [[ Iron Curtain ]] -- +-- Weapons +ws = "tacrp_ak_" +w = ws .. "aek971" +-- L["wep." .. w .. ".name"] = "AEK-971" +L["wep." .. w .. ".desc"] = "Eksperymentalny karabin szturmowy używający unikalnego mechanizmu tłumiącego odczuwalny odrzut. Wysoka szybkostrzelność, ale przeciętny zasięg." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kovrovskiy Mekhanicheskiy Zavod" +-- L["wep." .. w .. ".credits"] = "Assets: Casper, arby26 \nAnimations: Tactical Intervention" + +w = ws .. "ak12" +L["wep." .. w .. ".name.full"] = "AK-12 Prototyp" +-- L["wep." .. w .. ".name"] = "AK-12" +L["wep." .. w .. ".desc"] = "Jedna z wielu prób modernizacji AK, ten eksperymentalny model korzysta z ognia seriami i umożliwia szybkie przełączanie kalibru broni." +L["wep." .. w .. ".desc.quote"] = "Oko Śnieżnego Wilka się otwiera." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "ak74" +-- L["wep." .. w .. ".name"] = "AK-74" +L["wep." .. w .. ".desc"] = "Wszechstronny klasyk z Europy Wschodniej o kontrolowanym odrzucie i dobrym zasięgu. Modyfikacje obejmują lekkie meble i mocowania do optyki na krawędziach." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, TheLama \nTextures: Millenia, The Spork \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "ak74u" +-- L["wep." .. w .. ".name"] = "AKS-74U" +L["wep." .. w .. ".desc"] = "Karabinek o rozmiarze pistoletu maszynowego zaprojektowany dla załóg czołgów i sił specjalnych. Imponująca siła ognia w małym opakowaniu, ale odrzut nie jest delikatny." +-- L["wep." .. w .. ".desc.quote"] = "\"Mother Russia can rot, for all I care.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model: TheLama \nCompile: Bushmasta101 \nTextures: Thanez \nSounds: BlitzBoaR/CC5/modderfreak, .exe \nAnimations: Tactical Intervention" + +w = ws .. "an94" +-- L["wep." .. w .. ".name.full"] = "AN-94 \"Abakan\"" +-- L["wep." .. w .. ".name"] = "AN-94" +L["wep." .. w .. ".desc"] = "Eksperymentalny karabin szturmowy z unikalnym \"hyperburst\" na 2 rundy. Skomplikowany mechanizm karabinu zapewnia niski odrzut, ale jest bardzo masywny." +L["wep." .. w .. ".desc.quote"] = "\"Antje\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "galil_lmg" +-- L["wep." .. w .. ".name.full"] = "IMI Galil ARM" +-- L["wep." .. w .. ".name"] = "Galil ARM" +L["wep." .. w .. ".desc"] = "Pochodna AK w konfiguracji karabinu maszynowego. \nLekka i strzela w szybkim i kontrolowanym tempie, ale ma przeciętną siłę rażenia." +L["wep." .. w .. ".desc.quote"] = "\"Wiesz, dla mnie, liczy się akcja.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Galil Assets: Counter Strike: Online 2 \nAccessories: Insurgency (2014), ported by Lt. Rocky \nSuppressed Sound: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "galil_sniper" +-- L["wep." .. w .. ".name.full"] = "IMI Galil Sniper" +-- L["wep." .. w .. ".name"] = "Galatz" +L["wep." .. w .. ".desc"] = "Izraelski AK w konfiguracji karabinu snajperskiego. Bardzo łatwy do kontrolowania, ale ma niską szybkostrzelność i przeciętną śmiertelność." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Galil Assets: Counter Strike: Online 2 \nAccessories: Insurgency (2014), ported by Lt. Rocky \nSuppressed Sound: Magmacow \nAnimations: Tactical Intervention" + +w = ws .. "rk95" +-- L["wep." .. w .. ".name.full"] = "Sako RK 95" +-- L["wep." .. w .. ".name"] = "RK 95" +L["wep." .. w .. ".desc"] = "Fińska pochodna AK z wysoką penetracją pancerza i przedłużonym magazynkiem." +L["wep." .. w .. ".desc.quote"] = "Pomimo wyglądu... Właściwie, ten jest prawie jak AK." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "svd" +-- L["wep." .. w .. ".name.full"] = "Dragunov SVD" +-- L["wep." .. w .. ".name"] = "SVD" +L["wep." .. w .. ".desc"] = "Rosyjski karabin snajperski o niskiej szybkostrzelności, ale dużej sile rażenia i kontroli odrzutu. Wyposażony domyślnie w celownik 6x. \nMimo zewnętrznego podobieństwa do konstrukcji AK, jest całkowicie niepowiązany mechanicznie." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Model: Rafael De Jongh, Ettubrutesbro \nTextures: WangChung \nSounds: Ghost597879, King Friday, iFlip \nAnimations: Tactical Intervention" + +-- Attachments (ak) +-- L["att.acc_ak74_poly.name.full"] = "AK-74 Lightweight Furniture" +-- L["att.acc_ak74_poly.name"] = "Lightweight" +-- L["att.acc_ak74_poly.desc"] = "Paratrooper configuration for increased handling and mobility." + +-- L["att.ammo_ak12_762.name.full"] = "AK-12 7.62×39mm Mod Kit" +-- L["att.ammo_ak12_762.name"] = "7.62×39mm" +-- L["att.ammo_ak12_762.desc"] = "Load a more powerful cartridge, increasing both damage and recoil." + +-- L["att.muzz_ak_booster.name.full"] = "6P26 Muzzle Booster" +-- L["att.muzz_ak_booster.name"] = "Booster" +-- L["att.muzz_ak_booster.desc"] = "AK pattern muzzle device that increases rate of fire." + +-- L["att.muzz_ak_comp.name.full"] = "6P20 Compensator" +-- L["att.muzz_ak_comp.name"] = "Compensator" +-- L["att.muzz_ak_comp.desc"] = "AK pattern muzzle device that straightens recoil." + +-- L["att.muzz_supp_pbs.name.full"] = "PBS-5 Suppressor" +-- L["att.muzz_supp_pbs.name"] = "PBS-5" +-- L["att.muzz_supp_pbs.desc"] = "AK pattern suppressor improving recoil stability at cost of accuracy." + +-- L["att.optic_ak_kobra.name.full"] = "Kobra Sight" +-- L["att.optic_ak_kobra.name"] = "Kobra" +-- L["att.optic_ak_kobra.desc"] = "Russian dovetail reflex sight." + +-- L["att.optic_galil.name.full"] = "Nimrod 6x40 Scope" +-- L["att.optic_galil.name"] = "Nimrod 6x40" +-- L["att.optic_galil.desc"] = "Sniper optic designed to be mounted onto Galil rifles." + +///////////////////// -- [[ Heavy Duty ]] -- +-- Weapons +ws = "tacrp_h_" +w = ws .. "dual_hardballers" +-- L["wep." .. w .. ".name"] = "Dual Silverballers" +L["wep." .. w .. ".desc"] = "Para eleganckich pistoletów longslide, wysoki profil dla niskoprofilowych zabójców. Dobry zasięg jak na akimbo - o ile trafisz." +L["wep." .. w .. ".desc.quote"] = "\"Będę szukać sprawiedliwości dla siebie. Wybiorę prawdę, którą lubię.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool." +-- L["wep." .. w .. ".credits"] = "Assets: Terminator: Resistance (port by Sirgibsalot) \nSounds: Navaro \nAnimations: Tactical Intervention, Fesiug" + +w = ws .. "executioner" +-- L["wep." .. w .. ".name.full"] = "Taurus Raging Judge" +-- L["wep." .. w .. ".name"] = "Executioner" +L["wep." .. w .. ".desc"] = "Ogromny rewolwer strzelający małokalibrowymi nabojami śrutowymi. Strzela wieloma śrutami, ale rozrzut jest słaby." +L["wep." .. w .. ".desc.quote"] = "\"Chodźcie, przyjaciele. Jeszcze nie jest za późno, by szukać nowego świata.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Taurus" +-- L["wep." .. w .. ".credits"] = "Main Model: Firearms: Source, edited by speedonerd \nShells & speedloader: Call of Duty: Black Ops II \nSounds: Call of Duty: Black Ops II \nAnimations: Tactical Intervention" + +w = ws .. "hardballer" +-- L["wep." .. w .. ".name"] = "AMT Hardballer" +L["wep." .. w .. ".desc"] = "Pistolet z długą lufą i stalową konstrukcją. Dokładny i mocny na dystansie." +L["wep." .. w .. ".desc.quote"] = "\"Wrócę...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool." +-- L["wep." .. w .. ".credits"] = "Assets: Terminator: Resistance (port by Sirgibsalot) \nSounds: Navaro \nAnimations: Tactical Intervention" + +w = ws .. "hk23e" +-- L["wep." .. w .. ".name.full"] = "HK HK21E" +-- L["wep." .. w .. ".name"] = "HK21E" +-- L["wep." .. w .. ".desc"] = "Belt-fed machine gun variant of a classic battle rifle. Accurate and seriously powerful, but hard to use while on the move." +-- L["wep." .. w .. ".desc.quote"] = "\"Now I can solve up to 800 problems a minute!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: NZ-Reason \nAnimations: Tactical Intervention, edited by Fesiug \nSounds: Treyarch, rzen1th" + +w = ws .. "intervention" +-- L["wep." .. w .. ".name.full"] = "CheyTac Intervention" +-- L["wep." .. w .. ".name"] = "Intervention" +L["wep." .. w .. ".desc"] = "Wyglądający jak z przyszłości hybrydowy karabin snajperski-AMR z doskonałą penetracją pancerza i wysoką prędkością wylotową. \nWyposażony domyślnie w celownik 12x." +L["wep." .. w .. ".desc.quote"] = "1v1 na rust m8 założę się, że nawet nie potrafisz szybko celować, ty zjebie!" +-- L["wep." .. w .. ".trivia.manufacturer"] = "CheyTac USA" +-- L["wep." .. w .. ".credits"] = "Model: Syncing \nTextures: Frizz925 \nSounds: Seven-Zero & Ghost \nAnimations: Tactical Intervention" + +w = ws .. "jackhammer" +-- L["wep." .. w .. ".name.full"] = "Pancor Jackhammer" +-- L["wep." .. w .. ".name"] = "Jackhammer" +L["wep." .. w .. ".desc"] = "Jedyny w swoim rodzaju, ciężki automatyczny shotgun z cylindrycznym magazynkiem. Dość ciężki i trochę niepewny." +L["wep." .. w .. ".desc.quote"] = "\"Muuu, mówię!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pancor Corporation" +-- L["wep." .. w .. ".credits"] = "Model: Soldier11, edited by speedonerd (Front sight) \nTextures: Millenia \nSounds: Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "jdj" +-- L["wep." .. w .. ".name.full"] = "SSK .950 JDJ" +-- L["wep." .. w .. ".name"] = ".950 JDJ" +L["wep." .. w .. ".desc"] = "Niewyobrażalnie duży \"karabin myśliwski\" strzelający absurdalnie potężnym pociskiem. Nie wiem, jak w ogóle jesteś w stanie strzelać z tego z ramienia. \nDomyślnie wyposażony w lunetę 10x." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SSK Industries" +-- L["wep." .. w .. ".credits"] = "Model: RedRogueXIII \nTextures: Rafael De Jongh \nSounds: KillerExe_01 \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "nitrorifle" +L["wep." .. w .. ".name.full"] = "Karabin HH Double" +L["wep." .. w .. ".name"] = "Karabin Double" +L["wep." .. w .. ".desc"] = "Butikowy podwójny karabin do polowania na duże zwierzęta, strzelający pociskiem zaprojektowanym do powalania słoni. Pomimo swojego wyglądu, nie jest to strzelba." +L["wep." .. w .. ".desc.quote"] = "\"Życie to wielka przygoda, zaakceptuj to w takim duchu.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Holland & Holland" +-- L["wep." .. w .. ".credits"] = "Assets: Far Cry 4 (Originally ported by Robotnik) \nAdditional Sounds: speedonerd \nAnimations: 8Z, speedonerd" + +w = ws .. "smaw" +-- L["wep." .. w .. ".name.full"] = "Mk 153 SMAW" +-- L["wep." .. w .. ".name"] = "SMAW" +L["wep." .. w .. ".desc"] = "Przenośny miotacz bunkrów z powolnymi, potężnymi rakietami. Laserowy wskaźnik umożliwia sterowanie rakietą po włączeniu. Może montować optykę i ma wbudowany Corner-Cam." +-- L["wep." .. w .. ".desc.quote"] = "\"Here's what I think of your best laid plans...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Nammo Talley" +-- L["wep." .. w .. ".credits"] = "Assets: Call of Duty: Modern Warfare 3 (2011) \nAnimations: Tactical Intervention & speedonerd" + +w = ws .. "spas12" +-- L["wep." .. w .. ".name.full"] = "Franchi SPAS-12" +-- L["wep." .. w .. ".name"] = "SPAS-12" +L["wep." .. w .. ".desc"] = "Imponujący strzelba bojowa z dwoma trybami działania. Wysoka moc i stabilny odrzut. Składany kolba blokuje użycie optyki." +L["wep." .. w .. ".desc.quote"] = "\"Nieprawda.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Luigi Franchi S.p.A." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Stoke \nSounds: iFlip & speedonerd \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "xm25" +-- L["wep." .. w .. ".name.full"] = "HK XM25 CDTE" +-- L["wep." .. w .. ".name"] = "XM25" +L["wep." .. w .. ".desc"] = "Granatnik bullpup z zintegrowanym dalmierzem, dobry do tłumienia na średnim dystansie. Wysoka szybkostrzelność, ale granaty są dość wolne i słabe." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Call of Duty: Modern Warfare 3 (2011) \nAnimations: Tactical Intervention, edited by speedonerd" + +-- Attachments (heavy_pack_atts) +-- L["att.trigger_spas_semi.name.full"] = "Franchi SPAS-12 Semi-Auto Action" +-- L["att.trigger_spas_semi.name"] = "Semi" +-- L["att.trigger_spas_semi.desc"] = "Switch to semi-auto operation, sacrficing stopping power for fire rate." + +-- L["att.trigger_spas_freeman.name.full"] = "Half-Life 2 Double Shot" +-- L["att.trigger_spas_freeman.name"] = "Freeman" +-- L["att.trigger_spas_freeman.desc"] = "Alternate mehcanism that can fire two shots at once, somehow..." + +-- L["att.sound_m200_mlg.name.full"] = "MLG High-Mobility Gamer Rounds" +-- L["att.sound_m200_mlg.name"] = "MLG" +-- L["att.sound_m200_mlg.desc"] = "yep, this one's going in the montage." + +-- L["att.hardballer_laser.name.full"] = "Hardballer Surefire Laser Scope" +-- L["att.hardballer_laser.name"] = "Laser Scope" +-- L["att.hardballer_laser.desc"] = "Primitive bulky laser module that makes aiming almost unnecessary." + +-- L["att.ammo_smaw_tri.name.full"] = "SMAW Tri-Attack Rocket Pod" +-- L["att.ammo_smaw_tri.name"] = "Tri-Attack" +-- L["att.ammo_smaw_tri.desc"] = "A trio of fast and maneuverable anti-infantry missiles." + +-- L["att.ammo_smaw_nikita.name.full"] = "SMAW Nikita Rocket Pod" +-- L["att.ammo_smaw_nikita.name"] = "Nikita" +-- L["att.ammo_smaw_nikita.desc"] = "A very slow manually controllable rocket." +-- L["att.procon.nikita"] = "Manual Control (while aiming and Laser ON)" + +-- L["att.ammo_smaw_tandem.name.full"] = "SMAW Tandem Rocket Pod" +-- L["att.ammo_smaw_tandem.name"] = "Tandem" +-- L["att.ammo_smaw_tandem.desc"] = "A powerful anti-tank rocket that takes time to accelerate." + +-- L["att.ammo_smaw_agile.name.full"] = "SMAW Hummingbird Mini-Rocket Pod" +-- L["att.ammo_smaw_agile.name"] = "Hummingbird" +-- L["att.ammo_smaw_agile.desc"] = "Aerodynamic mini-rockets that accelerate as they turn." + +-- L["att.ammo_25mm_stun.name.full"] = "25mm Stunstorm Grenades" +-- L["att.ammo_25mm_stun.name"] = "Stunstorm" +-- L["att.ammo_25mm_stun.desc"] = "Grenades that briefly incapacitate the target." + +-- L["att.ammo_25mm_airburst.name.full"] = "25mm Airburst Grenades" +-- L["att.ammo_25mm_airburst.name"] = "Airburst" +-- L["att.ammo_25mm_airburst.desc"] = "Fragmentation grenades exploding mid-air. Large radius but less lethal." + +-- L["att.ammo_25mm_buckshot.name.full"] = "25mm Flechette Grenades" +-- L["att.ammo_25mm_buckshot.name"] = "Flechette" +-- L["att.ammo_25mm_buckshot.desc"] = "Flat-top grenade packing accurate flechette darts." + +-- L["att.ammo_25mm_heat.name.full"] = "25mm High-Explosive Anti-Tank Grenades" +-- L["att.ammo_25mm_heat.name"] = "HEAT" +-- L["att.ammo_25mm_heat.desc"] = "Grenades designed to penetrate armor and deal direct damage." + +-- L["att.pro.trigger_spas_freeman1"] = "Twice the fun" +-- L["att.procon.nikita"] = "Manual Control (while aiming and Laser ON)" + +///////////////////// -- [[ ExoOps ]] -- +-- Weapons +ws = "tacrp_eo_" +w = ws .. "93r" +-- L["wep." .. w .. ".name.full"] = "Beretta 93 Raffica" +-- L["wep." .. w .. ".name"] = "93R" +L["wep." .. w .. ".desc"] = "Premiumowy pistolet strzelający serią z niewielkim odrzutem i świetnym strzelaniem z biodra." +L["wep." .. w .. ".desc.quote"] = "\"Wiara, pociąg! Weź pociąg!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nOriginally made for CS 1.6 by GR_Lucia \nSounds: Vunsunta, Infinity Ward & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "acr" +-- L["wep." .. w .. ".name.full"] = "Bushmaster ACR" +-- L["wep." .. w .. ".name"] = "ACR" +L["wep." .. w .. ".desc"] = "Karabin cywilny oferowany jako zaawansowana alternatywa dla innych popularnych platform. Modułowy odbiornik pozwala na użycie różnych kalibrów." +L["wep." .. w .. ".desc.quote"] = "\"Zabicie smoka to największy zaszczyt.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Bushmaster Firearms LLC" +-- L["wep." .. w .. ".credits"] = "Model: End of Days \nTextures: IppE \nSounds: Strelok & XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "ar70" +-- L["wep." .. w .. ".name.full"] = "Beretta SC-70/.223" +-- L["wep." .. w .. ".name"] = "SC-70" +L["wep." .. w .. ".desc"] = "Masywny karabin szturmowy z kontrolowalną szybkostrzelnością." +-- L["wep." .. w .. ".desc.quote"] = "\"Is life always this hard, or is it just when you're a kid?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Model & Textures: DaveW \nSounds: Vunsunta, xLongWayHome & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "auga1" +-- L["wep." .. w .. ".name.full"] = "Steyr AUG A1" +-- L["wep." .. w .. ".name"] = "AUG A1" +L["wep." .. w .. ".desc"] = "Klasyczna wersja AUG z większym magazynkiem i trybem automatycznym. \nDostarczana z domyślnie 1.5-krotnym celownikiem." +-- L["wep." .. w .. ".desc.quote"] = "\"I want blood!\" \"And you'll have it.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Arms" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Silentassassin12 \nSound & Animations: Tactical Intervention" + +w = ws .. "browninghp" +-- L["wep." .. w .. ".name.full"] = "Browning Hi-Power" +-- L["wep." .. w .. ".name"] = "Browning HP" +L["wep." .. w .. ".desc"] = "Starożytny pistolet o doskonałej ogólnej wydajności, ale niskiej szybkostrzelności." +L["wep." .. w .. ".desc.quote"] = "\"Co do cholery...?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Model: Silvio Dante \nTextures: WangChung \nSounds: Vunsunta, Strelok \nAnimations: Tactical Intervention" + +w = ws .. "calico" +-- L["wep." .. w .. ".name.full"] = "Calico M950A" +-- L["wep." .. w .. ".name"] = "Calico" +L["wep." .. w .. ".desc"] = "Dziwnie wyglądający pistolet kosmiczny z ogromnym magazynkiem helikalnym. Przekształcony na pełne automatyczne." +-- L["wep." .. w .. ".desc.quote"] = "\"I've got more hostages than you've had hot dinners.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Calico Light Weapons Systems" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nRail and foregrip from Warface \nSounds: A.V.A., Warface, speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "dual_satana" +-- L["wep." .. w .. ".name"] = "Dueling Demons" +L["wep." .. w .. ".desc"] = "Para spersonalizowanych rewolwerów. Świetna celność dla akimbo, ale wolno strzela i przeładowuje." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Soul_Slayer \nTextures: Kimono \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "f2000" +-- L["wep." .. w .. ".name.full"] = "FN F2000" +-- L["wep." .. w .. ".name"] = "F2000" +L["wep." .. w .. ".desc"] = "Futurystyczny karabin szturmowy bullpup o doskonałej ergonomii. \nDomyślnie wyposażony w lunetę 1,6x." +L["wep." .. w .. ".desc.quote"] = "\"Czy właśnie powiedziałeś, że muszę wygrać jeden dla Gippera?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: DICE (Battlefield 3) \nScope model from CSO2 \nSounds: CSO2 & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "fal" +-- L["wep." .. w .. ".name.full"] = "FN FAL" +-- L["wep." .. w .. ".name"] = "FAL" +L["wep." .. w .. ".desc"] = "Weteran karabin bojowy o dużej sile ognia, ale z dużym odrzutem." +L["wep." .. w .. ".desc.quote"] = "Prawa ręka wolnego świata." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Model: Pete3D \nTextures: Millenia \nSounds: New World Interactive & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "g36c" +-- L["wep." .. w .. ".name.full"] = "HK G36C" +-- L["wep." .. w .. ".name"] = "G36C" +L["wep." .. w .. ".desc"] = "Kompaktowa wersja karabinu G36, która wymienia skuteczny zasięg na zwiększoną szybkostrzelność i poprawioną obsługę." +L["wep." .. w .. ".desc.quote"] = "\"Jasne. Co to za imię, Soap?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: TheLama \nTextures: Thanez, FxDarkLoki \nSound & Animations: Tactical Intervention" + +w = ws .. "hkcaws" +-- L["wep." .. w .. ".name"] = "HK CAWS" +L["wep." .. w .. ".desc"] = "Prototypowy strzelba bullpup o przyzwoitym zasięgu i pełnym ogniu automatycznym. \nWyposażony w stały celownik 1,5x." +L["wep." .. w .. ".desc.quote"] = "\"Koniec jazdy, mutancie.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Millenia \nPorted from Fallout: New Vegas by speedonerd \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "howa" +-- L["wep." .. w .. ".name.full"] = "Howa Type 64" +-- L["wep." .. w .. ".name"] = "Type 64" +L["wep." .. w .. ".desc"] = "Japoński karabin bojowy o kontrolowanej szybkostrzelności." +-- L["wep." .. w .. ".desc.quote"] = "\"Priest-21, this is Trevor. Clear to fire. Kill Wyvern.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Howa Machinery" +-- L["wep." .. w .. ".credits"] = "Assets: Michau, ported from Fallout: New Vegas by 8sianDude \nSounds: speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "hushpup" +-- L["wep." .. w .. ".name.full"] = "SW Mk 22 Mod 0" +-- L["wep." .. w .. ".name"] = "Mk 22" +L["wep." .. w .. ".desc"] = "Wintage pistolet sił specjalnych zaprojektowany do operacji tajnych." +L["wep." .. w .. ".desc.quote"] = "\"Rozpoczynanie operacji Snake Eater.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Stoke \nTextures: Dayofdfeat12, edited by speedonerd \nSounds: oneshotofficial & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "izzyfal" +-- L["wep." .. w .. ".name.full"] = "DSA SA58 Light-Barrel" +-- L["wep." .. w .. ".name"] = "FAL Izzy" +L["wep." .. w .. ".desc"] = "Cywilna wersja ikonicznego karabinu bojowego. Trochę ciężka, ale ma doskonałą moc zatrzymującą i zasięg." +-- L["wep." .. w .. ".trivia.manufacturer"] = "DS Arms" +-- L["wep." .. w .. ".credits"] = "Model: Pete3D \nTextures: Enron \nSounds: New World Interactive & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "jericho" +-- L["wep." .. w .. ".name.full"] = "Jericho 941" +-- L["wep." .. w .. ".name"] = "Jericho" +L["wep." .. w .. ".desc"] = "Solidny pistolet 9mm o dużej mobilności i wysokiej szybkostrzelności. \nReklamowany jako \"Baby Eagle\" ze względu na powierzchowne podobieństwo do Desert Eagle." +L["wep." .. w .. ".desc.quote"] = "\"Do zobaczenia, kosmiczny kowboju...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Model: philibuster \nTextures: oyman \nSounds: xLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "l85" +-- L["wep." .. w .. ".name.full"] = "Enfield L85A2" +-- L["wep." .. w .. ".name"] = "L85A2" +L["wep." .. w .. ".desc"] = "Brytyjski karabin bullpup o średniej wydajności i wątpliwej niezawodności. Nie zdziw się, jeśli czasami się zacina. \nDomyślnie wyposażony w lunetę 3x." +-- L["wep." .. w .. ".desc.quote"] = "\"SA80. Good bit of British kit...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Royal Ordnance" +-- L["wep." .. w .. ".credits"] = "Model & Texture: Milo, edited by speedonerd \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "m3" +-- L["wep." .. w .. ".name"] = "Benelli M3" +L["wep." .. w .. ".desc"] = "Półautomatyczna strzelba z dobrą kontrolą odrzutu i celnością." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Benelli Armi S.p.A." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: Cas., Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "m29" +-- L["wep." .. w .. ".name.full"] = "SW Model 29 \"Satan\"" +-- L["wep." .. w .. ".name"] = "M29" +L["wep." .. w .. ".desc"] = "Dostosowany rewolwer magnum z ciężkim spustem, ale doskonałą kontrolą odrzutu." +L["wep." .. w .. ".desc.quote"] = "\"Czujesz się szczęściarzem, łobuzie?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Soul_Slayer \nTextures: Kimono \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "m60" +-- L["wep." .. w .. ".name"] = "M60" +L["wep." .. w .. ".desc"] = "Ciężki karabin maszynowy o dużej sile rażenia, ale niskiej szybkostrzelności. Nazywany \"Świnią\" ze względu na swoją wagę." +L["wep." .. w .. ".desc.quote"] = "\"Żyj za nic, albo umrzyj za coś. Twój wybór.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "General Dynamics" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Millenia \nSounds: xLongWayHome, Lain & rzen1th \nAnimations: Tactical Intervention" + +w = ws .. "m712" +-- L["wep." .. w .. ".name.full"] = "M712 Schnellfeuer" +-- L["wep." .. w .. ".name"] = "M712" +L["wep." .. w .. ".desc"] = "Zabytkowy pistolet maszynowy o dużej celności, ale słabej kontroli odrzutu. Skuteczny w krótkich seriach." +L["wep." .. w .. ".desc.quote"] = "Zamek może uderzyć cię w twarz." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Mauser" +-- L["wep." .. w .. ".credits"] = "Assets: Battlefield: Korea \nOriginally ported to CS 1.6 by GR_Lucia \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "m733" +-- L["wep." .. w .. ".name.full"] = "Colt Model 733" +-- L["wep." .. w .. ".name"] = "M733" +L["wep." .. w .. ".desc"] = "Karabinek o długości zbliżonej do subkarabinka AR-15, z szybką szybkostrzelnością. Stała rękojeść nośna ogranicza opcje optyki." +L["wep." .. w .. ".desc.quote"] = "\"Robię to, co potrafię najlepiej; zbieram punkty.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta & The End \nTextures: Acid Snake & JamesM \nSounds: Vunsunta & Teh Strelok \nAnimations: Tactical Intervention" + +w = ws .. "masada" +-- L["wep." .. w .. ".name.full"] = "Magpul Masada" +-- L["wep." .. w .. ".name"] = "Masada" +L["wep." .. w .. ".desc"] = "Zaawansowany karabin z lekką konstrukcją z polimeru." +L["wep." .. w .. ".desc.quote"] = "\"Mógłbyś oczyścić świat z żelaza, a ja sprzedawałbym drewniane pałki.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magpul Indutries" +-- L["wep." .. w .. ".credits"] = "Model: End of Days \nTextures: IppE \nSounds: Strelok & XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "mcx" +-- L["wep." .. w .. ".name.full"] = "SIG MCX SPEAR" +-- L["wep." .. w .. ".name"] = "MCX SPEAR" +L["wep." .. w .. ".desc"] = "Karabin bojowy zaprojektowany do różnych ról piechoty. Specjalna amunicja ma dużą penetrację pancerza i utrzymuje obrażenia na dystansie." +L["wep." .. w .. ".desc.quote"] = "\"Bravo Six, idziemy w ciemność.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer Inc." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Akinaro & Farengar \nSounds: Infinity Ward, speedonerd, XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "megastar" +-- L["wep." .. w .. ".name.full"] = "Star Megastar" +-- L["wep." .. w .. ".name"] = "Megastar" +L["wep." .. w .. ".desc"] = "Duży pistolet o solidnej konstrukcji. Duża pojemność i siła ognia, ale ma intensywny odrzut." +L["wep." .. w .. ".desc.quote"] = "\"Chcesz się ze mną rozprawić?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Star Bonifacio Echeverria" +-- L["wep." .. w .. ".credits"] = "Model: General Tso \nTextures: the_tub \nSounds: oneshotofficial & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "mg42" +-- L["wep." .. w .. ".name"] = "MG 42" +L["wep." .. w .. ".desc"] = "Antyczny karabin maszynowy o niesamowicie szybkiej szybkostrzelności. Może przeciąć ludzi na pół jak piła łańcuchowa. \nNie zaleca się strzelania z ramienia." +-- L["wep." .. w .. ".desc.quote"] = "Every beast has its own story." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Großfuß AG" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Red Orchestra 2 \nSounds: Red Orechesta 2, rzen1th \nAnimations: Tactical Intervention" + +w = ws .. "mp5k" +-- L["wep." .. w .. ".name.full"] = "HK MP5K" +-- L["wep." .. w .. ".name"] = "MP5K" +L["wep." .. w .. ".desc"] = "Kompaktowa wersja ikonicznego SMG. Dobrze zrównoważony, ale zamiast precyzji i kontroli swojego pełnowymiarowego odpowiednika oferuje lepszą obsługę." +L["wep." .. w .. ".desc.quote"] = "\"Broń. Dużo broni.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Thanez \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "mp5sd" +-- L["wep." .. w .. ".name.full"] = "HK MP5SD6" +-- L["wep." .. w .. ".name"] = "MP5SD6" +L["wep." .. w .. ".desc"] = "Wariant legendarnego SMG z wbudowanym tłumikiem. Zmniejszony zasięg, ale niski odrzut i brak widocznych smug." +L["wep." .. w .. ".desc.quote"] = "\"Broń wolna.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Geno & Mr. Brightside \nSounds: Lakedown, Teh Sterlok \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "mpx" +-- L["wep." .. w .. ".name.full"] = "SIG MPX" +-- L["wep." .. w .. ".name"] = "MPX" +L["wep." .. w .. ".desc"] = "Zaawansowany SMG ze stabilnym odrzutem i przedłużonym magazynkiem." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Assets: Contract Wars \nOriginally ported to CS 1.6 by GR_Lucia \nAnimation: Tactical Intervention" + +w = ws .. "p7" +-- L["wep." .. w .. ".name.full"] = "HK P7" +-- L["wep." .. w .. ".name"] = "P7" +L["wep." .. w .. ".desc"] = "Kompaktowy pistolet o szybkiej obsłudze, ale słabym zasięgu." +L["wep." .. w .. ".desc.quote"] = "\"Kto powiedział, że jesteśmy terrorystami?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Lama \nTextures: The_Tub \nSounds: Optical Snare & One Shot \nAnimations: Tactical Intervention" + +w = ws .. "p99" +-- L["wep." .. w .. ".name.full"] = "Walther P99" +-- L["wep." .. w .. ".name"] = "P99" +L["wep." .. w .. ".desc"] = "Dobrze zbalansowany pistolet z szybkim tempem ognia." +L["wep." .. w .. ".desc.quote"] = "\"Ścigajcie ich i zabijajcie po kolei.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Model: Afterburner \nTextures: NCFurious \nSounds: KingFriday, Vunsunta & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "p210" +-- L["wep." .. w .. ".name.full"] = "SIG P210" +-- L["wep." .. w .. ".name"] = "P210" +L["wep." .. w .. ".desc"] = "Elegancki powojenny pistolet z jednorzędowym magazynkiem i kurkowym mechanizmem spustowym." +-- L["wep." .. w .. ".desc.quote"] = "\"Do you suppose that I come to bring peace to the world?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer" +-- L["wep." .. w .. ".credits"] = "Model: Silvio Dante \nTextures: Twinke Masta \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "psg1" +-- L["wep." .. w .. ".name.full"] = "HK PSG-1" +-- L["wep." .. w .. ".name"] = "PSG-1" +L["wep." .. w .. ".desc"] = "Elegancki półautomatyczny karabin snajperski o niezrównanej precyzji i kontroli odrzutu, ale z niższym niż średnia zasięgiem. \nDomyślnie wyposażony w lunetę 8x." +L["wep." .. w .. ".desc.quote"] = "\"Urodziłem się na polu bitwy. Wychowałem się na polu bitwy.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta & fallschrimjager \nTextures: Twinke Masta \nSounds: Navaro, Vunsunta, FxDarkLoki \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "rhino20ds" +-- L["wep." .. w .. ".name.full"] = "Chiappa Rhino 20DS" +-- L["wep." .. w .. ".name"] = "Rhino 20DS" +L["wep." .. w .. ".desc"] = "Nowoczesny rewolwer z krótką lufą i sześciokątnym bębnem. Mały kaliber i nisko położona lufa sprawiają, że broń jest szybka i łatwa do użycia." +L["wep." .. w .. ".desc.quote"] = "[ PAMIĘTAJ O NASZEJ OBIETNICY ]" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Chiappa Firearms" +-- L["wep." .. w .. ".credits"] = "Model & Textures: MirzaMiftahulFadillah, edited by 8Z \nSounds: Ghost597879, Tatwaffe, The Wastes Mod \nAnimations: Tactical Intervention" + +w = ws .. "scarl" +-- L["wep." .. w .. ".name.full"] = "FN SCAR-L" +-- L["wep." .. w .. ".name"] = "SCAR-L" +L["wep." .. w .. ".desc"] = "Modularny, lekki karabin szturmowy o umiarkowanej szybkostrzelności." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2" + +w = ws .. "scout" +-- L["wep." .. w .. ".name"] = "Steyr Scout" +L["wep." .. w .. ".desc"] = "Lekki karabin zaprojektowany z myślą o przenośności i komforcie, a nie o bezpośredniej sile ognia. Ma wbudowany dwójnóg. \nWyposażony w celownik 6x." +-- L["wep." .. w .. ".desc.quote"] = "\"Headshot!\" \"Humiliation!\" " +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Mannlicher" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Thanez \nSounds: iFlip, Vunsunta, Unbreakable \nAnimations: Tactical Intervention" + +w = ws .. "sg510" +-- L["wep." .. w .. ".name.full"] = "SIG SG 510-1" +-- L["wep." .. w .. ".name"] = "SG 510" +L["wep." .. w .. ".desc"] = "Zabytkowy karabin bojowy o doskonałych osiągach na daleki dystans. Odrzut jest ostry, ale stabilny." +-- L["wep." .. w .. ".desc.quote"] = "\"He had a lot of guts.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Assets: World of Guns: Disassembly \nSounds: Sledgehammer Games \nAnimations: Tactical Intervention" + +w = ws .. "spas15" +-- L["wep." .. w .. ".name.full"] = "Franchi SPAS-15" +-- L["wep." .. w .. ".name"] = "SPAS-15" +L["wep." .. w .. ".desc"] = "Następca legendarnej strzelby z trybem podawania magazynkowego. Ciężka waga pozwala na przyzwoitą kontrolę w trybie półautomatycznym." +L["wep." .. w .. ".desc.quote"] = "\"Poczekamy. Poczekamy i zobaczymy, co odkryje.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Luigi Franchi S.p.A." +-- L["wep." .. w .. ".credits"] = "Assets: filosoma, ported from Fallout: New Vegas by Kindred Flame \nSounds: Navaro, SevenZero & Magmacow \nAnimations: Tactical Intervention & speedonerd" + +w = ws .. "winchester" +-- L["wep." .. w .. ".name.full"] = "Winchester M1873" +-- L["wep." .. w .. ".name"] = "M1873" +L["wep." .. w .. ".desc"] = "Ikoniczny karabin kowbojski o konstrukcji, która przetrwała do dziś. Venerowany klasyk - ale nieodpowiedni na współczesne pola bitew." +L["wep." .. w .. ".desc.quote"] = "\"Broń, która zdobyła Zachód.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Winchester Repeating Arms" +-- L["wep." .. w .. ".credits"] = "Model: Enron \nTextures: !NC!Furious \nSounds: Vunsunta \nAnimations: speedonerd" + +-- Attachments (exoops) +-- L["att.ammo_modular_65gren.name.full"] = "ACR 6.5mm Grendel Mod Kit" +-- L["att.ammo_modular_65gren.name"] = "Grendel" +-- L["att.ammo_modular_65gren.desc"] = "Modification to load ammo with improved ballistics." + +-- L["att.ammo_modular_450bm.name.full"] = "ACR .450 Bushmaster Mod Kit" +-- L["att.ammo_modular_450bm.name"] = "Bushmaster" +-- L["att.ammo_modular_450bm.desc"] = "Modification to load low capacity, high power magnum rounds." + +-- L["att.bolt_spas15_pump.name.full"] = "Franchi SPAS-15 Pump-Action" +-- L["att.bolt_spas15_pump.name"] = "Pump" +-- L["att.bolt_spas15_pump.desc"] = "Switch to pump-action operation, sacrificing fire-rate for improved control." + +-- L["att.trigger_mk22_locked.name.full"] = "Mk 22 Slide Lock" +-- L["att.trigger_mk22_locked.name"] = "Slide Lock" +-- L["att.trigger_mk22_locked.desc"] = "Locks the slide in place when firing to further reduce noise." + +-- L["att.optic_howa_scope.name.full"] = "Howa Type 64 2.2x DMR Scope" +-- L["att.optic_howa_scope.name"] = "Scope" +-- L["att.optic_howa_scope.desc"] = "Proprietary marksman scope for the Type 64." + +-- L["att.optic_g36c_scope.name.full"] = "G36C Integrated Scope" +-- L["att.optic_g36c_scope.name"] = "Int. Scope" +-- L["att.optic_g36c_scope.desc"] = "Low power integrated scope and carry handle for the G36C." + +-- L["att.ammo_scout_376.name.full"] = "Scout .376 Steyr Mod Kit" +-- L["att.ammo_scout_376.name"] = ".376 Steyr" +-- L["att.ammo_scout_376.desc"] = "Modification to load a unique high-power hunting cartridge." + +///////////////////// -- [[ Scavenger's Spoils ]] -- +-- Weapons +ws = "tacrp_pa_" +w = ws .. "auto5" +-- L["wep." .. w .. ".name.full"] = "Browning Auto 5" +-- L["wep." .. w .. ".name"] = "Auto 5" +-- L["wep." .. w .. ".desc"] = "Old school automatic shotgun. Small caliber shells have good range and firerate, but poor stopping power." +-- L["wep." .. w .. ".desc.quote"] = "\"Line 'em up and knock 'em down.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Browning Arms" +-- L["wep." .. w .. ".credits"] = "Model: RedRogueXIII, An Aggressive Napkin \nTextures: Futon \nSounds: Futon \nAnimations: Tactical Intervention" + +w = ws .. "automag3" +-- L["wep." .. w .. ".name.full"] = "AMT AutoMag III" +-- L["wep." .. w .. ".name"] = "AutoMag III" +-- L["wep." .. w .. ".desc"] = "Stainless steel pistol chambered in an obscure cartridge. Not very powerful for a magnum pistol, but has good capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"You were always the best. Nobody ever came close.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Degenerate Dak \nSounds: Navaro \nAnimations: Tactical Intervention" + +w = ws .. "awp" +-- L["wep." .. w .. ".name.full"] = "AI AWM-F" +-- L["wep." .. w .. ".name"] = "AWM" +-- L["wep." .. w .. ".desc"] = "Robust magnum sniper with unmatched power and accuracy. A counter-terrorist favourite.\nEquipped with a 12x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"He's a madman, a scientist, and a sharpshooter.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Accuracy International" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c \nTextures: Bullethead & Kimono \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "coachgun" +-- L["wep." .. w .. ".name.full"] = "Stoeger Coachgun" +-- L["wep." .. w .. ".name"] = "Coachgun" +-- L["wep." .. w .. ".desc"] = "Double barrel shotgun from the Old West.\nCumbersome and inaccurate, but still packs a punch." +-- L["wep." .. w .. ".desc.quote"] = "Lock, stock, and two smoking barrels." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Stoeger" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Fistful of Frags (Macaroane, Paul68Rageous, Tigg) \nSounds: Fistful of Frags, rzen1th \nAnimations: 8Z" + +w = ws .. "cz75" +-- L["wep." .. w .. ".name.full"] = "CZ-75 Automatic" +-- L["wep." .. w .. ".name"] = "CZ-75" +-- L["wep." .. w .. ".desc"] = "Automatic variant of the quintessential \"Wonder Nine\".\nFast firing and controllable, but capacity is low." +-- L["wep." .. w .. ".desc.quote"] = "The pinnacle of semi-automatic handgun evolution." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Arby \nSounds: Hk, Vunsunta, xLongWayHome, Strelok, Cas, IceFluxx \nAnimations: Tactical Intervention" + +w = ws .. "dual_makarov" + +-- L["wep." .. w .. ".name"] = "Dual Makarovs" +-- L["wep." .. w .. ".desc"] = "A pair of Soviet pistols to compensate for the lack of firepower with just one. Minimal recoil, but lethality remains poor past point blank." +-- L["wep." .. w .. ".desc.quote"] = "\"Finish the job, James! Blow them all to hell!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhevsk Mechanical Plant" +-- L["wep." .. w .. ".credits"] = "Assets: TehSnake \nSounds: Hk, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "fnmag" +-- L["wep." .. w .. ".name"] = "FN MAG" +L["wep." .. w .. ".desc"] = "Niezwykle ciężki \"lekki\" karabin maszynowy, który łączy cechy LMG i broni stacjonarnej. Posiada niemal niekończący się magazynek." +L["wep." .. w .. ".desc.quote"] = "\"Nie możesz mnie tu zostawić z tymi... zwierzętami.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Fabrique National" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Call to Arms \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "fort12" +-- L["wep." .. w .. ".name"] = "Fort-12" +L["wep." .. w .. ".desc"] = "Ekonomiczny ukraiński pistolet rzadko spotykany poza Europą Wschodnią. Niska moc obalająca, ale poręczny i prawdopodobnie przetrwa kilka nadprzyrodzonych katastrof." +L["wep." .. w .. ".desc.quote"] = "Znajdź Streloka. Zabić Streloka?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "RPC Fort" +-- L["wep." .. w .. ".credits"] = "Assets: S.T.A.L.K.E.R. (ported by modderfreak) \nAnimations: Tactical Intervention" + +w = ws .. "hipoint" +-- L["wep." .. w .. ".name.full"] = "Hi-Point 995" +-- L["wep." .. w .. ".name"] = "Hi-Point" +-- L["wep." .. w .. ".desc"] = "Infamous semi-automatic pistol-caliber carbine.\nReasonably powerful... when it works, which is never." +-- L["wep." .. w .. ".desc.quote"] = "\"Good wombs have borne bad sons.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Hi-Point Firearms" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Yumyumshisha \nSounds: Obsidian \nAnimations: Tactical Intervention" + +w = ws .. "ithaca" +-- L["wep." .. w .. ".name"] = "Ithaca 37" +L["wep." .. w .. ".desc"] = "Strzelba z czasów II wojny światowej, używana przez policję i wojsko. Zdolna do szybkiego ognia, ale ma słabe rozproszenie i niską pojemność amunicji." +-- L["wep." .. w .. ".desc.quote"] = "\"What? It was obvious! He's the RED Spy!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Ithaca Gun Company" +-- L["wep." .. w .. ".credits"] = "Model: Millenia \nTextures: Poux \nSounds: Strelok \nAnimations: Tactical Intervention" + +w = ws .. "klin" +-- L["wep." .. w .. ".name.full"] = "PP-9 Klin" +-- L["wep." .. w .. ".name"] = "Klin" +-- L["wep." .. w .. ".desc"] = "Simple, lightweight, cheap machine pistol.\nHas a high fire rate but is prone to jamming." +-- L["wep." .. w .. ".desc.quote"] = "\"Eyy, importnyy? FUCK OFF. Ponyl?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhmash" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Teh Snake \nAnimations: Tactical Intervention" + +w = ws .. "ksg12" +-- L["wep." .. w .. ".name.full"] = "KelTec KSG" +-- L["wep." .. w .. ".name"] = "KSG" +-- L["wep." .. w .. ".desc"] = "A bizarre bullpup, twin-tubed, pump-action shotgun. Super high capacity offset by extreme heft." +-- L["wep." .. w .. ".desc.quote"] = "\"Hey! Quit eyeballing the chicks.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kel-Tec CNC Industries" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nSounds: Infinity Ward & Navarro \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "lapd" +-- L["wep." .. w .. ".name.full"] = "LAPD 2019 Blaster" +-- L["wep." .. w .. ".name"] = "LAPD 2019" +-- L["wep." .. w .. ".desc"] = "Gunsmith-custom based on an iconic film weapon, featuring excellent handling and an integrated laser sight." +-- L["wep." .. w .. ".desc.quote"] = "\"Quite an experience to live in fear, isn't it? That's what it is to be a slave.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr, Charter Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Fallout: New Vegas \nAnimations: Tactical Intervention, Fesiug, 8Z" + +w = ws .. "lewis" +L["wep." .. w .. ".name"] = "Karabin Lewis" +L["wep." .. w .. ".desc"] = "Karabin maszynowy zasilany z tacy, chłodzony wodą, z dość małym magazynkiem." +L["wep." .. w .. ".desc.quote"] = "\"Wszyscy z drogi!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Lewis Automatic Machine Gun Company" +-- L["wep." .. w .. ".credits"] = "Assets: Verdun \nAnimations: Tactical Intervention" + +w = ws .. "luty" +-- L["wep." .. w .. ".name"] = "Luty SMG" +-- L["wep." .. w .. ".desc"] = "Homemade SMG made as protest against UK firearm regulations. The first few shots in a burst have increased firerate, compensating for the weapon's terrible accuracy." +-- L["wep." .. w .. ".desc.quote"] = "\"Let us never succumb to the evil doctrine of the antigun movement.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "P.A. Luty" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Vasily \nSounds: New World Interactive, Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "m79" +-- L["wep." .. w .. ".name"] = "M79" +L["wep." .. w .. ".desc"] = "Granatnik z długą lufą i kolbą z drewna z dżungli. Precyzyjny i ma szybką prędkość początkową, ale jest dość ciężki." +L["wep." .. w .. ".desc.quote"] = "\"Goooooooooooooood morning, Wietnam!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Model: EdisLeado \nTextures: Millenia \nSounds: Firearms: Source \nAnimations: speedonerd & 8Z" + +w = ws .. "m202" +-- L["wep." .. w .. ".name.full"] = "M202 FLASH" +-- L["wep." .. w .. ".name"] = "M202" +-- L["wep." .. w .. ".desc"] = "Incendiary rocket launcher with four shots. Rockets ignite targets but have low damage and splash radius +-- L["wep." .. w .. ".desc.quote"] = "\"Fire and forget!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Northrop Electro-Mechanical Division" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "madsen" +-- L["wep." .. w .. ".name"] = "Madsen M-50" +L["wep." .. w .. ".desc"] = "Opłacalny duński SMG, który jest łatwy w użyciu, ale ma nieimponującą siłę ognia. Celownik nie może być montowany z powodu górnego uchwytu do ładowania." +L["wep." .. w .. ".desc.quote"] = "\"Czy wyrzekasz się Szatana... I wszystkich jego dzieł?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Dansk Industri Syndikat" +-- L["wep." .. w .. ".credits"] = "Model & Textures: jesamabin \nSounds: xLongWayHome \nAnimations: Tactical Intervention, 8Z" + +w = ws .. "makarov" +-- L["wep." .. w .. ".name.full"] = "Makarov PM" +-- L["wep." .. w .. ".name"] = "PM" +L["wep." .. w .. ".desc"] = "Masowo produkowany radziecki pistolet boczny zaprojektowany do częstego noszenia i rzadkiego strzelania. Skuteczność jest słaba poza zasięgiem punktowym." +-- L["wep." .. w .. ".desc.quote"] = "\"You're listening to Apocalypse Radio. The only surviving radio station for miles.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhevsk Mechanical Plant" +-- L["wep." .. w .. ".credits"] = "Assets: TehSnake \nSounds: Hk, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "mosin" +-- L["wep." .. w .. ".name.full"] = "Mosin-Nagant 91/30" +-- L["wep." .. w .. ".name"] = "M91/30" +-- L["wep." .. w .. ".desc"] = "Mass production Soviet bolt-action rifle. Infantry model cycles faster but is inaccurate; equip a scope for increased accuracy at cost of firerate." +-- L["wep." .. w .. ".desc.quote"] = "\"One out of two gets a rifle, one without follows him!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Red Orchestra 2 \nSounds: Red Orchestra 2, rzen1th \nAnimations: Cry of Fear, Lazarus, 8Z" + +w = ws .. "oa93" +-- L["wep." .. w .. ".name.full"] = "Olympic Arms OA-93" +-- L["wep." .. w .. ".name"] = "OA-93" +-- L["wep." .. w .. ".desc"] = "AR-15 derived pistol with a top charging handle and no buffer tube. Designed to circumvent legal restrictions, but this one is ironically modified to fire in full auto." +-- L["wep." .. w .. ".desc.quote"] = "\"The most absurdly engineered way to say 'fuck you!' I've ever come across.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Olympic Arms" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Tuuttipingu, edited by 8Z \nSounds: NightmareMutant, Teh Strelok \nAnimations: Tactical Intervention" + +w = ws .. "obrez" +-- L["wep." .. w .. ".name.full"] = "Remington 700 \"Obrez\"" +-- L["wep." .. w .. ".name"] = "Obrez" +L["wep." .. w .. ".desc"] = "Skrócony karabin myśliwski zbudowany przez osoby pozbawione broni bocznej, aby był zastępnikiem ukrytego pistoletu. Modyfikacja poważnie szkodzi dokładności i wydajności na dystansie." +L["wep." .. w .. ".desc.quote"] = "\"Yippee-yay, nie będzie dzwonków ślubnych na dziś...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms, Bubba" +-- L["wep." .. w .. ".credits"] = "Assets: Bethesda Game Studios \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "ots33" +-- L["wep." .. w .. ".name.full"] = "OTs-33 Pernach" +-- L["wep." .. w .. ".name"] = "OTs-33" +L["wep." .. w .. ".desc"] = "Rosyjski pistolet maszynowy o niskim odrzucie, zaprojektowany dla sił paramilitarnych." +-- L["wep." .. w .. ".desc.quote"] = "\"I hope this hurts.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "KBP Instrument Design Bureau" +-- L["wep." .. w .. ".credits"] = "Model: Kimono \nTextures: Kimono, Millenia \nSounds: iFlip, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "p2a1" +-- L["wep." .. w .. ".name.full"] = "HK P2A1" +-- L["wep." .. w .. ".name"] = "P2A1" +-- L["wep." .. w .. ".desc"] = "Single-shot flare pistol for signaling and illumination use.\nStandard round has a weak incendiary explosion. Can load a variety of payloads, including some shotshell types." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model & Textures: New World Interactive \nSounds: Raising The Bar: Redux, speedonerd \nAnimations: 8Z" + +w = ws .. "ppsh" +-- L["wep." .. w .. ".name"] = "PPSh-41" +-- L["wep." .. w .. ".desc"] = "Soviet SMG with a high fire rate and suitably massive but unreliable drum magazine. Accuracy is non-existent and very much optional." +-- L["wep." .. w .. ".desc.quote"] = "Ура!" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Various" +-- L["wep." .. w .. ".credits"] = "Model: Tripwire Interactive \nTextures: Rus_Ivan \nAnimations: Tactical Intervention, edited by speedonerd \nSounds: Rus_Ivan" + +w = ws .. "python" +-- L["wep." .. w .. ".name.full"] = "Colt Python" +-- L["wep." .. w .. ".name"] = "Python" +L["wep." .. w .. ".desc"] = "Rzadki rewolwer o reputacji precyzyjnej i mocnej broni. Do strzelania jedną ręką; styl kowbojski." +L["wep." .. w .. ".desc.quote"] = "\"Kiedy musisz strzelać, strzelaj. Nie mów.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Aggressive Napkin \nTextures: Teh Snake \nSounds: Vunsunta, XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "rhino60ds" +-- L["wep." .. w .. ".name.full"] = "Chiappa Rhino 60DS" +-- L["wep." .. w .. ".name"] = "Rhino 60DS" +-- L["wep." .. w .. ".desc"] = "Modern long barrel revolver with hexagonal cylinder.\nBulky and has a heavy trigger, but recoil control is excellent." +-- L["wep." .. w .. ".desc.quote"] = "\"It's simple: overspecialize, and you breed in weakness. It's a slow death.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Chiappa Firearms" +-- L["wep." .. w .. ".credits"] = "Assets: Warface, ported by Dorian15 \nAnimations: Tactical Intervention" + +w = ws .. "sako85" +-- L["wep." .. w .. ".name"] = "Sako 85" +L["wep." .. w .. ".desc"] = "Prosty, wytrzymały karabin myśliwski bez miejsca na taktyczne bzdury. Wyposażony w regulowany celownik 6x." +-- L["wep." .. w .. ".desc.quote"] = "Get a girl who loves you for your braaaains." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +-- L["wep." .. w .. ".credits"] = "Assets: No More Room In Hell \nAnimations: Tactical Intervention" + +w = ws .. "scorpionevo" +-- L["wep." .. w .. ".name.full"] = "CZ Scorpion EVO 3" +-- L["wep." .. w .. ".name"] = "Scorpion EVO" +-- L["wep." .. w .. ".desc"] = "Modern SMG with a extremely high rate of fire and intense recoil." +-- L["wep." .. w .. ".desc.quote"] = "Not to be confused with the vz. 61." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Assets: Warface, Ghost1592365 \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "shorty" +-- L["wep." .. w .. ".name.full"] = "Serbu Super Shorty" +-- L["wep." .. w .. ".name"] = "Shorty" +L["wep." .. w .. ".desc"] = "Niestandardowa modyfikacja odbiornika Mossberg 88, przeznaczona do ekstremalnego ukrywania. Jednak poświęca precyzję i pojemność. Pasuje do kabur bocznych." +L["wep." .. w .. ".desc.quote"] = "\"Shawty poszła nisko, nisko, nisko, nisko...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Serbu Firearms" +-- L["wep." .. w .. ".credits"] = "Assets: Millennia \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "sks" +-- L["wep." .. w .. ".name"] = "SKS" +-- L["wep." .. w .. ".desc"] = "Soviet semi-automatic rifle intended to complement the AK. A well-rounded medium range firearm." +-- L["wep." .. w .. ".desc.quote"] = "\"Don't breathe.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arsenal" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "smle" +-- L["wep." .. w .. ".name.full"] = "Lee-Enfield Mk III*" +-- L["wep." .. w .. ".name"] = "Lee-Enfield" +-- L["wep." .. w .. ".desc"] = "Mass production British bolt-action rifle with high capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"It was all so easy.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Royal Small Arms Factory" +-- L["wep." .. w .. ".credits"] = "Assets: Cry of Fear \nAnimations: Cry of Fear, Lazarus" + +w = ws .. "stg44" +-- L["wep." .. w .. ".name.full"] = "Sturmgewehr 44" +-- L["wep." .. w .. ".name"] = "StG 44" +-- L["wep." .. w .. ".desc"] = "Vintage assault rifle, considered by many to be the first of its kind. Somewhat powerful but prone to malfunctions." +-- L["wep." .. w .. ".desc.quote"] = "\"Mein carbine did not kill you? I will use gas! More gas!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "C.G. Haenel" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Tuuttipingu \nSounds: Navaro, Project Reality" + +w = ws .. "svt40" +-- L["wep." .. w .. ".name"] = "SVT-40" +L["wep." .. w .. ".desc"] = "Karabin samopowtarzalny z okresu II wojny światowej zaprojektowany do ekstremalnej produkcji masowej. Potężny, ale nieprecyzyjny i niewiarygodny. Ograniczone wybory optyki." +-- L["wep." .. w .. ".desc.quote"] = "\"It's a sign that the Germans are starting to shit their pants!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arsenal" +-- L["wep." .. w .. ".credits"] = "Model: MrRifleman \nTextures: BuLL5H1T" + +w = ws .. "svu" +-- L["wep." .. w .. ".name.full"] = "Dragunov SVU" +-- L["wep." .. w .. ".name"] = "SVU" +-- L["wep." .. w .. ".desc"] = "Bullpup modernized model of the SVD. Has good handling but average effective range.\nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"It's all just about perpective.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "KBP Instrument Design Bureau" +-- L["wep." .. w .. ".credits"] = "Assets: B0T \nSounds: NightmareMutant, sHiBaN, xLongWayHome \nAnimations: speedonerd" + +w = ws .. "sw10" +-- L["wep." .. w .. ".name.full"] = "SW Model 10" +-- L["wep." .. w .. ".name"] = "SW M10" +-- L["wep." .. w .. ".desc"] = "An iconic revolver favored by cops and criminals alike.\nChambered in a non-magnum cartridge, so it's easy to handle but not that powerful." +-- L["wep." .. w .. ".desc.quote"] = "\"Who's 'we', sucka?\" \"Smith and Wesson, and me.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Harry Ridgeway, MediocrityGoggles \nTextures: Millenia \nSounds: Bethesda, Obsidian \nAnimations: Tactical Intervention" + +w = ws .. "sw686" +-- L["wep." .. w .. ".name.full"] = "SW Model 686" +-- L["wep." .. w .. ".name"] = "SW M686" +-- L["wep." .. w .. ".desc"] = "Magnum revolver with balanced, reliable performance." +-- L["wep." .. w .. ".desc.quote"] = "When there's no more room in hell, the dead will walk the Earth." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Assets: No More Room In Hell \nAnimations: Tactical Intervention" + +w = ws .. "toz34" +-- Only has a .name.full because the weapon does. +-- L["wep." .. w .. ".name.full"] = "TOZ-34" +-- L["wep." .. w .. ".name"] = "TOZ-34" +L["wep." .. w .. ".desc"] = "Podwójna strzelba myśliwska. Ma przyzwoitą celność i siłę rażenia, ale jej rozmiar i wolne przeładowanie sprawiają, że jest nieodpowiednia do walki." +-- L["wep." .. w .. ".desc.quote"] = "\"A nu, chiki briki i v damki!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tulsky Oruzheiny Zavod" +-- L["wep." .. w .. ".credits"] = "Model: SAM61 \nSounds: rzen1th \nAnimations: speedonerd & 8Z" + +w = ws .. "toz106" +-- Only has a .name.full because the weapon does. +-- L["wep." .. w .. ".name.full"] = "TOZ-106" +-- L["wep." .. w .. ".name"] = "TOZ-106" +L["wep." .. w .. ".desc"] = "Strzelba myśliwska z zamkiem ślizgowym. Małe kaliberowe pociski mają doskonałą celność, ale nie są bardzo śmiercionośne." +-- L["wep." .. w .. ".desc.quote"] = "\"Head, eyes.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tulsky Oruzheiny Zavod" +-- L["wep." .. w .. ".credits"] = "Model: RusMarine85 \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "uzi" +-- L["wep." .. w .. ".name.full"] = "IMI Uzi" +-- L["wep." .. w .. ".name"] = "Uzi" +L["wep." .. w .. ".desc"] = "Powojenny pistolet maszynowy o niesamowitej sterowalności. Jeden z najbardziej kultowych pistoletów, jakie kiedykolwiek wynaleziono. Ta wersja strzela z zamkniętym ryglem." +L["wep." .. w .. ".desc.quote"] = "\"Znasz się na swoich broniach, kumpel.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israeli Military Industries" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Firearms: Source \nSounds: Ghost597879, Ganryu, Strelok, Sticer \nAnimations: Tactical Intervention" + +w = ws .. "vykhlop" +-- L["wep." .. w .. ".name.full"] = "VKS \"Vykhlop\"" +-- L["wep." .. w .. ".name"] = "VKS" +L["wep." .. w .. ".desc"] = "Poddźwiękowy karabin snajperski z potężną i szybką do przeładowania amunicją poddźwiękową, ale ma niską prędkość wylotową i słabą penetrację pancerza. Wyposażony w celownik 6x." +-- L["wep." .. w .. ".desc.quote"] = "\"Na'am seyidi, al qanas ala al khatt.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "TsKIB SOO" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Call to Arms \nAnimations: Tactical Intervention, Arctic" + +w = ws .. "woodsman" +-- L["wep." .. w .. ".name.full"] = "Colt Woodsman" +-- L["wep." .. w .. ".name"] = "Woodsman" +-- L["wep." .. w .. ".desc"] = "Small caliber sporting pistol. Light and controllable enough to use one-handed, but lethality is low." +-- L["wep." .. w .. ".desc.quote"] = "\"Oh, my God, is that a .22?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Kimono, Faffout \nTextures: Crash \nSounds: Vunsunta, DMG, Strelok" + +-- Attachments (ak) +-- L["att.optic_sako85_4x.name.full"] = "Sako 85 4x Scope Zoom" +-- L["att.optic_sako85_4x.name"] = "4x" +-- L["att.optic_sako85_4x.desc"] = "Reduced scope magnification." + +-- L["att.optic_sako85_8x.name.full"] = "Sako 85 8x Scope Zoom" +-- L["att.optic_sako85_8x.name"] = "8x" +-- L["att.optic_sako85_8x.desc"] = "Increased scope magnification." + +-- L["att.optic_sako85_irons.name"] = "Iron Sights" +-- L["att.optic_sako85_irons.desc"] = "Remove scope for faster aim and better mobility." + +-- L["att.ammo_sako85_222.name.full"] = "Sako 85 .222 Winchester Mod Kit" +-- L["att.ammo_sako85_222.name"] = ".222 Win" +-- L["att.ammo_sako85_222.desc"] = "Load intermediate rounds with reduced recoil and lethality." + +-- L["att.ammo_sako85_300mag.name.full"] = "Sako 85 .300 Winchester Magnum Mod Kit" +-- L["att.ammo_sako85_300mag.name"] = ".300 Win Mag" +-- L["att.ammo_sako85_300mag.desc"] = "Load magnum rounds for improved lethality." + +-- L["att.optic_svt_pu.name.full"] = "SVT-40 3.5x PU Scope" +-- L["att.optic_svt_pu.name"] = "PU" +-- L["att.optic_svt_pu.desc"] = "Low power scope with specialized mount for the SVT-40." + +-- L["att.ammo_m202_smoke.name.full"] = "M202 Smoke Rockets" +-- L["att.ammo_m202_smoke.name"] = "Smoke" +-- L["att.ammo_m202_smoke.desc"] = "Rockets that produces a concealing smokescreen on impact." + +-- L["att.ammo_m202_apers.name.full"] = "M202 Hornet Rockets" +-- L["att.ammo_m202_apers.name"] = "Hornet" +-- L["att.ammo_m202_apers.desc"] = "Airburst fragmentation rockets for direct fire anti-personnel use." + +-- L["att.ammo_m202_harpoon.name.full"] = "M202 Harpoon Rockets" +-- L["att.ammo_m202_harpoon.name"] = "Harpoon" +-- L["att.ammo_m202_harpoon.desc"] = "Launch fiery harpoons that do tremendous damage on impact." + +-- L["att.ammo_m202_he.name.full"] = "M202 High-Explosive Anti-Tank Rockets" +-- L["att.ammo_m202_he.name"] = "HEAT" +-- L["att.ammo_m202_he.desc"] = "Rockets with an explosive charge." + +-- L["att.optic_mosin_irons.name.full"] = "Mosin-Nagant Sniper Bolt" +-- L["att.optic_mosin_irons.name"] = "Sniper" +-- L["att.optic_mosin_irons.desc"] = "Use the sniper bolt without a scope, increasing accuracy." + +-- L["att.optic_mosin_pu.name.full"] = "Mosin-Nagant 3.5x PU Scope" +-- L["att.optic_mosin_pu.name"] = "PU" +-- L["att.optic_mosin_pu.desc"] = "Side-mounted low power scope for the Mosin-Nagant." + +-- L["att.optic_mosin_pem.name.full"] = "Mosin-Nagant 6x PEM Scope" +-- L["att.optic_mosin_pem.name"] = "PEM" +-- L["att.optic_mosin_pem.desc"] = "Side-mounted sniper scope for the Mosin-Nagant." + +-- L["att.optic_mosin_pe.name.full"] = "Mosin-Nagant 4x PE Scope" +-- L["att.optic_mosin_pe.name"] = "PE" +-- L["att.optic_mosin_pe.desc"] = "Top-mounted medium range scope for the Mosin-Nagant." + +-- L["att.muzz_mosin_bayonet.name.full"] = "Mosin-Nagant Spike Bayonet" +-- L["att.muzz_mosin_bayonet.name"] = "Bayonet" +-- L["att.muzz_mosin_bayonet.desc"] = "For stabbing fascist scum." + +-- L["att.muzz_svu_supp.name.full"] = "Dragunov SVU Suppressor" +-- L["att.muzz_svu_supp.name"] = "SVU Supp." +-- L["att.muzz_svu_supp.desc"] = "Weapon-specific suppressor that boosts fire rate." + +-- L["att.muzz_sks_bayonet.name.full"] = "SKS Folding Bayonet" +-- L["att.muzz_sks_bayonet.name"] = "Bayonet" +-- L["att.muzz_sks_bayonet.desc"] = "For stabbing capitalist scum." + +-- L["att.tac_cz75_mag.name.full"] = "CZ-75 Backup Magazine" +-- L["att.tac_cz75_mag.name"] = "Backup Mag" +-- L["att.tac_cz75_mag.desc"] = "An extra magazine mounted on the gun for peace of mind." + +-- L["att.barrel_coachgun_short.name.full"] = "Coachgun Short Barrels" +-- L["att.barrel_coachgun_short.name"] = "Short" +-- L["att.barrel_coachgun_short.desc"] = "Significantly shortened barrel for close range encounters." + +-- L["att.ammo_automag3_30carbine.name.full"] = "AutoMag III .30 Carbine Mod Kit" +-- L["att.ammo_automag3_30carbine.name"] = ".30 Carbine" +-- L["att.ammo_automag3_30carbine.desc"] = "Load a carbine cartridge for improved accuracy and range." + +-- L["att.optic_smle_no32.name.full"] = "Lee Enfield No. 32 Telescopic Scope" +-- L["att.optic_smle_no32.name"] = "No. 32 Scope" +-- L["att.optic_smle_no32.desc"] = "Top-mounted medium range scope for the Lee-Enfield." + +-- L["att.ammo_p2a1_incendiary.name.full"] = "P2A1 Incendiary Cartridges" +-- L["att.ammo_p2a1_incendiary.name"] = "Incendiary" +-- L["att.ammo_p2a1_incendiary.desc"] = "Flares with a more powerful explosion but no illumination." + +-- L["att.ammo_p2a1_smoke.name.full"] = "P2A1 Smoke Cartridges" +-- L["att.ammo_p2a1_smoke.name"] = "Smoke" +-- L["att.ammo_p2a1_smoke.desc"] = "Flares that creates a small smokescreen on impact." + +-- L["att.ammo_p2a1_para.name.full"] = "P2A1 Illumination Flare Cartridges" +-- L["att.ammo_p2a1_para.name"] = "Illumination" +-- L["att.ammo_p2a1_para.desc"] = "White flares with a mini-parachute, lighting up a large area while it falls." + +-- L["att.ammo_p2a1_buckshot.name.full"] = "P2A1 Magnum Buck Shotshells" +-- L["att.ammo_p2a1_buckshot.name"] = "Magnum Buck" +-- L["att.ammo_p2a1_buckshot.desc"] = "Cram some shotshells into your flare gun for direct firepower." + +-- L["att.ammo_p2a1_bird.name.full"] = "P2A1 Birdshot Shotshells" +-- L["att.ammo_p2a1_bird.name"] = "Birdshot" +-- L["att.ammo_p2a1_bird.desc"] = "Cram some birdshells into your flare gun. Insane spread but hard to miss." + +-- L["att.ammo_p2a1_slug.name.full"] = "P2A1 Slug Shotshells" +-- L["att.ammo_p2a1_slug.desc"] = "Cram slugs into your flare gun. Short barrel limits accuracy and range." + +-- L["att.ammo_p2a1_frag.name.full"] = "P2A1 High-Explosive Frag Shotshells" +-- L["att.ammo_p2a1_frag.name"] = "Frag" +-- L["att.ammo_p2a1_frag.desc"] = "Turn your flare gun into a knockoff grenade pistol." + +-- L["att.ammo_p2a1_flashbang.name.full"] = "P2A1 Zvezda Flash Shotshells" +-- L["att.ammo_p2a1_flashbang.name"] = "Zvezda" +-- L["att.ammo_p2a1_flashbang.desc"] = "Flashbang dispenser in your pocket. Best used around corners." + +-- L["att.ammo_p2a1_breach.name.full"] = "P2A1 Breaching Shotshells" +-- L["att.ammo_p2a1_breach.name"] = "Breach" +-- L["att.ammo_p2a1_breach.desc"] = "Load a specialized breaching slug for pocket door busting." + +-- L["att.ammo_p2a1_confetti.name.full"] = "P2A1 Confetti Shotshells" +-- L["att.ammo_p2a1_confetti.name"] = "Confetti" +-- L["att.ammo_p2a1_confetti.desc"] = "For celebrations. Yippie!" + +-- L["att.procon.illumradius"] = "Illumination Radius" +-- L["att.procon.noflare"] = "No Flare" + +///////////////////// -- [[ One-Offs ]] -- +-- Weapons +ws = "tacrp_sp_" +w = ws .. "sg510speedo" +-- L["wep." .. w .. ".name.full"] = "SG 510 \"Black Shark\"" +-- L["wep." .. w .. ".name"] = "Black Shark" +-- L["wep." .. w .. ".desc"] = "A customized SG 510 with a chopped barrel and a serious muzzle brake, producing low recoil and a spectacular muzzleflash." +-- L["wep." .. w .. ".desc.quote"] = "\"My armor's black. That doesn't mean my heart is as well.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Vanderbilt Company" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd \nAssets: World of Guns: Disassembly, Tactical Intervention \nSounds: Sledgehammer Games, speedonerd" + +w = ws .. "mp5_zeroeight" +-- L["wep." .. w .. ".name.full"] = "MP5/10 \"Zero Eight\"" +-- L["wep." .. w .. ".name"] = "Zero Eight" +-- L["wep." .. w .. ".desc"] = "Customized 10mm MP5 with Swordfish kit, prototype foregrip and drum magazine. Weighted muzzle brake improves recoil handling and smacks real hard too." +-- L["wep." .. w .. ".desc.quote"] = "\"The lesson for you is never try.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Neptunium Arms" +-- L["wep." .. w .. ".credits"] = "Custom edited by speedonerd and 8Z \nMP5: Mr. Brightside, Stoke, Twinkie Masta, FxDarkloki \nAccessories: Treyarch, BlackSpot Entertainment, Crytek \nSounds: Strelok, CS:O2" + +w = ws .. "hecate_vinierspecial" +-- L["wep." .. w .. ".name.full"] = "PGM Hécate II \"Kingbreaker\"" +-- L["wep." .. w .. ".name"] = "The Kingbreaker" +-- L["wep." .. w .. ".desc"] = "Customized Hécate II with a mammoth suppressor, custom 16x scope sporting a jury-rigged rangefinder, and a rebellious message scrawled on the gun's side." +-- L["wep." .. w .. ".desc.quote"] = "\"No more chivalry. Now we fight like wolves.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for VinierAardvark1 \nHécate II Model: Toby Burnside \nAdditional assets: Treyarch, Infinity Ward, valterjherson1, Unselles, speedonerd" + +w = ws .. "usp_valencespecial" +-- L["wep." .. w .. ".name.full"] = "HK USP \"The Governor\"" +-- L["wep." .. w .. ".name"] = "The Governor" +-- L["wep." .. w .. ".desc"] = "USP Elite decked out with competition parts and chambered in .45 Super for extra firepower. Superb performance at range, but not steady while moving." +-- L["wep." .. w .. ".desc.quote"] = "\"Loud enough to knock you down.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for Valence \nOriginal model: Thanez, Racer445, fxdarkloki \nAdditional assets: Battlestate Games, Crytek \nSounds: Vunsunta, BlitzBoaR" + +w = ws .. "tudspecial" +-- L["wep." .. w .. ".name.full"] = "Desert Eagle \"Arbiter\"" +-- L["wep." .. w .. ".name"] = "Arbiter" +-- L["wep." .. w .. ".desc"] = "Gold-plated Desert Eagle in a mock-carbine configuration and given the unholy power of fully-automatic fire." +-- L["wep." .. w .. ".desc.quote"] = "\"This is this and that is that.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for Tud \nDeagle: Vasht, Racer445 \nAdditional assets: kriboez, cR45h, Engys Epangelmatikes, Vitevius" + +w = ws .. "sr25_bladespecial" +-- L["wep." .. w .. ".name.full"] = "KAC SR-25 \"Symbiosis\"" +-- L["wep." .. w .. ".name"] = "Symbiosis" +-- L["wep." .. w .. ".desc"] = "Integrally suppressed SR-25 rechambered for .338 Lapua Magnum and sporting an adjustable 8x rangefinder scope." +-- L["wep." .. w .. ".desc.quote"] = "\"There are no gods. The only man in the sky, is me.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for Bladelordomega \nSR-25: Firearms: Source \nAdditional assets: Battlestate Games, Treyarch, Crytek, kriboez, cR45h" + +-- L["hint.tac.bladespecial"] = "Adjust Zoom" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_ru.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_ru.lua new file mode 100644 index 0000000..bb99a6b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_ru.lua @@ -0,0 +1,101 @@ +L = {} -- Русский язык - Контент + +--[[ +Для локализации модификаций (замените optic_acog на класс своей модификации): +L["att.optic_acog.name"] = "Название модификации" +L["att.optic_acog.name.full"] = "Полное название модификации" +L["att.optic_acog.desc"] = "Описание модификации" + +Для локализации оружия (замените tacrp_vertec на класс своего оружия): +L["wep.tacrp_vertec.name.full"] = "Полное название оружия" +L["wep.tacrp_vertec.name"] = "Название оружия" +L["wep.tacrp_vertec.desc"] = "Описание оружия" +L["wep.tacrp_vertec.desc.quote"] = "Цитата оружия" -- курсивная строка внизу +L["wep.tacrp_vertec.trivia.manufacturer"] = "Производитель" +L["wep.tacrp_vertec.trivia.caliber"] = "Калибр" +L["wep.tacrp_vertec.trivia.year"] = "Год" +L["wep.tacrp_vertec.credits"] = "Авторы" +]] + +///////////////////// -- [[ TacRP Base ]] -- +-- Быстрые гранаты +L["quicknade.fuse"] = "ВЗРЫВАТЕЛЬ:" + +L["quicknade.frag.name.full"] = "Осколочная граната" +L["quicknade.frag.name"] = "ОСКОЛ." +L["quicknade.frag.dettype"] = "Таймер: 2с" +L["quicknade.frag.desc"] = "Стандартная взрывная граната, разбрасывающая осколки в среднем радиусе.\n\nОбычно не смертельна сама по себе, но может ранить цели или вытеснить их из укрытия." + +L["quicknade.flash.name.full"] = "Светошумовая граната" +L["quicknade.flash.name"] = "СВЕТО." +L["quicknade.flash.dettype"] = "Таймер: 1.5с" +L["quicknade.flash.desc"] = "Излучает яркую вспышку и оглушительный хлопок, дезориентирующий цели.\n\nЗамедляет поражённые цели и наносит незначительный урон в большом радиусе." + +L["quicknade.smoke.name.full"] = "Дымовая граната" +L["quicknade.smoke.name"] = "ДЫМОВ." +L["quicknade.smoke.dettype"] = "Таймер: 2с" +L["quicknade.smoke.desc"] = "Создаёт маскирующее облако дыма, которое длится около 20 секунд.\n\nНе наносит урона вообще и обычно используется для прикрытия наступления или затруднения линии видимости." + +L["quicknade.gas.name.full"] = "Газовая граната" +L["quicknade.gas.name"] = "ГАЗ" +L["quicknade.gas.dettype"] = "Таймер: 2с" +L["quicknade.gas.desc"] = "Выпускает облако слезоточивого газа, которое длится около 15 секунд.\n\nВсе, кто попадут внутрь, получают несмертельный длительный урон и испытывают трудности с удержанием оружия.\n\nЭто химическое оружие, запрещённое Женевской конвенцией." + +L["quicknade.fire.name.full"] = "Термитная граната" +L["quicknade.fire.name"] = "ОГОНЬ" +L["quicknade.fire.dettype"] = "Таймер: 3с" +L["quicknade.fire.desc"] = "Прилипает к целям и интенсивно горит около 8 секунд, нанося урон в малом радиусе.\n\nХотя термит обычно используется для прожигания материалов, он также полезен для блокировки зон." + +L["quicknade.c4.name.full"] = "Заряд C4" +L["quicknade.c4.name"] = "C4" +L["quicknade.c4.dettype"] = "Дистанционно" +L["quicknade.c4.desc"] = "Брикет мощной взрывчатки, который можно подорвать детонатором дистанционно.\n\nC4 удивительно инертен, но сигнальное устройство можно снять или уничтожить, обезвредив заряд." + +L["quicknade.nuke.name.full"] = "Ядерное устройство" +L["quicknade.nuke.name"] = "ЯДЕРН." +L["quicknade.nuke.dettype"] = "Дистанционно" +L["quicknade.nuke.desc"] = "Микроядерная бомба размером с портфель, которую можно подорвать детонатором дистанционно.\n\nЕё взрывной результат не нуждается в описании." + +L["quicknade.breach.name.full"] = "Взрывной заряд" +L["quicknade.breach.name"] = "ВЗЛОМ" +L["quicknade.breach.dettype"] = "Таймер: 2с / Дистанционно" +L["quicknade.breach.desc"] = "Кумулятивный заряд, созданный для пробивания дверей и слабых стен.\n\nМаленький радиус взрыва, но уничтожит любую дверь, к которой прикреплён, и ранит цели с другой стороны ударной волной.\n\nПри удержании детонатора заряд настроен на дистанционный подрыв." + +L["quicknade.heal.name.full"] = "Медицинский дым" +L["quicknade.heal.name"] = "ЛЕЧЕН." +L["quicknade.heal.dettype"] = "Таймер: 5с" +L["quicknade.heal.desc"] = "Выпускает облако восстанавливающего газа примерно на 15 секунд.\n\nМедицинские наниты восстанавливают здоровье при вдыхании. Если получатели здоровы, их броня может быть отремонтирована или перезаряжена.\n\nИмеет противоположный эффект на нежить, нанося урон вместо лечения." + +L["quicknade.rock.name.full"] = "Камень" +L["quicknade.rock.name"] = "КАМЕНЬ" +L["quicknade.rock.dettype"] = "Тупая травма" +L["quicknade.rock.desc"] = "Возможно, первое оружие, когда-либо использованное людьми.\n\nИспользуйте в крайнем случае, для древних казней или жестоких розыгрышей.\n\nНасколько вы находчивы, нет предела тому, что ещё можно достать из штанов в крайнем случае..." + +L["quicknade.bump.name.full"] = "Отбрасывающая мина" +L["quicknade.bump.name"] = "ОТБРОС" +L["quicknade.bump.dettype"] = "Нажимная пластина" +L["quicknade.bump.desc"] = "Намагниченная мина, прилипающая к поверхностям.\n\nСоздаёт безвредный взрыв, отталкивающий всё вокруг, раня цели, отправляя их в стены или пол.\n\nМожет использоваться для запуска себя очень далеко, но не забудьте парашют." + +L["quicknade.t-smk.name.full"] = "Дымовая граната" +L["quicknade.t-smk.name"] = "T-ДЫМ" +L["quicknade.t-smk.dettype"] = "Таймер: 2с" +L["quicknade.t-smk.desc"] = "Террористическая дымовая граната.\n\nСоздаёт дымовую завесу." + +L["quicknade.t-dcb.name.full"] = "Дискомбобулятор" +L["quicknade.t-dcb.name"] = "T-ДСК" +L["quicknade.t-dcb.dettype"] = "Таймер: 3с" +L["quicknade.t-dcb.desc"] = "Террористическая граната сотрясения.\n\nНе наносит урона, но создаёт взрыв, притягивающий объекты и отбрасывающий игроков." + +L["quicknade.t-inc.name.full"] = "Зажигательная граната" +L["quicknade.t-inc.name"] = "T-ЗАЖ" +L["quicknade.t-inc.dettype"] = "Таймер: 2с" +L["quicknade.t-inc.desc"] = "Террористическая зажигательная граната.\n\nВзрывается с небольшим уроном и разжигает огонь в зоне." + +-- ПРИМЕЧАНИЕ: Полный перевод всех оружий и модификаций (~3000 строк) требует времени. +-- Выше переведены основные игровые элементы (гранаты/снаряжение). +-- Для перевода конкретного оружия добавьте строки в формате: +-- local w = "tacrp_имя_оружия" +-- L["wep." .. w .. ".name.full"] = "Полное название" +-- L["wep." .. w .. ".name"] = "Короткое" +-- L["wep." .. w .. ".desc"] = "Описание" +-- и т.д. diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_sv-se.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_sv-se.lua new file mode 100644 index 0000000..2b8f7f9 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_sv-se.lua @@ -0,0 +1,2915 @@ +L = {} -- Swedish Content Strings by Moka + +///////////////////// -- [[ TacRP Base ]] -- +-- QuickNades +L["quicknade.fuse"] = "TÄNDNING:" + +L["quicknade.frag.name.full"] = "Splittergranat" +L["quicknade.frag.name"] = "SPLITTER" +L["quicknade.frag.dettype"] = "Tid: 2s" +L["quicknade.frag.desc"] = "Normal splitterhandgranat som slänger splitter i en måttlig radie.\n\nTypiskt sett inte dödlig, men kan skada måltavlor eller tvinga ut dem från skydd." + +L["quicknade.flash.name.full"] = "Chockgranat" +L["quicknade.flash.name"] = "CHOCK" +L["quicknade.flash.dettype"] = "Tid: 1,5s" +L["quicknade.flash.desc"] = "Avger ett starkt ljus och en dövande smäll som chockar måltavlor (namnet säger så).\n\nSölar ned påverkade måltavlor, och gör minimal skada i en större radie." + +L["quicknade.smoke.name.full"] = "Rökgranat" +L["quicknade.smoke.name"] = "RÖK" +L["quicknade.smoke.dettype"] = "Tid: 2s" +L["quicknade.smoke.desc"] = "Avger ett täckande rökmoln som varar i cirka 20 sekunder.\n\nGör ingen skada över huvud taget, och används ofast att täcka ett framtryck eller täcka en siktlinje." + +L["quicknade.gas.name.full"] = "Tårgasgranat" +L["quicknade.gas.name"] = "GAS" +L["quicknade.gas.dettype"] = "Tid: 2s" +L["quicknade.gas.desc"] = "Avger ett moln av tårgas som varar i cirka 15 sekunder.\n\nAlla som är i den tar icke-dödlig lingeringskada och har problem att hålla deras vapen stadigt.\n\nDet är ett kemiskt vapen bannlyst av Genèvekonventionen och ÄR INTE LUSTGAS." + +L["quicknade.fire.name.full"] = "Termitgranat" +L["quicknade.fire.name"] = "ELD" +L["quicknade.fire.dettype"] = "Tid: 3s" +L["quicknade.fire.desc"] = "Fastnar på måltavlor och brinner intensit i cirka 8 sekunder och gör skada i en liten radie.\n\nMedans termit används oftast att gå genom material så är den också användbar för områdesförnekelse." + +L["quicknade.c4.name.full"] = "C4-Laddning" +L["quicknade.c4.name"] = "C4" +L["quicknade.c4.dettype"] = "Fjärr" +L["quicknade.c4.desc"] = "En bunt kraftfulla sprängmedel som kan fjärrsprängas med en detonator.\n\nC4 är ganska trögt, så signaleringsenheten kan tas bort eller förstöras, vilket desarmerar laddningen." + +L["quicknade.nuke.name.full"] = "Nukelär Anordning" +L["quicknade.nuke.name"] = "NUKE" +L["quicknade.nuke.dettype"] = "Fjärr" +L["quicknade.nuke.desc"] = "Mikro-nukleär bomb i portföljstorlek som kan fjärrsprängas med en detonator.\n\nDess explosiva resultat kräver ingen beskrivning." + +L["quicknade.breach.name.full"] = "Dörrbrytare" +L["quicknade.breach.name"] = "BRYTARE" +L["quicknade.breach.dettype"] = "Tid: 2s / Fjärr" +L["quicknade.breach.desc"] = "Formad laddning som kan spränga sig genom dörrar och svaga väggar.\n\nLiten sprängradie, men kommer förstöra alla dörrar den placeras på, och skada måltavlor på andra sidan med dess chockvåg.\nNär du håller en detonator så kan laddningen konfigureras att fjärrsprängas." + +L["quicknade.heal.name.full"] = "Medi-Rök Kanister" +L["quicknade.heal.name"] = "HEAL" +L["quicknade.heal.dettype"] = "Tid: 5s" +L["quicknade.heal.desc"] = "Avger ett moln av återställande gas i cirka 15 sekunder.\n\nMedicinska naniter återställer livspoäng när dem andas in. Om mottagaren är hälsosam så repareras eller fylls rustningen på.\n\nHar motsatt effekt på nekrotik och gör skada istället." + +L["quicknade.rock.name.full"] = "Sten" +L["quicknade.rock.name"] = "STEN" +L["quicknade.rock.dettype"] = "Trubbigt Trauma" +L["quicknade.rock.desc"] = "Möjligtvist människans första vapen.\n\nAnvänd som ett sista val, till antika dödsstraff, eller för våldsamma skämt.\n\nSå fyndig du må vara, ingen vet vad du kan ta fram från byxorna när det krävs..." + +L["quicknade.bump.name.full"] = "Hoppmina" +L["quicknade.bump.name"] = "HOPP" +L["quicknade.bump.dettype"] = "Tryckplatta" +L["quicknade.bump.desc"] = "Magnetiserad mina som fastnar på ytor.\n\nSkapar en ofarlig sprängning som puttar bort allt och skadar måltavlor genom att skicka in dem i väggar eller golv.\n\nKan användas för att skicka dig långt bort, men kom ihåg fallskärmen." + +L["quicknade.t-smk.name.full"] = "Rökgranat" +L["quicknade.t-smk.name"] = "T-RÖK" +L["quicknade.t-smk.dettype"] = "Tid: 2s" +L["quicknade.t-smk.desc"] = "Terroristutfärdad rökgranat.\n\nSkapar en rökskärm." + +L["quicknade.t-dcb.name.full"] = "Discombobulator" +L["quicknade.t-dcb.name"] = "T-DCB" +L["quicknade.t-dcb.dettype"] = "Tid: 3s" +L["quicknade.t-dcb.desc"] = "Terroristutfärdad chockgranat.\n\nGör ingen skada, men skapar en sprängning som trycker in föremål och spelare ut." + +L["quicknade.t-inc.name.full"] = "Brandgranat" +L["quicknade.t-inc.name"] = "T-ELD" +L["quicknade.t-inc.dettype"] = "Tid: 2s" +L["quicknade.t-inc.desc"] = "Terroristutfärdad brandgranat.\n\nSprängs med minimal skada, men påbörjar en brand ett område." + +-- Weapons +local ws = "tacrp_" +local w = ws .. "ak47" +L["wep." .. w .. ".name.full"] = "FB Beryl 96" +L["wep." .. w .. ".name"] = "Beryl 96" +L["wep." .. w .. ".desc"] = "Lätt att hantera gevär med låg eldhastighet och rekyl." +L["wep." .. w .. ".desc.quote"] = "Trots dess utseendet så är det ingen AK." +L["wep." .. w .. ".trivia.manufacturer"] = "FB \"Łucznik\" Radom" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "amd65" +L["wep." .. w .. ".name"] = "AMD-65" +L["wep." .. w .. ".desc"] = "AK-klon från ungern med inbyggt grepp och trådkolv. Hög skada, men lägre räckvidd än dem flesta gevären." +L["wep." .. w .. ".trivia.manufacturer"] = "Fegyver- és Gépgyár" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "ar15" +L["wep." .. w .. ".name.full"] = "Diemaco AR-15" +L["wep." .. w .. ".name"] = "AR-15" +L["wep." .. w .. ".desc"] = "Anpassad halvautomatisk modell av ettt allestädes närvarande amerikanskt gevär. Använd sänkt kapacitetsmagasin." +L["wep." .. w .. ".desc.quote"] = "\"Ett av dem mest älskade och förtalade gevären i landet.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Colt Canada" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "as50" +L["wep." .. w .. ".name.full"] = "AI AS50" +L["wep." .. w .. ".name"] = "AS50" +L["wep." .. w .. ".desc"] = "Halvautomatiskt antimateriellgevär som lätt kan förstöra vilken person som helst oavsett avstånd. \nUtrustat med ett 12x kikarsikte som standard. För tung att svänga runt, så försök inte ens att slåss med den." +L["wep." .. w .. ".trivia.manufacturer"] = "Accuracy International" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "aug" +L["wep." .. w .. ".name.full"] = "Steyr AUG A2" +L["wep." .. w .. ".name"] = "AUG A2" +L["wep." .. w .. ".desc"] = "Bullpup-gevär med salvoeld och generös magasinkapacitet och bra hantering." +L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Arms" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "bekas" +L["wep." .. w .. ".name.full"] = "Molot Bekas-16M" +L["wep." .. w .. ".name"] = "Bekas-16M" +L["wep." .. w .. ".desc"] = "Träffsäker jakthagelbössa med låg skada." +L["wep." .. w .. ".trivia.manufacturer"] = "Molot" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "c4_detonator" +L["wep." .. w .. ".name"] = "C4 Detonator" +L["wep." .. w .. ".desc"] = "Enhet för att spränga C4-laddningar eller andra sorters fjärrsprängmedel." +-- L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "civ_amd65" +L["wep." .. w .. ".name.full"] = "Arsenal SAM7SF" +L["wep." .. w .. ".name"] = "SAM7SF" +L["wep." .. w .. ".desc"] = "Amerikansk halvautomatisk AK-plattform gevär, anpassat med AR-15-liknande kolv och handskydd från ungern.\nAnvänder sänkt kapacitetsmagasin." +L["wep." .. w .. ".trivia.manufacturer"] = "Arsenal Inc" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention \nRedigerad Modell: Arctic" + +w = ws .. "civ_g36k" +L["wep." .. w .. ".name.full"] = "HK HK243" +L["wep." .. w .. ".name"] = "HK243" +L["wep." .. w .. ".desc"] = "Halvautomatisk modell av ett ikoniskt gevär av polymer. \nAnvänder sänkt kapacitetsmagasin." +L["wep." .. w .. ".desc.quote"] = "\"Krigsvapen tillhör inte på våra gator!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "civ_m320" +L["wep." .. w .. ".name.full"] = "HK M320LE" +L["wep." .. w .. ".name"] = "M320LE" +L["wep." .. w .. ".desc"] = "Ordningsmaktsversion av M320 santkionerad till icke-dödlig ammunition. Skjuter sittsäck kulor som oförmögnar vid direkt träff." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "civ_mp5" +L["wep." .. w .. ".name.full"] = "HK HK94" +L["wep." .. w .. ".name"] = "HK94" +L["wep." .. w .. ".desc"] = "Halvautomatisk modell av en legendarisk kulsprutepistol. \nAnvänder sänkt kapacitetsmagasin." +L["wep." .. w .. ".desc.quote"] = "Ses ofta som ersättning för dess militära motsvarighet." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention \nRedigerad Modell: speedonerd \n(det var sååå svårt lol)" + +w = ws .. "civ_p90" +L["wep." .. w .. ".name.full"] = "FN PS90" +L["wep." .. w .. ".name"] = "PS90" +L["wep." .. w .. ".desc"] = "Halvautomatisk variant av en framtidsrik PDW. \nAnvänder sänkt kapcitetsmagasin." +L["wep." .. w .. ".desc.quote"] = "\"Detta är ett terrorvapen. Den är gjord för att skrämma fienden.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention \nRedigerad Modell: speedonerd \n(det var sååå svårt lol)" + +w = ws .. "dsa58" +L["wep." .. w .. ".name.full"] = "DSA SA58 OSW" +L["wep." .. w .. ".name"] = "SA58" +L["wep." .. w .. ".desc"] = "Stridsgevär med låg eldhastighet men väldigt hög skada och penetration. Har ett grepp med skjutstöd som erbjuder stabilitet om den är utplacerad." +L["wep." .. w .. ".desc.quote"] = "\"Håll käften, klocka in och ladda på.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "DS Arms" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "ex_ak47" +L["wep." .. w .. ".name"] = "AK-47" +L["wep." .. w .. ".desc"] = "Ikonisk sovjetisk automatkarbin. Grovhuggen och simpel design som inspirerat otaligt antal kloner och derivat." +L["wep." .. w .. ".desc.quote"] = "Typiska \"skurk\" vapnet." +L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta, PoisonHeadcrab, Steelbeast \nTexturer: Millenia, IppE, FxDarkloki, Pete \nLjud: CC5, modderfreak, .exe \nAnimationer: Tactical Intervention" + +w = ws .. "ex_glock" +L["wep." .. w .. ".name"] = "Glock 17" +L["wep." .. w .. ".desc"] = "Pistol av polymer med större än normal kapacitet och snabb eldhastighet." +L["wep." .. w .. ".desc.quote"] = "Visas inte i metalldetektorer på flygplatser." +L["wep." .. w .. ".trivia.manufacturer"] = "Glock Ges.m.b.H" +L["wep." .. w .. ".credits"] = "Tillgångar: Twinke Masta \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "ex_hecate" +L["wep." .. w .. ".name.full"] = "PGM Hécate II" +L["wep." .. w .. ".name"] = "Hécate II" +L["wep." .. w .. ".desc"] = "Tungt antimateriellgevär som kan döda med ett skott. \nUtrustat med ett 12x kikarsikte som standard. \nLätt nog att svänga för närstridsanfall." +L["wep." .. w .. ".desc.quote"] = "Testad av Gun Runners, godkänd av NCR." +L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +L["wep." .. w .. ".credits"] = "Tillgångar: Toby Burnside \nKälla: Fallout 4: New Vegas Project" + +w = ws .. "ex_hk45c" +L["wep." .. w .. ".name.full"] = "HK HK45 Compact" +L["wep." .. w .. ".name"] = "HK45C" +L["wep." .. w .. ".desc"] = "Modern hög kaliber pistol med hög stoppkraft i ett kompakt paket." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: B0t, Red Crayon \nTexturer: IppE, Grall19 \nLjud: DMG, xLongWayHome, Leeroy Newman \nAnimationer: Tactical Intervention" + +w = ws .. "ex_m4a1" +L["wep." .. w .. ".name.full"] = "Colt M4A1" +L["wep." .. w .. ".name"] = "M4A1" +L["wep." .. w .. ".desc"] = "En sann amerikansk klassiker med hög eldhastighet och balanserad prestanda." +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Twinke Masta, DMG \nLjud: Teh Strelok \nAnimationer: Tactical Intervention" + +w = ws .. "ex_m1911" +L["wep." .. w .. ".name.full"] = "Colt M1911" +L["wep." .. w .. ".name"] = "M1911" +L["wep." .. w .. ".desc"] = "Överskottspistol från en era innan taktiska tillbehör och pistolsikten, men är ändå lika kraftfull." +L["wep." .. w .. ".desc.quote"] = "\"Hasta la vista, baby!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Twinke Masta, DMG \nLjud: xLongWayHome, Strelok, Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "ex_mac10" +L["wep." .. w .. ".name.full"] = "Ingram MAC-11" +L["wep." .. w .. ".name"] = "MAC-11" +L["wep." .. w .. ".desc"] = "En kulslang som bäst används i \"spray-and-pray\"-situationer." +L["wep." .. w .. ".desc.quote"] = "\"Ge mig förbannade vapnet, Tre!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Military Armament Corporation" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Enron \nLjud: Vunsunta, Erick F \nAnimationer: Tactical Intervention" + +w = ws .. "ex_mp9" +L["wep." .. w .. ".name.full"] = "BT MP9" +L["wep." .. w .. ".name"] = "MP9" +L["wep." .. w .. ".desc"] = "Kompakt kulsprutepistol i polymer har hög stoppkraft i ett litet paket." +L["wep." .. w .. ".desc.quote"] = "\"Är högra handen avtagbar?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Brügger & Thomet" +L["wep." .. w .. ".credits"] = "Tillgångar: Logger & The Expert \nKälla: Gamebanana" + +w = ws .. "ex_stinger" +L["wep." .. w .. ".name.full"] = "FIM-92 Stinger" +L["wep." .. w .. ".name"] = "Stinger" +L["wep." .. w .. ".desc"] = "Sökande luftvärnsrobot. Hög sprängskada men begränsad effekt på pansrade måltavlor. \nKräver lås för att kunna skjuta." +L["wep." .. w .. ".desc.quote"] = "\"En räv intryckt i en vägg är farligare än en schakal!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Raytheon Missiles and Defense" +L["wep." .. w .. ".credits"] = "Tillgångar: Modern Warfare 2 \nAnimationer: Tactical Intervention" + +w = ws .. "ex_ump45" +L["wep." .. w .. ".name.full"] = "HK UMP45" +L["wep." .. w .. ".name"] = "UMP45" +L["wep." .. w .. ".desc"] = "Boxig KPist. utvecklad med låg produktionskostnad. \nHög skada på nära håll, men räckvidden och eldhastigheten är låg." +L["wep." .. w .. ".desc.quote"] = "\"Fan är dessa för cocktail-vapen?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Hellspike, Logger & Cyper \nKälla: Gamebanana" + +w = ws .. "ex_usp" +L["wep." .. w .. ".name.full"] = "HK USP" +L["wep." .. w .. ".name"] = "USP" +L["wep." .. w .. ".desc"] = "Taktisk pistol med bra skada och räckvidd." +L["wep." .. w .. ".desc.quote"] = "\"En man av få ord, eller hur?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Thanez, Racer445 \nTexturer: Thanez, fxdarkloki \nLjud: Vunsunta, BlitzBoaR \nAnimationer: Tactical Intervention" + +w = ws .. "fp6" +L["wep." .. w .. ".name.full"] = "HK FABARM FP6" +L["wep." .. w .. ".name"] = "FABARM FP6" +L["wep." .. w .. ".desc"] = "Stridshagelbössa med hög eldhastighet och kapacitet." +L["wep." .. w .. ".trivia.manufacturer"] = "FABARM S.p.A." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "g36k" +L["wep." .. w .. ".name.full"] = "HK G36K" +L["wep." .. w .. ".name"] = "G36K" +L["wep." .. w .. ".desc"] = "Automatkarbin med hög mynningshastighet. Perfekt för kontinuerlig eld på medeldistans. \nUtrustat med ett 2x sikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "gsr1911" +L["wep." .. w .. ".name.full"] = "SIG 1911 TACOPS" +L["wep." .. w .. ".name"] = "SIG 1911" +L["wep." .. w .. ".desc"] = "Pistol med hög skada och låg räckvidd och kapacitet. \nEn taktisk evolution (eller som några kallar det - degeneration) av en ärevördig klassiker." +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer, Inc." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "hk417" +L["wep." .. w .. ".name.full"] = "HK HK417" +L["wep." .. w .. ".name"] = "HK417" +L["wep." .. w .. ".desc"] = "Stridsgevär med utmärkt skada, eldhastighet och precision. Möjlighet till automateld, även om det är ostabilt." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "k1a" +L["wep." .. w .. ".name.full"] = "Daewoo K1A" +L["wep." .. w .. ".name"] = "K1A" +L["wep." .. w .. ".desc"] = "Gevär med salvoeld och minimal rekyl och bra träffsäkerhet från höften." +L["wep." .. w .. ".trivia.manufacturer"] = "Daewoo Precision" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "knife" +L["wep." .. w .. ".name"] = "Fällkniv" +L["wep." .. w .. ".desc"] = "Mångsidig fällkniv, även om dem flesta sidorna handlar om att knivhugga någon." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "knife2" +L["wep." .. w .. ".name"] = "Schakalkniv" +L["wep." .. w .. ".desc"] = "Väldigt kantig kniv. Lätt, delvist skelettiserat blad gör det snabbare att använda, men gör mindre skada." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention (Unused)" + +w = ws .. "ks23" +L["wep." .. w .. ".name"] = "KS-23" +L["wep." .. w .. ".desc"] = "Gjort av återanvända pipor från flygplan, denna tunga hagelbössan använder patroner med dubbelt så stora hagelpatroner och kan lätt ta sönder allt som den siktas mot. Har möjlighet att bryta sönder dörrar." +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +L["wep." .. w .. ".credits"] = "Tillgångar: Firearms: Source" + +w = ws .. "m1" +L["wep." .. w .. ".name.full"] = "Ruger Mini-14" +L["wep." .. w .. ".name"] = "Mini-14" +L["wep." .. w .. ".desc"] = "Ett lätt gevär utan kolv eller möjlighet till sikte. \nBra träffsäkerhet från höften bland gevär, men räckvidden är låg." +L["wep." .. w .. ".desc.quote"] = "\"Den med vapnet får berätta sanningen.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "m4" +L["wep." .. w .. ".name.full"] = "Diemaco C8A1" +L["wep." .. w .. ".name"] = "C8A1" +L["wep." .. w .. ".desc"] = "En nära kusin till det klassiska amerikanska geväret med en lägre men mer kontrollerbar eldhastighet." +L["wep." .. w .. ".desc.quote"] = "Grön och väldigt, väldigt elak." +L["wep." .. w .. ".trivia.manufacturer"] = "Colt Canada" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "m4star10" +L["wep." .. w .. ".name.full"] = "Benelli M4 Super 90" +L["wep." .. w .. ".name"] = "M4 Super 90" +L["wep." .. w .. ".desc"] = "Halvautomatisk hagelbössa med väldigt hög stoppkraft. Omladdning kan ta sin tid." +L["wep." .. w .. ".desc.quote"] = "Finns inget som sju kaliber 12 hagelpatroner inte kan lösa." +L["wep." .. w .. ".trivia.manufacturer"] = "Benelli Armi S.p.A." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "m14" +L["wep." .. w .. ".name.full"] = "Springfield M1A" +L["wep." .. w .. ".name"] = "M1A" +L["wep." .. w .. ".desc"] = "Halvautomatiskt gevär med dödliga huvudskott. \nUtrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Detta är mitt gevär! Det finns många likt den men denna är min!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "m320" +L["wep." .. w .. ".name.full"] = "HK M320" +L["wep." .. w .. ".name"] = "M320" +L["wep." .. w .. ".desc"] = "Granattillsats med möjlighet att skjuta olika sorters granater." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "medkit" +L["wep." .. w .. ".name"] = "Förbandslåda" +L["wep." .. w .. ".desc"] = "Kompakt låda med medicinska förnödenheter för att behandla sår." +L["wep." .. w .. ".desc.quote"] = "\"Håll dig stilla, låt mig behandla dig.\"" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Left 4 Dead 2 \nAnimationer: Arqu" + +w = ws .. "mg4" +L["wep." .. w .. ".name.full"] = "HK MG4" +L["wep." .. w .. ".name"] = "MG4" +L["wep." .. w .. ".desc"] = "Kulspruta med hög eldvolym." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "mp5" +L["wep." .. w .. ".name.full"] = "HK MP5A3" +L["wep." .. w .. ".name"] = "MP5A3" +L["wep." .. w .. ".desc"] = "Välbalanserad kulsprutepistol känd för dess precision." +L["wep." .. w .. ".desc.quote"] = "\"Nu har jag en kulspruta. Ho, ho, ho.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "mp7" +L["wep." .. w .. ".name.full"] = "HK MP7" +L["wep." .. w .. ".name"] = "MP7" +L["wep." .. w .. ".desc"] = "En PDW med utmärkt hantering och effektivitet på nära distans. Patroner med hög hastighet behåller effektivitet vid räckvidd och penetrerar lätt rustning." +L["wep." .. w .. ".desc.quote"] = "\"Du glömde ladda den, din dumme jävel!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "mr96" +L["wep." .. w .. ".name.full"] = "Manurhin MR96" +L["wep." .. w .. ".name"] = "MR96" +L["wep." .. w .. ".desc"] = "Magnum-revolver med bra hantering och stoppkraft. Träffsäkerhet, men svår att skjuta snabbt." +L["wep." .. w .. ".trivia.manufacturer"] = "Chapuis Armes" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "mtx_dual" +L["wep." .. w .. ".name"] = "Dual MTX" +L["wep." .. w .. ".desc"] = "Eftergivet par av kompakta pistoler med hög kapacitet, hög skada och av hög kvalitet." +L["wep." .. w .. ".desc.quote"] = "With firepower like this, who needs aiming?" +L["wep." .. w .. ".trivia.manufacturer"] = "Detonics Defense" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "nade_charge" +L["wep." .. w .. ".name.full"] = "Dörrbrytare Laddning" + +w = ws .. "nade_flashbang" +L["wep." .. w .. ".name.full"] = "Chockgranat" + +w = ws .. "nade_frag" +L["wep." .. w .. ".name.full"] = "Splittergranat" + +w = ws .. "nade_gas" +L["wep." .. w .. ".name.full"] = "Tårgasgranat" + +w = ws .. "nade_heal" +L["wep." .. w .. ".name.full"] = "Medi-Rök Kanister" + +w = ws .. "nade_smoke" +L["wep." .. w .. ".name.full"] = "Rökgranat" + +w = ws .. "nade_thermite" +L["wep." .. w .. ".name"] = "Termitgranat" + +w = ws .. "p90" +L["wep." .. w .. ".name.full"] = "FN P90" +L["wep." .. w .. ".name"] = "P90" +L["wep." .. w .. ".desc"] = "En kontrollerbar bullpup PDW med toppladdat magasin. Patroner med hög hastighet behåller effektivitet vid räckvidd och penetrerar lätt rustning." +L["wep." .. w .. ".desc.quote"] = "\"Detta är ett krigsvapen, den är gjord för att döda din fiende.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "p250" +L["wep." .. w .. ".name.full"] = "SIG P250" +L["wep." .. w .. ".name"] = "P250" +L["wep." .. w .. ".desc"] = "Kraftfullt sidovapen som byter kapacitet mot skada och precision." +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer, Inc." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "p2000" +L["wep." .. w .. ".name.full"] = "HK P2000" +L["wep." .. w .. ".name"] = "P2000" +L["wep." .. w .. ".desc"] = "Balanserad och standard sidovapen till polisen." +L["wep." .. w .. ".desc.quote"] = "\"Raus! Raus! Raus!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "pdw" +L["wep." .. w .. ".name"] = "KAC PDW" +L["wep." .. w .. ".desc"] = "Karbinkaliber delkompakt PDW. Perfekt blandning av gevär och kulsprutepistol." +L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "riot_shield" +L["wep." .. w .. ".name"] = "Kravallsköld" +L["wep." .. w .. ".desc"] = "Lätt sköld. Trots dess plast-liknande kärna så har den möjligheten att stoppa nästan alla gevärskaliber. \nMöjlighet att springa och anfalla utan att kompromissa användarens säkerhet, men saktar ned rörelsen en bit." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Tactical Intervention \nAnimationer: Arqu" + +w = ws .. "rpg7" +L["wep." .. w .. ".name"] = "RPG-7" +L["wep." .. w .. ".desc"] = "Sovjetisk raketgevär med kraftfull sprängladdning. \nFördröjd säkring förhindrar detonering på nära håll." +L["wep." .. w .. ".desc.quote"] = "Om du hör någon som skriker dess namn, ducka... omedelbart." +L["wep." .. w .. ".trivia.manufacturer"] = "NPO Bazalt" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "sg551" +L["wep." .. w .. ".name.full"] = "SIG SG 551" +L["wep." .. w .. ".name"] = "SG 551" +L["wep." .. w .. ".desc"] = "Automatkarbin med generellt utmärkt prestanda, dock med lägre magasinkapacitet." +L["wep." .. w .. ".desc.quote"] = "\"Inga frågor, inga svar. Det är den sortens affärer vi är i.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "skorpion" +L["wep." .. w .. ".name.full"] = "Skorpion vz. 61" +L["wep." .. w .. ".name"] = "Skorpion" +L["wep." .. w .. ".desc"] = "En lätt automatpistol med bra räckvidd, rekyl och spridning." +L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "sphinx" +L["wep." .. w .. ".name.full"] = "Sphinx 2000" +L["wep." .. w .. ".name"] = "Sphinx" +L["wep." .. w .. ".desc"] = "En premium pistol anpassad till 3-skottsalvo. Hög eldhastighet men lång salvofördröjning." +L["wep." .. w .. ".trivia.manufacturer"] = "Sphinx Systems" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "spr" +L["wep." .. w .. ".name.full"] = "Remington M700 SPS" +L["wep." .. w .. ".name"] = "R700 SPS" +L["wep." .. w .. ".desc"] = "Jaktgevär för medeldistans med snabb repeterhastighet. \nUtrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "superv" +L["wep." .. w .. ".name.full"] = "Kriss Vector" +L["wep." .. w .. ".name"] = "Vector" +L["wep." .. w .. ".desc"] = "Kulsprutepistol för nära distanser med extremt hög eldhastighet och praktiskt taget noll rekyl. Låg penetration, men kan väldigt snabbt tugga sig genom." +L["wep." .. w .. ".trivia.manufacturer"] = "Kriss USA, Inc." +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "tgs12" +L["wep." .. w .. ".name.full"] = "Tomahawk Matador" +L["wep." .. w .. ".name"] = "Matador" +L["wep." .. w .. ".desc"] = "Hagelbössa med kort pipa och pistolgrepp. Hög rörlighet och rekyl, och bäst effektiv på nära håll." +L["wep." .. w .. ".desc.quote"] = "Dess sanna identitet har i åratal varit ett mysterium." +L["wep." .. w .. ".trivia.manufacturer"] = "Tomahawk Shotguns" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "uratio" +L["wep." .. w .. ".name.full"] = "PGM Ultima Ratio" +L["wep." .. w .. ".name"] = "Ultima Ratio" +L["wep." .. w .. ".desc"] = "Lätt krypskyttegevär med bra skada och hög rörlighet. \nUtrustat med ett 10x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "uzi" +L["wep." .. w .. ".name.full"] = "IMI Uzi Pro" +L["wep." .. w .. ".name"] = "Uzi Pro" +L["wep." .. w .. ".desc"] = "Balanserad automatpistol med kontrollerbar eldhastighet." +L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "vertec" +L["wep." .. w .. ".name.full"] = "Beretta 92FS Vertec" +L["wep." .. w .. ".name"] = "92FS Vertec" +L["wep." .. w .. ".desc"] = "Italiensk pistol med över genomsnittet räckvidd och träffsäkerhet." +L["wep." .. w .. ".desc.quote"] = "\"Yippie ki-yay, motherfucker!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "xd45" +L["wep." .. w .. ".name.full"] = "Springfield XD-45" +L["wep." .. w .. ".name"] = "XD-45" +L["wep." .. w .. ".desc"] = "Automatpistol med otrolig stoppkraft på nära håll." +L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +-- Attachments (Non-Bulk) +L["att.melee_spec_charge.name.full"] = "demoknight tf2" +L["att.melee_spec_charge.name"] = "Anfall" +L["att.melee_spec_charge.desc"] = "Avancera med hänsynslöst överge, och ta sönder fysikens lagar också." +L["att.pro.melee_spec_charge1"] = "LADDA OM: Anfall" +L["att.pro.melee_spec_charge2"] = "GÅ + LADDA OM: Välj Anfallsläge" +L["att.pro.melee_spec_charge3"] = "Sänkt Skada under Anfall" +L["att.pro.melee_spec_charge4"] = "Sänkt Självskada" + +L["att.melee_spec_lunge.name"] = "Galen" +L["att.melee_spec_lunge.desc"] = "Sänk avståndet och överväldiga dina fiender." +L["att.pro.melee_spec_lunge1"] = "LADDA OM: Longera mot Blickriktning" + +L["att.melee_spec_nade.name.full"] = "Bombardär" +L["att.melee_spec_nade.name"] = "Bombare" +L["att.melee_spec_nade.desc"] = "Använd juryriggade kontaktgranater för att krossa någons dag." +L["att.pro.melee_spec_nade1"] = "LADDA OM: Växla Granater" +L["att.pro.melee_spec_nade2"] = "Granater Sprängs vid Kontakt" +L["att.con.melee_spec_nade"] = "Granatskada" + +L["att.melee_spec_ninja.name"] = "Ninja" +L["att.melee_spec_ninja.desc"] = "Stör dina fiender och anfall med överraskningsmoment." +L["att.pro.melee_spec_ninja1"] = "LADDA OM: Palm Anfall / Klättra" +L["att.pro.melee_spec_ninja2"] = "LADDA OM: (I luften + Hukad): Dykspark" +L["att.pro.melee_spec_ninja3"] = "LADDA OM: (Hukad): Tillbakahopp" +L["att.pro.melee_spec_ninja4"] = "Tysta Fotsteg" + +L["att.melee_spec_scout.name.full"] = "jerma tf2" +L["att.melee_spec_scout.name"] = "Spanare" +L["att.melee_spec_scout.desc"] = "Gräs växer, solen skiner, fåglar flyger, och polar'n - jag skadar folk." +L["att.pro.melee_spec_scout1"] = "LADDA OM: Skjut Boll" +L["att.pro.melee_spec_scout2"] = "Bollens Skada Skalar med Avstånd" +L["att.pro.melee_spec_scout3"] = "Bollen Saktar Ned Måltavlor vid Träff" +L["att.pro.melee_spec_scout4"] = "Multi-Hopp" + +L["att.melee_spec_step.name"] = "Lufthopp" +L["att.melee_spec_step.desc"] = "Rörlighetsverktyg som används av blodtörstiga robotar och transkvinnor." +L["att.pro.melee_spec_step1"] = "LADDA OM: Rusa i Rörelseriktning" +L["att.pro.melee_spec_step2"] = "Odödlig under Rusning" + +L["att.melee_tech_block.name.full"] = "Högvakt" +L["att.melee_tech_block.name"] = "Vakt" +L["att.melee_tech_block.desc"] = "Försvar är den bästa offensiven. Det är också, av en slump, bästa försvaret." +L["att.pro.melee_tech_block1"] = "ALT-ELD: Blockera Närstridsanfall eller Projektiler" +L["att.pro.melee_tech_block2"] = "Tung Motanfall Efter Lyckad Blockering" + +L["att.melee_tech_heavy.name.full"] = "Tunghänt" +L["att.melee_tech_heavy.name"] = "Tung" +L["att.melee_tech_heavy.desc"] = "En klassisk antiterrorist teknik. Slå dem rejält hårt." +L["att.pro.melee_tech_heavy1"] = "ALT-ELD: Tungt Anfall" +L["att.pro.melee_tech_heavy2"] = "Skada i Ryggen" + +L["att.melee_tech_nade.name.full"] = "Grenadjär" +L["att.melee_tech_nade.name"] = "Granat" +L["att.melee_tech_nade.desc"] = "Alltid bra att ha något snabbt att kasta." +L["att.pro.melee_tech_nade1"] = "ALT-ELD: Snabbkast" +L["att.pro.melee_tech_nade2"] = "Snabbare Snabbkast" +L["att.pro.melee_tech_nade3"] = "Kasta Stenar" + +L["att.melee_tech_throw.name.full"] = "Knivkast" +L["att.melee_tech_throw.name"] = "Kast" +L["att.melee_tech_throw.desc"] = "Ett bartrick har blivit dödligt." +L["att.pro.melee_tech_throw1"] = "ALT-ELD: Kasta Vapnet" +L["att.pro.melee_tech_throw2"] = "Använder Armborstpil Ammo" +L["att.pro.melee_tech_throw3"] = "Bonus Skada i Huvudet" +L["att.pro.melee_tech_throw4"] = "Bonus Skada mot Saktade Ned/Chockade Måltavlor" + +-- Attachments (acc) +L["att.acc_bipod.name"] = "Skjutstöd" +L["att.acc_bipod.desc"] = "Vikbart stöd som stabiliserar vapnet när den är utplacerad." + +L["att.acc_brace.name"] = "Pistol \"Brace\"" +L["att.acc_brace.desc"] = "Förvandlar pistolen till ett gevär. ATF kommer förfölja dig." + +L["att.acc_cheekrest.name"] = "Kindskydd" +L["att.acc_cheekrest.desc"] = "Stabiliserar huvudet i siktet och sänker svängningen." + +L["att.acc_dual_ergo.name.full"] = "Ergonomiskt Grepp" +L["att.acc_dual_ergo.name"] = "Ergo. Grepp" +L["att.acc_dual_ergo.desc"] = "Räfflat grepp gör det lättare att förflytta sig när man skjuter två vapen." + +L["att.acc_dual_quickdraw.name.full"] = "Snabbt Framtagen Hölster" +L["att.acc_dual_quickdraw.name"] = "Framtag" +L["att.acc_dual_quickdraw.desc"] = "Ett par axelbandslösa hölstrar för att snabbt ta fram vapnen och öka laddningstiden." + +L["att.acc_dual_skel.name.full"] = "Lätt Grepp" +L["att.acc_dual_skel.name"] = "Lätt Grepp" +L["att.acc_dual_skel.desc"] = "Skelettgrepp gör vapnen lättare att förflytta sig med." + +L["att.acc_duffelbag.name"] = "Vapenväska" +L["att.acc_duffelbag.desc"] = "Göm vapnet i en väska så du inte orsakar masspanik." + +L["att.acc_ergo.desc"] = "Räfflat grepp gör att gå in och ur siktet snabbare och förflyttning under eld lättare." + +L["att.acc_extendedbelt.name.full"] = "Lådeförlängare" +L["att.acc_extendedbelt.name"] = "Lådeförl." +L["att.acc_extendedbelt.desc"] = "Ökar ammo kapaciteten drastiskt i kulsprutor." + +L["att.acc_extmag.name.full"] = "Förlängt Magasin" +L["att.acc_extmag.name"] = "Förl. Mag" +L["att.acc_extmag.desc"] = "Ökar lätt vapnets kapacitet." + +-- L["att.acc_extmag_shotgun_tube.name.full"] = "Tube Extender" +-- L["att.acc_extmag_shotgun_tube.name"] = "Tube Ext." +-- L["att.acc_extmag_shotgun_tube.desc"] = "Slightly increase weapon capacity." + +L["att.acc_foldstock.name"] = "Vikt Kolv" +L["att.acc_foldstock.desc"] = "Håller kolven vikt vilket förbättrar hanteringen drastiskt med kostnad på rekyl." + +L["att.acc_foldstock2.name.full"] = "Justerbar Kolv" +L["att.acc_foldstock2.name"] = "Justerbar" +L["att.acc_foldstock2.desc"] = "Korta ned kolven för att öka hanteringen en bit med kostnad på rekyl." + +L["att.acc_pad.name"] = "Rekylplatta" +L["att.acc_pad.desc"] = "Gummiplatta utrustat på kolvens ände." + +L["att.acc_quickdraw.desc"] = "Axelbandslös hölster med magasinhållare för snabb framtagning och laddning." + +L["att.acc_skel.desc"] = "Skelettgrepp gör vapnet snabbare att ta fram och hålla uppe." + +L["att.acc_sling.name"] = "Slinga" +L["att.acc_sling.desc"] = "Utrusta en slinga på vapnet som gör det lättare att ta fram och ladda om." + +-- Attachments (ammo) +L["att.ammo_40mm_3gl.name.full"] = "40 mm Klustergranater" +L["att.ammo_40mm_3gl.name"] = "3GT" +L["att.ammo_40mm_3gl.desc"] = "Tre svaga klustergranater som skjuts på en gång." + +L["att.ammo_40mm_buck.name.full"] = "40 mm Hagelskottgranater" +L["att.ammo_40mm_buck.name"] = "Hagelskott" +L["att.ammo_40mm_buck.desc"] = "Granat med flat topp som skjuter hagelskott. Svag tack vare den låga tryck kurvan." + +L["att.ammo_40mm_gas.name.full"] = "40 mm Tårgasgranater" +L["att.ammo_40mm_gas.name"] = "Tårgas" +L["att.ammo_40mm_gas.desc"] = "Granat som innehåller publikkontrollerande kemikalier som gör långvarig skada." + +L["att.ammo_40mm_heat.name.full"] = "40 mm Flechette Granater" +L["att.ammo_40mm_heat.name"] = "Flechette" +L["att.ammo_40mm_heat.desc"] = "Granat med flat topp som har träffsäkra flechettepilar." + +L["att.ammo_40mm_lvg.name.full"] = "40 mm Chockgranater" +L["att.ammo_40mm_lvg.name"] = "Chock" +L["att.ammo_40mm_lvg.desc"] = "Granat med låg hastighet gjord för att oförmögna mål med indirekt eld." + +L["att.ammo_40mm_ratshot.name.full"] = "40 mm Råttskott Granater" +L["att.ammo_40mm_ratshot.name"] = "Råttskott" +L["att.ammo_40mm_ratshot.desc"] = "Mot gnagare av otrolig storlek." + +L["att.ammo_40mm_smoke.name.full"] = "40 mm Rökgranater" +L["att.ammo_40mm_smoke.name"] = "Rök" +L["att.ammo_40mm_smoke.desc"] = "Rök som producerar en täckande rökskärm vid träff." + +L["att.ammo_40mm_heal.name.full"] = "40 mm Medi-Rökgranater" +L["att.ammo_40mm_heal.name"] = "Medi-Rök" +L["att.ammo_40mm_heal.desc"] = "Granater som producerar ett moln av stärkande gas vid träff." + +L["att.ammo_amr_hv.name.full"] = "Höghastighets Skott" +L["att.ammo_amr_hv.name"] = "HH" +L["att.ammo_amr_hv.desc"] = "Skott med mycket högre hastighet, men försämrar överpenetrationen." + +L["att.ammo_amr_ratshot.name.full"] = "Råttskott" +L["att.ammo_amr_ratshot.name"] = "Råttskott" +L["att.ammo_amr_ratshot.desc"] = "Mot gnagare av ovanlig storlek." + +L["att.ammo_amr_saphe.name.full"] = "Halvt Pansarbrytande Splitterskott" +L["att.ammo_amr_saphe.name"] = "HPBS" +L["att.ammo_amr_saphe.desc"] = "Explosiva skott effektiva mot både pansar och personal." + +L["att.ammo_ks23_flashbang.name.full"] = "KS-23 Zvezda Bländskott" +L["att.ammo_ks23_flashbang.name"] = "Zvezda" +L["att.ammo_ks23_flashbang.desc"] = "Chockgranatskott som chockar fiender, direkt från pipan." + +L["att.ammo_ks23_flashbang_top.name.full"] = "KS-23 Zvezda Bländskott (Toppladdad)" +L["att.ammo_ks23_flashbang_top.name"] = "Zvezda (T)" +L["att.ammo_ks23_flashbang_top.desc"] = "Ladda första skottet med bländskott, resterande med hagelskott." + +L["att.ammo_magnum.name.full"] = "Övertryckta Skott" +L["att.ammo_magnum.name"] = "+T" +L["att.ammo_magnum.desc"] = "Skott som behåller stoppkraft på nära håll bättre, men har högre rekyl." + +L["att.ammo_pistol_ap.name.full"] = "Skott med Stålkärna" +L["att.ammo_pistol_ap.name"] = "Stålkärna" +L["att.ammo_pistol_ap.desc"] = "Härdade skott som bättre penetrerar rustning, men destabiliserar ballistik." + +L["att.ammo_pistol_headshot.name.full"] = "\"Skullsplitter\"-Skott" +L["att.ammo_pistol_headshot.name"] = "Skullsplitter" +L["att.ammo_pistol_headshot.desc"] = "Specialskott som gör mer skada på livsviktiga kroppsdelar." + +L["att.ammo_pistol_hollowpoints.name.full"] = "Hålspetsskott" +L["att.ammo_pistol_hollowpoints.name"] = "HS" +L["att.ammo_pistol_hollowpoints.desc"] = "Skott som ökar sig vid träff och förbättrar skadan mot köttmåltavlor och lem." + +L["att.ammo_rifle_jhp.name.full"] = "Jackade Hålspetsskott" +L["att.ammo_rifle_jhp.name"] = "JHS" +L["att.ammo_rifle_jhp.desc"] = "Skott med mycket högre hastighet, men sämre överpenetration." + +L["att.ammo_pistol_match.name.full"] = "Match-Pistolskott" +L["att.ammo_pistol_match.name"] = "Match" +L["att.ammo_pistol_match.desc"] = "Skott med förbättrad räckvidd och träffsäkerhet." + +L["att.ammo_rifle_match.name.full"] = "Match-Gevärsskott" +L["att.ammo_rifle_match.name"] = "Match" +L["att.ammo_rifle_match.desc"] = "Skott med mycket förbättrad träffsäkerhet." + +L["att.ammo_roulette.name.full"] = "Rysk Roulett" +L["att.ammo_roulette.name"] = "Roulett" +L["att.ammo_roulette.desc"] = "En dödlig chanslek. Snurra cylindern medans den är laddad för att återställa oddsen." + +L["att.ammo_rpg_improvised.name.full"] = "RPG-7 Improviserad Granat" +L["att.ammo_rpg_improvised.name"] = "Improviserad" +L["att.ammo_rpg_improvised.desc"] = "Direkt från rabatt tunnan." + +L["att.ammo_rpg_mortar.name.full"] = "RPG-7 Mortelgranatkastare" +L["att.ammo_rpg_mortar.name"] = "Mortel" +L["att.ammo_rpg_mortar.desc"] = "En mortelkastare med booster på den, för \"odirekt eld\". Kräver tid att aktivera." + +L["att.ammo_rpg_ratshot.name.full"] = "RPG-7 Råttskott Granat" +L["att.ammo_rpg_ratshot.name"] = "Råttskott" +L["att.ammo_rpg_ratshot.desc"] = "Mot gnagare av oacceptabel storlek." + +L["att.ammo_rpg_harpoon.name.full"] = "RPG-7 Spade \"Granat\"" +L["att.ammo_rpg_harpoon.name"] = "Spade" +L["att.ammo_rpg_harpoon.desc"] = "På något sätt så skjuter den spadar. Antingen är du galen, slut på granater, eller både och." + +L["att.ammo_shotgun_bird.name"] = "Fågelskott" +L["att.ammo_shotgun_bird.desc"] = "Skjuter mindre skott med större spridning." + +L["att.ammo_shotgun_mag.name.full"] = "Magnum-Hagelskott" +L["att.ammo_shotgun_mag.name"] = "Magnum" +L["att.ammo_shotgun_mag.desc"] = "Högavkastande krut förbättrar skadans behållande förbi \"point blank\"." + +L["att.ammo_shotgun_slugs.name.full"] = "Sluggpatroner" +L["att.ammo_shotgun_slugs.name"] = "Slugg" +L["att.ammo_shotgun_slugs.desc"] = "Skjuter en enda projektil för eld på medelräckvidd." + +L["att.ammo_shotgun_triple.name.full"] = "Trippelträff Patroner" +L["att.ammo_shotgun_triple.name"] = "Trippelträff" +L["att.ammo_shotgun_triple.desc"] = "Skjuter tre projektiler för ökad träffsäkerhet." + +L["att.ammo_subsonic.name.full"] = "Subsoniska Patroner" +L["att.ammo_subsonic.name"] = "Subsonisk" +L["att.ammo_subsonic.desc"] = "Patroner med sänkt krutladdning." + +L["att.ammo_surplus.name.full"] = "Överskottspatroner" +L["att.ammo_surplus.name"] = "Överskott" +L["att.ammo_surplus.desc"] = "Opålitlig gammal ammunition, även om du lyckas hitta dem överallt." + +L["att.ammo_tmj.name.full"] = "\"Total Metal Jacket\"-Patroner" +L["att.ammo_tmj.name"] = "TMJ" +L["att.ammo_tmj.desc"] = "Patroner med förbättrad penetrationsförmåga." + +L["att.ammo_buckshotroulette.name.full"] = "Hagelskott Roulett" +L["att.ammo_buckshotroulette.name"] = "H. Roulett" +L["att.ammo_buckshotroulette.desc"] = "Patronerna går in i kammaren i en okänd ordning." + +L["att.ammo_shotgun_minishell.name.full"] = "Miniskott" +L["att.ammo_shotgun_minishell.name"] = "Mini" +L["att.ammo_shotgun_minishell.desc"] = "Korta patroner ökar kapaciteten men träffar inte lika hårt." + +L["att.ammo_shotgun_dragon.name.full"] = "\"Dragon's Breath\"" +L["att.ammo_shotgun_dragon.name"] = "\"Dragon\"" +L["att.ammo_shotgun_dragon.desc"] = "Magnesiumskott tänder fyr på måltavlor, men har dålig räckvidd och skada." + +L["att.ammo_shotgun_frag.name.full"] = "H.E.-Splittergranatskott" +L["att.ammo_shotgun_frag.name"] = "Splitter" +L["att.ammo_shotgun_frag.desc"] = "Explosiva sluggskott gör områdesskada, men förvänta inte för mycket av dem." + +L["att.ammo_shotgun_breach.name.full"] = "Dörrbrytande Skott (Toppladdad)" +L["att.ammo_shotgun_breach.name"] = "Bryt (T)" +L["att.ammo_shotgun_breach.desc"] = "Ladda första skottet med en speciell dörrbrytande sluggpatron." + +L["att.ammo_stinger_saam.name.full"] = "FIM-92 Stinger Halvt Aktiv Robot" +L["att.ammo_stinger_saam.name"] = "Halvt" +L["att.ammo_stinger_saam.desc"] = "Kraftfull robot får lås snabbt men kräver konstant guidning." + +L["att.ammo_stinger_qaam.name.full"] = "FIM-92 Stinger Hög Rörlighetsrobot" +L["att.ammo_stinger_qaam.name"] = "Rörlig" +L["att.ammo_stinger_qaam.desc"] = "Robot med hög rörlighet med kort räckvidd och lång låstid." + +L["att.ammo_stinger_4aam.name.full"] = "FIM-92 Stinger Quad-Robotar" +L["att.ammo_stinger_4aam.name"] = "4x" +L["att.ammo_stinger_4aam.desc"] = "Guidade klusterrobotar orsakar maximalt tryck mot fientliga piloter." + +L["att.ammo_stinger_apers.name.full"] = "FIM-92 Stinger Antipersonellrobotar" +L["att.ammo_stinger_apers.name"] = "Killer Bee" +L["att.ammo_stinger_apers.desc"] = "Mot gnagare med oacceptabel rörlighet." + +L["att.ammo_usp_9mm.name.full"] = "HK USP 9×19mm Konvertering" +L["att.ammo_usp_9mm.name"] = "9×19mm" +L["att.ammo_usp_9mm.desc"] = "Skjut en lägre kaliber patron med högre kapacitet och eldhastighet." + +-- Attachments (bolt_trigger) +L["att.bolt_fine.name.full"] = "Raffinerat Slutstycke" +L["att.bolt_fine.name"] = "Raffinerad" +L["att.bolt_fine.desc"] = "Delikat slutstycke gjord för kort salvoeld." + +L["att.bolt_greased.name.full"] = "Smord Slutstycke" +L["att.bolt_greased.name"] = "Smord" +L["att.bolt_greased.desc"] = "Snabbare cykelhastighet men sämre hantering." + +L["att.bolt_heavy.name.full"] = "Tungt Slutstycke" +L["att.bolt_heavy.name"] = "Tungt" +L["att.bolt_heavy.desc"] = "Sänkt rekyl med kostnad på eldhastighet." + +L["att.bolt_light.name.full"] = "Lätt Slutstycke" +L["att.bolt_light.name"] = "Lätt" +L["att.bolt_light.desc"] = "Ökad eldhastighet med kostnad på rekyl." + +L["att.bolt_rough.name.full"] = "Tufft Slutstycke" +L["att.bolt_rough.name"] = "Tuff" +L["att.bolt_rough.desc"] = "Hållbart slutstycke gjord för lång salvoeld." + +L["att.bolt_surplus.name.full"] = "Överskott Slutstycke" +L["att.bolt_surplus.name"] = "Överskott" +L["att.bolt_surplus.desc"] = "Rost har käkat det mesta av den, men den fungerar fortfarande." + +L["att.bolt_tactical.name.full"] = "Taktiskt Slutstycke" +L["att.bolt_tactical.name"] = "Taktisk" +L["att.bolt_tactical.desc"] = "Tungt slutstycke byter cykelhastighet mot utmärkt vapenkontroll." + +L["att.bolt_refurbished.name.full"] = "Renoverat Slutstycke" +L["att.bolt_refurbished.name"] = "Renoverad" +L["att.bolt_refurbished.desc"] = "Fixar vapnets pålitlighetsproblem med lite ändringar från förrådet." + +L["att.trigger_akimbo.name.full"] = "Akimbo-Avtryckare" +L["att.trigger_akimbo.name"] = "Akimbo" +L["att.trigger_akimbo.desc"] = "Släpp loss!" + +L["att.trigger_burst.name.full"] = "Salvoeld Avtryckare" +L["att.trigger_burst.name"] = "Salvoeld" +L["att.trigger_burst.desc"] = "Avtryckare som offrar automateld mot stabilitet." + +L["att.trigger_burst2.desc"] = "Avtryckare som emulerar salvoeld." + +L["att.trigger_burstauto.name.full"] = "Auto-Salvoeld Avtryckare" +L["att.trigger_burstauto.name"] = "Auto-Salvoeld" +L["att.trigger_burstauto.desc"] = "Avtryckare som tillåter fortsatt salvoeld när den hålls inne." + +L["att.trigger_comp.name.full"] = "Tävlingsavtryckare" +L["att.trigger_comp.name"] = "Tävling" +L["att.trigger_comp.desc"] = "Lätt avtryckare för sportskytte." + +L["att.trigger_comp2.desc"] = "Lätt avtryckare som återhämtar sig från träffsäkerhet snabbare." + +L["att.trigger_frcd.name.full"] = "Tvungen Återställning Avtryckare" +L["att.trigger_frcd.name"] = "T. Återställ." +L["att.trigger_frcd.desc"] = "Avtryckare som emulerar automateld men med dålig prestanda." + +L["att.trigger_hair.name.full"] = "Fjäder Avtryckare" +L["att.trigger_hair.name"] = "Fjäder" +L["att.trigger_hair.desc"] = "Väldigt känslig avtryckare för snabb halvautomatisk eld." + +L["att.trigger_hair_akimbo.desc"] = "Väldigt känslig avtryckare för snabb akimbo-eld." + +L["att.trigger_heavy.name.full"] = "Tungt Avtryckare" +L["att.trigger_heavy.name"] = "Tung" +L["att.trigger_heavy.desc"] = "Tung avtryckare för kontinuerlig eld." + +L["att.trigger_heavy2.desc"] = "Tung avtryckare som sänker rörlighet straffet under eld." + +L["att.trigger_semi.name.full"] = "Krypskytte Avtryckare" +L["att.trigger_semi.name"] = "Krypskytte" +L["att.trigger_semi.desc"] = "Avtryckare som offrar automateld mot precision." + +L["att.trigger_slam.name.full"] = "\"Slamfire\"-Avtryckare" +L["att.trigger_slam.name"] = "\"Slamfire\"" + +L["att.trigger_straight.name.full"] = "Rak Avtryckare" +L["att.trigger_straight.name"] = "Rak" +L["att.trigger_straight.desc"] = "Smal avtryckare med överlägsen rekylprestanda." + +L["att.trigger_wide.name.full"] = "Akimbo-Avtryckare" +L["att.trigger_wide.name"] = "Akimbo" +L["att.trigger_wide.desc"] = "Större varbygel, lätt att hålla i besvärliga positioner." + +L["att.trigger_dualstage.name.full"] = "\"Dual Stage\"-Avtryckare" +L["att.trigger_dualstage.name"] = "D. Stage" +L["att.trigger_dualstage.desc"] = "Avtryckare som sänker eldhastighet i siktet för bättre kontroll och träffsäkerhet." + +-- Attachments (melee_boost) +L["att.melee_boost_all.name"] = "Ökad Nivå" +L["att.melee_boost_all.desc"] = "Liten boost till alla attributer." + +L["att.melee_boost_str.name"] = "Bulka Upp" +L["att.melee_boost_str.desc"] = "Öka Styrkan drastiskt med kostnad på andra attributer." + +L["att.melee_boost_agi.name"] = "Kom Ikapp" +L["att.melee_boost_agi.desc"] = "Öka Smidigheten drastiskt med kostnad på andra attributer." + +L["att.melee_boost_int.name"] = "Tänk På" +L["att.melee_boost_int.desc"] = "Öka Strategin drastiskt med kostnad på andra attributer." + +L["att.melee_boost_lifesteal.name"] = "Livstjuv" +L["att.melee_boost_lifesteal.desc"] = "Få tillbaka livspoäng genom att göra skada." + +L["att.melee_boost_momentum.name"] = "Momentum" +L["att.melee_boost_momentum.desc"] = "Få tillbaka färdighetsladdning genom att göra skada." + +L["att.melee_boost_afterimage.name"] = "Efterbild" +L["att.melee_boost_afterimage.desc"] = "Sväng ditt vapen snabbt och landa omedelbart attacker." + +L["att.melee_boost_shock.name.full"] = "Chocktrupp" +L["att.melee_boost_shock.name"] = "Chocktrupp" +L["att.melee_boost_shock.desc"] = "Sänker påverkan av nedsatta effekter när vapnet hålls." + +-- Attachments (muzz) +L["att.muzz_hbar.name"] = "Tung Pipa" +L["att.muzz_hbar.desc"] = "Stadig pipa som förbättrar svängning och rekylprestanda." + +L["att.muzz_lbar.name"] = "Lätt Pipa" +L["att.muzz_lbar.desc"] = "Lätt pipa som är mer träffsäker och effektiv vid längre distanser." + +L["att.muzz_pistol_comp.name"] = "Kompensator" +L["att.muzz_pistol_comp.desc"] = "Mynningsenhet som sänker rekylträffen." + +L["att.muzz_supp_compact.name.full"] = "Kompakt Ljuddämpare" +L["att.muzz_supp_compact.name"] = "K. Ljuddämp." +L["att.muzz_supp_compact.desc"] = "Kort ljuddämpare förbättrar träffsäkerheten med låg påverkan på effektiv räckvidd." + +L["att.muzz_silencer.name.full"] = "Taktisk Ljuddämpare" +L["att.muzz_silencer.name"] = "T. Ljuddämp." +L["att.muzz_silencer.desc"] = "Balanserad ljuddämpare som sänker rekylen och effektiva räckvidden." + +L["att.muzz_supp_weighted.name.full"] = "Tung Ljuddämpare" +L["att.muzz_supp_weighted.name"] = "Tung Ljudd." +L["att.muzz_supp_weighted.desc"] = "Tung ljuddämpare med överlägsen ballistik men värre hantering." + +L["att.muzz_brake_aggressor.name.full"] = "Angripare Mynningsbroms" +L["att.muzz_brake_aggressor.name"] = "A. Broms" +L["att.muzz_brake_aggressor.desc"] = "Mynningsbroms designad att omdirigera gaser bort från skytten." + +L["att.muzz_brake_breaching.name.full"] = "Brytande Mynningsbroms" +L["att.muzz_brake_breaching.name"] = "B. Broms" +L["att.muzz_brake_breaching.desc"] = "Spikad mynningsbroms designad för närstrid." + +L["att.muzz_brake_concussive.name.full"] = "Hjärnskakning Mynningsbroms" +L["att.muzz_brake_concussive.name"] = "H. Broms" +L["att.muzz_brake_concussive.desc"] = "Ondskefullt högljudd, obekväm mynningsbroms för extrem rekylkontroll." + +-- Attachments (optic_tac) +L["att.optic_8x.name.full"] = "Teleskopiskt Sikte" +L["att.optic_8x.name"] = "Teleskopisk" +L["att.optic_8x.desc"] = "Kikarsikte för långa avstånd." + +L["att.optic_acog.name.full"] = "ACOG-Sikte" +L["att.optic_acog.name"] = "ACOG" +L["att.optic_acog.desc"] = "Stridssikte för måttliga avstånd." + +L["att.optic_elcan.name.full"] = "ELCAN-Sikte" +L["att.optic_elcan.name"] = "ELCAN" +L["att.optic_elcan.desc"] = "Stridssikte med låg kraft." + +L["att.optic_holographic.name.full"] = "Holografiskt Sikte" +L["att.optic_holographic.name"] = "Holografisk" +L["att.optic_holographic.desc"] = "Boxigt sikte för att hjälpa målförvärv på nära distans." + +L["att.optic_irons.name"] = "Järn & Korn" +L["att.optic_irons.desc"] = "Simpla sikten för tillagd rörlighet." + +L["att.optic_irons_sniper.desc"] = "Ersätt standard siktet för snabbare målförvärv och rörlighet." + +L["att.optic_okp7.name"] = "OKP-7" +L["att.optic_okp7.desc"] = "Reflexsikte i låg profil med minimal zoom-räckvidd." + +L["att.optic_rds2.name.full"] = "Rödpunktsikte" +L["att.optic_rds2.name"] = "Rödpunkt" +L["att.optic_rds2.desc"] = "Öppet reflexsikte med en klar vy." + +L["att.optic_rds.name"] = "Aimpoint" +L["att.optic_rds.desc"] = "Rörsikte för att hjälpa med målförvärv på nära distans." + +L["att.optic_rmr.name"] = "RMR" +L["att.optic_rmr.desc"] = "Sikte i låg profil till pistoler." + +L["att.optic_rmr_rifle.desc"] = "Sikte i låg profil." + +L["att.optic_shortdot.name"] = "\"Short Dot\"" +L["att.optic_shortdot.desc"] = "Kompakt sikte med måttlig magnifiering." + +L["att.tac_cornershot.name"] = "Corner-Cam" +L["att.tac_cornershot.desc"] = "Visar siktpunkten under blindeld." + +L["att.tac_dmic.name"] = "Radar" +L["att.tac_dmic.desc"] = "Upptäcker positionen av nära måltavlor, men skapar ljud." + +L["att.tac_flashlight.name"] = "Ficklampa" +L["att.tac_flashlight.desc"] = "Avger en stark ljusstråle som bländer dem som tittar in i den." + +L["att.tac_laser.name"] = "Laser" +L["att.tac_laser.desc"] = "Avger en smal röd stråle och punkt som visar vart vapnet pekar." + +L["att.tac_combo.name.full"] = "Laser-Lampa Kombo" +L["att.tac_combo.name"] = "Kombo" +L["att.tac_combo.desc"] = "Avger en grön stråle och ficklampa. Ljuset är för svart för att blända andra." + +L["att.tac_rangefinder.name"] = "Avståndsmätare" +L["att.tac_rangefinder.desc"] = "Mäter vapnets ballistik prestanda." + +L["att.tac_spreadgauge.name"] = "Spridningsmätare" +L["att.tac_spreadgauge.desc"] = "Mäter vapnets stabilitet från svängning och blom." + +L["att.tac_magnifier.name"] = "Variabel Zoom Sikte (2x)" +L["att.tac_magnifier.name"] = "2x Zoom" +L["att.tac_magnifier.desc"] = "Ger alla sikten tillgång till en 2x zoom-nivå och tillåter dem att zooma in och ut." + +L["att.tac_bullet.name.full"] = "Akut Patron" +L["att.tac_bullet.name"] = "Akut Patron" +L["att.tac_bullet.desc"] = "Tryck på taktiska knappen för att snabbat ladda en patron i akuta fall." + +L["att.tac_thermal.name.full"] = "ZUMQFY Thermal Imaging Device" +L["att.tac_thermal.name"] = "Thermal-Cam" +L["att.tac_thermal.desc"] = "Visar ett värmeseende överlägg som bländer med huvudvyn när du kikar." + +-- Attachments (perk) +L["att.perk_aim.name"] = "Dödsvy" +L["att.perk_aim.desc"] = "Zoomar in din vy och gör det lättare att skjuta i siktet." + +L["att.perk_blindfire.name.full"] = "Punktskytte" +L["att.perk_blindfire.name"] = "Punktskytte" +L["att.perk_blindfire.desc"] = "Förbättar blindeld och eld under kikande." + +L["att.perk_hipfire.name"] = "Rambo" +L["att.perk_hipfire.desc"] = "Förbättrar vapnets träffsäkerhet ur siktet." + +L["att.perk_melee.name"] = "Nedslag" +L["att.perk_melee.desc"] = "Förbättrar närstridsskafan och saktar ner slagna måltavlor." + +L["att.perk_reload.name"] = "Snabbladdning" +L["att.perk_reload.desc"] = "Förbättrar omladdnings hastigheten." + +L["att.perk_speed.name"] = "Rörlighet" +L["att.perk_speed.desc"] = "Förbättrar vapnets rödlighet, speciellt under omladdning." + +L["att.perk_throw.name"] = "Grenadjär" +L["att.perk_throw.desc"] = "Förbättrar snabbkast och lägger till alternativet att kasta stenar." + +L["att.perk_mlg.name"] = "Stilfull" +L["att.perk_mlg.desc"] = "Förbättrar \"quickscoping\" och träffsäkerheten under rörelse och i luften." + +///////////////////// -- [[ InterOps Weapons ]] -- +-- Weapons +ws = "tacrp_io_" +w = ws .. "870" +L["wep." .. w .. ".name.full"] = "Remington 870 SPMM" +L["wep." .. w .. ".name"] = "R870 SPMM" +L["wep." .. w .. ".desc"] = "Nickelpläterad \"Marine Magnum\"-hagelbössa. Dålig hantering, men bra kapacitet och eldkraft." +L["wep." .. w .. ".desc.quote"] = "\"DET FINNS GULD I DEM DÄR KULLARNA!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Remington" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Millenia \nLjud: Vunsunta, xLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "ab10" +L["wep." .. w .. ".name.full"] = "Intratec AB-10" +L["wep." .. w .. ".name"] = "AB-10" +L["wep." .. w .. ".desc"] = "Halvautomatisk \"Efter Förbjud\"-modell av TEC-9:an med en kort, icke-gängad pipa. Stort magasin men opålitlig." +L["wep." .. w .. ".desc.quote"] = "\"Vem behöver ett automatvapen?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Intratec" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Payday 2 \nLjud: The_AntiPirate \nAnimationer: Tactical Intervention" + +w = ws .. "af2011" +L["wep." .. w .. ".name.full"] = "AF2011-A1" +L["wep." .. w .. ".name"] = "AF2011" +L["wep." .. w .. ".desc"] = "Praktiskt taget två M1911-pistoler ihopsvetsade, detta exotiska monstret skjuter två skott per avtryck." +L["wep." .. w .. ".desc.quote"] = "\"Om nu 1911 var så bra, varför finns ingen 1911 2?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Arsenal Firearms" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "automag" +L["wep." .. w .. ".name.full"] = "Auto Mag Pistol" +L["wep." .. w .. ".name"] = "Auto Mag" +L["wep." .. w .. ".desc"] = "Väldigt träffsäker magnum-pistol. Bra hantering tack vare dess storlek men kan endast montera pistolsikten." +L["wep." .. w .. ".desc.quote"] = "\"Om den används korrekt så kan ta bort fingeravtryck.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Auto Mag Corporation" +L["wep." .. w .. ".credits"] = "Modell: RedRogueXIII \nTexturer/Ljud: Futon \nAnimationer: Tactical Intervention" + +w = ws .. "chinalake" +L["wep." .. w .. ".name.full"] = "\"China Lake\"-Kastare" +L["wep." .. w .. ".name"] = "China Lake" +L["wep." .. w .. ".desc"] = "Tung pumprepeter granatkastare med hög mynningshastighet men dålig hantering." +L["wep." .. w .. ".desc.quote"] = "Endast 59 av dessa finns. Hur fick du tag på den här?" +L["wep." .. w .. ".trivia.manufacturer"] = "NAWS China Lake" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "coltsmg" +L["wep." .. w .. ".name.full"] = "Colt 9mm KPist." +L["wep." .. w .. ".name"] = "Colt KPist." +L["wep." .. w .. ".desc"] = "AR-plattform kulsprutepistol med salvoeld. Utmärkt rekylkontroll, bra skada och räckvidd, men begränsade sikteval. Gynnas av Institutionen av Energi." +L["wep." .. w .. ".desc.quote"] = "Högtalare ej inkluderad." +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Tillgångar: Twinke Masta & Kimono \nLjud: Strelok & New World Interactive \nAnimationer: Tactical Intervention" + +w = ws .. "cx4" +L["wep." .. w .. ".name.full"] = "Beretta CX4 Storm" +L["wep." .. w .. ".name"] = "CX4 Storm" +L["wep." .. w .. ".desc"] = "Halvautomatisk pistolkarbin med bra räckvidd.\nMåttlig penetration, men den stora ramen gör vapnet ganska stabilt." +L["wep." .. w .. ".desc.quote"] = "Som känt sedd i en viss legosoldat ledares förråd." +L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "degala" +L["wep." .. w .. ".name.full"] = "Desert Eagle" +L["wep." .. w .. ".name"] = "Deagle" +L["wep." .. w .. ".desc"] = "Imponerande magnun-pistol, så ikonisk som den kan vara.\nKraftfull och hög kapacitet, men rekylen är svår att hantera." +L["wep." .. w .. ".desc.quote"] = "\"Hör du det där, Herr Anderson?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +L["wep." .. w .. ".credits"] = "Modell: Vashts1985 \nTexturer: Racer445 \nLjud:Vunsunta, XLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "fiveseven" +L["wep." .. w .. ".name.full"] = "FN Five-seveN" +L["wep." .. w .. ".name"] = "Five-seveN" +L["wep." .. w .. ".desc"] = "Skrymmande PDW-kaliber pistol med utmärkt kapacitet. \nPatroner med hög hastighet behåller effektivitet vid räckvidd och penetrerar lätt rustning." +L["wep." .. w .. ".desc.quote"] = "Är det en tamiller som springer runt?" +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter-Strike: Online 2, redigerad av speedonerd \nLjud: Vunsunta, Counter-Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "fnc" +L["wep." .. w .. ".name.full"] = "FN FNC Para" +L["wep." .. w .. ".name"] = "FNC Para" +L["wep." .. w .. ".desc"] = "Lätt automatkarbin med hög precision från höften och rörlighet, men låg räckvidd och dålig penetration av rustning." +L["wep." .. w .. ".desc.quote"] = "\"Jag säger det jag menar och jag gör det jag säger.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Twinke Masta, the_tub, Xero \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "glock18" +L["wep." .. w .. ".name"] = "Glock 18C" +L["wep." .. w .. ".desc"] = "Automatpistol med hög eldhastighet och rörlighet." +L["wep." .. w .. ".desc.quote"] = "\"Förr eller senare får du hoppa.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Glock Ges.m.b.H" +L["wep." .. w .. ".credits"] = "Modell: Hav0k101 \nTexturer: el maestro de graffiti \nLjud: BlitzBoaR, Lorn, Ghost597879, Zeven II \nAnimationer: Tactical Intervention" + +w = ws .. "k98" +L["wep." .. w .. ".name.full"] = "Karabiner 98k" +L["wep." .. w .. ".name"] = "Kar98k" +L["wep." .. w .. ".desc"] = "Antik cylinderrepetergevär med bestående design. Kraftfull på nära håll, men är praktiskt taget föråldrad på det moderna slagfältet." +L["wep." .. w .. ".desc.quote"] = "\"Vill du ha totalt krig?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Mauser" +L["wep." .. w .. ".credits"] = "Modell: Day of Defeat: Source, redigerad av 8Z \nTexturer: Cafe Rev., rascal, 5hifty \nLjud: rzen1th \nAnimations Cry of Fear, Lazarus" + +w = ws .. "k98_varmint" +L["wep." .. w .. ".name"] = "Varmintgevär" +L["wep." .. w .. ".desc"] = "Cylinderrepetergevär baserad på Mauser-slutstycket. Använder AR-15-magasin. Lätt vikt, lätt att hantera och har generös kapacitet, men skadan är låg." +L["wep." .. w .. ".desc.quote"] = "Mot gnagare av... anspråkslös storlek." +L["wep." .. w .. ".trivia.manufacturer"] = "All-American Firearms" +L["wep." .. w .. ".credits"] = "Modell: Day of Defeat: Source, redigerad av 8Z \nTexturer: 5hifty \nLjud: rzen1th \nAnimationer: Tactical Intervention" + +w = ws .. "m14" +L["wep." .. w .. ".name"] = "M14 SOPMOD" +L["wep." .. w .. ".desc"] = "Modernt gevär med kort pipa och förbättrad rörlighet och snabb automateld men bestraffande rekyl. \nUtrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "Troy Industries" +L["wep." .. w .. ".credits"] = "Modell: General Tso & Twinke Masta \nTexturer: Thanez, Twinke Masta \nLjud: Lakedown, teh strelok & Futon \nAnimationer: Tactical Intervention" + +w = ws .. "m16a2" +L["wep." .. w .. ".name.full"] = "Colt M16A2" +L["wep." .. w .. ".name"] = "M16A2" +L["wep." .. w .. ".desc"] = "Gammaldags automatkarbin med salvoeld och begränsade sikte alternativ. Trots det så gör dess stabila rekyl och höga skadan den pålitlig på medelavstånd." +L["wep." .. w .. ".desc.quote"] = "\"Ta klara skott, håll koll på bakgrunden.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta, SureShot, vagrant \nTexturer: Stoke, modderfreak \nLjud: Vunsunta, Navaro \nAnimationer: Tactical Intervention" + +w = ws .. "m500" +L["wep." .. w .. ".name.full"] = "SW Model 500" +L["wep." .. w .. ".name"] = "M500" +L["wep." .. w .. ".desc"] = "Stor revolver med lång pipa som skjuter en massiv magnum-kula. Sedd som världens mest kraftfulla produktions sidovapen." +L["wep." .. w .. ".desc.quote"] = "\"Ni dweebs förstörde precis en fem års utredning!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +L["wep." .. w .. ".credits"] = "Tillgångar: Alliance of Valiant Arms \nEgentligen portat till CS 1.6 av GR_Lucia \nLjud: Ghost597879, MSKyuuni & Zira \nAnimationer: Tactical Intervention" + +w = ws .. "mx4" +L["wep." .. w .. ".name.full"] = "Beretta MX4 Storm" +L["wep." .. w .. ".name"] = "MX4 Storm" +L["wep." .. w .. ".desc"] = "Stor pistolkarbin med hög eldhastighet men måttlig penetrationsförmåga." +L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "p226" +L["wep." .. w .. ".name.full"] = "SIG P226" +L["wep." .. w .. ".name"] = "P226" +L["wep." .. w .. ".desc"] = "Sidovapen med överlägsen räckvidd och precision, men låg kapacitet." +L["wep." .. w .. ".desc.quote"] = "\"Korrekta talesättet är 'babes', sir.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +L["wep." .. w .. ".credits"] = "Modell: SoulSlayer \nTexturer: Thanez \nLjud: Anders, DMG, FxDarkloki, & Thanez \nAnimationer: Tactical Intervention" + +w = ws .. "rpk" +L["wep." .. w .. ".name"] = "RPK" +L["wep." .. w .. ".desc"] = "Lätt kulspruta härledd från ett infanterigevär. Hög skada och god rekyl, men dålig rörlighet och spridning." +L["wep." .. w .. ".trivia.manufacturer"] = "Molot" +L["wep." .. w .. ".credits"] = "Tillgångar: Call To Arms \nAnimationer: Tactical Intervention" + +w = ws .. "ruger" +L["wep." .. w .. ".name.full"] = "AWC Amphibian Ruger" +L["wep." .. w .. ".name"] = "Amphibian" +L["wep." .. w .. ".desc"] = "Liten kaliber pistol utrustat med inbyggd ljuddämpare. Nästan ingen rekyl tack vare svaga patroner." +L["wep." .. w .. ".desc.quote"] = "\"Ta det lugnt i att du vet att jag inte hade något val.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +L["wep." .. w .. ".credits"] = "Modell: The Lama \nTexturer: The Miller \nAnimationer: Tactical Intervention" + +w = ws .. "saiga" +L["wep." .. w .. ".name"] = "Saiga-12K" +L["wep." .. w .. ".desc"] = "Hög kapacitets hagelbössa som matas från ett lådmagasin, perfekt för att tömma ett rum." +L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +L["wep." .. w .. ".credits"] = "Tillgångar: Battlefield 3 \nAnimationer: Tactical Intervention" + +w = ws .. "scarh" +L["wep." .. w .. ".name.full"] = "FN SCAR-H CQC" +L["wep." .. w .. ".name"] = "SCAR-H" +L["wep." .. w .. ".desc"] = "Kompakt stridsgevär med hög rörlighet och hantering." +L["wep." .. w .. ".desc.quote"] = "\"Sand Bravo, vi läser 70 kontakter i eran sektor.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "FN America" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter-Strike: Online 2, redigerad av speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "sg550" +L["wep." .. w .. ".name.full"] = "SIG SG 550-1 Krypskyttegevär" +L["wep." .. w .. ".name"] = "SG 550-1" +L["wep." .. w .. ".desc"] = "Karbinkaliber krypskyttegevär med snabb automateld. Lätt att kontrollera i korta salvor och har hög penetrationsförmåga. Utrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +L["wep." .. w .. ".credits"] = "Modell: Hav0c & Twinke Masta \nTexturer: Twinke Masta \nLjud: Farion, Treyarch & Tactical Intervention \nAnimationer: Tactical Intervention" + +w = ws .. "sg550r" +L["wep." .. w .. ".name.full"] = "SIG SG550-2 SP" +L["wep." .. w .. ".name"] = "SG 550-2" +L["wep." .. w .. ".desc"] = "Gevär med lång pipa konverterad till halvautomat för civila marknade. Lätt att kontrollera och har hög penetrationsförmåga." +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +L["wep." .. w .. ".credits"] = "Modell: Hav0c & Twinke Masta \nTexturer: Twinke Masta \nLjud: Farion, Treyarch & Tactical Intervention \nAnimationer: Tactical Intervention" + +w = ws .. "sl8" +L["wep." .. w .. ".name.full"] = "HK SL8" +L["wep." .. w .. ".name"] = "SL8" +L["wep." .. w .. ".desc"] = "Halvautomatisk variant av G36:an gjord för precisions-skytte. Låg eldhastighet, men rekylkontrollen är utmärkt. \nUtrustat med ett 2x sikte utan järn & korn alternativ." +L["wep." .. w .. ".desc.quote"] = "\"Brukade vara en snut jag med, men endast i en dag.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Hav0c \nTexturer: Twinke Masta \nLjud: KingFriday \nAnimationer: Tactical Intervention" + +w = ws .. "star15" +L["wep." .. w .. ".name.full"] = "Spikes Tactical AR-15" +L["wep." .. w .. ".name"] = "ST AR-15" +L["wep." .. w .. ".desc"] = "Eftermarknads AR-15 fint anpassad för precisionsskytte. \nAlltid valet för ensamma jägare på hämndens väg, ditt öde kommer bli en illalåtande begravning... och ett tyst förväl. Ta med en detonator." +L["wep." .. w .. ".trivia.manufacturer"] = "Spikes Tactical" +L["wep." .. w .. ".credits"] = "Tillgångar: carl ruins everything, Leon-DLL, Mira + diverse källor \nLjud: Insurgency, rzen1th & Tactical Intervention \nAnimationer: Tactical Intervention" + +w = ws .. "t850" +L["wep." .. w .. ".name.full"] = "Taurus 850 Ultralite" +L["wep." .. w .. ".name"] = "T850" +L["wep." .. w .. ".desc"] = "\"Snub-nose\"-revolver med kompakt formfaktor. Dödligheten faller snabbt efter skottet skjuts." +L["wep." .. w .. ".desc.quote"] = "Konditionsträningen är gratis när du jagar gangsters." +L["wep." .. w .. ".trivia.manufacturer"] = "Taurus" +L["wep." .. w .. ".credits"] = "Modell: Fearfisch \nTexturer: Millenia \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "tec9" +L["wep." .. w .. ".name.full"] = "Intratec TEC-9" +L["wep." .. w .. ".name"] = "TEC-9" +L["wep." .. w .. ".desc"] = "Automatpistol känd för dess lätta konvertering till automateld, och påföljd kriminell användning." +L["wep." .. w .. ".desc.quote"] = "\"Kunden har alltid rätt.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Intratec" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Payday 2 \nLjud: The_AntiPirate \nAnimationer: Tactical Intervention" + +w = ws .. "trg42" +L["wep." .. w .. ".name.full"] = "Sako TRG-42" +L["wep." .. w .. ".name"] = "TRG-42" +L["wep." .. w .. ".desc"] = "Magnum-krypskyttegevär med måttlig hantering och rörlighet. \nKraftfull, men låg cykelhastighet och inte väldigt stabil. \nUtrustat med ett 12x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +L["wep." .. w .. ".credits"] = "Modell: Darkstorn \nTexturer: SilentAssassin12 \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "usc" +L["wep." .. w .. ".name.full"] = "HK USC" +L["wep." .. w .. ".name"] = "USC" +L["wep." .. w .. ".desc"] = "Halvautomatisk karbinversion av UMP:n. Använder låg kapacitetsmagasin, men dess långa pipa och hopsatta greppkolv erbjuder utmärkt medelräckvidd prestanda." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Twinke Masta, xLongWayHome, Stoke, Teh Snake & Millenia etc. \nAnimationer: Tactical Intervention" + +w = ws .. "val" +L["wep." .. w .. ".name"] = "AS Val" +L["wep." .. w .. ".desc"] = "Gevär med inbyggd ljuddämpare och hög skada och precision, men har dålig prestanda vid längre salvoeld." +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +L["wep." .. w .. ".credits"] = "Modell & Texturer: S.T.A.L.K.E.R. \nLjud: S.T.A.L.K.E.R. & Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "vp70" +L["wep." .. w .. ".name.full"] = "HK VP70" +L["wep." .. w .. ".name"] = "VP70" +L["wep." .. w .. ".desc"] = "Pistol av polymer med innovativ hölsterkolv som tillåter otroligt snabb salvoeld." +L["wep." .. w .. ".desc.quote"] = "\"Vart är alla på väg? Bingo?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell & Texturer: KnechtRuprecht \nLjud: Strelok & xLongWayHome \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "vss" +L["wep." .. w .. ".name.full"] = "VSS Vintorez" +L["wep." .. w .. ".name"] = "Vintorez" +L["wep." .. w .. ".desc"] = "Krypskyttegevär med inbyggd ljuddämpare och hög eldhastighet med låg rekyl, men har dålig prestanda vid längre salvoeld. \nUtrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +L["wep." .. w .. ".credits"] = "Modell: Ettubrutesbro, Millenia & Twinke Masta \nTexturer: Millenia \nLjud: S.T.A.L.K.E.R. & Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "xm8car" +L["wep." .. w .. ".name.full"] = "HK XM8 Kompact" +L["wep." .. w .. ".name"] = "XM8 Kompact" +L["wep." .. w .. ".desc"] = "Experimentell mångsidig karbin. Lätt att hantera, men låg skada. \nHar justerbart inbyggt 2-8x sikte." +L["wep." .. w .. ".desc.quote"] = "\"Who loves spaghetti?!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: End Of Days \nTexturer: Copkiller, Twinke Masta & Wangchung \nAnimationer: Tactical Intervention" + +w = ws .. "xm8lmg" +L["wep." .. w .. ".name.full"] = "HK XM8 KSP" +L["wep." .. w .. ".name"] = "XM8 KSP" +L["wep." .. w .. ".desc"] = "Experimentell mångsidig karbin konfigurerad som en kulspruta. Lätt, hög kapacitet och låg rekyl, men dålig skada. \nHar justerbart inbyggt 2-8x sikte." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: End Of Days \nTexturer: Copkiller, Twinke Masta & Wangchung \nAnimationer: Tactical Intervention" + +-- Attachments (interops) +L["att.ammo_star15_300blk.name.full"] = "ST AR-15 .300 AC Blackout Modd Kit" +L["att.ammo_star15_300blk.name"] = ".300 BLK" +L["att.ammo_star15_300blk.desc"] = "Anpassning för att ladda lägre hastighetspatroner med högre potential på nära distans." + +L["att.ammo_star15_50beo.name.full"] = "ST AR-15 .50 Beowulf Modd Kit" +L["att.ammo_star15_50beo.name"] = ".50 BEO" +L["att.ammo_star15_50beo.desc"] = "Anpassning för att ladda låg kapacitet, hög kraft magnum-patroner." + +L["att.bolt_af2011_alt.name.full"] = "AF2011-A1 Omväxlande Slutstycke" +L["att.bolt_af2011_alt.name"] = "Omväxlande" +L["att.bolt_af2011_alt.desc"] = "Illvillig tolkning av konceptet för \"dubbelstaplat magasin\"." + +L["att.muzz_comp_io_m14.desc"] = "att.muzz_pistol_comp.desc" + +L["att.muzz_tec9_shroud.name.full"] = "TEC-9 Pipa med Skydd" +L["att.muzz_tec9_shroud.name"] = "Skydd" +L["att.muzz_tec9_shroud.desc"] = "Förlängd pipa som förbättrar prestandan på avstånd." + +L["att.optic_ak_pso1.name.full"] = "PSO-1-Sikte" +L["att.optic_ak_pso1.name"] = "PSO-1" +L["att.optic_ak_pso1.desc"] = "Ryskt laxstjärt sikte med medel-lång avstånd magnifiering." + +L["att.optic_ar_colt.name.full"] = "Colt 3x20-Sikte" +L["att.optic_ar_colt.name"] = "Colt 3x20" +L["att.optic_ar_colt.desc"] = "Riktmedel med låg kraft monterad på AR-plattform bärhandtag." + +L["att.optic_k98_zf41.name.full"] = "Zeiss 6x38-Kikarsikte" +L["att.optic_k98_zf41.name"] = "Zeiss" +L["att.optic_k98_zf41.desc"] = "Medelkraft kikarsikte gjord specifikt till Kar98k." + +L["att.optic_xm8_4x.name.full"] = "XM8 Inbyggt Sikte (4x)" +L["att.optic_xm8_4x.name"] = "4x" +L["att.optic_xm8_4x.desc"] = "Medelräckvidd zoominställning med ACOG-hårkors." + +L["att.optic_xm8_6x.name.full"] = "XM8 Inbyggt Sikte (6x)" +L["att.optic_xm8_6x.name"] = "6x" +L["att.optic_xm8_6x.desc"] = "Medel-lång räckvidd zoominställning med \"Short Dot\"-hårkors." + +L["att.optic_xm8_8x.name.full"] = "XM8 Inbyggt Sikte (8x)" +L["att.optic_xm8_8x.name"] = "8x" +L["att.optic_xm8_8x.desc"] = "Lång räckvidd zoominställning med kikarsikte hårkors." + +L["att.trigger_vp70_auto.name.full"] = "VP-70 Automateld Kolv" +L["att.trigger_vp70_auto.name"] = "Automateld" +L["att.trigger_vp70_auto.desc"] = "Ingenjörerna på H&K har skum i munnen vid detta ögonblick." + +L["att.trigger_vp70_semi.name.full"] = "VP-70 Ta Bort Kolv" +L["att.trigger_vp70_semi.name"] = "Kolvlös" +L["att.trigger_vp70_semi.desc"] = "Tar bort förmågan för salvoeld och förbättrar hantering och rörlighet." + +///////////////////// -- [[ ArmaLite Revolution ]] -- +-- Weapons +ws = "tacrp_ar_" +w = ws .. "ar15pistol" +L["wep." .. w .. ".name"] = "AR-15 Compact" +L["wep." .. w .. ".desc"] = "Kolvlös AR-15 med extremt kort pipa konfiguration. \nEnlist lag en pistol och lätt nog att fungera som sidovapen, men är ostabil och inte lika precis utan formfaktorn av ett gevär." +L["wep." .. w .. ".trivia.manufacturer"] = "Ultra-Tac" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Mateusz Woliński, Jordan Whincup \nMagazine: Twinke Masta \nLjud: Teh Strelok, Vunsunta, xLongWayHome, CS:O2 \nAnimationer: Tactical Intervention" + +w = ws .. "gilboa" +L["wep." .. w .. ".name.full"] = "Gilboa DBR Snake" +L["wep." .. w .. ".name"] = "Gilboa DBR" +L["wep." .. w .. ".desc"] = "Unikt dubbelpipig AR-karbin. Dubbelt så dödlig som en pipa, men designen är skrymmande och inte träffsäker. \nKan uppenbarligen inte montera mynningstillbehör." +L["wep." .. w .. ".trivia.manufacturer"] = "Silver Shadow" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "hk416" +L["wep." .. w .. ".name.full"] = "HK HK416" +L["wep." .. w .. ".name"] = "HK416" +L["wep." .. w .. ".desc"] = "Elegant svart karbin gjord som en motståndare till AR-15. Träffsäker med låg rekyl med kostnad på lite bulk." +L["wep." .. w .. ".desc.quote"] = "Ett elitvapen är allt du behöver." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta, B0T, SoulSlayer \nTexturer: Acid Snake, el maestro de graffiti, Antman \nLjud: Vunsunta, xLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "lr300" +L["wep." .. w .. ".name.full"] = "Z-M LR-300" +L["wep." .. w .. ".name"] = "LR-300" +L["wep." .. w .. ".desc"] = "AR-härledd \"Lätt Karbin\" med en anpassad gasregulator. Erbjuder hög eldhastighet och räckvidd, men stabilitet är undermålig. \"300\" betecknar gevärets maximala effektiva räckvidd i meter." +L["wep." .. w .. ".trivia.manufacturer"] = "Z-M Weapons" +L["wep." .. w .. ".credits"] = "Modell: TheLama \nTexturer: Wannabe \nLjud: NightmareMutant \nAnimationer: Tactical Intervention" + +w = ws .. "m16a1" +L["wep." .. w .. ".name.full"] = "Colt M16A1" +L["wep." .. w .. ".name"] = "M16A1" +L["wep." .. w .. ".desc"] = "Antikt gevär hämtat från risfälten. Har hög eldhastighet och bra räckvidd, men förvänta dig inte att denna rosthinken fungerar utan några problem här och där." +L["wep." .. w .. ".desc.quote"] = "Välkommen till djungeln." +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell: Hav0c \nTexturer: Millenia \nCompile: The_AntiPirate \nAnimationer: Tactical Intervention" + +w = ws .. "m16a4" +L["wep." .. w .. ".name.full"] = "Colt M16A4" +L["wep." .. w .. ".name"] = "M16A4" +L["wep." .. w .. ".desc"] = "Modernt infanterigevär med modern råd, som en toppmonterad skena och RIS-handskydd. Välbalanserat infanterigevär med bra effektiv räckvidd." +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta, SureShot, vagrant \nTexturer: Stoke, modderfreak \nLjud: Vunsunta, Navaro \nAnimationer: Tactical Intervention" + +w = ws .. "sr25" +L["wep." .. w .. ".name.full"] = "KAC SR-25 EMR" +L["wep." .. w .. ".name"] = "SR-25" +L["wep." .. w .. ".desc"] = "\"Enhanced Match Rifle\" gjord för precisionsskytte. \nLåg kapacitet, men annars utmärkt prestanda. \nUtrustat med ett 10x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +L["wep." .. w .. ".credits"] = "Tillgångar: Firearms: Source \nAnimationer: Tactical Intervention" + +w = ws .. "vltor" +L["wep." .. w .. ".name"] = "VLTOR SBR" +L["wep." .. w .. ".desc"] = "AR-mönster karbin med unig piggyback-style handskydd. Utmärker sig på nära distans utan att ge upp medelräckvidd prestanda." +L["wep." .. w .. ".desc.quote"] = "\"Skynda på! Iväg mot Whiskey Hotel!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "VLTOR" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Kimono \nLjud: Teh Strelok \nAnimationer: Tactical Intervention" + +-- Attachments (ar) +L["att.bolt_gilboa_alt.name.full"] = "Gilboa DBR Omväxlande Slutstycke" +L["att.bolt_gilboa_alt.name"] = "Omväxlande" +L["att.bolt_gilboa_alt.desc"] = "Separata slutstycken som kan, på något sätt, skjuta omväxlande." + +L["att.muzz_sr25.name.full"] = "SR-25 Ljuddämpare med Skydd" +L["att.muzz_sr25.name"] = "SR-25 Ljudd." +L["att.muzz_sr25.desc"] = "Unik ljuddämpare med skydd som förbättrar ballistik men sänker eldhastigheten." + +///////////////////// -- [[ Special Delivery ]] -- +-- Weapons +ws = "tacrp_sd_" +w = ws .. "1022" +L["wep." .. w .. ".name.full"] = "Ruger 10/22" +L["wep." .. w .. ".name"] = "10/22" +L["wep." .. w .. ".desc"] = "Ultralätt plink-gevär. Väldigt träffsäker och lätt att hantera, men den är knappt dödlig om du inte träffar huvudet." +L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +L["wep." .. w .. ".credits"] = "Tillgångar: No More Room in Hell \nAnimationer: Tactical Intervention" + +w = ws .. "1858" +L["wep." .. w .. ".name.full"] = "Remington 1858 Army" +L["wep." .. w .. ".name"] = "Army" +L["wep." .. w .. ".desc"] = "Antik tändhattsrevolver med tuff prestanda på nära håll, men är inte så snabb att skjuta. Perfekt för cowboy-rollspel." +L["wep." .. w .. ".desc.quote"] = "\"Skicka hit whiskey:n!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms" +L["wep." .. w .. ".credits"] = "Tillgångar: Nimrod Hempel (Fistful of Frags) \nAnimationer: CyloWalker \nSnabbkast & Närstrid Animationer: speedonerd" + +w = ws .. "aac_hb" +L["wep." .. w .. ".name.full"] = "AAC Honey Badger" +L["wep." .. w .. ".name"] = "Honey Badger" +L["wep." .. w .. ".desc"] = "En lätt automatkarbin med inbyggd ljuddämpare. Kraftfull på nära distanser och har inga synliga spårljus, men har dålig prestanda vid räckvidd." +L["wep." .. w .. ".trivia.manufacturer"] = "AAC" +L["wep." .. w .. ".credits"] = "Modell: Hyper \nAnimationer: Tactical Intervention" + +w = ws .. "bizon" +L["wep." .. w .. ".name.full"] = "PP-19 Bizon" +L["wep." .. w .. ".name"] = "Bizon" +L["wep." .. w .. ".desc"] = "AK-härledd kulsprutepistol med hög kapacitets spiralmagasin. Ganska svag men lätt att kontrollera och hantera." +L["wep." .. w .. ".trivia.manufacturer"] = "Izhmash" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Milo \nLjud: Vunsunta \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "contender" +L["wep." .. w .. ".name.full"] = "T/C G2 Contender" +L["wep." .. w .. ".name"] = "Contender" +L["wep." .. w .. ".desc"] = "Enkelskott jaktpistol. Träffsäker, kompakt och dödlig, så bäst att det enda skottet träffar. \nUtrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Vet du vad jag hatar? Två sorters folk...\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Thompson/Center" +L["wep." .. w .. ".credits"] = "Modell: kriboez, Doktor haus \nTexturer: cR45h, syncing, tenoyl, Ultimately \nLjud: Doktor haus \nAnimationer: 8Z, speedonerd" + +w = ws .. "db" +L["wep." .. w .. ".name.full"] = "Stoeger Double Defense" +L["wep." .. w .. ".name"] = "Double Defense" +L["wep." .. w .. ".desc"] = "Modern tillverkad kort dubbelpipig hagelbössa. Lätt att hantera, pålitlig och dödlig på nära avstånd." +L["wep." .. w .. ".desc.quote"] = "\"Eat leaden death, demon.\"" --To be translated +L["wep." .. w .. ".trivia.manufacturer"] = "Stoeger" +L["wep." .. w .. ".credits"] = "Modell: Counter-Strike: Online 2 \nLjud: Navaro & Vunsunta \nAnimationer: speedonerd & 8Z" + +w = ws .. "delisle" +L["wep." .. w .. ".name.full"] = "De Lisle-Karbin" +L["wep." .. w .. ".name"] = "De Lisle" +L["wep." .. w .. ".desc"] = "Ljuddämpad pistolkaliber cylinderrepeterkarbin. Ett av dem tystaste eldvapnen någonsin, dess subsoniska skott har inga spårljus men färdas ganska sakta." +L["wep." .. w .. ".desc.quote"] = "Sköts in i Thames-floden, inte en själ hörde det." +L["wep." .. w .. ".trivia.manufacturer"] = "Sterling Armaments" +L["wep." .. w .. ".credits"] = "Modell: RedRougeXIII \nTexturer: Storm (snällt fixad av Unselles) \nAnimationer: Tactical Intervention" + +w = ws .. "dual_1911" +L["wep." .. w .. ".name"] = "Dueling Wyverns" +L["wep." .. w .. ".desc"] = "Ett par pråliga, specialbeställda gyllene M1911-pistoler komplett med wyvern-graverat grepp. Träffar hårt, men dess låga kapacitet kan skada." +L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +L["wep." .. w .. ".credits"] = "Modell: Schmung \nTexturer: Millenia \nAnimationer: Tactical Intervention" + +w = ws .. "dual_degala" +L["wep." .. w .. ".name"] = "Dual Eagles" +L["wep." .. w .. ".desc"] = "Ett par pråliga guldbelagda Desert Eagles, som om en inte räckte. \nDubbelt så otrolig eldkraft, dubbelt otroligt hög rekyl." +L["wep." .. w .. ".desc.quote"] = "\"Mitt blod... på deras händer.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +L["wep." .. w .. ".credits"] = "Modell: Hav0c & Stoke \nTexturer: The_Tub & Stoke \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "dual_ppk" +L["wep." .. w .. ".name"] = "Dual Agents" +L["wep." .. w .. ".desc"] = "Ett par ljuddämpade PPK-pistoler. Snabba och träffsäkra, men den låga kapaciteten och måttliga skadan kräver ett skarpt öga och avtryckare disciplin." +L["wep." .. w .. ".desc.quote"] = "Du får fan inte välja Oddjob." +L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Kimono \nLjud: HK & Vunsunta \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "dual_usp" +L["wep." .. w .. ".name"] = "Dual Matches" +L["wep." .. w .. ".desc"] = "Ett par pistoler snattade från ett par döda metro-poliser. Måttlig skada och kapacitet, men rejälva och inte snabba att skjutas." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Silvio Dante & Twinke Masta \nTexturer: LoneWolf \nAnimationer: Tactical Intervention" + +w = ws .. "dual_uzis" +L["wep." .. w .. ".name"] = "Dual Uzis" +L["wep." .. w .. ".desc"] = "Ett par helautomatiska Micro Uzi:n. Vet inte hur exakt du förväntar dig att träffa något med dessa, men gör som du vill." +L["wep." .. w .. ".desc.quote"] = "\"Kommer slå dig med så många vänstra att du kommer be om en höger!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +L["wep." .. w .. ".credits"] = "Modell: BrainBread 2 \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "dualies" +L["wep." .. w .. ".name"] = "Dueling Dragons" +L["wep." .. w .. ".desc"] = "Ett par anpassade pistoler med en tvåfärgad finish och drakar utsmyckade på greppen. Snabbt hantering och måttlig rekylkontroll men låg eldkraft." +L["wep." .. w .. ".desc.quote"] = "\"Jag tog bort fingret från avtryckaren. Då var det slut.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +L["wep." .. w .. ".credits"] = "Modell: Spydr \nTexturer: NCFurious \nAnimationer: Tactical Intervention" + +w = ws .. "famas" +L["wep." .. w .. ".name"] = "FAMAS F1" +L["wep." .. w .. ".desc"] = "Bullpup-gevär med salvoeld som används av franska armén. Hög eldhastighet och bra träffsäkerhet, begränsad endast av den undermåliga magasinkapaciteten och ganska intensiva rekylen. \nHar ett skjutstöd tack vare typisk fransk anledning." +L["wep." .. w .. ".trivia.manufacturer"] = "GIAT Industries" +L["wep." .. w .. ".credits"] = "Modell: SnipaMasta \nTexturer: SnipaMasta, Fnuxray \nAnimationer: speedonerd" + +w = ws .. "famas_g2" +L["wep." .. w .. ".name"] = "FAMAS G2" +L["wep." .. w .. ".desc"] = "Bullpup-gevär med extremt snabb eldhastighet som används av franska marinen. Tillgång till automateld, men salvoeld eller det inbyggda skjutstödet rekommenderas." +L["wep." .. w .. ".desc.quote"] = "\"Gud har verkligen sinne för humör.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "GIAT Industries" +L["wep." .. w .. ".credits"] = "Modell: SnipaMasta \nTexturer: SnipaMasta, Fnuxray \nAnimationer: speedonerd" + +w = ws .. "g3" +L["wep." .. w .. ".name.full"] = "HK G3A3" +L["wep." .. w .. ".name"] = "G3A3" +L["wep." .. w .. ".desc"] = "Träffsäkert och tungt stridsgevär med en ganska hanterbart automateldläge men sölig hantering." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Firearms: Source \nLjud: Nightmare Mutant & FA:S2 \nAnimationer: Tactical Intervention" + +w = ws .. "groza" +L["wep." .. w .. ".name.full"] = "OTs-14 \"Groza\"" +L["wep." .. w .. ".name"] = "Groza-4" +L["wep." .. w .. ".desc"] = "Bullpup-gevär med inbyggd ljuddämpare gjord från omkonfigurerad AK. Svag men har bra hantering och stabilitet utan synliga spårljus." +L["wep." .. w .. ".desc.quote"] = "\"Bort härifrån, Stalker.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "TsKIB SOO" +L["wep." .. w .. ".credits"] = "Modell: Teh Snake, redigerad av speedonerd \nTexturer: Teh Snake \nLjud: Teh Snake & speedonerd \nAnimationer: speedonerd" + +w = ws .. "gyrojet" +L["wep." .. w .. ".name.full"] = "MBA Gyrojet" +L["wep." .. w .. ".name"] = "Gyrojet" +L["wep." .. w .. ".desc"] = "Experimentellt vapen som skjuter självgående minirobotar. Dem må vara kraftfulla, men dem har hög chans att inte kunna skjutas." +L["wep." .. w .. ".desc.quote"] = "\"Undrar hur mycket av dess rester går för på Tradera.\"" -- "Tradera" is Swedish Ebay... -ish. +L["wep." .. w .. ".trivia.manufacturer"] = "MBAssociates" +L["wep." .. w .. ".credits"] = "Modell & Texturer: RedRougeXIII \nLjud: speedonerd, Tactical Intervention \nAnimationer: speedonerd" + +w = ws .. "m1carbine" +L["wep." .. w .. ".name"] = "M1-Karbin" +L["wep." .. w .. ".desc"] = "Halvautomatisk karbin från andra världskriget. Gjord som ett försvarsvapen för understödstrupper, den är träffsäker och lätt men har måttlig stoppkraft." +L["wep." .. w .. ".desc.quote"] = "\"Flash.\" \"Thunder.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "General Motors" +L["wep." .. w .. ".credits"] = "Modell & Texturer: KnechtRuprecht \nLjud: NightmareMutant \nAnimationer: Tactical Intervention" + +w = ws .. "mp40" +L["wep." .. w .. ".name.full"] = "Steyr MP40" +L["wep." .. w .. ".name"] = "MP40" +L["wep." .. w .. ".desc"] = "Kulsprutepistol från andra världskriget med låg eldhastighet och lätt rekyl. Trots dess ålder så visar den sig i många krig än idag." +L["wep." .. w .. ".desc.quote"] = "\"Hans, ditt kaffe suger. Jag dricker inte den skiten.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Steyr-Mannlicher" +L["wep." .. w .. ".credits"] = "Modell: Soul-Slayer \nTexturer: Kimono \nLjud: Futon & Vunsunta \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "pkm" +L["wep." .. w .. ".name"] = "PKM" +L["wep." .. w .. ".desc"] = "Generell kulspruta med möjlighet av intensiv täckande eld. Hög kapacitet och skada men är väldigt, väldigt tung." +L["wep." .. w .. ".trivia.manufacturer"] = "Degtyaryov Plant" +L["wep." .. w .. ".credits"] = "Tillgångar: Call to Arms \nLjud: NightmareMutant & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "ppk" +L["wep." .. w .. ".name.full"] = "Walther PPK" +L["wep." .. w .. ".name"] = "PPK" +L["wep." .. w .. ".desc"] = "Kompakt fickpistol med låg kapacitet gjord känd av filmer. Bäst gjord för självförsvar på nära avstånd." +L["wep." .. w .. ".desc.quote"] = "\"Upprörande. Väldigt upprörande.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Kimono \nLjud: HK & Vunsunta \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "superx3" +L["wep." .. w .. ".name.full"] = "Winchester Super X3" +L["wep." .. w .. ".name"] = "Super X3" +L["wep." .. w .. ".desc"] = "Civil sporthagelbössa tillverkad för prestanda. Lång pipa och tävlingsgjord choke erbjuder bra träffsäkerhet och räckvidd, men dålig hantering." +L["wep." .. w .. ".trivia.manufacturer"] = "Winchester Repeating Arms" +L["wep." .. w .. ".credits"] = "Model, Textures & Sound: No More Room in Hell \nAnimationer: Tactical Intervention" + +w = ws .. "thompson" +L["wep." .. w .. ".name.full"] = "M1A1 Thompson" +L["wep." .. w .. ".name"] = "Thompson" +L["wep." .. w .. ".desc"] = "Kulsprutepistol från andra världskriget med kraftiga trädelar. Har imponerande stoppkraft på nära avstånd, men är ganska tung." +L["wep." .. w .. ".desc.quote"] = "\"Ät bly, tyskjävlar!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Auto-Ordnance Company" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter-Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "tt33" +L["wep." .. w .. ".name.full"] = "Tokarev TT-33" +L["wep." .. w .. ".name"] = "TT-33" +L["wep." .. w .. ".desc"] = "Antik pistol från bakom järnridån. Hög räckvidd och penetrationsförmåga, men har pålitlighetsproblem tack vare dess ålder." +L["wep." .. w .. ".desc.quote"] = "\"Du hade kanske föredragit att undvika röda tejpen?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +L["wep." .. w .. ".credits"] = "Modell: Mr.Rifleman \nTexturer: BuLL5H1T & Mr.Rifleman \nLjud: NightmareMutant \nAnimationer: Tactical Intervention" + +w = ws .. "vz58" +L["wep." .. w .. ".name.full"] = "Sa vz. 58" +L["wep." .. w .. ".name"] = "vz. 58" +L["wep." .. w .. ".desc"] = "Automatkarbin med hög skada och utmärkt penetrationsförmåga, konverterad till halvautomat för civila marknader." +L["wep." .. w .. ".desc.quote"] = "Trots dess utseende så är det definitivt inte en AK." +L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +L["wep." .. w .. ".credits"] = "Model, Textures & Ljud: No More Room in Hell \nAnimationer: Tactical Intervention" + +w = ws .. "wa2000" +L["wep." .. w .. ".name.full"] = "Walther WA 2000" +L["wep." .. w .. ".name"] = "WA 2000" +L["wep." .. w .. ".desc"] = "Elegant bullpup-krypskyttegevär med hög skada och eldhastighet. \nUtrustat med ett 12x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Namn är för vänner, och jag vill inte ha någon.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +L["wep." .. w .. ".credits"] = "Tillgångar: Alliance of Valiant Arms \nEgentligen portat till CS 1.6 av GR_Lucia \nLjud: HK & Vunsunta \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +-- Attachments (sd) +-- L["att.trigger_dual_uzis_semi.name.full"] = "att.trigger_semi.name.full" +-- L["att.trigger_dual_uzis_semi.name"] = "att.trigger_semi.name" +-- L["att.trigger_dual_uzis_semi.desc"] = "att.trigger_semi.desc" + +L["att.tac_1858_spin.name.full"] = "Revolver Spin" +L["att.tac_1858_spin.name"] = "Spin" +L["att.tac_1858_spin.desc"] = "wheeeeeeeeeeeee" + +L["att.optic_m1_scope.name.full"] = "M1-Karbin 3,5x24-Sikte" +L["att.optic_m1_scope.name"] = "3,5x Sikte" +L["att.optic_m1_scope.desc"] = "Riktmedel gjord med speciell montering till M1-Karbinen." + +L["att.optic_delisle_scope.name.full"] = "De Lisle 4x24-Sikte" +L["att.optic_delisle_scope.name"] = "4x Sikte" +L["att.optic_delisle_scope.desc"] = "Riktmedel med speciell montering till De Lisle." + +L["att.muzz_supp_assassin.name.full"] = "Lönnmördare Ljuddämpare" +L["att.muzz_supp_assassin.name"] = "L. Ljuddämp" +L["att.muzz_supp_assassin.desc"] = "Förlängd ljuddämpare som drastiskt förbättrar räckvidden med kostnad på stabilitet." + +L["att.ammo_gyrojet_ratshot.name.full"] = "13 mm Råttskott Mini-Robotar" +L["att.ammo_gyrojet_ratshot.name"] = "Råttskott" +L["att.ammo_gyrojet_ratshot.desc"] = "Närhetsaktiverade mini-splittergranatrobotar. Mot gnagare av oväntad storlek." + +L["att.ammo_gyrojet_pipe.name.full"] = "15 mm Förstärkta Rörgranater" +L["att.ammo_gyrojet_pipe.name"] = "Rör" +L["att.ammo_gyrojet_pipe.desc"] = "Tunga tidsinställda granater. Direkta träffar sprängs omedelbart." + +L["att.ammo_gyrojet_lv.name.full"] = "11 mm Låg Hastighets Mini-Robotar" +L["att.ammo_gyrojet_lv.name"] = "LH" +L["att.ammo_gyrojet_lv.desc"] = "Projektiler med sänkt diameter och hastighet som har ett mindre synligt spår." + +L["att.ammo_gyrojet_he.name.full"] = "13 mm Mini-Splittergranatrobotar" +L["att.ammo_gyrojet_he.name"] = "SGR" +L["att.ammo_gyrojet_he.desc"] = "Projektil med en mindre sprängladdning istället för patronhuvud." + +L["att.ammo_1858_45colt.name.full"] = "Remington 1858 .45 Colt Konvertering" +L["att.ammo_1858_45colt.name"] = ".45 Colt" +L["att.ammo_1858_45colt.desc"] = "Patronkonvertering skjuter större, mer kraftfulla, men mindre pålitliga skott." + +L["att.ammo_1858_36perc.name.full"] = "Remington 1858 .36 Tändhattkonvertering" +L["att.ammo_1858_36perc.name"] = ".36 Tändhatt" +L["att.ammo_1858_36perc.desc"] = "Kaliberkonvertering skjuter mindre skott med högre räckvidd." + +L["att.procon.yeehaw"] = "yeeeeeeeeeeeeehawwwwwwwwww" + +///////////////////// -- [[ Brute Force ]] -- +-- Weapons +ws = "tacrp_m_" +w = ws .. "bamaslama" +L["wep." .. w .. ".name"] = "Alabama Slammer" +L["wep." .. w .. ".desc"] = "En bowie-kniv döpt efter en cocktail. Har en sågtandad bakre kant och bajonettfäste." +L["wep." .. w .. ".desc.quote"] = "\"Sade jag att du kunde göra det där?\"" +L["wep." .. w .. ".credits"] = "Modell: Havok101 \nTexturer: Millenia" + +w = ws .. "bat" +L["wep." .. w .. ".name.full"] = "Loisville Slugger TPX" +L["wep." .. w .. ".name"] = "Slugger" +L["wep." .. w .. ".desc"] = "Aluminium basebollträ, perfekt att slå \"home runs\" eller skallar." +L["wep." .. w .. ".desc.quote"] = "\"Popquiz! Hur länge tar det att slå en dumskalle till döds?\"" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Yogensia \nAnimationer: Lazarus" + +w = ws .. "bayonet" +L["wep." .. w .. ".name"] = "M9 Phrobis III" +L["wep." .. w .. ".desc"] = "Militärstandard kniv som kan monteras på vapen som en bajonett. Inget som hindrar dig att använda den som en normal kniv dock." +L["wep." .. w .. ".credits"] = "Tillgångar: BrainBread 2" + +w = ws .. "boina" +L["wep." .. w .. ".name"] = "Cudeman Boina Verde" +L["wep." .. w .. ".desc"] = "Stadig, överdimensionerad överlevnadskniv. \"Boina verde\" är spanskt för \"grön basker\", ett försök att marknadsföra kniven som militärklass." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Shortez" + +w = ws .. "cleaver" +L["wep." .. w .. ".name"] = "Köttyxa" +L["wep." .. w .. ".desc"] = "Stor och kraftigt blad gjort för att hugga kött, antingen djur eller mänskligt kött. Har inbyggt Livstjuv." +L["wep." .. w .. ".credits"] = "Tillgångar: Warface" + +w = ws .. "crowbar" +L["wep." .. w .. ".name"] = "Kofot" +L["wep." .. w .. ".desc"] = "Mångsidigt verktyg gjord för att få upp grejer; lådor, kassaskåp, dörrar och människor!" +L["wep." .. w .. ".desc.quote"] = "\"Tror att du tappade den här där borta i Black Mesa.\"" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter-Strike: Online 2" + +w = ws .. "css" +L["wep." .. w .. ".name"] = "Klassikern" +L["wep." .. w .. ".desc"] = "En klassisk knivdesign från 90-talet innan snygga mönster och galna priser var normalt." +L["wep." .. w .. ".desc.quote"] = "\"Okej, nu kör vi!\"" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter-Strike: Source" + +w = ws .. "fasthawk" +L["wep." .. w .. ".name"] = "SOG Fasthawk" +L["wep." .. w .. ".desc"] = "Modern tomahawk gjord för strid. Aerodynamisk design kan kastas långt." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Yogensia" + +w = ws .. "gerber" +L["wep." .. w .. ".name.full"] = "Gerber LMF Infantry" +L["wep." .. w .. ".name"] = "LMF Infantry" +L["wep." .. w .. ".desc"] = "Kompakt kniv för överlevnadssituationer, som att rensa fisk, snida träd, eller rensa och snida en fiende." +L["wep." .. w .. ".credits"] = "Modell: Macaroane \nTexturer: Blackfire" + +w = ws .. "glock" +L["wep." .. w .. ".name.full"] = "Glock Feldmesser 78" +L["wep." .. w .. ".name"] = "Feldmesser 78" +L["wep." .. w .. ".desc"] = "Kraftig fältkniv med ett fast grepp, designad för användning av österrikes Jagdkommando." +L["wep." .. w .. ".desc.quote"] = "Ja, det är samma Glock som gör pistolerna." +L["wep." .. w .. ".credits"] = "Modell: HellSpike \nTexturer: Dr. Hubbler" + +w = ws .. "hamma" +L["wep." .. w .. ".name"] = "Hammare" +L["wep." .. w .. ".desc"] = "Vanligt arbetsverktyg för att få in spikar i träd eller fästa grejer på plats." +L["wep." .. w .. ".desc.quote"] = "Allt ser ut som en spik när du har en hammare..." +L["wep." .. w .. ".credits"] = "Modell: FearFisch & sHiBaN \nTexturer: Meltdown, sHiBaN & Kitteh" + +w = ws .. "harpoon" +L["wep." .. w .. ".name.full"] = "Extrema Ratio Harpun" +L["wep." .. w .. ".name"] = "Harpun" +L["wep." .. w .. ".desc"] = "Kniv med tunt blad och serrationer på bakre kanten för extra djup penetration." +L["wep." .. w .. ".credits"] = "Modell: Warfrog \nTexturer: kannoe" + +w = ws .. "heathawk" +L["wep." .. w .. ".name"] = "Heat Hawk" +L["wep." .. w .. ".desc"] = "Yxa-format MS-vapen skalad ned till människostorlek. \nOriginalet använder en överhettad kant som går igenom metall som smör, men denna replika har endast ett överhettat klistermärke på metallen." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Amouro" + +w = ws .. "incorp" +L["wep." .. w .. ".name"] = "Huggormskniv" +L["wep." .. w .. ".desc"] = "En schysst fällkniv med premium rostfritt utseende och träkornhandtag." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Teh Snake" + +w = ws .. "kitchen" +L["wep." .. w .. ".name"] = "Kökskniv" +L["wep." .. w .. ".desc"] = "Väldigt simpel kökskniv. Lätt att få tag på och tillräckligt dödlig, knivar som dessa är populära bland gängkriminella och psykopater. Har inbyggd Livstjuv." +L["wep." .. w .. ".desc.quote"] = "Polar'n, har du tillåtelse för den kniven?" +L["wep." .. w .. ".credits"] = "Modell: Kitteh \nTexturer: Rochenback" + +w = ws .. "knife3" +L["wep." .. w .. ".name"] = "Task Force-Kniv" +L["wep." .. w .. ".desc"] = "Överdimensionerad kniv med sågtandat blad och böjt handtag, lite överdrivet då det ska vara en militärkniv." +L["wep." .. w .. ".desc.quote"] = "\"MAMMA HÄMTA KAMERAN!!\"" +L["wep." .. w .. ".credits"] = "Modell: Syncing \nTexturer: Boba Fett" + +w = ws .. "kukri" +L["wep." .. w .. ".name"] = "Kukri" +L["wep." .. w .. ".desc"] = "Kort kniv med böjt handtag, speciellt gjord så den fångar måltavlor och gör maximal skada." +L["wep." .. w .. ".desc.quote"] = "\"Proffs har levnadsstandard.\"" +L["wep." .. w .. ".credits"] = "Modell: Loyen \nTexturer: Pain_Agent" + +w = ws .. "machete" +L["wep." .. w .. ".name"] = "Machete" +L["wep." .. w .. ".desc"] = "Mångsidigt blad som kan användas som jordbruksredskap, krigsvapen eller navigationshjälp när du är djupt i buskarna." +L["wep." .. w .. ".credits"] = "Tillgångar: BrainBread 2" + +w = ws .. "pan" +L["wep." .. w .. ".name"] = "Stekpanna" +L["wep." .. w .. ".desc"] = "Köksredskap till spisen gjord av gjutjärn. Välkänd som ett vapen genom film och tecknade serier." +L["wep." .. w .. ".credits"] = "Tillgångar: Left 4 Dead 2" + +w = ws .. "pipe" +L["wep." .. w .. ".name"] = "Blyrör" +L["wep." .. w .. ".desc"] = "Stabilt blyvattenrör. Trots att den inte gjordes för att göra ont, om du träffar någon med den så gör det ganska ont." +L["wep." .. w .. ".desc.quote"] = "\"FAN!! För sent!!\"" +L["wep." .. w .. ".credits"] = "Tillgångar: No More Room in Hell" + +w = ws .. "rambo" +L["wep." .. w .. ".name"] = "Bowie-Kniv" +L["wep." .. w .. ".desc"] = "Klassisk knivdesign gjord specifikt för duellering då knivslagsmål sågs som en form av underhållning. \nFortsätter vara populär tack vare dess sammanslutning med alltför maskulint 80-tals action filmstjärnor." +L["wep." .. w .. ".credits"] = "Modell: fallschirmjager \nTexturer: HellSpike & EinHain" + +w = ws .. "shovel" +L["wep." .. w .. ".name"] = "Spade" +L["wep." .. w .. ".desc"] = "En gammal arméspade, gjord för att snabbt gräva fram skyttegravar. Fungerar bra som ett grovt närstridsvapen, då den har både en trubbig sida och en vass kant." +L["wep." .. w .. ".desc.quote"] = "\"Maskar!\"" +L["wep." .. w .. ".credits"] = "Tillgångar: Day of Defeat: Source" + +w = ws .. "tonfa" +L["wep." .. w .. ".name"] = "Polis Batong" +L["wep." .. w .. ".desc"] = "Speciell polis batong med ett vinklat handtag för alternativt grepp. Användbar för att få slut på kravaller i misslyckande \"demokratiska\" länder." +L["wep." .. w .. ".credits"] = "Tillgångar: Left 4 Dead 2" + +w = ws .. "tracker" +L["wep." .. w .. ".name"] = "Spårkniv" +L["wep." .. w .. ".desc"] = "Överdimensionerad tarmkniv gjord som flertal jaktverktyg, en av dem är, typiskt sett, att skära upp människomål." +L["wep." .. w .. ".credits"] = "Modell: Cartman \nTexturer: Henron & Fxdarkloki" + +w = ws .. "wiimote" +L["wep." .. w .. ".name.full"] = "Nintendo Wii-Kontroller" +L["wep." .. w .. ".name"] = "Wii-Kontroll" +L["wep." .. w .. ".desc"] = "En ikonisk spelkontroller med revolutionär rörelsekontroll. \nFörsiktigt! Om du glömmer att ha på handledsremmen kan det leda till skador." +L["wep." .. w .. ".desc.quote"] = "Utslängd!" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Mr. Pickle" + +w = ws .. "wrench" +L["wep." .. w .. ".name"] = "Rörtång" +L["wep." .. w .. ".desc"] = "Stabil tång gjord för att täta till vatten- och gasrör. Konstruktion från heljärn gör den till ett bra trubbigt vapen." +L["wep." .. w .. ".credits"] = "Tillgångar: Counter-Strike: Online 2" + +///////////////////// -- [[ Iron Curtain ]] -- +-- Weapons +ws = "tacrp_ak_" +w = ws .. "aek971" +L["wep." .. w .. ".name"] = "AEK-971" +L["wep." .. w .. ".desc"] = "Experimentell automatkarbin med en unik dämpande mekanism som sänker känd rekyl. Hög eldhastighet med måttlig räckvidd." +L["wep." .. w .. ".trivia.manufacturer"] = "Kovrovskiy Mekhanicheskiy Zavod" +L["wep." .. w .. ".credits"] = "Tillgångar: Casper, arby26 \nAnimationer: Tactical Intervention" + +w = ws .. "ak12" +L["wep." .. w .. ".name.full"] = "AK-12 Prototyp" +L["wep." .. w .. ".name"] = "AK-12" +L["wep." .. w .. ".desc"] = "En av många försök att modernisera AK:n, denna experimentala modell använder salvoeld och tillåter snabbt växlande av vapnets kaliber." +L["wep." .. w .. ".desc.quote"] = "Snö Vargens ögon öppnas." +L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter Strike: Online 2 \nAnimationer: Tactical Intervention" + +w = ws .. "ak74" +L["wep." .. w .. ".name"] = "AK-74" +L["wep." .. w .. ".desc"] = "Välbalanserad råvara från östra europa med kontrollerbar rekyl och bra räckvidd. Modifikationer inkluderar lättare delar och montering för laxstjärt riktmedel." +L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta, TheLama \nTexturer: Millenia, The Spork \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "ak74u" +L["wep." .. w .. ".name"] = "AKS-74U" +L["wep." .. w .. ".desc"] = "Kulsprutepistol karbin designad för pansarbesättning och specialstyrkor. Imponerande stoppkraft i ett litet paket, men inte lika mild rekyl." +L["wep." .. w .. ".desc.quote"] = "\"Moder Ryssland kan ruttna, jag bryr mig icke.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +L["wep." .. w .. ".credits"] = "Modell: TheLama \nCompile: Bushmasta101 \nTexturer: Thanez \nLjud: BlitzBoaR/CC5/modderfreak, .exe \nAnimationer: Tactical Intervention" + +w = ws .. "an94" +L["wep." .. w .. ".name.full"] = "AN-94 \"Abakan\"" +L["wep." .. w .. ".name"] = "AN-94" +L["wep." .. w .. ".desc"] = "Experimentell automatkarbin med unikt 2-skott \"hyper-salvoeld\". Gevärets komplexa mekanism tillåter låg rekyl, men den är ganska massiv." +L["wep." .. w .. ".desc.quote"] = "\"Antje\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +L["wep." .. w .. ".credits"] = "Tillgångar: Firearms: Source \nAnimationer: Tactical Intervention" + +w = ws .. "galil_lmg" +L["wep." .. w .. ".name.full"] = "IMI Galil ARM" +L["wep." .. w .. ".name"] = "Galil ARM" +L["wep." .. w .. ".desc"] = "AK-härledd konfigurerad som en kulspruta. \nLätt och skjuter i högt och kontrollerbart tempo med måttlig stoppkraft." +L["wep." .. w .. ".desc.quote"] = "\"Tja, för min del så är händelsen juicen.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +L["wep." .. w .. ".credits"] = "Galil-Tillgångar: Counter Strike: Online 2 \nTillbehör: Insurgency (2014), portad av Lt. Rocky \nLjuddämpade Ljud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "galil_sniper" +L["wep." .. w .. ".name.full"] = "IMI Galil Krypskyttegevär" +L["wep." .. w .. ".name"] = "Galatz" +L["wep." .. w .. ".desc"] = "Israel-tillverkad AK-derivat konfigurerad som krypskyttegevär. \nVäldigt kontrollerbar, men har låg eldhastighet och måttlig dödlighet." +L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +L["wep." .. w .. ".credits"] = "Galil-Tillgångar: Counter Strike: Online 2 \nTillbehör: Insurgency (2014), portad av Lt. Rocky \nLjuddämpade Ljud: Magmacow \nAnimationer: Tactical Intervention" + +w = ws .. "rk95" +L["wep." .. w .. ".name.full"] = "Sako RK 95" +L["wep." .. w .. ".name"] = "RK 95" +L["wep." .. w .. ".desc"] = "Finsk AK-derivat med hög penetrationsförmåga och förlängt magasin." +L["wep." .. w .. ".desc.quote"] = "Trots dess utseende... Nja, detta är praktiskt taget en AK." +L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +L["wep." .. w .. ".credits"] = "Tillgångar: Firearms: Source \nAnimationer: Tactical Intervention" + +w = ws .. "svd" +L["wep." .. w .. ".name.full"] = "Dragunov SVD" +L["wep." .. w .. ".name"] = "SVD" +L["wep." .. w .. ".desc"] = "Ryskt krypskyttegevär med låg eldhastighet men bra skada och rekylkontroll. Utrustat med ett 6x kikarsikte som standard. Medans den ytligt likar AK:ns design så är den orelaterad mekaniskt sätt." +L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +L["wep." .. w .. ".credits"] = "Modell: Rafael De Jongh, Ettubrutesbro \nTexturer: WangChung \nLjud: Ghost597879, King Friday, iFlip \nAnimationer: Tactical Intervention" + +-- Attachments (ak) +L["att.acc_ak74_poly.name.full"] = "AK-74 Lätt Byggnad" +L["att.acc_ak74_poly.name"] = "Lätt" +L["att.acc_ak74_poly.desc"] = "Fallskärmsjägare konfiguration för ökad hantering och rörlighet." + +L["att.ammo_ak12_762.name.full"] = "AK-12 7,62 × 39 mm Modd Kit" +L["att.ammo_ak12_762.name"] = "7,62 × 39 mm" +L["att.ammo_ak12_762.desc"] = "Ladda en mer kraftfull patron som ökar både skadan och rekylen." + +L["att.muzz_ak_booster.name.full"] = "6P26 Mynningsbooster" +L["att.muzz_ak_booster.name"] = "Booster" +L["att.muzz_ak_booster.desc"] = "AK-mönster mynningsenhet som ökar eldhastigheten." + +L["att.muzz_ak_comp.name.full"] = "6P20 Kompensator" +L["att.muzz_ak_comp.name"] = "Kompensator" +L["att.muzz_ak_comp.desc"] = "AK-mönster mynningsenhet som rätar ut rekylen." + +L["att.muzz_supp_pbs.name.full"] = "PBS-5-Ljuddämpare" +L["att.muzz_supp_pbs.name"] = "PBS-5" +L["att.muzz_supp_pbs.desc"] = "AK-mönster ljuddämpare som förbättrar rekyl stabilitet med kostnad på träffsäkerhet." + +L["att.optic_ak_kobra.name.full"] = "Kobra-Sikte" +L["att.optic_ak_kobra.name"] = "Kobra" +L["att.optic_ak_kobra.desc"] = "Ryskt laxstjärt reflexsikte." + +L["att.optic_galil.name.full"] = "Nimrod 6x40-Sikte" +L["att.optic_galil.name"] = "Nimrod 6x40" +L["att.optic_galil.desc"] = "Kikarsikte designad att monteras på Galil-gevär." + +///////////////////// -- [[ Heavy Duty ]] -- +-- Weapons +ws = "tacrp_h_" +w = ws .. "dual_hardballers" +L["wep." .. w .. ".name"] = "Dual Silverballers" +L["wep." .. w .. ".desc"] = "Ett par snygga pistoler med långa slutstycken. Hög profilval för låg profil lönnmördare. Bra räckvidd för akimbo-vapen - om du vill träffa något, så klart." +L["wep." .. w .. ".desc.quote"] = "\"Jag söker efter rättvisan själv. Jag väljer själv sanningen jag gillar.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool." +L["wep." .. w .. ".credits"] = "Tillgångar: Terminator: Resistance (port av Sirgibsalot) \nLjud: Navaro \nAnimationer: Tactical Intervention, Fesiug" + +w = ws .. "executioner" +L["wep." .. w .. ".name.full"] = "Taurus Raging Judge" +L["wep." .. w .. ".name"] = "Executioner" +L["wep." .. w .. ".desc"] = "Massiv revolver som skjuter \"small-bore\" hagelskott. \nSkjuter mycket hagel, men har dålig spridning." +L["wep." .. w .. ".desc.quote"] = "\"Kom, mina vänner. Det är inte för sent att söka en nyare värld.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Taurus" +L["wep." .. w .. ".credits"] = "Huvudmodell: Firearms: Source, redigerad av speedonerd \nSkott & snabbladdare: Call of Duty: Black Ops II \nLjud: Call of Duty: Black Ops II \nAnimationer: Tactical Intervention" + +w = ws .. "hardballer" +L["wep." .. w .. ".name"] = "AMT Hardballer" +L["wep." .. w .. ".desc"] = "Pistol av rostfri stål konstruktion och med långt slutstycke. Träffsäker och träffar hårt på avstånd." +L["wep." .. w .. ".desc.quote"] = "\"Jag återkommer...\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool." +L["wep." .. w .. ".credits"] = "Tillgångar: Terminator: Resistance (port av Sirgibsalot) \nLjud: Navaro \nAnimationer: Tactical Intervention" + +w = ws .. "hk23e" +L["wep." .. w .. ".name.full"] = "HK HK21E" +L["wep." .. w .. ".name"] = "HK21E" +L["wep." .. w .. ".desc"] = "Bältmatad kulspruta version av ett klassiskt stridsgevär. Träffsäker och väldigt kraftfullt, men svår att använda under rörelse." +L["wep." .. w .. ".desc.quote"] = "\"Nu kan jag lösa upp till 800 problem i minuten!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: NZ-Reason \nAnimationer: Tactical Intervention, redigerad av Fesiug \nLjud: Treyarch, rzen1th" + +w = ws .. "intervention" +L["wep." .. w .. ".name.full"] = "CheyTac Intervention" +L["wep." .. w .. ".name"] = "Intervention" +L["wep." .. w .. ".desc"] = "Framtidsrik krypskytte-/pansarvärnsgevär hybrid med utmärkt penetrationsförmåga och mynningshastighet. \nUtrustat med ett 12x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "tävla 1mot1 på rust polarn du kan knappt quickscopa asså din fula jävel" +L["wep." .. w .. ".trivia.manufacturer"] = "CheyTac USA" +L["wep." .. w .. ".credits"] = "Modell: Syncing \nTexturer: Frizz925 \nLjud: Seven-Zero & Ghost \nAnimationer: Tactical Intervention" + +w = ws .. "jackhammer" +L["wep." .. w .. ".name.full"] = "Pancor Jackhammer" +L["wep." .. w .. ".name"] = "Jackhammer" +L["wep." .. w .. ".desc"] = "En-av-en massiv automatisk hagelbössa med cylindriskt magasin. Ganska tung och opålitlig." +L["wep." .. w .. ".desc.quote"] = "\"Moo säger jag!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Pancor Corporation" +L["wep." .. w .. ".credits"] = "Modell: Soldier11, redigerad av speedonerd (Framkorn) \nTexturer: Millenia \nLjud: Vunsunta \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "jdj" +L["wep." .. w .. ".name.full"] = "SSK .950 JDJ" +L["wep." .. w .. ".name"] = ".950 JDJ" +L["wep." .. w .. ".desc"] = "Otroligt massivt \"jaktgevär\" som skjuter en löjligt kraftfull patron. Har ingen aning om hur du ska kunna axelskjuta denna. \nUtrustat med ett 10x kikarsikte som standard." +L["wep." .. w .. ".trivia.manufacturer"] = "SSK Industries" +L["wep." .. w .. ".credits"] = "Modell: RedRogueXIII \nTexturer: Rafael De Jongh \nLjud: KillerExe_01 \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "nitrorifle" +L["wep." .. w .. ".name.full"] = "HH Double Rifle" +L["wep." .. w .. ".name"] = "Double Rifle" +L["wep." .. w .. ".desc"] = "Butik dubbelpipigt jaktgevär mot stora jakter som skjuter en patron gjord att stupa elefanter. Trots dess utseende så är det ingen hagelbössa." +L["wep." .. w .. ".desc.quote"] = "\"Livet är ett otroligt äventyr - acceptera det bara!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Holland & Holland" +L["wep." .. w .. ".credits"] = "Tillgångar: Far Cry 4 (Originellt portad av Robotnik) \nDiverse Ljud: speedonerd \nAnimationer: 8Z, speedonerd" + +w = ws .. "smaw" +L["wep." .. w .. ".name.full"] = "Mk 153 SMAW" +L["wep." .. w .. ".name"] = "SMAW" +L["wep." .. w .. ".desc"] = "Manportabel bunkerkrossare med söliga, kraftfulla robotar. Roboten kan styras när lasern är påslagen. Kan montera sikten och har inbyggt Corner-Cam." +L["wep." .. w .. ".desc.quote"] = "\"Detta tycker jag om dina bästa planer...\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Nammo Talley" +L["wep." .. w .. ".credits"] = "Tillgångar: Call of Duty: Modern Warfare 3 (2011) \nAnimationer: Tactical Intervention & speedonerd" + +w = ws .. "spas12" +L["wep." .. w .. ".name.full"] = "Franchi SPAS-12" +L["wep." .. w .. ".name"] = "SPAS-12" +L["wep." .. w .. ".desc"] = "Imponerande stridshagelbössa med dubbelläge hantering. Hög kraft och stabil rekyl. Vikbar kolv blockerar användning av sikten." +L["wep." .. w .. ".desc.quote"] = "\"Fel.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Luigi Franchi S.p.A." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Stoke \nLjud: iFlip & speedonerd \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "xm25" +L["wep." .. w .. ".name.full"] = "HK XM25 CDTE" +L["wep." .. w .. ".name"] = "XM25" +L["wep." .. w .. ".desc"] = "Bullpup-granatkastare med inbyggt avståndsmätande sikte, bra för medelavstånds täckande. Hög eldhastighet, men granaterna är ganska söliga och svaga." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Call of Duty: Modern Warfare 3 (2011) \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +-- Attachments (heavy_pack_atts) +L["att.trigger_spas_semi.name.full"] = "Franchi SPAS-12 Halvautomat Eld" +L["att.trigger_spas_semi.name"] = "Halv" +L["att.trigger_spas_semi.desc"] = "Växla till halvautomatisk eld som offrar stoppkraft mot eldhastighet." + +L["att.trigger_spas_freeman.name.full"] = "Half-Life 2 Dubbelskott" +L["att.trigger_spas_freeman.name"] = "Freeman" +L["att.trigger_spas_freeman.desc"] = "Alternativt mekanism som skjuter två skott samtidigt på något vänster..." + +L["att.sound_m200_mlg.name.full"] = "MLG Hög Rörlighets Gamer-Skott" +L["att.sound_m200_mlg.name"] = "MLG" +L["att.sound_m200_mlg.desc"] = "jupp, detta hamnar i en montage." + +L["att.hardballer_laser.name.full"] = "Hardballer Surefire Lasersikte" +L["att.hardballer_laser.name"] = "Lasersikte" +L["att.hardballer_laser.desc"] = "Primitivt skrymmande lasermodul som gör att siktet blir onödigt." + +L["att.ammo_smaw_tri.name.full"] = "SMAW \"Tri-Attack\"-Robotpodd" +L["att.ammo_smaw_tri.name"] = "Tri-Attack" +L["att.ammo_smaw_tri.desc"] = "En trio av snabba och manövrerbara anti-infanteri robotar." + +L["att.ammo_smaw_nikita.name.full"] = "SMAW \"Nikita\"-Robotpodd" +L["att.ammo_smaw_nikita.name"] = "Nikita" +L["att.ammo_smaw_nikita.desc"] = "Söligt manuellt kontrollerbar robot." + +L["att.ammo_smaw_tandem.name.full"] = "SMAW \"Tandem\"-Robotpodd" +L["att.ammo_smaw_tandem.name"] = "Tandem" +L["att.ammo_smaw_tandem.desc"] = "Kraftfull pansarvärnsrobot som tar tid att accelerera." + +L["att.ammo_smaw_agile.name.full"] = "SMAW Kolibri Mini-Robotpodd" +L["att.ammo_smaw_agile.name"] = "Kolibri" +L["att.ammo_smaw_agile.desc"] = "Aerodynamiska mini-robotar som accelererar när dem svänger." + +L["att.ammo_25mm_stun.name.full"] = "25 mm Stunstorm-Granater" +L["att.ammo_25mm_stun.name"] = "Stunstorm" +L["att.ammo_25mm_stun.desc"] = "Granater som, under en viss tid, oförmögnar måltavlan." + +L["att.ammo_25mm_airburst.name.full"] = "25 mm Airburst-Granater" +L["att.ammo_25mm_airburst.name"] = "Airburst" +L["att.ammo_25mm_airburst.desc"] = "Splittergranater som sprängs i luften. Stor radie men mindre dödlig." + +L["att.ammo_25mm_buckshot.name.full"] = "25 mm Flechette Granater" +L["att.ammo_25mm_buckshot.name"] = "Flechette" +L["att.ammo_25mm_buckshot.desc"] = "Granat med flat topp som har träffsäkra flechettepilar." + +L["att.ammo_25mm_heat.name.full"] = "25 mm Pansarspränggranater" +L["att.ammo_25mm_heat.name"] = "RSV" +L["att.ammo_25mm_heat.desc"] = "Granater designade att penetrera pansar och göra direkt skada." + +L["att.pro.trigger_spas_freeman1"] = "Dubbelt så skoj" +L["att.procon.nikita"] = "Manuell Kontroll (i siktet och Lasern PÅ)" + +///////////////////// -- [[ ExoOps ]] -- +-- Weapons +ws = "tacrp_eo_" +w = ws .. "93r" +L["wep." .. w .. ".name.full"] = "Beretta 93 Raffica" +L["wep." .. w .. ".name"] = "93R" +L["wep." .. w .. ".desc"] = "Premium pistol med snabb, icke-springande salvoeld och bra prestanda från höften." +L["wep." .. w .. ".desc.quote"] = "\"Faith, tåget! Ta tåget!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +L["wep." .. w .. ".credits"] = "Tillgångar: Alliance of Valiant Arms \nEgentligen gjord till CS 1.6 av GR_Lucia \nLjud: Vunsunta, Infinity Ward & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "acr" +L["wep." .. w .. ".name.full"] = "Bushmaster ACR" +L["wep." .. w .. ".name"] = "ACR" +L["wep." .. w .. ".desc"] = "Civilgevär erbjuden som ett avancerat alternativ till andra populära plattformar. Välbalanserat och kan använda diverse moderna ammunitionstyper." +L["wep." .. w .. ".desc.quote"] = "\"To slay a dragon is the greatest of honors.\"" +L["wep." .. w .. ".desc.quote"] = "\"Att slakta en drake är den största hedern.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Bushmaster Firearms LLC" +L["wep." .. w .. ".credits"] = "Modell: End of Days \nTexturer: IppE \nLjud: Strelok & XLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "ar70" +L["wep." .. w .. ".name.full"] = "Beretta SC-70/.223" +L["wep." .. w .. ".name"] = "SC-70" +L["wep." .. w .. ".desc"] = "Skrymmande automatkarbin med kontrollerbar eldhastighet." +L["wep." .. w .. ".desc.quote"] = "\"Är lite alltid såhär tufft eller bara när du är barn?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +L["wep." .. w .. ".credits"] = "Modell & Texturer: DaveW \nLjud: Vunsunta, xLongWayHome & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "auga1" +L["wep." .. w .. ".name.full"] = "Steyr AUG A1" +L["wep." .. w .. ".name"] = "AUG A1" +L["wep." .. w .. ".desc"] = "Klassisk AUG-version med ett större magasin och automateld.\nUtrustat med ett fast 1,5x sikte och reservsikten." +L["wep." .. w .. ".desc.quote"] = "\"Jag vill ha blod!\" \"Du kommer få det.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Arms" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Silentassassin12 \nSound & Animationer: Tactical Intervention" + +w = ws .. "browninghp" +L["wep." .. w .. ".name.full"] = "Browning Hi-Power" +L["wep." .. w .. ".name"] = "Browning HP" +L["wep." .. w .. ".desc"] = "Antik pistol med bra generell prestanda men låg eldhastighet." +L["wep." .. w .. ".desc.quote"] = "\"Vad i helskotta...?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Modell: Silvio Dante \nTexturer: WangChung \nLjud: Vunsunta, Strelok \nAnimationer: Tactical Intervention" + +w = ws .. "calico" +L["wep." .. w .. ".name.full"] = "Calico M950A" +L["wep." .. w .. ".name"] = "Calico" +L["wep." .. w .. ".desc"] = "Udda rymdliknande pistol med massivt spiralformat magasin." +L["wep." .. w .. ".desc.quote"] = "\"Jag har fler gisslan än du har varma middagar.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Calico Light Weapons Systems" +L["wep." .. w .. ".credits"] = "Tillgångar: Alliance of Valiant Arms \nSkena och framgrepp från Warface \nLjud: A.V.A., Warface, speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "dual_satana" +L["wep." .. w .. ".name"] = "Dueling Demons" +L["wep." .. w .. ".desc"] = "Ett par anpassader revolvrar. Bra träffsäkerhet för akimbo-vapen men sölig eldhastighet och omladdning." +L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +L["wep." .. w .. ".credits"] = "Modell: Soul_Slayer \nTexturer: Kimono \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "f2000" +L["wep." .. w .. ".name.full"] = "FN F2000" +L["wep." .. w .. ".name"] = "F2000" +L["wep." .. w .. ".desc"] = "Bullpup-karbin med hög eldhastighet och rörlighet, men ostabil rekyl tack vare dess ovanlig ergonomi.\nUtrustat med ett 1,6x sikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Sade du att jag måste vinna detta för Gipper:n?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Tillgångar: DICE (Battlefield 3) \nSiktets modell från CSO2 \nLjud: CSO2 & Vunsunta \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "fal" +L["wep." .. w .. ".name.full"] = "FN FAL" +L["wep." .. w .. ".name"] = "FAL" +L["wep." .. w .. ".desc"] = "Gammaldags stridsgevär med bra täckande eldkraft men väldigt hög rekyl." +L["wep." .. w .. ".desc.quote"] = "Fria världens högra arm." +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Modell: Pete3D \nTexturer: Millenia \nLjud: New World Interactive & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "g36c" +L["wep." .. w .. ".name.full"] = "HK G36C" +L["wep." .. w .. ".name"] = "G36C" +L["wep." .. w .. ".desc"] = "Kompakt karbinversion av G36:an som växlar effektiv räckvidd mot ökad eldhastighet och förbättrad hantering." +L["wep." .. w .. ".desc.quote"] = "\"Aja... vad fan för namn är \"Soap\"?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: TheLama \nTexturer: Thanez, FxDarkLoki \nSound & Animationer: Tactical Intervention" + +w = ws .. "hkcaws" +L["wep." .. w .. ".name"] = "HK CAWS" +L["wep." .. w .. ".desc"] = "Prototyp helautomatisk bullpup-hagelbössa med hög precision och penetrationsförmåga.\nUtrustat med ett fast 1,5x sikte." +L["wep." .. w .. ".desc.quote"] = "\"Vägen är slut, mutantjävel. Dags att dö.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Tillgångar: Millenia \nPortat från Fallout: New Vegas av speedonerd \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "howa" +L["wep." .. w .. ".name.full"] = "Howa Type 64" +L["wep." .. w .. ".name"] = "Type 64" +L["wep." .. w .. ".desc"] = "Japanskt stridsgevär som skjuter speciell patron med sänkt laddning. Betydande vikt men väldigt kontrollerbar." +L["wep." .. w .. ".desc.quote"] = "\"Priest-21, detta är Trebor. Klart att skjuta. Döda Wyvern.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Howa Machinery" +L["wep." .. w .. ".credits"] = "Tillgångar: Michau, portat från Fallout: New Vegas av 8sianDude \nLjud: speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "hushpup" +L["wep." .. w .. ".name.full"] = "SW Mk 22 Mod 0" +L["wep." .. w .. ".name"] = "Mk 22" +L["wep." .. w .. ".desc"] = "Gammaldags pistol till specialstyrkor designad för tysta operationer." +L["wep." .. w .. ".desc.quote"] = "\"Påbörjar operation Snake Eater.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +L["wep." .. w .. ".credits"] = "Modell: Stoke \nTexturer: Dayofdfeat12, redigerad av speedonerd \nLjud: oneshotofficial & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "izzyfal" +L["wep." .. w .. ".name.full"] = "DSA SA58 Light-Barrel" +L["wep." .. w .. ".name"] = "FAL Izzy" +L["wep." .. w .. ".desc"] = "Civilversion av det ikoniska stridsgeväret. Lite tung, men har utmärkt stoppkraft och räckvidd." +L["wep." .. w .. ".trivia.manufacturer"] = "DS Arms" +L["wep." .. w .. ".credits"] = "Modell: Pete3D \nTexturer: Enron \nLjud: New World Interactive & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "jericho" +L["wep." .. w .. ".name.full"] = "Jericho 941" +L["wep." .. w .. ".name"] = "Jericho" +L["wep." .. w .. ".desc"] = "Kraftig 9 mm pistol med bra rörlighet och hög eldhastighet. Marknadsförd som \"Baby Eagle\" för dess ytlig likhet till Desert Eagle." +L["wep." .. w .. ".desc.quote"] = "\"Vi ses, rymd-cowboy...\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +L["wep." .. w .. ".credits"] = "Modell: philibuster \nTexturer: oyman \nLjud: xLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "l85" +L["wep." .. w .. ".name.full"] = "Enfield L85A2" +L["wep." .. w .. ".name"] = "L85A2" +L["wep." .. w .. ".desc"] = "Brittiskt bullpup-gevär med måttlig prestanda och välkända pålitlighetsproblem.\nUtrustat med ett 3x sikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"SA80. Perfekta delen av den brittiska armén...\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Royal Ordnance" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Milo, redigerad av speedonerd \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "m3" +L["wep." .. w .. ".name"] = "Benelli M3" +L["wep." .. w .. ".desc"] = "Halvautomatisk hagelbössa med bra rekylkontroll och träffsäkerhet." +L["wep." .. w .. ".trivia.manufacturer"] = "Benelli Armi S.p.A." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Kimono \nLjud: Cas., Tactical Intervention \nAnimationer: Tactical Intervention" + +w = ws .. "m29" +L["wep." .. w .. ".name.full"] = "SW Model 29 \"Satan\"" +L["wep." .. w .. ".name"] = "M29" +L["wep." .. w .. ".desc"] = "Anpassad magnum-revolver med tung avtryckare men hög skada över distans." +L["wep." .. w .. ".desc.quote"] = "\"Känner du dig tursam, tuffing?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +L["wep." .. w .. ".credits"] = "Modell: Soul_Slayer \nTexturer: Kimono \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "m60" +L["wep." .. w .. ".name"] = "M60" +L["wep." .. w .. ".desc"] = "Tung kulspruta med intensiv stoppkraft men låg eldhastighet. Döpt till \"Grisen\" för dess betydande tyngd." +L["wep." .. w .. ".desc.quote"] = "\"Lev för inget eller dö för något. Du bestämmer.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "General Dynamics" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Millenia \nLjud: xLongWayHome, Lain & rzen1th \nAnimationer: Tactical Intervention" + +w = ws .. "m712" +L["wep." .. w .. ".name.full"] = "M712 Schnellfeuer" +L["wep." .. w .. ".name"] = "M712" +L["wep." .. w .. ".desc"] = "Gammaldags automatpistol med bra träffsäkerhet men dålig rekylkontroll. Effektiv vid kort salvoeld." +L["wep." .. w .. ".desc.quote"] = "Slutstycket kanske träffar dig i nyllet." +L["wep." .. w .. ".trivia.manufacturer"] = "Mauser" +L["wep." .. w .. ".credits"] = "Tillgångar: Battlefield: Korea \nEgentligen portat till CS 1.6 av GR_Lucia \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "m733" +L["wep." .. w .. ".name.full"] = "Colt Model 733" +L["wep." .. w .. ".name"] = "M733" +L["wep." .. w .. ".desc"] = "AR-15 med underkarbin längd och hög men svår kontrollerbar eldhastighet. Fast bärhandtag begränsar sikte alternativ." +L["wep." .. w .. ".desc.quote"] = "\"Jag gör det jag gör bäst; rån.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta & The End \nTexturer: Acid Snake & JamesM \nLjud: Vunsunta & Teh Strelok \nAnimationer: Tactical Intervention" + +w = ws .. "masada" +L["wep." .. w .. ".name.full"] = "Magpul Masada" +L["wep." .. w .. ".name"] = "Masada" +L["wep." .. w .. ".desc"] = "Modernt gevär med lätt polymer konstruktion." +L["wep." .. w .. ".desc.quote"] = "\"Du kan få tömma världen på järn så börjar jag sälja träklubbor.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Magpul Indutries" +L["wep." .. w .. ".credits"] = "Modell: End of Days \nTexturer: IppE \nLjud: Strelok & XLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "mcx" +L["wep." .. w .. ".name.full"] = "SIG MCX SPEAR" +L["wep." .. w .. ".name"] = "MCX SPEAR" +L["wep." .. w .. ".desc"] = "Stridsgevär designad för diverse infanteri roller. Speciell ammunition har hög penetrationsförmåga och behåller skadan över avstånd." +L["wep." .. w .. ".desc.quote"] = "\"Bravo Six, in i mörkret.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer Inc." +L["wep." .. w .. ".credits"] = "Modell & Texturer: Akinaro & Farengar \nLjud: Infinity Ward, speedonerd, XLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "megastar" +L["wep." .. w .. ".name.full"] = "Star Megastar" +L["wep." .. w .. ".name"] = "Megastar" +L["wep." .. w .. ".desc"] = "Stor pistol med tuff konstruktion. Bra kapacitet och stoppkraft men har intensiv rekyl." +L["wep." .. w .. ".desc.quote"] = "\"Vill du mucka med mig?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Star Bonifacio Echeverria" +L["wep." .. w .. ".credits"] = "Modell: General Tso \nTexturer: the_tub \nLjud: oneshotofficial & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "mg42" +L["wep." .. w .. ".name"] = "MG 42" +L["wep." .. w .. ".desc"] = "Gammaldags kulspruta med en otroligt snabb eldhastighet.\nSkjutande från axeln rekommenderas ej." +L["wep." .. w .. ".desc.quote"] = "Varje best har dess egna historia." +L["wep." .. w .. ".trivia.manufacturer"] = "Großfuß AG" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Red Orchestra 2 \nLjud: Red Orechesta 2, rzen1th \nAnimationer: Tactical Intervention" + +w = ws .. "mp5k" +L["wep." .. w .. ".name.full"] = "HK MP5K" +L["wep." .. w .. ".name"] = "MP5K" +L["wep." .. w .. ".desc"] = "Kompakt version av ikoniska kulsprutepistol. Välbalanserad, men den byter precision och kontrollen från dess helstora motsvarighet mot förbättrad hantering." +L["wep." .. w .. ".desc.quote"] = "\"Vapen. Många vapen.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Thanez \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "mp5sd" +L["wep." .. w .. ".name.full"] = "HK MP5SD6" +L["wep." .. w .. ".name"] = "MP5SD6" +L["wep." .. w .. ".desc"] = "En inbyggd ljuddämpad version av den ikoniska kulsprutepistolen. Sänkt räckvidd, men har låg rekyl och inget synligt spårljus." +L["wep." .. w .. ".desc.quote"] = "\"Fri eld.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Geno & Mr. Brightside \nLjud: Lakedown, Teh Sterlok \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "mpx" +L["wep." .. w .. ".name.full"] = "SIG MPX" +L["wep." .. w .. ".name"] = "MPX" +L["wep." .. w .. ".desc"] = "Avancerad kulsprutepistol med stabil rekyl och förlängt magasin." +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +L["wep." .. w .. ".credits"] = "Tillgångar: Contract Wars \nEgentligen portat till CS 1.6 av GR_Lucia \nAnimation: Tactical Intervention" + +w = ws .. "p7" +L["wep." .. w .. ".name.full"] = "HK P7" +L["wep." .. w .. ".name"] = "P7" +L["wep." .. w .. ".desc"] = "Kompakt sidovapen med snabb hantering men dålig räckvidd." +L["wep." .. w .. ".desc.quote"] = "\"Vem sade att vi var terrorister?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Lama \nTexturer: The_Tub \nLjud: Optical Snare & One Shot \nAnimationer: Tactical Intervention" + +w = ws .. "p99" +L["wep." .. w .. ".name.full"] = "Walther P99" +L["wep." .. w .. ".name"] = "P99" +L["wep." .. w .. ".desc"] = "Välbalanserad pistol med snabb eldhastighet." +L["wep." .. w .. ".desc.quote"] = "\"Jaga fram dem alla och döda dem, en efter en.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +L["wep." .. w .. ".credits"] = "Modell: Afterburner \nTexturer: NCFurious \nLjud: KingFriday, Vunsunta & speedonerd \nAnimationer: Tactical Intervention" + +w = ws .. "p210" +L["wep." .. w .. ".name.full"] = "SIG P210" +L["wep." .. w .. ".name"] = "P210" +L["wep." .. w .. ".desc"] = "Elegant efterkrigs-, enkelstaplat, haneskjuten pistol.\nLite opålitlig tack vare dess ålder." +L["wep." .. w .. ".desc.quote"] = "\"Tror du att jag kommer få fred på denna värld?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer" +L["wep." .. w .. ".credits"] = "Modell: Silvio Dante \nTexturer: Twinke Masta \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "psg1" +L["wep." .. w .. ".name.full"] = "HK PSG-1" +L["wep." .. w .. ".name"] = "PSG-1" +L["wep." .. w .. ".desc"] = "Elegant halvautomatiskt krypskyttegevär med ojämförlig precision och rekylkontroll. Utrustat med ett 8x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Jag föddes på ett slagfält. Växte upp på ett slagfält.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta & fallschrimjager \nTexturer: Twinke Masta \nLjud: Navaro, Vunsunta, FxDarkLoki \nAnimationer: Tactical Intervention, redigerad av speedonerd" + +w = ws .. "rhino20ds" +L["wep." .. w .. ".name.full"] = "Chiappa Rhino 20DS" +L["wep." .. w .. ".name"] = "Rhino 20DS" +L["wep." .. w .. ".desc"] = "Modern \"snub-nose\"-revolver med hexagonal cylinder. Liten kaliber och \"low-bore\"-pipa gör vapnet snabbt och lätt att skjuta." +L["wep." .. w .. ".desc.quote"] = "[ KOM IHÅG DET VI LOVADE ]" +L["wep." .. w .. ".trivia.manufacturer"] = "Chiappa Firearms" +L["wep." .. w .. ".credits"] = "Modell & Texturer: MirzaMiftahulFadillah, redigerad av 8Z \nLjud: Ghost597879, Tatwaffe, The Wastes Mod \nAnimationer: Tactical Intervention" + +w = ws .. "scarl" +L["wep." .. w .. ".name.full"] = "FN SCAR-L" +L["wep." .. w .. ".name"] = "SCAR-L" +L["wep." .. w .. ".desc"] = "Modulär och lätt automatkarbin med måttlig eldhastighet." +L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +L["wep." .. w .. ".credits"] = "Tillgångar: Counter-Strike: Online 2" + +w = ws .. "scout" +L["wep." .. w .. ".name"] = "Steyr Scout" +L["wep." .. w .. ".desc"] = "Lätt gevär designad för portabilitet och komfort istället för direkt stoppkraft. Har inbyggt skjutstöd.\nUtrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Headshot!\" \"Humiliation!\" " +L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Mannlicher" +L["wep." .. w .. ".credits"] = "Modell: Twinke Masta \nTexturer: Thanez \nLjud: iFlip, Vunsunta, Unbreakable \nAnimationer: Tactical Intervention" + +w = ws .. "sg510" +L["wep." .. w .. ".name.full"] = "SIG SG 510-1" +L["wep." .. w .. ".name"] = "SG 510" +L["wep." .. w .. ".desc"] = "Gammaldags stridsgevär med utmärkt räckvidd och precision. Rekylen är stark men stabil, tack vare vapnets vikt." +L["wep." .. w .. ".desc.quote"] = "\"Han vågar väldigt mycket, han.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +L["wep." .. w .. ".credits"] = "Tillgångar: World of Guns: Disassembly \nLjud: Sledgehammer Games \nAnimationer: Tactical Intervention" + +w = ws .. "spas15" +L["wep." .. w .. ".name.full"] = "Franchi SPAS-15" +L["wep." .. w .. ".name"] = "SPAS-15" +L["wep." .. w .. ".desc"] = "Tung och magasinmatad efterträdare till den ikoniska dubbelläge hagelbössan. Låg rekyl men inte lika träffsäker." +L["wep." .. w .. ".desc.quote"] = "\"Vi får vänta. Väntar och se vad han kommer fram med.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Luigi Franchi S.p.A." +L["wep." .. w .. ".credits"] = "Tillgångar: filosoma, portad från Fallout: New Vegas av Kindred Flame \nLjud: Navaro, SevenZero & Magmacow \nAnimationer: Tactical Intervention & speedonerd" + +w = ws .. "winchester" +L["wep." .. w .. ".name.full"] = "Winchester M1873" +L["wep." .. w .. ".name"] = "M1873" +L["wep." .. w .. ".desc"] = "Ikoniskt gevär som oftast kopplas till cowboys och fortfarande bland civila skytten. Rålig att skjuta, men är grundligt utklassad av moderna eldvapen." +L["wep." .. w .. ".desc.quote"] = "Vapnet som vann västern." +L["wep." .. w .. ".trivia.manufacturer"] = "Winchester Repeating Arms" +L["wep." .. w .. ".credits"] = "Modell: Enron \nTexturer: !NC!Furious \nLjud: Vunsunta \nAnimationer: speedonerd" + +-- Attachments (exoops) +L["att.ammo_modular_65gren.name.full"] = "ACR 6,5 mm Grendel Modd Kit" +L["att.ammo_modular_65gren.name"] = "Grendel" +L["att.ammo_modular_65gren.desc"] = "Modifikation för att ladda patroner med ökad ballistik." + +L["att.ammo_modular_450bm.name.full"] = "ACR .450 Bushmaster Modd Kit" +L["att.ammo_modular_450bm.name"] = "Bushmaster" +L["att.ammo_modular_450bm.desc"] = "Modifikation att ladda lägre kapacitet, högre kraft magnum-patroner." + +L["att.bolt_spas15_pump.name.full"] = "Franchi SPAS-15 Pumprepeter" +L["att.bolt_spas15_pump.name"] = "Pumprepeter" +L["att.bolt_spas15_pump.desc"] = "Växla till pumprepeter hantering som offrar eldhastighet mot förbättrad kontroll." + +L["att.trigger_mk22_locked.name.full"] = "Mk 22 Låst Slutstycke" +L["att.trigger_mk22_locked.name"] = "Låst Sluts." +L["att.trigger_mk22_locked.desc"] = "Lås slutstycket när du skjuter för att sänka ljudet." + +L["att.optic_howa_scope.name.full"] = "Howa Type 64 2,2x DMR-Sikte" +L["att.optic_howa_scope.name"] = "Sikte" +L["att.optic_howa_scope.desc"] = "Proprietärt kikarsikte till Type 64:an." + +L["att.optic_g36c_scope.name.full"] = "G36C Inbyggt Sikte" +L["att.optic_g36c_scope.name"] = "Inb. Sikte" +L["att.optic_g36c_scope.desc"] = "Inbyggt sikte med låg kraft och bärhandtag till G36C:n." + +L["att.ammo_scout_376.name.full"] = "Scout .376 Steyr Modd Kit" +L["att.ammo_scout_376.name"] = ".376 Steyr" +L["att.ammo_scout_376.desc"] = "Modifikation för att ladda en unik jaktpatron med hög stoppkraft." + +///////////////////// -- [[ Scavenger's Spoils ]] -- +-- Weapons +ws = "tacrp_pa_" +w = ws .. "auto5" +L["wep." .. w .. ".name.full"] = "Browning Auto 5" +L["wep." .. w .. ".name"] = "Auto 5" +L["wep." .. w .. ".desc"] = "Gammaldags automatisk hagelbössa. Patroner med lägre kaliber har bra räckvidd och eldhastighet, men dålig stoppkraft." +L["wep." .. w .. ".desc.quote"] = "\"Få upp dem och skjut ned dem.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Browning Arms" +L["wep." .. w .. ".credits"] = "Modell: RedRogueXIII, An Aggressive Napkin \nTexturer: Futon \nLjud: Futon \nAnimationer: Tactical Intervention" + +w = ws .. "automag3" +L["wep." .. w .. ".name.full"] = "AMT AutoMag III" +L["wep." .. w .. ".name"] = "AutoMag III" +L["wep." .. w .. ".desc"] = "Pistol av rostfri stål matad i en ovanlig patron. Inte lika kraftfull som en magnum-pistol, men har bra kapacitet." +L["wep." .. w .. ".desc.quote"] = "\"Du var alltid den bästa. Ingen har någonsin varit nära.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Degenerate Dak \nLjud: Navaro \nAnimationer: Tactical Intervention" + +w = ws .. "awp" +L["wep." .. w .. ".name.full"] = "AI AWM-F" +L["wep." .. w .. ".name"] = "AWM" +L["wep." .. w .. ".desc"] = "Tuff magnum-krypskyttegevär med oöverträffad kraft och precision. En anti-terrorists favorit.\nUtrustat med ett 12x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Han är galen, en forskare, och en krypskytt.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Accuracy International" +L["wep." .. w .. ".credits"] = "Modell: Hav0c \nTexturer: Bullethead & Kimono \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "coachgun" +L["wep." .. w .. ".name.full"] = "Stoeger Coachgun" +L["wep." .. w .. ".name"] = "Coachgun" +L["wep." .. w .. ".desc"] = "Dubbelpipig hagelbössa från gamle västern.\nBesvärlig och inte träffsäker, men har bra stoppkraft." +L["wep." .. w .. ".desc.quote"] = "Lås, kolv, och två rökande pipor." +L["wep." .. w .. ".trivia.manufacturer"] = "Stoeger" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Fistful of Frags (Macaroane, Paul68Rageous, Tigg) \nLjud: Fistful of Frags, rzen1th \nAnimationer: 8Z" + +w = ws .. "cz75" +L["wep." .. w .. ".name.full"] = "CZ-75 Automatic" +L["wep." .. w .. ".name"] = "CZ-75" +L["wep." .. w .. ".desc"] = "Automatisk version av den avgörande \"Wonder Nine\".\nSkjuter snabbt och kontrollerbart, men låg kapacitet." +L["wep." .. w .. ".desc.quote"] = "Höjdpunkten i halvautomatisk sidovapen evolutionen." +L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Arby \nLjud: Hk, Vunsunta, xLongWayHome, Strelok, Cas, IceFluxx \nAnimationer: Tactical Intervention" + +w = ws .. "dual_makarov" + +L["wep." .. w .. ".name"] = "Dual Makarovs" +L["wep." .. w .. ".desc"] = "Ett par sovjetiska pistoler för att kompensera den låga stoppkraften med endast en. Minimal rekyl men dödligheten är dålig förbi nära avstånd." +L["wep." .. w .. ".desc.quote"] = "\"Slutför jobbet, James! Spräng sönder dem!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Izhevsk Mechanical Plant" +L["wep." .. w .. ".credits"] = "Tillgångar: TehSnake \nLjud: Hk, Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "fnmag" +L["wep." .. w .. ".name"] = "FN MAG" +L["wep." .. w .. ".desc"] = "Kulspruta med praktiskt taget inten rörlighet men kompenserar med otrolig stoppkraft och kapacitet." +L["wep." .. w .. ".desc.quote"] = "\"Du kan inte lämna mig här med dessa... djur.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Fabrique National" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Call to Arms \nAnimationer: Tactical Intervention \nPortat av: Arctic" + +w = ws .. "fort12" +L["wep." .. w .. ".name"] = "Fort-12" +L["wep." .. w .. ".desc"] = "Kosteffektiv ukrainsk pistol som sällan ses utanför östeuropa. Låg stoppkraft, men pålitlig och kan möjligtvis överleva ett antal övernaturliga katastrofer." +L["wep." .. w .. ".desc.quote"] = "Hitta Strelok. Döda Strelok?" +L["wep." .. w .. ".trivia.manufacturer"] = "RPC Fort" +L["wep." .. w .. ".credits"] = "Tillgångar: S.T.A.L.K.E.R. (portad av modderfreak) \nAnimationer: Tactical Intervention" + +w = ws .. "hipoint" +L["wep." .. w .. ".name.full"] = "Hi-Point 995" +L["wep." .. w .. ".name"] = "Hi-Point" +L["wep." .. w .. ".desc"] = "Ökänd halvautomatisk pistolkaliber karbin.\nGanska kraftfull... när den fungerar, vilket den aldrig gör." +L["wep." .. w .. ".desc.quote"] = "\"Bra moderliv har haft dåliga söner.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Hi-Point Firearms" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Yumyumshisha \nLjud: Obsidian \nAnimationer: Tactical Intervention" + +w = ws .. "ithaca" +L["wep." .. w .. ".name"] = "Ithaca 37" +L["wep." .. w .. ".desc"] = "Gammaldags hagelbössa som har sett båda polisiär och militär användning. Har möjligheten till snabb \"slam-fire\" men har dålig spridning och låg patronkapacitet." +L["wep." .. w .. ".desc.quote"] = "\"Vad? Det var uppenbart! Han var den röda Spionen!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Ithaca Gun Company" +L["wep." .. w .. ".credits"] = "Modell: Millenia \nTexturer: Poux \nLjud: Strelok \nAnimationer: Tactical Intervention" + +w = ws .. "klin" +L["wep." .. w .. ".name.full"] = "PP-9 Klin" +L["wep." .. w .. ".name"] = "Klin" +L["wep." .. w .. ".desc"] = "Simpel, lätt och billig automatpistol.\nHar hög eldhastighet men kan få eldavbrott." +L["wep." .. w .. ".desc.quote"] = "\"Eyy, viktignyy? DRA Å' HELVETE. Ponyl?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Izhmash" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Teh Snake \nAnimationer: Tactical Intervention" + +w = ws .. "ksg12" +L["wep." .. w .. ".name.full"] = "KelTec KSG" +L["wep." .. w .. ".name"] = "KSG" +L["wep." .. w .. ".desc"] = "En udda bullpup-, pumprepeter hagelbössa med dubbla magasinrör. Super hög kapacitet kompenserad med vikt." +L["wep." .. w .. ".desc.quote"] = "\"Hörru! Sluta stirra på tjejerna.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Kel-Tec CNC Industries" +L["wep." .. w .. ".credits"] = "Tillgångar: Alliance of Valiant Arms \nLjud: Infinity Ward & Navarro \nAnimationer: Tactical Intervention, edited by speedonerd" + +w = ws .. "lapd" +L["wep." .. w .. ".name.full"] = "LAPD 2019 Blaster" +L["wep." .. w .. ".name"] = "LAPD 2019" +L["wep." .. w .. ".desc"] = "Vapensmedskapelse baserad på ett ikoniskt filmvapen som har utmärkt hantering och inbyggt lasersikte." +L["wep." .. w .. ".desc.quote"] = "\"En väldig upplevelse att leva i fruktan, eller hur? Så är det att vara en slav.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Steyr, Charter Arms" +L["wep." .. w .. ".credits"] = "Tillgångar: Fallout: New Vegas \nAnimationer: Tactical Intervention, Fesiug, 8Z" + +w = ws .. "lewis" +L["wep." .. w .. ".name"] = "Lewiskulspruta" +L["wep." .. w .. ".desc"] = "Grytmatad, vattenkyld, lätt kulspruta med ganska litet magasin." +L["wep." .. w .. ".desc.quote"] = "\"Ur vägen, allihopa!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Lewis Automatic Machine Gun Company" +L["wep." .. w .. ".credits"] = "Tillgångar: Verdun \nAnimationer: Tactical Intervention" + +w = ws .. "luty" +L["wep." .. w .. ".name.full"] = "Kulsprutepistol Luty" +L["wep." .. w .. ".name"] = "KPist. Luty" +L["wep." .. w .. ".desc"] = "Hemmagjord KPist. gjord som en protest mot brittiska vapenregler. Början av salvoeld har ökad eldhastighet som kompenserar vapnets dåliga träffsäkerhet." +L["wep." .. w .. ".desc.quote"] = "\"Låt oss aldrig dyka under den ondska läran av anti-vapenrörelsen.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "P.A. Luty" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Vasily \nLjud: New World Interactive, Firearms: Source \nAnimationer: Tactical Intervention" + +w = ws .. "m79" +L["wep." .. w .. ".name"] = "M79" +L["wep." .. w .. ".desc"] = "Granatkastare med träkolv och lång pipa från djungeln. Träffsäker och har snabb mynningshastighet, men betydande vikt." +L["wep." .. w .. ".desc.quote"] = "\"Goooooooooooooood morgon, Vietnam!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +L["wep." .. w .. ".credits"] = "Modell: EdisLeado \nTexturer: Millenia \nLjud: Firearms: Source \nAnimationer: speedonerd & 8Z" + +w = ws .. "m202" +L["wep." .. w .. ".name.full"] = "M202 FLASH" +L["wep." .. w .. ".name"] = "M202" +L["wep." .. w .. ".desc"] = "Raketgevär med fyra brandskott. Raketerna tänder fyr på måltavlor men har låg skada och sprängradie." +L["wep." .. w .. ".desc.quote"] = "\"Skjut och glöm!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Northrop Electro-Mechanical Division" +L["wep." .. w .. ".credits"] = "Tillgångar: Tactical Intervention" + +w = ws .. "madsen" +L["wep." .. w .. ".name"] = "Madsen M-50" +L["wep." .. w .. ".desc"] = "Kosteffektiv dansk kulsprutepistol med pålitlig stoppkraft men obekväm ergonomi. Ett sikte kan inte monteras tack vare det toppmonterade laddhandtaget." +L["wep." .. w .. ".desc.quote"] = "\"Avstår du från djävulen... och alla hans verk?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Dansk Industri Syndikat" +L["wep." .. w .. ".credits"] = "Modell & Texturer: jesamabin \nLjud: xLongWayHome \nAnimationer: Tactical Intervention, 8Z" + +w = ws .. "makarov" +L["wep." .. w .. ".name.full"] = "Makarov PM" +L["wep." .. w .. ".name"] = "PM" +L["wep." .. w .. ".desc"] = "Massproducerat sovjetiskt sidovapen designad att bäras ofta och sällan skjutas. Dödligheten är dålig förbi nära avstånd." +L["wep." .. w .. ".desc.quote"] = "\"Du lyssnar på Apocalypse radio. Den enda radiostationen i närheten.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Izhevsk Mechanical Plant" +L["wep." .. w .. ".credits"] = "Tillgångar: TehSnake \nLjud: Hk, Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "mosin" +L["wep." .. w .. ".name.full"] = "Mosin-Nagant 91/30" +L["wep." .. w .. ".name"] = "M91/30" +L["wep." .. w .. ".desc"] = "Massproducerat sovjetiskt cylinderrepetergevär. Infanteri modellen är snabbare men mindre träffsäker; utrusta ett sikte för ökad träffsäkerhet med kostnad på eldhastighet." +L["wep." .. w .. ".desc.quote"] = "\"En utav två får ett gevär, den utan följer honom!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Red Orchestra 2 \nLjud: Red Orchestra 2, rzen1th \nAnimationer: Cry of Fear, Lazarus, 8Z" + +w = ws .. "oa93" +L["wep." .. w .. ".name.full"] = "Olympic Arms OA-93" +L["wep." .. w .. ".name"] = "OA-93" +L["wep." .. w .. ".desc"] = "AR-15-härledd pistol med toppmonterat laddhandtag och inget bufferrör. Designad att kringgå lagen, men denna är ironiskt sett moddad att skjuta i automateld." +L["wep." .. w .. ".desc.quote"] = "\"Det mest absurda ingenjörsätt att säga 'fan ta dig!' jag någonsin sett.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Olympic Arms" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Tuuttipingu, edited by 8Z \nLjud: NightmareMutant, Teh Strelok \nAnimationer: Tactical Intervention" + +w = ws .. "obrez" +L["wep." .. w .. ".name.full"] = "Remington 700 \"Obrez\"" +L["wep." .. w .. ".name"] = "Obrez" +L["wep." .. w .. ".desc"] = "Avsågat jaktgevär byggt av dem sidovapen bristna för att vara en döljbar sidovapen ersättning. Modifiering skadar träffsäkerhet och räckvidd prestandan." +L["wep." .. w .. ".desc.quote"] = "\"Yippee-yay, det blir inga bröloppsklockor idag...\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms, Bubba" +L["wep." .. w .. ".credits"] = "Tillgångar: Bethesda Game Studios \nAnimationer: Tactical Intervention \nPortat av: Arctic" + +w = ws .. "ots33" +L["wep." .. w .. ".name.full"] = "OTs-33 Pernach" +L["wep." .. w .. ".name"] = "OTs-33" +L["wep." .. w .. ".desc"] = "Rysk automatpistol med låg rekyl och hög rörlighet, designad för paramilitära styrkor." +L["wep." .. w .. ".desc.quote"] = "\"Hoppas detta svider.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "KBP Instrument Design Bureau" +L["wep." .. w .. ".credits"] = "Modell: Kimono \nTexturer: Kimono, Millenia \nLjud: iFlip, Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "p2a1" +L["wep." .. w .. ".name.full"] = "HK P2A1" +L["wep." .. w .. ".name"] = "P2A1" +L["wep." .. w .. ".desc"] = "Enkelskott signalpistol för signalerings- och belysningsanvändning.\nStandard patron har en svag brand explosion. Kan ladda diverse skott, inklusive hagelskott." +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Modell & Texturer: New World Interactive \nLjud: Raising The Bar: Redux, speedonerd \nAnimationer: 8Z" + +w = ws .. "ppsh" +L["wep." .. w .. ".name"] = "PPSh-41" +L["wep." .. w .. ".desc"] = "Sovjetisk kulsprutepistol med hög eldhastighet och ganska massiv men opålitlig trummagasin. Träffsäkerhet finns inte och är valfritt." +L["wep." .. w .. ".desc.quote"] = "Ура!" +L["wep." .. w .. ".trivia.manufacturer"] = "Flertal" +L["wep." .. w .. ".credits"] = "Modell: Tripwire Interactive \nTexturer: Rus_Ivan \nAnimationer: Tactical Intervention, edited by speedonerd \nLjud: Rus_Ivan" + +w = ws .. "python" +L["wep." .. w .. ".name.full"] = "Colt Python" +L["wep." .. w .. ".name"] = "Python" +L["wep." .. w .. ".desc"] = "Kvalitet revolver ned väldigt bra precision och rå kraft.\nFör att hanteras med en hand, cowboy-style." +L["wep." .. w .. ".desc.quote"] = "\"När du behöver skjuta, skjut. Prata inte.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell: Aggressive Napkin \nTexturer: Teh Snake \nLjud: Vunsunta, XLongWayHome \nAnimationer: Tactical Intervention" + +w = ws .. "rhino60ds" +L["wep." .. w .. ".name.full"] = "Chiappa Rhino 60DS" +L["wep." .. w .. ".name"] = "Rhino 60DS" +L["wep." .. w .. ".desc"] = "Modern revolver med lång pipa och hexagonell cylinder.\nSkrymmande och har en tung avtryckare, men har utmärkt rekylkontroll." +L["wep." .. w .. ".desc.quote"] = "\"Simpelt: överspecialisera, och du förökar dig i svaghet. Det är en sölig död.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Chiappa Firearms" +L["wep." .. w .. ".credits"] = "Tillgångar: Warface, portad av Dorian15 \nAnimationer: Tactical Intervention" + +w = ws .. "sako85" +L["wep." .. w .. ".name"] = "Sako 85" +L["wep." .. w .. ".desc"] = "Simpelt och tufft jaktgevär utan rum för taktiskt skräp. Utrustat med ett justerbart 6x kikarsikte." +L["wep." .. w .. ".desc.quote"] = "Skaffa en tjej som älskar dig för din hjäääärna." +L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +L["wep." .. w .. ".credits"] = "Tillgångar: No More Room In Hell \nAnimationer: Tactical Intervention" + +w = ws .. "scorpionevo" +L["wep." .. w .. ".name.full"] = "CZ Scorpion EVO 3" +L["wep." .. w .. ".name"] = "Scorpion EVO" +L["wep." .. w .. ".desc"] = "Modern kulsprutepistol med extremt hög eldhastighet och intensiv rekyl." +L["wep." .. w .. ".desc.quote"] = "Förvirra inte den med vz. 61." +L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +L["wep." .. w .. ".credits"] = "Tillgångar: Warface, Ghost1592365 \nLjud: Vunsunta \nAnimationer: Tactical Intervention" + +w = ws .. "shorty" +L["wep." .. w .. ".name.full"] = "Serbu Super Shorty" +L["wep." .. w .. ".name"] = "Shorty" +L["wep." .. w .. ".desc"] = "En anpassad, ultra kort hagelbössa, gjord för extrem döljbarhet, men offrar träffsäkerhet och kapacitet. Passar i sidovapen hölstrar." +L["wep." .. w .. ".desc.quote"] = "\"Behöver du en facelift, lille pojk?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Serbu Firearms" +L["wep." .. w .. ".credits"] = "Tillgångar: Millennia \nAnimationer: Tactical Intervention \nPortat av: Arctic" + +w = ws .. "sks" +L["wep." .. w .. ".name"] = "SKS" +L["wep." .. w .. ".desc"] = "Sovjetisk halvautomatiskt gevär gjord för att kompletteras med AK:n. Välbalanserad eldvapen för måttligt avstånd." +L["wep." .. w .. ".desc.quote"] = "\"Andas inte.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arsenal" +L["wep." .. w .. ".credits"] = "Tillgångar: Firearms: Source \nAnimationer: Tactical Intervention" + +w = ws .. "smle" +L["wep." .. w .. ".name.full"] = "Lee-Enfield Mk III*" +L["wep." .. w .. ".name"] = "Lee-Enfield" +L["wep." .. w .. ".desc"] = "Massproducerat brittiskt cylinderrepetergevär med hög kapacitet." +L["wep." .. w .. ".desc.quote"] = "\"Det var så simpelt.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Royal Small Arms Factory" +L["wep." .. w .. ".credits"] = "Tillgångar: Cry of Fear \nAnimationer: Cry of Fear, Lazarus" + +w = ws .. "stg44" +L["wep." .. w .. ".name.full"] = "Sturmgewehr 44" +L["wep." .. w .. ".name"] = "StG 44" +L["wep." .. w .. ".desc"] = "Gammaldags automatkarbin, ansedd av många att vara den första. Ganska kraftfull, men hög chans för eldavbrott." +L["wep." .. w .. ".desc.quote"] = "\"Mein karbin dödade dig inte? Jag får använda mer gas! Mer gas!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "C.G. Haenel" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Tuuttipingu \nLjud: Navaro, Project Reality" + +w = ws .. "svt40" +L["wep." .. w .. ".name"] = "SVT-40" +L["wep." .. w .. ".desc"] = "Massproducerat halvautomatiskt gevär från andra världskriget. Opålitlig och har begränsade sikte alternativ, men har fortfarande rå kraft." +L["wep." .. w .. ".desc.quote"] = "\"Det är ett tecken på att tyskarna börjar skita ner sig!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arsenal" +L["wep." .. w .. ".credits"] = "Modell: MrRifleman \nTexturer: BuLL5H1T" + +w = ws .. "svu" +L["wep." .. w .. ".name.full"] = "Dragunov SVU" +L["wep." .. w .. ".name"] = "SVU" +L["wep." .. w .. ".desc"] = "Moderniserad bullpup-modell av SVD:n. Har bra hantering men måttlig effektiv räckvidd.\nUtrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Det handlar om perspektiv.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "KBP Instrument Design Bureau" +L["wep." .. w .. ".credits"] = "Tillgångar: B0T \nLjud: NightmareMutant, sHiBaN, xLongWayHome \nAnimationer: speedonerd" + +w = ws .. "sw10" +L["wep." .. w .. ".name.full"] = "SW Model 10" +L["wep." .. w .. ".name"] = "SW M10" +L["wep." .. w .. ".desc"] = "Ikonisk revolver som gynnas av både snutar och kriminella.\nMatad i en icke-magnum-patron, så den är lätt att hantera men inte lika kraftfull." +L["wep." .. w .. ".desc.quote"] = "\"Vem är 'vi', polarn?\" \"Smith och Wesson, och jag.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +L["wep." .. w .. ".credits"] = "Modell: Harry Ridgeway, MediocrityGoggles \nTexturer: Millenia \nLjud: Bethesda, Obsidian \nAnimationer: Tactical Intervention" + +w = ws .. "sw686" +L["wep." .. w .. ".name.full"] = "SW Model 686" +L["wep." .. w .. ".name"] = "SW M686" +L["wep." .. w .. ".desc"] = "Magnum-revolver med balanserad, pålitlig prestanda." +L["wep." .. w .. ".desc.quote"] = "När det inte finns rum kvar i helvetet så kommer dem döda gå på jorden." +L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +L["wep." .. w .. ".credits"] = "Tillgångar: No More Room In Hell \nAnimationer: Tactical Intervention" + +w = ws .. "toz34" +-- Only has a .name.full because the weapon does. +L["wep." .. w .. ".name.full"] = "TOZ-34" +L["wep." .. w .. ".name"] = "TOZ-34" +L["wep." .. w .. ".desc"] = "Dubbelpipig jakthagelbössa. Har måttlig träffsäkerhet och dödlighet, men dess tyngd och sölig omladdning gör den olämplig för strid." +L["wep." .. w .. ".desc.quote"] = "\"A nu, chiki briki i v damki!\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Tulsky Oruzheiny Zavod" +L["wep." .. w .. ".credits"] = "Modell: SAM61 \nLjud: rzen1th \nAnimationer: speedonerd & 8Z" + +w = ws .. "toz106" +-- Only has a .name.full because the weapon does. +L["wep." .. w .. ".name.full"] = "TOZ-106" +L["wep." .. w .. ".name"] = "TOZ-106" +L["wep." .. w .. ".desc"] = "Cylinderrepeter jakthagelbössa. Hagelskott i liten kaliber har utmärkt träffsäkerhet men är inte lika dödliga." +L["wep." .. w .. ".desc.quote"] = "\"Huvud, ögon.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Tulsky Oruzheiny Zavod" +L["wep." .. w .. ".credits"] = "Modell: RusMarine85 \nAnimationer: Tactical Intervention \nPortat av: Arctic" + +w = ws .. "uzi" +L["wep." .. w .. ".name.full"] = "IMI Uzi" +L["wep." .. w .. ".name"] = "Uzi" +L["wep." .. w .. ".desc"] = "Efterkrigs-kulsprutepistol med otrolig kontrollbarhet. En av världens mest ikoniska vapen någonsin." +L["wep." .. w .. ".desc.quote"] = "\"Du kan dina vapen, polarn.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Israeli Military Industries" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Firearms: Source \nLjud: Ghost597879, Ganryu, Strelok, Sticer \nAnimationer: Tactical Intervention" + +w = ws .. "vykhlop" +L["wep." .. w .. ".name.full"] = "VKS \"Vykhlop\"" +L["wep." .. w .. ".name"] = "VKS" +L["wep." .. w .. ".desc"] = "Subsoniskt krypskyttegevär med hög kapacitet och eldhastighet, men låg mynningshastighet och dålig hantering. Utrustat med ett 6x kikarsikte som standard." +L["wep." .. w .. ".desc.quote"] = "\"Na'am seyidi, al qanas ala al khatt.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "TsKIB SOO" +L["wep." .. w .. ".credits"] = "Modell & Texturer: Call to Arms \nAnimationer: Tactical Intervention, Arctic" + +w = ws .. "woodsman" +L["wep." .. w .. ".name.full"] = "Colt Woodsman" +L["wep." .. w .. ".name"] = "Woodsman" +L["wep." .. w .. ".desc"] = "Sportpistol i liten kaliber. Lätt och kontrollerbar nog att användas med en hand, men dödligheten är låg." +L["wep." .. w .. ".desc.quote"] = "\"Men herre gud, är det där en .22:a?\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +L["wep." .. w .. ".credits"] = "Modell: Kimono, Faffout \nTexturer: Crash \nLjud: Vunsunta, DMG, Strelok" + +-- Attachments (ak) +L["att.optic_sako85_4x.name.full"] = "Sako 85 4x Zoom-Räckvidd" +L["att.optic_sako85_4x.name"] = "4x" +L["att.optic_sako85_4x.desc"] = "Sänkt magnifiering i siktet." + +L["att.optic_sako85_8x.name.full"] = "Sako 85 8x Zoom-Räckvidd" +L["att.optic_sako85_8x.name"] = "8x" +L["att.optic_sako85_8x.desc"] = "Ökad magnifiering i siktet." + +L["att.optic_sako85_irons.name"] = "Järn & Korn" +L["att.optic_sako85_irons.desc"] = "Tar bort kikarsiktet för snabbare siktetid och bättre rörlighet." + +L["att.ammo_sako85_222.name.full"] = "Sako 85 .222 Winchester Modd Kit" +L["att.ammo_sako85_222.name"] = ".222 Win" +L["att.ammo_sako85_222.desc"] = "Ladda mellanliggande patroner med sänkt rekyl och dödlighet." + +L["att.ammo_sako85_300mag.name.full"] = "Sako 85 .300 Winchester Magnum Modd Kit" +L["att.ammo_sako85_300mag.name"] = ".300 Win Mag" +L["att.ammo_sako85_300mag.desc"] = "Ladda magnum-patroner för ökad dödlighet." + +L["att.optic_svt_pu.name.full"] = "SVT-40 3,5x PU-Sikte" +L["att.optic_svt_pu.name"] = "PU" +L["att.optic_svt_pu.desc"] = "Sikte med låg kraft med speciell montering till SVT-40:n." + +L["att.ammo_m202_smoke.name.full"] = "M202 Rök Raketer" +L["att.ammo_m202_smoke.name"] = "Rök" +L["att.ammo_m202_smoke.desc"] = "Raketer som producerar en täckande rökskärm vid träff." + +L["att.ammo_m202_apers.name.full"] = "M202 Hornet-Raketer" +L["att.ammo_m202_apers.name"] = "Hornet" +L["att.ammo_m202_apers.desc"] = "Splittergranat raketer som sprängs i luften dör direkt eld antipersonal användning." + +L["att.ammo_m202_harpoon.name.full"] = "M202 Harpun Raketer" +L["att.ammo_m202_harpoon.name"] = "Harpun" +L["att.ammo_m202_harpoon.desc"] = "Skjuter brinnande harpuner som gör otrolig skada vid träff." + +L["att.ammo_m202_he.name.full"] = "M202 Pansarsprängraketer" +L["att.ammo_m202_he.name"] = "RSV" +L["att.ammo_m202_he.desc"] = "Raketer med en explosiv laddning." + +L["att.optic_mosin_irons.name.full"] = "Mosin-Nagant Krypskytte Slutstycke" +L["att.optic_mosin_irons.name"] = "Krypskytt." +L["att.optic_mosin_irons.desc"] = "Använd krypskyttegevär slutstycket utan ett sikte som ökar träffsäkerheten." + +L["att.optic_mosin_pu.name.full"] = "Mosin-Nagant 3,5x PU-Sikte" +L["att.optic_mosin_pu.name"] = "PU" +L["att.optic_mosin_pu.desc"] = "Sidomonterat sikte med låg kraft till Mosin-Nagant:en." + +L["att.optic_mosin_pem.name.full"] = "Mosin-Nagant 6x PEM-Sikte" +L["att.optic_mosin_pem.name"] = "PEM" +L["att.optic_mosin_pem.desc"] = "Sidomonterat kikarsikte till Mosin-Nagant:en." + +L["att.optic_mosin_pe.name.full"] = "Mosin-Nagant 4x PE-Sikte" +L["att.optic_mosin_pe.name"] = "PE" +L["att.optic_mosin_pe.desc"] = "Toppmonterat sikte för måttligt avstånd till Mosin-Nagant:en." + +L["att.muzz_mosin_bayonet.name.full"] = "Mosin-Nagant Spikbajonett" +L["att.muzz_mosin_bayonet.name"] = "Bajonett" +L["att.muzz_mosin_bayonet.desc"] = "För att knivhugga fascist skitstövlar." + +L["att.muzz_svu_supp.name.full"] = "Dragunov SVU Ljuddämpare" +L["att.muzz_svu_supp.name"] = "SVU Ljud." +L["att.muzz_svu_supp.desc"] = "Vapenspecifik ljuddämpar som ökar eldhastigheten." + +L["att.muzz_sks_bayonet.name.full"] = "SKS Vikbar Bajonett" +L["att.muzz_sks_bayonet.name"] = "Bajonett" +L["att.muzz_sks_bayonet.desc"] = "För att knivhugga kapitalist skitstövlar." + +L["att.tac_cz75_mag.name.full"] = "CZ-75 Reservmagasin" +L["att.tac_cz75_mag.name"] = "Reservmag" +L["att.tac_cz75_mag.desc"] = "Ett extra magasin monterat på vapnet för sinnesfrid." + +L["att.barrel_coachgun_short.name.full"] = "Coachgun Korta Pipor" +L["att.barrel_coachgun_short.name"] = "Kort" +L["att.barrel_coachgun_short.desc"] = "Ganska förkortad pipa för motstånd på nära avstånd." + +L["att.ammo_automag3_30carbine.name.full"] = "AutoMag III .30 Carbine Modd Kit" +L["att.ammo_automag3_30carbine.name"] = ".30 Carbine" +L["att.ammo_automag3_30carbine.desc"] = "Ladda en karbinpatron för förbättrad träffsäkerhet och räckvidd." + +L["att.optic_smle_no32.name.full"] = "Lee Enfield No. 32 Teleskopiskt Sikte" +L["att.optic_smle_no32.name"] = "No. 32 Sikte" +L["att.optic_smle_no32.desc"] = "Toppmonterat sikte för måttligt avstånd till Lee-Enfield:en." + +L["att.ammo_p2a1_incendiary.name.full"] = "P2A1 Brandskott" +L["att.ammo_p2a1_incendiary.name"] = "Brand" +L["att.ammo_p2a1_incendiary.desc"] = "Signalskott med större sprängning men utan belysning." + +L["att.ammo_p2a1_smoke.name.full"] = "P2A1 Rökskott" +L["att.ammo_p2a1_smoke.name"] = "Rök" +L["att.ammo_p2a1_smoke.desc"] = "Signalskott som skapar en liten rökskärm vid träff." + +L["att.ammo_p2a1_para.name.full"] = "P2A1 Belysningskott" +L["att.ammo_p2a1_para.name"] = "Belysning" +L["att.ammo_p2a1_para.desc"] = "Vita signalskott med litet fallskärm som lyser upp ett område när den faller." + +L["att.ammo_p2a1_buckshot.name.full"] = "P2A1 Magnum Hagelskott" +L["att.ammo_p2a1_buckshot.name"] = "Magnum" +L["att.ammo_p2a1_buckshot.desc"] = "Släng in lite hagelskott i din signalpistol för direkt stoppkraft." + +L["att.ammo_p2a1_bird.name.full"] = "P2A1 Fågelskott" +L["att.ammo_p2a1_bird.name"] = "Fågelskott" +L["att.ammo_p2a1_bird.desc"] = "Släng in lite fågelskott i din signalpistol. Otrolig spridning men svårt att missa." + +L["att.ammo_p2a1_slug.name.full"] = "P2A1 Sluggpatroner" +L["att.ammo_p2a1_slug.desc"] = "Släng in lite sluggpatroner i din signalpistol. Korta pipan begränsar träffsäkerheten och räckvidden." + +L["att.ammo_p2a1_frag.name.full"] = "P2A1 Splittergranat Patroner" +L["att.ammo_p2a1_frag.name"] = "Splitter" +L["att.ammo_p2a1_frag.desc"] = "Förvandla din signalpistol in till en knockoff granatpistol." + +L["att.ammo_p2a1_flashbang.name.full"] = "P2A1 Zvezda-Chockgranatskott" +L["att.ammo_p2a1_flashbang.name"] = "Zvezda" +L["att.ammo_p2a1_flashbang.desc"] = "Chockgranatbutik i din ficka. Används bäst runt hörn." + +L["att.ammo_p2a1_breach.name.full"] = "P2A1 Dörrbrytande Patroner" +L["att.ammo_p2a1_breach.name"] = "Bryt" +L["att.ammo_p2a1_breach.desc"] = "Ladda speciella dörrbrytande sluggpatroner för dörrbrytning i fickpaket." + +L["att.ammo_p2a1_confetti.name.full"] = "P2A1 Konfetti Patroner" +L["att.ammo_p2a1_confetti.name"] = "Konfetti" +L["att.ammo_p2a1_confetti.desc"] = "Till firanden. Yippie!" + +L["att.procon.illumradius"] = "Belysningsradie" +L["att.procon.noflare"] = "Inga Signalskott" + +///////////////////// -- [[ One-Offs ]] -- +-- Weapons +ws = "tacrp_sp_" +w = ws .. "sg510speedo" +L["wep." .. w .. ".name.full"] = "SG 510 \"Black Shark\"" +L["wep." .. w .. ".name"] = "Black Shark" +L["wep." .. w .. ".desc"] = "Anpassad SG 510 med en avsågad pipa och seriös mynningsbroms som producerar låg rekyl och en spektakulär mynningseld." +L["wep." .. w .. ".desc.quote"] = "\"Min rustning är svart. Betyder inte att hjärtat är det också.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Vanderbilt Company" +L["wep." .. w .. ".credits"] = "Specialbyggd av speedonerd \nTillgångar: World of Guns: Disassembly, Tactical Intervention \nLjud: Sledgehammer Games, speedonerd" + +w = ws .. "mp5_zeroeight" +L["wep." .. w .. ".name.full"] = "MP5/10 \"Zero Eight\"" +L["wep." .. w .. ".name"] = "Zero Eight" +L["wep." .. w .. ".desc"] = "Anpassad 10 mm MP5:a med Swordfish-kit, prototyp framgrepp och trummagasin. Tung mynningsbroms förbättrar rekylhanteringen och ser tuff ut också." +L["wep." .. w .. ".desc.quote"] = "\"Läxan till dig är att inte ens pröva.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Neptunium Arms" +L["wep." .. w .. ".credits"] = "Speciellt redigerad av speedonerd och 8Z \nMP5: Mr. Brightside, Stoke, Twinkie Masta, FxDarkloki \nTillbehör: Treyarch, BlackSpot Entertainment, Crytek \nLjud: Strelok, CS:O2" + +w = ws .. "hecate_vinierspecial" +L["wep." .. w .. ".name.full"] = "PGM Hécate II \"Kingbreaker\"" +L["wep." .. w .. ".name"] = "The Kingbreaker" +L["wep." .. w .. ".desc"] = "Anpassad Hécate II med en mammut ljuddämpare, anpassat 16x kikarsikte med avståndsmätare, och ett upproriskt meddelande skrivet på vapnets sida." +L["wep." .. w .. ".desc.quote"] = "\"Betala en man tillräckligt så går han barfota till helvetet.\"" -- TODO: Change this to the new quote! +L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +L["wep." .. w .. ".credits"] = "Specialbyggd av speedonerd till VinierAardvark1 \nHécate II Modell: Toby Burnside \nYtterligare Tillgångar: Treyarch, Infinity Ward, valterjherson1, Unselles, speedonerd" + +w = ws .. "usp_valencespecial" +L["wep." .. w .. ".name.full"] = "HK USP \"The Governor\"" +L["wep." .. w .. ".name"] = "The Governor" +L["wep." .. w .. ".desc"] = "En USP Elite utsmyckad med tävlingsdelar och matad i .45 Super för ökad stoppkraft. Utmärkt prestanda på avstånd, men inte stadig under rörelse." +L["wep." .. w .. ".desc.quote"] = "\"Högljudd nog att knocka dig.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +L["wep." .. w .. ".credits"] = "Specialbyggd av speedonerd till Valence \nOriginalmodell: Thanez, Racer445, fxdarkloki \nYtterligare Tillgångar: Battlestate Games, Crytek \nLjud: Vunsunta, BlitzBoaR" + +w = ws .. "tudspecial" +L["wep." .. w .. ".name.full"] = "Desert Eagle \"Arbiter\"" +L["wep." .. w .. ".name"] = "Arbiter" +L["wep." .. w .. ".desc"] = "Guldbelagd Desert Eagle i en mock-karbin konfiguration och den oheliga förmågan av automateld." +L["wep." .. w .. ".desc.quote"] = "\"Detta är det och det där är det där.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +L["wep." .. w .. ".credits"] = "Specialbyggd av speedonerd till Tud \nDeagle: Vasht, Racer445 \nYtterligare Tillgångar: kriboez, cR45h, Engys Epangelmatikes, Vitevius" + +w = ws .. "sr25_bladespecial" +L["wep." .. w .. ".name.full"] = "KAC SR-25 \"Symbiosis\"" +L["wep." .. w .. ".name"] = "Symbiosis" +L["wep." .. w .. ".desc"] = "SR-25 med inbyggd ljuddämpare matad till .338 Lapua Magnum och har ett justerbart 8x kikarsikte med avståndsmätare." +L["wep." .. w .. ".desc.quote"] = "\"Det finns inga gudar. Enda mannen i skyn är jag.\"" +L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +L["wep." .. w .. ".credits"] = "Specialbyggd av speedonerd till Bladelordomega \nSR-25: Firearms: Source \nYtterligare Tillgångar: Battlestate Games, Treyarch, Crytek, kriboez, cR45h" + +L["hint.tac.bladespecial"] = "Växla Zoom" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_zh-cn.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_zh-cn.lua new file mode 100644 index 0000000..f8978ef --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/content_zh-cn.lua @@ -0,0 +1,2917 @@ +L = {} -- Chinese Content Strings by 8Z + +///////////////////// -- [[ TacRP Base ]] -- +-- QuickNades +-- L["quicknade.fuse"] = "FUSE:" + +-- L["quicknade.frag.name.full"] = "Frag Grenade" +-- L["quicknade.frag.name"] = "FRAG" +-- L["quicknade.frag.dettype"] = "Timed: 2s" +-- L["quicknade.frag.desc"] = "Standard explosive grenade spraying shrapnel in a medium radius.\n\nTypically not lethal by itself, but can wound targets or flush them out of cover." + +-- L["quicknade.flash.name.full"] = "Flashbang" +-- L["quicknade.flash.name"] = "FLASH" +-- L["quicknade.flash.dettype"] = "Timed: 1.5s" +-- L["quicknade.flash.desc"] = "Emits a bright flash and deafening bang that disorients targets (hence its name).\n\nSlows affected targets, and deals minor damage in a large radius." + +-- L["quicknade.smoke.name.full"] = "Smoke Grenade" +-- L["quicknade.smoke.name"] = "SMOKE" +-- L["quicknade.smoke.dettype"] = "Timed: 2s" +-- L["quicknade.smoke.desc"] = "Emits a concealing cloud of smoke that lasts about 20 seconds.\n\nDeals no damage whatsoever, and is commonly used to cover an advance or to obscure a line of sight." + +-- L["quicknade.gas.name.full"] = "CS Gas Grenade" +-- L["quicknade.gas.name"] = "GAS" +-- L["quicknade.gas.dettype"] = "Timed: 2s" +-- L["quicknade.gas.desc"] = "Emits a cloud of tear gas that lasts about 15 seconds.\n\nAnyone caught within will take non-lethal lingering damage and have trouble keeping their weapon steady.\n\nIt is a chemical weapon banned by the Geneva Convention and is ABSOLUTELY NOT FART GAS." + +-- L["quicknade.fire.name.full"] = "Thermite Grenade" +-- L["quicknade.fire.name"] = "FIRE" +-- L["quicknade.fire.dettype"] = "Timed: 3s" +-- L["quicknade.fire.desc"] = "Sticks to targets and burns intensely for about 8 seconds, dealing damage within a small radius.\n\nWhile thermite is typically used to burn through materiel, it is also useful for area denial." + +-- L["quicknade.c4.name.full"] = "C4 Charge" +-- L["quicknade.c4.name"] = "C4" +-- L["quicknade.c4.dettype"] = "Remote" +-- L["quicknade.c4.desc"] = "A brick of powerful explosives that can be touched off by a detonator remotely.\n\nC4 is remarkably inert, but the signalling device can be removed or destroyed, defusing the charge." + +-- L["quicknade.nuke.name.full"] = "Nuclear Device" +-- L["quicknade.nuke.name"] = "NUKE" +-- L["quicknade.nuke.dettype"] = "Remote" +-- L["quicknade.nuke.desc"] = "Briefcase-sized micro nuclear bomb that can be touched off by a detonator remotely.\n\nIts explosive outcome needs no description." + +-- L["quicknade.breach.name.full"] = "Breaching Charge" +-- L["quicknade.breach.name"] = "BREACH" +-- L["quicknade.breach.dettype"] = "Timed: 2s / Remote" +-- L["quicknade.breach.desc"] = "Shaped charge made to bust through doors and weak walls.\n\nSmall blast radius, but will destroy any door it is attached to and hurt targets on the other side with its shockwave.\n\nWhen holding a detonator, the charge is configured to detonate remotely." + +-- L["quicknade.heal.name.full"] = "Medi-Smoke Can" +-- L["quicknade.heal.name"] = "HEAL" +-- L["quicknade.heal.dettype"] = "Timed: 5s" +-- L["quicknade.heal.desc"] = "Emits a cloud of restorative gas for about 15 seconds.\n\nMedical nanites restores health when inhaled. If recipients are healthy, any armor they have can be repaired or recharged.\n\nHas the opposite effect on necrotics, dealing damage instead." + +-- L["quicknade.rock.name.full"] = "Rock" +-- L["quicknade.rock.name"] = "ROCK" +-- L["quicknade.rock.dettype"] = "Blunt Trauma" +-- L["quicknade.rock.desc"] = "Possibly the first weapon ever used by humans.\n\nUse as last resort, for ancient capital punishments, or for violent pranks.\n\nResourceful as you are, there's no telling what else you can pull out of your pants in a pinch..." + +-- L["quicknade.bump.name.full"] = "Bump Mine" +-- L["quicknade.bump.name"] = "BUMP" +-- L["quicknade.bump.dettype"] = "Pressure Plate" +-- L["quicknade.bump.desc"] = "Magnetized mine that sticks to surfaces.\n\nCreates a harmless explosion that pushes everything away, hurting targets by sending them into walls or floors.\n\nCan be used to launch yourself very far, but remember to bring a parachute." + +-- L["quicknade.t-smk.name.full"] = "Smoke Grenade" +-- L["quicknade.t-smk.name"] = "T-SMK" +-- L["quicknade.t-smk.dettype"] = "Timed: 2s" +-- L["quicknade.t-smk.desc"] = "Terrorist-issue smoke grenade.\n\nCreates a smokescreen." + +-- L["quicknade.t-dcb.name.full"] = "Discombobulator" +-- L["quicknade.t-dcb.name"] = "T-DCB" +-- L["quicknade.t-dcb.dettype"] = "Timed: 3s" +-- L["quicknade.t-dcb.desc"] = "Terrorist-issue concussion grenade.\n\nDoes no damage, but creates a blast that pulls props in and pulls players out." + +-- L["quicknade.t-inc.name.full"] = "Incendiary Grenade" +-- L["quicknade.t-inc.name"] = "T-INC" +-- L["quicknade.t-inc.dettype"] = "Timed: 2s" +-- L["quicknade.t-inc.desc"] = "Terrorist-issue incendiary grenade.\n\nExplodes with minor damage, and starts fires in an area." + +-- Weapons +local ws = "tacrp_" +local w = ws .. "ak47" +-- L["wep." .. w .. ".name.full"] = "FB Beryl 96" +-- L["wep." .. w .. ".name"] = "Beryl 96" +-- L["wep." .. w .. ".desc"] = "Easy to handle rifle with low fire rate and recoil." +-- L["wep." .. w .. ".desc.quote"] = "Despite its looks, it is not an AK." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FB \"Łucznik\" Radom" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "amd65" +-- L["wep." .. w .. ".name"] = "AMD-65" +-- L["wep." .. w .. ".desc"] = "Hungarian AK clone with integrated grip and wire stock. High damage, but lower range than most rifles." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Fegyver- és Gépgyár" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "ar15" +-- L["wep." .. w .. ".name.full"] = "Diemaco AR-15" +-- L["wep." .. w .. ".name"] = "AR-15" +-- L["wep." .. w .. ".desc"] = "Customized semi-automatic model of an ubiquitous American rifle. Uses reduced capacity magazines." +-- L["wep." .. w .. ".desc.quote"] = "\"One of the most beloved and most vilified rifles in the country.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt Canada" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "as50" +-- L["wep." .. w .. ".name.full"] = "AI AS50" +-- L["wep." .. w .. ".name"] = "AS50" +-- L["wep." .. w .. ".desc"] = "Semi-automatic anti-materiel rifle that can easily decimate any person at any distance. \nEquipped with a 12x scope by default. \nFar too heavy to swing, so bashing is out of the question." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Accuracy International" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "aug" +-- L["wep." .. w .. ".name.full"] = "Steyr AUG A2" +-- L["wep." .. w .. ".name"] = "AUG A2" +-- L["wep." .. w .. ".desc"] = "Burst bullpup rifle with a generous magazine capacity and great handling." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "bekas" +-- L["wep." .. w .. ".name.full"] = "Molot Bekas-16M" +-- L["wep." .. w .. ".name"] = "Bekas-16M" +-- L["wep." .. w .. ".desc"] = "Accurate hunting shotgun with low damage." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Molot" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "c4_detonator" +-- L["wep." .. w .. ".name"] = "C4 Detonator" +-- L["wep." .. w .. ".desc"] = "Device for touching off C4 charges or other types of remote explosives." +-- -- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "civ_amd65" +-- L["wep." .. w .. ".name.full"] = "Arsenal SAM7SF" +-- L["wep." .. w .. ".name"] = "SAM7SF" +-- L["wep." .. w .. ".desc"] = "American semi-automatic AK pattern rifle, customized with an AR-15 style stock and Hungarian handguard.\nUses reduced capacity magazines." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arsenal Inc" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention \nModel Edit: Arctic" + +w = ws .. "civ_g36k" +-- L["wep." .. w .. ".name.full"] = "HK HK243" +-- L["wep." .. w .. ".name"] = "HK243" +-- L["wep." .. w .. ".desc"] = "Semi-automatic model of an iconic polymer rifle.\nUses reduced capacity magazines." +-- L["wep." .. w .. ".desc.quote"] = "\"Weapons of war do not belong on our streets!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "civ_m320" +-- L["wep." .. w .. ".name.full"] = "HK M320LE" +-- L["wep." .. w .. ".name"] = "M320LE" +-- L["wep." .. w .. ".desc"] = "Law Enforcement version of the M320 sanctioned for less-lethal munitions. Fires beanbag rounds that incapacitate on direct hit." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "civ_mp5" +-- L["wep." .. w .. ".name.full"] = "HK HK94" +-- L["wep." .. w .. ".name"] = "HK94" +-- L["wep." .. w .. ".desc"] = "Semi-automatic model of a legendary submachine gun. \nUses reduced capacity magazines." +-- L["wep." .. w .. ".desc.quote"] = "Frequently seen standing in for its military counterpart." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention \nModel Edit: speedonerd \n(it was soooo hard lol)" + +w = ws .. "civ_p90" +-- L["wep." .. w .. ".name.full"] = "FN PS90" +-- L["wep." .. w .. ".name"] = "PS90" +-- L["wep." .. w .. ".desc"] = "Semi-automatic variation of a futuristic PDW. \nUses reduced capacity magazines." +-- L["wep." .. w .. ".desc.quote"] = "\"This is a weapon of terror. It's made to intimidate the enemy.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention \nModel Edit: speedonerd \n(it was soooo hard lol)" + +w = ws .. "dsa58" +-- L["wep." .. w .. ".name.full"] = "DSA SA58 OSW" +-- L["wep." .. w .. ".name"] = "SA58" +-- L["wep." .. w .. ".desc"] = "Battle rifle with slow fire rate but very high damage and armor penetration. Has a grippod that provides some stability if deployed." +-- L["wep." .. w .. ".desc.quote"] = "\"Shut up, clock in and load up.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "DS Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "ex_ak47" +-- L["wep." .. w .. ".name"] = "AK-47" +-- L["wep." .. w .. ".desc"] = "Iconic Soviet assault rifle. A rugged and simple design that inspired countless clones and derivatives." +-- L["wep." .. w .. ".desc.quote"] = "The quintessential bad guy gun." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, PoisonHeadcrab, Steelbeast \nTextures: Millenia, IppE, FxDarkloki, Pete \nSounds: CC5, modderfreak, .exe \nAnimations: Tactical Intervention" + +w = ws .. "ex_glock" +-- L["wep." .. w .. ".name"] = "Glock 17" +-- L["wep." .. w .. ".desc"] = "Polymer pistol with larger-than-standard capacity and a fast fire rate." +-- L["wep." .. w .. ".desc.quote"] = "Does not show up on airport metal detectors." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Glock Ges.m.b.H" +-- L["wep." .. w .. ".credits"] = "Assets: Twinke Masta \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "ex_hecate" +-- L["wep." .. w .. ".name.full"] = "PGM Hécate II" +-- L["wep." .. w .. ".name"] = "Hécate II" +-- L["wep." .. w .. ".desc"] = "Heavy anti-materiel rifle that can kill in one shot. \nEquipped with a 12x scope by default. \nLight enough for swing for melee." +-- L["wep." .. w .. ".desc.quote"] = "Gun Runner tested, NCR approved." +-- L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +-- L["wep." .. w .. ".credits"] = "Assets: Toby Burnside \nSource: Fallout 4: New Vegas Project" + +w = ws .. "ex_hk45c" +-- L["wep." .. w .. ".name.full"] = "HK HK45 Compact" +-- L["wep." .. w .. ".name"] = "HK45C" +-- L["wep." .. w .. ".desc"] = "Modern high-caliber pistol with great firepower in a handy package." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: B0t, Red Crayon \nTextures: IppE, Grall19 \nSounds: DMG, xLongWayHome, Leeroy Newman \nAnimations: Tactical Intervention" + +w = ws .. "ex_m4a1" +-- L["wep." .. w .. ".name.full"] = "Colt M4A1" +-- L["wep." .. w .. ".name"] = "M4A1" +-- L["wep." .. w .. ".desc"] = "A true American classic boasting high fire rate and balanced performance." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Twinke Masta, DMG \nSounds: Teh Strelok \nAnimations: Tactical Intervention" + +w = ws .. "ex_m1911" +-- L["wep." .. w .. ".name.full"] = "Colt M1911" +-- L["wep." .. w .. ".name"] = "M1911" +-- L["wep." .. w .. ".desc"] = "Surplus pistol from an era before tactical attachments and pistol optics, yet still hits quite hard." +-- L["wep." .. w .. ".desc.quote"] = "\"Hasta la vista, baby!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Twinke Masta, DMG \nSounds: xLongWayHome, Strelok, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "ex_mac10" +-- L["wep." .. w .. ".name.full"] = "Ingram MAC-11" +-- L["wep." .. w .. ".name"] = "MAC-11" +-- L["wep." .. w .. ".desc"] = "A bullet hose best used for point blank spray-and-pray." +-- L["wep." .. w .. ".desc.quote"] = "\"Give me the motherfuckin' gun, Tre!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Military Armament Corporation" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Enron \nSounds: Vunsunta, Erick F \nAnimations: Tactical Intervention" + +w = ws .. "ex_mp9" +-- L["wep." .. w .. ".name.full"] = "BT MP9" +-- L["wep." .. w .. ".name"] = "MP9" +-- L["wep." .. w .. ".desc"] = "Compact polymer submachine gun packing lots of firepower in a small package." +-- L["wep." .. w .. ".desc.quote"] = "\"Your right hand comes off?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Brügger & Thomet" +-- L["wep." .. w .. ".credits"] = "Assets: Logger & The Expert \nSource: Gamebanana" + +w = ws .. "ex_stinger" +-- L["wep." .. w .. ".name.full"] = "FIM-92 Stinger" +-- L["wep." .. w .. ".name"] = "Stinger" +-- L["wep." .. w .. ".desc"] = "Homing anti-air missile launcher. High blast damage but limited effect on armored targets. \nRequires a lock-on in order to fire." +-- L["wep." .. w .. ".desc.quote"] = "\"A cornered fox is more dangerous than a jackal!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Raytheon Missiles and Defense" +-- L["wep." .. w .. ".credits"] = "Assets: Modern Warfare 2 \nAnimations: Tactical Intervention" + +w = ws .. "ex_ump45" +-- L["wep." .. w .. ".name.full"] = "HK UMP45" +-- L["wep." .. w .. ".name"] = "UMP45" +-- L["wep." .. w .. ".desc"] = "Boxy SMG developed with low production costs in mind. \nHigh damage up close, but range and rate of fire is low." +-- L["wep." .. w .. ".desc.quote"] = "\"The fuck are these, cocktail guns?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Hellspike, Logger & Cyper \nSource: Gamebanana" + +w = ws .. "ex_usp" +-- L["wep." .. w .. ".name.full"] = "HK USP" +-- L["wep." .. w .. ".name"] = "USP" +-- L["wep." .. w .. ".desc"] = "Tactical pistol with good damage and range." +-- L["wep." .. w .. ".desc.quote"] = "\"Man of few words, aren't you?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Thanez, Racer445 \nTextures: Thanez, fxdarkloki \nSounds: Vunsunta, BlitzBoaR \nAnimations: Tactical Intervention" + +w = ws .. "fp6" +-- L["wep." .. w .. ".name.full"] = "HK FABARM FP6" +-- L["wep." .. w .. ".name"] = "FABARM FP6" +-- L["wep." .. w .. ".desc"] = "Combat shotgun with high fire rate and capacity." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FABARM S.p.A." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "g36k" +-- L["wep." .. w .. ".name.full"] = "HK G36K" +-- L["wep." .. w .. ".name"] = "G36K" +-- L["wep." .. w .. ".desc"] = "Assault rifle with high muzzle velocity. Well suited for medium range sustained fire. \nEquipped with a 2x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "gsr1911" +-- L["wep." .. w .. ".name.full"] = "SIG 1911 TACOPS" +-- L["wep." .. w .. ".name"] = "SIG 1911" +-- L["wep." .. w .. ".desc"] = "High damage pistol with low range and capacity. \nA tactical evolution, or some would call devolution, of a venerable classic." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer, Inc." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "hk417" +-- L["wep." .. w .. ".name.full"] = "HK HK417" +-- L["wep." .. w .. ".name"] = "HK417" +-- L["wep." .. w .. ".desc"] = "Battle rifle with superb damage, fire rate and precision. Capable of automatic fire, although it is very unstable." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "k1a" +-- L["wep." .. w .. ".name.full"] = "Daewoo K1A" +-- L["wep." .. w .. ".name"] = "K1A" +-- L["wep." .. w .. ".desc"] = "Burst rifle with minimal recoil and good hip firing accuracy." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Daewoo Precision" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "knife" +-- L["wep." .. w .. ".name"] = "Flip Knife" +-- L["wep." .. w .. ".desc"] = "A multi-purpose flip knife, although most of the purposes involving stabbing someone." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "knife2" +-- L["wep." .. w .. ".name"] = "Jackal Knife" +-- L["wep." .. w .. ".desc"] = "Very edgy looking knife. Light, partially skeletized blade makes it faster to swing but do less damage." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention (Unused)" + +w = ws .. "ks23" +-- L["wep." .. w .. ".name"] = "KS-23" +-- L["wep." .. w .. ".desc"] = "Made from recycled aircraft gun barrels, this heavy shotgun fires shells with twice the diameter of typical shotshells and can easily tear apart anything it's vaguely pointed at. Able to breach doors." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source" + +w = ws .. "m1" +-- L["wep." .. w .. ".name.full"] = "Ruger Mini-14" +-- L["wep." .. w .. ".name"] = "Mini-14" +-- L["wep." .. w .. ".desc"] = "Lightweight rifle with no stock or optic mount. \nGood hip-fire accuracy among rifles, but range is low." +-- L["wep." .. w .. ".desc.quote"] = "\"The one with the gun gets to tell the truth.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m4" +-- L["wep." .. w .. ".name.full"] = "Diemaco C8A1" +-- L["wep." .. w .. ".name"] = "C8A1" +-- L["wep." .. w .. ".desc"] = "A close cousin to the classic American rifle with a slower but more controllable rate of fire." +-- L["wep." .. w .. ".desc.quote"] = "Green and very, very mean." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt Canada" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m4star10" +-- L["wep." .. w .. ".name.full"] = "Benelli M4 Super 90" +-- L["wep." .. w .. ".name"] = "M4 Super 90" +-- L["wep." .. w .. ".desc"] = "Semi-automatic shotgun with very high damage output. Reloading may be a chore." +-- L["wep." .. w .. ".desc.quote"] = "There's nothing seven 12 Gauge shotshells can't solve." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Benelli Armi S.p.A." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m14" +-- L["wep." .. w .. ".name.full"] = "Springfield M1A" +-- L["wep." .. w .. ".name"] = "M1A" +-- L["wep." .. w .. ".desc"] = "Semi-automatic rifle with a lethal headshot. \nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"This my rifle! There are many like it, but this one is mine!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "m320" +-- L["wep." .. w .. ".name.full"] = "HK M320" +-- L["wep." .. w .. ".name"] = "M320" +-- L["wep." .. w .. ".desc"] = "Grenade launcher capable of firing a variety of payloads." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "medkit" +L["wep." .. w .. ".name"] = "急救包" +L["wep." .. w .. ".desc"] = "装满医疗补给的小包,治疗伤口用。" +-- L["wep." .. w .. ".desc.quote"] = "\"Keep still, let me patch you up.\"" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Left 4 Dead 2 \nAnimations: Arqu" + +w = ws .. "mg4" +-- L["wep." .. w .. ".name.full"] = "HK MG4" +-- L["wep." .. w .. ".name"] = "MG4" +-- L["wep." .. w .. ".desc"] = "Machine gun with huge volume of fire." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mp5" +-- L["wep." .. w .. ".name.full"] = "HK MP5A3" +-- L["wep." .. w .. ".name"] = "MP5A3" +-- L["wep." .. w .. ".desc"] = "Well-balanced submachine gun known for its precision." +-- L["wep." .. w .. ".desc.quote"] = "\"Now I have a machine gun. Ho, ho, ho.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mp7" +-- L["wep." .. w .. ".name.full"] = "HK MP7" +-- L["wep." .. w .. ".name"] = "MP7" +-- L["wep." .. w .. ".desc"] = "PDW with superb handling and close range effectiveness. High velocity rounds retain effectiveness at range and pierces armor easily." +-- L["wep." .. w .. ".desc.quote"] = "\"You forgot to cock it, muthafucka!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mr96" +-- L["wep." .. w .. ".name.full"] = "Manurhin MR96" +-- L["wep." .. w .. ".name"] = "MR96" +-- L["wep." .. w .. ".desc"] = "Magnum revolver with good handling and stopping power. Accurate, but hard to fire rapidly." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Chapuis Armes" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "mtx_dual" +-- L["wep." .. w .. ".name"] = "Dual MTX" +-- L["wep." .. w .. ".desc"] = "An indulgent pair of high capacity, high damage, high quality compact pistols." +-- L["wep." .. w .. ".desc.quote"] = "With firepower like this, who needs aiming?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Detonics Defense" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "nade_charge" +-- L["wep." .. w .. ".name.full"] = "Breaching Charge" + +w = ws .. "nade_flashbang" +-- L["wep." .. w .. ".name.full"] = "Flashbang" + +w = ws .. "nade_frag" +-- L["wep." .. w .. ".name.full"] = "Frag Grenade" + +w = ws .. "nade_gas" +-- L["wep." .. w .. ".name.full"] = "CS Gas Grenade" + +w = ws .. "nade_heal" +-- L["wep." .. w .. ".name.full"] = "Medi-Smoke Canister" + +w = ws .. "nade_smoke" +-- L["wep." .. w .. ".name.full"] = "Smoke Grenade" + +w = ws .. "nade_thermite" +-- L["wep." .. w .. ".name"] = "Thermite Grenade" + +w = ws .. "p90" +-- L["wep." .. w .. ".name.full"] = "FN P90" +-- L["wep." .. w .. ".name"] = "P90" +-- L["wep." .. w .. ".desc"] = "Bullpup PDW with a generous top-loaded magazine and controllable spread. High velocity rounds retain effectiveness at range and pierces armor easily." +-- L["wep." .. w .. ".desc.quote"] = "\"This is a weapon of war, it's made to kill your enemy.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "p250" +-- L["wep." .. w .. ".name.full"] = "SIG P250" +-- L["wep." .. w .. ".name"] = "P250" +-- L["wep." .. w .. ".desc"] = "Powerful handgun that exchanges capacity for damage and precision." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer, Inc." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "p2000" +-- L["wep." .. w .. ".name.full"] = "HK P2000" +-- L["wep." .. w .. ".name"] = "P2000" +-- L["wep." .. w .. ".desc"] = "Well-rounded, run-of-the-mill police handgun." +-- L["wep." .. w .. ".desc.quote"] = "\"Raus! Raus! Raus!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "pdw" +-- L["wep." .. w .. ".name"] = "KAC PDW" +-- L["wep." .. w .. ".desc"] = "Carbine caliber subcompact PDW. The perfect blend of rifle and submachine gun." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "riot_shield" +L["wep." .. w .. ".name"] = "防暴盾" +L["wep." .. w .. ".desc"] = "轻型护盾。 \n虽然看上去是塑料的,但能格挡大部分步枪口径子弹。 \n能冲刺时攻击且不暴露自己,但会稍微降低移动速度。" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Tactical Intervention \nAnimations: Arqu" + +w = ws .. "rpg7" +-- L["wep." .. w .. ".name"] = "RPG-7" +-- L["wep." .. w .. ".desc"] = "Soviet rocket launcher with powerful explosion. \nSafety fuse prevents point blank detonations." +-- L["wep." .. w .. ".desc.quote"] = "If you hear someone screaming its name, duck for cover." +-- L["wep." .. w .. ".trivia.manufacturer"] = "NPO Bazalt" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "sg551" +-- L["wep." .. w .. ".name.full"] = "SIG SG 551" +-- L["wep." .. w .. ".name"] = "SG 551" +-- L["wep." .. w .. ".desc"] = "Assault rifle with all around excellent performance, offset by a lower magazine capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"No questions, no answers. That's the business we're in.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "skorpion" +-- L["wep." .. w .. ".name.full"] = "Skorpion vz. 61" +-- L["wep." .. w .. ".name"] = "Skorpion" +-- L["wep." .. w .. ".desc"] = "Light machine pistol with good range, recoil and spread." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "sphinx" +-- L["wep." .. w .. ".name.full"] = "Sphinx 2000" +-- L["wep." .. w .. ".name"] = "Sphinx" +-- L["wep." .. w .. ".desc"] = "Premium pistol modified to be 3-round burst. High firerate but long burst delay." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sphinx Systems" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "spr" +-- L["wep." .. w .. ".name.full"] = "Remington M700 SPS" +-- L["wep." .. w .. ".name"] = "R700 SPS" +-- L["wep." .. w .. ".desc"] = "Medium range hunting rifle with a fast cycle speed. \nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "superv" +-- L["wep." .. w .. ".name.full"] = "Kriss Vector" +-- L["wep." .. w .. ".name"] = "Vector" +-- L["wep." .. w .. ".desc"] = "Close range SMG with extremely high fire rate and practically no recoil. Low armor penetration, but can chew through it very quickly." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kriss USA, Inc." +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "tgs12" +-- L["wep." .. w .. ".name.full"] = "Tomahawk Matador" +-- L["wep." .. w .. ".name"] = "Matador" +-- L["wep." .. w .. ".desc"] = "Short barrel pistol grip shotgun. High mobility and recoil, and most effective at close range." +-- L["wep." .. w .. ".desc.quote"] = "For years, its true identity was a mystery." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tomahawk Shotguns" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "uratio" +-- L["wep." .. w .. ".name.full"] = "PGM Ultima Ratio" +-- L["wep." .. w .. ".name"] = "Ultima Ratio" +-- L["wep." .. w .. ".desc"] = "Lightweight sniper rifle with good damage and high mobility. \nEquipped with a 10x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "uzi" +-- L["wep." .. w .. ".name.full"] = "IMI Uzi Pro" +-- L["wep." .. w .. ".name"] = "Uzi Pro" +-- L["wep." .. w .. ".desc"] = "Balanced machine pistol with a controllable rate of fire." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "vertec" +-- L["wep." .. w .. ".name.full"] = "Beretta 92FS Vertec" +-- L["wep." .. w .. ".name"] = "92FS Vertec" +L["wep." .. w .. ".desc"] = "意大利产手枪。射程和命中率较高。" +-- L["wep." .. w .. ".desc.quote"] = "\"Yippie ki-yay, motherfucker!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "xd45" +-- L["wep." .. w .. ".name.full"] = "Springfield XD-45" +-- L["wep." .. w .. ".name"] = "XD-45" +-- L["wep." .. w .. ".desc"] = "Automatic machine pistol with incredible close range power." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +-- Attachments (Non-Bulk) +L["att.melee_spec_charge.name.full"] = "demoknight tf2" +L["att.melee_spec_charge.name"] = "Charge" +L["att.melee_spec_charge.desc"] = "Advance with reckless abandon, and break some laws of physics too." +L["att.pro.melee_spec_charge1"] = "RELOAD: Charge" +L["att.pro.melee_spec_charge2"] = "WALK + RELOAD: Select Charge Mode" +L["att.pro.melee_spec_charge3"] = "Reduced Damage while Charging" +L["att.pro.melee_spec_charge4"] = "Reduced Self-Damage" + +L["att.melee_spec_lunge.name"] = "Frency" +L["att.melee_spec_lunge.desc"] = "Close the distance and overwhelm your enemies." +L["att.pro.melee_spec_lunge1"] = "RELOAD: Lunge in Aim Direction" + +L["att.melee_spec_nade.name.full"] = "Bombardier" +L["att.melee_spec_nade.name"] = "Bomber" +L["att.melee_spec_nade.desc"] = "Use jury-rigged impact grenades to ruin someone's day." +L["att.pro.melee_spec_nade1"] = "RELOAD: Toggle Grenades" +L["att.pro.melee_spec_nade2"] = "Grenades Explode on Impact" +L["att.con.melee_spec_nade"] = "Grenade Damage" + +L["att.melee_spec_ninja.name"] = "Ninja" +L["att.melee_spec_ninja.desc"] = "Disrupt your enemies and strike with the element of surprise." +L["att.pro.melee_spec_ninja1"] = "RELOAD: Palm Strike / Climb" +L["att.pro.melee_spec_ninja2"] = "RELOAD: (Mid-Air + Crouch): Dive Kick" +L["att.pro.melee_spec_ninja3"] = "RELOAD: (Crouch): Backhop" +L["att.pro.melee_spec_ninja4"] = "Silent Footsteps" + +L["att.melee_spec_scout.name.full"] = "jerma tf2" +L["att.melee_spec_scout.name"] = "Scout" +L["att.melee_spec_scout.desc"] = "Grass grows, sun shines, birds fly, and brotha' - I hurt people." +L["att.pro.melee_spec_scout1"] = "RELOAD: Launch Ball" +L["att.pro.melee_spec_scout2"] = "Ball Damage Scales with Distance" +L["att.pro.melee_spec_scout3"] = "Ball Applies Slow on Hit" +L["att.pro.melee_spec_scout4"] = "Multi Jump" + +L["att.melee_spec_step.name"] = "Airdash" +L["att.melee_spec_step.desc"] = "Mobility tool used by blood-fueled robots and transgender women." +L["att.pro.melee_spec_step1"] = "RELOAD: Dash in Movement Direction" +L["att.pro.melee_spec_step2"] = "Invulnerable while Dashing" + +L["att.melee_tech_block.name.full"] = "High Guard" +L["att.melee_tech_block.name"] = "Guard" +L["att.melee_tech_block.desc"] = "Defense is the best offense. It is, coincidentally, also the best defense." +L["att.pro.melee_tech_block1"] = "ALT-FIRE: Block Melee Attacks or Projectiles" +L["att.pro.melee_tech_block2"] = "Heavy Counterattack After Successful Block" + +L["att.melee_tech_heavy.name.full"] = "Heavy-handed" +L["att.melee_tech_heavy.name"] = "Heavy" +L["att.melee_tech_heavy.desc"] = "A classic counter-terrorist technique: Just smack them real hard." +L["att.pro.melee_tech_heavy1"] = "ALT-FIRE: Heavy Attack" +L["att.pro.melee_tech_heavy2"] = "Backstab Damage" + +L["att.melee_tech_nade.name.full"] = "Grenadier" +L["att.melee_tech_nade.name"] = "Grenade" +L["att.melee_tech_nade.desc"] = "Always good to have something handy to throw." +L["att.pro.melee_tech_nade1"] = "ALT-FIRE: Quickthrow" +L["att.pro.melee_tech_nade2"] = "Faster Quickthrow" +L["att.pro.melee_tech_nade3"] = "Throw Rocks" + +L["att.melee_tech_throw.name.full"] = "Knife Throw" +L["att.melee_tech_throw.name"] = "Throw" +L["att.melee_tech_throw.desc"] = "Bar trick turned lethal." +L["att.pro.melee_tech_throw1"] = "ALT-FIRE: Throw Weapon" +L["att.pro.melee_tech_throw2"] = "Consumes Crossbow Bolt Ammo" +L["att.pro.melee_tech_throw3"] = "Bonus Headshot Damage" +L["att.pro.melee_tech_throw4"] = "Bonus Damage on Slowed/Stunned Targets" + +-- Attachments (acc) +L["att.acc_bipod.name"] = "两脚架" +L["att.acc_bipod.desc"] = "可折叠的架子,蹲下使用可以稳定武器。" + +L["att.acc_brace.name"] = "手枪后托" +L["att.acc_brace.desc"] = "架在肩膀上提升手枪稳定性。" + +L["att.acc_cheekrest.name"] = "枪托枕" +L["att.acc_cheekrest.desc"] = "枕住下巴,降低瞄准时的武器摇晃。" + +L["att.acc_dual_ergo.name.full"] = "人体学握把" +L["att.acc_dual_ergo.name"] = "人体学握把" +L["att.acc_dual_ergo.desc"] = "更顺手的后握把,开火时移速增加。" + +L["att.acc_dual_quickdraw.name.full"] = "快切皮套" +L["att.acc_dual_quickdraw.name"] = "快切皮套" +L["att.acc_dual_quickdraw.desc"] = "没有扣子的皮套,可提升切换和装填速度。" + +L["att.acc_dual_skel.name.full"] = "骨架化握把" +L["att.acc_dual_skel.name"] = "骨架化握把" +L["att.acc_dual_skel.desc"] = "更轻盈的后握把,提升移动速度。" + +L["att.acc_duffelbag.name"] = "暗兜皮套" +L["att.acc_duffelbag.desc"] = "藏匿收起的武器,让其他人看不到。" + +L["att.acc_ergo.desc"] = "更顺手的后握把,开火时移速增加。" + +L["att.acc_extendedbelt.name.full"] = "扩容弹盒" +L["att.acc_extendedbelt.name"] = "扩容弹盒" +L["att.acc_extendedbelt.desc"] = "显著提升机枪的弹药装填量。" + +L["att.acc_extmag.name.full"] = "扩容弹匣" +L["att.acc_extmag.name"] = "扩容弹匣" +L["att.acc_extmag.desc"] = "小幅提升武器的弹药装填量。" + +-- L["att.acc_extmag_shotgun_tube.name.full"] = "Tube Extender" +-- L["att.acc_extmag_shotgun_tube.name"] = "Tube Ext." +-- L["att.acc_extmag_shotgun_tube.desc"] = "Slightly increase weapon capacity." + +L["att.acc_foldstock.name"] = "折叠枪托" +L["att.acc_foldstock.desc"] = "不使用枪托。" + +L["att.acc_foldstock2.name"] = "收短枪托" +L["att.acc_foldstock2.desc"] = "将枪托变短。" + +L["att.acc_pad.name"] = "枪托垫" +L["att.acc_pad.desc"] = "枪托底部插入的橡胶垫子,降低后坐力。" + +L["att.acc_quickdraw.desc"] = "没有扣子的皮套,可提升切换和装填速度。" + +L["att.acc_skel.desc"] = "更轻盈的后握把,瞄准时移速增加。" + +L["att.acc_sling.name"] = "枪带" +L["att.acc_sling.desc"] = "固定在枪上的皮带,可提升切换和装填速度。" + +-- Attachments (ammo) +L["att.ammo_40mm_3gl.name.full"] = "40mm 三联弹" +L["att.ammo_40mm_3gl.name"] = "三联弹" +L["att.ammo_40mm_3gl.desc"] = "一次射出三颗伤害较低的弹头。" + +L["att.ammo_40mm_buck.name.full"] = "40mm 霰弹" +L["att.ammo_40mm_buck.name"] = "霰弹" +L["att.ammo_40mm_buck.desc"] = "射出高威力弹丸的扁平弹头。" + +L["att.ammo_40mm_gas.name.full"] = "40mm 催泪弹" +L["att.ammo_40mm_gas.name"] = "催泪弹" +L["att.ammo_40mm_gas.desc"] = "产生非致命催泪气体的弹头。" + +L["att.ammo_40mm_heat.name.full"] = "40mm 箭弹" +L["att.ammo_40mm_heat.name"] = "箭弹" +L["att.ammo_40mm_heat.desc"] = "射出高精度铁箭的扁平弹头。" + +L["att.ammo_40mm_lvg.name.full"] = "40mm 震撼弹" +L["att.ammo_40mm_lvg.name"] = "震撼弹" +L["att.ammo_40mm_lvg.desc"] = "射出有闪光效果的弹头。弹头落地后会延迟引爆。" + +L["att.ammo_40mm_ratshot.name.full"] = "40mm 空爆弹" +L["att.ammo_40mm_ratshot.name"] = "空爆弹" +L["att.ammo_40mm_ratshot.desc"] = "延时空爆的弹头,杀伤范围很大。" + +L["att.ammo_40mm_smoke.name.full"] = "40mm 烟雾弹" +L["att.ammo_40mm_smoke.name"] = "烟雾弹" +L["att.ammo_40mm_smoke.desc"] = "射出无伤害的烟雾,可以隐蔽自己。" + +L["att.ammo_40mm_heal.name.full"] = "40mm 治疗弹" +L["att.ammo_40mm_heal.name"] = "治疗弹" +L["att.ammo_40mm_heal.desc"] = "射出有治疗效果的烟雾。" + +L["att.ammo_amr_hv.name.full"] = "高速狙击弹" +L["att.ammo_amr_hv.name"] = "高速狙击弹" +L["att.ammo_amr_hv.desc"] = "高射程高速度的弹药,近距离杀伤效果变弱。" + +L["att.ammo_amr_ratshot.name.full"] = "猎鼠狙击弹" +L["att.ammo_amr_ratshot.name"] = "猎鼠狙击弹" +L["att.ammo_amr_ratshot.desc"] = "装有微小弹丸的大口径弹药,更容易命中目标。" + +L["att.ammo_amr_saphe.name.full"] = "高爆狙击弹" +L["att.ammo_amr_saphe.name"] = "高爆狙击弹" +L["att.ammo_amr_saphe.desc"] = "产生爆炸效果的大口径弹药。" + +L["att.ammo_ks23_flashbang.name.full"] = "Zvezda 闪光弹" +L["att.ammo_ks23_flashbang.name"] = "闪光弹" +L["att.ammo_ks23_flashbang.desc"] = "可造成小范围眩晕的特种弹药,射程很短且无伤害。" + +L["att.ammo_ks23_flashbang_top.name.full"] = "Zvezda 闪光弹(首发)" +L["att.ammo_ks23_flashbang_top.name"] = "闪光弹(首发)" +L["att.ammo_ks23_flashbang_top.desc"] = "第一发装填闪光弹,后续弹药可正常使用。" + +L["att.ammo_magnum.name.full"] = "过压弹" +L["att.ammo_magnum.name"] = "过压弹" +L["att.ammo_magnum.desc"] = "过量火药能更好维持近距离杀伤力,但更难控制。" + +L["att.ammo_pistol_ap.name.full"] = "钢头弹" +L["att.ammo_pistol_ap.name"] = "钢头弹" +L["att.ammo_pistol_ap.desc"] = "硬化弹头提升穿透效果,但杀伤效果变弱。" + +L["att.ammo_pistol_headshot.name.full"] = "猎头弹" +L["att.ammo_pistol_headshot.name"] = "猎头弹" +L["att.ammo_pistol_headshot.desc"] = "对人型目标弱点效果更好的弹药。" + +L["att.ammo_pistol_hollowpoints.name.full"] = "空尖弹" +L["att.ammo_pistol_hollowpoints.name"] = "空尖弹" +L["att.ammo_pistol_hollowpoints.desc"] = "对肉体杀伤力效果更好的弹药。" + +L["att.ammo_rifle_jhp.name.full"] = "披甲空尖弹" +L["att.ammo_rifle_jhp.name"] = "披甲空尖弹" +L["att.ammo_rifle_jhp.desc"] = "对肉体和臂膀杀伤力更好的弹药。" + +L["att.ammo_pistol_match.name.full"] = "手枪竞赛弹" +L["att.ammo_pistol_match.name"] = "竞赛弹" +L["att.ammo_pistol_match.desc"] = "提升精准度和射程的弹药。" + +L["att.ammo_rifle_match.name.full"] = "步枪竞赛弹" +L["att.ammo_rifle_match.name"] = "竞赛弹" +L["att.ammo_rifle_match.desc"] = "精准度很高的弹药。" + +L["att.ammo_roulette.name.full"] = "俄罗斯轮盘" +L["att.ammo_roulette.name"] = "俄罗斯轮盘" +L["att.ammo_roulette.desc"] = "拿你的性命下注。有子弹时装填可重置概率。" + +L["att.ammo_rpg_improvised.name.full"] = "RPG-7 土制火箭" +L["att.ammo_rpg_improvised.name"] = "土制" +L["att.ammo_rpg_improvised.desc"] = "低价拼单抢来的,应该不会出事吧。" + +L["att.ammo_rpg_mortar.name.full"] = "RPG-7 迫击炮火箭" +L["att.ammo_rpg_mortar.name"] = "迫击炮" +L["att.ammo_rpg_mortar.desc"] = "给迫击炮上装了推进器,用来\"间接\"打击。需要一定时间起爆。" + +L["att.ammo_rpg_ratshot.name.full"] = "RPG-7 空爆火箭" +L["att.ammo_rpg_ratshot.name"] = "空爆" +L["att.ammo_rpg_ratshot.desc"] = "延时空爆的火箭,杀伤范围极高。" + +L["att.ammo_rpg_harpoon.name.full"] = "RPG-7 铲子火箭" +L["att.ammo_rpg_harpoon.name"] = "铲子" +L["att.ammo_rpg_harpoon.desc"] = "以神秘的方式发射铲子。你是疯了,火箭用完了,还是两个都有?" + +L["att.ammo_shotgun_bird.name"] = "鸟弹" +L["att.ammo_shotgun_bird.desc"] = "高弹丸量高散射的弹药。" + +L["att.ammo_shotgun_mag.name.full"] = "马格南鹿弹" +L["att.ammo_shotgun_mag.name"] = "马格南鹿弹" +L["att.ammo_shotgun_mag.desc"] = "能更好维持近距离杀伤力的弹药。" + +L["att.ammo_shotgun_slugs.name.full"] = "独头弹" +L["att.ammo_shotgun_slugs.name"] = "独头弹" +L["att.ammo_shotgun_slugs.desc"] = "发射单颗弹丸,显著提高准度和射程。" + +L["att.ammo_shotgun_triple.name.full"] = "三击弹" +L["att.ammo_shotgun_triple.name"] = "三击弹" +L["att.ammo_shotgun_triple.desc"] = "发射三颗中型弹丸,提高准度。" + +L["att.ammo_subsonic.name.full"] = "亚音弹" +L["att.ammo_subsonic.name"] = "亚音弹" +L["att.ammo_subsonic.desc"] = "通过减少火药降低子弹速度,可隐藏弹道并降低后坐力。" + +L["att.ammo_surplus.name.full"] = "次品弹" +L["att.ammo_surplus.name"] = "次品弹" +L["att.ammo_surplus.desc"] = "不太靠谱的弹药,但不知为何满地都是。" + +L["att.ammo_tmj.name.full"] = "披甲弹" +L["att.ammo_tmj.name"] = "披甲弹" +L["att.ammo_tmj.desc"] = "提升穿透力的弹药。" + +L["att.ammo_buckshotroulette.name.full"] = "霰弹轮盘赌" +L["att.ammo_buckshotroulette.name"] = "霰弹轮盘赌" +L["att.ammo_buckshotroulette.desc"] = "子弹以未知顺序进入枪膛。" + +L["att.ammo_shotgun_minishell.name.full"] = "迷你弹" +L["att.ammo_shotgun_minishell.name"] = "迷你弹" +L["att.ammo_shotgun_minishell.desc"] = "长度很短的霰弹弹药,增加装填量但降低弹丸量。" + +L["att.ammo_shotgun_dragon.name.full"] = "龙息弹" +L["att.ammo_shotgun_dragon.name"] = "龙息弹" +L["att.ammo_shotgun_dragon.desc"] = "镁制弹头能点然目标,但伤害和射程都较低。" + +L["att.ammo_shotgun_frag.name.full"] = "高爆破片弹" +L["att.ammo_shotgun_frag.name"] = "破片弹" +L["att.ammo_shotgun_frag.desc"] = "能造成范围伤害的弹药,但伤害很低。" + +L["att.ammo_shotgun_breach.name.full"] = "破门弹(首发)" +L["att.ammo_shotgun_breach.name"] = "破门弹" +L["att.ammo_shotgun_breach.desc"] = "第一发装填能够破门的特种弹头。" + +-- L["att.ammo_stinger_saam.name.full"] = "FIM-92 Stinger Semi-Active Missile" +-- L["att.ammo_stinger_saam.name"] = "Semi-Active" +-- L["att.ammo_stinger_saam.desc"] = "Powerful missiles that lock rapidly but require constant guidance." + +-- L["att.ammo_stinger_qaam.name.full"] = "FIM-92 Stinger High Agility Missile" +-- L["att.ammo_stinger_qaam.name"] = "Agile" +-- L["att.ammo_stinger_qaam.desc"] = "Highly maneuverable missile with a short range and long lock time." + +-- L["att.ammo_stinger_4aam.name.full"] = "FIM-92 Stinger Quad Missiles" +-- L["att.ammo_stinger_4aam.name"] = "4x" +-- L["att.ammo_stinger_4aam.desc"] = "Guided cluster missiles maximize pressure to enemy pilots." + +-- L["att.ammo_stinger_apers.name.full"] = "FIM-92 Stinger Anti-Personnel Missiles" +-- L["att.ammo_stinger_apers.name"] = "Killer Bee" +-- L["att.ammo_stinger_apers.desc"] = "For rodents of unacceptable agility." + +L["att.ammo_usp_9mm.name.full"] = "HK USP 9×19mm 改装" +L["att.ammo_usp_9mm.name"] = "9×19mm" +L["att.ammo_usp_9mm.desc"] = "发射小口径弹药,提升射速和装填量。" + +-- Attachments (bolt_trigger) +L["att.bolt_fine.name.full"] = "精密枪栓" +L["att.bolt_fine.name"] = "精致" +L["att.bolt_fine.desc"] = "后坐力恢复速度变快,但增长速度也更快。" + +L["att.bolt_greased.name.full"] = "注油枪栓" +L["att.bolt_greased.name"] = "注油" +L["att.bolt_greased.desc"] = "加快上膛速度,但对枪械性能有影响。" + +L["att.bolt_heavy.name.full"] = "稳重枪栓" +L["att.bolt_heavy.name"] = "稳重" +L["att.bolt_heavy.desc"] = "降低后坐力,但也降低射速。" + +L["att.bolt_light.name.full"] = "轻盈枪栓" +L["att.bolt_light.name"] = "轻盈" +L["att.bolt_light.desc"] = "加快射速,但提高后坐力。" + +L["att.bolt_rough.name.full"] = "耐久枪栓" +L["att.bolt_rough.name"] = "耐久" +L["att.bolt_rough.desc"] = "后坐力增长速度变慢,但恢复速度也变慢。" + +L["att.bolt_surplus.name.full"] = "锈铁枪栓" +L["att.bolt_surplus.name"] = "锈铁" +L["att.bolt_surplus.desc"] = "难以捉摸的破旧枪栓,时快时慢。" + +L["att.bolt_tactical.name.full"] = "战术枪栓" +L["att.bolt_tactical.name"] = "战术" +L["att.bolt_tactical.desc"] = "控制上膛速度,提升枪械性能。" + +L["att.bolt_refurbished.name.full"] = "修缮枪栓" +L["att.bolt_refurbished.name"] = "修缮" +L["att.bolt_refurbished.desc"] = "牺牲一些武器性能以提升可靠性。" + +L["att.trigger_akimbo.name.full"] = "双持扳机" +L["att.trigger_akimbo.name"] = "双持" +L["att.trigger_akimbo.desc"] = "放马过来!" + +L["att.trigger_burst.name.full"] = "三连发扳机" +L["att.trigger_burst.name"] = "三连发" +L["att.trigger_burst.desc"] = "使用点射模式提高稳定性的扳机,牺牲全自动射击功能。" + +L["att.trigger_burst2.desc"] = "模拟连发射击的扳机。" + +L["att.trigger_burstauto.name.full"] = "自动点射扳机" +L["att.trigger_burstauto.name"] = "自动点射" +L["att.trigger_burstauto.desc"] = "按住扳机可以让点射不间断开火的扳机。" + +L["att.trigger_comp.name.full"] = "竞赛扳机" +L["att.trigger_comp.name"] = "竞赛" +L["att.trigger_comp.desc"] = "用于运动射击的轻质扳机。" + +L["att.trigger_comp2.desc"] = "轻质扳机,能更快恢复精准度。" + +L["att.trigger_frcd.name.full"] = "强制复位扳机" +L["att.trigger_frcd.name"] = "强制复位" +L["att.trigger_frcd.desc"] = "模拟自动射击的扳机,但性能不佳。" + +L["att.trigger_hair.name.full"] = "快速扳机" +L["att.trigger_hair.name"] = "快速" +L["att.trigger_hair.desc"] = "非常灵敏的扳机,可快速半自动射击。" + +L["att.trigger_hair_akimbo.desc"] = "非常灵敏的扳机,可让双枪快速开火。" + +L["att.trigger_heavy.name.full"] = "重型扳机" +L["att.trigger_heavy.name"] = "重型" +L["att.trigger_heavy.desc"] = "用于持续射击的重型扳机。" + +L["att.trigger_heavy2.desc"] = "沉重的扳机,减少了射击时的移速影响。" + +L["att.trigger_semi.name.full"] = "射手扳机" +L["att.trigger_semi.name"] = "射手" +L["att.trigger_semi.desc"] = "提高稳定性但牺牲了全自动射击。" + +L["att.trigger_slam.name.full"] = "惯性自动扳机" +L["att.trigger_slam.name"] = "惯性" + +L["att.trigger_straight.name.full"] = "平移扳机" +L["att.trigger_straight.name"] = "平移" +L["att.trigger_straight.desc"] = "狭窄的扳机,具有卓越的后坐力性能。" + +L["att.trigger_wide.name.full"] = "宽幅扳机" +L["att.trigger_wide.name"] = "宽幅" +L["att.trigger_wide.desc"] = "大型扳机组件,即使在尴尬的位置也容易握住。" + +L["att.trigger_dualstage.name.full"] = "二阶扳机" +L["att.trigger_dualstage.name"] = "二阶" +L["att.trigger_dualstage.desc"] = "瞄准时降低射速并提升武器可控性的班级。" + +-- Attachments (melee_boost) +-- L["att.melee_boost_all.name"] = "Level Up" +-- L["att.melee_boost_all.desc"] = "Small boost to all attributes." + +-- L["att.melee_boost_str.name"] = "Bulk Up" +-- L["att.melee_boost_str.desc"] = "Increase Brawn significantly at the cost of other attributes." + +-- L["att.melee_boost_agi.name"] = "Catch Up" +-- L["att.melee_boost_agi.desc"] = "Increase Dexterity significantly at the cost of other attributes." + +-- L["att.melee_boost_int.name"] = "Wise Up" +-- L["att.melee_boost_int.desc"] = "Increase Strategy significantly at the cost of other attributes." + +-- L["att.melee_boost_lifesteal.name"] = "Lifestealer" +-- L["att.melee_boost_lifesteal.desc"] = "Restore health by dealing damage." + +-- L["att.melee_boost_momentum.name"] = "Momentum" +-- L["att.melee_boost_momentum.desc"] = "Restore perk charge by dealing damage." + +-- L["att.melee_boost_afterimage.name"] = "Afterimage" +-- L["att.melee_boost_afterimage.desc"] = "Swing your weapon in a flash, landing the attack instantly." + +L["att.melee_boost_shock.name.full"] = "冲锋队" +L["att.melee_boost_shock.name"] = "冲锋队" +L["att.melee_boost_shock.desc"] = "在持有武器的情况下,减少负面效果的影响。" + +-- Attachments (muzz) +L["att.muzz_hbar.name"] = "重枪管" +L["att.muzz_hbar.desc"] = "降低后坐力并提升稳定性的枪管配件。" + +L["att.muzz_lbar.name"] = "轻枪管" +L["att.muzz_lbar.desc"] = "提升射程和精准度的枪管配件。" + +L["att.muzz_pistol_comp.name"] = "制退器" +L["att.muzz_pistol_comp.desc"] = "显著降低后坐力的枪口配件。" + +L["att.muzz_supp_compact.name.full"] = "短型消音器" +L["att.muzz_supp_compact.name"] = "短型消音" +L["att.muzz_supp_compact.desc"] = "提升准度并对射程影响较低的小消音器。" + +L["att.muzz_silencer.name.full"] = "战术消音器" +L["att.muzz_silencer.name"] = "战术消音" +L["att.muzz_silencer.desc"] = "降低后坐力和射程的平衡类消音器。" + +L["att.muzz_supp_weighted.name.full"] = "加重消音器" +L["att.muzz_supp_weighted.name"] = "加重消音" +L["att.muzz_supp_weighted.desc"] = "提升射程但影响手感的重型消音器。" + +L["att.muzz_brake_aggressor.name.full"] = "强袭制退器" +L["att.muzz_brake_aggressor.name"] = "强袭制退" +L["att.muzz_brake_aggressor.desc"] = "转移枪炎方向的制退器,提升射击时移动速度。" + +L["att.muzz_brake_breaching.name.full"] = "突破制退器" +L["att.muzz_brake_breaching.name"] = "突破制退" +L["att.muzz_brake_breaching.desc"] = "带刺的制退器,适合近距离作战。" + +L["att.muzz_brake_concussive.name.full"] = "寸步制退器" +L["att.muzz_brake_concussive.name"] = "寸步制退" +L["att.muzz_brake_concussive.desc"] = "吵到难受的制退器,大幅降低后坐力。" + +-- Attachments (optic_tac) +L["att.optic_8x.name.full"] = "长焦瞄准镜" +L["att.optic_8x.name"] = "长焦" +L["att.optic_8x.desc"] = "专门给狙击手使用的远距离光学镜。" + +L["att.optic_acog.name.full"] = "ACOG 瞄准镜" +L["att.optic_acog.name"] = "ACOG" +L["att.optic_acog.desc"] = "中距离战斗准镜。" + +L["att.optic_elcan.name.full"] = "ELCAN 瞄准镜" +L["att.optic_elcan.name"] = "ELCAN" +L["att.optic_elcan.desc"] = "低倍率战斗准镜。" + +L["att.optic_holographic.name.full"] = "全息瞄准镜" +L["att.optic_holographic.name"] = "全息" +L["att.optic_holographic.desc"] = "盒状准镜,可辅助近距离瞄准。" + +L["att.optic_irons.name.full"] = "机械瞄具" +L["att.optic_irons.name"] = "机瞄" +L["att.optic_irons.desc"] = "基础瞄准器,可增加移动性。" + +L["att.optic_irons_sniper.desc"] = "替换默认的瞄准镜,获得更快的瞄准和更好的机动性。" + +L["att.optic_okp7.name.full"] = "OKP-7 瞄准镜" +L["att.optic_okp7.name"] = "OKP-7" +L["att.optic_okp7.desc"] = "俄罗斯制造的反射式瞄准镜。" + +L["att.optic_rds2.name.full"] = "红点瞄准镜" +L["att.optic_rds2.name"] = "红点" +L["att.optic_rds2.desc"] = "视野宽敞的低倍瞄准器。" + +L["att.optic_rds.name.full"] = "Aimpoint 瞄准镜" +L["att.optic_rds.name"] = "Aimpoint" +L["att.optic_rds.desc"] = "桶状准镜,可辅助近距离瞄准。" + +L["att.optic_rmr.name.full"] = "RMR 瞄准镜" +L["att.optic_rmr.name"] = "RMR" +L["att.optic_rmr.desc"] = "用于手枪的低轮廓光学瞄准器。" + +L["att.optic_rmr_rifle.desc"] = "低轮廓光学瞄准器。" + +L["att.optic_shortdot.name.full"] = "短点瞄准镜" +L["att.optic_shortdot.name"] = "短点" +L["att.optic_shortdot.desc"] = "紧凑的光学瞄准器,具有不错的缩放率。" + +L["att.tac_cornershot.name"] = "拐角摄像机" +L["att.tac_cornershot.desc"] = "在盲射时显示枪口情况。" + +L["att.tac_dmic.name"] = "雷达" +L["att.tac_dmic.desc"] = "探测附近目标的位置,但会发出声响。" + +L["att.tac_flashlight.name"] = "手电筒" +L["att.tac_flashlight.desc"] = "发出强烈的光束,使盯着它的人致盲。" + +L["att.tac_laser.name"] = "镭射" +L["att.tac_laser.desc"] = "发出狭窄的红色光束和圆点,指示枪支的指向。" + +L["att.tac_combo.name.full"] = "光瞄组合" +L["att.tac_combo.name"] = "光瞄组合" +L["att.tac_combo.desc"] = "整合手电筒和镭射的二合一配件。手电光太弱,无法干扰他人。" + +L["att.tac_rangefinder.name"] = "测距仪" +L["att.tac_rangefinder.desc"] = "衡量武器的弹道性能。" + +L["att.tac_spreadgauge.name"] = "扩散测量仪" +L["att.tac_spreadgauge.desc"] = "测量武器的水平后坐力和后坐力的稳定性。" + +L["att.tac_magnifier.name"] = "变焦瞄具" +L["att.tac_magnifier.name"] = "变焦瞄具" +L["att.tac_magnifier.desc"] = "让所有瞄具可以切换 2 倍缩放。" + +L["att.tac_bullet.name.full"] = "紧急装填" +L["att.tac_bullet.name"] = "紧急装填" +L["att.tac_bullet.desc"] = "较快地装填一颗子弹,以备没时间装填时用。" + +L["att.tac_thermal.name.full"] = "热成像" +L["att.tac_thermal.name"] = "热成像" +L["att.tac_thermal.desc"] = "半瞄准时在瞄准中心显示热成像效果。" + +-- Attachments (perk) +L["att.perk_aim.name"] = "死神之眼" +L["att.perk_aim.desc"] = "提高瞄准时的枪械性能。" + +L["att.perk_blindfire.name.full"] = "掩体射击" +L["att.perk_blindfire.name"] = "掩体射击" +L["att.perk_blindfire.desc"] = "改善盲射和精准腰射。" + +L["att.perk_hipfire.name"] = "兰博" +L["att.perk_hipfire.desc"] = "提高不开镜时的枪械精度。" + +L["att.perk_melee.name"] = "粉碎" +L["att.perk_melee.desc"] = "提高近战伤害,并使被击中的目标减速。" + +L["att.perk_reload.name"] = "快速换弹" +L["att.perk_reload.desc"] = "加快换弹速度。" + +L["att.perk_speed.name"] = "身手敏捷" +L["att.perk_speed.desc"] = "提高枪械的机动性,特别是在换弹时。" + +L["att.perk_throw.name"] = "掷弹兵" +L["att.perk_throw.desc"] = "加快了快速投掷,并增加了投掷石块的选项。" + +-- L["att.perk_mlg.name"] = "Stylish" +-- L["att.perk_mlg.desc"] = "Improves quickscoping and accuracy while jumping or moving." + +///////////////////// -- [[ InterOps Weapons ]] -- +-- Weapons +ws = "tacrp_io_" +w = ws .. "870" +-- L["wep." .. w .. ".name.full"] = "Remington 870 SPMM" +-- L["wep." .. w .. ".name"] = "R870 SPMM" +-- L["wep." .. w .. ".desc"] = "Nickel plated \"Marine Magnum\" shotgun. Poor handling, but has great capacity and firepower." +-- L["wep." .. w .. ".desc.quote"] = "\"THERE'S GOLD IN THEM THAR HILLS!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Millenia \nSounds: Vunsunta, xLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "ab10" +-- L["wep." .. w .. ".name.full"] = "Intratec AB-10" +-- L["wep." .. w .. ".name"] = "AB-10" +-- L["wep." .. w .. ".desc"] = "Semi-automatic \"After Ban\" model of the TEC-9 with a short, non-threaded barrel. Big magazine, but unreliable." +-- L["wep." .. w .. ".desc.quote"] = "\"Who needs an assault weapon?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Intratec" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Payday 2 \nSounds: The_AntiPirate \nAnimations: Tactical Intervention" + +w = ws .. "af2011" +-- L["wep." .. w .. ".name.full"] = "AF2011-A1" +-- L["wep." .. w .. ".name"] = "AF2011" +-- L["wep." .. w .. ".desc"] = "Effectively two M1911s welded together, this exotic abomination fires two bullets per trigger pull." +-- L["wep." .. w .. ".desc.quote"] = "\"If the 1911 is so good, why isn't there a 1911 2?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arsenal Firearms" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "automag" +-- L["wep." .. w .. ".name.full"] = "Auto Mag Pistol" +-- L["wep." .. w .. ".name"] = "Auto Mag" +-- L["wep." .. w .. ".desc"] = "Highly accurate magnum pistol. Great handling due to its size, but can only mount pistol optics." +-- L["wep." .. w .. ".desc.quote"] = "\"And if properly used, it can remove the fingerprints.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Auto Mag Corporation" +-- L["wep." .. w .. ".credits"] = "Model: RedRogueXIII \nTextures/Sounds: Futon \nAnimations: Tactical Intervention" + +w = ws .. "chinalake" +-- L["wep." .. w .. ".name.full"] = "China Lake Launcher" +-- L["wep." .. w .. ".name"] = "China Lake" +-- L["wep." .. w .. ".desc"] = "Heavy pump action grenade launcher with high muzzle velocity but poor handling." +-- L["wep." .. w .. ".desc.quote"] = "Only 59 of these ever existed. Where'd you get this one?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "NAWS China Lake" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "coltsmg" +-- L["wep." .. w .. ".name.full"] = "Colt 9mm SMG" +-- L["wep." .. w .. ".name"] = "Colt SMG" +-- L["wep." .. w .. ".desc"] = "AR platform burst-fire SMG. Excellent recoil control, good damage and range but limited optic options. Favored by the Department of Energy." +-- L["wep." .. w .. ".desc.quote"] = "Loudspeaker not included." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Assets: Twinke Masta & Kimono \nSounds: Strelok & New World Interactive \nAnimations: Tactical Intervention" + +w = ws .. "cx4" +-- L["wep." .. w .. ".name.full"] = "Beretta CX4 Storm" +-- L["wep." .. w .. ".name"] = "CX4 Storm" +-- L["wep." .. w .. ".desc"] = "Semi-automatic pistol carbine with good range. \nMediocre armor penetration, but the large frame makes the weapon quite stable." +-- L["wep." .. w .. ".desc.quote"] = "As famously seen in a certain PMC leader's arsenal." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "degala" +-- L["wep." .. w .. ".name.full"] = "Desert Eagle" +-- L["wep." .. w .. ".name"] = "Deagle" +-- L["wep." .. w .. ".desc"] = "Imposing magnum pistol, as iconic as it gets. \nPowerful and high capacity, but recoil is hard to manage." +-- L["wep." .. w .. ".desc.quote"] = "\"You hear that, Mr. Anderson?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +-- L["wep." .. w .. ".credits"] = "Model: Vashts1985 \nTextures: Racer445 \nSounds:Vunsunta, XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "fiveseven" +-- L["wep." .. w .. ".name.full"] = "FN Five-seveN" +-- L["wep." .. w .. ".name"] = "Five-seveN" +-- L["wep." .. w .. ".desc"] = "Bulky PDW caliber pistol with excellent capacity. \nHigh velocity rounds retain effectiveness at range and pierces armor easily." +-- L["wep." .. w .. ".desc.quote"] = "Is that a ferret running around?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2, edited by speedonerd \nSounds: Vunsunta, Counter-Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "fnc" +-- L["wep." .. w .. ".name.full"] = "FN FNC Para" +-- L["wep." .. w .. ".name"] = "FNC Para" +-- L["wep." .. w .. ".desc"] = "Lightweight assault rifle with high hipfire precision and mobility, but low range and poor armor penetration." +-- L["wep." .. w .. ".desc.quote"] = "\"I say what I mean and I do what I say.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Twinke Masta, the_tub, Xero \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "glock18" +-- L["wep." .. w .. ".name"] = "Glock 18C" +-- L["wep." .. w .. ".desc"] = "Machine pistol with high fire rate and mobility." +-- L["wep." .. w .. ".desc.quote"] = "\"Sooner or later, you'll have to jump.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Glock Ges.m.b.H" +-- L["wep." .. w .. ".credits"] = "Model: Hav0k101 \nTextures: el maestro de graffiti \nSounds: BlitzBoaR, Lorn, Ghost597879, Zeven II \nAnimations: Tactical Intervention" + +w = ws .. "k98" +-- L["wep." .. w .. ".name.full"] = "Karabiner 98k" +-- L["wep." .. w .. ".name"] = "Kar98k" +-- L["wep." .. w .. ".desc"] = "Antique bolt-action rifle with an enduring design. Powerful up close, but is essentially obsolete on the modern battlefield." +-- L["wep." .. w .. ".desc.quote"] = "\"Do you want total war?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Mauser" +-- L["wep." .. w .. ".credits"] = "Model: Day of Defeat: Source, edited by 8Z \nTextures: Cafe Rev., rascal, 5hifty \nSounds: rzen1th \nAnimations Cry of Fear, Lazarus" + +w = ws .. "k98_varmint" +-- L["wep." .. w .. ".name"] = "Varmint Rifle" +-- L["wep." .. w .. ".desc"] = "Bolt-action rifle based on the Mauser action. Accepts ubiquitous AR-15 magazines. Lightweight, easy to use and has a generous capacity, but damage is low." +-- L["wep." .. w .. ".desc.quote"] = "For rodents of... unassuming size." +-- L["wep." .. w .. ".trivia.manufacturer"] = "All-American Firearms" +-- L["wep." .. w .. ".credits"] = "Model: Day of Defeat: Source, edited by 8Z \nTextures: 5hifty \nSounds: rzen1th \nAnimations: Tactical Intervention" + +w = ws .. "m14" +-- L["wep." .. w .. ".name"] = "M14 SOPMOD" +-- L["wep." .. w .. ".desc"] = "Modernized short barrel rifle with improved mobility and fast automatic fire but punishing recoil. \nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Troy Industries" +-- L["wep." .. w .. ".credits"] = "Model: General Tso & Twinke Masta \nTextures: Thanez, Twinke Masta \nSounds: Lakedown, teh strelok & Futon \nAnimations: Tactical Intervention" + +w = ws .. "m16a2" +-- L["wep." .. w .. ".name.full"] = "Colt M16A2" +-- L["wep." .. w .. ".name"] = "M16A2" +-- L["wep." .. w .. ".desc"] = "Vintage burst assault rifle with limited optic options. Despite this, its stable recoil and high damage makes it reliable at medium range." +-- L["wep." .. w .. ".desc.quote"] = "\"Take clean shots, watch your background.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, SureShot, vagrant \nTextures: Stoke, modderfreak \nSounds: Vunsunta, Navaro \nAnimations: Tactical Intervention" + +w = ws .. "m500" +-- L["wep." .. w .. ".name.full"] = "SW Model 500" +-- L["wep." .. w .. ".name"] = "M500" +-- L["wep." .. w .. ".desc"] = "Massive long barrel revolver firing a massive magnum round, reigning as the most powerful production handgun in the world." +-- L["wep." .. w .. ".desc.quote"] = "\"You dweebs just ruined a five year investigation!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nOriginally ported to CS 1.6 by GR_Lucia \nSounds: Ghost597879, MSKyuuni & Zira \nAnimations: Tactical Intervention" + +w = ws .. "mx4" +-- L["wep." .. w .. ".name.full"] = "Beretta MX4 Storm" +-- L["wep." .. w .. ".name"] = "MX4 Storm" +-- L["wep." .. w .. ".desc"] = "Bulky pistol carbine with high rate of fire but mediocre armor penetration." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "p226" +-- L["wep." .. w .. ".name.full"] = "SIG P226" +-- L["wep." .. w .. ".name"] = "P226" +-- L["wep." .. w .. ".desc"] = "Handgun with superior range and precision but low capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"The correct term is 'babes,' sir.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Model: SoulSlayer \nTextures: Thanez \nSounds: Anders, DMG, FxDarkloki, & Thanez \nAnimations: Tactical Intervention" + +w = ws .. "rpk" +-- L["wep." .. w .. ".name"] = "RPK" +-- L["wep." .. w .. ".desc"] = "Light machine gun derived from an infantry rifle. High damage and good recoil, but mobility and spread is poor." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Molot" +-- L["wep." .. w .. ".credits"] = "Assets: Call To Arms \nAnimations: Tactical Intervention" + +w = ws .. "ruger" +-- L["wep." .. w .. ".name.full"] = "AWC Amphibian Ruger" +-- L["wep." .. w .. ".name"] = "Amphibian" +-- L["wep." .. w .. ".desc"] = "Small caliber pistol fitted with an integrated suppressor. Near-zero recoil due to weak rounds." +-- L["wep." .. w .. ".desc.quote"] = "\"Take comfort in knowing you never had a choice.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +-- L["wep." .. w .. ".credits"] = "Model: The Lama \nTextures: The Miller \nAnimations: Tactical Intervention" + +w = ws .. "saiga" +-- L["wep." .. w .. ".name"] = "Saiga-12K" +-- L["wep." .. w .. ".desc"] = "High capacity shotgun feeding from a box magazine, suitable for spraying down a room." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Assets: Battlefield 3 \nAnimations: Tactical Intervention" + +w = ws .. "scarh" +-- L["wep." .. w .. ".name.full"] = "FN SCAR-H CQC" +-- L["wep." .. w .. ".name"] = "SCAR-H" +-- L["wep." .. w .. ".desc"] = "Compact, high mobility battle rifle with swift handling." +-- L["wep." .. w .. ".desc.quote"] = "\"Sand Bravo, we're reading 70 bogeys in your sector.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN America" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2, edited by speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "sg550" +-- L["wep." .. w .. ".name.full"] = "SIG SG 550-1 Sniper" +-- L["wep." .. w .. ".name"] = "SG 550-1" +-- L["wep." .. w .. ".desc"] = "Carbine caliber marksman rifle with fast automatic fire. Easy to control in short bursts and has high armor penetration. Equipped with a 6x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c & Twinke Masta \nTextures: Twinke Masta \nSounds: Farion, Treyarch & Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "sg550r" +-- L["wep." .. w .. ".name.full"] = "SIG SG550-2 SP" +-- L["wep." .. w .. ".name"] = "SG 550-2" +-- L["wep." .. w .. ".desc"] = "Long barrel rifle converted to semi-automatic for civilian markets. Easy to control and has high armor penetration." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c & Twinke Masta \nTextures: Twinke Masta \nSounds: Farion, Treyarch & Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "sl8" +-- L["wep." .. w .. ".name.full"] = "HK SL8" +-- L["wep." .. w .. ".name"] = "SL8" +-- L["wep." .. w .. ".desc"] = "Semi-automatic variant of the G36 made for precision shooting. Low fire rate, but recoil control is excellent. \nEquipped with a 2x scope but has no ironsight option." +-- L["wep." .. w .. ".desc.quote"] = "\"Used to be a cop myself, only for one day though.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c \nTextures: Twinke Masta \nSounds: KingFriday \nAnimations: Tactical Intervention" + +w = ws .. "star15" +-- L["wep." .. w .. ".name.full"] = "Spikes Tactical AR-15" +-- L["wep." .. w .. ".name"] = "ST AR-15" +-- L["wep." .. w .. ".desc"] = "Aftermarket AR-15 fine tuned for precision shooting. \nEver the choice for lone wolves on a path of revenge, your fate will be a cacophonous funeral... and a silent farewell. Bring a detonator." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Spikes Tactical" +-- L["wep." .. w .. ".credits"] = "Assets: carl ruins everything, Leon-DLL, Mira + various sources \nSounds: Insurgency, rzen1th & Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "t850" +-- L["wep." .. w .. ".name.full"] = "Taurus 850 Ultralite" +-- L["wep." .. w .. ".name"] = "T850" +-- L["wep." .. w .. ".desc"] = "Snub-nosed revolver with compact form factor. Lethality falls off sharply past point blank." +-- L["wep." .. w .. ".desc.quote"] = "Cardio is free when you're chasing gangsters." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Taurus" +-- L["wep." .. w .. ".credits"] = "Model: Fearfisch \nTextures: Millenia \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "tec9" +-- L["wep." .. w .. ".name.full"] = "Intratec TEC-9" +-- L["wep." .. w .. ".name"] = "TEC-9" +-- L["wep." .. w .. ".desc"] = "Machine pistol notorious for its ease of conversion to fully automatic fire, and subsequent criminal usage." +-- L["wep." .. w .. ".desc.quote"] = "\"The customer is always right.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Intratec" +-- L["wep." .. w .. ".credits"] = "Model & Texture: Payday 2 \nSounds: The_AntiPirate \nAnimations: Tactical Intervention" + +w = ws .. "trg42" +-- L["wep." .. w .. ".name.full"] = "Sako TRG-42" +-- L["wep." .. w .. ".name"] = "TRG-42" +-- L["wep." .. w .. ".desc"] = "Magnum sniper rifle with decent handling and mobility. \nPowerful, but slow to cycle and not very stable. \nEquipped with a 12x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +-- L["wep." .. w .. ".credits"] = "Model: Darkstorn \nTextures: SilentAssassin12 \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "usc" +-- L["wep." .. w .. ".name.full"] = "HK USC" +-- L["wep." .. w .. ".name"] = "USC" +-- L["wep." .. w .. ".desc"] = "Semi-automatic carbine variant of the UMP. Uses low capacity magazines, but its long barrel and gripstock assembly provides excellent medium range performance." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Twinke Masta, xLongWayHome, Stoke, Teh Snake & Millenia etc. \nAnimations: Tactical Intervention" + +w = ws .. "val" +-- L["wep." .. w .. ".name"] = "AS Val" +-- L["wep." .. w .. ".desc"] = "Integrally-suppressed rifle with high damage output and precision, but performs poorly over long bursts." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model & Textures: S.T.A.L.K.E.R. \nSounds: S.T.A.L.K.E.R. & Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "vp70" +-- L["wep." .. w .. ".name.full"] = "HK VP70" +-- L["wep." .. w .. ".name"] = "VP70" +-- L["wep." .. w .. ".desc"] = "Polymer pistol with an innovative holster stock that allows for incredibly fast burst fire." +-- L["wep." .. w .. ".desc.quote"] = "\"Where's everyone going? Bingo?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model & Textures: KnechtRuprecht \nSounds: Strelok & xLongWayHome \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "vss" +-- L["wep." .. w .. ".name.full"] = "VSS Vintorez" +-- L["wep." .. w .. ".name"] = "Vintorez" +-- L["wep." .. w .. ".desc"] = "Integrally-suppressed marksman rifle with high fire rate and low recoil, but performs poorly over long bursts. \nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model: Ettubrutesbro, Millenia & Twinke Masta \nTextures: Millenia \nSounds: S.T.A.L.K.E.R. & Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "xm8car" +-- L["wep." .. w .. ".name.full"] = "HK XM8 Compact" +-- L["wep." .. w .. ".name"] = "XM8 Compact" +-- L["wep." .. w .. ".desc"] = "Experimental multi-purpose carbine. Easy to use, but low damage. \nHas an adjustable integrated 2-8x scope." +-- L["wep." .. w .. ".desc.quote"] = "\"Who loves spaghetti?!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: End Of Days \nTextures: Copkiller, Twinke Masta & Wangchung \nAnimations: Tactical Intervention" + +w = ws .. "xm8lmg" +-- L["wep." .. w .. ".name.full"] = "HK XM8 LMG" +-- L["wep." .. w .. ".name"] = "XM8 LMG" +-- L["wep." .. w .. ".desc"] = "Experimental multi-purpose carbine in MG configuration. Light, high capacity and low recoil, but damage is poor. \nHas an adjustable integrated 2-8x scope." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: End Of Days \nTextures: Copkiller, Twinke Masta & Wangchung \nAnimations: Tactical Intervention" + +-- Attachments (interops) +L["att.ammo_star15_300blk.name.full"] = "ST AR-15 .300 AAC Blackout 改装套件" +L["att.ammo_star15_300blk.name"] = ".300 BLK" +L["att.ammo_star15_300blk.desc"] = "装填低速但近距离效果更好的弹药。" + +L["att.ammo_star15_50beo.name.full"] = "ST AR-15 .50 Beowulf 改装套件" +L["att.ammo_star15_50beo.name"] = ".50 BEO" +L["att.ammo_star15_50beo.desc"] = "装填低容量高伤害的马格南弹药。" + +L["att.bolt_af2011_alt.name.full"] = "AF2011-A1 交替枪管" +L["att.bolt_af2011_alt.name"] = "交替" +L["att.bolt_af2011_alt.desc"] = "交替使用两根枪管。" + +L["att.muzz_comp_io_m14.desc"] = "att.muzz_pistol_comp.desc" + +L["att.muzz_tec9_shroud.name.full"] = "TEC-9 枪管护照" +L["att.muzz_tec9_shroud.name"] = ".300 护罩" +L["att.muzz_tec9_shroud.desc"] = "加长枪管提升中距离性能。" + +L["att.optic_ak_pso1.name.full"] = "PSO-1 瞄准镜" +L["att.optic_ak_pso1.name"] = "PSO-1" +L["att.optic_ak_pso1.desc"] = "中远距离倍率的光学准镜,部分华约枪械可用。" + +L["att.optic_ar_colt.name.full"] = "柯尔特 3x20 枪镜" +L["att.optic_ar_colt.name"] = "柯尔特 3x20" +L["att.optic_ar_colt.desc"] = "低倍率光学准镜,部分 AR 型号枪械可用。" + +L["att.optic_k98_zf42.name.full"] = "寨斯 6x36 枪镜" +L["att.optic_k98_zf42.name"] = "寨斯" +L["att.optic_k98_zf42.desc"] = "Kar98k 专用的中倍光学准镜。" + +L["att.optic_xm8_4x.name.full"] = "XM8 内置准镜(4倍)" +L["att.optic_xm8_4x.name"] = "4x" +L["att.optic_xm8_4x.desc"] = "中距离倍率,使用 ACOG 准心。" + +L["att.optic_xm8_6x.name.full"] = "XM8 内置准镜(6倍)" +L["att.optic_xm8_6x.name"] = "6x" +L["att.optic_xm8_6x.desc"] = "中远距离倍率,使用短点准心。" + +L["att.optic_xm8_8x.name.full"] = "XM8 内置准镜(8倍)" +L["att.optic_xm8_8x.name"] = "8x" +L["att.optic_xm8_8x.desc"] = "远距离倍率,使用狙击准心。" + +L["att.trigger_vp70_auto.name.full"] = "VP-70 自动枪托" +L["att.trigger_vp70_auto.name"] = "自动" +L["att.trigger_vp70_auto.desc"] = "用这个把 H&K 的工程师气到口吐白沫。" + +L["att.trigger_vp70_semi.name.full"] = "VP-70 无枪托" +L["att.trigger_vp70_semi.name"] = "无枪托" +L["att.trigger_vp70_semi.desc"] = "摘下枪托失去三连发功能,但提升机动性和操作速度。" + +///////////////////// -- [[ ArmaLite Revolution ]] -- +-- Weapons +ws = "tacrp_ar_" +w = ws .. "ar15pistol" +-- L["wep." .. w .. ".name"] = "AR-15 Compact" +-- L["wep." .. w .. ".desc"] = "Stockless, extremely short barrel AR-15 configuration. \nLegally a pistol and light enough to function as a sidearm, but it is unstable and imprecise without the form factor of a rifle." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Ultra-Tac" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Mateusz Woliński, Jordan Whincup \nMagazine: Twinke Masta \nSounds: Teh Strelok, Vunsunta, xLongWayHome, CS:O2 \nAnimations: Tactical Intervention" + +w = ws .. "gilboa" +-- L["wep." .. w .. ".name.full"] = "Gilboa DBR Snake" +-- L["wep." .. w .. ".name"] = "Gilboa DBR" +-- L["wep." .. w .. ".desc"] = "Unique double-barrel AR carbine. Twice the lethality as one barrel, but the design is bulky and inaccurate. \nCannot accept muzzle attachments for obvious reasons." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Silver Shadow" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "hk416" +-- L["wep." .. w .. ".name.full"] = "HK HK416" +-- L["wep." .. w .. ".name"] = "HK416" +-- L["wep." .. w .. ".desc"] = "Sleek black rifle made as a competitior to the AR-15. Accurate and low recoil at the cost of some bulk." +-- L["wep." .. w .. ".desc.quote"] = "An elite gun like this is all you need." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, B0T, SoulSlayer \nTextures: Acid Snake, el maestro de graffiti, Antman \nSounds: Vunsunta, xLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "lr300" +-- L["wep." .. w .. ".name.full"] = "Z-M LR-300" +-- L["wep." .. w .. ".name"] = "LR-300" +-- L["wep." .. w .. ".desc"] = "AR-derived \"Light Rifle\" with a modified gas system. Offers high fire rate and range, but stability is subpar. \nThe \"300\" denotes the rifle's maximum effective range in meters." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Z-M Weapons" +-- L["wep." .. w .. ".credits"] = "Model: TheLama \nTextures: Wannabe \nSounds: NightmareMutant \nAnimations: Tactical Intervention" + +w = ws .. "m16a1" +-- L["wep." .. w .. ".name.full"] = "Colt M16A1" +-- L["wep." .. w .. ".name"] = "M16A1" +-- L["wep." .. w .. ".desc"] = "An antique rifle recovered from the rice fields. \nBoasts high firing rate and good range, though don't expect this bucket of rust to run without a hitch or two." +-- L["wep." .. w .. ".desc.quote"] = "Welcome to the jungle." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c \nTextures: Millenia \nCompile: The_AntiPirate \nAnimations: Tactical Intervention" + +w = ws .. "m16a4" +-- L["wep." .. w .. ".name.full"] = "Colt M16A4" +-- L["wep." .. w .. ".name"] = "M16A4" +-- L["wep." .. w .. ".desc"] = "Modern infantry rifle with modern afforances like a top rail and RIS handguard. A well-rounded infantry weapon with good effective range." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, SureShot, vagrant \nTextures: Stoke, modderfreak \nSounds: Vunsunta, Navaro \nAnimations: Tactical Intervention" + +w = ws .. "sr25" +-- L["wep." .. w .. ".name.full"] = "KAC SR-25 EMR" +-- L["wep." .. w .. ".name"] = "SR-25" +-- L["wep." .. w .. ".desc"] = "\"Enhanced Match Rifle\" made for precision shooting. \nLow capacity, but otherwise has excellent performance. \nEquipped with a 10x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "vltor" +-- L["wep." .. w .. ".name"] = "VLTOR SBR" +-- L["wep." .. w .. ".desc"] = "AR pattern carbine with a unique piggyback-style handguard. Excels at close range without giving up mid-range performance." +-- L["wep." .. w .. ".desc.quote"] = "\"Hustle up! Get to Whiskey Hotel!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "VLTOR" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: Teh Strelok \nAnimations: Tactical Intervention" + +-- Attachments (ar) +-- L["att.bolt_gilboa_alt.name.full"] = "Gilboa DBR Alternating Bolt" +-- L["att.bolt_gilboa_alt.name"] = "Alternating" +-- L["att.bolt_gilboa_alt.desc"] = "Separated bolts that are able to fire alternatingly, somehow." + +-- L["att.muzz_sr25.name.full"] = "SR-25 Suppressor Shroud" +-- L["att.muzz_sr25.name"] = "SR-25 Supp." +-- L["att.muzz_sr25.desc"] = "Unique suppressor shroud that improves ballistics but lowers fire rate." + +///////////////////// -- [[ Special Delivery ]] -- +-- Weapons +ws = "tacrp_sd_" +w = ws .. "1022" +-- L["wep." .. w .. ".name.full"] = "Ruger 10/22" +-- L["wep." .. w .. ".name"] = "10/22" +-- L["wep." .. w .. ".desc"] = "Ultra-lightweight plinking rifle. Highly accurate and easy to handle, but is barely lethal unless scoring a headshot." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sturm, Ruger & Co." +-- L["wep." .. w .. ".credits"] = "Assets: No More Room in Hell \nAnimations: Tactical Intervention" + +w = ws .. "1858" +-- L["wep." .. w .. ".name.full"] = "Remington 1858 Army" +-- L["wep." .. w .. ".name"] = "Army" +-- L["wep." .. w .. ".desc"] = "Antique percussion revolver packing a punch up close, but is terribly slow to shoot. Suitable for cowboy roleplay." +-- L["wep." .. w .. ".desc.quote"] = "\"Pass the whiskey!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Nimrod Hempel (Fistful of Frags) \nAnimations: CyloWalker \nQuickthrow & melee animations: speedonerd" + +w = ws .. "aac_hb" +-- L["wep." .. w .. ".name.full"] = "AAC Honey Badger" +-- L["wep." .. w .. ".name"] = "Honey Badger" +-- L["wep." .. w .. ".desc"] = "A lightweight assault rifle with an integral suppressor. Powerful in close quarters and has no visible tracer, but has poor performance at range." +-- L["wep." .. w .. ".trivia.manufacturer"] = "AAC" +-- L["wep." .. w .. ".credits"] = "Model: Hyper \nAnimations: Tactical Intervention" + +w = ws .. "bizon" +-- L["wep." .. w .. ".name.full"] = "PP-19 Bizon" +-- L["wep." .. w .. ".name"] = "Bizon" +-- L["wep." .. w .. ".desc"] = "AK-derrivative SMG with a high-capacity helical magazine. Pretty weak but easy to control and handle." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhmash" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Milo \nSounds: Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "contender" +-- L["wep." .. w .. ".name.full"] = "T/C G2 Contender" +-- L["wep." .. w .. ".name"] = "Contender" +-- L["wep." .. w .. ".desc"] = "Single-shot hunting pistol. Accurate, compact and lethal, so you better make that one round count. \nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"You know what I hate? Two groups of people...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Thompson/Center" +-- L["wep." .. w .. ".credits"] = "Model: kriboez, Doktor haus \nTextures: cR45h, syncing, tenoyl, Ultimately \nSounds: Doktor haus \nAnimations: 8Z, speedonerd" + +w = ws .. "db" +-- L["wep." .. w .. ".name.full"] = "Stoeger Double Defense" +-- L["wep." .. w .. ".name"] = "Double Defense" +-- L["wep." .. w .. ".desc"] = "Modern-production short double-barrel shotgun. Easy to handle, reliable and deadly in close quarters." +-- L["wep." .. w .. ".desc.quote"] = "\"Eat leaden death, demon.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Stoeger" +-- L["wep." .. w .. ".credits"] = "Model: Counter-Strike: Online 2 \nSounds: Navaro & Vunsunta \nAnimations: speedonerd & 8Z" + +w = ws .. "delisle" +-- L["wep." .. w .. ".name.full"] = "De Lisle Carbine" +-- L["wep." .. w .. ".name"] = "De Lisle" +-- L["wep." .. w .. ".desc"] = "Pistol caliber bolt-action carbine with an integrated suppressor. One of the quietest firearms ever made, its subsonic rounds have no tracer but travel slowly." +-- L["wep." .. w .. ".desc.quote"] = "Fired into the River Thames, not a soul heard it." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Sterling Armaments" +-- L["wep." .. w .. ".credits"] = "Model: RedRougeXIII \nTextures: Storm (lovingly fixed by Unselles) \nAnimations: Tactical Intervention" + +w = ws .. "dual_1911" +-- L["wep." .. w .. ".name"] = "Dueling Wyverns" +-- L["wep." .. w .. ".desc"] = "A pair of gaudy, custom made golden M1911 pistols complete with wyvern-engraved grip. Hits hard, but its low capacity can be hurting." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Model: Schmung \nTextures: Millenia \nAnimations: Tactical Intervention" + +w = ws .. "dual_degala" +-- L["wep." .. w .. ".name"] = "Dual Eagles" +-- L["wep." .. w .. ".desc"] = "Pair of gaudy gold-coated Desert Eagles, as if one wasn't enough. \nTwice the insane firepower, twice the insane recoil." +-- L["wep." .. w .. ".desc.quote"] = "\"My blood...on their hands.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c & Stoke \nTextures: The_Tub & Stoke \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "dual_ppk" +-- L["wep." .. w .. ".name"] = "Dual Agents" +-- L["wep." .. w .. ".desc"] = "A pair of suppressed PPK pistols. Swift and accurate, but the low capacity and mediocre damage demands a sharp eye and trigger discpline." +-- L["wep." .. w .. ".desc.quote"] = "You better not be picking Oddjob." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: HK & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "dual_usp" +-- L["wep." .. w .. ".name"] = "Dual Matches" +-- L["wep." .. w .. ".desc"] = "Pair of pistols pilfered from a pair of dead metrocops. Decent damage and capacity but hefty and slow to fire." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Silvio Dante & Twinke Masta \nTextures: LoneWolf \nAnimations: Tactical Intervention" + +w = ws .. "dual_uzis" +-- L["wep." .. w .. ".name"] = "Dual Uzis" +-- L["wep." .. w .. ".desc"] = "Pair of full-auto Micro Uzis. I don't know how you expect to hit anything with this getup, but you do you I guess." +-- L["wep." .. w .. ".desc.quote"] = "\"I'll hit you with so many rights you'll be begging for a left!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Model: BrainBread 2 \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "dualies" +-- L["wep." .. w .. ".name"] = "Dueling Dragons" +-- L["wep." .. w .. ".desc"] = "Pair of customized pistols with a two-tone finish and dragons emblazoned on the grips. Swift handling and decent recoil control but low stopping power." +-- L["wep." .. w .. ".desc.quote"] = "\"I released my finger from the trigger, and it was over.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Model: Spydr \nTextures: NCFurious \nAnimations: Tactical Intervention" + +w = ws .. "famas" +-- L["wep." .. w .. ".name"] = "FAMAS F1" +-- L["wep." .. w .. ".desc"] = "Burst-fire bullpup used by the French Army. High rate of fire and great accuracy is limited only by its substandard magazine capacity and pretty intense recoil. \nHas a bipod for esoteric French reasons." +-- L["wep." .. w .. ".trivia.manufacturer"] = "GIAT Industries" +-- L["wep." .. w .. ".credits"] = "Model: SnipaMasta \nTextures: SnipaMasta, Fnuxray \nAnimations: speedonerd" + +w = ws .. "famas_g2" +-- L["wep." .. w .. ".name"] = "FAMAS G2" +-- L["wep." .. w .. ".desc"] = "Bullpup rifle with a blazing fast firerate, used by the French Navy. While capable of automatic fire, using burst-fire or the built-in bipod is recommended." +-- L["wep." .. w .. ".desc.quote"] = "\"God's got a sense of humor, alright.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "GIAT Industries" +-- L["wep." .. w .. ".credits"] = "Model: SnipaMasta \nTextures: SnipaMasta, Fnuxray \nAnimations: speedonerd" + +w = ws .. "g3" +-- L["wep." .. w .. ".name.full"] = "HK G3A3" +-- L["wep." .. w .. ".name"] = "G3A3" +-- L["wep." .. w .. ".desc"] = "Precise heavy battle rifle with a somewhat managable automatic firemode but slow handling." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nSounds: Nightmare Mutant & FA:S2 \nAnimations: Tactical Intervention" + +w = ws .. "groza" +-- L["wep." .. w .. ".name.full"] = "OTs-14 \"Groza\"" +-- L["wep." .. w .. ".name"] = "Groza-4" +-- L["wep." .. w .. ".desc"] = "Integrally-suppressed bullpup made from a reconfigured AK. Weak, but has great handling and stability and has no visible tracers." +-- L["wep." .. w .. ".desc.quote"] = "\"Get out of here, Stalker.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "TsKIB SOO" +-- L["wep." .. w .. ".credits"] = "Model: Teh Snake, edited by speedonerd \nTextures: Teh Snake \nSounds: Teh Snake & speedonerd \nAnimations: speedonerd" + +w = ws .. "gyrojet" +-- L["wep." .. w .. ".name.full"] = "MBA Gyrojet" +-- L["wep." .. w .. ".name"] = "Gyrojet" +-- L["wep." .. w .. ".desc"] = "Experimental weapon firing self-propelled mini-rockets. While they are powerful, the rounds are prone to failure." +-- L["wep." .. w .. ".desc.quote"] = "\"I wonder how much his remains would go for on Ebay.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "MBAssociates" +-- L["wep." .. w .. ".credits"] = "Model & Textures: RedRougeXIII \nSounds: speedonerd, Tactical Intervention \nAnimations: speedonerd" + +w = ws .. "m1carbine" +-- L["wep." .. w .. ".name"] = "M1 Carbine" +-- L["wep." .. w .. ".desc"] = "WW2-era semi-auto carbine. Intended as a defensive weapon for support troops, it is accurate and lightweight but has mediocre power." +-- L["wep." .. w .. ".desc.quote"] = "\"Flash.\" \"Thunder.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "General Motors" +-- L["wep." .. w .. ".credits"] = "Model & Textures: KnechtRuprecht \nSounds: NightmareMutant \nAnimations: Tactical Intervention" + +w = ws .. "mp40" +-- L["wep." .. w .. ".name.full"] = "Steyr MP40" +-- L["wep." .. w .. ".name"] = "MP40" +-- L["wep." .. w .. ".desc"] = "WW2-era SMG with a low rate of fire and light recoil. Despite its age, it still shows up in many warzones today." +-- L["wep." .. w .. ".desc.quote"] = "\"Hans, your coffee sucks. I'm not drinking that crap.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr-Mannlicher" +-- L["wep." .. w .. ".credits"] = "Model: Soul-Slayer \nTextures: Kimono \nSounds: Futon & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "pkm" +-- L["wep." .. w .. ".name"] = "PKM" +-- L["wep." .. w .. ".desc"] = "General-purpose machine gun capable of intense suppressive fire. High capacity and damage but is very, very bulky." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Degtyaryov Plant" +-- L["wep." .. w .. ".credits"] = "Assets: Call to Arms \nSounds: NightmareMutant & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "ppk" +-- L["wep." .. w .. ".name.full"] = "Walther PPK" +-- L["wep." .. w .. ".name"] = "PPK" +-- L["wep." .. w .. ".desc"] = "Compact, low-capacity pocket pistol made famous by the movies. Best suited for close range self defense." +-- L["wep." .. w .. ".desc.quote"] = "\"Shocking. Positively shocking.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: HK & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "superx3" +-- L["wep." .. w .. ".name.full"] = "Winchester Super X3" +-- L["wep." .. w .. ".name"] = "Super X3" +-- L["wep." .. w .. ".desc"] = "Civilian sporting shotgun engineered for performance. Long barrel and competition choke offer great accuracy and range but poor handling." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Winchester Repeating Arms" +-- L["wep." .. w .. ".credits"] = "Model, Textures & Sound: No More Room in Hell \nAnimations: Tactical Intervention" + +w = ws .. "thompson" +-- L["wep." .. w .. ".name.full"] = "M1A1 Thompson" +-- L["wep." .. w .. ".name"] = "Thompson" +-- L["wep." .. w .. ".desc"] = "WW2-era SMG with sturdy wooden furniture. Boasts impressive close-range firepower, but is rather heavy." +-- L["wep." .. w .. ".desc.quote"] = "\"Eat lead, jerries!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Auto-Ordnance Company" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "tt33" +-- L["wep." .. w .. ".name.full"] = "Tokarev TT-33" +-- L["wep." .. w .. ".name"] = "TT-33" +-- L["wep." .. w .. ".desc"] = "Antique pistol from beyond the Iron Curtain. High range and penetration, but has reliability issues due to its age." +-- L["wep." .. w .. ".desc.quote"] = "\"Perhaps you would prefer to avoid the red tape?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model: Mr.Rifleman \nTextures: BuLL5H1T & Mr.Rifleman \nSounds: NightmareMutant \nAnimations: Tactical Intervention" + +w = ws .. "vz58" +-- L["wep." .. w .. ".name.full"] = "Sa vz. 58" +-- L["wep." .. w .. ".name"] = "vz. 58" +-- L["wep." .. w .. ".desc"] = "High-damage assault rifle with excellent armor piercing capabilities, converted to semi auto for civilian markets." +-- L["wep." .. w .. ".desc.quote"] = "Despite its looks, it is definitely not an AK." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Model, Textures & Sounds: No More Room in Hell \nAnimations: Tactical Intervention" + +w = ws .. "wa2000" +-- L["wep." .. w .. ".name.full"] = "Walther WA 2000" +-- L["wep." .. w .. ".name"] = "WA 2000" +-- L["wep." .. w .. ".desc"] = "Elegant bullpup sniper rifle with high damage and high rate of fire. \nEquipped with a 12x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"Names are for friends, so I don't need one.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nOriginally ported to CS 1.6 by GR_Lucia \nSounds: HK & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +-- Attachments (sd) +L["att.trigger_dual_uzis_semi.name.full"] = "att.trigger_semi.name.full" +L["att.trigger_dual_uzis_semi.name"] = "att.trigger_semi.name" +L["att.trigger_dual_uzis_semi.desc"] = "att.trigger_semi.desc" + +L["att.tac_1858_spin.name.full"] = "左轮旋转" +L["att.tac_1858_spin.name"] = "旋转" +L["att.tac_1858_spin.desc"] = "转转武器很酷。" + +L["att.optic_m1_scope.name.full"] = "M1 Carbine 3.5x24 瞄准镜" +L["att.optic_m1_scope.name"] = "3.5x 瞄准镜" +L["att.optic_m1_scope.desc"] = "专门装在 M1 Carbine 上的瞄准镜。" + +L["att.optic_delisle_scope.name.full"] = "De Lisle 4x24 瞄准镜" +L["att.optic_delisle_scope.name"] = "4x 瞄准镜" +L["att.optic_delisle_scope.desc"] = "专门装在 De Lisle 上的瞄准镜。" + +L["att.muzz_supp_assassin.name.full"] = "刺客消音器" +L["att.muzz_supp_assassin.name"] = "刺客消音" +L["att.muzz_supp_assassin.desc"] = "显著提升射程但难以控制的超长消音器。" + +L["att.ammo_gyrojet_ratshot.name.full"] = "13mm 猎鼠迷你火箭" +L["att.ammo_gyrojet_ratshot.name"] = "猎鼠" +L["att.ammo_gyrojet_ratshot.desc"] = "就近引爆的破片迷你火箭。" + +L["att.ammo_gyrojet_pipe.name.full"] = "15mm 助推榴弹" +L["att.ammo_gyrojet_pipe.name"] = "榴弹" +L["att.ammo_gyrojet_pipe.desc"] = "定时引线的重型榴弹,命中目标会直接引爆。" + +L["att.ammo_gyrojet_lv.name.full"] = "11mm 低速迷你火箭" +L["att.ammo_gyrojet_lv.name"] = "低速" +L["att.ammo_gyrojet_lv.desc"] = "降低速度和大小的弹药,轨迹相对不太明显。" + +L["att.ammo_gyrojet_he.name.full"] = "13mm 高爆迷你火箭" +L["att.ammo_gyrojet_he.name"] = "高爆" +L["att.ammo_gyrojet_he.desc"] = "弹头装有少量爆炸物的弹药。" + +L["att.ammo_1858_45colt.name.full"] = "Remington 1858 .45 Colt 改装" +L["att.ammo_1858_45colt.name"] = ".45 Colt" +L["att.ammo_1858_45colt.desc"] = "改装发射威力更大但不太可靠的弹药。" + +L["att.ammo_1858_36perc.name.full"] = "Remington 1858 .36 Percussion 改装" +L["att.ammo_1858_36perc.name"] = ".36 Percussion" +L["att.ammo_1858_36perc.desc"] = "改装发射威力较小但射程更高的弹药。" + +L["att.procon.yeehaw"] = "yeeeeeeeeeeeeehawwwwwwwwww" + +///////////////////// -- [[ Brute Force ]] -- +-- Weapons +ws = "tacrp_m_" +w = ws .. "bamaslama" +-- L["wep." .. w .. ".name"] = "Alabama Slammer" +-- L["wep." .. w .. ".desc"] = "A bowie knife named after a cocktail, featuring a serrated back edge and a bayonet mount." +-- L["wep." .. w .. ".desc.quote"] = "\"Did I say you could do that?\"" +-- L["wep." .. w .. ".credits"] = "Model: Havok101 \nTextures: Millenia" + +w = ws .. "bat" +-- L["wep." .. w .. ".name.full"] = "Loisville Slugger TPX" +-- L["wep." .. w .. ".name"] = "Slugger" +-- L["wep." .. w .. ".desc"] = "Aluminum baseball bat, good for hitting home runs or cracking skulls." +-- L["wep." .. w .. ".desc.quote"] = "\"Pop quiz! How long's it take to beat a moron to death?\"" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Yogensia \nAnimations: Lazarus" + +w = ws .. "bayonet" +-- L["wep." .. w .. ".name"] = "M9 Phrobis III" +-- L["wep." .. w .. ".desc"] = "Military standard-issue knife that can be mounted on a weapon as a bayonet. Nothing stopping you from using it like a normal knife, however." +-- L["wep." .. w .. ".credits"] = "Assets: BrainBread 2" + +w = ws .. "boina" +-- L["wep." .. w .. ".name"] = "Cudeman Boina Verde" +-- L["wep." .. w .. ".desc"] = "Sturdy, oversized survival knife. \"Boina verde\" is Spanish for \"green beret,\" an attempt to market the knife as military-grade." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Shortez" + +w = ws .. "cleaver" +-- L["wep." .. w .. ".name"] = "Meat Cleaver" +-- L["wep." .. w .. ".desc"] = "Large, sturdy blade made for chopping meat, be that animal meat or human meat. Has innate Lifesteal." +-- L["wep." .. w .. ".credits"] = "Assets: Warface" + +w = ws .. "crowbar" +-- L["wep." .. w .. ".name"] = "Crowbar" +-- L["wep." .. w .. ".desc"] = "All-purpose utility tool meant for prying things open; crates, safes, doors and humans!" +-- L["wep." .. w .. ".desc.quote"] = "\"I think you dropped this back in Black Mesa.\"" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2" + +w = ws .. "css" +-- L["wep." .. w .. ".name"] = "The Classic" +-- L["wep." .. w .. ".desc"] = "A classic knife design from the 90's, before fancy patterns and ludicrous prices were all the rage." +-- L["wep." .. w .. ".desc.quote"] = "\"Alright, let's move out!\"" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Source" + +w = ws .. "fasthawk" +-- L["wep." .. w .. ".name"] = "SOG Fasthawk" +-- L["wep." .. w .. ".desc"] = "Modernized tomahawk designed for combat. Aerodynamic design can be thrown far." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Yogensia" + +w = ws .. "gerber" +-- L["wep." .. w .. ".name.full"] = "Gerber LMF Infantry" +-- L["wep." .. w .. ".name"] = "LMF Infantry" +-- L["wep." .. w .. ".desc"] = "Compact knife for survival situations, such as gutting fish, carving wood or gutting and carving an enemy." +-- L["wep." .. w .. ".credits"] = "Model: Macaroane \nTextures: Blackfire" + +w = ws .. "glock" +-- L["wep." .. w .. ".name.full"] = "Glock Feldmesser 78" +-- L["wep." .. w .. ".name"] = "Feldmesser 78" +-- L["wep." .. w .. ".desc"] = "A sturdy field knife with a firm grip, designed for use within the Austrian Jagdkommando." +-- L["wep." .. w .. ".desc.quote"] = "Yes, this is the same Glock that makes pistols." +-- L["wep." .. w .. ".credits"] = "Model: HellSpike \nTextures: Dr. Hubbler" + +w = ws .. "hamma" +-- L["wep." .. w .. ".name"] = "Hammer" +-- L["wep." .. w .. ".desc"] = "Common craftsman tool for inserting nails into wood or securing objects in place." +-- L["wep." .. w .. ".desc.quote"] = "When you have a hammer, everything looks like a nail..." +-- L["wep." .. w .. ".credits"] = "Model: FearFisch & sHiBaN \nTextures: Meltdown, sHiBaN & Kitteh" + +w = ws .. "harpoon" +-- L["wep." .. w .. ".name.full"] = "Extrema Ratio Harpoon" +-- L["wep." .. w .. ".name"] = "Harpoon" +-- L["wep." .. w .. ".desc"] = "Thin-bladed knife with serrations on the back edge for extra deep penetration." +-- L["wep." .. w .. ".credits"] = "Model: Warfrog \nTextures: kannoe" + +w = ws .. "heathawk" +-- L["wep." .. w .. ".name"] = "Heat Hawk" +-- L["wep." .. w .. ".desc"] = "Axe-shaped MS weapon scaled down to human size.\nThe original utilizes a superheated edge that cuts through metal like butter, but this replica only has a glowing sticker attached to a metal blade." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Amouro" + +w = ws .. "incorp" +-- L["wep." .. w .. ".name"] = "Viper Knife" +-- L["wep." .. w .. ".desc"] = "Flashy flip knife with a premium stainless finish and woodgrain handle." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Teh Snake" + +w = ws .. "kitchen" +-- L["wep." .. w .. ".name"] = "Kitchen Knife" +-- L["wep." .. w .. ".desc"] = "Plain, simple kitchen knife. Easy to acquire and adequately lethal, knives like these are popular among street gangs and psychopaths. Has innate Lifesteal." +-- L["wep." .. w .. ".desc.quote"] = "Oi mate, you gotta license for that knife?" +-- L["wep." .. w .. ".credits"] = "Model: Kitteh \nTextures: Rochenback" + +w = ws .. "knife3" +-- L["wep." .. w .. ".name"] = "Task Force Knife" +-- L["wep." .. w .. ".desc"] = "Oversized knife with a serrated edge and a curved handle, a bit overkill for what's supposed to be a military knife." +-- L["wep." .. w .. ".desc.quote"] = "\"MOM GET THE CAMERA!!\"" +-- L["wep." .. w .. ".credits"] = "Model: Syncing \nTextures: Boba Fett" + +w = ws .. "kukri" +-- L["wep." .. w .. ".name"] = "Kukri" +-- L["wep." .. w .. ".desc"] = "Short knife with a curved blade, supposedly designed as such so it hooks onto targets and inflicts maximum damage." +-- L["wep." .. w .. ".desc.quote"] = "\"Professionals have standards.\"" +-- L["wep." .. w .. ".credits"] = "Model: Loyen \nTextures: Pain_Agent" + +w = ws .. "machete" +-- L["wep." .. w .. ".name"] = "Machete" +-- L["wep." .. w .. ".desc"] = "Versatile blade that can be used as an agricultural tool, a weapon of war or as a navigational aid when deep in the bush." +-- L["wep." .. w .. ".credits"] = "Assets: BrainBread 2" + +w = ws .. "pan" +-- L["wep." .. w .. ".name"] = "Frying Pan" +-- L["wep." .. w .. ".desc"] = "Stovetop cooking device made of solid cast-iron. Made iconic as a weapon through slapstick and cartoons." +-- L["wep." .. w .. ".credits"] = "Assets: Left 4 Dead 2" + +w = ws .. "pipe" +-- L["wep." .. w .. ".name"] = "Lead Pipe" +-- L["wep." .. w .. ".desc"] = "Sturdy lead water pipe. Despite not being designed for any kind of harm-doing, hitting someone with it does quite a lot of harm." +-- L["wep." .. w .. ".desc.quote"] = "\"OH SHIT!! Too late!!\"" +-- L["wep." .. w .. ".credits"] = "Assets: No More Room in Hell" + +w = ws .. "rambo" +-- L["wep." .. w .. ".name"] = "Bowie Knife" +-- L["wep." .. w .. ".desc"] = "A classic knife design made specifically for dueling, back when knife fights were a form of entertainment. \nRemains popular due to its association with overly-masculine 80's action movie stars." +-- L["wep." .. w .. ".credits"] = "Model: fallschirmjager \nTextures: HellSpike & EinHain" + +w = ws .. "shovel" +-- L["wep." .. w .. ".name"] = "Shovel" +-- L["wep." .. w .. ".desc"] = "An old army shovel, designed to quickly dig trenches. Works great as a crude melee weapon, having both a blunt face and a sharp edge." +-- L["wep." .. w .. ".desc.quote"] = "\"Maggots!\"" +-- L["wep." .. w .. ".credits"] = "Assets: Day of Defeat: Source" + +w = ws .. "tonfa" +-- L["wep." .. w .. ".name"] = "Police Baton" +-- L["wep." .. w .. ".desc"] = "Specialized police baton with an angled handle for an alternative grip. Useful for putting down riots in failing \"democratic\" nations." +-- L["wep." .. w .. ".credits"] = "Assets: Left 4 Dead 2" + +w = ws .. "tracker" +-- L["wep." .. w .. ".name"] = "Tracker Knife" +-- L["wep." .. w .. ".desc"] = "Oversized gut knife designed for multiple hunting utilities, one of which is, conveniently, slicing open human targets." +-- L["wep." .. w .. ".credits"] = "Model: Cartman \nTextures: Henron & Fxdarkloki" + +w = ws .. "wiimote" +-- L["wep." .. w .. ".name.full"] = "Nintendo Wii Remote" +-- L["wep." .. w .. ".name"] = "Wii Remote" +-- L["wep." .. w .. ".desc"] = "An iconic game controller with revolutionary motion controls. \nCareful! Neglecting to wear the wrist strap could lead to injury." +-- L["wep." .. w .. ".desc.quote"] = "You're Out!" -- Wii Sports +-- L["wep." .. w .. ".credits"] = "Model & Textures: Mr. Pickle" + +w = ws .. "wrench" +-- L["wep." .. w .. ".name"] = "Pipe Wrench" +-- L["wep." .. w .. ".desc"] = "Sturdy wrench designed for tightening water and gas pipes. All-iron construction makes it quite the blunt weapon." +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2" + +///////////////////// -- [[ Iron Curtain ]] -- +-- Weapons +ws = "tacrp_ak_" +w = ws .. "aek971" +-- L["wep." .. w .. ".name"] = "AEK-971" +-- L["wep." .. w .. ".desc"] = "Experimental assault rifle using a unique dampening mechanism to reduce felt recoil. High fire rate but medicore range." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kovrovskiy Mekhanicheskiy Zavod" +-- L["wep." .. w .. ".credits"] = "Assets: Casper, arby26 \nAnimations: Tactical Intervention" + +w = ws .. "ak12" +-- L["wep." .. w .. ".name.full"] = "AK-12 Prototype" +-- L["wep." .. w .. ".name"] = "AK-12" +-- L["wep." .. w .. ".desc"] = "One of many attempts at modernizing the AK, this experimental model uses burst fire and allows for quick swapping of the weapon's caliber." +-- L["wep." .. w .. ".desc.quote"] = "The Snow Wolf's eye opens." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Assets: Counter Strike: Online 2 \nAnimations: Tactical Intervention" + +w = ws .. "ak74" +-- L["wep." .. w .. ".name"] = "AK-74" +-- L["wep." .. w .. ".desc"] = "A well-rounded staple from Eastern Europe with controllable recoil and good range. Modifications include lightweight furniture and mounts for dovetail optics." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta, TheLama \nTextures: Millenia, The Spork \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "ak74u" +-- L["wep." .. w .. ".name"] = "AKS-74U" +-- L["wep." .. w .. ".desc"] = "SMG-sized carbine designed for tank crews and special forces. Impressive firepower in a small package, but not gentle in terms of recoil." +-- L["wep." .. w .. ".desc.quote"] = "\"Mother Russia can rot, for all I care.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model: TheLama \nCompile: Bushmasta101 \nTextures: Thanez \nSounds: BlitzBoaR/CC5/modderfreak, .exe \nAnimations: Tactical Intervention" + +w = ws .. "an94" +-- L["wep." .. w .. ".name.full"] = "AN-94 \"Abakan\"" +-- L["wep." .. w .. ".name"] = "AN-94" +-- L["wep." .. w .. ".desc"] = "Experimental assault rifle with a unique 2-round \"hyperburst.\" The rifle's complex mechanism affords low recoil but is very bulky." +-- L["wep." .. w .. ".desc.quote"] = "\"Antje\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "galil_lmg" +-- L["wep." .. w .. ".name.full"] = "IMI Galil ARM" +-- L["wep." .. w .. ".name"] = "Galil ARM" +-- L["wep." .. w .. ".desc"] = "AK derivative in machine gun configuration.\nLightweight and fires at a brisk and controllable pace but with average stopping power." +-- L["wep." .. w .. ".desc.quote"] = "\"Well ya know, for me, the action is the juice.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Galil Assets: Counter Strike: Online 2 \nAccessories: Insurgency (2014), ported by Lt. Rocky \nSuppressed Sound: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "galil_sniper" +-- L["wep." .. w .. ".name.full"] = "IMI Galil Sniper" +-- L["wep." .. w .. ".name"] = "Galatz" +-- L["wep." .. w .. ".desc"] = "Israeli AK derivative in marksman rifle configuration.\nHighly controllable, but has a low firerate and average lethality." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Galil Assets: Counter Strike: Online 2 \nAccessories: Insurgency (2014), ported by Lt. Rocky \nSuppressed Sound: Magmacow \nAnimations: Tactical Intervention" + +w = ws .. "rk95" +-- L["wep." .. w .. ".name.full"] = "Sako RK 95" +-- L["wep." .. w .. ".name"] = "RK 95" +-- L["wep." .. w .. ".desc"] = "Finnish AK derivative with high armor penetration and extended magazine." +-- L["wep." .. w .. ".desc.quote"] = "Despite its looks... Actually, this one's pretty much an AK." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "svd" +-- L["wep." .. w .. ".name.full"] = "Dragunov SVD" +-- L["wep." .. w .. ".name"] = "SVD" +-- L["wep." .. w .. ".desc"] = "Russian sniper rifle with low fire rate but great damage and recoil control. Equipped with a 6x scope by default.\nWhile superficially resembling the AK design, it is completely unrelated mechanically." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kalashnikov Concern" +-- L["wep." .. w .. ".credits"] = "Model: Rafael De Jongh, Ettubrutesbro \nTextures: WangChung \nSounds: Ghost597879, King Friday, iFlip \nAnimations: Tactical Intervention" + +-- Attachments (ak) +L["att.acc_ak74_poly.name.full"] = "AK-74 轻量部件" +L["att.acc_ak74_poly.name"] = "轻量" +L["att.acc_ak74_poly.desc"] = "使用伞兵配置,提升机动性和手感。" + +L["att.ammo_ak12_762.name.full"] = "AK-12 7.62×39mm 改装套件" +L["att.ammo_ak12_762.name"] = "7.62×39mm" +L["att.ammo_ak12_762.desc"] = "装填更大口径的弹药,提高伤害和后座力。" + +L["att.muzz_ak_booster.name.full"] = "6P26 助推器" +L["att.muzz_ak_booster.name"] = "助推器" +L["att.muzz_ak_booster.desc"] = "提升射速的 AK 型号枪口配件。" + +L["att.muzz_ak_comp.name.full"] = "6P20 制退器" +L["att.muzz_ak_comp.name"] = "制退器" +L["att.muzz_ak_comp.desc"] = "控制后坐力的 AK 型号枪口配件。" + +L["att.muzz_supp_pbs.name.full"] = "PBS-5 消音器" +L["att.muzz_supp_pbs.name"] = "PBS消音" +L["att.muzz_supp_pbs.desc"] = "提升后坐力稳定性但准度降低的东欧消音器。" + +L["att.optic_ak_kobra.name.full"] = "Kobra 瞄准镜" +L["att.optic_ak_kobra.name"] = "Kobra" +L["att.optic_ak_kobra.desc"] = "低倍率反射准镜,部分华约枪械可用。" + +L["att.optic_galil.name.full"] = "Nimrod 6x40 瞄准镜" +L["att.optic_galil.name"] = "Nimrod 6x40" +L["att.optic_galil.desc"] = "专门安装在 Galil 型枪械上的狙击镜。" + +///////////////////// -- [[ Heavy Duty ]] -- +-- Weapons +ws = "tacrp_h_" +w = ws .. "dual_hardballers" +-- L["wep." .. w .. ".name"] = "Dual Silverballers" +-- L["wep." .. w .. ".desc"] = "A pair of sleek longslide pistols, a high profile choice for low profile assassins.\nGood range for akimbos - if you can hit anything, that is." +-- L["wep." .. w .. ".desc.quote"] = "\"I'll seek justice for myself. I'll choose the truth I like.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool." +-- L["wep." .. w .. ".credits"] = "Assets: Terminator: Resistance (port by Sirgibsalot) \nSounds: Navaro \nAnimations: Tactical Intervention, Fesiug" + +w = ws .. "executioner" +-- L["wep." .. w .. ".name.full"] = "Taurus Raging Judge" +-- L["wep." .. w .. ".name"] = "Executioner" +-- L["wep." .. w .. ".desc"] = "Massive revolver shooting small-bore shotgun shells.\nFires a lot of pellets, but spread is poor." +-- L["wep." .. w .. ".desc.quote"] = "\"Come, my friends. 'Tis not too late to seek a newer world.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Taurus" +-- L["wep." .. w .. ".credits"] = "Main Model: Firearms: Source, edited by speedonerd \nShells & speedloader: Call of Duty: Black Ops II \nSounds: Call of Duty: Black Ops II \nAnimations: Tactical Intervention" + +w = ws .. "hardballer" +-- L["wep." .. w .. ".name"] = "AMT Hardballer" +-- L["wep." .. w .. ".desc"] = "Long-slide pistol with stainless steel construction. Accurate and hits hard at range." +-- L["wep." .. w .. ".desc.quote"] = "\"I'll be back...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool." +-- L["wep." .. w .. ".credits"] = "Assets: Terminator: Resistance (port by Sirgibsalot) \nSounds: Navaro \nAnimations: Tactical Intervention" + +w = ws .. "hk23e" +-- L["wep." .. w .. ".name.full"] = "HK HK21E" +-- L["wep." .. w .. ".name"] = "HK21E" +-- L["wep." .. w .. ".desc"] = "Belt-fed machine gun variant of a classic battle rifle. Accurate and seriously powerful, but hard to use while on the move." +-- L["wep." .. w .. ".desc.quote"] = "\"Now I can solve up to 800 problems a minute!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: NZ-Reason \nAnimations: Tactical Intervention, edited by Fesiug \nSounds: Treyarch, rzen1th" + +w = ws .. "intervention" +-- L["wep." .. w .. ".name.full"] = "CheyTac Intervention" +-- L["wep." .. w .. ".name"] = "Intervention" +-- L["wep." .. w .. ".desc"] = "Futuristic-looking sniper-AMR hybrid with excellent armor penetration and high muzzle velocity. \nEquipped with a 12x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "1v1 me on rust m8 i bet u cant even quickscope u fkn noob" +-- L["wep." .. w .. ".trivia.manufacturer"] = "CheyTac USA" +-- L["wep." .. w .. ".credits"] = "Model: Syncing \nTextures: Frizz925 \nSounds: Seven-Zero & Ghost \nAnimations: Tactical Intervention" + +w = ws .. "jackhammer" +-- L["wep." .. w .. ".name.full"] = "Pancor Jackhammer" +-- L["wep." .. w .. ".name"] = "Jackhammer" +-- L["wep." .. w .. ".desc"] = "One-of-one bulky automatic shotgun with a cylinder magazine. Quite heavy and a bit unreliable." +-- L["wep." .. w .. ".desc.quote"] = "\"Moo, I say!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pancor Corporation" +-- L["wep." .. w .. ".credits"] = "Model: Soldier11, edited by speedonerd (Front sight) \nTextures: Millenia \nSounds: Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "jdj" +-- L["wep." .. w .. ".name.full"] = "SSK .950 JDJ" +-- L["wep." .. w .. ".name"] = ".950 JDJ" +-- L["wep." .. w .. ".desc"] = "Unfathomly large \"hunting rifle\" shooting a ludicrously powerful round. I don't know how you're even able to shoulder-fire this thing. \nEquipped with a 10x scope by default." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SSK Industries" +-- L["wep." .. w .. ".credits"] = "Model: RedRogueXIII \nTextures: Rafael De Jongh \nSounds: KillerExe_01 \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "nitrorifle" +-- L["wep." .. w .. ".name.full"] = "HH Double Rifle" +-- L["wep." .. w .. ".name"] = "Double Rifle" +-- L["wep." .. w .. ".desc"] = "Boutique double-barreled big-game hunting rifle, firing a round designed to fell elephants. Despite its looks, it is not a shotgun." +-- L["wep." .. w .. ".desc.quote"] = "\"Life is a great adventure, accept it in such a spirit.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Holland & Holland" +-- L["wep." .. w .. ".credits"] = "Assets: Far Cry 4 (Originally ported by Robotnik) \nAdditional Sounds: speedonerd \nAnimations: 8Z, speedonerd" + +w = ws .. "smaw" +-- L["wep." .. w .. ".name.full"] = "Mk 153 SMAW" +-- L["wep." .. w .. ".name"] = "SMAW" +-- L["wep." .. w .. ".desc"] = "Man-portable bunker buster with slow, powerful rockets. Laser pointer allows rocket steering when toggled on. Can mount optics and has a built in Corner-Cam." +-- L["wep." .. w .. ".desc.quote"] = "\"Here's what I think of your best laid plans...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Nammo Talley" +-- L["wep." .. w .. ".credits"] = "Assets: Call of Duty: Modern Warfare 3 (2011) \nAnimations: Tactical Intervention & speedonerd" + +w = ws .. "spas12" +-- L["wep." .. w .. ".name.full"] = "Franchi SPAS-12" +-- L["wep." .. w .. ".name"] = "SPAS-12" +-- L["wep." .. w .. ".desc"] = "Imposing combat shotgun with dual mode operation. High power and stable recoil. Folding stock blocks the use of optics." +-- L["wep." .. w .. ".desc.quote"] = "\"Wrong.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Luigi Franchi S.p.A." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Stoke \nSounds: iFlip & speedonerd \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "xm25" +-- L["wep." .. w .. ".name.full"] = "HK XM25 CDTE" +-- L["wep." .. w .. ".name"] = "XM25" +-- L["wep." .. w .. ".desc"] = "Bullpup grenade launcher with an integrated rangefinder scope, good for medium range suppression. High rate of fire but grenades are fairly slow and weak." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Call of Duty: Modern Warfare 3 (2011) \nAnimations: Tactical Intervention, edited by speedonerd" + +-- Attachments (heavy_pack_atts) +-- L["att.trigger_spas_semi.name.full"] = "Franchi SPAS-12 Semi-Auto Action" +-- L["att.trigger_spas_semi.name"] = "Semi" +-- L["att.trigger_spas_semi.desc"] = "Switch to semi-auto operation, sacrficing stopping power for fire rate." + +-- L["att.trigger_spas_freeman.name.full"] = "Half-Life 2 Double Shot" +-- L["att.trigger_spas_freeman.name"] = "Freeman" +-- L["att.trigger_spas_freeman.desc"] = "Alternate mehcanism that can fire two shots at once, somehow..." + +-- L["att.sound_m200_mlg.name.full"] = "MLG High-Mobility Gamer Rounds" +-- L["att.sound_m200_mlg.name"] = "MLG" +-- L["att.sound_m200_mlg.desc"] = "yep, this one's going in the montage." + +-- L["att.hardballer_laser.name.full"] = "Hardballer Surefire Laser Scope" +-- L["att.hardballer_laser.name"] = "Laser Scope" +-- L["att.hardballer_laser.desc"] = "Primitive bulky laser module that makes aiming almost unnecessary." + +-- L["att.ammo_smaw_tri.name.full"] = "SMAW Tri-Attack Rocket Pod" +-- L["att.ammo_smaw_tri.name"] = "Tri-Attack" +-- L["att.ammo_smaw_tri.desc"] = "A trio of fast and maneuverable anti-infantry missiles." + +-- L["att.ammo_smaw_nikita.name.full"] = "SMAW Nikita Rocket Pod" +-- L["att.ammo_smaw_nikita.name"] = "Nikita" +-- L["att.ammo_smaw_nikita.desc"] = "A very slow manually controllable rocket." + +-- L["att.ammo_smaw_tandem.name.full"] = "SMAW Tandem Rocket Pod" +-- L["att.ammo_smaw_tandem.name"] = "Tandem" +-- L["att.ammo_smaw_tandem.desc"] = "A powerful anti-tank rocket that takes time to accelerate." + +-- L["att.ammo_smaw_agile.name.full"] = "SMAW Hummingbird Mini-Rocket Pod" +-- L["att.ammo_smaw_agile.name"] = "Hummingbird" +-- L["att.ammo_smaw_agile.desc"] = "Aerodynamic mini-rockets that accelerate as they turn." + +-- L["att.ammo_25mm_stun.name.full"] = "25mm Stunstorm Grenades" +-- L["att.ammo_25mm_stun.name"] = "Stunstorm" +-- L["att.ammo_25mm_stun.desc"] = "Grenades that briefly incapacitate the target." + +-- L["att.ammo_25mm_airburst.name.full"] = "25mm Airburst Grenades" +-- L["att.ammo_25mm_airburst.name"] = "Airburst" +-- L["att.ammo_25mm_airburst.desc"] = "Fragmentation grenades exploding mid-air. Large radius but less lethal." + +-- L["att.ammo_25mm_buckshot.name.full"] = "25mm Flechette Grenades" +-- L["att.ammo_25mm_buckshot.name"] = "Flechette" +-- L["att.ammo_25mm_buckshot.desc"] = "Flat-top grenade packing accurate flechette darts." + +-- L["att.ammo_25mm_heat.name.full"] = "25mm High-Explosive Anti-Tank Grenades" +-- L["att.ammo_25mm_heat.name"] = "HEAT" +-- L["att.ammo_25mm_heat.desc"] = "Grenades designed to penetrate armor and deal direct damage." + +-- L["att.pro.trigger_spas_freeman1"] = "Twice the fun" +L["att.procon.nikita"] = "遥控火箭 (瞄准并且开启镭射时)" + +///////////////////// -- [[ ExoOps ]] -- +-- Weapons +ws = "tacrp_eo_" +w = ws .. "93r" +-- L["wep." .. w .. ".name.full"] = "Beretta 93 Raffica" +-- L["wep." .. w .. ".name"] = "93R" +-- L["wep." .. w .. ".desc"] = "Premium burst-fire pistol with a fast non-runaway burst and great hip-fire performance." +-- L["wep." .. w .. ".desc.quote"] = "\"Faith, the train! Take the train!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nOriginally made for CS 1.6 by GR_Lucia \nSounds: Vunsunta, Infinity Ward & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "acr" +-- L["wep." .. w .. ".name.full"] = "Bushmaster ACR" +-- L["wep." .. w .. ".name"] = "ACR" +-- L["wep." .. w .. ".desc"] = "Civilian rifle offered as an advanced alternative to other popular platforms. Well-rounded and can accept various modern ammo types." +-- L["wep." .. w .. ".desc.quote"] = "\"To slay a dragon is the greatest of honors.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Bushmaster Firearms LLC" +-- L["wep." .. w .. ".credits"] = "Model: End of Days \nTextures: IppE \nSounds: Strelok & XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "ar70" +-- L["wep." .. w .. ".name.full"] = "Beretta SC-70/.223" +-- L["wep." .. w .. ".name"] = "SC-70" +-- L["wep." .. w .. ".desc"] = "Bulky assault rifle with a controllable rate of fire." +-- L["wep." .. w .. ".desc.quote"] = "\"Is life always this hard, or is it just when you're a kid?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Pietro Beretta" +-- L["wep." .. w .. ".credits"] = "Model & Textures: DaveW \nSounds: Vunsunta, xLongWayHome & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "auga1" +-- L["wep." .. w .. ".name.full"] = "Steyr AUG A1" +-- L["wep." .. w .. ".name"] = "AUG A1" +-- L["wep." .. w .. ".desc"] = "Classic AUG variant with a larger magazine and full-auto.\nEquipped with a fixed 1.5x scope and backup ironsights." +-- L["wep." .. w .. ".desc.quote"] = "\"I want blood!\" \"And you'll have it.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Arms" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Silentassassin12 \nSound & Animations: Tactical Intervention" + +w = ws .. "browninghp" +-- L["wep." .. w .. ".name.full"] = "Browning Hi-Power" +-- L["wep." .. w .. ".name"] = "Browning HP" +-- L["wep." .. w .. ".desc"] = "Antique pistol with great overall performance but low fire rate." +-- L["wep." .. w .. ".desc.quote"] = "\"What in the goddamn...?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Model: Silvio Dante \nTextures: WangChung \nSounds: Vunsunta, Strelok \nAnimations: Tactical Intervention" + +w = ws .. "calico" +-- L["wep." .. w .. ".name.full"] = "Calico M950A" +-- L["wep." .. w .. ".name"] = "Calico" +-- L["wep." .. w .. ".desc"] = "Odd spacegun-looking pistol with a massive helical magazine." +-- L["wep." .. w .. ".desc.quote"] = "\"I've got more hostages than you've had hot dinners.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Calico Light Weapons Systems" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nRail and foregrip from Warface \nSounds: A.V.A., Warface, speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "dual_satana" +-- L["wep." .. w .. ".name"] = "Dueling Demons" +-- L["wep." .. w .. ".desc"] = "Pair of customized revolvers. Great accuracy for akimbos but slow to fire and reload." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Soul_Slayer \nTextures: Kimono \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "f2000" +-- L["wep." .. w .. ".name.full"] = "FN F2000" +-- L["wep." .. w .. ".name"] = "F2000" +-- L["wep." .. w .. ".desc"] = "Bullpup carbine with high fire rate and mobility, but unstable recoil due to its unusual ergonomics.\nEquipped with a 1.6x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"Did you just say I have to win one for the Gipper?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: DICE (Battlefield 3) \nScope model from CSO2 \nSounds: CSO2 & Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "fal" +-- L["wep." .. w .. ".name.full"] = "FN FAL" +-- L["wep." .. w .. ".name"] = "FAL" +-- L["wep." .. w .. ".desc"] = "Vintage battle rifle with great suppressing power but violent recoil." +-- L["wep." .. w .. ".desc.quote"] = "The right arm of the free world." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Model: Pete3D \nTextures: Millenia \nSounds: New World Interactive & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "g36c" +-- L["wep." .. w .. ".name.full"] = "HK G36C" +-- L["wep." .. w .. ".name"] = "G36C" +-- L["wep." .. w .. ".desc"] = "Compact carbine variant of the G36 that trades effective range for increased firerate and improved handling." +-- L["wep." .. w .. ".desc.quote"] = "\"Right... What the hell kind of name is \"Soap,\" eh?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: TheLama \nTextures: Thanez, FxDarkLoki \nSound & Animations: Tactical Intervention" + +w = ws .. "hkcaws" +-- L["wep." .. w .. ".name"] = "HK CAWS" +-- L["wep." .. w .. ".desc"] = "Prototype automatic bullpup shotgun with high accuracy and high armor penetration.\nEquipped with a fixed 1.5x scope." +-- L["wep." .. w .. ".desc.quote"] = "\"Your ride's over, mutie. Time to die.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Assets: Millenia \nPorted from Fallout: New Vegas by speedonerd \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "howa" +L["wep." .. w .. ".name.full"] = "丰和64式" +L["wep." .. w .. ".name"] = "64式" +-- L["wep." .. w .. ".desc"] = "Japanese battle rifle firing a special reduced load cartridge. Considerable weight but highly controllable." +-- L["wep." .. w .. ".desc.quote"] = "\"Priest-21, this is Trevor. Clear to fire. Kill Wyvern.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Howa Machinery" +-- L["wep." .. w .. ".credits"] = "Assets: Michau, ported from Fallout: New Vegas by 8sianDude \nSounds: speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "hushpup" +-- L["wep." .. w .. ".name.full"] = "SW Mk 22 Mod 0" +-- L["wep." .. w .. ".name"] = "Mk 22" +-- L["wep." .. w .. ".desc"] = "Vintage special forces pistol designed for covert operations." +-- L["wep." .. w .. ".desc.quote"] = "\"Commencing operation Snake Eater.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Stoke \nTextures: Dayofdfeat12, edited by speedonerd \nSounds: oneshotofficial & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "izzyfal" +-- L["wep." .. w .. ".name.full"] = "DSA SA58 Light-Barrel" +-- L["wep." .. w .. ".name"] = "FAL Izzy" +-- L["wep." .. w .. ".desc"] = "Civilian variant of the iconic battle rifle. A bit heavy but boasts excellent stopping power and range." +-- L["wep." .. w .. ".trivia.manufacturer"] = "DS Arms" +-- L["wep." .. w .. ".credits"] = "Model: Pete3D \nTextures: Enron \nSounds: New World Interactive & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "jericho" +-- L["wep." .. w .. ".name.full"] = "Jericho 941" +-- L["wep." .. w .. ".name"] = "Jericho" +-- L["wep." .. w .. ".desc"] = "Sturdy 9mm pistol with great mobility and high firerate. \nMarketed as the \"Baby Eagle\" for its superficial resemblance to the Desert Eagle." +-- L["wep." .. w .. ".desc.quote"] = "\"See you, space cowboy...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israel Military Industries" +-- L["wep." .. w .. ".credits"] = "Model: philibuster \nTextures: oyman \nSounds: xLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "l85" +-- L["wep." .. w .. ".name.full"] = "Enfield L85A2" +-- L["wep." .. w .. ".name"] = "L85A2" +-- L["wep." .. w .. ".desc"] = "British bullpup rifle with middling performance and notorious reliability issues.\nEquipped with a 3x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"SA80. Good bit of British kit...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Royal Ordnance" +-- L["wep." .. w .. ".credits"] = "Model & Texture: Milo, edited by speedonerd \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "m3" +-- L["wep." .. w .. ".name"] = "Benelli M3" +-- L["wep." .. w .. ".desc"] = "Semi-automatic shotgun with good recoil control and accuracy." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Benelli Armi S.p.A." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Kimono \nSounds: Cas., Tactical Intervention \nAnimations: Tactical Intervention" + +w = ws .. "m29" +-- L["wep." .. w .. ".name.full"] = "SW Model 29 \"Satan\"" +-- L["wep." .. w .. ".name"] = "M29" +-- L["wep." .. w .. ".desc"] = "Customized magnum revolver with a heavy trigger but great damage over distance." +-- L["wep." .. w .. ".desc.quote"] = "\"Do ya feel lucky, punk?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Soul_Slayer \nTextures: Kimono \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "m60" +-- L["wep." .. w .. ".name"] = "M60" +-- L["wep." .. w .. ".desc"] = "Heavy machine gun with intense stopping power but a low rate of fire. Nicknamed \"The Pig\" for its considerable heft." +-- L["wep." .. w .. ".desc.quote"] = "\"Live for nothing, or die for something. Your call.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "General Dynamics" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Millenia \nSounds: xLongWayHome, Lain & rzen1th \nAnimations: Tactical Intervention" + +w = ws .. "m712" +-- L["wep." .. w .. ".name.full"] = "M712 Schnellfeuer" +-- L["wep." .. w .. ".name"] = "M712" +-- L["wep." .. w .. ".desc"] = "Antique machine pistol with great accuracy but poor recoil control. Effective in short bursts." +-- L["wep." .. w .. ".desc.quote"] = "The bolt might smack you in the face." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Mauser" +-- L["wep." .. w .. ".credits"] = "Assets: Battlefield: Korea \nOriginally ported to CS 1.6 by GR_Lucia \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "m733" +-- L["wep." .. w .. ".name.full"] = "Colt Model 733" +-- L["wep." .. w .. ".name"] = "M733" +-- L["wep." .. w .. ".desc"] = "Sub-carbine-length AR-15 with a high but hard to control firerate. Fixed carry handle limits optic options." +-- L["wep." .. w .. ".desc.quote"] = "\"I do what I do best; I take scores.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta & The End \nTextures: Acid Snake & JamesM \nSounds: Vunsunta & Teh Strelok \nAnimations: Tactical Intervention" + +w = ws .. "masada" +-- L["wep." .. w .. ".name.full"] = "Magpul Masada" +-- L["wep." .. w .. ".name"] = "Masada" +-- L["wep." .. w .. ".desc"] = "Modern rifle with lightweight polymer construction." +-- L["wep." .. w .. ".desc.quote"] = "\"You could rid the world of iron and I'd sell wooden clubs.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magpul Indutries" +-- L["wep." .. w .. ".credits"] = "Model: End of Days \nTextures: IppE \nSounds: Strelok & XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "mcx" +-- L["wep." .. w .. ".name.full"] = "SIG MCX SPEAR" +-- L["wep." .. w .. ".name"] = "MCX SPEAR" +-- L["wep." .. w .. ".desc"] = "Battle rifle designed for multiple infantry roles. Specialized ammo has high armor penetration and maintains damage over range." +-- L["wep." .. w .. ".desc.quote"] = "\"Bravo Six, going dark.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer Inc." +-- L["wep." .. w .. ".credits"] = "Model & Textures: Akinaro & Farengar \nSounds: Infinity Ward, speedonerd, XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "megastar" +-- L["wep." .. w .. ".name.full"] = "Star Megastar" +-- L["wep." .. w .. ".name"] = "Megastar" +-- L["wep." .. w .. ".desc"] = "Large pistol with hefty construction. Great capacity and firepower but has intense recoil." +-- L["wep." .. w .. ".desc.quote"] = "\"You wanna fuck with me?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Star Bonifacio Echeverria" +-- L["wep." .. w .. ".credits"] = "Model: General Tso \nTextures: the_tub \nSounds: oneshotofficial & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "mg42" +-- L["wep." .. w .. ".name"] = "MG 42" +-- L["wep." .. w .. ".desc"] = "Antique machine gun with a blazing fast rate of fire.\nFiring from the shoulder is not recommended." +-- L["wep." .. w .. ".desc.quote"] = "Every beast has its own story." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Großfuß AG" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Red Orchestra 2 \nSounds: Red Orechesta 2, rzen1th \nAnimations: Tactical Intervention" + +w = ws .. "mp5k" +-- L["wep." .. w .. ".name.full"] = "HK MP5K" +-- L["wep." .. w .. ".name"] = "MP5K" +-- L["wep." .. w .. ".desc"] = "Compact variant of the iconic SMG. Well-rounded but trades the precision and control of its full-size counterpart for improved handling." +-- L["wep." .. w .. ".desc.quote"] = "\"Guns. Lots of guns.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Thanez \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "mp5sd" +-- L["wep." .. w .. ".name.full"] = "HK MP5SD6" +-- L["wep." .. w .. ".name"] = "MP5SD6" +-- L["wep." .. w .. ".desc"] = "Intergrally suppressed variant of the iconic SMG. Reduced range, but has low recoil and no visible tracer." +-- L["wep." .. w .. ".desc.quote"] = "\"Weapons free.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Geno & Mr. Brightside \nSounds: Lakedown, Teh Sterlok \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "mpx" +-- L["wep." .. w .. ".name.full"] = "SIG MPX" +-- L["wep." .. w .. ".name"] = "MPX" +-- L["wep." .. w .. ".desc"] = "Advanced SMG with stable recoil and extended magazine." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Assets: Contract Wars \nOriginally ported to CS 1.6 by GR_Lucia \nAnimation: Tactical Intervention" + +w = ws .. "p7" +-- L["wep." .. w .. ".name.full"] = "HK P7" +-- L["wep." .. w .. ".name"] = "P7" +-- L["wep." .. w .. ".desc"] = "Compact handgun with swift handling but poor range." +-- L["wep." .. w .. ".desc.quote"] = "\"Who said we were terrorists?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Lama \nTextures: The_Tub \nSounds: Optical Snare & One Shot \nAnimations: Tactical Intervention" + +w = ws .. "p99" +-- L["wep." .. w .. ".name.full"] = "Walther P99" +-- L["wep." .. w .. ".name"] = "P99" +-- L["wep." .. w .. ".desc"] = "Well-rounded pistol with a rapid rate of fire." +-- L["wep." .. w .. ".desc.quote"] = "\"Hunt them down and kill them off, one by one.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Walther" +-- L["wep." .. w .. ".credits"] = "Model: Afterburner \nTextures: NCFurious \nSounds: KingFriday, Vunsunta & speedonerd \nAnimations: Tactical Intervention" + +w = ws .. "p210" +-- L["wep." .. w .. ".name.full"] = "SIG P210" +-- L["wep." .. w .. ".name"] = "P210" +-- L["wep." .. w .. ".desc"] = "Sleek post-war, single-stack, hammer-fired pistol.\nSlightly unreliable due to its age." +-- L["wep." .. w .. ".desc.quote"] = "\"Do you suppose that I come to bring peace to the world?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer" +-- L["wep." .. w .. ".credits"] = "Model: Silvio Dante \nTextures: Twinke Masta \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "psg1" +-- L["wep." .. w .. ".name.full"] = "HK PSG-1" +-- L["wep." .. w .. ".name"] = "PSG-1" +-- L["wep." .. w .. ".desc"] = "Elegant semi-automatic sniper with unmatched precision and recoil control. Equipped with an 8x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"I was born on a battlefield. Raised on a battlefield.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta & fallschrimjager \nTextures: Twinke Masta \nSounds: Navaro, Vunsunta, FxDarkLoki \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "rhino20ds" +-- L["wep." .. w .. ".name.full"] = "Chiappa Rhino 20DS" +-- L["wep." .. w .. ".name"] = "Rhino 20DS" +-- L["wep." .. w .. ".desc"] = "Modern snub-nose revolver with a hexagonal cylinder. Small caliber and low-bore barrel makes the gun quick and easy to fire." +-- L["wep." .. w .. ".desc.quote"] = "[ REMEMBER OUR PROMISE ]" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Chiappa Firearms" +-- L["wep." .. w .. ".credits"] = "Model & Textures: MirzaMiftahulFadillah, edited by 8Z \nSounds: Ghost597879, Tatwaffe, The Wastes Mod \nAnimations: Tactical Intervention" + +w = ws .. "scarl" +-- L["wep." .. w .. ".name.full"] = "FN SCAR-L" +-- L["wep." .. w .. ".name"] = "SCAR-L" +-- L["wep." .. w .. ".desc"] = "Modular, lightweight assault rifle with a moderate rate of fire." +-- L["wep." .. w .. ".trivia.manufacturer"] = "FN Herstal" +-- L["wep." .. w .. ".credits"] = "Assets: Counter-Strike: Online 2" + +w = ws .. "scout" +-- L["wep." .. w .. ".name"] = "Steyr Scout" +-- L["wep." .. w .. ".desc"] = "Lightweight rifle designed for portability and comfort rather than direct firepower. Has a built-in bipod.\nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"Headshot!\" \"Humiliation!\" " +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr Mannlicher" +-- L["wep." .. w .. ".credits"] = "Model: Twinke Masta \nTextures: Thanez \nSounds: iFlip, Vunsunta, Unbreakable \nAnimations: Tactical Intervention" + +w = ws .. "sg510" +-- L["wep." .. w .. ".name.full"] = "SIG SG 510-1" +-- L["wep." .. w .. ".name"] = "SG 510" +-- L["wep." .. w .. ".desc"] = "Vintage battle rifle with excellent range and precision. Recoil is harsh but stable, thanks to the weapon's heft." +-- L["wep." .. w .. ".desc.quote"] = "\"He had a lot of guts.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "SIG Sauer AG" +-- L["wep." .. w .. ".credits"] = "Assets: World of Guns: Disassembly \nSounds: Sledgehammer Games \nAnimations: Tactical Intervention" + +w = ws .. "spas15" +-- L["wep." .. w .. ".name.full"] = "Franchi SPAS-15" +-- L["wep." .. w .. ".name"] = "SPAS-15" +-- L["wep." .. w .. ".desc"] = "Heavy, magazine-fed successor to the iconic dual-mode shotgun. Low recoil but isn't very accurate." +-- L["wep." .. w .. ".desc.quote"] = "\"We will wait. We will wait and see what he can discover.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Luigi Franchi S.p.A." +-- L["wep." .. w .. ".credits"] = "Assets: filosoma, ported from Fallout: New Vegas by Kindred Flame \nSounds: Navaro, SevenZero & Magmacow \nAnimations: Tactical Intervention & speedonerd" + +w = ws .. "winchester" +-- L["wep." .. w .. ".name.full"] = "Winchester M1873" +-- L["wep." .. w .. ".name"] = "M1873" +-- L["wep." .. w .. ".desc"] = "Iconic rifle synonymous with cowboys and still enjoyed among civilian shooters. Fun as it is to shoot, it's thoroughly outclassed by most modern firearms." +-- L["wep." .. w .. ".desc.quote"] = "The gun that won the West." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Winchester Repeating Arms" +-- L["wep." .. w .. ".credits"] = "Model: Enron \nTextures: !NC!Furious \nSounds: Vunsunta \nAnimations: speedonerd" + +-- Attachments (exoops) +-- L["att.ammo_modular_65gren.name.full"] = "ACR 6.5mm Grendel Mod Kit" +-- L["att.ammo_modular_65gren.name"] = "Grendel" +-- L["att.ammo_modular_65gren.desc"] = "Modification to load ammo with improved ballistics." + +-- L["att.ammo_modular_450bm.name.full"] = "ACR .450 Bushmaster Mod Kit" +-- L["att.ammo_modular_450bm.name"] = "Bushmaster" +-- L["att.ammo_modular_450bm.desc"] = "Modification to load low capacity, high power magnum rounds." + +-- L["att.bolt_spas15_pump.name.full"] = "Franchi SPAS-15 Pump-Action" +-- L["att.bolt_spas15_pump.name"] = "Pump" +-- L["att.bolt_spas15_pump.desc"] = "Switch to pump-action operation, sacrificing fire-rate for improved control." + +-- L["att.trigger_mk22_locked.name.full"] = "Mk 22 Slide Lock" +-- L["att.trigger_mk22_locked.name"] = "Slide Lock" +-- L["att.trigger_mk22_locked.desc"] = "Locks the slide in place when firing to further reduce noise." + +L["att.optic_howa_scope.name.full"] = "丰和64式2.2倍射手准镜" +L["att.optic_howa_scope.name"] = "准镜" +L["att.optic_howa_scope.desc"] = "64式专用瞄准镜。" + +-- L["att.optic_g36c_scope.name.full"] = "G36C Integrated Scope" +-- L["att.optic_g36c_scope.name"] = "Int. Scope" +-- L["att.optic_g36c_scope.desc"] = "Low power integrated scope and carry handle for the G36C." + +-- L["att.ammo_scout_376.name.full"] = "Scout .376 Steyr Mod Kit" +-- L["att.ammo_scout_376.name"] = ".376 Steyr" +-- L["att.ammo_scout_376.desc"] = "Modification to load a unique high-power hunting cartridge." + +///////////////////// -- [[ Scavenger's Spoils ]] -- +-- Weapons +ws = "tacrp_pa_" +w = ws .. "auto5" +-- L["wep." .. w .. ".name.full"] = "Browning Auto 5" +-- L["wep." .. w .. ".name"] = "Auto 5" +-- L["wep." .. w .. ".desc"] = "Old school automatic shotgun. Small caliber shells have good range and firerate, but poor stopping power." +-- L["wep." .. w .. ".desc.quote"] = "\"Line 'em up and knock 'em down.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Browning Arms" +-- L["wep." .. w .. ".credits"] = "Model: RedRogueXIII, An Aggressive Napkin \nTextures: Futon \nSounds: Futon \nAnimations: Tactical Intervention" + +w = ws .. "automag3" +-- L["wep." .. w .. ".name.full"] = "AMT AutoMag III" +-- L["wep." .. w .. ".name"] = "AutoMag III" +-- L["wep." .. w .. ".desc"] = "Stainless steel pistol chambered in an obscure cartridge. Not very powerful for a magnum pistol, but has good capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"You were always the best. Nobody ever came close.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Arcadia Machine & Tool" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Degenerate Dak \nSounds: Navaro \nAnimations: Tactical Intervention" + +w = ws .. "awp" +-- L["wep." .. w .. ".name.full"] = "AI AWM-F" +-- L["wep." .. w .. ".name"] = "AWM" +-- L["wep." .. w .. ".desc"] = "Robust magnum sniper with unmatched power and accuracy. A counter-terrorist favourite.\nEquipped with a 12x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"He's a madman, a scientist, and a sharpshooter.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Accuracy International" +-- L["wep." .. w .. ".credits"] = "Model: Hav0c \nTextures: Bullethead & Kimono \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "coachgun" +-- L["wep." .. w .. ".name.full"] = "Stoeger Coachgun" +-- L["wep." .. w .. ".name"] = "Coachgun" +-- L["wep." .. w .. ".desc"] = "Double barrel shotgun from the Old West.\nCumbersome and inaccurate, but still packs a punch." +-- L["wep." .. w .. ".desc.quote"] = "Lock, stock, and two smoking barrels." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Stoeger" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Fistful of Frags (Macaroane, Paul68Rageous, Tigg) \nSounds: Fistful of Frags, rzen1th \nAnimations: 8Z" + +w = ws .. "cz75" +-- L["wep." .. w .. ".name.full"] = "CZ-75 Automatic" +-- L["wep." .. w .. ".name"] = "CZ-75" +-- L["wep." .. w .. ".desc"] = "Automatic variant of the quintessential \"Wonder Nine\".\nFast firing and controllable, but capacity is low." +-- L["wep." .. w .. ".desc.quote"] = "The pinnacle of semi-automatic handgun evolution." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Arby \nSounds: Hk, Vunsunta, xLongWayHome, Strelok, Cas, IceFluxx \nAnimations: Tactical Intervention" + +w = ws .. "dual_makarov" + +-- L["wep." .. w .. ".name"] = "Dual Makarovs" +-- L["wep." .. w .. ".desc"] = "A pair of Soviet pistols to compensate for the lack of firepower with just one. Minimal recoil, but lethality remains poor past point blank." +-- L["wep." .. w .. ".desc.quote"] = "\"Finish the job, James! Blow them all to hell!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhevsk Mechanical Plant" +-- L["wep." .. w .. ".credits"] = "Assets: TehSnake \nSounds: Hk, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "fnmag" +-- L["wep." .. w .. ".name"] = "FN MAG" +-- L["wep." .. w .. ".desc"] = "Machine gun with practically no mobility but compensates with incredible firepower and capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"You can't leave me here with these... animals.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Fabrique National" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Call to Arms \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "fort12" +-- L["wep." .. w .. ".name"] = "Fort-12" +-- L["wep." .. w .. ".desc"] = "Cost effective Ukrainian pistol rarely seen outside Eastern Europe. Low stopping power, but handy and can probably survive quite a few supernatural disasters." +-- L["wep." .. w .. ".desc.quote"] = "Find Strelok. Kill Strelok?" +-- L["wep." .. w .. ".trivia.manufacturer"] = "RPC Fort" +-- L["wep." .. w .. ".credits"] = "Assets: S.T.A.L.K.E.R. (ported by modderfreak) \nAnimations: Tactical Intervention" + +w = ws .. "hipoint" +-- L["wep." .. w .. ".name.full"] = "Hi-Point 995" +-- L["wep." .. w .. ".name"] = "Hi-Point" +-- L["wep." .. w .. ".desc"] = "Infamous semi-automatic pistol-caliber carbine.\nReasonably powerful... when it works, which is never." +-- L["wep." .. w .. ".desc.quote"] = "\"Good wombs have borne bad sons.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Hi-Point Firearms" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Yumyumshisha \nSounds: Obsidian \nAnimations: Tactical Intervention" + +w = ws .. "ithaca" +-- L["wep." .. w .. ".name"] = "Ithaca 37" +-- L["wep." .. w .. ".desc"] = "Vintage shotgun that has seen both police and military use. Capable of fast slam-fire but has poor spread and a low shell capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"What? It was obvious! He's the RED Spy!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Ithaca Gun Company" +-- L["wep." .. w .. ".credits"] = "Model: Millenia \nTextures: Poux \nSounds: Strelok \nAnimations: Tactical Intervention" + +w = ws .. "klin" +-- L["wep." .. w .. ".name.full"] = "PP-9 Klin" +-- L["wep." .. w .. ".name"] = "Klin" +-- L["wep." .. w .. ".desc"] = "Simple, lightweight, cheap machine pistol.\nHas a high fire rate but is prone to jamming." +-- L["wep." .. w .. ".desc.quote"] = "\"Eyy, importnyy? FUCK OFF. Ponyl?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhmash" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Teh Snake \nAnimations: Tactical Intervention" + +w = ws .. "ksg12" +-- L["wep." .. w .. ".name.full"] = "KelTec KSG" +-- L["wep." .. w .. ".name"] = "KSG" +-- L["wep." .. w .. ".desc"] = "A bizarre bullpup, twin-tubed, pump-action shotgun. Super high capacity offset by extreme heft." +-- L["wep." .. w .. ".desc.quote"] = "\"Hey! Quit eyeballing the chicks.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Kel-Tec CNC Industries" +-- L["wep." .. w .. ".credits"] = "Assets: Alliance of Valiant Arms \nSounds: Infinity Ward & Navarro \nAnimations: Tactical Intervention, edited by speedonerd" + +w = ws .. "lapd" +-- L["wep." .. w .. ".name.full"] = "LAPD 2019 Blaster" +-- L["wep." .. w .. ".name"] = "LAPD 2019" +-- L["wep." .. w .. ".desc"] = "Gunsmith-custom based on an iconic film weapon, featuring excellent handling and an integrated laser sight." +-- L["wep." .. w .. ".desc.quote"] = "\"Quite an experience to live in fear, isn't it? That's what it is to be a slave.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Steyr, Charter Arms" +-- L["wep." .. w .. ".credits"] = "Assets: Fallout: New Vegas \nAnimations: Tactical Intervention, Fesiug, 8Z" + +w = ws .. "lewis" +-- L["wep." .. w .. ".name"] = "Lewis Gun" +-- L["wep." .. w .. ".desc"] = "Pan-fed, water-cooled light machine gun with a relatively small magazine." +-- L["wep." .. w .. ".desc.quote"] = "\"All of you, get out of my way!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Lewis Automatic Machine Gun Company" +-- L["wep." .. w .. ".credits"] = "Assets: Verdun \nAnimations: Tactical Intervention" + +w = ws .. "luty" +-- L["wep." .. w .. ".name"] = "Luty SMG" +-- L["wep." .. w .. ".desc"] = "Homemade SMG made as protest against UK firearm regulations. The first few shots in a burst have increased firerate, compensating for the weapon's terrible accuracy." +-- L["wep." .. w .. ".desc.quote"] = "\"Let us never succumb to the evil doctrine of the antigun movement.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "P.A. Luty" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Vasily \nSounds: New World Interactive, Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "m79" +-- L["wep." .. w .. ".name"] = "M79" +-- L["wep." .. w .. ".desc"] = "Wood stocked, long barrel grenade launcher from the jungles. Accurate and has fast muzzle velocity, but has considerable weight." +-- L["wep." .. w .. ".desc.quote"] = "\"Goooooooooooooood morning, Vietnam!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Springfield Armory" +-- L["wep." .. w .. ".credits"] = "Model: EdisLeado \nTextures: Millenia \nSounds: Firearms: Source \nAnimations: speedonerd & 8Z" + +w = ws .. "m202" +-- L["wep." .. w .. ".name.full"] = "M202 FLASH" +-- L["wep." .. w .. ".name"] = "M202" +-- L["wep." .. w .. ".desc"] = "Incendiary rocket launcher with four shots. Rockets ignite targets but have low damage and splash radius +-- L["wep." .. w .. ".desc.quote"] = "\"Fire and forget!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Northrop Electro-Mechanical Division" +-- L["wep." .. w .. ".credits"] = "Assets: Tactical Intervention" + +w = ws .. "madsen" +-- L["wep." .. w .. ".name"] = "Madsen M-50" +-- L["wep." .. w .. ".desc"] = "Cost-effective Danish SMG with reliable firepower but awkward ergonomics. An optic cannot be mounted due to the top charging handle." +-- L["wep." .. w .. ".desc.quote"] = "\"Do you renounce Satan... And all his works?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Dansk Industri Syndikat" +-- L["wep." .. w .. ".credits"] = "Model & Textures: jesamabin \nSounds: xLongWayHome \nAnimations: Tactical Intervention, 8Z" + +w = ws .. "makarov" +-- L["wep." .. w .. ".name.full"] = "Makarov PM" +-- L["wep." .. w .. ".name"] = "PM" +-- L["wep." .. w .. ".desc"] = "Mass production Soviet sidearm designed to be carried often and rarely shot. Lethality is poor beyond point blank." +-- L["wep." .. w .. ".desc.quote"] = "\"You're listening to Apocalypse Radio. The only surviving radio station for miles.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Izhevsk Mechanical Plant" +-- L["wep." .. w .. ".credits"] = "Assets: TehSnake \nSounds: Hk, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "mosin" +-- L["wep." .. w .. ".name.full"] = "Mosin-Nagant 91/30" +-- L["wep." .. w .. ".name"] = "M91/30" +-- L["wep." .. w .. ".desc"] = "Mass production Soviet bolt-action rifle. Infantry model cycles faster but is inaccurate; equip a scope for increased accuracy at cost of firerate." +-- L["wep." .. w .. ".desc.quote"] = "\"One out of two gets a rifle, one without follows him!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arms Plant" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Red Orchestra 2 \nSounds: Red Orchestra 2, rzen1th \nAnimations: Cry of Fear, Lazarus, 8Z" + +w = ws .. "oa93" +-- L["wep." .. w .. ".name.full"] = "Olympic Arms OA-93" +-- L["wep." .. w .. ".name"] = "OA-93" +-- L["wep." .. w .. ".desc"] = "AR-15 derived pistol with a top charging handle and no buffer tube. Designed to circumvent legal restrictions, but this one is ironically modified to fire in full auto." +-- L["wep." .. w .. ".desc.quote"] = "\"The most absurdly engineered way to say 'fuck you!' I've ever come across.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Olympic Arms" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Tuuttipingu, edited by 8Z \nSounds: NightmareMutant, Teh Strelok \nAnimations: Tactical Intervention" + +w = ws .. "obrez" +-- L["wep." .. w .. ".name.full"] = "Remington 700 \"Obrez\"" +-- L["wep." .. w .. ".name"] = "Obrez" +-- L["wep." .. w .. ".desc"] = "Cut-down hunting rifle built by the sidearm-deficient to be a concealable handgun substitute. Modification seriously harms accuracy and ranged performance." +-- L["wep." .. w .. ".desc.quote"] = "\"Yippee-yay, there'll be no weddin' bells for today...\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Remington Arms, Bubba" +-- L["wep." .. w .. ".credits"] = "Assets: Bethesda Game Studios \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "ots33" +-- L["wep." .. w .. ".name.full"] = "OTs-33 Pernach" +-- L["wep." .. w .. ".name"] = "OTs-33" +-- L["wep." .. w .. ".desc"] = "Russian machine pistol with low recoil and high mobility, designed for paramilitary forces." +-- L["wep." .. w .. ".desc.quote"] = "\"I hope this hurts.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "KBP Instrument Design Bureau" +-- L["wep." .. w .. ".credits"] = "Model: Kimono \nTextures: Kimono, Millenia \nSounds: iFlip, Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "p2a1" +-- L["wep." .. w .. ".name.full"] = "HK P2A1" +-- L["wep." .. w .. ".name"] = "P2A1" +-- L["wep." .. w .. ".desc"] = "Single-shot flare pistol for signaling and illumination use.\nStandard round has a weak incendiary explosion. Can load a variety of payloads, including some shotshell types." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Model & Textures: New World Interactive \nSounds: Raising The Bar: Redux, speedonerd \nAnimations: 8Z" + +w = ws .. "ppsh" +-- L["wep." .. w .. ".name"] = "PPSh-41" +-- L["wep." .. w .. ".desc"] = "Soviet SMG with a high fire rate and suitably massive but unreliable drum magazine. Accuracy is non-existent and very much optional." +-- L["wep." .. w .. ".desc.quote"] = "Ура!" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Various" +-- L["wep." .. w .. ".credits"] = "Model: Tripwire Interactive \nTextures: Rus_Ivan \nAnimations: Tactical Intervention, edited by speedonerd \nSounds: Rus_Ivan" + +w = ws .. "python" +-- L["wep." .. w .. ".name.full"] = "Colt Python" +-- L["wep." .. w .. ".name"] = "Python" +-- L["wep." .. w .. ".desc"] = "Quality revolver with pinpoint precision and raw power.\nTo be wielded one-handed, cowboy style." +-- L["wep." .. w .. ".desc.quote"] = "\"When you have to shoot, shoot. Don't talk.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Aggressive Napkin \nTextures: Teh Snake \nSounds: Vunsunta, XLongWayHome \nAnimations: Tactical Intervention" + +w = ws .. "rhino60ds" +-- L["wep." .. w .. ".name.full"] = "Chiappa Rhino 60DS" +-- L["wep." .. w .. ".name"] = "Rhino 60DS" +-- L["wep." .. w .. ".desc"] = "Modern long barrel revolver with hexagonal cylinder.\nBulky and has a heavy trigger, but recoil control is excellent." +-- L["wep." .. w .. ".desc.quote"] = "\"It's simple: overspecialize, and you breed in weakness. It's a slow death.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Chiappa Firearms" +-- L["wep." .. w .. ".credits"] = "Assets: Warface, ported by Dorian15 \nAnimations: Tactical Intervention" + +w = ws .. "sako85" +-- L["wep." .. w .. ".name"] = "Sako 85" +-- L["wep." .. w .. ".desc"] = "A simple, rugged hunting rifle with no room for tactical nonsense. Equipped with an adjustable 6x scope." +-- L["wep." .. w .. ".desc.quote"] = "Get a girl who loves you for your braaaains." +-- L["wep." .. w .. ".trivia.manufacturer"] = "SAKO" +-- L["wep." .. w .. ".credits"] = "Assets: No More Room In Hell \nAnimations: Tactical Intervention" + +w = ws .. "scorpionevo" +-- L["wep." .. w .. ".name.full"] = "CZ Scorpion EVO 3" +-- L["wep." .. w .. ".name"] = "Scorpion EVO" +-- L["wep." .. w .. ".desc"] = "Modern SMG with a extremely high rate of fire and intense recoil." +-- L["wep." .. w .. ".desc.quote"] = "Not to be confused with the vz. 61." +-- L["wep." .. w .. ".trivia.manufacturer"] = "CZ Uherský Brod" +-- L["wep." .. w .. ".credits"] = "Assets: Warface, Ghost1592365 \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +w = ws .. "shorty" +-- L["wep." .. w .. ".name.full"] = "Serbu Super Shorty" +-- L["wep." .. w .. ".name"] = "Shorty" +-- L["wep." .. w .. ".desc"] = "A custom-made ultra short shotgun, intended for extreme concealability but sacrifices accuracy and capacity. Fits in sidearm holsters." +-- L["wep." .. w .. ".desc.quote"] = "\"Need a facelift, pretty boy?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Serbu Firearms" +-- L["wep." .. w .. ".credits"] = "Assets: Millennia \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "sks" +-- L["wep." .. w .. ".name"] = "SKS" +-- L["wep." .. w .. ".desc"] = "Soviet semi-automatic rifle intended to complement the AK. A well-rounded medium range firearm." +-- L["wep." .. w .. ".desc.quote"] = "\"Don't breathe.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arsenal" +-- L["wep." .. w .. ".credits"] = "Assets: Firearms: Source \nAnimations: Tactical Intervention" + +w = ws .. "smle" +-- L["wep." .. w .. ".name.full"] = "Lee-Enfield Mk III*" +-- L["wep." .. w .. ".name"] = "Lee-Enfield" +-- L["wep." .. w .. ".desc"] = "Mass production British bolt-action rifle with high capacity." +-- L["wep." .. w .. ".desc.quote"] = "\"It was all so easy.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Royal Small Arms Factory" +-- L["wep." .. w .. ".credits"] = "Assets: Cry of Fear \nAnimations: Cry of Fear, Lazarus" + +w = ws .. "stg44" +-- L["wep." .. w .. ".name.full"] = "Sturmgewehr 44" +-- L["wep." .. w .. ".name"] = "StG 44" +-- L["wep." .. w .. ".desc"] = "Vintage assault rifle, considered by many to be the first of its kind. Somewhat powerful but prone to malfunctions." +-- L["wep." .. w .. ".desc.quote"] = "\"Mein carbine did not kill you? I will use gas! More gas!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "C.G. Haenel" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Tuuttipingu \nSounds: Navaro, Project Reality" + +w = ws .. "svt40" +-- L["wep." .. w .. ".name"] = "SVT-40" +-- L["wep." .. w .. ".desc"] = "WW2-era mass production semi-automatic rifle. Unreliable and has limited optic options, but still packs raw power." +-- L["wep." .. w .. ".desc.quote"] = "\"It's a sign that the Germans are starting to shit their pants!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tula Arsenal" +-- L["wep." .. w .. ".credits"] = "Model: MrRifleman \nTextures: BuLL5H1T" + +w = ws .. "svu" +-- L["wep." .. w .. ".name.full"] = "Dragunov SVU" +-- L["wep." .. w .. ".name"] = "SVU" +-- L["wep." .. w .. ".desc"] = "Bullpup modernized model of the SVD. Has good handling but average effective range.\nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"It's all just about perpective.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "KBP Instrument Design Bureau" +-- L["wep." .. w .. ".credits"] = "Assets: B0T \nSounds: NightmareMutant, sHiBaN, xLongWayHome \nAnimations: speedonerd" + +w = ws .. "sw10" +-- L["wep." .. w .. ".name.full"] = "SW Model 10" +-- L["wep." .. w .. ".name"] = "SW M10" +-- L["wep." .. w .. ".desc"] = "An iconic revolver favored by cops and criminals alike.\nChambered in a non-magnum cartridge, so it's easy to handle but not that powerful." +-- L["wep." .. w .. ".desc.quote"] = "\"Who's 'we', sucka?\" \"Smith and Wesson, and me.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Model: Harry Ridgeway, MediocrityGoggles \nTextures: Millenia \nSounds: Bethesda, Obsidian \nAnimations: Tactical Intervention" + +w = ws .. "sw686" +-- L["wep." .. w .. ".name.full"] = "SW Model 686" +-- L["wep." .. w .. ".name"] = "SW M686" +-- L["wep." .. w .. ".desc"] = "Magnum revolver with balanced, reliable performance." +-- L["wep." .. w .. ".desc.quote"] = "When there's no more room in hell, the dead will walk the Earth." +-- L["wep." .. w .. ".trivia.manufacturer"] = "Smith & Wesson" +-- L["wep." .. w .. ".credits"] = "Assets: No More Room In Hell \nAnimations: Tactical Intervention" + +w = ws .. "toz34" +-- Only has a .name.full because the weapon does. +-- L["wep." .. w .. ".name.full"] = "TOZ-34" +-- L["wep." .. w .. ".name"] = "TOZ-34" +-- L["wep." .. w .. ".desc"] = "Double-barrel hunting shotgun. Has decent accuracy and lethality, but its bulk and slow reload makes it ill-suited for combat." +-- L["wep." .. w .. ".desc.quote"] = "\"A nu, chiki briki i v damki!\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tulsky Oruzheiny Zavod" +-- L["wep." .. w .. ".credits"] = "Model: SAM61 \nSounds: rzen1th \nAnimations: speedonerd & 8Z" + +w = ws .. "toz106" +-- Only has a .name.full because the weapon does. +-- L["wep." .. w .. ".name.full"] = "TOZ-106" +-- L["wep." .. w .. ".name"] = "TOZ-106" +-- L["wep." .. w .. ".desc"] = "Bolt-action hunting shotgun. Small caliber shells have excellent accuracy but are not very lethal." +-- L["wep." .. w .. ".desc.quote"] = "\"Head, eyes.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Tulsky Oruzheiny Zavod" +-- L["wep." .. w .. ".credits"] = "Model: RusMarine85 \nAnimations: Tactical Intervention \nPorted by: Arctic" + +w = ws .. "uzi" +-- L["wep." .. w .. ".name.full"] = "IMI Uzi" +-- L["wep." .. w .. ".name"] = "Uzi" +-- L["wep." .. w .. ".desc"] = "Post-war submachine gun with amazing controllability. One of the most iconic guns ever invented." +-- L["wep." .. w .. ".desc.quote"] = "\"You know your weapons, buddy.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Israeli Military Industries" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Firearms: Source \nSounds: Ghost597879, Ganryu, Strelok, Sticer \nAnimations: Tactical Intervention" + +w = ws .. "vykhlop" +-- L["wep." .. w .. ".name.full"] = "VKS \"Vykhlop\"" +-- L["wep." .. w .. ".name"] = "VKS" +-- L["wep." .. w .. ".desc"] = "Subsonic sniper rifle with high capacity and rate of fire, but low muzzle velocity and poor handling.\nEquipped with a 6x scope by default." +-- L["wep." .. w .. ".desc.quote"] = "\"Na'am seyidi, al qanas ala al khatt.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "TsKIB SOO" +-- L["wep." .. w .. ".credits"] = "Model & Textures: Call to Arms \nAnimations: Tactical Intervention, Arctic" + +w = ws .. "woodsman" +-- L["wep." .. w .. ".name.full"] = "Colt Woodsman" +-- L["wep." .. w .. ".name"] = "Woodsman" +-- L["wep." .. w .. ".desc"] = "Small caliber sporting pistol. Light and controllable enough to use one-handed, but lethality is low." +-- L["wep." .. w .. ".desc.quote"] = "\"Oh, my God, is that a .22?\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Colt" +-- L["wep." .. w .. ".credits"] = "Model: Kimono, Faffout \nTextures: Crash \nSounds: Vunsunta, DMG, Strelok" + +-- Attachments (ak) +-- L["att.optic_sako85_4x.name.full"] = "Sako 85 4x Scope Zoom" +-- L["att.optic_sako85_4x.name"] = "4x" +-- L["att.optic_sako85_4x.desc"] = "Reduced scope magnification." + +-- L["att.optic_sako85_8x.name.full"] = "Sako 85 8x Scope Zoom" +-- L["att.optic_sako85_8x.name"] = "8x" +-- L["att.optic_sako85_8x.desc"] = "Increased scope magnification." + +-- L["att.optic_sako85_irons.name"] = "Iron Sights" +-- L["att.optic_sako85_irons.desc"] = "Remove scope for faster aim and better mobility." + +-- L["att.ammo_sako85_222.name.full"] = "Sako 85 .222 Winchester Mod Kit" +-- L["att.ammo_sako85_222.name"] = ".222 Win" +-- L["att.ammo_sako85_222.desc"] = "Load intermediate rounds with reduced recoil and lethality." + +-- L["att.ammo_sako85_300mag.name.full"] = "Sako 85 .300 Winchester Magnum Mod Kit" +-- L["att.ammo_sako85_300mag.name"] = ".300 Win Mag" +-- L["att.ammo_sako85_300mag.desc"] = "Load magnum rounds for improved lethality." + +-- L["att.optic_svt_pu.name.full"] = "SVT-40 3.5x PU Scope" +-- L["att.optic_svt_pu.name"] = "PU" +-- L["att.optic_svt_pu.desc"] = "Low power scope with specialized mount for the SVT-40." + +-- L["att.ammo_m202_smoke.name.full"] = "M202 Smoke Rockets" +-- L["att.ammo_m202_smoke.name"] = "Smoke" +-- L["att.ammo_m202_smoke.desc"] = "Rockets that produces a concealing smokescreen on impact." + +-- L["att.ammo_m202_apers.name.full"] = "M202 Hornet Rockets" +-- L["att.ammo_m202_apers.name"] = "Hornet" +-- L["att.ammo_m202_apers.desc"] = "Airburst fragmentation rockets for direct fire anti-personnel use." + +-- L["att.ammo_m202_harpoon.name.full"] = "M202 Harpoon Rockets" +-- L["att.ammo_m202_harpoon.name"] = "Harpoon" +-- L["att.ammo_m202_harpoon.desc"] = "Launch fiery harpoons that do tremendous damage on impact." + +-- L["att.ammo_m202_he.name.full"] = "M202 High-Explosive Anti-Tank Rockets" +-- L["att.ammo_m202_he.name"] = "HEAT" +-- L["att.ammo_m202_he.desc"] = "Rockets with an explosive charge." + +-- L["att.optic_mosin_irons.name.full"] = "Mosin-Nagant Sniper Bolt" +-- L["att.optic_mosin_irons.name"] = "Sniper" +-- L["att.optic_mosin_irons.desc"] = "Use the sniper bolt without a scope, increasing accuracy." + +-- L["att.optic_mosin_pu.name.full"] = "Mosin-Nagant 3.5x PU Scope" +-- L["att.optic_mosin_pu.name"] = "PU" +-- L["att.optic_mosin_pu.desc"] = "Side-mounted low power scope for the Mosin-Nagant." + +-- L["att.optic_mosin_pem.name.full"] = "Mosin-Nagant 6x PEM Scope" +-- L["att.optic_mosin_pem.name"] = "PEM" +-- L["att.optic_mosin_pem.desc"] = "Side-mounted sniper scope for the Mosin-Nagant." + +-- L["att.optic_mosin_pe.name.full"] = "Mosin-Nagant 4x PE Scope" +-- L["att.optic_mosin_pe.name"] = "PE" +-- L["att.optic_mosin_pe.desc"] = "Top-mounted medium range scope for the Mosin-Nagant." + +-- L["att.muzz_mosin_bayonet.name.full"] = "Mosin-Nagant Spike Bayonet" +-- L["att.muzz_mosin_bayonet.name"] = "Bayonet" +-- L["att.muzz_mosin_bayonet.desc"] = "For stabbing fascist scum." + +-- L["att.muzz_svu_supp.name.full"] = "Dragunov SVU Suppressor" +-- L["att.muzz_svu_supp.name"] = "SVU Supp." +-- L["att.muzz_svu_supp.desc"] = "Weapon-specific suppressor that boosts fire rate." + +-- L["att.muzz_sks_bayonet.name.full"] = "SKS Folding Bayonet" +-- L["att.muzz_sks_bayonet.name"] = "Bayonet" +-- L["att.muzz_sks_bayonet.desc"] = "For stabbing capitalist scum." + +-- L["att.tac_cz75_mag.name.full"] = "CZ-75 Backup Magazine" +-- L["att.tac_cz75_mag.name"] = "Backup Mag" +-- L["att.tac_cz75_mag.desc"] = "An extra magazine mounted on the gun for peace of mind." + +-- L["att.barrel_coachgun_short.name.full"] = "Coachgun Short Barrels" +-- L["att.barrel_coachgun_short.name"] = "Short" +-- L["att.barrel_coachgun_short.desc"] = "Significantly shortened barrel for close range encounters." + +-- L["att.ammo_automag3_30carbine.name.full"] = "AutoMag III .30 Carbine Mod Kit" +-- L["att.ammo_automag3_30carbine.name"] = ".30 Carbine" +-- L["att.ammo_automag3_30carbine.desc"] = "Load a carbine cartridge for improved accuracy and range." + +-- L["att.optic_smle_no32.name.full"] = "Lee Enfield No. 32 Telescopic Scope" +-- L["att.optic_smle_no32.name"] = "No. 32 Scope" +-- L["att.optic_smle_no32.desc"] = "Top-mounted medium range scope for the Lee-Enfield." + +-- L["att.ammo_p2a1_incendiary.name.full"] = "P2A1 Incendiary Cartridges" +-- L["att.ammo_p2a1_incendiary.name"] = "Incendiary" +-- L["att.ammo_p2a1_incendiary.desc"] = "Flares with a more powerful explosion but no illumination." + +-- L["att.ammo_p2a1_smoke.name.full"] = "P2A1 Smoke Cartridges" +-- L["att.ammo_p2a1_smoke.name"] = "Smoke" +-- L["att.ammo_p2a1_smoke.desc"] = "Flares that creates a small smokescreen on impact." + +-- L["att.ammo_p2a1_para.name.full"] = "P2A1 Illumination Flare Cartridges" +-- L["att.ammo_p2a1_para.name"] = "Illumination" +-- L["att.ammo_p2a1_para.desc"] = "White flares with a mini-parachute, lighting up a large area while it falls." + +-- L["att.ammo_p2a1_buckshot.name.full"] = "P2A1 Magnum Buck Shotshells" +-- L["att.ammo_p2a1_buckshot.name"] = "Magnum Buck" +-- L["att.ammo_p2a1_buckshot.desc"] = "Cram some shotshells into your flare gun for direct firepower." + +-- L["att.ammo_p2a1_bird.name.full"] = "P2A1 Birdshot Shotshells" +-- L["att.ammo_p2a1_bird.name"] = "Birdshot" +-- L["att.ammo_p2a1_bird.desc"] = "Cram some birdshells into your flare gun. Insane spread but hard to miss." + +-- L["att.ammo_p2a1_slug.name.full"] = "P2A1 Slug Shotshells" +-- L["att.ammo_p2a1_slug.desc"] = "Cram slugs into your flare gun. Short barrel limits accuracy and range." + +-- L["att.ammo_p2a1_frag.name.full"] = "P2A1 High-Explosive Frag Shotshells" +-- L["att.ammo_p2a1_frag.name"] = "Frag" +-- L["att.ammo_p2a1_frag.desc"] = "Turn your flare gun into a knockoff grenade pistol." + +-- L["att.ammo_p2a1_flashbang.name.full"] = "P2A1 Zvezda Flash Shotshells" +-- L["att.ammo_p2a1_flashbang.name"] = "Zvezda" +-- L["att.ammo_p2a1_flashbang.desc"] = "Flashbang dispenser in your pocket. Best used around corners." + +-- L["att.ammo_p2a1_breach.name.full"] = "P2A1 Breaching Shotshells" +-- L["att.ammo_p2a1_breach.name"] = "Breach" +-- L["att.ammo_p2a1_breach.desc"] = "Load a specialized breaching slug for pocket door busting." + +-- L["att.ammo_p2a1_confetti.name.full"] = "P2A1 Confetti Shotshells" +-- L["att.ammo_p2a1_confetti.name"] = "Confetti" +-- L["att.ammo_p2a1_confetti.desc"] = "For celebrations. Yippie!" + +-- L["att.procon.illumradius"] = "Illumination Radius" +-- L["att.procon.noflare"] = "No Flare" + +///////////////////// -- [[ One-Offs ]] -- +-- Weapons +ws = "tacrp_sp_" +w = ws .. "sg510speedo" +-- L["wep." .. w .. ".name.full"] = "SG 510 \"Black Shark\"" +-- L["wep." .. w .. ".name"] = "Black Shark" +-- L["wep." .. w .. ".desc"] = "A customized SG 510 with a chopped barrel and a serious muzzle brake, producing low recoil and a spectacular muzzleflash." +-- L["wep." .. w .. ".desc.quote"] = "\"My armor's black. That doesn't mean my heart is as well.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Vanderbilt Company" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd \nAssets: World of Guns: Disassembly, Tactical Intervention \nSounds: Sledgehammer Games, speedonerd" + +w = ws .. "mp5_zeroeight" +-- L["wep." .. w .. ".name.full"] = "MP5/10 \"Zero Eight\"" +-- L["wep." .. w .. ".name"] = "Zero Eight" +-- L["wep." .. w .. ".desc"] = "Customized 10mm MP5 with Swordfish kit, prototype foregrip and drum magazine. Weighted muzzle brake improves recoil handling and smacks real hard too." +-- L["wep." .. w .. ".desc.quote"] = "\"The lesson for you is never try.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Neptunium Arms" +-- L["wep." .. w .. ".credits"] = "Custom edited by speedonerd and 8Z \nMP5: Mr. Brightside, Stoke, Twinkie Masta, FxDarkloki \nAccessories: Treyarch, BlackSpot Entertainment, Crytek \nSounds: Strelok, CS:O2" + +w = ws .. "hecate_vinierspecial" +-- L["wep." .. w .. ".name.full"] = "PGM Hécate II \"Kingbreaker\"" +-- L["wep." .. w .. ".name"] = "The Kingbreaker" +-- L["wep." .. w .. ".desc"] = "Customized Hécate II with a mammoth suppressor, custom 16x scope sporting a jury-rigged rangefinder, and a rebellious message scrawled on the gun's side." +-- L["wep." .. w .. ".desc.quote"] = "\"No more chivalry. Now we fight like wolves.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "PGM Précision" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for VinierAardvark1 \nHécate II Model: Toby Burnside \nAdditional assets: Treyarch, Infinity Ward, valterjherson1, Unselles, speedonerd" + +w = ws .. "usp_valencespecial" +-- L["wep." .. w .. ".name.full"] = "HK USP \"The Governor\"" +-- L["wep." .. w .. ".name"] = "The Governor" +-- L["wep." .. w .. ".desc"] = "USP Elite decked out with competition parts and chambered in .45 Super for extra firepower. Superb performance at range, but not steady while moving." +-- L["wep." .. w .. ".desc.quote"] = "\"Loud enough to knock you down.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Heckler & Koch" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for Valence \nOriginal model: Thanez, Racer445, fxdarkloki \nAdditional assets: Battlestate Games, Crytek \nSounds: Vunsunta, BlitzBoaR" + +w = ws .. "tudspecial" +-- L["wep." .. w .. ".name.full"] = "Desert Eagle \"Arbiter\"" +-- L["wep." .. w .. ".name"] = "Arbiter" +-- L["wep." .. w .. ".desc"] = "Gold-plated Desert Eagle in a mock-carbine configuration and given the unholy power of fully-automatic fire." +-- L["wep." .. w .. ".desc.quote"] = "\"This is this and that is that.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Magnum Research" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for Tud \nDeagle: Vasht, Racer445 \nAdditional assets: kriboez, cR45h, Engys Epangelmatikes, Vitevius" + +w = ws .. "sr25_bladespecial" +-- L["wep." .. w .. ".name.full"] = "KAC SR-25 \"Symbiosis\"" +-- L["wep." .. w .. ".name"] = "Symbiosis" +-- L["wep." .. w .. ".desc"] = "Integrally suppressed SR-25 rechambered for .338 Lapua Magnum and sporting an adjustable 8x rangefinder scope." +-- L["wep." .. w .. ".desc.quote"] = "\"There are no gods. The only man in the sky, is me.\"" +-- L["wep." .. w .. ".trivia.manufacturer"] = "Knight's Armament" +-- L["wep." .. w .. ".credits"] = "Custom built by speedonerd for Bladelordomega \nSR-25: Firearms: Source \nAdditional assets: Battlestate Games, Treyarch, Crytek, kriboez, cR45h" + +-- L["hint.tac.bladespecial"] = "Adjust Zoom" diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/langs/legacy/content_pl.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/legacy/content_pl.lua new file mode 100644 index 0000000..43ebc56 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/langs/legacy/content_pl.lua @@ -0,0 +1,973 @@ +L = {} -- Polish Content Strings by meerfi +-- This file is incomplete and will be edited in the future. + +L["wep.tacrp_eo_sg510.name"] = [[SIG SG 510]] +L["wep.tacrp_eo_sg510.desc"] = [[Zabytkowy karabin bojowy o doskonałych osiągach na daleki dystans. Odrzut jest ostry, ale stabilny.]] +L["wep.tacrp_eo_sg510.desc.quote"] = [[]] + +L["wep.tacrp_sd_dualies.name"] = [[Dueling Dragons]] +L["wep.tacrp_sd_dualies.desc"] = [[Para spersonalizowanych pistoletów z dwukolorowym wykończeniem i smokami na uchwytach. Szybka obsługa i przyzwoita kontrola odrzutu, ale niska moc obalająca.]] +L["wep.tacrp_sd_dualies.desc.quote"] = [["Puściłem palec ze spustu i to było koniec."]] + +L["wep.tacrp_eo_spas15.name"] = [[Franchi SPAS-15]] +L["wep.tacrp_eo_spas15.desc"] = [[Następca legendarnej strzelby z trybem podawania magazynkowego. Ciężka waga pozwala na przyzwoitą kontrolę w trybie półautomatycznym.]] +L["wep.tacrp_eo_spas15.desc.quote"] = [["Poczekamy. Poczekamy i zobaczymy, co odkryje."]] + +L["wep.tacrp_h_executioner.name"] = [[Taurus Raging Judge]] +L["wep.tacrp_h_executioner.desc"] = [[Ogromny rewolwer strzelający małokalibrowymi nabojami śrutowymi. Strzela wieloma śrutami, ale rozrzut jest słaby.]] +L["wep.tacrp_h_executioner.desc.quote"] = [["Chodźcie, przyjaciele. Jeszcze nie jest za późno, by szukać nowego świata."]] + +L["wep.tacrp_io_p226.name"] = [[SIG P226]] +L["wep.tacrp_io_p226.desc"] = [[Pistolet z doskonałym zasięgiem i precyzją, ale niskimi obrażeniami ogólnymi.]] +L["wep.tacrp_io_p226.desc.quote"] = [["Poprawny termin to 'laski', proszę pana."]] + +L["wep.tacrp_io_k98_varmint.name"] = [[Varmint Rifle]] +L["wep.tacrp_io_k98_varmint.desc"] = [[Karabin powtarzalny oparty na mechanizmie Mausera. Akceptuje wszechobecne magazynki AR-15. Lekki, łatwy w obsłudze i ma dużą pojemność, ale obrażenia są niskie.]] +L["wep.tacrp_io_k98_varmint.desc.quote"] = [["Dla gryzoni... niepozornych rozmiarów."]] + +L["wep.tacrp_h_dual_hardballers.name"] = [[Dual Silverballers]] +L["wep.tacrp_h_dual_hardballers.desc"] = [[Para eleganckich pistoletów longslide, wysoki profil dla niskoprofilowych zabójców. Dobry zasięg jak na akimbo - o ile trafisz.]] +L["wep.tacrp_h_dual_hardballers.desc.quote"] = [["Będę szukać sprawiedliwości dla siebie. Wybiorę prawdę, którą lubię."]] + +L["wep.tacrp_pa_fort12.name"] = [[Fort-12]] +L["wep.tacrp_pa_fort12.desc"] = [[Ekonomiczny ukraiński pistolet rzadko spotykany poza Europą Wschodnią. Niska moc obalająca, ale poręczny i prawdopodobnie przetrwa kilka nadprzyrodzonych katastrof.]] +L["wep.tacrp_pa_fort12.desc.quote"] = [["Znajdź Streloka. Zabić Streloka?"]] + +L["wep.tacrp_io_cx4.name"] = [[Beretta CX4 Storm]] +L["wep.tacrp_io_cx4.desc"] = [[Półautomatyczna karabinek pistoletowy o dobrym zasięgu. Przeciętna penetracja pancerza, ale duża rama sprawia, że broń jest bardzo stabilna.]] +L["wep.tacrp_io_cx4.desc.quote"] = [["Jak powszechnie wiadomo, w arsenale pewnego lidera PMC."]] + +L["wep.tacrp_nade_smoke.name"] = [[Granat Dymny]] +L["wep.tacrp_nade_smoke.desc"] = [[]] +L["wep.tacrp_nade_smoke.desc.quote"] = [[]] + +L["wep.tacrp_io_coltsmg.name"] = [[Colt 9mm SMG]] +L["wep.tacrp_io_coltsmg.desc"] = [[SMG na platformie AR z ogniem seryjnym. Doskonała kontrola odrzutu, dobre obrażenia i zasięg, ale ograniczone opcje optyczne. Faworyzowany przez Departament Energii.]] +L["wep.tacrp_io_coltsmg.desc.quote"] = [["Głośnik nie jest w zestawie."]] + +L["wep.tacrp_m_harpoon.name"] = [[Extrema Ratio Harpoon]] +L["wep.tacrp_m_harpoon.desc"] = [[Cienki nóż z ząbkami na tylnej krawędzi dla głębszej penetracji.]] +L["wep.tacrp_m_harpoon.desc.quote"] = [[]] + +L["wep.tacrp_h_jackhammer.name"] = [[Pancor Jackhammer]] +L["wep.tacrp_h_jackhammer.desc"] = [[Jedyny w swoim rodzaju, ciężki automatyczny shotgun z cylindrycznym magazynkiem. Dość ciężki i trochę niepewny.]] +L["wep.tacrp_h_jackhammer.desc.quote"] = [["Muuu, mówię!"]] + +L["wep.tacrp_sd_1858.name"] = [[Remington 1858 Army]] +L["wep.tacrp_sd_1858.desc"] = [[Zabytkowy rewolwer na kapiszony o dużej mocy na krótkim dystansie, ale bardzo wolny w strzelaniu. Odpowiedni do kowbojskiego roleplayu.]] +L["wep.tacrp_sd_1858.desc.quote"] = [["Podaj whiskey!"]] + +L["wep.tacrp_sd_dual_1911.name"] = [[Dueling Wyverns]] +L["wep.tacrp_sd_dual_1911.desc"] = [[Para ozdobnych, złotych pistoletów M1911 z wyrytymi wizerunkami wiwern. Mocno uderzają, ale ich niska pojemność może być problematyczna.]] +L["wep.tacrp_sd_dual_1911.desc.quote"] = [[]] + +L["wep.tacrp_ex_hecate.name"] = [[PGM Hécate II]] +L["wep.tacrp_ex_hecate.desc"] = [[Ciężki karabin przeciwmateriałowy, który może zabić jednym strzałem. Wyposażony fabrycznie w lunetę 12x. Na tyle lekki, by go używać jako broń do walki wręcz.]] +L["wep.tacrp_ex_hecate.desc.quote"] = [["Testowany przez Gun Runner, zatwierdzony przez NCR."]] + +L["wep.tacrp_eo_g36c.name"] = [[HK G36C]] +L["wep.tacrp_eo_g36c.desc"] = [[Kompaktowa wersja karabinu G36, która wymienia skuteczny zasięg na zwiększoną szybkostrzelność i poprawioną obsługę.]] +L["wep.tacrp_eo_g36c.desc.quote"] = [[]] + +L["wep.tacrp_pa_fnmag.name"] = [[FN MAG]] +L["wep.tacrp_pa_fnmag.desc"] = [[Niezwykle ciężki "lekki" karabin maszynowy, który łączy cechy LMG i broni stacjonarnej. Posiada niemal niekończący się magazynek.]] +L["wep.tacrp_pa_fnmag.desc.quote"] = [["Nie możesz mnie tu zostawić z tymi... zwierzętami."]] + +L["wep.tacrp_ar_vltor.name"] = [[VLTOR SBR]] +L["wep.tacrp_ar_vltor.desc"] = [[Karabinek na platformie AR z unikalną osłoną na rękojeść w stylu piggyback. Doskonały na bliskie dystanse, bez utraty wydajności na średnich dystansach.]] +L["wep.tacrp_ar_vltor.desc.quote"] = [["Pospiesz się! Idź do Whiskey Hotel!"]] + +L["wep.tacrp_io_scarh.name"] = [[FN SCAR-H CQC]] +L["wep.tacrp_io_scarh.desc"] = [[Kompaktowy, bardzo mobilny karabin bojowy z szybką obsługą.]] +L["wep.tacrp_io_scarh.desc.quote"] = [["Sand Bravo, odczytujemy 70 celów w twoim sektorze."]] + +L["wep.tacrp_ak_galil_sniper.name"] = [[IMI Galil Sniper]] +L["wep.tacrp_ak_galil_sniper.desc"] = [[Izraelski AK w konfiguracji karabinu snajperskiego. Bardzo łatwy do kontrolowania, ale ma niską szybkostrzelność i przeciętną śmiertelność.]] +L["wep.tacrp_ak_galil_sniper.desc.quote"] = [[]] + +L["wep.tacrp_eo_winchester.name"] = [[Winchester M1873]] +L["wep.tacrp_eo_winchester.desc"] = [[Ikoniczny karabin kowbojski o konstrukcji, która przetrwała do dziś. Venerowany klasyk - ale nieodpowiedni na współczesne pola bitew.]] +L["wep.tacrp_eo_winchester.desc.quote"] = [["Broń, która zdobyła Zachód."]] + +L["wep.tacrp_eo_p99.name"] = [[Walther P99]] +L["wep.tacrp_eo_p99.desc"] = [[Dobrze zbalansowany pistolet z szybkim tempem ognia.]] +L["wep.tacrp_eo_p99.desc.quote"] = [["Ścigajcie ich i zabijajcie po kolei."]] + +L["wep.tacrp_io_af2011.name"] = [[AF2011-A1]] +L["wep.tacrp_io_af2011.desc"] = [[W zasadzie dwa połączone pistolety M1911, ta egzotyczna abominacja strzela dwiema kulami przy każdym pociągnięciu spustu.]] +L["wep.tacrp_io_af2011.desc.quote"] = [["Jeśli 1911 jest tak dobry, dlaczego nie ma 1911 2?"]] + +L["wep.tacrp_gsr1911.name"] = [[SIG 1911 TACOPS]] +L["wep.tacrp_gsr1911.desc"] = [[Pistolet o wysokich obrażeniach, ale niskim zasięgu i pojemności. Ewolucja taktyczna, albo jak niektórzy by powiedzieli, dewolucja klasyki.]] +L["wep.tacrp_gsr1911.desc.quote"] = [[]] + +L["wep.tacrp_ex_mac10.name"] = [[Ingram MAC-10]] +L["wep.tacrp_ex_mac10.desc"] = [[Karabin, który najlepiej sprawdza się w punktowym strzelaniu na oślep.]] +L["wep.tacrp_ex_mac10.desc.quote"] = [["Daj mi ten cholerny pistolet, Tre!"]] + +L["wep.tacrp_m_kitchen.name"] = [[Nóż Kuchenny]] +L["wep.tacrp_m_kitchen.desc"] = [[Zwykły, prosty nóż kuchenny. Łatwy do zdobycia i odpowiednio śmiertelny, noże takie jak te są popularne wśród gangów ulicznych i psychopatów. Posiada wbudowane Kradzież Życia.]] +L["wep.tacrp_m_kitchen.desc.quote"] = [["Hej, kolego, masz licencję na ten nóż?"]] + +L["wep.tacrp_eo_masada.name"] = [[Magpul Masada]] +L["wep.tacrp_eo_masada.desc"] = [[Zaawansowany karabin z lekką konstrukcją z polimeru.]] +L["wep.tacrp_eo_masada.desc.quote"] = [["Mógłbyś oczyścić świat z żelaza, a ja sprzedawałbym drewniane pałki."]] + +L["wep.tacrp_eo_m712.name"] = [[M712 Schnellfeuer]] +L["wep.tacrp_eo_m712.desc"] = [[Zabytkowy pistolet maszynowy o dużej celności, ale słabej kontroli odrzutu. Skuteczny w krótkich seriach.]] +L["wep.tacrp_eo_m712.desc.quote"] = [["Zamek może uderzyć cię w twarz."]] + +L["wep.tacrp_eo_mcx.name"] = [[SIG MCX SPEAR]] +L["wep.tacrp_eo_mcx.desc"] = [[Karabin bojowy zaprojektowany do różnych ról piechoty. Specjalna amunicja ma dużą penetrację pancerza i utrzymuje obrażenia na dystansie.]] +L["wep.tacrp_eo_mcx.desc.quote"] = [["Bravo Six, idziemy w ciemność."]] + +L["wep.tacrp_mp7.name"] = [[HK MP7]] +L["wep.tacrp_mp7.desc"] = [[PDW o doskonałej obsłudze i skuteczności na bliskim zasięgu. Amunicja o wysokiej prędkości zachowuje skuteczność na dystansie i łatwo przebija pancerz.]] +L["wep.tacrp_mp7.desc.quote"] = [["Zapomniałeś przeładować, cholerny!"]] + +L["wep.tacrp_io_t850.name"] = [[Taurus 850 Ultralite]] +L["wep.tacrp_io_t850.desc"] = [[Rewolwer z krótką lufą o kompaktowych wymiarach. Skuteczność gwałtownie spada poza zasięg punktowy.]] +L["wep.tacrp_io_t850.desc.quote"] = [[]] + +L["wep.tacrp_pa_makarov.name"] = [[Makarov PM]] +L["wep.tacrp_pa_makarov.desc"] = [[Masowo produkowany radziecki pistolet boczny zaprojektowany do częstego noszenia i rzadkiego strzelania. Skuteczność jest słaba poza zasięgiem punktowym.]] +L["wep.tacrp_pa_makarov.desc.quote"] = [[]] + +L["wep.tacrp_sd_dual_degala.name"] = [[Dual Eagles]] +L["wep.tacrp_sd_dual_degala.desc"] = [[Para ozdobnych, złotych Desert Eagles, jakby jeden nie wystarczył. Podwójna szalona moc obalająca, podwójny szalony odrzut.]] +L["wep.tacrp_sd_dual_degala.desc.quote"] = [["Moja krew... na ich rękach."]] + +L["wep.tacrp_p2000.name"] = [[HK P2000]] +L["wep.tacrp_p2000.desc"] = [[Dobrze zbalansowany, typowy pistolet policyjny.]] +L["wep.tacrp_p2000.desc.quote"] = [["Naprzód! Naprzód! Naprzód!"]] + +L["wep.tacrp_ex_stinger.name"] = [[FIM-92 Stinger]] +L["wep.tacrp_ex_stinger.desc"] = [[Kierowany wyrzutnik pocisków z funkcją namierzania. Wymaga namierzenia, aby strzelić. Trafienie nie jest gwarantowane.]] +L["wep.tacrp_ex_stinger.desc.quote"] = [["Rogacz przyparty do muru jest bardziej niebezpieczny niż szakal!"]] + +L["wep.tacrp_h_hardballer.name"] = [[AMT Hardballer]] +L["wep.tacrp_h_hardballer.desc"] = [[Pistolet z długą lufą i stalową konstrukcją. Dokładny i mocny na dystansie.]] +L["wep.tacrp_h_hardballer.desc.quote"] = [["Wrócę..."]] + +L["wep.tacrp_eo_mp5sd.name"] = [[HK MP5SD6]] +L["wep.tacrp_eo_mp5sd.desc"] = [[Wariant legendarnego SMG z wbudowanym tłumikiem. Zmniejszony zasięg, ale niski odrzut i brak widocznych smug.]] +L["wep.tacrp_eo_mp5sd.desc.quote"] = [["Broń wolna."]] + +L["wep.tacrp_eo_rhino20ds.name"] = [[Chiappa Rhino 20DS]] +L["wep.tacrp_eo_rhino20ds.desc"] = [[Nowoczesny rewolwer z krótką lufą i sześciokątnym bębnem. Mały kaliber i nisko położona lufa sprawiają, że broń jest szybka i łatwa do użycia.]] +L["wep.tacrp_eo_rhino20ds.desc.quote"] = "[ PAMIĘTAJ O NASZEJ OBIETNICY ]" + +L["wep.tacrp_ak_ak12.name"] = [[AK-12 Prototyp]] +L["wep.tacrp_ak_ak12.desc"] = [[Jedna z wielu prób modernizacji AK, ten eksperymentalny model korzysta z ognia seriami i umożliwia szybkie przełączanie kalibru broni.]] +L["wep.tacrp_ak_ak12.desc.quote"] = [[Oko Śnieżnego Wilka się otwiera.]] + +L["wep.tacrp_io_fnc.name"] = [[FN FNC Para]] +L["wep.tacrp_io_fnc.desc"] = [[Lekki karabin szturmowy z wysoką precyzją strzałów z biodra i mobilnością, ale niskim zasięgiem i słabą penetracją pancerza.]] +L["wep.tacrp_io_fnc.desc.quote"] = [["Mówię, co myślę i robię, co mówię."]] + +L["wep.tacrp_pa_vykhlop.name"] = [[VKS Vykhlop]] +L["wep.tacrp_pa_vykhlop.desc"] = [[Poddźwiękowy karabin snajperski z potężną i szybką do przeładowania amunicją poddźwiękową, ale ma niską prędkość wylotową i słabą penetrację pancerza. Wyposażony w celownik 6x.]] +L["wep.tacrp_pa_vykhlop.desc.quote"] = [[Na'am seyidi, al qanas ala al khatt.]] + +L["wep.tacrp_ex_m1911.name"] = [[Colt M1911]] +L["wep.tacrp_ex_m1911.desc"] = [[Nadmiarowy pistolet z ery przed taktycznymi dodatkami i optyką pistoletową, ale nadal mocno uderza.]] +L["wep.tacrp_ex_m1911.desc.quote"] = [["Hasta la vista, baby!"]] + +L["wep.tacrp_bekas.name"] = [[Molot Bekas-16M]] +L["wep.tacrp_bekas.desc"] = [[Celny strzelba myśliwska z niską szybkostrzelnością. +Ograniczona skuteczność przeciwko pancerzowi.]] +L["wep.tacrp_bekas.desc.quote"] = [[]] + +L["wep.tacrp_ar_sr25.name"] = [[KAC SR-25 EMR]] +L["wep.tacrp_ar_sr25.desc"] = [["Karabin Dostosowany" stworzony do precyzyjnego strzelania. +Niska pojemność, ale doskonała wydajność. +Wyposażony w celownik 10x.]] +L["wep.tacrp_ar_sr25.desc.quote"] = [[]] + +L["wep.tacrp_eo_m3.name"] = [[Benelli M3]] +L["wep.tacrp_eo_m3.desc"] = [[Półautomatyczna strzelba z dobrą kontrolą odrzutu i celnością.]] +L["wep.tacrp_eo_m3.desc.quote"] = [[]] + +L["wep.tacrp_mg4.name"] = [[HK MG4]] +L["wep.tacrp_mg4.desc"] = [[Karabin maszynowy o ogromnej sile ognia, ale trudny do użycia bez rozstawionego dwójnogu.]] +L["wep.tacrp_mg4.desc.quote"] = [[]] + +L["wep.tacrp_io_m14.name"] = [[M14 SOPMOD]] +L["wep.tacrp_io_m14.desc"] = [[Zmodernizowany karabin z krótką lufą o poprawionej mobilności i szybkostrzelności automatycznej, ale z dużym odrzutem. +Wyposażony w celownik 6x.]] +L["wep.tacrp_io_m14.desc.quote"] = [[]] + +L["wep.tacrp_ak_galil_lmg.name"] = [[IMI Galil ARM]] +L["wep.tacrp_ak_galil_lmg.desc"] = [[Pochodna AK w konfiguracji karabinu maszynowego. +Lekka i strzela w szybkim i kontrolowanym tempie, ale ma przeciętną siłę rażenia.]] +L["wep.tacrp_ak_galil_lmg.desc.quote"] = [["Wiesz, dla mnie, liczy się akcja."]] + +L["wep.tacrp_pa_ithaca.name"] = [[Ithaca 37]] +L["wep.tacrp_pa_ithaca.desc"] = [[Strzelba z czasów II wojny światowej, używana przez policję i wojsko. Zdolna do szybkiego ognia, ale ma słabe rozproszenie i niską pojemność amunicji.]] +L["wep.tacrp_pa_ithaca.desc.quote"] = [[]] + +L["wep.tacrp_eo_stg58.name"] = [[FN FAL]] +L["wep.tacrp_eo_stg58.desc"] = [[Weteran karabin bojowy o dużej sile ognia, ale z dużym odrzutem.]] +L["wep.tacrp_eo_stg58.desc.quote"] = [[Prawa ręka wolnego świata.]] + +L["wep.tacrp_ex_glock.name"] = [[Glock 17]] +L["wep.tacrp_ex_glock.desc"] = [[Polimerowy pistolet z większą niż standardowa pojemnością magazynka i szybkim tempem strzałów.]] +L["wep.tacrp_ex_glock.desc.quote"] = [[Nie wykrywa się na lotniskowych wykrywaczach metalu.]] + +L["wep.tacrp_nade_gas.name"] = [[Granat z gazem CS]] +L["wep.tacrp_nade_gas.desc"] = [[]] +L["wep.tacrp_nade_gas.desc.quote"] = [[]] + +L["wep.tacrp_ar_m16a4.name"] = [[Colt M16A4]] +L["wep.tacrp_ar_m16a4.desc"] = [[Nowoczesny karabin piechoty z nowoczesnymi usprawnieniami, takimi jak górna szyna i chwyt RIS. Wszechstronna broń piechoty o dobrym skutecznym zasięgu.]] +L["wep.tacrp_ar_m16a4.desc.quote"] = [[]] + +L["wep.tacrp_eo_m60.name"] = [[M60]] +L["wep.tacrp_eo_m60.desc"] = [[Ciężki karabin maszynowy o dużej sile rażenia, ale niskiej szybkostrzelności. Nazywany "Świnią" ze względu na swoją wagę.]] +L["wep.tacrp_eo_m60.desc.quote"] = [["Żyj za nic, albo umrzyj za coś. Twój wybór."]] + +L["wep.tacrp_sd_superx3.name"] = [[Winchester Super X3]] +L["wep.tacrp_sd_superx3.desc"] = [[Cywilna strzelba sportowa, zaprojektowana z myślą o wydajności. Długa lufa i sportowy czok zapewniają dobrą kontrolę i zasięg, ale słabe prowadzenie.]] +L["wep.tacrp_sd_superx3.desc.quote"] = [[]] + +L["wep.tacrp_ex_ump45.name"] = [[HK UMP45]] +L["wep.tacrp_ex_ump45.desc"] = [[Kanciasty pistolet maszynowy zaprojektowany z myślą o niskich kosztach produkcji. +Wysokie obrażenia z bliska, ale niska skuteczność na zasięg i niska szybkostrzelność.]] +L["wep.tacrp_ex_ump45.desc.quote"] = [["Co to za pistolety koktajlowe?"]] + +L["wep.tacrp_io_xm8car.name"] = [[HK XM8 Compact]] +L["wep.tacrp_io_xm8car.desc"] = [[Eksperymentalny karabinek wielozadaniowy. Łatwy w użyciu, ale z niskimi obrażeniami. +Wyposażony w regulowany, zintegrowany celownik 2-8x.]] +L["wep.tacrp_io_xm8car.desc.quote"] = [[]] + +L["wep.tacrp_g36k.name"] = [[HK G36K]] +L["wep.tacrp_g36k.desc"] = [[Karabin szturmowy o wysokiej prędkości wylotowej. Dobrze nadaje się do średniego zasięgu ognia ciągłego. +Wyposażony w celownik 2x.]] +L["wep.tacrp_g36k.desc.quote"] = [[]] + +L["wep.tacrp_io_star15.name"] = [[Spikes Tactical AR-15]] +L["wep.tacrp_io_star15.desc"] = [[Poprawiony karabin AR-15 z myślą o precyzyjnym strzelaniu. +Zawsze wybór samotników na ścieżce zemsty, twój los będzie hałaśliwym pogrzebem... i cichym pożegnaniem. Przynieś detonator.]] +L["wep.tacrp_io_star15.desc.quote"] = [[]] + +L["wep.tacrp_ar_ar15pistol.name"] = [[AR-15 Compact]] +L["wep.tacrp_ar_ar15pistol.desc"] = [[Bezkolbowy, bardzo krótki karabin AR-15. +Prawnie pistolet, wystarczająco lekki, aby funkcjonować jako broń boczna, ale niestabilny i nieprecyzyjny bez formy karabinu.]] +L["wep.tacrp_ar_ar15pistol.desc.quote"] = [[]] + +L["wep.tacrp_h_smaw.name"] = [[Mk 153 SMAW]] +L["wep.tacrp_h_smaw.desc"] = [[Przenośny miotacz bunkrów z powolnymi, potężnymi rakietami. Laserowy wskaźnik umożliwia sterowanie rakietą po włączeniu. Może montować optykę i ma wbudowany Corner-Cam.]] +L["wep.tacrp_h_smaw.desc.quote"] = [[]] + +L["wep.tacrp_ex_usp.name"] = [[HK USP]] +L["wep.tacrp_ex_usp.desc"] = [[Taktyczny pistolet o dobrej sile rażenia i zasięgu jak na jego pojemność.]] +L["wep.tacrp_ex_usp.desc.quote"] = [[Broń z wyboru dla wolnych ludzi.]] + +L["wep.tacrp_sd_thompson.name"] = [[M1A1 Thompson]] +L["wep.tacrp_sd_thompson.desc"] = [[Pistolet maszynowy z czasów II wojny światowej z solidnymi drewnianymi elementami. Imponująca siła ognia na bliskim dystansie, ale znacznie cięższy niż powinien być.]] +L["wep.tacrp_sd_thompson.desc.quote"] = [[]] + +L["wep.tacrp_ak47.name"] = [[FB Beryl 96]] +L["wep.tacrp_ak47.desc"] = [[Łatwy w obsłudze karabin o niskiej szybkostrzelności i niskim odrzucie.]] +L["wep.tacrp_ak47.desc.quote"] = [[Mimo wyglądu, to nie jest AK.]] + +L["wep.tacrp_io_k98.name"] = [[Karabiner 98k]] +L["wep.tacrp_io_k98.desc"] = [[Zabytkowy karabin z zamkiem czterotaktowym o trwałej konstrukcji. Potężny z bliska, ale zasadniczo przestarzały na współczesnym polu bitwy.]] +L["wep.tacrp_io_k98.desc.quote"] = [["Chcesz totalnej wojny?"]] + +L["wep.tacrp_eo_m29.name"] = [[SW Model 29 "Satan"]] +L["wep.tacrp_eo_m29.desc"] = [[Dostosowany rewolwer magnum z ciężkim spustem, ale doskonałą kontrolą odrzutu.]] +L["wep.tacrp_eo_m29.desc.quote"] = [["Czujesz się szczęściarzem, łobuzie?"]] + +L["wep.tacrp_eo_scout.name"] = [[Steyr Scout]] +L["wep.tacrp_eo_scout.desc"] = [[Lekki karabin zaprojektowany z myślą o przenośności i komforcie, a nie o bezpośredniej sile ognia. Ma wbudowany dwójnóg. +Wyposażony w celownik 6x.]] +L["wep.tacrp_eo_scout.desc.quote"] = [[]] + +L["wep.tacrp_ex_ak47.name"] = [[AK-47]] +L["wep.tacrp_ex_ak47.desc"] = [[Ikoniczny radziecki karabin szturmowy. Solidna i prosta konstrukcja, która zainspirowała niezliczone klony i pochodne.]] +L["wep.tacrp_ex_ak47.desc.quote"] = [[Esencjonalna broń złoczyńców.]] + +L["wep.tacrp_ex_mp9.name"] = [[BT MP9]] +L["wep.tacrp_ex_mp9.desc"] = [[Kompaktowy pistolet maszynowy z polimeru, oferujący dużą siłę ognia w małym opakowaniu.]] +L["wep.tacrp_ex_mp9.desc.quote"] = [["Twoja prawa ręka odpada?"]] + +L["wep.tacrp_eo_megastar.name"] = [[Star Megastar]] +L["wep.tacrp_eo_megastar.desc"] = [[Duży pistolet o solidnej konstrukcji. Duża pojemność i siła ognia, ale ma intensywny odrzut.]] +L["wep.tacrp_eo_megastar.desc.quote"] = [["Chcesz się ze mną rozprawić?"]] + +L["wep.tacrp_pa_sako85.name"] = [[Sako 85]] +L["wep.tacrp_pa_sako85.desc"] = [[Prosty, wytrzymały karabin myśliwski bez miejsca na taktyczne bzdury. Wyposażony w regulowany celownik 6x.]] +L["wep.tacrp_pa_sako85.desc.quote"] = [[]] + +L["wep.tacrp_m_css.name"] = [[The Classic]] +L["wep.tacrp_m_css.desc"] = [[Klasyczny nóż z lat 90., zanim modne wzory i absurdalne ceny stały się normą.]] +L["wep.tacrp_m_css.desc.quote"] = [["Dobra, ruszajmy!"]] + +L["wep.tacrp_m_gerber.name"] = [[Gerber LMF Infantry]] +L["wep.tacrp_m_gerber.desc"] = [[Kompaktowy nóż do sytuacji przetrwania, takich jak patroszenie ryb, rzeźbienie drewna lub patroszenie i rzeźbienie wroga.]] +L["wep.tacrp_m_gerber.desc.quote"] = [[]] + +L["wep.tacrp_uratio.name"] = [[PGM Ultima Ratio]] +L["wep.tacrp_uratio.desc"] = [[Lekki karabin snajperski o dobrej sile rażenia i dużej mobilności. +Wyposażony w domyślnie 10-krotny celownik.]] +L["wep.tacrp_uratio.desc.quote"] = [[]] + +L["wep.tacrp_eo_ar70.name"] = [[Beretta AR70/223]] +L["wep.tacrp_eo_ar70.desc"] = [[Masywny karabin szturmowy z kontrolowalną szybkostrzelnością.]] +L["wep.tacrp_eo_ar70.desc.quote"] = [[]] + +L["wep.tacrp_civ_mp5.name"] = [[HK HK94]] +L["wep.tacrp_civ_mp5.desc"] = [[Półautomatyczny model legendarnego pistoletu maszynowego. +Wykorzystuje magazynki o zmniejszonej pojemności.]] +L["wep.tacrp_civ_mp5.desc.quote"] = [[]] + +L["wep.tacrp_uzi.name"] = [[IMI Uzi Pro]] +L["wep.tacrp_uzi.desc"] = [[Zrównoważony pistolet maszynowy z kontrolowalną szybkostrzelnością.]] +L["wep.tacrp_uzi.desc.quote"] = [[]] + +L["wep.tacrp_eo_auga1.name"] = [[Steyr AUG A1]] +L["wep.tacrp_eo_auga1.desc"] = [[Klasyczna wersja AUG z większym magazynkiem i trybem automatycznym. +Dostarczana z domyślnie 1.5-krotnym celownikiem.]] +L["wep.tacrp_eo_auga1.desc.quote"] = [[]] + +L["wep.tacrp_eo_m733.name"] = [[Colt Model 733]] +L["wep.tacrp_eo_m733.desc"] = [[Karabinek o długości zbliżonej do subkarabinka AR-15, z szybką szybkostrzelnością. Stała rękojeść nośna ogranicza opcje optyki.]] +L["wep.tacrp_eo_m733.desc.quote"] = [["Robię to, co potrafię najlepiej; zbieram punkty."]] + +L["wep.tacrp_c4_detonator.name"] = [[Detonator C4]] +L["wep.tacrp_c4_detonator.desc"] = [[Urządzenie do detonacji ładunków C4 lub innych rodzajów ładunków wybuchowych zdalnie.]] +L["wep.tacrp_c4_detonator.desc.quote"] = [[]] + +L["wep.tacrp_ak_svd.name"] = [[SVD]] +L["wep.tacrp_ak_svd.desc"] = [[Rosyjski karabin snajperski o niskiej szybkostrzelności, ale dużej sile rażenia i kontroli odrzutu. Wyposażony domyślnie w celownik 6x. +Mimo zewnętrznego podobieństwa do konstrukcji AK, jest całkowicie niepowiązany mechanicznie.]] +L["wep.tacrp_ak_svd.desc.quote"] = [[]] + +L["wep.tacrp_m_bat.name"] = [[Louisville Slugger TPX]] +L["wep.tacrp_m_bat.desc"] = [[Aluminiowa pałka bejsbolowa, dobra do bijania home runów lub łamania czaszek.]] +L["wep.tacrp_m_bat.desc.quote"] = [["Pop quiz! Ile czasu zajmuje zabicie debila?"]] + +L["wep.tacrp_civ_p90.name"] = [[FN PS90]] +L["wep.tacrp_civ_p90.desc"] = [[Półautomatyczna wariacja futurystycznego PDW. +Wykorzystuje magazynki o zmniejszonej pojemności.]] +L["wep.tacrp_civ_p90.desc.quote"] = [[]] + +L["wep.tacrp_xd45.name"] = [[Springfield XD-45]] +L["wep.tacrp_xd45.desc"] = [[Automatyczny pistolet maszynowy o niesamowitej sile na krótkim dystansie.]] +L["wep.tacrp_xd45.desc.quote"] = [[]] + +L["wep.tacrp_nade_heal.name"] = [[Puszka z Medi-Dymem]] +L["wep.tacrp_nade_heal.desc"] = [[]] +L["wep.tacrp_nade_heal.desc.quote"] = [[]] + +L["wep.tacrp_sd_bizon.name"] = [[PP-19 Bizon]] +L["wep.tacrp_sd_bizon.desc"] = [[SMG pochodzące z AK z dużym pojemnym magazynkiem spiralnym. Dość słaby, ale łatwy do kontrolowania i obsługi.]] +L["wep.tacrp_sd_bizon.desc.quote"] = [[]] + +L["wep.tacrp_h_nitrorifle.name"] = [[Karabin H&H Double]] +L["wep.tacrp_h_nitrorifle.desc"] = [[Butikowy podwójny karabin do polowania na duże zwierzęta, strzelający pociskiem zaprojektowanym do powalania słoni. Pomimo swojego wyglądu, nie jest to strzelba.]] +L["wep.tacrp_h_nitrorifle.desc.quote"] = [["Życie to wielka przygoda, zaakceptuj to w takim duchu."]] + +L["wep.tacrp_io_rpk.name"] = [[RPK]] +L["wep.tacrp_io_rpk.desc"] = [[Lekki karabin maszynowy pochodzący od karabinu piechoty. Wysoka siła rażenia i dobry odrzut, ale słaba mobilność i rozrzut.]] +L["wep.tacrp_io_rpk.desc.quote"] = [[]] + +L["wep.tacrp_io_vp70.name"] = [[HK VP70]] +L["wep.tacrp_io_vp70.desc"] = [[Pistolet polimerowy z innowacyjnym kolbą-holsterem, umożliwiającym niezwykle szybki ogień seriami.]] +L["wep.tacrp_io_vp70.desc.quote"] = [[]] + +L["wep.tacrp_ar_m16a1.name"] = [[Colt M16A1]] +L["wep.tacrp_ar_m16a1.desc"] = [[An antique rifle recovered from the rice fields. +Boasts high firing rate and good range, though don't expect this bucket of rust to run without a hitch or two.]] +L["wep.tacrp_ar_m16a1.desc.quote"] = [[Welcome to the jungle.]] + +L["wep.tacrp_io_ruger.name"] = [[AWC Amphibian Ruger]] +L["wep.tacrp_io_ruger.desc"] = [[Pistolet małego kalibru wyposażony w zintegrowany tłumik. Prawie zerowy odrzut ze względu na słabe naboje.]] +L["wep.tacrp_io_ruger.desc.quote"] = [["Ciesz się, że nigdy nie miałeś wyboru."]] + +L["wep.tacrp_tgs12.name"] = [[Molot TGS-12]] +L["wep.tacrp_tgs12.desc"] = [[Strzelba z krótką lufą i chwyt pistoletowy. Wysoka mobilność i odrzut, najbardziej skuteczna na krótkim dystansie.]] +L["wep.tacrp_tgs12.desc.quote"] = [[]] + +L["wep.tacrp_io_m500.name"] = [[SW Model 500]] +L["wep.tacrp_io_m500.desc"] = [[Masywny rewolwer o długiej lufie wystrzeliwujący potężny nabój magnum, uznawany za najpotężniejszy pistolet produkcyjny na świecie.]] +L["wep.tacrp_io_m500.desc.quote"] = [["Wy, idioci, zrujnowaliście pięcioletnie śledztwo!"]] + +L["wep.tacrp_pa_uzi.name"] = [[IMI Uzi]] +L["wep.tacrp_pa_uzi.desc"] = [[Powojenny pistolet maszynowy o niesamowitej sterowalności. Jeden z najbardziej kultowych pistoletów, jakie kiedykolwiek wynaleziono. Ta wersja strzela z zamkniętym ryglem.]] +L["wep.tacrp_pa_uzi.desc.quote"] = [["Znasz się na swoich broniach, kumpel."]] + +L["wep.tacrp_sd_ppk.name"] = [[Walther PPK]] +L["wep.tacrp_sd_ppk.desc"] = [[Kompaktowy, niskopojemnościowy pistolet kieszonkowy, słynny dzięki filmom. Najlepiej nadaje się do obrony na bliskim dystansie.]] +L["wep.tacrp_sd_ppk.desc.quote"] = [["Szokujące. Absolutnie szokujące."]] + +L["wep.tacrp_ak_ak74u.name"] = [[AKS-74U]] +L["wep.tacrp_ak_ak74u.desc"] = [[Karabinek o rozmiarze pistoletu maszynowego zaprojektowany dla załóg czołgów i sił specjalnych. Imponująca siła ognia w małym opakowaniu, ale odrzut nie jest delikatny.]] +L["wep.tacrp_ak_ak74u.desc.quote"] = [[]] + +L["wep.tacrp_ar_lr300.name"] = [[Z-M LR-300]] +L["wep.tacrp_ar_lr300.desc"] = [[Pochodna AR "Light Rifle" z modyfikowanym systemem gazowym. Oferuje wysoką mobilność i szybkostrzelność, ale stabilność jest poniżej standardu. +"300" oznacza maksymalny zasięg skuteczny karabinu w metrach.]] +L["wep.tacrp_ar_lr300.desc.quote"] = [[]] + +L["wep.tacrp_eo_browninghp.name"] = [[Browning Hi-Power]] +L["wep.tacrp_eo_browninghp.desc"] = [[Starożytny pistolet o doskonałej ogólnej wydajności, ale niskiej szybkostrzelności.]] +L["wep.tacrp_eo_browninghp.desc.quote"] = [["Co do cholery...?"]] + +L["wep.tacrp_io_sl8.name"] = [[HK SL8]] +L["wep.tacrp_io_sl8.desc"] = [[Półautomatyczna wersja G36 przeznaczona do precyzyjnego strzelania. Niska szybkostrzelność, ale kontrola odrzutu jest doskonała. +Wyposażony w celownik 2x, ale nie ma opcji celownika mechanicznego.]] +L["wep.tacrp_io_sl8.desc.quote"] = [[]] + +L["wep.tacrp_m_machete.name"] = [[Machete]] +L["wep.tacrp_m_machete.desc"] = [[Wielofunkcyjny nóż maczety, chociaż większość zastosowań obejmuje kogoś kłującego.]] +L["wep.tacrp_m_machete.desc.quote"] = [[]] + +L["wep.tacrp_p250.name"] = [[SIG P250]] +L["wep.tacrp_p250.desc"] = [[Potężny pistolet, który rezygnuje z pojemności na rzecz siły rażenia i precyzji.]] +L["wep.tacrp_p250.desc.quote"] = [[]] + +L["wep.tacrp_h_intervention.name"] = [[CheyTac Intervention]] +L["wep.tacrp_h_intervention.desc"] = [[Wyglądający jak z przyszłości hybrydowy karabin snajperski-AMR z doskonałą penetracją pancerza i wysoką prędkością wylotową. +Wyposażony domyślnie w celownik 12x.]] +L["wep.tacrp_h_intervention.desc.quote"] = [[1v1 na rust m8 założę się, że nawet nie potrafisz szybko celować, ty zjebie!]] + +L["wep.tacrp_eo_p210.name"] = [[SIG P210]] +L["wep.tacrp_eo_p210.desc"] = [[Elegancki powojenny pistolet z jednorzędowym magazynkiem i kurkowym mechanizmem spustowym.]] +L["wep.tacrp_eo_p210.desc.quote"] = [[]] + +L["wep.tacrp_m_bayonet.name"] = [[M9 Phrobis III]] +L["wep.tacrp_m_bayonet.desc"] = [[Standardowy wojskowy nóż, który można zamontować na broni jako bagnet. Nic nie stoi na przeszkodzie, aby używać go jak zwykłego noża.]] +L["wep.tacrp_m_bayonet.desc.quote"] = [[]] + +L["wep.tacrp_io_870.name"] = [[Remington 870 SPMM]] +L["wep.tacrp_io_870.desc"] = [[Strzelba "Marine Magnum" pokryta niklem. Słaba obsługa, ale ma dużą pojemność i moc ognia.]] +L["wep.tacrp_io_870.desc.quote"] = [["JEST ZŁOTO W TYCH WZGÓRZACH!"]] + +L["wep.tacrp_m_boina.name"] = [[Cudeman Boina Verde]] +L["wep.tacrp_m_boina.desc"] = [[Solidny, duży nóż do przetrwania. "Boina verde" to po hiszpańsku "zielony beret", próba zareklamowania noża jako wojskowego.]] +L["wep.tacrp_m_boina.desc.quote"] = [[]] + +L["wep.tacrp_knife.name"] = [[Nóż składany]] +L["wep.tacrp_knife.desc"] = [[Wielofunkcyjny nóż składany, chociaż większość zastosowań wiąże się z kłuciem kogoś.]] +L["wep.tacrp_knife.desc.quote"] = [[]] + +L["wep.tacrp_nade_thermite.name"] = [[Granat termiczny]] +L["wep.tacrp_nade_thermite.desc"] = [[]] +L["wep.tacrp_nade_thermite.desc.quote"] = [[]] + +L["wep.tacrp_eo_l85.name"] = [[Enfield L85A2]] +L["wep.tacrp_eo_l85.desc"] = [[Brytyjski karabin bullpup o średniej wydajności i wątpliwej niezawodności. Nie zdziw się, jeśli czasami się zacina. +Domyślnie wyposażony w lunetę 3x.]] +L["wep.tacrp_eo_l85.desc.quote"] = [[]] + +L["wep.tacrp_io_xm8lmg.name"] = [[HK XM8 LMG]] +L["wep.tacrp_io_xm8lmg.desc"] = [[Eksperymentalny karabin wielozadaniowy w konfiguracji MG. Lekki, o dużej pojemności i niskim odrzucie, ale słaba moc. +Posiada regulowany wbudowany celownik 2-8x.]] +L["wep.tacrp_io_xm8lmg.desc.quote"] = [[]] + +L["wep.tacrp_vertec.name"] = [[Beretta 92FS Vertec]] +L["wep.tacrp_vertec.desc"] = [[Włoski pistolet o ponadprzeciętnym zasięgu i celności.]] +L["wep.tacrp_vertec.desc.quote"] = [["Yippie ki-yay, matko jedna!"]] + +L["wep.tacrp_m_fasthawk.name"] = [[SOG Fasthawk]] +L["wep.tacrp_m_fasthawk.desc"] = [[Zmodernizowany tomahawk zaprojektowany do walki. Aerodynamiczny design pozwala na dalekie rzuty.]] +L["wep.tacrp_m_fasthawk.desc.quote"] = [[]] + +L["wep.tacrp_superv.name"] = [[Kriss Vector]] +L["wep.tacrp_superv.desc"] = [[SMG na krótki zasięg o bardzo wysokiej szybkostrzelności i praktycznie braku odrzutu. Niska penetracja pancerza, ale może go szybko przegryźć.]] +L["wep.tacrp_superv.desc.quote"] = [[]] + +L["wep.tacrp_m_shovel.name"] = [[Łopata]] +L["wep.tacrp_m_shovel.desc"] = [[Stara łopata wojskowa, zaprojektowana do szybkiego kopania okopów. Świetnie sprawdza się jako prymitywna broń biała, mająca zarówno tępe oblicze, jak i ostry brzeg.]] +L["wep.tacrp_m_shovel.desc.quote"] = [["Robale!"]] + +L["wep.tacrp_sphinx.name"] = [[Sphinx 2000]] +L["wep.tacrp_sphinx.desc"] = [[Pistolet premium zmodyfikowany do strzału 3-rundowego. Wysoka szybkostrzelność, ale długi opóźnienie między seriami.]] +L["wep.tacrp_sphinx.desc.quote"] = [[]] + +L["wep.tacrp_skorpion.name"] = [[Skorpion vz. 61]] +L["wep.tacrp_skorpion.desc"] = [[Lekki pistolet maszynowy o dobrym zasięgu, odrzucie i rozrzucie.]] +L["wep.tacrp_skorpion.desc.quote"] = [[]] + +L["wep.tacrp_sg551.name"] = [[SIG SG 551]] +L["wep.tacrp_sg551.desc"] = [[Karabin szturmowy o doskonałej wydajności, zrównoważony mniejszą pojemnością magazynka.]] +L["wep.tacrp_sg551.desc.quote"] = [["Bez pytań, bez odpowiedzi. To jest nasz biznes."]] + +L["wep.tacrp_sd_wa2000.name"] = [[Walther WA 2000]] +L["wep.tacrp_sd_wa2000.desc"] = [[Elegancki karabin snajperski bullpup o wysokim uszkodzeniu i wysokiej szybkostrzelności. +Domyślnie wyposażony w lunetę 12x.]] +L["wep.tacrp_sd_wa2000.desc.quote"] = [["Imiona są dla przyjaciół, więc nie potrzebuję jednego."]] + +L["wep.tacrp_sd_vz58.name"] = [[Sa vz. 58]] +L["wep.tacrp_sd_vz58.desc"] = [[Karabin szturmowy o wysokim uszkodzeniu z doskonałymi możliwościami przebijania pancerza, przekształcony na półautomatyczny dla rynków cywilnych.]] +L["wep.tacrp_sd_vz58.desc.quote"] = [[Pomimo wyglądu, to na pewno nie jest AK.]] + +L["wep.tacrp_sd_tt33.name"] = [[Tokarev TT-33]] +L["wep.tacrp_sd_tt33.desc"] = [[Antyczny pistolet zza Żelaznej Kurtyny. Amunicja ma zaskakująco duży cios, ale jest niepewna.]] +L["wep.tacrp_sd_tt33.desc.quote"] = [[]] + +L["wep.tacrp_h_jdj.name"] = [[SSK .950 JDJ]] +L["wep.tacrp_h_jdj.desc"] = [[Niewyobrażalnie duży "karabin myśliwski" strzelający absurdalnie potężnym pociskiem. Nie wiem, jak w ogóle jesteś w stanie strzelać z tego z ramienia. +Domyślnie wyposażony w lunetę 10x.]] +L["wep.tacrp_h_jdj.desc.quote"] = [[]] + +L["wep.tacrp_p90.name"] = [[FN P90]] +L["wep.tacrp_p90.desc"] = [[Bullpup PDW z hojnym magazynkiem ładowanym od góry i kontrolowanym rozrzutem. Pociski o wysokiej prędkości zachowują skuteczność na odległość i łatwo przebijają pancerz.]] +L["wep.tacrp_p90.desc.quote"] = [["To jest broń wojenna, stworzona do zabijania twojego wroga."]] + +L["wep.tacrp_m_incorp.name"] = [[Nóż Viper]] +L["wep.tacrp_m_incorp.desc"] = [[Efektowny nóż składany z premium wykończeniem ze stali nierdzewnej i rękojeścią z drewna.]] +L["wep.tacrp_m_incorp.desc.quote"] = [[]] + +L["wep.tacrp_pa_ots33.name"] = [[OTs-33 Pernach]] +L["wep.tacrp_pa_ots33.desc"] = [[Rosyjski pistolet maszynowy o niskim odrzucie, zaprojektowany dla sił paramilitarnych.]] +L["wep.tacrp_pa_ots33.desc.quote"] = [[]] + +L["wep.tacrp_sd_m1carbine.name"] = [[M1 Carbine]] +L["wep.tacrp_sd_m1carbine.desc"] = [[Karabin samopowtarzalny z okresu II wojny światowej. Przeznaczony jako broń obronna dla wojsk wsparcia, jest precyzyjny i lekki, ale ma przeciętną moc.]] +L["wep.tacrp_sd_m1carbine.desc.quote"] = [[]] + +L["wep.tacrp_sd_gyrojet.name"] = [[MBA Gyrojet MkI]] +L["wep.tacrp_sd_gyrojet.desc"] = [[Niejasny eksperymentalny pistolet strzelający samonapędzającymi się minirakietami. Obecnie głównie przedmiot kolekcjonerski, amunicja jest potężna, ale niewiarygodna.]] +L["wep.tacrp_sd_gyrojet.desc.quote"] = [[Z czyjej prywatnej kolekcji to ukradłeś?]] + +L["wep.tacrp_sd_groza.name"] = [[OTs-14 "Groza-4"]] +L["wep.tacrp_sd_groza.desc"] = [[Niejasny karabin bullpup wykonany z przekonfigurowanego AK. Słaby, ale ma świetne prowadzenie i stabilność. +Tłumik nie jest integralny, ale działa jako chwyt przedni.]] +L["wep.tacrp_sd_groza.desc.quote"] = [["Znikaj stąd, stalkerze."]] + +L["wep.tacrp_sd_g3.name"] = [[HK G3A3]] +L["wep.tacrp_sd_g3.desc"] = [[Precyzyjny ciężki karabin bojowy z dość znośnym trybem ognia automatycznego, ale wolnym obsługiwaniem.]] +L["wep.tacrp_sd_g3.desc.quote"] = [[]] + +L["wep.tacrp_io_ab10.name"] = [[Intratec AB-10]] +L["wep.tacrp_io_ab10.desc"] = [[Półautomatyczny model "After Ban" TEC-9 z krótką, nietokowaną lufą. Duży magazynek, ale niewiarygodny.]] +L["wep.tacrp_io_ab10.desc.quote"] = [["Kto potrzebuje broni szturmowej?"]] + +L["wep.tacrp_sd_famas.name"] = [[FAMAS F1]] +L["wep.tacrp_sd_famas.desc"] = [[Bullpup z ogniem serii o wysokiej szybkostrzelności i świetnej celności, ale ograniczony przez poniżej standardową pojemność magazynka i dość intensywny odrzut. +Ma dwójnóg z tajemniczych francuskich powodów. ]] +L["wep.tacrp_sd_famas.desc.quote"] = [[]] + +L["wep.tacrp_sd_dual_uzis.name"] = [[Podwójne Uzis]] +L["wep.tacrp_sd_dual_uzis.desc"] = [[Para pełnoautomatycznych Micro Uzis. Nie wiem, jak oczekujesz trafić cokolwiek z tym wyposażeniem, ale rób, jak uważasz.]] +L["wep.tacrp_sd_dual_uzis.desc.quote"] = [["Uderzę cię tyloma prawami, że będziesz błagać o lewe!"]] + +L["wep.tacrp_sd_dual_usp.name"] = [[Podwójne mecze]] +L["wep.tacrp_sd_dual_usp.desc"] = [[Para pistoletów zdobytych od pary martwych metrocops. Przyzwoite obrażenia i pojemność, ale ciężkie i wolne do strzelania.]] +L["wep.tacrp_sd_dual_usp.desc.quote"] = [[]] + +L["wep.tacrp_sd_dual_ppk.name"] = [[Podwójni agenci]] +L["wep.tacrp_sd_dual_ppk.desc"] = [[Para tłumionych pistoletów PPK. Szybkie i precyzyjne, ale niska pojemność i przeciętne obrażenia wymagają ostrego oka i dyscypliny spustu.]] +L["wep.tacrp_sd_dual_ppk.desc.quote"] = [[Lepiej nie wybieraj Oddjoba.]] + +L["wep.tacrp_sd_delisle.name"] = [[Karabin De Lisle]] +L["wep.tacrp_sd_delisle.desc"] = [[Karabin samopowtarzalny kaliberu pistoletowego z wbudowanym tłumikiem. Jedna z najcichszych broni palnych kiedykolwiek wykonanych, jego pociski subsoniczne nie mają smug, ale poruszają się powoli.]] +L["wep.tacrp_sd_delisle.desc.quote"] = [[Wystrzelony do Tamizy, nikt go nie usłyszał.]] + +L["wep.tacrp_sd_contender.name"] = [[T/C G2 Contender]] +L["wep.tacrp_sd_contender.desc"] = [[Jednostrzałowy pistolet myśliwski. Precyzyjny, kompaktowy i śmiertelny, więc lepiej, żeby ta jedna runda się liczyła. +Domyślnie wyposażony w lunetę 6x.]] +L["wep.tacrp_sd_contender.desc.quote"] = [["Wiesz co mnie denerwuje? Dwie grupy ludzi..."]] + +L["wep.tacrp_sd_1022.name"] = [[Ruger 10/22]] +L["wep.tacrp_sd_1022.desc"] = [[Ultra lekki karabin do strzelania. Wysoce precyzyjny i łatwy w obsłudze, ale jest ledwo śmiertelny, chyba że zdobędzie strzał w głowę.]] +L["wep.tacrp_sd_1022.desc.quote"] = [[]] + +L["wep.tacrp_rpg7.name"] = [[RPG-7]] +L["wep.tacrp_rpg7.desc"] = [[Radziecka wyrzutnia rakiet z potężnym wybuchem. +Bezpiecznik zapobiega detonacjom z bliska.]] +L["wep.tacrp_rpg7.desc.quote"] = [[]] + +L["wep.tacrp_m_kukri.name"] = [[Kukri]] +L["wep.tacrp_m_kukri.desc"] = [[Krótki nóż z zakrzywionym ostrzem, rzekomo zaprojektowany tak, aby zaczepiał o cele i zadawał maksymalne obrażenia.]] +L["wep.tacrp_m_kukri.desc.quote"] = [["Profesjonaliści mają standardy."]] + +L["wep.tacrp_pdw.name"] = [[KAC PDW]] +L["wep.tacrp_pdw.desc"] = [[Karabin kaliberu subcompact PDW. Idealne połączenie karabinu i pistoletu maszynowego.]] +L["wep.tacrp_pdw.desc.quote"] = [[]] + +L["wep.tacrp_pa_toz34.name"] = [[TOZ-34]] +L["wep.tacrp_pa_toz34.desc"] = [[Podwójna strzelba myśliwska. Ma przyzwoitą celność i siłę rażenia, ale jej rozmiar i wolne przeładowanie sprawiają, że jest nieodpowiednia do walki.]] +L["wep.tacrp_pa_toz34.desc.quote"] = [["A nu, chiki briki i v damki!"]] + +L["wep.tacrp_pa_toz106.name"] = [[TOZ-106]] +L["wep.tacrp_pa_toz106.desc"] = [[Strzelba myśliwska z zamkiem ślizgowym. Małe kaliberowe pociski mają doskonałą celność, ale nie są bardzo śmiercionośne.]] +L["wep.tacrp_pa_toz106.desc.quote"] = [[]] + +L["wep.tacrp_pa_svt40.name"] = [[SVT-40]] +L["wep.tacrp_pa_svt40.desc"] = [[Karabin samopowtarzalny z okresu II wojny światowej zaprojektowany do ekstremalnej produkcji masowej. Potężny, ale nieprecyzyjny i niewiarygodny. Ograniczone wybory optyki.]] +L["wep.tacrp_pa_svt40.desc.quote"] = [[]] + +L["wep.tacrp_pa_shorty.name"] = [[Serbu Super Shorty]] +L["wep.tacrp_pa_shorty.desc"] = [[Niestandardowa modyfikacja odbiornika Mossberg 88, przeznaczona do ekstremalnego ukrywania. Jednak poświęca precyzję i pojemność. Pasuje do kabur bocznych.]] +L["wep.tacrp_pa_shorty.desc.quote"] = [["Shawty poszła nisko, nisko, nisko, nisko..."]] + +L["wep.tacrp_ex_m4a1.name"] = [[Colt M4A1]] +L["wep.tacrp_ex_m4a1.desc"] = [[Prawdziwa amerykańska klasyka z wysoką szybkostrzelnością i zrównoważonymi osiągami.]] +L["wep.tacrp_ex_m4a1.desc.quote"] = [[]] + +L["wep.tacrp_pa_python.name"] = [[Colt Python]] +L["wep.tacrp_pa_python.desc"] = [[Rzadki rewolwer o reputacji precyzyjnej i mocnej broni. Do strzelania jedną ręką; styl kowbojski.]] +L["wep.tacrp_pa_python.desc.quote"] = [["Kiedy musisz strzelać, strzelaj. Nie mów."]] + +L["wep.tacrp_eo_hkcaws.name"] = [[HK CAWS]] +L["wep.tacrp_eo_hkcaws.desc"] = [[Prototypowy strzelba bullpup o przyzwoitym zasięgu i pełnym ogniu automatycznym. +Wyposażony w stały celownik 1,5x.]] +L["wep.tacrp_eo_hkcaws.desc.quote"] = [["Koniec jazdy, mutancie."]] + +L["wep.tacrp_sd_mp40.name"] = [[Steyr MP40]] +L["wep.tacrp_sd_mp40.desc"] = [[SMG z okresu II wojny światowej o niskiej szybkostrzelności i diabelskim profilu odrzutu. Pomimo swojego wieku, nadal pojawia się w wielu strefach wojennych.]] +L["wep.tacrp_sd_mp40.desc.quote"] = [["Hans, twoja kawa jest okropna. Nie będę tego pił."]] + +L["wep.tacrp_m_cleaver.name"] = [[Siekać mięso]] +L["wep.tacrp_m_cleaver.desc"] = [[Duże, solidne ostrze do siekania mięsa, czy to mięsa zwierzęcego, czy ludzkiego. Ma wrodzone Wampiryzm.]] +L["wep.tacrp_m_cleaver.desc.quote"] = [[]] + +L["wep.tacrp_m_rambo.name"] = [[Nóż Bowie]] +L["wep.tacrp_m_rambo.desc"] = [[Klasyczny design noża stworzony specjalnie do pojedynków, kiedy walki na noże były formą rozrywki. +Pozostaje popularny dzięki swojemu związaniu z nadmiernie męskimi gwiazdami filmów akcji z lat 80.]] +L["wep.tacrp_m_rambo.desc.quote"] = [[]] + +L["wep.tacrp_pa_madsen.name"] = [[Madsen M-50]] +L["wep.tacrp_pa_madsen.desc"] = [[Opłacalny duński SMG, który jest łatwy w użyciu, ale ma nieimponującą siłę ognia. Celownik nie może być montowany z powodu górnego uchwytu do ładowania.]] +L["wep.tacrp_pa_madsen.desc.quote"] = [["Czy wyrzekasz się Szatana... I wszystkich jego dzieł?"]] + +L["wep.tacrp_pa_m79.name"] = [[M79]] +L["wep.tacrp_pa_m79.desc"] = [[Granatnik z długą lufą i kolbą z drewna z dżungli. Precyzyjny i ma szybką prędkość początkową, ale jest dość ciężki.]] +L["wep.tacrp_pa_m79.desc.quote"] = [["Goooooooooooooood morning, Wietnam!"]] + +L["wep.tacrp_ak_ak74.name"] = [[AK-74]] +L["wep.tacrp_ak_ak74.desc"] = [[Wszechstronny klasyk z Europy Wschodniej o kontrolowanym odrzucie i dobrym zasięgu. Modyfikacje obejmują lekkie meble i mocowania do optyki na krawędziach.]] +L["wep.tacrp_ak_ak74.desc.quote"] = [[]] + +L["wep.tacrp_ak_rk95.name"] = [[Sako RK 95]] +L["wep.tacrp_ak_rk95.desc"] = [[Fińska pochodna AK z wysoką penetracją pancerza i przedłużonym magazynkiem.]] +L["wep.tacrp_ak_rk95.desc.quote"] = [[Pomimo wyglądu... Właściwie, ten jest prawie jak AK.]] + +L["wep.tacrp_nade_frag.name"] = [[Granat odłamkowy]] +L["wep.tacrp_nade_frag.desc"] = [[]] +L["wep.tacrp_nade_frag.desc.quote"] = [[]] + +L["wep.tacrp_mr96.name"] = [[Manurhin MR96]] +L["wep.tacrp_mr96.desc"] = [[Rewolwer magnum o dobrym prowadzeniu i sile zatrzymania. Precyzyjny, ale trudny do szybkiego strzelania.]] +L["wep.tacrp_mr96.desc.quote"] = [[]] + +L["wep.tacrp_m4star10.name"] = [[Benelli M4]] +L["wep.tacrp_m4star10.desc"] = [[Półautomatyczna strzelba o bardzo wysokim wyjściu obrażeń. Przeładowywanie może być kłopotliwe.]] +L["wep.tacrp_m4star10.desc.quote"] = [[Nie ma nic, czego siedem śrutówek 12 Gauge nie mogłoby rozwiązać.]] + +L["wep.tacrp_m14.name"] = [[Springfield M1A]] +L["wep.tacrp_m14.desc"] = [[Półautomatyczny karabin z śmiertelnym strzałem w głowę. +Domyślnie wyposażony w lunetę 6x.]] +L["wep.tacrp_m14.desc.quote"] = [[]] + +L["wep.tacrp_mp5.name"] = [[HK MP5A3]] +L["wep.tacrp_mp5.desc"] = [[Zrównoważony pistolet maszynowy znany ze swojej precyzji.]] +L["wep.tacrp_mp5.desc.quote"] = [["Teraz mam karabin maszynowy. Ho, ho, ho."]] + +L["wep.tacrp_medkit.name"] = [[Apteczka pierwszej pomocy]] +L["wep.tacrp_medkit.desc"] = [[Kompaktowy zestaw środków medycznych do leczenia ran. +Może leczyć siebie, ale jest bardziej skuteczny, gdy jest używany na innych. +Zaopatrzenie regeneruje się z czasem.]] +L["wep.tacrp_medkit.desc.quote"] = [[]] + +L["wep.tacrp_m_wrench.name"] = [[Klucz do rur]] +L["wep.tacrp_m_wrench.desc"] = [[Solidny klucz do dokręcania rur wodnych i gazowych. Konstrukcja z całkowicie żelazna sprawia, że jest to dość tępa broń.]] +L["wep.tacrp_m_wrench.desc.quote"] = [[]] + +L["wep.tacrp_io_automag.name"] = [[Auto Mag Pistol]] +L["wep.tacrp_io_automag.desc"] = [[Wysoce precyzyjny pistolet magnum. Świetne prowadzenie dzięki swojemu rozmiarowi, ale może montować tylko optykę pistoletową.]] +L["wep.tacrp_io_automag.desc.quote"] = [[Przeklęty design, który zdołał większość swoich producentów.]] + +L["wep.tacrp_m_tracker.name"] = [[Nóż Tracker]] +L["wep.tacrp_m_tracker.desc"] = [[Duży nóż do flaków zaprojektowany do wielu zastosowań łowieckich, jednym z nich jest, wygodnie, rozcinanie ludzkich celów.]] +L["wep.tacrp_m_tracker.desc.quote"] = [[]] + +L["wep.tacrp_m_tonfa.name"] = [[Pałka policyjna]] +L["wep.tacrp_m_tonfa.desc"] = [[Specjalistyczna pałka policyjna z ukośnym uchwytem do alternatywnego chwytu. Przydatna do tłumienia zamieszek w upadających "demokratycznych" krajach.]] +L["wep.tacrp_m_tonfa.desc.quote"] = [[]] + +L["wep.tacrp_spr.name"] = [[Remington 700 SPS]] +L["wep.tacrp_spr.desc"] = [[Karabin myśliwski średniego zasięgu o szybkim cyklu strzału. +Domyślnie wyposażony w lunetę 6x.]] +L["wep.tacrp_spr.desc.quote"] = [[]] + +L["wep.tacrp_io_tec9.name"] = [[Intratec TEC-9]] +L["wep.tacrp_io_tec9.desc"] = [[Pistolet maszynowy o złej sławie ze względu na łatwość konwersji na pełny automat, a następnie kryminalne użycie.]] +L["wep.tacrp_io_tec9.desc.quote"] = [["Klient ma zawsze rację."]] + +L["wep.tacrp_m_pan.name"] = [[Patelnia]] +L["wep.tacrp_m_pan.desc"] = [[Naczynie do gotowania na kuchence wykonane z solidnego żeliwa. Stało się ikoniczną bronią dzięki slapstickowi i kreskówkom.]] +L["wep.tacrp_m_pan.desc.quote"] = [[]] + +L["wep.tacrp_m_knife3.name"] = [[Nóż Task Force]] +L["wep.tacrp_m_knife3.desc"] = [[Duży nóż z ząbkowanym ostrzem i zakrzywioną rękojeścią, trochę przesadzony dla tego, co ma być nożem wojskowym.]] +L["wep.tacrp_m_knife3.desc.quote"] = [["MAMO, WEŹ APARAT!!"]] + +L["wep.tacrp_sd_pkm.name"] = [[PKM]] +L["wep.tacrp_sd_pkm.desc"] = [[Uniwersalny karabin maszynowy zdolny do intensywnego ognia zaporowego. Wysoka pojemność i obrażenia, ale jest bardzo, bardzo masywny.]] +L["wep.tacrp_sd_pkm.desc.quote"] = [[]] + +L["wep.tacrp_m_heathawk.name"] = [[Heat Hawk]] +L["wep.tacrp_m_heathawk.desc"] = [[Broń MS w kształcie topora przeskalowana do ludzkiego rozmiaru. +Oryginał wykorzystuje nadgrzewane ostrze, które przecina metal jak masło, ale ta replika ma tylko świecącą naklejkę przymocowaną do metalowego ostrza.]] +L["wep.tacrp_m_heathawk.desc.quote"] = [[]] + +L["wep.tacrp_io_sg550.name"] = [[SIG SG 550-1 Sniper]] +L["wep.tacrp_io_sg550.desc"] = [[Karabin wyborowy kaliberu karabinu z szybkim ogniem automatycznym. Łatwy do kontroli w krótkich seriach i ma wysoką penetrację pancerza. Domyślnie wyposażony w lunetę 6x.]] +L["wep.tacrp_io_sg550.desc.quote"] = [[]] + +L["wep.tacrp_m_glock.name"] = [[Glock Feldmesser 78]] +L["wep.tacrp_m_glock.desc"] = [[Solidny nóż polowy z mocnym chwytem, zaprojektowany do użytku w austriackim Jagdkommando.]] +L["wep.tacrp_m_glock.desc.quote"] = [[Tak, to ten sam Glock, który produkuje pistolety.]] + +L["wep.tacrp_m_crowbar.name"] = [[Łom]] +L["wep.tacrp_m_crowbar.desc"] = [[Wszechstronne narzędzie użytkowe przeznaczone do wyważania rzeczy; skrzynki, sejfy, drzwi i ludzie!]] +L["wep.tacrp_m_crowbar.desc.quote"] = [["Myślę, że upuściłeś to z powrotem w Black Mesa."]] + +L["wep.tacrp_m_bamaslama.name"] = [[Alabama Slammer]] +L["wep.tacrp_m_bamaslama.desc"] = [[Nóż bowie nazwany na cześć koktajlu, z ząbkowanym tylnym ostrzem i mocowaniem bagnetu.]] +L["wep.tacrp_m_bamaslama.desc.quote"] = [["Czy powiedziałem, że możesz to zrobić?"]] + +L["wep.tacrp_eo_hushpup.name"] = [[SW Mk 22 Mod 0]] +L["wep.tacrp_eo_hushpup.desc"] = [[Wintage pistolet sił specjalnych zaprojektowany do operacji tajnych.]] +L["wep.tacrp_eo_hushpup.desc.quote"] = [["Rozpoczynanie operacji Snake Eater."]] + +L["wep.tacrp_m4.name"] = [[Diemaco C8A1]] +L["wep.tacrp_m4.desc"] = [[Bliski kuzyn klasycznego amerykańskiego karabinu o wolniejszym, ale bardziej kontrolowanym ogniu.]] +L["wep.tacrp_m4.desc.quote"] = [[Zielony i bardzo, bardzo zły.]] + +L["wep.tacrp_m1.name"] = [[Ruger Mini-14]] +L["wep.tacrp_m1.desc"] = [[Lekki karabin bez kolby ani montażu optyki. +Dobra celność strzałów z biodra wśród karabinów, ale zasięg jest niski.]] +L["wep.tacrp_m1.desc.quote"] = [["Ten z pistoletem mówi prawdę."]] + +L["wep.tacrp_ks23.name"] = [[KS-23]] +L["wep.tacrp_ks23.desc"] = [[Wykonana z recyklingowanych luf do karabinów lotniczych, ta ciężka strzelba strzela pociskami o dwukrotnie większej średnicy niż typowe śrutówki i może łatwo rozerwać na kawałki wszystko, do czego jest mniej więcej skierowana. Może wyważyć drzwi.]] +L["wep.tacrp_ks23.desc.quote"] = [[]] + +L["wep.tacrp_io_chinalake.name"] = [[China Lake Launcher]] +L["wep.tacrp_io_chinalake.desc"] = [[Ciężki granatnik o działaniu pompowym z wysoką prędkością początkową, ale słabą obsługą.]] +L["wep.tacrp_io_chinalake.desc.quote"] = [[Tylko 59 takich kiedykolwiek istniało. Skąd masz ten jeden?]] + +L["wep.tacrp_k1a.name"] = [[Daewoo K1A]] +L["wep.tacrp_k1a.desc"] = [[Karabin serii z minimalnym odrzutem i dobrą celnością strzałów z biodra.]] +L["wep.tacrp_k1a.desc.quote"] = [[]] + +L["wep.tacrp_io_vss.name"] = [[VSS Vintorez]] +L["wep.tacrp_io_vss.desc"] = [[Karabin wyborowy z integralnym tłumikiem o wysokiej szybkostrzelności i niskim odrzucie, ale słabo radzi sobie w długich seriach. +Domyślnie wyposażony w lunetę 6x.]] +L["wep.tacrp_io_vss.desc.quote"] = [[]] + +L["wep.tacrp_io_val.name"] = [[AS Val]] +L["wep.tacrp_io_val.desc"] = [[Karabin z integralnym tłumikiem o wysokim wyjściu obrażeń i precyzji, ale słabo radzi sobie przy długich seriach.]] +L["wep.tacrp_io_val.desc.quote"] = [[]] + +L["wep.tacrp_io_usc.name"] = [[HK USC]] +L["wep.tacrp_io_usc.desc"] = [[Półautomatyczna wersja karabinka UMP. Używa magazynków o niskiej pojemności, ale jego długa lufa i zespół chwytu zapewniają doskonałe osiągi na średnim dystansie.]] +L["wep.tacrp_io_usc.desc.quote"] = [[]] + +L["wep.tacrp_io_trg42.name"] = [[Sako TRG-42]] +L["wep.tacrp_io_trg42.desc"] = [[Karabin snajperski Magnum o przyzwoitej obsłudze i mobilności. +Potężny, ale wolno się przeładowuje i nie jest bardzo stabilny. +Domyślnie wyposażony w lunetę 12x.]] +L["wep.tacrp_io_trg42.desc.quote"] = [[]] + +L["wep.tacrp_m_pipe.name"] = [[Rura Ołowiana]] +L["wep.tacrp_m_pipe.desc"] = [[Solidna ołowiana rura wodociągowa. Mimo że nie została zaprojektowana do zadawania jakiejkolwiek szkody, uderzenie nią powoduje dość dużo szkód.]] +L["wep.tacrp_m_pipe.desc.quote"] = [[]] + +L["wep.tacrp_io_sg550r.name"] = [[SIG SG 550-2 SP]] +L["wep.tacrp_io_sg550r.desc"] = [[Długi karabin przekształcony na półautomatyczny dla rynków cywilnych. Łatwy w obsłudze i ma wysoką penetrację pancerza.]] +L["wep.tacrp_io_sg550r.desc.quote"] = [[]] + +L["wep.tacrp_ar_hk416.name"] = [[HK HK416]] +L["wep.tacrp_ar_hk416.desc"] = [[Elegancki czarny karabin stworzony jako konkurent dla AR-15. Precyzyjny i o niskim odrzucie kosztem pewnej masy.]] +L["wep.tacrp_ar_hk416.desc.quote"] = [[Elitarna broń jak ta to wszystko, czego potrzebujesz.]] + +L["wep.tacrp_nade_flashbang.name"] = [[Granat Błyskowy]] +L["wep.tacrp_nade_flashbang.desc"] = [[]] +L["wep.tacrp_nade_flashbang.desc.quote"] = [[]] + +L["wep.tacrp_eo_psg1.name"] = [[HK PSG-1]] +L["wep.tacrp_eo_psg1.desc"] = [[Elegancki półautomatyczny karabin snajperski o niezrównanej precyzji i kontroli odrzutu, ale z niższym niż średnia zasięgiem. +Domyślnie wyposażony w lunetę 8x.]] +L["wep.tacrp_eo_psg1.desc.quote"] = [["Urodziłem się na polu bitwy. Wychowałem się na polu bitwy."]] + +L["wep.tacrp_eo_calico.name"] = [[Calico M950A]] +L["wep.tacrp_eo_calico.desc"] = [[Dziwnie wyglądający pistolet kosmiczny z ogromnym magazynkiem helikalnym. Przekształcony na pełne automatyczne.]] +L["wep.tacrp_eo_calico.desc.quote"] = [[]] + +L["wep.tacrp_eo_mpx.name"] = [[SIG MPX]] +L["wep.tacrp_eo_mpx.desc"] = [[Zaawansowany SMG ze stabilnym odrzutem i przedłużonym magazynkiem.]] +L["wep.tacrp_eo_mpx.desc.quote"] = [[]] + +L["wep.tacrp_aug.name"] = [[Steyr AUG A2]] +L["wep.tacrp_aug.desc"] = [[Karabin bullpup z serią strzałów o dużej pojemności magazynka i świetnej obsłudze.]] +L["wep.tacrp_aug.desc.quote"] = [["Chcę krwi!" "I ją dostaniesz."]] + +L["wep.tacrp_pa_lewis.name"] = [[Karabin Lewis]] +L["wep.tacrp_pa_lewis.desc"] = [[Karabin maszynowy zasilany z tacy, chłodzony wodą, z dość małym magazynkiem.]] +L["wep.tacrp_pa_lewis.desc.quote"] = [["Wszyscy z drogi!"]] + +L["wep.tacrp_ak_aek971.name"] = [[AEK-971]] +L["wep.tacrp_ak_aek971.desc"] = [[Eksperymentalny karabin szturmowy używający unikalnego mechanizmu tłumiącego odczuwalny odrzut. Wysoka szybkostrzelność, ale przeciętny zasięg.]] +L["wep.tacrp_ak_aek971.desc.quote"] = [[]] + +L["wep.tacrp_eo_jericho.name"] = [[Jericho 941]] +L["wep.tacrp_eo_jericho.desc"] = [[Solidny pistolet 9mm o dużej mobilności i wysokiej szybkostrzelności. +Reklamowany jako "Baby Eagle" ze względu na powierzchowne podobieństwo do Desert Eagle.]] +L["wep.tacrp_eo_jericho.desc.quote"] = [["Do zobaczenia, kosmiczny kowboju..."]] + +L["wep.tacrp_sd_aac_hb.name"] = [[AAC Honey Badger]] +L["wep.tacrp_sd_aac_hb.desc"] = [[Lekki karabin szturmowy z integralnym tłumikiem. Mocny w walce wręcz i nie ma widocznego śladu, ale ma słabe osiągi na dystansie.]] +L["wep.tacrp_sd_aac_hb.desc.quote"] = [[]] + +L["wep.tacrp_mtx_dual.name"] = [[Dual MTX]] +L["wep.tacrp_mtx_dual.desc"] = [[Rozrzutna para kompaktowych pistoletów o dużej pojemności, wysokim uszkodzeniu i wysokiej jakości. +Przy takiej sile ognia, kto potrzebuje celowania?]] +L["wep.tacrp_mtx_dual.desc.quote"] = [[]] + +L["wep.tacrp_eo_scarl.name"] = [[FN SCAR-L]] +L["wep.tacrp_eo_scarl.desc"] = [[Modularny, lekki karabin szturmowy o umiarkowanej szybkostrzelności.]] +L["wep.tacrp_eo_scarl.desc.quote"] = [[]] + +L["wep.tacrp_nade_charge.name"] = [[Ładunek Wyważający]] +L["wep.tacrp_nade_charge.desc"] = [[]] +L["wep.tacrp_nade_charge.desc.quote"] = [[]] + +L["wep.tacrp_io_degala.name"] = [[Desert Eagle]] +L["wep.tacrp_io_degala.desc"] = [[Imponujący pistolet magnum, tak ikoniczny, jak to tylko możliwe. +Potężny i o dużej pojemności, ale odrzut jest trudny do opanowania.]] +L["wep.tacrp_io_degala.desc.quote"] = [["Słyszysz to, Mr. Anderson?"]] + +L["wep.tacrp_m320.name"] = [[HK M320]] +L["wep.tacrp_m320.desc"] = [[Granatnik zdolny do strzelania różnymi pociskami.]] +L["wep.tacrp_m320.desc.quote"] = [[]] + +L["wep.tacrp_h_spas12.name"] = [[Franchi SPAS-12]] +L["wep.tacrp_h_spas12.desc"] = [[Imponujący strzelba bojowa z dwoma trybami działania. Wysoka moc i stabilny odrzut. Składany kolba blokuje użycie optyki.]] +L["wep.tacrp_h_spas12.desc.quote"] = [["Nieprawda."]] + +L["wep.tacrp_knife2.name"] = [[Nóż Jackal]] +L["wep.tacrp_knife2.desc"] = [[Bardzo ostro wyglądający nóż. Lekkie, częściowo szkieletyzowane ostrze sprawia, że jest szybszy do machania, ale zadaje mniej obrażeń.]] +L["wep.tacrp_knife2.desc.quote"] = [[]] + +L["wep.tacrp_io_glock18.name"] = [[Glock 18C]] +L["wep.tacrp_io_glock18.desc"] = [[Pistolet maszynowy o wysokiej szybkostrzelności i mobilności.]] +L["wep.tacrp_io_glock18.desc.quote"] = [["Prędzej czy później, będziesz musiał skoczyć."]] + +L["wep.tacrp_io_fiveseven.name"] = [[FN Five-seveN]] +L["wep.tacrp_io_fiveseven.desc"] = [[Masywny pistolet kal. PDW o doskonałej pojemności. +Pociski o wysokiej prędkości zachowują skuteczność na dystansie i łatwo przebijają pancerz.]] +L["wep.tacrp_io_fiveseven.desc.quote"] = [[Czy to fretka biega wokół?]] + +L["wep.tacrp_amd65.name"] = [[AMD-65]] +L["wep.tacrp_amd65.desc"] = [[Węgierski klon AK z integralnym chwytem i kolbą drutową. Wysokie obrażenia, ale mniejszy zasięg niż większość karabinów.]] +L["wep.tacrp_amd65.desc.quote"] = [[]] + +L["wep.tacrp_io_saiga.name"] = [[Saiga-12K]] +L["wep.tacrp_io_saiga.desc"] = [[Strzelba o dużej pojemności zasilana z magazynka pudełkowego. Ciasny rozrzut i wysoka szybkostrzelność, ale obrażenia są stosunkowo niskie.]] +L["wep.tacrp_io_saiga.desc.quote"] = [[]] + +L["wep.tacrp_h_xm25.name"] = [[HK XM25 CDTE]] +L["wep.tacrp_h_xm25.desc"] = [[Granatnik bullpup z zintegrowanym dalmierzem, dobry do tłumienia na średnim dystansie. Wysoka szybkostrzelność, ale granaty są dość wolne i słabe.]] +L["wep.tacrp_h_xm25.desc.quote"] = [[]] + +L["wep.tacrp_io_m16a2.name"] = [[Colt M16A2]] +L["wep.tacrp_io_m16a2.desc"] = [[Stary karabin szturmowy z ograniczonymi opcjami optyki. Mimo to, jego stabilny odrzut i wysokie obrażenia sprawiają, że jest niezawodny na średnim dystansie.]] +L["wep.tacrp_io_m16a2.desc.quote"] = [["Strzelaj czysto, obserwuj tło."]] + +L["wep.tacrp_hk417.name"] = [[HK HK417]] +L["wep.tacrp_hk417.desc"] = [[Karabin bojowy o doskonałych obrażeniach, szybkostrzelności i precyzji. Zdolny do ognia automatycznego, chociaż jest bardzo niestabilny.]] +L["wep.tacrp_hk417.desc.quote"] = [[]] + +L["wep.tacrp_pa_obrez.name"] = [[Remington 700 Obrez]] +L["wep.tacrp_pa_obrez.desc"] = [[Skrócony karabin myśliwski zbudowany przez osoby pozbawione broni bocznej, aby był zastępnikiem ukrytego pistoletu. Modyfikacja poważnie szkodzi dokładności i wydajności na dystansie.]] +L["wep.tacrp_pa_obrez.desc.quote"] = [["Yippee-yay, nie będzie dzwonków ślubnych na dziś..."]] + +L["wep.tacrp_eo_mp5k.name"] = [[HK MP5K]] +L["wep.tacrp_eo_mp5k.desc"] = [[Kompaktowa wersja ikonicznego SMG. Dobrze zrównoważony, ale zamiast precyzji i kontroli swojego pełnowymiarowego odpowiednika oferuje lepszą obsługę.]] +L["wep.tacrp_eo_mp5k.desc.quote"] = [["Broń. Dużo broni."]] + +L["wep.tacrp_riot_shield.name"] = [[Tarcza Antyterrorystyczna]] +L["wep.tacrp_riot_shield.desc"] = [[Lekka tarcza. Pomimo plastikowo wyglądającego rdzenia, jest w stanie zatrzymać prawie wszystkie pociski kal. karabinowego. +Możliwość sprintu i ataku wręcz bez narażania bezpieczeństwa użytkownika, ale nieco spowalnia prędkość ruchu.]] +L["wep.tacrp_riot_shield.desc.quote"] = [[]] + +L["wep.tacrp_eo_howa.name"] = [[Howa Type 64]] +L["wep.tacrp_eo_howa.desc"] = [[Japoński karabin bojowy o kontrolowanej szybkostrzelności.]] +L["wep.tacrp_eo_howa.desc.quote"] = [[]] + +L["wep.tacrp_dsa58.name"] = [[DSA SA58 OSW]] +L["wep.tacrp_dsa58.desc"] = [[Karabin bojowy o wolnej szybkostrzelności, ale bardzo wysokim uszkodzeniu i penetracji pancerza. Posiada chwyt, który zapewnia pewną stabilność, jeśli jest rozwinięty.]] +L["wep.tacrp_dsa58.desc.quote"] = [["Zamknij się, zegarze i załaduj."]] + +L["wep.tacrp_eo_f2000.name"] = [[FN F2000]] +L["wep.tacrp_eo_f2000.desc"] = [[Futurystyczny karabin szturmowy bullpup o doskonałej ergonomii. +Domyślnie wyposażony w lunetę 1,6x.]] +L["wep.tacrp_eo_f2000.desc.quote"] = [["Czy właśnie powiedziałeś, że muszę wygrać jeden dla Gippera?"]] + +L["wep.tacrp_io_mx4.name"] = [[Beretta MX4 Storm]] +L["wep.tacrp_io_mx4.desc"] = [[Wojskowy karabinek pistoletowy o wysokiej szybkostrzelności. +Przeciętna penetracja pancerza, ale duża rama sprawia, że broń jest dość stabilna.]] +L["wep.tacrp_io_mx4.desc.quote"] = [[]] + +L["wep.tacrp_ak_an94.name"] = [[AN-94]] +L["wep.tacrp_ak_an94.desc"] = [[Eksperymentalny karabin szturmowy z unikalnym "hyperburst" na 2 rundy. Skomplikowany mechanizm karabinu zapewnia niski odrzut, ale jest bardzo masywny.]] +L["wep.tacrp_ak_an94.desc.quote"] = [["Antje"]] + +L["wep.tacrp_ar_gilboa.name"] = [[Gilboa DBR Snake]] +L["wep.tacrp_ar_gilboa.desc"] = [[Unikalny karabinek AR z podwójnym lufą. Dwa razy większa śmiertelność niż jedna lufa, ale konstrukcja jest masywna i nieprecyzyjna. +Z oczywistych powodów nie może przyjmować osprzętu do lufy.]] +L["wep.tacrp_ar_gilboa.desc.quote"] = [[]] + +L["wep.tacrp_as50.name"] = [[AI AS50]] +L["wep.tacrp_as50.desc"] = [[Półautomatyczny karabin przeciwpancerny, który może łatwo zdziesiątkować każdą osobę na dowolnym dystansie. +Domyślnie wyposażony w lunetę 12x. +Zdecydowanie za ciężki do machania, więc tłuczenie jest wykluczone.]] +L["wep.tacrp_as50.desc.quote"] = [[]] + +L["wep.tacrp_eo_93r.name"] = [[Beretta 93 Raffica]] +L["wep.tacrp_eo_93r.desc"] = [[Premiumowy pistolet strzelający serią z niewielkim odrzutem i świetnym strzelaniem z biodra.]] +L["wep.tacrp_eo_93r.desc.quote"] = [["Wiara, pociąg! Weź pociąg!"]] + +L["wep.tacrp_eo_acr.name"] = [[Bushmaster ACR]] +L["wep.tacrp_eo_acr.desc"] = [[Karabin cywilny oferowany jako zaawansowana alternatywa dla innych popularnych platform. Modułowy odbiornik pozwala na użycie różnych kalibrów.]] +L["wep.tacrp_eo_acr.desc.quote"] = [["Zabicie smoka to największy zaszczyt."]] + +L["wep.tacrp_eo_dual_satana.name"] = [[Dueling Demons]] +L["wep.tacrp_eo_dual_satana.desc"] = [[Para spersonalizowanych rewolwerów. Świetna celność dla akimbo, ale wolno strzela i przeładowuje.]] +L["wep.tacrp_eo_dual_satana.desc.quote"] = [[]] + +L["wep.tacrp_eo_izzyfal.name"] = [[DSA SA58 Light-Barrel]] +L["wep.tacrp_eo_izzyfal.desc"] = [[Cywilna wersja ikonicznego karabinu bojowego. Trochę ciężka, ale ma doskonałą moc zatrzymującą i zasięg.]] +L["wep.tacrp_eo_izzyfal.desc.quote"] = [[]] + +L["wep.tacrp_fp6.name"] = [[HK FABARM FP6]] +L["wep.tacrp_fp6.desc"] = [[Strzelba bojowa o wysokiej szybkostrzelności i pojemności.]] +L["wep.tacrp_fp6.desc.quote"] = [[]] + +L["wep.tacrp_eo_mg42.name"] = [[MG 42]] +L["wep.tacrp_eo_mg42.desc"] = [[Antyczny karabin maszynowy o niesamowicie szybkiej szybkostrzelności. Może przeciąć ludzi na pół jak piła łańcuchowa. +Nie zaleca się strzelania z ramienia.]] +L["wep.tacrp_eo_mg42.desc.quote"] = [[]] + +L["wep.tacrp_eo_p7.name"] = [[HK P7]] +L["wep.tacrp_eo_p7.desc"] = [[Kompaktowy pistolet o szybkiej obsłudze, ale słabym zasięgu.]] +L["wep.tacrp_eo_p7.desc.quote"] = [["Kto powiedział, że jesteśmy terrorystami?"]] + +L["wep.tacrp_sd_db.name"] = [[Stoeger Double Defense]] +L["wep.tacrp_sd_db.desc"] = [[Nowoczesna krótka strzelba dwulufowa. Łatwa w obsłudze, niezawodna i śmiertelna w walce wręcz.]] +L["wep.tacrp_sd_db.desc.quote"] = [["Jedyną rzeczą, której się boją... to ty."]] + +L["wep.tacrp_m_hamma.name"] = [[Młotek]] +L["wep.tacrp_m_hamma.desc"] = [[Powszechne narzędzie rzemieślnika do wbijania gwoździ w drewno lub mocowania przedmiotów na miejscu.]] +L["wep.tacrp_m_hamma.desc.quote"] = [[Kiedy masz młotek, wszystko wygląda jak gwóźdź...]] + +L["wep.tacrp_ex_hk45c.name"] = [[HK HK45 Compact]] +L["wep.tacrp_ex_hk45c.desc"] = [[Nowoczesny pistolet o dużej sile ognia w poręcznym opakowaniu.]] +L["wep.tacrp_ex_hk45c.desc.quote"] = [[]] diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_0_convar.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_0_convar.lua new file mode 100644 index 0000000..5013683 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_0_convar.lua @@ -0,0 +1,2139 @@ +// the 0 is for load order!!! + +local conVars = { + { + name = "pickuphint", + default = "1", + client = true + }, + { + name = "drawhud", + default = "1", + client = true + }, + { + name = "minhud", + default = "1", + client = true + }, + { + name = "autoreload", + default = "1", + client = true + }, + { + name = "autosave", + default = "1", + client = true, + userinfo = true, + }, + { + name = "spawnmenu_subcats", + default = "1", + client = true, + }, + { + name = "spawnmenu_highlight", + default = "1", + client = true, + }, + { + name = "spawnmenu_sortbytiers", + default = "1", + client = true, + }, + { + name = "shutup", + default = "0", + client = true, + }, + { + name = "togglepeek", + default = "1", + client = true, + userinfo = true, + }, + { + name = "bodydamagecancel", + default = "1", + replicated = true, + }, + { + name = "free_atts", + default = "1", + replicated = true, + notify = true, + }, + { + name = "lock_atts", + default = "1", + replicated = true, + notify = true, + }, + { + name = "loseattsondie", + default = "1", + }, + { + name = "generateattentities", + default = "1", + replicated = true, + }, + { + name = "npc_equality", + default = "0", + }, + { + name = "npc_atts", + default = "1", + }, + { + name = "penetration", + default = "1", + replicated = true, + notify = true, + }, + { + name = "freeaim", + default = "1", + replicated = true, + notify = true, + }, + { + name = "sway", + default = "1", + replicated = true, + notify = true, + }, + { + name = "physbullet", + default = "1", + replicated = true, + notify = true, + }, + { + name = "resupply_grenades", + default = "1", + }, + { + name = "fixedspread", + default = "1", + notify = true, + }, + { + name = "pelletspread", + default = "1", + notify = true, + }, + { + name = "client_damage", + default = "0", + replicated = true, + notify = true, + }, + { + name = "true_laser", + default = "1", + client = true, + }, + { + name = "toggletactical", + default = "1", + replicated = true, + }, + { + name = "rock_funny", + default = "0.05" + }, + { + name = "arcade", + default = "1", + replicated = true, + }, + { + name = "ammonames", + default = "1", + client = true + }, + { + name = "font1", + default = "", + client = true + }, + { + name = "font2", + default = "", + client = true + }, + { + name = "drawholsters", + default = "1", + client = true, + }, + { + name = "crosshair", + default = "0", + replicated = true, + notify = true, + }, + { + name = "vignette", + default = "1", + client = true, + }, + { + name = "flash_dark", + default = "0", + client = true, + }, + { + name = "flash_slow", + default = "0.4", + min = 0, + max = 1, + replicated = true, + }, + { + name = "melee_slow", + default = "0.4", + min = 0, + max = 1, + replicated = true, + }, + { + name = "metricunit", + default = "0", + client = true, + }, + { + name = "nademenu", + default = "1", + client = true, + userinfo = true, + }, + { + name = "nademenu_click", + default = "1", + client = true, + }, + { + name = "blindfiremenu", + default = "1", + client = true, + userinfo = true, + }, + { + name = "blindfiremenu_nocenter", + default = "0", + client = true, + userinfo = true, + }, + { + name = "gas_sway", + default = "6", + min = 0, + max = 10, + replicated = true, + }, + { + name = "idunwannadie", + default = "0", + client = true, + userinfo = true, + }, + { + name = "aim_cancels_sprint", + default = "1", + client = true, + userinfo = true, + min = 0, + max = 1, + }, + { + name = "holster", + default = "1", + replicated = true, + notify = true, + }, + { + name = "news_majoronly", + default = "0", + client = true, + }, + { + name = "hud", + default = "1", + replicated = true, + notify = true, + }, + { + name = "visibleholster", + default = "1", + replicated = true, + notify = true, + }, + { + name = "checknews", + default = "1", + replicated = true, + }, + { + name = "radar_quiet", + default = "0", + client = true, + }, + { + name = "toggleaim", + default = "0", + client = true, + userinfo = true, + }, + { + name = "toggleholdbreath", + default = "0", + client = true, + userinfo = true, + }, + { + name = "flashlight_blind", + default = "1", + replicated = true, + notify = true, + }, + { + name = "glint", + default = "1", + replicated = true, + notify = true, + }, + { + name = "funny_loudnoises", + default = "1", + min = 0, + max = 2, + replicated = true, + }, + { + name = "balance", + default = "-1", + min = -1, + max = 4, + replicated = true, + notify = true, + callback = function(convar, old, new) + if old != new and SERVER then + TacRP.LoadAtts() + TacRP.InvalidateCache() + net.Start("tacrp_reloadatts") + net.Broadcast() + end + end, + }, + { + name = "sprint_reload", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "sprint_counts_midair", + default = "0", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "sprint_lower", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "reload_sg_cancel", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "armorpenetration", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "nearwall", + default = "1", + client = true, + }, + { + name = "hudscale", + default = "1", + client = true, + }, + { + name = "language", + default = "", + replicated = true, + callback = function() + if SERVER then + TacRP:LoadLanguages() + net.Start("tacrp_reloadlangs") + net.Broadcast() + end + end + }, + { + name = "dev_benchgun", + default = "0", + client = true, + noarchive = true, + }, + { + name = "altrecoil", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "flashlight_alt", + default = "0", + client = true, + }, + // --------------------------- Hints + { + name = "hints", + default = "1", + client = true, + }, + { + name = "hints_always", + default = "0", + client = true, + }, + { + name = "hints_altfont", + default = "0", + client = true, + }, + + // --------------------------- Movement Penalties + + { + name = "penalty_move", + default = "1", + replicated = true, + notify = true, + }, + + { + name = "penalty_firing", + default = "1", + replicated = true, + notify = true, + }, + + { + name = "penalty_aiming", + default = "1", + replicated = true, + notify = true, + }, + + { + name = "penalty_reload", + default = "1", + replicated = true, + notify = true, + }, + + { + name = "penalty_melee", + default = "1", + replicated = true, + notify = true, + }, + + // --------------------------- Ammo + { + name = "defaultammo", + default = "2", + replicated = true, + notify = false, + min = 0, + }, + { + name = "infiniteammo", + default = "0", + replicated = true, + notify = true, + }, + { + name = "infinitelaunchers", + default = "0", + replicated = true, + notify = true, + }, + { + name = "infinitegrenades", + default = "0", + replicated = true, + notify = true, + }, + + // --------------------------- Slots + { + name = "slot_hl2", + default = "0", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "slot_limit", + default = "0", + notify = true, + replicated = true, + min = 0, + }, + { + name = "slot_countall", + default = "0", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "slot_action", + default = "1", + notify = true, + replicated = true, + min = 0, + max = 2, + }, + { + name = "max_grenades", + default = "9999", + min = 0, + }, + + { + name = "hud_ammo_number", + default = "0", + client = true, + min = 0, + max = 1 + }, + + // --------------------------- Irons + { + name = "irons_lower", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 2, + }, + { + name = "irons_procedural", + default = "1", + notify = true, + replicated = true, + min = 0, + max = 2, + }, + + // --------------------------- Attachments + { + name = "att_radartime", + default = "1.5", + replicated = true, + min = 0.5, + }, + + // --------------------------- TTT + { + name = "ttt_weapon_include", + default = "1", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_weapon_replace", + default = "1", // fraction chance + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_atts_random", + default = "0.5", // fraction chance + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_atts_max", + default = "0", // fraction chance + notify = true, + replicated = true, + min = 0, + }, + { + name = "ttt_atts_giveonspawn", + default = "20", + notify = true, + replicated = true, + min = 0, + }, + { + name = "ttt_cust_inno_allow", + default = "1", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_cust_role_allow", + default = "1", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_cust_inno_round", + default = "1", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_cust_role_round", + default = "1", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_cust_inno_needbench", + default = "0", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_cust_role_needbench", + default = "0", + notify = true, + replicated = true, + min = 0, + max = 1, + }, + { + name = "ttt_shortname", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "ttt_magazine_dna", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "ttt_bench_freeatts", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + + // Roleplay + { + name = "rp_requirebench", + default = "0", + replicated = true, + notify = true, + }, + { + name = "rp_biocode_cp", + default = "0", + replicated = true, + notify = true, + }, + + { + name = "laser_beam", + default = "0", + replicated = true, + min = 0, + max = 1, + }, + + { + name = "cust_legacy", + default = "0", + client = true, + min = 0, + max = 1, + }, + { + name = "muzzlelight", + default = "1", + client = true, + min = 0, + max = 1 + }, + { + name = "recoilpattern", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + + { + name = "allowdrop", + default = "1", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + { + name = "oldschool", + default = "0", + replicated = true, + notify = true, + min = 0, + max = 1, + callback = function(convar, old, new) + if tonumber(new) == 1 and SERVER then + TacRP.ConVars["sightsonly"]:SetBool(false) + end + end, + }, + { + name = "sightsonly", + default = "0", + replicated = true, + notify = true, + min = 0, + max = 1, + callback = function(convar, old, new) + if tonumber(new) == 1 and SERVER then + TacRP.ConVars["oldschool"]:SetBool(false) + end + end, + }, + { + name = "deploysafety", + default = "0", + replicated = true, + notify = true, + min = 0, + max = 1, + }, + + + { + name = "cust_drop", + default = "1", + client = true, + min = 0, + max = 1, + }, + { + name = "pickup_use", + default = "1", + client = true, + userinfo = true, + min = 0, + max = 1, + }, + { + name = "phystweak", + default = "1", + min = 0, + max = 1, + replicated = true, + }, + { + name = "doorbust", + default = "1", + min = 0, + max = 1, + replicated = true, + }, + { + name = "doorbust_time", + default = "180", + min = 1, + replicated = true, + }, + { + name = "dynamiclight", + default = "1", + min = 0, + max = 1, + replicated = true, + }, + { + name = "inversepeek", + default = "0", + client = true, + userinfo = true, + min = 0, + max = 1, + }, + + // --------------------------- Multipliers + { + name = "mult_damage", + default = "1", + min = 0.01, + replicated = true, + }, + { + name = "mult_damage_shotgun", + default = "1", + min = 0.01, + replicated = true, + }, + { + name = "mult_damage_sniper", + default = "1", + min = 0.01, + replicated = true, + }, + { + name = "mult_damage_magnum", + default = "1", + min = 0.01, + replicated = true, + }, + { + name = "mult_damage_explosive", + default = "1", + min = 0.01, + replicated = true, + }, + { + name = "mult_recoil_kick", + default = "1", + min = 0, + replicated = true, + }, + { + name = "mult_recoil_vis", + default = "1", + min = 0, + replicated = true, + }, + { + name = "mult_reloadspeed", + default = "1", + min = 0.1, + replicated = true, + }, + { + name = "mult_aimdownsights", + default = "1", + min = 0.1, + replicated = true, + }, + { + name = "mult_sprinttofire", + default = "1", + min = 0.1, + replicated = true, + }, + { + name = "mult_headshot", + default = "1", + min = 0, + replicated = true, + }, + { + name = "mult_damage_melee", + default = "1", + min = 0.01, + replicated = true, + }, + + { + name = "recoilreset", + default = "0", + min = 0, + max = 1, + replicated = true, + }, + { + name = "reload_dump", + default = "0", + min = 0, + max = 1, + replicated = true, + }, + { + name = "ads_reload", + default = "0", + min = 0, + max = 1, + replicated = true + }, + { + name = "can_jam", + default = "1", + min = 0, + max = 1, + replicated = true + }, + { + name = "jam_autoclear", + default = "0", + min = 0, + max = 1, + replicated = true + }, + { + name = "expandedammotypes", + default = "0", + min = 0, + notify = true, + replicated = true, + }, + { + name = "dropmagazinemodel", + default = "1", + min = 0, + max = 1, + notify = true, + replicated = true, + }, + { + name = "quicknade", + default = "1", + replicated = true, + notify = true, + }, + + // --------------------------- Medkit + { + name = "medkit_clipsize", + default = "30", + min = 1, + replicated = true, + }, + { + name = "medkit_regen_activeonly", + default = "0", + min = 0, + max = 1, + replicated = true, + }, + { + name = "medkit_regen_delay", + default = "2", + min = 0, + replicated = true, + }, + { + name = "medkit_regen_amount", + default = "1", + min = 0, + replicated = true, + notify = true, + }, + { + name = "medkit_heal_self", + default = "4", + min = 0, + replicated = true, + notify = true, + }, + { + name = "medkit_heal_others", + default = "4", + min = 0, + replicated = true, + notify = true, + }, + { + name = "medkit_interval", + default = "0.2", + min = 0, + replicated = true, + notify = true, + }, + + // --------------------------- Riot Shield + { + name = "shield_melee", + default = "1", + min = 0, + max = 1, + replicated = true, + }, + { + name = "shield_knockback", + default = "1", + min = 0, + max = 1, + replicated = true, + }, + { + name = "shield_riot_resistance", + default = "3.5", + min = 0, + replicated = true, + }, + { + name = "shield_riot_hp", + default = "0", + min = 0, + replicated = true, + }, + + // --------------------------- Grenades + { + name = "smoke_affectnpcs", + default = "1", + min = 0, + max = 1, + }, + { + name = "flash_affectnpcs", + default = "1", + min = 0, + max = 1, + }, + { + name = "flash_affectplayers", + default = "1", + min = 0, + max = 1, + }, + { + name = "gas_affectnpcs", + default = "1", + min = 0, + max = 1, + }, + { + name = "gas_affectplayers", + default = "1", + min = 0, + max = 1, + }, + { + name = "thermite_damage_min", + default = "20", + min = 0, + }, + { + name = "thermite_damage_max", + default = "40", + min = 0, + }, + { + name = "thermite_radius", + default = "200", + min = 0, + }, + { + name = "frag_damage", + default = "150", + min = 0, + }, + { + name = "frag_radius", + default = "350", + min = 0, + }, + { + name = "charge_damage", + default = "500", + min = 0, + }, + { + name = "charge_radius", + default = "200", + min = 0, + }, + { + name = "c4_damage", + default = "300", + min = 0, + }, + { + name = "c4_radius", + default = "400", + min = 0, + }, + { + name = "healnade_heal", + default = "3", + min = 0, + }, + { + name = "healnade_armor", + default = "1", + min = 0, + }, + { + name = "healnade_damage", + default = "20", + min = 0, + }, + + // Aim Assist + { + name = "aimassist", + default = "0", + replicated = true, + notify = true, + }, + { + name = "aimassist_head", + default = "0", + replicated = true, + notify = true, + }, + { + name = "aimassist_intensity", + default = "1", + replicated = true, + notify = true, + }, + { + name = "aimassist_cone", + default = "5", + replicated = true, + notify = true, + }, + { + name = "aimassist_cl", + default = "1", + client = true, + }, + { + name = "aimassist_multsens", + default = "0.75", + client = true, + }, + { + name = "aimsens", + default = "1", + client = true, + }, +} + +TacRP.ConVars = {} + +local prefix = "tacrp_" + +function TacRP.NetworkConvar(convar, old_value, value) + if IsValid(LocalPlayer()) and !LocalPlayer():IsAdmin() then return end + if old_value == value then return end + if value == true or value == false then + value = value and 1 or 0 + end + if IsColor(value) then + value = tostring(value.r) .. " " .. tostring(value.g) .. " " .. tostring(value.b) .. " " .. tostring(value.a) + end + + local command = convar .. " " .. tostring(value) + + local timername = "change" .. convar + + if timer.Exists(timername) then + timer.Remove(timername) + end + + timer.Create(timername, 0.25, 1, function() + net.Start("tacrp_sendconvar") + net.WriteString(command) + net.SendToServer() + end) +end + +local flags = { + ["replicated"] = FCVAR_REPLICATED, + ["userinfo"] = FCVAR_USERINFO, + ["notify"] = FCVAR_NOTIFY +} +for _, var in pairs(conVars) do + local convar_name = prefix .. var.name + + if var.client and CLIENT then + TacRP.ConVars[var.name] = CreateClientConVar(convar_name, var.default, !var.noarchive, var.userinfo) + elseif !var.client then + local flag = FCVAR_ARCHIVE + for k, v in pairs(flags) do if var[k] then flag = flag + v end end + TacRP.ConVars[var.name] = CreateConVar(convar_name, var.default, flag, var.help, var.min, var.max) + end + + if var.callback then + cvars.AddChangeCallback(convar_name, var.callback, "tacrp") + end + + if CLIENT then + cvars.AddChangeCallback(convar_name, TacRP.NetworkConvar, "tacrp_onchange") + end +end + +if CLIENT then + +local function reset_cvars() + for _, cvar in pairs(TacRP.ConVars) do + if bit.band(cvar:GetFlags(), FCVAR_LUA_CLIENT) != 0 then + cvar:Revert() + end + end +end + +local function header(panel, text) + local ctrl = panel:Help(text) + ctrl:SetFont("DermaDefaultBold") + return ctrl +end + +local function menu_client_ti(panel) + + local btn_reset = vgui.Create("DButton") + btn_reset:Dock(TOP) + btn_reset:SetText("#tacrp.client.default") -- Apply Default Client Settings + function btn_reset.DoClick(self) + Derma_Query( + "#tacrp.client.default.warning", + "TacRP", + "#openurl.yes", -- Yes + function() + reset_cvars() + end, + "#openurl.nope" -- Nope + ) + end + panel:AddPanel(btn_reset) + + header(panel, "#tacrp.client.interface") -- Interface + panel:AddControl("checkbox", { + label = "#tacrp.client.hud", -- Show HUD + command = "TacRP_drawhud" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.backuphud", -- Show Backup HUD + command = "tacrp_minhud" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.quickthrowradial", -- Quickthrow Radial Menu + command = "tacrp_nademenu" + }) + panel:ControlHelp("#tacrp.client.quickthrowradial.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.quickthrowclick", -- Quickthrow Menu Clicking + command = "tacrp_nademenu_click" + }) + panel:ControlHelp("#tacrp.client.quickthrowclick.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.blindfireradial", -- Blindfire Radial Menu + command = "tacrp_blindfiremenu" + }) + panel:ControlHelp("#tacrp.client.blindfireradial.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.blindfireemptymid", -- Blindfire Menu Empty Center + command = "tacrp_blindfiremenu_nocenter" + }) + panel:ControlHelp("#tacrp.client.blindfireemptymid.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.usemeters", -- Use Meters instead of Hammer Units + command = "tacrp_metricunit" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.recoilvignette", -- Recoil Vignette + command = "tacrp_vignette" + }) + panel:ControlHelp("#tacrp.client.recoilvignette.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.dropweaponbutton", -- Show "Drop Weapon" Button + command = "tacrp_cust_drop" + }) + panel:AddControl("slider", { + label = "#tacrp.client.hudscale", -- HUD Scale + command = "tacrp_hudscale", + type = "float", + min = 0.25, + max = 1.5, + }) + panel:ControlHelp("#tacrp.client.hudscale.desc") + + header(panel, "#tacrp.client.hints") -- \nHints + panel:AddControl("checkbox", { + label = "#tacrp.client.controlhints", -- Display Control Hints + command = "tacrp_hints" + }) + panel:ControlHelp("#tacrp.client.controlhints.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.hintsalways", -- Always Display Hints + command = "tacrp_hints_always" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.hintsaltfont", -- Hints Use Alternate Font + command = "tacrp_hints_altfont" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.hintsquickthrowremind", -- Hide Startup Quickthrow Bind Reminder + command = "tacrp_shutup" + }) + + header(panel, "#tacrp.client.preference") -- \nPreference + panel:AddControl("checkbox", { + label = "#tacrp.client.toggleaim", -- Toggle Aiming + command = "tacrp_toggleaim" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.togglepeek", -- Toggle Peeking + command = "tacrp_togglepeek" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.inversepeek", -- Invert Peeking + command = "tacrp_inversepeek" + }) + panel:ControlHelp("#tacrp.client.inversepeek.desc") + + panel:AddControl("slider", { + label = "#tacrp.client.aimsens", -- Aiming Sensitivity + command = "tacrp_aimsens", + type = "float", + min = 0.1, + max = 1, + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.aimstopssprint", -- Aiming Stops Sprinting + command = "tacrp_aim_cancels_sprint" + }) + panel:ControlHelp("#tacrp.client.aimstopssprint.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.autosave", -- Auto-Save Weapon + command = "TacRP_autosave" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.autoreload", -- Auto Reload When Empty + command = "TacRP_autoreload" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.flashdark", -- Flashbang Dark Mode + command = "tacrp_flash_dark" + }) + panel:ControlHelp("#tacrp.client.flashdark.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.quietradar", -- Quiet Radar + command = "tacrp_radar_quiet" + }) + panel:ControlHelp("#tacrp.client.quietradar.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.pickupuse", -- Pickup Requires +USE + command = "tacrp_pickup_use" + }) + panel:ControlHelp("#tacrp.client.pickupuse.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.toggletacticalwalk", -- Toggle Tactical with +WALK + command = "tacrp_flashlight_alt" + }) + panel:ControlHelp("#tacrp.client.toggletacticalwalk.desc") + + panel:AddControl("checkbox", { + label = "#tacrp.client.aimassist", -- Enable Aim Assist + command = "tacrp_aimassist_cl" + }) + panel:ControlHelp("#tacrp.client.aimassist.desc") + + header(panel, "#tacrp.client.spawnmenu") -- Spawnmenu + panel:ControlHelp("#tacrp.client.spawnmenu.desc") + + panel:AddControl("checkbox", { + label = "#tacrp.client.subcategory", -- Subcategory Headers + command = "tacrp_spawnmenu_subcats" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.sortbytier", -- Sort by Tiers + command = "tacrp_spawnmenu_sortbytiers" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.tierhighlight", -- Tier Highlights + command = "tacrp_spawnmenu_highlight" + }) + panel:ControlHelp("#tacrp.client.tierhighlight.desc") + + header(panel, "#tacrp.mechanics.misc") -- Misc. + panel:AddControl("checkbox", { + label = "#tacrp.client.muzzlelight", --Muzzle Light + command = "tacrp_muzzlelight" + }) + panel:ControlHelp("#tacrp.client.muzzlelight.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.nearwall", -- Near Walling + command = "tacrp_nearwall" + }) + panel:ControlHelp("#tacrp.client.nearwall.desc") + + panel:AddControl("checkbox", { + label = "#tacrp.client.disablesuicide", -- Disable Suicide Mode + command = "tacrp_idunwannadie" + }) + panel:ControlHelp("#tacrp.client.disablesuicide.desc") + panel:AddControl("checkbox", { + label = "#tacrp.client.drawholstered", -- Draw Holstered Weapons + command = "tacrp_drawholsters" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.truelaser", -- True Laser Position + command = "tacrp_true_laser" + }) + panel:AddControl("checkbox", { + label = "#tacrp.client.immersivenames", -- Immersive Ammo Names + command = "tacrp_ammonames" + }) + panel:ControlHelp("#tacrp.client.immersivenames.desc") +end + +local function menu_server_ti(panel) + header(panel, "#tacrp.server.features") -- Features + panel:Help("#tacrp.server.features.desc") + panel:AddControl("checkbox", { + label = "#tacrp.server.enablecrosshair", -- Enable Crosshair + command = "tacrp_crosshair" + }) + panel:AddControl("checkbox", { + label = "#tacrp.server.hud", -- Allow HUD + command = "tacrp_hud" + }) + panel:AddControl("checkbox", { + label = "#tacrp.server.holsteredmodels", -- Holstered Weapon Models + command = "tacrp_visibleholster" + }) + panel:AddControl("checkbox", { + label = "#tacrp.server.newsletter", -- Enable Newsletter + command = "tacrp_checknews" + }) + panel:ControlHelp("#tacrp.server.newsletter.desc") + panel:AddControl("checkbox", { + label = "#tacrp.server.allowdrop", -- Allow Dropping & Swapping Weapons + command = "tacrp_allowdrop" + }) + panel:AddControl("checkbox", { + label = "#tacrp.server.safeondeploy", -- Enable Safety On Deploy + command = "tacrp_deploysafety" + }) + + local cb_irons_procedural, lb_irons_procedural = panel:ComboBox("#tacrp.server.proceduralirons", "tacrp_irons_procedural") -- Use Procedural Iron Sights + cb_irons_procedural:AddChoice("#tacrp.server.proceduralirons.0", "0") -- 0 - Never + cb_irons_procedural:AddChoice("#tacrp.server.proceduralirons.1", "1") -- 1 - With Optic + cb_irons_procedural:AddChoice("#tacrp.server.proceduralirons.2", "2") -- 2 - Always + cb_irons_procedural:DockMargin(8, 0, 0, 0) + lb_irons_procedural:SizeToContents() + panel:ControlHelp("#tacrp.server.proceduralirons.desc") + + local cb_irons_lower, lb_irons_lower = panel:ComboBox("#tacrp.server.loweredirons", "tacrp_irons_lower") -- Use Lowered Iron Sights + cb_irons_lower:AddChoice("#tacrp.server.loweredirons.0", "0") -- 0 - Never + cb_irons_lower:AddChoice("#tacrp.server.loweredirons.1", "1") -- 1 - Only in TTT + cb_irons_lower:AddChoice("#tacrp.server.loweredirons.2", "2") -- 2 - Always + cb_irons_lower:DockMargin(8, 0, 0, 0) + lb_irons_lower:SizeToContents() + panel:ControlHelp("#tacrp.server.loweredirons.desc") + + panel:AddControl("checkbox", { + label = "#tacrp.server.aimassist", -- Allow Aim Assist + command = "tacrp_aimassist" + }) + panel:ControlHelp("#tacrp.server.aimassist.desc") + + panel:AddControl("slider", { + label = "#tacrp.server.aimassistintensity", -- Aim Assist Intensity + command = "tacrp_aimassist_intensity", + type = "float", + min = 0, + max = 2, + }) + panel:ControlHelp("#tacrp.server.aimassistintensity.desc") + + panel:AddControl("slider", { + label = "#tacrp.server.aimassistcone", -- Aim Assist Cone + command = "tacrp_aimassist_cone", + type = "int", + min = 0, + max = 10, + }) + panel:ControlHelp("#tacrp.server.aimassistcone.desc") + + header(panel, "#tacrp.server.slotrestrict") -- Weapon Slot Restriction + panel:ControlHelp("#tacrp.server.slotrestrict.desc") + panel:AddControl("slider", { + label = "#tacrp.server.maxperslot", -- Max per Slot + command = "tacrp_slot_limit", + type = "int", + min = 0, + max = 3, + }) + panel:ControlHelp("#tacrp.server.maxperslot.desc") + panel:AddControl("checkbox", { + label = "#tacrp.server.hl2slots", -- Use HL2-style Slots + command = "tacrp_slot_hl2" + }) + panel:ControlHelp("#tacrp.server.hl2slots.desc") + panel:AddControl("checkbox", { + label = "#tacrp.server.countall", -- Count ALL Weapons + command = "tacrp_slot_countall" + }) + panel:ControlHelp("#tacrp.server.countall.desc") + + local cb_slot_action, lb_slot_action = panel:ComboBox("#tacrp.server.spawnbehavior", "tacrp_slot_action") -- Weapon Spawning Behavior + cb_slot_action:AddChoice("#tacrp.server.spawnbehavior.0", "0") -- 0 - Ignore + cb_slot_action:AddChoice("#tacrp.server.spawnbehavior.1", "1") -- 1 - Remove + cb_slot_action:AddChoice("#tacrp.server.spawnbehavior.2", "2") -- 2 - Drop + cb_slot_action:DockMargin(8, 0, 0, 0) + lb_slot_action:SizeToContents() + + panel:ControlHelp("#tacrp.server.spawnbehavior.desc") + + header(panel, "#tacrp.server.npc") -- \nNPC + panel:AddControl("checkbox", { + label = "#tacrp.server.npcequaldmg", -- NPC's Deal Equal Damage + command = "TacRP_npc_equality" + }) + panel:AddControl("checkbox", { + label = "#tacrp.server.npcrandomatt", -- NPC's Get Random Attachments + command = "TacRP_npc_atts" + }) + + + header(panel, "#tacrp.mechanics.misc") -- \nMiscellaneous + panel:AddControl("checkbox", { + label = "#tacrp.server.clienthitreg", -- Client Authoratitive Hitreg + command = "tacrp_client_damage" + }) + panel:ControlHelp("#tacrp.server.clienthitreg.desc") + panel:AddControl("checkbox", { + label = "#tacrp.server.dropmag", -- Drop Magazine Models + command = "tacrp_dropmagazinemodel" + }) + panel:AddControl("checkbox", { + label = "#tacrp.server.supplyboxgrenades", -- Supply Boxes Resupply Grenades + command = "TacRP_resupply_grenades" + }) + panel:AddControl("checkbox", { + label = "#tacrp.server.canceldefaultbodydmg", -- Default Body Damage Cancel + command = "TacRP_bodydamagecancel" + }) + panel:ControlHelp("#tacrp.server.canceldefaultbodydmg.desc") + panel:AddControl("checkbox", { + label = "#tacrp.server.doorbust", -- Enable Door Busting + command = "tacrp_doorbust" + }) + panel:ControlHelp("#tacrp.server.doorbust.desc") + panel:AddControl("slider", { + label = "#tacrp.server.doorrespawntime", -- Door Respawn Time + command = "tacrp_doorbust_time", + type = "int", + min = 10, + max = 600, + }) + panel:ControlHelp("#tacrp.server.doorrespawntime.desc") + panel:AddControl("checkbox", { + label = "#tacrp.server.dynamiclight", -- Enable Dynamic Lights + command = "tacrp_dynamiclight" + }) + panel:ControlHelp("#tacrp.server.dynamiclight.desc") +end + +local function menu_balance_ti(panel) + header(panel, "#tacrp.balance.damage") -- Damage + panel:Help("#tacrp.balance.damage.desc") + local cb_balance, lb_balance = panel:ComboBox("#tacrp.balance.weapontier", "tacrp_balance") + cb_balance:AddChoice("#tacrp.balance.weapontier.3", "-1") -- [ Automatic ] + cb_balance:AddChoice("#tacrp.balance.weapontier.0", "0") -- 0 - Tiered + cb_balance:AddChoice("#tacrp.balance.weapontier.1", "1") -- 1 - Untiered + cb_balance:AddChoice("#tacrp.balance.weapontier.2", "2") -- 2 - TTT + cb_balance:DockMargin(8, 0, 0, 0) + lb_balance:SizeToContents() + panel:Help("#tacrp.balance.weapontier.desc") + panel:Help("#tacrp.balance.weapontier.desc2") + panel:Help("#tacrp.balance.weapontier.desc3") + + panel:AddControl("slider", { + label = "#tacrp.balance.dmg", -- Overall Damage + command = "tacrp_mult_damage", + type = "float", + min = 0.1, + max = 3, + }) + panel:ControlHelp("#tacrp.balance.dmg.desc") + panel:AddControl("slider", { + label = "#tacrp.balance.shotgundmg", -- Shotgun Damage + command = "tacrp_mult_damage_shotgun", + type = "float", + min = 0.1, + max = 3, + }) + panel:AddControl("slider", { + label = "#tacrp.balance.sniperdmg", -- Sniper Rifle Damage + command = "tacrp_mult_damage_sniper", + type = "float", + min = 0.1, + max = 3, + }) + panel:AddControl("slider", { + label = "#tacrp.balance.magnumdmg", -- Magnum Pistol Damage + command = "tacrp_mult_damage_magnum", + type = "float", + min = 0.1, + max = 3, + }) + panel:AddControl("slider", { + label = "#tacrp.balance.explosivedmg", -- Explosive Damage + command = "tacrp_mult_damage_explosive", + type = "float", + min = 0.1, + max = 10, + }) + panel:ControlHelp("#tacrp.balance.explosivedmg.desc") + panel:AddControl("slider", { + label = "#tacrp.balance.meleewpndmg", -- Melee Weapon Damage + command = "tacrp_mult_damage_melee", + type = "float", + min = 0.1, + max = 3, + }) + panel:ControlHelp("#tacrp.balance.meleewpndmg.desc") + panel:AddControl("slider", { + label = "#tacrp.balance.headshotmult", -- Headshot Multiplier + command = "tacrp_mult_headshot", + type = "float", + min = 0, + max = 2, + }) + panel:ControlHelp("#tacrp.balance.headshotmult.desc") + + + header(panel, "#tacrp.balance.recoil") -- \nRecoil + panel:AddControl("checkbox", { + label = "#tacrp.balance.bloommodrecoil", -- Bloom Modifies Recoil + command = "tacrp_altrecoil" + }) + panel:ControlHelp("#tacrp.balance.bloommodrecoil.desc") + panel:AddControl("checkbox", { + label = "#tacrp.balance.recoilpattern", -- Recoil Patterns + command = "tacrp_recoilpattern" + }) + panel:ControlHelp("#tacrp.balance.recoilpattern.desc") + panel:AddControl("slider", { + label = "#tacrp.balance.recoilkick", -- Recoil Kick + command = "tacrp_mult_recoil_kick", + type = "float", + min = 0, + max = 2, + }) + panel:AddControl("slider", { + label = "#tacrp.balance.visualrecoil", -- Visual Recoil + command = "tacrp_mult_recoil_vis", + type = "float", + min = 0, + max = 2, + }) + + header(panel, "#tacrp.balance.aiming") -- \nAiming + panel:AddControl("checkbox", { + label = "#tacrp.balance.crosshair", -- Enable Crosshair + command = "tacrp_crosshair" + }) + panel:AddControl("checkbox", { + label = "#tacrp.balance.oldschoolscopes", -- Enable Old-School Scopes + command = "tacrp_oldschool" + }) + panel:ControlHelp("#tacrp.balance.oldschoolscopes.desc") + panel:AddControl("checkbox", { + label = "#tacrp.balance.sway", -- Enable Sway + command = "tacrp_sway" + }) + panel:ControlHelp("#tacrp.balance.sway.desc") + panel:AddControl("checkbox", { + label = "#tacrp.balance.freeaim", -- Enable Free Aim + command = "tacrp_freeaim" + }) + panel:ControlHelp("#tacrp.balance.freeaim.desc") + panel:AddControl("slider", { + label = "#tacrp.balance.adstime", -- Aim Down Sights Time Multiplier + command = "tacrp_mult_aimdownsights", + type = "float", + min = 0.5, + max = 1.5, + }) + panel:AddControl("slider", { + label = "#tacrp.balance.sprinttofiretime", -- Sprint-to-Fire Time Multiplier + command = "tacrp_mult_sprinttofire", + type = "float", + min = 0.5, + max = 1.5, + }) + + + header(panel, "#tacrp.balance.ammoreload") -- \nAmmo & Reloading + panel:AddControl("checkbox", { + label = "#tacrp.balance.infammo", -- Infinite Ammo + command = "tacrp_infiniteammo" + }) + panel:ControlHelp("#tacrp.balance.infammo.desc") + panel:AddControl("checkbox", { + label = "#tacrp.balance.inflaunchers", -- Infinite Ammo + command = "tacrp_infinitelaunchers" + }) + panel:ControlHelp("#tacrp.balance.inflaunchers.desc") + panel:AddControl("checkbox", { + label = "#tacrp.balance.infgren", -- Infinite Grenades + command = "tacrp_infinitegrenades" + }) + panel:AddControl("checkbox", { + label = "#tacrp.balance.dumpammo", -- Dump Ammo In Magazines + command = "tacrp_reload_dump" + }) + panel:ControlHelp("#tacrp.balance.dumpammo.desc") + panel:AddControl("checkbox", { + label = "#tacrp.balance.canjam", -- Automatically Clear Jams + command = "tacrp_can_jam" + }) + panel:AddControl("checkbox", { + label = "#tacrp.balance.autoclearjam", -- Automatically Clear Jams + command = "tacrp_jam_autoclear" + }) + panel:ControlHelp("#tacrp.balance.autoclearjam.desc") + panel:AddControl("checkbox", { + label = "#tacrp.balance.expandammotype", -- Expanded Ammo Types + command = "tacrp_expandedammotypes" + }) + panel:ControlHelp("#tacrp.balance.expandammotype.desc") + panel:AddControl("slider", { + label = "#tacrp.balance.defaultclipmult", -- Default Clip Multiplier + command = "tacrp_defaultammo", + type = "float", + min = 0, + max = 10, + }) + panel:AddControl("slider", { + label = "#tacrp.balance.reloadspeed", -- Reload Speed Multiplier + command = "tacrp_mult_reloadspeed", + type = "float", + min = 0.5, + max = 1.5, + }) + panel:ControlHelp("\n") +end + +local function menu_mechanics_ti(panel) + header(panel, "#tacrp.mechanics.ballistics") -- Ballistics + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.penetration", -- Enable Bullet Penetration + command = "TacRP_penetration" + }) + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.physbullets", -- Enable Physical Bullets + command = "TacRP_physbullet" + }) + panel:ControlHelp("#tacrp.mechanics.physbullets.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.shotgunpattern", -- Enable Shotgun Patterns + command = "tacrp_fixedspread" + }) + panel:ControlHelp("#tacrp.mechanics.shotgunpattern.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.randompattern", -- Enable Pattern Randomness + command = "tacrp_pelletspread" + }) + panel:ControlHelp("#tacrp.mechanics.randompattern.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.customarmorpen", -- Custom Armor Penetration + command = "tacrp_armorpenetration" + }) + panel:ControlHelp("#tacrp.mechanics.customarmorpen.desc") + + header(panel, "#tacrp.mechanics.movement") -- \nMovement + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.reloadsprint", -- Allow Reload while Sprinting + command = "tacrp_sprint_reload" + }) + panel:ControlHelp("#tacrp.mechanics.reloadsprint.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.lowersprint", -- Lower Weapon While Sprinting + command = "tacrp_sprint_lower"}) + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.lowerair", -- Lower Weapon While Airborne + command = "tacrp_sprint_counts_midair"}) + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.lowerhipfire", -- Lower Weapon While Not Aiming + command = "tacrp_sightsonly" + }) + panel:ControlHelp("#tacrp.mechanics.lowerhipfire.desc") + + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.penaltymove", -- Movement Penalty + command = "tacrp_penalty_move" + }) + panel:ControlHelp("#tacrp.mechanics.penaltymove.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.penaltyfire", -- Firing Movement Penalty + command = "tacrp_penalty_firing" + }) + panel:ControlHelp("#tacrp.mechanics.penaltyfire.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.penaltyaim", -- Aiming Movement Penalty + command = "tacrp_penalty_aiming" + }) + panel:ControlHelp("#tacrp.mechanics.penaltyaim.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.penaltyreload", -- Reload Movement Penalty + command = "tacrp_penalty_reload" + }) + panel:ControlHelp("#tacrp.mechanics.penaltyreload.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.penaltymelee", -- Melee Movement Penalty + command = "tacrp_penalty_melee" + }) + panel:ControlHelp("#tacrp.mechanics.penaltymelee.desc") + + header(panel, "#tacrp.mechanics.misc") -- \nMiscellaneous + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.delayholster", -- Delayed Holstering + command = "tacrp_holster" + }) + panel:ControlHelp("#tacrp.mechanics.delayholster.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.shotgunreloadcancel", -- Shotgun Reload Cancel + command = "tacrp_reload_sg_cancel" + }) + panel:ControlHelp("#tacrp.mechanics.shotgunreloadcancel.desc") + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.reloadaim", -- Allow Aiming While Reloading + command = "tacrp_ads_reload" + }) + panel:AddControl("checkbox", { + label = "#tacrp.mechanics.quicknade", -- Enable Quick Grenades + command = "tacrp_quicknade" + }) +end + +local function menu_atts_ti(panel) + header(panel, "#tacrp.attachments.inventory") -- Attachment Inventory + panel:AddControl("checkbox", { + label = "#tacrp.attachments.free", -- Free Attachments + command = "TacRP_free_atts" + }) + panel:AddControl("checkbox", { + label = "#tacrp.attachments.locking", -- Attachment Locking + command = "TacRP_lock_atts" + }) + panel:ControlHelp("#tacrp.attachments.locking.desc") + panel:AddControl("checkbox", { + label = "#tacrp.attachments.ondeath", -- Lose Attachments on Death + command = "TacRP_loseattsondie" + }) + panel:AddControl("checkbox", { + label = "#tacrp.attachments.spawnmenu", -- Attachment Entities in Spawnmenu + command = "TacRP_generateattentities" + }) + + header(panel, "#tacrp.attachments.mechanics") -- \nAttachment Mechanics + panel:AddControl("checkbox", { + label = "#tacrp.attachments.scopeglint", -- Enable Scope Glint + command = "tacrp_glint" + }) + panel:ControlHelp("#tacrp.attachments.scopeglint.desc") + panel:AddControl("checkbox", { + label = "#tacrp.attachments.blindflashlight", -- Enable Blinding Flashlight + command = "tacrp_flashlight_blind" + }) + panel:ControlHelp("#tacrp.attachments.blindflashlight.desc") + panel:AddControl("checkbox", { + label = "#tacrp.attachments.laserbeam", -- Laser beam + command = "tacrp_laser_beam" + }) + panel:ControlHelp("#tacrp.attachments.laserbeam.desc") + + header(panel, "#tacrp.attachments.balance") -- \nAttachment Balance + panel:AddControl("slider", { + label = "#tacrp.attachments.smackdown", -- Smackdown Slow + command = "tacrp_melee_slow", + type = "float", + min = 0, + max = 1, + }) + panel:AddControl("slider", { + label = "#tacrp.attachments.radarfreq", -- Radar Frequency + command = "tacrp_att_radartime", + type = "float", + min = 0.5, + max = 10, + }) +end + + +local function menu_equipment_ti(panel) + header(panel, "#tacrp.equipment.grenades") -- Grenades + panel:AddControl("checkbox", { + label = "#tacrp.equipment.smokenpcs", -- Smoke affects NPCs + command = "tacrp_smoke_affectnpcs" + }) + panel:AddControl("checkbox", { + label = "#tacrp.equipment.flashnpcs", -- Flashbangs affect NPCs + command = "tacrp_flash_affectnpcs" + }) + panel:AddControl("checkbox", { + label = "#tacrp.equipment.flashplayers", -- Flashbangs affect Players + command = "tacrp_flash_affectplayers" + }) + panel:AddControl("checkbox", { + label = "#tacrp.equipment.gasnpcs", -- CS Gas affect NPCs + command = "tacrp_gas_affectnpcs" + }) + panel:AddControl("checkbox", { + label = "#tacrp.equipment.gasplayers", -- CS Gas affect Players + command = "tacrp_gas_affectplayers" + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.flashslow", -- Flashbang Slow + command = "tacrp_flash_slow", + type = "float", + min = 0, + max = 1, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.fragdmg", -- Frag Grenade Damage + command = "tacrp_frag_damage", + type = "int", + min = 50, + max = 500, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.fragradius", -- Frag Grenade Radius + command = "tacrp_frag_radius", + type = "int", + min = 64, + max = 512, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.thermitedmg", -- Thermite Starting Damage + command = "tacrp_thermite_damage_min", + type = "int", + min = 1, + max = 100, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.thermitedmgmax", -- Thermite Maximum Damage + command = "tacrp_thermite_damage_max", + type = "int", + min = 1, + max = 100, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.thermiteradius", -- Thermite Radius + command = "tacrp_thermite_radius", + type = "int", + min = 64, + max = 512, + }) + panel:ControlHelp("#tacrp.equipment.thermiteradius.desc") + panel:AddControl("slider", { + label = "#tacrp.equipment.doorchargedmg", -- Door Charge Damage + command = "tacrp_charge_damage", + type = "int", + min = 100, + max = 1000, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.doorchargeradius", -- Door Charge Radius + command = "tacrp_charge_radius", + type = "int", + min = 64, + max = 512, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.c4dmg", -- C4 Damage + command = "tacrp_c4_damage", + type = "int", + min = 100, + max = 1000, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.c4radius", -- C4 Radius + command = "tacrp_c4_radius", + type = "int", + min = 64, + max = 512, + }) + panel:ControlHelp("#tacrp.equipment.c4radius.desc") + panel:AddControl("slider", { + label = "#tacrp.equipment.medismokehealth", -- Medi-Smoke Health + command = "tacrp_healnade_heal", + type = "int", + min = 0, + max = 20, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.medismokearmor", -- Medi-Smoke Armor + command = "tacrp_healnade_heal", + type = "int", + min = 0, + max = 20, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.medismokedmg", -- Medi-Smoke Damage + command = "tacrp_healnade_damage", + type = "int", + min = 0, + max = 100, + }) + panel:ControlHelp("#tacrp.equipment.medismokedmg.desc") + panel:AddControl("slider", { + label = "#tacrp.equipment.csgassway", -- CS Gas Sway + command = "tacrp_gas_sway", + type = "float", + min = 0, + max = 10, + }) + + header(panel, "#tacrp.equipment.medkit") -- \nMedkit + panel:AddControl("checkbox", { + label = "#tacrp.equipment.onlywhenheld", -- Only Regen Charge When Held + command = "tacrp_medkit_regen_activeonly" + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.maxcharge", -- Max Charge + command = "tacrp_medkit_clipsize", + type = "int", + min = 10, + max = 100, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.chargeregentime", -- Charge Regen Interval + command = "tacrp_medkit_regen_delay", + type = "float", + min = 0.01, + max = 5, + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.chargeregennr", -- Charge Regen Amount + command = "tacrp_medkit_regen_amount", + type = "int", + min = 0, + max = 3, + }) + panel:ControlHelp("#tacrp.equipment.chargeregennr.desc") + + panel:AddControl("slider", { + label = "#tacrp.equipment.selfhealpercharge", -- Self Heal Per Charge + command = "tacrp_medkit_heal_self", + type = "int", + min = 0, + max = 10, + }) + panel:ControlHelp("#tacrp.equipment.selfhealpercharge.desc") + panel:AddControl("slider", { + label = "#tacrp.equipment.healpercharge", -- Heal Per Charge + command = "tacrp_medkit_heal_others", + type = "int", + min = 0, + max = 10, + }) + panel:ControlHelp("#tacrp.equipment.healpercharge.desc") + panel:AddControl("slider", { + label = "#tacrp.equipment.chargetime", -- Charge Interval + command = "tacrp_medkit_interval", + type = "float", + min = 0.01, + max = 1, + }) + panel:ControlHelp("#tacrp.equipment.chargetime.desc") + + header(panel, "#tacrp.equipment.riotshield") -- \nRiot Shield + panel:AddControl("checkbox", { + label = "#tacrp.equipment.allowquickmelee", -- Allow Quick Melee + command = "tacrp_shield_melee" + }) + panel:AddControl("checkbox", { + label = "#tacrp.equipment.knockbackonblock", -- Knockback On Melee Block + command = "tacrp_shield_knockback" + }) + panel:AddControl("slider", { + label = "#tacrp.equipment.penetrationresist", -- Penetration Resistance + command = "tacrp_shield_riot_resistance", + type = "float", + min = 0, + max = 5, + }) + panel:ControlHelp("#tacrp.equipment.penetrationresist.desc") + panel:AddControl("slider", { + label = "#tacrp.equipment.durability", -- Durability + command = "tacrp_shield_riot_hp", + type = "int", + min = 0, + max = 9999, + }) + panel:ControlHelp("#tacrp.equipment.durability.desc") + +end + +local clientmenus_ti = { + { + text = "#tacrp.settings.client", func = menu_client_ti + }, + { + text = "#tacrp.settings.server", func = menu_server_ti + }, + { + text = "#tacrp.settings.mechanics", func = menu_mechanics_ti + }, + { + text = "#tacrp.settings.attachments", func = menu_atts_ti + }, + { + text = "#tacrp.settings.balance", func = menu_balance_ti + }, + { + text = "#tacrp.settings.equipment", func = menu_equipment_ti + }, +} + +hook.Add("PopulateToolMenu", "TacRP_MenuOptions", function() + for smenu, data in pairs(clientmenus_ti) do + spawnmenu.AddToolMenuOption("Options", "Tactical RP Weapons", "TacRP_" .. tostring(smenu), data.text, "", "", data.func) + end +end) + +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_0_i18n.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_0_i18n.lua new file mode 100644 index 0000000..78c7e69 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_0_i18n.lua @@ -0,0 +1,149 @@ +TacRP.PhraseTable = TacRP.PhraseTable or {} +TacRP.STPTable = TacRP.STPTable or {} + +local lang_cvar = TacRP.ConVars["language"] +local gmod_language = GetConVar("gmod_language") + +function TacRP:GetLanguage() + if lang_cvar:GetString() ~= "" then + return string.lower(lang_cvar:GetString()) + end + local l = gmod_language:GetString() + return string.lower(l) +end + +function TacRP:AddPhrase(phrase, str, lang) + if phrase == nil or phrase == "" or str == nil or str == "" then return nil end + lang = lang and string.lower(lang) or "en" + TacRP.PhraseTable[lang] = TacRP.PhraseTable[lang] or {} + TacRP.PhraseTable[lang][string.lower(phrase)] = str +end + +--[[ + Add a "String to Phrase", converting a string to a phrase (i.e. "Assault Rifle" to "class.assaultrifle"). +]] +function TacRP:AddSTP(phrase, str, lang) + if phrase == nil or phrase == "" or str == nil or str == "" then return nil end + TacRP.STPTable[string.lower(str)] = phrase +end + +function TacRP:GetPhrase(phrase, format) + if phrase == nil or phrase == "" then return nil end + phrase = string.lower(phrase) + local lang = TacRP:GetLanguage() + if !lang or !TacRP.PhraseTable[lang] or !TacRP.PhraseTable[lang][phrase] then + lang = "en" + end + if TacRP.PhraseTable[lang] and TacRP.PhraseTable[lang][phrase] then + local str = TacRP.PhraseTable[lang][phrase] + for i, v in pairs(format or {}) do + str = string.Replace(str, "{" .. i .. "}", v) + end + return str + end + return nil +end + +function TacRP:TryTranslate(str) + if str == nil then return str end + if TacRP.STPTable[string.lower(str)] then + return TacRP:GetPhrase(TacRP.STPTable[string.lower(str)]) + end + return TacRP:GetPhrase(str) or str +end + +function TacRP:GetAttName(att, full) + local atttbl = TacRP.GetAttTable(att) + if atttbl == {} then return "INVALID ATT" end + if full then + return TacRP:GetPhrase("att." .. att .. ".name.full") + or TacRP:GetPhrase("att." .. att .. ".name") + or TacRP:GetPhrase(atttbl.FullName) + or TacRP:TryTranslate(atttbl.FullName or atttbl.PrintName) + else + return TacRP:GetPhrase("att." .. att .. ".name") or TacRP:TryTranslate(atttbl.PrintName) + end +end + +function TacRP:GetAttDesc(att) + local atttbl = TacRP.GetAttTable(att) + if atttbl == {} then return "INVALID ATT" end + return TacRP:GetPhrase("att." .. att .. ".desc") or TacRP:TryTranslate(atttbl.Description) +end + +-- client languages aren't loaded through lua anymore. use gmod's stock localization system instead + +function TacRP:LoadLanguage(lang) + local cur_lang = lang or TacRP:GetLanguage() + + for _, v in pairs(file.Find("tacrp/shared/langs/*_" .. cur_lang .. ".lua", "LUA")) do + + L = {} + STL = {} + include("tacrp/shared/langs/" .. v) + AddCSLuaFile("tacrp/shared/langs/" .. v) + + local exp = string.Explode("_", string.lower(string.Replace(v, ".lua", ""))) + + if !exp[#exp] then + print("Failed to load TacRP language file " .. v .. ", did not get language name (naming convention incorrect?)") + continue + elseif !L then + print("Failed to load TacRP language file " .. v .. ", did not get language table") + continue + end + + for phrase, str in pairs(L) do + TacRP:AddPhrase(phrase, str, cur_lang) + end + + for str, phrase in pairs(STL) do + TacRP:AddSTP(str, phrase) + end + + if table.Count(L) > 0 then + hasany = true + end + + print("Loaded TacRP language file " .. v .. " with " .. table.Count(L) .. " strings.") + L = nil + STL = nil + end +end + +function TacRP:LoadLanguages() + TacRP.PhraseTable = {} + TacRP.STPTable = {} + + local lang = TacRP:GetLanguage() + TacRP:LoadLanguage(lang) + if lang ~= "en" then + TacRP:LoadLanguage("en") + end +end + +TacRP:LoadLanguages() + +if CLIENT then + + concommand.Add("tacrp_reloadlangs", function() + if !LocalPlayer():IsSuperAdmin() then return end + + net.Start("tacrp_reloadlangs") + net.SendToServer() + end) + + net.Receive("tacrp_reloadlangs", function(len, ply) + TacRP:LoadLanguages() + TacRP.Regen(true) + end) +elseif SERVER then + net.Receive("tacrp_reloadlangs", function(len, ply) + if !ply:IsSuperAdmin() then return end + + TacRP:LoadLanguages() + + net.Start("tacrp_reloadlangs") + net.Broadcast() + end) +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_1_ttt.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_1_ttt.lua new file mode 100644 index 0000000..fab7100 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_1_ttt.lua @@ -0,0 +1,226 @@ +TacRP.TTTAmmoToEntity = { + ["pistol"] = "item_ammo_pistol_ttt", + ["smg1"] = "item_ammo_smg1_ttt", + ["AlyxGun"] = "item_ammo_revolver_ttt", + ["357"] = "item_ammo_357_ttt", + ["buckshot"] = "item_box_buckshot_ttt" +} +--[[ +WEAPON_TYPE_RANDOM = 1 +WEAPON_TYPE_MELEE = 2 +WEAPON_TYPE_NADE = 3 +WEAPON_TYPE_SHOTGUN = 4 +WEAPON_TYPE_HEAVY = 5 +WEAPON_TYPE_SNIPER = 6 +WEAPON_TYPE_PISTOL = 7 +WEAPON_TYPE_SPECIAL = 8 +]] + +TacRP.AmmoToTTT = { + ["357"] = "AlyxGun", + ["SniperPenetratedRound"] = "357", + ["ar2"] = "smg1", +} + +TacRP.TTTAmmoToClipMax = { + ["357"] = 20, + ["smg1"] = 60, + ["pistol"] = 60, + ["alyxgun"] = 36, + ["buckshot"] = 24 +} + +TacRP.TTTReplaceLookup = { + ["weapon_ttt_glock"] = true, + ["weapon_ttt_m16"] = true, + ["weapon_zm_mac10"] = true, + ["weapon_zm_pistol"] = true, + ["weapon_zm_revolver"] = true, + ["weapon_zm_rifle"] = true, + ["weapon_zm_shotgun"] = true, + ["weapon_zm_sledge"] = true, + + ["weapon_zm_molotov"] = true, + ["weapon_ttt_confgrenade"] = true, + ["weapon_ttt_smokegrenade"] = true, +} + +TacRP.TTTReplacePreset = { + ["Pistol"] = {["weapon_zm_pistol"] = 1}, + ["Magnum"] = {["weapon_zm_revolver"] = 1}, + ["MachinePistol"] = {["weapon_ttt_glock"] = 1}, + ["SMG"] = {["weapon_ttt_m16"] = 1, ["weapon_zm_mac10"] = 0.5}, + ["AssaultRifle"] = {["weapon_ttt_m16"] = 0.5, ["weapon_zm_mac10"] = 1}, + ["BattleRifle"] = {["weapon_ttt_m16"] = 1, ["weapon_zm_mac10"] = 1}, + ["MarksmanRifle"] = {["weapon_zm_mac10"] = 0.5, ["weapon_zm_rifle"] = 1}, + ["Shotgun"] = {["weapon_zm_shotgun"] = 1}, + ["AutoShotgun"] = {["weapon_zm_shotgun"] = 0.5}, + ["MachineGun"] = {["weapon_zm_sledge"] = 1}, + ["SniperRifle"] = {["weapon_zm_rifle"] = 1}, +} + +TacRP.TTTReplaceCache = {} +function TacRP.GetRandomTTTWeapon(key) + if !TacRP.TTTReplaceLookup[key] then return end + if !TacRP.TTTReplaceCache[key] then + TacRP.TTTReplaceCache[key] = {0, {}} + for i, wep in pairs(weapons.GetList()) do + local weap = weapons.Get(wep.ClassName) + if !weap or !weap.ArcticTacRP or wep.ClassName == "tacrp_base" or wep.ClassName == "tacrp_base_nade" or !wep.AutoSpawnable then + continue + end + + if (istable(wep.TTTReplace) and wep.TTTReplace[key]) then + TacRP.TTTReplaceCache[key][1] = TacRP.TTTReplaceCache[key][1] + wep.TTTReplace[key] + TacRP.TTTReplaceCache[key][2][wep.ClassName] = wep.TTTReplace[key] + end + end + end + + if TacRP.TTTReplaceCache[key][1] > 0 then + local rng = math.random() * TacRP.TTTReplaceCache[key][1] + for k, v in pairs(TacRP.TTTReplaceCache[key][2]) do + rng = rng - v + if rng <= 0 then + return k + end + end + end +end + +if engine.ActiveGamemode() != "terrortown" then return end +local function setupttt() + for i, wep in pairs(weapons.GetList()) do + local weap = weapons.Get(wep.ClassName) + if !weap or !weap.ArcticTacRP or wep.ClassName == "tacrp_base" or wep.ClassName == "tacrp_base_nade" then + continue + end + + if TacRP.ConVars["ttt_shortname"]:GetBool() and wep.AbbrevName then + wep.FullName = wep.PrintName + wep.PrintName = wep.AbbrevName + end + + if weap.AmmoTTT then + wep.Ammo = weap.AmmoTTT + elseif TacRP.AmmoToTTT[weap.Ammo] then + wep.Ammo = TacRP.AmmoToTTT[weap.Ammo] + end + + wep.AmmoEnt = TacRP.TTTAmmoToEntity[wep.Ammo or weap.Ammo] or "" + if wep.AutoSpawnable == nil then + wep.AutoSpawnable = wep.Spawnable and !wep.AdminOnly + end + wep.AllowDrop = wep.AllowDrop or true + + -- We have to do this here because TTT2 does a check for .Kind in WeaponEquip, + -- earlier than Initialize() which assigns .Kind + if !wep.Kind and !wep.CanBuy then + if wep.PrimaryGrenade then + wep.Slot = 3 + wep.Kind = WEAPON_NADE + wep.spawnType = wep.spawnType or WEAPON_TYPE_NADE + elseif wep.Slot == 0 then + -- melee weapons + wep.Slot = 6 + wep.Kind = WEAPON_MELEE or WEAPON_EQUIP1 + wep.spawnType = wep.spawnType or WEAPON_TYPE_MELEE + elseif wep.Slot == 1 then + -- sidearms + wep.Kind = WEAPON_PISTOL + wep.spawnType = wep.spawnType or WEAPON_TYPE_PISTOL + else + -- other weapons are considered primary + -- try to determine spawntype if none exists + if !wep.spawnType then + if (wep.Ammo or weap.Ammo) == "357" or (wep.Slot == 3 and (wep.Num or 1) == 1) then + wep.spawnType = WEAPON_TYPE_SNIPER + elseif (wep.Ammo or weap.Ammo) == "buckshot" or (wep.Num or 1) > 1 then + wep.spawnType = WEAPON_TYPE_SHOTGUN + else + wep.spawnType = WEAPON_TYPE_HEAVY + end + end + + wep.Slot = 2 + wep.Kind = WEAPON_HEAVY + end + end + + local class = wep.ClassName + local path = "tacrp/weaponicons/" .. class + local path2 = "tacrp/ttticons/" .. class .. ".png" + local path3 = "vgui/ttt/" .. class + local path4 = "entities/" .. class .. ".png" + + if !Material(path2):IsError() then + -- TTT icon (png) + wep.Icon = path2 + elseif !Material(path3):IsError() then + -- TTT icon (vtf) + wep.Icon = path3 + elseif !Material(path4):IsError() then + -- Entity spawn icon + wep.Icon = path4 + elseif !Material(path):IsError() then + -- Kill icon + wep.Icon = path + else + -- fallback: display _something_ + wep.Icon = "entities/npc_headcrab.png" + end + + if CLIENT then + local lang = TTT2 and "en" or "english" + LANG.AddToLanguage(lang, "tacrp_search_dmg_buckshot", "This terrorist was blasted to shreds by buckshot.") + end + end +end +hook.Add("OnGamemodeLoaded", "TacRP_TTT", setupttt) +hook.Add("TacRP_LoadAtts", "TacRP_TTT", setupttt) + +hook.Add( "OnEntityCreated", "TacRP_TTT_Spawn", function(ent) + if CLIENT then return end + if TacRP.ConVars["ttt_weapon_include"]:GetBool() + and TacRP.TTTReplaceLookup[ent:GetClass()] + and math.random() <= TacRP.ConVars["ttt_weapon_replace"]:GetFloat() then + + timer.Simple(0, function() + if !IsValid(ent) or IsValid(ent:GetOwner()) then return end + + local class = ent:GetClass() + local wpn = TacRP.GetRandomTTTWeapon(class) + + if wpn then + local wpnent = ents.Create(wpn) + wpnent:SetPos(ent:GetPos()) + wpnent:SetAngles(ent:GetAngles()) + wpnent:Spawn() + timer.Simple(0, function() + if !ent:IsValid() then return end + -- wpnent:OnDrop(true) + ent:Remove() + end) + end + end) + end +end) + +hook.Add("TTTPrepareRound", "TacRP_TTT", function() + if CLIENT then return end + local give = TacRP.ConVars["ttt_atts_giveonspawn"]:GetInt() + if give <= 0 then return end + + for _, ply in pairs(player.GetAll()) do + ply.TacRP_AttInv = {} + for i = 1, give do + local id + for j = 1, 5 do -- up to 5 random attempts + id = TacRP.Attachments_Index[math.random(1, TacRP.Attachments_Count)] + if !TacRP.Attachments[id].InvAtt and (!ply.TacRP_AttInv or ply.TacRP_AttInv[id] == 0) then break end + end + TacRP:PlayerGiveAtt(ply, id, 1) + end + TacRP:PlayerSendAttInv(ply) + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_ammo.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_ammo.lua new file mode 100644 index 0000000..856fd21 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_ammo.lua @@ -0,0 +1,116 @@ +local ammotypes = { + ["ti_flashbang"] = { + max = "tacrp_max_grenades", + }, + ["ti_thermite"] = { + max = "tacrp_max_grenades", + }, + ["ti_smoke"] = { + max = "tacrp_max_grenades", + }, + ["ti_c4"] = { + max = "tacrp_max_grenades", + }, + ["ti_gas"] = { + max = "tacrp_max_grenades", + }, + ["ti_nuke"] = { + max = "tacrp_max_grenades", + }, + ["ti_charge"] = { + max = "tacrp_max_grenades", + }, + ["ti_heal"] = { + max = "tacrp_max_grenades", + }, + + -- Only used when tacrp_expandedammotypes 1 + ["ti_pistol_light"] = { -- .22LR, .380 ACP etc. + expanded = true, + max = 200, + }, + ["ti_pistol_heavy"] = { -- .45 ACP, 10mm etc. + expanded = true, + max = 120, + }, + ["ti_pdw"] = { -- 4.6mm, 5.7mm etc. + expanded = true, + max = 225, + }, + ["ti_rifle"] = { -- above 7.62mm but below sniper caliber + expanded = true, + max = 50, + }, + ["ti_sniper"] = { -- sniper, amr calibers + expanded = true, + max = 10, + }, +} + +function TacRP.AddAmmoType(name, data) + ammotypes[name] = data +end + +hook.Add("Initialize", "tacrp_ammo", function() + local expanded = TacRP.ConVars["expandedammotypes"]:GetBool() + for k, v in SortedPairs(ammotypes) do + if v.expanded and not expanded then continue end + local maxcvar = v.max + if isnumber(v.max) then + maxcvar = "sk_max_" .. k + CreateConVar(maxcvar, v.max, FCVAR_REPLICATED + FCVAR_ARCHIVE) + end + game.AddAmmoType({ + name = k, + maxcarry = maxcvar + }) + + if CLIENT then + language.Add(k .. "_ammo", TacRP:GetPhrase("ammo." .. k) or k) + end + end +end) + + +local materials = { + ["ti_flashbang"] = "tacrp/grenades/flashbang", + ["ti_thermite"] = "tacrp/grenades/thermite", + ["ti_smoke"] = "tacrp/grenades/smoke", + ["ti_c4"] = "tacrp/grenades/c4", + ["ti_gas"] = "tacrp/grenades/gas", + ["ti_nuke"] = "tacrp/grenades/nuke", + ["ti_charge"] = "tacrp/grenades/breach", + ["ti_heal"] = "tacrp/grenades/heal", + ["SniperPenetratedRound"] = "tacrp/grenades/sniper", +} + +if CLIENT then + hook.Add("InitPostEntity", "tacrp_hl2hud", function() + if !HL2HUD then return end + local tbl = HL2HUD.scheme.DefaultSettings().HudTextures.AmmoInv + local tbl2 = HL2HUD.scheme.DefaultSettings().HudTextures.Ammo + + for k, v in pairs(materials) do + local info = { + type = 2, + w = 64, + h = 64, + x = 0, + y = 0, + u1 = 0, + u2 = 64, + v1 = 0, + v2 = 64, + scalable = false, + texture = surface.GetTextureID(v) + } + if !tbl[k] then + tbl[k] = info + end + if !tbl2[k] then + tbl2[k] = info + end + end + HL2HUD.settings.ReloadScheme() + end) +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_attinv.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_attinv.lua new file mode 100644 index 0000000..446d535 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_attinv.lua @@ -0,0 +1,115 @@ +function TacRP:PlayerGetAtts(ply, att) + if !IsValid(ply) then return 0 end + + local atttbl = TacRP.GetAttTable(att) + if !atttbl then return 0 end + if atttbl.InvAtt then att = atttbl.InvAtt end + if !ply:IsAdmin() and atttbl.AdminOnly then return 0 end + if atttbl.Free or TacRP.ConVars["free_atts"]:GetBool() then return 9999 end + if engine.ActiveGamemode() == "terrortown" and TacRP.ConVars["ttt_bench_freeatts"]:GetBool() and TacRP.NearBench(ply) then return 9999 end + + local ret = hook.Run("TacRP_PlayerAttCount", ply, att) + if ret != nil then return ret end + + if !ply.TacRP_AttInv then return 0 end + if !ply.TacRP_AttInv[att] then return 0 end + + return ply.TacRP_AttInv[att] +end + +function TacRP:PlayerGiveAtt(ply, att, amt) + amt = amt or 1 + + if !IsValid(ply) then return false end + + if !ply.TacRP_AttInv then ply.TacRP_AttInv = {} end + + local atttbl = TacRP.GetAttTable(att) + + if !atttbl then print("Invalid att " .. att) return false end + if atttbl.Free then return false end -- You can't give a free attachment, silly + if atttbl.AdminOnly and !(ply:IsPlayer() and ply:IsAdmin()) then return false end + if atttbl.InvAtt then att = atttbl.InvAtt end + + if TacRP.ConVars["free_atts"]:GetBool() then return true end + local ret = hook.Run("TacRP_PlayerGiveAtt", ply, att, amt) + if ret != nil then return ret end + + if TacRP.ConVars["lock_atts"]:GetBool() then + if ply.TacRP_AttInv[att] == 1 then return end + ply.TacRP_AttInv[att] = 1 + return true + else + ply.TacRP_AttInv[att] = (ply.TacRP_AttInv[att] or 0) + amt + return true + end +end + +function TacRP:PlayerTakeAtt(ply, att, amt) + amt = amt or 1 + + if !IsValid(ply) then return end + + if !ply.TacRP_AttInv then ply.TacRP_AttInv = {} end + + local atttbl = TacRP.GetAttTable(att) + if !atttbl then return false end + if atttbl.Free then return true end + if atttbl.InvAtt then att = atttbl.InvAtt end + + if TacRP.ConVars["free_atts"]:GetBool() then return true end + local ret = hook.Run("TacRP_PlayerTakeAtt", ply, att, amt) + if ret != nil then return ret end + + if (ply.TacRP_AttInv[att] or 0) < (TacRP.ConVars["lock_atts"]:GetBool() and 1 or amt) then return false end + + if !TacRP.ConVars["lock_atts"]:GetBool() then + ply.TacRP_AttInv[att] = ply.TacRP_AttInv[att] - amt + if ply.TacRP_AttInv[att] <= 0 then + ply.TacRP_AttInv[att] = nil + end + end + return true +end + +if CLIENT then + net.Receive("TacRP_sendattinv", function(len, ply) + LocalPlayer().TacRP_AttInv = {} + + local count = net.ReadUInt(32) + + for i = 1, count do + local attid = net.ReadUInt(TacRP.Attachments_Bits) + local acount = net.ReadUInt(32) + + local att = TacRP.Attachments_Index[attid] + + LocalPlayer().TacRP_AttInv[att] = acount + end + end) +elseif SERVER then + hook.Add("PlayerSpawn", "TacRP_SpawnAttInv", function(ply, trans) + if trans then return end + if engine.ActiveGamemode() != "terrortown" and TacRP.ConVars["loseattsondie"]:GetInt() > 0 then + ply.TacRP_AttInv = {} + TacRP:PlayerSendAttInv(ply) + end + end) + + function TacRP:PlayerSendAttInv(ply) + if TacRP.ConVars["free_atts"]:GetBool() then return end + if !IsValid(ply) then return end + if !ply.TacRP_AttInv then return end + + net.Start("TacRP_sendattinv") + net.WriteUInt(table.Count(ply.TacRP_AttInv), 32) + for att, count in pairs(ply.TacRP_AttInv) do + local atttbl = TacRP.GetAttTable(att) + local attid = atttbl.ID + net.WriteUInt(attid, TacRP.Attachments_Bits) + net.WriteUInt(count, 32) + end + net.Send(ply) + end + +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_atts.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_atts.lua new file mode 100644 index 0000000..809ebad --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_atts.lua @@ -0,0 +1,226 @@ +TacRP.Attachments = {} +TacRP.Attachments_Index = {} + +TacRP.Attachments_Count = 0 + +TacRP.Attachments_Bits = 16 + +function TacRP.InvalidateCache() + for _, e in pairs(ents.GetAll()) do + if e:IsWeapon() and e.ArcticTacRP then + e:InvalidateCache() + e:SetBaseSettings() + end + end +end + +function TacRP.LoadAtt(atttbl, shortname, id) + if atttbl.Ignore then return end + + if !id then + TacRP.Attachments_Count = TacRP.Attachments_Count + 1 + id = TacRP.Attachments_Count + end + + atttbl.ShortName = shortname + atttbl.ID = id + + TacRP.Attachments[shortname] = atttbl + TacRP.Attachments_Index[id] = shortname + + if TacRP.ConVars["generateattentities"]:GetBool() and !atttbl.DoNotRegister and !atttbl.InvAtt and !atttbl.Free then + local attent = {} + attent.Base = "tacrp_att" + attent.Icon = atttbl.Icon + if attent.Icon and attent.Icon.GetTexture and attent.Icon:GetTexture( "$basetexture" ) then + attent.IconOverride = string.Replace( attent.Icon:GetTexture( "$basetexture" ):GetName() .. ".png", "0001010", "" ) + end + attent.PrintName = atttbl.FullName or atttbl.PrintName or shortname + attent.Spawnable = true + attent.AdminOnly = atttbl.AdminOnly or false + attent.AttToGive = shortname + attent.Category = "Tactical RP - Attachments" + + scripted_ents.Register(attent, "tacrp_att_" .. shortname) + end +end + +function TacRP.LoadAtts() + TacRP.Attachments_Count = 0 + TacRP.Attachments = {} + TacRP.Attachments_Index = {} + + local searchdir = "tacrp/shared/atts/" + local searchdir_bulk = "tacrp/shared/atts_bulk/" + + local files = file.Find(searchdir .. "/*.lua", "LUA") + + for _, filename in pairs(files) do + AddCSLuaFile(searchdir .. filename) + end + + files = file.Find(searchdir .. "/*.lua", "LUA") + + for _, filename in pairs(files) do + if filename == "default.lua" then continue end + + ATT = {} + + local shortname = string.sub(filename, 1, -5) + + include(searchdir .. filename) + + if ATT.Ignore then continue end + + TacRP.LoadAtt(ATT, shortname) + end + + local bulkfiles = file.Find(searchdir_bulk .. "/*.lua", "LUA") + + for _, filename in pairs(bulkfiles) do + AddCSLuaFile(searchdir_bulk .. filename) + end + + bulkfiles = file.Find(searchdir_bulk .. "/*.lua", "LUA") + + for _, filename in pairs(bulkfiles) do + if filename == "default.lua" then continue end + + include(searchdir_bulk .. filename) + end + + TacRP.Attachments_Bits = math.min(math.ceil(math.log(TacRP.Attachments_Count + 1, 2)), 32) + hook.Run("TacRP_LoadAtts") + + TacRP.InvalidateCache() +end + +function TacRP.GetAttTable(name) + local shortname = name + if isnumber(shortname) then + shortname = TacRP.Attachments_Index[name] + end + + if TacRP.Attachments[shortname] then + return TacRP.Attachments[shortname] + else + // assert(false, "!!!! TacRP tried to access invalid attachment " .. (shortname or "NIL") .. "!!!") + return {} + end +end + +function TacRP.GetAttsForCats(cats, wpn) + if !istable(cats) then + cats = {cats} + end + + local atts = {} + + for i, k in pairs(TacRP.Attachments) do + if k.Compatibility then + local result = k.Compatibility(wpn) + + if result == true then + table.insert(atts, k.ShortName) + elseif result == false then + continue + end + end + + local attcats = k.Category + + if !istable(attcats) then + attcats = {attcats} + end + + for _, cat in pairs(cats) do + if table.HasValue(attcats, cat) then + + table.insert(atts, k.ShortName) + break + end + end + end + + return atts +end + +if CLIENT then + +concommand.Add("tacrp_reloadatts", function() + if !LocalPlayer():IsSuperAdmin() then return end + + net.Start("tacrp_reloadatts") + net.SendToServer() +end) + +net.Receive("tacrp_reloadatts", function(len, ply) + TacRP.LoadAtts() + TacRP.InvalidateCache() +end) + +elseif SERVER then + +net.Receive("tacrp_reloadatts", function(len, ply) + if !ply:IsSuperAdmin() then return end + + TacRP.LoadAtts() + TacRP.InvalidateCache() + + net.Start("tacrp_reloadatts") + net.Broadcast() +end) + +end + +TacRP.Benches = ents.FindByClass("tacrp_bench") or {} +TacRP.BenchDistSqr = 128 * 128 + +function TacRP.NearBench(ply) + local nearbench = false + for i, ent in pairs(TacRP.Benches) do + if !IsValid(ent) then table.remove(TacRP.Benches, i) continue end + if ent:GetPos():DistToSqr(ply:GetPos()) <= TacRP.BenchDistSqr then + nearbench = true + break + end + end + if !nearbench then return false end + return true +end + +function TacRP.CanCustomize(ply, wep, att, slot, detach) + + local can, reason = hook.Run("TacRP_CanCustomize", ply, wep, att, slot, detach) + if can ~= nil then + return can, reason + end + + if engine.ActiveGamemode() == "terrortown" then + local role = ply:GetTraitor() or ply:IsDetective() + + // disabled across role + if (role and !TacRP.ConVars["ttt_cust_role_allow"]:GetBool()) or (!role and !TacRP.ConVars["ttt_cust_inno_allow"]:GetBool()) then + return false, "Restricted for role" + end + + // disabled during round + if GetRoundState() == ROUND_ACTIVE and ((role and !TacRP.ConVars["ttt_cust_role_round"]:GetBool()) or (!role and !TacRP.ConVars["ttt_cust_inno_round"]:GetBool())) then + return false, "Restricted during round" + end + + // disabled when not near bench + if ((role and TacRP.ConVars["ttt_cust_role_needbench"]:GetBool()) or (!role and TacRP.ConVars["ttt_cust_inno_needbench"]:GetBool())) and !TacRP.NearBench(ply) then + return false, "Requires Customization Bench" + end + else + // check bench + if TacRP.ConVars["rp_requirebench"]:GetBool() and !TacRP.NearBench(ply) then + return false, "Requires Customization Bench" + end + end + + return true +end + +TacRP.LoadAtts() diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_common.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_common.lua new file mode 100644 index 0000000..65ee37a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_common.lua @@ -0,0 +1,376 @@ +TacRP.Version = "34" // 2025-12-08 + +TacRP.ShotgunHullSize = 0.5 // Bigger equals more generous hitboxes + +TacRP.PenTable = { + [MAT_ANTLION] = 0.1, + [MAT_BLOODYFLESH] = 0.1, + [MAT_CONCRETE] = 0.5, + [MAT_DIRT] = 0.25, + [MAT_EGGSHELL] = 0.25, + [MAT_FLESH] = 0.1, + [MAT_GRATE] = 0.25, + [MAT_ALIENFLESH] = 0.25, + [MAT_CLIP] = 1000, + [MAT_SNOW] = 0.1, + [MAT_PLASTIC] = 0.25, + [MAT_METAL] = 1, + [MAT_SAND] = 0.5, + [MAT_FOLIAGE] = 0.25, + [MAT_COMPUTER] = 0.25, + [MAT_SLOSH] = 0.25, + [MAT_TILE] = 0.5, // you know, like ceramic armor + [MAT_GRASS] = 0.25, + [MAT_VENT] = 0.1, + [MAT_WOOD] = 0.25, + [MAT_DEFAULT] = 0.25, + [MAT_GLASS] = 0.1, + [MAT_WARPSHIELD] = 1 +} + +// Why the fuck is this a thing??? +TacRP.CancelMultipliers = { + [1] = { + [HITGROUP_HEAD] = 2, + [HITGROUP_LEFTARM] = 0.25, + [HITGROUP_RIGHTARM] = 0.25, + [HITGROUP_LEFTLEG] = 0.25, + [HITGROUP_RIGHTLEG] = 0.25, + [HITGROUP_GEAR] = 0.25, + }, + ["terrortown"] = { + [HITGROUP_HEAD] = 1, + [HITGROUP_LEFTARM] = 0.55, + [HITGROUP_RIGHTARM] = 0.55, + [HITGROUP_LEFTLEG] = 0.55, + [HITGROUP_RIGHTLEG] = 0.55, + [HITGROUP_GEAR] = 0.55, + }, +} + +TacRP.PresetPath = "tacrp_presets/" + +TacRP.OverDraw = false + +TacRP.HUToM = 0.3048 / 12 + +TacRP.HolsterNetBits = 3 +TacRP.HOLSTER_SLOT_BACK = 1 +TacRP.HOLSTER_SLOT_BACK2 = 2 +TacRP.HOLSTER_SLOT_PISTOL = 3 +TacRP.HOLSTER_SLOT_GEAR = 4 +TacRP.HOLSTER_SLOT_SPECIAL = 5 + +TacRP.GRENADE1_Backup = KEY_G +TacRP.GRENADE2_Backup = KEY_H + +TacRP.IN_MELEE = IN_WEAPON1 +TacRP.IN_CUSTOMIZE = IN_WEAPON2 +TacRP.IN_TACTICAL = IN_BULLRUSH + +TacRP.LockableEntities = { + ["unity_flare"] = true +} + +TacRP.FlareEntities = { + ["unity_flare"] = true, + ["sw_flare_v3"] = true, + ["env_flare"] = true, + ["glide_flare"] = true, + ["tacrp_proj_p2a1_flare"] = true, + ["tacrp_proj_p2a1_paraflare"] = true, + ["tacrp_flare_cloud"] = true, + ["tacrp_flare_cloud_para"] = true, +} + +TacRP.HolsterBones = { + [TacRP.HOLSTER_SLOT_BACK] = { + "ValveBiped.Bip01_Spine2", + Vector(0, 0, 0), + {"models/props_c17/SuitCase_Passenger_Physics.mdl", Vector(6, 4, 8), Angle(0, 0, 0)}, + }, + [TacRP.HOLSTER_SLOT_BACK2] = { + "ValveBiped.Bip01_Spine2", + {Vector(0, 4, 12), Angle(180, 180, 0)}, + {"models/props_c17/SuitCase_Passenger_Physics.mdl", Vector(6, 4, 8), Angle(0, 0, 0)}, + }, + [TacRP.HOLSTER_SLOT_PISTOL] = { + "ValveBiped.Bip01_R_Thigh", + Vector(-1.5, 1.5, -0.75), + {"models/weapons/w_eq_eholster_elite.mdl", Vector(0, 8, -4), Angle(90, 0, 90)}, + }, + [TacRP.HOLSTER_SLOT_GEAR] = { + "ValveBiped.Bip01_Pelvis", + Vector(0, 10, 0), + {"models/weapons/w_defuser.mdl", Vector(0, -10, -8), Angle(-90, -90, 0)}, + }, + [TacRP.HOLSTER_SLOT_SPECIAL] = { + "ValveBiped.Bip01_Spine2", + Vector(0, 4, 4), + {"models/props_c17/SuitCase_Passenger_Physics.mdl", Vector(6, 4, 8), Angle(0, 0, 0)}, + }, +} + +TacRP.BlindFireNetBits = 3 + +TacRP.BLINDFIRE_NONE = 0 +TacRP.BLINDFIRE_UP = 1 +TacRP.BLINDFIRE_LEFT = 2 +TacRP.BLINDFIRE_RIGHT = 3 +TacRP.BLINDFIRE_KYS = 4 // You should kill yourself... NOW! + +TacRP.MuzzleEffects = { + "muzzleflash_smg", + "muzzleflash_smg_bizon", + "muzzleflash_shotgun", + "muzzleflash_slug", + "muzzleflash_slug_flame", + "muzzleflash_pistol", + "muzzleflash_pistol_cleric", + "muzzleflash_pistol_deagle", + "muzzleflash_suppressed", + "muzzleflash_mp5", + "muzzleflash_MINIMI", + "muzzleflash_m79", + "muzzleflash_m14", + "muzzleflash_ak47", + "muzzleflash_ak74", + "muzzleflash_m82_tacrp", + "muzzleflash_m82_rico", + "muzzleflash_m3", + "muzzleflash_famas", + "muzzleflash_g3", + "muzzleflash_1", + "muzzleflash_3", + "muzzleflash_4", + "muzzleflash_5", + "muzzleflash_6", +} +TacRP.MuzzleEffectsLookup = {} +for k, v in ipairs(TacRP.MuzzleEffects) do + TacRP.MuzzleEffectsLookup[v] = k +end + +TacRP.AreTheGrenadeAnimsReadyYet = true + +TacRP.FACTION_NEUTRAL = 0 +TacRP.FACTION_COALITION = 1 +TacRP.FACTION_MILITIA = 2 + +TacRP.FactionToPhrase = { + [TacRP.FACTION_NEUTRAL] = "faction.neutral", + [TacRP.FACTION_COALITION] = "faction.coalition", + [TacRP.FACTION_MILITIA] = "faction.militia", +} + +function TacRP.ShouldWeFunny(never_odds) + local i = TacRP.ConVars["funny_loudnoises"]:GetInt() + return i == 2 or (i == 1 and (os.date("%m-%d") == "04-01" or (!never_odds and math.random() <= 0.01))) +end + +TacRP.BALANCE_AUTO = -1 +TacRP.BALANCE_RP = 0 +TacRP.BALANCE_SBOX = 1 +TacRP.BALANCE_TTT = 2 +TacRP.BALANCE_PVE = 3 +TacRP.BALANCE_OLDSCHOOL = 4 + +function TacRP.GetBalanceMode() + local i = TacRP.ConVars["balance"]:GetInt() + if i == TacRP.BALANCE_AUTO then + if engine.ActiveGamemode() == "terrortown" then + return TacRP.BALANCE_TTT + elseif DarkRP or ix then + return TacRP.BALANCE_RP + else + return TacRP.BALANCE_SBOX + end + else + return i + end +end + +TacRP.BalanceUseTiers = { + [TacRP.BALANCE_RP] = true, + [TacRP.BALANCE_PVE] = true, +} + +TacRP.BalanceDefaults = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_PVE] = { + RecoilVisualKick = 0.75, + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 + } + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilVisualKick = 0, + + MoveSpreadPenalty = 0, + HipFireSpreadPenalty = 0.007, + + MeleeSpeedMult = 1, + ShootingSpeedMult = 1, + SightedSpeedMult = 1, + ReloadSpeedMult = 1, + MidAirSpreadPenalty = 0.025 + }, +} + +function TacRP.UseTiers() + return TacRP.BalanceUseTiers[TacRP.GetBalanceMode()] +end + +TacRP.AmmoJamMSB = { + ["pistol"] = 30, + ["smg1"] = 25, + ["ar2"] = 20, + ["357"] = 10, + ["buckshot"] = 15, + ["SniperPenetratedRound"] = 15, +} + +TacRP.HoldTypeSightedLookup = { + ["revolver"] = "revolver", + ["smg"] = "rpg", + ["ar2"] = "rpg", + ["shotgun"] = "rpg", +} + +TacRP.ReloadAnimOffsets = { + // animation cycle pointers: PLAYERANIMEVENT_RELOAD defines rounds-reload endpoint; others define start points + [PLAYERANIMEVENT_RELOAD] = { + [ACT_HL2MP_GESTURE_RELOAD_REVOLVER] = 0.6, + [ACT_HL2MP_GESTURE_RELOAD_SHOTGUN] = 0.25, + }, + [PLAYERANIMEVENT_RELOAD_LOOP] = { + [ACT_HL2MP_GESTURE_RELOAD_REVOLVER] = 0.6, + [ACT_HL2MP_GESTURE_RELOAD_SHOTGUN] = 0.31, + }, + [PLAYERANIMEVENT_RELOAD_END] = { + [ACT_HL2MP_GESTURE_RELOAD_REVOLVER] = 0.7, + [ACT_HL2MP_GESTURE_RELOAD_SHOTGUN] = 0.5, + }, + [PLAYERANIMEVENT_CANCEL_RELOAD] = { + }, +} + +TacRP.ShellTypes = { + [1] = { + Model = "models/tacint/shells/pistol_shell.mdl", + Sounds = { + "TacRP/shells/shell_drop-1.wav", + "TacRP/shells/shell_drop-2.wav", + "TacRP/shells/shell_drop-3.wav", + "TacRP/shells/shell_drop-4.wav", + "TacRP/shells/shell_drop-5.wav", + } + }, + [2] = { + Model = "models/tacint/shells/rifle_shell.mdl", + Sounds = { + "TacRP/shells/shell_drop-1.wav", + "TacRP/shells/shell_drop-2.wav", + "TacRP/shells/shell_drop-3.wav", + "TacRP/shells/shell_drop-4.wav", + "TacRP/shells/shell_drop-5.wav", + } + }, + [3] = { + Model = "models/tacint/shells/shotgun_shell.mdl", + Sounds = { + "TacRP/shells/shotshell_drop-1.wav", + "TacRP/shells/shotshell_drop-2.wav", + "TacRP/shells/shotshell_drop-3.wav", + "TacRP/shells/shotshell_drop-4.wav", + "TacRP/shells/shotshell_drop-5.wav", + } + }, + [4] = { + Model = "models/tacint/shells/ks23_shell.mdl", + Sounds = { + "TacRP/shells/shotshell_drop-1.wav", + "TacRP/shells/shotshell_drop-2.wav", + "TacRP/shells/shotshell_drop-3.wav", + "TacRP/shells/shotshell_drop-4.wav", + "TacRP/shells/shotshell_drop-5.wav", + } + }, +} +hook.Add("InitPostEntity", "tacrp_shelleffect", function() + hook.Run("TacRP_LoadShellEffects", TacRP.ShellTypes) + + if GetConVar("tacrp_phystweak"):GetBool() then + local v = physenv.GetPerformanceSettings().MaxVelocity + if v < 10000 then + physenv.SetPerformanceSettings({MaxVelocity = 10000}) + print("[TacRP] Increasing MaxVelocity for projectiles to behave as intended! (" .. v .. "-> 10000)") + print("[TacRP] Disable this behavior with 'tacrp_phystweak 0'.") + end + end +end) + +hook.Add("DoAnimationEvent", "TacRP_HandleAnimEvents", function(ply, event, data) + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.ArcticTacRP and event != 20 and data > 0 then // we are approximating; data must be an integer + local t = data * 0.001 + if event == PLAYERANIMEVENT_ATTACK_PRIMARY then + if data == 0 then + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, wep:GetValue("GestureShoot"), true) + else // second layer of bodgening + local gest = data < 0 and wep:GetValue("GestureBash2") or wep:GetValue("GestureBash") + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, gest, true) + ply:SetLayerDuration(GESTURE_SLOT_ATTACK_AND_RELOAD, math.abs(t)) + end + return ACT_INVALID + end + if event == PLAYERANIMEVENT_ATTACK_SECONDARY then + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_GMOD_GESTURE_ITEM_THROW, true) + ply:SetLayerDuration(GESTURE_SLOT_ATTACK_AND_RELOAD, t * 1.5) + return ACT_INVALID + end + if event == PLAYERANIMEVENT_RELOAD then + local gest = wep:GetValue("GestureReload") + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, gest, true) + if wep:GetValue("ShotgunReload") then + local offset = (TacRP.ReloadAnimOffsets[event][gest] or 0.5) + ply:SetLayerDuration(GESTURE_SLOT_ATTACK_AND_RELOAD, t / offset) + else + ply:SetLayerDuration(GESTURE_SLOT_ATTACK_AND_RELOAD, t) + end + return ACT_INVALID + end + if event == PLAYERANIMEVENT_RELOAD_LOOP then + local gest = wep:GetValue("GestureReload") + local offset = (TacRP.ReloadAnimOffsets[event][gest] or 0.5) + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, gest, true) + ply:SetLayerDuration(GESTURE_SLOT_ATTACK_AND_RELOAD, t / offset) + ply:SetLayerCycle(GESTURE_SLOT_ATTACK_AND_RELOAD, offset) + return ACT_INVALID + end + if event == PLAYERANIMEVENT_RELOAD_END then + local gest = wep:GetValue("GestureReload") + local offset = (TacRP.ReloadAnimOffsets[event][gest] or 0.6) + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, gest, true) + ply:SetLayerDuration(GESTURE_SLOT_ATTACK_AND_RELOAD, t / (1 - offset)) + ply:SetLayerCycle(GESTURE_SLOT_ATTACK_AND_RELOAD, offset) + return ACT_INVALID + end + if event == PLAYERANIMEVENT_CANCEL_RELOAD then + local gest = wep:GetValue("GestureReload") + local offset = (TacRP.ReloadAnimOffsets[event][gest] or 0.6) + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, gest, true) + ply:SetLayerDuration(GESTURE_SLOT_ATTACK_AND_RELOAD, t / (1 - offset)) + ply:SetLayerCycle(GESTURE_SLOT_ATTACK_AND_RELOAD, offset) + return ACT_INVALID + end + end +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_configs.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_configs.lua new file mode 100644 index 0000000..08a13df --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_configs.lua @@ -0,0 +1,140 @@ +TacRP.DefaultConfigs = { + -- General purpose configs + ["tactical"] = { -- This is the default config with all convars on default value! + ["balance"] = 0, + }, + ["arcade"] = { + ["balance"] = 1, + ["penalty_reload"] = false, + ["penalty_melee"] = false, + }, + ["hardcore"] = { + ["balance"] = 0, + ["mult_damage"] = 2.5, + ["mult_damage_magnum"] = 2, + ["mult_damage_sniper"] = 1.4, + ["mult_damage_shotgun"] = 1.5, + ["mult_damage_explosive"] = 2, + ["mult_recoil_kick"] = 1.25, + ["sprint_reload"] = false, + }, + ["boomer"] = { + ["balance"] = 1, + ["oldschool"] = true, + ["crosshair"] = true, + ["sway"] = false, + ["freeaim"] = false, + ["recoilpattern"] = false, + ["altrecoil"] = false, + }, + ["csgo"] = { + ["balance"] = 0, + ["oldschool"] = true, + ["crosshair"] = true, + ["sway"] = false, + ["freeaim"] = false, + ["physbullet"] = false, + }, + + -- pve configs + ["pve_hl2"] = { + ["balance"] = 1, + ["oldschool"] = true, + ["crosshair"] = true, + ["sway"] = false, + ["freeaim"] = false, + ["physbullet"] = false, + ["recoilpattern"] = false, + ["altrecoil"] = false, + + ["mult_damage"] = 0.5, + ["mult_damage_magnum"] = 0.8, + ["mult_damage_sniper"] = 0.8, + ["mult_damage_shotgun"] = 0.8, + ["mult_damage_explosive"] = 0.5, + ["mult_recoil_kick"] = 0.75, + + ["penalty_reload"] = false, + ["penalty_melee"] = false, + ["npc_equality"] = true, + }, + ["pve_tac"] = { + ["balance"] = 0, + + ["mult_damage"] = 0.5, + ["mult_damage_magnum"] = 0.8, + ["mult_damage_sniper"] = 0.8, + ["mult_damage_shotgun"] = 0.8, + ["mult_damage_explosive"] = 0.5, + ["mult_recoil_kick"] = 0.75, + + ["sprint_reload"] = false, + ["npc_equality"] = true, + }, + + -- TTT configs + ["ttt_modern"] = { + ["balance"] = 2, + ["sprint_reload"] = false, + }, + ["ttt_purist"] = { + ["balance"] = 2, + ["crosshair"] = true, + ["sway"] = false, + ["freeaim"] = false, + ["physbullet"] = false, + + ["penalty_reload"] = false, + ["penalty_melee"] = false, + ["sprint_reload"] = false, + } +} + +if SERVER then + function TacRP.ApplyConfig(tbl) + if isstring(tbl) then + tbl = TacRP.DefaultConfigs[tbl] or {} + end + for name, cvar in pairs(TacRP.ConVars) do + local val = tbl[name] + if val == nil then + cvar:Revert() + elseif isstring(val) then + cvar:SetString(val) + elseif isbool(val) then + cvar:SetBool(val) + elseif math.Round(val) == val then + cvar:SetInt(val) + else + cvar:SetFloat(val) + end + end + end + + net.Receive("tacrp_applyconfig", function(len, ply) + if !ply:IsAdmin() then return end + + local config = net.ReadString() + TacRP.ApplyConfig(config) + end) +end + +concommand.Add("tacrp_applyconfig", function(ply, cmd, args, argStr) + if #args < 1 or (IsValid(ply) and !ply:IsAdmin()) then return end + if SERVER then + TacRP.ApplyConfig(args[1]) + else + net.Start("tacrp_applyconfig") + net.WriteString(args[1]) + net.SendToServer() + end +end, function(cmd, argStr) + local arg = string.Trim(argStr:lower()) + local tbl = {} + for cfg, vals in SortedPairs(TacRP.DefaultConfigs) do + if string.Left(cfg, string.len(arg)) == arg then + table.insert(tbl, cmd .. " " .. cfg) + end + end + return tbl +end) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_containers.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_containers.lua new file mode 100644 index 0000000..448af96 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_containers.lua @@ -0,0 +1,140 @@ +TacRP.Containers = { + -- {entity = entity, capacity = number, items = {}} +} + +function TacRP.MakeContainer(ent, size) + if ent.TacRP_ContainerID then return end + ent.TacRP_ContainerID = table.insert(TacRP.Containers, { + entity = ent, + capacity = size or 100, + weight = 0, + items = {} + }) +end + +function TacRP.ContainerAccessors(con_id) + local container = TacRP.Containers[con_id] + if !container then return {} end + + local tbl = {} + + if IsValid(container.entity) and (container.entity:GetOwner()) then + table.insert(tbl, container.entity:GetOwner()) + end + + -- TODO: When a player is interacting with a bag entity, they should be here + + return tbl +end + +function TacRP.ContainerSize(i) + if !TacRP.Containers[i].weight then + local weight = 0 + for k, v in pairs(TacRP.Containers[i].items) do + weight = weight + v:GetWeight() + end + TacRP.Containers[i].weight = weight + end + return TacRP.Containers[i].weight +end + +function TacRP.CanFitContainer(ent, item) + local container = TacRP.Containers[ent.TacRP_ContainerID or -1] + if !container then return end + + return container.weight + item:GetWeight() <= container.capacity +end + +function TacRP.AddToContainer(ent, item) + local container = TacRP.Containers[ent.TacRP_ContainerID or -1] + if !container then return end + if !TacRP.CanFitContainer(ent, item) then return end + + local i = table.insert(container.items, item) + container.weight = container.weight + item:GetWeight() + TacRP.SyncContainer(ent, i) +end + +function TacRP.DropFromContainer(ent, i) + local container = TacRP.Containers[ent.TacRP_ContainerID or -1] + if !container then return end + + if isnumber(i) then + local item = table.remove(container.items, i) + container.weight = container.weight - item:GetWeight() + TacRP.SyncContainer(ent, i) + else + for k, v in pairs(container.items) do + if i == v then + table.remove(container.items, k) + container.weight = container.weight - i:GetWeight() + TacRP.SyncContainer(ent, k) + return + end + end + end +end + +function TacRP.SyncContainer(con_id, i, tgt) + local con = TacRP.Containers[con_id] + net.Start("tacrp_container") + net.WriteUInt(con_id, 16) + net.WriteUInt(con.capacity, 16) + net.WriteEntity(con.entity) + if i then + local item = con.items[i] + net.WriteUInt(i, 8) + if item then + net.WriteUInt(0, TacRP.PickupItems_Bits) + else + net.WriteUInt(item.ID, TacRP.PickupItems_Bits) + item:Write() + end + else + local count = table.Count(con.items) + net.WriteUInt(0, 8) + net.WriteUInt(count, 8) + for j = 1, count do + local item = con.items[j] + net.WriteUInt(i, 8) + if item then + net.WriteUInt(0, TacRP.PickupItems_Bits) + else + net.WriteUInt(item.ID, TacRP.PickupItems_Bits) + item:Write() + end + end + end + net.Send(tgt or TacRP.ContainerAccessors(con_id)) +end + +if CLIENT then + net.Receive("tacrp_container", function() + local con_id = net.ReadUInt(16) + TacRP.Containers[con_id].capacity = net.ReadUInt(16) + TacRP.Containers[con_id].entity = net.ReadEntity() + local i = net.ReadUInt(8) + + if i == 0 then + local item_id = net.ReadUInt(TacRP.PickupItems_Bits) + if item_id == 0 then + TacRP.Containers[con_id].items[i] = nil + else + local item = TacRP.CreateItem(item_id) + item:Read() + TacRP.Containers[con_id].items[i] = item + end + else + for j = 1, net.ReadUInt(8) do + local item_id = net.ReadUInt(TacRP.PickupItems_Bits) + if item_id == 0 then + TacRP.Containers[con_id].items[j] = nil + else + local item = TacRP.CreateItem(item_id) + item:Read() + TacRP.Containers[con_id].items[j] = item + end + end + end + end) +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_effects.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_effects.lua new file mode 100644 index 0000000..4aec368 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_effects.lua @@ -0,0 +1,46 @@ + +game.AddParticles( "particles/tacrp_muzzleflashes.pcf" ) +game.AddParticles( "particles/tacrp_muzzleflashes_b.pcf" ) + +PrecacheParticleSystem( "muzzleflash_m14" ) +PrecacheParticleSystem( "muzzleflash_5" ) +PrecacheParticleSystem( "muzzleflash_6" ) +PrecacheParticleSystem( "muzzleflash_m79" ) +PrecacheParticleSystem( "muzzleflash_pistol_deagle" ) +PrecacheParticleSystem( "muzzleflash_slug" ) +PrecacheParticleSystem( "muzzleflash_mp5" ) +PrecacheParticleSystem( "muzzleflash_g3" ) +PrecacheParticleSystem( "muzzleflash_pistol_red" ) +PrecacheParticleSystem( "muzzleflash_M3" ) +PrecacheParticleSystem( "muzzleflash_suppressed" ) +PrecacheParticleSystem( "muzzleflash_4" ) +PrecacheParticleSystem( "muzzleflash_ak74" ) +PrecacheParticleSystem( "muzzleflash_ak47" ) +PrecacheParticleSystem( "muzzleflash_3" ) +PrecacheParticleSystem( "muzzleflash_1" ) +PrecacheParticleSystem( "muzzleflash_M82_tacrp" ) +PrecacheParticleSystem( "muzzleflash_M82_rico" ) +PrecacheParticleSystem( "muzzleflash_MINIMI" ) +PrecacheParticleSystem( "port_smoke_heavy" ) +PrecacheParticleSystem( "port_shellsmoke" ) +PrecacheParticleSystem( "port_smoke" ) +PrecacheParticleSystem( "muzzleflash_pistol_cleric" ) +PrecacheParticleSystem( "muzzleflash_OTS" ) +PrecacheParticleSystem( "muzzleflash_shotgun" ) +PrecacheParticleSystem( "muzzleflash_smg" ) +PrecacheParticleSystem( "muzzleflash_FAMAS" ) +PrecacheParticleSystem( "party_spark" ) +PrecacheParticleSystem( "muzzleflash_pistol" ) +PrecacheParticleSystem( "muzzleflash_slug_flame" ) +PrecacheParticleSystem( "shellsmoke" ) +PrecacheParticleSystem( "barrel_smoke" ) +PrecacheParticleSystem( "muzzleflash_svd" ) +PrecacheParticleSystem( "muzzleflash_SR25" ) +PrecacheParticleSystem( "muzzleflash_pistol_rbull" ) +PrecacheParticleSystem( "muzzleflash_m24" ) +PrecacheParticleSystem( "muzzleflash_vollmer" ) +PrecacheParticleSystem( "muzzle_heatwave" ) +PrecacheParticleSystem( "muzzle_heatwave_long" ) + +PrecacheParticleSystem( "port_smoke" ) +PrecacheParticleSystem( "shellsmoke" ) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_move.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_move.lua new file mode 100644 index 0000000..196b8a9 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_move.lua @@ -0,0 +1,384 @@ +function TacRP.CalculateMaxMoveSpeed(ply) + local wpn = ply:GetActiveWeapon() + local iscurrent = true + + local totalmult = 1 + + if ply:GetNWFloat("TacRPLastBashed", 0) + 3 > CurTime() then + local slow = TacRP.ConVars["melee_slow"]:GetFloat() + local mult = slow + if ply:GetNWFloat("TacRPLastBashed", 0) + 1.5 < CurTime() then + mult = Lerp((CurTime() - ply:GetNWFloat("TacRPLastBashed", 0) - 1.5) / (3 - 1.5), slow, 1) + end + + totalmult = totalmult * mult + end + + local stunstart, stundur = ply:GetNWFloat("TacRPStunStart", 0), ply:GetNWFloat("TacRPStunDur", 0) + if stunstart + stundur > CurTime() then + local slow = TacRP.ConVars["flash_slow"]:GetFloat() + local mult = slow + if stunstart + stundur * 0.7 < CurTime() then + mult = Lerp((CurTime() - stunstart - stundur * 0.7) / (stundur * 0.3), slow, 1) + end + + totalmult = totalmult * mult + end + + -- Remember last weapon to keep applying slowdown on shooting and melee + if !wpn.ArcticTacRP then + if !IsValid(ply.LastTacRPWeapon) or ply.LastTacRPWeapon:GetOwner() != ply then + return + else + wpn = ply.LastTacRPWeapon + iscurrent = false + end + else + ply.LastTacRPWeapon = wpn + end + + local mult = 1 * totalmult + if iscurrent and (!wpn:GetSafe() or wpn:GetIsSprinting() or wpn:ShouldLowerWeapon()) and TacRP.ConVars["penalty_move"]:GetBool() then + mult = mult * math.Clamp(wpn:GetValue("MoveSpeedMult"), 0.0001, 1) + end + + -- mult2: sighted or reloading + local mult2 = 1 + if iscurrent and wpn:GetScopeLevel() > 0 and TacRP.ConVars["penalty_aiming"]:GetBool() then + mult2 = math.Clamp(wpn:GetValue("SightedSpeedMult"), 0.0001, 1) + end + if iscurrent and TacRP.ConVars["penalty_reload"]:GetBool() then + local rsmt = wpn:GetValue("ReloadSpeedMultTime") + + if wpn:GetReloading() then + -- mult = mult * math.Clamp(wpn:GetValue("ReloadSpeedMult"), 0.0001, 1) + mult2 = math.min(mult2, math.Clamp(wpn:GetValue("ReloadSpeedMult"), 0.0001, 1)) + elseif wpn:GetReloadFinishTime() + rsmt > CurTime() then + local mt = CurTime() - wpn:GetReloadFinishTime() + local d = mt / rsmt + + d = math.Clamp(d, 0, 1) + + mult2 = math.min(mult2, Lerp(d, math.Clamp(wpn:GetValue("ReloadSpeedMult"), 0.0001, 1), 1)) + -- mult = mult * Lerp(d, math.Clamp(wpn:GetValue("ReloadSpeedMult"), 0.0001, 1), 1) + end + end + + -- mult3: shooting and melee + local mult3 = 1 + if TacRP.ConVars["penalty_firing"]:GetBool() then + local shotdelta = 0 -- how close should we be to the shoot speed mult + local rpmd = wpn:GetValue("RPM") / 900 + local fulldur = Lerp(rpmd, 1, 0.25) -- time considered "during shot". cant be just primary fire since it hurts slow guns too much + local delay = Lerp(rpmd, 0.25, 0.5) + local shottime = wpn:GetNextPrimaryFire() - (60 / wpn:GetValue("RPM")) - CurTime() + fulldur + + -- slowdown based on recoil intensity (firing longer means heavier slowdown) + if shottime > -delay then + local aftershottime = math.Clamp(1 + shottime / delay, 0, 1) + shotdelta = Lerp((wpn:GetRecoilAmount() / (wpn:GetValue("RecoilMaximum") * 0.75)) ^ 1.5, 0.25, 1) * aftershottime + end + local shootmove = math.Clamp(wpn:GetValue("ShootingSpeedMult"), 0.0001, 1) + mult3 = Lerp(shotdelta, 1, shootmove) + end + + if TacRP.ConVars["penalty_melee"]:GetBool() then + local msmt = wpn:GetValue("MeleeSpeedMultTime") + + if wpn:GetLastMeleeTime() + msmt > CurTime() then + local mt = CurTime() - wpn:GetLastMeleeTime() + local d = mt / msmt + + d = math.Clamp(d, 0, 1) ^ 4 + + mult3 = math.min(mult3, Lerp(d, math.Clamp(wpn:GetValue("MeleeSpeedMult"), 0.0001, 1), 1)) + end + end + + return mult * mult2 * mult3, iscurrent +end + +function TacRP.Move(ply, mv, cmd) + local wpn = ply:GetActiveWeapon() + + local basespd = math.min((Vector(cmd:GetForwardMove(), cmd:GetUpMove(), cmd:GetSideMove())):Length(), mv:GetMaxClientSpeed()) + + local mult, iscurrent = TacRP.CalculateMaxMoveSpeed(ply) + + if !iscurrent then return end + + mv:SetMaxSpeed(basespd * mult) + mv:SetMaxClientSpeed(basespd * mult) + + -- Semi auto click buffer + if !wpn.NoBuffer and !wpn:GetCharge() and (wpn:GetCurrentFiremode() <= 1) and mv:KeyPressed(IN_ATTACK) and !wpn:GetJammed() + and wpn:StillWaiting() and !wpn:GetReloading() and !wpn:GetCustomize() and wpn:Clip1() >= wpn:GetValue("AmmoPerShot") + and wpn:GetNextSecondaryFire() < CurTime() and wpn:GetAnimLockTime() < CurTime() and (wpn:GetNextPrimaryFire() - CurTime()) < 0.15 then + wpn:SetCharge(true) + elseif !wpn.NoBuffer and wpn:GetCharge() and !wpn:StillWaiting() and !wpn:GetJammed() then + wpn:PrimaryAttack() + end +end + +hook.Add("SetupMove", "ArcticTacRP.SetupMove", TacRP.Move) + +TacRP.LastEyeAngles = Angle(0, 0, 0) +TacRP.RecoilRise = Angle(0, 0, 0) + +local function tgt_pos(ent, head) -- From ArcCW & ARC9 + local mins, maxs = ent:WorldSpaceAABB() + local pos = ent:WorldSpaceCenter() + pos.z = pos.z + (maxs.z - mins.z) * 0.2 -- Aim at chest level + if head and ent:GetAttachment(ent:LookupAttachment("eyes")) ~= nil then + pos = ent:GetAttachment(ent:LookupAttachment("eyes")).Pos + end + return pos +end + +local tacrp_aimassist = GetConVar("tacrp_aimassist") +local tacrp_aimassist_cone = GetConVar("tacrp_aimassist_cone") +local tacrp_aimassist_head = GetConVar("tacrp_aimassist_head") +local tacrp_aimassist_intensity = GetConVar("tacrp_aimassist_intensity") + +function TacRP.StartCommand(ply, cmd) + local wpn = ply:GetActiveWeapon() + local mt_notair = ply:GetMoveType() == MOVETYPE_NOCLIP or ply:GetMoveType() == MOVETYPE_LADDER + + if !mt_notair then + if ply:IsOnGround() and !ply.TacRP_LastOnGround then + ply.TacRP_LastAirDuration = CurTime() - (ply.TacRP_LastLeaveGroundTime or 0) + ply.TacRP_LastOnGroundTime = CurTime() + elseif !ply:IsOnGround() and ply.TacRP_LastOnGround then + ply.TacRP_LastLeaveGroundTime = CurTime() + end + end + ply.TacRP_LastOnGround = ply:IsOnGround() or mt_notair + + if !wpn.ArcticTacRP then + TacRP.RecoilRise = Angle(0, 0, 0) + TacRP.LastEyeAngles = ply:EyeAngles() + return + end + + local diff = TacRP.LastEyeAngles - cmd:GetViewAngles() + local recrise = TacRP.RecoilRise + + if recrise.p > 0 then + recrise.p = math.Clamp(recrise.p, 0, recrise.p - diff.p) + elseif recrise.p < 0 then + recrise.p = math.Clamp(recrise.p, recrise.p - diff.p, 0) + end + + if recrise.y > 0 then + recrise.y = math.Clamp(recrise.y, 0, recrise.y - diff.y) + elseif recrise.y < 0 then + recrise.y = math.Clamp(recrise.y, recrise.y - diff.y, 0) + end + + recrise:Normalize() + TacRP.RecoilRise = recrise + + if wpn:GetLastRecoilTime() + wpn:RecoilDuration() > CurTime() then + local kick = wpn:GetValue("RecoilKick") + local recoildir = wpn:GetRecoilDirection() + local rec = 1 + local cfm = wpn:GetCurrentFiremode() + + if wpn:UseAltRecoil() then + rec = 1 + math.Clamp((wpn:GetRecoilAmount() - 1) / (wpn:GetValue("RecoilMaximum") - 1), 0, 1) + kick = kick + wpn:GetValue("RecoilSpreadPenalty") * wpn:GetValue("RecoilAltMultiplier") + -- local recgain = rec * wpn:GetValue("RecoilSpreadPenalty") * 250 + -- kick = kick + recgain + end + + if wpn:GetInBipod() then + kick = kick * math.min(1, wpn:GetValue("BipodKick")) + end + + if ply:Crouching() then + kick = kick * math.min(1, wpn:GetValue("RecoilMultCrouch")) + end + + if cfm < 0 then + kick = kick * wpn:GetValue("RecoilMultBurst") + elseif cfm == 1 then + kick = kick * wpn:GetValue("RecoilMultSemi") + end + + kick = kick * TacRP.ConVars["mult_recoil_kick"]:GetFloat() + + local eyeang = cmd:GetViewAngles() + local suppressfactor = 1 + if wpn:UseRecoilPatterns() and cfm != 1 then + local stab = math.Clamp(wpn:GetValue("RecoilStability"), 0, 0.9) + local max = wpn:GetBaseValue("RPM") / 60 * (0.75 + stab * 0.833) + suppressfactor = math.min(3, 1 + (wpn:GetPatternCount() / max)) + end + + + local uprec = math.sin(math.rad(recoildir)) * FrameTime() * rec * kick / suppressfactor + local siderec = math.cos(math.rad(recoildir)) * FrameTime() * rec * kick + + eyeang.p = eyeang.p + uprec + eyeang.y = eyeang.y + siderec + + recrise = TacRP.RecoilRise + + if TacRP.ConVars["freeaim"]:GetBool() and wpn:GetValue("FreeAim") and wpn:GetScopeLevel() == 0 then + local freeaimang = wpn:GetFreeAimAngle() + siderec = siderec * 0.5 + freeaimang:Add(Angle(0, siderec, 0)) + wpn:SetFreeAimAngle(freeaimang) + end + + recrise = recrise + Angle(uprec, siderec, 0) + + TacRP.RecoilRise = recrise + + cmd:SetViewAngles(eyeang) + + -- local aim_kick_v = rec * math.sin(CurTime() * 15) * FrameTime() * (1 - sightdelta) + -- local aim_kick_h = rec * math.sin(CurTime() * 12.2) * FrameTime() * (1 - sightdelta) + + -- wpn:SetFreeAimAngle(wpn:GetFreeAimAngle() - Angle(aim_kick_v, aim_kick_h, 0)) + end + + local ping = 0 + if !game.SinglePlayer() then + ping = ply:Ping() + end + if TacRP.ConVars["recoilreset"]:GetBool() + and wpn:GetLastRecoilTime() + wpn:RecoilDuration() - (ping * 0.5) < CurTime() + and wpn:GetRecoilAmount() == 0 then + + recrise = TacRP.RecoilRise + + local recreset = recrise * FrameTime() * 6 + + recrise = recrise - recreset + + recrise:Normalize() + + local eyeang = cmd:GetViewAngles() + + -- eyeang.p = math.AngleDifference(eyeang.p, recreset.p) + -- eyeang.y = math.AngleDifference(eyeang.y, recreset.y) + + eyeang = eyeang - recreset + + cmd:SetViewAngles(eyeang) + + TacRP.RecoilRise = recrise + end + + if wpn:GetInBipod() then + local bipang = wpn:GetBipodAngle() + local eyeang = cmd:GetViewAngles() + + local dy, dp = math.AngleDifference(bipang.y, eyeang.y), math.AngleDifference(bipang.p, eyeang.p) + + if dy < -60 then + eyeang.y = bipang.y + 60 + elseif dy > 60 then + eyeang.y = bipang.y - 60 + end + + if dp > 20 then + eyeang.p = bipang.p - 20 + elseif dp < -20 then + eyeang.p = bipang.p + 20 + end + + cmd:SetViewAngles(eyeang) + + if game.SinglePlayer() then + ply:SetEyeAngles(eyeang) + end + end + + TacRP.LastEyeAngles = cmd:GetViewAngles() + + if cmd:KeyDown(IN_SPEED) and ( + -- Sprint cannot interrupt a runaway burst + (!wpn:CanShootInSprint() and wpn:GetBurstCount() > 0 and wpn:GetValue("RunawayBurst")) + + -- Stunned by a flashbang and cannot sprint + or (ply:GetNWFloat("TacRPStunStart", 0) + ply:GetNWFloat("TacRPStunDur", 0) > CurTime()) + + -- Cannot reload and sprint (now sprint takes priority) + -- or (!wpn:CanReloadInSprint() and wpn:GetReloading())\ + + -- Trying to aim disables sprinting if option is set + or (wpn:GetValue("Scope") and !wpn:DoOldSchoolScopeBehavior() and (ply:KeyDown(IN_ATTACK2) or wpn:GetScopeLevel() > 0) and ply:GetInfoNum("tacrp_aim_cancels_sprint", 0) > 0 and wpn:CanStopSprinting()) + ) then + cmd:SetButtons(cmd:GetButtons() - IN_SPEED) + cmd:SetButtons(bit.bor(cmd:GetButtons(), IN_RUN)) -- Abuse unused IN_ enum + ply.TacRP_SprintBlock = true -- for some reason KeyDown(IN_SPEED) doesn't seem to see the modified buttons, so we set this + else + ply.TacRP_SprintBlock = false + end + + -- Used for sprint checking + ply.TacRP_Moving = cmd:GetForwardMove() != 0 or cmd:GetSideMove() != 0 + + -- Aim assist imported from ARC9 + if CLIENT and IsValid(wpn) then + local cone = tacrp_aimassist_cone:GetFloat() + local dist = math.min(wpn.Range_Max * 0.95, 4000) -- 4000hu is somewhat about 100m + local inte = tacrp_aimassist_intensity:GetFloat() + local head = tacrp_aimassist_head:GetBool() + + local fav = GetConVar("tacrp_freeaim") + local far = wpn:GetValue("FreeAimMaxAngle") + local swayc = GetConVar("tacrp_sway"):GetBool() + local freeac = GetConVar("tacrp_freeaim"):GetBool() + + -- Check if current target is beyond tracking cone + local tgt = ply.tacrp_AATarget + if IsValid(tgt) and (tgt_pos(tgt, head) - ply:EyePos()):Cross(ply:EyeAngles():Forward()):Length() > cone * 2 then ply.tacrp_AATarget = nil end -- lost track + + -- Try to seek target if not exists + tgt = ply.tacrp_AATarget + if !IsValid(tgt) or (tgt.Health and tgt:Health() <= 0) or util.QuickTrace(ply:EyePos(), tgt_pos(tgt, head) - ply:EyePos(), ply).Entity ~= tgt then + local min_diff + ply.tacrp_AATarget = nil + -- for _, ent in ipairs(ents.FindInCone(ply:EyePos(), ply:EyeAngles():Forward(), 244, math.cos(math.rad(cone)))) do + for _, ent in ipairs(ents.FindInCone(ply:EyePos(), ply:EyeAngles():Forward(), dist, math.cos(math.rad(cone + (fav:GetBool() and far or 0))))) do + if ent == ply or (!ent:IsNPC() and !ent:IsNextBot() and !ent:IsPlayer()) or ent:Health() <= 0 + or (ent:IsPlayer() and ent:Team() ~= TEAM_UNASSIGNED and ent:Team() == ply:Team()) then continue end + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = tgt_pos(ent, head), + mask = MASK_SHOT, + filter = ply + }) + if tr.Entity ~= ent then continue end + local diff = (tgt_pos(ent, head) - ply:EyePos()):Cross(ply:EyeAngles():Forward()):Length() + if !ply.tacrp_AATarget or diff < min_diff then + ply.tacrp_AATarget = ent + min_diff = diff + end + end + end + + -- Aim towards target + tgt = ply.tacrp_AATarget + if tacrp_aimassist:GetBool() and ply:GetInfoNum("tacrp_aimassist_cl", 0) == 1 then + if IsValid(tgt) and !wpn:GetCustomize() then + if !wpn.NoAimAssist then + local ang = cmd:GetViewAngles() + local pos = tgt_pos(tgt, head) + local tgt_ang = (pos - ply:EyePos()):Angle() - ((swayc and wpn:GetSwayAngles()) or angle_zero) - ((freeac and wpn:GetFreeAimAngle()) or angle_zero) + local ang_diff = (pos - ply:EyePos()):Cross(ply:EyeAngles():Forward()):Length() + if ang_diff > 0.1 then + ang = LerpAngle(math.Clamp(inte / ang_diff, 0, 0.01), ang, tgt_ang) + cmd:SetViewAngles(ang) + end + end + end + end + end + +end + +hook.Add("StartCommand", "TacRP_StartCommand", TacRP.StartCommand) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_npc.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_npc.lua new file mode 100644 index 0000000..b86268d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_npc.lua @@ -0,0 +1,131 @@ +hook.Add("InitPostEntity", "TacRP_Register", function() + for _, wpn in pairs(weapons.GetList()) do + local tbl = weapons.Get(wpn.ClassName) + + if !tbl.ArcticTacRP or !tbl.NPCUsable or !tbl.Spawnable then continue end + + list.Add("NPCUsableWeapons", + { + class = wpn.ClassName, + title = wpn.PrintName + } + ) + end +end) + +if CLIENT then + local no_tiered_random = { + ["6Launcher"] = true, + ["9Special"] = true, + ["8Melee Weapon"] = true, + ["7Special Weapon"] = true, + ["9Equipment"] = true, + ["9Throwable"] = true, + } + hook.Add("PopulateMenuBar", "TacRP_NPCWeaponMenu", function (menubar) + timer.Simple(0.1, function() + local wpns = menubar:AddOrGetMenu("TacRP NPC Weapons") + + wpns:AddCVar( "#menubar.npcs.defaultweapon", "gmod_npcweapon", "" ) + wpns:AddCVar( "#menubar.npcs.noweapon", "gmod_npcweapon", "none" ) + + local random = wpns:AddSubMenu("Random...") + random:SetDeleteSelf(false) + + random:AddCVar("[Any TacRP Weapon]", "gmod_npcweapon", "!tacrp|npc|") + random:AddCVar("[Value Tier]", "gmod_npcweapon", "!tacrp|npc|5Value") + random:AddCVar("[Consumer Tier]", "gmod_npcweapon", "!tacrp|npc|4Consumer") + random:AddCVar("[Security Tier]", "gmod_npcweapon", "!tacrp|npc|3Security") + random:AddCVar("[Operator Tier]", "gmod_npcweapon", "!tacrp|npc|2Operator") + random:AddCVar("[Elite Tier]", "gmod_npcweapon", "!tacrp|npc|1Elite") + + wpns:AddSpacer() + + wpns:SetDeleteSelf(false) + + local weaponlist = weapons.GetList() + + local catdict = {} + local catnames = {} + local catcontents = {} + + -- table.SortByMember(weaponlist, "PrintName", true) + + local cats = {} + + for _, k in pairs(weaponlist) do + local weptbl = weapons.Get(k.ClassName) + if weptbl and weptbl.ArcticTacRP and weptbl.Spawnable + and weptbl.NPCUsable and !weptbl.PrimaryMelee and !weptbl.PrimaryGrenade and weptbl.SubCatType then + local cat = k.SubCatType + if !catdict[cat] then + catdict[cat] = true + table.insert(catnames, cat) + end + catcontents[cat] = catcontents[cat] or {} + table.insert(catcontents[cat], {k.PrintName, k.ClassName}) + end + end + + for _, cat in SortedPairsByValue(catnames) do + cats[cat] = wpns:AddSubMenu(string.sub(cat, 2)) + cats[cat]:SetDeleteSelf(false) + + cats[cat]:AddCVar("[Random]", "gmod_npcweapon", "!tacrp|" .. cat .. "|") + if !no_tiered_random[cat] then + cats[cat]:AddCVar("[Value Tier]", "gmod_npcweapon", "!tacrp|" .. cat .. "|5Value") + cats[cat]:AddCVar("[Consumer Tier]", "gmod_npcweapon", "!tacrp|" .. cat .. "|4Consumer") + cats[cat]:AddCVar("[Security Tier]", "gmod_npcweapon", "!tacrp|" .. cat .. "|3Security") + cats[cat]:AddCVar("[Operator Tier]", "gmod_npcweapon", "!tacrp|" .. cat .. "|2Operator") + cats[cat]:AddCVar("[Elite Tier]", "gmod_npcweapon", "!tacrp|" .. cat .. "|1Elite") + end + cats[cat]:AddSpacer() + + for _, info in SortedPairsByMemberValue(catcontents[cat], 1) do + cats[cat]:AddCVar(info[1], "gmod_npcweapon", info[2]) + end + end + end) + end) + + net.Receive("tacrp_npcweapon", function(len, ply) + local class = GetConVar("gmod_npcweapon"):GetString() + + net.Start("tacrp_npcweapon") + net.WriteString(class) + net.SendToServer() + end) +elseif SERVER then + hook.Add("PlayerSpawnedNPC", "TacRP_NPCWeapon", function(ply, ent) + net.Start("tacrp_npcweapon") + net.Send(ply) + + ply.TacRP_LastSpawnedNPC = ent + end) + + net.Receive("tacrp_npcweapon", function(len, ply) + local class = net.ReadString() + local ent = ply.TacRP_LastSpawnedNPC + + if !IsValid(ent) or !ent:IsNPC() or (class or "") == "" then return end + + local cap = ent:CapabilitiesGet() + if bit.band(cap, CAP_USE_WEAPONS) != CAP_USE_WEAPONS then return end + + local wpn + if string.Left(class, 6) == "!tacrp" then + local args = string.Explode("|", class, false) + class = TacRP.GetRandomWeapon(args[2], args[3]) + wpn = weapons.Get(class or "") + if !class or !wpn then return end + else + wpn = weapons.Get(class) + end + + if !wpn or (wpn.AdminOnly and !ply:IsPlayer()) then return end + + if wpn.ArcticTacRP and wpn.NPCUsable and wpn.Spawnable and (!wpn.AdminOnly or ply:IsAdmin()) then + ent:Give(class) + end + end) +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_physbullet.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_physbullet.lua new file mode 100644 index 0000000..45f7a13 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_physbullet.lua @@ -0,0 +1,516 @@ +TacRP.PhysBullets = { +} + +function TacRP:SendBullet(bullet, attacker) + net.Start("TacRP_sendbullet", true) + net.WriteVector(bullet.Pos) + net.WriteAngle(bullet.Vel:Angle()) + net.WriteFloat(bullet.Vel:Length()) + net.WriteFloat(bullet.Drag) + net.WriteFloat(bullet.Gravity) + net.WriteEntity(bullet.Weapon) + net.WriteUInt(bullet.HullSize, 4) + + if attacker and attacker:IsValid() and attacker:IsPlayer() and !game.SinglePlayer() then + net.SendOmit(attacker) + else + if game.SinglePlayer() then + net.WriteEntity(attacker) + end + net.Broadcast() + end +end + +function TacRP:ShootPhysBullet(wep, pos, vel, tbl) + tbl = tbl or {} + local bullet = { + Penleft = wep:GetValue("Penetration"), + Gravity = 1, + Pos = pos, + StartPos = Vector(pos), + Vel = vel, + Drag = 1, + Travelled = 0, + StartTime = CurTime(), + Imaginary = false, + Underwater = false, + Weapon = wep, + Attacker = wep:GetOwner(), + Filter = {wep:GetOwner()}, + Damaged = {}, + Dead = false, + NPC = wep:GetOwner():IsNPC(), + -- HullSize = wep:IsShotgun() and 2 or 0, + } + + if wep:GetValue("TracerNum") == 0 then + bullet.Invisible = true + else + if wep:Clip1() % wep:GetValue("TracerNum") != 0 then + bullet.Invisible = true + end + end + + for i, k in pairs(tbl) do + bullet[i] = k + end + + if bit.band( util.PointContents( pos ), CONTENTS_WATER ) == CONTENTS_WATER then + bullet.Underwater = true + end + + local add = game.SinglePlayer() or !TacRP.ConVars["client_damage"]:GetBool() or CLIENT + if add then + table.insert(TacRP.PhysBullets, bullet) + end + + --[[] + if wep:GetOwner():IsPlayer() and SERVER then + local ping = wep:GetOwner():Ping() / 1000 + ping = math.Clamp(ping, 0, 0.5) + local timestep = engine.TickInterval() + + while ping > 0 do + TacRP:ProgressPhysBullet(bullet, math.min(timestep, ping)) + ping = ping - timestep + end + end + ]] + if wep:GetOwner():IsPlayer() and SERVER and add then + local latency = math.floor(engine.TickCount() - wep:GetOwner():GetCurrentCommand():TickCount() - 1) -- FIXME: this math.floor does nothing + local timestep = engine.TickInterval() + while latency > 0 do + TacRP:ProgressPhysBullet(bullet, timestep) + latency = latency - 1 + end + end + + if SERVER then + TacRP:SendBullet(bullet, wep:GetOwner()) + end +end + +if CLIENT then + +net.Receive("TacRP_sendbullet", function(len, ply) + local pos = net.ReadVector() + local ang = net.ReadAngle() + local vel = net.ReadFloat() + local drag = net.ReadFloat() + local grav = net.ReadFloat() + local weapon = net.ReadEntity() + local hullsize = net.ReadUInt(4) + + if game.SinglePlayer() then + ent = net.ReadEntity() + end + + if !weapon:IsValid() or !weapon.GetValue then return end + + local bullet = { + Pos = pos, + StartPos = pos, + Vel = ang:Forward() * vel, + Travelled = 0, + StartTime = CurTime(), + Imaginary = false, + Underwater = false, + Dead = false, + Damaged = {}, + Drag = drag, + Gravity = grav, + Weapon = weapon, + Filter = {weapon:GetOwner()}, + HullSize = hullsize --weapon:IsShotgun() and TacRP.ShotgunHullSize or 0, + } + + if weapon:GetValue("TracerNum") == 0 then + bullet.Invisible = true + else + if weapon:Clip1() % weapon:GetValue("TracerNum") != 0 then + bullet.Invisible = true + end + end + + if bit.band( util.PointContents( pos ), CONTENTS_WATER ) == CONTENTS_WATER then + bullet.Underwater = true + end + + table.insert(TacRP.PhysBullets, bullet) +end) + +end + +function TacRP:DoPhysBullets() + local new = {} + for _, i in pairs(TacRP.PhysBullets) do + TacRP:ProgressPhysBullet(i, FrameTime()) + + if !i.Dead then + table.insert(new, i) + end + end + + TacRP.PhysBullets = new +end + +hook.Add("Think", "TacRP_DoPhysBullets", TacRP.DoPhysBullets) + +local function indim(vec, maxdim) + if math.abs(vec.x) > maxdim or math.abs(vec.y) > maxdim or math.abs(vec.z) > maxdim then + return false + else + return true + end +end + +function TacRP:ProgressPhysBullet(bullet, timestep) + timestep = timestep or FrameTime() + + if bullet.Dead then return end + + local oldpos = bullet.Pos + local oldvel = bullet.Vel + local dir = bullet.Vel:GetNormalized() + local spd = bullet.Vel:Length() * timestep + local drag = bullet.Drag * spd * spd * (1 / 150000) + local gravity = timestep * (bullet.Gravity or 1) * 600 + local first = math.abs(bullet.StartTime - CurTime()) <= 0.001 + + local attacker = bullet.Attacker or NULL + local weapon = bullet.Weapon + + if SERVER and !IsValid(attacker) then + bullet.Dead = true + return + elseif CLIENT and !IsValid(attacker) then + attacker = game.GetWorld() + end + + if !IsValid(weapon) then + bullet.Dead = true + return + end + + if bullet.Underwater then + drag = drag * 3 + end + + if spd <= 0.001 then bullet.Dead = true return end + + local newpos = oldpos + (oldvel * timestep) + local newvel = oldvel - (dir * drag) + newvel = newvel - (Vector(0, 0, 1) * gravity) + + if bullet.Imaginary then + -- the bullet has exited the map, but will continue being visible. + bullet.Pos = newpos + bullet.Vel = newvel + bullet.Travelled = bullet.Travelled + spd + else + if !first and attacker:IsPlayer() then + attacker:LagCompensation(true) + end + + local tr + + if (bullet.HullSize or 0) > 0 then + local hs = bullet.HullSize / 2 + tr = util.TraceHull({ + start = oldpos, + endpos = newpos, + filter = bullet.Filter, + mask = MASK_SHOT, + mins = Vector(-hs, -hs, -hs), + maxs = Vector(hs, hs, hs), + }) + else + tr = util.TraceLine({ + start = oldpos, + endpos = newpos, + filter = bullet.Filter, + mask = MASK_SHOT + }) + end + + if !first and attacker:IsPlayer() then + attacker:LagCompensation(false) + end + + if TacRP.Developer(2) then + if SERVER then + debugoverlay.Line(oldpos, tr.HitPos, 5, Color(100,100,255), true) + else + debugoverlay.Line(oldpos, tr.HitPos, 5, Color(255,200,100), true) + end + end + + if tr.HitSky then + if CLIENT then + bullet.Imaginary = true + else + bullet.Dead = true + end + + bullet.Pos = newpos + bullet.Vel = newvel + bullet.Travelled = bullet.Travelled + spd + + if SERVER then + bullet.Dead = true + end + elseif tr.Hit then + bullet.Travelled = bullet.Travelled + (oldpos - tr.HitPos):Length() + bullet.Pos = tr.HitPos + -- if we're the client, we'll get the bullet back when it exits. + + if !first and attacker:IsPlayer() then + attacker:LagCompensation(true) + end + + if SERVER then + debugoverlay.Cross(tr.HitPos, 5, 5, Color(100,100,255), true) + else + debugoverlay.Cross(tr.HitPos, 5, 5, Color(255,200,100), true) + end + + local eid = tr.Entity:EntIndex() + + if CLIENT then + -- do an impact effect and forget about it + if !game.SinglePlayer() then + attacker:FireBullets({ + Src = oldpos, + Dir = dir, + Distance = spd + 16, + Tracer = 0, + Damage = 0, + IgnoreEntity = attacker, + HullSize = weapon:IsShotgun() and 2 or 0, + Callback = function(att, btr, dmg) + if TacRP.ConVars["client_damage"]:GetBool() then + net.Start("tacrp_clientdamage") + net.WriteEntity(weapon) + net.WriteEntity(btr.Entity) + net.WriteVector(dir) + net.WriteVector(btr.Entity:WorldToLocal(btr.HitPos)) + net.WriteUInt(btr.HitGroup, 8) + net.WriteFloat(bullet.Travelled or 0) + net.WriteFloat(bullet.Penleft or 0) + net.WriteUInt(#bullet.Damaged, 4) + for i = 1, #bullet.Damaged do net.WriteEntity(bullet.Damaged[i]) end + net.SendToServer() + end + end + }) + end + bullet.Dead = true + return + elseif SERVER then + bullet.Damaged[eid] = true + bullet.Dead = true + if game.SinglePlayer() or !TacRP.ConVars["client_damage"]:GetBool() then + bullet.Attacker:FireBullets({ + Damage = weapon:GetValue("Damage_Max"), + Force = 8, + Tracer = 0, + Num = 1, + Dir = bullet.Vel:GetNormalized(), + Src = oldpos, + Spread = Vector(0, 0, 0), + HullSize = weapon:IsShotgun() and 2 or 0, + Callback = function(att, btr, dmg) + local range = bullet.Travelled + if !IsValid(weapon) then return end + weapon:AfterShotFunction(btr, dmg, range, bullet.Penleft, bullet.Damaged) + end + }) + end + end + + if !first and attacker:IsPlayer() then + attacker:LagCompensation(false) + end + else + -- bullet did not impact anything + -- break glass in the way + -- attacker:FireBullets({ + -- Src = oldpos, + -- Dir = dir, + -- Distance = spd, + -- Tracer = 0, + -- Damage = 0, + -- IgnoreEntity = bullet.Attacker + -- }) + + bullet.Pos = tr.HitPos + bullet.Vel = newvel + bullet.Travelled = bullet.Travelled + spd + + if bullet.Underwater then + if bit.band( util.PointContents( tr.HitPos ), CONTENTS_WATER ) != CONTENTS_WATER then + local utr = util.TraceLine({ + start = tr.HitPos, + endpos = oldpos, + filter = bullet.Attacker, + mask = MASK_WATER + }) + + if utr.Hit then + local fx = EffectData() + fx:SetOrigin(utr.HitPos) + fx:SetScale(5) + fx:SetFlags(0) + util.Effect("gunshotsplash", fx) + end + + bullet.Underwater = false + end + else + if bit.band( util.PointContents( tr.HitPos ), CONTENTS_WATER ) == CONTENTS_WATER then + local utr = util.TraceLine({ + start = oldpos, + endpos = tr.HitPos, + filter = bullet.Attacker, + mask = MASK_WATER + }) + + if utr.Hit then + local fx = EffectData() + fx:SetOrigin(utr.HitPos) + fx:SetScale(5) + fx:SetFlags(0) + util.Effect("gunshotsplash", fx) + end + + bullet.Underwater = true + end + end + end + end + + local MaxDimensions = 16384 * 4 + local WorldDimensions = 16384 + + if bullet.StartTime <= (CurTime() - 10) then + bullet.Dead = true + elseif !indim(bullet.Pos, MaxDimensions) then + bullet.Dead = true + elseif !indim(bullet.Pos, WorldDimensions) then + bullet.Imaginary = true + end +end + +local head = Material("particle/fire") +local tracer = Material("tacrp/tracer") + +function TacRP:DrawPhysBullets() + cam.Start3D() + for _, i in pairs(TacRP.PhysBullets) do + if i.Invisible then continue end + --if i.Travelled <= 1024 then continue end + local pos = i.Pos + + local speedvec = -i.Vel:GetNormalized() + local vec = speedvec + local shoulddraw = true + + if IsValid(i.Weapon) then + local fromvec = (i.Weapon:GetTracerOrigin() - pos):GetNormalized() + + local d = math.min(i.Travelled / 1024, 1) + if i.Indirect then + d = 1 + end + + vec = LerpVector(d, fromvec, speedvec) + end + + if !shoulddraw then continue end + + local size = math.Clamp(math.log(EyePos():DistToSqr(pos) - math.pow(512, 2)), 0, math.huge) + + local vel = i.Vel - LocalPlayer():GetVelocity() + + local dot = math.abs(EyeAngles():Forward():Dot(vel:GetNormalized())) + -- dot = math.Clamp(((dot * dot) - 0.25) * 5, 0, 1) + local headsize = size * dot * 2 -- * math.min(EyePos():DistToSqr(pos) / math.pow(2500, 2), 1) + + local col = i.Color or Color(255, 225, 200) + + render.SetMaterial(head) + render.DrawSprite(pos, headsize, headsize, col) + + render.SetMaterial(tracer) + + local tail = (vec:GetNormalized() * math.min(vel:Length() / 25, 512, i.Travelled - 64)) + + render.DrawBeam(pos, pos + tail, size * 0.75, 0, 1, col) + + end + cam.End3D() +end + +hook.Add("PreDrawEffects", "TacRP_DrawPhysBullets", TacRP.DrawPhysBullets) + +hook.Add("PostCleanupMap", "TacRP_CleanPhysBullets", function() + TacRP.PhysBullets = {} +end) + +if SERVER then + net.Receive("tacrp_clientdamage", function(len, ply) + local weapon = net.ReadEntity() + local tgt = net.ReadEntity() + if !IsValid(tgt) then return end + local dir = net.ReadVector() + local hitpos = tgt:LocalToWorld(net.ReadVector()) + local hitgroup = net.ReadUInt(8) + local range = net.ReadFloat() + local penleft = net.ReadFloat() + local count = net.ReadUInt(3) + local damaged = {} + for i = 1, count do + table.insert(damaged, net.ReadEntity()) + end + + if !ply:Alive() or !IsValid(weapon) or weapon:GetOwner() != ply then return end + -- if math.abs(ply:GetPos():DistToSqr(hitpos) - ply:GetPos():DistToSqr(tgt:GetPos())) > 64 * 64 then return end + + local suppress = !(tgt:IsNPC() or tgt:IsNextBot()) + if suppress then + SuppressHostEvents(ply) + end + + local dmg = DamageInfo() + dmg:SetAttacker(ply) + dmg:SetInflictor(ply) + dmg:SetDamagePosition(hitpos) + dmg:SetDamageType(DMG_BULLET) -- FireBullet attacks do DMG_BULLET by default + local btr = util.TraceLine({ + start = hitpos - dir * 2, + endpos = hitpos, + mask = MASK_SHOT, + }) + btr.Entity = tgt + btr.HitGroup = hitgroup + weapon:AfterShotFunction(btr, dmg, range, penleft, damaged, true) + tgt:DispatchTraceAttack(dmg, btr, dir) + + -- ply:FireBullets({ + -- Damage = weapon:GetValue("Damage_Max"), + -- Force = 8, + -- Tracer = 0, + -- Num = 1, + -- Dir = dir * 2, + -- Src = hitpos - dir * 1, + -- Spread = Vector(0, 0, 0), + -- Callback = function(att, btr, dmg) + -- debugoverlay.Line(btr.StartPos, btr.HitPos, 3, btr.Entity == tgt and Color(0, 255, 0) or Color(255, 255, 0)) + -- weapon:AfterShotFunction(btr, dmg, range, penleft, damaged, true) + -- end + -- }) + if suppress then + SuppressHostEvents() + end + end) +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_quicknade.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_quicknade.lua new file mode 100644 index 0000000..3e94a28 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_quicknade.lua @@ -0,0 +1,342 @@ +TacRP.QuickNades = { + ["frag"] = { + PrintName = "FRAG", + GrenadeEnt = "tacrp_proj_nade_frag", + GrenadeWep = "tacrp_nade_frag", + ThrowForce = 1000, + Ammo = "grenade", + AmmoEnt = "tacrp_ammo_frag", + Model = "models/weapons/tacint/v_quicknade_frag.mdl", + Spoon = true, + Icon = Material("TacRP/grenades/frag.png", "mips smooth"), + -- Texture = "tacrp/grenades/frag", + CanSetImpact = true, + + FullName = "Frag Grenade", + DetType = "Timed - 2 seconds", + Description = "Standard explosive grenade spraying shrapnel in a medium radius.\n\nTypically not lethal by itself, but can wound targets or flush them out of cover.", + Category = "LETHAL", + SortOrder = 1, + + }, + ["flashbang"] = { + PrintName = "FLASH", + GrenadeEnt = "tacrp_proj_nade_flashbang", + GrenadeWep = "tacrp_nade_flashbang", + ThrowForce = 1000, + Ammo = "ti_flashbang", + AmmoEnt = "tacrp_ammo_flashbang", + Model = "models/weapons/tacint/v_quicknade_flashbang.mdl", + Spoon = true, + Icon = Material("TacRP/grenades/flashbang.png", "mips smooth"), + Texture = "tacrp/grenades/flashbang", + CanSetImpact = true, + + FullName = "Flashbang", + DetType = "Timed - 1.5 seconds", + Description = "Emits a bright flash and deafening bang that disorients targets (hence its name).\n\nSlows affected targets, and deals minor damage in a large radius.", + Category = "UTILITY", + SortOrder = 4, + }, + ["smoke"] = { + PrintName = "SMOKE", + GrenadeEnt = "tacrp_proj_nade_smoke", + GrenadeWep = "tacrp_nade_smoke", + ThrowForce = 1000, + Ammo = "ti_smoke", + AmmoEnt = "tacrp_ammo_smoke", + Model = "models/weapons/tacint/v_quicknade_smoke.mdl", + Spoon = true, + Icon = Material("TacRP/grenades/smoke.png", "mips smooth"), + Texture = "tacrp/grenades/smoke", + CanSetImpact = true, + + FullName = "Smoke Grenade", + DetType = "Timed - 2 seconds", + Description = "Emits a concealing cloud of smoke that lasts about 20 seconds.\n\nDeals no damage whatsoever, and is commonly used to cover an advance or to obscure a line of sight.", + Category = "UTILITY", + SortOrder = 5, + }, + ["gas"] = { + PrintName = "GAS", + GrenadeEnt = "tacrp_proj_nade_gas", + GrenadeWep = "tacrp_nade_gas", + ThrowForce = 1000, + Ammo = "ti_gas", + AmmoEnt = "tacrp_ammo_gas", + Model = "models/weapons/tacint/v_quicknade_smoke.mdl", + Spoon = true, + Material = "models/tacint/weapons/v_models/smoke/gas-1", + Icon = Material("TacRP/grenades/gas.png", "mips smooth"), + Texture = "tacrp/grenades/gas", + CanSetImpact = true, + + FullName = "CS Gas Grenade", + DetType = "Timed - 2 seconds", + Description = "Emits a cloud of tear gas that lasts about 15 seconds.\n\nAnyone caught within will take non-lethal lingering damage and have trouble keeping their weapon steady.\n\nIt is a chemical weapon banned by the Geneva Convention and is ABSOLUTELY NOT FART GAS.", + Category = "UTILITY", + SortOrder = 6, + }, + ["thermite"] = { + PrintName = "FIRE", + GrenadeEnt = "tacrp_proj_nade_thermite", + GrenadeWep = "tacrp_nade_thermite", + ThrowForce = 1000, + Ammo = "ti_thermite", + AmmoEnt = "tacrp_ammo_fire", + Model = "models/weapons/tacint/v_quicknade_smoke.mdl", + Spoon = true, + Material = "models/tacint/weapons/v_models/smoke/thermite-1", + Icon = Material("TacRP/grenades/thermite.png", "mips smooth"), + Texture = "tacrp/grenades/thermite", + CanSetImpact = true, + + FullName = "Thermite Grenade", + DetType = "Timed - 3 seconds", + Description = "Sticks to targets and burns intensely for about 8 seconds, dealing damage within a small radius.\n\nWhile thermite is typically used to burn through materiel, it is also useful for area denial.", + Category = "LETHAL", + SortOrder = 2, + }, + ["c4"] = { + PrintName = "C4", + GrenadeEnt = "tacrp_proj_nade_c4", + ThrowForce = 2000, + ThrowSpeed = 0.75, + Ammo = "ti_c4", + AmmoEnt = "tacrp_ammo_c4", + Model = "models/weapons/tacint/v_quicknade_c4.mdl", + OverhandOnly = true, + Spoon = false, + Secret = true, + SecretWeapon = "tacrp_c4_detonator", + Icon = Material("TacRP/grenades/c4.png", "mips smooth"), + Texture = "tacrp/grenades/c4", + + FullName = "C4 Charge", + DetType = "Remote", + Description = "A brick of powerful explosives that can be touched off by a detonator remotely.\n\nC4 is remarkably inert, but the signalling device can be removed or destroyed, defusing the charge.", + Category = "SPECIAL", + SortOrder = 8, + }, + ["nuke"] = { + PrintName = "NUKE", + GrenadeEnt = "tacrp_proj_nade_nuke", + ThrowForce = 200, + ThrowSpeed = 0.6, + Ammo = "ti_nuke", + AmmoEnt = "tacrp_ammo_nuke", + Model = "models/weapons/tacint/v_quicknade_nuke.mdl", + Spoon = false, + Secret = true, + AdminOnly = true, + Icon = Material("TacRP/grenades/nuke.png", "mips smooth"), + Texture = "tacrp/grenades/nuke", + + NoSounds = false, + PullSound = "tacrp/weapons/grenade/deploy-1.wav", + + FullName = "Nuclear Device", + DetType = "Remote", + Description = "Briefcase-sized micro nuclear bomb that can be touched off by a detonator remotely.\n\nIts explosive outcome needs no description.", + Category = "SPECIAL", + SortOrder = 9, + }, + ["charge"] = { + PrintName = "BREACH", + GrenadeEnt = "tacrp_proj_nade_charge", + GrenadeWep = "tacrp_nade_charge", + ThrowForce = 2000, + ThrowSpeed = 0.75, + Ammo = "ti_charge", + AmmoEnt = "tacrp_ammo_charge", + Model = "models/weapons/tacint/v_quicknade_door_charge.mdl", + OverhandOnly = true, + Spoon = false, + Icon = Material("TacRP/grenades/breach.png", "mips smooth"), + Texture = "tacrp/grenades/breach", + + FullName = "Breaching Charge", + DetType = "Timed - 2 seconds OR Remote", + Description = "Shaped charge made to bust through doors and weak walls.\n\nSmall blast radius, but will destroy any door it is attached to and hurt targets on the other side with its shockwave.\n\nWhen holding a detonator, the charge is configured to detonate remotely.", + Category = "LETHAL", + SortOrder = 3, + }, + ["heal"] = { + PrintName = "HEAL", + GrenadeEnt = "tacrp_proj_nade_heal", + GrenadeWep = "tacrp_nade_heal", + ThrowForce = 1000, + Ammo = "ti_heal", + AmmoEnt = "tacrp_ammo_heal", + Model = "models/weapons/tacint/v_quicknade_smoke.mdl", + Material = "models/tacint/weapons/v_models/smoke/heal-1", + Spoon = true, + Icon = Material("TacRP/grenades/heal.png", "mips smooth"), + Texture = "tacrp/grenades/smoke", + CanSetImpact = true, + + FullName = "Medi-Smoke Can", + DetType = "Timed - 5 seconds", + Description = "Emits a cloud of restorative gas for about 15 seconds.\n\nMedical nanites restores health when inhaled. If recipients are healthy, any armor they have can be repaired or recharged.\n\nHas the opposite effect on necrotics, dealing damage instead.", + Category = "UTILITY", + SortOrder = 5, + }, + ["rock"] = { + PrintName = "ROCK", + GrenadeEnt = "tacrp_proj_nade_rock", + ThrowForce = 3000, + ThrowSpeed = 1.1, + Ammo = nil, + UnderhandSpecial = true, + Model = "models/weapons/tacint_extras/v_quicknade_rock.mdl", + Spoon = false, + NoSounds = true, + Icon = Material("TacRP/grenades/rock.png", "mips smooth"), + Texture = "tacrp/grenades/rock", + RequireStat = "ThrowRocks", + + FullName = "Rock", + DetType = "Blunt Trauma", + Description = "Possibly the first weapon ever used by humans.\n\nUse as last resort, for ancient capital punishments, or for violent pranks.\n\nResourceful as you are, there's no telling what else you can pull out of your pants in a pinch...", + Category = "SPECIAL", + SortOrder = 7, + }, + + ["dz_bumpmine"] = { + PrintName = "BUMP", + GrenadeEnt = "dz_proj_bumpmine", + GrenadeWep = "weapon_dz_bumpmine", + ThrowForce = 2000, + ThrowSpeed = 0.9, + Ammo = "dz_bumpmine", + Model = "models/weapons/dz_ents/c_bumpmine.mdl", + Spoon = false, + Icon = Material("entities/weapon_dz_bumpmine.png", "mips smooth"), + OverhandOnly = true, + Secret = true, + SecretWeapon = "weapon_dz_bumpmine", + NoSounds = false, + PullSound = "DZ_Ents.HEGrenade.Draw", + ThrowSound = "DZ_Ents.BumpMine.Throw", + + FullName = "Bump Mine", + DetType = "Pressure Plate", + Description = "Magnetized mine that sticks to surfaces.\n\nCreates a harmless explosion that pushes everything away, hurting targets by sending them into walls or floors.\n\nCan be used to launch yourself very far, but remember to bring a parachute.", + Category = "Danger Zone", + SortOrder = 8, + }, + + ["ttt_smoke"] = { + PrintName = "T-SMK", + GrenadeEnt = "ttt_smokegrenade_proj", + GrenadeWep = "weapon_ttt_smokegrenade", + ThrowForce = 4000, + Model = "models/weapons/tacint/v_quicknade_smoke.mdl", + Spoon = false, + Icon = Material("TacRP/grenades/smoke.png", "mips smooth"), + Texture = "tacrp/grenades/smoke", + + Singleton = true, + TTTTimer = 2, + + FullName = "Smoke Grenade", + DetType = "Timed - 2 seconds", + Description = "Terrorist issue smoke grenade.\n\nCreates a smokescreen.", + Category = "TTT", + SortOrder = 0, + }, + ["ttt_conf"] = { + PrintName = "T-DCB", + GrenadeEnt = "ttt_confgrenade_proj", + GrenadeWep = "weapon_ttt_confgrenade", + ThrowForce = 4000, + Model = "models/weapons/tacint/v_quicknade_frag.mdl", + Spoon = false, + Icon = Material("TacRP/grenades/frag.png", "mips smooth"), + Texture = "tacrp/grenades/frag", + + Singleton = true, + TTTTimer = 3, + + FullName = "Discombobulator", + DetType = "Timed - 3 seconds", + Description = "Terrorist issue concussion grenade.\n\nDoes no damage, but creates a blast that pulls props in and pulls players out.", + Category = "TTT", + SortOrder = 0, + }, + ["ttt_fire"] = { + PrintName = "T-INC", + GrenadeEnt = "ttt_firegrenade_proj", + GrenadeWep = "weapon_zm_molotov", + ThrowForce = 4000, + Model = "models/weapons/tacint/v_quicknade_smoke.mdl", + Material = "models/tacint/weapons/v_models/smoke/thermite-1", + Spoon = false, + Icon = Material("TacRP/grenades/thermite.png", "mips smooth"), + Texture = "tacrp/grenades/thermite", + + Singleton = true, + TTTTimer = 3, + + FullName = "Incendiary Grenade", + DetType = "Timed - 3 seconds", + Description = "Terrorist issue incendiary grenade.\n\nExplodes with minor damage, and starts fires in an area.", + Category = "TTT", + SortOrder = 0, + }, +} + +TacRP.QuickNades_Index = {} +TacRP.QuickNades_EntLookup = {} + +TacRP.QuickNades_Count = 0 + +local function reIndexQuickNades() + for i, k in SortedPairsByMemberValue(TacRP.QuickNades, "SortOrder") do + TacRP.QuickNades_Count = TacRP.QuickNades_Count + 1 + + TacRP.QuickNades_Index[TacRP.QuickNades_Count] = i + TacRP.QuickNades_EntLookup[k.GrenadeEnt] = i + k.Index = TacRP.QuickNades_Count + end + TacRP.QuickNades_Bits = math.min(math.ceil(math.log(TacRP.QuickNades_Count + 1, 2)), 32) +end + +hook.Add("InitPostEntity", "TacRP_IndexQuickNades", function() + reIndexQuickNades() +end) + +function TacRP.IsGrenadeInfiniteAmmo(i) + local nade = i + if isstring(i) then + nade = TacRP.QuickNades[i] + elseif isnumber(i) then + nade = TacRP.QuickNades[TacRP.QuickNades_Index[i]] + end + + if !istable(nade) then return false end + + -- ttt grenades do not use ammo + if nade.Singleton then return false end + + -- no ammo type means infinite ammo + if !nade.Ammo then return true end + + -- non-admin nades are affected by infinite grenades cvar + if !nade.AdminOnly and TacRP.ConVars["infinitegrenades"]:GetBool() then return true end + + return false +end + +function TacRP.RegisterQuickNade(name, nade) + if TacRP.QuickNades[name] then + print("TacRP: QuickNade " .. name .. " already exists! Overwriting.") + end + + if !istable(nade) then + print("TacRP: QuickNade " .. name .. " is not a table! Aborting registration.") + return + end + + TacRP.QuickNades[name] = nade +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_sound.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_sound.lua new file mode 100644 index 0000000..2d04dc8 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_sound.lua @@ -0,0 +1,124 @@ +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +-- addsound("Grenade.PullPin", { +-- "TacRP/weapons/grenade/pullpin-1.wav", +-- "TacRP/weapons/grenade/pullpin-2.wav" +-- }) + +addsound("Grenade.PullPin", "common/null.wav") +addsound("Grenade.Throw", "common/null.wav") + +-- addsound("Grenade.Throw", { +-- "TacRP/weapons/grenade/throw-1.wav", +-- "TacRP/weapons/grenade/throw-2.wav" +-- }) + +addsound("TacInt_genericrifle.jingle", { + "TacRP/weapons/rifle_jingle-1.wav", + "TacRP/weapons/rifle_jingle-2.wav", + "TacRP/weapons/rifle_jingle-3.wav", + "TacRP/weapons/rifle_jingle-4.wav", + "TacRP/weapons/rifle_jingle-5.wav", + "TacRP/weapons/rifle_jingle-6.wav", +}) + +addsound("weapon.swing", { + "TacRP/weapons/swing_rifle-1.wav", + "TacRP/weapons/swing_rifle-2.wav" +}) + +addsound("weapon.thrust", { + "TacRP/weapons/thrust_rifle-1.wav", + "TacRP/weapons/thrust_rifle-2.wav" +}) + +addsound("TacInt_genericpistol.holster", { + "TacRP/weapons/pistol_holster-1.wav", + "TacRP/weapons/pistol_holster-2.wav", + "TacRP/weapons/pistol_holster-3.wav", + "TacRP/weapons/pistol_holster-4.wav", +}) + +addsound("TacInt_genericpistol.unholster", { + "TacRP/weapons/pistol_unholster-1.wav", + "TacRP/weapons/pistol_unholster-2.wav", + "TacRP/weapons/pistol_unholster-3.wav", + "TacRP/weapons/pistol_unholster-4.wav", +}) + +addsound("Weapon.Pistol_Clip_Scrape_Metal", { + "TacRP/weapons/pistol_clip_scrape_metal-1.wav", + "TacRP/weapons/pistol_clip_scrape_metal-2.wav", + "TacRP/weapons/pistol_clip_scrape_metal-3.wav", + "TacRP/weapons/pistol_clip_scrape_metal-4.wav", +}) + +addsound("Weapon.Pistol_Clip_Scrape_plastic", { + "TacRP/weapons/pistol_clip_scrape_plastic-1.wav", + "TacRP/weapons/pistol_clip_scrape_plastic-2.wav", + "TacRP/weapons/pistol_clip_scrape_plastic-3.wav", +}) + +addsound("TacRP.Charge.Windup", { + "tacrp/charge/demo_charge_windup1.wav", + "tacrp/charge/demo_charge_windup2.wav", +}) + +sound.Add({ + name = "TacRP.Charge.Windup", + channel = 130, + volume = 1.0, + sound = { + "tacrp/charge/demo_charge_windup1.wav", + "tacrp/charge/demo_charge_windup2.wav", + } +}) + +sound.Add({ + name = "TacRP.Charge.End", + channel = 130, + volume = 1.0, + sound = { + "common/null.wav", + } +}) + +sound.Add({ + name = "TacRP.Charge.HitWorld", + channel = 130, + volume = 1.0, + sound = { + "tacrp/charge/demo_charge_hit_world1.wav", + "tacrp/charge/demo_charge_hit_world2.wav", + "tacrp/charge/demo_charge_hit_world3.wav", + } +}) + +sound.Add({ + name = "TacRP.Charge.HitFlesh", + channel = 130, + volume = 1.0, + sound = { + "tacrp/charge/demo_charge_hit_flesh1.wav", + "tacrp/charge/demo_charge_hit_flesh2.wav", + "tacrp/charge/demo_charge_hit_flesh3.wav", + } +}) + +sound.Add({ + name = "TacRP.Charge.HitFlesh_Range", + channel = 130, + volume = 1.0, + sound = { + "tacrp/charge/demo_charge_hit_flesh_range1.wav", + "tacrp/charge/demo_charge_hit_flesh_range2.wav", + "tacrp/charge/demo_charge_hit_flesh_range3.wav", + } +}) diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_util.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_util.lua new file mode 100644 index 0000000..987c564 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_util.lua @@ -0,0 +1,120 @@ +function TacRP.GetMoveVector(mv) + local ang = mv:GetAngles() + + local max_speed = mv:GetMaxSpeed() + + local forward = math.Clamp(mv:GetForwardSpeed(), -max_speed, max_speed) + local side = math.Clamp(mv:GetSideSpeed(), -max_speed, max_speed) + + local abs_xy_move = math.abs(forward) + math.abs(side) + + if abs_xy_move == 0 then + return Vector(0, 0, 0) + end + + local mul = max_speed / abs_xy_move + + local vec = Vector() + + vec:Add(ang:Forward() * forward) + vec:Add(ang:Right() * side) + + vec:Mul(mul) + + return vec +end + +function TacRP.GetCmdVector(cmd, defaultforward) + local ang = Angle(0, cmd:GetViewAngles().y, 0) + + local forward = cmd:GetForwardMove() / 10000 + local side = cmd:GetSideMove() / 10000 + + local abs_xy_move = math.abs(forward) + math.abs(side) + if abs_xy_move == 0 then + if defaultforward then + forward = 1 + else + return Vector() + end + end + local div = (forward ^ 2 + side ^ 2) ^ 0.5 + + local vec = Vector() + vec:Add(ang:Forward() * forward / div) + vec:Add(ang:Right() * side / div) + + return vec +end + +function TacRP.CancelBodyDamage(ent, dmginfo, hitgroup) + local tbl = TacRP.CancelMultipliers[string.lower(engine.ActiveGamemode())] or TacRP.CancelMultipliers[1] + + if IsValid(ent) and (ent:IsNPC() or ent:IsPlayer()) and TacRP.ConVars["bodydamagecancel"]:GetBool() then + dmginfo:ScaleDamage(1 / (tbl[hitgroup] or 1)) + end + + -- Lambda Players call ScalePlayerDamage and cancel out hitgroup damage... except on the head + if IsValid(ent) and ent.IsLambdaPlayer and hitgroup == HITGROUP_HEAD then + dmginfo:ScaleDamage(1 / (tbl[hitgroup] or 1)) + end + + return dmginfo +end + +function TacRP.EntityIsNecrotic(ent) + if ent:IsNPC() and ent:Classify() == CLASS_ZOMBIE then return true end + if ent.VJ_NPC_Class == "CLASS_ZOMBIE" or (istable(ent.VJ_NPC_Class) and table.HasValue(ent.VJ_NPC_Class, "CLASS_ZOMBIE")) then return true end + local ret = hook.Run("TacRP_EntityIsNecrotic", ent) + if ret != nil then return ret end + return false +end + +function TacRP.FormatTierType(wtype, wtier, use_tiers) + if wtype then + local type_txt = string.sub(TacRP:TryTranslate(wtype), 2) + if use_tiers and wtier and wtier != "9Special" then + type_txt = TacRP:GetPhrase("cust.type_tier", {tier = string.sub(TacRP:TryTranslate(wtier), 2), type = type_txt}) + end + return type_txt + end + return "Weapon" +end + + +TacRP.WeaponListCache = {} +function TacRP.GetWeaponList(subcat, tier) + if !subcat then subcat = "" end + if !tier then tier = "" end + if !TacRP.WeaponListCache[subcat] or !TacRP.WeaponListCache[subcat][tier] then + TacRP.WeaponListCache[subcat] = TacRP.WeaponListCache[subcat] or {} + TacRP.WeaponListCache[subcat][tier] = {} + + for i, wep in pairs(weapons.GetList()) do + local weap = weapons.Get(wep.ClassName) + if !weap or !weap.ArcticTacRP + or wep.ClassName == "tacrp_base" or wep.ClassName == "tacrp_base_nade" or wep.ClassName == "tacrp_base_melee" + or !weap.Spawnable or weap.AdminOnly + or (subcat == "npc" and !weap.NPCUsable) + or (subcat != "" and subcat != "npc" and subcat != weap.SubCatType) + or (tier != "" and tier != weap.SubCatTier) then + continue + end + + table.insert(TacRP.WeaponListCache[subcat][tier], wep.ClassName) + end + end + return TacRP.WeaponListCache[subcat][tier] +end + +function TacRP.GetRandomWeapon(subcat, tier) + if !subcat then subcat = "" end + if !tier then tier = "" end + + local tbl = TacRP.GetWeaponList(subcat, tier) + return tbl[math.random(1, #tbl)] +end + +function TacRP.Developer(i) + return (SERVER or (CLIENT and IsValid(LocalPlayer()) and LocalPlayer():IsSuperAdmin())) and GetConVar("developer"):GetInt() >= (i or 1) +end diff --git a/garrysmod/addons/tacrp/lua/tacrp/shared/sh_weaponlimit.lua b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_weaponlimit.lua new file mode 100644 index 0000000..fbd5bfa --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tacrp/shared/sh_weaponlimit.lua @@ -0,0 +1,196 @@ +if engine.ActiveGamemode() == "terrortown" then return end + +local whitelist = { + weapon_physgun = true, + weapon_physcannon = true, + gmod_tool = true, + gmod_camera = true, +} + +-- return true if within limit +-- wep is not guaranteed to be an Entity! it could be the weapon table from weapons.Get +function TacRP:CheckWeaponLimit(weplist, wep) + local limit = TacRP.ConVars["slot_limit"]:GetInt() + if limit == 0 then return true end + local countall = TacRP.ConVars["slot_countall"]:GetBool() + local slot = (wep.GetSlot and wep:GetSlot()) or wep.Slot + local weps = {} + for k, v in pairs(weplist) do + if !whitelist[v:GetClass()] and (countall or v.ArcticTacRP) and (v:GetSlot() == slot) and !(v.ArcticTacRP and v:GetValue("PrimaryGrenade")) then + table.insert(weps, v) + end + end + + if #weps >= limit then + return false, weps + end + return true +end + +hook.Add("PlayerCanPickupWeapon", "TacRP_Pickup", function(ply, wep) + if !IsValid(wep) or !wep.ArcticTacRP then return end + local limit, weps = TacRP:CheckWeaponLimit(ply:GetWeapons(), wep) + + if !limit then + if TacRP.ConVars["allowdrop"]:GetBool() and ((ply:KeyDown(IN_USE) and !ply:KeyDown(IN_WALK) and ply:GetEyeTrace().Entity == wep) or wep:GetPos() == ply:GetPos()) then + if weps[1] == ply:GetActiveWeapon() then + timer.Simple(0, function() + if IsValid(ply) and IsValid(wep) and wep:GetOwner() == ply then + ply:SelectWeapon(wep) + end + end) + end + TacRP.DropWeapon(ply, weps[1]) + return + else + return false + end + end + + if ply:GetInfoNum("tacrp_pickup_use", 0) == 1 + and wep:GetPos() ~= ply:GetPos() -- received through ply:Give() + and (!ply:KeyDown(IN_USE) or ply:KeyDown(IN_WALK) or ply:GetEyeTrace().Entity ~= wep) then + return false + end +end) + +hook.Add("AllowPlayerPickup", "TacRP_Pickup", function(ply, ent) + if ent.ArcticTacRP and ply:GetInfoNum("tacrp_pickup_use", 0) == 1 and !ply:KeyDown(IN_WALK) then + return false -- This prevents +USE physics pickup, to avoid awkward situations where you want to equip a weapon but + end +end) + +local slot = { + weapon_physgun = 0, + weapon_crowbar = 0, + weapon_stunstick = 0, + weapon_physcannon = 0, + weapon_pistol = 1, + weapon_357 = 1, + weapon_smg1 = 2, + weapon_ar2 = 2, + weapon_crossbow = 3, + weapon_shotgun = 3, + weapon_frag = 4, + weapon_rpg = 4, + weapon_slam = 4, + weapon_bugbait = 5 +} +hook.Add("PlayerGiveSWEP", "TacRP_Pickup", function(ply, wepname, weptbl) + local _, weps = TacRP:CheckWeaponLimit(ply:GetWeapons(), weapons.Get(wepname) or {Slot = slot[wepname]}) + if weps and !ply:HasWeapon(wepname) then + local mode = TacRP.ConVars["slot_action"]:GetInt() + local limit = TacRP.ConVars["slot_limit"]:GetInt() + + if mode == 0 then + ply:ChatPrint("[TacRP] Couldn't spawn " .. weptbl.PrintName .. " due to the slot limit (max " .. limit .. ").") + return false + elseif mode == 1 or mode == 2 then + + local amt = #weps + 1 - limit + + local str = amt == 1 and (weps[1]:GetPrintName() .. " was") or (amt .. " weapons were") + str = str .. (mode == 1 and " removed" or " dropped") + + for _, e in pairs(weps) do + if mode == 1 then + ply:StripWeapon(e:GetClass()) + else + TacRP.DropWeapon(ply, e) + end + amt = amt - 1 + if amt <= 0 then break end + end + + ply:ChatPrint("[TacRP] " .. str .. " due to the slot limit (max " .. limit .. ").") + end + end +end) + +local function slotty() + local ahh = TacRP.ConVars["slot_hl2"]:GetBool() + for _, wpn in pairs(weapons.GetList()) do + + -- weapons.GetStored does not contain inherited values (like ArcticTacRP) + local tbl = weapons.Get(wpn.ClassName) + if !tbl.ArcticTacRP then continue end + + local tblStored = weapons.GetStored(wpn.ClassName) + if tblStored.SlotOrig == nil then + tblStored.SlotOrig = tblStored.Slot + end + + tblStored.Slot = ahh and tblStored.SlotAlt or tblStored.SlotOrig + end +end + +hook.Add("InitPostEntity", "TacRP_Slot", function() + slotty() + + -- even if the convar is replicated, the callback is not run on client + cvars.AddChangeCallback("tacrp_slot_hl2", function(cvar, old, new) + if CLIENT then return end + slotty() + timer.Simple(0, function() + net.Start("tacrp_updateslot") + net.Broadcast() + end) + end, "slotty") +end) + + +if CLIENT then + net.Receive("tacrp_updateslot", slotty) + + hook.Add("HUDPaint", "TacRP_WeaponLimit", function() + local wep = LocalPlayer():GetEyeTrace().Entity + if !IsValid(wep) or wep:GetPos():DistToSqr(EyePos()) >= 96 * 96 then return end + local wepclass = wep:GetClass() + if wep.IsSpawnedWeapon then + wepclass = wep:GetWeaponClass() + wep = weapons.Get(wepclass) + elseif !wep.ArcticTacRP then + return + end + + if !GetConVar("tacrp_pickuphint"):GetBool() then return end + + local limit, weps = TacRP:CheckWeaponLimit(LocalPlayer():GetWeapons(), wep) + + local text = nil + + if !limit then + text = "[" .. TacRP.GetBindKey("+use") .. "] " + .. TacRP:GetPhrase("hint.swap", { + weapon = TacRP:GetPhrase("wep." .. weps[1]:GetClass() .. "name") or weps[1].PrintName, + weapon2 = TacRP:GetPhrase("wep." .. wepclass .. "name") or wep.PrintName + }) + elseif TacRP.ConVars["pickup_use"]:GetBool() then + text = "[" .. TacRP.GetBindKey("+use") .. "] " + .. TacRP:GetPhrase("hint.pickup", {weapon = TacRP:GetPhrase("wep." .. wepclass .. "name") or wep.PrintName}) + end + + if text then + local font = "TacRP_HD44780A00_5x8_4" + surface.SetFont(font) + local w, h = surface.GetTextSize(text) + w = w + TacRP.SS(8) + h = h + TacRP.SS(4) + + surface.SetDrawColor(0, 0, 0, 200) + TacRP.DrawCorneredBox(ScrW() / 2 - w / 2, ScrH() / 2 + TacRP.SS(32), w, h) + + draw.SimpleText(text, font, ScrW() / 2, ScrH() / 2 + TacRP.SS(32) + h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end) + + + concommand.Add("tacrp_drop", function(ply, cmd, args, argStr) + if !TacRP.ConVars["allowdrop"]:GetBool() then return end + local wep = ply:GetActiveWeapon() + if !IsValid(wep) or !wep.ArcticTacRP then return end + if !ply:Alive() then return end + net.Start("tacrp_drop") + net.SendToServer() + end, "Drops the currently held TacRP weapon.") +end diff --git a/garrysmod/addons/tacrp/lua/tfa/external/sw_9k38.lua b/garrysmod/addons/tacrp/lua/tfa/external/sw_9k38.lua new file mode 100644 index 0000000..a58318c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/tfa/external/sw_9k38.lua @@ -0,0 +1,11 @@ + +TFA.AddFireSound("Igla.1", "sw/weapons/9k38/motor_fire.wav", CHAN_STATIC ) +TFA.AddWeaponSound("Igla.ready", "sw/weapons/9k38/retract.wav") +TFA.AddWeaponSound("Igla.clank", "sw/weapons/9k38/clank.wav") +TFA.AddWeaponSound("Igla.insert", "sw/weapons/9k38/insert.wav") +TFA.AddWeaponSound("Igla.slide", "sw/weapons/9k38/slide.wav") +TFA.AddWeaponSound("Igla.click", "sw/weapons/9k38/click.wav") +TFA.AddWeaponSound("Igla.return", "sw/weapons/9k38/return.wav") +TFA.AddWeaponSound("Igla.draw", "sw/weapons/9k38/draw.wav") +TFA.AddWeaponSound("Igla.holster", "sw/weapons/9k38/holster.wav") +TFA.AddWeaponSound("Stinger.open", "sw/weapons/fim92/deploy.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_aek971.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_aek971.lua new file mode 100644 index 0000000..b80e103 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_aek971.lua @@ -0,0 +1,360 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "AEK-971" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Experimental assault rifle using a unique dampening mechanism to reduce felt recoil. High fire rate but medicore range." +SWEP.Description_Quote = "Saw extensive use in the Battle for Paris." -- Battlefield 3 (2011) + +SWEP.Trivia_Caliber = "5.45x39mm" +SWEP.Trivia_Manufacturer = "Kovrovskiy Mekhanicheskiy Zavod" +SWEP.Trivia_Year = "1990" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Assets: Casper, arby26 +Animation: Tactical Intervention]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_aek.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_aek.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 16, + Damage_Min = 11, + + Range_Min = 400, + Range_Max = 2000, + + RPM = 900, + + RecoilSpreadPenalty = 0.0025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.012, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 20 +SWEP.Damage_Min = 12 +SWEP.Range_Min = 400 // distance for which to maintain maximum damage +SWEP.Range_Max = 2400 // distance at which we drop to minimum damage +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.725 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 900 + +SWEP.Spread = 0.006 + +SWEP.ShootTimeMult = 0.65 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 9 +SWEP.RecoilResetTime = 0.01 +SWEP.RecoilDissipationRate = 35 +SWEP.RecoilFirstShotMult = 1.5 + +SWEP.RecoilVisualKick = 0.4 + +SWEP.RecoilKick = 4 +SWEP.RecoilStability = 0.5 +SWEP.RecoilAltMultiplier = 300 + +SWEP.RecoilSpreadPenalty = 0.0019 +SWEP.HipFireSpreadPenalty = 0.04 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.92 +SWEP.ShootingSpeedMult = 0.8 +SWEP.SightedSpeedMult = 0.6 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.33 +SWEP.SprintToFireTime = 0.37 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2.5, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.05, 1.35, -0.5) +SWEP.SightPos = Vector(-4.7, -7.5, -4) + +SWEP.CorrectivePos = Vector(-0.02, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.75, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/aek.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +SWEP.FreeAimMaxAngle = 4.5 + +// sounds + +local path = "tacint_shark/weapons/aek971/ak47" + +SWEP.Sound_Shoot = "^" .. path .. "_shoot.wav" +SWEP.Sound_Shoot_Silenced = "tacint_shark/weapons/an94/ak47_suppressed_fire1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload" +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 0} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5.5, 0.4, 1.5), + Pos_WM = Vector(-0.4, 2, 0.5), + VMScale = 1.2, + } + }, + SortOrder = 2, + }, +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.65, -0.5), + vm_ang = Angle(0, 1, 0), + t = 0.15, + tmax = 0.15, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.02, + t1 = 0.1, + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper", "optic_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + Pos_VM = Vector(-5.8, 0.2, 4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 3, 0.6), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = {"silencer", "muzz_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.9, + WMScale = 0.9, + Pos_VM = Vector(-3.5, 0.075, 26.25), + Pos_WM = Vector(-.15, 24.5, -1.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-4.5, -0.1, 19), + Pos_WM = Vector(0.2, 16, -0.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_aek.remove_clip", path .. "_clipout.wav") +addsound("tacint_aek.insert_clip", path .. "_clipin.wav") +addsound("tacint_aek.boltaction", path .. "_boltpull.wav") +addsound("tacint_aek.Buttstock_Back", "tacrp/weapons/ak47/ak47_buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak12.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak12.lua new file mode 100644 index 0000000..9730cea --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak12.lua @@ -0,0 +1,361 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "AK-12" +SWEP.AbbrevName = "AK-12" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "One of many attempts at modernizing the AK, this experimental model uses burst fire and allows for quick swapping of the weapon's caliber." +SWEP.Description_Quote = "Found on a dead Spetsnaz soldier." -- Killing Floor 2, explaining where the weapon came from. + +SWEP.Trivia_Caliber = "5.45x39mm" +SWEP.Trivia_Manufacturer = "Kalashnikov Concern" +SWEP.Trivia_Year = "2012" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Assets: Counter Strike: Online 2 +Animation: Tactical Intervention]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_ak12.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_ak12.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 24, + Damage_Min = 16, + + RecoilKick = 2, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 15, + Damage_Min = 12, + Range_Min = 600, + Range_Max = 2500, + + RecoilSpreadPenalty = 0.0025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 12, + Damage_Min = 6, + + RecoilSpreadPenalty = 0.0011, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 18, + RecoilDissipationRate = 15, + RecoilSpreadPenalty = 0.004 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 28 +SWEP.Damage_Min = 16 +SWEP.Range_Min = 1200 +SWEP.Range_Max = 3200 +SWEP.Penetration = 6 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + -3, + 2, + 1 +} + +SWEP.PostBurstDelay = 0.08 + +SWEP.RPM = 700 +SWEP.RPMMultBurst = 800 / 700 + +SWEP.Spread = 0.004 + +SWEP.ShootTimeMult = 0.6 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0.01 +SWEP.RecoilDissipationRate = 36 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 1 + +SWEP.RecoilKick = 2 +SWEP.RecoilStability = 0.55 + +SWEP.RecoilSpreadPenalty = 0.0026 +SWEP.HipFireSpreadPenalty = 0.03 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.65 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.37 +SWEP.SprintToFireTime = 0.4 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -3, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.25, 0, -1) +SWEP.SightPos = Vector(-4.48, -7, -3.55) + +SWEP.CorrectivePos = Vector(0.03, 0, 0.06) +SWEP.CorrectiveAng = Angle(0.64, 0.1, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 0.95 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/aek.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.35 +SWEP.DropMagazineTime = 0.45 + +SWEP.FreeAimMaxAngle = 4 + +// sounds + +local path = "tacrp/weapons/ak47/ak47_" +local path1 = "tacint_extras/ak12/" + +SWEP.Sound_Shoot = "^" .. path1 .. "ak12-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = "fire3_M", + ["fire5"] = "fire3_M", + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "midreload" +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.1, -0.1), + vm_ang = Angle(0, 0.1, 0), + t = 0.15, + tmax = 0.15, + bones = { + { + bone = "ValveBiped.bolt_cover", + pos = Vector(0, 0, -3), + t0 = 0.02, + t1 = 0.12, + }, + }, +} + + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["sight"] = { + BGs_VM = { + {2, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "ValveBiped._ROOT_AMD65", + WMBone = "Box01", + InstalledElements = {"sight"}, + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 0.9, + Pos_VM = Vector(-4.55, 0.225, 3.5), + Pos_WM = Vector(0, 4, 0.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped._ROOT_AMD65", + WMBone = "Box01", + VMScale = 0.9, + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-2.4, 0.25, 30), + Pos_WM = Vector(0, 27.5, -1.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped._ROOT_AMD65", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + Pos_VM = Vector(-2.5, -0.5, 15.5), + Pos_WM = Vector(0.65, 14, -1.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_4pos"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle", "ammo_ak12"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_ak12.remove_clip", "TacRP/weapons/ak47/ak47_remove_clip.wav") +addsound("tacint_ak12.insert_clip", "TacRP/weapons/ak47/ak47_insert_clip.wav") +addsound("tacint_ak12.Bolt_Back", path1 .. "ak12_reload.wav") +addsound("tacint_ak12.Bolt_Release", "common/null.wav") +addsound("tacint_ak12.Rifle_catch", path .. "rifle_catch.wav") +addsound("tacint_ak12.Buttstock_Lockback", path1 .. "ak12_draw.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak74.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak74.lua new file mode 100644 index 0000000..e1b90da --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak74.lua @@ -0,0 +1,377 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "AK-74" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "A well-rounded staple from Eastern Europe with controllable recoil and good range. Modifications include lightweight furniture and mounts for dovetail optics." +SWEP.Description_Quote = "\"On the battlefield, it's probably better to wound a man than to kill him.\"" -- 'The History of the AK-74' by S1apSh0es - https://youtu.be/6jZOzB3oeSs?feature=shared + +SWEP.Trivia_Caliber = "5.45x39mm" +SWEP.Trivia_Manufacturer = "Kalashnikov Concern" +SWEP.Trivia_Year = "1974" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Model: Twinke Masta, TheLama +Texture: Millenia, The Spork +Sound: Vunsunta +Animation: Tactical Intervention]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_ak74.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_ak74.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 23, + Damage_Min = 15, + + RecoilKick = 2.5, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 16, + Damage_Min = 11, + + Range_Min = 400, + Range_Max = 2500, + + RPM = 600, + + RecoilSpreadPenalty = 0.0025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 20, + RecoilSpreadPenalty = 0.003, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 22 +SWEP.Damage_Min = 12 +SWEP.Range_Min = 1000 +SWEP.Range_Max = 2700 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 21000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 650 + +SWEP.Spread = 0.004 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 36 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 1 + +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.35 +SWEP.RecoilAltMultiplier = 150 + +SWEP.RecoilSpreadPenalty = 0.002 +SWEP.HipFireSpreadPenalty = 0.05 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.6 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.4 +SWEP.SprintToFireTime = 0.4 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.02, -0.5, 0) +SWEP.SightPos = Vector(-4.66, -7.5, -3) + +SWEP.CorrectivePos = Vector(0, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.75, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/74u.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +SWEP.FreeAimMaxAngle = 5 + +// sounds + +local path = "tacrp/weapons/ak47/ak47_" +local path1 = "tacint_shark/weapons/ak74u/" +local path2 = "tacint_extras/ak74/" + +SWEP.Sound_Shoot = "^" .. path2 .. "ak47-1.wav" +SWEP.Sound_Shoot_Silenced = path2 .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload", +} + +SWEP.DeployTimeMult = 2.25 + +// attachments + +SWEP.AttachmentElements = { + ["polymer"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 0} + }, + BGs_WM = { + {2, 0} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5.5, 0.5, 2), + Pos_WM = Vector(-0.4, 1, 0.5), + } + }, + SortOrder = 2, + }, +} + +SWEP.NoRMR = true + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.4, -0.3), + vm_ang = Angle(0, 0.5, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.15, + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper", "optic_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + WMScale = 0.75, + Pos_VM = Vector(-5.5, 0.15, 2.4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 0.4, 0.1), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = {"silencer", "muzz_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.8, + WMScale = 0.8, + Pos_VM = Vector(-3.3, 0.14, 29), + Pos_WM = Vector(0, 27.4, -2), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.25, -0.1, 19), + Pos_WM = Vector(0, 19, -2.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_ak74", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_ak74.remove_clip", path1 .. "clipout.wav") +addsound("tacint_extras_ak74.insert_clip", path1 .. "clipin.wav") +addsound("tacint_extras_ak74.boltaction", path .. "boltaction.wav") +addsound("tacint_extras_ak74.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak74u.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak74u.lua new file mode 100644 index 0000000..1f8bce9 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_ak74u.lua @@ -0,0 +1,365 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "АКС-74У" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "3Submachine Gun" + +SWEP.Description = "SMG-sized carbine designed for tank crews and special forces. Impressive firepower in a small package, but not gentle in terms of recoil." +SWEP.Description_Quote = "\"Mother Russia can rot, for all I care.\"" + +SWEP.Trivia_Caliber = "5.45x39mm" +SWEP.Trivia_Manufacturer = "Tula Arms Plant" +SWEP.Trivia_Year = "1979" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Model: TheLama; Compile: Bushmasta101 +Texture: Thanez +Sound: BlitzBoaR/CC5/modderfreak, .exe +Animation: Tactical Intervention]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_74u.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_74u.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 25, + Damage_Min = 13, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 14, + Damage_Min = 7, + + Range_Min = 600, + Range_Max = 1800, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 10, + Damage_Min = 4, + + RecoilSpreadPenalty = 0.0022, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 23 +SWEP.Damage_Min = 10 +SWEP.Range_Min = 500 +SWEP.Range_Max = 1900 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.725 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 18000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 700 + +SWEP.Spread = 0.01 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 7 +SWEP.RecoilResetTime = 0.01 +SWEP.RecoilDissipationRate = 32 +SWEP.RecoilFirstShotMult = 1.25 + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 4 +SWEP.RecoilStability = 0.4 +SWEP.RecoilAltMultiplier = 300 + +SWEP.RecoilSpreadPenalty = 0.0031 +SWEP.HipFireSpreadPenalty = 0.022 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.8 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.3 +SWEP.SprintToFireTime = 0.33 + +SWEP.Sway = 0.9 +SWEP.ScopedSway = 0.2 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, -0.5, -0.5) +SWEP.SightPos = Vector(-4.66, -7.5, -2.8) + +SWEP.CorrectivePos = Vector(0, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.8, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +SWEP.DeployTimeMult = 0.9 + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 0.925 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/74u.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +SWEP.FreeAimMaxAngle = 3.5 + +// sounds + +local path = "tacrp/weapons/ak47/ak47_" +local path1 = "tacint_shark/weapons/ak74u/" + +SWEP.Sound_Shoot = "^" .. path1 .. "ak47-1.wav" +SWEP.Sound_Shoot_Silenced = path1 .. "g3sg1-1.wav" + +SWEP.Pitch_Shoot = 100 +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload" +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 0} + }, + BGs_WM = { + {2, 0} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5, 0.475, 1.5), + Pos_WM = Vector(-0.1, -1, 0.5), + VMScale = 1.2, + } + }, + SortOrder = 2, + }, +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.4, -0.2), + vm_ang = Angle(0, 0.5, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.15, + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper", "optic_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + Pos_VM = Vector(-5.8, 0.25, 3), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0.05, 2.5, 1.2), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = {"silencer", "muzz_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + VMScale = 0.75, + WMScale = 0.85, + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + Pos_VM = Vector(-3.35, 0.14, 19.3), + Pos_WM = Vector(0, 18.5, -1.65), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-4.25, 0.6, 12), + Pos_WM = Vector(-0.4, 10, -0.5), + Ang_VM = Angle(90, 0, 75), + Ang_WM = Angle(0, -90, 75), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_74u.remove_clip", path1 .. "clipout.wav") +addsound("tacint_74u.insert_clip", path1 .. "clipin.wav") +addsound("tacint_74u.boltaction", path .. "boltaction.wav") +addsound("tacint_74u.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_an94.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_an94.lua new file mode 100644 index 0000000..c880834 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_an94.lua @@ -0,0 +1,367 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "АН-94" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Experimental assault rifle with a unique 2-round \"hyperburst.\" The rifle's complex mechanism affords low recoil but is very bulky." +SWEP.Description_Quote = "\"Antje\"" + +SWEP.Trivia_Caliber = "5.45x39mm" +SWEP.Trivia_Manufacturer = "Kalashnikov Concern" +SWEP.Trivia_Year = "1994" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Assets: Firearms: Source +Animation: Tactical Intervention]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_an94.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_an94.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 23, + Damage_Min = 15, + + RecoilKick = 3, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 16, + Damage_Min = 11, + Range_Min = 400, + Range_Max = 2000, + + RecoilSpreadPenalty = 0.0025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilResetTime = 0.3, + RecoilSpreadPenalty = 0.004 + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 24 +SWEP.Damage_Min = 17 +SWEP.Range_Min = 1100 +SWEP.Range_Max = 3300 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.725 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + -2, + 2, + 1 +} + +SWEP.RPM = 600 +SWEP.RPMMultBurst = 3 + +SWEP.PostBurstDelay = 0.25 +SWEP.RunawayBurst = true + +SWEP.Spread = 0.006 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 4 +SWEP.RecoilResetTime = 0.12 +SWEP.RecoilDissipationRate = 10 +SWEP.RecoilFirstShotMult = 0.35 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 2 +SWEP.RecoilStability = 0.5 +SWEP.RecoilAltMultiplier = 55 + +SWEP.RecoilSpreadPenalty = 0.006 +SWEP.HipFireSpreadPenalty = 0.035 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.8 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.42 +SWEP.SprintToFireTime = 0.45 + +SWEP.Sway = 1.3 +SWEP.ScopedSway = 0.2 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -3, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.03, -0.15, 0) +SWEP.SightPos = Vector(-4.66, -7.5, -2.45) + +SWEP.CorrectivePos = Vector(0, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.75, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1.05 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/aek.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +SWEP.FreeAimMaxAngle = 4.5 + +// sounds + +local path = "tacint_shark/weapons/an94/ak47_" + +SWEP.Sound_Shoot = "tacint_shark/weapons/an94/an-94_fire.wav" +SWEP.Sound_Shoot_Silenced = path .. "suppressed_fire1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload" +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 0} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5, 0.4, 3), + Pos_WM = Vector(-0.4, 2, 0.5), + VMScale = 1.2, + } + }, + SortOrder = 2, + }, +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.2), + vm_ang = Angle(0, 0.25, 0), + t = 0.25, + tmax = 0.25, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.12, + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper", "optic_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.8, + Pos_VM = Vector(-5.8, 0.2, 4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 4.25, 0.5), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.bolt", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.85, + WMScale = 0.85, + Pos_VM = Vector(-0, 21.75, -1.45), + Pos_WM = Vector(0, 29.5, -1.75), + Ang_VM = Angle(90, -90, -90), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-2.25, -0, 22.25), + Pos_WM = Vector(-0.15, 22, -3), + Ang_VM = Angle(90, 0, 180), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_4pos"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_an94.remove_clip", path .. "magout_empty.wav") +addsound("tacint_an94.insert_clip", path .. "magin.wav") +addsound("tacint_an94.boltaction", path .. "cock.wav") +addsound("tacint_an94.Buttstock_Back", "tacrp/weapons/ak47/ak47_buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_svd.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_svd.lua new file mode 100644 index 0000000..bec9047 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ak_svd.lua @@ -0,0 +1,373 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "СВД" +SWEP.Category = "[FT] Оружие" // "Tactical RP (Extras)" + +SWEP.SubCatTier = "4Consumer" +SWEP.SubCatType = "7Sniper Rifle" + +SWEP.Description = "Russian sniper/DMR with low fire rate but great damage and control. Equipped with a 6x scope by default.\nWhile resembling an AK, they are mechanically unrelated." +SWEP.Description_Quote = "Reach out and touch someone the Russian Army way." -- Jagged Alliance: Back in Action (2012) (The gun itself is not a real SVD, but this part of the description is really funny to me) + +SWEP.Trivia_Caliber = "7.62x54mmR" +SWEP.Trivia_Manufacturer = "Kalashnikov Concern" +SWEP.Trivia_Year = "1963" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Model: Rafael De Jongh, Ettubrutesbro +Texture: WangChung +Sound: Ghost597879, King Friday, iFlip +Animation: Tactical Intervention]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_svd.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_svd.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 45, + Damage_Min = 75, + Range_Max = 8000, + Range_Min = 2500, + + RecoilKick = 5, + RPM = 180, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 30, + Damage_Min = 45, + Range_Min = 600, + Range_Max = 2500, + RPM = 100, + + RecoilResetInstant = true, + RecoilResetTime = 0.35, + RecoilDissipationRate = 3, + RecoilMaximum = 3, + RecoilSpreadPenalty = 0.005, + RecoilFirstShotMult = 0.9, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SniperRifle + +// "ballistics" + +SWEP.Damage_Max = 50 +SWEP.Damage_Min = 34 +SWEP.Range_Min = 2000 // distance for which to maintain maximum damage +SWEP.Range_Max = 6000 // distance at which we drop to minimum damage +SWEP.Penetration = 11 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.775 +SWEP.ArmorBonus = 2 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 38000 + +// misc. shooting + +SWEP.Firemode = 1 +SWEP.RPM = 150 + +SWEP.Spread = 0.0002 + +SWEP.ShootTimeMult = 0.7 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 4 +SWEP.RecoilResetTime = 0.22 // time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 4 +SWEP.RecoilFirstShotMult = 0.75 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 0.75 + +SWEP.RecoilKick = 6 +SWEP.RecoilStability = 0.6 + +SWEP.RecoilSpreadPenalty = 0.003 +SWEP.HipFireSpreadPenalty = 0.08 +SWEP.PeekPenaltyFraction = 0.1 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.4 + +SWEP.AimDownSightsTime = 0.47 +SWEP.SprintToFireTime = 0.55 + +SWEP.Sway = 2.5 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 8.5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0.5, -2.5, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, -0.35, 0) +SWEP.SightPos = Vector(-4.75, -7.5, -2.7) + +SWEP.CorrectivePos = Vector(-0.05, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.65, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/pso1_ak.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 6 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 10 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo = "ar2" +SWEP.Ammo_TTT = "357" +SWEP.Ammo_Expanded = "ti_rifle" + +SWEP.ReloadTimeMult = 1.15 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/svd.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +// sounds + +local path = "tacint_extras/svd/" +local path2 = "tacrp/weapons/ak47/ak47_" + +SWEP.Sound_Shoot = "^" .. path .. "g3sg1-1.wav" +SWEP.Sound_Shoot_Silenced = path2 .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire_iron"] = "fire1_M", + ["fire"] = {"fire5_M", "fire5_L", "fire5_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload" +} + +SWEP.DeployTimeMult = 2.5 + +// attachments + +SWEP.AttachmentElements = { + ["sights"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["irons"] = { + BGs_VM = { + {1, 2} + }, + BGs_WM = { + {1, 2} + }, + }, + ["akmount"] = { + BGs_VM = { + {1, 2} + }, + BGs_WM = { + {1, 2} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5.5, 0.55, 3), + Pos_WM = Vector(-0.4, 2, 0.5), + } + }, + SortOrder = 2, + }, +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.8, -0.5), + vm_ang = Angle(0, 1, 0), + t = 0.25, + tmax = 0.25, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.05, + t1 = 0.2, + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights_sniper", "optic_cqb", "optic_medium", "optic_sniper", "optic_ak2"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"sights"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.9, + Pos_VM = Vector(-5.9, 0.24, 5.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 5, 1.25), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.9, + Pos_VM = Vector(-3.6, 0.24, 39.2), + Pos_WM = Vector(0, 44, -1.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.25, -0.1, 19), + Pos_WM = Vector(0.3, 20, -1.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "acc_extmag_sniper"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_sniper"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_svd.remove_clip", path .. "clipin.wav") +addsound("tacint_svd.insert_clip", path .. "clipout.wav") +addsound("tacint_svd.bolt_back", path .. "bolt_back.wav") +addsound("tacint_svd.bolt_forward", path .. "bolt_forward.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_as50.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_as50.lua new file mode 100644 index 0000000..95a7add --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_as50.lua @@ -0,0 +1,370 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "AI AS50" +SWEP.AbbrevName = "AS50" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "7Sniper Rifle" + +SWEP.Description = "Semi-automatic AMR that can easily decimate personnel and materiel at any distance. Too heavy to bash with.\nEquipped with a 12x scope by default." +SWEP.Description_Quote = "As seen on Future Weapons." + +SWEP.Trivia_Caliber = ".50 BMG" +SWEP.Trivia_Manufacturer = "Accuracy International" +SWEP.Trivia_Year = "2005" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_as50_hq.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_as50.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Description = "Semi-automatic anti-materiel rifle with integral bipod.\nEquipped with a 12x scope by default.\nFar too heavy to swing, so bashing is out of the question.", + + Damage_Max = 70, + Damage_Min = 140, + + Range_Min = 900, + Range_Max = 5000, + }, + [TacRP.BALANCE_TTT] = { // this is a buyable weapon in TTT + Description = "Semi-automatic anti-materiel rifle with integral bipod.\nCan kill in up to 2 shots regardless of distance.\nEquipped with a 12x scope by default.", + + Damage_Max = 80, + Damage_Min = 150, + Range_Min = 500, + Range_Max = 4000, + RPM = 180, + + Penetration = 50, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.675, + [HITGROUP_RIGHTLEG] = 0.675, + [HITGROUP_GEAR] = 0.6 + }, + }, + [TacRP.BALANCE_PVE] = { + Description = "Semi-automatic anti-materiel rifle with integral bipod.\nEquipped with a 12x scope by default.", + + Damage_Max = 120, + Damage_Min = 92, + Range_Min = 4000, + Range_Max = 8000, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilDissipationRate = 3, + RecoilMaximum = 20, + RecoilSpreadPenalty = 0.03, + + HipFireSpreadPenalty = 0.025, + } +} + +// "ballistics" + +SWEP.Damage_Max = 150 +SWEP.Damage_Min = 110 +SWEP.Range_Min = 1200 +SWEP.Range_Max = 8000 +SWEP.Penetration = 30 +SWEP.ArmorPenetration = 1.5 +SWEP.ArmorBonus = 6 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, // nobody is surviving this + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.75 +} + +SWEP.MuzzleVelocity = 20000 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 200 + +SWEP.Spread = 0 +SWEP.RecoilSpreadPenalty = 0.075 +SWEP.HipFireSpreadPenalty = 0.1 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 2 +SWEP.RecoilResetTime = 0.25 +SWEP.RecoilDissipationRate = 3 +SWEP.RecoilFirstShotMult = 1 +// SWEP.RecoilMultCrouch = 0.25 + +SWEP.RecoilVisualKick = 4 +SWEP.RecoilKick = 12 +SWEP.RecoilStability = 0.6 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.4 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.2 + +SWEP.AimDownSightsTime = 0.75 +SWEP.SprintToFireTime = 0.65 // multiplies how long it takes to recover from sprinting + +SWEP.Sway = 3 +SWEP.ScopedSway = 0.25 +// SWEP.SwayCrouchMult = 0.15 + +SWEP.FreeAimMaxAngle = 10 + +SWEP.Bipod = true +SWEP.BipodRecoil = 0.25 +SWEP.BipodKick = 0.35 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(2, -2, -6) + +SWEP.BlindFireAng = Angle(0, 15, 0) +SWEP.BlindFirePos = Vector(2, -2, -4) + +SWEP.BlindFireSuicideAng = Angle(0, 135, 0) +SWEP.BlindFireSuicidePos = Vector(-3, 47, -29) + +SWEP.CustomizeAng = Angle(50, 15, 0) +SWEP.CustomizePos = Vector(12, 6, -8) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-4.485, -7.5, -5.16) + +SWEP.CorrectivePos = Vector(0.03, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 4, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/sniper.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 12 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +SWEP.CanMeleeAttack = false + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 5 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.AmmoTTT = "ti_sniper" +SWEP.Ammo_Expanded = "ti_sniper" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineImpact = "metal" +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/uratio.mdl" + +SWEP.ReloadUpInTime = 2.2 +SWEP.DropMagazineTime = 0.8 + +// sounds + +local path = "TacRP/weapons/as50/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_m82_tacrp" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2"} +} + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + SortOrder = 1 + }, + ["irons"] = { + BGs_VM = { + {1, 2} + }, + SortOrder = 2 + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights_sniper", "optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "ValveBiped._ROOT_AS50", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-6.6, -0.1, 7), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 5, 1.75), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "barrel", + Bone = "ValveBiped._ROOT_AS50", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + Pos_VM = Vector(-3.7, 0, 18.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(23, 1, -4.5), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped._ROOT_AS50", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + VMScale = 1.25, + Pos_VM = Vector(-4, -1.6, 18), + Pos_WM = Vector(1.5, 16, -0.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_sniper", "acc_sling", "acc_duffle"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_amr"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_as50.Clip_Out", path .. "clip_out.wav") +addsound("TacInt_as50.Clip_In", path .. "clip_in.wav") +addsound("TacInt_as50.Bolt_Back", path .. "bolt_back.wav") +addsound("TacInt_as50.bolt_forward", path .. "bolt_forward.wav") + +if engine.ActiveGamemode() == "terrortown" then + SWEP.AutoSpawnable = false + SWEP.Kind = WEAPON_HEAVY + SWEP.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } + SWEP.EquipMenuData = { + type = "Weapon", + desc = "Semi-automatic anti-materiel rifle.\nComes with 10 rounds.\n\nBEWARE: May be visible while holstered!", + } + + function SWEP:TTTBought(buyer) + buyer:GiveAmmo(5, "ti_sniper") + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_aug.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_aug.lua new file mode 100644 index 0000000..2a02c03 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_aug.lua @@ -0,0 +1,334 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Steyr AUG A2" +SWEP.AbbrevName = "AUG A2" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Burst bullpup rifle with a generous magazine capacity and great handling." +SWEP.Description_Quote = "\"Welcome to the party, pal.\"" // Die Hard + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "Steyr Arms" +SWEP.Trivia_Year = "1977" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_aug.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_aug.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 20, + Damage_Min = 12, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 14, + Damage_Min = 10, + Range_Min = 400, + Range_Max = 1800, + PostBurstDelay = 0.2, + + RecoilResetInstant = true, + RecoilResetTime = 0.2, + RecoilSpreadPenalty = 0.004, + RecoilDissipationRate = 18, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 11, + Damage_Min = 7, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilDissipationRate = 15, + RecoilMaximum = 15, + RecoilSpreadPenalty = 0.005, + HipFireSpreadPenalty = 0.007, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 22 +SWEP.Damage_Min = 13 +SWEP.Range_Min = 1500 +SWEP.Range_Max = 3500 +SWEP.Penetration = 7 +SWEP.ArmorPenetration = 0.8 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 24000 + +// misc. shooting + +SWEP.Firemodes = { + -3, + 1 +} + +SWEP.RPM = 750 +SWEP.RPMMultBurst = 900 / 750 + +SWEP.Spread = 0.002 + +SWEP.PostBurstDelay = 0.12 + +SWEP.RunawayBurst = true + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 12 +SWEP.RecoilResetTime = 0.04 +SWEP.RecoilDissipationRate = 36 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 0.75 + +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.25 + +SWEP.RecoilSpreadPenalty = 0.002 +SWEP.HipFireSpreadPenalty = 0.03 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.95 +SWEP.ShootingSpeedMult = 0.7 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.3 +SWEP.SprintToFireTime = 0.25 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.125 + +SWEP.FreeAimMaxAngle = 2 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5.5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.01, 0.12, 0) +SWEP.SightPos = Vector(-3.93, -6, -4.9) + +SWEP.CorrectivePos = Vector(0.12, 0, 0) +SWEP.CorrectiveAng = Angle(0, 0, -1) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 4, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/aug.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.7 +SWEP.DropMagazineTime = 0.8 + +// sounds + +local path = "tacrp/weapons/aug/aug_" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 115 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_5" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["irons"] = { + BGs_VM = { + {1, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + InstalledElements = {"irons"}, + Bone = "ValveBiped.AUG_rootbone", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 1, + Pos_VM = Vector(-6.4, 0, 1), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(4.5, 1, -7), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.AUG_rootbone", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + Pos_VM = Vector(-3.7, 0, 18.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(23, 1, -4.5), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AUG_rootbone", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-5, 0, 10), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(14, 1, -6), + Ang_WM = Angle(0, 0, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_burst"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_aug.insert_clip", path .. "insert_clip.wav") +addsound("tacint_aug.remove_clip", path .. "remove_clip.wav") +addsound("tacint_aug.Handle_FoldDown", path .. "handle_folddown.wav") +addsound("tacint_aug.bolt_lockback", path .. "bolt_lockback.wav") +addsound("tacint_aug.bolt_release", path .. "bolt_release.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_autosight.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_autosight.lua new file mode 100644 index 0000000..8b7daa7 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_autosight.lua @@ -0,0 +1,161 @@ +SWEP.AutoSightSlot = nil + +SWEP.AutoSightPos = nil +SWEP.AutoSightAng = nil + +SWEP.ClientProxyModel = nil + +function SWEP:GenerateAutoSight() + local slot + + self.AutoSightPos = nil + self.AutoSightAng = nil + + for i, k in ipairs(self.Attachments) do + if !k.Installed then continue end + local atttbl = TacRP.GetAttTable(k.Installed) + + if atttbl.SightPos and atttbl.SightAng then + slot = i + break + end + end + + if !slot then return end + + local slottbl = self.Attachments[slot] + + if !slottbl.Installed then return end + + local bone = slottbl.Bone + local offset_pos = slottbl.Pos_VM + local offset_ang = slottbl.Ang_VM + + for _, ele in ipairs(self:GetElements()) do + if !ele.AttPosMods or !ele.AttPosMods[slot] then continue end + if wm then + if ele.AttPosMods[slot].Pos_WM then + offset_pos = ele.AttPosMods[slot].Pos_WM + end + if ele.AttPosMods[slot].Ang_WM then + offset_ang = ele.AttPosMods[slot].Ang_WM + end + if ele.AttPosMods[slot].WMBone then + bone = ele.AttPosMods[slot].WMBone + end + else + if ele.AttPosMods[slot].Pos_VM then + offset_pos = ele.AttPosMods[slot].Pos_VM + end + if ele.AttPosMods[slot].Ang_VM then + offset_ang = ele.AttPosMods[slot].Ang_VM + end + if ele.AttPosMods[slot].Bone then + bone = ele.AttPosMods[slot].Bone + end + end + end + + if !bone then return end + + local atttbl = TacRP.GetAttTable(slottbl.Installed) + + local mdl = ClientsideModel(self.ViewModel) + mdl:SetPos(Vector(0, 0, 0)) + mdl:SetAngles(Angle(0, 0, 0)) + mdl:SetNoDraw(true) + + local anim = self:TranslateSequence("idle") + local seq = mdl:LookupSequence(anim) + + mdl:ResetSequence(seq) + + mdl:SetupBones() + + if !IsValid(mdl) then return end + + local boneid = mdl:LookupBone(bone) + + local bpos = mdl:GetBoneMatrix(boneid):GetTranslation() + local bang = self.CorrectiveBoneAng and Angle(self.CorrectiveBoneAng) or mdl:GetBoneMatrix(boneid):GetAngles() + + SafeRemoveEntity(mdl) + + local apos, aang = bpos, bang + + apos:Add(bang:Forward() * offset_pos.x) + apos:Add(bang:Right() * offset_pos.y) + apos:Add(bang:Up() * offset_pos.z) + + aang:RotateAroundAxis(aang:Right(), offset_ang.p) + aang:RotateAroundAxis(aang:Up(), offset_ang.y) + aang:RotateAroundAxis(aang:Forward(), offset_ang.r) + + local moffset = (atttbl.ModelOffset or Vector(0, 0, 0)) * (slottbl.VMScale or 1) + + apos:Add(aang:Forward() * moffset.x) + apos:Add(aang:Right() * moffset.y) + apos:Add(aang:Up() * moffset.z) + + local vpos, vang = WorldToLocal(apos, aang, Vector(0, 0, 0), Angle(0, 0, 0)) + + local x = vpos.x + local y = vpos.y + local z = vpos.z + + vpos.x = -y + vpos.y = x + vpos.z = z + + vpos = vpos + (self.CorrectivePos or Vector(0, 0, 0)) + vang = vang + (self.CorrectiveAng or Angle(0, 0, 0)) + + self.AutoSightPos, self.AutoSightAng = -vpos, -vang +end + +function SWEP:GetSightPositions() + local apos, aang = self.AutoSightPos, self.AutoSightAng + + if apos and aang then + return apos, aang + elseif self:GetOwner() != LocalPlayer() then + self:GenerateAutoSight() -- Not generated for spectators so must be done here + return self.AutoSightPos or self.SightPos, self.AutoSightAng or self.SightAng + else + return self.SightPos, self.SightAng + end +end + +function SWEP:GetExtraSightPosition() + local epos + local eang + + local scale = 1 + + for i, k in ipairs(self.Attachments) do + if !k.Installed then continue end + local atttbl = TacRP.GetAttTable(k.Installed) + + if atttbl.SightPos and atttbl.SightAng then + epos = atttbl.SightPos + eang = atttbl.SightAng + scale = k.VMScale or 1 + break + end + end + + local pos = Vector(0, 0, 0) + local ang = Angle(0, 0, 0) + + if epos then + pos:Set(epos) + end + + if eang then + ang:Set(eang) + end + + pos = pos * scale + + return pos, ang +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_camera.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_camera.lua new file mode 100644 index 0000000..351641c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_camera.lua @@ -0,0 +1,35 @@ +SWEP.SmoothedMagnification = 1 + +function SWEP:CalcView(ply, pos, ang, fov) + local rec = (self:GetLastRecoilTime() + self:RecoilDuration()) - CurTime() + + rec = math.max(rec, 0) + + rec = rec * (self:GetValue("RecoilKick") + 1) * (self:GetRecoilAmount() / self:GetValue("RecoilMaximum")) ^ 0.75 + + if !game.SinglePlayer() then + rec = rec * 0.5 -- ??????????????? + end + + if rec > 0 then + ang.r = ang.r + (math.sin(CurTime() * 70.151) * rec) + end + + local mag = Lerp(self:GetSightAmount() ^ 3, 1, self:GetMagnification()) + + if self:GetHoldBreathAmount() > 0 then + mag = mag * (1 + self:GetHoldBreathAmount() * 0.15) + end + + local diff = math.abs(self.SmoothedMagnification - mag) + + self.SmoothedMagnification = math.Approach(self.SmoothedMagnification, mag, FrameTime() * diff * (self.SmoothedMagnification > mag and 10 or 5)) + + fov = fov / self.SmoothedMagnification + + self.TacRPLastFOV = fov + + fov = fov - rec + + return pos, ang, fov +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_cornershot.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_cornershot.lua new file mode 100644 index 0000000..37bfd41 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_cornershot.lua @@ -0,0 +1,259 @@ +local rt_w = 426 +local rt_h = 240 + +local rtmat = GetRenderTarget("tacrp_pipscope", rt_w, rt_h, false) + +local lastblindfire = false +local blindfiretime = 0 + +local csm_boot_1 = Material("tacrp/hud/cornershot_boot_1.png", "mips smooth") +local csm_boot_2 = Material("tacrp/hud/cornershot_boot_2.png", "mips smooth") +local csm_boot_3 = Material("tacrp/hud/cornershot_boot_3.png", "mips smooth") + +local csm_1 = Material("tacrp/hud/cornershot_1.png", "mips smooth") +local csm_2 = Material("tacrp/hud/cornershot_2.png", "mips smooth") + +local csm_n_1 = Material("tacrp/hud/cornershot_n_1.png", "mips smooth") +local csm_n_2 = Material("tacrp/hud/cornershot_n_2.png", "mips smooth") + +local noise1 = Material("tacrp/hud/noise1.png") +local noise2 = Material("tacrp/hud/noise2.png") +local noise3 = Material("tacrp/hud/noise3.png") +local noise4 = Material("tacrp/hud/noise4.png") + +local noisemats = { + noise1, + noise2, + noise3, + noise4 +} + +local lastrendertime = 0 + +local fps = 30 + +function SWEP:DoRT() + if !self:GetBlindFire() and !IsValid(self:GetCornershotEntity()) then lastblindfire = false return end + if TacRP.OverDraw then return end + + if !lastblindfire then + blindfiretime = 0 + end + + if lastrendertime > CurTime() - (1 / fps) then return end + + local angles = self:GetShootDir() + local origin = self:GetMuzzleOrigin() + + if IsValid(self:GetCornershotEntity()) then + origin = self:GetCornershotEntity():LocalToWorld(self:GetCornershotEntity().CornershotOffset) + angles = self:GetCornershotEntity():LocalToWorldAngles(self:GetCornershotEntity().CornershotAngles) + TacRP.CornerCamDrawSelf = true + elseif self:GetBlindFireMode() == TacRP.BLINDFIRE_KYS then + local bone = self:GetOwner():LookupBone("ValveBiped.Bip01_R_Hand") + + if bone then + local pos, ang = self:GetOwner():GetBonePosition(bone) + + angles = ang + angles:RotateAroundAxis(angles:Forward(), 180) + origin = pos + + TacRP.CornerCamDrawSelf = true + end + end + + local rt = { + x = 0, + y = 0, + w = rt_w, + h = rt_h, + aspect = 4 / 3, + angles = angles, + origin = origin, + drawviewmodel = false, + fov = 40, + znear = 6 + } + + render.PushRenderTarget(rtmat, 0, 0, rt_w, rt_h) + + if blindfiretime >= 1 or blindfiretime == 0 then + TacRP.OverDraw = true + render.RenderView(rt) + TacRP.OverDraw = false + end + + TacRP.CornerCamDrawSelf = false + + if self:GetTactical() then + DrawColorModify({ + ["$pp_colour_addr"] = 0.25 * 132 / 255, + ["$pp_colour_addg"] = 0.25 * 169 / 255, + ["$pp_colour_addb"] = 0.25 * 154 / 255, + ["$pp_colour_brightness"] = 0.2, + ["$pp_colour_contrast"] = 0.85, + ["$pp_colour_colour"] = 0.95, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }) + else + DrawColorModify({ + ["$pp_colour_addr"] = 0.25 * 132 / 255, + ["$pp_colour_addg"] = 0.25 * 169 / 255, + ["$pp_colour_addb"] = 0.25 * 154 / 255, + ["$pp_colour_brightness"] = -0.1, + ["$pp_colour_contrast"] = 5, + ["$pp_colour_colour"] = 0, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }) + + DrawBloom(0.25, 0.5, 16, 8, 1, 1, 1, 1, 1) + end + + -- if blindfiretime < 0.33 then + -- surface.SetMaterial(csm_boot_1) + -- elseif blindfiretime < 0.66 then + -- surface.SetMaterial(csm_boot_2) + -- elseif blindfiretime < 1.25 then + -- surface.SetMaterial(csm_boot_3) + -- else + -- end + + cam.Start2D() + + render.ClearDepth() + + if blindfiretime < 1 then + if blindfiretime < 0.75 then + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(noisemats[math.random(#noisemats)]) + surface.DrawTexturedRect(0, 0, rt_w, rt_h) + else + surface.SetDrawColor(0, 0, 0) + surface.DrawRect(0, 0, rt_w, rt_h) + end + + DrawColorModify({ + ["$pp_colour_addr"] = 0.25 * 132 / 255, + ["$pp_colour_addg"] = 0.25 * 169 / 255, + ["$pp_colour_addb"] = 0.25 * 154 / 255, + ["$pp_colour_brightness"] = 0.2, + ["$pp_colour_contrast"] = 0.85, + ["$pp_colour_colour"] = 0.95, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }) + end + + if blindfiretime < 0.2 then + surface.SetMaterial(csm_boot_1) + elseif blindfiretime < 0.4 then + surface.SetMaterial(csm_boot_2) + elseif blindfiretime < 0.6 then + surface.SetMaterial(csm_boot_3) + else + if self:GetTactical() then + if math.sin(CurTime() * 3) > 0.5 then + surface.SetMaterial(csm_1) + else + surface.SetMaterial(csm_2) + end + else + if math.sin(CurTime() * 3) > 0.5 then + surface.SetMaterial(csm_n_1) + else + surface.SetMaterial(csm_n_2) + end + end + end + + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRect(0, 0, rt_w, rt_h) + cam.End2D() + + render.PopRenderTarget() + + blindfiretime = blindfiretime + (math.random(0, 5) * math.random(0, 5) * (1 / fps) / 6.25) + + lastblindfire = true + lastrendertime = CurTime() +end + +function SWEP:DoCornershot() + + if !self:GetBlindFire() and !IsValid(self:GetCornershotEntity()) then lastblindfire = false return end + + local w = TacRP.SS(640 / 4) + local h = TacRP.SS(480 / 4) + local x = (ScrW() - w) / 2 + local y = (ScrH() - h) / 2 + -- y = y + (ScrH() / 4) + render.DrawTextureToScreenRect(rtmat, x, y, w, h) +end + +hook.Add("ShouldDrawLocalPlayer", "TacRP_CornerCamDrawSelf", function(ply) + if TacRP.CornerCamDrawSelf then + return true + end +end) + +SWEP.NearWallTick = 0 +SWEP.NearWallCached = false + +local traceResults = {} + +local traceData = { + start = true, + endpos = true, + filter = true, + mask = MASK_SHOT_HULL, + output = traceResults +} + +local VECTOR = FindMetaTable("Vector") +local vectorAdd = VECTOR.Add +local vectorMul = VECTOR.Mul + +local angleForward = FindMetaTable("Angle").Forward +local entityGetOwner = FindMetaTable("Entity").GetOwner + +function SWEP:GetNearWallAmount() + local now = engine.TickCount() + + if !TacRP.ConVars["nearwall"]:GetBool() or LocalPlayer():GetMoveType() == MOVETYPE_NOCLIP then + return 0 + end + + if self.NearWallTick == now then + return self.NearWallCached + end + + local length = 32 + + local startPos = self:GetMuzzleOrigin() + + local endPos = angleForward(self:GetShootDir()) + vectorMul(endPos, length) + vectorAdd(endPos, startPos) + + traceData.start = startPos + traceData.endpos = endPos + traceData.filter = entityGetOwner(self) + + util.TraceLine(traceData) + local hit = 1 - traceResults.Fraction + + self.NearWallCached = hit + self.NearWallTick = now + + return hit +end + +function SWEP:ThinkNearWall() + self:GetNearWallAmount() +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_customize.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_customize.lua new file mode 100644 index 0000000..3d3ba4a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_customize.lua @@ -0,0 +1,1442 @@ +-- local customizedelta = 0 +local range = 0 + +local enable_armor = false +local armor = Material("tacrp/hud/armor.png", "mip smooth") + +local body = Material("tacrp/hud/body.png", "mips smooth") +local news = Material("tacrp/hud/news.png", "mips smooth") + +local body_head = Material("tacrp/hud/body_head.png", "mips smooth") +local body_chest = Material("tacrp/hud/body_chest.png", "mips smooth") +local body_stomach = Material("tacrp/hud/body_stomach.png", "mips smooth") +local body_arms = Material("tacrp/hud/body_arms.png", "mips smooth") +local body_legs = Material("tacrp/hud/body_legs.png", "mips smooth") + +local stk_clr = { + [1] = Color(255, 75, 75), + [2] = Color(120, 20, 20), + [3] = Color(130, 90, 90), + [4] = Color(60, 35, 35), + [5] = Color(80, 80, 80), + [6] = Color(160, 160, 160), + [7] = Color(180, 180, 180), + [8] = Color(200, 200, 200), + [9] = Color(220, 220, 220), + [10] = Color(240, 240, 240), + [11] = Color(255, 255, 255), +} +local function bodydamagetext(name, dmg, num, mult, x, y, hover) + + local stk = math.ceil(100 / (dmg * mult)) + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetTextColor(255, 255, 255, 255) + surface.DrawLine(TacRP.SS(2), y, x, y) + + surface.SetFont("TacRP_Myriad_Pro_6") + surface.SetTextPos(TacRP.SS(2), y) + -- surface.DrawText(name) + -- surface.SetTextPos(TacRP.SS(1), y + TacRP.SS(6)) + if hover then + surface.DrawText(stk .. TacRP:GetPhrase(num > 1 and "unit.ptk" or "unit.stk")) + else + surface.DrawText(math.floor(dmg * mult)) -- .. (num > 1 and ("×" .. num) or "") + end + + local c = stk_clr[math.Clamp(num > 1 and math.ceil(stk / num) or stk, 1, 11)] + if enable_armor then + surface.SetDrawColor(c.b, c.g, c.r - (c.r - c.g) * 0.25, 255) + else + surface.SetDrawColor(c.r, c.g, c.b, 255) + end +end + +local lastcustomize = false + +SWEP.CustomizeHUD = nil + +function SWEP:CreateCustomizeHUD() + self:RemoveCustomizeHUD() + + gui.EnableScreenClicker(true) + TacRP.CursorEnabled = true + + local bg = vgui.Create("DPanel") + + self.CustomizeHUD = bg + self.StaticStats = true + + local scrw = ScrW() + local scrh = ScrH() + + local airgap = TacRP.SS(8) + local smallgap = TacRP.SS(4) + + bg:SetPos(0, 0) + bg:SetSize(ScrW(), ScrH()) + bg.OnRemove = function(self2) + if !IsValid(self) then return end + if TacRP.ConVars["autosave"]:GetBool() and TacRP.ConVars["free_atts"]:GetBool() then + self:SavePreset() + end + end + bg.Paint = function(self2, w, h) + if !IsValid(self) or !IsValid(self:GetOwner()) or self:GetOwner():GetActiveWeapon() != self then + self2:Remove() + if (self.GrenadeMenuAlpha or 0) != 1 then + gui.EnableScreenClicker(false) + TacRP.CursorEnabled = false + end + return + end + + local name_txt = TacRP:GetPhrase("wep." .. self:GetClass() .. ".name.full") or TacRP:GetPhrase("wep." .. self:GetClass() .. ".name") or self:GetValue("FullName") or self:GetValue("PrintName") + + surface.SetFont("TacRP_Myriad_Pro_32") + local name_w = surface.GetTextSize(name_txt) + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(w - name_w - TacRP.SS(20), airgap, name_w + TacRP.SS(12), TacRP.SS(34)) + TacRP.DrawCorneredBox(w - name_w - TacRP.SS(20), airgap, name_w + TacRP.SS(12), TacRP.SS(34)) + + surface.SetTextPos(w - name_w - TacRP.SS(14), airgap) + surface.SetTextColor(255, 255, 255) + surface.DrawText(name_txt) + + surface.SetFont("TacRP_Myriad_Pro_12") + + if self:GetAmmoType() != "" then + -- Have to do this weird double wrapping because ammo type strings are apparently case sensitive now (e.g. "Pistol_ammo") + local ammo_txt = language.GetPhrase(game.GetAmmoName(game.GetAmmoID(self:GetAmmoType())) .. "_ammo") + local ammo_w = surface.GetTextSize(ammo_txt) + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(w - name_w - ammo_w - TacRP.SS(32) - smallgap, airgap + TacRP.SS(20), ammo_w + TacRP.SS(12), TacRP.SS(14)) + TacRP.DrawCorneredBox(w - name_w - ammo_w - TacRP.SS(32) - smallgap, airgap + TacRP.SS(20), ammo_w + TacRP.SS(12), TacRP.SS(14)) + + surface.SetTextPos(w - name_w - ammo_w - TacRP.SS(30), airgap + TacRP.SS(21)) + surface.SetTextColor(255, 255, 255) + surface.DrawText(ammo_txt) + end + + if self.SubCatTier and self.SubCatType then + local type_txt = TacRP.FormatTierType(self.SubCatType, self.SubCatTier, TacRP.UseTiers()) + surface.SetFont("TacRP_Myriad_Pro_12") + local type_w = surface.GetTextSize(type_txt) + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(w - name_w - type_w - TacRP.SS(32) - smallgap, airgap, type_w + TacRP.SS(12), TacRP.SS(18)) + TacRP.DrawCorneredBox(w - name_w - type_w - TacRP.SS(32) - smallgap, airgap, type_w + TacRP.SS(12), TacRP.SS(18)) + + surface.SetTextPos(w - name_w - type_w - TacRP.SS(30), airgap + TacRP.SS(3)) + surface.SetTextColor(255, 255, 255) + surface.DrawText(type_txt) + end + + end + + local stack = airgap + TacRP.SS(34) + + if !self:GetValue("NoRanger") then + local ranger = vgui.Create("DPanel", bg) + ranger:SetPos(scrw - TacRP.SS(128) - airgap, stack + smallgap) + ranger:SetSize(TacRP.SS(128), TacRP.SS(64)) + ranger.Paint = function(self2, w, h) + if !IsValid(self) then return end + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + local exp = self:GetValue("ExplosiveDamage") + + local dmg_max = (self:GetValue("Damage_Max") + exp) * self:GetConfigDamageMultiplier() + local dmg_min = (self:GetValue("Damage_Min") + exp) * self:GetConfigDamageMultiplier() + + local range_min, range_max = self:GetMinMaxRange() + + surface.SetDrawColor(255, 255, 255, 50) + + local range_1_y = 2 * (h / 5) + local range_2_y = 4 * (h / 5) + + local range_1_x = 0 + local range_2_x = (w / 3) + local range_3_x = 2 * (w / 3) + + if dmg_max < dmg_min then + range_1_y = 4 * (h / 5) + range_2_y = 2 * (h / 5) + elseif dmg_max == dmg_min then + range_1_y = 3 * (h / 5) + range_2_y = 3 * (h / 5) + end + + if range_min == 0 then + range_2_x = 0 + range_3_x = w / 2 + end + + surface.DrawLine(range_2_x, 0, range_2_x, h) + surface.DrawLine(range_3_x, 0, range_3_x, h) + + surface.SetDrawColor(255, 255, 255) + + for i = 0, 1 do + surface.DrawLine(range_1_x, range_1_y + i, range_2_x, range_1_y + i) + surface.DrawLine(range_2_x, range_1_y + i, range_3_x, range_2_y + i) + surface.DrawLine(range_3_x, range_2_y + i, w, range_2_y + i) + end + + local mouse_x, mouse_y = input.GetCursorPos() + mouse_x, mouse_y = self2:ScreenToLocal(mouse_x, mouse_y) + + local draw_rangetext = true + + if mouse_x > 0 and mouse_x < w and mouse_y > 0 and mouse_y < h then + + local range_m_x = 0 + + if mouse_x < range_2_x then + range = range_min + range_m_x = range_2_x + elseif mouse_x > range_3_x then + range = range_max + range_m_x = range_3_x + else + local d = (mouse_x - range_2_x) / (range_3_x - range_2_x) + range = Lerp(d, range_min, range_max) + range_m_x = mouse_x + end + + local dmg = self:GetDamageAtRange(range) + exp * self:GetConfigDamageMultiplier() + + local txt_dmg1 = tostring(math.Round(dmg)) .. TacRP:GetPhrase("unit.damage") + + if self:GetValue("Num") > 1 then + txt_dmg1 = math.Round(dmg * self:GetValue("Num")) .. "-" .. txt_dmg1 + end + + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawLine(range_m_x, 0, range_m_x, h) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + local txt_dmg1_w = surface.GetTextSize(txt_dmg1) + surface.SetTextPos((w / 3) - txt_dmg1_w - (TacRP.SS(2)), TacRP.SS(1)) + surface.DrawText(txt_dmg1) + + local txt_range1 = self:RangeUnitize(range) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + local txt_range1_w = surface.GetTextSize(txt_range1) + surface.SetTextPos((w / 3) - txt_range1_w - (TacRP.SS(2)), TacRP.SS(1 + 8)) + surface.DrawText(txt_range1) + + draw_rangetext = false + end + + + if draw_rangetext then + local txt_dmg1 = tostring(math.Round(dmg_max)) .. TacRP:GetPhrase("unit.damage") + + if self:GetValue("Num") > 1 then + txt_dmg1 = math.Round(dmg_max * self:GetValue("Num")) .. "-" .. txt_dmg1 + end + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + local txt_dmg1_w = surface.GetTextSize(txt_dmg1) + surface.SetTextPos((w / 3) - txt_dmg1_w - (TacRP.SS(2)), TacRP.SS(1)) + surface.DrawText(txt_dmg1) + + local txt_range1 = self:RangeUnitize(range_min) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + local txt_range1_w = surface.GetTextSize(txt_range1) + surface.SetTextPos((w / 3) - txt_range1_w - (TacRP.SS(2)), TacRP.SS(1 + 8)) + surface.DrawText(txt_range1) + + local txt_dmg2 = tostring(math.Round(dmg_min)) .. TacRP:GetPhrase("unit.damage") + + if self:GetValue("Num") > 1 then + txt_dmg2 = math.Round(dmg_min * self:GetValue("Num")) .. "-" .. txt_dmg2 + end + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(2 * (w / 3) + (TacRP.SS(2)), TacRP.SS(1)) + surface.DrawText(txt_dmg2) + + local txt_range2 = self:RangeUnitize(range_max) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(2 * (w / 3) + (TacRP.SS(2)), TacRP.SS(1 + 8)) + surface.DrawText(txt_range2) + end + end + + local bodychart = vgui.Create("DPanel", bg) + bodychart:SetPos(scrw - TacRP.SS(128 + 44) - airgap, stack + smallgap) + bodychart:SetSize(TacRP.SS(40), TacRP.SS(64)) + bodychart:SetZPos(100) + bodychart.Paint = function(self2, w, h) + if !IsValid(self) then return end + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + local za = TacRP.SS(1) + for i=1, 7 do + local c = stk_clr[i] + if enable_armor then + surface.SetDrawColor( c.b, c.g, c.r - (c.r - c.g) * 0.25, 255 ) + else + surface.SetDrawColor( c.r, c.g, c.b, 255 ) + end + surface.DrawRect( math.Round(w - (za*5) - za*2), math.Round(h - (za*5*i) - za*2), math.Round(za*5), math.Round(za*5) ) + + surface.SetTextColor( 255, 255, 255, 127 ) + surface.SetFont("TacRP_Myriad_Pro_5") + surface.SetTextPos( math.Round(w - za*5 - za*0.7), math.Round(h - (za*5*i) - za*2)) + surface.DrawText(i) + end + + local h2 = h - TacRP.SS(4) + local w2 = math.ceil(h2 * (136 / 370)) + local x2, y2 = w - w2 - TacRP.SS(2), TacRP.SS(2) + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(body) + surface.DrawTexturedRect(x2, y2, w2, h2) + + local dmg = self:GetDamageAtRange(range, true) + self:GetValue("ExplosiveDamage") + + if enable_armor then + dmg = dmg * math.Clamp(self:GetValue("ArmorPenetration"), 0, 1) + end + + local num = self:GetValue("Num") + local mult = self:GetBodyDamageMultipliers() --self:GetValue("BodyDamageMultipliers") + local hover = self2:IsHovered() + + local upperbody = mult[HITGROUP_STOMACH] == mult[HITGROUP_CHEST] + + bodydamagetext("Head", dmg, num, mult[HITGROUP_HEAD], w - TacRP.SS(16), upperbody and TacRP.SS(6) or TacRP.SS(4), hover) + surface.SetMaterial(body_head) + surface.DrawTexturedRect(x2, y2, w2, h2) + + bodydamagetext("Chest", dmg, num, mult[HITGROUP_CHEST], w - TacRP.SS(16), upperbody and TacRP.SS(18) or TacRP.SS(14), hover) + surface.SetMaterial(body_chest) + surface.DrawTexturedRect(x2, y2, w2, h2) + + if !upperbody then + bodydamagetext("Stomach", dmg, num, mult[HITGROUP_STOMACH], w - TacRP.SS(16), TacRP.SS(24), hover) + end + surface.SetMaterial(body_stomach) + surface.DrawTexturedRect(x2, y2, w2, h2) + + bodydamagetext("Arms", dmg, num, mult[HITGROUP_LEFTARM], w - TacRP.SS(22), upperbody and TacRP.SS(30) or TacRP.SS(34), hover) + surface.SetMaterial(body_arms) + surface.DrawTexturedRect(x2, y2, w2, h2) + + bodydamagetext("Legs", dmg, num, mult[HITGROUP_LEFTLEG], w - TacRP.SS(18), upperbody and TacRP.SS(42) or TacRP.SS(44), hover) + surface.SetMaterial(body_legs) + surface.DrawTexturedRect(x2, y2, w2, h2) + + surface.SetDrawColor(0, 0, 0, 50) + + surface.SetFont("TacRP_Myriad_Pro_8") + local txt = self:RangeUnitize(range) + --local tw, th = surface.GetTextSize(txt) + --surface.DrawRect(TacRP.SS(1), h - TacRP.SS(10), tw + TacRP.SS(1), th) + surface.SetTextPos(TacRP.SS(2) + 2, h - TacRP.SS(10) + 2) + surface.SetTextColor(0, 0, 0, 150) + surface.DrawText(txt) + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(TacRP.SS(2), h - TacRP.SS(10)) + surface.DrawText(txt) + if num > 1 then + local txt2 = "×" .. math.floor(num) + local tw2 = surface.GetTextSize(txt2) + --surface.DrawRect(w - tw2 - TacRP.SS(2), h - TacRP.SS(10), tw2 + TacRP.SS(1), th2) + + surface.SetTextPos(w - tw2 - TacRP.SS(2) + 2, h - TacRP.SS(10) + 2) + surface.SetTextColor(0, 0, 0, 150) + surface.DrawText(txt2) + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(w - tw2 - TacRP.SS(2), h - TacRP.SS(10)) + surface.DrawText(txt2) + end + end + + local armorbtn = vgui.Create("DLabel", bg) + armorbtn:SetText("") + armorbtn:SetPos(scrw - TacRP.SS(128 + 44 - 32) - airgap, stack + smallgap + TacRP.SS(2)) + armorbtn:SetSize(TacRP.SS(6), TacRP.SS(6)) + armorbtn:SetZPos(110) + armorbtn:SetMouseInputEnabled(true) + armorbtn:MoveToFront() + armorbtn.Paint = function(self2, w, h) + if !IsValid(self) then return end + + if enable_armor and self2:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 255)) + elseif self2:IsHovered() then + surface.SetDrawColor(Color(255, 220, 220, 255)) + elseif enable_armor then + surface.SetDrawColor(Color(255, 255, 255, 175)) + else + surface.SetDrawColor(Color(255, 200, 200, 125)) + end + surface.SetMaterial(armor) + -- surface.DrawTexturedRect(w * 0.2, h * 0.2, w * 0.6, h * 0.6) + surface.DrawTexturedRect(0, 0, w, h) + end + armorbtn.DoClick = function(self2) + enable_armor = !enable_armor + end + + stack = stack + TacRP.SS(64) + smallgap + end + + if self:GetValue("PrimaryGrenade") then + local desc_box = vgui.Create("DPanel", bg) + desc_box:SetSize(TacRP.SS(172), TacRP.SS(108)) + desc_box:SetPos(scrw - TacRP.SS(172) - airgap, stack + smallgap) + stack = stack + TacRP.SS(48) + smallgap + desc_box.Paint = function(self2, w, h) + if !IsValid(self) then return end + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + local nade = TacRP.QuickNades[self:GetValue("PrimaryGrenade")] + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextPos(TacRP.SS(4), TacRP.SS(4)) + surface.DrawText( TacRP:GetPhrase("quicknade.fuse") ) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextPos(TacRP.SS(4), TacRP.SS(12)) + surface.DrawText(TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".dettype") or nade.DetType or "") + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(TacRP.SS(4), TacRP.SS(24)) + surface.DrawText(TacRP:GetPhrase("cust.description")) + + if !self.MiscCache["cust_desc"] then + self.MiscCache["cust_desc"] = TacRP.MultiLineText(TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".desc") or nade.Description, w - TacRP.SS(8), "TacRP_Myriad_Pro_8") + end + + for i, k in ipairs(self.MiscCache["cust_desc"]) do + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(TacRP.SS(4), TacRP.SS(32) + (TacRP.SS(8 * (i - 1)))) + surface.DrawText(k) + end + end + else + local tabs_h = TacRP.SS(8) + + local desc_box = vgui.Create("DPanel", bg) + desc_box.PrintName = TacRP:GetPhrase("cust.description2") + desc_box:SetSize(TacRP.SS(172), TacRP.SS(36)) + desc_box:SetPos(scrw - TacRP.SS(172) - airgap, stack + smallgap + tabs_h + TacRP.SS(2)) + desc_box.Paint = function(self2, w, h) + if !IsValid(self) then return end + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + if !self.MiscCache["cust_desc"] then + local phrase = TacRP:GetPhrase("wep." .. self:GetClass() .. ".desc") or self.Description + self.MiscCache["cust_desc"] = TacRP.MultiLineText(phrase, w - TacRP.SS(8), "TacRP_Myriad_Pro_8") + end + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + for i, k in pairs(self.MiscCache["cust_desc"]) do + surface.SetTextPos(TacRP.SS(4), TacRP.SS(2) + (TacRP.SS(8 * (i - 1)))) + surface.DrawText(k) + end + + -- local lastseenquoteline = false + -- local startseequotelinetime = 0 + + local phrase_quote = TacRP:GetPhrase("wep." .. self:GetClass() .. ".desc.quote") or self.Description_Quote + -- local phrase_quote = "THIS IS A TEST QUOTE THAT IS VERY LONG IN LENGTH TO TEST SCROLLING TEXT THIS IS A TEST" + if phrase_quote then + surface.SetFont("TacRP_Myriad_Pro_6_Italic") + local tw, th = surface.GetTextSize(phrase_quote) + + surface.SetTextPos(TacRP.SS(4), TacRP.SS(34) - th) + + -- if tw > TacRP.SS(166) then + -- surface.SetFont("TacRP_Myriad_Pro_6_Italic") + -- tw, th = surface.GetTextSize(phrase_quote) + -- lastseenquoteline = true + -- surface.SetTextPos(TacRP.SS(200) - ((CurTime() - startseequotelinetime) * 100) % (tw * 2.25), TacRP.SS(34) - th) + -- end + surface.SetTextColor(255, 255, 255) + surface.DrawText(phrase_quote) + end + end + + local trivia_box = vgui.Create("DPanel", bg) + trivia_box.PrintName = TacRP:GetPhrase("cust.trivia") + trivia_box:SetSize(TacRP.SS(172), TacRP.SS(36)) + trivia_box:SetPos(scrw - TacRP.SS(172) - airgap, stack + smallgap + tabs_h + TacRP.SS(2)) + trivia_box.Paint = function(self2, w, h) + if !IsValid(self) then return end + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + surface.SetFont("TacRP_Myriad_Pro_10") + surface.SetTextColor(255, 255, 255) + + surface.SetTextPos(TacRP.SS(4), TacRP.SS(6)) + local manu_str = TacRP:GetPhrase("wep." .. self:GetClass() .. ".trivia.manufacturer") + or TacRP:TryTranslate(self:GetValue("Trivia_Manufacturer")) + or TacRP:GetPhrase("trivia.unknown") + local manu_w = surface.GetTextSize(manu_str) + if manu_w + TacRP.SS(6) >= w / 2 then + surface.SetFont("TacRP_Myriad_Pro_8") + if !self.MiscCache["cust_manufacturer"] then + self.MiscCache["cust_manufacturer"] = TacRP.MultiLineText(manu_str, w / 2 - TacRP.SS(2), "TacRP_Myriad_Pro_8") + end + for i, k in pairs(self.MiscCache["cust_manufacturer"]) do + surface.SetTextPos(TacRP.SS(4), TacRP.SS(7) + (TacRP.SS(6 * (i - 1)))) + surface.DrawText(k) + end + else + surface.DrawText(manu_str) + end + + surface.SetFont("TacRP_Myriad_Pro_10") + + surface.SetTextPos(TacRP.SS(4), TacRP.SS(24)) + surface.DrawText(TacRP:GetPhrase("wep." .. self:GetClass() .. ".trivia.year") + or TacRP:TryTranslate(self:GetValue("Trivia_Year")) + or TacRP:GetPhrase("trivia.unknown")) + + surface.SetTextPos(w / 2, TacRP.SS(6)) + surface.DrawText(TacRP:GetPhrase("wep." .. self:GetClass() .. ".trivia.caliber") + or TacRP:TryTranslate(self:GetValue("Trivia_Caliber")) + or TacRP:GetPhrase("trivia.unknown")) + + surface.SetTextPos(w / 2, TacRP.SS(24)) + surface.DrawText(TacRP:GetPhrase(TacRP.FactionToPhrase[self:GetValue("Faction")])) + + surface.SetFont("TacRP_Myriad_Pro_6") + surface.SetTextColor(255, 255, 255) + + surface.SetTextPos(TacRP.SS(4), TacRP.SS(2)) + surface.DrawText(TacRP:GetPhrase("trivia.manufacturer")) + + surface.SetTextPos(TacRP.SS(4), TacRP.SS(20)) + surface.DrawText(TacRP:GetPhrase("trivia.year")) + + surface.SetTextPos(w / 2, TacRP.SS(2)) + surface.DrawText(TacRP:GetPhrase("trivia.caliber")) + + surface.SetTextPos(w / 2, TacRP.SS(20)) + surface.DrawText(TacRP:GetPhrase("trivia.faction")) + end + + local credits_box = vgui.Create("DPanel", bg) + credits_box.PrintName = TacRP:GetPhrase("cust.credits") + credits_box:SetSize(TacRP.SS(172), TacRP.SS(36)) + credits_box:SetPos(scrw - TacRP.SS(172) - airgap, stack + smallgap + tabs_h + TacRP.SS(2)) + credits_box.Paint = function(self2, w, h) + if !IsValid(self) or !self.Credits then return end + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + if !self.MiscCache["cust_credits"] then + local creditphrase = TacRP:GetPhrase("wep." .. self:GetClass() .. ".credits") or self.Credits + self.MiscCache["cust_credits"] = TacRP.MultiLineText(creditphrase, w - TacRP.SS(8), "TacRP_Myriad_Pro_8") + end + + for i, k in ipairs(self.MiscCache["cust_credits"]) do + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(TacRP.SS(4), TacRP.SS(2) + TacRP.SS(8 * (i - 1))) + surface.DrawText(k) + end + end + + local tabs = {desc_box} + if self.Trivia_Caliber or self.Trivia_Manufacturer or self.Trivia_Year then + table.insert(tabs, trivia_box) + else + trivia_box:Hide() + end + if self.Credits then + table.insert(tabs, credits_box) + else + trivia_box:Hide() + end + self.ActiveDescTab = self.ActiveDescTab or 1 + + local tabs_w = TacRP.SS(172) / #tabs - #tabs * TacRP.SS(0.5) + for i = 1, #tabs do + if i != self.ActiveDescTab then + tabs[i]:Hide() + end + + local tab_button = vgui.Create("DLabel", bg) + tab_button.TabIndex = i + tab_button:SetSize(tabs_w, tabs_h) + tab_button:SetPos(scrw - TacRP.SS(172) - airgap + (TacRP.SS(2) + tabs_w) * (i - 1), stack + smallgap) + tab_button:SetText("") + tab_button:SetMouseInputEnabled(true) + tab_button:MoveToFront() + tab_button.Paint = function(self2, w2, h2) + if !IsValid(self) then return end + + local hover = #tabs > 1 and self2:IsHovered() + local selected = #tabs > 1 and self.ActiveDescTab == i + + local col_bg = Color(0, 0, 0, 150) + local col_corner = Color(255, 255, 255) + local col_text = Color(255, 255, 255) + + if selected then + col_bg = Color(150, 150, 150, 150) + col_corner = Color(50, 50, 255) + col_text = Color(0, 0, 0) + if hover then + col_bg = Color(255, 255, 255) + col_corner = Color(150, 150, 255) + col_text = Color(0, 0, 0) + end + elseif hover then + col_bg = Color(255, 255, 255) + col_corner = Color(0, 0, 0) + col_text = Color(0, 0, 0) + end + + surface.SetDrawColor(col_bg) + surface.DrawRect(0, 0, w2, h2) + TacRP.DrawCorneredBox(0, 0, w2, h2, col_corner) + + draw.SimpleText(tabs[i].PrintName, "TacRP_Myriad_Pro_8", w2 / 2, h2 / 2, col_text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + tab_button.DoClick = function(self2) + if self2.TabIndex == self.ActiveDescTab then return end + self.ActiveDescTab = self2.TabIndex + for j = 1, #tabs do + if j != self.ActiveDescTab then + tabs[j]:Hide() + else + tabs[j]:Show() + end + end + end + end + + stack = stack + TacRP.SS(48) + smallgap + end + + if !self:GetValue("NoStatBox") then + + local statgroup = self:GetValue("PrimaryMelee") and self.StatGroupsMelee or self.StatGroups + local statdisplay = self:GetValue("PrimaryMelee") and self.StatDisplayMelee or self.StatDisplay + + local tabs_h = TacRP.SS(10) + + local group_box = vgui.Create("DPanel", bg) + group_box.PrintName = TacRP:GetPhrase("cust.rating") + group_box:SetSize(TacRP.SS(164), TacRP.SS(172)) + group_box:SetPos(scrw - TacRP.SS(164) - airgap - smallgap, stack + smallgap * 2 + tabs_h) + group_box.Paint = function(self2) + if !IsValid(self) then return end + + local w, h = TacRP.SS(172), TacRP.SS(16) + local x, y = 0, 0 + + local hovered = false + local hoverindex = 0 + local hoverscore = 0 + + for i, v in ipairs(statgroup) do + + if !self.StatScoreCache[i] then + self.StaticStats = true + local sb = v.RatingFunction(self, true) + local sc = v.RatingFunction(self, false) + + local ib, ic = 0, 0 + for j = 1, #self.StatGroupGrades do + if ib == 0 and sb > self.StatGroupGrades[j][1] then + ib = j + end + if ic == 0 and sc > self.StatGroupGrades[j][1] then + ic = j + end + end + + self.StatScoreCache[i] = {{math.min(sc or 0, 100), ic}, {math.min(sb or 0, 100), ib}} + self.StaticStats = false + end + local scorecache = self.StatScoreCache[i] + local f = scorecache[1][1] / 100 + local f_base = scorecache[2][1] / 100 + + local w2, h2 = TacRP.SS(95), TacRP.SS(8) + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(x, y, w, h) + TacRP.DrawCorneredBox(x, y, w, h) + + draw.SimpleText(TacRP:GetPhrase(v.Name) or v.Name, "TacRP_Myriad_Pro_10", x + TacRP.SS(4), y + TacRP.SS(8), color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + surface.SetDrawColor(75, 75, 75, 100) + surface.DrawRect(x + TacRP.SS(64), y + TacRP.SS(4), w2, h2) + + surface.SetDrawColor(Lerp(f, 200, 0), Lerp(f, 0, 200), 0, 150) + surface.DrawRect(x + TacRP.SS(64), y + TacRP.SS(4), w2 * f, h2) + + surface.SetDrawColor(0, 0, 0, 0) + TacRP.DrawCorneredBox(x + TacRP.SS(64), y + TacRP.SS(4), w2, h2) + + for j = 1, 4 do + surface.SetDrawColor(255, 255, 255, 125) + surface.DrawRect(x + TacRP.SS(64) + w2 * (j / 5) - 0.5, y + h2 - TacRP.SS(1.5), 1, TacRP.SS(3)) + end + + surface.SetDrawColor(255, 255, 255, 20) + surface.DrawRect(x + TacRP.SS(64), y + TacRP.SS(2.5) + h2 / 2, w2 * f_base, TacRP.SS(3)) + + local grade = self.StatGroupGrades[scorecache[1][2]] + if grade then + draw.SimpleText(grade[2], "TacRP_HD44780A00_5x8_8", x + TacRP.SS(61), y + TacRP.SS(7.5), grade[3], TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + local mx, my = self2:CursorPos() + if mx > 0 and mx <= w and my > y and my <= y + h then + hovered = true + hoverindex = i + hoverscore = scorecache[1][1] + end + + y = y + TacRP.SS(18) + end + + if hovered then + local v = statgroup[hoverindex] + local todo = DisableClipping(true) + local col_bg = Color(0, 0, 0, 254) + local col_corner = Color(255, 255, 255) + local col_text = Color(255, 255, 255) + local rx, ry = self2:CursorPos() + rx = rx + TacRP.SS(16) + ry = ry + TacRP.SS(16) + + local desc = TacRP:GetPhrase(v.Description) or v.Description or "" + desc = string.Explode("\n", desc) + + if self2:GetY() + ry >= TacRP.SS(280) then + ry = ry - TacRP.SS(60) + end + + if self2:GetX() + rx + TacRP.SS(160) >= ScrW() then + rx = rx - TacRP.SS(160) + end + + local bw, bh = TacRP.SS(160), TacRP.SS(12 + (6 * #desc)) + surface.SetDrawColor(col_bg) + TacRP.DrawCorneredBox(rx, ry, bw, bh, col_corner) + + local txt = TacRP:GetPhrase(v.Name) or v.Name + surface.SetTextColor(col_text) + surface.SetFont("TacRP_Myriad_Pro_10") + surface.SetTextPos(rx + TacRP.SS(2), ry + TacRP.SS(1)) + surface.DrawText(txt) + + local scoretxt = TacRP:GetPhrase("rating.score", {score = math.Round(hoverscore, 1), max = 100}) + draw.SimpleText(scoretxt, "TacRP_Myriad_Pro_8", rx + bw - TacRP.SS(2), ry + TacRP.SS(2), col_text, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + surface.SetFont("TacRP_Myriad_Pro_6") + for j, k in pairs(desc) do + surface.SetTextPos(rx + TacRP.SS(2), ry + TacRP.SS(1 + 8 + 2) + (TacRP.SS(6 * (j - 1)))) + surface.DrawText(k) + end + + DisableClipping(todo) + end + end + + local w_statbox = TacRP.SS(164) + local x_3 = w_statbox - TacRP.SS(32) + local x_2 = x_3 - TacRP.SS(32) + local x_1 = x_2 - TacRP.SS(32) + + local function updatestat(i, k) + if k.ConVarCheck then + if !k.ConVar then k.ConVar = GetConVar(k.ConVarCheck) end + if k.ConVar:GetBool() == tobool(k.ConVarInvert) then return end + end + + if k.Spacer then + self.MiscCache["statbox"][i] = {} + return + end + local value = self:GetValue(k.Value) + local orig = self:GetBaseValue(k.Value) + local diff = nil + + if k.HideIfSame and orig == value then return end + if k.DefaultValue != nil and value == k.DefaultValue and orig == k.DefaultValue then return end + + if k.ValueCheck and self:GetValue(k.ValueCheck) != !k.ValueInvert then + return + end + + local stat_base = 0 + local stat_curr = 0 + + if k.AggregateFunction then + stat_base = k.AggregateFunction(self, true, orig) + stat_curr = k.AggregateFunction(self, false, value) + else + stat_base = math.Round(orig, 4) + stat_curr = math.Round(value, 4) + end + + if stat_base == nil and stat_cur == nil then return end + + if k.DifferenceFunction then + diff = k.DifferenceFunction(self, orig, value) + elseif isnumber(stat_base) and isnumber(stat_curr) then + if stat_curr == stat_base then + diff = "" + else + diff = math.Round((stat_curr / stat_base - 1) * 100) + if diff > 0 then + diff = "+" .. tostring(diff) .. "%" + else + diff = tostring(diff) .. "%" + end + end + end + + local txt_base = tostring(stat_base) + local txt_curr = tostring(stat_curr) + + if isbool(stat_base) then + if stat_base then + txt_base = "YES" + else + txt_base = "NO" + end + + if stat_curr then + txt_curr = "YES" + else + txt_curr = "NO" + end + end + + if k.DisplayFunction then + txt_base = k.DisplayFunction(self, true, orig) + txt_curr = k.DisplayFunction(self, false, value) + end + + if k.Unit then + local unit = TacRP:TryTranslate(k.Unit) + txt_base = txt_base .. unit + txt_curr = txt_curr .. unit + end + + local good = false + local goodorbad = false + + if k.BetterFunction then + goodorbad, good = k.BetterFunction(self, orig, value) + elseif stat_base != stat_curr then + if isnumber(stat_curr) then + good = stat_curr > stat_base + goodorbad = true + elseif isbool(stat_curr) then + good = !stat_base and stat_curr + goodorbad = true + end + end + + if k.LowerIsBetter then + good = !good + end + + if goodorbad then + if good then + surface.SetTextColor(175, 255, 175) + else + surface.SetTextColor(255, 175, 175) + end + else + surface.SetTextColor(255, 255, 255) + end + + self.MiscCache["statbox"][i] = {txt_base, txt_curr, goodorbad, good, diff} + end + + local function populate_stats(layout) + if !IsValid(self) then return end + self.StaticStats = true + layout:Clear() + self.MiscCache["statbox"] = {} + self.StatRows = {} + for i, k in ipairs(statdisplay) do + updatestat(i, k) + if !self.MiscCache["statbox"][i] then continue end + local spacer = k.Spacer + + local row = layout:Add("DPanel") + row:SetSize(w_statbox, TacRP.SS(spacer and 12 or 9)) + row.StatIndex = i + row.Paint = function(self2, w, h) + if !IsValid(self) then return end + if !self.MiscCache["statbox"] then + populate_stats(layout) + end + local sicache = self.MiscCache["statbox"][self2.StatIndex] + if !sicache then + self2:Remove() + return + end + surface.SetFont(spacer and "TacRP_Myriad_Pro_11" or "TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(TacRP.SS(3), 0) + local name = TacRP:GetPhrase(k.Name) or k.Name + surface.DrawText(name .. (spacer and "" or ":")) + + if !spacer then + surface.SetDrawColor(255, 255, 255) + surface.SetTextPos(x_1 + TacRP.SS(4), 0) + surface.DrawText(sicache[1]) + + if sicache[3] then + if sicache[4] then + surface.SetTextColor(175, 255, 175) + else + surface.SetTextColor(255, 175, 175) + end + end + + if sicache[2] != sicache[1] then + surface.SetTextPos(x_2 + TacRP.SS(4), 0) + surface.DrawText(sicache[2]) + end + + if sicache[5] then + surface.SetTextPos(x_3 + TacRP.SS(4), 0) + surface.DrawText(sicache[5]) + end + end + + surface.SetDrawColor(255, 255, 255, k.Spacer and 125 or 5) + local um, umm = k.Spacer and 3 or 1, k.Spacer and 2 or 1 + surface.DrawRect( 0, h-um, w, umm ) + end + self.StatRows[row] = i + end + self.StaticStats = false + end + + local stat_box = vgui.Create("DPanel", bg) + stat_box.PrintName = TacRP:GetPhrase("cust.stats") + stat_box:SetSize(w_statbox, TacRP.SS(172)) + stat_box:SetPos(scrw - w_statbox - airgap - smallgap, stack + smallgap * 2 + tabs_h) + stat_box.Paint = function(self2, w, h) + if !IsValid(self) then return end + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h) + + surface.SetDrawColor(255, 255, 255, 100) + --surface.DrawLine(x_1, 0, x_1, h) + --surface.DrawLine(x_2, 0, x_2, h) + --surface.DrawLine(x_3, 0, x_3, h) + surface.DrawLine(0, TacRP.SS(2 + 8 + 1), w, TacRP.SS(2 + 8 + 1)) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(TacRP.SS(4), TacRP.SS(2)) + surface.DrawText(TacRP:GetPhrase("stat.table.stat")) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(x_1 + TacRP.SS(4), TacRP.SS(2)) + surface.DrawText(TacRP:GetPhrase("stat.table.base")) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(x_2 + TacRP.SS(4), TacRP.SS(2)) + surface.DrawText(TacRP:GetPhrase("stat.table.curr")) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(x_3 + TacRP.SS(4), TacRP.SS(2)) + surface.DrawText(TacRP:GetPhrase("stat.table.diff")) + end + local stat_scroll = vgui.Create("DScrollPanel", stat_box) + stat_scroll:Dock(FILL) + stat_scroll:DockMargin(0, TacRP.SS(12), 0, 0) + local sbar = stat_scroll:GetVBar() + function sbar:Paint(w, h) + end + function sbar.btnUp:Paint(w, h) + local c_bg, c_txt = TacRP.GetPanelColor("bg2", self:IsHovered()), TacRP.GetPanelColor("text", self:IsHovered()) + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + draw.SimpleText("↑", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + function sbar.btnDown:Paint(w, h) + local c_bg, c_txt = TacRP.GetPanelColor("bg2", self:IsHovered()), TacRP.GetPanelColor("text", self:IsHovered()) surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + draw.SimpleText("↓", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + function sbar.btnGrip:Paint(w, h) + local c_bg, c_cnr = TacRP.GetPanelColor("bg2", self:IsHovered()), TacRP.GetPanelColor("corner", self:IsHovered()) surface.SetDrawColor(c_bg) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + end + local stat_layout = vgui.Create("DIconLayout", stat_scroll) + stat_layout:Dock(FILL) + stat_layout:SetLayoutDir(TOP) + -- stat_layout:SetSpaceY(TacRP.SS(2)) + populate_stats(stat_layout) + + stat_box.PaintOver = function(self2, w, h) + if !IsValid(self) then return end + local panel = vgui.GetHoveredPanel() + if self.StatRows[panel] then + local stat = statdisplay[self.StatRows[panel]] + + local todo = DisableClipping(true) + local col_bg = Color(0, 0, 0, 254) + local col_corner = Color(255, 255, 255) + local col_text = Color(255, 255, 255) + local rx, ry = self2:CursorPos() + rx = rx + TacRP.SS(16) + ry = ry + TacRP.SS(16) + + local desc = istable(stat.Description) and stat.Description or TacRP:GetPhrase(stat.Description) or stat.Description or "" + if isstring(desc) then + desc = string.Explode("\n", desc) + end + + if self2:GetY() + ry >= TacRP.SS(280) then + ry = ry - TacRP.SS(60) + end + + if self2:GetX() + rx + TacRP.SS(160) >= ScrW() then + rx = rx - TacRP.SS(160) + end + + local bw, bh = TacRP.SS(160), TacRP.SS(12 + (6 * #desc)) + surface.SetDrawColor(col_bg) + TacRP.DrawCorneredBox(rx, ry, bw, bh, col_corner) + + local txt = TacRP:GetPhrase(stat.Name) or stat.Name + surface.SetTextColor(col_text) + surface.SetFont("TacRP_Myriad_Pro_10") + surface.SetTextPos(rx + TacRP.SS(2), ry + TacRP.SS(1)) + surface.DrawText(txt) + + surface.SetFont("TacRP_Myriad_Pro_6") + for i, k in pairs(desc) do + surface.SetTextPos(rx + TacRP.SS(2), ry + TacRP.SS(1 + 8 + 2) + (TacRP.SS(6 * (i - 1)))) + surface.DrawText(k) + end + + DisableClipping(todo) + end + end + + local tabs = {group_box, stat_box} + self.ActiveTab = self.ActiveTab or 1 + + -- local tab_list = vgui.Create("DPanel", bg) + -- tab_list:SetSize(TacRP.SS(172), tabs_h) + -- tab_list:SetPos(scrw - TacRP.SS(172) - airgap, stack + smallgap) + -- tab_list:SetMouseInputEnabled(false) + -- tab_list.Paint = function() return end + + local tabs_w = TacRP.SS(172) / #tabs - #tabs * TacRP.SS(0.5) + for i = 1, #tabs do + if i != self.ActiveTab then + tabs[i]:Hide() + end + + local tab_button = vgui.Create("DLabel", bg) + tab_button.TabIndex = i + tab_button:SetSize(tabs_w, tabs_h) + tab_button:SetPos(scrw - TacRP.SS(172) - airgap + (TacRP.SS(2) + tabs_w) * (i - 1), stack + smallgap) + tab_button:SetText("") + tab_button:SetMouseInputEnabled(true) + tab_button:MoveToFront() + tab_button.Paint = function(self2, w2, h2) + if !IsValid(self) then return end + + local hover = self2:IsHovered() + local selected = self.ActiveTab == i + + local col_bg = Color(0, 0, 0, 150) + local col_corner = Color(255, 255, 255) + local col_text = Color(255, 255, 255) + + if selected then + col_bg = Color(150, 150, 150, 150) + col_corner = Color(50, 50, 255) + col_text = Color(0, 0, 0) + if hover then + col_bg = Color(255, 255, 255) + col_corner = Color(150, 150, 255) + col_text = Color(0, 0, 0) + end + elseif hover then + col_bg = Color(255, 255, 255) + col_corner = Color(0, 0, 0) + col_text = Color(0, 0, 0) + end + + surface.SetDrawColor(col_bg) + surface.DrawRect(0, 0, w2, h2) + TacRP.DrawCorneredBox(0, 0, w2, h2, col_corner) + + draw.SimpleText(tabs[i].PrintName, "TacRP_Myriad_Pro_8", w2 / 2, h2 / 2, col_text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + tab_button.DoClick = function(self2) + if self2.TabIndex == self.ActiveTab then return end + self.ActiveTab = self2.TabIndex + for j = 1, #tabs do + if j != self.ActiveTab then + tabs[j]:Hide() + else + tabs[j]:Show() + end + end + end + end + end + + local attachment_slots = {} + + local offset = (scrh - (TacRP.SS(34 + 8) * table.Count(self.Attachments))) / 2 + + self.Attachments["BaseClass"] = nil + + if TacRP.ConVars["cust_legacy"]:GetBool() then + + for slot, attslot in pairs(self.Attachments) do + local atts = TacRP.GetAttsForCats(attslot.Category or "", self) + + attachment_slots[slot] = {} + + local slot_name = vgui.Create("DPanel", bg) + slot_name:SetPos(airgap, offset + airgap - TacRP.SS(8) + ((slot - 1) * TacRP.SS(34 + 8))) + slot_name:SetSize(TacRP.SS(128), TacRP.SS(8)) + slot_name.Paint = function(self2, w, h) + if !IsValid(self) then return end + + local txt = TacRP:TryTranslate(attslot.PrintName or "Slot") + if txt then + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(Color(255, 255, 255)) + surface.SetTextPos(0, 0) + surface.DrawText(txt) + end + end + + table.sort(atts, function(a, b) + a = a or "" + b = b or "" + + if a == "" or b == "" then return true end + + local atttbl_a = TacRP.GetAttTable(a) + local atttbl_b = TacRP.GetAttTable(b) + + local order_a = 0 + local order_b = 0 + + order_a = atttbl_a.SortOrder or order_a + order_b = atttbl_b.SortOrder or order_b + + if order_a == order_b then + return (atttbl_a.PrintName or "") < (atttbl_b.PrintName or "") + end + + return order_a < order_b + end) + + local prosconspanel = vgui.Create("DPanel", bg) + prosconspanel:SetPos(airgap + ((table.Count(atts)) * TacRP.SS(34)), offset + airgap + ((slot - 1) * TacRP.SS(34 + 8))) + prosconspanel:SetSize(TacRP.SS(128), TacRP.SS(34)) + prosconspanel.Paint = function(self2, w, h) + if !IsValid(self) then return end + + local installed = attslot.Installed + + if !installed then return end + + local atttbl = TacRP.GetAttTable(installed) + + local pros = atttbl.Pros or {} + local cons = atttbl.Cons or {} + + local c = 0 + + for i, pro in pairs(pros) do + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(Color(50, 255, 50)) + surface.SetTextPos(0, TacRP.SS(c * 8)) + surface.DrawText("+" .. TacRP:TryTranslate(pro)) + + c = c + 1 + end + + for i, con in pairs(cons) do + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextColor(Color(255, 50, 50)) + surface.SetTextPos(0, TacRP.SS(c * 8)) + surface.DrawText("-" .. TacRP:TryTranslate(con)) + + c = c + 1 + end + end + + for i, att in pairs(atts) do + local slot_panel = vgui.Create("TacRPAttSlot", bg) + table.insert(attachment_slots[slot], slot_panel) + slot_panel:SetSlot(slot) + slot_panel:SetShortName(att) + slot_panel:SetWeapon(self) + slot_panel:SetPos(airgap + ((i - 1) * TacRP.SS(34)), offset + airgap + ((slot - 1) * TacRP.SS(34 + 8))) + slot_panel:SetSize(TacRP.SS(32), TacRP.SS(32)) + end + end + + else + + local rows = 1 + local cnt = table.Count(self.Attachments) + if cnt > 5 then cnt = math.ceil(cnt / 2) rows = 2 end + local ph = math.min(scrh, TacRP.SS((42 + 6) * cnt)) + + local layout = vgui.Create("DIconLayout", bg) + layout:SetSize(TacRP.SS(32 * rows + 6 * (rows - 1)), ph) + layout:SetPos(airgap, scrh / 2 - ph / 2) + layout:SetSpaceX(math.floor(TacRP.SS(6))) + layout:SetSpaceY(math.floor(TacRP.SS(6))) + layout:SetLayoutDir(LEFT) + bg.SlotLayout = layout + + local scroll = vgui.Create("DScrollPanel", bg) + scroll:SetSize(TacRP.SS(100), scrh * 0.9) + scroll:SetPos(airgap * 2 + layout:GetWide(), scrh * 0.05) + scroll:SetVisible(false) + + local slotlayout = vgui.Create("TacRPAttSlotLayout", scroll) + slotlayout:SetSize(TacRP.SS(100), scrh) + slotlayout:SetWeapon(self) + slotlayout:SetScroll(scroll) + slotlayout:SetSpaceY(TacRP.SS(4)) + slotlayout:SetSpaceX(TacRP.SS(4)) + slotlayout:SetLayoutDir(TOP) + if self.LastCustomizeSlot then + slotlayout:SetSlot(self.LastCustomizeSlot) + end + bg.ScrollLayout = slotlayout + -- slotlayout:Dock(FILL) + + for slot, attslot in pairs(self.Attachments) do + attachment_slots[slot] = {} + + local slot_bg = vgui.Create("DPanel", layout) + slot_bg:SetSize(TacRP.SS(32), TacRP.SS(42)) + slot_bg.Paint = function() end + + local slot_icon = vgui.Create("TacRPAttSlot", slot_bg) + slot_icon:SetSlot(slot) + if (attslot.Installed or "") != "" then + slot_icon:SetShortName(attslot.Installed) + end + slot_icon:SetWeapon(self) + slot_icon:SetIsMenu(true) + slot_icon:SetSlotLayout(slotlayout) + slot_icon:SetPos(0, TacRP.SS(10)) + slot_icon:SetSize(TacRP.SS(32), TacRP.SS(32)) + + local slot_name = vgui.Create("DPanel", slot_bg) + slot_name:SetSize(TacRP.SS(32), TacRP.SS(8)) + slot_name.Paint = function(self2, w, h) + if !IsValid(self) then return end + local col_bg, col_corner, col_text = slot_icon:GetColors() + + surface.SetDrawColor(col_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, col_corner) + + local txt = TacRP:TryTranslate(attslot.PrintName or "Slot") + if txt then + draw.SimpleText(txt, "TacRP_Myriad_Pro_8", w / 2, h / 2, col_text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + end + + local cvarbox = vgui.Create("DCheckBox", bg) + cvarbox:SetSize(TacRP.SS(8), TacRP.SS(8)) + cvarbox:SetPos(airgap, scrh - TacRP.SS(10)) + cvarbox:SetText("") + cvarbox:SetConVar("tacrp_cust_legacy") + function cvarbox.Paint(self2, w, h) + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(self2:IsHovered(), self2:GetChecked()) + surface.SetDrawColor(c_bg) + surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + if self2:GetChecked() then + draw.SimpleText("O", "TacRP_HD44780A00_5x8_4", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + local todo = DisableClipping(true) + + draw.SimpleText(TacRP:GetPhrase("menu.legacy") or "Legacy Menu", "TacRP_Myriad_Pro_8", w + TacRP.SS(2), h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + + DisableClipping(todo) + end + function cvarbox.DoClick(self2) + self2:Toggle() + timer.Simple(0, function() + self:CreateCustomizeHUD() + end) + end + + -- tacrp_drop + local primarygrenade = self:GetValue("PrimaryGrenade") + if (engine.ActiveGamemode() == "terrortown" or TacRP.ConVars["allowdrop"]:GetBool()) and TacRP.ConVars["cust_drop"]:GetBool() and (!primarygrenade or !TacRP.IsGrenadeInfiniteAmmo(primarygrenade)) then + local phrase = primarygrenade and "cust.drop_nade" or "cust.drop_wep" + local dropbox = vgui.Create("DButton", bg) + local bw, bh = TacRP.SS(52), TacRP.SS(10) + dropbox:SetSize(bw, bh) + dropbox:SetPos(ScrW() / 2 - bw / 2, scrh - bh - smallgap / 2) + dropbox:SetText("") + function dropbox.Paint(self2, w, h) + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(self2:IsHovered(), self2:IsDown()) + surface.SetDrawColor(c_bg) + -- surface.DrawRect(0, 0, w, h) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + draw.SimpleText(TacRP:GetPhrase(phrase), "TacRP_Myriad_Pro_8", w / 2, h / 2, c_txt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + end + function dropbox.DoClick(self2) + if engine.ActiveGamemode() == "terrortown" then + LocalPlayer():ConCommand("ttt_dropweapon") + else + LocalPlayer():ConCommand("tacrp_drop") + end + end + end + + local news_s = TacRP.SS(12) + local news_i = TacRP.SS(8) + local newsbutton = vgui.Create("DButton", bg) + newsbutton:SetSize(news_s, news_s) + newsbutton:SetPos(smallgap, smallgap / 2) + newsbutton:SetText("") + function newsbutton.Paint(self2, w, h) + local c_bg, c_cnr, c_txt = TacRP.GetPanelColors(self2:IsHovered(), self2:IsDown()) + surface.SetDrawColor(c_bg) + TacRP.DrawCorneredBox(0, 0, w, h, c_cnr) + + if self2.flash then + local todo = DisableClipping(true) + draw.NoTexture() + surface.SetDrawColor(c_bg) + draw.SimpleTextOutlined(string.upper(self2.flash), "TacRP_HD44780A00_5x8_6", w + smallgap, h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, 2, Color(0, 0, 0, 150)) + DisableClipping(todo) + local c = (math.sin(SysTime() * 10)) * 30 + (self2:IsHovered() and 50 or 225) + surface.SetDrawColor(c, c, c, 255) + else + surface.SetDrawColor(c_txt) + end + + surface.SetMaterial(news) + surface.DrawTexturedRect((news_s - news_i) / 2, (news_s - news_i) / 2, news_i,news_i) + end + function newsbutton.DoClick(self2) + LocalPlayer():ConCommand("tacrp_news") + end + TacRP.FetchNews(function() + if !self.CustomizeHUD then return end + + for i, v in ipairs(TacRP.NewsLoaded) do + if IsValid(newsbutton) and !TacRP.NewsRead[v.Key] then + newsbutton.flash = v.Type or "article" + break + end + end + end) + + hook.Run("TacRP_CreateCustomizeHUD", self, self.CustomizeHUD) + + self.StaticStats = false +end + +function SWEP:RemoveCustomizeHUD() + if self.CustomizeHUD then + self.CustomizeHUD:Remove() + + if (self.GrenadeMenuAlpha or 0) != 1 and (self.BlindFireMenuAlpha or 0) != 1 then + gui.EnableScreenClicker(false) + TacRP.CursorEnabled = false + end + + self.LastHintLife = CurTime() + end +end + +function SWEP:DrawCustomizeHUD() + + local customize = self:GetCustomize() + + if customize and !lastcustomize then + self:CreateCustomizeHUD() + elseif !customize and lastcustomize then + self:RemoveCustomizeHUD() + end + + lastcustomize = self:GetCustomize() + + -- if self:GetCustomize() then + -- customizedelta = math.Approach(customizedelta, 1, FrameTime() * 1 / 0.25) + -- else + -- customizedelta = math.Approach(customizedelta, 0, FrameTime() * 1 / 0.25) + -- end + + -- local curvedcustomizedelta = self:Curve(customizedelta) + + -- if curvedcustomizedelta > 0 then + -- RunConsoleCommand("pp_bokeh", "1") + -- else + -- RunConsoleCommand("pp_bokeh", "0") + -- end + + -- RunConsoleCommand("pp_bokeh_blur", tostring(curvedcustomizedelta * 5)) + -- RunConsoleCommand("pp_bokeh_distance", 0) + -- RunConsoleCommand("pp_bokeh_focus", tostring(((1 - curvedcustomizedelta) * 11) + 1)) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_hint.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_hint.lua new file mode 100644 index 0000000..f2971d7 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_hint.lua @@ -0,0 +1,242 @@ +local glyphs = { + ["MOUSE1"] = Material("tacrp/glyphs/shared_mouse_l_click_lg.png", "mips smooth"), + ["MOUSE2"] = Material("tacrp/glyphs/shared_mouse_r_click_lg.png", "mips smooth"), + ["MOUSE3"] = Material("tacrp/glyphs/shared_mouse_mid_click_lg.png", "mips smooth"), + ["MOUSE4"] = Material("tacrp/glyphs/shared_mouse_4_lg.png", "mips smooth"), + ["MOUSE5"] = Material("tacrp/glyphs/shared_mouse_5_lg.png", "mips smooth"), + + ["MWHEELUP"] = Material("tacrp/glyphs/shared_mouse_scroll_up_lg.png", "mips smooth"), + ["MWHEELDOWN"] = Material("tacrp/glyphs/shared_mouse_scroll_down_lg.png", "mips smooth"), +} + +local rename = { + KP_INS = "KP 0", + KP_END = "KP 1", + KP_DOWNARROW = "KP 2", + KP_PGDN = "KP 3", + KP_LEFTARROW = "KP 4", + KP_5 = "KP 5", + KP_RIGHTARROW = "KP 6", + KP_HOME = "KP 7", + KP_UPARROW = "KP 8", + KP_PGUP = "KP 9", + KP_SLASH = "KP /", + KP_MULTIPLY = "KP *", + KP_MINUS = "KP -", + KP_PLUS = "KP +", + KP_ENTER = "KP ENTER", + KP_DEL = "KP .", +} + +local mat_bipod = Material("tacrp/hud/bipod.png", "mips smooth") + +SWEP.CachedCapabilities = {} +function SWEP:GetHintCapabilities() + self.CachedCapabilities = {} + + if self:GetValue("PrimaryMelee") then + self.CachedCapabilities["+attack"] = {so = 0, str = "hint.melee"} + elseif self:GetValue("PrimaryGrenade") then + self.CachedCapabilities["+attack"] = {so = 0, str = "hint.quicknade.over"} + self.CachedCapabilities["+attack2"] = {so = 0.1, str = "hint.quicknade.under"} + end + -- hopefully you don't need me to tell you how to shoot a gun + + local bind_customize = "+menu_context" + if TacRP.GetKeyIsBound("+tacrp_customize") then + bind_customize = "+tacrp_customize" + end + + if self:GetScopeLevel() != 0 then + if TacRP.ConVars["togglepeek"]:GetBool() then + self.CachedCapabilities[bind_customize] = {so = 2, str = "hint.peek.toggle"} + else + self.CachedCapabilities[bind_customize] = {so = 2, str = "hint.peek.hold"} + end + + if self:CanHoldBreath() then + self.CachedCapabilities["+speed"] = {so = 2.1, str = "hint.hold_breath"} + end + elseif #self.Attachments > 0 then + self.CachedCapabilities[bind_customize] = {so = 1, str = "hint.customize"} + else + self.CachedCapabilities[bind_customize] = {so = 1, str = "hint.inspect"} + end + + if self:GetFiremodeAmount() > 1 and !self:GetSafe() then + self.CachedCapabilities["+use/+reload"] = {so = 11, str = "hint.firemode"} + end + if self:GetFiremodeAmount() > 0 and !self:DoForceSightsBehavior() then + if self:GetSafe() then + self.CachedCapabilities["+use/+attack2"] = {so = 12, str = "hint.safety.turn_off"} + else + self.CachedCapabilities["+use/+attack2"] = {so = 12, str = "hint.safety.turn_on"} + end + end + + if self:GetValue("CanMeleeAttack") and !self:GetValue("PrimaryMelee") then + local bind = "+use/+attack" + if TacRP.GetKeyIsBound("+tacrp_melee") then + bind = TacRP.GetBindKey("+tacrp_melee") + elseif self:DoOldSchoolScopeBehavior() then + bind = "+attack2" + end + self.CachedCapabilities[bind] = {so = 30, str = "hint.melee"} + end + + if self:GetValue("CanToggle") and TacRP.ConVars["toggletactical"]:GetBool() then + local tactical_text = self:GetValue("CustomTacticalHint") or TacRP:GetPhrase("hint.toggle_tactical", {TacRP:TryTranslate(self:GetValue("TacticalName") or "hint.tac")}) + local bind = nil + if TacRP.GetKeyIsBound("+tacrp_tactical") then + bind = "+tacrp_tactical" + end + if TacRP.ConVars["flashlight_alt"]:GetBool() then + self.CachedCapabilities[bind or "+walk/impulse 100"] = {so = 31, str = tactical_text} + self.CachedCapabilities["impulse 100"] = {so = 32, str = "hint.hl2_flashlight"} + else + self.CachedCapabilities[bind or "impulse 100"] = {so = 31, str = tactical_text} + self.CachedCapabilities["+walk/impulse 100"] = {so = 32, str = "hint.hl2_flashlight"} + end + end + + -- blindfire / quickthrow + if self:GetValue("CanBlindFire") and self:GetScopeLevel() == 0 and !self:GetSafe() then + if TacRP.ConVars["blindfiremenu"]:GetBool() then + self.CachedCapabilities["+zoom"] = {so = 39, str = "hint.blindfire.menu"} + else + if self:GetOwner():KeyDown(IN_ZOOM) then + self.CachedCapabilities = {} + self.CachedCapabilities["+zoom/+forward"] = {so = 39, str = "hint.blindfire.up"} + self.CachedCapabilities["+zoom/+moveleft"] = {so = 39.1, str = "hint.blindfire.left"} + self.CachedCapabilities["+zoom/+moveright"] = {so = 39.2, str = "hint.blindfire.right"} + self.CachedCapabilities["+zoom/+back"] = {so = 39.3, str = "hint.blindfire.cancel"} + if !TacRP.ConVars["idunwannadie"]:GetBool() then + self.CachedCapabilities["+zoom/+speed/+walk"] = {so = 39.4, str = "hint.blindfire.kys"} + end + return self.CachedCapabilities + else + self.CachedCapabilities["+zoom"] = {so = 39, str = "hint.blindfire"} + end + end + end + + if self:IsQuickNadeAllowed() then + local bound1, bound2 = TacRP.GetKeyIsBound("+grenade1"), TacRP.GetKeyIsBound("+grenade2") + if bound1 then + self.CachedCapabilities["+grenade1"] = {so = 35, str = "hint.quicknade.throw"} + end + if bound2 then + if TacRP.ConVars["nademenu"]:GetBool() then + if TacRP.ConVars["nademenu_click"]:GetBool() and self.GrenadeMenuAlpha == 1 then + self.CachedCapabilities = {} + self.CachedCapabilities["+grenade2"] = {so = 36, str = "hint.quicknade.menu"} + self.CachedCapabilities["+grenade2/+attack"] = {so = 36.1, str = "hint.quicknade.over"} + self.CachedCapabilities["+grenade2/+attack2"] = {so = 36.2, str = "hint.quicknade.under"} + if TacRP.AreTheGrenadeAnimsReadyYet then + self.CachedCapabilities["+grenade2/MOUSE3"] = {so = 36.3, str = "hint.quicknade.pull_out"} + end + -- if TacRP.GetKeyIsBound("+grenade1") then + -- self.CachedCapabilities["+grenade1"] = {so = 36.4, str = "Quickthrow"} + -- end + + return self.CachedCapabilities + else + self.CachedCapabilities["+grenade2"] = {so = 36, str = "hint.quicknade.menu"} + end + else + self.CachedCapabilities["+grenade2"] = {so = 36, str = "hint.quicknade.next"} + self.CachedCapabilities["+walk/+grenade2"] = {so = 37, str = "hint.quicknade.prev"} + end + end + if !bound2 and !bound1 then + self.CachedCapabilities["+grenade1"] = {so = 36, str = "hint.quicknade.throw"} + end + end + + if engine.ActiveGamemode() == "terrortown" then + self.CachedCapabilities["+use/+zoom"] = {so = 1001, str = "hint.ttt.radio"} + self.CachedCapabilities["+use/+menu_context"] = {so = 1002, str = "hint.ttt.shop"} + end + + self:RunHook("Hook_GetHintCapabilities", self.CachedCapabilities) + + return self.CachedCapabilities +end + +SWEP.LastHintLife = 0 +function SWEP:DrawHints() + if LocalPlayer() != self:GetOwner() then return end + local a = TacRP.ConVars["hints_always"]:GetBool() and 1 or math.Clamp(((self.LastHintLife + 4) - CurTime()) / 1, 0, 1) + if a <= 0 then return end + + local font = TacRP.ConVars["hints_altfont"]:GetBool() and "TacRP_Myriad_Pro_8" or "TacRP_HD44780A00_5x8_5" + + local caps = self:GetHintCapabilities() + + local clr_w = Color(255, 255, 255, a * 255) + + local x, y = TacRP.SS(4), ScrH() / 2 + local row = TacRP.SS(12) + local glyphsize = TacRP.SS(8) + local w, h = TacRP.SS(100), table.Count(caps) * row + surface.SetDrawColor(0, 0, 0, 150 * a) + TacRP.DrawCorneredBox(x, y - h / 2, w, h, clr_w) + local x2, x3 = TacRP.SS(6), TacRP.SS(30) + local y2 = y - h / 2 + for k, v in SortedPairsByMemberValue(self.CachedCapabilities, "so") do + local keys = string.Explode("/", k, false) + surface.SetDrawColor(clr_w) + local x_glyph = x2 + local y_glyph = y2 + row / 2 + for i = 1, #keys do + local key = TacRP.GetBindKey(keys[i]) + if glyphs[key] then + surface.SetMaterial(glyphs[key]) + surface.DrawTexturedRect(x + x_glyph, y_glyph - glyphsize / 2, TacRP.SS(8), glyphsize) + -- surface.DrawOutlinedRect(x + x_glyph, y_glyph - glyphsize / 2, glyphsize, glyphsize, 2) + x_glyph = x_glyph + glyphsize + else + key = rename[key] or key + local addw = string.len(key) * TacRP.SS(3.5) + TacRP.SS(5) + surface.DrawOutlinedRect(x + x_glyph, y_glyph - glyphsize / 2, addw, glyphsize, 1) + draw.SimpleText(key, "TacRP_HD44780A00_5x8_5", x + x_glyph + addw / 2, y_glyph, clr_w, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + x_glyph = x_glyph + addw + end + + if i < #keys then + x_glyph = x_glyph + TacRP.SS(2) + else + x_glyph = x_glyph + TacRP.SS(4) + end + end + + draw.SimpleText(TacRP:TryTranslate(v.str), font, x + math.max(x3, x_glyph), y2 + row / 2, clr_w, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + y2 = y2 + row + end +end + +function SWEP:DrawBipodHint(x, y, s) + surface.SetDrawColor(0, 0, 0, 150) + TacRP.DrawCorneredBox(x, y - s / 2, s, s, color_white) + if self:CanBipod() then + if self:GetInBipod() then + surface.SetDrawColor(255, 255, 255, 255) + else + local c = math.sin(CurTime() * 8) * 25 + 175 + surface.SetDrawColor(c, c, c, 255) + end + surface.SetMaterial(mat_bipod) + surface.DrawTexturedRect(x, y - s / 2, s, s) + + if !self:GetInBipod() then + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(glyphs["MOUSE2"]) + surface.DrawTexturedRect(x + s * 0.3333, y + s * 0.15, s / 3, s / 3) + end + else + surface.SetDrawColor(100, 100, 100, 255) + surface.SetMaterial(mat_bipod) + surface.DrawTexturedRect(x, y - s / 2, s, s) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_holosight.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_holosight.lua new file mode 100644 index 0000000..b875437 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_holosight.lua @@ -0,0 +1,84 @@ +function SWEP:DoHolosight(mdl) + if TacRP.OverDraw then return end + if self:GetSightAmount() <= 0 then return end + + local ref = 64 + + render.UpdateScreenEffectTexture() + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetStencilWriteMask(255) + render.SetStencilTestMask(255) + + render.SetBlend(0) + + render.SetStencilReferenceValue(ref) + + mdl:DrawModel() + + render.SetBlend(1) + + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilCompareFunction(STENCIL_EQUAL) + + -- cam.Start2D() + + -- surface.SetDrawColor(255, 255, 255) + -- surface.DrawRect(0, 0, ScrW(), ScrH()) + + -- render.SetColorMaterial() + -- render.DrawScreenQuad() + + local img = self:GetValue("Holosight") + + if img then + local pos = self:GetOwner():EyePos() + local dir = (self:GetShootDir() + self:GetOwner():GetViewPunchAngles() * 0.5):Forward() -- mdl:GetAngles():Forward() + local size = 512 * TacRP.HoloSS + + pos = pos + dir * 9000 + + -- cam.Start3D() + + local eyedist = WorldToLocal(mdl:GetPos(), mdl:GetAngles(), EyePos(), EyeAngles()).x + + render.DepthRange(0, 0.005 + (0.0005 * eyedist / 20)) + + render.SetMaterial(img) + render.DrawQuadEasy(pos, -dir, size, size, Color(255, 255, 255), 180) + + -- cam.End3D() + + -- local toscreen = pos:ToScreen() + + -- local x = toscreen.x + -- local y = toscreen.y + + -- local ss = TacRP.SS(32) + -- local sx = x - (ss / 2) + -- local sy = y - (ss / 2) + + -- local shakey = math.min(cross * 35, 3) + + -- sx = sx + math.Round(math.Rand(-shakey, shakey)) + -- sy = sy + math.Round(math.Rand(-shakey, shakey)) + + -- surface.SetMaterial(img) + -- surface.SetDrawColor(255, 255, 255, 255) + -- surface.DrawTexturedRect(sx, sy, ss, ss) + + -- surface.SetDrawColor(0, 0, 0) + -- surface.DrawRect(0, 0, w, sy) + -- surface.DrawRect(0, sy + ss, w, h - sy) + + -- surface.DrawRect(0, 0, sx, h) + -- surface.DrawRect(sx + ss, 0, w - sx, h) + end + -- cam.End2D() + + render.SetStencilEnable(false) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_hud.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_hud.lua new file mode 100644 index 0000000..1284e96 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_hud.lua @@ -0,0 +1,1015 @@ +function SWEP:ShouldDrawCrosshair() + if !TacRP.ConVars["crosshair"]:GetBool() then + return self:DoLowerIrons() and self:GetSightAmount() > 0 and !self:GetPeeking() and !self:GetReloading() + end + return !self:GetReloading() and !self:GetCustomize() and !self:GetSafe() and self:GetBlindFireMode() == TacRP.BLINDFIRE_NONE + and !(self:SprintLock() and !self.DrawCrosshairInSprint) + and !(self:DoForceSightsBehavior() and !self:GetPeeking()) + and !self:GetJammed() + and (self:GetSightAmount() <= 0.5 or (self:GetPeeking() and !self:GetValue("ThermalCamera")) or self:DoLowerIrons()) + and !(self:IsQuickNadeAllowed() and tobool(self:GetOwner():GetInfo("tacrp_nademenu")) and self:GetOwner():KeyDown(IN_GRENADE2)) + and !(self:GetValue("CanBlindFire") and tobool(self:GetOwner():GetInfo("tacrp_blindfiremenu")) and (self:GetOwner():KeyDown(IN_ZOOM) or self:GetOwner().TacRPBlindFireDown)) +end + +function SWEP:DoDrawCrosshair(x, y) + local ft = FrameTime() + local ply = self:GetOwner() + + self.CrosshairAlpha = self.CrosshairAlpha or 0 + if !self:ShouldDrawCrosshair() then + self.CrosshairAlpha = math.Approach(self.CrosshairAlpha, 0, -10 * ft) + else + self.CrosshairAlpha = math.Approach(self.CrosshairAlpha, 1, 5 * ft) + end + + local dev = TacRP.Developer() + local tacfunc + if self:GetValue("TacticalCrosshair") and self:GetTactical() then + tacfunc = self:GetValue("TacticalCrosshair") + elseif !dev and self.CrosshairAlpha <= 0 then return true end + + local loweriron = self:DoLowerIrons() and self:GetSightAmount() > 0 and !self:GetPeeking() and !self:GetReloading() + + local dir = self:GetShootDir(true) + + local tr = util.TraceLine({ + start = self:GetMuzzleOrigin(), + endpos = self:GetMuzzleOrigin() + (dir:Forward() * 50000), + mask = MASK_SHOT, + filter = self:GetOwner() + }) + cam.Start3D() + local w2s = tr.HitPos:ToScreen() + x = math.Round(w2s.x) + y = math.Round(w2s.y) + cam.End3D() + local x2, y2 = x, y + + local spread = TacRP.GetFOVAcc(self) + local sway = self:IsSwayEnabled() and self:GetSwayAmount() or self:GetForcedSwayAmount() + + local truepos = self:GetValue("TacticalCrosshairTruePos") + if dev or truepos then + local tr2 = util.TraceLine({ + start = self:GetMuzzleOrigin(), + endpos = self:GetMuzzleOrigin() + (self:GetShootDir():Forward() * 50000), + mask = MASK_SHOT, + filter = self:GetOwner() + }) + cam.Start3D() + local tw2s = tr2.HitPos:ToScreen() + x2 = math.Round(tw2s.x) + y2 = math.Round(tw2s.y) + cam.End3D() + end + + if tacfunc then + tacfunc(self, truepos and x2 or x, truepos and y2 or y, spread, sway) + end + + spread = math.Round( math.max(spread, 2) + TacRP.SS(sway * math.pi)) + + if !dev and self.CrosshairAlpha <= 0 then return true end + + local clr + if TacRP.ConVars["ttt_rolecrosshair"] and TacRP.ConVars["ttt_rolecrosshair"]:GetBool() then + if GetRoundState() == ROUND_PREP or GetRoundState() == ROUND_POST then + clr = Color(255, 255, 255) + elseif ply.GetRoleColor and ply:GetRoleColor() then + clr = ply:GetRoleColor() -- TTT2 feature + elseif ply:IsActiveTraitor() then + clr = Color(255, 50, 50) + elseif ply:IsActiveDetective() then + clr = Color(50, 50, 255) + else + clr = Color(50, 255, 50) + end + end + + if loweriron then + clr = clr or color_white + surface.SetDrawColor(clr.r, clr.g, clr.b, clr.a * self.CrosshairAlpha * 0.75 * self:GetSightAmount()) + surface.DrawRect(x - 1, y - 1, 3, 3) + surface.DrawRect(x - 13, y - 0.5, 6, 3) + surface.DrawRect(x + 8, y - 0.5, 6, 3) + + surface.SetDrawColor(0, 0, 0, clr.a * self.CrosshairAlpha * self:GetSightAmount() * 0.7) + surface.DrawOutlinedRect(x - 2, y - 2, 5, 5, 1) + surface.DrawOutlinedRect(x - 14, y - 1.25, 8, 5, 1) + surface.DrawOutlinedRect(x + 7, y - 1.25, 8, 5, 1) + elseif TacRP.ConVars["crosshair"]:GetBool() then + clr = clr or Color(50, 255, 50) + surface.SetDrawColor(clr.r, clr.g, clr.b, clr.a * self.CrosshairAlpha) + + surface.DrawRect(x, y, 1, 1) + if self.CrosshairStatic then spread = 16 end + local w = 16 + surface.DrawLine(x, y - spread - w, x, y - spread) + surface.DrawLine(x, y + spread, x, y + spread + w) + surface.DrawLine(x - spread - w, y, x - spread, y) + surface.DrawLine(x + spread, y, x + spread + w, y) + end + + -- Developer Crosshair + if dev then + + if self:StillWaiting() then + surface.SetDrawColor(150, 150, 150, 75) + else + surface.SetDrawColor(255, 50, 50, 75) + end + surface.DrawLine(x2, y2 - 256, x2, y2 + 256) + surface.DrawLine(x2 - 256, y2, x2 + 256, y2) + spread = TacRP.GetFOVAcc(self) + local recoil_txt = "Recoil: " .. tostring(math.Round(self:GetRecoilAmount() or 0, 3)) + surface.DrawCircle(x2, y2, spread, 255, 255, 255, 75) + surface.DrawCircle(x2, y2, spread + 1, 255, 255, 255, 75) + surface.SetFont("TacRP_Myriad_Pro_32_Unscaled") + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(x2 - 256, y2) + surface.DrawText(recoil_txt) + local spread_txt = tostring("Cone: " .. math.Round(math.deg(self:GetSpread()), 3)) .. " deg" + surface.SetTextPos(x2 - 256, y2 - 34) + surface.DrawText(spread_txt) + local spread_txt = tostring(math.Round( math.deg(self:GetSpread()) * 60, 3)) .. "MOA" + surface.SetTextPos(x2 - 256, y2 - 66) + surface.DrawText(spread_txt) + -- local tw = surface.GetTextSize(spread_txt) + -- surface.SetTextPos(x2 + 256 - tw, y2) + -- surface.DrawText(spread_txt) + + local dist = (tr.HitPos - tr.StartPos):Length() + local dist_txt = math.Round(dist) .. " HU" + local tw = surface.GetTextSize(dist_txt) + surface.SetTextPos(x2 + 256 - tw, y2) + surface.DrawText(dist_txt) + + local damage_txt = math.Round(self:GetDamageAtRange(dist)) .. " DMG" + local tw2 = surface.GetTextSize(damage_txt) + surface.SetTextPos(x2 + 256 - tw2, y2 - 34) + surface.DrawText(damage_txt) + + local sprint_txt = math.Round(self:GetSprintAmount() * 100) .. "%" + local tw3 = surface.GetTextSize(sprint_txt) + surface.SetTextPos(x2 - tw3 * 0.5, y2 + 256) + surface.DrawText(sprint_txt) + + local sight_txt = math.Round(self:GetSightAmount() * 100) .. "%" + local tw4 = surface.GetTextSize(sight_txt) + surface.SetTextPos(x2 - tw4 * 0.5, y2 + 256 + 32) + surface.DrawText(sight_txt) + end + + return true +end + +function SWEP:GetBinding(bind) + local t_bind = input.LookupBinding(bind) + + if !t_bind then + t_bind = "BIND " .. bind .. "!" + end + + return string.upper(t_bind) +end + +local mat_vignette = Material("tacrp/hud/vignette.png", "mips smooth") +local mat_radial = Material("tacrp/grenades/radial.png", "mips smooth") + +local rackrisetime = 0 +local lastrow = 0 + +local lasthp = 0 +local lasthealtime = 0 +local lastdmgtime = 0 +local lastarmor = 0 + +local faceindex = 0 + +local shockedtime = 0 +local lastblindfiremode = 0 + +local col = Color(255, 255, 255) +local col_hi = Color(255, 150, 0) +local col_hi2 = Color(255, 230, 200) +local col_dark = Color(255, 255, 255, 20) + +function SWEP:ShouldDrawBottomBar() + return self:GetFiremodeAmount() > 0 or self:IsQuickNadeAllowed() +end + +function SWEP:DrawBottomBar(x, y, w, h) + if self:GetFiremodeAmount() > 0 then + if self:GetSafe() then + surface.SetMaterial(self:GetFiremodeMat(0)) + else + surface.SetMaterial(self:GetFiremodeMat(self:GetCurrentFiremode())) + end + surface.SetDrawColor(col) + local sfm = TacRP.SS(14) + surface.DrawTexturedRect(x + w - sfm - TacRP.SS(1 + 10), y + h - sfm - TacRP.SS(1), sfm, sfm) + end + + local fmoffset = TacRP.SS(25) + + if self:GetFiremodeAmount() > 1 and !self:GetSafe() then + local nextfm = TacRP.GetBind("use") .. "+" .. TacRP.GetBind("reload") + + surface.SetTextColor(col) + surface.SetFont("TacRP_HD44780A00_5x8_4") + local tw = surface.GetTextSize(nextfm) + surface.SetTextPos(x + w - tw - TacRP.SS(2), y + h - TacRP.SS(14)) + surface.DrawText(nextfm) + + surface.SetMaterial(self:GetFiremodeMat(self:GetNextFiremode())) + surface.SetDrawColor(col) + local nfm = TacRP.SS(8) + surface.DrawTexturedRect(x + w - nfm - TacRP.SS(4), y + h - nfm - TacRP.SS(1), nfm, nfm) + elseif self:GetSafe() then + surface.SetMaterial(self:GetFiremodeMat(self:GetCurrentFiremode())) + surface.SetDrawColor(col) + local nfm = TacRP.SS(8) + surface.DrawTexturedRect(x + w - nfm - TacRP.SS(4), y + h - nfm - TacRP.SS(1), nfm, nfm) + elseif self:GetFiremodeAmount() == 0 then + fmoffset = 0 + end + + if self:IsQuickNadeAllowed() then + local nade = self:GetGrenade() + + local qty = nil --"INF" + + if nade.Singleton then + qty = self:GetOwner():HasWeapon(nade.GrenadeWep) and 1 or 0 + elseif !TacRP.IsGrenadeInfiniteAmmo(nade.Index) then + qty = self:GetOwner():GetAmmoCount(nade.Ammo) + end + + local sg = TacRP.SS(14) + + if nade.Icon then + surface.SetMaterial(nade.Icon) + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRect(x + TacRP.SS(2), y + h - sg - TacRP.SS(1), sg, sg) + end + + surface.SetTextColor(col) + if qty then + surface.SetTextPos(x + TacRP.SS(4) + sg, y + h - sg + TacRP.SS(8)) + surface.SetFont("TacRP_HD44780A00_5x8_4") + surface.DrawText("x" .. qty) + surface.SetFont("TacRP_HD44780A00_5x8_6") + else + surface.SetFont("TacRP_HD44780A00_5x8_8") + end + + local nadetext = TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".name") or nade.PrintName + surface.SetTextPos(x + TacRP.SS(4) + sg, y + h - sg + TacRP.SS(1)) + surface.DrawText(nadetext) + + + local mat = nil + if !TacRP.ConVars["nademenu"]:GetBool() then + mat = self:GetNextGrenade().Icon + else + mat = mat_radial + end + + local nsg = TacRP.SS(10) + + if mat then + surface.SetMaterial(mat) + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRect(x + w - fmoffset - TacRP.SS(5) - nsg, y + h - nsg, nsg, nsg) + end + + local nextnadetxt = TacRP.GetBind("+grenade2") + + surface.SetTextColor(col) + surface.SetFont("TacRP_HD44780A00_5x8_4") + local tw = surface.GetTextSize(nextnadetxt) + surface.SetTextPos(x + w - fmoffset - (tw / 2) - nsg, y + h - nsg - TacRP.SS(4)) + surface.DrawText(nextnadetxt) + end +end + +local breath_a = 0 +local last = 1 +local lastt = 0 +function SWEP:DrawBreathBar(x, y, w, h) + if CurTime() > lastt + 1 then + breath_a = math.Approach(breath_a, 0, FrameTime() * 2) + elseif breath_a < 1 then + breath_a = math.Approach(breath_a, 1, FrameTime()) + end + local breath = self:GetBreath() + if last != self:GetBreath() then + lastt = CurTime() + last = breath + end + if breath_a == 0 then return end + + x = x - w / 2 + y = y - h / 2 + + surface.SetDrawColor(90, 90, 90, 200 * breath_a) + surface.DrawOutlinedRect(x - 1, y - 1, w + 2, h + 2, 1) + surface.SetDrawColor(0, 0, 0, 75 * breath_a) + surface.DrawRect(x, y, w, h) + + if self:GetOutOfBreath() then + surface.SetDrawColor(255, 255 * breath ^ 0.5, 255 * breath, 150 * breath_a) + else + surface.SetDrawColor(255, 255, 255, 150 * breath_a) + end + + surface.DrawRect(x, y, w * self:GetBreath(), h) +end + +function SWEP:DrawHUDBackground() + self:DoScope() + + if !GetConVar("cl_drawhud"):GetBool() then return end + + -- draw a vignette effect around the screen based on recoil + local recoil = self:GetRecoilAmount() + if recoil > 0 and TacRP.ConVars["vignette"]:GetBool() then + local recoil_pct = math.Clamp(recoil / self:GetValue("RecoilMaximum"), 0, 1) ^ 1.25 + local delta = self:Curve(recoil_pct) + surface.SetDrawColor(0, 0, 0, 200 * delta) + surface.SetMaterial(mat_vignette) + surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) + end + + if self:GetValue("BlindFireCamera") then + self:DoCornershot() + end + + if self:GetValue("ThermalCamera") then + self:DoThermalCam() + end + + self:DrawLockOnHUD() + + if self:GetValue("TacticalDraw") and self:GetTactical() then + self:GetValue("TacticalDraw")(self) + end + + self:DrawCustomizeHUD() + + if !self:GetCustomize() and TacRP.ConVars["hints"]:GetBool() then + self:DrawHints() + end + + if !self:GetCustomize() and !TacRP.ConVars["jam_autoclear"]:GetBool() and self:GetJammed() then + local text = "[" .. TacRP.GetBindKey("+reload") .. "] " .. TacRP:GetPhrase("hint.unjam") + local font = "TacRP_HD44780A00_5x8_4" + surface.SetFont(font) + local w, h = surface.GetTextSize(text) + w = w + TacRP.SS(8) + h = h + TacRP.SS(4) + + surface.SetDrawColor(0, 0, 0, 200) + TacRP.DrawCorneredBox(ScrW() / 2 - w / 2, ScrH() / 2, w, h) + + draw.SimpleText(text, font, ScrW() / 2, ScrH() / 2 + h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif DarkRP and TacRP.ConVars["rp_biocode_cp"]:GetBool() and self:GetNWBool("TacRP_PoliceBiocode") then + local text = TacRP:GetPhrase("hint.rp_biocode_cp") + local font = "TacRP_HD44780A00_5x8_4" + surface.SetFont(font) + local w, h = surface.GetTextSize(text) + w = w + TacRP.SS(8) + h = h + TacRP.SS(4) + + if !LocalPlayer():isCP() then + surface.SetDrawColor(0, 0, 0, 200) + TacRP.DrawCorneredBox(ScrW() / 2 - w / 2, ScrH() / 2, w, h) + draw.SimpleText(text, font, ScrW() / 2, ScrH() / 2 + h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif self:GetCustomize() then + surface.SetDrawColor(0, 0, 0, 200) + TacRP.DrawCorneredBox(ScrW() / 2 - w / 2, ScrH() - TacRP.SS(24), w, h) + draw.SimpleText(text, font, ScrW() / 2, ScrH() - TacRP.SS(24) + h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + if !self:GetCustomize() then + if TacRP.ConVars["hud"]:GetBool() and TacRP.ConVars["drawhud"]:GetBool() and engine.ActiveGamemode() != "terrortown" then + + local w = TacRP.SS(110) + local h = TacRP.SS(40) + local x = ScrW() - w - TacRP.SS(8) + local y = ScrH() - h - TacRP.SS(8) + + surface.SetDrawColor(0, 0, 0, 150) + TacRP.DrawCorneredBox(x, y, w, h, col) + + local name_txt = TacRP:GetPhrase("wep." .. self:GetClass() .. ".name") or self:GetValue("PrintName") + + surface.SetFont("TacRP_HD44780A00_5x8_8") + local tw = surface.GetTextSize(name_txt) + surface.SetTextPos(x + TacRP.SS(3), y + TacRP.SS(1)) + if tw > w then + surface.SetFont("TacRP_HD44780A00_5x8_6") + tw = surface.GetTextSize(name_txt) + elseif tw > w - TacRP.SS(3) then + surface.SetTextPos(x + TacRP.SS(1.5), y + TacRP.SS(1)) + end + surface.SetTextColor(col) + surface.DrawText(name_txt) + + local ammotype = self:GetValue("PrimaryGrenade") and (TacRP.QuickNades[self:GetValue("PrimaryGrenade")].Ammo) or self:GetAmmoType() + local clips = math.min(math.ceil(self:GetOwner():GetAmmoCount(ammotype)), 999) + + if self:GetValue("HUDAmmoMeter") then + local boxes = 25 + local bw, bh = TacRP.SS(2), TacRP.SS(10) + local frac = math.Clamp(math.ceil(boxes * self:Clip1() / self:GetValue("ClipSize")), 0, boxes) + for i = 1, boxes do + if i - 1 > (boxes - frac) then + surface.SetDrawColor(col) + elseif i - 1 == (boxes - frac) then + surface.SetDrawColor(col_hi) + else + surface.SetDrawColor(col_dark) + end + surface.DrawRect(x + (i * (bw + TacRP.SS(1))), y + TacRP.SS(12), bw, bh) + end + + draw.SimpleText(self:Clip1(), "TacRP_HD44780A00_5x8_10", x + TacRP.SS(93), y + TacRP.SS(17), col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif self.Primary.ClipSize > 0 then + if TacRP.ConVars["hud_ammo_number"]:GetBool() then + surface.SetFont("TacRP_HD44780A00_5x8_10") + local t = math.max(0, self:Clip1()) .. " /" .. self.Primary.ClipSize + local tw = surface.GetTextSize(t) + surface.SetTextColor(col) + surface.SetTextPos(x + w - tw - TacRP.SS(40), y + TacRP.SS(12)) + surface.DrawText(t) + else + local sb = TacRP.SS(4) + local xoffset = TacRP.SS(77) + -- local pw = TacRP.SS(1) + + local row1_bullets = 0 + local row2_bullets = 0 + local rackrise = 0 + local cs = self:GetCapacity() + local c1 = self:Clip1() + local aps = self:GetValue("AmmoPerShot") + + local row_size = 15 + if cs == 20 then + row_size = 10 + end + + local row = math.ceil(c1 / row_size) + local maxrow = math.ceil(cs / row_size) + + local row2_size = math.min(row_size, self:GetCapacity()) + local row1_size = math.Clamp(self:GetCapacity() - row2_size, 0, row_size) + + if c1 > row_size * 2 then + if row == maxrow then + row1_size = cs - row_size * (maxrow - 1) + row1_bullets = c1 - row_size * (maxrow - 1) + elseif c1 % row_size == 0 then + row1_bullets = row_size + else + row1_bullets = c1 % row_size + end + row2_bullets = row2_size + else + row2_bullets = math.min(row2_size, c1) + row1_bullets = math.min(row1_size, c1 - row2_bullets) + end + + if row > 1 and row < lastrow then + rackrisetime = CurTime() + end + lastrow = row + + if rackrisetime + 0.2 > CurTime() then + local rackrisedelta = ((rackrisetime + 0.2) - CurTime()) / 0.2 + rackrise = rackrisedelta * (sb + TacRP.SS(1)) + end + + render.SetScissorRect(x, y, x + w, y + TacRP.SS(12) + sb + sb + 3, true) + + for i = 1, row1_size do + if i >= row1_bullets - aps + 1 and i <= row1_bullets then + surface.SetDrawColor(col_hi) + elseif i > row1_bullets then + surface.SetDrawColor(col_dark) + elseif i % 5 == 0 then + surface.SetDrawColor(col_hi2) + else + surface.SetDrawColor(col) + end + surface.DrawRect(x + xoffset - (i * (sb + TacRP.SS(1))), y + TacRP.SS(12) + rackrise, sb, sb) + end + + local hi_left = math.max(0, aps - row1_bullets) + for i = 1, row2_size do + if (i >= row2_bullets - aps + 1 and i <= row2_bullets and row1_bullets <= 0) or (row1_bullets > 0 and hi_left > 0 and i >= row2_bullets - hi_left + 1 and i <= row2_bullets) then + surface.SetDrawColor(col_hi) + elseif i > row2_bullets then + surface.SetDrawColor(col_dark) + elseif i % 5 == 0 then + surface.SetDrawColor(col_hi2) + else + surface.SetDrawColor(col) + end + + if row1_size == 0 and row2_size <= 10 then + local m = 1.5 + if row2_size <= 5 then + m = 2 + end + + surface.DrawRect(x + xoffset - (i * (sb * m + TacRP.SS(1))), y + TacRP.SS(12 + 1) + sb * (2 - m) / 2, sb * m, sb * m) + -- elseif row > 2 and i <= row - 2 then + -- surface.DrawRect(x + xoffset - (i * (sb + TacRP.SS(1))), y + TacRP.SS(12 + 1) + sb + rackrise, sb, sb) + -- surface.SetDrawColor(col_hi) + -- surface.DrawRect(x + xoffset - (i * (sb + TacRP.SS(1))) + sb / 2 - pw / 2, y + TacRP.SS(12 + 1) + sb + rackrise, pw, sb) + -- surface.DrawRect(x + xoffset - (i * (sb + TacRP.SS(1))), y + TacRP.SS(12 + 1) + sb + rackrise + sb / 2 - pw / 2, sb, pw) + else + surface.DrawRect(x + xoffset - (i * (sb + TacRP.SS(1))), y + TacRP.SS(12 + 1) + sb + rackrise, sb, sb) + end + end + end + + render.SetScissorRect(0, 0, 0, 0, false) + + if self.Primary.ClipSize <= 0 and ammotype == "" then + clips = "" + elseif ammotype == "" then + clips = "---" + elseif self.Primary.ClipSize > 0 then + surface.SetTextColor(col) + surface.SetTextPos(x + w - TacRP.SS(31), y + TacRP.SS(16)) + surface.SetFont("TacRP_HD44780A00_5x8_6") + surface.DrawText("+") + if (self:GetValue("PrimaryGrenade") and TacRP.IsGrenadeInfiniteAmmo(self:GetValue("PrimaryGrenade"))) or (!self:GetValue("PrimaryGrenade") and self:GetInfiniteAmmo()) then + clips = "INF" + end + end + + surface.SetTextColor(col) + surface.SetTextPos(x + w - TacRP.SS(25), y + TacRP.SS(12)) + surface.SetFont("TacRP_HD44780A00_5x8_10") + surface.DrawText(clips) + else + if ammotype == "" then + clips = "" + elseif (self:GetValue("PrimaryGrenade") and TacRP.IsGrenadeInfiniteAmmo(self:GetValue("PrimaryGrenade"))) or (!self:GetValue("PrimaryGrenade") and self:GetInfiniteAmmo()) then + clips = "INF" + end + + + if self:GetValue("PrimaryGrenade") then + local nade = TacRP.QuickNades[self:GetValue("PrimaryGrenade")] + if nade.Icon then + local sg = TacRP.SS(32) + surface.SetMaterial(nade.Icon) + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRect(x + TacRP.SS(4), y + h - sg + TacRP.SS(1), sg, sg) + end + + surface.SetTextColor(col) + surface.SetTextPos(x + TacRP.SS(36), y + h - TacRP.SS(20)) + surface.SetFont("TacRP_HD44780A00_5x8_14") + surface.DrawText("x" .. clips) + else + surface.SetTextColor(col) + surface.SetTextPos(x + TacRP.SS(36), y + TacRP.SS(12)) + surface.SetFont("TacRP_HD44780A00_5x8_10") + surface.DrawText(clips) + end + end + + if self:ShouldDrawBottomBar() then + surface.SetDrawColor(col) + surface.DrawLine(x + TacRP.SS(2), y + TacRP.SS(24), x + w - TacRP.SS(2), y + TacRP.SS(24)) + self:DrawBottomBar(x, y, w, h) + end + + if self:GetValue("Bipod") then + self:DrawBipodHint(x - h / 2 - TacRP.SS(4), y + h - TacRP.SS(10), h / 2) + end + + + local l_w = TacRP.SS(80) + local l_h = TacRP.SS(40) + local l_x = TacRP.SS(8) + local l_y = ScrH() - l_h - TacRP.SS(8) + + local perc = LocalPlayer():Health() / LocalPlayer():GetMaxHealth() + + surface.SetDrawColor(0, 0, 0, 150) + TacRP.DrawCorneredBox(l_x, l_y, l_w, l_h, col) + + surface.SetTextPos(l_x + TacRP.SS(4), l_y + TacRP.SS(1)) + surface.SetFont("TacRP_HD44780A00_5x8_10") + + if perc <= 0.2 then + surface.SetTextColor(col_hi) + + if math.sin(CurTime() * 7) > 0.5 then + surface.SetTextColor(col) + end + elseif perc <= 0.4 then + surface.SetTextColor(col_hi) + else + surface.SetTextColor(col) + end + + surface.DrawText("♥") + + local hpb_x = l_x + TacRP.SS(14) + local hpb_y = l_y + TacRP.SS(4) + local hpb_w = TacRP.SS(2) + local hpb_h = TacRP.SS(8) + + local hpb_can = math.ceil(20 * perc) + + hpb_can = math.min(hpb_can, 20) + + for i = 1, 20 do + if hpb_can <= 2 then + surface.SetDrawColor(col_hi) + else + surface.SetDrawColor(col) + end + if hpb_can >= i then + surface.DrawRect(hpb_x + (i * (hpb_w + TacRP.SS(1))), hpb_y, hpb_w, hpb_h) + else + surface.DrawOutlinedRect(hpb_x + (i * (hpb_w + TacRP.SS(1))), hpb_y, hpb_w, hpb_h) + end + end + + surface.SetDrawColor(col) + + surface.DrawLine(l_x + TacRP.SS(2), l_y + TacRP.SS(15), l_x + l_w - TacRP.SS(2), l_y + TacRP.SS(15)) + + local face = "-_-" + + local blindfiremode = self:GetBlindFireMode() + + if blindfiremode == TacRP.BLINDFIRE_KYS then + if lastblindfiremode != blindfiremode then + shockedtime = CurTime() + 1 + faceindex = math.random(1, 2) + end + end + + lastblindfiremode = blindfiremode + + if lastdmgtime + 1 > CurTime() then + face = ({ + "#> <", + "(>Д<)", + "(@_@)", + "(ー;ー)", + "(・ロ・)", + "゛> <", + "(>_メ)", + "(*_*)", + "゜・+_+" + })[faceindex] + elseif shockedtime > CurTime() then + face = ({ + ";O-O;", + ";>-<;", + })[faceindex] + elseif blindfiremode == TacRP.BLINDFIRE_KYS then + if math.sin(CurTime() * 1) > 0.995 then + face = ";>_<;" + else + face = ";o_o;" + end + elseif lasthealtime + 1 > CurTime() then + if perc >= 1 then + face = ({ + "(^ω~)", + "(>ω^)", + "(>3^)", + "(^.~)", + "(・ω<)", + "(^.~)", + "♥(ツ)♥" + })[faceindex] + + if lasthp < LocalPlayer():Health() then + lasthealtime = CurTime() + + faceindex = math.random(1, 7) + end + else + face = ({ + "(^w^)", + "('3')", + "(♡3♡)", + "(ПωП)", + "(>3<)", + "('w')", + "TYSM!" + })[faceindex] + end + else + if math.sin(CurTime() * 3) > 0.98 then + if perc < 0.1 then + face = "(>_<)" + elseif perc < 0.25 then + face = "(>_<)" + elseif perc < 0.5 then + face = "(>_<)" + elseif perc < 0.95 then + face = "(-_-)" + else + face = "(-_-)" + end + else + if perc < 0.1 then + face = "(×_×)" + elseif perc < 0.25 then + face = "(;_;)" + elseif perc < 0.5 then + face = "(゜_゜)" + elseif perc < 0.95 then + face = "('_')" + else + face = "(^_^)" + end + end + + if lasthp > LocalPlayer():Health() then + lastdmgtime = CurTime() + + faceindex = math.random(1, 8) + elseif lasthp < LocalPlayer():Health() or lastarmor < LocalPlayer():Armor() then + lasthealtime = CurTime() + + faceindex = math.random(1, 7) + end + end + + if LocalPlayer():GetNWBool("HasGodMode") or perc > 2.5 then + if math.sin(CurTime() * 3) > 0.96 then + face = "(UwU)" + else + face = "(OwO)" + end + end + + surface.SetTextPos(l_x + TacRP.SS(4), l_y + TacRP.SS(22)) + surface.SetFont("TacRP_HD44780A00_5x8_10") + surface.SetTextColor(col) + surface.DrawText(face) + + lasthp = LocalPlayer():Health() + + local armor = self:GetOwner():Armor() + + local asq = TacRP.SS(8) + local ss = TacRP.SS(4) + + local function drawarmorsquare(level, x, y) + if level == 1 then + surface.SetDrawColor(col) + surface.DrawOutlinedRect(x, y, asq, asq) + surface.DrawOutlinedRect(x + 1, y + 1, asq - 2, asq - 2) + elseif level == 2 then + surface.SetDrawColor(col) + surface.DrawRect(x + ((asq - ss) / 2), y + ((asq - ss) / 2), ss, ss) + surface.DrawOutlinedRect(x, y, asq, asq) + surface.DrawOutlinedRect(x + 1, y + 1, asq - 2, asq - 2) + else + surface.SetDrawColor(col) + surface.DrawRect(x, y, asq, asq) + end + end + + local cx1 = l_x + l_w - TacRP.SS(20) + local cy1 = l_y + TacRP.SS(19) + local cx2 = cx1 + asq + 2 + local cy2 = cy1 + asq + 2 + + surface.SetTextPos(cx1 - TacRP.SS(10), cy1 + TacRP.SS(3)) + surface.SetFont("TacRP_HD44780A00_5x8_10") + surface.SetTextColor(col) + surface.DrawText("⌂") + + if armor >= 100 then + drawarmorsquare(3, cx1, cy1) + elseif armor > 75 then + drawarmorsquare(2, cx1, cy1) + else + drawarmorsquare(1, cx1, cy1) + end + + if armor >= 75 then + drawarmorsquare(3, cx2, cy1) + elseif armor > 50 then + drawarmorsquare(2, cx2, cy1) + else + drawarmorsquare(1, cx2, cy1) + end + + if armor >= 50 then + drawarmorsquare(3, cx2, cy2) + elseif armor > 25 then + drawarmorsquare(2, cx2, cy2) + else + drawarmorsquare(1, cx2, cy2) + end + + if armor >= 25 then + drawarmorsquare(3, cx1, cy2) + elseif armor > 0 then + drawarmorsquare(2, cx1, cy2) + else + drawarmorsquare(1, cx1, cy2) + end + elseif TacRP.ConVars["minhud"]:GetBool() and self:ShouldDrawBottomBar() then + local bipod = self:GetValue("Bipod") + local w = TacRP.SS(110) + local h = TacRP.SS(16) + local x = ScrW() / 2 - w / 2 + local y = ScrH() - h - TacRP.SS(8) + + if bipod then x = x - h / 2 - TacRP.SS(2) end + + surface.SetDrawColor(0, 0, 0, 150) + TacRP.DrawCorneredBox(x, y, w, h, col) + + self:DrawBottomBar(x, y, w, h) + + if bipod then + self:DrawBipodHint(x + w + TacRP.SS(4), y + h / 2, h) + end + end + end + + if self:GetValue("Scope") or self:GetValue("PrimaryMelee") then + self:DrawBreathBar(ScrW() * 0.5, ScrH() * 0.65, TacRP.SS(64), TacRP.SS(4)) + end + + self:DrawGrenadeHUD() + + self:DrawBlindFireHUD() + + lastammo = self:Clip1() + lastarmor = LocalPlayer():Armor() +end + +SWEP.Mat_Select = nil + +function SWEP:DrawWeaponSelection(x, y, w, h, a) + if !self.Mat_Select then + self.Mat_Select = Material(self.IconOverride or "entities/" .. self:GetClass() .. ".png", "smooth mips") + + end + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(self.Mat_Select) + if self.IconOverride then + w = w - 128 + x = x + 64 + end + if w > h then + y = y - ((w - h) / 2) + end + + surface.DrawTexturedRect(x, y, w, w) +end + +function SWEP:RangeUnitize(range) + if TacRP.ConVars["metricunit"]:GetBool() then + return tostring(math.Round(range * TacRP.HUToM)) .. TacRP:GetPhrase("unit.meter") + else + return tostring(math.Round(range)) .. TacRP:GetPhrase("unit.hu") + end +end + +function SWEP:CustomAmmoDisplay() + self.AmmoDisplay = self.AmmoDisplay or {} + self.AmmoDisplay.Draw = true + + if TacRP.IsGrenadeInfiniteAmmo(self:GetGrenadeIndex()) then + self.AmmoDisplay.SecondaryAmmo = 99 + end + + if self.Primary.ClipSize <= 0 and self.Primary.Ammo != "" then + if self:GetValue("PrimaryGrenade") and TacRP.IsGrenadeInfiniteAmmo(self:GetValue("PrimaryGrenade")) then + self.AmmoDisplay.SecondaryAmmo = -1 + self.AmmoDisplay.PrimaryClip = -1 + self.AmmoDisplay.PrimaryAmmo = -1 + else + self.AmmoDisplay.PrimaryClip = self:Ammo1() + self.AmmoDisplay.PrimaryAmmo = -1 + end + elseif self.Primary.ClipSize <= 0 then + self.AmmoDisplay.PrimaryClip = -1 + else + self.AmmoDisplay.PrimaryClip = self:Clip1() + self.AmmoDisplay.PrimaryAmmo = self:GetInfiniteAmmo() and 9999 or self:Ammo1() + end + return self.AmmoDisplay +end + +local col2 = Color(50, 255, 50) + +function SWEP:DrawLockOnHUD() + local owner = self:GetOwner() + local dir = owner:GetAimVector(true) + + local tr = util.TraceLine({ + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + (dir * 50000), + mask = MASK_SHOT, + filter = owner + }) + cam.Start3D() + local w2s = tr.HitPos:ToScreen() + sx = math.Round(w2s.x) + sy = math.Round(w2s.y) + cam.End3D() + + local ss = ScreenScale(1) + + if (self:GetScopeLevel() > 0 and self:GetValue("LockOnInSights")) or (self:GetScopeLevel() <= 0 and self:GetValue("LockOnOutOfSights")) then + surface.SetDrawColor(col2) + + render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD) + + local trueFOV = self:WidescreenFix(self.TacRPLastFOV or 90) + + local circle = (ScrH() / trueFOV) * self:GetValue("LockOnTrackAngle") + ss + local offset = 0 + + for i = 0, 15 do + local angle = (i / 8) * math.pi + local x1 = sx + math.cos(angle + offset) * circle + local y1 = sy + math.sin(angle + offset) * circle + local x2 = sx + math.cos(angle + offset + (math.pi * 1 / 8)) * circle + local y2 = sy + math.sin(angle + offset + (math.pi * 1 / 8)) * circle + surface.DrawLine(x1, y1, x2, y2) + end + + if !IsValid(self:GetLockOnEntity()) then + local circle2 = (ScrH() / trueFOV) * 1 + ss + local offset2 = 0 + + for i = 0, 15 do + local angle = (i / 8) * math.pi + local x1 = sx + math.cos(angle + offset2) * circle2 + local y1 = sy + math.sin(angle + offset2) * circle2 + local x2 = sx + math.cos(angle + offset2 + (math.pi * 1 / 8)) * circle2 + local y2 = sy + math.sin(angle + offset2 + (math.pi * 1 / 8)) * circle2 + surface.DrawLine(x1, y1, x2, y2) + end + end + + render.OverrideBlend(false, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD) + end + + if !IsValid(self:GetLockOnEntity()) then return end + + local pos = self:GetLockOnEntity():WorldSpaceCenter() + + cam.Start3D() + local x, y = pos:ToScreen().x, pos:ToScreen().y + cam.End3D() + + surface.SetDrawColor(col2) + + render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD) + + local cross = ss * 10 + local offset = (1 / 4) * math.pi + + for i = 0, 3 do + local angle = (i / 2) * math.pi + local x1 = x + math.cos(angle + offset) * cross + local y1 = y + math.sin(angle + offset) * cross + local x2 = x + math.cos(angle + offset + (math.pi * 1 / 2)) * cross + local y2 = y + math.sin(angle + offset + (math.pi * 1 / 2)) * cross + surface.DrawLine(x1, y1, x2, y2) + end + + if CurTime() >= self:GetValue("LockOnTime") + self:GetLockOnStartTime() then + -- Target locked, draw a diamond + local offset2 = 0 + + for i = 0, 5 do + local cross2 = cross * 0.7 + local angle = (i / 2) * math.pi + local x1 = x + math.cos(angle + offset2) * cross2 + local y1 = y + math.sin(angle + offset2) * cross2 + local x2 = x + math.cos(angle + offset2 + (math.pi * 1 / 2)) * cross2 + local y2 = y + math.sin(angle + offset2 + (math.pi * 1 / 2)) * cross2 + surface.DrawLine(x1, y1, x2, y2) + end + end + + render.OverrideBlend(false, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_laser.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_laser.lua new file mode 100644 index 0000000..194276e --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_laser.lua @@ -0,0 +1,140 @@ +local lasermat = Material("effects/laser1") +local flaremat = Material("effects/whiteflare") + +function SWEP:DrawLaser(pos, ang, strength, thirdperson) + strength = strength or 1 + + local alwaysacc = self:GetValue("LaserAlwaysAccurate") + local behavior = (self:GetValue("ScopeHideWeapon") and self:IsInScope()) + local vm = self:GetOwner():IsPlayer() and self:GetOwner():GetViewModel() + local curr_seq = IsValid(vm) and vm:GetSequenceName(vm:GetSequence()) + + local delta = behavior and 1 or 0 + + if IsValid(vm) and (alwaysacc or TacRP.ConVars["true_laser"]:GetBool()) and (self:GetBlindFireMode() <= 1) and !self:GetCustomize() and !behavior then + local d1 = (CurTime() - self:GetNextSecondaryFire()) / 1 + if alwaysacc then + d1 = 1 + elseif TacRP.ConVars["laser_beam"]:GetBool() then + d1 = math.min((CurTime() - self:GetNextPrimaryFire()) / 2, (CurTime() - self:GetNextSecondaryFire()) / 1) + elseif self:GetValue("RPM") < 120 then + d1 = math.min((CurTime() - self:GetNextPrimaryFire()) / 0.5, (CurTime() - self:GetNextSecondaryFire()) / 1) + end + + local d2 = (curr_seq == "reload_start") and 0 or 1 + local d3 = (1 - math.min(self:GetAnimLockTime() - CurTime()) / vm:SequenceDuration(vm:GetSequence())) + local d4 = self:CanShootInSprint() and 1 or (1 - self:GetSprintDelta()) ^ 2 + if self:DoForceSightsBehavior() then d4 = self:GetSprintDelta() * self:GetSightDelta() end + local cutoff = 0.85 + d3 = math.max(d3 - cutoff, 0) / (1 - cutoff) + + delta = math.Clamp(self:GetReloading() and 0 or math.min(d1, d2, d3, d4), 0, 1) + + end + + local pos_tr = self:GetMuzzleOrigin() + + if behavior then + ang = self:GetShootDir() + else + ang = LerpAngle(delta, ang, self:GetShootDir()) + end + + local tr = util.TraceLine({ + start = pos_tr, + endpos = pos_tr + (ang:Forward() * 30000), + mask = MASK_OPAQUE, + filter = self:GetOwner() + }) + + if tr.StartSolid then return end + local laser_pos = tr.HitPos + tr.HitNormal + local adjusted_pos = thirdperson and laser_pos or TacRP.FormatViewModelAttachment(self.ViewModelFOV, laser_pos, false) + laser_pos = LerpVector(delta, laser_pos, adjusted_pos) + + if behavior then + cam.Start3D() + pos = pos - (ang:Forward() * 256) + end + + local col = self:GetValue("LaserColor") + + if TacRP.ConVars["laser_beam"]:GetBool() then + local width = math.Rand(0.1, 0.2) * strength + render.SetMaterial(lasermat) + render.DrawBeam(pos, laser_pos, width * 0.3, 0, 1, Color(200, 200, 200)) + render.DrawBeam(pos, laser_pos, width, 0, 1, col) + end + + if tr.Hit and !tr.HitSky then + local mul = strength + local rad = math.Rand(4, 6) * mul + + render.SetMaterial(flaremat) + render.DrawSprite(laser_pos, rad, rad, col) + render.DrawSprite(laser_pos, rad * 0.3, rad * 0.3, Color(200, 200, 200)) + + debugoverlay.Cross(tr.HitPos, 4, FrameTime() * 2, col) + end + + if behavior then + cam.End3D() + end +end + +function SWEP:DrawLasers(wm) + wm = wm or false + + if self.Laser and self:GetTactical() then + local power = self.LaserPower or 2 + if wm and self.LaserQCAttachmentWM then + local att = self:GetAttachment(self.LaserQCAttachmentWM) + if att then + self:DrawLaser(att.Pos, att.Ang, power, true) + end + elseif IsValid(self:GetOwner():GetViewModel()) and self.LaserQCAttachmentVM then + local vm = self:GetOwner():GetViewModel() + local att = vm:GetAttachment(self.LaserQCAttachmentVM) + if att then + local pos = TacRP.FormatViewModelAttachment(self.ViewModelFOV, att.Pos, false) + self:DrawLaser(pos, att.Ang, power) + end + end + end + + for i, k in pairs(self.Attachments) do + if !k.Installed then continue end + + local atttbl = TacRP.GetAttTable(k.Installed) + + local power = atttbl.LaserPower or 2 + + if atttbl.Laser and self:GetTactical() then + if wm then + if atttbl.LaserQCAttachmentWM then + local att = self:GetAttachment(atttbl.LaserQCAttachmentWM) + if att then + self:DrawLaser(att.Pos, self:GetOwner():IsPlayer() and self:GetShootDir() or att.Ang, power, true) + end + elseif IsValid(k.WModel) then + if self:GetOwner():IsPlayer() then + self:DrawLaser(k.WModel:GetPos(), self:GetShootDir(), power, true) + else + self:DrawLaser(k.WModel:GetPos(), k.WModel:GetAngles(), power, true) + end + end + else + if IsValid(self:GetOwner():GetViewModel()) and atttbl.LaserQCAttachmentVM then + local vm = self:GetOwner():GetViewModel() + local att = vm:GetAttachment(atttbl.LaserQCAttachmentVM) + if att then + local pos = TacRP.FormatViewModelAttachment(self.ViewModelFOV, att.Pos, false) + self:DrawLaser(pos, att.Ang, power) + end + elseif IsValid(k.VModel) then + self:DrawLaser(k.VModel:GetPos() + (k.VModel:GetAngles():Up() * 0.75), k.VModel:GetAngles(), power) + end + end + end + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_light.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_light.lua new file mode 100644 index 0000000..6e79296 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_light.lua @@ -0,0 +1,333 @@ +SWEP.Flashlights = {} -- tracks projectedlights +-- {{att = int, light = ProjectedTexture}} + +function SWEP:GetHasFlashlights() + for i, k in pairs(self.Attachments) do + if !k.Installed then continue end + + local atttbl = TacRP.GetAttTable(k.Installed) + + if atttbl.Flashlight then return true end + end + + return false +end + +function SWEP:CreateFlashlights() + self:KillFlashlights() + self.Flashlights = {} + + local total_lights = 0 + + for i, k in pairs(self.Attachments) do + if !k.Installed then continue end + + local atttbl = TacRP.GetAttTable(k.Installed) + + if atttbl.Flashlight then + local newlight = { + att = i, + light = ProjectedTexture(), + col = Color(255, 255, 255), + br = 4, + } + total_lights = total_lights + 1 + + local l = newlight.light + if !IsValid(l) then continue end + + table.insert(self.Flashlights, newlight) + + l:SetFOV(atttbl.FlashlightFOV or 60) + + l:SetFarZ(atttbl.FlashlightFarZ or 1024) + l:SetNearZ(4) + + l:SetQuadraticAttenuation(100) + + l:SetColor(atttbl.FlashlightColor or color_white) + l:SetTexture("effects/flashlight001") + l:SetBrightness(atttbl.FlashlightBrightness or 1.5) + l:SetEnableShadows(true) + l:Update() + + local g_light = { + Weapon = self, + ProjectedTexture = l + } + + table.insert(TacRP.FlashlightPile, g_light) + end + end + + if total_lights > 2 then -- you are a madman + for i, k in pairs(self.Flashlights) do + if k.light:IsValid() then k.light:SetEnableShadows(false) end + end + end +end + +function SWEP:KillFlashlights() + self:KillFlashlightsVM() + -- self:KillFlashlightsWM() +end + +function SWEP:KillFlashlightsVM() + if !self.Flashlights then return end + + for i, k in pairs(self.Flashlights) do + if k.light and k.light:IsValid() then + k.light:Remove() + end + end + + self.Flashlights = nil +end + +function SWEP:DrawFlashlightsVM() + + if !self:GetTactical() then + self:KillFlashlights() + return + end + + if !self.Flashlights then + self:CreateFlashlights() + end + + for i, k in pairs(self.Flashlights) do + local model = self.Attachments[k.att].VModel + + local pos, ang + + if !IsValid(model) then + pos = self:GetOwner():EyePos() + ang = self:GetOwner():EyeAngles() + else + pos = model:GetPos() + ang = model:GetAngles() + end + + local tr = util.TraceLine({ + start = self:GetOwner():EyePos(), + endpos = self:GetOwner():EyePos() - -ang:Forward() * 128, + mask = MASK_OPAQUE, + filter = LocalPlayer(), + }) + if tr.Fraction < 1 then -- We need to push the flashlight back + local tr2 = util.TraceLine({ + start = self:GetOwner():EyePos(), + endpos = self:GetOwner():EyePos() + -ang:Forward() * 128, + mask = MASK_OPAQUE, + filter = LocalPlayer(), + }) + -- push it as back as the area behind us allows + pos = pos + -ang:Forward() * 128 * math.min(1 - tr.Fraction, tr2.Fraction) + end + + -- ang:RotateAroundAxis(ang:Up(), 90) + + k.light:SetPos(pos) + k.light:SetAngles(ang) + k.light:Update() + + -- local col = k.col + + -- local dl = DynamicLight(self:EntIndex()) + + -- if dl then + -- dl.pos = pos + -- dl.r = col.r + -- dl.g = col.g + -- dl.b = col.b + -- dl.brightness = k.br or 2 + -- -- print(z / maxz) + -- dl.Decay = 1000 / 0.1 + -- dl.dietime = CurTime() + 0.1 + -- dl.size = (k.br or 2) * 64 + -- end + end +end + +function SWEP:DrawFlashlightsWM() + if self:GetOwner() != LocalPlayer() then return end + + if !self.Flashlights then + self:CreateFlashlights() + end + + for i, k in ipairs(self.Flashlights) do + local model = (k.slottbl or {}).WModel + + if !IsValid(model) then continue end + + local pos, ang + + if !model then + pos = self:GetOwner():EyePos() + ang = self:GetOwner():EyeAngles() + else + pos = model:GetPos() + ang = model:GetAngles() + end + + -- ang:RotateAroundAxis(ang:Up(), 90) + + local tr = util.TraceLine({ + start = pos, + endpos = pos + ang:Forward() * 16, + mask = MASK_OPAQUE, + filter = LocalPlayer(), + }) + if tr.Fraction < 1 then -- We need to push the flashlight back + local tr2 = util.TraceLine({ + start = pos, + endpos = pos - ang:Forward() * 16, + mask = MASK_OPAQUE, + filter = LocalPlayer(), + }) + -- push it as back as the area behind us allows + pos = pos + -ang:Forward() * 16 * math.min(1 - tr.Fraction, tr2.Fraction) + else + pos = tr.HitPos + end + + k.light:SetPos(pos) + k.light:SetAngles(ang) + k.light:Update() + end +end + +local flaremat = Material("tacrp/particle_flare") +function SWEP:DrawFlashlightGlare(pos, ang, strength, dot) + strength = strength or 1 + + local diff = EyePos() - pos + local wep = LocalPlayer():GetActiveWeapon() + --local dot = math.Clamp((-ang:Forward():Dot(EyeAngles():Forward()) - 0.707) / (1 - 0.707), 0, 1) ^ 2 + if TacRP.ConVars["flashlight_blind"]:GetBool() then + dot = dot ^ 4 + local tr = util.QuickTrace(pos, diff, {self:GetOwner(), LocalPlayer()}) + local s = math.Clamp(1 - diff:Length() / 328, 0, 1) ^ 1 * dot * 2000 * math.Rand(0.95, 1.05) + if IsValid(wep) and wep.ArcticTacRP and wep:IsInScope() and wep:GetValue("ScopeOverlay") then + s = s + math.Clamp(1 - diff:Length() / 4096, 0, 1) ^ 1.2 * wep:GetSightAmount() * dot * 3000 * math.Rand(0.95, 1.05) + end + if tr.Fraction == 1 then + s = TacRP.SS(s) + local toscreen = pos:ToScreen() + cam.Start2D() + surface.SetMaterial(flaremat) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(toscreen.x - s / 2, toscreen.y - s / 2, s, s) + cam.End2D() + end + end + + local rad = math.Rand(0.9, 1.1) * 128 * strength + local a = 50 + strength * 205 + + pos = pos + ang:Forward() * 2 + pos = pos + diff:GetNormalized() * (2 + 14 * strength) + + render.SetMaterial(flaremat) + render.DrawSprite(pos, rad, rad, Color(255, 255, 255, a)) +end + +function SWEP:DrawFlashlightGlares() + if !self:GetOwner():IsPlayer() then return end + for i, k in pairs(self.Attachments) do + if !k.Installed then continue end + + local atttbl = TacRP.GetAttTable(k.Installed) + + local src, dir + if atttbl.Blinding and self:GetTactical() then + if IsValid(k.WModel) then + src, dir = k.WModel:GetPos(), self:GetShootDir() + else + src, dir = self:GetTracerOrigin(), self:GetShootDir() + end + else + continue + end + + local power = 1 + local dot = -dir:Forward():Dot(EyeAngles():Forward()) + local dot2 = dir:Forward():Dot((EyePos() - src):GetNormalized()) + dot = (dot + dot2) / 2 + if dot < 0 then continue end + + power = power * math.Clamp(dot * 2 - 1, 0, 1) + local distsqr = (src - EyePos()):LengthSqr() + power = power * ((1 - math.Clamp(distsqr / 4194304, 0, 1)) ^ 1.25) + + self:DrawFlashlightGlare(src, dir, power, dot) + end +end + +local glintmat = Material("effects/blueflare1") +local glintmat2 = Material("tacrp/scope_flare") +function SWEP:DoScopeGlint() + --if self:GetOwner() == LocalPlayer() then return end + if !TacRP.ConVars["glint"]:GetBool() then return end + if !self:GetValue("ScopeOverlay") then return end + local src, dir = self:GetTracerOrigin(), self:GetShootDir() + + local diff = EyePos() - src + + local dot = -dir:Forward():Dot(EyeAngles():Forward()) + local dot2 = dir:Forward():Dot(diff:GetNormalized()) + dot = math.max(0, (dot + dot2) / 2) ^ 1.5 + + local strength = dot * math.Clamp((diff:Length() - 1024) / 3072, 0, 3) * math.Clamp(90 / self:GetValue("ScopeFOV") / 10, 0, 1) + + local rad = strength * 128 * (self:GetSightAmount() * 0.5 + 0.5) + + src = src + dir:Up() * 4 + diff:GetNormalized() * math.Clamp(diff:Length() / 2048, 0, 1) * 16 + + local a = math.min(255, strength * 200 + 100) + + render.SetMaterial(glintmat) + render.DrawSprite(src, rad, rad, Color(a, a, a)) + + -- if self:GetSightAmount() > 0 then + render.SetMaterial(glintmat2) + render.DrawSprite(src, rad * 2, rad * 2, color_white) + -- end +end + +function SWEP:DoMuzzleLight() + if (!IsFirstTimePredicted() and !game.SinglePlayer()) or !TacRP.ConVars["muzzlelight"]:GetBool() then return end + + if IsValid(self.MuzzleLight) then self.MuzzleLight:Remove() end + + local lamp = ProjectedTexture() + lamp:SetTexture("tacrp/muzzleflash_light") + local val1, val2 + if self:GetValue("Silencer") then + val1, val2 = math.Rand(0.2, 0.4), math.Rand(100, 105) + lamp:SetBrightness(val1) + lamp:SetFOV(val2) + else + val1, val2 = math.Rand(2, 3), math.Rand(115, 120) + lamp:SetBrightness(val1) + lamp:SetFOV(val2) + end + + lamp:SetFarZ(600) + lamp:SetPos(self:GetMuzzleOrigin() + self:GetShootDir():Forward() * 8) + lamp:SetAngles(self:GetShootDir() + Angle(math.Rand(-1, 1), math.Rand(-1, 1), math.Rand(0, 360))) + lamp:Update() + + self.MuzzleLight = lamp + self.MuzzleLightStart = UnPredictedCurTime() + self.MuzzleLightEnd = UnPredictedCurTime() + 0.06 + self.MuzzleLightBrightness = val1 + self.MuzzleLightFOV = val2 + + -- In multiplayer the timer will last longer than intended - sh_think should kill the light first. + -- This is a failsafe for when the weapon stops thinking before light is killed (holstered, removed etc.). + timer.Simple(0.06, function() + if IsValid(lamp) then lamp:Remove() end + end) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_model.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_model.lua new file mode 100644 index 0000000..d6739fd --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_model.lua @@ -0,0 +1,106 @@ +SWEP.VModel = nil +SWEP.WModel = nil + +function SWEP:KillModel() + for _, model in pairs(self.VModel or {}) do + SafeRemoveEntity(model) + end + for _, model in pairs(self.WModel or {}) do + SafeRemoveEntity(model) + end + + self.VModel = nil + self.WModel = nil +end + +function SWEP:CreateAttachmentModel(wm, atttbl, slot, slottbl, custom_wm) + local model = atttbl.Model + + if wm and atttbl.WorldModel then + model = atttbl.WorldModel + end + + local csmodel = ClientsideModel(model) + + if !IsValid(csmodel) then return end + + csmodel.Slot = slot + + local scale = Matrix() + local vec = Vector(1, 1, 1) * (atttbl.Scale or 1) + if wm then + vec = vec * (slottbl.WMScale or 1) + else + vec = vec * (slottbl.VMScale or 1) + end + scale:Scale(vec) + csmodel:EnableMatrix("RenderMultiply", scale) + csmodel:SetNoDraw(true) + + local tbl = { + Model = csmodel, + Weapon = self + } + + table.insert(TacRP.CSModelPile, tbl) + + if wm then + table.insert(self.WModel, csmodel) + else + table.insert(self.VModel, csmodel) + end + + return csmodel +end + +function SWEP:SetupModel(wm, custom_wm) + self:KillModel() + + if !wm and !IsValid(self:GetOwner()) then return end + + if !wm then + self.VModel = {} + else + self.WModel = {} + end + + if !wm and self:GetOwner() != LocalPlayer() and self:GetOwner() != LocalPlayer():GetObserverTarget() then return end + + self:DoBodygroups(wm) + + for slot, slottbl in pairs(self.Attachments) do + if !slottbl.Installed then continue end + + local atttbl = TacRP.GetAttTable(slottbl.Installed) + + if !atttbl.Model then continue end + + local csmodel = self:CreateAttachmentModel(wm, atttbl, slot, slottbl) + + csmodel.IsHolosight = atttbl.Holosight + + if atttbl.Silencer then + local slmodel = self:CreateAttachmentModel(wm, atttbl, slot, slottbl) + slmodel.IsMuzzleDevice = true + slmodel.NoDraw = true + end + + if wm then + slottbl.WModel = csmodel + else + slottbl.VModel = csmodel + end + end + + if !wm then + self:CreateFlashlights() + + local mat = self:GetValue("Material") + + if mat then + local vm = self:GetOwner():GetViewModel() + + vm:SetMaterial(mat) + end + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_net.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_net.lua new file mode 100644 index 0000000..b012c39 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_net.lua @@ -0,0 +1,37 @@ +function SWEP:ReceiveWeapon(ids) + for slot, slottbl in pairs(self.Attachments) do + local attid = ids and ids[slot] or net.ReadUInt(TacRP.Attachments_Bits) + + if attid == 0 then + slottbl.Installed = nil + else + slottbl.Installed = TacRP.Attachments_Index[attid] + end + end + + self:InvalidateCache() + + self:SetupModel(true) + self:SetupModel(false) + + self.CertainAboutAtts = true +end + +function SWEP:UpdateHolster() + local ply = self:GetOwner() + if IsValid(ply) and ply:IsPlayer() and ply:GetActiveWeapon() != self then + local visible = self:GetValue("HolsterVisible") + local slot = self:GetValue("HolsterSlot") + + if visible and slot then + ply.TacRP_Holster = ply.TacRP_Holster or {} + ply.TacRP_Holster[slot] = self + end + end +end + +function SWEP:RequestWeapon() + net.Start("tacrp_networkweapon") + net.WriteEntity(self) + net.SendToServer() +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_preset.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_preset.lua new file mode 100644 index 0000000..e1056ad --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_preset.lua @@ -0,0 +1,81 @@ +function SWEP:SavePreset(filename) + if LocalPlayer() != self:GetOwner() then return end + + filename = filename or "autosave" + + local str = "" + for i, k in pairs(self.Attachments) do + if k.Installed then + str = str .. k.Installed + end + + str = str .. "\n" + end + + filename = TacRP.PresetPath .. self:GetClass() .. "/" .. filename .. ".txt" + + file.CreateDir(TacRP.PresetPath .. self:GetClass()) + file.Write(filename, str) +end + +function SWEP:LoadPreset(filename) + if LocalPlayer() != self:GetOwner() then return end + + filename = TacRP.PresetPath .. self:GetClass() .. "/" .. "autosave" .. ".txt" + + if !file.Exists(filename, "DATA") then return end + + local f = file.Open(filename, "r", "DATA") + if !f then return end + + local presetTbl = {} + + for i = 1, table.Count(self.Attachments) do + local line = f:ReadLine() + if !line then continue end + presetTbl[i] = string.Trim(line, "\n") + end + + local anyinstalled = false + + for i = 1, table.Count(self.Attachments) do + if !self.Attachments[i] then continue end + + local att = presetTbl[i] + if att == "" then + self.Attachments[i].Installed = nil + continue + end + + + if att == self.Attachments[i].Installed then continue end + if !TacRP.GetAttTable(att) then continue end + + self.Attachments[i].Installed = att + + anyinstalled = true + end + + f:Close() + + if !anyinstalled then return end + + net.Start("TacRP_receivepreset") + net.WriteEntity(self) + for i, k in pairs(self.Attachments) do + if !k.Installed then + net.WriteUInt(0, TacRP.Attachments_Bits) + else + local atttbl = TacRP.GetAttTable(k.Installed) + net.WriteUInt(atttbl.ID or 0, TacRP.Attachments_Bits) + end + end + net.SendToServer() + + self:SetupModel(false) + self:SetupModel(true) + + self:InvalidateCache() + + self:SetBaseSettings() +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_radialmenu.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_radialmenu.lua new file mode 100644 index 0000000..5ec75bf --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_radialmenu.lua @@ -0,0 +1,993 @@ +local function filledcircle(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) + + 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 + +local function slicedcircle(x, y, radius, seg, ang0, ang1) + local cir = {} + + ang0 = ang0 + 90 + ang1 = ang1 + 90 + + local arcseg = math.Round(360 / math.abs(ang1 - ang0) * seg) + + table.insert(cir, { + x = x, + y = y, + u = 0.5, + v = 0.5 + }) + + for i = 0, arcseg do + local a = math.rad((i / arcseg) * -math.abs(ang1 - ang0) + ang0) + + 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 + + surface.DrawPoly(cir) +end + +SWEP.GrenadeMenuAlpha = 0 +SWEP.BlindFireMenuAlpha = 0 + +TacRP.CursorEnabled = false + +local currentnade +local currentind +local lastmenu +function SWEP:DrawGrenadeHUD() + if !TacRP.ConVars["nademenu"]:GetBool() then return end + if !self:IsQuickNadeAllowed() then return end + + -- adapted from tfa vox radial menu + local nades = self:GetAvailableGrenades(false) + local scrw = ScrW() + local scrh = ScrH() + local r = TacRP.SS(128) + local r2 = TacRP.SS(40) + local sg = TacRP.SS(32) + local ri = r * 0.667 + local arcdegrees = 360 / math.max(1, #nades) + local d = 360 + local ft = FrameTime() + + local cursorx, cursory = input.GetCursorPos() + local mouseangle = math.deg(math.atan2(cursorx - scrw / 2, cursory - scrh / 2)) + local mousedist = math.sqrt(math.pow(cursorx - scrw / 2, 2) + math.pow(cursory - scrh / 2, 2)) + mouseangle = math.NormalizeAngle(360 - (mouseangle - 90) + arcdegrees) + if mouseangle < 0 then + mouseangle = mouseangle + 360 + end + + local iskeydown = self:GetOwner():KeyDown(self.GrenadeMenuKey) + + if self.GrenadeMenuKey == IN_GRENADE1 and !input.LookupBinding("+grenade1") then + iskeydown = input.IsKeyDown(TacRP.GRENADE1_Backup) + elseif self.GrenadeMenuKey == IN_GRENADE2 and !input.LookupBinding("+grenade2") then + iskeydown = input.IsKeyDown(TacRP.GRENADE2_Backup) + end + + if iskeydown and !self:GetPrimedGrenade() and self.BlindFireMenuAlpha == 0 and self:GetHolsterTime() == 0 then + self.GrenadeMenuAlpha = math.Approach(self.GrenadeMenuAlpha, 1, 15 * ft) + if !lastmenu then + gui.EnableScreenClicker(true) + TacRP.CursorEnabled = true + lastmenu = true + end + + if mousedist > r2 then + local i = math.floor( mouseangle / arcdegrees ) + 1 + currentnade = nades[i] + currentind = i + else + currentnade = self:GetGrenade() + currentind = nil + end + self.GrenadeMenuHighlighted = currentind + else + self.GrenadeMenuAlpha = math.Approach(self.GrenadeMenuAlpha, 0, -10 * ft) + if lastmenu then + if !self:GetCustomize() then + gui.EnableScreenClicker(false) + TacRP.CursorEnabled = false + end + if currentnade then + if currentnade.Index != self:GetGrenade().Index then + self:GetOwner():EmitSound("tacrp/weapons/grenade/roll-" .. math.random(1, 3) .. ".wav") + end + net.Start("tacrp_togglenade") + net.WriteUInt(currentnade.Index, 4) + net.WriteBool(false) + net.SendToServer() + self.Secondary.Ammo = currentnade.Ammo or "none" + end + lastmenu = false + end + end + + if self.GrenadeMenuAlpha <= 0 then + return + end + + local a = self.GrenadeMenuAlpha + local col = Color(255, 255, 255, 255 * a) + + surface.DrawCircle(scrw / 2, scrh / 2, r, 255, 255, 255, a * 255) + + surface.SetDrawColor(0, 0, 0, a * 200) + draw.NoTexture() + filledcircle(scrw / 2, scrh / 2, r, 32) + + if #nades == 0 then + local nadetext = TacRP:GetPhrase("hint.nogrenades") + surface.SetFont("TacRP_HD44780A00_5x8_8") + local nadetextw = surface.GetTextSize(nadetext) + surface.SetTextPos(scrw / 2 - nadetextw * 0.5, scrh / 2 + TacRP.SS(6)) + surface.DrawText(nadetext) + return + end + + surface.SetDrawColor(150, 150, 150, a * 100) + draw.NoTexture() + if currentind then + local i = currentind + local d0 = 0 - arcdegrees * (i - 2) + slicedcircle(scrw / 2, scrh / 2, r, 32, d0, d0 + arcdegrees) + else + filledcircle(scrw / 2, scrh / 2, r2, 32) + end + + surface.SetDrawColor(0, 0, 0, a * 255) + surface.DrawCircle(scrw / 2, scrh / 2, r2, 255, 255, 255, a * 255) + + for i = 1, #nades do + local rad = math.rad( d + arcdegrees * 0.5 ) + + surface.SetDrawColor(255, 255, 255, a * 255) + surface.DrawLine( + scrw / 2 + math.cos(math.rad(d)) * r2, + scrh / 2 - math.sin(math.rad(d)) * r2, + scrw / 2 + math.cos(math.rad(d)) * r, + scrh / 2 - math.sin(math.rad(d)) * r) + + local nadex, nadey = scrw / 2 + math.cos(rad) * ri, scrh / 2 - math.sin(rad) * ri + local nade = nades[i] + + local qty = nil --"INF" + + if nade.Singleton then + qty = self:GetOwner():HasWeapon(nade.GrenadeWep) and 1 or 0 + elseif !TacRP.IsGrenadeInfiniteAmmo(nade.Index) then + qty = self:GetOwner():GetAmmoCount(nade.Ammo) + end + + if !qty or qty > 0 then + surface.SetDrawColor(255, 255, 255, a * 255) + surface.SetTextColor(255, 255, 255, a * 255) + else + surface.SetDrawColor(175, 175, 175, a * 255) + surface.SetTextColor(175, 175, 175, a * 255) + end + + if nade.Icon then + surface.SetMaterial(nade.Icon) + surface.DrawTexturedRect(nadex - sg * 0.5, nadey - sg * 0.5 - TacRP.SS(8), sg, sg) + end + local nadetext = TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".name") or nade.PrintName + surface.SetFont("TacRP_HD44780A00_5x8_8") + local nadetextw = surface.GetTextSize(nadetext) + surface.SetTextPos(nadex - nadetextw * 0.5, nadey + TacRP.SS(6)) + surface.DrawText(nadetext) + + if !TacRP.IsGrenadeInfiniteAmmo(nade.Index) then + local qty + if nade.Singleton then + qty = self:GetOwner():HasWeapon(nade.GrenadeWep) and "x1" or "x0" + else + qty = "x" .. tostring(self:GetOwner():GetAmmoCount(nade.Ammo)) + end + local qtyw = surface.GetTextSize(qty) + surface.SetTextPos(nadex - qtyw * 0.5, nadey + TacRP.SS(15)) + surface.DrawText(qty) + end + + d = d - arcdegrees + + end + + local nade = currentnade + if nade.Icon then + surface.SetMaterial(nade.Icon) + surface.SetDrawColor(255, 255, 255, a * 255) + surface.DrawTexturedRect(scrw / 2 - sg * 0.5, scrh / 2 - sg * 0.5 - TacRP.SS(8), sg, sg) + end + + local nadetext = TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".name") or nade.PrintName + surface.SetFont("TacRP_HD44780A00_5x8_8") + local nadetextw = surface.GetTextSize(nadetext) + surface.SetTextPos(scrw / 2 - nadetextw * 0.5, scrh / 2 + TacRP.SS(6)) + surface.SetTextColor(255, 255, 255, a * 255) + surface.DrawText(nadetext) + + if !TacRP.IsGrenadeInfiniteAmmo(nade.Index) then + local qty + if nade.Singleton then + qty = self:GetOwner():HasWeapon(nade.GrenadeWep) and "x1" or "x0" + else + qty = "x" .. tostring(self:GetOwner():GetAmmoCount(nade.Ammo)) + end + surface.SetFont("TacRP_HD44780A00_5x8_8") + local qtyw = surface.GetTextSize(qty) + surface.SetTextPos(scrw / 2 - qtyw * 0.5, scrh / 2 + TacRP.SS(16)) + surface.SetTextColor(255, 255, 255, a * 255) + surface.DrawText(qty) + end + + -- description box is blocked in customize + if self:GetCustomize() then return end + + local w, h = TacRP.SS(96), TacRP.SS(128) + local tx, ty = scrw / 2 + r + TacRP.SS(16), scrh / 2 + + -- full name + + surface.SetDrawColor(0, 0, 0, 200 * a) + TacRP.DrawCorneredBox(tx, ty - h * 0.5 - TacRP.SS(28), w, TacRP.SS(24), col) + surface.SetTextColor(255, 255, 255, a * 255) + + local name = TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".name.full") + or TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".name") + or nade.FullName + or nade.PrintName + surface.SetFont("TacRP_Myriad_Pro_16") + local name_w, name_h = surface.GetTextSize(name) + if name_w > w then + surface.SetFont("TacRP_Myriad_Pro_14") + name_w, name_h = surface.GetTextSize(name) + end + surface.SetTextPos(tx + w / 2 - name_w / 2, ty - h * 0.5 - TacRP.SS(28) + TacRP.SS(12) - name_h / 2) + surface.DrawText(name) + + + -- Description + + surface.SetDrawColor(0, 0, 0, 200 * a) + TacRP.DrawCorneredBox(tx, ty - h * 0.5, w, h, col) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextPos(tx + TacRP.SS(4), ty - h / 2 + TacRP.SS(2)) + surface.DrawText( TacRP:GetPhrase("quicknade.fuse") ) + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextPos(tx + TacRP.SS(4), ty - h / 2 + TacRP.SS(10)) + surface.DrawText(TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".dettype") or nade.DetType or "") + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetTextPos(tx + TacRP.SS(4), ty - h / 2 + TacRP.SS(22)) + surface.DrawText( TacRP:GetPhrase("cust.description") ) + + surface.SetFont("TacRP_Myriad_Pro_8") + + if nade.Description then + nade.DescriptionMultiLine = TacRP.MultiLineText(TacRP:GetPhrase("quicknade." .. nade.PrintName .. ".desc") or nade.Description or "", w - TacRP.SS(7), "TacRP_Myriad_Pro_8") + end + + surface.SetTextColor(255, 255, 255, a * 255) + for i, text in ipairs(nade.DescriptionMultiLine) do + surface.SetTextPos(tx + TacRP.SS(4), ty - h / 2 + TacRP.SS(30) + (i - 1) * TacRP.SS(8)) + surface.DrawText(text) + end + + surface.SetFont("TacRP_Myriad_Pro_8") + surface.SetDrawColor(0, 0, 0, 200 * a) + + -- Only use the old bind hints if current hint is disabled + if TacRP.ConVars["hints"]:GetBool() then + self.LastHintLife = CurTime() + return + end + + if TacRP.ConVars["nademenu_click"]:GetBool() then + + local binded = input.LookupBinding("grenade1") + + TacRP.DrawCorneredBox(tx, ty + h * 0.5 + TacRP.SS(2), w, TacRP.SS(28), col) + + surface.SetTextPos(tx + TacRP.SS(4), ty + h / 2 + TacRP.SS(4)) + surface.DrawText( "[ " .. TacRP.GetBind("+attack") .. " ] " .. TacRP:GetPhrase("hint.quicknade.over") ) + + surface.SetTextPos(tx + TacRP.SS(4), ty + h / 2 + TacRP.SS(12)) + surface.DrawText( "[ " .. TacRP.GetBind("+attack2") .. " ] " .. TacRP:GetPhrase("hint.quicknade.under") ) + + if TacRP.AreTheGrenadeAnimsReadyYet then + surface.SetTextPos(tx + TacRP.SS(4), ty + h / 2 + TacRP.SS(20)) + surface.DrawText( "[ MOUSE3 ] " .. TacRP:GetPhrase("hint.quicknade.pull_out") ) + end + else + local binded = input.LookupBinding("grenade1") + + if binded then button = TacRP.GetBind("grenade1") else button = "G" end + + TacRP.DrawCorneredBox(tx, ty + h * 0.5 + TacRP.SS(2), w, TacRP.SS(28), col) + + surface.SetTextPos(tx + TacRP.SS(4), ty + h / 2 + TacRP.SS(4)) + surface.DrawText("[ " ..button .. " ] " .. TacRP:GetPhrase("hint.quicknade.over") .. " " .. TacRP:GetPhrase("hint.hold") ) + + surface.SetTextPos(tx + TacRP.SS(4), ty + h / 2 + TacRP.SS(12)) + surface.DrawText("[ " .. button .. " ] " .. TacRP:GetPhrase("hint.quicknade.under") ) + + if TacRP.AreTheGrenadeAnimsReadyYet then + surface.SetTextPos(tx + TacRP.SS(4), ty + h / 2 + TacRP.SS(20)) + surface.DrawText( "[ MOUSE3 ] " .. TacRP:GetPhrase("hint.quicknade.pull_out") ) + end + end +end + +local mat_none = Material("tacrp/blindfire/none.png", "smooth") +local mat_wall = Material("tacrp/blindfire/wall.png", "smooth") +local bf_slices = { + {TacRP.BLINDFIRE_RIGHT, mat_wall, 270}, + {TacRP.BLINDFIRE_KYS, Material("tacrp/blindfire/suicide.png", "smooth"), 0}, + {TacRP.BLINDFIRE_LEFT, mat_wall, 90}, + {TacRP.BLINDFIRE_UP, mat_wall, 0}, +} +local bf_slices2 = { + {TacRP.BLINDFIRE_RIGHT, mat_wall, 270}, + {TacRP.BLINDFIRE_LEFT, mat_wall, 90}, + {TacRP.BLINDFIRE_UP, mat_wall, 0}, +} +local bf_slices3 = { + {TacRP.BLINDFIRE_RIGHT, mat_wall, 270}, + {TacRP.BLINDFIRE_NONE, mat_none, 0}, + {TacRP.BLINDFIRE_LEFT, mat_wall, 90}, + {TacRP.BLINDFIRE_UP, mat_wall, 0}, +} +local lastmenu_bf +local bf_suicidelock +local bf_funnyline +local bf_lines = { + "Go ahead, see if I care.", + "Why not just killbind?", + "But you have so much to live for!", + "Just like Hemingway.", + "... NOW!", + "DO IT!", + "Now THIS is realism.", + "See you in the next life!", + "Time to commit a little insurance fraud.", + "Don't give them the satisfaction.", + "Why not jump off a building instead?", + "Ripperoni in pepperoni.", + "F", + "L + ratio + you're a minge + touch grass", + "You serve NO PURPOSE!", + "type unbindall in console", + "Citizens aren't supposed to have guns.", + "I have decided that I want to die.", + "What's the point?", + "eh", + "not worth", + "Just like Hitler.", + "Kill your own worst enemy.", + "You've come to the right place.", + "Don't forget to like and subscribe", + "noooooooooooooo", + "tfa base sucks lololololol", + "The HUD is mandatory.", + "No Bitches?", + "now you have truly become garry's mod", + "type 'tacrp_rock_funny 1' in console", + "is only gaem, y u haev to be mad?", + "And so it ends.", + "Suicide is badass!", + "Stop staring at me and get to it!", + "you like kissing boys don't you", + "A most tactical decision.", + "Bye have a great time!", + "Try doing this with the Dual MTX!", + "Try doing this with the RPG-7!", + "sad", + "commit sudoku", + "kermit suicide", + "You can disable this button in the options.", + "Goodbye, cruel world!", + "Adios!", + "Sayonara, [------]!", + "Nice boat!", + "I find it quite Inconceievable!", + "Delete system32.dll", + "Press ALT+F4 for admin gun", + "AKA: Canadian Medkit", + "The coward's way out", + "No man lives forever.", + "Goodbye, cruel world.", + "Doing this will result in an admin sit", + "Do it, before you turn.", + "Your HUD Buddy will miss you.", + "1-800-273-8255: Suicide and Crisis Support", + "Guaranteed dead or your money back!", + "Free health restore", + "For best results, make a scene in public", + "What are you, chicken?", + "Don't pussy out NOW", + "-1 Kill", + "You COULD type 'kill' in console", + "You know, back before all this started, me and my buddy Keith would grab a couple of .25s, piss tiny little guns, and take turns down by the river shootin' each other in the forehead with 'em. Hurt like a motherfucker, but we figured if we kept going, we could work our way up to bigger rounds, and eventually, ain't nothin' gon' be able to hurt us no more. Then we moved up to .22 and... well, let's just say I didn't go first.", + "How many headshots can YOU survive?", + "Shoot yourself in the head CHALLENGE", + "The only remedy to admin abuse", + "Try doing this with the Riot Shield!", + "Too bad you can't overcook nades", + "It's incredible you can survive this", + "Physics-based suicide", + "Sheep go to Heaven; goats go to Hell.", + "Nobody will be impressed.", + "You have a REALLY tough skull", + "Think about the clean-up", + "Canadian Healthcare Edition", + "A permanent solution to a temporary problem.", + "At least take some cops with you", + "Don't let them take you alive!", + "Teleport to spawn!", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "THE VOICES TOLD ME TO DO IT", + "Equestria awaits.", + "Truck-Kun sends you to Heaven. Gun-San sends you to Hell.", + "40,000 men and women everyday", + "And it's all YOUR fault!", + "YOU made me do this!", + "AAA quality game design", + "Stream it on TikTok!", + "This button is banned in Russia", + "Was it ethical to add this? No. But it was funny.", + "Wrote amazing eulogy, couldn't wait for funeral", + "It's gonna be a closed casket for you I think", + "A shitpost in addon form", + "More fun than working on ARC9", + "A final rebellion against an indifferent world.", + "Probably part of an infinite money exploit", + "You're not a real gamer until you've done this", + "We call this one the Detroit Cellphone", + "Do a backflip!", + "Do it for the Vine", + "Show it to your mother", + "To kill for yourself is murder. To kill yourself is hilarious.", + "This is all your fault.", + "Life begins at the other side of despair.", + "You are still a good person.", + "Reports of my survival have been greatly exaggerated.", + "Home? We can't go home.", + "No matter what happens next, don't be too hard on yourself.", + "There is no escape.", + "Is this really what you want, Walker? So be it.", + "We call this one the Devil's Haircut", + "Open your mind", + "Edgy jokes for dumbass teens", + "The fun will be endless", + "The living will envy the dead", + "There is only darkness.", + "There is nothing on the other side.", + "Is this how you get your kicks?", + "ngl this is how I feel when I log on to a server and see m9k", + "Administer straight to forehead", + "No tactical advantages whatsoever.", + "The best is yet to come", + "I know what you did, Mikey.", + "AMONG US AMONG US AMONG US AMONG US AMONG US AMONG US AMONG US AMONG US AMONG US AMONG US AMONG US AMONG US", + "What would your waifu think?", + "You won't get to see her, you know.", + "ez", + "Ehh... it's ez", + "So ez it's hard to believe", + "As shrimple as that", + "Well, I won't try and stop you", + "Send it to your favorite Youtubers", + "SHED YOUR BODY FREE YOUR SOUL", + "Suicide is illegal because you're damaging government property.", + "ESCAPE THE SIMULATION", + "Classic schizoposting", + "See you in Hell", + "The person you are most likely to kill is yourself.", + "There will be no encore.", + "Can't you pick a less messy method?", + "Just like Jeffrey Epstein... *snort*", + "The enemy. Shoot the enemy.", + "Let's see you do this on M9K", + "You won't do it, you pussy.", + "Ka-POW!", + "Bam-kerchow!", + "Zoop!", + "Zycie jest bez sensu i wszyscy zginemy", + "We really shouldn't be encouraging this.", + "You'll never see all the quotes", + "When the going gets tough, the tough get going", + "Acute cerebral lead poisoning", + "Those bullets are laced with estrogen, you know", + "google en passant", + "And then he sacrificed... THE PLAYERRRRRRRRRRR", + "You should grow and change as a person", + "dont leave me #slices then currentind = 0 end + + local arcdegrees = 360 / #slices + local d = 360 - s + + local cursorx, cursory = input.GetCursorPos() + local mouseangle = math.deg(math.atan2(cursorx - scrw / 2, cursory - scrh / 2)) + local mousedist = math.sqrt(math.pow(cursorx - scrw / 2, 2) + math.pow(cursory - scrh / 2, 2)) + if #slices == 3 then + mouseangle = math.NormalizeAngle(360 - mouseangle + arcdegrees) -- ??? + else + mouseangle = math.NormalizeAngle(360 - (mouseangle - s) + arcdegrees) + end + if mouseangle < 0 then + mouseangle = mouseangle + 360 + end + + if (self:GetOwner():KeyDown(IN_ZOOM) or self:GetOwner().TacRPBlindFireDown) and self:CheckBlindFire(true) and self.GrenadeMenuAlpha == 0 then + self.BlindFireMenuAlpha = math.Approach(self.BlindFireMenuAlpha, 1, 15 * ft) + if !lastmenu_bf then + gui.EnableScreenClicker(true) + TacRP.CursorEnabled = true + lastmenu_bf = true + if self:GetBlindFireMode() == TacRP.BLINDFIRE_KYS then + bf_suicidelock = 0 + else + bf_suicidelock = 1 + bf_funnyline = nil + end + end + + if mousedist > r2 then + local i = math.floor( mouseangle / arcdegrees ) + 1 + currentind = i + else + currentind = 0 + end + else + self.BlindFireMenuAlpha = math.Approach(self.BlindFireMenuAlpha, 0, -10 * ft) + if lastmenu_bf then + if !self:GetCustomize() then + gui.EnableScreenClicker(false) + TacRP.CursorEnabled = false + end + if (!nocenter or currentind > 0) and (nosuicide or bf_suicidelock == 0 or currentind != 2) then + net.Start("tacrp_toggleblindfire") + net.WriteUInt(currentind > 0 and slices[currentind][1] or TacRP.BLINDFIRE_NONE, TacRP.BlindFireNetBits) + net.SendToServer() + end + + lastmenu_bf = false + end + end + + if self.BlindFireMenuAlpha < 1 then + bf_funnyline = nil + lastseenfunnyline = false + end + + if self.BlindFireMenuAlpha <= 0 then + return + end + + local a = self.BlindFireMenuAlpha + local col = Color(255, 255, 255, 255 * a) + + surface.DrawCircle(scrw / 2, scrh / 2, r, 255, 255, 255, a * 255) + + surface.SetDrawColor(0, 0, 0, a * 200) + draw.NoTexture() + filledcircle(scrw / 2, scrh / 2, r, 32) + + if currentind and canhighlight(self, slices[currentind]) then + surface.SetDrawColor(150, 150, 150, a * 100) + draw.NoTexture() + if currentind > 0 then + if !nosuicide and currentind == 2 and bf_suicidelock > 0 then + surface.SetDrawColor(150, 50, 50, a * 100) + end + local d0 = -s - arcdegrees * (currentind - 2) + slicedcircle(scrw / 2, scrh / 2, r, 32, d0, d0 + arcdegrees) + else + filledcircle(scrw / 2, scrh / 2, r2, 32) + end + end + + surface.SetDrawColor(0, 0, 0, a * 255) + surface.DrawCircle(scrw / 2, scrh / 2, r2, 255, 255, 255, a * 255) + + for i = 1, #slices do + local rad = math.rad( d + arcdegrees * 0.5 ) + + surface.SetDrawColor(255, 255, 255, a * 255) + surface.DrawLine( + scrw / 2 + math.cos(math.rad(d)) * r2, + scrh / 2 - math.sin(math.rad(d)) * r2, + scrw / 2 + math.cos(math.rad(d)) * r, + scrh / 2 - math.sin(math.rad(d)) * r) + + local nadex, nadey = scrw / 2 + math.cos(rad) * ri, scrh / 2 - math.sin(rad) * ri + + if !canhighlight(self, slices[i]) or (!nosuicide and i == 2 and bf_suicidelock > 0) then + surface.SetDrawColor(150, 150, 150, a * 200) + end + + surface.SetMaterial(slices[i][2]) + surface.DrawTexturedRectRotated(nadex, nadey, sg, sg, slices[i][3]) + + d = d - arcdegrees + end + + if !nocenter then + surface.SetDrawColor(255, 255, 255, a * 255) + surface.SetMaterial(mat_none) + surface.DrawTexturedRectRotated(scrw / 2, scrh / 2, TacRP.SS(28), TacRP.SS(28), 0) + end + + if !nosuicide and currentind == 2 then + + local w, h = TacRP.SS(132), TacRP.SS(24) + local tx, ty = scrw / 2, scrh / 2 + r + TacRP.SS(4) + + surface.SetDrawColor(0, 0, 0, 200 * a) + TacRP.DrawCorneredBox(tx - w / 2, ty, w, h, col) + surface.SetTextColor(255, 255, 255, a * 255) + + surface.SetFont("TacRP_Myriad_Pro_12") + surface.SetTextColor(255, 255, 255, 255 * a) + local t1 = TacRP:GetPhrase("hint.shootself") + local t1_w = surface.GetTextSize(t1) + surface.SetTextPos(tx - t1_w / 2, ty + TacRP.SS(2)) + surface.DrawText(t1) + + surface.SetFont("TacRP_Myriad_Pro_6") + + if !lastseenfunnyline then + startseefunnylinetime = CurTime() + end + + lastseenfunnyline = true + + local t2 = bf_funnyline or "" + if bf_suicidelock > 0 then + surface.SetFont("TacRP_Myriad_Pro_8") + t2 = "[ " .. TacRP.GetBind("attack") .. " ] - " .. TacRP:GetPhrase("hint.unlock") + + if self:GetCustomize() then + t2 = TacRP:GetPhrase("hint.exitcustmenu") + end + lastseenfunnyline = false + elseif !bf_funnyline then + bf_funnyline = bf_lines[math.random(1, #bf_lines)] + end + local t2_w, t2_h = surface.GetTextSize(t2) + if t2_w > w then + render.SetScissorRect(tx - w / 2, ty, tx + w / 2, ty + h, true) + surface.SetTextPos(tx - ((CurTime() - startseefunnylinetime + 2.5) * w * 0.3) % (t2_w * 2) + (t2_w / 2), ty + TacRP.SS(18) - t2_h / 2) + else + surface.SetTextPos(tx - t2_w / 2, ty + TacRP.SS(18) - t2_h / 2) + end + surface.DrawText(t2) + + render.SetScissorRect(0, 0, 0, 0, false) + end +end + +hook.Add("VGUIMousePressed", "tacrp_grenademenu", function(pnl, mousecode) + local wpn = LocalPlayer():GetActiveWeapon() + if !(LocalPlayer():Alive() and IsValid(wpn) and wpn.ArcticTacRP and !wpn:StillWaiting(nil, true)) then return end + if wpn.GrenadeMenuAlpha == 1 then + if !TacRP.ConVars["nademenu_click"]:GetBool() or !currentnade then return end + if mousecode == MOUSE_MIDDLE and TacRP.AreTheGrenadeAnimsReadyYet then + local nadewep = currentnade.GrenadeWep + if !nadewep or !wpn:CheckGrenade(currentnade.Index, true) then return end + wpn.GrenadeMenuAlpha = 0 + gui.EnableScreenClicker(false) + TacRP.CursorEnabled = false + if LocalPlayer():HasWeapon(nadewep) then + input.SelectWeapon(LocalPlayer():GetWeapon(nadewep)) + else + net.Start("tacrp_givenadewep") + net.WriteUInt(currentnade.Index, TacRP.QuickNades_Bits) + net.SendToServer() + wpn.GrenadeWaitSelect = nadewep -- cannot try to switch immediately as the nade wep does not exist on client yet + end + elseif mousecode == MOUSE_RIGHT or mousecode == MOUSE_LEFT then + wpn.GrenadeThrowOverride = mousecode == MOUSE_RIGHT + net.Start("tacrp_togglenade") + net.WriteUInt(currentnade.Index, TacRP.QuickNades_Bits) + net.WriteBool(true) + net.WriteBool(wpn.GrenadeThrowOverride) + net.SendToServer() + wpn.Secondary.Ammo = currentnade.Ammo or "none" + end + elseif wpn.BlindFireMenuAlpha == 1 then + if mousecode == MOUSE_LEFT and currentind == 2 then + bf_suicidelock = bf_suicidelock - 1 + end + end +end) diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_sway.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_sway.lua new file mode 100644 index 0000000..318def5 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_sway.lua @@ -0,0 +1,162 @@ +SWEP.ViewModelVelocityPos = Vector(0, 0, 0) +SWEP.ViewModelVelocityAng = Angle(0, 0, 0) + +SWEP.ViewModelPos = Vector(0, 0, 0) +SWEP.ViewModelAng = Angle(0, 0, 0) + +SWEP.SwayCT = 0 + +function SWEP:GetViewModelSway(pos, ang) + local d = Lerp(self:GetSightDelta(), 1, 0.02) + local v = 1 + local steprate = 1 + + local FT = self:DeltaSysTime() * 1 --FrameTime() + local CT = UnPredictedCurTime() -- CurTime() + + d = d * 0.25 + + pos = pos + (ang:Up() * (math.sin(self.SwayCT * 0.311 * v) + math.cos(self.SwayCT * 0.44 * v)) * math.sin(CT * 0.8) * d) + pos = pos + (ang:Right() * (math.sin(self.SwayCT * 0.324 * v) + math.cos(self.SwayCT * 0.214 * v)) * math.sin(CT * 0.76) * d) + + --if IsFirstTimePredicted() then + self.SwayCT = self.SwayCT + (FT * steprate) + --end + + return pos, ang +end + +SWEP.ViewModelLastEyeAng = Angle(0, 0, 0) +SWEP.ViewModelSwayInertia = Angle(0, 0, 0) + +function SWEP:GetViewModelInertia(pos, ang) + local d = 1 - self:GetSightDelta() + + local diff = self:GetOwner():EyeAngles() - self.ViewModelLastEyeAng + + diff = diff / 4 + + diff.p = math.Clamp(diff.p, -1, 1) + diff.y = math.Clamp(diff.y, -1, 1) + + local vsi = self.ViewModelSwayInertia + + vsi.p = math.ApproachAngle(vsi.p, diff.p, vsi.p / 10 * FrameTime() / 0.5) + vsi.y = math.ApproachAngle(vsi.y, diff.y, vsi.y / 10 * FrameTime() / 0.5) + + self.ViewModelLastEyeAng = self:GetOwner():EyeAngles() + + ang:RotateAroundAxis(ang:Up(), vsi.y * 12 * d) + ang:RotateAroundAxis(ang:Right(), -vsi.p * 12 * d) + + -- pos = pos - (ang:Up() * vsi.p * 0.5 * d) + -- pos = pos - (ang:Right() * vsi.y * 0.5 * d) + + return pos, ang +end + +function SWEP:GetViewModelSmooth(pos, ang) + return pos, ang +end + +SWEP.ViewModelBobVelocity = 0 +SWEP.ViewModelNotOnGround = 0 + +SWEP.BobCT = 0 + +function SWEP:GetViewModelBob(pos, ang) + local step = 10 + local mag = 1 + + local FT = self:DeltaSysTime() * 1 --FrameTime() + local CT = UnPredictedCurTime() -- CurTime() + + local v = self:GetOwner():GetVelocity():Length() + local walks = self:GetOwner():GetWalkSpeed() + local runs = self:GetOwner():GetRunSpeed() + + local sprints = walks + math.max(runs - walks, walks) + + v = math.Clamp(v, 0, sprints) + self.ViewModelBobVelocity = math.Approach(self.ViewModelBobVelocity, v, FT * 2400) + local d = math.Clamp(self.ViewModelBobVelocity / sprints, 0, 1) + + if self:GetOwner():OnGround() then + self.ViewModelNotOnGround = math.Approach(self.ViewModelNotOnGround, 0, FT / 1) + else + self.ViewModelNotOnGround = math.Approach(self.ViewModelNotOnGround, 1, FT / 1) + end + + d = d * Lerp(self:GetSightDelta(), 1, 0.1) + mag = d * 2 + step = 10 + + local m = 0.2 + ang:RotateAroundAxis(ang:Forward(), math.sin(self.BobCT * step * 0.5) * ((math.sin(CT * 6.151) * m) + 1) * 4.5 * d) + ang:RotateAroundAxis(ang:Right(), math.sin(self.BobCT * step * 0.12) * ((math.sin(CT * 1.521) * m) + 1) * 2.11 * d) + pos = pos - (ang:Up() * math.sin(self.BobCT * step) * 0.11 * ((math.sin(CT * 3.515) * m) + 1) * mag) + pos = pos + (ang:Forward() * math.sin(self.BobCT * step * 0.5) * 0.11 * ((math.sin(CT * 1.615) * m) + 1) * mag) + pos = pos + (ang:Right() * (math.sin(self.BobCT * step * 0.3) + (math.cos(self.BobCT * step * 0.3332))) * 0.16 * mag) + + local steprate = Lerp(d, 1, 2.5) + + steprate = Lerp(self.ViewModelNotOnGround, steprate, 0.9) + + --if IsFirstTimePredicted() or game.SinglePlayer() then + self.BobCT = self.BobCT + (FT * steprate) + --end + + return pos, ang +end + +SWEP.LastViewModelVerticalVelocity = 0 +-- SWEP.ViewModelLanded = 0 +-- SWEP.ViewModelLanding = 0 + +function SWEP:GetMidAirBob(pos, ang) + local v = -self:GetOwner():GetVelocity().z / 200 + + v = math.Clamp(v, -1, 1) + + -- if v == 0 and self.LastViewModelVerticalVelocity != 0 then + -- self.ViewModelLanding = self.LastViewModelVerticalVelocity + -- self.ViewModelLanded = 1 + -- end + + -- if self.ViewModelLanded > 0 then + -- self.ViewModelLanded = math.Approach(self.ViewModelLanded, 0, FrameTime() / 0.25) + + v = Lerp(5 * FrameTime(), self.LastViewModelVerticalVelocity, v) + -- end + + self.LastViewModelVerticalVelocity = v + + local d = self.ViewModelNotOnGround + + d = d * Lerp(self:GetSightDelta(), 1, 0.1) + + ang:RotateAroundAxis(ang:Right(), -v * d * 8 * math.sin(CurTime() * 0.15)) + + return pos, ang +end + +SWEP.ViewModelInertiaX = 0 +SWEP.ViewModelInertiaY = 0 + +function SWEP:GetViewModelLeftRight(pos, ang) + local v = self:GetOwner():GetVelocity() + local d = Lerp(self:GetSightDelta(), 1, 0) + + v, _ = WorldToLocal(v, Angle(0, 0, 0), Vector(0, 0, 0), self:GetOwner():EyeAngles()) + + local vx = math.Clamp(v.x / 200, -1, 1) + local vy = math.Clamp(v.y / 200, -1, 1) + + self.ViewModelInertiaX = math.Approach(self.ViewModelInertiaX, vx, math.abs(vx) * FrameTime() / 0.1) + self.ViewModelInertiaY = math.Approach(self.ViewModelInertiaY, vy, math.abs(vy) * FrameTime() / 0.1) + + pos = pos + (ang:Right() * -self.ViewModelInertiaX * 0.65 * d) + pos = pos + (ang:Forward() * self.ViewModelInertiaY * 0.5 * d) + + return pos, ang +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_thermal.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_thermal.lua new file mode 100644 index 0000000..84cbd56 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_thermal.lua @@ -0,0 +1,167 @@ +local rt_w = 240 +local rt_h = 240 + +local rtmat = GetRenderTarget("tacrp_pipscope_thermal", rt_w, rt_h, false) +local rtmat_spare = GetRenderTarget("tacrp_pipscope_thermal2", rt_w, rt_h, false) + +local lastthermalscope = false +local thermaltime = 0 + +local csm_boot_1 = Material("tacrp/hud/thermal_boot_1.png", "mips smooth") + +local csm_1 = Material("tacrp/hud/thermal_1.png", "mips smooth") +local csm_2 = Material("tacrp/hud/thermal_2.png", "mips smooth") + +local noise1 = Material("tacrp/hud/noise1.png") +local noise2 = Material("tacrp/hud/noise2.png") +local noise3 = Material("tacrp/hud/noise3.png") +local noise4 = Material("tacrp/hud/noise4.png") + +local noisemats = { + noise1, + noise2, + noise3, + noise4 +} + +local lastrendertime = 0 + +local fps = 30 + +function SWEP:DoThermalRT() + if self:GetScopeLevel() <= 0 or !self:GetPeeking() then lastthermalscope = false return end + if TacRP.OverDraw then return end + + if !lastthermalscope then + thermaltime = 0 + end + + if lastrendertime > CurTime() - (1 / fps) then return end + + local angles = self:GetShootDir() + local origin = self:GetMuzzleOrigin() + + local rt = { + x = 0, + y = 0, + w = rt_w, + h = rt_h, + aspect = 0.999, + angles = angles, + origin = origin, + drawviewmodel = false, + fov = 18.5, + znear = 6 + } + + render.PushRenderTarget(rtmat, 0, 0, rt_w, rt_h) + + if thermaltime >= 0.75 or thermaltime == 0 then + TacRP.OverDraw = true + render.RenderView(rt) + TacRP.OverDraw = false + end + + DrawColorModify({ + ["$pp_colour_addr"] = 0.25 * 132 / 255, + ["$pp_colour_addg"] = 0.25 * 169 / 255, + ["$pp_colour_addb"] = 0.25 * 154 / 255, + ["$pp_colour_brightness"] = 0.7, + ["$pp_colour_contrast"] = 0.75, + ["$pp_colour_colour"] = 0, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }) + + DrawBloom(0.25, 0.5, 16, 8, 1, 1, 1, 1, 1) + + if thermaltime >= 0.75 then + local thermalents = ents.FindInCone(origin, angles:Forward(), 10000, 0.939692620) // 20 degrees + + render.SuppressEngineLighting(true) + render.SetBlend(0.9) + local col = Color(255, 0, 0) + if self:GetTactical() then + col = Color(0, 255, 255) + end + render.SetColorModulation(col.r, col.g, col.b) + + cam.Start3D(origin, angles, 20) + + cam.IgnoreZ(false) + + for _, ent in ipairs(thermalents) do + if ent == self:GetOwner() then continue end + if !ent:IsValid() or ent:IsWorld() then continue end + if ent:Health() <= 0 then continue end + if ent:IsPlayer() or ent:IsNPC() or ent:IsNextBot() or ent:IsOnFire() then + ent:DrawModel() + end + if ent:IsVehicle() or ent:IsOnFire() or ent.ArcCW_Hot or ent:IsScripted() and !ent:GetOwner():IsValid() then + ent:DrawModel() + end + end + + cam.End3D() + + render.SetColorModulation(1, 1, 1) + render.SuppressEngineLighting(false) + render.MaterialOverride() + render.SetBlend(1) + end + + if self:GetTactical() then + render.PushRenderTarget(rtmat, 0, 0, rt_w, rt_h) + render.CopyTexture( rtmat, rtmat_spare ) + + render.Clear(255, 255, 255, 255, true, true) + render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_REVERSE_SUBTRACT) + + render.DrawTextureToScreen(rtmat_spare) + + render.OverrideBlend(false) + render.PopRenderTarget() + end + + cam.Start2D() + + render.ClearDepth() + + if thermaltime < 0.75 then + surface.SetDrawColor(0, 0, 0) + surface.DrawRect(0, 0, rt_w, rt_h) + end + + if thermaltime < 0.45 then + surface.SetMaterial(csm_boot_1) + else + if self:GetTactical() then + surface.SetMaterial(csm_1) + else + surface.SetMaterial(csm_2) + end + end + + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRect(0, 0, rt_w, rt_h) + cam.End2D() + + render.PopRenderTarget() + + thermaltime = thermaltime + (math.random(0, 5) * math.random(0, 5) * (1 / fps) / 6.25) + + lastthermalscope = true + lastrendertime = CurTime() +end + +function SWEP:DoThermalCam() + if self:GetScopeLevel() <= 0 or !self:GetPeeking() then lastthermalscope = false return end + + local w = TacRP.SS(480 / 4) + local h = TacRP.SS(480 / 4) + local x = (ScrW() - w) / 2 + local y = (ScrH() - h) / 2 + + render.DrawTextureToScreenRect(rtmat, x, y, w, h) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_viewmodel.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_viewmodel.lua new file mode 100644 index 0000000..265d9f2 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_viewmodel.lua @@ -0,0 +1,241 @@ +function SWEP:ViewModelDrawn() + if IsValid(self.QuickNadeModel) then + self.QuickNadeModel:DrawModel() + end + + self:DrawCustomModel(false) + self:DrawLasers() + + local newactiveeffects = {} + for _, effect in ipairs(self.ActiveEffects) do + if !IsValid(effect) then continue end + if !effect.VMContext then continue end + + effect:DrawModel() + + table.insert(newactiveeffects, effect) + end + + self.ActiveEffects = newactiveeffects +end + +function SWEP:DrawCustomModel(wm, custom_wm) + + if !wm and !IsValid(self:GetOwner()) then return end + if !wm and self:GetOwner():IsNPC() then return end + + local mdl = self.VModel + + if wm then + mdl = self.WModel + end + + if !mdl then + self:SetupModel(wm, custom_wm) + + mdl = self.VModel + + if wm then + mdl = self.WModel + end + end + + local parentmdl = self + + if !wm then + parentmdl = self:GetVM() + elseif custom_wm then + parentmdl = custom_wm + end + + if !mdl then return end + + for _, model in pairs(mdl) do + if !IsValid(model) then continue end + local offset_pos = model.Pos + local offset_ang = model.Ang + local bone = model.Bone + local atttbl = {} + local slottbl = {} + + if model.WMBase then + parentmdl = self:GetOwner() + end + + parentmdl:SetupBones() + parentmdl:InvalidateBoneCache() + + if !offset_pos or !offset_ang then + local slot = model.Slot + slottbl = self.Attachments[slot] + atttbl = TacRP.GetAttTable(self.Attachments[slot].Installed) + + bone = slottbl.Bone + + if wm then + bone = slottbl.WMBone or "ValveBiped.Bip01_R_Hand" + end + + offset_pos = slottbl.Pos_VM + offset_ang = slottbl.Ang_VM + + if wm then + offset_pos = slottbl.Pos_WM + offset_ang = slottbl.Ang_WM + end + + for _, ele in ipairs(self:GetElements()) do + if !ele.AttPosMods or !ele.AttPosMods[slot] then continue end + if wm then + if ele.AttPosMods[slot].Pos_WM then + offset_pos = ele.AttPosMods[slot].Pos_WM + end + if ele.AttPosMods[slot].Ang_WM then + offset_ang = ele.AttPosMods[slot].Ang_WM + end + if ele.AttPosMods[slot].WMBone then + bone = ele.AttPosMods[slot].WMBone + end + else + if ele.AttPosMods[slot].Pos_VM then + offset_pos = ele.AttPosMods[slot].Pos_VM + end + if ele.AttPosMods[slot].Ang_VM then + offset_ang = ele.AttPosMods[slot].Ang_VM + end + if ele.AttPosMods[slot].Bone then + bone = ele.AttPosMods[slot].Bone + end + end + end + end + + if !bone then continue end + + local boneindex = parentmdl:LookupBone(bone) + if !boneindex then continue end + + local bonemat = parentmdl:GetBoneMatrix(boneindex) + if !bonemat then continue end + + local bpos, bang + bpos = bonemat:GetTranslation() + bang = bonemat:GetAngles() + + local apos, aang = bpos, bang + + if offset_pos then + apos:Add(bang:Forward() * offset_pos.x) + apos:Add(bang:Right() * offset_pos.y) + apos:Add(bang:Up() * offset_pos.z) + end + + if offset_ang then + aang:RotateAroundAxis(aang:Right(), offset_ang.p) + aang:RotateAroundAxis(aang:Up(), offset_ang.y) + aang:RotateAroundAxis(aang:Forward(), offset_ang.r) + end + + local moffset = (atttbl.ModelOffset or Vector(0, 0, 0)) + if wm then + moffset = moffset * (slottbl.WMScale or 1) + else + moffset = moffset * (slottbl.VMScale or 1) + end + + apos:Add(aang:Forward() * moffset.x) + apos:Add(aang:Right() * moffset.y) + apos:Add(aang:Up() * moffset.z) + + model:SetPos(apos) + model:SetAngles(aang) + model:SetRenderOrigin(apos) + model:SetRenderAngles(aang) + + if model.IsHolosight and !wm then + cam.Start3D(EyePos(), EyeAngles(), self.ViewModelFOV, 0, 0, nil, nil, 1, 10000) + render.DepthRange(0.0, 0.1) + self:DoHolosight(model) + cam.End3D() + render.DepthRange(0.0, 0.1) + end + + if !model.NoDraw then + model:DrawModel() + end + end + + if !wm then + self:DrawFlashlightsVM() + end +end + +function SWEP:PreDrawViewModel() + if self:GetValue("ScopeHideWeapon") and self:IsInScope() then + render.SetBlend(0) + end + + -- Apparently setting this will fix the viewmodel position and angle going all over the place in benchgun. + if TacRP.ConVars["dev_benchgun"]:GetBool() then + if self.OriginalViewModelFOV == nil then + self.OriginalViewModelFOV = self.ViewModelFOV + end + self.ViewModelFOV = self:GetOwner():GetFOV() + elseif self.OriginalViewModelFOV then + self.ViewModelFOV = self.OriginalViewModelFOV + self.OriginalViewModelFOV = nil + end + -- self.ViewModelFOV = self:GetViewModelFOV() + + render.DepthRange(0.0, 0.1) +end + +function SWEP:PostDrawViewModel() + cam.IgnoreZ(false) + + if self:GetValue("ScopeHideWeapon") and self:IsInScope() then + render.SetBlend(1) + end + + cam.Start3D() + cam.IgnoreZ(false) + local newpcfs = {} + + for _, pcf in ipairs(self.PCFs) do + if IsValid(pcf) then + pcf:Render() + table.insert(newpcfs, pcf) + end + end + + if !inrt then self.PCFs = newpcfs end + + local newmzpcfs = {} + + for _, pcf in ipairs(self.MuzzPCFs) do + if IsValid(pcf) then + pcf:Render() + table.insert(newmzpcfs, pcf) + end + end + + if !inrt then self.MuzzPCFs = newmzpcfs end + cam.End3D() +end + +--[[ +SWEP.SmoothedViewModelFOV = nil +function SWEP:GetViewModelFOV() + local target = self.ViewModelFOV + + if TacRP.ConVars["dev_benchgun"]:GetBool() then + target = self:GetOwner():GetFOV() + end + + self.SmoothedViewModelFOV = self.SmoothedViewModelFOV or target + local diff = math.abs(target - self.SmoothedViewModelFOV) + self.SmoothedViewModelFOV = math.Approach(self.SmoothedViewModelFOV, target, diff * FrameTime() / 0.25) + + return self.SmoothedViewModelFOV +end +]] diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_worldmodel.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_worldmodel.lua new file mode 100644 index 0000000..23b0c9f --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/cl_worldmodel.lua @@ -0,0 +1,35 @@ +function SWEP:DrawWorldModel(flags) + + if !self.CertainAboutAtts and !self.AskedAboutAtts then + if !self:GetValue("PrimaryGrenade") and !self:GetValue("PrimaryMelee") then + self:RequestWeapon() + -- debugoverlay.Sphere(self:GetPos(), 16, 5, color_white, true) + end + self.AskedAboutAtts = true + end + + -- Ugly workaround: OBS_MODE_IN_EYE spectate seems to call DrawWorldModel but doesn't actually render it? + if LocalPlayer():GetObserverTarget() != self:GetOwner() or LocalPlayer():GetObserverMode() != OBS_MODE_IN_EYE then + self:DrawCustomModel(true) + end + + if self:GetValue("Laser") and self:GetTactical() then + self:SetRenderBounds(Vector(-16, -16, -16), Vector(16, 16, 15000)) + else + self:SetRenderBounds(Vector(-16, -16, -16), Vector(16, 16, 16)) + end + + self:DrawModel() +end + +hook.Add("PostDrawTranslucentRenderables", "TacRP_TranslucentDraw", function() + for _, ply in pairs(player.GetAll()) do + local wep = ply:GetActiveWeapon() + if ply != LocalPlayer() and IsValid(wep) and wep.ArcticTacRP then + wep:DrawLasers(true) + wep:DrawFlashlightsWM() + wep:DrawFlashlightGlares() + wep:DoScopeGlint() + end + end +end) diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_aggregate.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_aggregate.lua new file mode 100644 index 0000000..ff3d10d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_aggregate.lua @@ -0,0 +1,1452 @@ +function SWEP:GetReloadTime(base) + local vm = self:GetVM() + local valfunc = base and self.GetBaseValue or self.GetValue + + if !valfunc(self, "ShotgunReload") then + local seq = vm:LookupSequence(self:TranslateSequence("reload")) + local basetime = vm:SequenceDuration(seq) + local mult = valfunc(self, "ReloadTimeMult") / TacRP.ConVars["mult_reloadspeed"]:GetFloat() + + return basetime * mult + else + local seq1 = vm:LookupSequence(self:TranslateSequence("reload_start")) + -- local seq2 = vm:LookupSequence(self:TranslateSequence("reload")) + local seq3 = vm:LookupSequence(self:TranslateSequence("reload_finish")) + + local time_1 = vm:SequenceDuration(seq1) + local time_2 = valfunc(self, "ShotgunUpInTime") --vm:SequenceDuration(seq2) + local time_3 = vm:SequenceDuration(seq3) + + local mult = valfunc(self, "ReloadTimeMult") / TacRP.ConVars["mult_reloadspeed"]:GetFloat() + + local basetime = time_1 + (time_2 * valfunc(self, "ClipSize")) + time_3 + + if valfunc(self, "ShotgunThreeload") then + basetime = time_1 + (time_2 * valfunc(self, "ClipSize") / 3) + time_3 + end + + return basetime * mult + end +end + +function SWEP:GetDeployTime(base) + local vm = self:GetVM() + local valfunc = base and self.GetBaseValue or self.GetValue + + local anim = "deploy" + local mult = valfunc(self, "DeployTimeMult") + + if valfunc(self, "TryUnholster") then + anim = "unholster" + mult = mult * valfunc(self, "UnholsterTimeMult") + end + + local seq = vm:LookupSequence(self:TranslateSequence(anim)) + local basetime = vm:SequenceDuration(seq) + + return basetime * mult +end + +function SWEP:CalcHolsterTime(base) + local vm = self:GetVM() + local valfunc = base and self.GetBaseValue or self.GetValue + + local anim = "holster" + if valfunc(self, "NoHolsterAnimation") then + anim = "deploy" + end + + local mult = valfunc(self, "HolsterTimeMult") + + local seq = vm:LookupSequence(self:TranslateSequence(anim)) + local basetime = vm:SequenceDuration(seq) + + return basetime * mult +end + +function SWEP:GetMuzzleVelocity(base) + local valfunc = base and self.GetBaseValue or self.GetValue + + local basetime = valfunc(self, "MuzzleVelocity") + + if valfunc(self, "ShootEnt") then + basetime = valfunc(self, "ShootEntForce") + end + + return math.ceil(0.3048 * basetime / 12) +end + +-- function SWEP:GetMeanShotsToFail(base) +-- local valfunc = base and self.GetBaseValue or self.GetValue +-- local shootchance = valfunc(self, "ShootChance") + +-- return 1 / (1 - shootchance) +-- end + +function SWEP:GetBestFiremode(base) + local valfunc = base and self.GetBaseValue or self.GetValue + + if valfunc(self, "Firemodes") then + local bfm, bfm_i + for k, v in pairs(valfunc(self, "Firemodes")) do + if !bfm or v == 2 or (bfm <= 1 and v < bfm) then + bfm = v + bfm_i = k + end + end + return bfm, bfm_i + else + return valfunc(self, "Firemode") or 0, 1 + end +end + +local hitgroups = { + [HITGROUP_HEAD] = 0.1, + [HITGROUP_CHEST] = 0.2, + [HITGROUP_STOMACH] = 0.3, + [HITGROUP_LEFTARM] = 0.2, + [HITGROUP_LEFTLEG] = 0.2, +} + +local mssd_scoring = { + [HITGROUP_HEAD] = {0.15, 0.5, {1, 0.6, 0.3, 0.15, 0.05}}, + [HITGROUP_CHEST] = {0.25, 0.75, {1, 0.75, 0.4, 0.2, 0.1}}, + [HITGROUP_STOMACH] = {0.25, 0.8, {1, 0.8, 0.5, 0.25, 0.15, 0.05}}, + [HITGROUP_LEFTARM] = {0.2, 0.5, {1, 0.85, 0.6, 0.3, 0.2, 0.1, 0.05}}, + [HITGROUP_LEFTLEG] = {0.15, 0.5, {1, 0.9, 0.7, 0.4, 0.25, 0.15, 0.1}}, +} + +local mssd_scoring_ttt = { + [HITGROUP_HEAD] = {0.25, 0.5, {1, 0.75, 0.50, 0.25, 0.15, 0.10, 0.05, 0.025}}, + [HITGROUP_CHEST] = {0.25, 0.75, {1, 0.90, 0.75, 0.55, 0.45, 0.35, 0.25, 0.15, 0.10, 0.05}}, + [HITGROUP_STOMACH] = {0.25, 1, {1, 1.00, 0.90, 0.80, 0.60, 0.40, 0.30, 0.20, 0.15, 0.10, 0.05}}, + [HITGROUP_LEFTARM] = {0.15, 1, {1, 1.00, 0.80, 0.70, 0.50, 0.30, 0.25, 0.15, 0.10, 0.05, 0.025}}, + [HITGROUP_LEFTLEG] = {0.10, 1, {1, 1.00, 0.90, 0.75, 0.60, 0.50, 0.40, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05}}, +} + +SWEP.StatGroupGrades = { + {88, "S", Color(230, 60, 60)}, + {75, "A", Color(230, 180, 60)}, + {60, "B", Color(230, 230, 60)}, + {40, "C", Color(60, 230, 60)}, + {20, "D", Color(60, 60, 230)}, + {00, "E", Color(145, 94, 146)}, + { -math.huge, "F", Color(150, 150, 150)}, +} +SWEP.StatGroups = { + { + Name = "rating.lethality", + Description = "rating.lethality.desc", + RatingFunction = function(self, base) + -- local score = 0 + local valfunc = base and self.GetBaseValue or self.GetValue + + local bfm = self:GetBestFiremode(base) + local rrpm = self:GetRPM(base, bfm) + local pbd = valfunc(self, "PostBurstDelay") + local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT + local pve = TacRP.GetBalanceMode() == TacRP.BALANCE_PVE + local health = pve and 50 or 100 + + local num = valfunc(self, "Num") + local bdm = self:GetBodyDamageMultipliers(base) + local bdm_add = 0 + for k, v in pairs(hitgroups) do + bdm_add = bdm_add + bdm[k] * v + end + + local d_max, d_min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min") + local explosive_damage = valfunc(self, "ExplosiveDamage") + + d_max = d_max + explosive_damage + d_min = d_min + explosive_damage + + local dmg_max = math.max(d_max, d_min) + local dmg_avg = dmg_max * bdm_add --Lerp(0.2, math.max(d_max, d_min), math.min(d_max, d_min)) * bdm_add + + -- max single shot damage + local mssd = 0 + for k, v in pairs(ttt and mssd_scoring_ttt or mssd_scoring) do + local stk = math.ceil(health / (dmg_max * (bdm[k] or 1) * (1 + (num - 1) * v[2]))) + mssd = mssd + (v[3][stk] or 0) * v[1] + -- print(bdm[k], stk, (mssd_scoring[k][stk] or 0)) + end + if pve then + mssd = mssd ^ 1 + elseif ttt then + mssd = mssd ^ 0.75 + end + + -- avg time to kill + local stk = math.ceil(health / (dmg_avg * num)) + local ttk_s + if stk == 1 then + ttk_s = math.Clamp(rrpm / 120, 0, 1) ^ 0.75 + else + local ttk = (stk - 1) * (60 / rrpm) + if bfm < 0 then + ttk = ttk + math.floor(ttk / -bfm) * pbd + end + if pve then + ttk_s = math.Clamp(1 - ttk / 2, 0, 1) ^ 2 + elseif ttt then + ttk_s = math.Clamp(1 - ttk / 3, 0, 1) ^ 3 + else + ttk_s = math.Clamp(1 - ttk / 1.5, 0, 1) ^ 1.5 + end + end + + local scores = {mssd, ttk_s} + table.sort(scores) + + return scores[2] * 75 + scores[1] * 25 + + end, + }, + { + Name = "rating.suppression", + Description = "rating.suppression.desc", + RatingFunction = function(self, base) + local valfunc = base and self.GetBaseValue or self.GetValue + + local bfm = self:GetBestFiremode(base) + local rrpm = self:GetRPM(base, bfm) + local erpm = rrpm + local pbd = valfunc(self, "PostBurstDelay") + local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT + local pve = TacRP.GetBalanceMode() == TacRP.BALANCE_PVE + + if bfm == 1 then + erpm = math.min(rrpm, 600) + math.max(rrpm - 600, 0) ^ 0.75 -- you can't click *that* fast + elseif bfm < 0 then + erpm = 60 / ((1 / (rrpm / 60)) + (pbd / -bfm)) + end + + local num = valfunc(self, "Num") + local bdm = self:GetBodyDamageMultipliers(base) + local bdm_add = 0 + for k, v in pairs(hitgroups) do + bdm_add = bdm_add + bdm[k] * v + end + + local d_max, d_min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min") + local explosive_damage = valfunc(self, "ExplosiveDamage") + local explosive_radius = valfunc(self, "ExplosiveRadius") + + explosive_damage = explosive_damage * explosive_radius / 64 * 2 + + d_max = d_max + explosive_damage + d_min = d_min + explosive_damage + + -- local dmg_max = math.max(d_max, d_min) + local dmg_avg = Lerp(0.2, math.max(d_max, d_min), math.min(d_max, d_min)) * bdm_add + + -- raw dps + local dps = dmg_avg * num * erpm / 60 + -- average dps over time + local dot = dmg_avg * num / (60 / erpm + self:GetReloadTime(base) / (valfunc(self, "ClipSize") / valfunc(self, "AmmoPerShot"))) + local dps_s, dot_s + if pve then + dps_s = math.Clamp((dps - 12.5) / 150, 0, 1) + dot_s = math.Clamp((dot - 5) / 100, 0, 1) ^ 0.9 + elseif ttt then + dps_s = math.Clamp((dps - 25) / 200, 0, 1) + dot_s = math.Clamp((dot - 10) / 100, 0, 1) + else + dps_s = math.Clamp((dps - 50) / 400, 0, 1) ^ 0.9 + dot_s = math.Clamp((dot - 20) / 200, 0, 1) ^ 0.9 + end + + local scores = {dps_s, dot_s} + table.sort(scores) + + return scores[2] * 70 + scores[1] * 30 + end, + }, + { + Name = "rating.range", + Description = "rating.range.desc", + RatingFunction = function(self, base) + local score = 0 + local valfunc = base and self.GetBaseValue or self.GetValue + + local d_max, d_min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min") + local r_min, r_max = self:GetMinMaxRange(base) + local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT + + local r_mid = r_min + (r_max - r_min) / 2 + local d_diff = math.abs(d_max - d_min) / math.max(d_max, d_min) + if d_max > d_min then + -- [100] 50% damage falloff range + score = score + math.Clamp((r_mid - 500) / (ttt and 2500 or 3000), 0, 1) ^ 0.75 * 100 + + -- [0] damage reduction from range + -- score = score + math.Clamp(1 - d_diff, 0, 1) ^ 1.5 * 0 + else + -- [40] free points + -- [40] 50% damage rampup range + score = score + 40 + math.Clamp(r_mid / (ttt and 1500 or 3000), 0, 1) * 40 + -- print(r_mid, math.Clamp(1 - r_mid / 5000, 0, 1)) + + -- [20] damage reduction from range + score = score + math.Clamp(1 - d_diff, 0, 1) * 20 + end + + return score + end, + }, + { + Name = "rating.precision", + Description = "rating.precision.desc", + RatingFunction = function(self, base) + local score = 0 + local valfunc = base and self.GetBaseValue or self.GetValue + + local bfm = self:GetBestFiremode(base) + local rpm = valfunc(self, "RPM") + + local num = valfunc(self, "Num") + local spread = valfunc(self, "Spread") + local rps = valfunc(self, "RecoilPerShot") + local rsp = valfunc(self, "RecoilSpreadPenalty") + local rrt = self:GetRecoilResetTime(base) + local rdr = valfunc(self, "RecoilDissipationRate") + local dt = math.max(0, -rrt) + local rbs = dt * rdr -- amount of recoil we can recover between shots even if fired ASAP + + if self:UseAltRecoil() then + local min = 0.0001 + local tgt = 0.015 + if num > 2 then tgt = 0.04 end + score = math.Clamp(1 - (spread - min) / tgt, 0, 1) * 100 + else + -- [50] base spread + local min = 0.001 + local tgt = 0.015 + if num > 2 then tgt = 0.04 end + score = score + math.Clamp(1 - (spread - min) / tgt, 0, 1) * 50 + + local fss = valfunc(self, "RecoilFirstShotMult") * rps + -- score = score + math.Clamp(1 - (spread + fss * rsp - rbs) / tgt, 0, 1) * 25 + + -- [50] spread over 0.3s (or one burst) + local shots = math.min(math.ceil(rpm / 60 * 0.3), math.floor(self:GetBaseValue("ClipSize") * 0.5)) + if bfm < 0 then + shots = -bfm + end + if rbs <= fss then + local so1 = (fss - rbs + shots * (rps - rbs)) * rsp + score = score + math.Clamp(1 - so1 / 0.03, 0, 1) ^ 1.25 * 50 + else + -- delay is so long we always get first shot + score = score + 50 + end + end + + -- recoil reset time + -- score = score + math.Clamp(1 - math.max(0, rrt - delay) / 0.25, 0, 1) * 10 + + return score + end, + }, + { + Name = "rating.control", + Description = "rating.control.desc", + RatingFunction = function(self, base) + local score = 0 + local valfunc = base and self.GetBaseValue or self.GetValue + + local bfm = self:GetBestFiremode(base) + local erpm = valfunc(self, "RPM") + local pbd = valfunc(self, "AutoBurst") and valfunc(self, "PostBurstDelay") or math.max(0.15, valfunc(self, "PostBurstDelay")) + if bfm == 1 then + erpm = math.min(erpm, 600) -- you can't click *that* fast + elseif bfm < 0 then + erpm = 60 / ((1 / (erpm / 60)) + (pbd / -bfm)) + end + local rps = valfunc(self, "RecoilPerShot") + local rsp = valfunc(self, "RecoilSpreadPenalty") + local rrt = self:GetRecoilResetTime(base) + local rdr = valfunc(self, "RecoilDissipationRate") + local dt = math.max(0, -rrt) + local rbs = dt * rdr -- amount of recoil we can recover between shots even if fired ASAP + local fss = valfunc(self, "RecoilFirstShotMult") + local rmax = valfunc(self, "RecoilMaximum") + local rk = math.abs(valfunc(self, "RecoilKick")) + + -- local rrec_s = math.Clamp(rdr / rps / rmax / 5, 0, 1) ^ 0.9 + -- local mspr_s = math.Clamp(1 - rmax * rsp / 0.04, 0, 1) + -- score = score + mspr_s * 20 + + -- [50] recoil kick over 1s + local score_rk1 = 50 + local shots = math.ceil(erpm / 60 * 1) + score = score + math.Clamp(1 - (rk * shots * rrt - 3) / 12, 0, 1) * score_rk1 + -- print("rk1", rk * shots * rrt, math.Clamp(1 - rk * shots * rrt / 15, 0, 1) * score_rk1) + + -- [50] bloom over 1s + local score_sg = 50 + if bfm < 0 then + local rbb = math.max(0, pbd - rrt) * rdr -- recovery between bursts + local rpb = -bfm * rps - (-bfm - 1) * rbs - rbb -- recoil per full burst + score = score + math.Clamp(1 - (rpb * rsp * 2) / 0.03, 0, 1) ^ 1.5 * score_sg + -- print("spb", rpb * rsp, math.Clamp(1 - (rpb * rsp * 3) / 0.04, 0, 1) ^ 2 * score_sg) + else + -- local sg = math.min(shots, math.ceil(rmax / rsp)) + local sot = math.min(rmax, fss - rbs + (shots - 1) * (rps - rbs)) * rsp + -- print("sot", sot, math.Clamp(1 - (sot - 0.01) / 0.03, 0, 1) ^ 0.75 * score_sg) + score = score + math.Clamp(1 - (sot - 0.01) / 0.02, 0, 1) ^ 1.5 * score_sg + end + + return score + end, + }, + { + Name = "rating.handling", + Description = "rating.handling.desc", + RatingFunction = function(self, base) + local score = 0 + + -- [40] sprint + score = score + math.Clamp(1 - (self:GetSprintToFireTime(base) - 0.15) / 0.5, 0, 1) * 40 + + -- [45] ads + score = score + math.Clamp(1 - (self:GetAimDownSightsTime(base) - 0.15) / 0.5, 0, 1) * 45 + + -- [15] deploy + score = score + math.Clamp(1 - (self:GetDeployTime(base) - 0.5) / 1.5, 0, 1) * 15 + + return score + end, + }, + { + Name = "rating.maneuvering", + Description = "rating.maneuvering.desc", + RatingFunction = function(self, base) + local score = 0 + local valfunc = base and self.GetBaseValue or self.GetValue + + -- [40] free aim + sway (if both are disabled, score goes to other 2) + local bonus = 40 + local freeaim_s = 1 + if TacRP.ConVars["freeaim"]:GetBool() then + if valfunc(self, "FreeAim") then + freeaim_s = math.Clamp(1 - (valfunc(self, "FreeAimMaxAngle") - 2) / 8, 0, 1) ^ 0.8 + end + bonus = 0 + end + local sway_s = 1 + if TacRP.ConVars["sway"]:GetBool() then + sway_s = math.Clamp(1 - (valfunc(self, "Sway") - 0.75) / 2.25, 0, 1) + bonus = 0 + end + if bonus == 0 then + score = score + math.min(freeaim_s, sway_s) * 20 + math.max(freeaim_s, sway_s) * 20 + end + + -- local diff = valfunc(self, "HipFireSpreadPenalty") / math.Clamp(self:GetBaseValue("Spread"), 0.015, 0.03) + local hipspread = valfunc(self, "Spread") + valfunc(self, "HipFireSpreadPenalty") + valfunc(self, "MoveSpreadPenalty") + + -- [0] peeking + -- score = score + math.Clamp(1 - (hipspread * valfunc(self, "PeekPenaltyFraction") - 0.01) / 0.015, 0, 1) * (10 + bonus * 0.25) + + -- [50] hip spread + spread + score = score + math.Clamp(1 - (hipspread - 0.02) / 0.05, 0, 1) * (50 + bonus * 0.75) + + -- [10] mid-air spread + score = score + math.Clamp(1 - (valfunc(self, "MidAirSpreadPenalty") ) / 0.1, 0, 1) * (10 + bonus * 0.25) + + return score + end, + }, + { + Name = "rating.mobility", + Description = "rating.mobility.desc", + RatingFunction = function(self, base) + local score = 0 + local valfunc = base and self.GetBaseValue or self.GetValue + local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT + + if ttt then + -- [30] move + score = score + math.Clamp((valfunc(self, "MoveSpeedMult") - 0.6) / 0.4, 0, 1) * 30 + + -- [25] sighted + score = score + math.Clamp((valfunc(self, "SightedSpeedMult") - 0.2) / 0.8, 0, 1) * 25 + + -- [25] shooting + score = score + math.Clamp((valfunc(self, "ShootingSpeedMult") - 0.2) / 0.8, 0, 1) * 25 + + -- [20] reload + score = score + math.Clamp((valfunc(self, "ReloadSpeedMult") - 0.4) / 0.6, 0, 1) * 20 + else + -- [50] move + score = score + math.Clamp((math.min(1, valfunc(self, "MoveSpeedMult")) - 0.6) / 0.4, 0, 1) ^ 2 * 50 + + -- [25] sighted + score = score + math.Clamp((math.min(1, valfunc(self, "SightedSpeedMult")) - 0.3) / 0.7, 0, 1) * 25 + + -- [25] shooting + score = score + math.Clamp((math.min(1, valfunc(self, "ShootingSpeedMult")) - 0.4) / 0.6, 0, 1) * 25 + + -- [-20] reload + score = score - math.Clamp(1 - (math.min(1, valfunc(self, "ReloadSpeedMult")) - 0.4) / 0.6, 0, 1) * 20 + end + + + return score + end, + }, + { + Name = "rating.stability", + Description = "rating.stability.desc", + RatingFunction = function(self, base) + local score = 0 + local valfunc = base and self.GetBaseValue or self.GetValue + + -- [40] sway + score = score + math.Clamp(1 - valfunc(self, "Sway") / 2.5, 0, 1) ^ 0.8 * 40 + + -- [60] sighted sway + score = score + math.Clamp(1 - valfunc(self, "ScopedSway") / 1, 0, 1) ^ 1.5 * 60 + + -- blindfire sway + -- score = score + math.Clamp(1 - valfunc(self, "BlindFireSway") / 2, 0, 1) * 10 + + return score + end, + }, +} + +SWEP.StatDisplay = { + -- { + -- Name = "", + -- Value = "", + -- LowerIsBetter = false, + -- AggregateFunction = nil, + -- Unit = "" + -- } + { + Name = "spacer.damage", + Description = "spacer.damage.desc", + Spacer = true, + }, + { + Name = "stat.damage", + Description = "stat.damage.desc", + Value = "Damage_Max", + AggregateFunction = function(self, base, val) + if !(self:IsDamageConstant(false) and self:IsDamageConstant(true)) then return end + -- local valfunc = base and self.GetBaseValue or self.GetValue + -- return math.Round(val * valfunc(self, "Num"), 0) + return math.floor(val) + end, + }, + { + Name = "stat.damage_max", + Description = "stat.damage_max.desc", + Value = "Damage_Max", + AggregateFunction = function(self, base, val) + if self:IsDamageConstant(false) and self:IsDamageConstant(true) then return end + -- local valfunc = base and self.GetBaseValue or self.GetValue + -- return math.Round(val * valfunc(self, "Num"), 0) + return math.floor(val) + end, + }, + { + Name = "stat.damage_min", + Description = "stat.damage_min.desc", + Value = "Damage_Min", + AggregateFunction = function(self, base, val) + if self:IsDamageConstant(false) and self:IsDamageConstant(true) then return end + -- local valfunc = base and self.GetBaseValue or self.GetValue + -- return math.Round(val * valfunc(self, "Num"), 0) + return math.floor(val) + end, + }, + { + Name = "stat.explosivedamage", + Description = "stat.explosivedamage.desc", + Value = "ExplosiveDamage", + DefaultValue = 0, + }, + { + Name = "stat.explosiveradius", + Description = "stat.explosiveradius.desc", + Value = "ExplosiveRadius", + DefaultValue = 0, + DisplayFunction = function(self, base, val) + return self:RangeUnitize(val) + end, + }, + { + Name = "stat.damagemultnpc", + Description = "stat.damagemultnpc.desc", + Value = "DamageMultNPC", + DefaultValue = 1, + AggregateFunction = function(self, base, val) + return math.Round(val * 100, 1) + end, + Unit = "%", + }, + { + Name = "stat.num", + Description = "stat.num.desc", + Value = "Num", + DefaultValue = 1, + }, + { + Name = "stat.range_min", + Description = "stat.range_min.desc", + Value = "Range_Min", + AggregateFunction = function(self, base, val) + if self:IsDamageConstant(base) then return end + return val + end, + DisplayFunction = function(self, base, val) + if val == 0 then return "∞" end + return self:RangeUnitize(val) + end, + }, + { + Name = "stat.range_max", + Description = "stat.range_max.desc", + Value = "Range_Max", + AggregateFunction = function(self, base, val) + if self:IsDamageConstant(base) then return end + return val + end, + DisplayFunction = function(self, base, val) + if val == 0 then return "∞" end + return self:RangeUnitize(val) + end, + }, + { + Name = "stat.raw_dps", + Description = "stat.raw_dps.desc", + Value = "", + AggregateFunction = function(self, base, val) + local valfunc = base and self.GetBaseValue or self.GetValue + + local bfm = self:GetBestFiremode(base) + local rrpm = self:GetRPM(base, bfm) + local erpm = rrpm + local pbd = valfunc(self, "PostBurstDelay") + + if bfm < 0 then + erpm = 60 / ((1 / (rrpm / 60)) + (pbd / -bfm)) + end + + local num = valfunc(self, "Num") + local dmg = math.max(valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min")) + dmg = dmg + valfunc(self, "ExplosiveDamage") + + return math.Round(dmg * num * erpm / 60, 1) + end, + }, + { + Name = "stat.min_ttk", + Description = "stat.min_ttk.desc", + Value = "", + Unit = "unit.second", + LowerIsBetter = true, + AggregateFunction = function(self, base, val) + local valfunc = base and self.GetBaseValue or self.GetValue + + local bfm = self:GetBestFiremode(base) + local rpm = self:GetRPM(base, bfm) + + local num = valfunc(self, "Num") + local dmg = math.max(valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min")) + local stk = math.ceil(100 / (dmg * num)) + local ttk = stk * (60 / rpm) + + if bfm < 0 and stk > -bfm then + local pbd = valfunc(self, "PostBurstDelay") + ttk = ttk + pbd * math.floor(stk / -bfm) + end + return math.Round(ttk, 2) + end, + }, + { + Name = "spacer.action", + Description = "spacer.action.desc", + Spacer = true, + }, + { + Name = "stat.clipsize", + Description = "stat.clipsize.desc", + Value = "ClipSize", + }, + { + Name = "stat.ammopershot", + Description = "stat.ammopershot.desc", + Value = "AmmoPerShot", + DefaultValue = 1, + }, + { + Name = "stat.rpm", + Description = "stat.rpm.desc", + Value = "RPM", + Unit = "rpm", + AggregateFunction = function(self, base, val) + return math.Round(val, 0) + end, + }, + { + Name = "stat.rpm_burst", + Description = "stat.rpm_burst.desc", + Value = "RPMMultBurst", + Unit = "rpm", + AggregateFunction = function(self, base, val) + if !self:HasFiremode(-1) then return end + local valfunc = base and self.GetBaseValue or self.GetValue + return math.Round(val * valfunc(self, "RPM"), 0) + end, + DefaultValue = 1, + }, + { + Name = "stat.rpm_burst_peak", + Description = "stat.rpm_burst_peak.desc", + Value = "PostBurstDelay", + Unit = "rpm", + AggregateFunction = function(self, base, val) + if !self:HasFiremode(-1) then return end + local valfunc = base and self.GetBaseValue or self.GetValue + local cfm = self:GetFiremodeBurstLength() + local delay = 60 / ( valfunc(self, "RPM") * valfunc(self, "RPMMultBurst") ) -- delay + local nerd = 0 + nerd = nerd + (delay * (cfm-1)) + nerd = nerd + math.max( delay, valfunc(self, "PostBurstDelay") ) + nerd = nerd / cfm + nerd = 60 / nerd + return math.Round( nerd ) + end, + --DefaultValue = 0, + }, + { + Name = "stat.rpm_semi", + Description = "stat.rpm_semi.desc", + Value = "RPMMultSemi", + Unit = "rpm", + AggregateFunction = function(self, base, val) + if !self:HasFiremode(1) then return end + local valfunc = base and self.GetBaseValue or self.GetValue + return math.Round(val * valfunc(self, "RPM"), 0) + end, + DefaultValue = 1, + }, + { + Name = "stat.shotstofail", + Description = "stat.shotstofail.desc", + AggregateFunction = function(self, base, val) + return math.Round(1 / self:GetJamChance(base), 0) + end, + DisplayFunction = function(self, base, val) + if val == 0 then return "∞" end + return math.Round(1 / self:GetJamChance(base), 0) + end, + DefaultValue = 0, + Value = "JamFactor", + }, + { + Name = "stat.postburstdelay", + Description = "stat.postburstdelay.desc", + Value = "PostBurstDelay", + AggregateFunction = function(self, base, val) + if !self:HasFiremode(-1) then return end + return math.Round(val, 2) + end, + Unit = "unit.second", + LowerIsBetter = true, + DefaultValue = 0, + }, + { + Name = "stat.firemode", + Description = "stat.firemode.desc", + AggregateFunction = function(self, base, val) + if !val then + val = {base and self:GetTable()["Firemode"] or self:GetValue("Firemode")} + end + if #val == 1 then + if val[1] == 2 then + return "Auto" + elseif val[1] == 1 then + return "Semi" + elseif val[1] < 0 then + return (-val[1]) .. "-Burst" + end + else + local tbl = table.Copy(val) + table.sort(tbl, function(a, b) + if a == 2 then + return b == 2 + elseif b == 2 then + return a != 2 + end + return math.abs(a) <= math.abs(b) + end) + local str = "S-" + for i = 1, #tbl do + str = str .. (tbl[i] == 2 and "F" or math.abs(tbl[i])) .. (i < #tbl and "-" or "") + end + return str + end + return table.ToString(val) + end, + BetterFunction = function(self, old, new) + if !old then + old = {self:GetBaseValue("Firemode")} + end + if !new then + new = {self:GetValue("Firemode")} + end + local oldbest, newbest = 0, 0 + for i = 1, #old do + local v = math.abs(old[i]) + if math.abs(old[i]) > oldbest or old[i] == 2 then + oldbest = (old[i] == 2 and math.huge) or v + end + end + for i = 1, #new do + local v = math.abs(new[i]) + if v > newbest or new[i] == 2 then + newbest = (new[i] == 2 and math.huge) or v + end + end + if oldbest == newbest then + return #old != #new , #old < #new + else + return true, oldbest < newbest + end + end, + DifferenceFunction = function(self, orig, value) + if !orig then + orig = {self:GetBaseValue("Firemode")} + end + if !value then + value = {self:GetValue("Firemode")} + end + local old_best = self:GetBestFiremode(true) + local new_best = self:GetBestFiremode(false) + if old_best == new_best then return end + if new_best == 2 then + return "+Auto" + elseif old_best == 2 then + return "-Auto" + elseif new_best < 0 then + return "+Burst" + end + end, + Value = "Firemodes", + -- HideIfSame = true, + }, + { + Name = "stat.reloadtime", + Description = "stat.reloadtime.desc", + AggregateFunction = function(self, base, val) + return math.Round(self:GetReloadTime(base), 2) + end, + Value = "ReloadTimeMult", + LowerIsBetter = true, + Unit = "unit.second", + }, + { + Name = "spacer.ballistics", + Description = "spacer.ballistics.desc", + Spacer = true, + }, + { + Name = "stat.spread", + Description = "stat.spread.desc", + AggregateFunction = function(self, base, val) + return math.Round(math.deg(val) * 60, 1) + end, + Unit = "′", + Value = "Spread", + LowerIsBetter = true, + }, + { + Name = "stat.muzzlevelocity", + Description = "stat.muzzlevelocity.desc", + AggregateFunction = function(self, base, val) + return math.Round(self:GetMuzzleVelocity(base), 2) + end, + ConVarCheck = "tacrp_physbullet", + Value = "MuzzleVelocity", + LowerIsBetter = false, + Unit = "unit.mps", + }, + { + Name = "stat.penetration", + Description = "stat.penetration.desc", + Value = "Penetration", + Unit = "\"" + }, + { + Name = "stat.armorpenetration", + Description = "stat.armorpenetration.desc", + Value = "ArmorPenetration", + AggregateFunction = function(self, base, val) + return math.max(math.Round(val * 100, 1), 0) + end, + Unit = "%", + }, + { + Name = "stat.armorbonus", + Description = "stat.armorbonus.desc", + Value = "ArmorBonus", + AggregateFunction = function(self, base, val) + return math.Round(val * 1, 2) + end, + Unit = "x", + }, + { + Name = "spacer.recoilbloom", + Description = "spacer.recoilbloom.desc", + Spacer = true, + }, + { + Name = "stat.recoilkick", + Description = "stat.recoilkick.desc", + Value = "RecoilKick", + LowerIsBetter = true, + }, + { + Name = "stat.recoilstability", + Description = "stat.recoilstability.desc", + Value = "RecoilStability", + AggregateFunction = function(self, base, val) + return math.Clamp(math.Round(val * 100), 0, 90) + end, + Unit = "%", + LowerIsBetter = false, + }, + -- For use when bloom is modifying spread. (default) + { + Name = "stat.recoilspread", + Description = "stat.recoilspread.desc", + AggregateFunction = function(self, base, val) + if self:UseAltRecoil(base) then return end + return math.Round(math.deg(val) * 60, 1) + end, + Unit = "′", + Value = "RecoilSpreadPenalty", + LowerIsBetter = true, + -- ConVarCheck = "tacrp_altrecoil", + -- ConVarInvert = true, + }, + -- For use when in "Bloom Modifies Recoil" + { + Name = "stat.recoilspread2", + Description = "stat.recoilspread2.desc", + AggregateFunction = function(self, base, val) + if !self:UseAltRecoil(base) then return end + return math.Round(val * (base and self:GetBaseValue("RecoilAltMultiplier") or self:GetValue("RecoilAltMultiplier")), 2) + end, + Unit = nil, + Value = "RecoilSpreadPenalty", + LowerIsBetter = true, + -- ConVarCheck = "tacrp_altrecoil", + -- ConVarInvert = false, + }, + { + Name = "stat.recoildissipation", + Description = "stat.recoildissipation.desc", + AggregateFunction = function(self, base, val) + return math.Round(val, 2) + --return math.Round(math.deg(val * (base and self:GetTable().RecoilSpreadPenalty or self:GetValue("RecoilSpreadPenalty"))), 1) + end, + Unit = "unit.persecond", + Value = "RecoilDissipationRate", + }, + { + Name = "stat.recoilresettime", + Description = "stat.recoilresettime.desc", + Value = "RecoilResetTime", + LowerIsBetter = true, + Unit = "s", + }, + { + Name = "stat.recoilmaximum", + Description = "stat.recoilmaximum.desc", + Value = "RecoilMaximum", + LowerIsBetter = true, + }, + { + Name = "stat.recoilfirstshot", + Description = "stat.recoilfirstshot.desc", + Value = "RecoilFirstShotMult", + AggregateFunction = function(self, base, val) + return math.Round(val * (base and self:GetBaseValue("RecoilPerShot") or self:GetValue("RecoilPerShot")), 2) + end, + DefaultValue = 1, + LowerIsBetter = true, + }, + { + Name = "stat.recoilpershot", + Description = "stat.recoilpershot.desc", + AggregateFunction = function(self, base, val) + return math.Round(val, 2) + end, + Unit = "x", + Value = "RecoilPerShot", + HideIfSame = true, + LowerIsBetter = true, + }, + { + Name = "stat.recoilcrouch", + Description = "stat.recoilcrouch.desc", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + Value = "RecoilMultCrouch", + LowerIsBetter = true, + -- HideIfSame = true, + }, + { + Name = "stat.recoilburst", + Description = "stat.recoilburst.desc", + Value = "RecoilMultBurst", + Unit = "%", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + LowerIsBetter = true, + DefaultValue = 1, + }, + { + Name = "stat.recoilsemi", + Description = "stat.recoilsemi.desc", + Value = "RecoilMultSemi", + Unit = "%", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + LowerIsBetter = true, + DefaultValue = 1, + }, + { + Name = "spacer.mobility", + Description = "spacer.mobility.desc", + Spacer = true, + }, + { + Name = "stat.movespeed", + Description = "stat.movespeed.desc", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + Value = "MoveSpeedMult", + ConVarCheck = "tacrp_penalty_move" + }, + { + Name = "stat.shootingspeed", + Description = "stat.shootingspeed.desc", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + Value = "ShootingSpeedMult", + ConVarCheck = "tacrp_penalty_firing" + }, + { + Name = "stat.sightedspeed", + Description = "stat.sightedspeed.desc", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + Value = "SightedSpeedMult", + ValueCheck = "Scope", + ConVarCheck = "tacrp_penalty_aiming" + }, + { + Name = "stat.reloadspeed", + Description = "stat.reloadspeed.desc", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + Value = "ReloadSpeedMult", + DefaultValue = 1, + ConVarCheck = "tacrp_penalty_reload" + }, + { + Name = "stat.meleespeed", + Description = "stat.meleespeed.desc", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + Value = "MeleeSpeedMult", + DefaultValue = 1, + ConVarCheck = "tacrp_penalty_melee" + }, + { + Name = "spacer.handling", + Description = "spacer.handling.desc", + Spacer = true, + }, + { + Name = "stat.sprinttofire", + Description = "stat.sprinttofire.desc", + Value = "SprintToFireTime", + AggregateFunction = function(self, base, val) + return math.Round(self:GetSprintToFireTime(base), 3) + end, + Unit = "unit.second", + LowerIsBetter = true, + }, + { + Name = "stat.aimdownsights", + Description = "stat.aimdownsights.desc", + Value = "AimDownSightsTime", + AggregateFunction = function(self, base, val) + return math.Round(self:GetAimDownSightsTime(base), 3) + end, + Unit = "unit.second", + LowerIsBetter = true, + ValueCheck = "Scope", + }, + { + Name = "stat.deploytime", + Description = "stat.deploytime.desc", + AggregateFunction = function(self, base, val) + return math.Round(self:GetDeployTime(base), 2) + end, + Value = "DeployTimeMult", + LowerIsBetter = true, + -- HideIfSame = true, + Unit = "unit.second", + }, + { + Name = "stat.holstertime", + Description = "stat.holstertime.desc", + AggregateFunction = function(self, base, val) + return math.Round(self:CalcHolsterTime(base), 2) + end, + Value = "HolsterTimeMult", + LowerIsBetter = true, + Unit = "unit.second", + ConVarCheck = "tacrp_holster", + }, + { + Name = "spacer.maneuvering", + Description = "spacer.maneuvering.desc", + Spacer = true, + }, + { + Name = "stat.freeaimangle", + Description = "stat.freeaimangle.desc", + Unit = "°", + Value = "FreeAimMaxAngle", + AggregateFunction = function(self, base, val) + return math.max(0, math.Round(val, 1)) + end, + LowerIsBetter = true, + -- HideIfSame = true, + ConVarCheck = "tacrp_freeaim", + }, + { + Name = "stat.midairspread", + Description = "stat.midairspread.desc", + AggregateFunction = function(self, base, val) + return math.Round(math.deg(val), 1) + end, + Unit = "°", + Value = "MidAirSpreadPenalty", + LowerIsBetter = true, + -- HideIfSame = true, + }, + { + Name = "stat.hipfirespread", + Description = "stat.hipfirespread.desc", + AggregateFunction = function(self, base, val) + return math.Round(math.deg(val), 1) + end, + Unit = "°", + Value = "HipFireSpreadPenalty", + LowerIsBetter = true, + -- HideIfSame = true, + }, + { + Name = "spacer.sway", + Description = "spacer.sway.desc", + Spacer = true, + ConVarCheck = "tacrp_sway", + }, + { + Name = "stat.sway", + Description = "stat.sway.desc", + + Value = "Sway", + LowerIsBetter = true, + ConVarCheck = "tacrp_sway", + }, + { + Name = "stat.scopedsway", + Description = "stat.scopedsway.desc", + Value = "ScopedSway", + LowerIsBetter = true, + ConVarCheck = "tacrp_sway", + ValueCheck = "Scope", + }, + { + Name = "stat.swaycrouch", + Description = "stat.swaycrouch.desc", + Value = "SwayCrouchMult", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + LowerIsBetter = true, + -- HideIfSame = true, + ConVarCheck = "tacrp_sway", + }, + { + Name = "spacer.misc", + Description = "spacer.misc.desc", + Spacer = true, + }, + { + Name = "stat.meleedamage", + Description = "stat.meleedamage.desc", + Value = "MeleeDamage", + HideIfSame = false, + }, + { + Name = "stat.peekpenalty", + Description = "stat.peekpenalty.desc", + Value = "PeekPenaltyFraction", + AggregateFunction = function(self, base, val) + return math.min(100, math.Round(val * 100, 0)) + end, + Unit = "%", + LowerIsBetter = true, + }, + { + Name = "stat.quickscope", + Description = "stat.quickscope.desc", + Value = "QuickScopeSpreadPenalty", + AggregateFunction = function(self, base, val) + return math.Round(math.deg(val) * 60, 1) + end, + Unit = "′", + LowerIsBetter = true, + }, + { + Name = "stat.vol_shoot", + Description = "stat.vol_shoot.desc", + Value = "Vol_Shoot", + AggregateFunction = function(self, base, val) + return math.Round(val, 1) + end, + Unit = "dB", + LowerIsBetter = true, + }, +} + +SWEP.StatGroupsMelee = { + { + Name = "stat.damage", + Description = "stat.damage.desc_melee", + RatingFunction = function(self, base) + local valfunc = base and self.GetBaseValue or self.GetValue + return Lerp((valfunc(self, "MeleeDamage") - 10) / 50, 0, 100) + end, + }, + { + Name = "rating.meleeattacktime", + Description = "rating.meleeattacktime.desc", + RatingFunction = function(self, base) + local valfunc = base and self.GetBaseValue or self.GetValue + + return Lerp(1 - (valfunc(self, "MeleeAttackTime") - 0.15) / 0.55, 0, 100) + end, + }, + -- { + -- Name = "Reach", + -- Description = "Attack distance.", + -- RatingFunction = function(self, base) + -- local valfunc = base and self.GetBaseValue or self.GetValue + + -- return Lerp((valfunc(self, "MeleeRange") - 64) / 128, 0, 100) + -- end, + -- }, + { + Name = "stat.meleeperkstr", + Description = "stat.meleeperkstr.desc", + RatingFunction = function(self, base) + local valfunc = base and self.GetBaseValue or self.GetValue + return Lerp(valfunc(self, "MeleePerkStr"), 0, 100) + + end, + }, + { + Name = "stat.meleeperkagi", + Description = "stat.meleeperkagi.desc", + RatingFunction = function(self, base) + local valfunc = base and self.GetBaseValue or self.GetValue + return Lerp(valfunc(self, "MeleePerkAgi"), 0, 100) + end, + }, + { + Name = "stat.meleeperkint", + Description = "stat.meleeperkint.desc", + RatingFunction = function(self, base) + local valfunc = base and self.GetBaseValue or self.GetValue + return Lerp(valfunc(self, "MeleePerkInt"), 0, 100) + end, + }, +} + +SWEP.StatDisplayMelee = { + { + Name = "stat.damage", + Description = "stat.damage.desc_melee", + Value = "MeleeDamage", + AggregateFunction = function(self, base, val) + return math.floor(val * self:GetConfigDamageMultiplier()) + end, + }, + { + Name = "stat.meleeattacktime", + Description = "stat.meleeattacktime.desc", + Value = "MeleeAttackTime", + Unit = "unit.second", + LowerIsBetter = true, + }, + { + Name = "stat.meleeattackmisstime", + Description = "stat.meleeattackmisstime.desc", + Value = "MeleeAttackMissTime", + Unit = "unit.second", + LowerIsBetter = true, + }, + { + Name = "stat.meleerange", + Description = "stat.meleerange.desc", + Value = "MeleeRange", + DisplayFunction = function(self, base, val) + return self:RangeUnitize(val) + end, + }, + { + Name = "stat.meleedelay", + Description = "stat.meleedelay.desc", + Value = "MeleeDelay", + Unit = "unit.second", + LowerIsBetter = true, + }, + { + Name = "stat.meleeperkstr", + Description = "stat.meleeperkstr.desc", + Value = "MeleePerkStr", + AggregateFunction = function(self, base, val) + return math.floor(val * 100) + end, + }, + { + Name = "stat.meleeperkagi", + Description = "stat.meleeperkagi.desc", + Value = "MeleePerkAgi", + AggregateFunction = function(self, base, val) + return math.floor(val * 100) + end, + }, + { + Name = "stat.meleeperkint", + Description = "stat.meleeperkint.desc", + Value = "MeleePerkInt", + AggregateFunction = function(self, base, val) + return math.floor(val * 100) + end, + }, + + + { + Name = "stat.melee2damage", + Description = "stat.melee2damage.desc", + Value = "Melee2Damage", + ValueCheck = "HeavyAttack", + AggregateFunction = function(self, base, val) + return math.Round(self:GetHeavyAttackDamage(base) * self:GetConfigDamageMultiplier()) + end, + }, + { + Name = "stat.melee2attacktime", + Description = "stat.melee2attacktime.desc", + Value = "Melee2AttackTime", + ValueCheck = "HeavyAttack", + AggregateFunction = function(self, base, val) + return math.Round(self:GetHeavyAttackTime(false, base), 2) + end, + Unit = "unit.second", + LowerIsBetter = true, + }, + { + Name = "stat.melee2attackmisstime", + Description = "stat.melee2attackmisstime.desc", + Value = "Melee2AttackMissTime", + ValueCheck = "HeavyAttack", + AggregateFunction = function(self, base, val) + return math.Round(self:GetHeavyAttackTime(true, base), 2) + end, + Unit = "unit.second", + LowerIsBetter = true, + }, + { + Name = "stat.meleethrowdamage", + Description = "stat.meleethrowdamage.desc", + Value = "MeleeDamage", + ValueCheck = "ThrowAttack", + AggregateFunction = function(self, base, val) + return math.floor(val * self:GetMeleePerkDamage(base) * self:GetConfigDamageMultiplier()) + end, + }, + { + Name = "stat.meleethrowvelocity", + Description = "stat.meleethrowvelocity.desc", + Value = "MeleeThrowForce", + ValueCheck = "ThrowAttack", + AggregateFunction = function(self, base, val) + return math.Round(0.3048 * self:GetMeleePerkVelocity(base) / 12, 1) + end, + Unit = "unit.mps", + }, + { + Name = "stat.meleethrowtime", + Description = "stat.meleethrowtime.desc", + Value = "MeleeAttackTime", + ValueCheck = "ThrowAttack", + AggregateFunction = function(self, base, val) + return math.Round(val * 3 * self:GetMeleePerkCooldown(base), 2) + end, + Unit = "unit.second", + LowerIsBetter = true, + }, + + { + Name = "stat.lifesteal", + Description = "stat.lifesteal.desc", + Value = "Lifesteal", + DefaultValue = 0, + AggregateFunction = function(self, base, val) + return math.Round(val * 100, 2) + end, + Unit = "%", + }, + { + Name = "stat.damagecharge", + Description = "stat.damagecharge.desc", + Value = "DamageCharge", + DefaultValue = 0, + AggregateFunction = function(self, base, val) + return math.Round(val * 10000, 2) + end, + Unit = "%", + }, +} diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_anim.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_anim.lua new file mode 100644 index 0000000..e5c2797 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_anim.lua @@ -0,0 +1,88 @@ +function SWEP:PlayAnimation(seq, mult, lock, doidle) + mult = mult or 1 + lock = lock or false + local anim = self:TranslateSequence(seq) + doidle = doidle or false + local reverse = false + + if mult < 0 then + reverse = true + mult = -mult + end + + local vm = self:GetVM() + + if !IsValid(vm) then return end + + if isstring(anim) then + seq = vm:LookupSequence(anim) + end + + if seq == -1 then return end + + self.CurrentAnimation = anim + self.CurrentSeqeunce = seq + + local time = vm:SequenceDuration(seq) + + time = time * mult + + vm:SendViewModelMatchingSequence(seq) + + if reverse then + vm:SetCycle(1) + vm:SetPlaybackRate(-1 / mult) + else + vm:SetCycle(0) + vm:SetPlaybackRate(1 / mult) + end + + if lock then + self:SetAnimLockTime(CurTime() + time) + -- self:SetNextSecondaryFire(CurTime() + time) + else + self:SetAnimLockTime(0) + -- self:SetNextSecondaryFire(0) + end + + if doidle and !self.NoIdle then + self:SetNextIdle(CurTime() + time) + else + self:SetNextIdle(math.huge) + end + + self:SetLastProceduralFireTime(0) + + return time +end + +function SWEP:IdleAtEndOfAnimation() + local vm = self:GetVM() + local cyc = vm:GetCycle() + local duration = vm:SequenceDuration() + local rate = vm:GetPlaybackRate() + + local time = (1 - cyc) * (duration / rate) + + self:SetNextIdle(CurTime() + time) +end + +function SWEP:Idle() + if self:GetPrimedGrenade() then return end + + if self:GetBlindFire() then + if self:Clip1() == 0 then + self:PlayAnimation("blind_dryfire", 0, false, false) + else + self:PlayAnimation("blind_idle") + end + else + if self:Clip1() == 0 then + self:PlayAnimation("dryfire", 0, false, false) + else + self:PlayAnimation("idle") + end + end + + self:SetReady(true) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_animtranslate.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_animtranslate.lua new file mode 100644 index 0000000..ed54769 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_animtranslate.lua @@ -0,0 +1,20 @@ +function SWEP:TranslateSequence(seq) + seq = self:RunHook("Hook_TranslateSequence", seq) + + seq = self.AnimationTranslationTable[seq] or seq + + if istable(seq) then + seq["BaseClass"] = nil + seq = seq[math.Round(util.SharedRandom("TacRP_animtr", 1, #seq))] + end + + return seq +end + +function SWEP:HasSequence(seq) + seq = self:TranslateSequence(seq) + local vm = self:GetVM() + seq = vm:LookupSequence(seq) + + return seq != -1 +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_attach.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_attach.lua new file mode 100644 index 0000000..75268dd --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_attach.lua @@ -0,0 +1,201 @@ +function SWEP:Attach(slot, att, silent, suppress) + local slottbl = self.Attachments[slot] + if slottbl.Installed then return end + + if !self:CanAttach(slot, att) then return end + + local atttbl = TacRP.GetAttTable(att) + + if !atttbl then return end + if TacRP:PlayerGetAtts(self:GetOwner(), att) <= 0 then return end + + local inf_old = self:GetInfiniteAmmo() + local ammo_old = self:GetAmmoType() + + slottbl.Installed = att + + TacRP:PlayerTakeAtt(self:GetOwner(), att, 1) + + if atttbl.OnAttach then + atttbl.OnAttach(self) + end + + if CLIENT then + local attid = atttbl.ID + + net.Start("TacRP_attach") + net.WriteEntity(self) + net.WriteBool(true) + net.WriteUInt(slot, 8) + net.WriteUInt(attid, TacRP.Attachments_Bits) + net.SendToServer() + + if game.SinglePlayer() then -- Due to bodygroups also being networked by engine, this will cause bodygroup "flickering" + self:SetupModel(true) + self:SetupModel(false) + end + + if !silent then + surface.PlaySound(slottbl.AttachSound or "") + end + elseif SERVER and !suppress then + self:NetworkWeapon() + TacRP:PlayerSendAttInv(self:GetOwner()) + end + + self:SetBurstCount(0) + + self:InvalidateCache() + + self:SetBaseSettings() + + if atttbl.CanToggle then + self:SetTactical(true) + end + + if self:GetFiremode() > self:GetFiremodeAmount() then + self:SetFiremode(1) + end + + local inf_new = self:GetInfiniteAmmo() + local ammo_new = self:GetAmmoType() + if SERVER then + if inf_old and !inf_new then + self:SetClip1(0) + elseif (inf_new and !inf_old) or (ammo_old != ammo_new) then + self:Unload(ammo_old) + end + end +end + +function SWEP:Detach(slot, silent, suppress) + local slottbl = self.Attachments[slot] + if !slottbl.Installed then return end + + if !self:CanDetach(slot, slottbl.Installed) then return end + + TacRP:PlayerGiveAtt(self:GetOwner(), slottbl.Installed, 1) + + local inf_old = self:GetInfiniteAmmo() + local ammo_old = self:GetAmmoType() + + local atttbl = TacRP.GetAttTable(slottbl.Installed) + + if atttbl and atttbl.OnDetach then + atttbl.OnDetach(self) + end + + slottbl.Installed = nil + + if CLIENT then + net.Start("TacRP_attach") + net.WriteEntity(self) + net.WriteBool(false) + net.WriteUInt(slot, 8) + net.SendToServer() + + if game.SinglePlayer() then -- Due to bodygroups also being networked by engine, this will cause bodygroup "flickering" + self:SetupModel(true) + self:SetupModel(false) + end + + if !silent then + surface.PlaySound(slottbl.DetachSound or "") + end + elseif SERVER and !suppress then + self:NetworkWeapon() + TacRP:PlayerSendAttInv(self:GetOwner()) + end + + self:SetBurstCount(0) + + self:InvalidateCache() + + self:SetBaseSettings() + + if self:GetFiremode() > self:GetFiremodeAmount() then + self:SetFiremode(1) + end + + local nade = self:GetGrenade() + if (nade.AdminOnly and self:GetOwner():GetAmmoCount(nade.Ammo) <= 0) or (nade.RequireStat and !self:GetValue(nade.RequireStat)) then + self:SelectGrenade() + end + + local inf_new = self:GetInfiniteAmmo() + local ammo_new = self:GetAmmoType() + if SERVER then + if inf_old and !inf_new then + self:SetClip1(0) + elseif (inf_new and !inf_old) or (ammo_old != ammo_new) then + self:Unload(ammo_old) + end + end +end + +function SWEP:ToggleCustomize(on) + if on == self:GetCustomize() or (on and self:GetValue("RunawayBurst") and self:GetBurstCount() > 0) then return end + + self:ScopeToggle(0) + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + + self:SetCustomize(on) + + self:SetShouldHoldType() +end + +function SWEP:CanAttach(slot, att) + local atttbl = TacRP.GetAttTable(att) + + local slottbl = self.Attachments[slot] + + if atttbl.Compatibility then + local result = atttbl.Compatibility(self) + + if result == true then + return true + elseif result == false then + return false + end + end + + local cat = slottbl.Category + + if !istable(cat) then + cat = {cat} + end + + local attcat = atttbl.Category + + if !istable(attcat) then + attcat = {attcat} + end + + if !TacRP.CanCustomize(self:GetOwner(), self, att, slot) then return false end + + for _, c in pairs(attcat) do + if table.HasValue(cat, c) then + return true + end + end + + return false +end + +function SWEP:CanDetach(slot, att) + local slottbl = self.Attachments[slot] + + if slottbl.Integral then return false end + + if !TacRP.CanCustomize(self:GetOwner(), self, att, slot, true) then return false end + + return true +end + +function SWEP:ToggleTactical() + local ret = self:RunHook("Hook_ToggleTactical") + if !ret then + self:EmitSound(self:GetValue("Sound_ToggleTactical")) + self:SetTactical(!self:GetTactical()) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_autoaim.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_autoaim.lua new file mode 100644 index 0000000..747e355 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_autoaim.lua @@ -0,0 +1,95 @@ +SWEP.LockOnEntity = NULL + +SWEP.LastSearchTime = 0 + +SWEP.PlayedLockOnSound = false + +function SWEP:ThinkLockOn() + local owner = self:GetOwner() + local lastlockonentity = self:GetLockOnEntity() + + if CLIENT then + if !IsValid(self:GetLockOnEntity()) then + self.PlayedLockOnSound = false + end + end + + if not ((self:GetSightAmount() >= 1 and self:GetValue("LockOnInSights")) or (self:GetSightAmount() < 1 and self:GetValue("LockOnOutOfSights"))) then + self:SetLockOnEntity(nil) + self:SetLockOnStartTime(CurTime()) + else + local lockontarget = nil + + if lastlockonentity and IsValid(lastlockonentity) then + // check if it remains within seeker cage + + local player_aim_vector = owner:GetAimVector() + local target_angle = math.deg(math.acos(player_aim_vector:Dot((lastlockonentity:WorldSpaceCenter() - owner:GetShootPos()):GetNormalized()))) + + local dist = (lastlockonentity:WorldSpaceCenter() - owner:GetShootPos()):Length() + + if target_angle < self:GetValue("LockOnTrackAngle") and dist < self:GetValue("LockOnRange") then + lockontarget = lastlockonentity + end + else + local tr = util.TraceLine( + { + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * self:GetValue("LockOnRange"), + ignoreworld = true, + filter = function(target) + if target == owner then return false end + if target:IsPlayer() then return true end + if (target:IsNPC() or target:IsNextBot()) then return true end + if (target.LVS and target:GetHP() > 0) or target.Targetable then return true end + if target.IsGlideVehicle then return true end + if TacRP.LockableEntities[target:GetClass()] then return true end + if TacRP.FlareEntities[target:GetClass()] or target.IsTacRPFlare then return true end + end + } + ) + + lockontarget = tr.Entity + end + + if lockontarget and IsValid(lockontarget) then + local occlusion_tr = util.TraceLine({ + start = owner:GetShootPos(), + endpos = lockontarget:WorldSpaceCenter(), + mask = MASK_SHOT, + filter = function(ent) + if ent == lockontarget or ent == owner or ent:GetOwner() == lockontarget then return false end + if ent:IsVehicle() and ent:GetDriver() == owner then return false end + if ent:GetClass() == "lvs_wheeldrive_wheel" or scripted_ents.IsBasedOn(ent:GetClass(), "tacrp_proj_base") then return false end + return true + end + }) + if occlusion_tr.Hit then lockontarget = nil end + end + + if lockontarget and IsValid(lockontarget) then + if lastlockonentity != lockontarget then + self:SetLockOnStartTime(CurTime()) + if CLIENT and (IsFirstTimePredicted() or game.SinglePlayer()) then + self:EmitSound(self:GetValue("Sound_StartLockOn")) + end + elseif not self.PlayedLockOnSound and CurTime() > self:GetLockOnStartTime() + self:GetValue("LockOnTime") then + if CLIENT and (IsFirstTimePredicted() or game.SinglePlayer()) then + self:EmitSound(self:GetValue("Sound_FinishLockOn")) + self.PlayedLockOnSound = true + end + end + self:SetLockOnEntity(lockontarget) + self.LockOnEntity = lockontarget + else + self:SetLockOnEntity(nil) + self.PlayedLockOnSound = false + self:SetLockOnStartTime(CurTime()) + end + end + + if not IsValid(self:GetLockOnEntity()) then + self:SetLockOnEntity(nil) + self:SetLockOnStartTime(CurTime()) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_bipod.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_bipod.lua new file mode 100644 index 0000000..bdc2cb8 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_bipod.lua @@ -0,0 +1,132 @@ +function SWEP:InBipod() + local bip = self:GetInBipod() + + -- if !self:CanBipod() then + -- self:ExitBipod() + -- end + + if IsValid(self:GetOwner()) and (self:GetOwner():GetVelocity():LengthSqr() >= 100 or self:GetSprintAmount() > 0) then + self:ExitBipod() + end + + return bip +end + +SWEP.CachedCanBipod = true +SWEP.CachedCanBipodTime = 0 +SWEP.LastBipodTime = 0 +local dist = 24 +function SWEP:CanBipod() + if !self:GetValue("Bipod") then return false end + + if self:GetOwner():InVehicle() then return false end + if self:GetSprintAmount() > 0 and !self:DoForceSightsBehavior() then return false end + + if self.CachedCanBipodTime >= CurTime() then return tobool(self.CachedCanBipod), self.CachedCanBipod end + + local pos = self:GetOwner():EyePos() + local angle = self:GetOwner():EyeAngles() + if math.abs(angle.p) <= 45 then + angle.p = 0 + end + if self:GetOwner():GetVelocity():Length() > 0 then + return false + end + + local rangemult = 2 + if self:IsProne() then + rangemult = rangemult * 1.25 + end + + local tr = util.TraceLine({ + start = pos, + endpos = pos + (angle:Forward() * dist * rangemult), + filter = self:GetOwner(), + mask = MASK_PLAYERSOLID + }) + + if tr.Hit then -- check for stuff in front of us + return false + end + + local maxs = Vector(10, 10, 16) + local mins = Vector(-10, -10, 0) + + angle.p = angle.p + 45 + + tr = util.TraceHull({ + start = pos, + endpos = pos + (angle:Forward() * dist * rangemult), + filter = self:GetOwner(), + maxs = maxs, + mins = mins, + mask = MASK_PLAYERSOLID + }) + + self.CachedCanBipodTime = CurTime() + + if tr.Hit then + local tr2 = util.TraceHull({ + start = tr.HitPos, + endpos = tr.HitPos + Vector(0, 0, -dist), + filter = self:GetOwner(), + maxs = maxs, + mins = mins, + mask = MASK_PLAYERSOLID + }) + if tr2.Hit then + self.CachedCanBipod = tr2 + return true, tr2 + end + end + + self.CachedCanBipod = false + return false +end + +function SWEP:EnterBipod(sp) + if !sp and self:GetInBipod() then return end + local can, tr = self:CanBipod() + if !sp and !can then return end + + if SERVER and game.SinglePlayer() then self:CallOnClient("EnterBipod", "true") end + self.LastBipodTime = CurTime() + + local owner = self:GetOwner() + + local bipodang = tr.HitNormal:Cross(owner:EyeAngles():Right()):Angle() + -- bipodang.p = math.ApproachAngle(bipodang.p, owner:EyeAngles().p, 10) + + debugoverlay.Axis(tr.HitPos, tr.HitNormal:Angle(), 16, 5, true) + debugoverlay.Line(tr.HitPos, tr.HitPos + bipodang:Forward() * 32, 5, color_white, true) + debugoverlay.Line(tr.HitPos, tr.HitPos + owner:EyeAngles():Forward() * 32, 5, Color(255, 255, 0), true) + + self:SetBipodPos(owner:EyePos() + (owner:EyeAngles():Forward() * 4) - Vector(0, 0, 3)) + self:SetBipodAngle(bipodang) + + if game.SinglePlayer() and CLIENT then return end + + self:EmitSound(self.Sound_BipodDown, 70, 100, 1, CHAN_ITEM) + self:SetInBipod(true) + self:DoBodygroups() +end + +function SWEP:ExitBipod(sp) + if !sp and !self:GetInBipod() then return end + + if SERVER and game.SinglePlayer() then self:CallOnClient("ExitBipod", "true") end + self.LastBipodTime = CurTime() + if game.SinglePlayer() and CLIENT then return end + + self:EmitSound(self.Sound_BipodUp, 70, 100, 1, CHAN_ITEM) + self:SetInBipod(false) + self:DoBodygroups() +end + +function SWEP:IsProne() + if PRONE_INPRONE then + return self:GetOwner().IsProne and self:GetOwner():IsProne() + else + return false + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_blindfire.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_blindfire.lua new file mode 100644 index 0000000..73cfe04 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_blindfire.lua @@ -0,0 +1,285 @@ +function SWEP:GetMuzzleOrigin() + if !IsValid(self:GetOwner()) then + return self:GetPos() + end + if self:GetOwner():IsNPC() then + return SERVER and self:GetOwner():GetShootPos() or self:GetOwner():EyePos() + end + + local pos = self:GetOwner():EyePos() + + if self:GetBlindFire() then + local eyeang = self:GetOwner():EyeAngles() + + local testpos = pos + eyeang:Up() * 24 + + if self:GetBlindFireLeft() or self:GetBlindFireRight() then + testpos = pos + eyeang:Forward() * 24 + end + + local tr = util.TraceLine({ + start = pos, + endpos = testpos, + filter = self:GetOwner() + }) + + pos = tr.HitPos + end + + local offset = self:GetValue("ShootOffset") + + if offset.x != 0 then + pos = pos + self:GetOwner():GetRight() * offset.x + end + + if offset.y != 0 then + pos = pos + self:GetOwner():GetForward() * offset.y + end + + if offset.z != 0 then + pos = pos + self:GetOwner():GetUp() * offset.z + end + + return pos +end + +/* + +ValveBiped.Bip01_Spine +ValveBiped.Bip01_Spine1 +ValveBiped.Bip01_Spine2 +ValveBiped.Bip01_Spine4 +ValveBiped.Bip01_Neck1 +ValveBiped.Bip01_Head1 +ValveBiped.forward +ValveBiped.Bip01_R_Clavicle +ValveBiped.Bip01_R_UpperArm +ValveBiped.Bip01_R_Forearm +ValveBiped.Bip01_R_Hand +ValveBiped.Anim_Attachment_RH +ValveBiped.Bip01_L_Clavicle +ValveBiped.Bip01_L_UpperArm +ValveBiped.Bip01_L_Forearm +ValveBiped.Bip01_L_Hand +ValveBiped.Anim_Attachment_LH +ValveBiped.Bip01_R_Thigh +ValveBiped.Bip01_R_Calf +ValveBiped.Bip01_R_Foot +ValveBiped.Bip01_R_Toe0 +ValveBiped.Bip01_L_Thigh +ValveBiped.Bip01_L_Calf +ValveBiped.Bip01_L_Foot +ValveBiped.Bip01_L_Toe0 +ValveBiped.Bip01_L_Finger4 +ValveBiped.Bip01_L_Finger41 +ValveBiped.Bip01_L_Finger42 +ValveBiped.Bip01_L_Finger3 +ValveBiped.Bip01_L_Finger31 +ValveBiped.Bip01_L_Finger32 +ValveBiped.Bip01_L_Finger2 +ValveBiped.Bip01_L_Finger21 +ValveBiped.Bip01_L_Finger22 +ValveBiped.Bip01_L_Finger1 +ValveBiped.Bip01_L_Finger11 +ValveBiped.Bip01_L_Finger12 +ValveBiped.Bip01_L_Finger0 +ValveBiped.Bip01_L_Finger01 +ValveBiped.Bip01_L_Finger02 +ValveBiped.Bip01_R_Finger4 +ValveBiped.Bip01_R_Finger41 +ValveBiped.Bip01_R_Finger42 +ValveBiped.Bip01_R_Finger3 +ValveBiped.Bip01_R_Finger31 +ValveBiped.Bip01_R_Finger32 +ValveBiped.Bip01_R_Finger2 +ValveBiped.Bip01_R_Finger21 +ValveBiped.Bip01_R_Finger22 +ValveBiped.Bip01_R_Finger1 +ValveBiped.Bip01_R_Finger11 +ValveBiped.Bip01_R_Finger12 +ValveBiped.Bip01_R_Finger0 +ValveBiped.Bip01_R_Finger01 +ValveBiped.Bip01_R_Finger02 + +*/ + +local bone_list = { + "ValveBiped.Bip01_R_UpperArm", + "ValveBiped.Bip01_R_Forearm", + "ValveBiped.Bip01_R_Hand", + "ValveBiped.Bip01_L_UpperArm", + "ValveBiped.Bip01_L_Forearm", + "ValveBiped.Bip01_L_Hand", +} + +local bone_mods = { + -- ["ValveBiped.Bip01_R_UpperArm"] = Angle(0, -70, 0), + -- ["ValveBiped.Bip01_R_Hand"] = Angle(-55, 45, -90), + ["ValveBiped.Bip01_R_UpperArm"] = Angle(45, -90, 0), + ["ValveBiped.Bip01_R_Hand"] = Angle(-90, 0, 0), +} + +local bone_mods_left = { + -- ["ValveBiped.Bip01_R_UpperArm"] = Angle(0, -70, 0), + -- ["ValveBiped.Bip01_R_Hand"] = Angle(-55, 45, -90), + ["ValveBiped.Bip01_R_UpperArm"] = Angle(45, 0, 0), + ["ValveBiped.Bip01_R_Forearm"] = Angle(0, 0, 0), + ["ValveBiped.Bip01_R_Hand"] = Angle(0, -75, 0), +} + +local bone_mods_right = { + ["ValveBiped.Bip01_R_UpperArm"] = Angle(-45, 0, 0), + ["ValveBiped.Bip01_R_Forearm"] = Angle(0, 0, 0), + ["ValveBiped.Bip01_R_Hand"] = Angle(35, 75, 0), +} + +local bone_mods_kys = { + ["ValveBiped.Bip01_R_UpperArm"] = Angle(5, 0, 0), + ["ValveBiped.Bip01_R_Forearm"] = Angle(0, -5, 0), + ["ValveBiped.Bip01_R_Hand"] = Angle(0, -165, 0), +} +local bone_mods_kys_pistol = { + ["ValveBiped.Bip01_R_UpperArm"] = Angle(55, 0, 0), + ["ValveBiped.Bip01_R_Forearm"] = Angle(0, -75, 5), + ["ValveBiped.Bip01_R_Hand"] = Angle(45, -75, 0), +} +local bone_mods_kys_dual = { + ["ValveBiped.Bip01_L_UpperArm"] = Angle(-60, 0, -45), + ["ValveBiped.Bip01_L_Forearm"] = Angle(0, -60, -30), + ["ValveBiped.Bip01_L_Hand"] = Angle(-30, -45, -90), + ["ValveBiped.Bip01_R_UpperArm"] = Angle(55, 0, 30), + ["ValveBiped.Bip01_R_Forearm"] = Angle(0, -60, 30), + ["ValveBiped.Bip01_R_Hand"] = Angle(45, -75, 90), +} + +local bone_mods_index = { + [TacRP.BLINDFIRE_UP] = bone_mods, + [TacRP.BLINDFIRE_LEFT] = bone_mods_left, + [TacRP.BLINDFIRE_RIGHT] = bone_mods_right, + [TacRP.BLINDFIRE_KYS] = bone_mods_kys, +} + +function SWEP:ToggleBoneMods(on) + if !IsValid(self:GetOwner()) then return end + if on == TacRP.BLINDFIRE_NONE or on == false or on == nil then + for _, i in ipairs(bone_list) do + local boneindex = self:GetOwner():LookupBone(i) + if !boneindex then continue end + + self:GetOwner():ManipulateBoneAngles(boneindex, Angle(0, 0, 0)) + -- self:GetOwner():ManipulateBonePosition(boneindex, Vector(0, 0, 0)) + end + else + local tbl = bone_mods_index[on] + if on == TacRP.BLINDFIRE_KYS and self:GetValue("HoldTypeSuicide") == "duel" then + tbl = bone_mods_kys_dual + elseif on == TacRP.BLINDFIRE_KYS and self:GetValue("HoldType") == "revolver" then + tbl = bone_mods_kys_pistol + end + + for i, k in pairs(tbl) do + local boneindex = self:GetOwner():LookupBone(i) + if !boneindex then continue end + + self:GetOwner():ManipulateBoneAngles(boneindex, k) + end + + -- for i, k in pairs(tbl[2]) do + -- local boneindex = self:GetOwner():LookupBone(i) + -- if !boneindex then continue end + + -- self:GetOwner():ManipulateBonePosition(boneindex, k) + -- end + end +end + +function SWEP:GetBlindFireMode() + if !self:GetBlindFire() then + return TacRP.BLINDFIRE_NONE + elseif self:GetBlindFireLeft() and self:GetBlindFireRight() then + return TacRP.BLINDFIRE_KYS + elseif self:GetBlindFireLeft() then + return TacRP.BLINDFIRE_LEFT + elseif self:GetBlindFireRight() then + return TacRP.BLINDFIRE_RIGHT + else + return TacRP.BLINDFIRE_UP + end +end + +local bfmode = { + [TacRP.BLINDFIRE_NONE] = {false, false, false}, + [TacRP.BLINDFIRE_UP] = {true, false, false}, + [TacRP.BLINDFIRE_LEFT] = {true, true, false}, + [TacRP.BLINDFIRE_RIGHT] = {true, false, true}, + [TacRP.BLINDFIRE_KYS] = {true, true, true}, +} +function SWEP:SetBlindFireMode(mode) + if !bfmode[mode] then + print("[TacRP] WARNING! Trying to set invalid blindfire mode: " .. tostring(mode)) + mode = 0 + end + self:SetBlindFire(bfmode[mode][1]) + self:SetBlindFireLeft(bfmode[mode][2]) + self:SetBlindFireRight(bfmode[mode][3]) +end + +function SWEP:CheckBlindFire(suicide) + if !self:GetValue("CanBlindFire") and (!suicide or !self:GetValue("CanSuicide")) then return false end + if (self:GetIsSprinting() + or self:GetAnimLockTime() > CurTime() + or self:GetPrimedGrenade() + or self:IsInScope() + or self:GetSafe()) then + return false + end + return true +end + +function SWEP:ToggleBlindFire(bf) + local kms = bf == TacRP.BLINDFIRE_KYS or bf == TacRP.BLINDFIRE_NONE + if bf != TacRP.BLINDFIRE_NONE and (!self:CheckBlindFire(kms) or bf == self:GetBlindFireMode()) then return end + + local diff = bf != self:GetBlindFireMode() + + if diff then + self:ToggleCustomize(false) + self:ScopeToggle(0) + self:SetBlindFireFinishTime(CurTime() + (bf == TacRP.BLINDFIRE_KYS and 1 or 0.3)) + end + + self:SetBlindFireMode(bf) + self:ToggleBoneMods(bf) + self:SetShouldHoldType() + + if bf == TacRP.BLINDFIRE_KYS and TacRP.ShouldWeFunny() then + self:GetOwner():EmitSound("tacrp/low-tier-god.mp3", 80, 100) + end + + if diff then + if self:StillWaiting(true) then + self:IdleAtEndOfAnimation() + else + self:Idle() + end + end +end + +function SWEP:ThinkBlindFire() + if (self:GetOwner():KeyDown(IN_ZOOM) or self:GetOwner().TacRPBlindFireDown) and !tobool(self:GetOwner():GetInfo("tacrp_blindfiremenu")) then + if CLIENT then self.LastHintLife = CurTime() end + if self:GetOwner():KeyDown(IN_FORWARD) then + self:ToggleBlindFire(TacRP.BLINDFIRE_UP) + elseif self:GetOwner():KeyDown(IN_MOVELEFT) and !self:GetOwner():KeyDown(IN_MOVERIGHT) then + self:ToggleBlindFire(TacRP.BLINDFIRE_LEFT) + elseif self:GetOwner():KeyDown(IN_MOVERIGHT) and !self:GetOwner():KeyDown(IN_MOVELEFT) then + self:ToggleBlindFire(TacRP.BLINDFIRE_RIGHT) + elseif self:GetOwner():KeyDown(IN_SPEED) and self:GetOwner():KeyDown(IN_WALK) and !tobool(self:GetOwner():GetInfo("tacrp_idunwannadie")) then + self:ToggleBlindFire(TacRP.BLINDFIRE_KYS) + elseif self:GetOwner():KeyDown(IN_BACK) then + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + end + elseif (self:GetOwner():KeyDown(IN_ZOOM) or self:GetOwner().TacRPBlindFireDown) and self:GetOwner():GetInfo("tacrp_blindfiremenu") and self:GetOwner():GetCanZoom() then + self:GetOwner():SetCanZoom(false) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_bodygroups.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_bodygroups.lua new file mode 100644 index 0000000..3a0d718 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_bodygroups.lua @@ -0,0 +1,113 @@ +function SWEP:DoBodygroups(wm, custom_wm) + if wm == nil then + wm = false + self:DoBodygroups(true) + end + if !wm and (!IsValid(self:GetOwner()) or self:GetOwner():IsNPC()) then return end + + local dbg = self:GetValue("DefaultBodygroups") + + local mdl + + if wm then + mdl = custom_wm or self + dbg = self:GetValue("DefaultWMBodygroups") + else + mdl = self:GetVM() + end + + if !IsValid(mdl) then return end + + mdl:SetBodyGroups(dbg or "") + + local sk = self:GetValue("DefaultSkin") + + local eles = self:GetElements() + + for i, k in ipairs(eles) do + if wm then + for _, j in pairs(k.BGs_WM or {}) do + if _ == "BaseClass" then continue end + mdl:SetBodygroup(j[1], j[2] or 0) + end + if k.Skin_WM != nil then sk = k.Skin_WM end + else + for _, j in pairs(k.BGs_VM or {}) do + if _ == "BaseClass" then continue end + mdl:SetBodygroup(j[1] or 0, j[2] or 0) + end + if k.Skin_VM != nil then sk = k.Skin_VM end + end + end + + mdl:SetSkin(sk) + + local bbg = self:GetValue("BulletBodygroups") + + if bbg then + local amt = self:Clip1() + + if self:GetReloading() then + amt = self:GetLoadedRounds() + end + + for c, bgs in pairs(bbg) do + if amt < c then + mdl:SetBodygroup(bgs[1], bgs[2]) + if !self.BulletBodygroupsSetAll then + break + end + end + end + end + + self:RunHook("Hook_PostDoBodygroups") +end + +function SWEP:GetElements(holster) + if !self.AttachmentElements then return {} end + local eles = {} + + for i, k in pairs(self.Attachments) do + if k.Installed then + table.Add(eles, k.InstalledElements or {}) + + local atttbl = TacRP.GetAttTable(k.Installed) + + table.Add(eles, atttbl.InstalledElements or {}) + else + table.Add(eles, k.UnInstalledElements or {}) + end + end + local eleatts = {} + + local foldstock = false + for i, k in pairs(eles) do + if self.AttachmentElements[k] then + table.insert(eleatts, self.AttachmentElements[k]) + foldstock = foldstock or k == "foldstock" + end + end + + -- Bipod bodygroup + if self:GetInBipod() and self.AttachmentElements["bipod"] then + table.insert(eleatts, self.AttachmentElements["bipod"]) + end + + -- Hack: Always fold stock when weapon is holstered + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() + and self:GetOwner():GetActiveWeapon() != self and !foldstock + and self.AttachmentElements["foldstock"] then + table.insert(eleatts, self.AttachmentElements["foldstock"]) + end + + table.sort(eleatts, function(a, b) + return (a.SortOrder or 1) < (b.SortOrder or 1) + end) + + return eleatts +end + +function SWEP:DoBulletBodygroups() + self:DoBodygroups(false) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_effects.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_effects.lua new file mode 100644 index 0000000..4d5a1ed --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_effects.lua @@ -0,0 +1,107 @@ +function SWEP:DoEffects(alt) + if !IsFirstTimePredicted() then return end + local muzz_qca, muzz_qca_wm = self:GetQCAMuzzle(alt) + + local data = EffectData() + data:SetEntity(self) + data:SetAttachment(muzz_qca) + data:SetHitBox(muzz_qca_wm or muzz_qca) // unused field (integer between 0-2047) + + util.Effect( "TacRP_muzzleeffect", data ) +end + +function SWEP:GetQCAMuzzle(alt) + if self:GetValue("EffectsAlternate") then + if self:GetNthShot() % 2 == (alt and 1 or 0) then + return self:GetValue("QCA_MuzzleR"), self:GetValue("WM_QCA_MuzzleR") + else + return self:GetValue("QCA_MuzzleL"), self:GetValue("WM_QCA_MuzzleL") + end + else + return self:GetValue("QCA_Muzzle"), self:GetValue("WM_QCA_Muzzle") + end +end + +function SWEP:GetQCAEject(alt) + if self:GetValue("EffectsAlternate") then + if self:GetNthShot() % 2 == (alt and 1 or 0) then + return self:GetValue("QCA_EjectR"), self:GetValue("WM_QCA_EjectR") + else + return self:GetValue("QCA_EjectL"), self:GetValue("WM_QCA_EjectL") + end + else + return self:GetValue("QCA_Eject"), self:GetValue("WM_QCA_Eject") + end +end + +SWEP.EjectedShells = {} + +function SWEP:DoEject(alt) + if !IsFirstTimePredicted() then return end + if self:GetValue("EjectEffect") == 0 then return end + + local eject_qca, eject_qca_wm = self:GetQCAEject(alt) + + local data = EffectData() + data:SetEntity(self) + data:SetFlags(self:GetValue("EjectEffect")) + data:SetAttachment(eject_qca) + data:SetHitBox(eject_qca_wm or eject_qca) // unused field (integer between 0-2047) + data:SetScale(self:GetValue("EjectScale")) + + util.Effect( "TacRP_shelleffect", data ) +end + +function SWEP:GetTracerOrigin() + local ow = self:GetOwner() + local wm = !IsValid(ow) or !ow:IsPlayer() or !ow:GetViewModel():IsValid() or (ow != LocalPlayer() and ow != LocalPlayer():GetObserverTarget()) or (ow == LocalPlayer() and ow:ShouldDrawLocalPlayer()) + local att = self:GetQCAMuzzle() + local muzz = self + + if !wm then + muzz = ow:GetViewModel() + end + + if muzz and muzz:IsValid() then + local posang = muzz:GetAttachment(att) + if !posang then return muzz:GetPos() end + local pos = posang.Pos + + return pos + end +end + +function SWEP:GetMuzzleDevice(wm) + if !wm and self:GetOwner():IsNPC() then return end + + local model = self.WModel + local muzz = self + + if !wm then + model = self.VModel + muzz = self:GetVM() + end + + if model then + for i, k in pairs(model) do + if k.IsMuzzleDevice then + return k + end + end + end + + return muzz +end + +function SWEP:DrawEjectedShells() + local newshells = {} + + for i, k in pairs(self.EjectedShells) do + if !k:IsValid() then continue end + + k:DrawModel() + table.insert(newshells, k) + end + + self.EjectedShells = newshells +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_firemodes.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_firemodes.lua new file mode 100644 index 0000000..29cc222 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_firemodes.lua @@ -0,0 +1,116 @@ +function SWEP:SwitchFiremode() + local fm = self:GetFiremode() + + fm = fm + 1 + + if fm > #self:GetValue("Firemodes") then + fm = 1 + end + + -- Since pattern is firemode dependent, it has to be reset when firemode switchs + self.RecoilPatternCache = {} + + self:SetFiremode(fm) +end + +function SWEP:GetFiremodeAmount() + if istable(self:GetValue("Firemodes")) then + return #self:GetValue("Firemodes") + elseif self:GetValue("Firemode") == 0 then + return 0 + else + return 1 + end +end + +function SWEP:GetNextFiremode() + if self:GetFiremodeAmount() == 0 then return 1 end + if self:GetValue("Firemodes") then + local fm = self:GetFiremode() + + fm = fm + 1 + + if fm > #self:GetValue("Firemodes") then + fm = 1 + end + + return self:GetValue("Firemodes")[fm] + else + return self:GetValue("Firemode") + end +end + +function SWEP:GetCurrentFiremode() + if self:GetValue("Firemodes") then + return self:GetValue("Firemodes")[self:GetFiremode()] or self:GetValue("Firemode") or 0 + else + return self:GetValue("Firemode") + end +end + +function SWEP:ToggleSafety(onoff) + if self:GetValue("Firemode") == 0 then return end + onoff = onoff or !self:GetSafe() + + if onoff and self:DoForceSightsBehavior() then return end + if DarkRP and self:GetNWBool("TacRP_PoliceBiocode") and !self:GetOwner():isCP() then onoff = true end + + self:SetSafe(onoff) + + if onoff == true then + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + self:ScopeToggle(0) + end + + if CLIENT then self.LastHintLife = CurTime() end +end + +local mat_1 = Material("tacrp/hud/firemode_1.png", "mips smooth") +local mat_2 = Material("tacrp/hud/firemode_2.png", "mips smooth") +local mat_3 = Material("tacrp/hud/firemode_3.png", "mips smooth") +local mat_4 = Material("tacrp/hud/firemode_4.png", "mips smooth") +local mat_a = Material("tacrp/hud/firemode_a.png", "mips smooth") +local mat_s = Material("tacrp/hud/firemode_s.png", "mips smooth") + +function SWEP:GetFiremodeMat(mode) + if mode == 0 then + return mat_s + elseif mode == 1 then + return mat_1 + elseif mode == 2 then + return mat_a + elseif mode == -2 then + return mat_2 + elseif mode == -3 then + return mat_3 + elseif mode == -4 then + return mat_4 + else + return mat_1 // epic fail + end +end + +function SWEP:HasFiremode(mode, base) + local valfunc = base and self.GetBaseValue or self.GetValue + + if valfunc(self, "Firemodes") then + for _, v in pairs(valfunc(self, "Firemodes")) do + if v == mode or (mode < 0 and v < 0) then return true end + end + return false + else + return valfunc(self, "Firemode") == mode or (mode < 0 and valfunc(self, "Firemode") < 0) + end +end + +function SWEP:GetFiremodeBurstLength(base) + local valfunc = base and self.GetBaseValue or self.GetValue + if valfunc(self, "Firemodes") then + for _, v in pairs(valfunc(self, "Firemodes")) do + if v < 0 then return -v end + end + return false + else + return valfunc(self, "Firemode") < 0 and -valfunc(self, "Firemode") or false + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_freeaim.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_freeaim.lua new file mode 100644 index 0000000..7988442 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_freeaim.lua @@ -0,0 +1,80 @@ +SWEP.ClientFreeAimAng = Angle(0, 0, 0) +SWEP.ClientLastAimAngle = Angle(0, 0, 0) + +function SWEP:ThinkFreeAim() + if !TacRP.ConVars["freeaim"]:GetBool() or self:GetOwner():IsBot() then return Angle(0, 0, 0) end + + local eyeangles = self:GetOwner():EyeAngles() + + if self:GetValue("FreeAim") then + local diff = eyeangles + if SERVER then + diff = diff - self:GetLastAimAngle() + else + diff = diff - self.ClientLastAimAngle + end + diff = LerpAngle(0.9, diff, angle_zero) + + local freeaimang = Angle(self:GetFreeAimAngle()) + + local max = self:GetValue("FreeAimMaxAngle") + + local sightdelta = self:Curve(self:GetSightDelta()) + + max = max * Lerp(sightdelta, 1, 0) + + if self:GetBlindFireMode() > 0 then + max = max * 0.25 + end + + if self:GetValue("Bipod") then + local f = self:Curve(math.Clamp((CurTime() - self.LastBipodTime) / 0.25, 0, 1)) + if self:GetInBipod() then + max = Lerp(f, max, 0) + else + max = Lerp(f, 0, max) + end + end + + diff.p = math.NormalizeAngle(diff.p) + diff.y = math.NormalizeAngle(diff.y) + + diff = diff * Lerp(sightdelta, 1, 0.25) + + freeaimang.p = math.Clamp(math.NormalizeAngle(freeaimang.p) + math.NormalizeAngle(diff.p), -max, max) + freeaimang.y = math.Clamp(math.NormalizeAngle(freeaimang.y) + math.NormalizeAngle(diff.y), -max, max) + + local ang2d = math.atan2(freeaimang.p, freeaimang.y) + local mag2d = math.sqrt(math.pow(freeaimang.p, 2) + math.pow(freeaimang.y, 2)) + + mag2d = math.min(mag2d, max) + + freeaimang.p = mag2d * math.sin(ang2d) + freeaimang.y = mag2d * math.cos(ang2d) + + if CLIENT then + self.ClientFreeAimAng = freeaimang + else + self:SetFreeAimAngle(freeaimang) + end + end + + if SERVER then + self:SetLastAimAngle(eyeangles) + else + self.ClientLastAimAngle = eyeangles + end +end + +function SWEP:GetFreeAimOffset() + if !TacRP.ConVars["freeaim"]:GetBool() or !self:GetValue("FreeAim") or self:GetOwner():IsBot() then return Angle(0, 0, 0) end + if CLIENT and LocalPlayer() == self:GetOwner() then + return self.ClientFreeAimAng + elseif CLIENT and LocalPlayer() != self:GetOwner() then + local ang = self:GetFreeAimAngle() + ang:Normalize() -- Angles are networked as unsigned or something, so normalization converts it to what we expect + return ang + else + return self:GetFreeAimAngle() + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_init.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_init.lua new file mode 100644 index 0000000..8ab4ffc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_init.lua @@ -0,0 +1,382 @@ +function SWEP:DoDeployAnimation() + if self:GetReloading() and self:GetValue("MidReload") and !self:GetValue("ShotgunReload") and self:HasSequence("midreload") then + local t = self:PlayAnimation("midreload", self:GetValue("ReloadTimeMult"), true, true) + + self:SetReloadFinishTime(CurTime() + t) + else + self:SetReloading(false) + if self:GetValue("TryUnholster") then + self:PlayAnimation("unholster", self:GetValue("DeployTimeMult") * self:GetValue("UnholsterTimeMult"), true, true) + else + -- if self:GetReady() then + -- self:PlayAnimation("unholster", self:GetValue("DeployTimeMult"), true, true) + -- else + -- self:PlayAnimation("deploy", self:GetValue("DeployTimeMult"), true, true) + -- end + self:PlayAnimation("deploy", self:GetValue("DeployTimeMult"), true, true) + end + end +end + +function SWEP:Deploy() + if self:GetOwner():IsNPC() or self:GetOwner():IsNextBot() then + if SERVER then + self:NetworkWeapon() + end + if CLIENT then + self:SetupModel(true) + end + return + elseif SERVER and self:GetOwner():IsPlayer() then + self:GetOwner():SetSaveValue("m_flNextAttack", 0) + end + + self:SetBaseSettings() + + -- self:SetNextPrimaryFire(0) + self:SetNextSecondaryFire(0) + self:SetAnimLockTime(0) + self:SetSprintLockTime(0) + self:SetLastMeleeTime(0) + self:SetRecoilAmount(0) + self:SetLastScopeTime(0) + self:SetHolsterTime(0) + self:SetPrimedGrenade(false) + self:SetBlindFireFinishTime(0) + -- self:SetJammed(false) + self:SetCharge(false) + + self:SetBurstCount(0) + self:SetScopeLevel(0) + self:SetLoadedRounds(self:Clip1()) + self:SetCustomize(false) + + if self:GetOwner():IsPlayer() and IsFirstTimePredicted() then + self.InversePeek = self:GetOwner():GetInfoNum("tacrp_inversepeek", 0) == 1 + if !self.InversePeekInitialized then + self:SetPeeking(false) + end + self.InversePeekInitialized = true + end + + self.PreviousZoom = self:GetOwner():GetCanZoom() + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + self:GetOwner():SetCanZoom(false) + end + + self:DoDeployAnimation() + + self:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE) + + if CLIENT then + self:SetupModel(true) + self:SetupModel(false) + self.LastHintLife = CurTime() + elseif !game.SinglePlayer() then + self:DoBodygroups(true) -- Not sure why this is necessary + self:DoBodygroups(false) + end + + if (game.SinglePlayer() or CLIENT) and !TacRP.NewsPopup and TacRP.ConVars["checknews"]:GetBool() then + TacRP.NewsPopup = true + RunConsoleCommand("tacrp_news_check") + end + + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + + if TacRP.ConVars["deploysafety"]:GetBool() then + self:ToggleSafety(true) + end + + self:SetShouldHoldType() + + if self:IsQuickNadeAllowed() then + if self:GetValue("PrimaryGrenade") then + local nade = TacRP.QuickNades[self:GetValue("PrimaryGrenade")] + if !TacRP.IsGrenadeInfiniteAmmo(nade) and self:GetOwner():GetAmmoCount(nade.Ammo) == 0 then + if SERVER then self:Remove() end + return true + end + elseif !self:CheckGrenade() then + self:SelectGrenade() + return + end + end + + return true +end + +local v0 = Vector(0, 0, 0) +local v1 = Vector(1, 1, 1) +local a0 = Angle(0, 0, 0) + +function SWEP:ClientHolster() + if game.SinglePlayer() then + self:CallOnClient("ClientHolster") + end + + local vm = self:GetVM() + if IsValid(vm) then + vm:SetSubMaterial() + vm:SetMaterial() + + for i = 0, vm:GetBoneCount() do + vm:ManipulateBoneScale(i, v1) + vm:ManipulateBoneAngles(i, a0) + vm:ManipulateBonePosition(i, v0) + end + end +end + +function SWEP:Holster(wep) + if game.SinglePlayer() and CLIENT then return end + + if CLIENT and self:GetOwner() != LocalPlayer() then return end + + if self:GetOwner():IsNPC() then + return + end + + self:SetCustomize(false) + + if self:GetReloading() then + if self:GetValue("ShotgunReload") then + self:SetEndReload(false) + self:SetReloading(false) + self:KillTimer("ShotgunRestoreClip") + else + self:CancelReload(false) + end + end + + + if self:GetHolsterTime() > CurTime() then return false end -- or self:GetPrimedGrenade() + + if !TacRP.ConVars["holster"]:GetBool() or (self:GetHolsterTime() != 0 and self:GetHolsterTime() <= CurTime()) or !IsValid(wep) then + -- Do the final holster request + -- Picking up props try to switch to NULL, by the way + self:SetHolsterTime(0) + self:SetHolsterEntity(NULL) + self:SetReloadFinishTime(0) + + local holster = self:GetValue("HolsterVisible") + if SERVER and holster then + net.Start("TacRP_updateholster") + net.WriteEntity(self:GetOwner()) + net.WriteEntity(self) + net.Broadcast() + end + + if game.SinglePlayer() then + self:CallOnClient("KillModel") + else + if CLIENT then + self:RemoveCustomizeHUD() + self:KillModel() + end + end + + if self.PreviousZoom then + self:GetOwner():SetCanZoom(true) + end + + self:ClientHolster() + + return true + else + local reverse = 1 + local anim = "holster" + + if self:GetValue("NoHolsterAnimation") then + anim = "deploy" + reverse = -1 + end + + local animation = self:PlayAnimation(anim, self:GetValue("HolsterTimeMult") * reverse, true, true) + self:SetHolsterTime(CurTime() + (animation or 0)) + self:SetHolsterEntity(wep) + + self:SetScopeLevel(0) + self:KillTimers() + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + self:GetOwner():SetFOV(0, 0.1) + self:SetLastProceduralFireTime(0) + + self:GetOwner():DoAnimationEvent(ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM) + self:SetShouldHoldType() + + end +end + +local holsteranticrash = false + +hook.Add("StartCommand", "TacRP_Holster", function(ply, ucmd) + local wep = ply:GetActiveWeapon() + + if IsValid(wep) and wep.ArcticTacRP and wep:GetHolsterTime() != 0 and wep:GetHolsterTime() - wep:GetPingOffsetScale() <= CurTime() and IsValid(wep:GetHolsterEntity()) then + wep:SetHolsterTime(-math.huge) -- Pretty much force it to work + if !holsteranticrash then + holsteranticrash = true + ucmd:SelectWeapon(wep:GetHolsterEntity()) -- Call the final holster request + holsteranticrash = false + end + end +end) + +function SWEP:Initialize() + self:SetShouldHoldType() + + self:SetBaseSettings() + + self:SetLastMeleeTime(0) + self:SetNthShot(0) + + if self:GetOwner():IsNPC() then + self:NPC_Initialize() + return + end + + if self:GetOwner():IsNextBot() then + return + end + + self.m_WeaponDeploySpeed = 4 + + if engine.ActiveGamemode() == "terrortown" then + self:TTT_Init() + end + + self:ClientInitialize() + + if SERVER and engine.ActiveGamemode() != "terrortown" then + -- If we have any pre-existing attachments, network it + local empty = true + for slot, slottbl in pairs(self.Attachments) do + if slottbl.Installed then empty = false break end + end + if !empty then + self:NetworkWeapon() + end + end +end + +function SWEP:ClientInitialize() + if SERVER then return end + + if game.SinglePlayer() and SERVER and IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + self:CallOnClient("ClientInitialize") + end + + if !LocalPlayer().TacRPGreet and !TacRP.ConVars["shutup"]:GetBool() then + LocalPlayer().TacRPGreet = true + end + + -- local mat = Material("entities/" .. self:GetClass() .. ".png") + + -- local tex = mat:GetTexture("$basetexture") + + -- killicon.Add(self:GetClass(), tex:GetName(), Color( 255, 255, 255, 255 ) ) +end + +function SWEP:SetBaseSettings() + if game.SinglePlayer() and SERVER and IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + self:CallOnClient("SetBaseSettings") + end + + local fm = self:GetCurrentFiremode() + if fm != 1 then + if self:GetValue("RunawayBurst") and fm < 0 and !self:GetValue("AutoBurst") then + self.Primary.Automatic = false + else + self.Primary.Automatic = true + end + else + self.Primary.Automatic = false + end + + if self.PrimaryGrenade then + self.Primary.ClipSize = -1 + self.Primary.Ammo = TacRP.QuickNades[self.PrimaryGrenade].Ammo or "" + self.Primary.DefaultClip = 1 + else + self.Primary.ClipSize = self:GetCapacity() + self.Primary.Ammo = self:GetAmmoType() + self.Primary.DefaultClip = math.ceil(self.Primary.ClipSize * TacRP.ConVars["defaultammo"]:GetFloat()) + end + + if self:IsQuickNadeAllowed() then + self.Secondary.Ammo = self:GetGrenade().Ammo or "grenade" + else + self.Secondary.Ammo = "none" + end + + if SERVER and IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() and self:GetCapacity() > 0 and self:Clip1() > self:GetCapacity() then + self:GetOwner():GiveAmmo(self:Clip1() - self:GetCapacity(), self:GetAmmoType()) + self:SetClip1(self:GetCapacity()) + end +end + +function SWEP:SetShouldHoldType() + if self:GetOwner():IsNPC() and !self:GetOwner():IsNextBot() then + self:SetHoldType(self:GetValue("HoldTypeNPC") or self:GetValue("HoldType")) + return + end + + if self:GetHolsterTime() > CurTime() then + self:SetHoldType("passive") + return + end + + if (self:GetIsSprinting() or self:ShouldLowerWeapon() or self:GetSafe()) and self:GetValue("HoldTypeSprint") then + self:SetHoldType(self:GetValue("HoldTypeSprint")) + return + end + + if self:GetBlindFire() then + if self:GetBlindFireMode() == TacRP.BLINDFIRE_KYS and self:GetValue("HoldTypeSuicide") then + self:SetHoldType(self:GetValue("HoldTypeSuicide")) + return + elseif self:GetValue("HoldTypeBlindFire") then + self:SetHoldType(self:GetValue("HoldTypeBlindFire")) + return + end + elseif self:GetScopeLevel() > 0 and self:GetValue("HoldTypeSighted") then + self:SetHoldType(self:GetValue("HoldTypeSighted")) + return + elseif self:GetScopeLevel() > 0 and TacRP.HoldTypeSightedLookup[self:GetValue("HoldType")] then + self:SetHoldType(TacRP.HoldTypeSightedLookup[self:GetValue("HoldType")]) + return + end + + if self:GetCustomize() and self:GetValue("HoldTypeCustomize") then + self:SetHoldType(self:GetValue("HoldTypeCustomize")) + return + end + + self:SetHoldType(self:GetValue("HoldType")) +end + +function SWEP:OnRemove() + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + self:ToggleBoneMods(TacRP.BLINDFIRE_NONE) + + if CLIENT then + local vm = self:GetOwner():GetViewModel() + if IsValid(vm) then + vm:SetMaterial() -- Quick and dirty fix for grenade materials persisting on VM when stripped + end + end + end + if CLIENT and (self:GetCustomize() or (self.GrenadeMenuAlpha or 0) > 0 or (self.BlindFireMenuAlpha or 0) > 0) then + gui.EnableScreenClicker(false) + TacRP.CursorEnabled = false + end +end + +function SWEP:EquipAmmo(ply) + local ammotype = self.Primary.Ammo + if ammotype == "" then return end + + local supplyamount = self.GaveDefaultAmmo and self:Clip1() or math.ceil(math.max(1, self.Primary.ClipSize) * TacRP.ConVars["defaultammo"]:GetFloat()) + ply:GiveAmmo(supplyamount, ammotype) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_melee.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_melee.lua new file mode 100644 index 0000000..08b23ff --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_melee.lua @@ -0,0 +1,242 @@ +SWEP.Sound_MeleeHit = nil +SWEP.Sound_MeleeHitBody = nil + +-- Don't you love lua table inheritance?? +SWEP._Sound_MeleeHit = { + "TacRP/weapons/melee_hit-1.wav", + "TacRP/weapons/melee_hit-2.wav" +} +SWEP._Sound_MeleeHitBody = { + "TacRP/weapons/melee_body_hit-1.wav", + "TacRP/weapons/melee_body_hit-2.wav", + "TacRP/weapons/melee_body_hit-3.wav", + "TacRP/weapons/melee_body_hit-4.wav", + "TacRP/weapons/melee_body_hit-5.wav", +} + +function SWEP:Melee(alt) + if !self:GetValue("CanMeleeAttack") then return end + if self:StillWaiting(false, true) then return end + if DarkRP and self:GetNWBool("TacRP_PoliceBiocode") and !self:GetOwner():isCP() then return end + -- if self:SprintLock() then return end + + self.Primary.Automatic = true + self.Secondary.Automatic = true + + self:SetCharge(false) + + self:CancelReload() + + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + self:ScopeToggle(0) + + + local dmg = self:GetValue("MeleeDamage") * self:GetConfigDamageMultiplier() + local range = self:GetValue("MeleeRange") + local delay = self:GetValue("MeleeDelay") + if alt then + self:PlayAnimation("melee2", 1, false, true) + -- self:GetOwner():DoAnimationEvent(self:GetValue("GestureBash2") or self:GetValue("GestureBash")) + -- range = self:GetValue("Melee2Range") or range + dmg = self:GetHeavyAttackDamage() * self:GetConfigDamageMultiplier() + else + self:PlayAnimation("melee", 1, false, true) + -- self:GetOwner():DoAnimationEvent(self:GetValue("GestureBash")) + end + + local t = alt and self:GetHeavyAttackTime() or self:GetValue("MeleeAttackTime") + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_ATTACK_PRIMARY, t * 1000 * (alt and -1 or 1)) + + if delay > 0 then + self:EmitSound(self:ChooseSound(self:GetValue("Sound_MeleeSwing")), 75, 100, 1) + end + + self:SetTimer(delay, function() + self:GetOwner():LagCompensation(true) + + local filter = {self:GetOwner()} + + table.Add(filter, self.Shields) + + local start = self:GetOwner():GetShootPos() + local dir = self:GetOwner():GetAimVector() + local tr = util.TraceLine({ + start = start, + endpos = start + dir * range, + filter = filter, + mask = MASK_SHOT_HULL, + }) + + -- weapon_hl2mpbasebasebludgeon.cpp: do a hull trace if not hit + if tr.Fraction == 1 or !IsValid(tr.Entity) then + local dim = 32 + local pos2 = tr.HitPos - dir * (dim * 1.732) + local tr2 = util.TraceHull({ + start = start, + endpos = pos2, + filter = filter, + mask = MASK_SHOT_HULL, + mins = Vector(-dim, -dim, -dim), + maxs = Vector(dim, dim, dim) + }) + + if tr2.Fraction < 1 and IsValid(tr2.Entity) then + local dot = (tr2.Entity:GetPos() - start):GetNormalized():Dot(dir) + if dot >= 0.5 then + tr = tr2 + end + end + end + + local dmginfo = DamageInfo() + dmginfo:SetDamage(dmg) + dmginfo:SetDamageForce(dir * dmg * 500) + dmginfo:SetDamagePosition(tr.HitPos) + dmginfo:SetDamageType(self:GetValue("MeleeDamageType")) + if dmginfo:GetDamageType() == DMG_GENERIC and engine.ActiveGamemode() == "terrortown" then + dmginfo:SetDamageType(DMG_CLUB) -- use CLUB so TTT can assign DNA (it does not leave DNA on generic damage) + end + + dmginfo:SetAttacker(self:GetOwner()) + dmginfo:SetInflictor(self) + + if tr.Fraction < 1 then + + TacRP.CancelBodyDamage(tr.Entity, dmginfo, tr.HitGroup) + + if IsValid(tr.Entity) and (tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:IsNextBot() or tr.Entity:IsRagdoll()) then + self:EmitSound(self:ChooseSound(self:GetValue("Sound_MeleeHitBody") or self:GetValue("_Sound_MeleeHitBody")), 75, 100, 1, CHAN_ITEM) + + if !tr.Entity:IsRagdoll() and self:GetValue("MeleeBackstab") then + local ang = math.NormalizeAngle(self:GetOwner():GetAngles().y - tr.Entity:GetAngles().y) + if ang <= 60 and ang >= -60 then + dmginfo:ScaleDamage(self:GetValue("MeleeBackstabMult")) + self:EmitSound("tacrp/riki_backstab.wav", 70, 100, 0.4) + end + end + else + self:EmitSound(self:ChooseSound(self:GetValue("Sound_MeleeHit") or self:GetValue("_Sound_MeleeHit")), 75, 100, 1, CHAN_ITEM) + end + + if IsValid(tr.Entity) and self:GetValue("MeleeSlow") then + tr.Entity.TacRPBashSlow = true + end + + if tr.HitGroup == HITGROUP_HEAD then + dmginfo:ScaleDamage(1.25) + end + + if IsValid(tr.Entity) then + if tr.Entity.LVS then return end + --tr.Entity:TakeDamageInfo(dmginfo) + tr.Entity:DispatchTraceAttack(dmginfo, tr) + end + + self:FireBullets({ + Attacker = self:GetOwner(), + Damage = 0, + Force = 0, + Distance = range + 8, + HullSize = 0, + Tracer = 0, + Dir = (tr.HitPos - start):GetNormalized(), + Src = start, + }) + else + local tmiss + if !alt and self:GetValue("MeleeAttackMissTime") then + tmiss = self:GetValue("MeleeAttackMissTime") + elseif alt then + tmiss = self:GetHeavyAttackTime(true, false) + end + if tmiss then + self:SetNextSecondaryFire(CurTime() + (tmiss - delay)) + end + if delay == 0 then + self:EmitSound(self:ChooseSound(self:GetValue("Sound_MeleeSwing")), 75, 100, 1) + end + end + + self:GetOwner():LagCompensation(false) + end, "Melee") + + self:SetLastMeleeTime(CurTime()) + self:SetNextSecondaryFire(CurTime() + t) +end + +function SWEP:GetHeavyAttackDamage(base) + local valfunc = base and self.GetBaseValue or self.GetValue + return valfunc(self, "Melee2Damage") or valfunc(self, "MeleeDamage") * self:GetMeleePerkDamage(base) * 1.5 +end + +function SWEP:GetHeavyAttackTime(miss, base) + local valfunc = base and self.GetBaseValue or self.GetValue + if miss then + return (valfunc(self, "Melee2AttackMissTime") or (valfunc(self, "MeleeAttackMissTime") * 1.6)) + * self:GetMeleePerkCooldown(base) + else + return (valfunc(self, "Melee2AttackTime") or (valfunc(self, "MeleeAttackTime") * 1.6)) + * self:GetMeleePerkCooldown(base) + end +end + +function SWEP:GetMeleePerkDamage(base) + local valfunc = base and self.GetBaseValue or self.GetValue + local stat = valfunc(self, "MeleePerkStr") + if stat >= 0.5 then + return Lerp((stat - 0.5) * 2, 1, 2) + else + return Lerp(stat * 2, 0.7, 1) + end +end + +function SWEP:GetMeleePerkCooldown(base) + local valfunc = base and self.GetBaseValue or self.GetValue + local stat = valfunc(self, "MeleePerkAgi") + if stat >= 0.5 then + return Lerp((stat - 0.5) * 2, 1, 0.7) + else + return Lerp(stat * 2, 1.3, 1) + end +end + +function SWEP:GetMeleePerkSpeed(base) + local valfunc = base and self.GetBaseValue or self.GetValue + local stat = valfunc(self, "MeleePerkAgi") + if stat >= 0.5 then + return Lerp((stat - 0.5) * 2, 1, 1.5) + else + return Lerp(stat * 2, 0.5, 1) + end +end + +function SWEP:GetMeleePerkVelocity(base) + local valfunc = base and self.GetBaseValue or self.GetValue + local stat = valfunc(self, "MeleePerkInt") + if stat >= 0.5 then + return Lerp((stat - 0.5) * 2, 1, 3) * valfunc(self, "MeleeThrowForce") + else + return Lerp(stat * 2, 0.5, 1) * valfunc(self, "MeleeThrowForce") + end +end + +hook.Add("PostEntityTakeDamage", "tacrp_melee", function(ent, dmg, took) + if ent.TacRPBashSlow then + if took and (!ent:IsPlayer() or (ent:IsPlayer() and !(IsValid(ent:GetActiveWeapon()) and ent:GetActiveWeapon().ArcticTacRP and ent:GetActiveWeapon():GetValue("StunResist")))) then + ent:SetNWFloat("TacRPLastBashed", CurTime()) + end + ent.TacRPBashSlow = false + end + + local wep = dmg:GetInflictor() + if (!IsValid(wep) or !wep:IsWeapon()) and IsValid(dmg:GetAttacker()) and dmg:GetAttacker():IsPlayer() then wep = dmg:GetAttacker():GetActiveWeapon() end + if took and (ent:IsPlayer() or ent:IsNPC() or ent:IsNextBot()) and IsValid(wep) and wep.ArcticTacRP then + if (wep:GetValue("Lifesteal") or 0) > 0 then + wep:GetOwner():SetHealth(math.min(math.max(wep:GetOwner():GetMaxHealth(), wep:GetOwner():Health()), + wep:GetOwner():Health() + dmg:GetDamage() * wep:GetValue("Lifesteal"))) + end + if (wep:GetValue("DamageCharge") or 0) > 0 then + wep:SetBreath(math.min(1, wep:GetBreath() + dmg:GetDamage() * wep:GetValue("DamageCharge"))) + end + end +end) diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_npc.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_npc.lua new file mode 100644 index 0000000..02b1e6f --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_npc.lua @@ -0,0 +1,187 @@ +function SWEP:NPC_PrimaryAttack() + if !IsValid(self:GetOwner()) then return end + + local enemy = self:GetOwner():GetEnemy() + + local aps = self:GetValue("AmmoPerShot") + + if self:Clip1() < aps then + if !IsValid(enemy) or !IsValid(enemy:GetActiveWeapon()) or table.HasValue({"weapon_crowbar", "weapon_stunstick"}, enemy:GetActiveWeapon():GetClass()) then + // do not attempt to find cover if enemy does not have a ranged weapon + self:GetOwner():SetSchedule(SCHED_RELOAD) + else + self:GetOwner():SetSchedule(SCHED_HIDE_AND_RELOAD) + end + return + end + + self:SetBaseSettings() + self:SetShouldHoldType() + + self.Primary.Automatic = true + + local pvar = self:GetValue("ShootPitchVariance") + + local sshoot = self:GetValue("Sound_Shoot") + + if self:GetValue("Silencer") then + sshoot = self:GetValue("Sound_Shoot_Silenced") + end + + if istable(sshoot) then + sshoot = table.Random(sshoot) + end + + self:EmitSound(sshoot, self:GetValue("Vol_Shoot"), self:GetValue("Pitch_Shoot") + math.Rand(-pvar, pvar), 1, CHAN_WEAPON) + + self:SetClip1(self:Clip1() - aps) + + local delay = 60 / self:GetValue("RPM") + self:SetNextPrimaryFire(CurTime() + delay) + if delay < 0.1 then + self:GetOwner():NextThink(CurTime() + delay) // they will only attempt to fire once per think + end + + local spread = self:GetNPCSpread() + + local dir = self:GetOwner():GetAimVector() + + if self:GetValue("ShootEnt") then + if IsValid(enemy) then + dir = (enemy:WorldSpaceCenter() - self:GetOwner():GetShootPos()):GetNormalized():Angle() + dir = dir + ((spread + (0.1 / self:GetOwner():GetCurrentWeaponProficiency())) * AngleRand() / 3.6) + end + self:ShootRocket(dir) + else + self:GetOwner():FireBullets({ + Damage = self:GetValue("Damage_Max"), + Force = 8, + TracerName = "tacrp_tracer", + Tracer = self:GetValue("TracerNum"), + Num = self:GetValue("Num"), + Dir = dir, + Src = self:GetOwner():GetShootPos(), + Spread = Vector(spread, spread, spread), + Callback = function(att, btr, dmg) + local range = (btr.HitPos - btr.StartPos):Length() + + self:AfterShotFunction(btr, dmg, range, 0, {}) // self:GetValue("Penetration") + + if TacRP.Developer() then + if SERVER then + debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 0, 0), false) + else + debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 255, 255), false) + end + end + end + }) + end + + self:DoEffects() + self:DoEject() +end + +function SWEP:GetNPCBulletSpread(prof) + local mode = self:GetCurrentFiremode() + + if mode < 0 then + return 10 / (prof + 1) + elseif mode == 1 then + if math.Rand(0, 100) < (prof + 5) * 5 then + return 2 / (prof + 1) + else + return 20 / (prof + 1) + end + elseif mode > 1 then + if math.Rand(0, 100) < (prof + 5) * 2 then + return 5 / (prof + 1) + else + return 30 / (prof + 1) + end + + end + + return 15 +end + +function SWEP:GetNPCSpread() + return self:GetValue("Spread") +end + +function SWEP:GetNPCBurstSettings() + local mode = self:GetCurrentFiremode() + + local delay = 60 / self:GetValue("RPM") + + if !mode then return 1, 1, delay end + + if mode < 0 then + return -mode, -mode, delay + elseif mode == 0 then + return 0, 0, delay + elseif mode == 1 then + local c = self:GetCapacity() + return math.ceil(c * 0.075), math.max(1, math.floor(c * math.Rand(0.15, 0.3))), delay + math.Rand(0.1, 0.2) + elseif mode >= 2 then + return math.min(self:Clip1(), 1 + math.floor(0.5 / delay)), math.min(self:Clip1(), 1 + math.floor(2 / delay)), delay + end +end + +function SWEP:GetNPCRestTimes() + local mode = self:GetCurrentFiremode() + local postburst = self:GetValue("PostBurstDelay") or 0 + local m = self:GetValue("RecoilKick") + local delay = 60 / self:GetValue("RPM") + + if !mode then return delay + 0.3, delay + 0.6 end + + local o = m > 1 and math.sqrt(m) or m + if delay <= 60 / 90 then + return delay + 0.1 * o, delay + 0.2 * o + elseif mode < 0 then + o = delay + o * 0.5 + postburst + end + + return delay + 0.4 * o, delay + 0.6 * o +end + +function SWEP:CanBePickedUpByNPCs() + return self.NPCUsable +end + +function SWEP:NPC_Reload() + self:DropMagazine() +end + +function SWEP:NPC_Initialize() + if CLIENT then return end + // auto attachment + + if TacRP.ConVars["npc_atts"]:GetBool() then + for i, slot in pairs(self.Attachments) do + local atts = TacRP.GetAttsForCats(slot.Category or "", self) + + local ind = math.random(0, #atts) + + if ind > 0 and math.random() <= 0.75 then + slot.Installed = atts[ind] + end + end + + self:InvalidateCache() + end + + timer.Simple(0.25, function() + if !IsValid(self) then return end + self:NetworkWeapon() + end) + + self:SetBaseSettings() + + self:SetClip1(self:GetCapacity()) + + if math.random() <= 0.5 then + self:SetFiremode(math.random(1, self:GetFiremodeAmount())) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_penetration.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_penetration.lua new file mode 100644 index 0000000..28a605a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_penetration.lua @@ -0,0 +1,139 @@ + +local function draw_debug() + return (CLIENT or game.SinglePlayer()) and TacRP.Developer(2) +end + +local function IsPenetrating(ptr, ptrent) + if ptrent:IsWorld() then + return ptr.Contents != CONTENTS_EMPTY + elseif IsValid(ptrent) then + + local withinbounding = false + local hboxset = ptrent:GetHitboxSet() + local hitbone = ptrent:GetHitBoxBone(ptr.HitBox, hboxset) + if hitbone then + -- If we hit a hitbox, compare against that hitbox only + local mins, maxs = ptrent:GetHitBoxBounds(ptr.HitBox, hboxset) + local bonepos, boneang = ptrent:GetBonePosition(hitbone) + mins = mins * 1.1 + maxs = maxs * 1.1 + local lpos = WorldToLocal(ptr.HitPos, ptr.HitNormal:Angle(), bonepos, boneang) + + withinbounding = lpos:WithinAABox(mins, maxs) + if draw_debug() then + debugoverlay.BoxAngles(bonepos, mins, maxs, boneang, 5, Color(255, 255, 255, 10)) + end + elseif util.PointContents(ptr.HitPos) != CONTENTS_EMPTY then + -- Otherwise default to rotated OBB + local mins, maxs = ptrent:OBBMins(), ptrent:OBBMaxs() + withinbounding = ptrent:WorldToLocal(ptr.HitPos):WithinAABox(mins, maxs) + if draw_debug() then + debugoverlay.BoxAngles(ptrent:GetPos(), mins, maxs, ptrent:GetAngles(), 5, Color(255, 255, 255, 10)) + end + end + if draw_debug() then + debugoverlay.Cross(ptr.HitPos, withinbounding and 4 or 6, 5, withinbounding and Color(255, 255, 0) or Color(128, 255, 0), true) + end + + return withinbounding + end + return false +end + +function SWEP:Penetrate(tr, range, penleft, alreadypenned) + if !TacRP.ConVars["penetration"]:GetBool() then return end + + if !IsValid(self:GetOwner()) then return end + + local hitpos, startpos = tr.HitPos, tr.StartPos + local dir = (hitpos - startpos):GetNormalized() + + if tr.HitSky then return end + + if penleft <= 0 then return end + + alreadypenned = alreadypenned or {} + + local skip = false + + local trent = tr.Entity + + local penmult = TacRP.PenTable[tr.MatType] or 1 + + local pentracelen = math.max(penleft * penmult / 2, 2) + local curr_ent = trent + + if !tr.HitWorld then penmult = penmult * 0.5 end + if trent.Impenetrable then penmult = 100000 end + if trent.mmRHAe then penmult = trent.mmRHAe end + + -- penmult = penmult * math.Rand(0.9, 1.1) * math.Rand(0.9, 1.1) + + local endpos = hitpos + + local td = {} + td.start = endpos + td.endpos = endpos + (dir * pentracelen) + td.mask = MASK_SHOT + + local ptr = util.TraceLine(td) + + local ptrent = ptr.Entity + + while !skip and penleft > 0 and IsPenetrating(ptr, ptrent) and ptr.Fraction < 1 and ptrent == curr_ent do + penleft = penleft - (pentracelen * penmult) + + td.start = endpos + td.endpos = endpos + (dir * pentracelen) + td.mask = MASK_SHOT + + ptr = util.TraceLine(td) + + if TacRP.Developer() then + local pdeltap = penleft / self:GetValue("Penetration") + local colorlr = Lerp(pdeltap, 0, 255) + + debugoverlay.Line(endpos, endpos + (dir * pentracelen), 10, Color(255, colorlr, colorlr), true) + end + + endpos = endpos + (dir * pentracelen) + range = range + pentracelen + + ptrent = ptr.Entity + end + + if penleft > 0 then + if (dir:Length() == 0) then return end + + self:GetOwner():FireBullets({ + Damage = self:GetValue("Damage_Max"), + Force = 4, + Tracer = 0, + Num = 1, + Dir = dir, + Src = endpos, + Callback = function(att, btr, dmg) + range = range + (btr.HitPos - btr.StartPos):Length() + self:AfterShotFunction(btr, dmg, range, penleft, alreadypenned) + + if TacRP.Developer() then + if SERVER then + debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 0, 0), false) + else + debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 255, 255), false) + end + end + end + }) + + self:GetOwner():FireBullets({ + Damage = 0, + Force = 0, + Tracer = 0, + Num = 1, + Distance = 1, + Dir = -dir, + Src = endpos, + }) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_quicknade.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_quicknade.lua new file mode 100644 index 0000000..ce2e650 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_quicknade.lua @@ -0,0 +1,347 @@ +SWEP.GrenadeDownKey = IN_GRENADE1 +SWEP.GrenadeMenuKey = IN_GRENADE2 + +function SWEP:IsQuickNadeAllowed() + return TacRP.ConVars["quicknade"]:GetBool() and self:GetValue("CanQuickNade") +end + +function SWEP:PrimeGrenade() + self.Primary.Automatic = true + + if !self:IsQuickNadeAllowed() and !self:GetValue("PrimaryGrenade") then return end + if self:StillWaiting(nil, true) then return end + if self:GetPrimedGrenade() then return end + + if engine.ActiveGamemode() == "terrortown" and GetRoundState() == ROUND_PREP and ((TTT2 and !GetConVar("ttt_nade_throw_during_prep"):GetBool()) or (!TTT2 and GetConVar("ttt_no_nade_throw_during_prep"):GetBool())) then + return + end + + if !self:GetValue("PrimaryGrenade") and !self:CheckGrenade(nil, true) then + self:SelectGrenade(nil, true) + end + + -- if self:SprintLock() then return end + + self:CancelReload() + + local nade = self:GetValue("PrimaryGrenade") and TacRP.QuickNades[self:GetValue("PrimaryGrenade")] or self:GetGrenade() + + if nade.Singleton then + if !self:GetOwner():HasWeapon(nade.GrenadeWep) then return end + elseif !TacRP.IsGrenadeInfiniteAmmo(nade) then + local ammo = self:GetOwner():GetAmmoCount(nade.Ammo) + if ammo < 1 then return end + + -- self:GetOwner():SetAmmo(ammo - 1, nade.Ammo) + end + + local rate = self:GetValue("QuickNadeTimeMult") / (nade.ThrowSpeed or 1) + if self:GetValue("QuickNadeTryImpact") and nade.CanSetImpact then + rate = rate * 1.5 + end + + local t = self:PlayAnimation("prime_grenade", rate, true) + + self:SetPrimedGrenade(true) + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + self:ScopeToggle(0) + + local ct = CurTime() + + self:SetStartPrimedGrenadeTime(ct) + self:SetAnimLockTime(ct + (t * 0.75)) + self:SetNextPrimaryFire(ct + (t * 1.1)) + + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_ATTACK_SECONDARY, t * 1000) + + if !nade.NoSounds then + self:EmitSound(nade.PullSound or ("TacRP/weapons/grenade/pullpin-" .. math.random(1, 2) .. ".wav"), 65) + end + + if CLIENT then return end + + self.CurrentGrenade = self:GetGrenade() +end + +function SWEP:ThrowGrenade() + local nade = self:GetValue("PrimaryGrenade") and TacRP.QuickNades[self:GetValue("PrimaryGrenade")] or self.CurrentGrenade or self:GetGrenade() + + local force = nade.ThrowForce + local ent = nade.GrenadeEnt + + local src = self:GetOwner():EyePos() + local ang = self:GetOwner():EyeAngles() + local spread = 0 + + local amount = 1 + + local t = 0 + + if !nade.OverhandOnly and (self.GrenadeThrowOverride == true or (self.GrenadeThrowOverride == nil and !self:GetOwner():KeyDown(self.GrenadeDownKey))) then + t = self:PlayAnimation("throw_grenade_underhand", self:GetValue("QuickNadeTimeMult"), true, true) + + force = force / 2 + ang:RotateAroundAxis(ang:Right(), 20) + if nade.UnderhandSpecial then + force = force * 0.75 + ang:RotateAroundAxis(ang:Right(), -10) + amount = math.random(2, 4) + spread = 0.15 + end + else + ang:RotateAroundAxis(ang:Right(), 5) + t = self:PlayAnimation("throw_grenade", self:GetValue("QuickNadeTimeMult"), true, true) + end + + self.GrenadeThrowOverride = nil + self.GrenadeDownKey = IN_GRENADE1 + + if SERVER then + if (self.GrenadeThrowCharge or 0) > 0 then + force = force * (1 + self.GrenadeThrowCharge) + end + self.GrenadeThrowCharge = nil + + for i = 1, amount do + + local rocket = ents.Create(ent or "") + + if !IsValid(rocket) then return end + + local dispersion = Angle(math.Rand(-1, 1), math.Rand(-1, 1), 0) + dispersion = dispersion * spread * 36 + + rocket:SetPos(src) + rocket:SetOwner(self:GetOwner()) + rocket:SetAngles(ang + dispersion) + rocket:Spawn() + rocket:SetPhysicsAttacker(self:GetOwner(), 10) + + if TacRP.IsGrenadeInfiniteAmmo(nade) then + rocket.PickupAmmo = nil + rocket.WeaponClass = nil -- dz ents + end + + if self:GetValue("QuickNadeTryImpact") and nade.CanSetImpact then + rocket.InstantFuse = false + rocket.Delay = 0 + rocket.Armed = false + rocket.ImpactFuse = true + end + + if nade.TTTTimer then + rocket:SetGravity(0.4) + rocket:SetFriction(0.2) + rocket:SetElasticity(0.45) + rocket:SetDetonateExact(CurTime() + nade.TTTTimer) + rocket:SetThrower(self:GetOwner()) + end + + local phys = rocket:GetPhysicsObject() + + if phys:IsValid() then + phys:ApplyForceCenter((ang + dispersion):Forward() * force + self:GetOwner():GetVelocity()) + phys:AddAngleVelocity(VectorRand() * 1000) + end + + if nade.Spoon and TacRP.ConVars["dropmagazinemodel"]:GetBool() then + local mag = ents.Create("TacRP_droppedmag") + + if mag then + mag:SetPos(src) + mag:SetAngles(ang) + mag.Model = "models/weapons/tacint/flashbang_spoon.mdl" + mag.ImpactType = "spoon" + mag:SetOwner(self:GetOwner()) + mag:Spawn() + + local phys2 = mag:GetPhysicsObject() + + if IsValid(phys2) then + phys2:ApplyForceCenter(ang:Forward() * force * 0.25 + VectorRand() * 25) + phys2:AddAngleVelocity(Vector(math.Rand(-300, 300), math.Rand(-300, 300), math.Rand(-300, 300))) + end + end + end + end + + if !nade.NoSounds then + self:EmitSound(nade.ThrowSound or ("tacrp/weapons/grenade/throw-" .. math.random(1, 2) .. ".wav"), 65) + end + + if !nade.Singleton and !TacRP.IsGrenadeInfiniteAmmo(nade) then + self:GetOwner():RemoveAmmo(1, nade.Ammo) + end + end + + if self:GetValue("PrimaryGrenade") then + if !TacRP.IsGrenadeInfiniteAmmo(nade) and self:GetOwner():GetAmmoCount(nade.Ammo) == 0 then + if SERVER then + self:Remove() + end + else + self:SetTimer(t, function() + self:PlayAnimation("deploy", self:GetValue("DeployTimeMult"), true, true) + end) + end + elseif nade.Singleton and self:GetOwner():HasWeapon(nade.GrenadeWep) then + local nadewep = self:GetOwner():GetWeapon(nade.GrenadeWep) + nadewep.OnRemove = nil -- TTT wants to switch to unarmed when the nade wep is removed - DON'T. + if SERVER then + nadewep:Remove() + end + elseif nade.GrenadeWep and self:GetOwner():HasWeapon(nade.GrenadeWep) and !TacRP.IsGrenadeInfiniteAmmo(nade) and self:GetOwner():GetAmmoCount(nade.Ammo) == 0 then + if SERVER then + self:GetOwner():GetWeapon(nade.GrenadeWep):Remove() + end + end +end + +function SWEP:GetGrenade(index) + index = index or self:GetGrenadeIndex() + + return TacRP.QuickNades[TacRP.QuickNades_Index[index]] +end + +function SWEP:GetGrenadeIndex() + return IsValid(self:GetOwner()) and self:GetOwner():GetNWInt("ti_nade", 1) or 1 +end + +function SWEP:GetNextGrenade(ind) + ind = ind or self:GetGrenadeIndex() + + ind = ind + 1 + + if ind > TacRP.QuickNades_Count then + ind = 1 + elseif ind < 1 then + ind = TacRP.QuickNades_Count + end + + if !self:CheckGrenade(ind) then + return self:GetNextGrenade(ind) + end + + return self:GetGrenade(ind) +end + +function SWEP:SelectGrenade(index, requireammo) + if !self:IsQuickNadeAllowed() then return end + if !IsFirstTimePredicted() then return end + if self:GetPrimedGrenade() then return end + + local ind = self:GetOwner():GetNWInt("ti_nade", 1) + + if index then + ind = index + elseif !requireammo then + if self:GetOwner():KeyDown(IN_WALK) then + ind = ind - 1 + else + ind = ind + 1 + end + end + + if ind > TacRP.QuickNades_Count then + ind = 1 + elseif ind < 1 then + ind = TacRP.QuickNades_Count + end + + if !self:CheckGrenade(ind, requireammo) then + local nades = self:GetAvailableGrenades(requireammo) + if #nades > 0 then + ind = nades[1].Index + end + end + + self:GetOwner():SetNWInt("ti_nade", ind) + self.Secondary.Ammo = self:GetGrenade().Ammo or "none" +end + +function SWEP:CheckGrenade(index, checkammo) + index = index or (self:GetValue("PrimaryGrenade") and TacRP.QuickNades_Index[self:GetValue("PrimaryGrenade")] or self:GetOwner():GetNWInt("ti_nade", 1)) + local nade = self:GetGrenade(index) + if nade.Singleton then + return self:GetOwner():HasWeapon(nade.GrenadeWep) + end + local hasammo = (nade.Ammo == nil or self:GetOwner():GetAmmoCount(nade.Ammo) > 0) + if (nade.Secret and !hasammo and (!nade.SecretWeapon or !self:GetOwner():HasWeapon(nade.SecretWeapon))) or (nade.RequireStat and !self:GetValue(nade.RequireStat)) then + return false + end + if checkammo and !TacRP.IsGrenadeInfiniteAmmo(index) and !hasammo then + return false + end + return true +end + +function SWEP:GetAvailableGrenades(checkammo) + local nades = {} + + for i = 1, TacRP.QuickNades_Count do + if self:CheckGrenade(i, checkammo) then + table.insert(nades, self:GetGrenade(i)) + end + end + + return nades +end + +if CLIENT then + +SWEP.QuickNadeModel = nil + +end + +function SWEP:ThinkGrenade() + if !self:IsQuickNadeAllowed() then return end + + if CLIENT then + if self:GetPrimedGrenade() and !IsValid(self.QuickNadeModel) and self:GetStartPrimedGrenadeTime() + 0.2 < CurTime() and self:GetGrenade().Model then + local nade = self:GetGrenade() + local vm = self:GetVM() + + local model = ClientsideModel(nade.Model or "models/weapons/tacint/v_quicknade_frag.mdl") + + if !IsValid(model) then return end + + model:SetParent(vm) + model:AddEffects(EF_BONEMERGE) + model:SetNoDraw(true) + + if nade.Material then + model:SetMaterial(nade.Material) + end + + self.QuickNadeModel = model + + local tbl = { + Model = model, + Weapon = self + } + + table.insert(TacRP.CSModelPile, tbl) + elseif !self:GetPrimedGrenade() and self.QuickNadeModel then + SafeRemoveEntity(self.QuickNadeModel) + self.QuickNadeModel = nil + end + end + + if self:GetOwner():KeyPressed(IN_GRENADE1) then + self:PrimeGrenade() + elseif !tobool(self:GetOwner():GetInfo("tacrp_nademenu")) and self:GetOwner():KeyPressed(self.GrenadeMenuKey) then + self:SelectGrenade() + elseif tobool(self:GetOwner():GetInfo("tacrp_nademenu")) and self.GrenadeMenuKey != IN_GRENADE2 and !self:GetOwner():KeyDown(self.GrenadeMenuKey) then + self.GrenadeMenuKey = IN_GRENADE2 + end + + if CLIENT and self.GrenadeWaitSelect and self:GetOwner():HasWeapon(self.GrenadeWaitSelect) then + input.SelectWeapon(self:GetOwner():GetWeapon(self.GrenadeWaitSelect)) + self.GrenadeWaitSelect = nil + end + + if self:GetPrimedGrenade() and self:GetAnimLockTime() < CurTime() then + self:ThrowGrenade() + self:SetPrimedGrenade(false) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_recoil.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_recoil.lua new file mode 100644 index 0000000..40641ef --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_recoil.lua @@ -0,0 +1,136 @@ +function SWEP:GetRecoilResetTime(base) + if base then + return (self:GetBaseValue("RecoilResetInstant") and 0 or math.min(0.5, 60 / self:GetRPM(true))) + self:GetBaseValue("RecoilResetTime") + else + return (self:GetValue("RecoilResetInstant") and 0 or math.min(0.5, 60 / self:GetRPM())) + self:GetValue("RecoilResetTime") + end +end + +function SWEP:ThinkRecoil() + if self:GetLastRecoilTime() + engine.TickInterval() + self:GetRecoilResetTime() < CurTime() then + local rec = self:GetRecoilAmount() + + rec = rec - (FrameTime() * self:GetValue("RecoilDissipationRate")) + rec = math.Clamp(rec, 0, self:GetValue("RecoilMaximum")) + + self:SetRecoilAmount(rec) + end +end + +function SWEP:ApplyRecoil() + local rec = self:GetRecoilAmount() + local rps = self:GetValue("RecoilPerShot") + local cfm = self:GetCurrentFiremode() + + if rec == 0 then + rps = rps * self:GetValue("RecoilFirstShotMult") + end + + if self:GetOwner():Crouching() and self:GetOwner():OnGround() then + rps = rps * self:GetValue("RecoilMultCrouch") + end + + if cfm < 0 then + rps = rps * self:GetValue("RecoilMultBurst") + elseif cfm == 1 then + rps = rps * self:GetValue("RecoilMultSemi") + end + + if self:GetInBipod() then + rps = rps * math.min(1, self:GetValue("BipodRecoil")) + end + + rec = rec + rps + + rec = math.Clamp(rec, 0, self:GetValue("RecoilMaximum")) + + if self:UseRecoilPatterns() then + self:SetRecoilDirection(self:GetRecoilPatternDirection(self:GetPatternCount())) + else + local stab = math.Clamp(self:GetValue("RecoilStability"), 0, 0.9) + self:SetRecoilDirection(util.SharedRandom("tacrp_recoildir", -180 + stab * 90, -stab * 90)) + end + + -- self:SetRecoilDirection(-90) + self:SetRecoilAmount(rec) + self:SetLastRecoilTime(CurTime()) + + local vis_kick = self:GetValue("RecoilVisualKick") + local vis_shake = 0 + + vis_kick = vis_kick * TacRP.ConVars["mult_recoil_vis"]:GetFloat() + vis_shake = 0 + + if self:GetInBipod() then + vis_kick = vis_kick * math.min(1, self:GetValue("BipodKick")) + vis_shake = math.max(0, 1 - self:GetValue("BipodKick")) + end + + local vis_kick_v = vis_kick * 1 + local vis_kick_h = vis_kick * util.SharedRandom("tacrp_vis_kick_h", -1, 1) + + self:GetOwner():SetViewPunchAngles(Angle(vis_kick_v, vis_kick_h, vis_shake)) + + -- self:GetOwner():SetFOV(self:GetOwner():GetFOV() * 0.99, 0) + -- self:GetOwner():SetFOV(self:GetOwner():GetFOV(), 60 / (self:GetValue("RPM"))) +end + +function SWEP:RecoilDuration() + -- return self:GetValue("RecoilResetTime") + return 0.04 + math.Clamp(math.abs(self:GetValue("RecoilKick")) ^ 0.5, 0, 4) * 0.04 +end + +function SWEP:UseRecoilPatterns() + if !TacRP.ConVars["recoilpattern"]:GetBool() then return false end + if self:GetValue("ShootEnt") or self:GetValue("NoRecoilPattern") then return false end + if self:GetValue("RPM") <= 100 then return false end + if self:GetCurrentFiremode() < 0 then return false end + + return true +end + +SWEP.RecoilPatternCache = {} +SWEP.RecoilPatternSeedCache = nil +function SWEP:GetRecoilPatternDirection(shot) + local dir = 0 + + if !self.RecoilPatternSeedCache then + local cacheseed = self.RecoilPatternSeed or self:GetClass() + if isstring(cacheseed) then + local numseed = 0 + for _, i in ipairs(string.ToTable(cacheseed)) do + numseed = numseed + string.byte(i) + end + numseed = numseed % 16777216 + cacheseed = numseed + end + self.RecoilPatternSeedCache = cacheseed + end + + local seed = self.RecoilPatternSeedCache + shot + + if self.RecoilPatternCache[shot] then + dir = self.RecoilPatternCache[shot] + else + self.RecoilPatternCache[1] = 0 + if self.RecoilPatternCache[shot - 1] then + --dir = self.RecoilPatternCache[shot - 1] + math.randomseed(seed) + local stab = math.Clamp(self:GetValue("RecoilStability"), 0, 0.9) + local max = self:GetBaseValue("RPM") / 60 * (1.1 + stab * 1.1) + local cap = 120 --math.Clamp(30 + shot * (90 / max), 30, 120) + --dir = dir + math.Rand(-stab * 90, stab * 90) + dir = Lerp(0.4 + (shot / max) * 0.6, self.RecoilPatternCache[shot - 1], math.Rand(-(1 - stab) * cap, (1 - stab) * cap)) + if self:GetCurrentFiremode() != 1 then + dir = Lerp(shot / max, dir, math.Clamp(dir * 1.667, -cap, cap)) + end + math.randomseed(CurTime() + self:EntIndex()) + self.RecoilPatternCache[shot] = dir + -- print(shot, cap, max, dir) + else + dir = 0 + end + end + + return math.NormalizeAngle(dir - 90) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_reload.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_reload.lua new file mode 100644 index 0000000..d17d1f7 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_reload.lua @@ -0,0 +1,293 @@ +function SWEP:Reload(force) + force = force or false + + if self:GetOwner():IsNPC() then + self:NPC_Reload() + return + end + + if self:GetValue("Firemodes") and self:GetOwner():KeyDown(IN_USE) and self:GetOwner():KeyPressed(IN_RELOAD) and self:GetFiremodeAmount() > 1 and !self:GetSafe() then + self:SwitchFiremode() + self:EmitSound("tacrp/weapons/pdw/fire_select-1.wav", 75, 100, 1, CHAN_ITEM) + return + end + + local stop = self:RunHook("Hook_PreReload") + if stop then return end + + if !self:GetOwner():KeyPressed(IN_RELOAD) and !force then + return + end + + if self:StillWaiting(true) then return end + + if self:GetJammed() then + local t = self:PlayAnimation("jam", 0.75, true, true) + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_CANCEL_RELOAD, t * 1000) + self:SetJammed(false) + self:SetCharge(false) + return + end + + if !self:CanReloadInSprint() and self:GetIsSprinting() then return end + if self:GetCapacity() <= 0 then return end + if self:Clip1() >= self:GetCapacity() then return end + if self:Ammo1() <= 0 and !self:GetInfiniteAmmo() then return end + + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + + local mult = self:GetValue("ReloadTimeMult") / TacRP.ConVars["mult_reloadspeed"]:GetFloat() + + local anim = "reload" + + if self:GetValue("ShotgunReload") then + anim = "reload_start" + else + self:SetTimer(self:GetValue("LoadInTime") * mult, function() + self:SetLoadedRounds(math.min(self:GetCapacity(), self:Clip1() + self:Ammo1())) + self:DoBulletBodygroups() + end, "SetLoadedRounds") + if self.ReloadUpInTime then + self:SetTimer(self.ReloadUpInTime * mult, function() + self:RestoreClip(self:GetCapacity()) + self:SetNthShot(0) + self:SetEndReload(true) + end, "ReloadUpIn") + end + end + + local t = self:PlayAnimation(anim, mult, true, true) + + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_RELOAD, t * 1000) + + if SERVER then + self:SetTimer(self.DropMagazineTime * mult, function() + self:DropMagazine() + end, "DropMagazine") + end + + self:SetLoadedRounds(self:Clip1()) + + self:SetReloading(true) + self:SetEndReload(false) + + self:SetEmptyReload(self:GetValue("ShotgunNoReverseStart") or self:Clip1() == 0) + + self:DoBulletBodygroups() + + self:RunHook("Hook_StartReload") + + self:SetReloadFinishTime(CurTime() + (t * 0.95)) +end + +function SWEP:DropMagazine() + -- if !IsFirstTimePredicted() and !game.SinglePlayer() then return end + if self:GetValue("DropMagazineModel") and TacRP.ConVars["dropmagazinemodel"]:GetBool() then + local dropamt = math.floor(self:Clip1() / self:GetValue("DropMagazineAmount")) + local clip1 = self:Clip1() + for i = 1, self:GetValue("DropMagazineAmount") do + local mag = ents.Create("TacRP_droppedmag") + + if IsValid(mag) then + local bone = "ValveBiped.Bip01_R_Hand" + if i == 2 then bone = "ValveBiped.Bip01_L_Hand" end + local matrix = self:GetOwner():GetBoneMatrix(self:GetOwner():LookupBone(bone) or -1) + local pos, ang + if matrix then + pos = matrix:GetTranslation() + ang = matrix:GetAngles() + else + pos = self:GetOwner():EyePos() - (self:GetOwner():EyeAngles():Up() * 8) + ang = self:GetOwner():EyeAngles() + end + + mag:SetPos(pos) + mag:SetAngles(ang) + mag.Model = self:GetValue("DropMagazineModel") + mag.ImpactType = self:GetValue("DropMagazineImpact") + mag:SetOwner(self:GetOwner()) + if clip1 > 0 and TacRP.ConVars["reload_dump"]:GetBool() then + local amt = (i == self:GetValue("DropMagazineAmount") and clip1) or dropamt + clip1 = clip1 - amt + + if !self:GetInfiniteAmmo() then + mag.AmmoType = self:GetAmmoType() + mag.AmmoCount = amt + end + end + mag:Spawn() + + local phys = mag:GetPhysicsObject() + + if IsValid(phys) then + phys:AddAngleVelocity(Vector(math.Rand(-300, 300), math.Rand(-300, 300), math.Rand(-300, 300))) + end + end + end + self:SetClip1(clip1) + end +end + +function SWEP:RestoreClip(amt) + local reserve = self:GetInfiniteAmmo() and math.huge or (self:Clip1() + self:Ammo1()) + + local lastclip1 = self:Clip1() + + self:SetClip1(math.min(math.min(self:Clip1() + amt, self:GetCapacity()), reserve)) + + if !self:GetInfiniteAmmo() then + reserve = reserve - self:Clip1() + self:GetOwner():SetAmmo(reserve, self.Primary.Ammo) + end + + return self:Clip1() - lastclip1 +end + +function SWEP:Unload(ammotype) + self:GetOwner():GiveAmmo(self:Clip1(), ammotype or self.Primary.Ammo) + self:SetClip1(0) +end + +function SWEP:EndReload() + if self:GetValue("ShotgunReload") then + local mult = self:GetValue("ReloadTimeMult") / TacRP.ConVars["mult_reloadspeed"]:GetFloat() + if self:Clip1() >= self:GetCapacity() or (!self:GetInfiniteAmmo() and self:Ammo1() == 0) or self:GetEndReload() then + + local cancellable = TacRP.ConVars["reload_sg_cancel"]:GetBool() and !self:GetValue("ShotgunFullCancel") + local t = 1 + if !self.ShotgunNoReverseStart and (self:Clip1() == self:GetLoadedRounds() or !self:GetEmptyReload()) then + t = self:PlayAnimation("reload_start", -0.75 * mult, !cancellable, true) + else + t = self:PlayAnimation("reload_finish", mult, !cancellable, true) + end + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_RELOAD_END, t * 1000) + + self:SetReloading(false) + + self:SetNthShot(0) + + self:DoBulletBodygroups() + + self:RunHook("Hook_EndReload") + else + local t = self:PlayAnimation("reload", mult, true) + + local res = self:GetValue("ShotgunThreeload") and + math.min(math.min(3, self:GetCapacity() - self:Clip1()), self:GetInfiniteAmmo() and math.huge or self:Ammo1()) + or 1 + + local delay = self:GetValue("ShotgunUpInTime") + for i = 1, res do + self:SetTimer(t * delay * ((i - 1) / 3) + 0.22, function() + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_RELOAD_LOOP, t * 1000) + self:RestoreClip(1) + self:RunHook("Hook_InsertReload", res) + end, "ShotgunRestoreClip") + end + + self:SetTimer(t * self:GetValue("ShotgunLoadInTime") * (res / 3), function() + self:SetLoadedRounds(self:GetLoadedRounds() + res) + self:DoBulletBodygroups() + end, "SetLoadedRounds") + + self:SetReloadFinishTime(CurTime() + (t * delay * (res / 3)) + 0.05) + + self:DoBulletBodygroups() + end + else + if !self.ReloadUpInTime then + self:RestoreClip(self:GetCapacity()) + self:SetNthShot(0) + end + self:SetReloading(false) + self:SetEndReload(false) + + self:RunHook("Hook_EndReload") + end +end + +function SWEP:CancelReload(doanims, keeptime) + if self:GetReloading() then + + self:RunHook("Hook_CancelReload") + + local stop = false + + if doanims then + if self:GetValue("ShotgunReload") then + local mult = self:GetValue("ReloadTimeMult") / TacRP.ConVars["mult_reloadspeed"]:GetFloat() + local t = 1 + if self.CurrentAnimation == "reload_start" and self.ShotgunReloadCompleteStart then + self:SetEndReload(true) + elseif self:Clip1() == self:GetLoadedRounds() and !self.ShotgunNoReverseStart then + t = self:PlayAnimation("reload_start", -0.75 * mult, true, true) + stop = true + else + t = self:PlayAnimation("reload_finish", mult, true, true) + stop = true + end + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_RELOAD_END, t * 1000) + else + self:Idle() + stop = true + end + else + stop = true + end + + if stop then + self:KillTimer("SetLoadedRounds") + self:KillTimer("ShotgunRestoreClip") + self:KillTimer("ReloadUpIn") + self:KillTimer("DropMagazine") + self:SetReloading(false) + self:SetEndReload(false) + self:SetNthShot(0) + self:DoBulletBodygroups() + if !keeptime then + self:SetReloadFinishTime(0) + end + end + end +end + +function SWEP:ThinkReload() + if self:GetReloading() and self:GetReloadFinishTime() < CurTime() then + self:EndReload() + end +end + +local launcher_ammo = { + ["smg1_grenade"] = true, + ["rpg_round"] = true, +} +function SWEP:GetInfiniteAmmo() + local ammo = string.lower(self:GetAmmoType()) + if launcher_ammo[ammo] then + return TacRP.ConVars["infinitelaunchers"]:GetBool() or self:GetValue("InfiniteAmmo") + else + return TacRP.ConVars["infiniteammo"]:GetBool() or self:GetValue("InfiniteAmmo") + end +end + +function SWEP:GetCapacity(base) + if base then + return self:GetBaseValue("ClipSize") + else + return self:GetValue("ClipSize") + end +end + +function SWEP:GetAmmoType(base) + local valfunc = base and self.GetBaseValue or self.GetValue + if self.Ammo_Expanded and TacRP.ConVars["expandedammotypes"]:GetBool() then + return valfunc(self, "Ammo_Expanded") or valfunc(self, "Ammo") + else + return valfunc(self, "Ammo") + end +end + +-- Override to disable auto-reload for one reason or another. +function SWEP:ShouldAutoReload() + return true +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_scope.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_scope.lua new file mode 100644 index 0000000..0228b85 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_scope.lua @@ -0,0 +1,398 @@ +local blur = Material("pp/blurscreen") +local function drawBlurAt(x, y, w, h, amount, passes, reverse) + -- Intensity of the blur. + amount = amount or 5 + + surface.SetMaterial(blur) + surface.SetDrawColor(color_white) + + local scrW, scrH = ScrW(), ScrH() + local x2, y2 = x / scrW, y / scrH + local w2, h2 = (x + w) / scrW, (y + h) / scrH + + for i = -(passes or 0.2), 1, 0.2 do + if reverse then + blur:SetFloat("$blur", i * -1 * amount) + else + blur:SetFloat("$blur", i * amount) + end + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRectUV(x, y, w, h, x2, y2, w2, h2) + end +end + +local peekzoom = 1.2 + +function SWEP:ScopeToggle(setlevel) + if (setlevel or 0) > 0 and (!self:GetValue("Scope") or self:GetPrimedGrenade()) then return end + -- if setlevel and setlevel > 0 and self:GetAnimLockTime() > CurTime() or (!setlevel and self:GetAnimLockTime() > CurTime()) then return end + -- if (setlevel and setlevel > 0 and self:GetReloading()) or (!setlevel and self:GetReloading()) then return end + + local level = self:GetScopeLevel() + local oldlevel = level + + level = setlevel or (level + 1) + + if level > self:GetValue("ScopeLevels") then + level = self:GetValue("ScopeLevels") + end + + if self:GetCustomize() or self:GetLastMeleeTime() + 1 > CurTime() then -- self:SprintLock(true) + level = 0 + end + + if self:GetIsSprinting() and level > 0 then + if self:GetOwner():GetInfoNum("tacrp_aim_cancels_sprint", 0) > 0 and self:CanStopSprinting() then + self:GetOwner().TacRP_SprintBlock = true + else + level = 0 + end + end + + if self:DoOldSchoolScopeBehavior() then + level = 0 + end + + if level == self:GetScopeLevel() then return end + + self:SetScopeLevel(level) + + if level > 0 then + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + end + + if oldlevel == 0 or level == 0 then + self:SetLastScopeTime(CurTime()) + end + + if self:GetValue("AlwaysPeek") then + self:SetPeeking(true) + end + + -- HACK: In singleplayer, SWEP:Think is called on client but IsFirstTimePredicted is NEVER true. + -- This causes ScopeToggle to NOT be called on client in singleplayer... + -- GenerateAutoSight needs to run clientside or scopes will break. Good old CallOnClient it is. + + if SERVER and game.SinglePlayer() then + self:CallOnClient("GenerateAutoSight") + elseif CLIENT and (IsFirstTimePredicted() or game.SinglePlayer()) then + self:GenerateAutoSight() + self.LastHintLife = CurTime() + end + + self:EmitSound(self:GetValue("Sound_ScopeIn"), 75, 100, 1, CHAN_ITEM) + + self:SetShouldHoldType() + + self:RunHook("Hook_PostScopeToggle", setlevel) +end + +function SWEP:GetShouldFOV(ignorepeek) + local base = 90 + if !ignorepeek and self:GetPeeking() then + return base / peekzoom + else + return base / self:GetMagnification() + end +end + +function SWEP:IsInScope() + local sightdelta = self:Curve(self:GetSightDelta()) + + return (SERVER or !self:GetPeeking()) and !self:GetSafe() and ((self:GetScopeLevel() > 0 and sightdelta > 0.5) or (sightdelta > 0.9)) +end + +function SWEP:DoScope() + local h = ScrH() + local w = ScrW() + if self:IsInScope() then + local img = self:GetValue("ScopeOverlay") + if img then + -- assume players have a screen that is wider than it is tall because... that's stupid + + local pos = self:GetOwner():EyePos() + + pos = pos + self:GetShootDir():Forward() * 9000 + + local toscreen = pos:ToScreen() + + local x = toscreen.x + local y = toscreen.y + + local ss = math.Round(h * (self:GetValue("ScopeOverlaySize") or 1)) + local sx = x - (ss / 2) + local sy = y - (ss / 2) + + -- local shakey = math.min(cross * 35, 3) + + -- sx = sx + math.Round(math.Rand(-shakey, shakey)) + -- sy = sy + math.Round(math.Rand(-shakey, shakey)) + + -- local int = self:CheckFlashlightPointing() + -- if int > 0 then + -- surface.SetDrawColor(255, 255, 255, int * 250) + -- surface.DrawRect(0, 0, w, h) + -- end + + surface.SetMaterial(img) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(sx, sy, ss, ss) + + surface.SetDrawColor(0, 0, 0) + surface.DrawRect(0, 0, w, sy) + surface.DrawRect(0, sy + ss, w, h - sy) + + surface.DrawRect(0, 0, sx, h) + surface.DrawRect(sx + ss, 0, w - sx, h) + + if self:GetReloading() then + drawBlurAt(0, 0, w, h, 1, 1) + end + + -- if int > 0 then + -- surface.SetDrawColor(255, 255, 255, int * 25) + -- surface.DrawRect(0, 0, w, h) + -- end + + if self:GetValue("ScopeDraw") then + self:GetValue("ScopeDraw")(self) + end + end + end + + local blur_hook = self:RunHook("Hook_BlurScope") + if blur_hook then + if !istable(blur_hook) then blur_hook = {1, 1} end + drawBlurAt(0, 0, w, h, blur_hook[0], blur_hook[1]) + end +end + +function SWEP:GetSightDelta() + return self:GetSightAmount() +end + +function SWEP:SetSightDelta(d) + self:SetSightAmount(d) +end + +function SWEP:CanSight() + if self:GetReloading() and !TacRP.ConVars["ads_reload"]:GetBool() then return false end + + return true +end + +function SWEP:ThinkSights() + if !IsValid(self:GetOwner()) then return end + + local ftp = IsFirstTimePredicted() + local ftsp = IsFirstTimePredicted() or !game.SinglePlayer() + + if self:GetOwner():KeyDown(IN_USE) and self:GetOwner():KeyPressed(IN_ATTACK2) and ftsp then + self:ToggleSafety() + return + end + + if ftp and self:GetValue("Bipod") and self:GetOwner():KeyPressed(IN_ATTACK2) + and !self:GetInBipod() and self:CanBipod() and ftsp then + self:EnterBipod() + end + + local FT = FrameTime() + + local sighted = self:GetScopeLevel() > 0 + + local amt = self:GetSightAmount() + + local adst = self:GetAimDownSightsTime() + + if ftp or game.SinglePlayer() then + if sighted then + if self:GetSprintLockTime() > CurTime() then + adst = adst + self:GetSprintToFireTime() + end + amt = math.Approach(amt, 1, FT / adst) + else + amt = math.Approach(amt, 0, FT / adst) + end + end + + self:SetSightDelta(amt) + + if self:GetSafe() then return end + + if CLIENT then + self:ThinkPeek() + end + local toggle = self:GetOwner():GetInfoNum("tacrp_toggleaim", 0) == 1 + local press, down = self:GetOwner():KeyPressed(IN_ATTACK2), self:GetOwner():KeyDown(IN_ATTACK2) + + if (!self:GetValue("Scope") or self:DoOldSchoolScopeBehavior()) and !self.NoSecondaryMelee and down then + self:Melee() + elseif sighted and ((toggle and press and ftp) or (!toggle and !down)) then + self:ScopeToggle(0) + elseif !sighted and ((toggle and press and ftp) or (!toggle and down)) and self:CanSight() then + self:ScopeToggle(1) + elseif sighted and !self:CanSight() then + self:ScopeToggle(0) + end +end + +function SWEP:GetMagnification() + local mag = 1 + + local level = self:GetScopeLevel() + + if level > 0 then + if self:GetPeeking() then + return peekzoom + end + mag = 90 / self:GetValue("ScopeFOV") + mag = Lerp(level / self:GetValue("ScopeLevels"), 1, mag) + + mag = self:RunHook("Hook_ModifyMagnification") or mag + end + + if (mag <= 0) then + return 0.001 -- just in case + end + + return mag +end + +function SWEP:AdjustMouseSensitivity() + local mag = self:GetMagnification() + local sensmult = GetConVar("tacrp_aimsens"):GetFloat() + -- local aa = GetConVar("tacrp_aimassist") + -- local aac = GetConVar("tacrp_aimassist_cl") + -- local aai = GetConVar("tacrp_aimassist_intensity") + -- local aams = GetConVar("tacrp_aimassist_multsens") + + -- if self:GetOwner().tacrp_AATarget != nil and aa:GetBool() and aac:GetBool()) then + -- aamult = aams:GetFloat() / aai:GetFloat() + -- else + -- aamult = 1 + -- end + + if mag > 1 then + return 1 / mag * math.Clamp(sensmult, 0.1, 1) + end +end + +function SWEP:ThinkPeek() +end + +function SWEP:GetCCIP(pos, ang) + -- get calculated point of impact + + local sp, sa = self:GetMuzzleOrigin(), self:GetShootDir() + + pos = pos or sp + ang = ang or sa + + local v = self:GetValue("MuzzleVelocity") + local g = Vector(0, 0, -600) + local d = 1 + local h = 0 + + if self:GetValue("ShootEnt") then + v = self:GetValue("ShootEntForce") + d = 0 + g = physenv.GetGravity() + h = 4 + end + + local vel = ang:Forward() * v + local maxiter = 100 + local timestep = 1 / 15 + local gravity = timestep * g + + local steps = {} + + for i = 1, maxiter do + local dir = vel:GetNormalized() + local spd = vel:Length() * timestep + local drag = d * spd * spd * (1 / 150000) + + if spd <= 0.001 then return nil end + + local newpos = pos + (vel * timestep) + local newvel = vel - (dir * drag) + gravity + + local tr + if h > 0 then + tr = util.TraceHull({ + start = pos, + endpos = newpos, + filter = self:GetOwner(), + mask = MASK_SHOT, + mins = Vector(-h, -h, -h), + maxs = Vector(h, h, h), + }) + else + tr = util.TraceLine({ + start = pos, + endpos = newpos, + filter = self:GetOwner(), + mask = MASK_SHOT + }) + end + table.insert(steps, 0, tr.HitPos) + + if tr.Hit then + debugoverlay.Sphere(tr.HitPos, 8, 0.25, color_white, true) + return tr, i * timestep, steps + else + pos = newpos + vel = newvel + end + end + + return nil +end + +function SWEP:GetCorVal() + local vmfov = self.ViewModelFOV + local fov = self:GetShouldFOV() + + return vmfov / (fov * 1.33333) +end + +function SWEP:HasOptic() + return self:GetValue("Scope") and (self:GetValue("ScopeOverlay") or self:GetValue("Holosight")) +end + +function SWEP:DoOldSchoolScopeBehavior() + return (TacRP.ConVars["oldschool"]:GetBool() or TacRP.GetBalanceMode() == TacRP.BALANCE_OLDSCHOOL) + and !self:HasOptic() +end + + +function SWEP:GetAimDownSightsTime(base) + if base then + return self:GetBaseValue("AimDownSightsTime") * TacRP.ConVars["mult_aimdownsights"]:GetFloat() + else + return self:GetValue("AimDownSightsTime") * TacRP.ConVars["mult_aimdownsights"]:GetFloat() + end +end + +function SWEP:GetSprintToFireTime(base) + if base then + return self:GetBaseValue("SprintToFireTime") * TacRP.ConVars["mult_sprinttofire"]:GetFloat() + else + return self:GetValue("SprintToFireTime") * TacRP.ConVars["mult_sprinttofire"]:GetFloat() + end +end + +function SWEP:GetPeeking() + return (!self.InversePeek and self:GetNWPeeking()) or (self.InversePeek and !self:GetNWPeeking()) +end + +function SWEP:SetPeeking(b) + if self.InversePeek then + self:SetNWPeeking(!b) + else + self:SetNWPeeking(b) + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_shoot.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_shoot.lua new file mode 100644 index 0000000..01cc36c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_shoot.lua @@ -0,0 +1,872 @@ +function SWEP:StillWaiting(cust, reload) + if self:GetNextPrimaryFire() > CurTime() then return true end + if self:GetNextSecondaryFire() > CurTime() and (!reload or !(reload and self:GetReloading())) then return true end + if self:GetAnimLockTime() > CurTime() and (!reload or !(reload and self:GetReloading())) then return true end + if !cust and self:GetBlindFireFinishTime() > CurTime() then return true end + if !cust and self:GetCustomize() then return true end + if self:GetPrimedGrenade() then return true end + + return false +end + +function SWEP:SprintLock(shoot) + if self:GetSprintLockTime() > CurTime() or self:GetIsSprinting() or self:ShouldLowerWeapon() then + return true + end + + if shoot and self:DoForceSightsBehavior() and (self:GetSprintDelta() > 0 or self:GetSightDelta() < 0.75) and !self:GetBlindFire() then + return true + end + + if self:GetValue("CannotHipFire") and self:GetSightAmount() < 1 and !self:GetBlindFire() then + return true + end + + return false +end + +local function anglerotate(main, off) + local forward, up, right = main:Forward(), main:Up(), main:Right() + + main:RotateAroundAxis(right, off.p) + main:RotateAroundAxis(up, off.y) + main:RotateAroundAxis(forward, off.r) + + return main +end + +function SWEP:PrimaryAttack() + if self:GetOwner():IsNPC() then + self:NPC_PrimaryAttack() + return + end + + if self:GetValue("Melee") and self:GetOwner():KeyDown(IN_USE) and !(self:GetValue("RunawayBurst") and self:GetBurstCount() > 0) then + -- self.Primary.Automatic = false + self:SetSafe(false) + self:Melee() + return + end + + -- if self:GetJammed() then return end + if self:GetCurrentFiremode() < 0 and self:GetBurstCount() >= -self:GetCurrentFiremode() then return end + + if self:GetReloading() and self:GetValue("ShotgunReload") then + if TacRP.ConVars["reload_sg_cancel"]:GetBool() and !self:GetValue("ShotgunFullCancel") then + self:CancelReload(false) + self:Idle() + else + self:CancelReload(true) + end + end + + if self:SprintLock(true) then return end + if DarkRP and self:GetNWBool("TacRP_PoliceBiocode") and !self:GetOwner():isCP() then return end + if self:GetSafe() and !self:GetReloading() then self:ToggleSafety(false) return end + if self:StillWaiting() then return end + + if self:GetValue("RequireLockOn") and !(IsValid(self:GetLockOnEntity()) and CurTime() > self:GetLockOnStartTime() + self:GetValue("LockOnTime")) then return end + + if self:Clip1() < self:GetValue("AmmoPerShot") or self:GetJammed() then + local ret = self:RunHook("Hook_PreDryfire") + if ret != true then + self.Primary.Automatic = false + if self:GetBlindFire() then + self:PlayAnimation("blind_dryfire") + else + self:PlayAnimation("dryfire") + end + self:EmitSound(self:GetValue("Sound_DryFire"), 75, 100, 1, CHAN_ITEM) + self:SetBurstCount(0) + self:SetNextPrimaryFire(CurTime() + 0.2) + self:RunHook("Hook_PostDryfire") + return + end + end + + if util.SharedRandom("tacRP_shootChance", 0, 1) <= self:GetJamChance(false) then + local ret = self:RunHook("Hook_PreJam") + if ret != true then + if self:GetBurstCount() == 0 then + self.Primary.Automatic = false + end + if self:GetBlindFire() then + self:PlayAnimation("blind_dryfire") + else + self:PlayAnimation("dryfire") + end + self:EmitSound(self:GetValue("Sound_Jam"), 75, 100, 1, CHAN_ITEM) + self:SetBurstCount(0) + self:SetPatternCount(0) + self:SetNextPrimaryFire(CurTime() + self:GetValue("JamWaitTime")) + self:SetNextSecondaryFire(CurTime() + self:GetValue("JamWaitTime")) + if self:GetValue("JamTakesRound") then + self:TakePrimaryAmmo(self:GetValue("AmmoPerShot")) + end + if self:Clip1() > 0 and !self:GetValue("JamSkipFix") then + self:SetJammed(true) + end + self:RunHook("Hook_PostJam") + return + end + end + + self:SetBaseSettings() + + local stop = self:RunHook("Hook_PreShoot") + if stop then return end + + local seq = "fire" + + local idle = true + + local mult = self:GetValue("ShootTimeMult") + + if self:GetValue("LastShot") and self:Clip1() == self:GetValue("AmmoPerShot") then + seq = self:TranslateSequence("lastshot") + idle = false + end + + if self:GetBlindFire() then + seq = "blind_" .. seq + end + + if self:GetValue("Akimbo") and !self:GetBlindFire() then + if self:GetNthShot() % 2 == 0 then + seq = "shoot_left" + else + seq = "shoot_right" + end + + if self:GetValue("LastShot") then + if self:Clip1() == self:GetValue("AmmoPerShot") then + seq = seq .. "_lastshot" + elseif self:Clip1() == self:GetValue("AmmoPerShot") * 2 then + seq = seq .. "_second_2_lastshot" + end + end + end + + local prociron = self:DoProceduralIrons() + if self:GetScopeLevel() > 0 and (prociron or self:HasSequence(seq .. "_iron")) and !self:GetPeeking() then + if prociron then + if self:GetValue("LastShot") and self:Clip1() == self:GetValue("AmmoPerShot") then + self:PlayAnimation(self:TranslateSequence("dryfire"), mult, false) + end + self:SetLastProceduralFireTime(CurTime()) + else + self:PlayAnimation(seq .. "_iron", mult, false, idle) + end + elseif self:HasSequence(seq .. "1") then + local seq1 = seq .. "1" + if !self:GetInBipod() and (self:GetScopeLevel() < 1 or self:GetPeeking()) then + seq1 = seq .. tostring(self:GetBurstCount() + 1) + end + + if self:HasSequence(seq1) then + self:PlayAnimation(seq1, mult, false, idle) + elseif self:GetScopeLevel() < 1 or self:GetPeeking() then + for i = self:GetBurstCount() + 1, 1, -1 do + local seq2 = seq .. tostring(i) + if self:HasSequence(seq2) then + self:PlayAnimation(seq2, mult, false, idle) + break + end + end + end + else + self:PlayAnimation(seq, mult, false, idle) + end + + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_ATTACK_PRIMARY, 0) + + local pvar = self:GetValue("ShootPitchVariance") + + local sshoot = self:GetValue("Sound_Shoot") + + if self:GetValue("Silencer") then + sshoot = self:GetValue("Sound_Shoot_Silenced") + end + + if istable(sshoot) then + sshoot = table.Random(sshoot) + end + + if self:GetValue("Sound_ShootAdd") then + self:EmitSound(self:GetValue("Sound_ShootAdd"), self:GetValue("Vol_Shoot"), self:GetValue("Pitch_Shoot") + util.SharedRandom("TacRP_sshoot", -pvar, pvar), self:GetValue("Loudness_Shoot"), CHAN_BODY) + end + + -- if we die from suicide, EmitSound will not play, so do this instead + if self:GetBlindFireMode() == TacRP.BLINDFIRE_KYS then + if SERVER then + sound.Play(sshoot, self:GetMuzzleOrigin(), self:GetValue("Vol_Shoot"), self:GetValue("Pitch_Shoot") + util.SharedRandom("TacRP_sshoot", -pvar, pvar), self:GetValue("Loudness_Shoot")) + end + else + self:EmitSound(sshoot, self:GetValue("Vol_Shoot"), self:GetValue("Pitch_Shoot") + util.SharedRandom("TacRP_sshoot", -pvar, pvar), self:GetValue("Loudness_Shoot"), CHAN_WEAPON) + end + + local delay = 60 / self:GetRPM() + -- local delay = 60 / self:GetRPM() + + local curatt = self:GetNextPrimaryFire() + local diff = CurTime() - curatt + + if diff > engine.TickInterval() or diff < 0 then + curatt = CurTime() + end + + self:SetNthShot(self:GetNthShot() + 1) + + local ejectdelay = self:GetValue("EjectDelay") + if ejectdelay == 0 then + self:DoEject() + else + self:SetTimer(ejectdelay, function() + self:DoEject() + end) + end + + self:DoEffects() + + if self:GetValue("EffectsDoubled") then + -- self:SetNthShot(self:GetNthShot() + 1) + self:DoEffects(true) + if ejectdelay == 0 then + self:DoEject(true) + else + self:SetTimer(ejectdelay, function() + self:DoEject(true) + end) + end + -- self:SetNthShot(self:GetNthShot() - 1) + end + + local num = self:GetValue("Num") + local fixed_spread = self:IsShotgun() and TacRP.ConVars["fixedspread"]:GetBool() + local pellet_spread = self:IsShotgun() and self:GetValue("ShotgunPelletSpread") > 0 and TacRP.ConVars["pelletspread"]:GetBool() + + local spread = self:GetSpread() + + local dir = self:GetShootDir() + + local tr = self:GetValue("TracerNum") + + local shootent = self:GetValue("ShootEnt") + + if IsFirstTimePredicted() then + + local hitscan = !TacRP.ConVars["physbullet"]:GetBool() + + local dist = 100000 + + -- If the bullet is going to hit something very close in front, use hitscan bullets instead + -- This uses the aim direction without random spread, which may result in hitscan bullets in distances where it shouldn't be. + if !hitscan and (game.SinglePlayer() or !TacRP.ConVars["client_damage"]:GetBool()) then + dist = math.max(self:GetValue("MuzzleVelocity"), 15000) * engine.TickInterval() + * game.GetTimeScale() + * (num == 1 and 2 or 1) * (game.IsDedicated() and 1 or 2) + local threshold = dir:Forward() * dist + local inst_tr = util.TraceLine({ + start = self:GetMuzzleOrigin(), + endpos = self:GetMuzzleOrigin() + threshold, + mask = MASK_SHOT, + filter = {self:GetOwner(), self:GetOwner():GetVehicle(), self}, + }) + if inst_tr.Hit and !inst_tr.HitSky then + hitscan = true + end + -- debugoverlay.Line(self:GetMuzzleOrigin(), self:GetMuzzleOrigin() + threshold, 2, hitscan and Color(255, 0, 255) or Color(255, 255, 255)) + end + + -- Firebullets already does this so this is just placebo + -- self:GetOwner():LagCompensation(true) + + if shootent or !hitscan or fixed_spread then + local d = math.random() -- self:GetNthShot() / self:GetCapacity() + for i = 1, num do + local new_dir = Angle(dir) + if fixed_spread then + local sgp_x, sgp_y = self:GetShotgunPattern(i, d) + // new_dir:Add(Angle(sgp_x, sgp_y, 0) * 36 * 1.4142135623730) + new_dir = anglerotate(new_dir, Angle(sgp_x, sgp_y, 0) * 36 * 1.4142135623730) + if pellet_spread then + // new_dir:Add(self:RandomSpread(self:GetValue("ShotgunPelletSpread"), i)) + new_dir = anglerotate(new_dir, self:RandomSpread(self:GetValue("ShotgunPelletSpread"), i)) + end + else + // new_dir:Add(self:RandomSpread(spread, i)) + new_dir = anglerotate(new_dir, self:RandomSpread(spread, i)) + end + + if shootent then + self:ShootRocket(new_dir) + elseif hitscan then + self:GetOwner():FireBullets({ + Damage = self:GetValue("Damage_Max"), + Force = 8, + Tracer = tr, + TracerName = "tacrp_tracer", + Num = 1, + Dir = new_dir:Forward(), + Src = self:GetMuzzleOrigin(), + Spread = Vector(), + IgnoreEntity = self:GetOwner():GetVehicle(), + Distance = dist, + HullSize = (self:IsShotgun() and i % 2 == 0) and TacRP.ShotgunHullSize or 0, + Callback = function(att, btr, dmg) + local range = (btr.HitPos - btr.StartPos):Length() + + self:AfterShotFunction(btr, dmg, range, self:GetValue("Penetration"), {}) + -- if SERVER then + -- debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 0, 0), false) + -- else + -- debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 255, 255), false) + -- end + end + }) + else + TacRP:ShootPhysBullet(self, self:GetMuzzleOrigin(), new_dir:Forward() * self:GetValue("MuzzleVelocity"), + {HullSize = (self:IsShotgun() and i % 2 == 0) and TacRP.ShotgunHullSize or 0,}) + end + end + else + local new_dir = Angle(dir) + local new_spread = spread + -- if pellet_spread then + -- new_spread = self:GetValue("ShotgunPelletSpread") + -- new_dir:Add(self:RandomSpread(spread, 0)) + -- end + + -- Try to use Num in FireBullets if at all possible, as this is more performant and better for damage calc compatibility + -- Also it generates nice big numbers in various hit number addons instead of a buncha small ones. + self:GetOwner():FireBullets({ + Damage = self:GetValue("Damage_Max"), + Force = 8, + Tracer = tr, + TracerName = "tacrp_tracer", + Num = num, + Dir = new_dir:Forward(), + Src = self:GetMuzzleOrigin(), + Spread = Vector(new_spread, new_spread, 0), + IgnoreEntity = self:GetOwner():GetVehicle(), + Distance = dist, + Callback = function(att, btr, dmg) + local range = (btr.HitPos - btr.StartPos):Length() + + if IsValid(btr.Entity) and (!game.SinglePlayer() and TacRP.ConVars["client_damage"]:GetBool()) then + if CLIENT then + net.Start("tacrp_clientdamage") + net.WriteEntity(self) + net.WriteEntity(btr.Entity) + net.WriteVector(btr.Normal) + net.WriteVector(btr.Entity:WorldToLocal(btr.HitPos)) + net.WriteUInt(btr.HitGroup, 8) + net.WriteFloat(range) + net.WriteFloat(self:GetValue("Penetration")) + net.WriteUInt(0, 4) + net.SendToServer() + else + self:AfterShotFunction(btr, dmg, range, self:GetValue("Penetration"), {[btr.Entity] = true}) + end + else + self:AfterShotFunction(btr, dmg, range, self:GetValue("Penetration"), {}) + end + + if SERVER then + debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 0, 0), false) + else + debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 255, 255), false) + + end + end + }) + end + + -- self:GetOwner():LagCompensation(false) + end + + self:ApplyRecoil() + + self:SetNextPrimaryFire(curatt + delay) + self:TakePrimaryAmmo(self:GetValue("AmmoPerShot")) + + self:SetBurstCount(self:GetBurstCount() + 1) + self:SetPatternCount(self:GetPatternCount() + 1) + self:DoBulletBodygroups() + + if self:Clip1() == 0 then self.Primary.Automatic = false end + + -- FireBullets won't hit ourselves. Apply damage directly! + if SERVER and self:GetBlindFireMode() == TacRP.BLINDFIRE_KYS and !self:GetValue("ShootEnt") then + timer.Simple(0, function() + if !IsValid(self) or !IsValid(self:GetOwner()) then return end + local damage = DamageInfo() + damage:SetAttacker(self:GetOwner()) + damage:SetInflictor(self) + damage:SetDamage(self:GetValue("Damage_Max") * self:GetValue("Num") * self:GetConfigDamageMultiplier()) + damage:SetDamageType(self:GetValue("DamageType") or self:IsShotgun() and DMG_BUCKSHOT or DMG_BULLET) + damage:SetDamagePosition(self:GetMuzzleOrigin()) + damage:SetDamageForce(dir:Forward() * self:GetValue("Num")) + + damage:ScaleDamage(self:GetBodyDamageMultipliers()[HITGROUP_HEAD]) + -- self:GetOwner():SetLastHitGroup(HITGROUP_HEAD) + + self:GetOwner():TakeDamageInfo(damage) + end) + end + + if CLIENT and self:GetOwner() == LocalPlayer() then + self:DoMuzzleLight() + elseif game.SinglePlayer() then + self:CallOnClient("DoMuzzleLight") + end + + self:SetCharge(false) + + -- Troll + if self:GetBurstCount() >= 8 and TacRP.ShouldWeFunny(true) and (self.NextTroll or 0) < CurTime() and math.random() <= 0.02 then + timer.Simple(math.Rand(0, 0.25), function() + if IsValid(self) then + self:EmitSound("tacrp/discord-notification.wav", nil, 100, math.Rand(0.1, 0.5), CHAN_BODY) + end + end) + self.NextTroll = CurTime() + 180 + end + + self:RunHook("Hook_PostShoot") +end + +local rings = {1, 9, 24, 45} +local function ringnum(i) + return rings[i] or (rings[#rings] + i ^ 2) +end + +local function getring(x) + local i = 1 + while x > ringnum(i) do i = i + 1 end + return i +end + +function SWEP:GetShotgunPattern(i, d) + local ring_spread = self:GetSpread() + local num = self:GetValue("Num") + if num == 1 then return 0, 0 end + + local pelspread = self:GetValue("ShotgunPelletSpread") > 0 and TacRP.ConVars["pelletspread"]:GetBool() + if pelspread then + ring_spread = ring_spread - self:GetValue("ShotgunPelletSpread") + else + d = 0 + end + + local x = 0 + local y = 0 + local red = num <= 3 and 0 or 1 + local f = (i - red) / (num - red) + + if num == 2 then + local angle = f * 180 + (pelspread and (d - 0.5) * 60 or 0) + + x = math.sin(math.rad(angle)) * ring_spread + y = math.cos(math.rad(angle)) * ring_spread + elseif num == 3 then + local angle = f * 360 + d * 180 + 30 + x = math.sin(math.rad(angle)) * ring_spread + y = math.cos(math.rad(angle)) * ring_spread + elseif i == 1 then + return x, y + -- elseif num <= 9 then + -- local angle = 360 * (f + d - (1 / (num - 2))) + -- x = math.sin(math.rad(angle)) * ring_spread + -- y = math.cos(math.rad(angle)) * ring_spread + else + local tr = getring(num) + local ri = getring(i) + local rin = ringnum(ri) + local rln = ringnum(ri - 1) + + local l = (ri - 1) / (tr - 1) + if ri == tr then + f = (i - rln) / ((math.min(rin, num)) - rln) + else + f = (i - rln) / (rin - rln) + end + + local angle = 360 * (f + l + d) + x = math.sin(math.rad(angle)) * ring_spread * l + y = math.cos(math.rad(angle)) * ring_spread * l + end + + return x, y +end + +local doorclasses = { + ["func_door_rotating"] = true, + ["prop_door_rotating"] = true, + ["prop_door_rotating_checkpoint"] = true +} + +function SWEP:AfterShotFunction(tr, dmg, range, penleft, alreadypenned, forced) + if !forced and !IsFirstTimePredicted() and !game.SinglePlayer() then return end + + if self:GetValue("DamageType") then + dmg:SetDamageType(self:GetValue("DamageType")) + elseif self:IsShotgun() then + dmg:SetDamageType(DMG_BUCKSHOT + (engine.ActiveGamemode() == "terrortown" and DMG_BULLET or 0)) + end + + local matpen = self:GetValue("Penetration") + + if tr.Entity and alreadypenned[tr.Entity] then + dmg:SetDamage(0) + elseif IsValid(tr.Entity) then + dmg:SetDamage(self:GetDamageAtRange(range)) + local bodydamage = self:GetBodyDamageMultipliers() + + if bodydamage[tr.HitGroup] then + dmg:ScaleDamage(bodydamage[tr.HitGroup]) + end + + if tr.Entity:IsNextBot() or tr.Entity:IsNPC() then + dmg:ScaleDamage(self:GetValue("DamageMultNPC")) + end + + TacRP.CancelBodyDamage(tr.Entity, dmg, tr.HitGroup) + + if self:GetOwner():IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() then + dmg:ScaleDamage(0.25) + elseif matpen > 0 and TacRP.ConVars["penetration"]:GetBool() and !self:GetOwner():IsNPC() then + local pendelta = penleft / matpen + pendelta = Lerp(pendelta, math.Clamp(matpen * 0.02, 0.25, 0.5), 1) + dmg:ScaleDamage(pendelta) + end + alreadypenned[tr.Entity] = true + + if tr.Entity.LVS and !self:IsShotgun() then + dmg:ScaleDamage(0.5) + dmg:SetDamageForce(dmg:GetDamageForce():GetNormalized() * matpen * 75) + dmg:SetDamageType(DMG_AIRBOAT + DMG_SNIPER) + penleft = 0 + end + + if SERVER and self:GetValue("DamageType") == DMG_BURN and IsValid(tr.Entity) then + tr.Entity:Ignite(1, 64) + end + end + + if self:GetValue("ExplosiveDamage") > 0 and penleft == matpen then + -- Add DMG_AIRBOAT to hit helicopters + -- Need a timer here because only one DamageInfo can exist at a time + timer.Simple(0.0001, function() + if !IsValid(self) or !IsValid(self:GetOwner()) then return end + local dmginfo = DamageInfo() + dmginfo:SetAttacker(self:GetOwner()) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(self:GetValue("ExplosiveDamageType") or (DMG_BLAST + DMG_AIRBOAT)) + dmginfo:SetDamage(self:GetValue("ExplosiveDamage")) + util.BlastDamageInfo(dmginfo, tr.HitPos, self:GetValue("ExplosiveRadius")) + end) + -- penleft = 0 + --util.BlastDamage(self, self:GetOwner(), tr.HitPos, self:GetValue("ExplosiveRadius"), self:GetValue("ExplosiveDamage")) + end + + if self:GetValue("ExplosiveEffect") then + local fx = EffectData() + fx:SetOrigin(tr.HitPos) + fx:SetNormal(tr.HitNormal) + + if bit.band(util.PointContents(tr.HitPos), CONTENTS_WATER) == CONTENTS_WATER then + util.Effect("WaterSurfaceExplosion", fx, true) + else + util.Effect(self:GetValue("ExplosiveEffect"), fx, true) + end + end + + if SERVER and IsValid(tr.Entity) and !tr.Entity.TacRP_DoorBusted + and doorclasses[tr.Entity:GetClass()] and self:GetValue("DoorBreach") then + if !tr.Entity.TacRP_BreachThreshold or CurTime() - tr.Entity.TacRP_BreachThreshold[1] > 0.1 then + tr.Entity.TacRP_BreachThreshold = {CurTime(), 0} + end + + tr.Entity.TacRP_BreachThreshold[2] = tr.Entity.TacRP_BreachThreshold[2] + dmg:GetDamage() + if tr.Entity.TacRP_BreachThreshold[2] > (self:GetValue("DoorBreachThreshold") or 100) then + tr.Entity:EmitSound("ambient/materials/door_hit1.wav", 80, math.Rand(95, 105)) + for _, otherDoor in pairs(ents.FindInSphere(tr.Entity:GetPos(), 72)) do + if tr.Entity != otherDoor and otherDoor:GetClass() == tr.Entity:GetClass() then + local v = (otherDoor.TacRP_BreachThreshold and CurTime() - otherDoor.TacRP_BreachThreshold[1] <= 0.1) and 800 or 200 + TacRP.DoorBust(otherDoor, tr.Normal * v, dmg:GetAttacker()) + break + end + end + TacRP.DoorBust(tr.Entity, tr.Normal * 800, dmg:GetAttacker()) + tr.Entity.TacRP_BreachThreshold = nil + end + end + + self:Penetrate(tr, range, penleft, alreadypenned) +end + +function SWEP:GetMinMaxRange(base, static) + local valfunc = base and self.GetBaseValue or self.GetValue + + local max, min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min") + return valfunc(self, "Range_Min", static, max < min), valfunc(self, "Range_Max", static, max < min) +end + +function SWEP:GetDamageAtRange(range, noround) + local d = 1 + + local r_min, r_max = self:GetMinMaxRange() + + if range <= r_min then + d = 0 + elseif range >= r_max then + d = 1 + else + d = (range - r_min) / (r_max - r_min) + end + + local dmgv = Lerp(d, self:GetValue("Damage_Max"), self:GetValue("Damage_Min")) * self:GetConfigDamageMultiplier() + + if !noround then + dmgv = math.ceil(dmgv) + end + + return dmgv +end + +function SWEP:GetShootDir(nosway) + if !IsValid(self:GetOwner()) then return self:GetAngles() end + local dir = self:GetOwner():EyeAngles() + + local bf = self:GetBlindFireMode() + if bf == TacRP.BLINDFIRE_KYS then + dir.y = dir.y + 180 + elseif bf == TacRP.BLINDFIRE_LEFT then + dir.y = dir.y + 75 + elseif bf == TacRP.BLINDFIRE_RIGHT then + dir.y = dir.y - 75 + end + + local u, r = dir:Up(), dir:Right() + + local oa = self:GetFreeAimOffset() + if !nosway then + oa = oa + self:GetSwayAngles() + end + + dir:RotateAroundAxis(u, oa.y) + -- dir:RotateAroundAxis(r, oa.r) + dir:RotateAroundAxis(r, -oa.p) + + dir = dir + self:GetValue("ShootOffsetAngle") + + return dir +end + +function SWEP:ShootRocket(dir) + if CLIENT then return end + + local src = self:GetMuzzleOrigin() + dir = dir or self:GetShootDir() + + local ent = self:GetValue("ShootEnt") + + local rocket = ents.Create(ent) + if !IsValid(rocket) then return end + + rocket:SetPos(src) + if self:GetBlindFireMode() != TacRP.BLINDFIRE_KYS then + rocket:SetOwner(self:GetOwner()) + else + rocket.Attacker = self:GetOwner() + end + rocket.Inflictor = self + rocket:SetAngles(dir) + if isfunction(rocket.SetWeapon) then + rocket:SetWeapon(self) + end + if self:GetOwner():IsNPC() then + rocket.LockOnEntity = self:GetOwner():GetTarget() + else + if IsValid(self:GetLockOnEntity()) and CurTime() >= self:GetValue("LockOnTime") + self:GetLockOnStartTime() then + rocket.LockOnEntity = self:GetLockOnEntity() + end + end + self:RunHook("Hook_PreShootEnt", rocket) + rocket:Spawn() + self:RunHook("Hook_PostShootEnt", rocket) + + local phys = rocket:GetPhysicsObject() + + if phys:IsValid() and self:GetValue("ShootEntForce") > 0 then + phys:SetVelocityInstantaneous(dir:Forward() * self:GetValue("ShootEntForce")) + end +end + +function SWEP:GetSpread(baseline) + local ply = self:GetOwner() + local spread = self:GetValue("Spread") + + if baseline then return spread end + + local hippenalty = self:GetValue("HipFireSpreadPenalty") + local movepenalty = self:GetValue("MoveSpreadPenalty") + if TacRP.ConVars["oldschool"]:GetBool() or TacRP.GetBalanceMode() == TacRP.BALANCE_OLDSCHOOL then + movepenalty = movepenalty + hippenalty * 0.25 + hippenalty = hippenalty * Lerp(12 / (self:GetValue("ScopeFOV") - 1.1), 0.05, 0.5) + end + + if self:GetInBipod() and self:GetScopeLevel() == 0 then + spread = spread + Lerp(1 - self:GetValue("PeekPenaltyFraction"), hippenalty, 0) + else + spread = spread + Lerp(self:GetSightAmount() - (self:GetPeeking() and self:GetValue("PeekPenaltyFraction") or 0), hippenalty, 0) + end + + if !self:UseAltRecoil() then + spread = spread + (self:GetRecoilAmount() * self:GetValue("RecoilSpreadPenalty")) + end + + local v = ply:GetAbsVelocity() + local spd = math.min(math.sqrt(v.x * v.x + v.y * v.y) / 250, 1) + + spread = spread + (spd * movepenalty) + + local groundtime = CurTime() - (ply.TacRP_LastOnGroundTime or 0) + local gd = math.Clamp(!ply:IsOnGround() and 0 or groundtime / math.Clamp((ply.TacRP_LastAirDuration or 0) - 0.25, 0.1, 1.5), 0, 1) ^ 0.75 + + if gd < 1 and ply:GetMoveType() != MOVETYPE_NOCLIP then + local v = (ply:WaterLevel() > 0 or ply:GetMoveType() == MOVETYPE_LADDER) and 0.5 or 0 + spread = spread + Lerp(gd + v, self:GetValue("MidAirSpreadPenalty"), 0) + end + + if ply:OnGround() and ply:Crouching() then + spread = spread + self:GetValue("CrouchSpreadPenalty") + end + + if self:GetBlindFire() then + spread = spread + self:GetValue("BlindFireSpreadPenalty") + end + + local quickscopetime = CurTime() - self:GetLastScopeTime() + + local qsd = (quickscopetime / self:GetValue("QuickScopeTime")) ^ 4 + + if qsd < 1 then + spread = spread + Lerp(qsd, self:GetValue("QuickScopeSpreadPenalty"), 0) + end + + spread = math.max(spread, 0) + + return spread +end + +local type_to_cvar = { + ["2Magnum Pistol"] = "mult_damage_magnum", + ["7Sniper Rifle"] = "mult_damage_sniper", + -- ["5Shotgun"] = "mult_damage_shotgun", + + ["6Launcher"] = "", + ["7Special Weapon"] = "", + ["8Melee Weapon"] = "", + ["9Equipment"] = "", + ["9Throwable"] = "", +} +function SWEP:GetConfigDamageMultiplier() + if self:IsShotgun() then + return TacRP.ConVars["mult_damage_shotgun"]:GetFloat() + elseif self:GetValue("PrimaryMelee") then + return TacRP.ConVars["mult_damage_melee"]:GetFloat() + else + local cvar = type_to_cvar[self.SubCatType] or "mult_damage" + return TacRP.ConVars[cvar] and TacRP.ConVars[cvar]:GetFloat() or 1 + end +end + +local shotgundmgmult = { + [HITGROUP_HEAD] = 1, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 1, + [HITGROUP_RIGHTLEG] = 1, + [HITGROUP_GEAR] = 1, +} + +function SWEP:GetBodyDamageMultipliers(base) + if self:IsShotgun(base) then -- Shotguns using hull traces will never hit bodygroups + return table.Copy(shotgundmgmult) + end + + local valfunc = base and self.GetBaseValue or self.GetValue + + local btbl = table.Copy(valfunc(self, "BodyDamageMultipliers")) + + for k, v in pairs(valfunc(self, "BodyDamageMultipliersExtra") or {}) do + if v < 0 then + btbl[k] = math.abs(v) + else + btbl[k] = btbl[k] * v + end + end + + local mult = TacRP.ConVars["mult_headshot"]:GetFloat() + if mult <= 0 then + btbl[HITGROUP_HEAD] = 1 + elseif mult <= 1 then + btbl[HITGROUP_HEAD] = Lerp(mult, 1, btbl[HITGROUP_HEAD]) + else + btbl[HITGROUP_HEAD] = btbl[HITGROUP_HEAD] * mult + end + + return btbl +end + +function SWEP:FireAnimationEvent( pos, ang, event, options ) + if event != 5004 then return true end +end + +-- DO NOT USE AngleRand() to do bullet spread as it generates a random angle in 3 directions when we only use two (roll is not relevant!) +-- Also, multiplying by 36 is not correct! you need to also multiply by square root of 2. Trig stuff, i forgot why exactly. +-- Arctic I fixed this THREE FUCKING TIMES on your THREE FUCKING WEAPON BASES do not make me come to Australia and beat the shit out of you +function SWEP:RandomSpread(spread, seed) + seed = (seed or 0) + self:EntIndex() + engine.TickCount() + local a = util.SharedRandom("tacrp_randomspread", 0, 360, seed) + local angleRand = Angle(math.sin(a), math.cos(a), 0) + angleRand:Mul(spread * util.SharedRandom("tacrp_randomspread2", 0, 45, seed) * 1.4142135623730) + + return angleRand +end + +function SWEP:IsShotgun(base) + if base then + return self:GetBaseValue("Num") > 1 and !self:GetBaseValue("NotShotgun") + else + return self:GetValue("Num") > 1 and !self:GetValue("NotShotgun") + end +end + +function SWEP:GetJamChance(base) + + local valfunc = base and self.GetBaseValue or self.GetValue + local factor = valfunc(self, "JamFactor") + if factor <= 0 or !TacRP.ConVars["can_jam"]:GetBool() then return 0 end + + local default = TacRP.AmmoJamMSB[self:GetAmmoType(base)] or 15 + local msb = (valfunc(self, "JamBaseMSB") or default) / math.sqrt(factor) + + return 1 / msb +end + +function SWEP:GetRPM(base, fm) + fm = fm or self:GetCurrentFiremode() + local valfunc = base and self.GetBaseValue or self.GetValue + local rpm = valfunc(self, "RPM") + if fm == 1 then + rpm = rpm * valfunc(self, "RPMMultSemi") + elseif fm < 0 then + rpm = rpm * valfunc(self, "RPMMultBurst") + end + return rpm +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_sprint.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_sprint.lua new file mode 100644 index 0000000..eb4b5f1 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_sprint.lua @@ -0,0 +1,143 @@ +function SWEP:GetIsSprinting() + local owner = self:GetOwner() + + if !owner:IsValid() or owner:IsNPC() or owner:IsNextBot() then + return false + end + + if self:CanShootInSprint() then return false end + + local walkspeed = owner:GetWalkSpeed() + local runspeed = owner:GetRunSpeed() + + if owner.TacRP_SprintBlock then return false end + if owner:GetNWBool("TacRPChargeState", false) then return true end + if owner:GetNWBool("SlidingAbilityIsSliding", false) then return false end + + if TTT2 then + if SPRINT and SPRINT:IsSprinting(owner) then + return true + else + return owner.isSprinting == true and (owner.sprintProgress or 0) > 0 and owner:KeyDown(IN_SPEED) and !owner:Crouching() and owner:OnGround() + end + end + + -- TTT sets runspeed to curspeed, so this will disable it unless sprint addons exist (who ideally sets runspeed. i didn't check) + if runspeed <= walkspeed then return false end + + if !owner.TacRP_Moving then return false end -- Don't check IN_ move keys because 1) controllers and 2) bots + if !owner:KeyDown(IN_SPEED) then return false end -- SetButtons does not seem to affect this? + local curspeed = owner:GetVelocity():Length() + if curspeed <= 0 then return false end + if !owner:OnGround() then return false end + + if self:GetOwner():GetInfoNum("tacrp_aim_cancels_sprint", 0) > 0 and self:GetScopeLevel() > 0 then return false end + + return true +end + +function SWEP:ShouldLowerWeapon() + local owner = self:GetOwner() + + if !owner:IsValid() or owner:IsNPC() or owner:IsNextBot() then + return false + end + + if TacRP.ConVars["sprint_counts_midair"]:GetBool() and owner:GetMoveType() != MOVETYPE_NOCLIP and !owner:IsOnGround() then + return true + end + + if self:DoForceSightsBehavior() and self:GetScopeLevel() == 0 and !self:GetInBipod() and self:GetBlindFireMode() == TacRP.BLINDFIRE_NONE then + return true + end + + return false +end + +function SWEP:CanStopSprinting() + local owner = self:GetOwner() + if !owner:IsValid() or owner:IsNPC() or owner:IsNextBot() then + return false + end + + if TacRP.ConVars["sprint_counts_midair"]:GetBool() and owner:GetMoveType() != MOVETYPE_NOCLIP and !owner:OnGround() and !self:GetReloading() then + return false + end + + if owner:GetNWBool("TacRPChargeState", false) then + return false + end + + return true +end + +function SWEP:GetSprintDelta() + return self:GetSprintAmount() +end + +function SWEP:EnterSprint() + if !self:CanShootInSprint() then + self:ToggleBlindFire(TacRP.BLINDFIRE_NONE) + end + if !self:CanReloadInSprint() and self:GetReloading() then + -- use clip1 to check for whether the up-in has happened. if so, do not cancel (can't have you cancel the animation *that* easily) + -- this causes fringe cases related to maniuplating magazine sizes but shouldn't be abusable + if self:Clip1() < self:GetMaxClip1() then + self:CancelReload(true) + -- self:Idle() + end + end + self:ScopeToggle(0) + + self:SetShouldHoldType() +end + +function SWEP:ExitSprint() + local amt = self:GetSprintAmount() + self:SetSprintLockTime(CurTime() + (self:GetValue("SprintToFireTime") * amt)) + + self:SetShouldHoldType() +end + +SWEP.LastWasSprinting = false + +function SWEP:ThinkSprint() + local sprinting = self:GetIsSprinting() or self:GetSafe() + + local amt = self:GetSprintAmount() + + if self.LastWasSprinting and !sprinting then + self:ExitSprint() + elseif !self.LastWasSprinting and sprinting then + self:EnterSprint() + end + + self.LastWasSprinting = sprinting + + if IsFirstTimePredicted() or game.SinglePlayer() then + if (sprinting or (self:ShouldLowerWeapon() and !self:DoForceSightsBehavior())) and !self:GetInBipod() then + amt = math.Approach(amt, 1, FrameTime() / self:GetValue("SprintToFireTime")) + else + amt = math.Approach(amt, 0, FrameTime() / self:GetValue("SprintToFireTime")) + end + end + + self:SetSprintAmount(amt) +end + +function SWEP:CanShootInSprint(base) + if !TacRP.ConVars["sprint_lower"]:GetBool() and !self:DoForceSightsBehavior() then return true end + if base then + return self:GetBaseValue("ShootWhileSprint") + else + return self:GetValue("ShootWhileSprint") + end +end + +function SWEP:CanReloadInSprint(base) + return TacRP.ConVars["sprint_reload"]:GetBool() +end + +function SWEP:DoForceSightsBehavior() + return TacRP.ConVars["sightsonly"]:GetBool() and self:GetValue("Scope") +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_stats.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_stats.lua new file mode 100644 index 0000000..bff69ff --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_stats.lua @@ -0,0 +1,239 @@ +SWEP.StatCache = {} +SWEP.HookCache = {} +SWEP.StatScoreCache = {} -- used by cust menu +SWEP.MiscCache = {} + +SWEP.ExcludeFromRawStats = { + ["FullName"] = true, + ["PrintName"] = true, + ["Description"] = true, +} + +SWEP.IntegerStats = { + ["ClipSize"] = true, + ["Num"] = true, +} + +SWEP.AllowNegativeStats = { + ["RecoilKick"] = true, +} + +function SWEP:InvalidateCache() + self.StatCache = {} + self.HookCache = {} + self.StatScoreCache = {} + self.MiscCache = {} + self.RecoilPatternCache = {} + + self.AutoSightPos = nil + self.AutoSightAng = nil +end + +function SWEP:RunHook(val, data) + if !self.HookCache[val] then + self.HookCache[val] = {} + + if self:GetTable()[val] then + table.insert(self.HookCache[val], self:GetTable()[val]) + end + + for slot, slottbl in pairs(self.Attachments) do + if !slottbl.Installed then continue end + + local atttbl = TacRP.GetAttTable(slottbl.Installed) + + if atttbl[val] then + table.insert(self.HookCache[val], atttbl[val]) + end + end + end + + for _, chook in pairs(self.HookCache[val]) do + local d = chook(self, data) + if d != nil then + data = d + end + end + + data = hook.Run("TacRP_" .. val, self, data) or data + + return data +end + +function SWEP:GetBaseValue(val) + local stat = self:GetTable()[val] + + local b = TacRP.GetBalanceMode() + if b > 0 and self.BalanceStats != nil then + if TacRP.BalanceDefaults[b] and TacRP.BalanceDefaults[b][val] != nil then + stat = TacRP.BalanceDefaults[b][val] + end + for j = b, 1, -1 do + if self.BalanceStats[b] and self.BalanceStats[b][val] != nil then + stat = self.BalanceStats[b][val] + break + end + end + end + + if isnumber(stat) then + if self.IntegerStats[val] then + stat = math.ceil(stat) + end + if !self.AllowNegativeStats[val] then + stat = math.max(stat, 0) + end + end + + return stat +end + +function SWEP:GetValue(val, static, invert) + + local cachei = invert and 2 or 1 + + if static == nil then + static = self.StaticStats + end + + local stat = nil + + -- Generate a cache if it doesn't exist already + if !self.StatCache[val] or !self.StatCache[val][cachei] then + + self.StatCache[val] = self.StatCache[val] or {} + + stat = self:GetBaseValue(val) + + local modifiers = { + ["stat"] = nil, -- return this unless hook is set + ["hook"] = nil, -- if set, always call hook and use the following values + ["func"] = {}, -- modifying functions + ["set"] = stat, -- override and no prefix + ["prio"] = 0, -- override priority + ["add"] = 0, + ["mul"] = 1, + } + + -- local priority = 0 + + if !self.ExcludeFromRawStats[val] then + for slot, slottbl in pairs(self.Attachments) do + if !slottbl.Installed then continue end + + local atttbl = TacRP.GetAttTable(slottbl.Installed) + + local att_priority = atttbl["Priority_" .. val] or 1 + + if atttbl[val] != nil and att_priority > modifiers.prio then + -- stat = atttbl[val] + -- priority = att_priority + modifiers.set = atttbl[val] + modifiers.prio = att_priority + end + end + end + + for slot, slottbl in pairs(self.Attachments) do + if !slottbl.Installed then continue end + + local atttbl = TacRP.GetAttTable(slottbl.Installed) + + local att_priority = atttbl["Override_Priority_" .. val] or 1 + + if atttbl["Override_" .. val] != nil and att_priority > modifiers.prio then + -- stat = atttbl["Override_" .. val] + -- priority = att_priority + modifiers.set = atttbl["Override_" .. val] + modifiers.prio = att_priority + end + + if atttbl["Add_" .. val] then -- isnumber(stat) and + -- stat = stat + atttbl["Add_" .. val] * (invert and -1 or 1) + modifiers.add = modifiers.add + atttbl["Add_" .. val] * (invert and -1 or 1) + end + + if atttbl["Mult_" .. val] then -- isnumber(stat) and + if invert then + -- stat = stat / atttbl["Mult_" .. val] + modifiers.mul = modifiers.mul / atttbl["Mult_" .. val] + else + -- stat = stat * atttbl["Mult_" .. val] + modifiers.mul = modifiers.mul * atttbl["Mult_" .. val] + end + end + + if atttbl["Func_" .. val] then + table.insert(modifiers.func, atttbl["Func_" .. val]) + end + end + + if isfunction(self["Func_" .. val]) then + table.insert(modifiers.func, self["Func_" .. val]) + end + + -- Check for stat hooks. If any exist, we must call it whenever we try to get the stat. + -- Cache this check so we don't unnecessarily call hook.Run a million times when nobody wants to hook us. + if table.Count(hook.GetTable()["TacRP_Stat_" .. val] or {}) > 0 then + modifiers.hook = true + end + + -- Calculate the final value + if isnumber(modifiers.set) then + modifiers.stat = (modifiers.set + modifiers.add) * modifiers.mul + if self.IntegerStats[val] then + modifiers.stat = math.ceil(modifiers.stat) + end + if !self.AllowNegativeStats[val] then + modifiers.stat = math.max(modifiers.stat, 0) + end + else + modifiers.stat = modifiers.set + end + + -- Cache our final value, presence of hooks, and summed modifiers + self.StatCache[val][cachei] = modifiers + end + + local cache = self.StatCache[val][cachei] + if !static and (cache.hook or #cache.func > 0) then + -- Run the hook + -- Hooks are expected to modify "set", "prio", "add" and "mul", so we can do all calculations in the right order. + local modifiers = {set = nil, prio = 0, add = 0, mul = 1} + + if #cache.func > 0 then + for _, f in ipairs(cache.func) do + f(self, modifiers) + end + end + if cache.hook then + hook.Run("TacRP_Stat_" .. val, self, modifiers) + if !istable(modifiers) then modifiers = {set = nil, prio = 0, add = 0, mul = 1} end -- some hook isn't cooperating! + end + + if modifiers.prio > cache.prio then + stat = modifiers.set + else + stat = cache.set + end + + if isnumber(stat) then + if invert then + stat = (stat - modifiers.add - cache.add) / modifiers.mul / cache.mul + else + stat = (stat + modifiers.add + cache.add) * modifiers.mul * cache.mul + end + + if self.IntegerStats[val] then + stat = math.ceil(stat) + end + if !self.AllowNegativeStats[val] then + stat = math.max(stat, 0) + end + end + else + stat = cache.stat + end + + return stat +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_sway.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_sway.lua new file mode 100644 index 0000000..4b68019 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_sway.lua @@ -0,0 +1,134 @@ +function SWEP:GetSwayAmount(pure) + if self:GetOwner():IsNPC() then return 0 end + + local sway = self:GetValue("Sway") + + local d = self:GetSightDelta() - (self:GetPeeking() and self:GetValue("PeekPenaltyFraction") or 0) + sway = Lerp(d, sway, self:GetValue("ScopedSway")) + + if self:GetBreath() < 1 then + sway = sway + (1 * (1 - self:GetBreath()) * (self:GetOutOfBreath() and 1 or 0.5)) + end + sway = Lerp(self:GetHoldBreathAmount() ^ 0.75, sway, 0) + + if self:GetBlindFire() then + sway = sway + self:GetValue("BlindFireSway") + end + + if self:GetOwner():Crouching() and !(self:GetOwner():KeyDown(IN_FORWARD) or self:GetOwner():KeyDown(IN_MOVELEFT) or self:GetOwner():KeyDown(IN_MOVERIGHT) or self:GetOwner():KeyDown(IN_BACK)) then + sway = sway * self:GetValue("SwayCrouchMult") + end + + if !pure then + sway = sway + self:GetForcedSwayAmount() + end + + if self:GetValue("Bipod") then + local f = self:Curve(math.Clamp((CurTime() - self.LastBipodTime) / 0.15, 0, 1)) + if self:GetInBipod() then + sway = Lerp(f, sway, 0) + else + sway = Lerp(f, 0, sway) + end + end + + return sway +end + +function SWEP:GetForcedSwayAmount() + local sway = 0 + + if self:GetOwner():GetNWFloat("TacRPGasEnd", 0) > CurTime() then + sway = sway + TacRP.ConVars["gas_sway"]:GetFloat() * Lerp(self:GetSightAmount(), 1, 0.25) * math.Clamp((self:GetOwner():GetNWFloat("TacRPGasEnd") - CurTime()) / 2, 0, 1) + end + + sway = sway + (hook.Run("TacRP_GetForcedSway", self, self:GetOwner()) or 0) + + return sway +end + +function SWEP:GetSwayAngles() + local swayamt = self:IsSwayEnabled() and self:GetSwayAmount() or self:GetForcedSwayAmount() + local swayspeed = 1 + + if swayamt <= 0 then return Angle(0, 0, 0) end + + local ct = CLIENT and UnPredictedCurTime() or CurTime() + + local ang = Angle(math.sin(ct * 0.6 * swayspeed) + (math.cos(ct * 2) * 0.5), math.sin(ct * 0.4 * swayspeed) + (math.cos(ct * 1.6) * 0.5), 0) + + ang = ang * swayamt + + return ang +end + +function SWEP:IsSwayEnabled() + return TacRP.ConVars["sway"]:GetBool() +end + +function SWEP:ThinkHoldBreath() + local owner = self:GetOwner() + if !owner:IsPlayer() then return end + + local ft = FrameTime() + + if self:HoldingBreath() then + self:SetBreath(self:GetBreath() - ft * (self:GetBreathDrain() * (self:HasOptic() and 1 or 0.75) * (self:GetRecoilAmount() > 0 and 1.5 or 1))) + + if self:GetBreath() <= 0 then + self:SetOutOfBreath(true) + self:SetHoldingBreath(false) + end + + if self:GetHoldBreathAmount() < 1 then + self:SetHoldBreathAmount(math.min(1, self:GetHoldBreathAmount() + ft * self:GetBreathSpeed())) + end + else + if self:GetHoldBreathAmount() > 0 then + self:SetHoldBreathAmount(math.max(0, self:GetHoldBreathAmount() - ft * self:GetBreathSpeed() * 2)) + end + + if self:GetOutOfBreath() and self:GetBreath() >= 1 then + self:SetOutOfBreath(false) + end + + self:SetBreath(math.min(1, self:GetBreath() + ft * self:GetValue("BreathRecovery") * (self:GetOutOfBreath() and 0.2 or 0.25))) + end +end + +function SWEP:CanHoldBreath() + return self:GetValue("Scope") and TacRP.ConVars["sway"]:GetBool() and self:GetScopeLevel() > 0 and !self:GetReloading() +end + +function SWEP:NotOutOfBreath() + return self:GetBreath() > 0 and !self:GetOutOfBreath() +end + +function SWEP:HoldingBreath() + local holding = self:GetOwner():KeyDown(IN_SPEED) or self:GetOwner():KeyDown(IN_RUN) + if self:GetOwner():GetInfoNum("tacrp_toggleholdbreath", 0) == 1 then + if self:GetOwner():KeyPressed(IN_SPEED) or self:GetOwner():KeyPressed(IN_RUN) then + self:SetHoldingBreath(!self:GetHoldingBreath()) + end + else + self:SetHoldingBreath(holding) + end + + lastpressed = holding + + return self:CanHoldBreath() and self:GetSightAmount() >= 1 and self:NotOutOfBreath() and self:GetHoldingBreath() +end + +function SWEP:GetBreathDrain() + if self.MiscCache["breath_cost"] == nil then + self.MiscCache["breath_cost"] = (math.Clamp(self:GetValue("ScopedSway"), 0.1, 0.3) ^ 0.75) * (1 - 0.3 * math.Clamp((4 - 90 / self:GetValue("ScopeFOV")) / 3, 0, 1)) + end + return self.MiscCache["breath_cost"] * self:GetValue("BreathDrain") +end + +function SWEP:GetBreathSpeed() + if self.MiscCache["breath_rate"] == nil then + self.MiscCache["breath_rate"] = (math.Clamp(self:GetValue("ScopedSway"), 0.1, 0.5) ^ 0.5) / 0.3 + (0.5 * math.Clamp((4 - 90 / self:GetValue("ScopeFOV")) / 3, 0, 1)) + end + return self.MiscCache["breath_rate"] +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_think.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_think.lua new file mode 100644 index 0000000..a13e6c7 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_think.lua @@ -0,0 +1,146 @@ +function SWEP:Think() + local owner = self:GetOwner() + + local stop = self:RunHook("Hook_PreThink") + if stop then return end + + local cfm = self:GetCurrentFiremode() + if self:GetValue("RunawayBurst") and cfm < 0 then + if self:GetBurstCount() >= -cfm then + self:SetBurstCount(0) + self:SetNextPrimaryFire(CurTime() + self:GetValue("PostBurstDelay")) + elseif self:GetBurstCount() > 0 and self:GetBurstCount() < -cfm then + self:PrimaryAttack() + end + else + if (owner:KeyReleased(IN_ATTACK) or (cfm < 0 and self:GetBurstCount() >= -cfm)) then + if cfm < 0 and self:GetBurstCount() > 1 then + if !self:GetValue("AutoBurst") then + self.Primary.Automatic = false + end + local add = 0 + if self:GetBurstCount() >= -cfm then + add = 60 / self:GetValue("RPM") + end + self:SetNextPrimaryFire(CurTime() + self:GetValue("PostBurstDelay") + add) + end + self:SetBurstCount(0) + end + end + + if self:GetPatternCount() > 0 and (self:GetRecoilAmount() == 0 or self:GetReloading()) then + self:SetPatternCount(0) + end + + if owner:KeyPressed(TacRP.IN_CUSTOMIZE) then + if self:GetScopeLevel() == 0 then + self:ToggleCustomize(!self:GetCustomize()) + else + if !self:GetValue("AlwaysPeek") then + local shouldpeek = !self:GetPeeking() + + if owner:GetInfoNum("tacrp_togglepeek", 0) == 0 then + shouldpeek = true + end + + self:SetPeeking(shouldpeek) + + if self:GetSightAmount() > 0 then + self:SetLastScopeTime(CurTime()) + end + end + end + elseif !self:GetValue("AlwaysPeek") and owner:GetInfoNum("tacrp_togglepeek", 0) == 0 and self:GetPeeking() and owner:KeyReleased(TacRP.IN_CUSTOMIZE) then + self:SetPeeking(false) + + if self:GetSightAmount() > 0 then + self:SetLastScopeTime(CurTime()) + end + end + + if owner:KeyPressed(TacRP.IN_TACTICAL) then + self:ToggleTactical() + end + + self:ThinkRecoil() + + self:ThinkSprint() + + self:ThinkGrenade() + + self:ThinkReload() + + self:ThinkSights() + + self:ThinkFreeAim() + + self:ThinkBlindFire() + + self:ProcessTimers() + + self:ThinkLockOn() + + self:ThinkHoldBreath() + + if self:GetValue("Melee") and self:GetOwner():KeyPressed(TacRP.IN_MELEE) then + self:Melee() + return + end + + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() and self:GetValue("TacticalThink") and self:GetTactical() then + self:GetValue("TacticalThink")(self) + end + + -- if IsValid(self:GetOwner()) and self:GetTactical() and self:GetValue("Minimap") and (SERVER and !game.SinglePlayer()) and (self.NextRadarBeep or 0) < CurTime() then + -- self.NextRadarBeep = CurTime() + 1.5 + -- local f = RecipientFilter() + -- f:AddPVS(self:GetPos()) + -- f:RemovePlayer(self:GetOwner()) + -- local s = CreateSound(self, "plats/elevbell1.wav", f) + -- s:SetSoundLevel(75) + -- s:PlayEx(0.25, 105) + -- end + + if self:GetJammed() and !self:StillWaiting() and TacRP.ConVars["jam_autoclear"]:GetBool() then + local t = self:PlayAnimation("jam", 0.75, true, true) + self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_CANCEL_RELOAD, t * 1000) + self:SetJammed(false) + end + + if self:GetNextIdle() < CurTime() and (SERVER or !game.SinglePlayer()) then + self:Idle() + end + + if self:GetValue("Bipod") and self:GetInBipod() and !self:CanBipod() then + self:ExitBipod() + end + + if CLIENT and (IsFirstTimePredicted() or game.SinglePlayer()) then + + self:ThinkNearWall() + + if IsValid(self.MuzzleLight) then + if (self.MuzzleLightEnd or 0) < UnPredictedCurTime() then + self.MuzzleLight:Remove() + self.MuzzleLight = nil + else + self.MuzzleLight:SetBrightness( math.Remap( UnPredictedCurTime(), self.MuzzleLightStart, self.MuzzleLightEnd, self.MuzzleLightBrightness, 0 ) ) + self.MuzzleLight:SetFOV( math.Remap( UnPredictedCurTime(), self.MuzzleLightStart, self.MuzzleLightEnd, self.MuzzleLightFOV, 60 ) ) + self.MuzzleLight:Update() + end + end + + if !self.LoadedPreset then + self.LoadedPreset = true + + if TacRP.ConVars["autosave"]:GetBool() and TacRP.ConVars["free_atts"]:GetBool() then + self:LoadPreset() + -- self:DoDeployAnimation() + end + end + + self:CanBipod() + end + + self:RunHook("Hook_PostThink") +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_timers.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_timers.lua new file mode 100644 index 0000000..57a0e58 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_timers.lua @@ -0,0 +1,51 @@ +local tick = 0 + +function SWEP:InitTimers() + self.ActiveTimers = {} -- { { time, id, func } } +end + +function SWEP:SetTimer(time, callback, id) + if !IsFirstTimePredicted() then return end + + table.insert(self.ActiveTimers, { time + CurTime(), id or "", callback }) +end + +function SWEP:TimerExists(id) + for _, v in pairs(self.ActiveTimers) do + if v[2] == id then return true end + end + + return false +end + +function SWEP:KillTimer(id) + local keeptimers = {} + + for _, v in pairs(self.ActiveTimers) do + if v[2] != id then table.insert(keeptimers, v) end + end + + self.ActiveTimers = keeptimers +end + +function SWEP:KillTimers() + self.ActiveTimers = {} +end + +function SWEP:ProcessTimers() + local keeptimers, UCT = {}, CurTime() + + if CLIENT and UCT == tick then return end + + if !self.ActiveTimers then self:InitTimers() end + + for _, v in pairs(self.ActiveTimers) do + if v[1] <= UCT then v[3]() end + end + + for _, v in pairs(self.ActiveTimers) do + if v[1] > UCT then table.insert(keeptimers, v) end + end + + self.ActiveTimers = keeptimers +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_ttt.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_ttt.lua new file mode 100644 index 0000000..b2b56f1 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_ttt.lua @@ -0,0 +1,146 @@ +function SWEP:OnRestore() +end + +function SWEP:GetHeadshotMultiplier(victim, dmginfo) + return 1 -- Hey hey hey, don't forget about me!!! +end + +function SWEP:IsEquipment() + return WEPS.IsEquipment(self) +end + +SWEP.IsSilent = false + +-- The OnDrop() hook is useless for this as it happens AFTER the drop. OwnerChange +-- does not occur when a drop happens for some reason. Hence this thing. +function SWEP:PreDrop() + if SERVER and IsValid(self:GetOwner()) and self.Primary.Ammo != "none" and self.Primary.Ammo != "" then + local ammo = self:Ammo1() + + -- Do not drop ammo if we have another gun that uses this type + for _, w in ipairs(self:GetOwner():GetWeapons()) do + if IsValid(w) and w != self and w:GetPrimaryAmmoType() == self:GetPrimaryAmmoType() then + ammo = 0 + end + end + + self.StoredAmmo = ammo + + if ammo > 0 then + self:GetOwner():RemoveAmmo(ammo, self.Primary.Ammo) + end + end +end + +function SWEP:DampenDrop() + -- For some reason gmod drops guns on death at a speed of 400 units, which + -- catapults them away from the body. Here we want people to actually be able + -- to find a given corpse's weapon, so we override the velocity here and call + -- this when dropping guns on death. + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocityInstantaneous(Vector(0,0,-75) + phys:GetVelocity() * 0.001) + phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.99) + end +end + +SWEP.StoredAmmo = 0 + +-- Picked up by player. Transfer of stored ammo and such. + +function SWEP:Equip(newowner) + if SERVER then + if self:IsOnFire() then + self:Extinguish() + end + + self.fingerprints = self.fingerprints or {} + + if !table.HasValue(self.fingerprints, newowner) then + table.insert(self.fingerprints, newowner) + end + + if newowner:GetActiveWeapon() != self and self:GetValue("HolsterVisible") then + net.Start("TacRP_updateholster") + net.WriteEntity(self:GetOwner()) + net.WriteEntity(self) + net.Broadcast() + end + end + + if SERVER and IsValid(newowner) and self.Primary.ClipMax and self.StoredAmmo > 0 and self.Primary.Ammo != "none" and self.Primary.Ammo != "" then + local ammo = newowner:GetAmmoCount(self.Primary.Ammo) + local given = math.min(self.StoredAmmo, self.Primary.ClipMax - ammo) + + newowner:GiveAmmo(given, self.Primary.Ammo) + self.StoredAmmo = 0 + end + + self:SetHolsterTime(0) + self:SetHolsterEntity(NULL) + self:SetReloadFinishTime(0) +end + +-- other guns may use this function to setup stuff +function SWEP:TTTBought(buyer) +end + +function SWEP:WasBought(buyer) + if buyer:GetActiveWeapon() != self and self:GetValue("HolsterVisible") then + net.Start("TacRP_updateholster") + net.WriteEntity(self:GetOwner()) + net.WriteEntity(self) + net.Broadcast() + end + + self:TTTBought(buyer) +end + +function SWEP:TTT_PostAttachments() +end + +function SWEP:TTT_Init() + if engine.ActiveGamemode() != "terrortown" then return end + + if SERVER then + self.fingerprints = {} + + + local att_chance = TacRP.ConVars["ttt_atts_random"]:GetFloat() + local att_max = TacRP.ConVars["ttt_atts_max"]:GetFloat() + local added = 0 + + if att_chance > 0 then + for i, slot in pairs(self.Attachments) do + if math.random() > att_chance then continue end + + local atts = TacRP.GetAttsForCats(slot.Category or "", self) + local ind = math.random(1, #atts) + slot.Installed = atts[ind] + added = added + 1 + + if att_max > 0 and added >= att_max then break end + end + + self:InvalidateCache() + self:SetBaseSettings() + + if added > 0 then + self:NetworkWeapon() + end + end + end + + if self.PrimaryGrenade then + self.Primary.ClipMax = 1 + return + end + + self.Primary.ClipMax = TacRP.TTTAmmoToClipMax[string.lower(self.AmmoTTT or self.Ammo)] or self.Primary.ClipSize * 2 + self:SetClip1(self.Primary.ClipSize) + self.GaveDefaultAmmo = true +end + +--- TTT2 uses this to populate custom convars in the equip menu +function SWEP:AddToSettingsMenu(parent) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_util.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_util.lua new file mode 100644 index 0000000..a0104ec --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_util.lua @@ -0,0 +1,91 @@ +function SWEP:SanityCheck() + if !IsValid(self) then return false end + if !IsValid(self:GetOwner()) then return false end + if !IsValid(self:GetVM()) then return false end +end + +function SWEP:GetVM() + if !IsValid(self:GetOwner()) or !self:GetOwner():IsPlayer() then return end + return self:GetOwner():GetViewModel() +end + +function SWEP:Curve(x) + return 0.5 * math.cos((x + 1) * math.pi) + 0.5 +end + +SWEP.LastSysTime = SysTime() +function SWEP:DeltaSysTime() + local ret = (SysTime() - (self.LastSysTime or SysTime())) * GetConVar("host_timescale"):GetFloat() + return ret +end + +function SWEP:IsAnimLocked() + return self:GetAnimLockTime() > CurTime() +end + +function SWEP:ChooseSound(tbl) + if !istable(tbl) then return tbl end + tbl.BaseClass = nil -- lua tables lel + return tbl[math.random(1, #tbl)] +end + +function SWEP:OnReloaded() + self:InvalidateCache() + self:SetBaseSettings() + + hook.Run("TacRP_WeaponReloaded", self) +end + +function SWEP:DoLowerIrons() + if self:GetValue("Holosight") or self:GetValue("ScopeOverlay") then return false end + local i = TacRP.ConVars["irons_lower"]:GetInt() + return i == 2 or (i == 1 and engine.ActiveGamemode() == "terrortown") +end + +function SWEP:DoProceduralIrons() + local i = TacRP.ConVars["irons_procedural"]:GetInt() + return self:GetValue("ProceduralIronFire") and (i == 2 or (i == 1 and self:GetValue("Holosight"))) --and (!self.LastShot or self:Clip1() > 1) +end + +function SWEP:CountAttachments() + local count = 0 + for k, v in ipairs(self.Attachments) do + if v.Installed then count = count + 1 end + end + return count +end + +function SWEP:GetMaxClip1() + return self:GetCapacity() +end + +function SWEP:IsDamageConstant(base) + local valfunc = base and self.GetBaseValue or self.GetValue + return valfunc(self, "Damage_Min") == valfunc(self, "Damage_Max") +end + +function SWEP:GetPingOffsetScale() + if game.SinglePlayer() then return 0 end + + return (self:GetOwner():Ping() - 5) / 1000 +end + +function SWEP:ScaleFOVByWidthRatio(fovDegrees, ratio) + local halfAngleRadians = fovDegrees * (0.5 * math.pi / 180) + local t = math.tan(halfAngleRadians) + t = t * ratio + local retDegrees = (180 / math.pi) * math.atan(t) + + return retDegrees * 2 +end + +function SWEP:WidescreenFix(target) + return self:ScaleFOVByWidthRatio(target, ((ScrW and ScrW() or 4) / (ScrH and ScrH() or 3)) / (4 / 3)) +end + +function SWEP:UseAltRecoil(base) + local valfunc = base and self.GetBaseValue or self.GetValue + if valfunc(self, "AlwaysAltRecoil") then return true end + if valfunc(self, "NeverAltRecoil") then return false end + return TacRP.ConVars["altrecoil"]:GetBool() +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_vm.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_vm.lua new file mode 100644 index 0000000..0a4cdc3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_vm.lua @@ -0,0 +1,356 @@ +local customizedelta = 0 +local sightdelta = 0 +local sprintdelta = 0 +local peekdelta = 0 +local bipoddelta = 0 +local blindfiredelta, blindfiredeltaleft, blindfiredeltaright, blindfiredeltakys = 0, 0, 0, 0 +local freeaim_p, freeaim_y = 0, 0 +local nearwalldelta = 0 + +local angle_zero = Angle(0, 0, 0) +local vector_origin = Vector(0, 0, 0) + +local peekvector = Vector(0, 0, -2) + +local m_appor = math.Approach +local f_lerp = function(dlt, from, to) return from + (to - from) * dlt end +local function ApproachMod(usrobj, to, dlt) + usrobj[1] = m_appor(usrobj[1], to[1], dlt) + usrobj[2] = m_appor(usrobj[2], to[2], dlt) + usrobj[3] = m_appor(usrobj[3], to[3], dlt) +end + +local function LerpMod(usrobj, to, dlt, clamp_ang) + usrobj[1] = f_lerp(dlt, usrobj[1], to[1]) + usrobj[2] = f_lerp(dlt, usrobj[2], to[2]) + usrobj[3] = f_lerp(dlt, usrobj[3], to[3]) + if clamp_ang then + for i = 1, 3 do usrobj[i] = math.NormalizeAngle(usrobj[i]) end + end +end + +SWEP.BenchPos = SWEP.BenchPos or Vector(0, 0, 0) +SWEP.BenchAng = SWEP.BenchAng or Angle(0, 0, 0) + +SWEP.ViewModelPos = Vector(0, 0, 0) +SWEP.ViewModelAng = Angle(0, 0, 0) + +function SWEP:GetViewModelPosition(pos, ang) + if !IsValid(self:GetOwner()) then + return Vector(0, 0, 0), Angle(0, 0, 0) + end + + if TacRP.ConVars["dev_benchgun"]:GetBool() then + return self.BenchPos, self.BenchAng + end + self.BenchPos = pos + self.BenchAng = ang + + local vm = self:GetOwner():GetViewModel() + local FT = self:DeltaSysTime() -- FrameTime() + local RFT = RealFrameTime() + + ang = ang - (self:GetOwner():GetViewPunchAngles() * 0.5) + + local oldang = Angle(0, 0, 0) + + oldang:Set(ang) + + local offsetpos = Vector(self.PassivePos) + local offsetang = Angle(self.PassiveAng) + + local extra_offsetpos = Vector(0, 0, 0) + local extra_offsetang = Angle(0, 0, 0) + + -- local cor_val = (self.ViewModelFOV / self:GetShouldFOV()) + local cor_val = 0.75 + + --------------------------------------------- + -- Blindfire + --------------------------------------------- + local bfmode = self:GetBlindFireMode() + local bfl = bfmode == TacRP.BLINDFIRE_LEFT + local bfr = bfmode == TacRP.BLINDFIRE_RIGHT + local bfs = bfmode == TacRP.BLINDFIRE_KYS + blindfiredelta = math.Approach(blindfiredelta, self:GetBlindFire() and 1 or 0, FT / 0.3) + blindfiredeltaleft = math.Approach(blindfiredeltaleft, bfl and 1 or 0, FT / (bfr and 0.45 or 0.3)) + blindfiredeltaright = math.Approach(blindfiredeltaright, bfr and 1 or 0, FT / (bfl and 0.45 or 0.3)) + blindfiredeltakys = math.Approach(blindfiredeltakys, bfs and 1 or 0, FT / (bfs and 0.75 or 0.3)) + if blindfiredelta > 0 then + local curvedblindfiredelta = self:Curve(blindfiredelta) + local curvedblindfiredeltaleft = self:Curve(blindfiredeltaleft) + local curvedblindfiredeltaright = self:Curve(blindfiredeltaright) + local curvedblindfiredeltakys = self:Curve(blindfiredeltakys) + + offsetpos = LerpVector(curvedblindfiredelta, offsetpos, self:GetValue("BlindFirePos")) + offsetang = LerpAngle(curvedblindfiredelta, offsetang, self:GetValue("BlindFireAng")) + + if curvedblindfiredeltaleft > 0 then + offsetpos = LerpVector(curvedblindfiredeltaleft, offsetpos, self:GetValue("BlindFireLeftPos")) + offsetang = LerpAngle(curvedblindfiredeltaleft, offsetang, self:GetValue("BlindFireLeftAng")) + end + + if curvedblindfiredeltaright > 0 then + offsetpos = LerpVector(curvedblindfiredeltaright, offsetpos, self:GetValue("BlindFireRightPos")) + offsetang = LerpAngle(curvedblindfiredeltaright, offsetang, self:GetValue("BlindFireRightAng")) + end + + if curvedblindfiredeltakys > 0 then + offsetpos = LerpVector(curvedblindfiredeltakys, offsetpos, self:GetValue("BlindFireSuicidePos")) + offsetang = LerpAngle(curvedblindfiredeltakys, offsetang, self:GetValue("BlindFireSuicideAng")) + end + end + + --------------------------------------------- + -- Aiming & Peeking + --------------------------------------------- + local ads = self:GetAimDownSightsTime() + if self:GetScopeLevel() > 0 then + if self:GetSprintLockTime() > CurTime() then + ads = ads + self:GetSprintToFireTime() + end + sightdelta = m_appor(sightdelta, 1, FT / ads) + else + sightdelta = m_appor(sightdelta, 0, FT / ads) + end + + -- if IsFirstTimePredicted() or game.SinglePlayer() then + if self:GetPeeking() then + peekdelta = m_appor(peekdelta, 1, FT / 0.2) + else + peekdelta = m_appor(peekdelta, 0, FT / 0.2) + end + -- end + + local curvedsightdelta = self:Curve(sightdelta) + local curvedpeekdelta = self:Curve(peekdelta) + + -- cor_val = Lerp(sightdelta, cor_val, 1) + + local ppos = Vector(self:GetValue("PeekPos")) * curvedpeekdelta + local pang = Angle(self:GetValue("PeekAng")) * curvedpeekdelta + + if sightdelta > 0 then + local sightpos, sightang = self:GetSightPositions() + + if self:DoLowerIrons() then + sightpos = sightpos + LerpVector(curvedpeekdelta, peekvector, vector_origin) + end + + LerpMod(offsetpos, sightpos + ppos, curvedsightdelta) + LerpMod(offsetang, sightang + pang, curvedsightdelta, true) + + local eepos, eeang = self:GetExtraSightPosition() + local im = self:GetValue("SightMidPoint") + local midpoint = curvedsightdelta * math.cos(curvedsightdelta * (math.pi / 2)) * (1 - curvedpeekdelta) + local joffset = (im and im.Pos or Vector(0, 0, 0) + ppos) * midpoint + local jaffset = (im and im.Ang or Angle(0, 0, 0) + pang) * midpoint + + LerpMod(extra_offsetpos, -eepos + joffset, curvedsightdelta) + LerpMod(extra_offsetang, -eeang + jaffset, curvedsightdelta) + end + + --------------------------------------------- + -- Bipod + --------------------------------------------- + local amt = math.Clamp(self:GetBipodPos():Distance(self:GetOwner():EyePos()) / 60, 0.15, 0.3) + bipoddelta = math.Approach(bipoddelta, self:GetInBipod() and 1 or 0, FT / (self:GetInBipod() and 0.4 or amt)) + + if bipoddelta > 0 then + local curvedbipoddelta = self:Curve(bipoddelta) + pos = LerpVector(math.Clamp(curvedbipoddelta - curvedsightdelta, 0, 1), pos, self:GetBipodPos()) + end + + --------------------------------------------- + -- Procedural Firing + --------------------------------------------- + local procdata = self:GetValue("ProceduralIronFire") + if IsValid(vm) and procdata then + local dt = math.max(0, UnPredictedCurTime() - self:GetLastProceduralFireTime() + self:GetPingOffsetScale()) + + if dt <= procdata.tmax then + self.ProceduralIronCleanup = false + if !(self:GetValue("LastShot") and self:Clip1() == 0) then + for k, v in pairs(procdata.bones or {}) do + local bone = vm:LookupBone(v.bone or "") + if !bone then continue end + + local f = 1 + if v.t0 == 0 then + f = v.t1 and math.Clamp(1 - dt / v.t1, 0, 1) or 0 + else + f = v.t1 and (dt > v.t0 and math.Clamp(1 - (dt - v.t0) / (v.t1 - v.t0), 0, 1) or (dt / v.t0)) or (dt > v.t0 and 1 or (dt / v.t0)) + end + if v.pos then + local offset = LerpVector(f, vector_origin, v.pos) + vm:ManipulateBonePosition(bone, offset, false) + end + if v.ang then + local offset = LerpAngle(f, angle_zero, v.ang) + vm:ManipulateBoneAngles(bone, offset, false) + end + end + end + + local dtc = math.ease.InQuad(math.Clamp(1 - dt / procdata.t, 0, 1)) + + if dtc > 0 and procdata.vm_pos then + LerpMod(offsetpos, offsetpos + procdata.vm_pos, dtc) + end + if dtc > 0 and procdata.vm_ang then + LerpMod(offsetang, offsetang + procdata.vm_ang, dtc, true) + end + elseif !self.ProceduralIronCleanup then + self.ProceduralIronCleanup = true + for k, v in pairs(procdata.bones or {}) do + local bone = vm:LookupBone(v.bone or "") + if !bone then continue end + if v.pos then + vm:ManipulateBonePosition(bone, vector_origin, false) + end + if v.ang then + vm:ManipulateBoneAngles(bone, angle_zero, false) + end + end + end + end + + --------------------------------------------- + -- Free Aim & Sway + --------------------------------------------- + local swayang = self:GetSwayAngles() + extra_offsetang.y = extra_offsetang.y - (swayang.p * cor_val) + extra_offsetang.p = extra_offsetang.p + (swayang.y * cor_val) + + local idlesway = Lerp(self:GetSightDelta(), 1 / 3, 0) + extra_offsetpos.x = extra_offsetpos.x + (swayang.y * cor_val * idlesway) + extra_offsetpos.z = extra_offsetpos.z + (swayang.p * cor_val * idlesway) + + local freeaimang = self:GetFreeAimOffset() + + freeaim_p = f_lerp(0.5, freeaim_p, freeaimang.p) + freeaim_y = f_lerp(0.5, freeaim_y, freeaimang.y) + freeaim_p = m_appor(freeaim_p, freeaimang.p, FT) + freeaim_y = m_appor(freeaim_y, freeaimang.y, FT) + + extra_offsetang.y = extra_offsetang.y - (freeaim_p * cor_val) + extra_offsetang.p = extra_offsetang.p + (freeaim_y * cor_val) + + --------------------------------------------- + -- Customization + --------------------------------------------- + -- if IsFirstTimePredicted() or game.SinglePlayer() then + if self:GetCustomize() then + customizedelta = m_appor(customizedelta, 1, RealFrameTime() * 1 / 0.15) + else + customizedelta = m_appor(customizedelta, 0, RealFrameTime() * 1 / 0.15) + end + -- end + + if customizedelta > 0 then + local curvedcustomizedelta = self:Curve(customizedelta) + LerpMod(offsetpos, self:GetValue("CustomizePos"), curvedcustomizedelta) + LerpMod(offsetang, self:GetValue("CustomizeAng"), curvedcustomizedelta) + + LerpMod(extra_offsetang, angle_zero, curvedcustomizedelta, true) + end + + --------------------------------------------- + -- Sprinting + --------------------------------------------- + local stf = self:GetSprintToFireTime() + if self:GetCustomize() or self:GetInBipod() or (!self:GetSafe() and !self.LastWasSprinting and !self:ShouldLowerWeapon()) then + -- not accurate to how sprint progress works but looks much smoother + if self:GetScopeLevel() > 0 and self:GetSprintLockTime() > UnPredictedCurTime() then + stf = stf + self:GetAimDownSightsTime() * 0.5 + end + sprintdelta = m_appor(sprintdelta, 0, FT / stf) + self.LastReloadEnd = nil + elseif self:GetReloading() then + if (self.LastWasSprinting or self:ShouldLowerWeapon()) and self:GetEndReload() then + self.LastReloadEnd = self.LastReloadEnd or (self:GetReloadFinishTime() - UnPredictedCurTime()) + sprintdelta = 1 - self:Curve((self:GetReloadFinishTime() - UnPredictedCurTime()) / self.LastReloadEnd) + else + sprintdelta = m_appor(sprintdelta, 0, FT / 0.5) + end + else + if self:GetLastMeleeTime() + 0.5 > CurTime() or self:GetStartPrimedGrenadeTime() + 0.8 > CurTime() then + sprintdelta = m_appor(sprintdelta, 0, FT / 0.2) + else + sprintdelta = m_appor(sprintdelta, 1, FT / stf) + end + self.LastReloadEnd = nil + end + local curvedsprintdelta = self:Curve(sprintdelta) + if curvedsprintdelta > 0 then + LerpMod(offsetpos, self:GetValue("SprintPos"), curvedsprintdelta) + LerpMod(offsetang, self:GetValue("SprintAng"), curvedsprintdelta) + LerpMod(extra_offsetang, angle_zero, curvedsprintdelta, true) + + local sim = self:GetValue("SprintMidPoint") + local spr_midpoint = curvedsprintdelta * math.cos(curvedsprintdelta * (math.pi / 2)) + local spr_joffset = (sim and sim.Pos or Vector(0, 0, 0)) * spr_midpoint + local spr_jaffset = (sim and sim.Ang or Angle(0, 0, 0)) * spr_midpoint + extra_offsetpos:Add(spr_joffset) + extra_offsetang:Add(spr_jaffset) + end + + --------------------------------------------- + -- Near Walling + --------------------------------------------- + nearwalldelta = m_appor(nearwalldelta, self:GetNearWallAmount(), FT / 0.3) + local curvednearwalldelta = self:Curve(nearwalldelta) - customizedelta - sightdelta + if curvednearwalldelta > 0 then + local sprpos = LerpVector(curvednearwalldelta, vector_origin, self:GetValue("NearWallPos")) + local sprang = LerpAngle(curvednearwalldelta, angle_zero, self:GetValue("NearWallAng")) + + local pointdir = self:GetOwner():WorldToLocalAngles(self:GetShootDir()) + + extra_offsetpos:Add(pointdir:Right() * sprpos[2]) + extra_offsetpos:Add(pointdir:Forward() * sprpos[1]) + extra_offsetpos:Add(pointdir:Up() * sprpos[3]) + + extra_offsetang:Add(sprang) + end + + self.SwayScale = f_lerp(sightdelta, 1, 0.1) + self.BobScale = 0 + + local speed = 15 * FT * (game.SinglePlayer() and 1 or 2) + + LerpMod(self.ViewModelPos, offsetpos, speed) + LerpMod(self.ViewModelAng, offsetang, speed, true) + ApproachMod(self.ViewModelPos, offsetpos, speed * 0.1) + ApproachMod(self.ViewModelAng, offsetang, speed * 0.1) + + self.ViewModelAng:Normalize() + + pos = pos + (ang:Right() * offsetpos[1]) + pos = pos + (ang:Forward() * offsetpos[2]) + pos = pos + (ang:Up() * offsetpos[3]) + + ang:RotateAroundAxis(ang:Up(), offsetang[1]) + ang:RotateAroundAxis(ang:Right(), offsetang[2]) + ang:RotateAroundAxis(ang:Forward(), offsetang[3]) + + pos = pos + (oldang:Right() * extra_offsetpos[1]) + pos = pos + (oldang:Forward() * extra_offsetpos[2]) + pos = pos + (oldang:Up() * extra_offsetpos[3]) + + ang:RotateAroundAxis(oldang:Up(), extra_offsetang[1]) + ang:RotateAroundAxis(oldang:Right(), extra_offsetang[2]) + ang:RotateAroundAxis(oldang:Forward(), extra_offsetang[3]) + + pos, ang = self:GetViewModelBob(pos, ang) + pos, ang = self:GetViewModelSway(pos, ang) + + self.ViewModelPos = pos + self.ViewModelAng = ang + + self.LastSysTime = SysTime() + return pos, ang +end + +function SWEP:TranslateFOV(fov) + return fov +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/shared.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/shared.lua new file mode 100644 index 0000000..6c1e291 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/shared.lua @@ -0,0 +1,666 @@ +// spawnable +SWEP.Spawnable = false +SWEP.AdminOnly = false + +// names and stuff +SWEP.PrintName = "Arctic's Tactical RP Base" +SWEP.Category = "[FT] Оружие" + +SWEP.Description = "" +SWEP.Description_Quote = nil // Italics, always on last line. Make sure to save some space for it (may overlap) + +SWEP.Trivia_Caliber = nil +SWEP.Trivia_Manufacturer = nil +SWEP.Trivia_Year = nil // Production Year + +SWEP.Faction = TacRP.FACTION_NEUTRAL // Only used in trivia for now +// Valid values: TacRP.FACTION_NEUTRAL, TacRP.FACTION_COALITION, TacRP.FACTION_MILITIA + +SWEP.Credits = nil // Multiline string like Description + +SWEP.ViewModel = "" +SWEP.WorldModel = "" + +SWEP.ViewModelFOV = 65 + +SWEP.NoRanger = false +SWEP.NoStatBox = false + +SWEP.NPCUsable = true + +SWEP.Slot = 1 + +SWEP.RenderGroup = RENDERGROUP_BOTH + +SWEP.BalanceStats = {} // replacement stats for each TacRP.BALANCE_ enum + +// What weapon this will replace in TTT if enabled. Use TacRP.TTTReplacePreset presets or define your own +SWEP.TTTReplace = nil // {["weapon_ttt_glock"] = 1} // key is weapon to replace, value is relative weight. + +// "ballistics" + +SWEP.Damage_Max = 30 // damage at minimum range +SWEP.Damage_Min = 20 // damage at maximum range +SWEP.Range_Min = 256 // distance for which to maintain maximum damage +SWEP.Range_Max = 1024 // distance at which we drop to minimum damage +SWEP.Penetration = 0 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.5 // How good the weapon can penetrate body armor. +SWEP.ArmorBonus = 1 // multiplier on armor damage + +SWEP.DamageMultNPC = 1 // Damage multiplier against NPCs and Nextbots +SWEP.DamageType = nil // override damage type + +SWEP.ShootEnt = false +SWEP.ShootEntForce = 10000 + +SWEP.Num = 1 +SWEP.TracerNum = 1 +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1.25, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 30000 + +SWEP.ExplosiveEffect = nil +SWEP.ExplosiveDamage = 0 +SWEP.ExplosiveRadius = 0 + +// misc. shooting + +// Firemode system works just like ArcCW +// 2 = full auto +// 1 = semi auto +// 0 = safe +// negative numbers = burst +SWEP.Firemode = 2 + +SWEP.Firemodes = nil // {1, 2, 0} ... + +SWEP.RunawayBurst = false // continue firing until burst is completely expended +SWEP.PostBurstDelay = 0 // only applies to runaway burst guns +SWEP.AutoBurst = false // hold the trigger to keep firing burst after burst + +SWEP.RPM = 600 +SWEP.RPMMultBurst = 1 // modify RPM while in burst mode +SWEP.RPMMultSemi = 1 // modify RPM while in semi mode + +SWEP.Spread = 0.01 + +SWEP.ShootTimeMult = 1 + +SWEP.Bipod = false // Weapon can deploy bipod +SWEP.BipodRecoil = 0.35 // Recoil Amount multiplier per shot +SWEP.BipodKick = 0.25 // Recoil Kick multiplier + +SWEP.TriggerDelay = 0.0 + +// SWEP.ShootChance = 1 +SWEP.JamWaitTime = 0.3 +SWEP.JamFactor = 0 // higher = more frequent jams. no jams at 0 +SWEP.JamTakesRound = false // consume ammo on jam +SWEP.JamSkipFix = false // only do dryfire and the initial delay. use on revolvers mostly +SWEP.JamBaseMSB = nil // use this number as the base value instead of being based on ammo. + +// Spread penalties are in spread units and are additive +SWEP.MoveSpreadPenalty = 0.01 // spread penalty while travelling at max. 250 u/s +SWEP.MidAirSpreadPenalty = 0.1 // spread penalty for being in the air +SWEP.HipFireSpreadPenalty = 0.02 // spread penalty for not being scoped in +SWEP.ScopedSpreadPenalty = 0 // spread penalty for... being scoped in? +SWEP.BlindFireSpreadPenalty = 0 // spread penalty for blind firing +SWEP.CrouchSpreadPenalty = 0 +SWEP.PeekPenaltyFraction = 0.3 // percentage of hipfire penalty to use while peeking in sights + +// Technically does not affect recoil at all, but affects spread (now called "bloom") +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0 // time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 2 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount +SWEP.RecoilSpreadPenalty = 0.001 // extra spread per one unit of recoil +SWEP.RecoilResetInstant = true // Set false to account for RPM. + +SWEP.RecoilMultBurst = 0.9 // Affects both "bloom" and recoil kick while current firemode is burst +SWEP.RecoilMultSemi = 0.9 // Affects both "bloom" and recoil kick while current firemode is semi +SWEP.RecoilMultCrouch = 0.85 // Affects both "bloom" and recoil kick while crouched + +// Controls alternate bloom behavior, defaults to convar +SWEP.AlwaysAltRecoil = nil +SWEP.NeverAltRecoil = nil + +SWEP.RecoilVisualKick = 0.1 +SWEP.RecoilKick = 0.25 +SWEP.RecoilStability = 0 // Direction of recoil kick, 1 is completely vertical and 0 is 180deg cone +SWEP.RecoilAltMultiplier = 200 // Multiplier to RecoilSpreadPenalty when using alternative recoil mode. + +SWEP.ShotgunPelletSpread = 0 // per-pellet spread for shotguns (if enabled). Otherwise just adds to spread + +SWEP.NoRecoilPattern = false // set true to not use recoil patterns for this gun +SWEP.RecoilPatternSeed = nil // custom seed. Defaults to weapon class + +SWEP.CanBlindFire = true + +SWEP.CannotHipFire = false + +// lockon + +// We're using a new system for lockon now, which doesn't use this system. +// SWEP.LockOnAngle = math.cos(math.rad(5)) +SWEP.LockOnTrackAngle = 5 +SWEP.LockOnRange = 3500 + +SWEP.LockOnTime = 1.5 + +SWEP.ProvideTargetData = true + +SWEP.LockOnOutOfSights = false +SWEP.LockOnInSights = false + +SWEP.RequireLockOn = false // Cannot shoot without a lock + +// handling + +SWEP.MoveSpeedMult = 1 +SWEP.ShootingSpeedMult = 0.5 // slow down applied while shooting +SWEP.SightedSpeedMult = 0.5 +SWEP.MeleeSpeedMult = 0.75 +SWEP.MeleeSpeedMultTime = 1 // seconds to apply slow down for +SWEP.ReloadSpeedMult = 1 +SWEP.ReloadSpeedMultTime = 0.5 // duration for slowdown to fade out for AFTER RELOAD FINISHES + +SWEP.ShootWhileSprint = false + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.25 // how long it takes to go from sprinting to shooting + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeCustomize = "slam" +SWEP.HoldTypeNPC = nil +SWEP.HoldTypeSuicide = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 +SWEP.GestureBash = ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, 0, 0) + +SWEP.SprintAng = Angle(0, 45, 0) +SWEP.SprintPos = Vector(0, 0, 0) + +SWEP.BlindFireAng = Angle(0, 15, 0) +SWEP.BlindFirePos = Vector(0, 0, -6) + +SWEP.BlindFireLeftAng = Angle(75, 0, 0) +SWEP.BlindFireLeftPos = Vector(8, 14, -12) + +SWEP.BlindFireRightAng = Angle(-75, 0, 0) +SWEP.BlindFireRightPos = Vector(-8, 14, -12) + +SWEP.BlindFireSuicideAng = Angle(0, 145, 130) +SWEP.BlindFireSuicidePos = Vector(-6, 32, -26) + +SWEP.CustomizeAng = Angle(30, 15, 0) +SWEP.CustomizePos = Vector(6, 0, -7) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(0, 1, 1) + +SWEP.PeekAng = Angle(0, 0, -7) +SWEP.PeekPos = Vector(3, 2, -1.5) + +SWEP.HolsterVisible = false +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(0, 0, 0) +SWEP.HolsterAng = Angle(0, 0, 0) + +SWEP.HolsterModel = nil // string, model. + +SWEP.SightMidPoint = { + Pos = Vector(-1, 15, -6), + Ang = Angle(0, 0, -45) +} + +SWEP.SprintMidPoint = { + Pos = Vector(4, 10, 2), + Ang = Angle(0, -10, -45) +} + +SWEP.NearWallPos = Vector(0, 6, 0) +SWEP.NearWallAng = Angle(-3, 5, -5) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = nil // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 1.1 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = false + +SWEP.QuickScopeSpreadPenalty = 0.05 +SWEP.QuickScopeTime = 0.2 // amount of time over which to fade out the quickscope spread penalty + +// sway + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.1 +SWEP.BlindFireSway = 2 +SWEP.SwayCrouchMult = 0.75 + +SWEP.BreathRecovery = 1 +SWEP.BreathDrain = 1 + +SWEP.FreeAim = true +SWEP.FreeAimMaxAngle = 3.5 + +SWEP.ShootOffset = Vector(0, 0, 0) +SWEP.ShootOffsetAngle = Angle(0, 0, 0) + +// quicknade + +SWEP.CanQuickNade = true +SWEP.QuickNadeTimeMult = 1 + +// melee + +SWEP.CanMeleeAttack = true + +SWEP.MeleeDamage = 25 +SWEP.MeleeAttackTime = 0.8 // time between swings +SWEP.MeleeRange = 96 +SWEP.MeleeDamageType = DMG_GENERIC +SWEP.MeleeDelay = 0.25 // delay between swing start and trace + +// secondary attack, used on knives +SWEP.Melee2Damage = nil +SWEP.Melee2AttackTime = nil +SWEP.Melee2Range = nil +SWEP.Melee2AttackMissTime = nil +SWEP.Melee2Delay = nil + +SWEP.MeleeThrowForce = 3000 + +// used on knife perks +SWEP.MeleePerkStr = 0.5 +SWEP.MeleePerkAgi = 0.5 +SWEP.MeleePerkInt = 0.5 + +// reload + +SWEP.AmmoPerShot = 1 +SWEP.ClipSize = 30 +SWEP.Ammo = "pistol" + +SWEP.InfiniteAmmo = false // do not consume reserve ammo + +SWEP.SupplyAmmoAmount = false // overrides clipsize/ammo for ammo pickups +SWEP.SupplyLimit = 1 // Multiplier for supply ammo + +SWEP.TryUnholster = false // if we have an "unholster" animation use it instead of "deploy" + +SWEP.ShotgunReload = false +SWEP.ShotgunThreeload = true // use those stupid 3 shot reload animations +SWEP.ShotgunReloadCompleteStart = false // do not interrupt reload_start and instead wait for it to finish first. used on FP6 animations +SWEP.ShotgunFullCancel = false // Ignore tacrp_reload_sg_cancel and force cancel animation +SWEP.ShotgunNoReverseStart = true // don't reverse starting animation on a non-empty reload +SWEP.ShotgunUpInTime = 0.9 // time after which one round is finished loading + +SWEP.ReloadUpInTime = nil // time to restore ammo, if unset restores at end of animation +SWEP.ReloadTimeMult = 1 +SWEP.DeployTimeMult = 1 +SWEP.HolsterTimeMult = 1 +SWEP.UnholsterTimeMult = 1 +SWEP.DropMagazineModel = false +SWEP.DropMagazineImpact = "pistol" // available: "pistol", "plastic", "metal", "bullet", "shotgun" +SWEP.DropMagazineAmount = 1 +SWEP.DropMagazineTime = 0 +SWEP.MidReload = false // allow guns with "midreload" animation to continue reload after holster + +SWEP.DefaultBodygroups = "0000000" +SWEP.DefaultWMBodygroups = "0000000" +SWEP.DefaultSkin = 0 +SWEP.BulletBodygroups = nil +SWEP.BulletBodygroupsSetAll = false // Set all applicable bullet groups, rather than just the last + +/* +{ + [1] = {5, 1} +} +*/ +SWEP.LoadInTime = 0.25 // how long to replenish the visible "belt" of ammo +SWEP.ShotgunLoadInTime = 0.9 // visual update delay for shotguns + +// sounds + +SWEP.Sound_Shoot = "^" +SWEP.Sound_Shoot_Silenced = "" + +SWEP.Vol_Shoot = 130 +SWEP.Pitch_Shoot = 100 +SWEP.Loudness_Shoot = 1 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +SWEP.Sound_ScopeIn = "" +SWEP.Sound_MeleeAttack = "" +SWEP.Sound_DryFire = "TacRP/weapons/dryfire_pistol-1.wav" +SWEP.Sound_Jam = "TacRP/malfunction.wav" + +SWEP.Sound_BipodDown = "tacrp/bipod_down.wav" +SWEP.Sound_BipodUp = "tacrp/bipod_up.wav" + +SWEP.Sound_MeleeSwing = "" + +SWEP.Sound_ToggleTactical = "tacrp/firemode.wav" + +SWEP.Sound_StartLockOn = "tacrp/check1.wav" +SWEP.Sound_FinishLockOn = "tacrp/locked1.wav" + +// effects + +SWEP.EffectsAlternate = false // Effects will alternate using L and R attachments. +SWEP.EffectsDoubled = false // Per shot, play effects a second time on the other attachment. + +// .qc attachment for muzzle flash and eject when EffectsAlternate is NOT true. +SWEP.QCA_Muzzle = 1 +SWEP.QCA_Eject = 2 + +// .qc attachments when EffectsAlternate is set to true. +SWEP.QCA_MuzzleL = 3 +SWEP.QCA_MuzzleR = 4 +SWEP.QCA_EjectL = 6 +SWEP.QCA_EjectR = 7 + +// ditto but for worldmodel +SWEP.WM_QCA_Muzzle = 1 +SWEP.WM_QCA_Eject = 2 + +SWEP.WM_QCA_MuzzleL = 1 +SWEP.WM_QCA_MuzzleR = 2 +SWEP.WM_QCA_EjectL = 3 +SWEP.WM_QCA_EjectR = 4 + +SWEP.MuzzleEffect = "muzzleflash_pistol" + +SWEP.EjectEffect = 0 // 1 = pistol, 2 = rifle, 3 = shotgun +SWEP.EjectDelay = 0 +SWEP.EjectScale = 1 + +// anims +// VM: +// idle +// fire +// fire1, fire2... +// dryfire +// melee +// reload +// midreload +// prime_grenade +// throw_grenade +// throw_grenade_underhand +// deploy +// blind_idle +// blind_fire +// blind_fire1, blind_fire2... +// blind_dryfire + +// WM: +// attack1 +SWEP.AnimationTranslationTable = { + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "midreload" +} // translates ["fire"] = "shoot"; key = translates from, value = translates to +// e.g. you have a "shoot1" sequence and need "fire" +// so ["fire"] = "shoot1" +// can be ["fire"] = {"list", "of", "values"} + +SWEP.ProceduralIronFire = nil // procedurally animate the viewmodel and bones when using ironsights +// { +// vm_pos = Vector(0, -0.5, -0.6), +// vm_ang = Angle(0, 2, 0), +// t = 0.2, // duration of vm pos/ang +// tmax = 0.2, // clean up after this time has passed +// bones = { +// { +// bone = "ValveBiped.slide", +// pos = Vector(0, 0, -3), // optional +// ang = Angle(0, 0, 0), // optional +// t0 = 0.05, // duration to reach full movement +// t1 = 0.2, // duration to reset +// }, +// }, +// } + +SWEP.NoHolsterAnimation = false // Will play draw reversed instead +SWEP.LastShot = false + +// Use special animation setup for akimbo pistols. +// Does not adjust effects - set AlternatingEffects separately. +SWEP.Akimbo = false + +// attachments + +SWEP.AttachmentElements = nil +/* +{ + ["bg_name"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(), + Pos_WM = Vector(), + Ang_VM = Angle(), + Ang_WM = Angle(), + }, + }, + SortOrder = 1, // defaults to 1, higher value means process later + } +} +*/ + +SWEP.Attachments = nil +// { +// [1] = { +// Installed = nil, +// Default = nil, // install this attachment by default +// InstalledElements = "", // single or list of elements to activate when something is installed here +// UnInstalledElements = "", +// Integral = false, // cannot be removed +// Category = "", // single or {"list", "of", "values"} +// Bone = "", +// WMBone = "", +// Pos_VM = Vector(0, 0, 0), +// Pos_WM = Vector(0, 0, 0), +// Ang_VM = Angle(0, 0, 0), +// Ang_WM = Angle(0, 0, 0), +// CapacityMult = 1, // multiply the amount of Capacity this attachment occupies +// } +// } + +// boilerplate + +SWEP.FreeAim = true + +SWEP.ArcticTacRP = true +SWEP.DrawCrosshair = true +SWEP.AccurateCrosshair = true +SWEP.DrawWeaponInfoBox = false +SWEP.UseHands = true + +SWEP.Shields = {} + +SWEP.CurrentAnimation = "" + +SWEP.Primary.Automatic = false +SWEP.Primary.ClipSize = 0 +SWEP.Primary.Ammo = "" +SWEP.Primary.DefaultClip = 0 + +SWEP.Secondary.Automatic = true +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.Ammo = "" +SWEP.Secondary.DefaultClip = 0 + +SWEP.GaveDefaultAmmo = false + +SWEP.BounceWeaponIcon = false + +SWEP.SwayScale = 1 +SWEP.BobScale = 1 + +SWEP.LaserColor = Color(255, 0, 0) + +SWEP.ActiveEffects = {} + +SWEP.PCFs = {} +SWEP.MuzzPCFs = {} + +AddCSLuaFile() + +local searchdir = "weapons/tacrp_base" + +local function autoinclude(dir) + local files, dirs = file.Find(searchdir .. "/*.lua", "LUA") + + for _, filename in pairs(files) do + if filename == "shared.lua" then continue end + local luatype = string.sub(filename, 1, 2) + + if luatype == "sv" then + if SERVER then + include(dir .. "/" .. filename) + end + elseif luatype == "cl" then + AddCSLuaFile(dir .. "/" .. filename) + if CLIENT then + include(dir .. "/" .. filename) + end + else + AddCSLuaFile(dir .. "/" .. filename) + include(dir .. "/" .. filename) + end + end + + for _, path in pairs(dirs) do + autoinclude(dir .. "/" .. path) + end +end + +autoinclude(searchdir) + +function SWEP:SetupDataTables() + self:NetworkVar("Float", 0, "RecoilAmount") + self:NetworkVar("Float", 1, "AnimLockTime") + self:NetworkVar("Float", 2, "NextIdle") + self:NetworkVar("Float", 3, "LastRecoilTime") + self:NetworkVar("Float", 4, "RecoilDirection") + self:NetworkVar("Float", 5, "SprintAmount") + self:NetworkVar("Float", 6, "SprintLockTime") + self:NetworkVar("Float", 7, "LastScopeTime") + self:NetworkVar("Float", 8, "LastMeleeTime") + self:NetworkVar("Float", 9, "PrimedGrenadeTime") + self:NetworkVar("Float", 10, "StartPrimedGrenadeTime") + self:NetworkVar("Float", 11, "ReloadFinishTime") + self:NetworkVar("Float", 12, "NWSightAmount") + self:NetworkVar("Float", 13, "BlindFireFinishTime") + self:NetworkVar("Float", 14, "HolsterTime") + self:NetworkVar("Float", 15, "NWLastProceduralFireTime") + self:NetworkVar("Float", 16, "NWHoldBreathAmount") + self:NetworkVar("Float", 17, "Breath") + self:NetworkVar("Float", 18, "LockOnStartTime") + + self:NetworkVar("Int", 0, "BurstCount") + self:NetworkVar("Int", 1, "ScopeLevel") + self:NetworkVar("Int", 2, "NthShot") + self:NetworkVar("Int", 3, "LoadedRounds") + self:NetworkVar("Int", 4, "Firemode") + self:NetworkVar("Int", 5, "PatternCount") + + self:NetworkVar("Bool", 0, "Customize") + self:NetworkVar("Bool", 1, "Reloading") + self:NetworkVar("Bool", 2, "BlindFire") + self:NetworkVar("Bool", 3, "EndReload") + self:NetworkVar("Bool", 4, "PrimedGrenade") + self:NetworkVar("Bool", 5, "Safe") + self:NetworkVar("Bool", 6, "BlindFireLeft") + self:NetworkVar("Bool", 7, "Tactical") + self:NetworkVar("Bool", 8, "Charge") + self:NetworkVar("Bool", 9, "NWPeeking") + self:NetworkVar("Bool", 10, "BlindFireRight") // bleh, but actually less networking load than using an integer (32 bit) + self:NetworkVar("Bool", 11, "Jammed") + self:NetworkVar("Bool", 12, "Ready") + self:NetworkVar("Bool", 13, "InBipod") + self:NetworkVar("Bool", 14, "OutOfBreath") + self:NetworkVar("Bool", 15, "HoldingBreath") + self:NetworkVar("Bool", 16, "LastWasSprinting") + self:NetworkVar("Bool", 17, "EmptyReload") + + self:NetworkVar("Angle", 0, "FreeAimAngle") + self:NetworkVar("Angle", 1, "LastAimAngle") + self:NetworkVar("Angle", 2, "BipodAngle") + + self:NetworkVar("Vector", 0, "BipodPos") + + self:NetworkVar("Entity", 0, "HolsterEntity") + self:NetworkVar("Entity", 1, "CornershotEntity") + self:NetworkVar("Entity", 2, "LockOnEntity") + + self:SetFreeAimAngle(Angle()) + self:SetLastAimAngle(Angle()) + self:SetFiremode(1) + self:SetTactical(true) + self:SetReady(false) + self:SetBreath(1) + self:SetHoldBreathAmount(0) + self:SetLastWasSprinting(false) + self:SetHoldingBreath(false) + self:SetLockOnEntity(NULL) + self:SetLockOnStartTime(0) +end + +function SWEP:OnDrop(owner) + if self.PreviousZoom and IsValid(owner) then + owner:SetCanZoom(true) + end + self:SetReady(false) +end + +function SWEP:SecondaryAttack() + self:RunHook("Hook_SecondaryAttack") + return +end + +local function clunpredictvar(tbl, name, varname, default) + local clvar = "CL_" .. name + + tbl[clvar] = default + + tbl["Set" .. name] = function(self, v) + if (!game.SinglePlayer() and CLIENT and self:GetOwner() == LocalPlayer()) then self[clvar] = v end + self["Set" .. varname](self, v) + end + + tbl["Get" .. name] = function(self) + if (!game.SinglePlayer() and CLIENT and self:GetOwner() == LocalPlayer()) then return self[clvar] end + return self["Get" .. varname](self) + end +end + +clunpredictvar(SWEP, "SightAmount", "NWSightAmount", 0) +-- clunpredictvar(SWEP, "SprintAmount", "NWSprintAmount", 0) +clunpredictvar(SWEP, "LastProceduralFireTime", "NWLastProceduralFireTime", 0) +clunpredictvar(SWEP, "HoldBreathAmount", "NWHoldBreathAmount", 0) diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sv_net.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sv_net.lua new file mode 100644 index 0000000..c1d1223 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sv_net.lua @@ -0,0 +1,62 @@ +function SWEP:NetworkWeapon(sendto) + net.Start("TacRP_networkweapon") + net.WriteUInt(self:EntIndex(), 13) + + for slot, slottbl in pairs(self.Attachments) do + if !slottbl.Installed then net.WriteUInt(0, TacRP.Attachments_Bits) continue end + + local atttbl = TacRP.GetAttTable(slottbl.Installed) + + net.WriteUInt(atttbl.ID, TacRP.Attachments_Bits) + end + + if sendto then + net.Send(sendto) + else + net.SendPVS(self:GetPos()) + end + + self:InvalidateCache() + + self:DoBodygroups(true) + self:DoBodygroups(false) +end + +function SWEP:ReceivePreset() + for slot, slottbl in pairs(self.Attachments) do + local attid = net.ReadUInt(TacRP.Attachments_Bits) + + if attid == 0 then + if slottbl.Installed then + self:Detach(slot, true) + end + else + local att = TacRP.Attachments_Index[attid] + local atttbl = TacRP.GetAttTable(att) + + if !atttbl then continue end + + if slottbl.Installed then + self:Detach(slot, true) + end + + self:Attach(slot, att, true, true) + + if atttbl.OnPresetLoad then + atttbl.OnPresetLoad(self) + end + end + end + + self:NetworkWeapon() + TacRP:PlayerSendAttInv(self:GetOwner()) + + self:InvalidateCache() + self:SetBaseSettings() + + if self:GetValue("TryUnholster") then + self:DoDeployAnimation() + end + + self:RestoreClip(self.Primary.ClipSize) +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base_knife.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base_knife.lua new file mode 100644 index 0000000..0a86351 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base_knife.lua @@ -0,0 +1,211 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = false + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Складной нож" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.NoAimAssist = true + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "8Melee Weapon" + +SWEP.ViewModel = "models/weapons/tacint/v_knife.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_knife.mdl" + +SWEP.NoRanger = true +SWEP.NoStatBox = false + +SWEP.Slot = 0 + +SWEP.NPCUsable = false + +SWEP.PrimaryMelee = true + +SWEP.MeleeDamage = 35 +SWEP.MeleeAttackTime = 0.4 +SWEP.MeleeRange = 72 +SWEP.MeleeAttackMissTime = 0.5 +SWEP.MeleeDelay = 0.15 + +SWEP.MeleeThrowForce = 2000 + +SWEP.MeleeDamageType = DMG_SLASH + +SWEP.MeleeRechargeRate = 1 + +SWEP.MeleePerkStr = 0.5 +SWEP.MeleePerkAgi = 0.5 +SWEP.MeleePerkInt = 0.5 + +SWEP.Lifesteal = 0 +SWEP.DamageCharge = 0 + +SWEP.Firemode = 0 + +SWEP.RPM = 120 + +SWEP.CanBlindFire = false + +SWEP.Ammo = "" +SWEP.ClipSize = -1 +SWEP.Primary.ClipSize = -1 + +// handling + +SWEP.MoveSpeedMult = 1 + +SWEP.MeleeSpeedMult = 1 +SWEP.MeleeSpeedMultTime = 0.5 + +SWEP.SprintToFireTime = 0.25 + +SWEP.QuickNadeTimeMult = 0.8 + +SWEP.Scope = false + +SWEP.Sway = 0 + +// hold types + +SWEP.HoldType = "knife" +SWEP.HoldTypeSprint = "knife" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL +SWEP.GestureBash = ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE +SWEP.GestureBash2 = ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE + +SWEP.MidAirSpreadPenalty = 0 + +SWEP.PassiveAng = Angle(-2.5, 0, 0) +SWEP.PassivePos = Vector(1, 0, -5) + +SWEP.SprintAng = Angle(0, 0, 0) +SWEP.SprintPos = Vector(2, 0, -5) + +SWEP.CustomizeAng = Angle(0, 25, 0) +SWEP.CustomizePos = Vector(2, 0, -12) + +SWEP.SprintMidPoint = { + Pos = Vector(2, 0, -5), + Ang = Angle(0, 0, 0) +} + +SWEP.Sound_Lunge = "npc/fast_zombie/leap1.wav" + +SWEP.HolsterVisible = false +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_GEAR +SWEP.HolsterPos = Vector(2, 0, 0) +SWEP.HolsterAng = Angle(-90, -90, 15) + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Technique", + Category = "melee_tech", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [2] = { + PrintName = "Special", + Category = "melee_spec", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Boost", + Category = "melee_boost", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, +} + +SWEP.FreeAim = false + +SWEP.DrawCrosshair = true +SWEP.DrawCrosshairInSprint = true +SWEP.CrosshairStatic = true + +function SWEP:PrimaryAttack() + local stop = self:RunHook("Hook_PreShoot") + if stop then return end + + local owner = self:GetOwner() + if IsValid(owner) then + local tr = owner:GetEyeTrace(72) + if IsValid(tr.Entity) and tr.Entity.LVS then return end + end + + self:Melee() + return +end + +function SWEP:ThinkSprint() +end + +function SWEP:ThinkSights() +end + +function SWEP:ThinkHoldBreath() + local ret = self:RunHook("Hook_Recharge") + if ret then return end + local f = 10 - math.min(self:GetValue("MeleePerkInt"), 0.5) * 2 - math.max((self:GetValue("MeleePerkInt") - 0.5) * 2, 0) * 6 + self:SetBreath(math.min(1, self:GetBreath() + FrameTime() / f * self:GetValue("MeleeRechargeRate"))) +end + +SWEP.NoBreathBar = false +SWEP.BreathSegmentSize = 0 + +local breath_a = 0 +local last = 1 +local lastt = 0 +function SWEP:DrawBreathBar(x, y, w, h) + if self:GetValue("NoBreathBar") then return end + local seg = self:GetValue("BreathSegmentSize") + if CurTime() > lastt + 1 then + breath_a = math.Approach(breath_a, 0, FrameTime() * 2) + elseif breath_a < 1 then + breath_a = math.Approach(breath_a, 1, FrameTime()) + end + local breath = self:GetBreath() + if last != self:GetBreath() then + lastt = CurTime() + last = breath + end + if breath_a == 0 then return end + + x = x - w / 2 + y = y - h / 2 + + surface.SetDrawColor(90, 90, 90, 200 * breath_a) + surface.DrawOutlinedRect(x - 1, y - 1, w + 2, h + 2, 1) + surface.SetDrawColor(0, 0, 0, 75 * breath_a) + surface.DrawRect(x, y, w, h) + + if seg > 0 then + local segcount = math.ceil(1 / seg) + surface.SetDrawColor(255, 255, 255, 200 * breath_a) + for i = 1, segcount - 1 do + local d = i / segcount + surface.DrawLine(x + w * d, y, x + w * d, y + h) + end + end + + if seg > 0 and breath < seg then + surface.SetDrawColor(255, 128, 0, 150 * breath_a) + else + surface.SetDrawColor(255, 255, 255, 150 * breath_a) + end + + surface.DrawRect(x, y, w * breath, h) +end + +function SWEP:CalcView(ply, pos, ang, fov) + return pos, ang, fov +end + +SWEP.AutoSpawnable = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_base_nade.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_base_nade.lua new file mode 100644 index 0000000..37618e3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_base_nade.lua @@ -0,0 +1,183 @@ +AddCSLuaFile() + +SWEP.Base = "tacrp_base" + +// spawnable +SWEP.Spawnable = false +SWEP.AdminOnly = false + +SWEP.NoAimAssist = true + +// names and stuff +SWEP.PrintName = "Arctic's Tactical RP Base Grenade" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "9Throwable" + +SWEP.Description = "" + +SWEP.ViewModel = "models/weapons/cstrike/c_eq_fraggrenade.mdl" +SWEP.WorldModel = "models/weapons/w_eq_fraggrenade.mdl" + +SWEP.ViewModelFOV = 65 + +SWEP.NoRanger = true +SWEP.NoStatBox = true +SWEP.NPCUsable = false + +SWEP.Slot = 4 + +SWEP.FreeAim = false + +SWEP.DrawCrosshair = true +SWEP.DrawCrosshairInSprint = true +SWEP.CrosshairStatic = true + +SWEP.Scope = false +SWEP.CanBlindFire = false +SWEP.CanQuickNade = false +SWEP.CanMeleeAttack = false + +SWEP.Firemode = 0 + +SWEP.Ammo = "" +SWEP.PrimaryGrenade = "" + +SWEP.Sway = 0 + +SWEP.QuickNadeTimeMult = 0.6 + +function SWEP:Equip(newowner) + local wep = self:GetOwner():GetActiveWeapon() + if self:GetOwner():IsPlayer() and wep != self and wep.ArcticTacRP and !wep:CheckGrenade(nil, true) then + self:GetOwner():SetNWInt("ti_nade", TacRP.QuickNades[self.PrimaryGrenade].Index) + end + + if engine.ActiveGamemode() == "terrortown" and SERVER then + if self:IsOnFire() then + self:Extinguish() + end + + self.fingerprints = self.fingerprints or {} + + if !table.HasValue(self.fingerprints, newowner) then + table.insert(self.fingerprints, newowner) + end + + if self:HasSpawnFlags(SF_WEAPON_START_CONSTRAINED) then + local flags = self:GetSpawnFlags() + local newflags = bit.band(flags, bit.bnot(SF_WEAPON_START_CONSTRAINED)) + self:SetKeyValue("spawnflags", newflags) + end + end + + if engine.ActiveGamemode() == "terrortown" and SERVER and IsValid(newowner) and (self.StoredAmmo or 0) > 0 and self.Primary.Ammo != "none" then + newowner:GiveAmmo(self.StoredAmmo, self.Primary.Ammo) + self.StoredAmmo = 0 + end +end + +function SWEP:ThinkSprint() +end + +function SWEP:ThinkSights() +end + +function SWEP:ThinkGrenade() + if self:GetPrimedGrenade() and self:GetAnimLockTime() < CurTime() then + if !self:GetOwner():KeyDown(self.GrenadeDownKey) then + self:ThrowGrenade() + self:SetPrimedGrenade(false) + elseif SERVER and self.GrenadeDownKey == IN_ATTACK then + self.GrenadeThrowCharge = math.Clamp(CurTime() - self:GetAnimLockTime(), 0, 0.25) * 2 + end + elseif !self:GetPrimedGrenade() then + local nade = TacRP.QuickNades[self:GetValue("PrimaryGrenade")] + if !TacRP.IsGrenadeInfiniteAmmo(nade) and self:GetOwner():GetAmmoCount(nade.Ammo) == 0 then + if SERVER then + self:Remove() + elseif CLIENT and IsValid(self:GetOwner():GetPreviousWeapon()) and self:GetOwner():GetPreviousWeapon():IsWeapon() then + input.SelectWeapon(self:GetOwner():GetPreviousWeapon()) + end + end + end +end + +function SWEP:PrimaryAttack() + + if engine.ActiveGamemode() == "terrortown" and GetRoundState() == ROUND_PREP and + ((TTT2 and !GetConVar("ttt_nade_throw_during_prep"):GetBool()) or (!TTT2 and GetConVar("ttt_no_nade_throw_during_prep"):GetBool())) then + return + end + + self.Primary.Automatic = false + self.Secondary.Automatic = false + self.GrenadeDownKey = IN_ATTACK + self.GrenadeThrowOverride = false + self.GrenadeThrowCharge = 0 + + if self:GetValue("Melee") and self:GetOwner():KeyDown(IN_USE) then + self:Melee() + return + end + + if self:StillWaiting() then + return + end + + self:SetBaseSettings() + + local stop = self:RunHook("Hook_PreShoot") + if stop then return end + + self:PrimeGrenade() + self:SetNextPrimaryFire(self:GetAnimLockTime()) + self:SetNextSecondaryFire(self:GetAnimLockTime()) + + self:RunHook("Hook_PostShoot") + + if game.SinglePlayer() and SERVER then self:CallOnClient("PrimaryAttack") end +end + +function SWEP:SecondaryAttack() + + if engine.ActiveGamemode() == "terrortown" and GetRoundState() == ROUND_PREP and GetConVar("ttt_no_nade_throw_during_prep"):GetBool() then + return + end + + self.Primary.Automatic = false + self.Secondary.Automatic = false + self.GrenadeDownKey = IN_ATTACK2 + self.GrenadeThrowOverride = true + + if self:StillWaiting() then + return + end + + self:SetBaseSettings() + + local stop = self:RunHook("Hook_PreShoot") + if stop then return end + + self:PrimeGrenade() + self:SetNextPrimaryFire(self:GetAnimLockTime()) + self:SetNextSecondaryFire(self:GetAnimLockTime()) + + self:RunHook("Hook_PostShoot") + + if game.SinglePlayer() and SERVER then self:CallOnClient("SecondaryAttack") end +end + +function SWEP:Reload() +end + +function SWEP:PreDrop() + if SERVER and IsValid(self:GetOwner()) and self.Primary.Ammo != "" and self.Primary.Ammo != "none" then + local ammo = self:Ammo1() + if ammo > 0 then + self.StoredAmmo = 1 + self:GetOwner():RemoveAmmo(1, self.Primary.Ammo) + end + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_bekas.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_bekas.lua new file mode 100644 index 0000000..e4b8677 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_bekas.lua @@ -0,0 +1,327 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Molot Bekas-16M" +SWEP.AbbrevName = "Bekas-16M" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "4Consumer" +SWEP.SubCatType = "5Shotgun" + +SWEP.Description = "Accurate hunting shotgun with low damage." +SWEP.Description_Quote = "\"If you can hear them but can't shoot them, you can probably grenade them.\"" --Hardcore Henry (2016) (It's not a Bekas in that exact scene, but one is used later on in the complex so uuuuhhhhh explode) + +SWEP.Trivia_Caliber = "16 Gauge" +SWEP.Trivia_Manufacturer = "Molot" +SWEP.Trivia_Year = "1999" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_bekas.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_bekas.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 24, + Damage_Min = 10, + Range_Min = 1200, + ClipSize = 6, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 14, + Damage_Min = 6, + Range_Min = 400, + Range_Max = 2500, + Num = 6, + + ClipSize = 6, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + + Range_Min = 500, + Range_Max = 2500, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilDissipationRate = 5, + RecoilMaximum = 12, + RecoilSpreadPenalty = 0.01, + HipFireSpreadPenalty = 0.015, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Shotgun + +// "ballistics" + +SWEP.Damage_Max = 18 +SWEP.Damage_Min = 5 +SWEP.Range_Min = 200 // distance for which to maintain maximum damage +SWEP.Range_Max = 2000 // distance at which we drop to minimum damage +SWEP.Penetration = 1 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.47 +SWEP.ArmorBonus = 0.25 + +SWEP.Num = 6 + +SWEP.MuzzleVelocity = 11000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Pump-Action" // only used externally for firemode name distinction + +SWEP.RPM = 60 + +SWEP.ShootTimeMult = 0.85 + +SWEP.Spread = 0.015 +SWEP.ShotgunPelletSpread = 0.0075 + +SWEP.HipFireSpreadPenalty = 0.025 +SWEP.MidAirSpreadPenalty = 0 + +SWEP.ScopedSpreadPenalty = 0 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 3 +SWEP.RecoilResetTime = 0.25 // time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1.1 + +SWEP.RecoilVisualKick = 2 +SWEP.RecoilKick = 15 +SWEP.RecoilStability = 0.4 + +SWEP.RecoilSpreadPenalty = 0.02 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.7 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.36 +SWEP.SprintToFireTime = 0.4 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.2 + +SWEP.FreeAimMaxAngle = 5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "shotgun" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SHOTGUN + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(4, -2, -4) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.57, -0.6, 2) +SWEP.SightPos = Vector(-3.45, -5, -2.7) + +SWEP.CorrectivePos = Vector(0.275, 0, -0.2) +SWEP.CorrectiveAng = Angle(1.21, 0.1, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 5 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "buckshot" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.ShotgunReload = true + +SWEP.ReloadTimeMult = 1 + +// sounds + +local path = "TacRP/weapons/bekas/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/sg551/sg551_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 0 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_shotgun" +SWEP.EjectEffect = 3 +SWEP.EjectDelay = 0.5 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire"] = {"shoot1", "shoot2"}, + ["blind_fire"] = {"blind_shoot1"}, + ["melee"] = {"melee1", "melee2"}, + ["reload"] = {"reload", "reload2"}, + ["jam"] = "reload_finish" +} + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {5, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.bekas_rootbone", + WMBone = "ValveBiped.Bip01_R_Hand", + InstalledElements = {"optic"}, + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 0.75, + Pos_VM = Vector(-3.2, 0, 8), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(11, 0.85, -7), + Ang_WM = Angle(-25, 3.5, 180), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom"}, + Bone = "ValveBiped.bekas_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + VMScale = 1.25, + Pos_VM = Vector(-1, -0.3, 24), + Ang_VM = Angle(90, 0, -90), + Pos_WM = Vector(25, 0, -12), + Ang_WM = Angle(-25, 3.5, 90), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "acc_extmag_shotgun_tube"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_manual", "trigger_pump2"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_shotgun"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_Bekas.Insertshell", path .. "insertshell-1.wav") +addsound("TacInt_Bekas.Movement", path .. "movement-1.wav") +addsound("TacInt_Bekas.PumpBack", path .. "pump_backward-1.wav") +addsound("TacInt_Bekas.PumpForward", path .. "pump_forward-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_c4_detonator.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_c4_detonator.lua new file mode 100644 index 0000000..1d8e629 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_c4_detonator.lua @@ -0,0 +1,174 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "C4 Detonator" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.NoAimAssist = true + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "9Equipment" + +SWEP.Description = "Device for touching off C4 charges or other types of remote explosives." +SWEP.Description_Quote = "\"Yeah, fuck you too!\"" -- The Thing (1982) + +SWEP.ViewModel = "models/weapons/tacint/v_c4.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_c4_det.mdl" + +SWEP.NoRanger = true +SWEP.NoStatBox = true + + +SWEP.ArcadeStats = { + MeleeSpeedMult = 1, +} + +SWEP.Slot = 4 + +SWEP.NPCUsable = false + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 120 + +SWEP.CanBlindFire = false + +SWEP.Ammo = "ti_c4" +SWEP.ClipSize = -1 +SWEP.Primary.ClipSize = -1 +SWEP.SupplyAmmoAmount = 3 + +// handling + +SWEP.MoveSpeedMult = 1 + + +SWEP.MeleeSpeedMultTime = 2 // seconds to apply slow down for + +SWEP.SprintToFireTime = 0.25 + +SWEP.Scope = false +SWEP.NoSecondaryMelee = true + +// hold types + +SWEP.HoldType = "normal" +SWEP.HoldTypeSprint = "normal" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(1, -2, -5) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_GEAR +SWEP.HolsterPos = Vector(2, 0, 0) +SWEP.HolsterAng = Angle(-90, -90, 15) + +// sounds + +local path = "TacRP/weapons/c4/" + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["melee"] = {"melee1", "melee2"} +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Accessory", + Category = {"acc_holster"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [2] = { + PrintName = "Perk", + Category = {"perk_melee", "perk_throw"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +SWEP.FreeAim = false + +SWEP.AttachmentCapacity = 30 // amount of "Capacity" this gun can accept + +SWEP.DrawCrosshair = false + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_C4_Detonator.Detonator_Press", path .. "detonator_press.wav") +addsound("TacInt_C4_Detonator.antenna_open", path .. "antenna_open.wav") + +function SWEP:PrimaryAttack() + if self:GetValue("Melee") then + if self:GetOwner():KeyDown(IN_USE) then + self.Primary.Automatic = true + self:Melee() + return + end + end + + if self:StillWaiting() then return end + + self:SetBaseSettings() + + self:PlayAnimation("detonate") + + for i, k in pairs(ents.FindByClass("tacrp_proj_nade_*")) do + if (k:GetOwner() == self:GetOwner() or k.Attacker == self:GetOwner()) and k.RemoteFuse then + k:RemoteDetonate() + end + end + + self:SetNextPrimaryFire(CurTime() + (60 / self:GetValue("RPM"))) +end + +function SWEP:IsQuickNadeAllowed() + return true // Otherwise it's useless! +end + +function SWEP:SecondaryAttack() + local nade = self:GetOwner():GetNWInt("ti_nade") + if nade != 12 and nade != 6 then + self:GetOwner():SetNWInt("ti_nade", 12) + end + self:PrimeGrenade() +end + +SWEP.AutoSpawnable = false + +if engine.ActiveGamemode() == "terrortown" then + SWEP.AutoSpawnable = false + SWEP.HolsterVisible = false + SWEP.Kind = WEAPON_EQUIP + SWEP.Slot = 6 + SWEP.CanBuy = { ROLE_TRAITOR } + SWEP.EquipMenuData = { + type = "Weapon", + desc = "A remote detonator for C4s and Breaching Charges.\nComes with 1 C4 and 3 Breaching Charges.", + } + + function SWEP:TTTBought(buyer) + buyer:GiveAmmo(1, "ti_c4") + buyer:GiveAmmo(3, "ti_charge") + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_civ_m320.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_civ_m320.lua new file mode 100644 index 0000000..e4f7b34 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_civ_m320.lua @@ -0,0 +1,116 @@ +SWEP.Base = "tacrp_m320" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK M320LE" +SWEP.AbbrevName = "M320LE" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.NoAimAssist = true + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "6Launcher" + +SWEP.Description = "Law Enforcement version of the M320 sanctioned for less-lethal munitions. Fires beanbag rounds that incapacitate on direct hit." +SWEP.Description_Quote = "That is one dangerous beanbag..." + +SWEP.Trivia_Caliber = "40mm Grenades" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "2008" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_m320.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_m320_civ.mdl" +SWEP.DefaultSkin = 1 + +SWEP.NoRanger = true + +SWEP.Slot = 4 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + }, +} + +// "ballistics" + +SWEP.Damage_Max = 100 +SWEP.Damage_Min = 100 +SWEP.Range_Max = 2000 +SWEP.Range_Min = 800 + +SWEP.ShootEnt = "tacrp_proj_40mm_beanbag" +SWEP.ShootEntForce = 7000 + +SWEP.Spread = 0.01 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 7000 + +SWEP.Num = 1 + +// sounds + +local path = "TacRP/weapons/m320/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" + +SWEP.Vol_Shoot = 100 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// anims + +SWEP.Attachments = { + [1] = { + PrintName = "Ammo", + Category = "ammo_40mm_civ", + AttachSound = "TacRP/weapons/m320/shell_in-1.wav", + DetachSound = "TacRP/weapons/m320/shell_out-1.wav", + }, + [2] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_holster"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Perk", + Category = {"perk", "perk_shooting", "perk_reload", "perk_melee"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_m320.Tube_Open", path .. "tube_open-1.wav") +addsound("TacInt_m320.Tube_close", path .. "tube_close-1.wav") +addsound("TacInt_m320.shell_out", path .. "shell_out-1.wav") +addsound("TacInt_m320.shell_in", path .. "shell_in-1.wav") +addsound("TacInt_m320.buttstock_back", path .. "buttstock_back-1.wav") +addsound("TacInt_m320.sight_flipup", path .. "sight_flipup-1.wav") + +SWEP.AutoSpawnable = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_glock.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_glock.lua new file mode 100644 index 0000000..78c588c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_glock.lua @@ -0,0 +1,358 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Glock 17" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "4Consumer" +SWEP.SubCatType = "1Pistol" + +SWEP.Description = "Polymer pistol with larger-than-standard capacity and a fast fire rate." +SWEP.Description_Quote = "Does not show up on airport metal detectors." + +SWEP.Trivia_Caliber = "9x19mm" +SWEP.Trivia_Manufacturer = "Glock Ges.m.b.H" +SWEP.Trivia_Year = "1982" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = "Assets: Twinke Masta \nSounds: Vunsunta \nAnimations: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint_extras/v_glock_new.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_glock_new.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 24, + Damage_Min = 12, + RecoilKick = 3, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 13, + Damage_Min = 5, + Range_Min = 500, + Range_Max = 1750, + RPM = 600, + RPMMultSemi = 1, + + RecoilMaximum = 10, + RecoilDissipationRate = 10, + RecoilSpreadPenalty = 0.0045, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 7, + Damage_Min = 3, + + HipFireSpreadPenalty = 0.012, + RecoilSpreadPenalty = 0.002, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + Description = "Lightweight polymer handgun with a high fire rate but below-average spread.", + HipFireSpreadPenalty = 0.01, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Pistol + +// "ballistics" + +SWEP.Damage_Max = 18 +SWEP.Damage_Min = 10 +SWEP.Range_Min = 500 // distance for which to maintain maximum damage +SWEP.Range_Max = 1800 // distance at which we drop to minimum damage +SWEP.Penetration = 3 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.45 +SWEP.ArmorBonus = 0.5 + +SWEP.MuzzleVelocity = 11000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 550 +SWEP.RPMMultSemi = 0.65 + +SWEP.Spread = 0.008 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 4 +SWEP.RecoilResetTime = 0.035 +SWEP.RecoilDissipationRate = 16 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 1.25 +SWEP.RecoilKick = 4 +SWEP.RecoilStability = 0.25 + +SWEP.RecoilSpreadPenalty = 0.005 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.9 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.25 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.5 + +SWEP.FreeAimMaxAngle = 2.5 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-125, 0, 45) +SWEP.BlindFireSuicidePos = Vector(25, 12, -5) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(-0.01, 0.35, 0) +SWEP.SightPos = Vector(-3.3, 0, -3.35) + +SWEP.CorrectivePos = Vector(0.02, -1, 0) +SWEP.CorrectiveAng = Angle(0.05, -0.05, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 18 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadUpInTime = 0.85 +SWEP.DropMagazineTime = 0.2 + +SWEP.ReloadTimeMult = 1.4 + +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/glock_new.mdl" +SWEP.DropMagazineImpact = "pistol" + +// sounds + +local path = "tacrp/weapons/p2000/p2000_" +local path2 = "tacrp_extras/glock/" + +SWEP.Sound_Shoot = "^" .. path2 .. "fire-1new.wav" +SWEP.Sound_Shoot_Silenced = path2 .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 + +SWEP.MuzzleEffect = "muzzleflash_pistol" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire_iron"] = "shoot2", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.6), + vm_ang = Angle(0, 2, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.slide", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(-15, 0, 0), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.1, + }, + }, +} + +SWEP.NoIdle = true --fixes animation bug, why was this set to false?? + +SWEP.ShootTimeMult = 0.45 + +SWEP.LastShot = true + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = "optic_pistol", + Bone = "ValveBiped.slide", + WMBone = "Box01", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 1, + WMScale = 1.4, + Pos_VM = Vector(0.21, 0, -0.15), + Ang_VM = Angle(0, 90, 180), + Pos_WM = Vector(0, -3, -1), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.barrel_assembly", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.6, + WMScale = 0.6, + Pos_VM = Vector(-0.5, 0.25, 7.25), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0.05, 8.9, -1.7), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.p2000_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + VMScale = 1, + WMScale = 1.3, + Pos_VM = Vector(-2.1, -0.21, 6.5), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(0.1, 5, -2.75), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_smg", "acc_holster", "acc_brace"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_glock.clip_in", path .. "clip_in.wav") +addsound("tacint_extras_glock.clip_in-mid", path .. "clip_in-mid.wav") +addsound("tacint_extras_glock.clip_out", path2 .. "magout.mp3") +addsound("tacint_extras_glock.slide_action", path .. "slide_action.wav") +addsound("tacint_extras_glock.slide_shut", path .. "slide_shut.wav") +addsound("tacint_extras_glock.cock_hammer", path .. "cockhammer.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_hecate.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_hecate.lua new file mode 100644 index 0000000..0dae813 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_hecate.lua @@ -0,0 +1,397 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "PGM Hécate II" +SWEP.AbbrevName = "Hécate II" + +SWEP.Category = "[FT] Оружие" // "Tactical RP (Extras)" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "7Sniper Rifle" + +SWEP.Description = "Heavy anti-materiel rifle that can kill in one shot. Too heavy to bash with. \nEquipped with a 12x scope by default." +SWEP.Description_Quote = "Gun Runner tested, NCR approved." + +SWEP.Trivia_Caliber = ".50 BMG" +SWEP.Trivia_Manufacturer = "PGM Précision" +SWEP.Trivia_Year = "1993" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Toby Burnside\nSource: Fallout 4: New Vegas Project" + +SWEP.ViewModel = "models/weapons/tacint_extras/v_hecate.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_hecate.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 80, + Damage_Min = 150, + Range_Min = 700, + Range_Max = 5000, + }, + [TacRP.BALANCE_TTT] = { // this is a buyable weapon in TTT + Damage_Max = 80, + Damage_Min = 200, + Range_Min = 500, + Range_Max = 5000, + + Penetration = 75, + RecoilDissipationRate = 0.5, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 90, + Damage_Min = 75, + Range_Min = 4000, + Range_Max = 8000, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.025, + } +} + +// "ballistics" + +SWEP.Damage_Max = 175 // damage at minimum range +SWEP.Damage_Min = 130 // damage at maximum range +SWEP.Range_Min = 1500 // distance for which to maintain maximum damage +SWEP.Range_Max = 9000 // distance at which we drop to minimum damage +SWEP.Penetration = 40 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 1.5 +SWEP.ArmorBonus = 5 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, // nobody is surviving this + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 +} + +SWEP.MuzzleVelocity = 20000 + +SWEP.ShootTimeMult = 1.35 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Bolt-Action" // only used externally for firemode name distinction + +SWEP.RPM = 25 + +SWEP.Spread = 0 + +SWEP.HipFireSpreadPenalty = 0.06 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.25 +SWEP.RecoilDissipationRate = 1.25 +SWEP.RecoilFirstShotMult = 1 +SWEP.RecoilMultCrouch = 1 + +SWEP.RecoilVisualKick = 5 +SWEP.RecoilKick = 10 +SWEP.RecoilStability = 0.75 + +SWEP.RecoilSpreadPenalty = 0.05 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.875 +SWEP.ShootingSpeedMult = 0.4 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.3 + +SWEP.AimDownSightsTime = 0.7 +SWEP.SprintToFireTime = 0.7 + +SWEP.Sway = 2.5 +SWEP.ScopedSway = 0.2 + +SWEP.FreeAimMaxAngle = 9 + +SWEP.Bipod = true +SWEP.BipodRecoil = 1 +SWEP.BipodKick = 0.15 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(2, -2, -6) + +SWEP.BlindFireAng = Angle(0, 15, -60) +SWEP.BlindFirePos = Vector(1, -2, -1) + +SWEP.BlindFireLeftAng = Angle(75, 0, 0) +SWEP.BlindFireLeftPos = Vector(8, 10, -6) + +SWEP.BlindFireRightAng = Angle(-75, 0, 0) +SWEP.BlindFireRightPos = Vector(-8, 12, -4) + +SWEP.BlindFireSuicideAng = Angle(0, 135, 0) +SWEP.BlindFireSuicidePos = Vector(-4, 44, -35) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0.02, 0.11, 0) +SWEP.SightPos = Vector(-3.835, -7.5, -4.07) + +SWEP.CorrectivePos = Vector(0.025, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 6, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/sniper.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 12 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true +SWEP.ScopeOverlaySize = 0.75 + +SWEP.CanMeleeAttack = false + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 7 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.AmmoTTT = "ti_sniper" +SWEP.Ammo_Expanded = "ti_sniper" + +SWEP.ReloadTimeMult = 1.4 +SWEP.DropMagazineImpact = "metal" +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/uratio.mdl" + +SWEP.ReloadUpInTime = 1.75 +SWEP.DropMagazineTime = 0.8 + +// sounds + +local path = "tacrp_extras/hecate/ax308_" + +SWEP.Sound_Shoot = "^tacrp_extras/hecate/ax308_fire_1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/ak47/ak47_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 1 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_m82_tacrp" +SWEP.EjectEffect = 2 +SWEP.EjectDelay = 1.15 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire"] = "shoot1", + ["blind_idle"] = "idle", + ["blind_fire"] = "shoot1", + ["reload"] = "reload", +} + +SWEP.BulletBodygroups = { + [1] = {4, 1}, +} + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {1, 2} + }, + BGs_WM = { + {1, 2} + }, + }, + ["irons"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + SortOrder = 2, + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["bipod"] = { + BGs_VM = { + {3, 1} + }, + BGs_WM = { + {3, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights_sniper", "optic_cqb", "optic_medium", "optic_sniper"}, + WMBone = "Box01", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + VMScale = 0.9, + Pos_VM = Vector(-5.75, 0, 5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 5.5, 2.2), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "barrel", + WMBone = "Box01", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-4.1, 0, 30), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 35, 0.25), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + WMBone = "Box01", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-3.9, -1.5, 19.25), + Ang_VM = Angle(90, 0, 270), + Pos_WM = Vector(-1.75, 22.5, -0.05), + Ang_WM = Angle(0, -90, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_sniper", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_amr"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_hecate.Clip_Out", path .. "magout.wav") +addsound("tacint_extras_hecate.Clip_In", path .. "magin.wav") +addsound("tacint_extras_hecate.Bolt_Back", path .. "boltrelease.wav") +addsound("tacint_extras_hecate.bolt_forward", path .. "boltback.wav") +addsound("tacint_extras_hecate.Bolt_Up", path .. "boltup.wav") +addsound("tacint_extras_hecate.bolt_down", path .. "boltdown.wav") + +if engine.ActiveGamemode() == "terrortown" then + SWEP.AutoSpawnable = false + SWEP.Kind = WEAPON_HEAVY + SWEP.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } + SWEP.EquipMenuData = { + type = "Weapon", + desc = "Heavy bolt-action anti-materiel rifle.\nComes with 10 rounds.\n\nBEWARE: May be visible while holstered!", + } + + function SWEP:TTTBought(buyer) + buyer:GiveAmmo(3, "ti_sniper") + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_m4a1.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_m4a1.lua new file mode 100644 index 0000000..70c1e7c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_m4a1.lua @@ -0,0 +1,358 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Colt M4A1" +SWEP.AbbrevName = "M4A1" +SWEP.Category = "[FT] Оружие" // "Tactical RP (Extras)" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "A true American classic boasting high fire rate and balanced performance." +SWEP.Description_Quote = "\"Burn 'em, Eldridge!\"" --The Hurt Locker (2008) + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "Colt" +SWEP.Trivia_Year = "1987" + +SWEP.Faction = TacRP.FACTION_NEUTRAL // This is older military kit so it's not uncommon to see some captured examples in the hands of bad guys. +SWEP.Credits = [[ +Model/Texture: Twinke Masta, DMG +Sound: Teh Strelok +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_m4a1.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_m4a1.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 14, + Damage_Min = 8, + Range_Min = 600, + Range_Max = 2000, + RPM = 750, + + RecoilSpreadPenalty = 0.0022, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 9, + Damage_Min = 5, + Range_Min = 600, + Range_Max = 2800, + RPM = 800, + + RecoilSpreadPenalty = 0.0017, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 15, + RecoilPerShot = 1, + RecoilDissipationRate = 10, + RecoilSpreadPenalty = 0.005, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 22 +SWEP.Damage_Min = 15 +SWEP.Range_Min = 1000 +SWEP.Range_Max = 2800 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 800 + +SWEP.Spread = 0.0069 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 35 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 0.75 + +SWEP.RecoilKick = 4.5 +SWEP.RecoilStability = 0.25 + +SWEP.RecoilSpreadPenalty = 0.002 +SWEP.HipFireSpreadPenalty = 0.04 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.8 +SWEP.SightedSpeedMult = 0.65 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.36 +SWEP.SprintToFireTime = 0.38 // multiplies how long it takes to recover from sprinting + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +SWEP.FreeAimMaxAngle = 4.5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -6) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, -4, -3) + +SWEP.SightAng = Angle(0.1, 0, 0) +SWEP.SightPos = Vector(-4.154, -7.5, -4.45) + +SWEP.CorrectivePos = Vector(-0.05, 0, 0.05) +SWEP.CorrectiveAng = Angle(0.03, 0.45, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/m4.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.3 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "TacRP/weapons/m4/m4_" +local path2 = "tacrp_extras/m4a1/m4a1_" + +SWEP.Sound_Shoot = "^" .. path2 .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path2 .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1.5, -0.05), + vm_ang = Angle(0, 0.1, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["sights"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["chmount"] = { + BGs_VM = { + {1, 0} + }, + BGs_WM = { + {1, 0} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-7.7, -0.05, 4), + Pos_WM = Vector(0.4, 3, 3.5), + } + }, + SortOrder = 2, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb_verytall", "optic_medium", "optic_sniper", "optic_ar"}, + InstalledElements = {"sights"}, + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1, + Pos_VM = Vector(-5.6, -0.075, 4), + Pos_WM = Vector(0.4, 3, 1.15), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90 + 3.5, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 0.9, + Pos_VM = Vector(-3.95, 0, 25.65), + Pos_WM = Vector(1.4, 26, -0.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90 + 3.5, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.9, -1.2, 13.5), + Pos_WM = Vector(2, 13, -0.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(-90, -90 + 3.5, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_m4a1.Remove_Clip", path2 .. "clipout.mp3") +addsound("tacint_extras_m4a1.Insert_Clip", path2 .. "clipin.mp3") +addsound("tacint_extras_m4a1.Insert_Clip-mid", path .. "insert_clip-mid.wav") +addsound("tacint_extras_m4a1.bolt_action", path .. "bolt_action.wav") +addsound("tacint_extras_m4a1.bolt_slap", path .. "bolt_slap.wav") +addsound("tacint_extras_m4a1.throw_catch", path .. "throw_catch.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_stinger.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_stinger.lua new file mode 100644 index 0000000..2d3ad34 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ex_stinger.lua @@ -0,0 +1,253 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "FIM-92 Stinger" +SWEP.AbbrevName = "Stinger" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.NoAimAssist = true + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "6Launcher" + +SWEP.Description = "Homing anti-air missile launcher. High blast damage but limited effect on armored targets.\nRequires a lock-on in order to fire." +SWEP.Description_Quote = "\"A cornered fox is more dangerous than a jackal!\"" + +SWEP.Trivia_Caliber = "Infrared Homing Missile" +SWEP.Trivia_Manufacturer = "Raytheon Missiles and Defense" +SWEP.Trivia_Year = "1967" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Modern Warfare 2\nAnimations: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_stinger.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_stinger.mdl" + +SWEP.NoRanger = true + +SWEP.Slot = 2 +SWEP.SlotAlt = 4 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + }, +} + +// "ballistics" + +SWEP.Damage_Max = 100 // just to fool the ratings +SWEP.Damage_Min = 100 +SWEP.Range_Max = 30000 +SWEP.Range_Min = 5000 + +SWEP.ShootEnt = "tacrp_proj_stinger" +SWEP.ShootEntForce = 2000 + +SWEP.Num = 1 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Single-Shot" // only used externally for firemode name distinction + +SWEP.RPM = 60 + +SWEP.Spread = 0.01 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.2// time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 5 +SWEP.RecoilVisualShake = 0.2 + +SWEP.RecoilKick = 0 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil + +SWEP.CanBlindFire = false + +SWEP.CannotHipFire = true + +// lockon + +// SWEP.LockOnAngle = math.cos(math.rad(5)) +SWEP.LockOnTrackAngle = 5 +SWEP.LockOnRange = 40000 + +SWEP.LockOnTime = 1 + +SWEP.ProvideTargetData = true + +SWEP.LockOnOutOfSights = false +SWEP.LockOnInSights = true + +SWEP.ShootOffset = Vector(0, 0, 0) +SWEP.ShootOffsetAngle = Angle(0, 0, 0) + +SWEP.RequireLockOn = true // Cannot shoot without a lock + +// handling + +SWEP.MoveSpeedMult = 0.8 +SWEP.ShootingSpeedMult = 0.25 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.2 + +SWEP.AimDownSightsTime = 0.6 +SWEP.SprintToFireTime = 0.8 // multiplies how long it takes to recover from sprinting + +SWEP.DeployTimeMult = 1.25 + +// hold types + +SWEP.HoldType = "passive" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeSighted = "rpg" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "ar2" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(40, -15, 0) +SWEP.PassivePos = Vector(4, 0, -4) + +SWEP.SprintMidPoint = { + Pos = Vector(0, 0, 0), + Ang = Angle(0, 0, 0) +} + +SWEP.BlindFireAng = Angle(0, 0, 0) +SWEP.BlindFirePos = Vector(1, -3, 0) + +SWEP.BlindFireLeftAng = Angle(75, 0, 0) +SWEP.BlindFireLeftPos = Vector(8, 10, -6) + +SWEP.BlindFireSuicideAng = Angle(0, 125, 0) +SWEP.BlindFireSuicidePos = Vector(-2, 25, -24) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(4, 0, -4) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-0.62, -10, -4.57) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_SPECIAL +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// sway + +SWEP.Sway = 3 +SWEP.ScopedSway = 0.25 + +SWEP.FreeAimMaxAngle = 5 + +// melee + +SWEP.CanMeleeAttack = false + +// reload + +SWEP.ClipSize = 1 +SWEP.Ammo = "rpg_round" + +// sounds + +local path = "TacRP/weapons/rpg7/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" + +SWEP.Sound_StartLockOn = "tacrp/check1.wav" +SWEP.Sound_FinishLockOn = "tacrp/locked1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_deagle" + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["blind_fire"] = "aimed_fire", + ["blind_idle"] = "aimed_idle", +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Ammo", + Category = {"ammo_stinger"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [2] = { + PrintName = "Accessory", + Category = {"acc", "acc_duffle", "acc_sling"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Perk", + Category = {"perk", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_rpg7.jingle", path .. "handling-1.wav") +addsound("TacInt_rpg7.insert_rocket", path .. "insert_rocket.wav") +addsound("TacInt_rpg7.flipup_sight", path .. "flipup_sight.wav") + +if engine.ActiveGamemode() == "terrortown" then + SWEP.AutoSpawnable = false + SWEP.Kind = WEAPON_EQUIP + SWEP.Slot = 6 + SWEP.CanBuy = { ROLE_TRAITOR } + SWEP.LimitedStock = true + SWEP.EquipMenuData = { + type = "Weapon", + desc = "Rocket launcher. Can't explode at point blank.\nComes with 2 rockets.\n\nBEWARE: May be visible while holstered!", + } + + function SWEP:TTTBought(buyer) + buyer:GiveAmmo(1, "RPG_Round") + end +end + +function SWEP:ShouldAutoReload() + return !self:GetValue("NoAutoReload") +end + +function SWEP:DoOldSchoolScopeBehavior() + return false +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_g36k.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_g36k.lua new file mode 100644 index 0000000..3acec6c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_g36k.lua @@ -0,0 +1,348 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK G36K" +SWEP.AbbrevName = "G36K" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Assault rifle with high muzzle velocity. Well suited for medium range sustained fire.\nEquipped with a 2x scope by default." +SWEP.Description_Quote = "\"Yeah, it was a good day.\"" -- Stargate SG-1 (S9E16) + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "1996" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_g36k_hq.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_g36k.mdl" + +SWEP.Slot = 2 + + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 18, + Damage_Min = 13, + Range_Min = 1200, + Range_Max = 2800, + + RPM = 550, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 9, + Damage_Min = 6, + + RecoilKick = 1.5, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.003, + HipFireSpreadPenalty = 0.009 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 20 +SWEP.Damage_Min = 16 +SWEP.Range_Min = 1200 // distance for which to maintain maximum damage +SWEP.Range_Max = 3200 // distance at which we drop to minimum damage +SWEP.Penetration = 9 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.775 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.4, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 37000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 750 + +SWEP.Spread = 0.0018 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 40 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 0.8 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.5 + +SWEP.RecoilSpreadPenalty = 0.0015 +SWEP.HipFireSpreadPenalty = 0.05 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.6 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.4 +SWEP.SprintToFireTime = 0.38 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +SWEP.FreeAimMaxAngle = 4.25 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-4.03, -2.5, -4.1) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/g36.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 2 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/g36k.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.4 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "TacRP/weapons/g36k/g36k_" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + }, + ["irons"] = { + BGs_VM = { + {3, 1} + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights", "optic_cqb_nookp7", "optic_medium"}, + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + InstalledElements = {"irons"}, + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1, + Pos_VM = Vector(-6.4, 0.14, 7), + Pos_WM = Vector(0, 0, 2.75), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-3.45, 0.075, 24.5), + Pos_WM = Vector(-0.25, 24, -1), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-3.75, -0.75, 17), + Pos_WM = Vector(0.9, 15, -1), + Ang_VM = Angle(90, 0, -80), + Ang_WM = Angle(-70, -90, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_g36k.remove_clip", path .. "remove_clip.wav") +addsound("TacInt_g36k.insert_clip", path .. "insert_clip.wav") +addsound("TacInt_g36k.bolt_action", path .. "bolt_action.wav") +addsound("TacInt_g36k.fire_select", path .. "fire_selector.wav") +addsound("TacInt_g36k.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_hk417.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_hk417.lua new file mode 100644 index 0000000..8cf1125 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_hk417.lua @@ -0,0 +1,340 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK HK417" +SWEP.AbbrevName = "HK417" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "5Battle Rifle" + +SWEP.Description = "Battle rifle with superb damage, fire rate and precision. Capable of automatic fire, although it is very unstable." +SWEP.Description_Quote = "\"Hey, he saved our asses back there, and right now he's all we got.\"" -- Spec Ops: The Line (2012) + +SWEP.Trivia_Caliber = "7.62x51mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "2006" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_hk417.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_hk417.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 38, + Damage_Min = 25, + + ShootingSpeedMult = 0.5, + RecoilStability = 0.5, + }, + [TacRP.BALANCE_TTT] = { + + Description = "Battle rifle with high rate of fire.", + + Damage_Max = 28, + Damage_Min = 20, + Range_Min = 600, + Range_Max = 2500, + RPM = 360, + + RecoilResetInstant = true, + RecoilResetTime = 0.15, + RecoilDissipationRate = 12, + RecoilMaximum = 9, + RecoilSpreadPenalty = 0.01, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 18, + Damage_Min = 14, + + RecoilDissipationRate = 10, + RecoilSpreadPenalty = 0.006, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.BattleRifle + +// "ballistics" + +SWEP.Damage_Max = 42 +SWEP.Damage_Min = 35 +SWEP.Range_Min = 1200 // distance for which to maintain maximum damage +SWEP.Range_Max = 4200 // distance at which we drop to minimum damage +SWEP.Penetration = 12 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.875 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 29500 + +// misc. shooting + +SWEP.Firemodes = {1, 2} + +SWEP.RPM = 700 + +SWEP.Spread = 0.0005 + +SWEP.ShootTimeMult = 0.4 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 7 +SWEP.RecoilResetTime = 0.04 +SWEP.RecoilDissipationRate = 13 +SWEP.RecoilFirstShotMult = 0.75 + +SWEP.RecoilVisualKick = 1.5 +SWEP.RecoilKick = 8 +SWEP.RecoilStability = 0.7 + +SWEP.RecoilSpreadPenalty = 0.005 +SWEP.HipFireSpreadPenalty = 0.06 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.875 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.6 + +SWEP.ReloadSpeedMult = 0.4 + +SWEP.AimDownSightsTime = 0.4 +SWEP.SprintToFireTime = 0.42 + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 5.5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, 0, 0) +SWEP.SightPos = Vector(-4.495, -7.5, -4.17) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/g36k.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.3 +SWEP.DropMagazineTime = 0.3 + +// sounds + +local path = "tacrp/weapons/hk417/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = "tacrp/weapons/sg551/sg551_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["sights"] = { + BGs_VM = { + {1, 1} + }, + }, + ["foldstock"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {1, 1} + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "ValveBiped._ROOT_HK417", + InstalledElements = {"sights"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.85, + Pos_VM = Vector(-4.7, 0.6, 5), + Pos_WM = Vector(10, 1.25, -6.25), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped._ROOT_HK417", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + Pos_VM = Vector(-2.9, 0.6, 24), + Pos_WM = Vector(27, 1.25, -4.25), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped._ROOT_HK417", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-3, -0.35, 15), + Pos_WM = Vector(19, 2.25, -4.4), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, 0, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_extmag_rifle2", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_hk417.clip_out", path .. "clip_out.wav") +addsound("tacint_hk417.clip_in", path .. "clip_in.wav") +addsound("tacint_hk417.bolt_action", path .. "bolt_action.wav") +addsound("tacint_hk417.bolt_latch", path .. "bolt_latch.wav") +addsound("tacint_hk417.fire_select", path .. "fire_select.wav") +addsound("tacint_hk417.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_degala.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_degala.lua new file mode 100644 index 0000000..d0468c6 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_degala.lua @@ -0,0 +1,360 @@ + +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Desert Eagle" +SWEP.AbbrevName = "Deagle" +SWEP.Category = "[FT] Оружие" // "Tactical RP (Extras)" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "2Magnum Pistol" + +SWEP.Description = "Imposing magnum pistol, as iconic as it gets.\nPowerful and high capacity, but recoil is hard to manage." +SWEP.Description_Quote = "\"You hear that, Mr. Anderson?\"" + +SWEP.Trivia_Caliber = ".50 AE" +SWEP.Trivia_Manufacturer = "Magnum Research" +SWEP.Trivia_Year = "1983" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Model: Vashts1985 +Texture: Racer445 +Sounds:Vunsunta, XLongWayHome +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_deagle.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_deagle.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 75, + Damage_Min = 45, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 45, + Damage_Min = 20, + + Range_Min = 100, + Range_Max = 1500, + + RPM = 150, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.125, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 + }, + + RecoilDissipationRate = 4.5, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 65, + Damage_Min = 30, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilDissipationRate = 20 + } +} + +// "ballistics" + +SWEP.Damage_Max = 80 +SWEP.Damage_Min = 50 +SWEP.Range_Min = 150 +SWEP.Range_Max = 1500 +SWEP.Penetration = 10 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.85 +SWEP.ArmorBonus = 5 + +SWEP.MuzzleVelocity = 10000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 150 + +SWEP.Spread = 0.009 + +SWEP.ShootTimeMult = 0.7 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 4 +SWEP.RecoilResetTime = 0.3 +SWEP.RecoilDissipationRate = 5 +SWEP.RecoilFirstShotMult = 0.9 + +SWEP.RecoilVisualKick = 5 +SWEP.RecoilKick = 15 +SWEP.RecoilStability = 0.5 +SWEP.RecoilAltMultiplier = 100 + +SWEP.RecoilSpreadPenalty = 0.02 +SWEP.HipFireSpreadPenalty = 0.02 +SWEP.PeekPenaltyFraction = 0.125 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.32 +SWEP.SprintToFireTime = 0.27 + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.6 + +SWEP.FreeAimMaxAngle = 4.5 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -2, -5) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(-0.05, -0.1, 0) +SWEP.SightPos = Vector(-3.28, 0, -4) + +SWEP.CorrectiveAng = Angle(0, 0, 0) +SWEP.CorrectivePos = Vector(0, 0, 0.1) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +// reload +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 7 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.ReloadTimeMult = 1.4 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/deagle.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 0.95 +SWEP.DropMagazineTime = 0.25 + +// sounds +local path = "tacint_extras/degala/" +local path1 = "tacrp/weapons/gsr1911/" +SWEP.Sound_Shoot = path .. "deagle-1.wav" +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects +SWEP.EjectEffect = 1 + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 4 +SWEP.MuzzleEffect = "muzzleflash_pistol_deagle" + +// anims +// VM: +// idle +// fire +// fire1, fire2... +// dryfire +// melee +// reload +// midreload +// prime_grenade +// throw_grenade +// throw_grenade_underhand +// deploy +// blind_idle +// blind_fire +// blind_fire1, blind_fire2... +// blind_dryfire +// WM: +// attack1 +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -2, -1.75), + vm_ang = Angle(0, 8, 0), + t = 0.4, + tmax = 0.5, + bones = { + { + bone = "ValveBiped.slide", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.35, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(-15, 0, 0), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Magnum + +SWEP.LastShot = true +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.GSR1911_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1, + WMScale = 1, + Pos_VM = Vector(-3.8, 0, 5.55), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, -1, -0.85), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = {"barrel"}, + Bone = "ValveBiped.GSR1911_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 0.4, + WMScale = 0.5, + Pos_VM = Vector(-0.76, 0.7, 7.35), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 9.25, -1.5), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.GSR1911_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + VMScale = 1.1, + WMScale = 1.3, + Pos_VM = Vector(-1.7, 0, 7), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(0, 5, -3), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_pistol2", "acc_holster", "acc_brace"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end +addsound("tacint_degala.clip_in", path .. "clip_in.wav") +addsound("tacint_degala.clip_in-mid", path1 .. "gsr1911_clip_in-mid.wav") +addsound("tacint_degala.clip_out", path .. "clip_out.wav") +addsound("tacint_degala.slide_action", path1 .. "gsr1911_slide_action.wav") +addsound("tacint_degala.slide_shut", path .. "sliderelease.wav") +addsound("tacint_degala.cock_hammer", path1 .. "gsr1911_cockhammer.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_fiveseven.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_fiveseven.lua new file mode 100644 index 0000000..7741012 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_fiveseven.lua @@ -0,0 +1,348 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "FN Five-seveN" +SWEP.AbbrevName = "Five-seveN" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "1Pistol" + +SWEP.Description = "Bulky PDW caliber pistol with excellent capacity.\nHigh velocity rounds retain effectiveness at range and pierces armor easily." +SWEP.Description_Quote = "The notorious \"mata-policias.\"" -- The Five-seveN has large purchase among Mexican cartels. The nickname translates to "cop-killer." + +SWEP.Trivia_Caliber = "5.7x28mm" +SWEP.Trivia_Manufacturer = "FN Herstal" +SWEP.Trivia_Year = "1998" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Assets: Counter-Strike: Online 2, edited by speedonerd +Sounds: Vunsunta, Counter-Strike: Online 2 +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_57.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_57.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 22, + Damage_Min = 20, + RPM = 500, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 20, + Damage_Min = 16, + + RPM = 240, + + RecoilMaximum = 5, + RecoilResetTime = 0.22, + RecoilDissipationRate = 7, + RecoilSpreadPenalty = 0.0075, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 14, + Damage_Min = 12, + RPM = 600, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Pistol + +// "ballistics" + +SWEP.Damage_Max = 27 +SWEP.Damage_Min = 20 +SWEP.Range_Min = 1200 +SWEP.Range_Max = 3000 +SWEP.Penetration = 10 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.95 +SWEP.ArmorBonus = 2 + +SWEP.MuzzleVelocity = 22500 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 550 +SWEP.RPMMultSemi = 0.65 + +SWEP.Spread = 0.001 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 8 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 12 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 4 +SWEP.RecoilStability = 0.2 + +SWEP.RecoilSpreadPenalty = 0.0025 +SWEP.HipFireSpreadPenalty = 0.025 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.28 +SWEP.SprintToFireTime = 0.28 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.5 + +SWEP.FreeAimMaxAngle = 4 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0.25, -2, -5.25) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-130, 0, 45) +SWEP.BlindFireSuicidePos = Vector(25, 15, -6) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(0.1, 0.55, 0) +SWEP.SightPos = Vector(-3.44, 0, -3.75) + +SWEP.CorrectivePos = Vector(0, 0, 0) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pdw" + +SWEP.ReloadTimeMult = 1.35 + +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/57.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 0.85 +SWEP.DropMagazineTime = 0.2 + +// sounds + +local path = "tacrp/weapons/p2000/p2000_" +local path1 = "tacint_shark/57/" + +SWEP.Sound_Shoot = "^" .. path1 .. "fiveseven-1.wav" +SWEP.Sound_Shoot_Silenced = path1 .. "usp1.wav" + + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 4 +SWEP.EjectEffect = 2 + +SWEP.MuzzleEffect = "muzzleflash_pistol" + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire_iron"] = "shoot2", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.7), + vm_ang = Angle(0, 2, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.slide", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(-15, 0, 0), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +SWEP.NoIdle = true + +SWEP.ShootTimeMult = 0.5 + +SWEP.LastShot = true + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = "optic_pistol", + Bone = "ValveBiped.slide", + WMBone = "Box01", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 1, + WMScale = 1, + Pos_VM = Vector(0.04, 0.25, -0.4), + Ang_VM = Angle(0, 90, 180), + Pos_WM = Vector(-0.2, -1, -0.75), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.p2000_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.55, + WMScale = 0.6, + Pos_VM = Vector(-3.2, -0.05, 10), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(-0.1, 9, -1.5), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.p2000_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + VMScale = 1.1, + WMScale = 1.3, + Pos_VM = Vector(-2, -0.05, 6), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(-0.2, 5, -2.75), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_pistol", "acc_holster", "acc_brace"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_57.clip_in", path1 .. "p228_clipin.wav") +addsound("tacint_57.clip_in-mid", path1 .. "p228_clipin.wav") +addsound("tacint_57.clip_out", path1 .. "magout.wav") +addsound("tacint_57.slide_action", path1 .. "fiveseven_slidepull.wav") +addsound("tacint_57.slide_shut", path1 .. "sliderelease.wav") +addsound("tacint_57.cock_hammer", path .. "cockhammer.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_glock18.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_glock18.lua new file mode 100644 index 0000000..e59f774 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_glock18.lua @@ -0,0 +1,356 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Glock 18C" +SWEP.Category = "[FT] Оружие" // "Tactical RP (Extras)" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "3Machine Pistol" + +SWEP.Description = "Machine pistol with high fire rate and mobility." +SWEP.Description_Quote = "\"Sooner or later, you'll have to jump.\"" + +SWEP.Trivia_Caliber = "9x19mm" +SWEP.Trivia_Manufacturer = "Glock Ges.m.b.H" +SWEP.Trivia_Year = "1982" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Model: Hav0k101 +Texture: el maestro de graffiti +Sound: BlitzBoaR, Lorn, Ghost597879, Zeven II +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_glock18.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_glock18.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 20, + Damage_Min = 6, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 10, + Damage_Min = 4, + Range_Min = 200, + Range_Max = 1750, + RPM = 1000, + + HipFireSpreadPenalty = 0.015, + RecoilMaximum = 10, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 6, + Damage_Min = 2, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilDissipationRate = 25, + RecoilMaximum = 10 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachinePistol + +// "ballistics" + +SWEP.Damage_Max = 18 +SWEP.Damage_Min = 6 +SWEP.Range_Min = 400 +SWEP.Range_Max = 1500 +SWEP.Penetration = 3 +SWEP.ArmorPenetration = 0.425 +SWEP.ArmorBonus = 0.5 + +SWEP.MuzzleVelocity = 10500 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemodes = {2, 1} + +SWEP.RPM = 1000 + +SWEP.Spread = 0.011 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 7 +SWEP.RecoilResetTime = 0.1 +SWEP.RecoilDissipationRate = 15 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 2 +SWEP.RecoilKick = 4 + +SWEP.RecoilSpreadPenalty = 0.005 +SWEP.HipFireSpreadPenalty = 0.024 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.25 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.5 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-125, 0, 45) +SWEP.BlindFireSuicidePos = Vector(25, 12, -5) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-3.31, 0, -3.2) + +SWEP.CorrectivePos = Vector(0.02, -1, 0) +SWEP.CorrectiveAng = Angle(0.05, -0.05, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1.4 + +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/glock.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 0.85 +SWEP.DropMagazineTime = 0.2 + +// sounds + +local path = "tacrp/weapons/p2000/p2000_" +local path2 = "tacint_extras/glock18/" + +SWEP.Sound_Shoot = "^" .. path2 .. "glock18-1.wav" +SWEP.Sound_Shoot_Silenced = "tacrp_extras/glock/" .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 4 + +SWEP.MuzzleEffect = "muzzleflash_pistol" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire_iron"] = "shoot2", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.6), + vm_ang = Angle(0, 2, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.slide", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(-15, 0, 0), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +SWEP.NoIdle = true + +SWEP.ShootTimeMult = 0.4 + +SWEP.LastShot = true + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = "optic_pistol", + Bone = "ValveBiped.slide", + WMBone = "ValveBiped.Bip01_R_Hand", // someone forgor to rig the box bone + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.9, + WMScale = 1.2, + Pos_VM = Vector(0.21, 0.7, -0.05), + Ang_VM = Angle(0, 90, 180), + Pos_WM = Vector(2, 1.2, -4.5), + Ang_WM = Angle(180, 0, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.barrel_assembly", + WMBone = "ValveBiped.Bip01_R_Hand", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.5, + WMScale = 0.6, + Pos_VM = Vector(-0.5, 0.25, 7), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(14.2, 1.2, -4.25), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.p2000_rootbone", + WMBone = "ValveBiped.Bip01_R_Hand", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + VMScale = 1.1, + WMScale = 1.2, + Pos_VM = Vector(-2.1, -0.23, 6.6), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(10.5, 1.2, -2.75), + Ang_WM = Angle(0, 0, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_smg", "acc_holster", "acc_brace"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_glock18.clip_in", path2 .. "Clipin.wav") +addsound("tacint_extras_glock18.clip_in-mid", path .. "clip_in-mid.wav") +addsound("tacint_extras_glock18.clip_out", path2 .. "Clipout.wav") +addsound("tacint_extras_glock18.slide_action", path .. "slide_action.wav") +addsound("tacint_extras_glock18.slide_shut", path .. "slide_shut.wav") +addsound("tacint_extras_glock18.cock_hammer", path .. "cockhammer.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_k98.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_k98.lua new file mode 100644 index 0000000..7e5a5a5 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_k98.lua @@ -0,0 +1,408 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Karabiner 98k" +SWEP.AbbrevName = "Kar98k" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "5Value" +SWEP.SubCatType = "6Marksman Rifle" + +SWEP.Description = "Antique bolt-action rifle with an enduring design. Powerful up close, but is essentially obsolete on the modern battlefield." +SWEP.Description_Quote = "\"Do you want total war?\"" + +SWEP.Trivia_Caliber = "7.92x57mm Mauser" +SWEP.Trivia_Manufacturer = "Mauser" +SWEP.Trivia_Year = "1935" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Model: Day of Defeat: Source, edited by 8Z +Texture: Cafe Rev., rascal, 5hifty +Sound: rzen1th +Animations: speedonerd +]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_k98.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_k98.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 80, + Damage_Min = 55, + + HipFireSpreadPenalty = 0.03, + Spread = 0.002, + RPM = 50, + ShootTimeMult = 0.75, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 70, + Damage_Min = 30, + Range_Min = 500, + Range_Max = 4000, + + RPM = 35, + ShootTimeMult = 1.1, + Spread = 0.001, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 50, + Damage_Min = 30, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.01, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SniperRifle + +// "ballistics" + +SWEP.Damage_Max = 85 +SWEP.Damage_Min = 46 +SWEP.Range_Min = 300 +SWEP.Range_Max = 2800 +SWEP.Penetration = 12 +SWEP.ArmorPenetration = 0.8 +SWEP.ArmorBonus = 1 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 +} + +SWEP.MuzzleVelocity = 32000 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Bolt-Action" // only used externally for firemode name distinction + +SWEP.RPM = 45 + +SWEP.Spread = 0.004 // WW2 rifles weren't all that accurate... + +SWEP.HipFireSpreadPenalty = 0.04 +SWEP.PeekPenaltyFraction = 0.3 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 5 + +SWEP.RecoilKick = 5 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.55 + +SWEP.ReloadSpeedMult = 0.3 + +SWEP.AimDownSightsTime = 0.34 +SWEP.SprintToFireTime = 0.4 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 7 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "shotgun" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SHOTGUN + +SWEP.PassiveAng = Angle(0, 2, 0) +SWEP.PassivePos = Vector(0, 1, -0.5) + +SWEP.BlindFireAng = Angle(0, 0, -45) +SWEP.BlindFirePos = Vector(1, 0, 5) + +SWEP.BlindFireLeftAng = Angle(75, 0, -20) +SWEP.BlindFireLeftPos = Vector(8, 10, -2) + +SWEP.BlindFireRightAng = Angle(-75, 0, -45) +SWEP.BlindFireRightPos = Vector(-9, 17, -5) + +SWEP.BlindFireSuicideAng = Angle(0, 115, 0) +SWEP.BlindFireSuicidePos = Vector(-4.5, 25, -45) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(2, 4.5, 0.75) + +SWEP.SightAng = Angle(0, 0.4, 0) +SWEP.SightPos = Vector(-4.72, 1, 2.15) + +SWEP.CorrectivePos = Vector(0.05, 0, 0.2) +SWEP.CorrectiveAng = Angle(0.1, -0.3, 0) + +SWEP.CustomizePos = Vector(4, 3, -1.5) +SWEP.CustomizeAng = Angle(30, 15, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 4, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeFOV = 90 / 1.25 + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 5 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_rifle" + +SWEP.ShotgunReload = true +SWEP.ShotgunThreeload = false +SWEP.ShotgunFullCancel = true +SWEP.ShotgunNoReverseStart = true + +SWEP.ReloadTimeMult = 0.8 +SWEP.ShootTimeMult = 0.65 +SWEP.DropMagazineModel = false + +SWEP.ShotgunUpInTime = 2.3 +SWEP.ReloadUpInTime = 2.2 + +SWEP.BulletBodygroups = { + [1] = {1, 1}, + [2] = {1, 0}, +} + +// sounds + +// local path = "TacRP/weapons/spr/" +local path1 = "tacint_extras/k98/" + +SWEP.Sound_Shoot = "^" .. path1 .. "scout_fire-1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/ak47/ak47_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_1" +SWEP.EjectEffect = 2 +SWEP.EjectDelay = 0.7 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2"}, + ["blind_idle"] = "idle", + ["blind_dryfire"] = "dryfire", + ["blind_fire"] = "shoot1", + ["reload"] = "reload", + ["reload_finish"] = "reload_end", + ["reload_clip"] = "reload_clip", + ["melee"] = {"melee1", "melee2"}, + --["melee"] = "melee_bayonet", + ["dryfire"] = "dryfire", + ["jam"] = "reload_end" +} + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["optic_clip"] = { + BGs_VM = { + {2, 0} + }, + BGs_WM = { + {1, 0} + }, + }, + ["scope"] = { + BGs_VM = { + {2, 2} + }, + BGs_WM = { + {1, 2}, + {2, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Top", + Category = {"optic_cqb_nookp7", "optic_okp7", "optic_medium", "optic_kar98", "stripper_clip"}, + WMBone = "Bone02", + Bone = "k98_root", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + VMScale = 0.9, + Pos_VM = Vector(0.05, -2.8, 7), + Ang_VM = Angle(90, 0, -90), + Pos_WM = Vector(0, 1.25, -5.6), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + WMBone = "Bone02", + Bone = "k98_root", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 0.75, + WMScale = 0.75, + Pos_VM = Vector(-0.03, -1.45, 34), + Pos_WM = Vector(25.5, 1.2, -4.9), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom"}, + WMBone = "Bone02", + Bone = "k98_root", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-0.5, -1, 22), + Pos_WM = Vector(8, 2, -4), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = CHAN_AUTO, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_k98.Clip_Out", path1 .. "magout.wav") +addsound("tacint_extras_k98.Clip_In", path1 .. "magin.wav") +addsound("tacint_extras_k98.CockBack", path1 .. "boltback.wav") +addsound("tacint_extras_k98.CockForward", path1 .. "boltforward.wav") +addsound("tacint_extras_k98.safety", path1 .. "magrelease.wav") +addsound("tacint_extras_k98.InsertShell", path1 .. "roundinsert.wav") +addsound("tacint_extras_k98.ClipIn", path1 .. "clipin2.wav") +addsound("tacint_extras_k98.ClipIn2", path1 .. "roundinsert_clip.wav") +addsound("tacint_extras_k98.bolt_up", path1 .. "boltup.wav") +addsound("tacint_extras_k98.bolt_down", path1 .. "boltdown.wav") +--addsound("tacint_extras_k98.bolt_up", path1 .. "boltlatch.wav") +--addsound("tacint_extras_k98.bolt_down", path1 .. "boltrelease.wav") + + diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_p226.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_p226.lua new file mode 100644 index 0000000..199659b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_p226.lua @@ -0,0 +1,360 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "SIG P226" // Apparently this is specifically the XM25 model adopted by the Navy SEALs but i dont wanna specify it +SWEP.AbbrevName = "P226" +SWEP.Category = "[FT] Оружие" // "Tactical RP (Extras)" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "1Pistol" + +SWEP.Description = "Handgun with superior range and precision but low capacity." +SWEP.Description_Quote = "\"The correct term is 'babes,' sir.\"" + +SWEP.Trivia_Caliber = ".40 S&W" // ... because the XM25 is 9mm only and doesn't come in 13 rounders +SWEP.Trivia_Manufacturer = "SIG Sauer AG" +SWEP.Trivia_Year = "1984" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Model: SoulSlayer +Texture: Thanez +Sound: Anders, DMG, FxDarkloki, & Thanez +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_p226.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_p226.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 28, + Damage_Min = 20, + Range_Min = 1200, + Range_Max = 3000, + RPM = 450, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 + }, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 24, + Damage_Min = 16, + Range_Min = 750, + Range_Max = 3000, + ClipSize = 10, + RPM = 180, + RPMMultSemi = 1, + + RecoilMaximum = 4, + RecoilResetTime = 0.2, + RecoilDissipationRate = 5, + RecoilFirstShotMult = 1, + RecoilSpreadPenalty = 0.008, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Pistol + +// "ballistics" + +SWEP.Damage_Max = 25 +SWEP.Damage_Min = 15 +SWEP.Range_Min = 800 +SWEP.Range_Max = 2500 +SWEP.Penetration = 6 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.75 +SWEP.ArmorBonus = 1 + +SWEP.MuzzleVelocity = 14000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 380 +SWEP.RPMMultSemi = 0.75 + +SWEP.Spread = 0.001 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 5 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 7 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 1.5 +SWEP.RecoilKick = 3.5 +SWEP.RecoilStability = 0.35 + +SWEP.RecoilSpreadPenalty = 0.0025 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.9 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.25 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.5 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -2, -5) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(0.02, 0.15, 0) +SWEP.SightPos = Vector(-3.45, 0, -3.35) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 13 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pistol_heavy" + +SWEP.ReloadTimeMult = 1 + +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/p226.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 0.85 +SWEP.DropMagazineTime = 0.2 + +// sounds + +local path = "tacint_extras/p226/" +local path1 = "tacrp/weapons/p250/p250_" +SWEP.Sound_Shoot = "^" .. path .. "p228-1.wav" +SWEP.Sound_Shoot_Silenced = path1 .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 4 + +SWEP.MuzzleEffect = "muzzleflash_pistol" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.6), + vm_ang = Angle(0, 2, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.slide", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(-15, 0, 0), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +SWEP.LastShot = true + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = "optic_pistol", + Bone = "ValveBiped.slide", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1, + WMScale = 1.2, + Pos_VM = Vector(0.01, -0.3, -0.1), + Ang_VM = Angle(0, 90, 180), + Pos_WM = Vector(0.04, -1.4, -1), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.barrel_assembly", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 0.5, + WMScale = 0.5, + Pos_VM = Vector(-0.6, 0.43, 6.4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 8.2, -1.5), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.p250_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + VMScale = 1.1, + WMScale = 1, + Pos_VM = Vector(-2, 0, 5.25), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(0, 4.5, -2.9), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_pistol", "acc_holster", "acc_brace"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_p226.clip_in", path .. "magshove.wav") +addsound("tacint_p226.clip_in-mid", path .. "magshove.wav") +addsound("tacint_p226.clip_out", path .. "magout.wav") +addsound("tacint_p226.slide_action", path1 .. "slide_action.wav") +addsound("tacint_p226.slide_shut", path .. "sliderelease.wav") +addsound("tacint_p226.cock_hammer", path .. "cockhammer.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_rpk.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_rpk.lua new file mode 100644 index 0000000..7e96a07 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_rpk.lua @@ -0,0 +1,359 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "РПК-74" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "5Machine Gun" + +SWEP.Description = "Light machine gun derived from an infantry rifle. High damage and good recoil, but mobility and spread is poor." +SWEP.Description_Quote = "A simple yet effective concept." + +SWEP.Trivia_Caliber = "7.62x39mm" +SWEP.Trivia_Manufacturer = "Molot" // I checked, neither Izhmash nor Tula ever manufactured the original RPK. +SWEP.Trivia_Year = "1961" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Assets: Call To Arms +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_rpk.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_rpk.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 35, + Damage_Min = 24, + Spread = 0.003, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 20, + Damage_Min = 15, + + Range_Min = 800, + Range_Max = 3500, + + ClipSize = 50, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 12, + Damage_Min = 5, + Spread = 0.005, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 20, + HipFireSpreadPenalty = 0.015 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachineGun + +// "ballistics" + +SWEP.Damage_Max = 30 +SWEP.Damage_Min = 19 +SWEP.Range_Min = 1000 // distance for which to maintain maximum damage +SWEP.Range_Max = 4500 // distance at which we drop to minimum damage +SWEP.Penetration = 10 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.775 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 550 + +SWEP.Spread = 0.0075 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 20 +SWEP.RecoilResetTime = 0.22 +SWEP.RecoilDissipationRate = 20 +SWEP.RecoilFirstShotMult = 2 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 4 +SWEP.RecoilStability = 0.4 +SWEP.RecoilAltMultiplier = 300 + +SWEP.RecoilSpreadPenalty = 0.0012 +SWEP.HipFireSpreadPenalty = 0.035 +SWEP.PeekPenaltyFraction = 0.25 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.875 +SWEP.ShootingSpeedMult = 0.5 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.25 + +SWEP.AimDownSightsTime = 0.45 +SWEP.SprintToFireTime = 0.5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, -0.7, 0) +SWEP.SightPos = Vector(-4.68, -7.5, -2.9) + +SWEP.CorrectivePos = Vector(0, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.75, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 6, -8) +SWEP.HolsterAng = Angle(0, 0, 0) + +SWEP.Sway = 2 +SWEP.ScopedSway = 0.175 + +SWEP.FreeAimMaxAngle = 6 + +SWEP.Bipod = true +SWEP.BipodRecoil = 0.4 +SWEP.BipodKick = 0.4 + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 50 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1.3 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/rpk.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.55 +SWEP.DropMagazineTime = 0.6 + +// sounds + +local path = "tacrp/weapons/ak47/ak47_" +local path1 = "tacint_shark/rpk/" +local path2 = "tacrp_extras/ak47/" + +SWEP.Sound_Shoot = "^" .. path1 .. "shoot-2.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload", +} + +SWEP.DeployTimeMult = 3 + +// attachments + +SWEP.AttachmentElements = { + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["bipod"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 0} + }, + BGs_WM = { + {2, 0} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5.3, 0.4, 3), + Pos_WM = Vector(-0.4, 2, 0.5), + } + }, + SortOrder = 2, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper", "optic_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.8, + Pos_VM = Vector(-5.3, 0.15, 4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 3, 0.5), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = {"silencer", "muzz_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.9, + Pos_VM = Vector(-3.35, 0.15, 33), + Pos_WM = Vector(0, 31, -1.75), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.2, -0.1, 25), + Pos_WM = Vector(-0.5, 19, -1.75), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "extendedbelt", "acc_bipod"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_rpk.remove_clip", path1 .. "rpk_magout.wav") +addsound("tacint_rpk.insert_clip", path1 .. "rpk_magin.wav") +addsound("tacint_rpk.boltaction", path2 .. "bolt.mp3") +addsound("tacint_rpk.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_saiga.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_saiga.lua new file mode 100644 index 0000000..2698710 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_saiga.lua @@ -0,0 +1,324 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Сайга-12К" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "5Shotgun" + +SWEP.Description = "High capacity shotgun feeding from a box magazine, suitable for spraying down a room." +SWEP.Description_Quote = "Some people call it the SASG." -- Splinter Cell: Blacklist, Rainbow Six: Siege, and The Division 2 all do this + +SWEP.Trivia_Caliber = "12 Gauge" +SWEP.Trivia_Manufacturer = "Kalashnikov Concern" +SWEP.Trivia_Year = "1997" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Assets: Battlefield 3 +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_saiga.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_saiga.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 6, + Damage_Min = 2, + Range_Min = 200, + Range_Max = 2500, + Num = 6, + RPM = 200, + RPMMultSemi = 1, + + Spread = 0.02, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AutoShotgun + +// "ballistics" + +SWEP.ShootTimeMult = 0.85 + +SWEP.Damage_Max = 12 +SWEP.Damage_Min = 3 +SWEP.Range_Min = 100 // distance for which to maintain maximum damage +SWEP.Range_Max = 1200 // distance at which we drop to minimum damage +SWEP.Penetration = 1 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.6 +SWEP.ArmorBonus = 0.5 + +SWEP.Num = 6 + +SWEP.MuzzleVelocity = 10000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 300 +SWEP.RPMMultSemi = 0.85 +SWEP.PostBurstDelay = 0.1 + +SWEP.Spread = 0.04 +SWEP.ShotgunPelletSpread = 0.03 + +SWEP.HipFireSpreadPenalty = 0.015 +SWEP.MidAirSpreadPenalty = 0 +SWEP.MoveSpreadPenalty = 0 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 5 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 8 +SWEP.RecoilFirstShotMult = 1.25 + +SWEP.RecoilVisualKick = 1.5 +SWEP.RecoilKick = 7 +SWEP.RecoilStability = 0.25 +SWEP.RecoilAltMultiplier = 150 +SWEP.NoRecoilPattern = true + +SWEP.RecoilSpreadPenalty = 0.009 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.6 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.4 +SWEP.SprintToFireTime = 0.44 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.2 + +SWEP.FreeAimMaxAngle = 5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -6) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(4, -2, -4) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, -1, 0) +SWEP.SightPos = Vector(-4.66, -7.5, -3.05) + +SWEP.CorrectivePos = Vector(0, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.75, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 8 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "buckshot" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1.15 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/saiga.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + + +// sounds + +local path = "tacint_shark/saiga/" +local path1 = "tacrp/weapons/ak47/ak47_" + +SWEP.Sound_Shoot = "^" .. path .. "12k_fire.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 0 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_m3" +SWEP.EjectEffect = 3 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire1"] = {"fire3_M", "fire3_R"}, + ["fire2"] = {"fire4_M", "fire4_L"}, + ["fire3"] = {"fire5_M", "fire5_L", "fire5_R"}, + ["fire4"] = {"fire5_M", "fire5_L", "fire5_R"}, + ["fire5"] = {"fire5_M", "fire5_L", "fire5_R"}, + ["fire_iron"] = "fire1_M", + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload" +} + +SWEP.DeployTimeMult = 1.1 + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb_nookp7", "optic_medium"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.8, + Pos_VM = Vector(-5.25, 0.1, 5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 2.5, 1), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-4.5, -0.2, 14), + Pos_WM = Vector(0, 19, -2), + Ang_VM = Angle(90, 0, -60), + Ang_WM = Angle(0, -90, 180), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc_foldstock", "acc", "acc_sling", "acc_duffle", "acc_extmag_shotgun_mag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_shotgun2"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_saiga.remove_clip", path1 .. "remove_clip.wav") +addsound("tacint_saiga.insert_clip", path1 .. "insert_clip.wav") +addsound("tacint_saiga.boltaction", path .. "12k_boltpull.wav") +addsound("tacint_saiga.Buttstock_Back", path1 .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_scarh.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_scarh.lua new file mode 100644 index 0000000..6801392 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_scarh.lua @@ -0,0 +1,353 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "FN SCAR-H CQC" +SWEP.AbbrevName = "SCAR-H" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "5Battle Rifle" + +SWEP.Description = "Compact, high mobility battle rifle with swift handling." +SWEP.Description_Quote = "\"Sand Bravo, we're reading 70 bogeys in your sector.\"" + +SWEP.Trivia_Caliber = "7.62x51mm" +SWEP.Trivia_Manufacturer = "FN America" +SWEP.Trivia_Year = "2004" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Assets: Counter-Strike: Online 2, edited by speedonerd +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_scarh.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_scarh.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 38, + Damage_Min = 26, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 25, + Damage_Min = 12, + Range_Min = 500, + Range_Max = 2500, + RPM = 400, + + RecoilResetTime = 0.125, + RecoilDissipationRate = 5, + RecoilMaximum = 5, + RecoilSpreadPenalty = 0.015, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 17, + Damage_Min = 12, + RPM = 450, + + RecoilDissipationRate = 8, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.006, + RecoilMaximum = 10 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.BattleRifle + +// "ballistics" + +SWEP.Damage_Max = 38 +SWEP.Damage_Min = 24 +SWEP.Range_Min = 800 +SWEP.Range_Max = 2800 +SWEP.Penetration = 20 +SWEP.ArmorPenetration = 0.8 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.35, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 26000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 550 + +SWEP.Spread = 0.001 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 7 +SWEP.RecoilResetTime = 0.12 +SWEP.RecoilDissipationRate = 12 +SWEP.RecoilFirstShotMult = 0.85 + +SWEP.RecoilVisualKick = 2 +SWEP.RecoilKick = 6 +SWEP.RecoilStability = 0.75 + +SWEP.RecoilSpreadPenalty = 0.005 +SWEP.HipFireSpreadPenalty = 0.04 +SWEP.PeekPenaltyFraction = 0.25 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.825 +SWEP.SightedSpeedMult = 0.65 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.35 +SWEP.SprintToFireTime = 0.38 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 5 + +SWEP.Bipod = false +SWEP.BipodRecoil = 0.5 +SWEP.BipodKick = 0.4 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SightAng = Angle(0.07, 0.45, 0) +SWEP.SightPos = Vector(-4.36, -7, -4.77) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.CorrectivePos = Vector(0, 2, 0) +SWEP.CorrectiveAng = Angle(-0.1, 0.45, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/scarh.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.1 +SWEP.DropMagazineTime = 0.3 + +// sounds + +local path = "tacrp/weapons/dsa58/dsa58_" +local path1 = "tacint_shark/scarh/" + +SWEP.Sound_Shoot = "^" .. path1 .. "scarh-1.wav" +SWEP.Sound_Shoot_Silenced = "^" .. path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_g3" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, + bones = { + { + bone = "ValveBiped.bolt_cover", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.1, + }, + } +} + +// attachments + +SWEP.AttachmentElements = { + ["sights"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {4, 1} + } + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "ValveBiped._ROOT_K1a", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"sights"}, + VMScale = 1, + Pos_VM = Vector(-5.25, 0.19, 3.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 2.5, 1.3), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped._ROOT_K1a", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-2.85, 0.35, 23), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 21, -0.9), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped._ROOT_K1a", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + VMScale = 1.1, + Pos_VM = Vector(-2.8, -0.75, 15), + Ang_VM = Angle(90, 0, -90), + Pos_WM = Vector(1, 11.5, -0.75), + Ang_WM = Angle(0, -90, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "acc_extmag_rifle2", "acc_bipod"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_scar.insert_clip", path1 .. "scarh_clipin.wav") +addsound("tacint_scar.insert_clip-mid", path1 .. "scarh_clipin.wav") +addsound("tacint_scar.remove_clip", path1 .. "scarh_clipout.wav") +addsound("tacint_scar.Handle_FoldDown", path .. "handle_folddown.wav") +addsound("tacint_scar.bolt_back", path1 .. "scarh_boltpull.wav") +addsound("tacint_scar.bolt_release", path .. "bolt_shut.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sg550.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sg550.lua new file mode 100644 index 0000000..4926789 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sg550.lua @@ -0,0 +1,375 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "SIG SG 550-1 Sniper" +SWEP.AbbrevName = "SG 550-1" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "6Marksman Rifle" + +SWEP.Description = "Carbine caliber marksman rifle with fast automatic fire. Easy to control in short bursts and has high armor penetration. Equipped with a 6x scope by default." +SWEP.Description_Quote = "\"I work alone, like you. We always work alone.\"" -- The Bourne Identity (2002) + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "SIG Sauer AG" +SWEP.Trivia_Year = "1988" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Model: Hav0c & Twinke Masta +Texture: Twinke Masta +Sound: Farion, Treyarch & Tactical Intervention +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_sg550_sniper.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_sg550_sniper.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 25, + Damage_Min = 15, + + RecoilKick = 3, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 22, + Damage_Min = 10, + RPM = 450, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 20, + Damage_Min = 12, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.005, + HipFireSpreadPenalty = 0.01, + RecoilDissipationRate = 8, + RecoilMaximum = 15 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MarksmanRifle + +SWEP.MuzzleVelocity = 28000 + +// "ballistics" + +SWEP.Damage_Max = 28 +SWEP.Damage_Min = 17 +SWEP.Range_Min = 1800 +SWEP.Range_Max = 4500 +SWEP.Penetration = 10 +SWEP.ArmorPenetration = 0.95 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + 1, + 2, +} + +SWEP.RPM = 700 +SWEP.RPMMultSemi = 0.8 + +SWEP.Spread = 0.0005 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 6 +SWEP.RecoilResetTime = 0.07 +SWEP.RecoilDissipationRate = 24 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 0.75 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.45 +SWEP.RecoilAltMultiplier = 750 + +SWEP.RecoilSpreadPenalty = 0.0029 +SWEP.HipFireSpreadPenalty = 0.06 +SWEP.PeekPenaltyFraction = 0.15 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.3 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.4 + +SWEP.AimDownSightsTime = 0.42 +SWEP.SprintToFireTime = 0.45 + +SWEP.Sway = 2 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 7 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5.5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-4.2, -7.5, -4.225) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/l96.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 6 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/sg550.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.7 +SWEP.DropMagazineTime = 0.6 + +// sounds + +local path = "tacrp/weapons/sg551/sg551_" +local path1 = "tacint_shark/krieg/" + +SWEP.Sound_Shoot = path1 .. "sg500.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.3, -0.2), + vm_ang = Angle(0, 0.5, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.bolt_cover", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.1, + }, + }, +} + +// attachments + +SWEP.AttachmentElements = { + ["sights"] = { + BGs_VM = { + {1, 2} + }, + BGs_WM = { + {2, 2} + }, + }, + ["irons"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["tactical"] = { + BGs_VM = { + {3, 1} + }, + BGs_WM = { + {3, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights", "optic_cqb", "optic_medium", "optic_sniper"}, + InstalledElements = {"sights"}, + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + WMScale = 0.75, + Pos_VM = Vector(-5.1, 0, 5.5), + Pos_WM = Vector(0, 5, 1), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.85, + Pos_VM = Vector(-3.2, 0.05, 32.25), + Pos_WM = Vector(0.1, 36, -1.2), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + InstalledElements = {"tactical"}, + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-4.25, -0.75, 15), + Pos_WM = Vector(2, 13, -0.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(-90, -90 + 3.5, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "perk_extendedmag", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_krieg.Remove_Clip", path1 .. "magout.wav") +addsound("tacint_krieg.Insert_Clip", path1 .. "magin.ogg") +addsound("tacint_krieg.Insert_Clip-mid", path1 .. "magin.wav") +addsound("tacint_krieg.bolt_action", path1 .. "boltback.ogg") +addsound("tacint_krieg.bolt_slap", path1 .. "boltback.ogg") +addsound("tacint_krieg.bolt_back", path1 .. "boltback.ogg") +addsound("tacint_krieg.throw_catch", path .. "throw_catch.wav") +addsound("tacint_krieg.fire_selector", path .. "fire_selector.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sg550r.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sg550r.lua new file mode 100644 index 0000000..82eaa72 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sg550r.lua @@ -0,0 +1,364 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "SIG SG 550-2 SP" +SWEP.AbbrevName = "SG 550-2" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "5Sporter" + +SWEP.Description = "Long barrel rifle converted to semi-automatic for civilian markets. Easy to control and has high armor penetration." +SWEP.Description_Quote = "The Krieg 550 Commando's civilian brother." + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "SIG Sauer AG" +SWEP.Trivia_Year = "1988" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Model: Hav0c & Twinke Masta +Texture: Twinke Masta +Sound: Farion, Treyarch & Tactical Intervention +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_sg550_rifle.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_sg550_rifle.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 30, + Damage_Min = 17, + HipFireSpreadPenalty = 0.05, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 22, + Damage_Min = 10, + RPM = 450, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 20, + Damage_Min = 12, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.005, + HipFireSpreadPenalty = 0.01, + RecoilDissipationRate = 8, + RecoilMaximum = 15 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MarksmanRifle + +SWEP.MuzzleVelocity = 26000 + +// "ballistics" + +SWEP.Damage_Max = 30 +SWEP.Damage_Min = 20 +SWEP.Range_Min = 1800 +SWEP.Range_Max = 4500 +SWEP.Penetration = 10 +SWEP.ArmorPenetration = 0.95 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 27000 + +// misc. shooting + +SWEP.Firemodes = false +SWEP.Firemode = 1 + +SWEP.RPM = 500 +SWEP.RPMMultSemi = 0.7 + +SWEP.Spread = 0.0008 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 6 +SWEP.RecoilResetTime = 0.045 +SWEP.RecoilDissipationRate = 22 +SWEP.RecoilFirstShotMult = 0.8 + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 2.5 +SWEP.RecoilStability = 0.55 +SWEP.RecoilAltMultiplier = 200 + +SWEP.RecoilSpreadPenalty = 0.0033 +SWEP.HipFireSpreadPenalty = 0.04 +SWEP.PeekPenaltyFraction = 0.15 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.8 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.4 + +SWEP.AimDownSightsTime = 0.39 +SWEP.SprintToFireTime = 0.44 + +SWEP.Sway = 2 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 7 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5.5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0, 1, 0) +SWEP.SightPos = Vector(-4.17, -7.5, -4.425) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/sg550.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.7 +SWEP.DropMagazineTime = 0.6 + +// sounds + +local path = "tacrp/weapons/sg551/sg551_" +local path1 = "tacint_shark/krieg/" + +SWEP.Sound_Shoot = path1 .. "sg500.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.Pitch_Shoot = 94 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.3, -0.2), + vm_ang = Angle(0, 0.5, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.bolt_cover", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.1, + }, + }, +} + +// attachments + +SWEP.AttachmentElements = { + ["sights"] = { + BGs_VM = { + {3, 2} + }, + }, + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + InstalledElements = {"sights"}, + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + WMScale = 0.75, + Pos_VM = Vector(-5.1, 0, 6.0), + Pos_WM = Vector(0, 5, 1), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.85, + Pos_VM = Vector(-3.2, 0.05, 29.25), + Pos_WM = Vector(0.1, 33, -1.2), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + InstalledElements = {"tactical"}, + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-4.25, -0.75, 15), + Pos_WM = Vector(2, 13, -0.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(-90, -90 + 3.5, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "perk_extendedmag", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_krieg.Remove_Clip", path1 .. "magout.wav") +addsound("tacint_krieg.Insert_Clip", path1 .. "magin.ogg") +addsound("tacint_krieg.Insert_Clip-mid", path1 .. "magin.wav") +addsound("tacint_krieg.bolt_action", path1 .. "boltback.ogg") +addsound("tacint_krieg.bolt_slap", path1 .. "boltback.ogg") +addsound("tacint_krieg.bolt_back", path1 .. "boltback.ogg") +addsound("tacint_krieg.throw_catch", path .. "throw_catch.wav") +addsound("tacint_krieg.fire_selector", path .. "fire_selector.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sl8.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sl8.lua new file mode 100644 index 0000000..d06bbcd --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_sl8.lua @@ -0,0 +1,363 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK SL8" +SWEP.AbbrevName = "SL8" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "5Sporter" + +SWEP.Description = "Semi-automatic variant of the G36 made for precision shooting. Low fire rate, but recoil control is excellent.\nEquipped with a 2x scope but has no ironsight option." +SWEP.Description_Quote = "\"Used to be a cop myself, only for one day though.\"" -- Resident Evil 4 + +SWEP.Trivia_Caliber = ".223 Remington" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "1998" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Model: Hav0c +Texture: Twinke Masta +Sound: KingFriday +Animation: Tactical Intervention +]] + +// https://gamebanana.com/mods/211404 +SWEP.ViewModel = "models/weapons/tacint_extras/v_sl8.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_sl8.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 28, + Damage_Min = 19, + Range_Min = 2000, + Range_Max = 8000, + ArmorPenetration = 0.875, + + RPM = 360, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 35, + Damage_Min = 22, + Range_Min = 500, + Range_Max = 5000, + RPM = 200, + ClipSize = 25, + + RecoilMaximum = 6, + RecoilResetTime = 0.2, + RecoilDissipationRate = 6, + RecoilSpreadPenalty = 0.002, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 17, + Damage_Min = 12, + Range_Min = 2000, + Range_Max = 8000, + ArmorPenetration = 0.875, + + RPM = 360, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.015, + RecoilSpreadPenalty = 0.002 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 26 +SWEP.Damage_Min = 16 +SWEP.Range_Min = 1500 +SWEP.Range_Max = 4000 +SWEP.Penetration = 8 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.75 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 35000 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 450 +SWEP.RPMMultSemi = 0.7 + +SWEP.Spread = 0.001 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 15 +SWEP.RecoilResetTime = 0.175 +SWEP.RecoilDissipationRate = 15 +SWEP.RecoilFirstShotMult = 0.75 + +SWEP.RecoilVisualKick = 0.75 +SWEP.RecoilKick = 1.5 +SWEP.RecoilStability = 0.4 +SWEP.RecoilAltMultiplier = 1000 + +SWEP.RecoilSpreadPenalty = 0.0009 +SWEP.HipFireSpreadPenalty = 0.04 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.6 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.4 +SWEP.SprintToFireTime = 0.44 + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 6 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0.5, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-4.03, -2.5, -4.1) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/g36.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 2 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1.1 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/sl8.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.4 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "TacRP/weapons/g36k/g36k_" +local path1 = "tacint_extras/sl8/" + +SWEP.Sound_Shoot = "^" .. path1 .. "sl8-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["deploy"] = "deploy2", +} +SWEP.DeployTimeMult = 0.9 + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["tactical"] = { + BGs_VM = { + {1, 1} + }, + }, + ["irons"] = { + BGs_VM = { + {2, 1} + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + InstalledElements = {"irons"}, + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 0.9, + Pos_VM = Vector(-6.8, 0, 7), + Pos_WM = Vector(0, 0, 2.75), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-3.55, 0.075, 32), + Pos_WM = Vector(-0.25, 24, -1), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-3.9, -0.75, 18), + Pos_WM = Vector(0.9, 20, -1), + Ang_VM = Angle(90, 0, -80), + Ang_WM = Angle(-70, -90, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_sl8.MagOut", path1 .. "MagOut.wav") +addsound("tacint_extras_sl8.MagFiddle", path1 .. "MagFiddle.wav") +addsound("tacint_extras_sl8.MagSlap", path1 .. "MagSlap.wav") +addsound("tacint_extras_sl8.BoltPull", path1 .. "BoltPull.wav") +addsound("tacint_extras_sl8.BoltBack", path1 .. "BoltBack.wav") +addsound("tacint_extras_sl8.fire_select", path .. "fire_selector.wav") +addsound("tacint_extras_sl8.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_trg42.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_trg42.lua new file mode 100644 index 0000000..e6f09c1 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_trg42.lua @@ -0,0 +1,391 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Sako TRG-42" +SWEP.AbbrevName = "TRG-42" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "7Sniper Rifle" + +SWEP.Description = "Magnum sniper rifle with decent handling and mobility.\nPowerful, but slow to cycle and not very stable.\nEquipped with a 12x scope by default." +SWEP.Description_Quote = "Almost unique in being a purpose-designed sharpshooter rifle." -- Hitman: Absolution (2012) (taken from the weapon description) + +SWEP.Trivia_Caliber = ".338 Lapua Magnum" +SWEP.Trivia_Manufacturer = "SAKO" +SWEP.Trivia_Year = "1999" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Model: Darkstorn +Texture: SilentAssassin12 +Sound: Vunsunta +Animation: Tactical Intervention +]] + + +// https://gamebanana.com/mods/211224 +SWEP.ViewModel = "models/weapons/tacint_extras/v_trg42.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_trg42.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Description = "Magnum sniper rifle with decent handling and mobility.\nPowerful, but slow to fire and only lethal at long range.\nEquipped with a 12x scope by default.", + + Damage_Max = 70, + Damage_Min = 115, + + Range_Min = 1800, + Range_Max = 8000, + + Sway = 2, + ScopedSway = 0.075, + }, + [TacRP.BALANCE_TTT] = { + + Description = "Magnum sniper rifle lethal to the chest at long range, but has poor handling and low close range damage.\nEquipped with a 12x scope by default.", + + Damage_Max = 25, + Damage_Min = 80, + Range_Min = 1000, + Range_Max = 5000, + RPM = 30, + ShootTimeMult = 1.175, + ClipSize = 4, + + AimDownSightsTime = 0.45, + + RecoilResetTime = 0.4, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_PVE] = { + Description = "Magnum sniper rifle with decent handling and mobility.\nPowerful, but slow to fire and only lethal at long range.\nEquipped with a 12x scope by default.", + + Damage_Max = 25, + Damage_Min = 75, + Range_Min = 700, + Range_Max = 3000, + + Sway = 2, + ScopedSway = 0.075, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.023, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SniperRifle + +// "ballistics" + +SWEP.Damage_Max = 115 +SWEP.Damage_Min = 90 +SWEP.Range_Min = 1800 +SWEP.Range_Max = 6000 +SWEP.Penetration = 24 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.95 +SWEP.ArmorBonus = 4 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.15, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 +} + +SWEP.MuzzleVelocity = 38000 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Bolt-Action" // only used externally for firemode name distinction + +SWEP.RPM = 32 + +SWEP.Spread = 0.00 + +SWEP.HipFireSpreadPenalty = 0.05 +SWEP.PeekPenaltyFraction = 0.3 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.3 +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 5 + +SWEP.RecoilKick = 6 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil + +SWEP.CanBlindFire = true + +SWEP.ShootTimeMult = 1.15 + +// handling + +SWEP.MoveSpeedMult = 0.875 +SWEP.ShootingSpeedMult = 0.65 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.3 + +SWEP.AimDownSightsTime = 0.38 +SWEP.SprintToFireTime = 0.46 + +SWEP.Sway = 2.25 +SWEP.ScopedSway = 0.12 + +SWEP.FreeAimMaxAngle = 9 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "shotgun" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(1.5, -2, -5.5) + +SWEP.BlindFireAng = Angle(-10, -15, -0) +SWEP.BlindFirePos = Vector(3, -2, -2) + +SWEP.BlindFireSuicideAng = Angle(0, 115, 0) +SWEP.BlindFireSuicidePos = Vector(0, 32, -24) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0.02, 0.11, 0) +SWEP.SightPos = Vector(-3.855, -11, -3.52) + +SWEP.CorrectivePos = Vector(0.025, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 8, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/sniper.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 12 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true +SWEP.ScopeOverlaySize = 0.75 + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 5 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_sniper" + +SWEP.ReloadTimeMult = 1.15 +SWEP.DropMagazineImpact = "metal" +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/trg42.mdl" + +SWEP.ReloadUpInTime = 1.75 +SWEP.DropMagazineTime = 0.8 + +// sounds + +local path = "TacRP/weapons/uratio/uratio_" +local path1 = "tacint_extras/trg42/" + +SWEP.Sound_Shoot = "^" .. path1 .. "awp1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/ak47/ak47_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_1" +SWEP.EjectEffect = 2 +SWEP.EjectDelay = 0.9 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2"}, + ["blind_fire"] = "blind_shoot1" +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["optic"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["irons"] = { + BGs_VM = { + {3, 1} + }, + }, + ["muzzle"] = { + BGs_VM = { + {4, 1} + }, + BGs_WM = { + {4, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights_sniper", "optic_cqb", "optic_medium", "optic_sniper"}, + WMBone = "Box01", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + VMScale = 0.8, + WMScale = 0.8, + Pos_VM = Vector(-5.1, 0.02, 6), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 6, 1.6), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + WMBone = "Box01", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + InstalledElements = {"muzzle"}, + Pos_VM = Vector(-4.1, 0, 36.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 45, 0.5), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + WMBone = "Box01", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-2, 0, 17.5), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(0, 19.5, -2.6), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_extmag_sniper", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_sniper"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_trg42.Clip_Out", path .. "clip_out.wav") +addsound("tacint_extras_trg42.Clip_In", path .. "clip_in.wav") +addsound("tacint_extras_trg42.Bolt_Back", path .. "bolt_back.wav") +addsound("tacint_extras_trg42.bolt_forward", path .. "bolt_forward.wav") +addsound("tacint_extras_trg42.safety", path .. "safety.wav") +addsound("tacint_extras_trg42.buttstock_back", path .. "buttstock_back.wav") +addsound("tacint_extras_trg42.buttstock_rest_down", path .. "buttstock_rest_down.wav") +addsound("tacint_extras_trg42.flip_up_cover", path .. "flip_up_cover.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_val.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_val.lua new file mode 100644 index 0000000..07c337c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_val.lua @@ -0,0 +1,348 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "АС ВАЛ" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Integrally-suppressed rifle with high damage output and precision, but performs poorly over long bursts." +SWEP.Description_Quote = "\"Call me the cloaker-smoker!\"" -- PAYDAY 2 (2015) + +SWEP.Trivia_Caliber = "9x39mm" +SWEP.Trivia_Manufacturer = "Tula Arms Plant" +SWEP.Trivia_Year = "1987" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Model & Texture: S.T.A.L.K.E.R. +Sound: S.T.A.L.K.E.R. & Vunsunta +Animation: Tactical Intervention +]] + + +SWEP.ViewModel = "models/weapons/tacint_extras/v_val.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_val.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 15, + Damage_Min = 12, + Range_Min = 800, + Range_Max = 4000, + RPM = 650, + + RecoilSpreadPenalty = 0.005, + HipFireSpreadPenalty = 0.035, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1.5, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.8, + [HITGROUP_RIGHTARM] = 0.8, + [HITGROUP_LEFTLEG] = 0.6, + [HITGROUP_RIGHTLEG] = 0.6, + [HITGROUP_GEAR] = 0.6 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 10, + Damage_Min = 7, + Range_Min = 800, + Range_Max = 4000, + RPM = 800, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 22 +SWEP.Damage_Min = 16 +SWEP.Range_Min = 900 +SWEP.Range_Max = 2200 +SWEP.Penetration = 9 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.925 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 14500 + +// misc. shooting + +SWEP.Firemodes = {2, 1} +SWEP.RPM = 900 + +SWEP.Spread = 0.001 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 5 +SWEP.RecoilResetTime = 0.01 +SWEP.RecoilDissipationRate = 28 +SWEP.RecoilFirstShotMult = 0.75 + +SWEP.RecoilVisualKick = 0.75 +SWEP.RecoilKick = 4.5 +SWEP.RecoilStability = 0.45 +SWEP.RecoilAltMultiplier = 300 + +SWEP.RecoilSpreadPenalty = 0.0035 +SWEP.HipFireSpreadPenalty = 0.027 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.55 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.39 +SWEP.SprintToFireTime = 0.37 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.175 + +SWEP.FreeAimMaxAngle = 5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, 0, 0) +SWEP.SightPos = Vector(-4.66, -7.5, -2.7) + +SWEP.CorrectivePos = Vector(0, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.75, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pdw" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/vss.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +// sounds + +local path = "tacint_extras/vss/" + +SWEP.Sound_Shoot = "^" .. path .. "fire.wav" +SWEP.Sound_Shoot_Silenced = "^" .. path .. "fire.wav" + +SWEP.Vol_Shoot = 80 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_suppressed" +SWEP.EjectEffect = 2 +SWEP.Silencer = true + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload", +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.4, -0.1), + vm_ang = Angle(0, 0.1, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.15, + }, + }, +} + +SWEP.DeployTimeMult = 2.2 +SWEP.UnholsterTimeMult = 1 / 2.2 + +// attachments + +SWEP.AttachmentElements = { + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["pso1"] = { + BGs_VM = { + {2, 2} + }, + BGs_WM = { + {2, 2} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 0} + }, + BGs_WM = { + {2, 0} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5.5, 0.5, 2.5), + Pos_WM = Vector(-0.4, 2, 0.5), + } + }, + SortOrder = 2, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_pso1", "optic_cqb", "optic_medium", "optic_sniper", "optic_ak2"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + Pos_VM = Vector(-5.25, 0.15, 4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 3, 0.2), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.25, -0.1, 19), + Pos_WM = Vector(0, 19, -2), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, 180), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc_foldstock", "acc", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_rifle_sub"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_vss.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_vss.lua new file mode 100644 index 0000000..0cec180 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_vss.lua @@ -0,0 +1,368 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "ВСС Винторез" +SWEP.AbbrevName = "Vintorez" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "6Marksman Rifle" + +SWEP.Description = "Integrally-suppressed marksman rifle with high fire rate and low recoil, but performs poorly over long bursts.\nEquipped with a 6x scope by default." +SWEP.Description_Quote = "\"Enemy has no more medicine!\"" -- PAYDAY 2 (2015) + +SWEP.Trivia_Caliber = "9x39mm" +SWEP.Trivia_Manufacturer = "Tula Arms Plant" +SWEP.Trivia_Year = "1987" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Model: Ettubrutesbro, Millenia & Twinke Masta +Texture: Millenia +Sound: S.T.A.L.K.E.R. & Vunsunta +Animation: Tactical Intervention +]] + + +SWEP.ViewModel = "models/weapons/tacint_extras/v_vss.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_vss.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 15, + Damage_Min = 12, + Range_Min = 800, + Range_Max = 4000, + RPM = 650, + + RecoilSpreadPenalty = 0.005, + HipFireSpreadPenalty = 0.035, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1.5, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.8, + [HITGROUP_RIGHTARM] = 0.8, + [HITGROUP_LEFTLEG] = 0.6, + [HITGROUP_RIGHTLEG] = 0.6, + [HITGROUP_GEAR] = 0.6 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 13, + Damage_Min = 8, + Range_Min = 1000, + Range_Max = 5000, + RPM = 600, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.004 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 24 +SWEP.Damage_Min = 20 +SWEP.Range_Min = 1000 +SWEP.Range_Max = 2500 +SWEP.Penetration = 9 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.925 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 16000 + +// misc. shooting + +SWEP.Firemodes = { + 1, + 2 +} + +SWEP.RPM = 700 + +SWEP.Spread = 0.0005 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 5 +SWEP.RecoilResetTime = 0.01 +SWEP.RecoilDissipationRate = 28 +SWEP.RecoilFirstShotMult = 0.65 + +SWEP.RecoilVisualKick = 0.5 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.5 +SWEP.RecoilAltMultiplier = 400 + +SWEP.RecoilSpreadPenalty = 0.0048 +SWEP.HipFireSpreadPenalty = 0.03 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.55 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.44 +SWEP.SprintToFireTime = 0.4 + +SWEP.Sway = 1.3 +SWEP.ScopedSway = 0.1 + +SWEP.FreeAimMaxAngle = 5.5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, 0, 0) +SWEP.SightPos = Vector(-4.66, -7.5, -2.7) + +SWEP.CorrectivePos = Vector(0, 0, -0.05) +SWEP.CorrectiveAng = Angle(0.75, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/pso1.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 6 +SWEP.ScopeLevels = 1 +SWEP.ScopeHideWeapon = true +SWEP.ScopeOverlaySize = 0.9 + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pdw" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/vss.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +// sounds + +local path = "tacint_extras/vss/" +local path1 = "tacrp_extras/ak47/" + +SWEP.Sound_Shoot = "^" .. path .. "fire.wav" +SWEP.Sound_Shoot_Silenced = "^" .. path .. "fire.wav" + +SWEP.Vol_Shoot = 80 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_suppressed" +SWEP.EjectEffect = 2 +SWEP.Silencer = true + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload", +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.4, -0.1), + vm_ang = Angle(0, 0.1, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.15, + }, + }, +} + +SWEP.DeployTimeMult = 2.25 + +// attachments + +SWEP.AttachmentElements = { + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["irons"] = { + BGs_VM = { + {2, 2} + }, + BGs_WM = { + {2, 2} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 2} + }, + BGs_WM = { + {2, 2} + }, + AttPosMods = { + [1] = { + Pos_VM = Vector(-5.5, 0.5, 2.5), + Pos_WM = Vector(-0.4, 2, 0.5), + } + }, + SortOrder = 2, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights", "optic_cqb", "optic_medium", "optic_sniper", "optic_ak2"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + Pos_VM = Vector(-5.25, 0.15, 4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 3, 0.2), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.25, -0.1, 19), + Pos_WM = Vector(0, 19, -2), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, 180), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "perk_extendedmag", "acc_bipod"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_rifle_sub"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_extras_vss.remove_clip", path .. "clipin1.wav") +addsound("tacint_extras_vss.insert_clip", path .. "clipin2.wav") +addsound("tacint_extras_vss.boltaction", path1 .. "bolt.mp3") +addsound("tacint_extras_vss.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_xm8car.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_xm8car.lua new file mode 100644 index 0000000..55de4dc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_xm8car.lua @@ -0,0 +1,333 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK XM8 Compact" +SWEP.AbbrevName = "XM8 Compact" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Experimental multi-purpose carbine. Easy to use, but low damage.\nHas an adjustable integrated 2-8x scope." +SWEP.Description_Quote = "\"Who loves spaghetti?!\"" -- Battlefield: Bad Company 2 + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "2003" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Model: End Of Days +Texture: Copkiller, Twinke Masta & Wangchung +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_xm8car.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_xm8car.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 12, + Damage_Min = 6, + Range_Min = 750, + Range_Max = 2500, + RPM = 750, + + RecoilSpreadPenalty = 0.001, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 10, + Damage_Min = 7, + RPM = 700, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.022, + RecoilMaximum = 15 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 18 +SWEP.Damage_Min = 10 +SWEP.Range_Min = 1000 +SWEP.Range_Max = 2500 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 29000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 750 + +SWEP.Spread = 0.0025 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 12 +SWEP.RecoilResetTime = 0.1 // time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 20 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 0.5 +SWEP.RecoilKick = 1.5 +SWEP.RecoilStability = 0.5 + +SWEP.RecoilSpreadPenalty = 0.001 +SWEP.HipFireSpreadPenalty = 0.025 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.95 +SWEP.ShootingSpeedMult = 0.9 +SWEP.SightedSpeedMult = 0.85 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.33 +SWEP.SprintToFireTime = 0.35 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-4.03, -2.5, -4.1) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/scopeddot.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 2 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/xm8.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.3 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "tacrp/weapons/g36k/g36k_" +local path1 = "tacint_extras/xm8/" +SWEP.Sound_Shoot = "^" .. path1 .. "m249-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.Pitch_Shoot = 105 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} +SWEP.DeployTimeMult = 1.75 + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_xm8"}, + AttachSound = "tacrp/weapons/mp5/mp5_fire_select-1.wav", + DetachSound = "tacrp/weapons/mp5/mp5_fire_select-3.wav", + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-3.3, 0.075, 22), + Pos_WM = Vector(-0.25, 24, -1), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.g36k_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + VMScale = 1.1, + Pos_VM = Vector(-4.4, -0.3, 14.2), + Pos_WM = Vector(0.9, 15, -1), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(-70, -90, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_xm8c.remove_clip", path .. "remove_clip.wav") +addsound("tacint_xm8c.insert_clip", path .. "insert_clip.wav") +addsound("tacint_xm8c.bolt_action", path .. "bolt_action.wav") +addsound("tacint_xm8c.fire_select", path .. "fire_selector.wav") +addsound("tacint_xm8c.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_io_xm8lmg.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_xm8lmg.lua new file mode 100644 index 0000000..0f29dc8 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_io_xm8lmg.lua @@ -0,0 +1,346 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK XM8 LMG" +SWEP.AbbrevName = "XM8 LMG" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "5Machine Gun" + +SWEP.Description = "Experimental multi-purpose carbine in MG configuration. Light, high capacity and low recoil, but damage is poor.\nHas an adjustable integrated 2-8x scope." +SWEP.Description_Quote = "\"How ya like me now, bitch!?\"" -- Battlefield: Bad Company 2 + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "2003" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Model: End Of Days +Texture: Copkiller, Twinke Masta & Wangchung +Animation: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_extras/v_xm8lmg.mdl" +SWEP.WorldModel = "models/weapons/tacint_extras/w_xm8lmg.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 12, + Damage_Min = 6, + Range_Min = 750, + Range_Max = 3000, + RPM = 750, + + RecoilSpreadPenalty = 0.0009, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 10, + Damage_Min = 7, + RPM = 700, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.001, + HipFireSpreadPenalty = 0.01, + RecoilMaximum = 14 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachineGun + +// "ballistics" + +SWEP.Damage_Max = 18 +SWEP.Damage_Min = 10 +SWEP.Range_Min = 1200 +SWEP.Range_Max = 3000 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 32000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 750 + +SWEP.Spread = 0.008 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 25 +SWEP.RecoilResetTime = 0.1 // time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 20 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 0.5 +SWEP.RecoilKick = 2 +SWEP.RecoilStability = 0.5 + +SWEP.RecoilSpreadPenalty = 0.0007 +SWEP.HipFireSpreadPenalty = 0.035 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.75 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.42 +SWEP.SprintToFireTime = 0.37 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.225 + +SWEP.FreeAimMaxAngle = 4.5 + +SWEP.Bipod = true +SWEP.BipodRecoil = 0.65 +SWEP.BipodKick = 0.3 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0, 0, 0) +SWEP.SightPos = Vector(-4.03, -2.5, -4.1) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/scopeddot.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 2 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 100 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1.5 +SWEP.DropMagazineModel = "models/weapons/tacint_extras/magazines/xm8lmg.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.4 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "tacrp/weapons/g36k/g36k_" +local path1 = "tacint_extras/xm8/" +SWEP.Sound_Shoot = "^" .. path1 .. "m249-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "unholster", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} +SWEP.DeployTimeMult = 2.25 + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + }, + ["bipod"] = { + BGs_VM = { + {3, 1} + }, + BGs_WM = { + {3, 1} + } + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_xm8"}, + AttachSound = "tacrp/weapons/mp5/mp5_fire_select-1.wav", + DetachSound = "tacrp/weapons/mp5/mp5_fire_select-3.wav", + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.g36k_rootbone", + WMBone = "ValveBiped.Bip01_R_Hand", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + WMScale = 0.9, + Pos_VM = Vector(-3.65, 0.3, 31), + Pos_WM = Vector(35, 1.2, -4.7), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -0, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.g36k_rootbone", + WMBone = "ValveBiped.Bip01_R_Hand", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-3.75, -0.8, 17), + Pos_WM = Vector(20, 2, -5), + Ang_VM = Angle(90, 0, -80), + Ang_WM = Angle(-0, 0, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_sling", "acc_duffle", "extendedbelt", "acc_bipod"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_xm8.remove_clip", path1 .. "magout.wav") +addsound("tacint_xm8.insert_clip", path1 .. "magin.wav") +addsound("tacint_xm8.bolt_action", path .. "bolt_action.wav") +addsound("tacint_xm8.fire_select", path .. "fire_selector.wav") +addsound("tacint_xm8.Buttstock_Back", path .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_knife.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_knife.lua new file mode 100644 index 0000000..fa5f7ce --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_knife.lua @@ -0,0 +1,100 @@ +SWEP.Base = "tacrp_base_knife" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Flip Knife" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "8Bladed Melee" + +SWEP.Description = "A multi-purpose flip knife, although most of the purposes involving stabbing someone." +SWEP.Description_Quote = "Stabbin' time." + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_knife.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_knife.mdl" + +SWEP.Slot = 0 + +SWEP.MeleeDamage = 35 +SWEP.MeleeAttackTime = 0.4 +SWEP.MeleeAttackMissTime = 0.5 +SWEP.MeleeDelay = 0.15 + +SWEP.MeleeDamageType = DMG_SLASH + +SWEP.MeleePerkStr = 0.5 +SWEP.MeleePerkAgi = 0.5 +SWEP.MeleePerkInt = 0.5 + +// hold types + +SWEP.HoldType = "knife" +SWEP.HoldTypeSprint = "knife" + +SWEP.GestureBash = ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE +SWEP.GestureBash2 = ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE + +SWEP.PassiveAng = Angle(-2.5, 0, 0) +SWEP.PassivePos = Vector(1, 0, -5) + +SWEP.SprintAng = Angle(0, 0, 0) +SWEP.SprintPos = Vector(2, 0, -5) + +SWEP.CustomizeAng = Angle(0, 25, 0) +SWEP.CustomizePos = Vector(2, 0, -12) + +SWEP.SprintMidPoint = { + Pos = Vector(2, 0, -5), + Ang = Angle(0, 0, 0) +} + +// sounds + +local path = "tacrp/weapons/knife/" + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["melee"] = {"slash_left1", "slash_left2", "slash_right1", "slash_right2"}, + ["melee2"] = {"slash_forward1", "slash_forward2"}, + ["meleethrow"] = {"knifethrow"}, +} + +SWEP.Sound_MeleeHit = { + path .. "/scrape_metal-1.wav", + path .. "/scrape_metal-2.wav", + path .. "/scrape_metal-3.wav", +} + +SWEP.Sound_MeleeHitBody = { + path .. "/flesh_hit-1.wav", + path .. "/flesh_hit-2.wav", + path .. "/flesh_hit-3.wav", + path .. "/flesh_hit-4.wav", + path .. "/flesh_hit-5.wav", +} + +SWEP.Sound_MeleeSwing = { + path .. "swing-1.wav", + path .. "swing-2.wav", + path .. "swing-3.wav", + path .. "swing-4.wav", + path .. "swing-5.wav", + path .. "swing-6.wav", +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_knife.deploy", path .. "open-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_knife2.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_knife2.lua new file mode 100644 index 0000000..b5ee38f --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_knife2.lua @@ -0,0 +1,102 @@ +SWEP.Base = "tacrp_base_knife" +SWEP.Spawnable = true + +AddCSLuaFile() + +SWEP.PrintName = "Jackal Knife" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "8Bladed Melee" + +SWEP.Description = "Very edgy looking knife. Light, partially skeletized blade makes it faster to swing but do less damage." +SWEP.Description_Quote = "That doesn't look very pleasant." + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Tactical Intervention (unused)" + +SWEP.ViewModel = "models/weapons/tacint/v_knife2.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_knife2.mdl" + +SWEP.NoRanger = true +SWEP.NoStatBox = false + +SWEP.Slot = 0 + +SWEP.MeleeDamage = 30 +SWEP.MeleeAttackTime = 0.35 +SWEP.MeleeAttackMissTime = 0.45 +SWEP.MeleeDelay = 0.12 + +SWEP.MeleeDamageType = DMG_SLASH + +SWEP.MeleePerkStr = 0.5 +SWEP.MeleePerkAgi = 0.6 +SWEP.MeleePerkInt = 0.55 + +// hold types + +SWEP.HoldType = "knife" +SWEP.HoldTypeSprint = "knife" + +SWEP.GestureBash = ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE +SWEP.GestureBash2 = ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE + +SWEP.PassiveAng = Angle(-2.5, 0, 0) +SWEP.PassivePos = Vector(1, 0, -5) + +SWEP.SprintAng = Angle(0, 0, 0) +SWEP.SprintPos = Vector(2, 0, -5) + +SWEP.CustomizeAng = Angle(0, 25, 0) +SWEP.CustomizePos = Vector(2, 0, -12) + +SWEP.SprintMidPoint = { + Pos = Vector(2, 0, -5), + Ang = Angle(0, 0, 0) +} + +// sounds + +local path = "tacrp/weapons/knife/" + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["melee"] = {"slash_left1", "slash_left2", "slash_right1", "slash_right2"}, + ["melee2"] = {"slash_forward1", "slash_forward2"}, + ["meleethrow"] = {"knifethrow"}, +} + +SWEP.Sound_MeleeHit = { + path .. "/scrape_metal-1.wav", + path .. "/scrape_metal-2.wav", + path .. "/scrape_metal-3.wav", +} + +SWEP.Sound_MeleeHitBody = { + path .. "/flesh_hit-1.wav", + path .. "/flesh_hit-2.wav", + path .. "/flesh_hit-3.wav", + path .. "/flesh_hit-4.wav", + path .. "/flesh_hit-5.wav", +} + +SWEP.Sound_MeleeSwing = { + path .. "swing-1.wav", + path .. "swing-2.wav", + path .. "swing-3.wav", + path .. "swing-4.wav", + path .. "swing-5.wav", + path .. "swing-6.wav", +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_knife2.deploy", "tacrp/magtap.ogg") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_ks23.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_ks23.lua new file mode 100644 index 0000000..ce22f97 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_ks23.lua @@ -0,0 +1,315 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "KC-23" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "5Shotgun" + +SWEP.Description = "Massive shotgun that shoots massive shells from its massive barrel.\nAble to breach doors without specialized shells." +SWEP.Description_Quote = "\"You tried to make me kill my president!\" \"Tried?\"" -- Black Ops 1 (2010) + +SWEP.Trivia_Caliber = "23x75mmR" +SWEP.Trivia_Manufacturer = "Tula Arms Plant" +SWEP.Trivia_Year = "1971" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Firearms: Source" + +SWEP.ViewModel = "models/weapons/tacint/v_ks23.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_ks23.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 10, + Damage_Min = 2, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 8, + Damage_Min = 2, + Range_Min = 100, + Range_Max = 1000, + Num = 16, + ClipSize = 4, + + Spread = 0.04, + ShotgunPelletSpread = 0.03, + HipFireSpreadPenalty = 0.025, + RecoilSpreadPenalty = 0.02, + FreeAimMaxAngle = 5, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 9, + Damage_Min = 4, + Range_Min = 500, + Range_Max = 3000, + Num = 32, + Spread = 0.06, + ShotgunPelletSpread = 0.015, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.04, + RecoilDissipationRate = 1.25, + RecoilMaximum = 5, + ReloadTimeMult = 1.3 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Shotgun + +// "ballistics" + +SWEP.Damage_Max = 14 +SWEP.Damage_Min = 4 +SWEP.Range_Min = 100 // distance for which to maintain maximum damage +SWEP.Range_Max = 1200 // distance at which we drop to minimum damage +SWEP.Penetration = 4 // units of metal this weapon can penetrate +SWEP.Num = 20 +SWEP.ArmorPenetration = 0.35 +SWEP.ArmorBonus = 0.25 + +SWEP.MuzzleVelocity = 10000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 1, + [HITGROUP_RIGHTLEG] = 1, + [HITGROUP_GEAR] = 1 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Pump-Action" // only used externally for firemode name distinction + +SWEP.RPM = 45 + +SWEP.Spread = 0.04 +SWEP.ShotgunPelletSpread = 0.035 + +SWEP.ShootTimeMult = 1 + +SWEP.HipFireSpreadPenalty = 0.025 +SWEP.MidAirSpreadPenalty = 0 + +SWEP.ScopedSpreadPenalty = 0 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 3 +SWEP.RecoilResetTime = 0.35 +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1.5 + +SWEP.RecoilVisualKick = 2 +SWEP.RecoilKick = 25 +SWEP.RecoilStability = 0.65 + +SWEP.RecoilSpreadPenalty = 0.02 + +SWEP.CanBlindFire = true + +SWEP.DoorBreach = true +SWEP.DoorBreachThreshold = 100 + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.5 +SWEP.SightedSpeedMult = 0.6 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.37 +SWEP.SprintToFireTime = 0.4 + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.65 + +SWEP.FreeAimMaxAngle = 4 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "shotgun" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SHOTGUN + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(4, -2, -4) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.55, 0.1, 0.75) +SWEP.SightPos = Vector(-3.4, -2, -3.25) + +SWEP.CorrectivePos = Vector(0.275, 0, -0.2) +SWEP.CorrectiveAng = Angle(1.21, 0.1, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 8, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 4 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "buckshot" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.ShotgunReload = true + +SWEP.ReloadTimeMult = 1.25 +SWEP.ShotgunThreeload = false +// sounds + +local path = "TacRP/weapons/ks23/ks23_" + +SWEP.Sound_Shoot = "tacrp/weapons/ks23/ks23_fire1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/sg551/sg551_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.Pitch_Shoot = 95 +SWEP.ShootPitchVariance = 0 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_shotgun" +SWEP.EjectEffect = 4 +SWEP.EjectDelay = 0.5 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire"] = {"shoot1", "shoot2"}, + ["blind_fire"] = {"blind_shoot1"}, + ["melee"] = {"melee1", "melee2"}, + ["reload"] = {"reload", "reload2"}, + ["jam"] = "reload_finish" +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.bekas_rootbone", + WMBone = "ValveBiped.Bip01_R_Hand", + InstalledElements = {"sights"}, + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 0.75, + Pos_VM = Vector(-3.4, 0, 8), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(9, 0.85, -7), + Ang_WM = Angle(-25, 3.5, 180), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom"}, + Bone = "ValveBiped.bekas_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + VMScale = 1.15, + Pos_VM = Vector(-2, -0.3, 22), + Ang_VM = Angle(90, 0, -90), + Pos_WM = Vector(22, 0.5, -11), + Ang_WM = Angle(-25, 3.5, 90), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_duffle", "acc_extmag_shotgun_tube", "acc_sling"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_manual", "trigger_pump"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_shotgun", "ammo_ks23"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_ks23.Insertshell", "tacrp/weapons/bekas/insertshell-1.wav") +addsound("tacint_ks23.Movement", "tacrp/weapons/bekas/movement-1.wav") +addsound("tacint_ks23.PumpBack", path .. "pump_back.wav") +addsound("tacint_ks23.PumpForward", path .. "pump_forward.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_m320.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_m320.lua new file mode 100644 index 0000000..c4b650a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_m320.lua @@ -0,0 +1,264 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK M320" +SWEP.AbbrevName = "M320" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.NoAimAssist = true + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "6Launcher" + +SWEP.Description = "Grenade launcher capable of firing a variety of payloads." +SWEP.Description_Quote = "Not beanbags, NOT BEANBAGS!" + +SWEP.Trivia_Caliber = "40mm Grenades" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "2008" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_m320.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_m320.mdl" + +SWEP.NoRanger = true + +SWEP.Slot = 4 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + }, +} + +// "ballistics" + +SWEP.Damage_Max = 150 +SWEP.Damage_Min = 150 +SWEP.Range_Max = 4000 +SWEP.Range_Min = 1000 + +SWEP.ShootEnt = "tacrp_proj_40mm_he" +SWEP.ShootEntForce = 4000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 7000 + +SWEP.Num = 1 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Single-Shot" // only used externally for firemode name distinction + +SWEP.RPM = 60 + +SWEP.Spread = 0.025 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.2 // time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 5 +SWEP.RecoilVisualShake = 2 + +SWEP.RecoilKick = 15 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil +SWEP.HipFireSpreadPenalty = 0.05 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.5 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.32 // multiplies how long it takes to recover from sprinting + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.2 + +SWEP.FreeAimMaxAngle = 6 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(2, -2, -6) + +SWEP.BlindFireAng = Angle(0, 0, -30) +SWEP.BlindFirePos = Vector(1, -3, 0) + +SWEP.BlindFireLeftAng = Angle(75, 0, 0) +SWEP.BlindFireLeftPos = Vector(8, 10, -6) + +SWEP.BlindFireSuicideAng = Angle(0, 135, 0) +SWEP.BlindFireSuicidePos = Vector(-3, 25, -19) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(4, 0, -4) + +SWEP.SightAng = Angle(1.075, 7.1, 0) +SWEP.SightPos = Vector(-1.34, 0, -8.15) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_GEAR +SWEP.HolsterPos = Vector(0, -2, -3) +SWEP.HolsterAng = Angle(0, -90, -25) + +// melee + +SWEP.CanMeleeAttack = true + +// reload + +SWEP.ClipSize = 1 +SWEP.Ammo = "smg1_grenade" + +// sounds + +local path = "TacRP/weapons/m320/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_m79" + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["blind_idle"] = "idle", + ["blind_fire"] = "fire", +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["3gl"] = { + BGs_VM = { + {2, 1} + }, + }, + ["buck"] = { + BGs_VM = { + {2, 2} + }, + }, + ["heat"] = { + BGs_VM = { + {2, 3} + }, + }, + ["lvg"] = { + BGs_VM = { + {2, 4} + }, + }, + ["smoke"] = { + BGs_VM = { + {2, 5} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Ammo", + Category = "ammo_40mm", + AttachSound = "TacRP/weapons/m320/shell_in-1.wav", + DetachSound = "TacRP/weapons/m320/shell_out-1.wav", + }, + [2] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_holster"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Perk", + Category = {"perk", "perk_shooting", "perk_reload", "perk_melee"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_m320.Tube_Open", path .. "tube_open-1.wav") +addsound("TacInt_m320.Tube_close", path .. "tube_close-1.wav") +addsound("TacInt_m320.shell_out", path .. "shell_out-1.wav") +addsound("TacInt_m320.shell_in", path .. "shell_in-1.wav") +addsound("TacInt_m320.buttstock_back", path .. "buttstock_back-1.wav") +addsound("TacInt_m320.sight_flipup", path .. "sight_flipup-1.wav") + +SWEP.AutoSpawnable = false + +if engine.ActiveGamemode() == "terrortown" then + SWEP.AutoSpawnable = false + SWEP.Kind = WEAPON_EQUIP + SWEP.Slot = 6 + SWEP.CanBuy = { ROLE_TRAITOR } + SWEP.EquipMenuData = { + type = "Weapon", + desc = "Low power grenade launcher. Variety of payload\noptions, but standard explosive grenades are weak.\nComes with 3 grenades.\n\nBEWARE: May be visible while holstered!", + } + + function SWEP:TTTBought(buyer) + buyer:GiveAmmo(2, "SMG1_Grenade") + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_m4.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_m4.lua new file mode 100644 index 0000000..c91733b --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_m4.lua @@ -0,0 +1,340 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Diemaco C8A1" +SWEP.AbbrevName = "C8A1" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "A close cousin to the classic American rifle with a slower but more controllable rate of fire." +SWEP.Description_Quote = "Green and very, very mean." + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "Colt Canada" +SWEP.Trivia_Year = "1994" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_m4.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_m4.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 14, + Damage_Min = 8, + + Range_Min = 600, + Range_Max = 2000, + + RPM = 650, + + RecoilSpreadPenalty = 0.002, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 9, + Damage_Min = 5, + Range_Min = 600, + Range_Max = 2800, + RPM = 700, + + RecoilSpreadPenalty = 0.0017, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 25, + RecoilPerShot = 2, + RecoilDissipationRate = 20 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 22 +SWEP.Damage_Min = 15 +SWEP.Range_Min = 1200 +SWEP.Range_Max = 2800 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 700 + +SWEP.Spread = 0.003 +SWEP.RecoilSpreadPenalty = 0.0017 +SWEP.HipFireSpreadPenalty = 0.04 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 35 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 0.75 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.25 + + + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.8 +SWEP.SightedSpeedMult = 0.65 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.36 +SWEP.SprintToFireTime = 0.38 + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +SWEP.FreeAimMaxAngle = 4.5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -6) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.18, 0.9, 0) +SWEP.SightPos = Vector(-4.15, -7.5, -4.3) + +SWEP.CorrectivePos = Vector(-0.05, 0, 0.05) +SWEP.CorrectiveAng = Angle(0.03, 0.45, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/m4.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.3 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "TacRP/weapons/m4/m4_" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 120 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + }, + ["sights"] = { + BGs_VM = { + {2, 1} + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb_nookp7", "optic_medium", "optic_sniper"}, + InstalledElements = {"sights"}, + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1, + Pos_VM = Vector(-5.5, -0.05, 6), + Pos_WM = Vector(0.75, 5, 1.15), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90 + 3.5, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-3.95, 0, 23), + Pos_WM = Vector(1.4, 21, -0.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90 + 3.5, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.9, -1.2, 13.5), + Pos_WM = Vector(2, 13, -0.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(-90, -90 + 3.5, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_m4.Remove_Clip", path .. "remove_clip.wav") +addsound("TacInt_m4.Insert_Clip", path .. "insert_clip.wav") +addsound("TacInt_m4.Insert_Clip-mid", path .. "insert_clip-mid.wav") +addsound("TacInt_m4.bolt_action", path .. "bolt_action.wav") +addsound("TacInt_m4.bolt_slap", path .. "bolt_slap.wav") +addsound("TacInt_m4.throw_catch", path .. "throw_catch.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_m_bayonet.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_m_bayonet.lua new file mode 100644 index 0000000..27e346f --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_m_bayonet.lua @@ -0,0 +1,147 @@ +SWEP.Base = "tacrp_base_knife" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Нож" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "8Bladed Melee" + +SWEP.Description = "Military standard-issue knife that can be mounted on a weapon as a bayonet. Nothing stopping you from using it like a normal knife, however." + +SWEP.Credits = "Assets: BrainBread 2" + +SWEP.ViewModel = "models/weapons/tacint_melee/v_bayonet.mdl" +SWEP.WorldModel = "models/weapons/tacint_melee/w_bayonet.mdl" + +SWEP.Slot = 0 + +SWEP.MeleeDamage = 35 +SWEP.MeleeAttackTime = 0.4 +SWEP.MeleeAttackMissTime = 0.5 + +SWEP.MeleeDamageType = DMG_SLASH + +SWEP.MeleeThrowForce = 1200 + +SWEP.MeleePerkStr = 0.45 +SWEP.MeleePerkAgi = 0.5 +SWEP.MeleePerkInt = 0.55 + +// hold types + +SWEP.HoldType = "knife" +SWEP.HoldTypeSprint = "knife" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL +SWEP.GestureBash = ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE +SWEP.GestureBash2 = ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE + +SWEP.MidAirSpreadPenalty = 0 + +SWEP.PassiveAng = Angle(-2.5, 0, 0) +SWEP.PassivePos = Vector(1, 0, -5) + +SWEP.SprintAng = Angle(0, 0, 0) +SWEP.SprintPos = Vector(2, 0, -5) + +SWEP.CustomizeAng = Angle(0, 25, 0) +SWEP.CustomizePos = Vector(2, 0, -12) + +SWEP.SprintMidPoint = { + Pos = Vector(2, 0, -5), + Ang = Angle(0, 0, 0) +} + +SWEP.HolsterVisible = false +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_GEAR +SWEP.HolsterPos = Vector(2, 0, 0) +SWEP.HolsterAng = Angle(-90, -90, 15) + +// sounds + +local path = "tacrp/weapons/knife/" + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["melee"] = {"slash_left1", "slash_left2", "slash_right1", "slash_right2"}, + ["melee2"] = {"slash_forward1", "slash_forward2"}, + ["meleethrow"] = {"knifethrow"}, +} + +SWEP.Sound_MeleeHit = { + path .. "/scrape_metal-1.wav", + path .. "/scrape_metal-2.wav", + path .. "/scrape_metal-3.wav", +} + +SWEP.Sound_MeleeHitBody = { + path .. "/flesh_hit-1.wav", + path .. "/flesh_hit-2.wav", + path .. "/flesh_hit-3.wav", + path .. "/flesh_hit-4.wav", + path .. "/flesh_hit-5.wav", +} + +SWEP.Sound_MeleeSwing = { + path .. "swing-1.wav", + path .. "swing-2.wav", + path .. "swing-3.wav", + path .. "swing-4.wav", + path .. "swing-5.wav", + path .. "swing-6.wav", +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Technique", + Category = "melee_tech", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [2] = { + PrintName = "Special", + Category = "melee_spec", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, +} + +SWEP.FreeAim = false + +SWEP.DrawCrosshair = true +SWEP.DrawCrosshairInSprint = true +SWEP.CrosshairStatic = true + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_knife2.deploy", "tacrp/magtap.ogg") + +function SWEP:PrimaryAttack() + local stop = self:RunHook("Hook_PreShoot") + if stop then return end + + self:Melee() + return +end + +function SWEP:ThinkSprint() +end + +function SWEP:ThinkSights() +end + +SWEP.AutoSpawnable = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_medkit.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_medkit.lua new file mode 100644 index 0000000..e2184ab --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_medkit.lua @@ -0,0 +1,310 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() +DEFINE_BASECLASS( "tacrp_base" ) + +// names and stuff +SWEP.PrintName = "Аптечка первой помощи" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.NoAimAssist = true + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "9Equipment" + +SWEP.Description = "Compact pack of medical supplies for treating wounds." +SWEP.Description_Quote = "\"Keep still, let me patch you up.\"" -- Left 4 Dead 1/2 + +SWEP.Credits = "Model/Texture: Left 4 Dead 2\nAnimation: Arqu" + +SWEP.ViewModel = "models/weapons/tacint/v_medkit.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_medkit.mdl" + +SWEP.NoRanger = true +SWEP.NoStatBox = true +SWEP.HUDAmmoMeter = true + +SWEP.Slot = 4 + +SWEP.NPCUsable = false +SWEP.FreeAim = false + +// misc. shooting + +SWEP.CanBlindFire = false + +SWEP.Ammo = "" +SWEP.ClipSize = 30 + +// handling + +SWEP.NoBuffer = true + +SWEP.Firemode = 0 + +SWEP.MoveSpeedMult = 1 + +SWEP.MeleeSpeedMultTime = 2 // seconds to apply slow down for + +SWEP.SprintToFireTime = 0.25 + +SWEP.MidAirSpreadPenalty = 0 +SWEP.MoveSpreadPenalty = 0 +SWEP.HipFireSpreadPenalty = 0 + +SWEP.Scope = false +SWEP.NoSecondaryMelee = true + +// hold types + +SWEP.HoldType = "slam" +SWEP.HoldTypeSprint = "normal" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(-2, 0, -3) + +SWEP.SprintAng = Angle(0, 0, 0) +SWEP.SprintPos = Vector(2, 0, -4) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_GEAR +SWEP.HolsterPos = Vector(-3, -5, 0) +SWEP.HolsterAng = Angle(-90, -90, 15) + + +SWEP.SprintMidPoint = { + Pos = Vector(0.25, 2, 1), + Ang = Angle(0, -5, 10) +} + +// sounds + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["melee"] = {"melee1", "melee2"}, +} +SWEP.NoHolsterAnimation = true +SWEP.HolsterTimeMult = 0.75 + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Perk", + Category = {"perk_melee", "perk_throw", "perk_passive"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +function SWEP:Hook_GetHintCapabilities(tbl) + if TacRP.ConVars["medkit_heal_others"]:GetInt() > 0 then + tbl["+attack"] = {so = 0, str = "hint.medkit.others"} + end + if TacRP.ConVars["medkit_heal_self"]:GetInt() > 0 then + tbl["+attack2"] = {so = 0.1, str = "hint.medkit.self"} + end +end + +SWEP.HealTarget = nil + +SWEP.LoopSound = nil + +function SWEP:PrimaryAttack() + if self:GetValue("Melee") and self:GetOwner():KeyDown(IN_USE) then + self.Primary.Automatic = true + self:Melee() + return + end + + if self:StillWaiting() then return end + if self:GetCharge() then return end + if TacRP.ConVars["medkit_heal_others"]:GetInt() <= 0 then return end + + local tr = util.TraceLine({ + start = self:GetOwner():EyePos(), + endpos = self:GetOwner():EyePos() + (self:GetOwner():EyeAngles():Forward() * 32), + filter = self:GetOwner() + }) + + if !tr.Hit then return end + if !tr.Entity then return end + + if !(tr.Entity:IsPlayer() or tr.Entity:IsNPC()) then return end + + if tr.Entity:Health() >= tr.Entity:GetMaxHealth() then return end + + self.HealTarget = tr.Entity + + self.Primary.Automatic = false + + self:PlayAnimation("unzip", 1, true) + + self:SetCharge(true) + + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + + self.LoopSound = CreateSound(self, "tacrp/weapons/bandaging_1.wav") + self.LoopSound:Play() +end + +function SWEP:OnRemove() + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + + return BaseClass.OnRemove(self) +end + +function SWEP:SecondaryAttack() + if self:StillWaiting() or !IsFirstTimePredicted() or self:GetCharge() then return end + + if self:GetOwner():Health() >= self:GetOwner():GetMaxHealth() then return end + if TacRP.ConVars["medkit_heal_self"]:GetInt() <= 0 then return end + + self.HealTarget = self:GetOwner() + + self.Primary.Automatic = false + self.Secondary.Automatic = false + + self:PlayAnimation("unzip", 1, true) + + self:SetCharge(true) + + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + + self.LoopSound = CreateSound(self:GetOwner(), "tacrp/weapons/bandaging_1.wav") + self.LoopSound:Play() +end + +function SWEP:Think() + if IsFirstTimePredicted() and self:GetCharge() and self:GetNextPrimaryFire() < CurTime() then + if !IsValid(self.HealTarget) or + self:Clip1() <= 0 or + (self.HealTarget:GetPos() - self:GetOwner():GetPos()):Length() > 64 + or + (self.HealTarget:Health() >= self.HealTarget:GetMaxHealth()) or + !(self:GetOwner():KeyDown(IN_ATTACK) or self:GetOwner():KeyDown(IN_ATTACK2)) then + self.HealTarget = nil + self:SetCharge(false) + self:PlayAnimation("draw", 1, true) + + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + else + if SERVER then + local selfheal = self.HealTarget == self:GetOwner() + local amt = TacRP.ConVars[selfheal and "medkit_heal_self" or "medkit_heal_others"]:GetInt() + local ret = {amt} + hook.Run("TacRP_MedkitHeal", self, self:GetOwner(), self.HealTarget, ret) + amt = ret and ret[1] or amt + self:SetClip1(self:Clip1() - 1) + self.HealTarget:SetHealth(math.min(self.HealTarget:Health() + amt, self.HealTarget:GetMaxHealth())) + end + self:SetNextPrimaryFire(CurTime() + TacRP.ConVars["medkit_interval"]:GetFloat()) + end + end + + return BaseClass.Think(self) +end + +function SWEP:Regenerate() + if CLIENT then return end + if self:GetNextPrimaryFire() + 0.1 > CurTime() then return end + local amt = TacRP.ConVars["medkit_regen_amount"]:GetInt() + if amt == 0 then + if self:Clip1() == 0 then + self:Remove() + end + return + end + if TacRP.ConVars["medkit_regen_activeonly"]:GetBool() + and (!IsValid(self:GetOwner()) or self:GetOwner():GetActiveWeapon() != self) then return end + self:SetClip1(math.min(self:Clip1() + amt, self:GetValue("ClipSize"))) +end + +function SWEP:Holster(wep) + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + + self:SetCharge(false) + self.HealTarget = nil + + return BaseClass.Holster(self, wep) +end + +function SWEP:OnRemove() + if game.SinglePlayer() then + self:CallOnClient("OnRemove") + end + + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + + return BaseClass.OnRemove(self) +end + +function SWEP:Initialize() + + self.ClipSize = TacRP.ConVars["medkit_clipsize"]:GetInt() + + self:SetClip1(self:GetValue("ClipSize")) + + self:SetCharge(false) + + timer.Create("medkit_ammo" .. self:EntIndex(), TacRP.ConVars["medkit_regen_delay"]:GetFloat(), 0, function() + if !IsValid(self) then + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + timer.Stop("medkit_ammo" .. self:EntIndex()) + return + end + if IsValid(self:GetOwner()) and self != self:GetOwner():GetActiveWeapon() then + if self.LoopSound then + self.LoopSound:Stop() + self.LoopSound = nil + end + end + self:Regenerate() + end) + + return BaseClass.Initialize(self) +end + +if engine.ActiveGamemode() == "terrortown" then + SWEP.HolsterVisible = false + SWEP.AutoSpawnable = false + SWEP.Kind = WEAPON_EQUIP2 + SWEP.Slot = 6 + SWEP.CanBuy = { ROLE_DETECTIVE, ROLE_TRAITOR } + SWEP.LimitedStock = true + SWEP.EquipMenuData = { + type = "Weapon", + desc = "Medical supplies for treating wounds.\nCharge regenrates over time.", + } + + function SWEP:TTTBought(buyer) + end +end + +function SWEP:Reload() +end // do nothing diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_mg4.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_mg4.lua new file mode 100644 index 0000000..6187730 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_mg4.lua @@ -0,0 +1,360 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK MG4" +SWEP.AbbrevName = "MG4" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "5Machine Gun" + +SWEP.Description = "Machine gun with huge volume of fire." +SWEP.Description_Quote = "The buzzsaw's great grandson." + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "2005" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_mg4.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_mg4.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 26, + Damage_Min = 18, + + ClipSize = 75, + }, + [TacRP.BALANCE_TTT] = { + + Description = "Machine gun with high damage but very low mobility.", + + Damage_Max = 20, + Damage_Min = 12, + Range_Min = 750, + Range_Max = 3000, + ClipSize = 75, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 19, + Damage_Min = 14, + + ClipSize = 100, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + Description = "Balanced light machine gun that can put down some serious firepower.", + ClipSize = 75, + RecoilSpreadPenalty = 0.0013, + ReloadTimeMult = 1.1 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachineGun + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +// "ballistics" + +SWEP.Damage_Max = 28 +SWEP.Damage_Min = 20 +SWEP.Range_Min = 800 +SWEP.Range_Max = 4000 +SWEP.Penetration = 10 +SWEP.ArmorPenetration = 0.8 + +SWEP.MuzzleVelocity = 17500 + +// misc. shooting + +SWEP.Firemode = 2 + +SWEP.RPM = 750 + +SWEP.Spread = 0.005 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 25 +SWEP.RecoilResetTime = 0.15 +SWEP.RecoilDissipationRate = 40 +SWEP.RecoilFirstShotMult = 2 + +SWEP.RecoilVisualKick = 1 + +SWEP.RecoilKick = 5 +SWEP.RecoilStability = 0.1 + +SWEP.HipFireSpreadPenalty = 0.025 +SWEP.RecoilSpreadPenalty = 0.0009 +SWEP.PeekPenaltyFraction = 0.125 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.4 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.1 +SWEP.ReloadSpeedMultTime = 1 + +SWEP.AimDownSightsTime = 0.44 +SWEP.SprintToFireTime = 0.48 + +SWEP.Sway = 2 +SWEP.ScopedSway = 0.75 + +SWEP.FreeAimMaxAngle = 7 + +SWEP.Bipod = true +SWEP.BipodRecoil = 0.35 +SWEP.BipodKick = 0.25 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.11, -0.6, 0) +SWEP.SightPos = Vector(-4.55, -7.5, -3.7) + +SWEP.CorrectivePos = Vector(0.025, 0, 0.125) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 100 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/mg4.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 3.5 +SWEP.DropMagazineTime = 0.7 + +SWEP.BulletBodygroups = { + [1] = {5, 0}, + [2] = {5, 1}, + [3] = {5, 2}, + [4] = {5, 3}, + [5] = {5, 4}, + [6] = {5, 5}, + [7] = {5, 6}, +} +SWEP.DefaultBodygroups = "000007" + +// sounds + +local path = "TacRP/weapons/mg4/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/g36k/g36k_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_minimi" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = {"fire4_M", "fire3_M", "fire2_M", "fire1_M"}, + ["fire1"] = "fire1_L", + ["fire2"] = "fire2_L", + ["fire3"] = "fire3_L", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +// attachments + +SWEP.AttachmentElements = { + ["sights"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["extendedbelt"] = { + BGs_VM = { + {2, 1} + }, + }, + ["bipod"] = { + BGs_VM = { + {3, 1} + }, + BGs_WM = { + {3, 1} + }, + }, +} + +//ValveBiped.MG4_root + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.feed_cover", + InstalledElements = {"sights"}, + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1.1, + Pos_VM = Vector(-0.75, 0, -3), + Pos_WM = Vector(8, 1.15, -7), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.MG4_root", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-5.5, 0, 32), + Pos_WM = Vector(33, 1.15, -5.75), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.MG4_root", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-5.225, -0.9, 19), + Pos_WM = Vector(21.5, 2, -5.25), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, 0, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "extendedbelt", "acc_duffle", "acc_bipod", "acc_sling"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_mg4.Clip_Out", path .. "clip_out-1.wav") +addsound("TacInt_mg4.Clip_In", path .. "clip_in-1.wav") +addsound("TacInt_mg4.bolt_release", path .. "bolt_release-1.wav") +addsound("TacInt_mg4.bolt_back", path .. "bolt_back-1.wav") +addsound("TacInt_mg4.bolt_forward", path .. "bolt_forward-1.wav") +addsound("TacInt_mg4.feedcover_close", path .. "feed_cover_close-1.wav") +addsound("TacInt_mg4.feedcover_open", path .. "feed_cover_open-1.wav") +addsound("TacInt_mg4.insertbullets", path .. "insert_bullets-1.wav") +addsound("TacInt_mg4.deploy", path .. "deploy-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_mp5.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_mp5.lua new file mode 100644 index 0000000..64b5ccb --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_mp5.lua @@ -0,0 +1,355 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK MP5A3" +SWEP.AbbrevName = "MP5A3" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "3Submachine Gun" + +SWEP.Description = "Well-balanced submachine gun known for its precision." +SWEP.Description_Quote = "\"Now I have a machine gun. Ho, ho, ho.\"" + +SWEP.Trivia_Caliber = "9x19mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "1966" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_mp5.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_mp5.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 20, + Damage_Min = 14, + + Range_Min = 1500, + Range_Max = 4000, + + HipFireSpreadPenalty = 0.025, + + RecoilKick = 3.5, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 14, + Damage_Min = 9, + + RecoilSpreadPenalty = 0.0015, + HipFireSpreadPenalty = 0.03, + RecoilMaximum = 12, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + + Range_Min = 1500, + Range_Max = 4000, + + HipFireSpreadPenalty = 0.02, + + RecoilKick = 2, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 20, + RecoilDissipationRate = 25 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SMG + +// "ballistics" + +SWEP.Damage_Max = 27 +SWEP.Damage_Min = 17 +SWEP.Range_Min = 800 // distance for which to maintain maximum damage +SWEP.Range_Max = 2500 // distance at which we drop to minimum damage +SWEP.Penetration = 6 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 13500 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 750 + +SWEP.Spread = 0.0038 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 12 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 40 +SWEP.RecoilFirstShotMult = 0.9 + +SWEP.RecoilVisualKick = 0.5 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.4 + +SWEP.RecoilSpreadPenalty = 0.002 +SWEP.HipFireSpreadPenalty = 0.025 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.95 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.33 +SWEP.SprintToFireTime = 0.33 + +SWEP.Sway = 0.75 +SWEP.ScopedSway = 0.25 + +SWEP.FreeAimMaxAngle = 3.5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(8, -1, -2) + +SWEP.SightAng = Angle(-0.02, 0.75, 0) +SWEP.SightPos = Vector(-4.555, -7.5, -3.6) + +SWEP.CorrectivePos = Vector(0.025, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/mp5.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.55 +SWEP.DropMagazineTime = 1 + +// sounds + +local path = "TacRP/weapons/mp5/mp5_" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = {"fire1_L", "fire1_M"}, + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["optic"] = { + BGs_VM = { + {2, 1} + }, + }, + ["tactical"] = { + BGs_VM = { + {3, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.mp5_rootbone", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-6.25, -0.3, 6.5), + Pos_WM = Vector(7, 1.5, -6.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.mp5_rootbone", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-4.32, -0.3, 20.5), + Pos_WM = Vector(24, 2.5, -5.25), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.mp5_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-4, 0.5, 11), + Pos_WM = Vector(14, 1.3, -5), + Ang_VM = Angle(90, 0, 90), + Ang_WM = Angle(0, -3.5, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_sling", "acc_duffle", "acc_extmag_smg"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_mp5.remove_clip", path .. "remove_clip.wav") +addsound("TacInt_mp5.insert_clip", path .. "insert_clip.wav") +addsound("TacInt_mp5.insert_clip-mid", path .. "insert_clip-mid.wav") +addsound("TacInt_mp5.HK_Slap", path .. "hk_slap.wav") +addsound("TacInt_mp5.bolt_back", path .. "bolt_back.wav") +addsound("TacInt_mp5.fire_select", { + path .. "fire_select-1.wav", + path .. "fire_select-2.wav", + path .. "fire_select-3.wav", +}) diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_mp7.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_mp7.lua new file mode 100644 index 0000000..1106091 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_mp7.lua @@ -0,0 +1,340 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK MP7" +SWEP.AbbrevName = "MP7" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "3Submachine Gun" + +SWEP.Description = "PDW with superb handling and close range effectiveness. High velocity rounds retain effectiveness at range and pierces armor easily." +SWEP.Description_Quote = "\"You've got to cock it motherfucker.\"" -- Big Game (2014) + +SWEP.Trivia_Caliber = "4.6x30mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "2001" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_mp7.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_mp7.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 12, + Damage_Min = 7, + + Range_Min = 1000, + Range_Max = 2000, + + RPM = 750, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 10, + Damage_Min = 5, + RPM = 800, + + RecoilSpreadPenalty = 0.003, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 20, + RecoilSpreadPenalty = 0.0035 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SMG + +// "ballistics" + +SWEP.Damage_Max = 16 +SWEP.Damage_Min = 14 +SWEP.Range_Min = 800 // distance for which to maintain maximum damage +SWEP.Range_Max = 3500 // distance at which we drop to minimum damage +SWEP.Penetration = 10 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.85 +SWEP.ArmorBonus = 2 + +SWEP.MuzzleVelocity = 23500 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 850 + +SWEP.Spread = 0.008 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 11 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 33 +SWEP.RecoilFirstShotMult = 1.5 + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.5 + +SWEP.RecoilSpreadPenalty = 0.0018 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.8 +SWEP.SightedSpeedMult = 0.75 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.3 +SWEP.SprintToFireTime = 0.28 + +SWEP.Sway = 0.75 +SWEP.ScopedSway = 0.25 + +SWEP.FreeAimMaxAngle = 4 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.1, 1, 0) +SWEP.SightPos = Vector(-3.22, -2, -4.2) + +SWEP.CorrectivePos = Vector(0.04, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 6, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 40 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pdw" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/mp7.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.2 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "tacrp/weapons/mp7/mp7_" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 115 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + }, + ["optic"] = { + BGs_VM = { + {2, 1} + }, + }, + ["rail"] = { + BGs_VM = { + {3, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.mp7_rootbone", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-4.25, 0, 1.5), + Pos_WM = Vector(4, 1.5, -6), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.mp7_rootbone", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.8, + Pos_VM = Vector(-2.25, 0.1, 11.75), + Pos_WM = Vector(18, 2.25, -4.25), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.mp7_rootbone", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + InstalledElements = {"rail"}, + Pos_VM = Vector(-2.2, -0.5, 5), + Pos_WM = Vector(10, 1.3, -4), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -3.5, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_sling", "acc_duffle", "acc_extmag_smg"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +local path_mp5 = "tacrp/weapons/mp5/mp5_" + +addsound("tacint_mp7.remove_clip", path .. "remove_clip.wav") +addsound("tacint_mp7.insert_clip", path .. "insert_clip.wav") +addsound("tacint_mp7.bolt_action", path .. "bolt_action.wav") +addsound("tacint_mp7.forearm_deploy", path .. "forearm_deploy.wav") +addsound("tacint_mp5.fire_select", { + path_mp5 .. "fire_select-1.wav", + path_mp5 .. "fire_select-2.wav", + path_mp5 .. "fire_select-3.wav", +}) diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_mr96.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_mr96.lua new file mode 100644 index 0000000..09c61ec --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_mr96.lua @@ -0,0 +1,331 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Manurhin MR96" +SWEP.AbbrevName = "MR96" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "2Magnum Pistol" + +SWEP.Description = "Magnum revolver with good handling and stopping power. Accurate, but hard to fire rapidly." +SWEP.Description_Quote = "Legend has it a stainless steel version exists..." -- One of the thugs in Ghost in the Shell 2: Innocence (2004) uses a stainless steel MR96 + +SWEP.Trivia_Caliber = ".357 Magnum" +SWEP.Trivia_Manufacturer = "Chapuis Armes" +SWEP.Trivia_Year = "1996" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_mr96.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_mr96.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 35, + Damage_Min = 20, + + Range_Min = 600, + Range_Max = 1600, + + RPM = 120, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 + }, + + RecoilMaximum = 2, + RecoilDissipationRate = 2.5, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 55, + Damage_Min = 24, + RPM = 120, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilDissipationRate = 4 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Magnum + +// "ballistics" + +SWEP.Damage_Max = 54 // damage at minimum range +SWEP.Damage_Min = 28 // damage at maximum range +SWEP.Range_Min = 300 // distance for which to maintain maximum damage +SWEP.Range_Max = 2500 // distance at which we drop to minimum damage +SWEP.Penetration = 6 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.9 + +SWEP.MuzzleVelocity = 12500 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Double-Action" // only used externally for firemode name distinction + +SWEP.RPM = 140 + +SWEP.Spread = 0.001 + +SWEP.ShootTimeMult = 1 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 2 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 9 +SWEP.RecoilFirstShotMult = 0.9 + +SWEP.RecoilVisualKick = 5 +SWEP.RecoilKick = 9 +SWEP.RecoilStability = 0.5 + +SWEP.RecoilSpreadPenalty = 0.018 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.22 +SWEP.SprintToFireTime = 0.3 + +SWEP.FreeAimMaxAngle = 4.5 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_REVOLVER + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-125, 0, 45) +SWEP.BlindFireSuicidePos = Vector(25, 12, -6) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(-0.15, 1, 0) +SWEP.SightPos = Vector(-3.5, 0, -3.7) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.6 + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 6 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 + +SWEP.ReloadUpInTime = 1.35 + +SWEP.JamSkipFix = true + +// sounds + +local path = "TacRP/weapons/mr96/" + +SWEP.Sound_Shoot = { + "^" .. path .. "mr96_fire-1.wav", + "^" .. path .. "mr96_fire-2.wav", + "^" .. path .. "mr96_fire-3.wav", +} + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 2 +SWEP.QCA_Eject = 0 +SWEP.EjectEffect = 0 + +SWEP.MuzzleEffect = "muzzleflash_1" + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["fire_iron"] = "shoot1", + ["blind_fire"] = {"blind_shoot1", "blind_shoot2"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "draw" +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -3, -4.5), + vm_ang = Angle(0, 15, 0), + t = 0.5, + tmax = 0.5, + bones = { + { + bone = "ValveBiped.cylinder", + ang = Angle(-60, 0, 0), + t0 = 0, + t1 = 0.25, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(35, 0, 0), + t0 = 0.05, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.mr96_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 0.8, + WMScale = 1, + Pos_VM = Vector(-3.9, -0.125, 6.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 1.5, -0.8), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.mr96_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + VMScale = 1, + WMScale = 1, + Pos_VM = Vector(-2.25, -0.125, 9), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(0, 8, -2.25), + Ang_WM = Angle(0, -90, 180), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_holster", "acc_brace", "acc_bipod"}, // yes, MR96 with bipod is a real thing! + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Trigger", + Category = {"trigger_revolver"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Ammo", + Category = {"ammo_pistol", "ammo_roulette"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_MR96.Release_Cylinder", path .. "mr96_release_cylinder.wav") +addsound("TacInt_MR96.Eject_Shells", path .. "mr96_eject_shells.wav") +addsound("TacInt_MR96.Insert_Bullets", path .. "mr96_insert_bullets.wav") +addsound("TacInt_MR96.Shut_Cylinder", path .. "mr96_shut_cylinder.wav") +addsound("TacInt_MR96.Insert_Bullets-Mid", path .. "mr96_insert_bullets-mid.wav") +addsound("TacInt_MR96.Cock_Hammer", path .. "mr96_cockhammer.wav") +addsound("TacInt_MR96.Deploy", path .. "mr96_deploy.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_charge.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_charge.lua new file mode 100644 index 0000000..df4105c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_charge.lua @@ -0,0 +1,35 @@ +AddCSLuaFile() + +SWEP.Base = "tacrp_base_nade" +SWEP.Spawnable = TacRP.AreTheGrenadeAnimsReadyYet +SWEP.IconOverride = "entities/tacrp_ammo_charge.png" + + +// names and stuff +SWEP.PrintName = "Взрывной заряд" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.ViewModel = "models/weapons/tacint/v_throwable_breach.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_frag.mdl" + +SWEP.ViewModelFOV = 65 + +SWEP.Slot = 4 + +SWEP.PrimaryGrenade = "charge" + +SWEP.FiremodeName = "Throw" + +SWEP.AnimationTranslationTable = { + ["prime_grenade"] = "pullpin", + ["throw_grenade"] = "throw", + ["throw_grenade_underhand"] = "throw", +} + +SWEP.TTTReplace = {} // {["weapon_ttt_confgrenade"] = 0.5} + +SWEP.Attachments = {} + +SWEP.HoldType = "melee" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_flashbang.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_flashbang.lua new file mode 100644 index 0000000..e2b2044 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_flashbang.lua @@ -0,0 +1,35 @@ +AddCSLuaFile() + +SWEP.Base = "tacrp_base_nade" +SWEP.Spawnable = TacRP.AreTheGrenadeAnimsReadyYet +SWEP.IconOverride = "entities/tacrp_ammo_flashbang.png" + + +// names and stuff +SWEP.PrintName = "Светошумовая граната" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.ViewModel = "models/weapons/tacint/v_throwable_flashbang.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_flashbang.mdl" + +SWEP.ViewModelFOV = 65 + +SWEP.Slot = 4 + +SWEP.PrimaryGrenade = "flashbang" + +SWEP.FiremodeName = "Throw" + +SWEP.AnimationTranslationTable = { + ["prime_grenade"] = "pullpin", + ["throw_grenade"] = "throw", + ["throw_grenade_underhand"] = "throw", +} + +SWEP.TTTReplace = {["weapon_ttt_confgrenade"] = 1} + +SWEP.Attachments = {} + +SWEP.HoldType = "melee" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_frag.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_frag.lua new file mode 100644 index 0000000..51a0973 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_frag.lua @@ -0,0 +1,35 @@ +AddCSLuaFile() + +SWEP.Base = "tacrp_base_nade" +SWEP.Spawnable = TacRP.AreTheGrenadeAnimsReadyYet +SWEP.IconOverride = "entities/tacrp_ammo_frag.png" + +// names and stuff +SWEP.PrintName = "Осколочная граната" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.ViewModel = "models/weapons/tacint/v_throwable_frag.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_frag.mdl" + +SWEP.ViewModelFOV = 65 + +SWEP.Slot = 4 + +SWEP.PrimaryGrenade = "frag" + +SWEP.FiremodeName = "Throw" + +SWEP.AnimationTranslationTable = { + ["prime_grenade"] = "pullpin", + ["throw_grenade"] = "throw", + ["throw_grenade_underhand"] = "throw", +} + +SWEP.TTTReplace = {} +SWEP.AutoSpawnable = false + +SWEP.Attachments = {} + +SWEP.HoldType = "melee" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_smoke.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_smoke.lua new file mode 100644 index 0000000..f02645c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_smoke.lua @@ -0,0 +1,34 @@ +AddCSLuaFile() + +SWEP.Base = "tacrp_base_nade" +SWEP.Spawnable = TacRP.AreTheGrenadeAnimsReadyYet +SWEP.IconOverride = "entities/tacrp_ammo_smoke.png" + +// names and stuff +SWEP.PrintName = "Дымовая граната" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.ViewModel = "models/weapons/tacint/v_throwable_smoke.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_smoke.mdl" + +SWEP.ViewModelFOV = 65 + +SWEP.Slot = 4 + +SWEP.PrimaryGrenade = "smoke" + +SWEP.FiremodeName = "Throw" + +SWEP.AnimationTranslationTable = { + ["prime_grenade"] = "pullpin", + ["throw_grenade"] = "throw", + ["throw_grenade_underhand"] = "throw", +} + +SWEP.TTTReplace = {["weapon_ttt_smokegrenade"] = 1} + +SWEP.Attachments = {} + +SWEP.HoldType = "melee" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_thermite.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_thermite.lua new file mode 100644 index 0000000..a0f3ac3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_nade_thermite.lua @@ -0,0 +1,34 @@ +AddCSLuaFile() + +SWEP.Base = "tacrp_base_nade" +SWEP.Spawnable = TacRP.AreTheGrenadeAnimsReadyYet +SWEP.IconOverride = "entities/tacrp_ammo_fire.png" + +// names and stuff +SWEP.PrintName = "Термитная граната" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.ViewModel = "models/weapons/tacint/v_throwable_thermite.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_flashbang.mdl" + +SWEP.ViewModelFOV = 65 + +SWEP.Slot = 4 + +SWEP.PrimaryGrenade = "thermite" + +SWEP.FiremodeName = "Throw" + +SWEP.AnimationTranslationTable = { + ["prime_grenade"] = "pullpin", + ["throw_grenade"] = "throw", + ["throw_grenade_underhand"] = "throw", +} + +SWEP.TTTReplace = {["weapon_zm_molotov"] = 1} + +SWEP.Attachments = {} + +SWEP.HoldType = "melee" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_p250.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_p250.lua new file mode 100644 index 0000000..e29b208 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_p250.lua @@ -0,0 +1,367 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "SIG P250" +SWEP.AbbrevName = "P250" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "1Pistol" + +SWEP.Description = "Powerful handgun that exchanges capacity for damage and precision." +SWEP.Description_Quote = "\"You wait until they get close and aim for the face. You understand?\"" -- Watch_Dogs (2014) + +SWEP.Trivia_Caliber = ".357 SIG" +SWEP.Trivia_Manufacturer = "SIG Sauer, Inc." +SWEP.Trivia_Year = "2007" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_p250.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_p250.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 32, + Damage_Min = 20, + Range_Min = 900, + Range_Max = 2800, + ArmorPenetration = 0.65, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3.25, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_TTT] = { + Description = "Powerful handgun that exchanges fire rate for damage and precision.", + + Damage_Max = 22, + Damage_Min = 12, + Range_Min = 800, + Range_Max = 2000, + RPM = 200, + RPMMultSemi = 1, + + RecoilResetInstant = true, + RecoilMaximum = 5, + RecoilResetTime = 0.2, + RecoilDissipationRate = 5.5, + RecoilFirstShotMult = 0.8, + RecoilSpreadPenalty = 0.012, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 18, + Damage_Min = 12, + Range_Min = 900, + Range_Max = 2800, + RPM = 450, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Pistol + +// "ballistics" + +SWEP.Damage_Max = 28 +SWEP.Damage_Min = 15 +SWEP.Range_Min = 800 // distance for which to maintain maximum damage +SWEP.Range_Max = 2500 // distance at which we drop to minimum damage +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.775 +SWEP.ArmorBonus = 1 + +SWEP.MuzzleVelocity = 11000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 500 +SWEP.RPMMultSemi = 0.7 + +SWEP.Spread = 0.004 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 3 +SWEP.RecoilResetTime = 0.02 +SWEP.RecoilDissipationRate = 12 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 1.5 +SWEP.RecoilKick = 5 +SWEP.RecoilStability = 0.6 + +SWEP.RecoilSpreadPenalty = 0.0065 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.9 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.25 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.5 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-130, 0, 45) +SWEP.BlindFireSuicidePos = Vector(25, 15, -6) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(0.02, 0.15, 0) +SWEP.SightPos = Vector(-3.45, 0, -3.35) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 12 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pistol_heavy" + +SWEP.ReloadTimeMult = 1.1 + +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/p250.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 0.9 +SWEP.DropMagazineTime = 0.2 + +// sounds + +local path = "TacRP/weapons/p250/p250_" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 4 + +SWEP.MuzzleEffect = "muzzleflash_pistol" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.6), + vm_ang = Angle(0, 2, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.slide", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(-15, 0, 0), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +SWEP.LastShot = true + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = "optic_pistol", + Bone = "ValveBiped.slide", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1, + WMScale = 1, + Pos_VM = Vector(0.01, 0, 0), + Ang_VM = Angle(0, 90, 180), + Pos_WM = Vector(0, -1, -1), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.barrel_assembly", + WMBone = "Box01", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 0.5, + WMScale = 0.5, + Pos_VM = Vector(-0.6, 0.45, 6), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0, 8, -1.5), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.p250_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + VMScale = 1.1, + WMScale = 1.3, + Pos_VM = Vector(-2, 0, 5.25), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(0, 5, -2.75), + Ang_WM = Angle(0, -90, 180), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_pistol", "acc_holster", "acc_brace"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_p250.clip_in", path .. "clip_in.wav") +addsound("TacInt_p250.clip_in-mid", path .. "clip_in-mid.wav") +addsound("TacInt_p250.clip_out", path .. "clip_out.wav") +addsound("TacInt_p250.slide_action", path .. "slide_action.wav") +addsound("TacInt_p250.slide_shut", path .. "slide_shut.wav") +addsound("TacInt_p250.cock_hammer", path .. "cockhammer.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_p90.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_p90.lua new file mode 100644 index 0000000..d527d14 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_p90.lua @@ -0,0 +1,319 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "FN P90" +SWEP.AbbrevName = "P90" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "3Submachine Gun" + +SWEP.Description = "Bullpup PDW with a generous top-loaded magazine and controllable spread. High velocity rounds retain effectiveness at range and pierces armor easily." +SWEP.Description_Quote = "\"This is a weapon of war, it's made to kill your enemy.\"" // SG-1 Stargate + +SWEP.Trivia_Caliber = "5.7x28mm" +SWEP.Trivia_Manufacturer = "FN Herstal" +SWEP.Trivia_Year = "1990" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_p90.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_p90.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 14, + Damage_Min = 12, + + RecoilKick = 2, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 10, + Damage_Min = 8, + Range_Min = 1000, + Range_Max = 2000, + + RPM = 800, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 9, + Damage_Min = 7, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.0025, + RecoilMaximum = 25 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SMG + +// "ballistics" + +SWEP.Damage_Max = 15 +SWEP.Damage_Min = 12 +SWEP.Range_Min = 1000 // distance for which to maintain maximum damage +SWEP.Range_Max = 5000 // distance at which we drop to minimum damage +SWEP.Penetration = 10 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.875 +SWEP.ArmorBonus = 2 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 850 + +SWEP.Spread = 0.006 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 20 +SWEP.RecoilResetTime = 0.05 +SWEP.RecoilDissipationRate = 45 +SWEP.RecoilFirstShotMult = 2 + +SWEP.RecoilVisualKick = 0.75 + +SWEP.RecoilKick = 1.5 +SWEP.RecoilStability = 0.1 + +SWEP.RecoilSpreadPenalty = 0.0014 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.35 +SWEP.SprintToFireTime = 0.375 + +SWEP.Sway = 0.75 +SWEP.ScopedSway = 0.25 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.01, 0, 0) +SWEP.SightPos = Vector(-3.705, -15, -4.6) + +SWEP.CorrectivePos = Vector(0.02, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 6, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 50 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pdw" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/p90.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.85 +SWEP.DropMagazineTime = 0.55 + +// sounds + +local path = "tacrp/weapons/p90/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 115 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_smg" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {1, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "p90_ROOT", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-5.35, 0, 6.5), + Pos_WM = Vector(8, 1.5, -7), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "p90_ROOT", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + Pos_VM = Vector(-1.85, 0, 14), + Pos_WM = Vector(16, 2.25, -3.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "p90_ROOT", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + InstalledElements = {"rail"}, + Pos_VM = Vector(-4.35, -0.6, 7.6), + Pos_WM = Vector(10, 1.3, -4), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -3.5, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "acc_extmag_smg"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_p90.Clip_Out", path .. "clip_out.wav") +addsound("tacint_p90.Clip_In", path .. "clip_in.wav") +addsound("tacint_p90.Clip_slap", path .. "clip_slap.wav") +addsound("tacint_p90.bolt_release", path .. "bolt_release.wav") +addsound("tacint_p90.bolt_back", path .. "bolt_back.wav") +addsound("tacint_p90.bolt_forward", path .. "bolt_forward.wav") +addsound("tacint_p90.fire_select", path .. "fire_select.wav") +addsound("tacint_p90.mag_release", path .. "mag_release.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_awp.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_awp.lua new file mode 100644 index 0000000..2ab56a0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_awp.lua @@ -0,0 +1,342 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "AWM" +SWEP.AbbrevName = "AWM" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "7Sniper Rifle" + +SWEP.Description = "Robust magnum sniper with unmatched power and accuracy. A counter-terrorist favourite.\nEquipped with a 12x scope by default." +SWEP.Description_Quote = "\"He's a madman, a scientist, and a sharpshooter.\"" // Nifty's 51 frags at IEM Sydney 2018 + +SWEP.Trivia_Caliber = ".338 Lapua Magnum" +SWEP.Trivia_Manufacturer = "Accuracy International" +SWEP.Trivia_Year = "1988" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = [[ +Model: Hav0c +Texture: Bullethead & Kimono +Sounds: Vunsunta +Animations: Tactical Intervention +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_awp.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_awp.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 90, + Damage_Min = 125, + + Range_Min = 2000, + Range_Max = 6000, + + Sway = 2, + ScopedSway = 0.075, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 80, + Damage_Min = 130, + + Range_Min = 2000, + Range_Max = 5000, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SniperRifle + +// "ballistics" + +SWEP.Damage_Max = 125 +SWEP.Damage_Min = 100 +SWEP.Range_Min = 1000 +SWEP.Range_Max = 6000 +SWEP.Penetration = 25 +SWEP.ArmorPenetration = 1 +SWEP.ArmorBonus = 4 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.5 +} + +SWEP.MuzzleVelocity = 36000 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Bolt-Action" // only used externally for firemode name distinction + +SWEP.RPM = 30 + +SWEP.ShootTimeMult = 1.2 + +SWEP.Spread = 0.00 + +SWEP.HipFireSpreadPenalty = 0.05 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.5 // time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 5 + +SWEP.RecoilKick = 10 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ReloadSpeedMult = 0.1 +SWEP.ReloadSpeedMultTime = 1 + +SWEP.ReloadSpeedMult = 0.3 + +SWEP.AimDownSightsTime = 0.75 +SWEP.SprintToFireTime = 0.90 + +SWEP.Sway = 3 +SWEP.ScopedSway = 0.75 + +SWEP.FreeAimMaxAngle = 9 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "shotgun" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(2, -2, -6) + +SWEP.BlindFireAng = Angle(-10, -15, -0) +SWEP.BlindFirePos = Vector(3, -2, -2) + +SWEP.BlindFireSuicideAng = Angle(0, 115, 0) +SWEP.BlindFireSuicidePos = Vector(0, 32, -24) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -4) + +SWEP.SightAng = Angle(0.4, 0.1, 0) +SWEP.SightPos = Vector(-3.75, -10.5, -3.83) + +SWEP.CorrectivePos = Vector(0.015, 0, 0.3) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 4, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/sniper.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 12 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true +SWEP.ScopeOverlaySize = 0.75 + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 5 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.ReloadTimeMult = 1.25 +SWEP.DropMagazineImpact = "metal" +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/uratio.mdl" + +SWEP.ReloadUpInTime = 2.2 +SWEP.DropMagazineTime = 1.15 + +// sounds + +local path = "TacRP/weapons/uratio/uratio_" +local path1 = "tacint_shark/awp/awp" + +SWEP.Sound_Shoot = "^" .. path1 .. "1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/ak47/ak47_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_1" +SWEP.EjectEffect = 2 +SWEP.EjectDelay = 0.9 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2"}, + ["blind_fire"] = "blind_shoot1" +} + +SWEP.DeployTimeMult = 1.3 + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["optic"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["irons"] = { + BGs_VM = { + {3, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights_sniper", "optic_cqb", "optic_medium", "optic_sniper"}, + WMBone = "ValveBiped.Bip01_R_Hand", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-5.3, 0.05, 6.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(10, 1.3, -6), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + WMBone = "ValveBiped.Bip01_R_Hand", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-4.2, 0.1, 41), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(48, 1.3, -5.4), + Ang_WM = Angle(0, 0, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + WMBone = "ValveBiped.Bip01_R_Hand", + Bone = "ValveBiped.uratio_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-2.2, 0, 17), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(23, 1.2, -2.8), + Ang_WM = Angle(0, 0, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_extmag_sniper", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_sniper"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_aiawp.Clip_Out", path1 .. "_magout.wav") +addsound("tacint_aiawp.Clip_In", path1 .. "_magin.wav") +addsound("tacint_aiawp.Bolt_Back", path1 .. "_boltback.wav") +addsound("tacint_aiawp.bolt_forward", path1 .. "_boltforward.wav") +addsound("tacint_aiawp.safety", path .. "safety.wav") +addsound("tacint_aiawp.buttstock_back", path .. "buttstock_back.wav") +addsound("tacint_aiawp.buttstock_rest_down", path .. "buttstock_rest_down.wav") +addsound("tacint_aiawp.flip_up_cover", path .. "flip_up_cover.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_fnmag.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_fnmag.lua new file mode 100644 index 0000000..034a6e1 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_fnmag.lua @@ -0,0 +1,319 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "FN MAG" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "5Machine Gun" + +SWEP.Description = "Machine gun with practically no mobility but compensates with incredible firepower and capacity." +SWEP.Description_Quote = "\"You can't leave me here with these... animals.\"" // Made up out of thin air + +SWEP.Trivia_Caliber = "7.62x51mm" +SWEP.Trivia_Manufacturer = "Fabrique National" +SWEP.Trivia_Year = "1958" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Model & Texture: Call to Arms\nAnimation: Tactical Intervention\nPorted By: Arctic" + +SWEP.ViewModel = "models/weapons/tacint/v_fnmag.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_fnmag.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 50, + Damage_Min = 34, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 20, + Damage_Min = 13, + Range_Min = 600, + Range_Max = 2000, + ClipSize = 200, + + HipFireSpreadPenalty = 0.03, + RecoilMaximum = 20, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachineGun + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +// "ballistics" + +SWEP.Damage_Max = 45 +SWEP.Damage_Min = 28 +SWEP.Range_Min = 1800 +SWEP.Range_Max = 6000 +SWEP.Penetration = 10 +SWEP.ArmorPenetration = 0.75 + +SWEP.MuzzleVelocity = 22000 + +// misc. shooting + +SWEP.Firemode = 2 + +SWEP.RPM = 650 + +SWEP.Spread = 0.009 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 50 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 25 +SWEP.RecoilFirstShotMult = 2 + +SWEP.RecoilVisualKick = 1.25 +SWEP.RecoilKick = 5 +SWEP.RecoilAltMultiplier = 170 +SWEP.RecoilStability = 0.05 + +SWEP.HipFireSpreadPenalty = 0.05 +SWEP.MoveSpreadPenalty = 0.02 +SWEP.RecoilSpreadPenalty = 0.01 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.75 +SWEP.ShootingSpeedMult = 0.5 +SWEP.SightedSpeedMult = 0.55 + +SWEP.ReloadSpeedMult = 0.15 +SWEP.ReloadSpeedMultTime = 1 + +SWEP.AimDownSightsTime = 0.36 +SWEP.SprintToFireTime = 0.42 + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.01 + +SWEP.FreeAimMaxAngle = 9 + +SWEP.Bipod = true +SWEP.BipodRecoil = 0.35 +SWEP.BipodKick = 0.25 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -7) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0, 0.15, 0) +SWEP.SightPos = Vector(-4.355, -5, -3.2) + +SWEP.CorrectivePos = Vector(0.025, 0, 0.125) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 100 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_rifle" + +SWEP.ReloadTimeMult = 1.75 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/fnmag.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 3.5 +SWEP.DropMagazineTime = 0.7 + +SWEP.BulletBodygroups = { + [1] = {1, 1}, + [2] = {2, 1}, + [3] = {3, 1}, + [4] = {4, 1}, + [5] = {5, 1}, + [6] = {6, 1}, + [7] = {7, 1}, + [8] = {8, 1}, + [9] = {9, 1}, + [10] = {10, 1}, + [11] = {11, 1}, + [12] = {12, 1}, +} +SWEP.DefaultBodygroups = "000000000000000000000000000" +SWEP.BulletBodygroupsSetAll = true + +// sounds + +local path = "TacRP/weapons/mg4/" +local path1 = "tacint_extras/fnmag/" + +SWEP.Sound_Shoot = "^" .. path1 .. "fire.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/g36k/g36k_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.Pitch_Shoot = 90 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_minimi" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = {"fire4_L", "fire3_L", "fire2_L", "fire1_L"}, + ["fire1"] = "fire1_L", + ["fire2"] = "fire2_L", + ["fire3"] = "fire3_L", + ["fire4"] = "fire4_L", + ["fire5"] = "fire5_L", + ["melee"] = {"melee1", "melee2"} +} + +// attachments + +SWEP.AttachmentElements = { +} + +//ValveBiped.MG4_root + +SWEP.NoTactical = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.feed_cover", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1.1, + Pos_VM = Vector(-1.1, -0.2, -4), + Pos_WM = Vector(8, 1.15, -7), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical_zoom", "tactical_ebullet"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "extendedbelt", "acc_duffle", "acc_bipod", "acc_sling"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_mg4.Clip_Out", path .. "clip_out-1.wav") +addsound("TacInt_mg4.Clip_In", path .. "clip_in-1.wav") +addsound("TacInt_mg4.bolt_release", path .. "bolt_release-1.wav") +addsound("TacInt_mg4.bolt_back", path .. "bolt_back-1.wav") +addsound("TacInt_mg4.bolt_forward", path .. "bolt_forward-1.wav") +addsound("TacInt_mg4.feedcover_close", path .. "feed_cover_close-1.wav") +addsound("TacInt_mg4.feedcover_open", path .. "feed_cover_open-1.wav") +addsound("TacInt_mg4.insertbullets", path .. "insert_bullets-1.wav") +addsound("TacInt_mg4.deploy", path .. "deploy-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_makarov.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_makarov.lua new file mode 100644 index 0000000..84f4c53 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_makarov.lua @@ -0,0 +1,351 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Пистолет Макарова" +SWEP.AbbrevName = "PM" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "5Value" +SWEP.SubCatType = "1Pistol" + +SWEP.Description = "Mass production Soviet sidearm designed to be carried often and rarely shot. Lethality is poor beyond point blank." +SWEP.Description_Quote = "\"You're listening to Apocalypse Radio.\"" -- ROBLOX - Apocalypse Rising + +SWEP.Trivia_Caliber = "9x18mm" +SWEP.Trivia_Manufacturer = "Izhevsk Mechanical Plant" +SWEP.Trivia_Year = "1948" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: TehSnake\nSound: Hk, Vunsunta\nAnimation: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_makarov.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_makarov.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 25, + Damage_Min = 11, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.15, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 18, + Damage_Min = 6, + Range_Min = 200, + Range_Max = 1200, + RPM = 400, + RPMMultSemi = 1, + + RecoilResetInstant = true, + RecoilMaximum = 8, + RecoilResetTime = 0.22, + RecoilDissipationRate = 10, + RecoilSpreadPenalty = 0.003, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Pistol + +// "ballistics" + +SWEP.Damage_Max = 20 +SWEP.Damage_Min = 5 +SWEP.Range_Min = 250 +SWEP.Range_Max = 1200 +SWEP.Penetration = 2 +SWEP.ArmorPenetration = 0.55 +SWEP.ArmorBonus = 0.5 + +SWEP.MuzzleVelocity = 11000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 700 +SWEP.RPMMultSemi = 0.7 + +SWEP.Spread = 0.008 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 3 +SWEP.RecoilResetTime = 0.03 +SWEP.RecoilDissipationRate = 12 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.15 + +SWEP.RecoilSpreadPenalty = 0.003 +SWEP.HipFireSpreadPenalty = 0.014 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 1 +SWEP.ShootingSpeedMult = 0.9 +SWEP.SightedSpeedMult = 0.825 + +SWEP.ReloadSpeedMult = 0.85 + +SWEP.AimDownSightsTime = 0.21 +SWEP.SprintToFireTime = 0.21 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.5 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-130, 0, 45) +SWEP.BlindFireSuicidePos = Vector(25, 15, -6) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(-0.01, 0.3, 0) +SWEP.SightPos = Vector(-3.5, 0, -3.45) + +SWEP.CorrectivePos = Vector(0, 0, 0) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 8 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pistol_light" + +SWEP.ReloadUpInTime = 0.85 +SWEP.DropMagazineTime = 0.2 + +SWEP.ReloadTimeMult = 1.2 + +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/makarov.mdl" +SWEP.DropMagazineImpact = "pistol" + +// sounds + +local path = "tacrp/weapons/p2000/p2000_" +local path1 = "tacint_extras/makarov/" + +SWEP.Sound_Shoot = "^" .. path1 .. "makarov-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 4 + +SWEP.MuzzleEffect = "muzzleflash_pistol" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire_iron"] = "shoot2", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.6), + vm_ang = Angle(0, 2, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.slide", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.hammer", + ang = Angle(-15, 0, 0), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +SWEP.NoIdle = true + +SWEP.ShootTimeMult = 0.5 + +SWEP.LastShot = true + +// attachments + +SWEP.NoTactical = true +SWEP.NoOptic = true + +SWEP.Attachments = { + [1] = { + PrintName = "Muzzle", + Category = {"silencer"}, + Bone = "ValveBiped.p2000_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.6, + WMScale = 0.5, + Pos_VM = Vector(-3.2, 0, 9.22), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(0.2, 6.9, -1.8), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical_ebullet"}, + Bone = "ValveBiped.p2000_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + VMScale = 1.1, + WMScale = 1.3, + Pos_VM = Vector(-2, 0, 6), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(0, 5, -2.75), + Ang_WM = Angle(0, -90, 180), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_pistol2", "acc_holster", "acc_brace"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_makarov.clip_in", path1 .. "MagInTap.wav") +addsound("tacint_makarov.clip_in-mid", path1 .. "MagInScratch.wav") +addsound("tacint_makarov.clip_out", path1 .. "MagOut.wav") +addsound("tacint_makarov.slide_action", path1 .. "SlideBack.wav") +addsound("tacint_makarov.slide_shut", path1 .. "Sliderelease.wav") +addsound("tacint_makarov.cock_hammer", path .. "cockhammer.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_vykhlop.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_vykhlop.lua new file mode 100644 index 0000000..f81575c --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_pa_vykhlop.lua @@ -0,0 +1,341 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "ВСК Выхлоп" +SWEP.AbbrevName = "Vykhlop" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "7Sniper Rifle" + +SWEP.Description = "Subsonic sniper rifle with high capacity and rate of fire, but low muzzle velocity and poor handling.\nEquipped with a 6x scope by default." +SWEP.Description_Quote = "\"Na'am seyidi, al qanas ala al khatt.\"" // Syrian Warfare (2016) + +SWEP.Trivia_Caliber = "12.7x55mm" +SWEP.Trivia_Manufacturer = "TsKIB SOO" +SWEP.Trivia_Year = "2002" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = "Model/Texture: Call to Arms\nAnimations: Tactical Intervention, Arctic" + +SWEP.ViewModel = "models/weapons/tacint/v_vykhlop.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_vykhlop.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 75, + Damage_Min = 120, + + Range_Min = 1500, + Range_Max = 4000, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 55, + Damage_Min = 80, + Range_Min = 300, + Range_Max = 2000, + RPM = 45, + ShootTimeMult = 0.9, + HipFireSpreadPenalty = 0.025, + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SniperRifle + +// "ballistics" + +SWEP.Damage_Max = 125 +SWEP.Damage_Min = 85 +SWEP.Range_Min = 800 +SWEP.Range_Max = 5000 +SWEP.Penetration = 18 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.8 +SWEP.ArmorBonus = 2.5 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 +} + +SWEP.MuzzleVelocity = 11500 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Bolt-Action" // only used externally for firemode name distinction + +SWEP.RPM = 44 +SWEP.ShootTimeMult = 0.42 + +SWEP.Spread = 0 + +SWEP.HipFireSpreadPenalty = 0.1 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.4 +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 10 +SWEP.RecoilKick = 15 +SWEP.RecoilStability = 0.5 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.75 +SWEP.ShootingSpeedMult = 0.25 +SWEP.SightedSpeedMult = 0.4 + +SWEP.ReloadSpeedMult = 0.25 + +SWEP.AimDownSightsTime = 0.5 +SWEP.SprintToFireTime = 0.6 + +SWEP.Sway = 3 +SWEP.ScopedSway = 0.2 + +SWEP.FreeAimMaxAngle = 10 + +SWEP.Bipod = true +SWEP.BipodRecoil = 0.25 +SWEP.BipodKick = 0.2 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "shotgun" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(2, 1, -6) + +SWEP.BlindFireAng = Angle(0, 15, -45) +SWEP.BlindFirePos = Vector(1, -2, -3) + +SWEP.BlindFireLeftAng = Angle(75, 0, 0) +SWEP.BlindFireLeftPos = Vector(8, 10, -6) + +SWEP.BlindFireRightAng = Angle(-75, 0, 0) +SWEP.BlindFireRightPos = Vector(-10, 10, -5) + +SWEP.BlindFireSuicideAng = Angle(0, 135, 0) +SWEP.BlindFireSuicidePos = Vector(-2, 45, -35) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.03, 0.8, 0) +SWEP.SightPos = Vector(-3.92, -4, -4.77) + +SWEP.CorrectivePos = Vector(0.09, 0, 0.05) +SWEP.CorrectiveAng = Angle(0, 0, -1) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 4, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/l96.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 6 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 10 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.AmmoTTT = "357" +SWEP.Ammo_Expanded = "ti_sniper" + +SWEP.TracerNum = 0 + +SWEP.ReloadTimeMult = 2 +SWEP.ShootTimeMult = 0.8 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/vykhlop.mdl" + +SWEP.ReloadUpInTime = 2.1 +SWEP.DropMagazineTime = 0.7 + +// sounds + +local path = "tacrp/weapons/aug/aug_" + +SWEP.Sound_Shoot = "^tacrp/weapons/sg551/sg551_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 80 +SWEP.Pitch_Shoot = 80 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_suppressed" +SWEP.EjectEffect = 2 +SWEP.EjectDelay = 0.5 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire"] = "fire_bolt", + ["blind_fire"] = "shoot1", + ["jam"] = "midreload" +} + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["irons"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights_sniper", "optic_cqb_nookp7", "optic_medium", "optic_sniper"}, + InstalledElements = {"optic"}, + Bone = "ValveBiped.AUG_rootbone", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 1, + Pos_VM = Vector(-5.3, 0, 1), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(4, 1, -5.8), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AUG_rootbone", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-3, -1.05, 8), + Ang_VM = Angle(90, 0, -90), + Pos_WM = Vector(12, 1.75, -4), + Ang_WM = Angle(0, 0, 90), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_sniper", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_amr"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_aug.insert_clip", path .. "insert_clip.wav") +addsound("tacint_aug.remove_clip", path .. "remove_clip.wav") +addsound("tacint_aug.Handle_FoldDown", path .. "handle_folddown.wav") +addsound("tacint_aug.bolt_lockback", path .. "bolt_lockback.wav") +addsound("tacint_aug.bolt_release", path .. "bolt_release.wav") + +path = "TacRP/weapons/m14/m14_" + +addsound("TacInt_m14.bolt_back", path .. "bolt_back.wav") +addsound("TacInt_m14.bolt_release", path .. "bolt_release.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_pdw.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_pdw.lua new file mode 100644 index 0000000..830c87e --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_pdw.lua @@ -0,0 +1,349 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "KAC PDW" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "3Submachine Gun" + +SWEP.Description = "Experimental compact PDW firing a carbine-type cartridge. The perfect blend of rifle and submachine gun." +SWEP.Description_Quote = "The weight and size of an SMG with the power of a rifle." -- Based off the weapon's description in Contract Wars (2010) + +SWEP.Trivia_Caliber = "6mm Whisper" +SWEP.Trivia_Manufacturer = "Knight's Armament" +SWEP.Trivia_Year = "2006" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_pdw.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_pdw.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 20, + Damage_Min = 10, + + Range_Min = 1000, + Range_Max = 3000, + + RecoilKick = 5, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 16, + Damage_Min = 13, + Range_Min = 800, + Range_Max = 1800, + RPM = 650, + + RecoilSpreadPenalty = 0.0028, + HipFireSpreadPenalty = 0.04, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 12, + Damage_Min = 8, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.003, + RecoilDissipationRate = 24 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SMG + +// "ballistics" + +SWEP.Damage_Max = 25 +SWEP.Damage_Min = 12 +SWEP.Range_Min = 800 // distance for which to maintain maximum damage +SWEP.Range_Max = 2200 // distance at which we drop to minimum damage +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 21000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 800 + +SWEP.Spread = 0.006 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 11 +SWEP.RecoilResetTime = 0.02 +SWEP.RecoilDissipationRate = 33 +SWEP.RecoilFirstShotMult = 1.75 + +SWEP.RecoilVisualKick = 1.25 +SWEP.RecoilKick = 4 +SWEP.RecoilStability = 0.35 +SWEP.RecoilAltMultiplier = 150 + +SWEP.RecoilSpreadPenalty = 0.0025 +SWEP.HipFireSpreadPenalty = 0.025 + + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.925 +SWEP.ShootingSpeedMult = 0.8 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.275 +SWEP.SprintToFireTime = 0.3 + +SWEP.Sway = 0.9 +SWEP.ScopedSway = 0.2 + +SWEP.FreeAimMaxAngle = 4.5 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.95, -1.1, 1) +SWEP.SightPos = Vector(-4.78, -7.5, -3.45) + +SWEP.CorrectivePos = Vector(0.52, 0, -0.55) +SWEP.CorrectiveAng = Angle(2.5, 0.45, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pdw" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/pdw.mdl" +SWEP.DropMagazineImpact = "plastic" + +SWEP.ReloadUpInTime = 1.35 +SWEP.DropMagazineTime = 0.45 + +// sounds + +local path = "TacRP/weapons/pdw/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "fire2_L", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["optic"] = { + BGs_VM = { + {2, 1} + }, + }, + ["tactical"] = { + BGs_VM = { + {3, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "pdw_ROOT", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + VMScale = 1, + Pos_VM = Vector(-5.2, -0.12, 6), + Pos_WM = Vector(7, 1.5, -6.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "pdw_ROOT", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-3.25, -0.1, 21.6), + Pos_WM = Vector(24, 2.5, -5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "pdw_ROOT", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-3.25, 0.5, 11), + Pos_WM = Vector(14, 1.3, -5), + Ang_VM = Angle(90, 0, 90), + Ang_WM = Angle(0, -3.5, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + + +addsound("TacInt_pdw.clip_out", path .. "clip_out-1.wav") +addsound("TacInt_pdw.clip_in", path .. "clip_in-1.wav") +addsound("TacInt_pdw.bolt_back", path .. "bolt_back-1.wav") +addsound("TacInt_pdw.bolt_shut", path .. "bolt_shut-1.wav") +addsound("TacInt_pdw.fire_select", path .. "fire_select-1.wav") +addsound("TacInt_pdw.Buttstock_Flip_Open", path .. "buttstock_flip_open-1.wav") +addsound("TacInt_pdw.Buttstock_lockdown", path .. "buttstock_lockdown-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_rpg7.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_rpg7.lua new file mode 100644 index 0000000..6af549d --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_rpg7.lua @@ -0,0 +1,217 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "RPG-7" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.NoAimAssist = true + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "6Launcher" + +SWEP.Description = "Soviet rocket launcher with powerful explosion.\nSafety fuse prevents point blank detonations." +SWEP.Description_Quote = "If you hear someone screaming its name, duck for cover." // not a quote but is good life advice + +SWEP.Trivia_Caliber = "40mm Rockets" +SWEP.Trivia_Manufacturer = "NPO Bazalt" +SWEP.Trivia_Year = "1961" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_rpg7.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_rpg7.mdl" + +SWEP.NoRanger = true + +SWEP.Slot = 2 +SWEP.SlotAlt = 4 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + }, +} + +// "ballistics" + +SWEP.Damage_Max = 200 // just to fool the ratings +SWEP.Damage_Min = 200 +SWEP.Range_Max = 7000 +SWEP.Range_Min = 4000 + +SWEP.ShootEnt = "tacrp_proj_rpg7" +SWEP.ShootEntForce = 6000 + +SWEP.Num = 1 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Single-Shot" // only used externally for firemode name distinction + +SWEP.RPM = 60 + +SWEP.Spread = 0.01 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.2// time after you stop shooting for recoil to start dissipating +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 5 +SWEP.RecoilVisualShake = 0.2 + +SWEP.RecoilKick = 0 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.8 +SWEP.ShootingSpeedMult = 0.25 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.2 + +SWEP.AimDownSightsTime = 0.6 +SWEP.SprintToFireTime = 0.8 // multiplies how long it takes to recover from sprinting + +// hold types + +SWEP.HoldType = "rpg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "ar2" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(2, -2, -6) + +SWEP.BlindFireAng = Angle(0, 0, 0) +SWEP.BlindFirePos = Vector(1, -3, 0) + +SWEP.BlindFireLeftAng = Angle(75, 0, 0) +SWEP.BlindFireLeftPos = Vector(8, 10, -6) + +SWEP.BlindFireSuicideAng = Angle(0, 125, 0) +SWEP.BlindFireSuicidePos = Vector(-2, 25, -24) + +SWEP.SprintAng = Angle(40, -15, 0) +SWEP.SprintPos = Vector(4, 0, -4) + +SWEP.SightAng = Angle(0.03, -0.4, 0) +SWEP.SightPos = Vector(-3.57, -6.5, -5.1) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_SPECIAL +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// sway + +SWEP.Sway = 3 +SWEP.ScopedSway = 0.25 + +SWEP.FreeAimMaxAngle = 10 + +// melee + +SWEP.CanMeleeAttack = false + +// reload + +SWEP.ClipSize = 1 +SWEP.Ammo = "rpg_round" + +SWEP.BulletBodygroups = { + [1] = {1, 1} +} + +// sounds + +local path = "TacRP/weapons/rpg7/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_1" + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["blind_fire"] = "aimed_fire", + ["blind_idle"] = "aimed_idle", +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Ammo", + Category = {"ammo_rpg"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [2] = { + PrintName = "Accessory", + Category = {"acc", "acc_duffle", "acc_sling"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Perk", + Category = {"perk", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_rpg7.jingle", path .. "handling-1.wav") +addsound("TacInt_rpg7.insert_rocket", path .. "insert_rocket.wav") +addsound("TacInt_rpg7.flipup_sight", path .. "flipup_sight.wav") + +if engine.ActiveGamemode() == "terrortown" then + SWEP.AutoSpawnable = false + SWEP.Kind = WEAPON_EQUIP + SWEP.Slot = 6 + SWEP.CanBuy = { ROLE_TRAITOR } + SWEP.LimitedStock = true + SWEP.EquipMenuData = { + type = "Weapon", + desc = "Rocket launcher. Can't explode at point blank.\nComes with 2 rockets.\n\nBEWARE: May be visible while holstered!", + } + + function SWEP:TTTBought(buyer) + buyer:GiveAmmo(1, "RPG_Round") + end +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_aac_hb.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_aac_hb.lua new file mode 100644 index 0000000..11f1d63 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_aac_hb.lua @@ -0,0 +1,341 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "AAC Honey Badger" +SWEP.AbbrevName = "Honey Badger" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "A lightweight assault rifle with an integral suppressor. Powerful in close quarters and has no visible tracer, but has poor performance at range." +SWEP.Description_Quote = "\"We're Ghosts, Merrick. We finish the mission.\"" -- Call of Duty: Ghosts (2013) + +SWEP.Trivia_Caliber = ".300 Blackout" +SWEP.Trivia_Manufacturer = "AAC" +SWEP.Trivia_Year = "2011" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Model: Hyper \nAnimations: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint_shark/v_aac_hb2.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_aac_hb2.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 28, + Damage_Min = 8, + ClipSize = 24, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 14, + Damage_Min = 6, + + Range_Min = 800, + Range_Max = 2200, + + RPM = 650, + + RecoilSpreadPenalty = 0.002, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 15, + Damage_Min = 8, + Range_Min = 500, + Range_Max = 2500, + RPM = 700, + + RecoilSpreadPenalty = 0.0017, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + Spread = 0.0098, + RecoilMaximum = 20, + RecoilDissipationRate = 18 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 34 +SWEP.Damage_Min = 10 +SWEP.Range_Min = 300 +SWEP.Range_Max = 1800 +SWEP.Penetration = 6 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.7 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 10000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 800 + +SWEP.Spread = 0.0089 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 14 +SWEP.RecoilResetTime = 0.11 +SWEP.RecoilDissipationRate = 18 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 0.75 +SWEP.RecoilKick = 4 +SWEP.RecoilStability = 0.2 +SWEP.RecoilAltMultiplier = 150 + +SWEP.RecoilSpreadPenalty = 0.0027 +SWEP.HipFireSpreadPenalty = 0.02 +SWEP.PeekPenaltyFraction = 0.2 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.75 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.3 +SWEP.SprintToFireTime = 0.32 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.2 + +SWEP.FreeAimMaxAngle = 4 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -6) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.2, -0.16, 0) +SWEP.SightPos = Vector(-4.165, -7.5, -4.19) + +SWEP.CorrectivePos = Vector(-0.05, 0, 0.05) +SWEP.CorrectiveAng = Angle(0.03, 0.45, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/aac.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.3 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "tacint_shark/weapons/aac/" +local path1 = "TacRP/weapons/m4/m4_" + +SWEP.Sound_Shoot = "^" .. path .. "HB_Fire.wav" +SWEP.Sound_Shoot_Silenced = "^" .. path .. "HB_Fire.wav" + +SWEP.Silencer = true + +SWEP.Vol_Shoot = 75 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_suppressed" +SWEP.EjectEffect = 2 + +SWEP.TracerNum = 0 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["sights"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + InstalledElements = {"sights"}, + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 0.95, + Pos_VM = Vector(-5.55, -0.05, 6), + Pos_WM = Vector(0.5, 4, 1.15), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90 + 3.5, 0), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.m4_rootbone", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + Pos_VM = Vector(-2.8, -0.05, 14), + Pos_WM = Vector(1.1, 12, -1.5), + Ang_VM = Angle(90, 0, 180), + Ang_WM = Angle(180, -90 + 3.5, 0), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock2", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_rifle_sub"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_aac.Remove_Clip", path .. "magout.wav") +addsound("tacint_aac.Insert_Clip", path .. "magin.ogg") +addsound("tacint_aac.Insert_Clip-mid", path .. "magin.ogg") +addsound("tacint_aac.bolt_action", path .. "boltback.wav") +addsound("tacint_aac.bolt_slap", path .. "boltforward.ogg") +addsound("tacint_aac.throw_catch", path1 .. "throw_catch.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_bizon.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_bizon.lua new file mode 100644 index 0000000..e034537 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_bizon.lua @@ -0,0 +1,351 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "ПП-19 Бизон" +SWEP.AbbrevName = "Bizon" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "3Submachine Gun" + +SWEP.Description = "AK-derrivative SMG with a high-capacity helical magazine. Pretty weak but easy to control and handle." +SWEP.Description_Quote = "\"NEEEJJJJ!!!! FAN BATTLE-SCARRED PP-BIZONNNN!!!! FITTAAAA!!!!!\"" -- those who know + +SWEP.Trivia_Caliber = "9x18mm" +SWEP.Trivia_Manufacturer = "Izhmash" +SWEP.Trivia_Year = "1996" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Model: Twinke Masta \nTexture: Milo \nSound: Vunsunta \nAnimations: Tactical Intervention, edited by speedonerd" + +SWEP.ViewModel = "models/weapons/tacint_shark/v_bizon.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_bizon.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 16, + Damage_Min = 9, + Spread = 0.008, + FreeAimMaxAngle = 3, + + RecoilKick = 2, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 9, + Damage_Min = 5, + + Range_Min = 500, + Range_Max = 2000, + + RecoilSpreadPenalty = 0.0025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + Spread = 0.02 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 15 +SWEP.Damage_Min = 10 +SWEP.Range_Min = 600 +SWEP.Range_Max = 2500 +SWEP.Penetration = 7 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.725 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 12000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 700 + +SWEP.Spread = 0.012 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 20 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 0.5 +SWEP.RecoilKick = 1.5 +SWEP.RecoilStability = 0.3 +SWEP.RecoilAltMultiplier = 400 + +SWEP.RecoilSpreadPenalty = 0.002 +SWEP.HipFireSpreadPenalty = 0.05 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.95 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.65 + +SWEP.ReloadSpeedMult = 0.65 + +SWEP.AimDownSightsTime = 0.3 +SWEP.SprintToFireTime = 0.32 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.2 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-0.1, -0.5, -1) +SWEP.SightPos = Vector(-4.66, -7.6, -2.7) + +SWEP.CorrectivePos = Vector(0, 0, -0.07) +SWEP.CorrectiveAng = Angle(0.8, 0.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + + +SWEP.ClipSize = 64 +SWEP.Ammo = "pistol" +SWEP.Ammo_Expanded = "ti_pistol_light" + +SWEP.ReloadTimeMult = 1.2 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/bizon.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.65 +SWEP.DropMagazineTime = 0.65 + +SWEP.FreeAimMaxAngle = 5 + +// sounds + +local path = "tacint_shark/weapons/bizon/" +local path1 = "tacint_shark/weapons/ar57/" +local path2 = "tacrp/weapons/ak47/ak47_" + +SWEP.Sound_Shoot = "^" .. path .. "p90-1.wav" +SWEP.Sound_Shoot_Silenced = path2 .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_smg" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = "fire4_M", + ["fire5"] = "fire5_M", + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "mid_reload" +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.75, -0.12), + vm_ang = Angle(0, 0.3, 0), + t = 0.25, + tmax = 0.25, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.05, + t1 = 0.2, + }, + }, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + }, + }, + ["akmount"] = { + BGs_VM = { + {2, 0} + }, + BGs_WM = { + {2, 0} + }, + AttPosMods = { + [1] = { + VMScale = 0.7, + Pos_VM = Vector(-5, 0.5, 2), + Pos_WM = Vector(-0.4, 1, 0.5), + } + }, + SortOrder = 2, + } +} + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + InstalledElements = {"tactical"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.8, + Pos_VM = Vector(-5.55, 0.15, 4), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(-0.1, 2.25, 1), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = {"silencer", "muzz_ak"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + VMScale = 0.95, + WMScale = 0.85, + Pos_VM = Vector(-3.54, 0.1, 19.7), + Pos_WM = Vector(-0.1, 18.5, -1.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.AK47_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.5, -0.3, 13), + Pos_WM = Vector(0.5, 9, -1), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -90, -90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_sling", "acc_duffle", "acc_extmag_smg"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_bizon.remove_clip", path1 .. "magout1.wav") +addsound("tacint_bizon.insert_clip", path1 .. "magtap.wav") +addsound("tacint_bizon.boltaction", path2 .. "boltaction.wav") +addsound("tacint_bizon.Buttstock_Back", path2 .. "buttstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_dual_degala.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_dual_degala.lua new file mode 100644 index 0000000..0d716ba --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_dual_degala.lua @@ -0,0 +1,298 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Dual Eagles" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "3Akimbo" + +SWEP.Description = "Pair of gaudy gold-coated Desert Eagles, as if one wasn't enough. \nTwice the insane firepower, twice the insane recoil." +SWEP.Description_Quote = "\"My blood...on their hands.\"" + +SWEP.Trivia_Caliber = ".50 AE" +SWEP.Trivia_Manufacturer = "Magnum Research" +SWEP.Trivia_Year = "1983" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Model: Hav0c & Stoke +Texture: The_Tub & Stoke +Sound: Vunsunta +Animations: Tactical Intervention]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_dual_eagles.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_dual_eagles.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 75, + Damage_Min = 40, + + Spread = 0.03, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3.75, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 45, + Damage_Min = 20, + Range_Min = 100, + Range_Max = 1500, + RPM = 240, + + Spread = 0.03, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 65, + Damage_Min = 30, + + Spread = 0.015, + RecoilKick = 8, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilKick = 10, + Spread = 0.015 + } +} + +SWEP.TTTReplace = {["weapon_zm_pistol"] = 0.5, ["weapon_ttt_glock"] = 0.5} + +// "ballistics" + +SWEP.Damage_Max = 80 +SWEP.Damage_Min = 50 +SWEP.Range_Min = 150 +SWEP.Range_Max = 1500 +SWEP.Penetration = 10 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.85 +SWEP.ArmorBonus = 5 + +SWEP.MuzzleVelocity = 10000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemodes = {1, 2} +SWEP.Firemode = false + +SWEP.RPM = 240 +SWEP.RPMMultBurst = 1.1 +SWEP.RPMMultSemi = 1.1 + +SWEP.Spread = 0.025 + +SWEP.MoveSpreadPenalty = 0 +SWEP.MidAirSpreadPenalty = 0.075 +SWEP.HipFireSpreadPenalty = 0 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 10 +SWEP.RecoilResetTime = 0.175 +SWEP.RecoilDissipationRate = 12 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 2 +SWEP.RecoilKick = 15 +SWEP.RecoilStability = 0.1 + +SWEP.RecoilSpreadPenalty = 0.004 + +SWEP.Sway = 0.5 + +SWEP.CanBlindFire = false +SWEP.CanSuicide = true + +SWEP.ShootTimeMult = 0.6 + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 1 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.3 +SWEP.SprintToFireTime = 0.3 + +// hold types + +SWEP.HoldType = "duel" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeSuicide = "duel" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_DUEL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_DUEL + +SWEP.PassiveAng = Angle(5, 0, -2) +SWEP.PassivePos = Vector(3, 0, -6) + +SWEP.CustomizeAng = Angle(0, 35, 0) +SWEP.CustomizePos = Vector(1, 0, -12) + +SWEP.BlindFireAng = Angle(0, 0, 0) +SWEP.BlindFirePos = Vector(0, 0, 0) + +SWEP.BlindFireSuicideAng = Angle(0, 130, 0) +SWEP.BlindFireSuicidePos = Vector(1, 22, -15) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(1, 0, -12) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(60, 5, 0) + +// reload + +SWEP.ClipSize = 14 +SWEP.Ammo = "357" + +SWEP.ReloadTimeMult = 1 + +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/dual_deagles.mdl" +SWEP.DropMagazineImpact = "pistol" +SWEP.DropMagazineAmount = 2 + +SWEP.ReloadUpInTime = 2.8 +SWEP.DropMagazineTime = 0.25 + +// sounds + +local path = "tacint_shark/weapons/dualeagles/" +local path1 = "tacrp/weapons/mtx/" + +SWEP.Sound_Shoot = "^" .. path .. "elite-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects +SWEP.EjectEffect = 1 + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 4 + +SWEP.WM_QCA_MuzzleL = 1 +SWEP.WM_QCA_MuzzleR = 2 +SWEP.WM_QCA_EjectL = 3 +SWEP.WM_QCA_EjectR = 4 + +SWEP.MuzzleEffect = "muzzleflash_pistol_deagle" + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["blind_idle"] = "idle", + ["blind_fire"] = "idle", + ["melee"] = {"melee1", "melee2"}, + ["shoot_left"] = {"shoot_left-1", "shoot_left-2"}, + ["shoot_right"] = {"shoot_right-1", "shoot_right-2"} +} + +SWEP.LastShot = true +SWEP.Akimbo = true +SWEP.EffectsAlternate = true + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_ebullet"}, + Bone = "ValveBiped.mtx_root2", + WMBone = "Box01", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + VMScale = 0.9, + WMScale = 1, + Pos_VM = Vector(-2.25, -0.2, 7), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(-0.1, 9, -1.5), + Ang_WM = Angle(0, -90, 180), + }, + [2] = { + PrintName = "Accessory", + Category = {"acc_dual", "acc_extmag_dual2"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Trigger", + Category = {"trigger_akimbo"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +SWEP.AttachmentCapacity = 30 // amount of "Capacity" this gun can accept + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_dualeagles.clip_in_left", path .. "elite_leftclipin.wav") +addsound("tacint_dualeagles.clip_in_right", path .. "elite_rightclipin.wav") +addsound("tacint_dualeagles.clip_out", path .. "elite_clipout.wav") +addsound("tacint_dualeagles.slide_back", path1 .. "slide_back.wav") +addsound("tacint_dualeagles.slide_release", path1 .. "slide_release.wav") +addsound("tacint_dualeagles.slide_shut", path .. "sliderelease.wav") + +SWEP.Scope = false + +SWEP.FreeAim = false diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_g3.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_g3.lua new file mode 100644 index 0000000..0e32a84 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_g3.lua @@ -0,0 +1,350 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "HK G3A3" +SWEP.AbbrevName = "G3A3" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "5Battle Rifle" + +SWEP.Description = "Precise heavy battle rifle with a somewhat managable automatic firemode but slow handling." +SWEP.Description_Quote = "\"Yeah, well, we can't all be Schwarzenegger.\"" -- Spriggan (1998) + +SWEP.Trivia_Caliber = "7.62x51mm" +SWEP.Trivia_Manufacturer = "Heckler & Koch" +SWEP.Trivia_Year = "1958" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Firearms: Source \nSound: Nightmare Mutant & FA:S2 \nAnimations: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint_shark/v_g3.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_g3.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 48, + Damage_Min = 30, + + Range_Min = 1500, + Range_Max = 5000, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 18, + Damage_Min = 12, + + Range_Min = 900, + Range_Max = 3000, + + RecoilKick = 5, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 14, + Damage_Min = 10, + RPM = 420, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 14, + RecoilDissipationRate = 10 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SMG + +// "ballistics" + +SWEP.Damage_Max = 35 +SWEP.Damage_Min = 28 +SWEP.Range_Min = 1500 +SWEP.Range_Max = 4000 +SWEP.Penetration = 15 +SWEP.ArmorPenetration = 0.775 +SWEP.ArmorBonus = 2 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 4, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 25000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 500 + +SWEP.Spread = 0.001 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 4 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 20 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 1.25 +SWEP.RecoilKick = 5.5 +SWEP.RecoilStability = 0.75 +SWEP.RecoilAltMultiplier = 300 + +SWEP.RecoilSpreadPenalty = 0.006 +SWEP.HipFireSpreadPenalty = 0.045 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.65 +SWEP.SightedSpeedMult = 0.5 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.45 +SWEP.SprintToFireTime = 0.5 + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.25 + +SWEP.FreeAimMaxAngle = 6.5 + +SWEP.Bipod = false +SWEP.BipodRecoil = 0.5 +SWEP.BipodKick = 0.3 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(8, -1, -2) + +SWEP.SightAng = Angle(0.02, 0.6, 0) +SWEP.SightPos = Vector(-4.56, -7.5, -3.3) + +SWEP.CorrectivePos = Vector(0.025, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1.35 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/g3.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.55 +SWEP.DropMagazineTime = 1 + +// sounds + +local path = "TacRP/weapons/mp5/mp5_" +local path1 = "tacint_shark/weapons/g3/g3sg1_" + +SWEP.Sound_Shoot = "^" .. path1 .. "1.wav" +SWEP.Sound_Shoot_Silenced = "^tacrp/weapons/dsa58/dsa58_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 +SWEP.EjectEffect = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = "fire4_M", + ["fire5"] = "fire5_M", + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +SWEP.HolsterTimeMult = 3 + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {1, 1} + }, + }, + ["tactical"] = { + BGs_VM = { + {2, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "ValveBiped.mp5_rootbone", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-6.2, -0.3, 4), + Pos_WM = Vector(6, 1.35, -5.9), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 1, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.mp5_rootbone", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 0.75, + WMScale = 0.75, + Pos_VM = Vector(-4.1, -0.25, 29.75), + Pos_WM = Vector(32, 1.25, -4.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.mp5_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-4.7, -1.15, 17), + Pos_WM = Vector(20, 1.9, -4.5), + Ang_VM = Angle(90, 0, -75), + Ang_WM = Angle(0, 0, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_bipod", "acc_sling", "acc_duffle", "acc_extmag_rifle2"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_g3.remove_clip", path1 .. "clipout.wav") +addsound("tacint_g3.insert_clip", path1 .. "clipin.wav") +addsound("tacint_g3.insert_clip-mid", path1 .. "clipin.wav") +addsound("tacint_g3.HK_Slap", path1 .. "boltrelease.wav") +addsound("tacint_g3.bolt_back", path1 .. "boltpull.wav") +addsound("tacint_g3.fire_select", { + path .. "fire_select-1.wav", + path .. "fire_select-2.wav", + path .. "fire_select-3.wav", +}) diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_groza.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_groza.lua new file mode 100644 index 0000000..98f5ce3 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_groza.lua @@ -0,0 +1,345 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "OЦ-14 \"Гроза\"" +SWEP.AbbrevName = "Groza-4" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "3Security" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Integrally-suppressed bullpup made from a reconfigured AK. Weak, but has great handling and stability and has no visible tracers." +SWEP.Description_Quote = "\"Get out of here, stalker.\"" + +SWEP.Trivia_Caliber = "9x39mm" +SWEP.Trivia_Manufacturer = "TsKIB SOO" +SWEP.Trivia_Year = "1992" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Model: Teh Snake, edited by speedonerd +Texture: Teh Snake +Sound: Teh Snake & speedonerd +Animations: speedonerd +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_groza.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_groza.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 16, + Damage_Min = 11, + + Range_Min = 400, + Range_Max = 2000, + + RPM = 600, + + RecoilSpreadPenalty = 0.0025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2.5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.01 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 19 +SWEP.Damage_Min = 14 +SWEP.Range_Min = 700 // distance for which to maintain maximum damage +SWEP.Range_Max = 1500 // distance at which we drop to minimum damage +SWEP.Penetration = 5 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.8 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.85, + [HITGROUP_RIGHTLEG] = 0.85, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 12000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 750 + +SWEP.Spread = 0.0065 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 7 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 24 +SWEP.RecoilFirstShotMult = 1.5 + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 2.5 +SWEP.RecoilStability = 0.25 + +SWEP.RecoilSpreadPenalty = 0.0018 +SWEP.HipFireSpreadPenalty = 0.025 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ShootingSpeedMult = 0.85 +SWEP.SightedSpeedMult = 0.65 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.32 +SWEP.SprintToFireTime = 0.35 + +SWEP.Sway = 1.4 +SWEP.ScopedSway = 0.2 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, 1, 0) + +SWEP.CustomizeAng = Angle(30, 15, 0) +SWEP.CustomizePos = Vector(4.5, -0.4, -0.7) + +SWEP.BlindFireAng = Angle(0, 0, 0) +SWEP.BlindFirePos = Vector(-1, 1, 1) + +SWEP.BlindFireLeftAng = Angle(90, 0, 0) +SWEP.BlindFireLeftPos = Vector(10, 6, -4) + +SWEP.BlindFireRightAng = Angle(-90, 0, 0) +SWEP.BlindFireRightPos = Vector(-4, 20, -4) + +SWEP.SprintAng = Angle(30, -15, -5) +SWEP.SprintPos = Vector(2, 0, 1.5) + +SWEP.SightAng = Angle(0, 0.1, 0) +SWEP.SightPos = Vector(-4, -7, 0.9) + +SWEP.CorrectivePos = Vector(0, 0, 0.12) +SWEP.CorrectiveAng = Angle(0, 0, -0) + +--SWEP.CorrectiveBoneAng = Angle(90, 38, 38) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, -2, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pdw" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/groza.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.2 +SWEP.DropMagazineTime = 0.65 + +SWEP.FreeAimMaxAngle = 4 + +// sounds + +local path = "tacint_shark/weapons/groza/" +local path1 = "tacrp/weapons/ak47/ak47_" + +SWEP.Sound_Shoot = "^" .. path .. "groza_fire-1.wav" +SWEP.Sound_Shoot_Silenced = "^" .. path .. "groza_fire-1.wav" + +SWEP.Vol_Shoot = 75 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_suppressed" +SWEP.EjectEffect = 2 +SWEP.Silencer = true + +SWEP.TracerNum = 0 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "deploy", + ["fire_iron"] = "idle", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = "fire4_M", + ["fire5"] = "fire5_M", + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "midreload" +} + + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.2, -0.12), + vm_ang = Angle(0, 0.25, 0), + t = 0.1, + tmax = 0.1, + bones = { + { + bone = "ValveBiped.bolt", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.08, + }, + }, +} + +SWEP.DeployTimeMult = 1.05 + +// attachments + +SWEP.AttachmentElements = { + ["rail"] = { + BGs_VM = { + {1, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + Bone = "famas_root", + WMBone = "ValveBiped.Bip01_R_Hand", + InstalledElements = {"rail"}, + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + WMScale = 0.75, + Pos_VM = Vector(0, -6.6, 0.5), + Ang_VM = Angle(90, 0, -90), + Pos_WM = Vector(4.5, 1, -7.2), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "famas_root", + WMBone = "ValveBiped.Bip01_R_Hand", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-0.55, -3.3, 1), + Pos_WM = Vector(4, 1.5, -3.75), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 90), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_sling", "acc_duffle", "perk_extendedmag"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_rifle_sub"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_groza.remove_clip", path .. "famas_clipout.wav") +addsound("tacint_groza.insert_clip", path .. "famas_clipin.wav") +addsound("tacint_groza.boltaction", path .. "famas_forearm.wav") +addsound("tacint_groza.Buttstock_Back", path .. "buttstock_back.wav") +addsound("tacint_groza.bolt_lockback", path .. "famas_boltback.wav") +addsound("tacint_groza.bolt_release", path .. "famas_boltforward.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_gyrojet.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_gyrojet.lua new file mode 100644 index 0000000..939edd0 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_gyrojet.lua @@ -0,0 +1,320 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "MBA Gyrojet" +SWEP.AbbrevName = "Gyrojet" +SWEP.Category = "[FT] Специальное Оружие" + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "7Special Weapon" + +SWEP.Description = "Experimental weapon firing self-propelled mini-rockets. While they are powerful, the rounds are prone to failure." +SWEP.Description_Quote = "\"I wonder how much his remains would go for on Ebay.\"" // Postal 2 - Completely unrelated but its funi + +SWEP.Trivia_Caliber = ".51 Caliber" +SWEP.Trivia_Manufacturer = "MBAssociates" // i checked every credible source and they dont put a space between MB and associates please dont change this +SWEP.Trivia_Year = "1962" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = [[ +Model & Textures: RedRougeXIII +Sounds: speedonerd, Tactical Intervention +Animations: speedonerd +]] + +SWEP.ViewModel = "models/weapons/tacint_shark/v_gyrojet.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_gyrojet.mdl" + +SWEP.NoRanger = false + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 50, + Damage_Min = 50, + }, + [TacRP.BALANCE_PVE] = { + + Damage_Max = 30, + Damage_Min = 30, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + Description = "Experimental pistol firing self-propelled minirockets. Powerful but inaccurate, projectiles have significant travel time.", + ClipSize = 5, + JamFactor = 0, + Spread = 0.022 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.Pistol + +// "ballistics" + +SWEP.Damage_Max = 75 +SWEP.Damage_Min = 75 + +SWEP.Range_Min = 9000 +SWEP.Range_Max = 9000 + +SWEP.Penetration = 20 +SWEP.ArmorPenetration = 1 + +SWEP.ShootEnt = "tacrp_proj_gyrojet" +SWEP.ShootEntForce = 15000 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 1, + [HITGROUP_RIGHTLEG] = 1, + [HITGROUP_GEAR] = 1 +} + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.RPM = 180 + +SWEP.Spread = 0.0025 + +SWEP.ShootTimeMult = 0.7 + +SWEP.JamFactor = 0.25 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 4 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 4 +SWEP.RecoilFirstShotMult = 1 + +SWEP.RecoilVisualKick = 1 + +SWEP.RecoilKick = 2 + +SWEP.RecoilSpreadPenalty = 0.01 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.9 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.75 + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.25 + +SWEP.Sway = 1.05 +SWEP.ScopedSway = 0.5 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_REVOLVER + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, 0, 0) + +SWEP.CustomizeAng = Angle(35, 15, 0) +SWEP.CustomizePos = Vector(9, 0, -3) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(-1, -2, -2) + +SWEP.BlindFireLeftAng = Angle(80, -2, 0) +SWEP.BlindFireLeftPos = Vector(15, 5, -9) + +SWEP.BlindFireRightAng = Angle(-80, -2, 0) +SWEP.BlindFireRightPos = Vector(-4, 20, -11) + +SWEP.BlindFireSuicideAng = Angle(-135, 0, 45) +SWEP.BlindFireSuicidePos = Vector(25, 19, -5) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(1, -5, -8) + +SWEP.SightAng = Angle(0.22, -0.7, 0) +SWEP.SightPos = Vector(-3.72, -4, 1.45) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +SWEP.CorrectivePos = Vector(0, 0, 0) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +// reload + +SWEP.ClipSize = 6 +SWEP.Ammo = "357" + +SWEP.ShotgunReload = true + +SWEP.ReloadTimeMult = 1.1 + +// sounds + +local path = "tacint_shark/weapons/gyrojet/" +local path1 = "tacrp/weapons/xd45/" + +SWEP.Sound_Shoot = "^" .. path .. "fire.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +SWEP.EffectsAlternate = true +SWEP.EffectsDoubled = true + +SWEP.QCA_MuzzleL = 1 +SWEP.QCA_MuzzleR = 4 +SWEP.QCA_EjectL = 2 +SWEP.QCA_EjectR = 3 + +SWEP.EjectEffect = 0 + +SWEP.MuzzleEffect = "muzzleflash_suppressed" + +// anims +// VM: +// idle +// fire +// fire1, fire2 +// dryfire +// melee +// reload +// midreload +// prime_grenade +// throw_grenade +// throw_grenade_underhand +// deploy +// blind_idle +// blind_fire +// blind_fire1, blind_fire2... +// blind_dryfire + +// WM: +// attack1 +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2", "shoot3"}, + ["blind_fire"] = {"blind_shoot1", "blind_shoot2", "blind_shoot3"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.5, -0.6), + vm_ang = Angle(0, 2, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "gyrojet_hammer", + ang = Angle(0, 0, -45), + t0 = 0, + t1 = 0.15, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.15, + }, + }, +} + +SWEP.LastShot = false + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Accessory", + Category = {"acc", "acc_holster", "acc_brace", "bolt_jammable"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [2] = { + PrintName = "Trigger", + Category = {"trigger_semi"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [3] = { + PrintName = "Ammo", + Category = {"ammo_gyrojet"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_gyrojet.slide_back", { + path1 .. "slide_back-1.wav", + path1 .. "slide_back-2.wav", +}) +addsound("tacint_gyrojet.Catch", + { + path .. "gyrojet_reloadcatch.wav", + path .. "gyrojet_reloadcatch2.wav", + } +) +addsound("tacint_gyrojet.hammer", path .. "gyrojet_cockhammer.wav") +addsound("tacint_gyrojet.insert", + { + path .. "gyrojet_insert1.wav", + path .. "gyrojet_insert2.wav", + path .. "gyrojet_insert3.wav", + } +) + +if engine.ActiveGamemode() == "terrortown" then + SWEP.HolsterVisible = false + SWEP.AutoSpawnable = false + SWEP.Kind = WEAPON_PISTOL + SWEP.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } + SWEP.EquipMenuData = { + type = "Weapon", + desc = "High damage pistol firing mini-rockets.\nUses standard magnum ammo.", + } +end diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_pkm.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_pkm.lua new file mode 100644 index 0000000..e53a864 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sd_pkm.lua @@ -0,0 +1,368 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "ПКМ" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "5Machine Gun" + +SWEP.Description = "General-purpose machine gun capable of intense suppressive fire. High capacity and damage but is very, very bulky." +SWEP.Description_Quote = "Notably seen atop Mercedes-Benz G-Wagens." -- The Expendables 2 (2012) opening scene + +SWEP.Trivia_Caliber = "7.62x54mmR" +SWEP.Trivia_Manufacturer = "Degtyaryov Plant" +SWEP.Trivia_Year = "1961" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = [[ +Assets: Call to Arms +Sounds: NightmareMutant & speedonerd +Animations: Tactical Intervention +]] +SWEP.ViewModel = "models/weapons/tacint_shark/v_pkm.mdl" +SWEP.WorldModel = "models/weapons/tacint_shark/w_pkm.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 35, + Damage_Min = 24, + Range_Min = 700, + Range_Max = 4000, + + ClipSize = 100, + }, + [TacRP.BALANCE_TTT] = { + + Description = "Machine gun with high damage but very low mobility.", + + Damage_Max = 20, + Damage_Min = 12, + Range_Min = 750, + Range_Max = 3000, + ClipSize = 80, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 20, + Damage_Min = 12, + + ClipSize = 100, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + ClipSize = 100, + Damage_Max = 29, + RecoilMaximum = 26, + MoveSpeedMult = 0.7, + ShootingSpeedMult = 0.4 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachineGun + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.75 +} + +// "ballistics" + +SWEP.Damage_Max = 38 +SWEP.Damage_Min = 28 +SWEP.Range_Min = 1600 // distance for which to maintain maximum damage +SWEP.Range_Max = 5000 // distance at which we drop to minimum damage +SWEP.Penetration = 10 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.8 + +SWEP.MuzzleVelocity = 17500 + +// misc. shooting + +SWEP.Firemode = 2 + +SWEP.RPM = 650 + +SWEP.Spread = 0.0065 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 25 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 30 +SWEP.RecoilFirstShotMult = 3 + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 9 +SWEP.RecoilStability = 0.25 +SWEP.RecoilAltMultiplier = 300 + +SWEP.HipFireSpreadPenalty = 0.07 +SWEP.RecoilSpreadPenalty = 0.0012 +SWEP.PeekPenaltyFraction = 0.15 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.75 +SWEP.ShootingSpeedMult = 0.5 +SWEP.SightedSpeedMult = 0.55 + +SWEP.ReloadSpeedMult = 0.15 +SWEP.ReloadSpeedMultTime = 1 + +SWEP.AimDownSightsTime = 0.36 +SWEP.SprintToFireTime = 0.42 + +SWEP.Sway = 1.5 +SWEP.ScopedSway = 0.01 + +SWEP.FreeAimMaxAngle = 9 + +SWEP.Bipod = true +SWEP.BipodRecoil = 0.35 +SWEP.BipodKick = 0.25 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.03, 1, 0) +SWEP.SightPos = Vector(-4.425, -7.5, -4.15) + +SWEP.CorrectivePos = Vector(0.025, 0, 0.125) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 100 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "ar2" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_rifle" + +SWEP.ReloadTimeMult = 1.25 +SWEP.DropMagazineModel = "models/weapons/tacint_shark/magazines/pkm.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 3.5 +SWEP.DropMagazineTime = 0.7 + +SWEP.BulletBodygroups = { + [1] = {5, 0}, + [2] = {5, 1}, + [3] = {5, 2}, + [4] = {5, 3}, + [5] = {5, 4}, + [6] = {5, 5}, + [7] = {5, 6}, + [8] = {5, 7}, +} +SWEP.DefaultBodygroups = "000006" + +// sounds + +local path = "tacint_shark/weapons/pkm/pkm" + +SWEP.Sound_Shoot = "^" .. path .. "-1.wav" +SWEP.Sound_Shoot_Silenced = "^" .. path .. "-1_silenced.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_minimi" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = {"fire4_M", "fire3_M", "fire2_M", "fire1_M"}, + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = "fire4_M", + ["fire5"] = "fire5_M", + ["melee"] = "melee1" +} + +// attachments + +SWEP.AttachmentElements = { + ["bipod"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["extendedbelt"] = { + BGs_VM = { + {2, 1} + }, + }, + ["sights"] = { + BGs_VM = { + {3, 1} + }, + BGs_WM = { + {3, 1} + }, + } +} + +//ValveBiped.MG4_root + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.feed_cover", + InstalledElements = {"sights"}, + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + VMScale = 1.25, + Pos_VM = Vector(-2, 0, 0), + Pos_WM = Vector(12, 1.15, -8), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.MG4_root", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-5.1, 0, 43.5), + Pos_WM = Vector(43.2, 1.15, -5.3), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.MG4_root", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-4.6, -1.1, 13), + Pos_WM = Vector(14, 1.75, -4.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, 0, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_bipod", "extendedbelt", "acc_sling", "acc_duffle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + } +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_pkm.Clip_Out", path .. "_boxout.wav") +addsound("tacint_pkm.Clip_In", path .. "_boxin.wav") +addsound("tacint_pkm.bolt_release", path .. "_bolt.wav") +addsound("tacint_pkm.bolt_back", path .. "_bolt.wav") +addsound("tacint_pkm.bolt_forward", path .. "_coversmack.wav") +addsound("tacint_pkm.feedcover_close", path .. "_coverdown.wav") +addsound("tacint_pkm.feedcover_open", path .. "_coverup.wav") +addsound("tacint_pkm.insertbullets", path .. "_chain.wav") +addsound("tacint_pkm.deploy", path .. "_draw.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_sg551.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_sg551.lua new file mode 100644 index 0000000..cdfb1e7 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_sg551.lua @@ -0,0 +1,365 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "SIG SG 551" +SWEP.AbbrevName = "SG 551" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "4Assault Rifle" + +SWEP.Description = "Assault rifle with all around excellent performance, offset by a lower magazine capacity." +SWEP.Description_Quote = "\"No questions, no answers. That's the business we're in.\"" + +SWEP.Trivia_Caliber = "5.56x45mm" +SWEP.Trivia_Manufacturer = "SIG Sauer AG" +SWEP.Trivia_Year = "1986" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_sg551.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_sg551.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 24, + Damage_Min = 16, + + RPM = 800, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 6, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.9, + [HITGROUP_RIGHTLEG] = 0.9, + [HITGROUP_GEAR] = 0.9 + }, + + ClipSize = 20, + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 16, + Damage_Min = 12, + Range_Min = 800, + Range_Max = 2000, + RPM = 700, + + ClipSize = 20, + + RecoilSpreadPenalty = 0.0025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 12, + Damage_Min = 9, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 20, + RecoilDissipationRate = 18 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.AssaultRifle + +// "ballistics" + +SWEP.Damage_Max = 28 +SWEP.Damage_Min = 18 +SWEP.Range_Min = 1100 // distance for which to maintain maximum damage +SWEP.Range_Max = 4000 // distance at which we drop to minimum damage +SWEP.Penetration = 8 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.8 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.25, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 22000 + +// misc. shooting + +SWEP.Firemodes = {2, -3, 1} + +SWEP.RPM = 850 + +SWEP.PostBurstDelay = 0.05 + +SWEP.Spread = 0.001 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 8 +SWEP.RecoilResetTime = 0.02 +SWEP.RecoilDissipationRate = 32 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 3 +SWEP.RecoilStability = 0.55 + +SWEP.RecoilSpreadPenalty = 0.0025 +SWEP.HipFireSpreadPenalty = 0.035 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.9 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.65 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.35 +SWEP.SprintToFireTime = 0.38 + +SWEP.FreeAimMaxAngle = 5 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -6) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.05, 0.9, 0) +SWEP.SightPos = Vector(-4.1, -7.5, -4.3) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +SWEP.Sway = 1.25 +SWEP.ScopedSway = 0.15 + +// reload +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 25 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "smg1" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/sg551.mdl" +SWEP.DropMagazineImpact = "metal" + +SWEP.ReloadUpInTime = 1.7 +SWEP.DropMagazineTime = 0.6 + +// sounds + +local path = "tacrp/weapons/sg551/sg551_" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 2 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "dryfire", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.3, -0.2), + vm_ang = Angle(0, 0.5, 0), + t = 0.2, + tmax = 0.2, + bones = { + { + bone = "ValveBiped.bolt_cover", + pos = Vector(0, 0, -3), + t0 = 0.01, + t1 = 0.1, + }, + }, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + }, + ["sights"] = { + BGs_VM = { + {2, 1}, + {4, 1}, + {5, 1} + }, + }, + ["tactical"] = { + BGs_VM = { + {3, 1} + }, + }, +} + +SWEP.NoRMR = true + + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium", "optic_sniper"}, + InstalledElements = {"sights"}, + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/optic_on.wav", + DetachSound = "tacrp/weapons/optic_off.wav", + VMScale = 0.75, + Pos_VM = Vector(-5, 0, 5.5), + Pos_WM = Vector(-0.1, 5, 1.15), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/silencer_on.wav", + DetachSound = "tacrp/weapons/silencer_off.wav", + Pos_VM = Vector(-3.5, 0, 23.5), + Pos_WM = Vector(-0.1, 25, -0.9), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -90, 0), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + InstalledElements = {"tactical"}, + Bone = "ValveBiped.sg551_rootbone", + WMBone = "Box01", + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + Pos_VM = Vector(-3.5, -0.75, 15), + Pos_WM = Vector(2, 13, -0.5), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(-90, -90 + 3.5, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "perk_extendedmag", "acc_sling", "acc_duffle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_4pos"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_rifle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_sg551.Remove_Clip", path .. "remove_clip.wav") +addsound("tacint_sg551.Insert_Clip", path .. "insert_clip.wav") +addsound("tacint_sg551.Insert_Clip-mid", path .. "insert_clip-mid.wav") +addsound("tacint_sg551.bolt_action", path .. "bolt_action.wav") +addsound("tacint_sg551.bolt_slap", path .. "bolt_slap.wav") +addsound("tacint_sg551.bolt_back", path .. "bolt_back.wav") +addsound("tacint_sg551.throw_catch", path .. "throw_catch.wav") +addsound("tacint_sg551.fire_selector", path .. "fire_selector.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_skorpion.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_skorpion.lua new file mode 100644 index 0000000..5ca654a --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_skorpion.lua @@ -0,0 +1,378 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Skorpion vz. 61" +SWEP.AbbrevName = "Skorpion" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "4Consumer" +SWEP.SubCatType = "3Machine Pistol" + +SWEP.Description = "Light machine pistol with good range, recoil and spread." +SWEP.Description_Quote = "\"Please remove any metallic items you're carrying, keys, loose change...\"" -- The Matrix (1999), lobby shootout + +SWEP.Trivia_Caliber = ".32 ACP" +SWEP.Trivia_Manufacturer = "CZ Uherský Brod" +SWEP.Trivia_Year = "1963" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_skorpion.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_skorpion.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 18, + Damage_Min = 10, + ArmorPenetration = 0.6, + + RecoilKick = 1, + + ClipSize = 30, + DefaultBodygroups = "0000", + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 9, + Damage_Min = 5, + Range_Min = 500, + Range_Max = 2500, + RPM = 850, + + ClipSize = 30, + DefaultBodygroups = "0000", + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.9, + [HITGROUP_RIGHTARM] = 0.9, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 6, + Damage_Min = 3, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilMaximum = 10 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachinePistol + +// "ballistics" + +SWEP.DefaultBodygroups = "0000" + +SWEP.Damage_Max = 18 +SWEP.Damage_Min = 9 +SWEP.Range_Min = 400 +SWEP.Range_Max = 2000 +SWEP.Penetration = 4 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.5 +SWEP.ArmorBonus = 0.75 + +SWEP.MuzzleVelocity = 12500 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 850 + +SWEP.Spread = 0.009 +SWEP.RecoilSpreadPenalty = 0.0014 +SWEP.HipFireSpreadPenalty = 0.011 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 9 +SWEP.RecoilResetTime = 0.01 +SWEP.RecoilDissipationRate = 40 +SWEP.RecoilFirstShotMult = 1.25 + +SWEP.RecoilVisualKick = 0.5 +SWEP.RecoilKick = 1.5 +SWEP.RecoilStability = 0.75 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.6 + +SWEP.AimDownSightsTime = 0.275 +SWEP.SprintToFireTime = 0.30 + +SWEP.Sway = 1 +SWEP.ScopedSway = 0.3 + +SWEP.FreeAimMaxAngle = 3 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(-2.5, -1, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-135, 0, 45) +SWEP.BlindFireSuicidePos = Vector(31, 25, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(-4.26, -1.025, 1) +SWEP.SightPos = Vector(-5.2, -5, -2.4) + +SWEP.CorrectivePos = Vector(1.2, 0, -0.4) +SWEP.CorrectiveAng = Angle(5.5, -2.7, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 20 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.Ammo_Expanded = "ti_pistol_light" + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/skorpion.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 0.9 +SWEP.DropMagazineTime = 0.2 + +// sounds + +local path = "TacRP/weapons/skorpion/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 100 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_pistol" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "fire2_M", + ["fire1"] = {"fire1_L", "fire1_M"}, + ["fire2"] = {"fire2_M", "fire2_R"}, + ["fire3"] = {"fire3_L", "fire3_M"}, + ["fire4"] = {"fire4_M", "fire4_R"}, + ["fire5"] = {"fire5_L", "fire5_M", "fire5_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -0.3, -0.15), + vm_ang = Angle(0, 0.5, 0), + t = 0.1, + tmax = 0.2, + bones = { + { + bone = "bolt_handle", + pos = Vector(0, 0, -3), + t0 = 0, + t1 = 0.1, + }, + { + bone = "ValveBiped.Bip01_R_Finger1", + ang = Angle(0, -15, 0), + t0 = 0, + t1 = 0.2, + }, + { + bone = "ValveBiped.Bip01_R_Finger11", + ang = Angle(-35, 0, 0), + t0 = 0, + t1 = 0.2, + }, + }, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {3, 1} + }, + }, + ["smallmag"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {2, 1} + } + }, +} + +SWEP.Attachments = { + --[1] = { + --PrintName = "Optic", + --Category = "optic_pistol", + --Bone = "Skorpion_ROOT", + --WMBone = "Bone02", + --AttachSound = "tacrp/weapons/optic_on.wav", + --DetachSound = "tacrp/weapons/optic_off.wav", + --VMScale = 1.2, + --WMScale = 1.2, + --Pos_VM = Vector(4.6, 0, 0.5), + --Ang_VM = Angle(90, 0, 180), + --Pos_WM = Vector(-12, 1.25, -5), + --Ang_WM = Angle(0, 0, 180), + --}, + [1] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "Skorpion_ROOT", + WMBone = "Bone02", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 0.6, + WMScale = 0.7, + Pos_VM = Vector(3.75, -0.015, 14), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(1.5, 1.25, -4.3), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "Skorpion_ROOT", + WMBone = "Bone02", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + VMScale = 0.75, + WMScale = 0.75, + Pos_VM = Vector(3, 0, 8), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(-4, 1.25, -3.5), + Ang_WM = Angle(0, 0, 0), + }, + [3] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_extmag_smg", "acc_holster"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [4] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_Skorpion.Clip_Out", path .. "clip_out-1.wav") +addsound("TacInt_Skorpion.Clip_In", path .. "clip_in-1.wav") +addsound("TacInt_Skorpion.bolt_action", path .. "bolt_action-1.wav") + +addsound("TacInt_pdw.fire_select", "TacRP/weapons/pdw/fire_select-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_spr.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_spr.lua new file mode 100644 index 0000000..5a63aca --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_spr.lua @@ -0,0 +1,364 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Remington M700 SPS" +SWEP.AbbrevName = "R700 SPS" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "4Consumer" +SWEP.SubCatType = "7Sniper Rifle" + +SWEP.Description = "Medium range hunting rifle with a fast cycle speed.\nEquipped with a 6x scope by default." +SWEP.Description_Quote = "SPS stands for \"Special Purpose Synthetic.\"" + +SWEP.Trivia_Caliber = ".308 Winchester" +SWEP.Trivia_Manufacturer = "Remington Arms" +SWEP.Trivia_Year = "1962" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_spr.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_spr.mdl" + +SWEP.Slot = 2 +SWEP.SlotAlt = 3 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 50, + Damage_Min = 70, + + Range_Min = 500, + Range_Max = 1500, + }, + [TacRP.BALANCE_TTT] = { + + Description = "Well rounded hunting rifle with good damage up close.\nEquipped with a 6x scope by default.", + + Damage_Max = 30, + Damage_Min = 50, + Range_Min = 300, + Range_Max = 1500, + + RPM = 45, + ShootTimeMult = 0.9, + HipFireSpreadPenalty = 0.025, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 5, + [HITGROUP_CHEST] = 1.25, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 22, + Damage_Min = 50, + + Range_Min = 500, + Range_Max = 1750, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 + }, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + HipFireSpreadPenalty = 0.018 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SniperRifle + +// "ballistics" + +SWEP.Damage_Max = 75 +SWEP.Damage_Min = 60 +SWEP.Range_Min = 2000 +SWEP.Range_Max = 6000 +SWEP.Penetration = 17 // units of metal this weapon can penetrate +SWEP.ArmorPenetration = 0.825 +SWEP.ArmorBonus = 2.5 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 3, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1.1, + [HITGROUP_LEFTARM] = 0.75, + [HITGROUP_RIGHTARM] = 0.75, + [HITGROUP_LEFTLEG] = 0.5, + [HITGROUP_RIGHTLEG] = 0.5, + [HITGROUP_GEAR] = 0.5 +} + +SWEP.MuzzleVelocity = 35000 + +// misc. shooting + +SWEP.Firemode = 1 + +SWEP.FiremodeName = "Bolt-Action" // only used externally for firemode name distinction + +SWEP.RPM = 54 + +SWEP.Spread = 0 + +SWEP.HipFireSpreadPenalty = 0.04 +SWEP.PeekPenaltyFraction = 0.25 + +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 1 +SWEP.RecoilResetTime = 0.2 +SWEP.RecoilDissipationRate = 1 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 5 + +SWEP.RecoilKick = 3 + +SWEP.RecoilSpreadPenalty = 0 // extra spread per one unit of recoil + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.85 +SWEP.ReloadSpeedMult = 0.1 +SWEP.ReloadSpeedMultTime = 1 + +SWEP.ReloadSpeedMult = 0.3 + +SWEP.AimDownSightsTime = 0.75 +SWEP.SprintToFireTime = 0.90 + +SWEP.Sway = 3 +SWEP.ScopedSway = 0.75 + +SWEP.FreeAimMaxAngle = 9 + +// hold types + +SWEP.HoldType = "ar2" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = false +SWEP.HoldTypeNPC = "shotgun" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_AR2 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(2, -2, -6) + +SWEP.BlindFireAng = Angle(0, 15, -45) +SWEP.BlindFirePos = Vector(1, -2, -3) + +SWEP.BlindFireLeftAng = Angle(75, 0, 0) +SWEP.BlindFireLeftPos = Vector(8, 10, -6) + +SWEP.BlindFireRightAng = Angle(-75, 0, 0) +SWEP.BlindFireRightPos = Vector(-10, 10, -5) + +SWEP.BlindFireSuicideAng = Angle(0, 135, 0) +SWEP.BlindFireSuicidePos = Vector(-2, 45, -35) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.03, 0.4, 0) +SWEP.SightPos = Vector(-2.715, -6.5, -4.62) + +SWEP.CorrectivePos = Vector(0.05, 0, 0.2) +SWEP.CorrectiveAng = Angle(-0.36, -0.3, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK2 +SWEP.HolsterPos = Vector(5, 4, -6) +SWEP.HolsterAng = Angle(0, 0, 0) + +// scope + +SWEP.Scope = true +SWEP.ScopeOverlay = Material("tacrp/scopes/l96.png", "mips smooth") // Material("path/to/overlay") +SWEP.ScopeFOV = 90 / 6 +SWEP.ScopeLevels = 1 // 2 = like CS:S +SWEP.ScopeHideWeapon = true + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 5 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "357" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end +SWEP.AmmoTTT = "357" +SWEP.Ammo_Expanded = "ti_rifle" + +SWEP.ReloadTimeMult = 1 +SWEP.ShootTimeMult = 0.75 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/uratio.mdl" // why doesn't it have its own mag model? + +SWEP.ReloadUpInTime = 2.1 +SWEP.DropMagazineTime = 1.4 + +// sounds + +local path = "TacRP/weapons/spr/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = "TacRP/weapons/ak47/ak47_fire_silenced-1.wav" + +SWEP.Vol_Shoot = 130 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_1" +SWEP.EjectEffect = 2 +SWEP.EjectDelay = 0.85 + +// anims + +SWEP.AnimationTranslationTable = { + ["deploy"] = "draw", + ["fire"] = {"shoot1", "shoot2"}, + ["blind_fire"] = "shoot1" +} + +// attachments + +SWEP.AttachmentElements = { + ["optic"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + }, + }, + ["irons"] = { + BGs_VM = { + {2, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"ironsights_sniper", "optic_cqb", "optic_medium", "optic_sniper"}, + WMBone = "Bone02", + Bone = "SPR_root", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-3.95, 0.1, 9), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(-3, 1.25, -5.6), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + WMBone = "Bone02", + Bone = "SPR_root", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-2.85, 0.125, 42), + Pos_WM = Vector(22.5, 1.2, -4.9), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + WMBone = "Bone02", + Bone = "SPR_root", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-2, -0.8, 20), + Pos_WM = Vector(8, 2, -4), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, 0, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_extmag_sniper", "acc_sling", "acc_duffle", "acc_bipod"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_manual"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_sniper", "ammo_R700"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_spr.Clip_Out", path .. "clip_out-1.wav") +addsound("TacInt_spr.Clip_In", path .. "clip_in-1.wav") +addsound("TacInt_spr.Bolt_Back", path .. "bolt_back-1.wav") +addsound("TacInt_spr.bolt_forward", path .. "bolt_forward-1.wav") +addsound("TacInt_spr.safety", path .. "safety-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_superv.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_superv.lua new file mode 100644 index 0000000..686afdc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_superv.lua @@ -0,0 +1,361 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "KRISS Vector" +SWEP.AbbrevName = "Vector" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "1Elite" +SWEP.SubCatType = "3Submachine Gun" + +SWEP.Description = "Close range SMG with extremely high fire rate and practically no recoil. Low armor penetration, but can chew through it very quickly." +SWEP.Description_Quote = "\"We go forward like a breath exhaled from the Earth.\"" -- Modern Warfare 2 (2009). + +SWEP.Trivia_Caliber = "9x19mm" +SWEP.Trivia_Manufacturer = "Kriss USA, Inc." +SWEP.Trivia_Year = "2009" + +SWEP.Faction = TacRP.FACTION_COALITION +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_superv.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_superv.mdl" + +SWEP.Slot = 2 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + Damage_Max = 15, + Damage_Min = 6, + Range_Min = 200, + Range_Max = 3500, + + FreeAimMaxAngle = 3.5, + RPM = 1100, + }, + [TacRP.BALANCE_TTT] = { + Description = "Close range SMG with extremely high fire rate. Practically no recoil, but accuracy is very poor.", + + Damage_Max = 10, + Damage_Min = 5, + Range_Min = 250, + Range_Max = 1000, + + ClipSize = 24, + + Spread = 0.014, + RecoilSpreadPenalty = 0.0005, + HipFireSpreadPenalty = 0.015, + RecoilMaximum = 15, + FreeAimMaxAngle = 3.5, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 9, + Damage_Min = 4, + Range_Min = 200, + Range_Max = 3500, + + Spread = 0.012, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + Description = "Close-range SMG with an insane rate of fire and basically no recoil, but very poor accuracy.", + RecoilSpreadPenalty = 0.003, + HipFireSpreadPenalty = 0.01 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.SMG + +// "ballistics" + +SWEP.Damage_Max = 20 +SWEP.Damage_Min = 10 +SWEP.Range_Min = 200 +SWEP.Range_Max = 1700 +SWEP.Penetration = 4 +SWEP.ArmorPenetration = 0.35 +SWEP.ArmorBonus = 1 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +SWEP.MuzzleVelocity = 12000 + +// misc. shooting + +SWEP.Firemodes = { + 2, + -2, + 1 +} + +SWEP.RPM = 1200 +SWEP.RPMMultBurst = 2 +SWEP.RunawayBurst = true +SWEP.PostBurstDelay = 0.11 + +SWEP.Spread = 0.01 + +SWEP.HipFireSpreadPenalty = 0.012 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 15 +SWEP.RecoilResetTime = 0.04 +SWEP.RecoilDissipationRate = 60 +SWEP.RecoilFirstShotMult = 1 // multiplier for the first shot's recoil amount + +SWEP.RecoilVisualKick = 0.25 + +SWEP.RecoilKick = 1 + +SWEP.RecoilSpreadPenalty = 0.0012 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.95 +SWEP.ShootingSpeedMult = 0.7 +SWEP.SightedSpeedMult = 0.7 + +SWEP.ReloadSpeedMult = 0.5 + +SWEP.AimDownSightsTime = 0.3 +SWEP.SprintToFireTime = 0.3 + +SWEP.Sway = 0.75 +SWEP.ScopedSway = 0.25 + +SWEP.FreeAimMaxAngle = 2.5 + +// hold types + +SWEP.HoldType = "smg" +SWEP.HoldTypeSprint = "passive" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -4, -5) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(0, -4, -3) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.025, 0.1, 0) +SWEP.SightPos = Vector(-4.7, -7.5, -3.55) + +SWEP.CorrectivePos = Vector(0, 0, 0.1) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_BACK +SWEP.HolsterPos = Vector(5, 0, -5) +SWEP.HolsterAng = Angle(0, 0, 0) + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 32 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/superv.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 1.3 +SWEP.DropMagazineTime = 0.4 + +// sounds + +local path = "TacRP/weapons/superv/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_ak47" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "fire2_M", + ["fire1"] = "fire1_L", + ["fire2"] = "fire2_L", + ["fire3"] = "fire3_L", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"}, + ["jam"] = "deploy" +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.25, + tmax = 0.25, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {2, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["optic"] = { + BGs_VM = { + {1, 1} + }, + }, + ["tactical"] = { + BGs_VM = { + {3, 1} + }, + }, +} + +SWEP.NoRMR = true + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "superv_rig.SuperV_ROOT", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-4.1, -0.15, 5), + Pos_WM = Vector(7, 1.5, -5.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "superv_rig.SuperV_ROOT", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + Pos_VM = Vector(-1.3, -0.1, 19), + Pos_WM = Vector(23, 2.5, -2.8), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, -3.5, 180), + }, + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "superv_rig.SuperV_ROOT", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + InstalledElements = {"tactical"}, + Pos_VM = Vector(-1.15, -1.1, 11), + Pos_WM = Vector(14, 3, -3), + Ang_VM = Angle(90, 0, -90), + Ang_WM = Angle(0, -3.5, 90), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_extmag_smg", "acc_sling", "acc_duffle"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_4pos"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("TacInt_superv.Clip_Out", path .. "clip_out-1.wav") +addsound("TacInt_superv.Clip_In", path .. "clip_in-1.wav") +addsound("TacInt_superv.bolt_release", path .. "bolt_release-1.wav") +addsound("TacInt_superv.bolt_back", path .. "bolt_back-1.wav") +addsound("TacInt_superv.bolt_forward", path .. "bolt_forward-1.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/tacrp_uzi.lua b/garrysmod/addons/tacrp/lua/weapons/tacrp_uzi.lua new file mode 100644 index 0000000..b0084f2 --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/tacrp_uzi.lua @@ -0,0 +1,339 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "IMI Uzi Pro" -- The ingame model seems to be partially referenced from an Ekol ASI, a Turkish blank-firing gun based on the Uzi. +SWEP.AbbrevName = "Uzi Pro" +SWEP.Category = "[FT] Оружие" + +SWEP.SubCatTier = "2Operator" +SWEP.SubCatType = "3Machine Pistol" + +SWEP.Description = "Balanced machine pistol with a controllable rate of fire." +SWEP.Description_Quote = "Slava Ukraini." -- Reference to the gun's name in MWIII + +SWEP.Trivia_Caliber = "9x19mm" +SWEP.Trivia_Manufacturer = "Israel Military Industries" +SWEP.Trivia_Year = "1980" + +SWEP.Faction = TacRP.FACTION_MILITIA +SWEP.Credits = "Assets: Tactical Intervention" + +SWEP.ViewModel = "models/weapons/tacint/v_uzi.mdl" +SWEP.WorldModel = "models/weapons/tacint/w_uzi.mdl" + +SWEP.Slot = 1 + +SWEP.BalanceStats = { + [TacRP.BALANCE_SBOX] = { + }, + [TacRP.BALANCE_TTT] = { + Damage_Max = 12, + Damage_Min = 6, + Range_Min = 800, + Range_Max = 2000, + RPM = 750, + + BodyDamageMultipliers = { + [HITGROUP_HEAD] = 2, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 + }, + }, + [TacRP.BALANCE_PVE] = { + Damage_Max = 8, + Damage_Min = 4, + RPM = 850, + }, + [TacRP.BALANCE_OLDSCHOOL] = { + RecoilSpreadPenalty = 0.0024 + } +} + +SWEP.TTTReplace = TacRP.TTTReplacePreset.MachinePistol + +// "ballistics" + +SWEP.Damage_Max = 22 +SWEP.Damage_Min = 6 +SWEP.Range_Min = 500 +SWEP.Range_Max = 2000 +SWEP.Penetration = 4 +SWEP.ArmorPenetration = 0.675 +SWEP.ArmorBonus = 0.5 + +SWEP.MuzzleVelocity = 12500 + +SWEP.BodyDamageMultipliers = { + [HITGROUP_HEAD] = 1.5, + [HITGROUP_CHEST] = 1, + [HITGROUP_STOMACH] = 1, + [HITGROUP_LEFTARM] = 1, + [HITGROUP_RIGHTARM] = 1, + [HITGROUP_LEFTLEG] = 0.75, + [HITGROUP_RIGHTLEG] = 0.75, + [HITGROUP_GEAR] = 0.9 +} + +// misc. shooting + +SWEP.Firemodes = { + 2, + 1 +} + +SWEP.RPM = 950 + +SWEP.Spread = 0.008 +SWEP.RecoilSpreadPenalty = 0.0018 +SWEP.HipFireSpreadPenalty = 0.014 + +SWEP.ShootTimeMult = 0.5 + +SWEP.RecoilResetInstant = false +SWEP.RecoilPerShot = 1 +SWEP.RecoilMaximum = 12 +SWEP.RecoilResetTime = 0 +SWEP.RecoilDissipationRate = 40 +SWEP.RecoilFirstShotMult = 2 + +SWEP.RecoilVisualKick = 1 +SWEP.RecoilKick = 3.5 +SWEP.RecoilStability = 0.2 + +SWEP.CanBlindFire = true + +// handling + +SWEP.MoveSpeedMult = 0.975 +SWEP.ShootingSpeedMult = 0.75 +SWEP.SightedSpeedMult = 0.8 + +SWEP.ReloadSpeedMult = 0.6 + +SWEP.AimDownSightsTime = 0.275 +SWEP.SprintToFireTime = 0.30 + +// hold types + +SWEP.HoldType = "revolver" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = "pistol" + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2 +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_SMG1 + +SWEP.PassiveAng = Angle(-2.5, -1, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 5, 0) +SWEP.BlindFirePos = Vector(3, -2, -5) + +SWEP.BlindFireSuicideAng = Angle(-135, 5, 45) +SWEP.BlindFireSuicidePos = Vector(31, 25, -5) + +SWEP.SprintAng = Angle(30, -15, 0) +SWEP.SprintPos = Vector(5, 0, -2) + +SWEP.SightAng = Angle(0.275, 0.1, 1) +SWEP.SightPos = Vector(-3.65, -6.5, -3.4) + +SWEP.CorrectivePos = Vector(0.04, 0, 0.15) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_PISTOL +SWEP.HolsterPos = Vector(0, 3, -4) +SWEP.HolsterAng = Angle(90, 0, 0) + +SWEP.Sway = 1.2 +SWEP.ScopedSway = 0.4 + +SWEP.FreeAimMaxAngle = 4 + +// reload + +-- Устанавливаем размер магазина и начальное кол-во патрон на 0 +SWEP.ClipSize = 30 +SWEP.DefaultAmmo = 0 +SWEP.Ammo = "pistol" + +-- Принудительное обнуление при создании (Initialize) +function SWEP:Initialize() + self.BaseClass.Initialize(self) + + -- Обнуляем патроны внутри самого оружия + self:SetClip1(0) + + -- Обнуляем резервные патроны у игрока через мгновение после спавна + if SERVER then + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) then + self:GetOwner():SetAmmo(0, self:GetPrimaryAmmoType()) + end + end) + end +end + +SWEP.ReloadTimeMult = 1 +SWEP.DropMagazineModel = "models/weapons/tacint/magazines/uzi.mdl" +SWEP.DropMagazineImpact = "pistol" + +SWEP.ReloadUpInTime = 1.1 +SWEP.DropMagazineTime = 0.35 + +// sounds + +local path = "tacrp/weapons/uzi/" + +SWEP.Sound_Shoot = "^" .. path .. "fire-1.wav" +SWEP.Sound_Shoot_Silenced = path .. "fire_silenced-1.wav" + +SWEP.Vol_Shoot = 110 +SWEP.ShootPitchVariance = 2.5 // amount to vary pitch by each shot + +// effects + +// the .qc attachment for the muzzle +SWEP.QCA_Muzzle = 1 +// ditto for shell +SWEP.QCA_Eject = 2 + +SWEP.MuzzleEffect = "muzzleflash_smg" +SWEP.EjectEffect = 1 + +// anims + +SWEP.AnimationTranslationTable = { + ["fire_iron"] = "fire1_M", + ["fire1"] = "fire1_M", + ["fire2"] = "fire2_M", + ["fire3"] = "fire3_M", + ["fire4"] = {"fire4_M", "fire4_L", "fire4_R"}, + ["melee"] = {"melee1", "melee2"} +} + +SWEP.ProceduralIronFire = { + vm_pos = Vector(0, -1, -0.1), + vm_ang = Angle(0, 0.4, 0), + t = 0.1, + tmax = 0.1, + bones = { + { + bone = "ValveBiped.bolt_handle", + pos = Vector(0, 0, -2), + t0 = 0.05, + t1 = 0.2, + }, + }, +} + +// attachments + +SWEP.AttachmentElements = { + ["foldstock"] = { + BGs_VM = { + {1, 1} + }, + BGs_WM = { + {1, 1} + } + }, + ["tactical"] = { + BGs_VM = { + {3, 1} + }, + }, +} + +SWEP.Attachments = { + [1] = { + PrintName = "Optic", + Category = {"optic_cqb", "optic_medium"}, + Bone = "ValveBiped.uzi_rootbone", + AttachSound = "TacRP/weapons/optic_on.wav", + DetachSound = "TacRP/weapons/optic_off.wav", + InstalledElements = {"optic"}, + Pos_VM = Vector(-5, -0.25, 2.75), + Pos_WM = Vector(7, 1.5, -5.5), + Ang_VM = Angle(90, 0, 0), + Ang_WM = Angle(0, 0, 180), + }, + [2] = { + PrintName = "Muzzle", + Category = "silencer", + Bone = "ValveBiped.uzi_rootbone", + AttachSound = "TacRP/weapons/silencer_on.wav", + DetachSound = "TacRP/weapons/silencer_off.wav", + VMScale = 1, + WMScale = 1, + Pos_VM = Vector(-3.15, -0.25, 13.5), + Ang_VM = Angle(90, 0, 0), + Pos_WM = Vector(18, 1.2, -3.8), + Ang_WM = Angle(0, 0, 180), + }, + + [3] = { + PrintName = "Tactical", + Category = {"tactical", "tactical_zoom", "tactical_ebullet"}, + Bone = "ValveBiped.uzi_rootbone", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + VMScale = 1, + WMScale = 1, + Pos_VM = Vector(-2, -0.25, 7.5), + Ang_VM = Angle(90, 0, 180), + Pos_WM = Vector(11, 1.2, -2.5), + Ang_WM = Angle(0, 0, 0), + }, + [4] = { + PrintName = "Accessory", + Category = {"acc", "acc_foldstock", "acc_extmag_smg", "acc_holster"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [5] = { + PrintName = "Bolt", + Category = {"bolt_automatic"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [6] = { + PrintName = "Trigger", + Category = {"trigger_auto"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [7] = { + PrintName = "Ammo", + Category = {"ammo_pistol"}, + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, + [8] = { + PrintName = "Perk", + Category = {"perk", "perk_melee", "perk_shooting", "perk_reload"}, + AttachSound = "tacrp/weapons/flashlight_on.wav", + DetachSound = "tacrp/weapons/flashlight_off.wav", + }, +} + +local function addsound(name, spath) + sound.Add({ + name = name, + channel = 16, + volume = 1.0, + sound = spath + }) +end + +addsound("tacint_miniuzi.insert_clip", path .. "insert_clip.wav") +addsound("tacint_miniuzi.remove_clip", path .. "remove_clip.wav") +addsound("tacint_miniuzi.bolt_action", path .. "bolt_action.wav") +addsound("tacint_miniuzi.foldingstock_back", path .. "foldingstock_back.wav") diff --git a/garrysmod/addons/tacrp/lua/weapons/weapon_ttt_tacrp_ammo_crate.lua b/garrysmod/addons/tacrp/lua/weapons/weapon_ttt_tacrp_ammo_crate.lua new file mode 100644 index 0000000..af313dc --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/weapon_ttt_tacrp_ammo_crate.lua @@ -0,0 +1,138 @@ +if engine.ActiveGamemode() != "terrortown" then return end + +AddCSLuaFile() + +SWEP.HoldType = "normal" + + +if CLIENT then + SWEP.PrintName = "tacrp_ammocrate_name" + SWEP.Slot = 6 + + SWEP.ViewModelFOV = 10 + SWEP.DrawCrosshair = false + + LANG.AddToLanguage("english", "tacrp_ammocrate_name", "Ammo Crate") + LANG.AddToLanguage("english", "tacrp_ammocrate_help", "{primaryfire} places the Ammo Crate.") + LANG.AddToLanguage("english", "tacrp_ammocrate_desc", [[ +Allows people to replenish ammo. Slow recharge. +Can even give certain traitor weapon ammo types. + +Anyone can use it. Explodes if destroyed. +Can be checked for DNA samples of its users.]]) + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "tacrp_ammocrate_desc" + }; + + SWEP.Icon = "vgui/ttt/tacrp_ammo_crate" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/weapons/tacint/ammoboxes/ammo_box-2b.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 1.0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 1.0 + +-- This is special equipment +SWEP.Kind = WEAPON_EQUIP +SWEP.CanBuy = {ROLE_DETECTIVE, ROLE_TRAITOR} +SWEP.LimitedStock = false -- only buyable once + +SWEP.AllowDrop = false +SWEP.NoSights = true + +function SWEP:OnDrop() + self:Remove() +end + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + self:HealthDrop() +end +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire( CurTime() + self.Secondary.Delay ) + self:HealthDrop() +end + +local throwsound = Sound( "Weapon_SLAM.SatchelThrow" ) + +-- ye olde droppe code +function SWEP:HealthDrop() + if SERVER then + local ply = self:GetOwner() + if not IsValid(ply) then return end + + if self.Planted then return end + + local vsrc = ply:GetShootPos() + local vang = ply:GetAimVector() + local vvel = ply:GetVelocity() + + local vthrow = vvel + vang * 150 + + local health = ents.Create("tacrp_ammo_crate_ttt") + if IsValid(health) then + health:SetPos(vsrc + vang * 10) + health:SetAngles(Angle(0, ply:GetAngles().y - 180, 0)) + health:Spawn() + + health:SetPlacer(ply) + + health:PhysWake() + local phys = health:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(vthrow) + end + self:Remove() + + self.Planted = true + end + end + + self:EmitSound(throwsound) +end + + +function SWEP:Reload() + return false +end + +function SWEP:OnRemove() + if CLIENT and IsValid(self:GetOwner()) and self:GetOwner() == LocalPlayer() and self:GetOwner():Alive() then + RunConsoleCommand("lastinv") + end +end + +if CLIENT then + function SWEP:Initialize() + self:AddHUDHelp("tacrp_ammocrate_help", nil, true) + + return self.BaseClass.Initialize(self) + end +end + +function SWEP:Deploy() + if SERVER and IsValid(self:GetOwner()) then + self:GetOwner():DrawViewModel(false) + end + return true +end + +function SWEP:DrawWorldModel() +end + +function SWEP:DrawWorldModelTranslucent() +end diff --git a/garrysmod/addons/tacrp/lua/weapons/weapon_ttt_tacrp_bench.lua b/garrysmod/addons/tacrp/lua/weapons/weapon_ttt_tacrp_bench.lua new file mode 100644 index 0000000..1d12ded --- /dev/null +++ b/garrysmod/addons/tacrp/lua/weapons/weapon_ttt_tacrp_bench.lua @@ -0,0 +1,135 @@ +if engine.ActiveGamemode() != "terrortown" then return end + +AddCSLuaFile() + +SWEP.HoldType = "normal" + + +if CLIENT then + SWEP.PrintName = "tacrp_bench_name" + SWEP.Slot = 6 + + SWEP.ViewModelFOV = 10 + SWEP.DrawCrosshair = false + + LANG.AddToLanguage("english", "tacrp_bench_name", "Customization Bench") + LANG.AddToLanguage("english", "tacrp_bench_help", "{primaryfire} places the Customization Bench.") + LANG.AddToLanguage("english", "tacrp_bench_desc", [[ +When near, allows for free weapon customization. +Attachments won't be required.]]) + + SWEP.EquipMenuData = { + type = "item_weapon", + desc = "tacrp_bench_desc" + }; + + SWEP.Icon = "vgui/ttt/tacrp_bench" +end + +SWEP.Base = "weapon_tttbase" + +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/weapons/tacint/ammoboxes/ammo_box-2b.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" +SWEP.Primary.Delay = 1.0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Delay = 1.0 + +-- This is special equipment +SWEP.Kind = WEAPON_EQUIP +SWEP.CanBuy = {ROLE_DETECTIVE, ROLE_TRAITOR} +SWEP.LimitedStock = false -- only buyable once + +SWEP.AllowDrop = false +SWEP.NoSights = true + +function SWEP:OnDrop() + self:Remove() +end + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + self:HealthDrop() +end +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire( CurTime() + self.Secondary.Delay ) + self:HealthDrop() +end + +local throwsound = Sound( "Weapon_SLAM.SatchelThrow" ) + +-- ye olde droppe code +function SWEP:HealthDrop() + if SERVER then + local ply = self:GetOwner() + if not IsValid(ply) then return end + + if self.Planted then return end + + local vsrc = ply:GetShootPos() + local vang = ply:GetAimVector() + local vvel = ply:GetVelocity() + + local vthrow = vvel + vang * 50 + + local health = ents.Create("tacrp_bench") + if IsValid(health) then + health:SetPos(vsrc + vang * 24) + health:SetAngles(Angle(0, ply:GetAngles().y, 0)) + health:Spawn() + + -- health:SetPlacer(ply) + + health:PhysWake() + local phys = health:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(vthrow) + end + self:Remove() + + self.Planted = true + end + end + + self:EmitSound(throwsound) +end + + +function SWEP:Reload() + return false +end + +function SWEP:OnRemove() + if CLIENT and IsValid(self:GetOwner()) and self:GetOwner() == LocalPlayer() and self:GetOwner():Alive() then + RunConsoleCommand("lastinv") + end +end + +if CLIENT then + function SWEP:Initialize() + self:AddHUDHelp("tacrp_bench_help", nil, true) + + return self.BaseClass.Initialize(self) + end +end + +function SWEP:Deploy() + if SERVER and IsValid(self:GetOwner()) then + self:GetOwner():DrawViewModel(false) + end + return true +end + +function SWEP:DrawWorldModel() +end + +function SWEP:DrawWorldModelTranslucent() +end diff --git a/garrysmod/addons/talkinganims_ft/lua/autorun/cl_script.lua b/garrysmod/addons/talkinganims_ft/lua/autorun/cl_script.lua new file mode 100644 index 0000000..4a480cf --- /dev/null +++ b/garrysmod/addons/talkinganims_ft/lua/autorun/cl_script.lua @@ -0,0 +1,101 @@ +AddCSLuaFile() +local animCooldown = CreateConVar("talking_animation_cooldown", "2.5", FCVAR_ARCHIVE, "Change cooldown of animation(starts with the animation)") +local animChance = CreateClientConVar("talking_animation_chance", "1",true,true, "Change the chance of talking animation happening", 0, 1) +local enableAnimsClient = CreateClientConVar("enable_player_talking_animation", "1",true,true, "Enable or disable your talking animation.", 0, 1) +local enableVoiceText = CreateClientConVar("talking_chatvoice_animations", "0", true, true, "Enable talking animation only for voice/text chat or both") +local enableWeaponAnims = CreateClientConVar("enable_player_weapon_anims","1",true,true,"Enable talking animations with weapons") + +if SERVER then + util.AddNetworkString( "TalkingStart" ) +end +if CLIENT then + local doingAnim = false + timer.Create("toggleAnim", 5, 0, function() + if doingAnim then + doingAnim = false + end + end ) + + + hook.Add("PopulateToolMenu", "TalkingAnimationMenu", function() + spawnmenu.AddToolMenuOption("Options", "Player Talking Animation", "TalkingAnimationMenu", "Settings", "", "", function(panel) + local isAdmin = LocalPlayer():IsAdmin() + panel:NumSlider("Talking animation chance", "talking_animation_chance", 0, 1, 2) + + panel:NumSlider("Talking animation cooldown", "talking_animation_cooldown", 0,60,1) + panel:Help("(Cooldown starts with the animation)") + panel:NumSlider("Talking animation only for voice/text chat or both", "talking_chatvoice_animations", 0,2,0) + panel:Help("0 - Animations for both text and voice chat") + panel:Help("1 - Animations only for voice chat") + panel:Help("2 - Animations only for text chat") + panel:CheckBox("Enable talking animation with weapons", "enable_player_weapon_anims") + panel:CheckBox("Enable talking animation", "enable_player_talking_animation") + if isAdmin then + panel:Help("------------------------------------") + panel:CheckBox("Enable talking animation (Server)", "enable_talking_animation") + panel:Help("Enable or disable talking animation for everyone") + end + end) + end) + hook.Add("PlayerStartVoice", "AnimationStart", function(player) + if ( player != LocalPlayer() ) then return end + if enableAnimsClient:GetBool() and (!bDead) and enableVoiceText:GetInt() == 0 or enableVoiceText:GetInt() == 1 then + local chance = math.Rand(0,1) + if chance <= animChance:GetFloat() and doingAnim == false then + doingAnim = true + lookup = player:LookupSequence("M_g_sweepout") + if lookup == -1 then + net.Start( "TalkingStart" ) + net.WriteBool(true) + net.WriteString("M") + net.WriteBool(enableWeaponAnims:GetBool()) + net.SendToServer() + else + net.Start( "TalkingStart" ) + net.WriteBool(true) + net.WriteString("W") + net.WriteBool(enableWeaponAnims:GetBool()) + net.SendToServer() + end + end + end + end) + hook.Add( "OnPlayerChat", "SendChatTalkingAnimation", function( ply, strText, bTeam, bDead ) + if ( ply != LocalPlayer() ) then return end + if enableAnimsClient:GetBool() and (!bDead) and enableVoiceText:GetInt() == 0 or enableVoiceText:GetInt() == 2 then + local chance = math.Rand(0,1) + if chance <= animChance:GetFloat() and doingAnim == false then + doingAnim = true + lookup = ply:LookupSequence("M_g_sweepout") + if lookup == -1 then + net.Start( "TalkingStart" ) + net.WriteBool(true) + net.WriteString("M") + net.WriteBool(enableWeaponAnims:GetBool()) + net.SendToServer() + else + net.Start( "TalkingStart" ) + net.WriteBool(true) + net.WriteString("W") + net.WriteBool(enableWeaponAnims:GetBool()) + net.SendToServer() + end + end + end + end) + + net.Receive("TalkingAnimNet", function() + local ply = net.ReadPlayer() + local lookup = net.ReadUInt(16) + + if not IsValid(ply) then return end + if not ply:IsPlayer() then return end + if not lookup or lookup == 0 then return end + + ply:SetLayerBlendIn(lookup, 1) + ply:AddVCDSequenceToGestureSlot(6, lookup, 0, true) + + timer.Adjust("toggleAnim", animCooldown:GetFloat()) + timer.Start("toggleAnim") + end) +end diff --git a/garrysmod/addons/talkinganims_ft/lua/autorun/sh_script.lua b/garrysmod/addons/talkinganims_ft/lua/autorun/sh_script.lua new file mode 100644 index 0000000..7d2c115 --- /dev/null +++ b/garrysmod/addons/talkinganims_ft/lua/autorun/sh_script.lua @@ -0,0 +1,23 @@ +hook.Add( "InitLoadAnimations", "wOS.DynaBase.CustomMount", function() + wOS.DynaBase:RegisterSource({ + Name = "Player Talking Animation", + Type = WOS_DYNABASE.EXTENSION, + Male = "models/humans/male_gestures.mdl", + Female = "models/humans/female_gestures.mdl", + }) + + hook.Add( "PreLoadAnimations", "wOS.DynaBase.MountPTA", function( gender ) + if gender == WOS_DYNABASE.SHARED then return end + + if gender == WOS_DYNABASE.FEMALE then + IncludeModel( "models/mossman_gestures.mdl" ) + IncludeModel( "models/alyx_gest_ep1.mdl") + IncludeModel( "models/alyx_gest_ep2.mdl") + IncludeModel( "models/Eli_gestures.mdl" ) + IncludeModel( "models/humans/male_gestures.mdl") + elseif gender == WOS_DYNABASE.MALE then + IncludeModel( "models/Eli_gestures.mdl" ) + IncludeModel( "models/humans/male_gestures.mdl") + end + end ) +end ) diff --git a/garrysmod/addons/talkinganims_ft/lua/autorun/sv_script.lua b/garrysmod/addons/talkinganims_ft/lua/autorun/sv_script.lua new file mode 100644 index 0000000..4e5eca3 --- /dev/null +++ b/garrysmod/addons/talkinganims_ft/lua/autorun/sv_script.lua @@ -0,0 +1,233 @@ +AddCSLuaFile() + +local enableAnims = CreateConVar("enable_talking_animation", "1", FCVAR_ARCHIVE, "Enable or disable talking animation.") +local animCooldown = CreateConVar("talking_animation_cooldown", "2.5", FCVAR_ARCHIVE, "Change cooldown of animation(starts with the animation)") + +local gender = "M" +local lastAnim = "" + +local talkingAnimationM = { + "Gesture01", + "Gesture05", + "Gesture05NP", + "Gesture06", + "Gesture06NP", + "Gesture07", + "Gesture13", + "E_g_shrug", + "G_medurgent_mid", + "G_righthandroll", + "G_righthandheavy", + "g_palm_out_l", + "g_palm_out_high_l" +} + +local talkingAnimationWeaponsM = { + "g_Rifle_Lhand", + "g_Rifle_Lhand_low", + "bg_accentUp", + "bg_up_l", + "bg_up_r", + "g_palm_out_high_l", + "g_palm_up_high_l" +} + +local talkingAnimationW = { + "A_gesture16", + "M_g_sweepout", + "A_g_midhigh_arcout", + "A_g_midhigh_arcout_left", + "A_g_midhigh_arcout_right", + "A_g_rtl_dwnshp", + "A_g_low2side_palmsout", + "A_g_hflipout", + "A_g_armscrossed", + "A_g_rthdflipout", + "A_g_mid_rtfingflareout", + "A_g_mid_2hdcutdwn", + "A_g_mid_2hdcutdwn_rt", + "A_g_midrtarcdwnout", + "A_g_rtsweepoutbig", + "A_g_leftsweepoutbig", + "A_g_mid_rtcutdwn" +} + +local talkingAnimationWeaponsW = { + "A_g_midhigh_arcout_left", + "g_Rifle_Lhand", + "g_Rifle_Lhand_low", + "bg_accentUp", + "bg_up_l", + "bg_up_r" +} + + +local nonweapon = { + "camera", + "duel", + "knife", + "melee", + "melee2", + "physgun", + "slam", + "normal", + "grenade", + "fist" +} + +local doingAnim = false +local lastAnim = "" + +function fixtables() + print("Fixed tables!") + talkingAnimationWeaponsM = { + "g_Rifle_Lhand", + "g_Rifle_Lhand_low", + "bg_accentUp", + "bg_up_l", + "bg_up_r", + "g_palm_out_high_l", + "g_palm_up_high_l" + } + + talkingAnimationM = { + "Gesture01", + "Gesture05", + "Gesture05NP", + "Gesture06", + "Gesture06NP", + "Gesture07", + "Gesture13", + "E_g_shrug", + "G_medurgent_mid", + "G_righthandroll", + "G_righthandheavy", + "g_palm_out_l", + "g_palm_out_high_l" + } + + talkingAnimationW = { + "A_gesture16", + "M_g_sweepout", + "A_g_midhigh_arcout", + "A_g_midhigh_arcout_left", + "A_g_midhigh_arcout_right", + "A_g_rtl_dwnshp", + "A_g_low2side_palmsout", + "A_g_hflipout", + "A_g_armscrossed", + "A_g_rthdflipout", + "A_g_mid_rtfingflareout", + "A_g_mid_2hdcutdwn", + "A_g_mid_2hdcutdwn_rt", + "A_g_midrtarcdwnout", + "A_g_rtsweepoutbig", + "A_g_leftsweepoutbig", + "A_g_mid_rtcutdwn" + } + + talkingAnimationWeaponsW = { + "A_g_midhigh_arcout_left", + "g_Rifle_Lhand", + "g_Rifle_Lhand_low", + "bg_accentUp", + "bg_up_l", + "bg_up_r" + } +end + +if SERVER then + + + util.AddNetworkString("TalkingAnimNet") + + function GetWeaponHoldAnim( ent) + + if( !IsValid( ent ) ) then return nil end + local physObj = ent:GetPhysicsObject() + if (!IsValid(physObj)) then return false end + + local activeweapon = ent:GetActiveWeapon() + print(activeweapon) + if activeweapon == not nil then + local wephold = activeweapon:GetHoldType() + elseif activeweapon == nil then + local wephold = "normal" + end + + for i=0,#nonweapon do + if wephold == nonweapon[i] then + return true + end + end + end + + net.Receive("TalkingStart", function(len,ply) + local IsDeveloper = GetConVar("developer"):GetInt() + local isTalking = net.ReadBool() + local gender = net.ReadString() + local enableWeaponAnims = net.ReadBool() + local isDealingWithTables = false + if(!IsValid(ply)) then return nil end + if (!ply:Alive()) then return nil end + if(!isTalking) then return nil end + if isTalking and isDealingWithTables == false and enableAnims:GetBool() then + local wephold = GetWeaponHoldAnim(ply) + local animationToLookup = "" + isDealingWithTables = true + if wephold and gender == "M" then + animationToLookup = talkingAnimationM[math.random(#talkingAnimationM)] + if lastAnim == animationToLookup then + table.RemoveByValue(talkingAnimationM, lastAnim) + animationToLookup = talkingAnimationM[math.random(#talkingAnimationM)] + table.insert(talkingAnimationM, lastAnim) + end + elseif wephold and gender == "W" then + animationToLookup = talkingAnimationW[math.random(#talkingAnimationW)] + if lastAnim == animationToLookup then + table.RemoveByValue(talkingAnimationW, lastAnim) + animationToLookup = talkingAnimationW[math.random(#talkingAnimationW)] + table.insert(talkingAnimationW, lastAnim) + end + elseif gender == "M" and enableWeaponAnims then + animationToLookup = talkingAnimationWeaponsM[math.random(#talkingAnimationWeaponsM)] + if lastAnim == animationToLookup then + table.RemoveByValue(talkingAnimationWeaponsM, lastAnim) + animationToLookup = talkingAnimationWeaponsM[math.random(#talkingAnimationWeaponsM)] + table.insert(talkingAnimationWeaponsM, lastAnim) + end + elseif gender == "W" and enableWeaponAnims then + animationToLookup = talkingAnimationWeaponsW[math.random(#talkingAnimationWeaponsW)] + if lastAnim == animationToLookup then + table.RemoveByValue(talkingAnimationWeaponsW, lastAnim) + animationToLookup = talkingAnimationWeaponsW[math.random(#talkingAnimationWeaponsW)] + table.insert(talkingAnimationWeaponsW, lastAnim) + end + end + + if animationToLookup == nil then + fixtables() + return + end + lookup = ply:LookupSequence(animationToLookup) + isDealingWithTables = false + if lookup == -1 then + if IsDeveloper > 0 then + print("Sequence not found!") + ply:ChatPrint(animationToLookup) + print(lookup) + end + else + if IsDeveloper > 0 then + ply:ChatPrint(animationToLookup) + print(lookup) + end + net.Start("TalkingAnimNet") + net.WritePlayer(ply) + net.WriteUInt(lookup, 16) + net.Broadcast() + lastAnim = animationToLookup + end + end + end) +end diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/client/arccw_firearms2_clientmenu.lua b/garrysmod/addons/tfa_antitank/lua/autorun/client/arccw_firearms2_clientmenu.lua new file mode 100644 index 0000000..383ddac --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/client/arccw_firearms2_clientmenu.lua @@ -0,0 +1,79 @@ +local function ARCCW_FAS2_ClientsidePanel(panel) + panel:ClearControls() + + local slider = vgui.Create("DNumSlider", panel) + slider:SetDecimals(0) + slider:SetMin(0) + slider:SetMax(3) + slider:SetConVar("arccw_fas2_handrig") + slider:SetValue(GetConVarNumber("arccw_fas2_handrig")) + slider:SetText("Hand Rig") + + panel:AddItem(slider) + + local MC = vgui.Create("DComboBox", panel) + MC:SetText("Hand Skin") + MC.ConVar = "arccw_fas2_handskin" + + MC:AddChoice("White") + MC:AddChoice("Tan") + MC:AddChoice("Black") + MC:AddChoice("Camo") + + MC.OnSelect = function(panel, index, value, data) + RunConsoleCommand(MC.ConVar, tonumber(index)) + end + + panel:AddItem(MC) + + local MC = vgui.Create("DComboBox", panel) + MC:SetText("Glove Skin") + MC.ConVar = "arccw_fas2_gloveskin" + + MC:AddChoice("Nomex") + MC:AddChoice("Black") + MC:AddChoice("Desert Khaki") + MC:AddChoice("Multicam") + MC:AddChoice("Green") + + MC.OnSelect = function(panel, index, value, data) + RunConsoleCommand(MC.ConVar, tonumber(index)) + end + + panel:AddItem(MC) + + local MC = vgui.Create("DComboBox", panel) + MC:SetText("Sleeve Skin") + MC.ConVar = "arccw_fas2_sleeveskin" + + MC:AddChoice("Woodland Camo") + MC:AddChoice("Digital Camo") + MC:AddChoice("Russian Digital Camo") + MC:AddChoice("Black") + + MC.OnSelect = function(panel, index, value, data) + RunConsoleCommand(MC.ConVar, tonumber(index)) + end + + panel:AddItem(MC) + + panel:AddControl("Button", {Label = "Apply Skin", Command = "arccw_fas2_handrig_applynow"}) + + local slider = vgui.Create("DNumSlider", panel) + slider:SetDecimals(2) + slider:SetMin(0) + slider:SetMax(2) + slider:SetConVar("arccw_fas2_headbob_intensity") + slider:SetValue(GetConVarNumber("arccw_fas2_headbob_intensity")) + slider:SetText("Headbob Intensity") + + panel:AddItem(slider) + + panel:AddControl("CheckBox", {Label = "FA:S 2.0 Bolting", Command = "arccw_fas2_bolting"}) +end + +local function ARCCW_FAS2_PopulateToolMenu() + spawnmenu.AddToolMenuOption("Options", "ArcCW", "ArcCW_FAS2", "FA:S 2.0 Options", "", "", ARCCW_FAS2_ClientsidePanel) +end + +hook.Add("PopulateToolMenu", "ArcCW_FAS2_PopulateToolMenu", ARCCW_FAS2_PopulateToolMenu) diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/client/at4_particles.lua b/garrysmod/addons/tfa_antitank/lua/autorun/client/at4_particles.lua new file mode 100644 index 0000000..60fc255 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/client/at4_particles.lua @@ -0,0 +1,4 @@ +AddCSLuaFile() +game.AddParticles( "particles/at4_explo.pcf") +game.AddParticles( "particles/struc_bomb.pcf") +game.AddParticles( "particles/fires.pcf") diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/hb_download_basics.lua b/garrysmod/addons/tfa_antitank/lua/autorun/hb_download_basics.lua new file mode 100644 index 0000000..3d61f38 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/hb_download_basics.lua @@ -0,0 +1,9 @@ +-- DO NOT REMOVE THIS FILE! THIS IS BASIC FILE DOWNLOAD WHICH IS ABSOLUTELY REQUIRED BY ANY CLIENT! IF YOU REMOVE THIS THEN MOST CLIENTS WILL BE BLIND BY PURPLE CHECKERS! +AddCSLuaFile(); +if (SERVER) then + resource.AddSingleFile( "materials/hud/radiation.vmt" ) + resource.AddSingleFile( "materials/hud/radiation.vtf" ) + resource.AddSingleFile( "sound/natsu/firedrive.mp3" ) + resource.AddWorkshop( 668552230 ) + resource.AddWorkshop( 668558959 ) +end diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_soundlist.lua b/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_soundlist.lua new file mode 100644 index 0000000..75d3648 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_soundlist.lua @@ -0,0 +1,19 @@ + +if SERVER then + AddCSLuaFile(); +emp_soundlist = { + "gbombs_5/explosions/emp_wave/emp_discharge.mp3", + "ambient/energy/newspark01.wav", + "ambient/energy/newspark02.wav", + "ambient/energy/newspark03.wav", + "ambient/energy/newspark04.wav", + "ambient/energy/newspark05.wav", + "ambient/energy/newspark06.wav", + "ambient/energy/newspark07.wav", + "ambient/energy/newspark08.wav", + "ambient/energy/newspark09.wav", + "ambient/energy/newspark10.wav", + "ambient/energy/newspark11.wav", + } + +end diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_whitelist.lua b/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_whitelist.lua new file mode 100644 index 0000000..aa2a9da --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_whitelist.lua @@ -0,0 +1,26 @@ + +if SERVER then + AddCSLuaFile(); +emp_whitelist = {"gmod_wire_pod", + "gmod_wire_expression2", + "gmod_wire_gate", + "gmod_wire_value", + "gmod_wire_button", + "gmod_wire_dynamic_button", + "gmod_button", + "gmod_hoverball", + "radio_music", + "airraid_siren", + "general_siren", + "missile_detector", + "nuclear_siren", + "sent_tardis", + "npc_rollermine", + "npc_manhack", + "npc_cscanner", + "npc_turret_floor", + "npc_clawscanner", + "npc_helicopter", + "npc_combinegunship", + "scifi_siren"} +end diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_whitelist_ragdoll.lua b/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_whitelist_ragdoll.lua new file mode 100644 index 0000000..11be385 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/hb_emp_whitelist_ragdoll.lua @@ -0,0 +1,10 @@ + +if SERVER then + AddCSLuaFile(); +emp_whiteragdolllist = {"npc_combinedropship", + "npc_strider", + "npc_dog", + "npc_stalker", + "npc_combinegunship", + "npc_hunter"} +end diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/panzerfaust3_rocket_ammo.lua b/garrysmod/addons/tfa_antitank/lua/autorun/panzerfaust3_rocket_ammo.lua new file mode 100644 index 0000000..2a0a601 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/panzerfaust3_rocket_ammo.lua @@ -0,0 +1,10 @@ +function AddAmmoType(name, text) +game.AddAmmoType({name = name, +dmgtype = DMG_BLAST}) + +if CLIENT then +language.Add(name .. "_ammo", text) +end +end + +AddAmmoType("PanzerFaust3 Rocket", "PanzerFaust3 Rocket") diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/server/at4_particles.lua b/garrysmod/addons/tfa_antitank/lua/autorun/server/at4_particles.lua new file mode 100644 index 0000000..60fc255 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/server/at4_particles.lua @@ -0,0 +1,4 @@ +AddCSLuaFile() +game.AddParticles( "particles/at4_explo.pcf") +game.AddParticles( "particles/struc_bomb.pcf") +game.AddParticles( "particles/fires.pcf") diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_convars.lua b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_convars.lua new file mode 100644 index 0000000..d89d63d --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_convars.lua @@ -0,0 +1,33 @@ +AddCSLuaFile() + +if GetConVar("hb_easyuse") == nil then + CreateConVar("hb_easyuse", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end + +if GetConVar("hb_fragility") == nil then + CreateConVar("hb_fragility", "1", {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_nuclear_emp") == nil then + CreateConVar("hb_nuclear_emp", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_safeemp") == nil then + CreateConVar("hb_safeemp", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_nuclear_vaporisation") == nil then + CreateConVar("hb_nuclear_vaporisation", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_shockwave_unfreeze") == nil then + CreateConVar("hb_shockwave_unfreeze", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_decals") == nil then + CreateConVar("hb_decals", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_realistic_sound") == nil then + CreateConVar("hb_realistic_sound", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_sound_shake") == nil then + CreateConVar("hb_sound_shake", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end +if GetConVar("hb_nuclear_fallout") == nil then + CreateConVar("hb_nuclear_fallout", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY } ) +end diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_decals.lua b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_decals.lua new file mode 100644 index 0000000..59ebce2 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_decals.lua @@ -0,0 +1,11 @@ +AddCSLuaFile() +game.AddDecal( "scorch_small", "decals/scorch_small" ); +game.AddDecal( "scorch_medium", "decals/scorch_medium" ); +game.AddDecal( "scorch_big", "decals/scorch_big" ); +game.AddDecal( "scorch_big_2", "decals/scorch_big_2" ); +game.AddDecal( "scorch_big_3", "decals/scorch_big_3" ); +game.AddDecal( "nuke_small", "decals/nuke_small" ); +game.AddDecal( "nuke_medium", "decals/nuke_medium" ); +game.AddDecal( "nuke_big", "decals/nuke_big" ); +game.AddDecal( "nuke_tsar", "decals/nuke_tsar" ); +game.AddDecal( "tiberium", "models/rogue/tibe/tibe_decal" ); diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_main.lua b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_main.lua new file mode 100644 index 0000000..3ffd1b4 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_main.lua @@ -0,0 +1,42 @@ +AddCSLuaFile() +util.AddNetworkString( "hbombs_cvar" ) +util.AddNetworkString( "hbombs_net" ) +util.AddNetworkString( "hbombs_general" ) +util.AddNetworkString( "hbombs_announcer" ) +SetGlobalString ( "hb_ver", 5 ) + +TOTAL_BOMBS = 0 +net.Receive( "hbombs_cvar", function( len, pl ) + if( !pl:IsAdmin() ) then return end + local cvar = net.ReadString(); + local val = net.ReadFloat(); + if( GetConVar( tostring( cvar ) ) == nil ) then return end + if( GetConVarNumber( tostring( cvar ) ) == tonumber( val ) ) then return end + + game.ConsoleCommand( tostring( cvar ) .." ".. tostring( val ) .."\n" ); + +end ); + + +function source_debug( ply, command) + ply:ChatPrint("Engine Tickrate: \n"..tostring(1/engine.TickInterval())) +end +concommand.Add( "source_debug", source_debug ) + +function hbversion( ply, command, arguments ) + ply:ChatPrint( "Hbombs 5/14/16" ) +end +concommand.Add( "hb_version", hbversion ) + + +function hb_spawn(ply) + ply.gasmasked=false + ply.hazsuited=false + net.Start( "hbombs_net" ) + net.WriteBit( false ) + ply:StopSound("breathing") + net.Send(ply) +end +hook.Add( "PlayerSpawn", "hb_spawn", hb_spawn ) + + diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_physics.lua b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_physics.lua new file mode 100644 index 0000000..458d097 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/server/hb_physics.lua @@ -0,0 +1,14 @@ +AddCSLuaFile() + +function hb_physics() +Msg("\n|Hbombs physics module initialized!") +Msg("\n|If you don't want this, delete the hb_physics.lua file\n") + +phys = {} +phys.MaxVelocity = 5000 +phys.MaxAngularVelocity = 3636.3637695313 +physenv.SetPerformanceSettings(phys) + +end + +hook.Add( "InitPostEntity", "hb_physics", hb_physics ) diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/sh_firearms2_hands.lua b/garrysmod/addons/tfa_antitank/lua/autorun/sh_firearms2_hands.lua new file mode 100644 index 0000000..13fe06e --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/sh_firearms2_hands.lua @@ -0,0 +1,92 @@ + +CreateClientConVar("arccw_fas2_handskin", "1", true, true) +CreateClientConVar("arccw_fas2_gloveskin", "1", true, true) +CreateClientConVar("arccw_fas2_sleeveskin", "1", true, true) +CreateClientConVar("arccw_fas2_handrig", "0", true, true) +CreateClientConVar("arccw_fas2_headbob_intensity", "1", true, true) +CreateClientConVar("arccw_fas2_bolting", "1", true, true) + +NumberToTexture = {[1] = "models/weapons/fas2/hands/hand", + [2] = "models/weapons/fas2/hands/hand_tan", + [3] = "models/weapons/fas2/hands/hand_black", + [4] = "models/weapons/fas2/hands/hand_camo"} + +NumberToGlove = {[1] = "models/weapons/fas2/hands/nomex", + [2] = "models/weapons/fas2/hands/black", + [3] = "models/weapons/fas2/hands/desertkhaki", + [4] = "models/weapons/fas2/hands/multicam", + [5] = "models/weapons/fas2/hands/green"} + +NumberToSleeve = {[1] = "models/weapons/fas2/hands/sleeve", + [2] = "models/weapons/fas2/hands/sleeve2", + [3] = "models/weapons/fas2/hands/sleeve3", + [4] = "models/weapons/fas2/hands/sleeve4"} + +if CLIENT then + -- local function ARCCW_FAS2_InitPostEntity() + -- ply = LocalPlayer() + -- ply.ARCCW_FAS_FamiliarWeapons = {} + + -- for k, v in pairs(weapons.GetList()) do + -- if v.ViewModel then + -- util.PrecacheModel(v.ViewModel) + -- end + -- end + -- end + + -- hook.Add("InitPostEntity", "ARCCW_FAS2_InitPostEntity", ARCCW_FAS2_InitPostEntity) + + local cvar + local ArcCW_HandMat = Material("models/weapons/fas2/hands/hand") + local ArcCW_HandMat2 = Material("models/weapons/fas2/hands/hand") + local ArcCW_GloveMat, ArcCW_GloveMat2 = Material("models/weapons/fas2/hands/nomex"), Material("models/weapons/fas2/hands/nomex") + local ArcCW_SleeveMat, ArcCW_SleeveMat2 = Material("models/weapons/fas2/hands/sleeve"), Material("models/weapons/fas2/hands/sleeve") + local vm + + local function ArcCW_FAS2_ApplyRigNow(ply, com, args) + -- cvar = GetConVarNumber("arccw_fas2_handrig") + + -- LocalPlayer():GetViewModel():SetBodygroup(0, cvar) + + -- for k, v in pairs(LocalPlayer():GetWeapons()) do + -- print(v.VMElements) + -- if v.IsARCCWFAS2Weapon and IsValid(v.VMElements) then + -- v.VMElements:SetBodygroup(1, cvar) + -- end + -- end + + local t = NumberToTexture[GetConVarNumber("arccw_fas2_handskin")] + + ArcCW_HandMat:SetTexture("$basetexture", t and t or "models/weapons/fas2/hands/hand") + ArcCW_HandMat2:SetTexture("$basetexture", t and t or "models/weapons/fas2/hands/hand") + + local t = NumberToSleeve[GetConVarNumber("arccw_fas2_sleeveskin")] + + ArcCW_SleeveMat:SetTexture("$basetexture", t and t or "models/weapons/fas2/hands/sleeve") + ArcCW_SleeveMat2:SetTexture("$basetexture", t and t or "models/weapons/fas2/hands/sleeve") + + local t = NumberToGlove[GetConVarNumber("arccw_fas2_gloveskin")] + + ArcCW_GloveMat:SetTexture("$basetexture", t and t or "models/weapons/fas2/hands/nomex") + ArcCW_GloveMat2:SetTexture("$basetexture", t and t or "models/weapons/fas2/hands/nomex") + end + + concommand.Add("arccw_fas2_handrig_applynow", ArcCW_FAS2_ApplyRigNow) +end + +-- if self:GetOwner():IsPlayer() then +-- vm = self:GetOwner():GetViewModel() +-- end + +-- if vm and vm:IsValid() then +-- ArcCW.SetBodyGroups(1, cvar) +-- -- vm:SetMaterial(vmm) +-- -- vm:SetColor(vmc) +-- -- vm:SetSkin(vms) + +-- vmp["BaseClass"] = nil + +-- for i, k in pairs(vmp) do +-- vm:SetPoseParameter(i, k) +-- end +-- end diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/sh_firearms2_misc.lua b/garrysmod/addons/tfa_antitank/lua/autorun/sh_firearms2_misc.lua new file mode 100644 index 0000000..92d93c1 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/sh_firearms2_misc.lua @@ -0,0 +1,144 @@ +game.AddParticles("particles/muzzleflashes.pcf") +game.AddParticles("particles/muzzleflashes_test.pcf") +game.AddParticles("particles/muzzleflashes_test_b.pcf") +game.AddParticles("particles/new_muzzleflashes.pcf") +game.AddParticles("particles/new_muzzleflashes_b.pcf") +game.AddParticles("particles/flashbang.pcf") +game.AddParticles("particles/rocket_fx.pcf") +game.AddParticles("particles/fas_explosions.pcf") +game.AddParticles("particles/ammo.pcf") +game.AddParticles("particles/mortar_fx.pcf") +game.AddParticles("particles/fire_01.pcf") +game.AddParticles("particles/env_effects.pcf") +game.AddParticles("particles/burning_fx.pcf") + +--muzzleflashes +PrecacheParticleSystem("muzzleflash_1") -- SKS, RPK +PrecacheParticleSystem("muzzleflash_3") -- RK95, UMP45, (SG552) +PrecacheParticleSystem("muzzleflash_4") -- G36K, (G36C) +PrecacheParticleSystem("muzzleflash_5") -- (M60E3), ASH12 +PrecacheParticleSystem("muzzleflash_6") -- M4A1, M16 +PrecacheParticleSystem("muzzleflash_ak47") -- AK47 +PrecacheParticleSystem("muzzleflash_ak74") -- AK74 +PrecacheParticleSystem("muzzleflash_FAMAS") -- FAMAS F1, SG550 +PrecacheParticleSystem("muzzleflash_g3") -- G3A3 +PrecacheParticleSystem("muzzleflash_m14") -- M14, L85, (M21) +PrecacheParticleSystem("muzzleflash_M3") -- M3S90, MP153 +PrecacheParticleSystem("muzzleflash_m79") -- (M79) +PrecacheParticleSystem("muzzleflash_M82") -- (M82) +PrecacheParticleSystem("muzzleflash_MINIMI") -- (MINIMI) +PrecacheParticleSystem("muzzleflash_mp5") -- MP5A5 +PrecacheParticleSystem("muzzleflash_none") -- Undefined -- +PrecacheParticleSystem("muzzleflash_none_red") -- Undefined -- +PrecacheParticleSystem("muzzleflash_none_smoke") -- Undefined -- +PrecacheParticleSystem("muzzleflash_OTS") -- P226, (OTS) +PrecacheParticleSystem("muzzleflash_pistol") -- Glock, M1911, (Beretta F92) +PrecacheParticleSystem("muzzleflash_pistol_cleric") -- Vector +PrecacheParticleSystem("muzzleflash_pistol_deagle") -- Deagle, M40A3 +PrecacheParticleSystem("muzzleflash_pistol_red") -- PMM +PrecacheParticleSystem("muzzleflash_shotgun") -- Remington 870, SAIGA12K +PrecacheParticleSystem("muzzleflash_slug") -- KS23 +PrecacheParticleSystem("muzzleflash_smg") -- L2A3, UZI, (MK7A4), (MAC11) +PrecacheParticleSystem("muzzleflash_smg_bizon") -- Bizon +PrecacheParticleSystem("muzzleflash_suppressed") -- All Rifles +PrecacheParticleSystem("party_spark") -- Undefined -- +PrecacheParticleSystem("port_smoke_heavy") -- Undefined -- +PrecacheParticleSystem("muzzle_rockets") -- RPG26 +PrecacheParticleSystem("muzzleflash_1bb") -- Undefined -- +PrecacheParticleSystem("muzzleflash_3bb") -- AK104 +PrecacheParticleSystem("muzzleflash_4bb") -- FN P90 +PrecacheParticleSystem("muzzleflash_6b") -- FN FAL +PrecacheParticleSystem("muzzleflash_m24") -- M24 +PrecacheParticleSystem("muzzleflash_pistol_rbull") -- Raging bull +PrecacheParticleSystem("muzzleflash_SR25") -- SR25, M110 +PrecacheParticleSystem("muzzleflash_svd") -- SVD, Kar98K +PrecacheParticleSystem("muzzleflash_vollmer") -- MC51B Vollmer + +PrecacheParticleSystem("explosion_claymore") -- medium +PrecacheParticleSystem("explosion_HE_claymore") -- big +PrecacheParticleSystem("explosion_grenade") -- medium +PrecacheParticleSystem("explosion_he_grenade") -- big +PrecacheParticleSystem("explosion_m79") -- medium +PrecacheParticleSystem("explosion_HE_m79") -- big +PrecacheParticleSystem("explosion_m79_body") -- playerhit +-- PrecacheParticleSystem("party_fireworks") -- +PrecacheParticleSystem("explosion_water") -- + +PrecacheParticleSystem("fire_vehicle") -- helicrush +PrecacheParticleSystem("explosion") -- heliflyburn + +PrecacheParticleSystem("explosion_flashbang") -- medium +PrecacheParticleSystem("explosion_he_flashbang") -- big +PrecacheParticleSystem("HE_smoke_grenade_smoke") -- m18 purple +PrecacheParticleSystem("smoke_grenade_smoke") -- m18 green +PrecacheParticleSystem("artillery_smoke_blue") -- flare +PrecacheParticleSystem("artillery_smoke_red") -- flare +PrecacheParticleSystem("m79_trail") -- + + +function AddAmmoType(name, text) + game.AddAmmoType({name = name}) + + if CLIENT then + language.Add(name .. "_ammo", text) + end +end + +--PISTOL +AddAmmoType("9x18mm", "9x18MM") +AddAmmoType("9x19mm", "9x19MM") +AddAmmoType(".45acp", ".45 ACP") +AddAmmoType(".357magnum", ".357 Magnum") +AddAmmoType(".454casull", ".454 Casull") +AddAmmoType(".50ae", ".50AE") + +AddAmmoType("9x18mmap", "9x18MM AP") +AddAmmoType("9x19mmap", "9x19MM AP") +AddAmmoType(".45acphs", ".45 ACP HS") +AddAmmoType(".357magnumap", ".357 Magnum AP") +AddAmmoType(".50aeap", ".50AE AP") +AddAmmoType(".454casullap", ".454 Casull AP") + + + +AddAmmoType("9x39mm", "9x39MM") +AddAmmoType("7.62x39mm", "7.62x39MM") +AddAmmoType("5.56x45mm", "5.56x45MM") +AddAmmoType("5.45x39mm", "5.45x39MM") +AddAmmoType("12.7x55mm", "12.7x55MM") +AddAmmoType("5.7x28mm", "5.7x28MM") +AddAmmoType(".50bmg", ".50 BMG") +AddAmmoType("7.62x54mm", "7.62x54MM") +AddAmmoType("7.62x51mm", "7.62x51MM") +AddAmmoType(".338lapua", ".338 Lapua") +AddAmmoType("12gauge", "12 Gauge") +AddAmmoType("12gauge_50bmg", "12 Gauge .50BMG Slug") +AddAmmoType("23x75mm", "23x75MM") +AddAmmoType("23x75mmap", "23x75MM S-25") +AddAmmoType("23x75mmzvezda", "23x75MM Zvezda") + +--ADDITIONAL +AddAmmoType("9x39mmap", "9x39MM AP") +AddAmmoType("9x39mmsp", "9x39MM SP") +AddAmmoType("7.62x39mmap", "7.62x39MM AP") +AddAmmoType("5.56x45mmap", "5.56x45MM AP") +AddAmmoType("5.45x39mmap", "5.45x39MM AP") +AddAmmoType("12.7x55mmap", "12.7x55MM AP") +AddAmmoType("5.7x28mmap", "5.7x28MM AP") +AddAmmoType("7.62x54mmap", "7.62x54MM AP") +AddAmmoType("7.62x54mmsp", "7.62x54MM SP") +AddAmmoType("7.62x51mmap", "7.62x51MM AP") +AddAmmoType("12gaugeincendiary", "12 Gauge Incendiary") +AddAmmoType("12gaugeslug", "12 Gauge Slug") + +-- AddAmmoType("9x18mmtr", "9x18MM Tracer") + +AddAmmoType("40mmhe", "40MM HE") +AddAmmoType("vog25", "VOG-25") +AddAmmoType("vog30", "VOG-30") +AddAmmoType("40mmsmoke", "40MM Smoke") +AddAmmoType("60mmp1ap", "M60P1 AP") +AddAmmoType("m67_nades", "M67 Grenades") +AddAmmoType("m18_nades", "M18 Grenades") +AddAmmoType("m84_nades", "M84 Grenades") +AddAmmoType("rpg26_rocket", "RPG 26 Rocket") diff --git a/garrysmod/addons/tfa_antitank/lua/autorun/tfa_panzerfaust3_killicon.lua b/garrysmod/addons/tfa_antitank/lua/autorun/tfa_panzerfaust3_killicon.lua new file mode 100644 index 0000000..d2f7b8d --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/autorun/tfa_panzerfaust3_killicon.lua @@ -0,0 +1,6 @@ +local hudcolor = Color(255, 255, 255, 255) + +if killicon and killicon.Add then + killicon.Add("tfa_panzerfaust3_rocket", "vgui/hud/tfa_ins2_panzerfaust3", hudcolor) + killicon.Add("tfa_ins2_panzerfaust3", "vgui/hud/tfa_ins2_panzerfaust3", hudcolor) +end diff --git a/garrysmod/addons/tfa_antitank/lua/effects/m9k_gdcw_cinematicboom/init.lua b/garrysmod/addons/tfa_antitank/lua/effects/m9k_gdcw_cinematicboom/init.lua new file mode 100644 index 0000000..90ca9bb --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/effects/m9k_gdcw_cinematicboom/init.lua @@ -0,0 +1,838 @@ + + //Sound,Impact + + // 1 2 3 4 5 + //Dirt, Concrete, Metal, Glass, Flesh + + // 1 2 3 4 5 6 7 8 9 + //Dust, Dirt, Sand, Metal, Smoke, Wood, Glass, Blood, YellowBlood +local mats={ + [MAT_ALIENFLESH] ={5,9}, + [MAT_ANTLION] ={5,9}, + [MAT_BLOODYFLESH] ={5,8}, + [45] ={5,8}, // Metrocop heads are a source glitch, they have no enumeration + [MAT_CLIP] ={3,5}, + [MAT_COMPUTER] ={4,5}, + [MAT_FLESH] ={5,8}, + [MAT_GRATE] ={3,4}, + [MAT_METAL] ={3,4}, + [MAT_PLASTIC] ={2,5}, + [MAT_SLOSH] ={5,5}, + [MAT_VENT] ={3,4}, + [MAT_FOLIAGE] ={1,5}, + [MAT_TILE] ={2,5}, + [MAT_CONCRETE] ={2,1}, + [MAT_DIRT] ={1,2}, + --[85] ={1,2}, + [MAT_SAND] ={1,3}, + [MAT_WOOD] ={2,6}, + [MAT_GLASS] ={4,7}, +} + +local sounds={ + [1]={"Bullet.Dirt",}, + [2]={"Bullet.Concrete",}, + [3]={"Bullet.Metal",}, + [4]={"Bullet.Glass",}, + [5]={"Bullet.Flesh",}, +} + +function EFFECT:Init(data) +self.Entity = data:GetEntity() // Entity determines what is creating the dynamic light // + +self.Pos = data:GetOrigin() // Origin determines the global position of the effect // + +self.Scale = data:GetScale() // Scale determines how large the effect is // +self.Radius = data:GetRadius() or 1 // Radius determines what type of effect to create, default is Concrete // + +self.DirVec = data:GetNormal() // Normal determines the direction of impact for the effect // +self.PenVec = data:GetStart() // PenVec determines the direction of the round for penetrations // +self.Particles = data:GetMagnitude() // Particles determines how many puffs to make, primarily for "trails" // +self.Angle = self.DirVec:Angle() // Angle is the angle of impact from Normal // +self.DebrizzlemyNizzle = 10+data:GetScale() // Debrizzle my Nizzle is how many "trails" to make // +self.Size = 5*self.Scale // Size is exclusively for the explosion "trails" size // + +self.Emitter = ParticleEmitter( self.Pos ) // Emitter must be there so you don't get an error // + + + + if self.Scale<1.2 then + sound.Play( "ambient/explosions/explode_" .. math.random(1, 4) .. ".wav", self.Pos, 100, 100 ) + else + sound.Play( "Explosion.Boom", self.Pos) + sound.Play( "ambient/explosions/explode_" .. math.random(1, 4) .. ".wav", self.Pos, 100, 100 ) + end + + + self.Mat=math.ceil(self.Radius) + + foundTheMat = false + for k, v in pairs(mats) do + if k == self.Mat then + foundTheMat = true + continue + end + end + if not (foundTheMat) then self.Mat = 84 end + --THERE! I FIXED IT! + + if mats[self.Mat][2]==1 then self:Dust() + elseif mats[self.Mat][2]==2 then self:Dirt() + elseif mats[self.Mat][2]==3 then self:Sand() + elseif mats[self.Mat][2]==4 then self:Metal() + elseif mats[self.Mat][2]==5 then self:Smoke() + elseif mats[self.Mat][2]==6 then self:Wood() + elseif mats[self.Mat][2]==7 then self:Glass() + elseif mats[self.Mat][2]==8 then self:Blood() + elseif mats[self.Mat][2]==9 then self:YellowBlood() + else self:Smoke() + end + +end + + function EFFECT:Dust() + + for i=1,5 do + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=1, 20*self.Scale do + local Dust = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*300)*self.Scale) ) + Dust:SetDieTime( math.Rand( 2 , 3 ) ) + Dust:SetStartAlpha( 230 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (50*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 150 ) + Dust:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Dust:SetColor( 80,80,80 ) + end + end + + for i=1, 15*self.Scale do + local Dust = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*400)*self.Scale) ) + Dust:SetDieTime( math.Rand( 1 , 5 )*self.Scale ) + Dust:SetStartAlpha( 50 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (80*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 250 ) + Dust:SetGravity( Vector( math.Rand( -200 , 200 ), math.Rand( -200 , 200 ), math.Rand( 10 , 100 ) ) ) + Dust:SetColor( 90,85,75 ) + end + end + + for i=1, 25*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(0,700)*self.Scale + VectorRand():GetNormalized() * math.random(0,700)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,10)*self.Scale) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 60,60,60 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + local Angle = self.DirVec:Angle() + + for i = 1, self.DebrizzlemyNizzle do /// This part makes the trailers /// + Angle:RotateAroundAxis(Angle:Forward(), (360/self.DebrizzlemyNizzle)) + local DustRing = Angle:Up() + local RanVec = self.DirVec*math.Rand(1, 5) + (DustRing*math.Rand(2, 5)) + + for k = 3, self.Particles do + local Rcolor = math.random(-20,20) + + local particle1 = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + particle1:SetVelocity((VectorRand():GetNormalized()*math.Rand(1, 2) * self.Size) + (RanVec*self.Size*k*3.5)) + particle1:SetDieTime( math.Rand( 0.5, 4 )*self.Scale ) + + particle1:SetStartAlpha( math.Rand( 90, 100 ) ) + particle1:SetEndAlpha(0) + particle1:SetGravity((VectorRand():GetNormalized()*math.Rand(5, 10)* self.Size) + Vector(0,0,-50)) + particle1:SetAirResistance( 200+self.Scale*20 ) + particle1:SetStartSize( (5*self.Size)-((k/self.Particles)*self.Size*3) ) + particle1:SetEndSize( (20*self.Size)-((k/self.Particles)*self.Size) ) + particle1:SetRoll( math.random( -500, 500 )/100 ) + + particle1:SetRollDelta( math.random( -0.5, 0.5 ) ) + particle1:SetColor( 90+Rcolor,87+Rcolor,80+Rcolor ) + end + end + end + +function EFFECT:Dirt() + + for i=1,5 do + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=1, 20*self.Scale do + local Dust = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*300)*self.Scale) ) + Dust:SetDieTime( math.Rand( 2 , 3 ) ) + Dust:SetStartAlpha( 230 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (50*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 150 ) + Dust:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Dust:SetColor( 90,83,68 ) + end + end + + for i=1, 15*self.Scale do + local Dust = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,400)*self.Scale + ((VectorRand():GetNormalized()*400)*self.Scale) ) + Dust:SetDieTime( math.Rand( 1 , 5 )*self.Scale ) + Dust:SetStartAlpha( 50 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( (80*self.Scale) ) + Dust:SetEndSize( (100*self.Scale) ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 250 ) + Dust:SetGravity( Vector( math.Rand( -200 , 200 ), math.Rand( -200 , 200 ), math.Rand( 10 , 100 ) ) ) + Dust:SetColor( 90,83,68 ) + end + end + + for i=1, 25*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(0,700)*self.Scale + VectorRand():GetNormalized() * math.random(0,700)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,10)*self.Scale) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 50,53,45 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + local Angle = self.DirVec:Angle() + + for i = 1, self.DebrizzlemyNizzle do /// This part makes the trailers /// + Angle:RotateAroundAxis(Angle:Forward(), (360/self.DebrizzlemyNizzle)) + local DustRing = Angle:Up() + local RanVec = self.DirVec*math.Rand(2, 6) + (DustRing*math.Rand(1, 4)) + + for k = 3, self.Particles do + local Rcolor = math.random(-20,20) + + local particle1 = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + particle1:SetVelocity((VectorRand():GetNormalized()*math.Rand(1, 2) * self.Size) + (RanVec*self.Size*k*3.5)) + particle1:SetDieTime( math.Rand( 0.5, 4 )*self.Scale ) + + particle1:SetStartAlpha( math.Rand( 90, 100 ) ) + particle1:SetEndAlpha(0) + particle1:SetGravity((VectorRand():GetNormalized()*math.Rand(5, 10)* self.Size) + Vector(0,0,-50)) + particle1:SetAirResistance( 200+self.Scale*20 ) + particle1:SetStartSize( (5*self.Size)-((k/self.Particles)*self.Size*3) ) + particle1:SetEndSize( (20*self.Size)-((k/self.Particles)*self.Size) ) + particle1:SetRoll( math.random( -500, 500 )/100 ) + + particle1:SetRollDelta( math.random( -0.5, 0.5 ) ) + particle1:SetColor( 90+Rcolor,83+Rcolor,68+Rcolor ) + end + end + end + + function EFFECT:Sand() + + for i=0, 45*self.Scale do // This is the main plume + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( self.DirVec * math.random( 50,1000*self.Scale) + VectorRand():GetNormalized()*300*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 5 )*self.Scale ) + Smoke:SetStartAlpha( math.Rand( 100, 120 ) ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 50*self.Scale ) + Smoke:SetEndSize( 120*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-1, 1) ) + Smoke:SetAirResistance( 200 ) + Smoke:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Smoke:SetColor( 90,83,68 ) + end + end + + for i=0, 20*self.Scale do // This is the dirt kickup + local Dust = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Dust) then + Dust:SetVelocity( self.DirVec * math.random( 100,700)*self.Scale + VectorRand():GetNormalized()*250*self.Scale ) + Dust:SetDieTime( math.Rand( 0.5 , 1,5 ) ) + Dust:SetStartAlpha( 200 ) + Dust:SetEndAlpha( 0 ) + Dust:SetStartSize( 60*self.Scale ) + Dust:SetEndSize( 90*self.Scale ) + Dust:SetRoll( math.Rand(150, 360) ) + Dust:SetRollDelta( math.Rand(-1, 1) ) + Dust:SetAirResistance( 200 ) + Dust:SetGravity( Vector( 0, 0, math.Rand(-100, -400) ) ) + Dust:SetColor( 90,83,68 ) + end + end + + for i=0, 25*self.Scale do // Chunkage + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(50,900)*self.Scale + VectorRand():GetNormalized() * math.random(0,700)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,8)*self.Scale ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 53,50,45 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + for i=0, 25*self.Scale do // Shrapnel + local Shrapnel = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos+self.DirVec ) + if (Shrapnel) then + Shrapnel:SetVelocity ( (self.DirVec*700*self.Scale) + (VectorRand():GetNormalized() * 1000*self.Scale) ) + Shrapnel:SetDieTime( math.random( 0.3, 0.5) * self.Scale ) + Shrapnel:SetStartAlpha( 255 ) + Shrapnel:SetEndAlpha( 0 ) + Shrapnel:SetStartSize( math.random(4,7)*self.Scale ) + Shrapnel:SetRoll( math.Rand(0, 360) ) + Shrapnel:SetRollDelta( math.Rand(-5, 5) ) + Shrapnel:SetAirResistance( 10 ) + Shrapnel:SetColor( 53,50,45 ) + Shrapnel:SetGravity( Vector( 0, 0, -600) ) + Shrapnel:SetCollide( true ) + Shrapnel:SetBounce( 0.8 ) + end + end + + for i=1,5 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.10 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=0, 10*self.Scale do + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( self.DirVec * math.random( 30,120*self.Scale) + VectorRand():GetNormalized() * math.random( 50,100*self.Scale) ) + Smoke:SetDieTime( math.Rand( 0.5 , 1 )*self.Scale ) + Smoke:SetStartAlpha( math.Rand( 80, 100 ) ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 10*self.Scale ) + Smoke:SetEndSize( 30*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 100 ) + Smoke:SetGravity( Vector( math.random(-20,20)*self.Scale, math.random(-20,20)*self.Scale, 250 ) ) + Smoke:SetColor( 90,83,68 ) + end + end + + + for i=0, 5*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 300,600*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 30, 40 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 150,150,150 ) + end + end + + + local Density = 40*self.Scale /// This part is for the dust ring /// + local Angle = self.DirVec:Angle() + for i=0, Density do + Angle:RotateAroundAxis(Angle:Forward(), (360/Density)) + local ShootVector = Angle:Up() + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( ShootVector * math.Rand(50,700*self.Scale) ) + Smoke:SetDieTime( math.Rand( 1 , 4 )*self.Scale ) + Smoke:SetStartAlpha( math.Rand( 90, 120 ) ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 40*self.Scale ) + Smoke:SetEndSize( 70*self.Scale ) + Smoke:SetRoll( math.Rand(0, 360) ) + Smoke:SetRollDelta( math.Rand(-1, 1) ) + Smoke:SetAirResistance( 200 ) + Smoke:SetGravity( Vector( math.Rand( -200 , 200 ), math.Rand( -200 , 200 ), math.Rand( 10 , 100 ) ) ) + Smoke:SetColor( 90,83,68 ) + end + end + end + + function EFFECT:Metal() + sound.Play( "Bullet.Impact", self.Pos) + + for i=1,5 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=0, 30*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,1000*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 50, 70 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 120,120,120 ) + end + end + + for i=0, 30*self.Scale do + local Sparks = self.Emitter:Add( "effects/spark", self.Pos ) + if (Sparks) then + Sparks:SetVelocity( ((self.DirVec*0.75)+VectorRand()) * math.Rand(200, 600)*self.Scale ) + Sparks:SetDieTime( math.Rand(0.3, 1) ) + Sparks:SetStartAlpha( 255 ) + Sparks:SetStartSize( math.Rand(7, 15)*self.Scale ) + Sparks:SetEndSize( 0 ) + Sparks:SetRoll( math.Rand(0, 360) ) + Sparks:SetRollDelta( math.Rand(-5, 5) ) + Sparks:SetAirResistance( 20 ) + Sparks:SetGravity( Vector( 0, 0, -600 ) ) + end + end + + for i=0, 10*self.Scale do + local Sparks = self.Emitter:Add( "effects/yellowflare", self.Pos ) + if (Sparks) then + Sparks:SetVelocity( VectorRand() * math.Rand(200, 600)*self.Scale ) + Sparks:SetDieTime( math.Rand(1, 1.7) ) + Sparks:SetStartAlpha( 200 ) + Sparks:SetStartSize( math.Rand(10, 13)*self.Scale ) + Sparks:SetEndSize( 0 ) + Sparks:SetRoll( math.Rand(0, 360) ) + Sparks:SetRollDelta( math.Rand(-5, 5) ) + Sparks:SetAirResistance( 100 ) + Sparks:SetGravity( Vector( 0, 0, -60 ) ) + end + end + +end + + + function EFFECT:Smoke() + sound.Play( "Bullet.Impact", self.Pos) + + for i=1,5 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=0, 30*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,1200*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 35, 50 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 120,120,120 ) + end + end + + + for i=1, 25*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_tile"..math.random(1,2), self.Pos ) + if (Debris) then + Debris:SetVelocity ( self.DirVec * math.random(100,600)*self.Scale + VectorRand():GetNormalized() * math.random(100,1200)*self.Scale ) + Debris:SetDieTime( math.random( 1, 3) * self.Scale ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(5,10)*self.Scale) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 40 ) + Debris:SetColor( 70,70,70 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end + + local Angle = self.DirVec:Angle() + + for i = 1, self.DebrizzlemyNizzle do /// This part makes the trailers /// + Angle:RotateAroundAxis(Angle:Forward(), (360/self.DebrizzlemyNizzle)) + local DustRing = Angle:Up() + local RanVec = self.DirVec*math.Rand(1, 4) + (DustRing*math.Rand(3, 4)) + + for k = 3, self.Particles do + local Rcolor = math.random(-20,20) + + local particle1 = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + particle1:SetVelocity((VectorRand():GetNormalized()*math.Rand(1, 2) * self.Size) + (RanVec*self.Size*k*3.5)) + particle1:SetDieTime( math.Rand( 0, 3 )*self.Scale ) + + particle1:SetStartAlpha( math.Rand( 90, 100 ) ) + particle1:SetEndAlpha(0) + particle1:SetGravity((VectorRand():GetNormalized()*math.Rand(5, 10)* self.Size) + Vector(0,0,-50)) + particle1:SetAirResistance( 200+self.Scale*20 ) + particle1:SetStartSize( (5*self.Size)-((k/self.Particles)*self.Size*3) ) + particle1:SetEndSize( (20*self.Size)-((k/self.Particles)*self.Size) ) + particle1:SetRoll( math.random( -500, 500 )/100 ) + + particle1:SetRollDelta( math.random( -0.5, 0.5 ) ) + particle1:SetColor( 90+Rcolor,85+Rcolor,75+Rcolor ) + end + end +end + + function EFFECT:Wood() + + for i=1,5 do + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=0, 30*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,1000)*self.Scale ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 70, 90 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 90,85,75 ) + end + end + + for i=0, 20*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_wood"..math.random(1,2), self.Pos+self.DirVec ) + if (Debris) then + Debris:SetVelocity( self.DirVec * math.random(50,500)*self.Scale + VectorRand():GetNormalized() * math.random(200,900)*self.Scale ) + Debris:SetDieTime( math.random( 0.75, 2) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(10,15)*self.Scale ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 70 ) + Debris:SetColor( 90,85,75 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + end + end +end + + function EFFECT:Glass() + + for i=1,5 do // Blast flash + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*200 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=0, 30*self.Scale do + local Debris = self.Emitter:Add( "effects/fleck_glass"..math.random(1,3), self.Pos ) + if (Debris) then + Debris:SetVelocity ( VectorRand():GetNormalized() * math.random(100,600)*self.Scale ) + Debris:SetDieTime( math.random( 1, 2.5) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( math.random(3,7)*self.Scale ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-15, 15) ) + Debris:SetAirResistance( 50 ) + Debris:SetColor( 200,200,200 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + Debris:SetCollide( true ) + Debris:SetBounce( 0.5 ) + end + end + + + for i=0, 30*self.Scale do + local Whisp = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Whisp) then + Whisp:SetVelocity(VectorRand():GetNormalized() * math.random( 200,800*self.Scale) ) + Whisp:SetDieTime( math.Rand( 4 , 10 )*self.Scale/2 ) + Whisp:SetStartAlpha( math.Rand( 35, 50 ) ) + Whisp:SetEndAlpha( 0 ) + Whisp:SetStartSize( 70*self.Scale ) + Whisp:SetEndSize( 100*self.Scale ) + Whisp:SetRoll( math.Rand(150, 360) ) + Whisp:SetRollDelta( math.Rand(-2, 2) ) + Whisp:SetAirResistance( 300 ) + Whisp:SetGravity( Vector( math.random(-40,40)*self.Scale, math.random(-40,40)*self.Scale, 0 ) ) + Whisp:SetColor( 150,150,150 ) + end + end + +end + + function EFFECT:Blood() + for i=0, 30*self.Scale do // If you recieve over 50,000 joules of energy, you become red mist. + local Smoke = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(100,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 2 ) ) + Smoke:SetStartAlpha( 80 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(0, -200) ) ) + Smoke:SetColor( 70,35,35 ) + end + end + + for i=0, 20*self.Scale do // Add some finer details.... + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(200,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 4 ) ) + Smoke:SetStartAlpha( 120 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(-50, -300) ) ) + Smoke:SetColor( 70,35,35 ) + end + end + + for i=1,5 do // Into the flash! + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=1, 20*self.Scale do // Chunkage NOT contained + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos-(self.DirVec*5) ) + if (Debris) then + Debris:SetVelocity ( VectorRand():GetNormalized() * 400*self.Scale ) + Debris:SetDieTime( math.random( 0.3, 0.6) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( 8 ) + Debris:SetEndSize( 9 ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 30 ) + Debris:SetColor( 70,35,35 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + Debris:SetCollide( true ) + Debris:SetBounce( 0.2 ) + end + end + +end + + function EFFECT:YellowBlood() + for i=0, 30*self.Scale do // If you recieve over 50,000 joules of energy, you become red mist. + local Smoke = self.Emitter:Add( "particle/particle_composite", self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(100,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 2 ) ) + Smoke:SetStartAlpha( 80 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(0, -200) ) ) + Smoke:SetColor( 120,120,0 ) + end + end + + for i=0, 20*self.Scale do // Add some finer details.... + local Smoke = self.Emitter:Add( "particle/smokesprites_000"..math.random(1,9), self.Pos ) + if (Smoke) then + Smoke:SetVelocity( VectorRand():GetNormalized()*math.random(200,600)*self.Scale ) + Smoke:SetDieTime( math.Rand( 1 , 4 ) ) + Smoke:SetStartAlpha( 120 ) + Smoke:SetEndAlpha( 0 ) + Smoke:SetStartSize( 30*self.Scale ) + Smoke:SetEndSize( 100*self.Scale ) + Smoke:SetRoll( math.Rand(150, 360) ) + Smoke:SetRollDelta( math.Rand(-2, 2) ) + Smoke:SetAirResistance( 400 ) + Smoke:SetGravity( Vector( math.Rand(-50, 50) * self.Scale, math.Rand(-50, 50) * self.Scale, math.Rand(-50, -300) ) ) + Smoke:SetColor( 120,120,0 ) + end + end + + for i=1,5 do // Into the flash! + local Flash = self.Emitter:Add( "effects/muzzleflash"..math.random(1,4), self.Pos ) + if (Flash) then + Flash:SetVelocity( self.DirVec*100 ) + Flash:SetAirResistance( 200 ) + Flash:SetDieTime( 0.15 ) + Flash:SetStartAlpha( 255 ) + Flash:SetEndAlpha( 0 ) + Flash:SetStartSize( self.Scale*300 ) + Flash:SetEndSize( 0 ) + Flash:SetRoll( math.Rand(180,480) ) + Flash:SetRollDelta( math.Rand(-1,1) ) + Flash:SetColor(255,255,255) + end + end + + for i=1, 20*self.Scale do // Chunkage NOT contained + local Debris = self.Emitter:Add( "effects/fleck_cement"..math.random(1,2), self.Pos-(self.DirVec*5) ) + if (Debris) then + Debris:SetVelocity ( VectorRand():GetNormalized() * 400*self.Scale ) + Debris:SetDieTime( math.random( 0.3, 0.6) ) + Debris:SetStartAlpha( 255 ) + Debris:SetEndAlpha( 0 ) + Debris:SetStartSize( 8 ) + Debris:SetEndSize( 9 ) + Debris:SetRoll( math.Rand(0, 360) ) + Debris:SetRollDelta( math.Rand(-5, 5) ) + Debris:SetAirResistance( 30 ) + Debris:SetColor( 120,120,0 ) + Debris:SetGravity( Vector( 0, 0, -600) ) + Debris:SetCollide( true ) + Debris:SetBounce( 0.2 ) + end + end +end + + +function EFFECT:Think( ) +return false +end + + +function EFFECT:Render() + +end diff --git a/garrysmod/addons/tfa_antitank/lua/effects/rpg_explosion/init.lua b/garrysmod/addons/tfa_antitank/lua/effects/rpg_explosion/init.lua new file mode 100644 index 0000000..adcaec8 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/effects/rpg_explosion/init.lua @@ -0,0 +1,34 @@ + + + +function EFFECT:Init( data ) + + local tr = util.TraceLine( { + start = data:GetOrigin(), + endpos = data:GetOrigin() - Vector(0,0,60), + mask = MASK_SOLID_BRUSHONLY + } ) + + if tr.HitWorld then + ParticleEffect(table.Random({"hd_explosion","hd_explosion","hd_explosion"}), data:GetOrigin(), Angle(0,math.random(0,360),0), nil) + + else + ParticleEffect(table.Random({"hd_explosion","hd_explosion","hd_explosion"}), data:GetOrigin(), Angle(0,math.random(0,360),0), nil) + + end + + sound.Play( "hd/new_grenadeexplo.mp3", data:GetOrigin(), 100, math.random(90,110), 1) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end + + + + + + diff --git a/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/cl_init.lua b/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/cl_init.lua new file mode 100644 index 0000000..69caa36 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/cl_init.lua @@ -0,0 +1,11 @@ +include("shared.lua") + +function ENT:Draw() + if CurTime() > self:GetNWFloat("HideTime", CurTime() + 1) then + self:DrawModel() + end +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/init.lua b/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/init.lua new file mode 100644 index 0000000..38615a4 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/init.lua @@ -0,0 +1,146 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +local LVS = LVS +ENT.Damage = 0 +ENT.Prime = 0.03 +ENT.Delay = 30 +ENT.HideDelay = 0.0 + + +function ENT:Initialize() + local mdl = self:GetModel() + + if not mdl or mdl == "" or mdl == "models/error.mdl" then + self:SetModel("models/Weapons/w_panzerfaust3_sandstorm_projectile.mdl") + end + self.burnout = CurTime() + 2 + self:PhysicsInit(SOLID_VPHYSICS) + --self:PhysicsInitSphere((self:OBBMaxs() - self:OBBMins()):Length() / 4, "metal") + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + end + + self:SetFriction(self.Delay) + self.killtime = CurTime() + self.Delay + self:DrawShadow(true) + self.StartTime = CurTime() + self:EmitSound( "TFA_INS2_RPG7.Loop" ) + self:SetUseType(SIMPLE_USE) + self.HasIdle = true + timer.Simple(0.1, function() + if IsValid(self) then + self:SetOwner() + end + end) + self:SetNWFloat("HideTime",CurTime() + self.HideDelay ) + self.HP = math.random(30, 60) +end + +function ENT:Think() + if self.killtime < CurTime() then + return false + end + local phys = self:GetPhysicsObject() + local force = phys:GetMass() * Vector( 0, 0, -9.80665 ) * 7-- This gives us the force in kg*source_unit/s^2 + local dt = engine.TickInterval() -- The time interval over which the force acts on the object (in seconds) + phys:ApplyForceCenter( force * dt ) -- Multiplying the two gives us the impulse in kg*source_unit/s + self:NextThink(CurTime()) + + return true +end + +local effectdata, shake + +function ENT:Explode() + if not IsValid(self.Owner) then + self:Remove() + + return + end + + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetScale(10) + effectdata:SetMagnitude(5) + util.Effect("lvs_explosion_small", effectdata) + util.Effect("lvs_explosion_small", effectdata) + util.BlastDamage(self, self.Owner, self:GetPos(), 100, 10) + shake = ents.Create("env_shake") + shake:SetOwner(self.Owner) + shake:SetPos(self:GetPos()) + shake:SetKeyValue("amplitude", tostring(20)) -- Power of the shake + shake:SetKeyValue("radius", tostring( 768 ) ) -- Radius of the shake + shake:SetKeyValue("duration", tostring( self.Damage / 800 )) -- Time of shake + shake:SetKeyValue("frequency", "255") -- How har should the screenshake be + shake:SetKeyValue("spawnflags", "4") -- Spawnflags(In Air) + shake:Spawn() + shake:Activate() + shake:Fire("StartShake", "", 0) + self:EmitSound("TFA_INS2_RPG7.2") + local heat = {} + heat.Src = self:GetPos() + heat.Dir = self:GetAngles():Forward() + heat.Spread = Vector(0,0,0) + heat.Force = 20000 + heat.HullSize = 0 + heat.Damage = 8000 + heat.Velocity = 2000 + heat.Attacker = self.Owner + LVS:FireBullet( heat ) + self:Remove() +end + +function ENT:PhysicsCollide(data, phys) + if data.Speed > 60 and CurTime() > self.StartTime + self.Prime then + timer.Simple(0,function() + if IsValid(self) then + self:Explode() + end + end) + else + self.Prime = math.huge + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + self:SetNWFloat("HideTime", -1 ) + end + end + --[[elseif self:GetOwner() ~= self then + self.Prime = math.huge + self:StopSound("TFA_INS2_RPG7.Loop") + self:SetOwner(self) + end + ]]-- +end + +function ENT:OnRemove() + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + end +end + +function ENT:Use(activator, caller) + if activator:IsPlayer() and self.WeaponClass and activator:GetWeapon(self.WeaponClass) then + activator:GiveAmmo(1, activator:GetWeapon(self.WeaponClass):GetPrimaryAmmoType(), false) + self:Remove() + end +end + +function ENT:OnTakeDamage( dmg ) + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + if self.HP > 0 and self.HP - dmg:GetDamage() <= 0 then + self.Exploded = true + self:Explode() + end + self.HP = self.HP - dmg:GetDamage() + dmg:SetAttacker(self) + dmg:SetInflictor(self) + self:TakePhysicsDamage( dmg ) +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/shared.lua b/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/shared.lua new file mode 100644 index 0000000..1d6e140 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/panzerfaust3_rocket/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = "anim" +ENT.PrintName = "Contact Explosive" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/cl_init.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/cl_init.lua new file mode 100644 index 0000000..e7c1ae1 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/cl_init.lua @@ -0,0 +1,11 @@ +include("shared.lua") + +function ENT:Draw() + if CurTime() > self:GetNWFloat("HideTime", 0) then + self:DrawModel() + end +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/init.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/init.lua new file mode 100644 index 0000000..1089e2f --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/init.lua @@ -0,0 +1,229 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +local LVS = LVS + +ENT.Damage = 0 +ENT.Prime = 0.03 +ENT.Delay = 30 +ENT.HideDelay = 0.0 + +function ENT:Initialize() + if not self:GetModel() or self:GetModel() == "" or self:GetModel() == "models/error.mdl" then + self:SetModel("models/weapons/fas2/world/explosives/rpg26/rocket.mdl") + end + + if not self.Owner:IsPlayer() then + self.Owner = NULL + end + + self.burnout = CurTime() + 2 + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + + self:SetFriction(self.Delay) + self.killtime = CurTime() + self.Delay + self.StartTime = CurTime() + + self:EmitSound("TFA_INS2_RPG7.Loop") + self:SetUseType(SIMPLE_USE) + + self.HasIdle = true + self.HP = math.random(30, 60) + + timer.Simple(0.1, function() + if IsValid(self) then + self:SetOwner(self:GetOwner() or NULL) + end + end) + + self:SetNWFloat("HideTime", CurTime() + self.HideDelay) +end + +function ENT:Think() + local ct = CurTime() + if self.killtime < ct then return false end + + local phys = self:GetPhysicsObject() + if not IsValid(phys) then return end + if phys:IsValid() then + phys:ApplyForceCenter(Vector(0, 0, -9.80665 * 7) * phys:GetMass() * engine.TickInterval()) + end + + self:NextThink(ct) + return true +end + +function ENT:Explode() + if self._Exploding then return end + self._Exploding = true + + if not IsValid(self.Owner) then + self:Remove() + return + end + + local tr = util.TraceLine({ + start = pos, + endpos = pos + self:GetAngles():Forward() * 200, + filter = self + }) + + local hit = tr.Entity + + if IsValid(hit) and hit.LVS then + local dmg = DamageInfo() + dmg:SetDamage(250) + dmg:SetDamageType(DMG_BLAST) + dmg:SetAttacker(self.Owner or self) + dmg:SetInflictor(self) + hit:TakeDamageInfo(dmg) + + if hit.GetSubSystem then + local subsys = hit:GetSubSystem(tr.HitPos) + if subsys then + subsys:TakeDamage(300) + end + end + end + + local pos = self:GetPos() + + local effectdata = EffectData() + effectdata:SetOrigin(pos) + effectdata:SetScale(5) + effectdata:SetMagnitude(5) + + util.Effect("HelicopterMegaBomb", effectdata) + util.Effect("Explosion", effectdata) + + util.BlastDamage(self, self.Owner, pos, 100, 10) + + local shake = ents.Create("env_shake") + shake:SetOwner(self.Owner) + shake:SetPos(pos) + shake:SetKeyValue("amplitude", "20") + shake:SetKeyValue("radius", "768") + shake:SetKeyValue("duration", tostring(self.Damage / 800)) + shake:SetKeyValue("frequency", "255") + shake:SetKeyValue("spawnflags", "4") + shake:Spawn() + shake:Activate() + shake:Fire("StartShake", "", 0) + + self:EmitSound("TFA_INS2_RPG7.2") + + LVS:FireBullet({ + Src = pos, + Dir = self:GetAngles():Forward(), + Spread = vector_origin, + Force = 42000, + HullSize = 0, + Damage = 12000, + Velocity = 16000, + Attacker = self.Owner + }) + + local tr = util.TraceHull({ + start = pos, + endpos = pos, + mins = Vector(-80, -80, -80), + maxs = Vector(80, 80, 80), + filter = self + }) + + local hit = tr.Entity + + if IsValid(hit) and hit.LVS then + local dmg = DamageInfo() + dmg:SetDamage(200) + dmg:SetDamageType(DMG_BLAST) + dmg:SetAttacker(self.Owner or self) + dmg:SetInflictor(self) + hit:TakeDamageInfo(dmg) + + if hit.GetSubSystem then + local subsys = hit:GetSubSystem(pos) + + if subsys then + local name = subsys.Name:lower() + + if name:find("engine") then + subsys:SetHP(0) + + elseif name:find("ammo") then + subsys:SetHP(0) + + elseif name:find("fuel") then + subsys:SetHP(0) + + else + subsys:TakeDamage(250) + end + end + end + end + self:Remove() +end + +function ENT:PhysicsCollide(data, phys) + if self._LastCollide and self._LastCollide > CurTime() - 0.05 then return end + self._LastCollide = CurTime() + local ct = CurTime() + + if data.Speed > 60 and ct > self.StartTime + self.Prime then + timer.Simple(0, function() + if IsValid(self) then self:Explode() end + end) + return + end + + self.Prime = math.huge + + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + self:SetNWFloat("HideTime", -1) + end +end + +function ENT:OnRemove() + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + end +end + +function ENT:Use(activator) + if activator:IsPlayer() and self.WeaponClass then + local wep = activator:GetWeapon(self.WeaponClass) + if IsValid(wep) then + activator:GiveAmmo(1, wep:GetPrimaryAmmoType(), false) + self:Remove() + end + end +end + +function ENT:OnTakeDamage(dmg) + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + + local damage = dmg:GetDamage() + self.HP = self.HP - damage + + if self.HP <= 0 then + self.Exploded = true + self:Explode() + return + end + + self:TakePhysicsDamage(dmg) +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/shared.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/shared.lua new file mode 100644 index 0000000..1d6e140 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = "anim" +ENT.PrintName = "Contact Explosive" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/cl_init.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/cl_init.lua new file mode 100644 index 0000000..69caa36 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/cl_init.lua @@ -0,0 +1,11 @@ +include("shared.lua") + +function ENT:Draw() + if CurTime() > self:GetNWFloat("HideTime", CurTime() + 1) then + self:DrawModel() + end +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/init.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/init.lua new file mode 100644 index 0000000..49505b2 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/init.lua @@ -0,0 +1,219 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +local LVS = LVS +ENT.Damage = 0 +ENT.Prime = 0.03 +ENT.Delay = 30 +ENT.HideDelay = 5 +ENT.FireRadius = 600 -- Общий радиус распространения пожара +ENT.FireDuration = 10 -- Длительность пожара в секундах +ENT.FireCount = 20 -- Количество отдельных источников огня +ENT.MaxFireDistance = 200 -- Максимальное расстояние от центра взрыва для источников огня + + +function ENT:Initialize() + local mdl = self:GetModel() + + if not mdl or mdl == "" or mdl == "models/error.mdl" then + self:SetModel("models/weapons/fas2/world/explosives/rpg26/rocket.mdl") + end + self.burnout = CurTime() + 2 + self:PhysicsInit(SOLID_VPHYSICS) + --self:PhysicsInitSphere((self:OBBMaxs() - self:OBBMins()):Length() / 4, "metal") + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + end + + self:SetFriction(self.Delay) + self.killtime = CurTime() + self.Delay + self:DrawShadow(true) + self.StartTime = CurTime() + self:EmitSound( "TFA_INS2_RPG7.Loop" ) + self:SetUseType(SIMPLE_USE) + self.HasIdle = true + timer.Simple(0.1, function() + if IsValid(self) then + self:SetOwner() + end + end) + self:SetNWFloat("HideTime",CurTime() + self.HideDelay ) + self.HP = math.random(30, 60) +end + +function ENT:Think() + if self.killtime < CurTime() then + return false + end + local phys = self:GetPhysicsObject() + local force = phys:GetMass() * Vector( 0, 0, -9.80665 ) * 7-- This gives us the force in kg*source_unit/s^2 + local dt = engine.TickInterval() -- The time interval over which the force acts on the object (in seconds) + phys:ApplyForceCenter( force * dt ) -- Multiplying the two gives us the impulse in kg*source_unit/s + self:NextThink(CurTime()) + + return true +end + +local effectdata, shake + +function ENT:CreateFire() + -- Создаем несколько источников огня в случайных позициях внутри радиуса + for i = 1, self.FireCount do + -- Вычисляем случайную позицию в радиусе взрыва + local angle = math.random(0, 360) + local distance = math.random(0, self.MaxFireDistance) + local rad = math.rad(angle) + + local offset = Vector( + math.cos(rad) * distance, + math.sin(rad) * distance, + 0 + ) + + local firePos = self:GetPos() + offset + + -- Проверяем, что позиция не находится внутри объекта + local trace = util.TraceLine({ + start = self:GetPos(), + endpos = firePos, + mask = MASK_SOLID_BRUSHONLY + }) + + + -- Создаем отдельный источник огня + local fire = ents.Create("env_fire") + if IsValid(fire) then + fire:SetPos(firePos) + + -- Размер каждого отдельного огня (меньше общего радиуса) + local individualFireSize = math.random(10, 40) + + fire:SetKeyValue("health", self.FireDuration + math.random(-5, 5)) -- Случайная длительность + fire:SetKeyValue("firesize", individualFireSize) + fire:SetKeyValue("fireattack", "5") + fire:SetKeyValue("damagescale", "20") + fire:SetKeyValue("spawnflags", "130") -- Бесконечный огонь + дым + старт сразу + fire:Spawn() + fire:Fire("StartFire", "", 0) + + -- Автоматическое удаление огня после заданного времени + timer.Simple(self.FireDuration + math.random(-2, 2), function() + if IsValid(fire) then + fire:Remove() + end + end) + end + end + + + local centerFire = ents.Create("env_fire") + if IsValid(centerFire) then + centerFire:SetPos(self:GetPos()) + centerFire:SetKeyValue("health", self.FireDuration) + centerFire:SetKeyValue("firesize", 150) + centerFire:SetKeyValue("fireattack", "10") + centerFire:SetKeyValue("damagescale", "20") + centerFire:SetKeyValue("spawnflags", "130") + centerFire:Spawn() + centerFire:Fire("StartFire", "", 0) + + timer.Simple(self.FireDuration, function() + if IsValid(centerFire) then + centerFire:Remove() + end + end) + end +end + +function ENT:Explode() + if not IsValid(self.Owner) then + self:Remove() + return + end + + effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetScale(5) + effectdata:SetMagnitude(5) + util.Effect("lvs_explosion_small", effectdata) + util.Effect("lvs_explosion_small", effectdata) + util.BlastDamage(self, self.Owner, self:GetPos(), 100, 10) + shake = ents.Create("env_shake") + shake:SetOwner(self.Owner) + shake:SetPos(self:GetPos()) + shake:SetKeyValue("amplitude", tostring(20)) -- Power of the shake + shake:SetKeyValue("radius", tostring( 768 ) ) -- Radius of the shake + shake:SetKeyValue("duration", tostring( self.Damage / 800 )) -- Time of shake + shake:SetKeyValue("frequency", "255") -- How har should the screenshake be + shake:SetKeyValue("spawnflags", "4") -- Spawnflags(In Air) + shake:Spawn() + shake:Activate() + shake:Fire("StartShake", "", 0) + self:EmitSound("TFA_INS2_RPG7.2") + local he = {} + he.Src = self:GetPos() + he.Dir = self:GetAngles():Forward() + he.Spread = Vector(0,0,0) + he.Force = 100 + he.HullSize = 0 + he.Damage = 700 + he.SplashDamage = 200 + he.SplashDamageRadius = 250 + he.Velocity = 300 + he.TracerName = "lvs_tracer_autocannon" -- Изменено на нужный трассер + he.Attacker = self.Owner + LVS:FireBullet( heat ) + + -- Создаем пожар после взрыва + self:CreateFire() + + self:Remove() +end + +function ENT:PhysicsCollide(data, phys) + if data.Speed > 60 and CurTime() > self.StartTime + self.Prime then + timer.Simple(0,function() + if IsValid(self) then + self:Explode() + end + end) + else + self.Prime = math.huge + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + self:SetNWFloat("HideTime", -1 ) + end + end +end + +function ENT:OnRemove() + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + end +end + +function ENT:Use(activator, caller) + if activator:IsPlayer() and self.WeaponClass and activator:GetWeapon(self.WeaponClass) then + activator:GiveAmmo(1, activator:GetWeapon(self.WeaponClass):GetPrimaryAmmoType(), false) + self:Remove() + end +end + +function ENT:OnTakeDamage( dmg ) + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + if self.HP > 0 and self.HP - dmg:GetDamage() <= 0 then + self.Exploded = true + self:Explode() + end + self.HP = self.HP - dmg:GetDamage() + dmg:SetAttacker(self) + dmg:SetInflictor(self) + self:TakePhysicsDamage( dmg ) +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/shared.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/shared.lua new file mode 100644 index 0000000..1d6e140 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg26_rocket_fire/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = "anim" +ENT.PrintName = "Contact Explosive" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/cl_init.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/cl_init.lua new file mode 100644 index 0000000..69caa36 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/cl_init.lua @@ -0,0 +1,11 @@ +include("shared.lua") + +function ENT:Draw() + if CurTime() > self:GetNWFloat("HideTime", CurTime() + 1) then + self:DrawModel() + end +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/init.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/init.lua new file mode 100644 index 0000000..53997e7 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/init.lua @@ -0,0 +1,146 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +local LVS = LVS +ENT.Damage = 0 +ENT.Prime = 0.03 +ENT.Delay = 30 +ENT.HideDelay = 0.0 + + +function ENT:Initialize() + local mdl = self:GetModel() + + if not mdl or mdl == "" or mdl == "models/error.mdl" then + self:SetModel("models/weapons/rpg28_rocket.mdl") + end + self.burnout = CurTime() + 2 + self:PhysicsInit(SOLID_VPHYSICS) + --self:PhysicsInitSphere((self:OBBMaxs() - self:OBBMins()):Length() / 4, "metal") + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + end + + self:SetFriction(self.Delay) + self.killtime = CurTime() + self.Delay + self:DrawShadow(true) + self.StartTime = CurTime() + self:EmitSound( "TFA_INS2_RPG7.Loop" ) + self:SetUseType(SIMPLE_USE) + self.HasIdle = true + timer.Simple(0.1, function() + if IsValid(self) then + self:SetOwner() + end + end) + self:SetNWFloat("HideTime",CurTime() + self.HideDelay ) + self.HP = math.random(30, 60) +end + +function ENT:Think() + if self.killtime < CurTime() then + return false + end + local phys = self:GetPhysicsObject() + local force = phys:GetMass() * Vector( 0, 0, -9.80665 ) * 7-- This gives us the force in kg*source_unit/s^2 + local dt = engine.TickInterval() -- The time interval over which the force acts on the object (in seconds) + phys:ApplyForceCenter( force * dt ) -- Multiplying the two gives us the impulse in kg*source_unit/s + self:NextThink(CurTime()) + + return true +end + +local effectdata, shake + +function ENT:Explode() + if not IsValid(self.Owner) then + self:Remove() + + return + end + + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetScale(10) + effectdata:SetMagnitude(5) + util.Effect("lvs_explosion_small", effectdata) + util.Effect("lvs_explosion_small", effectdata) + util.BlastDamage(self, self.Owner, self:GetPos(), 100, 10) + shake = ents.Create("env_shake") + shake:SetOwner(self.Owner) + shake:SetPos(self:GetPos()) + shake:SetKeyValue("amplitude", tostring(20)) -- Power of the shake + shake:SetKeyValue("radius", tostring( 768 ) ) -- Radius of the shake + shake:SetKeyValue("duration", tostring( self.Damage / 800 )) -- Time of shake + shake:SetKeyValue("frequency", "255") -- How har should the screenshake be + shake:SetKeyValue("spawnflags", "4") -- Spawnflags(In Air) + shake:Spawn() + shake:Activate() + shake:Fire("StartShake", "", 0) + self:EmitSound("TFA_INS2_RPG7.2") + local heat = {} + heat.Src = self:GetPos() + heat.Dir = self:GetAngles():Right() + heat.Spread = Vector(0,0,0) + heat.Force = 18000 + heat.HullSize = 0 + heat.Damage = 8000 + heat.Velocity = 16000 + heat.Attacker = self.Owner + LVS:FireBullet( heat ) + self:Remove() +end + +function ENT:PhysicsCollide(data, phys) + if data.Speed > 60 and CurTime() > self.StartTime + self.Prime then + timer.Simple(0,function() + if IsValid(self) then + self:Explode() + end + end) + else + self.Prime = math.huge + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + self:SetNWFloat("HideTime", -1 ) + end + end + --[[elseif self:GetOwner() ~= self then + self.Prime = math.huge + self:StopSound("TFA_INS2_RPG7.Loop") + self:SetOwner(self) + end + ]]-- +end + +function ENT:OnRemove() + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + end +end + +function ENT:Use(activator, caller) + if activator:IsPlayer() and self.WeaponClass and activator:GetWeapon(self.WeaponClass) then + activator:GiveAmmo(1, activator:GetWeapon(self.WeaponClass):GetPrimaryAmmoType(), false) + self:Remove() + end +end + +function ENT:OnTakeDamage( dmg ) + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + if self.HP > 0 and self.HP - dmg:GetDamage() <= 0 then + self.Exploded = true + self:Explode() + end + self.HP = self.HP - dmg:GetDamage() + dmg:SetAttacker(self) + dmg:SetInflictor(self) + self:TakePhysicsDamage( dmg ) +end diff --git a/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/shared.lua b/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/shared.lua new file mode 100644 index 0000000..1d6e140 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/entities/rpg28_rocket/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = "anim" +ENT.PrintName = "Contact Explosive" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true diff --git a/garrysmod/addons/tfa_antitank/lua/tfa/external/at4.lua b/garrysmod/addons/tfa_antitank/lua/tfa/external/at4.lua new file mode 100644 index 0000000..1908b78 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/tfa/external/at4.lua @@ -0,0 +1,28 @@ +local path = "weapons/at4/" + +TFA.AddFireSound("AT4_FIRE", path .. "at4_fp.wav", true, ")" ) +TFA.AddWeaponSound("Universal.Draw", {"weapons/universal/uni_weapon_draw_01.wav", "weapons/universal/uni_weapon_draw_02.wav", "weapons/universal/uni_weapon_draw_03.wav"}) +TFA.AddWeaponSound("Universal.LeanOut", { "weapons/universal/uni_lean_out_01.wav", "weapons/universal/uni_lean_out_02.wav", "weapons/universal/uni_lean_out_03.wav", "weapons/universal/uni_lean_out_04.wav"}) +TFA.AddWeaponSound("Universal.WeaponLower", "weapons/universal/uni_weapon_lower_01.wav") +TFA.AddWeaponSound("Universal.Holster", "weapons/universal/uni_weapon_holster.wav") +TFA.AddWeaponSound("Weapon_M9.safety", "weapons/m9/handling/m9_safety.wav") +TFA.AddWeaponSound("Weapon_AT4.Latch_01", path .. "/handling/at4_latch_01.wav") +TFA.AddWeaponSound("Weapon_AT4.Latch_02", path .. "/handling/at4_latch_02.wav") +TFA.AddWeaponSound("Weapon_AT4.Ready", path .. "/handling/at4_ready.wav") +TFA.AddWeaponSound("Weapon_AT4.Shoulder", path .. "/handling/at4_shoulder.wav") + +TFA.AddAmmo( "at4", "AT4" ) +TFA.AddAmmo( "at4armor", "AT4ARMOR" ) +TFA.AddAmmo( "at4strc", "AT4STRC" ) +--[[ + +TFA.AddWeaponSound("TFA_INS2_RPG7.Empty", path .. "ak74_empty.wav") + +TFA.AddWeaponSound("TFA_INS2_RPG7.MagRelease", path .. "ak74_magrelease.wav") +TFA.AddWeaponSound("TFA_INS2_RPG7.Magout", path .. "ak74_magout.wav") +TFA.AddWeaponSound("TFA_INS2_RPG7.Rattle", path .. "ak74_rattle.wav") +TFA.AddWeaponSound("TFA_INS2_RPG7.Magin", path .. "ak74_magin.wav") +TFA.AddWeaponSound("TFA_INS2_RPG7.Boltback", path .. "ak74_boltback.wav") +TFA.AddWeaponSound("TFA_INS2_RPG7.Boltrelease", path .. "ak74_boltrelease.wav") + +]]-- diff --git a/garrysmod/addons/tfa_antitank/lua/tfa/external/panzerfaust3_xd_killicon.lua b/garrysmod/addons/tfa_antitank/lua/tfa/external/panzerfaust3_xd_killicon.lua new file mode 100644 index 0000000..dcd5198 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/tfa/external/panzerfaust3_xd_killicon.lua @@ -0,0 +1,5 @@ +local hudcolor = Color(255, 255, 255, 255) + +if killicon and killicon.Add then + killicon.Add("tfa_ins2_panzerfaust3", "vgui/hud/tfa_ins2_panzerfaust3", hudcolor) +end diff --git a/garrysmod/addons/tfa_antitank/lua/tfa/external/weapon_sw_rpg7_sounds.lua b/garrysmod/addons/tfa_antitank/lua/tfa/external/weapon_sw_rpg7_sounds.lua new file mode 100644 index 0000000..42896be --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/tfa/external/weapon_sw_rpg7_sounds.lua @@ -0,0 +1,20 @@ +local path = "weapons/tfa_ins2_rpg7_scoped/" +local pref = "TFA_SCOPED_RPG7" +local hudcolor = Color(255, 255, 255, 255) +if (CLIENT) then + game.AddParticles( "particles/hd_explosions.pcf" ) +end +PrecacheParticleSystem("hd_explosion") +TFA.AddFireSound(pref .. ".1", path .. "rhino_1.wav", false, ")") + +TFA.AddWeaponSound(pref .. ".Fetch", path .. "rpg7_fetch.wav") +TFA.AddWeaponSound(pref .. ".Load1", path .. "rpg7_load1.wav") +TFA.AddWeaponSound(pref .. ".Load2", path .. "rpg7_load2.wav") +TFA.AddWeaponSound(pref .. ".EndGrab", path .. "rpg7_endgrab.wav") +TFA.AddWeaponSound(pref .. ".safety", path .. "m9_safety.wav") + +if killicon and killicon.Add then + killicon.Add("tfa_anti_tank", "vgui/hud/tfa_anti_tank", hudcolor) + killicon.Add("tfa_crocket_rpg", "vgui/hud/tfa_crocket_rpg", hudcolor) + killicon.Add("tfa_ins2_rpg7_scoped", "vgui/hud/tfa_ins2_rpg7_scoped", hudcolor) +end diff --git a/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_at4/shared.lua b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_at4/shared.lua new file mode 100644 index 0000000..413e52c --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_at4/shared.lua @@ -0,0 +1,601 @@ + +-- Copyright (c) 2018-2020 TFA Base Devs + +-- 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. + +SWEP.Base = "tfa_gun_base" +SWEP.Category = "FT | Antitank" --The category. Please, just choose something generic or something I've already done if you plan on only doing like one swep.. +SWEP.Manufacturer = "FT_Scripty" --Gun Manufactrer (e.g. Hoeckler and Koch ) +SWEP.Author = "Shtormer, Scripty" --Author Tooltip +SWEP.Contact = "" --Contact Info Tooltip +SWEP.Purpose = "" --Purpose Tooltip +SWEP.Instructions = "" --Instructions Tooltip +SWEP.Spawnable = true --Can you, as a normal user, spawn this? +SWEP.AdminSpawnable = true --Can an adminstrator spawn this? Does not tie into your admin mod necessarily, unless its coded to allow for GMod's default ranks somewhere in its code. Evolve and ULX should work, but try to use weapon restriction rather than these. +SWEP.DrawCrosshair = true -- Draw the crosshair? +SWEP.DrawCrosshairIS = true --Draw the crosshair in ironsights? +SWEP.PrintName = "AT4" -- Weapon name (Shown on HUD) +SWEP.Slot = 5 -- Slot in the weapon selection menu. Subtract 1, as this starts at 0. +SWEP.SlotPos = 73 -- Position in the slot +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.Weight = 30 -- This controls how "good" the weapon is for autopickup. + +--[[WEAPON HANDLING]]-- +SWEP.Primary.Sound = Sound("weapons/at4/at4_fp.wav") +SWEP.Primary.SilencedSound = nil -- This is the sound of the weapon, when silenced. +SWEP.Primary.PenetrationMultiplier = 1 --Change the amount of something this gun can penetrate through +SWEP.Primary.Damage = 500 -- Damage, in standard damage points. +SWEP.Primary.DamageTypeHandled = true --true will handle damagetype in base +SWEP.Primary.DamageType = nil --See DMG enum. This might be DMG_SHOCK, DMG_BURN, DMG_BULLET, etc. Leave nil to autodetect. DMG_AIRBOAT opens doors. +SWEP.Primary.Force = nil --Force value, leave nil to autocalc +SWEP.Primary.Knockback = 1 --Autodetected if nil; this is the velocity kickback +SWEP.Primary.HullSize = 0 --Big bullets, increase this value. They increase the hull size of the hitscan bullet. +SWEP.Primary.NumShots = 1 --The number of shots the weapon fires. SWEP.Shotgun is NOT required for this to be >1. +SWEP.Primary.Automatic = false -- Automatic/Semi Auto +SWEP.Primary.RPM = 120 -- This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Displayed = 15 -- This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Semi = nil -- RPM for semi-automatic or burst fire. This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Burst = nil -- RPM for burst fire, overrides semi. This is in Rounds Per Minute / RPM +SWEP.Primary.DryFireDelay = nil --How long you have to wait after firing your last shot before a dryfire animation can play. Leave nil for full empty attack length. Can also use SWEP.StatusLength[ ACT_VM_BLABLA ] +SWEP.Primary.BurstDelay = nil -- Delay between bursts, leave nil to autocalculate + +SWEP.Primary.LoopSound = nil -- Looped fire sound, unsilenced +SWEP.Primary.LoopSoundSilenced = nil -- Looped fire sound, silenced +SWEP.Primary.LoopSoundTail = nil -- Loop end/tail sound, unsilenced +SWEP.Primary.LoopSoundTailSilenced = nil -- Loop end/tail sound, silenced +SWEP.Primary.LoopSoundAutoOnly = false -- Play loop sound for full-auto only? Fallbacks to Primary.Sound for semi/burst if true + +SWEP.CanJam = false -- whenever weapon cam jam +SWEP.JamChance = 0.04 -- the (maximal) chance the weapon will jam. Newly spawned weapon will never jam on first shot for example. +-- Default value is 0.04 (4%) +-- Maxmial value is 1, means weapon will always jam when factor become 100 +-- Also remember that there is a minimal factor before weapon can jam +-- This number is not treated "as-is" but as basic value that needs to be concluded as chance +-- You don't really need to cry over it and trying to balance it, TFA Base will do the job for you +-- (TFA Base will calculate the best value between 0 and JamChance based on current JamFactor of the weapon) +SWEP.JamFactor = 0.06 -- How to increase jam factor after each shot. +-- When factor reach 100 it will mean that on each shot there will be SWEP.Primary.JamChance chance to jam +-- When factor reach 50 it will mean that on each shot there will be SWEP.Primary.JamChance / 2 chance to jam +-- and so on +-- Default value is 0.06, means weapon will jam with SWEP.Primary.JamChance chance right after 1666 shots + +-- These settings are good for Assault Rifles, however, not good for anything else. +-- Suggested stats: + +--[[ +-- Pistols +SWEP.JamChance = 0.20 +SWEP.JamFactor = 0.14 +]] + +--[[ +-- Revolvers +SWEP.JamChance = 0.17 +SWEP.JamFactor = 0.50 +]] + +--[[ +-- Miniguns +SWEP.JamChance = 0.03 +SWEP.JamFactor = 0.01 +]] + +--[[ +-- Submachine gun +SWEP.JamChance = 0.04 +SWEP.JamFactor = 0.09 +]] + +--[[ +-- Auto shotguns +SWEP.JamChance = 0.15 +SWEP.JamFactor = 0.2 +]] + +--[[ +-- Pump-action shotguns +SWEP.JamChance = 0.25 +SWEP.JamFactor = 0.3 +]] + +--[[ +-- Sniper rifle +SWEP.JamChance = 0.17 +SWEP.JamFactor = 0.35 +]] + +SWEP.FiresUnderwater = false +--Miscelaneous Sounds +SWEP.IronInSound = nil --Sound to play when ironsighting in? nil for default +SWEP.IronOutSound = nil --Sound to play when ironsighting out? nil for default +--Silencing +SWEP.CanBeSilenced = false --Can we silence? Requires animations. +SWEP.Silenced = false --Silenced by default? +-- Selective Fire Stuff +SWEP.SelectiveFire = false --Allow selecting your firemode? +SWEP.DisableBurstFire = false --Only auto/single? +SWEP.OnlyBurstFire = false --No auto, only burst/single? +SWEP.BurstFireCount = nil -- Burst fire count override (autocalculated by the clip size if nil) +SWEP.DefaultFireMode = "safe" --Default to auto or whatev +SWEP.FireModeName = nil --Change to a text value to override it +SWEP.FireSoundAffectedByClipSize = true -- Whenever adjuct pitch (and proably other properties) of fire sound based on current clip / maxclip +-- This is always false when either: +-- Weapon has no primary clip +-- Weapon's clip is smaller than 4 rounds +-- Weapon is a shotgun +--Ammo Related +SWEP.Primary.ClipSize = 1 -- This is the size of a clip +SWEP.Primary.ClipSize = 1 -- This is the size of a clip +SWEP.Primary.DefaultClip = 1 -- This is the number of bullets the gun gives you, counting a clip as defined directly above. +SWEP.Primary.Ammo = "at4" -- What kind of ammo. Options, besides custom, include pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, and AirboatGun. +SWEP.Primary.AmmoConsumption = 1 --Ammo consumed per shot +--Pistol, buckshot, and slam like to ricochet. Use AirboatGun for a light metal peircing shotgun pellets +SWEP.DisableChambering = true --Disable round-in-the-chamber +--Recoil Related +SWEP.Primary.KickUp = 0 -- This is the maximum upwards recoil (rise) +SWEP.Primary.KickDown = 0 -- This is the maximum downwards recoil (skeet) +SWEP.Primary.KickHorizontal = 0 -- This is the maximum sideways recoil (no real term) +SWEP.Primary.StaticRecoilFactor = 0.5 --Amount of recoil to directly apply to EyeAngles. Enter what fraction or percentage (in decimal form) you want. This is also affected by a convar that defaults to 0.5. +--Firing Cone Related +SWEP.Primary.Spread = .01 --This is hip-fire acuracy. Less is more (1 is horribly awful, .0001 is close to perfect) +SWEP.Primary.IronAccuracy = .005 -- Ironsight accuracy, should be the same for shotguns +--Unless you can do this manually, autodetect it. If you decide to manually do these, uncomment this block and remove this line. +SWEP.Primary.SpreadMultiplierMax = nil--How far the spread can expand when you shoot. Example val: 2.5 +SWEP.Primary.SpreadIncrement = nil --What percentage of the modifier is added on, per shot. Example val: 1/3.5 +SWEP.Primary.SpreadRecovery = nil--How much the spread recovers, per second. Example val: 3 +--Range Related +SWEP.Primary.Range = -1 -- The distance the bullet can travel in source units. Set to -1 to autodetect based on damage/rpm. +SWEP.Primary.RangeFalloff = -1 -- The percentage of the range the bullet damage starts to fall off at. Set to 0.8, for example, to start falling off after 80% of the range. +--Penetration Related +SWEP.MaxPenetrationCounter = 4 --The maximum number of ricochets. To prevent stack overflows. +--Misc +SWEP.IronRecoilMultiplier = 0.5 --Multiply recoil by this factor when we're in ironsights. This is proportional, not inversely. +SWEP.CrouchAccuracyMultiplier = 0.5 --Less is more. Accuracy * 0.5 = Twice as accurate, Accuracy * 0.1 = Ten times as accurate +--Movespeed +SWEP.MoveSpeed = 1 --Multiply the player's movespeed by this. +SWEP.IronSightsMoveSpeed = 0.8 --Multiply the player's movespeed by this when sighting. +--[[PROJECTILES]]-- +SWEP.Primary.ProjectileDelay = 10 / 1000 --Entity to shoot +SWEP.ProjectileEntity = "at4_rocket" --Entity to shoot +SWEP.Primary.ProjectileVelocity = 294 * 100 --Entity to shoot's velocity +SWEP.Primary.ProjectileModel = nil --Entity to shoot's model +--[[VIEWMODEL]]-- +SWEP.ViewModel = "models/weapons/at4genius_antipersonel/v_at4.mdl" --Viewmodel path +SWEP.ViewModelFOV = 65 -- This controls how big the viewmodel looks. Less is more. +SWEP.ViewModelFlip = false -- Set this to true for CSS models, or false for everything else (with a righthanded viewmodel.) +SWEP.UseHands = true --Use gmod c_arms system. +SWEP.VMPos = Vector(0,0,0) --The viewmodel positional offset, constantly. Subtract this from any other modifications to viewmodel position. +SWEP.VMAng = Vector(0,0,0) --The viewmodel angular offset, constantly. Subtract this from any other modifications to viewmodel angle. +SWEP.VMPos_Additive = true --Set to false for an easier time using VMPos. If true, VMPos will act as a constant delta ON TOP OF ironsights, run, whateverelse +SWEP.CenteredPos = nil --The viewmodel positional offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.CenteredAng = nil --The viewmodel angular offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.Bodygroups_V = nil --{ + --[0] = 1, + --[1] = 4, + --[2] = etc. +--} +SWEP.AllowIronSightsDoF = true -- whenever allow DoF effect on viewmodel when zoomed in with iron sights +--[[WORLDMODEL]]-- +SWEP.WorldModel = "models/weapons/personel.mdl" -- Weapon world model path +SWEP.Bodygroups_W = nil --{ +--[0] = 1, +--[1] = 4, +--[2] = etc. +--} +SWEP.HoldType = "rpg" -- This is how others view you carrying the weapon. Options include: +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- You're mostly going to use ar2, smg, shotgun or pistol. rpg and crossbow make for good sniper rifles +SWEP.Offset = { + Pos = { + Up = -5, + Right = 1, + Forward = 8 + }, + Ang = { + Up = 0, + Right = -10, + Forward = 180 + }, + Scale = 1 +} --Procedural world model animation, defaulted for CS:S purposes. +SWEP.ThirdPersonReloadDisable = false --Disable third person reload? True disables. +--[[SCOPES]]-- +SWEP.IronSightsSensitivity = 1 --Useful for a RT scope. Change this to 0.25 for 25% sensitivity. This is if normal FOV compenstaion isn't your thing for whatever reason, so don't change it for normal scopes. +SWEP.BoltAction = false --Unscope/sight after you shoot? +SWEP.Scoped = false --Draw a scope overlay? +SWEP.ScopeOverlayThreshold = 0.875 --Percentage you have to be sighted in to see the scope. +SWEP.BoltTimerOffset = 0.25 --How long you stay sighted in after shooting, with a bolt action. +SWEP.ScopeScale = 0.5 --Scale of the scope overlay +SWEP.ReticleScale = 0.7 --Scale of the reticle overlay +--GDCW Overlay Options. Only choose one. +SWEP.Secondary.UseACOG = false --Overlay option +SWEP.Secondary.UseMilDot = false --Overlay option +SWEP.Secondary.UseSVD = false --Overlay option +SWEP.Secondary.UseParabolic = false --Overlay option +SWEP.Secondary.UseElcan = false --Overlay option +SWEP.Secondary.UseGreenDuplex = false --Overlay option +if surface then + SWEP.Secondary.ScopeTable = nil --[[ + { + scopetex = surface.GetTextureID("scope/gdcw_closedsight"), + reticletex = surface.GetTextureID("scope/gdcw_acogchevron"), + dottex = surface.GetTextureID("scope/gdcw_acogcross") + } + ]]-- +end +--[[SHOTGUN CODE]]-- +SWEP.Shotgun = false --Enable shotgun style reloading. +SWEP.ShotgunEmptyAnim = false --Enable emtpy reloads on shotguns? +SWEP.ShotgunEmptyAnim_Shell = true --Enable insertion of a shell directly into the chamber on empty reload? +SWEP.ShotgunStartAnimShell = false --shotgun start anim inserts shell +SWEP.ShellTime = .35 -- For shotguns, how long it takes to insert a shell. +--[[SPRINTING]]-- +SWEP.RunSightsPos = Vector(0, 0, 0) --Change this, using SWEP Creation Kit preferably +SWEP.RunSightsAng = Vector(0, 0, 0) --Change this, using SWEP Creation Kit preferably +--[[IRONSIGHTS]]-- +SWEP.data = {} +SWEP.data.ironsights = 1 --Enable Ironsights +SWEP.Secondary.IronFOV = 70 -- How much you "zoom" in. Less is more! Don't have this be <= 0. A good value for ironsights is like 70. +SWEP.IronSightsPos = Vector(-1.2966, -2, 0.5867) --Change this, using SWEP Creation Kit preferably +SWEP.IronSightsAng = Vector(1.6276, -2.1752, 7) --Change this, using SWEP Creation Kit preferably +--[[INSPECTION]]-- +SWEP.InspectPos = nil--Vector(0,0,0) --Replace with a vector, in style of ironsights position, to be used for inspection +SWEP.InspectAng = nil--Vector(0,0,0) --Replace with a vector, in style of ironsights angle, to be used for inspection +--[[VIEWMODEL BLOWBACK]]-- +SWEP.BlowbackEnabled = false --Enable Blowback? +SWEP.BlowbackVector = Vector(0,-1,0) --Vector to move bone relative to bone orientation. +SWEP.BlowbackCurrentRoot = 0 --Amount of blowback currently, for root +SWEP.BlowbackCurrent = 0 --Amount of blowback currently, for bones +SWEP.BlowbackBoneMods = nil --Viewmodel bone mods via SWEP Creation Kit +SWEP.Blowback_Only_Iron = true --Only do blowback on ironsights +SWEP.Blowback_PistolMode = false --Do we recover from blowback when empty? +SWEP.Blowback_Shell_Enabled = true --Shoot shells through blowback animations +SWEP.Blowback_Shell_Effect = "ShellEject"--Which shell effect to use +--[[VIEWMODEL PROCEDURAL ANIMATION]]-- +SWEP.DoProceduralReload = false--Animate first person reload using lua? +SWEP.ProceduralReloadTime = 1 --Procedural reload time? +--[[HOLDTYPES]]-- +SWEP.IronSightHoldTypeOverride = "" --This variable overrides the ironsights holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +SWEP.SprintHoldTypeOverride = "" --This variable overrides the sprint holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +--[[ANIMATION]]-- +SWEP.StatusLengthOverride = {} --Changes the status delay of a given animation; only used on reloads. Otherwise, use SequenceLengthOverride or one of the others +SWEP.SequenceLengthOverride = {} --Changes both the status delay and the nextprimaryfire of a given animation +SWEP.SequenceTimeOverride = {} --Like above but changes animation length to a target +SWEP.SequenceRateOverride = {} --Like above but scales animation length rather than being absolute + +SWEP.ProceduralHolsterEnabled = nil +SWEP.ProceduralHolsterTime = 0.3 +SWEP.ProceduralHolsterPos = Vector(3, 0, -5) +SWEP.ProceduralHolsterAng = Vector(-40, -30, 10) + +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = lua but continue idle, Lua = stop mdl animation +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.Walk_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH --TFA.Enum.IDLE_DISABLED = no idle, TFA.Enum.IDLE_LUA = lua idle, TFA.Enum.IDLE_ANI = mdl idle, TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA +SWEP.Idle_Blend = 0.25 --Start an idle this far early into the end of a transition +SWEP.Idle_Smooth = 0.05 --Start an idle this far early into the end of another animation +--MDL Animations Below +SWEP.IronAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Idle_To_Iron", --Number for act, String/Number for sequence + ["value_empty"] = "Idle_To_Iron_Dry", + ["transition"] = true + }, --Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Idle_Iron", --Number for act, String/Number for sequence + ["value_empty"] = "Idle_Iron_Dry" + }, --Looping Animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Iron_To_Idle", --Number for act, String/Number for sequence + ["value_empty"] = "Iron_To_Idle_Dry", + ["transition"] = true + }, --Outward transition + ["shoot"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Fire_Iron", --Number for act, String/Number for sequence + ["value_last"] = "Fire_Iron_Last", + ["value_empty"] = "Fire_Iron_Dry" + } --What do you think +} + +SWEP.SprintAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Idle_to_Sprint", --Number for act, String/Number for sequence + ["value_empty"] = "Idle_to_Sprint_Empty", + ["transition"] = true + }, --Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Sprint_", --Number for act, String/Number for sequence + ["value_empty"] = "Sprint_Empty_", + ["is_idle"] = true + },--looping animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Sprint_to_Idle", --Number for act, String/Number for sequence + ["value_empty"] = "Sprint_to_Idle_Empty", + ["transition"] = true + } --Outward transition +} + +SWEP.WalkAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Idle_to_Walk", --Number for act, String/Number for sequence + ["value_empty"] = "Idle_to_Walk_Empty", + ["transition"] = true + }, --Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Walk", --Number for act, String/Number for sequence + ["value_empty"] = "Walk_Empty", + ["is_idle"] = true + },--looping animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Walk_to_Idle", --Number for act, String/Number for sequence + ["value_empty"] = "Walk_to_Idle_Empty", + ["transition"] = true + } --Outward transition +} + +-- Looping fire animation (full-auto only) +SWEP.ShootAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "ShootLoop_Start", --Number for act, String/Number for sequence + ["value_is"] = "ShootLoop_Iron_Start", --Number for act, String/Number for sequence + ["transition"] = true + }, --Looping Start, fallbacks to loop + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "ShootLoop", --Number for act, String/Number for sequence, + ["value_is"] = "ShootLoop_Iron", --Number for act, String/Number for sequence, + ["is_idle"] = true, + }, --Looping Animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "ShootLoop_End", --Number for act, String/Number for sequence + ["value_is"] = "ShootLoop_Iron_End", --Number for act, String/Number for sequence + ["transition"] = true + }, --Looping End +} + +--[[ +SWEP.PumpAction = { -- Pump/bolt animations + ["type"] = TFA.Enum.ANIMATION_ACT, -- Sequence or act + ["value"] = ACT_VM_PULLBACK_HIGH, -- Number for act, String/Number for sequence + ["value_empty"] = ACT_VM_PULLBACK, -- Last shot pump + ["value_is"] = ACT_VM_PULLBACK_LOW, -- ADS pump +} +]]-- + +--[[EFFECTS]]-- +--Attachments +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellAttachment = "2" -- Should be "2" for CSS models or "shell" for hl2 models +SWEP.MuzzleFlashEnabled = true --Enable muzzle flash +SWEP.MuzzleAttachmentRaw = nil --This will override whatever string you gave. This is the raw attachment number. This is overridden or created when a gun makes a muzzle event. +SWEP.AutoDetectMuzzleAttachment = false --For multi-barrel weapons, detect the proper attachment? +SWEP.MuzzleFlashEffect = nil --Change to a string of your muzzle flash effect. Copy/paste one of the existing from the base. +SWEP.SmokeParticle = nil --Smoke particle (ID within the PCF), defaults to something else based on holdtype; "" to disable +SWEP.EjectionSmokeEnabled = true --Disable automatic ejection smoke +--Shell eject override +SWEP.LuaShellEject = false --Enable shell ejection through lua? +SWEP.LuaShellEjectDelay = 0 --The delay to actually eject things +SWEP.LuaShellModel = nil --The model to use for ejected shells +SWEP.LuaShellScale = nil --The model scale to use for ejected shells +SWEP.LuaShellYaw = nil --The model yaw rotation ( relative ) to use for ejected shells +--Tracer Stuff +SWEP.TracerName = nil --Change to a string of your tracer name. Can be custom. There is a nice example at https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/base/entities/effects/tooltracer.lua +SWEP.TracerCount = 3 --0 disables, otherwise, 1 in X chance +--Impact Effects +SWEP.ImpactEffect = nil--Impact Effect +SWEP.ImpactDecal = nil--Impact Decal +--[[EVENT TABLE]]-- +SWEP.EventTable = {} --Event Table, used for custom events when an action is played. This can even do stuff like playing a pump animation after shooting. +--example: +--[[RENDER TARGET]]-- +SWEP.RTMaterialOverride = nil -- Take the material you want out of print(LocalPlayer():GetViewModel():GetMaterials()), subtract 1 from its index, and set it to this. +SWEP.RTOpaque = false -- Do you want your render target to be opaque? +SWEP.RTCode = nil--function(self) return end --This is the function to draw onto your rendertarget +--[[AKIMBO]]-- +SWEP.Akimbo = false --Akimbo gun? Alternates between primary and secondary attacks. +SWEP.AnimCycle = 1 -- Start on the right +--[[ATTACHMENTS]]-- +SWEP.VElements = nil --Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.WElements = nil --Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.Attachments = { + --[ORDER] = = { atts = { "si_eotech" }, sel = 0 } + --sel allows you to have an attachment pre-selected, and is used internally by the base to show which attachment is selected in each category. +} +SWEP.AttachmentDependencies = {} --{["si_acog"] = {"bg_rail", ["type"] = "OR"}}--type could also be AND to require multiple +SWEP.AttachmentExclusions = {} --{ ["si_iron"] = { [1] = "bg_heatshield"} } +SWEP.AttachmentTableOverride = {} --[[{ -- overrides WeaponTable for attachments + ["ins2_ub_laser"] = { -- attachment id, root of WeaponTable override + ["VElements"] = { + ["laser_rail"] = { + ["active"] = true + }, + }, + } +}]] + + +--[[MISC INFO FOR MODELERS]]-- +--[[ + +Used Animations (for modelers): + +ACT_VM_DRAW - Draw +ACT_VM_DRAW_EMPTY - Draw empty +ACT_VM_DRAW_SILENCED - Draw silenced, overrides empty + +ACT_VM_IDLE - Idle +ACT_VM_IDLE_SILENCED - Idle empty, overwritten by silenced +ACT_VM_IDLE_SILENCED - Idle silenced + +ACT_VM_PRIMARYATTACK - Shoot +ACT_VM_PRIMARYATTACK_EMPTY - Shoot last chambered bullet +ACT_VM_PRIMARYATTACK_SILENCED - Shoot silenced, overrides empty +ACT_VM_PRIMARYATTACK_1 - Shoot ironsights, overriden by everything besides normal shooting +ACT_VM_DRYFIRE - Dryfire + +ACT_VM_RELOAD - Reload / Tactical Reload / Insert Shotgun Shell +ACT_SHOTGUN_RELOAD_START - Start shotgun reload, unless ACT_VM_RELOAD_EMPTY is there. +ACT_SHOTGUN_RELOAD_FINISH - End shotgun reload. +ACT_VM_RELOAD_EMPTY - Empty mag reload, chambers the new round. Works for shotguns too, where applicable. +ACT_VM_RELOAD_SILENCED - Silenced reload, overwrites all + + +ACT_VM_HOLSTER - Holster +ACT_VM_HOLSTER_SILENCED - Holster empty, overwritten by silenced +ACT_VM_HOLSTER_SILENCED - Holster silenced + +]]-- +-- отключаем TFA bullet logic +function SWEP:CanPrimaryAttack() + return true +end + +function SWEP:ShootBulletInformation() + return +end + +function SWEP:Recoil() + return +end + +function SWEP:PrimaryAttack() + if not SERVER then return end + self:ShootAT4() +end + + +function SWEP:DropTube() + local ply = self:GetOwner() + if not IsValid(ply) then return end + + local eyePos = ply:EyePos() + local eyeAng = ply:EyeAngles() + + local tube = ents.Create("prop_physics") + if not IsValid(tube) then return end + + tube:SetModel(self.WorldModel) + tube:SetPos(eyePos + eyeAng:Forward() * -20) + tube:SetAngles(eyeAng) + tube:Spawn() + tube:Activate() + + -- ply:StripWeapon(self:GetClass()) -- REMOVED: make it reloadable + + timer.Simple(3, function() + if IsValid(tube) then tube:Remove() end + end) +end + + +function SWEP:ShootAT4() + local ply = self:GetOwner() + if not IsValid(ply) then return end + if self:Clip1() <= 0 then return end + + local eyePos = ply:EyePos() + local eyeAng = ply:EyeAngles() + + local tr = util.TraceLine({ + start = eyePos, + endpos = eyePos + eyeAng:Forward() * (3 * 52.49), + filter = ply + }) + + ply:EmitSound("weapons/at4/at4_fp.wav", 100, 100, 1, CHAN_WEAPON) + + if tr.Hit then + ply:EmitSound("weapons/rpg/rocketclose.wav", 75, 100, 1, CHAN_ITEM) + + local inert = ents.Create("prop_physics") + if IsValid(inert) then + inert:SetModel("models/w_at4_projectile.mdl") + inert:SetPos(eyePos + eyeAng:Forward() * 10) + inert:SetAngles(eyeAng) + inert:Spawn() + inert:Activate() + + local phys = inert:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(eyeAng:Forward() * 300) + end + + timer.Simple(10, function() + if IsValid(inert) then inert:Remove() end + end) + end + + self:TakePrimaryAmmo(1) + self:DropTube() + return + end + + local missile = ents.Create("lvs_missile") + if not IsValid(missile) then + self:DropTube() + return + end + + missile:SetPos(eyePos) + missile:SetAngles(eyeAng) + missile:Spawn() + missile:Activate() + + missile:SetDamage(900) + missile:SetRadius(150) + missile:SetForce(80000) + missile:SetAttacker(ply) + missile:SetEntityFilter({ply}) + missile:SetTurnSpeed(0) + missile:SetSpeed(1600) + missile:Enable() + + + ParticleEffect("ins_weapon_rpg_frontblast", eyePos, eyeAng, ply) + ParticleEffect("ins_weapon_rpg_backblast", eyePos - eyeAng:Forward()*80, eyeAng - Angle(0,180,0), ply) + + local pphys = ply:GetPhysicsObject() + if IsValid(pphys) then + pphys:ApplyForceOffset(-eyeAng:Forward() * 2000, eyePos) + end + + self:TakePrimaryAmmo(1) + self:DropTube() + + -- Force reload after 1 second + timer.Simple(1, function() + if IsValid(self) then self:Reload() end + end) +end + +SWEP.SprintBobMult = 0 +DEFINE_BASECLASS( SWEP.Base ) diff --git a/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_panzerfaust3/shared.lua b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_panzerfaust3/shared.lua new file mode 100644 index 0000000..a93f816 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_panzerfaust3/shared.lua @@ -0,0 +1,233 @@ +-- Variables that are used on both client and server +SWEP.Gun = ("weapon_sw_panzerfaust3") -- must be the name of your swep but NO CAPITALS! +SWEP.Category = "FT | Antitank" +SWEP.Manufacturer = "FT_Scripty" --Gun Manufactrer (e.g. Hoeckler and Koch ) +SWEP.Author = "The Master MLG, Scripty" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "" +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellEjectAttachment = "2" -- Should be "2" for CSS models or "1" for hl2 models +SWEP.PrintName = "PanzerFaust - 3" -- Weapon name (Shown on HUD) +SWEP.Slot = 2 -- Slot in the weapon selection menu +SWEP.SlotPos = 3 -- Position in the slot +SWEP.DrawAmmo = true -- Should draw the default HL2 ammo counter +SWEP.DrawWeaponInfoBox = false -- Should draw the weapon info box +SWEP.BounceWeaponIcon = false -- Should the weapon icon bounce? +SWEP.DrawCrosshair = true -- set false if you want no crosshair +SWEP.Weight = 2 -- rank relative ot other weapons. bigger is better +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.HoldType = "smg" -- how others view you carrying the weapon +SWEP.Type = "Anti-Tank Rocket Launcher" +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- you're mostly going to use ar2, smg, shotgun or pistol. rpg and crossbow make for good sniper rifles + +SWEP.ViewModelFOV = 70 +SWEP.ViewModelFlip = false +SWEP.ViewModel = "models/weapons/v_panzerfaust3_sandstorm.mdl" -- Weapon view model +SWEP.WorldModel = "models/weapons/w_panzerfaust3_sandstorm.mdl" -- Weapon world model +SWEP.Base = "tfa_3dbash_base" +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.FiresUnderwater = false +SWEP.UseHands = true + +SWEP.Primary.Sound = Sound("weapons/at4/at4_fp.wav") +SWEP.Primary.RPM = 125 -- This is in Rounds Per Minute +SWEP.Primary.ClipSize = 1 -- Size of a clip +SWEP.Primary.DefaultClip = 1 -- Bullets you start with +SWEP.Primary.KickUp = 0.345 -- Maximum up recoil (rise) +SWEP.Primary.KickDown = 0.267 -- Maximum down recoil (skeet) +SWEP.Primary.KickHorizontal = 0.186 -- Maximum up recoil (stock) +SWEP.Primary.StaticRecoilFactor = 0.63 --Amount of recoil to directly apply to EyeAngles. Enter what fraction or percentage (in decimal form) you want. This is also affected by a convar that defaults to 0.5. +SWEP.IronRecoilMultiplier = 0.61 +SWEP.Primary.Automatic = false -- Automatic = true; Semi Auto = false +SWEP.Primary.Ammo = "PanzerFaust3 Rocket" -- pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, AirboatGun +-- Pistol, buckshot, and slam always ricochet. Use AirboatGun for a light metal peircing shotgun pellets + +SWEP.Secondary.IronFOV = 70 -- How much you 'zoom' in. Less is more! Don't have this be <= 0. A good value for ironsights is like 70. +SWEP.MoveSpeed = 0.72 --Multiply the player's movespeed by this. +SWEP.IronSightsMoveSpeed = SWEP.MoveSpeed * 0.91 + +SWEP.data = {} --The starting firemode +SWEP.data.ironsights = 1 +SWEP.Primary.NumShots = 1 -- How many bullets to shoot per trigger pull +SWEP.Primary.Damage = 1000 -- Base damage per bullet +SWEP.Primary.Spread = .001 -- Define from-the-hip accuracy 1 is terrible, .0001 is exact) +SWEP.Primary.IronAccuracy = .001 -- Ironsight accuracy, should be the same for shotguns +SWEP.DisableChambering = true + +-- Enter iron sight info and bone mod info below +SWEP.IronSightsPos = Vector(-0.565, -4, 0.97) +SWEP.IronSightsAng = Vector(16.535, 0, 0) +SWEP.RunSightsPos = Vector(4.762, -4.238, -0.717) +SWEP.RunSightsAng = Vector(-6.743, 46.284, 0) +SWEP.InspectPos = Vector(7.76, -2, 0.016) +SWEP.InspectAng = Vector(1, 37.277, 3.2) + +SWEP.ProjectileVelocity = 7000 * 16 / 12 --Entity to shoot's velocity +SWEP.FireModeName = "One-Shot" + +SWEP.RTMaterialOverride = -1 --the number of the texture, which you subtract from GetAttachment + +SWEP.ScopeAngleTransforms = { + {"P",-6}, --Pitch, 1 + {"Y",1.25}, --Yaw, 1 + {"R",20}, --Roll, 1 +} + +SWEP.RTScopeFOV = 9.32 +SWEP.RTScopeAttachment = 3 + +SWEP.RTMaterialOverride = -1 + +SWEP.IronSightsSensitivity = 1 / 3 + +SWEP.ScopeShadow = nil + +SWEP.UpdateScopeType = function() end + +SWEP.RTScopeAttachment = 3 +SWEP.Primary.UseLuaBullet = false +SWEP.Primary.Projectile = nil +SWEP.Primary.Damage = 0 + +SWEP.ScopeReticule = ("models/weapons/panzerfaust3_sandstorm/panzefaust_reticle") + + +SWEP.SelectiveFire = false +SWEP.DisableBurstFire = false --Only auto/single? + +SWEP.Offset = { + Pos = { + Up = -8.8, + Right = 2, + Forward = 7.5, + }, + Ang = { + Up = -5, + Right = 170, + Forward = 185, + }, + Scale = 1 +} + +SWEP.VElements = { + ["RT_Scope"] = { type = "Model", model = "models/rtcircle.mdl", bone = "Weapon", rel = "", pos = Vector(2.644, -3.55, 5.192), angle = Angle(0, 90, -20), size = Vector(0.428, 0.428, 0.428), color = Color(255, 255, 255, 255), surpresslightning = false, material = "!tfa_rtmaterial", skin = 0, bodygroup = {} } +} + + +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_ANI -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.SprintAnimation = { + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "base_sprint", --Number for act, String/Number for sequence + ["is_idle"] = true + } +} + +SWEP.WElements = { + ["ref"] = { type = "Model", model = SWEP.WorldModel, bone = "oof", rel = "", pos = Vector(0, 0, 0), angle = Angle(0, 0, 0), size = Vector(0.9, 0.9, 0.9), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {}, bonemerge = true, active = false } +} + +function SWEP:DropTube() + local ply = self:GetOwner() + if not IsValid(ply) then return end + + local eyePos = ply:EyePos() + local eyeAng = ply:EyeAngles() + + local tube = ents.Create("prop_physics") + if not IsValid(tube) then return end + + tube:SetModel(self.WorldModel) + tube:SetPos(eyePos + eyeAng:Forward() * -20) + tube:SetAngles(eyeAng) + tube:Spawn() + tube:Activate() + + ply:StripWeapon(self:GetClass()) + + timer.Simple(3, function() + if IsValid(tube) then tube:Remove() end + end) +end + +function SWEP:PrimaryAttack() + if not SERVER then return end + self:ShootBullet() +end + +function SWEP:ShootBullet() + if not SERVER then return end + + local ply = self:GetOwner() + if not IsValid(ply) then return end + + local eyePos = ply:EyePos() + local eyeAng = ply:EyeAngles() + + local tr = util.TraceLine({ + start = eyePos, + endpos = eyePos + eyeAng:Forward() * (3 * 52.49), + filter = ply + }) + + ply:EmitSound("weapons/at4/at4_fp.wav", 100, 100, 1, CHAN_WEAPON) + + if tr.Hit then + ply:EmitSound("weapons/rpg/rocketclose.wav", 75, 100, 1, CHAN_ITEM) + + local inert = ents.Create("prop_physics") + if IsValid(inert) then + inert:SetModel("models/weapons/w_panzerfaust3_sandstorm_projectile.mdl") + inert:SetPos(eyePos + eyeAng:Forward() * 10) + inert:SetAngles(eyeAng) + inert:Spawn() + inert:Activate() + + local phys = inert:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(eyeAng:Forward() * 300) + end + + timer.Simple(10, function() + if IsValid(inert) then inert:Remove() end + end) + end + + self:DropTube() + return + end + + local missile = ents.Create("lvs_missile") + if not IsValid(missile) then + self:DropTube() + return + end + + missile:SetPos(eyePos) + missile:SetAngles(eyeAng) + missile:Spawn() + missile:Activate() + + missile:SetDamage(1000) + missile:SetRadius(120) + missile:SetForce(60000) + missile:SetAttacker(ply) + missile:SetEntityFilter({ply}) + missile:SetTurnSpeed(0) + missile:SetSpeed(1500) + missile:Enable() + + ParticleEffect("ins_weapon_rpg_frontblast", eyePos, eyeAng, ply) + ParticleEffect("ins_weapon_rpg_backblast", eyePos - eyeAng:Forward()*80, eyeAng - Angle(0,180,0), ply) + + local pphys = ply:GetPhysicsObject() + if IsValid(pphys) then + pphys:ApplyForceOffset(-eyeAng:Forward() * 2000, eyePos) + end + + self:DropTube() +end diff --git a/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_rpg26.lua b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_rpg26.lua new file mode 100644 index 0000000..8196dc0 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_rpg26.lua @@ -0,0 +1,226 @@ +-- Variables that are used on both client and server +SWEP.Gun = ("weapon_sw_rpg26") -- must be the name of your swep but NO CAPITALS! +SWEP.Category = "FT | Antitank" --Category where you will find your weapons +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellEjectAttachment = "1" -- Should be "2" for CSS models or "1" for hl2 models +SWEP.Author = "Shtormer, Scripty" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Manufacturer = "FT_Scripty" +SWEP.Instructions = "" +SWEP.PrintName = "RPG-26" -- Weapon name (Shown on HUD) +SWEP.Slot = 4 -- Slot in the weapon selection menu +SWEP.SlotPos = 4 -- Position in the slot +SWEP.DrawAmmo = true -- Should draw the default HL2 ammo counter +SWEP.DrawWeaponInfoBox = false -- Should draw the weapon info box +SWEP.BounceWeaponIcon = false -- Should the weapon icon bounce? +SWEP.DrawCrosshair = true -- set false if you want no crosshair +SWEP.Weight = 89 -- rank relative ot other weapons. bigger is better +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.HoldType = "rpg" -- how others view you carrying the weapon +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- you're mostly going to use ar2, smg, shotgun or pistol. rpg makes for good sniper rifles + +SWEP.ViewModelFOV = 70 +SWEP.ViewModelFlip = false +SWEP.ViewModel = "models/weapons/fas2/view/explosives/rpg26.mdl" +SWEP.WorldModel = "models/weapons/fas2/world/explosives/rpg26.mdl" +SWEP.ShowWorldModel = true +SWEP.Base = "tfa_bash_base" --the Base this weapon will work on. PLEASE RENAME THE BASE! +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.FiresUnderwater = false +SWEP.UseHands = true +SWEP.Type = "Man-Portable Anti-Tank Weapon" +SWEP.IronSightTime = 0.5 +SWEP.Primary.Sound = Sound("weapons/at4/at4_fp.wav") -- This is the sound of the weapon, when you shoot. +SWEP.Primary.RPM = 325 -- This is in Rounds Per Minute +SWEP.Primary.ClipSize = 1 -- Size of a clip +SWEP.Primary.DefaultClip = 1 -- Bullets you start with +SWEP.Primary.KickUp = 2.25 -- Maximum up recoil (rise) +SWEP.Primary.KickDown = 1.75 -- Maximum down recoil (skeet) +SWEP.Primary.KickHorizontal = 1.45 -- Maximum up recoil (stock) +SWEP.Primary.Automatic = false -- Automatic = true; Semi Auto = false +SWEP.Primary.Ammo = "rpg_round" -- pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, AirboatGun +SWEP.Primary.StaticRecoilFactor = 1.3 + +SWEP.SelectiveFire = false + +SWEP.Secondary.BashInterrupt = false -- Do you need to be in a "ready" status to bash? +SWEP.Secondary.BashDelay = 0.1 +SWEP.Secondary.BashLength = 52 +SWEP.Secondary.BashEnd = 2.25 -- Override bash sequence length easier +SWEP.Secondary.BashDamage = 56 + +SWEP.Secondary.IronFOV = 50 -- How much you 'zoom' in. Less is more! + +SWEP.data = {} --The starting firemode + +SWEP.Primary.Damage = 225 -- Base damage per bullet +SWEP.Primary.Spread = .021 -- Define from-the-hip accuracy 1 is terrible, .0001 is exact) +SWEP.Primary.IronAccuracy = .008 -- Ironsight accuracy, should be the same for shotguns + +-- Enter iron sight info and bone mod info below +SWEP.IronSightsPos = Vector(-2, -1, -1) +SWEP.IronSightsAng = Vector(0.65, -0.02, 0) +SWEP.IronSightsPos_Pgo7 = Vector(-2.5, -1, 0.6) +SWEP.IronSightsAng_Pgo7 = Vector(0, 0, 0) + +SWEP.RunSightsPos = Vector(4.762, -4.238, -0.717) +SWEP.RunSightsAng = Vector(-6.743, 46.284, 0) +SWEP.InspectPos = Vector(7.76, -2, 0.016) +SWEP.InspectAng = Vector(1, 37.277, 3.2) + +SWEP.Offset = { + Pos = { + Up = -2.5, + Right = 1.1, + Forward = 8.295 + }, + Ang = { + Up = -1.043, + Right = 0, + Forward = 180, + }, + Scale = 1.0 +} --Procedural world model animation, defaulted for CS:S purposes. + +SWEP.VElements = { + +} + +SWEP.Attachments = { + + +} +SWEP.WElements = { + +} +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_HYBRID -- ANI = mdl, HYBRID = lua but continue idle, Lua = stop mdl animation +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH --TFA.Enum.IDLE_DISABLED = no idle, TFA.Enum.IDLE_LUA = lua idle, TFA.Enum.IDLE_ANI = mdl idle, TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA + +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.RunSightsPos = Vector(0, -5, 0) --Change this, using SWEP Creation Kit preferably +SWEP.RunSightsAng = Vector(20, 0, 0) --Change this, using SWEP Creation Kit preferably +SWEP.SprintBobMult = 1.3 +SWEP.SprintAnimation = { + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "base_sprint", --Number for act, String/Number for sequence + ["is_idle"] = true + } +} +SWEP.Entities = {} +function SWEP:PrimaryAttack() + if not SERVER then return end + if self._NextFire and self._NextFire > CurTime() then return end + if self:Clip1() <= 0 then + self:Reload() + return + end + + self._NextFire = CurTime() + 5.5 -- Запрет на стрельбу во время перезарядки + self:FireShot() + self:TakePrimaryAmmo(1) + + local ply = self:GetOwner() + if not IsValid(ply) then return end + + local shootPos = ply:GetShootPos() + local shootAng = ply:EyeAngles() + + -- Эффект вылета тубуса (через 0.2с после выстрела) + timer.Simple(0.2, function() + if not IsValid(ply) then return end + + local ent = ents.Create("prop_physics") + if not IsValid(ent) then return end + + ent:SetModel("models/weapons/fas2/world/explosives/rpg26.mdl") + ent:SetPos(shootPos + shootAng:Forward() * -20) + ent:SetAngles(shootAng) + + ent:Spawn() + ent:Activate() + + timer.Simple(3, function() + if IsValid(ent) then ent:Remove() end + end) + end) +end + +function SWEP:FireShot() + if SERVER then + local ply = self:GetOwner() + if not IsValid(ply) then return end + + local safetyDist = 3 + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:EyeAngles():Forward() * (safetyDist * 52.49), + filter = ply + }) + + if tr.Hit then + ply:EmitSound("weapons/rpg/rocketclose.wav") + + local inert = ents.Create("prop_physics") + if IsValid(inert) then + inert:SetModel("models/weapons/fas2/world/explosives/rpg26/rocket.mdl") + inert:SetPos(ply:EyePos() + ply:EyeAngles():Forward() * 10) + inert:SetAngles(ply:EyeAngles()) + inert:Spawn() + inert:Activate() + + local phys = inert:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(ply:EyeAngles():Forward() * 300) + end + + timer.Simple(10, function() + if IsValid(inert) then inert:Remove() end + end) + end + + return + end + + local pos = ply:EyePos() + local ang = ply:EyeAngles() + + local missile = ents.Create("lvs_missile") + if not IsValid(missile) then return end + + missile:SetPos(pos) + missile:SetAngles(ang) + missile:Spawn() + missile:Activate() + + missile:SetDamage(1000) -- урон + missile:SetRadius(120) -- радиус поражения + missile:SetForce(60000) -- сила взрыва + missile:SetAttacker(ply) + missile:SetEntityFilter({ply}) + missile:SetTurnSpeed(0) + missile:SetSpeed(1500) -- скорость полёта + missile:Enable() + + ParticleEffect("ins_weapon_rpg_frontblast", pos, ang, ply) + ParticleEffect("ins_weapon_rpg_backblast", pos - ang:Forward()*80, ang - Angle(0,180,0), ply) + + local phys = ply:GetPhysicsObject() + if IsValid(phys) then + phys:ApplyForceOffset(-ang:Forward() * 2000, pos) + end + end +end + +SWEP.SequenceRateOverrideScaled = { + [ACT_VM_PRIMARYATTACK] = 0.85, + [ACT_VM_PRIMARYATTACK_1] = 0.85 +} + +SWEP.ProjectileVelocity = 7150 * 12 / 10 +SWEP.DisableChambering = true + diff --git a/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_rpg28.lua b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_rpg28.lua new file mode 100644 index 0000000..352a9c2 --- /dev/null +++ b/garrysmod/addons/tfa_antitank/lua/weapons/weapon_sw_rpg28.lua @@ -0,0 +1,267 @@ +-- БИБА СТРОЧКА 100!!!!!!! + +SWEP.Gun = ("weapon_sw_rpg28") -- must be the name of your swep but NO CAPITALS! +SWEP.Category = "FT | Antitank" --Category where you will find your weapons +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellEjectAttachment = "1" -- Should be "2" for CSS models or "1" for hl2 models +SWEP.Author = "Shtormer, Scripty" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Manufacturer = "FT_Scripty" +SWEP.Instructions = "" +SWEP.PrintName = "RPG-28" -- Weapon name (Shown on HUD) +SWEP.Slot = 4 -- Slot in the weapon selection menu +SWEP.SlotPos = 4 -- Position in the slot +SWEP.DrawAmmo = true -- Should draw the default HL2 ammo counter +SWEP.DrawWeaponInfoBox = false -- Should draw the weapon info box +SWEP.BounceWeaponIcon = false -- Should the weapon icon bounce? +SWEP.DrawCrosshair = true -- set false if you want no crosshair +SWEP.Weight = 89 -- rank relative ot other weapons. bigger is better +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.HoldType = "rpg" -- how others view you carrying the weapon +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- you're mostly going to use ar2, smg, shotgun or pistol. rpg makes for good sniper rifles + +SWEP.ViewModelFOV = 70 +SWEP.ViewModelFlip = false +SWEP.ViewModel = "models/weapons/javelin_c.mdl" -- Weapon view model +SWEP.WorldModel = "models/weapons/rpg28_launcher.mdl" -- Weapon world model +SWEP.ShowWorldModel = true +SWEP.Base = "tfa_bash_base" --the Base this weapon will work on. PLEASE RENAME THE BASE! +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.FiresUnderwater = false +SWEP.UseHands = true +SWEP.Type = "Man-Portable Anti-Tank Weapon" +SWEP.IronSightTime = 0.5 +SWEP.Primary.Sound = Sound("weapons/at4/at4_fp.wav") +SWEP.Primary.RPM = 325 -- This is in Rounds Per Minute +SWEP.Primary.ClipSize = 1 -- Size of a clip +SWEP.Primary.DefaultClip = 3 -- Bullets you start with +SWEP.Primary.KickUp = 2.25 -- Maximum up recoil (rise) +SWEP.Primary.KickDown = 1.75 -- Maximum down recoil (skeet) +SWEP.Primary.KickHorizontal = 1.45 -- Maximum up recoil (stock) +SWEP.Primary.Automatic = false -- Automatic = true; Semi Auto = false +SWEP.Primary.Ammo = "rpg_round" -- pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, AirboatGun +SWEP.Primary.StaticRecoilFactor = 1.3 + +SWEP.SelectiveFire = false +SWEP.ShowViewModel = false +SWEP.ShowWorldModel = false +SWEP.Secondary.BashInterrupt = false -- Do you need to be in a "ready" status to bash? +SWEP.Secondary.BashDelay = 0.1 +SWEP.Secondary.BashLength = 52 +SWEP.Secondary.BashEnd = 2.25 -- Override bash sequence length easier +SWEP.Secondary.BashDamage = 56 + +SWEP.Secondary.IronFOV = 25 -- How much you 'zoom' in. Less is more! + +SWEP.data = {} --The starting firemode + +SWEP.Primary.Damage = 225 -- Base damage per bullet +SWEP.Primary.Spread = .021 -- Define from-the-hip accuracy 1 is terrible, .0001 is exact) +SWEP.Primary.IronAccuracy = .008 -- Ironsight accuracy, should be the same for shotguns +SWEP.Scoped = true +-- Enter iron sight info and bone mod info below +SWEP.IronSightsPos = Vector(-9, 1, -9) +SWEP.IronSightsAng = Vector(0, -5, 0) +SWEP.IronSightsPos_Pgo7 = Vector(-2.5, -1, 0.6) +SWEP.IronSightsAng_Pgo7 = Vector(0, 0, 0) + +SWEP.RunSightsPos = Vector(4.762, -4.238, -0.717) +SWEP.RunSightsAng = Vector(-6.743, 46.284, 0) +SWEP.InspectPos = Vector(7.76, -2, 0.016) +SWEP.InspectAng = Vector(1, 37.277, 3.2) + +SWEP.Offset = { + Pos = { + Up = -1.5, + Right = 1, + Forward = 0 + }, + Ang = { + Up = -1, + Right = -2, + Forward = 178 + }, + Scale = 1 +} --Procedural world model animation, defaulted for CS:S purposes. + +SWEP.ViewModelBoneMods = { + ["j_gun"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(-18, -5, -8), angle = Angle(-10, 0, 3) } +} + +SWEP.VElements = { + ["nlaw"] = { type = "Model", model = "models/weapons/rpg28_launcher.mdl", bone = "j_gun", rel = "", pos = Vector(0, 0, 0), angle = Angle(0, 180, 0), size = Vector(0.8, 0.8, 0.8), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} } +} + +SWEP.WElements = { + ["nlaw"] = { type = "Model", model = "models/weapons/rpg28_launcher.mdl", bone = "ValveBiped.Bip01_R_Hand", rel = "", pos = Vector(0.3, 2, 1), angle = Angle(10, 180, 180), size = Vector(0.5, 0.5, 0.5), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} } +} +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_HYBRID -- ANI = mdl, HYBRID = lua but continue idle, Lua = stop mdl animation +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH --TFA.Enum.IDLE_DISABLED = no idle, TFA.Enum.IDLE_LUA = lua idle, TFA.Enum.IDLE_ANI = mdl idle, TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA + +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.RunSightsPos = Vector(0, -5, 0) --Change this, using SWEP Creation Kit preferably +SWEP.RunSightsAng = Vector(20, 0, 0) --Change this, using SWEP Creation Kit preferably +SWEP.SprintBobMult = 1.3 +SWEP.SprintAnimation = { + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "base_sprint", --Number for act, String/Number for sequence + ["is_idle"] = true + } +} + +SWEP.ReticleScale = 0.7 +SWEP.ScopeScale = 0.5 +DEFINE_BASECLASS( SWEP.Base ) +SWEP.Primary.AttacksWithNoAmmo = true +SWEP.Primary.ClipSize = -1 + +function SWEP:PrimaryAttack() + if not SERVER then return end + if self._NextFire and self._NextFire > CurTime() then return end + self._NextFire = CurTime() + 0.5 + + self:FireShot() +end + +function SWEP:FireShot() + if not SERVER then return end + + local ply = self:GetOwner() + if not IsValid(ply) then return end + ply:EmitSound("weapons/at4/at4_fp.wav", 100, 100, 1, CHAN_WEAPON) + + + local eyePos = ply:GetShootPos() + local eyeAng = ply:EyeAngles() + + local safetyDist = 3 * 52.49 + + local tr = util.TraceLine({ + start = eyePos, + endpos = eyePos + eyeAng:Forward() * safetyDist, + filter = ply + }) + + if tr.Hit then + ply:EmitSound("weapons/rpg/rocketclose.wav", 75, 100, 1, CHAN_ITEM) + + local inert = ents.Create("prop_physics") + if IsValid(inert) then + inert:SetModel("models/weapons/fas2/world/explosives/rpg26/rocket.mdl") + inert:SetPos(eyePos + eyeAng:Forward() * 10) + inert:SetAngles(eyeAng) + inert:Spawn() + inert:Activate() + + local phys = inert:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(eyeAng:Forward() * 300) + end + + timer.Simple(10, function() + if IsValid(inert) then inert:Remove() end + end) + end + + self:DropTube() + + ply:StripWeapon(self:GetClass()) + + return + end + + local spawnPos = eyePos + eyeAng:Forward() * 40 + + local missile = ents.Create("lvs_missile") + if not IsValid(missile) then return end + + missile:SetPos(spawnPos) + missile:SetAngles(eyeAng) + missile:Spawn() + missile:Activate() + + missile:SetDamage(3000) + missile:SetRadius(180) + missile:SetForce(90000) + missile:SetAttacker(ply) + missile:SetEntityFilter({ply}) + + missile:SetTurnSpeed(0) + missile:SetSpeed(1800) + missile.Autopilot = false + + timer.Simple(0, function() + if IsValid(missile) then + local phys = missile:GetPhysicsObject() + if IsValid(phys) then + phys:SetVelocity(eyeAng:Forward() * 1800) + phys:SetAngles(eyeAng) + end + end + end) + + missile:Enable() + + ParticleEffect("ins_weapon_rpg_frontblast", spawnPos, eyeAng, ply) + ParticleEffect("ins_weapon_rpg_backblast", spawnPos - eyeAng:Forward()*80, eyeAng - Angle(0,180,0), ply) + + local phys = ply:GetPhysicsObject() + if IsValid(phys) then + phys:ApplyForceOffset(-eyeAng:Forward() * 2000, eyePos) + end + + self:DropTube() + + ply:StripWeapon(self:GetClass()) +end + +function SWEP:DropTube() + local ply = self:GetOwner() + if not IsValid(ply) then return end + + local eyePos = ply:EyePos() + local eyeAng = ply:EyeAngles() + + local tube = ents.Create("prop_physics") + if not IsValid(tube) then return end + + tube:SetModel(self.WorldModel) + tube:SetPos(eyePos + eyeAng:Forward() * -20) + tube:SetAngles(eyeAng) + tube:Spawn() + tube:Activate() + + ply:StripWeapon(self:GetClass()) + + timer.Simple(3, function() + if IsValid(tube) then tube:Remove() end + end) +end + +if CLIENT then + SWEP.Secondary.ScopeTable = + { + ScopeBorder = Color(0, 0, 0, 255), + ScopeMaterial = Material("models/eryx/noise"), + ScopeOverlay = Material("models/eryx/overlay"), + ScopeCrosshair = { -- can also be just a Material() value + r = 0, g = 0, b = 0, a = 255, -- color + scale = 1, -- scale or crosshair line width if no material specified + Material = Material("scope/gdcw_acogcross"), -- material, OPTIONAL! + } + } + +end +SWEP.SequenceRateOverrideScaled = { + [ACT_VM_PRIMARYATTACK] = 0.85, + [ACT_VM_PRIMARYATTACK_1] = 0.85 +} + +SWEP.ProjectileVelocity = 7150 * 12 / 10 --Entity to shoot's velocity +SWEP.DisableChambering = true + diff --git a/garrysmod/addons/tfa_base/lua/autorun/tfa_base_autorun.lua b/garrysmod/addons/tfa_base/lua/autorun/tfa_base_autorun.lua new file mode 100644 index 0000000..52fbfcf --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/autorun/tfa_base_autorun.lua @@ -0,0 +1,224 @@ +if SERVER then AddCSLuaFile() end + +TFA = TFA or {} + +local version = 4.794 +local version_string = "4.7.9.4" + +local function testFunc() +end + +local my_path = debug.getinfo(testFunc) +if my_path and type(my_path) == "table" and my_path.short_src then + my_path = my_path["short_src"] +else + my_path = "legacy" +end + +local official_modules_sorted = { + "tfa_commands.lua", + "cl_tfa_commands.lua", -- we need to load clientside convars before anything else + + "tfa_envcheck.lua", + + "tfa_data.lua", + "tfa_ammo.lua", + "tfa_attachments.lua", + "tfa_ballistics.lua", + "tfa_bodygroups.lua", + + "tfa_darkrp.lua", + "tfa_effects.lua", + "tfa_functions.lua", + "tfa_hooks.lua", + "tfa_keybinds.lua", + "tfa_keyvalues.lua", + "tfa_matproxies.lua", + "tfa_melee_autorun.lua", + "tfa_meta.lua", + "tfa_netcode.lua", + "tfa_small_entities.lua", + "tfa_npc_teamcolor.lua", + "tfa_npc_weaponmenu.lua", + "tfa_nzombies.lua", + "tfa_particles.lua", + "tfa_snd_timescale.lua", + "tfa_soundscripts.lua", + "tfa_tttpatch.lua", + + "sv_tfa_settingsmenu.lua", -- TFA.BASE_LOAD_COMPLETE server + + "cl_tfa_attachment_icon.lua", + "cl_tfa_attachment_panel.lua", + "cl_tfa_attachment_tip.lua", + + "cl_tfa_devtools.lua", + "cl_tfa_fonts.lua", + "cl_tfa_hitmarker.lua", + "cl_tfa_inspection.lua", + "cl_tfa_materials.lua", + "cl_tfa_models.lua", + "cl_tfa_particles_lua.lua", + "cl_tfa_projtex.lua", + "cl_tfa_rendertarget.lua", + "cl_tfa_rtbgblur.lua", + "cl_tfa_settingsmenu.lua", + "cl_tfa_vgui.lua", + "cl_tfa_vm_blur.lua", + "cl_tfa_stencilsights.lua", + "cl_tfa_subcategories.lua", -- TFA.BASE_LOAD_COMPLETE client +} + +local official_modules = {} + +for _, modulename in ipairs(official_modules_sorted) do + official_modules[modulename] = true +end + +TFA_BASE_VERSION = version +TFA_BASE_VERSION_STRING = version_string +TFA_BASE_VERSION_CHANGES = "In-game changelog has been removed.\nCheck GitHub or Steam Workshop change notes." +TFA_FILE_PATH = my_path + +TFA.BASE_LOAD_COMPLETE = false + +TFA.Enum = TFA.Enum or {} + +local flist = file.Find("tfa/enums/*.lua","LUA") + +for _, filename in pairs(flist) do + local typev = "SHARED" + + if filename:StartWith("cl_") then + typev = "CLIENT" + elseif filename:StartWith("sv_") then + typev = "SERVER" + end + + if SERVER and typev ~= "SERVER" then + AddCSLuaFile("tfa/enums/" .. filename) + end + + if SERVER and typev ~= "CLIENT" or CLIENT and typev ~= "SERVER" then + include("tfa/enums/" .. filename) + end +end + +hook.Run("TFABase_PreEarlyInit") + +for _, filename in ipairs(official_modules_sorted) do + if filename:StartWith("cl_") then + if SERVER then + AddCSLuaFile("tfa/modules/" .. filename) + else + include("tfa/modules/" .. filename) + end + elseif filename:StartWith("sv_") then + if SERVER then + include("tfa/modules/" .. filename) + end + else + if SERVER then + AddCSLuaFile("tfa/modules/" .. filename) + end + + include("tfa/modules/" .. filename) + end +end + +hook.Run("TFABase_EarlyInit") +hook.Run("TFABase_PreInit") + +flist = file.Find("tfa/modules/*.lua", "LUA") +local toload = {} +local toload2 = {} + +for _, filename in pairs(flist) do + if not official_modules[filename] then + local typev = "SHARED" + + if filename:StartWith("cl_") then + typev = "CLIENT" + elseif filename:StartWith("sv_") then + typev = "SERVER" + end + + if SERVER and typev ~= "SERVER" then + AddCSLuaFile("tfa/modules/" .. filename) + end + + if SERVER and typev == "SERVER" or CLIENT and typev == "CLIENT" then + table.insert(toload2, filename) + elseif typev == "SHARED" then + table.insert(toload, filename) + end + end +end + +local yell = #toload ~= 0 or #toload2 ~= 0 + +table.sort(toload) +table.sort(toload2) + +for _, filename in ipairs(toload) do + include("tfa/modules/" .. filename) + print("[TFA Base] [!] Loaded unofficial module " .. string.sub(filename, 1, -5) .. ".") +end + +for _, filename in ipairs(toload2) do + include("tfa/modules/" .. filename) + print("[TFA Base] [!] Loaded unofficial module " .. string.sub(filename, 1, -5) .. ".") +end + +hook.Run("TFABase_Init") +hook.Run("TFABase_PreFullInit") + +flist = file.Find("tfa/external/*.lua", "LUA") +toload = {} +toload2 = {} + +for _, filename in pairs(flist) do + local typev = "SHARED" + + if filename:StartWith("cl_") then + typev = "CLIENT" + elseif filename:StartWith("sv_") then + typev = "SERVER" + end + + if SERVER and typev ~= "SERVER" then + AddCSLuaFile("tfa/external/" .. filename) + end + + if SERVER and typev == "SERVER" or CLIENT and typev == "CLIENT" then + table.insert(toload2, filename) + elseif typev == "SHARED" then + table.insert(toload, filename) + end +end + +table.sort(toload) +table.sort(toload2) + +for _, filename in ipairs(toload) do + include("tfa/external/" .. filename) +end + +for _, filename in ipairs(toload2) do + include("tfa/external/" .. filename) +end + +if yell then + print("[TFA Base] [!] Some of files not belonging to TFA Base were loaded from tfa/modules/ directory") + print("[TFA Base] This behavior is kept for backward compatiblity and using this is highly discouraged!") + print("[TFA Base] Files loaded this way have no pre-defined sorting applied and result of execution of those files is undefined.") + print("[TFA Base] If you are author of these files, please consider moving your modules to tfa/external/ as soon as possible.") +end + +hook.Run("TFABase_FullInit") + +if not VLL2_FILEDEF then + TFAUpdateAttachments(false) +end + +hook.Run("TFABase_LateInit") diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_bullet_impact/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_bullet_impact/init.lua new file mode 100644 index 0000000..722235a --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_bullet_impact/init.lua @@ -0,0 +1,25 @@ +function EFFECT:Init(data) + local posoffset = data:GetOrigin() + local emitter = ParticleEmitter(posoffset) + + if TFA.GetGasEnabled() then + local p = emitter:Add("sprites/heatwave", posoffset) + p:SetVelocity(50 * data:GetNormal() + 0.5 * VectorRand()) + p:SetAirResistance(200) + p:SetStartSize(math.random(12.5, 17.5)) + p:SetEndSize(2) + p:SetDieTime(math.Rand(0.15, 0.225)) + p:SetRoll(math.Rand(-180, 180)) + p:SetRollDelta(math.Rand(-0.75, 0.75)) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +return false +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_dust_impact/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_dust_impact/init.lua new file mode 100644 index 0000000..45cca4f --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_dust_impact/init.lua @@ -0,0 +1,41 @@ +function EFFECT:Init(data) + local ply = data:GetEntity() + local ent + + if IsValid(ply) and ply:IsPlayer() then + ent = ply:GetActiveWeapon() + end + + local sfac = (IsValid(ent) and ent.Primary and ent.Primary.Damage) and math.sqrt(ent.Primary.Damage / 30) or 1 + local sfac_sqrt = math.sqrt(sfac) + local posoffset = data:GetOrigin() + local forward = data:GetNormal() + local emitter = ParticleEmitter(posoffset) + + for i = 0, math.Round(8 * sfac) do + local p = emitter:Add("particle/particle_smokegrenade", posoffset) + p:SetVelocity(90 * math.sqrt(i) * forward) + p:SetAirResistance(400) + p:SetStartAlpha(math.Rand(255, 255)) + p:SetEndAlpha(0) + p:SetDieTime(math.Rand(0.75, 1) * (1 + math.sqrt(i) / 3)) + local iclamped = math.Clamp(i, 1, 8) + local iclamped_sqrt = math.sqrt(iclamped / 8) * 8 + p:SetStartSize(math.Rand(1, 1) * sfac_sqrt * iclamped_sqrt) + p:SetEndSize(math.Rand(1.5, 1.75) * sfac_sqrt * iclamped) + p:SetRoll(math.Rand(-25, 25)) + p:SetRollDelta(math.Rand(-0.05, 0.05)) + p:SetColor(255, 255, 255) + p:SetLighting(true) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +return false +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_metal_impact/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_metal_impact/init.lua new file mode 100644 index 0000000..328635b --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_metal_impact/init.lua @@ -0,0 +1,56 @@ +local gravity_cv = GetConVar("sv_gravity") +EFFECT.VelocityRandom = 0.25 +EFFECT.VelocityMin = 95 +EFFECT.VelocityMax = 125 +EFFECT.ParticleCountMin = 4 +EFFECT.ParticleCountMax = 7 +EFFECT.ParticleLife = 1.3 + +function EFFECT:Init(data) + self.StartPos = data:GetOrigin() + self.Dir = data:GetNormal() + self.LifeTime = 0.1 + self.DieTime = CurTime() + self.LifeTime + self.PartMult = 0.2 + self.Grav = Vector(0, 0, -gravity_cv:GetFloat()) + self.SparkLife = 1 + local emitter = ParticleEmitter(self.StartPos) + local partcount = math.random(self.ParticleCountMin, self.ParticleCountMax) + + --Sparks + for _ = 1, partcount do + local part = emitter:Add("effects/yellowflare", self.StartPos) + part:SetVelocity(Lerp(self.VelocityRandom, self.Dir, VectorRand()) * math.Rand(self.VelocityMin, self.VelocityMax)) + part:SetDieTime(math.Rand(0.25, 1) * self.SparkLife) + part:SetStartAlpha(255) + part:SetStartSize(math.Rand(2, 4)) + part:SetEndSize(0) + part:SetRoll(0) + part:SetGravity(self.Grav) + part:SetCollide(true) + part:SetBounce(0.55) + part:SetAirResistance(0.5) + part:SetStartLength(0.2) + part:SetEndLength(0) + part:SetVelocityScale(true) + part:SetCollide(true) + end + + --Impact + local part = emitter:Add("effects/yellowflare", self.StartPos) + part:SetStartAlpha(255) + part:SetStartSize(15 * self.PartMult) + part:SetDieTime(self.LifeTime * 1) + part:SetEndSize(0) + part:SetEndAlpha(0) + part:SetRoll(math.Rand(0, 360)) + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +return false +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzle_smoketrail/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzle_smoketrail/init.lua new file mode 100644 index 0000000..57c614c --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzle_smoketrail/init.lua @@ -0,0 +1,124 @@ +local vector_origin = Vector() + +local smokecol = Color(225, 225, 225, 200) +local smokemat = Material("trails/smoke") +smokemat:SetInt("$nocull", 1) + +function EFFECT:AddPart() + local pos, rawdat, norm + pos = self.startpos + norm = self.startnormal + + if self.targent and self.targatt then + --pos = self:GetTracerShootPos(self.startpos, self.targent, self.targatt) + rawdat = self.targent:GetAttachment(self.targatt) + + if rawdat then + pos = rawdat.Pos + norm = rawdat.Ang:Forward() + end + end + + local p = {} + p.position = pos + p.normal = norm + p.velocity = p.normal * 5 + p.startlife = CurTime() + p.lifetime = self.lifetime + p.radius = self.radius + + if self.vparticles then + table.insert(self.vparticles, #self.vparticles + 1, p) + end +end + +function EFFECT:ProcessFakeParticles() + self.stepcount = self.stepcount + 1 + + if self.vparticles then + if CurTime() < self.emittime and self.stepcount % self.partinterval == 0 then + self:AddPart() + end + + for k, v in ipairs(self.vparticles) do + v.position = v.position + v.velocity * FrameTime() + v.velocity = v.velocity + self.grav * FrameTime() + + if CurTime() > v.startlife + v.lifetime then + --print("Curtime:"..CurTime()) + --print("Lifetime:"..v.lifetime) + --print("CTime:"..v.startlife) + table.remove(self.vparticles, k) + end + end + + if #self.vparticles <= 0 then + return false + else + return true + end + else + return true + end +end + +local cv_gr = GetConVar("sv_gravity") + +function EFFECT:Init(ef) + self.lifetime = 1 + self.stepcount = 0 + self.partinterval = 3 + self.emittime = CurTime() + 3 + self.targent = ef:GetEntity() + self.targatt = ef:GetAttachment() + self.startpos = ef:GetOrigin() + self.startnormal = ef:GetNormal() + self.radius = ef:GetRadius() + self.grav = Vector(0, 0, cv_gr:GetFloat() * 0.2) + self.randfac = 1 + + if not self.startpos then + self.startpos = vector_origin + + if LocalPlayer():IsValid() then + self.startpos = LocalPlayer():GetShootPos() + end + end + + if not self.startnormal then + self.startnormal = vector_origin + end + + if not self.radius or self.radius == 0 then + self.radius = 1 + end + + self.vparticles = {} + self:AddPart() +end + +function EFFECT:Think() + if self.vparticles and #self.vparticles <= 0 then return false end + + return true +end + +function EFFECT:DrawBeam() + render.StartBeam(#self.vparticles) + + for k, v in ipairs(self.vparticles) do + local alphac = ColorAlpha(smokecol, (1 - (CurTime() - v.startlife) / v.lifetime) * 64) + render.AddBeam(v.position, v.radius * (1 - k / #self.vparticles), k / #self.vparticles, alphac) + end + + render.EndBeam() +end + +function EFFECT:Render() + self:ProcessFakeParticles() + + if self.vparticles and #self.vparticles >= 2 then + render.SetMaterial(smokemat) + self:DrawBeam() + end +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_cryo/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_cryo/init.lua new file mode 100644 index 0000000..65b8d98 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_cryo/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.1 +EFFECT.XFlashSize = 1 +EFFECT.FlashSize = 1 +EFFECT.SmokeSize = 0 +EFFECT.SparkSize = 1.5 +EFFECT.HeatSize = 1.5 +EFFECT.Color = Color(162,192,255) +EFFECT.ColorSprites = true diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_energy/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_energy/init.lua new file mode 100644 index 0000000..0c4dcb0 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_energy/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.15 +EFFECT.XFlashSize = 1 +EFFECT.FlashSize = 1 +EFFECT.SmokeSize = 0 +EFFECT.SparkSize = 1.25 +EFFECT.HeatSize = 1 +EFFECT.Color = Color(128,192,255) +EFFECT.ColorSprites = true diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_gauss/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_gauss/init.lua new file mode 100644 index 0000000..1ec223c --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_gauss/init.lua @@ -0,0 +1,102 @@ +local blankvec = Vector(0, 0, 0) + +local vector_origin = Vector() + +function EFFECT:Init(data) + self.Position = blankvec + self.WeaponEnt = data:GetEntity() + self.WeaponEntOG = self.WeaponEnt + self.Attachment = data:GetAttachment() + self.Dir = data:GetNormal() + local owent + + if IsValid(self.WeaponEnt) then + owent = self.WeaponEnt:GetOwner() + end + + if not IsValid(owent) then + owent = self.WeaponEnt:GetParent() + end + + if IsValid(owent) and owent:IsPlayer() then + if owent ~= LocalPlayer() or owent:ShouldDrawLocalPlayer() then + self.WeaponEnt = owent:GetActiveWeapon() + if not IsValid(self.WeaponEnt) then return end + else + local theirweapon = self.WeaponEnt + self.WeaponEnt = self.WeaponEnt.OwnerViewModel + + if IsValid(theirweapon) and theirweapon.ViewModelFlip or theirweapon.ViewModelFlipped then + self.Flipped = true + end + + if not IsValid(self.WeaponEnt) then return end + end + end + + if IsValid(self.WeaponEntOG) and self.WeaponEntOG.MuzzleAttachment then + self.Attachment = self.WeaponEnt:LookupAttachment(self.WeaponEntOG.MuzzleAttachment) + + if not self.Attachment or self.Attachment <= 0 then + self.Attachment = 1 + end + + if self.WeaponEntOG:GetStatL("IsAkimbo") then + self.Attachment = 2 - self.WeaponEntOG:GetAnimCycle() + end + end + + local angpos = self.WeaponEnt:GetAttachment(self.Attachment) + + if not angpos or not angpos.Pos then + angpos = { + Pos = vector_origin, + Ang = angle_zero + } + end + + if self.Flipped then + local tmpang = (self.Dir or angpos.Ang:Forward()):Angle() + local localang = self.WeaponEnt:WorldToLocalAngles(tmpang) + localang.y = localang.y + 180 + localang = self.WeaponEnt:LocalToWorldAngles(localang) + --localang:RotateAroundAxis(localang:Up(),180) + --tmpang:RotateAroundAxis(tmpang:Up(),180) + self.Dir = localang:Forward() + end + + -- Keep the start and end Pos - we're going to interpolate between them + self.Position = self:GetTracerShootPos(angpos.Pos, self.WeaponEnt, self.Attachment) + self.Norm = self.Dir + self.vOffset = self.Position + local dir = self.Norm + local dlight + + if IsValid(self.WeaponEnt) then + dlight = DynamicLight(self.WeaponEnt:EntIndex()) + else + dlight = DynamicLight(0) + end + + local fadeouttime = 0.2 + + if (dlight) then + dlight.Pos = self.Position + dir * 1 - dir:Angle():Right() * 5 + dlight.r = 25 + dlight.g = 200 + dlight.b = 255 + dlight.Brightness = 4.0 + dlight.size = 96 + dlight.decay = 1000 + dlight.DieTime = CurTime() + fadeouttime + end + + ParticleEffectAttach("tfa_muzzle_gauss", PATTACH_POINT_FOLLOW, self.WeaponEnt, data:GetAttachment()) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_generic/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_generic/init.lua new file mode 100644 index 0000000..041f1ac --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_generic/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.075 +EFFECT.XFlashSize = 0.5 +EFFECT.FlashSize = 0.8 +EFFECT.SmokeSize = 1 +EFFECT.SparkSize = 1 +EFFECT.HeatSize = 1 +EFFECT.Color = Color(255, 225, 128) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_incendiary/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_incendiary/init.lua new file mode 100644 index 0000000..faf19a7 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_incendiary/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.125 +EFFECT.XFlashSize = 0 +EFFECT.FlashSize = 1 +EFFECT.SmokeSize = 2 +EFFECT.SparkSize = 2 +EFFECT.HeatSize = 2 +EFFECT.Color = Color(255, 128, 64) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_pistol/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_pistol/init.lua new file mode 100644 index 0000000..256af05 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_pistol/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.07 +EFFECT.XFlashSize = 0.5 +EFFECT.FlashSize = 0.8 +EFFECT.SmokeSize = 1 +EFFECT.SparkSize = 1 +EFFECT.HeatSize = 1 +EFFECT.Color = Color(255, 225, 128) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_revolver/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_revolver/init.lua new file mode 100644 index 0000000..d0856d4 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_revolver/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.1 +EFFECT.XFlashSize = 0 +EFFECT.FlashSize = 1 +EFFECT.SmokeSize = 2 +EFFECT.SparkSize = 1 +EFFECT.HeatSize = 1.25 +EFFECT.Color = Color(255, 225, 128) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_rifle/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_rifle/init.lua new file mode 100644 index 0000000..8b46c20 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_rifle/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.1 +EFFECT.XFlashSize = 1 +EFFECT.FlashSize = 1 +EFFECT.SmokeSize = 1 +EFFECT.SparkSize = 1 +EFFECT.HeatSize = 1 +EFFECT.Color = Color(255, 192, 64) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_shotgun/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_shotgun/init.lua new file mode 100644 index 0000000..645a7b5 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_shotgun/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.125 +EFFECT.XFlashSize = 0 +EFFECT.FlashSize = 1.3 +EFFECT.SmokeSize = 2 +EFFECT.SparkSize = 1.5 +EFFECT.HeatSize = 2 +EFFECT.Color = Color(255, 225, 128) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_silenced/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_silenced/init.lua new file mode 100644 index 0000000..97c15dc --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_silenced/init.lua @@ -0,0 +1,12 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.1 +EFFECT.XFlashSize = 0 +EFFECT.FlashSize = 0.1 +EFFECT.SmokeSize = 2 +EFFECT.SparkSize = 1 +EFFECT.HeatSize = 1 +EFFECT.Color = Color(255, 225, 128) +EFFECT.ColorSprites = false +EFFECT.UseDynamicLight = false + diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_smg/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_smg/init.lua new file mode 100644 index 0000000..31e3d54 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_smg/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.075 +EFFECT.XFlashSize = 1 +EFFECT.FlashSize = 1 +EFFECT.SmokeSize = 1 +EFFECT.SparkSize = 1 +EFFECT.HeatSize = 1 +EFFECT.Color = Color(255, 225, 128) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_sniper/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_sniper/init.lua new file mode 100644 index 0000000..2c55096 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_sniper/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.125 +EFFECT.XFlashSize = 1.5 +EFFECT.FlashSize = 1.2 +EFFECT.SmokeSize = 2 +EFFECT.SparkSize = 1.3 +EFFECT.HeatSize = 2 +EFFECT.Color = Color(255, 225, 128) +EFFECT.ColorSprites = false diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_sniper_energy/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_sniper_energy/init.lua new file mode 100644 index 0000000..e753a4c --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzleflash_sniper_energy/init.lua @@ -0,0 +1,10 @@ +include("tfa/muzzleflash_base.lua") + +EFFECT.Life = 0.125 +EFFECT.XFlashSize = 2 +EFFECT.FlashSize = 2 +EFFECT.SmokeSize = 0 +EFFECT.SparkSize = 1.45 +EFFECT.HeatSize = 2 +EFFECT.Color = Color(128,192,255) +EFFECT.ColorSprites = true diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_muzzlesmoke/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzlesmoke/init.lua new file mode 100644 index 0000000..88290e9 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_muzzlesmoke/init.lua @@ -0,0 +1,74 @@ +local ang +local limit_particle_cv = GetConVar("cl_tfa_fx_muzzlesmoke_limited") +local SMOKEDELAY = 1.5 + +function EFFECT:Init(data) + self.WeaponEnt = data:GetEntity() + if not IsValid(self.WeaponEnt) then return end + self.WeaponEntOG = self.WeaponEnt + if limit_particle_cv:GetBool() and self.WeaponEnt:GetOwner() ~= LocalPlayer() then return end + self.Attachment = data:GetAttachment() + local smokepart = "smoke_trail_tfa" + local delay = self.WeaponEnt.GetStatL and self.WeaponEnt:GetStatL("SmokeDelay") or self.WeaponEnt.SmokeDelay + + if self.WeaponEnt.SmokeParticle then + smokepart = self.WeaponEnt.SmokeParticle + elseif self.WeaponEnt.SmokeParticles then + smokepart = self.WeaponEnt.SmokeParticles[self.WeaponEnt.DefaultHoldType or self.WeaponEnt.HoldType] or smokepart + end + + self.Position = self:GetTracerShootPos(data:GetOrigin(), self.WeaponEnt, self.Attachment) + + if IsValid(self.WeaponEnt:GetOwner()) then + if self.WeaponEnt:GetOwner() == LocalPlayer() then + if not self.WeaponEnt:IsFirstPerson() then + ang = self.WeaponEnt:GetOwner():EyeAngles() + ang:Normalize() + --ang.p = math.max(math.min(ang.p,55),-55) + self.Forward = ang:Forward() + else + self.WeaponEnt = self.WeaponEnt.OwnerViewModel + end + --ang.p = math.max(math.min(ang.p,55),-55) + else + ang = self.WeaponEnt:GetOwner():EyeAngles() + ang:Normalize() + self.Forward = ang:Forward() + end + end + + if TFA.GetMZSmokeEnabled == nil or TFA.GetMZSmokeEnabled() then + local e = self.WeaponEnt + local w = self.WeaponEntOG + local a = self.Attachment + local tn = "tfasmokedelay_" .. w:EntIndex() .. "_" .. a + local sp = smokepart + + if timer.Exists(tn) then + timer.Remove(tn) + end + + e.SmokePCF = e.SmokePCF or {} + local _a = w:GetStatL("IsAkimbo") and a or 1 + + if IsValid(e.SmokePCF[_a]) then + e.SmokePCF[_a]:StopEmission() + end + + timer.Create(tn, delay or SMOKEDELAY, 1, function() + if not IsValid(e) then return end + e.SmokePCF[_a] = CreateParticleSystem(e, sp, PATTACH_POINT_FOLLOW, a) + + if IsValid(e.SmokePCF[_a]) then + e.SmokePCF[_a]:StartEmission() + end + end) + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_penetrate/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_penetrate/init.lua new file mode 100644 index 0000000..676e800 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_penetrate/init.lua @@ -0,0 +1,132 @@ +local PenetColor = Color(255, 255, 255, 255) +local PenetMat = Material("trails/smoke") +local PenetMat2 = Material("effects/yellowflare") +local cv_gv = GetConVar("sv_gravity") +local cv_sl = GetConVar("cl_tfa_fx_impact_ricochet_sparklife") + +--local cv_sc = GetConVar("cl_tfa_fx_impact_ricochet_sparks") +local DFX = { + ["AR2Tracer"] = true, + ["Tracer"] = true, + ["GunshipTracer"] = true, + ["GaussTracer"] = true, + ["AirboatGunTracer"] = true, + ["AirboatGunHeavyTracer"] = true +} + +function EFFECT:Init(data) + self.StartPos = data:GetOrigin() + self.Dir = data:GetNormal() + self.Dir:Normalize() + self.Len = 32 + self.EndPos = self.StartPos + self.Dir * self.Len + self.LifeTime = 0.75 + self.DieTime = CurTime() + self.LifeTime + self.Thickness = 1 + self.Grav = Vector(0, 0, -cv_gv:GetFloat()) + self.PartMult = data:GetRadius() + self.SparkLife = cv_sl:GetFloat() + self.WeaponEnt = data:GetEntity() + if not IsValid(self.WeaponEnt) then return end + + if self.WeaponEnt.TracerPCF then + local traceres = util.QuickTrace(self.StartPos, self.Dir * 9999999, Entity(math.Round(data:GetScale()))) + self.EndPos = traceres.HitPos or self.StartPos + local efn = self.WeaponEnt.TracerName + local spos = self.StartPos + local cnt = math.min(math.Round(data:GetMagnitude()), 6000) + + timer.Simple(cnt / 1000000, function() + TFA.ParticleTracer(efn, spos, traceres.HitPos or spos, false) + end) + + return + end + + local tn = self.WeaponEnt.BulletTracerName + + if tn and tn ~= "" and not DFX[tn] then + local fx = EffectData() + fx:SetStart(self.StartPos) + local traceres = util.QuickTrace(self.StartPos, self.Dir * 9999999, Entity(math.Round(data:GetScale()))) + self.EndPos = traceres.HitPos or self.StartPos + fx:SetOrigin(self.EndPos) + fx:SetEntity(self.WeaponEnt) + fx:SetMagnitude(1) + util.Effect(tn, fx) + SafeRemoveEntityDelayed(self, 0) + --Sparks + --Impact + + return + else + local emitter = ParticleEmitter(self.StartPos) + --[[ + for i = 1, cv_sc:GetFloat() * self.PartMult * 0.1 do + local part = emitter:Add("effects/yellowflare", self.StartPos) + part:SetVelocity((self.Dir + VectorRand() * 0.5) * math.Rand(75, 185)) + part:SetDieTime(math.Rand(0.25, 1) * self.SparkLife) + part:SetStartAlpha(255) + part:SetStartSize(math.Rand(2, 4)) + part:SetEndSize(0) + part:SetRoll(0) + part:SetGravity(self.Grav) + part:SetCollide(true) + part:SetBounce(0.55) + part:SetAirResistance(0.5) + part:SetStartLength(0.2) + part:SetEndLength(0) + part:SetVelocityScale(true) + part:SetCollide(true) + end + ]] + -- + local part = emitter:Add("effects/select_ring", self.StartPos) + part:SetStartAlpha(225) + part:SetStartSize(1) + part:SetDieTime(self.LifeTime / 5) + part:SetEndSize(0) + part:SetEndAlpha(0) + part:SetRoll(math.Rand(0, 360)) + part:SetColor(200, 200, 200) + part = emitter:Add("effects/select_ring", self.StartPos) + part:SetStartAlpha(255) + part:SetStartSize(1.5 * self.PartMult) + part:SetDieTime(self.LifeTime / 6) + part:SetEndSize(0) + part:SetEndAlpha(0) + part:SetRoll(math.Rand(0, 360)) + part:SetColor(200, 200, 200) + emitter:Finish() + end +end + +function EFFECT:Think() + if self.DieTime and (CurTime() > self.DieTime) then return false end + + return true +end + +function EFFECT:Render() + if self.DieTime then + local fDelta = (self.DieTime - CurTime()) / self.LifeTime + fDelta = math.Clamp(fDelta, 0, 1) + render.SetMaterial(PenetMat) + local color = ColorAlpha(PenetColor, 32 * fDelta) + local precision = 16 + local i = 1 + + while i <= precision do + render.DrawBeam(self.StartPos + self.Dir * self.Len * ((i - 1) / precision), self.StartPos + self.Dir * self.Len * (i / precision), self.Thickness * fDelta * (1 - i / precision), 0.5, 0.5, color) + i = i + 1 + end + + render.SetMaterial(PenetMat2) + i = 1 + + while i <= precision do + render.DrawBeam(self.StartPos + self.Dir * self.Len * ((i - 1) / precision), self.StartPos + self.Dir * self.Len * (i / precision), self.Thickness / 3 * 2 * fDelta * (1 - i / precision), 0.5, 0.5, color) + i = i + 1 + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_ricochet/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_ricochet/init.lua new file mode 100644 index 0000000..ee55046 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_ricochet/init.lua @@ -0,0 +1,90 @@ +local RicochetColor = Color(255, 255, 255, 255) +local RicochetIDOffset = 33 +local RicochetMat = Material("effects/yellowflare") +local cv_gv = GetConVar("sv_gravity") +local cv_sl = GetConVar("cl_tfa_fx_impact_ricochet_sparklife") +local cv_sc = GetConVar("cl_tfa_fx_impact_ricochet_sparks") + +function EFFECT:Init(data) + self.StartPos = data:GetOrigin() + self.Dir = data:GetNormal() + self.Dir:Normalize() + self.Len = 128 + self.EndPos = self.StartPos + self.Dir * self.Len + self.LifeTime = 0.1 + self.DieTime = CurTime() + self.LifeTime + self.Grav = Vector(0, 0, -cv_gv:GetFloat()) + self.PartMult = data:GetMagnitude() + self.SparkLife = cv_sl:GetFloat() + local emitter = ParticleEmitter(self.StartPos) + + --Sparks + for _ = 1, cv_sc:GetInt() * self.PartMult do + local part = emitter:Add("effects/yellowflare", self.StartPos) + part:SetVelocity((self.Dir + VectorRand() * 0.5) * math.Rand(75, 185)) + part:SetDieTime(math.Rand(0.25, 1) * self.SparkLife) + part:SetStartAlpha(255) + part:SetStartSize(math.Rand(2, 4)) + part:SetEndSize(0) + part:SetRoll(0) + part:SetGravity(self.Grav) + part:SetCollide(true) + part:SetBounce(0.55) + part:SetAirResistance(0.5) + part:SetStartLength(0.2) + part:SetEndLength(0) + part:SetVelocityScale(true) + part:SetCollide(true) + end + + --Impact + local part = emitter:Add("effects/yellowflare", self.StartPos) + part:SetStartAlpha(225) + part:SetStartSize(64) + part:SetDieTime(self.LifeTime) + part:SetEndSize(0) + part:SetEndAlpha(0) + part:SetRoll(math.Rand(0, 360)) + part = emitter:Add("effects/yellowflare", self.StartPos) + part:SetStartAlpha(255) + part:SetStartSize(30 * self.PartMult) + part:SetDieTime(self.LifeTime * 1.5) + part:SetEndSize(0) + part:SetEndAlpha(0) + part:SetRoll(math.Rand(0, 360)) + emitter:Finish() + local dlight = DynamicLight(LocalPlayer():EntIndex() + RicochetIDOffset) + + if (dlight) then + dlight.Pos = self.StartPos + dlight.r = 255 + dlight.g = 225 + dlight.b = 185 + dlight.Brightness = 2.75 * self.PartMult + dlight.size = 48 + --dlight.DieTime = CurTime() + self.DieTime*0.7 + dlight.Decay = 1000 / math.max(0.01, math.min(self.SparkLife * 0.66, 1)) + end +end + +function EFFECT:Think() + if self.DieTime and (CurTime() > self.DieTime) then return false end + + return true +end + +function EFFECT:Render() + if self.DieTime then + local fDelta = (self.DieTime - CurTime()) / self.LifeTime + fDelta = math.Clamp(fDelta, 0, 1) + render.SetMaterial(RicochetMat) + local color = ColorAlpha(RicochetColor, 255 * fDelta) + local precision = 16 + local i = 1 + + while i <= precision do + render.DrawBeam(self.StartPos + self.Dir * self.Len * ((i - 1) / precision), self.StartPos + self.Dir * self.Len * (i / precision), 8 * fDelta * (1 - i / precision), 0.5, 0.5, color) + i = i + 1 + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_shell/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_shell/init.lua new file mode 100644 index 0000000..81e3666 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_shell/init.lua @@ -0,0 +1,230 @@ +EFFECT.Velocity = {120, 160} +EFFECT.VelocityRand = {-15, 40} +EFFECT.VelocityAngle = Vector(1,1,10) +EFFECT.VelocityRandAngle = Vector(10,10,5) + +local modelReplaceLookup = { + ["models/hdweapons/rifleshell.mdl"] = "models/tfa/rifleshell.mdl", + ["models/hdweapons/rifleshell_hd.mdl"] = "models/tfa/rifleshell.mdl", + ["models/weapons/rifleshell_hd.mdl"] = "models/tfa/rifleshell.mdl", + ["models/hdweapons/shell.mdl"] = "models/tfa/pistolshell.mdl", + ["models/hdweapons/shell_hd.mdl"] = "models/tfa/pistolshell.mdl", + ["models/weapons/shell_hd.mdl"] = "models/tfa/pistolshell.mdl", + ["models/hdweapons/shotgun_shell.mdl"] = "models/tfa/shotgunshell.mdl", + ["models/hdweapons/shotgun_shell_hd.mdl"] = "models/tfa/shotgunshell.mdl", + ["models/weapons/shotgun_shell_hd.mdl"] = "models/tfa/shotgunshell.mdl", +} + +EFFECT.ShellPresets = { + ["sniper"] = {"models/tfa/rifleshell.mdl", math.pow(0.487 / 1.236636, 1 / 3), 90}, --1.236636 is shell diameter, then divide base diameter into that for 7.62x54mm + ["rifle"] = {"models/tfa/rifleshell.mdl", math.pow(0.4709 / 1.236636, 1 / 3), 90}, --1.236636 is shell diameter, then divide base diameter into that for standard nato rifle + ["pistol"] = {"models/tfa/pistolshell.mdl", math.pow(0.391 / 0.955581, 1 / 3), 90}, --0.955581 is shell diameter, then divide base diameter into that for 9mm luger + ["smg"] = {"models/tfa/pistolshell.mdl", math.pow(.476 / 0.955581, 1 / 3), 90}, --.45 acp + ["shotgun"] = {"models/tfa/shotgunshell.mdl", 1, 90} +} + +EFFECT.SoundFiles = {Sound(")player/pl_shell1.wav"), Sound(")player/pl_shell2.wav"), Sound(")player/pl_shell3.wav")} +EFFECT.SoundFilesSG = {Sound(")weapons/fx/tink/shotgun_shell1.wav"), Sound(")weapons/fx/tink/shotgun_shell2.wav"), Sound(")weapons/fx/tink/shotgun_shell3.wav")} +EFFECT.SoundLevel = {45, 55} +EFFECT.SoundPitch = {80, 120} +EFFECT.SoundVolume = {0.85, 0.95} +EFFECT.LifeTime = 15 +EFFECT.FadeTime = 0.5 +EFFECT.SmokeTime = {3, 3} +EFFECT.SmokeParticle = "tfa_ins2_weapon_shell_smoke" +local cv_eject +local cv_life +local upVec = Vector(0,0,1) + +function EFFECT:ComputeSmokeLighting() + if not self.PCFSmoke then return end + local licht = render.ComputeLighting(self:GetPos() + upVec * 2, upVec) + local lichtFloat = math.Clamp((licht.r + licht.g + licht.b) / 3, 0, TFA.Particles.SmokeLightingClamp) / TFA.Particles.SmokeLightingClamp + local lichtFinal = LerpVector(lichtFloat, TFA.Particles.SmokeLightingMin, TFA.Particles.SmokeLightingMax) + self.PCFSmoke:SetControlPoint(1, lichtFinal) +end + +function EFFECT:Init(data) + self.IsTFAShell = true + + if not cv_eject then + cv_eject = GetConVar("cl_tfa_fx_ejectionsmoke") + end + + if not cv_life then + cv_life = GetConVar("cl_tfa_fx_ejectionlife") + end + + if cv_life then + self.LifeTime = cv_life:GetFloat() + end + + self.StartTime = CurTime() + self.Emitter = ParticleEmitter(self:GetPos()) + self.SmokeDelta = 0 + + if cv_eject:GetBool() then + self.SmokeDeath = self.StartTime + math.Rand(self.SmokeTime[1], self.SmokeTime[2]) + else + self.SmokeDeath = -1 + end + + self.WeaponEnt = data:GetEntity() + if not IsValid(self.WeaponEnt) then return end + self.WeaponEntOG = self.WeaponEnt + if self.WeaponEntOG.LuaShellEffect and self.WeaponEntOG.LuaShellEffect == "" then return end + self.Attachment = data:GetAttachment() + self.Dir = data:GetNormal() + self.DirAng = data:GetNormal():Angle() + self.OriginalOrigin = data:GetOrigin() + local owent = self.WeaponEnt:GetOwner() + + if self.LifeTime <= 0 or not IsValid(owent) then + self.StartTime = -1000 + self.SmokeDeath = -1000 + + return + end + + if owent:IsPlayer() and owent == GetViewEntity() and not owent:ShouldDrawLocalPlayer() then + self.WeaponEnt = self.WeaponEnt.OwnerViewModel + if not IsValid(self.WeaponEnt) then return end + end + + local model, scale, yaw = self:FindModel(self.WeaponEntOG) + model = self.WeaponEntOG:GetStatL("ShellModel") or self.WeaponEntOG:GetStatL("LuaShellModel") or model + model = modelReplaceLookup[model] or model + scale = self.WeaponEntOG:GetStatL("ShellScale") or self.WeaponEntOG:GetStatL("LuaShellScale") or scale + yaw = self.WeaponEntOG:GetStatL("ShellYaw") or self.WeaponEntOG:GetStatL("LuaShellYaw") or yaw + + if model:lower():find("shotgun") then + self.Shotgun = true + end + + self:SetModel(model) + self:SetModelScale(scale, 0) + self:SetPos(data:GetOrigin()) + local mdlang = self.DirAng * 1 + mdlang:RotateAroundAxis(mdlang:Up(), yaw) + local owang = IsValid(owent) and owent:EyeAngles() or mdlang + self:SetAngles(owang) + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetCollisionBounds(self:OBBMins(), self:OBBMaxs()) + self:PhysicsInitBox(self:OBBMins(), self:OBBMaxs()) + local velocity = self.Dir * math.Rand(self.Velocity[1], self.Velocity[2]) + owang:Forward() * math.Rand(self.VelocityRand[1], self.VelocityRand[2]) + + if IsValid(owent) then + velocity = velocity + owent:GetVelocity() + end + + local physObj = self:GetPhysicsObject() + + if physObj:IsValid() then + physObj:SetDamping(0.1, 1) + physObj:SetMass(5) + physObj:SetMaterial("gmod_silent") + physObj:SetVelocity(velocity) + local localVel = velocity:Length() * self.WeaponEnt:WorldToLocalAngles(velocity:Angle()):Forward() + physObj:AddAngleVelocity(localVel.y * self.VelocityAngle) + physObj:AddAngleVelocity(VectorRand() * velocity:Length() * self.VelocityRandAngle * 0.5) + end + + local ss = self.WeaponEntOG:GetStatL("ShellSound") or self.WeaponEntOG:GetStatL("LuaShellSound") + + if ss then + self.ImpactSound = ss + else + self.ImpactSound = self.Shotgun and self.SoundFilesSG[math.random(1, #self.SoundFiles)] or self.SoundFiles[math.random(1, #self.SoundFiles)] + end + + self.setup = true +end + +function EFFECT:FindModel(wep) + if not IsValid(wep) then return unpack(self.ShellPresets["rifle"]) end + local ammotype = (wep.Primary.Ammo or wep:GetPrimaryAmmoType()):lower() + local guntype = (wep.Type or wep:GetHoldType()):lower() + + if guntype:find("sniper") or ammotype:find("sniper") or guntype:find("dmr") then + return unpack(self.ShellPresets["sniper"]) + elseif guntype:find("rifle") or ammotype:find("rifle") then + return unpack(self.ShellPresets["rifle"]) + elseif ammotype:find("pist") or guntype:find("pist") then + return unpack(self.ShellPresets["pistol"]) + elseif ammotype:find("smg") or guntype:find("smg") then + return unpack(self.ShellPresets["smg"]) + elseif ammotype:find("buckshot") or ammotype:find("shotgun") or guntype:find("shot") then + return unpack(self.ShellPresets["shotgun"]) + end + + return unpack(self.ShellPresets["rifle"]) +end + +function EFFECT:BounceSound() + sound.Play(self.ImpactSound, self:GetPos(), math.Rand(self.SoundLevel[1], self.SoundLevel[2]), math.Rand(self.SoundPitch[1], self.SoundPitch[2]), math.Rand(self.SoundVolume[1], self.SoundVolume[2])) +end + +function EFFECT:PhysicsCollide(data) + if self:WaterLevel() > 0 then return end + + if TFA.GetEJSmokeEnabled() and not self.PCFSmoke and CurTime() < self.SmokeDeath then + self.PCFSmoke = CreateParticleSystem(self, self.SmokeParticle, self:GetAttachment(1) ~= nil and PATTACH_POINT_FOLLOW or PATTACH_ABSORIGIN_FOLLOW, 1) + if IsValid(self.PCFSmoke) then + self:ComputeSmokeLighting() + self.PCFSmoke:StartEmission() + else + self.PCFSmoke = nil + end + end + + if data.Speed > 60 then + self:BounceSound() + local impulse = (data.OurOldVelocity - 2 * data.OurOldVelocity:Dot(data.HitNormal) * data.HitNormal) * 0.33 + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:ApplyForceCenter(impulse) + end + end +end + +function EFFECT:Think() + if CurTime() > self.SmokeDeath and self.PCFSmoke then + self.PCFSmoke:StopEmission() + self.PCFSmoke = nil + else + self:ComputeSmokeLighting() + end + + if self:WaterLevel() > 0 and not self.WaterSplashed then + self.WaterSplashed = true + local ef = EffectData() + ef:SetOrigin(self:GetPos()) + ef:SetScale(1) + util.Effect("watersplash", ef) + end + + if CurTime() > self.StartTime + self.LifeTime then + if self.Emitter then + self.Emitter:Finish() + end + + return false + else + return true + end +end + +function EFFECT:Render() + if not self.setup then return end + self:SetColor(ColorAlpha(color_white, (1 - math.Clamp(CurTime() - (self.StartTime + self.LifeTime - self.FadeTime), 0, self.FadeTime) / self.FadeTime) * 255)) + self:SetupBones() + self:DrawModel() +end + +hook.Add("EntityEmitSound", "TFA_BlockShellScrapeSound", function(sndData) + if IsValid(sndData.Entity) and sndData.Entity.IsTFAShell and sndData.SoundName:find("scrape") then + return false + end +end) diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_shell_legacy/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_shell_legacy/init.lua new file mode 100644 index 0000000..881e2ab --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_shell_legacy/init.lua @@ -0,0 +1,90 @@ +local vector_origin = Vector() + +function EFFECT:Init(data) + self.WeaponEnt = data:GetEntity() + if not IsValid(self.WeaponEnt) then return end + self.WeaponEntOG = self.WeaponEnt + self.Attachment = data:GetAttachment() + self.Dir = data:GetNormal() + local owent = self.WeaponEnt:GetOwner() + + if not IsValid(owent) then + owent = self.WeaponEnt:GetParent() + end + + if IsValid(owent) and owent:IsPlayer() then + if owent ~= LocalPlayer() or owent:ShouldDrawLocalPlayer() then + self.WeaponEnt = owent:GetActiveWeapon() + if not IsValid(self.WeaponEnt) then return end + else + local theirweapon = self.WeaponEnt + self.WeaponEnt = self.WeaponEnt.OwnerViewModel + + if IsValid(theirweapon) and theirweapon.ViewModelFlip or theirweapon.ViewModelFlipped then + self.Flipped = true + end + + if not IsValid(self.WeaponEnt) then return end + end + end + + if IsValid(self.WeaponEntOG) and self.WeaponEntOG.ShellAttachment then + self.Attachment = self.WeaponEnt:LookupAttachment(self.WeaponEntOG.ShellAttachment) + + if not self.Attachment or self.Attachment <= 0 then + self.Attachment = 2 + end + + if self.WeaponEntOG:GetStatL("IsAkimbo") then + self.Attachment = 4 - self.WeaponEntOG:GetAnimCycle() + end + + if self.WeaponEntOG.ShellAttachmentRaw then + self.Attachment = self.WeaponEntOG.ShellAttachmentRaw + end + end + + local angpos = self.WeaponEnt:GetAttachment(self.Attachment) + + if not angpos or not angpos.Pos then + angpos = { + Pos = vector_origin, + Ang = angle_zero + } + end + + if self.Flipped then + local tmpang = (self.Dir or angpos.Ang:Forward()):Angle() + local localang = self.WeaponEnt:WorldToLocalAngles(tmpang) + localang.y = localang.y + 180 + localang = self.WeaponEnt:LocalToWorldAngles(localang) + --localang:RotateAroundAxis(localang:Up(),180) + --tmpang:RotateAroundAxis(tmpang:Up(),180) + self.Dir = localang:Forward() + end + + -- Keep the start and end Pos - we're going to interpolate between them + self.Pos = self:GetTracerShootPos(angpos.Pos, self.WeaponEnt, self.Attachment) + self.Norm = angpos.Ang:Forward() --angpos.Ang:Forward() + --print(self.Norm) + self.Magnitude = data:GetMagnitude() + self.Scale = data:GetScale() + local fx = EffectData() + fx:SetOrigin(self.Pos) + fx:SetStart(self.Pos) + fx:SetEntity(self.WeaponEnt) + fx:SetAttachment(self.Attachment) + fx:SetNormal(self.Norm) + fx:SetAngles(self.Norm:Angle()) + fx:SetScale(self.Scale) + fx:SetMagnitude(self.Magnitude) + local se = (self.WeaponEntOG.LuaShellEffect or self.WeaponEntOG.Blowback_Shell_Effect) or "ShellEject" + util.Effect(se, fx) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_shelleject_smoke/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_shelleject_smoke/init.lua new file mode 100644 index 0000000..77c8dbb --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_shelleject_smoke/init.lua @@ -0,0 +1,89 @@ +local vector_origin = Vector() + +EFFECT.SmokeParticle = "tfa_ins2_shell_eject" +local upVec = Vector(0, 0, 1) + +function EFFECT:ComputeSmokeLighting(part, pos) + if not IsValid(part) then return end + local licht = render.ComputeLighting(pos + upVec * 2, upVec) + local lichtFloat = math.Clamp((licht.r + licht.g + licht.b) / 3, 0, TFA.Particles.SmokeLightingClamp) / TFA.Particles.SmokeLightingClamp + local lichtFinal = LerpVector(lichtFloat, TFA.Particles.SmokeLightingMin, TFA.Particles.SmokeLightingMax) + lichtFinal.x = math.sqrt(math.Clamp(lichtFinal.x-0.2,0,0.8)) / 0.8 + lichtFinal.y = math.sqrt(math.Clamp(lichtFinal.y-0.2,0,0.8)) / 0.8 + lichtFinal.z = math.sqrt(math.Clamp(lichtFinal.z-0.2,0,0.8)) / 0.8 + part:SetControlPoint(1, lichtFinal) +end + +function EFFECT:Init(data) + if not TFA.GetEJSmokeEnabled() then return end + self.WeaponEnt = data:GetEntity() + if not IsValid(self.WeaponEnt) then return end + self.WeaponEntOG = self.WeaponEnt + self.Attachment = data:GetAttachment() + local owent = self.WeaponEnt:GetOwner() + + if not IsValid(owent) then + owent = self.WeaponEnt:GetParent() + end + + if IsValid(owent) and owent:IsPlayer() then + if owent ~= LocalPlayer() or owent:ShouldDrawLocalPlayer() then + self.WeaponEnt = owent:GetActiveWeapon() + if not IsValid(self.WeaponEnt) then return end + else + local theirweapon = owent:GetActiveWeapon() + self.WeaponEnt = self.WeaponEnt.OwnerViewModel + + if IsValid(theirweapon) and theirweapon.ViewModelFlip or theirweapon.ViewModelFlipped then + self.Flipped = true + end + + if not IsValid(self.WeaponEnt) then return end + end + end + + if IsValid(self.WeaponEntOG) and self.WeaponEntOG.ShellAttachment then + self.Attachment = self.WeaponEnt:LookupAttachment(self.WeaponEntOG.ShellAttachment) + + if not self.Attachment or self.Attachment <= 0 then + self.Attachment = 2 + end + + if self.WeaponEntOG:GetStatL("IsAkimbo") then + self.Attachment = 3 + self.WeaponEntOG:GetAnimCycle() + end + + if self.WeaponEntOG.ShellAttachmentRaw then + self.Attachment = self.WeaponEntOG.ShellAttachmentRaw + end + end + + local angpos = self.WeaponEnt:GetAttachment(self.Attachment) + + if not angpos or not angpos.Pos then + angpos = { + Pos = vector_origin, + Ang = angle_zero + } + end + + local PCFSmoke = CreateParticleSystem(self.WeaponEnt, self.SmokeParticle, PATTACH_POINT_FOLLOW, self.Attachment) + + if IsValid(PCFSmoke) then + self:ComputeSmokeLighting(PCFSmoke, angpos.Pos) + PCFSmoke:StartEmission() + + timer.Simple(0.2, function() + if IsValid(PCFSmoke) then + PCFSmoke:StopEmission(false,true) + end + end) + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_cryo/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_cryo/init.lua new file mode 100644 index 0000000..9a19e10 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_cryo/init.lua @@ -0,0 +1,55 @@ +EFFECT.Mat = Material("effects/laser_tracer") +EFFECT.Col1 = Color(255, 255, 255, 255) --Color(225,225,225,225) +EFFECT.Col2 = Color(65, 128, 255, 200) +EFFECT.Speed = 1024*3 +EFFECT.TracerLength = 128 + +--[[--------------------------------------------------------- +Init( data table ) +-----------------------------------------------------------]] +function EFFECT:Init(data) + self.Position = data:GetStart() + self.WeaponEnt = data:GetEntity() + self.Attachment = data:GetAttachment() + + if IsValid(self.WeaponEnt) and self.WeaponEnt.GetMuzzleAttachment then + self.Attachment = self.WeaponEnt:GetMuzzleAttachment() + end + + -- 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.Normal = (self.EndPos - self.StartPos):GetNormalized() + self.Length = (self.EndPos - self.StartPos):Length() + --self.Alpha = 255 + self.Life = 0 + self.MaxLife = self.Length / self.Speed + self:SetRenderBoundsWS(self.StartPos, self.EndPos, Vector(1000,1000,1000)) + self.CurPos = self.StartPos +end + +--[[--------------------------------------------------------- +THINK +-----------------------------------------------------------]] +function EFFECT:Think() + self.Life = self.Life + FrameTime() * (1 / self.MaxLife) + --self.Alpha = 255 * ( 1 - self.Life ) + + return self.Life < 1 +end + +--[[--------------------------------------------------------- +Draw the effect +-----------------------------------------------------------]] +local lerpedcol = Color(225, 225, 225, 225) + +function EFFECT:Render() +render.SetMaterial(self.Mat) +lerpedcol.r = Lerp(self.Life, self.Col1.r, self.Col2.r) +lerpedcol.g = Lerp(self.Life, self.Col1.g, self.Col2.g) +lerpedcol.b = Lerp(self.Life, self.Col1.b, self.Col2.b) +lerpedcol.a = Lerp(self.Life, self.Col1.a, self.Col2.a) +local startbeampos = LerpVector(self.Life, self.StartPos, self.EndPos) +local endbeampos = LerpVector(self.Life + self.TracerLength / self.Length, self.StartPos, self.EndPos) +render.DrawBeam(startbeampos, endbeampos, 8, 0, 1, lerpedcol) +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_gauss/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_gauss/init.lua new file mode 100644 index 0000000..766804c --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_gauss/init.lua @@ -0,0 +1,123 @@ +local vector_origin = Vector() + +EFFECT.Thickness = 16 +EFFECT.Life = 0.25 +EFFECT.RotVelocity = 30 +EFFECT.InValid = false +local Mat_Impact = Material("effects/combinemuzzle2") +local Mat_Beam = Material("effects/tool_tracer") +local Mat_TracePart = Material("effects/select_ring") + +function EFFECT:Init(data) + self.Position = data:GetStart() + self.WeaponEnt = data:GetEntity() + self.Attachment = data:GetAttachment() + + if IsValid(self.WeaponEnt) and self.WeaponEnt.GetMuzzleAttachment then + self.Attachment = self.WeaponEnt:GetMuzzleAttachment() + end + + local owent + + if IsValid(self.WeaponEnt) then + owent = self.WeaponEnt:GetOwner() + + if not IsValid(owent) then + owent = self.WeaponEnt:GetParent() + end + end + + if IsValid(owent) and owent:IsPlayer() then + if owent ~= LocalPlayer() or owent:ShouldDrawLocalPlayer() then + self.WeaponEnt = owent:GetActiveWeapon() + if not IsValid(self.WeaponEnt) then return end + else + local theirweapon = self.WeaponEnt + self.WeaponEnt = self.WeaponEnt.OwnerViewModel + + if IsValid(theirweapon) and theirweapon.ViewModelFlip or theirweapon.ViewModelFlipped then + self.Flipped = true + end + + if not IsValid(self.WeaponEnt) then return end + end + end + + local angpos + + if IsValid(self.WeaponEnt) then + angpos = self.WeaponEnt:GetAttachment(self.Attachment) + end + + if not angpos or not angpos.Pos then + angpos = { + Pos = vector_origin, + Ang = angle_zero + } + end + + + if self.Flipped then + local tmpang = (self.Dir or angpos.Ang:Forward()):Angle() + local localang = self.WeaponEnt:WorldToLocalAngles(tmpang) + localang.y = localang.y + 180 + localang = self.WeaponEnt:LocalToWorldAngles(localang) + --localang:RotateAroundAxis(localang:Up(),180) + --tmpang:RotateAroundAxis(tmpang:Up(),180) + self.Dir = localang:Forward() + end + + -- Keep the start and end Pos - we're going to interpolate between them + if IsValid(owent) and self.Position:Distance(owent:EyePos()) > 72 then + self.WeaponEnt = nil + end + + self.StartPos = self:GetTracerShootPos(self.WeaponEnt and angpos.Pos or self.Position, self.WeaponEnt, self.Attachment) + self.EndPos = data:GetOrigin() + self.Entity:SetRenderBoundsWS(self.StartPos, self.EndPos) + self.Normal = (self.EndPos - self.StartPos):GetNormalized() + self.StartTime = 0 + self.LifeTime = self.Life + self.data = data + self.rot = 0 +end + +function EFFECT:Think() + if self.InValid then return false end + self.LifeTime = self.LifeTime - FrameTime() + self.StartTime = self.StartTime + FrameTime() + + return self.LifeTime > 0 +end + +local beamcol = table.Copy(color_white) +local beamcol2 = Color(0, 225, 255, 255) + +function EFFECT:Render() + if self.InValid then return false end + self.StartPos = self:GetTracerShootPos(self.StartPos, self.WeaponEnt, self.Attachment) + local startPos = self.StartPos + local endPos = self.EndPos + local tracerpos + beamcol.a = self.LifeTime / self.Life * 255 + self.rot = self.rot + FrameTime() * self.RotVelocity + render.SetMaterial(Mat_Impact) + render.DrawSprite(endPos, 12, 12, ColorAlpha(color_white, beamcol.a)) + render.SetMaterial(Mat_TracePart) + tracerpos = Lerp(math.Clamp(self.LifeTime / self.Life - 0.1, 0, 1), endPos, startPos) + render.DrawQuadEasy(tracerpos, self.Normal, 12, 12, beamcol2, self.rot - 60) + tracerpos = Lerp(math.Clamp(self.LifeTime / self.Life - 0.05, 0, 1), endPos, startPos) + render.DrawQuadEasy(tracerpos, self.Normal, 12, 12, beamcol2, self.rot - 30) + tracerpos = Lerp(math.Clamp(self.LifeTime / self.Life, 0, 1), endPos, startPos) + render.DrawQuadEasy(tracerpos, self.Normal, 12, 12, beamcol2, self.rot) + tracerpos = Lerp(math.Clamp(self.LifeTime / self.Life + 0.05, 0, 1), endPos, startPos) + render.DrawQuadEasy(tracerpos, self.Normal, 12, 12, beamcol2, self.rot + 30) + tracerpos = Lerp(math.Clamp(self.LifeTime / self.Life + 0.1, 0, 1), endPos, startPos) + render.DrawQuadEasy(tracerpos, self.Normal, 12, 12, beamcol2, self.rot + 60) + tracerpos = Lerp(math.Clamp(self.LifeTime / self.Life + 0.15, 0, 1), endPos, startPos) + render.DrawQuadEasy(tracerpos, self.Normal, 12, 12, beamcol2, self.rot + 30) + tracerpos = Lerp(math.Clamp(self.LifeTime / self.Life + 0.2, 0, 1), endPos, startPos) + render.DrawQuadEasy(tracerpos, self.Normal, 12, 12, beamcol2, self.rot + 60) + render.SetMaterial(Mat_Beam) + render.DrawBeam(startPos, endPos, self.Thickness, 0 + beamcol.a / 128, endPos:Distance(startPos) / 64 + beamcol.a / 128, beamcol) +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_incendiary/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_incendiary/init.lua new file mode 100644 index 0000000..ab91176 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_incendiary/init.lua @@ -0,0 +1,55 @@ +EFFECT.Mat = Material("effects/laser_tracer") +EFFECT.Col1 = Color(255, 90, 25, 200) --Color(225,225,225,225) +EFFECT.Col2 = Color(225, 25, 25, 200) +EFFECT.Speed = 8192 +EFFECT.TracerLength = 128 + +--[[--------------------------------------------------------- +Init( data table ) +-----------------------------------------------------------]] +function EFFECT:Init(data) + self.Position = data:GetStart() + self.WeaponEnt = data:GetEntity() + self.Attachment = data:GetAttachment() + + if IsValid(self.WeaponEnt) and self.WeaponEnt.GetMuzzleAttachment then + self.Attachment = self.WeaponEnt:GetMuzzleAttachment() + end + + -- 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.Normal = (self.EndPos - self.StartPos):GetNormalized() + self.Length = (self.EndPos - self.StartPos):Length() + --self.Alpha = 255 + self.Life = 0 + self.MaxLife = self.Length / self.Speed + self:SetRenderBoundsWS(self.StartPos, self.EndPos) + self.CurPos = self.StartPos +end + +--[[--------------------------------------------------------- +THINK +-----------------------------------------------------------]] +function EFFECT:Think() + self.Life = self.Life + FrameTime() * (1 / self.MaxLife) + --self.Alpha = 255 * ( 1 - self.Life ) + + return self.Life < 1 +end + +--[[--------------------------------------------------------- +Draw the effect +-----------------------------------------------------------]] +local lerpedcol = Color(225, 225, 225, 225) + +function EFFECT:Render() + render.SetMaterial(self.Mat) + lerpedcol.r = Lerp(self.Life, self.Col1.r, self.Col2.r) + lerpedcol.g = Lerp(self.Life, self.Col1.g, self.Col2.g) + lerpedcol.b = Lerp(self.Life, self.Col1.b, self.Col2.b) + lerpedcol.a = Lerp(self.Life, self.Col1.a, self.Col2.a) + local startbeampos = Lerp(self.Life, self.StartPos, self.EndPos) + local endbeampos = Lerp(self.Life + self.TracerLength / self.Length, self.StartPos, self.EndPos) + render.DrawBeam(startbeampos, endbeampos, 8, 0, 1, lerpedcol) +end diff --git a/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_plasma/init.lua b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_plasma/init.lua new file mode 100644 index 0000000..03023c0 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/effects/tfa_tracer_plasma/init.lua @@ -0,0 +1,55 @@ +EFFECT.Mat = Material("effects/laser_tracer") +EFFECT.Col1 = Color(128, 255, 255) --Color(225,225,225,225) +EFFECT.Col2 = Color(97, 218, 255) +EFFECT.Speed = 4096 +EFFECT.TracerLength = 128 + +--[[--------------------------------------------------------- +Init( data table ) +-----------------------------------------------------------]] +function EFFECT:Init(data) + self.Position = data:GetStart() + self.WeaponEnt = data:GetEntity() + self.Attachment = data:GetAttachment() + + if IsValid(self.WeaponEnt) and self.WeaponEnt.GetMuzzleAttachment then + self.Attachment = self.WeaponEnt:GetMuzzleAttachment() + end + + -- 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.Normal = (self.EndPos - self.StartPos):GetNormalized() + self.Length = (self.EndPos - self.StartPos):Length() + --self.Alpha = 255 + self.Life = 0 + self.MaxLife = self.Length / self.Speed + self:SetRenderBoundsWS(self.StartPos, self.EndPos) + self.CurPos = self.StartPos +end + +--[[--------------------------------------------------------- +THINK +-----------------------------------------------------------]] +function EFFECT:Think() + self.Life = self.Life + FrameTime() * (1 / self.MaxLife) + --self.Alpha = 255 * ( 1 - self.Life ) + + return self.Life < 1 +end + +--[[--------------------------------------------------------- +Draw the effect +-----------------------------------------------------------]] +local lerpedcol = Color(225, 225, 225, 225) + +function EFFECT:Render() +render.SetMaterial(self.Mat) +lerpedcol.r = Lerp(self.Life, self.Col1.r, self.Col2.r) +lerpedcol.g = Lerp(self.Life, self.Col1.g, self.Col2.g) +lerpedcol.b = Lerp(self.Life, self.Col1.b, self.Col2.b) +lerpedcol.a = Lerp(self.Life, self.Col1.a, self.Col2.a) +local startbeampos = Lerp(self.Life, self.StartPos, self.EndPos) +local endbeampos = Lerp(self.Life + self.TracerLength / self.Length, self.StartPos, self.EndPos) +render.DrawBeam(startbeampos, endbeampos, 8, 0, 1, lerpedcol) +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_ammo_base.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_ammo_base.lua new file mode 100644 index 0000000..a632c95 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_ammo_base.lua @@ -0,0 +1,211 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.PrintName = "AmmoBase" +ENT.Category = "TFA Ammunition" +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.Class = "" +ENT.MyModel = "models/props_junk/popcan01a.mdl" +ENT.ImpactSound = "Default.ImpactSoft" +ENT.AmmoCount = 100 +ENT.AmmoType = "357" +ENT.TextPosition = Vector(-2.5, -3.3, 4) +ENT.TextAngles = Vector(48, -90, 0) +ENT.TextColor = Color(240, 35, 35, 255) +ENT.DrawText = false +ENT.ShouldDrawShadow = true +ENT.ImpactSound = "Default.ImpactSoft" +ENT.DamageThreshold = 80 +ENT.ExplosionOffset = Vector(0, 0, 10) +ENT.Damage = 30 +ENT.TextOffX = 30 +ENT.TextOffY = -20 +ENT.TextScale = 1 + +if SERVER then + AddCSLuaFile() + + function ENT:SpawnFunction(ply, tr, classname) + if (not tr.Hit) then return end + local pos = tr.HitPos + tr.HitNormal * 4 + local ent = ents.Create(classname) + ent:SetPos(pos) + ent:Spawn() + ent:Activate() + ent.Class = classname + ent.Spawner = ply + + return ent + end + + function ENT:Initialize() + local model = self.MyModel + self.Class = self:GetClass() + self:SetModel(model) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:DrawShadow(self.ShouldDrawShadow) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:SetUseType(SIMPLE_USE) + self:SetHealth(self.DamageThreshold) + self:SetNW2Bool("ShouldRemove", false) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + end + end + + function ENT:PhysicsCollide(data, physobj) + if (data.Speed > 60 and data.DeltaTime > 0.2) then + self:EmitSound(self.ImpactSound) + end + end + + function ENT:Use(activator, caller) + if IsValid(activator) and activator:IsPlayer() then + activator:GiveAmmo(self.AmmoCount, self.AmmoType) + self:SetNW2Bool("ShouldRemove", true) + end + end + + local bul = {} + local randvec = Vector(0, 0, 0) + bul.Tracer = 3 + bul.Num = 1 + bul.TracerName = "Tracer" + bul.Spread = Vector(0, 0, 0) + + local cv_dc = GetConVar("sv_tfa_ammo_detonation_chain") + local cv_dm = GetConVar("sv_tfa_ammo_detonation_mode") + function ENT:OnTakeDamage(dmginfo) + if not IsValid(self) then return end + local at = dmginfo:GetInflictor() + local shouldtakedamage = true + + if IsValid(at) then + local base = at.Base + + if (base and string.find(base, "tfa_ammo_base")) or string.find(at:GetClass(), "tfa_ammo_") and not cv_dc:GetBool() then + shouldtakedamage = false + end + end + + if dmginfo:GetDamage() < 1 then + shouldtakedamage = false + end + + self.Attacker = at + + if shouldtakedamage then + self:SetHealth(self:Health() - dmginfo:GetDamage()) + end + + self:EmitSound(self.ImpactSound) + local phy = self:GetPhysicsObject() + + if IsValid(phy) then + local f = dmginfo:GetDamageForce() + local p = dmginfo:GetDamagePosition() + + if f and p then + phy:ApplyForceOffset(f / 4, p) + end + end + end + + function ENT:Think() + if self:GetNW2Bool("ShouldRemove", false) then + self:Remove() + + return false + end + + if not cv_dc:GetBool() then return true end + + if self:Health() <= 0 then + self:EmitSound(self.ImpactSound) + local adm = cv_dm:GetInt() + bul.AmmoType = self.AmmoType + bul.Damage = self.Damage + bul.Force = math.Max(self.Damage / 25, 0.1) + bul.Attacker = self + + if IsValid(self.Attacker) then + bul.Attacker = self.Attacker + end + + local upang = self:GetAngles():Up() + bul.Dir = upang + randvec * 0.75 + local numbuls = math.random(math.Round(self.AmmoCount * 0.25), math.Round(self.AmmoCount * 0.75)) + local i = 1 + + if adm == 2 then + bul.Damage = bul.Damage / 2 + end + + bul.Dir = (upang + randvec * 0.75):GetNormalized() + bul.Src = self:GetPos() + self:FireBullets(bul) + + if adm ~= 1 then + while i <= math.Clamp(numbuls, 1, 35) do + randvec.x = math.Rand(-1, 1) + randvec.y = math.Rand(-1, 1) + randvec.z = math.Rand(-1, 1) + bul.Dir = (upang + randvec * 0.75):GetNormalized() + bul.Src = self:GetPos() + self:FireBullets(bul) + i = i + 1 + end + end + + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetMagnitude(0.1) + effectdata:SetScale(0.5) + + if adm == 1 then + bul.Damage = bul.Damage * 3 / 4 + end + + if adm > 0 then + util.BlastDamage(bul.Attacker, bul.Attacker, bul.Src, (bul.Damage * 6 + 128) / 2, bul.Damage * 2) + util.Effect("Explosion", effectdata) + end + + if adm ~= 1 then + util.Effect("cball_explode", effectdata) + end + + self:SetNW2Bool("ShouldRemove", true) + end + end +end + +if CLIENT then + function ENT:Initialize() + self.Class = self:GetClass() + end + + function ENT:Draw() + self:DrawModel() + + if self.TextPosition and self.TextAngles and self.DrawText then + local pos = self:GetPos() + (self:GetUp() * self.TextPosition.z) + (self:GetRight() * self.TextPosition.x) + (self:GetForward() * self.TextPosition.y) + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Right(), self.TextAngles.x) + ang:RotateAroundAxis(ang:Up(), self.TextAngles.y) + ang:RotateAroundAxis(ang:Forward(), self.TextAngles.z) + + if not self.Text then + self.Text = string.upper(self.AmmoType) + end + + cam.Start3D2D(pos, ang, .07 * self.TextScale) + draw.SimpleText(self.Text, "DermaLarge", self.TextOffX, self.TextOffY, self.TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/cl_init.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/cl_init.lua new file mode 100644 index 0000000..0f748b0 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/cl_init.lua @@ -0,0 +1,9 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/init.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/init.lua new file mode 100644 index 0000000..a442b11 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/init.lua @@ -0,0 +1,79 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.DefaultModel = Model("models/weapons/w_eq_fraggrenade.mdl") + +ENT.Damage = 100 +ENT.Delay = 3 + +function ENT:Initialize() + local mdl = self:GetModel() + + if not mdl or mdl == "" or mdl == "models/error.mdl" then + self:SetModel(self.DefaultModel) + end + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + + self:SetFriction(self.Delay) + self.killtime = CurTime() + self.Delay + self:DrawShadow(true) + + if not self.Inflictor and self:GetOwner():IsValid() and self:GetOwner():GetActiveWeapon():IsValid() then + self.Inflictor = self:GetOwner():GetActiveWeapon() + end +end + +function ENT:Think() + if self.killtime < CurTime() then + self:Explode() + + return false + end + + self:NextThink(CurTime()) + + return true +end + +ENT.ExplosionSound = "BaseExplosionEffect.Sound" + +function ENT:DoExplosionEffect() + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + + util.Effect("HelicopterMegaBomb", effectdata) + util.Effect("Explosion", effectdata) + + self:EmitSoundNet(self.ExplosionSound) +end + +function ENT:Explode() + if not IsValid(self.Inflictor) then + self.Inflictor = self + end + + self.Damage = self.mydamage or self.Damage + + local dmg = DamageInfo() + dmg:SetInflictor(self.Inflictor) + dmg:SetAttacker(IsValid(self:GetOwner()) and self:GetOwner() or self) + dmg:SetDamage(self.Damage) + dmg:SetDamageType(bit.bor(DMG_BLAST, DMG_AIRBOAT)) + + util.BlastDamageInfo(dmg, self:GetPos(), math.pow( self.Damage / 100, 0.75) * 200) + + util.ScreenShake(self:GetPos(), self.Damage * 20, 255, self.Damage / 200, math.pow(self.Damage / 100, 0.75) * 400) + + self:DoExplosionEffect() + + self:Remove() +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/shared.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/shared.lua new file mode 100644 index 0000000..0dc5165 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_base/shared.lua @@ -0,0 +1,32 @@ +ENT.Type = "anim" +ENT.PrintName = "Base Explosive" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true + +local sp = game.SinglePlayer() + +function ENT:EmitSoundNet(sound) + if CLIENT or sp then + if sp and not IsFirstTimePredicted() then return end + + self:EmitSound(sound) + + return + end + + local filter = RecipientFilter() + filter:AddPAS(self:GetPos()) + if IsValid(self:GetOwner()) then + filter:RemovePlayer(self:GetOwner()) + end + + net.Start("tfaSoundEvent", true) + net.WriteEntity(self) + net.WriteString(sound) + net.WriteBool(false) + net.Send(filter) +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_exp_contact.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_contact.lua new file mode 100644 index 0000000..5e76106 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_contact.lua @@ -0,0 +1,10 @@ +AddCSLuaFile() + +ENT.Base = "tfa_exp_base" +ENT.PrintName = "Contact Explosive" + +function ENT:PhysicsCollide(data, phys) + if data.Speed > 60 then + self.killtime = -1 + end +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_exp_rocket.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_rocket.lua new file mode 100644 index 0000000..79b4c7b --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_rocket.lua @@ -0,0 +1,96 @@ +AddCSLuaFile() + +ENT.Base = "tfa_exp_base" +ENT.PrintName = "Rocket-Propelled Explosive" + +-- EDITABLE PARAMETERS -- START + +ENT.LaunchSound = "" -- none, replace to enable +ENT.PropelSound = Sound("Missile.Accelerate") -- looped propel sound + +ENT.BaseSpeed = 500 -- base rocket speed, in units + +ENT.AccelerationTime = 0.25 -- time in seconds to accelerate to max speed +ENT.MaxSpeed = 1500 -- maximum speed, works if AccelerationTime > 0 + +ENT.HasTrail = true -- create trail + +-- EDITABLE PARAMETERS -- END + +ENT.AccelProgress = 0 + +ENT.DefaultModel = Model("models/weapons/w_missile.mdl") +ENT.Delay = 10 + +DEFINE_BASECLASS(ENT.Base) + +-- Creates HL2 rocket trail by default, feel free to copy and edit to your needs +function ENT:CreateRocketTrail() + if not SERVER then return end + + local rockettrail = ents.Create("env_rockettrail") + rockettrail:DeleteOnRemove(self) + + rockettrail:SetPos(self:GetPos()) + rockettrail:SetAngles(self:GetAngles()) + rockettrail:SetParent(self) + rockettrail:SetMoveType(MOVETYPE_NONE) + rockettrail:AddSolidFlags(FSOLID_NOT_SOLID) + + rockettrail:SetSaveValue("m_Opacity", 0.2) + rockettrail:SetSaveValue("m_SpawnRate", 100) + rockettrail:SetSaveValue("m_ParticleLifetime", 0.5) + rockettrail:SetSaveValue("m_StartColor", Vector(0.65, 0.65, 0.65)) + rockettrail:SetSaveValue("m_EndColor", Vector(0, 0, 0)) + rockettrail:SetSaveValue("m_StartSize", 8) + rockettrail:SetSaveValue("m_EndSize", 32) + rockettrail:SetSaveValue("m_SpawnRadius", 4) + rockettrail:SetSaveValue("m_MinSpeed", 2) + rockettrail:SetSaveValue("m_MaxSpeed", 16) + rockettrail:SetSaveValue("m_nAttachment", 0) + rockettrail:SetSaveValue("m_flDeathTime", CurTime() + 999) + + rockettrail:Activate() + rockettrail:Spawn() +end + +function ENT:Initialize(...) + BaseClass.Initialize(self, ...) + + self:EmitSoundNet(self.PropelSound) + + if self.LaunchSound and self.LaunchSound ~= "" then + self:EmitSoundNet(self.LaunchSound) + end + + self:SetFriction(0) + self:SetLocalAngularVelocity(angle_zero) + + self:SetMoveType(bit.bor(MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE)) + self:SetLocalVelocity(self:GetForward() * self.BaseSpeed) + + if self.HasTrail then + self:CreateRocketTrail() + end +end + +function ENT:Think(...) + if self.AccelerationTime > 0 and self.AccelProgress < 1 then + self.LastAccelThink = self.LastAccelThink or CurTime() + self.AccelProgress = Lerp((CurTime() - self.LastAccelThink) / self.AccelerationTime, self.AccelProgress, 1) + end + + self:SetLocalVelocity(self:GetForward() * Lerp(self.AccelProgress, self.BaseSpeed, self.MaxSpeed)) + + return BaseClass.Think(self, ...) +end + +function ENT:Explode(...) + self:StopSound(self.PropelSound) + + return BaseClass.Explode(self, ...) +end + +function ENT:Touch() + self.killtime = -1 +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_exp_timed.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_timed.lua new file mode 100644 index 0000000..1d83ff2 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_exp_timed.lua @@ -0,0 +1,17 @@ +AddCSLuaFile() + +ENT.Base = "tfa_exp_base" +ENT.PrintName = "Timed Explosive" + +ENT.BounceSound = Sound("HEGrenade.Bounce") + +function ENT:PhysicsCollide(data, phys) + if data.Speed > 60 then + if self.BounceSound then + self:EmitSoundNet(self.BounceSound) + end + + local impulse = (data.OurOldVelocity - 2 * data.OurOldVelocity:Dot(data.HitNormal) * data.HitNormal) * 0.25 + phys:ApplyForceCenter(impulse) + end +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/cl_init.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/cl_init.lua new file mode 100644 index 0000000..82fb317 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/cl_init.lua @@ -0,0 +1,5 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/init.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/init.lua new file mode 100644 index 0000000..7c314da --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/init.lua @@ -0,0 +1,176 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +function ENT:Initialize() + local mdl = self:GetModel() + + if not mdl or mdl == "" or string.find(mdl, "error") then + self:SetModel("models/weapons/w_knife_t.mdl") + end + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + self:NextThink(CurTime() + 1) + + if (phys:IsValid()) then + phys:Wake() + phys:SetMass(10) + end + + for _, v in pairs(self.HitSounds) do + for _, o in pairs(v) do + util.PrecacheSound(o) + end + end + + local bounds = self:OBBMaxs() - self:OBBMins() + + if bounds.z > bounds.x and bounds.z > bounds.y then + self.up = true + elseif bounds.y > bounds.x and bounds.y > bounds.z then + self.right = true + end + + self:SetUseType(SIMPLE_USE) + + self.mydamage = self.mydamage or 40 + + self.DestroyTime = CurTime() + 30 +end + +function ENT:Think() + if CurTime() > self.DestroyTime then + self:Remove() + end +end + +function ENT:Stick() + self.DestroyTime = CurTime() + 60 + timer.Simple(0,function() + if IsValid(self) then + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:GetPhysicsObject():EnableMotion(false) + end + end) +end + +function ENT:PhysicsCollide(data, phys) + timer.Simple(0,function() + if not IsValid(self) then return end + local owner = self:GetOwner() + self:SetOwner(nil) + local fwdang = self:GetAngles() + local fwdvec + + if self.up then + fwdvec = fwdang:Up() + elseif self.right then + fwdvec = fwdang:Right() + else + fwdvec = fwdang:Forward() + end + + local ent = data.HitEntity + if not IsValid(ent) and not (ent and ent:IsWorld()) then return end + local dmg = self.mydamage * math.sqrt(data.Speed / 1500) + + if dmg > 5 and ent and not ent:IsWorld() then + local dmginfo = DamageInfo() + dmginfo:SetDamage(dmg) + dmginfo:SetDamagePosition(data.HitPos) + dmginfo:SetDamageForce(data.OurOldVelocity) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_SLASH) + local att = self:GetPhysicsAttacker() + + if not IsValid(att) then + att = owner + end + + if not IsValid(att) then + att = self + end + + dmginfo:SetAttacker(att) + ent:TakeDamageInfo(dmginfo) + end + + local traceres = util.QuickTrace(self:GetPos(), data.OurOldVelocity, self) + if not traceres.HitPos then return end + + if data.Speed > 50 then + local soundtbl + + if self.HitSounds[traceres.MatType] then + soundtbl = self.HitSounds[traceres.MatType] + else + soundtbl = self.HitSounds[MAT_DIRT] + end + + local snd = soundtbl[math.random(1, #soundtbl)] + self:EmitSound(snd) + end + + local dp = traceres.HitNormal:Dot(fwdvec) + + if dp >= -0.3 then + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetMagnitude(1) + fx:SetScale((data.Speed / 1500 * (dp + 0.6)) / 5) + util.Effect("Sparks", fx) + end + + local canstick = data.Speed > 250 and dp < (-1 + data.Speed / 1000 * 0.3) + + if ent:IsWorld() and canstick then + util.Decal("ManhackCut", traceres.HitPos + traceres.HitNormal, traceres.HitPos - traceres.HitNormal) + self:EmitSound(self.ImpactSound) + self:SetPos(traceres.HitPos + traceres.HitNormal * 12) + local tmpang = data.HitNormal:Angle() + tmpang:RotateAroundAxis(tmpang:Right(), 270) + --self:SetAngles(tmpang) + local fx = EffectData() + fx:SetOrigin(data.HitPos) + fx:SetMagnitude(2) + fx:SetScale(0.1) + util.Effect("Sparks", fx) + self:Stick() + elseif IsValid(ent) then + if not (ent:IsPlayer() or ent:IsNPC() or ent:GetClass() == "prop_ragdoll") then + if canstick then + util.Decal("ManhackCut", data.HitPos + data.HitNormal, data.HitPos - data.HitNormal) + end + + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + else + local fx = EffectData() + fx:SetOrigin(data.HitPos) + util.Effect("BloodImpact", fx) + self:GetPhysicsObject():SetVelocity(-(data.OurOldVelocity / 8)) + end + if IsValid(self) then + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + end + end + + if canstick then + self:GetPhysicsObject():SetVelocity(-(data.OurOldVelocity / 16)) + end + + self:GetPhysicsObject():AddAngleVelocity(-self:GetPhysicsObject():GetAngleVelocity() / 3) + end) +end + +function ENT:Use(ply, caller) + local classname = self:GetNW2String("ClassName") + if not classname or classname == "" then return end + + if ply:IsPlayer() and ply:GetWeapon(classname) == NULL then + ply:Give(classname) + self:Remove() + end +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/shared.lua b/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/shared.lua new file mode 100644 index 0000000..431c6ae --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfa_thrown_blade/shared.lua @@ -0,0 +1,14 @@ +ENT.Type = "anim" +ENT.PrintName = "Thrown Blade" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true + +ENT.HitSounds = { + [MAT_DIRT] = {Sound("physics/metal/metal_grenade_impact_hard1.wav"), Sound("physics/metal/metal_grenade_impact_hard2.wav"), Sound("physics/metal/metal_grenade_impact_hard3.wav")}, + [MAT_FLESH] = {Sound("physics/flesh/flesh_impact_bullet1.wav"), Sound("physics/flesh/flesh_impact_bullet2.wav"), Sound("physics/flesh/flesh_impact_bullet3.wav")} +} + +ENT.ImpactSound = Sound("weapons/blades/impact.mp3") diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/cl_init.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/cl_init.lua new file mode 100644 index 0000000..7e30698 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/cl_init.lua @@ -0,0 +1,23 @@ +include("shared.lua") +local cv_ht = GetConVar("host_timescale") + +function ENT:Draw() + local ang, tmpang + tmpang = self:GetAngles() + ang = tmpang + + if not self.roll then + self.roll = 0 + end + + local phobj = self:GetPhysicsObject() + + if IsValid(phobj) then + self.roll = self.roll + phobj:GetVelocity():Length() / 3600 * cv_ht:GetFloat() + end + + ang:RotateAroundAxis(ang:Forward(), self.roll) + self:SetAngles(ang) + self:DrawModel() -- Draw the model. + self:SetAngles(tmpang) +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/init.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/init.lua new file mode 100644 index 0000000..6f71ae5 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/init.lua @@ -0,0 +1,3 @@ +AddCSLuaFile("cl_init.lua") -- Make sure clientside +AddCSLuaFile("shared.lua") -- and shared scripts are sent. +include("shared.lua") diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/shared.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/shared.lua new file mode 100644 index 0000000..6a261a6 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow/shared.lua @@ -0,0 +1,247 @@ +local vector_origin = Vector() + +ENT.Type = "anim" +ENT.PrintName = "TFBow Arrow" +ENT.Author = "TheForgottenArchitect" +ENT.Contact = "Don't" +ENT.Purpose = "Arrow Entity" +ENT.Instructions = "Spawn this with a velocity, get rich" + +local function GetBoneCenter(ent, bone) + local bonechildren = ent:GetChildBones(bone) + + if #bonechildren <= 0 then + return ent:GetBonePosition(bone) + else + local bonepos = ent:GetBonePosition(bone) + local tmppos = bonepos + + if tmppos then + for i = 1, #bonechildren do + local childpos = ent:GetBonePosition(bonechildren[i]) + + if childpos then + tmppos = (tmppos + childpos) / 2 + end + end + else + return ent:GetPos() + end + + return tmppos + end +end + +function ENT:GetClosestBonePos(ent, pos) + local i, count, dist, ppos, cbone + i = 1 + count = ent:GetBoneCount() + cbone = 0 + dist = 99999999 + ppos = ent:GetPos() + + while (i < count) do + local bonepos = GetBoneCenter(ent, i) + + if bonepos:Distance(pos) < dist then + dist = bonepos:Distance(pos) + cbone = i + ppos = bonepos + end + + i = i + 1 + end + + return ppos, cbone +end + +local cv_al = GetConVar("sv_tfa_arrow_lifetime") +local cv_ht = GetConVar("host_timescale") + +function ENT:Initialize() + if SERVER then + if not IsValid(self.myowner) then + self.myowner = self:GetOwner() + + if not IsValid(self.myowner) then + self.myowner = self + end + end + + timer.Simple(0, function() + if self.model then + self:SetModel(self.model) + end + end) + + if cv_al:GetInt() ~= -1 then + timer.Simple( cv_al:GetFloat() + 5, function() + if IsValid(self) then + self:Remove() + end + end) + end + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + + if self.velocity then + phys:SetVelocityInstantaneous(self.velocity) + end + + phys:EnableCollisions(false) + self:StartMotionController() + self:PhysicsUpdate(phys, 0.1 * cv_ht:GetFloat() ) + end + end + + self:SetNW2Vector("lastpos", self:GetPos()) + + if not self.mydamage then + self.mydamage = 60 + end + + if not self.gun then + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + self:UpdateGun() + else + timer.Simple(0, function() + if IsValid(self) and IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + self:UpdateGun() + end + end) + end + end +end + +function ENT:UpdateGun() + local wep = self:GetOwner():GetActiveWeapon() + + if IsValid(wep) then + self.gun = wep:GetClass() + end +end + +local wl,tracedata,tr + +local cv_fm = GetConVar("sv_tfa_force_multiplier") + +function ENT:HitCB(a,b,c) + c:SetDamageType(bit.bor(DMG_NEVERGIB, DMG_CLUB)) + + if IsValid(self) and IsValid(self:GetOwner()) then + if b.HitWorld then + local arrowstuck = ents.Create("tfbow_arrow_stuck") + arrowstuck:SetModel(self:GetModel()) + arrowstuck.gun = self.gun + arrowstuck:SetPos(tr.HitPos) + local phys = self:GetPhysicsObject() + arrowstuck:SetAngles((phys:GetVelocity()):Angle()) + arrowstuck:Spawn() + else + if IsValid(b.Entity) then + if (not b.Entity:IsWorld()) then + local arrowstuck = ents.Create("tfbow_arrow_stuck_clientside") + arrowstuck:SetModel(self:GetModel()) + arrowstuck:SetPos(tr.HitPos) + local ang = self:GetAngles() + arrowstuck.gun = self.gun + arrowstuck:SetAngles(ang) + arrowstuck.targent = tr.Entity + arrowstuck.targphysbone = tr.PhysicsBone + arrowstuck:Spawn() + else + local arrowstuck = ents.Create("tfbow_arrow_stuck") + arrowstuck:SetModel(self:GetModel()) + arrowstuck.gun = self.gun + arrowstuck:SetPos(tr.HitPos) + arrowstuck:SetAngles(self:GetAngles()) + arrowstuck:Spawn() + end + end + end + + self:Remove() + elseif IsValid(self) then + self:Remove() + end +end + +function ENT:Think() + wl = self:WaterLevel() + + if not self.prevwaterlevel then + self.prevwaterlevel = wl + end + + if self.prevwaterlevel ~= wl and wl - self.prevwaterlevel >= 1 then + --print(wl) + local ef = EffectData() + ef:SetOrigin(self:GetPos()) + util.Effect("watersplash", ef) + end + + self.prevwaterlevel = wl + + if wl >= 2 then + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:SetVelocity(phys:GetVelocity() * math.sqrt(9 / 10)) + end + end + + tracedata = {} + tracedata.start = self:GetNW2Vector("lastpos", self:GetPos()) + tracedata.endpos = self:GetPos() + tracedata.mask = MASK_SOLID + tracedata.filter = {self.myowner, self:GetOwner(), self} + tr = util.TraceLine(tracedata) + + --self:SetAngles((((tracedata.endpos-tracedata.start):GetNormalized()+self:GetAngles():Forward())/2):Angle()) + if (tr.Hit and tr.Fraction < 1 and tr.Fraction > 0) then + debugoverlay.Line(tracedata.start, tr.HitPos, 10, Color(255, 0, 0, 255), true) + debugoverlay.Cross(tr.HitPos, 5, 10, Color(255, 0, 0, 255), true) + + if SERVER then + --[[ + local bul ={} + bul.Attacker=self:GetOwner() and self:GetOwner() or self:GetOwner() + bul.Spread=vector_origin + bul.Src=tracedata.start + bul.Force=self.mydamage*0.25*GetConVarNumber("sv_tfbow_force_multiplier",1) + bul.Damage=self.mydamage + bul.Tracer = 0 -- Show a tracer on every x bullets + bul.TracerName = "None" + bul.Dir=((tr.HitPos-bul.Src):GetNormalized()) + + bul.Attacker:FireBullets( bul ) + ]] + -- + local bul = {} + bul.Attacker = self:GetOwner() and self:GetOwner() or self:GetOwner() + bul.Spread = vector_origin + bul.Src = tracedata.start + bul.Force = self.mydamage * 0.25 * cv_fm:GetFloat() + bul.Damage = self.mydamage + bul.Tracer = 0 -- Show a tracer on every x bullets + bul.TracerName = "None" + bul.Dir = (tr.HitPos - bul.Src):GetNormalized() + + bul.Callback = function(a, b, c) + self:HitCB(a,b,c) + end + + bul.Attacker:FireBullets(bul) + end + + return + end + + self:SetNW2Vector("lastpos", self:GetPos()) +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/cl_init.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/cl_init.lua new file mode 100644 index 0000000..1622d8a --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/cl_init.lua @@ -0,0 +1,5 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() -- Draw the model. +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/init.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/init.lua new file mode 100644 index 0000000..6f71ae5 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/init.lua @@ -0,0 +1,3 @@ +AddCSLuaFile("cl_init.lua") -- Make sure clientside +AddCSLuaFile("shared.lua") -- and shared scripts are sent. +include("shared.lua") diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/shared.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/shared.lua new file mode 100644 index 0000000..b76dab7 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck/shared.lua @@ -0,0 +1,54 @@ +ENT.Type = "anim" +ENT.PrintName = "TFBow Arrow Stuck" +ENT.Author = "TheForgottenArchitect" +ENT.Contact = "Don't" +ENT.Purpose = "Arrow Entity" +ENT.Instructions = "Arrow that's stuck in ground" + +local cv_al = GetConVar("sv_tfa_arrow_lifetime") + +function ENT:Initialize() + if SERVER then + if cv_al:GetInt() ~= -1 then + timer.Simple( cv_al:GetFloat(), function() + if IsValid(self) then + self:Remove() + end + end) + end + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + phys:SetMass(2) + end + + if IsValid(self) and self.SetUseType then + self:SetUseType(SIMPLE_USE) + end + end + + if (self:GetModel() and self:GetModel() == "") then + self:SetModel("models/weapons/w_tfa_arrow.mdl") + end + + self:SetOwner(nil) + self.PhysicsCollide = function() end + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Sleep() + end +end + +function ENT:Use(activator, caller) + if activator:IsPlayer() and activator:GetWeapon(self.gun) then + activator:GiveAmmo(1, activator:GetWeapon(self.gun):GetPrimaryAmmoType(), false) + self:Remove() + end +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/cl_init.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/cl_init.lua new file mode 100644 index 0000000..5e7e361 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/cl_init.lua @@ -0,0 +1,9 @@ +include("shared.lua") + +function ENT:Draw() + if IsValid( self:GetParent() ) then + self:GetParent():SetupBones() + end + self:SetupBones() + self:DrawModel() +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/init.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/init.lua new file mode 100644 index 0000000..b8ffcd4 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/init.lua @@ -0,0 +1,13 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +function ENT:Use( activator, caller, usetype, val ) + if activator:IsPlayer() and activator:GetWeapon(self.gun) ~= nil then + activator:GiveAmmo(1, activator:GetWeapon(self.gun):GetPrimaryAmmoType(), false) + self:Remove() + end +end + +function ENT:PhysicsCollide(data, phys) +end diff --git a/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/shared.lua b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/shared.lua new file mode 100644 index 0000000..4e8f558 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/entities/tfbow_arrow_stuck_clientside/shared.lua @@ -0,0 +1,197 @@ +ENT.Type = "anim" +ENT.PrintName = "Sawblade" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true + +ENT.glitchthreshold = 24 --threshold distance from bone to reset pos +ENT.glitchthresholds = {} +ENT.glitchthresholds["ValveBiped.Bip01_Head1"] = 8 +ENT.glitchthresholds["ValveBiped.Bip01_Head"] = 8 +ENT.glitchthresholds["ValveBiped.Bip01_R_Hand"] = 1 +ENT.glitchthresholds["ValveBiped.Bip01_L_Hand"] = 1 +ENT.glitchthresholds["ValveBiped.Bip01_Spine2"] = 40 + +ENT.Hull = 1.5 --Expand hull to make it easier to grab +ENT.PredictCL = false +ENT.UseMod = false --Experimentally modify the parent's Use func + +local cv_al = GetConVar("sv_tfa_arrow_lifetime") +local nzombies + +local function GetBoneCenter(ent, bone) + local bonechildren = ent:GetChildBones(bone) + + if #bonechildren <= 0 then + return ent:GetBonePosition(bone) + else + local bonepos = ent:GetBonePosition(bone) + local tmppos = bonepos + + if tmppos then + for i = 1, #bonechildren do + local childpos = ent:GetBonePosition(bonechildren[i]) + + if childpos then + tmppos = (tmppos + childpos) / 2 + end + end + else + return ent:GetPos() + end + + return tmppos + end +end + +function ENT:Initialize() + + if nzombies == nil then + nzombies = nZombies or NZ or NZombies or engine.ActiveGamemode() == "nzombies" + end + + local mdl = self:GetModel() + + if not mdl or mdl == "" or mdl == "models/error.mdl" then + self:SetModel("models/weapons/w_tfa_arrow.mdl") + end + + if SERVER then + + local mins = (self:OBBMins() and self:OBBMins() or Vector(0, 0, 0)) - Vector(1, 1, 1) + local maxs = (self:OBBMaxs() and self:OBBMaxs() or Vector(0, 0, 0)) + Vector(1, 1, 1) + self:PhysicsInitBox(mins * self.Hull, maxs * self.Hull) + --self:PhysicsInit( SOLID_VPHYSICS ) + --self:SetSolid( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + phys:SetMass(2) + phys:EnableGravity(false) + phys:EnableCollisions(false) + end + + if self.SetUseType then + self:SetUseType(SIMPLE_USE) + end + + if cv_al:GetInt() ~= -1 then + timer.Simple( cv_al:GetFloat(), function() + if IsValid(self) then + self:Remove() + end + end) + end + + self:SetUseType( SIMPLE_USE ) + end + + if SERVER then + self:TargetEnt( true ) + end + + if CLIENT then + self:SetPredictable(false) + end + + if (self:GetModel() and self:GetModel() == "") then + self:SetModel("models/weapons/w_tfa_arrow.mdl") + end + + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + self:DrawShadow(true) +end + +function ENT:TargetEnt( init ) + if self.targent and IsValid(self.targent) then + if init then + local ent, bone, bonepos, bonerot + ent = self.targent + bone = self.targent:TranslatePhysBoneToBone(self.targphysbone) + self.targbone = bone + + if not ent:GetBoneCount() or ent:GetBoneCount() <= 1 or string.find(ent:GetModel(), "door") then + bonepos = ent:GetPos() + bonerot = ent:GetAngles() + self.enthasbones = false + else + if ent.SetupBones then + ent:SetupBones() + end + + bonepos, bonerot = ent:GetBonePosition(bone) + self.enthasbones = true + end + + if self.enthasbones == true then + local gpos = self:GetPos() + local bonepos2 = GetBoneCenter(ent, bone) + local tmpgts = self.glitchthresholds[ent:LookupBone(bone)] or self.glitchthreshold + + while gpos:Distance(bonepos2) > tmpgts do + self:SetPos((gpos + bonepos2) / 2) + gpos = (gpos + bonepos2) / 2 + end + end + + if not bonepos then + bonepos = ent:GetPos() + bonerot = ent:GetAngles() + end + + self.posoff, self.angoff = WorldToLocal(self:GetPos(), self:GetAngles(), bonepos, bonerot) + end + self:FollowBone( self.targent, self.targbone or -1 ) + self:SetOwner( self.targent ) + self:SetLocalPos( self.posoff ) + self:SetLocalAngles( self.angoff ) + self.HTE = true + if SERVER and self.PredictCL then + timer.Simple(0.05,function() + if IsValid(self) then + net.Start("tfaArrowFollow") + net.WriteEntity( self ) + net.WriteEntity( self.targent ) + net.WriteInt( self.targbone, 8 ) + net.WriteVector( self.posoff ) + net.WriteAngle( self.angoff ) + net.Broadcast() + end + end) + end + end +end + +function ENT:Think() + if CLIENT and not self.PredictCL then return end + if IsValid(self.targent) and self.targent.Health and self.targent:Health() <= 0 and self.targent.GetRagdollEntity then + local rag = self.targent:GetRagdollEntity() + if IsValid(rag) then + self.targent = rag + self:TargetEnt( false ) + end + end + local par = self:GetParent() + if IsValid(par) and self.UseMod and not par.HasUseMod then + par.HasUseMod = true + par.ArrowUseOld = par.ArrowUseOld or par.Use + par.Use = function( parent, ... ) + for _,v in pairs( par:GetChildren() ) do + if v.Use then v:Use(...) end + end + parent:Use( ... ) + end + par:SetUseType( SIMPLE_USE ) + end + if SERVER and not self.HTE then + self:TargetEnt( true ) + end + self:NextThink(CurTime()) + return true +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/3dscoped_base.lua b/garrysmod/addons/tfa_base/lua/tfa/3dscoped_base.lua new file mode 100644 index 0000000..8b7d7c5 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/3dscoped_base.lua @@ -0,0 +1,260 @@ +local SWEP = {} + +local BaseClass = baseclass.Get("tfa_gun_base") + +local scopeshadowcvar = GetConVar("cl_tfa_3dscope_overlay") + +local sp = game.SinglePlayer() + +function SWEP:Do3DScope() + return true +end + +function SWEP:Do3DScopeOverlay() + if scopeshadowcvar then + return scopeshadowcvar:GetBool() + else + return false + end +end + +function SWEP:UpdateScopeType() + -- empty, function retains for error preventing +end + +function SWEP:Initialize(...) + local unsetA = self.Primary_TFA == nil + local unsetB = self.Secondary_TFA == nil + + self.Primary_TFA = self.Primary_TFA or self.Primary + self.Secondary_TFA = self.Secondary_TFA or self.Secondary + + if unsetA then + self.Primary_TFA = nil + end + + if unsetB then + self.Secondary_TFA = nil + end + + BaseClass.Initialize(self, ...) +end + +local flipcv = GetConVar("cl_tfa_viewmodel_flip") +local cd = {} +local crosscol = Color(255, 255, 255, 255) +SWEP.RTOpaque = true + +local cv_cc_r = GetConVar("cl_tfa_hud_crosshair_color_r") +local cv_cc_g = GetConVar("cl_tfa_hud_crosshair_color_g") +local cv_cc_b = GetConVar("cl_tfa_hud_crosshair_color_b") +local cv_cc_a = GetConVar("cl_tfa_hud_crosshair_color_a") + +SWEP.defaultscrvec = Vector() + +SWEP.ScopeAngleTransformISAngleFallback = true -- fallback to reverse ironsights angle for scope transforms +SWEP.ScopeAngleTransforms = { -- this is bad this is bad this is bad this is bad this is bad this is bad this is bad this is bad this is bad + -- {"P", 0}, -- Pitch + -- {"Y", 0}, -- Yaw + -- {"R", 0}, -- Roll +} + +function SWEP:RTCode(rt, scrw, scrh) + local legacy = self.ScopeLegacyOrientation + local rttw = ScrW() + local rtth = ScrH() + + if not self:VMIV() then return end + + if not self.myshadowmask then + self.myshadowmask = surface.GetTextureID(self.ScopeShadow or "vgui/scope_shadowmask_test") + end + + if not self.myreticule then + self.myreticule = Material(self.ScopeReticule or "scope/gdcw_scopesightonly") + end + + if not self.mydirt then + self.mydirt = Material(self.ScopeDirt or "vgui/scope_dirt") + end + + local vm = self.OwnerViewModel + + if not self.LastOwnerPos then + self.LastOwnerPos = self:GetOwner():GetShootPos() + end + + local owoff = self:GetOwner():GetShootPos() - self.LastOwnerPos + + self.LastOwnerPos = self:GetOwner():GetShootPos() + + local scrpos + + local attShadowID = self:GetStatL("RTScopeShadowAttachment") + if attShadowID and attShadowID > 0 then + vm:SetupBones() + + local att = vm:GetAttachment(attShadowID) + if att and att.Pos then + local pos = att.Pos - owoff + cam.Start3D() + cam.End3D() + scrpos = pos:ToScreen() + end + end + + if not scrpos then + local spos = self:GetOwner():GetShootPos() + self:GetOwner():EyeAngles():Forward() * 16 + + local pos = spos - owoff + cam.Start3D() + cam.End3D() + scrpos = pos:ToScreen() + + -- self.defaultscrvec.x = scrw / 2 + -- self.defaultscrvec.y = scrh / 2 + -- scrpos = self.defaultscrvec + end + + scrpos.x = scrpos.x - scrw / 2 + self.ScopeOverlayTransforms[1] + scrpos.y = scrpos.y - scrh / 2 + self.ScopeOverlayTransforms[2] + scrpos.x = scrpos.x / scrw * 1920 + scrpos.y = scrpos.y / scrw * 1920 + scrpos.x = math.Clamp(scrpos.x, -1024, 1024) + scrpos.y = math.Clamp(scrpos.y, -1024, 1024) + --scrpos.x = scrpos.x * ( 2 - self:GetIronSightsProgress()*1 ) + --scrpos.y = scrpos.y * ( 2 - self:GetIronSightsProgress()*1 ) + scrpos.x = scrpos.x * self.ScopeOverlayTransformMultiplier + scrpos.y = scrpos.y * self.ScopeOverlayTransformMultiplier + + if not self.scrpos then + self.scrpos = scrpos + end + + self.scrpos.x = math.Approach(self.scrpos.x, scrpos.x, (scrpos.x - self.scrpos.x) * FrameTime() * 10) + self.scrpos.y = math.Approach(self.scrpos.y, scrpos.y, (scrpos.y - self.scrpos.y) * FrameTime() * 10) + scrpos = self.scrpos + render.OverrideAlphaWriteEnable(true, true) + surface.SetDrawColor(color_white) + surface.DrawRect(-512, -512, 1024, 1024) + render.OverrideAlphaWriteEnable(true, true) + + local ang = legacy and self:GetOwner():EyeAngles() or vm:GetAngles() + + local attID = self:GetStatL("RTScopeAttachment") + if attID and attID > 0 then + vm:SetupBones() + local AngPos = vm:GetAttachment( attID ) + + if AngPos then + ang = AngPos.Ang + + if flipcv:GetBool() then + ang.y = -ang.y + end + end + elseif self:GetStatL("ScopeAngleTransformISAngleFallback") then + local isang = self:GetStatL("IronSightsAngle") * self:GetIronSightsProgress() + + ang:RotateAroundAxis(ang:Forward(), -isang.z) + ang:RotateAroundAxis(ang:Right(), -isang.x) + ang:RotateAroundAxis(ang:Up(), -isang.y) + + ang:RotateAroundAxis(ang:Forward(), isang.z) + end + + -- WHY WHY WHY WHY WHY WHY WHY WHY + for _, v in ipairs(self:GetStatL("ScopeAngleTransforms")) do + if v[1] == "P" then + ang:RotateAroundAxis(ang:Right(), v[2]) + elseif v[1] == "Y" then + ang:RotateAroundAxis(ang:Up(), v[2]) + elseif v[1] == "R" then + ang:RotateAroundAxis(ang:Forward(), v[2]) + end + end + + cd.angles = ang + cd.origin = self:GetOwner():GetShootPos() + + if not self.RTScopeOffset then + self.RTScopeOffset = {0, 0} + end + + if not self.RTScopeScale then + self.RTScopeScale = {1, 1} + end + + local rtow, rtoh = self.RTScopeOffset[1], self.RTScopeOffset[2] + local rtw, rth = rttw * self.RTScopeScale[1], rtth * self.RTScopeScale[2] + + cd.x = 0 + cd.y = 0 + cd.w = rtw + cd.h = rth + cd.fov = self:GetStatL("RTScopeFOV") + cd.drawviewmodel = false + cd.drawhud = false + render.Clear(0, 0, 0, 255, true, true) + render.SetScissorRect(0 + rtow, 0 + rtoh, rtw + rtow, rth + rtoh, true) + + if self:GetIronSightsProgress() > 0.01 and self.Scoped_3D then + render.RenderView(cd) + end + + render.SetScissorRect(0, 0, rtw, rth, false) + render.OverrideAlphaWriteEnable(false, true) + cam.Start2D() + draw.NoTexture() + surface.SetTexture(self.myshadowmask) + surface.SetDrawColor(color_white) + + if self:Do3DScopeOverlay() then + surface.DrawTexturedRect(scrpos.x + rtow - rtw / 2, scrpos.y + rtoh - rth / 2, rtw * 2, rth * 2) + end + + if self.ScopeReticule_CrossCol then + crosscol.r = cv_cc_r:GetFloat() + crosscol.g = cv_cc_g:GetFloat() + crosscol.b = cv_cc_b:GetFloat() + crosscol.a = cv_cc_a:GetFloat() + surface.SetDrawColor(crosscol) + end + + surface.SetMaterial(self.myreticule) + local tmpborderw = rtw * (1 - self.ScopeReticule_Scale[1]) / 2 + local tmpborderh = rth * (1 - self.ScopeReticule_Scale[2]) / 2 + surface.DrawTexturedRect(rtow + tmpborderw, rtoh + tmpborderh, rtw - tmpborderw * 2, rth - tmpborderh * 2) + surface.SetDrawColor(color_black) + draw.NoTexture() + + if self:Do3DScopeOverlay() then + surface.DrawRect(scrpos.x - 2048 + rtow, -1024 + rtoh, 2048, 2048) + surface.DrawRect(scrpos.x + rtw + rtow, -1024 + rtoh, 2048, 2048) + surface.DrawRect(-1024 + rtow, scrpos.y - 2048 + rtoh, 2048, 2048) + surface.DrawRect(-1024 + rtow, scrpos.y + rth + rtoh, 2048, 2048) + end + + surface.SetDrawColor(ColorAlpha(color_black, 255 - 255 * (math.Clamp(self:GetIronSightsProgress() - 0.75, 0, 0.25) * 4))) + surface.DrawRect(-1024 + rtow, -1024 + rtoh, 2048, 2048) + surface.SetMaterial(self.mydirt) + surface.SetDrawColor(ColorAlpha(color_white, 128)) + surface.DrawTexturedRect(0, 0, rtw, rth) + surface.SetDrawColor(ColorAlpha(color_white, 64)) + surface.DrawTexturedRectUV(rtow, rtoh, rtw, rth, 2, 0, 0, 2) + cam.End2D() +end + +local function l_Lerp(v, f, t) + return f + (t - f) * v +end + +function SWEP:AdjustMouseSensitivity(...) + local retVal = BaseClass.AdjustMouseSensitivity(self, ...) + + retVal = retVal * l_Lerp(self:GetIronSightsProgress(), 1, self:Get3DSensitivity()) + + return retVal +end + +return SWEP diff --git a/garrysmod/addons/tfa_base/lua/tfa/att/base.lua b/garrysmod/addons/tfa_base/lua/tfa/att/base.lua new file mode 100644 index 0000000..51ec2e6 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/att/base.lua @@ -0,0 +1,35 @@ +if not ATTACHMENT then + ATTACHMENT = {} +end + +-- ATTACHMENT.TFADataVersion = 1 -- Uncomment this in your attachment file +-- If it is undefined, if fallback to 0 and WeaponTable gets migrated like SWEPs do + +ATTACHMENT.Name = "Base Attachment" +ATTACHMENT.ShortName = nil --Abbreviation, 5 chars or less please +ATTACHMENT.Description = {} --TFA.Attachments.Colors["+"], "Does something good", TFA.Attachments.Colors["-"], "Does something bad" } +ATTACHMENT.Icon = nil --Revers to label, please give it an icon though! This should be the path to a png, like "entities/tfa_ammo_match.png" +ATTACHMENT.WeaponTable = {} --put replacements for your SWEP talbe in here e.g. ["Primary"] = {} + +ATTACHMENT.DInv2_GridSizeX = nil -- DInventory/2 Specific. Determines attachment's width in grid. +ATTACHMENT.DInv2_GridSizeY = nil -- DInventory/2 Specific. Determines attachment's height in grid. +ATTACHMENT.DInv2_Volume = nil -- DInventory/2 Specific. Determines attachment's volume in liters. +ATTACHMENT.DInv2_Mass = nil -- DInventory/2 Specific. Determines attachment's mass in kilograms. +ATTACHMENT.DInv2_StackSize = nil -- DInventory/2 Specific. Determines attachment's maximal stack size. + +ATTACHMENT.TFADataVersion = nil -- TFA.LatestDataVersion, specifies version of TFA Weapon Data this attachment utilize in `WeaponTable` +-- 0 is original, M9K-like data, and is the fallback if `TFADataVersion` is undefined + +function ATTACHMENT:CanAttach(wep) + return true --can be overridden per-attachment +end + +function ATTACHMENT:Attach(wep) +end + +function ATTACHMENT:Detach(wep) +end + +if not TFA_ATTACHMENT_ISUPDATING then + TFAUpdateAttachments() +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/att/si_rt_base.lua b/garrysmod/addons/tfa_base/lua/tfa/att/si_rt_base.lua new file mode 100644 index 0000000..679ece8 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/att/si_rt_base.lua @@ -0,0 +1,109 @@ +if not ATTACHMENT then + ATTACHMENT = {} +end + +ATTACHMENT.Name = "RT Scope Base" +ATTACHMENT.Description = {} + +ATTACHMENT.WeaponTable = { + ["RTDrawEnabled"] = true, + + -- ["RTScopeFOV"] = 90 / 1 / 2, -- Default FOV / Scope Zoom / screenscale + -- ["RTScopeAttachment"] = -1, + + -- ["RTReticleMaterial"] = Material("scope/gdcw_acog"), + -- ["RTReticleColor"] = color_white, + -- ["RTReticleScale"] = 1, + + -- ["RTShadowMaterial"] = Material("vgui/scope_shadowmask_test"), + -- ["RTShadowColor"] = color_white, + -- ["RTShadowScale"] = 2, +} + +local cd = {} + +local fallbackReticle = Material("scope/gdcw_scopesightonly") +local fallbackShadow = Material("vgui/scope_shadowmask_test") + +local flipcv = GetConVar("cl_tfa_viewmodel_flip") + +function ATTACHMENT:RTCode(wep, rt, scrw, scrh) + if not wep.OwnerIsValid or not wep:VMIV() then return end + + local rtw, rth = rt:Width(), rt:Height() + + -- clearing view + render.OverrideAlphaWriteEnable(true, true) + surface.SetDrawColor(color_white) + surface.DrawRect(-rtw, -rth, rtw * 2, rth * 2) + + local vm = wep.OwnerViewModel + + local ang = vm:GetAngles() + + local isang = wep:GetStatL("IronSightsAngle") * wep:GetIronSightsProgress() + + ang:RotateAroundAxis(ang:Forward(), -isang.z) + ang:RotateAroundAxis(ang:Right(), -isang.x) + ang:RotateAroundAxis(ang:Up(), -isang.y) + + ang:RotateAroundAxis(ang:Forward(), isang.z) + + local scopeAtt = wep:GetStatL("RTScopeAttachment", -1) + + if scopeAtt > 0 then + local AngPos = vm:GetAttachment(scopeAtt) + + if AngPos then + ang = AngPos.Ang + + if flipcv:GetBool() then + ang.y = -ang.y + end + end + end + + cd.angles = ang + cd.origin = wep:GetOwner():GetShootPos() + cd.x = 0 + cd.y = 0 + cd.w = rtw + cd.h = rth + cd.fov = wep:GetStatL("RTScopeFOV", 90 / wep:GetStatL("ScopeZoom", 1) / 2) + cd.drawviewmodel = false + cd.drawhud = false + + -- main RT render view + render.Clear(0, 0, 0, 255, true, true) + render.SetScissorRect(0, 0, rtw, rth, true) + + if wep:GetIronSightsProgress() > 0.005 then + render.RenderView(cd) + end + + render.SetScissorRect(0, 0, rtw, rth, false) + render.OverrideAlphaWriteEnable(false, true) + + cam.Start2D() + + -- ADS transition darkening + draw.NoTexture() + surface.SetDrawColor(ColorAlpha(color_black, 255 * (1 - wep:GetIronSightsProgress()))) + surface.DrawRect(0, 0, rtw, rth) + + surface.SetMaterial(wep:GetStatL("RTReticleMaterial", fallbackReticle)) + surface.SetDrawColor(wep:GetStatL("RTReticleColor", color_white)) + local retScale = wep:GetStatL("RTReticleScale", 1) + surface.DrawTexturedRect(rtw / 2 - rtw * retScale / 2, rth / 2 - rth * retScale / 2, rtw * retScale, rth * retScale) + + surface.SetMaterial(wep:GetStatL("RTShadowMaterial", fallbackShadow)) + surface.SetDrawColor(wep:GetStatL("RTShadowColor", color_white)) + local shadScale = wep:GetStatL("RTShadowScale", 2) + surface.DrawTexturedRect(rtw / 2 - rtw * shadScale / 2, rth / 2 - rth * shadScale / 2, rtw * shadScale, rth * shadScale) + + cam.End2D() +end + +if not TFA_ATTACHMENT_ISUPDATING then + TFAUpdateAttachments() +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/attbatch/0_base_attachments.lua b/garrysmod/addons/tfa_base/lua/tfa/attbatch/0_base_attachments.lua new file mode 100644 index 0000000..cb270d3 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/attbatch/0_base_attachments.lua @@ -0,0 +1,254 @@ +if not TFA_ATTACHMENT_ISUPDATING then TFAUpdateAttachments(false) return end + +TFA.Attachments.RegisterFromTable("am_gib", { + Name = "G.I.B Ammunition", + ShortName = "GIB", + Description = { + TFA.Attachments.Colors["+"], "Always gibs enemies", + TFA.Attachments.Colors["+"], "10% more damage", + TFA.Attachments.Colors["-"], "20% more recoil", + TFA.Attachments.Colors["-"], "10% more spread" + }, + Icon = "entities/tfa_ammo_gib.png", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + Primary = { + DamageType = function(wep,stat) return bit.bor( stat or DMG_BULLET, DMG_ALWAYSGIB ) end, + Damage = function( wep, stat ) return stat * 1.1 end, + Spread = function( wep, stat ) return stat * 1.1 end, + IronAccuracy = function( wep, stat ) return stat * 1.1 end, + KickUp = function( wep, stat ) return stat * 1.2 end, + KickDown = function( wep, stat ) return stat * 1.2 end + } + } +}) +TFA.Attachments.RegisterFromTable("am_magnum", { + Name = "Magnum Ammunition", + ShortName = "MAG", + Description = { + TFA.Attachments.Colors["+"], "10% more damage", + TFA.Attachments.Colors["-"], "15% more recoil", + TFA.Attachments.Colors["-"], "10% more spread" + }, + Icon = "entities/tfa_ammo_magnum.png", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + Primary = { + Damage = function( wep, stat ) return stat * 1.1 end, + Spread = function( wep, stat ) return stat * 1.1 end, + IronAccuracy = function( wep, stat ) return stat * 1.1 end, + KickUp = function( wep, stat ) return stat * 1.15 end, + KickDown = function( wep, stat ) return stat * 1.15 end + } + } +}) +TFA.Attachments.RegisterFromTable("am_match", { + Name = "Match Ammunition", + ShortName = "Match", + Description = { + TFA.Attachments.Colors["+"], "20% lower spread kick", + "10% lower recoil", + TFA.Attachments.Colors["-"], "20% lower spread recovery" + }, + Icon = "entities/tfa_ammo_match.png", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + Primary = { + SpreadIncrement = function( wep, stat ) return stat * 0.9 end, + SpreadRecovery = function( wep, stat ) return stat * 0.8 end, + KickUp = function( wep, stat ) return stat * 0.9 end, + KickDown = function( wep, stat ) return stat * 0.9 end + } + } +}) + +TFA.Attachments.RegisterFromTable("sg_frag", { + Name = "Frag Ammunition", + ShortName = "Frag", + Description = { + TFA.Attachments.Colors["+"], "Explosive Damage", + "2x damage", + TFA.Attachments.Colors["-"], "0.5x pellets" + }, + Icon = "entities/tfa_ammo_fragshell.png", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + ["Primary"] = { + ["DamageType"] = function(wep,stat) return bit.bor( stat or 0, DMG_BLAST ) end, + ["Damage"] = function(wep,stat) return stat * 2 end, + ["NumShots"] = function(wep,stat) return stat / 2 end + } + } +}) +TFA.Attachments.RegisterFromTable("sg_slug", { + Name = "Slug Ammunition", + ShortName = "Slug", + Description = { + TFA.Attachments.Colors["+"], "Much lower spread", + TFA.Attachments.Colors["+"], "100m higher range", + TFA.Attachments.Colors["-"], "30% less damage", + "One pellet" + }, + Icon = "entities/tfa_ammo_slug.png", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + Primary = { + Damage = function( wep, stat ) return wep.Primary_TFA.NumShots * stat * 0.7 end, + NumShots = function( wep, stat ) return 1, true end, + Spread = function( wep, stat ) return math.max( stat - 0.015, stat * 0.5 ) end, + IronAccuracy = function( wep, stat ) return math.max( stat - 0.03, stat * 0.25 ) end, + Range = function( wep, stat ) return stat + 100 * 39.370 end + } + } +}) + + + +TFA.Attachments.RegisterFromTable("br_supp", { + Name = "Suppressor", + Description = { + TFA.Attachments.Colors["+"], "Less firing noise", + TFA.Attachments.Colors["-"], "10% less spread", + TFA.Attachments.Colors["-"], "5% less damage", + TFA.Attachments.Colors["-"], "10% less vertical recoil" + }, + Icon = "entities/tfa_br_supp.png", + ShortName = "SUPP", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + ["ViewModelElements"] = { + ["suppressor"] = { + ["active"] = true + } + }, + ["WorldModelElements"] = { + ["suppressor"] = { + ["active"] = true + } + }, + ["Primary"] = { + ["Damage"] = function(wep,stat) return stat * 0.95 end, + ["KickUp"] = function(wep,stat) return stat * 0.9 end, + ["KickDown"] = function(wep,stat) return stat * 0.9 end, + ["Spread"] = function(wep,stat) return stat * 0.9 end, + ["IronAccuracy"] = function(wep,stat) return stat * 0.9 end, + ["Sound"] = function(wep,stat) return wep.Primary.SilencedSound or stat end + }, + ["MuzzleFlashEffect"] = "tfa_muzzleflash_silenced", + ["MuzzleAttachmentMod"] = function(wep,stat) return wep.MuzzleAttachmentSilenced or stat end + } +}) + +TFA.Attachments.RegisterFromTable("si_acog", { + Base = "si_rt_base", + Name = "ACOG", + Description = { + TFA.Attachments.Colors["="], "4x zoom", + TFA.Attachments.Colors["-"], "20% higher zoom time", + TFA.Attachments.Colors["-"], "10% slower aimed walking" + }, + Icon = "entities/tfa_si_acog.png", + ShortName = "ACOG", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + ["ViewModelElements"] = { + ["acog"] = { + ["active"] = true + }, + ["rtcircle_acog"] = { + ["active"] = true + } + }, + ["WorldModelElements"] = { + ["acog"] = { + ["active"] = true + } + }, + ["IronSightsPosition"] = function( wep, val ) return wep.IronSightsPos_ACOG or val, true end, + ["IronSightsAngle"] = function( wep, val ) return wep.IronSightsAng_ACOG or val, true end, + ["IronSightsSensitivity"] = function(wep,val) return TFA.CalculateSensitivtyScale( 90 / 4 / 2, wep:GetStatL("Secondary.OwnerFOV"), wep.ACOGScreenScale ) end , + ["Secondary"] = { + ["OwnerFOV"] = function( wep, val ) return val * 0.7 end + }, + ["IronSightTime"] = function( wep, val ) return val * 1.20 end, + ["IronSightMoveSpeed"] = function(stat) return stat * 0.9 end, + ["RTOpaque"] = true, + ["RTMaterialOverride"] = -1, + + ["RTScopeFOV"] = 90 / 4 / 2, -- Default FOV / Scope Zoom / screenscale + + ["RTReticleMaterial"] = Material("scope/gdcw_acog"), + ["RTReticleScale"] = 1, + } +}) +TFA.Attachments.RegisterFromTable("si_aimpoint", { + Name = "Aimpoint", + Description = { + TFA.Attachments.Colors["="], "10% higher zoom", + TFA.Attachments.Colors["-"], "10% higher zoom time" + }, + Icon = "entities/tfa_si_aimpoint.png", + ShortName = "AIM", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + ["ViewModelElements"] = { + ["aimpoint"] = { + ["active"] = true + }, + ["aimpoint_spr"] = { + ["active"] = true + } + }, + ["WorldModelElements"] = { + ["aimpoint"] = { + ["active"] = true + }, + ["aimpoint_spr"] = { + ["active"] = true + } + }, + ["IronSightsPosition"] = function( wep, val ) return wep.IronSightsPos_AimPoint or val, true end, + ["IronSightsAngle"] = function( wep, val ) return wep.IronSightsAng_AimPoint or val, true end, + ["Secondary"] = { + ["OwnerFOV"] = function( wep, val ) return val * 0.9 end + }, + ["IronSightTime"] = function( wep, val ) return val * 1.10 end + } +}) +TFA.Attachments.RegisterFromTable("si_eotech", { + Name = "EOTech", + Description = { + TFA.Attachments.Colors["="], "10% higher zoom", + TFA.Attachments.Colors["-"], "10% higher zoom time" + }, + Icon = "entities/tfa_si_eotech.png", + ShortName = "EOTEK", + TFADataVersion = TFA.LatestDataVersion, + + WeaponTable = { + ["ViewModelElements"] = { + ["eotech"] = { + ["active"] = true + } + }, + ["WorldModelElements"] = { + ["eotech"] = { + ["active"] = true + } + }, + ["IronSightsPosition"] = function( wep, val ) return wep.IronSightsPos_EOTech or val, true end, + ["IronSightsAngle"] = function( wep, val ) return wep.IronSightsAng_EOTech or val, true end, + ["Secondary"] = { + ["OwnerFOV"] = function( wep, val ) return val * 0.9 end + }, + ["IronSightTime"] = function( wep, val ) return val * 1.10 end + } +}) diff --git a/garrysmod/addons/tfa_base/lua/tfa/ballistics/bullet.lua b/garrysmod/addons/tfa_base/lua/tfa/ballistics/bullet.lua new file mode 100644 index 0000000..4a5e41f --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/ballistics/bullet.lua @@ -0,0 +1,372 @@ +local vector_origin = Vector() + +--[[Bullet Struct: +[BULLET_ID] = { + ["owner"] = Entity, --used for dmginfo SetAttacker + ["inflictor"] = Entity, --used for dmginfo SetInflictor + ["damage"] = Double, --floating point number representing inflicted damage + ["force"] = Double, + ["pos"] = Vector, --vector representing current position + ["velocity"] = Vector, --vector representing movement velocity + ["model"] = String --optional variable representing the given model, + ["bul"] = {} --optional table containing bullet data, + ["smokeparticle"] = String, --smoke particle name from within pcf + ["bulletOverride"] = Bool --disable coming out of gun barrel on clientside +} +]] +local BallisticBullet = { + ["owner"] = NULL, + ["inflictor"] = NULL, + ["damage"] = 0, + ["force"] = 0, + ["pos"] = vector_origin, + ["velocity"] = vector_origin, + ["model"] = "models/bullets/w_pbullet1.mdl", + ["bul"] = {}, + ["delete"] = false, + ["smokeparticle"] = "tfa_bullet_smoke_tracer" +} + +local traceRes = {} + +local traceData = { + mask = MASK_SHOT, + collisiongroup = COLLISION_GROUP_NONE, + ignoreworld = false, + output = traceRes +} + +local MASK_SHOT_NOWATER = MASK_SHOT + +--main update block +function BallisticBullet:Update(delta) + if self.delete then return end + self:_setup() + if self.delete then return end + + local realdelta = (delta - self.last_update) / TFA.Ballistics.SubSteps + self.last_update = delta + + local newPos = self:_getnewPosition(realdelta) + newPos = self:_checkWater(realdelta, newPos) + self:_accelerate(realdelta) + self:_moveSafe(newPos) +end + +--internal function for sanity checks, etc. +function BallisticBullet:_setup() + self.creationTime = CurTime() + + if (not IsValid(self.owner)) or (not IsValid(self.inflictor)) then + self:Remove() + end + + if CurTime() > self.creationTime + TFA.Ballistics.BulletLife then + self:Remove() + end + + self.playerOwned = self.owner.IsPlayer and self.owner:IsPlayer() + self.startVelocity = self.velocity:Length() + self.startDamage = self.damage +end + +function BallisticBullet:_think() + if (not IsValid(self.owner)) or (not IsValid(self.inflictor)) then + self:Remove() + end + + if CurTime() > self.creationTime + TFA.Ballistics.BulletLife then + self:Remove() + end +end + +--internal function for calculating position change +function BallisticBullet:_getnewPosition(delta) + --verlet + return self.pos + (self.velocity + TFA.Ballistics.Gravity * delta * 0.5) * delta +end + +--internal function for handling water +function BallisticBullet:_checkWater(delta, target) + local newPos = target + traceData.start = self.pos + traceData.endpos = newPos + traceData.filter = {self.owner, self.inflictor} + traceData.mask = MASK_WATER + util.TraceLine(traceData) + + if traceRes.Hit and traceRes.Fraction < 1 and traceRes.Fraction > 0 and not self.Underwater then + self.Underwater = true + newPos = traceRes.HitPos + traceRes.Normal + self.velocity = self.velocity / TFA.Ballistics.WaterEntranceResistance + local fx = EffectData() + fx:SetOrigin(newPos) + local sc = math.sqrt(self.damage / 28) * 6 + fx:SetScale(sc) + util.Effect("gunshotsplash", fx) + end + + return newPos +end + +--internal function for handling acceleration +local function GetWind() + return vector_origin +end + +if StormFox and StormFox.Version then + if StormFox.Version < 2 then -- SF1 + local SF_GetNetworkData = StormFox.GetNetworkData + + function GetWind() + local windSpeed = SF_GetNetworkData("Wind") * TFA.Ballistics.UnitScale + local windAng = Angle(0, SF_GetNetworkData("WindAngle"), 0) + + return windSpeed * windAng:Forward():GetNormalized() + end + elseif StormFox.Wind then -- SF2 + local SFW_GetForce = StormFox.Wind.GetForce + local SFW_GetYaw = StormFox.Wind.GetYaw + + function GetWind() + local windSpeed = SFW_GetForce() * TFA.Ballistics.UnitScale + local windAng = Angle(0, SFW_GetYaw(), 0) + + return windSpeed * windAng:Forward():GetNormalized() + end + end +end + +function BallisticBullet:_accelerate(delta) + local dragDensity = self.Underwater and TFA.Ballistics.WaterResistance or TFA.Ballistics.AirResistance + local drag = -self.velocity:GetNormalized() * self.velocity:Length() * self.velocity:Length() * 0.00006 * dragDensity + local wind = GetWind() + + if self.Underwater then + self.velocity = self.velocity / (1 + TFA.Ballistics.WaterResistance * delta) + end + + self.velocity = self.velocity + (TFA.Ballistics.Gravity + drag + wind) * delta + self.damage = self.startDamage * math.sqrt(self.velocity:Length() / self.startVelocity) +end + +local IsInWorld, IsInWorld2 + +do + local tr = {collisiongroup = COLLISION_GROUP_WORLD} + + function IsInWorld2(pos) + tr.start = pos + tr.endpos = pos + return not util.TraceLine(tr).AllSolid + end +end + +if CLIENT then + IsInWorld = IsInWorld2 +else + IsInWorld = util.IsInWorld +end + +--internal function for moving with collision test +function BallisticBullet:_moveSafe(newPos) + if not self.tr_filter then + if IsValid(self.IgnoreEntity) then + self.tr_filter = {self.owner, self.inflictor, self.IgnoreEntity} + else + self.tr_filter = {self.owner, self.inflictor} + end + end + + traceData.start = self.pos + traceData.endpos = newPos + (newPos - self.pos):GetNormalized() + traceData.filter = self.tr_filter + traceData.mask = MASK_SHOT_NOWATER + + --collision trace + if self.playerOwned then + self.owner:LagCompensation(true) + end + + util.TraceLine(traceData) + + if self.playerOwned then + self.owner:LagCompensation(false) + end + + --collision check + if traceRes.Hit and traceRes.Fraction < 1 and traceRes.Fraction > 0 then + self:Impact(traceRes) + elseif IsInWorld(newPos) then + self.pos = newPos + else + self:Remove() + end +end + +--called when hitting something, or manually if necessary +function BallisticBullet:Impact(tr) + self.pos = tr.HitPos + self:Remove() + + if CLIENT and (game.SinglePlayer() or self.owner ~= LocalPlayer()) then return end + + if tr.HitSky then return end + local vn = self.velocity:GetNormalized() + + local bul = { + ["Damage"] = self.damage, + ["Force"] = self.force, + ["Num"] = 1, + ["Src"] = self.pos - vn * 4, + ["Dir"] = vn * 8, + ["Spread"] = vector_origin, + ["IgnoreEntity"] = self.owner, + ["Attacker"] = self.owner, + ["Distance"] = 8, + ["Tracer"] = 0 + } + + setmetatable(bul, { + ["__index"] = self.bul + }) + + self.owner:FireBullets(bul) +end + +--Render +--local cv_bullet_style, cv_tracers_adv +local cv_bullet_style + +if CLIENT then + CreateClientConVar("cl_tfa_ballistics_mp", "1", true, false, "Receive bullet data from other players?") + cv_bullet_style = CreateClientConVar("cl_tfa_ballistics_fx_bullet", "1", true, false, "Display bullet models for each TFA ballistics bullet?") + CreateClientConVar("cl_tfa_ballistics_fx_tracers_style", "1", true, false, "Style of tracers for TFA ballistics? 0=disable,1=smoke") + CreateClientConVar("cl_tfa_ballistics_fx_tracers_mp", "1", true, false, "Enable tracers for other TFA ballistics users?") + --cv_tracers_adv = CreateClientConVar("cl_tfa_ballistics_fx_tracers_adv", "1", true, false, "Enable advanced tracer calculations for other users? This corrects smoke trail to their barrel") + --[[ + cv_receive = GetConVar("cl_tfa_ballistics_mp") + cv_bullet_style = GetConVar("cl_tfa_ballistics_fx_bullet") + cv_tracers_style = GetConVar("cl_tfa_ballistics_fx_tracers_style") + cv_tracers_mp = GetConVar("cl_tfa_ballistics_fx_tracers_mp") + cv_tracers_adv = GetConVar("cl_tfa_ballistics_fx_tracers_adv") + ]] + -- +end + +--[[local DEFANGPOS = { + Pos = vector_origin, + Ang = angle_zero +}]] + +function BallisticBullet:Render() + if SERVER then return end + if self.delete then return end + + if not self.curmodel then + self.curmodel = ClientsideModel(self.model, RENDERGROUP_OPAQUE) + self.curmodel:SetNoDraw(not cv_bullet_style:GetBool()) + end + + --[==[if IsValid(self.curmodel) and (cv_bullet_style:GetBool() or self.smokeparticle ~= "") then + if self.customPosition then + fpos = self.pos + --fang = self.velocity:Angle() + else + if self.owner == GetViewEntity() or self.owner == LocalPlayer() then + local spos, sang = self.pos, self.velocity:Angle() + self.curmodel:SetPos(spos) + self.curmodel:SetAngles(sang) + + if not self.vOffsetPos then + local att + + if self.inflictor.GetMuzzleAttachment and self.inflictor:GetMuzzleAttachment() then + att = self.inflictor:GetMuzzleAttachment() + else + att = self.inflictor.MuzzleAttachmentRaw or 1 + end + + if LocalPlayer():ShouldDrawLocalPlayer() then + local npos = LocalPlayer():GetActiveWeapon():GetAttachment(att) or DEFANGPOS + self.vOffsetPos = self.curmodel:WorldToLocal(npos.Pos) + self.vOffsetAng = self.curmodel:WorldToLocalAngles(npos.Ang) + else + local npos = LocalPlayer():GetViewModel():GetAttachment(att) or DEFANGPOS + self.vOffsetPos = self.curmodel:WorldToLocal(npos.Pos) + self.vOffsetAng = self.curmodel:WorldToLocalAngles(npos.Ang) + end + end + + fpos = self.curmodel:LocalToWorld(self.vOffsetPos) + --fang = self.curmodel:LocalToWorldAngles(self.vOffsetAng) + elseif self.owner:IsPlayer() and cv_tracers_adv:GetBool() then + local spos, sang = self.pos, self.velocity:Angle() + self.curmodel:SetPos(spos) + self.curmodel:SetAngles(sang) + + if not self.vOffsetPos then + local npos = self.owner:GetActiveWeapon():GetAttachment(1) or DEFANGPOS + self.vOffsetPos = self.curmodel:WorldToLocal(npos.Pos) + self.vOffsetAng = self.curmodel:WorldToLocalAngles(npos.Ang) + end + + fpos = self.curmodel:LocalToWorld(self.vOffsetPos) + --fang = self.curmodel:LocalToWorldAngles(self.vOffsetAng) + else + fpos = self.pos + --fang = self.velocity:Angle() + end + end + + --[[if cv_bullet_style:GetBool() then + self.curmodel:SetupBones() + self.curmodel:DrawModel() + end]] + end]==] + + local fpos, fang = self.pos, self.velocity:Angle() + + self.curmodel:SetPos(fpos) + self.curmodel:SetAngles(fang) + + if self.smokeparticle ~= "" and not self.cursmoke then + self.cursmoke = CreateParticleSystem(self.curmodel, self.smokeparticle, PATTACH_ABSORIGIN_FOLLOW, 1) + if not self.cursmoke then return end + self.cursmoke:StartEmission() + elseif self.cursmoke and IsValid(self.owner) then + self.cursmoke:SetSortOrigin(self.owner.GetShootPos and self.owner:GetShootPos() or self.owner.EyePos and self.owner:EyePos() or vector_origin) + + if self.Underwater then + self.cursmoke:StopEmission() + self.cursmoke = nil + self.smokeparticle = "" + end + end +end + +function BallisticBullet:Remove() + if self.cursmoke then + self.cursmoke:StopEmission() + self.cursmoke = nil + end + + if self.curmodel and self.curmodel.Remove then + self.curmodel:Remove() + self.curmodel = nil + end + + self.delete = true +end + +local CopyTable = table.Copy + +function TFA.Ballistics:Bullet(t) + local b = CopyTable(t or {}) + + setmetatable(b, { + ["__index"] = BallisticBullet + }) + + return b +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_anims_template.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_anims_template.lua new file mode 100644 index 0000000..eaca6d0 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_anims_template.lua @@ -0,0 +1,248 @@ +-- TFA Base Animations reference + +-- Example of animation table: +SWEP.Animations = { + ["shoot1"] = { + -- Еype of the animation entry, either TFA.Enum.ANIMATION_ACT or TFA.Enum.ANIMATION_SEQ + -- ALL ENTRIES OF THE ANIMATION MUST BE OF THE SAME TYPE!!! + -- for ANIMATION_ACT, "value" must be one of the enums from https://wiki.facepunch.com/gmod/Enums/ACT + -- for ANIMATION_SEQ "value" is the sequence name + ["type"] = TFA.Enum.ANIMATION_ACT, + + -- Basic + ["value"] = ACT_VM_PRIMARYATTACK, + ["value_empty"] = ACT_VM_DRYFIRE, + ["value_last"] = ACT_VM_PRIMARYATTACK_EMPTY, + + -- Silenced + ["value_sil"] = ACT_VM_PRIMARYATTACK_SILENCED, + ["value_sil_empty"] = ACT_VM_DRYFIRE_SILENCED, + + -- Ironsights/ADS + ["value_is"] = ACT_VM_PRIMARYATTACK_1, + ["value_is_empty"] = ACT_VM_PRIMARYATTACK_2, + ["value_is_last"] = ACT_VM_PRIMARYATTACK_3, + + -- ADS + Silenced + ["value_is_sil"] = ACT_VM_PRIMARYATTACK_DEPLOYED_1, + ["value_is_sil_empty"] = ACT_VM_PRIMARYATTACK_DEPLOYED_2, + ["value_is_sil_last"] = ACT_VM_PRIMARYATTACK_DEPLOYED_3, + + -- Force enable animation (when it's not autodetected) + ["enabled"] = true + }, +} + +-- Uncomment entry and add to SWEP.Animations table of your SWEP. DO NOT COPY THE WHOLE BLOCK! +-- SWEP.Animations = { + --[[ Gun Base ]]-- + -- ["draw_first"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DRAW_DEPLOYED, + -- ["enabled"] = nil + -- }, + -- ["draw"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DRAW + -- }, + -- ["draw_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DRAW_EMPTY + -- }, + -- ["draw_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DRAW_SILENCED + -- }, + -- ["shoot1"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_PRIMARYATTACK + -- }, + -- ["shoot1_last"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_PRIMARYATTACK_EMPTY + -- }, + -- ["shoot1_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DRYFIRE + -- }, + -- ["shoot1_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_PRIMARYATTACK_SILENCED + -- }, + -- ["shoot1_silenced_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DRYFIRE_SILENCED or 0 + -- }, + -- ["shoot1_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_PRIMARYATTACK_1 + -- }, + -- ["shoot2"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_SECONDARYATTACK + -- }, + -- ["shoot2_last"] = { + -- ["type"] = TFA.Enum.ANIMATION_SEQ, + -- ["value"] = "shoot2_last" + -- }, + -- ["shoot2_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DRYFIRE + -- }, + -- ["shoot2_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_SEQ, + -- ["value"] = "shoot2_silenced" + -- }, + -- ["shoot2_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_ISHOOT_M203 + -- }, + -- ["idle"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_IDLE + -- }, + -- ["idle_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_IDLE_EMPTY + -- }, + -- ["idle_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_IDLE_SILENCED + -- }, + -- ["reload"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_RELOAD + -- }, + -- ["reload_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_RELOAD_EMPTY + -- }, + -- ["reload_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_RELOAD_SILENCED + -- }, + -- ["reload_shotgun_start"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_SHOTGUN_RELOAD_START + -- }, + -- ["reload_shotgun_finish"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_SHOTGUN_RELOAD_FINISH + -- }, + -- ["reload_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_RELOAD_ADS + -- }, + -- ["reload_empty_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_RELOAD_EMPTY_ADS + -- }, + -- ["reload_silenced_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_RELOAD_SILENCED_ADS + -- }, + -- ["reload_shotgun_start_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_SHOTGUN_RELOAD_START_ADS + -- }, + -- ["reload_shotgun_finish_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_SHOTGUN_RELOAD_FINISH_ADS + -- }, + -- ["holster"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_HOLSTER + -- }, + -- ["holster_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_HOLSTER_EMPTY + -- }, + -- ["holster_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_HOLSTER_SILENCED + -- }, + -- ["silencer_attach"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_ATTACH_SILENCER + -- }, + -- ["silencer_detach"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_DETACH_SILENCER + -- }, + -- ["rof"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_FIREMODE + -- }, + -- ["rof_is"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_IFIREMODE + -- }, + -- ["inspect"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_FIDGET + -- }, + -- ["inspect_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_FIDGET_EMPTY + -- }, + -- ["inspect_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_FIDGET_SILENCED + -- }, + + --[[ Bash Base ]]-- + -- ["bash"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_HITCENTER + -- }, + -- ["bash_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_HITCENTER2 + -- }, + -- ["bash_empty"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_MISSCENTER + -- }, + -- ["bash_empty_silenced"] = { + -- ["type"] = TFA.Enum.ANIMATION_ACT, + -- ["value"] = ACT_VM_MISSCENTER2 + -- }, +-- } + + +-- Fake ACT enum entries added by the base +-- These values do not exist in the engine, so for those names use the replacement value in the $sequence definition: + +-- ACT_VM_FIDGET_EMPTY - ACT_CROSSBOW_FIDGET_UNLOADED +-- ACT_VM_FIDGET_SILENCED - ACT_RPG_FIDGET_UNLOADED +-- ACT_VM_HOLSTER_SILENCED - ACT_CROSSBOW_HOLSTER_UNLOADED +-- ACT_VM_RELOAD_ADS - ACT_IDLE_AIM_RIFLE_STIMULATED +-- ACT_VM_RELOAD_EMPTY_ADS - ACT_WALK_AIM_RIFLE_STIMULATED +-- ACT_VM_RELOAD_SILENCED_ADS - ACT_RUN_AIM_RIFLE_STIMULATED +-- ACT_SHOTGUN_RELOAD_START_ADS - ACT_IDLE_SHOTGUN_RELAXED +-- ACT_SHOTGUN_RELOAD_FINISH_ADS - ACT_IDLE_SHOTGUN_STIMULATED + + +--[[ Bow Base ]]-- +-- SWEP.BowAnimations = { + -- ["shake"] = { + -- ["type"] = TFA.Enum.ANIMATION_SEQ, + -- ["value"] = "tiredloop", + -- ["enabled"] = true + -- }, + -- ["shoot"] = { + -- ["type"] = TFA.Enum.ANIMATION_SEQ, + -- ["value"] = "fire_1", + -- ["enabled"] = true + -- }, + -- ["cancel"] = { + -- ["type"] = TFA.Enum.ANIMATION_SEQ, + -- ["value"] = "cancelarrow", + -- ["enabled"] = true + -- }, + -- ["draw"] = { + -- ["type"] = TFA.Enum.ANIMATION_SEQ, + -- ["value"] = "drawarrow", + -- ["enabled"] = true + -- } +-- } diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_attachment_template.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_attachment_template.lua new file mode 100644 index 0000000..ee9f8ae --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_attachment_template.lua @@ -0,0 +1,95 @@ + +-- TFA Base Attachment Template by TFA Base Devs + +-- To the extent possible under law, the person who associated CC0 with +-- TFA Base Template has waived all copyright and related or neighboring rights +-- to TFA Base Template. + +-- You should have received a copy of the CC0 legalcode along with this +-- work. If not, see . + +if not ATTACHMENT then + ATTACHMENT = {} +end + +ATTACHMENT.TFADataVersion = 1 -- If it is undefined, it fallbacks to 0 and WeaponTable gets migrated like SWEPs do + +-- ATTACHMENT.Base = "base" -- Attachment baseclass, defaults to "base" attachment + +ATTACHMENT.Name = "Example Attachment" +ATTACHMENT.ShortName = nil -- Abbreviation shown on the bottom left of the icon, generated from name if not defined +ATTACHMENT.Description = { + TFA.Attachments.Colors["+"], "Does something good", + TFA.Attachments.Colors["-"], "Does something bad", + -- Color(255, 255, 255), "bottom text", +} -- all colors are defined in lua/tfa/modules/tfa_attachments.lua +ATTACHMENT.Icon = nil -- "entities/tfa_ammo_match.png" -- Full path to the icon, reverts to '?' by default + +ATTACHMENT.WeaponTable = { -- The place where you change the stats (CACHED STATS ONLY!) + ["Primary"] = { + ["Damage"] = 60, -- For example, you want to change SWEP.Primary.Damage value to 60 + ["ClipSize"] = function(wep, stat) + return wep.Primary_TFA.ClipSize_Override or stat * 1.5 + end -- Stat functions support changing value dynamically (which is cached afterwards), SWEP.Primary_TFA contains original unchanged values + } +} + +-- ATTACHMENT.DInv2_GridSizeX = nil -- DInventory/2 Specific. Determines attachment's width in grid. +-- ATTACHMENT.DInv2_GridSizeY = nil -- DInventory/2 Specific. Determines attachment's height in grid. +-- ATTACHMENT.DInv2_Volume = nil -- DInventory/2 Specific. Determines attachment's volume in liters. +-- ATTACHMENT.DInv2_Mass = nil -- DInventory/2 Specific. Determines attachment's mass in kilograms. +-- ATTACHMENT.DInv2_StackSize = nil -- DInventory/2 Specific. Determines attachment's maximal stack size. + +--[[ +-- Default behavior is always allow, override to change +function ATTACHMENT:CanAttach(wep) + return true +end +]]-- + +--[[ +-- These functions are called BEFORE stat cache is rebuilt +function ATTACHMENT:Attach(wep) +end + +function ATTACHMENT:Detach(wep) +end +]]-- + +-- Attachment functions called from base +--[[ +-- Called from render target code if SWEP.RTDrawEnabled is true +function ATTACHMENT:RTCode(wep, rt_texture, w, h) +end +]]-- + +--[[ +-- Called from FireBullets for each bullet trace hit; arguments are passed from bullet callback +function ATTACHMENT:CustomBulletCallback(wep, attacker, trace, dmginfo) +end +]]-- + +--[[ +-- Called before stencil sight reticle is drawn +function ATTACHMENT:PreDrawStencilSight(wep, vm, ply, sightVElementTable) + -- 3D rendering context from PostDrawViewModel + -- https://wiki.facepunch.com/gmod/3D_Rendering_Functions + + -- return true -- to prevent SWEP:PreDrawStencilSight from being called + -- return false -- to stop reticle from drawing +end +]]-- + +--[[ +-- Called right after stencil sight reticle is drawn +function ATTACHMENT:PostDrawStencilSight(wep, vm, ply, sightVElementTable) + -- 3D rendering context from PostDrawViewModel + -- https://wiki.facepunch.com/gmod/3D_Rendering_Functions + + -- return true -- to prevent SWEP:PostDrawStencilSight from being called +end +]]-- + +if not TFA_ATTACHMENT_ISUPDATING then + TFAUpdateAttachments() +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_attbatch_template.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_attbatch_template.lua new file mode 100644 index 0000000..8cb908b --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_attbatch_template.lua @@ -0,0 +1,97 @@ + +-- TFA Base Batch Attachment Registration Template by TFA Base Devs + +-- To the extent possible under law, the person who associated CC0 with +-- TFA Base Template has waived all copyright and related or neighboring rights +-- to TFA Base Template. + +-- You should have received a copy of the CC0 legalcode along with this +-- work. If not, see . + +-- Place your file in addons//lua/tfa/attbatch/.lua +-- These are loaded after regular lua/tfa/att folder, allowing to place base attachments there. + +if not TFA_ATTACHMENT_ISUPDATING then TFAUpdateAttachments(false) return end + +-- TFA.Attachments.RegisterFromTable(string id, table ATTACHMENT) +TFA.Attachments.RegisterFromTable("your_att_id_here", { + TFADataVersion = 1, -- If it is undefined, it fallbacks to 0 and WeaponTable gets migrated like SWEPs do + + -- Base = "base", -- Attachment baseclass, defaults to "base" attachment + + Name = "Example Attachment", + ShortName = nil, -- Abbreviation shown on the bottom left of the icon, generated from name if not defined + Description = { + TFA.Attachments.Colors["+"], "Does something good", + TFA.Attachments.Colors["-"], "Does something bad", + -- Color(255, 255, 255), "bottom text", + }, -- all colors are defined in lua/tfa/modules/tfa_attachments.lua + Icon = nil, -- "entities/tfa_ammo_match.png" -- Full path to the icon, reverts to '?' by default + + WeaponTable = { -- The place where you change the stats (CACHED STATS ONLY!) + ["Primary"] = { + ["Damage"] = 60, -- For example, you want to change SWEP.Primary.Damage value to 60 + ["ClipSize"] = function(wep, stat) + return wep.Primary_TFA.ClipSize_Override or stat * 1.5 + end -- Stat functions support changing value dynamically (which is cached afterwards), SWEP.Primary_TFA contains original unchanged values + } + }, + + -- DInv2_GridSizeX = nil, -- DInventory/2 Specific. Determines attachment's width in grid. + -- DInv2_GridSizeY = nil, -- DInventory/2 Specific. Determines attachment's height in grid. + -- DInv2_Volume = nil, -- DInventory/2 Specific. Determines attachment's volume in liters. + -- DInv2_Mass = nil, -- DInventory/2 Specific. Determines attachment's mass in kilograms. + -- DInv2_StackSize = nil, -- DInventory/2 Specific. Determines attachment's maximal stack size. + + --[[ + -- Default behavior is always allow, override to change + CanAttach = function(self, wep) + return true + end, + ]]-- + + --[[ + -- These functions are called BEFORE stat cache is rebuilt + Attach = function(self, wep) + end, + + Detach = function(self, wep) + end, + ]]-- + + -- Attachment functions called from base + --[[ + -- Called from render target code if SWEP.RTDrawEnabled is true + RTCode = function(self, wep, rt_texture, w, h) + end, + ]]-- + + --[[ + -- Called from FireBullets for each bullet trace hit; arguments are passed from bullet callback + CustomBulletCallback = function(self, wep, attacker, trace, dmginfo) + end, + ]]-- + + --[[ + -- Called before stencil sight reticle is drawn + PreDrawStencilSight = function(self, wep, vm, ply, sightVElementTable) + -- 3D rendering context from PostDrawViewModel + -- https://wiki.facepunch.com/gmod/3D_Rendering_Functions + + -- return true -- to prevent SWEP:PreDrawStencilSight from being called + -- return false -- to stop reticle from drawing + end, + ]]-- + + --[[ + -- Called right after stencil sight reticle is drawn + PostDrawStencilSight = function(self, wep, vm, ply, sightVElementTable) + -- 3D rendering context from PostDrawViewModel + -- https://wiki.facepunch.com/gmod/3D_Rendering_Functions + + -- return true -- to prevent SWEP:PostDrawStencilSight from being called + end, + ]]-- +}) + +-- and so on diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_base_template.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_base_template.lua new file mode 100644 index 0000000..86f971f --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_base_template.lua @@ -0,0 +1,1059 @@ + +-- TFA Base Template by TFA Base Devs + +-- To the extent possible under law, the person who associated CC0 with +-- TFA Base Template has waived all copyright and related or neighboring rights +-- to TFA Base Template. + +-- You should have received a copy of the CC0 legalcode along with this +-- work. If not, see . + +-- M9K compatible version is dated as 0 (and 0 is also fallback if TFADataVersion not present) +-- as well as everything made for TFA Base before 4.7 +SWEP.TFADataVersion = 1 + +----------------- Basic Garry's Mod SWEP structure stats / TFA Base properties +SWEP.Base = "tfa_gun_base" +SWEP.Category = "TFA Template" -- The category. +-- Please, just choose something generic or something I've already done if you plan on only doing like one (or two or three) swep(s). +SWEP.SubCategory = "" -- Spawnmenu subcategory. +SWEP.Manufacturer = nil -- Gun Manufactrer (e.g. Hoeckler and Koch) +SWEP.Author = "" -- Author Tooltip +SWEP.Contact = "" -- Contact Info Tooltip +SWEP.Purpose = "" -- Purpose Tooltip +SWEP.Instructions = "" -- Instructions Tooltip +SWEP.Spawnable = false -- Can you, as a normal user, spawn this? +SWEP.AdminSpawnable = false -- Can an adminstrator spawn this? Does not tie into your admin mod necessarily, unless its coded to allow for GMod's default ranks somewhere in its code. Evolve and ULX should work, but try to use weapon restriction rather than these. +SWEP.DrawCrosshair = true -- Draw the crosshair? + +-- AKA DrawCrosshairIS +SWEP.DrawCrosshairIronSights = false -- Draw the crosshair in ironsights? +SWEP.PrintName = "TFA Base Template" -- Weapon name (Shown on HUD) +SWEP.Slot = 2 -- Slot in the weapon selection menu. Subtract 1, as this starts at 0. +SWEP.SlotPos = 73 -- Position in the slot +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.Weight = 30 -- This controls how "good" the weapon is for autopickup. + +SWEP.Type = nil -- Weapon type. Autodetected, but in some cases needs to be set manually. PLEASE MAKE SURE THE TYPE IS SET PROPERLY so that autodetection code won't make funky stuff. + -- Recognized generic types: "Pistol", "Machine Pistol", "Revolver", "Sub-Machine Gun", "Rifle", "Carbine", "Light Machine Gun", "Shotgun", "Designated Marksman Rifle", "Sniper Rifle", "Grenade", "Launcher"; "Dual Pistols", "Dual Revolvers", "Dual Sub-Machine Guns" and "Dual Guns". +SWEP.Type_Displayed = nil -- Weapon type override for displaying in the inspection menu. If you want to add wacky text below the gun name, DO IT HERE AND NOT IN THE ACTUAL TYPE FIELD! + +----------------- The Most basic weapon stats +SWEP.Primary.RPM = 600 -- This is in Rounds Per Minute / RPM +SWEP.Primary.NumShots = 1 -- The number of shots the weapon fires +SWEP.Primary.HullSize = 0 -- Big bullets, increase this value. They increase the hull size of the hitscan bullet. +SWEP.Primary.Automatic = true -- Automatic/Semi Auto + +-- If your gun is bullet based +SWEP.Primary.Damage = 0.01 -- Damage, in standard damage points. +SWEP.Primary.Force = nil -- Force value, leave nil to autocalc +-- elseif Your gun is projectile based +-- If your gun is projectile based, ignore Primary.Damage and Primary.Force +SWEP.Primary.Projectile = nil -- Entity to shoot +SWEP.Primary.ProjectileVelocity = 0 -- Entity to shoot's velocity +SWEP.Primary.ProjectileModel = nil -- Entity to shoot's model + +----------------- TFA Base basic stats +SWEP.Primary.Knockback = nil -- Autodetected if nil; this is the velocity kickback +SWEP.Primary.DryFireDelay = nil -- How long you have to wait after firing your last shot before a dryfire animation can play. Leave nil for full empty attack length. Can also use SWEP.StatusLength[ ACT_VM_BLABLA ] +SWEP.Primary.BurstDelay = nil -- Delay between bursts, leave nil to autocalculate +-- AKA FiresUnderwater +SWEP.Primary.FiresUnderwater = false -- Whenever this weapon can fire underwater + +----------------- TFA Base extended basic stats +SWEP.Primary.RPM_Semi = nil -- RPM for semi-automatic or burst fire. This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Burst = nil -- RPM for burst fire, overrides semi. This is in Rounds Per Minute / RPM + +SWEP.Primary.DamageTypeHandled = true -- true will handle damagetype in base +SWEP.Primary.DamageType = nil -- See DMG enum. This might be DMG_SHOCK, DMG_BURN, DMG_BULLET, etc. Leave nil to autodetect. DMG_AIRBOAT opens doors. + +----------------- TFA Base Basic sound handling +SWEP.Primary.Sound = Sound("") -- This is the sound of the weapon, when you shoot. +SWEP.Primary.SilencedSound = nil -- This is the sound of the weapon, when silenced. +-- AKA IronInSound +SWEP.Secondary.IronSightsInSound = nil -- Sound to play when iron sighting in? nil for default +-- AKA IronOutSound +SWEP.Secondary.IronSightsOutSound = nil -- Sound to play when iron sighting out? nil for default + +----------------- TFA Base Advanced sound handling +-- Requires looped .wav files prepared beforehand - https://wiki.facepunch.com/gmod/Creating_Looping_Sounds +SWEP.Primary.LoopSound = nil -- Looped fire sound, unsilenced +SWEP.Primary.LoopSoundSilenced = nil -- Looped fire sound, silenced +SWEP.Primary.LoopSoundTail = nil -- Loop end/tail sound, unsilenced +SWEP.Primary.LoopSoundTailSilenced = nil -- Loop end/tail sound, silenced +SWEP.Primary.LoopSoundAutoOnly = false -- Play loop sound for full-auto only? Fallbacks to Primary.Sound for semi/burst if true + +-- WORLD/THIRDPERSON/NPC FIRING SOUNDS! Fallbacks to first person sound if not defined. +SWEP.Primary.Sound_World = nil -- This is the sound of the weapon, when you shoot. +SWEP.Primary.SilencedSound_World = nil -- This is the sound of the weapon, when silenced. + +SWEP.Primary.LoopSound_World = nil -- Looped fire sound, unsilenced +SWEP.Primary.LoopSoundSilenced_World = nil -- Looped fire sound, silenced +SWEP.Primary.LoopSoundTail_World = nil -- Loop end/tail sound, unsilenced +SWEP.Primary.LoopSoundTailSilenced_World = nil -- Loop end/tail sound, silenced + +-- LOW AMMO +SWEP.FireSoundAffectedByClipSize = true -- Play low ammo and last shot sounds? Controlled by "Enable nearly-empty sounds" server option. +SWEP.LowAmmoSoundThreshold = nil -- Clip fill percentage below which low ammo sound will start playing; default is 0.33 +SWEP.LowAmmoSound = nil -- Low ammo sound +SWEP.LastAmmoSound = nil -- Last shot sound +-- Both are autodetected based on weapon type +-- Low ammo sounds that are supplied by the base: "TFA.LowAmmo.Handgun", "TFA.LowAmmo.Shotgun", "TFA.LowAmmo.AutoShotgun", "TFA.LowAmmo.MachineGun", "TFA.LowAmmo.AssaultRifle", "TFA.LowAmmo.DMR", "TFA.LowAmmo.Revolver", "TFA.LowAmmo.Sniper", "TFA.LowAmmo.SMG", "TFA.LowAmmo.SciFi", "TFA.LowAmmo.GL" +-- For last ammo sound just add _Dry at the end (for example "TFA.LowAmmo.Handgun_Dry") + +----------------- Jamming mechanics +SWEP.CanJam = true -- whenever weapon cam jam +SWEP.JamChance = 0.04 -- the (maximal) chance the weapon will jam. Newly spawned weapon will never jam on first shot for example. +-- Default value is 0.04 (4%) +-- Maxmial value is 1, means weapon will always jam when factor become 100 +-- Also remember that there is a minimal factor before weapon can jam +-- This number is not treated "as-is" but as basic value that needs to be concluded as chance +-- You don't really need to cry over it and trying to balance it, TFA Base will do the job for you +-- (TFA Base will calculate the best value between 0 and JamChance based on current JamFactor of the weapon) +SWEP.JamFactor = 0.06 -- How to increase jam factor after each shot. +-- When factor reach 100 it will mean that on each shot there will be SWEP.Primary.JamChance chance to jam +-- When factor reach 50 it will mean that on each shot there will be SWEP.Primary.JamChance / 2 chance to jam +-- and so on +-- Default value is 0.06, means weapon will jam with SWEP.Primary.JamChance chance right after 1666 shots + +-- These settings are good for Assault Rifles, however, not good for anything else. +-- Suggested stats: + +--[[ +-- Pistols +SWEP.JamChance = 0.20 +SWEP.JamFactor = 0.14 +]] + +--[[ +-- Revolvers +SWEP.JamChance = 0.17 +SWEP.JamFactor = 0.50 +]] + +--[[ +-- Miniguns +SWEP.JamChance = 0.03 +SWEP.JamFactor = 0.01 +]] + +--[[ +-- Submachine gun +SWEP.JamChance = 0.04 +SWEP.JamFactor = 0.09 +]] + +--[[ +-- Auto shotguns +SWEP.JamChance = 0.15 +SWEP.JamFactor = 0.2 +]] + +--[[ +-- Pump-action shotguns +SWEP.JamChance = 0.25 +SWEP.JamFactor = 0.3 +]] + +--[[ +-- Sniper rifle +SWEP.JamChance = 0.17 +SWEP.JamFactor = 0.35 +]] + +----------------- Silencing +SWEP.CanBeSilenced = false -- Can we silence? Requires animations. +SWEP.Silenced = false -- Silenced by default? + +----------------- Selective Fire Stuff +SWEP.SelectiveFire = false -- Allow selecting your firemode? +SWEP.DisableBurstFire = false -- Only auto/single? +SWEP.OnlyBurstFire = false -- No auto, only burst/single? +SWEP.BurstFireCount = nil -- Burst fire count override (autocalculated by the clip size if nil) +SWEP.DefaultFireMode = "" -- Default to auto or whatev +SWEP.FireModeName = nil -- Change to a text value to override it + +----------------- Ammo Related +SWEP.Primary.ClipSize = 0 -- This is the size of a clip + +SWEP.Primary.DefaultClip = 0 -- This is the number of bullets the gun gives you, counting a clip as defined directly above. +SWEP.Primary.Ammo = "none" -- What kind of ammo. Options, besides custom, include pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, and AirboatGun. +-- Pistol, buckshot, and slam like to ricochet. Use AirboatGun for a light metal peircing shotgun pellets +SWEP.Primary.AmmoConsumption = 1 -- Ammo consumed per shot + +-- AKA DisableChambering +SWEP.Primary.DisableChambering = false -- Disable round-in-the-chamber + +-- Recoil Related +SWEP.Primary.KickUp = 0 -- This is the maximum upwards recoil (rise) +SWEP.Primary.KickDown = 0 -- This is the maximum downwards recoil (skeet) +SWEP.Primary.KickHorizontal = 0 -- This is the maximum sideways recoil (no real term) +SWEP.Primary.StaticRecoilFactor = 0.5 -- Amount of recoil to directly apply to EyeAngles. Enter what fraction or percentage (in decimal form) you want. This is also affected by a convar that defaults to 0.5. + +-- Firing Cone Related +SWEP.Primary.Spread = .01 -- This is hip-fire acuracy. Less is more (1 is horribly awful, .0001 is close to perfect) +SWEP.Primary.IronAccuracy = .005 -- Ironsight accuracy, should be the same for shotguns +SWEP.Primary.DisplaySpread = nil -- Defaults to true. Display spread on customization screen? +SWEP.Primary.DisplayIronSpread = nil -- Defaults to Primary.DisplaySpread. Display spread on customization screen for iron sights? This is always false if Secondary.IronSightsEnabled is false + +-- Unless you can do this manually, autodetect it. If you decide to manually do these, uncomment this block and remove this line. +SWEP.Primary.SpreadMultiplierMax = nil -- How far the spread can expand when you shoot. Example val: 2.5 +SWEP.Primary.SpreadIncrement = nil -- What percentage of the modifier is added on, per shot. Example val: 1/3.5 +SWEP.Primary.SpreadRecovery = nil -- How much the spread recovers, per second. Example val: 3 +SWEP.Primary.SpreadRecoveryDelay = nil -- Delay in seconds before spread starts to recover + +----------------- Range stats +SWEP.Primary.DisplayFalloff = nil -- Defaults to true (false for melees) + +-- Use these if you don't want/understand how to use LUT below. +-- These values are automatically converted to RangeFalloffLUT table +SWEP.Primary.FalloffMetricBased = false -- Set to true if you set up values below +SWEP.Primary.FalloffByMeter = nil -- How much damage points will bullet loose when travel +SWEP.Primary.MinRangeStartFalloff = nil -- How long will bullet travel in Meters before starting to lose damage? +SWEP.Primary.MaxFalloff = nil -- Maximal amount of damage to be lost + +-- Use this for full control over damage dropoff. +--[[ +SWEP.Primary.RangeFalloffLUT = { + bezier = true, -- Whenever to use Bezier or not to interpolate points? + -- you probably always want it to be set to true + range_func = "quintic", -- function to spline range + -- "linear" for linear splining. + -- Possible values are "quintic", "cubic", "cosine", "sinusine", "linear" or your own function + units = "meters", -- possible values are "inches", "inch", "hammer", "hu" (are all equal) + -- everything else is considered to be meters + lut = { -- providing zero point is not required + -- without zero point it is considered to be as {range = 0, damage = 1} + {range = 5, damage = 0.9}, + {range = 12, damage = 0.8}, + {range = 18, damage = 0.5}, + {range = 24, damage = 0.2}, + {range = 30, damage = 0.55}, + {range = 38, damage = 0.76}, + {range = 50, damage = 1}, + {range = 52, damage = 0.96}, + {range = 60, damage = 0.3}, + {range = 70, damage = 0.1}, + } +} +]] + +----------------- Spread stats +-- Spread bias +-- The bigger is the number, the stronger is spread +-- along specified axis (Yaw for X, Pitch for Y). Note that this is not clamped and it is possible +-- to make spread along X or Y or both bigger than current aimcone +SWEP.Primary.SpreadBiasYaw = nil -- Defaults to 1 +SWEP.Primary.SpreadBiasPitch = nil -- Defaults to 1 + +-- Check common/bullet.lua for more information on how to implement custom +-- functions if you REALLY need them + +-- Less is more. Accuracy * 0.5 = Twice as accurate, Accuracy * 0.1 = Ten times as accurate +SWEP.CrouchAccuracyMultiplier = nil -- 0.5 +SWEP.JumpAccuracyMultiplier = nil -- 2 +SWEP.WalkAccuracyMultiplier = nil -- 1.35 +SWEP.ChangeStateAccuracyMultiplier = nil -- 1.5 + +----------------- Recoil related things +SWEP.ViewModelPunchPitchMultiplier = nil -- Default value is 0.5 +SWEP.ViewModelPunchPitchMultiplier_IronSights = nil -- Default value is 0.09 + +SWEP.ViewModelPunch_MaxVertialOffset = nil -- Default value is 3 +SWEP.ViewModelPunch_MaxVertialOffset_IronSights = nil -- Default value is 1.95 +SWEP.ViewModelPunch_VertialMultiplier = nil -- Default value is 1 +SWEP.ViewModelPunch_VertialMultiplier_IronSights = nil -- Default value is 0.25 + +SWEP.ViewModelPunchYawMultiplier = nil -- Default value is 0.6 +SWEP.ViewModelPunchYawMultiplier_IronSights = nil -- Default value is 0.25 + +-- AKA IronRecoilMultiplier +SWEP.Primary.IronRecoilMultiplier = nil -- 0.5 -- Multiply recoil by this factor when we're in ironsights. This is proportional, not inversely. + +SWEP.CrouchRecoilMultiplier = nil -- 0.65 -- Multiply recoil by this factor when we're crouching. This is proportional, not inversely. +SWEP.JumpRecoilMultiplier = nil -- 1.3 -- Multiply recoil by this factor when we're jumping. This is proportional, not inversely. +SWEP.WallRecoilMultiplier = nil -- 1.1 -- Multiply recoil by this factor when we're walking. This is proportional, not inversely. (yes the name is "Wall" it's not a typo in template) +SWEP.ChangeStateRecoilMultiplier = nil -- 1.3 -- Multiply recoil by this factor when we're changing state e.g. not completely ironsighted. This is proportional, not inversely. + +-- Stats below have no effect if RecoilLUT is not defined +SWEP.Primary.RecoilLUT_IronSightsMult = nil -- Defaults to 0.5 +-- controls how much effective LUT is when iron sighting +SWEP.Primary.RecoilLUT_AnglePunchMult = nil -- Defaults to 0.25 +-- controls how much effective LUT at pushing EyeAngles of shooter +SWEP.Primary.RecoilLUT_ViewPunchMult = nil -- Defaults to 1 +-- controls how much effective LUT at viewpunch + +--[[ +SWEP.Primary.RecoilLUT = { + ["in"] = { + bezier = true, + func = "quintic", -- function to inerpolate progress when sampling points from table + -- Possible values are "quintic", "cubic", "cosine", "sinusine", "linear" or your own function + cooldown_speed = 1, -- how much to loose progress when we are at this stage + -- 1 means we lose entire progress in a second + increase = 0.1, -- how much to increase progress after shot + -- 0.1 means that this stage would be full after 10 shots + wait = 0.1, -- how much time do we wait in seconds after we stopped shooting + -- after this time, IN stage begin to cooldown until it reach zero + + -- table is always prepended with an Angle() + -- only Pitch and Yaw are utilized + -- sampled point is added to aimvector of player + -- when they shoot + points = { + Angle(-1, 0.4), + Angle(-4, -2), + Angle(-6, -4), + Angle(-10, -6), + } + }, + + ["loop"] = { + bezier = true, + func = "quintic", + -- this stage can not cooldown, so no cooldown_speed is defined + increase = 0.1, -- when LOOP stage reach 1, it is reset to 0 + wait = 0.1, -- how much time do we wait in seconds after we stopped shooting + -- after this time, stage switch to OUT + + -- table is NOT prepended with an Angle() + -- make sure it's starting point match the one from IN stage + -- last and first points are connected automatically + points = { + Angle(-10, -6), + Angle(-12, -0.4), + Angle(-8, 9), + Angle(-11, 12), + Angle(-13, 2), + Angle(-8, -4), + } + }, + + ["out"] = { + bezier = true, + func = "quintic", + -- this stage is different + -- it is only started after LOOP took place + -- shooting in this stage will actually roll back it's state + -- until it reach zero and switch back to LOOP + -- cooling down actually increase stage's progress + cooldown_speed = 1, + -- increase act as negative number to reach zero in this stage + increase = 0.2, + + -- after this stage reach 1, everything reset to IN and wait for next fire + -- table is always appended with an Angle() + + -- starting point is dynamic + -- and will always match current LOOP's one + points = { + Angle(-7, -2), + Angle(-4, -1), + Angle(-2, 0), + } + } +} +]] + +----------------- Penetration Related +-- AKA MaxPenetrationCounter / MaxPenetration +SWEP.Primary.MaxSurfacePenetrationCount = nil -- Defaults to infinity and is clamped by convar which defaults to 100 +-- The maximum number of surface penetrations. You probably shouldn't touch this unless you need to remove penetration completely or limit it somehow +-- aside from Penetration power exhaust + +SWEP.Primary.PenetrationPower = nil -- Defaults to autodetect +-- This control how much we can penetrate various surfaces in hammer units +-- So, PenetrationPower of 400 say that we can penetrate 400 hammer units of material with penetration multiplier of 1 +-- 800 hammer units of material with penetration multiplier of 0.5 +-- 1600 hammer units of material with penetration multiplier of 0.25 +-- and so on +-- TFA Base is designed to work with small to insanely large penetration power values, so don't be shy at experimenting with this value + +SWEP.Primary.PenetrationMultiplier = nil -- Defaults to 1 +-- Change the amount of something this gun can penetrate through +-- the LESSER this value is, the BETTER is penetration +-- this is basically multiplier for next values +-- Checkout https://wiki.facepunch.com/gmod/Enums/MAT for list of all materials +--[==[ +-- AKA PenetrationMaterials +SWEP.Primary.PenetrationMaterials = { + [MAT_NAME] = 0.5, +} +]==] + +----------------- Mobility / Moving speed related +SWEP.AllowSprintAttack = nil -- Allows firing the weapon while sprinting; false by default +-- Multiplies moving speed (velocity) of owner by this value +-- e.g. if their WalkSpeed is 300 HU/s and they hold gun with multiplier of 0.75 +-- then when they press +forward their speed will be capped at 225 HU/s +-- AKA MoveSpeed +SWEP.RegularMoveSpeedMultiplier = nil -- Defaults to 1 +-- Multiply the player's movespeed by this when aiming down sights +-- !!! This penalty / bonus is not additive (multiplied by RegularMoveSpeedMultiplier) and is preemptive +-- (e.g. if RegularMoveSpeedMultiplier is 0.5 and AimingDownSightsSpeedMultiplier is 1, then when fully Aiming Down Sights +-- speed multiplier will be 1, when half-way ADS'ing speed will be 0.75 and so on +-- Keep in mind that this also affect mouse sensivity when aiming down sights (creating kind of "mouse weight" effect) +-- AKA IronSightsMoveSpeed +SWEP.AimingDownSightsSpeedMultiplier = nil -- Defaults to 0.8 + +----------------- ViewModel related +SWEP.ViewModel = "models/your/path/here.mdl" -- Viewmodel path +SWEP.ViewModelFOV = 65 -- This controls how big the viewmodel looks. Less is more. +SWEP.ViewModelFlip = false -- Set this to true for CSS models, or false for everything else (with a righthanded viewmodel.) +SWEP.UseHands = false -- Use gmod c_arms system. + +-- The viewmodel positional offset, constantly. +-- Subtract this from any other modifications to viewmodel position. +-- AKA VMPos (SWEP Construction Kit naming, VMPos is always checked for presence and it always override ViewModelPosition if present) +SWEP.ViewModelPosition = Vector(0, 0, 0) +-- AKA VMAng (SWEP Construction Kit naming) +-- The viewmodel angular offset, constantly. +-- Subtract this from any other modifications to viewmodel angle. +SWEP.ViewModelAngle = Vector(0, 0, 0) + +-- Position when sprinting +-- AKA RunSightsPos (SWEP Construction Kit naming) +SWEP.SprintViewModelPosition = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably +-- AKA RunSightsAng (SWEP Construction Kit naming) +SWEP.SprintViewModelAngle = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably + +-- Position when crouching +-- Viewmodel offset when player is crouched +-- AKA CrouchPos +SWEP.CrouchViewModelPosition = nil -- Defaults to nothing, use Vector(0, 0, 0) as starting point +-- AKA CrouchAng +SWEP.CrouchViewModelAngle = nil -- Defaults to nothing, use Vector(0, 0, 0) as starting point + +SWEP.IronSightsPosition = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably +SWEP.IronSightsAngle = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably + +-- Inspection position +-- Replace with a vector, in style of ironsights position, to be used for inspection +SWEP.InspectPos = nil -- Vector(0, 0, 0) +SWEP.InspectAng = nil -- Vector(0, 0, 0) -- Replace with a vector, in style of ironsights angle, to be used for inspection + +-- Whenever positions defined above are additive to any other position modification +-- Set to false for an easier time using VMPos +-- If true, VMPos will act as a constant value added to every other position modification +-- (iron sights position, run position, everything else) +-- AKA VMPos_Additive (SWEP Construction Kit naming) +SWEP.AdditiveViewModelPosition = true +SWEP.CenteredViewModelPosition = nil -- The viewmodel positional offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.CenteredViewModelAngle = nil -- The viewmodel angular offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.ViewModelBodygroups = nil -- { + -- [0] = 1, + -- [1] = 4, + -- [2] = etc. +-- } + +-- Procedural sights position. +-- Applies !!BEFORE!! IronSightsPosition/IronSightsAngle and ViewModelPosition/ViewModelAngle +SWEP.ProceduralSight = nil -- Enables procedural viewmodel sight position lookup +SWEP.ProceduralSight_VElement = nil -- "reciever_p90_fn_p90_std" -- Name of VElement to follow; must be active to work; when not defined viewmodel is selected instead + +-- Position lookup type: QC Attachment +-- SWEP.ProceduralSight_PositionType = TFA.Enum.SIGHTSPOS_ATTACH -- Enabled by default +-- SWEP.ProceduralSight_Attachment = "mod_aim_camera" -- $attachment name or ID to get the position of + +-- Position lookup type: Bone +-- SWEP.ProceduralSight_PositionType = TFA.Enum.SIGHTSPOS_BONE +-- SWEP.ProceduralSight_Bone = "ValveBiped.P250_rootbone" -- Name of the bone to get the position of + +SWEP.ProceduralSight_OffsetPos = nil -- Vector(0, 0, 0) +SWEP.ProceduralSight_OffsetAng = nil -- Angle(0, 0, 0) + +SWEP.AllowIronSightsDoF = true -- whenever allow DoF effect on viewmodel when zoomed in with iron sights +SWEP.IronSightsDoF_FocusAttachment = nil -- number of the QC attachment used for DoF effect focus (autodetected to muzzle attachment by default) + +-- Enable ADS reload animations support (requires animations to be enabled in SWEP.Animations) +SWEP.IronSightsReloadEnabled = nil +-- Lock ADS state when reloading +SWEP.IronSightsReloadLock = true + +-- Export from SWEP Creation Kit (if it is being utilized to create gun) +-- For each item that can/will be toggled, set active = false in its individual table +-- AKA VElements (SWEP Construction Kit naming) +SWEP.ViewModelElements = nil --[[ { + ["element_name"] = { + -- Basic SCK table syntax + -- Copy only one type element table! + + -- Model element: + ["type"] = "Model", + ["model"] = "models/error.mdl", -- Model path + ["bone"] = "", -- Bone name of the viewmodel/parent element to attach to (ignored when bonemerge is used) + ["rel"] = "", -- Name of the parent element, empty means viewmodel + ["pos"] = Vector(0, 0, 0), -- Position offset (ignored when bonemerge is used) + ["angle"] = Angle(0, 0, 0), -- Angle offset (ignored when bonemerge is used) + ["size"] = Vector(1, 1, 1), -- Element size (ignored when bonemerge is used) + ["color"] = Color(255, 255, 255, 255), -- Color and opacity of the element + ["surpresslightning"] = false, -- Suppress engine lighting for the element + ["material"] = "", -- Singular material override (keps for backwards compatibilty, use the "materials" table below instead) + ["skin"] = 0, -- Material skin index to use + ["bodygroup"] = {}, -- Bodygroup overrides by index + ["active"] = true, -- Element is active by default? + + -- Sprite element: + ["type"] = "Sprite", + ["sprite"] = "sprites/glow1", -- Sprite texture path (for material use the "vmt" key instead) + ["bone"] = "", -- Bone name of the viewmodel/parent element to attach to + ["rel"] = "", -- Name of the parent element, empty means viewmodel + ["pos"] = Vector(0, 0, 0), -- Position offset + ["size"] = Vector(1, 1, 1), -- Sprite size + ["color"] = Color(255, 255, 255, 255), -- Color and opacity of the element + -- VMT generation parameters: + ["nocull"] = false, + ["additive"] = false, + ["vertexalpha"] = false, + ["vertexcolor"] = false, + ["ignorez"] = false, + ["active"] = true, -- Element is active by default? + + -- Quad element: + ["type"] = "Quad", + ["bone"] = "", -- Bone name of the viewmodel/parent element to attach to + ["rel"] = "", -- Name of the parent element, empty means viewmodel + ["pos"] = Vector(0, 0, 0), -- Position offset + ["angle"] = Angle(0, 0, 0), -- Angle offset + ["size"] = Vector(1, 1, 1), -- Element size + ["draw_func"] = function(element) -- Draw function is called when element is active + -- your code here + end, + ["active"] = true, -- Element is active by default? + + -- Additional syntax from TFA Base: + ["attachment"] = "muzzle", -- Parent attachment name, overrides the "bone" value (all types) + ["bonemerge"] = false, -- Bonemerge model instead of positioning it at reference point (model only) + ["materials"] = {}, -- Submaterials replacement table (model only) + ["translucent"] = false, -- Workaround for translucent models drawing behind player hands (all types) + ["draw_func_outer"] = function(element, pos, ang, size) -- Secondary draw function added to address stuff like player hands clipping (quad only) + -- your code here + end, + + -- For models with stencil sights: + ["mask"] = "models/error.mdl", -- Sight mask model path; if not defined the element itself becomes the stencil mask + ["reticle"] = "models/error.mdl", -- Model path for model reticle type; must be bonemergeable on this element to work + } +}]]-- + +----------------- Iron sights related +-- AKA data.ironsights +SWEP.Secondary.IronSightsEnabled = true +-- Controls Field of View when scoping in. +-- Default FoV of Garry's Mod is 75, most of players prefer 90 +-- Lesser FoV value means stronger "zoom" +-- Good value to begin experimenting with is 70 +-- AKA Secondary.IronFOV +SWEP.Secondary.OwnerFOV = 70 +-- AKA IronViewModelFOV +SWEP.Secondary.ViewModelFOV = nil -- Defaults to 65. Target viewmodel FOV when aiming down the sights. + +SWEP.Secondary.OwnerFOVUseThreshold = nil -- true/false -- If enabled, OwnerFOV will be changed only past the threshold when aiming. Defaults to SWEP.Scoped value +SWEP.Secondary.OwnerFOVThreshold = nil -- 0 to 1 -- defaults to SWEP.ScopeOverlayThreshold (which is 0.875 by default) + +----------------- Worldmodel related +SWEP.WorldModel = "models/your/wmodel/path/here.mdl" -- Weapon world model path +-- AKA Bodygroups_W +SWEP.WorldModelBodygroups = nil -- { +-- [0] = 1, +-- [1] = 4, +-- [2] = etc. +-- } + +SWEP.HoldType = "" -- This is how others view you carrying the weapon. Options include: +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- You're mostly going to use ar2, smg, shotgun or pistol. rpg and crossbow make for good sniper rifles + +-- Holdtypes overrides (not cached, nil or "" to disable; only active when dynamic holdtypes are enabled except sprint override) +SWEP.IronSightHoldTypeOverride = nil -- ADS +SWEP.SprintHoldTypeOverride = nil -- Sprinting, holster and safety/passive hold +SWEP.ReloadHoldTypeOverride = nil -- Reloading +SWEP.CrouchHoldTypeOverride = nil -- Crouching + +-- Procedural world model offset +-- Value below is good enough for Counter-Strike: Source worldmodels +--[[ +-- AKA Offset +SWEP.WorldModelOffset = { + Pos = { + Up = 0, + Right = 0, + Forward = 0 + }, + + Ang = { + Up = -1, + Right = -2, + Forward = 178 + }, + + Scale = 1 +} +]] + + +-- Export from SWEP Creation Kit. +-- For each item that can/will be toggled, set active = false in its individual table +-- AKA WElements (if it is being utilized to create gun) +SWEP.WorldModelElements = nil + +----------------- Scopes related +SWEP.IronSightsSensitivity = 1 -- Useful for a RT scope. Change this to 0.25 for 25% sensitivity. This is if normal FOV compenstaion isn't your thing for whatever reason, so don't change it for normal scopes. +SWEP.BoltAction = false -- Unscope/sight after you shoot? +SWEP.Scoped = false -- Draw a scope overlay? +SWEP.ScopeOverlayThreshold = 0.875 -- Percentage you have to be sighted in to see the scope. +SWEP.BoltTimerOffset = 0.25 -- How long you stay sighted in after shooting, with a bolt action. +SWEP.ScopeScale = 0.5 -- Scale of the scope overlay +SWEP.ReticleScale = 0.7 -- Scale of the reticle overlay + +-- GDCW Overlay Options. Only choose one. +SWEP.Secondary.UseACOG = false -- Overlay option +SWEP.Secondary.UseMilDot = false -- Overlay option +SWEP.Secondary.UseSVD = false -- Overlay option +SWEP.Secondary.UseParabolic = false -- Overlay option +SWEP.Secondary.UseElcan = false -- Overlay option +SWEP.Secondary.UseGreenDuplex = false -- Overlay option + +-- Clientside only +-- Defines custom scope overlay +if CLIENT then + SWEP.Secondary.ScopeTable = nil --[[ + { + ScopeBorder = Color(0, 0, 0, 255), + ScopeMaterial = Material("scope/gdcw_closedsight"), + ScopeOverlay = Material("effects/combine_binocoverlay"), + ScopeCrosshair = { -- can also be just a Material() value + r = 0, g = 0, b = 0, a = 255, -- color + scale = 1, -- scale or crosshair line width if no material specified + Material = Material("scope/gdcw_acogcross"), -- material, OPTIONAL! + } + } + ]] +end + +----------------- Looped reload related + +-- AKA Shotgun +SWEP.LoopedReload = false -- Enable looped / shotgun style / one round at time reloading. +SWEP.LoopedReloadInsertAmount = 1 -- How much rounds to insert on each reload cycle + +SWEP.ShotgunEmptyAnim = false -- Enable emtpy reloads on shotguns? +SWEP.ShotgunEmptyAnim_Shell = true -- Enable insertion of a shell directly into the chamber on empty reload? +SWEP.ShotgunStartAnimShell = false -- shotgun start anim inserts shell + +-- For looped reloads, how long it take to insert extra round into weapon +-- Adjuct to match visual representation when it actually insert round +-- AKA ShellTime +SWEP.LoopedReloadInsertTime = 0.35 + +----------------- Animation stuff / procedural ones (Lua animated) + +-- ViewModel custom blowback +SWEP.BlowbackEnabled = false -- Enable Blowback? +SWEP.BlowbackVector = Vector(0, -1, 0) -- Vector to move bone relative to bone orientation. +SWEP.BlowbackAngle = nil -- Angle(0, 0, 0) +SWEP.BlowbackRandomAngleMin = nil -- Angle(.1, -.5, -1) +SWEP.BlowbackRandomAngleMax = nil -- Angle(.2, .5, 1) +SWEP.BlowbackCurrentRoot = 0 -- Amount of blowback currently, for root +SWEP.BlowbackCurrent = 0 -- Amount of blowback currently, for bones +SWEP.BlowbackBoneMods = nil -- Viewmodel bone mods via SWEP Creation Kit +SWEP.Blowback_Only_Iron = true -- Only do blowback on ironsights +SWEP.Blowback_PistolMode = false -- Do we recover from blowback when empty? +SWEP.Blowback_Shell_Enabled = true -- Shoot shells through blowback animations +SWEP.Blowback_Shell_Effect = "ShellEject" -- Which shell effect to use +SWEP.BlowbackAllowAnimation = nil -- Allow playing shoot animation with blowback? + +-- Lua animated reload animation +-- Animate first person reload using Lua? +-- When reloading weapon will be offset to holster position (TODO: Add separate property for that) +-- AKA DoProceduralReload +SWEP.IsProceduralReloadBased = false +SWEP.ProceduralReloadTime = 1 -- Procedural reload time in seconds + +-- Animation / sequence control +SWEP.StatusLengthOverride = {} -- Changes the status delay of a given animation; only used on reloads. Otherwise, use SequenceLengthOverride or one of the others +SWEP.SequenceLengthOverride = {} -- Changes both the status delay and the nextprimaryfire of a given animation +SWEP.SequenceTimeOverride = {} -- Like above but changes animation length to a target +SWEP.SequenceRateOverride = {} -- Like above but scales animation length rather than being absolute + +SWEP.ProceduralHolsterEnabled = nil -- Defaults to autodetection (if weapon has no ACT_VM_HOLSTER animation this is enabled if not specified) +SWEP.ProceduralHolsterTime = 0.3 +-- AKA ProceduralHolsterPos +SWEP.ProceduralHolsterPosition = Vector(3, 0, -5) +-- AKA ProceduralHolsterAng +SWEP.ProceduralHolsterAngle = Vector(-40, -30, 10) + +----------------- Basic animation related + +-- TFA.Enum.IDLE_DISABLED = No idle +-- TFA.Enum.IDLE_LUA = Lua animated idle +-- TFA.Enum.IDLE_ANI = Model's animated idle +-- TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH +SWEP.Idle_Blend = 0.25 -- Start an idle this far early into the end of a transition +SWEP.Idle_Smooth = 0.05 -- Start an idle this far early into the end of another animation +-- Model based animations Below + +-- TFA.Enum.LOCOMOTION_ANI = Model's animation +-- TFA.Enum.LOCOMOTION_LUA = Lua only +-- TFA.Enum.LOCOMOTION_HYBRID = TFA.Enum.LOCOMOTION_ANI + TFA.Enum.LOCOMOTION_LUA +-- Keep in mind that HYBRID sometimes produce very weird results, especially if +-- model's animation is "out of sync" with Lua's one +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_LUA +--[[ +SWEP.IronAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_To_Iron", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_To_Iron_Dry", + ["transition"] = true + }, -- Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_Iron", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_Iron_Dry" + }, -- Looping Animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Iron_To_Idle", -- Number for act, String/Number for sequence + ["value_empty"] = "Iron_To_Idle_Dry", + ["transition"] = true + }, -- Outward transition + ["shoot"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Fire_Iron", -- Number for act, String/Number for sequence + ["value_last"] = "Fire_Iron_Last", + ["value_empty"] = "Fire_Iron_Dry" + } -- What do you think +} +]] + +-- TFA.Enum.LOCOMOTION_ANI = Model's animation +-- TFA.Enum.LOCOMOTION_LUA = Lua only +-- TFA.Enum.LOCOMOTION_HYBRID = TFA.Enum.LOCOMOTION_ANI + TFA.Enum.LOCOMOTION_LUA +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_LUA +--[[ +SWEP.SprintAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_to_Sprint", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_to_Sprint_Empty", + ["transition"] = true + }, -- Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Sprint_", -- Number for act, String/Number for sequence + ["value_empty"] = "Sprint_Empty_", + ["is_idle"] = true + }, -- looping animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Sprint_to_Idle", -- Number for act, String/Number for sequence + ["value_empty"] = "Sprint_to_Idle_Empty", + ["transition"] = true + } -- Outward transition +} +]] + +-- TFA.Enum.LOCOMOTION_ANI = Model's animation +-- TFA.Enum.LOCOMOTION_LUA = Lua only +-- TFA.Enum.LOCOMOTION_HYBRID = TFA.Enum.LOCOMOTION_ANI + TFA.Enum.LOCOMOTION_LUA +SWEP.Walk_Mode = TFA.Enum.LOCOMOTION_LUA +--[[ +SWEP.WalkAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_to_Walk", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_to_Walk_Empty", + ["transition"] = true + }, -- Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Walk", -- Number for act, String/Number for sequence + ["value_empty"] = "Walk_Empty", + ["is_idle"] = true + }, -- looping animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Walk_to_Idle", -- Number for act, String/Number for sequence + ["value_empty"] = "Walk_to_Idle_Empty", + ["transition"] = true + } -- Outward transition +} +]] + +--[[ +-- Looping fire animation (full-auto only) +SWEP.ShootAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "ShootLoop_Start", -- Number for act, String/Number for sequence + ["value_is"] = "ShootLoop_Iron_Start", -- Number for act, String/Number for sequence + ["transition"] = true + }, -- Looping Start, fallbacks to loop + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "ShootLoop", -- Number for act, String/Number for sequence, + ["value_is"] = "ShootLoop_Iron", -- Number for act, String/Number for sequence, + ["is_idle"] = true, + }, -- Looping Animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "ShootLoop_End", -- Number for act, String/Number for sequence + ["value_is"] = "ShootLoop_Iron_End", -- Number for act, String/Number for sequence + ["transition"] = true + }, -- Looping End +} +]] +SWEP.ShootAnimationLoopAutoOnly = nil -- true + +-- TFA.Enum.LOCOMOTION_ANI = Model's animation +-- TFA.Enum.LOCOMOTION_LUA = Lua only +-- TFA.Enum.LOCOMOTION_HYBRID = TFA.Enum.LOCOMOTION_ANI + TFA.Enum.LOCOMOTION_LUA +SWEP.Customize_Mode = TFA.Enum.LOCOMOTION_LUA +--[[ +SWEP.CustomizeAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "customization_in", -- Number for act, String/Number for sequence + ["transition"] = true + }, + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "customization_idle", -- Number for act, String/Number for sequence + ["is_idle"] = true + }, + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "customization_out", -- Number for act, String/Number for sequence + ["transition"] = true + } +} +]] + +--[[ +SWEP.PumpAction = { -- Pump/bolt animations + ["type"] = TFA.Enum.ANIMATION_ACT, -- Sequence or act + ["value"] = ACT_VM_PULLBACK_HIGH, -- Number for act, String/Number for sequence + ["value_empty"] = ACT_VM_PULLBACK, -- Last shot pump + ["value_is"] = ACT_VM_PULLBACK_LOW, -- ADS pump +} +]] -- + +----------------- Effects related + +-- Attachments +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellAttachment = "2" -- Should be "2" for CSS models or "shell" for hl2 models +SWEP.MuzzleFlashEnabled = true -- Enable muzzle flash +SWEP.MuzzleAttachmentRaw = nil -- This will override whatever string you gave. This is the raw attachment NUMBER. This is overridden or created when a gun makes a muzzle event. +SWEP.AutoDetectMuzzleAttachment = false -- For multi-barrel weapons, detect the proper attachment? +SWEP.MuzzleFlashEffect = nil -- Change to a string of your muzzle flash effect. Copy/paste one of the existing from the base. +SWEP.SmokeParticle = nil -- Smoke particle (ID within the PCF), defaults to something else based on holdtype; "" to disable +SWEP.EjectionSmokeEnabled = true -- Disable automatic ejection smoke + +-- Shell eject override +SWEP.LuaShellEject = false -- Enable shell ejection through lua? +SWEP.LuaShellEjectDelay = 0 -- The delay to actually eject things +SWEP.LuaShellModel = nil -- The model to use for ejected shells +SWEP.LuaShellScale = nil -- The model scale to use for ejected shells +SWEP.LuaShellYaw = nil -- The model yaw rotation ( relative ) to use for ejected shells +SWEP.LuaShellSound = nil -- Path to sound file that will be played on impact for ejected shells + -- If not set, "shotgun" in path will force to use shotgun shell impact sounds set + +-- Tracer Stuff +SWEP.TracerName = nil -- Change to a string of your tracer name. Can be custom. There is a nice example at https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/base/entities/effects/tooltracer.lua +SWEP.TracerCount = 3 -- 0 disables, otherwise, 1 in X chance + +-- Impact Effects +SWEP.ImpactEffect = nil -- Impact Effect +SWEP.ImpactDecal = nil -- Impact Decal + +----------------- Event table +-- Utilized for firing custom events (including running Lua code) when an action is played. +-- This can even do stuff like playing a pump animation after shooting, discarding clip when reloading +-- playing sounds and so much more! +SWEP.EventTable = {} + +-- example: +--[==[ +SWEP.EventTable = { + [ACT_VM_RELOAD] = { + {["time"] = 0.1, ["type"] = "lua", ["value"] = function(wep, viewmodel, ifp --[[IsFirstTimePredicted()]]) end, ["client"] = true, ["server"] = true}, + {["time"] = 0.1, ["type"] = "sound", ["value"] = Sound("x")} + } +} +]==] + +----------------- Render target related +SWEP.RTMaterialOverride = nil -- Take the material you want out of PrintTable(LocalPlayer():GetViewModel():GetMaterials()), subtract 1 from its index, and set it to this. +SWEP.RTOpaque = false -- Do you want your render target to be opaque? +SWEP.RTCode = nil -- function(self) return end -- This is the function to draw onto your rendertarget +SWEP.RTBGBlur = true -- Draw background blur when 3D scope is active? + +----------------- Akimbo related +-- AKA Akimbo +SWEP.IsAkimbo = false -- Akimbo gun? Alternates between primary and secondary attacks. +-- AKA AkimboHUD +SWEP.EnableAkimboHUD = true -- Draw holographic HUD for both weapons? + +----------------- Attachments +SWEP.Attachments = { + --[[ + [slot number] = { + atts = { + "si_eotech", + -- ... + }, -- table of available attachments IDs + + sel = 0, -- index or ID of pre-selected attachment (index starts with 1) + default = nil, -- attachment ID to equip on deselect + hidden = nil, -- true to hide category from attachments selector (this does not prevent attachments to be selected through other means!) + } + ]] + + -- sel allows you to have an attachment pre-selected, and is used internally by the base to show which attachment is selected in each category. +} + +SWEP.AttachmentDependencies = {} -- {["si_acog"] = {"bg_rail", ["type"] = "OR"}} -- type could also be AND to require multiple +SWEP.AttachmentExclusions = {} -- { ["si_iron"] = { [1] = "bg_heatshield"} } +SWEP.AttachmentTableOverride = {} --[[{ -- overrides WeaponTable for attachments + ["ins2_ub_laser"] = { -- attachment id, root of WeaponTable override + ["ViewModelElements"] = { + ["laser_rail"] = { + ["active"] = true + }, + }, + } +}]] +SWEP.AttachmentIconOverride = {} --[[{ -- overrides icons for attachments + ["am_magnum"] = Material("entities/ammo_357.png", "smooth") +}]] + +SWEP.DInv2_GridSizeX = nil -- DInventory/2 Specific. Determines weapon's width in grid. This is not TFA Base specific and can be specified to any Scripted SWEP. +SWEP.DInv2_GridSizeY = nil -- DInventory/2 Specific. Determines weapon's height in grid. This is not TFA Base specific and can be specified to any Scripted SWEP. +SWEP.DInv2_Volume = nil -- DInventory/2 Specific. Determines weapon's volume in liters. This is not TFA Base specific and can be specified to any Scripted SWEP. +SWEP.DInv2_Mass = nil -- DInventory/2 Specific. Determines weapon's mass in kilograms. This is not TFA Base specific and can be specified to any Scripted SWEP. + +-- Stencil Sights. +SWEP.StencilSight = nil -- Enables stencil sight drawing +SWEP.StencilSight_MinPercent = nil -- Mimimum aim progress percentage to draw; 0.05 by default; set to 0 to always draw +SWEP.StencilSight_VElement = nil -- "scope_p90_fn_ring_sight_std" -- Name of VElement to draw sight on; must be active to work +SWEP.StencilSight_UseMask = nil -- Use the .mask value of VElement's table as stencil mask model, otherwise VElement is the stencil mask (model must be bonemergeable on the sights VElement!) + +-- StencilSight_ReticleType can accept bitwise OR of multiple sight types, such as: +-- SWEP.StencilSight_ReticleType = bit.bor(TFA.Enum.RETICLE_FLAT, TFA.Enum.RETICLE_MODEL) +-- Draw order is: TFA.Enum.RETICLE_MODEL, TFA.Enum.RETICLE_QUAD, TFA.Enum.RETICLE_FLAT + +-- Sight Type: Flat +-- SWEP.StencilSight_ReticleType = TFA.Enum.RETICLE_FLAT +-- SWEP.StencilSight_ReticleMaterial = "models/weapons/yurie_bcry2/scope_assault/reddot" -- Reticle material, accepts either string or Material() object; must be a square texture! +-- SWEP.StencilSight_ReticleSize = 256 -- Reticle size; scales with screen height using HL2 scale formula (size * (screen height / 480)) +-- SWEP.StencilSight_ScaleReticleByScreenHeight = nil -- Reticle size is affected by screen height; true by default +-- SWEP.StencilSight_ScaleReticleByProgress = nil -- Scale reticle with aim progress for smoother fade-in; true by default +-- SWEP.StencilSight_FollowRecoil = nil -- If enabled, reticle follows crosshair recoil instead of being locked in center; true by default; affected by crosshair settings +-- SWEP.StencilSight_ReticleTint = nil -- Reticle color; Color(255, 255, 255) by default +-- SWEP.StencilSight_ReticleTintBySightColor = nil -- Use the "Reticule Color" value from base settings instead of StencilSight_ReticleTint? false by default +-- SWEP.StencilSight_FadeReticleByProgress = nil -- Fade-in reticle alpha with aim progress; false by default +-- // Sight Type: Flat + +-- Sight Type: Model; requires .reticle value of ViewModelElement's table (model must be bonemergeable on the sights VElement!) +-- SWEP.StencilSight_ReticleType = TFA.Enum.RETICLE_MODEL +-- SWEP.StencilSight_FadeReticleByProgress = nil -- Fade-in reticle alpha with aim progress; false by default +-- SWEP.StencilSight_EnableQuad = nil -- Enables drawing Quad type reticles (example below) over model ones +-- // Sight Type: Model + +-- Sight Type: Quad (aka the old way from TFA INS2) +-- SWEP.StencilSight_ReticleType = TFA.Enum.RETICLE_QUAD +-- SWEP.StencilSight_ReticleMaterial = "models/weapons/yurie_eft/parts/scopes/scope_p90_fn_ring_sight_std_LOD0_marks" -- Reticle material, accepts either string or Material() object; must be a square texture! +-- SWEP.StencilSight_ReticleSize = 1 -- Reticle quad size +-- SWEP.StencilSight_ScaleReticleByProgress = nil -- Scale reticle with aim progress for smoother fade-in; true by default +-- SWEP.StencilSight_ReticleTint = nil -- Reticle color; Color(255, 255, 255) by default +-- SWEP.StencilSight_ReticleTintBySightColor = nil -- Use the "Reticule Color" value from base settings instead of StencilSight_ReticleTint? false by default +-- SWEP.StencilSight_FadeReticleByProgress = nil -- Fade-in reticle alpha with aim progress; false by default + +-- Quad Reticle Position Lookup + +-- Lookup Type: Attachment +-- SWEP.StencilSight_PositionType = TFA.Enum.SIGHTSPOS_ATTACH -- Enabled by default +-- SWEP.StencilSight_ReticleAttachment = "tag_reticle" -- Name or index of target $attachment + +-- Lookup Type: Bone +-- SWEP.StencilSight_PositionType = TFA.Enum.SIGHTSPOS_BONE +-- SWEP.StencilSight_ReticleBone = "tag_reticle" -- Name or index of target bone + +-- SWEP.StencilSight_ReticleOffsetPos = nil -- Vector(0, 0, 0) +-- SWEP.StencilSight_ReticleOffsetAng = nil -- Angle(0, 0, 0) +-- // Sight Type: Quad + +-- [[ BASH BASE PARAMETERS ]] -- +-- If you're using "tfa_bash_base" or something that's derived from it +SWEP.Secondary.CanBash = true -- set to false to disable bashing +SWEP.Secondary.BashDamage = 25 -- Melee bash damage +SWEP.Secondary.BashSound = "TFA.Bash" -- Soundscript name for bash swing sound +SWEP.Secondary.BashHitSound = "TFA.BashWall" -- Soundscript name for non-flesh hit sound +SWEP.Secondary.BashHitSound_Flesh = "TFA.BashFlesh" -- Soundscript name for flesh hit sound +SWEP.Secondary.BashLength = 54 -- Length of bash melee trace in units +SWEP.Secondary.BashDelay = 0.2 -- Delay (in seconds) from bash start to bash attack trace +SWEP.Secondary.BashDamageType = DMG_SLASH -- Damage type (DMG_ enum value) +SWEP.Secondary.BashEnd = nil -- Bash end time (in seconds), defaults to animation end if undefined +SWEP.Secondary.BashInterrupt = false -- Bash attack interrupts everything (reload, draw, whatever) + +-- [[MISC INFO FOR MODELERS]] -- +--[[ + +Utilized animations (for modelers): + +ACT_VM_DRAW - Draw +ACT_VM_DRAW_EMPTY - Draw empty +ACT_VM_DRAW_SILENCED - Draw silenced, overrides empty + +ACT_VM_IDLE - Idle +ACT_VM_IDLE_EMPTY - Idle empty, overwritten by silenced +ACT_VM_IDLE_SILENCED - Idle silenced + +ACT_VM_PRIMARYATTACK - Shoot +ACT_VM_PRIMARYATTACK_EMPTY - Shoot last chambered bullet +ACT_VM_PRIMARYATTACK_SILENCED - Shoot silenced, overrides empty +ACT_VM_PRIMARYATTACK_1 - Shoot ironsights, overriden by everything besides normal shooting +ACT_VM_DRYFIRE - Dryfire + +ACT_VM_RELOAD - Reload / Tactical Reload / Insert Shotgun Shell +ACT_SHOTGUN_RELOAD_START - Start shotgun reload, unless ACT_VM_RELOAD_EMPTY is there. +ACT_SHOTGUN_RELOAD_FINISH - End shotgun reload. +ACT_VM_RELOAD_EMPTY - Empty mag reload, chambers the new round. Works for shotguns too, where applicable. +ACT_VM_RELOAD_SILENCED - Silenced reload, overwrites all + +ACT_VM_FIREMODE - Firemode switch +ACT_VM_IFIREMODE - ADS firemode switch + +ACT_VM_FIDGET - Inspect/fidget animation (hold Reload key to play) +ACT_CROSSBOW_FIDGET_UNLOADED - Inspect empty +ACT_RPG_FIDGET_UNLOADED - Inspect silenced + +ACT_VM_HOLSTER - Holster +ACT_VM_HOLSTER_SILENCED - Holster empty, overwritten by silenced +ACT_VM_HOLSTER_SILENCED - Holster silenced + +ACT_VM_HITCENTER - Melee bash +ACT_VM_HITCENTER2 - Melee bash silenced (why would you use that) +ACT_VM_MISSCENTER - Melee bash empty +ACT_VM_MISSCENTER2 - Melee bash empty and silenced (...) + +-- For more verbose list check lua/tfa/documentation/tfa_anims_template.lua + +]] -- + +-- Define local BaseClass to be SWEP.Base table +-- Example usage (AND PROPER ONE!): +--[[ +function SWEP:Think2(...) -- We're overriding Think2 without touching the main think function, which is called from there anyway + BaseClass.Think2(self, ...) -- THE MOST IMPORTANT LINE! It calls the Think2 function of the parent class, which is the base ifself + + -- Your code here. +end +]] +-- Write any code involving `BaseClass` indexing (like above) STRICTLY below DEFINE_BASECLASS(SWEP.Base), otherwise it won't work! +-- You can do the same with ANY function defined in TFA Base itself, as long as you call BaseClass function +DEFINE_BASECLASS(SWEP.Base) diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_hooks_custom.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_hooks_custom.lua new file mode 100644 index 0000000..f057850 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_hooks_custom.lua @@ -0,0 +1,918 @@ +-- TFA Base custom hooks reference +-- To be used with https://wiki.facepunch.com/gmod/hook.Add in your own code +-- All returns are optional, you can use hooks just to listen for events happening +-- Where state is listed as Shared, hook has to be added both on server and client to avoid any issues! + + + +--------------------------- +-- -- +-- BASE INITIALIZATION -- +-- -- +--------------------------- + +GM:TFABase_PreEarlyInit() +-- Hook name: TFABase_PreEarlyInit +-- Description: Called after base enums has been loaded; preparing to load official modules +-- State: Shared + +GM:TFABase_EarlyInit() +-- Hook name: TFABase_EarlyInit +-- Description: Called after official base modules has been loaded +-- State: Shared + +GM:TFABase_PreInit() +-- Hook name: TFABase_PreInit +-- Description: Called before unofficial modules has been loaded +-- State: Shared + +GM:TFABase_Init() +-- Hook name: TFABase_Init +-- Description: Called after unofficial modules have been found and loaded +-- State: Shared + +GM:TFABase_PreFullInit() +-- Hook name: TFABase_PreFullInit +-- Description: Called before loading external files +-- State: Shared + +GM:TFABase_FullInit() +-- Hook name: TFABase_FullInit +-- Description: Called after external files have been loaded +-- State: Shared + +GM:TFABase_LateInit() +-- Hook name: TFABase_LateInit +-- Description: Called after (re)initializing the attachments +-- State: Shared + + + +---------------------------------- +-- -- +-- ATTACHMENTS INITIALIZATION -- +-- -- +---------------------------------- + +boolean GM:TFABase_ShouldLoadAttachment(string attachmentID, string path) +-- Hook name: TFABase_ShouldLoadAttachment +-- Description: Called to determine if attachment should be registered +-- State: Shared +-- Arguments: +-- 1. string attachmentID - Requested attachment ID +-- 2. string path - Attachment file path +-- Returns: +-- 1. boolean - Return false to prevent attachment from loading and registration +-- Example: +hook.Add("TFABase_ShouldLoadAttachment", "TFA_Hooks_Example", function(id, path) + if id and (id == "ins2_fg_gp25" or id == "ins2_fg_m203") then + return false -- block INS2 Shared Parts grenade launcher attachments from loading + end +end) + +GM:TFABase_PreBuildAttachment(string attachmentID, string path, table attTbl) +-- Hook name: TFABase_PreBuildAttachment +-- Description: Called before loading attachment file +-- State: Shared +-- Arguments: +-- 1. string attachmentID - Requested attachment ID +-- 2. string path - Attachment file path +-- 3. table attTbl - Empty attachment reference table (only containing assigned ID) + +GM:TFABase_BuildAttachment(string attachmentID, string path, table attTbl) +-- Hook name: TFABase_BuildAttachment +-- Description: Called after attachment file has been loaded and executed +-- State: Shared +-- Arguments: +-- 1. string attachmentID - Requested attachment ID +-- 2. string path - Attachment file path +-- 3. table attTbl - Populated attachment reference table + +GM:TFABase_RegisterAttachment(string attachmentID, table attTbl) +-- Hook name: TFABase_RegisterAttachment +-- Description: Called when attachment has been registered +-- State: Shared +-- Arguments: +-- 1. string attachmentID - Requested attachment ID +-- 2. table attTbl - Attachment reference table + +GM:TFAAttachmentsLoaded() +-- Hook name: TFAAttachmentsLoaded +-- Description: Called after all attachments has been loaded and registered +-- State: Shared + +GM:TFAAttachmentsInitialized() +-- Hook name: TFAAttachmentsInitialized +-- Description: Called after all attachments has been fully loaded and initialized +-- State: Shared + + + +------------------------- +-- -- +-- WEAPON STAT CACHE -- +-- -- +------------------------- + +any GM:TFA_GetStat(Weapon weapon, string stat, any value) +-- Hook name: TFA_GetStat +-- Description: Called when a cached stat value is requested from weapon, allowing to intercept and modify it. +-- State: Shared +-- Arguments: +-- - 1. Weapon weapon +-- - 2. string stat - Cached stat name +-- - 3. any value - Cached stat value that was received from weapon +-- Returns: +-- - 1. any - Return the modified stat to pass it to :GetStat call +-- Example #1: +hook.Add("TFA_GetStat", "TFA_Hooks_Example", function(weapon, stat, value) + if stat == "Primary.ClipSize" then -- We want to modify SWEP.Primary.ClipSize which is a cached stat + return value + 10 -- We add 10 to it's current (number) value + end +end) +-- Example #2: +hook.Add("TFA_GetStat", "TFA_Hooks_Example_2", function(weapon, stat, value) + if stat == "Primary.AmmoConsumption" or stat == "Secondary.AmmoConsumption" then + return 0 -- We tell the base that the gun does not consume any ammo - infinite ammo hook! + end +end) + +GM:TFA_ClearStatCache(Weapon weapon) +-- Hook name: TFA_ClearStatCache +-- Description: Called after weapon's stat cache has been cleared +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + + + +--------------------------------- +-- -- +-- DEPLOY AND INITIALIZATION -- +-- -- +--------------------------------- + +GM:TFA_SetupDataTables(Weapon weapon) +-- Hook name: TFA_SetupDataTables +-- Description: Called after WEAPON:SetupDataTables, allowing to add custom networked data values +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Example: +hook.Add("TFA_SetupDataTables", "TFA_Hooks_Example", function(weapon) + weapon:NetworkVarTFA("Bool", "TestVar") -- Adding a boolean variable with name TestVar with getter WEAPON:GetTestVar() and setter WEAPON:SetTestVar(true) + -- Syntax of WEAPON:NetworkVarTFA is quite identical to Entity:NetworkVar ( https://wiki.facepunch.com/gmod/Entity:NetworkVar ) + -- WEAPON:NetworkVarTFA(string type, string name) +end) + +GM:TFA_PreInitialize(Weapon weapon) +-- Hook name: TFA_PreInitialize +-- Description: Called from SWEP:Initialize, allowing to do things before weapon is initialized +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +GM:TFA_Initialize(Weapon weapon) +-- Hook name: TFA_Initialize +-- Description: Called after weapon has been initialized from SWEP:Initialize +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +GM:TFA_PreDeploy(Weapon weapon) +-- Hook name: TFA_PreDeploy +-- Description: Called from SWEP:Deploy before weapon has been deployed +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon + +boolean GM:TFA_Deploy(Weapon weapon) +-- Hook name: TFA_Deploy +-- Description: Called from SWEP:Deploy after weapon has been deployed. +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return a boolean to override SWEP:Deploy output (default behavior returns true) + + + +--------------------------- +-- -- +-- HOLSTER AND REMOVAL -- +-- -- +--------------------------- + +boolean GM:TFA_PreHolster(Weapon weapon, Entity target) +-- Hook name: TFA_PreHolster +-- Description: Called from SWEP:Holster, enabling to prevent switching from currently equipped weapon +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- 2. Entity target - The weapon that is being switched to +-- Returns: +-- 1. boolean - Return true to switch to target weapon instantly + +GM:TFA_Holster(Weapon weapon) +-- Hook name: TFA_Holster +-- Description: Called when weapon is finished holstering and ready to switch +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +GM:TFA_OnRemove(Weapon weapon) +-- Hook name: TFA_OnRemove +-- Description: Called from SWEP:OnRemove when weapon is being removed +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +GM:TFA_OnDrop(Weapon weapon) +-- Hook name: TFA_OnDrop +-- Description: Called from SWEP:OnDrop when weapon is dropped by the player +-- State: Server +-- Arguments: +-- 1. Weapon weapon + + + +------------------------------------- +-- -- +-- PRIMARY AND SECONDARY ATTACKS -- +-- -- +------------------------------------- + +boolean GM:TFA_PreCanPrimaryAttack(Weapon weapon) +-- Hook name: TFA_PreCanPrimaryAttack +-- Description: Called before weapon checks if it can shoot +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return to override return value of SWEP:CanPrimaryAttack +-- Example: +hook.Add("TFA_PreCanPrimaryAttack", "TFA_Hooks_Example", function(weapon) + if IsValid(weapon:GetOwner()) and weapon:GetOwner():IsPlayer() and weapon:GetOwner():Crouching() then + return false -- we are blocking shooting if player that holding the weapon is crouching + end +end) + +boolean GM:TFA_CanPrimaryAttack(Weapon weapon) +-- Hook name: TFA_CanPrimaryAttack +-- Description: Same as above but called after all checks were done (except jamming) +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return to override return value of SWEP:CanPrimaryAttack + +boolean GM:TFA_PrimaryAttack(Weapon weapon) +-- Hook name: TFA_PrimaryAttack +-- Description: Called before weapon shoots +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent weapon shooting + +GM:TFA_PostPrimaryAttack(Weapon weapon) +-- Hook name: TFA_PostPrimaryAttack +-- Description: Called after successful weapon attack +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon + +boolean GM:TFA_PreCanSecondaryAttack(Weapon weapon) +-- Hook name: TFA_PreCanSecondaryAttack +-- Description: Called before weapon checks the right-click attack in SWEP:CanSecondaryAttack overrides. +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return to override return value of SWEP:CanSecondaryAttack + +boolean GM:TFA_CanSecondaryAttack(Weapon weapon) +-- Hook name: TFA_CanSecondaryAttack +-- Description: Checks if weapon is allowed to use right-click attack. DOES NOT PREVENT AIMING DOWN THE SIGHTS! +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return to override return value of SWEP:CanSecondaryAttack + +boolean GM:TFA_SecondaryAttack(Weapon weapon) +-- Hook name: TFA_SecondaryAttack +-- Description: Called when weapon is attempting to attack with right click (AltAttack/melee bash) +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent secondary attack + +GM:TFA_PostSecondaryAttack(Weapon weapon) +-- Hook name: TFA_PostSecondaryAttack +-- Description: Called after successful right-click attack +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon + + + +-------------------------- +-- -- +-- BULLET PENETRATION -- +-- -- +-------------------------- + +boolean GM:TFA_Bullet_Penetrate(Weapon weapon, Entity attacker, TraceResult traceres, CTakeDamageInfo dmginfo, table penetrated, Vector previousStartPos) +-- Hook name: TFA_Bullet_Penetrate +-- Description: Called before bullet is allowed to penetrate next surface +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. Entity attacker - The entity (player or NPC) who used the weapon +-- 3. TraceResult traceres - Bullet trace result +-- 4. CTakeDamageInfo dmginfo - Bullet callback damage info structure +-- 5. table penetrated - Table of entities bullet has gone through already +-- 6. Vector previousStartPos - Either previous penetration hit position or nil +-- Returns: +-- 1. boolean - Return false to block penetration + +GM:TFA_BulletPenetration(Bullet bullet, Entity attacker, TraceResult traceres, CTakeDamageInfo dmginfo) +-- Hook name: TFA_BulletPenetration +-- Description: Called when bullet hits/penetrates surface +-- State: Shared +-- Arguments: +-- 1. Bullet bullet - The bullet (to access weapon which fired the bullet use bullet.Wep) +-- 2. Entity attacker - The entity (player or NPC) who used the weapon +-- 3. TraceResult traceres - Bullet trace result +-- 4. CTakeDamageInfo dmginfo - Bullet callback damage info structure + + + +----------------- +-- -- +-- RELOADING -- +-- -- +----------------- + +boolean GM:TFA_PreReload(Weapon weapon, boolean released) +-- Hook name: TFA_PreReload +-- Description: Called when reload key is pressed/released (before any checks done) +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- 2. boolean released - If legacy reloads are enabled, reload happens when reload key is pressed, otherwise when it's released (allowing for stuff like inspect) +-- Returns: +-- 1. boolean - Return true to prevent weapon from reloading + +boolean GM:TFA_Reload(Weapon weapon) +-- Hook name: TFA_Reload +-- Description: Called when weapon is attempting to enter reload status +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent weapon from reloading + +GM:TFA_PostReload(Weapon weapon) +-- Hook name: TFA_PostReload +-- Description: Called after reload status checks +-- State: Shared, Predicted (not called in SP clientside) +-- Arguments: +-- 1. Weapon weapon + +boolean GM:TFA_LoadShell(Weapon weapon) +-- Hook name: TFA_LoadShell +-- Description: Called when shotgun reload type weapons attempt to enter looped reload status and play animation +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent default logic + +boolean GM:TFA_Pump(Weapon weapon) +-- Hook name: TFA_Pump +-- Description: Called when weapon is attempting to play pump/bolt animation +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent default logic + +boolean GM:TFA_CompleteReload(Weapon weapon) +-- Hook name: TFA_CompleteReload +-- Description: Called when weapon is exiting reloading status and trying to take ammo (and clear jamming status) +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent default logic +-- Example: +hook.Add("TFA_CompleteReload", "TFA_Hooks_Example", function(weapon) + weapon:SetClip1(weapon:GetPrimaryClipSizeForReload(true)) -- We set weapon's primary clip to max clipsize without taking ammo from the player + weapon:SetJammed(false) -- Force clear jammed status (since default logic does that) + + return true -- Suppressing default logic +end) + +boolean GM:TFA_CheckAmmo(Weapon weapon) +-- Hook name: TFA_CheckAmmo +-- Description: Called when player is holding Reload key to play inspect animation (called even if player has bound inspection keybind) +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent default logic + + + +------------------- +-- -- +-- ATTACHMENTS -- +-- -- +------------------- + +GM:TFA_PreInitAttachments(Weapon weapon) +-- Hook name: TFA_PreInitAttachments +-- Description: Called before attachments are initialized +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +GM:TFA_PostInitAttachments(Weapon weapon) +-- Hook name: TFA_PostInitAttachments +-- Description: Called after attachments sorting and initial setup but before cleanup and attachment cache build +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +GM:TFA_FinalInitAttachments(Weapon weapon) +-- Hook name: TFA_FinalInitAttachments +-- Description: Called after full attachments setup/initialization +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +boolean GM:TFA_PreCanAttach(Weapon weapon, string attachmentID) +-- Hook name: TFA_PreCanAttach +-- Description: Called before weapon checks to determine if attachment can be attached +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. string attachmentID - The ID of the attachment +-- Returns: +-- 1. boolean - Return a boolean to prevent checks and override + +boolean GM:TFA_CanAttach(Weapon weapon, string attachmentID) +-- Hook name: TFA_CanAttach +-- Description: Called after default checks to determine if attachment can be attached +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. string attachmentID - The ID of the attachment +-- Returns: +-- 1. boolean - Return a boolean to override the return value + +GM:TFA_Attachment_Attached(Weapon weapon, string attachmentID, table attTable, number category, number index, boolean forced) +-- Hook name: TFA_Attachment_Attached +-- Description: Called after attachment has been attached to the weapon +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. string attachmentID - The ID of the attachment +-- 3. table attTable - The ATTACHMENT reference table of the attachment +-- 4. number category - The category of the attachment in SWEP.Attachments +-- 5. number index - The index of the attachment in the category's attachments table +-- 6. boolean forced - If attachment was applied forcefully (bypassing any checks) + +boolean GM:TFA_PreCanDetach(Weapon weapon, string attachmentID) +-- Hook name: TFA_PreCanDetach +-- Description: Equivalent of TFA_PreCanAttach but for detaching +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. string attachmentID - The ID of the attachment +-- Returns: +-- 1. boolean - Return a boolean to prevent checks and override + +boolean GM:TFA_CanDetach(Weapon weapon, string attachmentID) +-- Hook name: TFA_CanDetach +-- Description: Equivalent of TFA_CanAttach but for detaching +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. string attachmentID - The ID of the attachment +-- Returns: +-- 1. boolean - Return a boolean to override the return value + +GM:TFA_Attachment_Detached(Weapon weapon, string attachmentID, table attTable, number category, number index, boolean forced) +-- Hook name: TFA_Attachment_Detached +-- Description: Equivalent of TFA_Attachment_Attached but for detaching +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. string attachmentID - The ID of the attachment that was detached +-- 3. table attTable - The ATTACHMENT reference table of the attachment +-- 4. number category - The category of the attachment in SWEP.Attachments +-- 5. number index - The index of the attachment in the category's attachments table that's going to be selected +-- 6. boolean forced - If attachment was applied forcefully (bypassing any checks) + + + +----------- +-- -- +-- FOV -- +-- -- +----------- + +number GM:TFA_PreTranslateFOV(Weapon weapon, number fov) +-- Hook name: TFA_PreTranslateFOV +-- Description: Called before weapon modifies player's FOV +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. number fov - Reference FOV received by weapon +-- Returns: +-- 1. number - Return a number to override FOV and prevent default logic + +number GM:TFA_TranslateFOV(Weapon weapon, number fov) +-- Hook name: TFA_TranslateFOV +-- Description: Called after weapon calculated modified FOV +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. number fov - Modified FOV value returned by weapon +-- Returns: +-- 1. number - Return to override modified FOV + + + +------------------ +-- -- +-- ANIMATIONS -- +-- -- +------------------ + +number GM:TFA_AnimationRate(Weapon weapon, number sequence, number rate) +-- Hook name: TFA_AnimationRate +-- Description: Called +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- 2. number sequence - Index of sequence from viewmodel animations +-- 3. number rate - Playback speed multiplier calculated by weapon +-- Returns: +-- 1. number - Return modified playback speed multiplier to override + + + +--------------- +-- -- +-- EFFECTS -- +-- -- +--------------- + +any GM:TFA_MakeShell(Weapon weapon) +-- Hook name: TFA_MakeShell +-- Description: Called when weapon is trying to emit a shell casing effect +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. any - Return anything to cancel + +any GM:TFA_EjectionSmoke(Weapon weapon) +-- Hook name: TFA_EjectionSmoke +-- Description: Called when weapon is trying to emit smoke from shell ejection port +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. any - Return anything to cancel + +any GM:TFA_MuzzleFlash(Weapon weapon) +-- Hook name: TFA_MuzzleFlash +-- Description: Called when weapon is trying to emit muzzle flash effect +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. any - Return anything to cancel + +any GM:TFA_MuzzleSmoke(Weapon weapon) +-- Hook name: TFA_MuzzleSmoke +-- Description: Called when weapon is trying to emit smoke from muzzle +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. any - Return anything to cancel + + + +------------------ +-- -- +-- IRONSIGHTS -- +-- -- +------------------ + +any GM:TFA_IronSightSounds(Weapon weapon) +-- Hook name: TFA_IronSightSounds +-- Description: Called when weapon tries to play ironsights enter/exit sound +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. any - Return anything to cancel + + + +----------------------- +-- -- +-- MELEE / BASHING -- +-- -- +----------------------- + +boolean GM:TFA_CanBash(Weapon weapon) +-- Hook name: TFA_CanBash +-- Description: Called when player is attempting to use melee bash attack (after initial checks) +-- State: Shared +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return false to disallow melee attack + +GM:TFA_Bash(Weapon weapon) +-- Hook name: TFA_Bash +-- Description: Called when weapon is about to perform a melee attack +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +GM:TFA_PostBash(Weapon weapon) +-- Hook name: TFA_PostBash +-- Description: Called after weapon has entered melee attack status +-- State: Shared +-- Arguments: +-- 1. Weapon weapon + +boolean GM:TFA_MeleeCanBlockDamage(Weapon weapon, Player player, CTakeDamageInfo dmginfo, boolean canblock) +-- Hook name: TFA_MeleeCanBlockDamage +-- Description: Callen when melee weapon is asked to block the incoming damage +-- State: Shared (only for bullet damage), Server (for any other type) +-- Arguments: +-- 1. Weapon weapon +-- 2. Player player - The player that is receiving the damage that's about to be blocked +-- 3. CTakeDamageInfo dmginfo - Incoming damage +-- 4. boolean canblock - Original decision of weapon if it's allowed to block or not +-- Returns: +-- 1. boolean - Return to override the decision + + + +---------------------------- +-- -- +-- HUD / USER INTERFACE -- +-- -- +---------------------------- + +boolean GM:TFA_DrawCrosshair(Weapon weapon, number x, number y) +-- Hook name: TFA_DrawCrosshair +-- Description: Called from SWEP:DoDrawCrosshair when weapon is about to draw crosshair +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. number x +-- 3. number y +-- Returns: +-- 1. boolean - Return false to draw only engine crosshair, true to block both + +boolean, number, number, number GM:TFA_DrawHUDAmmo(Weapon weapon, number x, number y, number alpha) +-- Hook name: TFA_DrawHUDAmmo +-- Description: Called before drawing holographic ammo indicator on screen +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. number x - left position +-- 3. number y - top position +-- 4. number alpha +-- Returns: +-- 1. boolean - Return false to prevent drawing (next returns ignored), true to modify following values: +-- 2. number - modified X position +-- 3. number - modified Y position +-- 4. number - modified alpha + +boolean GM:TFA_DrawScopeOverlay(Weapon weapon) +-- Hook name: TFA_DrawScopeOverlay +-- Description: Called before drawing 2D scope overlay +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return true to prevent drawing the overlay + +table GM:TFA_PopulateKeyBindHints(Weapon weapon, table rawKeysTable) +-- Hook name: TFA_PopulateKeyBindHints +-- Description: Allows to populate keybinds table prior to drawing (called from SWEP:PopulateKeyBindHints() function) +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. table rawKeysTable - Raw keybindings table +-- Returns: +-- 1. table rawKeysTableResult - Return the table to stop further processing +-- Example: +hook.Add("TFA_PopulateKeyBindHints", "TFA_Hooks_Example", function(wep, keys) + -- self:GetKeyBind({"+use", "+reload"}, "firemode") + -- First argument is a table of commands + -- Second argument is an ID of keybind registered through TFA.RegisterKeyBind, replaces all keys from first argument if key is bound (optional) + + table.insert(keys, { + label = "Open Spawnmenu", -- it is recommended to use a localized string with language.GetPhrase + keys = {wep:GetKeyBind({"+menu"})} -- can have multiple keys; GetKeyBind args: first is table of commands (they will be chained with +), the second (optional) is keybind identifier added with TFA.RegisterKeyBind and will be displayed instead of first + }) -- this will add "[Q] - Open Spawnmenu" at the end of the keys table (before the TAB one, it always comes the last) + + table.insert(keys, 1, { + label = "Sprint Forward", + keys = {wep:GetKeyBind({"+speed", "+forward"})} + }) -- this will add "[SHIFT + W] - Sprint Forward" at the start of the keys table + + -- table.insert(keys, { + -- label = language.GetPhrase("tfa.hint.keys.safety"), + -- keys = {self:GetKeyBind({"+speed"}), self:GetKeyBind({"+use", "+reload"}, "firemode")} + -- }) -- example from the base, will display SHIFT+E+R or SHIFT+ +end) + +boolean GM:TFA_PreDrawKeyBindHint(Weapon weapon, number x, number y, number alpha, table rawKeysTable, table keyStrings) +-- Hook name: TFA_PreDrawKeyBindHint +-- Description: Called before keybinds hint is drawn (only if alpha > 0) +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. number x +-- 3. number y +-- 4. number alpha +-- 5. table rawKeysTable +-- 6. table keyStrings - Processed keybinds in format "[KEY(S)] - Label" +-- Returns: +-- 1. boolean - Return true to prevent drawing + +GM:TFA_PostDrawKeyBindHint(Weapon weapon, number x, number y, number alpha, table rawKeysTable, table keyStrings) +-- Hook name: TFA_PostDrawKeyBindHint +-- Description: Called after keybinds hint is drawn +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. number x +-- 3. number y +-- 4. number alpha +-- 5. table rawKeysTable +-- 6. table keyStrings + +boolean GM:TFA_ShouldDrawStencilSight(Weapon weapon) +-- Hook name: TFA_ShouldDrawStencilSight +-- Description: Called when weapon is trying to draw stencil sight reticle +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return false to prevent reticle from drawing + +GM:TFA_FontsLoaded() +-- Hook name: TFA_FontsLoaded +-- Description: Called after base (re)registers all it's fonts +-- State: Client + + + +--------------------------------------- +-- -- +-- CUSTOMIZATION / INSPECTION MENU -- +-- -- +--------------------------------------- + +boolean GM:TFA_InspectVGUI_Start(Weapon weapon) +-- Hook name: TFA_InspectVGUI_Start +-- Description: Called before main customization screen panel is generated +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- Returns: +-- 1. boolean - Return false to prevent creation + +GM:TFA_InspectVGUI_Finish(Weapon weapon, Panel mainpanel, Panel contentpanel) +-- Hook name: TFA_InspectVGUI_Finish +-- Description: Called after every panel has been initialized +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel mainpanel - The parent inspection screen panel +-- 3. Panel contentpanel - The padded panel that contains all elements + +boolean GM:TFA_InspectVGUI_InfoStart(Weapon weapon, Panel contentpanel) +-- Hook name: TFA_InspectVGUI_InfoStart +-- Description: Called before populating screen with weapon info elements +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- Returns: +-- 1. boolean - Return false to prevent creation + +GM:TFA_InspectVGUI_InfoFinish(Weapon weapon, Panel contentpanel, Panel infopanel) +-- Hook name: TFA_InspectVGUI_InfoFinish +-- Description: Called when weapon info elements are added +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- 3. Panel infopanel - The container panel for all displayed info elements + +boolean GM:TFA_InspectVGUI_StatsStart(Weapon weapon, Panel contentpanel) +-- Hook name: TFA_InspectVGUI_StatsStart +-- Description: Called before adding weapon stats panel +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- Returns: +-- 1. boolean - Return false to prevent creation + +GM:TFA_InspectVGUI_StatsFinish(Weapon weapon, Panel contentpanel, Panel statspanel) +-- Hook name: TFA_InspectVGUI_StatsFinish +-- Description: Called when weapon stats are added +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- 3. Panel statspanel - The container panel for all displayed stats + +boolean GM:TFA_InspectVGUI_AttachmentsStart(Weapon weapon, Panel contentpanel) +-- Hook name: TFA_InspectVGUI_AttachmentsStart +-- Description: Called before adding attachments selector panel +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- Returns: +-- 1. boolean - Return false to prevent creation (also blocks damage falloff graph) + +GM:TFA_InspectVGUI_AttachmentsFinish(Weapon weapon, Panel contentpanel, Panel attachmentspanel) +-- Hook name: TFA_InspectVGUI_AttachmentsFinish +-- Description: Called after attachments selector panel is generated +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- 3. Panel attachmentspanel - The resulting attachments selector panel + +boolean GM:TFA_InspectVGUI_FalloffStart(Weapon weapon, Panel contentpanel) +-- Hook name: TFA_InspectVGUI_FalloffStart +-- Description: Called before damage falloff graph is initialized +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- Returns: +-- 1. boolean - Return false to prevent creation + +GM:TFA_InspectVGUI_FalloffFinish(Weapon weapon, Panel contentpanel, Panel falloffpanel) +-- Hook name: TFA_InspectVGUI_FalloffFinish +-- Description: Called after damage falloff graph panel is initialized +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Panel contentpanel - The padded panel that contains all elements +-- 3. Panel falloffpanel - The resulting damage falloff graph panel + + + +---------------------- +-- -- +-- DEPTH OF FIELD -- +-- -- +---------------------- + +boolean GM:TFA_AllowDoFDraw(Weapon weapon, Player player, Entity viewmodel) +-- Hook name: TFA_AllowDoFDraw +-- Description: Called before drawing aim DoF effect on viewmodel +-- State: Client +-- Arguments: +-- 1. Weapon weapon +-- 2. Player player - The player that is currently holding the weapon +-- 3. Entity viewmodel - Weapon's viewmodel +-- Returns: +-- 1. boolean - Return false to prevent effect from drawing + +number GM:TFA_GetDoFMuzzleAttachmentID(Weapon weapon, Player player, Entity viewmodel) +-- Hook name: TFA_GetDoFMuzzleAttachmentID +-- Description: Called when deciding reference attachment for DoF effect focus +-- State: Client. +-- Arguments: +-- 1. Weapon weapon +-- 2. Player player - The player that is currently holding the weapon +-- 3. Entity viewmodel - Weapon's viewmodel +-- Returns: +-- 1. number - Return viewmodel's attachment point index to override + + diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_legacy_template.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_legacy_template.lua new file mode 100644 index 0000000..080049a --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_legacy_template.lua @@ -0,0 +1,703 @@ + +-- TFA Base Template by TFA Base Devs + +-- To the extent possible under law, the person who associated CC0 with +-- TFA Base Template has waived all copyright and related or neighboring rights +-- to TFA Base Template. + +-- You should have received a copy of the CC0 legalcode along with this +-- work. If not, see . + + + +-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -- +-- ! ! -- +-- ! WARNING! This template is outdated, not supported anymore ! -- +-- ! and is only kept in for reference/comparison reasons. ! -- +-- ! ! -- +-- ! Please use the updated template ! -- +-- ! located at lua/weapons/tfa_base_template/shared.lua ! -- +-- ! for future weapon development purposes. ! -- +-- ! ! -- +-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -- + + + +SWEP.Base = "tfa_gun_base" +SWEP.Category = "TFA Template" -- The category. Please, just choose something generic or something I've already done if you plan on only doing like one swep.. +SWEP.Manufacturer = nil -- Gun Manufactrer (e.g. Hoeckler and Koch ) +SWEP.Author = "" -- Author Tooltip +SWEP.Contact = "" -- Contact Info Tooltip +SWEP.Purpose = "" -- Purpose Tooltip +SWEP.Instructions = "" -- Instructions Tooltip +SWEP.Spawnable = false -- Can you, as a normal user, spawn this? +SWEP.AdminSpawnable = false -- Can an adminstrator spawn this? Does not tie into your admin mod necessarily, unless its coded to allow for GMod's default ranks somewhere in its code. Evolve and ULX should work, but try to use weapon restriction rather than these. +SWEP.DrawCrosshair = true -- Draw the crosshair? +SWEP.DrawCrosshairIS = false -- Draw the crosshair in ironsights? +SWEP.PrintName = "TFA Base Template" -- Weapon name (Shown on HUD) +SWEP.Slot = 2 -- Slot in the weapon selection menu. Subtract 1, as this starts at 0. +SWEP.SlotPos = 73 -- Position in the slot +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.Weight = 30 -- This controls how "good" the weapon is for autopickup. + +-- [[WEAPON HANDLING]] -- +SWEP.Primary.Sound = Sound("") -- This is the sound of the weapon, when you shoot. +SWEP.Primary.SilencedSound = nil -- This is the sound of the weapon, when silenced. +SWEP.Primary.PenetrationMultiplier = 1 -- Change the amount of something this gun can penetrate through +-- the LESSER this value is, the BETTER is penetration +-- this is basically multiplier for next values +-- you don't need to uncomment these if you are not going to modify them! +--[[ +SWEP.PenetrationMaterials = { + [MAT_DEFAULT] = 1, + [MAT_VENT] = 0.4, --Since most is aluminum and stuff + [MAT_METAL] = 0.6, --Since most is aluminum and stuff + [MAT_WOOD] = 0.2, + [MAT_PLASTIC] = 0.23, + [MAT_FLESH] = 0.48, + [MAT_CONCRETE] = 0.87, + [MAT_GLASS] = 0.16, + [MAT_SAND] = 1, + [MAT_SLOSH] = 1, + [MAT_DIRT] = 0.95, --This is plaster, not dirt, in most cases. + [MAT_FOLIAGE] = 0.9 +} +]] + +SWEP.Primary.Damage = 0.01 -- Damage, in standard damage points. +SWEP.Primary.DamageTypeHandled = true -- true will handle damagetype in base +SWEP.Primary.DamageType = nil -- See DMG enum. This might be DMG_SHOCK, DMG_BURN, DMG_BULLET, etc. Leave nil to autodetect. DMG_AIRBOAT opens doors. +SWEP.Primary.Force = nil -- Force value, leave nil to autocalc +SWEP.Primary.Knockback = nil -- Autodetected if nil; this is the velocity kickback +SWEP.Primary.HullSize = 0 -- Big bullets, increase this value. They increase the hull size of the hitscan bullet. +SWEP.Primary.NumShots = 1 -- The number of shots the weapon fires. SWEP.Shotgun is NOT required for this to be >1. +SWEP.Primary.Automatic = true -- Automatic/Semi Auto +SWEP.Primary.RPM = 600 -- This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Semi = nil -- RPM for semi-automatic or burst fire. This is in Rounds Per Minute / RPM +SWEP.Primary.RPM_Burst = nil -- RPM for burst fire, overrides semi. This is in Rounds Per Minute / RPM +SWEP.Primary.DryFireDelay = nil -- How long you have to wait after firing your last shot before a dryfire animation can play. Leave nil for full empty attack length. Can also use SWEP.StatusLength[ ACT_VM_BLABLA ] +SWEP.Primary.BurstDelay = nil -- Delay between bursts, leave nil to autocalculate + +SWEP.Primary.LoopSound = nil -- Looped fire sound, unsilenced +SWEP.Primary.LoopSoundSilenced = nil -- Looped fire sound, silenced +SWEP.Primary.LoopSoundTail = nil -- Loop end/tail sound, unsilenced +SWEP.Primary.LoopSoundTailSilenced = nil -- Loop end/tail sound, silenced +SWEP.Primary.LoopSoundAutoOnly = false -- Play loop sound for full-auto only? Fallbacks to Primary.Sound for semi/burst if true + +-- WORLD/THIRDPERSON/NPC FIRING SOUNDS! Fallbacks to first person sound if not defined. + +SWEP.Primary.Sound_World = nil -- This is the sound of the weapon, when you shoot. +SWEP.Primary.SilencedSound_World = nil -- This is the sound of the weapon, when silenced. + +SWEP.Primary.LoopSound_World = nil -- Looped fire sound, unsilenced +SWEP.Primary.LoopSoundSilenced_World = nil -- Looped fire sound, silenced +SWEP.Primary.LoopSoundTail_World = nil -- Loop end/tail sound, unsilenced +SWEP.Primary.LoopSoundTailSilenced_World = nil -- Loop end/tail sound, silenced + +SWEP.ViewModelPunchPitchMultiplier = nil -- Default value is 0.5 +SWEP.ViewModelPunchPitchMultiplier_IronSights = nil -- Default value is 0.09 + +SWEP.ViewModelPunch_MaxVertialOffset = nil -- Default value is 3 +SWEP.ViewModelPunch_MaxVertialOffset_IronSights = nil -- Default value is 1.95 +SWEP.ViewModelPunch_VertialMultiplier = nil -- Default value is 1 +SWEP.ViewModelPunch_VertialMultiplier_IronSights = nil -- Default value is 0.25 + +SWEP.ViewModelPunchYawMultiplier = nil -- Default value is 0.6 +SWEP.ViewModelPunchYawMultiplier_IronSights = nil -- Default value is 0.25 + +SWEP.CanJam = true -- whenever weapon cam jam +SWEP.JamChance = 0.04 -- the (maximal) chance the weapon will jam. Newly spawned weapon will never jam on first shot for example. +-- Default value is 0.04 (4%) +-- Maxmial value is 1, means weapon will always jam when factor become 100 +-- Also remember that there is a minimal factor before weapon can jam +-- This number is not treated "as-is" but as basic value that needs to be concluded as chance +-- You don't really need to cry over it and trying to balance it, TFA Base will do the job for you +-- (TFA Base will calculate the best value between 0 and JamChance based on current JamFactor of the weapon) +SWEP.JamFactor = 0.06 -- How to increase jam factor after each shot. +-- When factor reach 100 it will mean that on each shot there will be SWEP.Primary.JamChance chance to jam +-- When factor reach 50 it will mean that on each shot there will be SWEP.Primary.JamChance / 2 chance to jam +-- and so on +-- Default value is 0.06, means weapon will jam with SWEP.Primary.JamChance chance right after 1666 shots + +-- These settings are good for Assault Rifles, however, not good for anything else. +-- Suggested stats: + +--[[ +-- Pistols +SWEP.JamChance = 0.20 +SWEP.JamFactor = 0.14 +]] + +--[[ +-- Revolvers +SWEP.JamChance = 0.17 +SWEP.JamFactor = 0.50 +]] + +--[[ +-- Miniguns +SWEP.JamChance = 0.03 +SWEP.JamFactor = 0.01 +]] + +--[[ +-- Submachine gun +SWEP.JamChance = 0.04 +SWEP.JamFactor = 0.09 +]] + +--[[ +-- Auto shotguns +SWEP.JamChance = 0.15 +SWEP.JamFactor = 0.2 +]] + +--[[ +-- Pump-action shotguns +SWEP.JamChance = 0.25 +SWEP.JamFactor = 0.3 +]] + +--[[ +-- Sniper rifle +SWEP.JamChance = 0.17 +SWEP.JamFactor = 0.35 +]] + +SWEP.FiresUnderwater = false +-- Miscelaneous Sounds +SWEP.IronInSound = nil -- Sound to play when ironsighting in? nil for default +SWEP.IronOutSound = nil -- Sound to play when ironsighting out? nil for default +-- Silencing +SWEP.CanBeSilenced = false -- Can we silence? Requires animations. +SWEP.Silenced = false -- Silenced by default? +-- Selective Fire Stuff +SWEP.SelectiveFire = false -- Allow selecting your firemode? +SWEP.DisableBurstFire = false -- Only auto/single? +SWEP.OnlyBurstFire = false -- No auto, only burst/single? +SWEP.BurstFireCount = nil -- Burst fire count override (autocalculated by the clip size if nil) +SWEP.DefaultFireMode = "" -- Default to auto or whatev +SWEP.FireModeName = nil -- Change to a text value to override it +SWEP.FireSoundAffectedByClipSize = true -- Whenever adjuct pitch (and proably other properties) of fire sound based on current clip / maxclip +-- This is always false when either: +-- Weapon has no primary clip +-- Weapon's clip is smaller than 4 rounds +-- Weapon is a shotgun +-- Ammo Related +SWEP.Primary.ClipSize = 0 -- This is the size of a clip +SWEP.Primary.DefaultClip = 0 -- This is the number of bullets the gun gives you, counting a clip as defined directly above. +SWEP.Primary.Ammo = "none" -- What kind of ammo. Options, besides custom, include pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, and AirboatGun. +SWEP.Primary.AmmoConsumption = 1 -- Ammo consumed per shot +-- Pistol, buckshot, and slam like to ricochet. Use AirboatGun for a light metal peircing shotgun pellets +SWEP.DisableChambering = false -- Disable round-in-the-chamber + +-- Recoil Related +SWEP.Primary.KickUp = 0 -- This is the maximum upwards recoil (rise) +SWEP.Primary.KickDown = 0 -- This is the maximum downwards recoil (skeet) +SWEP.Primary.KickHorizontal = 0 -- This is the maximum sideways recoil (no real term) +SWEP.Primary.StaticRecoilFactor = 0.5 -- Amount of recoil to directly apply to EyeAngles. Enter what fraction or percentage (in decimal form) you want. This is also affected by a convar that defaults to 0.5. + +-- Firing Cone Related +SWEP.Primary.Spread = .01 -- This is hip-fire acuracy. Less is more (1 is horribly awful, .0001 is close to perfect) +SWEP.Primary.IronAccuracy = .005 -- Ironsight accuracy, should be the same for shotguns + +-- Unless you can do this manually, autodetect it. If you decide to manually do these, uncomment this block and remove this line. +SWEP.Primary.SpreadMultiplierMax = nil -- How far the spread can expand when you shoot. Example val: 2.5 +SWEP.Primary.SpreadIncrement = nil -- What percentage of the modifier is added on, per shot. Example val: 1/3.5 +SWEP.Primary.SpreadRecovery = nil -- How much the spread recovers, per second. Example val: 3 + +-- Range Related + +-- DEPRECATED. Automatically converted to RangeFalloffLUT table +SWEP.Primary.Range = -1 -- The distance the bullet can travel in source units. Set to -1 to autodetect based on damage/rpm. +SWEP.Primary.RangeFalloff = -1 -- The percentage of the range the bullet damage starts to fall off at. Set to 0.8, for example, to start falling off after 80% of the range. + +-- Use these if you don't want/understand how to use LUT below. These values are automatically converted to RangeFalloffLUT table +SWEP.Primary.FalloffMetricBased = false -- Set to true if you set up values below +SWEP.Primary.FalloffByMeter = nil -- How much damage points will bullet loose when travel +SWEP.Primary.MinRangeStartFalloff = nil -- How long will bullet travel in Meters before starting to lose damage? +SWEP.Primary.MaxFalloff = nil -- Maximal amount of damage to be lost + +-- Use this for full control over damage dropoff. +--[[ +SWEP.Primary.RangeFalloffLUT = { + bezier = true, -- Whenever to use Bezier or not to interpolate points? + -- you probably always want it to be set to true + range_func = "quintic", -- function to spline range + -- "linear" for linear splining. + -- Possible values are "quintic", "cubic", "cosine", "sinusine", "linear" or your own function + units = "meters", -- possible values are "inches", "inch", "hammer", "hu" (are all equal) + -- everything else is considered to be meters + lut = { -- providing zero point is not required + -- without zero point it is considered to be as {range = 0, damage = 1} + {range = 5, damage = 0.9}, + {range = 12, damage = 0.8}, + {range = 18, damage = 0.5}, + {range = 24, damage = 0.2}, + {range = 30, damage = 0.55}, + {range = 38, damage = 0.76}, + {range = 50, damage = 1}, + {range = 52, damage = 0.96}, + {range = 60, damage = 0.3}, + {range = 70, damage = 0.1}, + } +} +]] + +SWEP.DisplayFalloff = nil -- Defaults to true (false for melees) + +--[[ +SWEP.Primary.RecoilLUT_IronSightsMult = nil -- Defaults to 0.5 +-- controls how much effective LUT is when iron sighting +SWEP.Primary.RecoilLUT_AnglePunchMult = nil -- Defaults to 0.25 +-- controls how much effective LUT at pushing EyeAngles of shooter +SWEP.Primary.RecoilLUT_ViewPunchMult = nil -- Defaults to 1 +-- controls how much effective LUT at viewpunch + +SWEP.Primary.RecoilLUT = { + ["in"] = { + bezier = true, + func = "quintic", -- function to inerpolate progress when sampling points from table + -- Possible values are "quintic", "cubic", "cosine", "sinusine", "linear" or your own function + cooldown_speed = 1, -- how much to loose progress when we are at this stage + -- 1 means we lose entire progress in a second + increase = 0.1, -- how much to increase progress after shot + -- 0.1 means that this stage would be full after 10 shots + wait = 0.1, -- how much time do we wait in seconds after we stopped shooting + -- after this time, IN stage begin to cooldown until it reach zero + + -- table is always prepended with an Angle() + -- only Pitch and Yaw are utilized + -- sampled point is added to aimvector of player + -- when they shoot + points = { + Angle(-1, 0.4), + Angle(-4, -2), + Angle(-6, -4), + Angle(-10, -6), + } + }, + + ["loop"] = { + bezier = true, + func = "quintic", + -- this stage can not cooldown, so no cooldown_speed is defined + increase = 0.1, -- when LOOP stage reach 1, it is reset to 0 + wait = 0.1, -- how much time do we wait in seconds after we stopped shooting + -- after this time, stage switch to OUT + + -- table is NOT prepended with an Angle() + -- make sure it's starting point match the one from IN stage + -- last and first points are connected automatically + points = { + Angle(-10, -6), + Angle(-12, -0.4), + Angle(-8, 9), + Angle(-11, 12), + Angle(-13, 2), + Angle(-8, -4), + } + }, + + ["out"] = { + bezier = true, + func = "quintic", + -- this stage is different + -- it is only started after LOOP took place + -- shooting in this stage will actually roll back it's state + -- until it reach zero and switch back to LOOP + -- cooling down actually increase stage's progress + cooldown_speed = 1, + -- increase act as negative number to reach zero in this stage + increase = 0.2, + + -- after this stage reach 1, everything reset to IN and wait for next fire + -- table is always appended with an Angle() + + -- starting point is dynamic + -- and will always match current LOOP's one + points = { + Angle(-7, -2), + Angle(-4, -1), + Angle(-2, 0), + } + } +} +]] + +-- Penetration Related +SWEP.MaxPenetrationCounter = 4 -- The maximum number of ricochets. To prevent stack overflows. + +-- Misc +SWEP.IronRecoilMultiplier = 0.5 -- Multiply recoil by this factor when we're in ironsights. This is proportional, not inversely. +SWEP.CrouchAccuracyMultiplier = 0.5 -- Less is more. Accuracy * 0.5 = Twice as accurate, Accuracy * 0.1 = Ten times as accurate + +-- Movespeed +SWEP.MoveSpeed = 1 -- Multiply the player's movespeed by this. +SWEP.IronSightsMoveSpeed = 0.8 -- Multiply the player's movespeed by this when sighting. + +-- PROJECTILES +SWEP.Primary.Projectile = nil -- Entity to shoot +SWEP.Primary.ProjectileVelocity = 0 -- Entity to shoot's velocity +SWEP.Primary.ProjectileModel = nil -- Entity to shoot's model + +-- VIEWMODEL +SWEP.ViewModel = "models/your/path/here.mdl" -- Viewmodel path +SWEP.ViewModelFOV = 65 -- This controls how big the viewmodel looks. Less is more. +SWEP.ViewModelFlip = false -- Set this to true for CSS models, or false for everything else (with a righthanded viewmodel.) +SWEP.UseHands = false -- Use gmod c_arms system. +SWEP.VMPos = Vector(0, 0, 0) -- The viewmodel positional offset, constantly. Subtract this from any other modifications to viewmodel position. +SWEP.VMAng = Vector(0, 0, 0) -- The viewmodel angular offset, constantly. Subtract this from any other modifications to viewmodel angle. +SWEP.VMPos_Additive = true -- Set to false for an easier time using VMPos. If true, VMPos will act as a constant delta ON TOP OF ironsights, run, whateverelse +SWEP.CenteredPos = nil -- The viewmodel positional offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.CenteredAng = nil -- The viewmodel angular offset, used for centering. Leave nil to autodetect using ironsights. +SWEP.Bodygroups_V = nil -- { + -- [0] = 1, + -- [1] = 4, + -- [2] = etc. +-- } + +SWEP.AllowIronSightsDoF = true -- whenever allow DoF effect on viewmodel when zoomed in with iron sights + +SWEP.IronSightsReloadEnabled = nil -- Enable ADS reload animations support (requires animations to be enabled in SWEP.Animations) +SWEP.IronSightsReloadLock = true -- Lock ADS state when reloading + +-- WORLDMODEL +SWEP.WorldModel = "models/your/wmodel/path/here.mdl" -- Weapon world model path +SWEP.Bodygroups_W = nil -- { +-- [0] = 1, +-- [1] = 4, +-- [2] = etc. +-- } + +SWEP.HoldType = "" -- This is how others view you carrying the weapon. Options include: +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive +-- You're mostly going to use ar2, smg, shotgun or pistol. rpg and crossbow make for good sniper rifles + +SWEP.Offset = { + Pos = { + Up = 0, + Right = 0, + Forward = 0 + }, + Ang = { + Up = -1, + Right = -2, + Forward = 178 + }, + Scale = 1 +} -- Procedural world model animation, defaulted for CS:S purposes. + +SWEP.ThirdPersonReloadDisable = false -- Disable third person reload? True disables. + +-- SCOPES +SWEP.IronSightsSensitivity = 1 -- Useful for a RT scope. Change this to 0.25 for 25% sensitivity. This is if normal FOV compenstaion isn't your thing for whatever reason, so don't change it for normal scopes. +SWEP.BoltAction = false -- Unscope/sight after you shoot? +SWEP.Scoped = false -- Draw a scope overlay? +SWEP.ScopeOverlayThreshold = 0.875 -- Percentage you have to be sighted in to see the scope. +SWEP.BoltTimerOffset = 0.25 -- How long you stay sighted in after shooting, with a bolt action. +SWEP.ScopeScale = 0.5 -- Scale of the scope overlay +SWEP.ReticleScale = 0.7 -- Scale of the reticle overlay +-- GDCW Overlay Options. Only choose one. +SWEP.Secondary.UseACOG = false -- Overlay option +SWEP.Secondary.UseMilDot = false -- Overlay option +SWEP.Secondary.UseSVD = false -- Overlay option +SWEP.Secondary.UseParabolic = false -- Overlay option +SWEP.Secondary.UseElcan = false -- Overlay option +SWEP.Secondary.UseGreenDuplex = false -- Overlay option +if surface then + SWEP.Secondary.ScopeTable = nil --[[ + { + scopetex = surface.GetTextureID("scope/gdcw_closedsight"), + reticletex = surface.GetTextureID("scope/gdcw_acogchevron"), + dottex = surface.GetTextureID("scope/gdcw_acogcross") + } + ]] -- +end +-- [[SHOTGUN CODE]] -- +SWEP.Shotgun = false -- Enable shotgun style reloading. +SWEP.ShotgunEmptyAnim = false -- Enable emtpy reloads on shotguns? +SWEP.ShotgunEmptyAnim_Shell = true -- Enable insertion of a shell directly into the chamber on empty reload? +SWEP.ShotgunStartAnimShell = false -- shotgun start anim inserts shell +SWEP.ShellTime = .35 -- For shotguns, how long it takes to insert a shell. +-- [[SPRINTING]] -- +SWEP.RunSightsPos = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably +SWEP.RunSightsAng = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably +-- [[CROUCHING]] -- +-- Viewmodel offset when player is crouched +-- SWEP.CrouchPos = Vector(0, 0, 0) +-- SWEP.CrouchAng = Vector(0, 0, 0) +-- [[IRONSIGHTS]] -- +SWEP.data = {} +SWEP.data.ironsights = 1 -- Enable Ironsights +SWEP.Secondary.IronFOV = 70 -- How much you "zoom" in. Less is more! Don't have this be <= 0. A good value for ironsights is like 70. +-- SWEP.IronViewModelFOV = 65 -- Target viewmodel FOV when aiming down the sights. +SWEP.IronSightsPos = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably +SWEP.IronSightsAng = Vector(0, 0, 0) -- Change this, using SWEP Creation Kit preferably +-- [[INSPECTION]] -- +SWEP.InspectPos = nil -- Vector(0, 0, 0) -- Replace with a vector, in style of ironsights position, to be used for inspection +SWEP.InspectAng = nil -- Vector(0, 0, 0) -- Replace with a vector, in style of ironsights angle, to be used for inspection +-- [[VIEWMODEL BLOWBACK]] -- +SWEP.BlowbackEnabled = false -- Enable Blowback? +SWEP.BlowbackVector = Vector(0, -1, 0) -- Vector to move bone relative to bone orientation. +SWEP.BlowbackAngle = nil -- Angle(0, 0, 0) +SWEP.BlowbackCurrentRoot = 0 -- Amount of blowback currently, for root +SWEP.BlowbackCurrent = 0 -- Amount of blowback currently, for bones +SWEP.BlowbackBoneMods = nil -- Viewmodel bone mods via SWEP Creation Kit +SWEP.Blowback_Only_Iron = true -- Only do blowback on ironsights +SWEP.Blowback_PistolMode = false -- Do we recover from blowback when empty? +SWEP.Blowback_Shell_Enabled = true -- Shoot shells through blowback animations +SWEP.Blowback_Shell_Effect = "ShellEject" -- Which shell effect to use +SWEP.BlowbackAllowAnimation = nil -- Allow playing shoot animation with blowback? +-- [[VIEWMODEL PROCEDURAL ANIMATION]] -- +SWEP.DoProceduralReload = false -- Animate first person reload using lua? +SWEP.ProceduralReloadTime = 1 -- Procedural reload time? +-- [[HOLDTYPES]] -- +SWEP.IronSightHoldTypeOverride = "" -- This variable overrides the ironsights holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +SWEP.SprintHoldTypeOverride = "" -- This variable overrides the sprint holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +-- [[ANIMATION]] -- + +SWEP.StatusLengthOverride = {} -- Changes the status delay of a given animation; only used on reloads. Otherwise, use SequenceLengthOverride or one of the others +SWEP.SequenceLengthOverride = {} -- Changes both the status delay and the nextprimaryfire of a given animation +SWEP.SequenceTimeOverride = {} -- Like above but changes animation length to a target +SWEP.SequenceRateOverride = {} -- Like above but scales animation length rather than being absolute + +SWEP.ProceduralHolsterEnabled = nil +SWEP.ProceduralHolsterTime = 0.3 +SWEP.ProceduralHolsterPos = Vector(3, 0, -5) +SWEP.ProceduralHolsterAng = Vector(-40, -30, 10) + +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH -- TFA.Enum.IDLE_DISABLED = no idle, TFA.Enum.IDLE_LUA = lua idle, TFA.Enum.IDLE_ANI = mdl idle, TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA +SWEP.Idle_Blend = 0.25 -- Start an idle this far early into the end of a transition +SWEP.Idle_Smooth = 0.05 -- Start an idle this far early into the end of another animation +-- MDL Animations Below + +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_LUA -- LOCOMOTION_ANI = mdl, LOCOMOTION_HYBRID = ani + lua, LOCOMOTION_LUA = lua only +--[[ +SWEP.IronAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_To_Iron", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_To_Iron_Dry", + ["transition"] = true + }, -- Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_Iron", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_Iron_Dry" + }, -- Looping Animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Iron_To_Idle", -- Number for act, String/Number for sequence + ["value_empty"] = "Iron_To_Idle_Dry", + ["transition"] = true + }, -- Outward transition + ["shoot"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Fire_Iron", -- Number for act, String/Number for sequence + ["value_last"] = "Fire_Iron_Last", + ["value_empty"] = "Fire_Iron_Dry" + } -- What do you think +} +]] + +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_LUA -- LOCOMOTION_ANI = mdl, LOCOMOTION_HYBRID = ani + lua, LOCOMOTION_LUA = lua only +--[[ +SWEP.SprintAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_to_Sprint", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_to_Sprint_Empty", + ["transition"] = true + }, -- Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Sprint_", -- Number for act, String/Number for sequence + ["value_empty"] = "Sprint_Empty_", + ["is_idle"] = true + }, -- looping animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Sprint_to_Idle", -- Number for act, String/Number for sequence + ["value_empty"] = "Sprint_to_Idle_Empty", + ["transition"] = true + } -- Outward transition +} +]] + +SWEP.Walk_Mode = TFA.Enum.LOCOMOTION_LUA -- LOCOMOTION_ANI = mdl, LOCOMOTION_HYBRID = ani + lua, LOCOMOTION_LUA = lua only +--[[ +SWEP.WalkAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Idle_to_Walk", -- Number for act, String/Number for sequence + ["value_empty"] = "Idle_to_Walk_Empty", + ["transition"] = true + }, -- Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Walk", -- Number for act, String/Number for sequence + ["value_empty"] = "Walk_Empty", + ["is_idle"] = true + }, -- looping animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "Walk_to_Idle", -- Number for act, String/Number for sequence + ["value_empty"] = "Walk_to_Idle_Empty", + ["transition"] = true + } -- Outward transition +} +]] + +--[[ +-- Looping fire animation (full-auto only) +SWEP.ShootAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "ShootLoop_Start", -- Number for act, String/Number for sequence + ["value_is"] = "ShootLoop_Iron_Start", -- Number for act, String/Number for sequence + ["transition"] = true + }, -- Looping Start, fallbacks to loop + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "ShootLoop", -- Number for act, String/Number for sequence, + ["value_is"] = "ShootLoop_Iron", -- Number for act, String/Number for sequence, + ["is_idle"] = true, + }, -- Looping Animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "ShootLoop_End", -- Number for act, String/Number for sequence + ["value_is"] = "ShootLoop_Iron_End", -- Number for act, String/Number for sequence + ["transition"] = true + }, -- Looping End +} +]] + +SWEP.Customize_Mode = TFA.Enum.LOCOMOTION_LUA -- LOCOMOTION_ANI = mdl, LOCOMOTION_HYBRID = ani + lua, LOCOMOTION_LUA = lua only +--[[ +SWEP.CustomizeAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "customization_in", -- Number for act, String/Number for sequence + ["transition"] = true + }, + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "customization_idle", -- Number for act, String/Number for sequence + ["is_idle"] = true + }, + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, -- Sequence or act + ["value"] = "customization_out", -- Number for act, String/Number for sequence + ["transition"] = true + } +} +]] + +--[[ +SWEP.PumpAction = { -- Pump/bolt animations + ["type"] = TFA.Enum.ANIMATION_ACT, -- Sequence or act + ["value"] = ACT_VM_PULLBACK_HIGH, -- Number for act, String/Number for sequence + ["value_empty"] = ACT_VM_PULLBACK, -- Last shot pump + ["value_is"] = ACT_VM_PULLBACK_LOW, -- ADS pump +} +]] -- + +-- [[EFFECTS]] -- +-- Attachments +SWEP.MuzzleAttachment = "1" -- Should be "1" for CSS models or "muzzle" for hl2 models +SWEP.ShellAttachment = "2" -- Should be "2" for CSS models or "shell" for hl2 models +SWEP.MuzzleFlashEnabled = true -- Enable muzzle flash +SWEP.MuzzleAttachmentRaw = nil -- This will override whatever string you gave. This is the raw attachment number. This is overridden or created when a gun makes a muzzle event. +SWEP.AutoDetectMuzzleAttachment = false -- For multi-barrel weapons, detect the proper attachment? +SWEP.MuzzleFlashEffect = nil -- Change to a string of your muzzle flash effect. Copy/paste one of the existing from the base. +SWEP.SmokeParticle = nil -- Smoke particle (ID within the PCF), defaults to something else based on holdtype; "" to disable +SWEP.EjectionSmokeEnabled = true -- Disable automatic ejection smoke +-- Shell eject override +SWEP.LuaShellEject = false -- Enable shell ejection through lua? +SWEP.LuaShellEjectDelay = 0 -- The delay to actually eject things +SWEP.LuaShellModel = nil -- The model to use for ejected shells +SWEP.LuaShellScale = nil -- The model scale to use for ejected shells +SWEP.LuaShellYaw = nil -- The model yaw rotation ( relative ) to use for ejected shells +-- Tracer Stuff +SWEP.TracerName = nil -- Change to a string of your tracer name. Can be custom. There is a nice example at https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/base/entities/effects/tooltracer.lua +SWEP.TracerCount = 3 -- 0 disables, otherwise, 1 in X chance +-- Impact Effects +SWEP.ImpactEffect = nil -- Impact Effect +SWEP.ImpactDecal = nil -- Impact Decal +-- [[EVENT TABLE]] -- +SWEP.EventTable = {} -- Event Table, used for custom events when an action is played. This can even do stuff like playing a pump animation after shooting. +-- example: +-- SWEP.EventTable = { +-- [ACT_VM_RELOAD] = { +-- -- ifp is IsFirstTimePredicted() +-- { ["time"] = 0.1, ["type"] = "lua", ["value"] = function( wep, viewmodel, ifp ) end, ["client"] = true, ["server"] = true}, +-- { ["time"] = 0.1, ["type"] = "sound", ["value"] = Sound("x") } +-- } +-- } +-- [[RENDER TARGET]] -- +SWEP.RTMaterialOverride = nil -- Take the material you want out of print(LocalPlayer():GetViewModel():GetMaterials()), subtract 1 from its index, and set it to this. +SWEP.RTOpaque = false -- Do you want your render target to be opaque? +SWEP.RTCode = nil -- function(self) return end -- This is the function to draw onto your rendertarget +SWEP.RTBGBlur = true -- Draw background blur when 3D scope is active? +-- [[AKIMBO]] -- +SWEP.Akimbo = false -- Akimbo gun? Alternates between primary and secondary attacks. +SWEP.AnimCycle = 1 -- Start on the right +SWEP.AkimboHUD = true -- Draw holographic HUD for both weapons? +-- [[ATTACHMENTS]] -- +SWEP.VElements = nil -- Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.WElements = nil -- Export from SWEP Creation Kit. For each item that can/will be toggled, set active=false in its individual table +SWEP.Attachments = { + -- [ORDER] = = { atts = { "si_eotech" }, sel = 0 } + -- sel allows you to have an attachment pre-selected, and is used internally by the base to show which attachment is selected in each category. +} +SWEP.AttachmentDependencies = {} -- {["si_acog"] = {"bg_rail", ["type"] = "OR"}} -- type could also be AND to require multiple +SWEP.AttachmentExclusions = {} -- { ["si_iron"] = { [1] = "bg_heatshield"} } +SWEP.AttachmentTableOverride = {} --[[{ -- overrides WeaponTable for attachments + ["ins2_ub_laser"] = { -- attachment id, root of WeaponTable override + ["VElements"] = { + ["laser_rail"] = { + ["active"] = true + }, + }, + } +}]] + +SWEP.DInv2_GridSizeX = nil -- DInventory/2 Specific. Determines weapon's width in grid. This is not TFA Base specific and can be specified to any Scripted SWEP. +SWEP.DInv2_GridSizeY = nil -- DInventory/2 Specific. Determines weapon's height in grid. This is not TFA Base specific and can be specified to any Scripted SWEP. +SWEP.DInv2_Volume = nil -- DInventory/2 Specific. Determines weapon's volume in liters. This is not TFA Base specific and can be specified to any Scripted SWEP. +SWEP.DInv2_Mass = nil -- DInventory/2 Specific. Determines weapon's mass in kilograms. This is not TFA Base specific and can be specified to any Scripted SWEP. + +-- [[MISC INFO FOR MODELERS]] -- +--[[ + +Used Animations (for modelers): + +ACT_VM_DRAW - Draw +ACT_VM_DRAW_EMPTY - Draw empty +ACT_VM_DRAW_SILENCED - Draw silenced, overrides empty + +ACT_VM_IDLE - Idle +ACT_VM_IDLE_SILENCED - Idle empty, overwritten by silenced +ACT_VM_IDLE_SILENCED - Idle silenced + +ACT_VM_PRIMARYATTACK - Shoot +ACT_VM_PRIMARYATTACK_EMPTY - Shoot last chambered bullet +ACT_VM_PRIMARYATTACK_SILENCED - Shoot silenced, overrides empty +ACT_VM_PRIMARYATTACK_1 - Shoot ironsights, overriden by everything besides normal shooting +ACT_VM_DRYFIRE - Dryfire + +ACT_VM_RELOAD - Reload / Tactical Reload / Insert Shotgun Shell +ACT_SHOTGUN_RELOAD_START - Start shotgun reload, unless ACT_VM_RELOAD_EMPTY is there. +ACT_SHOTGUN_RELOAD_FINISH - End shotgun reload. +ACT_VM_RELOAD_EMPTY - Empty mag reload, chambers the new round. Works for shotguns too, where applicable. +ACT_VM_RELOAD_SILENCED - Silenced reload, overwrites all + + +ACT_VM_HOLSTER - Holster +ACT_VM_HOLSTER_SILENCED - Holster empty, overwritten by silenced +ACT_VM_HOLSTER_SILENCED - Holster silenced + +]] -- +DEFINE_BASECLASS( SWEP.Base ) diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_matproxies.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_matproxies.lua new file mode 100644 index 0000000..2ad2f64 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_matproxies.lua @@ -0,0 +1,80 @@ +-- Name: PlayerWeaponColorStatic +-- Description: Static/direct variation of PlayerWeaponColor, without any flickering. +-- Parameters: +-- 1. resultvar - Result variable for the color (such as $color2) +-- VMT Example: +--[[ + Proxies + { + PlayerWeaponColorStatic + { + resultvar $color2 + } + } +]] + + +-- Name: TFALaserColor +-- Description: +-- Parameters: +-- 1. resultvar - Result variable for the color (such as $color2) +-- VMT Example: +--[[ + Proxies + { + TFALaserColor + { + resultVar $color2 + } + } +]] + + +-- Name: TFAReticuleColor +-- Description: +-- Parameters: +-- 1. resultvar - Result variable for the color (such as $color2) +-- VMT Example: +--[[ + Proxies + { + TFAReticuleColor + { + resultVar $color2 + } + } +]] + + +-- Name: TFA_RTScope +-- Description: Replaces $basetexture with render target texture of 3D scopes +-- VMT Example: +--[[ + Proxies + { + TFA_RTScope + { + } + } +]] + + +-- Name: TFA_CubemapTint +-- Description: Tints +-- Parameters: +-- 1. resultvar - Variable for resulting envmap tint ($envmaptint) +-- 2. multiplier - Variable for base tint multiplier (a vector) +-- VMT Example: +--[[ + $envmapmultiplier "[1 1 1]" // Lighting will be multiplied by this value + + Proxies + { + TFA_CubemapTint + { + resultvar $envmaptint // Write final output to $envmaptint + multiplier $envmapmultiplier // Use our value for default envmap tint + } + } +]] + diff --git a/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_melee_template.lua b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_melee_template.lua new file mode 100644 index 0000000..8c20b95 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/documentation/tfa_melee_template.lua @@ -0,0 +1,95 @@ + +-- TFA Base Melee Template by TFA Base Devs + +-- To the extent possible under law, the person who associated CC0 with +-- TFA Base Template has waived all copyright and related or neighboring rights +-- to TFA Base Template. + +-- You should have received a copy of the CC0 legalcode along with this +-- work. If not, see . + +-- M9K compatible version is dated as 0 (and 0 is also fallback if TFADataVersion not present) +-- as well as everything made for TFA Base before 4.7 +SWEP.TFADataVersion = 1 + +----------------- Basic Garry's Mod SWEP structure stats / TFA Base properties +SWEP.Base = "tfa_melee_base" +SWEP.Category = "TFA Template" -- The category. +-- Please, just choose something generic or something I've already done if you plan on only doing like one (or two or three) swep(s). +SWEP.Manufacturer = nil -- Gun Manufactrer (e.g. Hoeckler and Koch) +SWEP.Author = "" -- Author Tooltip +SWEP.Contact = "" -- Contact Info Tooltip +SWEP.Purpose = "" -- Purpose Tooltip +SWEP.Instructions = "" -- Instructions Tooltip +SWEP.Spawnable = false -- Can you, as a normal user, spawn this? +SWEP.AdminSpawnable = false -- Can an adminstrator spawn this? Does not tie into your admin mod necessarily, unless its coded to allow for GMod's default ranks somewhere in its code. Evolve and ULX should work, but try to use weapon restriction rather than these. +SWEP.DrawCrosshair = true -- Draw the crosshair? + +SWEP.PrintName = "TFA Base Melee Template" -- Weapon name (Shown on HUD) +SWEP.Slot = 0 -- Slot in the weapon selection menu. Subtract 1, as this starts at 0. +SWEP.SlotPos = 73 -- Position in the slot +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.Weight = 30 -- This controls how "good" the weapon is for autopickup. +-- For base values please refer to the base template at lua/weapons/tfa_base_template/shared.lua + +-- Display values (inspection screen etc.) +SWEP.Primary.Damage = 0.01 -- Damage, in standard damage points. +SWEP.Primary.RPM = 600 -- This is in Rounds Per Minute / RPM + +----------------- ViewModel related +SWEP.ViewModel = "models/your/path/here.mdl" -- Viewmodel path +SWEP.ViewModelFOV = 65 -- This controls how big the viewmodel looks. Less is more. +SWEP.ViewModelFlip = false -- Set this to true for CSS models, or false for everything else (with a righthanded viewmodel.) +SWEP.UseHands = false -- Use gmod c_arms system. + +----------------- Worldmodel related +SWEP.WorldModel = "models/your/wmodel/path/here.mdl" -- Weapon world model path +SWEP.HoldType = "" -- This is how others view you carrying the weapon. Options include: +-- normal melee melee2 fist knife smg ar2 pistol rpg physgun grenade shotgun crossbow slam passive + +-- Attacks - Primary +SWEP.Primary.Attacks = { -- main attacks table, the values are selected randomly + { + ["act"] = ACT_VM_HITLEFT, -- Animation acvitity to use (ACT_ enum value) + ["len"] = 8 * 4.5, -- Trace distance + ["src"] = Vector(20, 10, 0), -- Trace source; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) + ["dir"] = Vector(-40, 30, 0), -- Trace direction/length; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) + ["dmg"] = 60, -- Damage + ["dmgtype"] = DMG_SLASH, -- Damage type (DMG_ enum value) + ["delay"] = 0.2, -- Delay (in seconds) before attack trace + ["force"] = 12, -- Damage force + ["hull"] = 10, -- Trace hull size + ["spr"] = true, -- Allow attack while sprinting? + ["snd"] = "Swing.Sound", -- Soundscript name for swing sound + ["hitflesh"] = "TFA.BashFlesh", -- Soundscript name for flesh hit + ["hitworld"] = "TFA.BashWall", -- Soundscript name for non-flesh hit + ["snd_delay"] = 0.1, -- Delay before swing sound + ["viewpunch"] = Angle(1, -10, 0), -- Viewpunch angle + ["end"] = 0.5, -- Time (from attack start) until next attack is allowed + ["direction"] = "L", -- Swing direction (for directional preference); L,R,F,B + }, +} +SWEP.Primary.MaxCombo = -1 -- How many attacks are allowed on single attack key hold +SWEP.Primary.Directional = false -- Prefer attacks with player's movement direction first +SWEP.Primary.SplitDamage = true -- Use the "dmg" value of the attack table? If false, SWEP.Primary.Damage will be used instead. + +-- Attacks - Secondary +-- If secondary attacks table is empty or not defined, it falls back to primary table +SWEP.Secondary.Attacks = {} -- same as SWEP.Primary.Attacks +SWEP.Secondary.MaxCombo = -1 +SWEP.Secondary.Directional = false +SWEP.Secondary.SplitDamage = true -- Use the "dmg" value of the attack table? If false, SWEP.Secondary.Damage will be used instead. +SWEP.Secondary.PrimaryFallback = true -- Allow falling back to primary attacks if secondary attacks table is empty/unavailable + +-- Attacks - Alternative (melee bash) +SWEP.Secondary.CanBash = true -- set to false to disable bashing +SWEP.Secondary.BashDamage = 25 -- Melee bash damage +SWEP.Secondary.BashSound = "TFA.Bash" -- Soundscript name for bash swing sound +SWEP.Secondary.BashHitSound = "TFA.BashWall" -- Soundscript name for non-flesh hit sound +SWEP.Secondary.BashHitSound_Flesh = "TFA.BashFlesh" -- Soundscript name for flesh hit sound +SWEP.Secondary.BashLength = 54 -- Length of bash melee trace in units +SWEP.Secondary.BashDelay = 0.2 -- Delay (in seconds) from bash start to bash attack trace +SWEP.Secondary.BashDamageType = DMG_SLASH -- Damage type (DMG_ enum value) +SWEP.Secondary.BashEnd = nil -- Bash end time (in seconds), defaults to animation end if undefined +SWEP.Secondary.BashInterrupt = false -- Bash attack interrupts everything (reload, draw, whatever) diff --git a/garrysmod/addons/tfa_base/lua/tfa/enums/animation.lua b/garrysmod/addons/tfa_base/lua/tfa/enums/animation.lua new file mode 100644 index 0000000..dde3b01 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/enums/animation.lua @@ -0,0 +1,14 @@ +-- luacheck: globals ACT_VM_FIDGET_EMPTY ACT_VM_FIDGET_SILENCED ACT_VM_BLOWBACK ACT_VM_HOLSTER_SILENCED +TFA.Enum.ANIMATION_ACT = 0 +TFA.Enum.ANIMATION_SEQ = 1 +ACT_VM_FIDGET_EMPTY = ACT_VM_FIDGET_EMPTY or ACT_CROSSBOW_FIDGET_UNLOADED +ACT_VM_FIDGET_SILENCED = ACT_VM_FIDGET_SILENCED or ACT_RPG_FIDGET_UNLOADED +ACT_VM_HOLSTER_SILENCED = ACT_VM_HOLSTER_SILENCED or ACT_CROSSBOW_HOLSTER_UNLOADED +ACT_VM_BLOWBACK = ACT_VM_BLOWBACK or -2 + +-- luacheck: globals ACT_VM_RELOAD_ADS ACT_VM_RELOAD_EMPTY_ADS ACT_VM_RELOAD_SILENCED_ADS ACT_SHOTGUN_RELOAD_START_ADS ACT_SHOTGUN_RELOAD_FINISH_ADS +ACT_VM_RELOAD_ADS = ACT_VM_RELOAD_ADS or ACT_IDLE_AIM_RIFLE_STIMULATED +ACT_VM_RELOAD_EMPTY_ADS = ACT_VM_RELOAD_EMPTY_ADS or ACT_WALK_AIM_RIFLE_STIMULATED +ACT_VM_RELOAD_SILENCED_ADS = ACT_VM_RELOAD_SILENCED_ADS or ACT_RUN_AIM_RIFLE_STIMULATED +ACT_SHOTGUN_RELOAD_START_ADS = ACT_SHOTGUN_RELOAD_START_ADS or ACT_IDLE_SHOTGUN_RELAXED +ACT_SHOTGUN_RELOAD_FINISH_ADS = ACT_SHOTGUN_RELOAD_FINISH_ADS or ACT_IDLE_SHOTGUN_STIMULATED diff --git a/garrysmod/addons/tfa_base/lua/tfa/enums/idle.lua b/garrysmod/addons/tfa_base/lua/tfa/enums/idle.lua new file mode 100644 index 0000000..e0e4e02 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/enums/idle.lua @@ -0,0 +1,5 @@ +--IDLE TYPE ENUM +TFA.Enum.IDLE_DISABLED = 0 +TFA.Enum.IDLE_LUA = 1 +TFA.Enum.IDLE_ANI = 2 +TFA.Enum.IDLE_BOTH = 3 diff --git a/garrysmod/addons/tfa_base/lua/tfa/enums/locomotion.lua b/garrysmod/addons/tfa_base/lua/tfa/enums/locomotion.lua new file mode 100644 index 0000000..ae3eae2 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/enums/locomotion.lua @@ -0,0 +1,4 @@ +--LOCOMOTION ENUM +TFA.Enum.LOCOMOTION_LUA = 0 +TFA.Enum.LOCOMOTION_HYBRID = 1 +TFA.Enum.LOCOMOTION_ANI = 2 diff --git a/garrysmod/addons/tfa_base/lua/tfa/enums/sights.lua b/garrysmod/addons/tfa_base/lua/tfa/enums/sights.lua new file mode 100644 index 0000000..f251a21 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/enums/sights.lua @@ -0,0 +1,12 @@ +TFA.Enum.SIGHTSPOS_ATTACH = 0 +TFA.Enum.SIGHTSPOS_BONE = 1 + +TFA.Enum.RETICLE_FLAT = bit.lshift(1, 1) +TFA.Enum.RETICLE_MODEL = bit.lshift(1, 2) +TFA.Enum.RETICLE_QUAD = bit.lshift(1, 3) + +TFA.Enum.RETICLE_DRAW_ORDER = { + TFA.Enum.RETICLE_MODEL, + TFA.Enum.RETICLE_QUAD, + TFA.Enum.RETICLE_FLAT, +} diff --git a/garrysmod/addons/tfa_base/lua/tfa/enums/statusnew.lua b/garrysmod/addons/tfa_base/lua/tfa/enums/statusnew.lua new file mode 100644 index 0000000..a5b6cec --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/enums/statusnew.lua @@ -0,0 +1,158 @@ +TFA.ENUM_COUNTER = TFA.ENUM_COUNTER or 0 + +TFA.Enum.InverseStatus = TFA.Enum.InverseStatus or {} +local upper = string.upper + +local function gen(input) + return "STATUS_" .. upper(input) +end + +function TFA.AddStatus(input) + local key = gen(input) + local getkey = TFA.Enum[key] + + if not getkey then + getkey = TFA.ENUM_COUNTER + TFA.ENUM_COUNTER = TFA.ENUM_COUNTER + 1 + TFA.Enum[key] = getkey + end + + TFA.Enum.InverseStatus[getkey] = key + + return getkey +end + +function TFA.GetStatus(input) + local key = gen(input) + local getkey = TFA.Enum[key] + + if not getkey then + return TFA.AddStatus(input) -- DANGEROUS: + -- Race condition: + -- If something go terribly wrong and order of addition of new statuses fuck up + -- everything will fail horribly! + end + + return getkey +end + +TFA.AddStatus("idle") +TFA.AddStatus("draw") +TFA.AddStatus("holster") +TFA.AddStatus("holster_final") +TFA.AddStatus("holster_ready") +TFA.AddStatus("reloading") +TFA.AddStatus("reloading_wait") + +TFA.AddStatus("reloading_loop_start") +TFA.AddStatus("reloading_loop_start_empty") +TFA.AddStatus("reloading_loop") +TFA.AddStatus("reloading_loop_end") + +TFA.Enum.STATUS_RELOADING_SHOTGUN_START = TFA.Enum.STATUS_RELOADING_LOOP_START +TFA.Enum.STATUS_RELOADING_SHOTGUN_START_SHELL = TFA.Enum.STATUS_RELOADING_LOOP_START_EMPTY +TFA.Enum.STATUS_RELOADING_SHOTGUN_LOOP = TFA.Enum.STATUS_RELOADING_LOOP +TFA.Enum.STATUS_RELOADING_SHOTGUN_END = TFA.Enum.STATUS_RELOADING_LOOP_END + +TFA.AddStatus("shooting") +TFA.AddStatus("silencer_toggle") +TFA.AddStatus("bashing") +TFA.AddStatus("bashing_wait") +TFA.AddStatus("inspecting") +TFA.AddStatus("fidget") +TFA.AddStatus("firemode") + +TFA.AddStatus("pump") + +TFA.AddStatus("knife_slash") +TFA.AddStatus("knife_stab") + +TFA.AddStatus("grenade_pull") +TFA.AddStatus("grenade_ready") +TFA.AddStatus("grenade_throw") + +TFA.AddStatus("blocking") +TFA.AddStatus("blocking_end") + +TFA.AddStatus("bow_shoot") +TFA.AddStatus("bow_cancel") + +TFA.AddStatus("grenade_pull") +TFA.AddStatus("grenade_throw") +TFA.AddStatus("grenade_ready") +TFA.AddStatus("grenade_throw_wait") + +TFA.Enum.HolsterStatus = { + [TFA.Enum.STATUS_HOLSTER] = true, + [TFA.Enum.STATUS_HOLSTER_FINAL] = true, + [TFA.Enum.STATUS_HOLSTER_READY] = true +} + +TFA.Enum.HolsterStatusFinal = { + [TFA.Enum.STATUS_HOLSTER_FINAL] = true, + [TFA.Enum.STATUS_HOLSTER_READY] = true +} + +TFA.Enum.ReloadStatus = { + [TFA.Enum.STATUS_RELOADING] = true, + [TFA.Enum.STATUS_RELOADING_WAIT] = true, + [TFA.Enum.STATUS_RELOADING_LOOP_START] = true, + [TFA.Enum.STATUS_RELOADING_LOOP_START_EMPTY] = true, + [TFA.Enum.STATUS_RELOADING_LOOP] = true, + [TFA.Enum.STATUS_RELOADING_LOOP_END] = true +} + +TFA.Enum.ReadyStatus = { + [TFA.Enum.STATUS_IDLE] = true, + [TFA.Enum.STATUS_INSPECTING] = true, + [TFA.Enum.STATUS_FIDGET] = true +} + +TFA.Enum.IronStatus = { + [TFA.Enum.STATUS_IDLE] = true, + [TFA.Enum.STATUS_SHOOTING] = true, + [TFA.Enum.STATUS_PUMP] = true, + [TFA.Enum.STATUS_FIREMODE] = true--, + --[TFA.Enum.STATUS_FIDGET] = true +} + +TFA.Enum.HUDDisabledStatus = { + [TFA.Enum.STATUS_IDLE] = true, + [TFA.Enum.STATUS_SHOOTING] = true, + [TFA.Enum.STATUS_FIREMODE] = true, + [TFA.Enum.STATUS_BASHING] = true, + [TFA.Enum.STATUS_BASHING_WAIT] = true, + [TFA.Enum.STATUS_HOLSTER] = true, + [TFA.Enum.STATUS_HOLSTER_FINAL] = true, + [TFA.Enum.STATUS_HOLSTER_READY] = true, + [TFA.Enum.STATUS_KNIFE_SLASH] = true, + [TFA.Enum.STATUS_KNIFE_STAB] = true, + [TFA.Enum.STATUS_GRENADE_PULL] = true, + [TFA.Enum.STATUS_GRENADE_READY] = true, + [TFA.Enum.STATUS_GRENADE_THROW] = true, + [TFA.Enum.STATUS_BLOCKING] = true, + [TFA.Enum.STATUS_BLOCKING_END] = true, + [TFA.Enum.STATUS_PUMP] = true +} + +TFA.Enum.BashStatus = { + [TFA.Enum.STATUS_BASHING] = true, + [TFA.Enum.STATUS_BASHING_WAIT] = true, +} + +TFA.Enum.SHOOT_IDLE = 0 +TFA.Enum.SHOOT_START = 1 +TFA.Enum.SHOOT_LOOP = 2 +TFA.Enum.SHOOT_CHECK = 3 +TFA.Enum.SHOOT_END = 4 + +TFA.Enum.ShootReadyStatus = { + [TFA.Enum.SHOOT_IDLE] = true, + [TFA.Enum.SHOOT_END] = true +} + +TFA.Enum.ShootLoopingStatus = { + [TFA.Enum.SHOOT_START] = true, + [TFA.Enum.SHOOT_LOOP] = true, + [TFA.Enum.SHOOT_CHECK] = true +} diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_icon.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_icon.lua new file mode 100644 index 0000000..abd53d0 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_icon.lua @@ -0,0 +1,136 @@ +local padding = TFA.Attachments.UIPadding +local PANEL = {} +PANEL.Wep = nil +PANEL.ID = nil +PANEL.Att = nil --Weapon attachment +PANEL.Attachment = nil --Actual TFA attachment table + +function PANEL:Init() + self.Wep = nil --Weapon Entity + self.ID = nil --Attachment ID + self.Att = nil --Attachment Category + self.Attachment = nil --TFA Attachment Name + self:SetMouseInputEnabled(true) + self:SetZPos(500) +end + +function PANEL:SetWeapon(wep) + if IsValid(wep) then + self.Wep = wep + end +end + +function PANEL:SetGunAttachment(att) + if att ~= nil then + self.Att = att + end +end + +function PANEL:SetAttachment(att) + self.Attachment = att +end + +function PANEL:SetID(id) + if id ~= nil then + self.ID = id + end +end + +function PANEL:GetSelected() + if not IsValid(self.Wep) then return false end + if not self.Att then return end + if not self.ID then return end + if not self.Wep.Attachments[self.Att] then return end + + return self.Wep.Attachments[self.Att].sel == self.ID +end + +function PANEL:AttachSound(attached) + if self.Attachment and TFA.Attachments.Atts[self.Attachment] then + local att = TFA.Attachments.Atts[self.Attachment] + + local snd = attached and att.AttachSound or att.DetachSound + + if snd and IsValid(self.Wep) then + self.Wep:EmitSound(snd) + + return + end + end + + chat.PlaySound() +end + +function PANEL:OnMousePressed() + if not IsValid(self.Wep) or not self.Attachment or self.Attachment == "" then return end + + if self:GetSelected() and self.Wep:CanAttach(self.Attachment, true) then + self.Wep:SetTFAAttachment(self.Att, -1, true) + self:AttachSound(false) + elseif self.Wep.Attachments[self.Att] and self.Wep:CanAttach(self.Attachment) then + self.Wep:SetTFAAttachment(self.Att, self.ID, true) + self:AttachSound(true) + end +end + +local function abbrev(str) + local tbl = string.Explode(" ",str,false) + local retstr = "" + for k,v in ipairs(tbl) do + local tmpstr = utf8.sub(v,1,1) + retstr = retstr .. ((k == 1) and string.upper(tmpstr) or string.lower(tmpstr)) + end + return retstr +end + +function PANEL:Paint(w, h) + if not IsValid(self.Wep) then return end + if self.Attachment == nil then return end + if not TFA.Attachments.Atts[self.Attachment] then self:SetMouseInputEnabled(false) return end + local sel = self:GetSelected() + local col = sel and TFA.Attachments.Colors["active"] or TFA.Attachments.Colors["background"] + + if not sel and not self.Wep:CanAttach(self.Attachment) then + col = TFA.Attachments.Colors["error"] + elseif sel and not self.Wep:CanAttach(self.Attachment, true) then + col = TFA.Attachments.Colors["error_attached"] + end + + draw.RoundedBox(0, 0, 0, w, h, ColorAlpha(col, self.Wep:GetInspectingProgress() * 225)) + + if not TFA.Attachments.Atts[self.Attachment].Icon then + TFA.Attachments.Atts[self.Attachment].Icon = "entities/tfa_qmark.png" + end + + if not TFA.Attachments.Atts[self.Attachment].Icon_Cached then + TFA.Attachments.Atts[self.Attachment].Icon_Cached = Material(TFA.Attachments.Atts[self.Attachment].Icon, "noclamp smooth") + end + + local attachmentIcon = TFA.Attachments.Atts[self.Attachment].Icon_Cached + + local iconOverride = self.Wep:GetStat("AttachmentIconOverride." .. self.Attachment) + if iconOverride and type(iconOverride) == "IMaterial" then + attachmentIcon = iconOverride + end + + surface.SetDrawColor(ColorAlpha(color_white, self.Wep:GetInspectingProgress() * 255)) + surface.SetMaterial(attachmentIcon) + surface.DrawTexturedRect(padding, padding, w - padding * 2, h - padding * 2) + if not TFA.Attachments.Atts[self.Attachment].ShortName then + TFA.Attachments.Atts[self.Attachment].ShortName = abbrev(language.GetPhrase(TFA.Attachments.Atts[self.Attachment].Name) or "") + TFA.Attachments.Atts[self.Attachment].ShortNameGenerated = true + end + draw.SimpleText(string.upper(TFA.Attachments.Atts[self.Attachment].ShortName) , "TFAAttachmentIconFontTiny", padding / 4, h, ColorAlpha(TFA.Attachments.Colors["primary"], self.Wep:GetInspectingProgress() * (sel and 192 or 64)), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) +end + +vgui.Register("TFAAttachmentIcon", PANEL, "Panel") + +-- cleanup generated shortnames +cvars.AddChangeCallback("gmod_language", function() + for id, att in pairs(TFA.Attachments.Atts or {}) do + if att.ShortNameGenerated then + att.ShortName = nil + att.ShortNameGenerated = nil + end + end +end, "tfa_attachment_clearshortnames") diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_panel.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_panel.lua new file mode 100644 index 0000000..f41a13b --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_panel.lua @@ -0,0 +1,268 @@ +if SERVER then + AddCSLuaFile() + return +end + +local dimensions, padding +local tooltip_mincount = 1 + +local PANEL = {} + +PANEL.HasInitialized = false +PANEL.Wep = nil +PANEL.Att = nil +PANEL.x = -1 +PANEL.y = -1 +PANEL.AttachmentTable = {} +PANEL.AttachmentIcons = {} +PANEL.VAtt = 0 + +function PANEL:Init() + self.HasInitialized = false + self.Wep = nil + self.Att = nil + self.x = -1 + self.y = -1 + self.AttachmentTable = {} + self.AttachmentIcons = {} + self:SetMouseInputEnabled(true) +end + +function PANEL:Initialize() + if not IsValid(self.Wep) then return false end + + if not self.Att then return end + + self.AttachmentTable = self.Wep.Attachments[ self.VAtt ] + self.VGUIAttachmentTable = self.Wep.VGUIAttachments[ self.VAtt ] + + dimensions = math.Round(TFA.ScaleH(TFA.Attachments.IconSize)) + padding = math.Round(TFA.ScaleH(TFA.Attachments.UIPadding)) + + local attCnt = #self.VGUIAttachmentTable.atts + local truewidth = dimensions * attCnt + padding * ( math.max(0,attCnt-1) + 2 ) + local finalwidth = math.max( truewidth, dimensions * tooltip_mincount + padding * ( math.max(0,tooltip_mincount-1) + 2 ) ) + + self:SetSize( finalwidth, dimensions + padding * 2 ) --+ tooltipheightmax + padding * 2 ) + self:DockPadding( 0, 0, 0, 0 ) + + local toppanel = self:Add("DPanel") + + --toppanel:Dock( FILL ) + --toppanel:Dock(TOP) + + toppanel:SetWidth( finalwidth ) + toppanel:SetHeight( self:GetTall() ) + toppanel:DockPadding( padding,padding, padding, padding ) + toppanel.Paint = function(myself,w,h) + if not IsValid(self.Wep) then return end + draw.RoundedBox( 0, 0, 0, w, h, ColorAlpha( TFA.Attachments.Colors["secondary"], ( self.Wep:GetInspectingProgress() or 0 ) * 128 ) ) + end + + self.FinalWidth = finalwidth + self.TopDockPanel = toppanel + + --self:InitializeTooltip() + + --[[ + + local tooltip = self:Add("TFAAttachmentTip") + tooltip:SetWeapon( self.Wep ) + tooltip:SetAttachment( self.Att ) + --tooltip:SetHeight( tooltipheightmax + padding * 2 ) + tooltip:SetSize( finalwidth, tooltipheightmax + padding * 2 ) + tooltip:SetPos(0, toppanel:GetTall() ) + self.ToolTip = tooltip + + ]]-- + + --local keyz = table.GetKeys( self.AttachmentTable.atts ) + --table.sort(keyz) + --PrintTable(keyz) + --for _,k in ipairs(keyz) do + -- local v = self.AttachmentTable.atts[k] + + self.HasInitialized = true + return true +end + +function PANEL:PopulateIcons() + dimensions = math.Round(TFA.ScaleH(TFA.Attachments.IconSize)) + padding = math.Round(TFA.ScaleH(TFA.Attachments.UIPadding)) + + local i = 0 + + for k,v in ipairs( self.VGUIAttachmentTable.atts ) do + local p = self.TopDockPanel:Add("TFAAttachmentIcon") + + p:SetWeapon( self.Wep ) + p:SetGunAttachment( self.Att ) + p:SetAttachment( v[1] ) + p:SetID( v[2] ) + + p:SetName("Attachment Icon: " .. v[1]) + + p:SetSize(dimensions, dimensions) + p:SetPos(dimensions * i + padding * ( i + 1 ), padding) + + i = i + 1 + --p:SetPos(0,0) + --p:DockMargin( 0,0, padding, 0 ) + --p:Dock(LEFT) + self.AttachmentIcons[k] = p + end + + return self +end + +function PANEL:InitializeTooltip() + local tooltip = vgui.Create("TFAAttachmentTip") + tooltip.Anchor = self + tooltip:SetWeapon(self.Wep) + tooltip:SetAttachment(self.Att) + tooltip:SetWidth(self.FinalWidth) + tooltip:SetPos(0, self.TopDockPanel:GetTall()) + self.ToolTip = tooltip + tooltip.LastTouched = 0 + tooltip.LastFrameAffectedImportant = 0 + + return tooltip +end + +function PANEL:OnRemove() + if IsValid(self.ToolTip) then + self.ToolTip:Remove() + end +end + +function PANEL:SetupTooltip(tooltip) + tooltip.Anchor = self + tooltip:SetWidth(math.max(self.FinalWidth, tooltip:GetWide())) + tooltip:SetPos(0, self.TopDockPanel:GetTall()) + self.ToolTip = tooltip + + return tooltip +end + +--[[ +function PANEL:CalcVAtt() + if not self.VAtt then + self.VAtt = 0 + local keyz = table.GetKeys( self.Wep.Attachments or {} ) + table.RemoveByValue( keyz, "BaseClass" ) + table.sort( keyz, function(a,b) + --A and B are keys + local v1 = self.Wep.Attachments[a] + local v2 = self.Wep.Attachments[b] + if v1 and v2 and v1.order then + return v1.order < ( v2.order or math.huge ) + else + return a < b + end + end) + for k,v in ipairs(keyz) do + if self.Att == v then + self.VAtt = k + end + end + --self:SetZPos( 100 - self.VAtt ) + end +end +]]-- + +function PANEL:Think() + if not IsValid(self.ToolTip) then return end + + --self:CalcVAtt() + + local header + local texttable + + for _,v in pairs( self.AttachmentIcons ) do + if v:IsHovered() then + header = TFA.Attachments.Atts[v.Attachment].Name + texttable = TFA.Attachments.Atts[v.Attachment].Description + break + end + end + + if not header then + for _,v in pairs( self.AttachmentIcons ) do + if v:GetSelected() then + header = TFA.Attachments.Atts[v.Attachment].Name + texttable = {}--TFA.Attachments.Atts[v.Attachment].Description + break + end + end + end + + if header and header ~= "" or self.ToolTip.LastTouched < RealTime() then + if texttable and #texttable == 0 and self.ToolTip.LastFrameAffectedImportant > RealTime() then + return + end + + self.ToolTip:SetHeader(header) + self.ToolTip:SetTextTable(texttable) + self.ToolTip:SetActive( texttable and #texttable > 0 ) + self.ToolTip:SetContentPanel( self.ContentPanel ) + self.ToolTip.LastTouched = RealTime() + 0.1 + + if texttable and #texttable ~= 0 then + self.ToolTip.LastFrameAffectedImportant = RealTime() + 0.1 + end + end +end + +function PANEL:SetContentPanel( p ) + if IsValid(p) then + self.ContentPanel = p + else + self.ContentPanel = nil + end +end + +function PANEL:SetWeapon( wepv ) + if IsValid(wepv) then + self.Wep = wepv + end +end + +function PANEL:SetAttachment( att ) + if att ~= nil then + self.VAtt = att + end +end + +function PANEL:SetCategory( att ) + if att ~= nil then + self.Att = att + end +end + +function PANEL:GetAnchoredH() + return true +end + +-- @Deprecated +function PANEL:Position() + -- self:SetPos( math.floor( self:GetParent():GetWide() - 32 - self:GetWide() ), math.max( self.VAtt - 1, 0 ) * dimensions + math.max( self.VAtt - 1, 0 ) * padding * 4 + math.max( self.VAtt - 1, 0 ) * spacing ) + -- self.HAnchored = true +end + +function PANEL:Paint( w, h ) + if not self.HasInitialized then return false end + + if not IsValid(self.Wep) + or not IsValid(self.Wep:GetOwner()) + or not self.Wep:GetOwner():IsPlayer() + or self.Wep:GetOwner():GetActiveWeapon() ~= self.Wep + or (self.Wep:GetInspectingProgress() or 0) < 0.01 then + if IsValid(self.ToolTip) then + self.ToolTip:Remove() + end + + self:Remove() + end +end + +vgui.Register( "TFAAttachmentPanel", PANEL, "Panel" ) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_tip.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_tip.lua new file mode 100644 index 0000000..342d2d9 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_attachment_tip.lua @@ -0,0 +1,230 @@ +if SERVER then + AddCSLuaFile() + return +end + +local padding = TFA.Attachments.UIPadding +local PANEL = {} + +PANEL.Wep = nil +PANEL.Header = nil +PANEL.TextTable = {} +PANEL.DefaultWidth = 0 +PANEL.DefaultHeight = 0 + +function PANEL:SetWidthNeue( val ) + self.DefaultWidth = val +end + +function PANEL:SetHeightNeue( val ) + self.DefaultHeight = val +end + +function PANEL:Init() + self.Wep = nil + self.Header = nil + self.TextTable = {} + self.DefaultHeight = 0 + self.DefaultWidth = 0 + self:SetMouseInputEnabled(false) + self:SetZPos(0) + self.SetWidthOld = self.SetWidthOld or self.SetWidth + self.SetWidth = self.SetWidthNeue + self.SetHeightOld = self.SetHeightOld or self.SetHeight + self.SetHeight = self.SetHeightNeue +end + +function PANEL:SetWeapon( wepv ) + if IsValid(wepv) then + self.Wep = wepv + end +end + +function PANEL:SetAttachment( att ) + if att ~= nil then + self:SetZPos( 200 - att ) + end +end + +function PANEL:SetHeader( h ) + self.Header = h +end + +function PANEL:SetTextTable( t ) + self.TextTable = t or {} +end + +PANEL.HeaderFont = "TFAAttachmentTTHeader" +PANEL.BodyFont = "TFAAttachmentTTBody" + +function PANEL:GetHeaderHeight() + if not IsValid(self.Wep) then return 0 end + if not self.Header then return 0 end + surface.SetFont(self.HeaderFont) + local _, th = surface.GetTextSize( language.GetPhrase(self.Header) ) + return th + padding * 2 +end + +function PANEL:GetHeaderSize() + if not IsValid(self.Wep) then return 0, 0 end + if not self.Header then return 0, 0 end + surface.SetFont(self.HeaderFont) + local tw, th = surface.GetTextSize( language.GetPhrase(self.Header) ) + return tw + padding * 2, th + padding * 2 +end + +function PANEL:GetTextTableHeight() + if not self.TextTable or #self.TextTable <= 0 then return 0 end + local hv = padding + surface.SetFont(self.BodyFont) + for _,v in pairs(self.TextTable) do + if type(v) == "string" then + v = language.GetPhrase(v) + local _, th = surface.GetTextSize( v ) + hv = hv + th + end + end + hv = hv + padding + return hv +end + +function PANEL:GetTextTableSize( ) + if not self.TextTable or #self.TextTable <= 0 then return 0, 0 end + local mw = 0 + local hv = padding + surface.SetFont(self.BodyFont) + for _,v in pairs(self.TextTable) do + if type(v) == "string" then + v = language.GetPhrase(v) + local tw, th = surface.GetTextSize( v ) + hv = hv + th + mw = math.max( mw, tw ) + end + end + hv = hv + padding + return mw + padding * 2, hv +end + +function PANEL:DrawHeader( w, h ) + if not self.Header then return 0 end + surface.SetFont(self.HeaderFont) + + local header = language.GetPhrase(self.Header) + local _, th = surface.GetTextSize( header ) + draw.RoundedBox( 0, 0, 0, w, th + padding * 2, ColorAlpha( TFA.Attachments.Colors["background"], self.Wep:GetInspectingProgress() * 192 ) ) + if self.AnchoredH then + draw.DrawText( header, self.HeaderFont, self:GetWide() / 2 , padding, ColorAlpha( TFA.Attachments.Colors["primary"], self.Wep:GetInspectingProgress() * 225 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + --draw.RoundedBox( 0, w / 2 - tw / 2, padding + th + padding / 4, tw, padding / 2, ColorAlpha( TFA.Attachments.Colors["primary"], self.Wep:GetInspectingProgress() * 225 ) ) + + --draw.DrawText( header, self.HeaderFont, self:GetWide() - padding, padding, ColorAlpha( TFA.Attachments.Colors["primary"], self.Wep:GetInspectingProgress() * 225 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + --draw.RoundedBox( 0, w - padding - tw, padding + th + padding / 4, tw, padding / 2, ColorAlpha( TFA.Attachments.Colors["primary"], self.Wep:GetInspectingProgress() * 225 ) ) + else + draw.DrawText( header, self.HeaderFont, padding, padding, ColorAlpha( TFA.Attachments.Colors["primary"], self.Wep:GetInspectingProgress() * 225 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + --draw.RoundedBox( 0, padding, padding + th + padding / 4, tw, padding / 2, ColorAlpha( TFA.Attachments.Colors["primary"], self.Wep:GetInspectingProgress() * 225 ) ) + end + return th + padding * 2 +end + +function PANEL:DrawTextTable( x, y ) + if not self.TextTable then return 0 end + --y = y + padding + local hv = padding + local acol = TFA.Attachments.Colors["primary"] + surface.SetFont(self.BodyFont) + for _,v in pairs(self.TextTable) do + if type(v) == "table" or type(v) == "vector" then + if v.r then + acol = Color( v.r or 0, v.g or 0, v.b or 0, v.a or 255 ) + elseif v.x then + acol = Color( v.x or 0, v.y or 0, v.z or 0, v.a or 255 ) + end + end + if type(v) == "string" then + v = language.GetPhrase(v) + local _, th = surface.GetTextSize( v ) + if self.AnchoredH then + --draw.DrawText( v, self.BodyFont, x + self:GetWide() - padding, y + hv, ColorAlpha( acol, self.Wep:GetInspectingProgress() * 225 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.DrawText( v, self.BodyFont, x + padding * 2, y + hv, ColorAlpha( acol, self.Wep:GetInspectingProgress() * 225 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + else + draw.DrawText( v, self.BodyFont, x + padding * 2, y + hv, ColorAlpha( acol, self.Wep:GetInspectingProgress() * 225 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + end + hv = hv + th + end + end + hv = hv + padding + return hv +end + +function PANEL:CalcSize() + local header_w, header_h = self:GetHeaderSize() + local text_w, text_h = self:GetTextTableSize() + self:SetWidthOld( math.max( self.DefaultWidth, math.max( header_w, text_w ) + padding * 2 )) + local h = header_h + text_h + if text_h > 0 then + h = h + padding * 2 + end + if IsValid( self.ContentPanel ) and not self:GetActive() then + local _, cph = self.ContentPanel:LocalToScreen(0,self.ContentPanel:GetTall()) + local _, yy = self:LocalToScreen(0,0) + h = math.min( h, cph - yy ) + end + self:SetHeightOld( h ) +end + +function PANEL:CalcPos() + if IsValid(self.Anchor) then + local x,y = self.Anchor:LocalToScreen(0,0) + y = y + if self.Anchor:GetAnchoredH() then + self.AnchoredH = true + if IsValid( self.ContentPanel ) and self:GetActive() then + local _, cph = self.ContentPanel:LocalToScreen(0,self.ContentPanel:GetTall()) + self:SetPos( x + self.Anchor:GetWide() - self:GetWide() , math.min( y + self.Anchor:GetTall(), cph - self:GetTall() ) ) + else + self:SetPos( x + self.Anchor:GetWide() - self:GetWide() , math.min( y + self.Anchor:GetTall(), ScrH() - self:GetTall() ) ) + end + else + self.AnchoredH = false + self:SetPos( x, y + self.Anchor:GetTall() ) + end + end +end + +function PANEL:Think() + self:CalcSize() + self:CalcPos() +end + +function PANEL:SetContentPanel( p ) + if IsValid(p) then + self.ContentPanel = p + else + self.ContentPanel = nil + end +end + +function PANEL:Paint( w, h ) + if not IsValid(self.Wep) then return end + if ( self.Wep:GetInspectingProgress() or 0 ) < 0.01 then self:Remove() end + if IsValid( self.ContentPanel ) and not self:GetActive() then + local _, cph = self.ContentPanel:LocalToScreen(0,math.max(self.ContentPanel:GetTall(),32)) + local _, yy = self:LocalToScreen(0,0) + if cph - yy <= 0 then + return + end + end + draw.RoundedBox( 0, 0, 0, w, h, ColorAlpha( TFA.Attachments.Colors["background"], self.Wep:GetInspectingProgress() * 192 ) ) + local hh = self:DrawHeader( w, h ) + self:DrawTextTable( 0, hh ) + render.SetScissorRect(0,0,ScrW(),ScrH(),false) +end + +function PANEL:SetActive( a ) + self.Active = a +end + +function PANEL:GetActive( a ) + return self.Active +end + +vgui.Register( "TFAAttachmentTip", PANEL, "Panel" ) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_commands.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_commands.lua new file mode 100644 index 0000000..4389979 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_commands.lua @@ -0,0 +1,441 @@ +if GetConVar("cl_tfa_inspection_bokeh") == nil then + CreateClientConVar("cl_tfa_inspection_bokeh", 0, true, false, "Enable inspection bokeh DOF") +end + +if GetConVar("cl_tfa_inspection_bokeh_radius") == nil then + CreateClientConVar("cl_tfa_inspection_bokeh_radius", 0.1, true, false, "Inspection bokeh DOF radius", 0.01, 1) +end + +if GetConVar("cl_tfa_inspect_hide_in_screenshots") == nil then + CreateClientConVar("cl_tfa_inspect_hide_in_screenshots", 0, true, false, "Hide inspection panel in screenshots") +end + +if GetConVar("cl_tfa_inspect_hide") == nil then + CreateClientConVar("cl_tfa_inspect_hide", 0, false, false, "Hide inspection panel") +end + +if GetConVar("cl_tfa_inspect_hide_hud") == nil then + CreateClientConVar("cl_tfa_inspect_hide_hud", 0, true, false, "Hide HUD when inspecting weapon (DLib required)") +end + +if GetConVar("cl_tfa_inspect_newbars") == nil then + CreateClientConVar("cl_tfa_inspect_newbars", 0, true, false, "Use new stat bars in inspection screen") +end + +if GetConVar("cl_tfa_inspect_spreadinmoa") == nil then + CreateClientConVar("cl_tfa_inspect_spreadinmoa", 0, true, false, "Show accuracy in MOA instead of degrees on inspection screen") +end + +if GetConVar("cl_tfa_viewbob_intensity") == nil then + CreateClientConVar("cl_tfa_viewbob_intensity", 1, true, false, "View bob intensity multiplier", 0, 10) +end + +if GetConVar("cl_tfa_gunbob_intensity") == nil then + CreateClientConVar("cl_tfa_gunbob_intensity", 1, true, false, "Gun bob intensity multiplier", 0, 10) +end + +if GetConVar("cl_tfa_gunbob_custom") == nil then + CreateClientConVar("cl_tfa_gunbob_custom", 1, true, false, "Use custom gun bob") +end + +if GetConVar("cl_tfa_gunbob_invertsway") == nil then + CreateClientConVar("cl_tfa_gunbob_invertsway", 0, true, false, "Invert gun sway direction") +end + +if GetConVar("cl_tfa_3dscope_quality") == nil then + CreateClientConVar("cl_tfa_3dscope_quality", 0, true, true, "3D scope quality (0 - Full quality, 1 - Half, 2 - Quarter, 3 - Eighth)", 0, 3) +end + +if GetConVar("cl_tfa_3dscope") == nil then + CreateClientConVar("cl_tfa_3dscope", 1, true, true, "[IGNORED] Enable 3D scopes?") +end + +if GetConVar("cl_tfa_scope_sensitivity_3d") == nil then + CreateClientConVar("cl_tfa_scope_sensitivity_3d", 2, true, true, "3D scope sensitivity (0 - No compensation, 1 - Standard compensation, 2 - 3D compensation, 3 - 3D + FOV compensation)", 0, 3) +end + +if GetConVar("cl_tfa_3dscope_overlay") == nil then + CreateClientConVar("cl_tfa_3dscope_overlay", 0, true, true, "Enable 3D scope shadows?") +end + +if GetConVar("cl_tfa_scope_sensitivity_autoscale") == nil then + CreateClientConVar("cl_tfa_scope_sensitivity_autoscale", 1, true, true, "Compensate sensitivity for FOV?") +end + +if GetConVar("cl_tfa_scope_sensitivity") == nil then + CreateClientConVar("cl_tfa_scope_sensitivity", 100, true, true, "3D scope sensitivity percentage", 0.01, 100) +end + +if GetConVar("cl_tfa_ironsights_toggle") == nil then + CreateClientConVar("cl_tfa_ironsights_toggle", 1, true, true, "Toggle ironsights?") +end + +if GetConVar("cl_tfa_ironsights_resight") == nil then + CreateClientConVar("cl_tfa_ironsights_resight", 1, true, true, "Keep ironsights after reload or sprint?") +end + +if GetConVar("cl_tfa_ironsights_responsive") == nil then + CreateClientConVar("cl_tfa_ironsights_responsive", 0, true, true, "Allow both toggle and held down iron sights") +end + +if GetConVar("cl_tfa_ironsights_responsive_timer") == nil then + CreateClientConVar("cl_tfa_ironsights_responsive_timer", 0.175, true, true, "Time in seconds to determine responsivness time", 0.01, 2) +end + +if GetConVar("cl_tfa_laser_trails") == nil then + CreateClientConVar("cl_tfa_laser_trails", 1, true, true, "Enable laser dot trails?") +end + +--Crosshair Params +if GetConVar("cl_tfa_hud_crosshair_length") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_length", 1, true, false, "Crosshair length") +end + +if GetConVar("cl_tfa_hud_crosshair_length_use_pixels") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_length_use_pixels", 0, true, false, "Should crosshair length use pixels?") +end + +if GetConVar("cl_tfa_hud_crosshair_width") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_width", 1, true, false, "Crosshair width") +end + +if GetConVar("cl_tfa_hud_crosshair_enable_custom") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_enable_custom", 1, true, false, "Enable custom crosshair?") +end + +if GetConVar("cl_tfa_hud_crosshair_gap_scale") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_gap_scale", 1, true, false, "Crosshair gap scale") +end + +if GetConVar("cl_tfa_hud_crosshair_dot") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_dot", 0, true, false, "Enable crosshair dot?") +end + +--Crosshair Color +if GetConVar("cl_tfa_hud_crosshair_color_r") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_r", 225, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_crosshair_color_g") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_g", 225, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_crosshair_color_b") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_b", 225, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_crosshair_color_a") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_a", 200, true, false, nil, 0, 255) +end + +if GetConVar("cl_tfa_hud_crosshair_color_team") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_team", 1, true, false, "Should crosshair use team color of entity being aimed at?") +end + +-- Crosshair Team Color: Friendly +if GetConVar("cl_tfa_hud_crosshair_color_friendly_r") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_friendly_r", 0, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_crosshair_color_friendly_g") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_friendly_g", 255, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_crosshair_color_friendly_b") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_friendly_b", 0, true, false, nil, 0, 255) +end + +-- Crosshair Team Color: Enemy +if GetConVar("cl_tfa_hud_crosshair_color_enemy_r") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_enemy_r", 255, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_crosshair_color_enemy_g") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_enemy_g", 0, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_crosshair_color_enemy_b") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_color_enemy_b", 0, true, false, nil, 0, 255) +end + +--Crosshair Outline +if GetConVar("cl_tfa_hud_crosshair_outline_color_r") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_outline_color_r", 5, true, false, nil, 0, 255) +end + +if GetConVar("cl_tfa_hud_crosshair_outline_color_g") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_outline_color_g", 5, true, false, nil, 0, 255) +end + +if GetConVar("cl_tfa_hud_crosshair_outline_color_b") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_outline_color_b", 5, true, false, nil, 0, 255) +end + +if GetConVar("cl_tfa_hud_crosshair_outline_color_a") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_outline_color_a", 200, true, false, nil, 0, 255) +end + +if GetConVar("cl_tfa_hud_crosshair_outline_width") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_outline_width", 1, true, false, "Crosshair outline width") +end + +if GetConVar("cl_tfa_hud_crosshair_outline_enabled") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_outline_enabled", 1, true, false, "Enable crosshair outline?") +end + +if GetConVar("cl_tfa_hud_crosshair_triangular") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_triangular", 0, true, false, "Enable triangular Crysis-like crosshair?") +end + +if GetConVar("cl_tfa_hud_crosshair_pump") == nil then + CreateClientConVar("cl_tfa_hud_crosshair_pump", 0, true, false, "Enable pump feedback on crosshair?") +end + +if GetConVar("cl_tfa_hud_hitmarker_enabled") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_enabled", 1, true, false, "Enable hit marker?") +end + +if GetConVar("cl_tfa_hud_hitmarker_fadetime") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_fadetime", 0.3, true, false, "Hit marker fade time (in seconds)", 0, 3) +end + +if GetConVar("cl_tfa_hud_hitmarker_solidtime") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_solidtime", 0.1, true, false, nil, 0, 3) +end + +if GetConVar("cl_tfa_hud_hitmarker_scale") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_scale", 1, true, false, "Hit marker scale", 0, 5) +end + +-- Hitmarker Color +if GetConVar("cl_tfa_hud_hitmarker_color_r") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_color_r", 225, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_hitmarker_color_g") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_color_g", 225, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_hitmarker_color_b") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_color_b", 225, true, false, nil, 0, 255) +end +if GetConVar("cl_tfa_hud_hitmarker_color_a") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_color_a", 200, true, false, nil, 0, 255) +end + +if GetConVar("cl_tfa_hud_hitmarker_3d_all") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_3d_all", 0, true, true) +end + +if GetConVar("cl_tfa_hud_hitmarker_3d_shotguns") == nil then + CreateClientConVar("cl_tfa_hud_hitmarker_3d_shotguns", 1, true, true) +end + +--Other stuff +if GetConVar("cl_tfa_hud_ammodata_fadein") == nil then + CreateClientConVar("cl_tfa_hud_ammodata_fadein", 0.2, true, false) +end + +if GetConVar("cl_tfa_hud_hangtime") == nil then + CreateClientConVar("cl_tfa_hud_hangtime", 1, true, true) +end + +if GetConVar("cl_tfa_hud_enabled") == nil then + CreateClientConVar("cl_tfa_hud_enabled", 1, true, false, "Enable 3D2D hud?") +end + +if GetConVar("cl_tfa_hud_fallback_enabled") == nil then + CreateClientConVar("cl_tfa_hud_fallback_enabled", 1, true, false, "Enable basic fallback hud?") +end + +if GetConVar("cl_tfa_hud_scale") == nil then + CreateClientConVar("cl_tfa_hud_scale", 1, true, false, "Size multiplier of HUD elements", .25, 4) +end + +if GetConVar("cl_tfa_fx_gasblur") == nil then + CreateClientConVar("cl_tfa_fx_gasblur", 0, true, true, "Enable muzzle gas blur?") +end + +if GetConVar("cl_tfa_fx_muzzlesmoke") == nil then + CreateClientConVar("cl_tfa_fx_muzzlesmoke", 1, true, true, "Enable muzzle smoke trail?") +end + +if GetConVar("cl_tfa_fx_muzzlesmoke_limited") == nil then + CreateClientConVar("cl_tfa_fx_muzzlesmoke_limited", 1, true, true, "Limit muzzle smoke trails?") +end + +if GetConVar("cl_tfa_fx_muzzleflashsmoke") == nil then + CreateClientConVar("cl_tfa_fx_muzzleflashsmoke", 1, true, true, "Enable muzzleflash smoke?") +end + +if GetConVar("cl_tfa_legacy_shells") == nil then + CreateClientConVar("cl_tfa_legacy_shells", 0, true, true, "Use legacy shells?") +end + +if GetConVar("cl_tfa_fx_ejectionsmoke") == nil then + CreateClientConVar("cl_tfa_fx_ejectionsmoke", 1, true, true, "Enable shell ejection smoke?") +end + +if GetConVar("cl_tfa_fx_ejectionlife") == nil then + CreateClientConVar("cl_tfa_fx_ejectionlife", 15, true, true, "How long shells exist in the world") +end + +if GetConVar("cl_tfa_fx_impact_enabled") == nil then + CreateClientConVar("cl_tfa_fx_impact_enabled", 1, true, true, "Enable custom bullet impact effects?") +end + +if GetConVar("cl_tfa_fx_impact_ricochet_enabled") == nil then + CreateClientConVar("cl_tfa_fx_impact_ricochet_enabled", 1, true, true, "Enable bullet ricochet effect?") +end + +if GetConVar("cl_tfa_fx_impact_ricochet_sparks") == nil then + CreateClientConVar("cl_tfa_fx_impact_ricochet_sparks", 6, true, true, "Enable bullet ricochet sparks?") +end + +if GetConVar("cl_tfa_fx_impact_ricochet_sparklife") == nil then + CreateClientConVar("cl_tfa_fx_impact_ricochet_sparklife", 2, true, true) +end + +if GetConVar("cl_tfa_fx_ads_dof") == nil then + CreateClientConVar("cl_tfa_fx_ads_dof", 0, true, true, "Enable iron sights DoF (Depth of Field)") +end + +if GetConVar("cl_tfa_fx_ads_dof_hd") == nil then + CreateClientConVar("cl_tfa_fx_ads_dof_hd", 0, true, true, "Enable better quality for DoF") +end + +--viewbob + +if GetConVar("cl_tfa_viewbob_animated") == nil then + CreateClientConVar("cl_tfa_viewbob_animated", 1, true, false, "Use animated viewbob?") +end + +--Viewmodel Mods +if GetConVar("cl_tfa_viewmodel_offset_x") == nil then + CreateClientConVar("cl_tfa_viewmodel_offset_x", 0, true, false, nil, -2, 2) +end + +if GetConVar("cl_tfa_viewmodel_offset_y") == nil then + CreateClientConVar("cl_tfa_viewmodel_offset_y", 0, true, false, nil, -2, 2) +end + +if GetConVar("cl_tfa_viewmodel_offset_z") == nil then + CreateClientConVar("cl_tfa_viewmodel_offset_z", 0, true, false, nil, -2, 2) +end + +if GetConVar("cl_tfa_viewmodel_offset_fov") == nil then + CreateClientConVar("cl_tfa_viewmodel_offset_fov", 0, true, false, nil, -5, 5) +end + +if GetConVar("cl_tfa_viewmodel_multiplier_fov") == nil then + CreateClientConVar("cl_tfa_viewmodel_multiplier_fov", 1, true, false, nil, 0.75, 2) +end + +if GetConVar("cl_tfa_viewmodel_flip") == nil then + CreateClientConVar("cl_tfa_viewmodel_flip", 0, true, false) +end + +if GetConVar("cl_tfa_viewmodel_centered") == nil then + CreateClientConVar("cl_tfa_viewmodel_centered", 0, true, false) +end + +if GetConVar("cl_tfa_viewmodel_nearwall") == nil then + CreateClientConVar("cl_tfa_viewmodel_nearwall", 1, true, false) +end + +if GetConVar("cl_tfa_viewmodel_vp_enabled") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_enabled", 1, true, false) +end + +if GetConVar("cl_tfa_viewmodel_vp_pitch") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_pitch", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_viewmodel_vp_pitch_is") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_pitch_is", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_viewmodel_vp_vertical") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_vertical", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_viewmodel_vp_vertical_is") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_vertical_is", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_viewmodel_vp_max_vertical") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_max_vertical", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_viewmodel_vp_max_vertical_is") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_max_vertical_is", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_viewmodel_vp_yaw") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_yaw", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_viewmodel_vp_yaw_is") == nil then + CreateClientConVar("cl_tfa_viewmodel_vp_yaw_is", 1, true, false, nil, 0, 10) +end + +if GetConVar("cl_tfa_debug_crosshair") == nil then + CreateClientConVar("cl_tfa_debug_crosshair", 0, false, false, "Debug crosshair (Admin only)") +end + +if GetConVar("cl_tfa_debug_animations") == nil then + CreateClientConVar("cl_tfa_debug_animations", 0, false, false, "Debug animations (Admin only)") +end + +if GetConVar("cl_tfa_debug_rt") == nil then + CreateClientConVar("cl_tfa_debug_rt", 0, false, false, "Debug RT scopes (Admin only)") +end + +if GetConVar("cl_tfa_debug_cache") == nil then + CreateClientConVar("cl_tfa_debug_cache", 0, false, false, "Disable stat caching (may cause heavy performance impact!)") +end + +local function UpdateColorCVars() + timer.Create("tfa_apply_player_color", 0.5, 1, function() + RunConsoleCommand("sv_tfa_apply_player_colors") + end) +end + +--Reticule Color +if GetConVar("cl_tfa_reticule_color_r") == nil then + CreateClientConVar("cl_tfa_reticule_color_r", 255, true, true) + cvars.AddChangeCallback("cl_tfa_reticule_color_r", UpdateColorCVars, "TFANetworkPlayerColors") +end +if GetConVar("cl_tfa_reticule_color_g") == nil then + CreateClientConVar("cl_tfa_reticule_color_g", 100, true, true) + cvars.AddChangeCallback("cl_tfa_reticule_color_g", UpdateColorCVars, "TFANetworkPlayerColors") +end +if GetConVar("cl_tfa_reticule_color_b") == nil then + CreateClientConVar("cl_tfa_reticule_color_b", 0, true, true) + cvars.AddChangeCallback("cl_tfa_reticule_color_b", UpdateColorCVars, "TFANetworkPlayerColors") +end + +--Laser Color +if GetConVar("cl_tfa_laser_color_r") == nil then + CreateClientConVar("cl_tfa_laser_color_r", 255, true, true) + cvars.AddChangeCallback("cl_tfa_laser_color_r", UpdateColorCVars, "TFANetworkPlayerColors") +end +if GetConVar("cl_tfa_laser_color_g") == nil then + CreateClientConVar("cl_tfa_laser_color_g", 0, true, true) + cvars.AddChangeCallback("cl_tfa_laser_color_g", UpdateColorCVars, "TFANetworkPlayerColors") +end +if GetConVar("cl_tfa_laser_color_b") == nil then + CreateClientConVar("cl_tfa_laser_color_b", 0, true, true) + cvars.AddChangeCallback("cl_tfa_laser_color_b", UpdateColorCVars, "TFANetworkPlayerColors") +end + +if GetConVar("cl_tfa_attachments_persist_enabled") == nil then + CreateClientConVar("cl_tfa_attachments_persist_enabled", 1, true, true, "Should attachments selection persist across different weapons/lives/sessions?") +end + +if GetConVar("cl_tfa_hud_keybindhints_enabled") == nil then + CreateClientConVar("cl_tfa_hud_keybindhints_enabled", "1", true, false, "Enable keybind hints?") +end + +if GetConVar("cl_tfa_hud_keybindhints_solidtime") == nil then + CreateClientConVar("cl_tfa_hud_keybindhints_solidtime", 3, true, false, "How long keybind hint will stay on screen (in seconds)", 0) +end + +if GetConVar("cl_tfa_hud_keybindhints_fadeintime") == nil then + CreateClientConVar("cl_tfa_hud_keybindhints_fadeintime", 1, true, false, "Keybind hint fade-in time (in seconds)", 0.01) +end + +if GetConVar("cl_tfa_hud_keybindhints_fadeouttime") == nil then + CreateClientConVar("cl_tfa_hud_keybindhints_fadeouttime", 4, true, false, "Keybind hint fade-out time (in seconds)", 0.01) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_devtools.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_devtools.lua new file mode 100644 index 0000000..7886655 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_devtools.lua @@ -0,0 +1,183 @@ +local cv_dba = GetConVar("cl_tfa_debug_animations") +local cv_dbc = GetConVar("cl_tfa_debug_crosshair") + +local color_red = Color(255, 0, 0, 255) +local color_white = Color(255, 255, 255, 255) + +local state_strings = {} + +for i = 1, 32 do + local strcomp = string.rep("%d", i) + local slice = {} + + for i2 = 0, i - 1 do + table.insert(slice, "band(rshift(state, " .. i2 .. "), 1) == 0 and 0 or 1") + end + + local fn = CompileString([[ + local rshift = bit.rshift + local band = bit.band + return function(state) + return ]] .. table.concat(slice, ", ") .. [[ + end + ]], "tfa_dev_tools")() + + state_strings[i] = function(state) + return string.format(strcomp, fn(state)) + end +end + +local lastStatusBarWidth = 300 +local lastAnimStatusWidth = 300 + +local STATUS_BAR_COLOR = Color(255, 255, 255) +local STATUS_BAR_COLOR_BG = Color(74, 74, 74) + +local function DrawDebugInfo(w, h, ply, wep) + if not cv_dba:GetBool() then return end + + local x, y = w * .5, h * .2 + + if wep.event_table_overflow then + if wep.EventTableEdict[0] then + draw.SimpleTextOutlined("UNPREDICTED Event table state:", "TFASleekDebug", x + 240, y, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, color_black) + local y2 = y + TFA.Fonts.SleekHeightDebug + + if not wep._built_event_debug_string_fn then + local str = "" + local str2 = "" + + for i = 0, #wep.EventTableEdict do + str = str .. "%d" + + if (i + 1) % 32 == 0 then + str = str .. "\n" + end + + if str2 == "" then + str2 = "self.EventTableEdict[" .. i .. "].called and 1 or 0" + else + str2 = str2 .. ", self.EventTableEdict[" .. i .. "].called and 1 or 0" + end + end + + wep._built_event_debug_string_fn = CompileString([[ + local format = string.format + return function(self) + return format([==[]] .. str .. [[]==], ]] .. str2 .. [[) + end + ]], "TFA Base Debug Tools")() + end + + for line in string.gmatch(wep:_built_event_debug_string_fn(), "(%S+)") do + draw.SimpleTextOutlined(line, "TFASleekDebug", x + 240, y2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, color_black) + y2 = y2 + TFA.Fonts.SleekHeightDebug + end + end + elseif wep._EventSlotCount ~= 0 then + draw.SimpleTextOutlined("Event table state:", "TFASleekDebug", x + 240, y, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, color_black) + local y2 = y + TFA.Fonts.SleekHeightDebug + + for i = 1, wep._EventSlotCount do + local state = wep["GetEventStatus" .. i](wep) + local stringbake + + if i ~= wep._EventSlotCount then + stringbake = state_strings[32](state) + else + local fn = state_strings[wep._EventSlotNum % 32 + 1] + + if not fn then break end + stringbake = fn(state) + end + + draw.SimpleTextOutlined(stringbake, "TFASleekDebug", x + 240, y2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 1, color_black) + y2 = y2 + TFA.Fonts.SleekHeightDebug + end + end + + local statusText = string.format( + "%s [%.2f, %.2f, %.2f, %.2f]", + TFA.Enum.InverseStatus[wep:GetStatus()] or wep:GetStatus(), + CurTime() + (wep.CurTimePredictionAdvance or 0), + wep:GetStatusProgress(true), + wep:GetStatusStart(), + wep:GetStatusEnd()) + + draw.SimpleTextOutlined(statusText, "TFASleekDebug", x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 1, color_black) + + --[[if wep:GetStatusProgress() >= 1 then + local stW, stH = surface.GetTextSize(statusText) + + lastStatusBarWidth = math.max(300, stW) + end]] + + y = y + TFA.Fonts.SleekHeightDebug + 2 + + surface.SetDrawColor(STATUS_BAR_COLOR_BG) + surface.DrawRect(x - lastStatusBarWidth / 2, y, lastStatusBarWidth, 4) + + surface.SetDrawColor(STATUS_BAR_COLOR) + surface.DrawRect(x - lastStatusBarWidth / 2, y, lastStatusBarWidth * wep:GetStatusProgress(true), 4) + + y = y + 8 + + local vm = wep.OwnerViewModel + + if IsValid(vm) then + local seq = vm:GetSequence() + + draw.SimpleTextOutlined(string.format("%s [%d] (%s/%d)", vm:GetSequenceName(seq), seq, vm:GetSequenceActivityName(seq), vm:GetSequenceActivity(seq)), "TFASleekDebug", x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 1, color_black) + y = y + TFA.Fonts.SleekHeightDebug + + local cycle = vm:GetCycle() + local len = vm:SequenceDuration(seq) + local rate = vm:GetPlaybackRate() + + local animStatus = string.format("%.2fs / %.2fs (%.2f) @ %d%%", cycle * len, len, cycle, rate * 100) + + draw.SimpleTextOutlined(animStatus, "TFASleekDebug", x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, 1, color_black) + --local stW, stH = surface.GetTextSize(animStatus) + --lastAnimStatusWidth = math.max(300, stW) + + y = y + TFA.Fonts.SleekHeightDebug + 2 + + surface.SetDrawColor(STATUS_BAR_COLOR_BG) + surface.DrawRect(x - lastAnimStatusWidth / 2, y, lastAnimStatusWidth, 4) + + if len * rate >= 0.2 then + surface.SetDrawColor(STATUS_BAR_COLOR) + surface.DrawRect(x - lastAnimStatusWidth / 2, y, lastAnimStatusWidth * cycle, 4) + end + end +end + +local function DrawDebugCrosshair(w, h, ply, wep) + if not cv_dbc:GetBool() then return end + + surface.SetDrawColor(color_red) + surface.DrawRect(w * .5 - 1, h * .5 - 1, 2, 2) + + local tr = util.QuickTrace(ply:GetShootPos(), wep:GetAimVector(), ply) + local tsc = tr.HitPos:ToScreen() + + if tsc.visible then + surface.SetDrawColor(color_white) + surface.DrawRect(tsc.x - 1, tsc.y - 1, 2, 2) + end +end + +local w, h + +hook.Add("HUDPaint", "tfa_drawdebughud", function() + local ply = LocalPlayer() or NULL + if not ply:IsValid() or not ply:IsAdmin() then return end + + local wep = ply:GetActiveWeapon() or NULL + if not wep:IsValid() or not wep.IsTFAWeapon then return end + + w, h = ScrW(), ScrH() + + DrawDebugInfo(w, h, ply, wep) + DrawDebugCrosshair(w, h, ply, wep) +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_fonts.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_fonts.lua new file mode 100644 index 0000000..ed26b39 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_fonts.lua @@ -0,0 +1,83 @@ +TFA.Fonts = TFA.Fonts or {} + +local ScaleH = TFA.ScaleH + +local function GetFontHeight(fontname) -- UNCACHED! + surface.SetFont(fontname) + + local _, h = surface.GetTextSize("W") + + return h +end + +local function CreateFonts() + local fontdata = {} + + fontdata.font = "Inter" + fontdata.shadow = false + fontdata.extended = true + fontdata.weight = 400 + fontdata.size = ScaleH(36) + surface.CreateFont("TFASleek", fontdata) + TFA.Fonts.SleekHeight = GetFontHeight("TFASleek") + + fontdata.size = ScaleH(30) + surface.CreateFont("TFASleekMedium", fontdata) + TFA.Fonts.SleekHeightMedium = GetFontHeight("TFASleekMedium") + + fontdata.size = ScaleH(24) + surface.CreateFont("TFASleekSmall", fontdata) + TFA.Fonts.SleekHeightSmall = GetFontHeight("TFASleekSmall") + + fontdata.size = ScaleH(18) + surface.CreateFont("TFASleekTiny", fontdata) + TFA.Fonts.SleekHeightTiny = GetFontHeight("TFASleekTiny") + + fontdata = {} + + fontdata.font = "Inter" + fontdata.extended = true + fontdata.weight = 500 + fontdata.size = ScaleH(64) + surface.CreateFont("TFA_INSPECTION_TITLE", fontdata) + TFA.Fonts.InspectionHeightTitle = GetFontHeight("TFA_INSPECTION_TITLE") + + fontdata.size = ScaleH(32) + surface.CreateFont("TFA_INSPECTION_DESCR", fontdata) + TFA.Fonts.InspectionHeightDescription = GetFontHeight("TFA_INSPECTION_DESCR") + + fontdata.size = ScaleH(24) + fontdata.weight = 400 + surface.CreateFont("TFA_INSPECTION_SMALL", fontdata) + TFA.Fonts.InspectionHeightSmall = GetFontHeight("TFA_INSPECTION_SMALL") + + fontdata = {} + fontdata.extended = true + fontdata.weight = 400 + + fontdata.font = "Inter" + fontdata.size = ScaleH(12) + surface.CreateFont("TFAAttachmentIconFont", fontdata) + fontdata.size = ScaleH(10) + surface.CreateFont("TFAAttachmentIconFontTiny", fontdata) + + fontdata.font = "Inter" + fontdata.weight = 500 + fontdata.size = ScaleH(24) + surface.CreateFont("TFAAttachmentTTHeader", fontdata) + + fontdata.font = "Inter" + fontdata.weight = 300 + fontdata.size = ScaleH(18) + surface.CreateFont("TFAAttachmentTTBody", fontdata) + + surface.CreateFont("TFASleekDebug", { font = "Roboto", size = 24, extended = true }) + TFA.Fonts.SleekHeightDebug = 24 + + hook.Run("TFA_FontsLoaded") +end + +CreateFonts() + +hook.Add("OnScreenSizeChanged", "TFA_Fonts_Regenerate", CreateFonts) +cvars.AddChangeCallback("cl_tfa_hud_scale", CreateFonts, "TFA_RecreateFonts") diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_hitmarker.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_hitmarker.lua new file mode 100644 index 0000000..8c04137 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_hitmarker.lua @@ -0,0 +1,94 @@ +local ScrW, ScrH = ScrW, ScrH + +local markers = {} + +local cl_drawhud = GetConVar("cl_drawhud") +local enabledcvar = GetConVar("cl_tfa_hud_hitmarker_enabled") +local solidtimecvar = GetConVar("cl_tfa_hud_hitmarker_solidtime") +local fadetimecvar = GetConVar("cl_tfa_hud_hitmarker_fadetime") +local scalecvar = GetConVar("cl_tfa_hud_hitmarker_scale") +local tricross_cvar = GetConVar("cl_tfa_hud_crosshair_triangular") + +local rcvar = GetConVar("cl_tfa_hud_hitmarker_color_r") +local gcvar = GetConVar("cl_tfa_hud_hitmarker_color_g") +local bcvar = GetConVar("cl_tfa_hud_hitmarker_color_b") +local acvar = GetConVar("cl_tfa_hud_hitmarker_color_a") + +net.Receive("tfaHitmarker", function() + if not enabledcvar:GetBool() then return end + + local marker = { + time = RealTime() + } + + table.insert(markers, marker) +end) + +net.Receive("tfaHitmarker3D", function() + if not enabledcvar:GetBool() then return end + + local marker = { + pos = net.ReadVector(), + time = RealTime() + } + + table.insert(markers, marker) +end) + +local mat_regular = Material("vgui/tfa_hitmarker.png", "smooth mips") +local mat_triang = Material("vgui/tfa_hitmarker_triang.png", "smooth mips") + +local cl_tfa_hud_crosshair_enable_custom = GetConVar("cl_tfa_hud_crosshair_enable_custom") + +hook.Add("HUDPaint", "tfaDrawHitmarker", function() + if not enabledcvar:GetBool() or not cl_drawhud:GetBool() then return end + + local solidtime = solidtimecvar:GetFloat() + local fadetime = math.max(fadetimecvar:GetFloat(), 0.001) + + local r = rcvar:GetFloat() + local g = gcvar:GetFloat() + local b = bcvar:GetFloat() + local a = acvar:GetFloat() + + local w, h = ScrW(), ScrH() + local sprh = math.floor((h / 1080) * 64 * scalecvar:GetFloat()) + local sprh2 = sprh / 2 + local mX, mY = w / 2, h / 2 + local ltime = RealTime() + + if cl_tfa_hud_crosshair_enable_custom:GetBool() and isnumber(TFA.LastCrosshairPosX) and isnumber(TFA.LastCrosshairPosY) then + local weapon = LocalPlayer():GetActiveWeapon() + + if IsValid(weapon) and weapon.IsTFAWeapon then + mX, mY = TFA.LastCrosshairPosX, TFA.LastCrosshairPosY + end + end + + for k, v in pairs(markers) do + if v.time then + local alpha = math.Clamp(v.time - ltime + solidtime + fadetime, 0, fadetime) / fadetime + + if alpha > 0 then + local x, y = mX, mY + local visible = true + + if v.pos then + local pos = v.pos:ToScreen() + x, y = pos.x, pos.y + visible = pos.visible + end + + if visible then + surface.SetDrawColor(r, g, b, a * alpha) + surface.SetMaterial(tricross_cvar:GetBool() and mat_triang or mat_regular) + surface.DrawTexturedRect(x - sprh2, y - sprh2, sprh, sprh) + end + else + markers[k] = nil + end + else + markers[k] = nil + end + end +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_inspection.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_inspection.lua new file mode 100644 index 0000000..f9a4e47 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_inspection.lua @@ -0,0 +1,76 @@ +if CLIENT then + local doblur = GetConVar("cl_tfa_inspection_bokeh") + local blurdist = GetConVar("cl_tfa_inspection_bokeh_radius") + local tfablurintensity = 0 + local blur_mat = Material("pp/bokehblur") + local tab = {} + tab["$pp_colour_addr"] = 0 + tab["$pp_colour_addg"] = 0 + tab["$pp_colour_addb"] = 0 + tab["$pp_colour_brightness"] = 0 + tab["$pp_colour_contrast"] = 1 + tab["$pp_colour_colour"] = 1 + tab["$pp_colour_mulr"] = 0 + tab["$pp_colour_mulg"] = 0 + tab["$pp_colour_mulb"] = 0 + + local function MyDrawBokehDOF() + render.UpdateScreenEffectTexture() + render.UpdateFullScreenDepthTexture() + blur_mat:SetTexture("$BASETEXTURE", render.GetScreenEffectTexture()) + blur_mat:SetTexture("$DEPTHTEXTURE", render.GetResolvedFullFrameDepth()) + blur_mat:SetFloat("$size", tfablurintensity * 6) + blur_mat:SetFloat("$focus", 0) + blur_mat:SetFloat("$focusradius", blurdist:GetFloat()) + render.SetMaterial(blur_mat) + render.DrawScreenQuad() + end + + local cv_dxlevel = GetConVar("mat_dxlevel") + + local function Render() + tfablurintensity = 0 + + if cv_dxlevel:GetInt() < 90 then return end + if TFA.DrawingRenderTarget then return end + + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or not wep.IsTFAWeapon then return end + + tfablurintensity = wep:GetInspectingProgress() + + if tfablurintensity > 0.01 then + if doblur and doblur:GetBool() then + MyDrawBokehDOF() + end + + tab["$pp_colour_brightness"] = -tfablurintensity * 0.02 + tab["$pp_colour_contrast"] = 1 - tfablurintensity * 0.1 + + DrawColorModify(tab) + end + end + + local function InitTFABlur() + hook.Add("PreDrawViewModels", "PreDrawViewModels_TFA_INSPECT", Render) + + local pp_bokeh = GetConVar( "pp_bokeh" ) + hook.Remove("NeedsDepthPass","NeedsDepthPass_Bokeh") + hook.Add("NeedsDepthPass", "aaaaaaaaaaaaaaaaaaNeedsDepthPass_TFA_Inspect", function() + if not ( doblur and doblur:GetBool() ) then return end + + if tfablurintensity > 0.01 or ( pp_bokeh and pp_bokeh:GetBool() ) then + DOFModeHack(true) + + return true + end + end) + end + + hook.Add("InitPostEntity","InitTFABlur",InitTFABlur) + + InitTFABlur() +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_materials.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_materials.lua new file mode 100644 index 0000000..e44a038 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_materials.lua @@ -0,0 +1,40 @@ +TFA_SCOPE_ACOG = { + scopetex = surface.GetTextureID("scope/gdcw_closedsight"), + reticletex = surface.GetTextureID("scope/gdcw_acogchevron"), + dottex = surface.GetTextureID("scope/gdcw_acogcross") +} + +TFA_SCOPE_MILDOT = { + scopetex = surface.GetTextureID("scope/gdcw_scopesight") +} + +TFA_SCOPE_SVD = { + scopetex = surface.GetTextureID("scope/gdcw_svdsight") +} + +TFA_SCOPE_PARABOLIC = { + scopetex = surface.GetTextureID("scope/gdcw_parabolicsight") +} + +TFA_SCOPE_ELCAN = { + scopetex = surface.GetTextureID("scope/gdcw_elcansight"), + reticletex = surface.GetTextureID("scope/gdcw_elcanreticle") +} + +TFA_SCOPE_GREENDUPLEX = { + scopetex = surface.GetTextureID("scope/gdcw_closedsight"), + reticletex = surface.GetTextureID("scope/gdcw_nvgilluminatedduplex") +} + +TFA_SCOPE_AIMPOINT = { + scopetex = surface.GetTextureID("scope/gdcw_closedsight"), + reticletex = surface.GetTextureID("scope/aimpoint") +} + +TFA_SCOPE_MATADOR = { + scopetex = surface.GetTextureID("scope/rocketscope") +} + +TFA_SCOPE_SCOPESCALE = 4 +TFA_SCOPE_RETICLESCALE = 1 +TFA_SCOPE_DOTSCALE = 1 diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_models.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_models.lua new file mode 100644 index 0000000..e56ba23 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_models.lua @@ -0,0 +1,61 @@ +TFA.ClientsideModels = TFA.ClientsideModels or {} + +timer.Create("TFA_UpdateClientsideModels", 0.1, 0, function() + local i = 1 + + while i <= #TFA.ClientsideModels do + local t = TFA.ClientsideModels[i] + + if not t then + table.remove(TFA.ClientsideModels, i) + elseif not IsValid(t.wep) then + t.mdl:Remove() + table.remove(TFA.ClientsideModels, i) + elseif IsValid(t.wep:GetOwner()) and t.wep:GetOwner().GetActiveWeapon and t.wep ~= t.wep:GetOwner():GetActiveWeapon() then + t.mdl:Remove() + table.remove(TFA.ClientsideModels, i) + elseif t.wep.IsHidden and t.wep:IsHidden() then + t.mdl:Remove() + table.remove(TFA.ClientsideModels, i) + else + i = i + 1 + end + end + + if #TFA.ClientsideModels == 0 then + timer.Stop("TFA_UpdateClientsideModels") + end +end) + +if #TFA.ClientsideModels == 0 then + timer.Stop("TFA_UpdateClientsideModels") +end + +function TFA.RegisterClientsideModel(cmdl, wepv) -- DEPRECATED + -- don't use please + -- pleaz + TFA.ClientsideModels[#TFA.ClientsideModels + 1] = { + ["mdl"] = cmdl, + ["wep"] = wepv + } + + timer.Start("TFA_UpdateClientsideModels") +end + +local function NotifyShouldTransmit(ent, notdormant) + if notdormant or not ent.IsTFAWeapon then return end + if ent:GetOwner() == LocalPlayer() then return end + + ent:CleanModels(ent:GetStatRaw("ViewModelElements", TFA.LatestDataVersion)) + ent:CleanModels(ent:GetStatRaw("WorldModelElements", TFA.LatestDataVersion)) +end + +local function EntityRemoved(ent) + if not ent.IsTFAWeapon then return end + + ent:CleanModels(ent:GetStatRaw("ViewModelElements", TFA.LatestDataVersion)) + ent:CleanModels(ent:GetStatRaw("WorldModelElements", TFA.LatestDataVersion)) +end + +hook.Add("NotifyShouldTransmit", "TFA_ClientsideModels", NotifyShouldTransmit) +hook.Add("EntityRemoved", "TFA_ClientsideModels", EntityRemoved) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_particles_lua.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_particles_lua.lua new file mode 100644 index 0000000..486ca85 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_particles_lua.lua @@ -0,0 +1,172 @@ +local vector_origin = Vector() + +TFA.Particles = TFA.Particles or {} +TFA.Particles.FlareParts = {} +TFA.Particles.VMAttachments = {} + +local VMAttachments = TFA.Particles.VMAttachments +local FlareParts = TFA.Particles.FlareParts + +local ply, vm, wep + +local IsValid_ = FindMetaTable("Entity").IsValid +local GetModel = FindMetaTable("Entity").GetModel +local lastVMModel, lastVMAtts + +local lastRequired = 0 +local RealTime = RealTime +local FrameTime = FrameTime +local LocalPlayer = LocalPlayer +local ipairs = ipairs +local istable = istable +local isfunction = isfunction +local WorldToLocal = WorldToLocal +local table = table + +local thinkAttachments = {} +local slowThinkers = 0 + +hook.Add("PreDrawEffects", "TFAMuzzleUpdate", function() + if lastRequired < RealTime() then return end + + if not ply then + ply = LocalPlayer() + end + + if not IsValid_(vm) then + vm = ply:GetViewModel() + if not IsValid_(vm) then return end + end + + local vmmodel = GetModel(vm) + + if vmmodel ~= lastVMModel then + lastVMModel = vmmodel + lastVMAtts = vm:GetAttachments() + end + + if not lastVMAtts then return end + + if slowThinkers == 0 then + for i in pairs(thinkAttachments) do + VMAttachments[i] = vm:GetAttachment(i) + end + else + for i = 1, #lastVMAtts do + VMAttachments[i] = vm:GetAttachment(i) + end + end + + for _, v in ipairs(FlareParts) do + if v and v.ThinkFunc then + v:ThinkFunc() + end + end +end) + +function TFA.Particles.RegisterParticleThink(particle, partfunc) + if not particle or not isfunction(partfunc) then return end + + if not ply then + ply = LocalPlayer() + end + + if not IsValid_(vm) then + vm = ply:GetViewModel() + if not IsValid_(vm) then return end + end + + particle.ThinkFunc = partfunc + + if IsValid(particle.FollowEnt) and particle.Att then + local angpos = particle.FollowEnt:GetAttachment(particle.Att) + + if angpos then + particle.OffPos = WorldToLocal(particle:GetPos(), particle:GetAngles(), angpos.Pos, angpos.Ang) + end + end + + local att = particle.Att + + local isFast = partfunc == TFA.Particles.FollowMuzzle and att ~= nil + local isVM = particle.FollowEnt == vm + + if isFast then + if isVM then + thinkAttachments[att] = (thinkAttachments[att] or 0) + 1 + end + else + slowThinkers = slowThinkers + 1 + end + + table.insert(FlareParts, particle) + + timer.Simple(particle:GetDieTime(), function() + if particle then + table.RemoveByValue(FlareParts, particle) + end + + if not isFast then + slowThinkers = slowThinkers - 1 + elseif isVM and att then + thinkAttachments[att] = thinkAttachments[att] - 1 + if thinkAttachments[att] <= 0 then thinkAttachments[att] = nil end + end + end) + + lastRequired = RealTime() + 0.5 +end + +function TFA.Particles.FollowMuzzle(self, first) + if lastRequired < RealTime() then + lastRequired = RealTime() + 0.5 + return + end + + lastRequired = RealTime() + 0.5 + + if self.isfirst == nil then + self.isfirst = false + first = true + end + + if not IsValid_(ply) or not IsValid_(vm) then return end + wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.IsCurrentlyScoped and wep:IsCurrentlyScoped() then return end + + if not IsValid(self.FollowEnt) then return end + local owent = self.FollowEnt:GetOwner() or self.FollowEnt + if not IsValid(owent) then return end + + local firvel + + if first then + firvel = owent:GetVelocity() * FrameTime() * 1.1 + else + firvel = vector_origin + end + + if not self.Att or not self.OffPos then return end + + if self.FollowEnt == vm then + local angpos = VMAttachments[self.Att] + + if angpos then + local tmppos = LocalToWorld(self.OffPos, self:GetAngles(), angpos.Pos, angpos.Ang) + local npos = tmppos + self:GetVelocity() * FrameTime() + self.OffPos = WorldToLocal(npos + firvel, self:GetAngles(), angpos.Pos, angpos.Ang) + self:SetPos(npos + firvel) + end + + return + end + + local angpos = self.FollowEnt:GetAttachment(self.Att) + + if angpos then + local tmppos = LocalToWorld(self.OffPos, self:GetAngles(), angpos.Pos, angpos.Ang) + local npos = tmppos + self:GetVelocity() * FrameTime() + self.OffPos = WorldToLocal(npos + firvel * 0.5, self:GetAngles(), angpos.Pos, angpos.Ang) + self:SetPos(npos + firvel) + end +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_projtex.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_projtex.lua new file mode 100644 index 0000000..e3c57d3 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_projtex.lua @@ -0,0 +1,35 @@ +local ply = LocalPlayer() +local LocalPlayer = LocalPlayer + +hook.Add("PreRender", "TFACleanupProjectedTextures", function() + if not IsValid(ply) then + ply = LocalPlayer() + if not IsValid(ply) then return end + end + + local wep = ply:GetActiveWeapon() + + if not IsValid(wep) or not wep.IsTFAWeapon then + if IsValid(ply.TFAFlashlightGun) then + ply.TFAFlashlightGun:Remove() + end + + if IsValid(ply.TFALaserDot) then + ply.TFALaserDot:Remove() + end + end +end) + +hook.Add("PrePlayerDraw", "TFACleanupProjectedTextures", function(plyv) + local wep = plyv:GetActiveWeapon() + + if not IsValid(wep) or not wep.IsTFAWeapon then + if IsValid(plyv.TFAFlashlightGun) then + plyv.TFAFlashlightGun:Remove() + end + + if IsValid(plyv.TFALaserDot) then + plyv.TFALaserDot:Remove() + end + end +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_rendertarget.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_rendertarget.lua new file mode 100644 index 0000000..32685b1 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_rendertarget.lua @@ -0,0 +1,144 @@ +TFA.DrawingRenderTarget = false + +local props = { + ["$translucent"] = 1 +} + +local TFA_RTMat = CreateMaterial("tfa_rtmaterial", "UnLitGeneric", props) --Material("models/weapons/TFA/shared/optic") +local TFA_RTScreen, TFA_RTScreenO = {}, {} +local tgt +local old_bt +local ply, vm, wep +local w, h +local qualitySizes + +local function callFunc() + if wep.RTCode then + wep:RTCode(TFA_RTMat, w, h) + end + + if wep:GetStatL("RTDrawEnabled") then + wep:CallAttFunc("RTCode", TFA_RTMat, w, h) + end +end + +hook.Add("OnScreenSizeChanged", "TFA_rendertargets", function() + qualitySizes = nil + TFA_RTScreen, TFA_RTScreenO = {}, {} +end) + +local function TFARenderScreen() + ply = GetViewEntity() + + if not IsValid(ply) or not ply:IsPlayer() then + ply = LocalPlayer() + + return + end + + wep = ply:GetActiveWeapon() + if not IsValid(wep) or not wep.IsTFAWeapon then return end + + if not IsValid(vm) then + if not wep:VMIV() then return end + + vm = wep.OwnerViewModel + end + + if not wep.MaterialCached then + wep.MaterialCached = true + wep.MaterialCached_V = nil + wep.MaterialCached_W = nil + end + + local skinStat = wep:GetStatL("Skin") + if isnumber(skinStat) then + if vm:GetSkin() ~= skinStat then + vm:SetSkin(skinStat) + end + end + + if wep:GetStatL("MaterialTable_V") and not wep.MaterialCached_V then + wep.MaterialCached_V = {} + vm:SetSubMaterial() + local collectedKeys = table.GetKeys(wep:GetStatL("MaterialTable_V")) + table.Merge(collectedKeys, table.GetKeys(wep:GetStatL("MaterialTable"))) + + for _, k in pairs(collectedKeys) do + if k ~= "BaseClass" then + local v = wep:GetStatL("MaterialTable_V")[k] + + if not wep.MaterialCached_V[k] then + vm:SetSubMaterial(k - 1, v) + wep.MaterialCached_V[k] = true + end + end + end + end + + if not (wep:GetStatL("RTDrawEnabled") or wep.RTCode ~= nil) then return end + w, h = ScrW(), ScrH() + + if not qualitySizes then + qualitySizes = { + [0] = h, + [1] = math.Round(h * 0.5), + [2] = math.Round(h * 0.25), + [3] = math.Round(h * 0.125), + } + end + + local quality = TFA.RTQuality() + + if wep:GetStatL("RTOpaque") then + tgt = TFA_RTScreenO[quality] + + if not tgt then + local size = qualitySizes[quality] or qualitySizes[0] + tgt = GetRenderTargetEx("TFA_RT_ScreenO_" .. size, size, size, RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_SHARED, 0, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGB888) + TFA_RTScreenO[quality] = tgt + end + else + tgt = TFA_RTScreen[quality] + + if not tgt then + local size = qualitySizes[quality] or qualitySizes[0] + tgt = GetRenderTargetEx("TFA_RT_Screen_" .. size, size, size, RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_SHARED, 0, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGBA8888) + TFA_RTScreen[quality] = tgt + end + end + + TFA.LastRTUpdate = CurTime() + 0.01 + + render.PushRenderTarget(tgt) + render.Clear(0, 0, 0, 255, true, true) + + TFA.DrawingRenderTarget = true + render.CullMode(MATERIAL_CULLMODE_CCW) + ProtectedCall(callFunc) + TFA.DrawingRenderTarget = false + + render.SetScissorRect(0, 0, 0, 0, false) + render.PopRenderTarget() + + if old_bt ~= tgt then + TFA_RTMat:SetTexture("$basetexture", tgt) + old_bt = tgt + end + + if wep:GetStatL("RTMaterialOverride", -1) >= 0 then + vm:SetSubMaterial(wep:GetStatL("RTMaterialOverride"), "!tfa_rtmaterial") + end +end + +hook.Remove("PostRender", "TFASCREENS") + +hook.Add("PreRender", "TFASCREENS", function() + if not TFA.RT_DRAWING then + TFA.RT_DRAWING = true + TFARenderScreen() + TFA.RT_DRAWING = false + end +end) + +TFA.RT_DRAWING = false diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_rtbgblur.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_rtbgblur.lua new file mode 100644 index 0000000..cab38c6 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_rtbgblur.lua @@ -0,0 +1,80 @@ +local FT = FrameTime + +local tfablurintensity = 0 + +local cv_3dscopes = GetConVar("cl_tfa_3dscope") + +local cv_mode = CreateClientConVar("cl_tfa_fx_rtscopeblur_mode", "1", true, false) +local funcs = {} + +local cv_blur_passes = CreateClientConVar("cl_tfa_fx_rtscopeblur_passes", "3", true, false) +local cv_blur_intensity = CreateClientConVar("cl_tfa_fx_rtscopeblur_intensity", "4", true, false) +local blurTex = Material("pp/blurscreen") +funcs[1] = function() + surface.SetDrawColor(color_white) + render.SetMaterial(blurTex) + local passes = cv_blur_passes:GetInt() + + for _ = 1, passes do + render.UpdateScreenEffectTexture() + + blurTex:SetFloat("$blur", tfablurintensity * cv_blur_intensity:GetFloat() / math.sqrt(passes) ) + blurTex:Recompute() + + render.DrawScreenQuad() + end +end + +local blur_mat = Material("pp/bokehblur") +funcs[2] = function() + render.UpdateScreenEffectTexture() + render.UpdateFullScreenDepthTexture() + + blur_mat:SetTexture("$BASETEXTURE", render.GetScreenEffectTexture()) + blur_mat:SetTexture("$DEPTHTEXTURE", render.GetResolvedFullFrameDepth()) + + blur_mat:SetFloat("$size", tfablurintensity * cv_blur_intensity:GetFloat() * 1.5 ) + blur_mat:SetFloat("$focus", 0) + blur_mat:SetFloat("$focusradius", 0.25) + + render.SetMaterial(blur_mat) + render.DrawScreenQuad() +end + +hook.Add("PostDrawTranslucentRenderables", "tfa_draw_rt_blur", function() + if TFA.DrawingRenderTarget then return end + + if not cv_3dscopes:GetBool() then return end + + local mode = cv_mode:GetInt() + if not isfunction(funcs[mode]) then return end + + local ply = LocalPlayer() + if not IsValid(ply) or ply:ShouldDrawLocalPlayer() then return end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or not wep.IsTFAWeapon or not wep.GetStat then return end + if not wep:GetStatL("RTBGBlur") then return end + if not wep:GetStatL("RTDrawEnabled") and not wep:GetStatL("RTMaterialOverride") and not wep.RTCode then return end + + if wep.GLDeployed and wep:GLDeployed() then + tfablurintensity = Lerp(FT() * 12.5, tfablurintensity, 0) + else + local progress = math.Clamp(wep.CLIronSightsProgress or 0, 0, 1) + tfablurintensity = Lerp(FT() * 25, tfablurintensity, progress) + end + + if tfablurintensity > 0.05 then + funcs[mode]() + end +end) + +hook.Add("NeedsDepthPass", "aaaaaaaaaaaaaaaaaaNeedsDepthPass_TJA_IronSight", function() + if tfablurintensity > 0.05 and cv_mode:GetInt() == 2 then + if not cv_3dscopes:GetBool() then return end + + DOFModeHack(true) + + return true + end +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_settingsmenu.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_settingsmenu.lua new file mode 100644 index 0000000..6ceb148 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_settingsmenu.lua @@ -0,0 +1,116 @@ +local IsSinglePlayer = game.SinglePlayer() + +function TFA.NumSliderNet(_parent, label, convar, min, max, decimals, ...) + local gconvar = assert(GetConVar(convar), "Unknown ConVar: " .. convar .. "!") + local newpanel + + if IsSinglePlayer then + newpanel = _parent:NumSlider(label, convar, min, max, decimals, ...) + else + newpanel = _parent:NumSlider(label, nil, min, max, decimals, ...) + end + + decimals = decimals or 0 + local sf = "%." .. decimals .. "f" + + if not IsSinglePlayer then + local ignore = false + + newpanel.Think = function(_self) + if _self._wait_for_update and _self._wait_for_update > RealTime() then return end + local float = gconvar:GetFloat() + + if _self:GetValue() ~= float then + ignore = true + _self:SetValue(float) + ignore = false + end + end + + newpanel.OnValueChanged = function(_self, _newval) + if ignore then return end + + if not LocalPlayer():IsAdmin() then return end + _self._wait_for_update = RealTime() + 1 + + timer.Create("tfa_vgui_" .. convar, 0.5, 1, function() + if not LocalPlayer():IsAdmin() then return end + + net.Start("TFA_SetServerCommand") + net.WriteString(convar) + net.WriteString(string.format(sf, _newval)) + net.SendToServer() + end) + end + end + + return newpanel +end + +function TFA.CheckBoxNet(_parent, label, convar, ...) + local gconvar = assert(GetConVar(convar), "Unknown ConVar: " .. convar .. "!") + local newpanel + + if IsSinglePlayer then + newpanel = _parent:CheckBox(label, convar, ...) + else + newpanel = _parent:CheckBox(label, nil, ...) + end + + if not IsSinglePlayer then + if not IsValid(newpanel.Button) then return newpanel end + + newpanel.Button.Think = function(_self) + local bool = gconvar:GetBool() + + if _self:GetChecked() ~= bool then + _self:SetChecked(bool) + end + end + + newpanel.OnChange = function(_self, _bVal) + if not LocalPlayer():IsAdmin() then return end + if _bVal == gconvar:GetBool() then return end + + net.Start("TFA_SetServerCommand") + net.WriteString(convar) + net.WriteString(_bVal and "1" or "0") + net.SendToServer() + end + end + + return newpanel +end + +function TFA.ComboBoxNet(_parent, label, convar, ...) + local gconvar = assert(GetConVar(convar), "Unknown ConVar: " .. convar .. "!") + local combobox, leftpanel + + if IsSinglePlayer then + combobox, leftpanel = _parent:ComboBox(label, convar, ...) + else + combobox, leftpanel = _parent:ComboBox(label, nil, ...) + end + + if not IsSinglePlayer then + combobox.Think = function(_self) + local value = gconvar:GetString() + + if _self:GetValue() ~= value then + _self:SetValue(value) + end + end + + combobox.OnSelect = function(_self, _index, _value, _data) + if not LocalPlayer():IsAdmin() then return end + local _newval = tostring(_data or _value) + + net.Start("TFA_SetServerCommand") + net.WriteString(convar) + net.WriteString(_newval) + net.SendToServer() + end + end + + return combobox, leftpanel +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_stencilsights.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_stencilsights.lua new file mode 100644 index 0000000..1b7c51c --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_stencilsights.lua @@ -0,0 +1,348 @@ +-- stencil functions +local useStencils = render.SupportsPixelShaders_2_0() and render.SupportsVertexShaders_2_0() + +local function defineCanvas(ref) + render.UpdateScreenEffectTexture() + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_REPLACE) + render.SetStencilWriteMask(255) + render.SetStencilTestMask(255) + render.SetStencilReferenceValue(ref or 54) +end + +local function drawOn() + render.SetStencilCompareFunction(STENCIL_EQUAL) +end + +local function stopCanvas() + render.SetStencilEnable(false) +end + +-- main draw functions +local CachedMaterials = {} + +local DrawFunctions = {} + +do -- Flat reticle, stays at center or moves with recoil + local function ScreenScaleH(num) + return num * (ScrH() / 480) + end + + DrawFunctions[TFA.Enum.RETICLE_FLAT] = function(vm, ply, wep, SightElementTable) + local ReticleMaterial = wep:GetStat("StencilSight_ReticleMaterial") + if not ReticleMaterial then return end + + if type(ReticleMaterial) == "string" then + CachedMaterials[ReticleMaterial] = CachedMaterials[ReticleMaterial] or Material(ReticleMaterial, "noclamp nocull smooth") + ReticleMaterial = CachedMaterials[ReticleMaterial] + end + + local ReticleSize = wep:GetStat("StencilSight_ReticleSize") + if not ReticleSize then return end + + if wep:GetStat("StencilSight_ScaleReticleByScreenHeight", true) then + ReticleSize = ScreenScaleH(ReticleSize) + end + + if wep:GetStat("StencilSight_ScaleReticleByProgress", true) then + ReticleSize = ReticleSize * wep.IronSightsProgress + end + + local w, h = ScrW(), ScrH() + + local x, y = w * .5, h * .5 + if wep:GetStat("StencilSight_FollowRecoil", true) then + x, y = TFA.LastCrosshairPosX or x, TFA.LastCrosshairPosY or y + end + + local TargetColor = wep:GetStat("StencilSight_ReticleTint", color_white) + + if wep:GetStat("StencilSight_ReticleTintBySightColor", false) and IsValid(wep:GetOwner()) then + local Owner = wep:GetOwner() + + local _GetNWVector = Owner.GetNW2Vector or Owner.GetNWVector + + local ColorVec = _GetNWVector(Owner, "TFAReticuleColor") + + if ColorVec then + TargetColor = Color(ColorVec.x, ColorVec.y, ColorVec.z) + end + end + + if wep:GetStat("StencilSight_FadeReticleByProgress", false) then + TargetColor = ColorAlpha(TargetColor, wep.IronSightsProgress * 255) + end + + render.DepthRange(0.0, 0.1) + cam.Start2D(0, 0, w, h) + surface.SetMaterial(ReticleMaterial) + surface.SetDrawColor(TargetColor) + surface.DrawTexturedRect(x - ReticleSize * .5, y - ReticleSize * .5, ReticleSize, ReticleSize) + cam.End2D() + if not wep.UseHands then + render.DepthRange(0.0, 1.0) + end + end +end + +do -- Model reticle, for when you don't have an attach point + if IsValid(TFA.SightReticleEnt) then + TFA.SightReticleEnt:Remove() + TFA.SightReticleEnt = nil + end + + TFA.SightReticleEnt = ClientsideModel("models/error.mdl", RENDERGROUP_VIEWMODEL) + TFA.SightReticleEnt:SetNoDraw(true) + + local SightReticleEnt = TFA.SightReticleEnt + + DrawFunctions[TFA.Enum.RETICLE_MODEL] = function(vm, ply, wep, SightElementTable) + if not SightElementTable.reticle then return end + + local SightElementModel = SightElementTable.curmodel + + SightReticleEnt:SetModel(SightElementTable.reticle) + if SightReticleEnt:GetModel() == "models/error.mdl" then return end + + local matrix = Matrix() + matrix:Scale(SightElementTable.size) + SightReticleEnt:EnableMatrix("RenderMultiply", matrix) + + if SightReticleEnt:GetParent() ~= SightElementModel then + SightReticleEnt:SetParent(SightElementModel) + SightReticleEnt:SetPos(SightElementModel:GetPos()) + SightReticleEnt:SetAngles(SightElementModel:GetAngles()) + + if not SightReticleEnt:IsEffectActive(EF_BONEMERGE) then + SightReticleEnt:AddEffects(EF_BONEMERGE) + SightReticleEnt:AddEffects(EF_BONEMERGE_FASTCULL) + end + end + + if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CW) end + if wep:GetStat("StencilSight_FadeReticleByProgress", false) then + local oldBlend = render.GetBlend() + + render.SetBlend(wep.IronSightsProgress) + SightReticleEnt:DrawModel() + render.SetBlend(oldBlend) + else + SightReticleEnt:DrawModel() + end + if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CCW) end + + if wep:GetStat("StencilSight_EnableQuad") and bit.band(wep:GetStat("StencilSight_ReticleType")) ~= TFA.Enum.RETICLE_QUAD then + DrawFunctions[TFA.Enum.RETICLE_QUAD](vm, ply, wep, SightElementTable) + end + end +end + +do -- Quad/Attach reticle, TFA INS2 method + local function GetTargetPosition(wep, SightElementTable) + local TargetEntity = SightElementTable.curmodel + if not IsValid(TargetEntity) then return end + + local Type = wep:GetStat("StencilSight_PositionType", TFA.Enum.SIGHTSPOS_ATTACH) + + local pos, ang + + if Type == TFA.Enum.SIGHTSPOS_ATTACH then + local AttachmentID = wep:GetStat("StencilSight_ReticleAttachment") + if not AttachmentID then return end + + if type(AttachmentID) == "string" then + AttachmentID = TargetEntity:LookupAttachment(AttachmentID) + end + + if not AttachmentID or AttachmentID <= 0 then return end + + local Attachment = TargetEntity:GetAttachment(AttachmentID) + if not Attachment.Pos or not Attachment.Ang then return end + + pos, ang = Attachment.Pos, Attachment.Ang + elseif Type == TFA.Enum.SIGHTSPOS_BONE then + local BoneID = wep:GetStat("StencilSight_ReticleBone") + + if type(BoneID) == "string" then + BoneID = TargetEntity:LookupBone(BoneID) + end + + if not BoneID or BoneID < 0 then return end + + pos, ang = TargetEntity:GetBonePosition(BoneID) + + if pos == TargetEntity:GetPos() then + pos = TargetEntity:GetBoneMatrix(BoneID):GetTranslation() + ang = TargetEntity:GetBoneMatrix(BoneID):GetAngles() + end + else + return + end + + local OffsetPos = wep:GetStat("StencilSight_ReticleOffsetPos") + if OffsetPos then + pos = pos + ang:Right() * OffsetPos.x + ang:Forward() * OffsetPos.y + ang:Up() * OffsetPos.z + end + + local OffsetAng = wep:GetStat("StencilSight_ReticleOffsetAng") + if OffsetAng then + ang:RotateAroundAxis(ang:Right(), OffsetAng.p) + ang:RotateAroundAxis(ang:Up(), OffsetAng.y) + ang:RotateAroundAxis(ang:Forward(), OffsetAng.r) + end + + return pos, ang + end + + DrawFunctions[TFA.Enum.RETICLE_QUAD] = function(vm, ply, wep, SightElementTable) + local ReticleMaterial = wep:GetStat("StencilSight_ReticleMaterial") + if not ReticleMaterial then return end + + if type(ReticleMaterial) == "string" then + CachedMaterials[ReticleMaterial] = CachedMaterials[ReticleMaterial] or Material(ReticleMaterial, "noclamp nocull smooth") + ReticleMaterial = CachedMaterials[ReticleMaterial] + end + + local ReticleSize = wep:GetStat("StencilSight_ReticleSize") + if not ReticleSize then return end + + if wep:GetStat("StencilSight_ScaleReticleByProgress", false) then + ReticleSize = ReticleSize * wep.IronSightsProgress + end + + local TargetColor = wep:GetStat("StencilSight_ReticleTint", color_white) + + if wep:GetStat("StencilSight_ReticleTintBySightColor", false) and IsValid(wep:GetOwner()) then + local Owner = wep:GetOwner() + + local _GetNWVector = Owner.GetNW2Vector or Owner.GetNWVector + + local ColorVec = _GetNWVector(Owner, "TFAReticuleColor") + + if ColorVec then + TargetColor = Color(ColorVec.x, ColorVec.y, ColorVec.z) + end + end + + if wep:GetStat("StencilSight_FadeReticleByProgress", false) then + TargetColor = ColorAlpha(TargetColor, wep.IronSightsProgress * 255) + end + + local p, a = GetTargetPosition(wep, SightElementTable) + if not p or not a then return end + + render.OverrideDepthEnable(true, true) + + render.SetMaterial(ReticleMaterial) + render.DrawQuadEasy(p, a:Forward() * -1, ReticleSize, ReticleSize, TargetColor, 180 + a.r * (wep.ViewModelFlip and 1 or -1)) + + render.OverrideDepthEnable(false, false) + end +end + +-- hook logic +if IsValid(TFA.SightMaskEnt) then + TFA.SightMaskEnt:Remove() + TFA.SightMaskEnt = nil +end + +TFA.SightMaskEnt = ClientsideModel("models/error.mdl", RENDERGROUP_VIEWMODEL) +TFA.SightMaskEnt:SetNoDraw(true) + +local SightMaskEnt = TFA.SightMaskEnt + +local function DrawSight(vm, ply, wep) + if not IsValid(wep) or not wep.IsTFAWeapon then return end + + local shouldDraw = hook.Run("TFA_ShouldDrawStencilSight", wep) + if shouldDraw == false then return end + + local wep2 = wep:GetTable() + + if wep2.TFA_IsDrawingStencilSights then return end + wep2.TFA_IsDrawingStencilSights = true + + if not wep2.GetStat(wep, "StencilSight") then wep2.TFA_IsDrawingStencilSights = false return end + if wep2.IronSightsProgress < wep2.GetStat(wep, "StencilSight_MinPercent", 0.05) then wep2.TFA_IsDrawingStencilSights = false return end + + local SightElementName = wep2.GetStat(wep, "StencilSight_VElement") + if not SightElementName or not wep2.GetStat(wep, "VElements." .. SightElementName .. ".active") then wep2.TFA_IsDrawingStencilSights = false return end + + local SightElementTable = wep2.VElements[SightElementName] + if not SightElementTable then wep2.TFA_IsDrawingStencilSights = false return end + + local SightElementModel = SightElementTable.curmodel + if not IsValid(SightElementModel) then wep2.TFA_IsDrawingStencilSights = false return end + + if useStencils then + defineCanvas() + + local SightMaskModel = SightElementModel + + if wep2.GetStat(wep, "StencilSight_UseMask", false) and SightElementTable.mask then + SightMaskEnt:SetModel(SightElementTable.mask) + + if SightMaskEnt:GetModel() ~= "models/error.mdl" then + SightMaskModel = SightMaskEnt + + local matrix = Matrix() + matrix:Scale(SightElementTable.size) + SightMaskEnt:EnableMatrix("RenderMultiply", matrix) + + if SightMaskEnt:GetParent() ~= SightElementModel then + SightMaskEnt:SetParent(SightElementModel) + SightMaskEnt:SetPos(SightElementModel:GetPos()) + SightMaskEnt:SetAngles(SightElementModel:GetAngles()) + + if not SightMaskEnt:IsEffectActive(EF_BONEMERGE) then + SightMaskEnt:AddEffects(EF_BONEMERGE) + SightMaskEnt:AddEffects(EF_BONEMERGE_FASTCULL) + end + end + end + end + + if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CW) end + local oldBlend = render.GetBlend() + render.SetBlend(0) + SightMaskModel:DrawModel() + render.SetBlend(oldBlend) + if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CCW) end + + drawOn() + end + + local retValPre = wep2.CallAttFunc(wep, "PreDrawStencilSight", vm, ply, SightElementTable) + if retValPre ~= true then + retValPre = wep2.PreDrawStencilSight(wep, vm, ply, SightElementTable) or retValPre + end + + if retValPre ~= false then + local funcType = wep2.GetStat(wep, "StencilSight_ReticleType", TFA.Enum.RETICLE_FLAT) + + for _, retType in ipairs(TFA.Enum.RETICLE_DRAW_ORDER) do + if bit.band(funcType, retType) == retType and DrawFunctions[retType] then + ProtectedCall(function() + DrawFunctions[retType](vm, ply, wep, SightElementTable) + end) + end + end + + local retValPost = wep2.CallAttFunc(wep, "PostDrawStencilSight", vm, ply, SightElementTable) + if retValPost ~= true then + wep2.PostDrawStencilSight(wep, vm, ply, SightElementTable) + end + end + + if useStencils then + stopCanvas() + end + + wep2.TFA_IsDrawingStencilSights = false +end + +hook.Add("PostDrawViewModel", "TFA_DrawStencilSight", DrawSight) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_subcategories.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_subcategories.lua new file mode 100644 index 0000000..d023727 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_subcategories.lua @@ -0,0 +1,84 @@ +local cv_enable = CreateClientConVar("cl_tfa_subcategories_enabled", "1", true, false, "Enable spawnmenu subcategories? (Update spawnmenu with spawnmenu_reload command after changing this!)") +local cv_autoassign = CreateClientConVar("cl_tfa_subcategories_auto", "0", true, false, "Attempt to auto-assign missing subcategories based on weapon's type?") + +local function PopulateWeapons(pnlContent, tree, browseNode) + if not cv_enable:GetBool() then return end + + local cats, subs = {}, {} + + for class, _wep in pairs(list.Get("Weapon") or {}) do + if not _wep.Spawnable then continue end + + local cat = _wep.Category or "Other2" + cats[cat] = cats[cat] or {} + + if not weapons.IsBasedOn(class, "tfa_gun_base") then + table.insert(cats[cat], _wep) + + continue + end + + local wep = weapons.Get(class) + local sub = wep.SubCategory or (cv_autoassign:GetBool() and wep:GetType()) + if not sub or sub == "" then + table.insert(cats[cat], wep) + + continue + end + + subs[cat] = subs[cat] or {} + subs[cat][sub] = subs[cat][sub] or {} + + table.insert(subs[cat][sub], wep) + end + + local root = tree:Root() + if not IsValid(root) then return end + + for _, node in ipairs(root:GetChildNodes()) do + local name = node:GetText() + if not name or not subs[name] then continue end + + node.DoPopulate = function(self) + if self.PropPanel then return end + + self.PropPanel = vgui.Create("ContentContainer", pnlContent) + self.PropPanel:SetVisible(false) + self.PropPanel:SetTriggerSpawnlistChange(false) + + for sname, subcat in SortedPairs(subs[name]) do + spawnmenu.CreateContentIcon("header", self.PropPanel, {text = sname}) + + for _, ent in SortedPairsByMemberValue(subcat, "PrintName") do + spawnmenu.CreateContentIcon(ent.ScriptedEntityType or "weapon", self.PropPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = ent.IconOverride or "entities/" .. ent.ClassName .. ".png", + admin = ent.AdminOnly + }) + end + end + + if cats[name] and #cats[name] > 0 then + spawnmenu.CreateContentIcon("header", self.PropPanel, {text = "Other"}) + + for _, ent in SortedPairsByMemberValue(cats[name], "PrintName") do + spawnmenu.CreateContentIcon(ent.ScriptedEntityType or "weapon", self.PropPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = ent.IconOverride or "entities/" .. ent.ClassName .. ".png", + admin = ent.AdminOnly + }) + end + end + end + end +end + +hook.Add("PopulateWeapons", "AddTFAWeaponContent", function(pnlContent, tree, browseNode) + timer.Simple(0, function() + PopulateWeapons(pnlContent, tree, browseNode) + end) +end, 1) + +TFA.BASE_LOAD_COMPLETE = true diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_vgui.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_vgui.lua new file mode 100644 index 0000000..3b4acbb --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_vgui.lua @@ -0,0 +1,1044 @@ +local DLIB_IS_PRESENT = DLib ~= nil + +local function AddColorMixer(panel, label, cvar_r, cvar_g, cvar_b, cvar_a) + local colorMixer = vgui.Create(DLIB_IS_PRESENT and "DLibColorMixer" or "DColorMixer") + + if DLIB_IS_PRESENT and colorMixer.EnableTallLayout then + colorMixer:EnableTallLayout() + end + + if label then + if colorMixer.SetLabel then + colorMixer:SetLabel(label) + else + panel:Help(label) + end + end + + colorMixer:SetConVarR(cvar_r) + colorMixer:SetConVarG(cvar_g) + colorMixer:SetConVarB(cvar_b) + if cvar_a then + colorMixer:SetConVarA(cvar_a) + else + colorMixer:SetAlphaBar(false) + end + + panel:AddItem(colorMixer) + + return colorMixer +end +TFA.AddColorMixer = AddColorMixer + +local function AddFoldedColorMixer(panel, label, cvar_r, cvar_g, cvar_b, cvar_a) + local form = vgui.Create("DForm", panel) + form:SetName(label) + panel:AddItem(form) + + return AddColorMixer(form, "", cvar_r, cvar_g, cvar_b, cvar_a), form +end +TFA.AddFoldedColorMixer = AddFoldedColorMixer + +local function tfaOptionServer(panel) + --Here are whatever default categories you want. + local tfaOptionSV = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_server" + } + + tfaOptionSV.Options["#preset.default"] = { + sv_tfa_ironsights_enabled = "1", + sv_tfa_sprint_enabled = "1", + sv_tfa_weapon_strip = "0", + sv_tfa_allow_dryfire = "1", + sv_tfa_damage_multiplier = "1", + sv_tfa_damage_multiplier_npc = "1", + sv_tfa_damage_mult_min = "0.95", + sv_tfa_damage_mult_max = "1.05", + sv_tfa_default_clip = "-1", + sv_tfa_arrow_lifetime = "30", + sv_tfa_force_multiplier = "1", + sv_tfa_bullet_penetration_power_mul = "1", + sv_tfa_dynamicaccuracy = "1", + sv_tfa_range_modifier = "0.5", + sv_tfa_spread_multiplier = "1", + sv_tfa_bullet_penetration = "1", + sv_tfa_bullet_ricochet = "0", + sv_tfa_bullet_doordestruction = "1", + sv_tfa_melee_doordestruction = "1", + sv_tfa_bullet_randomseed = "0", + sv_tfa_reloads_legacy = "0", + sv_tfa_fixed_crosshair = "0", + sv_tfa_crosshair_showplayer = "1", + sv_tfa_crosshair_showplayerteam = engine.ActiveGamemode() == "terrortown" and "0" or "1", + sv_tfa_reloads_enabled = "1", + sv_tfa_cmenu = "1", + sv_tfa_penetration_hardlimit = "100", + sv_tfa_jamming = "1", + sv_tfa_jamming_mult = "1", + sv_tfa_jamming_factor = "1", + sv_tfa_jamming_factor_inc = "1", + sv_tfa_door_respawn = "-1", + sv_tfa_attachments_enabled = "1", + sv_tfa_npc_randomize_atts = "1", + sv_tfa_fov_sprintmod = "1", + sv_tfa_recoil_multiplier = "1", + sv_tfa_knockback_multiplier = "1", + sv_tfa_first_draw_anim_enabled = "1", + } + + tfaOptionSV.CVars = table.GetKeys(tfaOptionSV.Options["#preset.default"]) + + panel:AddControl("ComboBox", tfaOptionSV) + + --These are the panel controls. Adding these means that you don't have to go into the console. + TFA.CheckBoxNet(panel, "#tfa.svsettings.dryfire", "sv_tfa_allow_dryfire") + TFA.CheckBoxNet(panel, "#tfa.svsettings.dynaccuracy", "sv_tfa_dynamicaccuracy") + TFA.CheckBoxNet(panel, "#tfa.svsettings.stripempty", "sv_tfa_weapon_strip") + TFA.CheckBoxNet(panel, "#tfa.svsettings.ironsight", "sv_tfa_ironsights_enabled") + TFA.CheckBoxNet(panel, "#tfa.svsettings.sprint", "sv_tfa_sprint_enabled") + TFA.CheckBoxNet(panel, "#tfa.svsettings.attachments", "sv_tfa_attachments_enabled") + TFA.CheckBoxNet(panel, "#tfa.svsettings.randomizenpcatts", "sv_tfa_npc_randomize_atts") + TFA.CheckBoxNet(panel, "#tfa.svsettings.cmenu", "sv_tfa_cmenu") + TFA.CheckBoxNet(panel, "#tfa.svsettings.penetration", "sv_tfa_bullet_penetration") + TFA.CheckBoxNet(panel, "#tfa.svsettings.ricochet", "sv_tfa_bullet_ricochet") + TFA.CheckBoxNet(panel, "#tfa.svsettings.doorbust", "sv_tfa_bullet_doordestruction") + TFA.CheckBoxNet(panel, "#tfa.svsettings.doorbash", "sv_tfa_melee_doordestruction") + TFA.CheckBoxNet(panel, "#tfa.svsettings.reloads", "sv_tfa_reloads_enabled") + TFA.CheckBoxNet(panel, "#tfa.svsettings.jamming", "sv_tfa_jamming") + TFA.CheckBoxNet(panel, "#tfa.svsettings.nearlyempty", "sv_tfa_nearlyempty") + TFA.CheckBoxNet(panel, "#tfa.svsettings.legacyreloads", "sv_tfa_reloads_legacy") + TFA.CheckBoxNet(panel, "#tfa.svsettings.firstdrawanim", "sv_tfa_first_draw_anim_enabled") + TFA.CheckBoxNet(panel, "#tfa.svsettings.fixedcrosshair", "sv_tfa_fixed_crosshair") + TFA.CheckBoxNet(panel, "#tfa.svsettings.crosshairshowplayer", "sv_tfa_crosshair_showplayer") + TFA.CheckBoxNet(panel, "#tfa.svsettings.crosshairshowplayerteam", "sv_tfa_crosshair_showplayerteam") + TFA.CheckBoxNet(panel, "#tfa.svsettings.fov.sprintmod", "sv_tfa_fov_sprintmod") + + TFA.CheckBoxNet(panel, "#tfa.svsettings.randomseed", "sv_tfa_bullet_randomseed") + panel:Help("#tfa.svsettings.randomseed_tip") + + TFA.NumSliderNet(panel, "#tfa.svsettings.damagemult", "sv_tfa_damage_multiplier", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.damagemultnpc", "sv_tfa_damage_multiplier_npc", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.damagemultrand_min", "sv_tfa_damage_mult_min", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.damagemultrand_max", "sv_tfa_damage_mult_max", 0, 10, 2) + panel:Help("#tfa.svsettings.damagemultrand_tip") + + TFA.NumSliderNet(panel, "#tfa.svsettings.doorrespawntime", "sv_tfa_door_respawn", -1, 120, 0) + + TFA.NumSliderNet(panel, "#tfa.svsettings.jamchance", "sv_tfa_jamming_mult", 0.01, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.jamfactormult", "sv_tfa_jamming_factor", 0.01, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.jamfactorinc", "sv_tfa_jamming_factor_inc", 0.01, 10, 2) + + TFA.NumSliderNet(panel, "#tfa.svsettings.forcemult", "sv_tfa_force_multiplier", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.penpowermul", "sv_tfa_bullet_penetration_power_mul", 0, 40, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.spreadmult", "sv_tfa_spread_multiplier", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.penetrationlimit", "sv_tfa_penetration_hardlimit", 0, 200) + TFA.NumSliderNet(panel, "#tfa.svsettings.defaultclip", "sv_tfa_default_clip", -1, 10, 0) + TFA.NumSliderNet(panel, "#tfa.svsettings.rangemod", "sv_tfa_range_modifier", 0, 1, 3) + TFA.NumSliderNet(panel, "#tfa.svsettings.recoilmult", "sv_tfa_recoil_multiplier", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.svsettings.knockbackmult", "sv_tfa_knockback_multiplier", 0, 10, 2) +end + +local function tfaOptionSights(panel) + --Here are whatever default categories you want. + local tfaOptionCL = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_sights" + } + + tfaOptionCL.Options["#preset.default"] = { + cl_tfa_3dscope_overlay = "1", + cl_tfa_3dscope_quality = "0", + cl_tfa_fx_rtscopeblur_passes = "3", + cl_tfa_fx_rtscopeblur_intensity = "4", + cl_tfa_fx_rtscopeblur_mode = "1", + cl_tfa_scope_sensitivity_3d = "2", + cl_tfa_scope_sensitivity_autoscale = "1", + cl_tfa_scope_sensitivity = "100", + cl_tfa_ironsights_toggle = "0", + cl_tfa_ironsights_resight = "1", + cl_tfa_ironsights_responsive = "0", + cl_tfa_ironsights_responsive_timer = "0.175", + } + + tfaOptionCL.CVars = table.GetKeys(tfaOptionCL.Options["#preset.default"]) + + panel:AddControl("ComboBox", tfaOptionCL) + + panel:CheckBox("#tfa.sightsettings.3dscopeshadows", "cl_tfa_3dscope_overlay") + + local tfaOption3DSM = { + Options = {}, + CVars = {}, + Label = "#tfa.sightsettings.3dsm", + MenuButton = "0", + Folder = "TFA 3D Scope Sens." + } + + tfaOption3DSM.Options["#tfa.sightsettings.3dsm.nc"] = { + cl_tfa_scope_sensitivity_3d = "0" + } + + tfaOption3DSM.Options["#tfa.sightsettings.3dsm.sc"] = { + cl_tfa_scope_sensitivity_3d = "1" + } + + tfaOption3DSM.Options["#tfa.sightsettings.3dsm.3d"] = { + cl_tfa_scope_sensitivity_3d = "2" + } + + tfaOption3DSM.Options["#tfa.sightsettings.3dsm.rt"] = { + cl_tfa_scope_sensitivity_3d = "3" + } + + tfaOption3DSM.CVars = table.GetKeys(tfaOption3DSM.Options["#tfa.sightsettings.3dsm.3d"]) + panel:AddControl("ComboBox", tfaOption3DSM) + + local tfaOption3DSQ = { + Options = {}, + CVars = {}, + Label = "#tfa.sightsettings.3dsq", + MenuButton = "0", + Folder = "TFA 3D Scope Sens." + } + + tfaOption3DSQ.Options["#tfa.sightsettings.3dsq.ul"] = { + cl_tfa_3dscope_quality = "0" + } + + tfaOption3DSQ.Options["#tfa.sightsettings.3dsq.hq"] = { + cl_tfa_3dscope_quality = "1" + } + + tfaOption3DSQ.Options["#tfa.sightsettings.3dsq.mq"] = { + cl_tfa_3dscope_quality = "2" + } + + tfaOption3DSQ.Options["#tfa.sightsettings.3dsq.lq"] = { + cl_tfa_3dscope_quality = "3" + } + + tfaOption3DSQ.CVars = table.GetKeys(tfaOption3DSQ.Options["#tfa.sightsettings.3dsq.ul"]) + panel:AddControl("ComboBox", tfaOption3DSQ) + + local tfaOption3DSB = { + Options = {}, + CVars = {}, + Label = "#tfa.sightsettings.3dsb", + MenuButton = "0", + Folder = "TFA 3D Scope Blur." + } + + tfaOption3DSB.Options["#tfa.sightsettings.3dsb.nb"] = { + cl_tfa_fx_rtscopeblur_mode = "0" + } + + tfaOption3DSB.Options["#tfa.sightsettings.3dsb.sb"] = { + cl_tfa_fx_rtscopeblur_mode = "1" + } + + tfaOption3DSB.Options["#tfa.sightsettings.3dsb.bb"] = { + cl_tfa_fx_rtscopeblur_mode = "2" + } + + tfaOption3DSB.CVars = table.GetKeys(tfaOption3DSB.Options["#tfa.sightsettings.3dsb.bb"]) + panel:AddControl("ComboBox", tfaOption3DSB) + + panel:NumSlider("#tfa.sightsettings.rtbgblurpasses", "cl_tfa_fx_rtscopeblur_passes", 1, 5, 0) + panel:NumSlider("#tfa.sightsettings.rtbgblurintensity", "cl_tfa_fx_rtscopeblur_intensity", 0.01, 10, 2) + panel:CheckBox("#tfa.sightsettings.adstoggle", "cl_tfa_ironsights_toggle") + panel:CheckBox("#tfa.sightsettings.adsresight", "cl_tfa_ironsights_resight") + panel:CheckBox("#tfa.sightsettings.responsive", "cl_tfa_ironsights_responsive") + panel:NumSlider("#tfa.sightsettings.responsive_timer", "cl_tfa_ironsights_responsive_timer", 0.01, 2, 3) + panel:CheckBox("#tfa.sightsettings.scopesensscale", "cl_tfa_scope_sensitivity_autoscale") + panel:NumSlider("#tfa.sightsettings.scopesenspct", "cl_tfa_scope_sensitivity", 0.01, 100, 2) +end + +local function tfaOptionVM(panel) + --Here are whatever default categories you want. + local tfaOptionCL = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_viewmodel" + } + + tfaOptionCL.Options["#preset.default"] = { + cl_tfa_viewbob_animated = "0", + cl_tfa_viewbob_intensity = "1", + cl_tfa_gunbob_intensity = "1", + cl_tfa_gunbob_custom = "1", + cl_tfa_gunbob_invertsway = "0", + cl_tfa_viewmodel_offset_x = "0", + cl_tfa_viewmodel_offset_y = "0", + cl_tfa_viewmodel_offset_z = "0", + cl_tfa_viewmodel_offset_fov = "0", + cl_tfa_viewmodel_multiplier_fov = "1", + cl_tfa_viewmodel_flip = "0", + cl_tfa_viewmodel_centered = "0", + cl_tfa_viewmodel_nearwall = "1", + cl_tfa_laser_trails = "1" + } + + tfaOptionCL.CVars = table.GetKeys(tfaOptionCL.Options["#preset.default"]) + panel:AddControl("ComboBox", tfaOptionCL) + + panel:CheckBox("#tfa.vmsettings.viewbobanim", "cl_tfa_viewbob_animated") + panel:NumSlider("#tfa.vmsettings.viewbobmult", "cl_tfa_viewbob_intensity", 0, 2, 2) + panel:CheckBox("#tfa.vmsettings.gunbobcustom", "cl_tfa_gunbob_custom") + panel:CheckBox("#tfa.vmsettings.gunswayinvert", "cl_tfa_gunbob_invertsway") + panel:NumSlider("#tfa.vmsettings.gunbobmult", "cl_tfa_gunbob_intensity", 0, 2, 2) + + panel:NumSlider("#tfa.vmsettings.offset.x", "cl_tfa_viewmodel_offset_x", -2, 2, 2) + panel:NumSlider("#tfa.vmsettings.offset.y", "cl_tfa_viewmodel_offset_y", -2, 2, 2) + panel:NumSlider("#tfa.vmsettings.offset.z", "cl_tfa_viewmodel_offset_z", -2, 2, 2) + panel:NumSlider("#tfa.vmsettings.offset.fov", "cl_tfa_viewmodel_offset_fov", -5, 5, 2) + panel:NumSlider("#tfa.vmsettings.fovmult", "cl_tfa_viewmodel_multiplier_fov", 0.75, 2, 2) + + panel:CheckBox("#tfa.vmsettings.centered", "cl_tfa_viewmodel_centered") + panel:CheckBox("#tfa.vmsettings.flip", "cl_tfa_viewmodel_flip") + + panel:CheckBox("#tfa.vmsettings.laserdottrail", "cl_tfa_laser_trails") + panel:CheckBox("#tfa.vmsettings.nearwall", "cl_tfa_viewmodel_nearwall") +end + +local function tfaOptionPerformance(panel) + --Here are whatever default categories you want. + local tfaOptionPerf = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_performance" + } + + tfaOptionPerf.Options["#preset.default"] = { + sv_tfa_fx_penetration_decal = "1", + cl_tfa_fx_impact_enabled = "1", + cl_tfa_fx_impact_ricochet_enabled = "1", + cl_tfa_fx_impact_ricochet_sparks = "20", + cl_tfa_fx_impact_ricochet_sparklife = "2", + cl_tfa_fx_gasblur = "1", + cl_tfa_fx_muzzlesmoke = "1", + cl_tfa_fx_muzzlesmoke_limited = "1", + cl_tfa_fx_muzzleflashsmoke = "1", + cl_tfa_fx_ejectionlife = "15", + cl_tfa_legacy_shells = "0", + cl_tfa_fx_ads_dof = "0", + cl_tfa_fx_ads_dof_hd = "0" + } + + tfaOptionPerf.CVars = table.GetKeys(tfaOptionPerf.Options["#preset.default"]) + panel:AddControl("ComboBox", tfaOptionPerf) + + panel:Help("#tfa.settings.client") + panel:CheckBox("#tfa.perfsettings.gasblur", "cl_tfa_fx_gasblur") + panel:CheckBox("#tfa.perfsettings.mzsmoke", "cl_tfa_fx_muzzleflashsmoke") + panel:CheckBox("#tfa.perfsettings.mztrail", "cl_tfa_fx_muzzlesmoke") + panel:CheckBox("#tfa.perfsettings.mztrail.limit", "cl_tfa_fx_muzzlesmoke_limited") + panel:CheckBox("#tfa.perfsettings.ejsmoke", "cl_tfa_fx_ejectionsmoke") + panel:CheckBox("#tfa.perfsettings.impactfx", "cl_tfa_fx_impact_enabled") + panel:CheckBox("#tfa.perfsettings.ricochetfx", "cl_tfa_fx_impact_ricochet_enabled") + + panel:CheckBox("#tfa.perfsettings.oldshells", "cl_tfa_legacy_shells") + + panel:CheckBox("#tfa.perfsettings.adsdof", "cl_tfa_fx_ads_dof") + panel:CheckBox("#tfa.perfsettings.adsdof.hd", "cl_tfa_fx_ads_dof_hd") + + panel:NumSlider("#tfa.perfsettings.ejlife", "cl_tfa_fx_ejectionlife", 0, 60, 0) + + panel:NumSlider("#tfa.perfsettings.ricochetspark.amount", "cl_tfa_fx_impact_ricochet_sparks", 0, 50, 0) + panel:AddControl("Slider", { + Label = "Ricochet Spark Amount", + Command = "cl_tfa_fx_impact_ricochet_sparks", + Type = "Integer", + Min = "0", + Max = "50" + }) + + panel:NumSlider("#tfa.perfsettings.ricochetspark.life", "cl_tfa_fx_impact_ricochet_sparklife", 0, 5, 2) + + panel:Help("#tfa.settings.server") + TFA.CheckBoxNet(panel, "#tfa.perfsettings.penetrationdecal", "sv_tfa_fx_penetration_decal") +end + +local function tfaOptionHUD(panel) + --Here are whatever default categories you want. + local tfaTBLOptionHUD = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_hud" + } + + tfaTBLOptionHUD.Options["#preset.default"] = { + cl_tfa_hud_crosshair_enable_custom = "1", + cl_tfa_hud_crosshair_color_r = "225", + cl_tfa_hud_crosshair_color_g = "225", + cl_tfa_hud_crosshair_color_b = "225", + cl_tfa_hud_crosshair_color_a = "225", + cl_tfa_hud_crosshair_color_team = "1", + cl_tfa_hud_crosshair_color_enemy_r = "255", + cl_tfa_hud_crosshair_color_enemy_g = "0", + cl_tfa_hud_crosshair_color_enemy_b = "0", + cl_tfa_hud_crosshair_color_friendly_r = "0", + cl_tfa_hud_crosshair_color_friendly_g = "255", + cl_tfa_hud_crosshair_color_friendly_b = "0", + cl_tfa_hud_enabled = "1", + cl_tfa_hud_fallback_enabled = "1", + cl_tfa_hud_ammodata_fadein = "0.2", + cl_tfa_hud_hangtime = "1", + cl_tfa_hud_crosshair_length_use_pixels = "0", + cl_tfa_hud_crosshair_length = "1", + cl_tfa_hud_crosshair_width = "1", + cl_tfa_hud_crosshair_gap_scale = "1", + cl_tfa_hud_crosshair_outline_enabled = "1", + cl_tfa_hud_crosshair_outline_width = "1", + cl_tfa_hud_crosshair_outline_color_r = "5", + cl_tfa_hud_crosshair_outline_color_g = "5", + cl_tfa_hud_crosshair_outline_color_b = "5", + cl_tfa_hud_crosshair_outline_color_a = "225", + cl_tfa_hud_crosshair_dot = "0", + cl_tfa_hud_crosshair_triangular = "0", + cl_tfa_hud_crosshair_pump = "0", + cl_tfa_hud_hitmarker_enabled = "1", + cl_tfa_hud_hitmarker_3d_all = "0", + cl_tfa_hud_hitmarker_3d_shotguns = "1", + cl_tfa_hud_hitmarker_solidtime = "0.1", + cl_tfa_hud_hitmarker_fadetime = "0.3", + cl_tfa_hud_hitmarker_scale = "1", + cl_tfa_hud_hitmarker_color_r = "225", + cl_tfa_hud_hitmarker_color_g = "225", + cl_tfa_hud_hitmarker_color_b = "225", + cl_tfa_hud_hitmarker_color_a = "225", + cl_tfa_hud_scale = "1", + cl_tfa_hud_keybindhints_enabled = "1", + cl_tfa_hud_keybindhints_solidtime = "3", + cl_tfa_hud_keybindhints_fadeintime = "1", + cl_tfa_hud_keybindhints_fadeouttime = "4", + } + + tfaTBLOptionHUD.Options["#tfa.hudpreset.cross"] = { + cl_tfa_hud_crosshair_enable_custom = "1", + cl_tfa_hud_crosshair_color_r = "255", + cl_tfa_hud_crosshair_color_g = "255", + cl_tfa_hud_crosshair_color_b = "255", + cl_tfa_hud_crosshair_color_a = "200", + cl_tfa_hud_crosshair_color_team = "1", + cl_tfa_hud_crosshair_color_enemy_r = "255", + cl_tfa_hud_crosshair_color_enemy_g = "0", + cl_tfa_hud_crosshair_color_enemy_b = "0", + cl_tfa_hud_crosshair_color_friendly_r = "0", + cl_tfa_hud_crosshair_color_friendly_g = "255", + cl_tfa_hud_crosshair_color_friendly_b = "0", + cl_tfa_hud_enabled = "1", + cl_tfa_hud_fallback_enabled = "1", + cl_tfa_hud_ammodata_fadein = "0.2", + cl_tfa_hud_hangtime = "1", + cl_tfa_hud_crosshair_length_use_pixels = "0", + cl_tfa_hud_crosshair_length = "0.75", + cl_tfa_hud_crosshair_width = "1", + cl_tfa_hud_crosshair_gap_scale = "0", + cl_tfa_hud_crosshair_outline_enabled = "1", + cl_tfa_hud_crosshair_outline_width = "1", + cl_tfa_hud_crosshair_outline_color_r = "154", + cl_tfa_hud_crosshair_outline_color_g = "152", + cl_tfa_hud_crosshair_outline_color_b = "175", + cl_tfa_hud_crosshair_outline_color_a = "255", + cl_tfa_hud_crosshair_dot = "0", + cl_tfa_hud_crosshair_triangular = "0", + cl_tfa_hud_crosshair_pump = "0", + cl_tfa_hud_hitmarker_enabled = "1", + cl_tfa_hud_hitmarker_3d_all = "0", + cl_tfa_hud_hitmarker_3d_shotguns = "1", + cl_tfa_hud_hitmarker_solidtime = "0.1", + cl_tfa_hud_hitmarker_fadetime = "0.3", + cl_tfa_hud_hitmarker_scale = "1", + cl_tfa_hud_hitmarker_color_r = "225", + cl_tfa_hud_hitmarker_color_g = "225", + cl_tfa_hud_hitmarker_color_b = "225", + cl_tfa_hud_hitmarker_color_a = "225", + cl_tfa_hud_scale = "1", + cl_tfa_hud_keybindhints_enabled = "1", + cl_tfa_hud_keybindhints_solidtime = "3", + cl_tfa_hud_keybindhints_fadeintime = "1", + cl_tfa_hud_keybindhints_fadeouttime = "4", + } + + tfaTBLOptionHUD.Options["#tfa.hudpreset.dot"] = { + cl_tfa_hud_crosshair_enable_custom = "1", + cl_tfa_hud_crosshair_color_r = "72", + cl_tfa_hud_crosshair_color_g = "72", + cl_tfa_hud_crosshair_color_b = "72", + cl_tfa_hud_crosshair_color_a = "85", + cl_tfa_hud_crosshair_color_team = "1", + cl_tfa_hud_crosshair_color_enemy_r = "255", + cl_tfa_hud_crosshair_color_enemy_g = "0", + cl_tfa_hud_crosshair_color_enemy_b = "0", + cl_tfa_hud_crosshair_color_friendly_r = "0", + cl_tfa_hud_crosshair_color_friendly_g = "255", + cl_tfa_hud_crosshair_color_friendly_b = "0", + cl_tfa_hud_enabled = "1", + cl_tfa_hud_fallback_enabled = "1", + cl_tfa_hud_ammodata_fadein = "0.1", + cl_tfa_hud_hangtime = "0.5", + cl_tfa_hud_crosshair_length_use_pixels = "0", + cl_tfa_hud_crosshair_length = "0", + cl_tfa_hud_crosshair_width = "1", + cl_tfa_hud_crosshair_gap_scale = "0", + cl_tfa_hud_crosshair_outline_enabled = "1", + cl_tfa_hud_crosshair_outline_width = "1", + cl_tfa_hud_crosshair_outline_color_r = "225", + cl_tfa_hud_crosshair_outline_color_g = "225", + cl_tfa_hud_crosshair_outline_color_b = "225", + cl_tfa_hud_crosshair_outline_color_a = "85", + cl_tfa_hud_crosshair_dot = "0", + cl_tfa_hud_crosshair_triangular = "0", + cl_tfa_hud_crosshair_pump = "0", + cl_tfa_hud_hitmarker_enabled = "0", + cl_tfa_hud_hitmarker_3d_all = "0", + cl_tfa_hud_hitmarker_3d_shotguns = "0", + cl_tfa_hud_hitmarker_solidtime = "0.1", + cl_tfa_hud_hitmarker_fadetime = "0.3", + cl_tfa_hud_hitmarker_scale = "1", + cl_tfa_hud_hitmarker_color_r = "225", + cl_tfa_hud_hitmarker_color_g = "225", + cl_tfa_hud_hitmarker_color_b = "225", + cl_tfa_hud_hitmarker_color_a = "225", + cl_tfa_hud_scale = "1", + cl_tfa_hud_keybindhints_enabled = "1", + cl_tfa_hud_keybindhints_solidtime = "3", + cl_tfa_hud_keybindhints_fadeintime = "1", + cl_tfa_hud_keybindhints_fadeouttime = "4", + } + + tfaTBLOptionHUD.Options["#tfa.hudpreset.rockstar"] = { + cl_tfa_hud_crosshair_enable_custom = "1", + cl_tfa_hud_crosshair_color_r = "225", + cl_tfa_hud_crosshair_color_g = "225", + cl_tfa_hud_crosshair_color_b = "225", + cl_tfa_hud_crosshair_color_a = "85", + cl_tfa_hud_crosshair_color_team = "1", + cl_tfa_hud_crosshair_color_enemy_r = "255", + cl_tfa_hud_crosshair_color_enemy_g = "0", + cl_tfa_hud_crosshair_color_enemy_b = "0", + cl_tfa_hud_crosshair_color_friendly_r = "0", + cl_tfa_hud_crosshair_color_friendly_g = "255", + cl_tfa_hud_crosshair_color_friendly_b = "0", + cl_tfa_hud_enabled = "1", + cl_tfa_hud_fallback_enabled = "1", + cl_tfa_hud_ammodata_fadein = "0.1", + cl_tfa_hud_hangtime = "0.5", + cl_tfa_hud_crosshair_length_use_pixels = "0", + cl_tfa_hud_crosshair_length = "0", + cl_tfa_hud_crosshair_width = "2", + cl_tfa_hud_crosshair_gap_scale = "0", + cl_tfa_hud_crosshair_outline_enabled = "1", + cl_tfa_hud_crosshair_outline_width = "1", + cl_tfa_hud_crosshair_outline_color_r = "30", + cl_tfa_hud_crosshair_outline_color_g = "30", + cl_tfa_hud_crosshair_outline_color_b = "30", + cl_tfa_hud_crosshair_outline_color_a = "85", + cl_tfa_hud_crosshair_dot = "0", + cl_tfa_hud_crosshair_triangular = "0", + cl_tfa_hud_crosshair_pump = "0", + cl_tfa_hud_hitmarker_enabled = "1", + cl_tfa_hud_hitmarker_3d_all = "0", + cl_tfa_hud_hitmarker_3d_shotguns = "0", + cl_tfa_hud_hitmarker_solidtime = "0.1", + cl_tfa_hud_hitmarker_fadetime = "0.3", + cl_tfa_hud_hitmarker_scale = "1", + cl_tfa_hud_hitmarker_color_r = "225", + cl_tfa_hud_hitmarker_color_g = "225", + cl_tfa_hud_hitmarker_color_b = "225", + cl_tfa_hud_hitmarker_color_a = "8", + cl_tfa_hud_scale = "1", + cl_tfa_hud_keybindhints_enabled = "1", + cl_tfa_hud_keybindhints_solidtime = "3", + cl_tfa_hud_keybindhints_fadeintime = "1", + cl_tfa_hud_keybindhints_fadeouttime = "4", + } + + tfaTBLOptionHUD.Options["#tfa.hudpreset.hl2"] = { + cl_tfa_hud_crosshair_enable_custom = "0", + cl_tfa_hud_crosshair_color_r = "255", + cl_tfa_hud_crosshair_color_g = "255", + cl_tfa_hud_crosshair_color_b = "255", + cl_tfa_hud_crosshair_color_a = "225", + cl_tfa_hud_crosshair_color_team = "0", + cl_tfa_hud_crosshair_color_enemy_r = "255", + cl_tfa_hud_crosshair_color_enemy_g = "0", + cl_tfa_hud_crosshair_color_enemy_b = "0", + cl_tfa_hud_crosshair_color_friendly_r = "0", + cl_tfa_hud_crosshair_color_friendly_g = "255", + cl_tfa_hud_crosshair_color_friendly_b = "0", + cl_tfa_hud_enabled = "1", + cl_tfa_hud_fallback_enabled = "1", + cl_tfa_hud_ammodata_fadein = "0.01", + cl_tfa_hud_hangtime = "0", + cl_tfa_hud_crosshair_length_use_pixels = "1", + cl_tfa_hud_crosshair_length = "0.5", + cl_tfa_hud_crosshair_width = "1", + cl_tfa_hud_crosshair_gap_scale = "1", + cl_tfa_hud_crosshair_outline_enabled = "0", + cl_tfa_hud_crosshair_outline_width = "0", + cl_tfa_hud_crosshair_outline_color_r = "5", + cl_tfa_hud_crosshair_outline_color_g = "5", + cl_tfa_hud_crosshair_outline_color_b = "5", + cl_tfa_hud_crosshair_outline_color_a = "0", + cl_tfa_hud_crosshair_dot = "1", + cl_tfa_hud_crosshair_triangular = "0", + cl_tfa_hud_crosshair_pump = "0", + cl_tfa_hud_hitmarker_enabled = "0", + cl_tfa_hud_hitmarker_3d_all = "0", + cl_tfa_hud_hitmarker_3d_shotguns = "0", + cl_tfa_hud_hitmarker_solidtime = "0.1", + cl_tfa_hud_hitmarker_fadetime = "0.3", + cl_tfa_hud_hitmarker_scale = "1", + cl_tfa_hud_hitmarker_color_r = "225", + cl_tfa_hud_hitmarker_color_g = "225", + cl_tfa_hud_hitmarker_color_b = "225", + cl_tfa_hud_hitmarker_color_a = "225", + cl_tfa_hud_scale = "1", + cl_tfa_hud_keybindhints_enabled = "1", + cl_tfa_hud_keybindhints_solidtime = "3", + cl_tfa_hud_keybindhints_fadeintime = "1", + cl_tfa_hud_keybindhints_fadeouttime = "4", + } + + tfaTBLOptionHUD.Options["#tfa.hudpreset.hl2plus"] = { + cl_tfa_hud_crosshair_enable_custom = "1", + cl_tfa_hud_crosshair_color_r = "255", + cl_tfa_hud_crosshair_color_g = "255", + cl_tfa_hud_crosshair_color_b = "255", + cl_tfa_hud_crosshair_color_a = "225", + cl_tfa_hud_crosshair_color_team = "1", + cl_tfa_hud_crosshair_color_enemy_r = "255", + cl_tfa_hud_crosshair_color_enemy_g = "0", + cl_tfa_hud_crosshair_color_enemy_b = "0", + cl_tfa_hud_crosshair_color_friendly_r = "0", + cl_tfa_hud_crosshair_color_friendly_g = "255", + cl_tfa_hud_crosshair_color_friendly_b = "0", + cl_tfa_hud_enabled = "1", + cl_tfa_hud_fallback_enabled = "1", + cl_tfa_hud_ammodata_fadein = "0.2", + cl_tfa_hud_hangtime = "1", + cl_tfa_hud_crosshair_length_use_pixels = "1", + cl_tfa_hud_crosshair_length = "0.5", + cl_tfa_hud_crosshair_width = "1", + cl_tfa_hud_crosshair_gap_scale = "1", + cl_tfa_hud_crosshair_outline_enabled = "0", + cl_tfa_hud_crosshair_outline_width = "0", + cl_tfa_hud_crosshair_outline_color_r = "5", + cl_tfa_hud_crosshair_outline_color_g = "5", + cl_tfa_hud_crosshair_outline_color_b = "5", + cl_tfa_hud_crosshair_outline_color_a = "0", + cl_tfa_hud_crosshair_dot = "1", + cl_tfa_hud_crosshair_triangular = "0", + cl_tfa_hud_crosshair_pump = "0", + cl_tfa_hud_hitmarker_enabled = "1", + cl_tfa_hud_hitmarker_3d_all = "0", + cl_tfa_hud_hitmarker_3d_shotguns = "1", + cl_tfa_hud_hitmarker_solidtime = "0.1", + cl_tfa_hud_hitmarker_fadetime = "0.3", + cl_tfa_hud_hitmarker_scale = "1", + cl_tfa_hud_hitmarker_color_r = "225", + cl_tfa_hud_hitmarker_color_g = "225", + cl_tfa_hud_hitmarker_color_b = "225", + cl_tfa_hud_hitmarker_color_a = "225", + cl_tfa_hud_scale = "1", + cl_tfa_hud_keybindhints_enabled = "1", + cl_tfa_hud_keybindhints_solidtime = "3", + cl_tfa_hud_keybindhints_fadeintime = "1", + cl_tfa_hud_keybindhints_fadeouttime = "4", + } + + tfaTBLOptionHUD.Options["#tfa.hudpreset.crysis2"] = { + cl_tfa_hud_crosshair_enable_custom = "1", + cl_tfa_hud_crosshair_color_r = "231", + cl_tfa_hud_crosshair_color_g = "255", + cl_tfa_hud_crosshair_color_b = "255", + cl_tfa_hud_crosshair_color_a = "255", + cl_tfa_hud_crosshair_color_team = "1", + cl_tfa_hud_crosshair_color_enemy_r = "204", + cl_tfa_hud_crosshair_color_enemy_g = "0", + cl_tfa_hud_crosshair_color_enemy_b = "0", + cl_tfa_hud_crosshair_color_friendly_r = "38", + cl_tfa_hud_crosshair_color_friendly_g = "255", + cl_tfa_hud_crosshair_color_friendly_b = "38", + cl_tfa_hud_enabled = "1", + cl_tfa_hud_fallback_enabled = "1", + cl_tfa_hud_ammodata_fadein = "0.2", + cl_tfa_hud_hangtime = "1", + cl_tfa_hud_crosshair_length_use_pixels = "0", + cl_tfa_hud_crosshair_length = "1", + cl_tfa_hud_crosshair_width = "2", + cl_tfa_hud_crosshair_gap_scale = "1", + cl_tfa_hud_crosshair_outline_enabled = "0", + cl_tfa_hud_crosshair_outline_width = "0", + cl_tfa_hud_crosshair_outline_color_r = "0", + cl_tfa_hud_crosshair_outline_color_g = "0", + cl_tfa_hud_crosshair_outline_color_b = "0", + cl_tfa_hud_crosshair_outline_color_a = "0", + cl_tfa_hud_crosshair_dot = "0", + cl_tfa_hud_crosshair_triangular = "1", + cl_tfa_hud_crosshair_pump = "1", + cl_tfa_hud_hitmarker_enabled = "1", + cl_tfa_hud_hitmarker_3d_all = "0", + cl_tfa_hud_hitmarker_3d_shotguns = "1", + cl_tfa_hud_hitmarker_solidtime = "0.1", + cl_tfa_hud_hitmarker_fadetime = "0.3", + cl_tfa_hud_hitmarker_scale = "1.5", + cl_tfa_hud_hitmarker_color_r = "231", + cl_tfa_hud_hitmarker_color_g = "255", + cl_tfa_hud_hitmarker_color_b = "255", + cl_tfa_hud_hitmarker_color_a = "255", + cl_tfa_hud_scale = "1", + cl_tfa_hud_keybindhints_enabled = "1", + cl_tfa_hud_keybindhints_solidtime = "3", + cl_tfa_hud_keybindhints_fadeintime = "1", + cl_tfa_hud_keybindhints_fadeouttime = "4", + } + + tfaTBLOptionHUD.CVars = table.GetKeys(tfaTBLOptionHUD.Options["#preset.default"]) + panel:AddControl("ComboBox", tfaTBLOptionHUD) + + panel:NumSlider("#tfa.hudsettings.scalemul", "cl_tfa_hud_scale", 0.25, 4, 3) + + local hudpnl = vgui.Create("DForm", panel) + hudpnl:SetName("#tfa.hudsettings.label") + panel:AddItem(hudpnl) + + hudpnl:CheckBox("#tfa.hudsettings.enabled", "cl_tfa_hud_enabled") + hudpnl:NumSlider("#tfa.hudsettings.fadein", "cl_tfa_hud_ammodata_fadein", 0.01, 1, 2) + hudpnl:NumSlider("#tfa.hudsettings.hangtime", "cl_tfa_hud_hangtime", 0, 5, 2) + + hudpnl:Help("") + hudpnl:CheckBox("#tfa.hudsettings.fallback", "cl_tfa_hud_fallback_enabled") + hudpnl:Help("#tfa.hudsettings.fallback.desc") + + local xhairpnl = vgui.Create("DForm", panel) + xhairpnl:SetName("#tfa.hudsettings.crosshair.label") + panel:AddItem(xhairpnl) + + xhairpnl:CheckBox("#tfa.hudsettings.crosshair.enabled", "cl_tfa_hud_crosshair_enable_custom") + xhairpnl:CheckBox("#tfa.hudsettings.crosshair.dot", "cl_tfa_hud_crosshair_dot") + xhairpnl:CheckBox("#tfa.hudsettings.crosshair.triangular", "cl_tfa_hud_crosshair_triangular") + xhairpnl:CheckBox("#tfa.hudsettings.crosshair.pump", "cl_tfa_hud_crosshair_pump") + xhairpnl:NumSlider("#tfa.hudsettings.crosshair.length", "cl_tfa_hud_crosshair_length", 0, 10, 2) + xhairpnl:CheckBox("#tfa.hudsettings.crosshair.length.usepixels", "cl_tfa_hud_crosshair_length_use_pixels") + xhairpnl:NumSlider("#tfa.hudsettings.crosshair.gapscale", "cl_tfa_hud_crosshair_gap_scale", 0, 2, 2) + xhairpnl:NumSlider("#tfa.hudsettings.crosshair.width", "cl_tfa_hud_crosshair_width", 0, 3, 0) + AddFoldedColorMixer(xhairpnl, "#tfa.hudsettings.crosshair.color", "cl_tfa_hud_crosshair_color_r", "cl_tfa_hud_crosshair_color_g", "cl_tfa_hud_crosshair_color_b", "cl_tfa_hud_crosshair_color_a") + xhairpnl:CheckBox("#tfa.hudsettings.crosshair.teamcolor", "cl_tfa_hud_crosshair_color_team") + AddFoldedColorMixer(xhairpnl, "#tfa.hudsettings.crosshair.teamcolor.enemy", "cl_tfa_hud_crosshair_color_enemy_r", "cl_tfa_hud_crosshair_color_enemy_g", "cl_tfa_hud_crosshair_color_enemy_b") + AddFoldedColorMixer(xhairpnl, "#tfa.hudsettings.crosshair.teamcolor.friendly", "cl_tfa_hud_crosshair_color_friendly_r", "cl_tfa_hud_crosshair_color_friendly_g", "cl_tfa_hud_crosshair_color_friendly_b") + + xhairpnl:CheckBox("#tfa.hudsettings.crosshair.outline.enabled", "cl_tfa_hud_crosshair_outline_enabled") + xhairpnl:NumSlider("#tfa.hudsettings.crosshair.outline.width", "cl_tfa_hud_crosshair_outline_width", 0, 3, 0) + AddFoldedColorMixer(xhairpnl, "#tfa.hudsettings.crosshair.outline.color", "cl_tfa_hud_crosshair_outline_color_r", "cl_tfa_hud_crosshair_outline_color_g", "cl_tfa_hud_crosshair_outline_color_b", "cl_tfa_hud_crosshair_outline_color_a") + + local hmpnl = vgui.Create("DForm", panel) + hmpnl:SetName("#tfa.hudsettings.hitmarker.label") + panel:AddItem(hmpnl) + + hmpnl:CheckBox("#tfa.hudsettings.hitmarker.enabled", "cl_tfa_hud_hitmarker_enabled") + hmpnl:CheckBox("#tfa.hudsettings.hitmarker.3d.shotguns", "cl_tfa_hud_hitmarker_3d_shotguns") + hmpnl:CheckBox("#tfa.hudsettings.hitmarker.3d.all", "cl_tfa_hud_hitmarker_3d_all") + hmpnl:NumSlider("#tfa.hudsettings.hitmarker.solidtime", "cl_tfa_hud_hitmarker_solidtime", 0, 3, 3) + hmpnl:NumSlider("#tfa.hudsettings.hitmarker.fadetime", "cl_tfa_hud_hitmarker_fadetime", 0, 3, 3) + hmpnl:NumSlider("#tfa.hudsettings.hitmarker.scale", "cl_tfa_hud_hitmarker_scale", 0, 5, 2) + AddFoldedColorMixer(hmpnl, "#tfa.hudsettings.hitmarker.color", "cl_tfa_hud_hitmarker_color_r", "cl_tfa_hud_hitmarker_color_g", "cl_tfa_hud_hitmarker_color_b", "cl_tfa_hud_hitmarker_color_a") + + local kbhpnl = vgui.Create("DForm", panel) + kbhpnl:SetName("#tfa.hudsettings.keybindhints.label") + panel:AddItem(kbhpnl) + + kbhpnl:CheckBox("#tfa.hudsettings.keybindhints.enabled", "cl_tfa_hud_keybindhints_enabled") + kbhpnl:NumSlider("#tfa.hudsettings.keybindhints.solidtime", "cl_tfa_hud_keybindhints_solidtime", 0, 15, 3) + kbhpnl:NumSlider("#tfa.hudsettings.keybindhints.fadeintime", "cl_tfa_hud_keybindhints_fadeintime", 0.01, 10, 3) + kbhpnl:NumSlider("#tfa.hudsettings.keybindhints.fadeouttime", "cl_tfa_hud_keybindhints_fadeouttime", 0.01, 10, 3) +end + +local function tfaOptionDeveloper(panel) + --Here are whatever default categories you want. + local tfaOptionPerf = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_debug" + } + + tfaOptionPerf.Options["#preset.default"] = { + ["cl_tfa_debug_crosshair"] = 0, + ["cl_tfa_debug_animations"] = 0, + ["cl_tfa_debug_rt"] = 0, + ["cl_tfa_debug_cache"] = 0 + } + + tfaOptionPerf.CVars = table.GetKeys(tfaOptionPerf.Options["#preset.default"]) + panel:AddControl("ComboBox", tfaOptionPerf) + + panel:Help("#tfa.devsettings.adminonly") + + panel:CheckBox("#tfa.devsettings.debug.crosshair", "cl_tfa_debug_crosshair") + panel:CheckBox("#tfa.devsettings.debug.animations", "cl_tfa_debug_animations") + panel:CheckBox("#tfa.devsettings.debug.rtshadow", "cl_tfa_debug_rt") + panel:CheckBox("#tfa.devsettings.debug.cache", "cl_tfa_debug_cache") + + panel:Help("") + + panel:Help("#tfa.devsettings.documentation.label") + local btnDocumentation = panel:Button("#tfa.devsettings.documentation.btn.github") + btnDocumentation.DoClick = function() + gui.OpenURL("https://github.com/YuRaNnNzZZ/TFA-SWEP-Base-Documentation") + end + + local sdpnl = vgui.Create("DForm", panel) + sdpnl:SetName("#tfa.devsettings.statmigrator.label") + panel:AddItem(sdpnl) + + sdpnl:Help("#tfa.devsettings.statmigrator.help") + sdpnl:Help(language.GetPhrase("tfa.devsettings.statmigrator.dataversion"):format("TFADataVersion", TFA.LatestDataVersion)) + + local stat_old = sdpnl:TextEntry("#tfa.devsettings.statmigrator.old_name") + + local stat_new = sdpnl:TextEntry("#tfa.devsettings.statmigrator.new_name") + stat_new:SetEditable(false) + + local copy_new = sdpnl:Button("#tfa.devsettings.statmigrator.copy_btn.label") + copy_new.DoClick = function(self) + SetClipboardText(stat_new:GetValue()) + end + + stat_old.OnChange = function(self) + local stat = self:GetValue() + + if string.StartsWith(stat, "SWEP.") or string.StartsWith(stat, "self.") then + stat = string.sub(stat, 6) + end + + local _, path, _ = TFA.GetStatPath(stat, 0, TFA.LatestDataVersion) + + stat_new:SetValue(path) + end +end + +local function tfaOptionColors(panel) + local tfaOptionCO = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_colors" + } + + tfaOptionCO.Options["#preset.default"] = { + cl_tfa_laser_color_r = "255", + cl_tfa_laser_color_g = "0", + cl_tfa_laser_color_b = "0", + cl_tfa_reticule_color_r = "255", + cl_tfa_reticule_color_g = "100", + cl_tfa_reticule_color_b = "0" + } + + tfaOptionCO.CVars = table.GetKeys(tfaOptionCO.Options["#preset.default"]) + panel:AddControl("ComboBox", tfaOptionCO) + + AddFoldedColorMixer(panel, "#tfa.colorsettings.laser", "cl_tfa_laser_color_r", "cl_tfa_laser_color_g", "cl_tfa_laser_color_b") + AddFoldedColorMixer(panel, "#tfa.colorsettings.reticule", "cl_tfa_reticule_color_r", "cl_tfa_reticule_color_g", "cl_tfa_reticule_color_b") +end + +local function tfaOptionBallistics(panel) + --Here are whatever default categories you want. + local tfaOptionPerf = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_ballistics" + } + + tfaOptionPerf.Options["#preset.default"] = { + ["sv_tfa_ballistics_enabled"] = nil, + ["sv_tfa_ballistics_mindist"] = -1, + ["sv_tfa_ballistics_bullet_life"] = 10, + ["sv_tfa_ballistics_bullet_damping_air"] = 1, + ["sv_tfa_ballistics_bullet_damping_water"] = 3, + ["sv_tfa_ballistics_bullet_velocity"] = 1, + ["sv_tfa_ballistics_bullet_substeps"] = 3, + ["sv_tfa_ballistics_custom_gravity"] = 0, + ["sv_tfa_ballistics_custom_gravity_value"] = 0, + ["cl_tfa_ballistics_mp"] = 1, + ["cl_tfa_ballistics_fx_bullet"] = 1, + ["cl_tfa_ballistics_fx_tracers_style"] = 1, + ["cl_tfa_ballistics_fx_tracers_mp"] = 1, + ["cl_tfa_ballistics_fx_tracers_adv"] = 1 + } + + tfaOptionPerf.CVars = table.GetKeys(tfaOptionPerf.Options["#preset.default"]) + panel:AddControl("ComboBox", tfaOptionPerf) + + panel:Help("#tfa.settings.server") + TFA.CheckBoxNet(panel, "#tfa.ballisticsettings.enabled", "sv_tfa_ballistics_enabled") + TFA.NumSliderNet(panel, "#tfa.ballisticsettings.mindist", "sv_tfa_ballistics_mindist", -1, 100, 0) + TFA.NumSliderNet(panel, "#tfa.ballisticsettings.bullet.life", "sv_tfa_ballistics_bullet_life", 0, 20, 2) + TFA.NumSliderNet(panel, "#tfa.ballisticsettings.bullet.damping.air", "sv_tfa_ballistics_bullet_damping_air", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.ballisticsettings.bullet.damping.water", "sv_tfa_ballistics_bullet_damping_water", 0, 10, 2) + TFA.NumSliderNet(panel, "#tfa.ballisticsettings.bullet.velocity", "sv_tfa_ballistics_bullet_velocity", 0, 2, 3) + TFA.NumSliderNet(panel, "#tfa.ballisticsettings.substeps", "sv_tfa_ballistics_substeps", 1, 5, 0) + TFA.CheckBoxNet(panel, "#tfa.ballisticsettings.customgravity", "sv_tfa_ballistics_custom_gravity") + TFA.CheckBoxNet(panel, "#tfa.ballisticsettings.customgravityvalue", "sv_tfa_ballistics_custom_gravity_value") + + panel:Help("#tfa.settings.client") + + panel:CheckBox("#tfa.ballisticsettings.fx.bullet", "cl_tfa_ballistics_fx_bullet") + panel:CheckBox("#tfa.ballisticsettings.fx.hq", "cl_tfa_ballistics_fx_tracers_adv") + panel:CheckBox("#tfa.ballisticsettings.fx.mp", "cl_tfa_ballistics_mp") + panel:CheckBox("#tfa.ballisticsettings.fx.mptracer", "cl_tfa_ballistics_fx_tracers_mp") + + local tfaOptionTracerStyle = { + Options = {}, + CVars = {"cl_tfa_ballistics_fx_tracers_style"}, + Label = "#tfa.ballisticsettings.tracer", + MenuButton = "1", + Folder = "TFASSBallTracerStyle" + } + + tfaOptionTracerStyle.Options["#tfa.ballisticsettings.tracer.di"] = { + ["cl_tfa_ballistics_fx_tracers_style"] = 0 + } + + tfaOptionTracerStyle.Options["#tfa.ballisticsettings.tracer.sm"] = { + ["cl_tfa_ballistics_fx_tracers_style"] = 1 + } + + tfaOptionTracerStyle.Options["#tfa.ballisticsettings.tracer.re"] = { + ["cl_tfa_ballistics_fx_tracers_style"] = 2 + } + + panel:AddControl("ComboBox", tfaOptionTracerStyle) +end + +local contributors = { + "INCONCEIVABLE!", + "Generic Default", + "Clavus", + "Nemole/Scotch", + "Daniel Stevens", + "code_gs", + "Juckey", + "RalphORama", + "Iamgoofball", + "Alexander Grist-Hucker", + "Zombine", + "FlorianLeChat", + "Global", + "TheAsian EggrollMaker", + "Kris", + "DaNike_" +} + +local function tfaOptionAbout(panel) + panel:Help("TFA Base") + panel:Help(language.GetPhrase("tfa.about.version"):format(TFA_BASE_VERSION_STRING or TFA_BASE_VERSION)) + panel:Help(language.GetPhrase("tfa.about.author"):format("The Forgotten Architect")) + panel:Help(language.GetPhrase("tfa.about.maintain"):format("YuRaNnNzZZ", "DBotThePony")) + panel:Help(language.GetPhrase("tfa.about.contributors"):format(table.concat(contributors, ", "))) + + panel:Help("") + + panel:Help("#tfa.about.font") + panel:Help("Inter by The Inter Project Authors") + local fontBtn = panel:Button("https://github.com/rsms/inter") + fontBtn.DoClick = function() + gui.OpenURL("https://github.com/rsms/inter") + end + + panel:Help("") + + panel:Help("#tfa.about.changelog.label") + local btnChangelog = panel:Button("#tfa.about.changelog.btn.github") + btnChangelog.DoClick = function() + gui.OpenURL("https://github.com/YuRaNnNzZZ/TFA-SWEP-Base-Documentation/blob/master/CHANGELOG.md") + end + local btnSteamChangeNotes = panel:Button("#tfa.about.changelog.btn.steam") + btnSteamChangeNotes.DoClick = function() + gui.OpenURL("https://steamcommunity.com/sharedfiles/filedetails/changelog/2840031720") + end +end + +local function tfaOptionInspect(panel) + local presetTable = { + Options = {}, + CVars = {}, + MenuButton = "1", + Folder = "tfa_base_inspect" + } + + presetTable.Options["#preset.default"] = { + cl_tfa_inspection_bokeh = "0", + cl_tfa_inspection_bokeh_radius = "0.1", + cl_tfa_inspect_hide = "0", + cl_tfa_inspect_hide_hud = "0", + cl_tfa_inspect_hide_in_screenshots = "0", + cl_tfa_inspect_newbars = "0", + cl_tfa_inspect_spreadinmoa = "0", + cl_tfa_attachments_persist_enabled = "1" + } + + presetTable.CVars = table.GetKeys(presetTable.Options["#preset.default"]) + panel:AddControl("ComboBox", presetTable) + + panel:CheckBox("#tfa.inspectsettings.inspectdof.enabled", "cl_tfa_inspection_bokeh") + panel:NumSlider("#tfa.inspectsettings.inspectdof.radius", "cl_tfa_inspection_bokeh_radius", 0.01, 1, 3) + + panel:CheckBox("#tfa.inspectsettings.hide", "cl_tfa_inspect_hide") + if DLib then + panel:CheckBox("#tfa.inspectsettings.hidehud", "cl_tfa_inspect_hide_hud") + end + panel:CheckBox("#tfa.inspectsettings.hideinscreenshots", "cl_tfa_inspect_hide_in_screenshots") + panel:CheckBox("#tfa.inspectsettings.newbars", "cl_tfa_inspect_newbars") + panel:CheckBox("#tfa.inspectsettings.spreadinmoa", "cl_tfa_inspect_spreadinmoa") + + panel:Help("") + panel:CheckBox("#tfa.attsettings.persist", "cl_tfa_attachments_persist_enabled") + + -- these two are not concommands by design + local btnResetWep = panel:Button("#tfa.attsettings.resetsaved.current.btn") + btnResetWep.DoClick = function() + local ply = LocalPlayer() + if not IsValid(ply) or not ply:Alive() then return end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or not wep.IsTFAWeapon then return end + + local class = wep:GetClass() + Derma_Query("#tfa.attsettings.resetsaved.current.text", string.format(language.GetPhrase("tfa.attsettings.resetsaved.current.title"), wep:GetPrintName()), "#GameUI_Yes", function() + sql.Query(string.format([[DELETE FROM tfa_savedattachments WHERE class = '%s']], sql.SQLStr(class, true))) + end, "#GameUI_No", function() end) + end + + local btnResetAll = panel:Button("#tfa.attsettings.resetsaved.all.btn") + btnResetAll.DoClick = function() + Derma_Query("#tfa.attsettings.resetsaved.all.text", "#tfa.attsettings.resetsaved.all.title", "#GameUI_Yes", function() + sql.Query([[DELETE FROM tfa_savedattachments]]) + end, "#GameUI_No", function() end) + end +end + +local function tfaAddOption() + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "tfaOptionVM", "#tfa.smsettings.viewmodel", "", "", tfaOptionVM) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "tfaOptionSights", "#tfa.smsettings.sights", "", "", tfaOptionSights) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "tfaOptionPerformance", "#tfa.smsettings.perf", "", "", tfaOptionPerformance) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseCrosshair", "#tfa.smsettings.hud", "", "", tfaOptionHUD) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseDeveloper", "#tfa.smsettings.dev", "", "", tfaOptionDeveloper) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseColor", "#tfa.smsettings.color", "", "", tfaOptionColors) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseBallistics", "#tfa.smsettings.ballistics", "", "", tfaOptionBallistics) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseServer", "#tfa.smsettings.server", "", "", tfaOptionServer) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseAbout", "#tfa.smsettings.about", "", "", tfaOptionAbout) + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseInspect", "#tfa.smsettings.inspect", "", "", tfaOptionInspect) +end + +hook.Add("PopulateToolMenu", "tfaAddOption", tfaAddOption) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_vm_blur.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_vm_blur.lua new file mode 100644 index 0000000..0a28abc --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/cl_tfa_vm_blur.lua @@ -0,0 +1,244 @@ +local supports +local cl_tfa_fx_dof, cl_tfa_fx_dof_hd +local fmat = CreateMaterial("TFA_DOF_Material4", "Refract", { + ["$model"] = "1", + ["$alpha"] = "1", + ["$alphatest"] = "1", + ["$normalmap"] = "effects/flat_normal", + ["$refractamount"] = "0.1", + ["$vertexalpha"] = "1", + ["$vertexcolor"] = "1", + ["$translucent"] = "1", + ["$forcerefract"] = "0", + ["$bluramount"] = "1.5", + ["$nofog"] = "1" +}) + +local fmat2 = CreateMaterial("TFA_DOF_Material5", "Refract", { + ["$model"] = "1", + ["$alpha"] = "1", + ["$alphatest"] = "1", + ["$normalmap"] = "effects/flat_normal", + ["$refractamount"] = "0.1", + ["$vertexalpha"] = "1", + ["$vertexcolor"] = "1", + ["$translucent"] = "1", + ["$forcerefract"] = "0", + ["$bluramount"] = "0.9", + ["$nofog"] = "1" +}) + +local fmat3 = CreateMaterial("TFA_DOF_Material16", "Refract", { + ["$model"] = "1", + ["$alpha"] = "1", + ["$alphatest"] = "1", + ["$normalmap"] = "effects/flat_normal", + ["$refractamount"] = "0.1", + ["$vertexalpha"] = "1", + ["$vertexcolor"] = "1", + ["$translucent"] = "1", + ["$forcerefract"] = "0", + ["$bluramount"] = "0.8", + ["$nofog"] = "1" +}) + +local white = CreateMaterial("TFA_DOF_White", "UnlitGeneric", { + ["$alpha"] = "0", + ["$basetexture"] = "models/debug/debugwhite" +}) + +TFA.LastRTUpdate = TFA.LastRTUpdate or UnPredictedCurTime() + +hook.Add("PreDrawViewModel", "TFA_DrawViewModel", function(vm, plyv, wep) + if not vm or not plyv or not wep then return end + if not wep.IsTFAWeapon then return end + + if supports == nil then + supports = render.SupportsPixelShaders_1_4() and render.SupportsPixelShaders_2_0() and render.SupportsVertexShaders_2_0() + + if not supports then + print("[TFA] Your videocard does not support pixel shaders! DoF of Iron Sights is disabled!") + end + end + + if not supports then return end + + if not cl_tfa_fx_dof then + cl_tfa_fx_dof = GetConVar("cl_tfa_fx_ads_dof") + end + + if not cl_tfa_fx_dof or not cl_tfa_fx_dof:GetBool() then return end + if not wep.AllowIronSightsDoF then return end + local aimingDown = wep:GetIronSightsProgress() > 0.4 + local scoped = TFA.LastRTUpdate > UnPredictedCurTime() or wep:GetStatL("Scoped") + + if aimingDown and not scoped then + if hook.Run("TFA_AllowDoFDraw", wep, plyv, vm) == false then return end + wep.__TFA_AimDoFFrame = FrameNumber() + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilTestMask(0) + render.SetStencilWriteMask(1) + render.SetStencilReferenceValue(1) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.OverrideColorWriteEnable(true, true) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_KEEP) + end +end) + +local transparent = Color(0, 0, 0, 0) +local color_white = Color(255, 255, 255) +local STOP = false + +local function DrawDOF(muzzledata,fwd2) + local w, h = ScrW(), ScrH() + render.SetMaterial(fmat) + cam.Start2D() + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(fmat) + surface.DrawTexturedRect(0, 0, w, h) + cam.End2D() + + if muzzledata then + -- :POG: + render.SetMaterial(fmat2) + + for i = 28, 2, -1 do + render.UpdateScreenEffectTexture() + render.DrawSprite(muzzledata.Pos - fwd2 * i * 3, 200, 200, color_white) + end + end + + render.SetMaterial(fmat3) + cam.Start2D() + surface.SetMaterial(fmat3) + + for i = 0, 32 do + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(0, h / 1.6 + h / 2 * i / 32, w, h / 2) + end + + cam.End2D() +end + +hook.Add("PostDrawViewModel", "TFA_DrawViewModel", function(vm, plyv, wep) + if not wep.IsTFAWeapon then return end + + if not supports then + wep:ViewModelDrawnPost() + return + end + + if not cl_tfa_fx_dof then + cl_tfa_fx_dof = GetConVar("cl_tfa_fx_ads_dof") + end + + if not cl_tfa_fx_dof_hd then + cl_tfa_fx_dof_hd = GetConVar("cl_tfa_fx_ads_dof_hd") + end + + if not cl_tfa_fx_dof or not cl_tfa_fx_dof:GetBool() then + wep:ViewModelDrawnPost() + return + end + + if not wep:GetStatL("AllowIronSightsDoF") then + wep:ViewModelDrawnPost() + return + end + + local aimingDown = wep:GetIronSightsProgress() > 0.4 + local eangles = EyeAngles() + local fwd2 = vm:GetAngles():Forward() + local scoped = TFA.LastRTUpdate > UnPredictedCurTime() + + if aimingDown and not scoped and wep.__TFA_AimDoFFrame == FrameNumber() then + fmat:SetFloat("$alpha", wep:GetIronSightsProgress()) + + local muzzle = wep:GetStatL("IronSightsDoF_FocusAttachment") + if not muzzle then + wep:UpdateMuzzleAttachment() + wep:SetStatRawL("IronSightsDoF_FocusAttachment", wep.MuzzleAttachmentRaw) + + muzzle = wep:GetStatL("IronSightsDoF_FocusAttachment") + end + + muzzle = hook.Run("TFA_GetDoFMuzzleAttachmentID", wep, plyv, vm, muzzle) or muzzle + + local muzzledata + if muzzle and muzzle ~= 0 then + muzzledata = vm:GetAttachment(muzzle) + end + + local hands = plyv:GetHands() + + if IsValid(hands) and wep.UseHands then + render.OverrideColorWriteEnable(true, false) + STOP = true + local candraw = hook.Run("PreDrawPlayerHands", hands, vm, plyv, wep) + STOP = false + if candraw ~= true then + if wep.ViewModelFlip then + render.CullMode(MATERIAL_CULLMODE_CW) + end + + hands:DrawModel() + + if wep.ViewModelFlip then + render.CullMode(MATERIAL_CULLMODE_CCW) + end + end + + render.OverrideColorWriteEnable(false, false) + end + + if muzzledata then + render.SetStencilPassOperation(STENCIL_ZERO) + render.SetMaterial(white) + render.DrawSprite(muzzledata.Pos - fwd2 * 6 + eangles:Up() * 4, 30, 30, transparent) + render.SetStencilPassOperation(STENCIL_REPLACE) + end + + render.SetStencilTestMask(1) + render.SetStencilWriteMask(2) + render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.UpdateScreenEffectTexture() + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + render.PushFilterMag(TEXFILTER.ANISOTROPIC) + if cl_tfa_fx_dof_hd and cl_tfa_fx_dof_hd:GetBool() then + DrawDOF(muzzledata,fwd2) + else + DrawToyTown(3,ScrH() * 2 / 3 ) + end + render.PopFilterMin() + render.PopFilterMag() + --render.PopRenderTarget() + render.SetStencilEnable(false) + end + + wep:ViewModelDrawnPost() +end) + +hook.Add("PreDrawPlayerHands", "TFA_DrawViewModel", function(hands, vm, plyv, wep) + if STOP then return end + if not wep.IsTFAWeapon then return end + if not supports then return end + + if not cl_tfa_fx_dof then + cl_tfa_fx_dof = GetConVar("cl_tfa_fx_ads_dof") + end + + if not cl_tfa_fx_dof or not cl_tfa_fx_dof:GetBool() then return end + if not wep.AllowIronSightsDoF then return end + if TFA.LastRTUpdate > UnPredictedCurTime() then return end + if wep:GetIronSightsProgress() > 0.4 then return true end +end) + +hook.Add("PostDrawPlayerHands", "TFA_DrawViewModel", function(hands, vm, plyv, wep) + if not wep.IsTFAWeapon then return end + + wep:ViewModelDrawnPostFinal() +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/sv_tfa_settingsmenu.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/sv_tfa_settingsmenu.lua new file mode 100644 index 0000000..1f3c992 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/sv_tfa_settingsmenu.lua @@ -0,0 +1,27 @@ +local IsSinglePlayer = game.SinglePlayer() + +util.AddNetworkString("TFA_SetServerCommand") + +local function QueueConVarChange(convarname, convarvalue) + if not convarname or not convarvalue then return end + + timer.Create("tfa_cvarchange_" .. convarname, 0.1, 1, function() + if not string.find(convarname, "_tfa") or not GetConVar(convarname) then return end -- affect only TFA convars + + RunConsoleCommand(convarname, convarvalue) + end) +end + +local function ChangeServerOption(_length, _player) + local _cvarname = net.ReadString() + local _value = net.ReadString() + + if IsSinglePlayer then return end + if not IsValid(_player) or not _player:IsAdmin() then return end + + QueueConVarChange(_cvarname, _value) +end + +net.Receive("TFA_SetServerCommand", ChangeServerOption) + +TFA.BASE_LOAD_COMPLETE = true diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_ammo.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_ammo.lua new file mode 100644 index 0000000..69e7cb6 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_ammo.lua @@ -0,0 +1,17 @@ +--[[Bow Ammo]] +-- +game.AddAmmoType({ + name = "tfbow_arrow", + dmgtype = DMG_CLUB, + tracer = 0, + minsplash = 5, + maxsplash = 5 +}) + +game.AddAmmoType({ + name = "tfbow_bolt", + dmgtype = DMG_CLUB, + tracer = 0, + minsplash = 5, + maxsplash = 5 +}) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_attachments.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_attachments.lua new file mode 100644 index 0000000..a1e238b --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_attachments.lua @@ -0,0 +1,408 @@ +TFA.Attachments = TFA.Attachments or {} +TFA.Attachments.Atts = {} + +TFA.Attachments.Colors = { + ["active"] = Color(252, 151, 50, 255), + ["error"] = Color(225, 0, 0, 255), + ["error_attached"] = Color(140, 80, 30, 255), + ["background"] = Color(15, 15, 15, 64), + ["primary"] = Color(245, 245, 245, 255), + ["secondary"] = Color(153, 253, 220, 255), + ["+"] = Color(128, 255, 128, 255), + ["-"] = Color(255, 128, 128, 255), + ["="] = Color(192, 192, 192, 255) +} + +TFA.Attachments.UIPadding = 2 +TFA.Attachments.IconSize = 64 +TFA.Attachments.CategorySpacing = 128 + +if SERVER then + util.AddNetworkString("TFA_Attachment_Set") + util.AddNetworkString("TFA_Attachment_SetStatus") + util.AddNetworkString("TFA_Attachment_Reload") + util.AddNetworkString("TFA_Attachment_Request") + + local function UpdateWeapon(wep, ply) + for category, data in pairs(wep.Attachments or {}) do + if type(category) ~= "string" then + net.Start("TFA_Attachment_Set") + net.WriteEntity(wep) + + net.WriteUInt(category, 8) + if data.atts and data.atts[data.sel] then + net.WriteString(data.atts[data.sel]) + else + net.WriteString("") + end + + net.Send(ply) + end + end + end + + net.Receive("TFA_Attachment_Request", function(len, ply) + if not IsValid(ply) then return end + local wep = net.ReadEntity() + if not IsValid(wep) or not wep.IsTFAWeapon or not wep.HasInitAttachments or wep.AttachmentCount < 1 then return end + local ctime = SysTime() + + local currentScheduleRequest = ply.__TFA_Base_Next_Attachment_Request or ctime + local nextScheduleRequest = math.max(ctime + 0.2, currentScheduleRequest + 0.2) + ply.__TFA_Base_Next_Attachment_Request = nextScheduleRequest + + if currentScheduleRequest <= ctime then + UpdateWeapon(wep, ply) + -- elseif currentScheduleRequest - ctime >= 10 then + -- ply:Kick("TFA_Attachment_Request spam") + else + timer.Simple(nextScheduleRequest - ctime, function() + if IsValid(ply) and IsValid(wep) then + UpdateWeapon(wep, ply) + end + end) + end + end) + + net.Receive("TFA_Attachment_Set", function(len, ply) + local wep = ply:GetWeapon(net.ReadString()) + if not IsValid(wep) or not wep.IsTFAWeapon then return end + + local cat = net.ReadUInt(8) + local ind = net.ReadString() + local status = wep:SetTFAAttachment(cat, ind, ply) + + net.Start("TFA_Attachment_SetStatus") + net.WriteEntity(wep) + net.WriteBool(status) + + if not status then + if wep.Attachments and wep.Attachments[cat] then + local data = wep.Attachments[cat] + net.WriteUInt(cat, 8) + + if data.atts and data.atts[data.sel] then + net.WriteString(data.atts[data.sel]) + else + net.WriteString("") + end + end + end + + net.Send(ply) + end) +else + sql.Query([[ + CREATE TABLE IF NOT EXISTS tfa_savedattachments ( + class VARCHAR(80) NOT NULL, + atts TEXT NOT NULL, + PRIMARY KEY (class) + ) + ]]) + + net.Receive("TFA_Attachment_Set", function(len) + local wep = net.ReadEntity() + + local cat = net.ReadUInt(8) + local ind = net.ReadString() + + if IsValid(wep) and wep.SetTFAAttachment then + wep:SetTFAAttachment(cat, ind, false) + end + end) + + net.Receive("TFA_Attachment_Reload", function(len) + TFAUpdateAttachments() + end) + + net.Receive("TFA_Attachment_SetStatus", function(len) + local weapon = net.ReadEntity() + if not IsValid(weapon) then return end + local status = net.ReadBool() + + if status then + weapon:SaveAttachments() + return + end + surface.PlaySound("buttons/button2.wav") + + local cat = net.ReadUInt(8) + local ind = net.ReadString() + weapon:SetTFAAttachment(cat, ind, false) + end) + + local function request(self) + if self._TFA_Attachment_Request then return end + if not self.HasInitAttachments or self.AttachmentCount < 1 then return end + net.Start("TFA_Attachment_Request") + net.WriteEntity(self) + net.SendToServer() + self._TFA_Attachment_Request = true + end + + hook.Add("NotifyShouldTransmit", "TFA_AttachmentsRequest", function(self, notDormant) + if not self.IsTFAWeapon or not notDormant then return end + request(self) + end) + + hook.Add("NetworkEntityCreated", "TFA_AttachmentsRequest", function(self) + timer.Simple(0, function() + if not IsValid(self) or not self.IsTFAWeapon then return end + request(self) + end) + end) + + hook.Add("OnEntityCreated", "TFA_AttachmentsRequest", function(self) + timer.Simple(0, function() + if not IsValid(self) or not self.IsTFAWeapon then return end + request(self) + end) + end) + + local LoadQuery = [[SELECT atts FROM tfa_savedattachments WHERE class = '%s']] + function TFA.GetSavedAttachments(Weapon) + if not IsValid(Weapon) or not Weapon.IsTFAWeapon then return end + + local data = sql.QueryRow(string.format(LoadQuery, sql.SQLStr(Weapon:GetClass(), true))) + + if data and data.atts then + return util.JSONToTable(data.atts) + end + end + + local SaveQuery = [[REPLACE INTO tfa_savedattachments (class, atts) VALUES ('%s', '%s');]] + function TFA.SetSavedAttachments(Weapon) + if not IsValid(Weapon) or not Weapon.IsTFAWeapon or not next(Weapon.Attachments or {}) then return end + + local seltbl = {} + for cat, catTbl in pairs(Weapon.Attachments or {}) do + if cat ~= "BaseClass" and catTbl.atts then + seltbl[cat] = catTbl.atts[catTbl.sel or -1] or "" + end + end + + return sql.Query(string.format(SaveQuery, sql.SQLStr(Weapon:GetClass(), true), sql.SQLStr(util.TableToJSON(seltbl), true))) + end +end + +local function basefunc(t, k) + if k == "Base" then return end + + if t.Base then + local bt = TFA.Attachments.Atts[t.Base] + if bt then return bt[k] end + end +end + +function TFA.Attachments.SetupBaseTable(id, path) + local ATTACHMENT = {} + + setmetatable(ATTACHMENT, { + __index = basefunc + }) + + ATTACHMENT.ID = id + + ProtectedCall(function() + hook.Run("TFABase_PreBuildAttachment", id, path, ATTACHMENT) + end) + + return ATTACHMENT +end + +function TFA.Attachments.Register(id, ATTACHMENT, path) + if istable(id) then + ATTACHMENT = id + id = ATTACHMENT.ID + end + + assert(istable(ATTACHMENT), "Invalid attachment argument provided") + assert(isstring(id), "Invalid attachment ID provided") + local size = table.Count(ATTACHMENT) + + if size == 0 or size == 1 and ATTACHMENT.ID ~= nil then + local id2 = id or ATTACHMENT.ID + + if id2 then + ErrorNoHalt("[TFA Base] Attempt to register an empty attachment " .. id2 .. "\n") + else + ErrorNoHalt("[TFA Base] Attempt to register an empty attachment\n") + end + + ErrorNoHalt(debug.traceback() .. "\n") + MsgC("\n") + return + end + + ProtectedCall(function() + hook.Run("TFABase_BuildAttachment", id, path, ATTACHMENT) + end) + + ATTACHMENT.ID = ATTACHMENT.ID or id + + if ATTACHMENT.ID and ATTACHMENT.ID ~= "base" then + ATTACHMENT.Base = ATTACHMENT.Base or "base" + end + + --[[if not TFA_ATTACHMENT_ISUPDATING and istable(ATTACHMENT.WeaponTable) then + TFA.MigrateStructure(ATTACHMENT, ATTACHMENT.WeaponTable, id or "", false) + end]] + + ProtectedCall(function() + hook.Run("TFABase_RegisterAttachment", id, ATTACHMENT) + end) + + TFA.Attachments.Atts[ATTACHMENT.ID or ATTACHMENT.Name] = ATTACHMENT +end + +function TFA.Attachments.RegisterFromTable(id, tbl, path) + local status + + ProtectedCall(function() + status = hook.Run("TFABase_ShouldLoadAttachment", id, path) + end) + + if status == false then return end + + local ATTACHMENT = TFA.Attachments.SetupBaseTable(id) + + for k, v in pairs(tbl) do + ATTACHMENT[k] = v + end + + TFA.Attachments.Register(id, ATTACHMENT) +end + +TFARegisterAttachment = TFA.Attachments.Register +TFA.Attachments.Path = "tfa/att/" +TFA.Attachments.Path_Batch = "tfa/attbatch/" +TFA_ATTACHMENT_ISUPDATING = false + +local inheritanceCached = {} +local missingBaseWarningShown = {} + +local function patchInheritance(t, basetbl) + if t.Base and t.Base ~= "base" and not TFA.Attachments.Atts[t.Base] then + if t.ID and not missingBaseWarningShown[t.ID] then + missingBaseWarningShown[t.ID] = true + + print("[TFA Base] [!] Attachment '" .. t.ID .. "' depends on unknown attachment '" .. t.Base .. "'!") + end + + t.Base = "base" + end + + if not basetbl and t.Base then + basetbl = TFA.Attachments.Atts[t.Base] + + if basetbl and istable(basetbl) and basetbl.ID and not inheritanceCached[basetbl.ID] then + inheritanceCached[basetbl.ID] = true + patchInheritance(basetbl) + end + end + + if not (basetbl and istable(basetbl)) then return end + + for k, v in pairs(t) do + local baseT = basetbl[k] + + if istable(v) and baseT then + patchInheritance(v, baseT) + end + end + + for k, v in pairs(basetbl) do + if rawget(t, k) == nil then + t[k] = v + end + end +end + +function TFAUpdateAttachments(network) + if SERVER and network ~= false then + net.Start("TFA_Attachment_Reload") + net.Broadcast() + end + + TFA.AttachmentColors = TFA.Attachments.Colors --for compatibility + TFA.Attachments.Atts = {} + TFA_ATTACHMENT_ISUPDATING = true + local tbl = file.Find(TFA.Attachments.Path .. "*base*", "LUA") + local addtbl = file.Find(TFA.Attachments.Path .. "*", "LUA") + + for _, v in ipairs(addtbl) do + if not string.find(v, "base") then + table.insert(tbl, #tbl + 1, v) + end + end + + table.sort(tbl) + + for _, fname in ipairs(tbl) do + local path = TFA.Attachments.Path .. fname + local id = fname:lower():Replace(".lua", "") + + local status + + ProtectedCall(function() + status = hook.Run("TFABase_ShouldLoadAttachment", id, path) + end) + + if status ~= false then + ATTACHMENT = TFA.Attachments.SetupBaseTable(id, path) + + if SERVER then + AddCSLuaFile(path) + include(path) + else + include(path) + end + + TFA.Attachments.Register(id, ATTACHMENT, path) + ATTACHMENT = nil + end + end + + local tbl2 = file.Find(TFA.Attachments.Path_Batch .. "*", "LUA") + + for _, fname in ipairs(tbl2) do + local path = TFA.Attachments.Path_Batch .. fname + + if SERVER then + AddCSLuaFile(path) + include(path) + else + include(path) + end + end + + ProtectedCall(function() + hook.Run("TFAAttachmentsLoaded") + end) + + for _, v in pairs(TFA.Attachments.Atts) do + patchInheritance(v) + + --[[if istable(v.WeaponTable) then + TFA.MigrateStructure(v, v.WeaponTable, v.ID or "", false) + end]] + end + + ProtectedCall(function() + hook.Run("TFAAttachmentsInitialized") + end) + + TFA_ATTACHMENT_ISUPDATING = false + + TFA.ATTACHMENTS_LOADED = true +end + +if not VLL2_FILEDEF and TFA.ATTACHMENTS_LOADED then + TFAUpdateAttachments(false) +end + +concommand.Add("sv_tfa_attachments_reload", function(ply, cmd, args, argStr) + if SERVER and (not IsValid(ply) or ply:IsAdmin()) then + TFAUpdateAttachments() + end +end, function() end, "Reloads all TFA Attachments", {FCVAR_SERVER_CAN_EXECUTE}) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_ballistics.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_ballistics.lua new file mode 100644 index 0000000..491c8b9 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_ballistics.lua @@ -0,0 +1,415 @@ +-- Degrees to accuracy vector, Valve's formula from SDK 2013 +TFA.DegreesToAccuracy = math.sin((math.pi / 180) / 2) -- approx. 0.00873 + +--default cvar integration +local cv_gravity = GetConVar("sv_gravity") + +--[[local function TimeScale(v) + return v * game.GetTimeScale() / TFA.Ballistics.SubSteps +end]] + +--init code +TFA.Ballistics = TFA.Ballistics or {} +TFA.Ballistics.Enabled = false +TFA.Ballistics.Gravity = Vector(0, 0, -cv_gravity:GetFloat()) +TFA.Ballistics.Bullets = TFA.Ballistics.Bullets or {} +TFA.Ballistics.Bullets.bullet_registry = TFA.Ballistics.Bullets.bullet_registry or {} +TFA.Ballistics.BulletLife = 10 +TFA.Ballistics.UnitScale = TFA.UnitScale or 39.3701 --meters to inches +TFA.Ballistics.AirResistance = 1 +TFA.Ballistics.WaterResistance = 3 +TFA.Ballistics.WaterEntranceResistance = 6 + +TFA.Ballistics.DamageVelocityLUT = { + [13] = 350, --shotgun + [25] = 425, --mp5k etc. + [35] = 900, --ak-12 + [65] = 830, --SVD + [120] = 1100 --sniper cap +} + +TFA.Ballistics.VelocityMultiplier = 1 +TFA.Ballistics.SubSteps = 1 +TFA.Ballistics.BulletCreationNetString = "TFABallisticsBullet" + +TFA.Ballistics.TracerStyles = { + [0] = "", + [1] = "tfa_bullet_smoke_tracer", + [2] = "tfa_bullet_fire_tracer" +} + +setmetatable(TFA.Ballistics.TracerStyles, { + ["__index"] = function(t, k) return t[math.Round(tonumber(k) or 1)] or t[1] end +}) + +if SERVER then + util.AddNetworkString(TFA.Ballistics.BulletCreationNetString) +end + +--bullet class +local function IncludeClass(fn) + include("tfa/ballistics/" .. fn .. ".lua") + AddCSLuaFile("tfa/ballistics/" .. fn .. ".lua") +end + +IncludeClass("bullet") +--cvar code +local function CreateReplConVar(cvarname, cvarvalue, description, ...) + return CreateConVar(cvarname, cvarvalue, CLIENT and {FCVAR_REPLICATED} or {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY}, description, ...) +end -- replicated only on clients, archive/notify on server + +local cv_enabled = CreateReplConVar("sv_tfa_ballistics_enabled", "0", "Enable TFA Ballistics?") +local cv_bulletlife = CreateReplConVar("sv_tfa_ballistics_bullet_life", 10, "Time to process bullets before removing.") +local cv_res_air = CreateReplConVar("sv_tfa_ballistics_bullet_damping_air", 1, "Air resistance, which makes bullets arc faster.") +local cv_res_water = CreateReplConVar("sv_tfa_ballistics_bullet_damping_water", 3, "Water resistance, which makes bullets arc faster in water.") +local cv_vel = CreateReplConVar("sv_tfa_ballistics_bullet_velocity", 1, "Global velocity multiplier for TFA ballistics bullets.") +local cv_substep = CreateReplConVar("sv_tfa_ballistics_substeps", 1, "Substeps for ballistics; more is more precise, at the cost of performance.") +local sv_tfa_ballistics_custom_gravity = CreateReplConVar("sv_tfa_ballistics_custom_gravity", 0, "Enable sv_gravity override for ballistics") +local sv_tfa_ballistics_custom_gravity_value = CreateReplConVar("sv_tfa_ballistics_custom_gravity_value", 0, "Z velocity down of custom gravity") +CreateReplConVar("sv_tfa_ballistics_mindist", -1, "Minimum distance to activate; -1 for always.") + +local function updateCVars() + TFA.Ballistics.BulletLife = cv_bulletlife:GetFloat() + TFA.Ballistics.AirResistance = cv_res_air:GetFloat() + TFA.Ballistics.WaterResistance = cv_res_water:GetFloat() + TFA.Ballistics.WaterEntranceResistance = TFA.Ballistics.WaterResistance * 2 + TFA.Ballistics.VelocityMultiplier = cv_vel:GetFloat() + + if sv_tfa_ballistics_custom_gravity:GetBool() then + TFA.Ballistics.Gravity.z = -sv_tfa_ballistics_custom_gravity_value:GetFloat() + else + TFA.Ballistics.Gravity.z = -cv_gravity:GetFloat() + end + + TFA.Ballistics.Enabled = cv_enabled:GetBool() + TFA.Ballistics.SubSteps = cv_substep:GetInt() +end + +cvars.AddChangeCallback("sv_tfa_ballistics_enabled", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_bullet_life", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_bullet_damping_air", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_bullet_damping_water", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_bullet_velocity", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_substeps", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_mindist", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_custom_gravity", updateCVars, "TFA") +cvars.AddChangeCallback("sv_tfa_ballistics_custom_gravity_value", updateCVars, "TFA") +cvars.AddChangeCallback("sv_gravity", updateCVars, "TFA Ballistics") +updateCVars() + +--client cvar code +local cv_receive, cv_tracers_style, cv_tracers_mp + +if CLIENT then + cv_receive = CreateClientConVar("cl_tfa_ballistics_mp", "1", true, false, "Receive bullet data from other players?") + CreateClientConVar("cl_tfa_ballistics_fx_bullet", "1", true, false, "Display bullet models for each TFA ballistics bullet?") + cv_tracers_style = CreateClientConVar("cl_tfa_ballistics_fx_tracers_style", "1", true, false, "Style of tracers for TFA ballistics? 0=disable,1=smoke") + cv_tracers_mp = CreateClientConVar("cl_tfa_ballistics_fx_tracers_mp", "1", true, false, "Enable tracers for other TFA ballistics users?") + CreateClientConVar("cl_tfa_ballistics_fx_tracers_adv", "1", true, false, "Enable advanced tracer calculations for other users? This corrects smoke trail to their barrel") +end + +--utility func +local function Remap(inp, u, v, x, y) + return (inp - u) / (v - u) * (y - x) + x +end + +--Accessors +local CopyTable = table.Copy + +function TFA.Ballistics.Bullets:Add(bulletStruct, originalBulletData) + local bullet = TFA.Ballistics:Bullet(bulletStruct) + bullet.bul = CopyTable(originalBulletData or bullet.bul) + bullet.last_update = CurTime() - TFA.FrameTime() + + table.insert(self.bullet_registry, bullet) + + bullet:_setup() + + if SERVER and game.GetTimeScale() > 0.99 then + -- always update bullet since they are being added from predicted hook + bullet:Update(CurTime()) + end +end + +function TFA.Ballistics.Bullets:Update(ply) + --local delta = TimeScale(SysTime() - (self.lastUpdate or (SysTime() - FrameTime()))) + local delta = CurTime() + + --self.lastUpdate = SysTime() + local toremove + local lply = CLIENT and LocalPlayer() + + for i, bullet in ipairs(self.bullet_registry) do + if bullet.delete then + if not toremove then + toremove = {} + end + + table.insert(toremove, i) + elseif not ply and not bullet.playerOwned or CLIENT and bullet.owner ~= lply or ply == bullet.owner then + for _ = 1, TFA.Ballistics.SubSteps do + bullet:Update(delta) + end + end + end + + if toremove then + for i = #toremove, 1, -1 do + table.remove(self.bullet_registry, toremove[i]) + end + end +end + +function TFA.Ballistics:AutoDetectVelocity(damage) + local lutMin, lutMax, LUT, DMGs + LUT = self.DamageVelocityLUT + DMGs = table.GetKeys(LUT) + table.sort(DMGs) + + for _, v in ipairs(DMGs) do + if v < damage then + lutMin = v + elseif lutMin then + lutMax = v + break + end + end + + if not lutMax then + lutMax = DMGs[#DMGs] + lutMin = DMGs[#DMGs - 1] + elseif not lutMin then + lutMin = DMGs[1] + lutMax = DMGs[2] + end + + return Remap(damage, lutMin, lutMax, LUT[lutMin], LUT[lutMax]) +end + +function TFA.Ballistics:ShouldUse(wep) + if not IsValid(wep) or not wep.IsTFAWeapon then + return false + end + + local shouldUse = wep:GetStatL("UseBallistics") + + if shouldUse == nil then + if wep:GetStatL("TracerPCF") then + return false + end + + return self.Enabled + else + return shouldUse + end +end + +local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy") + +function TFA.Ballistics:FireBullets(wep, bulletStruct, angIn, bulletOverride) + if not IsValid(wep) then return end + if not IsValid(wep:GetOwner()) then return end + + local vel + + if bulletStruct.Velocity then + vel = bulletStruct.Velocity + elseif wep.GetStat and wep:GetStatL("Primary.Velocity") then + vel = wep:GetStatL("Primary.Velocity") * TFA.Ballistics.UnitScale + elseif wep.Primary and wep.Primary.Velocity then + vel = wep.Primary.Velocity * TFA.Ballistics.UnitScale + elseif wep.Velocity then + vel = wep.Velocity * TFA.Ballistics.UnitScale + else + local dmg + + if wep.GetStat and wep:GetStatL("Primary.Damage") then + dmg = wep:GetStatL("Primary.Damage") + else + dmg = wep.Primary.Damage or wep.Damage or 30 + end + + vel = TFA.Ballistics:AutoDetectVelocity(dmg) * TFA.Ballistics.UnitScale + end + + vel = vel * (TFA.Ballistics.VelocityMultiplier or 1) + + local oldNum = bulletStruct.Num + bulletStruct.Num = 1 + bulletStruct.IsBallistics = true + + local owner = wep:GetOwner() + local isnpc = owner:IsNPC() + local ac = bulletStruct.Spread + local sharedRandomSeed = "Ballistics" .. CurTime() + + for i = 1, oldNum do + local ang + + if angIn then + ang = angIn + else + ang = owner:GetAimVector():Angle() + + if sv_tfa_recoil_legacy:GetBool() and not isnpc then + ang:Add(owner:GetViewPunchAngles()) + else + ang.p = ang.p + wep:GetViewPunchP() + ang.y = ang.y + wep:GetViewPunchY() + end + end + + if not angIn then + ang:RotateAroundAxis(ang:Up(), util.SharedRandom(sharedRandomSeed, -ac.x * 45, ac.x * 45, 0 + i)) + ang:RotateAroundAxis(ang:Right(), util.SharedRandom(sharedRandomSeed, -ac.y * 45, ac.y * 45, 1 + i)) + end + + local struct = { + owner = owner, --used for dmginfo SetAttacker + inflictor = wep, --used for dmginfo SetInflictor + damage = bulletStruct.Damage, --floating point number representing inflicted damage + force = bulletStruct.Force, + pos = bulletOverride and bulletStruct.Src or owner:GetShootPos(), --b.Src, --vector representing current position + velocity = (bulletOverride and bulletStruct.Dir or ang:Forward()) * vel, --b.Dir * vel, --vector representing movement velocity + model = wep.BulletModel or bulletStruct.Model, --optional variable representing the given model + smokeparticle = bulletStruct.SmokeParticle, + customPosition = bulletStruct.CustomPosition or bulletOverride, + IgnoreEntity = bulletStruct.IgnoreEntity + } + + if CLIENT then + if not struct.smokeparticle then + struct.smokeparticle = TFA.Ballistics.TracerStyles[cv_tracers_style:GetInt()] + end + end + + self.Bullets:Add(struct, bulletStruct) + + if SERVER then + net.Start(TFA.Ballistics.BulletCreationNetString) + + net.WriteEntity(struct.owner) + net.WriteEntity(struct.inflictor) + net.WriteFloat(struct.damage) + net.WriteFloat(struct.force) + net.WriteVector(struct.pos) + + net.WriteDouble(struct.velocity.x) + net.WriteDouble(struct.velocity.y) + net.WriteDouble(struct.velocity.z) + + net.WriteString(struct.model or '') + net.WriteString(struct.smokeparticle or '') + net.WriteBool(struct.customPosition == true) + net.WriteEntity(struct.IgnoreEntity or NULL) + + net.WriteVector(bulletStruct.Src) + net.WriteNormal(bulletStruct.Dir) + net.WriteEntity(bulletStruct.Attacker) + net.WriteVector(bulletStruct.Spread) + net.WriteFloat(vel) + + if game.SinglePlayer() or isnpc then + net.SendPVS(struct.pos) + else + net.SendOmit(owner) + end + end + end +end + +function TFA.Ballistics.Bullets:Render() + for i = 1, #self.bullet_registry do + self.bullet_registry[i]:Render() + end +end + +local sp = game.SinglePlayer() + +--Netcode and Hooks +if CLIENT then + net.Receive(TFA.Ballistics.BulletCreationNetString, function() + if not sp and not cv_receive:GetBool() then return end + + local owner = net.ReadEntity() + local inflictor = net.ReadEntity() + local damage = net.ReadFloat() + local force = net.ReadFloat() + local pos = net.ReadVector() + local velocity = Vector(net.ReadDouble(), net.ReadDouble(), net.ReadDouble()) + local model = net.ReadString() + local smokeparticle = net.ReadString() + local customPosition = net.ReadBool() + local IgnoreEntity = net.ReadEntity() + + + local Src = net.ReadVector() + local Dir = net.ReadNormal() + local Attacker = net.ReadEntity() + local Spread = net.ReadVector() + local Velocity = net.ReadFloat() + + if not IsValid(owner) or not IsValid(inflictor) then return end + + if not cv_tracers_mp:GetBool() and owner ~= LocalPlayer() then + smokeparticle = "" + elseif smokeparticle == "" then + smokeparticle = TFA.Ballistics.TracerStyles[cv_tracers_style:GetInt()] + end + + local struct = { + owner = owner, + inflictor = inflictor, + damage = damage, + force = force, + pos = pos, + velocity = velocity, + model = model ~= "" and model or nil, + smokeparticle = smokeparticle, + customPosition = customPosition, + IgnoreEntity = IgnoreEntity, + } + + local bulletStruct = { + Damage = damage, + Force = force, + Num = 1, + Src = Src, + Dir = Dir, + Attacker = Attacker, + Spread = Spread, + SmokeParticle = smokeparticle, + CustomPosition = customPosition, + Model = model ~= "" and model or nil, + Velocity = Velocity, + IsBallistics = true, + } + + TFA.Ballistics.Bullets:Add(struct, bulletStruct) + end) +end + +if CLIENT then + hook.Add("FinishMove", "TFABallisticsTick", function(self) + if IsFirstTimePredicted() then + TFA.Ballistics.Bullets:Update(self) + end + end) +else + hook.Add("PlayerPostThink", "TFABallisticsTick", function(self) + TFA.Ballistics.Bullets:Update(self) + end) +end + +hook.Add("Tick", "TFABallisticsTick", function() + TFA.Ballistics.Bullets:Update() + + if CLIENT and sp then + TFA.Ballistics.Bullets:Update(LocalPlayer()) + end +end) + +--Rendering +hook.Add("PostDrawOpaqueRenderables", "TFABallisticsRender", function() + TFA.Ballistics.Bullets:Render() +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_bodygroups.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_bodygroups.lua new file mode 100644 index 0000000..a47d96f --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_bodygroups.lua @@ -0,0 +1,15 @@ +local sp = game.SinglePlayer() + +hook.Add("PlayerSwitchWeapon", "TFA_Bodygroups_PSW", function(ply, oldwep, wep) + if not IsValid(wep) or not wep.IsTFAWeapon then return end + + timer.Simple(0, function() + if not IsValid(ply) or ply:GetActiveWeapon() ~= wep then return end + + wep:ApplyViewModelModifications() + + if sp then + wep:CallOnClient("ApplyViewModelModifications") + end + end) +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_commands.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_commands.lua new file mode 100644 index 0000000..18cee82 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_commands.lua @@ -0,0 +1,372 @@ +local function CreateReplConVar(cvarname, cvarvalue, description, ...) + return CreateConVar(cvarname, cvarvalue, CLIENT and {FCVAR_REPLICATED} or {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY}, description, ...) +end -- replicated only on clients, archive/notify on server + +-- Shared Convars + +if GetConVar("sv_tfa_soundscale") == nil then + CreateReplConVar("sv_tfa_soundscale", "1", "Scale sound pitch in accordance to timescale?") +end + +if GetConVar("sv_tfa_weapon_strip") == nil then + CreateReplConVar("sv_tfa_weapon_strip", "0", "Allow the removal of empty weapons?") +end + +if GetConVar("sv_tfa_spread_legacy") == nil then + CreateReplConVar("sv_tfa_spread_legacy", "0", "Use legacy spread algorithms?") +end + +if GetConVar("sv_tfa_cmenu") == nil then + CreateReplConVar("sv_tfa_cmenu", "1", "Allow custom context menu?") +end + +if GetConVar("sv_tfa_cmenu_key") == nil then + CreateReplConVar("sv_tfa_cmenu_key", "-1", "Override the inspection menu key? Uses the KEY enum available on the gmod wiki. -1 to not.") +end + +if GetConVar("sv_tfa_range_modifier") == nil then + CreateReplConVar("sv_tfa_range_modifier", "0.5", "This controls how much the range affects damage. 0.5 means the maximum loss of damage is 0.5.") +end + +if GetConVar("sv_tfa_allow_dryfire") == nil then + CreateReplConVar("sv_tfa_allow_dryfire", "1", "Allow dryfire?") +end + +if GetConVar("sv_tfa_penetration_hardlimit") == nil then + CreateReplConVar("sv_tfa_penetration_hardlimit", "100", "Max number of objects we can penetrate through.") +end + +if GetConVar("sv_tfa_bullet_penetration_power_mul") == nil then + CreateReplConVar("sv_tfa_bullet_penetration_power_mul", "1", "Power multiplier. 1 or 1.5 for CS 1.6 experience, 0.25 for semi-realistic behavior") +end + +if GetConVar("sv_tfa_penetration_hitmarker") == nil then + CreateReplConVar("sv_tfa_penetration_hitmarker", "1", "Should penetrating bullet send hitmarker to attacker?") +end + +if GetConVar("sv_tfa_damage_multiplier") == nil then + CreateReplConVar("sv_tfa_damage_multiplier", "1", "Multiplier for TFA base projectile damage.") +end + +if GetConVar("sv_tfa_damage_multiplier_npc") == nil then + CreateReplConVar("sv_tfa_damage_multiplier_npc", "1", "Multiplier for TFA base projectile damage for NPCs.") +end + +if GetConVar("sv_tfa_damage_mult_min") == nil then + CreateReplConVar("sv_tfa_damage_mult_min", "0.95", "This is the lower range of a random damage factor.") +end + +if GetConVar("sv_tfa_damage_mult_max") == nil then + CreateReplConVar("sv_tfa_damage_mult_max", "1.05", "This is the higher range of a random damage factor.") +end + +if GetConVar("sv_tfa_melee_damage_npc") == nil then + CreateReplConVar("sv_tfa_melee_damage_npc", "1", "Damage multiplier against NPCs using TFA Melees.") +end + +if GetConVar("sv_tfa_melee_damage_ply") == nil then + CreateReplConVar("sv_tfa_melee_damage_ply", "0.65", "Damage multiplier against players using TFA Melees.") +end + +if GetConVar("sv_tfa_melee_blocking_timed") == nil then + CreateReplConVar("sv_tfa_melee_blocking_timed", "1", "Enable timed blocking?") +end + +if GetConVar("sv_tfa_melee_blocking_anglemult") == nil then + CreateReplConVar("sv_tfa_melee_blocking_anglemult", "1", "Players can block attacks in an angle around their view. This multiplies that angle.") +end + +if GetConVar("sv_tfa_melee_blocking_deflection") == nil then + CreateReplConVar("sv_tfa_melee_blocking_deflection", "1", "For weapons that can deflect bullets ( e.g. certain katans ), can you deflect bullets? Set to 1 to enable for parries, or 2 for all blocks.") +end + +if GetConVar("sv_tfa_melee_blocking_timed") == nil then + CreateReplConVar("sv_tfa_melee_blocking_timed", "1", "Enable timed blocking?") +end + +if GetConVar("sv_tfa_melee_blocking_stun_enabled") == nil then + CreateReplConVar("sv_tfa_melee_blocking_stun_enabled", "1", "Stun NPCs on block?") +end + +if GetConVar("sv_tfa_melee_blocking_stun_time") == nil then + CreateReplConVar("sv_tfa_melee_blocking_stun_time", "0.65", "How long to stun NPCs on block.") +end + +if GetConVar("sv_tfa_melee_doordestruction") == nil then + CreateReplConVar("sv_tfa_melee_doordestruction", "1", "Allow players to bash open doors?") +end + +if GetConVar("sv_tfa_door_respawn") == nil then + CreateReplConVar("sv_tfa_door_respawn", "-1", "Time for doors to respawn; -1 for never.") +end + +if GetConVar("sv_tfa_npc_randomize_atts") == nil then + CreateReplConVar("sv_tfa_npc_randomize_atts", "1", "Randomize NPC's weapons attachments.") +end + +local cv_dfc +if GetConVar("sv_tfa_default_clip") == nil then + cv_dfc = CreateReplConVar("sv_tfa_default_clip", "-1", "How many clips will a weapon spawn with? Negative reverts to default values.") +else + cv_dfc = GetConVar("sv_tfa_default_clip") +end + +local function TFAUpdateDefaultClip() + local dfc = cv_dfc:GetInt() + local weplist = weapons.GetList() + if not weplist or #weplist <= 0 then return end + + for _, v in pairs(weplist) do + local cl = v.ClassName and v.ClassName or v + local wep = weapons.GetStored(cl) + + if wep and (wep.IsTFAWeapon or string.find(string.lower(wep.Base and wep.Base or ""), "tfa")) then + if not wep.Primary then + wep.Primary = {} + end + + if not wep.Primary.TrueDefaultClip then + wep.Primary.TrueDefaultClip = wep.Primary.DefaultClip + end + + if not wep.Primary.TrueDefaultClip then + wep.Primary.TrueDefaultClip = 0 + end + + if dfc < 0 then + wep.Primary.DefaultClip = wep.Primary.TrueDefaultClip + else + if wep.Primary.ClipSize and wep.Primary.ClipSize > 0 then + wep.Primary.DefaultClip = wep.Primary.ClipSize * dfc + else + wep.Primary.DefaultClip = wep.Primary.TrueDefaultClip * 1 + end + end + end + end +end + +hook.Add("InitPostEntity", "TFADefaultClipPE", TFAUpdateDefaultClip) + +if TFAUpdateDefaultClip then + TFAUpdateDefaultClip() +end + +--if GetConVar("sv_tfa_default_clip") == nil then + +cvars.AddChangeCallback("sv_tfa_default_clip", function(convar_name, value_old, value_new) + TFAUpdateDefaultClip() +end, "TFAUpdateDefaultClip") + +local function sv_tfa_range_modifier() + for k, v in ipairs(ents.GetAll()) do + if v.IsTFAWeapon and v.Primary_TFA.RangeFalloffLUT_IsConverted then + v.Primary_TFA.RangeFalloffLUT = nil + v:AutoDetectRange() + end + end +end + +cvars.AddChangeCallback("sv_tfa_range_modifier", sv_tfa_range_modifier, "TFA") + +sv_tfa_range_modifier() + +if CLIENT then + hook.Add("InitPostEntity", "sv_tfa_range_modifier", sv_tfa_range_modifier) +end + +--end +if GetConVar("sv_tfa_unique_slots") == nil then + CreateReplConVar("sv_tfa_unique_slots", "1", "Give TFA-based Weapons unique slots? 1 for true, 0 for false. RESTART AFTER CHANGING.") +end + +if GetConVar("sv_tfa_spread_multiplier") == nil then + CreateReplConVar("sv_tfa_spread_multiplier", "1", "Increase for more spread, decrease for less.") +end + +if GetConVar("sv_tfa_force_multiplier") == nil then + CreateReplConVar("sv_tfa_force_multiplier", "1", "Arrow force multiplier (not arrow velocity, but how much force they give on impact).") +end + +if GetConVar("sv_tfa_recoil_multiplier") == nil then + CreateReplConVar("sv_tfa_recoil_multiplier", "1", "Recoil multiplier") +end + +if GetConVar("sv_tfa_knockback_multiplier") == nil then + CreateReplConVar("sv_tfa_knockback_multiplier", "1", "Knockback force multiplier") +end + +if GetConVar("sv_tfa_dynamicaccuracy") == nil then + CreateReplConVar("sv_tfa_dynamicaccuracy", "1", "Dynamic acuracy? (e.g.more accurate on crouch, less accurate on jumping.") +end + +if GetConVar("sv_tfa_ammo_detonation") == nil then + CreateReplConVar("sv_tfa_ammo_detonation", "1", "Ammo Detonation? (e.g. shoot ammo until it explodes) ") +end + +if GetConVar("sv_tfa_ammo_detonation_mode") == nil then + CreateReplConVar("sv_tfa_ammo_detonation_mode", "2", "Ammo Detonation Mode? (0=Bullets,1=Blast,2=Mix) ") +end + +if GetConVar("sv_tfa_ammo_detonation_chain") == nil then + CreateReplConVar("sv_tfa_ammo_detonation_chain", "1", "Ammo Detonation Chain? (0=Ammo boxes don't detonate other ammo boxes, 1 you can chain them together) ") +end + +if GetConVar("sv_tfa_scope_gun_speed_scale") == nil then + CreateReplConVar("sv_tfa_scope_gun_speed_scale", "0", "Scale player sensitivity based on player move speed?") +end + +if GetConVar("sv_tfa_bullet_penetration") == nil then + CreateReplConVar("sv_tfa_bullet_penetration", "1", "Allow bullet penetration?") +end + +if GetConVar("sv_tfa_bullet_doordestruction") == nil then + CreateReplConVar("sv_tfa_bullet_doordestruction", "1", "Allow to shoot down doors?") +end + +if GetConVar("sv_tfa_bullet_doordestruction_keep") == nil then + CreateReplConVar("sv_tfa_bullet_doordestruction_keep", "0", "Don't shoot door off hinges") +end + +if GetConVar("sv_tfa_npc_burst") == nil then + CreateReplConVar("sv_tfa_npc_burst", "0", "Whenever NPCs should fire in bursts like they do with HL2 weapons.") +end + +if GetConVar("sv_tfa_bullet_ricochet") == nil then + CreateReplConVar("sv_tfa_bullet_ricochet", "0", "Allow bullet ricochet?") +end + +if GetConVar("sv_tfa_bullet_randomseed") == nil then + CreateReplConVar("sv_tfa_bullet_randomseed", "0", "Populate extra seed serverside? This will cause spread to be out of sync with server!") +end + +if GetConVar("sv_tfa_debug") == nil then + CreateReplConVar("sv_tfa_debug", "0", "Enable debug mode?") +end + +if GetConVar("sv_tfa_holdtype_dynamic") == nil then + CreateReplConVar("sv_tfa_holdtype_dynamic", "1", "Allow dynamic holdtype?") +end + +if GetConVar("sv_tfa_arrow_lifetime") == nil then + CreateReplConVar("sv_tfa_arrow_lifetime", "30", "Arrow lifetime.") +end + +if GetConVar("sv_tfa_worldmodel_culldistance") == nil then + CreateReplConVar("sv_tfa_worldmodel_culldistance", "-1", "-1 to leave unculled. Anything else is feet*16.") +end + +if GetConVar("sv_tfa_reloads_legacy") == nil then + CreateReplConVar("sv_tfa_reloads_legacy", "0", "Enable legacy-style reloading?") +end + +if GetConVar("sv_tfa_recoil_legacy") == nil then + CreateReplConVar("sv_tfa_recoil_legacy", "0", "Enable legacy-style recoil? This will cause prediction issues in multiplayer. Always disabled for NPCs!") +end + +if GetConVar("sv_tfa_recoil_mul_p") == nil then + CreateReplConVar("sv_tfa_recoil_mul_p", "1", "Pitch kick multiplier for recoil") +end + +if GetConVar("sv_tfa_recoil_mul_y") == nil then + CreateReplConVar("sv_tfa_recoil_mul_y", "1", "Yaw kick multiplier for recoil") +end + +if GetConVar("sv_tfa_recoil_mul_p_npc") == nil then + CreateReplConVar("sv_tfa_recoil_mul_p_npc", "1", "Pitch kick multiplier for recoil for NPCs") +end + +if GetConVar("sv_tfa_recoil_mul_y_npc") == nil then + CreateReplConVar("sv_tfa_recoil_mul_y_npc", "1", "Yaw kick multiplier for recoil for NPCs") +end + +if GetConVar("sv_tfa_recoil_viewpunch_mul") == nil then + CreateReplConVar("sv_tfa_recoil_viewpunch_mul", "1", "Multiplier for viewpunch recoil (visual viewmodel recoil)") +end + +if GetConVar("sv_tfa_recoil_eyeangles_mul") == nil then + CreateReplConVar("sv_tfa_recoil_eyeangles_mul", "1", "Multiplier for eye angles recoil (real angle change recoil)") +end + +if GetConVar("sv_tfa_fx_penetration_decal") == nil then + CreateReplConVar("sv_tfa_fx_penetration_decal", "1", "Enable decals on the other side of a penetrated object?") +end + +local cv_ironsights = GetConVar("sv_tfa_ironsights_enabled") + +if cv_ironsights == nil then + cv_ironsights = CreateReplConVar("sv_tfa_ironsights_enabled", "1", "Enable ironsights? Disabling this still allows scopes.") +end + +local is_stats = { + ["data.ironsights"] = 0, + ["Secondary.IronSightsEnabled"] = false, +} + +hook.Add("TFA_GetStat", "TFA_IronsightsConVarToggle", function(wep, stat, val) + if not IsValid(wep) or is_stats[stat] == nil then return end + + if not cv_ironsights:GetBool() and not wep:GetStatRawL("Scoped") and not wep:GetStatRawL("Scoped_3D") then + return is_stats[stat] + end +end) + +if GetConVar("sv_tfa_sprint_enabled") == nil then + CreateReplConVar("sv_tfa_sprint_enabled", "1", "Enable sprinting? Disabling this allows shooting while IN_SPEED.") +end + +if GetConVar("sv_tfa_reloads_enabled") == nil then + CreateReplConVar("sv_tfa_reloads_enabled", "1", "Enable reloading? Disabling this allows shooting from ammo pool.") +end + +if GetConVar("sv_tfa_attachments_enabled") == nil then + CreateReplConVar("sv_tfa_attachments_enabled", "1", "Display attachment picker?") +end + +if GetConVar("sv_tfa_attachments_alphabetical") == nil then + CreateReplConVar("sv_tfa_attachments_alphabetical", "0", "Override weapon attachment order to be alphabetical.") +end + +if GetConVar("sv_tfa_jamming") == nil then + CreateReplConVar("sv_tfa_jamming", "1", "Enable jamming mechanics?") +end + +if GetConVar("sv_tfa_jamming_mult") == nil then + CreateReplConVar("sv_tfa_jamming_mult", "1", "Multiply jam chance by this value. You really should modify sv_tfa_jamming_factor_inc rather than this.") +end + +if GetConVar("sv_tfa_jamming_factor") == nil then + CreateReplConVar("sv_tfa_jamming_factor", "1", "Multiply jam factor by this value") +end + +if GetConVar("sv_tfa_jamming_factor_inc") == nil then + CreateReplConVar("sv_tfa_jamming_factor_inc", "1", "Multiply jam factor gain by this value") +end + +if GetConVar("sv_tfa_nearlyempty") == nil then + CreateReplConVar("sv_tfa_nearlyempty", "1", "Enable nearly-empty sounds") +end + +if GetConVar("sv_tfa_fixed_crosshair") == nil then + CreateReplConVar("sv_tfa_fixed_crosshair", "0", "Fix crosshair position on center of the screen (CS:GO style)") +end + +if GetConVar("sv_tfa_crosshair_showplayer") == nil then + CreateReplConVar("sv_tfa_crosshair_showplayer", "1", "Crosshair team color option reveals players") +end + +if GetConVar("sv_tfa_crosshair_showplayerteam") == nil then + CreateReplConVar("sv_tfa_crosshair_showplayerteam", engine.ActiveGamemode() == "terrortown" and "0" or "1", "Crosshair team color option reveals players's team") +end + +if GetConVar("sv_tfa_weapon_weight") == nil then + CreateReplConVar("sv_tfa_weapon_weight", "1", "Disabling this WILL break certain SWEPs and Mechanics. You were warned.") +end + +if GetConVar("sv_tfa_fov_sprintmod") == nil then + CreateReplConVar("sv_tfa_fov_sprintmod", "1", "Enable sprint FOV modification") +end + +if GetConVar("sv_tfa_first_draw_anim_enabled") == nil then + CreateReplConVar("sv_tfa_first_draw_anim_enabled", "1", "Enable first draw animation") +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_darkrp.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_darkrp.lua new file mode 100644 index 0000000..77f8698 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_darkrp.lua @@ -0,0 +1,26 @@ +local TFA_PocketBlock = {} +TFA_PocketBlock["tfa_ammo_357"] = true +TFA_PocketBlock["tfa_ammo_ar2"] = true +TFA_PocketBlock["tfa_ammo_buckshot"] = true +TFA_PocketBlock["tfa_ammo_c4"] = true +TFA_PocketBlock["tfa_ammo_frags"] = true +TFA_PocketBlock["tfa_ammo_ieds"] = true +TFA_PocketBlock["tfa_ammo_nervegas"] = true +TFA_PocketBlock["tfa_ammo_nuke"] = true +TFA_PocketBlock["tfa_ammo_pistol"] = true +TFA_PocketBlock["tfa_ammo_proxmines"] = true +TFA_PocketBlock["tfa_ammo_rockets"] = true +TFA_PocketBlock["tfa_ammo_smg"] = true +TFA_PocketBlock["tfa_ammo_smg1_grenade"] = true +TFA_PocketBlock["tfa_ammo_smg1_grenade_large"] = true +TFA_PocketBlock["tfa_ammo_sniper_rounds"] = true +TFA_PocketBlock["tfa_ammo_stickynades"] = true +TFA_PocketBlock["tfa_ammo_winchester"] = true + +local function TFA_PockBlock(ply, wep) --Get it, because cockblock, hehe..... so mature. + if not IsValid(wep) then return end + local class = wep:GetClass() + if TFA_PocketBlock[class] then return false end +end + +hook.Add("canPocket", "TFA_PockBlock", TFA_PockBlock) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_data.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_data.lua new file mode 100644 index 0000000..4121fa3 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_data.lua @@ -0,0 +1,566 @@ +-- This file is holding seamless translation of older versions of data to newer +-- versions of data + +TFA.LatestDataVersion = 1 + +TFA.DataVersionMapping = { + [0] = { + { + old_path = "DrawCrosshairIS", + new_path = "DrawCrosshairIronSights", + }, + + { + old_path = "FiresUnderwater", + new_path = "Primary.FiresUnderwater", + }, + + { + old_path = "PenetrationMaterials", + new_path = "Primary.PenetrationMaterials", + }, + + { + old_path = "MaxPenetrationCounter", + new_path = "Primary.MaxSurfacePenetrationCount", + }, + + { + old_path = "MaxPenetration", + new_path = "Primary.MaxSurfacePenetrationCount", + }, + + { + old_path = "IronRecoilMultiplier", + new_path = "Primary.IronRecoilMultiplier", + }, + + { + old_path = "MoveSpeed", + new_path = "RegularMoveSpeedMultiplier", + }, + + { + old_path = "IronSightsMoveSpeed", + new_path = "AimingDownSightsSpeedMultiplier", + }, + + { + old_path = "Shotgun", + new_path = "LoopedReload", + }, + + { + old_path = "ShellTime", + new_path = "LoopedReloadInsertTime", + }, + + { + old_path = "CrouchPos", + new_path = "CrouchViewModelPosition", + }, + + { + old_path = "CrouchAng", + new_path = "CrouchViewModelAngle", + }, + + { + old_path = "data.ironsights", + new_path = "Secondary.IronSightsEnabled", + upgrade = function(value) return value == 1 end, + downgrade = function(value) return value and 1 or 0 end, + }, + + { + old_path = "Secondary.IronFOV", + new_path = "Secondary.OwnerFOV", + }, + + { + old_path = "IronViewModelFOV", + new_path = "Secondary.ViewModelFOV", + }, + + { + old_path = "DoProceduralReload", + new_path = "IsProceduralReloadBased", + }, + + { + old_path = "ProceduralReloadEnabled", + new_path = "IsProceduralReloadBased", + }, + + { + old_path = "Akimbo", + new_path = "IsAkimbo", + }, + + { + old_path = "AkimboHUD", + new_path = "EnableAkimboHUD", + }, + + { + old_path = "IronInSound", + new_path = "Secondary.IronSightsInSound", + }, + + { + old_path = "IronOutSound", + new_path = "Secondary.IronSightsOutSound", + }, + + { + old_path = "DisableChambering", + new_path = "Primary.DisableChambering", + }, + + { + old_path = "DisplayFalloff", + new_path = "Primary.DisplayFalloff", + }, + + { + old_path = "SpreadBiasYaw", + new_path = "Primary.SpreadBiasYaw", + }, + + { + old_path = "SpreadBiasPitch", + new_path = "Primary.SpreadBiasPitch", + }, + + { + old_path = "VMPos", + new_path = "ViewModelPosition", + }, + + { + old_path = "VMAng", + new_path = "ViewModelAngle", + }, + + { + old_path = "VMPos_Additive", + new_path = "AdditiveViewModelPosition", + }, + + { + old_path = "RunSightsPos", + new_path = "SprintViewModelPosition", + }, + + { + old_path = "RunSightsAng", + new_path = "SprintViewModelAngle", + }, + + { + old_path = "IronSightsPos", + new_path = "IronSightsPosition", + }, + + { + old_path = "IronSightsAng", + new_path = "IronSightsAngle", + }, + + { + old_path = "Bodygroups_V", + new_path = "ViewModelBodygroups", + }, + + { + old_path = "Bodygroups_W", + new_path = "WorldModelBodygroups", + }, + + { + old_path = "CenteredPos", + new_path = "CenteredViewModelPosition", + }, + + { + old_path = "CenteredAng", + new_path = "CenteredViewModelAngle", + }, + + { + old_path = "Offset", + new_path = "WorldModelOffset", + }, + + { + old_path = "ProceduralHolsterPos", + new_path = "ProceduralHolsterPosition", + }, + + { + old_path = "ProceduralHolsterAng", + new_path = "ProceduralHolsterAngle", + }, + + { + old_path = "VElements", + new_path = "ViewModelElements", + }, + + { + old_path = "WElements", + new_path = "WorldModelElements", + }, + } +} + +local function identity(...) return ... end + +for version = 0, #TFA.DataVersionMapping do + for i, data in ipairs(TFA.DataVersionMapping[version]) do + if not isfunction(data.upgrade) then data.upgrade = identity end + if not isfunction(data.downgrade) then data.downgrade = identity end + end +end + +TFA.PathParseCache = {} +TFA.PathParseCacheTR = {} +TFA.StatPathRemapCache = {} +TFA.PathParseCacheDirect = {} +TFA.PathParseChildren = {} + +local PathParseCache = TFA.PathParseCache +local PathParseCacheTR = TFA.PathParseCacheTR +local PathParseCacheDirect = TFA.PathParseCacheDirect +local StatPathRemapCache = TFA.StatPathRemapCache +local PathParseChildren = TFA.PathParseChildren +local string_Explode = string.Explode +local ipairs = ipairs +local pairs = pairs +local string_sub = string.sub +local tonumber = tonumber +local table_Copy = table.Copy +local table_concat = table.concat +local istable = istable +local string_format = string.format + +local function doDowngrade(path, migrations) + for i, data in ipairs(migrations) do + if data.new_path == path then + return data.old_path, data.upgrade + elseif path:StartWith(data.new_path) and path[#data.new_path + 1] == '.' then + return data.old_path .. path:sub(#data.new_path + 1), data.upgrade + end + end + + return path +end + +local function doUpgrade(path, migrations) + for i, data in ipairs(migrations) do + if data.old_path == path then + return data.new_path, data.downgrade + elseif path:StartWith(data.old_path) and path[#data.old_path + 1] == '.' then + return data.new_path .. path:sub(#data.old_path + 1), data.downgrade + end + end + + return path +end + +function TFA.RemapStatPath(path, path_version, structure_version) + local cache_path = path + + if path_version == nil then path_version = 0 end + if structure_version == nil then structure_version = 0 end + + -- version do not match + if path_version ~= structure_version then + cache_path = string_format("%d_%d_%s", path_version, structure_version, path) + end + + local get_cache = StatPathRemapCache[cache_path] + if get_cache ~= nil then return get_cache end + + if cache_path ~= path then + -- downgrade path + if path_version > structure_version then + for version = path_version, structure_version, -1 do + local mapping = TFA.DataVersionMapping[version] + + if istable(mapping) then + path = doDowngrade(path, mapping) + end + end + else -- upgrade path + for version = path_version, structure_version do + local mapping = TFA.DataVersionMapping[version] + + if istable(mapping) then + path = doUpgrade(path, mapping) + end + end + end + end + + StatPathRemapCache[cache_path] = path + return StatPathRemapCache[cache_path] +end + +function TFA.GetStatPathChildren(path, path_version, structure_version) + -- version do not match + if path_version ~= structure_version then + path = TFA.RemapStatPath(path, path_version, structure_version) + end + + if not PathParseChildren[path] then + TFA.GetStatPath(path, path_version, structure_version) + end + + return PathParseChildren[path].list +end + +local function concat_to(tab, to) + local str = tab[1] + + for i = 2, to do + str = str .. '.' .. tab[i] + end + + return str +end + +local function concat_from(tab, from) + local str = tab[from] + + for i = from + 1, #tab do + str = str .. '.' .. tab[i] + end + + return str +end + +function TFA.GetStatPath(path, path_version, structure_version, no_translate) + local cache_path = path + + if path_version == nil then path_version = 0 end + if structure_version == nil then structure_version = 0 end + + -- version do not match + if path_version ~= structure_version then + cache_path = string_format("%d_%d_%s", path_version, structure_version, path) + end + + local _PathParseCache = no_translate and PathParseCacheTR or PathParseCache + local get_cache = _PathParseCache[cache_path] + if get_cache ~= nil then return get_cache[1], get_cache[2], get_cache[3] end + + local fn, fnGet + + if cache_path ~= path then + -- downgrade + if path_version > structure_version then + for version = path_version, structure_version, -1 do + local mapping = TFA.DataVersionMapping[version] + + if istable(mapping) then + path, fnGet = doDowngrade(path, mapping) + + if fnGet and fnGet ~= identity then + if not fn then + fn = fnGet + else + local _fn = fn + function fn(...) return fnGet(_fn(...)) end + end + end + end + end + else -- upgrade + for version = path_version, structure_version do + local mapping = TFA.DataVersionMapping[version] + + if istable(mapping) then + path, fnGet = doUpgrade(path, mapping) + + if fnGet and fnGet ~= identity then + if not fn then + fn = fnGet + else + local _fn = fn + function fn(...) return fnGet(_fn(...)) end + end + end + end + end + end + end + + get_cache = string_Explode(".", path, false) + + do + local children = PathParseChildren[get_cache[1]] + + if not children then + children = { + list = {get_cache[1]}, + children = {} + } + + PathParseChildren[get_cache[1]] = children + end + + local childrens = {children} + + for i = 2, #get_cache do + local obj = get_cache[i] + local path2 = concat_to(get_cache, i) + + for i3 = 1, #childrens do + local list = childrens[i3].list + local hit = false + + for i2 = 1, #list do + if list[i2] == path2 then + hit = true + break + end + end + + if not hit then + table.insert(list, path2) + end + end + + if not children.children[obj] then + children.children[obj] = { + list = {path2}, + children = {} + } + end + + if not PathParseChildren[path2] then + PathParseChildren[path2] = { + list = {path2}, + children = {} + } + end + + children = children.children[obj] + table.insert(childrens, children) + table.insert(childrens, PathParseChildren[path2]) + end + end + + if not no_translate then + if get_cache[1] == "Primary" then + get_cache[1] = "Primary_TFA" + elseif get_cache[1] == "Secondary" then + get_cache[1] = "Secondary_TFA" + end + end + + for k, v in ipairs(get_cache) do + get_cache[k] = tonumber(v) or v + end + + _PathParseCache[cache_path] = {get_cache, path, fn or identity} + return get_cache, path, fn or identity +end + +function TFA.GetStatPathRaw(path) + local get_cache = PathParseCacheDirect[path] + if get_cache ~= nil then return get_cache end + + local t_stbl = string_Explode(".", path, false) + + for k, v in ipairs(t_stbl) do + t_stbl[k] = tonumber(v) or v + end + + PathParseCacheDirect[path] = t_stbl + return t_stbl +end + +local GetStatPathRaw = TFA.GetStatPathRaw + +do + local function get(self, path) + local value = self[path[1]] + + for i = 2, #path do + if not istable(value) then return end + value = value[path[i]] + end + + return value + end + + local function set(self, path, val) + if #path == 1 then + if self[path[1]] == nil then + self[path[1]] = val + end + + return + end + + local value = self[path[1]] + + if value == nil then + self[path[1]] = {} + value = self[path[1]] + end + + for i = 2, #path - 1 do + if not istable(value) then return end + if value[path[i]] == nil then value[path[i]] = {} end + value = value[path[i]] + end + + if istable(value) and value[path[#path]] == nil then + value[path[#path]] = val + elseif not istable(value) then + print('[TFA Base] unable to fill gap for older version in meta structure of ' .. table_concat(path, '.')) + end + end + + function TFA.FillMissingMetaValues(SWEP) + for version = TFA.LatestDataVersion, 0, -1 do + local mapping = TFA.DataVersionMapping[version] + + if istable(mapping) then + for i, data in ipairs(mapping) do + local getVal = get(SWEP, GetStatPathRaw(data.new_path)) + + if getVal ~= nil then + set(SWEP, GetStatPathRaw(data.old_path), data.downgrade(getVal)) + end + end + end + end + end +end + +if CLIENT then + concommand.Add("cl_tfa_data_translatestat", function(ply, cmd, args, argStr) + if #args <= 0 then + print("Usage: " .. cmd .. " ") + + return + end + + for _, arg in ipairs(args) do + if string.StartsWith(arg, "SWEP.") or string.StartsWith(arg, "self.") then + arg = string.sub(arg, 6) + end + + local _, path, _ = TFA.GetStatPath(arg, 0, TFA.LatestDataVersion) + if path then + print("SWEP." .. arg .. " => SWEP." .. path .. " (at data version " .. TFA.LatestDataVersion .. ")") + else + print("Unable to lookup updated stat path for " .. arg) + end + end + end) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_effects.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_effects.lua new file mode 100644 index 0000000..015f341 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_effects.lua @@ -0,0 +1,32 @@ +TFA.Effects = TFA.Effects or {} +local Effects = TFA.Effects + +Effects.Overrides = Effects.Overrides or {} +local Overrides = Effects.Overrides + +function Effects.AddOverride(target, override) + assert(type(target) == "string", "No target effect name or not a string") + assert(type(override) == "string", "No override effect name or not a string") + + Overrides[target] = override +end + +function Effects.GetOverride(target) + if Overrides[target] then + return Overrides[target] + end + + return target +end + +local util_Effect = util.Effect + +function Effects.Create(effectName, effectData, allowOverride, ignorePredictionOrRecipientFilter) + effectName = Effects.GetOverride(effectName) + + util_Effect(effectName, effectData, allowOverride, ignorePredictionOrRecipientFilter) +end + +if SERVER then + AddCSLuaFile("tfa/muzzleflash_base.lua") +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_envcheck.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_envcheck.lua new file mode 100644 index 0000000..5661fcc --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_envcheck.lua @@ -0,0 +1,77 @@ +local EmptyFunc = function() end + +local debugInfoTbl = debug.getinfo(EmptyFunc) + +local cv_do_check = CreateConVar("sv_tfa_envcheck", "1", {FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED}, "Enable environment sanity checks and warnings?") + +local function checkEnv(plyIn) + if not cv_do_check:GetBool() then return end + + local function printFunc(msg, ...) + msg = "[TFA Base] " .. msg + + if chat and chat.AddText then + return chat.AddText(msg, ...) + end + + return print(msg, ...) + end + + if game.SinglePlayer() then + if CLIENT then + local found = false + for _, wepDefTable in ipairs(weapons.GetList()) do + if wepDefTable.Spawnable and weapons.IsBasedOn(wepDefTable.ClassName, "tfa_gun_base") then + found = true + + break + end + end + + if not found then + printFunc("Thank you for installing our weapons base! It appears that you have installed only the base itself, which does not include any weapons by default. Please install some weapons/packs that utilize TFA Base for full experience!") + end + end + + local shortsrc = debugInfoTbl.short_src + + if shortsrc:StartWith("addons") then -- legacy/unpacked addon + local addonRootFolder = shortsrc:GetPathFromFilename():Replace("lua/tfa/modules/", "") + + if not file.Exists(addonRootFolder .. ".git", "GAME") then -- assume unpacked version by missing .git folder, which is ignored by gmad.exe + printFunc("You are using unpacked version of TFA Base.\nWe only provide support for Steam Workshop version.") + end + end + else + local activeGamemode = engine.ActiveGamemode() + local isRP = activeGamemode:find("rp") + or activeGamemode:find("roleplay") + or activeGamemode:find("serious") + + if isRP and (SERVER or (IsValid(plyIn) and (plyIn:IsAdmin() or plyIn:IsSuperAdmin()))) then + print("You are running the base on DarkRP or DarkRP-derived gamemode. We can't guarantee that it will work correctly with any possible addons the server might have installed (especially the paid ones), so we don't provide support for RP gamemodes/servers. If you've encountered a conflict error with another addon, it's most likely that addon's fault. DO NOT CONTACT US ABOUT THAT!") + end + end + + timer.Simple(0, function() + if not TFA.BASE_LOAD_COMPLETE or not TFA.SWEP_LOAD_COMPLETE then + printFunc("Some of the base's modules have failed to load. You are probably going over Lua files limit. Try disabling some addons until you stop getting this error.") + end + end) +end + +if CLIENT then + hook.Add("HUDPaint", "TFA_CheckEnv", function() + local ply = LocalPlayer() + + if not IsValid(ply) then return end + + hook.Remove("HUDPaint", "TFA_CheckEnv") + + checkEnv(ply) + end) +else + --resource.AddWorkshop("2840031720") + + hook.Add("InitPostEntity", "TFA_CheckEnv", checkEnv) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_functions.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_functions.lua new file mode 100644 index 0000000..130e9f4 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_functions.lua @@ -0,0 +1,415 @@ +local gas_cl_enabled = GetConVar("cl_tfa_fx_gasblur") +local oldshell_cl_enabled = GetConVar("cl_tfa_legacy_shells") + +local ScrW, ScrH = ScrW, ScrH + +local BindToKeyTBL = { + ["ctrl"] = KEY_LCONTROL, + ["rctrl"] = KEY_LCONTROL, + ["alt"] = KEY_LALT, + ["ralt"] = KEY_RALT, + ["space"] = KEY_SPACE, + ["caps"] = KEY_CAPSLOCK, + ["capslock"] = KEY_CAPSLOCK, + ["tab"] = KEY_TAB, + ["back"] = KEY_BACKSPACE, + ["backspace"] = KEY_BACKSPACE, + [0] = KEY_0, + [1] = KEY_1, + [2] = KEY_2, + [3] = KEY_3, + [4] = KEY_4, + [5] = KEY_5, + [6] = KEY_6, + [7] = KEY_7, + [8] = KEY_8, + [9] = KEY_9 +} + +local alphabet = "abcdefghijklmnopqrstuvwxyz" + +for i = 1, string.len(alphabet) do + local sstr = string.sub( alphabet, i, i ) + BindToKeyTBL[ sstr ] = string.byte( sstr ) - 86 +end + + +local SoundChars = { + ["*"] = "STREAM",--Streams from the disc and rapidly flushed; good on memory, useful for music or one-off sounds + ["#"] = "DRYMIX",--Skip DSP, affected by music volume rather than sound volume + ["@"] = "OMNI",--Play the sound audible everywhere, like a radio voiceover or surface.PlaySound + [">"] = "DOPPLER",--Left channel for heading towards the listener, Right channel for heading away + ["<"] = "DIRECTIONAL",--Left channel = front facing, Right channel = read facing + ["^"] = "DISTVARIANT",--Left channel = close, Right channel = far + ["("] = "SPATIALSTEREO_LOOP",--Position a stereo sound in 3D space; broken + [")"] = "SPATIALSTEREO",--Same as above but actually useful + ["}"] = "FASTPITCH",--Low quality pitch shift + ["$"] = "CRITICAL",--Keep it around in memory + ["!"] = "SENTENCE",--NPC dialogue + ["?"] = "USERVOX"--Fake VOIP data; not that useful +} +local DefaultSoundChar = ")" + +local SoundChannels = { + ["shoot"] = CHAN_WEAPON, + ["shootwrap"] = CHAN_STATIC, + ["misc"] = CHAN_AUTO +} + + +--Scope + +local cv_rt + +function TFA.RTQuality() + if not cv_rt then + cv_rt = GetConVar("cl_tfa_3dscope_quality") + end + + return math.Clamp(cv_rt:GetInt(), cv_rt:GetMin(), cv_rt:GetMax()) +end + +--Sensitivity + +local ss, fov_og, resrat, fov_cv + +fov_cv = GetConVar("fov_desired") +function TFA.CalculateSensitivtyScale( fov_target, fov_src, screenscale ) + if not LocalPlayer():IsValid() then return 1 end + resrat = ScrW() / ScrH() + fov_og = fov_src or TFADUSKFOV or fov_cv:GetFloat() + ss = screenscale or 1 + return math.Clamp(math.atan( resrat * math.tan(math.rad( fov_target / 2 ) ) ) / math.atan( resrat * math.tan( math.rad( fov_og / 2) ) ) / ss, 0, 1) +end + +--Ammo + +local AmmoTypesByName = {} +local AmmoTypesAdded = {} + +function TFA.AddAmmo(id, name) + if not AmmoTypesAdded[id] then + AmmoTypesAdded[id] = true + + game.AddAmmoType({ + name = id + }) + end + + if name and language then + language.Add(id .. "_ammo", name) + end + + if name then + AmmoTypesByName[name] = AmmoTypesByName[name] or id + + return AmmoTypesByName[name] + end + + return id +end + +--Particles + +function TFA.ParticleTracer( name,startPos,endPos,doWhiz,ent,att) + if type(ent) ~= "number" and IsValid(ent) and ent.EntIndex then + ent = ent:EntIndex() + end + if ent then + att = att or -1 + return util.ParticleTracerEx(name,startPos,endPos,doWhiz,ent,att) + else + return util.ParticleTracerEx(name,startPos,endPos,doWhiz,0,-1) + end +end + +--Binds + +function TFA.BindToKey( bind, default ) + return BindToKeyTBL[ string.lower( bind ) ] or default or KEY_C +end + +--Sounds + +function TFA.PatchSound( path, kind ) + local pathv + local c = string.sub(path,1,1) + + if SoundChars[c] then + pathv = string.sub( path, 2, string.len(path) ) + else + pathv = path + end + + local kindstr = kind + if not kindstr then + kindstr = DefaultSoundChar + end + if string.len(kindstr) > 1 then + local found = false + for k,v in pairs( SoundChars ) do + if v == kind then + kindstr = k + found = true + break + end + end + if not found then + kindstr = DefaultSoundChar + end + end + + return kindstr .. pathv +end + +function TFA.AddSound( name, channel, volume, level, pitch, wave, char ) + char = char or "" + + local SoundData = { + name = name, + channel = channel or CHAN_AUTO, + volume = volume or 1, + level = level or 75, + pitch = pitch or 100 + } + + if char ~= "" then + if type(wave) == "string" then + wave = TFA.PatchSound(wave, char) + elseif type(wave) == "table" then + local patchWave = table.Copy(wave) + + for k, v in pairs(patchWave) do + patchWave[k] = TFA.PatchSound(v, char) + end + + wave = patchWave + end + end + + SoundData.sound = wave + + sound.Add(SoundData) +end + +function TFA.AddFireSound( id, path, wrap, kindv ) + kindv = kindv or ")" + + TFA.AddSound(id, wrap and SoundChannels.shootwrap or SoundChannels.shoot, 1, 120, {97, 103}, path, kindv) +end + +function TFA.AddWeaponSound( id, path, kindv ) + kindv = kindv or ")" + + TFA.AddSound(id, SoundChannels.misc, 1, 80, {97, 103}, path, kindv) +end + +--Frametime + +--CVar Mediators +function TFA.GetGasEnabled() + local enabled = false + + if gas_cl_enabled then + enabled = gas_cl_enabled:GetBool() + end + + return enabled +end + +function TFA.GetLegacyShellsEnabled() + local enabled = false + + if oldshell_cl_enabled then + enabled = oldshell_cl_enabled:GetBool() + end + + return enabled +end + +local ejectionsmoke_cl_enabled = GetConVar("cl_tfa_fx_ejectionsmoke") +local muzzlesmoke_cl_enabled = GetConVar("cl_tfa_fx_muzzlesmoke") + +function TFA.GetMZSmokeEnabled() + local enabled = false + + if muzzlesmoke_cl_enabled then + enabled = muzzlesmoke_cl_enabled:GetBool() + end + + return enabled +end + +function TFA.GetEJSmokeEnabled() + local enabled = false + + if ejectionsmoke_cl_enabled then + enabled = ejectionsmoke_cl_enabled:GetBool() + end + + return enabled +end + +local muzzleflashsmoke_cl_enabled = GetConVar("cl_tfa_fx_muzzleflashsmoke") + +function TFA.GetMZFSmokeEnabled() + local enabled = false + + if muzzleflashsmoke_cl_enabled then + enabled = muzzleflashsmoke_cl_enabled:GetBool() + end + + return enabled +end + +local ricofx_cl_enabled = GetConVar("cl_tfa_fx_impact_ricochet_enabled") + +function TFA.GetRicochetEnabled() + local enabled = false + + if ricofx_cl_enabled then + enabled = ricofx_cl_enabled:GetBool() + end + + return enabled +end + +--Local function for detecting TFA Base weapons. +function TFA.PlayerCarryingTFAWeapon(ply) + if not ply then + if CLIENT then + if LocalPlayer():IsValid() then + ply = LocalPlayer() + else + return false, nil, nil + end + elseif game.SinglePlayer() then + ply = Entity(1) + else + return false, nil, nil + end + end + + if not (IsValid(ply) and ply:IsPlayer() and ply:Alive()) then return end + local wep = ply:GetActiveWeapon() + + if IsValid(wep) then + if (wep.IsTFAWeapon) then return true, ply, wep end + + return false, ply, wep + end + + return false, ply, nil +end + +local sv_cheats = GetConVar("sv_cheats") +local host_timescale = GetConVar("host_timescale") + +function TFA.FrameTime() + return engine.TickInterval() * game.GetTimeScale() * (sv_cheats:GetBool() and host_timescale:GetFloat() or 1) +end + +local buffer = {} + +function TFA.tbezier(t, values, amount) + assert(isnumber(t), 't is not a number') + assert(t >= 0 and t <= 1, '0 <= t <= 1!') + assert(#values >= 2, 'at least two values must be provided') + amount = amount or #values + local a, b = values[1], values[2] + + -- linear + if amount == 2 then + return a + (b - a) * t + -- square + elseif amount == 3 then + return (1 - t) * (1 - t) * a + 2 * t * (1 - t) * b + t * t * values[3] + -- cube + elseif amount == 4 then + return (1 - t) * (1 - t) * (1 - t) * a + 3 * t * (1 - t) * (1 - t) * b + 3 * t * t * (1 - t) * values[3] + t * t * t * values[4] + end + + for point = 1, amount do + local point1 = values[point] + local point2 = values[point + 1] + if not point2 then break end + buffer[point] = point1 + (point2 - point1) * t + end + + return TFA.tbezier(t, buffer, amount - 1) +end + +if DLib and math.tbezier and isfunction(math.tbezier) then + TFA.tbezier = math.tbezier +end + +function TFA.Quintic(t) + return t * t * t * (t * (t * 6 - 15) + 10) +end + +function TFA.Cosine(t) + return (1 - math.cos(t * math.pi)) / 2 +end + +function TFA.Sinusine(t) + return (1 - math.sin(t * math.pi)) / 2 +end + +function TFA.Cubic(t) + return -2 * t * t * t + 3 * t * t +end + +function TFA.UnfoldBaseClass(tableInput, tableOutput) + if tableOutput == nil then tableOutput = tableInput end + if not istable(tableInput) or not istable(tableOutput) then return tableOutput end + + if tableInput ~= tableOutput then + for k, v in pairs(tableInput) do + if tableOutput[k] == nil then + tableOutput[k] = v + end + end + end + + if istable(tableInput.BaseClass) then + TFA.UnfoldBaseClass(tableInput.BaseClass, tableOutput) + end + + return tableOutput +end + +if CLIENT then + local cvar_scale = GetConVar("cl_tfa_hud_scale") + local sscache = {} + + function TFA.ScaleH(num) + if not sscache[num] then + sscache[num] = num * (ScrH() / 1080) * cvar_scale:GetFloat() + end + + return sscache[num] + end + + local function EmptyCache() + sscache = {} + end + + cvars.AddChangeCallback("cl_tfa_hud_scale", EmptyCache, "TFA_ClearSScaleCache") + hook.Add("OnScreenSizeChanged", "_TFA_DropSScaleCache", EmptyCache) + + local color_black = Color(0, 0, 0, 255) + function TFA.DrawTextShadowed(text, font, x, y, color, shadowlength, shadowcolor) + shadowlength = shadowlength or 2 + shadowcolor = shadowcolor or ColorAlpha(color_black, color.a) + + surface.SetFont(font) + + surface.SetTextPos(x + shadowlength, y + shadowlength) + surface.SetTextColor(shadowcolor.r, shadowcolor.g, shadowcolor.b, shadowcolor.a) + surface.DrawText(text) + + surface.SetTextPos(x, y) + surface.SetTextColor(color.r, color.g, color.b, color.a) + surface.DrawText(text) + end +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_hooks.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_hooks.lua new file mode 100644 index 0000000..d455196 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_hooks.lua @@ -0,0 +1,661 @@ +local TFA = TFA + +TFA.INSPECTION_IMPULSE = 148 +TFA.BASH_IMPULSE = 149 +TFA.CYCLE_FIREMODE_IMPULSE = 150 +TFA.CYCLE_SAFETY_IMPULSE = 151 + +TFA.INSPECTION_IMPULSE_STRING = "148" +TFA.BASH_IMPULSE_STRING = "149" +TFA.CYCLE_FIREMODE_IMPULSE_STRING = "150" +TFA.CYCLE_SAFETY_IMPULSE_STRING = "151" + +local sp = game.SinglePlayer() +local CurTime = CurTime + +local ENTITY = FindMetaTable("Entity") +local PLAYER = FindMetaTable("Player") + +--[[ +Hook: PlayerPostThink +Function: Weapon Logic +Used For: Main weapon "think" logic +]] +-- +if CLIENT and sp then + local engine_TickCount = engine.TickCount + + hook.Add("PlayerPostThink", "PlayerTickTFA", function(plyv) + local wepv = PLAYER.GetActiveWeapon(plyv) + if not IsValid(wepv) then return end + local wep2 = ENTITY.GetTable(wepv) + + if wep2.IsTFAWeapon then + local ply2 = PLAYER.GetTable(plyv) + + local tickCount = engine_TickCount() + wep2.PlayerThink(wepv, plyv, ply2.last_tfa_think == tickCount) + ply2.last_tfa_think = tickCount + end + end) +end + +if SERVER or not sp then + local IsFirstTimePredicted = IsFirstTimePredicted + + hook.Add("FinishMove", "PlayerTickTFA", function(plyv) + local wepv = PLAYER.GetActiveWeapon(plyv) + if not IsValid(wepv) then return end + local wep2 = ENTITY.GetTable(wepv) + + if wep2.IsTFAWeapon then + wep2.PlayerThink(wepv, plyv, not IsFirstTimePredicted()) + end + end) +end + +--[[ +Hook: Think +Function: Weapon Logic for NPC +User For: Calling SWEP:Think for NPCs manually +]] +-- +if SERVER then + hook.Add("Think", "NPCTickTFA", function() + hook.Run("TFA_NPCWeaponThink") + end) +end + +--[[ +Hook: Tick +Function: Inspection mouse support +Used For: Enables and disables screen clicker +]] +-- +if CLIENT then + local tfablurintensity + local its_old = 0 + local ScreenClicker = false + local cl_tfa_inspect_hide = GetConVar("cl_tfa_inspect_hide") + local cl_drawhud = GetConVar("cl_drawhud") + + hook.Add("Tick", "TFAInspectionScreenClicker", function() + tfablurintensity = 0 + + if LocalPlayer():IsValid() and LocalPlayer():GetActiveWeapon():IsValid() then + local w = LocalPlayer():GetActiveWeapon() + + if w.IsTFAWeapon then + tfablurintensity = w:GetCustomizing() and 1 or 0 + end + end + + if tfablurintensity > its_old and not ScreenClicker and not cl_tfa_inspect_hide:GetBool() and cl_drawhud:GetBool() then + gui.EnableScreenClicker(true) + ScreenClicker = true + elseif tfablurintensity < its_old and ScreenClicker then + gui.EnableScreenClicker(false) + ScreenClicker = false + end + + its_old = tfablurintensity + end) + + local engine_TickCount = engine.TickCount + local tickInterval = engine.TickInterval() + local FrameTime = FrameTime + local math_Clamp = math.Clamp + + TFA.TickDeltaLastTick = TFA.TickDeltaLastTick or engine_TickCount() - 1 + TFA.TickDelta = 0 + + hook.Add("Think", "TFABase_PlayerThinkCL", function() + TFA.TickDelta = math_Clamp(TFA.TickDelta + FrameTime() / tickInterval, 0, 1) + + local tcount = engine_TickCount() + if TFA.TickDeltaLastTick ~= tcount then + TFA.TickDeltaLastTick = tcount + TFA.TickDelta = 0 + end + + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local weapon = ply:GetActiveWeapon() + + if IsValid(weapon) and weapon.IsTFAWeapon and weapon.PlayerThinkCL then + weapon:PlayerThinkCL(ply) + end + end) +end + +if CLIENT and sp then + local lastSDLP + + local function SyncSDLP() + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local SDLP = ply:ShouldDrawLocalPlayer() + if lastSDLP == nil or lastSDLP ~= SDLP then + net.Start("tfaSDLP", true) + net.WriteBool(SDLP) + net.SendToServer() + + lastSDLP = SDLP + end + end + + hook.Add("Think", "TFABase_ShouldDrawLocalPlayer", SyncSDLP) +end + +--[[ +Hook: AllowPlayerPickup +Function: Prop holding +Used For: Records last held object +]] +-- +hook.Add("AllowPlayerPickup", "TFAPickupDisable", function(plyv, ent) + plyv:SetNW2Entity("LastHeldEntity", ent) +end) + +--[[ +Hook: PlayerBindPress +Function: Intercept Keybinds +Used For: Alternate attack, inspection, shotgun interrupts, and more +]] +-- +local cv_cm = GetConVar("sv_tfa_cmenu") +local cv_cm_key = GetConVar("sv_tfa_cmenu_key") +local keyv + +local function GetInspectionKey() + if cv_cm_key and cv_cm_key:GetInt() >= 0 then + keyv = cv_cm_key:GetInt() + else + keyv = TFA.BindToKey(input.LookupBinding("+menu_context", true) or "c", KEY_C) + end + + return keyv +end + +local function TFAContextBlock() + local plyv = LocalPlayer() + + if not plyv:IsValid() or GetViewEntity() ~= plyv then return end + + if plyv:InVehicle() and not plyv:GetAllowWeaponsInVehicle() then return end + + local wepv = plyv:GetActiveWeapon() + if not IsValid(wepv) then return end + + if plyv:GetInfoNum("cl_tfa_keys_customize", 0) > 0 then return end + + if GetInspectionKey() == TFA.BindToKey(input.LookupBinding("+menu_context", true) or "c", KEY_C) and wepv.ToggleInspect and cv_cm:GetBool() and not plyv:KeyDown(IN_USE) then return false end +end + +hook.Add("ContextMenuOpen", "TFAContextBlock", TFAContextBlock) + +if CLIENT then + local kd_old = false + + local cl_tfa_keys_customize + + local function TFAKPThink() + local plyv = LocalPlayer() + if not plyv:IsValid() then return end + local wepv = plyv:GetActiveWeapon() + if not IsValid(wepv) then return end + + if not cl_tfa_keys_customize then + cl_tfa_keys_customize = GetConVar("cl_tfa_keys_customize") + end + + if cl_tfa_keys_customize:GetBool() then return end + + local key = GetInspectionKey() + local kd = input.IsKeyDown(key) + + if IsValid(vgui.GetKeyboardFocus()) then + kd = false + end + + if kd ~= kd_old and kd and cv_cm:GetBool() and not plyv:KeyDown(IN_USE) then + RunConsoleCommand("impulse", tostring(TFA.INSPECTION_IMPULSE)) + end + + kd_old = kd + end + + hook.Add("Think", "TFAInspectionMenu", TFAKPThink) +end + +local cv_lr = GetConVar("sv_tfa_reloads_legacy") +local reload_threshold = 0.3 + +local sv_cheats = GetConVar("sv_cheats") +local host_timescale = GetConVar("host_timescale") + +local band = bit.band +local bxor = bit.bxor +local bnot = bit.bnot +local GetTimeScale = game.GetTimeScale +local IN_ATTACK2 = IN_ATTACK2 +local IN_RELOAD = IN_RELOAD + +local function FinishMove(ply, cmovedata) + if ply:InVehicle() and not ply:GetAllowWeaponsInVehicle() then return end + + local wepv = ply:GetActiveWeapon() + if not IsValid(wepv) or not wepv.IsTFAWeapon then return end + + wepv:TFAFinishMove(ply, cmovedata:GetVelocity(), cmovedata) + + local impulse = cmovedata:GetImpulseCommand() + + if impulse == TFA.INSPECTION_IMPULSE then + wepv:ToggleInspect() + elseif impulse == TFA.CYCLE_FIREMODE_IMPULSE and wepv:GetStatus() == TFA.Enum.STATUS_IDLE and wepv:GetStatL("SelectiveFire") then + wepv:CycleFireMode() + elseif impulse == TFA.CYCLE_SAFETY_IMPULSE and wepv:GetStatus() == TFA.Enum.STATUS_IDLE then + wepv:CycleSafety() + end + + local BashImpulse = cmovedata:GetImpulseCommand() == TFA.BASH_IMPULSE + ply:TFA_SetZoomKeyDown(BashImpulse) -- this may or may not work + + if wepv.SetBashImpulse then + wepv:SetBashImpulse(BashImpulse) + end + + if cmovedata:GetImpulseCommand() == 100 and (wepv:GetStatL("FlashlightAttachmentName") ~= nil or wepv:GetStatL("FlashlightAttachment", 0) > 0) then + wepv:ToggleFlashlight() + end + + local lastButtons = wepv:GetDownButtons() + local buttons = cmovedata:GetButtons() + local stillPressed = band(lastButtons, buttons) + local changed = bxor(lastButtons, buttons) + local pressed = band(changed, bnot(lastButtons), buttons) + local depressed = band(changed, lastButtons, bnot(buttons)) + + wepv:SetDownButtons(buttons) + wepv:SetLastPressedButtons(pressed) + + local time = CurTime() + + local cl_tfa_ironsights_toggle = (ply:GetInfoNum("cl_tfa_ironsights_toggle", 0) or 0) >= 1 + local cl_tfa_ironsights_resight = (ply:GetInfoNum("cl_tfa_ironsights_resight", 0) or 0) >= 1 + local cl_tfa_ironsights_responsive = (ply:GetInfoNum("cl_tfa_ironsights_responsive", 0) or 0) >= 1 + local cl_tfa_ironsights_responsive_timer = ply:GetInfoNum("cl_tfa_ironsights_responsive_timer", 0.175) or 0.175 + + local scale_dividier = GetTimeScale() * (sv_cheats:GetBool() and host_timescale:GetFloat() or 1) + + if wepv:GetStatL("Secondary.IronSightsEnabled", false) and not wepv:IsSafety() then + if band(changed, IN_ATTACK2) == IN_ATTACK2 then + local deltaPress = (time - wepv:GetLastIronSightsPressed()) / scale_dividier + + -- pressing for first time + if not wepv:GetIronSightsRaw() and band(pressed, IN_ATTACK2) == IN_ATTACK2 then + wepv:SetIronSightsRaw(true) + wepv:SetLastIronSightsPressed(time) + elseif wepv:GetIronSightsRaw() and + ((cl_tfa_ironsights_toggle or cl_tfa_ironsights_responsive) and band(pressed, IN_ATTACK2) == IN_ATTACK2 or + not cl_tfa_ironsights_toggle and not cl_tfa_ironsights_responsive and band(depressed, IN_ATTACK2) == IN_ATTACK2) + then + -- get out of iron sights + wepv:SetIronSightsRaw(false) + wepv:SetLastIronSightsPressed(-1) + elseif wepv:GetIronSightsRaw() and cl_tfa_ironsights_responsive and band(depressed, IN_ATTACK2) == IN_ATTACK2 and deltaPress > cl_tfa_ironsights_responsive_timer then + -- we depressed IN_ATTACK2 with it were being held down + wepv:SetIronSightsRaw(false) + wepv:SetLastIronSightsPressed(-1) + end + elseif wepv:GetIronSightsRaw() and not cl_tfa_ironsights_resight and (not TFA.Enum.IronStatus[wepv:GetStatus()] or wepv:GetSprinting()) then + wepv:SetIronSightsRaw(false) + wepv:SetLastIronSightsPressed(-1) + end + end + + if + band(depressed, IN_RELOAD) == IN_RELOAD and + not cv_lr:GetBool() + and band(buttons, IN_USE) == 0 + and time <= (wepv:GetLastReloadPressed() + reload_threshold * scale_dividier) + then + wepv:SetLastReloadPressed(-1) + wepv:Reload(true) + elseif band(pressed, IN_RELOAD) == IN_RELOAD then + wepv:SetLastReloadPressed(time) + elseif band(buttons, IN_RELOAD) ~= 0 and band(buttons, IN_USE) == 0 and time > (wepv:GetLastReloadPressed() + reload_threshold * scale_dividier) then + wepv:CheckAmmo() + end + + if BashImpulse then + if wepv.AltAttack then + wepv:AltAttack() + end + end +end + +hook.Add("FinishMove", "TFAFinishMove", FinishMove) + +local function TFABashZoom(plyv, cusercmd) + if plyv:InVehicle() and not plyv:GetAllowWeaponsInVehicle() then return end + + if plyv:GetInfoNum("cl_tfa_keys_bash", 0) ~= 0 then + if (sp or CLIENT) and plyv.tfa_bash_hack then + cusercmd:SetImpulse(TFA.BASH_IMPULSE) + end + + return + end + + local zoom = cusercmd:KeyDown(IN_ZOOM) + + if zoom then + local wepv = plyv:GetActiveWeapon() + + if IsValid(wepv) and wepv.IsTFAWeapon and wepv.AltAttack then + cusercmd:RemoveKey(IN_ZOOM) + cusercmd:SetImpulse(TFA.BASH_IMPULSE) + end + end +end + +hook.Add("StartCommand", "TFABashZoom", TFABashZoom) + +--[[ +Hook: PlayerSpawn +Function: Extinguishes players, zoom cleanup +Used For: Fixes incendiary bullets post-respawn +]] +-- +hook.Add("PlayerSpawn", "TFAExtinguishQOL", function(plyv) + if IsValid(plyv) and plyv:IsOnFire() then + plyv:Extinguish() + end +end) + +local sv_tfa_weapon_weight = GetConVar("sv_tfa_weapon_weight") + +--[[ +Hook: SetupMove +Function: Modify movement speed +Used For: Weapon slowdown, ironsights slowdown +]] +-- +hook.Add("SetupMove", "tfa_setupmove", function(plyv, movedata, commanddata) + local wepv = plyv:GetActiveWeapon() + + if IsValid(wepv) and wepv.IsTFAWeapon and sv_tfa_weapon_weight:GetBool() then + local speedmult = Lerp(wepv:GetIronSightsProgress(), wepv:GetStatL("RegularMoveSpeedMultiplier", 1), wepv:GetStatL("AimingDownSightsSpeedMultiplier", 1)) + movedata:SetMaxClientSpeed(movedata:GetMaxClientSpeed() * speedmult) + commanddata:SetForwardMove(commanddata:GetForwardMove() * speedmult) + commanddata:SetSideMove(commanddata:GetSideMove() * speedmult) + end +end) + +--[[ +Hook: InitPostEntity +Function: Patches or removes other hooks that breaking or changing behavior of our weapons in a negative way +Used For: Fixing our stuff +]] +-- + +local function FixInvalidPMHook() + if not CLIENT then return end + + local hookTable = hook.GetTable() + + if hookTable["PostDrawViewModel"] and hookTable["PostDrawViewModel"]["Set player hand skin"] then + local targetFunc = hookTable["PostDrawViewModel"]["Set player hand skin"] + if not targetFunc then return end + + local cv_shouldfix = GetConVar("cl_tfa_fix_pmhands_hook") or CreateClientConVar("cl_tfa_fix_pmhands_hook", "1", true, false, "Fix hands skin hook for CaptainBigButt's (and others) playermodels (Change requires map restart)") + + if not cv_shouldfix:GetBool() then return end + + print("[TFA Base] The playermodels you have installed breaks the automatic rig parenting for Insurgency and CS:GO weapons. The fix is applied but it's more of a band-aid, the solution would be to either fix this properly on author's side or to uninstall the addon.") + + if CLIENT and debug and debug.getinfo then + local funcPath = debug.getinfo(targetFunc).short_src + + print("Type whereis " .. funcPath .. " in console to see the conflicting addon.") + end + + hook.Remove("PostDrawViewModel", "Set player hand skin") + hook.Add("PreDrawPlayerHands", "Set player hand skin BUT FIXED", function(hands, vm, ply, weapon) + if hands:SkinCount() == ply:SkinCount() then + hands:SetSkin(ply:GetSkin()) + end + end) + end +end + +local function PatchSiminovSniperHook() + if not CLIENT then return end -- that hook is clientside only + + local hookTable = hook.GetTable() + + if hookTable["CreateMove"] and hookTable["CreateMove"]["SniperCreateMove"] then + local SniperCreateMove = hookTable["CreateMove"]["SniperCreateMove"] -- getting the original function + if not SniperCreateMove then return end + + local cv_shouldfix = GetConVar("cl_tfa_fix_siminov_scopes") or CreateClientConVar("cl_tfa_fix_siminov_scopes", "1", true, false, "Patch Siminov's sniper overlay hook with weapon base check (Change requires map restart)") + + if not cv_shouldfix:GetBool() then return end + + local PatchedSniperCreateMove = function(cmd) -- wrapping their function with our check + local ply = LocalPlayer() + + if IsValid(ply) and IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon().IsTFAWeapon then + return + end + + SniperCreateMove(cmd) + end + + hook.Remove("CreateMove", "SniperCreateMove") -- removing original hook + hook.Add("CreateMove", "SniperCreateMove_PatchedByTFABase", PatchedSniperCreateMove) -- creating new hook with wrap + end +end + +hook.Add("InitPostEntity", "tfa_unfuckeverything", function() + FixInvalidPMHook() + PatchSiminovSniperHook() +end) + +--[[ +Hook: PlayerSwitchFlashlight +Function: Flashlight toggle +Used For: Switching flashlight on weapon and blocking HEV flashlight +]] +-- +hook.Add("PlayerSwitchFlashlight", "tfa_toggleflashlight", function(plyv, toEnable) + if CLIENT then return end -- this is serverside hook GO AWAY + -- fuck you source + -- where is fucking prediction??!??!?!?/ + + if not IsValid(plyv) or not toEnable then return end -- allow disabling HEV flashlight + + local wepv = plyv:GetActiveWeapon() + + if IsValid(wepv) and wepv.IsTFAWeapon and (wepv:GetStatL("FlashlightAttachmentName") ~= nil or wepv:GetStatL("FlashlightAttachment", 0) > 0) then + -- wepv:ToggleFlashlight() + + return false + end +end) + +--[[ +Hook: SetupMove +Function: Update players NW2 variable +Used For: Walking animation NW2 var +]] +-- +hook.Add("SetupMove", "tfa_checkforplayerwalking", function(plyv, mvdatav, cmdv) + if not IsValid(plyv) or not mvdatav then return end + + if mvdatav:GetForwardSpeed() ~= 0 or mvdatav:GetSideSpeed() ~= 0 then + if not plyv:GetNW2Bool("TFA_IsWalking") then + plyv:SetNW2Bool("TFA_IsWalking", true) + end + elseif plyv:GetNW2Bool("TFA_IsWalking") then + plyv:SetNW2Bool("TFA_IsWalking", false) + end +end) + +--[[ +Hook: PreDrawOpaqueRenderables +Function: Calls SWEP:PreDrawOpaqueRenderables() +Used For: whatever draw stuff you need lol +]] +-- +hook.Add("PreDrawOpaqueRenderables", "tfaweaponspredrawopaque", function() + for _, v in ipairs(player.GetAll()) do + local wepv = v:GetActiveWeapon() + + if IsValid(wepv) and wepv.IsTFAWeapon and wepv.PreDrawOpaqueRenderables then + wepv:PreDrawOpaqueRenderables() + end + end +end) + +--[[ +Hook: PreDrawViewModel +Function: Calculating viewmodel offsets +Used For: Viewmodel sway, offset and flip +]] +-- +if CLIENT then + local vec = Vector() + local ang = Angle() + + local IsGameUIVisible = gui and gui.IsGameUIVisible + + local FrameTime = FrameTime + local FrameNumber = FrameNumber + local lastframe = FrameNumber() - 1 + hook.Add("PreDrawViewModel", "TFACalculateViewmodel", function(vm, plyv, wepv) + if lastframe == FrameNumber() then return end + lastframe = FrameNumber() + + if not IsValid(wepv) or not wepv.IsTFAWeapon then return end + local wep2 = wepv:GetTable() + + if sp and IsGameUIVisible and IsGameUIVisible() then return end + + wep2.UpdateEngineBob(wepv) + + local delta = FrameTime() + + wep2.Sway(wepv, vec, ang, delta) + wep2.CalculateViewModelOffset(wepv, delta) + wep2.CalculateViewModelFlip(wepv) + + wep2.UpdateProjectedTextures(wepv, true) + end) +end + +--[[ +Hook: EntityTakeDamage +Function: Applies physics damage to Combine Turrets +Used For: Knocking up Combine Turrets with TFA Base weapons +]] +-- +hook.Add("EntityTakeDamage", "TFA_TurretPhysics", function(entv, dmg) + if entv:GetClass() == "npc_turret_floor" then + entv:TakePhysicsDamage(dmg) + end +end) + +--[[ +Hook: HUDPaint +Function: Calls another hook +Used For: Hook that notifies when player is fully loaded. +]] +-- +hook.Add("HUDPaint", "TFA_TRIGGERCLIENTLOAD", function() + if LocalPlayer():IsValid() then + hook.Remove("HUDPaint", "TFA_TRIGGERCLIENTLOAD") + + hook.Run("TFA_ClientLoad") + end +end) + +--[[ +Hook: InitPostEntity +Function: Wraps SWEP:Think functions +Used For: Patching old, broken weapons that override SWEP:Think without calling baseclass +]] +-- +local PatchClassBlacklisted = { + tfa_gun_base = true, + tfa_melee_base = true, + tfa_bash_base = true, + tfa_bow_base = true, + tfa_knife_base = true, + tfa_nade_base = true, + tfa_sword_advanced_base = true, + tfa_cssnade_base = true, + tfa_shotty_base = true, + tfa_akimbo_base = true, + tfa_3dbash_base = true, + tfa_3dscoped_base = true, + tfa_scoped_base = true, +} + +local cv_shouldpatchthink = GetConVar("sv_tfa_backcompat_patchswepthink") or CreateConVar("sv_tfa_backcompat_patchswepthink", "1", CLIENT and {FCVAR_REPLICATED} or {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY}, "Enable patching of old weapons that override SWEP:Think function to work with newer version of the base?\n\tDISABLING THIS IS NOT RECOMMENDED AND MAY LEAD TO NON-FUNCTIONING WEAPONS!") + +hook.Add("InitPostEntity", "TFA_PatchThinkOverride", function() + if not cv_shouldpatchthink:GetBool() then return end + if not debug or not debug.getinfo then return end + + for _, wepRefTable in ipairs(weapons.GetList()) do + local class = wepRefTable.ClassName + + if PatchClassBlacklisted[class] or not weapons.IsBasedOn(class, "tfa_gun_base") then + goto THINK1FOUND + end + + local wepRealTbl = weapons.GetStored(class) + + if wepRealTbl.Think then + local info = debug.getinfo(wepRealTbl.Think, "S") + if not info or not info.linedefined or not info.lastlinedefined then goto THINK1FOUND end + + local src = info.short_src + + if src:StartWith("addons/") then + src = src:gsub("^addons/[^%0:/]+/", "") + end + + local luafile = file.Read(src:sub(5), "LUA") + if not luafile or luafile == "" then goto THINK1FOUND end + + local lua = luafile:gsub("\r\n","\n"):gsub("\r","\n"):Split("\n") + + for i = info.linedefined, info.lastlinedefined do + local line = lua[i] + + if not line or line:find("BaseClass%s*.%s*Think%s*%(") then + goto THINK1FOUND + end + end + + print(("[TFA Base] Weapon %s (%s) is overriding SWEP:Think() function without calling baseclass!"):format(wepRefTable.ClassName, info.short_src)) + + local BaseClass = baseclass.Get(wepRealTbl.Base) + + wepRealTbl.ThinkFuncUnwrapped = wepRealTbl.ThinkFuncUnwrapped or wepRealTbl.Think + function wepRealTbl:Think(...) + self:ThinkFuncUnwrapped(...) + + return BaseClass.Think(self, ...) + end + end + + ::THINK1FOUND:: + end +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_keybinds.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_keybinds.lua new file mode 100644 index 0000000..7143f05 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_keybinds.lua @@ -0,0 +1,234 @@ +TFA._KeyBindTable = TFA._KeyBindTable or {} +local KeyBindTable = TFA._KeyBindTable + +local cv_prefix = "cl_tfa_keys_" + +local sp = game.SinglePlayer() + +if sp then -- THANK YOU GARRY FOR HIGH QUALITY PREDICTION IN SINGLEPLAYER + if SERVER then + util.AddNetworkString("TFA_KB_State") + util.AddNetworkString("TFA_KB_Think") + end + + if CLIENT then + net.Receive("TFA_KB_State", function() + local ply = LocalPlayer() + + local bind = net.ReadString() + local state = net.ReadBool() + + local data = KeyBindTable[bind] + + if data and data.state ~= state then + data.state = state + + if state then + data.onpress(ply) + else + data.onrelease(ply) + end + end + end) + + net.Receive("TFA_KB_Think", function() + local ply = LocalPlayer() + + local bind = net.ReadString() + + local data = KeyBindTable[bind] + + if data and data.think and data.state then + data.think(ply) + end + end) + end +end + +local function empty() +end + +function TFA.RegisterKeyBind(data_in) + assert(type(data_in) == "table", "Data must be a table!") + assert(data_in.bind and type(data_in.bind) == "string", "Invalid bind name!") + -- assert(not TFA._KeyBindTable[data.bind], "Keybind already registered!") + + local data = table.Copy(data_in) + + if not data.onpress then + data.onpress = empty + elseif type(data.onpress) ~= "function" then + error("data.onpress - function expected, got " .. type(data.onpress)) + end + + if not data.onrelease then + data.onrelease = empty + elseif type(data.onrelease) ~= "function" then + error("data.onrelease - function expected, got " .. type(data.onrelease)) + end + + data.state = false + + if CLIENT and GetConVar(cv_prefix .. data.bind) == nil then + CreateClientConVar(cv_prefix .. data.bind, 0, true, true, data.desc) + end + + hook.Add("PlayerButtonDown", "TFA_KB_KeyDown_" .. data.bind, function(ply, button) + if not IsFirstTimePredicted() then return end + local cv_key = ply:GetInfoNum(cv_prefix .. data.bind, 0) + + if cv_key > 0 and cv_key == button and not data.state then + data.state = true + data.onpress(ply) + + if sp and SERVER then + net.Start("TFA_KB_State", true) + net.WriteString(data.bind) + net.WriteBool(data.state) + net.Send(ply) + end + end + end) + + hook.Add("PlayerButtonUp", "TFA_KB_KeyUp_" .. data.bind, function(ply, button) + if not IsFirstTimePredicted() then return end + local cv_key = ply:GetInfoNum(cv_prefix .. data.bind, 0) + + if cv_key > 0 and cv_key == button and data.state then + data.state = false + data.onrelease(ply) + + if sp and SERVER then + net.Start("TFA_KB_State", true) + net.WriteString(data.bind) + net.WriteBool(data.state) + net.Send(ply) + end + end + end) + + hook.Remove("PlayerPostThink", "TFA_KB_Think_" .. data.bind) + + if data.think and type(data.think) == "function" then + hook.Add("PlayerPostThink", "TFA_KB_Think_" .. data.bind, function(ply) + if data.state then + data.think(ply) + + if sp and SERVER then + net.Start("TFA_KB_Think", true) + net.WriteString(data.bind) + net.Send(ply) + end + end + end) + end + + KeyBindTable[data.bind] = data +end + +if CLIENT then -- Populate spawnmenu settings with registered keybinds + local function tfaOptionKeys(panel) + panel:Help("#tfa.keybinds.help.bind") + panel:Help("#tfa.keybinds.help.bound") + panel:Help("#tfa.keybinds.help.unbind") + panel:Help("") + + for _, data in pairs(KeyBindTable) do + local cv = GetConVar(cv_prefix .. data.bind) + + if cv then + panel:Help("#tfa.keybind." .. data.bind) + + local binder = vgui.Create("DBinder") + + binder:SetValue(cv:GetInt()) + + function binder:OnChange(newcode) + cv:SetInt(newcode) + end + + panel:AddItem(binder) + panel:Help("") + end + end + end + + hook.Add("PopulateToolMenu", "TFA_AddKeyBinds", function() + spawnmenu.AddToolMenuOption("Utilities", "TFA SWEP Base Settings", "TFASwepBaseKeybinds", "#tfa.smsettings.keybinds", "", "", tfaOptionKeys) + end) +end + +-- Default keybinds +TFA.RegisterKeyBind({ + bind = "bash", + + onpress = function(plyv) + if not plyv:IsValid() then return end + + plyv.tfa_bash_hack = true + end, + + onrelease = function(plyv) + if not plyv:IsValid() then return end + + plyv.tfa_bash_hack = false + end +}) + +TFA.RegisterKeyBind({ + bind = "customize", + onpress = CLIENT and function(plyv) + if not plyv:IsValid() then return end + + RunConsoleCommand("impulse", TFA.INSPECTION_IMPULSE_STRING) + end +}) + +TFA.RegisterKeyBind({ + bind = "inspect", + onpress = function(plyv) + local wepv = plyv:GetActiveWeapon() + + if (IsValid(wepv) and wepv.GetStat) and (wepv:GetActivityEnabled(ACT_VM_FIDGET) or wepv.InspectionActions) and wepv:GetStatus() == TFA.Enum.STATUS_IDLE then + local _, tanim, ttype = wepv:ChooseInspectAnim() + wepv:ScheduleStatus(TFA.Enum.STATUS_FIDGET, wepv:GetActivityLength(tanim, false, ttype)) + end + end +}) + +TFA.RegisterKeyBind({ + bind = "firemode", + onpress = CLIENT and function(plyv) + local wepv = plyv:GetActiveWeapon() + + if IsValid(wepv) and wepv.GetStat then + if wepv:GetStatL("SelectiveFire") and not wepv:GetOwner():KeyDown(IN_SPEED) then + RunConsoleCommand("impulse", TFA.CYCLE_FIREMODE_IMPULSE_STRING) + elseif wepv:GetOwner():KeyDown(IN_SPEED) then + RunConsoleCommand("impulse", TFA.CYCLE_SAFETY_IMPULSE_STRING) + end + end + end +}) + +TFA.RegisterKeyBind({ + bind = "silencer", + onpress = function(plyv) + local wepv = plyv:GetActiveWeapon() + + if (IsValid(wepv) and wepv.GetStat) and wepv:GetStatRawL("CanBeSilenced") and TFA.Enum.ReadyStatus[wepv:GetStatus()] then + local _, tanim, ttype = wepv:ChooseSilenceAnim(not wepv:GetSilenced()) + wepv:ScheduleStatus(TFA.Enum.STATUS_SILENCER_TOGGLE, wepv:GetActivityLength(tanim, true, ttype)) + end + end +}) + +-- EXAMPLE KEYBIND: +--[[ + TFA.RegisterKeyBind({ + bind = "whatever", -- bind id, cvar is cl_tfa_keys_whatever + onpress = function(ply) end, -- function called on key press + onrelease = function(ply) end, -- function called on key release + think = function(ply) end, -- called from PlayerPostThink when key is held down + }) +]] diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_keyvalues.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_keyvalues.lua new file mode 100644 index 0000000..e1bc3d7 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_keyvalues.lua @@ -0,0 +1,205 @@ +local CHAR_STRING = { + ["\""] = true, + ["'"] = true +} + +local CHAR_TABLE_OPEN = { + ["{"] = true, + ["["] = true +} + +local CHAR_TABLE_CLOSE = { + ["}"] = true, + ["]"] = true +} + +local CHAR_WHITESPACE = { + [" "] = true, + ["\t"] = true, + ["\r"] = true, + ["\n"] = true +} + +local CHAR_NEWLINE = { + ["\r"] = true, + ["\n"] = true +} + +local CHAR_COMMENT = { + ["/"] = true, + ["-"] = true +} + +local KEY_CASE = true +local ORDERED = false + +local buffer = "" +local tbl = {} +local tbl_focus +local tbl_tmp +local value, lastvalue +local ignore_next_pop +local escape +local stringtype +local is_comment = false +local f + +local strsub = string.sub +local strlow = string.lower + +local fread = file.Read + +local function strchar( strv, ind ) + return strsub( strv, ind, ind) +end + +local function ResetValues() + lastvalue = nil + value = nil +end + +local function FlushBuffer(write) + if buffer ~= "" or stringtype then + lastvalue = value + if lastvalue and not KEY_CASE then + lastvalue = strlow(lastvalue) + end + value = buffer + buffer = "" + + if tbl_focus and (write == nil or write) and lastvalue and value then + if ORDERED then + tbl_focus[ #tbl_focus + 1 ] = { ["key"] = lastvalue, ["value"] = value } + else + tbl_focus[lastvalue] = value + end + ResetValues() + end + end +end + +local function PushTable() + FlushBuffer(true) + if value and not KEY_CASE then + value = strlow(value) + end + if value and value ~= "" then + if ORDERED then + tbl_focus[ #tbl_focus + 1 ] = { ["key"] = value, ["value"] = {} } + tbl_focus[ #tbl_focus ].value.__par = tbl_focus + tbl_focus = tbl_focus[ #tbl_focus ].value + else + tbl_focus[value] = istable(tbl_focus[value]) and tbl_focus[value] or {} + tbl_focus[value].__par = tbl_focus + tbl_focus = tbl_focus[value] + end + ignore_next_pop = false + else + ignore_next_pop = true + end + + ResetValues() +end + +local function PopTable() + if not ignore_next_pop then + FlushBuffer(true) + + if tbl_focus.__par then + tbl_tmp = tbl_focus.__par + tbl_focus.__par = nil + tbl_focus = tbl_tmp + end + end + + ignore_next_pop = false + ResetValues() +end + +function TFA.ParseKeyValues(fn, path, use_escape, keep_key_case, invalid_escape_addslash, ordered ) + if use_escape == nil then + use_escape = true + end + if keep_key_case == nil then + keep_key_case = true + end + KEY_CASE = keep_key_case + if invalid_escape_addslash == nil then + invalid_escape_addslash = true + end + if ordered then + ORDERED = true + else + ORDERED = false + end + tbl = {} + tbl_focus = tbl + tbl_tmp = nil + value = nil + lastvalue = nil + escape = false + is_comment = false + stringtype = nil + f = fread(fn, path) + if not f then return tbl end + for i = 1, #f do + local char = strchar(f,i) + + if not char then + FlushBuffer() + break + end + + if is_comment then + if CHAR_NEWLINE[char] then + is_comment = false + end + elseif escape then + if char == "t" then + buffer = buffer .. "\t" + elseif char == "n" then + buffer = buffer .. "\n" + elseif char == "r" then + buffer = buffer + else + if invalid_escape_addslash then + buffer = buffer .. "\\" + end + buffer = buffer .. char + end + + escape = false + elseif char == "\\" and use_escape then + escape = true + elseif CHAR_STRING[char] then + if not stringtype then + FlushBuffer() + stringtype = char + elseif stringtype == char then + FlushBuffer() + stringtype = nil + elseif stringtype then + buffer = buffer .. char + end + elseif stringtype then + buffer = buffer .. char + elseif CHAR_COMMENT[char] then + if CHAR_COMMENT[ strchar(f,i + 1,i + 1 ) ] then + is_comment = true + else + buffer = buffer .. char + end + elseif CHAR_WHITESPACE[char] then + if buffer ~= "" then + FlushBuffer() + end + elseif CHAR_TABLE_OPEN[char] then + PushTable() + elseif CHAR_TABLE_CLOSE[char] then + PopTable() + else + buffer = buffer .. char + end + end + return tbl +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_matproxies.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_matproxies.lua new file mode 100644 index 0000000..96a24b6 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_matproxies.lua @@ -0,0 +1,177 @@ +local nvec = Vector() + +local function SetPlayerColors(ply) + if not IsValid(ply) then return end + + local _SetNWVector = ply.SetNW2Vector or ply.SetNWVector + + nvec.x = ply:GetInfoNum("cl_tfa_laser_color_r", 255) + nvec.y = ply:GetInfoNum("cl_tfa_laser_color_g", 0) + nvec.z = ply:GetInfoNum("cl_tfa_laser_color_b", 0) + _SetNWVector(ply, "TFALaserColor", nvec) + + nvec.x = ply:GetInfoNum("cl_tfa_reticule_color_r", 255) + nvec.y = ply:GetInfoNum("cl_tfa_reticule_color_g", 0) + nvec.z = ply:GetInfoNum("cl_tfa_reticule_color_b", 0) + _SetNWVector(ply, "TFAReticuleColor", nvec) +end + +hook.Add("PlayerSpawn", "TFANetworkColors_Spawn", SetPlayerColors) +concommand.Add("sv_tfa_apply_player_colors", SetPlayerColors) + +if not matproxy then return end + +matproxy.Add({ + name = "PlayerWeaponColorStatic", + init = function(self, mat, values) + self.ResultTo = values.resultvar + end, + bind = function(self, mat, ent) + if (not IsValid(ent)) then return end + local owner = ent:GetOwner() + if (not IsValid(owner) or not owner:IsPlayer()) then return end + local col = owner:GetWeaponColor() + if (not isvector(col)) then return end + mat:SetVector(self.ResultTo, col * 1) + end +}) + +local cvec = Vector() + +matproxy.Add({ + name = "TFALaserColor", + init = function(self, mat, values) + self.ResultTo = values.resultvar + end, + bind = function(self, mat, ent) + local owner + + if (IsValid(ent)) then + owner = ent:GetOwner() + + if not IsValid(owner) then + owner = ent:GetParent() + end + + if IsValid(owner) and owner:IsWeapon() then + owner = owner:GetOwner() or owner:GetOwner() + end + + if not (IsValid(owner) and owner:IsPlayer()) then + owner = GetViewEntity() + end + else + owner = GetViewEntity() + end + + if (not IsValid(owner) or not owner:IsPlayer()) then return end + local c + + if owner.GetNW2Vector then + c = owner:GetNW2Vector("TFALaserColor") or cvec + else + c = owner:GetNWVector("TFALaserColor") or cvec + end + + cvec.x = math.sqrt(c.r / 255) --sqrt for gamma + cvec.y = math.sqrt(c.g / 255) + cvec.z = math.sqrt(c.b / 255) + mat:SetVector(self.ResultTo, cvec) + end +}) + +local cvec_r = Vector() + +matproxy.Add({ + name = "TFAReticuleColor", + init = function(self, mat, values) + self.ResultTo = values.resultvar + end, + bind = function(self, mat, ent) + local owner + + if (IsValid(ent)) then + owner = ent:GetOwner() + + if not IsValid(owner) then + owner = ent:GetParent() + end + + if IsValid(owner) and owner:IsWeapon() then + owner = owner:GetOwner() or owner:GetOwner() + end + + if not (IsValid(owner) and owner:IsPlayer()) then + owner = GetViewEntity() + end + else + owner = GetViewEntity() + end + + if (not IsValid(owner) or not owner:IsPlayer()) then return end + local c + + if owner.GetNW2Vector then + c = owner:GetNW2Vector("TFAReticuleColor") or cvec_r + else + c = owner:GetNWVector("TFAReticuleColor") or cvec_r + end + + cvec_r.x = c.r / 255 + cvec_r.y = c.g / 255 + cvec_r.z = c.b / 255 + mat:SetVector(self.ResultTo, cvec_r) + end +}) + +matproxy.Add({ + name = "TFA_RTScope", + init = function(self, mat, values) + self.RTMaterial = Material("!tfa_rtmaterial") + end, + bind = function(self, mat, ent) + if not self.RTMaterial then + self.RTMaterial = Material("!tfa_rtmaterial") + end + + mat:SetTexture("$basetexture", self.RTMaterial:GetTexture("$basetexture")) + end +}) + +local Lerp = Lerp +local RealFrameTime = RealFrameTime + +local vector_one = Vector(1, 1, 1) + +matproxy.Add({ + name = "TFA_CubemapTint", + init = function(self, mat, values) + self.ResultVar = values.resultvar or "$envmaptint" + self.MultVar = values.multiplier + end, + bind = function(self, mat, ent) + local tint = vector_one + + if IsValid(ent) then + local mult = self.MultVar and mat:GetVector(self.MultVar) or vector_one + + tint = Lerp(RealFrameTime() * 10, mat:GetVector(self.ResultVar), mult * render.GetLightColor(ent:GetPos())) + end + + mat:SetVector(self.ResultVar, tint) + end +}) + +-- VMT Example: +--[[ + $envmapmultiplier "[1 1 1]" // Lighting will be multiplied by this value + + Proxies + { + TFA_CubemapTint + { + resultvar $envmaptint // Write final output to $envmaptint + multiplier $envmapmultiplier // Use our value for default envmap tint + } + } +]] diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_melee_autorun.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_melee_autorun.lua new file mode 100644 index 0000000..76e15a3 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_melee_autorun.lua @@ -0,0 +1,232 @@ +local vector_origin = Vector() + +local timed_blocking_cv = GetConVar("sv_tfa_melee_blocking_timed") +local angle_mult_cv = GetConVar("sv_tfa_melee_blocking_anglemult") +local deflect_cv = GetConVar("sv_tfa_melee_blocking_deflection") +local stun_cv = GetConVar("sv_tfa_melee_blocking_stun_enabled") +local stuntime_cv = GetConVar("sv_tfa_melee_blocking_stun_time") + +local bul = { + HullSize = 5, + Num = 1, + Tracer = 1, + AmmoType = "", + TracerName = "Tracer", + Spread = Vector(0.05,0.05,0), + Distance = 56756 +} + +local function CanDeflect() + return true +end + +local function DeflectBullet( ent, dmginfo, olddmg ) + if dmginfo:IsDamageType( DMG_BULLET ) and CanDeflect() and ent.FireBullets then + bul.Src = ent:GetShootPos() + bul.Dir = ent:EyeAngles():Forward() + bul.Damage = olddmg + bul.Force = olddmg / 10 + local atk = dmginfo:GetAttacker() + if IsValid( atk ) and atk.TFALastTracer then + bul.Tracer = atk.TFALastTracer + end + ent:FireBullets( bul, false ) + dmginfo:ScaleDamage(0) + end +end + +local stuntime = 0.65 + +local function StunNPC( npc, ply ) + if stun_cv and not stun_cv:GetBool() then return end + if ( not IsValid( npc ) ) or ( not npc:IsNPC() ) then + return + end + if npc.ClearSchedule then + npc:ClearSchedule() + end + if npc.SetEnemy then + npc:SetEnemy(nil) + end + if npc.AddEntityRelationship and IsValid(ply) then + local oldrel = npc.GetRelationship and npc:GetRelationship(ply) or D_HT + npc:AddEntityRelationship( ply, D_NU, 99) + stuntime = stuntime_cv:GetFloat() + timer.Simple( stuntime , function() + if IsValid(npc) and npc:IsNPC() and IsValid(ply) then + npc:AddEntityRelationship( ply, oldrel, 99) + end + end) + end + if npc.ClearEnemyMemory then + npc:ClearEnemyMemory() + end +end + +local function BlockDamageNew( ent, dmginfo ) + if not ent:IsPlayer() then return end + if dmginfo:IsDamageType( DMG_DROWNRECOVER ) or dmginfo:IsDamageType(DMG_DIRECT) then return end + local wep + wep = ent:GetActiveWeapon() + + if (wep.IsTFAWeapon and wep.BlockDamageTypes and wep:GetStatus() == TFA.Enum.STATUS_BLOCKING) then + local canblock = false + for _,v in ipairs(wep.BlockDamageTypes) do + if dmginfo:IsDamageType(v) then canblock = true end + end + + local retVal = hook.Run("TFA_MeleeCanBlockDamage", wep, ent, dmginfo, canblock) + if retVal ~= nil then + canblock = retVal + end + + if canblock then + local damageinflictor, blockthreshold + damageinflictor = dmginfo:GetInflictor() + + if (not IsValid(damageinflictor)) then + damageinflictor = dmginfo:GetAttacker() + end + + blockthreshold = ( wep.BlockCone or 135 ) / 2 + if angle_mult_cv then + blockthreshold = blockthreshold * angle_mult_cv:GetFloat() + end + if ( IsValid(damageinflictor) and ( math.abs(math.AngleDifference( ent:EyeAngles().y, ( damageinflictor:GetPos() - ent:GetPos() ):Angle().y )) <= blockthreshold)) then + local fac = math.Clamp( ( CurTime() - wep:GetBlockStart() - wep.BlockTimeWindow ) / wep.BlockTimeFade, 0, 1) + local dmgscale + if ( not timed_blocking_cv ) or timed_blocking_cv:GetBool() then + dmgscale = Lerp(fac, wep.BlockDamageMaximum, wep.BlockDamageMinimum) + else + dmgscale = wep.BlockDamageMaximum + end + local olddmg = dmginfo:GetDamage() + dmgscale = math.min( dmgscale, wep.BlockDamageCap / dmginfo:GetDamage() ) + --print(fac) + dmginfo:ScaleDamage(dmgscale) + dmginfo:SetDamagePosition(vector_origin) + dmginfo:SetDamageType( bit.bor( dmginfo:GetDamageType(), DMG_DROWNRECOVER ) ) + wep:EmitSound(wep.BlockSound or "") + + if wep.ChooseBlockAnimation then + wep:ChooseBlockAnimation() + end + + if deflect_cv and deflect_cv:GetInt() == 2 then + DeflectBullet( ent, dmginfo, olddmg ) + end + + if dmginfo:GetDamage() < 1 then + if deflect_cv and deflect_cv:GetInt() == 1 and wep.BlockCanDeflect then + DeflectBullet( ent, dmginfo, olddmg ) + end + StunNPC( dmginfo:GetAttacker(), ent ) + return true + end + return + end + end + end +end + + +hook.Add("EntityFireBullets","TFA_Melee_LogTracer",function(ent,bulv) --Record tracer for blocking + ent.TFALastTracer = bulv.TracerName or "" +end) + +local npc_dmg_scale_cv = GetConVar("sv_tfa_melee_damage_npc") +local ply_dmg_scale_cv = GetConVar("sv_tfa_melee_damage_ply") + +hook.Add("EntityTakeDamage", "TFA_Melee_Scaling", function( ent, dmginfo ) + local wep = dmginfo:GetInflictor() + if not IsValid(wep) then return end + + if wep:IsPlayer() then wep = wep:GetActiveWeapon() end + if not IsValid(wep) or not wep:IsWeapon() or not wep.IsTFAWeapon or not wep.IsMelee then return end + + if ent:IsNPC() then + dmginfo:ScaleDamage( npc_dmg_scale_cv:GetFloat() ) + elseif ent:IsPlayer() then + dmginfo:ScaleDamage( ply_dmg_scale_cv:GetFloat() ) + end +end) --Cancel +hook.Add("EntityTakeDamage", "aaa_TFA_Melee_Block", function( ent, dmginfo ) + return BlockDamageNew( ent, dmginfo ) +end) --Cancel +hook.Add("ScalePlayerDamage", "aaa_TFA_Melee_Block", function( ent, _, dmginfo ) --Cancel + return BlockDamageNew( ent, dmginfo ) +end) + +game.AddAmmoType({ + name = "TFMSwordHitGenericSlash", + dmgtype = DMG_SLASH, + tracer = TRACER_NONE +}) + +local function TFMPlayerSpawn(ply) + ply:SetNW2Vector("TFM_SwordPosition", Vector(1, 1, 1)) + ply:SetNW2Vector("TFM_SwordNormal", Vector(1, 1, 1)) + ply:SetNW2Bool("TFM_IsSprinting", false) + ply:SetNW2Bool("TFM_IsBlocking", false) + ply:SetNW2Bool("TFM_IsSwinging", false) + ply:SetNW2Float("TFM_SwingStart", CurTime()) +end + +hook.Add("PlayerSpawn", "TFM_PlayerSpawn", TFMPlayerSpawn) + +hook.Add("EntityTakeDamage", "TFM_Block", function(ent, dmginfo) --Legacy + if ent:IsPlayer() then + local wep + wep = ent:GetActiveWeapon() + + if (wep.IsTFAWeapon and wep.BlockAngle) and (dmginfo:IsDamageType(DMG_SLASH) or dmginfo:IsDamageType(DMG_CLUB) or (wep.NinjaMode and wep.NinjaMode == true and (dmginfo:IsDamageType(DMG_CRUSH) or dmginfo:IsDamageType(DMG_BULLET)))) and wep:GetIronSights() then + local damageinflictor, blockthreshold + damageinflictor = dmginfo:GetInflictor() + + if (not IsValid(damageinflictor)) then + damageinflictor = dmginfo:GetAttacker() + end + + blockthreshold = wep.BlockAngle / 2 or 90 + + if (IsValid(damageinflictor) and (math.abs((ent:GetAimVector():Angle() - (damageinflictor:GetPos() - ent:GetPos()):Angle()).y) <= blockthreshold)) or (math.abs((ent:GetAimVector():Angle() - (dmginfo:GetDamagePosition() - ent:GetPos()):Angle()).y) <= blockthreshold) then + local fac = math.Clamp((CurTime() - wep:GetBlockStart() - wep.BlockWindow) / wep.BlockFadeTime, 0, 1) + local dmgscale + if ( not timed_blocking_cv ) or timed_blocking_cv:GetBool() then + dmgscale = Lerp(fac, wep.BlockMaximum, wep.BlockMinimum) + else + dmgscale = wep.BlockMaximum + end + --print(fac) + dmginfo:ScaleDamage(dmgscale) + dmginfo:SetDamagePosition(vector_origin) + wep:EmitSound(wep.Primary.Sound_Impact_Metal) + + if wep.BlockAnim then + wep:BlockAnim() + end + end + end + end +end) +--Getting the position and angle of an attachment and sending it back to the server is wayyy too laggy. Must be pre-coded. +--[[ +if SERVER then + util.AddNetworkString( "TFM_SAPacket" ) + net.Receive("TFM_SAPacket", function() + local ply; + ply = net.ReadEntity() + local pos; + pos = net.ReadVector() + local norm; + norm = net.ReadNormal() + if IsValid(ply) then + if pos and norm then + ply:SetNW2Vector("TFM_SwordPosition",pos) + ply:SetNW2Vector("TFM_SwordNormal",norm) + end + end + end) +end +]] +-- diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_meta.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_meta.lua new file mode 100644 index 0000000..c6e89c1 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_meta.lua @@ -0,0 +1,27 @@ +local WEAPON = FindMetaTable("Weapon") + +if WEAPON then + function WEAPON:IsTFA() -- please do not use, just check for IsTFAWeapon directly + return self.IsTFAWeapon + end +else + print("[TFA Base] Can't find weapon metatable!") +end + +local PLAYER = FindMetaTable("Player") + +if PLAYER then + function PLAYER:TFA_ZoomKeyDown() + if not IsValid(self) then return false end + + return self:GetNW2Bool("TFA_ZoomKeyDown", false) + end + + function PLAYER:TFA_SetZoomKeyDown(isdown) + if not IsValid(self) then return end + + self:SetNW2Bool("TFA_ZoomKeyDown", isdown) + end +else + print("[TFA Base] Can't find player metatable!") +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_netcode.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_netcode.lua new file mode 100644 index 0000000..9d861d5 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_netcode.lua @@ -0,0 +1,146 @@ +if SERVER then + --Pool netstrings + util.AddNetworkString("tfaSoundEvent") + util.AddNetworkString("tfaSoundEventStop") + util.AddNetworkString("tfa_base_muzzle_mp") + util.AddNetworkString("tfaShotgunInterrupt") + util.AddNetworkString("tfaRequestFidget") + util.AddNetworkString("tfaSDLP") + util.AddNetworkString("tfaArrowFollow") + util.AddNetworkString("tfaTracerSP") + util.AddNetworkString("tfaBaseShellSV") + --util.AddNetworkString("tfaAltAttack") + + util.AddNetworkString("tfaHitmarker") + util.AddNetworkString("tfaHitmarker3D") + util.AddNetworkString("tfa_friendly_encounter") + + do + local old_state = false + + timer.Create("tfa_friendly_encounter", 2, 0, function() + local new_state = game.GetGlobalState("friendly_encounter") == GLOBAL_ON + + if old_state ~= new_state then + net.Start("tfa_friendly_encounter") + net.WriteBool(new_state) + net.Broadcast() + + old_state = new_state + end + end) + + hook.Add("PlayerAuthed", "tfa_friendly_encounter", function() + old_state = false + end) + end + + --Enable CKey Inspection + + net.Receive("tfaRequestFidget",function(length,client) + local wep = client:GetActiveWeapon() + if IsValid(wep) and wep.CheckAmmo then wep:CheckAmmo() end + end) + + --Enable shotgun interruption + net.Receive("tfaShotgunInterrupt", function(length, client) + if IsValid(client) and client:IsPlayer() and client:Alive() then + local ply = client + local wep = ply:GetActiveWeapon() + + if IsValid(wep) and wep.ShotgunInterrupt then + wep:ShotgunInterrupt() + end + end + end) + + if game.SinglePlayer() then + net.Receive("tfaSDLP",function(length,client) + local bool = net.ReadBool() + client.TFASDLP = bool + end) + end + + --Enable alternate attacks + --[[ + net.Receive("tfaAltAttack", function(length, client) + if IsValid(client) and client:IsPlayer() and client:Alive() then + local ply = client + wep = ply:GetActiveWeapon() + + if IsValid(wep) and wep.AltAttack then + wep:AltAttack() + end + end + end) + ]]-- +else + TFA.FriendlyEncounter = false + + net.Receive("tfa_friendly_encounter", function() + TFA.FriendlyEncounter = net.ReadBool() + end) + + --Arrow can follow entities clientside too + net.Receive("tfaArrowFollow",function() + local ent = net.ReadEntity() + ent.targent = net.ReadEntity() + ent.targbone = net.ReadInt( 8 ) + ent.posoff = net.ReadVector( ) + ent.angoff = net.ReadAngle( ) + ent:TargetEnt( false ) + end) + + --Receive sound events on client + net.Receive("tfaSoundEvent", function(length, ply) + local wep = net.ReadEntity() + local snd = net.ReadString() + local shouldPause = net.ReadBool() + + if IsValid(wep) and snd and snd ~= "" then + wep:EmitSound(snd, nil, nil, nil, nil, shouldPause and SND_SHOULDPAUSE or SND_NOFLAGS) + end + end) + + net.Receive("tfaSoundEventStop", function(length, ply) + local wep = net.ReadEntity() + local snd = net.ReadString() + + if IsValid(wep) and snd and snd ~= "" then + wep:StopSound(snd) + end + end) + + --Receive muzzleflashes on client + net.Receive("tfa_base_muzzle_mp", function(length, ply) + local wep = net.ReadEntity() + + if IsValid(wep) and wep.ShootEffectsCustom then + wep:ShootEffectsCustom(true) + end + end) + + net.Receive("tfaBaseShellSV", function(length, ply) + local wep = net.ReadEntity() + + if IsValid(wep) and wep.MakeShellBridge then + wep:MakeShellBridge() + end + end) + + net.Receive( "tfaTracerSP", function( length, ply ) + local part = net.ReadString() + local startPos = net.ReadVector() + local endPos = net.ReadVector() + local woosh = net.ReadBool() + local vent = net.ReadEntity() + local att = net.ReadInt( 8 ) + if IsValid( vent ) then + local aP = vent:GetAttachment( att or 1 ) + if aP then + startPos = aP.Pos + end + end + TFA.ParticleTracer( part, startPos, endPos, woosh, vent, att ) + end) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_npc_teamcolor.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_npc_teamcolor.lua new file mode 100644 index 0000000..36a53b4 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_npc_teamcolor.lua @@ -0,0 +1,65 @@ +local ENTMETA = FindMetaTable("Entity") +local PLYMETA = FindMetaTable("Player") +local NPCMETA = FindMetaTable("NPC") + +local IsValid = ENTMETA.IsValid + +local Alive = PLYMETA.Alive +local GetActiveWeapon = PLYMETA.GetActiveWeapon +local GetAimVector = PLYMETA.GetAimVector +local GetShootPos = PLYMETA.GetShootPos + +local Disposition = NPCMETA.Disposition + +local util_TraceLine = util.TraceLine +local MASK_SHOT = MASK_SHOT + +if SERVER then + util.AddNetworkString("TFA_NPC_DISP") + + local NPCDispCacheSV = {} + local function PlayerPostThink(ply) + if not Alive(ply) then return end + + local wep = GetActiveWeapon(ply) + if not IsValid(wep) or not wep.IsTFAWeapon then return end + + if not NPCDispCacheSV[ply] then + NPCDispCacheSV[ply] = {} + end + + local tr = {} + tr.start = GetShootPos(ply) + tr.endpos = tr.start + GetAimVector(ply) * 0xffff + tr.filter = ply + tr.mask = MASK_SHOT + local targent = util_TraceLine(tr).Entity + + if IsValid(targent) and type(targent) == "NPC" then + local disp = Disposition(targent, ply) + + if not NPCDispCacheSV[ply][targent] or NPCDispCacheSV[ply][targent] ~= disp then + NPCDispCacheSV[ply][targent] = disp + + net.Start("TFA_NPC_DISP") + net.WriteEntity(targent) + net.WriteUInt(disp, 3) + net.Send(ply) + end + end + end + + hook.Add("PlayerPostThink", "TFA_NPCDispositionSync", PlayerPostThink) +else + local NPCDispCacheSV = {} + net.Receive("TFA_NPC_DISP", function() + local ent = net.ReadEntity() + local disp = net.ReadUInt(3) + + NPCDispCacheSV[ent] = disp + end) + + function TFA.GetNPCDisposition(ent) + return NPCDispCacheSV[ent] + end +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_npc_weaponmenu.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_npc_weaponmenu.lua new file mode 100644 index 0000000..6333958 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_npc_weaponmenu.lua @@ -0,0 +1,57 @@ +-- AI Options +if CLIENT then + hook.Add("PopulateMenuBar", "NPCOptions_MenuBar_TFA", function(menubarV) + local m = menubarV:AddOrGetMenu("#menubar.npcs") + local wpns = m:AddSubMenu("#tfa.menubar.npcs.weapon") + wpns:SetDeleteSelf(false) + + wpns:AddCVar("#menubar.npcs.defaultweapon", "gmod_npcweapon", "") + wpns:AddCVar("#menubar.npcs.noweapon", "gmod_npcweapon", "none") + wpns:AddSpacer() + + local weaponCats = {} + + for _, wep in pairs(weapons.GetList()) do + if wep and wep.Spawnable and weapons.IsBasedOn(wep.ClassName, "tfa_gun_base") then + local cat = wep.Category or "Other" + weaponCats[cat] = weaponCats[cat] or {} + + table.insert(weaponCats[cat], { + ["class"] = wep.ClassName, + ["title"] = wep.PrintName or wep.ClassName + }) + end + end + + local catKeys = table.GetKeys(weaponCats) + table.sort(catKeys, function(a, b) return a < b end) + + for _, k in ipairs(catKeys) do + local v = weaponCats[k] + local wpnSub = wpns:AddSubMenu(k) + wpnSub:SetDeleteSelf(false) + table.SortByMember(v, "title", true) + + for _, b in ipairs(v) do + wpnSub:AddCVar(b.title, "gmod_npcweapon", b.class) + end + end + end) +else + local npcWepList = list.GetForEdit("NPCUsableWeapons") + + hook.Add("PlayerSpawnNPC", "TFACheckNPCWeapon", function(plyv, npcclassv, wepclassv) + if type(wepclassv) ~= "string" or wepclassv == "" then return end + + if not npcWepList[wepclassv] then -- do not copy the table + local wep = weapons.GetStored(wepclassv) + + if wep and (wep.Spawnable and not wep.AdminOnly) and weapons.IsBasedOn(wep.ClassName, "tfa_gun_base") then + npcWepList[wepclassv] = { + ["class"] = wep.ClassName, + ["title"] = wep.PrintName or wep.ClassName + } + end + end + end) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_nzombies.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_nzombies.lua new file mode 100644 index 0000000..3c5a670 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_nzombies.lua @@ -0,0 +1,205 @@ +TFA.NZombies = TFA.NZombies or {} + +if TFA.NZombies.Patch == nil then + TFA.NZombies.Patch = true --Change this if you need to +end + +local cv_melee_scaling, cv_melee_basefactor, cv_melee_berserkscale +local nzombies = string.lower(engine.ActiveGamemode() or "") == "nzombies" + +if nZombies or NZombies or NZ then + nzombies = true +end + +if nzombies then + cv_melee_scaling = CreateConVar("sv_tfa_nz_melee_scaling", "1", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_REPLICATED}, "0.5x means if zombies have 4x health, melee does 2x damage") + cv_melee_basefactor = CreateConVar("sv_tfa_nz_melee_multiplier", "0.65", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_REPLICATED}, "Base damage scale for TFA Melees.") + cv_melee_berserkscale = CreateConVar("sv_tfa_nz_melee_immunity", "0.67", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_REPLICATED}, "Take X% damage from zombies while you're melee.") + --cv_melee_juggscale = CreateConVar("sv_tfa_nz_melee_juggernaut", "1.5", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_REPLICATED}, "Do X% damage to zombies while you're jug.") + hook.Add("TFA_AnimationRate","NZBase",function(wep,act,rate) + if wep:OwnerIsValid() and wep:GetOwner().HasPerk and wep:GetOwner():HasPerk("speed") and wep.SpeedColaActivities[ act ] then + rate = rate * wep.SpeedColaFactor + end + if wep:OwnerIsValid() and wep:GetOwner().HasPerk and wep:GetOwner():HasPerk("dtap") and wep.DTapActivities[ act ] then + rate = rate * wep.DTapSpeed + end + if wep:OwnerIsValid() and wep:GetOwner().HasPerk and wep:GetOwner():HasPerk("dtap2") and wep.DTapActivities[ act ] then + rate = rate * wep.DTap2Speed + end + return rate + end) + hook.Add("TFA_Deploy","NZBase",function(wep) + local pap = wep:GetPaP() + wep.OldPaP = pap + local spd2 = wep:OwnerIsValid() and wep:GetOwner().HasPerk and wep:GetOwner():HasPerk("speed") + if pap and pap ~= wep.OldPaP then + if AddPackAPunchName and wep.NZPaPName and not wep.HasAddedNZName then + AddPackAPunchName( wep.ClassName, wep.NZPaPName ) + wep.HasAddedNZName = true + end + if wep.NZPaPName and wep:GetPaP() then + wep.PrintName = wep.NZPaPName + wep:SetNW2String("PrintName",wep.NZPaPName) + end + local pn = wep:GetNW2String("PrintName") + if pn and pn ~= "" then + wep.PrintName = pn + end + wep:ClearStatCache() + timer.Simple(0.1,function() + if IsValid(wep) then + wep:ClearStatCache() + end + end) + end + if spd2 ~= wep.OldSpCola then + wep:ClearStatCache() + end + wep.OldSpCola = spd2 + end) + hook.Add("TFA_Initialize", "NZBase", function(wep) + timer.Simple(0.1, function() -- timers for everything YAY + if not IsValid(wep) then return end + + wep.Primary_TFA.Ammo = game.GetAmmoName(wep:GetPrimaryAmmoType()) + wep:ClearStatCache("Primary.Ammo") + end) + end) +end +--[[ +local function SpreadFix() + + local GAMEMODE = gmod.GetGamemode() or GAMEMODE + if not GAMEMODE then return end + + print("[TFA] Patching NZombies") + if TFA.NZombies.Patch then return end + + local ghosttraceentities = { + ["wall_block"] = true, + ["invis_wall"] = true, + ["player"] = true + } + + function GAMEMODE:EntityFireBullets(ent, data) + -- Fire the PaP shooting sound if the weapon is PaP'd + --print(wep, wep.pap) + if ent:IsPlayer() and IsValid(ent:GetActiveWeapon()) then + local wep = ent:GetActiveWeapon() + if wep.pap and ( not wep.IsMelee ) and ( not wep.IsKnife ) then + wep:EmitSound("nz/effects/pap_shoot_glock20.wav", 105, 100) + end + end + + if ent:IsPlayer() and ent:HasPerk("dtap2") then + data.Num = data.Num * 2 + end + + -- Perform a trace that filters out entities from the table above + local tr = util.TraceLine({ + start = data.Src, + endpos = data.Src + (data.Dir * data.Distance), + filter = function(entv) + if ghosttraceentities[entv:GetClass()] and not entv:IsPlayer() then + return true + else + return false + end + end + }) + + --PrintTable(tr) + -- If we hit anything, move the source of the bullets up to that point + if IsValid(tr.Entity) and tr.Fraction < 1 then + local tr2 = util.TraceLine({ + start = data.Src, + endpos = data.Src + (data.Dir * data.Distance), + filter = function(entv) + if ghosttraceentities[entv:GetClass()] then + return false + else + return true + end + end + }) + + data.Src = tr2.HitPos - data.Dir * 5 + + return true + end + + if ent:IsPlayer() and ent:HasPerk("dtap2") then return true end + end +end +]] +-- +local function MeleeFix() + hook.Add("EntityTakeDamage", "TFA_MeleeScaling", function(target, dmg) + if not TFA.NZombies.Patch then return end + if not nzRound then return end + local ent = dmg:GetInflictor() + + if not ent:IsWeapon() and ent:IsPlayer() then + ent = ent:GetActiveWeapon() + end + + if not IsValid(ent) or not ent:IsWeapon() then return end + + if ent.IsTFAWeapon and (dmg:IsDamageType(DMG_CRUSH) or dmg:IsDamageType(DMG_CLUB) or dmg:IsDamageType(DMG_SLASH)) then + local scalefactor = cv_melee_scaling:GetFloat() + local basefactor = cv_melee_basefactor:GetFloat() + dmg:ScaleDamage(((nzRound:GetZombieHealth() - 75) / 75 * scalefactor + 1) * basefactor) + --if IsValid(ent:GetOwner()) and ent:GetOwner():IsPlayer() and ent:GetOwner():HasPerk("jugg") then + -- dmg:ScaleDamage(cv_melee_juggscale:GetFloat()) + --end + end + end) + + hook.Add("EntityTakeDamage", "TFA_MeleeReceiveLess", function(target, dmg) + if not TFA.NZombies.Patch then return end + + if target:IsPlayer() and target.GetActiveWeapon then + local wep = target:GetActiveWeapon() + + if IsValid(wep) and wep.IsTFAWeapon and (wep.IsKnife or wep.IsMelee or wep.Primary.Reach) then + dmg:ScaleDamage(cv_melee_berserkscale:GetFloat()) + end + end + end) + + hook.Add("EntityTakeDamage", "TFA_MeleePaP", function(target, dmg) + if not TFA.NZombies.Patch then return end + local ent = dmg:GetInflictor() + + if IsValid(ent) then + local wep + + if ent:IsPlayer() then + wep = ent:GetActiveWeapon() + elseif ent:IsWeapon() then + wep = ent + end + + if IsValid(wep) and wep.IsTFAWeapon and (wep.Primary.Attacks or wep.IsMelee or wep.Primary.Reach) and wep:GetPaP() then + dmg:ScaleDamage(2) + end + end + end) +end + +local function NZPatch() + if not TFA.NZombies.Patch then return end + nzombies = string.lower(engine.ActiveGamemode() or "") == "nzombies" + + if nZombies or NZombies or NZ or NZombies then + nzombies = true + end + + if nzombies then + --SpreadFix() + MeleeFix() + end +end + +hook.Add("InitPostEntity", "TFA_NZPatch", NZPatch) +NZPatch() diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_particles.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_particles.lua new file mode 100644 index 0000000..5cb23d7 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_particles.lua @@ -0,0 +1,48 @@ +TFA.Particles = TFA.Particles or {} +TFA.Particles.FlareParts = {} +TFA.Particles.VMAttachments = {} + +TFA.Particles.PCFParticles = TFA.Particles.PCFParticles or {} + +TFA.Particles.PCFParticles["tfa_muzzle_rifle"] = "tfa_muzzleflashes" +TFA.Particles.PCFParticles["tfa_muzzle_sniper"] = "tfa_muzzleflashes" +TFA.Particles.PCFParticles["tfa_muzzle_energy"] = "tfa_muzzleflashes" +TFA.Particles.PCFParticles["tfa_muzzle_energy"] = "tfa_muzzleflashes" +TFA.Particles.PCFParticles["tfa_muzzle_gauss"] = "tfa_muzzleflashes" + +-- TFA.Particles.PCFParticles["weapon_muzzle_smoke_long"] = "csgo_fx" +-- TFA.Particles.PCFParticles["weapon_muzzle_smoke"] = "csgo_fx" + +TFA.Particles.PCFParticles["tfa_ins2_weapon_muzzle_smoke"] = "tfa_ins2_muzzlesmoke" +TFA.Particles.PCFParticles["tfa_ins2_weapon_shell_smoke"] = "tfa_ins2_shellsmoke" +TFA.Particles.PCFParticles["tfa_bullet_smoke_tracer"] = "tfa_ballistics" +TFA.Particles.PCFParticles["tfa_bullet_fire_tracer"] = "tfa_ballistics" +TFA.Particles.PCFParticles["tfa_ins2_shell_eject"] = "tfa_ins2_ejectionsmoke" + +--legacy +TFA.Particles.PCFParticles["smoke_trail_tfa"] = "tfa_smoke" +TFA.Particles.PCFParticles["smoke_trail_controlled"] = "tfa_smoke" + +TFA.Particles.SmokeLightingMin = Vector(0.15, 0.15, 0.15) +TFA.Particles.SmokeLightingMax = Vector(0.75, 0.75, 0.75) +TFA.Particles.SmokeLightingClamp = 1 + +local addedparts = {} +local cachedparts = {} + +function TFA.Particles.Initialize() + for k, v in pairs(TFA.Particles.PCFParticles) do + if not addedparts[v] then + game.AddParticles("particles/" .. v .. ".pcf") + addedparts[v] = true + end + + if not cachedparts[k] and not string.find(k, "DUMMY") then + PrecacheParticleSystem(k) + cachedparts[k] = true + end + end +end + +hook.Add("InitPostEntity", "TFA.Particles.Initialize", TFA.Particles.Initialize) +TFA.Particles.Initialize() diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_small_entities.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_small_entities.lua new file mode 100644 index 0000000..b3efeb0 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_small_entities.lua @@ -0,0 +1,293 @@ +-- This file contain aliases/slight modifications which do not deserve their own Lua file + +weapons.Register({ + Base = "tfa_nade_base", + AllowUnderhanded = true, +}, "tfa_cssnade_base") + +weapons.Register({ + Base = "tfa_gun_base", + Shotgun = true, +}, "tfa_shotty_base") + +weapons.Register({ + Base = "tfa_gun_base", +}, "tfa_akimbo_base") + +if SERVER then + AddCSLuaFile("tfa/3dscoped_base.lua") +end + +local SWEP_ = include("tfa/3dscoped_base.lua") +local SWEP = table.Copy(SWEP_) +SWEP.Secondary = {} + +SWEP.Secondary.UseACOG = false +SWEP.Secondary.UseMilDot = false +SWEP.Secondary.UseSVD = false +SWEP.Secondary.UseParabolic = false +SWEP.Secondary.UseElcan = false +SWEP.Secondary.UseGreenDuplex = false +SWEP.RTScopeFOV = 6 +SWEP.RTScopeAttachment = 3 --Anchor the scope shadow to this +SWEP.Scoped = false +SWEP.BoltAction = false +SWEP.ScopeLegacyOrientation = false --used to align with eyeangles instead of vm angles +SWEP.ScopeAngleTransforms = {} +--{"P",1} --Pitch, 1 +--{"Y",1} --Yaw, 1 +--{"R",1} --Roll, 1 +SWEP.ScopeOverlayTransforms = {0, 0} +SWEP.ScopeOverlayTransformMultiplier = 0.8 +SWEP.RTMaterialOverride = 1 +SWEP.IronSightsSensitivity = 1 +SWEP.ScopeShadow = nil +SWEP.ScopeReticule = nil +SWEP.ScopeDirt = nil +SWEP.ScopeReticule_CrossCol = false +SWEP.ScopeReticule_Scale = {1, 1} +--[[End of Tweakable Parameters]]-- +SWEP.Scoped_3D = true +SWEP.BoltAction_3D = false + +SWEP.Base = "tfa_bash_base" + +weapons.Register(SWEP, "tfa_3dbash_base") + +SWEP = table.Copy(SWEP_) +SWEP.Secondary = {} + +SWEP.Secondary.UseACOG = false +SWEP.Secondary.UseMilDot = false +SWEP.Secondary.UseSVD = false +SWEP.Secondary.UseParabolic = false +SWEP.Secondary.UseElcan = false +SWEP.Secondary.UseGreenDuplex = false +SWEP.RTScopeFOV = 6 +SWEP.RTScopeAttachment = 3 +SWEP.Scoped = false +SWEP.BoltAction = false +SWEP.ScopeLegacyOrientation = false --used to align with eyeangles instead of vm angles +SWEP.ScopeAngleTransforms = {} +--{"P",1} --Pitch, 1 +--{"Y",1} --Yaw, 1 +--{"R",1} --Roll, 1 +SWEP.ScopeOverlayTransforms = {0, 0} +SWEP.ScopeOverlayTransformMultiplier = 0.8 +SWEP.RTMaterialOverride = 1 +SWEP.IronSightsSensitivity = 1 +SWEP.ScopeShadow = nil +SWEP.ScopeReticule = nil +SWEP.ScopeDirt = nil +SWEP.ScopeReticule_CrossCol = false +SWEP.ScopeReticule_Scale = {1, 1} +--[[End of Tweakable Parameters]]-- +SWEP.Scoped_3D = true +SWEP.BoltAction_3D = false + +SWEP.Base = "tfa_gun_base" + +weapons.Register(SWEP, "tfa_3dscoped_base") + +weapons.Register({ + Base = "tfa_gun_base", + + Secondary = { + UseACOG = false, + UseMilDot = false, + UseSVD = false, + UseParabolic = false, + UseElcan = false, + UseGreenDuplex = false, + }, + + Scoped = true, + BoltAction = false, +}, "tfa_scoped_base") + +local ammo = { + ["357"] = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "357", + Category = "TFA Ammunition", + Spawnable = true, + AdminSpawnable = true, + Class = "", + MyModel = "models/Items/357ammo.mdl", + AmmoCount = 25, + AmmoType = "357", + DrawText = true, + TextColor = Color(225, 225, 225, 255), + TextPosition = Vector(5, 0, 7.5), + TextAngles = Vector(42, 90, 0), + ShouldDrawShadow = true, + ImpactSound = "Default.ImpactSoft", + Damage = 50, + }, + + ar2 = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "Assault Ammo", + Category = "TFA Ammunition", + Spawnable = true, + AdminSpawnable = true, + Class = "", + MyModel = "models/Items/BoxMRounds.mdl", + AmmoCount = 100, + AmmoType = "ar2", + DrawText = true, + TextColor = Color(5, 5, 5, 255), + TextPosition = Vector(2, 1.5, 13.4), + TextAngles = Vector(90, 90, 90), + ShouldDrawShadow = true, + ImpactSound = "Default.ImpactSoft", + Damage = 35, + Text = "Assault Ammo", + }, + + buckshot = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "Buckshot", + Category = "TFA Ammunition", + Spawnable = true, + AdminSpawnable = true, + Class = "", + MyModel = "models/Items/BoxBuckshot.mdl", + AmmoCount = 20, + AmmoType = "buckshot", + DrawText = true, + TextColor = Color(225, 225, 225, 255), + TextPosition = Vector(2, 3.54, 3), + TextAngles = Vector(0, 90, 90), + ShouldDrawShadow = true, + ImpactSound = "Default.ImpactSoft", + Damage = 40, + Text = "Buckshot", + }, + + pistol = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "Pistol Rounds", + Category = "TFA Ammunition", + Spawnable = true, + AdminSpawnable = true, + Class = "", + MyModel = "models/Items/BoxSRounds.mdl", + AmmoCount = 100, + AmmoType = "pistol", + DrawText = true, + TextColor = Color(255, 255, 255, 255), + TextPosition = Vector(2, 1.5, 11.6), + TextAngles = Vector(90, 90, 90), + ShouldDrawShadow = true, + ImpactSound = "Default.ImpactSoft", + Damage = 40, + Text = "Pistol Rounds", + }, + + smg = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "SMG Rounds", + Category = "TFA Ammunition", + Spawnable = true, + AdminSpawnable = true, + Class = "", + MyModel = "models/Items/BoxSRounds.mdl", + AmmoCount = 100, + AmmoType = "smg1", + DrawText = true, + TextColor = Color(255, 255, 255, 255), + TextPosition = Vector(2, 1.5, 11.6), + TextAngles = Vector(90, 90, 90), + ShouldDrawShadow = true, + ImpactSound = "Default.ImpactSoft", + Damage = 20, + Text = "SMG Rounds", + }, + + smg1_grenade = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "SMG Grenade", + Category = "TFA Ammunition", + + Spawnable = true, + AdminSpawnable = true, + + MyModel = "models/items/tfa/ar2_grenade.mdl", + + AmmoType = "SMG1_Grenade", + AmmoCount = 1, + + DamageThreshold = 15, + }, + + smg1_grenade_large = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "SMG Grenades", + Category = "TFA Ammunition", + + Spawnable = true, + AdminSpawnable = true, + + MyModel = "models/items/tfa/boxar2grenades.mdl", + + AmmoType = "SMG1_Grenade", + AmmoCount = 5, + + DamageThreshold = 55, + }, + + sniper_rounds = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "Sniper Ammo", + Category = "TFA Ammunition", + Spawnable = true, + AdminSpawnable = true, + Class = "", + MyModel = "models/Items/sniper_round_box.mdl", + AmmoCount = 30, + AmmoType = "SniperPenetratedRound", + DrawText = true, + TextColor = Color(185, 25, 25, 255), + TextPosition = Vector(1, -1.45, 2.1), + TextAngles = Vector(90, 0, 0), + ShouldDrawShadow = true, + ImpactSound = "Default.ImpactSoft", + Damage = 80, + Text = "Sniper Rounds", + TextScale = 0.5, + }, + + winchester = { + Type = "anim", + Base = "tfa_ammo_base", + PrintName = "Winchester Ammo", + Category = "TFA Ammunition", + Spawnable = true, + AdminSpawnable = true, + Class = "", + MyModel = "models/Items/sniper_round_box.mdl", + AmmoCount = 50, + AmmoType = "AirboatGun", + DrawText = true, + TextColor = Color(185, 25, 25, 255), + TextPosition = Vector(1, -1.45, 1.5), + TextAngles = Vector(90, 0, 0), + ShouldDrawShadow = true, + ImpactSound = "Default.ImpactSoft", + Damage = 30, + Text = ".308", + } +} + +for ammoclass, ENT in pairs(ammo) do + scripted_ents.Register(ENT, "tfa_ammo_" .. ammoclass) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_snd_timescale.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_snd_timescale.lua new file mode 100644 index 0000000..1e4483a --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_snd_timescale.lua @@ -0,0 +1,50 @@ +local sv_cheats_cv = GetConVar("sv_cheats") +local host_timescale_cv = GetConVar("host_timescale") +local ts + +local en_cvar = GetConVar("sv_tfa_soundscale") + +hook.Add("EntityEmitSound", "zzz_TFA_EntityEmitSound", function(soundData) + local ent = soundData.Entity + local modified + local weapon + + if ent:IsWeapon() then + weapon = ent + elseif ent:IsNPC() or ent:IsPlayer() then + weapon = ent:GetActiveWeapon() + end + + if IsValid(weapon) and weapon.IsTFA and weapon.IsTFAWeapon then + if weapon.GonnaAdjuctPitch then + soundData.Pitch = soundData.Pitch * weapon.RequiredPitch + weapon.GonnaAdjuctPitch = false + modified = true + end + + if weapon.GonnaAdjustVol then + soundData.Volume = soundData.Volume * weapon.RequiredVolume + weapon.GonnaAdjustVol = false + modified = true + end + end + + if not en_cvar then return modified end + if not en_cvar:GetBool() then return modified end + ts = game.GetTimeScale() + + if sv_cheats_cv:GetBool() then + ts = ts * host_timescale_cv:GetFloat() + end + + if engine.GetDemoPlaybackTimeScale then + ts = ts * engine.GetDemoPlaybackTimeScale() + end + + if ts ~= 1 then + soundData.Pitch = math.Clamp(soundData.Pitch * ts, 0, 255) + return true + end + + return modified +end) diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_soundscripts.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_soundscripts.lua new file mode 100644 index 0000000..16eb9e5 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_soundscripts.lua @@ -0,0 +1,147 @@ +sound.Add({ + name = "Weapon_Bow.1", + channel = CHAN_STATIC, + volume = 1.0, + sound = {"weapons/tfbow/fire1.wav", "weapons/tfbow/fire2.wav", "weapons/tfbow/fire3.wav"} +}) + +sound.Add({ + name = "Weapon_Bow.boltpull", + channel = CHAN_USER_BASE + 11, + volume = 1.0, + sound = {"weapons/tfbow/pull1.wav", "weapons/tfbow/pull2.wav", "weapons/tfbow/pull3.wav"} +}) + +sound.Add({ + name = "TFA.NearlyEmpty", + channel = CHAN_USER_BASE + 15, + volume = 1, + pitch = 100, + level = 65, + sound = "weapons/tfa/lowammo.wav" +}) + +sound.Add({ + name = "TFA.Bash", + channel = CHAN_USER_BASE + 14, + volume = 1.0, + sound = { + ")weapons/tfa/melee1.wav", + ")weapons/tfa/melee2.wav", + ")weapons/tfa/melee3.wav", + ")weapons/tfa/melee4.wav", + ")weapons/tfa/melee5.wav", + ")weapons/tfa/melee6.wav" + }, + pitch = {97, 103} +}) + +sound.Add({ + name = "TFA.BashWall", + channel = CHAN_USER_BASE + 14, + volume = 1.0, + sound = { + ")weapons/tfa/melee_hit_world1.wav", + ")weapons/tfa/melee_hit_world2.wav", + ")weapons/tfa/melee_hit_world3.wav" + }, + pitch = {97, 103} +}) + +sound.Add({ + name = "TFA.BashFlesh", + channel = CHAN_USER_BASE + 14, + volume = 1.0, + sound = { + ")weapons/tfa/melee_hit_body1.wav", + ")weapons/tfa/melee_hit_body2.wav", + ")weapons/tfa/melee_hit_body3.wav", + ")weapons/tfa/melee_hit_body4.wav", + ")weapons/tfa/melee_hit_body5.wav", + ")weapons/tfa/melee_hit_body6.wav" + }, + pitch = {97, 103} +}) + +sound.Add({ + name = "TFA.IronIn", + channel = CHAN_USER_BASE + 13, + volume = 1.0, + sound = {"weapons/tfa/ironin.wav"}, + pitch = {97, 103} +}) + +sound.Add({ + name = "TFA.IronOut", + channel = CHAN_USER_BASE + 13, + volume = 1.0, + sound = {"weapons/tfa/ironout.wav"}, + pitch = {97, 103} +}) + +sound.Add({ + name = "Weapon_Pistol.Empty2", + channel = CHAN_USER_BASE + 11, + volume = 1.0, + level = 80, + sound = {"weapons/pistol/pistol_empty.wav"}, + pitch = {97, 103} +}) + +sound.Add({ + name = "Weapon_AR2.Empty2", + channel = CHAN_USER_BASE + 11, + volume = 1.0, + level = 80, + sound = {"weapons/ar2/ar2_empty.wav"}, + pitch = {97, 103} +}) + +sound.Add({ + name = "TFA.LowAmmo", + channel = CHAN_USER_BASE + 15, + volume = 1.0, + level = 75, + pitch = 100, + sound = ")weapons/tfa/lowammo_indicator_automatic.wav" +}) +sound.Add({ + name = "TFA.LowAmmo_Dry", + channel = CHAN_USER_BASE + 15, + volume = 1.0, + level = 75, + pitch = 100, + sound = ")weapons/tfa/lowammo_dry_automatic.wav" +}) + +local ammos = { + ["Handgun"] = "handgun", + ["Shotgun"] = "shotgun", + ["AutoShotgun"] = "shotgun_auto", + ["MachineGun"] = "mg", + ["AssaultRifle"] = "ar", + ["DMR"] = "dmr", + ["Revolver"] = "revolver", + ["Sniper"] = "sr", + ["SMG"] = "smg", + ["SciFi"] = "scifi", + ["GL"] = "gl", +} +for k,v in pairs(ammos) do + sound.Add({ + name = "TFA.LowAmmo." .. k, -- "TFA.LowAmmo.Handgun" + channel = CHAN_USER_BASE + 15, + volume = 1.0, + level = 75, + pitch = 100, + sound = ")weapons/tfa/lowammo_indicator_" .. v .. ".wav" + }) + sound.Add({ + name = "TFA.LowAmmo." .. k .. "_Dry", -- "TFA.LowAmmo.Handgun_Dry" + channel = CHAN_USER_BASE + 15, + volume = 1.0, + level = 75, + pitch = 100, + sound = ")weapons/tfa/lowammo_dry_" .. v .. ".wav" + }) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_tttpatch.lua b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_tttpatch.lua new file mode 100644 index 0000000..ce79d4d --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/modules/tfa_tttpatch.lua @@ -0,0 +1,149 @@ +if engine.ActiveGamemode() ~= "terrortown" then return end + +local cv_enabled = CreateConVar("sv_tfa_ttt_patch_weapons", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_NOTIFY}, "Patch TFA Base weapons to automatically spawn") + +-- luacheck: globals WEAPON_NONE WEAPON_MELEE WEAPON_PISTOL WEAPON_HEAVY WEAPON_NADE WEAPON_CARRY WEAPON_EQUIP1 WEAPON_EQUIP2 WEAPON_ROLE WEAPON_EQUIP WEAPON_UNARMED ROLE_INNOCENT ROLE_TRAITOR ROLE_DETECTIVE ROLE_NONE +WEAPON_NONE = WEAPON_NONE or 0 +WEAPON_MELEE = WEAPON_MELEE or 1 +WEAPON_PISTOL = WEAPON_PISTOL or 2 +WEAPON_HEAVY = WEAPON_HEAVY or 3 +WEAPON_NADE = WEAPON_NADE or 4 +WEAPON_CARRY = WEAPON_CARRY or 5 +WEAPON_EQUIP1 = WEAPON_EQUIP1 or 6 +WEAPON_EQUIP2 = WEAPON_EQUIP2 or 7 +WEAPON_ROLE = WEAPON_ROLE or 8 +WEAPON_EQUIP = WEAPON_EQUIP or WEAPON_EQUIP1 +WEAPON_UNARMED = WEAPON_UNARMED or -1 +ROLE_INNOCENT = ROLE_INNOCENT or 0 +ROLE_TRAITOR = ROLE_TRAITOR or 1 +ROLE_DETECTIVE = ROLE_DETECTIVE or 2 +ROLE_NONE = ROLE_NONE or ROLE_INNOCENT + +local KindTable = { + [0] = WEAPON_MELEE, + [1] = WEAPON_PISTOL, + [2] = WEAPON_HEAVY, + [3] = WEAPON_HEAVY, + [4] = WEAPON_HEAVY, + [5] = WEAPON_EQUIP1, + [6] = WEAPON_EQUIP2 +} + +local TypeStrings = { + [WEAPON_NONE] = "Invalid", + [WEAPON_MELEE] = "Melee", + [WEAPON_PISTOL] = "Pistol", + [WEAPON_HEAVY] = "Heavy", + [WEAPON_NADE] = "Grenade", + [WEAPON_CARRY] = "Carry", + [WEAPON_EQUIP1] = "Equipment", + [WEAPON_EQUIP2] = "Equipment", + [WEAPON_ROLE] = "Role" +} + +local function PatchWep(wep) + if not weapons.IsBasedOn(wep, "tfa_gun_base") then return end + if wep:find("base") then return end + + local tbl = weapons.GetStored(wep) + if not tbl then return end + + tbl.AllowSprintAttack = true -- no sprinting ever, running convar is a dumb idea + + if (not tbl.Kind) or (not isnumber(tbl.Kind)) then + tbl.Kind = KindTable[tbl.Slot or 2] or WEAPON_HEAVY + + if (tbl.ProjectileVelocity and tbl.ProjectileVelocity < 1000 and tbl.ProjectileVelocity > 0) or string.find(tbl.Base or "", "nade") then + tbl.Kind = WEAPON_NADE + end + + if tbl.IsMelee then + tbl.Kind = WEAPON_MELEE + end + + if not tbl.Spawnable then + tbl.Kind = WEAPON_NONE + end + end + + --if not tbl.Icon then + -- tbl.Icon = nil--"vgui/entities/" .. wep + --end + tbl.model = tbl.model or tbl.WorldModel + + if not tbl.CanBuy then + --if tbl.Spawnable then + -- tbl.CanBuy = { ROLE_TRAITOR, ROLE_DETECTIVE } + --else + tbl.CanBuy = {} + --end + end + + for _, v in pairs(tbl.CanBuy) do + if v ~= ROLE_TRAITOR and v ~= ROLE_DETECTIVE then + table.RemoveByValue(tbl.CanBuy, v) + end + end + + if (not tbl.Icon) or (string.len(tbl.Icon) <= 0) then + tbl.Icon = nil + if file.Exists("materials/entities/" .. wep .. ".png", "GAME") then + tbl.Icon = "entities/" .. wep .. ".png" + elseif file.Exists("materials/vgui/entities/" .. wep .. ".vmt", "GAME") then + tbl.Icon = "vgui/entities/" .. wep + end + end + + if tbl.LimitedStock == nil then + tbl.LimitedStock = false + end + + if not tbl.EquipMenuData then + tbl.EquipMenuData = { + ["type"] = TypeStrings[tbl.Kind], + ["desc"] = tbl.PrintName or wep + } + end + + if tbl.IsSilent == nil then + tbl.IsSilent = false + end + + if tbl.NoSights == nil then + if tbl.data then + tbl.NoSights = tbl.Secondary.IronSightsEnabled == false or tbl.data and tbl.data.ironsights ~= 0 or false + end + + if tbl.NoSights == nil then + tbl.NoSights = false + end + end + + if tbl.AutoSpawnable == nil then + tbl.AutoSpawnable = tbl.Spawnable + end +end + +local function Patch() + if not cv_enabled:GetBool() then return end + + for _, v in pairs(weapons.GetList()) do + local wep = v.ClassName + + if wep then + PatchWep(wep) + end + end +end + +if SERVER then + hook.Add("Initialize", "TFAPatchTTT", Patch) +end +if CLIENT then + hook.Add("HUDPaint", "TFAPatchTTT", function() + if LocalPlayer():IsValid() then + Patch() + hook.Remove("HUDPaint","TFAPatchTTT") + end + end) +end diff --git a/garrysmod/addons/tfa_base/lua/tfa/muzzleflash_base.lua b/garrysmod/addons/tfa_base/lua/tfa/muzzleflash_base.lua new file mode 100644 index 0000000..8db6b8e --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/tfa/muzzleflash_base.lua @@ -0,0 +1,338 @@ +local vector_origin = Vector() + +EFFECT.Life = 0.1 +EFFECT.XFlashSize = 1 +EFFECT.FlashSize = 1 +EFFECT.SmokeSize = 1 +EFFECT.SparkSize = 1 +EFFECT.HeatSize = 1 +EFFECT.Color = Color(255, 192, 64) +EFFECT.ColorSprites = false +EFFECT.UseDynamicLight = true + +local AddVel = Vector() +local ang + +function EFFECT:Init(data) + self.WeaponEnt = data:GetEntity() + if not IsValid(self.WeaponEnt) then return end + self.Attachment = data:GetAttachment() + self.Position = self:GetTracerShootPos(data:GetOrigin(), self.WeaponEnt, self.Attachment) + + if IsValid(self.WeaponEnt:GetOwner()) then + if self.WeaponEnt:GetOwner() == LocalPlayer() then + if self.WeaponEnt:GetOwner():ShouldDrawLocalPlayer() then + ang = self.WeaponEnt:GetOwner():EyeAngles() + ang:Normalize() + --ang.p = math.max(math.min(ang.p,55),-55) + self.Forward = ang:Forward() + else + self.WeaponEnt = self.WeaponEnt.OwnerViewModel + end + --ang.p = math.max(math.min(ang.p,55),-55) + else + ang = self.WeaponEnt:GetOwner():EyeAngles() + ang:Normalize() + self.Forward = ang:Forward() + end + end + + self.Forward = self.Forward or data:GetNormal() + self.Angle = self.Forward:Angle() + self.Right = self.Angle:Right() + self.vOffset = self.Position + local dir = self.Forward + local ownerent = self.WeaponEnt:GetOwner() + + if not IsValid(ownerent) then + ownerent = LocalPlayer() + end + + AddVel = ownerent:GetVelocity() + self.vOffset = self.Position + AddVel = AddVel * 0.05 + local dot = dir:GetNormalized():Dot(GetViewEntity():EyeAngles():Forward()) + local halofac = math.abs(dot) + local epos = ownerent:EyePos() + + local dlight + if self.UseDynamicLight then + dlight = DynamicLight(ownerent:EntIndex()) + + if (dlight) then + dlight.pos = epos + ownerent:EyeAngles():Forward() * self.vOffset:Distance(epos) --self.vOffset - ownerent:EyeAngles():Right() * 5 + 1.05 * ownerent:GetVelocity() * FrameTime() + dlight.r = self.Color.r + dlight.g = self.Color.g + dlight.b = self.Color.b + dlight.brightness = 4.5 + dlight.decay = 200 / self.Life + dlight.size = self.FlashSize * 96 + dlight.dietime = CurTime() + self.Life + end + end + + self.Dist = self.vOffset:Distance(epos) + self.DLight = dlight + self.DieTime = CurTime() + self.Life + self.OwnerEnt = ownerent + local emitter = ParticleEmitter(self.vOffset) + local sval = 1 - math.random(0, 1) * 2 + + if self.WeaponEnt.XTick == nil then + self.WeaponEnt.XTick = 0 + end + + self.WeaponEnt.XTick = 1 - self.WeaponEnt.XTick + + if self.WeaponEnt.XTick == 1 and self.XFlashSize > 0 then + local particle = emitter:Add(self.ColorSprites and "effects/muzzleflashx_nemole_w" or "effects/muzzleflashx_nemole", self.vOffset + FrameTime() * AddVel) + + if (particle) then + particle:SetVelocity(dir * 4 * self.XFlashSize) + particle:SetLifeTime(0) + particle:SetDieTime(self.Life / 2) + particle:SetStartAlpha(math.Rand(200, 255)) + particle:SetEndAlpha(0) + --particle:SetStartSize( 8 * (halofac*0.8+0.2), 0, 1) + --particle:SetEndSize( 0 ) + particle:SetStartSize(3 * (halofac * 0.8 + 0.2) * self.XFlashSize) + particle:SetEndSize(15 * (halofac * 0.8 + 0.2) * self.XFlashSize) + local r = math.Rand(-10, 10) * 3.14 / 180 + particle:SetRoll(r) + particle:SetRollDelta(r / 5) + + if self.ColorSprites then + particle:SetColor(self.Color.r, self.Color.g, self.Color.b) + else + particle:SetColor(255, 255, 255) + end + + particle:SetLighting(false) + particle.FollowEnt = self.WeaponEnt + particle.Att = self.Attachment + TFA.Particles.RegisterParticleThink(particle, TFA.Particles.FollowMuzzle) + particle:SetPos(vector_origin) + end + --particle:SetStartSize( 8 * (halofac*0.8+0.2), 0, 1) + --particle:SetEndSize( 0 ) + elseif self.XFlashSize > 0 then + local particle = emitter:Add(self.ColorSprites and "effects/muzzleflashx_nemole_w" or "effects/muzzleflashx_nemole", self.vOffset + FrameTime() * AddVel) + + if (particle) then + particle:SetVelocity(dir * 4 * self.FlashSize) + particle:SetLifeTime(0) + particle:SetDieTime(self.Life / 2) + particle:SetStartAlpha(math.Rand(200, 255)) + particle:SetEndAlpha(0) + particle:SetStartSize(2 * (halofac * 0.8 + 0.2) * 0.3 * self.FlashSize) + particle:SetEndSize(6 * (halofac * 0.8 + 0.2) * 0.3 * self.FlashSize) + local r = math.Rand(-10, 10) * 3.14 / 180 + particle:SetRoll(r) + particle:SetRollDelta(r / 5) + + if self.ColorSprites then + particle:SetColor(self.Color.r, self.Color.g, self.Color.b) + else + particle:SetColor(255, 255, 255) + end + + particle:SetLighting(false) + particle.FollowEnt = self.WeaponEnt + particle.Att = self.Attachment + TFA.Particles.RegisterParticleThink(particle, TFA.Particles.FollowMuzzle) + particle:SetPos(vector_origin) + end + end + + local flashCount = math.Round(self.FlashSize * 8) + + for i = 1, flashCount do + local particle = emitter:Add(self.ColorSprites and "effects/scotchmuzzleflashw" or "effects/scotchmuzzleflash4", self.vOffset + FrameTime() * AddVel) + + if (particle) then + particle:SetVelocity(dir * 300 * (0.2 + (i / flashCount) * 0.8) * self.FlashSize) + particle:SetLifeTime(0) + particle:SetDieTime(self.Life * 0.75) + particle:SetStartAlpha(math.Rand(128, 255)) + particle:SetEndAlpha(0) + --particle:SetStartSize( 7.5 * (halofac*0.8+0.2), 0, 1) + --particle:SetEndSize( 0 ) + local szsc = 1 + (flashCount - i) * math.pow(1 / flashCount * 0.9,0.8) + particle:SetStartSize(1.25 * math.Rand(1, 1.5) * szsc * self.FlashSize) + particle:SetEndSize(6 * math.Rand(0.75, 1) * szsc * self.FlashSize) + particle:SetRoll(math.rad(math.Rand(0, 360))) + particle:SetRollDelta(math.rad(math.Rand(15, 30)) * sval) + + if self.ColorSprites then + particle:SetColor(self.Color.r, self.Color.g, self.Color.b) + else + particle:SetColor(255, 255, 255) + end + + particle:SetLighting(false) + particle.FollowEnt = self.WeaponEnt + particle.Att = self.Attachment + TFA.Particles.RegisterParticleThink(particle, TFA.Particles.FollowMuzzle) + end + end + + for _ = 1, flashCount do + local particle = emitter:Add("effects/scotchmuzzleflash1", self.vOffset + FrameTime() * AddVel) + + if (particle) then + particle:SetVelocity(dir * 6 * self.FlashSize + 1.05 * AddVel) + particle:SetLifeTime(0) + particle:SetDieTime(self.Life * 1) + particle:SetStartAlpha(math.Rand(40, 140)) + particle:SetEndAlpha(0) + --particle:SetStartSize( 7.5 * (halofac*0.8+0.2), 0, 1) + --particle:SetEndSize( 0 ) + particle:SetStartSize(2 * math.Rand(1, 1.5) * self.FlashSize) + particle:SetEndSize(20 * math.Rand(0.5, 1) * self.FlashSize) + particle:SetRoll(math.rad(math.Rand(0, 360))) + particle:SetRollDelta(math.rad(math.Rand(30, 60)) * sval) + + if self.ColorSprites then + particle:SetColor(self.Color.r, self.Color.g, self.Color.b) + else + particle:SetColor(255, 255, 255) + end + + particle:SetLighting(false) + particle.FollowEnt = self.WeaponEnt + particle.Att = self.Attachment + --TFA.Particles.RegisterParticleThink(particle, TFA.Particles.FollowMuzzle) + end + end + + local glowCount = math.ceil(self.FlashSize * 3) + + for i = 1, glowCount do + local particle = emitter:Add("effects/scotchmuzzleflash1", self.vOffset + dir * 0.9 * i) + + if (particle) then + --particle:SetVelocity(dir * 32 ) + particle:SetLifeTime(0) + particle:SetDieTime(self.Life * 0.75) + particle:SetStartAlpha(255 * (1 - halofac)) + particle:SetEndAlpha(0) + --particle:SetStartSize( 7.5 * (halofac*0.8+0.2), 0, 1) + --particle:SetEndSize( 0 ) + particle:SetStartSize(math.max(12 - 12 / glowCount * i * 0.5, 1) * 0.2 * self.FlashSize) + particle:SetEndSize(math.max(12 - 12 / glowCount * i * 0.5, 1) * 0.6 * self.FlashSize) + particle:SetRoll(math.rad(math.Rand(0, 360))) + particle:SetRollDelta(math.rad(math.Rand(15, 30)) * sval) + + if self.ColorSprites then + particle:SetColor(self.Color.r, self.Color.g, self.Color.b) + else + particle:SetColor(255, 255, 255) + end + + particle:SetLighting(false) + particle.FollowEnt = self.WeaponEnt + particle.Att = self.Attachment + TFA.Particles.RegisterParticleThink(particle, TFA.Particles.FollowMuzzle) + end + end + + if TFA.GetMZFSmokeEnabled() then + local smokeCount = math.ceil(self.SmokeSize * 6) + + for _ = 0, smokeCount do + local particle = emitter:Add("particles/smokey", self.vOffset + dir * math.Rand(3, 14)) + + if (particle) then + particle:SetVelocity(VectorRand() * 10 * self.SmokeSize + dir * math.Rand(35, 50) * self.SmokeSize + 1.05 * AddVel) + particle:SetDieTime(math.Rand(0.6, 1) * self.Life * 6) + particle:SetStartAlpha(math.Rand(12, 24)) + particle:SetEndAlpha(0) + particle:SetStartSize(math.Rand(5, 7) * self.SmokeSize) + particle:SetEndSize(math.Rand(15, 20) * self.SmokeSize) + particle:SetRoll(math.rad(math.Rand(0, 360))) + particle:SetRollDelta(math.Rand(-0.8, 0.8)) + particle:SetLighting(true) + particle:SetAirResistance(20) + particle:SetGravity(Vector(0, 0, 60)) + particle:SetColor(255, 255, 255) + end + end + end + + local sparkcount = math.Round(math.random(8, 12) * self.SparkSize) + + for _ = 0, sparkcount do + local particle = emitter:Add("effects/yellowflare", self.Position) + + if (particle) then + particle:SetVelocity( VectorRand() * 30 * self.SparkSize) + particle:SetVelocity(particle:GetVelocity() + 1.15 * AddVel ) + particle:SetVelocity( particle:GetVelocity() + dir * math.Rand(80, 100) * (1-math.abs(math.max(particle:GetVelocity():GetNormalized():Dot(-dir),0))) * self.SparkSize ) + particle:SetLifeTime(0) + particle:SetDieTime(self.Life * math.Rand(0.9,1.1)) + particle:SetStartAlpha(255) + particle:SetEndAlpha(0) + particle:SetStartSize(0.6) + particle:SetEndSize(1) + particle:SetRoll(math.rad(math.Rand(0, 360))) + particle:SetGravity(vector_origin) + particle:SetAirResistance(1) + particle:SetStartLength(0.1) + particle:SetEndLength(0.05) + + if self.ColorSprites then + particle:SetColor(self.Color.r, self.Color.g, self.Color.b) + else + particle:SetColor(255, math.random(192, 225), math.random(140, 192)) + end + + particle:SetVelocityScale(true) + local sl = self.SparkSize + + particle:SetThinkFunction(function(pa) + math.randomseed(SysTime()) + local spd = pa:GetVelocity():Length()*12 + pa.ranvel = pa.ranvel or VectorRand() * spd + pa.ranvel:Add(VectorRand() * spd * math.sqrt(FrameTime())) + pa:SetVelocity(pa:GetVelocity() + pa.ranvel * sl * FrameTime() ) + pa:SetNextThink(CurTime()) + end) + + particle:SetNextThink(CurTime() + 0.01) + end + end + + if TFA.GetGasEnabled() then + local particle = emitter:Add("sprites/heatwave", self.vOffset + dir*2) + + if (particle) then + particle:SetVelocity(dir * 25 * self.HeatSize + 1.05 * AddVel) + particle:SetLifeTime(0) + particle:SetDieTime(self.Life) + particle:SetStartAlpha(math.Rand(200, 225)) + particle:SetEndAlpha(0) + particle:SetStartSize(math.Rand(3, 5) * self.HeatSize) + particle:SetEndSize(math.Rand(8, 12) * self.HeatSize) + particle:SetRoll(math.Rand(0, 360)) + particle:SetRollDelta(math.Rand(-2, 2)) + particle:SetAirResistance(5) + particle:SetGravity(Vector(0, 0, 40)) + particle:SetColor(255, 255, 255) + end + end + + emitter:Finish() +end + +function EFFECT:Think() + if CurTime() > (self.DieTime or 0) then + return false + elseif self.DLight and IsValid(self.OwnerEnt) then + self.DLight.pos = self.OwnerEnt:EyePos() + self.OwnerEnt:EyeAngles():Forward() * self.Dist + end + + return true +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_bash_base.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_bash_base.lua new file mode 100644 index 0000000..9a745aa --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_bash_base.lua @@ -0,0 +1,234 @@ +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("tfa_gun_base") +SWEP.Secondary.BashDamage = 25 +SWEP.Secondary.BashSound = Sound("TFA.Bash") +SWEP.Secondary.BashHitSound = Sound("TFA.BashWall") +SWEP.Secondary.BashHitSound_Flesh = Sound("TFA.BashFlesh") +SWEP.Secondary.BashLength = 54 +SWEP.Secondary.BashDelay = 0.2 +SWEP.Secondary.BashDamageType = DMG_SLASH +SWEP.Secondary.BashEnd = nil --Override bash sequence length easier +SWEP.Secondary.BashInterrupt = false --Do you need to be in a "ready" status to bash? +SWEP.BashBase = true + +function SWEP:BashForce(ent, force, pos, now) + if not IsValid(ent) or not ent.GetPhysicsObjectNum then return end + + if now then + if ent.GetRagdollEntity then + ent = ent:GetRagdollEntity() or ent + end + + local phys = ent:GetPhysicsObjectNum(0) + + if IsValid(phys) then + if ent:IsPlayer() or ent:IsNPC() then + ent:SetVelocity( force * 0.1) + phys:SetVelocity(phys:GetVelocity() + force * 0.1) + else + phys:ApplyForceOffset(force, pos) + end + end + else + timer.Simple(0, function() + if IsValid(self) and self:OwnerIsValid() and IsValid(ent) then + self:BashForce(ent, force, pos, true) + end + end) + end +end + +local cv_doordestruction = GetConVar("sv_tfa_melee_doordestruction") + +function SWEP:HandleDoor(slashtrace) + if CLIENT or not IsValid(slashtrace.Entity) then return end + + if not cv_doordestruction:GetBool() then return end + + if slashtrace.Entity:GetClass() == "func_door_rotating" or slashtrace.Entity:GetClass() == "prop_door_rotating" then + slashtrace.Entity:EmitSound("ambient/materials/door_hit1.wav", 100, math.random(80, 120)) + + local newname = "TFABash" .. self:EntIndex() + self.PreBashName = self:GetName() + self:SetName(newname) + + slashtrace.Entity:SetKeyValue("Speed", "500") + slashtrace.Entity:SetKeyValue("Open Direction", "Both directions") + slashtrace.Entity:SetKeyValue("opendir", "0") + slashtrace.Entity:Fire("unlock", "", .01) + slashtrace.Entity:Fire("openawayfrom", newname, .01) + + timer.Simple(0.02, function() + if not IsValid(self) or self:GetName() ~= newname then return end + + self:SetName(self.PreBashName) + end) + + timer.Simple(0.3, function() + if IsValid(slashtrace.Entity) then + slashtrace.Entity:SetKeyValue("Speed", "100") + end + end) + end +end + +local l_CT = CurTime +local sp = game.SinglePlayer() + +function SWEP:AltAttack() + local time = l_CT() + + if + self:GetStatL("Secondary.CanBash") == false or + not self:OwnerIsValid() or + time < self:GetNextSecondaryFire() + then return end + + local stat = self:GetStatus() + if not TFA.Enum.ReadyStatus[stat] and not self:GetStatL("Secondary.BashInterrupt") or + stat == TFA.Enum.STATUS_BASHING and self:GetStatL("Secondary.BashInterrupt") then return end + + if self:IsSafety() or self:GetHolding() then return end + + local retVal = hook.Run("TFA_CanBash", self) + if retVal == false then return end + + local enabled, tanim, ttype = self:ChooseBashAnim() + if not enabled then return end + + hook.Run("TFA_Bash", self) + + if self:GetOwner().Vox and IsFirstTimePredicted() then + self:GetOwner():Vox("bash", 0) + end + + self:BashAnim() + if sp and SERVER then self:CallOnClient("BashAnim", "") end + + local bashend = self:GetStatL("Secondary.BashEnd") + local nextTime = time + (bashend or self:GetActivityLength(tanim, false, ttype)) + + self:SetNextPrimaryFire(nextTime) + self:SetNextSecondaryFire(nextTime) + + self:EmitSoundNet(self:GetStatL("Secondary.BashSound")) + + self:ScheduleStatus(TFA.Enum.STATUS_BASHING, self:GetStatL("Secondary.BashDelay")) + + hook.Run("TFA_PostBash", self) +end + +function SWEP:BashAnim() + if not IsFirstTimePredicted() then return end + local ht = self.DefaultHoldType or self.HoldType + local altanim = false + + if ht == "ar2" or ht == "shotgun" or ht == "crossbow" or ht == "physgun" then + altanim = true + end + + self:GetOwner():AnimRestartGesture(0, altanim and ACT_GMOD_GESTURE_MELEE_SHOVE_2HAND or ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE2, true) +end + +local ttime = -1 + +function SWEP:HandleBashAttack() + local ply = self:GetOwner() + local pos = ply:GetShootPos() + local av = ply:GetAimVector() + + local slash = {} + slash.start = pos + slash.endpos = pos + (av * self:GetStatL("Secondary.BashLength")) + slash.filter = ply + slash.mins = Vector(-10, -5, 0) + slash.maxs = Vector(10, 5, 5) + local slashtrace = util.TraceHull(slash) + + local pain = self:GetStatL("Secondary.BashDamage") + + if not slashtrace.Hit then return end + self:HandleDoor(slashtrace) + + if not (sp and CLIENT) then + self:EmitSound( + (slashtrace.MatType == MAT_FLESH or slashtrace.MatType == MAT_ALIENFLESH) and + self:GetStatL("Secondary.BashHitSound_Flesh") or + self:GetStatL("Secondary.BashHitSound")) + end + + if CLIENT then return end + + local dmg = DamageInfo() + dmg:SetAttacker(ply) + dmg:SetInflictor(self) + dmg:SetDamagePosition(pos) + dmg:SetDamageForce(av * pain) + dmg:SetDamage(pain) + dmg:SetDamageType(self:GetStatL("Secondary.BashDamageType")) + + if IsValid(slashtrace.Entity) and slashtrace.Entity.TakeDamageInfo then + slashtrace.Entity:TakeDamageInfo(dmg) + end + + local ent = slashtrace.Entity + if not IsValid(ent) or not ent.GetPhysicsObject then return end + + local phys + + if ent:IsRagdoll() then + phys = ent:GetPhysicsObjectNum(slashtrace.PhysicsBone or 0) + else + phys = ent:GetPhysicsObject() + end + + if IsValid(phys) then + if ent:IsPlayer() or ent:IsNPC() then + ent:SetVelocity(av * self:GetStatL("Secondary.BashDamage") * 0.5) + phys:SetVelocity(phys:GetVelocity() + av * self:GetStatL("Secondary.BashDamage") * 0.5) + else + phys:ApplyForceOffset(av * self:GetStatL("Secondary.BashDamage") * 0.5, slashtrace.HitPos) + end + end +end + +function SWEP:Think2(...) + if self:GetStatus() == TFA.Enum.STATUS_BASHING and self:GetStatusEnd() < l_CT() then + self:SetStatus(TFA.Enum.STATUS_BASHING_WAIT, self:GetNextSecondaryFire()) + + if IsFirstTimePredicted() then + self:HandleBashAttack() + end + end + + BaseClass.Think2(self, ...) +end + +function SWEP:SecondaryAttack() + if not self:GetStatL("Secondary.IronSightsEnabled", false) then + self:AltAttack() + return + end + + BaseClass.SecondaryAttack(self) +end + +function SWEP:GetBashing() + local stat = self:GetStatus() + + if not self:VMIV() then + return stat == TFA.Enum.STATUS_BASHING or stat == TFA.Enum.STATUS_BASHING_WAIT + end + + return (stat == TFA.Enum.STATUS_BASHING or stat == TFA.Enum.STATUS_BASHING_WAIT) and self.OwnerViewModel:GetCycle() > 0 and self.OwnerViewModel:GetCycle() < 0.65 +end + +function SWEP:GetBashingStrict() + local stat = self:GetStatus() + return stat == TFA.Enum.STATUS_BASHING or stat == TFA.Enum.STATUS_BASHING_WAIT +end + +TFA.FillMissingMetaValues(SWEP) diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_bow_base.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_bow_base.lua new file mode 100644 index 0000000..fb6fbee --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_bow_base.lua @@ -0,0 +1,331 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.IsBow = true + +DEFINE_BASECLASS("tfa_gun_base") +--primary stats +SWEP.Primary.Spread = 0.001 +SWEP.Primary.SpreadShake = 0.05 --when shaking +SWEP.Primary.Velocity = 64 --velocity in m/s +SWEP.Primary.Damage_Charge = {0.2, 1} --velocity/damage multiplier between min and max charge +SWEP.Primary.Shake = true --enable shaking +--options +SWEP.Secondary.Cancel = true --enable cancelling +--bow base shit +SWEP.ChargeRate = 30 / 75 --1 is fully charged +SWEP.ChargeThreshold = 0.75 --minimum charge percent to fire +SWEP.ShakeTime = 5 --minimum time to start shaking +SWEP.Secondary.IronSightsEnabled = false +--tfa ballistics integration +SWEP.UseBallistics = true +SWEP.BulletModel = "models/weapons/w_tfa_arrow.mdl" +SWEP.BulletTracer = "" + +--animation +SWEP.BowAnimations = { + ["shake"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "tiredloop", + ["enabled"] = true --Manually force a sequence to be enabled + }, + ["shoot"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "fire_1", + ["enabled"] = true --Manually force a sequence to be enabled + }, + ["cancel"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "cancelarrow", + ["enabled"] = true --Manually force a sequence to be enabled + }, + ["draw"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "drawarrow", + ["enabled"] = true --Manually force a sequence to be enabled + } +} + +--["idle_charged"] = {["type"] = TFA.Enum.ANIMATION_SEQ, ["value"] = "idle_charged", ["enabled"] = true } +function SWEP:SetupDataTables(...) + BaseClass.SetupDataTables(self, ...) + + self:NetworkVarTFA("Bool", "Shaking") + self:NetworkVarTFA("Float", "Charge") + self:NetworkVarTFA("Float", "ShakingSpreadIncrement") +end + +function SWEP:GetChargeTime() + return self:GetCharge() / self.ChargeRate +end + +function SWEP:ShouldShake() + return self:GetChargeTime() >= self.ShakeTime +end + +function SWEP:Deploy(...) + self:SetCharge(0) + self:SetShaking(false) + self:SetShakingSpreadIncrement(0) + + return BaseClass.Deploy(self, ...) +end + +function SWEP:Charge(t) + self:SetCharge(self:GetCharge() + self.ChargeRate * t) +end + +local sp = game.SinglePlayer() + +function SWEP:Think2(...) + local ft = FrameTime() + + if self:GetStatus() == TFA.Enum.STATUS_BOW_CANCEL and self:GetStatusEnd() > CurTime() then + self:SetCharge(0) + self:SetShaking(false) + end + + if TFA.Enum.ReadyStatus[self:GetStatus()] and self:CanPrimaryAttack() then + if self:GetOwner():KeyDown(IN_ATTACK2) and self:GetCharge() > self.ChargeThreshold then + self:PlayAnimation(self.BowAnimations.cancel) + self:ScheduleStatus(TFA.Enum.STATUS_BOW_CANCEL, self:GetActivityLength()) + elseif self:GetOwner():KeyDown(IN_ATTACK) then + if self:GetCharge() <= 0 then + self:PlayAnimation(self.BowAnimations.draw) + self:SetCharge(0.01) + self:SetShaking(false) + end + + self:Charge(ft) + + if self:ShouldShake() and not self:GetShaking() then + self:SetShaking(true) + self:PlayAnimation(self.BowAnimations.shake) + end + else + local c = self:GetCharge() + + if c > self.ChargeThreshold then + self:Shoot() + elseif c > 0 then + self:Charge(ft) + end + end + elseif self:GetCharge() > 0 then + if TFA.Enum.ReadyStatus[self:GetStatus()] then + if self:GetCharge() > self.ChargeThreshold then + self:PlayAnimation(self.BowAnimations.cancel) + self:ScheduleStatus(TFA.Enum.STATUS_BOW_CANCEL, self:GetActivityLength()) + else + self.Idle_ModeOld = self.Idle_Mode + self:ClearStatCache("Idle_Mode") + self.Idle_Mode = TFA.Enum.IDLE_BOTH + self:ChooseIdleAnim() + self.Idle_Mode = self.Idle_ModeOld + end + end + + self:SetCharge(0) + self:SetShaking(false) + end + + BaseClass.Think2(self, ...) +end + +function SWEP:CalculateRatios() + BaseClass.CalculateRatios(self) + + local targ = self:GetShaking() and 1 or 0 + local prog = self:GetShakingSpreadIncrement() + self:SetShakingSpreadIncrement(math.Approach(prog, targ, (targ - prog) * FrameTime() * 5)) +end + +function SWEP:GetBaseSpread() + return Lerp(self:GetShakingSpreadIncrement(), BaseClass.GetBaseSpread(self), self:GetStatL("Primary.SpreadShake")) +end + +function SWEP:Shoot() + self:PrePrimaryAttack() + + if hook.Run("TFA_PrimaryAttack", self) then return end + + if self:GetStatL("Primary.Sound") and IsFirstTimePredicted() and not ( sp and CLIENT ) then + if self:GetStatL("Primary.SilencedSound") and self:GetSilenced() then + self:EmitSound(self:GetStatL("Primary.SilencedSound") ) + else + self:EmitSound(self:GetStatL("Primary.Sound")) + end + end + self:TakePrimaryAmmo(self:GetStatL("Primary.AmmoConsumption")) + self:PlayAnimation(self.BowAnimations.shoot) + self:ShootBulletInformation() + self:SetCharge(0) + self:SetShaking(false) + self:ScheduleStatus(TFA.Enum.STATUS_BOW_SHOOT, 0.1) + + self:PostPrimaryAttack() + hook.Run("TFA_PostPrimaryAttack", self) +end + +function SWEP:ChooseIdleAnim(...) + if self:GetShaking() then + return self:PlayAnimation(self.BowAnimations.shake) + elseif self:GetCharge() > 0 and self.BowAnimations["idle_charged"] then + return self:PlayAnimation(self.BowAnimations.idle_charged) + end + + return BaseClass.ChooseIdleAnim(self, ...) +end + +function SWEP:PrimaryAttack() +end + +function SWEP:SecondaryAttack() +end + +SWEP.MainBullet = {} +SWEP.MainBullet.Spread = Vector() +local ballistics_distcv = GetConVar("sv_tfa_ballistics_mindist") + +local function BallisticFirebullet(ply, bul, ovr) + local wep = ply:GetActiveWeapon() + + if TFA.Ballistics and TFA.Ballistics:ShouldUse(wep) then + if ballistics_distcv:GetInt() == -1 or ply:GetEyeTrace().HitPos:Distance(ply:GetShootPos()) > (ballistics_distcv:GetFloat() * TFA.Ballistics.UnitScale) then + bul.SmokeParticle = bul.SmokeParticle or wep.BulletTracer or wep.TracerBallistic or wep.BallisticTracer or wep.BallisticsTracer + + if ovr then + TFA.Ballistics:FireBullets(wep, bul, angle_zero, true) + else + TFA.Ballistics:FireBullets(wep, bul) + end + else + ply:FireBullets(bul) + end + else + ply:FireBullets(bul) + end +end + +--[[ +Function Name: ShootBulletInformation +Syntax: self:ShootBulletInformation(). +Returns: Nothing. +Notes: Used to generate a self.MainBullet table which is then sent to self:ShootBullet, and also to call shooteffects. +Purpose: Bullet +]] +-- +local cv_dmg_mult = GetConVar("sv_tfa_damage_multiplier") +local cv_dmg_mult_min = GetConVar("sv_tfa_damage_mult_min") +local cv_dmg_mult_max = GetConVar("sv_tfa_damage_mult_max") +local dmg, con, rec + +function SWEP:ShootBulletInformation() + self:UpdateConDamage() + self.lastbul = nil + self.lastbulnoric = false + self.ConDamageMultiplier = cv_dmg_mult:GetFloat() + if not IsFirstTimePredicted() then return end + con, rec = self:CalculateConeRecoil() + local tmpranddamage = math.Rand(cv_dmg_mult_min:GetFloat(), cv_dmg_mult_max:GetFloat()) + local basedamage = self.ConDamageMultiplier * self:GetStatL("Primary.Damage") + dmg = basedamage * tmpranddamage + local ns = self:GetStatL("Primary.NumShots") + local clip = (self:GetStatL("Primary.ClipSize") == -1) and self:Ammo1() or self:Clip1() + ns = math.Round(ns, math.min(clip / self:GetStatL("Primary.NumShots"), 1)) + self:ShootBullet(dmg, rec, ns, con) +end + +--[[ +Function Name: ShootBullet +Syntax: self:ShootBullet(damage, recoil, number of bullets, spray cone, disable ricochet, override the generated self.MainBullet table with this value if you send it). +Returns: Nothing. +Notes: Used to shoot a self.MainBullet. +Purpose: Bullet +]] +-- +local cv_forcemult = GetConVar("sv_tfa_force_multiplier") + +local AttachArrowModel = function(a, b, c, wep) + c:SetDamageType(bit.bor(DMG_NEVERGIB, DMG_CLUB)) + if CLIENT then return end + if not IsValid(wep) then return end + + if b.HitWorld and not (IsValid(b.Entity) and not b.Entity:IsWorld()) then + local arrowstuck = ents.Create("tfbow_arrow_stuck") + arrowstuck:SetModel(wep:GetStatL("BulletModel")) + arrowstuck.gun = wep:GetClass() + arrowstuck:SetPos(b.HitPos) + arrowstuck:SetAngles(b.Normal:Angle()) + arrowstuck:Spawn() + else + local arrowstuck = ents.Create("tfbow_arrow_stuck_clientside") + arrowstuck:SetModel(wep:GetStatL("BulletModel")) + arrowstuck:SetModel(wep:GetStatL("BulletModel")) + arrowstuck.gun = wep:GetClass() + arrowstuck:SetPos(b.HitPos) + arrowstuck:SetAngles(b.Normal:Angle()) + arrowstuck.targent = b.Entity + arrowstuck.targphysbone = b.PhysicsBone or -1 + arrowstuck:Spawn() + end +end + +function SWEP:AutoDetectForce() + if self:GetStatRawL("Primary.Force") == -1 or not self:GetStatRawL("Primary.Force") then + self:SetStatRawL("Primary.Force", self:GetStatRawL("Force") or self:GetStatRawL("Primary.Damage") / 6 * math.sqrt(self:GetStatRawL("Primary.KickUp") + self:GetStatRawL("Primary.KickDown") + self:GetStatRawL("Primary.KickHorizontal"))) + end +end + +function SWEP:ShootBullet(damage, recoil, num_bullets, aimcone, disablericochet, bulletoverride) + if not IsFirstTimePredicted() and not game.SinglePlayer() then return end + local chargeTable = self:GetStatL("Primary.Damage_Charge") + local mult = Lerp(math.Clamp(self:GetCharge() - self.ChargeThreshold, 0, 1 - self.ChargeThreshold) / (1 - self.ChargeThreshold), chargeTable[1], chargeTable[2]) + local unitScale = TFA.Ballistics.UnitScale or TFA.UnitScale or 40 + num_bullets = num_bullets or 1 + aimcone = aimcone or 0 + self.MainBullet.Attacker = self:GetOwner() + self.MainBullet.Inflictor = self + self.MainBullet.Num = num_bullets + self.MainBullet.Src = self:GetOwner():GetShootPos() + self.MainBullet.Dir = self:GetOwner():EyeAngles():Forward() + self.MainBullet.HullSize = 0 + self.MainBullet.Spread.x = aimcone + self.MainBullet.Spread.y = aimcone + + if self.TracerPCF then + self.MainBullet.Tracer = 0 + else + self.MainBullet.Tracer = self:GetStatL("TracerCount") or 3 + end + + self.MainBullet.PenetrationCount = 0 + self.MainBullet.AmmoType = self:GetPrimaryAmmoType() + self.MainBullet.Force = self:GetStatL("Primary.Force") * cv_forcemult:GetFloat() * self:GetAmmoForceMultiplier() * mult + self.MainBullet.Damage = damage * mult + self.MainBullet.HasAppliedRange = false + self.MainBullet.Velocity = self:GetStatL("Primary.Velocity") * mult * unitScale + + self.MainBullet.Callback = function(a, b, c) + if IsValid(self) then + c:SetInflictor(self) + end + + if self.MainBullet.Callback2 then + self.MainBullet.Callback2(a, b, c) + end + + self:CallAttFunc("CustomBulletCallback", a, b, c) + + if SERVER and IsValid(a) and a:IsPlayer() and IsValid(b.Entity) and (b.Entity:IsPlayer() or b.Entity:IsNPC() or type(b.Entity) == "NextBot") then + self:SendHitMarker(a, b, c) + end + + AttachArrowModel(a, b, c, self) + end + + BallisticFirebullet(self:GetOwner(), self.MainBullet) +end + +TFA.FillMissingMetaValues(SWEP) diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/cl_init.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/cl_init.lua new file mode 100644 index 0000000..14b9951 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/cl_init.lua @@ -0,0 +1,31 @@ +TFA.SWEP_LOAD_COMPLETE = false + +include("shared.lua") + +include("common/ai_translations.lua") +include("common/anims.lua") +include("common/autodetection.lua") +include("common/utils.lua") +include("common/stat.lua") +include("common/attachments.lua") +include("common/bullet.lua") +include("common/effects.lua") +include("common/calc.lua") +include("common/akimbo.lua") +include("common/events.lua") +include("common/nzombies.lua") +include("common/ttt.lua") +include("common/viewmodel.lua") +include("common/skins.lua") + +include("client/effects.lua") +include("client/viewbob.lua") +include("client/viewmodel.lua") +include("client/bobcode.lua") +include("client/hud.lua") +include("client/mods.lua") +include("client/laser.lua") +include("client/fov.lua") +include("client/flashlight.lua") + +TFA.FillMissingMetaValues(SWEP) diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/bobcode.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/bobcode.lua new file mode 100644 index 0000000..0c0705b --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/bobcode.lua @@ -0,0 +1,216 @@ +local vector_origin = Vector() + +SWEP.ti = 0 +SWEP.LastCalcBob = 0 +SWEP.tiView = 0 +SWEP.LastCalcViewBob = 0 +local TAU = math.pi * 2 +local rateScaleFac = 2 +local rate_up = 6 * rateScaleFac +local scale_up = 0.5 +local rate_right = 3 * rateScaleFac +local scale_right = -0.5 +local rate_forward_view = 3 * rateScaleFac +local scale_forward_view = 0.35 +local rate_right_view = 3 * rateScaleFac +local scale_right_view = -1 +local rate_p = 6 * rateScaleFac +local scale_p = 3 +local rate_y = 3 * rateScaleFac +local scale_y = 6 +local rate_r = 3 * rateScaleFac +local scale_r = -6 +local pist_rate = 3 * rateScaleFac +local pist_scale = 9 +local rate_clamp = 2 * rateScaleFac +local walkIntensitySmooth, breathIntensitySmooth = 0, 0 +local walkRate = 160 / 60 * TAU / 1.085 / 2 * rateScaleFac --steps are at 160bpm at default velocity, then divide that by 60 for per-second, multiply by TAU for trig, divided by default walk rate +local walkVec = Vector() +local ownerVelocity, ownerVelocityMod = Vector(), Vector() +local zVelocity, zVelocitySmooth = 0, 0 +local xVelocity, xVelocitySmooth, rightVec = 0, 0, Vector() +local flatVec = Vector(1, 1, 0) +local WalkPos = Vector() +local WalkPosLagged = Vector() +local gunbob_intensity_cvar = GetConVar("cl_tfa_gunbob_intensity") +local gunbob_intensity = 0 +SWEP.VMOffsetWalk = Vector(0.5, -0.5, -0.5) +SWEP.footstepTotal = 0 +SWEP.footstepTotalTarget = 0 +local upVec, riVec, fwVec = Vector(0, 0, 1), Vector(1, 0, 0), Vector(0, 1, 0) + +local function l_Lerp(t, a, b) + if t <= 0 then return a end + if t >= 1 then return b end + return a + (b - a) * t +end + +function SWEP:WalkBob(pos, ang, breathIntensity, walkIntensity, rate, ftv) + local self2 = self:GetTable() + if not self2.OwnerIsValid(self) then return end + rate = math.min(rate or 0.5, rate_clamp) + gunbob_intensity = gunbob_intensity_cvar:GetFloat() + + local ea = self:GetOwner():EyeAngles() + local up = ang:Up() + local ri = ang:Right() + local fw = ang:Forward() + local upLocal = upVec + local riLocal = riVec + local fwLocal = fwVec + local delta = ftv + local flip_v = self2.ViewModelFlip and -1 or 1 + --delta = delta * game.GetTimeScale() + --self2.LastCalcBob = SysTime() + self2.bobRateCached = rate + self2.ti = self2.ti + delta * rate + + if self2.SprintStyle == nil then + if self:GetStatL("SprintViewModelAngle") and self:GetStatL("SprintViewModelAngle").x > 5 then + self2.SprintStyle = 1 + else + self2.SprintStyle = 0 + end + end + + --preceding calcs + walkIntensitySmooth = l_Lerp(delta * 10 * rateScaleFac, walkIntensitySmooth, walkIntensity) + breathIntensitySmooth = l_Lerp(delta * 10 * rateScaleFac, breathIntensitySmooth, breathIntensity) + walkVec = LerpVector(walkIntensitySmooth, vector_origin, self2.VMOffsetWalk) + ownerVelocity = self:GetOwner():GetVelocity() + zVelocity = ownerVelocity.z + zVelocitySmooth = l_Lerp(delta * 7 * rateScaleFac, zVelocitySmooth, zVelocity) + ownerVelocityMod = ownerVelocity * flatVec + ownerVelocityMod:Normalize() + rightVec = ea:Right() * flatVec + rightVec:Normalize() + xVelocity = ownerVelocity:Length2D() * ownerVelocityMod:Dot(rightVec) + xVelocitySmooth = l_Lerp(delta * 5 * rateScaleFac, xVelocitySmooth, xVelocity) + + --multipliers + breathIntensity = breathIntensitySmooth * gunbob_intensity * 1.5 + walkIntensity = walkIntensitySmooth * gunbob_intensity * 1.5 + + --breathing / walking while ADS + local breatheMult2 = math.Clamp((self2.IronSightsProgressUnpredicted2 or self:GetIronSightsProgress()), 0, 1) + --local breatheMult2 = 0 + local breatheMult1 = 1 - breatheMult2 + --local breatheMult1 = 1 + + pos:Add(riLocal * (math.sin(self2.ti * walkRate) - math.cos(self2.ti * walkRate)) * flip_v * breathIntensity * 0.2 * breatheMult1) + pos:Add(upLocal * math.sin(self2.ti * walkRate) * breathIntensity * 0.5 * breatheMult1) + + pos:Add(riLocal * math.cos(self2.ti * walkRate / 2) * flip_v * breathIntensity * 0.6 * breatheMult2) + pos:Add(upLocal * math.sin(self2.ti * walkRate) * breathIntensity * 0.3 * breatheMult2) + + --walk anims, danny method because i just can't + self2.walkTI = (self2.walkTI or 0) + delta * 160 / 60 * self:GetOwner():GetVelocity():Length2D() / self:GetOwner():GetWalkSpeed() + WalkPos.x = l_Lerp(delta * 5 * rateScaleFac, WalkPos.x, -math.sin(self2.ti * walkRate * 0.5) * gunbob_intensity * walkIntensity) + WalkPos.y = l_Lerp(delta * 5 * rateScaleFac, WalkPos.y, math.sin(self2.ti * walkRate) / 1.5 * gunbob_intensity * walkIntensity) + WalkPosLagged.x = l_Lerp(delta * 5 * rateScaleFac, WalkPosLagged.x, -math.sin((self2.ti * walkRate * 0.5) + math.pi / 3) * gunbob_intensity * walkIntensity) + WalkPosLagged.y = l_Lerp(delta * 5 * rateScaleFac, WalkPosLagged.y, math.sin(self2.ti * walkRate + math.pi / 3) / 1.5 * gunbob_intensity * walkIntensity) + pos:Add(WalkPos.x * 0.33 * riLocal) + pos:Add(WalkPos.y * 0.25 * upLocal) + ang:RotateAroundAxis(ri, -WalkPosLagged.y) + ang:RotateAroundAxis(up, WalkPosLagged.x) + ang:RotateAroundAxis(fw, WalkPos.x) + + --constant offset + pos:Add(riLocal * walkVec.x * flip_v) + pos:Add(fwLocal * walkVec.y) + pos:Add(upLocal * walkVec.z) + + --jumping + local trigX = -math.Clamp(zVelocitySmooth / 200, -1, 1) * math.pi / 2 + local jumpIntensity = (3 + math.Clamp(math.abs(zVelocitySmooth) - 100, 0, 200) / 200 * 4) * (1 - (self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress()) * 0.8) + pos:Add(ri * math.sin(trigX) * scale_r * 0.1 * jumpIntensity * flip_v * 0.4) + pos:Add(-up * math.sin(trigX) * scale_r * 0.1 * jumpIntensity * 0.4) + ang:RotateAroundAxis(ang:Forward(), math.sin(trigX) * scale_r * jumpIntensity * flip_v * 0.4) + + --rolling with horizontal motion + local xVelocityClamped = xVelocitySmooth + + if math.abs(xVelocityClamped) > 200 then + local sign = (xVelocityClamped < 0) and -1 or 1 + xVelocityClamped = (math.sqrt((math.abs(xVelocityClamped) - 200) / 50) * 50 + 200) * sign + end + + ang:RotateAroundAxis(ang:Forward(), xVelocityClamped * 0.04 * flip_v) + + return pos, ang +end + +function SWEP:SprintBob(pos, ang, intensity, origPos, origAng) + local self2 = self:GetTable() + if not IsValid(self:GetOwner()) or not gunbob_intensity then return pos, ang end + local flip_v = self2.ViewModelFlip and -1 or 1 + + local eyeAngles = self:GetOwner():EyeAngles() + local localUp = ang:Up() + local localRight = ang:Right() + local localForward = ang:Forward() + + local playerUp = eyeAngles:Up() + local playerRight = eyeAngles:Right() + local playerForward = eyeAngles:Forward() + + intensity = intensity * gunbob_intensity * 1.5 + gunbob_intensity = gunbob_intensity_cvar:GetFloat() + + if intensity > 0.005 then + if self2.SprintStyle == 1 then + local intensity3 = math.max(intensity - 0.3, 0) / (1 - 0.3) + ang:RotateAroundAxis(ang:Up(), math.sin(self2.ti * pist_rate) * pist_scale * intensity3 * 0.33 * 0.75) + ang:RotateAroundAxis(ang:Forward(), math.sin(self2.ti * pist_rate) * pist_scale * intensity3 * 0.33 * -0.25) + pos:Add(ang:Forward() * math.sin(self2.ti * pist_rate * 2 + math.pi) * pist_scale * -0.1 * intensity3 * 0.4) + pos:Add(ang:Right() * math.sin(self2.ti * pist_rate) * pist_scale * 0.15 * intensity3 * 0.33 * 0.2) + else + pos:Add(localUp * math.sin(self2.ti * rate_up + math.pi) * scale_up * intensity * 0.33) + pos:Add(localRight * math.sin(self2.ti * rate_right) * scale_right * intensity * flip_v * 0.33) + pos:Add(eyeAngles:Forward() * math.max(math.sin(self2.ti * rate_forward_view), 0) * scale_forward_view * intensity * 0.33) + pos:Add(eyeAngles:Right() * math.sin(self2.ti * rate_right_view) * scale_right_view * intensity * flip_v * 0.33) + + ang:RotateAroundAxis(localRight, math.sin(self2.ti * rate_p + math.pi) * scale_p * intensity * 0.33) + pos:Add(-localUp * math.sin(self2.ti * rate_p + math.pi) * scale_p * 0.1 * intensity * 0.33) + + ang:RotateAroundAxis(localUp, math.sin(self2.ti * rate_y) * scale_y * intensity * flip_v * 0.33) + pos:Add(localRight * math.sin(self2.ti * rate_y) * scale_y * 0.1 * intensity * flip_v * 0.33) + + ang:RotateAroundAxis(localForward, math.sin(self2.ti * rate_r) * scale_r * intensity * flip_v * 0.33) + pos:Add(localRight * math.sin(self2.ti * rate_r) * scale_r * 0.05 * intensity * flip_v * 0.33) + pos:Add(localUp * math.sin(self2.ti * rate_r) * scale_r * 0.1 * intensity * 0.33) + end + end + + return pos, ang +end + +local cv_customgunbob = GetConVar("cl_tfa_gunbob_custom") +local fac, bscale + +function SWEP:UpdateEngineBob() + local self2 = self:GetTable() + + if cv_customgunbob:GetBool() then + self2.BobScale = 0 + self2.SwayScale = 0 + + return + end + + local isp = self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress() + local wpr = self2.WalkProgressUnpredicted or self:GetWalkProgress() + local spr = self:GetSprintProgress() + + fac = gunbob_intensity_cvar:GetFloat() * ((1 - isp) * 0.85 + 0.15) + bscale = fac + + if spr > 0.005 then + bscale = bscale * l_Lerp(spr, 1, self2.SprintBobMult) + elseif wpr > 0.005 then + bscale = bscale * l_Lerp(wpr, 1, l_Lerp(isp, self2.WalkBobMult, self2.WalkBobMult_Iron or self2.WalkBobMult)) + end + + self2.BobScale = bscale + self2.SwayScale = fac +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/effects.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/effects.lua new file mode 100644 index 0000000..37b04e7 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/effects.lua @@ -0,0 +1,171 @@ +local vector_up = Vector(0, 0, 1) +local math = math +local render = render +local LerpVector = LerpVector + +--[[ +Function Name: ComputeSmokeLighting +Syntax: self:ComputeSmokeLighting(pos, nrm, pcf). +Returns: Nothing. +Notes: Used to light the muzzle smoke trail, by setting its PCF Control Point 1 +Purpose: FX +]]-- +function SWEP:ComputeSmokeLighting( pos, nrm, pcf ) + if not IsValid(pcf) then return end + local licht = render.ComputeLighting(pos, nrm) + local lichtFloat = math.Clamp((licht.r + licht.g + licht.b) / 3, 0, TFA.Particles.SmokeLightingClamp) / TFA.Particles.SmokeLightingClamp + local lichtFinal = LerpVector(lichtFloat, TFA.Particles.SmokeLightingMin, TFA.Particles.SmokeLightingMax) + pcf:SetControlPoint(1, lichtFinal) +end + +--[[ +Function Name: SmokePCFLighting +Syntax: self:SmokePCFLighting(). +Returns: Nothing. +Notes: Used to loop through all of our SmokePCF tables and call ComputeSmokeLighting on them +Purpose: FX +]]-- +function SWEP:SmokePCFLighting() + local mzPos = self:GetMuzzlePos() + if not mzPos or not mzPos.Pos then return end + local pos = mzPos.Pos + if self.SmokePCF then + for _, v in pairs(self.SmokePCF) do + self:ComputeSmokeLighting(pos, vector_up, v) + end + end + if not self:VMIV() then return end + local vm = self.OwnerViewModel + if vm.SmokePCF then + for _, v in pairs(vm.SmokePCF) do + self:ComputeSmokeLighting(pos, vector_up, v) + end + end +end + +--[[ +Function Name: FireAnimationEvent +Syntax: self:FireAnimationEvent( position, angle, event id, options). +Returns: Nothing. +Notes: Used to capture and disable viewmodel animation events, unless you disable that feature. +Purpose: FX +]]-- +function SWEP:FireAnimationEvent(pos, ang, event, options) + if self.CustomMuzzleFlash or not self.MuzzleFlashEnabled then + -- Disables animation based muzzle event + if (event == 21) then return true end + -- Disable thirdperson muzzle flash + if (event == 5003) then return true end + + -- Disable CS-style muzzle flashes, but chance our muzzle flash attachment if one is given. + if (event == 5001 or event == 5011 or event == 5021 or event == 5031) then + if self.AutoDetectMuzzleAttachment then + self.MuzzleAttachmentRaw = math.Clamp(math.floor((event - 4991) / 10), 1, 4) + self:ShootEffectsCustom(true) + end + + return true + end + end + + if (self.LuaShellEject and event ~= 5004) then return true end +end + +--[[ +Function Name: MakeMuzzleSmoke +Syntax: self:MakeMuzzleSmoke( entity, attachment). +Returns: Nothing. +Notes: Deprecated. Used to make the muzzle smoke effect, clientside. +Purpose: FX +]]-- + +local limit_particle_cv = GetConVar("cl_tfa_fx_muzzlesmoke_limited") + +function SWEP:MakeMuzzleSmoke(entity, attachment) + if ( not limit_particle_cv ) or limit_particle_cv:GetBool() then + self:CleanParticles() + end + local ht = self.DefaultHoldType and self.DefaultHoldType or self.HoldType + + if (CLIENT and TFA.GetMZSmokeEnabled() and IsValid(entity) and attachment and attachment ~= 0) then + ParticleEffectAttach(self.SmokeParticles[ht], PATTACH_POINT_FOLLOW, entity, attachment) + end +end + +--[[ +Function Name: ImpactEffect +Syntax: self:ImpactEffect( position, normal (ang:Up()), materialt ype). +Returns: Nothing. +Notes: Used to make the impact effect. See utilities code for CanDustEffect. +Purpose: FX +]]-- + +function SWEP:DoImpactEffect(tr, dmgtype) + if tr.HitSky then return true end + local ib = self.BashBase and IsValid(self) and self:GetBashing() + local dmginfo = DamageInfo() + dmginfo:SetDamageType(dmgtype) + + if dmginfo:IsDamageType(DMG_SLASH) or (ib and self.Secondary_TFA.BashDamageType == DMG_SLASH and tr.MatType ~= MAT_FLESH and tr.MatType ~= MAT_ALIENFLESH) or (self and self.DamageType and self.DamageType == DMG_SLASH) then + util.Decal("ManhackCut", tr.HitPos + tr.HitNormal, tr.HitPos - tr.HitNormal) + + return true + end + + if ib and self.Secondary_TFA.BashDamageType == DMG_GENERIC then return true end + if ib then return end + + if IsValid(self) then + self:ImpactEffectFunc(tr.HitPos, tr.HitNormal, tr.MatType) + end + + if self.ImpactDecal and self.ImpactDecal ~= "" then + util.Decal(self.ImpactDecal, tr.HitPos + tr.HitNormal, tr.HitPos - tr.HitNormal) + + return true + end +end + +local impact_cl_enabled = GetConVar("cl_tfa_fx_impact_enabled") +local impact_sv_enabled = GetConVar("sv_tfa_fx_impact_override") + +function SWEP:ImpactEffectFunc(pos, normal, mattype) + local enabled + + if impact_cl_enabled then + enabled = impact_cl_enabled:GetBool() + else + enabled = true + end + + if impact_sv_enabled and impact_sv_enabled:GetInt() >= 0 then + enabled = impact_sv_enabled:GetBool() + end + + if enabled then + local fx = EffectData() + fx:SetOrigin(pos) + fx:SetNormal(normal) + + if self:CanDustEffect(mattype) then + TFA.Effects.Create("tfa_dust_impact", fx) + end + + if self:CanSparkEffect(mattype) then + TFA.Effects.Create("tfa_metal_impact", fx) + end + + local scal = math.sqrt(self:GetStatL("Primary.Damage") / 30) + if mattype == MAT_FLESH then + scal = scal * 0.25 + end + fx:SetEntity(self:GetOwner()) + fx:SetMagnitude(mattype or 0) + fx:SetScale( scal ) + TFA.Effects.Create("tfa_bullet_impact", fx) + + if self.ImpactEffect then + TFA.Effects.Create(self.ImpactEffect, fx) + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/flashlight.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/flashlight.lua new file mode 100644 index 0000000..a15bb56 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/flashlight.lua @@ -0,0 +1,184 @@ +local vector_origin = Vector() + +local att, angpos, attname, elemname, targetent +SWEP.FlashlightDistance = 12 * 50 -- default 50 feet +SWEP.FlashlightAttachment = 0 +SWEP.FlashlightBrightness = 1 +SWEP.FlashlightFOV = 60 + +local Material = Material +local ProjectedTexture = ProjectedTexture +local math = math + +local function IsHolstering(wep) + if IsValid(wep) and TFA.Enum.HolsterStatus[wep:GetStatus()] then return true end + + return false +end + +-- TODO: This seems to be *extremely* similar to drawlaser +-- Should we merge them? +function SWEP:DrawFlashlight(is_vm) + local self2 = self:GetTable() + + if not self2.FlashlightDotMaterial then + self2.FlashlightDotMaterial = Material(self2.GetStatL(self, "FlashlightMaterial") or "effects/flashlight001") + end + + local ply = self:GetOwner() + if not IsValid(ply) then return end + + if not self:GetFlashlightEnabled() then + self:CleanFlashlight() + + return + end + + if is_vm then + if not self2.VMIV(self) then + self:CleanFlashlight() + + return + end + + targetent = self2.OwnerViewModel + elemname = self2.GetStatL(self, "Flashlight_VElement", self2.GetStatL(self, "Flashlight_Element")) + + local ViewModelElements = self:GetStatRaw("ViewModelElements", TFA.LatestDataVersion) + + if elemname and ViewModelElements[elemname] and IsValid(ViewModelElements[elemname].curmodel) then + targetent = ViewModelElements[elemname].curmodel + end + + att = self2.GetStatL(self, "FlashlightAttachment") + + attname = self2.GetStatL(self, "FlashlightAttachmentName") + + if attname then + att = targetent:LookupAttachment(attname) + end + + if (not att) or att <= 0 then + self:CleanFlashlight() + + return + end + + angpos = targetent:GetAttachment(att) + + if not angpos then + self:CleanFlashlight() + + return + end + + if self2.FlashlightISMovement and self2.CLIronSightsProgress > 0 then + local isang = self2.GetStatL(self, "IronSightsAngle") + angpos.Ang:RotateAroundAxis(angpos.Ang:Right(), isang.y * (self2.ViewModelFlip and -1 or 1) * self2.CLIronSightsProgress) + angpos.Ang:RotateAroundAxis(angpos.Ang:Up(), -isang.x * self2.CLIronSightsProgress) + end + + local localProjAng = select(2, WorldToLocal(vector_origin, angpos.Ang, vector_origin, EyeAngles())) + localProjAng.p = localProjAng.p * ply:GetFOV() / self2.ViewModelFOV + localProjAng.y = localProjAng.y * ply:GetFOV() / self2.ViewModelFOV + local wsProjAng = select(2, LocalToWorld(vector_origin, localProjAng, vector_origin, EyeAngles())) --reprojection for view angle + + if not IsValid(ply.TFAFlashlightGun) and not IsHolstering(self) then + local lamp = ProjectedTexture() + ply.TFAFlashlightGun = lamp + lamp:SetTexture(self2.FlashlightDotMaterial:GetString("$basetexture")) + lamp:SetFarZ(self2.GetStatL(self, "FlashlightDistance")) -- How far the light should shine + lamp:SetFOV(self2.GetStatL(self, "FlashlightFOV")) + lamp:SetPos(angpos.Pos) + lamp:SetAngles(angpos.Ang) + lamp:SetBrightness(self2.GetStatL(self, "FlashlightBrightness") * (0.9 + 0.1 * math.max(math.sin(CurTime() * 120), math.cos(CurTime() * 40)))) + lamp:SetNearZ(1) + lamp:SetColor(color_white) + lamp:SetEnableShadows(true) + lamp:Update() + end + + local lamp = ply.TFAFlashlightGun + + if IsValid(lamp) then + lamp:SetPos(angpos.Pos) + lamp:SetAngles(wsProjAng) + lamp:SetBrightness(1.4 + 0.1 * math.max(math.sin(CurTime() * 120), math.cos(CurTime() * 40))) + lamp:Update() + end + + return + end + + targetent = self + + elemname = self2.GetStatL(self, "Flashlight_WElement", self2.GetStatL(self, "Flashlight_Element")) + + local WorldModelElements = self:GetStatRaw("WorldModelElements", TFA.LatestDataVersion) + + if elemname and WorldModelElements[elemname] and IsValid(WorldModelElements[elemname].curmodel) then + targetent = WorldModelElements[elemname].curmodel + end + + att = self2.GetStatL(self, "FlashlightAttachmentWorld", self2.GetStatL(self, "FlashlightAttachment")) + + attname = self2.GetStatL(self, "FlashlightAttachmentNameWorld", self2.GetStatL(self, "FlashlightAttachmentName")) + + if attname then + att = targetent:LookupAttachment(attname) + end + + if (not att) or att <= 0 then + self:CleanFlashlight() + + return + end + + angpos = targetent:GetAttachment(att) + + if not angpos then + angpos = targetent:GetAttachment(1) + end + + if not angpos then + self:CleanFlashlight() + + return + end + + if not IsValid(ply.TFAFlashlightGun) and not IsHolstering(self) then + local lamp = ProjectedTexture() + ply.TFAFlashlightGun = lamp + lamp:SetTexture(self2.FlashlightDotMaterial:GetString("$basetexture")) + lamp:SetFarZ(self2.GetStatL(self, "FlashlightDistance")) -- How far the light should shine + lamp:SetFOV(self2.GetStatL(self, "FlashlightFOV")) + lamp:SetPos(angpos.Pos) + lamp:SetAngles(angpos.Ang) + lamp:SetBrightness(self2.GetStatL(self, "FlashlightBrightness") * (0.9 + 0.1 * math.max(math.sin(CurTime() * 120), math.cos(CurTime() * 40)))) + lamp:SetNearZ(1) + lamp:SetColor(color_white) + lamp:SetEnableShadows(false) + lamp:Update() + end + + local lamp = ply.TFAFlashlightGun + + if IsValid(lamp) then + local lamppos = angpos.Pos + local ang = angpos.Ang + lamp:SetPos(lamppos) + lamp:SetAngles(ang) + lamp:SetBrightness(self2.GetStatL(self, "FlashlightBrightness") * (0.9 + 0.1 * math.max(math.sin(CurTime() * 120), math.cos(CurTime() * 40)))) + lamp:Update() + end +end + +function SWEP:CleanFlashlight() + local ply = self:GetOwner() + + if IsValid(ply) and IsValid(ply.TFAFlashlightGun) then + ply.TFAFlashlightGun:Remove() + end +end + +TFA.SWEP_LOAD_COMPLETE = true diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/fov.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/fov.lua new file mode 100644 index 0000000..ac84667 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/fov.lua @@ -0,0 +1,46 @@ +local LocalPlayer = LocalPlayer +local math = math + +local function GetScreenAspectRatio() + return ScrW() / ScrH() +end + +local function ScaleFOVByWidthRatio(fovDegrees, ratio) + local halfAngleRadians = fovDegrees * (0.5 * math.pi / 180.0) + local t = math.tan(halfAngleRadians) + t = t * ratio + local retDegrees = (180.0 / math.pi) * math.atan(t) + + return retDegrees * 2.0 +end + +local default_fov_cv = GetConVar("default_fov") + +function SWEP:GetTrueFOV() + local fov = TFADUSKFOV or default_fov_cv:GetFloat() + local ply = LocalPlayer() + + if not ply:IsValid() then return fov end + + if ply:GetFOV() < ply:GetDefaultFOV() - 1 then + fov = ply:GetFOV() + end + + if TFADUSKFOV_FINAL then + fov = TFADUSKFOV_FINAL + end + + return fov +end + +function SWEP:GetViewModelFinalFOV() + local fov_default = default_fov_cv:GetFloat() + local fov = self:GetTrueFOV() + local flFOVOffset = fov_default - fov + local fov_vm = self.ViewModelFOV - flFOVOffset + local aspectRatio = GetScreenAspectRatio() * 0.75 -- (4/3) + --local final_fov = ScaleFOVByWidthRatio( fov, aspectRatio ) + local final_fovViewmodel = ScaleFOVByWidthRatio(fov_vm, aspectRatio) + + return final_fovViewmodel +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/hud.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/hud.lua new file mode 100644 index 0000000..bea525a --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/hud.lua @@ -0,0 +1,1985 @@ +if IsValid(TFA.INSPECTIONPANEL) then TFA.INSPECTIONPANEL:Remove() end + +local l_CT = CurTime + +local CMIX_MULT = 1 +local c1t = {} +local c2t = {} + +local function ColorMix(c1, c2, fac, t) + c1 = c1 or color_white + c2 = c2 or color_white + c1t.r = c1.r + c1t.g = c1.g + c1t.b = c1.b + c1t.a = c1.a + c2t.r = c2.r + c2t.g = c2.g + c2t.b = c2.b + c2t.a = c2.a + + for k, v in pairs(c1t) do + if t == CMIX_MULT then + c1t[k] = Lerp(fac, v, (c1t[k] / 255 * c2t[k] / 255) * 255) + else + c1t[k] = Lerp(fac, v, c2t[k]) + end + end + + return Color(c1t.r, c1t.g, c1t.b, c1t.a) +end + + + +local cv_red_r, cv_red_g, cv_red_b = GetConVar("cl_tfa_hud_crosshair_color_enemy_r"), GetConVar("cl_tfa_hud_crosshair_color_enemy_g"), GetConVar("cl_tfa_hud_crosshair_color_enemy_b") +local c_red = Color(cv_red_r:GetInt(), cv_red_g:GetInt(), cv_red_b:GetInt(), 255) + +local function UpdateEnemyTeamColor() + c_red.r = cv_red_r:GetInt() + c_red.g = cv_red_g:GetInt() + c_red.b = cv_red_b:GetInt() +end +cvars.AddChangeCallback(cv_red_r:GetName(), UpdateEnemyTeamColor, "c_red") +cvars.AddChangeCallback(cv_red_g:GetName(), UpdateEnemyTeamColor, "c_red") +cvars.AddChangeCallback(cv_red_b:GetName(), UpdateEnemyTeamColor, "c_red") + + + +local cv_grn_r, cv_grn_g, cv_grn_b = GetConVar("cl_tfa_hud_crosshair_color_friendly_r"), GetConVar("cl_tfa_hud_crosshair_color_friendly_g"), GetConVar("cl_tfa_hud_crosshair_color_friendly_b") +local c_grn = Color(cv_grn_r:GetInt(), cv_grn_g:GetInt(), cv_grn_b:GetInt(), 255) + +local function UpdateFriendlyTeamColor() + c_grn.r = cv_grn_r:GetInt() + c_grn.g = cv_grn_g:GetInt() + c_grn.b = cv_grn_b:GetInt() +end +cvars.AddChangeCallback(cv_grn_r:GetName(), UpdateFriendlyTeamColor, "c_grn") +cvars.AddChangeCallback(cv_grn_g:GetName(), UpdateFriendlyTeamColor, "c_grn") +cvars.AddChangeCallback(cv_grn_b:GetName(), UpdateFriendlyTeamColor, "c_grn") + + + +local cl_xhair_teamcolorcvar, sv_xhair_showplayercvar, sv_xhair_showplayerteamcvar + +local GetNPCDisposition = TFA.GetNPCDisposition +local GameModeTeamBased = GAMEMODE.TeamBased + +function SWEP:GetTeamColor(ent) + if not cl_xhair_teamcolorcvar then + cl_xhair_teamcolorcvar = GetConVar("cl_tfa_hud_crosshair_color_team") + end + + if not sv_xhair_showplayercvar then + sv_xhair_showplayercvar = GetConVar("sv_tfa_crosshair_showplayer") + end + + if not sv_xhair_showplayerteamcvar then + sv_xhair_showplayerteamcvar = GetConVar("sv_tfa_crosshair_showplayerteam") + end + + if not cl_xhair_teamcolorcvar:GetBool() then return color_white end + + local ply = LocalPlayer() + if not IsValid(ply) then return color_white end + + if ent:IsPlayer() then + if not sv_xhair_showplayercvar:GetBool() then return color_white end + + if GameModeTeamBased and sv_xhair_showplayerteamcvar:GetBool() then + if ent:Team() == ply:Team() then + return c_grn + else + return c_red + end + end + + return c_red + end + + if ent:IsNPC() then + local disp = GetNPCDisposition(ent) or ent:GetNW2Int("tfa_disposition", -1) + + if disp > 0 then + if disp == (D_FR or 2) or disp == (D_HT or 1) then + return c_red + else + return c_grn + end + end + + if IsFriendEntityName(ent:GetClass()) then + return c_grn + else + return c_red + end + end + + return color_white +end + +--[[ +local function RoundDecimals(number, decimals) + local decfactor = math.pow(10, decimals) + + return math.Round(tonumber(number) * decfactor) / decfactor +end +]] +-- +--[[ +Function Name: DoInspectionDerma +Syntax: self:DoInspectionDerma(). +Returns: Nothing. +Notes: Used to manage our Derma. +Purpose: Used to manage our Derma. +]] +-- +local TFA_INSPECTIONPANEL +local spacing = 64 + +local ScaleH = TFA.ScaleH + +local DrawTextShadowed = TFA.DrawTextShadowed +local function TextShadowPaint(myself, w, h) + if not myself.TextColor then + myself.TextColor = ColorAlpha(color_white, 0) + end + + DrawTextShadowed(myself.Text, myself.Font, myself.TextPosX or 0, myself.TextPosY or 0, myself.TextColor, ScaleH(2)) +end +SWEP.TextShadowPaintFunc = TextShadowPaint + +local cv_bars_exp = GetConVar("cl_tfa_inspect_newbars") +function SWEP:PaintStatPanel(w, h) + if not IsValid(self) then return end + if not IsValid(TFA_INSPECTIONPANEL) then return end + if not IsValid(self.Weapon) then return end + if not self.StatTable then return end + + if self.StatTable.bar then + local bar = math.Clamp(self.StatTable.bar(self.Weapon), 0, 1) + + local xx, ww, blockw, padw + xx = w - ScaleH(120) + ww = w - xx + + local bgcol = ColorAlpha(TFA_INSPECTIONPANEL.BackgroundColor or color_white, (TFA_INSPECTIONPANEL.Alpha or 0) / 2) + + if cv_bars_exp and cv_bars_exp:GetBool() then + draw.RoundedBox(4, xx + 1, 1, ww - 2, h - 2, bgcol) + + local w1, h1 = self:LocalToScreen(xx + 2, 2) + local w2, h2 = self:LocalToScreen(xx - 2 + ww * bar, h - 2) + + render.SetScissorRect(w1, h1, w2, h2, true) + draw.RoundedBox(4, xx + 2, 2, ww - 4, h - 4, TFA_INSPECTIONPANEL.SecondaryColor or color_white) + render.SetScissorRect(0, 0, 0, 0, false) + + if self.StatTable.text then + DrawTextShadowed(self.StatTable.text(self.Weapon), self.Font, 0, 0, TFA_INSPECTIONPANEL.SecondaryColor, ScaleH(2)) + end + + return + end + + blockw = math.floor(ww / 15) + padw = math.floor(ww / 10) + + self.Bars = math.Clamp(math.Round(bar * 10), 0, 10) + + surface.SetDrawColor(bgcol) + for _ = 0, 9 do + surface.DrawRect(xx, 2, blockw, h - 5) + xx = math.floor(xx + padw) + end + + xx = w - ScaleH(120) + surface.SetDrawColor(TFA_INSPECTIONPANEL.BackgroundColor or color_white) + + for _ = 0, self.Bars - 1 do + surface.DrawRect(xx + 1, 3, blockw, h - 5) + xx = math.floor(xx + padw) + end + + xx = w - ScaleH(120) + surface.SetDrawColor(TFA_INSPECTIONPANEL.SecondaryColor or color_white) + + for _ = 0, self.Bars - 1 do + surface.DrawRect(xx, 2, blockw, h - 5) + xx = math.floor(xx + padw) + end + end + + if self.StatTable.text then + DrawTextShadowed(self.StatTable.text(self.Weapon), self.Font, 0, 0, TFA_INSPECTIONPANEL.SecondaryColor, ScaleH(2)) + end +end + +local function WrapTextLines(textlines, maxwidth, font) + if type(textlines) == "string" then + textlines = string.Split(textlines, "\n") + end + + local lines = {} + + surface.SetFont(font) + + for _, text in ipairs(textlines) do + local w, _ = surface.GetTextSize(text) + + if w > maxwidth then + local line = "" + + for _, word in ipairs(string.Explode(" ", text)) do + local added = line == "" and word or line .. " " .. word + w, _ = surface.GetTextSize(added) + + if w > maxwidth then + table.insert(lines, line) + line = word + else + line = added + end + end + + if line ~= "" then + table.insert(lines, line) + end + else + table.insert(lines, text) + end + end + + return lines +end + +local pad = 4 +local infotextpad = "\t" +local INSPECTION_BACKGROUND = TFA.Attachments.Colors["background"] +local INSPECTION_ACTIVECOLOR = TFA.Attachments.Colors["active"] +local INSPECTION_PRIMARYCOLOR = TFA.Attachments.Colors["primary"] +local INSPECTION_SECONDARYCOLOR = TFA.Attachments.Colors["secondary"] + + +SWEP.AmmoTypeStrings = { + ["pistol"] = "tfa.ammo.pistol", + ["smg1"] = "tfa.ammo.smg1", + ["ar2"] = "tfa.ammo.ar2", + ["buckshot"] = "tfa.ammo.buckshot", + ["357"] = "tfa.ammo.357", + ["SniperPenetratedRound"] = "tfa.ammo.sniperpenetratedround" +} + +SWEP.WeaponTypeStrings = { + ["weapon"] = "tfa.weptype.generic", + ["pistol"] = "tfa.weptype.pistol", + ["machine pistol"] = "tfa.weptype.machpistol", + ["revolver"] = "tfa.weptype.revolver", + ["sub-machine gun"] = "tfa.weptype.smg", + ["rifle"] = "tfa.weptype.rifle", + ["carbine"] = "tfa.weptype.carbine", + ["light machine gun"] = "tfa.weptype.lmg", + ["shotgun"] = "tfa.weptype.shotgun", + ["designated marksman rifle"] = "tfa.weptype.dmr", + ["sniper rifle"] = "tfa.weptype.sniper", + ["grenade"] = "tfa.weptype.grenade", + ["launcher"] = "tfa.weptype.launcher", + ["dual pistols"] = "tfa.weptype.pistol.dual", + ["dual revolvers"] = "tfa.weptype.revolver.dual", + ["dual sub-machine guns"] = "tfa.weptype.smg.dual", + ["dual guns"] = "tfa.weptype.generic.dual", +} -- if you have more generalized (and widely used) types that could be localized please let us know so that we can add them here! + +local att_enabled_cv = GetConVar("sv_tfa_attachments_enabled") + +SWEP.VGUIPaddingW = 32 +SWEP.VGUIPaddingH = 80 + +function SWEP:InspectionVGUISideBars(mainpanel) + local barleft = vgui.Create("DPanel", mainpanel) + barleft:SetWidth(ScaleH(self.VGUIPaddingW)) + barleft:Dock(LEFT) + + barleft.Paint = function(myself, w, h) + local mycol = mainpanel.SecondaryColor + + if not mycol then return end + + surface.SetDrawColor(mycol) + surface.SetTexture(mainpanel.SideBar or 1) + surface.DrawTexturedRect(0, 0, w, h) + end + + local barright = vgui.Create("DPanel", mainpanel) + barright:SetWidth(ScaleH(self.VGUIPaddingW)) + barright:Dock(RIGHT) + + barright.Paint = function(myself, w, h) + local mycol = mainpanel.SecondaryColor + + if not mycol then return end + + surface.SetDrawColor(mycol) + surface.SetTexture(mainpanel.SideBar or 1) + surface.DrawTexturedRectUV(0, 0, w, h, 1, 0, 0, 1) + end +end + +function SWEP:InspectionVGUIMainInfo(contentpanel) + if hook.Run("TFA_InspectVGUI_InfoStart", self, contentpanel) ~= false then + local mainpanel = contentpanel:GetParent() + + local infopanel = contentpanel:Add("DSizeToContents") + infopanel:SetName("Main Weapon Info") + + infopanel:SetSize(0, 0) + infopanel:Dock(TOP) + infopanel.Paint = function() end + + local titletext = infopanel:Add("DPanel") + titletext:SetName("Name") + titletext.Text = self.PrintName or "TFA Weapon" + + titletext.Think = function(myself) + myself.TextColor = mainpanel.PrimaryColor + end + + titletext.Font = "TFA_INSPECTION_TITLE" + titletext:Dock(TOP) + titletext:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightTitle) + titletext.Paint = TextShadowPaint + + local typetext = infopanel:Add("DPanel") + typetext:SetName("Type") + + local weptype = self:GetStatL("Type_Displayed") or self:GetType() + typetext.Text = language.GetPhrase(self.WeaponTypeStrings[weptype:lower()] or weptype) + + typetext.Think = function(myself) + myself.TextColor = mainpanel.PrimaryColor + end + + typetext.Font = "TFA_INSPECTION_DESCR" + typetext:Dock(TOP) + typetext:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightDescription) + typetext.Paint = TextShadowPaint + + --Space things out for block1 + local spacer = infopanel:Add("DPanel") + spacer:SetName("Spacer") + spacer:Dock(TOP) + spacer:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightDescription) + spacer.Paint = function() end + + --First stat block + local categorytext = infopanel:Add("DPanel") + categorytext:SetName("Category") + categorytext.Text = self.Category or self.Base + + categorytext.Think = function(myself) + myself.TextColor = mainpanel.SecondaryColor + end + + categorytext.Font = "TFA_INSPECTION_SMALL" + categorytext:Dock(TOP) + categorytext:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightSmall) + categorytext.Paint = TextShadowPaint + + if self.Author and string.Trim(self.Author) ~= "" then + local authortext = infopanel:Add("DPanel") + authortext:SetName("Author") + authortext.Text = infotextpad .. language.GetPhrase("tfa.inspect.creator"):format(self.Author) + + authortext.Think = function(myself) + myself.TextColor = mainpanel.SecondaryColor + end + + authortext.Font = "TFA_INSPECTION_SMALL" + authortext:Dock(TOP) + authortext:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightSmall) + authortext.Paint = TextShadowPaint + end + + if self.Manufacturer and string.Trim(self.Manufacturer) ~= "" then + local makertext = infopanel:Add("DPanel") + makertext:SetName("Manufacturer") + makertext.Text = infotextpad .. language.GetPhrase("tfa.inspect.manufacturer"):format(self.Manufacturer) + + makertext.Think = function(myself) + myself.TextColor = mainpanel.SecondaryColor + end + + makertext.Font = "TFA_INSPECTION_SMALL" + makertext:Dock(TOP) + makertext:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightSmall) + makertext.Paint = TextShadowPaint + end + + local clip = self:GetStatL("Primary.ClipSize") + + if clip > 0 then + local capacitytext = infopanel:Add("DPanel") + capacitytext:SetName("Capacity") + capacitytext.Text = infotextpad .. language.GetPhrase("tfa.inspect.capacity"):format(clip .. (self:CanChamber() and (self:GetStatL("IsAkimbo") and " + 2" or " + 1") or "")) + + capacitytext.Think = function(myself) + myself.TextColor = mainpanel.SecondaryColor + end + + capacitytext.Font = "TFA_INSPECTION_SMALL" + capacitytext:Dock(TOP) + capacitytext:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightSmall) + capacitytext.Paint = TextShadowPaint + end + + local an = game.GetAmmoName(self:GetPrimaryAmmoType()) + + if an and an ~= "" and string.len(an) > 1 then + local ammotypetext = infopanel:Add("DPanel") + ammotypetext:SetName("Ammo Type") + ammotypetext.Text = infotextpad .. language.GetPhrase("tfa.inspect.ammotype"):format(language.GetPhrase(self.AmmoTypeStrings[an:lower()] or (an .. "_ammo"))) + + ammotypetext.Think = function(myself) + myself.TextColor = mainpanel.SecondaryColor + end + + ammotypetext.Font = "TFA_INSPECTION_SMALL" + ammotypetext:Dock(TOP) + ammotypetext:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightSmall) + ammotypetext.Paint = TextShadowPaint + end + + local maxlinewidth = ScrW() * .5 - ScaleH(self.VGUIPaddingW) * 4 + + if self.Purpose and string.Trim(self.Purpose) ~= "" then + local lines = WrapTextLines(language.GetPhrase("tfa.inspect.purpose"):format(language.GetPhrase(self.Purpose)), maxlinewidth, "TFA_INSPECTION_SMALL") + + for _, line in pairs(lines) do + local purposeline = infopanel:Add("DPanel") + purposeline:SetName("Purpose") + purposeline.Text = infotextpad .. line + + purposeline.Think = function(myself) + myself.TextColor = mainpanel.SecondaryColor + end + + purposeline.Font = "TFA_INSPECTION_SMALL" + purposeline:Dock(TOP) + purposeline:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightSmall) + purposeline.Paint = TextShadowPaint + end + end + + if self.Description and string.Trim(self.Description) ~= "" then + local lines = WrapTextLines(language.GetPhrase(self.Description), maxlinewidth, "TFA_INSPECTION_SMALL") + + for _, line in ipairs(lines) do + local descline = infopanel:Add("DPanel") + descline:SetName("Description") + descline.Text = infotextpad .. line + + descline.Think = function(myself) + myself.TextColor = mainpanel.SecondaryColor + end + + descline.Font = "TFA_INSPECTION_SMALL" + descline:Dock(TOP) + descline:SetSize(ScrW() * .5, TFA.Fonts.InspectionHeightSmall) + descline.Paint = TextShadowPaint + end + end + + hook.Run("TFA_InspectVGUI_InfoFinish", self, contentpanel, infopanel) + end +end + +local cv_display_moa = GetConVar("cl_tfa_inspect_spreadinmoa") +local sv_tfa_weapon_weight = GetConVar("sv_tfa_weapon_weight") + +local AccuracyToDegrees = 1 / TFA.DegreesToAccuracy +local AccuracyToMOA = 1 / TFA.DegreesToAccuracy * 60 + +SWEP.InspectionVGUI_BestDamage = 100 +SWEP.InspectionVGUI_BestRPM = 1200 +SWEP.InspectionVGUI_WorstAccuracy = 0.045 +SWEP.InspectionVGUI_WorstMove = 0.8 +SWEP.InspectionVGUI_WorstRecoil = 1 + +SWEP.InspectionVGUIStatsTable = { + { + name = "bash", + cond = function(wep) return wep.BashBase and wep:GetStatL("Secondary.CanBash") ~= false end, + text = function(wep) return language.GetPhrase("tfa.inspect.stat.bashdamage"):format(math.Round(wep:GetStatL("Secondary.BashDamage", 0))) end, + bar = function(wep) return wep:GetStatL("Secondary.BashDamage") / wep.InspectionVGUI_BestDamage end + }, + { + name = "stability", + cond = function(wep) return true end, + text = function(wep) return language.GetPhrase("tfa.inspect.stat.stability"):format(math.Clamp(math.Round((1 - math.abs(wep:GetStatL("Primary.KickUp") + wep:GetStatL("Primary.KickDown")) / 2 / 1) * 100), 0, 100)) end, + bar = function(wep) return (1 - math.abs(wep:GetStatL("Primary.KickUp") + wep:GetStatL("Primary.KickDown")) / 2 / wep.InspectionVGUI_WorstRecoil) end + }, + { + name = "damage", + cond = function(wep) return true end, + text = function(wep) + local dmgstr = language.GetPhrase("tfa.inspect.stat.damage"):format(math.Round(wep:GetStatL("Primary.Damage"))) + + if wep:GetStatL("Primary.NumShots") ~= 1 then + dmgstr = dmgstr .. "x" .. math.Round(wep:GetStatL("Primary.NumShots")) + end + + return dmgstr + end, + bar = function(wep) return (wep:GetStatL("Primary.Damage") * math.Round(wep:GetStatL("Primary.NumShots") * 0.75)) / wep.InspectionVGUI_BestDamage end + }, + { + name = "mobility", + cond = function(wep) return sv_tfa_weapon_weight:GetBool() end, + text = function(wep) return language.GetPhrase("tfa.inspect.stat.mobility"):format(math.Round(wep:GetStatL("RegularMoveSpeedMultiplier") * 100)) end, + bar = function(wep) return (wep:GetStatL("RegularMoveSpeedMultiplier") - wep.InspectionVGUI_WorstMove) / (1 - wep.InspectionVGUI_WorstMove) end + }, + { + name = "firerate", + cond = function(wep) return true end, + text = function(wep) return language.GetPhrase("tfa.inspect.stat.rpm"):format(wep:GetStatL("Primary.RPM_Displayed") or wep:GetStatL("Primary.RPM")) end, + bar = function(wep) return (wep:GetStatL("Primary.RPM_Displayed") or wep:GetStatL("Primary.RPM")) / wep.InspectionVGUI_BestRPM end + }, + { + name = "spread_hip", + cond = function(wep) return wep:GetStatL("Secondary.DisplaySpread", true) end, + text = function(wep) + local spread = wep:GetStatL("Primary.Spread") + + local spreadtext + if cv_display_moa and cv_display_moa:GetBool() then + spreadtext = language.GetPhrase("tfa.inspect.val.moa"):format(spread * AccuracyToMOA) + else + spreadtext = language.GetPhrase("tfa.inspect.val.degrees"):format(spread * AccuracyToDegrees) + end + + return language.GetPhrase("tfa.inspect.stat.accuracy.hip"):format(spreadtext) + end, + bar = function(wep) return 1 - wep:GetStatL("Primary.Spread") / wep.InspectionVGUI_WorstAccuracy end + }, + { + name = "spread_iron", + cond = function(wep) return wep:GetStatL("Secondary.IronSightsEnabled", false) and wep:GetStatL("Secondary.DisplayIronSpread", true) end, + text = function(wep) + local spread = wep:GetStatL("Primary.IronAccuracy") + + local spreadtext + if cv_display_moa and cv_display_moa:GetBool() then + spreadtext = language.GetPhrase("tfa.inspect.val.moa"):format(spread * AccuracyToMOA) + else + spreadtext = language.GetPhrase("tfa.inspect.val.degrees"):format(spread * AccuracyToDegrees) + end + + return language.GetPhrase("tfa.inspect.stat.accuracy"):format(spreadtext) + end, + bar = function(wep) return 1 - wep:GetStatL("Primary.IronAccuracy") / wep.InspectionVGUI_WorstAccuracy end + }, + { + name = "condition", + cond = function(wep) return wep:CanBeJammed() end, + text = function(wep) return language.GetPhrase("tfa.inspect.condition"):format(math.Clamp(math.Round((1 - wep:GetJamFactor() * .01) * 100), 0, 100)) end, + bar = function(wep) return 1 - wep:GetJamFactor() * .01 end + }, +} + +function SWEP:InspectionVGUIStats(contentpanel) + if hook.Run("TFA_InspectVGUI_StatsStart", self, contentpanel) ~= false then + local mainpanel = contentpanel:GetParent() + + local statspanel = contentpanel:Add("DPanel") + statspanel:SetName("Weapon Stats Container") + + local preferredWidth = math.min(ScaleH(400), ScrW() * .4) + + statspanel:SetSize(0, 0) + statspanel:Dock(BOTTOM) + statspanel:DockMargin(0, 0, ScrW() - preferredWidth - ScaleH(self.VGUIPaddingW) * 4, 0) + statspanel.Paint = function() end + + statspanel.stats = {} + for _, tbl in ipairs(self.InspectionVGUIStatsTable) do + if not tbl.name or not tbl.text or not tbl.bar or statspanel.stats[tbl.name] then continue end + if tbl.cond and not tbl.cond(self) then continue end + + statspanel:SetTall(statspanel:GetTall() + TFA.Fonts.InspectionHeightSmall) + + local statpanel = statspanel:Add("DPanel") + + statpanel:SetName("Stat Panel - " .. tbl.name) + statpanel:SetSize(preferredWidth, TFA.Fonts.InspectionHeightSmall) + + statpanel.StatTable = tbl + statpanel.Weapon = self + statpanel.Font = "TFA_INSPECTION_SMALL" + + statpanel.Paint = self.PaintStatPanel + statpanel:Dock(BOTTOM) + + statspanel.stats[tbl.name] = statpanel + end + + hook.Run("TFA_InspectVGUI_StatsFinish", self, contentpanel, statspanel) + end +end + +function SWEP:InspectionVGUIAttachments(contentpanel) + local mainpanel = contentpanel:GetParent() + local scrollpanel, vbar + + if att_enabled_cv:GetBool() and hook.Run("TFA_InspectVGUI_AttachmentsStart", self, contentpanel) ~= false then + if self.Attachments then + scrollpanel = mainpanel:Add("DScrollPanel") + scrollpanel:SetName("Attachments Container") + + scrollpanel:SetSize(ScrW() * .5 - ScaleH(self.VGUIPaddingW) * 2, mainpanel:GetTall() - ScaleH(self.VGUIPaddingH) * 2) + scrollpanel:SetPos(ScrW() * .5, ScaleH(self.VGUIPaddingH)) + + vbar = scrollpanel:GetVBar() + + vbar.Paint = function(myself, w, h) + if not mainpanel or not mainpanel.BackgroundColor then return end + surface.SetDrawColor(mainpanel.BackgroundColor.r, mainpanel.BackgroundColor.g, mainpanel.BackgroundColor.b, mainpanel.BackgroundColor.a / 2) + surface.DrawRect(w * .65, 0, w * .35, h) + end + + vbar.btnUp.Paint = function(myself, w, h) + if not mainpanel or not mainpanel.PrimaryColor then return end + surface.SetDrawColor(mainpanel.PrimaryColor.r, mainpanel.PrimaryColor.g, mainpanel.PrimaryColor.b, mainpanel.PrimaryColor.a) + surface.DrawRect(w * .65, 0, w * .35, h) + end + + vbar.btnDown.Paint = function(myself, w, h) + if not mainpanel or not mainpanel.PrimaryColor then return end + surface.SetDrawColor(mainpanel.PrimaryColor.r, mainpanel.PrimaryColor.g, mainpanel.PrimaryColor.b, mainpanel.PrimaryColor.a) + surface.DrawRect(w * .65, 0, w * .35, h) + end + + vbar.btnGrip.Paint = function(myself, w, h) + if not mainpanel or not mainpanel.PrimaryColor then return end + surface.SetDrawColor(mainpanel.PrimaryColor.r, mainpanel.PrimaryColor.g, mainpanel.PrimaryColor.b, mainpanel.PrimaryColor.a) + surface.DrawRect(w * .65, 0, w * .35, h) + end + end + + self:GenerateVGUIAttachmentTable() + local i = 0 + local prevCat + local lineY = 0 + local scrollWide = scrollpanel:GetWide() - (IsValid(vbar) and vbar:GetTall() or 0) + local lastTooltipPanel + + local iconsize = math.Round(ScaleH(TFA.Attachments.IconSize)) + local catspacing = math.Round(ScaleH(TFA.Attachments.CategorySpacing)) + local padding = math.Round(ScaleH(TFA.Attachments.UIPadding)) + + for k, v in pairs(self.VGUIAttachments) do + if k ~= "BaseClass" then + if prevCat then + local isContinuing = prevCat == (v.cat or k) + lineY = lineY + (isContinuing and iconsize + padding or catspacing) + + if not isContinuing then + lastTooltipPanel = nil + end + end + + prevCat = v.cat or k + local testpanel = mainpanel:Add("TFAAttachmentPanel") + testpanel:SetName("Attachment Category Container") + testpanel:SetParent(scrollpanel) + testpanel:SetContentPanel(scrollpanel) + i = i + 1 + testpanel:SetWeapon(self) + testpanel:SetAttachment(k) + testpanel:SetCategory(v.cat or k) + testpanel:Initialize() + lastTooltipPanel = lastTooltipPanel or testpanel:InitializeTooltip() + testpanel:SetupTooltip(lastTooltipPanel) + testpanel:PopulateIcons() + testpanel:SetPos(scrollWide - testpanel:GetWide(), lineY) + end + end + + hook.Run("TFA_InspectVGUI_AttachmentsFinish", self, contentpanel, scrollpanel) + end + + if self.Primary.RangeFalloffLUTBuilt and self:GetStatL("Primary.DisplayFalloff") and hook.Run("TFA_InspectVGUI_FalloffStart", self, contentpanel) ~= false then + local falloffpanel = vgui.Create("EditablePanel", mainpanel) + falloffpanel:SetSize(ScrW() * .5 - ScaleH(self.VGUIPaddingW) * 2, mainpanel:GetTall() * 0.2) + falloffpanel:SetPos(ScrW() * .5, mainpanel:GetTall() - falloffpanel:GetTall() - ScaleH(self.VGUIPaddingH)) + falloffpanel:SetName("Damage Falloff Graph") + + if scrollpanel then + scrollpanel:SetTall(scrollpanel:GetTall() - falloffpanel:GetTall()) + falloffpanel:SetPos(ScrW() * .5, ScaleH(self.VGUIPaddingH) + scrollpanel:GetTall()) + end + + falloffpanel:NoClipping(true) + + local self2 = self + local shadow_color = Color(0, 0, 0) + + -- it differ from function above + local function shadowed_text(text, font, x, y, color, ...) + draw.SimpleText(text, font, x + 2, y + 2, shadow_color, ...) + draw.SimpleText(text, font, x, y, color, ...) + end + + local function shadowed_line(x, y, x2, y2, color, color2) + surface.SetDrawColor(color2) + surface.DrawLine(x + 1, y + 1, x2 + 1, y2 + 1) + surface.SetDrawColor(color) + surface.DrawLine(x, y, x2, y2) + end + + function falloffpanel.Paint(myself, w, h) + if not IsValid(self2) or not IsValid(mainpanel) then return end + + local lut = self2.Primary.RangeFalloffLUTBuilt + if not lut then return end + local wepdmg = self2.Primary.Damage + + shadow_color.a = mainpanel.SecondaryColor.a + + shadowed_text("#tfa.inspect.damagedrop", "TFASleekSmall", 0, ScaleH(pad), mainpanel.SecondaryColor, TEXT_ALIGN_LEFT) + + local ax, ay = 0, TFA.Fonts.SleekHeightSmall + ScaleH(pad) * 2 + + surface.SetDrawColor(mainpanel.SecondaryColor) + + local range = 0 + local div = 1 + + for i, data in ipairs(lut) do + if data[1] > range then + range = data[1] + end + + if data[2] > div then + div = data[2] + end + end + + range = range * 1.337 + + local rightpadding = 18 + + for pos = 1, 4 do + shadowed_line(ax + pos * (w - rightpadding) / 4, h - 2 - ay, ax + pos * (w - rightpadding) / 4, h - 12 - ay, mainpanel.SecondaryColor, mainpanel.BackgroundColor) + shadowed_text(string.format("%dm", range * 0.0254 * pos / 4), "TFASleekSmall", ax + pos * (w - rightpadding) / 4, h - ay, mainpanel.SecondaryColor, TEXT_ALIGN_CENTER) + end + + shadowed_line(ax + 1, ay + 1, 1, h - 2 - ay, mainpanel.SecondaryColor, mainpanel.BackgroundColor) + shadowed_line(ax + 1, h - 2 - ay, w - rightpadding, h - 2 - ay, mainpanel.SecondaryColor, mainpanel.BackgroundColor) + + local lx, ly = myself:LocalToScreen(ax, 0) + local mx, my = input.GetCursorPos() + local rmx, rmy = mx, my + mx = mx - lx + my = my - ly + + local px, py + + local cirX, cirY, dmg, drange + + local progression = mx / (w - ax - rightpadding) + + for i, data in ipairs(lut) do + local x, y = ax + data[1] / range * (w - ax - rightpadding), ay + (div - data[2]) * (h - ay * 2) / div + + if not px then + px, py = x, y + end + + shadowed_line(px, py, x, y, mainpanel.PrimaryColor, mainpanel.BackgroundColor) + + if x > mx and px < mx then + local t = (mx - px) / (x - px) + cirX, cirY = Lerp(t, px, x), Lerp(t, py, y) + local ndmg = lut[i + 1] and lut[i + 1][2] or data[2] + local deltadmg = ndmg - data[2] + dmg = deltadmg * t + data[2] + end + + px, py = x, y + end + + shadowed_line(px, py, w - ax - 18, py, mainpanel.PrimaryColor, mainpanel.BackgroundColor) + + if mx > px and (w - ax - 18) > mx then + cirX, cirY = mx, py + dmg = lut[#lut][2] + end + + if mx > 0 and my > 0 and mx < w and my < h and dmg then + shadowed_line(mx, ay, mx, h - ay, mainpanel.PrimaryColor, mainpanel.BackgroundColor) + + if cirX then + local Xsize = ScaleH(8) + + surface.SetDrawColor(mainpanel.BackgroundColor) + surface.DrawLine(cirX - Xsize + 1, cirY - Xsize + 1, cirX + Xsize + 1, cirY + Xsize + 1) + surface.DrawLine(cirX + Xsize + 1, cirY - Xsize + 1, cirX - Xsize + 1, cirY + Xsize + 1) + + surface.SetDrawColor(mainpanel.PrimaryColor) + surface.DrawLine(cirX - Xsize, cirY - Xsize, cirX + Xsize, cirY + Xsize) + surface.DrawLine(cirX + Xsize, cirY - Xsize, cirX - Xsize, cirY + Xsize) + end + + shadowed_text(string.format("%dm", math.Round(range * progression * 0.0254)), "TFASleekSmall", mx - ScaleH(pad), my - TFA.Fonts.SleekHeightSmall, mainpanel.SecondaryColor, TEXT_ALIGN_RIGHT) + shadowed_text(string.format("%ddmg", dmg * wepdmg), "TFASleekSmall", mx + ScaleH(pad), my - TFA.Fonts.SleekHeightSmall, mainpanel.SecondaryColor, TEXT_ALIGN_LEFT) + end + end + + hook.Run("TFA_InspectVGUI_FalloffFinish", self, contentpanel, falloffpanel) + end +end + +local cl_tfa_inspect_hide_in_screenshots = GetConVar("cl_tfa_inspect_hide_in_screenshots") +local cl_tfa_inspect_hide_hud = GetConVar("cl_tfa_inspect_hide_hud") +local cl_tfa_inspect_hide = GetConVar("cl_tfa_inspect_hide") +local cl_drawhud = GetConVar("cl_drawhud") + +local blacklist = { + CHudAmmo = false, + CHudBattery = false, + CHudHealth = false, +} + +local function HUDShouldDraw(_, elem) + return blacklist[elem] +end + +function SWEP:GenerateInspectionDerma() + if hook.Run("TFA_InspectVGUI_Start", self) == false then return end + if cl_tfa_inspect_hide:GetBool() then return end + + TFA_INSPECTIONPANEL = vgui.Create("DPanel") + TFA_INSPECTIONPANEL:SetSize(ScrW(), ScrH()) + TFA_INSPECTIONPANEL:DockPadding(ScaleH(self.VGUIPaddingW), ScaleH(self.VGUIPaddingH), ScaleH(self.VGUIPaddingW), ScaleH(self.VGUIPaddingH)) + TFA_INSPECTIONPANEL:SetRenderInScreenshots(not cl_tfa_inspect_hide_in_screenshots:GetBool()) + TFA_INSPECTIONPANEL:SetName("TFA Base Inspection Panel") + + TFA.INSPECTIONPANEL = TFA_INSPECTIONPANEL + + local function update_visible(status) + if not cl_tfa_inspect_hide_hud:GetBool() or not DLib then return end + + if status then + hook.DisableHook("HUDPaint") + hook.DisableHook("HUDPaintBackground") + hook.DisableHook("PreDrawHUD") + hook.DisableHook("PostDrawHUD") + hook.DisableHook("DrawDeathNotice") + + hook.Add("HUDShouldDraw", TFA_INSPECTIONPANEL, HUDShouldDraw) + else + hook.EnableHook("HUDPaint") + hook.EnableHook("HUDPaintBackground") + hook.EnableHook("PreDrawHUD") + hook.EnableHook("PostDrawHUD") + hook.EnableHook("DrawDeathNotice") + + hook.Remove("HUDShouldDraw", TFA_INSPECTIONPANEL, HUDShouldDraw) + end + end + + if not cl_drawhud:GetBool() then + TFA_INSPECTIONPANEL:SetVisible(false) + else + update_visible(true) + end + + cvars.AddChangeCallback("cl_drawhud", function() + if not IsValid(TFA_INSPECTIONPANEL) then return end + TFA_INSPECTIONPANEL:Think() + if not IsValid(TFA_INSPECTIONPANEL) then return end + TFA_INSPECTIONPANEL:SetVisible(cl_drawhud:GetBool()) + update_visible(cl_drawhud:GetBool()) + end, "TFA_INSPECTIONPANEL") + + local lastcustomizing = true + + function TFA_INSPECTIONPANEL.Think(myself, w, h) + local ply = LocalPlayer() + + if not IsValid(ply) then + myself:Remove() + + return + end + + local wep = ply:GetActiveWeapon() + + if not IsValid(wep) or not wep.IsTFAWeapon or wep:GetInspectingProgress() <= 0.01 then + myself:Remove() + + return + end + + if cl_tfa_inspect_hide_hud:GetBool() and DLib then + local customizing = wep:GetCustomizing() + + if customizing ~= lastcustomizing then + lastcustomizing = customizing + update_visible(customizing) + end + end + + myself.Player = ply + myself.Weapon = wep + end + + function TFA_INSPECTIONPANEL.OnRemove(myself) + update_visible(false) + end + + function TFA_INSPECTIONPANEL.Paint(myself, w, h) + local wep = self + + if IsValid(wep) then + myself.Alpha = wep:GetInspectingProgress() * 255 + myself.PrimaryColor = ColorAlpha(INSPECTION_PRIMARYCOLOR, TFA_INSPECTIONPANEL.Alpha) + myself.SecondaryColor = ColorAlpha(INSPECTION_SECONDARYCOLOR, TFA_INSPECTIONPANEL.Alpha) + myself.BackgroundColor = ColorAlpha(INSPECTION_BACKGROUND, TFA_INSPECTIONPANEL.Alpha) + myself.ActiveColor = ColorAlpha(INSPECTION_ACTIVECOLOR, TFA_INSPECTIONPANEL.Alpha) + + if not myself.SideBar then + myself.SideBar = surface.GetTextureID("vgui/inspectionhud/sidebar") + end + end + end + + self:InspectionVGUISideBars(TFA_INSPECTIONPANEL) + + local contentpanel = vgui.Create("DPanel", TFA_INSPECTIONPANEL) + contentpanel:Dock(FILL) + local spad = ScaleH(pad) + contentpanel:DockPadding(spad, spad, spad, spad) + contentpanel:SetName("Inspection Content Panel") + + function contentpanel.Paint() end + + -- Top block (gun name and info) + self:InspectionVGUIMainInfo(contentpanel) + + -- Bottom block (stats) + self:InspectionVGUIStats(contentpanel) + + -- Attachments + self:InspectionVGUIAttachments(contentpanel) + + hook.Run("TFA_InspectVGUI_Finish", self, TFA_INSPECTIONPANEL, contentpanel) +end + +function SWEP:DoInspectionDerma() + if not IsValid(TFA_INSPECTIONPANEL) and self:GetInspectingProgress() > 0.01 then + self:GenerateInspectionDerma() + end + + if not IsValid(TFA_INSPECTIONPANEL) then return end + if not self:OwnerIsValid() then return end +end + +cvars.AddChangeCallback("gmod_language", function(convar, oldvalue, newvalue) + if oldvalue == newvalue then return end + + if IsValid(TFA_INSPECTIONPANEL) then + TFA_INSPECTIONPANEL:Remove() + end +end, "TFA_INSPECTIONPANEL_LANGCHECK") + +local crosscol = Color(255, 255, 255, 255) +local crossa_cvar = GetConVar("cl_tfa_hud_crosshair_color_a") +local outa_cvar = GetConVar("cl_tfa_hud_crosshair_outline_color_a") +local crosscustomenable_cvar = GetConVar("cl_tfa_hud_crosshair_enable_custom") +local crossr_cvar = GetConVar("cl_tfa_hud_crosshair_color_r") +local crossg_cvar = GetConVar("cl_tfa_hud_crosshair_color_g") +local crossb_cvar = GetConVar("cl_tfa_hud_crosshair_color_b") +local crosslen_cvar = GetConVar("cl_tfa_hud_crosshair_length") +local crosshairwidth_cvar = GetConVar("cl_tfa_hud_crosshair_width") +local drawdot_cvar = GetConVar("cl_tfa_hud_crosshair_dot") +local clen_usepixels = GetConVar("cl_tfa_hud_crosshair_length_use_pixels") +local outline_enabled_cvar = GetConVar("cl_tfa_hud_crosshair_outline_enabled") +local outr_cvar = GetConVar("cl_tfa_hud_crosshair_outline_color_r") +local outg_cvar = GetConVar("cl_tfa_hud_crosshair_outline_color_g") +local outb_cvar = GetConVar("cl_tfa_hud_crosshair_outline_color_b") +local outlinewidth_cvar = GetConVar("cl_tfa_hud_crosshair_outline_width") +local hudenabled_cvar = GetConVar("cl_tfa_hud_enabled") +local hudfallback_cvar = GetConVar("cl_tfa_hud_fallback_enabled") +local cgapscale_cvar = GetConVar("cl_tfa_hud_crosshair_gap_scale") +local tricross_cvar = GetConVar("cl_tfa_hud_crosshair_triangular") + +--[[ +Function Name: DrawHUD +Syntax: self:DrawHUD(). +Returns: Nothing. +Notes: Used to draw the HUD. Can you read? +Purpose: HUD +]] +-- +function SWEP:DrawHUD() + -- Inspection Derma + self:DoInspectionDerma() + -- 3D2D Ammo + self:DrawHUDAmmo() --so it's swappable easily + + self:DrawKeyBindHints() +end + +function SWEP:DrawHUDBackground() + --Scope Overlay + if self:GetIronSightsProgress() > self:GetStatL("ScopeOverlayThreshold") and self:GetStatL("Scoped") then + self:DrawScopeOverlay() + end +end + +function SWEP:DrawHUD3D2D() +end + +local draw = draw +local cam = cam +local surface = surface +local render = render +local Vector = Vector +local Matrix = Matrix +local TFA = TFA +local math = math + +local function ColorAlpha(color_in, new_alpha) + if color_in.a == new_alpha then return color_in end + return Color(color_in.r, color_in.g, color_in.b, new_alpha) +end + +local targ, lactive = 0, -1 +local targbool = false +local hudhangtime_cvar = GetConVar("cl_tfa_hud_hangtime") +local hudfade_cvar = GetConVar("cl_tfa_hud_ammodata_fadein") +local lfm, fm = 0, 0 + +SWEP.CLAmmoProgress = 0 +SWEP.TextCol = Color(255, 255, 255, 255) --Primary text color +SWEP.TextColContrast = Color(32, 32, 32, 255) --Secondary Text Color (used for shadow) + +local TFAHudHide = { + CHudAmmo = true, + CHudSecondaryAmmo = true +} + +function SWEP:HUDShouldDraw(name) + if (TFAHudHide[name] and hudenabled_cvar:GetBool()) then + return false + end +end + +function SWEP:DrawFallbackHUD() + if not hudfallback_cvar:GetBool() or hook.Run("HUDShouldDraw", "TFA_HUDFallback") == false then return end + if self:GetMaxClip1() <= 0 then return end + + local fmn = string.upper(self:GetFireModeName() .. (#self:GetStatL("FireModes") > 2 and " | +" or "")) + + surface.SetFont("TFASleekSmall") + local w, h = surface.GetTextSize(fmn) + + DrawTextShadowed(fmn, "TFASleekSmall", ScrW() * .5 - w * .5, ScaleH(1017), self.TextCol, ScaleH(2)) +end + +function SWEP:DrawHUDAmmo() + local self2 = self:GetTable() + local stat = self2.GetStatus(self) + + if self2.GetStatL(self, "BoltAction") then + if stat == TFA.Enum.STATUS_SHOOTING then + if not self2.LastBoltShoot then + self2.LastBoltShoot = l_CT() + end + elseif self2.LastBoltShoot then + self2.LastBoltShoot = nil + end + end + + if not hudenabled_cvar:GetBool() or hook.Run("HUDShouldDraw", "TFA_HUDAmmo") == false then + self:DrawFallbackHUD() + return + end + + fm = self:GetFireMode() + targbool = (not TFA.Enum.HUDDisabledStatus[stat]) or fm ~= lfm + targbool = targbool or (stat == TFA.Enum.STATUS_SHOOTING and self2.LastBoltShoot and l_CT() > self2.LastBoltShoot + self2.GetStatL(self, "BoltTimerOffset")) + targbool = targbool or (self2.GetStatL(self, "PumpAction") and (stat == TFA.Enum.STATUS_PUMP or (stat == TFA.Enum.STATUS_SHOOTING and self:Clip1() == 0))) + targbool = targbool or (stat == TFA.Enum.STATUS_FIDGET) + + targ = targbool and 1 or 0 + lfm = fm + + if targ == 1 then + lactive = RealTime() + elseif RealTime() < lactive + hudhangtime_cvar:GetFloat() then + targ = 1 + elseif self:GetOwner():KeyDown(IN_RELOAD) then + targ = 1 + end + + self2.CLAmmoProgress = math.Approach(self2.CLAmmoProgress, targ, (targ - self2.CLAmmoProgress) * RealFrameTime() * 2 / hudfade_cvar:GetFloat()) + + local myalpha = 225 * self2.CLAmmoProgress + if myalpha < 1 then return end + local amn = self2.GetStatL(self, "Primary.Ammo") + if not amn then return end + if amn == "none" or amn == "" then return end + local mzpos = self:GetMuzzlePos() + + if self2.GetStatL(self, "IsAkimbo") then + self2.MuzzleAttachmentRaw = self2.MuzzleAttachmentRaw2 or 1 + end + + if self2.GetHidden(self) then return end + + local xx, yy + + if mzpos and mzpos.Pos then + local pos = mzpos.Pos + local textsize = self2.textsize and self2.textsize or 1 + local pl = IsValid(self:GetOwner()) and self:GetOwner() or LocalPlayer() + local ang = pl:EyeAngles() --(angpos.Ang):Up():Angle() + ang:RotateAroundAxis(ang:Right(), 90) + ang:RotateAroundAxis(ang:Up(), -90) + ang:RotateAroundAxis(ang:Forward(), 0) + pos = pos + ang:Right() * (self2.textupoffset and self2.textupoffset or -2 * (textsize / 1)) + pos = pos + ang:Up() * (self2.textfwdoffset and self2.textfwdoffset or 0 * (textsize / 1)) + pos = pos + ang:Forward() * (self2.textrightoffset and self2.textrightoffset or -1 * (textsize / 1)) + cam.Start3D() + local postoscreen = pos:ToScreen() + cam.End3D() + xx = postoscreen.x + yy = postoscreen.y + else -- fallback to pseudo-3d if no muzzle + xx, yy = ScrW() * .65, ScrH() * .6 + end + + local v, newx, newy, newalpha = hook.Run("TFA_DrawHUDAmmo", self, xx, yy, myalpha) + if v ~= nil then + if v then + xx = newx or xx + yy = newy or yy + myalpha = newalpha or myalpha + else + return + end + end + + if self:GetInspectingProgress() < 0.01 and self2.GetStatL(self, "Primary.Ammo") ~= "" and self2.GetStatL(self, "Primary.Ammo") ~= 0 then + local str, clipstr + + if self2.GetStatL(self, "Primary.ClipSize") and self2.GetStatL(self, "Primary.ClipSize") ~= -1 then + clipstr = language.GetPhrase("tfa.hud.ammo.clip1") + + if self2.GetStatL(self, "IsAkimbo") and self2.GetStatL(self, "EnableAkimboHUD") ~= false then + if self2.Akimbo_Inverted then + str = clipstr:format(math.ceil(self:Clip1() / 2)) + + if (self:Clip1() > self2.GetStatL(self, "Primary.ClipSize")) then + str = clipstr:format(math.ceil(self:Clip1() / 2) - 1 .. " + " .. (math.ceil(self:Clip1() / 2) - math.ceil(self2.GetStatL(self, "Primary.ClipSize") / 2))) + end + else + str = clipstr:format(math.floor(self:Clip1() / 2)) + + if (math.floor(self:Clip1() / 2) > math.floor(self2.GetStatL(self, "Primary.ClipSize") / 2)) then + str = clipstr:format(math.floor(self:Clip1() / 2) - 1 .. " + " .. (math.floor(self:Clip1() / 2) - math.floor(self2.GetStatL(self, "Primary.ClipSize") / 2))) + end + end + else + str = clipstr:format(self:Clip1()) + + if (self:Clip1() > self2.GetStatL(self, "Primary.ClipSize")) then + str = clipstr:format(self2.GetStatL(self, "Primary.ClipSize") .. " + " .. (self:Clip1() - self2.GetStatL(self, "Primary.ClipSize"))) + end + end + + draw.DrawText(str, "TFASleek", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleek", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + str = language.GetPhrase("tfa.hud.ammo.reserve1"):format(self2.Ammo1(self)) + yy = yy + TFA.Fonts.SleekHeight + xx = xx - TFA.Fonts.SleekHeight / 3 + draw.DrawText(str, "TFASleekMedium", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleekMedium", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + yy = yy + TFA.Fonts.SleekHeightMedium + xx = xx - TFA.Fonts.SleekHeightMedium / 3 + else + str = language.GetPhrase("tfa.hud.ammo1"):format(self2.Ammo1(self)) + draw.DrawText(str, "TFASleek", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleek", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + yy = yy + TFA.Fonts.SleekHeightMedium + xx = xx - TFA.Fonts.SleekHeightMedium / 3 + end + + str = string.upper(self:GetFireModeName() .. (#self2.GetStatL(self, "FireModes") > 2 and " | +" or "")) + + if self:IsJammed() then + str = str .. "\n" .. language.GetPhrase("tfa.hud.jammed") + end + + draw.DrawText(str, "TFASleekSmall", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleekSmall", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + yy = yy + TFA.Fonts.SleekHeightSmall + xx = xx - TFA.Fonts.SleekHeightSmall / 3 + + if self2.GetStatL(self, "IsAkimbo") and self2.GetStatL(self, "EnableAkimboHUD") ~= false then + local angpos2 = self:GetOwner():ShouldDrawLocalPlayer() and self:GetAttachment(2) or self2.OwnerViewModel:GetAttachment(2) + + if angpos2 then + local pos2 = angpos2.Pos + local ts2 = pos2:ToScreen() + + xx, yy = ts2.x, ts2.y + else + xx, yy = ScrW() * .35, ScrH() * .6 + end + + if self2.GetStatL(self, "Primary.ClipSize") and self2.GetStatL(self, "Primary.ClipSize") ~= -1 then + clipstr = language.GetPhrase("tfa.hud.ammo.clip1") + + if self2.Akimbo_Inverted then + str = clipstr:format(math.floor(self:Clip1() / 2)) + + if (math.floor(self:Clip1() / 2) > math.floor(self2.GetStatL(self, "Primary.ClipSize") / 2)) then + str = clipstr:format(math.floor(self:Clip1() / 2) - 1 .. " + " .. (math.floor(self:Clip1() / 2) - math.floor(self2.GetStatL(self, "Primary.ClipSize") / 2))) + end + else + str = clipstr:format(math.ceil(self:Clip1() / 2)) + + if (self:Clip1() > self2.GetStatL(self, "Primary.ClipSize")) then + str = clipstr:format(math.ceil(self:Clip1() / 2) - 1 .. " + " .. (math.ceil(self:Clip1() / 2) - math.ceil(self2.GetStatL(self, "Primary.ClipSize") / 2))) + end + end + + draw.DrawText(str, "TFASleek", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleek", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + str = language.GetPhrase("tfa.hud.ammo.reserve1"):format(self2.Ammo1(self)) + yy = yy + TFA.Fonts.SleekHeight + xx = xx - TFA.Fonts.SleekHeight / 3 + draw.DrawText(str, "TFASleekMedium", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleekMedium", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + yy = yy + TFA.Fonts.SleekHeightMedium + xx = xx - TFA.Fonts.SleekHeightMedium / 3 + else + str = language.GetPhrase("tfa.hud.ammo1"):format(self2.Ammo1(self)) + draw.DrawText(str, "TFASleek", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleek", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + yy = yy + TFA.Fonts.SleekHeightMedium + xx = xx - TFA.Fonts.SleekHeightMedium / 3 + end + + str = string.upper(self:GetFireModeName() .. (#self2.FireModes > 2 and " | +" or "")) + draw.DrawText(str, "TFASleekSmall", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleekSmall", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + end + + if self2.GetStatL(self, "Secondary.Ammo") and self2.GetStatL(self, "Secondary.Ammo") ~= "" and self2.GetStatL(self, "Secondary.Ammo") ~= "none" and self2.GetStatL(self, "Secondary.Ammo") ~= 0 and not self2.GetStatL(self, "IsAkimbo") then + if self2.GetStatL(self, "Secondary.ClipSize") and self2.GetStatL(self, "Secondary.ClipSize") ~= -1 then + clipstr = language.GetPhrase("tfa.hud.ammo.clip2") + str = (self:Clip2() > self2.GetStatL(self, "Secondary.ClipSize")) and clipstr:format(self2.GetStatL(self, "Secondary.ClipSize") .. " + " .. (self:Clip2() - self2.GetStatL(self, "Primary.ClipSize"))) or clipstr:format(self:Clip2()) + draw.DrawText(str, "TFASleekSmall", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleekSmall", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + str = language.GetPhrase("tfa.hud.ammo.reserve2"):format(self2.Ammo2(self)) + yy = yy + TFA.Fonts.SleekHeightSmall + xx = xx - TFA.Fonts.SleekHeightSmall / 3 + draw.DrawText(str, "TFASleekSmall", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleekSmall", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + else + str = language.GetPhrase("tfa.hud.ammo2"):format(self2.Ammo2(self)) + draw.DrawText(str, "TFASleekSmall", xx + 1, yy + 1, ColorAlpha(self2.TextColContrast, myalpha), TEXT_ALIGN_RIGHT) + draw.DrawText(str, "TFASleekSmall", xx, yy, ColorAlpha(self2.TextCol, myalpha), TEXT_ALIGN_RIGHT) + end + end + end +end + +local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy") +local cl_tfa_hud_crosshair_pump = GetConVar("cl_tfa_hud_crosshair_pump") +local sv_tfa_fixed_crosshair = GetConVar("sv_tfa_fixed_crosshair") + +local crosshairMatrix = Matrix() +local crosshairMatrixLeft = Matrix() +local crosshairMatrixRight = Matrix() +local crosshairRotation = Angle() + +local pixelperfectshift = Vector(-0.5) + +function SWEP:CalculateCrosshairConeRecoil() + return self:GetStatL("CrosshairConeRecoilOverride", false) or self:CalculateConeRecoil() +end + +function SWEP:DoDrawCrosshair() + local self2 = self:GetTable() + local x, y + + if not self2.ratios_calc or not self2.DrawCrosshairDefault then return true end + if self2.GetHolding(self) then return true end + + local stat = self2.GetStatus(self) + + if not crosscustomenable_cvar:GetBool() then + return TFA.Enum.ReloadStatus[stat] or math.min(1 - (self2.IronSightsProgressUnpredicted2 or self:GetIronSightsProgress()), 1 - self:GetSprintProgress(), 1 - self:GetInspectingProgress()) <= 0.5 + end + + self2.clrelp = self2.clrelp or 0 + self2.clrelp = math.Approach( + self2.clrelp, + TFA.Enum.ReloadStatus[stat] and 0 or 1, + ((TFA.Enum.ReloadStatus[stat] and 0 or 1) - self2.clrelp) * RealFrameTime() * 7) + + local crossa = crossa_cvar:GetFloat() * + math.pow(math.min(1 - (((self2.IronSightsProgressUnpredicted2 or self:GetIronSightsProgress()) and + not self2.GetStatL(self, "DrawCrosshairIronSights")) and (self2.IronSightsProgressUnpredicted2 or self:GetIronSightsProgress()) or 0), + 1 - self:GetSprintProgress(), + 1 - self:GetInspectingProgress(), + self2.clrelp), + 2) + + local outa = outa_cvar:GetFloat() * + math.pow(math.min(1 - (((self2.IronSightsProgressUnpredicted2 or self:GetIronSightsProgress()) and + not self2.GetStatL(self, "DrawCrosshairIronSights")) and (self2.IronSightsProgressUnpredicted2 or self:GetIronSightsProgress()) or 0), + 1 - self:GetSprintProgress(), + 1 - self:GetInspectingProgress(), + self2.clrelp), + 2) + + local ply = LocalPlayer() + if not ply:IsValid() or self:GetOwner() ~= ply then return false end + + if not ply.interpposx then + ply.interpposx = ScrW() / 2 + end + + if not ply.interpposy then + ply.interpposy = ScrH() / 2 + end + + local tr = {} + tr.start = ply:GetShootPos() + tr.endpos = tr.start + ply:GetAimVector() * 0x7FFF + tr.filter = ply + tr.mask = MASK_SHOT + local traceres = util.TraceLine(tr) + local targent = traceres.Entity + + -- If we're drawing the local player, draw the crosshair where they're aiming + -- instead of in the center of the screen. + if self:GetOwner():ShouldDrawLocalPlayer() and not ply:GetNW2Bool("ThirtOTS", false) then + local coords = traceres.HitPos:ToScreen() + coords.x = math.Clamp(coords.x, 0, ScrW()) + coords.y = math.Clamp(coords.y, 0, ScrH()) + ply.interpposx = math.Approach(ply.interpposx, coords.x, (ply.interpposx - coords.x) * RealFrameTime() * 7.5) + ply.interpposy = math.Approach(ply.interpposy, coords.y, (ply.interpposy - coords.y) * RealFrameTime() * 7.5) + x, y = ply.interpposx, ply.interpposy + -- Center of screen + elseif sv_tfa_fixed_crosshair:GetBool() then + x, y = ScrW() / 2, ScrH() / 2 + else + tr.endpos = tr.start + self:GetAimAngle():Forward() * 0x7FFF + local pos = util.TraceLine(tr).HitPos:ToScreen() + x, y = pos.x, pos.y + end + + TFA.LastCrosshairPosX, TFA.LastCrosshairPosY = x, y + + local v = hook.Run("TFA_DrawCrosshair", self, x, y) + + if v ~= nil then + return v + end + + local s_cone = self:CalculateCrosshairConeRecoil() + + if not self2.selftbl then + self2.selftbl = {ply, self} + end + + local crossr, crossg, crossb, crosslen, crosshairwidth, drawdot, teamcol + teamcol = self2.GetTeamColor(self, targent) + crossr = crossr_cvar:GetFloat() + crossg = crossg_cvar:GetFloat() + crossb = crossb_cvar:GetFloat() + crosslen = crosslen_cvar:GetFloat() * 0.01 + crosscol.r = crossr + crosscol.g = crossg + crosscol.b = crossb + crosscol.a = crossa + crosscol = ColorMix(crosscol, teamcol, 1, CMIX_MULT) + crossr = crosscol.r + crossg = crosscol.g + crossb = crosscol.b + crossa = crosscol.a + crosshairwidth = crosshairwidth_cvar:GetFloat() + drawdot = drawdot_cvar:GetBool() + local scale = (s_cone * 90) / self:GetOwner():GetFOV() * ScrH() / 1.44 * cgapscale_cvar:GetFloat() + + if self:GetSprintProgress() >= 0.1 and not self:GetStatL("AllowSprintAttack", false) then + scale = scale * (1 + TFA.Cubic(self:GetSprintProgress() - 0.1) * 6) + end + + if self2.clrelp < 0.9 then + scale = scale * Lerp(TFA.Cubic(0.9 - self2.clrelp) * 1.111, 1, 8) + end + + local gap = math.Round(scale / 2) * 2 + local length + + if not clen_usepixels:GetBool() then + length = gap + ScrH() * 1.777 * crosslen + else + length = gap + crosslen * 100 + end + + local extraRotation = 0 + local cPos = Vector(x, y) + + if stat == TFA.Enum.STATUS_PUMP and cl_tfa_hud_crosshair_pump:GetBool() then + if tricross_cvar:GetBool() then + extraRotation = TFA.Quintic(self:GetStatusProgress(true)) + local mul = 360 + extraRotation = extraRotation * mul + else + extraRotation = TFA.Quintic(TFA.Cosine(self:GetStatusProgress(true))) + local mul = -180 + + if extraRotation < 0.5 then + extraRotation = extraRotation * mul + else + extraRotation = (1 - extraRotation) * mul + end + end + end + + extraRotation = extraRotation - EyeAngles().r + + crosshairMatrix:Identity() + crosshairMatrix:Translate(cPos) + crosshairRotation.y = extraRotation + crosshairMatrix:Rotate(crosshairRotation) + + if tricross_cvar:GetBool() then + crosshairMatrixLeft:Identity() + crosshairMatrixRight:Identity() + + crosshairMatrixLeft:Translate(cPos) + crosshairMatrixRight:Translate(cPos) + + crosshairRotation.y = extraRotation + 135 + crosshairMatrixRight:SetAngles(crosshairRotation) + crosshairRotation.y = extraRotation - 135 + crosshairMatrixLeft:SetAngles(crosshairRotation) + + if crosshairwidth % 2 ~= 0 then + crosshairMatrixLeft:Translate(pixelperfectshift) + crosshairMatrixRight:Translate(pixelperfectshift) + end + end + + DisableClipping(true) + + render.PushFilterMag(TEXFILTER.ANISOTROPIC) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + + --Outline + if outline_enabled_cvar:GetBool() then + local outr, outg, outb, outlinewidth + outr = outr_cvar:GetFloat() + outg = outg_cvar:GetFloat() + outb = outb_cvar:GetFloat() + outlinewidth = outlinewidth_cvar:GetFloat() + + cam.PushModelMatrix(crosshairMatrix) + surface.SetDrawColor(outr, outg, outb, outa) + + local tHeight = math.Round(length - gap + outlinewidth * 2) + crosshairwidth + + local tX, tY, tWidth = + math.Round(-outlinewidth) - crosshairwidth / 2, + -gap * self:GetStatL("Primary.SpreadBiasPitch") - tHeight + outlinewidth, + math.Round(outlinewidth * 2) + crosshairwidth + + -- Top + surface.DrawRect(tX, tY, tWidth, tHeight) + cam.PopModelMatrix() + + if tricross_cvar:GetBool() then + tY = -gap - tHeight + + cam.PushModelMatrix(crosshairMatrixLeft) + surface.DrawRect(tX, tY + outlinewidth, tWidth, tHeight) + cam.PopModelMatrix() + + cam.PushModelMatrix(crosshairMatrixRight) + surface.DrawRect(tX, tY + outlinewidth, tWidth, tHeight) + cam.PopModelMatrix() + else + cam.PushModelMatrix(crosshairMatrix) + + local width = math.Round(length - gap + outlinewidth * 2) + crosshairwidth + local realgap = math.Round(gap * self:GetStatL("Primary.SpreadBiasYaw") - outlinewidth) - crosshairwidth / 2 + + -- Left + surface.DrawRect( + -realgap - width, + math.Round(-outlinewidth) - crosshairwidth / 2, + width, + math.Round(outlinewidth * 2) + crosshairwidth) + + -- Right + surface.DrawRect( + realgap, + math.Round(-outlinewidth) - crosshairwidth / 2, + width, + math.Round(outlinewidth * 2) + crosshairwidth) + + -- Bottom + surface.DrawRect( + math.Round(-outlinewidth) - crosshairwidth / 2, + math.Round(gap * self:GetStatL("Primary.SpreadBiasPitch") - outlinewidth) - crosshairwidth / 2, + math.Round(outlinewidth * 2) + crosshairwidth, + math.Round(length - gap + outlinewidth * 2) + crosshairwidth) + + cam.PopModelMatrix() + end + + if drawdot then + cam.PushModelMatrix(crosshairMatrix) + surface.DrawRect(-math.Round((crosshairwidth - 1) / 2) - math.Round(outlinewidth), -math.Round((crosshairwidth - 1) / 2) - math.Round(outlinewidth), math.Round(outlinewidth * 2) + crosshairwidth, math.Round(outlinewidth * 2) + crosshairwidth) --dot + cam.PopModelMatrix() + end + end + + --Main Crosshair + cam.PushModelMatrix(crosshairMatrix) + surface.SetDrawColor(crossr, crossg, crossb, crossa) + + local tHeight = math.Round(length - gap) + crosshairwidth + + local tX, tY, tWidth = + -crosshairwidth / 2, + math.Round(-gap * self:GetStatL("Primary.SpreadBiasPitch") - tHeight), + crosshairwidth + + -- Top + surface.DrawRect(tX, tY, tWidth, tHeight) + cam.PopModelMatrix() + + if tricross_cvar:GetBool() then + local xhl = math.Round(length - gap) + crosshairwidth + + tY = math.Round(-gap - tHeight) + + cam.PushModelMatrix(crosshairMatrixLeft) + surface.DrawRect(tX, tY, tWidth, tHeight) + cam.PopModelMatrix() + + cam.PushModelMatrix(crosshairMatrixRight) + surface.DrawRect(tX, tY, tWidth, tHeight) + cam.PopModelMatrix() + else + cam.PushModelMatrix(crosshairMatrix) + + local width = math.Round(length - gap) + crosshairwidth + local realgap = math.Round(gap * self:GetStatL("Primary.SpreadBiasYaw")) - crosshairwidth / 2 + + -- Left + surface.DrawRect( + -realgap - width, + -crosshairwidth / 2, + width, + crosshairwidth) + + -- Right + surface.DrawRect( + realgap, + -crosshairwidth / 2, + width, + crosshairwidth) + + -- Bottom + surface.DrawRect( + -crosshairwidth / 2, + math.Round(gap * self:GetStatL("Primary.SpreadBiasPitch")) - crosshairwidth / 2, + crosshairwidth, + math.Round(length - gap) + crosshairwidth) + + cam.PopModelMatrix() + end + + render.PopFilterMag() + render.PopFilterMin() + + if drawdot then + cam.PushModelMatrix(crosshairMatrix) + surface.DrawRect(-math.Round((crosshairwidth - 1) / 2), -math.Round((crosshairwidth - 1) / 2), crosshairwidth, crosshairwidth) --dot + cam.PopModelMatrix() + end + + DisableClipping(false) + + return true +end + +function SWEP:DrawScopeOverlay() + if hook.Run("TFA_DrawScopeOverlay", self) == true then return end + local self2 = self:GetTable() + + local tbl + + if self2.GetStatL(self, "Secondary.UseACOG") then + tbl = TFA_SCOPE_ACOG + end + + if self2.GetStatL(self, "Secondary.UseMilDot") then + tbl = TFA_SCOPE_MILDOT + end + + if self2.GetStatL(self, "Secondary.UseSVD") then + tbl = TFA_SCOPE_SVD + end + + if self2.GetStatL(self, "Secondary.UseParabolic") then + tbl = TFA_SCOPE_PARABOLIC + end + + if self2.GetStatL(self, "Secondary.UseElcan") then + tbl = TFA_SCOPE_ELCAN + end + + if self2.GetStatL(self, "Secondary.UseGreenDuplex") then + tbl = TFA_SCOPE_GREENDUPLEX + end + + if self2.GetStatL(self, "Secondary.UseAimpoint") then + tbl = TFA_SCOPE_AIMPOINT + end + + if self2.GetStatL(self, "Secondary.UseMatador") then + tbl = TFA_SCOPE_MATADOR + end + + if self2.GetStatL(self, "Secondary.ScopeTable") then + tbl = self2.GetStatL(self, "Secondary.ScopeTable") + end + + if not tbl then + tbl = TFA_SCOPE_MILDOT + end + + local w, h = ScrW(), ScrH() + + for k, v in pairs(tbl) do + local dimension = h + + if k == "ScopeBorder" then + if istable(v) then + surface.SetDrawColor(v) + else + surface.SetDrawColor(color_black) + end + + surface.DrawRect(0, 0, w / 2 - dimension / 2, dimension) + surface.DrawRect(w / 2 + dimension / 2, 0, w / 2 - dimension / 2, dimension) + elseif k == "ScopeMaterial" then + surface.SetMaterial(v) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(w / 2 - dimension / 2, (h - dimension) / 2, dimension, dimension) + elseif k == "ScopeOverlay" then + surface.SetMaterial(v) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(0, 0, w, h) + elseif k == "ScopeCrosshair" then + local t = type(v) + + if t == "IMaterial" then + surface.SetMaterial(v) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(w / 2 - dimension / 4, h / 2 - dimension / 4, dimension / 2, dimension / 2) + elseif t == "table" then + if not v.cached then + v.cached = true + v.r = v.r or v.x or v[1] or 0 + v.g = v.g or v.y or v[2] or v[1] or 0 + v.b = v.b or v.z or v[3] or v[1] or 0 + v.a = v.a or v[4] or 255 + v.s = v.Scale or v.scale or v.s or 0.25 + end + + surface.SetDrawColor(v.r, v.g, v.b, v.a) + + if v.Material then + surface.SetMaterial(v.Material) + surface.DrawTexturedRect(w / 2 - dimension * v.s / 2, h / 2 - dimension * v.s / 2, dimension * v.s, dimension * v.s) + elseif v.Texture then + surface.SetTexture(v.Texture) + surface.DrawTexturedRect(w / 2 - dimension * v.s / 2, h / 2 - dimension * v.s / 2, dimension * v.s, dimension * v.s) + else + surface.DrawRect(w / 2 - dimension * v.s / 2, h / 2, dimension * v.s, 1) + surface.DrawRect(w / 2, h / 2 - dimension * v.s / 2, 1, dimension * v.s) + end + end + else + if k == "scopetex" then + dimension = dimension * self:GetStatL("ScopeScale") ^ 2 * TFA_SCOPE_SCOPESCALE + elseif k == "reticletex" then + dimension = dimension * (self:GetStatL("ReticleScale") and self:GetStatL("ReticleScale") or 1) ^ 2 * (TFA_SCOPE_RETICLESCALE and TFA_SCOPE_RETICLESCALE or 1) + else + dimension = dimension * self:GetStatL("ReticleScale") ^ 2 * TFA_SCOPE_DOTSCALE + end + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetTexture(v) + surface.DrawTexturedRect(w / 2 - dimension / 2, (h - dimension) / 2, dimension, dimension) + end + end +end + +SWEP.KeyNameOverrides = { + ["MOUSE1"] = "tfa.keys.lmb", + ["MOUSE2"] = "tfa.keys.rmb", + ["MOUSE3"] = "tfa.keys.mmb", +} + +function SWEP:GetKeyBind(binds, altbind) + if type(binds) == "string" then + binds = {binds} + end + + if altbind then + local cvar = GetConVar("cl_tfa_keys_" .. altbind) + + if cvar and cvar:GetInt() > 0 then + local key = input.GetKeyName(cvar:GetInt()) + + return language.GetPhrase(self.KeyNameOverrides[key] or key:upper()) + end + end + + local result = {} + for _,bind in ipairs(binds) do + local binding = input.LookupBinding(bind, true) or input.LookupBinding(bind) + + table.insert(result, binding and language.GetPhrase(self.KeyNameOverrides[binding] or binding:upper()) or bind) + end + + return result +end + +local cv_cm = GetConVar("sv_tfa_cmenu") +local cv_cm_key = GetConVar("sv_tfa_cmenu_key") +local cv_ironsights = GetConVar("sv_tfa_ironsights_enabled") + +function SWEP:PrePopulateKeyBindHints(keys) + return keys +end + +function SWEP:PostPopulateKeyBindHints(keys) + return keys +end + +function SWEP:PopulateKeyBindHints(keys) + keys = keys or {} + + keys = self:PrePopulateKeyBindHints(keys) or keys + + local CanAim = cv_ironsights:GetInt() and self:GetStatL("Secondary.IronSightsEnabled") + if CanAim then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.ironsights"), + keys = {self:GetKeyBind({"+attack2"})} + }) + end + + if self.BashBase then + if self:GetStatL("Secondary.CanBash") ~= false then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.meleebash"), + keys = {CanAim and self:GetKeyBind({"+zoom"}, "bash") or self:GetKeyBind("+attack2")} + }) + end + elseif self.AltAttack then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.altattack"), + keys = {CanAim and self:GetKeyBind({"+zoom"}, "bash") or self:GetKeyBind("+attack2")} + }) + end + + if cv_cm:GetBool() and cv_cm_key:GetInt() ~= 0 then + local kcode = cv_cm_key:GetInt() + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.customize"), + keys = {kcode > 0 and language.GetPhrase(input.GetKeyName(kcode)):upper() or self:GetKeyBind({"+menu_context"}, "customize")} + }) + end + + if self:GetActivityEnabled(ACT_VM_FIDGET) or self.InspectionActions then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.inspect"), + keys = {self:GetKeyBind({"+reload"}, "inspect")} + }) + end + + if self:GetStatL("SelectiveFire") then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.firemode"), + keys = {self:GetKeyBind({"+use", "+reload"}, "firemode")} + }) + end + + if not self.IsMelee and not self.IsBow and not self.IsKnife then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.safety"), + keys = {self:GetKeyBind({"+speed"}), self:GetKeyBind({"+use", "+reload"}, "firemode")} + }) + end + + if self:GetStatL("FlashlightAttachmentName") ~= nil or self:GetStatL("FlashlightAttachment", 0) > 0 then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.flashlight"), + keys = {self:GetKeyBind({"impulse 100"})} + }) + end + + if self:GetStatRawL("CanBeSilenced") then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.silencer"), + keys = {self:GetKeyBind({"+use", "+attack"}, "silencer")} + }) + end + + keys = self:PostPopulateKeyBindHints(keys) or keys + keys = hook.Run("TFA_PopulateKeyBindHints", self, keys) or keys + + if #keys > 0 then + table.insert(keys, { + label = language.GetPhrase("tfa.hint.keys.showkeys"), + keys = {self:GetKeyBind({"+showscores"})} + }) + end + + return keys +end + +local GradientMaterial = Material("gui/gradient") +-- SWEP.KeyBindHintAlphaLastUpdate = -1 + +local lastwep_kb +function SWEP:ResetKeyBindHintAlpha(force) + if (not force or force == "") and lastwep_kb == self then return end + lastwep_kb = self + + self.KeyBindHintAlphaLastUpdate = CurTime() +end + +local cv_hint_enabled = GetConVar("cl_tfa_hud_keybindhints_enabled") +local cv_hint_solidtime = GetConVar("cl_tfa_hud_keybindhints_solidtime") +local cv_hint_fadeintime = GetConVar("cl_tfa_hud_keybindhints_fadeintime") +local cv_hint_fadeouttime = GetConVar("cl_tfa_hud_keybindhints_fadeouttime") + +local HintAlpha = 0 +function SWEP:DrawKeyBindHints() + if self:GetOwner():KeyDown(IN_SCORE) or not self.KeyBindHintAlphaLastUpdate then + self:ResetKeyBindHintAlpha(true) + end + + local fadeout = CurTime() >= self.KeyBindHintAlphaLastUpdate + cv_hint_solidtime:GetFloat() + HintAlpha = math.Clamp(HintAlpha + (fadeout and FrameTime() * -1000 / cv_hint_fadeouttime:GetFloat() or FrameTime() * 1000 / cv_hint_fadeintime:GetFloat()), 0, 255) + + if not cv_hint_enabled:GetBool() or cv_hint_solidtime:GetFloat() <= 0 then return end + + local a = HintAlpha + if self:GetInspectingProgress() > 0 then + a = a * (1 - self:GetInspectingProgress()) + end + + if a <= 0 then return end + + local maincol, bgcol = ColorAlpha(INSPECTION_PRIMARYCOLOR, a), INSPECTION_BACKGROUND + local textfont = "TFA_INSPECTION_SMALL" + local textheight = TFA.Fonts.InspectionHeightSmall + + local x, y = ScrW() - ScaleH(80), ScrH() * .5 + local bgpad = ScaleH(8) + + local keystbl = self:PopulateKeyBindHints({}) + if #keystbl <= 0 then return end + + local keystr = {} + local TargetWidth = 0 + surface.SetFont(textfont) + for _, tbl in ipairs(keystbl) do + local key = "" + for k, v in ipairs(tbl.keys) do + tbl.keys[k] = type(v) == "table" and table.concat(v, " + ") or v + end + + local keytext = string.format("[%s] - %s", table.concat(tbl.keys, " + "), tbl.label) + + local tw, _ = surface.GetTextSize(keytext) + TargetWidth = math.max(tw, TargetWidth) + + table.insert(keystr, keytext) + end + x = x - TargetWidth + y = y - textheight * #keystr * .5 + + if hook.Run("TFA_PreDrawKeyBindHint", self, x, y, a, keystbl, keystr) == true then return end + + surface.SetDrawColor(bgcol.r, bgcol.g, bgcol.b, a) + surface.SetMaterial(GradientMaterial) + surface.DrawTexturedRectUV(x - bgpad, y - bgpad, TargetWidth + bgpad * 2, textheight * #keystr + bgpad * 2, 1, 0, 0, 1) + + for i, key in ipairs(keystr) do + TextShadowPaint({Text = key, Font = textfont, TextColor = maincol, TextPosX = x, TextPosY = y + (i - 1) * textheight}) + end + + hook.Run("TFA_PostDrawKeyBindHint", self, x, y, a, keystbl, keystr) +end + + +local fsin, icon +local matcache = {} + +function SWEP:DrawWeaponSelection(x, y, wide, tall, alpha) + local self2 = self:GetTable() + + surface.SetDrawColor(255, 255, 255, alpha) + + icon = self2.GetStatL(self, "WepSelectIcon_Override", self2.WepSelectIcon) + + if not icon then + self2.IconFix(self) + + return + end + + local ticon = type(icon) + + if ticon == "IMaterial" then + surface.SetMaterial(icon) + elseif ticon == "string" then + if not matcache[icon] then + matcache[icon] = Material(icon, "smooth noclamp") + end + + surface.SetMaterial(matcache[icon]) + else + surface.SetTexture(icon) + end + + fsin = self2.BounceWeaponIcon and math.sin( RealTime() * 10 ) * 5 or 0 + + -- Borders + y = y + 10 + x = x + 10 + wide = wide - 20 + + surface.DrawTexturedRect(x + fsin, y - fsin, wide - fsin * 2, wide / 2 + fsin) + + self2.PrintWeaponInfo(self, x + wide + 20, y + tall * 0.95, alpha) +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/laser.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/laser.lua new file mode 100644 index 0000000..ef31040 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/laser.lua @@ -0,0 +1,213 @@ +local vector_origin = Vector() + +local att, angpos, attname, elemname, targetent +local col = Color(255, 0, 0, 255) +local pc +local laserline +local laserdot +local laserFOV = 1.5 +local traceres + +local render = render +local Material = Material +local ProjectedTexture = ProjectedTexture +local math = math + +SWEP.LaserDistance = 12 * 50 -- default 50 feet +SWEP.LaserDistanceVisual = 12 * 4 --default 4 feet + +local function IsHolstering(wep) + if IsValid(wep) and TFA.Enum.HolsterStatus[wep:GetStatus()] then return true end + + return false +end + +function SWEP:DrawLaser(is_vm) + local self2 = self:GetTable() + + if not laserline then + laserline = Material(self2.LaserLine or "cable/smoke") + end + + if not laserdot then + laserdot = Material(self2.LaserDot or "effects/tfalaserdot") + end + + local ply = self:GetOwner() + if not IsValid(ply) then return end + + if ply:IsPlayer() then + local f = ply.GetNW2Vector or ply.GetNWVector + pc = f(ply, "TFALaserColor", vector_origin) + col.r = pc.x + col.g = pc.y + col.b = pc.z + else + col.r = 255 + col.g = 0 + col.b = 0 + end + + if is_vm then + if not self2.VMIV(self) then + self:CleanLaser() + + return + end + + targetent = self2.OwnerViewModel + elemname = self2.GetStatL(self, "LaserSight_VElement", self2.GetStatL(self, "LaserSight_Element")) + + local ViewModelElements = self:GetStatRaw("ViewModelElements", TFA.LatestDataVersion) + + if elemname and ViewModelElements[elemname] and IsValid(ViewModelElements[elemname].curmodel) then + targetent = ViewModelElements[elemname].curmodel + end + + att = self2.GetStatL(self, "LaserSightAttachment") + attname = self2.GetStatL(self, "LaserSightAttachmentName") + + if attname then + att = targetent:LookupAttachment(attname) + end + + if (not att) or att <= 0 then + self:CleanLaser() + + return + end + + angpos = targetent:GetAttachment(att) + + if not angpos then + self:CleanLaser() + + return + end + + if self2.LaserDotISMovement and self2.CLIronSightsProgress > 0 then + local isang = self2.GetStatL(self, "IronSightsAngle") + angpos.Ang:RotateAroundAxis(angpos.Ang:Right(), isang.y * (self2.ViewModelFlip and -1 or 1) * self2.CLIronSightsProgress) + angpos.Ang:RotateAroundAxis(angpos.Ang:Up(), -isang.x * self2.CLIronSightsProgress) + end + + local localProjAng = select(2, WorldToLocal(vector_origin, angpos.Ang, vector_origin, EyeAngles())) + localProjAng.p = localProjAng.p * ply:GetFOV() / self2.ViewModelFOV + localProjAng.y = localProjAng.y * ply:GetFOV() / self2.ViewModelFOV + local wsProjAng = select(2, LocalToWorld(vector_origin, localProjAng, vector_origin, EyeAngles())) --reprojection for trace angle + traceres = util.QuickTrace(ply:GetShootPos(), wsProjAng:Forward() * 999999, ply) + + if not IsValid(ply.TFALaserDot) and not IsHolstering(self) then + local lamp = ProjectedTexture() + ply.TFALaserDot = lamp + lamp:SetTexture(laserdot:GetString("$basetexture")) + lamp:SetFarZ(self2.LaserDistance) -- How far the light should shine + lamp:SetFOV(laserFOV) + lamp:SetPos(angpos.Pos) + lamp:SetAngles(angpos.Ang) + lamp:SetBrightness(5) + lamp:SetNearZ(1) + lamp:SetEnableShadows(false) + lamp:Update() + end + + local lamp = ply.TFALaserDot + + if IsValid(lamp) then + local lamppos = EyePos() + EyeAngles():Up() * 4 + local ang = (traceres.HitPos - lamppos):Angle() + self2.laserpos_old = traceres.HitPos + ang:RotateAroundAxis(ang:Forward(), math.Rand(-180, 180)) + lamp:SetPos(lamppos) + lamp:SetAngles(ang) + lamp:SetColor(col) + lamp:SetFOV(laserFOV * math.Rand(0.9, 1.1)) + lamp:Update() + end + + return + end + + targetent = self + + elemname = self2.GetStatL(self, "LaserSight_WElement", self2.GetStatL(self, "LaserSight_Element")) + + local WorldModelElements = self:GetStatRaw("WorldModelElements", TFA.LatestDataVersion) + + if elemname and WorldModelElements[elemname] and IsValid(WorldModelElements[elemname].curmodel) then + targetent = WorldModelElements[elemname].curmodel + end + + att = self2.GetStatL(self, "LaserSightAttachmentWorld", self2.GetStatL(self, "LaserSightAttachment")) + + attname = self2.GetStatL(self, "LaserSightAttachmentWorldName", self2.GetStatL(self, "LaserSightAttachmentName")) + + if attname then + att = targetent:LookupAttachment(attname) + end + + if (not att) or att <= 0 then + self:CleanLaser() + + return + end + + angpos = targetent:GetAttachment(att) + + if not angpos then + angpos = targetent:GetAttachment(1) + end + + if not angpos then + self:CleanLaser() + + return + end + + if not IsValid(ply.TFALaserDot) and not IsHolstering(self) then + local lamp = ProjectedTexture() + ply.TFALaserDot = lamp + lamp:SetTexture(laserdot:GetString("$basetexture")) + lamp:SetFarZ(self2.LaserDistance) -- How far the light should shine + lamp:SetFOV(laserFOV) + lamp:SetPos(angpos.Pos) + lamp:SetAngles(angpos.Ang) + lamp:SetBrightness(5) + lamp:SetNearZ(1) + lamp:SetEnableShadows(false) + lamp:Update() + end + + local lamp = ply.TFALaserDot + + if IsValid(lamp) then + local ang = angpos.Ang + ang:RotateAroundAxis(ang:Forward(), math.Rand(-180, 180)) + lamp:SetPos(angpos.Pos) + lamp:SetAngles(ang) + lamp:SetColor(col) + lamp:SetFOV(laserFOV * math.Rand(0.9, 1.1)) + lamp:Update() + end + + traceres = util.QuickTrace(angpos.Pos, angpos.Ang:Forward() * self2.LaserDistance, ply) + local hpos = traceres.StartPos + angpos.Ang:Forward() * math.min(traceres.HitPos:Distance(angpos.Pos), self2.LaserDistanceVisual ) + render.SetMaterial(laserline) + render.SetColorModulation(1, 1, 1) + render.StartBeam(2) + col.r = math.sqrt(col.r / 255) * 255 + col.g = math.sqrt(col.g / 255) * 255 + col.b = math.sqrt(col.b / 255) * 255 + render.AddBeam(angpos.Pos, self2.LaserBeamWidth or 0.25, 0, col) + col.a = 0 + render.AddBeam(hpos, 0, 0, col) + render.EndBeam() +end + +function SWEP:CleanLaser() + local ply = self:GetOwner() + + if IsValid(ply) and IsValid(ply.TFALaserDot) then + ply.TFALaserDot:Remove() + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/mods.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/mods.lua new file mode 100644 index 0000000..e2c942b --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/mods.lua @@ -0,0 +1,1329 @@ +--[[Thanks to Clavus. Like seriously, SCK was brilliant. Even though you didn't include a license anywhere I could find, it's only fit to credit you.]] +-- + +local vector_origin = Vector() + +--[[ +Function Name: InitMods +Syntax: self:InitMods(). Should be called only once for best performance. +Returns: Nothing. +Notes: Creates the VElements and WElements table, and sets up mods. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:InitMods() + --Create a new table for every weapon instance. + self.SWEPConstructionKit = true + + self.ViewModelElements = self:CPTbl(self.ViewModelElements) + self.WorldModelElements = self:CPTbl(self.WorldModelElements) + self.ViewModelBoneMods = self:CPTbl(self.ViewModelBoneMods) + + -- i have no idea how this gonna behave without that with SWEP Construction kit + -- so we gonna leave this thing alone and precache everything + self:CreateModels(self.ViewModelElements, true) -- create viewmodels + self:CreateModels(self.WorldModelElements) -- create worldmodels + + --Build the bones and such. + if self:OwnerIsValid() then + local vm = self.OwnerViewModel + + if IsValid(vm) then + --self:ResetBonePositions(vm) + if (self.ShowViewModel == nil or self.ShowViewModel) then + vm:SetColor(Color(255, 255, 255, 255)) + --This hides the viewmodel, FYI, lol. + else + vm:SetMaterial("Debug/hsv") + end + end + end +end + +--[[ +Function Name: UpdateProjectedTextures +Syntax: self:UpdateProjectedTextures(). Automatically called already. +Returns: Nothing. +Notes: This takes care of our flashlight and laser. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- + +function SWEP:UpdateProjectedTextures(view) + self:DrawLaser(view) + self:DrawFlashlight(view) +end + +--[[ +Function Name: ViewModelDrawn +Syntax: self:ViewModelDrawn(). Automatically called already. +Returns: Nothing. +Notes: This draws the mods. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:PreDrawViewModel(vm, wep, ply) + self:ProcessBodygroups() + + --vm:SetupBones() + + if self:GetHidden() then + render.SetBlend(0) + end +end + +SWEP.CameraAttachmentOffsets = {{"p", 0}, {"y", 0}, {"r", 0}} +SWEP.CameraAttachment = nil +SWEP.CameraAttachments = {"camera", "attach_camera", "view", "cam", "look"} +SWEP.CameraAngCache = nil +local tmpvec = Vector(0, 0, -2000) + +do + local reference_table + + local function rendersorter(a, b) + local ar, br = reference_table[a], reference_table[b] + + if ar == br then + return a > b + end + + return ar > br + end + + local function inc_references(lookup, name, entry, output, level) + output[name] = (output[name] or 0) + level + local elemother = lookup[entry.rel] + + if elemother then + inc_references(lookup, entry.rel, elemother, output, level + 1) + end + end + + function SWEP:RebuildModsRenderOrder() + self.vRenderOrder = {} + self.wRenderOrder = {} + self.VElementsBodygroupsCache = {} + self.WElementsBodygroupsCache = {} + + local ViewModelElements = self:GetStatRaw("ViewModelElements", TFA.LatestDataVersion) or {} + local WorldModelElements = self:GetStatRaw("WorldModelElements", TFA.LatestDataVersion) or {} + + if istable(ViewModelElements) then + local target = self.vRenderOrder + reference_table = {} + + for k, v in pairs(ViewModelElements) do + if v.type == "Model" then + table.insert(target, k) + inc_references(ViewModelElements, k, v, reference_table, 10000) + elseif v.type == "Sprite" or v.type == "Quad" or v.type == "Bodygroup" then + table.insert(target, k) + inc_references(ViewModelElements, k, v, reference_table, 1) + end + end + + table.sort(target, rendersorter) + end + + if istable(WorldModelElements) then + local target2 = self.wRenderOrder + reference_table = {} + + for k, v in pairs(WorldModelElements) do + if v.type == "Model" then + table.insert(target2, 1, k) + inc_references(WorldModelElements, k, v, reference_table, 10000) + elseif v.type == "Sprite" or v.type == "Quad" or v.type == "Bodygroup" then + table.insert(target2, k) + inc_references(WorldModelElements, k, v, reference_table, 1) + end + end + + table.sort(target2, rendersorter) + end + + return self.vRenderOrder, self.wRenderOrder + end +end + +function SWEP:RemoveModsRenderOrder() + self.vRenderOrder = nil +end + +local drawfn, drawself, fndrawpos, fndrawang, fndrawsize + +local function dodrawfn() + drawfn(drawself, fndrawpos, fndrawang, fndrawsize) +end + +local next_setup_bones = 0 + +function TFA._IncNextSetupBones() + next_setup_bones = next_setup_bones + 1 +end + +function TFA._GetNextSetupBones() + return next_setup_bones +end + +local mirror_scale = Vector(1, -1, 1) +local normal_scale = Vector(1, 1, 1) + +local mirror = Matrix() + +local DRAW_AND_SETUP = 0 +local ONLY_DRAW = 1 +local ONLY_SETUP = 2 + +local function draw_element_closure(self, self2, name, index, vm, ViewModelElements, element, nodraw) + if self2.GetStatL(self, "ViewModelElements." .. name .. ".active") == false then return end + if self2.TFA_IsDrawingStencilSights and self2.GetStatL(self, "StencilSight_VElement") == name then return end + + local pos, ang = self:GetBoneOrientation(ViewModelElements, element, vm, nil, true) + if not pos and not element.bonemerge then return end + + self:PrecacheElement(element, true) + + local model = element.curmodel + local sprite = element.spritemat + + local dodraw = nodraw == DRAW_AND_SETUP or nodraw == ONLY_DRAW + local dosetup = nodraw == DRAW_AND_SETUP or nodraw == ONLY_SETUP + + if element.type == "Model" and IsValid(model) then + if not element.bonemerge and dosetup then + mirror:Identity() + + if self2.ViewModelFlip then + model:SetPos(pos + ang:Forward() * element.pos.x - ang:Right() * element.pos.y + ang:Up() * element.pos.z) + + ang:RotateAroundAxis(ang:Up(), -element.angle.y) + ang:RotateAroundAxis(ang:Right(), element.angle.p) + ang:RotateAroundAxis(ang:Forward(), -element.angle.r) + + mirror:Scale(mirror_scale) + mirror:Scale(element.size) + else + model:SetPos(pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z) + + ang:RotateAroundAxis(ang:Up(), element.angle.y) + ang:RotateAroundAxis(ang:Right(), element.angle.p) + ang:RotateAroundAxis(ang:Forward(), element.angle.r) + + mirror:Scale(normal_scale) + mirror:Scale(element.size) + end + + model:SetAngles(ang) + model:EnableMatrix("RenderMultiply", mirror) + end + + if dodraw then + if element.surpresslightning then + render.SuppressEngineLighting(true) + end + + local material = self:GetStatL("ViewModelElements." .. name .. ".material") + + if not material or material == "" then + model:SetMaterial("") + elseif model:GetMaterial() ~= material then + model:SetMaterial(material) + end + + local skin = self:GetStatL("ViewModelElements." .. name .. ".skin") + + if skin and skin ~= model:GetSkin() then + model:SetSkin(skin) + end + + if not self2.SCKMaterialCached_V[name] then + self2.SCKMaterialCached_V[name] = true + + local materialtable = self:GetStatL("ViewModelElements." .. name .. ".materials", {}) + local entmats = table.GetKeys(model:GetMaterials()) + + for _, k in ipairs(entmats) do + model:SetSubMaterial(k - 1, materialtable[k] or "") + end + end + end + + if dosetup then + if not self2.VElementsBodygroupsCache[index] then + self2.VElementsBodygroupsCache[index] = #model:GetBodyGroups() - 1 + end + + if self2.VElementsBodygroupsCache[index] then + for _b = 0, self2.VElementsBodygroupsCache[index] do + local newbg = self2.GetStatL(self, "ViewModelElements." .. name .. ".bodygroup." .. _b, 0) -- names are not supported, use overridetable + + if model:GetBodygroup(_b) ~= newbg then + model:SetBodygroup(_b, newbg) + end + end + end + + if element.bonemerge then + model:SetPos(pos) + model:SetAngles(ang) + + if element.rel and ViewModelElements[element.rel] and IsValid(ViewModelElements[element.rel].curmodel) then + element.parModel = ViewModelElements[element.rel].curmodel + else + element.parModel = self2.OwnerViewModel or self + end + + if model:GetParent() ~= element.parModel then + model:SetParent(element.parModel) + end + + if not model:IsEffectActive(EF_BONEMERGE) then + model:AddEffects(EF_BONEMERGE) + model:AddEffects(EF_BONEMERGE_FASTCULL) + model:SetMoveType(MOVETYPE_NONE) + model:SetLocalPos(vector_origin) + model:SetLocalAngles(angle_zero) + end + elseif model:IsEffectActive(EF_BONEMERGE) then + model:RemoveEffects(EF_BONEMERGE) + model:SetParent(NULL) + end + end + + if dodraw then + render.SetColorModulation(element.color.r / 255, element.color.g / 255, element.color.b / 255) + render.SetBlend(element.color.a / 255) + end + + if dosetup and model.tfa_next_setup_bones ~= next_setup_bones then + model:InvalidateBoneCache() + model:SetupBones() + model.tfa_next_setup_bones = next_setup_bones + end + + if dodraw then + if self2.ViewModelFlip then + render.CullMode(MATERIAL_CULLMODE_CW) + end + + model:DrawModel() + + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if self2.ViewModelFlip then + render.CullMode(MATERIAL_CULLMODE_CCW) + end + + if element.surpresslightning then + render.SuppressEngineLighting(false) + end + end + elseif dodraw and element.type == "Sprite" and sprite then + local drawpos = pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z + render.SetMaterial(sprite) + render.DrawSprite(drawpos, element.size.x, element.size.y, element.color) + elseif dodraw and element.type == "Quad" and element.draw_func then + local drawpos = pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z + ang:RotateAroundAxis(ang:Up(), element.angle.y) + ang:RotateAroundAxis(ang:Right(), element.angle.p) + ang:RotateAroundAxis(ang:Forward(), element.angle.r) + + cam.Start3D2D(drawpos, ang, element.size) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + render.PushFilterMag(TEXFILTER.ANISOTROPIC) + + drawfn, drawself, fndrawpos, fndrawang, fndrawsize = element.draw_func, self, nil, nil, nil + ProtectedCall(dodrawfn) + + render.PopFilterMin() + render.PopFilterMag() + cam.End3D2D() + end +end + +function SWEP:ViewModelDrawn() + local self2 = self:GetTable() + render.SetBlend(1) + + if self2.DrawHands then + self2.DrawHands(self) + end + + local vm = self.OwnerViewModel + if not IsValid(vm) then return end + if not self:GetOwner().GetHands then return end + + if self2.UseHands then + local hands = self:GetOwner():GetHands() + + if IsValid(hands) then + if not self2.GetHidden(self) then + hands:SetParent(vm) + else + hands:SetParent(nil) + hands:SetPos(tmpvec) + end + end + end + + self2.UpdateBonePositions(self, vm) + + if not self2.CameraAttachment then + self2.CameraAttachment = -1 + + for _, v in ipairs(self2.CameraAttachments) do + local attid = vm:LookupAttachment(v) + + if attid and attid > 0 then + self2.CameraAttachment = attid + break + end + end + end + + if self2.CameraAttachment and self2.CameraAttachment > 0 then + local angpos = vm:GetAttachment(self2.CameraAttachment) + + if angpos and angpos.Ang then + local ftv = FrameTime() + local angv = angpos.Ang + local off = vm:WorldToLocalAngles(angv) + local spd = 15 + local cycl = vm:GetCycle() + self2.CameraAngCache = self2.CameraAngCache or off + + for _, v in pairs(self2.CameraAttachmentOffsets) do + local offtype = v[1] + local offang = v[2] + + if offtype == "p" then + off:RotateAroundAxis(off:Right(), offang) + elseif offtype == "y" then + off:RotateAroundAxis(off:Up(), offang) + elseif offtype == "r" then + off:RotateAroundAxis(off:Forward(), offang) + end + end + + if self2.ViewModelFlip then + off = Angle() + end + + local stat = self:GetStatus() + + if (stat == TFA.Enum.STATUS_DRAW) and cycl < 0.05 then + local mul = cycl / 0.05 + + self2.CameraAngCache.p = self2.CameraAngCache.p * mul + self2.CameraAngCache.y = self2.CameraAngCache.y * mul + self2.CameraAngCache.r = self2.CameraAngCache.r * mul + elseif TFA.Enum.HolsterStatus[stat] and cycl > 0.95 and not self2.GetStatL(self, "ProceduralHolsterEnabled") then + local mul = 1 - (cycl - 0.95) / 0.05 + + self2.CameraAngCache.p = self2.CameraAngCache.p * mul + self2.CameraAngCache.y = self2.CameraAngCache.y * mul + self2.CameraAngCache.r = self2.CameraAngCache.r * mul + end + + self2.CameraAngCache.p = math.Approach(self2.CameraAngCache.p, off.p, (self2.CameraAngCache.p - off.p) * ftv * spd) + self2.CameraAngCache.y = math.Approach(self2.CameraAngCache.y, off.y, (self2.CameraAngCache.y - off.y) * ftv * spd) + self2.CameraAngCache.r = math.Approach(self2.CameraAngCache.r, off.r, (self2.CameraAngCache.r - off.r) * ftv * spd) + else + self2.CameraAngCache.p = 0 + self2.CameraAngCache.y = 0 + self2.CameraAngCache.r = 0 + end + end + + local ViewModelElements = self:GetStatRawL("ViewModelElements") or {} + local ViewModelBodygroups = self:GetStatRawL("ViewModelBodygroups") or {} + + if ViewModelElements and self2.HasInitAttachments then + -- ViewModelElements = self:GetStatL("ViewModelElements") + -- self:CreateModels(ViewModelElements, true) + + self2.SCKMaterialCached_V = self2.SCKMaterialCached_V or {} + + if not self2.vRenderOrder then + self:RebuildModsRenderOrder() + end + + vm:InvalidateBoneCache() + vm:SetupBones() + next_setup_bones = next_setup_bones + 1 + + for index = 1, #self2.vRenderOrder do + local name = self2.vRenderOrder[index] + local element = ViewModelElements[name] + + if not element then + self:RebuildModsRenderOrder() + break + end + + if element.type == "Bodygroup" then + if element.index and element.value_active then + ViewModelBodygroups[element.index] = self2.GetStatL(self, "ViewModelElements." .. name .. ".active") and element.value_active or (element.value_inactive or 0) + end + + goto CONTINUE + end + + if element.hide then goto CONTINUE end + + if element.type == "Quad" and element.draw_func_outer then goto CONTINUE end + if not element.bone and not element.attachment then goto CONTINUE end + + draw_element_closure(self, self2, name, index, vm, ViewModelElements, element, element.translucent == true and ONLY_SETUP or DRAW_AND_SETUP) + + ::CONTINUE:: + end + end + + if not self2.UseHands and self2.ViewModelDrawnPost then + self:ViewModelDrawnPost() + self:ViewModelDrawnPostFinal() + end + + if self2.ShellEjectionQueue ~= 0 then + for i = 1, self2.ShellEjectionQueue do + self:MakeShell(true) + end + + self2.ShellEjectionQueue = 0 + end +end + +function SWEP:ViewModelDrawnPostFinal() + local self2 = self:GetTable() + local vm = self.OwnerViewModel + if not IsValid(vm) then return end + + local ViewModelElements = self:GetStatRawL("ViewModelElements") + if not ViewModelElements then return end + + for index = 1, #self2.vRenderOrder do + local name = self2.vRenderOrder[index] + local element = ViewModelElements[name] + + if element.hide or not element.translucent then goto CONTINUE end + + if element.type == "Quad" and element.draw_func_outer then goto CONTINUE end + if not element.bone and not element.attachment then goto CONTINUE end + + draw_element_closure(self, self2, name, index, vm, ViewModelElements, element, ONLY_DRAW) + + ::CONTINUE:: + end +end + +function SWEP:ViewModelDrawnPost() + local self2 = self:GetTable() + if not self2.VMIV(self) then return end + + if not self.ViewModelFlip then + self2.CacheSightsPos(self, self.OwnerViewModel, false) + end + + local ViewModelElements = self:GetStatRaw("ViewModelElements", TFA.LatestDataVersion) + + if not ViewModelElements or not self2.vRenderOrder then return end + + for index = 1, #self2.vRenderOrder do + local name = self2.vRenderOrder[index] + local element = ViewModelElements[name] + + if element.type == "Quad" and element.draw_func_outer and not element.hide and (element.bone or element.attachment and element.attachment ~= "") and self:GetStatL("ViewModelElements." .. name .. ".active") ~= false then + local pos, ang = self:GetBoneOrientation(ViewModelElements, element, self2.OwnerViewModel) + + if pos then + local drawpos = pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z + + ang:RotateAroundAxis(ang:Up(), element.angle.y) + ang:RotateAroundAxis(ang:Right(), element.angle.p) + ang:RotateAroundAxis(ang:Forward(), element.angle.r) + + drawfn, drawself, fndrawpos, fndrawang, fndrawsize = element.draw_func_outer, self, drawpos, ang, element.size + ProtectedCall(dodrawfn) + end + end + end +end + +--[[ +Function Name: DrawWorldModel +Syntax: self:DrawWorldModel(). Automatically called already. +Returns: Nothing. +Notes: This draws the world model, plus its attachments. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:DrawWorldModel() + local self2 = self:GetTable() + + local skinStat = self2.GetStatL(self, "Skin") + if isnumber(skinStat) then + if self:GetSkin() ~= skinStat then + self:SetSkin(skinStat) + end + end + + if not self2.MaterialCached_W and self2.GetStatL(self, "MaterialTable_W") then + self2.MaterialCached_W = {} + self:SetSubMaterial() + + local collectedKeys = table.GetKeys(self2.GetStatL(self, "MaterialTable_W")) + table.Merge(collectedKeys, table.GetKeys(self2.GetStatL(self, "MaterialTable"))) + + for _, k in ipairs(collectedKeys) do + if (k == "BaseClass") then goto CONTINUE end + + local v = self2.GetStatL(self, "MaterialTable_W")[k] + + if not self2.MaterialCached_W[k] then + self:SetSubMaterial(k - 1, v) + self2.MaterialCached_W[k] = true + end + + ::CONTINUE:: + end + end + + local ply = self:GetOwner() + local validowner = IsValid(ply) + + if validowner then + -- why? this tanks FPS because source doesn't have a chance to setup bones when it needs to + -- instead we ask it to do it `right now` + -- k then + ply:SetupBones() + ply:InvalidateBoneCache() + self:InvalidateBoneCache() + end + + if self2.ShowWorldModel == nil or self2.ShowWorldModel or not validowner then + self2.WorldModelOffsetUpdate(self, ply) + self2.ProcessBodygroups(self) + + self:DrawModel() + end + + self:SetupBones() + self2.UpdateWMBonePositions(self) + + self:DrawWElements() + + if IsValid(self) and self.IsTFAWeapon and (self:GetOwner() ~= LocalPlayer() or not self:IsFirstPerson()) then + self2.UpdateProjectedTextures(self, false) + end +end + +function SWEP:DrawWElements() + local self2 = self:GetTable() + + local WorldModelElements = self2.GetStatRaw(self, "WorldModelElements", TFA.LatestDataVersion) + + if not WorldModelElements then return end + + if not self2.SCKMaterialCached_W then + self2.SCKMaterialCached_W = {} + end + + if not self2.wRenderOrder then + self2.RebuildModsRenderOrder(self) + end + + local ply = self:GetOwner() + local validowner = IsValid(ply) + + for index = 1, #self2.wRenderOrder do + local name = self2.wRenderOrder[index] + local element = WorldModelElements[name] + + if not element then + self2.RebuildModsRenderOrder(self) + break + end + + if element.type == "Bodygroup" then + if element.index and element.value_active then + self2.WorldModelBodygroups[element.index] = self2.GetStatL(self, "WorldModelElements." .. name .. ".active") and element.value_active or (element.value_inactive or 0) + end + + goto CONTINUE + end + + if element.hide then goto CONTINUE end + if self2.GetStatL(self, "WorldModelElements." .. name .. ".active") == false then goto CONTINUE end + + local bone_ent = (validowner and ply:LookupBone(element.bone or "ValveBiped.Bip01_R_Hand")) and ply or self + local pos, ang + + if element.bone then + pos, ang = self2.GetBoneOrientation(self, WorldModelElements, element, bone_ent) + else + pos, ang = self2.GetBoneOrientation(self, WorldModelElements, element, bone_ent, "ValveBiped.Bip01_R_Hand") + end + + if not pos and not element.bonemerge then goto CONTINUE end + + self2.PrecacheElement(self, element, true) + + local model = element.curmodel + local sprite = element.spritemat + + if element.type == "Model" and IsValid(model) then + if element.bonemerge then + model:SetPos(pos) + model:SetAngles(ang) + else + model:SetPos(pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z) + + ang:RotateAroundAxis(ang:Up(), element.angle.y) + ang:RotateAroundAxis(ang:Right(), element.angle.p) + ang:RotateAroundAxis(ang:Forward(), element.angle.r) + + model:SetAngles(ang) + end + + local material = self2.GetStatL(self, "WorldModelElements." .. name .. ".material") + + if not material or material == "" then + model:SetMaterial("") + elseif model:GetMaterial() ~= material then + model:SetMaterial(material) + end + + local skin = self2.GetStatL(self, "WorldModelElements." .. name .. ".skin") + + if skin and skin ~= model:GetSkin() then + model:SetSkin(skin) + end + + if not self2.SCKMaterialCached_W[name] then + self2.SCKMaterialCached_W[name] = true + + local materialtable = self2.GetStatL(self, "WorldModelElements." .. name .. ".materials", {}) + local entmats = table.GetKeys(model:GetMaterials()) + + for _, k in ipairs(entmats) do + model:SetSubMaterial(k - 1, materialtable[k] or "") + end + end + + if not self2.WElementsBodygroupsCache[index] then + self2.WElementsBodygroupsCache[index] = #model:GetBodyGroups() - 1 + end + + if self2.WElementsBodygroupsCache[index] then + for _b = 0, self2.WElementsBodygroupsCache[index] do + local newbg = self2.GetStatL(self, "WorldModelElements." .. name .. ".bodygroup." .. _b, 0) -- names are not supported, use overridetable + + if model:GetBodygroup(_b) ~= newbg then + model:SetBodygroup(_b, newbg) + end + end + end + + if element.surpresslightning then + render.SuppressEngineLighting(true) + end + + if element.bonemerge then + if element.rel and WorldModelElements[element.rel] and IsValid(WorldModelElements[element.rel].curmodel) and WorldModelElements[element.rel].bone ~= "oof" then + element.parModel = WorldModelElements[element.rel].curmodel + else + element.parModel = self + end + + if model:GetParent() ~= element.parModel then + model:SetParent(element.parModel) + end + + if not model:IsEffectActive(EF_BONEMERGE) then + model:AddEffects(EF_BONEMERGE) + model:SetLocalPos(vector_origin) + model:SetLocalAngles(angle_zero) + end + elseif model:IsEffectActive(EF_BONEMERGE) then + model:RemoveEffects(EF_BONEMERGE) + model:SetParent(nil) + end + + render.SetColorModulation(element.color.r / 255, element.color.g / 255, element.color.b / 255) + render.SetBlend(element.color.a / 255) + + model:DrawModel() + + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if element.surpresslightning then + render.SuppressEngineLighting(false) + end + elseif element.type == "Sprite" and sprite then + local drawpos = pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z + render.SetMaterial(sprite) + render.DrawSprite(drawpos, element.size.x, element.size.y, element.color) + elseif element.type == "Quad" and element.draw_func then + local drawpos = pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z + ang:RotateAroundAxis(ang:Up(), element.angle.y) + ang:RotateAroundAxis(ang:Right(), element.angle.p) + ang:RotateAroundAxis(ang:Forward(), element.angle.r) + cam.Start3D2D(drawpos, ang, element.size) + + drawfn, drawself, fndrawpos, fndrawang, fndrawsize = element.draw_func, self, nil, nil, nil + ProtectedCall(dodrawfn) + + cam.End3D2D() + end + + ::CONTINUE:: + end +end + +function SWEP:WorldModelOffsetUpdate(ply) + if not IsValid(ply) then + self:SetRenderOrigin(nil) + self:SetRenderAngles(nil) + + local WorldModelOffset = self:GetStatRawL("WorldModelOffset") + + if WorldModelOffset and WorldModelOffset.Scale then + self:SetModelScale(WorldModelOffset.Scale, 0) + end + + return + end + + + local WorldModelOffset = self:GetStatRawL("WorldModelOffset") + + -- THIS IS DANGEROUS + if WorldModelOffset and WorldModelOffset.Pos and WorldModelOffset.Ang then + -- TO DO ONLY CLIENTSIDE + -- since this will break hitboxes! + local handBone = ply:LookupBone("ValveBiped.Bip01_R_Hand") + + if handBone then + --local pos, ang = ply:GetBonePosition(handBone) + local pos, ang + local mat = ply:GetBoneMatrix(handBone) + + if mat then + pos, ang = mat:GetTranslation(), mat:GetAngles() + else + pos, ang = ply:GetBonePosition(handBone) + end + + local opos, oang, oscale = WorldModelOffset.Pos, WorldModelOffset.Ang, WorldModelOffset.Scale + + pos = pos + ang:Forward() * opos.Forward + ang:Right() * opos.Right + ang:Up() * opos.Up + ang:RotateAroundAxis(ang:Up(), oang.Up) + ang:RotateAroundAxis(ang:Right(), oang.Right) + ang:RotateAroundAxis(ang:Forward(), oang.Forward) + self:SetRenderOrigin(pos) + self:SetRenderAngles(ang) + --if WorldModelOffset.Scale and ( !self2.MyModelScale or ( WorldModelOffset and self2.MyModelScale!=WorldModelOffset.Scale ) ) then + self:SetModelScale(oscale or 1, 0) + --end + end + end +end + +--[[ +Function Name: GetBoneOrientation +Syntax: self:GetBoneOrientation( base bone mod table, bone mod table, entity, bone override ). +Returns: Position, Angle. +Notes: This is a very specific function for a specific purpose, and shouldn't be used generally to get a bone's orientation. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:GetBoneOrientation(basetabl, tabl, ent, bone_override, isVM, isAttachment, isNonRoot) + local bone, pos, ang + + if not IsValid(ent) then return Vector(), Angle() end + + if not isNonRoot and tabl.rel and tabl.rel ~= "" and not tabl.bonemerge then + local v = basetabl[tabl.rel] + if not v then return Vector(), Angle() end + + local boneName = tabl.bone + + if tabl.attachment and tabl.attachment ~= "" and v.curmodel:LookupAttachment(tabl.attachment) ~= 0 then + pos, ang = self:GetBoneOrientation(basetabl, v, v.curmodel, tabl.attachment, isVM, true, true) + + if pos and ang then return pos, ang end + elseif v.curmodel and ent ~= v.curmodel and (v.bonemerge or (boneName and boneName ~= "" and v.curmodel:LookupBone(boneName))) then + pos, ang = self:GetBoneOrientation(basetabl, v, v.curmodel, boneName, isVM, false, true) + + if pos and ang then return pos, ang end + else + --As clavus states in his original code, don't make your elements named the same as a bone, because recursion. + pos, ang = self:GetBoneOrientation(basetabl, v, ent, nil, isVM, false, true) + + if pos and ang then + pos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + -- For mirrored viewmodels. You might think to scale negatively on X, but this isn't the case. + + return pos, ang + end + end + end + + if isAttachment == nil then isAttachment = tabl.attachment ~= nil end + + if isnumber(bone_override) then + bone = bone_override + elseif isAttachment then + bone = ent:LookupAttachment(bone_override or tabl.attachment) + else + bone = ent:LookupBone(bone_override or tabl.bone) or 0 + end + + if not bone or bone == -1 then return end + pos, ang = Vector(0, 0, 0), Angle(0, 0, 0) + + if ent.tfa_next_setup_bones ~= next_setup_bones then + ent:InvalidateBoneCache() + ent:SetupBones() + ent.tfa_next_setup_bones = next_setup_bones + end + + if isAttachment then + -- mmmm yes tasty LuaVM memory + -- GC screams in agony + local get = ent:GetAttachment(bone) + + if get then + pos, ang = get.Pos, get.Ang + end + else + + local m = ent:GetBoneMatrix(bone) + + if m then + pos, ang = m:GetTranslation(), m:GetAngles() + end + end + + local owner = self:GetOwner() + + if isVM and self.ViewModelFlip then + ang.r = -ang.r + end + + return pos, ang +end +--[[ +Function Name: CleanModels +Syntax: self:CleanModels( elements table ). +Returns: Nothing. +Notes: Removes all existing models. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:CleanModels(input) + if not istable(input) then return end + + for _, v in pairs(input) do + if (v.type == "Model" and v.curmodel) then + if IsValid(v.curmodel) then + v.curmodel:Remove() + end + + v.curmodel = nil + elseif (v.type == "Sprite" and v.sprite and v.sprite ~= "" and (not v.spritemat or v.cursprite ~= v.sprite)) then + v.cursprite = nil + v.spritemat = nil + end + end +end + +function SWEP:PrecacheElementModel(element, is_vm) + element.curmodel = ClientsideModel(element.model, RENDERGROUP_OTHER) + element.curmodel.tfa_gun_parent = self + element.curmodel.tfa_gun_clmodel = true + + if self.SWEPConstructionKit then + TFA.RegisterClientsideModel(element.curmodel, self) + end + + if not IsValid(element.curmodel) then + element.curmodel = nil + return + end + + element.curmodel:SetPos(self:GetPos()) + element.curmodel:SetAngles(self:GetAngles()) + element.curmodel:SetParent(self) + element.curmodel:SetOwner(self) + element.curmodel:SetNoDraw(true) + + if element.material then + element.curmodel:SetMaterial(element.material or "") + end + + if element.skin then + element.curmodel:SetSkin(element.skin) + end + + local matrix = Matrix() + matrix:Scale(element.size) + + element.curmodel:EnableMatrix("RenderMultiply", matrix) + element.curmodelname = element.model + element.view = is_vm == true + + -- // make sure we create a unique name based on the selected options +end + +do + local tocheck = {"nocull", "additive", "vertexalpha", "vertexcolor", "ignorez"} + + function SWEP:PrecacheElementSprite(element, is_vm) + if element.vmt then + element.spritemat = Material(element.sprite) + element.cursprite = element.sprite + return + end + + local name = "tfa-" .. element.sprite .. "-" + + local params = { + ["$basetexture"] = element.sprite + } + + for _, element_property in ipairs(tocheck) do + if (element[element_property]) then + params["$" .. element_property] = 1 + name = name .. "1" + else + name = name .. "0" + end + end + + element.cursprite = element.sprite + element.spritemat = CreateMaterial(name, "UnlitGeneric", params) + end +end + +function SWEP:PrecacheElement(element, is_vm) + if element.type == "Model" and element.model and (not IsValid(element.curmodel) or element.curmodelname ~= element.model) and element.model ~= "" then + if IsValid(element.curmodel) then + element.curmodel:Remove() + end + + self:PrecacheElementModel(element, is_vm) + elseif (element.type == "Sprite" and element.sprite and element.sprite ~= "" and (not element.spritemat or element.cursprite ~= element.sprite)) then + self:PrecacheElementSprite(element, is_vm) + end +end + +--[[ +Function Name: CreateModels +Syntax: self:CreateModels( elements table ). +Returns: Nothing. +Notes: Creates the elements for whatever you give it. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:CreateModels(input, is_vm) + if not istable(input) then return end + + for _, element in pairs(input) do + self:PrecacheElement(element, is_vm) + end +end + +--[[ +Function Name: UpdateBonePositions +Syntax: self:UpdateBonePositions( viewmodel ). +Returns: Nothing. +Notes: Updates the bones for a viewmodel. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +local bpos, bang +local onevec = Vector(1, 1, 1) +local getKeys = table.GetKeys + +local function appendTable(t, t2) + for i = 1, #t2 do + t[#t + 1] = t2[i] + end +end + +SWEP.ChildrenScaled = {} +SWEP.ViewModelBoneMods_Children = {} + +function SWEP:ScaleChildBoneMods(ent,bone,cumulativeScale) + if self.ChildrenScaled[bone] then + return + end + self.ChildrenScaled[bone] = true + local boneid = ent:LookupBone(bone) + if not boneid then return end + local curScale = (cumulativeScale or Vector(1,1,1)) * 1 + if self.ViewModelBoneMods[bone] then + curScale = curScale * self.ViewModelBoneMods[bone].scale + end + local ch = ent:GetChildBones(boneid) + if ch and #ch > 0 then + for _, boneChild in ipairs(ch) do + self:ScaleChildBoneMods(ent,ent:GetBoneName(boneChild),curScale) + end + end + if self.ViewModelBoneMods[bone] then + self.ViewModelBoneMods[bone].scale = curScale + else + self.ViewModelBoneMods_Children[bone] = { + ["pos"] = vector_origin, + ["angle"] = angle_zero, + ["scale"] = curScale * 1 + } + end +end + +local vmbm_old_count = 0 + +function SWEP:UpdateBonePositions(vm) + local self2 = self:GetTable() + local vmbm = self2.GetStatL(self, "ViewModelBoneMods") + + local vmbm_count = 0 + + if vmbm then + vmbm_count = table.Count(vmbm) + end + + if vmbm_old_count ~= vmbm_count then + self:ResetBonePositions() + end + + vmbm_old_count = vmbm_count + + if vmbm then + local stat = self:GetStatus() + + if not self2.BlowbackBoneMods then + self2.BlowbackBoneMods = {} + self2.BlowbackCurrent = 0 + end + + if not self2.HasSetMetaVMBM then + for k,v in pairs(self2.ViewModelBoneMods) do + if (k == "BaseClass") then goto CONTINUE end -- do not name your bones like this pls + + local scale = v.scale + + if scale and scale.x ~= 1 or scale.y ~= 1 or scale.z ~= 1 then + self:ScaleChildBoneMods(vm, k) + end + + ::CONTINUE:: + end + + for _,v in pairs(self2.BlowbackBoneMods) do + v.pos_og = v.pos + v.angle_og = v.angle + v.scale_og = v.scale or onevec + end + + self2.HasSetMetaVMBM = true + self2.ViewModelBoneMods["wepEnt"] = self + + setmetatable(self2.ViewModelBoneMods, {__index = function(t,k) + if not IsValid(self) then return end + if self2.ViewModelBoneMods_Children[k] then return self2.ViewModelBoneMods_Children[k] end + if not self2.BlowbackBoneMods[k] then return end + if not ( self2.SequenceEnabled[ACT_VM_RELOAD_EMPTY] and TFA.Enum.ReloadStatus[stat] and self2.Blowback_PistolMode ) then + self2.BlowbackBoneMods[k].pos = self2.BlowbackBoneMods[k].pos_og * self2.BlowbackCurrent + self2.BlowbackBoneMods[k].angle = self2.BlowbackBoneMods[k].angle_og * self2.BlowbackCurrent + self2.BlowbackBoneMods[k].scale = Lerp(self2.BlowbackCurrent, onevec, self2.BlowbackBoneMods[k].scale_og) + return self2.BlowbackBoneMods[k] + end + end}) + end + + if not ( self2.SequenceEnabled[ACT_VM_RELOAD_EMPTY] and TFA.Enum.ReloadStatus[stat] and self2.Blowback_PistolMode ) then + self2.BlowbackCurrent = math.Approach(self2.BlowbackCurrent, 0, self2.BlowbackCurrent * FrameTime() * 30) + end + + local keys = getKeys(vmbm) + appendTable(keys, getKeys(self2.GetStatL(self, "BlowbackBoneMods") or self2.BlowbackBoneMods)) + appendTable(keys, getKeys(self2.ViewModelBoneMods_Children)) + + for _,k in pairs(keys) do + if k == "wepEnt" then goto CONTINUE end + + local v = vmbm[k] or self2.GetStatL(self, "ViewModelBoneMods." .. k) + if not v then goto CONTINUE end + + local vscale, vangle, vpos = v.scale, v.angle, v.pos + + local bone = vm:LookupBone(k) + if not bone then goto CONTINUE end + + local b = self2.GetStatL(self, "BlowbackBoneMods." .. k) + + if b then + vscale = Lerp(self2.BlowbackCurrent, vscale, vscale * b.scale) + vangle = vangle + b.angle * self2.BlowbackCurrent + vpos = vpos + b.pos * self2.BlowbackCurrent + end + + if vm:GetManipulateBoneScale(bone) ~= vscale then + vm:ManipulateBoneScale(bone, vscale) + end + + if vm:GetManipulateBoneAngles(bone) ~= vangle then + vm:ManipulateBoneAngles(bone, vangle) + end + + if vm:GetManipulateBonePosition(bone) ~= vpos then + vm:ManipulateBonePosition(bone, vpos) + end + + ::CONTINUE:: + end + elseif self2.BlowbackBoneMods then + for bonename, tbl in pairs(self2.BlowbackBoneMods) do + local bone = vm:LookupBone(bonename) + + if bone and bone >= 0 then + bpos = tbl.pos * self2.BlowbackCurrent + bang = tbl.angle * self2.BlowbackCurrent + vm:ManipulateBonePosition(bone, bpos) + vm:ManipulateBoneAngles(bone, bang) + end + end + end +end + +--[[ +Function Name: ResetBonePositions +Syntax: self:ResetBonePositions( viewmodel ). +Returns: Nothing. +Notes: Resets the bones for a viewmodel. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:ResetBonePositions(val) + if SERVER then + self:CallOnClient("ResetBonePositions", "") + + return + end + + local vm = self.OwnerViewModel + if not IsValid(vm) then return end + if (not vm:GetBoneCount()) then return end + + for i = 0, vm:GetBoneCount() do + vm:ManipulateBoneScale(i, Vector(1, 1, 1)) + vm:ManipulateBoneAngles(i, Angle(0, 0, 0)) + vm:ManipulateBonePosition(i, vector_origin) + end +end + +--[[ +Function Name: UpdateWMBonePositions +Syntax: self:UpdateWMBonePositions( worldmodel ). +Returns: Nothing. +Notes: Updates the bones for a worldmodel. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:UpdateWMBonePositions() + if not self.WorldModelBoneMods then + self.WorldModelBoneMods = {} + end + + local WM_BoneMods = self:GetStatL("WorldModelBoneMods", self.WorldModelBoneMods) + + if next(WM_BoneMods) then + for bone = 0, self:GetBoneCount() - 1 do + local bonemod = WM_BoneMods[self:GetBoneName(bone)] + if not bonemod then goto CONTINUE end + + local childscale + local cur = self:GetBoneParent(bone) + + while (cur ~= -1) do + local par = WM_BoneMods[self:GetBoneName(cur)] + + if par then + childscale = (childscale or onevec) * (par.scale or onevec) + end + + cur = self:GetBoneParent(cur) + end + + local s = (bonemod.scale or onevec) + if childscale then + s = s * childscale + end + + if self:GetManipulateBoneScale(bone) ~= s then + self:ManipulateBoneScale(bone, s) + end + + local a = bonemod.angle or angle_zero + + if self:GetManipulateBoneAngles(bone) ~= a then + self:ManipulateBoneAngles(bone, a) + end + + local p = bonemod.pos or vector_origin + + if self:GetManipulateBonePosition(bone) ~= p then + self:ManipulateBonePosition(bone, p) + end + + ::CONTINUE:: + end + end +end + +--[[ +Function Name: ResetWMBonePositions +Syntax: self:ResetWMBonePositions( worldmodel ). +Returns: Nothing. +Notes: Resets the bones for a worldmodel. +Purpose: SWEP Construction Kit Compatibility / Basic Attachments. +]] +-- +function SWEP:ResetWMBonePositions(wm) + if SERVER then + self:CallOnClient("ResetWMBonePositions", "") + + return + end + + if not wm then + wm = self + end + + if not IsValid(wm) then return end + + for i = 0, wm:GetBoneCount() - 1 do + wm:ManipulateBoneScale(i, Vector(1, 1, 1)) + wm:ManipulateBoneAngles(i, Angle(0, 0, 0)) + wm:ManipulateBonePosition(i, vector_origin) + end +end + +function SWEP:PreDrawStencilSight(vm, ply, SightElementTable) +end + +function SWEP:PostDrawStencilSight(vm, ply, SightElementTable) +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/viewbob.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/viewbob.lua new file mode 100644 index 0000000..803bf1c --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/viewbob.lua @@ -0,0 +1,170 @@ +SWEP.SprintBobMult = 1.5 -- More is more bobbing, proportionally. This is multiplication, not addition. You want to make this > 1 probably for sprinting. +SWEP.IronBobMult = 0.0 -- More is more bobbing, proportionally. This is multiplication, not addition. You want to make this < 1 for sighting, 0 to outright disable. +SWEP.IronBobMultWalk = 0.2 -- More is more bobbing, proportionally. This is multiplication, not addition. You want to make this < 1 for sighting, 0 to outright disable. +SWEP.WalkBobMult = 1 -- More is more bobbing, proportionally. This is multiplication, not addition. You may want to disable it when using animated walk. +SWEP.SprintViewBobMult = 4 +--[[ +Function Name: CalcView +Syntax: Don't ever call this manually. +Returns: Nothing. +Notes: Used to calculate view angles. +Purpose: Feature +]] +--" +--[[ + +local ta = Angle() +local v = Vector() + +local m_AD = math.AngleDifference +local m_NA = math.NormalizeAngle + +local l_LA = function(t,a1,a2) + ta.p = m_NA( a1.p + m_AD(a2.p,a1.p) * t ) + ta.y = m_NA( a1.y + m_AD(a2.y,a1.y) * t ) + ta.r = m_NA( a1.r + m_AD(a2.r,a1.r) * t ) + return ta +end + +local l_LV = function(t,v1,v2) + v = v1 + ( v2 - v1 ) * t + return v * 1 +end +]] +-- +SWEP.ViewHolProg = 0 +SWEP.AttachmentViewOffset = Angle(0, 0, 0) +SWEP.ProceduralViewOffset = Angle(0, 0, 0) +--local procedural_fadeout = 0.6 +local procedural_vellimit = 5 +local l_Lerp = Lerp +local l_mathApproach = math.Approach +local l_mathClamp = math.Clamp +local viewbob_intensity_cvar, viewbob_animated_cvar +viewbob_intensity_cvar = GetConVar("cl_tfa_viewbob_intensity") +viewbob_animated_cvar = GetConVar("cl_tfa_viewbob_animated") +local oldangtmp +local mzang_fixed +local mzang_fixed_last +local mzang_velocity = Angle() +local progress = 0 +local targint, targbool + +SWEP.ViewBob_Bash = true +SWEP.ViewBob_Draw = true +SWEP.ViewBob_Holster = true +SWEP.ViewBob_Inspect = true +SWEP.ViewBob_Pump = true +SWEP.ViewBob_Reload = true +SWEP.ViewBob_Shoot = false + +SWEP.ViewBob_DontFadeOutStatus = { + [TFA.Enum.STATUS_RELOADING_LOOP_START] = true, + [TFA.Enum.STATUS_RELOADING_LOOP_START_EMPTY] = true, + [TFA.Enum.STATUS_RELOADING_LOOP] = true, +} + +SWEP.ViewBob_DontFadeOutShootStatus = { + [TFA.Enum.SHOOT_START] = true, + [TFA.Enum.SHOOT_LOOP] = true, + [TFA.Enum.SHOOT_CHECK] = true, +} -- looped fire ends on TFA.Enum.SHOOT_IDLE so we include anything but that + +function SWEP:CalcView(ply, pos, ang, fov) + if not ang then return end + if ply ~= GetViewEntity() then return end + + local self2 = self:GetTable() + + local vm = self2.OwnerViewModel + if not IsValid(vm) then return end + + local ftv = FrameTime() + local viewbobintensity = viewbob_intensity_cvar:GetFloat() + local holprog = TFA.Enum.HolsterStatus[self2.GetStatus(self)] and 1 or 0 + self2.ViewHolProg = math.Approach(self2.ViewHolProg, holprog, ftv / 5) + + oldangtmp = ang * 1 + + local stat = self:GetStatus() + + local ibash = stat == TFA.Enum.STATUS_BASHING or stat == TFA.Enum.STATUS_BASHING_WAIT and self2.GetStatL(self, "ViewBob_Bash") + local idraw = stat == TFA.Enum.STATUS_DRAW and self2.GetStatL(self, "ViewBob_Draw") + local ihols = TFA.Enum.HolsterStatus[stat] and self2.GetStatL(self, "ViewBob_Holster") + local ifidget = stat == TFA.Enum.STATUS_FIDGET and self2.GetStatL(self, "ViewBob_Inspect") + local ipump = stat == TFA.Enum.STATUS_PUMP and self2.GetStatL(self, "ViewBob_Pump") + local ireload = TFA.Enum.ReloadStatus[stat] and self2.GetStatL(self, "ViewBob_Reload") + local ishoot = stat == TFA.Enum.STATUS_SHOOTING and self2.GetStatL(self, "ViewBob_Shoot") and not self:CanInterruptShooting() + + targbool = idraw or ireload or ibash or ishoot or ipump or ifidget or (ihols and not self2.GetStatL(self, "ProceduralHolsterEnabled")) + targint = targbool and 1 or 0 + + if not self2.ViewBob_DontFadeOutStatus[stat] and not self2.ViewBob_DontFadeOutShootStatus[self:GetShootStatus()] then + targint = math.min(targint, 1 - math.pow(math.max(vm:GetCycle() - 0.5, 0) * 2, 2)) + end + + progress = l_Lerp(ftv * 20, progress, targint) + + if self2.CameraAngCache and viewbob_animated_cvar:GetBool() then + self2.CameraAttachmentScale = self2.CameraAttachmentScale or 1 + ang:RotateAroundAxis(ang:Right(), Lerp(progress, 0, (self2.CameraAngCache.p + self2.CameraOffset.p) * viewbobintensity * -self2.CameraAttachmentScale) * viewbobintensity) + ang:RotateAroundAxis(ang:Up(), Lerp(progress, 0, (self2.CameraAngCache.y + self2.CameraOffset.y) * viewbobintensity * self2.CameraAttachmentScale) * viewbobintensity) + ang:RotateAroundAxis(ang:Forward(), Lerp(progress, 0, (self2.CameraAngCache.r + self2.CameraOffset.r) * viewbobintensity * self2.CameraAttachmentScale) * viewbobintensity) + -- - self2.MZReferenceAngle--WorldToLocal( angpos.Pos, angpos.Ang, angpos.Pos, oldangtmp + self2.MZReferenceAngle ) + --* progress ) + --self2.ProceduralViewOffset.p = l_mathApproach(self2.ProceduralViewOffset.p, 0 , l_mathClamp( procedural_pitchrestorefac - math.min( math.abs( self2.ProceduralViewOffset.p ), procedural_pitchrestorefac ) ,1,procedural_pitchrestorefac)*ftv/5 ) + --self2.ProceduralViewOffset.y = l_mathApproach(self2.ProceduralViewOffset.y, 0 , l_mathClamp( procedural_pitchrestorefac - math.min( math.abs( self2.ProceduralViewOffset.y ), procedural_pitchrestorefac ) ,1,procedural_pitchrestorefac)*ftv/5 ) + --self2.ProceduralViewOffset.r = l_mathApproach(self2.ProceduralViewOffset.r, 0 , l_mathClamp( procedural_pitchrestorefac - math.min( math.abs( self2.ProceduralViewOffset.r ), procedural_pitchrestorefac ) ,1,procedural_pitchrestorefac)*ftv/5 ) + else + local mul = 1 + if ifidget then + mul = -1 + end + + local att = self2.MuzzleAttachmentRaw or vm:LookupAttachment(self2.MuzzleAttachment) + if not att then + att = 1 + end + + local angpos = vm:GetAttachment(att) + if angpos and angpos.Ang then + mzang_fixed = vm:WorldToLocalAngles(angpos.Ang) + mzang_fixed:Normalize() + end + + self2.ProceduralViewOffset:Normalize() + + if mzang_fixed_last then + local delta = mzang_fixed - mzang_fixed_last + delta:Normalize() + mzang_velocity = mzang_velocity + delta * (2 * (1 - self2.ViewHolProg)) + + mzang_velocity.p = math.Approach(mzang_velocity.p, -self2.ProceduralViewOffset.p * 2, ftv * 20) + mzang_velocity.p = math.Clamp(mzang_velocity.p, -procedural_vellimit, procedural_vellimit) + self2.ProceduralViewOffset.p = self2.ProceduralViewOffset.p + mzang_velocity.p * ftv * mul + self2.ProceduralViewOffset.p = math.Clamp(self2.ProceduralViewOffset.p, -90, 90) + + mzang_velocity.y = math.Approach(mzang_velocity.y, -self2.ProceduralViewOffset.y * 2, ftv * 20) + mzang_velocity.y = math.Clamp(mzang_velocity.y, -procedural_vellimit, procedural_vellimit) + self2.ProceduralViewOffset.y = self2.ProceduralViewOffset.y + mzang_velocity.y * ftv * mul + self2.ProceduralViewOffset.y = math.Clamp(self2.ProceduralViewOffset.y, -90, 90) + + mzang_velocity.r = math.Approach(mzang_velocity.r, -self2.ProceduralViewOffset.r * 2, ftv * 20) + mzang_velocity.r = math.Clamp(mzang_velocity.r, -procedural_vellimit, procedural_vellimit) + self2.ProceduralViewOffset.r = self2.ProceduralViewOffset.r + mzang_velocity.r * ftv * mul + self2.ProceduralViewOffset.r = math.Clamp(self2.ProceduralViewOffset.r, -90, 90) + end + mzang_fixed_last = mzang_fixed + + self2.ProceduralViewOffset.p = math.Approach(self2.ProceduralViewOffset.p, 0, (1 - progress) * ftv * -self2.ProceduralViewOffset.p * 20) + self2.ProceduralViewOffset.y = math.Approach(self2.ProceduralViewOffset.y, 0, (1 - progress) * ftv * -self2.ProceduralViewOffset.y * 20) + self2.ProceduralViewOffset.r = math.Approach(self2.ProceduralViewOffset.r, 0, (1 - progress) * ftv * -self2.ProceduralViewOffset.r * 20) + + local ints = viewbobintensity * 1.25 + ang:RotateAroundAxis(ang:Right(), Lerp(progress, 0, -self2.ProceduralViewOffset.p) * ints) + ang:RotateAroundAxis(ang:Up(), Lerp(progress, 0, self2.ProceduralViewOffset.y / 2) * ints) + ang:RotateAroundAxis(ang:Forward(), Lerp(progress, 0, self2.ProceduralViewOffset.r / 3) * ints) + end + + return pos, LerpAngle(math.pow(self2.ViewHolProg, 2), ang, oldangtmp), fov +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/viewmodel.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/viewmodel.lua new file mode 100644 index 0000000..186f3f5 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/client/viewmodel.lua @@ -0,0 +1,763 @@ +local vector_origin = Vector() +local angle_zero = Angle() + +local Vector = Vector +local Angle = Angle +local math = math +local LerpVector = LerpVector + +local sv_cheats = GetConVar("sv_cheats") +local host_timescale = GetConVar("host_timescale") + +local cv_fov = GetConVar("fov_desired") +local cl_vm_nearwall = GetConVar("cl_tfa_viewmodel_nearwall") + +local cl_tfa_viewmodel_offset_x = GetConVar("cl_tfa_viewmodel_offset_x") +local cl_tfa_viewmodel_offset_y = GetConVar("cl_tfa_viewmodel_offset_y") +local cl_tfa_viewmodel_offset_z = GetConVar("cl_tfa_viewmodel_offset_z") +local cl_tfa_viewmodel_centered = GetConVar("cl_tfa_viewmodel_centered") + +local cl_tfa_viewmodel_vp_enabled = GetConVar("cl_tfa_viewmodel_vp_enabled") +local cl_tfa_viewmodel_vp_pitch = GetConVar("cl_tfa_viewmodel_vp_pitch") +local cl_tfa_viewmodel_vp_pitch_is = GetConVar("cl_tfa_viewmodel_vp_pitch_is") +local cl_tfa_viewmodel_vp_vertical = GetConVar("cl_tfa_viewmodel_vp_vertical") +local cl_tfa_viewmodel_vp_vertical_is = GetConVar("cl_tfa_viewmodel_vp_vertical_is") +local cl_tfa_viewmodel_vp_max_vertical = GetConVar("cl_tfa_viewmodel_vp_max_vertical") +local cl_tfa_viewmodel_vp_max_vertical_is = GetConVar("cl_tfa_viewmodel_vp_max_vertical_is") +local cl_tfa_viewmodel_vp_yaw = GetConVar("cl_tfa_viewmodel_vp_yaw") +local cl_tfa_viewmodel_vp_yaw_is = GetConVar("cl_tfa_viewmodel_vp_yaw_is") + +local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy") + +local cv_customgunbob = GetConVar("cl_tfa_gunbob_custom") + +local function Lerp(t, a, b) + return a + (b - a) * t +end + +local function Clamp(a, b, c) + if a < b then return b end + if a > c then return c end + return a +end + +local function GetClampedCVarFloat(cvar) + return Clamp(cvar:GetFloat(), cvar:GetMin(), cvar:GetMax()) +end + +local math_max = math.max + +local cl_vm_flip_cv = GetConVar("cl_tfa_viewmodel_flip") +local fovmod_add = GetConVar("cl_tfa_viewmodel_offset_fov") +local fovmod_mult = GetConVar("cl_tfa_viewmodel_multiplier_fov") + +function SWEP:AirWalkScale() + return (self:OwnerIsValid() and self:GetOwner():IsOnGround()) and 1 or 0.2 +end + +SWEP.OldPos = Vector(0, 0, 0) +SWEP.OldAng = Angle(0, 0, 0) + +function SWEP:GetViewModelPosition(opos, oang, ...) + local self2 = self:GetTable() + + if not self2.pos_cached then return opos, oang end + + local npos, nang = opos * 1, oang * 1 + + nang:RotateAroundAxis(nang:Right(), self2.ang_cached.p) + nang:RotateAroundAxis(nang:Up(), self2.ang_cached.y) + nang:RotateAroundAxis(nang:Forward(), self2.ang_cached.r) + npos:Add(nang:Right() * self2.pos_cached.x) + npos:Add(nang:Forward() * self2.pos_cached.y) + npos:Add(nang:Up() * self2.pos_cached.z) + + if cv_customgunbob:GetBool() then + npos, nang = self:Sway(npos, nang) + npos, nang = self:SprintBob(npos, nang, Lerp(self2.SprintProgressUnpredicted3 or self2.SprintProgressUnpredicted or self:GetSprintProgress(), 0, self2.SprintBobMult)) + end + + local pos, ang = self2.SightsAttPos, Angle(self2.SightsAttAng) + if not pos or not ang then return npos, nang end + + local ofpos, ofang = WorldToLocal(npos, nang, opos, oang) + + self2.OldPos = npos + self2.OldAng = nang + + if self.IronSightsProgressUnpredicted > 0.005 then + local _opos, _oang = opos * 1, oang * 1 + + -- tfa base vm offset + local right, up, fwd = _oang:Right(), _oang:Up(), _oang:Forward() + + _opos = _opos - ofpos.y * right + ofpos.x * fwd + ofpos.z * up + _oang:RotateAroundAxis(fwd, ofang.r) + _oang:RotateAroundAxis(right, -ofang.p) + _oang:RotateAroundAxis(up, ofang.y) + + -- sight offset + + _oang:RotateAroundAxis(_oang:Forward(), -ang.r) + _oang:RotateAroundAxis(_oang:Right(), ang.p) + _oang:RotateAroundAxis(_oang:Up(), -ang.y) + + right, up, fwd = _oang:Right(), _oang:Up(), _oang:Forward() + + _opos = _opos - pos.x * fwd + pos.y * right - pos.z * up + + self2.OldPos = LerpVector(self2.IronSightsProgressUnpredicted, npos, _opos) + self2.OldAng = LerpAngle(self2.IronSightsProgressUnpredicted, nang, _oang) + end + + return self2.OldPos, self2.OldAng +end + +function SWEP:CalculateViewModelFlip() + local self2 = self:GetTable() + + if self2.ViewModelFlipDefault == nil then + self2.ViewModelFlipDefault = self2.ViewModelFlip + end + + local righthanded = true + + if cl_vm_flip_cv:GetBool() then + righthanded = false + end + + local shouldflip = self2.ViewModelFlipDefault + + if not righthanded then + shouldflip = not self2.ViewModelFlipDefault + end + + if self2.ViewModelFlip ~= shouldflip then + self2.ViewModelFlip = shouldflip + end + + self2.ViewModelFOV_OG = self2.ViewModelFOV_OG or self2.ViewModelFOV + + local cam_fov = self2.LastTranslatedFOV or cv_fov:GetInt() or 90 + local iron_add = cam_fov * (1 - 90 / cam_fov) * math.max(1 - self2.GetStatL(self, "Secondary.OwnerFOV", 90) / 90, 0) + + local ironSightsProgress = TFA.Cosine(self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress()) + self2.ViewModelFOV = Lerp(ironSightsProgress, self2.ViewModelFOV_OG, self2.GetStatL(self, "Secondary.ViewModelFOV", self2.ViewModelFOV_OG)) * GetClampedCVarFloat(fovmod_mult) + GetClampedCVarFloat(fovmod_add) + iron_add * ironSightsProgress +end + +function SWEP:UpdateWeaponLength() + local self2 = self:GetTable() + if not self2.VMIV(self) then return end + local vm = self2.OwnerViewModel + local mzpos = self:GetMuzzlePos() + if not mzpos then return end + if not mzpos.Pos then return end + if GetViewEntity and GetViewEntity() ~= self:GetOwner() then return end + local mzVec = vm:WorldToLocal(mzpos.Pos) + self2.WeaponLength = math.abs(mzVec.x) +end + +function SWEP:CalculateNearWall(p, a) + local self2 = self:GetTable() + if not self:OwnerIsValid() then return p, a end + + if not cl_vm_nearwall:GetBool() then return p, a end + + local ply = self:GetOwner() + + local sp = ply:GetShootPos() + local ea = ply:EyeAngles() + local et = util.QuickTrace(sp,ea:Forward()*128,{self,ply})--self:GetOwner():GetEyeTrace() + local dist = et.HitPos:Distance(sp) + + if dist<1 then + et=util.QuickTrace(sp,ea:Forward()*128,{self,ply,et.Entity}) + dist = et.HitPos:Distance(sp) + end + + self:UpdateWeaponLength() + + local nw_offset_vec = LerpVector(self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress(), self2.NearWallVector, self2.NearWallVectorADS) + local off = self2.WeaponLength - dist + self2.LastNearWallOffset = self2.LastNearWallOffset or 0 + + local ft = RealFrameTime() * game.GetTimeScale() * (sv_cheats:GetBool() and host_timescale:GetFloat() or 1) + + if off > self2.LastNearWallOffset then + self2.LastNearWallOffset = math.min(self2.LastNearWallOffset + math.max(ft * 66, off * 0.1), off, 34) + elseif off < self2.LastNearWallOffset then + self2.LastNearWallOffset = math.max(self2.LastNearWallOffset - ft * 66, off, 0) + end + + off = TFA.Cosine(self2.LastNearWallOffset / 34) * 34 + + if off > 0 then + p = p + nw_offset_vec * off / 2 + local posCompensated = sp * 1 + posCompensated:Add(ea:Right() * nw_offset_vec.x * off / 2 * (self2.ViewModelFlip and -1 or 1)) + posCompensated:Add(ea:Forward() * nw_offset_vec.y * off / 2) + posCompensated:Add(ea:Up() * nw_offset_vec.z * off / 2) + local angleComp = (et.HitPos - posCompensated):Angle() + a.x = a.x - math.AngleDifference(angleComp.p, ea.p) / 2 + a.y = a.y + math.AngleDifference(angleComp.y, ea.y) / 2 + end + + return p, a +end + +local centered_sprintpos = Vector(0, -1, 1) +local centered_sprintang = Vector(-15, 0, 0) + +local bezierVectorBuffer = {} + +local function bezierVector(t, vec1, vec2, vec3) + local _1, _2 = vec1.x, vec3.x + bezierVectorBuffer[1] = _1 + bezierVectorBuffer[2] = _1 + bezierVectorBuffer[3] = _1 + bezierVectorBuffer[4] = _1 + bezierVectorBuffer[5] = vec2.x + bezierVectorBuffer[6] = _2 + bezierVectorBuffer[7] = _2 + bezierVectorBuffer[8] = _2 + bezierVectorBuffer[9] = _2 + + local x = TFA.tbezier(t, bezierVectorBuffer) + + _1, _2 = vec1.y, vec3.y + bezierVectorBuffer[1] = _1 + bezierVectorBuffer[2] = _1 + bezierVectorBuffer[3] = _1 + bezierVectorBuffer[4] = _1 + bezierVectorBuffer[5] = vec2.y + bezierVectorBuffer[6] = _2 + bezierVectorBuffer[7] = _2 + bezierVectorBuffer[8] = _2 + bezierVectorBuffer[9] = _2 + + local y = TFA.tbezier(t, bezierVectorBuffer) + + _1, _2 = vec1.z, vec3.z + bezierVectorBuffer[1] = _1 + bezierVectorBuffer[2] = _1 + bezierVectorBuffer[3] = _1 + bezierVectorBuffer[4] = _1 + bezierVectorBuffer[5] = vec2.z + bezierVectorBuffer[6] = _2 + bezierVectorBuffer[7] = _2 + bezierVectorBuffer[8] = _2 + bezierVectorBuffer[9] = _2 + + local z = TFA.tbezier(t, bezierVectorBuffer) + + return Vector(x, y, z) +end + +function SWEP:CalculateViewModelOffset(delta) + local self2 = self:GetTable() + + local target_pos, target_ang + local additivePos = self2.GetStatL(self, "AdditiveViewModelPosition") + + if additivePos then + target_pos, target_ang = Vector(), Vector() + else + target_pos = Vector(self2.GetStatL(self, "ViewModelPosition")) + target_ang = Vector(self2.GetStatL(self, "ViewModelAngle")) + end + + local CenteredViewModelPosition = self2.GetStatL(self, "CenteredViewModelPosition") + local CenteredViewModelAngle = self2.GetStatL(self, "CenteredViewModelAngle") + local IronSightsPosition = self2.GetStatL(self, "IronSightsPosition", self2.SightsPos) + local IronSightsAngle = self2.GetStatL(self, "IronSightsAngle", self2.SightsAng) + + local targetPosCenter, targetAngCenter + + if CenteredViewModelPosition then + targetPosCenter = Vector(CenteredViewModelPosition) + + if CenteredViewModelAngle then + targetAngCenter = Vector(CenteredViewModelAngle) + end + elseif IronSightsPosition then + targetPosCenter = Vector((self2.IronSightsPositionCurrent or IronSightsPosition).x, target_pos.y, target_pos.z - 3) + + if IronSightsAngle then + targetAngCenter = Vector(0, (self2.IronSightsAngleCurrent or IronSightsAngle).y, 0) + end + else + targetPosCenter, targetAngCenter = target_pos, target_ang + end + + local stat = self:GetStatus() + + local holsterStatus = TFA.Enum.HolsterStatus[stat] and self2.GetStatL(self, "ProceduralHolsterEnabled") + local proceduralReloadStatus = TFA.Enum.ReloadStatus[stat] and self2.GetStatL(self, "IsProceduralReloadBased") + local holsterProgress = 0 + local statusProgress = self:GetStatusProgress() + + if proceduralReloadStatus then + holsterProgress = TFA.Quintic(Clamp((statusProgress >= 0.5 and (2 - statusProgress * 2) or (statusProgress * 2)), 0, 1)) + elseif self2.GetStatL(self, "ProceduralHolsterEnabled") then + if TFA.Enum.HolsterStatusFinal[stat] then + holsterProgress = 1 + elseif TFA.Enum.HolsterStatus[stat] then + holsterProgress = TFA.Quintic(Clamp(statusProgress * 1.1, 0, 1)) + end + end + + local sprintAnimAllowed = self2.GetStatL(self, "Sprint_Mode") ~= TFA.Enum.LOCOMOTION_ANI + + local ironSights = self:GetIronSights() + local sprintProgress = sprintAnimAllowed and TFA.Cubic(self2.SprintProgressUnpredicted2 or self2.SprintProgressUnpredicted or self:GetSprintProgress()) or 0 + local safetyProgress = Lerp(sprintProgress, TFA.Cubic(self2.SafetyProgressUnpredicted or 0), 0) + + local ironSightsProgress = Clamp( + Lerp( + math_max(holsterProgress, sprintProgress, safetyProgress), + TFA.Cubic(self2.IronSightsProgressUnpredicted2 or self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress()), + 0) + , 0, 1) + + --local ironSightsProgress = TFA.tbezier(self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress(), IRON_SIGHTS_BEZIER) + + local crouchRatio = Lerp(math_max(ironSightsProgress, holsterProgress, Clamp(sprintProgress * 2, 0, 1), safetyProgress), TFA.Quintic(self2.CrouchingRatioUnpredicted or self:GetCrouchingRatio()), 0) + + if crouchRatio > 0.01 then + target_pos = LerpVector(crouchRatio, target_pos, self2.GetStatL(self, "CrouchViewModelPosition")) + target_ang = LerpVector(crouchRatio, target_ang, self2.GetStatL(self, "CrouchViewModelAngle")) + end + + local isCentered = cl_tfa_viewmodel_centered:GetBool() + if isCentered then + target_pos:Set(targetPosCenter) + target_ang:Set(targetAngCenter) + end + + if holsterStatus or proceduralReloadStatus then + local targetHolsterPos = Vector(self2.GetStatL(self, "ProceduralHolsterPosition")) + local targetHolsterAng = Vector(self2.GetStatL(self, "ProceduralHolsterAngle")) + + if self2.ViewModelFlip then + targetHolsterPos.x = -targetHolsterPos.x + + targetHolsterAng.y = -targetHolsterAng.y + targetHolsterAng.z = -targetHolsterAng.z + end + + target_pos = LerpVector(holsterProgress, target_pos, targetHolsterPos) + target_ang = LerpVector(holsterProgress, target_ang, targetHolsterAng) + end + + if + (sprintProgress > 0.01 or safetyProgress > 0.01) and + (sprintAnimAllowed and sprintProgress > 0.01 or safetyProgress > 0.01) + and not TFA.Enum.BashStatus[stat] + then + local add_pos = isCentered and centered_sprintpos or vector_origin + local add_ang = isCentered and centered_sprintang or vector_origin + + local sprint_pos = self2.GetStatL(self, "SprintViewModelPosition") + local sprint_ang = self2.GetStatL(self, "SprintViewModelAngle") + + target_pos = LerpVector(safetyProgress, target_pos, self2.GetStatL(self, "SafetyPos", sprint_pos) + add_pos) + target_ang = LerpVector(safetyProgress, target_ang, self2.GetStatL(self, "SafetyAng", sprint_ang) + add_ang) + + if sprintAnimAllowed then + target_pos = LerpVector(sprintProgress, target_pos, sprint_pos + add_pos) + target_ang = LerpVector(sprintProgress, target_ang, sprint_ang + add_ang) + end + end + + if ironSightsProgress > 0.02 and self2.GetStatL(self, "Sights_Mode") ~= TFA.Enum.LOCOMOTION_ANI then + local score = self2.VM_IronPositionScore or 1 + local getSightsPos = self2.IronSightsPositionCurrent or IronSightsPosition or self2.GetStatL(self, "SightsPos", vector_origin) + + if targetPosCenter and score > 0.04 then + target_pos = bezierVector(ironSightsProgress, target_pos, LerpVector(score, getSightsPos, targetPosCenter), getSightsPos) + else + target_pos = LerpVector(ironSightsProgress, target_pos, getSightsPos) + end + + if targetAngCenter and score > 0.04 then + local deviate = 30 * score + + if self2.VM_IsScopedIn then + deviate = -deviate + end + + if self2.ViewModelFlip then + deviate = -deviate + end + + local targetAngCenter2 = Vector(targetAngCenter.x * score, targetAngCenter.y * score, targetAngCenter.z * score + deviate) + target_ang = bezierVector(ironSightsProgress, target_ang, targetAngCenter2, self2.IronSightsAngleCurrent or IronSightsAngle or self2.GetStatL(self, "SightsAng", vector_origin)) + else + target_ang = LerpVector(ironSightsProgress, target_ang, self2.IronSightsAngleCurrent or IronSightsAngle or self2.GetStatL(self, "SightsAng", vector_origin)) + end + end + + target_pos.x = target_pos.x + GetClampedCVarFloat(cl_tfa_viewmodel_offset_x) * (1 - ironSightsProgress) + target_pos.y = target_pos.y + GetClampedCVarFloat(cl_tfa_viewmodel_offset_y) * (1 - ironSightsProgress) + target_pos.z = target_pos.z + GetClampedCVarFloat(cl_tfa_viewmodel_offset_z) * (1 - ironSightsProgress) + + local customizationProgress = TFA.Quintic(self2.CustomizingProgressUnpredicted or self:GetInspectingProgress()) + + if customizationProgress > 0.01 and self2.GetStatL(self, "Customize_Mode") ~= TFA.Enum.LOCOMOTION_ANI then + if not self2.InspectPos then + self2.InspectPos = Vector(self2.InspectPosDef) + + if self2.ViewModelFlip then + self2.InspectPos.x = self2.InspectPos.x * -1 + end + end + + if not self2.InspectAng then + self2.InspectAng = Vector(self2.InspectAngDef) + + if self2.ViewModelFlip then + self2.InspectAng.y = self2.InspectAngDef.y * -1 + self2.InspectAng.z = self2.InspectAngDef.z * -1 + end + end + + target_pos = LerpVector(customizationProgress, target_pos, self2.GetStatL(self, "InspectPos")) + target_ang = LerpVector(customizationProgress, target_ang, self2.GetStatL(self, "InspectAng")) + end + + target_pos, target_ang = self:CalculateNearWall(target_pos, target_ang) + + if additivePos then + target_pos:Add(self2.GetStatL(self, "ViewModelPosition")) + target_ang:Add(self2.GetStatL(self, "ViewModelAngle")) + end + + target_ang.z = target_ang.z + -7.5 * (1 - math.abs(0.5 - ironSightsProgress) * 2) * (self:GetIronSights() and 1 or 0.5) * (self2.ViewModelFlip and 1 or -1) * (self2.VM_IronPositionScore or 1) + + if self:GetHidden() then + target_pos.z = target_pos.z - 5 + end + + if self2.GetStatL(self, "BlowbackEnabled") and self2.BlowbackCurrentRoot > 0.01 then + local bbvec = self2.GetStatL(self, "BlowbackVector") + target_pos = target_pos + bbvec * self2.BlowbackCurrentRoot + local bbang = self2.GetStatL(self, "BlowbackAngle") or angle_zero + bbvec = bbvec * 1 + bbvec.x = bbang.p + bbvec.y = bbang.y + bbvec.z = bbang.r + target_ang = target_ang + bbvec * self2.BlowbackCurrentRoot + bbang = self2.BlowbackRandomAngle * (1 - math.max(0, ironSightsProgress) * .8) + bbvec.x = bbang.p + bbvec.y = bbang.y + bbvec.z = bbang.r + target_ang = target_ang + bbvec * self2.BlowbackCurrentRoot + end + + if not sv_tfa_recoil_legacy:GetBool() and cl_tfa_viewmodel_vp_enabled:GetBool() then + if self:HasRecoilLUT() then + if not ironSights then + local ang = self:GetRecoilLUTAngle() + + target_ang.x = target_ang.x - ang.p / 2 * Lerp(ironSightsProgress, self:GetStatL("ViewModelPunchPitchMultiplier") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_pitch), self:GetStatL("ViewModelPunchPitchMultiplier_IronSights") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_pitch_is)) + target_ang.y = target_ang.y + ang.y / 2 * Lerp(ironSightsProgress, self:GetStatL("ViewModelPunchYawMultiplier") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_yaw), self:GetStatL("ViewModelPunchYawMultiplier_IronSights") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_yaw_is)) + end + else + target_ang.x = target_ang.x - self:GetViewPunchP() * Lerp(ironSightsProgress, self:GetStatL("ViewModelPunchPitchMultiplier") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_pitch), self:GetStatL("ViewModelPunchPitchMultiplier_IronSights") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_pitch_is)) + target_ang.y = target_ang.y + self:GetViewPunchY() * Lerp(ironSightsProgress, self:GetStatL("ViewModelPunchYawMultiplier") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_yaw), self:GetStatL("ViewModelPunchYawMultiplier_IronSights") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_yaw_is)) + + local ViewModelPunch_MaxVertialOffset = Lerp(ironSightsProgress, self:GetStatL("ViewModelPunch_MaxVertialOffset") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_max_vertical), self:GetStatL("ViewModelPunch_MaxVertialOffset_IronSights") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_max_vertical_is)) + + target_pos.y = target_pos.y + math.Clamp( + self:GetViewPunchP() * Lerp(ironSightsProgress, self:GetStatL("ViewModelPunch_VertialMultiplier") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_vertical), self:GetStatL("ViewModelPunch_VertialMultiplier_IronSights") * GetClampedCVarFloat(cl_tfa_viewmodel_vp_vertical_is)), + -ViewModelPunch_MaxVertialOffset, + ViewModelPunch_MaxVertialOffset) + end + end + + if not cv_customgunbob:GetBool() then + self2.pos_cached, self2.ang_cached = Vector(target_pos), Angle(target_ang.x, target_ang.y, target_ang.z) + + return + end + + local intensityWalk = math.min(self:GetOwner():GetVelocity():Length2D() / self:GetOwner():GetWalkSpeed(), 1) * Lerp(ironSightsProgress, self2.WalkBobMult, self2.WalkBobMult_Iron or self2.WalkBobMult) + local intensityBreath = Lerp(ironSightsProgress, self2.GetStatL(self, "BreathScale", 0.2), self2.GetStatL(self, "IronBobMultWalk", 0.5) * intensityWalk) + intensityWalk = (1 - ironSightsProgress) * intensityWalk + local intensityRun = Lerp(self2.SprintProgressUnpredicted3 or self2.SprintProgressUnpredicted or self:GetSprintProgress(), 0, self2.SprintBobMult) + local velocity = math.max(self:GetOwner():GetVelocity():Length2D() * self:AirWalkScale() - self:GetOwner():GetVelocity().z * 0.5, 0) + local rate = math.min(math.max(0.15, math.sqrt(velocity / self:GetOwner():GetRunSpeed()) * 1.75), self:GetSprinting() and 5 or 3) + + self2.pos_cached, self2.ang_cached = self:WalkBob( + target_pos, + Angle(target_ang.x, target_ang.y, target_ang.z), + math.max(intensityBreath - intensityWalk - intensityRun, 0), + math.max(intensityWalk - intensityRun, 0), rate, delta) +end + +local rft, eyeAngles, viewPunch, oldEyeAngles, delta, motion, counterMotion, compensation, fac, positionCompensation, swayRate, wiggleFactor, flipFactor + +local gunswaycvar = GetConVar("cl_tfa_gunbob_intensity") +local gunswayinvertcvar = GetConVar("cl_tfa_gunbob_invertsway") +local sv_tfa_weapon_weight = GetConVar("sv_tfa_weapon_weight") + +function SWEP:Sway(pos, ang, ftv) + local self2 = self:GetTable() + --sanity check + if not self:OwnerIsValid() then return pos, ang end + --convar + fac = GetClampedCVarFloat(gunswaycvar) * 3 * ((1 - ((self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress()) or 0)) * 0.85 + 0.15) + if gunswayinvertcvar:GetBool() then fac = -fac end + flipFactor = (self2.ViewModelFlip and -1 or 1) + --init vars + delta = delta or Angle() + motion = motion or Angle() + counterMotion = counterMotion or Angle() + compensation = compensation or Angle() + + if ftv then + --grab eye angles + eyeAngles = self:GetOwner():EyeAngles() + viewPunch = self:GetOwner():GetViewPunchAngles() + eyeAngles.p = eyeAngles.p - viewPunch.p + eyeAngles.y = eyeAngles.y - viewPunch.y + oldEyeAngles = oldEyeAngles or eyeAngles + --calculate delta + wiggleFactor = (1 - (sv_tfa_weapon_weight:GetBool() and self2.GetStatL(self, "RegularMoveSpeedMultiplier") or 1)) / 0.6 + 0.15 + swayRate = math.pow(sv_tfa_weapon_weight:GetBool() and self2.GetStatL(self, "RegularMoveSpeedMultiplier") or 1, 1.5) * 10 + rft = math.Clamp(ftv, 0.001, 1 / 20) + local clampFac = 1.1 - math.min((math.abs(motion.p) + math.abs(motion.y) + math.abs(motion.r)) / 20, 1) + delta.p = math.AngleDifference(eyeAngles.p, oldEyeAngles.p) / rft / 120 * clampFac + delta.y = math.AngleDifference(eyeAngles.y, oldEyeAngles.y) / rft / 120 * clampFac + delta.r = math.AngleDifference(eyeAngles.r, oldEyeAngles.r) / rft / 120 * clampFac + oldEyeAngles = eyeAngles + --calculate motions, based on Juckey's methods + counterMotion = LerpAngle(rft * (swayRate * (0.75 + math.max(0, 0.5 - wiggleFactor))), counterMotion, -motion) + compensation.p = math.AngleDifference(motion.p, -counterMotion.p) + compensation.y = math.AngleDifference(motion.y, -counterMotion.y) + motion = LerpAngle(rft * swayRate, motion, delta + compensation) + end + + --modify position/angle + positionCompensation = 0.2 + 0.2 * ((self2.IronSightsProgressUnpredicted or self:GetIronSightsProgress()) or 0) + pos:Add(-motion.y * positionCompensation * 0.66 * fac * ang:Right() * flipFactor) --compensate position for yaw + pos:Add(-motion.p * positionCompensation * fac * ang:Up()) --compensate position for pitch + ang:RotateAroundAxis(ang:Right(), motion.p * fac) + ang:RotateAroundAxis(ang:Up(), -motion.y * 0.66 * fac * flipFactor) + ang:RotateAroundAxis(ang:Forward(), counterMotion.r * 0.5 * fac * flipFactor) + + return pos, ang +end + +local mirror = Matrix() + +hook.Add("PostRender", "TFA:CacheSightsPos", function() + local self = LocalPlayer():GetActiveWeapon() + if not IsValid(self) then return end + local self2 = self:GetTable() + if not self2.IsTFAWeapon then return end + if not self2.ViewModelFlip then return end + + if not self2.VMIV(self) then return end + local vm = self2.OwnerViewModel + + self2.ViewModelFlip = false + + vm:SetRenderOrigin(vector_origin) + vm:SetRenderAngles(angle_zero) + + vm:InvalidateBoneCache() + vm:SetupBones() + + local ViewModelElements = self:GetStatRaw("ViewModelElements", TFA.LatestDataVersion) + + if ViewModelElements and self2.HasInitAttachments then + if not self2.vRenderOrder then + self:RebuildModsRenderOrder() + end + + TFA._IncNextSetupBones() + + for index = 1, #self2.vRenderOrder do + local name = self2.vRenderOrder[index] + local element = ViewModelElements[name] + + if not element then + self:RebuildModsRenderOrder() + break + end + + if element.type ~= "Model" then goto CONTINUE end + + if element.hide then goto CONTINUE end + if not element.bone then goto CONTINUE end + + if self2.GetStatL(self, "ViewModelElements." .. name .. ".active") == false then goto CONTINUE end + + local pos, ang = self:GetBoneOrientation(ViewModelElements, element, vm, nil, true) + if not pos and not element.bonemerge then goto CONTINUE end + + self:PrecacheElement(element, true) + + local model = element.curmodel + local sprite = element.spritemat + + if IsValid(model) then + if not element.bonemerge then + model:SetPos(pos + ang:Forward() * element.pos.x + ang:Right() * element.pos.y + ang:Up() * element.pos.z) + ang:RotateAroundAxis(ang:Up(), element.angle.y) + ang:RotateAroundAxis(ang:Right(), element.angle.p) + ang:RotateAroundAxis(ang:Forward(), element.angle.r) + model:SetAngles(ang) + mirror:Identity() + mirror:Scale(element.size) + model:EnableMatrix("RenderMultiply", mirror) + end + + if not self2.VElementsBodygroupsCache[index] then + self2.VElementsBodygroupsCache[index] = #model:GetBodyGroups() - 1 + end + + if self2.VElementsBodygroupsCache[index] then + for _b = 0, self2.VElementsBodygroupsCache[index] do + local newbg = self2.GetStatL(self, "ViewModelElements." .. name .. ".bodygroup." .. _b, 0) -- names are not supported, use overridetable + + if model:GetBodygroup(_b) ~= newbg then + model:SetBodygroup(_b, newbg) + end + end + end + + if element.bonemerge then + if element.rel and ViewModelElements[element.rel] and IsValid(ViewModelElements[element.rel].curmodel) then + element.parModel = ViewModelElements[element.rel].curmodel + else + element.parModel = self2.OwnerViewModel or self + end + + if model:GetParent() ~= element.parModel then + model:SetParent(element.parModel) + end + + if not model:IsEffectActive(EF_BONEMERGE) then + model:AddEffects(EF_BONEMERGE) + model:AddEffects(EF_BONEMERGE_FASTCULL) + model:SetMoveType(MOVETYPE_NONE) + model:SetLocalPos(vector_origin) + model:SetLocalAngles(angle_zero) + end + elseif model:IsEffectActive(EF_BONEMERGE) then + model:RemoveEffects(EF_BONEMERGE) + model:SetParent(NULL) + end + + model:InvalidateBoneCache() + model:SetupBones() + model.tfa_next_setup_bones = TFA._GetNextSetupBones() + end + + ::CONTINUE:: + end + end + + self:CacheSightsPos(vm, true) + + vm:SetRenderOrigin() + vm:SetRenderAngles() + + self.ViewModelFlip = true + vm:InvalidateBoneCache() +end) + +function SWEP:CacheSightsPos(vm, flipped) + self.SightsAttPos, self.SightsAttAng = nil, nil + + if not self:GetStat("ProceduralSight", false) then return end + + local model = vm + local attname = self:GetStat("ProceduralSight_VElement") + + if attname then + if not self:GetStat("VElements." .. attname .. ".active", false) then return end + + model = self.VElements[attname].curmodel + end + + if not IsValid(model) then return end + + local ViewModelElements = self:GetStatRaw("ViewModelElements", TFA.LatestDataVersion) + + TFA._IncNextSetupBones() + + if self:GetStat("ProceduralSight_PositionType", TFA.Enum.SIGHTSPOS_ATTACH) == TFA.Enum.SIGHTSPOS_BONE then + local boneid = self:GetStat("ProceduralSight_Bone") + if not boneid then return end + + if type(boneid) == "string" then + boneid = model:LookupBone(boneid) + end + + if not boneid or boneid < 0 then return end + + self.SightsAttPos, self.SightsAttAng = model:GetBonePosition(boneid) + else + local attid = self:GetStat("ProceduralSight_Attachment") + if not attid then return end + + if type(attid) == "string" then + attid = model:LookupAttachment(attid) + end + + if not attid or attid <= 0 then return end + + local attpos = model:GetAttachment(attid) + + self.SightsAttPos, self.SightsAttAng = attpos.Pos, attpos.Ang + end + + if self.SightsAttPos and self.SightsAttAng then + if not flipped then + local transform = Matrix() + transform:Translate(vm:GetPos()) + transform:Rotate(vm:GetAngles()) + transform:Invert() + + transform:Translate(self.SightsAttPos) + transform:Rotate(self.SightsAttAng) + + self.SightsAttPos, self.SightsAttAng = transform:GetTranslation(), transform:GetAngles() + end + + local OffsetPos = self:GetStatL("ProceduralSight_OffsetPos") + + if OffsetPos then + if GetConVarNumber("developer") > 0 then -- draw pre-offset pos + local a, b = LocalToWorld(self.SightsAttPos, self.SightsAttAng, vm:GetPos(), vm:GetAngles()) + + render.DrawLine(a, a + b:Forward() * 1, Color(127, 0, 0), false) + render.DrawLine(a, a - b:Right() * 1, Color(0, 127, 0), false) + render.DrawLine(a, a + b:Up() * 1, Color(0, 0, 127), false) + end + + self.SightsAttPos:Add(self.SightsAttAng:Right() * OffsetPos.x) + self.SightsAttPos:Add(self.SightsAttAng:Forward() * OffsetPos.y) + self.SightsAttPos:Add(self.SightsAttAng:Up() * OffsetPos.z) + end + + local OffsetAng = self:GetStatL("ProceduralSight_OffsetAng") + + if OffsetAng then + self.SightsAttAng:RotateAroundAxis(self.SightsAttAng:Right(), OffsetAng.p) + self.SightsAttAng:RotateAroundAxis(self.SightsAttAng:Up(), OffsetAng.y) + self.SightsAttAng:RotateAroundAxis(self.SightsAttAng:Forward(), OffsetAng.r) + end + + if GetConVarNumber("developer") > 0 then -- draw final pos + local a, b = LocalToWorld(self.SightsAttPos, self.SightsAttAng, vm:GetPos(), vm:GetAngles()) + + render.DrawLine(a, a + b:Forward() * 1, Color(255, 0, 0), false) + render.DrawLine(a, a - b:Right() * 1, Color(0, 255, 0), false) + render.DrawLine(a, a + b:Up() * 1, Color(0, 0, 255), false) + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/ai_translations.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/ai_translations.lua new file mode 100644 index 0000000..a5d9233 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/ai_translations.lua @@ -0,0 +1,150 @@ +local HoldTypeTable = { + ["melee"] = { + [ACT_IDLE] = ACT_IDLE_MELEE, + [ACT_IDLE_ANGRY] = ACT_IDLE_MELEE, + [ACT_IDLE_RELAXED] = ACT_IDLE_MELEE, + [ACT_IDLE_STIMULATED] = ACT_IDLE_MELEE, + [ACT_IDLE_AGITATED] = ACT_IDLE_MELEE, + [ACT_IDLE_AIM_RELAXED] = ACT_IDLE_MELEE, + [ACT_IDLE_AIM_STIMULATED] = ACT_IDLE_MELEE, + [ACT_IDLE_AIM_AGITATED] = ACT_IDLE_MELEE, + [ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_THROW, + [ACT_RANGE_ATTACK1_LOW] = ACT_MELEE_ATTACK_SWING, + [ACT_MELEE_ATTACK1] = ACT_MELEE_ATTACK_SWING, + [ACT_MELEE_ATTACK2] = ACT_MELEE_ATTACK_SWING, + [ACT_SPECIAL_ATTACK1] = ACT_RANGE_ATTACK_THROW, + [ACT_RANGE_AIM_LOW] = ACT_IDLE_MELEE, + [ACT_COVER_LOW] = ACT_IDLE_MELEE, + [ACT_WALK] = ACT_WALK_SUITCASE, + [ACT_WALK_RELAXED] = ACT_WALK_SUITCASE, + [ACT_WALK_STIMULATED] = ACT_WALK_SUITCASE, + [ACT_WALK_AGITATED] = ACT_WALK_SUITCASE, + [ACT_RUN_CROUCH] = ACT_RUN_CROUCH, + [ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH, + [ACT_RUN] = ACT_RUN, + [ACT_RUN_AIM_RELAXED] = ACT_RUN, + [ACT_RUN_AIM_STIMULATED] = ACT_RUN, + [ACT_RUN_AIM_AGITATED] = ACT_RUN, + [ACT_RUN_AIM] = ACT_RUN, + [ACT_SMALL_FLINCH] = ACT_RANGE_ATTACK_PISTOL, + [ACT_BIG_FLINCH] = ACT_RANGE_ATTACK_PISTOL + }, + ["melee2"] = { + [ACT_IDLE] = ACT_IDLE_ANGRY_MELEE, + [ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_MELEE, + [ACT_IDLE_RELAXED] = ACT_IDLE, + [ACT_IDLE_STIMULATED] = ACT_IDLE, + [ACT_IDLE_AGITATED] = ACT_IDLE_ANGRY_MELEE, + [ACT_MP_RUN] = ACT_HL2MP_RUN_SUITCASE, + [ACT_RUN] = ACT_RUN, + [ACT_RUN_AIM_RELAXED] = ACT_RUN, + [ACT_RUN_AIM_STIMULATED] = ACT_RUN, + [ACT_RUN_AIM_AGITATED] = ACT_RUN, + [ACT_RUN_AIM] = ACT_RUN, + [ACT_WALK] = ACT_WALK_SUITCASE, + [ACT_MELEE_ATTACK1] = ACT_MELEE_ATTACK_SWING, + [ACT_RANGE_ATTACK1] = ACT_MELEE_ATTACK_SWING, + [ACT_MELEE_ATTACK2] = ACT_MELEE_ATTACK_SWING, + [ACT_RANGE_ATTACK2] = ACT_MELEE_ATTACK_SWING, + [ACT_SPECIAL_ATTACK1] = ACT_RANGE_ATTACK_THROW, + [ACT_SMALL_FLINCH] = ACT_RANGE_ATTACK_PISTOL, + [ACT_BIG_FLINCH] = ACT_RANGE_ATTACK_PISTOL + }, + ["pistol"] = { + [ACT_IDLE] = ACT_IDLE_PISTOL, + [ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_PISTOL, + [ACT_IDLE_AGITATED] = ACT_IDLE_ANGRY_PISTOL, + [ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_PISTOL, + [ACT_RELOAD] = ACT_RELOAD_PISTOL, + [ACT_WALK] = ACT_WALK_PISTOL, + [ACT_WALK_AIM] = ACT_WALK_AIM_PISTOL, + [ACT_RUN] = ACT_RUN_PISTOL, + [ACT_RUN_AIM] = ACT_RUN_AIM_PISTOL, + [ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_PISTOL, + [ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_PISTOL, + [ACT_RELOAD_LOW] = ACT_RELOAD_PISTOL_LOW, + [ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_PISTOL_LOW, + [ACT_COVER_LOW] = ACT_COVER_PISTOL_LOW, + [ACT_RANGE_AIM_LOW] = ACT_RANGE_AIM_PISTOL_LOW, + [ACT_GESTURE_RELOAD] = ACT_GESTURE_RELOAD_PISTOL + }, + ["ar2"] = { + [ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_AR2, + [ACT_RELOAD] = ACT_RELOAD_SMG1, + [ACT_IDLE] = ACT_IDLE_SMG1, + [ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_SMG1, + [ACT_WALK] = ACT_WALK_RIFLE, + [ACT_IDLE_RELAXED] = ACT_IDLE_SMG1_RELAXED, + [ACT_IDLE_STIMULATED] = ACT_IDLE_SMG1_STIMULATED, + [ACT_IDLE_AGITATED] = ACT_IDLE_ANGRY_SMG1, + [ACT_WALK_RELAXED] = ACT_WALK_RIFLE_RELAXED, + [ACT_WALK_STIMULATED] = ACT_WALK_RIFLE_STIMULATED, + [ACT_WALK_AGITATED] = ACT_WALK_AIM_RIFLE, + [ACT_RUN_RELAXED] = ACT_RUN_RIFLE_RELAXED, + [ACT_RUN_STIMULATED] = ACT_RUN_RIFLE_STIMULATED, + [ACT_RUN_AGITATED] = ACT_RUN_AIM_RIFLE, + [ACT_IDLE_AIM_RELAXED] = ACT_IDLE_SMG1_RELAXED, + [ACT_IDLE_AIM_STIMULATED] = ACT_IDLE_AIM_RIFLE_STIMULATED, + [ACT_IDLE_AIM_AGITATED] = ACT_IDLE_ANGRY_SMG1, + [ACT_WALK_AIM_RELAXED] = ACT_WALK_RIFLE_RELAXED, + [ACT_WALK_AIM_STIMULATED] = ACT_WALK_AIM_RIFLE_STIMULATED, + [ACT_WALK_AIM_AGITATED] = ACT_WALK_AIM_RIFLE, + [ACT_RUN_AIM_RELAXED] = ACT_RUN_RIFLE_RELAXED, + [ACT_RUN_AIM_STIMULATED] = ACT_RUN_AIM_RIFLE_STIMULATED, + [ACT_RUN_AIM_AGITATED] = ACT_RUN_AIM_RIFLE, + [ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE, + [ACT_WALK_CROUCH] = ACT_WALK_CROUCH_RIFLE, + [ACT_WALK_CROUCH_AIM] = ACT_WALK_CROUCH_AIM_RIFLE, + [ACT_RUN] = ACT_RUN_RIFLE, + [ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE, + [ACT_RUN_CROUCH] = ACT_RUN_CROUCH_RIFLE, + [ACT_RUN_CROUCH_AIM] = ACT_RUN_CROUCH_AIM_RIFLE, + [ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_AR2, + [ACT_COVER_LOW] = ACT_COVER_SMG1_LOW, + [ACT_RANGE_AIM_LOW] = ACT_RANGE_AIM_AR2_LOW, + [ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW, + [ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW, + [ACT_GESTURE_RELOAD] = ACT_GESTURE_RELOAD_SMG1 + }, + ["smg"] = { + [ACT_IDLE] = ACT_IDLE_SMG1, + [ACT_IDLE_ANGRY] = ACT_IDLE_ANGRY_SMG1, + [ACT_RANGE_ATTACK1] = ACT_RANGE_ATTACK_SMG1, + [ACT_RELOAD] = ACT_RELOAD_SMG1, + [ACT_WALK_AIM] = ACT_WALK_AIM_RIFLE, + [ACT_RUN_AIM] = ACT_RUN_AIM_RIFLE, + [ACT_GESTURE_RANGE_ATTACK1] = ACT_GESTURE_RANGE_ATTACK_SMG1, + [ACT_RELOAD_LOW] = ACT_RELOAD_SMG1_LOW, + [ACT_RANGE_ATTACK1_LOW] = ACT_RANGE_ATTACK_SMG1_LOW, + [ACT_COVER_LOW] = ACT_COVER_SMG1_LOW, + [ACT_RANGE_AIM_LOW] = ACT_RANGE_AIM_SMG1_LOW, + [ACT_GESTURE_RELOAD] = ACT_GESTURE_RELOAD_SMG1 + } +} + +function SWEP:SetupWeaponHoldTypeForAI(t) + local usedT = HoldTypeTable[t or "ar2"] or HoldTypeTable["ar2"] + self.ActivityTranslateAI = table.Copy(usedT) +end + +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 + +function SWEP:GetCapabilities() + local ht = self.DefaultHoldType or self.HoldType or "pistol" + + if ht == "melee" or ht == "melee2" or ht == "knife" or self.IsKnife then + return CAP_WEAPON_MELEE_ATTACK1 + else + return bit.bor(CAP_WEAPON_RANGE_ATTACK1, CAP_INNATE_RANGE_ATTACK1) + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/akimbo.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/akimbo.lua new file mode 100644 index 0000000..a1502ac --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/akimbo.lua @@ -0,0 +1,32 @@ +SWEP.AnimCycle = SWEP.ViewModelFlip and 0 or 1 + +function SWEP:FixAkimbo() + if not self:GetStatL("IsAkimbo") or self.Secondary_TFA.ClipSize <= 0 then return end + + self.Primary_TFA.ClipSize = self.Primary_TFA.ClipSize + self.Secondary_TFA.ClipSize + self.Secondary_TFA.ClipSize = -1 + self.Primary_TFA.RPM = self.Primary_TFA.RPM * 2 + self.Akimbo_Inverted = self.ViewModelFlip + self:ResetAnimCycle() + self:ClearStatCache() + + timer.Simple(FrameTime(), function() + timer.Simple(0.01, function() + if IsValid(self) and self:OwnerIsValid() then + self:SetClip1(self.Primary_TFA.ClipSize) + end + end) + end) +end + +function SWEP:ResetAnimCycle() + self:SetAnimCycle(self.Akimbo_Inverted and 0 or 1) + self.AnimCycle = self:GetAnimCycle() +end + +function SWEP:ToggleAkimbo(arg1) + if self:GetStatL("IsAkimbo") then + self:SetAnimCycle(1 - self:GetAnimCycle()) + self.AnimCycle = self:GetAnimCycle() + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/anims.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/anims.lua new file mode 100644 index 0000000..46f39fa --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/anims.lua @@ -0,0 +1,1353 @@ +local sp = game.SinglePlayer() + +SWEP.Locomotion_Data_Queued = nil + +local ServersideLooped = { + [ACT_VM_FIDGET] = true, + [ACT_VM_FIDGET_EMPTY] = true +} + +--[ACT_VM_IDLE] = true, +--[ACT_VM_IDLE_EMPTY] = true, +--[ACT_VM_IDLE_SILENCED] = true +local d, pbr + +-- Override this after SWEP:Initialize, for example, in attachments +SWEP.BaseAnimations = { + ["draw_first"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DRAW_DEPLOYED, + ["enabled"] = nil --Manually force a sequence to be enabled + }, + ["draw"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DRAW + }, + ["draw_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DRAW_EMPTY + }, + ["draw_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DRAW_SILENCED + }, + ["shoot1"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_PRIMARYATTACK + }, + ["shoot1_last"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_PRIMARYATTACK_EMPTY + }, + ["shoot1_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DRYFIRE + }, + ["shoot1_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_PRIMARYATTACK_SILENCED + }, + ["shoot1_silenced_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DRYFIRE_SILENCED or 0 + }, + ["shoot1_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_PRIMARYATTACK_1 + }, + ["shoot2"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_SECONDARYATTACK + }, + ["shoot2_last"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "shoot2_last" + }, + ["shoot2_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DRYFIRE + }, + ["shoot2_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "shoot2_silenced" + }, + ["shoot2_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_ISHOOT_M203 + }, + ["idle"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_IDLE + }, + ["idle_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_IDLE_EMPTY + }, + ["idle_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_IDLE_SILENCED + }, + ["reload"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_RELOAD + }, + ["reload_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_RELOAD_EMPTY + }, + ["reload_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_RELOAD_SILENCED + }, + ["reload_shotgun_start"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, + ["value"] = ACT_SHOTGUN_RELOAD_START + }, + ["reload_shotgun_finish"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, + ["value"] = ACT_SHOTGUN_RELOAD_FINISH + }, + ["reload_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, + ["value"] = ACT_VM_RELOAD_ADS + }, + ["reload_empty_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, + ["value"] = ACT_VM_RELOAD_EMPTY_ADS + }, + ["reload_silenced_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, + ["value"] = ACT_VM_RELOAD_SILENCED_ADS + }, + ["reload_shotgun_start_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, + ["value"] = ACT_SHOTGUN_RELOAD_START_ADS + }, + ["reload_shotgun_finish_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, + ["value"] = ACT_SHOTGUN_RELOAD_FINISH_ADS + }, + ["holster"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_HOLSTER + }, + ["holster_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_HOLSTER_EMPTY + }, + ["holster_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_HOLSTER_SILENCED + }, + ["silencer_attach"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_ATTACH_SILENCER + }, + ["silencer_detach"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DETACH_SILENCER + }, + ["rof"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_FIREMODE + }, + ["rof_is"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_IFIREMODE + }, + ["bash"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_HITCENTER + }, + ["bash_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_HITCENTER2 + }, + ["bash_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_MISSCENTER + }, + ["bash_empty_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_MISSCENTER2 + }, + ["inspect"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_FIDGET + }, + ["inspect_empty"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_FIDGET_EMPTY + }, + ["inspect_silenced"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_FIDGET_SILENCED + } +} + +SWEP.Animations = {} + +function SWEP:InitializeAnims() + local self2 = self:GetTable() + + setmetatable(self2.Animations, { + __index = function(t, k) return self2.BaseAnimations[k] end + }) +end + +function SWEP:BuildAnimActivities() + local self2 = self:GetTable() + self2.AnimationActivities = self2.AnimationActivities or {} + + for k, v in pairs(self2.BaseAnimations) do + if v.value then + self2.AnimationActivities[v.value] = k + end + + local kvt = self2.GetStatL(self, "Animations." .. k) + + if kvt.value then + self2.AnimationActivities[kvt.value] = k + end + end + + for k, _ in pairs(self2.Animations) do + local kvt = self2.GetStatL(self, "Animations." .. k) + + if kvt.value then + self2.AnimationActivities[kvt.value] = k + end + end +end + +function SWEP:GetActivityEnabled(act) + local self2 = self:GetTable() + local stat = self2.GetStatL(self, "SequenceEnabled." .. act) + if stat then return stat end + + if not self2.AnimationActivities then + self:BuildAnimActivities() + end + + local keysel = self2.AnimationActivities[act] or "" + local kv = self2.GetStatL(self, "Animations." .. keysel) + if not kv then return false end + + if kv["enabled"] then + return kv["enabled"] + else + return false + end +end + +function SWEP:ChooseAnimation(keyOrData) + local self2 = self:GetTable() + + local data + + if isstring(keyOrData) then + data = self2.GetStatL(self, "Animations." .. keyOrData) + elseif istable(keyOrData) then + data = keyOrData + else + error("Unknown value type " .. type(keyOrData) .. " passed!") + end + + if not data then return 0, 0 end + if not data["type"] then return 0, 0 end + if not data["value"] then return 0, 0 end + + local retType, retValue = data["type"], data["value"] + + if self:Clip1() <= 0 and self2.GetStatL(self, "Primary.ClipSize") >= 0 then + if data.value_empty then + retValue = data.value_empty + retType = data.type_empty or retType + end + end + + if self:Clip1() == 1 and self2.GetStatL(self, "Primary.ClipSize") >= 0 then + if data.value_last then + retValue = data.value_last + retType = data.type_last or retType + end + end + + if self2.GetSilenced(self) then + local previousRetType = retType + + if data.value_sil then + retValue = data.value_sil + retType = data.type_sil or previousRetType + end + + if self:Clip1() <= 0 and self2.GetStatL(self, "Primary.ClipSize") >= 0 then + if data.value_sil_empty then + retValue = data.value_sil_empty + retType = data.type_sil_empty or previousRetType + end + end + end + + if self:GetIronSights() then + local previousRetType = retType + + if data.value_is then + retValue = data.value_is + retType = data.type_is or previousRetType + end + + if self:Clip1() <= 0 and self2.GetStatL(self, "Primary.ClipSize") >= 0 then + if data.value_is_empty then + retValue = data.value_is_empty + retType = data.type_is_empty or previousRetType + end + end + + if self:Clip1() == 1 and self2.GetStatL(self, "Primary.ClipSize") >= 0 then + if data.value_is_last then + retValue = data.value_is_last + retType = data.type_is_last or previousRetType + end + end + + if self2.GetSilenced(self) then + if data.value_is_sil then + retValue = data.value_is_sil + retType = data.type_is_sil or previousRetType + end + + if self:Clip1() <= 0 and self2.GetStatL(self, "Primary.ClipSize") >= 0 then + if data.value_is_sil_empty then + retValue = data.value_is_sil_empty + retType = data.type_is_sil_empty or previousRetType + end + end + + if self:Clip1() == 1 and self2.GetStatL(self, "Primary.ClipSize") >= 0 then + if data.value_is_sil_last then + retValue = data.value_is_sil_last + retType = data.type_is_sil_last or previousRetType + end + end + end + end + + if retType == TFA.Enum.ANIMATION_ACT and isstring(retValue) then + retValue = tonumber(retValue) or -1 + elseif retType == TFA.Enum.ANIMATION_SEQ and isstring(retValue) then + retValue = self2.OwnerViewModel:LookupSequence(retValue) + end + + return retType, retValue +end + +function SWEP:GetAnimationRate(ani, animationType) + local self2 = self:GetTable() + local rate = 1 + if not ani or ani < 0 or not self2.VMIV(self) then return rate end + + local nm + + if animationType == TFA.Enum.ANIMATION_ACT or animationType == nil then + nm = self2.OwnerViewModel:GetSequenceName(self2.OwnerViewModel:SelectWeightedSequence(ani)) + elseif isnumber(ani) then + nm = self2.OwnerViewModel:GetSequenceName(ani) + elseif isstring(ani) then + nm = ani + else + error("ani argument is typeof " .. type(ani)) + end + + local sqto = self2.GetStatL(self, "SequenceTimeOverride." .. nm) or self2.GetStatL(self, "SequenceTimeOverride." .. (ani or "0")) + local sqro = self2.GetStatL(self, "SequenceRateOverride." .. nm) or self2.GetStatL(self, "SequenceRateOverride." .. (ani or "0")) + + if sqro then + rate = rate * sqro + elseif sqto then + local t = self:GetActivityLengthRaw(ani, false) + + if t then + rate = rate * t / sqto + end + end + + rate = hook.Run("TFA_AnimationRate", self, ani, rate) or rate + + return rate +end + +function SWEP:SendViewModelAnim(act, rate, targ, blend) + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, act end + local vm = self2.OwnerViewModel + + if rate and not targ then + rate = math.max(rate, 0.0001) + end + + if not rate then + rate = 1 + end + + if targ then + rate = rate / self:GetAnimationRate(act) + else + rate = rate * self:GetAnimationRate(act) + end + + if act < 0 then return false, act end + local seq = vm:SelectWeightedSequenceSeeded(act, self:GetSeedIrradical()) + + if seq < 0 then + if act == ACT_VM_IDLE_EMPTY then + seq = vm:SelectWeightedSequenceSeeded(ACT_VM_IDLE, self:GetSeedIrradical()) + elseif act == ACT_VM_PRIMARYATTACK_EMPTY then + seq = vm:SelectWeightedSequenceSeeded(ACT_VM_PRIMARYATTACK, self:GetSeedIrradical()) + else + return false, 0 + end + + if seq < 0 then return false, act end + end + + local preLastActivity = self:GetLastActivity() + --local preLastSequence = self:GetLastActivity() + self:SetLastActivity(act) + self:SetLastSequence(seq) + self:ResetEvents() + + if preLastActivity == act and ServersideLooped[act] then + self:ChooseIdleAnim() + d = vm:SequenceDuration(seq) + pbr = targ and (d / (rate or 1)) or (rate or 1) + + if IsValid(self) then + if blend == nil then + blend = self2.Idle_Smooth + end + + self:SetNextIdleAnim(CurTime() + d / pbr - blend) + end + + if IsFirstTimePredicted() then + timer.Simple(0, function() + vm:SendViewModelMatchingSequence(seq) + d = vm:SequenceDuration() + pbr = targ and (d / (rate or 1)) or (rate or 1) + vm:SetPlaybackRate(pbr) + + if IsValid(self) then + if blend == nil then + blend = self2.Idle_Smooth + end + + self:SetNextIdleAnim(CurTime() + d / pbr - blend) + self:SetLastActivity(act) + end + end) + end + else + if seq >= 0 then + vm:SendViewModelMatchingSequence(seq) + end + + d = vm:SequenceDuration() + pbr = targ and (d / (rate or 1)) or (rate or 1) + vm:SetPlaybackRate(pbr) + + if blend == nil then + blend = self2.Idle_Smooth + end + + self:SetNextIdleAnim(CurTime() + math.max(d / pbr - blend, self2.Idle_Smooth)) + end + + return true, act +end + +function SWEP:SendViewModelSeq(seq, rate, targ, blend) + local self2 = self:GetTable() + local seqold = seq + + if not self2.VMIV(self) then return false, 0 end + local vm = self2.OwnerViewModel + + if isstring(seq) then + seq = vm:LookupSequence(seq) or 0 + end + + local act = vm:GetSequenceActivity(seq) + + if self2.SequenceRateOverride[seqold] then + rate = self2.SequenceRateOverride[seqold] + targ = false + elseif self2.SequenceRateOverride[act] then + rate = self2.SequenceRateOverride[act] + targ = false + elseif self2.SequenceTimeOverride[seqold] then + rate = self2.SequenceTimeOverride[seqold] + targ = true + elseif self2.SequenceTimeOverride[act] then + rate = self2.SequenceTimeOverride[act] + targ = true + end + + if not rate then + rate = 1 + end + + if targ then + rate = rate / self:GetAnimationRate(seq, TFA.Enum.ANIMATION_SEQ) + else + rate = rate * self:GetAnimationRate(seq, TFA.Enum.ANIMATION_SEQ) + end + + if seq < 0 then return false, seq end + + local preLastActivity = self:GetLastActivity() + --local preLastSequence = self:GetLastSequence() + self:SetLastActivity(act) + self:SetLastSequence(seq) + self:ResetEvents() + + if preLastActivity == act and ServersideLooped[act] then + vm:SendViewModelMatchingSequence(act == 0 and 1 or 0) + vm:SetPlaybackRate(0) + vm:SetCycle(0) + self:SetNextIdleAnim(CurTime() + 0.03) + + if IsFirstTimePredicted() then + timer.Simple(0, function() + vm:SendViewModelMatchingSequence(seq) + d = vm:SequenceDuration() + pbr = targ and (d / (rate or 1)) or (rate or 1) + vm:SetPlaybackRate(pbr) + + if IsValid(self) then + if blend == nil then + blend = self2.Idle_Smooth + end + + self:SetNextIdleAnim(CurTime() + d / pbr - blend) + self:SetLastActivity(act) + end + end) + end + else + if seq >= 0 then + vm:SendViewModelMatchingSequence(seq) + end + + d = vm:SequenceDuration() + pbr = targ and (d / (rate or 1)) or (rate or 1) + vm:SetPlaybackRate(pbr) + + if IsValid(self) then + if blend == nil then + blend = self2.Idle_Smooth + end + + self:SetNextIdleAnim(CurTime() + d / pbr - blend) + end + end + + return true, seq +end + +function SWEP:PlayAnimation(data, fade, rate, targ) + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, -1 end + if not data then return false, -1 end + + local ttype, tval = self:ChooseAnimation(data) + + if ttype == TFA.Enum.ANIMATION_SEQ then + local success, activityID = self:SendViewModelSeq(tval, rate or 1, targ, fade or (data.transition and self2.Idle_Blend or self2.Idle_Smooth)) + return success, activityID, TFA.Enum.ANIMATION_SEQ + end + + local success, activityID = self:SendViewModelAnim(tval, rate or 1, targ, fade or (data.transition and self2.Idle_Blend or self2.Idle_Smooth)) + return success, activityID, TFA.Enum.ANIMATION_ACT +end + +--[[ +Function Name: Locomote +Syntax: self:Locomote( flip ironsights, new is, flip sprint, new sprint, flip walk, new walk). +Returns: +Notes: +Purpose: Animation / Utility +]] +local tldata + +function SWEP:Locomote(flipis, is, flipsp, spr, flipwalk, walk, flipcust, cust) + local self2 = self:GetTable() + if not (flipis or flipsp or flipwalk or flipcust) then return end + if not (self:GetStatus() == TFA.Enum.STATUS_IDLE or (self:GetStatus() == TFA.Enum.STATUS_SHOOTING and self:CanInterruptShooting())) then return end + tldata = nil + + if flipis then + if is and self2.GetStatL(self, "IronAnimation.in") then + tldata = self2.GetStatL(self, "IronAnimation.in", tldata) + elseif self2.GetStatL(self, "IronAnimation.out") and not flipsp then + tldata = self2.GetStatL(self, "IronAnimation.out", tldata) + end + end + + if flipsp then + if spr and self2.GetStatL(self, "SprintAnimation.in") then + tldata = self2.GetStatL(self, "SprintAnimation.in", tldata) + elseif self2.GetStatL(self, "SprintAnimation.out") and not flipis and not spr then + tldata = self2.GetStatL(self, "SprintAnimation.out", tldata) + end + end + + if flipwalk and not is then + if walk and self2.GetStatL(self, "WalkAnimation.in") then + tldata = self2.GetStatL(self, "WalkAnimation.in", tldata) + elseif self2.GetStatL(self, "WalkAnimation.out") and (not flipis and not flipsp and not flipcust) and not walk then + tldata = self2.GetStatL(self, "WalkAnimation.out", tldata) + end + end + + if flipcust then + if cust and self2.GetStatL(self, "CustomizeAnimation.in") then + tldata = self2.GetStatL(self, "CustomizeAnimation.in", tldata) + elseif self2.GetStatL(self, "CustomizeAnimation.out") and (not flipis and not flipsp and not flipwalk) and not cust then + tldata = self2.GetStatL(self, "CustomizeAnimation.out", tldata) + end + end + + if tldata then + return self:PlayAnimation(tldata) + end + + return false, -1, TFA.Enum.ANIMATION_SEQ +end + +function SWEP:LocomoteOrIdle(...) + local success, animID, animType = self:Locomote(...) + + if not success then + return self:SetNextIdleAnim(-1) + end + + return success, animID, animType +end + +--[[ +Function Name: ChooseDrawAnim +Syntax: self:ChooseDrawAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. +Purpose: Animation / Utility +]] +SWEP.IsFirstDeploy = true + +local function PlayChosenAnimation(self, typev, tanim, ...) + local fnName = typev == TFA.Enum.ANIMATION_SEQ and "SendViewModelSeq" or "SendViewModelAnim" + local a, b = self[fnName](self, tanim, ...) + return a, b, typev +end + +SWEP.PlayChosenAnimation = PlayChosenAnimation + +local success, tanim, typev + +local cv_firstdeploy = GetConVar("sv_tfa_first_draw_anim_enabled") + +function SWEP:ChooseDrawAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + --self:ResetEvents() + tanim = ACT_VM_DRAW + success = true + + local ct = CurTime() + + if self:GetIsFirstDeploy() and self:GetLastDeployAnim() >= 0 and ct > (self:GetLastDeployAnim() or ct) + 0.1 then + self:SetIsFirstDeploy(false) + self2.IsFirstDeploy = false + end + + if self:GetActivityEnabled(ACT_VM_DRAW_EMPTY) and self:IsEmpty1() then + typev, tanim = self:ChooseAnimation("draw_empty") + elseif (self:GetActivityEnabled(ACT_VM_DRAW_DEPLOYED) or self2.GetStat(self, "FirstDeployEnabled", false)) and self:GetIsFirstDeploy() and cv_firstdeploy:GetBool() then + typev, tanim = self:ChooseAnimation("draw_first") + elseif self:GetActivityEnabled(ACT_VM_DRAW_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation("draw_silenced") + else + typev, tanim = self:ChooseAnimation("draw") + end + + self:SetLastDeployAnim(ct) + self2.LastDeployAnim = ct + + return PlayChosenAnimation(self, typev, tanim) +end + +function SWEP:ResetFirstDeploy() + self.IsFirstDeploy = true + self:SetIsFirstDeploy(true) + + self.LastDeployAnim = -1 + self:SetLastDeployAnim(-1) +end + +--[[ +Function Name: ChooseInspectAnim +Syntax: self:ChooseInspectAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. +Purpose: Animation / Utility +]] +-- + +function SWEP:ChooseInspectAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + + if self:GetActivityEnabled(ACT_VM_FIDGET_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation("inspect_silenced") + elseif self:GetActivityEnabled(ACT_VM_FIDGET_EMPTY) and self:IsEmpty1() then + typev, tanim = self:ChooseAnimation("inspect_empty") + elseif self2.InspectionActions then + tanim = self2.InspectionActions[self:SharedRandom(1, #self2.InspectionActions, "Inspect")] + elseif self:GetActivityEnabled(ACT_VM_FIDGET) then + typev, tanim = self:ChooseAnimation("inspect") + else + typev, tanim = self:ChooseAnimation("idle") + success = false + end + + return PlayChosenAnimation(self, typev, tanim) +end + +--[[ +Function Name: ChooseHolsterAnim +Syntax: self:ChooseHolsterAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseHolsterAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + + if self:GetActivityEnabled(ACT_VM_HOLSTER_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation("holster_silenced") + elseif self:GetActivityEnabled(ACT_VM_HOLSTER_EMPTY) and self:IsEmpty1() then + typev, tanim = self:ChooseAnimation("holster_empty") + elseif self:GetActivityEnabled(ACT_VM_HOLSTER) then + typev, tanim = self:ChooseAnimation("holster") + else + return false, select(2, self:ChooseIdleAnim()) + end + + return PlayChosenAnimation(self, typev, tanim) +end + +--[[ +Function Name: ChooseProceduralReloadAnim +Syntax: self:ChooseProceduralReloadAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Uses some holster code +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseProceduralReloadAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + + if not self2.DisableIdleAnimations then + self:SendViewModelAnim(ACT_VM_IDLE) + end + + return true, ACT_VM_IDLE +end + +--[[ +Function Name: ChooseReloadAnim +Syntax: self:ChooseReloadAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseReloadAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + if self2.GetStatL(self, "IsProceduralReloadBased") then return false, 0 end + + local ads = self:GetStatL("IronSightsReloadEnabled") and self:GetIronSightsDirect() + + if self:GetActivityEnabled(ACT_VM_RELOAD_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation((ads and self:GetActivityEnabled(ACT_VM_RELOAD_SILENCED_ADS)) and "reload_silenced_is" or "reload_silenced") + elseif self:GetActivityEnabled(ACT_VM_RELOAD_EMPTY) and (self:Clip1() == 0 or self:IsJammed()) and not self:GetStatL("LoopedReload") then + typev, tanim = self:ChooseAnimation((ads and self:GetActivityEnabled(ACT_VM_RELOAD_EMPTY_ADS)) and "reload_empty_is" or "reload_empty") + else + typev, tanim = self:ChooseAnimation((ads and self:GetActivityEnabled(ACT_VM_RELOAD_ADS)) and "reload_is" or "reload") + end + + local fac = 1 + + if self:GetStatL("LoopedReload") and self:GetStatL("LoopedReloadInsertTime") then + fac = self:GetStatL("LoopedReloadInsertTime") + end + + return PlayChosenAnimation(self, typev, tanim, fac, fac ~= 1) +end + +--[[ +Function Name: ChooseReloadAnim +Syntax: self:ChooseReloadAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseShotgunReloadAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + + local ads = self:GetStatL("IronSightsReloadEnabled") and self:GetIronSightsDirect() + + if self:GetActivityEnabled(ACT_VM_RELOAD_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation((ads and self:GetActivityEnabled(ACT_VM_RELOAD_SILENCED_ADS)) and "reload_silenced_is" or "reload_silenced") + elseif self:GetActivityEnabled(ACT_VM_RELOAD_EMPTY) and self2.ShotgunEmptyAnim and (self:Clip1() == 0 or self:IsJammed()) then + typev, tanim = self:ChooseAnimation((ads and self:GetActivityEnabled(ACT_VM_RELOAD_EMPTY_ADS)) and "reload_empty_is" or "reload_empty") + elseif self:GetActivityEnabled(ACT_SHOTGUN_RELOAD_START) then + typev, tanim = self:ChooseAnimation((ads and self:GetActivityEnabled(ACT_SHOTGUN_RELOAD_START_ADS)) and "reload_shotgun_start_is" or "reload_shotgun_start") + else + return false, select(2, self:ChooseIdleAnim()) + end + + return PlayChosenAnimation(self, typev, tanim) +end + +function SWEP:ChooseShotgunPumpAnim() + if not self:VMIV() then return false, 0 end + + typev, tanim = self:ChooseAnimation( + (self:GetStatL("IronSightsReloadEnabled") and + self:GetIronSightsDirect() and + self:GetActivityEnabled(ACT_SHOTGUN_RELOAD_START_ADS)) and "reload_shotgun_finish_is" or "reload_shotgun_finish") + + return PlayChosenAnimation(self, typev, tanim) +end + +--[[ +Function Name: ChooseIdleAnim +Syntax: self:ChooseIdleAnim(). +Returns: True, Which action? +Notes: Requires autodetection for full features. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseIdleAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + --if self2.Idle_WithHeld then + -- self2.Idle_WithHeld = nil + -- return + --end + + if TFA.Enum.ShootLoopingStatus[self:GetShootStatus()] and self:ShouldPlayLoopShootAnim() then + return self:ChooseLoopShootAnim() + end + + local idleMode = self2.GetStatL(self, "Idle_Mode") + + if idleMode ~= TFA.Enum.IDLE_BOTH and idleMode ~= TFA.Enum.IDLE_ANI then return end + + --self:ResetEvents() + if self:GetIronSights() then + local sightsMode = self2.GetStatL(self, "Sights_Mode") + + if sightsMode == TFA.Enum.LOCOMOTION_LUA then + return self:ChooseFlatAnim() + else + return self:ChooseADSAnim() + end + elseif self:GetSprinting() and self2.GetStatL(self, "Sprint_Mode") ~= TFA.Enum.LOCOMOTION_LUA then + return self:ChooseSprintAnim() + elseif self:GetWalking() and self2.GetStatL(self, "Walk_Mode") ~= TFA.Enum.LOCOMOTION_LUA then + return self:ChooseWalkAnim() + elseif self:GetCustomizing() and self2.GetStatL(self, "Customize_Mode") ~= TFA.Enum.LOCOMOTION_LUA then + return self:ChooseCustomizeAnim() + end + + if self:GetActivityEnabled(ACT_VM_IDLE_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation("idle_silenced") + elseif self:IsEmpty1() then + --self:GetActivityEnabled( ACT_VM_IDLE_EMPTY ) and (self:Clip1() == 0) then + if self:GetActivityEnabled(ACT_VM_IDLE_EMPTY) then + typev, tanim = self:ChooseAnimation("idle_empty") + else --if not self:GetActivityEnabled( ACT_VM_PRIMARYATTACK_EMPTY ) then + typev, tanim = self:ChooseAnimation("idle") + end + else + typev, tanim = self:ChooseAnimation("idle") + end + + --else + -- return + --end + return PlayChosenAnimation(self, typev, tanim) +end + +function SWEP:ChooseFlatAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + --self:ResetEvents() + typev, tanim = self:ChooseAnimation("idle") + + if self:GetActivityEnabled(ACT_VM_IDLE_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation("idle_silenced") + elseif self:GetActivityEnabled(ACT_VM_IDLE_EMPTY) and self:IsEmpty1() then + typev, tanim = self:ChooseAnimation("idle_empty") + end + + return PlayChosenAnimation(self, typev, tanim, 0.000001) +end + +function SWEP:ChooseADSAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + local a, b, c = self:PlayAnimation(self2.GetStatL(self, "IronAnimation.loop")) + + --self:SetNextIdleAnim(CurTime() + 1) + if not a then + local _ + _, b, c = self:ChooseFlatAnim() + a = false + end + + return a, b, c +end + +function SWEP:ChooseSprintAnim() + return self:PlayAnimation(self:GetStatL("SprintAnimation.loop")) +end + +function SWEP:ChooseWalkAnim() + return self:PlayAnimation(self:GetStatL("WalkAnimation.loop")) +end + +function SWEP:ChooseLoopShootAnim() + return self:PlayAnimation(self:GetStatL("ShootAnimation.loop")) +end + +function SWEP:ChooseCustomizeAnim() + return self:PlayAnimation(self:GetStatL("CustomizeAnimation.loop")) +end + +--[[ +Function Name: ChooseShootAnim +Syntax: self:ChooseShootAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. +Purpose: Animation / Utility +]] +-- +local shouldAnim, shouldBlowback +function SWEP:ChooseShootAnim(ifp) + local self2 = self:GetTable() + if ifp == nil then ifp = IsFirstTimePredicted() end + if not self2.VMIV(self) then return false, 0 end + + if self2.GetStatL(self, "ShootAnimation.loop") and self2.ShouldPlayLoopShootAnim(self) then + if self2.LuaShellEject and ifp then + self:EventShell() + end + + if TFA.Enum.ShootReadyStatus[self:GetShootStatus()] then + self:SetShootStatus(TFA.Enum.SHOOT_START) + + local inan = self2.GetStatL(self, "ShootAnimation.in") + + if not inan then + inan = self2.GetStatL(self, "ShootAnimation.loop") + end + + return self:PlayAnimation(inan) + end + + return + end + + local sightsMode = self2.GetStatL(self, "Sights_Mode") + + if self:GetIronSights() and (sightsMode == TFA.Enum.LOCOMOTION_ANI or sightsMode == TFA.Enum.LOCOMOTION_HYBRID) and self2.GetStatL(self, "IronAnimation.shoot") then + if self2.LuaShellEject and ifp then + self:EventShell() + end + + return self:PlayAnimation(self2.GetStatL(self, "IronAnimation.shoot")) + end + + shouldBlowback = self2.GetStatL(self, "BlowbackEnabled") and (not self2.GetStatL(self, "Blowback_Only_Iron") or self:GetIronSights()) + shouldAnim = not shouldBlowback or self2.GetStatL(self, "BlowbackAllowAnimation") + + if shouldBlowback then + if sp and SERVER then + self:CallOnClient("BlowbackFull", "") + end + + if ifp then + self:BlowbackFull(ifp) + end + + if self2.GetStatL(self, "Blowback_Shell_Enabled") and (ifp or sp) then + self:EventShell() + end + end + + if shouldAnim then + success = true + + if self2.LuaShellEject and (ifp or sp) then + self:EventShell() + end + + if self:GetActivityEnabled(ACT_VM_PRIMARYATTACK_SILENCED) and self2.GetSilenced(self) then + typev, tanim = self:ChooseAnimation("shoot1_silenced") + elseif self:Clip1() <= self2.Primary_TFA.AmmoConsumption and self:GetActivityEnabled(ACT_VM_PRIMARYATTACK_EMPTY) and self2.Primary_TFA.ClipSize >= 1 and not self2.ForceEmptyFireOff then + typev, tanim = self:ChooseAnimation("shoot1_last") + elseif self:Ammo1() <= self2.Primary_TFA.AmmoConsumption and self:GetActivityEnabled(ACT_VM_PRIMARYATTACK_EMPTY) and self2.Primary_TFA.ClipSize < 1 and not self2.ForceEmptyFireOff then + typev, tanim = self:ChooseAnimation("shoot1_last") + elseif self:Clip1() == 0 and self:GetActivityEnabled(ACT_VM_DRYFIRE) and not self2.ForceDryFireOff then + typev, tanim = self:ChooseAnimation("shoot1_empty") + elseif self2.GetStatL(self, "IsAkimbo") and self:GetActivityEnabled(ACT_VM_SECONDARYATTACK) and ((self:GetAnimCycle() == 0 and not self2.Akimbo_Inverted) or (self:GetAnimCycle() == 1 and self2.Akimbo_Inverted)) then + typev, tanim = self:ChooseAnimation((self:GetIronSights() and self:GetActivityEnabled(ACT_VM_ISHOOT_M203)) and "shoot2_is" or "shoot2") + elseif self:GetIronSights() and self:GetActivityEnabled(ACT_VM_PRIMARYATTACK_1) then + typev, tanim = self:ChooseAnimation("shoot1_is") + else + typev, tanim = self:ChooseAnimation("shoot1") + end + + return PlayChosenAnimation(self, typev, tanim) + end + + self:SendViewModelAnim(ACT_VM_BLOWBACK) + + return true, ACT_VM_IDLE +end + +SWEP.BlowbackRandomAngle = Angle(0, 0, 0) -- not cached, overwritten with each shot + +SWEP.BlowbackRandomAngleMin = Angle(.1, -.5, -1) +SWEP.BlowbackRandomAngleMax = Angle(.2, .5, 1) + +local minang, maxang + +function SWEP:BlowbackFull() + local self2 = self:GetTable() + + if IsValid(self) then + self2.BlowbackCurrent = 1 + self2.BlowbackCurrentRoot = 1 + + if CLIENT then + minang, maxang = self2.GetStatL(self, "BlowbackRandomAngleMin"), self2.GetStatL(self, "BlowbackRandomAngleMax") + + self2.BlowbackRandomAngle = Angle(math.Rand(minang.p, maxang.p), math.Rand(minang.y, maxang.y), math.Rand(minang.r, maxang.r)) + end + end +end + +--[[ +Function Name: ChooseSilenceAnim +Syntax: self:ChooseSilenceAnim( true if we're silencing, false for detaching the silencer). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. This is played when you silence or unsilence a gun. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseSilenceAnim(val) + if not self:VMIV() then return false, 0 end + --self:ResetEvents() + typev, tanim = self:ChooseAnimation("idle_silenced") + success = false + + if val then + if self:GetActivityEnabled(ACT_VM_ATTACH_SILENCER) then + typev, tanim = self:ChooseAnimation("silencer_attach") + success = true + end + elseif self:GetActivityEnabled(ACT_VM_DETACH_SILENCER) then + typev, tanim = self:ChooseAnimation("silencer_detach") + success = true + end + + if not success then + return false, select(2, self:ChooseIdleAnim()) + end + + return PlayChosenAnimation(self, typev, tanim) +end + +--[[ +Function Name: ChooseDryFireAnim +Syntax: self:ChooseDryFireAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. set SWEP.ForceDryFireOff to false to properly use. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseDryFireAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + --self:ResetEvents() + typev, tanim = self:ChooseAnimation("shoot1_empty") + success = true + + if self:GetActivityEnabled(ACT_VM_DRYFIRE_SILENCED) and self2.GetSilenced(self) and not self2.ForceDryFireOff then + typev, tanim = self:ChooseAnimation("shoot1_silenced_empty") + --self:ChooseIdleAnim() + else + if self:GetActivityEnabled(ACT_VM_DRYFIRE) and not self2.ForceDryFireOff then + typev, tanim = self:ChooseAnimation("shoot1_empty") + else + success = false + local _ + _, tanim = nil, nil + + return success, tanim -- ??? + end + end + + return PlayChosenAnimation(self, typev, tanim) +end + +--[[ +Function Name: ChooseROFAnim +Syntax: self:ChooseROFAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. Called when we change the firemode. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseROFAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + + --self:ResetEvents() + if self:GetIronSights() and self:GetActivityEnabled(ACT_VM_IFIREMODE) then + typev, tanim = self2.ChooseAnimation(self, "rof_is") + success = true + elseif self:GetActivityEnabled(ACT_VM_FIREMODE) then + typev, tanim = self2.ChooseAnimation(self, "rof") + success = true + else + success = false + local _ + _, tanim = nil, nil + + return success, tanim -- ??? + end + + return PlayChosenAnimation(self, typev, tanim) +end + +--[[ +Function Name: ChooseBashAnim +Syntax: self:ChooseBashAnim(). +Returns: Could we successfully find an animation? Which action? +Notes: Requires autodetection or otherwise the list of valid anims. Called when we bash. +Purpose: Animation / Utility +]] +-- +function SWEP:ChooseBashAnim() + local self2 = self:GetTable() + if not self2.VMIV(self) then return false, 0 end + + typev, tanim = nil, nil + success = false + + local isempty = self2.GetStatL(self, "Primary.ClipSize") > 0 and self:Clip1() == 0 + + if self2.GetSilenced(self) and self:GetActivityEnabled(ACT_VM_HITCENTER2) then + if self:GetActivityEnabled(ACT_VM_MISSCENTER2) and isempty then + typev, tanim = self:ChooseAnimation("bash_empty_silenced") + success = true + else + typev, tanim = self:ChooseAnimation("bash_silenced") + success = true + end + elseif self:GetActivityEnabled(ACT_VM_MISSCENTER) and isempty then + typev, tanim = self:ChooseAnimation("bash_empty") + success = true + elseif self:GetActivityEnabled(ACT_VM_HITCENTER) then + typev, tanim = self:ChooseAnimation("bash") + success = true + end + + if not success then + return success, tanim + end + + return PlayChosenAnimation(self, typev, tanim) +end + +--[[THIRDPERSON]] +--These holdtypes are used in ironsights. Syntax: DefaultHoldType=NewHoldType +SWEP.IronSightHoldTypes = { + pistol = "revolver", + smg = "rpg", + grenade = "melee", + ar2 = "rpg", + shotgun = "ar2", + rpg = "rpg", + physgun = "physgun", + crossbow = "ar2", + melee = "melee2", + slam = "camera", + normal = "fist", + melee2 = "magic", + knife = "fist", + duel = "duel", + camera = "camera", + magic = "magic", + revolver = "revolver" +} + +--These holdtypes are used while sprinting. Syntax: DefaultHoldType=NewHoldType +SWEP.SprintHoldTypes = { + pistol = "normal", + smg = "passive", + grenade = "normal", + ar2 = "passive", + shotgun = "passive", + rpg = "passive", + physgun = "normal", + crossbow = "passive", + melee = "normal", + slam = "normal", + normal = "normal", + melee2 = "melee", + knife = "fist", + duel = "normal", + camera = "slam", + magic = "normal", + revolver = "normal" +} + +--These holdtypes are used in reloading. Syntax: DefaultHoldType=NewHoldType +SWEP.ReloadHoldTypes = { + pistol = "pistol", + smg = "smg", + grenade = "melee", + ar2 = "ar2", + shotgun = "shotgun", + rpg = "ar2", + physgun = "physgun", + crossbow = "crossbow", + melee = "pistol", + slam = "smg", + normal = "pistol", + melee2 = "pistol", + knife = "pistol", + duel = "duel", + camera = "pistol", + magic = "pistol", + revolver = "revolver" +} + +--These holdtypes are used in reloading. Syntax: DefaultHoldType=NewHoldType +SWEP.CrouchHoldTypes = { + ar2 = "ar2", + smg = "smg", + rpg = "ar2" +} + +SWEP.IronSightHoldTypeOverride = "" --This variable overrides the ironsights holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +SWEP.SprintHoldTypeOverride = "" --This variable overrides the sprint holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +SWEP.ReloadHoldTypeOverride = "" --This variable overrides the reload holdtype, choosing it instead of something from the above tables. Change it to "" to disable. +local dynholdtypecvar = GetConVar("sv_tfa_holdtype_dynamic") +SWEP.mht_old = "" +local mht + +function SWEP:IsOwnerCrouching() + local ply = self:GetOwner() + + if not ply:IsPlayer() then return false end + + return (ply:Crouching() or self:KeyDown(IN_DUCK)) and ply:OnGround() and not ply:InVehicle() +end + +function SWEP:ProcessHoldType() + local self2 = self:GetTable() + mht = self2.GetStatL(self, "HoldType", "ar2") + + if mht ~= self2.mht_old or not self2.DefaultHoldType then + self2.DefaultHoldType = mht + self2.SprintHoldType = nil + self2.IronHoldType = nil + self2.ReloadHoldType = nil + self2.CrouchHoldType = nil + end + + self2.mht_old = mht + + if not self2.SprintHoldType then + self2.SprintHoldType = self2.SprintHoldTypes[self2.DefaultHoldType] or "passive" + + if self2.SprintHoldTypeOverride and self2.SprintHoldTypeOverride ~= "" then + self2.SprintHoldType = self2.SprintHoldTypeOverride + end + end + + if not self2.IronHoldType then + self2.IronHoldType = self2.IronSightHoldTypes[self2.DefaultHoldType] or "rpg" + + if self2.IronSightHoldTypeOverride and self2.IronSightHoldTypeOverride ~= "" then + self2.IronHoldType = self2.IronSightHoldTypeOverride + end + end + + if not self2.ReloadHoldType then + self2.ReloadHoldType = self2.ReloadHoldTypes[self2.DefaultHoldType] or "ar2" + + if self2.ReloadHoldTypeOverride and self2.ReloadHoldTypeOverride ~= "" then + self2.ReloadHoldType = self2.ReloadHoldTypeOverride + end + end + + if not self2.SetCrouchHoldType then + self2.SetCrouchHoldType = true + self2.CrouchHoldType = self2.CrouchHoldTypes[self2.DefaultHoldType] + + if self2.CrouchHoldTypeOverride and self2.CrouchHoldTypeOverride ~= "" then + self2.CrouchHoldType = self2.CrouchHoldTypeOverride + end + end + + local curhold, targhold, stat + curhold = self:GetHoldType() + targhold = self2.DefaultHoldType + stat = self:GetStatus() + + if dynholdtypecvar:GetBool() then + if self:OwnerIsValid() and self:IsOwnerCrouching() and self2.CrouchHoldType then + targhold = self2.CrouchHoldType + else + if self:GetIronSights() then + targhold = self2.IronHoldType + end + + if TFA.Enum.ReloadStatus[stat] then + targhold = self2.ReloadHoldType + end + end + end + + if self:GetSprinting() or TFA.Enum.HolsterStatus[stat] or self:IsSafety() then + targhold = self2.SprintHoldType + end + + if targhold ~= curhold then + self:SetHoldType(targhold) + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/attachments.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/attachments.lua new file mode 100644 index 0000000..c8733aa --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/attachments.lua @@ -0,0 +1,734 @@ +local ATT_DIMENSION +local ATT_MAX_SCREEN_RATIO = 1 / 3 +local tableCopy = table.Copy + +SWEP.Attachments = {} --[MDL_ATTACHMENT] = = { offset = { 0, 0 }, atts = { "sample_attachment_1", "sample_attachment_2" }, sel = 1, order = 1 } --offset will move the offset the display from the weapon attachment when using CW2.0 style attachment display --atts is a table containing the visible attachments --sel allows you to have an attachment pre-selected, and is used internally by the base to show which attachment is selected in each category. --order is the order it will appear in the TFA style attachment menu +SWEP.AttachmentCache = {} --["att_name"] = true +SWEP.AttachmentTableCache = {} +SWEP.AttachmentCount = 0 +SWEP.AttachmentDependencies = {} --{["si_acog"] = {"bg_rail"}} +SWEP.AttachmentExclusions = {} --{ ["si_iron"] = {"bg_heatshield"} } +SWEP.AttachmentTableOverride = {} + +local att_enabled_cv = GetConVar("sv_tfa_attachments_enabled") + +function SWEP:RemoveUnusedAttachments() + for k, v in pairs(self.Attachments) do + if v.atts then + local t = {} + local i = 1 + + for _, b in pairs(v.atts) do + if TFA.Attachments.Atts[b] then + t[i] = b + i = i + 1 + end + end + + v.atts = tableCopy(t) + end + + if #v.atts <= 0 then + self.Attachments[k] = nil + end + end +end + +local function select_function_table(target) + -- idk + local found_table + + for i_index, base_value in pairs(target) do + if istable(base_value) then + found_table = base_value + end + end + + if not found_table then + found_table = {} + table.insert(target, found_table) + end + + return found_table +end + +local function get(path, target) + if #path == 1 then + return target[path[1]] + end + + local _target = target[path[1]] + + if _target == nil then + target[path[1]] = {} + _target = target[path[1]] + end + + for i = 2, #path do + if not istable(_target) then + return + end + + if _target[path[i]] == nil then _target[path[i]] = {} end + + if istable(_target[path[i]]) and _target[path[i]].functionTable and i ~= #path then + _target = select_function_table(_target[path[i]]) + else + _target = _target[path[i]] + end + end + + return _target +end + +local function set(path, target, value) + if #path == 1 then + target[path[1]] = value + return value + end + + local _target = target[path[1]] + + if _target == nil then + target[path[1]] = {} + _target = target[path[1]] + end + + for i = 2, #path - 1 do + if not istable(_target) then + return value + end + + if _target[path[i]] == nil then _target[path[i]] = {} end + + if _target[path[i]].functionTable then + _target = select_function_table(_target[path[i]]) + else + _target = _target[path[i]] + end + end + + if istable(_target) then + if istable(_target[path[#path]]) and _target[path[#path]].functionTable then + table.insert(select_function_table(_target[path[#path]]), value) + else + _target[path[#path]] = value + end + end + + return value +end + +local function CloneTableRecursive(source, target, root, source_version, target_version) + for index, value in pairs(source) do + local _root = root == "" and index or (root .. "." .. index) + + -- merge two tables + if istable(value) then + local baseTable = get(TFA.GetStatPath(_root, source_version, target_version), target) + + -- target is a function table + if istable(baseTable) and baseTable.functionTable then + local found_table + + for i_index, base_value in pairs(baseTable) do + if istable(base_value) then + found_table = base_value + end + end + + if not found_table then + found_table = {} + table.insert(baseTable, 1, found_table) + end + + CloneTableRecursive(value, target, _root, source_version, target_version) + -- target is a regular table + else + if not istable(baseTable) then + set(TFA.GetStatPath(_root, source_version, target_version), target, {}) + end + + CloneTableRecursive(value, target, _root, source_version, target_version) + end + -- final value is determined by function + elseif isfunction(value) then + local temp + local get_path = TFA.GetStatPath(_root, source_version, target_version) + local get_table = get(get_path, target) + + if get_table ~= nil and not istable(get_table) then + temp = get_table + end + + local get_value = not istable(get_table) and set(get_path, target, {}) or get_table + + -- mark this table as function based + get_value.functionTable = true + + if temp ~= nil then + -- insert variable that was there before + table.insert(get_value, temp) + end + + -- insert function + table.insert(get_value, value) + -- final value is a scalar + else + local get_table = get(TFA.GetStatPath(_root, source_version, target_version), target) + + if istable(get_table) and get_table.functionTable then + table.insert(get_table, 1, value) + else + set(TFA.GetStatPath(_root, source_version, target_version), target, value) + end + end + end +end + +function SWEP:BuildAttachmentCache() + self.AttachmentCount = 0 + + for k, v in pairs(self.Attachments) do + if v.atts then + for l, b in pairs(v.atts) do + self.AttachmentCount = self.AttachmentCount + 1 + self.AttachmentCache[b] = (v.sel == l) and k or false + end + end + end + + table.Empty(self.AttachmentTableCache) + + for attName, sel in pairs(self.AttachmentCache) do + if not sel then goto CONTINUE end + if not TFA.Attachments.Atts[attName] then goto CONTINUE end + + local srctbl = TFA.Attachments.Atts[attName].WeaponTable + + if istable(srctbl) then + CloneTableRecursive(srctbl, self.AttachmentTableCache, "", TFA.Attachments.Atts[attName].TFADataVersion or 0, self.TFADataVersion or 0) + end + + if istable(self.AttachmentTableOverride[attName]) then + CloneTableRecursive(self.AttachmentTableOverride[attName], self.AttachmentTableCache, "", self.TFADataVersion or 0, self.TFADataVersion or 0) + end + + ::CONTINUE:: + end + + self:ClearStatCache() + self:ClearMaterialCache() + + if CLIENT then + self:ResetKeyBindHintAlpha(true) + end +end + +function SWEP:IsAttached(attn) + return isnumber(self.AttachmentCache[attn]) +end + +local tc + +function SWEP:CanAttach(attn, detaching) + local retVal + + if detaching then + retVal = hook.Run("TFA_PreCanDetach", self, attn) + else + retVal = hook.Run("TFA_PreCanAttach", self, attn) + end + + if retVal ~= nil then return retVal end + + local self2 = self:GetTable() + + if not self2.HasBuiltMutualExclusions then + tc = tableCopy(self2.AttachmentExclusions) + + for k, v in pairs(tc) do + if k ~= "BaseClass" then + for _, b in pairs(v) do + self2.AttachmentExclusions[b] = self2.AttachmentExclusions[b] or {} + + if not table.HasValue(self2.AttachmentExclusions[b]) then + self2.AttachmentExclusions[b][#self2.AttachmentExclusions[b] + 1] = k + end + end + end + end + + self2.HasBuiltMutualExclusions = true + end + + if att_enabled_cv and not att_enabled_cv:GetBool() then return false end + + if self2.AttachmentExclusions[attn] then + for _, v in pairs(self2.AttachmentExclusions[attn]) do + if not detaching and self2.IsAttached(self, v) then + return false + end + end + end + + if not detaching and self2.AttachmentDependencies[attn] then + local t = self2.AttachmentDependencies[attn] + + if isstring(t) then + if t ~= "BaseClass" and not self2.IsAttached(self, t) then return false end + elseif istable(t) then + t.type = t.type or "OR" + + if t.type == "AND" then + for k, v in pairs(self.AttachmentDependencies[attn]) do + if k ~= "BaseClass" and k ~= "type" and not self2.IsAttached(self, v) then return false end + end + else + local cnt = 0 + + for k, v in pairs(self.AttachmentDependencies[attn]) do + if k ~= "BaseClass" and k ~= "type" and self2.IsAttached(self, v) then + cnt = cnt + 1 + end + end + + if cnt == 0 then return false end + end + end + end + + local atTable = TFA.Attachments.Atts[attn] + + if atTable then + if detaching then + if atTable.CanDetach and not atTable.CanDetach(self) then return false end + else + if not atTable:CanAttach(self) then return false end + end + end + + local retVal2 + + if detaching then + retVal2 = hook.Run("TFA_CanDetach", self, attn) + else + retVal2 = hook.Run("TFA_CanAttach", self, attn) + end + + if retVal2 ~= nil then return retVal2 end + return true +end + +local ATTACHMENT_SORTING_DEPENDENCIES = false + +function SWEP:ForceAttachmentReqs(attn) + if not ATTACHMENT_SORTING_DEPENDENCIES then + ATTACHMENT_SORTING_DEPENDENCIES = true + local related = {} + + for k, v in pairs(self.AttachmentDependencies) do + if istable(v) then + for _, b in pairs(v) do + if k == attn then + related[b] = true + elseif b == attn then + related[k] = true + end + end + elseif isstring(v) then + if k == attn then + related[v] = true + elseif v == attn then + related[k] = true + end + end + end + + for k, v in pairs(self.AttachmentExclusions) do + if istable(v) then + for _, b in pairs(v) do + if k == attn then + related[b] = true + elseif b == attn then + related[k] = true + end + end + elseif isstring(v) then + if k == attn then + related[v] = true + elseif v == attn then + related[k] = true + end + end + end + + for k, v in pairs(self.AttachmentCache) do + if v and related[k] and not self:CanAttach(k) then + self:SetTFAAttachment(v, 0, true, true) + end + end + + ATTACHMENT_SORTING_DEPENDENCIES = false + end +end + +do + local self3, att_neue, att_old + + local function attach() + att_neue:Attach(self3) + end + + local function detach() + att_old:Detach(self3) + end + + function SWEP:SetTFAAttachment(cat, id, nw, force) + self3 = self + local self2 = self:GetTable() + + if not self2.Attachments[cat] then return false end + + if isstring(id) then + if id == "" then + id = -1 + else + id = table.KeyFromValue(self2.Attachments[cat].atts, id) + if not id then return false end + end + end + + if id <= 0 and self2.Attachments[cat].default and type(self2.Attachments[cat].default) == "string" and self2.Attachments[cat].default ~= "" then + return self2.SetTFAAttachment(self, cat, self2.Attachments[cat].default, nw, force) + end + + local attn = self2.Attachments[cat].atts[id] or "" + local attn_old = self2.Attachments[cat].atts[self2.Attachments[cat].sel or -1] or "" + if SERVER and id > 0 and not (force or self2.CanAttach(self, attn)) then return false end + if SERVER and id <= 0 and not (force or self2.CanAttach(self, attn_old, true)) then return false end + + if id ~= self2.Attachments[cat].sel then + att_old = TFA.Attachments.Atts[self2.Attachments[cat].atts[self2.Attachments[cat].sel] or -1] + local detach_status = att_old == nil + + if att_old then + detach_status = ProtectedCall(detach) + + if detach_status then + hook.Run("TFA_Attachment_Detached", self, attn_old, att_old, cat, id, force) + end + end + + att_neue = TFA.Attachments.Atts[self2.Attachments[cat].atts[id] or -1] + local attach_status = att_neue == nil + + if detach_status then + if att_neue then + attach_status = ProtectedCall(attach) + + if attach_status then + hook.Run("TFA_Attachment_Attached", self, attn, att_neue, cat, id, force) + end + end + end + + if detach_status and attach_status then + if id > 0 then + self2.Attachments[cat].sel = id + else + self2.Attachments[cat].sel = nil + end + end + end + + self2.BuildAttachmentCache(self) + self2.ForceAttachmentReqs(self, (id > 0) and attn or attn_old) + + if nw and (not isentity(nw) or SERVER) then + net.Start("TFA_Attachment_Set") + if SERVER then net.WriteEntity(self) elseif CLIENT then net.WriteString(self:GetClass()) end + net.WriteUInt(cat, 8) + net.WriteString(attn) + + if SERVER then + if isentity(nw) then + local filter = RecipientFilter() + filter:AddPVS(self:GetPos()) + filter:RemovePlayer(nw) + net.Send(filter) + else + net.SendPVS(self:GetPos()) + end + elseif CLIENT then + net.SendToServer() + end + end + + return true + end +end + +function SWEP:Attach(attname, force) + if not attname or not IsValid(self) then return false end + if self.AttachmentCache[attname] == nil then return false end + + for cat, tbl in pairs(self.Attachments) do + local atts = tbl.atts + + for id, att in ipairs(atts) do + if att == attname then return self:SetTFAAttachment(cat, id, true, force) end + end + end + + return false +end + +function SWEP:Detach(attname, force) + if not attname or not IsValid(self) then return false end + local cat = self.AttachmentCache[attname] + if not cat then return false end + + return self:SetTFAAttachment(cat, 0, true, force) +end + +function SWEP:RandomizeAttachments(force) + for key, slot in pairs(self.AttachmentCache) do + if slot then + self:Detach(key) + end + end + + for category, def in pairs(self.Attachments) do + if istable(def) and istable(def.atts) and #def.atts > 0 then + if math.random() > 0.3 then + local randkey = math.random(1, #def.atts) + self:SetTFAAttachment(category, randkey, true, force) + end + end + end +end + +local attachments_sorted_alphabetically = GetConVar("sv_tfa_attachments_alphabetical") + +function SWEP:InitAttachments() + if self.HasInitAttachments then return end + hook.Run("TFA_PreInitAttachments", self) + self.HasInitAttachments = true + + for k, v in pairs(self.Attachments) do + if type(k) == "string" then + local tatt = self:VMIV() and self.OwnerViewModel:LookupAttachment(k) or self:LookupAttachment(k) + + if tatt > 0 then + self.Attachments[tatt] = v + end + + self.Attachments[k] = nil + elseif (not attachments_sorted_alphabetically) and attachments_sorted_alphabetically:GetBool() then + local sval = v.atts[v.sel] + + table.sort(v.atts, function(a, b) + local aname = "" + local bname = "" + local att_a = TFA.Attachments.Atts[a] + + if att_a then + aname = att_a.Name or a + end + + local att_b = TFA.Attachments.Atts[b] + + if att_b then + bname = att_b.Name or b + end + + return aname < bname + end) + + if sval then + v.sel = table.KeyFromValue(v.atts, sval) or v.sel + end + end + end + + for k, v in pairs(self.Attachments) do + if v.sel then + local vsel = v.sel + v.sel = nil + + if type(vsel) == "string" then + vsel = table.KeyFromValue(v.atts, vsel) or tonumber(vsel) + + if not vsel then goto CONTINUE end + end + + timer.Simple(0, function() + if IsValid(self) and self.SetTFAAttachment then + self:SetTFAAttachment(k, vsel, false) + end + end) + end + + ::CONTINUE:: + end + + hook.Run("TFA_PostInitAttachments", self) + self:RemoveUnusedAttachments() + self:BuildAttachmentCache() + hook.Run("TFA_FinalInitAttachments", self) + + self:RestoreAttachments() +end + +local cv_persist_enable = GetConVar("cl_tfa_attachments_persist_enabled") + +function SWEP:SaveAttachments() + if not CLIENT or not cv_persist_enable:GetBool() then return end + + TFA.SetSavedAttachments(self) +end + +function SWEP:RestoreAttachments() + if not CLIENT or not cv_persist_enable:GetBool() then return end + + for cat, id in pairs(TFA.GetSavedAttachments(self) or {}) do + self:SetTFAAttachment(cat, id, true, true) + end +end + +function SWEP:GenerateVGUIAttachmentTable() + self.VGUIAttachments = {} + local keyz = table.GetKeys(self.Attachments) + table.RemoveByValue(keyz, "BaseClass") + + table.sort(keyz, function(a, b) + --A and B are keys + local v1 = self.Attachments[a] + local v2 = self.Attachments[b] + + if v1 and v2 and (v1.order or v2.order) then + return (v1.order or a) < (v2.order or b) + else + return a < b + end + end) + + for _, k in ipairs(keyz) do + local v = self.Attachments[k] + + if not v.hidden then + local aTbl = tableCopy(v) + + aTbl.cat = k + aTbl.offset = nil + aTbl.order = nil + + table.insert(self.VGUIAttachments, aTbl) + end + end + + ATT_DIMENSION = math.Round(TFA.ScaleH(TFA.Attachments.IconSize)) + local max_row_atts = math.floor(ScrW() * ATT_MAX_SCREEN_RATIO / ATT_DIMENSION) + local i = 1 + + while true do + local v = self.VGUIAttachments[i] + if not v then break end + i = i + 1 + + for l, b in pairs(v.atts) do + if not istable(b) then + v.atts[l] = {b, l} --name, ID + end + end + + if (#v.atts > max_row_atts) then + while (#v.atts > max_row_atts) do + local t = tableCopy(v) + + for _ = 1, max_row_atts do + table.remove(t.atts, 1) + end + + for _ = 1, #v.atts - max_row_atts do + table.remove(v.atts) + end + + table.insert(self.VGUIAttachments, i, t) + end + end + end +end + +local bgt = {} +SWEP.ViewModelBodygroups = {} +SWEP.WorldModelBodygroups = {} + +function SWEP:IterateBodygroups(entity, tablename, version) + local self2 = self:GetTable() + + bgt = self2.GetStatVersioned(self, tablename, version or 0, self2[tablename]) + + for k, v in pairs(bgt) do + if isnumber(k) then + local bgn = entity:GetBodygroupName(k) + + if bgt[bgn] then + v = bgt[bgn] + end + + if entity:GetBodygroup(k) ~= v then + entity:SetBodygroup(k, v) + end + end + end +end + +function SWEP:ProcessBodygroups() + local self2 = self:GetTable() + local ViewModelBodygroups = self:GetStatRawL("ViewModelBodygroups") + local WorldModelBodygroups = self:GetStatRawL("WorldModelBodygroups") + + if not self2.HasFilledBodygroupTables then + if self2.VMIV(self) then + for i = 0, #(self2.OwnerViewModel:GetBodyGroups() or ViewModelBodygroups) do + ViewModelBodygroups[i] = ViewModelBodygroups[i] or 0 + end + end + + for i = 0, #(self:GetBodyGroups() or WorldModelBodygroups) do + WorldModelBodygroups[i] = WorldModelBodygroups[i] or 0 + end + + self2.HasFilledBodygroupTables = true + end + + if self2.VMIV(self) then + self2.IterateBodygroups(self, self2.OwnerViewModel, "ViewModelBodygroups", TFA.LatestDataVersion) + end + + self2.IterateBodygroups(self, self, "WorldModelBodygroups", TFA.LatestDataVersion) +end + +function SWEP:CallAttFunc(funcName, ...) + for attName, sel in pairs(self.AttachmentCache or {}) do + if not sel then goto CONTINUE end + + local att = TFA.Attachments.Atts[attName] + if not att then goto CONTINUE end + + local attFunc = att[funcName] + if attFunc and type(attFunc) == "function" then + local _ret1, _ret2, _ret3, _ret4, _ret5, _ret6, _ret7, _ret8, _ret9, _ret10 = attFunc(att, self, ...) + + if _ret1 ~= nil then + return _ret1, _ret2, _ret3, _ret4, _ret5, _ret6, _ret7, _ret8, _ret9, _ret10 + end + end + + ::CONTINUE:: + end + + return nil +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/autodetection.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/autodetection.lua new file mode 100644 index 0000000..8370df1 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/autodetection.lua @@ -0,0 +1,826 @@ +function SWEP:RunAutoDetection() + local self2 = self:GetTable() + + self2.PatchAmmoTypeAccessors(self) + + self2.FixRPM(self) + self2.FixIdles(self) + self2.FixIS(self) + self2.FixCone(self) + self2.FixProjectile(self) + self2.FixAkimbo(self) -- external: akimbo.lua + self2.FixSprintAnimBob(self) + self2.FixWalkAnimBob(self) + + self2.AutoDetectMuzzle(self) + self2.AutoDetectDamage(self) + self2.AutoDetectDamageType(self) + self2.AutoDetectForce(self) + self2.AutoDetectPenetrationPower(self) + self2.AutoDetectKnockback(self) + self2.AutoDetectSpread(self) + self2.AutoDetectRange(self) + self2.AutoDetectLowAmmoSound(self) + + self2.CreateFireModes(self) + self2.IconFix(self) + + self2.RemoveEmptyRTCode(self) +end + +function SWEP:FixSprintAnimBob() + local self2 = self:GetTable() + + if self:GetStatRawL("Sprint_Mode") == TFA.Enum.LOCOMOTION_ANI then + self:SetStatRawL("SprintBobMult", 0) + end +end + +function SWEP:FixWalkAnimBob() + local self2 = self:GetTable() + if self:GetStatRawL("Walk_Mode") == TFA.Enum.LOCOMOTION_ANI then + self:SetStatRawL("WalkBobMult_Iron", self:GetStatRawL("WalkBobMult")) + self:SetStatRawL("WalkBobMult", 0) + end +end + +function SWEP:PatchAmmoTypeAccessors() + local self2 = self:GetTable() + self:SetStatRawL("GetPrimaryAmmoTypeOld", self:GetStatRawL("GetPrimaryAmmoTypeOld") or self:GetStatRawL("GetPrimaryAmmoType")) + self:SetStatRawL("GetPrimaryAmmoType", function(myself, ...) return myself.GetPrimaryAmmoTypeC(myself, ...) end) + self:SetStatRawL("GetSecondaryAmmoTypeOld", self:GetStatRawL("GetSecondaryAmmoTypeOld") or self:GetStatRawL("GetSecondaryAmmoType")) + self:SetStatRawL("GetSecondaryAmmoType", function(myself, ...) return myself.GetSecondaryAmmoTypeC(myself, ...) end) +end + +function SWEP:FixProjectile() + local self2 = self:GetTable() + if self:GetStatRawL("ProjectileEntity") and self:GetStatRawL("ProjectileEntity") ~= "" then + self:SetStatRawL("Primary.Projectile", self:GetStatRawL("ProjectileEntity")) + self:SetStatRawL("ProjectileEntity", nil) + end + + if self:GetStatRawL("ProjectileModel") and self:GetStatRawL("ProjectileModel") ~= "" then + self:SetStatRawL("Primary.ProjectileModel", self:GetStatRawL("ProjectileModel")) + self:SetStatRawL("ProjectileModel", nil) + end + + if self:GetStatRawL("ProjectileVelocity") and self:GetStatRawL("ProjectileVelocity") ~= "" then + self:SetStatRawL("Primary.ProjectileVelocity", self:GetStatRawL("ProjectileVelocity")) + self:SetStatRawL("ProjectileVelocity", nil) + end +end + +local sv_tfa_range_modifier = GetConVar("sv_tfa_range_modifier") + +function SWEP:AutoDetectRange() + local self2 = self:GetTable() + + if self:GetStatL("Primary.FalloffMetricBased") and not self:GetStatRawL("Primary.RangeFalloffLUT") then + self:SetStatRawL("Primary.RangeFalloffLUT_IsConverted", true) + + self:SetStatRawL("Primary.RangeFalloffLUT", { + bezier = false, + range_func = "linear", -- function to spline range + units = "meters", + lut = { + {range = self:GetStatL("Primary.MinRangeStartFalloff"), damage = 1}, + {range = self:GetStatL("Primary.MinRangeStartFalloff") + self:GetStatL("Primary.MaxFalloff") / self:GetStatL("Primary.FalloffByMeter"), + damage = (self:GetStatL("Primary.Damage") - self:GetStatL("Primary.MaxFalloff")) / self:GetStatL("Primary.Damage")}, + } + }) + + return + end + + if self:GetStatL("Primary.FalloffMetricBased") or self:GetStatL("Primary.RangeFalloffLUT") then return end + + if self:GetStatL("Primary.Range") <= 0 then + self:SetStatRawL("Primary.Range", math.sqrt(self:GetStatL("Primary.Damage") / 32) * self:MetersToUnits(350) * self:AmmoRangeMultiplier()) + end + + if self:GetStatL("Primary.RangeFalloff") <= 0 then + self:SetStatRawL("Primary.RangeFalloff", 0.5) + end + + self:SetStatRawL("Primary.RangeFalloffLUT_IsConverted", true) + + self:SetStatRawL("Primary.RangeFalloffLUT", { + bezier = false, + range_func = "linear", -- function to spline range + units = "hammer", + lut = { + {range = self:GetStatL("Primary.Range") * self:GetStatL("Primary.RangeFalloff"), damage = 1}, + {range = self:GetStatL("Primary.Range"), damage = 1 - sv_tfa_range_modifier:GetFloat()}, + } + }) +end + +function SWEP:FixProceduralReload() + -- do nothing +end + +function SWEP:FixRPM() + local self2 = self:GetTable() + if not self:GetStatRawL("Primary.RPM") then + if self:GetStatRawL("Primary.Delay") then + self:SetStatRawL("Primary.RPM", 60 / self:GetStatRawL("Primary.Delay")) + else + self:SetStatRawL("Primary.RPM", 120) + end + end +end + +function SWEP:FixCone() + local self2 = self:GetTable() + if self:GetStatRawL("Primary.Cone") then + if (not self:GetStatRawL("Primary.Spread")) or self:GetStatRawL("Primary.Spread") < 0 then + self:SetStatRawL("Primary.Spread", self:GetStatRawL("Primary.Cone")) + end + + self:SetStatRawL("Primary.Cone", nil) + end +end + +--legacy compatibility +function SWEP:FixIdles() + local self2 = self:GetTable() + if self:GetStatRawL("DisableIdleAnimations") ~= nil and self:GetStatRawL("DisableIdleAnimations") == true then + self:SetStatRawL("Idle_Mode", TFA.Enum.IDLE_LUA) + end +end + +function SWEP:FixIS() + local self2 = self:GetTable() + if self:GetStatRawL("SightsPos") and (not self:GetStatRawL("IronSightsPosition") or (self:GetStatRawL("IronSightsPosition").x ~= self:GetStatRawL("SightsPos").x and self:GetStatRawL("SightsPos").x ~= 0)) then + self:SetStatRawL("IronSightsPosition", self:GetStatRawL("SightsPos") or Vector()) + self:SetStatRawL("IronSightsAngle", self:GetStatRawL("SightsAng") or Vector()) + end +end + +local legacy_spread_cv = GetConVar("sv_tfa_spread_legacy") + +function SWEP:AutoDetectSpread() + local self2 = self:GetTable() + if legacy_spread_cv and legacy_spread_cv:GetBool() then + self:SetUpSpreadLegacy() + + return + end + + if self:GetStatRawL("Primary.SpreadMultiplierMax") == -1 or not self:GetStatRawL("Primary.SpreadMultiplierMax") then + self:SetStatRawL("Primary.SpreadMultiplierMax", math.Clamp(math.sqrt(math.sqrt(self:GetStatRawL("Primary.Damage") / 35) * 10 / 5) * 5, 0.01 / self:GetStatRawL("Primary.Spread"), 0.1 / self:GetStatRawL("Primary.Spread"))) + end + + if self:GetStatRawL("Primary.SpreadIncrement") == -1 or not self:GetStatRawL("Primary.SpreadIncrement") then + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadMultiplierMax") * 60 / self:GetStatRawL("Primary.RPM") * 0.85 * 1.5) + end + + if self:GetStatRawL("Primary.SpreadRecovery") == -1 or not self:GetStatRawL("Primary.SpreadRecovery") then + self:SetStatRawL("Primary.SpreadRecovery", math.max(self:GetStatRawL("Primary.SpreadMultiplierMax") * math.pow(self:GetStatRawL("Primary.RPM") / 600, 1 / 3) * 0.75, self:GetStatRawL("Primary.SpreadMultiplierMax") / 1.5)) + end +end + +--[[ +Function Name: AutoDetectMuzzle +Syntax: self:AutoDetectMuzzle(). Call only once, or it's redundant. +Returns: Nothing. +Notes: Detects the proper muzzle flash effect if you haven't specified one. +Purpose: Autodetection +]] +-- +function SWEP:AutoDetectMuzzle() + local self2 = self:GetTable() + if not self:GetStatRawL("MuzzleFlashEffect") then + local a = string.lower(self:GetStatRawL("Primary.Ammo")) + local cat = string.lower(self:GetStatRawL("Category") and self:GetStatRawL("Category") or "") + + if self:GetStatRawL("Silenced") or self:GetSilenced() then + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_silenced") + elseif string.find(a, "357") or self:GetStatRawL("Revolver") or string.find(cat, "revolver") then + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_revolver") + elseif self:GetStatL("LoopedReload") or a == "buckshot" or a == "slam" or a == "airboatgun" or string.find(cat, "shotgun") then + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_shotgun") + elseif string.find(a, "smg") or string.find(cat, "smg") or string.find(cat, "submachine") or string.find(cat, "sub-machine") then + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_smg") + elseif string.find(a, "sniper") or string.find(cat, "sniper") then + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_sniper") + elseif string.find(a, "pistol") or string.find(cat, "pistol") then + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_pistol") + elseif string.find(a, "ar2") or string.find(a, "rifle") or (string.find(cat, "revolver") and not string.find(cat, "rifle")) then + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_rifle") + else + self:SetStatRawL("MuzzleFlashEffect", "tfa_muzzleflash_generic") + end + end +end + +--[[ +Function Name: AutoDetectDamage +Syntax: self:AutoDetectDamage(). Call only once. Hopefully you call this only once on like SWEP:Initialize() or something. +Returns: Nothing. +Notes: Fixes the damage for GDCW. +Purpose: Autodetection +]] +-- +function SWEP:AutoDetectDamage() + local self2 = self:GetTable() + if self:GetStatRawL("Primary.Damage") and self:GetStatRawL("Primary.Damage") ~= -1 then return end + + if self:GetStatRawL("Primary.Round") then + local rnd = string.lower(self:GetStatRawL("Primary.Round")) + + if string.find(rnd, ".50bmg") then + self:SetStatRawL("Primary.Damage", 185) + elseif string.find(rnd, "5.45x39") then + self:SetStatRawL("Primary.Damage", 22) + elseif string.find(rnd, "5.56x45") then + self:SetStatRawL("Primary.Damage", 30) + elseif string.find(rnd, "338_lapua") then + self:SetStatRawL("Primary.Damage", 120) + elseif string.find(rnd, "338") then + self:SetStatRawL("Primary.Damage", 100) + elseif string.find(rnd, "7.62x51") then + self:SetStatRawL("Primary.Damage", 100) + elseif string.find(rnd, "9x39") then + self:SetStatRawL("Primary.Damage", 32) + elseif string.find(rnd, "9mm") then + self:SetStatRawL("Primary.Damage", 22) + elseif string.find(rnd, "9x19") then + self:SetStatRawL("Primary.Damage", 22) + elseif string.find(rnd, "9x18") then + self:SetStatRawL("Primary.Damage", 20) + end + + if string.find(rnd, "ap") then + self:SetStatRawL("Primary.Damage", self:GetStatRawL("Primary.Damage") * 1.2) + end + end + + if (not self:GetStatRawL("Primary.Damage")) or (self:GetStatRawL("Primary.Damage") <= 0.01) and self:GetStatRawL("Velocity") then + self:SetStatRawL("Primary.Damage", self:GetStatRawL("Velocity") / 5) + end + + if (not self:GetStatRawL("Primary.Damage")) or (self:GetStatRawL("Primary.Damage") <= 0.01) then + self:SetStatRawL("Primary.Damage", (self:GetStatRawL("Primary.KickUp") + self:GetStatRawL("Primary.KickUp") + self:GetStatRawL("Primary.KickUp")) * 10) + end +end + +--[[ +Function Name: AutoDetectDamageType +Syntax: self:AutoDetectDamageType(). Call only once. Hopefully you call this only once on like SWEP:Initialize() or something. +Returns: Nothing. +Notes: Sets a damagetype +Purpose: Autodetection +]] +-- +function SWEP:AutoDetectDamageType() + local self2 = self:GetTable() + if self:GetStatRawL("Primary.DamageType") == -1 or not self:GetStatRawL("Primary.DamageType") then + if self:GetStatRawL("DamageType") and not self:GetStatRawL("Primary.DamageType") then + self:SetStatRawL("Primary.DamageType", self:GetStatRawL("DamageType")) + else + self:SetStatRawL("Primary.DamageType", DMG_BULLET) + end + end +end + +--[[ +Function Name: AutoDetectForce +Syntax: self:AutoDetectForce(). Call only once. Hopefully you call this only once on like SWEP:Initialize() or something. +Returns: Nothing. +Notes: Detects force from damage +Purpose: Autodetection +]] +-- +function SWEP:AutoDetectForce() + local self2 = self:GetTable() + if self:GetStatRawL("Primary.Force") == -1 or not self:GetStatRawL("Primary.Force") then + self:SetStatRawL("Primary.Force", self:GetStatRawL("Force") or (math.sqrt(self:GetStatRawL("Primary.Damage") / 16) * 3 / math.sqrt(self:GetStatRawL("Primary.NumShots")))) + end +end + +function SWEP:AutoDetectPenetrationPower() + local self2 = self:GetTable() + + if self:GetStatRawL("Primary.PenetrationPower") == -1 or not self:GetStatRawL("Primary.PenetrationPower") then + local am = string.lower(self:GetStatL("Primary.Ammo")) + local m = 1 + + if (am == "pistol") then + m = 0.4 + elseif (am == "357") then + m = 1.75 + elseif (am == "smg1") then + m = 0.34 + elseif (am == "ar2") then + m = 1.1 + elseif (am == "buckshot") then + m = 0.3 + elseif (am == "airboatgun") then + m = 2.25 + elseif (am == "sniperpenetratedround") then + m = 3 + end + + self:SetStatRawL("Primary.PenetrationPower", self:GetStatRawL("PenetrationPower") or math.sqrt(self:GetStatRawL("Primary.Force") * 200 * m)) + end +end + +--[[ +Function Name: AutoDetectKnockback +Syntax: self:AutoDetectKnockback(). Call only once. Hopefully you call this only once on like SWEP:Initialize() or something. +Returns: Nothing. +Notes: Detects knockback from force +Purpose: Autodetection +]] +-- +function SWEP:AutoDetectKnockback() + local self2 = self:GetTable() + if self:GetStatRawL("Primary.Knockback") == -1 or not self:GetStatRawL("Primary.Knockback") then + self:SetStatRawL("Primary.Knockback", self:GetStatRawL("Knockback") or math.max(math.pow(self:GetStatRawL("Primary.Force") - 3.25, 2), 0) * math.pow(self:GetStatRawL("Primary.NumShots"), 1 / 3)) + end +end + +--[[ +Function Name: IconFix +Syntax: self:IconFix(). Call only once. Hopefully you call this only once on like SWEP:Initialize() or something. +Returns: Nothing. +Notes: Fixes the icon. Call this if you give it a texture path, or just nothing. +Purpose: Autodetection +]] +-- +local selicon_final = {} + +function SWEP:IconFix() + local self2 = self:GetTable() + + if not surface then return end + + local class = self2.ClassName + + if selicon_final[class] then + self:SetStatRawL("WepSelectIcon", selicon_final[class]) + + return + end + + if self2.WepSelectIcon and type(self2.WepSelectIcon) == "string" then + self:SetStatRawL("WepSelectIcon", surface.GetTextureID(self2.WepSelectIcon)) + else + if file.Exists("materials/vgui/hud/" .. class .. ".png", "GAME") then + self:SetStatRawL("WepSelectIcon", Material("vgui/hud/" .. class .. ".png", "smooth noclamp")) -- NOTHING should access this variable directly and our DrawWeaponSelection override supports IMaterial. + elseif file.Exists("materials/vgui/hud/" .. class .. ".vmt", "GAME") then + self:SetStatRawL("WepSelectIcon", surface.GetTextureID("vgui/hud/" .. class)) + end + end + + selicon_final[class] = self2.WepSelectIcon +end + +--[[ +Function Name: CorrectScopeFOV +Syntax: self:CorrectScopeFOV( fov ). Call only once. Hopefully you call this only once on like SWEP:Initialize() or something. +Returns: Nothing. +Notes: If you're using scopezoom instead of FOV, this translates it. +Purpose: Autodetection +]] +-- +function SWEP:CorrectScopeFOV(fov) + local self2 = self:GetTable() + fov = fov or self:GetStatRawL("DefaultFOV") + + if not self:GetStatRawL("Secondary.OwnerFOV") or self:GetStatRawL("Secondary.OwnerFOV") <= 0 then + if self:GetStatRawL("Scoped") and self:GetStatL("Secondary.ScopeZoom", -1) >= 1 then + self:SetStatRawL("Secondary.OwnerFOV", fov / self:GetStatL("Secondary.ScopeZoom")) + elseif self:GetStatRawL("Scoped_3D") then + self:SetStatRawL("Secondary.OwnerFOV", 70) + else + self:SetStatRawL("Secondary.OwnerFOV", 32) + end + end +end + +--[[ +Function Name: CreateFireModes +Syntax: self:CreateFireModes( is first draw). Call as much as you like. isfirstdraw controls whether the default fire mode is set. +Returns: Nothing. +Notes: Autodetects fire modes depending on what params you set up. +Purpose: Autodetection +]] +-- +SWEP.FireModeCache = {} + +function SWEP:CreateFireModes(isfirstdraw) + local self2 = self:GetTable() + if not self2.FireModes then + self:SetStatRawL("FireModes", {}) + local burstcnt = self:FindEvenBurstNumber() + + if self2.SelectiveFire then + if self2.OnlyBurstFire then + if burstcnt then + self2.FireModes[1] = burstcnt .. "Burst" + self2.FireModes[2] = "Single" + else + self2.FireModes[1] = "Single" + end + else + self2.FireModes[1] = "Automatic" + + if self2.DisableBurstFire then + self2.FireModes[2] = "Single" + else + if burstcnt then + self2.FireModes[2] = burstcnt .. "Burst" + self2.FireModes[3] = "Single" + else + self2.FireModes[2] = "Single" + end + end + end + else + if self2.Primary_TFA.Automatic then + self2.FireModes[1] = "Automatic" + + if self2.OnlyBurstFire and burstcnt then + self2.FireModes[1] = burstcnt .. "Burst" + end + else + self2.FireModes[1] = "Single" + end + end + end + + if self2.FireModes[#self2.FireModes] ~= "Safe" then + self2.FireModes[#self2.FireModes + 1] = "Safe" + end + + if not self2.FireModeCache or #self2.FireModeCache <= 0 then + for k, v in ipairs(self2.FireModes) do + self2.FireModeCache[v] = k + end + + if type(self2.DefaultFireMode) == "number" then + self:SetFireMode(self2.DefaultFireMode or (self2.Primary_TFA.Automatic and 1 or #self2.FireModes - 1)) + else + self:SetFireMode(self2.FireModeCache[self2.DefaultFireMode] or (self2.Primary_TFA.Automatic and 1 or #self2.FireModes - 1)) + end + end +end + +--[[ +Function Name: CacheAnimations +Syntax: self:CacheAnimations(). Call as much as you like. +Returns: Nothing. +Notes: This is what autodetects animations for the SWEP.SequenceEnabled and SWEP.SequenceLength tables. +Purpose: Autodetection +]] +-- +--SWEP.actlist = {ACT_VM_DRAW, ACT_VM_DRAW_EMPTY, ACT_VM_DRAW_SILENCED, ACT_VM_DRAW_DEPLOYED, ACT_VM_HOLSTER, ACT_VM_HOLSTER_EMPTY, ACT_VM_IDLE, ACT_VM_IDLE_EMPTY, ACT_VM_IDLE_SILENCED, ACT_VM_PRIMARYATTACK, ACT_VM_PRIMARYATTACK_1, ACT_VM_PRIMARYATTACK_EMPTY, ACT_VM_PRIMARYATTACK_SILENCED, ACT_VM_SECONDARYATTACK, ACT_VM_RELOAD, ACT_VM_RELOAD_EMPTY, ACT_VM_RELOAD_SILENCED, ACT_VM_ATTACH_SILENCER, ACT_VM_RELEASE, ACT_VM_DETACH_SILENCER, ACT_VM_FIDGET, ACT_VM_FIDGET_EMPTY, ACT_VM_FIDGET_SILENCED, ACT_SHOTGUN_RELOAD_START, ACT_VM_DRYFIRE, ACT_VM_DRYFIRE_SILENCED } +--If you really want, you can remove things from SWEP.actlist and manually enable animations and set their lengths. +SWEP.SequenceEnabled = {} +SWEP.SequenceLength = {} +SWEP.SequenceLengthOverride = {} --Override this if you want to change the length of a sequence but not the next idle +SWEP.ActCache = {} +local vm, seq + +function SWEP:CacheAnimations() + local self2 = self:GetTable() + table.Empty(self2.ActCache) + + if self:GetStatRawL("CanBeSilenced") and self2.SequenceEnabled[ACT_VM_IDLE_SILENCED] == nil then + self2.SequenceEnabled[ACT_VM_IDLE_SILENCED] = true + end + + if not self2.VMIV(self) then return end + vm = self2.OwnerViewModel + + if IsValid(vm) then + self:BuildAnimActivities() + + for _, v in ipairs(table.GetKeys(self2.AnimationActivities)) do + if isnumber(v) then + seq = vm:SelectWeightedSequence(v) + + if seq ~= -1 and vm:GetSequenceActivity(seq) == v and not self2.ActCache[seq] then + self2.SequenceEnabled[v] = true + self2.SequenceLength[v] = vm:SequenceDuration(seq) + self2.ActCache[seq] = v + else + self2.SequenceEnabled[v] = false + self2.SequenceLength[v] = 0.0 + end + else + local s = vm:LookupSequence(v) + + if s and s > 0 then + self2.SequenceEnabled[v] = true + self2.SequenceLength[v] = vm:SequenceDuration(s) + self2.ActCache[s] = v + else + self2.SequenceEnabled[v] = false + self2.SequenceLength[v] = 0.0 + end + end + end + else + return false + end + + if self:GetStatRawL("ProceduralHolsterEnabled") == nil then + if self2.SequenceEnabled[ACT_VM_HOLSTER] then + self:SetStatRawL("ProceduralHolsterEnabled", false) + else + self:SetStatRawL("ProceduralHolsterEnabled", true) + end + end + + self:SetStatRawL("HasDetectedValidAnimations", true) + + return true +end + +function SWEP:GetType() + if self:GetStatRawL("Type") then return self:GetStatRawL("Type") end + local at = string.lower(self:GetStatRawL("Primary.Ammo") or "") + local ht = string.lower((self:GetStatRawL("DefaultHoldType") or self:GetStatRawL("HoldType")) or "") + local rpm = self:GetStatRawL("Primary.RPM_Displayed") or self:GetStatRawL("Primary.RPM") or 600 + + if self:GetStatRawL("Primary.Projectile") or self:GetStatRawL("ProjectileEntity") then + if (self:GetStatRawL("Primary.ProjectileVelocity") or self:GetStatRawL("ProjectileVelocity")) > 400 then + self:SetStatRawL("Type", "Launcher") + else + self:SetStatRawL("Type", "Grenade") + end + + return self:GetType() + end + + if at == "buckshot" then + self:SetStatRawL("Type", "Shotgun") + + return self:GetType() + end + + if self:GetStatRawL("Pistol") or (at == "pistol" and (ht == "pistol" or ht == "revolver")) then + self:SetStatRawL("Type", "Pistol") + + return self:GetType() + end + + if self:GetStatRawL("SMG") or (at == "smg1" and (ht == "smg" or ht == "pistol" or ht == "357")) then + self:SetStatRawL("Type", "Sub-Machine Gun") + + return self:GetType() + end + + if self:GetStatRawL("Revolver") or (at == "357" and ht == "revolver") then + self:SetStatRawL("Type", "Revolver") + + return self:GetType() + end + + --Detect Sniper Type + if ( (self:GetStatRawL("Scoped") or self:GetStatRawL("Scoped_3D")) and rpm < 600 ) or at == "sniperpenetratedround" then + if rpm > 180 and (self:GetStatRawL("Primary.Automatic") or self:GetStatRawL("Primary.SelectiveFire")) then + self:SetStatRawL("Type", "Designated Marksman Rifle") + + return self:GetType() + else + self:SetStatRawL("Type", "Sniper Rifle") + + return self:GetType() + end + end + + --Detect based on holdtype + if ht == "pistol" then + if self:GetStatRawL("Primary.Automatic") then + self:SetStatRawL("Type", "Machine Pistol") + else + self:SetStatRawL("Type", "Pistol") + end + + return self:GetType() + end + + if ht == "duel" then + if at == "pistol" then + self:SetStatRawL("Type", "Dual Pistols") + + return self:GetType() + elseif at == "357" then + self:SetStatRawL("Type", "Dual Revolvers") + + return self:GetType() + elseif at == "smg1" then + self:SetStatRawL("Type", "Dual Sub-Machine Guns") + + return self:GetType() + else + self:SetStatRawL("Type", "Dual Guns") + + return self:GetType() + end + end + + --If it's using rifle ammo, it's a rifle or a carbine + if at == "ar2" then + if self:GetStatRawL("Primary.ClipSize") >= 60 then + self:SetStatRawL("Type", "Light Machine Gun") + + return self:GetType() + elseif ht == "rpg" or ht == "revolver" then + self:SetStatRawL("Type", "Carbine") + + return self:GetType() + else + self:SetStatRawL("Type", "Rifle") + + return self:GetType() + end + end + + --Check SMG one last time + if ht == "smg" or at == "smg1" then + self:SetStatRawL("Type", "Sub-Machine Gun") + + return self:GetType() + end + + if self:GetStatRawL("IsMelee") then + self:SetStatRawL("Type", "Melee") + + return self:GetType() + end + + if self:GetStatRawL("IsBow") then + self:SetStatRawL("Type", "Bow") + + return self:GetType() + end + + --Fallback to generic + self:SetStatRawL("Type", "Weapon") + + return self:GetType() +end + +function SWEP:SetUpSpreadLegacy() + local ht = self:GetStatRawL("DefaultHoldType") and self:GetStatRawL("DefaultHoldType") or self:GetStatRawL("HoldType") + + if not self:GetStatRawL("Primary.SpreadMultiplierMax") or self:GetStatRawL("Primary.SpreadMultiplierMax") <= 0 or self:GetStatRawL("AutoDetectSpreadMultiplierMax") then + self:SetStatRawL("Primary.SpreadMultiplierMax", 2.5 * math.max(self:GetStatRawL("Primary.RPM"), 400) / 600 * math.sqrt(self:GetStatRawL("Primary.Damage") / 30 * self:GetStatRawL("Primary.NumShots"))) --How far the spread can expand when you shoot. + + if ht == "smg" then + self:SetStatRawL("Primary.SpreadMultiplierMax", self:GetStatRawL("Primary.SpreadMultiplierMax") * 0.8) + end + + if ht == "revolver" then + self:SetStatRawL("Primary.SpreadMultiplierMax", self:GetStatRawL("Primary.SpreadMultiplierMax") * 2) + end + + if self:GetStatRawL("Scoped") then + self:SetStatRawL("Primary.SpreadMultiplierMax", self:GetStatRawL("Primary.SpreadMultiplierMax") * 1.5) + end + + self:SetStatRawL("AutoDetectSpreadMultiplierMax", true) + end + + if not self:GetStatRawL("Primary.SpreadIncrement") or self:GetStatRawL("Primary.SpreadIncrement") <= 0 or self:GetStatRawL("AutoDetectSpreadIncrement") then + self:SetStatRawL("AutoDetectSpreadIncrement", true) + self:SetStatRawL("Primary.SpreadIncrement", 1 * math.Clamp(math.sqrt(self:GetStatRawL("Primary.RPM")) / 24.5, 0.7, 3) * math.sqrt(self:GetStatRawL("Primary.Damage") / 30 * self:GetStatRawL("Primary.NumShots"))) --What percentage of the modifier is added on, per shot. + + if ht == "revolver" then + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * 2) + end + + if ht == "pistol" then + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * 1.35) + end + + if ht == "ar2" or ht == "rpg" then + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * 0.65) + end + + if ht == "smg" then + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * 1.75) + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * (math.Clamp((self:GetStatRawL("Primary.RPM") - 650) / 150, 0, 1) + 1)) + end + + if ht == "pistol" and self:GetStatRawL("Primary.Automatic") == true then + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * 1.5) + end + + if self:GetStatRawL("Scoped") then + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * 1.25) + end + + self:SetStatRawL("Primary.SpreadIncrement", self:GetStatRawL("Primary.SpreadIncrement") * math.sqrt(self:GetStatRawL("Primary.Recoil") * (self:GetStatRawL("Primary.KickUp") + self:GetStatRawL("Primary.KickDown") + self:GetStatRawL("Primary.KickHorizontal"))) * 0.8) + end + + if not self:GetStatRawL("Primary.SpreadRecovery") or self:GetStatRawL("Primary.SpreadRecovery") <= 0 or self:GetStatRawL("AutoDetectSpreadRecovery") then + self:SetStatRawL("AutoDetectSpreadRecovery", true) + self:SetStatRawL("Primary.SpreadRecovery", math.sqrt(math.max(self:GetStatRawL("Primary.RPM"), 300)) / 29 * 4) --How much the spread recovers, per second. + + if ht == "smg" then + self:SetStatRawL("Primary.SpreadRecovery", self:GetStatRawL("Primary.SpreadRecovery") * (1 - math.Clamp((self:GetStatRawL("Primary.RPM") - 600) / 200, 0, 1) * 0.33)) + end + end +end + +SWEP.LowAmmoSoundTypeBlacklist = { + ["launcher"] = true, + ["grenade"] = true, +} + +SWEP.LowAmmoSoundByType = { + ["handgun"] = "TFA.LowAmmo.Handgun", + ["pistol"] = "TFA.LowAmmo.Handgun", + ["dualpistols"] = "TFA.LowAmmo.Handgun", + ["machinepistol"] = "TFA.LowAmmo.Handgun", + ["handcannon"] = "TFA.LowAmmo.Revolver", + ["revolver"] = "TFA.LowAmmo.Revolver", + ["dualrevolvers"] = "TFA.LowAmmo.Revolver", + ["shotgun"] = "TFA.LowAmmo.Shotgun", + ["machinegun"] = "TFA.LowAmmo.MachineGun", + ["lightmachinegun"] = "TFA.LowAmmo.MachineGun", + ["heavymachinegun"] = "TFA.LowAmmo.MachineGun", + ["carbine"] = "TFA.LowAmmo.AssaultRifle", + ["rifle"] = "TFA.LowAmmo.AssaultRifle", + ["assaultrifle"] = "TFA.LowAmmo.AssaultRifle", + ["dmr"] = "TFA.LowAmmo.DMR", + ["designatedmarksmanrifle"] = "TFA.LowAmmo.DMR", + ["sniperrifle"] = "TFA.LowAmmo.Sniper", + ["smg"] = "TFA.LowAmmo.SMG", + ["submachinegun"] = "TFA.LowAmmo.SMG", +} +SWEP.LastAmmoSoundByType = { + ["handgun"] = "TFA.LowAmmo.Handgun_Dry", + ["pistol"] = "TFA.LowAmmo.Handgun_Dry", + ["dualpistols"] = "TFA.LowAmmo.Handgun_Dry", + ["machinepistol"] = "TFA.LowAmmo.Handgun_Dry", + ["handcannon"] = "TFA.LowAmmo.Revolver_Dry", + ["revolver"] = "TFA.LowAmmo.Revolver_Dry", + ["dualrevolvers"] = "TFA.LowAmmo.Revolver_Dry", + ["shotgun"] = "TFA.LowAmmo.Shotgun_Dry", + ["machinegun"] = "TFA.LowAmmo.MachineGun_Dry", + ["lightmachinegun"] = "TFA.LowAmmo.MachineGun_Dry", + ["heavymachinegun"] = "TFA.LowAmmo.MachineGun_Dry", + ["carbine"] = "TFA.LowAmmo.AssaultRifle_Dry", + ["rifle"] = "TFA.LowAmmo.AssaultRifle_Dry", + ["assaultrifle"] = "TFA.LowAmmo.AssaultRifle_Dry", + ["dmr"] = "TFA.LowAmmo.DMR_Dry", + ["designatedmarksmanrifle"] = "TFA.LowAmmo.DMR_Dry", + ["sniperrifle"] = "TFA.LowAmmo.Sniper_Dry", + ["smg"] = "TFA.LowAmmo.SMG_Dry", + ["submachinegun"] = "TFA.LowAmmo.SMG_Dry", +} + +function SWEP:AutoDetectLowAmmoSound() + if not self.FireSoundAffectedByClipSize then return end + + local t1, t2 = self:GetType():lower():gsub("[^%w]+", ""), (self:GetStatRawL("Type_Displayed") or ""):lower():gsub("[^%w]+", "") + + if self.LowAmmoSoundTypeBlacklist[t2] or self.LowAmmoSoundTypeBlacklist[t1] then return end + + local clip1 = self:GetStatRawL("Primary.ClipSize") + if (not clip1 or clip1 <= 4) then return end + + if not self.LowAmmoSound then + local snd = self.LowAmmoSoundByType[t2] or self.LowAmmoSoundByType[t1] or "TFA.LowAmmo" + + if (t2 == "shotgun" or t1 == "shotgun") and not self:GetStatL("LoopedReload") then + snd = "TFA.LowAmmo.AutoShotgun" + end + + self:SetStatRawL("LowAmmoSound", snd) + end + + if not self.LastAmmoSound then + local snd = self.LastAmmoSoundByType[t2] or self.LastAmmoSoundByType[t1] or "TFA.LowAmmo_Dry" + + if (t2 == "shotgun" or t1 == "shotgun") and not self:GetStatL("LoopedReload") then + snd = "TFA.LowAmmo.AutoShotgun_Dry" + end + + self:SetStatRawL("LastAmmoSound", snd) + end +end + +local EmptyFunctions = { + string.dump(function() end, true), + string.dump(function(self) end, true), +} +function SWEP:RemoveEmptyRTCode() + if not self.RTCode or type(self.RTCode) ~= "function" then return end + + local dump = string.dump(self.RTCode, true) + for _, str in ipairs(EmptyFunctions) do + if dump == str then + self.RTCode = nil + + break + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/bullet.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/bullet.lua new file mode 100644 index 0000000..e5399eb --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/bullet.lua @@ -0,0 +1,1114 @@ +local vector_origin = Vector() +local angle_zero = Angle() + +local l_mathClamp = math.Clamp +local Lerp = Lerp +SWEP.MainBullet = {} +SWEP.MainBullet.Spread = Vector() + +local function DisableOwnerDamage(a, b, c) + if b.Entity == a and c then + c:ScaleDamage(0) + end +end + +local ballistics_distcv = GetConVar("sv_tfa_ballistics_mindist") + +local function BallisticFirebullet(ply, bul, ovr, angPreserve) + local wep = ply:GetActiveWeapon() + + if TFA.Ballistics and TFA.Ballistics:ShouldUse(wep) then + if ballistics_distcv:GetInt() == -1 or util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 0x7fff, ply).HitPos:Distance(ply:GetShootPos()) > (ballistics_distcv:GetFloat() * TFA.Ballistics.UnitScale) then + bul.SmokeParticle = bul.SmokeParticle or wep.BulletTracer or wep.TracerBallistic or wep.BallisticTracer or wep.BallisticsTracer + + if ovr then + TFA.Ballistics:FireBullets(wep, bul, angPreserve or angle_zero, true) + else + TFA.Ballistics:FireBullets(wep, bul, angPreserve) + end + else + ply:FireBullets(bul) + end + else + ply:FireBullets(bul) + end +end + +--[[ +Function Name: ShootBulletInformation +Syntax: self:ShootBulletInformation(). +Returns: Nothing. +Notes: Used to generate a self.MainBullet table which is then sent to self:ShootBullet, and also to call shooteffects. +Purpose: Bullet +]] +-- +local sv_tfa_damage_multiplier = GetConVar("sv_tfa_damage_multiplier") +local sv_tfa_damage_multiplier_npc = GetConVar("sv_tfa_damage_multiplier_npc") +local cv_dmg_mult_min = GetConVar("sv_tfa_damage_mult_min") +local cv_dmg_mult_max = GetConVar("sv_tfa_damage_mult_max") +local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy") +local dmg, con, rec + +function SWEP:ShootBulletInformation() + --self:CalculateRatios() + self:UpdateConDamage() + + self.lastbul = nil + self.lastbulnoric = false + self.ConDamageMultiplier = self:GetOwner():IsNPC() and sv_tfa_damage_multiplier_npc:GetFloat() or sv_tfa_damage_multiplier:GetFloat() + + if not IsFirstTimePredicted() then return end + + con, rec = self:CalculateConeRecoil() + + local tmpranddamage = util.SharedRandom("TFA_Bullet_RandomDamageMult" .. CurTime(), cv_dmg_mult_min:GetFloat(), cv_dmg_mult_max:GetFloat(), self:EntIndex()) + local basedamage = self.ConDamageMultiplier * self:GetStatL("Primary.Damage") + dmg = basedamage * tmpranddamage + + local ns = self:GetStatL("Primary.NumShots") + local clip = (self:GetStatL("Primary.ClipSize") == -1) and self:Ammo1() or self:Clip1() + + ns = math.Round(ns, math.min(clip / self:GetStatL("Primary.NumShots"), 1)) + + self:ShootBullet(dmg, rec, ns, con) +end + +function SWEP:PreSpawnProjectile(ent) + -- override +end + +function SWEP:PostSpawnProjectile(ent) + -- override +end + +--[[ +Function Name: ShootBullet +Syntax: self:ShootBullet(damage, recoil, number of bullets, spray cone, disable ricochet, override the generated self.MainBullet table with this value if you send it). +Returns: Nothing. +Notes: Used to shoot a self.MainBullet. +Purpose: Bullet +]] +-- +local TracerName +local cv_forcemult = GetConVar("sv_tfa_force_multiplier") +local cv_knockbackmult = GetConVar("sv_tfa_knockback_multiplier") +local sv_tfa_bullet_penetration_power_mul = GetConVar("sv_tfa_bullet_penetration_power_mul") +local sv_tfa_bullet_randomseed = GetConVar("sv_tfa_bullet_randomseed") + +local randomseed = "tfa_" .. tostring({}) + +SWEP.Primary.SpreadBiasYaw = 1 +SWEP.Primary.SpreadBiasPitch = 1 + +-- Default ComputeBulletDeviation implementation +-- Custom implementations should return two numbers +-- Yaw (X) and Pitch (Y) deviation +function SWEP:ComputeBulletDeviation(bulletNum, totalBullets, aimcone) + local sharedRandomSeed + + if sv_tfa_bullet_randomseed:GetBool() then + sharedRandomSeed = randomseed .. CurTime() + else + sharedRandomSeed = "TFA_ShootBullet" .. CurTime() + end + + return + -- Yaw + util.SharedRandom(sharedRandomSeed, -aimcone * 45 * self:GetStatL("Primary.SpreadBiasYaw"), aimcone * 45 * self:GetStatL("Primary.SpreadBiasYaw"), totalBullets + 1 + bulletNum), + -- Pitch + util.SharedRandom(sharedRandomSeed, -aimcone * 45 * self:GetStatL("Primary.SpreadBiasPitch"), aimcone * 45 * self:GetStatL("Primary.SpreadBiasPitch"), bulletNum) +end + +function SWEP:ShootBullet(damage, recoil, num_bullets, aimcone, disablericochet, bulletoverride) + if not IsFirstTimePredicted() and not game.SinglePlayer() then return end + num_bullets = num_bullets or 1 + aimcone = aimcone or 0 + + self:SetLastGunFire(CurTime()) + + local owner = self:GetOwner() + + if self:GetStatL("Primary.Projectile") then + if CLIENT then return end + + for i = 1, num_bullets do + local ent = ents.Create(self:GetStatL("Primary.Projectile")) + + local ang = self:GetAimAngle() + + local dYaw, dPitch = self:ComputeBulletDeviation(i, num_bullets, aimcone) + ang:RotateAroundAxis(ang:Up(), dYaw) + ang:RotateAroundAxis(ang:Right(), dPitch) + + ent:SetPos(owner:GetShootPos()) + ent:SetOwner(owner) + ent:SetAngles(ang) + ent.damage = self:GetStatL("Primary.Damage") + ent.mydamage = self:GetStatL("Primary.Damage") + + if self:GetStatL("Primary.ProjectileModel") then + ent:SetModel(self:GetStatL("Primary.ProjectileModel")) + end + + self:PreSpawnProjectile(ent) + + ent:Spawn() + + local dir = ang:Forward() + dir:Mul(self:GetStatL("Primary.ProjectileVelocity")) + + ent:SetVelocity(dir) + local phys = ent:GetPhysicsObject() + + if IsValid(phys) then + phys:SetVelocity(dir) + end + + if self.ProjectileModel then + ent:SetModel(self:GetStatL("Primary.ProjectileModel")) + end + + self:PostSpawnProjectile(ent) + end + -- Source + -- Dir of self.MainBullet + -- Aim Cone X + -- Aim Cone Y + -- Show a tracer on every x bullets + -- Amount of force to give to phys objects + + return + end + + if self.Tracer == 1 then + TracerName = "Ar2Tracer" + elseif self.Tracer == 2 then + TracerName = "AirboatGunHeavyTracer" + else + TracerName = "Tracer" + end + + self.MainBullet.PCFTracer = nil + + if self:GetStatL("TracerName") and self:GetStatL("TracerName") ~= "" then + if self:GetStatL("TracerPCF") then + TracerName = nil + self.MainBullet.PCFTracer = self:GetStatL("TracerName") + self.MainBullet.Tracer = 0 + else + TracerName = self:GetStatL("TracerName") + end + end + + self.MainBullet.Attacker = owner + self.MainBullet.Inflictor = self + self.MainBullet.Src = owner:GetShootPos() + + self.MainBullet.Dir = self:GetAimVector() + self.MainBullet.HullSize = self:GetStatL("Primary.HullSize") or 0 + self.MainBullet.Spread.x = 0 + self.MainBullet.Spread.y = 0 + + self.MainBullet.Num = 1 + + if num_bullets == 1 then + local dYaw, dPitch = self:ComputeBulletDeviation(1, 1, aimcone) + + local ang = self.MainBullet.Dir:Angle() + local up, right = ang:Up(), ang:Right() + + ang:RotateAroundAxis(up, dYaw) + ang:RotateAroundAxis(right, dPitch) + + self.MainBullet.Dir = ang:Forward() + end + + self.MainBullet.Wep = self + + if self.TracerPCF then + self.MainBullet.Tracer = 0 + else + self.MainBullet.Tracer = self:GetStatL("TracerCount") or 3 + end + + self.MainBullet.TracerName = TracerName + self.MainBullet.PenetrationCount = 0 + self.MainBullet.PenetrationPower = self:GetStatL("Primary.PenetrationPower") * sv_tfa_bullet_penetration_power_mul:GetFloat(1) + self.MainBullet.InitialPenetrationPower = self.MainBullet.PenetrationPower + self.MainBullet.AmmoType = self:GetPrimaryAmmoType() + self.MainBullet.Force = self:GetStatL("Primary.Force") * cv_forcemult:GetFloat() * self:GetAmmoForceMultiplier() + self.MainBullet.Damage = damage + self.MainBullet.InitialDamage = damage + self.MainBullet.InitialForce = self.MainBullet.Force + self.MainBullet.InitialPosition = Vector(self.MainBullet.Src) + self.MainBullet.HasAppliedRange = false + + if self.CustomBulletCallback then + self.MainBullet.Callback2 = self.CustomBulletCallback + else + self.MainBullet.Callback2 = nil + end + + if num_bullets > 1 then + local ang_ = self.MainBullet.Dir:Angle() + local up, right = ang_:Up(), ang_:Right() + + -- single callback per multiple bullets fix + for i = 1, num_bullets do + local bullet = table.Copy(self.MainBullet) + + local ang = Angle(ang_) + + local dYaw, dPitch = self:ComputeBulletDeviation(i, num_bullets, aimcone) + ang:RotateAroundAxis(up, dYaw) + ang:RotateAroundAxis(right, dPitch) + + bullet.Dir = ang:Forward() + + function bullet.Callback(attacker, trace, dmginfo) + if not IsValid(self) then return end + + dmginfo:SetInflictor(self) + dmginfo:SetDamage(dmginfo:GetDamage() * bullet:CalculateFalloff(trace.HitPos)) + + if bullet.Callback2 then + bullet.Callback2(attacker, trace, dmginfo) + end + + self:CallAttFunc("CustomBulletCallback", attacker, trace, dmginfo) + + bullet:Penetrate(attacker, trace, dmginfo, self, {}) + self:PCFTracer(bullet, trace.HitPos or vector_origin) + end + + BallisticFirebullet(owner, bullet, nil, ang) + end + + return + end + + function self.MainBullet.Callback(attacker, trace, dmginfo) + if not IsValid(self) then return end + + dmginfo:SetInflictor(self) + dmginfo:SetDamage(dmginfo:GetDamage() * self.MainBullet:CalculateFalloff(trace.HitPos)) + + if self.MainBullet.Callback2 then + self.MainBullet.Callback2(attacker, trace, dmginfo) + end + + self:CallAttFunc("CustomBulletCallback", attacker, trace, dmginfo) + + self.MainBullet:Penetrate(attacker, trace, dmginfo, self, {}) + self:PCFTracer(self.MainBullet, trace.HitPos or vector_origin) + end + + BallisticFirebullet(owner, self.MainBullet, nil, self.MainBullet.Dir:Angle()) +end + +local sp = game.SinglePlayer() + +function SWEP:TFAMove(ply, movedata) + local velocity = self:GetQueuedRecoil() + + if velocity:Length() ~= 0 then + movedata:SetVelocity(movedata:GetVelocity() + velocity) + self:SetQueuedRecoil(vector_origin) + end +end + +hook.Add("Move", "TFAMove", function(self, movedata) + local weapon = self:GetActiveWeapon() + + if IsValid(weapon) and weapon.IsTFAWeapon then + weapon:TFAMove(self, movedata) + end +end) + +local sv_tfa_recoil_mul_p = GetConVar("sv_tfa_recoil_mul_p") +local sv_tfa_recoil_mul_p_npc = GetConVar("sv_tfa_recoil_mul_p_npc") +local sv_tfa_recoil_mul_y = GetConVar("sv_tfa_recoil_mul_y") +local sv_tfa_recoil_mul_y_npc = GetConVar("sv_tfa_recoil_mul_y_npc") + +local sv_tfa_recoil_viewpunch_mul = GetConVar("sv_tfa_recoil_viewpunch_mul") +local sv_tfa_recoil_eyeangles_mul = GetConVar("sv_tfa_recoil_eyeangles_mul") + +function SWEP:SetRecoilVector(vector) + if self:GetOwner():IsPlayer() then + self:SetQueuedRecoil(vector) + else + self:GetOwner():SetVelocity(vector) + end +end + +function SWEP:QueueRecoil(vector) + if self:GetOwner():IsPlayer() then + self:SetQueuedRecoil(vector + self:GetQueuedRecoil()) + else + self:GetOwner():SetVelocity(vector) + end +end + +function SWEP:Recoil(recoil, ifp) + if sp and type(recoil) == "string" then + local _, CurrentRecoil = self:CalculateConeRecoil() + self:Recoil(CurrentRecoil, true) + + return + end + + local owner = self:GetOwner() + local isplayer = owner:IsPlayer() + + self:SetSpreadRatio(l_mathClamp(self:GetSpreadRatio() + self:GetStatL("Primary.SpreadIncrement"), 1, self:GetStatL("Primary.SpreadMultiplierMax"))) + self:QueueRecoil(-owner:GetAimVector() * self:GetStatL("Primary.Knockback") * cv_knockbackmult:GetFloat() * recoil / 5) + + local seed = self:GetSeed() + 1 + + local kickP = util.SharedRandom("TFA_KickDown", self:GetStatL("Primary.KickDown"), self:GetStatL("Primary.KickUp"), seed) * recoil * -1 + local kickY = util.SharedRandom("TFA_KickHorizontal", -self:GetStatL("Primary.KickHorizontal"), self:GetStatL("Primary.KickHorizontal"), seed) * recoil + + if isplayer then + kickP, kickY = kickP * sv_tfa_recoil_mul_p:GetFloat(), kickY * sv_tfa_recoil_mul_y:GetFloat() + else + kickP, kickY = kickP * sv_tfa_recoil_mul_p_npc:GetFloat(), kickY * sv_tfa_recoil_mul_y_npc:GetFloat() + end + + local factor = 1 - self:GetStatL("Primary.StaticRecoilFactor") + + if self:GetIronSights() then + factor = factor * Lerp(self:GetIronSightsProgress(), 1, self:GetStatL("Primary.IronRecoilMultiplier", 0.5)) + end + + factor = factor * Lerp(self:GetCrouchingRatio(), 1, self:GetStatL("CrouchAccuracyMultiplier", 0.5)) + + local punchY = kickY * factor + local deltaP = 0 + local deltaY = 0 + + if self:HasRecoilLUT() then + local ang = self:GetRecoilLUTAngle() + + if self:GetPrevRecoilAngleTime() < CurTime() then + self:SetPrevRecoilAngleTime(CurTime() + 0.1) + self:SetPrevRecoilAngle(ang) + end + + local prev_recoil_angle = self:GetPrevRecoilAngle() + deltaP = ang.p - prev_recoil_angle.p + deltaY = ang.y - prev_recoil_angle.y + self:SetPrevRecoilAngle(ang) + end + + if isplayer then + local maxdist = math.min(math.max(0, 89 + owner:EyeAngles().p - math.abs(owner:GetViewPunchAngles().p * 2)), 88.5) + local punchP = l_mathClamp((kickP + deltaP * self:GetStatL("Primary.RecoilLUT_ViewPunchMult")) * factor, -maxdist, maxdist) + + owner:ViewPunch(Angle(punchP * sv_tfa_recoil_viewpunch_mul:GetFloat(), (punchY + deltaY * self:GetStatL("Primary.RecoilLUT_ViewPunchMult")) * sv_tfa_recoil_viewpunch_mul:GetFloat())) + end + + if (not isplayer or not sv_tfa_recoil_legacy:GetBool()) and not self:HasRecoilLUT() then + local maxdist2 = l_mathClamp(30 - math.abs(self:GetViewPunchP()), 0, 30) + local punchP2 = l_mathClamp(kickP, -maxdist2, maxdist2) * factor + + self:SetViewPunchP(self:GetViewPunchP() + punchP2 * 1.5) + self:SetViewPunchY(self:GetViewPunchY() + punchY * 1.5) + self:SetViewPunchBuild(math.min(3, self:GetViewPunchBuild() + math.sqrt(math.pow(punchP2, 2) + math.pow(punchY, 2)) / 3) + 0.2) + end + + if isplayer and ((game.SinglePlayer() and SERVER) or (CLIENT and ifp)) then + local neweyeang = owner:EyeAngles() + + local ap, ay = (kickP + deltaP * self:GetStatL("Primary.RecoilLUT_AnglePunchMult")) * self:GetStatL("Primary.StaticRecoilFactor") * sv_tfa_recoil_eyeangles_mul:GetFloat(), + (kickY + deltaY * self:GetStatL("Primary.RecoilLUT_AnglePunchMult")) * self:GetStatL("Primary.StaticRecoilFactor") * sv_tfa_recoil_eyeangles_mul:GetFloat() + + neweyeang.p = neweyeang.p + ap + neweyeang.y = neweyeang.y + ay + --neweyeang.p = l_mathClamp(neweyeang.p, -90 + math.abs(owner:GetViewPunchAngles().p), 90 - math.abs(owner:GetViewPunchAngles().p)) + owner:SetEyeAngles(neweyeang) + end +end + +--[[ +Function Name: GetAmmoRicochetMultiplier +Syntax: self:GetAmmoRicochetMultiplier(). +Returns: The ricochet multiplier for our ammotype. More is more chance to ricochet. +Notes: Only compatible with default ammo types, unless you/I mod that. BMG ammotype is detected based on name and category. +Purpose: Utility +]] +-- +function SWEP:GetAmmoRicochetMultiplier() + local am = string.lower(self:GetStatL("Primary.Ammo")) + + if (am == "pistol") then + return 1.25 + elseif (am == "357") then + return 0.75 + elseif (am == "smg1") then + return 1.1 + elseif (am == "ar2") then + return 0.9 + elseif (am == "buckshot") then + return 2 + elseif (am == "slam") then + return 1.5 + elseif (am == "airboatgun") then + return 0.8 + elseif (am == "sniperpenetratedround") then + return 0.5 + else + return 1 + end +end + +--[[ +Function Name: GetMaterialConcise +Syntax: self:GetMaterialConcise(). +Returns: The string material name. +Notes: Always lowercase. +Purpose: Utility +]] +-- +function SWEP:GetAmmoForceMultiplier() + -- pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, AirboatGun + --AR2=Rifle ~= Caliber>.308 + --SMG1=SMG ~= Small/Medium Calber ~= 5.56 or 9mm + --357=Revolver ~= .357 through .50 magnum + --Pistol = Small or Pistol Bullets ~= 9mm, sometimes .45ACP but rarely. Generally light. + --Buckshot = Buckshot = Light, barely-penetrating sniper bullets. + --Slam = Medium Shotgun Round + --AirboatGun = Heavy, Penetrating Shotgun Round + --SniperPenetratedRound = Heavy Large Rifle Caliber ~= .50 Cal blow-yer-head-off + local am = string.lower(self:GetStatL("Primary.Ammo")) + + if (am == "pistol") then + return 0.4 + elseif (am == "357") then + return 0.6 + elseif (am == "smg1") then + return 0.475 + elseif (am == "ar2") then + return 0.6 + elseif (am == "buckshot") then + return 0.5 + elseif (am == "slam") then + return 0.5 + elseif (am == "airboatgun") then + return 0.7 + elseif (am == "sniperpenetratedround") then + return 1 + else + return 1 + end +end + +--[[ +Function Name: GetPenetrationMultiplier +Syntax: self:GetPenetrationMultiplier( concise material name). +Returns: The multilier for how much you can penetrate through a material. +Notes: Should be used with GetMaterialConcise. +Purpose: Utility +]] +-- +SWEP.Primary.PenetrationMaterials = { + [MAT_DEFAULT] = 1, + [MAT_VENT] = 0.4, --Since most is aluminum and stuff + [MAT_METAL] = 0.6, --Since most is aluminum and stuff + [MAT_WOOD] = 0.2, + [MAT_PLASTIC] = 0.23, + [MAT_FLESH] = 0.48, + [MAT_CONCRETE] = 0.87, + [MAT_GLASS] = 0.16, + [MAT_SAND] = 1, + [MAT_SLOSH] = 1, + [MAT_DIRT] = 0.95, --This is plaster, not dirt, in most cases. + [MAT_FOLIAGE] = 0.9 +} + +local fac + +function SWEP:GetPenetrationMultiplier(mat) + fac = self.Primary.PenetrationMaterials[mat or MAT_DEFAULT] or self.Primary.PenetrationMaterials[MAT_DEFAULT] + + return fac * (self:GetStatL("Primary.PenetrationMultiplier") and self:GetStatL("Primary.PenetrationMultiplier") or 1) +end + +local decalbul = { + Num = 1, + Spread = vector_origin, + Tracer = 0, + TracerName = "", + Force = 0, + Damage = 0, + Distance = 40 +} + +local maxpen +local penetration_max_cvar = GetConVar("sv_tfa_penetration_hardlimit") +local penetration_cvar = GetConVar("sv_tfa_bullet_penetration") +local ricochet_cvar = GetConVar("sv_tfa_bullet_ricochet") +local cv_rangemod = GetConVar("sv_tfa_range_modifier") +local cv_decalbul = GetConVar("sv_tfa_fx_penetration_decal") +local atype +local develop = GetConVar("developer") +local sv_tfa_debug = GetConVar("sv_tfa_debug") + +function SWEP:SetBulletTracerName(nm) + self.BulletTracerName = nm or self.BulletTracerName or "" +end + +local debugcolors = { + Color(166, 91, 236), + Color(91, 142, 236), + Color(29, 197, 208), + Color(61, 232, 109), + Color(194, 232, 61), + Color(232, 178, 61), + Color(232, 61, 129), + Color(128, 31, 109), +} + +local nextdebugcol = -1 +local debugsphere1 = Color(149, 189, 230) +local debugsphere2 = Color(34, 43, 53) +local debugsphere3 = Color(255, 255, 255) +local debugsphere4 = Color(0, 0, 255) +local debugsphere5 = Color(12, 255, 0) + +local IsInWorld, IsInWorld2 + +do + local tr = {collisiongroup = COLLISION_GROUP_WORLD} + + function IsInWorld2(pos) + tr.start = pos + tr.endpos = pos + return not util.TraceLine(tr).AllSolid + end +end + +if CLIENT then + IsInWorld = IsInWorld2 +else + IsInWorld = util.IsInWorld +end + +local MAX_CORRECTION_ITERATIONS = 20 + +-- bullettable can be nil +function SWEP:CalculateFalloff(InitialPosition, HitPos, bullettable) + local dist = InitialPosition:Distance(HitPos) + + if not self.Primary.RangeFalloffLUTBuilt then return 1 end + + local target = self.Primary.RangeFalloffLUTBuilt + + if dist <= target[1][1] then + return target[1][2] + end + + if dist >= target[#target][1] then + return target[#target][2] + end + + for i = 1, #target - 1 do + local a, b = target[i], target[i + 1] + + if a[1] <= dist and b[1] >= dist then + return Lerp((dist - a[1]) / (b[1] - a[1]), a[2], b[2]) + end + end + + return target[#target][2] -- wtf? +end + +function SWEP.MainBullet:CalculateFalloff(HitPos) + return self.Wep:CalculateFalloff(self.InitialPosition, HitPos, self) +end + +local function shouldDisplayDebug() + return SERVER and sv_tfa_debug:GetBool() and develop:GetBool() and DLib +end + +function SWEP.MainBullet:Penetrate(ply, traceres, dmginfo, weapon, penetrated, previousStartPos) + if hook.Run("TFA_Bullet_Penetrate", weapon, ply, traceres, dmginfo, penetrated, previousStartPos) == false then return end + + --debugoverlay.Sphere( self.Src, 5, 5, color_white, true) + + DisableOwnerDamage(ply, traceres, dmginfo) + + if self.TracerName and self.TracerName ~= "" then + weapon.BulletTracerName = self.TracerName + + if game.SinglePlayer() then + weapon:CallOnClient("SetBulletTracerName", weapon.BulletTracerName) + end + end + + if not IsValid(weapon) then return end + + local hitent = traceres.Entity + + self:HandleDoor(ply, traceres, dmginfo, weapon) + + atype = weapon:GetStatL("Primary.DamageType") + dmginfo:SetDamageType(atype) + + if SERVER and IsValid(ply) and ply:IsPlayer() and IsValid(hitent) and (hitent:IsPlayer() or hitent:IsNPC() or type(hitent) == "NextBot") then + weapon:SendHitMarker(ply, traceres, dmginfo) + end + + if IsValid(traceres.Entity) and traceres.Entity:GetClass() == "npc_sniper" then + traceres.Entity.TFAHP = (traceres.Entity.TFAHP or 100) - dmginfo:GetDamage() + + if traceres.Entity.TFAHP <= 0 then + traceres.Entity:Fire("SetHealth", "", -1) + end + end + + local cl = hitent:GetClass() + + if cl == "npc_helicopter" then + dmginfo:SetDamageType(bit.bor(dmginfo:GetDamageType(), DMG_AIRBOAT)) + end + + -- custom damage checks + if atype ~= DMG_BULLET then + --[[if cl == "npc_strider" and (dmginfo:IsDamageType(DMG_SHOCK) or dmginfo:IsDamageType(DMG_BLAST)) and traceres.Hit and IsValid(hitent) and hitent.Fire then + hitent:SetHealth(math.max(hitent:Health() - dmginfo:GetDamage(), 2)) + + if hitent:Health() <= 3 then + hitent:Extinguish() + hitent:Fire("sethealth", "-1", 0.01) + dmginfo:ScaleDamage(0) + end + end]] + + if dmginfo:IsDamageType(DMG_BURN) and weapon.Primary.DamageTypeHandled and traceres.Hit and IsValid(hitent) and not traceres.HitWorld and not traceres.HitSky and dmginfo:GetDamage() > 1 and hitent.Ignite then + hitent:Ignite(dmginfo:GetDamage() / 2, 1) + end + + if dmginfo:IsDamageType(DMG_BLAST) and weapon.Primary.DamageTypeHandled and traceres.Hit and not traceres.HitSky then + local tmpdmg = dmginfo:GetDamage() + dmginfo:SetDamageForce(dmginfo:GetDamageForce() / 2) + util.BlastDamageInfo(dmginfo, traceres.HitPos, weapon:GetStatL("Primary.BlastRadius") or (tmpdmg / 2) ) + --util.BlastDamage(weapon, weapon:GetOwner(), traceres.HitPos, tmpdmg / 2, tmpdmg) + local fx = EffectData() + fx:SetOrigin(traceres.HitPos) + fx:SetNormal(traceres.HitNormal) + + if weapon:GetStatL("Primary.ImpactEffect") then + TFA.Effects.Create(weapon:GetStatL("Primary.ImpactEffect"), fx) + elseif tmpdmg > 90 then + TFA.Effects.Create("HelicopterMegaBomb", fx) + TFA.Effects.Create("Explosion", fx) + elseif tmpdmg > 45 then + TFA.Effects.Create("cball_explode", fx) + else + TFA.Effects.Create("MuzzleEffect", fx) + end + + dmginfo:ScaleDamage(0.15) + end + end + + if self:Ricochet(ply, traceres, dmginfo, weapon) then + if shouldDisplayDebug() then + DLib.debugoverlay.Text(traceres.HitPos - Vector(0, 0, 12), 'ricochet', 10) + end + + return + end + + if penetration_cvar and not penetration_cvar:GetBool() then return end + if self.PenetrationCount > math.min(penetration_max_cvar:GetInt(100), weapon:GetStatL("Primary.MaxSurfacePenetrationCount", math.huge)) then return end + -- source engine quirk - if bullet is fired too close to brush surface + -- it is assumed to be fired right in front of it, rather than exact + -- position you specified. + if previousStartPos and previousStartPos:Distance(traceres.HitPos) < 0.05 then return end + local oldTraceResHitPos = Vector(traceres.HitPos) + + local mult = weapon:GetPenetrationMultiplier(traceres.MatType) + local newdir = (traceres.HitPos - traceres.StartPos):GetNormalized() + local desired_length = l_mathClamp(self.PenetrationPower / mult, 0, l_mathClamp(sv_tfa_bullet_penetration_power_mul:GetFloat() * 100, 1000, 8000)) + local penetrationoffset = newdir * desired_length + + local pentrace = { + start = traceres.HitPos, + endpos = traceres.HitPos + penetrationoffset, + mask = MASK_SHOT, + filter = penetrated + } + + local isent = IsValid(traceres.Entity) + local startpos, decalstartpos + + if isent then + table.insert(penetrated, traceres.Entity) + else + pentrace.start:Add(traceres.Normal) + pentrace.start:Add(traceres.Normal) + pentrace.collisiongroup = COLLISION_GROUP_WORLD + pentrace.filter = NULL + end + + local pentraceres = util.TraceLine(pentrace) + local pentraceres2, pentrace2 + local loss + local realstartpos + + if not isent then + local acc_length = pentraceres.HitPos:Distance(pentraceres.StartPos) + + local ostart = pentrace.start + local FractionLeftSolid = pentraceres.FractionLeftSolid + local iter = 0 + + local cond = (pentraceres.AllSolid or not IsInWorld2(pentraceres.HitPos)) and acc_length < desired_length + + while (pentraceres.AllSolid or not IsInWorld2(pentraceres.HitPos)) and acc_length <= desired_length and iter < MAX_CORRECTION_ITERATIONS do + iter = iter + 1 + + pentrace.start = pentraceres.HitPos + newdir * 8 + + if shouldDisplayDebug() then + DLib.debugoverlay.Cross(pentrace.start, 8, 10, Color(iter / MAX_CORRECTION_ITERATIONS * 255, iter / MAX_CORRECTION_ITERATIONS * 255, iter / MAX_CORRECTION_ITERATIONS * 255), true) + end + + pentraceres = util.TraceLine(pentrace) + acc_length = acc_length + pentraceres.HitPos:Distance(pentraceres.StartPos) + 8 + end + + if cond and not (pentraceres.AllSolid or not IsInWorld2(pentraceres.HitPos)) then + pentraceres.FractionLeftSolid = ostart:Distance(pentrace.start) / ostart:Distance(pentrace.endpos) + pentraceres.FractionLeftSolid + 0.02 + pentrace.start = ostart + pentraceres.StartPos = ostart + else + pentraceres.FractionLeftSolid = FractionLeftSolid + pentrace.start = ostart + pentraceres.StartPos = ostart + end + end + + if isent then + startpos = pentraceres.HitPos - newdir + local ent = traceres.Entity + + pentrace2 = { + start = startpos, + endpos = pentrace.start, + mask = MASK_SHOT, + ignoreworld = true, + filter = function(ent2) + return ent2 == ent + end + } + + pentraceres2 = util.TraceLine(pentrace2) + loss = pentraceres2.HitPos:Distance(pentrace.start) * mult + + if pentraceres2.HitPos:Distance(pentrace.start) < 0.01 then + -- bullet stuck in + loss = self.PenetrationPower + end + + decalstartpos = pentraceres2.HitPos + newdir * 3 + + if shouldDisplayDebug() then + nextdebugcol = (nextdebugcol + 1) % #debugcolors + DLib.debugoverlay.Line(pentrace.start, pentrace.endpos, 10, debugcolors[nextdebugcol + 1], true) + DLib.debugoverlay.Cross(pentrace.start, 8, 10, debugsphere1, true) + DLib.debugoverlay.Cross(pentraceres2.HitPos, 8, 10, debugsphere2, true) + end + + if self.IsBallistics then + startpos = decalstartpos + end + + realstartpos = decalstartpos + else + startpos = LerpVector(pentraceres.FractionLeftSolid, pentrace.start, pentrace.endpos) + newdir * 4 + realstartpos = startpos + decalstartpos = startpos + newdir * 2 + loss = startpos:Distance(pentrace.start) * mult + + if shouldDisplayDebug() then + nextdebugcol = (nextdebugcol + 1) % #debugcolors + DLib.debugoverlay.Line(pentrace.start, pentrace.endpos, 10, debugcolors[nextdebugcol + 1], true) + DLib.debugoverlay.Cross(pentrace.start, 8, 10, debugsphere1, true) + DLib.debugoverlay.Cross(startpos, 8, 10, debugsphere2, true) + end + + if pentraceres.AllSolid then + return + elseif not IsInWorld(pentraceres.HitPos) then + return + end + + if not IsInWorld2(startpos) then + for i = 1, 10 do + startpos = LerpVector(pentraceres.FractionLeftSolid, pentrace.start, pentrace.endpos) + newdir * ((4 - i) * 3) + + if IsInWorld2(startpos) then break end + + startpos = LerpVector(pentraceres.FractionLeftSolid, pentrace.start, pentrace.endpos) + newdir * ((4 + i) * 3) + + if IsInWorld2(startpos) then break end + end + + if not IsInWorld2(startpos) then + return + end + end + end + + if self.PenetrationPower - loss <= 0 then + if shouldDisplayDebug() then + DLib.debugoverlay.Text(startpos, string.format('Lost penetration power %.3f %.3f', self.PenetrationPower, loss), 10) + end + + return + end + + self.PenetrationCount = self.PenetrationCount + 1 + local prev = self.PenetrationPower + self.PenetrationPower = self.PenetrationPower - loss + + local mfac = self.PenetrationPower / self.InitialPenetrationPower + + if shouldDisplayDebug() and weapon.Primary.RangeFalloffLUTBuilt then + DLib.debugoverlay.Text(traceres.HitPos + Vector(0, 0, 12), string.format('NEW Damage falloff final %.3f %.3f %.3f %.3f', self.InitialPosition:Distance(traceres.HitPos), self:CalculateFalloff(traceres.HitPos), mfac * self.InitialDamage * self:CalculateFalloff(traceres.HitPos), mfac), 12) + end + + local Damage = self.InitialDamage * self:CalculateFalloff(realstartpos) * mfac + + local bul = { + PenetrationPower = self.PenetrationPower, + PenetrationCount = self.PenetrationCount, + InitialPenetrationPower = self.InitialPenetrationPower, + InitialDamage = self.InitialDamage, + InitialForce = self.InitialForce, + CalculateFalloff = self.CalculateFalloff, + InitialPosition = self.InitialPosition, + Src = startpos, + Dir = newdir, + Tracer = 1, + TracerName = self.TracerName, + IgnoreEntity = traceres.Entity, + + Num = 1, + Force = self.InitialForce * mfac, + Damage = Damage, + Penetrate = self.Penetrate, + MakeDoor = self.MakeDoor, + HandleDoor = self.HandleDoor, + Ricochet = self.Ricochet, + Spread = vector_origin, + Wep = weapon, + } + + if shouldDisplayDebug() then + DLib.debugoverlay.Text(startpos, string.format('penetrate %.3f->%.3f %d %.3f', prev, self.PenetrationPower, self.PenetrationCount, mfac), 10) + end + + function bul.Callback(attacker, trace, dmginfo2) + if shouldDisplayDebug() then + DLib.debugoverlay.Cross(trace.HitPos, 8, 10, debugsphere3, true) + DLib.debugoverlay.Text(trace.HitPos - Vector(0, 0, 7), string.format('hit %.3f %d', mfac, bul.PenetrationCount, bul.PenetrationPower), 10) + end + + dmginfo2:SetInflictor(IsValid(bul.Wep) and bul.Wep or IsValid(ply) and ply or Entity(0)) + + bul.Damage = self.InitialDamage * self:CalculateFalloff(trace.HitPos) * mfac + dmginfo2:SetDamage(bul.Damage) + + hook.Run("TFA_BulletPenetration", bul, attacker, trace, dmginfo2) + + -- TODO: User died while bullet make penetration + -- handle further penetrations even when user is dead + if IsValid(bul.Wep) then + bul:Penetrate(attacker, trace, dmginfo2, bul.Wep, penetrated, oldTraceResHitPos) + end + end + + decalbul.Dir = -newdir + decalbul.Src = decalstartpos + decalbul.Callback = DisableOwnerDamage + decalbul.IgnoreEntity = bul.IgnoreEntity + + if shouldDisplayDebug() then + DLib.debugoverlay.Cross(decalbul.Src, 8, 10, debugsphere4, true) + DLib.debugoverlay.Cross(decalbul.Src + decalbul.Dir * decalbul.Distance, 8, 10, debugsphere5, true) + end + + if self.PenetrationCount <= 1 and IsValid(weapon) then + weapon:PCFTracer(self, pentraceres.HitPos or traceres.HitPos, true) + end + + local fx = EffectData() + fx:SetOrigin(bul.Src) + fx:SetNormal(bul.Dir) + + fx:SetMagnitude((bul.PenetrationCount + 1) * 1000) + fx:SetEntity(weapon) + + if IsValid(pentraceres.Entity) and pentraceres.Entity.EntIndex then + fx:SetScale(pentraceres.Entity:EntIndex()) + end + + fx:SetRadius(bul.Damage / 32) + TFA.Effects.Create("tfa_penetrate", fx) + + if cv_decalbul:GetBool() then + ply:FireBullets(decalbul) + end + + BallisticFirebullet(ply, bul, true) +end + +local RicochetChanceEnum = { + [MAT_GLASS] = 0, + [MAT_PLASTIC] = 0.01, + [MAT_DIRT] = 0.01, + [MAT_GRASS] = 0.01, + [MAT_SAND] = 0.01, + [MAT_CONCRETE] = 0.15, + [MAT_METAL] = 0.7, + [MAT_DEFAULT] = 0.5, + [MAT_FLESH] = 0.0 +} + +function SWEP.MainBullet:Ricochet(ply, traceres, dmginfo, weapon) + if ricochet_cvar and not ricochet_cvar:GetBool() then return end + maxpen = math.min(penetration_max_cvar and penetration_max_cvar:GetInt() - 1 or 1, weapon:GetStatL("Primary.MaxSurfacePenetrationCount", math.huge)) + if self.PenetrationCount > maxpen then return end + local ricochetchance = RicochetChanceEnum[traceres.MatType] or RicochetChanceEnum[MAT_DEFAULT] + local dir = traceres.HitPos - traceres.StartPos + dir:Normalize() + local dp = dir:Dot(traceres.HitNormal * -1) + ricochetchance = ricochetchance * weapon:GetAmmoRicochetMultiplier() + local riccbak = ricochetchance / 0.7 + local ricothreshold = 0.6 + ricochetchance = l_mathClamp(ricochetchance * ( 1 + l_mathClamp(1 - (dp + ricothreshold), 0, 1) ), 0, 1) + if dp <= ricochetchance and math.Rand(0, 1) < ricochetchance then + local ric = {} + ric.Ricochet = self.Ricochet + ric.Penetrate = self.Penetrate + ric.MakeDoor = self.MakeDoor + ric.HandleDoor = self.HandleDoor + ric.Damage = self.Damage * 0.5 + ric.Force = self.Force * 0.5 + ric.Num = 1 + ric.Spread = vector_origin + ric.Tracer = 0 + ric.Src = traceres.HitPos + ric.Dir = ((2 * traceres.HitNormal * dp) + traceres.Normal) + (VectorRand() * 0.02) + ric.PenetrationCount = self.PenetrationCount + 1 + self.PenetrationCount = self.PenetrationCount + 1 + + if TFA.GetRicochetEnabled() then + local fx = EffectData() + fx:SetOrigin(ric.Src) + fx:SetNormal(ric.Dir) + fx:SetMagnitude(riccbak) + TFA.Effects.Create("tfa_ricochet", fx) + end + + BallisticFirebullet(ply, ric, true) + + return true + end +end + +local defaultdoorhealth = 250 +local cv_doorres = GetConVar("sv_tfa_door_respawn") + +function SWEP.MainBullet:MakeDoor(ent, dmginfo) + local dir = dmginfo:GetDamageForce():GetNormalized() + local force = dir * math.max(math.sqrt(dmginfo:GetDamageForce():Length() / 1000), 1) * 1000 + local pos = ent:GetPos() + local ang = ent:GetAngles() + local mdl = ent:GetModel() + local ski = ent:GetSkin() + ent:SetNotSolid(true) + ent:SetNoDraw(true) + local prop = ents.Create("prop_physics") + prop:SetPos(pos + dir * 16) + prop:SetAngles(ang) + prop:SetModel(mdl) + prop:SetSkin(ski or 0) + prop:Spawn() + prop:SetVelocity(force) + prop:GetPhysicsObject():ApplyForceOffset(force, dmginfo:GetDamagePosition()) + prop:SetPhysicsAttacker(dmginfo:GetAttacker()) + prop:EmitSound("physics/wood/wood_furniture_break" .. tostring(math.random(1, 2)) .. ".wav", 110, math.random(90, 110)) + + if cv_doorres and cv_doorres:GetInt() ~= -1 then + timer.Create("TFA_DoorRespawner_" .. ent:EntIndex(), cv_doorres:GetFloat(), 1, function() + if IsValid(prop) then + prop:Remove() + end + + if IsValid(ent) then + ent.TFADoorHealth = defaultdoorhealth + ent:SetNotSolid(false) + ent:SetNoDraw(false) + end + end) + end +end + +local cv_doordestruction = GetConVar("sv_tfa_bullet_doordestruction") +local sv_tfa_bullet_doordestruction_keep = GetConVar("sv_tfa_bullet_doordestruction_keep") + +function SWEP.MainBullet:HandleDoor(ply, traceres, dmginfo, wep) + -- Don't do anything if door desstruction isn't enabled + if not cv_doordestruction:GetBool() then return end + local ent = traceres.Entity + if not IsValid(ent) then return end + if not IsValid(ply) then return end + if not ents.Create then return end + if not ply.SetName then return end + if ent.TFADoorUntouchable and ent.TFADoorUntouchable > CurTime() then return end + ent.TFADoorHealth = ent.TFADoorHealth or defaultdoorhealth + if ent:GetClass() ~= "func_door_rotating" and ent:GetClass() ~= "prop_door_rotating" then return end + local realDamage = dmginfo:GetDamage() * self.Num + ent.TFADoorHealth = l_mathClamp(ent.TFADoorHealth - realDamage, 0, defaultdoorhealth) + if ent.TFADoorHealth > 0 then return end + ply:EmitSound("ambient/materials/door_hit1.wav", 100, math.random(90, 110)) + + if not sv_tfa_bullet_doordestruction_keep:GetBool() and self.Damage * self.Num > 100 then + self:MakeDoor(ent, dmginfo) + ent.TFADoorUntouchable = CurTime() + 0.5 + + return + end + + ply.oldname = ply:GetName() + ply:SetName("bashingpl" .. ply:EntIndex()) + ent:Fire("unlock", "", .01) + ent:SetKeyValue("Speed", "500") + ent:SetKeyValue("Open Direction", "Both directions") + ent:SetKeyValue("opendir", "0") + ent:Fire("openawayfrom", "bashingpl" .. ply:EntIndex(), .01) + + timer.Simple(0.02, function() + if IsValid(ply) then + ply:SetName(ply.oldname) + end + end) + + timer.Simple(0.3, function() + if IsValid(ent) then + ent:SetKeyValue("Speed", "100") + end + end) + + timer.Simple(5, function() + if IsValid(ent) then + ent.TFADoorHealth = defaultdoorhealth + end + end) + + ent.TFADoorUntouchable = CurTime() + 5 +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/calc.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/calc.lua new file mode 100644 index 0000000..fdf898f --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/calc.lua @@ -0,0 +1,291 @@ +TFA.GUESS_NPC_WALKSPEED = 160 + +local function l_Lerp(t, a, b) return a + (b - a) * t end +local function l_mathMin(a, b) return (a < b) and a or b end +local function l_mathMax(a, b) return (a > b) and a or b end +local function l_ABS(a) return (a < 0) and -a or a end +local function l_mathClamp(t, a, b) + if a > b then return b end + + if t > b then + return b + end + + if t < a then + return a + end + + return t +end + +local function l_mathApproach(a, b, delta) + if a < b then + return l_mathMin(a + l_ABS(delta), b) + else + return l_mathMax(a - l_ABS(delta), b) + end +end + +local sprint_cv = GetConVar("sv_tfa_sprint_enabled") +local sv_tfa_weapon_weight = GetConVar("sv_tfa_weapon_weight") + +function SWEP:TFAFinishMove(ply, velocity, movedata) + local ft = FrameTime() + local self2 = self:GetTable() + local isply = ply:IsPlayer() + + if CLIENT then + self2.LastUnpredictedVelocity = velocity + end + + local speedmult = Lerp(self:GetIronSightsProgress(), sv_tfa_weapon_weight:GetBool() and self:GetStatL("RegularMoveSpeedMultiplier") or 1, self:GetStatL("AimingDownSightsSpeedMultiplier")) + + local jr_targ = math.min(math.abs(velocity.z) / 500, 1) + self:SetJumpRatio(l_mathApproach(self:GetJumpRatio(), jr_targ, (jr_targ - self:GetJumpRatio()) * ft * 20)) + self2.JumpRatio = self:GetJumpRatio() + self:SetCrouchingRatio(l_mathApproach(self:GetCrouchingRatio(), (self:IsOwnerCrouching()) and 1 or 0, ft / self2.ToCrouchTime)) + self2.CrouchingRatio = self:GetCrouchingRatio() + + local status = self2.GetStatus(self) + local oldsprinting, oldwalking = self:GetSprinting(), self:GetWalking() + local vellen = velocity:Length2D() + + --if TFA.Enum.ReloadStatus[status] then + -- self:SetSprinting(false) + --elseif sprint_cv:GetBool() and not self:GetStatL("AllowSprintAttack", false) and movedata then + if sprint_cv:GetBool() and not self:GetStatL("AllowSprintAttack", false) and movedata then + self:SetSprinting(vellen > ply:GetRunSpeed() * 0.6 * speedmult and movedata:KeyDown(IN_SPEED) and ply:OnGround()) + else + self:SetSprinting(false) + end + + self:SetWalking(vellen > ((isply and ply:GetWalkSpeed() or TFA.GUESS_NPC_WALKSPEED) * (sv_tfa_weapon_weight:GetBool() and self:GetStatL("RegularMoveSpeedMultiplier", 1) or 1) * .75) and ply:GetNW2Bool("TFA_IsWalking") and ply:OnGround() and not self:GetSprinting() and not self:GetCustomizing()) + + self2.walking_updated = oldwalking ~= self:GetWalking() + self2.sprinting_updated = oldsprinting ~= self:GetSprinting() + + if self:GetCustomizing() and (self2.GetIronSights(self) or self:GetSprinting() or not TFA.Enum.ReadyStatus[status]) then + self:ToggleCustomize() + end + + local spr = self:GetSprinting() + local walk = self:GetWalking() + + local sprt = spr and 1 or 0 + local walkt = walk and 1 or 0 + local adstransitionspeed = (spr or walk) and 7.5 or 12.5 + + self:SetSprintProgress(l_mathApproach(self:GetSprintProgress(), sprt, (sprt - self:GetSprintProgress()) * ft * adstransitionspeed)) + self:SetWalkProgress(l_mathApproach(self:GetWalkProgress(), walkt, (walkt - self:GetWalkProgress()) * ft * adstransitionspeed)) + + self:SetLastVelocity(vellen) +end + +local sp = game.SinglePlayer() +local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy") + +function SWEP:CalculateRatios() + local owent = self:GetOwner() + --if not IsValid(owent) or not owent:IsPlayer() then return end + if not IsValid(owent) then return end + + local self2 = self:GetTable() + + if self2.ratios_calc == nil then + self2.ratios_calc = true + end + + local ft = FrameTime() + local time = CurTime() + + if ft <= 0 then return end + + local is = self2.GetIronSights(self) + local spr = self2.GetSprinting(self) + local walk = self2.GetWalking(self) + + local ist = is and 1 or 0 + local sprt = spr and 1 or 0 + + local adstransitionspeed + if is then + adstransitionspeed = 12.5 / (self:GetStatL("IronSightTime") / 0.3) + elseif spr or walk then + adstransitionspeed = 7.5 + else + adstransitionspeed = 12.5 + end + adstransitionspeed = math.min(adstransitionspeed, 1000) + + if not owent:IsPlayer() then + self:TFAFinishMove(owent, owent:GetVelocity()) + end + + local lastshoottime = self2.GetLastGunFire(self, -1) + + if lastshoottime < 0 or time >= (lastshoottime + self2.GetStatL(self, "Primary.SpreadRecoveryDelay")) then + self:SetSpreadRatio(l_mathClamp(self:GetSpreadRatio() - self2.GetStatL(self, "Primary.SpreadRecovery") * ft, 1, self2.GetStatL(self, "Primary.SpreadMultiplierMax"))) + end + + self:SetIronSightsProgress(l_mathApproach(self:GetIronSightsProgress(), ist, (ist - self:GetIronSightsProgress()) * ft * adstransitionspeed)) + self:SetProceduralHolsterProgress(l_mathApproach(self:GetProceduralHolsterProgress(), sprt, (sprt - self:GetSprintProgress()) * ft * self2.ProceduralHolsterTime * 15)) + self:SetInspectingProgress(l_mathApproach(self:GetInspectingProgress(), self:GetCustomizing() and 1 or 0, ((self:GetCustomizing() and 1 or 0) - self:GetInspectingProgress()) * ft * 10)) + + if self:GetRecoilThink() then + if self:GetRecoilLoop() then + -- loop or after loop + + if self:GetRecoilLoopWait() < time then + self:SetRecoilOutProgress(l_mathMin(1, self:GetRecoilOutProgress() + ft / self2.Primary_TFA.RecoilLUT["out"].cooldown_speed)) + + if self:GetRecoilOutProgress() == 1 then + self:SetRecoilThink(false) + self:SetRecoilLoop(false) + self:SetRecoilLoopProgress(0) + self:SetRecoilInProgress(0) + self:SetRecoilOutProgress(0) + end + end + else + -- IN only + + if self:GetRecoilInWait() < time then + self:SetRecoilInProgress(l_mathMax(0, self:GetRecoilInProgress() - ft / self2.Primary_TFA.RecoilLUT["in"].cooldown_speed)) + + if self:GetRecoilInProgress() == 0 then + self:SetRecoilThink(false) + end + end + end + end + + if not sv_tfa_recoil_legacy:GetBool() then + ft = l_mathClamp(ft, 0, 1) + self:SetViewPunchBuild(l_mathMax(0, self:GetViewPunchBuild() - self:GetViewPunchBuild() * ft)) + local build = l_mathMax(0, 4.5 - self:GetViewPunchBuild()) + ft = ft * build * build + self:SetViewPunchP(self:GetViewPunchP() - self:GetViewPunchP() * ft) + self:SetViewPunchY(self:GetViewPunchY() - self:GetViewPunchY() * ft) + end + + self2.SpreadRatio = self:GetSpreadRatio() + self2.IronSightsProgress = self:GetIronSightsProgress() + self2.SprintProgress = self:GetSprintProgress() + self2.WalkProgress = self:GetWalkProgress() + self2.ProceduralHolsterProgress = self:GetProceduralHolsterProgress() + self2.InspectingProgress = self:GetInspectingProgress() + + if sp and CLIENT then + self2.Inspecting = self:GetCustomizing() --compatibility + end + + self2.CLIronSightsProgress = self:GetIronSightsProgress() --compatibility +end + +SWEP.Primary.IronRecoilMultiplier = 0.5 --Multiply recoil by this factor when we're in ironsights. This is proportional, not inversely. +SWEP.CrouchRecoilMultiplier = 0.65 --Multiply recoil by this factor when we're crouching. This is proportional, not inversely. +SWEP.JumpRecoilMultiplier = 1.3 --Multiply recoil by this factor when we're crouching. This is proportional, not inversely. +SWEP.WallRecoilMultiplier = 1.1 --Multiply recoil by this factor when we're changing state e.g. not completely ironsighted. This is proportional, not inversely. +SWEP.ChangeStateRecoilMultiplier = 1.3 --Multiply recoil by this factor when we're crouching. This is proportional, not inversely. +SWEP.CrouchAccuracyMultiplier = 0.5 --Less is more. Accuracy * 0.5 = Twice as accurate, Accuracy * 0.1 = Ten times as accurate +SWEP.ChangeStateAccuracyMultiplier = 1.5 --Less is more. A change of state is when we're in the progress of doing something, like crouching or ironsighting. Accuracy * 2 = Half as accurate. Accuracy * 5 = 1/5 as accurate +SWEP.JumpAccuracyMultiplier = 2 --Less is more. Accuracy * 2 = Half as accurate. Accuracy * 5 = 1/5 as accurate +SWEP.WalkAccuracyMultiplier = 1.35 --Less is more. Accuracy * 2 = Half as accurate. Accuracy * 5 = 1/5 as accurate +SWEP.ToCrouchTime = 0.25 + +local mult_cvar = GetConVar("sv_tfa_spread_multiplier") +local rec_cvar = GetConVar("sv_tfa_recoil_multiplier") +local dynacc_cvar = GetConVar("sv_tfa_dynamicaccuracy") +local ccon, crec + +SWEP.JumpRatio = 0 + +function SWEP:GetBaseSpread() + return self:GetStatL("Primary.Spread") or self:GetStatL("Primary.Accuracy") +end + +function SWEP:GetBaseRecoil() + return self:GetStatL("Primary.Recoil") +end + +function SWEP:CalculateConeRecoil() + local dynacc = false + local self2 = self:GetTable() + local isr = self:GetIronSightsProgress() + + if dynacc_cvar:GetBool() and (self2.GetStatL(self, "Primary.NumShots") <= 1) then + dynacc = true + end + + local isr_1 = l_mathClamp(isr * 2, 0, 1) + local isr_2 = l_mathClamp((isr - 0.5) * 2, 0, 1) + local acv = self2.GetBaseSpread(self) + local recv = self2.GetBaseRecoil(self) * 5 + + if dynacc then + ccon = l_Lerp(isr_2, l_Lerp(isr_1, acv, acv * self2.GetStatL(self, "ChangeStateAccuracyMultiplier")), self2.GetStatL(self, "Primary.IronAccuracy")) + crec = l_Lerp(isr_2, l_Lerp(isr_1, recv, recv * self2.GetStatL(self, "ChangeStateRecoilMultiplier")), recv * self2.GetStatL(self, "Primary.IronRecoilMultiplier")) + else + ccon = l_Lerp(isr, acv, self2.GetStatL(self, "Primary.IronAccuracy")) + crec = l_Lerp(isr, recv, recv * self2.GetStatL(self, "Primary.IronRecoilMultiplier")) + end + + local crc_1 = l_mathClamp(self:GetCrouchingRatio() * 2, 0, 1) + local crc_2 = l_mathClamp((self:GetCrouchingRatio() - 0.5) * 2, 0, 1) + + if dynacc then + ccon = l_Lerp(crc_2, l_Lerp(crc_1, ccon, ccon * self2.GetStatL(self, "ChangeStateAccuracyMultiplier")), ccon * self2.GetStatL(self, "CrouchAccuracyMultiplier")) + crec = l_Lerp(crc_2, l_Lerp(crc_1, crec, self2.GetStatL(self, "Primary.Recoil") * self2.GetStatL(self, "ChangeStateRecoilMultiplier")), crec * self2.GetStatL(self, "CrouchRecoilMultiplier")) + end + + local owner = self:GetOwner() + local isply = owner:IsPlayer() + local ovel + + if IsValid(owner) then + if owner:IsPlayer() then + ovel = self:GetLastVelocity() + else + ovel = owner:GetVelocity():Length2D() + end + else + ovel = 0 + end + + local vfc_1 = l_mathClamp(ovel / (isply and owner:GetWalkSpeed() or TFA.GUESS_NPC_WALKSPEED), 0, 2) + + if dynacc then + ccon = l_Lerp(vfc_1, ccon, ccon * self2.GetStatL(self, "WalkAccuracyMultiplier")) + crec = l_Lerp(vfc_1, crec, crec * self2.GetStatL(self, "WallRecoilMultiplier")) + end + + local jr = self:GetJumpRatio() + + if dynacc then + ccon = l_Lerp(jr, ccon, ccon * self2.GetStatL(self, "JumpAccuracyMultiplier")) + crec = l_Lerp(jr, crec, crec * self2.GetStatL(self, "JumpRecoilMultiplier")) + end + + ccon = ccon * self:GetSpreadRatio() + + ccon = ccon * mult_cvar:GetFloat() + crec = crec * rec_cvar:GetFloat() + + if not isply and IsValid(owner) then + local prof = owner:GetCurrentWeaponProficiency() + + if prof == WEAPON_PROFICIENCY_POOR then + ccon = ccon * 8 + elseif prof == WEAPON_PROFICIENCY_AVERAGE then + ccon = ccon * 5 + elseif prof == WEAPON_PROFICIENCY_GOOD then + ccon = ccon * 3 + elseif prof == WEAPON_PROFICIENCY_VERY_GOOD then + ccon = ccon * 2 + elseif prof == WEAPON_PROFICIENCY_PERFECT then + ccon = ccon * 1.5 + end + end + + return ccon, crec +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/effects.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/effects.lua new file mode 100644 index 0000000..47be598 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/effects.lua @@ -0,0 +1,352 @@ +local fx, sp = nil, game.SinglePlayer() +local shelltype + +function SWEP:PCFTracer(bul, hitpos, ovrride) + if bul.PCFTracer then + self:UpdateMuzzleAttachment() + local mzp = self:GetMuzzlePos() + if bul.PenetrationCount > 0 and not ovrride then return end --Taken care of with the pen effect + + if (CLIENT or game.SinglePlayer()) and self.Scoped and self:IsCurrentlyScoped() and self:IsFirstPerson() then + TFA.ParticleTracer(bul.PCFTracer, self:GetOwner():GetShootPos() - self:GetOwner():EyeAngles():Up() * 5, hitpos, false, 0, -1) + else + local vent = self + + if (CLIENT or game.SinglePlayer()) and self:IsFirstPerson() then + vent = self.OwnerViewModel + end + + if sp and not self:IsFirstPerson() then + TFA.ParticleTracer(bul.PCFTracer, self:GetOwner():GetShootPos() + self:GetOwner():GetAimVector() * 32, hitpos, false) + else + TFA.ParticleTracer(bul.PCFTracer, mzp.Pos, hitpos, false, vent, self.MuzzleAttachmentRaw or 1) + end + end + end +end + +function SWEP:EventShell() + if SERVER and self.processing_events and sp then return end + + if SERVER then + net.Start("tfaBaseShellSV", true) + net.WriteEntity(self) + + if self:GetOwner():IsPlayer() then + if sp then + net.Broadcast() + else + net.SendOmit(self:GetOwner()) + end + else + net.SendPVS(self:GetPos()) + end + + return + end + + self:MakeShellBridge() +end + +function SWEP:MakeShellBridge(ifp) + if ifp == false then return end + + if self.LuaShellEjectDelay > 0 then + self.LuaShellRequestTime = CurTime() + self.LuaShellEjectDelay / self:GetAnimationRate(ACT_VM_PRIMARYATTACK) + else + self:MakeShell() + end +end + +SWEP.ShellEffectOverride = nil -- ??? +SWEP.ShellEjectionQueue = 0 + +function SWEP:GetShellAttachmentID(ent, isVM) + local raw = self:GetStatL("ShellAttachmentRaw") + local israw = false + local attid + + if raw and ent:GetAttachment(raw) then + attid = raw + israw = true + else + attid = ent:LookupAttachment(self:GetStatL("ShellAttachment")) + end + + if self:GetStatL("IsAkimbo") and not israw then + return 3 + self:GetAnimCycle() + end + + if attid and attid <= 0 then attid = 2 end + + attid = math.Clamp(attid and attid or 2, 1, 127) + + return attid +end + +function SWEP:GetShellEjectPosition(ent, isVM) + local attid = self:GetShellAttachmentID(ent, isVM) + + local angpos = ent:GetAttachment(attid) + + if angpos then + return angpos.Pos, angpos.Ang, attid + end +end + +function SWEP:MakeShell(eject_now) + if not self:IsValid() then return end -- what + if self.current_event_iftp == false then return end + + local retVal = hook.Run("TFA_MakeShell", self) + + if retVal ~= nil then + return retVal + end + + if self:GetStatL("ShellEffectOverride") then + shelltype = self:GetStatL("ShellEffectOverride") + elseif TFA.GetLegacyShellsEnabled() then + shelltype = "tfa_shell_legacy" + else + shelltype = "tfa_shell" + end + + local ent = self + local isVM = false + + if self:IsFirstPerson() then + if not eject_now and CLIENT then + self.ShellEjectionQueue = self.ShellEjectionQueue + 1 + return + end + + ent = self.OwnerViewModel or self + isVM = ent == self.OwnerViewModel + end + + self:EjectionSmoke(true) + + if not isstring(shelltype) or shelltype == "" then return end -- allows to disable shells by setting override to "" - will shut up all rp fags + + if not IsValid(ent) then return end + local pos, ang, attid = self:GetShellEjectPosition(ent, isVM) + + if not pos then return end + + fx = EffectData() + fx:SetEntity(self) + fx:SetAttachment(attid) + fx:SetMagnitude(1) + fx:SetScale(1) + fx:SetOrigin(pos) + fx:SetNormal(ang:Forward()) + TFA.Effects.Create(shelltype, fx) +end + +--[[ +Function Name: CleanParticles +Syntax: self:CleanParticles(). +Returns: Nothing. +Notes: Cleans up particles. +Purpose: FX +]] +-- +function SWEP:CleanParticles() + if not IsValid(self) then return end + + if self.StopParticles then + self:StopParticles() + end + + if self.StopParticleEmission then + self:StopParticleEmission() + end + + if not self:VMIV() then return end + local vm = self.OwnerViewModel + + if IsValid(vm) then + if vm.StopParticles then + vm:StopParticles() + end + + if vm.StopParticleEmission then + vm:StopParticleEmission() + end + end +end + +--[[ +Function Name: EjectionSmoke +Syntax: self:EjectionSmoke(). +Returns: Nothing. +Notes: Puff of smoke on shell attachment. +Purpose: FX +]] +-- +function SWEP:EjectionSmoke(ovrr) + local retVal = hook.Run("TFA_EjectionSmoke",self) + if retVal ~= nil then + return retVal + end + if TFA.GetEJSmokeEnabled() and (self:GetStatL("EjectionSmokeEnabled") or ovrr) then + local vm = self:IsFirstPerson() and self.OwnerViewModel or self + + if IsValid(vm) then + local att = vm:LookupAttachment(self:GetStatL("ShellAttachment")) + + if not att or att <= 0 then + att = 2 + end + + local oldatt = att + att = self:GetStatL("ShellAttachmentRaw", att) + local angpos = vm:GetAttachment(att) + + if not angpos then + att = oldatt + angpos = vm:GetAttachment(att) + end + + if angpos then + fx = EffectData() + fx:SetEntity(self) + fx:SetOrigin(angpos.Pos) + fx:SetAttachment(att) + fx:SetNormal(angpos.Ang:Forward()) + TFA.Effects.Create("tfa_shelleject_smoke", fx) + end + end + end +end + +--[[ +Function Name: ShootEffectsCustom +Syntax: self:ShootEffectsCustom(). +Returns: Nothing. +Notes: Calls the proper muzzleflash, muzzle smoke, muzzle light code. +Purpose: FX +]] +-- +function SWEP:MuzzleSmoke(spv) + local retVal = hook.Run("TFA_MuzzleSmoke",self) + if retVal ~= nil then + return retVal + end + if self.SmokeParticle == nil then + self.SmokeParticle = self.SmokeParticles[self.DefaultHoldType or self.HoldType] + end + + if self:GetStatL("SmokeParticle") and self:GetStatL("SmokeParticle") ~= "" then + self:UpdateMuzzleAttachment() + local att = self:GetMuzzleAttachment() + fx = EffectData() + fx:SetOrigin(self:GetOwner():GetShootPos()) + fx:SetNormal(self:GetOwner():EyeAngles():Forward()) + fx:SetEntity(self) + fx:SetAttachment(att) + TFA.Effects.Create("tfa_muzzlesmoke", fx) + end +end + +function SWEP:MuzzleFlashCustom(spv) + local retVal = hook.Run("TFA_MuzzleFlash",self) + if retVal ~= nil then + return retVal + end + local att = self:GetMuzzleAttachment() + fx = EffectData() + fx:SetOrigin(self:GetOwner():GetShootPos()) + fx:SetNormal(self:GetOwner():EyeAngles():Forward()) + fx:SetEntity(self) + fx:SetAttachment(att) + local mzsil = self:GetStatL("MuzzleFlashEffectSilenced") + + if (self:GetSilenced() and mzsil and mzsil ~= "") then + TFA.Effects.Create(mzsil, fx) + else + TFA.Effects.Create(self:GetStatL("MuzzleFlashEffect", self.MuzzleFlashEffect or ""), fx) + end +end + +function SWEP:ShootEffectsCustom(ifp) + if self.DoMuzzleFlash ~= nil then + self.MuzzleFlashEnabled = self.DoMuzzleFlash + self.DoMuzzleFlash = nil + end + + if not self.MuzzleFlashEnabled then return end + if self:IsFirstPerson() and not self:VMIV() then return end + if not self:GetOwner().GetShootPos then return end + ifp = ifp or IsFirstTimePredicted() + + if (SERVER and sp and self.ParticleMuzzleFlash) or (SERVER and not sp) then + net.Start("tfa_base_muzzle_mp", true) + net.WriteEntity(self) + + if sp or not self:GetOwner():IsPlayer() then + net.SendPVS(self:GetPos()) + else + net.SendOmit(self:GetOwner()) + end + + return + end + + if (CLIENT and ifp and not sp) or (sp and SERVER) then + self:UpdateMuzzleAttachment() + self:MuzzleFlashCustom(sp) + self:MuzzleSmoke(sp) + end +end + +--[[ +Function Name: CanDustEffect +Syntax: self:CanDustEffect( concise material name ). +Returns: True/False +Notes: Used for the impact effect. Should be used with GetMaterialConcise. +Purpose: Utility +]] +-- +local DustEffects = { + [MAT_DIRT] = true, + [MAT_CONCRETE] = true, + [MAT_PLASTIC] = true, + [MAT_WOOD] = true +} +function SWEP:CanDustEffect(matv) + if DustEffects[matv] then return true end + + return false +end + +--[[ +Function Name: CanSparkEffect +Syntax: self:CanSparkEffect( concise material name ). +Returns: True/False +Notes: Used for the impact effect. Should be used with GetMaterialConcise. +Purpose: Utility +]] +-- +local SparkEffects = { + [MAT_METAL] = true, + [MAT_GRATE] = true, + [MAT_VENT] = true +} +function SWEP:CanSparkEffect(matv) + if SparkEffects[matv] then return true end + + return false +end + +-- Returns muzzle attachment position for HL2 tracers +function SWEP:GetTracerOrigin(...) + local att = self:GetMuzzleAttachment() + + local attpos = (self:IsFirstPerson() and self.OwnerViewModel or self):GetAttachment(att) + + if attpos and attpos.Pos then + return attpos.Pos + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/events.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/events.lua new file mode 100644 index 0000000..35ad8ce --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/events.lua @@ -0,0 +1,605 @@ +local lshift = bit.lshift +local band = bit.band +local bor = bit.bor + +local sp = game.SinglePlayer() +local l_CT = CurTime + +local is, spr, wlk, cst + +--[[ +Function Name: ResetEvents +Syntax: self:ResetEvents() +Returns: Nothing. +Purpose: Cleans up events table. +]]-- +function SWEP:ResetEvents() + self:SetEventStatus1(0x00000000) + self:SetEventStatus2(0x00000000) + self:SetEventStatus3(0x00000000) + self:SetEventStatus4(0x00000000) + self:SetEventStatus5(0x00000000) + self:SetEventStatus6(0x00000000) + self:SetEventStatus7(0x00000000) + self:SetEventStatus8(0x00000000) + + self:SetEventTimer(l_CT()) + -- self:SetFirstDeployEvent(false) + + if self.EventTable then + for _, eventtable in pairs(self.EventTable) do + for i = 1, #eventtable do + eventtable[i].called = false + end + end + end + + if self.event_table_overflow then + local editcts = self.EventTableEdict + + if editcts[0] then + editcts[0].called = false + + for i = 1, #editcts do + editcts[i].called = false + end + end + end + + if sp then + self:CallOnClient("ResetEvents", "") + end +end + +function SWEP:GetEventPlayed(event_slot) + if self.event_table_overflow then + return assert(self.EventTableEdict[event_slot], string.format("Unknown event %d", event_slot)).called + end + + local inner_index = event_slot % 32 + local outer_index = (event_slot - inner_index) / 32 + 1 + local lindex = lshift(1, inner_index) + return band(self.get_event_status_lut[outer_index](self), lindex) ~= 0, inner_index, outer_index, lindex +end + +function SWEP:SetEventPlayed(event_slot) + if self.event_table_overflow then + assert(self.EventTableEdict[event_slot], string.format("Unknown event %d", event_slot)).called = true + return + end + + local inner_index = event_slot % 32 + local outer_index = (event_slot - inner_index) / 32 + 1 + local lindex = lshift(1, inner_index) + + self.set_event_status_lut[outer_index](self, bor(self.get_event_status_lut[outer_index](self), lindex)) + return inner_index, outer_index, lindex +end + +--[[ +Function Name: ProcessEvents +Syntax: self:ProcessEvents(). +Returns: Nothing. +Notes: Critical for the event table to function. +Purpose: Main SWEP function +]]-- + +SWEP._EventSlotCount = 0 +SWEP.EventTableEdict = {} + +function SWEP:DispatchLuaEvent(arg) + if not self.event_table_built then + self:RebuildEventEdictTable() + end + + local fn = assert(assert(self.EventTableEdict[tonumber(arg)], "No such event with edict " .. arg).value, "Event is missing a function to call") + assert(isfunction(fn), "Event " .. arg .. " is not a Lua event") + fn(self, self:VMIV(), true) +end + +function SWEP:DispatchBodygroupEvent(arg) + if not self.event_table_built then + self:RebuildEventEdictTable() + end + + local event = assert(self.EventTableEdict[tonumber(arg)], "No such event with edict " .. arg) + assert(isstring(event.name), "Event " .. arg .. " is missing bodygroup name to set") + assert(isstring(event.value), "Event " .. arg .. " is missing bodygroup value to set") + + if event.view then + self.ViewModelBodygroups[event.name] = event.value + end + + if event.world then + self.WorldModelBodygroups[event.name] = event.value + end +end + +local isstring = isstring + +local function eventtablesorter(a, b) + local sa, sb = isstring(a), isstring(b) + + if sa and not sb or not sa and sb then + if sa then + return false + end + + return true + end + + return a < b +end + +function SWEP:RebuildEventEdictTable() + local self2 = self:GetTable() + local slot = 0 + + for i = #self2.EventTableEdict, 0, -1 do + self2.EventTableEdict[i] = nil + end + + self:ResetEvents() + + local eventtable = self2.EventTable + eventtable.BaseClass = nil + + local keys = table.GetKeys(eventtable) + table.sort(keys, eventtablesorter) + + for _, key in ipairs(keys) do + local value = eventtable[key] + + if istable(value) then + for _, event in SortedPairs(value) do + if istable(event) then + event.slot = slot + slot = slot + 1 + + if not event.autodetect then + if event.type == "lua" then + if event.server == nil then + event.server = true + end + elseif event.type == "snd" or event.type == "sound" then + if event.server == nil then + event.server = false + end + elseif event.type == "bg" or event.type == "bodygroup" then + if event.server == nil then event.server = true end + if event.view == nil then event.view = true end + if event.world == nil then event.world = true end + end + + if event.client == nil then + event.client = true + end + + event.autodetect = true + end + + event.called = false + + if slot > 256 and not self.event_table_warning then + ErrorNoHalt("[TFA Base] Weapon " .. self:GetClass() .. " got too many events! 256 is maximum! Event table would NOT be properly predicted this time!\n") + self.event_table_warning = true + end + + self2.EventTableEdict[event.slot] = event + end + end + end + end + + self.event_table_overflow = slot > 256 + self._built_event_debug_string_fn = nil + + self._EventSlotCount = math.ceil(slot / 32) + self._EventSlotNum = slot - 1 + self.event_table_built = true +end + +function SWEP:ProcessEvents(firstprediction) + local viewmodel = self:VMIVNPC() + if not viewmodel then return end + + if not self.event_table_built then + self:RebuildEventEdictTable() + end + + if sp and CLIENT then return end + if sp and SERVER then return self:ProcessEventsSP() end + + local ply = self:GetOwner() + local isplayer = ply:IsPlayer() + + local evtbl = self.EventTable[self:GetLastActivity() or -1] or self.EventTable[viewmodel:GetSequenceName(viewmodel:GetSequence())] + if not evtbl then return end + + local curtime = l_CT() + local eventtimer = self:GetEventTimer() + local is_local = CLIENT and ply == LocalPlayer() + local animrate = self:GetAnimationRate(self:GetLastActivity() or -1) + + self.current_event_iftp = firstprediction + self.processing_events = true + + for i = 1, #evtbl do + local event = evtbl[i] + if self:GetEventPlayed(event.slot) or curtime < eventtimer + event.time / animrate then goto CONTINUE end + self:SetEventPlayed(event.slot) + event.called = true + + if not event.autodetect then + if event.type == "lua" then + if event.server == nil then + event.server = true + end + elseif event.type == "snd" or event.type == "sound" then + if event.server == nil then + event.server = false + end + elseif event.type == "bg" or event.type == "bodygroup" then + if event.server == nil then event.server = true end + if event.view == nil then event.view = true end + if event.world == nil then event.world = true end + end + + if event.client == nil then + event.client = true + end + + event.autodetect = true + end + + if event.type == "lua" then + if ((event.client and CLIENT and (not event.client_predictedonly or is_local)) or (event.server and SERVER)) and event.value then + event.value(self, viewmodel, firstprediction) + end + elseif event.type == "snd" or event.type == "sound" then + if SERVER then + if event.client then + if not isplayer and player.GetCount() ~= 0 then + net.Start("tfaSoundEvent", true) + net.WriteEntity(self) + net.WriteString(event.value or "") + net.WriteBool(event.shouldpause or false) + net.SendPVS(self:GetPos()) + elseif isplayer then + net.Start("tfaSoundEvent", true) + net.WriteEntity(self) + net.WriteString(event.value or "") + net.WriteBool(event.shouldpause or false) + net.SendOmit(ply) + end + elseif event.server and event.value and event.value ~= "" then + self:EmitSound(event.value) + end + elseif event.client and is_local and not sp and event.value and event.value ~= "" then + if firstprediction or firstprediction == nil then + if event.time <= 0.01 then + self:EmitSoundSafe(event.value) + else + self:EmitSound(event.value) + end + end + end + elseif event.type == "bg" or event.type == "bodygroup" then + if ((event.client and CLIENT and (not event.client_predictedonly or is_local)) or + (event.server and SERVER)) and (event.name and event.value and event.value ~= "") then + + if event.view then + self.ViewModelBodygroups[event.name] = event.value + end + + if event.world then + self.WorldModelBodygroups[event.name] = event.value + end + end + end + + ::CONTINUE:: + end + + self.processing_events = false + self.current_event_iftp = nil +end + +-- This function is exclusively targeting singleplayer +function SWEP:ProcessEventsSP(firstprediction) + local viewmodel = self:VMIVNPC() + if not viewmodel then return end + + local evtbl = self.EventTable[self:GetLastActivity() or -1] or self.EventTable[viewmodel:GetSequenceName(viewmodel:GetSequence())] + if not evtbl then return end + + local curtime = l_CT() + local eventtimer = self:GetEventTimer() + local is_local = self:GetOwner() == Entity(1) + local animrate = self:GetAnimationRate(self:GetLastActivity() or -1) + + self.processing_events = true + + for i = 1, #evtbl do + local event = evtbl[i] + if self:GetEventPlayed(event.slot) or curtime < eventtimer + event.time / animrate then goto CONTINUE end + self:SetEventPlayed(event.slot) + event.called = true + + if not event.autodetect then + if event.type == "lua" then + if event.server == nil then + event.server = true + end + elseif event.type == "snd" or event.type == "sound" then + if event.server == nil then + event.server = false + end + elseif event.type == "bg" or event.type == "bodygroup" then + if event.server == nil then event.server = true end + if event.view == nil then event.view = true end + if event.world == nil then event.world = true end + end + + if event.client == nil then + event.client = true + end + + event.autodetect = true + end + + if event.type == "lua" then + if event.value then + if event.server then + event.value(self, viewmodel, true) + end + + if event.client and (not event.client_predictedonly or is_local) then + self:CallOnClient("DispatchLuaEvent", tostring(event.slot)) + end + end + elseif event.type == "snd" or event.type == "sound" then + if event.client then + net.Start("tfaSoundEvent", true) + net.WriteEntity(self) + net.WriteString(event.value or "") + net.WriteBool(event.shouldpause or false) + net.Broadcast() + elseif event.server and event.value and event.value ~= "" then + self:EmitSound(event.value) + end + elseif event.type == "bg" or event.type == "bodygroup" then + if event.name and event.value and event.value ~= "" then + if event.server then + if event.view then + self.ViewModelBodygroups[event.name] = event.value + end + + if event.world then + self.WorldModelBodygroups[event.name] = event.value + end + end + + if event.client and (not event.client_predictedonly or is_local) then + self:CallOnClient("DispatchBodygroupEvent", tostring(event.slot)) + end + end + end + + ::CONTINUE:: + end + + self.processing_events = false +end + +function SWEP:EmitSoundSafe(snd) + timer.Simple(0, function() + if IsValid(self) and snd then self:EmitSound(snd) end + end) +end + +local ct, stat, statend, finalstat, waittime, lact + +function SWEP:ProcessStatus() + local self2 = self:GetTable() + + is = self2.GetIronSightsRaw(self) + spr = self2.GetSprinting(self) + wlk = self2.GetWalking(self) + cst = self2.GetCustomizing(self) + + local ply = self:GetOwner() + local isplayer = ply:IsPlayer() + + if stat == TFA.Enum.STATUS_FIDGET and is then + self:SetStatusEnd(0) + + self2.Idle_Mode_Old = self2.Idle_Mode + self2.Idle_Mode = TFA.Enum.IDLE_BOTH + self2.ClearStatCache(self, "Idle_Mode") + self2.ChooseIdleAnim(self) + + if sp then + self:CallOnClient("ChooseIdleAnim", "") + end + + self2.Idle_Mode = self2.Idle_Mode_Old + self2.ClearStatCache(self, "Idle_Mode") + self2.Idle_Mode_Old = nil + statend = -1 + end + + is = self:GetIronSights() + stat = self:GetStatus() + statend = self:GetStatusEnd() + + ct = l_CT() + + if stat ~= TFA.Enum.STATUS_IDLE and ct > statend then + self:SetFirstDeployEvent(false) + finalstat = TFA.Enum.STATUS_IDLE + + --Holstering + if stat == TFA.Enum.STATUS_HOLSTER then + finalstat = TFA.Enum.STATUS_HOLSTER_READY + self:SetStatusEnd(ct) + elseif stat == TFA.Enum.STATUS_HOLSTER_READY then + self2.FinishHolster(self) + finalstat = TFA.Enum.STATUS_HOLSTER_FINAL + self:SetStatusEnd(ct + 0.6) + elseif stat == TFA.Enum.STATUS_RELOADING_LOOP_START_EMPTY then + --Shotgun Reloading from empty + if not self2.IsJammed(self) then + self2.InsertPrimaryAmmo(self, self2.GetStatL(self, "LoopedReloadInsertAmount", 1)) + end + + if self2.Ammo1(self) <= 0 or self:Clip1() >= self2.GetPrimaryClipSize(self) or self:GetReloadLoopCancel() then + finalstat = TFA.Enum.STATUS_RELOADING_LOOP_END + local _, tanim, ttype = self2.ChooseShotgunPumpAnim(self) + self:SetStatusEnd(ct + self2.GetActivityLength(self, tanim, false, ttype)) + self:SetReloadLoopCancel(false) + + if not self:GetReloadLoopCancel() then + self:SetJammed(false) + end + else + lact = self:GetLastActivity() + waittime = self2.GetActivityLength(self, lact, false) - self2.GetActivityLength(self, lact, true) + + if waittime > 0.01 then + finalstat = TFA.Enum.STATUS_RELOADING_WAIT + self:SetStatusEnd(ct + waittime) + else + finalstat = self2.LoadShell(self) + end + + self:SetJammed(false) + --finalstat = self:LoadShell() + --self:SetStatusEnd( self:GetNextPrimaryFire() ) + end + elseif stat == TFA.Enum.STATUS_RELOADING_LOOP_START then + --Shotgun Reloading + finalstat = self2.LoadShell(self) + elseif stat == TFA.Enum.STATUS_RELOADING_LOOP then + self2.InsertPrimaryAmmo(self, self2.GetStatL(self, "LoopedReloadInsertAmount", 1)) + lact = self:GetLastActivity() + + if self2.GetActivityLength(self, lact, true) < self2.GetActivityLength(self, lact, false) - 0.01 then + local sht = self2.GetStatL(self, "LoopedReloadInsertTime") + + if sht then + sht = sht / self2.GetAnimationRate(self, ACT_VM_RELOAD) + end + + waittime = (sht or self2.GetActivityLength(self, lact, false)) - self2.GetActivityLength(self, lact, true) + else + waittime = 0 + end + + if waittime > 0.01 then + finalstat = TFA.Enum.STATUS_RELOADING_WAIT + self:SetStatusEnd(ct + waittime) + else + if self2.Ammo1(self) <= 0 or self:Clip1() >= self:GetPrimaryClipSize() or self:GetReloadLoopCancel() then + finalstat = TFA.Enum.STATUS_RELOADING_LOOP_END + local _, tanim, ttype = self2.ChooseShotgunPumpAnim(self) + self:SetStatusEnd(ct + self2.GetActivityLength(self, tanim, false, ttype)) + self:SetReloadLoopCancel(false) + else + finalstat = self2.LoadShell(self) + end + end + elseif stat == TFA.Enum.STATUS_RELOADING then + self2.CompleteReload(self) + lact = self:GetLastActivity() + waittime = self2.GetActivityLength(self, lact, false) - self2.GetActivityLength(self, lact, true) + + if waittime > 0.01 then + finalstat = TFA.Enum.STATUS_RELOADING_WAIT + self:SetStatusEnd(ct + waittime) + end + elseif stat == TFA.Enum.STATUS_SILENCER_TOGGLE then + --self:SetStatusEnd( self:GetNextPrimaryFire() ) + self:SetSilenced(not self:GetSilenced()) + self2.Silenced = self:GetSilenced() + elseif stat == TFA.Enum.STATUS_RELOADING_WAIT and self:GetStatL("LoopedReload") then + if self2.Ammo1(self) <= 0 or self:Clip1() >= self:GetPrimaryClipSize() or self:GetReloadLoopCancel() then + finalstat = TFA.Enum.STATUS_RELOADING_LOOP_END + local _, tanim, ttype = self2.ChooseShotgunPumpAnim(self) + self:SetStatusEnd(ct + self2.GetActivityLength(self, tanim, false, ttype)) + --self:SetReloadLoopCancel( false ) + else + finalstat = self2.LoadShell(self) + end + elseif stat == TFA.Enum.STATUS_RELOADING_LOOP_END and self:GetStatL("LoopedReload") then + self:SetReloadLoopCancel(false) + elseif self2.GetStatL(self, "PumpAction") and stat == TFA.Enum.STATUS_PUMP then + self:SetReloadLoopCancel(false) + elseif stat == TFA.Enum.STATUS_SHOOTING and self2.GetStatL(self, "PumpAction") then + if self:Clip1() == 0 and self2.GetStatL(self, "PumpAction").value_empty then + --finalstat = TFA.Enum.STATUS_PUMP_READY + self:SetReloadLoopCancel(true) + elseif (self2.GetStatL(self, "Primary.ClipSize") < 0 or self:Clip1() > 0) and self2.GetStatL(self, "PumpAction").value then + --finalstat = TFA.Enum.STATUS_PUMP_READY + self:SetReloadLoopCancel(true) + end + end + + --self:SetStatusEnd( math.huge ) + self:SetStatus(finalstat) + + local sightsMode = self2.GetStatL(self, "Sights_Mode") + local sprintMode = self2.GetStatL(self, "Sprint_Mode") + local walkMode = self2.GetStatL(self, "Walk_Mode") + local customizeMode = self2.GetStatL(self, "Customize_Mode") + + local smi = sightsMode ~= TFA.Enum.LOCOMOTION_LUA + local spi = sprintMode ~= TFA.Enum.LOCOMOTION_LUA + local wmi = walkMode ~= TFA.Enum.LOCOMOTION_LUA + local cmi = customizeMode ~= TFA.Enum.LOCOMOTION_LUA + + if + not TFA.Enum.ReadyStatus[stat] and + stat ~= TFA.Enum.STATUS_SHOOTING and + stat ~= TFA.Enum.STATUS_PUMP and + finalstat == TFA.Enum.STATUS_IDLE and + ((smi or spi) or (cst and cmi)) + then + is = self2.GetIronSights(self, true) + + if (is and smi) or (spr and spi) or (wlk and wmi) or (cst and cmi) then + local success, _ = self2.Locomote(self, is and smi, is, spr and spi, spr, wlk and wmi, wlk, cst and cmi, cst) + + if success == false then + self:SetNextIdleAnim(-1) + else + self:SetNextIdleAnim(math.max(self:GetNextIdleAnim(), ct + 0.1)) + end + end + end + + self2.LastBoltShoot = nil + + if self:GetBurstCount() > 0 then + if finalstat ~= TFA.Enum.STATUS_SHOOTING and finalstat ~= TFA.Enum.STATUS_IDLE then + self:SetBurstCount(0) + elseif self:GetBurstCount() < self:GetMaxBurst() and self:Clip1() > 0 then + self:PrimaryAttack() + else + self:SetBurstCount(0) + self:SetNextPrimaryFire(self2.GetNextCorrectedPrimaryFire(self, self2.GetBurstDelay(self))) + end + end + end + + --if stat == TFA.Enum.STATUS_IDLE and self:GetReloadLoopCancel() and (self2.GetStatL(self, "AllowSprintAttack") or self:GetSprintProgress() < 0.1) then + if stat == TFA.Enum.STATUS_IDLE and self:GetReloadLoopCancel() then + if self2.GetStatL(self, "PumpAction") then + if ct > self:GetNextPrimaryFire() and not self:KeyDown(IN_ATTACK) then + self2.DoPump(self) + end + else + self:SetReloadLoopCancel(false) + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/nzombies.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/nzombies.lua new file mode 100644 index 0000000..26e8e62 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/nzombies.lua @@ -0,0 +1,95 @@ +SWEP.OldPaP = false +SWEP.OldSpCola = false +SWEP.SpeedColaFactor = 2 --Amount to speed up by when u get dat speed cola +SWEP.SpeedColaActivities = { + [ACT_VM_DRAW] = true, + [ACT_VM_DRAW_EMPTY] = true, + [ACT_VM_DRAW_SILENCED] = true, + [ACT_VM_DRAW_DEPLOYED or 0] = true, + [ACT_VM_RELOAD] = true, + [ACT_VM_RELOAD_EMPTY] = true, + [ACT_VM_RELOAD_SILENCED] = true, + [ACT_VM_HOLSTER] = true, + [ACT_VM_HOLSTER_EMPTY] = true, + [ACT_VM_HOLSTER_SILENCED] = true, + [ACT_SHOTGUN_RELOAD_START] = true, + [ACT_SHOTGUN_RELOAD_FINISH] = true +} +SWEP.DTapActivities = { + [ACT_VM_PRIMARYATTACK] = true, + [ACT_VM_PRIMARYATTACK_EMPTY] = true, + [ACT_VM_PRIMARYATTACK_SILENCED] = true, + [ACT_VM_PRIMARYATTACK_1] = true, + [ACT_VM_SECONDARYATTACK] = true, + [ACT_VM_HITCENTER] = true, + [ACT_SHOTGUN_PUMP] = true +} +SWEP.DTapSpeed = 1 / 0.8 +SWEP.DTap2Speed = 1 / 0.8 + +local nzombies + +local count, upperclamp + +function SWEP:NZMaxAmmo() + if nzombies == nil then + nzombies = engine.ActiveGamemode() == "nzombies" + end + local at = self:GetPrimaryAmmoType() + local at2 = self.GetSecondaryAmmoType and self:GetSecondaryAmmoType() or self.Secondary_TFA.Ammo + + if IsValid(self:GetOwner()) then + if self:GetStatL("Primary.ClipSize") <= 0 then + count = math.Clamp(10, 300 / (self:GetStatL("Primary.Damage") / 30), 10, 300) + if self.Primary_TFA.NZMaxAmmo and self.Primary_TFA.NZMaxAmmo > 0 then + count = self.Primary_TFA.NZMaxAmmo + if self:GetPaP() then + count = count * 5 / 3 + end + end + self:GetOwner():SetAmmo(count, at) + else + upperclamp = self:GetPaP() and 600 or 300 + count = math.Clamp(math.abs(self:GetStatL("Primary.ClipSize")) * 10, 10, upperclamp) + count = count + self:GetStatL("Primary.ClipSize") - self:Clip1() + if self.Primary_TFA.NZMaxAmmo and self.Primary_TFA.NZMaxAmmo > 0 then + count = self.Primary_TFA.NZMaxAmmo + if self:GetPaP() then + count = count * 5 / 3 + end + end + self:GetOwner():SetAmmo(count, at) + end + if self:GetStatL("Secondary.ClipSize") > 0 or self:GetSecondaryAmmoType() >= 0 then + if self:GetStatL("Secondary.ClipSize") <= 0 then + count = math.ceil( math.Clamp(10, 300 / math.pow( ( self:GetStatL("Secondary.Damage") or 100 ) / 30, 2 ), 10, 300) / 5 ) * 5 + if self.Secondary_TFA.NZMaxAmmo and self.Secondary_TFA.NZMaxAmmo > 0 then + count = self.Secondary_TFA.NZMaxAmmo + if self:GetPaP() then + count = count * 5 / 3 + end + end + self:GetOwner():SetAmmo(count, at2) + else + upperclamp = self:GetPaP() and 600 or 300 + count = math.Clamp(math.abs(self:GetStatL("Secondary.ClipSize")) * 10, 10, upperclamp) + count = count + self:GetStatL("Secondary.ClipSize") - self:Clip2() + if self.Secondary_TFA.NZMaxAmmo and self.Secondary_TFA.NZMaxAmmo > 0 then + count = self.Secondary_TFA.NZMaxAmmo + if self:GetPaP() then + count = count * 5 / 3 + end + end + self:GetOwner():SetAmmo(count, at2) + end + end + end +end + +function SWEP:GetPaP() + return ( self.HasNZModifier and self:HasNZModifier("pap") ) or self.pap or false +end + +function SWEP:IsPaP() + return self:GetPaP() +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/skins.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/skins.lua new file mode 100644 index 0000000..8078379 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/skins.lua @@ -0,0 +1,40 @@ +SWEP.MaterialTable = {} +SWEP.MaterialTable_V = {} +SWEP.MaterialTable_W = {} + +function SWEP:InitializeMaterialTable() + if not self.HasSetMaterialMeta then + setmetatable(self.MaterialTable_V, { + ["__index"] = function(t,k) return self:GetStatL("MaterialTable")[k] end + }) + + setmetatable(self.MaterialTable_W, { + ["__index"] = function(t,k) return self:GetStatL("MaterialTable")[k] end + }) + + self.HasSetMaterialMeta = true + end +end + +--if both nil then we can just clear it all +function SWEP:ClearMaterialCache(view, world) + if view == nil and world == nil then + self.MaterialCached_V = nil + self.MaterialCached_W = nil + self.MaterialCached = nil + self.SCKMaterialCached_V = nil + self.SCKMaterialCached_W = nil + else + if view then + self.MaterialCached_V = nil + self.SCKMaterialCached_V = nil + end + + if world then + self.MaterialCached_W = nil + self.SCKMaterialCached_W = nil + end + end +end + +if SERVER then TFA.SWEP_LOAD_COMPLETE = true end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/stat.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/stat.lua new file mode 100644 index 0000000..7d80b87 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/stat.lua @@ -0,0 +1,386 @@ +local tableCopy = table.Copy + +function SWEP:GetStatRecursive(srctbl, stbl, ...) + stbl = tableCopy(stbl) + + for _ = 1, #stbl do + if #stbl > 1 then + if srctbl[stbl[1]] then + srctbl = srctbl[stbl[1]] + table.remove(stbl, 1) + else + return true, ... + end + end + end + + local val = srctbl[stbl[1]] + + if val == nil then + return true, ... + end + + if istable(val) and val.functionTable then + local currentStat, isFinal, nocache, nct + nocache = false + + for i = 1, #val do + local v = val[i] + + if isfunction(v) then + if currentStat == nil then + currentStat, isFinal, nct = v(self, ...) + else + currentStat, isFinal, nct = v(self, currentStat) + end + + nocache = nocache or nct + + if isFinal then break end + elseif v then + currentStat = v + end + end + + if currentStat ~= nil then + return false, currentStat, nocache + end + + return true, ... + end + + return false, val +end + +SWEP.StatCache_Blacklist = { + ["ViewModelBoneMods"] = true, + ["WorldModelBoneMods"] = true, + ["MaterialTable"] = true, + ["MaterialTable_V"] = true, + ["MaterialTable_W"] = true, + ["ViewModelBodygroups"] = true, + ["Bodygroups_V"] = true, + ["WorldModelBodygroups"] = true, + ["Skin"] = true +} + +SWEP.StatCache = {} +SWEP.StatCache2 = {} +SWEP.StatStringCache = {} + +SWEP.LastClearStatCache = 0 +SWEP.ClearStatCacheWarnCount = 0 +SWEP.ClearStatCacheWarned = false + +local IdealCSCDeltaTime = engine.TickInterval() * 2 + +local LatestDataVersion = TFA.LatestDataVersion + +function SWEP:ClearStatCache(vn) + return self:ClearStatCacheVersioned(vn, 0) +end + +function SWEP:ClearStatCacheL(vn) + return self:ClearStatCacheVersioned(vn, LatestDataVersion) +end + +local trigger_lut_rebuild = { + FalloffMetricBased = true, + Range = true, + RangeFalloff = true, +} + +function SWEP:ClearStatCacheVersioned(vn, path_version) + local self2 = self:GetTable() + self2.ignore_stat_cache = true + local getpath, getpath2 + + if isstring(vn) then + vn = TFA.RemapStatPath(vn, path_version, self.TFADataVersion) + end + + if not vn and not self2.ClearStatCacheWarned then + local ct = CurTime() + local delta = ct - self2.LastClearStatCache + + if delta < IdealCSCDeltaTime and debug.traceback():find("Think2") then + self2.ClearStatCacheWarnCount = self2.ClearStatCacheWarnCount + 1 + + if self2.ClearStatCacheWarnCount >= 5 then + self2.ClearStatCacheWarned = true + + print(("[TFA Base] Weapon %s (%s) is abusing ClearStatCache function from Think2! This will lead to really bad performance issues, tell weapon's author to fix it ASAP!"):format(self2.PrintName, self:GetClass())) + end + elseif self2.ClearStatCacheWarnCount > 0 then + self2.ClearStatCacheWarnCount = 0 + end + + self2.LastClearStatCache = ct + end + + if vn then + local list = TFA.GetStatPathChildren(vn, path_version, self.TFADataVersion) + + for i = 1, #list do + self2.StatCache[list[i]] = nil + self2.StatCache2[list[i]] = nil + end + + getpath2 = self2.GetStatPath(self, vn) + getpath = getpath2[1] + else + table.Empty(self2.StatCache) + table.Empty(self2.StatCache2) + end + + if vn == "Primary" or not vn then + table.Empty(self2.Primary) + + local temp = {} + + setmetatable(self2.Primary, { + __index = function(self3, key) + return self2.GetStatVersioned(self, "Primary." .. key, self2.TFADataVersion) + end, + + __newindex = function() end + }) + + for k in pairs(self2.Primary_TFA) do + if isstring(k) then + temp[k] = self2.GetStatVersioned(self, "Primary." .. k, self2.TFADataVersion) + end + end + + setmetatable(self2.Primary, nil) + + for k, v in pairs(temp) do + self2.Primary[k] = v + end + + if self2.Primary_TFA.RangeFalloffLUT_IsConverted then + self2.Primary_TFA.RangeFalloffLUT = nil + self2.AutoDetectRange(self) + end + + local getLUT = self2.GetStatL(self, "Primary.RangeFalloffLUT", nil, true) + + if getLUT then + self2.Primary.RangeFalloffLUTBuilt = self:BuildFalloffTable(getLUT) + end + + if self2.Primary_TFA.RecoilLUT then + if self2.Primary_TFA.RecoilLUT["in"] then + self2.Primary_TFA.RecoilLUT["in"].points_p = {0} + self2.Primary_TFA.RecoilLUT["in"].points_y = {0} + + for i, point in ipairs(self2.Primary_TFA.RecoilLUT["in"].points) do + table.insert(self2.Primary_TFA.RecoilLUT["in"].points_p, point.p) + table.insert(self2.Primary_TFA.RecoilLUT["in"].points_y, point.y) + end + end + + if self2.Primary_TFA.RecoilLUT["loop"] then + self2.Primary_TFA.RecoilLUT["loop"].points_p = {} + self2.Primary_TFA.RecoilLUT["loop"].points_y = {} + + for i, point in ipairs(self2.Primary_TFA.RecoilLUT["loop"].points) do + table.insert(self2.Primary_TFA.RecoilLUT["loop"].points_p, point.p) + table.insert(self2.Primary_TFA.RecoilLUT["loop"].points_y, point.y) + end + + table.insert(self2.Primary_TFA.RecoilLUT["loop"].points_p, self2.Primary_TFA.RecoilLUT["loop"].points[1].p) + table.insert(self2.Primary_TFA.RecoilLUT["loop"].points_y, self2.Primary_TFA.RecoilLUT["loop"].points[1].y) + end + + if self2.Primary_TFA.RecoilLUT["out"] then + self2.Primary_TFA.RecoilLUT["out"].points_p = {0} + self2.Primary_TFA.RecoilLUT["out"].points_y = {0} + + for i, point in ipairs(self2.Primary_TFA.RecoilLUT["out"].points) do + table.insert(self2.Primary_TFA.RecoilLUT["out"].points_p, point.p) + table.insert(self2.Primary_TFA.RecoilLUT["out"].points_y, point.y) + end + + table.insert(self2.Primary_TFA.RecoilLUT["out"].points_p, 0) + table.insert(self2.Primary_TFA.RecoilLUT["out"].points_y, 0) + end + end + elseif getpath == "Primary_TFA" and isstring(getpath2[2]) then + if trigger_lut_rebuild[getpath2[2]] and self2.Primary_TFA.RangeFalloffLUT_IsConverted then + self2.Primary_TFA.RangeFalloffLUT = nil + self2.AutoDetectRange(self) + end + + self2.Primary[getpath[2]] = self2.GetStatVersioned(self, vn, path_version) + end + + if vn == "Secondary" or not vn then + table.Empty(self2.Secondary) + + local temp = {} + + setmetatable(self2.Secondary, { + __index = function(self3, key) + return self2.GetStatVersioned(self, "Secondary." .. key, self2.TFADataVersion) + end, + + __newindex = function() end + }) + + for k in pairs(self.Secondary_TFA) do + if isstring(k) then + temp[k] = self2.GetStatVersioned(self, "Secondary." .. k, self2.TFADataVersion) + end + end + + setmetatable(self2.Secondary, nil) + + for k, v in pairs(temp) do + self2.Secondary[k] = v + end + elseif getpath == "Secondary_TFA" and isstring(getpath2[2]) then + self2.Secondary[getpath[2]] = self2.GetStatVersioned(self, vn, path_version) + end + + if CLIENT then + self:RebuildModsRenderOrder() + end + + self2.ignore_stat_cache = false + hook.Run("TFA_ClearStatCache", self) +end + +local ccv = GetConVar("cl_tfa_debug_cache") + +function SWEP:GetStatPath(stat, path_version) + return TFA.GetStatPath(stat, path_version or 0, self.TFADataVersion) +end + +function SWEP:RemapStatPath(stat, path_version) + return TFA.RemapStatPath(stat, path_version or 0, self.TFADataVersion) +end + +function SWEP:GetStatPathRaw(stat) + return TFA.GetStatPathRaw(stat) +end + +function SWEP:GetStatRaw(stat, path_version) + local path = TFA.GetStatPath(stat, path_version or 0, self.TFADataVersion, not IsValid(self)) + local value = self[path[1]] + + for i = 2, #path do + if not istable(value) then return end + value = value[path[i]] + end + + return value +end + +function SWEP:GetStatRawL(stat) + return self:GetStatRaw(stat, LatestDataVersion) +end + +function SWEP:SetStatRaw(stat, path_version, _value) + local path = TFA.GetStatPath(stat, path_version or 0, self.TFADataVersion, not IsValid(self)) + + if #path == 1 then + self[path[1]] = _value + return self + end + + local value = self[path[1]] + + for i = 2, #path - 1 do + if not istable(value) then return self end + value = value[path[i]] + end + + if istable(value) then + value[path[#path]] = _value + end + + return self +end + +function SWEP:SetStatRawL(stat, _value) + return self:SetStatRaw(stat, LatestDataVersion, _value) +end + +function SWEP:GetStat(stat, default, dontMergeTables) + return self:GetStatVersioned(stat, 0, default, dontMergeTables) +end + +function SWEP:GetStatL(stat, default, dontMergeTables) + return self:GetStatVersioned(stat, LatestDataVersion, default, dontMergeTables) +end + +function SWEP:GetStatVersioned(stat, path_version, default, dontMergeTables) + local self2 = self:GetTable() + local statPath, currentVersionStat, translate = self2.GetStatPath(self, stat, path_version) + + if self2.StatCache2[currentVersionStat] ~= nil then + local finalReturn + + if self2.StatCache[currentVersionStat] ~= nil then + finalReturn = self2.StatCache[currentVersionStat] + else + local isDefault, retval = self2.GetStatRecursive(self, self2, statPath) + + if retval ~= nil then + if not isDefault then + self2.StatCache[currentVersionStat] = retval + end + + finalReturn = retval + else + finalReturn = istable(default) and tableCopy(default) or default + end + end + + local getstat = hook.Run("TFA_GetStat", self, currentVersionStat, finalReturn) + if getstat ~= nil then return translate(getstat) end + + return translate(finalReturn) + end + + if not self2.OwnerIsValid(self) then + local finalReturn = default + + if IsValid(self) then + local _ + _, finalReturn = self2.GetStatRecursive(self, self2, statPath, istable(default) and tableCopy(default) or default) + end + + local getstat = hook.Run("TFA_GetStat", self, currentVersionStat, finalReturn) + if getstat ~= nil then return translate(getstat) end + + return translate(finalReturn) + end + + local isDefault, statSelf = self2.GetStatRecursive(self, self2, statPath, istable(default) and tableCopy(default) or default) + local isDefaultAtt, statAttachment, noCache = self2.GetStatRecursive(self, self2.AttachmentTableCache, statPath, istable(statSelf) and tableCopy(statSelf) or statSelf) + local shouldCache = not noCache and + not (self2.StatCache_Blacklist_Real or self2.StatCache_Blacklist)[currentVersionStat] and + not (self2.StatCache_Blacklist_Real or self2.StatCache_Blacklist)[statPath[1]] and + not (ccv and ccv:GetBool()) + + if istable(statAttachment) and istable(statSelf) and not dontMergeTables then + statSelf = table.Merge(tableCopy(statSelf), statAttachment) + else + statSelf = statAttachment + end + + if shouldCache and not self2.ignore_stat_cache then + if not isDefault or not isDefaultAtt then + self2.StatCache[currentVersionStat] = statSelf + end + + self2.StatCache2[currentVersionStat] = true + end + + local getstat = hook.Run("TFA_GetStat", self, currentVersionStat, statSelf) + if getstat ~= nil then return translate(getstat) end + + return translate(statSelf) +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/ttt.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/ttt.lua new file mode 100644 index 0000000..35cd5e9 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/ttt.lua @@ -0,0 +1,193 @@ +SWEP.HeadshotMultiplier = 2.7 +SWEP.StoredAmmo = 0 +SWEP.IsDropped = false +SWEP.DeploySpeed = 1.4 +SWEP.fingerprints = {} + +-- crosshair +if CLIENT then + -- luacheck: globals LANG Key + local SafeTranslation = function(x) return x end + local GetPTranslation = LANG and LANG.GetParamTranslation or SafeTranslation + + -- Many non-gun weapons benefit from some help + local help_spec = { + text = "", + font = "TabLarge", + xalign = TEXT_ALIGN_CENTER + } + + function SWEP:DrawHelp() + local data = self.HUDHelp + local translate = data.translatable + local primary = data.primary + local secondary = data.secondary + + if translate then + primary = primary and GetPTranslation(primary, data.translate_params) + secondary = secondary and GetPTranslation(secondary, data.translate_params) + end + + help_spec.pos = {ScrW() / 2.0, ScrH() - 40} + help_spec.text = secondary or primary + draw.TextShadow(help_spec, 2) + + -- if no secondary exists, primary is drawn at the bottom and no top line + -- is drawn + if secondary then + help_spec.pos[2] = ScrH() - 60 + help_spec.text = primary + draw.TextShadow(help_spec, 2) + end + end + + local function SafeKey(binding, default) + local b = input.LookupBinding(binding) + if not b then return default end + + return string.upper(b) + end + + local Key = Key or SafeKey + + -- mousebuttons are enough for most weapons + local default_key_params = { + primaryfire = Key("+attack", "LEFT MOUSE"), + secondaryfire = Key("+attack2", "RIGHT MOUSE"), + usekey = Key("+use", "USE") + } + + function SWEP:AddHUDHelp(primary_text, secondary_text, translate, extra_params) + extra_params = extra_params or {} + + self.HUDHelp = { + primary = primary_text, + secondary = secondary_text, + translatable = translate, + translate_params = table.Merge(extra_params, default_key_params) + } + end +end + +function SWEP:GetHeadshotMultiplier(victim, dmginfo) + return self.HeadshotMultiplier or 2 +end + +function SWEP:IsEquipment() + -- luacheck: globals WEPS + if WEPS and WEPS.IsEquipment then + local val = WEPS.IsEquipment(self) + + if val ~= nil then + return val + else + return false + end + else + return false + end +end + +-- The OnDrop() hook is useless for this as it happens AFTER the drop. OwnerChange +-- does not occur when a drop happens for some reason. Hence this thing. +function SWEP:PreDrop() + if not IsValid(self) then return end + if not self.Ammo1 then return end + + if SERVER and IsValid(self:GetOwner()) and self.Primary_TFA.Ammo ~= "none" then + local ammo = self:Ammo1() + + -- Do not drop ammo if we have another gun that uses this type + for _, w in pairs(self:GetOwner():GetWeapons()) do + if IsValid(w) and w ~= self and w:GetPrimaryAmmoType() == self:GetPrimaryAmmoType() then + ammo = 0 + end + end + + self.StoredAmmo = ammo + + if ammo > 0 then + self:GetOwner():RemoveAmmo(ammo, self.Primary_TFA.Ammo) + end + end +end + +function SWEP:DampenDrop() + if not IsValid(self) then return end + -- For some reason gmod drops guns on death at a speed of 400 units, which + -- catapults them away from the body. Here we want people to actually be able + -- to find a given corpse's weapon, so we override the velocity here and call + -- this when dropping guns on death. + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:SetVelocityInstantaneous(Vector(0, 0, -75) + phys:GetVelocity() * 0.001) + phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.99) + end +end + +local SF_WEAPON_START_CONSTRAINED = 1 + +-- Picked up by player. Transfer of stored ammo and such. +function SWEP:EquipTTT(newowner) + if engine.ActiveGamemode() ~= "terrortown" then return end + + if SERVER then + if self:IsOnFire() then + self:Extinguish() + end + + self.fingerprints = self.fingerprints or {} + + if not table.HasValue(self.fingerprints, newowner) then + table.insert(self.fingerprints, newowner) + end + + if self:HasSpawnFlags(SF_WEAPON_START_CONSTRAINED) then + -- If this weapon started constrained, unset that spawnflag, or the + -- weapon will be re-constrained and float + local flags = self:GetSpawnFlags() + local newflags = bit.band(flags, bit.bnot(SF_WEAPON_START_CONSTRAINED)) + self:SetKeyValue("spawnflags", newflags) + end + end + + if not self.Ammo1 then return end + + if SERVER and IsValid(newowner) and self.StoredAmmo > 0 and self.Primary_TFA.Ammo ~= "none" then + local ammo = newowner:GetAmmoCount(self.Primary_TFA.Ammo) + self.Primary_TFA.ClipMax = self.Primary_TFA.ClipMax or (math.abs(self.Primary_TFA.ClipSize) * 4) + local given = math.min(self.StoredAmmo, self.Primary_TFA.ClipMax - ammo) + newowner:GiveAmmo(given, self.Primary_TFA.Ammo) + self.StoredAmmo = 0 + end +end + +-- We were bought as special equipment, some weapons will want to do something +-- extra for their buyer +function SWEP:WasBought(buyer) +end + +function SWEP:DyingShot() + local fired = false + -- if self:GetIronSightsProgress() and self:GetIronSightsProgress() > 0.01 then + self:SetIronSightsRaw(false) + if self:GetNextPrimaryFire() > CurTime() then return fired end + + -- Owner should still be alive here + if IsValid(self:GetOwner()) then + local punch = self.Primary_TFA.Recoil or 5 + -- Punch view to disorient aim before firing dying shot + local eyeang = self:GetOwner():EyeAngles() + eyeang.pitch = eyeang.pitch - math.Rand(-punch, punch) + eyeang.yaw = eyeang.yaw - math.Rand(-punch, punch) + self:GetOwner():SetEyeAngles(eyeang) + MsgN(self:GetOwner():Nick() .. " fired his DYING SHOT") + self:GetOwner().dying_wep = self + self:PrimaryAttack() + fired = true + end + -- end + + return fired +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/utils.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/utils.lua new file mode 100644 index 0000000..f767506 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/utils.lua @@ -0,0 +1,1276 @@ +TFA.RangeFalloffLUTStep = 0.01 +TFA.RangeFalloffLUTStepInv = 1 / TFA.RangeFalloffLUTStep + +SWEP.AmmoRangeTable = { + ["SniperPenetratedRound"] = 2, + ["SniperPenetratedBullet"] = 2, + ["buckshot"] = 0.5, + ["ar2"] = 1, + ["smg1"] = 0.7, + ["pistol"] = 0.33, + ["def"] = 1 +} + +function SWEP:AmmoRangeMultiplier() + return self.AmmoRangeTable[self.Primary_TFA.Ammo or "def"] or self.AmmoRangeTable["def"] or 1 +end + +function SWEP:MetersToUnits(x) + return x * 39.3701 * 4 / 3 +end + +function SWEP:GetLastSequenceString() + if not self:VMIV() then return "" end + + if self:GetLastSequence() < 0 then return "" end + return self.OwnerViewModel:GetSequenceName(self:GetLastSequence()) +end + +local cv_3dmode = GetConVar("cl_tfa_scope_sensitivity_3d") + +SWEP.SensitivtyFunctions = { + [0] = function() return 1 end, + [1] = function(self, ...) + local zoom = self:GetStatL("Secondary.ScopeZoom") + + if zoom and zoom >= 1 then + return TFA.CalculateSensitivtyScale(90 / zoom, self:GetStatL("Secondary.OwnerFOV"), self:GetStatL("Secondary.ScopeScreenScale")) + else + return self.SensitivtyFunctions[2](self, ...) + end + end, + [2] = function(self, ...) + local rtfov = self:GetStatL("RTScopeFOV") + + if rtfov and rtfov > 0 then + return TFA.CalculateSensitivtyScale(rtfov, self:GetStatL("Secondary.OwnerFOV"), self:GetStatL("Secondary.ScopeScreenScale")) + else + return self.SensitivtyFunctions[0](self, ...) + end + end, + [3] = function(self, ...) + local rtfov = self:GetStatL("RTScopeFOV") + + if rtfov and rtfov > 0 then + return TFA.CalculateSensitivtyScale(rtfov, self:GetStatL("Secondary.OwnerFOV"), 1) + else + return self.SensitivtyFunctions[0](self, ...) + end + end +} + +function SWEP:Get3DSensitivity() + local f = self.SensitivtyFunctions[cv_3dmode:GetInt()] + + return f(self) +end + +function SWEP:GetSeed() + local sd = math.floor(self:Clip1() + self:Ammo1() + self:Clip2() + self:Ammo2() + self:GetLastActivity()) + self:GetNextIdleAnim() + self:GetNextPrimaryFire() + self:GetNextSecondaryFire() + + return math.Round(sd) +end + +function SWEP:GetSeedIrradical() + return math.floor(self:Clip1() + self:Ammo1() + self:Clip2() + self:Ammo2() + self:GetLastActivity()) + self:GetNextIdleAnim() + self:GetNextPrimaryFire() + self:GetNextSecondaryFire() +end + +SWEP.SharedRandomValues = {} +local seed + +--math.random equivalent +function SWEP:SharedRandom(min, max, id) + if min and not max then + max = min + min = 1 + end + + min = math.Round(min) + max = math.Round(max) + local key = (id or "Weapon") .. min .. max + seed = self:GetSeed() + local val = math.floor(util.SharedRandom(id or "Weapon", min, max + 1, seed)) + + if self.SharedRandomValues[key] and self.SharedRandomValues[key] == val then + if min < val and max > val then + math.randomseed(seed) + + if (math.Rand(0, 1) < 0.5) then + math.randomseed(seed + 1) + val = math.random(min, val - 1) + else + math.randomseed(seed + 1) + val = math.random(val + 1, max) + end + elseif min < val then + math.randomseed(seed + 1) + val = math.random(min, val - 1) + elseif max > val then + math.randomseed(seed + 1) + val = math.random(val + 1, max) + end + end + + if IsFirstTimePredicted() then + timer.Simple(0, function() + if IsValid(self) then + self.SharedRandomValues[key] = val + end + end) + end + + return val +end + +local oiv = nil +local rlcv = GetConVar("sv_tfa_reloads_enabled") +local holding_result_cached = false +local last_held_check = -1 +local sp = game.SinglePlayer() +local slo, sqlo +local nm + +--[[ +local sqind + +function SWEP:TranslateSequenceActivityTable( tbl ) + if not self:VMIV() then return end + for k,v in pairs(tbl) do + if type(k) == "string" then + sqind = self.OwnerViewModel:GetSequenceActivity( self.OwnerViewModel:LookupSequence( k ) or -1 ) or -1 + tbl[ sqind ] = tbl[sqind] or v + end + tbl[k] = nil + end +end +]] +-- +--, seq ) +function SWEP:GetActivityLengthRaw(tanim, status, animType) + local vm = self:VMIVNPC() + if not vm then return 0 end + + if tanim == nil then + -- we already track last sequence so, we can account sequence + tanim = self:GetLastSequence() + animType = TFA.Enum.ANIMATION_SEQ + end + + if tanim < 0 then return 0 end + + if animType == nil or animType == TFA.Enum.ANIMATION_ACT then + nm = vm:GetSequenceName(vm:SelectWeightedSequenceSeeded(tanim, self:GetSeedIrradical())) + else + nm = vm:GetSequenceName(tanim) + end + + local sqlen + + if animType == TFA.Enum.ANIMATION_SEQ then + sqlen = vm:SequenceDuration(tanim) + elseif tanim == vm:GetSequenceActivity(vm:GetSequence()) then + sqlen = vm:SequenceDuration(vm:GetSequence()) + else + sqlen = vm:SequenceDuration(vm:SelectWeightedSequenceSeeded(math.max(tanim or 1, 1), self:GetSeedIrradical())) + end + + slo = self:GetStatL("StatusLengthOverride." .. nm) or self:GetStatL("StatusLengthOverride." .. (tanim or "0")) + sqlo = self:GetStatL("SequenceLengthOverride." .. nm) or self:GetStatL("SequenceLengthOverride." .. (tanim or "0")) + + if status and slo then + sqlen = slo + elseif sqlo then + sqlen = sqlo + end + + return sqlen +end + +function SWEP:GetActivityLength(tanim, status, animType) + if not self:VMIVNPC() then return 0 end + local sqlen = self:GetActivityLengthRaw(tanim, status, animType) + if sqlen <= 0 then return 0 end + return sqlen / self:GetAnimationRate(tanim) +end + +function SWEP:GetHolding() + if CurTime() > last_held_check + 0.2 then + last_held_check = CurTime() + holding_result_cached = nil + end + + if holding_result_cached == nil then + holding_result_cached = false + + if not IsValid(self:GetOwner()) or not self:GetOwner():IsPlayer() then + holding_result_cached = false + + return false + end + + local ent = self:GetOwner():GetNW2Entity("LastHeldEntity") + + if not IsValid(ent) then + holding_result_cached = false + + return false + end + + if ent.IsPlayerHolding then + ent:SetNW2Bool("PlayerHolding", ent:IsPlayerHolding()) + end + + if ent:GetNW2Bool("PlayerHolding") then + holding_result_cached = true + + return true + end + end + + return holding_result_cached +end + +function SWEP:CanInterruptShooting() + return self:GetStatL("Primary.RPM") > 160 and not self:GetStatL("BoltAction") and not self:GetStatL("BoltAction_Forced") +end + +function SWEP:ReloadCV() + if rlcv then + if (not rlcv:GetBool()) and (not self.Primary_TFA.ClipSize_PreEdit) then + self.Primary_TFA.ClipSize_PreEdit = self.Primary_TFA.ClipSize + self.Primary_TFA.ClipSize = -1 + self:ClearStatCache() + elseif rlcv:GetBool() and self.Primary_TFA.ClipSize_PreEdit then + self.Primary_TFA.ClipSize = self.Primary_TFA.ClipSize_PreEdit + self.Primary_TFA.ClipSize_PreEdit = nil + self:ClearStatCache() + end + end +end + +function SWEP:OwnerIsValid() + if oiv == nil then + oiv = IsValid(self:GetOwner()) + end + + return oiv +end + +function SWEP:NullifyOIV() + if oiv ~= nil then + self:GetHolding() + oiv = nil + end + + return self:VMIV() +end + +function SWEP:VMIVNPC() + local ply = self:GetOwner() + + if ply:IsPlayer() then return self:VMIV() end + + if ply:IsNPC() then + return self + end + + return false +end + +function SWEP:VMIV() + local owent = self:GetOwner() + + if not IsValid(self.OwnerViewModel) then + if IsValid(owent) and owent.GetViewModel then + self.OwnerViewModel = owent:GetViewModel() + end + + return false + else + if not IsValid(owent) or not owent.GetViewModel then + self.OwnerViewModel = nil + + return false + end + + return self.OwnerViewModel + end +end + +function SWEP:CanChamber() + if self.C_CanChamber ~= nil then + return self.C_CanChamber + else + self.C_CanChamber = not self:GetStatL("BoltAction") and not self:GetStatL("LoopedReload") and not self.Revolver and not self:GetStatL("Primary.DisableChambering") + + return self.C_CanChamber + end +end + +function SWEP:GetPrimaryClipSize(calc) + local targetclip = self:GetStatL("Primary.ClipSize") + + if self:CanChamber() and not (calc and self:Clip1() <= 0) then + targetclip = targetclip + (self:GetStatL("IsAkimbo") and 2 or 1) + end + + return math.max(targetclip, -1) +end + +function SWEP:GetPrimaryClipSizeForReload(calc) + local targetclip = self:GetStatL("Primary.ClipSize") + + if self:CanChamber() and not (calc and self:Clip1() <= 0) and not self:IsJammed() then + targetclip = targetclip + (self:GetStatL("IsAkimbo") and 2 or 1) + end + + return math.max(targetclip, -1) +end + +function SWEP:GetSecondaryClipSize(calc) + local targetclip = self:GetStatL("Secondary.ClipSize") + + return math.max(targetclip, -1) +end + +--[[ + +function SWEP:GetPrimaryAmmoType() + return self:GetStatL( "Primary.Ammo" ) or "" +end + +function SWEP:GetPrimaryAmmoTypeC() + return self:GetStatL( "Primary.Ammo" ) or self:GetPrimaryAmmoType() +end + +function SWEP:Ammo1() + return self:GetOwner():GetAmmoCount( self:GetPrimaryAmmoTypeC() or 0 ) +end + +function SWEP:GetSecondaryAmmoType() + return self:GetStatL( "Secondary.Ammo" ) or "" +end + +function SWEP:GetSecondaryAmmoTypeC() + return self:GetStatL( "Secondary.Ammo" ) or self:GetSecondaryAmmoType() +end + +function SWEP:Ammo2() + return self:GetOwner():GetAmmoCount( self:GetSecondaryAmmoTypeC() or -1 ) +end + +]] +-- +local at + +function SWEP:GetPrimaryAmmoTypeC() + at = self:GetStatL("Primary.Ammo") + + if at and at ~= self.Primary_TFA.Ammo then + return at + elseif self.GetPrimaryAmmoTypeOld then + return self:GetPrimaryAmmoTypeOld() + else + return self:GetPrimaryAmmoType() + end +end + +function SWEP:GetSecondaryAmmoTypeC() + at = self:GetStatL("Secondary.Ammo") + + if at and at ~= self.Secondary_TFA.Ammo then + return at + elseif self.GetSecondaryAmmoTypeOld then + return self:GetSecondaryAmmoTypeOld() + else + return self:GetSecondaryAmmoType() + end +end + +function SWEP:Ammo1() + if not self:GetOwner():IsValid() then return 0 end + if self:GetOwner():IsNPC() then return 9999 end + + return self:GetOwner():GetAmmoCount(self:GetPrimaryAmmoTypeC() or 0) +end + +function SWEP:Ammo2() + if not self:GetOwner():IsValid() then return 0 end + if self:GetOwner():IsNPC() then return 9999 end + + return self:GetOwner():GetAmmoCount(self:GetSecondaryAmmoTypeC() or -1) +end + +-- Returns absolute delta of change in ammo count +function SWEP:TakePrimaryAmmo(num, pool) + num = math.floor(num) + if num == 0 then return 0 end + + if num < 0 then + -- Doesn't use clips + if self:GetStatL("Primary.ClipSize") < 0 or pool then + if not self:GetOwner():IsPlayer() then return -num end -- assume NPCs always take all the ammo + return self:GetOwner():GiveAmmo(-num, self:GetPrimaryAmmoTypeC()) + else + local old = self:Clip1() + local new = math.max(self:Clip1() - num, 0) + self:SetClip1(new) + return new - old + end + else + -- Doesn't use clips + if self:GetStatL("Primary.ClipSize") < 0 or pool then + if not self:GetOwner():IsPlayer() then return num end -- assume NPCs always provide all the ammo + local old = self:Ammo1() + if old <= 0 then return 0 end + local toRemove = math.min(old, num) + self:GetOwner():RemoveAmmo(toRemove, self:GetPrimaryAmmoTypeC()) + return toRemove + else + local old = self:Clip1() + local new = math.max(self:Clip1() - num, 0) + self:SetClip1(new) + return old - new + end + end +end + +-- Returns absolute delta of change in ammo count +function SWEP:TakeSecondaryAmmo(num, pool) + num = math.floor(num) + if num == 0 then return 0 end + + if num < 0 then + -- Doesn't use clips + if self:GetStatL("Secondary.ClipSize") < 0 or pool then + if not self:GetOwner():IsPlayer() then return -num end -- assume NPCs always take all the ammo + return self:GetOwner():GiveAmmo(-num, self:GetSecondaryAmmoTypeC()) + else + local old = self:Clip2() + local new = math.max(self:Clip2() - num, 0) + self:SetClip2(new) + return new - old + end + else + -- Doesn't use clips + if self:GetStatL("Secondary.ClipSize") < 0 or pool then + if not self:GetOwner():IsPlayer() then return num end -- assume NPCs always provide all the ammo + local old = self:Ammo2() + if old <= 0 then return 0 end + local toRemove = math.min(old, num) + self:GetOwner():RemoveAmmo(toRemove, self:GetSecondaryAmmoTypeC()) + return toRemove + else + local old = self:Clip2() + local new = math.max(self:Clip2() - num, 0) + self:SetClip2(new) + return old - new + end + end +end + +-- Inserts up to num ammo rounds into gun's primary clip +-- negative values will unload clip back into ammo reserve (WITHOUT accounting for max ammo reserve!) +-- Returns absolute delta of change in ammo count +function SWEP:InsertPrimaryAmmo(num) + num = math.floor(num) + local self2 = self:GetTable() + + if num > 0 then + num = math.min(math.max(self:GetMaxClip1() - self:Clip1(), 0), num) + return self2.TakePrimaryAmmo(self, -self2.TakePrimaryAmmo(self, num, true)) + end + + return self2.TakePrimaryAmmo(self, -self2.TakePrimaryAmmo(self, num, true)) +end + +-- Inserts up to num ammo rounds into gun's secondary clip +-- negative values will unload clip back into ammo reserve (WITHOUT accounting for max ammo reserve!) +-- Returns absolute delta of change in ammo count +function SWEP:InsertSecondaryAmmo(num) + num = math.floor(num) + local self2 = self:GetTable() + + if num > 0 then + num = math.min(math.max(self:GetMaxClip2() - self:Clip2(), 0), num) + return self2.TakeSecondaryAmmo(self, -self2.TakeSecondaryAmmo(self, num, true)) + end + + return self2.TakeSecondaryAmmo(self, -self2.TakeSecondaryAmmo(self, num, true)) +end + +function SWEP:IsEmpty1() + return self:GetStatL("Primary.ClipSize") > 0 and self:Clip1() == 0 or + self:GetStatL("Primary.ClipSize") <= 0 and self:Ammo1() == 0 +end + +function SWEP:IsEmpty2() + return self:GetStatL("Secondary.ClipSize") > 0 and self:Clip2() == 0 or + self:GetStatL("Secondary.ClipSize") <= 0 and self:Ammo2() == 0 +end + +SWEP.TakeAmmo1 = SWEP.TakePrimaryAmmo +SWEP.TakeAmmo2 = SWEP.TakeSecondaryAmmo + +function SWEP:GetFireDelay() + if self:GetMaxBurst() > 1 and self:GetStatL("Primary.RPM_Burst") and self:GetStatL("Primary.RPM_Burst") > 0 then + return 60 / self:GetStatL("Primary.RPM_Burst") + elseif self:GetStatL("Primary.RPM_Semi") and not self.Primary_TFA.Automatic and self:GetStatL("Primary.RPM_Semi") and self:GetStatL("Primary.RPM_Semi") > 0 then + return 60 / self:GetStatL("Primary.RPM_Semi") + elseif self:GetStatL("Primary.RPM") and self:GetStatL("Primary.RPM") > 0 then + return 60 / self:GetStatL("Primary.RPM") + else + return self:GetStatL("Primary.Delay") or 0.1 + end +end + +function SWEP:GetBurstDelay(bur) + if not bur then + bur = self:GetMaxBurst() + end + + if bur <= 1 then return 0 end + if self:GetStatL("Primary.BurstDelay") then return self:GetStatL("Primary.BurstDelay") end + + return self:GetFireDelay() * 3 +end + +local tickrate = engine.TickInterval() + +function SWEP:GetNextCorrectedPrimaryFire(delay) + local nextfire = self:GetNextPrimaryFire() + local delta = CurTime() - nextfire + + if delta < 0 or delta > tickrate then + nextfire = CurTime() + end + + return nextfire + delay +end + +function SWEP:GetNextCorrectedSecondaryFire(delay) + local nextfire = self:GetNextSecondaryFire() + local delta = CurTime() - nextfire + + if delta < 0 or delta > tickrate then + nextfire = CurTime() + end + + return nextfire + delay +end + +--[[ +Function Name: IsSafety +Syntax: self:IsSafety(). +Returns: Are we in safety firemode. +Notes: Non. +Purpose: Utility +]] +-- +function SWEP:IsSafety() + if not self:GetStatL("FireModes") then return false end + local fm = self:GetStatL("FireModes")[self:GetFireMode()] + local fmn = string.lower(fm and fm or self:GetStatL("FireModes")[1]) + + if fmn == "safe" or fmn == "holster" then + return true + else + return false + end +end + +function SWEP:UpdateMuzzleAttachment() + if not self:VMIV() then return end + local vm = self.OwnerViewModel + if not IsValid(vm) then return end + self.MuzzleAttachmentRaw = nil + + if not self.MuzzleAttachmentSilenced then + self.MuzzleAttachmentSilenced = (vm:LookupAttachment("muzzle_silenced") <= 0) and self.MuzzleAttachment or "muzzle_silenced" + end + + if self:GetSilenced() and self.MuzzleAttachmentSilenced then + self.MuzzleAttachmentRaw = vm:LookupAttachment(self.MuzzleAttachmentSilenced) + + if not self.MuzzleAttachmentRaw or self.MuzzleAttachmentRaw <= 0 then + self.MuzzleAttachmentRaw = nil + end + end + + if not self.MuzzleAttachmentRaw and self.MuzzleAttachment then + self.MuzzleAttachmentRaw = vm:LookupAttachment(self.MuzzleAttachment) + + if not self.MuzzleAttachmentRaw or self.MuzzleAttachmentRaw <= 0 then + self.MuzzleAttachmentRaw = 1 + end + end + + local mzm = self:GetStatL("MuzzleAttachmentMod", 0) + + if mzm then + if isstring(mzm) then + self.MuzzleAttachmentRaw = vm:LookupAttachment(mzm) + elseif mzm > 0 then + self.MuzzleAttachmentRaw = mzm + end + end +end + +function SWEP:UpdateConDamage() + if not IsValid(self) then return end + + if not self.DamageConVar then + self.DamageConVar = GetConVar("sv_tfa_damage_multiplier") + end + + if self.DamageConVar and self.DamageConVar.GetFloat then + self.ConDamageMultiplier = self.DamageConVar:GetFloat() + end +end + +--[[ +Function Name: IsCurrentlyScoped +Syntax: self:IsCurrentlyScoped(). +Returns: Is the player scoped in enough to display the overlay? true/false, returns a boolean. +Notes: Change SWEP.ScopeOverlayThreshold to change when the overlay is displayed. +Purpose: Utility +]] +-- +function SWEP:IsCurrentlyScoped() + return (self:GetIronSightsProgress() > self:GetStatL("ScopeOverlayThreshold")) and self:GetStatL("Scoped") +end + +--[[ +Function Name: IsCurrently3DScoped +Syntax: self:IsCurrently3DScoped(). +Returns: Is player aiming down the sights while having a RT-enabled scope equipped? +Notes: +Purpose: Utility +]] +-- +function SWEP:IsCurrently3DScoped() + return (self:GetStatL("RTDrawEnabled") or self.RTCode ~= nil) and self:GetIronSights() +end + +--[[ +Function Name: IsHidden +Syntax: self:IsHidden(). +Returns: Should we hide self?. +Notes: +Purpose: Utility +]] +-- +function SWEP:GetHidden() + if not self:VMIV() then return true end + if self.DrawViewModel ~= nil and not self.DrawViewModel then return true end + if self.ShowViewModel ~= nil and not self.ShowViewModel then return true end + if self:GetHolding() then return true end + + return self:IsCurrentlyScoped() +end + +--[[ +Function Name: IsFirstPerson +Syntax: self:IsFirstPerson(). +Returns: Is the owner in first person. +Notes: Broken in singplayer because gary. +Purpose: Utility +]] +-- +function SWEP:IsFirstPerson() + if not IsValid(self) or not self:OwnerIsValid() then return false end + if self:GetOwner():IsNPC() then return false end + if CLIENT and (not game.SinglePlayer()) and self:GetOwner() ~= GetViewEntity() then return false end + if sp and SERVER then return not self:GetOwner().TFASDLP end + if self:GetOwner().ShouldDrawLocalPlayer and self:GetOwner():ShouldDrawLocalPlayer() then return false end + if LocalPlayer and hook.Call("ShouldDrawLocalPlayer", GAMEMODE, self:GetOwner()) then return false end + + return true +end + +--[[ +Function Name: GetMuzzlePos +Syntax: self:GetMuzzlePos( hacky workaround that doesn't work anyways ). +Returns: The AngPos for the muzzle attachment. +Notes: Defaults to the first attachment, and uses GetFPMuzzleAttachment +Purpose: Utility +]] +-- +local fp + +function SWEP:GetMuzzleAttachment() + local vmod = self.OwnerViewModel + local att = math.max(1, self.MuzzleAttachmentRaw or (sp and vmod or self):LookupAttachment(self.MuzzleAttachment)) + + if self:GetStatL("IsAkimbo") then + att = 1 + self:GetAnimCycle() + end + + return att +end + +function SWEP:GetMuzzlePos(ignorepos) + fp = self:IsFirstPerson() + local vm = self.OwnerViewModel + + if not IsValid(vm) then + vm = self + end + + -- Avoid returning strings inside MuzzleAttachmentMod, since this would decrease performance + -- Better call :UpdateMuzzleAttachment() or return number in MuzzleAttachmentMod + local obj = self:GetStatL("MuzzleAttachmentMod") or self.MuzzleAttachmentRaw or vm:LookupAttachment(self.MuzzleAttachment) + + if type(obj) == "string" then + obj = tonumber(obj) or vm:LookupAttachment(obj) + end + + local muzzlepos + obj = math.Clamp(obj or 1, 1, 128) + + if fp then + muzzlepos = vm:GetAttachment(obj) + else + muzzlepos = self:GetAttachment(obj) + end + + return muzzlepos +end + +function SWEP:FindEvenBurstNumber() + local burstOverride = self:GetStatL("BurstFireCount") + + if burstOverride then + return burstOverride + end + + if (self:GetStatL("Primary.ClipSize") % 3 == 0) then + return 3 + elseif (self:GetStatL("Primary.ClipSize") % 2 == 0) then + return 2 + else + local i = 4 + + while i <= 7 do + if self:GetStatL("Primary.ClipSize") % i == 0 then return i end + i = i + 1 + end + end + + return nil +end + +function SWEP:GetFireModeName() + local fm = self:GetFireMode() + local fmn = string.lower(self:GetStatL("FireModes")[fm]) + if fmn == "safe" or fmn == "holster" then return language.GetPhrase("tfa.firemode.safe") end + if self:GetStatL("FireModeName") then return language.GetPhrase(self:GetStatL("FireModeName")) end + if fmn == "auto" or fmn == "automatic" then return language.GetPhrase("tfa.firemode.auto") end + + if fmn == "semi" or fmn == "single" then + if self:GetStatL("Revolver") then + if (self:GetStatL("BoltAction")) then + return language.GetPhrase("tfa.firemode.single") + else + return language.GetPhrase("tfa.firemode.revolver") + end + else + if (self:GetStatL("BoltAction")) then + return language.GetPhrase("tfa.firemode.bolt") + else + if self:GetStatL("LoopedReload") and self:GetStatL("Primary.RPM") < 250 then + return language.GetPhrase("tfa.firemode.pump") + else + return language.GetPhrase("tfa.firemode.semi") + end + end + end + end + + local bpos = string.find(fmn, "burst") + if bpos then return language.GetPhrase("tfa.firemode.burst"):format(string.sub(fmn, 1, bpos - 1)) end + + return "" +end + +SWEP.BurstCountCache = {} + +function SWEP:GetMaxBurst() + local fm = self:GetFireMode() + + if not self.BurstCountCache[fm] then + local fmt = self:GetStatL("FireModes") + local fmn = string.lower(fmt[fm]) + local bpos = string.find(fmn, "burst") + + if bpos then + self.BurstCountCache[fm] = tonumber(string.sub(fmn, 1, bpos - 1)) + else + self.BurstCountCache[fm] = 1 + end + end + + return self.BurstCountCache[fm] +end + +--[[ +Function Name: CycleFireMode +Syntax: self:CycleFireMode() +Returns: Nothing. +Notes: Cycles to next firemode. +Purpose: Feature +]] +-- +local l_CT = CurTime + +SWEP.FireModesAutomatic = { + ["Automatic"] = true, + ["Auto"] = true, +} + +SWEP.FireModeSound = Sound("Weapon_AR2.Empty") -- firemode toggle sound + +function SWEP:CycleFireMode() + local ct = l_CT() + local fm = self:GetFireMode() + fm = fm + 1 + + if fm >= #self:GetStatL("FireModes") then + fm = 1 + end + + self:SetFireMode(fm) + local success, tanim, ttype = self:ChooseROFAnim() + + if success then + self:SetNextPrimaryFire(ct + self:GetActivityLength(tanim, false, ttype)) + else + self:EmitSound(self:GetStatL("FireModeSound")) + self:SetNextPrimaryFire(ct + math.max(self:GetFireDelay(), 0.25)) + end + + self.BurstCount = 0 + self:SetIsCyclingSafety(false) + self:SetStatus(TFA.Enum.STATUS_FIREMODE, self:GetNextPrimaryFire()) + + self.Primary.Automatic = self:IsFireModeAutomatic(fm) + self.Primary_TFA.Automatic = self.Primary.Automatic +end + +--[[ +Function Name: CycleSafety +Syntax: self:CycleSafety() +Returns: Nothing. +Notes: Toggles safety +Purpose: Feature +]] +-- +function SWEP:CycleSafety() + local ct = l_CT() + local fm = self:GetFireMode() + local fmt = self:GetStatL("FireModes") + + self.BurstCount = 0 + self:SetIsCyclingSafety(true) + self:SetIronSightsRaw(false) + + if fm ~= #fmt then + self.LastFireMode = fm + self:SetFireMode(#fmt) + else + self:SetFireMode(self.LastFireMode or 1) + end + + local success, tanim, ttype = self:ChooseROFAnim() + + if success then + self:SetSafetyCycleAnimated(true) + self:SetNextPrimaryFire(ct + self:GetActivityLength(tanim, false, ttype)) + else + self:SetSafetyCycleAnimated(false) + self:EmitSound(self:GetStatL("FireModeSound")) + self:SetNextPrimaryFire(ct + math.max(self:GetFireDelay(), 0.25)) + end + + self:SetStatus(TFA.Enum.STATUS_FIREMODE, self:GetNextPrimaryFire()) + + if self:IsSafety() then + self.Primary.Automatic = false + self.Primary_TFA.Automatic = false + else + self.Primary.Automatic = self:IsFireModeAutomatic() + self.Primary_TFA.Automatic = self.Primary.Automatic + end +end + +--[[ +Function Name: 1FireMode +Syntax: self:ProcessFireMode() +Returns: Nothing. +Notes: Processes fire mode changing and whether the swep is auto or not. +Purpose: Feature +]] +-- +function SWEP:ProcessFireMode() + if self:GetOwner():IsNPC() then return end + + if self:GetOwner().GetInfoNum and self:GetOwner():GetInfoNum("cl_tfa_keys_firemode", 0) > 0 then + return + end + + if self:OwnerIsValid() and self:KeyPressed(IN_RELOAD) and self:KeyDown(IN_USE) and self:GetStatus() == TFA.Enum.STATUS_IDLE and (SERVER or not sp) then + if self:GetStatL("SelectiveFire") and not self:KeyDown(IN_SPEED) then + self:CycleFireMode() + elseif self:GetOwner():KeyDown(IN_SPEED) then + self:CycleSafety() + end + end +end + +--[[ +Function Name: Unload +Syntax: self:Unload() +Returns: Nothing. +Notes: Returns Clip1 ammo to reserve. +Purpose: Utility +]] +-- +function SWEP:Unload() + local amm = self:Clip1() + self:SetClip1(0) + + if self.OwnerIsValid and self:OwnerIsValid() and self.Owner.GiveAmmo then + self:GetOwner():GiveAmmo(amm, self:GetPrimaryAmmoType(), true) + end +end + +--[[ +Function Name: Unload +Syntax: self:Unload() +Returns: Nothing. +Notes: Returns Clip1 ammo to reserve. +Purpose: Utility +]] +-- +function SWEP:Unload2() + local amm = self:Clip2() + self:SetClip2(0) + + if self.OwnerIsValid and self:OwnerIsValid() and self.Owner.GiveAmmo then + self:GetOwner():GiveAmmo(amm, self:GetSecondaryAmmoType(), true) + end +end + +local penetration_hitmarker_cvar = GetConVar("sv_tfa_penetration_hitmarker") + +function SWEP:SendHitMarker(ply, traceres, dmginfo) + if CLIENT or not penetration_hitmarker_cvar:GetBool() then return end + if not IsValid(ply) or not ply:IsPlayer() then return end + + local hm3d = ply:GetInfoNum("cl_tfa_hud_hitmarker_3d_all", 0) > 0 + local hm3d_sg = ply:GetInfoNum("cl_tfa_hud_hitmarker_3d_shotguns", 0) > 0 and self:GetStatL("Primary.NumShots") > 1 + + if hm3d or hm3d_sg then + net.Start("tfaHitmarker3D", true) + net.WriteVector(traceres.HitPos) + net.Send(ply) + else + net.Start("tfaHitmarker", true) + net.Send(ply) + end +end + +SWEP.VMSeqCache = {} +local vm -- are you fucking kidding me + +function SWEP:CheckVMSequence(seqname) + if not IsValid(self) then return false end + vm = self.OwnerViewModel + if not IsValid(vm) then return false end + local mdl = vm:GetModel() + if not mdl then return false end + self.VMSeqCache[mdl] = self.VMSeqCache[mdl] or {} + + if self.VMSeqCache[mdl][seqname] == nil then + self.VMSeqCache[mdl][seqname] = vm:LookupSequence(seqname) >= 0 + end + + return self.VMSeqCache[mdl][seqname] +end + +do + local function sorter(a, b) + return a.range < b.range + end + + local function linear(a) return a end + + function SWEP:BuildFalloffTable(input, step) + if step == nil then step = TFA.RangeFalloffLUTStep end + + table.sort(input.lut, sorter) + + if input.lut[1].range > 0 then + for i = #input.lut, 1, -1 do + input.lut[i + 1] = input.lut[i] + end + + input.lut[1] = {range = 0, damage = 1} + end + + local div = (input.units == "hammer" or input.units == "inches" or input.units == "inch" or input.units == "hu") and 1 or 0.0254 + + local build = {} + local minimal = input.lut[1].range + local maximal = input.lut[#input.lut].range + + local fnrange = isfunction(input.range_func) and input.range_func or + input.range_func == "quintic" and TFA.Quintic or + input.range_func == "cubic" and TFA.Cubic or + input.range_func == "cosine" and TFA.Cosine or + input.range_func == "sinusine" and TFA.Sinusine or + linear + + if input.bezier then + local build_range = {} + local build_damage = {} + + for _, data in ipairs(input.lut) do + table.insert(build_range, data.range / div) + table.insert(build_damage, data.damage) + end + + for i = 0, 1, step do + local value = fnrange(i) + table.insert(build, {TFA.tbezier(value, build_range), TFA.tbezier(value, build_damage)}) + end + else + local current, next = input.lut[1], input.lut[2] + local nextindex = 1 + + for i = 0, 1, step do + local value = fnrange(i) + local interp = Lerp(value, minimal, maximal) + + if next.range < interp then + nextindex = nextindex + 1 + current, next = input.lut[nextindex], input.lut[nextindex + 1] + end + + if not current or not next then break end -- safeguard + table.insert(build, {interp / div, Lerp(1 - (next.range - interp) / (next.range - current.range), current.damage, next.damage)}) + end + end + + return build + end +end + +function SWEP:IncreaseRecoilLUT() + if not self:HasRecoilLUT() then return end + + local self2 = self:GetTable() + local time = CurTime() + + if not self:GetRecoilThink() then + self:SetRecoilThink(true) + end + + if not self:GetRecoilLoop() then + local newvalue = self:GetRecoilInProgress() + self2.Primary_TFA.RecoilLUT["in"].increase + + self:SetRecoilInProgress(math.min(1, newvalue)) + + self:SetRecoilInWait(time + self2.Primary_TFA.RecoilLUT["in"].wait) + + if self:GetRecoilInProgress() >= 1 then + self:SetRecoilLoop(true) + self:SetRecoilLoopProgress(math.Clamp(newvalue % 1, 0, 1)) + self:SetRecoilLoopWait(time + self2.Primary_TFA.RecoilLUT["loop"].wait) + end + + return + end + + local sub = 0 + + if self:GetRecoilOutProgress() ~= 0 then + local prev = self:GetRecoilOutProgress() + local newvalue = math.max(0, prev - self2.Primary_TFA.RecoilLUT["out"].increase) + self:SetRecoilOutProgress(newvalue) + self:SetRecoilLoopWait(time + self2.Primary_TFA.RecoilLUT["loop"].wait) + + if newvalue ~= 0 then + return + end + + sub = self2.Primary_TFA.RecoilLUT["out"].increase - prev + end + + local newvalue = (self:GetRecoilLoopProgress() + self2.Primary_TFA.RecoilLUT["loop"].increase + sub) % 1 + self:SetRecoilLoopProgress(newvalue) + self:SetRecoilLoopWait(time + self2.Primary_TFA.RecoilLUT["loop"].wait) +end + +function SWEP:HasRecoilLUT() + return self.Primary_TFA.RecoilLUT ~= nil +end + +do + local function linear(a) return a end + + local function getfn(input) + return isfunction(input.func) and input.func or + input.func == "quintic" and TFA.Quintic or + input.func == "cubic" and TFA.Cubic or + input.func == "cosine" and TFA.Cosine or + input.func == "sinusine" and TFA.Sinusine or + linear + end + + function SWEP:GetRecoilLUTAngle() + if not self:GetRecoilThink() then + return Angle() + end + + local self2 = self:GetTable() + local isp = 1 - self:GetIronSightsProgress() * self2.GetStatL(self, "Primary.RecoilLUT_IronSightsMult") + + if not self:GetRecoilLoop() then + -- currently, we only playing IN animation + + local t = getfn(self2.Primary_TFA.RecoilLUT["in"])(self:GetRecoilInProgress()) + + local pitch = TFA.tbezier(t, self2.Primary_TFA.RecoilLUT["in"].points_p) + local yaw = TFA.tbezier(t, self2.Primary_TFA.RecoilLUT["in"].points_y) + + return Angle(pitch * isp, yaw * isp) + end + + local out = getfn(self2.Primary_TFA.RecoilLUT["out"])(self:GetRecoilOutProgress()) + local loop = getfn(self2.Primary_TFA.RecoilLUT["loop"])(self:GetRecoilLoopProgress()) + + local pitch = TFA.tbezier(loop, self2.Primary_TFA.RecoilLUT["loop"].points_p) + local yaw = TFA.tbezier(loop, self2.Primary_TFA.RecoilLUT["loop"].points_y) + + if out ~= 0 then + -- cooling out + self2.Primary_TFA.RecoilLUT["out"].points_p[1] = pitch + self2.Primary_TFA.RecoilLUT["out"].points_y[1] = yaw + + local pitch2 = TFA.tbezier(out, self2.Primary_TFA.RecoilLUT["out"].points_p) + local yaw2 = TFA.tbezier(out, self2.Primary_TFA.RecoilLUT["out"].points_y) + + return Angle(pitch2 * isp, yaw2 * isp) + end + + return Angle(pitch * isp, yaw * isp) + end +end + +local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy") + +function SWEP:GetAimVector() + return self:GetAimAngle():Forward() +end + +function SWEP:GetAimAngle() + local ang = self:GetOwner():GetAimVector():Angle() + + if sv_tfa_recoil_legacy:GetBool() and self:GetOwner():IsPlayer() then + ang:Add(self:GetOwner():GetViewPunchAngles()) + elseif self:HasRecoilLUT() then + ang:Add(self:GetRecoilLUTAngle()) + else + ang.p = ang.p + self:GetViewPunchP() + ang.y = ang.y + self:GetViewPunchY() + end + + ang:Normalize() + return ang +end + +function SWEP:EmitSoundNet(sound, ifp, shouldPause) + if ifp == nil then ifp = IsFirstTimePredicted() end + if not ifp then return end + + if shouldPause == nil then shouldPause = false end + + if CLIENT and sp then return end + + if CLIENT or sp then + self:EmitSound(sound, nil, nil, nil, nil, shouldPause and SND_SHOULDPAUSE or SND_NOFLAGS) + return + end + + local filter = RecipientFilter() + + filter:AddPAS(self:GetPos()) + + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + filter:RemovePlayer(self:GetOwner()) + end + + if filter:GetCount() == 0 then return end + + net.Start("tfaSoundEvent", true) + net.WriteEntity(self) + net.WriteString(sound) + net.WriteBool(shouldPause) + net.Send(filter) +end + +function SWEP:StopSoundNet(sound, ifp) + if ifp == nil then ifp = IsFirstTimePredicted() end + if not ifp then return end + + if CLIENT and sp then return end + + if CLIENT or sp then + self:StopSound(sound) + return + end + + local filter = RecipientFilter() + + filter:AddPAS(self:GetPos()) + + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + filter:RemovePlayer(self:GetOwner()) + end + + if filter:GetCount() == 0 then return end + + net.Start("tfaSoundEventStop", true) + net.WriteEntity(self) + net.WriteString(sound) + net.Send(filter) +end + +function SWEP:IsFireModeAutomatic(fm) + local fmn + + if type(fm) == "string" then + fmn = fm + elseif type(fm) == "number" then + fmn = self:GetStatL("FireModes")[fm] + else + fmn = self:GetStatL("FireModes")[self:GetFireMode()] + end + + return self:GetStatL("FireModesAutomatic." .. fmn) == true +end + +function SWEP:ShouldEmitGunfireLoop(tableName) + if self:IsFireModeAutomatic() then return true end + if not self:GetStatL((tableName or "Primary") .. ".LoopSoundAutoOnly", false) then return true end + + return false +end + +function SWEP:ShouldPlayLoopShootAnim() + if self:IsFireModeAutomatic() then return true end + if not self:GetStatL("ShootAnimationLoopAutoOnly", true) then return true end + + return false +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/viewmodel.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/viewmodel.lua new file mode 100644 index 0000000..6ea52a7 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/common/viewmodel.lua @@ -0,0 +1,76 @@ +local vector_origin = Vector() +local angle_zero = Angle() + +SWEP.WeaponLength = 0 + +SWEP.NearWallVector = Vector(0.091287083923817, -0.4564354121685, -0.18257416784763) +SWEP.NearWallVectorADS = Vector(0, 0, 0) + +SWEP.ViewModelPunchPitchMultiplier = 0.5 +SWEP.ViewModelPunchPitchMultiplier_IronSights = 0.09 + +SWEP.ViewModelPunch_MaxVertialOffset = 3 +SWEP.ViewModelPunch_MaxVertialOffset_IronSights = 1.95 +SWEP.ViewModelPunch_VertialMultiplier = 1 +SWEP.ViewModelPunch_VertialMultiplier_IronSights = 0.25 + +SWEP.ViewModelPunchYawMultiplier = 0.6 +SWEP.ViewModelPunchYawMultiplier_IronSights = 0.25 + +local onevec = Vector(1, 1, 1) + +local function RBP(vm) + local bc = vm:GetBoneCount() + if not bc or bc <= 0 then return end + + for i = 0, bc do + vm:ManipulateBoneScale(i, onevec) + vm:ManipulateBoneAngles(i, angle_zero) + vm:ManipulateBonePosition(i, vector_origin) + end +end + +function SWEP:ApplyViewModelModifications() + local self2 = self:GetTable() + if not self2.VMIV(self) then return end + + local vm = self2.OwnerViewModel + + local bgcount = #(vm:GetBodyGroups() or {}) + local ViewModelBodygroups = self2.GetStatRawL(self, "ViewModelBodygroups") + local bgt = ViewModelBodygroups or self2.Bodygroups or {} + + for i = 0, bgcount - 1 do + vm:SetBodygroup(i, bgt[i] or 0) + end + + local skinind = self2.GetStatL(self, "Skin") + + if skinind and isnumber(skinind) then + vm:SetSkin(skinind) + self:SetSkin(skinind) + end + + self2.ClearMaterialCache(self) +end + +function SWEP:ResetViewModelModifications() + local self2 = self:GetTable() + if not self2.VMIV(self) then return end + + local vm = self2.OwnerViewModel + + RBP(vm) + + vm:SetSkin(0) + + local matcount = #(vm:GetMaterials() or {}) + + for i = 0, matcount do + vm:SetSubMaterial(i, "") + end + + for i = 0, #(vm:GetBodyGroups() or {}) - 1 do + vm:SetBodygroup(i, 0) + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/init.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/init.lua new file mode 100644 index 0000000..f32b333 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/init.lua @@ -0,0 +1,131 @@ +include("shared.lua") + +include("common/ai_translations.lua") +include("common/anims.lua") +include("common/autodetection.lua") +include("common/utils.lua") +include("common/stat.lua") +include("common/attachments.lua") +include("common/bullet.lua") +include("common/effects.lua") +include("common/calc.lua") +include("common/akimbo.lua") +include("common/events.lua") +include("common/nzombies.lua") +include("common/ttt.lua") +include("common/viewmodel.lua") +include("common/skins.lua") + +AddCSLuaFile("common/ai_translations.lua") +AddCSLuaFile("common/anims.lua") +AddCSLuaFile("common/autodetection.lua") +AddCSLuaFile("common/utils.lua") +AddCSLuaFile("common/stat.lua") +AddCSLuaFile("common/attachments.lua") +AddCSLuaFile("common/bullet.lua") +AddCSLuaFile("common/effects.lua") +AddCSLuaFile("common/calc.lua") +AddCSLuaFile("common/akimbo.lua") +AddCSLuaFile("common/events.lua") +AddCSLuaFile("common/nzombies.lua") +AddCSLuaFile("common/ttt.lua") +AddCSLuaFile("common/viewmodel.lua") +AddCSLuaFile("common/skins.lua") + +AddCSLuaFile("shared.lua") +AddCSLuaFile("cl_init.lua") + +AddCSLuaFile("client/effects.lua") +AddCSLuaFile("client/viewbob.lua") +AddCSLuaFile("client/hud.lua") +AddCSLuaFile("client/mods.lua") +AddCSLuaFile("client/laser.lua") +AddCSLuaFile("client/fov.lua") +AddCSLuaFile("client/flashlight.lua") +AddCSLuaFile("client/viewmodel.lua") +AddCSLuaFile("client/bobcode.lua") + +SWEP.Weight = 60 -- Decides whether we should switch from/to this +SWEP.AutoSwitchTo = true -- Auto switch to +SWEP.AutoSwitchFrom = true -- Auto switch from + +local sv_tfa_npc_burst = GetConVar("sv_tfa_npc_burst") + +function SWEP:NPCShoot_Primary() + if self:Clip1() <= 0 and self:GetMaxClip1() > 0 then + self:GetOwner():SetSchedule(SCHED_RELOAD) + return + end + + return self:PrimaryAttack() +end + +function SWEP:GetNPCRestTimes() + if sv_tfa_npc_burst:GetBool() or self:GetStatL("NPCBurstOverride", false) then + return self:GetStatL("NPCMinRest", self:GetFireDelay()), self:GetStatL("NPCMaxRest", self:GetFireDelay() * 2) + end + + if self:GetStatL("Primary.Automatic") then + return 0, 0 + else + return self:GetFireDelay(), self:GetFireDelay() * 2 + end +end + +function SWEP:GetNPCBurstSettings() + if sv_tfa_npc_burst:GetBool() or self:GetStatL("NPCBurstOverride", false) then + return self:GetStatL("NPCMinBurst", 1), self:GetStatL("NPCMinBurst", 6), self:GetStatL("NPCBurstDelay", self:GetFireDelay() * self:GetMaxBurst()) + end + + if self:GetMaxClip1() > 0 then + local burst = self:GetMaxBurst() + local value = math.ceil(self:Clip1() / burst) + local delay = self:GetFireDelay() * burst + + if self:GetStatL("Primary.Automatic") then + return math.min(4, value), math.min(12, value), delay + else + return 1, math.min(4, value), delay + end + else + return 1, 30, self:GetFireDelay() * self:GetMaxBurst() + end +end + +function SWEP:GetNPCBulletSpread() + return 1 -- we handle this manually, in calculate cone, recoil and shootbullet +end + +function SWEP:CanBePickedUpByNPCs() + return true +end + +local sv_tfa_npc_randomize_atts = GetConVar("sv_tfa_npc_randomize_atts") + +function SWEP:Equip(...) + local owner = self:GetOwner() + + if owner:IsNPC() then + self.IsNPCOwned = true + + if not self.IsFirstEquip and sv_tfa_npc_randomize_atts:GetBool() then + self:RandomizeAttachments(true) + end + + local function closure() + self:NPCWeaponThinkHook() + end + + hook.Add("TFA_NPCWeaponThink", self, function() + ProtectedCall(closure) + end) + else + self.IsNPCOwned = false + end + + self.IsFirstEquip = true + self.OwnerViewModel = nil + self:EquipTTT(...) +end + +TFA.FillMissingMetaValues(SWEP) diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/shared.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/shared.lua new file mode 100644 index 0000000..5a48070 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_gun_base/shared.lua @@ -0,0 +1,2480 @@ +SWEP.Category = "" --The category. Please, just choose something generic or something I've already done if you plan on only doing like one swep. +SWEP.Author = "" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "" +SWEP.DrawCrosshair = true +SWEP.DrawCrosshairIronSights = false +SWEP.ViewModelFOV = 65 +SWEP.ViewModelFlip = false +SWEP.Skin = 0 --Viewmodel skin +SWEP.Spawnable = false +SWEP.IsTFAWeapon = true + +SWEP.LoopedReload = false +SWEP.LoopedReloadInsertAmount = 1 +SWEP.ShotgunEmptyAnim = false +SWEP.ShotgunEmptyAnim_Shell = true +SWEP.ShotgunStartAnimShell = false --shotgun start anim inserts shell + +SWEP.Secondary.IronSightsEnabled = true +SWEP.Secondary.ScopeZoom = 1 +SWEP.Secondary.ScopeScreenScale = 0.392592592592592 + +SWEP.RegularMoveSpeedMultiplier = 1 + +SWEP.FireSoundAffectedByClipSize = true + +SWEP.Primary.Damage = -1 +SWEP.Primary.DamageTypeHandled = true --true will handle damagetype in base +SWEP.Primary.NumShots = 1 +SWEP.Primary.Force = -1 +SWEP.Primary.Knockback = -1 +SWEP.Primary.Recoil = 1 +SWEP.Primary.RPM = 600 +SWEP.Primary.RPM_Semi = -1 +SWEP.Primary.RPM_Burst = -1 +SWEP.Primary.StaticRecoilFactor = 0.5 +SWEP.Primary.KickUp = 0.5 +SWEP.Primary.KickDown = 0.5 +SWEP.Primary.KickRight = 0.5 +SWEP.Primary.KickHorizontal = 0.5 +SWEP.Primary.DamageType = nil +SWEP.Primary.Ammo = "smg1" +SWEP.Primary.AmmoConsumption = 1 +SWEP.Primary.Spread = 0 +SWEP.Primary.DisplaySpread = true +SWEP.Primary.SpreadMultiplierMax = -1 --How far the spread can expand when you shoot. +SWEP.Primary.SpreadIncrement = -1 --What percentage of the modifier is added on, per shot. +SWEP.Primary.SpreadRecovery = -1 --How much the spread recovers, per second. +SWEP.Primary.SpreadRecoveryDelay = 0 +SWEP.Primary.IronAccuracy = 0 +SWEP.Primary.Range = -1--1200 +SWEP.Primary.RangeFalloff = -1--0.5 +SWEP.Primary.PenetrationMultiplier = 1 +SWEP.Primary.DryFireDelay = nil + +--[[Actual clientside values]]-- + +SWEP.DrawAmmo = true -- Should draw the default HL2 ammo counter +SWEP.DrawWeaponInfoBox = false -- Should draw the weapon info box +SWEP.BounceWeaponIcon = false -- Should the weapon icon bounce? + +local sv_tfa_jamming = GetConVar("sv_tfa_jamming") +local sv_tfa_jamming_mult = GetConVar("sv_tfa_jamming_mult") +local sv_tfa_jamming_factor = GetConVar("sv_tfa_jamming_factor") +local sv_tfa_jamming_factor_inc = GetConVar("sv_tfa_jamming_factor_inc") + +-- RP owners always like realism, so this feature might be something they like. Enable it for them! +TFA_AUTOJAMMING_ENABLED = string.find(engine.ActiveGamemode(), 'rp') or + string.find(engine.ActiveGamemode(), 'roleplay') or + string.find(engine.ActiveGamemode(), 'nutscript') or + string.find(engine.ActiveGamemode(), 'serious') or + TFA_ENABLE_JAMMING_BY_DEFAULT + +SWEP.CanJam = tobool(TFA_AUTOJAMMING_ENABLED) + +SWEP.JamChance = 0.04 +SWEP.JamFactor = 0.06 + +SWEP.BoltAction = false --Unscope/sight after you shoot? +SWEP.BoltAction_Forced = false +SWEP.Scoped = false --Draw a scope overlay? +SWEP.ScopeOverlayThreshold = 0.875 --Percentage you have to be sighted in to see the scope. +SWEP.BoltTimerOffset = 0.25 --How long you stay sighted in after shooting, with a bolt action. +SWEP.ScopeScale = 0.5 +SWEP.ReticleScale = 0.7 + +SWEP.MuzzleAttachment = "1" +SWEP.ShellAttachment = "2" + +SWEP.MuzzleFlashEnabled = true +SWEP.MuzzleFlashEffect = nil +SWEP.MuzzleFlashEffectSilenced = "tfa_muzzleflash_silenced" +SWEP.CustomMuzzleFlash = true + +SWEP.EjectionSmokeEnabled = true + +SWEP.LuaShellEject = false +SWEP.LuaShellEjectDelay = 0 +SWEP.LuaShellEffect = nil --Defaults to blowback + +SWEP.SmokeParticle = nil --Smoke particle (ID within the PCF), defaults to something else based on holdtype + +SWEP.StatusLengthOverride = {} --Changes the status delay of a given animation; only used on reloads. Otherwise, use SequenceLengthOverride or one of the others +SWEP.SequenceLengthOverride = {} --Changes both the status delay and the nextprimaryfire of a given animation +SWEP.SequenceTimeOverride = {} --Like above but changes animation length to a target +SWEP.SequenceRateOverride = {} --Like above but scales animation length rather than being absolute + +SWEP.BlowbackEnabled = false --Enable Blowback? +SWEP.BlowbackVector = Vector(0, -1, 0) --Vector to move bone relative to bone orientation. +SWEP.BlowbackCurrentRoot = 0 --Amount of blowback currently, for root +SWEP.BlowbackCurrent = 0 --Amount of blowback currently, for bones +SWEP.BlowbackBoneMods = nil --Viewmodel bone mods via SWEP Creation Kit +SWEP.Blowback_Only_Iron = true --Only do blowback on ironsights +SWEP.Blowback_PistolMode = false --Do we recover from blowback when empty? +SWEP.BlowbackAllowAnimation = false + +SWEP.ProceduralHolsterEnabled = nil +SWEP.ProceduralHolsterTime = 0.3 +SWEP.ProceduralHolsterPosition = Vector(3, 0, -5) +SWEP.ProceduralHolsterAngle = Vector(-40, -30, 10) + +SWEP.IsProceduralReloadBased = false --Do we reload using lua instead of a .mdl animation +SWEP.ProceduralReloadTime = 1 --Time to take when procedurally reloading, including transition in (but not out) + +SWEP.Blowback_PistolMode_Disabled = { + [ACT_VM_RELOAD] = true, + [ACT_VM_RELOAD_EMPTY] = true, + [ACT_VM_DRAW_EMPTY] = true, + [ACT_VM_IDLE_EMPTY] = true, + [ACT_VM_HOLSTER_EMPTY] = true, + [ACT_VM_DRYFIRE] = true, + [ACT_VM_FIDGET] = true, + [ACT_VM_FIDGET_EMPTY] = true +} + +SWEP.Blowback_Shell_Enabled = true +SWEP.Blowback_Shell_Effect = "ShellEject" + +SWEP.Secondary.Ammo = "" +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.AmmoConsumption = 0 + +SWEP.Sights_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = lua but continue idle, Lua = stop mdl animation +SWEP.Sprint_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.Walk_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.Customize_Mode = TFA.Enum.LOCOMOTION_LUA -- ANI = mdl, HYBRID = ani + lua, Lua = lua only +SWEP.SprintFOVOffset = 5 +SWEP.Idle_Mode = TFA.Enum.IDLE_BOTH --TFA.Enum.IDLE_DISABLED = no idle, TFA.Enum.IDLE_LUA = lua idle, TFA.Enum.IDLE_ANI = mdl idle, TFA.Enum.IDLE_BOTH = TFA.Enum.IDLE_ANI + TFA.Enum.IDLE_LUA +SWEP.Idle_Blend = 0.25 --Start an idle this far early into the end of a transition +SWEP.Idle_Smooth = 0.05 --Start an idle this far early into the end of another animation + +SWEP.IronSightTime = 0.3 +SWEP.IronSightsSensitivity = 1 + +SWEP.InspectPosDef = Vector(9.779, -11.658, -2.241) +SWEP.InspectAngDef = Vector(24.622, 42.915, 15.477) + +SWEP.SprintViewModelPosition = Vector(0,0,0) +SWEP.SprintViewModelAngle = Vector(0,0,0) +SWEP.AllowSprintAttack = false --Shoot while sprinting? + +SWEP.CrouchViewModelPosition = Vector(0, -1, -.5) +SWEP.CrouchViewModelAngle = Vector(0, 0, 0) + +SWEP.Primary.RecoilLUT_IronSightsMult = 0.5 +SWEP.Primary.RecoilLUT_AnglePunchMult = 0.25 +SWEP.Primary.RecoilLUT_ViewPunchMult = 1 + +SWEP.EventTable = {} + +SWEP.RTMaterialOverride = nil +SWEP.RTOpaque = false +SWEP.RTCode = nil--function(self) return end +SWEP.RTBGBlur = true + +SWEP.ViewModelPosition = Vector(0,0,0) +SWEP.ViewModelAngle = Vector(0,0,0) +SWEP.CameraOffset = Angle(0, 0, 0) +SWEP.AdditiveViewModelPosition = true + +SWEP.AllowIronSightsDoF = true + +SWEP.Primary.DisplayFalloff = true + +SWEP.IronAnimation = { + --[[ + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Idle_To_Iron", --Number for act, String/Number for sequence + ["value_empty"] = "Idle_To_Iron_Dry", + ["transition"] = true + }, --Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Idle_Iron", --Number for act, String/Number for sequence + ["value_empty"] = "Idle_Iron_Dry" + }, --Looping Animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Iron_To_Idle", --Number for act, String/Number for sequence + ["value_empty"] = "Iron_To_Idle_Dry", + ["transition"] = true + }, --Outward transition + ["shoot"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Fire_Iron", --Number for act, String/Number for sequence + ["value_last"] = "Fire_Iron_Last", + ["value_empty"] = "Fire_Iron_Dry" + } --What do you think + ]]-- +} + +SWEP.SprintAnimation = { + --[[ + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Idle_to_Sprint", --Number for act, String/Number for sequence + ["value_empty"] = "Idle_to_Sprint_Empty", + ["transition"] = true + }, --Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Sprint_", --Number for act, String/Number for sequence + ["value_empty"] = "Sprint_Empty_", + ["is_idle"] = true + },--looping animation + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "Sprint_to_Idle", --Number for act, String/Number for sequence + ["value_empty"] = "Sprint_to_Idle_Empty", + ["transition"] = true + } --Outward transition + ]]-- +} + +SWEP.ShootAnimation = {--[[ + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "shoot_loop_start", --Number for act, String/Number for sequence + ["value_is"] = "shoot_loop_iron_start" + }, + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "shoot_loop", --Number for act, String/Number for sequence + ["value_is"] = "shoot_loop_iron", + ["is_idle"] = true + }, + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_SEQ, --Sequence or act + ["value"] = "shoot_loop_end", --Number for act, String/Number for sequence + ["value_is"] = "shoot_loop_iron_end" + }]]-- +} + +SWEP.FirstDeployEnabled = nil--Force first deploy enabled + +--[[Dont edit under this unless you know what u r doing]] + +SWEP.IronSightsProgress = 0 +SWEP.CLIronSightsProgress = 0 +SWEP.SprintProgress = 0 +SWEP.WalkProgress = 0 +SWEP.SpreadRatio = 0 +SWEP.CrouchingRatio = 0 +SWEP.SmokeParticles = { + pistol = "tfa_ins2_weapon_muzzle_smoke", + smg = "tfa_ins2_weapon_muzzle_smoke", + grenade = "tfa_ins2_weapon_muzzle_smoke", + ar2 = "tfa_ins2_weapon_muzzle_smoke", + shotgun = "tfa_ins2_weapon_muzzle_smoke", + rpg = "tfa_ins2_weapon_muzzle_smoke", + physgun = "tfa_ins2_weapon_muzzle_smoke", + crossbow = "tfa_ins2_weapon_muzzle_smoke", + melee = "tfa_ins2_weapon_muzzle_smoke", + slam = "tfa_ins2_weapon_muzzle_smoke", + normal = "tfa_ins2_weapon_muzzle_smoke", + melee2 = "tfa_ins2_weapon_muzzle_smoke", + knife = "tfa_ins2_weapon_muzzle_smoke", + duel = "tfa_ins2_weapon_muzzle_smoke", + camera = "tfa_ins2_weapon_muzzle_smoke", + magic = "tfa_ins2_weapon_muzzle_smoke", + revolver = "tfa_ins2_weapon_muzzle_smoke", + silenced = "tfa_ins2_weapon_muzzle_smoke" +} +--[[ SWEP.SmokeParticles = { + pistol = "weapon_muzzle_smoke", + smg = "weapon_muzzle_smoke", + grenade = "weapon_muzzle_smoke", + ar2 = "weapon_muzzle_smoke", + shotgun = "weapon_muzzle_smoke_long", + rpg = "weapon_muzzle_smoke_long", + physgun = "weapon_muzzle_smoke_long", + crossbow = "weapon_muzzle_smoke_long", + melee = "weapon_muzzle_smoke", + slam = "weapon_muzzle_smoke", + normal = "weapon_muzzle_smoke", + melee2 = "weapon_muzzle_smoke", + knife = "weapon_muzzle_smoke", + duel = "weapon_muzzle_smoke", + camera = "weapon_muzzle_smoke", + magic = "weapon_muzzle_smoke", + revolver = "weapon_muzzle_smoke_long", + silenced = "weapon_muzzle_smoke" +}--]] +--[[ +SWEP.SmokeParticles = { + pistol = "smoke_trail_controlled", + smg = "smoke_trail_tfa", + grenade = "smoke_trail_tfa", + ar2 = "smoke_trail_tfa", + shotgun = "smoke_trail_wild", + rpg = "smoke_trail_tfa", + physgun = "smoke_trail_tfa", + crossbow = "smoke_trail_tfa", + melee = "smoke_trail_tfa", + slam = "smoke_trail_tfa", + normal = "smoke_trail_tfa", + melee2 = "smoke_trail_tfa", + knife = "smoke_trail_tfa", + duel = "smoke_trail_tfa", + camera = "smoke_trail_tfa", + magic = "smoke_trail_tfa", + revolver = "smoke_trail_tfa", + silenced = "smoke_trail_controlled" +} +]]-- + +SWEP.Inspecting = false +SWEP.InspectingProgress = 0 +SWEP.LuaShellRequestTime = -1 +SWEP.BobScale = 0 +SWEP.SwayScale = 0 +SWEP.BoltDelay = 1 +SWEP.ProceduralHolsterProgress = 0 +SWEP.BurstCount = 0 +SWEP.DefaultFOV = 90 +SWEP.m_WeaponDeploySpeed = 255 + +--[[ Localize Functions ]] +local function l_Lerp(v, f, t) + return f + (t - f) * v +end +local l_mathApproach = math.Approach +local l_CT = CurTime +--[[Frequently Reused Local Vars]] +local stat --Weapon status +local ct = 0--Curtime, frametime, real frametime +local sp = game.SinglePlayer() --Singleplayer +local developer = GetConVar("developer") + +function SWEP:NetworkVarTFA(typeIn, nameIn) + if not self.TrackedDTTypes then + self.TrackedDTTypes = { + Angle = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + Bool = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + Entity = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + Float = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + Int = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + String = {0, 1, 2, 3}, + Vector = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, + } + + if istable(self.dt) then + local meta = getmetatable(self.dt) + + if istable(meta) and isfunction(meta.__index) then + local name, value = debug.getupvalue(meta.__index, 1) + + if name == "datatable" and istable(value) then + for variableName, variableData in SortedPairs(value) do + if istable(variableData) and isstring(variableData.typename) and isnumber(variableData.index) then + local trackedData = self.TrackedDTTypes[variableData.typename] + + if trackedData then + table.RemoveByValue(trackedData, variableData.index) + end + end + end + end + end + end + end + + if not self.TrackedDTTypes[typeIn] then + error("Variable type " .. typeIn .. " is invalid") + end + + local gatherindex = table.remove(self.TrackedDTTypes[typeIn], 1) + + if gatherindex then + (self["NetworkVar_TFA"] or self["NetworkVar"])(self, typeIn, gatherindex, nameIn) + return + end + + local get = self["GetNW2" .. typeIn] + local set = self["SetNW2" .. typeIn] + + self["Set" .. nameIn] = function(_self, value) + set(_self, nameIn, value) + end + + self["Get" .. nameIn] = function(_self, def) + return get(_self, nameIn, def) + end + + if developer:GetBool() then + print("[TFA Base] developer 1: Variable " .. nameIn .. " can not use DTVars due to " .. typeIn .. " index exhaust") + end +end + +--[[ +Function Name: SetupDataTables +Syntax: Should not be manually called. +Returns: Nothing. Simple sets up DTVars to be networked. +Purpose: Networking. +]] +function SWEP:SetupDataTables() + self.TrackedDTTypes = nil + self.NetworkVar_TFA = self.NetworkVar + + --self:NetworkVarTFA("Bool", "IronSights") + self:NetworkVarTFA("Bool", "IronSightsRaw") + self:NetworkVarTFA("Bool", "Sprinting") + self:NetworkVarTFA("Bool", "Silenced") + self:NetworkVarTFA("Bool", "ReloadLoopCancel") + self:NetworkVarTFA("Bool", "Walking") + self:NetworkVarTFA("Bool", "Customizing") + + self.GetShotgunCancel = self.GetReloadLoopCancel + self.SetShotgunCancel = self.SetReloadLoopCancel + + self:NetworkVarTFA("Bool", "FlashlightEnabled") + self:NetworkVarTFA("Bool", "Jammed") + self:NetworkVarTFA("Bool", "FirstDeployEvent") + self:NetworkVarTFA("Bool", "IsCyclingSafety") + self:NetworkVarTFA("Bool", "SafetyCycleAnimated") + self:NetworkVarTFA("Bool", "HasPlayedEmptyClick") + + self:NetworkVarTFA("Float", "StatusEnd") + self:NetworkVarTFA("Float", "NextIdleAnim") + self:NetworkVarTFA("Float", "NextLoopSoundCheck") + self:NetworkVarTFA("Float", "JamFactor") + self:NetworkVarTFA("Float", "EventTimer") + self:NetworkVarTFA("Float", "LastGunFire") + + self:NetworkVarTFA("Int", "StatusRaw") + self:NetworkVarTFA("Int", "FireMode") + self:NetworkVarTFA("Int", "LastActivity") + self:NetworkVarTFA("Int", "BurstCount") + self:NetworkVarTFA("Int", "ShootStatus") + self:NetworkVarTFA("Int", "EventStatus1") + self:NetworkVarTFA("Int", "EventStatus2") + self:NetworkVarTFA("Int", "EventStatus3") + self:NetworkVarTFA("Int", "EventStatus4") + self:NetworkVarTFA("Int", "EventStatus5") + self:NetworkVarTFA("Int", "EventStatus6") + self:NetworkVarTFA("Int", "EventStatus7") + self:NetworkVarTFA("Int", "EventStatus8") + + self:NetworkVarTFA("Bool", "RecoilLoop") + self:NetworkVarTFA("Bool", "RecoilThink") + + self:NetworkVarTFA("Float", "RecoilInProgress") + self:NetworkVarTFA("Float", "RecoilInWait") + self:NetworkVarTFA("Float", "RecoilLoopProgress") + self:NetworkVarTFA("Float", "RecoilLoopWait") + self:NetworkVarTFA("Float", "RecoilOutProgress") + + if not self.get_event_status_lut then + self.get_event_status_lut = {} + self.set_event_status_lut = {} + + for i = 1, 8 do + self.get_event_status_lut[i] = self['GetEventStatus' .. i] + self.set_event_status_lut[i] = self['SetEventStatus' .. i] + end + end + + self:NetworkVarTFA("Entity", "SwapTarget") + + self:NetworkVarNotify("Customizing", self.CustomizingUpdated) + + self:NetworkVarTFA("Float", "SpreadRatio") + self:NetworkVarTFA("Float", "IronSightsProgress") + self:NetworkVarTFA("Float", "ProceduralHolsterProgress") + self:NetworkVarTFA("Float", "InspectingProgress") + self:NetworkVarTFA("Float", "JumpRatio") + self:NetworkVarTFA("Float", "CrouchingRatio") + + self:NetworkVarTFA("Float", "ViewPunchBuild") + self:NetworkVarTFA("Float", "ViewPunchP") + self:NetworkVarTFA("Float", "ViewPunchY") + + self:NetworkVarTFA("Float", "SprintProgress") + self:NetworkVarTFA("Float", "WalkProgress") + self:NetworkVarTFA("Float", "LastVelocity") + + self:NetworkVarTFA("Int", "AnimCycle") + + self:NetworkVarTFA("Vector", "QueuedRecoil") + self:NetworkVarTFA("Float", "PrevRecoilAngleTime") + self:NetworkVarTFA("Angle", "PrevRecoilAngle") + + self:NetworkVarTFA("Bool", "CustomizeUpdated") + self:NetworkVarTFA("Bool", "IronSightsOldFinal") + + function self.NetworkVar(self2, typeIn, slotIn, nameIn) + return self2:NetworkVarTFA(typeIn, nameIn) + end + + self:NetworkVarTFA("Float", "StatusStart") + self:NetworkVarTFA("Float", "LastSafetyShoot") + + self:NetworkVarTFA("Int", "LastSequence") + self:NetworkVarTFA("Int", "DownButtons") + self:NetworkVarTFA("Int", "LastPressedButtons") + + self:NetworkVarTFA("Float", "LastReloadPressed") + + self:NetworkVarTFA("Float", "LastIronSightsPressed") + + self.GetStatus = self.GetStatusRaw + self.GetIronSights = self.GetIronSightsOldFinal + self.GetIronSightsDirect = self.GetIronSightsOldFinal + + self:NetworkVarTFA("Bool", "IsFirstDeploy") + self:NetworkVarTFA("Float", "LastDeployAnim") + + hook.Run("TFA_SetupDataTables", self) +end + +function SWEP:GetStatusProgress(unpredicted) + if self:GetStatus() == TFA.Enum.STATUS_IDLE then return 1 end + local StatusStart = self:GetStatusStart() + + if StatusStart <= 0 then return end + local StatusEnd = self:GetStatusEnd() + + if StatusStart > StatusEnd then return 1 end + + local time = unpredicted and (l_CT() + (self.CurTimePredictionAdvance or 0)) or l_CT() + if StatusStart >= time then return 0 end + if StatusEnd <= time then return 1 end + + return (time - StatusStart) / (StatusEnd - StatusStart) +end + +function SWEP:GetStatusProgressTime(unpredicted) + if self:GetStatus() == TFA.Enum.STATUS_IDLE then return 0 end + local StatusStart = self:GetStatusStart() + + if StatusStart <= 0 then return end + local StatusEnd = self:GetStatusEnd() + + if StatusStart > StatusEnd then return 0 end + + local time = unpredicted and (l_CT() + (self.CurTimePredictionAdvance or 0)) or l_CT() + if StatusEnd <= time then return 0 end + + return StatusEnd - time +end + +function SWEP:SetStatus(statusIn, timeOn) + self:SetStatusRaw(statusIn) + self:SetStatusStart(l_CT()) + + if timeOn ~= nil then + self:SetStatusEnd(timeOn) + end +end + +function SWEP:ScheduleStatus(statusIn, timeFor) + self:SetStatusRaw(statusIn) + local time = l_CT() + self:SetStatusStart(time) + self:SetStatusEnd(time + timeFor) +end + +function SWEP:ExtendStatus(timeFor) + self:SetStatusEnd(self:GetStatusEnd() + timeFor) +end + +function SWEP:ExtendStatusTo(timeFor) + self:SetStatusEnd(math.max(self:GetStatusEnd(), timeFor)) +end + +--[[ +Function Name: Initialize +Syntax: Should not be normally called. +Notes: Called after actual SWEP code, but before deploy, and only once. +Returns: Nothing. Sets the intial values for the SWEP when it's created. +Purpose: Standard SWEP Function +]] + +local PistolHoldTypes = { + ["pistol"] = true, + ["357"] = true, + ["revolver"] = true +} +local MeleeHoldTypes = { + ["melee"] = true, + ["melee2"] = true, + ["knife"] = true +} + +local patch_blacklist + +do + local string_sub = string.sub + + function patch_blacklist(input, structure_version) + local target = {} + + for key in pairs(input) do + target[TFA.RemapStatPath(key, TFA.LatestDataVersion, structure_version)] = true + end + + table.Empty(input) + + setmetatable(input, { + __index = target, + __newindex = function(_, key, value) + target[TFA.RemapStatPath(key, TFA.LatestDataVersion, structure_version)] = value + end + }) + + return target + end +end + +function SWEP:Initialize() + local self2 = self:GetTable() + + if self2.HasInitialized then + ErrorNoHalt(debug.traceback("SWEP:Initialize was called out of order", 2) .. "\n") + return + end + + self2.HasInitialized = true + + --TFA.MigrateStructure(self, self2, self:GetClass(), true) + + hook.Run("TFA_PreInitialize", self) + + self2.DrawCrosshairDefault = self2.DrawCrosshair + + if not self2.BobScaleCustom or self2.BobScaleCustom <= 0 then + self2.BobScaleCustom = 1 + end + + TFA.UnfoldBaseClass(self2.Primary) + TFA.UnfoldBaseClass(self2.Secondary) + + TFA.UnfoldBaseClass(self2.Primary.PenetrationMaterials) + + TFA.UnfoldBaseClass(self2.AttachmentTableOverride) + + --[[for k, v in pairs(self2.AttachmentTableOverride) do + if istable(v) and k ~= "BaseClass" then + TFA.MigrateStructure(self, v, self:GetClass(), false) + end + end]] + + self2.Primary.BaseClass = nil + self2.Secondary.BaseClass = nil + + if self2.Primary.DisplayIronSpread == nil then + self2.Primary.DisplayIronSpread = self2.Primary.DisplaySpread + end + + self2.Primary_TFA = table.Copy(self2.Primary) + self2.Secondary_TFA = table.Copy(self2.Secondary) + + self2.BobScale = 0 + self2.SwayScaleCustom = 1 + self2.SwayScale = 0 + self2.SetSilenced(self, self2.Silenced or self2.DefaultSilenced) + self2.Silenced = self2.Silenced or self2.DefaultSilenced + self2.InitializeAnims(self) + self2.InitializeMaterialTable(self) + + self2.RunAutoDetection(self) + + table.Merge(self2.Primary, self2.Primary_TFA) + table.Merge(self2.Secondary, self2.Secondary_TFA) + + TFA.UnfoldBaseClass(self2.StatCache_Blacklist) + self2.StatCache_Blacklist_Real = patch_blacklist(self2.StatCache_Blacklist, self2.TFADataVersion) + + TFA.UnfoldBaseClass(self2.Attachments) + TFA.UnfoldBaseClass(self:GetStatRaw("ViewModelElements", TFA.LatestDataVersion)) + TFA.UnfoldBaseClass(self2.ViewModelBoneMods) + TFA.UnfoldBaseClass(self2.EventTable) + + TFA.UnfoldBaseClass(self2.Blowback_PistolMode_Disabled) + TFA.UnfoldBaseClass(self2.IronAnimation) + TFA.UnfoldBaseClass(self2.SprintAnimation) + TFA.UnfoldBaseClass(self2.ShootAnimation) + TFA.UnfoldBaseClass(self2.SmokeParticles) + + self2.ClearStatCache(self) + + self2.InitAttachments(self) + + self2.WorldModelBodygroups = self:GetStatRawL("WorldModelBodygroups") + self2.ViewModelBodygroups = self:GetStatRawL("ViewModelBodygroups") + + if not self:GetStatRawL("AimingDownSightsSpeedMultiplier") then + self:SetStatRawL("AimingDownSightsSpeedMultiplier", self:GetStatRawL("RegularMoveSpeedMultiplier") * 0.8) + end + + if isnumber(self2.GetStatL(self, "Skin")) then + self:SetSkin(self:GetStatL("Skin")) + end + + self:ResetAnimCycle() + self:ResetFirstDeploy() + + self:SetNextLoopSoundCheck(-1) + self:SetShootStatus(TFA.Enum.SHOOT_IDLE) + + if SERVER and self:GetOwner():IsNPC() then + local seq = self:GetOwner():LookupSequence("shootp1") + + if MeleeHoldTypes[self2.DefaultHoldType or self2.HoldType] then + if self:GetOwner():GetSequenceName(seq) == "shootp1" then + self:SetWeaponHoldType("melee2") + else + self:SetWeaponHoldType("melee") + end + elseif PistolHoldTypes[self2.DefaultHoldType or self2.HoldType] then + if self:GetOwner():GetSequenceName(seq) == "shootp1" then + self:SetWeaponHoldType("pistol") + else + self:SetWeaponHoldType("smg") + end + else + self:SetWeaponHoldType(self2.DefaultHoldType or self2.HoldType) + end + + if self:GetOwner():GetClass() == "npc_citizen" then + self:GetOwner():Fire( "DisableWeaponPickup", "", 0 ) + end + + self:GetOwner():SetKeyValue("spawnflags", "256") + + return + end + + hook.Run("TFA_Initialize", self) +end + +function SWEP:NPCWeaponThinkHook() + local self2 = self:GetTable() + + if not self:GetOwner():IsNPC() then + hook.Remove("TFA_NPCWeaponThink", self) + return + end + + self2.Think(self) +end + +--[[ +Function Name: Deploy +Syntax: self:Deploy() +Notes: Called after self:Initialize(). Called each time you draw the gun. This is also essential to clearing out old networked vars and resetting them. +Returns: True/False to allow quickswitch. Why not? You should really return true. +Purpose: Standard SWEP Function +]] + +function SWEP:Deploy() + local self2 = self:GetTable() + hook.Run("TFA_PreDeploy", self) + local ply = self:GetOwner() + + self2.IsNPCOwned = ply:IsNPC() + + if IsValid(ply) and IsValid(ply:GetViewModel()) then + self2.OwnerViewModel = ply:GetViewModel() + end + + if SERVER and self:GetStatL("FlashlightAttachment", 0) > 0 and IsValid(ply) and ply:IsPlayer() and ply:FlashlightIsOn() then + if not self:GetFlashlightEnabled() then + self:ToggleFlashlight(true) + end + + ply:Flashlight(false) + end + + if IsValid(ply) and ply:IsPlayer() then + if CLIENT then + self:ResetKeyBindHintAlpha() + elseif sp then + self:CallOnClient("ResetKeyBindHintAlpha") + end + end + + ct = l_CT() + + if not self2.VMIV(self) then + print("Invalid VM on owner: ") + print(ply) + + return + end + + if not self2.HasDetectedValidAnimations then + self:CacheAnimations() + end + + local _, tanim, ttype = self:ChooseDrawAnim() + + if sp then + self:CallOnClient("ChooseDrawAnim", "") + end + + local len = self:GetActivityLength(tanim, false, ttype) + + self:ScheduleStatus(TFA.Enum.STATUS_DRAW, len) + self:SetFirstDeployEvent(true) + + self:SetNextPrimaryFire(ct + len) + self:SetIronSightsRaw(false) + + if not self:GetStatL("PumpAction") then + self:SetReloadLoopCancel( false ) + end + + self:SetBurstCount(0) + + self:SetIronSightsProgress(0) + self:SetSprintProgress(0) + self:SetInspectingProgress(0) + self:SetProceduralHolsterProgress(0) + + if self:GetCustomizing() then + self:ToggleCustomize() + end + + self2.DefaultFOV = TFADUSKFOV or ( IsValid(ply) and ply:GetFOV() or 90 ) + + self:ApplyViewModelModifications() + self:CallOnClient("ApplyViewModelModifications") + + local v = hook.Run("TFA_Deploy", self) + + if v ~= nil then return v end + + return true +end + +--[[ +Function Name: Holster +Syntax: self:Holster( weapon entity to switch to ) +Notes: This is kind of broken. I had to manually select the new weapon using ply:ConCommand. Returning true is simply not enough. This is also essential to clearing out old networked vars and resetting them. +Returns: True/False to allow holster. Useful for animations. +Purpose: Standard SWEP Function +]] +function SWEP:Holster(target) + local self2 = self:GetTable() + + local v = hook.Run("TFA_PreHolster", self, target) + if v ~= nil then return v end + + if not IsValid(target) then + self2.InspectingProgress = 0 + + return true + end + + if not IsValid(self) then return end + ct = l_CT() + stat = self:GetStatus() + + if not TFA.Enum.HolsterStatus[stat] then + if stat == TFA.Enum.STATUS_RELOADING_WAIT and self:Clip1() <= self:GetStatL("Primary.ClipSize") and (not self:GetStatL("Primary.DisableChambering")) and (not self:GetStatL("LoopedReload")) then + self:ResetFirstDeploy() + + if sp then + self:CallOnClient("ResetFirstDeploy", "") + end + end + + local success, tanim, ttype = self:ChooseHolsterAnim() + + if IsFirstTimePredicted() then + self:SetSwapTarget(target) + end + + self:ScheduleStatus(TFA.Enum.STATUS_HOLSTER, success and self:GetActivityLength(tanim, false, ttype) or (self:GetStatL("ProceduralHolsterTime") / self:GetAnimationRate(ACT_VM_HOLSTER))) + + return false + elseif stat == TFA.Enum.STATUS_HOLSTER_READY or stat == TFA.Enum.STATUS_HOLSTER_FINAL then + self:ResetViewModelModifications() + + if IsValid(target) and target:IsWeapon() and not target.IsTFAWeapon then + if CLIENT then + self:ResetKeyBindHintAlpha(true) + elseif sp then + self:CallOnClient("ResetKeyBindHintAlpha", "true") + end + end + + return true + end +end + +function SWEP:FinishHolster() + local self2 = self:GetTable() + + self:CleanParticles() + + local v2 = hook.Run("TFA_Holster", self) + + if self:GetOwner():IsNPC() then return end + if v2 ~= nil then return v2 end + + if SERVER then + local ent = self:GetSwapTarget() + self:Holster(ent) + + if IsValid(ent) and ent:IsWeapon() then + self:GetOwner():SelectWeapon(ent:GetClass()) + + if ent.IsTFAWeapon then + ent:ApplyViewModelModifications() + ent:CallOnClient("ApplyViewModelModifications") + end + + self2.OwnerViewModel = nil + end + end +end + +--[[ +Function Name: OnRemove +Syntax: self:OnRemove() +Notes: Resets bone mods and cleans up. +Returns: Nil. +Purpose: Standard SWEP Function +]] +function SWEP:OnRemove() + local self2 = self:GetTable() + + if self2.CleanParticles then + self2.CleanParticles(self) + end + + if self2.ResetViewModelModifications then + self2.ResetViewModelModifications(self) + end + + return hook.Run("TFA_OnRemove", self) +end + +--[[ +Function Name: OnDrop +Syntax: self:OnDrop() +Notes: Resets bone mods and cleans up. +Returns: Nil. +Purpose: Standard SWEP Function +]] +function SWEP:OnDrop() + local self2 = self:GetTable() + + if self2.CleanParticles then + self2.CleanParticles(self) + end + + -- if self2.ResetViewModelModifications then + -- self:ResetViewModelModifications() + -- end + + return hook.Run("TFA_OnDrop", self) +end + +function SWEP:OwnerChanged() -- TODO: sometimes not called after switching weapon ??? + if not IsValid(self:GetOwner()) and self.ResetViewModelModifications then + self:ResetViewModelModifications() + end + + if SERVER then + if self.IsNPCOwned and (not IsValid(self:GetOwner()) or not self:GetOwner():IsNPC()) then + self:SetClip1(self:GetMaxClip1()) + self:SetClip2(self:GetMaxClip2()) + end + end +end + +--[[ +Function Name: Think +Syntax: self:Think() +Returns: Nothing. +Notes: This is blank. +Purpose: Standard SWEP Function +]] +function SWEP:Think() + local self2 = self:GetTable() + self2.CalculateRatios(self) + + if self:GetOwner():IsNPC() and SERVER then + if self2.ThinkNPC then self2.ThinkNPC(self) end + self2.Think2(self, false) + end + + stat = self2.GetStatus(self) + + if (not sp or SERVER) and not self:GetFirstDeployEvent() then + self2.ProcessEvents(self, sp or IsFirstTimePredicted()) + end + + -- backward compatibility + self2.AnimCycle = self:GetAnimCycle() + + if (not sp or SERVER) and ct > self:GetNextIdleAnim() and (TFA.Enum.ReadyStatus[stat] or (stat == TFA.Enum.STATUS_SHOOTING and TFA.Enum.ShootLoopingStatus[self:GetShootStatus()])) then + self:ChooseIdleAnim() + end + + self2.ProcessLoopFire(self) +end + +function SWEP:PlayerThink(plyv, is_working_out_prediction_errors) + if not self:NullifyOIV() then return end + + self:Think2(is_working_out_prediction_errors) +end + +local sv_cheats = GetConVar("sv_cheats") +local host_timescale = GetConVar("host_timescale") + +local function Clamp(a, b, c) + if a < b then return b end + if a > c then return c end + return a +end + +local Lerp = Lerp + +function SWEP:ShouldPlaySafetyAnim() + if self:IsSafety() then + return not self.SprintProgressUnpredicted2 or self.SprintProgressUnpredicted2 < 0.3 + end + + if not TFA.FriendlyEncounter then return false end + return not self:GetIronSights() and (self:GetLastGunFire() + 1 < CurTime()) and (not self.SprintProgressUnpredicted2 or self.SprintProgressUnpredicted2 < 0.3) +end + +local tickInterval = engine.TickInterval() + +function SWEP:PlayerThinkCL(plyv) + local self2 = self:GetTable() + + if not self:NullifyOIV() then return end + + self:SmokePCFLighting() + + if sp then + self:Think2(false) + end + + local ft = RealFrameTime() * game.GetTimeScale() * (sv_cheats:GetBool() and host_timescale:GetFloat() or 1) + + if self2.GetStatL(self, "BlowbackEnabled") then + if not self2.Blowback_PistolMode or self:Clip1() == -1 or self:Clip1() > 0.1 or self2.Blowback_PistolMode_Disabled[self:GetLastActivity()] or self2.Blowback_PistolMode_Disabled[self:GetLastSequence()] or self2.Blowback_PistolMode_Disabled[self:GetLastSequenceString()] then + self2.BlowbackCurrent = l_mathApproach(self2.BlowbackCurrent, 0, self2.BlowbackCurrent * ft * 15) + end + + self2.BlowbackCurrentRoot = l_mathApproach(self2.BlowbackCurrentRoot, 0, self2.BlowbackCurrentRoot * ft * 15) + end + + local is = self2.GetIronSights(self) + local spr = self2.GetSprinting(self) + local walk = self2.GetWalking(self) + local status = self2.GetStatus(self) + + local ist = is and 1 or 0 + local ist2 = TFA.Enum.ReloadStatus[self:GetStatus()] and ist * .25 or ist + + local reloadBlendMult, reloadBlendMult2 = 1, 1 + + if not self:GetStatL("LoopedReload") and (status == TFA.Enum.STATUS_RELOADING or status == TFA.Enum.STATUS_RELOADING_WAIT) and self2.ReloadAnimationEnd and self2.ReloadAnimationStart then + local time = l_CT() + local progress = Clamp((time - self2.ReloadAnimationStart) / (self2.ReloadAnimationEnd - self2.ReloadAnimationStart), 0, 1) + + reloadBlendMult = TFA.Cubic(math.max( + Clamp(progress - 0.7, 0, 0.3) / 0.3, + Clamp(0.1 - progress, 0, 0.1) / 0.1 + )) + + reloadBlendMult2 = (1 + reloadBlendMult) / 2 + elseif TFA.Enum.ReloadStatus[status] then + reloadBlendMult = 0 + reloadBlendMult2 = 0.5 + end + + local fidgetBlendMult = 1 + + if status == TFA.Enum.STATUS_FIDGET then + local progress = self:GetStatusProgress(true) + + fidgetBlendMult = TFA.Cubic(math.max( + Clamp(progress - 0.8, 0, 0.2) / 0.2, + Clamp(0.1 - progress, 0, 0.1) / 0.1 + )) + end + + local sprt = spr and reloadBlendMult or 0 + local sprt2 = spr and (fidgetBlendMult * reloadBlendMult) or 0 + local sprt3 = spr and reloadBlendMult2 or 0 + + local walkt = walk and 1 or 0 + + local IronSightsPosition = self2.GetStatL(self, "IronSightsPosition", self2.SightsPos) + local IronSightsAngle = self2.GetStatL(self, "IronSightsAngle", self2.SightsAng) + + if IronSightsPosition then + self2.IronSightsPositionCurrent = self2.IronSightsPositionCurrent or Vector(IronSightsPosition) + self2.IronSightsAngleCurrent = self2.IronSightsAngleCurrent or Vector(IronSightsAngle) + + self2.IronSightsPositionCurrent.x = Lerp(ft * 11, self2.IronSightsPositionCurrent.x, IronSightsPosition.x) + self2.IronSightsPositionCurrent.y = Lerp(ft * 11, self2.IronSightsPositionCurrent.y, IronSightsPosition.y) + self2.IronSightsPositionCurrent.z = Lerp(ft * 11, self2.IronSightsPositionCurrent.z, IronSightsPosition.z) + + self2.IronSightsAngleCurrent.x = Lerp(ft * 11, self2.IronSightsAngleCurrent.x, self2.IronSightsAngleCurrent.x - math.AngleDifference(self2.IronSightsAngleCurrent.x, IronSightsAngle.x)) + self2.IronSightsAngleCurrent.y = Lerp(ft * 11, self2.IronSightsAngleCurrent.y, self2.IronSightsAngleCurrent.y - math.AngleDifference(self2.IronSightsAngleCurrent.y, IronSightsAngle.y)) + self2.IronSightsAngleCurrent.z = Lerp(ft * 11, self2.IronSightsAngleCurrent.z, self2.IronSightsAngleCurrent.z - math.AngleDifference(self2.IronSightsAngleCurrent.z, IronSightsAngle.z)) + end + + local adstransitionspeed + if is then + adstransitionspeed = 12.5 / (self:GetStatL("IronSightTime") / 0.3) + elseif spr or walk then + adstransitionspeed = 7.5 + else + adstransitionspeed = 12.5 + end + adstransitionspeed = math.min(adstransitionspeed, 1000) + + local ply = self:GetOwner() + local velocity = self2.LastUnpredictedVelocity or ply:GetVelocity() + + local jr_targ = math.min(math.abs(velocity.z) / 500, 1) + self2.JumpRatioUnpredicted = l_mathApproach((self2.JumpRatioUnpredicted or 0), jr_targ, (jr_targ - (self2.JumpRatioUnpredicted or 0)) * ft * 20) + self2.CrouchingRatioUnpredicted = l_mathApproach((self2.CrouchingRatioUnpredicted or 0), ((ply:Crouching() or self2.KeyDown(self, IN_DUCK)) and ply:OnGround() and not ply:InVehicle()) and 1 or 0, ft / self2.ToCrouchTime) + + self2.IronSightsProgressPredicted = self2.GetIronSightsProgress(self) + (ist - self2.GetIronSightsProgress(self)) * tickInterval * adstransitionspeed * TFA.TickDelta + self2.SprintProgressPredicted = self2.GetSprintProgress(self) + (sprt - self2.GetSprintProgress(self)) * tickInterval * adstransitionspeed * TFA.TickDelta + self2.InspectingProgressPredicted = self2.GetInspectingProgress(self) + ((self2.GetCustomizing(self) and 1 or 0) - self2.GetInspectingProgress(self)) * tickInterval * 10 * TFA.TickDelta + + self2.IronSightsProgressUnpredicted = l_mathApproach(self2.IronSightsProgressUnpredicted or 0, ist, (ist - (self2.IronSightsProgressUnpredicted or 0)) * ft * adstransitionspeed * 1.2) + self2.IronSightsProgressUnpredicted2 = l_mathApproach(self2.IronSightsProgressUnpredicted2 or 0, ist, (ist - (self2.IronSightsProgressUnpredicted2 or 0)) * ft * adstransitionspeed * 0.4) + self2.IronSightsProgressUnpredicted3 = l_mathApproach(self2.IronSightsProgressUnpredicted3 or 0, ist2, (ist2 - (self2.IronSightsProgressUnpredicted3 or 0)) * ft * adstransitionspeed * 0.7) + self2.SprintProgressUnpredicted = l_mathApproach(self2.SprintProgressUnpredicted or 0, sprt, (sprt - (self2.SprintProgressUnpredicted or 0)) * ft * adstransitionspeed) + self2.SprintProgressUnpredicted2 = l_mathApproach(self2.SprintProgressUnpredicted2 or 0, sprt2, (sprt2 - (self2.SprintProgressUnpredicted2 or 0)) * ft * adstransitionspeed) + self2.SprintProgressUnpredicted3 = l_mathApproach(self2.SprintProgressUnpredicted3 or 0, sprt3, (sprt3 - (self2.SprintProgressUnpredicted3 or 0)) * ft * adstransitionspeed) + + if is and not self2.VM_IronPositionScore then + self2.VM_IronPositionScore = Clamp(self2.GetStatL(self, "ViewModelPosition"):Distance(self2.IronSightsPositionCurrent or self2.GetStatL(self, "IronSightsPosition", self2.GetStat(self, "SightsPos", vector_origin))) / 7, 0, 1) + elseif not is and self2.VM_IronPositionScore and self2.IronSightsProgressUnpredicted2 <= 0.08 then + self2.VM_IronPositionScore = nil + end + + if self2.IronSightsProgressUnpredicted2 >= 0.8 and not self2.VM_IsScopedIn then + self2.VM_IsScopedIn = true + --elseif self2.IronSightsProgressUnpredicted2 <= 0.1 and self2.VM_IsScopedIn then + elseif self2.IronSightsProgressUnpredicted2 <= 0.15 then + self2.VM_IsScopedIn = false + end + + local customizingTarget = self:GetCustomizing() and 1 or 0 + self2.CustomizingProgressUnpredicted = l_mathApproach((self2.CustomizingProgressUnpredicted or 0), customizingTarget, (customizingTarget - (self2.CustomizingProgressUnpredicted or 0)) * ft * 5) + + self2.WalkProgressUnpredicted = l_mathApproach((self2.WalkProgressUnpredicted or 0), walkt, (walkt - (self2.WalkProgressUnpredicted or 0)) * ft * adstransitionspeed) + + if status ~= TFA.Enum.STATUS_FIREMODE or not self:GetIsCyclingSafety() then + local safetyTarget = self:ShouldPlaySafetyAnim() and (fidgetBlendMult * reloadBlendMult) or 0 + self2.SafetyProgressUnpredicted = l_mathApproach(self2.SafetyProgressUnpredicted or 0, safetyTarget, (safetyTarget - (self2.SafetyProgressUnpredicted or 0)) * ft * adstransitionspeed * 0.7) + elseif status == TFA.Enum.STATUS_FIREMODE and self:GetIsCyclingSafety() then + if not self:ShouldPlaySafetyAnim() then + local safetyTarget = 0 + + if self:GetSafetyCycleAnimated() then + self2.SafetyProgressUnpredicted = l_mathApproach(self2.SafetyProgressUnpredicted or 0, safetyTarget, (safetyTarget - (self2.SafetyProgressUnpredicted or 0)) * ft * adstransitionspeed * 1.1) + else + self2.SafetyProgressUnpredicted = l_mathApproach(self2.SafetyProgressUnpredicted or 0, safetyTarget, (safetyTarget - (self2.SafetyProgressUnpredicted or 0)) * ft * adstransitionspeed) + end + else + local safetyTarget = fidgetBlendMult * reloadBlendMult + + if not self:GetSafetyCycleAnimated() then + self2.SafetyProgressUnpredicted = l_mathApproach(self2.SafetyProgressUnpredicted or 0, safetyTarget, (safetyTarget - (self2.SafetyProgressUnpredicted or 0)) * ft * adstransitionspeed * 0.7) + end + end + end +end + +local UnPredictedCurTime = UnPredictedCurTime + +--[[ +Function Name: Think2 +Syntax: self:Think2(). Called from Think. +Returns: Nothing. +Notes: Essential for calling other important functions. +Purpose: Standard SWEP Function +]] +function SWEP:Think2(is_working_out_prediction_errors) + local self2 = self:GetTable() + + ct = l_CT() + + if not is_working_out_prediction_errors then + if CLIENT then + self2.CurTimePredictionAdvance = ct - UnPredictedCurTime() + end + + if self2.LuaShellRequestTime > 0 and ct > self2.LuaShellRequestTime then + self2.LuaShellRequestTime = -1 + self2.MakeShell(self) + end + + if not self2.HasInitialized then + self:Initialize() + end + + if not self2.HasDetectedValidAnimations then + self2.CacheAnimations(self) + self2.ChooseDrawAnim(self) + end + + self2.InitAttachments(self) + + self2.ProcessBodygroups(self) + + self2.ProcessHoldType(self) + self2.ReloadCV(self) + self2.IronSightSounds(self) + self2.ProcessLoopSound(self) + end + + self2.ProcessFireMode(self) + + if (not sp or SERVER) and self:GetFirstDeployEvent() then + self2.ProcessEvents(self, sp or not is_working_out_prediction_errors) + end + + --if is_working_out_prediction_errors then return end + + if not sp or SERVER then + self2.IronSights(self) + end + + self2.ProcessStatus(self) +end + +SWEP.IronSightsReloadEnabled = false +SWEP.IronSightsReloadLock = true + +function SWEP:IronSights() + local self2 = self:GetTable() + local owent = self:GetOwner() + if not IsValid(owent) then return end + + ct = l_CT() + stat = self:GetStatus() + + local issprinting = self:GetSprinting() + local iswalking = self:GetWalking() + + local issighting = self:GetIronSightsRaw() + local isplayer = owent:IsPlayer() + local old_iron_sights_final = self:GetIronSightsOldFinal() + + if TFA.Enum.ReloadStatus[stat] and self2.GetStatL(self, "IronSightsReloadLock") then + issighting = old_iron_sights_final + end + + if issighting and isplayer and owent:InVehicle() and not owent:GetAllowWeaponsInVehicle() then + issighting = false + self:SetIronSightsRaw(false) + end + + -- self:SetLastSightsStatusCached(false) + local userstatus = issighting + + if issprinting then + issighting = false + end + + if issighting and not TFA.Enum.IronStatus[stat] and (not self:GetStatL("IronSightsReloadEnabled") or not TFA.Enum.ReloadStatus[stat]) then + issighting = false + end + + if issighting and self:IsSafety() then + issighting = false + end + + if stat == TFA.Enum.STATUS_FIREMODE and self:GetIsCyclingSafety() then + issighting = false + end + + local isbolt = self2.GetStatL(self, "BoltAction") + local isbolt_forced = self2.GetStatL(self, "BoltAction_Forced") + if isbolt or isbolt_forced then + if stat == TFA.Enum.STATUS_SHOOTING and not self2.LastBoltShoot then + self2.LastBoltShoot = l_CT() + end + + if self2.LastBoltShoot then + if stat == TFA.Enum.STATUS_SHOOTING then + if l_CT() > self2.LastBoltShoot + self2.GetStatL(self, "BoltTimerOffset") then + issighting = false + end + else + self2.LastBoltShoot = nil + end + end + + if (stat == TFA.Enum.STATUS_IDLE and self:GetReloadLoopCancel(true)) or stat == TFA.Enum.STATUS_PUMP then + issighting = false + end + end + + local sightsMode = self2.GetStatL(self, "Sights_Mode") + local sprintMode = self2.GetStatL(self, "Sprint_Mode") + local walkMode = self2.GetStatL(self, "Walk_Mode") + local customizeMode = self2.GetStatL(self, "Customize_Mode") + + if old_iron_sights_final ~= issighting and sightsMode == TFA.Enum.LOCOMOTION_LUA then -- and stat == TFA.Enum.STATUS_IDLE then + self:SetNextIdleAnim(-1) + end + + local smi = (sightsMode ~= TFA.Enum.LOCOMOTION_LUA) + and old_iron_sights_final ~= issighting + + local spi = (sprintMode ~= TFA.Enum.LOCOMOTION_LUA) + and self2.sprinting_updated + + local wmi = (walkMode ~= TFA.Enum.LOCOMOTION_LUA) + and self2.walking_updated + + local cmi = (customizeMode ~= TFA.Enum.LOCOMOTION_LUA) + and self:GetCustomizeUpdated() + + self:SetCustomizeUpdated(false) + + if + (smi or spi or wmi or cmi) and + (self:GetStatus() == TFA.Enum.STATUS_IDLE or + (self:GetStatus() == TFA.Enum.STATUS_SHOOTING and self:CanInterruptShooting())) + and not self:GetReloadLoopCancel() + then + local toggle_is = old_iron_sights_final ~= issighting + + if issighting and self:GetSprinting() then + toggle_is = true + end + + local success, _ = self:Locomote(toggle_is and (sightsMode ~= TFA.Enum.LOCOMOTION_LUA), issighting, spi, issprinting, wmi, iswalking, cmi, self:GetCustomizing()) + + if not success and (toggle_is and smi or spi or wmi or cmi) then + self:SetNextIdleAnim(-1) + end + end + + self:SetIronSightsOldFinal(issighting) + + return userstatus, issighting +end + +SWEP.is_sndcache_old = false + +function SWEP:IronSightSounds() + local self2 = self:GetTable() + + local is = self:GetIronSights() + + if SERVER or IsFirstTimePredicted() then + if is ~= self2.is_sndcache_old and hook.Run("TFA_IronSightSounds", self) == nil then + if is then + self:EmitSound(self:GetStatL("Secondary.IronSightsInSound", "TFA.IronIn")) + else + self:EmitSound(self:GetStatL("Secondary.IronSightsOutSound", "TFA.IronOut")) + end + end + + self2.is_sndcache_old = is + end +end + +local legacy_reloads_cv = GetConVar("sv_tfa_reloads_legacy") +local dryfire_cvar = GetConVar("sv_tfa_allow_dryfire") + +SWEP.Primary.Sound_DryFire = Sound("Weapon_Pistol.Empty2") -- dryfire sound, played only once +SWEP.Primary.Sound_DrySafety = Sound("Weapon_AR2.Empty2") -- safety click sound +SWEP.Primary.Sound_Blocked = Sound("Weapon_AR2.Empty") -- underwater click sound +SWEP.Primary.Sound_Jammed = Sound("Default.ClipEmpty_Rifle") -- jammed click sound + +SWEP.Primary.SoundHint_Fire = true +SWEP.Primary.SoundHint_DryFire = true + +local function Dryfire(self, self2, reload) + if not dryfire_cvar:GetBool() and reload then + self:Reload(true) + end + + if self2.GetHasPlayedEmptyClick(self) then return end + + self2.SetHasPlayedEmptyClick(self, true) + + if SERVER and self:GetStatL("Primary.SoundHint_DryFire") then + sound.EmitHint(SOUND_COMBAT, self:GetPos(), 500, 0.2, self:GetOwner()) + end + + if self:GetOwner():IsNPC() or self:KeyPressed(IN_ATTACK) then + local enabled, tanim, ttype = self:ChooseDryFireAnim() + + if enabled then + self:SetNextPrimaryFire(l_CT() + self:GetStatL("Primary.DryFireDelay", self:GetActivityLength(tanim, true, ttype))) + return + end + end + + if IsFirstTimePredicted() then + self:EmitSound(self:GetStatL("Primary.Sound_DryFire")) + end +end + +function SWEP:CanPrimaryAttack() + local attackKeyPressed = false + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + attackKeyPressed = self:GetOwner():KeyPressed(IN_ATTACK) + end + + local self2 = self:GetTable() + + local v = hook.Run("TFA_PreCanPrimaryAttack", self) + + if v ~= nil then + return v + end + + stat = self:GetStatus() + + if not TFA.Enum.ReadyStatus[stat] and stat ~= TFA.Enum.STATUS_SHOOTING then + if attackKeyPressed and self:GetStatL("LoopedReload") and TFA.Enum.ReloadStatus[stat] then + self:SetReloadLoopCancel(true) + end + + return false + end + + if self:IsSafety() then + if attackKeyPressed then + if IsFirstTimePredicted() then + self:EmitSound(self:GetStatL("Primary.Sound_DrySafety")) + + if SERVER and self:GetStatL("Primary.SoundHint_DryFire") then + sound.EmitHint(SOUND_COMBAT, self:GetPos(), 200, 0.2, self:GetOwner()) + end + end + + if l_CT() < self:GetLastSafetyShoot() + 0.2 then + self:CycleSafety() + -- self:SetNextPrimaryFire(l_CT() + 0.1) + end + + self:SetLastSafetyShoot(l_CT() + 0.2) + end + + return false + end + + if self:GetSprintProgress() >= 0.1 and not self:GetStatL("AllowSprintAttack", false) then + return false + end + + if self:GetStatL("Primary.ClipSize") <= 0 and self:Ammo1() < self:GetStatL("Primary.AmmoConsumption") then + Dryfire(self, self2) + return false + end + + if self:GetPrimaryClipSize(true) > 0 and self:Clip1() < self:GetStatL("Primary.AmmoConsumption") then + Dryfire(self, self2, true) + return false + end + + if self2.GetStatL(self, "Primary.FiresUnderwater") == false and self:GetOwner():WaterLevel() >= 3 then + self:SetNextPrimaryFire(l_CT() + 0.5) + self:EmitSound(self:GetStatL("Primary.Sound_Blocked")) + return false + end + + self2.SetHasPlayedEmptyClick(self, false) + + if l_CT() < self:GetNextPrimaryFire() then return false end + + local v2 = hook.Run("TFA_CanPrimaryAttack", self) + + if v2 ~= nil then + return v2 + end + + if self:CheckJammed() then + if IsFirstTimePredicted() then + self:EmitSound(self:GetStatL("Primary.Sound_Jammed")) + end + + local typev, tanim = self:ChooseAnimation("shoot1_empty") + + if typev ~= TFA.Enum.ANIMATION_SEQ then + self:SendViewModelAnim(tanim) + else + self:SendViewModelSeq(tanim) + end + + self:SetNextPrimaryFire(l_CT() + 1) + + return false + end + + return true +end + +function SWEP:EmitGunfireLoop() + local self2 = self:GetTable() + local tgtSound = self:GetStatL("Primary.LoopSound") + + if self:GetSilenced() then + tgtSound = self:GetStatL("Primary.LoopSoundSilenced", tgtSound) + end + + if (not sp and SERVER) or not self:IsFirstPerson() then + tgtSound = self:GetSilenced() and self:GetStatL("Primary.LoopSoundSilenced_World", tgtSound) or self:GetStatL("Primary.LoopSound_World", tgtSound) + end + + if self:GetNextLoopSoundCheck() < 0 or (l_CT() >= self:GetNextLoopSoundCheck() and self2.LastLoopSound ~= tgtSound) then + if self2.LastLoopSound ~= tgtSound and self2.LastLoopSound ~= nil then + self:StopSound(self2.LastLoopSound) + end + + self2.LastLoopSound = tgtSound + self2.GunfireLoopIFTPHack = true + + self:EmitSoundNet(tgtSound, nil, true) + end + + self:SetNextLoopSoundCheck(CurTime() + self:GetFireDelay()) +end + +function SWEP:EmitGunfireSound(soundscript) + self:EmitSoundNet(soundscript) +end + +local sv_tfa_nearlyempty = GetConVar("sv_tfa_nearlyempty") + +SWEP.LowAmmoSoundThreshold = 0.33 + +function SWEP:EmitLowAmmoSound() + if not sv_tfa_nearlyempty:GetBool() then return end + + local self2 = self:GetTable() + + if not self2.FireSoundAffectedByClipSize then return end + + local clip1, maxclip1 = self:Clip1(), self:GetMaxClip1() + if clip1 <= 0 then return end + + local nextclip1 = clip1 - self:GetStatL("Primary.AmmoConsumption", 1) + if self:GetStatL("IsAkimbo") then + nextclip1 = nextclip1 - self:GetAnimCycle() + end + + local mult = nextclip1 / maxclip1 + if mult >= self2.LowAmmoSoundThreshold then return end + + local soundname = (nextclip1 <= 0) and self:GetStatL("LastAmmoSound", "") or self:GetStatL("LowAmmoSound", "") + + if soundname and soundname ~= "" then + self2.GonnaAdjustVol = true + self2.RequiredVolume = 1 - (mult / math.max(self2.LowAmmoSoundThreshold, 0.01)) + + self:EmitSound(soundname) + end +end + +function SWEP:TriggerAttack(tableName, clipID) + local self2 = self:GetTable() + local ply = self:GetOwner() + + local fnname = clipID == 2 and "Secondary" or "Primary" + + if TFA.Enum.ShootReadyStatus[self:GetShootStatus()] then + self:SetShootStatus(TFA.Enum.SHOOT_IDLE) + end + + if self:GetStatRawL("CanBeSilenced") and (ply.KeyDown and self:KeyDown(IN_USE)) and (SERVER or not sp) and (ply.GetInfoNum and ply:GetInfoNum("cl_tfa_keys_silencer", 0) == 0) then + local _, tanim, ttype = self:ChooseSilenceAnim(not self:GetSilenced()) + self:ScheduleStatus(TFA.Enum.STATUS_SILENCER_TOGGLE, self:GetActivityLength(tanim, true, ttype)) + + return + end + + self["SetNext" .. fnname .. "Fire"](self, self2["GetNextCorrected" .. fnname .. "Fire"](self, self2.GetFireDelay(self))) + + if self:GetMaxBurst() > 1 then + self:SetBurstCount(math.max(1, self:GetBurstCount() + 1)) + end + + if self:GetStatL("PumpAction") and self:GetReloadLoopCancel() then return end + + self:SetStatus(TFA.Enum.STATUS_SHOOTING, self["GetNext" .. fnname .. "Fire"](self)) + self:ToggleAkimbo() + self:IncreaseRecoilLUT() + + local ifp = IsFirstTimePredicted() + + local _, tanim, ttype = self:ChooseShootAnim(ifp) + + ply:SetAnimation(PLAYER_ATTACK1) + + if SERVER and self:GetStatL(tableName .. ".SoundHint_Fire") then + sound.EmitHint(bit.bor(SOUND_COMBAT, SOUND_CONTEXT_GUNFIRE), self:GetPos(), self:GetSilenced() and 500 or 1500, 0.2, self:GetOwner()) + end + + if self:GetStatL(tableName .. ".Sound") and ifp and not (sp and CLIENT) then + if ply:IsPlayer() and self:GetStatL(tableName .. ".LoopSound") and self:ShouldEmitGunfireLoop(tableName) then + self:EmitGunfireLoop() + else + local tgtSound = self:GetStatL(tableName .. ".Sound") + + if self:GetSilenced() then + tgtSound = self:GetStatL(tableName .. ".SilencedSound", tgtSound) + end + + if (not sp and SERVER) or not self:IsFirstPerson() then + tgtSound = self:GetSilenced() and self:GetStatL(tableName .. ".SilencedSound_World", tgtSound) or self:GetStatL(tableName .. ".Sound_World", tgtSound) + end + + self:EmitGunfireSound(tgtSound) + end + + self:EmitLowAmmoSound() + end + + self2["Take" .. fnname .. "Ammo"](self, self:GetStatL(tableName .. ".AmmoConsumption")) + + if self["Clip" .. clipID](self) == 0 and self:GetStatL(tableName .. ".ClipSize") > 0 then + self["SetNext" .. fnname .. "Fire"](self, math.max(self["GetNext" .. fnname .. "Fire"](self), l_CT() + (self:GetStatL(tableName .. ".DryFireDelay", self:GetActivityLength(tanim, true, ttype))))) + end + + self:ShootBulletInformation() + self:UpdateJamFactor() + local _, CurrentRecoil = self:CalculateConeRecoil() + self:Recoil(CurrentRecoil, ifp) + + -- shouldn't this be not required since recoil state is completely networked? + if sp and SERVER then + self:CallOnClient("Recoil", "") + end + + if self:GetStatL(tableName .. ".MuzzleFlashEnabled", self:GetStatL("MuzzleFlashEnabled")) and (not self:IsFirstPerson() or not self:GetStatL(tableName .. ".AutoDetectMuzzleAttachment", self:GetStatL("AutoDetectMuzzleAttachment"))) then + self:ShootEffectsCustom() + end + + if self:GetStatL(tableName .. ".EjectionSmoke", self:GetStatL("EjectionSmoke")) and CLIENT and ply == LocalPlayer() and ifp and not self:GetStatL(tableName .. ".LuaShellEject", self:GetStatL("LuaShellEject")) then + self:EjectionSmoke() + end + + self:DoAmmoCheck(clipID) + + -- Condition self:GetStatus() == TFA.Enum.STATUS_SHOOTING is always true? + if self:GetStatus() == TFA.Enum.STATUS_SHOOTING and self:GetStatL("PumpAction") then + if self["Clip" .. clipID](self) == 0 and self:GetStatL("PumpAction.value_empty") then + self:SetReloadLoopCancel(true) + elseif (self:GetStatL(tableName .. ".ClipSize") < 0 or self["Clip" .. clipID](self) > 0) and self:GetStatL("PumpAction.value") then + self:SetReloadLoopCancel(true) + end + end + + self:RollJamChance() +end + +function SWEP:PrimaryAttack() + local self2 = self:GetTable() + local ply = self:GetOwner() + if not IsValid(ply) then return end + + if not IsValid(self) then return end + if ply:IsPlayer() and not self:VMIV() then return end + if not self:CanPrimaryAttack() then return end + + self:PrePrimaryAttack() + + if hook.Run("TFA_PrimaryAttack", self) then return end + + self:TriggerAttack("Primary", 1) + + self:PostPrimaryAttack() + hook.Run("TFA_PostPrimaryAttack", self) +end + +function SWEP:PrePrimaryAttack() + -- override +end + +function SWEP:PostPrimaryAttack() + -- override +end + +function SWEP:CanSecondaryAttack() + local attackKeyPressed = false + if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then + attackKeyPressed = self:GetOwner():KeyPressed(IN_ATTACK2) + end + + local self2 = self:GetTable() + + local v = hook.Run("TFA_PreCanSecondaryAttack", self) + + if v ~= nil then + return v + end + + if self:IsSafety() then + if attackKeyPressed then + if IsFirstTimePredicted() then + self:EmitSound(self:GetStatL("Primary.Sound_DrySafety")) + + if SERVER and self:GetStatL("Primary.SoundHint_DryFire") then + sound.EmitHint(SOUND_COMBAT, self:GetPos(), 200, 0.2, self:GetOwner()) + end + end + + if l_CT() < self:GetLastSafetyShoot() + 0.2 then + self:CycleSafety() + end + + self:SetLastSafetyShoot(l_CT() + 0.2) + end + + return false + end + + if self:GetSprintProgress() >= 0.1 and not self:GetStatL("AllowSprintAttack", false) then + return false + end + + if self:GetStatL("Secondary.ClipSize") <= 0 and self:Ammo2() < self:GetStatL("Secondary.AmmoConsumption") then + Dryfire(self, self2) + return false + end + + if self:GetSecondaryClipSize(true) > 0 and self:Clip2() < self:GetStatL("Secondary.AmmoConsumption") then + Dryfire(self, self2, true) + return false + end + + if self2.GetStatL(self, "Secondary.FiresUnderwater") == false and self:GetOwner():WaterLevel() >= 3 then + self:SetNextSecondaryFire(l_CT() + 0.5) + self:EmitSound(self:GetStatL("Primary.Sound_Blocked")) + return false + end + + -- self2.SetHasPlayedEmptyClick(self, false) + + if l_CT() < self:GetNextSecondaryFire() then return false end + + local v2 = hook.Run("TFA_CanSecondaryAttack", self) + + if v2 ~= nil then + return v2 + end + + if self:CheckJammed() then + if IsFirstTimePredicted() then + self:EmitSound(self:GetStatL("Primary.Sound_Jammed")) + end + + local typev, tanim = self:ChooseAnimation("shoot2_empty") + + if typev ~= TFA.Enum.ANIMATION_SEQ then + self:SendViewModelAnim(tanim) + else + self:SendViewModelSeq(tanim) + end + + self:SetNextPrimaryFire(l_CT() + 1) + + return false + end + + return true -- override +end + +function SWEP:SecondaryAttack() + if not self:CanSecondaryAttack() then return end + + self:PreSecondaryAttack() + + if hook.Run("TFA_SecondaryAttack", self) then return end + + if not self:GetStatL("Secondary.IronSightsEnabled", false) and self.AltAttack and self:GetOwner():IsPlayer() then + self:AltAttack() + end + + self:PostSecondaryAttack() + hook.Run("TFA_PostSecondaryAttack", self) +end + +function SWEP:PreSecondaryAttack() + -- override +end + +function SWEP:PostSecondaryAttack() + -- override +end + +function SWEP:GetLegacyReloads() + return legacy_reloads_cv:GetBool() +end + +do + local bit_band = bit.band + + function SWEP:KeyDown(keyIn) + return bit_band(self:GetDownButtons(), keyIn) == keyIn + end + + function SWEP:KeyPressed(keyIn) + return bit_band(self:GetLastPressedButtons(), keyIn) == keyIn + end +end + +if SERVER and sp then + util.AddNetworkString("tfa_reload_blending") +elseif CLIENT and sp then + net.Receive("tfa_reload_blending", function() + local self = net.ReadEntity() + if not IsValid(self) then return end + self.ReloadAnimationStart = net.ReadDouble() + self.ReloadAnimationEnd = net.ReadDouble() + end) +end + +function SWEP:Reload(released) + local self2 = self:GetTable() + + self:PreReload(released) + + if hook.Run("TFA_PreReload", self, released) then return end + + local isplayer = self:GetOwner():IsPlayer() + local vm = self2.VMIV(self) + + if isplayer and not vm then return end + + if not self:IsJammed() then + if self:Ammo1() <= 0 then return end + if self:GetStatL("Primary.ClipSize") < 0 then return end + end + + if not released and not self:GetLegacyReloads() then return end + if self:GetLegacyReloads() and not dryfire_cvar:GetBool() and not self:KeyDown(IN_RELOAD) then return end + if self:KeyDown(IN_USE) then return end + + ct = l_CT() + stat = self:GetStatus() + + if self:GetStatL("PumpAction") and self:GetReloadLoopCancel() then + if stat == TFA.Enum.STATUS_IDLE then + self:DoPump() + end + elseif TFA.Enum.ReadyStatus[stat] or (stat == TFA.Enum.STATUS_SHOOTING and self:CanInterruptShooting()) or self:IsJammed() then + if self:Clip1() < self:GetPrimaryClipSize() or self:IsJammed() then + if hook.Run("TFA_Reload", self) then return end + self:SetBurstCount(0) + + if self:GetStatL("LoopedReload") then + local _, tanim, ttype = self:ChooseShotgunReloadAnim() + + if self:GetStatL("ShotgunStartAnimShell") then + self:SetStatus(TFA.Enum.STATUS_RELOADING_LOOP_START_EMPTY) + elseif self2.ShotgunEmptyAnim then + local _, tg = self:ChooseAnimation("reload_empty") + local action = tanim + + if type(tg) == "string" and tonumber(tanim) and tonumber(tanim) > 0 and isplayer then + if ttype == TFA.Enum.ANIMATION_ACT then + action = vm:GetSequenceName(vm:SelectWeightedSequenceSeeded(tanim, self:GetSeedIrradical())) + else + action = vm:GetSequenceName(tanim) + end + end + + if action == tg and self:GetStatL("ShotgunEmptyAnim_Shell") then + self:SetStatus(TFA.Enum.STATUS_RELOADING_LOOP_START_EMPTY) + else + self:SetStatus(TFA.Enum.STATUS_RELOADING_LOOP_START) + end + else + self:SetStatus(TFA.Enum.STATUS_RELOADING_LOOP_START) + end + + self:SetStatusEnd(ct + self:GetActivityLength(tanim, true, ttype)) + --self:SetNextPrimaryFire(ct + self:GetActivityLength( tanim, false ) ) + else + local _, tanim, ttype = self:ChooseReloadAnim() + + self:SetStatus(TFA.Enum.STATUS_RELOADING) + + if self:GetStatL("IsProceduralReloadBased") then + self:SetStatusEnd(ct + self:GetStatL("ProceduralReloadTime")) + else + self:SetStatusEnd(ct + self:GetActivityLength(tanim, true, ttype)) + self:SetNextPrimaryFire(ct + self:GetActivityLength(tanim, false, ttype)) + end + + if CLIENT then + self2.ReloadAnimationStart = ct + self2.ReloadAnimationEnd = ct + self:GetActivityLength(tanim, false, ttype) + elseif sp then + net.Start("tfa_reload_blending", true) + net.WriteEntity(self) + net.WriteDouble(ct) + net.WriteDouble(ct + self:GetActivityLength(tanim, false, ttype)) + net.Broadcast() + end + end + + self:GetOwner():SetAnimation(PLAYER_RELOAD) + + if self:GetStatL("Primary.ReloadSound") and IsFirstTimePredicted() then + self:EmitSound(self:GetStatL("Primary.ReloadSound")) + end + + self:ResetAnimCycle() + + self:SetNextPrimaryFire( -1 ) + elseif released or self:KeyPressed(IN_RELOAD) then--if self:GetOwner():KeyPressed(IN_RELOAD) or not self:GetLegacyReloads() then + self:CheckAmmo() + end + end + + self:PostReload(released) + + hook.Run("TFA_PostReload", self) +end + +function SWEP:PreReload(released) + -- override +end + +function SWEP:PostReload(released) + -- override +end + +function SWEP:Reload2(released) + local self2 = self:GetTable() + + local isplayer = self:GetOwner():IsPlayer() + local vm = self2.VMIV(self) + + if isplayer and not vm then return end + + if self:Ammo2() <= 0 then return end + if self:GetStatL("Secondary.ClipSize") < 0 then return end + if not released and not self:GetLegacyReloads() then return end + if self:GetLegacyReloads() and not dryfire_cvar:GetBool() and not self:KeyDown(IN_RELOAD) then return end + if self:KeyDown(IN_USE) then return end + + ct = l_CT() + stat = self:GetStatus() + + if self:GetStatL("PumpAction") and self:GetReloadLoopCancel() then + if stat == TFA.Enum.STATUS_IDLE then + self:DoPump() + end + elseif TFA.Enum.ReadyStatus[stat] or ( stat == TFA.Enum.STATUS_SHOOTING and self:CanInterruptShooting() ) then + if self:Clip2() < self:GetSecondaryClipSize() then + if self:GetStatL("LoopedReload") then + local _, tanim, ttype = self:ChooseShotgunReloadAnim() + + if self2.ShotgunEmptyAnim then + local _, tg = self:ChooseAnimation("reload_empty") + local action = tanim + + if type(tg) == "string" and tonumber(tanim) and tonumber(tanim) > 0 and isplayer then + if ttype == TFA.Enum.ANIMATION_ACT then + action = vm:GetSequenceName(vm:SelectWeightedSequenceSeeded(tanim, self:GetSeedIrradical())) + else + action = vm:GetSequenceName(tanim) + end + end + + if action == tg and self:GetStatL("ShotgunEmptyAnim_Shell") then + self:SetStatus(TFA.Enum.STATUS_RELOADING_LOOP_START_EMPTY) + else + self:SetStatus(TFA.Enum.STATUS_RELOADING_LOOP_START) + end + else + self:SetStatus(TFA.Enum.STATUS_RELOADING_LOOP_START) + end + + self:SetStatusEnd(ct + self:GetActivityLength(tanim, true, ttype)) + --self:SetNextPrimaryFire(ct + self:GetActivityLength( tanim, false ) ) + else + local _, tanim, ttype = self:ChooseReloadAnim() + + self:SetStatus(TFA.Enum.STATUS_RELOADING) + + if self:GetStatL("IsProceduralReloadBased") then + self:SetStatusEnd(ct + self:GetStatL("ProceduralReloadTime")) + else + self:SetStatusEnd(ct + self:GetActivityLength(tanim, true, ttype)) + self:SetNextPrimaryFire(ct + self:GetActivityLength(tanim, false, ttype)) + end + + if CLIENT then + self2.ReloadAnimationStart = ct + self2.ReloadAnimationEnd = ct + self:GetActivityLength(tanim, false, ttype) + end + end + + self:GetOwner():SetAnimation(PLAYER_RELOAD) + + if self:GetStatL("Secondary.ReloadSound") and IsFirstTimePredicted() then + self:EmitSound(self:GetStatL("Secondary.ReloadSound")) + end + + self:SetNextPrimaryFire( -1 ) + elseif released or self:KeyPressed(IN_RELOAD) then--if self:GetOwner():KeyPressed(IN_RELOAD) or not self:GetLegacyReloads() then + self:CheckAmmo() + end + end +end + +function SWEP:DoPump() + if hook.Run("TFA_Pump", self) then return end + + local _, tanim, activityType = self:PlayAnimation(self:GetStatL("PumpAction")) + + self:ScheduleStatus(TFA.Enum.STATUS_PUMP, self:GetActivityLength(tanim, true, activityType)) + self:SetNextPrimaryFire(l_CT() + self:GetActivityLength(tanim, false, activityType)) + self:SetNextIdleAnim(math.max(self:GetNextIdleAnim(), l_CT() + self:GetActivityLength(tanim, false, activityType))) +end + +function SWEP:LoadShell() + if hook.Run("TFA_LoadShell", self) then return end + + local _, tanim, ttype = self:ChooseReloadAnim() + + if self:GetActivityLength(tanim, true, ttype) < self:GetActivityLength(tanim, false, ttype) then + self:SetStatusEnd(ct + self:GetActivityLength(tanim, true, ttype)) + else + local sht = self:GetStatL("LoopedReloadInsertTime") + if sht then sht = sht / self:GetAnimationRate(ACT_VM_RELOAD) end + self:SetStatusEnd(ct + ( sht or self:GetActivityLength(tanim, true, ttype))) + end + + return TFA.Enum.STATUS_RELOADING_LOOP +end + +function SWEP:CompleteReload() + if hook.Run("TFA_CompleteReload", self) then return end + + local maxclip = self:GetPrimaryClipSizeForReload(true) + local curclip = self:Clip1() + local amounttoreplace = math.min(maxclip - curclip, self:Ammo1()) + self:TakePrimaryAmmo(amounttoreplace * -1) + self:TakePrimaryAmmo(amounttoreplace, true) + self:SetJammed(false) +end + +function SWEP:CheckAmmo() + if hook.Run("TFA_CheckAmmo", self) then return end + + local self2 = self:GetTable() + + if self2.GetIronSights(self) or self2.GetSprinting(self) then return end + + --if self2.NextInspectAnim == nil then + -- self2.NextInspectAnim = -1 + --end + + if self:GetOwner().GetInfoNum and self:GetOwner():GetInfoNum("cl_tfa_keys_inspect", 0) > 0 then + return + end + + if (self:GetActivityEnabled(ACT_VM_FIDGET) or self2.InspectionActions) and self:GetStatus() == TFA.Enum.STATUS_IDLE then--and CurTime() > self2.NextInspectAnim then + local _, tanim, ttype = self:ChooseInspectAnim() + self:ScheduleStatus(TFA.Enum.STATUS_FIDGET, self:GetActivityLength(tanim, false, ttype)) + end +end + +local cv_strip = GetConVar("sv_tfa_weapon_strip") + +function SWEP:DoAmmoCheck(clipID) + if self:GetOwner():IsNPC() then return end + if clipID == nil then clipID = 1 end + local self2 = self:GetTable() + + if IsValid(self) and SERVER and cv_strip:GetBool() and self["Clip" .. clipID](self) == 0 and self["Ammo" .. clipID](self) == 0 then + timer.Simple(.1, function() + if SERVER and IsValid(self) and self:OwnerIsValid() then + self:GetOwner():StripWeapon(self2.ClassName) + end + end) + end +end + +--[[ +Function Name: AdjustMouseSensitivity +Syntax: Should not normally be called. +Returns: SWEP sensitivity multiplier. +Purpose: Standard SWEP Function +]] + +local fovv +local sensval +local sensitivity_cvar, sensitivity_fov_cvar, sensitivity_speed_cvar +if CLIENT then + sensitivity_cvar = GetConVar("cl_tfa_scope_sensitivity") + sensitivity_fov_cvar = GetConVar("cl_tfa_scope_sensitivity_autoscale") + sensitivity_speed_cvar = GetConVar("sv_tfa_scope_gun_speed_scale") +end + +function SWEP:AdjustMouseSensitivity() + sensval = 1 + + if self:GetIronSights() then + sensval = sensval * sensitivity_cvar:GetFloat() / 100 + + if sensitivity_fov_cvar:GetBool() then + fovv = self:GetStatL("Secondary.OwnerFOV") or 70 + sensval = sensval * TFA.CalculateSensitivtyScale( fovv, nil, 1 ) + else + sensval = sensval + end + + if sensitivity_speed_cvar:GetFloat() then + -- weapon heaviness + sensval = sensval * self:GetStatL("AimingDownSightsSpeedMultiplier") + end + end + + sensval = sensval * l_Lerp(self:GetIronSightsProgress(), 1, self:GetStatL( "IronSightsSensitivity" ) ) + return sensval +end + +--[[ +Function Name: TranslateFOV +Syntax: Should not normally be called. Takes default FOV as parameter. +Returns: New FOV. +Purpose: Standard SWEP Function +]] + +local cv_fov_sprintmult = GetConVar("sv_tfa_fov_sprintmod") + +function SWEP:TranslateFOV(fov) + local self2 = self:GetTable() + + self2.LastTranslatedFOV = fov + + local retVal = hook.Run("TFA_PreTranslateFOV", self, fov) + + if retVal then return retVal end + + self2.CorrectScopeFOV(self) + + local ironprog = self2.IronSightsProgressPredicted or self2.GetIronSightsProgress(self) + if self2.GetStatL(self, "Secondary.OwnerFOVUseThreshold", self2.GetStatL(self, "Scoped")) then + local threshold = math.min(self2.GetStatL(self, "Secondary.OwnerFOVThreshold", self2.GetStatL(self, "ScopeOverlayThreshold")), 0.999999) + + ironprog = ironprog < threshold and 0 or math.max(ironprog - threshold, 0) / (1 - threshold) + end + + local nfov = l_Lerp(ironprog, fov, fov * math.min(self2.GetStatL(self, "Secondary.OwnerFOV") / 90, 1)) + + local ret = nfov + if cv_fov_sprintmult:GetBool() then + ret = l_Lerp(self2.SprintProgressPredicted or self2.GetSprintProgress(self), nfov, nfov + self2.GetStatL(self, "SprintFOVOffset", 5)) + end + + if self2.OwnerIsValid(self) and not self2.IsMelee then + local vpa = self:GetOwner():GetViewPunchAngles() + + ret = ret + math.abs(vpa.p) / 4 + math.abs(vpa.y) / 4 + math.abs(vpa.r) / 4 + end + + ret = hook.Run("TFA_TranslateFOV", self, ret) or ret + + return ret +end + +function SWEP:GetPrimaryAmmoType() + return self:GetStatL("Primary.Ammo") or "" +end + +function SWEP:ToggleInspect() + if self:GetOwner():IsNPC() then return false end -- NPCs can't look at guns silly + + local self2 = self:GetTable() + + if (self:GetSprinting() or self:GetIronSights() or self:GetStatus() ~= TFA.Enum.STATUS_IDLE) and not self:GetCustomizing() then return end + + self:SetCustomizing(not self:GetCustomizing()) + self2.Inspecting = self:GetCustomizing() + self:SetCustomizeUpdated(true) + + --if self2.Inspecting then + -- gui.EnableScreenClicker(true) + --else + -- gui.EnableScreenClicker(false) + --end + + return self:GetCustomizing() +end + +SWEP.ToggleCustomize = SWEP.ToggleInspect + +function SWEP:GetIsInspecting() + return self:GetCustomizing() +end + +function SWEP:CustomizingUpdated(_, old, new) + if old ~= new and self._inspect_hack ~= new then + self._inspect_hack = new + + if new then + self:OnCustomizationOpen() + else + self:OnCustomizationClose() + end + end +end + +function SWEP:OnCustomizationOpen() + -- override + -- example: + --[[ + if CLIENT then surface.PlaySound("ui/buttonclickrelease.wav") end + ]] +end + +function SWEP:OnCustomizationClose() + -- override +end + +function SWEP:CanBeJammed() + return self.CanJam and self:GetMaxClip1() > 0 and sv_tfa_jamming:GetBool() +end + +-- Use this to increase/decrease factor added based on ammunition/weather conditions/etc +function SWEP:GrabJamFactorMult() + return 1 -- override +end + +function SWEP:UpdateJamFactor() + local self2 = self:GetTable() + if not self:CanBeJammed() then return self end + self:SetJamFactor(math.min(100, self:GetJamFactor() + self2.JamFactor * sv_tfa_jamming_factor_inc:GetFloat() * self:GrabJamFactorMult())) + return self +end + +function SWEP:IsJammed() + if not self:CanBeJammed() then return false end + return self:GetJammed() +end + +function SWEP:NotifyJam() + local ply = self:GetOwner() + + if IsValid(ply) and ply:IsPlayer() and IsFirstTimePredicted() and (not ply._TFA_LastJamMessage or ply._TFA_LastJamMessage < RealTime()) then + ply:PrintMessage(HUD_PRINTCENTER, "#tfa.msg.weaponjammed") + ply._TFA_LastJamMessage = RealTime() + 4 + end +end + +function SWEP:CheckJammed() + if not self:IsJammed() then return false end + self:NotifyJam() + return true +end + +function SWEP:RollJamChance() + if not self:CanBeJammed() then return false end + if self:IsJammed() then return true end + + local chance = self:GetJamChance() + local roll = util.SharedRandom('tfa_base_jam', math.max(0.002711997795105, math.pow(chance, 1.19)), 1, l_CT()) + + if roll <= chance * sv_tfa_jamming_mult:GetFloat() then + self:SetJammed(true) + + if IsFirstTimePredicted() then + self:NotifyJam() + end + + return true + end + + return false +end + +function SWEP:GrabJamChanceMult() + return 1 -- override +end + +function SWEP:GetJamChance() + -- you can safely override this with your own logic if you desire + local self2 = self:GetTable() + if not self:CanBeJammed() then return 0 end + return self:GetJamFactor() * sv_tfa_jamming_factor:GetFloat() * (self2.JamChance / 100) * self:GrabJamChanceMult() +end + +SWEP.FlashlightSoundToggleOn = Sound("HL2Player.FlashLightOn") +SWEP.FlashlightSoundToggleOff = Sound("HL2Player.FlashLightOff") + +function SWEP:ToggleFlashlight(toState) + if toState == nil then + toState = not self:GetFlashlightEnabled() + end + + self:SetFlashlightEnabled(toState) + self:EmitSoundNet(self:GetStatL("FlashlightSoundToggle" .. (toState and "On" or "Off"))) +end + +-- source engine save load +function SWEP:OnRestore() + self:BuildAttachmentCache() + + self:InitializeAnims() + self:InitializeMaterialTable() + + self:IconFix() + self:RemoveEmptyRTCode() + + do -- attempt to restore attachments; weapons DO have owner so we don't need the precautions + local OldFD = self:GetIsFirstDeploy() + + self:SetIsFirstDeploy(true) -- so extmag attachments don't unload the clip + self.IsFirstDeploy = true + for attName, sel in pairs(self.AttachmentCache or {}) do + if sel then + local att = TFA.Attachments.Atts[attName] + + if att and att.Attach then + att:Attach(self) + end + end + end + self:SetIsFirstDeploy(OldFD) + self.IsFirstDeploy = OldFD + end +end + +-- lua autorefresh / weapons.Register +function SWEP:OnReloaded() + -- queue to next game frame since gmod is a fucking idiot + timer.Simple(0, function() + if not self:IsValid() then return end + + local baseclassSelf = table.Copy(baseclass.Get(self:GetClass())) + if not baseclassSelf then return end + + local self2 = self:GetTable() + + self2.Primary_TFA.RangeFalloffLUTBuilt = nil + self2.Primary.RangeFalloffLUTBuilt = nil + + --TFA.MigrateStructure(self, baseclassSelf, self:GetClass(), true) + --TFA.MigrateStructure(self, self2, self:GetClass(), true) + + if istable(baseclassSelf.Primary) then + self2.Primary_TFA = table.Copy(baseclassSelf.Primary) + TFA.UnfoldBaseClass(baseclassSelf.Primary) + end + + if istable(baseclassSelf.Secondary) then + self2.Secondary_TFA = table.Copy(baseclassSelf.Secondary) + TFA.UnfoldBaseClass(baseclassSelf.Secondary) + end + + self2.StatCache_Blacklist = baseclassSelf.StatCache_Blacklist + TFA.UnfoldBaseClass(self2.StatCache_Blacklist) + + if self2.StatCache_Blacklist_Real then + table.Merge(self2.StatCache_Blacklist, self2.StatCache_Blacklist_Real) + end + + self2.StatCache_Blacklist_Real = patch_blacklist(self2.StatCache_Blacklist, self2.TFADataVersion) + + self2.event_table_warning = false + self2.event_table_built = false + + self2.AutoDetectMuzzle(self) + self2.AutoDetectDamage(self) + self2.AutoDetectDamageType(self) + self2.AutoDetectForce(self) + self2.AutoDetectPenetrationPower(self) + self2.AutoDetectKnockback(self) + self2.AutoDetectSpread(self) + self2.AutoDetectRange(self) + self2.IconFix(self) + self2.RemoveEmptyRTCode(self) + self2.ClearStatCache(self) + end) +end + +function SWEP:ProcessLoopSound() + if sp and not SERVER then return end + if self:GetNextLoopSoundCheck() < 0 or ct < self:GetNextLoopSoundCheck() or self:GetStatus() == TFA.Enum.STATUS_SHOOTING then return end + + self:SetNextLoopSoundCheck(-1) + + local tgtSound = self:GetStatL("Primary.LoopSound") + + if self:GetSilenced() then + tgtSound = self:GetStatL("Primary.LoopSoundSilenced", tgtSound) + end + + if tgtSound then + self:StopSoundNet(tgtSound) + end + + if (not sp and SERVER) or not self:IsFirstPerson() then + tgtSound = self:GetSilenced() and self:GetStatL("Primary.LoopSoundSilenced_World", tgtSound) or self:GetStatL("Primary.LoopSound_World", tgtSound) + + if tgtSound then + self:StopSoundNet(tgtSound) + end + end + + tgtSound = self:GetStatL("Primary.LoopSoundTail") + + if self:GetSilenced() then + tgtSound = self:GetStatL("Primary.LoopSoundTailSilenced", tgtSound) + end + + if (not sp and SERVER) or not self:IsFirstPerson() then + tgtSound = self:GetSilenced() and self:GetStatL("Primary.LoopSoundTailSilenced_World", tgtSound) or self:GetStatL("Primary.LoopSoundTail_World", tgtSound) + end + + if tgtSound and (SERVER or self.GunfireLoopIFTPHack) then + self:EmitSoundNet(tgtSound, nil, true) + self.GunfireLoopIFTPHack = false + end +end + +function SWEP:ProcessLoopFire() + if sp and not IsFirstTimePredicted() then return end + if (self:GetStatus() == TFA.Enum.STATUS_SHOOTING ) then + if TFA.Enum.ShootLoopingStatus[self:GetShootStatus()] then + self:SetShootStatus(TFA.Enum.SHOOT_LOOP) + end + else --not shooting + if (not TFA.Enum.ShootReadyStatus[self:GetShootStatus()]) then + if ( self:GetShootStatus() ~= TFA.Enum.SHOOT_CHECK ) then + self:SetShootStatus(TFA.Enum.SHOOT_CHECK) --move to check first + else --if we've checked for one more tick that we're not shooting + self:SetShootStatus(TFA.Enum.SHOOT_IDLE) --move to check first + + if TFA.Enum.ReadyStatus[self:GetStatus()] then + self:PlayAnimation(self:GetStatL("ShootAnimation.out")) --exit + + -- force flip walking and sprinting flags when needed + if self:GetWalking() then + self.walking_updated = true + end + if self:GetSprinting() then + self.sprinting_updated = true + end + end + end + end + end +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_knife_base.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_knife_base.lua new file mode 100644 index 0000000..afdd774 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_knife_base.lua @@ -0,0 +1,352 @@ +if SERVER then + AddCSLuaFile() +end + +DEFINE_BASECLASS("tfa_gun_base") +SWEP.DrawCrosshair = true +SWEP.Primary.Automatic = true +SWEP.Secondary.Automatic = true +SWEP.Primary.RPM = 120 --Primary Slashs per minute +SWEP.Secondary.RPM = 60 --Secondary stabs per minute +SWEP.Primary.Delay = 0.15 --Delay for hull (primary) +SWEP.Secondary.Delay = 0.33 --Delay for hull (secondary) +SWEP.Primary.Length = 32 +SWEP.Secondary.Length = 48 +SWEP.Primary.Sound = Sound("Weapon_Knife.Slash") --Sounds +SWEP.KnifeShink = "Weapon_Knife.HitWall" --Sounds +SWEP.KnifeSlash = "Weapon_Knife.Hit" --Sounds +SWEP.KnifeStab = "Weapon_Knife.Slash" --Sounds +SWEP.SlashTable = {"midslash1", "midslash2"} --Table of possible hull sequences +SWEP.StabTable = {"stab"} --Table of possible hull sequences +SWEP.StabMissTable = {"stab_miss"} --Table of possible hull sequences +SWEP.DisableIdleAnimations = false --Enable idles +--[[ Don't Edit Below ]] +-- +SWEP.DamageType = DMG_SLASH +SWEP.MuzzleFlashEffect = "" --No muzzle +SWEP.DoMuzzleFlash = false --No muzzle +SWEP.WeaponLength = 1 --No nearwall +SWEP.Primary.Ammo = "" -- pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, AirboatGun +SWEP.Primary.ClipSize = 1 -- Size of a clip +SWEP.Primary.DefaultClip = 1 -- Bullets you start with +SWEP.Primary.AmmoConsumption = 0 +SWEP.Secondary.AmmoConsumption = 0 +SWEP.data = {} --No ironsights +SWEP.Secondary.IronSightsEnabled = false --No ironsights +SWEP.Secondary.DisplaySpread = false +SWEP.IsMelee = true + +SWEP.CrosshairConeRecoilOverride = .05 + +SWEP.HullData = { + hullMin = Vector(-16, -16, -16), + hullMax = Vector(16, 16, 16) +} + +SWEP.Primary.DisplayFalloff = false + +SWEP.SlashCounter = 1 +SWEP.StabCounter = 1 + +function SWEP:Deploy() + return BaseClass.Deploy(self) +end + +local lim_up_vec = Vector(1,1,0.1) + +function SWEP:ApplyForce(ent, force, posv, now) + if not IsValid(ent) or not ent.GetPhysicsObjectNum then return end + + if now then + if ent.GetRagdollEntity then + ent = ent:GetRagdollEntity() or ent + end + + if not IsValid(ent) then return end + local phys = ent:GetPhysicsObjectNum(0) + + if IsValid(phys) then + if ent:IsPlayer() or ent:IsNPC() then + ent:SetVelocity( force * 0.1 * lim_up_vec ) + phys:SetVelocity(phys:GetVelocity() + force * 0.1 * lim_up_vec ) + else + phys:ApplyForceOffset(force, posv) + end + end + else + timer.Simple(0, function() + if IsValid(self) and self:OwnerIsValid() and IsValid(ent) then + self:ApplyForce(ent, force, posv, true) + end + end) + end +end + +function SWEP:SlashSound(tr) + if IsFirstTimePredicted() then + if tr.Hit then + if tr.MatType == MAT_FLESH or tr.MatType == MAT_ALIENFLESH then + self:EmitSound(self.KnifeSlash) + else + self:EmitSound(self.KnifeShink) + end + else + self:EmitSound(self.Primary_TFA.Sound) + end + end +end + +local sp = game.SinglePlayer() + +function SWEP:GetSlashTrace(tbl, fwd) + local ow = self:GetOwner() + + if not sp and ow:IsPlayer() then + ow:LagCompensation(true) + end + + local traceRes = util.TraceLine(tbl) + + if (not traceRes.Hit) then + if not self.HullData.Radius then + self.HullData.Radius = self.HullData.hullMin:Distance(self.HullData.hullMax) / 2 + end + + local hd = self.HullData + tbl.mins = -hd.hullMin + tbl.maxs = hd.hullMax + tbl.endpos = tbl.endpos - fwd * hd.Radius + traceRes = util.TraceHull(tbl) + end + + if not sp and ow:IsPlayer() then + ow:LagCompensation(false) + end + + return traceRes +end + +function SWEP:SmackDamage(tr, fwd, primary) + if not tr.Entity:IsValid() then return end + if tr.Entity.LVS then return end + local dmg, force + + if primary then + dmg = self:GetStatL("Primary.Damage") + else + dmg = self:GetStatL("Secondary.Damage") + end + + force = dmg * 25 + local dmginfo = DamageInfo() + dmginfo:SetAttacker(self:GetOwner()) + dmginfo:SetInflictor(self) + dmginfo:SetDamage(dmg) + dmginfo:SetDamageType(self.DamageType) + dmginfo:SetDamagePosition(tr.HitPos) + dmginfo:SetReportedPosition(tr.StartPos) + dmginfo:SetDamageForce(fwd * force) + tr.Entity:DispatchTraceAttack(dmginfo, tr, fwd) + self:ApplyForce( tr.Entity, dmginfo:GetDamageForce(), tr.HitPos ) +end + +function SWEP:SmackEffect(tr) + local vSrc = tr.StartPos + local bFirstTimePredicted = IsFirstTimePredicted() + local bHitWater = bit.band(util.PointContents(vSrc), MASK_WATER) ~= 0 + local bEndNotWater = bit.band(util.PointContents(tr.HitPos), MASK_WATER) == 0 + + local trSplash = bHitWater and bEndNotWater and util.TraceLine({ + start = tr.HitPos, + endpos = vSrc, + mask = MASK_WATER + }) or not (bHitWater or bEndNotWater) and util.TraceLine({ + start = vSrc, + endpos = tr.HitPos, + mask = MASK_WATER + }) + + if (trSplash and bFirstTimePredicted) then + local data = EffectData() + data:SetOrigin(trSplash.HitPos) + data:SetScale(1) + + if (bit.band(util.PointContents(trSplash.HitPos), CONTENTS_SLIME) ~= 0) then + data:SetFlags(1) --FX_WATER_IN_SLIME + end + + util.Effect("watersplash", data) + end + + self:DoImpactEffect(tr, self.DamageType) + + if (tr.Hit and bFirstTimePredicted and not trSplash) then + local data = EffectData() + data:SetOrigin(tr.HitPos) + data:SetStart(vSrc) + data:SetSurfaceProp(tr.SurfaceProps) + data:SetDamageType(self.DamageType) + data:SetHitBox(tr.HitBox) + data:SetEntity(tr.Entity) + util.Effect("Impact", data) + end +end + +local tracedata = {} + +function SWEP:Slash(bPrimary) + local ow, gsp, ea, fw, tr, rpm, delay + + if bPrimary == nil then + bPrimary = true + end + + ow = self:GetOwner() + gsp = ow:GetShootPos() + ea = ow:EyeAngles() + fw = ea:Forward() + tracedata.start = gsp + tracedata.endpos = gsp + fw * (bPrimary and self.Primary_TFA.Length or self.Secondary_TFA.Length) + tracedata.filter = ow + + tr = self:GetSlashTrace(tracedata, fw) + rpm = self:GetStatL("Primary.RPM") + delay = self:GetStatL("Primary.Delay") + self:SlashSound(tr) + self:SmackDamage(tr, fw, bPrimary) + self:SmackEffect(tr, fw, bPrimary) + self:ScheduleStatus(TFA.Enum.STATUS_SHOOTING, 60 / rpm - delay) +end + +function SWEP:CanAttack() + return self:CanPrimaryAttack() -- UNUSED, kept for backwards compatibility +end + +function SWEP:PrimaryAttack() + local ply = self:GetOwner() + if not ply:IsPlayer() or self:GetOwner():KeyDown(IN_RELOAD) then return end + + if not self:VMIV() then return end + if not self:CanPrimaryAttack() then return end + + self:PrePrimaryAttack() + + if hook.Run("TFA_PrimaryAttack", self) then return end + + self.SlashCounter = self.SlashCounter + 1 + + if self.SlashCounter > #self.SlashTable then + self.SlashCounter = 1 + end + + self:SendViewModelSeq(self.SlashTable[self.SlashCounter]) + + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:SetNextPrimaryFire(CurTime() + 1 / (self.Primary_TFA.RPM / 60)) + self:SetNextSecondaryFire(self:GetNextPrimaryFire()) + self:ScheduleStatus(TFA.Enum.STATUS_KNIFE_SLASH, self.Primary_TFA.Delay) + + self:PostPrimaryAttack() + hook.Run("TFA_PostPrimaryAttack", self) +end + +function SWEP:SecondaryAttack() + local ply = self:GetOwner() + if not ply:IsPlayer() or self:GetOwner():KeyDown(IN_RELOAD) then return end + + if not self:VMIV() then return end + if not self:CanSecondaryAttack() then return end + + self:PreSecondaryAttack() + + if hook.Run("TFA_SecondaryAttack", self) then return end + + local ow, gsp, ea, fw, tr + + ow = self:GetOwner() + gsp = ow:GetShootPos() + ea = ow:EyeAngles() + fw = ea:Forward() + tracedata.start = gsp + tracedata.endpos = gsp + fw * self.Secondary_TFA.Length + tracedata.filter = ow + + tr = self:GetSlashTrace(tracedata, fw) + + if tr.Hit then + self.StabIndex = self.StabIndex or 0 + self.StabIndex = self.StabIndex + 1 + + if self.StabIndex > #self.StabTable then + self.StabIndex = 1 + end + + self:SendViewModelSeq(self.StabTable[self.StabIndex]) + else + self.StabMiss = self.StabMiss or 0 + self.StabMiss = self.StabMiss + 1 + + if self.StabMiss > #self.StabMissTable then + self.StabMiss = 1 + end + + self:SendViewModelSeq(self.StabMissTable[self.StabMiss]) + end + + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:SetNextPrimaryFire(CurTime() + 60 / self.Secondary_TFA.RPM) + self:SetNextSecondaryFire(self:GetNextPrimaryFire()) + self:ScheduleStatus(TFA.Enum.STATUS_KNIFE_STAB, self.Secondary_TFA.Delay) + + self:PostSecondaryAttack() + hook.Run("TFA_PostSecondaryAttack", self) +end + +function SWEP:ThrowKnife() + if not IsFirstTimePredicted() then return end + self:EmitSound(self.Primary_TFA.Sound) + + if SERVER then + local ent = ents.Create("tfa_thrown_blade") + + if ent:IsValid() then + ent:SetPos(self:GetOwner():GetShootPos()) + ent:SetAngles(self:GetOwner():EyeAngles()) + ent:SetModel(self.Primary_TFA.ProjectileModel or self.WorldModel) + ent:SetOwner(self:GetOwner()) + ent:SetPhysicsAttacker(self:GetOwner()) + ent:Spawn() + ent:Activate() + ent:SetNW2String("ClassName", self:GetClass()) + local phys = ent:GetPhysicsObject() + + if IsValid(phys) then + phys:SetVelocity(self:GetOwner():GetAimVector() * 1250) + phys:AddAngleVelocity(Vector(0, 480, 0)) + end + + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:GetOwner():StripWeapon(self:GetClass()) + end + end +end + +function SWEP:Reload() + if not self:OwnerIsValid() and self:GetOwner():KeyDown(IN_RELOAD) then return end + self:ThrowKnife() +end + +function SWEP:Think2(...) + if self:GetStatus() == TFA.Enum.STATUS_KNIFE_STAB and CurTime() > self:GetStatusEnd() then + self:Slash(false) + elseif self:GetStatus() == TFA.Enum.STATUS_KNIFE_SLASH and CurTime() > self:GetStatusEnd() then + self:Slash(true) + end + + BaseClass.Think2(self, ...) +end + +SWEP.IsKnife = true +SWEP.WeaponLength = 8 + +TFA.FillMissingMetaValues(SWEP) diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/cl_init.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/init.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/init.lua new file mode 100644 index 0000000..cfa6392 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/init.lua @@ -0,0 +1,15 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +function SWEP:GetNPCRestTimes() + return 0, 0 +end + +function SWEP:GetNPCBurstSettings() + return 1, 1, 1 +end + +function SWEP:GetNPCBulletSpread() + return 1 -- we handle this manually, in calculate cone, recoil and shootbullet +end diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/shared.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/shared.lua new file mode 100644 index 0000000..f669225 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_melee_base/shared.lua @@ -0,0 +1,988 @@ +DEFINE_BASECLASS("tfa_bash_base") +SWEP.DrawCrosshair = true +SWEP.SlotPos = 72 +SWEP.Slot = 0 +SWEP.WeaponLength = 8 +SWEP.Secondary.IronSightsEnabled = false +SWEP.Secondary.DisplaySpread = false +SWEP.Primary.Directional = false +SWEP.Primary.Attacks = {} +--[[{ +{ +["act"] = ACT_VM_HITLEFT, -- Animation; ACT_VM_THINGY, ideally something unique per-sequence +["len"] = 8 * 4.5, -- Trace distance +["src"] = Vector(20,10,0), -- Trace source; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) +["dir"] = Vector(-40,30,0), -- Trace direction/length; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) +["dmg"] = 60, --Damage +["dmgtype"] = DMG_SLASH, --DMG_SLASH,DMG_CRUSH, etc. +["delay"] = 0.2, --Delay +["spr"] = true, --Allow attack while sprinting? +["snd"] = "Swing.Sound", -- Sound ID +["viewpunch"] = Angle(1,-10,0), --viewpunch angle +["end"] = 1, --time before next attack +["hull"] = 10, --Hullsize +["direction"] = "L", --Swing direction +["combotime"] = 0.2 --If you hold attack down, attack this much earlier +}, +{ +["act"] = ACT_VM_HITRIGHT, -- Animation; ACT_VM_THINGY, ideally something unique per-sequence +["len"] = 8 * 4.5, -- Trace distance +["src"] = Vector(-10,10,0), -- Trace source; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) +["dir"] = Vector(40,30,0), -- Trace direction/length; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) +["dmg"] = 60, --Damage +["dmgtype"] = DMG_SLASH, --DMG_SLASH,DMG_CRUSH, etc. +["delay"] = 0.2, --Delay +["spr"] = true, --Allow attack while sprinting? +["snd"] = "Swing.Sound", -- Sound ID +["viewpunch"] = Angle(1,10,0), --viewpunch angle +["end"] = 1, --time before next attack +["hull"] = 10, --Hullsize +["direction"] = "R", --Swing direction +["combotime"] = 0.2 --If you hold attack down, attack this much earlier +} +} + +SWEP.Secondary.Attacks = { +{ +["act"] = ACT_VM_MISSCENTER, -- Animation; ACT_VM_THINGY, ideally something unique per-sequence +["src"] = Vector(0,5,0), -- Trace source; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) +["dir"] = Vector(0,50,0), -- Trace direction/length; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down ) +["dmg"] = 60, --Damage +["dmgtype"] = DMG_SLASH, --DMG_SLASH,DMG_CRUSH, etc. +["delay"] = 0.2, --Delay +["spr"] = true, --Allow attack while sprinting? +["snd"] = "Swing.Sound", -- Sound ID +["viewpunch"] = Angle(5,0,0), --viewpunch angle +["end"] = 1, --time before next attack +["callback"] = function(tbl,wep,tr) end, +["kickback"] = nil--Recoil if u hit something with this activity +} +} +]] +-- +SWEP.IsMelee = true +SWEP.Precision = 9 --Traces to use per attack +SWEP.Primary.MaxCombo = 3 --Max amount of times you'll attack by simply holding down the mouse; -1 to unlimit +SWEP.Secondary.MaxCombo = 3 --Max amount of times you'll attack by simply holding down the mouse; -1 to unlimit +SWEP.CanBlock = false + +SWEP.BlockAnimation = { + ["in"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_DEPLOY, --Number for act, String/Number for sequence + ["transition"] = true + }, + --Inward transition + ["loop"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_IDLE_DEPLOYED, --Number for act, String/Number for sequence + ["is_idle"] = true + }, + --looping animation + ["hit"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_RELOAD_DEPLOYED, --Number for act, String/Number for sequence + ["is_idle"] = true + }, + --when you get hit and block it + ["out"] = { + ["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act + ["value"] = ACT_VM_UNDEPLOY, --Number for act, String/Number for sequence + ["transition"] = true + } +} + +--Outward transition +SWEP.BlockDamageTypes = {DMG_SLASH, DMG_CLUB} +SWEP.BlockCone = 135 --Think of the player's view direction as being the middle of a sector, with the sector's angle being this +SWEP.BlockDamageMaximum = 0.1 --Multiply damage by this for a maximumly effective block +SWEP.BlockDamageMinimum = 0.4 --Multiply damage by this for a minimumly effective block +SWEP.BlockTimeWindow = 0.5 --Time to absorb maximum damage +SWEP.BlockTimeFade = 1 --Time for blocking to do minimum damage. Does not include block window +SWEP.BlockDamageCap = 100 +SWEP.BlockSound = "" +SWEP.BlockFadeOut = nil --Override the length of the ["out"] block animation easily +SWEP.BlockFadeOutEnd = 0.2 --In absense of BlockFadeOut, shave this length off of the animation time +SWEP.BlockHoldType = "magic" +SWEP.BlockCanDeflect = true --Can "bounce" bullets off a perfect parry? +SWEP.Secondary.Directional = true +SWEP.Primary.Automatic = true +SWEP.Secondary.Automatic = true +SWEP.ImpactDecal = "ManhackCut" +SWEP.Secondary.CanBash = false +SWEP.Secondary.PrimaryFallback = true +SWEP.DefaultComboTime = 0.2 +SWEP.AllowSprintAttack = true +SWEP.Primary.SplitDamage = true +SWEP.Secondary.SplitDamage = true + +--[[ START OF BASE CODE ]] +-- +SWEP.Primary.ClipSize = -1 +SWEP.Primary.AmmoConsumption = 0 +SWEP.Secondary.AmmoConsumption = 0 +SWEP.Primary.Ammo = "" +SWEP.Seed = 0 +SWEP.AttackSoundTime = -1 +SWEP.VoxSoundTime = -1 + +SWEP.Primary.DisplayFalloff = false + +SWEP.CrosshairConeRecoilOverride = .05 + +function SWEP:SetupDataTables() + BaseClass.SetupDataTables(self) + + self:NetworkVarTFA("Bool", "VP") + self:NetworkVarTFA("Bool", "BashImpulse") + + self:NetworkVarTFA("Float", "VPTime") + self:NetworkVarTFA("Float", "VPPitch") + self:NetworkVarTFA("Float", "VPYaw") + self:NetworkVarTFA("Float", "VPRoll") + + self:NetworkVarTFA("Int", "ComboCount") + self:NetworkVarTFA("Int", "MelAttackID") + + self:SetMelAttackID(1) + self:SetVP(false) + self:SetVPPitch(0) + self:SetVPYaw(0) + self:SetVPRoll(0) + self:SetVPTime(-1) + self:SetComboCount(0) +end + +function SWEP:Deploy() + self:SetMelAttackID(1) + self:SetVP(false) + self:SetVPPitch(0) + self:SetVPYaw(0) + self:SetVPRoll(0) + self:SetVPTime(-1) + self.up_hat = false + self:SetComboCount(0) + self:AddNZAnimations() + + return BaseClass.Deploy(self) +end + +function SWEP:AddNZAnimations() + if self.Primary.Attacks then + for _, v in pairs(self.Primary.Attacks) do + if v.act then + self.DTapActivities[v.act] = true + end + end + end + + if self.Secondary.Attacks then + for _, v in pairs(self.Secondary.Attacks) do + if v.act then + self.DTapActivities[v.act] = true + end + end + end +end + +function SWEP:CanInterruptShooting() + return false +end + +local att = {} +local tr = {} +local traceres = {} +local pos, ang, mdl, ski, prop +local fwd, eang, scl, dirv +local strikedir = Vector() +SWEP.hpf = false +SWEP.hpw = false +local lim_up_vec = Vector(1, 1, 0.05) + +function SWEP:ApplyForce(ent, force, posv) + if not IsValid(ent) or not ent.GetPhysicsObjectNum then return end + if hook.Run("TFAMeleeApplyForce", ent) ~= false then return end + + if ent.GetRagdollEntity and IsValid(ent:GetRagdollEntity()) and ent ~= ent:GetRagdollEntity() then + ent = ent:GetRagdollEntity() + + timer.Simple(0, function() + if IsValid(self) and self:OwnerIsValid() and IsValid(ent) then + self:ApplyForce(ent, force, posv, false) + end + end) + + return + end + + if not IsValid(ent) then return end + + if ent:IsPlayer() or ent:IsNPC() then + ent:SetVelocity(force * 0.1 * lim_up_vec) + end + + if ent:GetPhysicsObjectCount() > 1 then + for i = 0, ent:GetPhysicsObjectCount() - 1 do + local phys = ent:GetPhysicsObjectNum(i) + + if IsValid(phys) then + phys:ApplyForceOffset(force / ent:GetPhysicsObjectCount(), posv) + end + end + else + local phys = ent:GetPhysicsObjectNum(0) + + if IsValid(phys) then + phys:ApplyForceOffset(force, posv) + end + end +end + +function SWEP:ApplyDamage(trace, dmginfo, attk) + local dam, force = dmginfo:GetBaseDamage(), dmginfo:GetDamageForce() + dmginfo:SetDamagePosition(trace.HitPos) + dmginfo:SetReportedPosition(trace.StartPos) + trace.Entity:DispatchTraceAttack(dmginfo, trace, fwd) + dmginfo:SetDamage(dam) + dmginfo:SetDamageForce(force) + -- dmginfo:SetAttacker( self:GetOwner() ) + self:ApplyForce(trace.Entity, dmginfo:GetDamageForce(), trace.HitPos) + dmginfo:SetDamage(dam) + dmginfo:SetDamageForce(force) + -- dmginfo:SetAttacker( self:GetOwner() ) +end + +function SWEP:SmackEffect(trace, dmg) + local vSrc = trace.StartPos + local bFirstTimePredicted = IsFirstTimePredicted() + local bHitWater = bit.band(util.PointContents(vSrc), MASK_WATER) ~= 0 + local bEndNotWater = bit.band(util.PointContents(trace.HitPos), MASK_WATER) == 0 + + local trSplash = bHitWater and bEndNotWater and util.TraceLine({ + start = trace.HitPos, + endpos = vSrc, + mask = MASK_WATER + }) or not (bHitWater or bEndNotWater) and util.TraceLine({ + start = vSrc, + endpos = trace.HitPos, + mask = MASK_WATER + }) + + if (trSplash and bFirstTimePredicted) then + local data = EffectData() + data:SetOrigin(trSplash.HitPos) + data:SetScale(1) + + if (bit.band(util.PointContents(trSplash.HitPos), CONTENTS_SLIME) ~= 0) then + data:SetFlags(1) --FX_WATER_IN_SLIME + end + + util.Effect("watersplash", data) + end + + local dam, force, dt = dmg:GetBaseDamage(), dmg:GetDamageForce(), dmg:GetDamageType() + + if (trace.Hit and bFirstTimePredicted and (not trSplash) and self:DoImpactEffect(trace, dt) ~= true) then + local data = EffectData() + data:SetOrigin(trace.HitPos) + data:SetStart(vSrc) + data:SetSurfaceProp(trace.SurfaceProps) + data:SetDamageType(dt) + data:SetHitBox(trace.HitBox) + data:SetEntity(trace.Entity) + util.Effect("Impact", data) + end + + dmg:SetDamage(dam) + dmg:SetDamageForce(force) + -- dmg:SetAttacker( self:GetOwner() ) +end + +local defaultdoorhealth = 250 +local cv_doorres = GetConVar("sv_tfa_door_respawn") + +function SWEP:MakeDoor(ent, dmginfo) + pos = ent:GetPos() + ang = ent:GetAngles() + mdl = ent:GetModel() + ski = ent:GetSkin() + ent:SetNotSolid(true) + ent:SetNoDraw(true) + prop = ents.Create("prop_physics") + prop:SetPos(pos) + prop:SetAngles(ang) + prop:SetModel(mdl) + prop:SetSkin(ski or 0) + prop:Spawn() + prop:SetVelocity(dmginfo:GetDamageForce() * 48) + prop:GetPhysicsObject():ApplyForceOffset(dmginfo:GetDamageForce() * 48, dmginfo:GetDamagePosition()) + if IsValid(dmginfo:GetAttacker()) then + prop:SetPhysicsAttacker(dmginfo:GetAttacker()) + end + prop:EmitSound("physics/wood/wood_furniture_break" .. tostring(math.random(1, 2)) .. ".wav", 110, math.random(90, 110)) + + if cv_doorres and cv_doorres:GetInt() ~= -1 then + timer.Create("TFA_DoorRespawner_" .. ent:EntIndex(), cv_doorres:GetFloat(), 1, function() + if IsValid(prop) then + prop:Remove() + end + + if IsValid(ent) then + ent.TFADoorHealth = defaultdoorhealth + ent:SetNotSolid(false) + ent:SetNoDraw(false) + end + end) + end +end + +local cv_doordestruction = GetConVar("sv_tfa_melee_doordestruction") + +function SWEP:BurstDoor(ent, dmginfo) + if not ents.Create then return end + + if not cv_doordestruction:GetBool() then return end + + if dmginfo:GetDamage() > 60 and (dmginfo:IsDamageType(DMG_CRUSH) or dmginfo:IsDamageType(DMG_CLUB)) and (ent:GetClass() == "func_door_rotating" or ent:GetClass() == "prop_door_rotating") then + if dmginfo:GetDamage() > 150 then + local ply = self:GetOwner() + self:MakeDoor(ent, dmginfo) + ply:EmitSound("ambient/materials/door_hit1.wav", 100, math.random(90, 110)) + else + local ply = self:GetOwner() + ply:EmitSound("ambient/materials/door_hit1.wav", 100, math.random(90, 110)) + ply.oldname = ply:GetName() + ply:SetName("bashingpl" .. ply:EntIndex()) + ent:SetKeyValue("Speed", "500") + ent:SetKeyValue("Open Direction", "Both directions") + ent:SetKeyValue("opendir", "0") + ent:Fire("unlock", "", .01) + ent:Fire("openawayfrom", "bashingpl" .. ply:EntIndex(), .01) + + timer.Simple(0.02, function() + if IsValid(ply) then + ply:SetName(ply.oldname) + end + end) + + timer.Simple(0.3, function() + if IsValid(ent) then + ent:SetKeyValue("Speed", "100") + end + end) + end + end +end + +function SWEP:ThinkNPC() + local ow = self:GetOwner() + if ow:IsCurrentSchedule(SCHED_CHASE_ENEMY) then return end + if ow:IsCurrentSchedule(SCHED_MELEE_ATTACK1) then return end + if not self.Range then + local _, t = self:ChoosePrimaryAttack() + if t and t.range then + self.Range = t.src:Length() + t.dir:Length() + else + self.Range = 80 + end + end + local en = ow:GetEnemy() + if IsValid(en) and en:GetPos():Distance(self:GetPos()) <= self.Range and CurTime() > self:GetNextPrimaryFire() then + self:PrimaryAttack() + else + self:GetOwner():SetSchedule( SCHED_CHASE_ENEMY ) + end +end + +function SWEP:Think2(...) + if not self:VMIV() then return end + + if (not self:GetOwner():KeyDown(IN_ATTACK)) and (not self:GetOwner():KeyDown(IN_ATTACK2)) then + self:SetComboCount(0) + end + + if self:GetVP() and CurTime() > self:GetVPTime() then + self:SetVP(false) + self:SetVPTime(-1) + self:GetOwner():ViewPunch(Angle(self:GetVPPitch(), self:GetVPYaw(), self:GetVPRoll())) + end + + if self.CanBlock then + local stat = self:GetStatus() + + if self:GetBashImpulse() and TFA.Enum.ReadyStatus[stat] and not self:GetOwner():KeyDown(IN_USE) then + self:SetStatus(TFA.Enum.STATUS_BLOCKING, math.huge) + + if self.BlockAnimation["in"] then + self:PlayAnimation(self.BlockAnimation["in"]) + elseif self.BlockAnimation["loop"] then + self:PlayAnimation(self.BlockAnimation["loop"]) + end + + self.BlockStart = CurTime() + elseif stat == TFA.Enum.STATUS_BLOCKING and not self:GetBashImpulse() then + local _, tanim, ttype + + if self.BlockAnimation["out"] then + _, tanim, ttype = self:PlayAnimation(self.BlockAnimation["out"]) + else + _, tanim, ttype = self:ChooseIdleAnim() + end + + self:ScheduleStatus(TFA.Enum.STATUS_BLOCKING_END, self.BlockFadeOut or (self:GetActivityLength(tanim, false, ttype) - self.BlockFadeOutEnd)) + elseif stat == TFA.Enum.STATUS_BLOCKING and CurTime() > self:GetNextIdleAnim() then + self:ChooseIdleAnim() + end + end + + self:StrikeThink() + BaseClass.Think2(self, ...) +end + +function SWEP:ProcessHoldType(...) + if self:GetStatus() == TFA.Enum.STATUS_BLOCKING then + self:SetHoldType(self.BlockHoldType or "magic") + + return self.BlockHoldType or "magic" + else + return BaseClass.ProcessHoldType(self, ...) + end +end + +function SWEP:GetBlockStart() + return self.BlockStart or -1 +end + +function SWEP:ChooseBlockAnimation() + if self.BlockAnimation["hit"] then + self:PlayAnimation(self.BlockAnimation["hit"]) + elseif self.BlockAnimation["in"] then + self:PlayAnimation(self.BlockAnimation["in"]) + end +end + +function SWEP:ChooseIdleAnim(...) + if self.CanBlock and self:GetStatus() == TFA.Enum.STATUS_BLOCKING and self.BlockAnimation["loop"] then + return self:PlayAnimation(self.BlockAnimation["loop"]) + else + return BaseClass.ChooseIdleAnim(self, ...) + end +end + +function SWEP:StrikeThink() + if self:GetSprinting() and not self:GetStatL("AllowSprintAttack", false) then + self:SetComboCount(0) + --return + end + + if self:IsSafety() then + self:SetComboCount(0) + + return + end + + if not IsFirstTimePredicted() then return end + if self:GetStatus() ~= TFA.Enum.STATUS_SHOOTING then return end + if self.up_hat then return end + + local ind = self:GetMelAttackID() or 1 + local srctbl = ind >= 0 and "Primary" or "Secondary" + local attackstbl = self:GetStatL(srctbl .. ".Attacks") + local attack = attackstbl[math.abs(ind)] + + if self.AttackSoundTime ~= -1 and CurTime() > self.AttackSoundTime then + self:EmitSound(attack.snd) + + if self:GetOwner().Vox then + self:GetOwner():Vox("bash", 4) + end + + self.AttackSoundTime = -1 + end + + if self:GetOwner().Vox and self.VoxSoundTime ~= -1 and CurTime() > self.VoxSoundTime - self:GetOwner():Ping() * 0.001 then + if self:GetOwner().Vox then + self:GetOwner():Vox("bash", 4) + end + + self.VoxSoundTime = -1 + end + + if CurTime() > self:GetStatusEnd() then + self.DamageType = attack.dmgtype + --Just attacked, so don't do it again + self.up_hat = true + self:SetStatus(TFA.Enum.STATUS_IDLE, math.huge) + + if self:GetComboCount() > 0 then + self:SetNextPrimaryFire(self:GetNextPrimaryFire() - (attack.combotime or 0)) + self:SetNextSecondaryFire(self:GetNextSecondaryFire() - (attack.combotime or 0)) + end + + self:Strike(attack, self.Precision) + end +end + +local function TraceHitFlesh(b) + return b.MatType == MAT_FLESH or b.MatType == MAT_ALIENFLESH or (IsValid(b.Entity) and b.Entity.IsNPC and (b.Entity:IsNPC() or b.Entity:IsPlayer() or b.Entity:IsRagdoll())) +end + +local cv_dmg_mult = GetConVar("sv_tfa_damage_multiplier") +local cv_dmg_mult_npc = GetConVar("sv_tfa_damage_multiplier_npc") +local cv_dmg_mult_min = GetConVar("sv_tfa_damage_mult_min") +local cv_dmg_mult_max = GetConVar("sv_tfa_damage_mult_max") + +function SWEP:Strike(attk, precision) + local hitWorld, hitNonWorld, hitFlesh, needsCB + local distance, direction, maxhull + local ow = self:GetOwner() + if not IsValid(ow) then return end + distance = attk.len + direction = attk.dir + maxhull = attk.hull + eang = ow:EyeAngles() + fwd = ow:EyeAngles():Forward() + tr.start = ow:GetShootPos() + scl = direction:Length() / precision / 2 + tr.maxs = Vector(scl, scl, scl) + tr.mins = -tr.maxs + tr.mask = MASK_SHOT + tr.filter = {self, ow} + + hitWorld = false + hitNonWorld = false + hitFlesh = false + + if attk.callback then + needsCB = true + else + needsCB = false + end + + if maxhull then + tr.maxs.x = math.min(tr.maxs.x, maxhull / 2) + tr.maxs.y = math.min(tr.maxs.y, maxhull / 2) + tr.maxs.z = math.min(tr.maxs.z, maxhull / 2) + tr.mins = -tr.maxs + end + + strikedir:Zero() + strikedir:Add(direction.x * eang:Right()) + strikedir:Add(direction.y * eang:Forward()) + strikedir:Add(direction.z * eang:Up()) + local strikedirfull = strikedir * 1 + + if ow:IsPlayer() and ow:IsAdmin() and GetConVarNumber("developer") > 0 then + local spos, epos = tr.start + Vector(0, 0, -1) + fwd * distance / 2 - strikedirfull / 2, tr.start + Vector(0, 0, -1) + fwd * distance / 2 + strikedirfull / 2 + debugoverlay.Line(spos, epos, 5, Color(255, 0, 0)) + debugoverlay.Cross(spos, 8, 5, Color(0, 255, 0), true) + debugoverlay.Cross(epos, 4, 5, Color(0, 255, 255), true) + end + + if SERVER and not game.SinglePlayer() and ow:IsPlayer() then + ow:LagCompensation(true) + end + + local totalResults = {} + for i = 1, precision do + dirv = LerpVector((i - 0.5) / precision, -direction / 2, direction / 2) + strikedir:Zero() + strikedir:Add(dirv.x * eang:Right()) + strikedir:Add(dirv.y * eang:Forward()) + strikedir:Add(dirv.z * eang:Up()) + tr.endpos = tr.start + distance * fwd + strikedir + traceres = util.TraceLine(tr) + table.insert(totalResults, traceres) + end + + if SERVER and not game.SinglePlayer() and ow:IsPlayer() then + ow:LagCompensation(false) + end + + local basedmg = attk.dmg + + local ind = self:GetMelAttackID() or 1 + local srctbl = ind >= 0 and "Primary" or "Secondary" + if not self:GetStatL(srctbl .. ".SplitDamage") or not basedmg then + basedmg = self:GetStatL(srctbl .. ".Damage") + end + + local dmg = basedmg * util.SharedRandom("TFA_Melee_RandomDamageMult" .. CurTime(), cv_dmg_mult_min:GetFloat(), cv_dmg_mult_max:GetFloat(), self:EntIndex()) + if ow:IsNPC() then + dmg = dmg * cv_dmg_mult_npc:GetFloat() + else + dmg = dmg * cv_dmg_mult:GetFloat() + end + + local forcevec = strikedirfull:GetNormalized() * (attk.force or basedmg / 4) * 128 + local damage = DamageInfo() + damage:SetAttacker(self:GetOwner()) + damage:SetInflictor(self) + damage:SetDamage(dmg) + damage:SetDamageType(attk.dmgtype or DMG_SLASH) + damage:SetDamageForce(forcevec) + local fleshHits = 0 + + --Handle flesh + for _, v in ipairs(totalResults) do + if v.Hit and IsValid(v.Entity) and TraceHitFlesh(v) and (not v.Entity.TFA_HasMeleeHit) then + self:ApplyDamage(v, damage, attk) + self:SmackEffect(v, damage) + v.Entity.TFA_HasMeleeHit = true + fleshHits = fleshHits + 1 + if fleshHits >= (attk.maxhits or 3) then break end + + if attk.hitflesh and not hitFlesh then + self:EmitSoundNet(attk.hitflesh) + end + + if attk.callback and needsCB then + attk.callback(attk, self, v) + needsCB = false + end + + hitFlesh = true + end + --debugoverlay.Sphere( v.HitPos, 5, 5, color_white ) + end + + --Handle non-world + for _, v in ipairs(totalResults) do + if v.Hit and (not TraceHitFlesh(v)) and (not v.Entity.TFA_HasMeleeHit) then + self:ApplyDamage(v, damage, attk) + v.Entity.TFA_HasMeleeHit = true + + if not hitNonWorld then + self:SmackEffect(v, damage) + + if attk.hitworld and not hitFlesh then + self:EmitSoundNet(attk.hitworld) + end + + if attk.callback and needsCB then + attk.callback(attk, self, v) + needsCB = false + end + + self:BurstDoor(v.Entity, damage) + hitNonWorld = true + end + end + end + + -- Handle world + if not hitNonWorld and not hitFlesh then + for _, v in ipairs(totalResults) do + if v.Hit and v.HitWorld and not hitWorld then + hitWorld = true + + if attk.hitworld then + self:EmitSoundNet(attk.hitworld) + end + + self:SmackEffect(v, damage) + + if attk.callback and needsCB then + attk.callback(attk, self, v) + needsCB = false + end + end + end + end + + --Handle empty + cleanup + for _, v in ipairs(totalResults) do + if needsCB then + attk.callback(attk, self, v) + needsCB = false + end + + if IsValid(v.Entity) then + v.Entity.TFA_HasMeleeHit = false + end + end + + if attk.kickback and (hitFlesh or hitNonWorld or hitWorld) then + self:SendViewModelAnim(attk.kickback) + end +end + +function SWEP:PlaySwing(act) + self:SendViewModelAnim(act) + + return true, act +end + +local lvec = Vector(0, 0, 0) +function SWEP:ChooseAttack(tblName) + local attacks = self:GetStatL(tblName .. ".Attacks") + if not attacks or #attacks <= 0 then return -1 end + + local keys = table.GetKeys(attacks) + table.RemoveByValue(keys, "BaseClass") + if #keys <= 0 then return -1 end + + local ply = self:GetOwner() + local isdir = self:GetStatL(tblName .. ".Directional") and IsValid(ply) and ply:IsPlayer() + local founddir = false + local foundkeys = {} + + if isdir then + lvec.x = 0 + lvec.y = 0 + + if ply:KeyDown(IN_MOVERIGHT) then + lvec.y = lvec.y - 1 + end + + if ply:KeyDown(IN_MOVELEFT) then + lvec.y = lvec.y + 1 + end + + if ply:KeyDown(IN_FORWARD) or ply:KeyDown(IN_JUMP) then + lvec.x = lvec.x + 1 + end + + if ply:KeyDown(IN_BACK) or ply:KeyDown(IN_DUCK) then + lvec.x = lvec.x - 1 + end + + local targ = "" + if lvec.y > 0.3 then + targ = "L" + elseif lvec.y < -0.3 then + targ = "R" + elseif lvec.x > 0.5 then + targ = "F" + elseif lvec.x < -0.1 then + targ = "B" + end + + for k, v in pairs(attacks) do + if (not self:GetSprinting() or v.spr) and v.direction and string.find(v.direction, targ) then + founddir = true + + table.insert(foundkeys, k) + end + end + end + + if not isdir or #foundkeys <= 0 or not founddir then + for k, v in pairs(attacks) do + if (not self:GetSprinting() or v.spr) and v.dmg then + table.insert(foundkeys, k) + end + end + end + + if #foundkeys <= 0 then return 0 end + local key = foundkeys[self:SharedRandom(1, #foundkeys, tblName .. "Attack")] + if not key then return 0 end + + return key, attacks[key] +end + +function SWEP:ChoosePrimaryAttack() + return self:ChooseAttack("Primary") +end + +function SWEP:ChooseSecondaryAttack() + return self:ChooseAttack("Secondary") +end + +function SWEP:PrimaryAttack() + local ow = self:GetOwner() + + if IsValid(ow) and ow:IsNPC() then + local _, attk = self:ChoosePrimaryAttack() + if not attk then return end + local owv = self:GetOwner() + + timer.Simple(0.5, function() + if IsValid(self) and IsValid(owv) and owv:IsCurrentSchedule(SCHED_MELEE_ATTACK1) then + self:Strike(attk, 5) + end + end) + + self:SetNextPrimaryFire(CurTime() + attk["end"] or 1) + self:SetNextSecondaryFire(self:GetNextPrimaryFire()) + + timer.Simple(self:GetNextPrimaryFire() - CurTime(), function() + if IsValid(owv) then + owv:ClearSchedule() + end + end) + + self:GetOwner():SetSchedule(SCHED_MELEE_ATTACK1) + return + end + + if not self:VMIV() then return end + if CurTime() <= self:GetNextPrimaryFire() then return end + if not self:CanPrimaryAttack() then return end + + self:PrePrimaryAttack() + + if hook.Run("TFA_PrimaryAttack", self) then return end + + local maxcombo = self:GetStatL("Primary.MaxCombo", 0) + if maxcombo > 0 and self:GetComboCount() >= maxcombo then return end + + local ind, attack = self:ChoosePrimaryAttack() + if not attack then return end + + --We have attack isolated, begin attack logic + self:PlaySwing(attack.act) + + if not attack.snd_delay or attack.snd_delay <= 0 then + if IsFirstTimePredicted() then + self:EmitSound(attack.snd) + + if self:GetOwner().Vox then + self:GetOwner():Vox("bash", 4) + end + end + + self:GetOwner():ViewPunch(attack.viewpunch) + elseif attack.snd_delay then + if IsFirstTimePredicted() then + self.AttackSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act) + self.VoxSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act) + end + + --[[ + timer.Simple(attack.snd_delay, function() + if IsValid(self) and self:IsValid() and SERVER then + self:EmitSound(attack.snd) + + if self:OwnerIsValid() and self:GetOwner().Vox then + self:GetOwner():Vox("bash", 4) + end + end + end) + ]] + -- + self:SetVP(true) + self:SetVPPitch(attack.viewpunch.p) + self:SetVPYaw(attack.viewpunch.y) + self:SetVPRoll(attack.viewpunch.r) + self:SetVPTime(CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act)) + self:GetOwner():ViewPunch(-Angle(attack.viewpunch.p / 2, attack.viewpunch.y / 2, attack.viewpunch.r / 2)) + end + + self.up_hat = false + self:ScheduleStatus(TFA.Enum.STATUS_SHOOTING, attack.delay / self:GetAnimationRate(attack.act)) + self:SetMelAttackID(ind) + self:SetNextPrimaryFire(CurTime() + attack["end"] / self:GetAnimationRate(attack.act)) + self:SetNextSecondaryFire(self:GetNextPrimaryFire()) + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:SetComboCount(self:GetComboCount() + 1) + + self:PostPrimaryAttack() + hook.Run("TFA_PostPrimaryAttack", self) +end + +function SWEP:SecondaryAttack() + if not self:VMIV() then return end + if not self:CanSecondaryAttack() then return end + self:PreSecondaryAttack() + + if hook.Run("TFA_SecondaryAttack", self) then return end + + local maxcombo = self:GetStatL("Secondary.MaxCombo", 0) + if maxcombo > 0 and self:GetComboCount() >= maxcombo then return end + + local ind, attack = self:ChooseSecondaryAttack() + if attack then + ind = -ind + elseif ind < 0 and self:GetStatL("Secondary.PrimaryFallback") then + ind, attack = self:ChoosePrimaryAttack() + end + if not attack then return end + + --We have attack isolated, begin attack logic + self:PlaySwing(attack.act) + + if not attack.snd_delay or attack.snd_delay <= 0 then + if IsFirstTimePredicted() then + self:EmitSound(attack.snd) + + if self:GetOwner().Vox then + self:GetOwner():Vox("bash", 4) + end + end + + self:GetOwner():ViewPunch(attack.viewpunch) + elseif attack.snd_delay then + if IsFirstTimePredicted() then + self.AttackSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act) + self.VoxSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act) + end + + --[[ + timer.Simple(attack.snd_delay, function() + if IsValid(self) and self:IsValid() and SERVER then + self:EmitSound(attack.snd) + + if self:OwnerIsValid() and self:GetOwner().Vox then + self:GetOwner():Vox("bash", 4) + end + end + end) + ]] + -- + self:SetVP(true) + self:SetVPPitch(attack.viewpunch.p) + self:SetVPYaw(attack.viewpunch.y) + self:SetVPRoll(attack.viewpunch.r) + self:SetVPTime(CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act)) + self:GetOwner():ViewPunch(-Angle(attack.viewpunch.p / 2, attack.viewpunch.y / 2, attack.viewpunch.r / 2)) + end + + self.up_hat = false + self:ScheduleStatus(TFA.Enum.STATUS_SHOOTING, attack.delay / self:GetAnimationRate(attack.act)) + self:SetMelAttackID(ind) + self:SetNextPrimaryFire(CurTime() + attack["end"] / self:GetAnimationRate(attack.act)) + self:SetNextSecondaryFire(self:GetNextPrimaryFire()) + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:SetComboCount(self:GetComboCount() + 1) + + self:PostSecondaryAttack() + hook.Run("TFA_PostSecondaryAttack", self) +end + +function SWEP:AltAttack() + if self.CanBlock then + if self:GetStatL("Secondary.CanBash") and self.CanBlock and self:GetOwner():KeyDown(IN_USE) then + BaseClass.AltAttack(self) + + return + end + else + if not self:VMIV() then return end + if not TFA.Enum.ReadyStatus[self:GetStatus()] then return end + if not self:GetStatL("Secondary.CanBash") then return end + if self:IsSafety() then return end + + return BaseClass.AltAttack(self) + end +end + +function SWEP:Reload(released, ovr, ...) + if not self:VMIV() then return end + if ovr then return BaseClass.Reload(self, released, ...) end + + if self:GetOwner().GetInfoNum and self:GetOwner():GetInfoNum("cl_tfa_keys_inspect", 0) > 0 then + return + end + + if (self.SequenceEnabled[ACT_VM_FIDGET] or self.InspectionActions) and self:GetStatus() == TFA.Enum.STATUS_IDLE then + local _, tanim, ttype = self:ChooseInspectAnim() + self:ScheduleStatus(TFA.Enum.STATUS_FIDGET, self:GetActivityLength(tanim, false, ttype)) + end +end + +function SWEP:CycleSafety() +end + +TFA.FillMissingMetaValues(SWEP) diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_nade_base.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_nade_base.lua new file mode 100644 index 0000000..2bfa276 --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_nade_base.lua @@ -0,0 +1,256 @@ +if SERVER then + AddCSLuaFile() +end + +local CurTime = CurTime +local sp = game.SinglePlayer() + +DEFINE_BASECLASS("tfa_gun_base") +SWEP.DrawCrosshair = true +SWEP.Type = "Grenade" +SWEP.IsGrenade = true +SWEP.MuzzleFlashEffect = "" +SWEP.Secondary.IronSightsEnabled = false +SWEP.Delay = 0.3 -- Delay to fire entity +SWEP.Delay_Underhand = 0.3 -- Delay to fire entity when underhand +SWEP.Primary.Round = "" -- Nade Entity +SWEP.Velocity = 550 -- Entity Velocity +SWEP.Underhanded = false +SWEP.DisableIdleAnimations = true +SWEP.IronSightsPosition = Vector(5,0,0) +SWEP.IronSightsAngle = Vector(0,0,0) +SWEP.Callback = {} + +SWEP.AllowUnderhanded = true + +SWEP.AllowSprintAttack = true + +local nzombies = nil + +function SWEP:Initialize() + if nzombies == nil then + nzombies = engine.ActiveGamemode() == "nzombies" + end + + self.ProjectileEntity = self.ProjectileEntity or self.Primary.Round -- Entity to shoot + self.ProjectileVelocity = self.Velocity or 550 -- Entity to shoot's velocity + self.ProjectileModel = nil -- Entity to shoot's model + + self:SetNW2Bool("Underhanded", false) + + BaseClass.Initialize(self) +end + +local cl_defaultweapon = GetConVar("cl_defaultweapon") + +function SWEP:SwitchToPreviousWeapon() + local wep = LocalPlayer():GetPreviousWeapon() + + if IsValid(wep) and wep:IsWeapon() and wep:GetOwner() == LocalPlayer() then + input.SelectWeapon(wep) + else + wep = LocalPlayer():GetWeapon(cl_defaultweapon:GetString()) + + if IsValid(wep) then + input.SelectWeapon(wep) + else + local _ + _, wep = next(LocalPlayer():GetWeapons()) + + if IsValid(wep) then + input.SelectWeapon(wep) + end + end + end +end + +function SWEP:Deploy() + if self:Clip1() <= 0 then + if self:Ammo1() <= 0 then + if self:GetOwner():IsPlayer() then + if CLIENT and not sp then + self:SwitchToPreviousWeapon() + elseif SERVER and not nzombies then + if sp then + self:CallOnClient("SwitchToPreviousWeapon", "") + local ply = self:GetOwner() + local classname = self:GetClass() + timer.Simple(0, function() ply:StripWeapon(classname) end) + else + self:GetOwner():StripWeapon(self:GetClass()) + return + end + end + end + else + self:TakePrimaryAmmo(1, true) + self:SetClip1(1) + end + end + + self:SetNW2Bool("Underhanded", false) + + self.oldang = self:GetOwner():EyeAngles() + self.anga = Angle() + self.angb = Angle() + self.angc = Angle() + + self:CleanParticles() + + return BaseClass.Deploy(self) +end + +function SWEP:ChoosePullAnim() + if not self:OwnerIsValid() then return end + + if self.Callback.ChoosePullAnim then + self.Callback.ChoosePullAnim(self) + end + + if self:GetOwner():IsPlayer() then + self:GetOwner():SetAnimation(PLAYER_RELOAD) + end + + self:SendViewModelAnim(ACT_VM_PULLPIN) + + if sp then + self:CallOnClient("AnimForce", ACT_VM_PULLPIN) + end + + return true, ACT_VM_PULLPIN +end + +function SWEP:ChooseShootAnim() + if not self:OwnerIsValid() then return end + + if self.Callback.ChooseShootAnim then + self.Callback.ChooseShootAnim(self) + end + + if self:GetOwner():IsPlayer() then + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + end + + local tanim = self:GetNW2Bool("Underhanded", false) and self.SequenceEnabled[ACT_VM_RELEASE] and ACT_VM_RELEASE or ACT_VM_THROW + self:SendViewModelAnim(tanim) + + if sp then + self:CallOnClient("AnimForce", tanim) + end + + return true, tanim +end + +function SWEP:ThrowStart() + if self:Clip1() <= 0 then return end + + local success, tanim, animType = self:ChooseShootAnim() + + local delay = self:GetNW2Bool("Underhanded", false) and self.Delay_Underhand or self.Delay + self:ScheduleStatus(TFA.Enum.STATUS_GRENADE_THROW, delay) + + if success then + self.LastNadeAnim = tanim + self.LastNadeAnimType = animType + self.LastNadeDelay = delay + end +end + +function SWEP:Throw() + if self:Clip1() <= 0 then return end + self:SetStatRawL("Primary.ProjectileVelocity", (self:GetNW2Bool("Underhanded", false) and self.Velocity_Underhand) or (self.Velocity or 550)) + self:ClearStatCache("Primary.ProjectileVelocity") + + self:TakePrimaryAmmo(1) + self:ShootBulletInformation() + + if self.LastNadeAnim then + local len = self:GetActivityLength(self.LastNadeAnim, true, self.LastNadeAnimType) + self:ScheduleStatus(TFA.Enum.STATUS_GRENADE_THROW_WAIT, len - (self.LastNadeDelay or len)) + end +end + +function SWEP:Think2(...) + if not self:OwnerIsValid() then return end + + local stat = self:GetStatus() + + -- This is the best place to do this since Think2 is called inside FinishMove + -- self:SetNW2Bool("Underhanded", self.AllowUnderhanded and self:KeyDown(IN_ATTACK2)) + + local statusend = CurTime() >= self:GetStatusEnd() + + if stat == TFA.Enum.STATUS_GRENADE_PULL and statusend then + stat = TFA.Enum.STATUS_GRENADE_READY + self:SetStatus(stat, math.huge) + end + + if stat == TFA.Enum.STATUS_GRENADE_READY and (self:GetOwner():IsNPC() or not self:KeyDown(IN_ATTACK2) and not self:KeyDown(IN_ATTACK)) then + self:ThrowStart() + end + + if stat == TFA.Enum.STATUS_GRENADE_THROW and statusend then + self:Throw() + end + + if stat == TFA.Enum.STATUS_GRENADE_THROW_WAIT and statusend then + self:Deploy() + end + + return BaseClass.Think2(self, ...) +end + +function SWEP:PullStart(secondary) + if self:Clip1() <= 0 then return end + + self:SetNW2Bool("Underhanded", secondary and self.AllowUnderhanded) + + local _, tanim = self:ChoosePullAnim() + + self:ScheduleStatus(TFA.Enum.STATUS_GRENADE_PULL, self:GetActivityLength(tanim)) +end + +function SWEP:PrimaryAttack() + if not self:CanPrimaryAttack() then return end + + self:PrePrimaryAttack() + + if hook.Run("TFA_PrimaryAttack", self) then return end + + self:PullStart(false) + + self:PostPrimaryAttack() + hook.Run("TFA_PostPrimaryAttack", self) +end + +function SWEP:SecondaryAttack() + if not self:CanSecondaryAttack() then return end + + self:PreSecondaryAttack() + + if hook.Run("TFA_SecondaryAttack", self) then return end + + self:PullStart(true) + + self:PostSecondaryAttack() + hook.Run("TFA_PostSecondaryAttack", self) +end + +function SWEP:Reload() + if self:Clip1() <= 0 and self:OwnerIsValid() and self:CanFire() then + self:Deploy() + end +end + +function SWEP:CanFire() -- what + return self:CanPrimaryAttack() +end + +function SWEP:ChooseIdleAnim(...) + if self:GetStatus() == TFA.Enum.STATUS_GRENADE_READY then return end + return BaseClass.ChooseIdleAnim(self, ...) +end + +SWEP.CrosshairConeRecoilOverride = .05 + +TFA.FillMissingMetaValues(SWEP) diff --git a/garrysmod/addons/tfa_base/lua/weapons/tfa_sword_advanced_base.lua b/garrysmod/addons/tfa_base/lua/weapons/tfa_sword_advanced_base.lua new file mode 100644 index 0000000..042064e --- /dev/null +++ b/garrysmod/addons/tfa_base/lua/weapons/tfa_sword_advanced_base.lua @@ -0,0 +1,563 @@ +local vector_origin = Vector() + +if SERVER then + AddCSLuaFile() +end + +-- This base is kept for backward compatiblity purposes +-- This should not, by any means, be utilized in newer SWEPS +-- Look at melee base/knife base + +--[[ +PLEASE DON TUSE THIS ANYMIRE +PLEASe +PLEASSSSSSSS +]] +DEFINE_BASECLASS("tfa_gun_base") +SWEP.Primary.Ammo = "" -- Required for GMod legacy purposes. Don't remove unless you want to see your sword's ammo. Wat? +SWEP.data = {} --Ignore this. +--[[SWEP Info]] +-- +SWEP.Gun = "" -- must be the name of your swep but NO CAPITALS! +SWEP.Category = "" +SWEP.Base = "tfa_gun_base" +SWEP.Author = "" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "Left click to slash" .. "\n" .. "Hold right mouse to put up guard." +SWEP.PrintName = "Snowflake Katana" -- Weapon name (Shown on HUD) +SWEP.Slot = 0 -- Slot in the weapon selection menu +SWEP.SlotPos = 21 -- Position in the slot +SWEP.DrawAmmo = false -- Should draw the default HL2 ammo counter +SWEP.DrawWeaponInfoBox = true -- Should draw the weapon info box +SWEP.BounceWeaponIcon = false -- Should the weapon icon bounce? +SWEP.DrawCrosshair = false -- set false if you want no crosshair +SWEP.Weight = 50 -- rank relative ot other weapons. bigger is better +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon +SWEP.Secondary.OwnerFOV = 90 -- How much you "zoom" in. Less is more! Don't have this be <= 0 +SWEP.WeaponLength = 8 --16 = 1 foot +SWEP.MoveSpeed = 0.9 --Multiply the player's movespeed by this. +SWEP.IronSightsMoveSpeed = 0.8 --Multiply the player's movespeed by this when sighting. +SWEP.IsMelee = true +SWEP.AllowSprintAttack = true +--[[TTT CRAP]] +-- +-- SWEP.Kind = WEAPON_EQUIP +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +-- SWEP.CanBuy = {ROLE_TRAITOR, ROLE_DETECTIVE, ROLE_INNOCENT} -- only traitors can buy +-- SWEP.LimitedStock = true -- only buyable once +-- SWEP.NoSights = false +-- SWEP.IsSilent = true +--[[Worldmodel Variables]] +-- +SWEP.HoldType = "melee2" -- how others view you carrying the weapon +SWEP.BlockHoldType = "slam" -- how others view you carrying the weapon, while blocking +--[[ +Options: +normal - Pistol Idle / Weaponless, hands at sides +melee - One Handed Melee +melee2 - Two Handed Melee +fist - Fists Raised +knife - Knife/Dagger style melee. Kind of hunched. +smg - SMG or Rifle with grip +ar2 - Rifle +pistol - One handed pistol +rpg - Used for RPGs or sometimes snipers. AFAIK has no reload anim. +physgun - Used for physgun. Kind of like SLAM, but holding a grip. +grenade - Used for nades, kind of similar to melee but more of a throwing animation. +shotgun - Used for shotugns, and really that's it. +crossbow -Similar to shotgun, but aimed. Used for crossbows. +slam - Holding an explosive or other rectangular object with two hands +passive -- SMG idle, like you can see with some HL2 citizens +magic - One hand to temple, the other reaching out. Can be used to mimic blocking a melee, if you're OK with the temple-hand-thing. +duel- dual pistols +revolver - 2 handed pistol +--]] +SWEP.WorldModel = "" -- Weapon world model +SWEP.ShowWorldModel = true --Draw the world model? +SWEP.Spawnable = false --Can it be spawned by a user? +SWEP.AdminSpawnable = false --Can it be spawned by an admin? +--[[Viewmodel Variables]] +-- +SWEP.UseHands = true --Uses c_hands? If you port a model directly from HL2, CS:S, etc. then set to false + SWEP.ViewModelFOV = 60 --This controls the viewmodel FOV. The larger, the smaller it appears. Decrease if you can see something you shouldn't. + SWEP.ViewModelFlip = false --Flip the viewmodel? Usually gonna be yes for CS:S ports. + SWEP.ViewModel = "" -- Weapon view model + --[[Shooting/Attacking Vars]] + -- + SWEP.Primary.Damage = 200 -- Base damage per bullet + SWEP.DamageType = DMG_SLASH + SWEP.Primary.RPM = 180 -- This is in Rounds Per Minute + SWEP.Primary.KickUp = 0.4 -- Maximum up recoil (rise) + SWEP.Primary.KickDown = 0.3 -- Maximum down recoil (skeet) + SWEP.Primary.KickHorizontal = 0.3 -- Maximum up recoil (stock) + SWEP.Primary.Automatic = false -- Automatic = true; Semi Auto = false. In the case of our sword, if you can hold and keep swinging. + SWEP.Primary.FiresUnderwater = true --Can you swing your sword underwater? + --[[ Block Procedural Animation Variables]] + -- + SWEP.BlockPos = Vector(-18, -10, 3) --Blocking Position. + SWEP.BlockAng = Vector(10, -25, -15) --Blocking Angle. + --[[Begin Slashing Variables]] + -- + SWEP.Slash = 1 + SWEP.Sequences = {} --Swinging Sequences + --[[ + SWEP.Sequences[1]={ + name="swipe_u2d",--Sequence name, can be found in HLMV + holdtype="melee2",--Holdtype (thirdperson type of weapon, usually gonna be melee for a one handed or melee2 for a two handed) + startt=10/60,--swing start in seconds, from the sequence start +endt=20/60,--swing end in seconds, from the sequence start +pitch=5, --This is a component of the slash's arc. Pitch is added last, and changes based on the time of the trace. +yaw=35, --This is a component of the slash's arc. Yaw is added second, and changes based on the time of the trace. +roll=-90,--This is a component of the slash's arc. Roll is added first, and remains static. +dir=1--Left to right = -1, right to left =1. Base this off if the roll were 0. +} +SWEP.Sequences[2]={ +name="swipe_l2r", +holdtype="melee2", +startt=10/60, +endt=20/60, +pitch=5, +yaw=45, +roll=10, +dir=-1 +} +SWEP.Sequences[3]={ +name="swipe_r2l", +holdtype="melee2", +startt=10/60, +endt=20/60, +pitch=5, +yaw=45, +roll=-5, +dir=1 +} +]] +-- +SWEP.SlashRandom = Angle(5, 0, 10) --This is a random angle for the overall slash, added onto the sequence angle +SWEP.SlashJitter = Angle(1, 1, 1) --This is jitter for each point of the slash +SWEP.randfac = 0 --Don't change this, it's autocalculated +SWEP.HitRange = 86 -- Blade Length. Set slightly longer to compensate for animation. +SWEP.AmmoType = "TFMSwordHitGenericSlash" --Ammotype. You can set a damage type in a custom ammo, which you can create in autorun. Then set it to that custom ammotype here. +SWEP.SlashPrecision = 15 --The number of traces per slash +SWEP.SlashDecals = 8 --The number of decals per slash. May slightly vary +SWEP.SlashSounds = 6 --The number of sounds per slash. May slightly vary. +SWEP.LastTraceTime = 0 --Don't change this, it's autocalculated +SWEP.NextPrimaryFire = 0 --In case SetNextPrimaryFire doesn't work. Don't change this here. Please. +--[[Blocking Variables]] +-- +SWEP.BlockSequences = {} --Sequences for blocking +--[[ +SWEP.BlockSequences[1]={ +name="swipe_u2d", --Sequence name, can be found in HLMV +recoverytime=0.3, --Recovery Time (Added onto sequence time, if enabled) +recoverysequence=false --Automatically add recovery time based on sequence length +} +SWEP.BlockSequences[2]={ +name="swipe_l2r", +recoverytime=0.3, +recoverysequence=false +} +SWEP.BlockSequences[3]={ +name="swipe_r2l", +recoverytime=0.3, +recoverysequence=false +} +]] +-- +SWEP.DisableIdleAnimations = false --Disables idle animations. Set to false to enable them. +SWEP.IronBobMult = 1 -- More is more bobbing, proportionally. This is multiplication, not addition. You want to make this < 1 for sighting, 0 to outright disable. +SWEP.NinjaMode = false --Can block bullets/everything +SWEP.DrawTime = 0.2 --Time you can't swing after drawing +SWEP.BlockAngle = 135 --Think of the player's view direction as being the middle of a sector, with the sector's angle being this +SWEP.BlockMaximum = 0.1 --Multiply damage by this for a maximumly effective block +SWEP.BlockMinimum = 0.7 --Multiply damage by this for a minimumly effective block +SWEP.BlockWindow = 0.5 --Time to absorb maximum damage +SWEP.BlockFadeTime = 1 --Time for blocking to do minimum damage. Does not include block window +SWEP.PrevBlocking = false --Don't change this, just related to the block procedural animation +SWEP.BlockProceduralAnimTime = 0.15 --Change how slow or quickly the player moves their sword to block +--[[Sounds]] +-- +--These are just kinda constants you can use. Don't change these, or do if you want to be lazy. +SWEP.SlashSound = Sound("weapons/blades/woosh.mp3") --Weapon woosh/slash sound +SWEP.KnifeShink = Sound("weapons/blades/hitwall.mp3") --When a knife hits a wall. Grating noise. +SWEP.KnifeSlash = Sound("weapons/blades/slash.mp3") --Meaty slash +SWEP.KnifeStab = Sound("weapons/blades/nastystab.mp3") --Meaty stab and pull-out +SWEP.SwordChop = Sound("weapons/blades/swordchop.mp3") --Meaty impact, without the pull-out +SWEP.SwordClash = Sound("weapons/blades/clash.mp3") --Sound played when you block something +--[[ Edit These ]] +-- +SWEP.Primary.Sound = SWEP.SlashSound --Change this to your swing sound +SWEP.Primary.Sound_Impact_Flesh = SWEP.SwordChop --Change this to your flesh hit sound +SWEP.Primary.Sound_Impact_Generic = SWEP.KnifeShink --Change this to your generic hit sound +SWEP.Primary.Sound_Impact_Metal = SWEP.SwordClash --Change this to your metal hit +SWEP.Primary.Sound_Pitch_Low = 97 --Percentage of pitch out of 100, lowe end. Up to 255. +SWEP.Primary.Sound_Pitch_High = 100 --Percentage of pitch out of 100 Up to 255. +SWEP.Primary.Sound_World_Glass_Enabled = true --Override for glass? +SWEP.Primary.Sound_Glass_Enabled = true --Override for glass? +SWEP.Primary.Sound_Glass = Sound("impacts/glass_impact.wav") +SWEP.GlassSoundPlayed = false -- DO NOT CHANGE THIS. It's automatically set. This way, it doesn't spam the glass sound. +SWEP.ViewModelElements = {} --View elements +SWEP.WorldModelElements = {} --World elements +SWEP.sounds = 0 +SWEP.Action = true --Use action IDs? +--[[Stop editing here for normal users of my base. Code starts here.]]-- +--[[ +function SWEP:Precache() +util.PrecacheSound(self.Primary_TFA.Sound) +util.PrecacheModel(self.ViewModel) +util.PrecacheModel(self.WorldModel) +end +]]-- + +function SWEP:Deploy() + self:SetNW2Float("SharedRandomVal", CurTime()) + self:SetBlockStart(-1) + self.PrevBlockRat = 0 + BaseClass.Deploy(self) +end + +function SWEP:SetupDataTables() + BaseClass.SetupDataTables(self) + + self:NetworkVarTFA("Float", "BlockStart") +end + +function SWEP:DoImpactEffect(tr, dmg) + local impactpos, impactnormal + impactpos = tr.HitPos + impactnormal = tr.HitNormal + self.sounds = self.sounds and self.sounds or 0 + + if (tr.HitSky == false) then + if (util.SharedRandom(CurTime(), 1, self.SlashPrecision, "TFMSwordDecal") < self.SlashDecals) then + util.Decal("ManhackCut", impactpos + impactnormal, impactpos - impactnormal) + end + + if (tr.MatType == MAT_GLASS) and (self.Primary_TFA.Sound_Glass and self.Primary_TFA.Sound_Glass_Enabled == true) and (self.GlassSoundPlayed == false) then + self:EmitSound(self.Primary_TFA.Sound_Glass, 100, math.random(self.Primary_TFA.Sound_Pitch_Low, self.Primary_TFA.Sound_Pitch_High), 0.75, CHAN_WEAPON) + self.GlassSoundPlayed = true + end + end + + return true +end + +function SWEP:HitThing(ent, posv, normalv, damage, tr) + local ply + ply = self:GetOwner() + + if IsValid(ply) then + --[[ + ply:LagCompensation(true) + local tr,tres; + tr={} + tr.start=posv + tr.endpos=posv+normalv*self.HitRange + tr.filter=ply + tr.mask=2147483647--MASK_SOLID && MASK_SHOT && MASK_VISIBLE_AND_NPCS--MASK_SHOT + tres=util.TraceLine(tr) + ply:LagCompensation(false) + if tres.Hit and tres.Fraction<1 and !tres.HitSky then + ]] + -- + local bullet = {} + bullet.Num = 1 + bullet.Src = posv -- Source + bullet.Dir = normalv -- Dir of bullet + bullet.Spread = vector_origin -- Aim Cone + bullet.Tracer = 0 -- Show a tracer on every x bullets + bullet.Force = damage / 16 -- Amount of force to give to phys objects + bullet.Damage = damage + bullet.Distance = self.HitRange + bullet.HullSize = self.WeaponLength / self.SlashPrecision + bullet.AmmoType = self.AmmoType + + bullet.Callback = function(a, b, c) + local wep = a:GetActiveWeapon() + if not IsValid(self) then return end + if not self.sounds then return end + c:SetDamageType(self.DamageType) + + if (self.sounds < self.SlashSounds) then + local hitmat = b.MatType + + if (hitmat == MAT_METAL or hitmat == MAT_GRATE or hitmat == MAT_VENT or hitmat == MAT_COMPUTER) then + --Emit metal sound + wep.Weapon:EmitSound(self.Primary_TFA.Sound_Impact_Metal, 100, math.random(self.Primary_TFA.Sound_Pitch_Low, self.Primary_TFA.Sound_Pitch_High), 0.75, CHAN_AUTO) + wep.sounds = self.sounds + 1 + --Emit flesh sound + --Emit generic sound. + elseif (hitmat == MAT_FLESH or hitmat == MAT_BLOODYFLESH or hitmat == MAT_ALIENFLESH) then + wep.Weapon:EmitSound(self.Primary_TFA.Sound_Impact_Flesh, 100, math.random(self.Primary_TFA.Sound_Pitch_Low, self.Primary_TFA.Sound_Pitch_High), 0.75, CHAN_AUTO) + wep.sounds = self.sounds + 1 + else + wep.Weapon:EmitSound(self.Primary_TFA.Sound_Impact_Generic, 100, math.random(self.Primary_TFA.Sound_Pitch_Low, self.Primary_TFA.Sound_Pitch_High), 0.75, CHAN_AUTO) + wep.sounds = self.sounds + 1 + end + end + end + + if CLIENT and SERVER then + if self:GetOwner() ~= LocalPlayer() then + self:GetOwner():FireBullets(bullet) + end + else + self:GetOwner():FireBullets(bullet) + end + --end + end +end + +function SWEP:PrimaryAttack() + local sharedrandomval = self:GetNW2Float("SharedRandomVal", 0) + math.randomseed(sharedrandomval) + if CLIENT and not IsFirstTimePredicted() then return end + if not self:OwnerIsValid() then return end + if CurTime() < self:GetNextPrimaryFire() then return end + if not TFA.Enum.ReadyStatus[self:GetStatus()] then return end + + if self:IsSafety() then return end + self:SetStatus(TFA.Enum.STATUS_SHOOTING) + self.sounds = 0 + self:ChooseShootAnim() -- View model animation + + if SERVER then + timer.Simple(0, function() + if IsValid(self) then + self:SetNW2Float("SharedRandomVal", math.Rand(-1024, 1024)) + end + end) + end + + local vm = self.OwnerViewModel + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:GetOwner():SetNW2Float("TFM_SwingStart", CurTime()) + self:SetStatusEnd(CurTime() + vm:SequenceDuration(vm:LookupSequence(self.Sequences[self:GetNW2Int("Slash", 1)].name))) + self.LastTraceTime = CurTime() + self.Sequences[self:GetNW2Int("Slash", 1)].startt + + self:SetNextPrimaryFire(CurTime() + 1 / (self.Primary_TFA.RPM / 60)) + + if SERVER then + timer.Simple(self.Sequences[self:GetNW2Int("Slash", 1)].startt, function() + if IsValid(self) and self.Primary_TFA.Sound then + self:EmitSound(self.Primary_TFA.Sound) + end + end) + end +end + +local seq, swe +local ft, len, strikepercent, swingprogress, sws +local aimoff, jitfac +local blockseqn, ply +local vm + +function SWEP:IronSights() + BaseClass.IronSights(self) + ply = self:GetOwner() + seq = self.Sequences[self:GetNW2Int("Slash", 1)] + swe = ply:GetNW2Float("TFM_SwingStart", CurTime()) + seq.endt + + if CurTime() < swe then + self:SetIronSightsRaw(false) + end +end + + +function SWEP:Think2(...) + BaseClass.Think2(self, ...) + + local isr = self:GetIronSightsProgress() + + ply = self:GetOwner() + + if self.PrevBlockRat and isr and self.PrevBlockRat <= 0.3 and isr > 0.3 then + self:SetBlockStart(CurTime()) + --print(CurTime()) + end + + if isr and self.PrevBlockRat and isr < 0.1 and self.PrevBlockRat > 0.1 then + self:SetBlockStart(-1) + --print(-1) + end + + self.PrevBlockRat = isr + local stat = self:GetStatus() + if stat == TFA.Enum.STATUS_SHOOTING then + seq = self.Sequences[self:GetNW2Int("Slash", 1)] + ft = CurTime() - self.LastTraceTime + len = seq.endt - seq.startt + strikepercent = ft / len + sws = ply:GetNW2Float("TFM_SwingStart", CurTime()) + seq.startt + swe = ply:GetNW2Float("TFM_SwingStart", CurTime()) + seq.endt + swingprogress = (CurTime() - sws) / len + + if CurTime() < swe then + self:SetIronSightsRaw(false) + end + + if (CurTime() > sws) and CurTime() < swe and ft > len / self.SlashPrecision and (strikepercent > 0) then + aimoff = ply:EyeAngles() + --aimoff = Angle(0,0,0) + local cutangle = Angle(seq.pitch * (swingprogress - 0.5) * seq.dir, seq.yaw * (swingprogress - 0.5) * seq.dir, seq.roll) + jitfac = 0.5 - util.SharedRandom("TFMSwordJitter", 0, 1, CurTime()) + aimoff:RotateAroundAxis(aimoff:Forward(), cutangle.r + self.SlashRandom.r * self.randfac + self.SlashJitter.r * jitfac) --Roll is static + aimoff:RotateAroundAxis(aimoff:Up(), cutangle.y + self.SlashRandom.y * self.randfac + self.SlashJitter.y * jitfac) + aimoff:RotateAroundAxis(aimoff:Right(), cutangle.p + self.SlashRandom.p * self.randfac + self.SlashJitter.p * jitfac) + self:HitThing(ply, ply:GetShootPos(), aimoff:Forward(), self.Primary_TFA.Damage * strikepercent) + self.LastTraceTime = CurTime() + end + end +end + +function SWEP:ChooseShootAnim(mynewvar) + local sharedrandomval = self:GetNW2Float("SharedRandomVal", 0) + if not self:OwnerIsValid() then return end + if not IsValid(self) or not self:OwnerIsValid() then return end + ply = self:GetOwner() + vm = self.OwnerViewModel + local selection = {} + local relativedir = WorldToLocal(ply:GetVelocity(), Angle(0, 0, 0), vector_origin, ply:EyeAngles()) + local fwd = relativedir.x + local hor = relativedir.y + + if hor < -ply:GetWalkSpeed() / 2 then + for k, v in pairs(self.Sequences) do + if v.right then + table.insert(selection, #selection + 1, k) + end + end + elseif hor > ply:GetWalkSpeed() / 2 then + for k, v in pairs(self.Sequences) do + if v.left then + table.insert(selection, #selection + 1, k) + end + end + elseif fwd > ply:GetWalkSpeed() / 2 then + for k, v in pairs(self.Sequences) do + if (v.up) then + table.insert(selection, #selection + 1, k) + end + end + elseif fwd < ply:GetWalkSpeed() / 2 then + for k, v in pairs(self.Sequences) do + if (v.down) then + table.insert(selection, #selection + 1, k) + end + end + end + + if #selection <= 0 and math.abs(fwd) < ply:GetWalkSpeed() / 2 and math.abs(hor) < ply:GetWalkSpeed() / 2 then + for k, v in pairs(self.Sequences) do + if v.up or v.down then + table.insert(selection, #selection + 1, k) + end + end + end + + if #selection <= 0 and math.abs(hor) < ply:GetWalkSpeed() / 2 and math.abs(fwd) < ply:GetWalkSpeed() / 2 then + for k, v in pairs(self.Sequences) do + if v.standing then + table.insert(selection, #selection + 1, k) + end + end + end + + if #selection <= 0 then + --print("random test:") + math.randomseed(sharedrandomval) + + if math.random(0, 1) == 0 then + math.randomseed(sharedrandomval) + self:SetNW2Int("Slash", math.random(1, #self.Sequences)) + else + self:SetNW2Int("Slash", self:GetNW2Int("Slash", 1) + 1) + + if self:GetNW2Int("Slash", 1) > #self.Sequences then + self:SetNW2Int("Slash", 1) + end + end + --print("selection sequence") + --print(math.Round( util.SharedRandom( "TFAMelee", 1, #selection, sharedrandomval ) )) + else + math.randomseed(sharedrandomval) + self:SetNW2Int("Slash", selection[math.random(1, #selection)]) + end + + --print("Shared Random Value:") + --print(sharedrandomval) + --print("Slash Number") + --print(self:GetNW2Int("Slash",0)) + local n = tonumber(mynewvar and mynewvar or "") + local seqn = n and n or self:GetNW2Int("Slash", 1) + --self.Weapon:SendWeaponAnim( ACT_VM_IDLE ) + seq = self.Sequences[seqn] + --vm:ResetSequence(vm:LookupSequence(seq.name)) + --print(seq.name) + local seqid = vm:LookupSequence(seq.name) + seqid = seqid and seqid or 0 + local actid = vm:GetSequenceActivity(seqid) + + if actid and actid >= 0 and self.Action then + self:SendViewModelAnim(actid) + --vm:SendViewModelMatchingSequence(seqid) + else + self:SendViewModelSeq(seqid) + end + + if SERVER and game.SinglePlayer() then + self:CallOnClient("ChooseShootAnim", tostring(seqn)) + end + + return true, ACT_VM_PRIMARYATTACK +end + +function SWEP:BlockAnim() + local sharedrandomval = self:GetNW2Float("SharedRandomVal", 0) + + if self.BlockSequences and #self.BlockSequences > 0 then + math.randomseed(sharedrandomval) + blockseqn = math.random(1, #self.BlockSequences) + seq = self.BlockSequences[blockseqn] + ply = self:GetOwner() + + if IsValid(ply) then + vm = self.OwnerViewModel + + if IsValid(vm) then + self:SetNextIdleAnim(-1) + self:SendWeaponAnim(ACT_VM_IDLE) + vm:SendViewModelMatchingSequence(vm:LookupSequence(seq.name)) + + if seq.recoverysequence and seq.recoverysequence == true then + if seq.recoverytime then + self.NextPrimaryFire = CurTime() + vm:SequenceDuration() + seq.recoverytime + self:SetNextPrimaryFire(CurTime() + vm:SequenceDuration() + seq.recoverytime) + self:SetStatus(TFA.Enum.STATUS_FIDGET) + self:SetStatusEnd(self.NextPrimaryFire) + else + self.NextPrimaryFire = CurTime() + vm:SequenceDuration() + self:SetNextPrimaryFire(CurTime() + vm:SequenceDuration()) + self:SetStatus(TFA.Enum.STATUS_FIDGET) + self:SetStatusEnd(self.NextPrimaryFire) + end + else + self.NextPrimaryFire = CurTime() + seq.recoverytime + + if seq.recoverytime then + self.NextPrimaryFire = CurTime() + seq.recoverytime + self:SetNextPrimaryFire(CurTime() + seq.recoverytime) + else + self.NextPrimaryFire = CurTime() + self:SetNextPrimaryFire(CurTime()) + end + end + end + end + end +end diff --git a/garrysmod/addons/vmanip_ft/lua/autorun/client/cl_vmanip.lua b/garrysmod/addons/vmanip_ft/lua/autorun/client/cl_vmanip.lua new file mode 100644 index 0000000..260a8ae --- /dev/null +++ b/garrysmod/addons/vmanip_ft/lua/autorun/client/cl_vmanip.lua @@ -0,0 +1,704 @@ +--[[ +More detail on stuff in lua/vmanip/vmanip_baseanims.lua + +Please keep in mind that you do not fire events *through vmanip*. Think of it as a fully +clientside animation system. So instead, you request to play an anim, and if the request +went through (true return value), you do your thing + +You probably don't need to snoop around this file, but feel free +]] + +VManip={} +VMLegs={} +local curtime=0 + +local function LerpC(t,a,b,powa) --Non linear lerping + +return a + (b - a) * math.pow(t,powa) + +end + +local properang = Angle(-79.750,0,-90) +local leftarmbones={"ValveBiped.Bip01_L_UpperArm", +"ValveBiped.Bip01_L_Forearm", +"ValveBiped.Bip01_L_Hand", +"ValveBiped.Bip01_L_Wrist", +"ValveBiped.Bip01_L_Ulna", +"ValveBiped.Bip01_L_Finger4", +"ValveBiped.Bip01_L_Finger41", +"ValveBiped.Bip01_L_Finger42", +"ValveBiped.Bip01_L_Finger3", +"ValveBiped.Bip01_L_Finger31", +"ValveBiped.Bip01_L_Finger32", +"ValveBiped.Bip01_L_Finger2", +"ValveBiped.Bip01_L_Finger21", +"ValveBiped.Bip01_L_Finger22", +"ValveBiped.Bip01_L_Finger1", +"ValveBiped.Bip01_L_Finger11", +"ValveBiped.Bip01_L_Finger12", +"ValveBiped.Bip01_L_Finger0", +"ValveBiped.Bip01_L_Finger01", +"ValveBiped.Bip01_L_Finger02"} + +local playermodelbonesupper={"ValveBiped.Bip01_L_Forearm", +"ValveBiped.Bip01_L_UpperArm", +"ValveBiped.Bip01_L_Clavicle", +"ValveBiped.Bip01_L_Hand", +"ValveBiped.Bip01_Spine4", +"ValveBiped.Bip01_Neck1", +"ValveBiped.Bip01_Head1", +"ValveBiped.Bip01_L_Finger4", +"ValveBiped.Bip01_L_Finger41", +"ValveBiped.Bip01_L_Finger42", +"ValveBiped.Bip01_L_Finger3", +"ValveBiped.Bip01_L_Finger31", +"ValveBiped.Bip01_L_Finger32", +"ValveBiped.Bip01_L_Finger2", +"ValveBiped.Bip01_L_Finger21", +"ValveBiped.Bip01_L_Finger22", +"ValveBiped.Bip01_L_Finger1", +"ValveBiped.Bip01_L_Finger11", +"ValveBiped.Bip01_L_Finger12", +"ValveBiped.Bip01_L_Finger0", +"ValveBiped.Bip01_L_Finger01", +"ValveBiped.Bip01_L_Finger02", +"ValveBiped.Bip01_R_Forearm", +"ValveBiped.Bip01_R_UpperArm", +"ValveBiped.Bip01_R_Clavicle", +"ValveBiped.Bip01_R_Hand", +"ValveBiped.Bip01_R_Finger4", +"ValveBiped.Bip01_R_Finger41", +"ValveBiped.Bip01_R_Finger42", +"ValveBiped.Bip01_R_Finger3", +"ValveBiped.Bip01_R_Finger31", +"ValveBiped.Bip01_R_Finger32", +"ValveBiped.Bip01_R_Finger2", +"ValveBiped.Bip01_R_Finger21", +"ValveBiped.Bip01_R_Finger22", +"ValveBiped.Bip01_R_Finger1", +"ValveBiped.Bip01_R_Finger11", +"ValveBiped.Bip01_R_Finger12", +"ValveBiped.Bip01_R_Finger0", +"ValveBiped.Bip01_R_Finger01"} + +local tableintensity={1,1,1} +VManip.Reset = function() + VManip.Anims={} + VManip.VMGesture=nil + VManip.AssurePos=false + VManip.LockToPly=false + VManip.LockZ=0 + VManip.VMCam=nil + VManip.Cam_Ang=properang + VManip.Cam_AngInt=nil + VManip.StartCycle=0 + VManip.Cycle=0 + VManip.CurGesture=nil + VManip.CurGestureData=nil + VManip.GestureMatrix=nil + VManip.Lerp_Peak=nil + VManip.Lerp_Speed_In=nil + VManip.Lerp_Speed_Out=nil + VManip.Lerp_Curve=nil + VManip.Duration=0 + VManip.HoldTime=nil + VManip.HoldQuit=false + VManip.PreventQuit=false + VManip.QueuedAnim=nil + VManip.Segmented=false + VManip.SegmentFinished=false + VManip.CurSegment=nil + VManip.LastSegment=false + VManip.SegmentCount=0 + VManip.CurSegmentSequence=nil + VManip.GesturePastHold=false + VManip.GestureOnHold=false + VManip.Attachment=nil +end +VManip.Remove = function() + if VManip:IsActive() then hook.Run("VManipPreRemove",VManip:GetCurrentAnim()) end + if IsValid(VManip.VMGesture) then VManip.VMGesture:Remove() end + if IsValid(VManip.VMCam) then VManip.VMCam:Remove() end + VManip.VMGesture=nil + VManip.AssurePos=false + VManip.LockToPly=false + VManip.LockZ=0 + VManip.VMCam=nil + VManip.Cam_Ang=properang + VManip.Cam_AngInt=nil + VManip.Cycle=0 + VManip.StartCycle=0 + VManip.Attachment=nil + VManip.CurGesture=nil + VManip.CurGestureData=nil + VManip.GestureMatrix=nil + VManip.Lerp_Peak=nil + VManip.Lerp_Speed_In=nil + VManip.Lerp_Speed_Out=nil + VManip.Duration=0 + VManip.HoldTime=nil + VManip.HoldQuit=false + VManip.PreventQuit=false + VManip.QueuedAnim=nil + VManip.Segmented=false + VManip.SegmentFinished=false + VManip.CurSegment=nil + VManip.LastSegment=false + VManip.SegmentCount=0 + VManip.CurSegmentSequence=nil + VManip.GesturePastHold=false + VManip.GestureOnHold=false + hook.Run("VManipRemove") +end +VManip:Reset() + +VManip.RegisterAnim = function(self,name,tbl) self.Anims[name]=tbl end +VManip.GetAnim = function(self,name) return self.Anims[name] end +VManip.IsActive = function(self) return IsValid(self.VMGesture) end +VManip.GetVMGesture = function(self) return self.VMGesture end +VManip.GetCurrentAnim = function(self) return self.CurGesture end +VManip.GetCurrentSegment = function(self) return self.CurSegment end +VManip.GetCycle = function(self) return self.Cycle end +VManip.SetCycle = function(self,newcycle) self.Cycle=newcycle end +VManip.IsSegmented = function(self) return self.Segmented end +VManip.GetSegmentCount = function(self) return self.SegmentCount end + local function PlayVMPSound(ent,sound,anim) + if VManip:GetCurrentAnim()==anim and ent:Alive() then + ent:EmitSound(sound) + end + end + local function PlaySoundsInTable(tbl,animname) + + local ply=LocalPlayer() + for k,v in pairs(tbl) do + timer.Simple(v, function() PlayVMPSound(ply,k,animname) end) + end + + end +VManip.PlaySegment = function(self,sequence,lastsegment,soundtable) + if self:IsActive() and self:IsSegmented() and self.SegmentFinished and !self.LastSegment then + if self:GetVMGesture():LookupSequence(sequence)!=-1 then + if hook.Run("VManipPrePlaySegment",self:GetCurrentAnim(),sequence,lastsegment)==false then return end + self:GetVMGesture():ResetSequence(sequence) + VManip.CurSegment=sequence + self:SetCycle(0) + VManip.SegmentFinished=false + self.SegmentCount=self.SegmentCount+1 + if lastsegment then self.LastSegment=true VManip.Lerp_Peak = curtime + VManip.CurGestureData["lerp_peak"] end + if soundtable then PlaySoundsInTable(soundtable,self:GetCurrentAnim()) end + hook.Run("VManipPlaySegment",self:GetCurrentAnim(),sequence,lastsegment) + return true + end + end + return false +end +VManip.IsPreventQuit = function(self) return self.PreventQuit end +VManip.QuitHolding = function(self,animtostop) + if self:IsActive() then + if hook.Run("VManipPreHoldQuit",self:GetCurrentAnim(),animtostop)==false then return end + if (!animtostop and !VManip:IsPreventQuit()) or self:GetCurrentAnim()==animtostop then + self.HoldQuit=true + if self:IsSegmented() then self.LastSegment=true end + hook.Run("VManipHoldQuit",self:GetCurrentAnim(),animtostop) + end + if self.QueuedAnim==animtostop then + self.QueuedAnim=nil + end + end +end +VManip.QueueAnim = function(self,animtoqueue) if self:GetAnim(animtoqueue) then self.QueuedAnim=animtoqueue end end --For event related animations that you want to make sure will play no matter what + + +VMLegs.Reset = function() + + VMLegs.Anims={} + VMLegs.LegParent=nil + VMLegs.LegModel=nil + VMLegs.Cycle=0 + VMLegs.StartCycle=0 + VMLegs.SeqID=nil + VMLegs.CurLegs=nil + +end +VMLegs.Remove = function() + + if IsValid(VMLegs.LegParent) then VMLegs.LegParent:Remove() end + if IsValid(VMLegs.LegModel) then VMLegs.LegModel:Remove() end + + VMLegs.LegParent=nil + VMLegs.LegModel=nil + VMLegs.Cycle=0 + VMLegs.StartCycle=0 + VMLegs.SeqID=nil + VMLegs.CurLegs=nil + +end +VMLegs:Reset() + +VMLegs.RegisterAnim = function(self,name,tbl) self.Anims[name]=tbl end +VMLegs.GetAnim = function(self,name) return self.Anims[name] end +VMLegs.IsActive = function(self) return IsValid(self.LegParent) end +VMLegs.GetCurrentAnim = function(self) return self.CurLegs end + +VManip.PlayAnim = function(self,name) + +local ply=LocalPlayer() +if ply:GetViewEntity() != ply and !self:IsActive() then return end +if IsValid(ply:GetActiveWeapon()) then if ply:GetActiveWeapon():GetHoldType()=="duel" then return false end --doesnt always work +else return false end +if ply:InVehicle() or !ply:Alive() then return false end +if self:IsActive() then return false end + +local vm=ply:GetViewModel() + +local bypass=hook.Run("VManipPreActCheck",name,vm) +if !bypass then + if type(ply:GetActiveWeapon().GetStatus) == "function" then if ply:GetActiveWeapon():GetStatus() == 5 then return false end end + if vm:GetSequenceActivity(vm:GetSequence())==ACT_VM_RELOAD then return false end +end + +local animtoplay=self:GetAnim(name) +if !animtoplay then print("Invalid anim",name) return false end + +if hook.Run("VManipPrePlayAnim",name)==false then return false end + +curtime=CurTime() + +self.Remove() +self.GesturePastHold = false +self.GestureOnHold = false +self.CurGestureData = animtoplay +self.CurGesture = name +self.Lerp_Peak = curtime + animtoplay["lerp_peak"] +vmatrixpeakinfo = animtoplay["lerp_peak"] +self.Lerp_Speed_In = animtoplay["lerp_speed_in"] or 1 +self.Lerp_Speed_Out = animtoplay["lerp_speed_out"] or 1 +self.Loop = animtoplay["loop"] +VManip_modelname = animtoplay["model"] +vmanipholdtime = animtoplay["holdtime"] + +self.VMGesture = ClientsideModel( "models/"..VManip_modelname, RENDERGROUP_BOTH ) +self.VMCam = ClientsideModel( "models/"..VManip_modelname, RENDERGROUP_BOTH ) --Saves me the headache of attachment shit + +self.Cam_AngInt=animtoplay["cam_angint"] or tableintensity + +self.SeqID = self.VMGesture:LookupSequence(name) +if animtoplay["assurepos"] then + self.VMGesture:SetPos( ply:EyePos() ) + VManip.AssurePos=true +elseif !animtoplay["locktoply"] then + self.VMGesture:SetPos( vm:GetPos() ) +end + +if animtoplay["locktoply"] then + self.LockToPly=true + local eyepos=ply:EyePos() + self.VMGesture:SetAngles( ply:EyeAngles() ) + self.VMGesture:SetPos( eyepos ) + self.LockZ=eyepos.z +else + self.VMGesture:SetAngles( vm:GetAngles() ) + self.VMGesture:SetParent(vm) +end + +self.Cam_Ang=animtoplay["cam_ang"] or properang +self.VMCam:SetPos(vector_origin) +self.VMCam:SetAngles( angle_zero ) + +self.VMGesture:ResetSequenceInfo() +self.VMGesture:SetPlaybackRate( 1 ) +self.VMGesture:ResetSequence(self.SeqID) + +self.VMCam:ResetSequenceInfo() +self.VMCam:SetPlaybackRate( 1 ) +self.VMCam:ResetSequence(self.SeqID) + +self.VMatrixlerp=1 +self.Speed=animtoplay["speed"] or 1 + +self.Lerp_Curve=animtoplay["lerp_curve"] or 1 +self.StartCycle=animtoplay["startcycle"] or 0 +self.Segmented=animtoplay["segmented"] or false +self.HoldTime=animtoplay["holdtime"] or nil +self.HoldTimeData=self.HoldTime +self.PreventQuit=animtoplay["preventquit"] or false +if self.HoldTime then self.HoldTime=curtime+self.HoldTime end +self.Cycle=self.StartCycle +self.VMGesture:SetNoDraw(true) +self.VMCam:SetNoDraw(true) +self.Duration=self.VMGesture:SequenceDuration(self.SeqID) +if animtoplay["sounds"] and animtoplay["sounds"]!={} then + PlaySoundsInTable(animtoplay["sounds"],self.CurGesture) +end + +hook.Run("VManipPostPlayAnim",name) +return true + +end + + + +VMLegs.PlayAnim = function(self,name) + +if self:IsActive() then return false end +local animtoplay=self:GetAnim(name) +if !animtoplay then print("Invalid anim",name) return false end + +local ply=LocalPlayer() +if LocalPlayer():ShouldDrawLocalPlayer() then return end +self.Cycle=0 +self.CurLegs=name +self.Speed=animtoplay["speed"] +self.FBoost=animtoplay["forwardboost"] +self.UBoost=animtoplay["upwardboost"] +self.UBoostCache=Vector(0,0,self.UBoost) +local model=animtoplay["model"] +local vm=ply:GetViewModel() +local vmang=vm:GetAngles() +local vmpos=vm:GetPos() + +self.LegParent = ClientsideModel( "models/"..model, RENDERGROUP_BOTH ) +self.LegParent:SetPos( vmpos ) +self.LegParent:SetParent( vm ) +local legang=vm:GetAngles() +legang=Angle(0,legang.y,0) +VMLegs.LegParent:SetAngles(legang) + +self.LegModel = ClientsideModel( string.Replace(ply:GetModel(),"models/models/","models/"), RENDERGROUP_TRANSLUCENT ) +self.LegModel:SetPos( vmpos ) +self.LegModel:SetAngles( vmang ) + +local plyhands=ply:GetHands() +if IsValid(plyhands) then + self.LegModel.GetPlayerColor=plyhands.GetPlayerColor --yes, this is how you do player color. Fucking lol +end + +self.LegModel:SetParent( self.LegParent ) +self.LegModel:AddEffects(EF_BONEMERGE) +for i = 0, self.LegModel:GetNumBodyGroups() do + local bodyg = ply:GetBodygroup(i) + self.LegModel:SetBodygroup(i,bodyg) +end + +for k,v in pairs(playermodelbonesupper) do +local plybone = self.LegModel:LookupBone(v) +if plybone!=nil then +self.LegModel:ManipulateBoneScale( plybone, Vector(0,0,0) ) +end +end + +self.SeqID = self.LegParent:LookupSequence(name) +self.LegParent:ResetSequenceInfo() +self.LegParent:SetPlaybackRate( 1 ) +self.LegParent:ResetSequence(self.SeqID) + +end + +--#########################-- + + +local posparentcache +local curtimecheck=0 --prevents the hook from ever running twice in the same frame +local scalevec = Vector(1,1,1) +hook.Add("PostDrawViewModel", "VManip", function(vm,ply,weapon) + +if VManip:IsActive() then +curtime=CurTime() +if (curtime==curtimecheck and !gui.IsGameUIVisible()) then return end +curtimecheck=CurTime() + +local vment = hook.Run("VManipVMEntity",ply,weapon) +if IsValid(vment) then + vm = vment +end + +if VManip.AssurePos then --Some SWEPs have RIDICULOUS offsets + if posparentcache!=weapon then + posparentcache=weapon + VManip.VMGesture:SetParent(nil) + VManip.VMGesture:SetPos( EyePos() ) + VManip.VMGesture:SetAngles( vm:GetAngles() ) + VManip.VMGesture:SetParent(vm) + end +end + + +if VManip.LockToPly then --A more cruel version of AssurePos + local eyeang=ply:EyeAngles() + local eyepos=EyePos() + local vmang=vm:GetAngles() + local finang=(eyeang-vmang) + finang.y=0 --fucks up on 180 + local newang=eyeang+(finang*0.25) + VManip.VMGesture:SetAngles( newang ) + VManip.VMGesture:SetPos(eyepos) +end + +if !ply:Alive() then VManip:Remove() return end --fun fact, this only runs on respawn for an obvious reason +--VManip.VMGesture:FrameAdvance(FrameTime()*VManip.Speed) --shit the bed, don't use this + + +if VManip.Loop then + if VManip.Cycle>=1 then VManip.Lerp_Peak = curtime + VManip.CurGestureData["lerp_peak"] VManip.Cycle=0 end + if VManip.HoldQuit then VManip.Loop=false end +end + +if !VManip.GestureOnHold then VManip.Cycle=VManip.Cycle+FrameTime()*VManip.Speed end +VManip.VMGesture:SetCycle(VManip.Cycle) +VManip.VMCam:SetCycle(VManip.Cycle) + +if VManip.HoldTime then +if curtime>=VManip.HoldTime and !VManip.GestureOnHold and !VManip.GesturePastHold and !VManip.HoldQuit then + -- local seqdur=VManip.VMGesture:SequenceDuration() + -- VManip.Cycle=(VManip.HoldTimeData)/(seqdur) ply:ChatPrint(seqdur) + -- VManip.VMGesture:SetCycle(VManip.Cycle) + VManip.GestureOnHold=true +elseif VManip.HoldQuit and VManip.GestureOnHold then + VManip.GestureOnHold=false + VManip.GesturePastHold=true + VManip.Lerp_Peak = curtime + VManip.CurGestureData["lerp_peak"]-VManip.CurGestureData["holdtime"] +end +end + + +if (curtime < VManip.Lerp_Peak or (VManip:IsSegmented() and !VManip.LastSegment)) and (!VManip.GestureOnHold or VManip.GesturePastHold) then + VManip.VMatrixlerp = math.Clamp(VManip.VMatrixlerp-(FrameTime()*7)*VManip.Lerp_Speed_In,0,1) +elseif !VManip.Loop and (!VManip.GestureOnHold or VManip.GesturePastHold) then + if !VManip:IsSegmented() or VManip.LastSegment then + VManip.VMatrixlerp = math.Clamp(VManip.VMatrixlerp+(FrameTime()*7)*VManip.Lerp_Speed_Out,0,1) + end +end + +local rigpick2 = leftarmbones +local rigpick = leftarmbones + +VManip.VMGesture:SetupBones() +VManip.VMGesture:DrawModel() + +--[[The actual manipulation part below]] + +for k,v in pairs(rigpick) do + +if v == "ValveBiped.Bip01_L_Ulna" then + local lb=VManip.VMGesture:LookupBone("ValveBiped.Bip01_L_Forearm") + if lb then + VManip.GestureMatrix = VManip.VMGesture:GetBoneMatrix(lb) + end +else + local lb=VManip.VMGesture:LookupBone(rigpick2[k]) + if lb then + VManip.GestureMatrix = VManip.VMGesture:GetBoneMatrix(lb) + end +end + +local VMBone = vm:LookupBone(v) +if VMBone !=nil and VManip.GestureMatrix != nil then + local VMBoneMatrix = vm:GetBoneMatrix(VMBone) + if VMBoneMatrix then + local VMBoneMatrixCache = VMBoneMatrix:ToTable() + local VMGestureMatrixCache = VManip.GestureMatrix:ToTable() + for k,v in pairs(VMGestureMatrixCache) do + for l,b in pairs(v) do + VMGestureMatrixCache[k][l] = LerpC(VManip.VMatrixlerp, b, VMBoneMatrixCache[k][l],VManip.Lerp_Curve) + end + end + local m = Matrix(VMGestureMatrixCache) + m:SetScale(scalevec) + if type(ply:GetActiveWeapon().GetStatus) == "function" then if ply:GetActiveWeapon():GetStatus() != 5 then + vm:SetBoneMatrix(VMBone,m) + end + else vm:SetBoneMatrix(VMBone,m) end + end + +end +end + +if VManip.Cycle>=1 and !VManip.Loop then + if VManip:IsSegmented() and !VManip.SegmentFinished then + VManip.SegmentFinished=true + hook.Run("VManipSegmentFinish",VManip:GetCurrentAnim(),VManip:GetCurrentSegment(),VManip.LastSegment,VManip:GetSegmentCount()) + elseif VManip:IsSegmented() and VManip.LastSegment then + if VManip.VMatrixlerp>=1 then VManip:Remove() end + elseif !VManip:IsSegmented() then + if VManip.CurGestureData["loop"] then + if VManip.VMatrixlerp>=1 then VManip:Remove() end + else VManip.Remove() return + end + end +end + +elseif VManip.QueuedAnim then + +if VManip:PlayAnim(VManip.QueuedAnim) then VManip.QueuedAnim=nil end + +end + +end) + +local anglef=Angle(0,1,0) +local curtimelegscheck = 0 +hook.Add("PostDrawViewModel", "VMLegs", function(vm,ply,weapon) --Very basic stuff, you see + +if VMLegs:IsActive() then + curtime=CurTime() + if (curtime==curtimelegscheck and !gui.IsGameUIVisible()) then return end + curtimelegscheck=CurTime() + + local vment = hook.Run("VManipLegsVMEntity",ply,weapon) + if IsValid(vment) then + vm = vment + end + + local legang=vm:GetAngles() + legang=Angle(0,legang.y,0) + VMLegs.LegParent:SetAngles(legang) + VMLegs.LegParent:SetPos(vm:GetPos()+(legang:Forward()*VMLegs.FBoost)+VMLegs.UBoostCache) + VMLegs.Cycle=VMLegs.Cycle+FrameTime()*VMLegs.Speed + VMLegs.LegParent:SetCycle(VMLegs.Cycle) + if VMLegs.Cycle>=1 then VMLegs.Remove() return end + +end + +end) + +concommand.Add("VManip_List",function(ply) PrintTable(VManip.Anims) end) +concommand.Add("VManip_ListSimple",function(ply) for k,v in pairs(VManip.Anims) do print(k," | ",v["model"]) end end) + + +net.Receive("VManip_SimplePlay",function(len) + +local anim=net.ReadString() +VManip:PlayAnim(anim) + +end) +--[[Maybe merge these two in one message, using enums]] +net.Receive("VManip_StopHold",function(len) + +local anim=net.ReadString() +if anim=="" then VManip:QuitHolding() else VManip:QuitHolding(anim) end + +end) + +hook.Add("NeedsDepthPass","VManip_RubatPLZ",function() --CalcView attachments need to be retrieved outside of CalcView + +--Just gonna slide this in there, yea. +if VManip.QueuedAnim then +local ply=LocalPlayer() +if ply:GetViewEntity()!=ply or ply:ShouldDrawLocalPlayer() then VManip.QueuedAnim=nil end +end +--Good. + +if !VManip:IsActive() then return end + +if !LocalPlayer():Alive() then VManip:Remove() return end + +local allatt=VManip.VMCam:GetAttachments() +if #allatt==0 then return end +local lookup=allatt[1]["id"] +local att=VManip.VMCam:GetAttachment(lookup) +VManip.Attachment=att + +end) + +local calcang = Angle() +hook.Add("CalcView","VManip_Cam",function(ply,origin,angles,fov,self) + +if self == true then return end +if !VManip:IsActive() or !VManip.Attachment then return end +if ply:GetViewEntity()!=ply or ply:ShouldDrawLocalPlayer() then return end +local view={} +local camang=VManip.Attachment.Ang-VManip.Cam_Ang +camang.x = camang.x*VManip.Cam_AngInt[1] +camang.y = camang.y*VManip.Cam_AngInt[2] +camang.z = camang.z*VManip.Cam_AngInt[3] + +view.angles = angles + +local hookv = hook.Run("CalcView", ply, origin, angles, fov, true) +if hookv.angles then + hookv.angles:Add(camang) + view.angles:Set(hookv.angles) +end + +view.fov = fov or hookv.fov +view.origin = hookv.origin +return view + +end) + + +hook.Add( "StartCommand", "VManip_PreventReload", function(ply,ucmd) --prevent reload hook +if VManip:IsActive() and !ply:ShouldDrawLocalPlayer() then ucmd:RemoveKey(8192) end +end) +hook.Add("TFA_PreReload", "VManip_PreventTFAReload", function(wepom,keyreleased) --prevent reload on tfa hook +if VManip:IsActive() then return "no" end +end) + + + +--Time to load everythin' +local function VManip_FindAndImport() + +local path="vmanip/anims/" +local anims=file.Find(path.."*.lua","lcl") + +for k,v in pairs(anims) do + include(path..v) +end +print("VManip loaded with "..table.Count(VManip.Anims).." animations") + +end + + +hook.Add("InitPostEntity","VManip_ImportAnims",function() + +VManip_FindAndImport() +hook.Remove("InitPostEntity","VManip_ImportAnims") +end) + +hook.Add("VManipPreActCheck","VManipArcCWFix",function(name,vm) + +local ply=LocalPlayer() +local activewep=ply:GetActiveWeapon() +if activewep.ArcCW then + if activewep:ShouldDrawCrosshair() or vm:GetCycle()>0.99 then return true end --crossh check is pretty rudimentary +end --vm getcycle is fucked for some reason except on some anims, makes me wonder + +end) +hook.Add("VManipPrePlayAnim","VManipArcCWReload",function() + +local ply=LocalPlayer() +local activewep=ply:GetActiveWeapon() +if activewep.ArcCW then + if activewep:GetNWBool("reloading") then return false end +end + +end) +hook.Add("VManipPrePlayAnim","VManipMWBaseReload",function() + +local ply=LocalPlayer() +local activewep=ply:GetActiveWeapon() +if activewep.GetIsReloading then + if activewep:GetIsReloading() then return false end +end + +end) + +hook.Add("VManipVMEntity", "VManipMWBase", function(ply, weapon) + local vm = weapon.m_ViewModel + if IsValid(vm) then + VManip:GetVMGesture():SetPos(vm:GetPos()) + VManip:GetVMGesture():SetAngles(vm:GetAngles()) + return vm + end +end) + +hook.Add("VManipLegsVMEntity", "VManipMWBase", function(ply, weapon) + local vm = weapon.m_ViewModel + if IsValid(vm) then + return vm + end +end) + +concommand.Add("VManip_FindAndImport",VManip_FindAndImport) +RunConsoleCommand("VManip_FindAndImport") --Runs it again if this file is refreshed diff --git a/garrysmod/addons/vmanip_ft/lua/autorun/server/sv_vmanip.lua b/garrysmod/addons/vmanip_ft/lua/autorun/server/sv_vmanip.lua new file mode 100644 index 0000000..f54f5c2 --- /dev/null +++ b/garrysmod/addons/vmanip_ft/lua/autorun/server/sv_vmanip.lua @@ -0,0 +1,18 @@ +util.AddNetworkString("VManip_SimplePlay") +util.AddNetworkString("VManip_StopHold") + +--VManip_SimplePlay: WriteString of anim to play on client (not guaranteed to play) +--VManip_StopHold: WriteString of anim to stop holding on client + +local function VManip_FindAndImport() + +local path="vmanip/anims/" +local anims=file.Find(path.."*.lua","lsv") + +for k,v in pairs(anims) do + AddCSLuaFile(path..v) +end + +end + +VManip_FindAndImport() diff --git a/garrysmod/addons/vmanip_ft/lua/autorun/sh_contextualanims.lua b/garrysmod/addons/vmanip_ft/lua/autorun/sh_contextualanims.lua new file mode 100644 index 0000000..673713b --- /dev/null +++ b/garrysmod/addons/vmanip_ft/lua/autorun/sh_contextualanims.lua @@ -0,0 +1,104 @@ +if SERVER then +util.AddNetworkString("VManip_BarnacleGrip") + +hook.Add("PlayerUse","VManip_UseAnim",function(ply,ent) + +local usecooldown=ply.usecooldown or 0 +if ent!=nil and ply:KeyPressed(IN_USE) and CurTime()>usecooldown and !ent.LFS then --fix LFS + net.Start("VManip_SimplePlay") net.WriteString("use") net.Send(ply) ply.usecooldown=CurTime()+1 +end + +end) + +hook.Add("PlayerPostThink","VManip_BarnacleCheck",function(ply) --EFlag does not return correct value clientside + +local check=ply.barnaclecheck!=2 +local eflag=ply:IsEFlagSet(EFL_IS_BEING_LIFTED_BY_BARNACLE) + +if eflag and check then + ply.barnaclecheck=1 +elseif !check and !eflag then + ply.barnaclecheck=0 + ply.barnacledelay=CurTime()+1 + net.Start("VManip_BarnacleGrip") net.WriteBool(false) net.Send(ply) +end + +if ply.barnaclecheck==1 then + ply.barnaclecheck=2 + net.Start("VManip_BarnacleGrip") net.WriteBool(true) net.Send(ply) +end + +end) + +hook.Add("EntityTakeDamage","VManip_ShieldExplosion",function(ply,dmg) + if !ply:IsPlayer() then return end + local dmgtype=dmg:GetDamageType() + if dmgtype==DMG_BLAST or dmgtype==134217792 then + net.Start("VManip_SimplePlay") net.WriteString("shieldexplosion") net.Send(ply) + end +end) + +elseif CLIENT then + +local vmp_contextual_voicechat = CreateClientConVar("cl_vmanip_voicechat", 1, true, false,"Toggles the voice chat animation") + + +net.Receive("VManip_BarnacleGrip",function(len) + +local choked=net.ReadBool() +local ply=LocalPlayer() + +if choked then + VManip:QueueAnim("barnaclechoke") + ply.barnaclegrip=true +else + VManip:QuitHolding("barnaclechoke") + ply.barnaclegrip=false +end + +end) + +hook.Add("CreateMove","VManip_SwimAnims",function(cmd) + +local ply=LocalPlayer() +if ply:WaterLevel()<2 then + local vmpcuranim=VManip:GetCurrentAnim() + if vmpcuranim=="swimleft" or vmpcuranim=="swimforward" then + VManip:QuitHolding() + end +return end + +local buttons=cmd:GetButtons() +local movingleft=bit.band(buttons,IN_MOVELEFT)!=0 +local movingforward=(bit.band(buttons,IN_FORWARD)!=0 or bit.band(buttons,IN_MOVERIGHT)!=0) +local vmpcuranim=VManip:GetCurrentAnim() + +if !VManip:IsActive() then + if movingleft then + VManip:PlayAnim("swimleft") + elseif movingforward then + VManip:PlayAnim("swimforward") + end +else + if !movingleft and vmpcuranim=="swimleft" then + VManip:QuitHolding("swimleft") return + elseif !movingforward and vmpcuranim=="swimforward" then + VManip:QuitHolding("swimforward") return + end +end + +end) + + +hook.Add("PlayerStartVoice","VManip_StartVoiceAnim",function(ply) +if vmp_contextual_voicechat:GetBool() and ply==LocalPlayer() then + VManip:PlayAnim("voicechat") +end +end) +hook.Add("PlayerEndVoice","VManip_EndVoiceAnim",function(ply) +if vmp_contextual_voicechat:GetBool() and ply==LocalPlayer() then + VManip:QuitHolding("voicechat") +end +end) + +end diff --git a/garrysmod/addons/vmanip_ft/lua/autorun/sh_vmanipvaulting.lua b/garrysmod/addons/vmanip_ft/lua/autorun/sh_vmanipvaulting.lua new file mode 100644 index 0000000..4543183 --- /dev/null +++ b/garrysmod/addons/vmanip_ft/lua/autorun/sh_vmanipvaulting.lua @@ -0,0 +1,232 @@ +local chestvec = Vector(0,0,32) +local thoraxvec = Vector(0,0,48) +local eyevec = Vector(0,0,64) +local mantlevec = Vector(0,0,16) +local vault1vec = Vector(0,0,24) + +local vpunch1 = Angle(0,0,-1) +local vpunch2 = Angle(-1,0,0) + +//DT vars +local meta = FindMetaTable("Player") +function meta:GetMantle() + return self:GetDTInt(13) +end +function meta:SetMantle(value) + return self:SetDTInt(13, value) +end + +function meta:GetMantleLerp() + return self:GetDTFloat(13) +end +function meta:SetMantleLerp(value) + return self:SetDTFloat(13, value) +end + +function meta:GetMantleStartPos() + return self:GetDTVector(13) +end +function meta:SetMantleStartPos(value) + return self:SetDTVector(13, value) +end + +function meta:GetMantleEndPos() + return self:GetDTVector(14) +end +function meta:SetMantleEndPos(value) + return self:SetDTVector(14, value) +end + +local pkweps = { --these already have their own climbing +["parkourmod"] = true, +["m_sprinting"] = true +} + +local function PlayVaultAnim(ply, legs) + if game.SinglePlayer() and SERVER then + ply:SendLua('if VManip then VManip:PlayAnim("vault") end') + if legs then + ply:SendLua('if VMLegs then VMLegs:PlayAnim("test") end') + end + return + end + if CLIENT and VManip then + VManip:PlayAnim("vault") + if legs then + VMLegs:PlayAnim("test") + end + end +end + +local function Vault1(ply, mv, ang, t, h) + t.start = mv:GetOrigin() + eyevec + ang:Forward()* 50 + t.endpos = t.start - (chestvec) + t.filter = ply + t.mask = MASK_PLAYERSOLID + t = util.TraceLine(t) + if (t.Entity and t.Entity.IsNPC) and (t.Entity:IsNPC() or t.Entity:IsPlayer()) then + return false + end + if t.Hit and t.Fraction > 0.5 then + local tsafety = {} + tsafety.start = t.StartPos - ang:Forward() * 50 + tsafety.endpos = t.StartPos + tsafety.filter = ply + tsafety.mask = MASK_PLAYERSOLID + tsafety = util.TraceLine(tsafety) + + if tsafety.Hit then return false end + + h.start = t.HitPos + mantlevec + h.endpos = h.start + h.filter = ply + h.mask = MASK_PLAYERSOLID + h.mins, h.maxs = ply:GetCollisionBounds() + local hulltr = util.TraceHull(h) + if !hulltr.Hit then + if t.HitNormal.x != 0 then t.HitPos.z = t.HitPos.z + 12 end + ply:SetMantleStartPos(mv:GetOrigin()) + ply:SetMantleEndPos(t.HitPos + mantlevec) + ply:SetMantleLerp(0) + ply:SetMantle(1) + PlayVaultAnim(ply) + ply:ViewPunch(vpunch1) + if SERVER then ply:PlayStepSound(1) end + return true + end + end + return false +end + +local function Vault2(ply, mv, ang, t, h) + t.start = mv:GetOrigin() + chestvec + t.endpos = t.start + ang:Forward() * 20 + t.filter = ply + t.mask = MASK_PLAYERSOLID + + local vaultpos = t.endpos + ang:Forward() * 50 + t = util.TraceLine(t) + if (t.Entity and t.Entity.IsNPC) and (t.Entity:IsNPC() or t.Entity:IsPlayer()) then + return false + end + + if t.Hit and t.Fraction > 0.5 then + local tsafety = {} + tsafety.start = mv:GetOrigin() + eyevec + tsafety.endpos = tsafety.start + ang:Forward() * 50 + tsafety.filter = ply + tsafety.mask = MASK_PLAYERSOLID + tsafety = util.TraceLine(tsafety) + + if tsafety.Hit then return false end + + local tsafety = {} + tsafety.start = mv:GetOrigin() + eyevec + ang:Forward() * 50 + tsafety.endpos = tsafety.start - thoraxvec + tsafety.filter = ply + tsafety.mask = MASK_PLAYERSOLID + tsafety = util.TraceLine(tsafety) + + if tsafety.Hit then return false end + h.start = t.StartPos + ang:Forward() * 50 + h.endpos = h.start + h.filter = ply + h.mask = MASK_PLAYERSOLID + h.mins, h.maxs = ply:GetCollisionBounds() + local hulltr = util.TraceHull(h) + if !hulltr.Hit then + ply:SetMantleStartPos(mv:GetOrigin()) + ply:SetMantleEndPos(vaultpos) + ply:SetMantleLerp(0) + ply:SetMantle(2) + PlayVaultAnim(ply, true) + ply:ViewPunch(vpunch2) + if SERVER then ply:EmitSound("vmanip/goprone_0"..math.random(1,3)..".wav") end + return true + end + end + return false +end + +hook.Add("SetupMove", "vmanip_vault", function(ply, mv, cmd) + + if ply.MantleDisabled then + return + end + + if !ply:Alive() then + if ply:GetMantle() != 0 then + ply:SetMantle(0) + end + return + end + + if ply:GetMantle() == 0 then + local mvtype = ply:GetMoveType() + if (ply:OnGround() or mv:GetVelocity().z < -600 or mvtype == MOVETYPE_NOCLIP or mvtype == MOVETYPE_LADDER) then + return + end + end + + local activewep = ply:GetActiveWeapon() + if IsValid(activewep) and activewep.GetClass then + if pkweps[activewep:GetClass()] then + return + end + end + + ply.mantletr = ply.mantletr or {} + ply.mantlehull = ply.mantlehull or {} + local t = ply.mantletr + local h = ply.mantlehull + + if ply:GetMantle() == 0 and !ply:OnGround() and mv:KeyDown(IN_FORWARD) and !mv:KeyDown(IN_DUCK) and !ply:Crouching() then + local ang = mv:GetAngles() + ang.x = 0 ang.z = 0 + if !Vault1(ply, mv, ang, t, h) then + Vault2(ply, mv, ang, t, h) + end + end + + if ply:GetMantle() != 0 then + mv:SetButtons(0) + mv:SetMaxClientSpeed(0) + mv:SetSideSpeed(0) mv:SetUpSpeed(0) mv:SetForwardSpeed(0) + mv:SetVelocity(vector_origin) + ply:SetMoveType(MOVETYPE_NOCLIP) + + local mantletype = ply:GetMantle() + local mlerp = ply:GetMantleLerp() + local FT = FrameTime() + local TargetTick = (1/FT)/66.66 + local mlerpend = ((mantletype == 1 and 0.8) or 0.75) + local mlerprate = ((mantletype == 1 and 0.075) or 0.1)/TargetTick + + ply:SetMantleLerp(Lerp(mlerprate, mlerp, 1)) + local mvec = LerpVector(ply:GetMantleLerp(), ply:GetMantleStartPos(), ply:GetMantleEndPos()) + mv:SetOrigin(mvec) + + if mlerp >= mlerpend or mvec:DistToSqr(ply:GetMantleEndPos()) < 280 then + if ply:GetMantle() == 2 then + local ang = mv:GetAngles() + ang.x = 0 ang.z = 0 + mv:SetVelocity(ang:Forward() * 200) + end + ply:SetMantle(0) + ply:SetMoveType(MOVETYPE_WALK) + if SERVER and !ply:IsInWorld() then + mv:SetOrigin(ply:GetMantleStartPos()) + end + h.start = mv:GetOrigin() + h.endpos = h.start + h.filter = ply + h.mask = MASK_PLAYERSOLID + h.mins, h.maxs = ply:GetCollisionBounds() + local hulltr = util.TraceHull(h) + if hulltr.Hit then + mv:SetOrigin(ply:GetMantleEndPos()) + end + end + end + +end) diff --git a/garrysmod/addons/vmanip_ft/lua/vmanip/anims/contextual_anims.lua b/garrysmod/addons/vmanip_ft/lua/vmanip/anims/contextual_anims.lua new file mode 100644 index 0000000..97f43af --- /dev/null +++ b/garrysmod/addons/vmanip_ft/lua/vmanip/anims/contextual_anims.lua @@ -0,0 +1,75 @@ +AddCSLuaFile() + +VManip:RegisterAnim("swimforward", +{ +["model"]="c_vmanip.mdl", +["lerp_peak"]=1.4, +["lerp_speed_in"]=1, +["lerp_speed_out"]=0.8, +["lerp_curve"]=1, +["speed"]=0.6, +["startcycle"]=0, +["sounds"]={}, +["loop"]=true +} +) + +VManip:RegisterAnim("swimleft", +{ +["model"]="c_vmanip.mdl", +["lerp_peak"]=1.4, +["lerp_speed_in"]=1, +["lerp_speed_out"]=0.8, +["lerp_curve"]=1, +["speed"]=0.6, +["startcycle"]=0, +["sounds"]={}, +["loop"]=true +} +) + +VManip:RegisterAnim("shieldexplosion", +{ +["model"]="weapons/c_handanims.mdl", +["lerp_peak"]=1.25, +["lerp_speed_in"]=0.65, +["lerp_speed_out"]=0.95, +["lerp_curve"]=1.75, +["speed"]=0.5, +["startcycle"]=0.02, +["cam_angint"]={1,0.25,0.5}, +["loop"]=false +--["holdtime"]=nil +} +) + +VManip:RegisterAnim("barnaclechoke", +{ +["model"]="weapons/c_handanims.mdl", +["lerp_peak"]=0.3, +["lerp_speed_in"]=0.65, +["lerp_speed_out"]=0.95, +["lerp_curve"]=1.75, +["speed"]=1.5, +["startcycle"]=0.02, +["cam_angint"]={1,0.25,0.5}, +["loop"]=false, +["holdtime"]=0.2 +} +) + +VManip:RegisterAnim("voicechat", +{ +["model"]="weapons/c_handanims.mdl", +["lerp_peak"]=0.3, +["lerp_speed_in"]=0.65, +["lerp_speed_out"]=0.95, +["lerp_curve"]=1.25, +["speed"]=2.5, +["startcycle"]=0, +["cam_angint"]={0.01,0.01,0.05}, +["loop"]=false, +["holdtime"]=0.21, +["locktoply"]=true +} +) diff --git a/garrysmod/addons/vmanip_ft/lua/vmanip/anims/vmanip_baseanims.lua b/garrysmod/addons/vmanip_ft/lua/vmanip/anims/vmanip_baseanims.lua new file mode 100644 index 0000000..9ca8bde --- /dev/null +++ b/garrysmod/addons/vmanip_ft/lua/vmanip/anims/vmanip_baseanims.lua @@ -0,0 +1,104 @@ +AddCSLuaFile() + +--[[ IN BOTH CASES: NAME SHOULD BE THE ACTUAL SEQUENCE NAME +You don't have to put every value, but some like model are obviously needed + +Hands +"model" - path to model +"lerp_peak" - time when the hand should transition back to the weapon +"lerp_speed_in" - speed at which the hand transitions into the anim +"lerp_speed_out" - speed at which the hand transitions out of the anim +"lerp_curve" - power of the curve +"speed" - playback speed +"startcycle" - time to start the anim at +"cam_ang" - angle offset for the camera +"cam_angint" - intensity multiplier of the camera +"sounds" - table of sounds, keys represent the path and their value the time it plays at. do not use past holdtime lmao +"loop" - loop the anim instead of stopping +"segmented" - when anim is over, freezes it and waits for SegmentPlay(sequence,lastanim). Repeat if lastanim is false +^Note: lerp peak and related values are used for the "last segment" instead. + +"holdtime" - the time when the anim should be paused +"preventquit" - ONLY accept QuitHolding request if the argument is our anim. Use very cautiously +"assurepos" - for important anims, makes sure the position isn't offset by sweps. Use locktoply it's better +"locktoply" - for when assurepos isn't enough. + + +Legs +"model" - path to model +"speed" - playback speed +"forwardboost" - forward offset +"upboost" - vertical offset (in actual hammer units) + +]] + +VManip:RegisterAnim("use", +{ +["model"]="c_vmanipinteract.mdl", +["lerp_peak"]=0.4, +["lerp_speed_in"]=1, +["lerp_speed_out"]=0.8, +["lerp_curve"]=2.5, +["speed"]=1, +["startcycle"]=0.1, +["sounds"]={}, +["loop"]=false +} +) + + +VManip:RegisterAnim("vault", +{ +["model"]="c_vmanipvault.mdl", +["lerp_peak"]=0.4, +["lerp_speed_in"]=1, +["lerp_speed_out"]=0.5, +["lerp_curve"]=1, +["speed"]=1 +} +) + +VManip:RegisterAnim("handslide", +{ +["model"]="c_vmanipvault.mdl", +["lerp_peak"]=0.2, +["lerp_speed_in"]=1, +["lerp_speed_out"]=0.8, +["lerp_curve"]=2, +["speed"]=1.5, +["holdtime"]=0.25, +} +) + +VManip:RegisterAnim("adrenalinestim", +{ +["model"]="old/c_vmanip.mdl", +["lerp_peak"]=1.1, +["lerp_speed_in"]=1, +["speed"]=0.7, +["sounds"]={}, +["loop"]=false +} +) + +VManip:RegisterAnim("thrownade", +{ +["model"]="c_vmanipgrenade.mdl", +["lerp_peak"]=0.85, +["lerp_speed_in"]=1.2, +["lerp_speed_out"]=1.2, +["lerp_curve"]=1, +["speed"]=1, +["holdtime"]=0.4, +} +) + +--################################### + +VMLegs:RegisterAnim("test", --lmao, im not recompiling to change THAT shit +{ +["model"]="c_vmaniplegs.mdl", +["speed"]=1.5, +["forwardboost"]=4, +["upwardboost"]=0 +}) diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanip.dx80.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanip.dx80.vtx new file mode 100644 index 0000000..642c2f1 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanip.dx80.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanip.dx90.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanip.dx90.vtx new file mode 100644 index 0000000..a6d210a Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanip.dx90.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanip.mdl b/garrysmod/addons/vmanip_ft/models/c_vmanip.mdl new file mode 100644 index 0000000..3fa8993 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanip.mdl differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanip.sw.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanip.sw.vtx new file mode 100644 index 0000000..e9834a4 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanip.sw.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanip.vvd b/garrysmod/addons/vmanip_ft/models/c_vmanip.vvd new file mode 100644 index 0000000..d1ca9fa Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanip.vvd differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.dx80.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.dx80.vtx new file mode 100644 index 0000000..ce14ef9 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.dx80.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.dx90.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.dx90.vtx new file mode 100644 index 0000000..383edb1 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.dx90.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.mdl b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.mdl new file mode 100644 index 0000000..88501f0 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.mdl differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.sw.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.sw.vtx new file mode 100644 index 0000000..1e714c6 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.sw.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.vvd b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.vvd new file mode 100644 index 0000000..388ff58 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipgrenade.vvd differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.dx80.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.dx80.vtx new file mode 100644 index 0000000..70f93ea Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.dx80.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.dx90.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.dx90.vtx new file mode 100644 index 0000000..0f786c8 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.dx90.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.mdl b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.mdl new file mode 100644 index 0000000..d7cc2a0 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.mdl differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.sw.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.sw.vtx new file mode 100644 index 0000000..3eaee94 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.sw.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.vvd b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.vvd new file mode 100644 index 0000000..ef22095 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipinteract.vvd differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.dx80.vtx b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.dx80.vtx new file mode 100644 index 0000000..8379004 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.dx80.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.dx90.vtx b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.dx90.vtx new file mode 100644 index 0000000..3879413 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.dx90.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.mdl b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.mdl new file mode 100644 index 0000000..b00593f Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.mdl differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.sw.vtx b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.sw.vtx new file mode 100644 index 0000000..07ce928 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.sw.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.vvd b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.vvd new file mode 100644 index 0000000..ae54626 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmaniplegs.vvd differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipvault.dx80.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.dx80.vtx new file mode 100644 index 0000000..aed072b Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.dx80.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipvault.dx90.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.dx90.vtx new file mode 100644 index 0000000..aceb43f Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.dx90.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipvault.mdl b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.mdl new file mode 100644 index 0000000..43ca63a Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.mdl differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipvault.sw.vtx b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.sw.vtx new file mode 100644 index 0000000..99a28c3 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.sw.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/c_vmanipvault.vvd b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.vvd new file mode 100644 index 0000000..2aba5f2 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/c_vmanipvault.vvd differ diff --git a/garrysmod/addons/vmanip_ft/models/old/c_vmanip.dx80.vtx b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.dx80.vtx new file mode 100644 index 0000000..d5759b6 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.dx80.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/old/c_vmanip.dx90.vtx b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.dx90.vtx new file mode 100644 index 0000000..8fc514d Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.dx90.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/old/c_vmanip.mdl b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.mdl new file mode 100644 index 0000000..9948f97 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.mdl differ diff --git a/garrysmod/addons/vmanip_ft/models/old/c_vmanip.sw.vtx b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.sw.vtx new file mode 100644 index 0000000..c3fb6af Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.sw.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/old/c_vmanip.vvd b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.vvd new file mode 100644 index 0000000..ad1e167 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/old/c_vmanip.vvd differ diff --git a/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.dx80.vtx b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.dx80.vtx new file mode 100644 index 0000000..49919b4 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.dx80.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.dx90.vtx b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.dx90.vtx new file mode 100644 index 0000000..a20e03c Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.dx90.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.mdl b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.mdl new file mode 100644 index 0000000..f78b040 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.mdl differ diff --git a/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.sw.vtx b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.sw.vtx new file mode 100644 index 0000000..b83e2f0 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.sw.vtx differ diff --git a/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.vvd b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.vvd new file mode 100644 index 0000000..cdc571a Binary files /dev/null and b/garrysmod/addons/vmanip_ft/models/weapons/c_handanims.vvd differ diff --git a/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_01.wav b/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_01.wav new file mode 100644 index 0000000..dd8f6e6 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_01.wav differ diff --git a/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_02.wav b/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_02.wav new file mode 100644 index 0000000..08b52a6 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_02.wav differ diff --git a/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_03.wav b/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_03.wav new file mode 100644 index 0000000..ece0dad Binary files /dev/null and b/garrysmod/addons/vmanip_ft/sound/vmanip/goprone_03.wav differ diff --git a/garrysmod/addons/vmanip_ft/sound/vmanip/unprone.wav b/garrysmod/addons/vmanip_ft/sound/vmanip/unprone.wav new file mode 100644 index 0000000..2695146 Binary files /dev/null and b/garrysmod/addons/vmanip_ft/sound/vmanip/unprone.wav differ diff --git a/garrysmod/addons/wos/lua/autorun/wiltos_dynabase_loader.lua b/garrysmod/addons/wos/lua/autorun/wiltos_dynabase_loader.lua new file mode 100644 index 0000000..502f0a4 --- /dev/null +++ b/garrysmod/addons/wos/lua/autorun/wiltos_dynabase_loader.lua @@ -0,0 +1,29 @@ + +--[[------------------------------------------------------------------- + Dynamic Base: + Bringing the power everyone. Developer, server owner, and player. + Powered by + _ _ _ ___ ____ + __ _(_) | |_ / _ \/ ___| + \ \ /\ / / | | __| | | \___ \ + \ V V /| | | |_| |_| |___) | + \_/\_/ |_|_|\__|\___/|____/ + + _____ _ _ _ +|_ _|__ ___| |__ _ __ ___ | | ___ __ _(_) ___ ___ + | |/ _ \/ __| '_ \| '_ \ / _ \| |/ _ \ / _` | |/ _ \/ __| + | | __/ (__| | | | | | | (_) | | (_) | (_| | | __/\__ \ + |_|\___|\___|_| |_|_| |_|\___/|_|\___/ \__, |_|\___||___/ + |___/ +-------------------------------------------------------------------]]--[[ + + Lua Developer: King David + Contact: http://steamcommunity.com/groups/wiltostech + +----------------------------------------]]-- + +if SERVER then + AddCSLuaFile( "wos/dynabase/loader/loader.lua" ) +end + +include( "wos/dynabase/loader/loader.lua" ) diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/cl_config_menu.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_config_menu.lua new file mode 100644 index 0000000..9bd8e81 --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_config_menu.lua @@ -0,0 +1,816 @@ +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} +wOS.DynaBase.WorkshopCache = wOS.DynaBase.WorkshopCache or {} + +local w, h = ScrW(), ScrH() + +local male_mat = "icon16/male.png" +local female_mat = "icon16/female.png" +local zombie_mat = "icon16/bug.png" +local shared_mat = "icon16/group.png" + +local reanim_mat = "icon16/application_edit.png" +local extend_mat = "icon16/application_add.png" + +surface.CreateFont( "wOS.DynaBase.TitleFont", { + font = "Roboto Cn", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 24*(h/1200), + weight = 1000, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "wOS.DynaBase.QuestionFont", { + font = "Roboto Cn", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 108*(h/1200), + weight = 1000, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "wOS.DynaBase.DescFont",{ + font = "Roboto Cn", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 18*(h/1200), + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "wOS.DynaBase.URLFont",{ + font = "Roboto Cn", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 12*(h/1200), + weight = 300, + blursize = 0, + scanlines = 0, + antialias = true, + underline = true, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +local function GetContainingAddon( mdl ) + mdl = mdl or "models/m_anm.mdl" + for _, addon in pairs( engine.GetAddons() ) do + local modelFound = file.Find(mdl, addon.title) + if #modelFound != 0 then return addon end + end +end + +local function GetWorkshopInfo( addon, callback ) + if not addon or not callback then return end + if wOS.DynaBase.WorkshopCache[ addon.wsid ] then + callback( wOS.DynaBase.WorkshopCache[ addon.wsid ].Data, wOS.DynaBase.WorkshopCache[ addon.wsid ].Icon ) + return + end + + steamworks.FileInfo( addon.wsid, function( result ) + if not result then return end + wOS.DynaBase.WorkshopCache[ addon.wsid ] = {} + wOS.DynaBase.WorkshopCache[ addon.wsid ].Data = result + if not result.previewid then return end + steamworks.Download( result.previewid, true, function( cache ) + if not cache then callback( result, nil ) return end + local mat = AddonMaterial(cache) + + // Jazztronauts taught me not to trust this little material + local baseTex = ( mat and mat:GetTexture("$basetexture") ) or nil + if baseTex == nil then + mat = AddonMaterial(cache) + end + wOS.DynaBase.WorkshopCache[ addon.wsid ].Icon = mat + callback( result, mat ) + end ) + end ) +end + +function wOS.DynaBase:OpenConfigMenu() + + if self.OverFrame then + self.OverFrame:Remove() + self.OverFrame = nil + gui.EnableScreenClicker( false ) + return + end + + self.OverFrame = vgui.Create( "DFrame" ) + self.OverFrame:SetSize( w, h ) + self.OverFrame:Center() + self.OverFrame.Paint = function() end + self.OverFrame:SetTitle( "" ) + self.OverFrame:ShowCloseButton( false ) + self.OverFrame:SetDraggable( false ) + + gui.EnableScreenClicker( true ) + self.AnimMenu = vgui.Create( "DFrame", self.OverFrame ) + self.AnimMenu:SetSize( w*0.4, h*0.5 ) + self.AnimMenu:Center() + self.AnimMenu:MakePopup() + self.AnimMenu:SetTitle( "wiltOS Dynamic Animation Manager" ) + self.AnimMenu:ShowCloseButton( true ) + self.AnimMenu:SetDraggable( true ) + self.AnimMenu.OnClose = function( pan ) + self:OpenConfigMenu() + end + + local aw, ah = self.AnimMenu:GetSize() + + local sheet = vgui.Create( "DPropertySheet", self.AnimMenu ) + sheet:SetSize(aw*0.9, ah*0.8) + sheet:SetPos(aw*0.05, ah*0.05) + + ////////////////////////////////////////////////////////////////// Server Menu + local scroll = vgui.Create("DScrollPanel", sheet ) + scroll:SetSize(aw*0.9, ah*0.8) + local sw, sh = scroll:GetSize() + sheet:AddSheet( "Server Animations", scroll, "icon16/server.png" ) + + //placehold + local layout2 + local layout = vgui.Create("DListLayout", scroll) + layout:SetSize(sw, sh) + layout:SetPaintBackground(true) + layout:SetBackgroundColor(Color(100, 100, 100)) + + local temp_loaded = {} + local temp_count = 0 + local drop_check = WOS_DYNABASE_ENFORCECONTENT_CVAR:GetInt() >= 2 and WOS_DYNABASE_LOADORDERENFORCE_CVAR:GetBool() + if !drop_check then + layout:MakeDroppable( "WOS_DYNABASE_DROPPABLE" ) + layout.OnModified = function( pan, opnl ) + local opos = pan.DroppedPan.OrderPos + local npos = opos + local children = pan:GetChildren() + for id, child in ipairs( children ) do + if child != pan.DroppedPan then continue end + npos = id + break + end + + if WOS_DYNABASE_LOADORDERENFORCE_CVAR:GetBool() and ( npos <= temp_count or opos <= temp_count ) then + if IsValid( pan.DroppedPan.PreviousPanel ) and pan.DroppedPan.PreviousPanel != pan.DroppedPan then + pan.DroppedPan:MoveToAfter( pan.DroppedPan.PreviousPanel ) + else + pan.DroppedPan:MoveToBefore( pan.DroppedPan.ForwardPanel ) + end + return + end + + for id, child in ipairs( children ) do + child.OrderPos = id + child.PreviousPanel = children[id - 1] + child.ForwardPanel = children[id + 1] + end + end + end + + local lw, lh = layout:GetSize() + local fw, fh = lw, lh*0.2 + local iw, ih = fh*0.9, fh*0.9 + + local add_pad = lw*0.05 + + local order_checker = {} + local disabled_list = {} + if WOS_DYNABASE_LOADORDERENFORCE_CVAR:GetBool() and self.EnforceCount >= 1 and self.EnforcedOrder then + for cnt, name in ipairs( self.EnforcedOrder ) do + local data = self:GetSource( name ) + if not data then continue end + temp_loaded[ name ] = true + temp_count = temp_count + 1 + table.insert( order_checker, data ) + end + end + + if WOS_DYNABASE_ENFORCECONTENT_CVAR:GetInt() < 2 then + + if self.PlayerOrder and self.PlayerCount > 1 then + for _, dt in ipairs( self.PlayerOrder ) do + local name = dt.Name + local data = self:GetSource( name ) + if not data then continue end + disabled_list[ name ] = dt.Toggled + if temp_loaded[ name ] then continue end + temp_loaded[ name ] = true + table.insert( order_checker, data ) + end + end + + for name, data in pairs( self:GetAllSources() ) do + if temp_loaded[ name ] then continue end + table.insert( order_checker, data ) + end + + end + + for order, data in ipairs( order_checker ) do + if WOS_DYNABASE_ENFORCECONTENT_CVAR:GetInt() >= 1 and not data.ServerValid then continue end + local name = data.Name + local frame = vgui.Create( "DPanel", layout ) + local prev_child = layout:GetChild( order - 2 ) + if prev_child then + frame.PreviousPanel = prev_child + prev_child.ForwardPanel = frame + end + frame:SetSize( fw, fh ) + frame.Icon = data.IconOverwrite + frame.RegisterName = name + frame.PreventSort = order <= temp_count + frame.OrderPos = order + frame.Toggled = true + if disabled_list[ name ] != nil then + frame.Toggled = disabled_list[ name ] + end + + frame.OnDrop = function( pan ) + layout.DroppedPan = pan + return pan + end + frame.Paint = function( pan, ww, hh ) + draw.RoundedBox( 0, 0, 0, ww, hh, Color( 0, 0, 0, 155 ) ) + draw.RoundedBox( 0, ww*0.005, 0, add_pad, hh, Color( 0, 0, 0, 200 ) ) + + surface.SetDrawColor( Color( 255, 255, 255, 100 ) ) + surface.DrawOutlinedRect( 0, 0, ww, hh ) + + + if !drop_check and not pan.PreventSort then + surface.SetDrawColor( color_white ) + for i=0, 1 do + surface.DrawRect( ww*0.005 + add_pad*0.2 + i*(add_pad*0.4), hh*0.32, add_pad*0.2, add_pad*0.2 ) + end + + for i=0, 1 do + surface.DrawRect( ww*0.005 + add_pad*0.2 + i*(add_pad*0.4), hh*0.5, add_pad*0.2, add_pad*0.2 ) + end + + for i=0, 1 do + surface.DrawRect( ww*0.005 + add_pad*0.2 + i*(add_pad*0.4), hh*0.68, add_pad*0.2, add_pad*0.2 ) + end + end + + + local tx, th = draw.SimpleText( pan.RegisterName, "wOS.DynaBase.TitleFont", hh*0.1 + add_pad + iw + ww*0.01, hh*0.06, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + tx, th = draw.SimpleText( pan.AddonTitle or "Local File", "wOS.DynaBase.DescFont", hh*0.1 + add_pad + iw + ww*0.01, hh*0.06 + th + hh*0.03, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + + + surface.SetDrawColor( color_white ) + if not pan.Icon then + surface.DrawOutlinedRect( hh*0.05 + add_pad , hh*0.05, iw, ih ) + draw.SimpleText( "?", "wOS.DynaBase.QuestionFont", hh*0.05 + iw*0.5 + add_pad, hh*0.05 + ih*0.5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + else + surface.SetMaterial( pan.Icon ) + surface.DrawTexturedRect( hh*0.05 + add_pad, hh*0.05, iw, ih ) + end + if pan.Toggled then return end + draw.RoundedBox( 0, hh*0.05 + add_pad, hh*0.05, iw, ih, Color( 0, 0, 0, 215 ) ) + + end + + local url = vgui.Create( "DButton", frame ) + url:SetPos(fh*0.1 + fw*0.01 + iw + add_pad, fh - fh*0.15) + url:SetSize(fw*0.43, fh*0.125) + url:SetColor( color_white ) + url:SetFont( "wOS.DynaBase.URLFont" ) + url:SetText( "" ) + url.Paint = function( pan, ww, hh ) + local txt = pan.URL or "No Workshop URL" + draw.SimpleText( txt, "wOS.DynaBase.URLFont", 0, hh*0.5, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER ) + end + url:SetDisabled( true ) + url.DoClick = function( pan ) + if not pan.URL then return end + gui.OpenURL( pan.URL ) + end + + local icon = vgui.Create("DImageButton", frame) + icon:SetPos(lw*0.01 + add_pad, lw*0.01) + icon:SetSize(iw, ih) + + local ix, iy = icon:GetPos() + + local validated = vgui.Create("DImage", icon) + validated:SetPos( -8, -8 ) + validated:SetSize( 16, 16 ) + validated:NoClipping( true ) + validated.UpdateCast = function( pan ) + if !frame.Toggled then + pan:SetImage( "icon16/delete.png" ) + icon:SetTooltip( "This item is currently disabled" ) + else + if data.ServerValid then + pan:SetImage( "icon16/accept.png" ) + icon:SetTooltip( "This item is registered by the server and client" ) + else + pan:SetImage( "icon16/error.png" ) + icon:SetTooltip( "This item is only registered by the client and may cause unpredictable issues" ) + end + end + end + validated:UpdateCast() + + local dock_bar = vgui.Create( "DPanel", frame ) + dock_bar:SetPos(fh*0.1 + fw*0.01 + iw + add_pad , fh - fh*0.42) + dock_bar:SetSize(fw*0.7, fh*0.2) + dock_bar:SetPaintBackground( false ) + + local thetype = vgui.Create("DImageButton", dock_bar) + thetype:SetTall( fh*0.2 ) + thetype:SetWide( fh*0.2 ) + thetype:Dock( LEFT ) + + if data.Type == WOS_DYNABASE.REANIMATION then + thetype:SetImage( reanim_mat ) + thetype:SetTooltip( "This addon replaces existing animations" ) + else + thetype:SetImage( extend_mat ) + thetype:SetTooltip( "This addon includes new animations" ) + end + + local div = vgui.Create("DPanel", dock_bar) + div:SetWide( fh*0.22 ) + div:Dock( LEFT ) + div.Paint = function( pan, ww, hh ) + surface.SetDrawColor( color_white ) + surface.DrawRect( ww*0.45, 0, ww*0.1, hh ) + end + + local shared = vgui.Create("DImageButton", dock_bar) + shared:SetTall( fh*0.2 ) + shared:SetWide( fh*0.2 ) + shared:SetImage( shared_mat ) + shared:Dock( LEFT ) + if data.Shared then + shared:SetTooltip( "Common / All Gender Animations" ) + else + shared:SetColor(Color(135, 135, 135, 255)) + end + + local maled = vgui.Create("DImageButton", dock_bar) + maled:SetTall( fh*0.2 ) + maled:SetWide( fh*0.2 ) + maled:Dock( LEFT ) + maled:SetImage( male_mat ) + if data.Male then + maled:SetTooltip( "Male Animations" ) + else + maled:SetColor(Color(125, 125, 125, 125)) + end + + local femaled = vgui.Create("DImageButton", dock_bar) + femaled:SetTall( fh*0.2 ) + femaled:SetWide( fh*0.2 ) + femaled:Dock( LEFT ) + femaled:SetImage( female_mat ) + if data.Female then + femaled:SetTooltip( "Female Animations" ) + else + femaled:SetColor(Color(125, 125, 125, 125)) + end + + local zombied = vgui.Create("DImageButton", dock_bar) + zombied:SetTall( fh*0.2 ) + zombied:SetWide( fh*0.2 ) + zombied:Dock( LEFT ) + zombied:SetImage( zombie_mat ) + if data.Zombie then + zombied:SetTooltip( "Zombie Animations" ) + else + zombied:SetColor(Color(125, 125, 125, 125)) + end + + local mdl = data.Shared + if not mdl then mdl = data.Male end + if not mdl then mdl = data.Female end + if not mdl then mdl = data.Zombie end + + if mdl then + local function UpdateContent( ndat, mat ) + if ndat and ndat.id then + frame.AddonTitle = ndat.title + frame.Description = ndat.description + frame.ID = ndat.id + + url.URL = "https://steamcommunity.com/sharedfiles/filedetails/?id=" .. ndat.id + url:SetDisabled( false ) + end + frame.Icon = mat + end + local addon = GetContainingAddon( mdl ) + GetWorkshopInfo( addon, UpdateContent ) + end + + + local check1 = data.Type == WOS_DYNABASE.REANIMATION and WOS_DYNABASE_ENFORCEREANIMATE_CVAR:GetBool() + local check2 = data.Type == WOS_DYNABASE.EXTENSION and WOS_DYNABASE_ENFORCEEXTENDERS_CVAR:GetBool() + if !check1 and !check2 then + local div2 = vgui.Create("DPanel", dock_bar) + div2:SetWide( fh*0.22 ) + div2:Dock( LEFT ) + div2.Paint = function( pan, ww, hh ) + surface.SetDrawColor( color_white ) + surface.DrawRect( ww*0.45, 0, ww*0.1, hh ) + end + + local toggle = vgui.Create("DButton", dock_bar) + toggle:SetTall( fh*0.2 ) + if frame.Toggled then + toggle:SetText( "Unmount Addon" ) + else + toggle:SetText( "Mount Addon" ) + end + toggle:SetWide( fw*0.15 ) + toggle:Dock( LEFT ) + toggle.DoClick = function( pan ) + frame.Toggled = !frame.Toggled + if frame.Toggled then + pan:SetText( "Unmount Addon" ) + else + pan:SetText( "Mount Addon" ) + end + validated:UpdateCast() + end + end + + layout:Add( frame ) + end + + + ////////////////////////////////////////////////////////////////// Local Menu + + -- if WOS_DYNABASE_ENFORCECONTENT_CVAR:GetInt() > 0 then + -- local err = vgui.Create( "DPanel", sheet ) + -- err:Dock( FILL ) + -- err.Paint = function( pan, ww, hh ) + -- draw.SimpleText( "SERVER HAS DISABLED LOCAL ANIMATION CONTENT", "wOS.DynaBase.TitleFont", ww/2, hh*0.4, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + -- draw.SimpleText( "USE THE SERVER TAB FOR AVAILABILITY", "wOS.DynaBase.TitleFont", ww/2, hh*0.6, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + -- end + -- sheet:AddSheet( "User Animations", err, "icon16/user.png" ) + -- else + + local core = vgui.Create( "DPanel", sheet ) + core:SetSize(aw*0.9, ah*0.8) + sheet:AddSheet( "User Animations", core, "icon16/user.png" ) + + local create_butt = vgui.Create( "DButton", core ) + create_butt:SetSize( aw*0.9, ah*0.05 ) + create_butt:Dock( TOP ) + create_butt:SetText( "Create User Mount" ) + + local scroll2 = vgui.Create("DScrollPanel", core ) + scroll2:SetSize(aw*0.9, ah*0.69) + scroll2:Dock( TOP ) + + local function PopulateEntries() + if layout2 then layout2:Remove() end + + local sw, sh = aw*0.9, ah*0.73 + layout2 = vgui.Create("DListLayout", scroll2) + layout2:SetSize(sw, sh) + layout2:SetPaintBackground(true) + layout2:SetBackgroundColor(Color(100, 100, 100)) + layout2:MakeDroppable( "WOS_DYNABASE_DROPPABLE_LOCAL" ) + + local local_temp_loaded = {} + local lw, lh = layout2:GetSize() + local fw, fh = lw, lh*0.13 + local iw, ih = 0, 0 + + local add_pad = lw*0.05 + + local local_order_checker = {} + + local read = file.Read( "wos/dynabase/usermounts/preference.txt", "DATA" ) or "{}" + local local_order = util.JSONToTable( read ) + local enabled_check = {} + + if table.Count( local_order ) > 0 then + for id, dd in ipairs( local_order ) do + local name = dd.Name + local data = self:GetUserMount( name ) + if not data then continue end + local_temp_loaded[ name ] = true + table.insert( local_order_checker, data ) + if dd.Toggled then enabled_check[ name ] = true end + end + end + + for name, data in pairs( self:GetAllUserMounts() ) do + if local_temp_loaded[ name ] then continue end + table.insert( local_order_checker, data ) + end + + for order, data in ipairs( local_order_checker ) do + local name = data.Name + local frame = vgui.Create( "DPanel", layout2 ) + frame:SetSize( fw, fh ) + frame.RegisterName = name + frame.Toggled = enabled_check[ name ] + frame.Paint = function( pan, ww, hh ) + draw.RoundedBox( 0, 0, 0, ww, hh, Color( 0, 0, 0, 155 ) ) + draw.RoundedBox( 0, ww*0.005, 0, add_pad, hh, Color( 0, 0, 0, 200 ) ) + + surface.SetDrawColor( Color( 255, 255, 255, 100 ) ) + surface.DrawOutlinedRect( 0, 0, ww, hh ) + + surface.SetDrawColor( color_white ) + for i=0, 1 do + surface.DrawRect( ww*0.005 + add_pad*0.2 + i*(add_pad*0.4), hh*0.32, add_pad*0.2, add_pad*0.2 ) + end + + for i=0, 1 do + surface.DrawRect( ww*0.005 + add_pad*0.2 + i*(add_pad*0.4), hh*0.5, add_pad*0.2, add_pad*0.2 ) + end + + for i=0, 1 do + surface.DrawRect( ww*0.005 + add_pad*0.2 + i*(add_pad*0.4), hh*0.68, add_pad*0.2, add_pad*0.2 ) + end + + local tx, th = draw.SimpleText( pan.RegisterName, "wOS.DynaBase.TitleFont", hh*0.1 + add_pad + iw + ww*0.01, hh*0.06, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) + end + + local dock_bar = vgui.Create( "DPanel", frame ) + dock_bar:SetPos(fh*0.1 + fw*0.01 + iw + add_pad , fh - fh*0.42) + dock_bar:SetSize(fw*0.7, fh*0.35) + dock_bar:SetPaintBackground( false ) + + local validated = vgui.Create("DImageButton", dock_bar ) + validated:SetTall( fh*0.35 ) + validated:SetWide( fh*0.35 ) + validated:Dock( LEFT ) + validated.UpdateCast = function( pan ) + if !frame.Toggled then + pan:SetImage( "icon16/delete.png" ) + pan:SetTooltip( "This item is currently disabled" ) + else + pan:SetImage( "icon16/accept.png" ) + pan:SetTooltip( "This item is currently enabled" ) + pan:CheckModelValidity() + end + end + validated.CheckModelValidity = function( pan ) + for _, mdl in ipairs( pan.Models ) do + if file.Exists( mdl, "GAME" ) then continue end + pan:SetImage( "icon16/error.png" ) + pan:SetTooltip( "This item is missing models" ) + return + end + end + + local div = vgui.Create("DPanel", dock_bar) + div:SetWide( fh*0.22 ) + div:Dock( LEFT ) + div.Paint = function( pan, ww, hh ) + surface.SetDrawColor( color_white ) + surface.DrawRect( ww*0.45, 0, ww*0.1, hh ) + end + + local mdls = {} + + local shared = vgui.Create("DImageButton", dock_bar) + shared:SetTall( fh*0.35 ) + shared:SetWide( fh*0.35 ) + shared:SetImage( shared_mat ) + shared:Dock( LEFT ) + if data.Shared then + table.insert( mdls, data.Shared ) + shared:SetTooltip( "Common / All Gender Animations" ) + else + shared:SetColor(Color(135, 135, 135, 255)) + end + + local maled = vgui.Create("DImageButton", dock_bar) + maled:SetTall( fh*0.35 ) + maled:SetWide( fh*0.35 ) + maled:Dock( LEFT ) + maled:SetImage( male_mat ) + if data.Male then + table.insert( mdls, data.Male ) + maled:SetTooltip( "Male Animations" ) + else + maled:SetColor(Color(125, 125, 125, 125)) + end + + local femaled = vgui.Create("DImageButton", dock_bar) + femaled:SetTall( fh*0.35 ) + femaled:SetWide( fh*0.35 ) + femaled:Dock( LEFT ) + femaled:SetImage( female_mat ) + if data.Female then + table.insert( mdls, data.Female ) + femaled:SetTooltip( "Female Animations" ) + else + femaled:SetColor(Color(125, 125, 125, 125)) + end + + local zombied = vgui.Create("DImageButton", dock_bar) + zombied:SetTall( fh*0.35 ) + zombied:SetWide( fh*0.35 ) + zombied:Dock( LEFT ) + zombied:SetImage( zombie_mat ) + if data.Zombie then + table.insert( mdls, data.Zombie ) + zombied:SetTooltip( "Zombie Animations" ) + else + zombied:SetColor(Color(125, 125, 125, 125)) + end + + validated.Models = mdls + validated:UpdateCast() + + local div2 = vgui.Create("DPanel", dock_bar) + div2:SetWide( fh*0.22 ) + div2:Dock( LEFT ) + div2.Paint = function( pan, ww, hh ) + surface.SetDrawColor( color_white ) + surface.DrawRect( ww*0.45, 0, ww*0.1, hh ) + end + + local toggle = vgui.Create("DButton", dock_bar) + toggle:SetTall( fh*0.2 ) + if frame.Toggled then + toggle:SetText( "Unmount" ) + else + toggle:SetText( "Mount" ) + end + toggle:SetWide( fw*0.1 ) + toggle:Dock( LEFT ) + toggle.DoClick = function( pan ) + frame.Toggled = !frame.Toggled + if frame.Toggled then + pan:SetText( "Unmount" ) + else + pan:SetText( "Mount" ) + end + validated:UpdateCast() + end + + local div3 = vgui.Create("DPanel", dock_bar) + div3:SetWide( fh*0.22 ) + div3:Dock( LEFT ) + div3.Paint = function( pan, ww, hh ) + surface.SetDrawColor( color_white ) + surface.DrawRect( ww*0.45, 0, ww*0.1, hh ) + end + + local edit = vgui.Create("DButton", dock_bar) + edit:SetTall( fh*0.2 ) + edit:SetWide( fw*0.1 ) + edit:Dock( LEFT ) + edit:SetText( "Edit" ) + edit.DoClick = function( pan ) + if IsValid( pan.OverFrame ) then pan.OverFrame:Remove() pan.OverFrame = nil end + pan.OverFrame = vgui.Create( "DPanel" ) + pan.OverFrame:SetPaintBackground( true ) + pan.OverFrame:SetBackgroundColor( Color( 0, 0, 0, 185 ) ) + pan.OverFrame:SetSize( w, h ) + pan.OverFrame:MakePopup() + pan.OverFrame.RefreshList = function() + PopulateEntries() + end + + self:CreateLocalMenu( pan.OverFrame, data ) + end + + local div4 = vgui.Create("DPanel", dock_bar) + div4:SetWide( fh*0.22 ) + div4:Dock( LEFT ) + div4.Paint = function( pan, ww, hh ) + surface.SetDrawColor( color_white ) + surface.DrawRect( ww*0.45, 0, ww*0.1, hh ) + end + + local delete = vgui.Create("DButton", dock_bar) + delete:SetTall( fh*0.2 ) + delete:SetWide( fw*0.1 ) + delete:Dock( LEFT ) + delete:SetText( "Delete" ) + delete.DoClick = function( pan ) + self:DeleteUserMount( name ) + PopulateEntries() + end + + layout2:Add( frame ) + end + end + + create_butt.DoClick = function(pan) + if IsValid( pan.OverFrame ) then pan.OverFrame:Remove() pan.OverFrame = nil end + pan.OverFrame = vgui.Create( "DPanel" ) + pan.OverFrame:SetPaintBackground( true ) + pan.OverFrame:SetBackgroundColor( Color( 0, 0, 0, 185 ) ) + pan.OverFrame:SetSize( w, h ) + pan.OverFrame:MakePopup() + pan.OverFrame.RefreshList = function() + PopulateEntries() + end + + self:CreateLocalMenu( pan.OverFrame ) + end + + PopulateEntries() + + + ////////////////////////////////////////////////////////////////// Helper Menu + local scroll = vgui.Create("DScrollPanel", sheet ) + scroll:SetSize(aw*0.9, ah*0.8) + scroll.ReloadAddons = function() PopulateEntries() end + local sw, sh = scroll:GetSize() + sheet:AddSheet( "Helper Functions", scroll, "icon16/heart.png" ) + + local download_butt = vgui.Create( "DButton", scroll ) + download_butt:SetSize( aw*0.9, ah*0.05 ) + download_butt:Dock( TOP ) + download_butt:SetText( "Convert Server to User Mounts (Will overwrite mounts with the same name!)" ) + download_butt.DoClick = function(pan) + for name, data in pairs( self:GetAllSources() ) do + if data.Core then continue end + if data.PreventActivities then continue end + self:CreateUserMount( data ) + end + PopulateEntries() + chat.AddText( color_white, "[", Color( 0, 175, 255 ), "wOS-DynaBase", color_white, "] All registered server mounts have been added to your user mount list!" ) + end + hook.Call( "wOS.DynaBase.PopulateHelperFunctions", nil, scroll ) + + + local savebutt = vgui.Create("DButton", self.AnimMenu ) + savebutt:SetSize(aw*0.9, ah*0.04) + savebutt:SetPos( aw*0.05, ah*0.86 ) + savebutt:SetText( "Save Animation Settings" ) + sheet.OnActiveTabChanged = function( pan, old, new ) + local txt = new:GetText() + if txt:find( "User" ) then + savebutt:SetText( "Save User Settings" ) + savebutt:Show() + elseif txt:find( "Server" ) then + savebutt:SetText( "Save Server Settings" ) + savebutt:Show() + else + savebutt:Hide() + end + end + savebutt.DoClick = function( pan ) + if sheet:GetActiveTab():GetText():find( "User" ) then + if not layout2 then return end + local order = {} + for _, child in ipairs( layout2:GetChildren() ) do + table.insert( order, { Name = child.RegisterName, Toggled = child.Toggled } ) + end + local pref = util.TableToJSON( order ) + file.Write( "wos/dynabase/usermounts/preference.txt", pref ) + self:ReloadLocalAnimations( order ) + else + self.PlayerOrder = {} + self.PlayerCount = 0 + for _, child in ipairs( layout:GetChildren() ) do + table.insert( self.PlayerOrder, { Name = child.RegisterName, Toggled = child.Toggled } ) + self.PlayerCount = self.PlayerCount + 1 + end + self:ProcessPlayerOrder() + end + self:ReloadAnimations() + end + + local nobutt = vgui.Create("DButton", self.AnimMenu ) + nobutt:SetSize(aw*0.9, ah*0.04) + nobutt:SetPos( aw*0.05, ah*0.91 ) + nobutt:SetText( "Cancel" ) + nobutt.DoClick = function() self:OpenConfigMenu() end + //toggle:SetText( "Cancel" ) +end diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/cl_core.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_core.lua new file mode 100644 index 0000000..8369aac --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_core.lua @@ -0,0 +1,87 @@ +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} +wOS.DynaBase.UserMounts = {} + +hook.Add( "RenderScene", "wOS.DynaBase.PreventDataAccess", function() + if wOS.DynaBase.ResumeRendering and wOS.DynaBase.ResumeRendering >= CurTime() then return true end + if not wOS.DynaBase.ReloadModelBool then return end + wOS.DynaBase.DataCachePass = wOS.DynaBase.DataCachePass + 1 + return true +end ) + +hook.Add( "PostRender", "wOS.DynaBase.PreventDataAccess", function() + if not wOS.DynaBase.ReloadModelBool then return end + wOS.DynaBase.ReloadModelBool = false + local val = wOS.DynaBase.DataCachePass + wOS.DynaBase.DataCachePass = 0 + if not val or val < WOS_DYNABASE.MAXCACHE then + chat.AddText( Color( 255, 0, 0 ), "[wOS-Dynabase] Can not apply animation selection due to conflicting addons. Please let us know on the Workshop Page or Discord!" ) + return + end + RunConsoleCommand( "r_flushlod" ) + hook.Call( "PostLoadAnimations" ) + wOS.DynaBase.ResumeRendering = CurTime() + 0.3 + if not wOS.DynaBase.FIRST_TIME_LOADED then wOS.DynaBase.FIRST_TIME_LOADED = true return end + chat.AddText( Color( 0, 255, 0 ), "[wOS-Dynabase] Successfully applied animation selection to models!" ) +end ) + +concommand.Add( "wos_dynabase_reloadmodels", function() + wOS.DynaBase.LIVE_RELOAD = true + wOS.DynaBase:ReloadAnimations() + wOS.DynaBase.LIVE_RELOAD = false +end ) + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Credits to Starlight (Oliver) for the code snippet and also giving it to me every time +// I asked, for the last 3 years +local function FindPotentialBases() + + for _ ,addon in pairs( engine.GetAddons() ) do + local modelFound = file.Find("models/m_anm.mdl", addon.title) + if #modelFound != 0 then + MsgC( Color( 255, 0, 255 ), "\n\tAddon: " .. addon.title .. "\n" ) + MsgC( Color( 255, 0, 255 ), "\tLink: https://steamcommunity.com/sharedfiles/filedetails/?id=" .. addon.wsid .. "\n" ) + return true + end + end + + return false +end +/////////////////////////////////////////////////////////////////////////////////////////////////// + +concommand.Add( "wos_dynabase_help", function( ply, cmd, args ) + MsgC( Color( 255, 255, 255 ), "------------------ ", Color( 133, 173, 219 ), "wiltOS HELP PRINT", Color( 255, 255, 255 ), " ----------------------\n" ) + MsgC( Color( 255, 255, 255 ), "Installed Animation Base\n" ) + timer.Simple( 0.01, function() + local found = FindPotentialBases() + if not found then + MsgC( Color( 255, 0, 255 ), "\n\tNo Animation Bases found! You must get the Dynamic Animation Manager\n" ) + MsgC( Color( 255, 0, 255 ), "\tWorkshop Link: https://steamcommunity.com/sharedfiles/filedetails/?id=2916561591\n" ) + end + MsgC( color_white, "\nPlease ensure that the only addon above is ", Color( 0, 255, 0 ), "'[wOS] DynaBase - The Dynamic Animation Manager'\n") + MsgC( color_white, "If there are more addons, unsubscribe from them and install the Dynamic Animation Manager.\n") + print("\n") + + local seq = LocalPlayer():LookupSequence( "_dynamic_wiltos_enabled_" ) + local resp = ( seq >= 0 and Color( 0, 255, 0 ) ) or Color( 255, 0, 0 ) + MsgC( color_white, "Sequence Check: ", resp, "\t", seq, "\n" ) + MsgC( color_white, "If the above sequence check is ", Color( 255, 0, 0 ), -1, color_white, " and the addon above points to the correct location,\nensure your model is a ", Color( 0, 255, 0 ), "PLAYER MODEL", color_white, " and not an ", Color( 255, 0, 0 ), "NPC MODEL\n" ) + MsgC( color_white, "Run this commmand again as a default GMod player model. If it still prints ", Color( 255, 0, 0 ), -1, color_white, " your Animation Base may be outdated\n" ) + + print( "\n" ) + MsgC( color_white, "Make can find the Dynamic Animation Manager workshop page here: https://steamcommunity.com/sharedfiles/filedetails/?id=2916561591\n") + MsgC( Color( 255, 255, 255 ), "-----------------------------------------------------------\n" ) + end ) +end ) + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +concommand.Add( "wos_dynabase_openconfig", function( ply, cmd, args ) + wOS.DynaBase:OpenConfigMenu() +end ) + +list.Add( "DesktopWindows", { + icon = "wos/dynabase/widget.png", + title = "DynaBase Menu", + init = function() wOS.DynaBase:OpenConfigMenu() end, +}) diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/cl_local_copy.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_local_copy.lua new file mode 100644 index 0000000..a01a57a --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_local_copy.lua @@ -0,0 +1,239 @@ +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} + +function wOS.DynaBase:CreateUserMount( data ) + if not data then return end + if not data.Name then return end + self.UserMounts[ data.Name ] = data + + local mounts = file.Read( "wos/dynabase/usermounts/mounts.txt", "DATA" ) or "{}" + local local_mounts = util.JSONToTable( mounts ) + + local_mounts[ data.Name ] = data + local_mounts = util.TableToJSON( local_mounts ) + file.Write( "wos/dynabase/usermounts/mounts.txt", local_mounts ) +end + +function wOS.DynaBase:DeleteUserMount( name ) + if not name then return end + if not self.UserMounts[ name ] then return end + self.UserMounts[ name ] = nil + + local mounts = file.Read( "wos/dynabase/usermounts/mounts.txt", "DATA" ) or "{}" + local local_mounts = util.JSONToTable( mounts ) + local_mounts[ name ] = nil + + local_mounts = util.TableToJSON( local_mounts ) + file.Write( "wos/dynabase/usermounts/mounts.txt", local_mounts ) +end + +function wOS.DynaBase:GetAllUserMounts() + return self.UserMounts +end + +function wOS.DynaBase:GetUserMount( name ) + return self.UserMounts[ name ] +end + +function wOS.DynaBase:CreateLocalMenu( parent, old_data ) + old_data = old_data or {} + local w, h = parent:GetSize() + + local frame = vgui.Create( "DFrame", parent ) + frame:SetSize( w*0.5, h*0.4 ) + frame:Center() + frame:MakePopup() + frame:SetTitle( "Create User Mount" ) + frame.OnClose = function() + parent:Remove() + end + frame.OldThink = frame.Think + frame.Think = function( pan ) + pan:OldThink() + if parent:HasFocus() then pan:MoveToFront() end + if not self.AnimMenu then parent:Remove() end + end + + local fw, fh = frame:GetSize() + + local addon_list = vgui.Create( "DListView", frame ) + addon_list:SetWide( fw*0.3 ) + addon_list:Dock( LEFT ) + addon_list:SetMultiSelect( false ) + addon_list:AddColumn( "Addon Name" ) + + local row = addon_list:AddLine( "Base Game" ) + row.title = "GAME" + + for _, addon in ipairs( engine.GetAddons() ) do + if not addon.downloaded then continue end + if not addon.mounted then continue end + local row = addon_list:AddLine( addon.title ) + end + + local browser = vgui.Create( "DFileBrowser", frame ) + browser:SetTall( fh ) + browser:SetWide( fw*0.4 ) + browser:Dock( LEFT ) + browser:SetPath( "GAME" ) + browser:SetBaseFolder( "models" ) + browser:SetFileTypes( "*.mdl" ) + browser:SetOpen( true ) + + addon_list.OnRowSelected = function( pan, i, row ) + browser:SetPath( row.title or row:GetColumnText(1) ) + browser:SetBaseFolder( "models" ) + browser:SetOpen( true ) + end + + local div = vgui.Create( "DPanel", frame ) + div:SetBackgroundColor( Color( 0, 0, 0 ) ) + div:SetWide( fw*0.01 ) + div:Dock( LEFT ) + + local creation_frame = vgui.Create( "DPanel", frame ) + creation_frame:SetWide( fw*0.29 ) + creation_frame:SetBackgroundColor( Color( 130, 130, 130 ) ) + creation_frame:Dock( LEFT ) + creation_frame:DockPadding( fw*0.005, 0, fw*0.01, 0 ) + + local cw, ch = creation_frame:GetSize() + + local name_lab = vgui.Create( "DLabel", creation_frame ) + name_lab:SetTextColor( color_white ) + name_lab:SetFont( "wOS.DynaBase.DescFont" ) + name_lab:SetText( "Name of User Mount" ) + name_lab:Dock( TOP ) + name_lab.DefaultCol = name_lab:GetColor() + + local name_ent = vgui.Create( "DTextEntry", creation_frame ) + name_ent:Dock( TOP ) + name_ent:SetFont( "DermaDefaultBold" ) + name_ent:SetText( old_data.Name or "" ) + name_ent:SetPlaceholderText( "Enter name of mount" ) + name_ent:DockMargin( fw*0.01, 0, 0, 0 ) + name_ent.OnGetFocus = function(pan) + name_lab:SetTextColor( name_lab.DefaultCol ) + end + + local name = vgui.Create( "DLabel", creation_frame ) + name:SetTextColor( color_white ) + name:SetFont( "wOS.DynaBase.DescFont" ) + name:SetText( "Shared Mounting Model" ) + name:Dock( TOP ) + name:DockMargin( 0, fh*0.02, 0, 0 ) + + local share_ent = vgui.Create( "DTextEntry", creation_frame ) + share_ent:Dock( TOP ) + share_ent:SetFont( "DermaDefaultBold" ) + share_ent:SetText( old_data.Shared or "" ) + share_ent:SetPlaceholderText( "Enter path or right click on model in browser" ) + share_ent:DockMargin( fw*0.01, 0, 0, 0 ) + + local name = vgui.Create( "DLabel", creation_frame ) + name:SetTextColor( color_white ) + name:SetFont( "wOS.DynaBase.DescFont" ) + name:SetText( "Male Mounting Model" ) + name:Dock( TOP ) + name:DockMargin( 0, fh*0.02, 0, 0 ) + + local male_ent = vgui.Create( "DTextEntry", creation_frame ) + male_ent:Dock( TOP ) + male_ent:SetText( old_data.Male or "" ) + male_ent:SetFont( "DermaDefaultBold" ) + male_ent:SetPlaceholderText( "Enter path or right click on model in browser" ) + male_ent:DockMargin( fw*0.01, 0, 0, 0 ) + + local name = vgui.Create( "DLabel", creation_frame ) + name:SetTextColor( color_white ) + name:SetFont( "wOS.DynaBase.DescFont" ) + name:SetText( "Female Mounting Model" ) + name:Dock( TOP ) + name:DockMargin( 0, fh*0.02, 0, 0 ) + + local female_ent = vgui.Create( "DTextEntry", creation_frame ) + female_ent:Dock( TOP ) + female_ent:SetText( old_data.Female or "" ) + female_ent:SetFont( "DermaDefaultBold" ) + female_ent:SetPlaceholderText( "Enter path or right click on model in browser" ) + female_ent:DockMargin( fw*0.01, 0, 0, 0 ) + + local name = vgui.Create( "DLabel", creation_frame ) + name:SetTextColor( color_white ) + name:SetFont( "wOS.DynaBase.DescFont" ) + name:SetText( "Zombie Mounting Model" ) + name:Dock( TOP ) + name:DockMargin( 0, fh*0.02, 0, 0 ) + + local zombie_ent = vgui.Create( "DTextEntry", creation_frame ) + zombie_ent:Dock( TOP ) + zombie_ent:SetText( old_data.Zombie or "" ) + zombie_ent:SetFont( "DermaDefaultBold" ) + zombie_ent:SetPlaceholderText( "Enter path or right click on model in browser" ) + zombie_ent:DockMargin( fw*0.01, 0, 0, 0 ) + + local cancel = vgui.Create("DButton", creation_frame ) + cancel:SetTall( fh*0.05 ) + cancel:Dock( BOTTOM ) + cancel:SetText( "Cancel" ) + cancel.DoClick = function() frame:Close() end + + local save = vgui.Create("DButton", creation_frame ) + save:SetTall( fh*0.05 ) + save:Dock( BOTTOM ) + save:SetText( "Save User Mount" ) + save.DoClick = function() + local name = name_ent:GetText() + if #name < 1 then + name_lab:SetTextColor( Color( 255, 0, 0 ) ) + surface.PlaySound( "buttons/button10.wav" ) + return + end + + local tbl = { + Name = name, + Shared = ( #share_ent:GetText() > 1 and share_ent:GetText() ) or nil, + Male = ( #male_ent:GetText() > 1 and male_ent:GetText() ) or nil, + Female = ( #female_ent:GetText() > 1 and female_ent:GetText() ) or nil, + Zombie = ( #zombie_ent:GetText() > 1 and zombie_ent:GetText() ) or nil, + } + if old_data.Name then + self:DeleteUserMount( old_data.Name ) + end + self:CreateUserMount( tbl ) + parent:RefreshList() + frame:Close() + end + + function browser:OnRightClick( path, pnl ) -- Called when a file is clicked + local menu = DermaMenu() + menu:AddOption( "Set as Shared Mount", function() + share_ent:SetText( path ) + end ):SetIcon( "icon16/group.png" ) + + menu:AddOption( "Set as Male Mount", function() + male_ent:SetText( path ) + end ):SetIcon( "icon16/male.png" ) + + menu:AddOption( "Set as Female Mount", function() + female_ent:SetText( path ) + end ):SetIcon( "icon16/female.png" ) + + menu:AddOption( "Set as Zombie Mount", function() + zombie_ent:SetText( path ) + end ):SetIcon( "icon16/bug.png" ) + + menu:Open() + end + +end + +hook.Add( "Initialize", "wOS.DynaBase.LoadUserMounts", function() + local mounts = file.Read( "wos/dynabase/usermounts/mounts.txt", "DATA" ) or "{}" + local local_mounts = util.JSONToTable( mounts ) + + for mount, data in pairs( local_mounts ) do + wOS.DynaBase.UserMounts[ mount ] = data + end + +end ) diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/cl_net.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_net.lua new file mode 100644 index 0000000..d9bdfe6 --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/cl_net.lua @@ -0,0 +1,31 @@ +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} + +net.Receive( "wOS.DynaBase.SendAllRegisters", function() + wOS.DynaBase.EnforceCount = net.ReadUInt( 32 ) + for i=1, wOS.DynaBase.EnforceCount do + local name = net.ReadString() + local tbl = wOS.DynaBase:GetSource( name ) + if not tbl then wOS.DynaBase:RegisterSource({Name = name, ServerValid = true}) return end + tbl.ServerValid = true + end +end ) + +net.Receive( "wOS.DynaBase.SendRegister", function() + local name = net.ReadString() + local tbl = wOS.DynaBase:GetSource( name ) + if not tbl then wOS.DynaBase:RegisterSource({Name = name, ServerValid = true}) return end + tbl.ServerValid = true + wOS.DynaBase.EnforceCount = wOS.DynaBase.EnforceCount + 1 +end ) + +net.Receive( "wOS.DynaBase.ForceMountCallback", function() + local tbl = cvars.GetConVarCallbacks( "wos_dynabase_mountorder" ) or {} + + local oldval = net.ReadString() + local newval = net.ReadString() + + for _, func in ipairs( tbl ) do + func( "wos_dynabase_mountorder", oldval, newval ) + end +end ) diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/sh_core.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/sh_core.lua new file mode 100644 index 0000000..b0abd67 --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/sh_core.lua @@ -0,0 +1,239 @@ +// That's right, x64 won't work with .dat in this trick. Special thanks to 8Z +WOS_DYNABASE_FILE_EXTENSION = ".dat" +-- if jit.arch == "x64" then +-- WOS_DYNABASE_FILE_EXTENSION = ".txt" +-- end + +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} +wOS.DynaBase.DataCachePass = 0 +wOS.DynaBase.InitCompleted = false + +wOS.DynaBase.Registers = {} +wOS.DynaBase.RegisterCount = 0 + +wOS.DynaBase.EnforcedOrder = {} +wOS.DynaBase.EnforceCount = 1 + +wOS.DynaBase.PlayerOrder = {} +wOS.DynaBase.PlayerCount = 1 + +WOS_DYNABASE = WOS_DYNABASE or {} +WOS_DYNABASE.SHARED = 1 +WOS_DYNABASE.MALE = 2 +WOS_DYNABASE.FEMALE = 3 +WOS_DYNABASE.ZOMBIE = 4 +WOS_DYNABASE.MAXCACHE = 1 + +WOS_DYNABASE.EXTENSION = 1 +WOS_DYNABASE.REANIMATION = 2 + +wOS.DynaBase.DefaultTable = {} +wOS.DynaBase.DefaultTable.Male = { "data/wos/dynabase/local_male" .. WOS_DYNABASE_FILE_EXTENSION, "models/m_wos.mdl" } +wOS.DynaBase.DefaultTable.Female = { "data/wos/dynabase/local_female" .. WOS_DYNABASE_FILE_EXTENSION, "models/f_wos.mdl" } +wOS.DynaBase.DefaultTable.Zombie = { "data/wos/dynabase/local_zombie" .. WOS_DYNABASE_FILE_EXTENSION, "models/z_wos.mdl" } +wOS.DynaBase.DefaultTable.Shared = { "data/wos/dynabase/local_shared" .. WOS_DYNABASE_FILE_EXTENSION } + +wOS.DynaBase.FilteredLoadOrder = {} +wOS.DynaBase.FilteredPlayerOrder = {} + +wOS.DynaBase.FilteredBlacklist = {} +wOS.DynaBase.FilteredPlayerBlacklist = {} + +local flags = {FCVAR_REPLICATED, FCVAR_PROTECTED, FCVAR_ARCHIVE} +WOS_DYNABASE_ENFORCECONTENT_CVAR = CreateConVar( "wos_dynabase_restrict_client_content", "0", flags, "Prevents usage of custom content (animations) that are not registered on the server.\n \t0 = No Restrictions\n \t1 = Registered Addons\n \t2 = Registered and Mounted Addons" ) +WOS_DYNABASE_ENFORCEREANIMATE_CVAR = CreateConVar( "wos_dynabase_restrict_client_reanimation", "1", flags, "Prevents clients from being able to customize what re-animations are locally enabled" ) +WOS_DYNABASE_ENFORCEEXTENDERS_CVAR = CreateConVar( "wos_dynabase_restrict_client_extension", "1", flags, "Prevents clients from being able to customize what animation extensions are locally enabled" ) +WOS_DYNABASE_LOADORDERENFORCE_CVAR = CreateConVar( "wos_dynabase_restrict_server_loadorder", "1", flags, "Enforces the order of animations as registered by the order of wos_dynabase_mountaddon command executions. Addons not specified will be mounted after the initial list" ) +WOS_DYNABASE_MOUNTORDERLIST_CVAR = CreateConVar( "wos_dynabase_mountorder", "", flags, "Enforces the order of animations as comma seperated names of registered addons. Addons not specified will be mounted after the initial list unless content enforcement is set to 2. Any addons not installed are simply skipped" ) +WOS_DYNABASE_MOUNTBLACKLIST_CVAR = CreateConVar( "wos_dynabase_blacklist", "", flags, "A comma seperated list of the names of blacklisted registered sources" ) +WOS_DYNABASE_SHOULDHOTLOAD_CVAR = CreateConVar( "wos_dynabase_live_reload", "1", {FCVAR_ARCHIVE}, "Should animations reload in-game when changes are made automatically? If you are experiencing crashing issues, disable this" ) + +local function InitializeBase() + if not wOS.DynaBase.InitCompleted then + hook.Call( "InitLoadAnimations" ) + end + wOS.DynaBase:ProcessLoadOrder() + wOS.DynaBase:ReloadAnimations() + + if SERVER then + if wOS.DynaBase.InitCompleted and not WOS_DYNABASE_SHOULDHOTLOAD_CVAR:GetBool() then return end + RunConsoleCommand( "r_flushlod" ) + end + + wOS.DynaBase.InitCompleted = true +end + +local function HandleMountOrderList( args ) + if not args or #args < 1 then + wOS.DynaBase.EnforceCount = 0 + wOS.DynaBase.EnforcedOrder = nil + return + end + + wOS.DynaBase.EnforcedOrder = {} + wOS.DynaBase.EnforceCount = 0 + + local tbl = string.Explode( ",", args ) + for _, arg in ipairs( tbl ) do + if table.HasValue( wOS.DynaBase.EnforcedOrder, arg ) then continue end + wOS.DynaBase.EnforceCount = wOS.DynaBase.EnforceCount + 1 + wOS.DynaBase.EnforcedOrder[ wOS.DynaBase.EnforceCount ] = arg + end +end + +cvars.AddChangeCallback("wos_dynabase_mountorder", function(_, oldargs, args) + + if SERVER then + net.Start( "wOS.DynaBase.ForceMountCallback" ) + net.WriteString( oldargs ) + net.WriteString( args ) + net.Broadcast() + end + + HandleMountOrderList( args ) + + if not WOS_DYNABASE_LOADORDERENFORCE_CVAR:GetBool() then return end + InitializeBase() + wOS.DynaBase.InitCompleted = true +end) + +hook.Add( "CreateTeams", "wOS.DynaBase.InitLoadAnimations", function() + if wOS.DynaBase.InitCompleted then return end + if WOS_DYNABASE_LOADORDERENFORCE_CVAR:GetBool() then + HandleMountOrderList( WOS_DYNABASE_MOUNTORDERLIST_CVAR:GetString() ) + end + InitializeBase() +end ) + +function wOS.DynaBase:RegisterSource( data ) + if not data then return end + if not data.Name then return end + self.Registers[ data.Name ] = data + self.RegisterCount = self.RegisterCount + 1 + + print( "[wOS-DynaBase] Registered new animation source: " .. data.Name ) + + if data.PreventActivities then + self:FixActivities( data ) + end + + if CLIENT then + if data.IconOverwrite then + self.Registers[ data.Name ].IconOverwrite = Material( data.IconOverwrite, "unlitgeneric" ) + end + return + end + + if not self.InitCompleted then return end + net.Start( "wOS.DynaBase.SendRegister" ) + net.WriteString( data.Name ) + net.Broadcast() +end + +function wOS.DynaBase:ProcessLoadOrder() + if self.EnforceCount < 1 then self.FilteredLoadOrder = {} return end + + // Initialize it here so it can fail later + self.FilteredLoadOrder[ WOS_DYNABASE.MALE ] = {} + self.FilteredLoadOrder[ WOS_DYNABASE.FEMALE ] = {} + self.FilteredLoadOrder[ WOS_DYNABASE.ZOMBIE ] = {} + self.FilteredLoadOrder[ WOS_DYNABASE.SHARED ] = {} + + for _, name in ipairs( self.EnforcedOrder ) do + local data = self:GetSource( name ) + if not data then continue end + + if data.Shared then + table.insert( self.FilteredLoadOrder[ WOS_DYNABASE.SHARED ], data.Shared ) + end + + if data.Male then + table.insert( self.FilteredLoadOrder[ WOS_DYNABASE.MALE ], data.Male ) + end + + if data.Female then + table.insert( self.FilteredLoadOrder[ WOS_DYNABASE.FEMALE ], data.Female ) + end + + if data.Zombie then + table.insert( self.FilteredLoadOrder[ WOS_DYNABASE.ZOMBIE ], data.Zombie ) + end + end +end + +function wOS.DynaBase:ProcessPlayerOrder() + if self.PlayerCount < 1 then + self.FilteredPlayerBlacklist = {} + self.FilteredPlayerOrder = {} + return + end + + // Initialize it here so it can fail later + self.FilteredPlayerOrder[ WOS_DYNABASE.MALE ] = {} + self.FilteredPlayerOrder[ WOS_DYNABASE.FEMALE ] = {} + self.FilteredPlayerOrder[ WOS_DYNABASE.ZOMBIE ] = {} + self.FilteredPlayerOrder[ WOS_DYNABASE.SHARED ] = {} + + self.FilteredPlayerBlacklist[ WOS_DYNABASE.MALE ] = {} + self.FilteredPlayerBlacklist[ WOS_DYNABASE.FEMALE ] = {} + self.FilteredPlayerBlacklist[ WOS_DYNABASE.ZOMBIE ] = {} + self.FilteredPlayerBlacklist[ WOS_DYNABASE.SHARED ] = {} + + for _, dt in ipairs( self.PlayerOrder ) do + local name = dt.Name + local data = self:GetSource( name ) + if not data then continue end + + if data.Shared then + table.insert( ( !dt.Toggled and self.FilteredPlayerBlacklist[ WOS_DYNABASE.SHARED ] ) or self.FilteredPlayerOrder[ WOS_DYNABASE.SHARED ], data.Shared ) + end + + if data.Male then + table.insert( ( !dt.Toggled and self.FilteredPlayerBlacklist[ WOS_DYNABASE.MALE ] ) or self.FilteredPlayerOrder[ WOS_DYNABASE.MALE ], data.Male ) + end + + if data.Female then + table.insert( ( !dt.Toggled and self.FilteredPlayerBlacklist[ WOS_DYNABASE.FEMALE ] ) or self.FilteredPlayerOrder[ WOS_DYNABASE.FEMALE ], data.Female ) + end + + if data.Zombie then + table.insert( ( !dt.Toggled and self.FilteredPlayerBlacklist[ WOS_DYNABASE.ZOMBIE ] ) or self.FilteredPlayerOrder[ WOS_DYNABASE.ZOMBIE ], data.Zombie ) + end + end +end + +function wOS.DynaBase:GetSourceCount() + return self.RegisterCount +end + +function wOS.DynaBase:GetAllSources() + return self.Registers +end + +function wOS.DynaBase:GetSource( name ) + return self.Registers[ name ] +end + + +// Need to register the base stuff LMAO +wOS.DynaBase:RegisterSource({ + Name = "Base Animations", + Type = WOS_DYNABASE.REANIMATION, + IconOverwrite = "wos/dynabase/gmod.png", + Core = true, + Male = "models/m_wos.mdl", + Female = "models/f_wos.mdl", + Zombie = "models/z_wos.mdl", +}) + +wOS.DynaBase:RegisterSource({ + Name = "Local Player Animations", + Type = WOS_DYNABASE.REANIMATION, + IconOverwrite = "wos/dynabase/local.png", + Core = true, + Male = "data/wos/dynabase/local_male" .. WOS_DYNABASE_FILE_EXTENSION, + Female = "data/wos/dynabase/local_female" .. WOS_DYNABASE_FILE_EXTENSION, + Zombie = "data/wos/dynabase/local_zombie" .. WOS_DYNABASE_FILE_EXTENSION, + Shared = "data/wos/dynabase/local_shared" .. WOS_DYNABASE_FILE_EXTENSION, +}) diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/sh_model_operations.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/sh_model_operations.lua new file mode 100644 index 0000000..abf117b --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/sh_model_operations.lua @@ -0,0 +1,107 @@ +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} +wOS.DynaBase.PreservedModels = wOS.DynaBase.PreservedModels or {} + +local function FIX_MODEL_ACTIVITIES( path ) + if not path then return end + + local new_path = string.Replace(path, "models/", "wos/dynabase/fixedmodels/") + new_path = string.Replace( new_path, ".mdl", WOS_DYNABASE_FILE_EXTENSION ) + if file.Exists( new_path, "DATA" ) and not wOS.DynaBase.ReloadFixedModels then return "data/" .. new_path end + + local mdl_file = file.Open( path, "r", "GAME" ) + if not mdl_file then return end + + local str = "" + local sanitary_str = "" + for i=0, mdl_file:Size() do + local byte = mdl_file:ReadByte() + if not byte then continue end + str = str .. string.char( byte ) + if byte == 0 then + sanitary_str = sanitary_str .. string.char( 35 ) + else + sanitary_str = sanitary_str .. string.char( byte ) + end + end + local _start, _end = string.find( sanitary_str, "ACT_" ) + while _start do + local act_name = "WDB_" + for i=0, 3 do + str = string.SetChar( str, _start + i, act_name[i + 1] ) + end + _start, _end = string.find( sanitary_str, "ACT_", _end ) + end + + + local dt = string.Explode("/", new_path) + dt = dt[#dt] + + local file_path = string.Left( path, #path - #dt - 1 ) + + file.CreateDir( file_path ) + file.Write( new_path, str ) + + return "data/" .. new_path +end + +function wOS.DynaBase:FixActivities( data ) + if not data then return end + + if data.Shared and ( not self.PreservedModels[ data.Shared ] or wOS.DynaBase.ReloadFixedModels ) then + local path = FIX_MODEL_ACTIVITIES( data.Shared ) + if path then + self.PreservedModels[ data.Shared ] = path + end + end + + if data.Male and ( not self.PreservedModels[ data.Male ] or wOS.DynaBase.ReloadFixedModels ) then + local path = FIX_MODEL_ACTIVITIES( data.Male ) + if path then + self.PreservedModels[ data.Male ] = path + end + end + + if data.Female and ( not self.PreservedModels[ data.Female ] or wOS.DynaBase.ReloadFixedModels ) then + local path = FIX_MODEL_ACTIVITIES( data.Female ) + if path then + self.PreservedModels[ data.Female ] = path + end + end + + if data.Zombie and ( not self.PreservedModels[ data.Zombie ] or wOS.DynaBase.ReloadFixedModels ) then + local path = FIX_MODEL_ACTIVITIES( data.Zombie ) + if path then + self.PreservedModels[ data.Zombie ] = path + end + end + +end + +concommand.Add( "wos_dynabase_debug_reloadfixedmodels", function() + wOS.DynaBase.ReloadFixedModels = true + for name, data in pairs( wOS.DynaBase:GetAllSources() ) do + if not data.PreventActivities then continue end + wOS.DynaBase:FixActivities( data ) + end + wOS.DynaBase.ReloadFixedModels = false +end ) + +file.CreateDir("wos/dynabase/fixedmodels") + + +local function FixConflictingBases() + for _, addon in ipairs( engine.GetAddons() ) do + if addon.wsid == "2916561591" then + game.MountGMA( addon.file ) + RunConsoleCommand( "r_flushlod" ) + break + end + end +end +hook.Add( "InitLoadAnimations", "wOS.DynaBase.FixConflictingBases", FixConflictingBases ) + +concommand.Add( "wos_dynabase_fixconflicts", function() + FixConflictingBases() + print( "[wOS-DynaBase] Conflicting bases have been unloaded for this session. Double check by running the wos_dynabase_help command!\n\nNote: The addon may still be mounted when you restart your game.") +end ) diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/sh_mounting.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/sh_mounting.lua new file mode 100644 index 0000000..20b2bb6 --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/sh_mounting.lua @@ -0,0 +1,384 @@ +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} +local added = {} + +////////////////////////////////////// BASELINE STUFF ////////////////////////////////////// +local model_table = {} +model_table[ WOS_DYNABASE.SHARED ] = "models/player/wiltos/anim_dynamic_pointer.mdl" +model_table[ WOS_DYNABASE.MALE ] = "models/player/wiltos/anim_dynamic_maleptr.mdl" +model_table[ WOS_DYNABASE.FEMALE ] = "models/player/wiltos/anim_dynamic_femaler.mdl" +model_table[ WOS_DYNABASE.ZOMBIE ] = "models/player/wiltos/anim_dynamic_zombier.mdl" + +local model_ext = {} +model_ext[ WOS_DYNABASE.MALE ] = "_male" +model_ext[ WOS_DYNABASE.FEMALE ] = "_female" +model_ext[ WOS_DYNABASE.ZOMBIE ] = "_zombie" + +local INCLUDE_MODEL_START = 737 +local INCLUDE_MODEL_END_SHARED = 806 + +// "Hard code the math idiot" says the guy who doesn't realize I'm making it easier on myself to change their names or expand in the future +// I also can't count letters, only numbers?? GENIUS. NICE ONE +local INCLUDE_MODEL_END_MALE = INCLUDE_MODEL_END_SHARED + ( #"_male" )*2 +local INCLUDE_MODEL_END_FEMALE = INCLUDE_MODEL_END_SHARED + ( #"_female" )*2 +local INCLUDE_MODEL_END_ZOMBIE = INCLUDE_MODEL_END_SHARED + ( #"_zombie" )*2 + +//////////////////////////////////////////////////////////////////////////////////////////// + +function IncludeModel( mdl ) + if not mdl then return end + if wOS.DynaBase.PreservedModels[ mdl ] then mdl = wOS.DynaBase.PreservedModels[ mdl ] end + if table.HasValue( added, mdl ) then return end //This isn't needed but let's not bloat the mount file + table.insert( added, 1, mdl ) +end + +local function IntToByte( num ) + if not num then return end + local t = {} + t[1] = num % 256 + for i=1, 3 do + t[i+1] = math.modf( num / ( 256^i ) ) % ( 256 ) + end + return t +end + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// If you don't know what this does do not mess with it or you're gonna mess up your player model +// There'll be a lot of repeated code here for now while we get some TESTING done +local function WRITE_TO_LOCAL( start, finish, type ) + if not type then return end + local base_path = model_table[ type ] + if not base_path then return end + + local mdl_file = file.Open( base_path, "r", "GAME" ) + local dat = {} + local str = "" + for i=0, mdl_file:Size() do + local byte = mdl_file:ReadByte() + table.insert( dat, byte ) + if not byte then continue end + str = str .. string.char( byte ) + end + + + local endchar = str[finish] + local tstr = "" + for i=0, #str do + if i == start then + for _, newmodel in ipairs( added ) do + tstr = tstr .. newmodel .. endchar + end + end + if i >= start and i <= finish then continue end + local char = str[i] + tstr = tstr .. char + end + + local includenum = IntToByte( #added ) + for i=1, 4 do + tstr = string.SetChar( tstr, 336 + i, string.char( includenum[i] ) ) + end + + local final_line = "models/player/wiltos/anim_dynamic_pointer/." + local endptr = #tstr - #final_line + + local null_insert = IntToByte( 0 ) + + // You can manipulate the list here to remove anything you don't want for this type + hook.Call( "PreCreateLocalAnimation", nil, type, added ) + + local first_num = 60 + ( #added - 1 )*8 + local includenum = IntToByte( first_num ) + for i=1, 4 do + tstr = string.SetChar( tstr, 680 + i, string.char( includenum[i] ) ) + end + + for i=1, #added - 1 do + + local anim_pos = first_num - 7*i + for j=1, i do + anim_pos = anim_pos + #added[j] + end + local anim_num = IntToByte( anim_pos ) + + local start_pos = 676 + 8*i + local start_half = string.sub( tstr, 1, start_pos ) + for j=1, 4 do + start_half = start_half .. string.char( null_insert[j] ) + end + for j=1, 4 do + start_half = start_half .. string.char( anim_num[j] ) + end + endptr = endptr + 8 + tstr = start_half .. string.sub( tstr, start_pos + 1 ) + end + + + local endincludeaddr = IntToByte( endptr ) + for i=1, 4 do + tstr = string.SetChar( tstr, 684 + 8*(#added - 1 ) + i, string.char( endincludeaddr[i] ) ) + end + + for i=0, #added - 1 do + tstr = tstr .. endchar + end + + local datalen = IntToByte( #tstr ) + for i=1, 4 do + tstr = string.SetChar( tstr, 76 + i, string.char( datalen[i] ) ) + end + + local ext = model_ext[ type ] or "_shared" + file.Write( "wos/dynabase/local" .. ext .. WOS_DYNABASE_FILE_EXTENSION, tstr ) + hook.Call( "PostCreateLocalAnimation", nil, type, added ) +end + +// If you don't know what this does do not mess with it or you're gonna mess up your player model +local function WRITE_TO_POINTER( start, finish, type ) + if not type then return end + local base_path = model_table[ type ] + if not base_path then return end + + local mdl_file = file.Open( base_path, "r", "GAME" ) + local str = "" + for i=0, mdl_file:Size() do + local byte = mdl_file:ReadByte() + if not byte then continue end + str = str .. string.char( byte ) + end + + + local endchar = str[finish] + local tstr = "" + for i=0, #str do + if i == start then + for _, newmodel in ipairs( added ) do + tstr = tstr .. newmodel .. endchar + end + end + if i >= start and i <= finish then continue end + local char = str[i] + tstr = tstr .. char + end + + local includenum = IntToByte( #added ) + for i=1, 4 do + tstr = string.SetChar( tstr, 336 + i, string.char( includenum[i] ) ) + end + + local final_line = "models/player/wiltos/anim_dynamic_pointer/." + local endptr = #tstr - #final_line + + local null_insert = IntToByte( 0 ) + + // You can manipulate the list here to remove anything you don't want for this type + hook.Call( "PreMountAnimation", nil, type, added ) + + local first_num = 60 + ( #added - 1 )*8 + local includenum = IntToByte( first_num ) + for i=1, 4 do + tstr = string.SetChar( tstr, 680 + i, string.char( includenum[i] ) ) + end + + for i=1, #added - 1 do + + local anim_pos = first_num - 7*i + for j=1, i do + anim_pos = anim_pos + #added[j] + end + local anim_num = IntToByte( anim_pos ) + + local start_pos = 676 + 8*i + local start_half = string.sub( tstr, 1, start_pos ) + for j=1, 4 do + start_half = start_half .. string.char( null_insert[j] ) + end + for j=1, 4 do + start_half = start_half .. string.char( anim_num[j] ) + end + endptr = endptr + 8 + tstr = start_half .. string.sub( tstr, start_pos + 1 ) + end + + + local endincludeaddr = IntToByte( endptr ) + for i=1, 4 do + tstr = string.SetChar( tstr, 684 + 8*(#added - 1 ) + i, string.char( endincludeaddr[i] ) ) + end + + for i=0, #added - 1 do + tstr = tstr .. endchar + end + + local datalen = IntToByte( #tstr ) + for i=1, 4 do + tstr = string.SetChar( tstr, 76 + i, string.char( datalen[i] ) ) + end + + local ext = model_ext[ type ] or "" + file.Write( "wos/dynabase/anim_dynamic" .. ext .. WOS_DYNABASE_FILE_EXTENSION, tstr ) + hook.Call( "PostMountAnimation", nil, type, added ) +end + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +local function AdjustMountOrder( tbl, typ ) + if not tbl then return end + + local reference = wOS.DynaBase.FilteredLoadOrder[ typ ] + local blacklist = wOS.DynaBase.FilteredBlacklist[ typ ] + local plyref = wOS.DynaBase.FilteredPlayerOrder[ typ ] + local plyblacklist = wOS.DynaBase.FilteredPlayerBlacklist[ typ ] + + local new_tbl = {} + + if WOS_DYNABASE_ENFORCECONTENT_CVAR:GetInt() > 0 then + local cust_mount = wOS.DynaBase:GetSource( "Local Player Animations" ) + if cust_mount then + if typ == WOS_DYNABASE.MALE then + table.RemoveByValue( tbl, cust_mount.Male ) + elseif typ == WOS_DYNABASE.FEMALE then + table.RemoveByValue( tbl, cust_mount.Female ) + elseif typ == WOS_DYNABASE.ZOMBIE then + table.RemoveByValue( tbl, cust_mount.Zombie ) + else + table.RemoveByValue( tbl, cust_mount.Shared ) + end + end + end + + if reference then + for _, path in ipairs( reference ) do + path = wOS.DynaBase.PreservedModels[ path ] or path + if !table.HasValue( tbl, path ) then continue end + table.insert( new_tbl, path ) + table.RemoveByValue( tbl, path ) + end + end + + // If we aren't allowed to use any other addon than the ones that are dictated, don't merge any extra models + if WOS_DYNABASE_ENFORCECONTENT_CVAR:GetInt() >= 2 and reference then return new_tbl end + + if plyref then + for _, path in ipairs( plyref ) do + path = wOS.DynaBase.PreservedModels[ path ] or path + if !table.HasValue( tbl, path ) then continue end + table.insert( new_tbl, path ) + table.RemoveByValue( tbl, path ) + end + end + + if plyblacklist then + for _, path in ipairs( plyblacklist ) do + path = wOS.DynaBase.PreservedModels[ path ] or path + table.RemoveByValue( tbl, path ) + end + end + + // Add the ones we don't care about now + table.Add( new_tbl, tbl ) + + // Server gets final word what we can use + if blacklist then + for _, path in ipairs( blacklist ) do + path = wOS.DynaBase.PreservedModels[ path ] or path + table.RemoveByValue( new_tbl, path ) + end + end + + + return new_tbl +end + +function wOS.DynaBase:ReloadAnimations() + + if self.LIVE_RELOAD then + local seq = LocalPlayer():LookupSequence( "_dynamic_wiltos_enabled_" ) + if seq <= 0 then + chat.AddText( Color( 255, 0, 0 ), "[wOS-Dynabase] Dynamic Animation Base failed due to conflicting animation base. Enter wos_dynabase_help into console to find the conflict, unsubscribe from the addon, and restart Garry's Mod.\n\nIf you are on someone's server, contact the owner to ensure they do not have a conflicting animation addon as required content." ) + return + end + end + + added = table.Copy( self.DefaultTable.Male ) + hook.Call( "PreLoadAnimations", nil, WOS_DYNABASE.MALE, added ) + local ntable = AdjustMountOrder( added, WOS_DYNABASE.MALE ) + added = ntable or added + WRITE_TO_POINTER( INCLUDE_MODEL_START, INCLUDE_MODEL_END_MALE, WOS_DYNABASE.MALE ) + + added = table.Copy( wOS.DynaBase.DefaultTable.Female ) + hook.Call( "PreLoadAnimations", nil, WOS_DYNABASE.FEMALE, added ) + ntable = AdjustMountOrder( added, WOS_DYNABASE.FEMALE ) + added = ntable or added + WRITE_TO_POINTER( INCLUDE_MODEL_START, INCLUDE_MODEL_END_FEMALE, WOS_DYNABASE.FEMALE ) + + added = table.Copy( wOS.DynaBase.DefaultTable.Zombie ) + hook.Call( "PreLoadAnimations", nil, WOS_DYNABASE.ZOMBIE, added ) + ntable = AdjustMountOrder( added, WOS_DYNABASE.ZOMBIE ) + added = ntable or added + WRITE_TO_POINTER( INCLUDE_MODEL_START, INCLUDE_MODEL_END_ZOMBIE, WOS_DYNABASE.ZOMBIE ) + + added = table.Copy( wOS.DynaBase.DefaultTable.Shared ) + hook.Call( "PreLoadAnimations", nil, WOS_DYNABASE.SHARED, added ) + ntable = AdjustMountOrder( added, WOS_DYNABASE.SHARED ) + added = ntable or added + WRITE_TO_POINTER( INCLUDE_MODEL_START, INCLUDE_MODEL_END_SHARED, WOS_DYNABASE.SHARED ) + + if SERVER then + hook.Call( "PostLoadAnimations" ) --Server doesn't need to worry about render stuff so we load here + return + end + + if wOS.DynaBase.InitCompleted and not WOS_DYNABASE_SHOULDHOTLOAD_CVAR:GetBool() then return end + wOS.DynaBase.ReloadModelBool = true +end + +function wOS.DynaBase:ReloadLocalAnimations( order ) + + if not order then + order = file.Read( "wos/dynabase/usermounts/preference.txt", "DATA" ) or "{}" + order = util.JSONToTable( order ) + end + + local shared_tbl = {} + local male_tbl = {} + local female_tbl = {} + local zombie_tbl = {} + + for _, mount in ipairs( order ) do + if not mount.Toggled then continue end + local data = self:GetUserMount( mount.Name ) + if not data then continue end + + if data.Shared and not table.HasValue( shared_tbl, data.Shared ) then + table.insert( shared_tbl, data.Shared ) + end + + if data.Male and not table.HasValue( male_tbl, data.Male ) then + table.insert( male_tbl, data.Male ) + end + + if data.Female and not table.HasValue( female_tbl, data.Female ) then + table.insert( female_tbl, data.Female ) + end + + if data.Zombie and not table.HasValue( zombie_tbl, data.Zombie ) then + table.insert( zombie_tbl, data.Zombie ) + end + + end + + added = male_tbl + WRITE_TO_LOCAL( INCLUDE_MODEL_START, INCLUDE_MODEL_END_MALE, WOS_DYNABASE.MALE ) + + added = female_tbl + WRITE_TO_LOCAL( INCLUDE_MODEL_START, INCLUDE_MODEL_END_FEMALE, WOS_DYNABASE.FEMALE ) + + added = zombie_tbl + WRITE_TO_LOCAL( INCLUDE_MODEL_START, INCLUDE_MODEL_END_ZOMBIE, WOS_DYNABASE.ZOMBIE ) + + added = shared_tbl + WRITE_TO_LOCAL( INCLUDE_MODEL_START, INCLUDE_MODEL_END_SHARED, WOS_DYNABASE.SHARED ) +end + +file.CreateDir("wos/dynabase/usermounts") diff --git a/garrysmod/addons/wos/lua/wos/dynabase/core/sv_core.lua b/garrysmod/addons/wos/lua/wos/dynabase/core/sv_core.lua new file mode 100644 index 0000000..fa21e4e --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/core/sv_core.lua @@ -0,0 +1,15 @@ +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} + +util.AddNetworkString("wOS.DynaBase.SendAllRegisters") +util.AddNetworkString("wOS.DynaBase.SendRegister") +util.AddNetworkString("wOS.DynaBase.ForceMountCallback") + +hook.Add( "PlayerInitialSpawn", "wOS.DynaBase.NetworkRegisteredAnimations", function( ply ) + net.Start( "wOS.DynaBase.SendAllRegisters" ) + net.WriteUInt( wOS.DynaBase:GetSourceCount(), 32 ) + for name, _ in pairs( wOS.DynaBase:GetAllSources() ) do + net.WriteString( name ) + end + net.Send( ply ) +end ) diff --git a/garrysmod/addons/wos/lua/wos/dynabase/loader/loader.lua b/garrysmod/addons/wos/lua/wos/dynabase/loader/loader.lua new file mode 100644 index 0000000..938e473 --- /dev/null +++ b/garrysmod/addons/wos/lua/wos/dynabase/loader/loader.lua @@ -0,0 +1,81 @@ +--[[------------------------------------------------------------------- + wiltOS Dynamic Animation Base: + Powered by + _ _ _ ___ ____ + __ _(_) | |_ / _ \/ ___| + \ \ /\ / / | | __| | | \___ \ + \ V V /| | | |_| |_| |___) | + \_/\_/ |_|_|\__|\___/|____/ + + _____ _ _ _ +|_ _|__ ___| |__ _ __ ___ | | ___ __ _(_) ___ ___ + | |/ _ \/ __| '_ \| '_ \ / _ \| |/ _ \ / _` | |/ _ \/ __| + | | __/ (__| | | | | | | (_) | | (_) | (_| | | __/\__ \ + |_|\___|\___|_| |_|_| |_|\___/|_|\___/ \__, |_|\___||___/ + |___/ +-------------------------------------------------------------------]]--[[ + + Lua Developer: King David + Contact: http://steamcommunity.com/groups/wiltostech + +----------------------------------------]]-- + +wOS = wOS or {} +wOS.DynaBase = wOS.DynaBase or {} + +local string = string +local file = file + +local function _AddCSLuaFile( lua ) + + if SERVER then + AddCSLuaFile( lua ) + end + +end + +local function _include( load_type, lua ) + + if load_type then + include( lua ) + end + +end + +function wOS.DynaBase:Autoloader() + + _AddCSLuaFile( "wos/dynabase/core/sh_core.lua" ) + _include( SERVER, "wos/dynabase/core/sh_core.lua" ) + _include( CLIENT, "wos/dynabase/core/sh_core.lua" ) + + _AddCSLuaFile( "wos/dynabase/core/sh_model_operations.lua" ) + _include( SERVER, "wos/dynabase/core/sh_model_operations.lua" ) + _include( CLIENT, "wos/dynabase/core/sh_model_operations.lua" ) + + _AddCSLuaFile( "wos/dynabase/core/sh_mounting.lua" ) + _include( SERVER, "wos/dynabase/core/sh_mounting.lua" ) + _include( CLIENT, "wos/dynabase/core/sh_mounting.lua" ) + + _AddCSLuaFile( "wos/dynabase/core/cl_net.lua" ) + _include( CLIENT, "wos/dynabase/core/cl_net.lua" ) + + _AddCSLuaFile( "wos/dynabase/core/cl_core.lua" ) + _include( CLIENT, "wos/dynabase/core/cl_core.lua" ) + _include( SERVER, "wos/dynabase/core/sv_core.lua" ) + + _AddCSLuaFile( "wos/dynabase/core/cl_local_copy.lua" ) + _include( CLIENT, "wos/dynabase/core/cl_local_copy.lua" ) + + _AddCSLuaFile( "wos/dynabase/core/cl_config_menu.lua" ) + _include( CLIENT, "wos/dynabase/core/cl_config_menu.lua" ) + + for _,source in pairs( file.Find( "wos/dynabase/registers/*", "LUA"), true ) do + local lua = "wos/dynabase/registers/" .. source + _AddCSLuaFile( lua ) + _include( SERVER, lua ) + _include( CLIENT, lua ) + end + +end + +wOS.DynaBase:Autoloader() diff --git a/garrysmod/addons/wos/models/f_anm.ani b/garrysmod/addons/wos/models/f_anm.ani new file mode 100644 index 0000000..f1b29d5 Binary files /dev/null and b/garrysmod/addons/wos/models/f_anm.ani differ diff --git a/garrysmod/addons/wos/models/f_anm.mdl b/garrysmod/addons/wos/models/f_anm.mdl new file mode 100644 index 0000000..747d330 Binary files /dev/null and b/garrysmod/addons/wos/models/f_anm.mdl differ diff --git a/garrysmod/addons/wos/models/f_wos.mdl b/garrysmod/addons/wos/models/f_wos.mdl new file mode 100644 index 0000000..f6c74e7 Binary files /dev/null and b/garrysmod/addons/wos/models/f_wos.mdl differ diff --git a/garrysmod/addons/wos/models/m_anm.ani b/garrysmod/addons/wos/models/m_anm.ani new file mode 100644 index 0000000..f1b29d5 Binary files /dev/null and b/garrysmod/addons/wos/models/m_anm.ani differ diff --git a/garrysmod/addons/wos/models/m_anm.mdl b/garrysmod/addons/wos/models/m_anm.mdl new file mode 100644 index 0000000..1ed0cb4 Binary files /dev/null and b/garrysmod/addons/wos/models/m_anm.mdl differ diff --git a/garrysmod/addons/wos/models/m_wos.mdl b/garrysmod/addons/wos/models/m_wos.mdl new file mode 100644 index 0000000..7157d03 Binary files /dev/null and b/garrysmod/addons/wos/models/m_wos.mdl differ diff --git a/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_femaler.mdl b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_femaler.mdl new file mode 100644 index 0000000..dc40f13 Binary files /dev/null and b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_femaler.mdl differ diff --git a/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_maleptr.mdl b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_maleptr.mdl new file mode 100644 index 0000000..83a7e91 Binary files /dev/null and b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_maleptr.mdl differ diff --git a/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_pointer.mdl b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_pointer.mdl new file mode 100644 index 0000000..931656a Binary files /dev/null and b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_pointer.mdl differ diff --git a/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_zombier.mdl b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_zombier.mdl new file mode 100644 index 0000000..d9cbd07 Binary files /dev/null and b/garrysmod/addons/wos/models/player/wiltos/anim_dynamic_zombier.mdl differ diff --git a/garrysmod/addons/wos/models/z_anm.ani b/garrysmod/addons/wos/models/z_anm.ani new file mode 100644 index 0000000..f1b29d5 Binary files /dev/null and b/garrysmod/addons/wos/models/z_anm.ani differ diff --git a/garrysmod/addons/wos/models/z_anm.mdl b/garrysmod/addons/wos/models/z_anm.mdl new file mode 100644 index 0000000..4740d7f Binary files /dev/null and b/garrysmod/addons/wos/models/z_anm.mdl differ diff --git a/garrysmod/addons/wos/models/z_wos.mdl b/garrysmod/addons/wos/models/z_wos.mdl new file mode 100644 index 0000000..1a00085 Binary files /dev/null and b/garrysmod/addons/wos/models/z_wos.mdl differ diff --git a/garrysmod/addons/wos_legacy/lua/wos/dynabase/registers/wos_legacy_registers.lua b/garrysmod/addons/wos_legacy/lua/wos/dynabase/registers/wos_legacy_registers.lua new file mode 100644 index 0000000..081cf73 --- /dev/null +++ b/garrysmod/addons/wos_legacy/lua/wos/dynabase/registers/wos_legacy_registers.lua @@ -0,0 +1,202 @@ +// These are a bunch of legacy functions to register addons that don't have integrated support for animation management +// This is not how you should be doing your registers. See other addons + +local function GenericRegister( data ) + wOS.DynaBase:RegisterSource({ + Name = data.Name, + Type = ( data.Extension and WOS_DYNABASE.EXTENSION ) or WOS_DYNABASE.REANIMATION, + Male = ( data.Male and "models/xdreanims/m_anm_slot_" .. data.BaseSlot .. ".mdl" ) or nil, + Female = ( data.Female and "models/xdreanims/f_anm_slot_" .. data.BaseSlot .. ".mdl" ) or nil, + Zombie = ( data.Zombie and "models/xdreanims/z_anm_slot_" .. data.BaseSlot .. ".mdl" ) or nil, + }) + + hook.Add( "PreLoadAnimations", "wOS.DynaBase.Mount" .. data.Name, function( gender ) + if gender == WOS_DYNABASE.MALE and data.Male then + IncludeModel( "models/xdreanims/m_anm_slot_" .. data.BaseSlot .. ".mdl" ) + elseif gender == WOS_DYNABASE.FEMALE and data.Female then + IncludeModel( "models/xdreanims/f_anm_slot_" .. data.BaseSlot .. ".mdl" ) + elseif gender == WOS_DYNABASE.ZOMBIE and data.Zombie then + IncludeModel( "models/xdreanims/z_anm_slot_" .. data.BaseSlot .. ".mdl" ) + end + end ) +end + +local tbl = { + ['2261825706'] = { + Name = "CSGO Reanimations", + BaseSlot = '046', + Male = true, + Female = true, + Zombie = true, + }, + ['2143589929'] = { + Name = "Two-Hand Pistol Reanimations", + BaseSlot = '002', + Male = true, + Female = true, + Zombie = true, + }, + ['2493356270'] = { + Name = "Ironight Reanimations", + BaseSlot = '011', + Male = true, + Female = true, + Zombie = true, + }, + ['2737372889'] = { + Name = "COD Zombie Reanimations", + BaseSlot = '035', + Male = true, + Female = true, + Zombie = true, + }, + ['2432553338'] = { + Name = "Combine Passive Reanimations", + BaseSlot = '018', + Male = true, + Female = true, + Zombie = true, + }, + ['2424958167'] = { + Name = "Drip Idle Reanimations", + BaseSlot = '019', + Male = true, + Female = true, + Zombie = true, + }, + ['2348399590'] = { + Name = "Cut Fist Reanimations", + BaseSlot = '016', + Male = true, + Female = true, + Zombie = true, + }, + ['2169293226'] = { + Name = "Radio Chatter Reanimations", + BaseSlot = '038', + Male = true, + Female = true, + Zombie = true, + }, + ['2148772437'] = { + Name = "Reduced Breath Reanimations", + BaseSlot = '029', + Male = true, + Female = true, + Zombie = true, + }, + ['2903472153'] = { + Name = "Human Realm Reanimations", + BaseSlot = '039', + Male = true, + Female = true, + Zombie = true, + }, + ['2918092137'] = { + Name = "COD Modern Warfare Reanimations", + BaseSlot = '046', + Male = true, + Female = false, + Zombie = false, + }, + ['2791673215'] = { + Name = "CODIW Idle Reanimations", + BaseSlot = '030', + Male = true, + Female = true, + Zombie = true, + }, + ['2792431263'] = { + Name = "CODIW Last Stand Extension", + BaseSlot = '040', + Extension = true, + Male = true, + Female = true, + Zombie = true, + }, + ['2912631064'] = { + Name = "Feminine Sitting Reanimations", + BaseSlot = '015', + Male = false, + Female = true, + Zombie = false, + }, + ['2742793067'] = { + Name = "TF2 Laughing Reanimations", + BaseSlot = '031', + Male = true, + Female = true, + Zombie = false, + }, + ['2891284985'] = { + Name = "SadCat Dance Reanimations", + BaseSlot = '027', + Male = true, + Female = true, + Zombie = false, + }, + ['2895861489'] = { + Name = "Zero Two Dance Reanimations", + BaseSlot = '013', + Male = true, + Female = true, + Zombie = false, + }, + ['2892723717'] = { + Name = "KDA Dance Reanimations", + BaseSlot = '026', + Male = true, + Female = true, + Zombie = false, + }, +} + +// Workshop addon check first because that's the most reliable +local op_table = table.Copy( tbl ) +for _, addon in ipairs( engine.GetAddons() ) do + if not addon.mounted then continue end + if addon.wsid == "2247494212" then //have to hardcode this cause Yongli needs to do an update.. + wOS.DynaBase:RegisterSource({ + Name = "Sword Art Extension", + Type = WOS_DYNABASE.EXTENSION, + Shared = "models/player/wiltos/anim_extension_mod18.mdl", + }) + + hook.Add( "PreLoadAnimations", "wOS.DynaBase.MountSwordArt", function( gender ) + if gender != WOS_DYNABASE.SHARED then return end + IncludeModel( "models/player/wiltos/anim_extension_mod18.mdl" ) + end ) + end + if not op_table[addon.wsid] then continue end + GenericRegister( op_table[addon.wsid] ) + op_table[addon.wsid] = nil +end + +// Now for the longer version we have to do for servers. +for _, data in pairs( op_table ) do + local base_path = "models/xdreanims/f_anm_slot_" .. data.BaseSlot .. ".mdl" //They happen to all have female so we'll use that + if !file.Exists(base_path, "GAME") then continue end + GenericRegister( data ) +end + +if CLIENT then + hook.Add( "wOS.DynaBase.PopulateHelperFunctions", "wOS.DynaBase.LEgacyAddHelper", function( parent ) + local download_butt = vgui.Create( "DButton", parent ) + download_butt:SetSize( parent:GetWide(), parent:GetTall()*0.0625 ) + download_butt:Dock( TOP ) + download_butt:SetText( "Create User Mounts from Legacy Addon (Will overwrite mounts with the same name!)" ) + download_butt.DoClick = function(pan) + for wsid, data in pairs( tbl ) do + local ndata = { + Name = data.Name, + Male = ( data.Male and "models/xdreanims/m_anm_slot_" .. data.BaseSlot .. ".mdl" ) or nil, + Female = ( data.Female and "models/xdreanims/f_anm_slot_" .. data.BaseSlot .. ".mdl" ) or nil, + Zombie = ( data.Zombie and "models/xdreanims/z_anm_slot_" .. data.BaseSlot .. ".mdl" ) or nil, + } + wOS.DynaBase:CreateUserMount( ndata ) + end + chat.AddText( color_white, "[", Color( 0, 175, 255 ), "wOS-DynaBase", color_white, "] All legacy mounts regardless of subscription status have been added!" ) + parent:ReloadAddons() + end + end ) +end diff --git a/garrysmod/gamemodes/helix/.editorconfig b/garrysmod/gamemodes/helix/.editorconfig new file mode 100644 index 0000000..6e4b872 --- /dev/null +++ b/garrysmod/gamemodes/helix/.editorconfig @@ -0,0 +1,13 @@ + +root = true + +[*] +end_of_line = crlf +insert_final_newline = true +charset = utf-8 +indent_style = tab +indent_size = 4 + +[.travis.yml] +indent_style = space +indent_size = 2 diff --git a/garrysmod/gamemodes/helix/.github/workflows/ci.yml b/garrysmod/gamemodes/helix/.github/workflows/ci.yml new file mode 100644 index 0000000..8e73d3b --- /dev/null +++ b/garrysmod/gamemodes/helix/.github/workflows/ci.yml @@ -0,0 +1,74 @@ + +name: CI +on: [push, pull_request] + +jobs: + linter: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + path: helix + + - uses: leafo/gh-actions-lua@v8.0.0 + with: + luaVersion: "5.2" + + - uses: leafo/gh-actions-luarocks@v4.0.0 + + - name: Pull gluacheck + uses: actions/checkout@v2 + with: + repository: impulsh/gluacheck + path: luacheck + + - name: Build gluacheck + working-directory: luacheck + run: luarocks make + + - name: Lint + working-directory: helix + run: luacheck . + + docs: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + path: helix + + - uses: leafo/gh-actions-lua@v8.0.0 + with: + luaVersion: "5.2" + + - uses: leafo/gh-actions-luarocks@v4.0.0 + + - name: Pull LDoc + uses: actions/checkout@v2 + with: + repository: impulsh/LDoc + path: ldoc + + - name: Build LDoc + working-directory: ldoc + run: luarocks make + + - name: Build docs + working-directory: helix + run: ldoc . --fatalwarnings + + - name: Copy assets + working-directory: helix + run: | + cp -v docs/css/* docs/html + cp -v docs/js/* docs/html + + - name: Deploy + if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'NebulousCloud/helix' && success() + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: helix/docs/html + cname: docs.gethelix.co diff --git a/garrysmod/gamemodes/helix/.luacheckrc b/garrysmod/gamemodes/helix/.luacheckrc new file mode 100644 index 0000000..2553624 --- /dev/null +++ b/garrysmod/gamemodes/helix/.luacheckrc @@ -0,0 +1,3463 @@ + +max_line_length = 128 +std = "luajit+gmod+helix" +ignore = { + "212", -- unused argument +} + +-- helix +stds.helix = {} +stds.helix.globals = { + "ix", + "Schema", + + "ITEM", + "PLUGIN", + "ATTRIBUTE", + "NAME", + "LANGUAGE", + "FACTION", + "CLASS", + "CHAT_RECOGNIZED", + "ALWAYS_RAISED", + "ICON_RENDER_QUEUE", + "USABLE_FUNCS", +} +stds.helix.read_globals = { + "L", + "L2", + "IX_RELOADED", + "CHAT_CLASS", + "HOOKS_CACHE", + "BAR_HEIGHT", + "ACCESS_LABELS", + "CAMI", + + "netstream", + "mysql", + "pon", + "ikon", + "BaseClass", + + "SetNetVar", + "GetNetVar", + "ixSoundDuration", + + "HOLDTYPE_TRANSLATOR", + "PLAYER_HOLDTYPE_TRANSLATOR", + + "ACT_VM_FISTS_DRAW", + "ACT_VM_FISTS_HOLSTER", + + "TOOLTIP_GENERIC", + "TOOLTIP_ITEM", + + "FLAG_NORMAL", + "FLAG_SUCCESS", + "FLAG_WARNING", + "FLAG_DANGER", + "FLAG_SERVER", + "FLAG_DEV", + + "DOOR_OWNER", + "DOOR_TENANT", + "DOOR_GUEST", + "DOOR_NONE", + + "VENDOR_BUY", + "VENDOR_SELL", + "VENDOR_BOTH", + "VENDOR_WELCOME", + "VENDOR_LEAVE", + "VENDOR_NOTRADE", + "VENDOR_PRICE", + "VENDOR_STOCK", + "VENDOR_MODE", + "VENDOR_MAXSTOCK", + "VENDOR_SELLANDBUY", + "VENDOR_SELLONLY", + "VENDOR_BUYONLY", + "VENDOR_TEXT", + + "FCAP_IMPULSE_USE", + "FCAP_CONTINUOUS_USE", + "FCAP_ONOFF_USE", + "FCAP_DIRECTIONAL_USE", + "FCAP_USE_ONGROUND", + "FCAP_USE_IN_RADIUS", +} + +files = { + -- some phrases are unavoidably long, so we'll ignore the max line length for language files + ["gamemode/languages/**/*.lua"] = { + ignore = { + "631" + } + }, + + ["plugins/**/languages/*.lua"] = { + ignore = { + "631" + } + } +} + +-- ignore third party files +exclude_files = { + "gamemode/core/libs/thirdparty/**/*.lua" +} + +-- gmod +stds.gmod = {} +stds.gmod.globals = { + "GM", + "ENT", + "TOOL", + "SWEP" +} +stds.gmod.read_globals = { + "VERSION", + "CLIENT", + "SERVER", + "GAMEMODE", + "NULL", + + "vector_origin", + "vector_up", + "angle_zero", + "color_white", + "color_black", + "color_transparent", + + "PLAYERANIMEVENT_CANCEL_RELOAD", + "ACT_COMBINE_THROW_GRENADE", + + -- Generated on Wed Jan 17 02:56:57 2018 + "ACT_MP_GESTURE_VC_NODYES", + "ACT_MELEE_ATTACK_SWING_GESTURE", + "SCHED_TAKE_COVER_FROM_ORIGIN", + "ACT_BUSY_SIT_GROUND_ENTRY", + "ACT_DOD_CROUCH_IDLE_C96", + "ACT_IDLE_STEALTH", + "ACT_DOD_PRIMARYATTACK_DEPLOYED", + "ACT_MP_STAND_PRIMARY", + "ACT_DOD_CROUCHWALK_AIM", + "ACT_DOD_RELOAD_PRONE_DEPLOYED_BAR", + "ACT_GLOCK_SHOOT_RELOAD", + "TEXT_ALIGN_CENTER", + "DMG_CRUSH", + "ScrH", + "ACT_DOD_RELOAD_CROUCH_BAR", + "ACT_HL2MP_JUMP_PASSIVE", + "ACT_MP_GESTURE_VC_HANDMOUTH_SECONDARY", + "ACT_MP_JUMP_START_MELEE", + "ACT_GET_UP_CROUCH", + "ACT_DOD_CROUCHWALK_IDLE_GREASE", + "ACT_DOD_CROUCHWALK_IDLE_PISTOL", + "ACT_HL2MP_WALK_CROUCH_SUITCASE", + "ACT_HL2MP_GESTURE_RELOAD_SLAM", + "SIMPLE_USE", + "ACT_DOD_WALK_AIM_GREN_STICK", + "ACT_DOD_PRONEWALK_IDLE_MG", + "FL_FLY", + "ACT_HL2MP_IDLE_CROUCH_REVOLVER", + "presets", + "ACT_MP_RUN_MELEE", + "halo", + "HULL_WIDE_SHORT", + "video", + "SND_SPAWNING", + "ACT_MP_GRENADE2_DRAW", + "ACT_OVERLAY_PRIMARYATTACK", + "COND_SEE_NEMESIS", + "KEY_O", + "ACT_OVERLAY_GRENADEREADY", + "ACT_COVER_MED", + "AddConsoleCommand", + "ACT_VM_IIDLE_M203", + "ACT_HL2MP_JUMP_RPG", + "ACT_DOD_STAND_AIM_MG", + "MAT_VENT", + "ACT_VM_HITRIGHT2", + "ACT_VM_RELOAD_EMPTY", + "DSprite", + "BONE_CALCULATE_MASK", + "GetGlobalVector", + "ACT_HL2MP_IDLE_PISTOL", + "SetGlobalVector", + "HTTP", + "WorldToLocal", + "ACT_MP_CROUCH_IDLE", + "COLLISION_GROUP_VEHICLE", + "KEY_XBUTTON_B", + "ACT_PICKUP_RACK", + "CAP_MOVE_CRAWL", + "ACT_DOD_CROUCHWALK_AIM_BOLT", + "ACT_MP_MELEE_GRENADE2_DRAW", + "ACT_DIE_BARNACLE_SWALLOW", + "ACT_WALK_STEALTH_PISTOL", + "ACT_DOD_PRONE_AIM_MP44", + "ACT_PRONE_FORWARD", + "COND_ENEMY_OCCLUDED", + "ACT_DOD_RUN_IDLE_BOLT", + "GetConVarNumber", + "ACT_DOD_CROUCHWALK_AIM_RIFLE", + "ACT_DOD_RELOAD_RIFLEGRENADE", + "ACT_VM_IIN_M203", + "ACT_MP_ATTACK_AIRWALK_BUILDING", + "FVPHYSICS_DMG_DISSOLVE", + "ACT_INVALID", + "ACT_DOD_RELOAD_PRONE_MP44", + "ACT_SLAM_TRIPMINE_DRAW", + "ACT_MP_GESTURE_VC_NODNO", + "SetGlobalAngle", + "ACT_DOD_SPRINT_IDLE_BOLT", + "ACT_MP_PRIMARY_GRENADE1_IDLE", + "ACT_VM_PRIMARYATTACK_DEPLOYED_1", + "ClientsideScene", + "MASK_SHOT", + "DTree_Node_Button", + "GetGlobalAngle", + "MOVETYPE_ISOMETRIC", + "ACT_GESTURE_RANGE_ATTACK_SMG1_LOW", + "NAV_MESH_STOP", + "COND_NONE", + "ACT_SHIELD_ATTACK", + "ACT_GESTURE_RANGE_ATTACK_SMG2", + "CNavArea", + "ACT_HL2MP_WALK", + "include", + "ACT_WALK_ANGRY", + "ACT_DOD_PRIMARYATTACK_PRONE_PISTOL", + "ACT_MP_GESTURE_VC_FINGERPOINT_BUILDING", + "DDrawer", + "ACT_VM_IRECOIL1", + "BOX_TOP", + "ACT_MP_RUN_PDA", + "ACT_DOD_RELOAD_BOLT", + "SaveLastMap", + "ACT_DOD_RUN_ZOOM_BOLT", + "ACT_MP_RELOAD_AIRWALK_LOOP", + "ACT_DEEPIDLE1", + "COND_SEE_ENEMY", + "DColorPalette", + "DMG_DIRECT", + "Derma_Message", + "DPanelSelect", + "ACT_HL2MP_ZOMBIE_SLUMP_RISE", + "FVPHYSICS_NO_IMPACT_DMG", + "CLASS_CONSCRIPT", + "CONTINUOUS_USE", + "STUDIO_TRANSPARENCY", + "NOTIFY_UNDO", + "ACT_MP_ATTACK_CROUCH_GRENADE_PRIMARY", + "ACT_SMG2_FIRE2", + "ACT_RANGE_ATTACK_AR1", + "ACT_MP_DEPLOYED_IDLE", + "ACT_MP_ATTACK_SWIM_PRIMARYFIRE", + "kRenderFxEnvSnow", + "COND_WAY_CLEAR", + "MATERIAL_LINE_STRIP", + "SetGlobalString", + "ACT_IDLE_RELAXED", + "ACT_VM_PRIMARYATTACK", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_DUEL", + "ACT_VM_DEPLOY_EMPTY", + "ACT_VM_IDLE_EMPTY", + "Matrix", + "ACT_DOD_SECONDARYATTACK_PRONE_TOMMY", + "COND_WEAPON_SIGHT_OCCLUDED", + "ACT_DOD_PRIMARYATTACK_KNIFE", + "ACT_VM_RELOAD_END_EMPTY", + "menu", + "CLASS_SCANNER", + "ACT_MP_CROUCHWALK_SECONDARY", + "ACT_DOD_SECONDARYATTACK_BOLT", + "SF_NPC_ALWAYSTHINK", + "ACT_DOD_WALK_IDLE_TNT", + "ACT_HL2MP_WALK_ZOMBIE", + "ACT_IDLE_SMG1_RELAXED", + "TextEntry", + "ACT_DOD_STAND_ZOOM_RIFLE", + "COND_HEAR_BUGBAIT", + "COND_WEAPON_PLAYER_NEAR_TARGET", + "LoadPresets", + "IN_ALT2", + "ACT_MP_SWIM_DEPLOYED", + "SetGlobalInt", + "DrawSunbeams", + "ACT_MP_ATTACK_SWIM_SECONDARY", + "KEY_4", + "ACT_MP_GESTURE_VC_FISTPUMP_MELEE", + "ACT_DOD_RUN_IDLE_PSCHRECK", + "ACT_PICKUP_GROUND", + "ACT_WALK_RPG", + "ACT_BUSY_SIT_CHAIR", + "ACT_DOD_CROUCHWALK_AIM_KNIFE", + "TYPE_COLOR", + "ACT_DOD_CROUCH_IDLE_30CAL", + "ACT_DOD_PRONEWALK_AIM_SPADE", + "properties", + "ACT_HL2MP_IDLE_RPG", + "achievements", + "CAP_WEAPON_MELEE_ATTACK2", + "ACT_VM_UNLOAD", + "ACT_VM_DRAW_EMPTY", + "LocalPlayer", + "ACT_ZOMBIE_CLIMB_UP", + "SURF_WARP", + "MAT_SLOSH", + "ACT_DOD_WALK_ZOOM_PSCHRECK", + "FSOLID_TRIGGER_TOUCH_DEBRIS", + "ACT_MP_JUMP_LAND_MELEE", + "PLAYERANIMEVENT_SWIM", + "ACT_DOD_RELOAD_PSCHRECK", + "ACT_DOD_RELOAD_CROUCH", + "PLAYER", + "ACT_DOD_RELOAD_CROUCH_MP44", + "table", + "ACT_HL2MP_FIST_BLOCK", + "ACT_VM_PRIMARYATTACK_EMPTY", + "ACT_RANGE_AIM_PISTOL_LOW", + "Panel", + "BONE_USED_BY_VERTEX_MASK", + "ACT_MP_ATTACK_CROUCH_PRIMARY", + "KEY_PAD_8", + "ACT_CROUCHING_SHIELD_UP", + "ipairs", + "KEY_LALT", + "ACT_DIEVIOLENT", + "ACT_DOD_PRIMARYATTACK_MP40", + "KEY_PAD_PLUS", + "KEY_LBRACKET", + "MsgAll", + "ACT_DOD_CROUCHWALK_IDLE_MP44", + "GetGlobalInt", + "ACT_CROUCHING_SHIELD_DOWN", + "NUM_BEAMS", + "COND_CAN_MELEE_ATTACK1", + "COND_LOST_ENEMY", + "ACT_RUN_AIM_RIFLE", + "ACT_VM_PULLBACK_HIGH", + "DrawBackground", + "TEXT_ALIGN_BOTTOM", + "ACT_HL2MP_WALK_AR2", + "ACT_RUN_PROTECTED", + "CreateParticleSystem", + "ACT_HL2MP_JUMP_DUEL", + "ACT_DOD_CROUCHWALK_AIM_BAR", + "ACT_VM_HOLSTERFULL_M203", + "ACT_DOD_PRONEWALK_IDLE_PSCHRECK", + "ACT_DOD_PRIMARYATTACK_PRONE_MP44", + "ACT_STARTDYING", + "TYPE_BOOL", + "MOVETYPE_NOCLIP", + "FVPHYSICS_PENETRATING", + "NPC_STATE_INVALID", + "STUDIO_TWOPASS", + "ACT_VM_MISSRIGHT2", + "KEY_E", + "FORCE_STRING", + "HITGROUP_LEFTLEG", + "ACT_GET_DOWN_STAND", + "ACT_HL2MP_SWIM_REVOLVER", + "ACT_DOD_PRONE_ZOOM_FORWARD_RIFLE", + "ACT_DOD_PRONEWALK_IDLE_MP40", + "ACT_MP_GESTURE_VC_NODNO_SECONDARY", + "BLEND_ZERO", + "spawnmenu", + "D_NU", + "KEY_PAD_0", + "ACT_MP_RUN_SECONDARY", + "ACT_DIE_BACKSHOT", + "ACT_WALK_CROUCH_RIFLE", + "ACT_MP_ATTACK_STAND_GRENADE_PRIMARY", + "ACT_HL2MP_GESTURE_RELOAD_REVOLVER", + "ACT_CROSSBOW_IDLE_UNLOADED", + "IMAGE_FORMAT_RGBA16161616", + "ACT_DOD_CROUCHWALK_ZOOM_BOLT", + "STENCILCOMPARISONFUNCTION_LESS", + "ACT_DOD_SPRINT_AIM_KNIFE", + "ACT_BARNACLE_PULL", + "CONTENTS_TESTFOGVOLUME", + "SNDLVL_45dB", + "ACT_DIE_LEFTSIDE", + "kRenderFxEnvRain", + "ACT_GMOD_GESTURE_DISAGREE", + "ACT_MP_JUMP_LAND_PRIMARY", + "SQLStr", + "ACT_OVERLAY_SHIELD_ATTACK", + "SCHED_ALERT_WALK", + "ACT_SIGNAL_ADVANCE", + "DImageButton", + "search", + "ACT_DOD_RELOAD_CROUCH_RIFLEGRENADE", + "KEY_XSTICK1_DOWN", + "FVPHYSICS_NO_NPC_IMPACT_DMG", + "RENDERGROUP_TRANSLUCENT", + "DrawTexturize", + "ACT_HANDGRENADE_THROW3", + "ACT_OVERLAY_SHIELD_UP", + "HITGROUP_CHEST", + "BONE_ALWAYS_PROCEDURAL", + "ACT_DOD_PRIMARYATTACK_PRONE_SPADE", + "ACT_HL2MP_JUMP_SMG1", + "ACT_DOD_STAND_AIM_MP40", + "CreateMaterial", + "COND_TARGET_OCCLUDED", + "kRenderFxHologram", + "KEY_APOSTROPHE", + "RENDERGROUP_STATIC", + "ENT_BRUSH", + "ACT_VM_DETACH_SILENCER", + "KEY_ENTER", + "ACT_VM_DEPLOYED_LIFTED_IDLE", + "SCHED_MOVE_TO_WEAPON_RANGE", + "ACT_MP_RELOAD_AIRWALK_SECONDARY", + "ACT_GMOD_GESTURE_RANGE_ZOMBIE_SPECIAL", + "KEY_F7", + "SetClipboardText", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE2", + "ACT_MP_WALK_MELEE", + "DMG_ACID", + "ACT_MP_RELOAD_AIRWALK_PRIMARY_LOOP", + "DProperty_Generic", + "PANEL", + "ProjectedTexture", + "ACT_MP_RELOAD_SWIM_SECONDARY_END", + "ACT_VM_IDLE_DEPLOYED_4", + "ACT_IDLE_MANNEDGUN", + "ACT_DOD_RELOAD_K43", + "ACT_DOD_HS_CROUCH_PSCHRECK", + "Add_NPC_Class", + "TEXTUREFLAGS_UNUSED_00080000", + "COND_HEAR_WORLD", + "ACT_VM_PRIMARYATTACK_8", + "MOVECOLLIDE_DEFAULT", + "ACT_DOD_WALK_IDLE_GREASE", + "ACT_WALK_SUITCASE", + "FCVAR_GAMEDLL", + "ACT_PLAYER_CROUCH_FIRE", + "ACT_MP_ATTACK_AIRWALK_GRENADE_BUILDING", + "ACT_HL2MP_GESTURE_RELOAD_PISTOL", + "ACT_RANGE_ATTACK_SMG1_LOW", + "ACT_RELOAD_SMG1_LOW", + "ACT_FLINCH_RIGHTLEG", + "ACT_MP_PRIMARY_GRENADE1_ATTACK", + "ACT_COVER_SMG1_LOW", + "ONOFF_USE", + "ACT_HL2MP_JUMP_SLAM", + "CheckButton", + "kRenderFxStrobeSlow", + "TYPE_EFFECTDATA", + "FSOLID_VOLUME_CONTENTS", + "TimedCos", + "ACT_DEEPIDLE3", + "ACT_HL2MP_WALK_CROUCH_PASSIVE", + "ACT_DUCK_DODGE", + "ACT_HL2MP_RUN_ZOMBIE_FAST", + "PrintMessage", + "usermessage", + "ACT_DOD_RUN_AIM", + "JoinServer", + "ACT_MP_GESTURE_VC_THUMBSUP_PRIMARY", + "TYPE_FILE", + "RunGameUICommand", + "ACT_HL2MP_JUMP_KNIFE", + "ACT_HL2MP_WALK_FIST", + "MOVECOLLIDE_FLY_BOUNCE", + "EFL_NO_WATER_VELOCITY_CHANGE", + "SURF_HITBOX", + "STENCILOPERATION_REPLACE", + "ACT_MP_ATTACK_AIRWALK_MELEE", + "ColorToHSV", + "ACT_MP_WALK_PDA", + "IMAGE_FORMAT_ABGR8888", + "BuildNetworkedVarsTable", + "KEY_XBUTTON_A", + "hook", + "SF_CITIZEN_RANDOM_HEAD", + "CreateSprite", + "DListView_ColumnPlain", + "IsTableOfEntitiesValid", + "ACT_HL2MP_IDLE_CROUCH_MELEE", + "ACT_TURN_RIGHT", + "CompileFile", + "ACT_DOD_SPRINT_IDLE_RIFLE", + "DKillIcon", + "SNDLVL_180dB", + "ACT_DOD_WALK_IDLE_PSCHRECK", + "DMG_DROWN", + "DMG_BURN", + "ACT_MP_SWIM_BUILDING", + "CONTENTS_SOLID", + "ACT_MP_STAND_IDLE", + "SCHED_SCENE_GENERIC", + "PLAYERANIMEVENT_FLINCH_RIGHTLEG", + "CreateClientConVar", + "DListLayout", + "ACT_MP_RELOAD_AIRWALK_SECONDARY_END", + "ACT_CROUCH", + "ACT_VM_PULLBACK_LOW", + "NPC_STATE_IDLE", + "ACT_MP_RELOAD_SWIM_END", + "ACT_MP_ATTACK_CROUCH_GRENADE_SECONDARY", + "COND_HEAR_BULLET_IMPACT", + "ACT_OPEN_DOOR", + "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED_MG", + "ACT_IDLE_ANGRY_PISTOL", + "ACT_DOD_STAND_ZOOM_PSCHRECK", + "COND_CAN_MELEE_ATTACK2", + "ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED", + "engine", + "SF_PHYSBOX_NEVER_PICK_UP", + "ACT_GESTURE_RANGE_ATTACK_ML", + "ACT_DOD_DEPLOY_30CAL", + "CLASS_ZOMBIE", + "ACT_RPG_IDLE_UNLOADED", + "DProperty_Int", + "ACT_IDLE_SHOTGUN_RELAXED", + "RunStringEx", + "ACT_MP_JUMP", + "MOVETYPE_FLYGRAVITY", + "matproxy", + "ACT_VM_MISSRIGHT", + "physenv", + "DColorMixer", + "RenderStereoscopy", + "Schedule", + "ACT_MP_GESTURE_VC_FISTPUMP", + "ACT_RELOAD_PISTOL_LOW", + "SF_NPC_NO_WEAPON_DROP", + "ACT_SLAM_STICKWALL_DRAW", + "ACT_HL2MP_WALK_CROUCH_GRENADE", + "MsgN", + "ACT_DOD_CROUCHWALK_IDLE_PSCHRECK", + "CAP_SQUAD", + "ACT_MP_AIRWALK_SECONDARY", + "ALL_VISIBLE_CONTENTS", + "ColorAlpha", + "ACT_DOD_SPRINT_IDLE_GREASE", + "SNDLVL_STATIC", + "killicon", + "ACT_SIGNAL2", + "DProperty_VectorColor", + "DCollapsibleCategory", + "FCVAR_ARCHIVE_XBOX", + "KEY_PAD_MULTIPLY", + "fingerposer", + "RENDERGROUP_OPAQUE", + "require", + "GetGlobalBool", + "SCHED_COMBAT_WALK", + "ACT_DEEPIDLE2", + "ACT_HL2MP_SWIM_IDLE_PASSIVE", + "SNDLVL_50dB", + "NAV_MESH_FUNC_COST", + "rawequal", + "ACT_MP_GESTURE_VC_THUMBSUP", + "ACT_DOD_RUN_IDLE_30CAL", + "ACT_DRIVE_AIRBOAT", + "ACT_GMOD_SIT_ROLLERCOASTER", + "PrecacheScene", + "SetGlobalBool", + "IsInGame", + "HUD_PRINTNOTIFY", + "ACT_DOD_SPRINT_IDLE_MP40", + "TYPE_SAVE", + "ParticleEffect", + "ACT_RUN_AIM_STEALTH", + "ACT_DOD_RELOAD_PRONE_C96", + "KEY_LEFT", + "EFL_DONTWALKON", + "PATTACH_ABSORIGIN", + "ACT_HL2MP_GESTURE_RELOAD_PHYSGUN", + "ACT_HL2MP_SWIM", + "CHAN_VOICE_BASE", + "ACT_DOD_PRIMARYATTACK_PRONE_GREN_FRAG", + "ACT_HL2MP_WALK_CROUCH_ANGRY", + "ACT_DOD_RUN_ZOOM_PSCHRECK", + "ACT_DOD_PRONEWALK_IDLE_TNT", + "TextEntryLoseFocus", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1", + "IsColor", + "ACT_RANGE_ATTACK2", + "ACT_DOD_WALK_AIM_GREN_FRAG", + "BONE_USED_BY_VERTEX_LOD4", + "CONTENTS_TEAM3", + "ACT_DOD_WALK_AIM_SPADE", + "ACT_DOD_PRIMARYATTACK_PRONE_GREASE", + "PresetEditor", + "FVPHYSICS_PLAYER_HELD", + "SetGlobalEntity", + "ACT_HL2MP_SWIM_KNIFE", + "ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND", + "ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE", + "GetSaveFileDetails", + "ACT_DOD_RELOAD_PRONE_DEPLOYED_MG", + "ACT_RUN_STEALTH", + "coroutine", + "GetGlobalEntity", + "ACT_VM_IDLE_DEPLOYED_EMPTY", + "HITGROUP_LEFTARM", + "ACT_DOD_RELOAD_PRONE_DEPLOYED_MG34", + "TEXFILTER", + "SF_PHYSPROP_PREVENT_PICKUP", + "ACT_HL2MP_WALK_CROUCH", + "DrawToyTown", + "DBubbleContainer", + "EndTooltip", + "MsgC", + "SCHED_FLEE_FROM_BEST_SOUND", + "ACT_MP_CROUCH_SECONDARY", + "ACT_OBJ_UPGRADING", + "ACT_DOD_CROUCH_AIM", + "SF_NPC_ALTCOLLISION", + "ACT_MP_AIRWALK_PRIMARY", + "ACT_DOD_STAND_IDLE_TNT", + "ACT_MP_GRENADE1_DRAW", + "ACT_DOD_RELOAD_PRONE_DEPLOYED_FG42", + "ACT_VM_PULLBACK_HIGH_BAKE", + "ACT_RANGE_ATTACK_AR2", + "SF_NPC_GAG", + "UpdateLoadPanel", + "CHAN_REPLACE", + "ACT_VM_ISHOOT_M203", + "ACT_DOD_RELOAD_PRONE_RIFLEGRENADE", + "FCVAR_SPONLY", + "TRACER_NONE", + "DTooltip", + "KEY_HOME", + "ACT_IDLE_STEALTH_PISTOL", + "CAP_MOVE_SHOOT", + "Task", + "ACT_VM_DRAW_DEPLOYED", + "TYPE_CONVAR", + "ACT_GMOD_TAUNT_DANCE", + "ACT_FLINCH_CHEST", + "ACT_DOD_HS_CROUCH_KNIFE", + "ACT_DOD_PRIMARYATTACK_DEPLOYED_MG", + "JOYSTICK_FIRST", + "ACT_MP_SECONDARY_GRENADE2_IDLE", + "SCHED_SCRIPTED_FACE", + "ACT_DOD_RUN_IDLE_RIFLE", + "ACT_MP_ATTACK_STAND_GRENADE_SECONDARY", + "ACT_IDLE_RIFLE", + "ACT_HL2MP_SWIM_AR2", + "KEY_M", + "ACT_SLAM_TRIPMINE_IDLE", + "ContentIcon", + "ACT_VM_PRIMARYATTACK_DEPLOYED", + "DrawBloom", + "ACT_DOD_CROUCHWALK_ZOOM_RIFLE", + "DAlphaBar", + "MAT_SNOW", + "ACT_VM_IDLE", + "ACT_MP_RELOAD_AIRWALK", + "ACT_DOD_WALK_ZOOM_RIFLE", + "IMAGE_FORMAT_RGB565", + "ACT_DOD_PRONE_ZOOMED", + "ACT_VM_DRYFIRE", + "TEXTUREFLAGS_SINGLECOPY", + "ACT_HL2MP_IDLE_CROUCH_MAGIC", + "ACT_MP_RELOAD_CROUCH_PRIMARY", + "STUDIO_STATIC_LIGHTING", + "ACT_DOD_CROUCH_ZOOM_BOLT", + "ACT_LOOKBACK_LEFT", + "SNDLVL_130dB", + "EFL_KEEP_ON_RECREATE_ENTITIES", + "ACT_MP_ATTACK_CROUCH_GRENADE", + "IN_MOVERIGHT", + "EFL_DIRTY_ABSANGVELOCITY", + "TRACER_RAIL", + "COND_PROVOKED", + "PLAYER_WALK", + "ACT_MP_ATTACK_STAND_GRENADE_MELEE", + "ACT_DOD_HS_CROUCH_BAZOOKA", + "ACT_HL2MP_IDLE_CROUCH_FIST", + "ACT_DOD_STAND_IDLE", + "ACT_DOD_PRIMARYATTACK_GREASE", + "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED_RIFLE", + "ACT_DIEFORWARD", + "FFT_2048", + "ACT_DOD_CROUCHWALK_IDLE_BAZOOKA", + "SNDLVL_90dB", + "DrawMotionBlur", + "DPanel", + "MAT_METAL", + "EF_NOSHADOW", + "ACT_MP_SECONDARY_GRENADE1_IDLE", + "ACT_HL2MP_JUMP_ZOMBIE", + "ACT_VM_UNDEPLOY_8", + "COLLISION_GROUP_NONE", + "ACT_VM_DEPLOYED_IRON_IN", + "ACT_MP_ATTACK_STAND_GRENADE", + "RENDERMODE_TRANSALPHADD", + "ACT_MP_JUMP_MELEE", + "ACT_MP_JUMP_SECONDARY", + "Either", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_CAMERA", + "KEY_W", + "SCHED_COMBAT_PATROL", + "ACT_FLINCH_HEAD", + "ACT_DROP_WEAPON", + "NOTIFY_CLEANUP", + "DCategoryList", + "ACT_SPECIAL_ATTACK1", + "ACT_DOD_PRONEWALK_IDLE_BAZOOKA", + "ACT_HL2MP_WALK_CROUCH_ZOMBIE_01", + "ACT_GAUSS_SPINCYCLE", + "D_HT", + "FL_CONVEYOR", + "ACT_GMOD_TAUNT_MUSCLE", + "TYPE_TEXTURE", + "DLabelURL", + "list", + "DMG_NEVERGIB", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW", + "ACT_DOD_STAND_ZOOM_BAZOOKA", + "ACT_DOD_PRONE_DEPLOY_TOMMY", + "ACT_DOD_RUN_AIM_30CAL", + "ACT_DOD_PRONEWALK_IDLE_BOLT", + "ACT_DOD_RELOAD_PRONE_TOMMY", + "FL_UNBLOCKABLE_BY_PLAYER", + "ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED", + "ACT_SLAM_STICKWALL_ATTACH2", + "ACT_HL2MP_WALK_ZOMBIE_05", + "ACT_DOD_CROUCH_IDLE", + "ACT_MP_MELEE_GRENADE2_IDLE", + "isfunction", + "SetPhysConstraintSystem", + "STENCIL_DECR", + "ACT_VM_DEPLOY", + "KEY_XSTICK2_RIGHT", + "ACT_DOD_RUN_IDLE_MP44", + "CREATERENDERTARGETFLAGS_HDR", + "ACT_BUSY_LEAN_BACK_ENTRY", + "MATERIAL_LINES", + "Particle", + "ACT_DOD_STAND_IDLE_RIFLE", + "SCHED_RUN_FROM_ENEMY", + "TranslateDownloadableName", + "ACT_DOD_RELOAD_DEPLOYED_30CAL", + "ACT_DOD_DEFUSE_TNT", + "kRenderFxPulseFastWider", + "FindMetaTable", + "RENDERGROUP_BOTH", + "ACT_DOD_WALK_AIM_PSCHRECK", + "RandomPairs", + "SNDLVL_NORM", + "ACT_VM_ISHOOT", + "BONE_SCREEN_ALIGN_CYLINDER", + "PLAYERANIMEVENT_FLINCH_LEFTARM", + "player_manager", + "ACT_MP_ATTACK_CROUCH_GRENADE_MELEE", + "ACT_VM_IDLE_4", + "SF_NPC_FADE_CORPSE", + "ACT_DO_NOT_DISTURB", + "ACT_VM_DEPLOYED_IRON_OUT", + "ACT_RANGE_ATTACK_PISTOL_LOW", + "ACT_DOD_HS_IDLE_BAZOOKA", + "NPC", + "ACT_DOD_STAND_IDLE_MG", + "ACT_WALK_CROUCH", + "Weapon", + "FL_OBJECT", + "ACT_MP_AIRWALK_PDA", + "ACT_IDLE_AIM_RELAXED", + "DHorizontalScroller", + "isvector", + "ACT_DOD_WALK_AIM_BAZOOKA", + "KEY_SLASH", + "ACT_RANGE_ATTACK_SMG2", + "COND_REPEATED_DAMAGE", + "ACT_TRANSITION", + "DTab", + "MATERIAL_TRIANGLES", + "ACT_DOD_CROUCH_IDLE_TNT", + "FVPHYSICS_WAS_THROWN", + "DNotify", + "ACT_MP_ATTACK_STAND_PRIMARY", + "ACT_MP_MELEE_GRENADE1_DRAW", + "SCHED_ALERT_FACE", + "ACT_HL2MP_WALK_CROUCH_SLAM", + "KEY_XBUTTON_RIGHT", + "ACT_VM_HITCENTER2", + "ACT_DOD_CROUCHWALK_IDLE_C96", + "ACT_MP_RELOAD_SWIM_LOOP", + "KEY_XBUTTON_X", + "SNDLVL_75dB", + "ACT_DOD_PRIMARYATTACK_PRONE_BOLT", + "ACT_MP_RELOAD_STAND_SECONDARY_LOOP", + "COND_NO_CUSTOM_INTERRUPTS", + "ACT_DOD_IDLE_ZOOMED", + "CUserCmd", + "BOX_BACK", + "ACT_DOD_RUN_IDLE_PISTOL", + "ACT_DOD_PRONE_DEPLOYED", + "FCVAR_NOTIFY", + "KEY_COMMA", + "ACT_DOD_WALK_ZOOMED", + "ACT_RELOAD_PISTOL", + "TYPE_DLIGHT", + "CLASS_MILITARY", + "ACT_MP_DEPLOYED", + "ACT_VM_DEPLOYED_IN", + "OBS_MODE_DEATHCAM", + "Format", + "ACT_DOD_SPRINT_AIM_GREN_STICK", + "CAP_WEAPON_RANGE_ATTACK1", + "ACT_MP_JUMP_LAND_SECONDARY", + "CONTENTS_WATER", + "ACT_OBJ_DISMANTLING", + "EyePos", + "FingerVar", + "PathFollower", + "ACT_DOD_RELOAD_C96", + "IN_BULLRUSH", + "DLabelEditable", + "ACT_HL2MP_GESTURE_RELOAD_SUITCASE", + "CONTENTS_MONSTERCLIP", + "MAT_WARPSHIELD", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_ANGRY", + "ACT_DOD_HS_IDLE_K98", + "KEY_PERIOD", + "ACT_DOD_SPRINT_IDLE_BAR", + "ACT_DOD_CROUCH_AIM_MP44", + "ACT_STEP_RIGHT", + "ACT_RUNTOIDLE", + "TYPE_STRING", + "SCHED_BACK_AWAY_FROM_SAVE_POSITION", + "ACT_VM_MISSLEFT", + "FFT_512", + "ACT_DYINGLOOP", + "ACT_DOD_CROUCH_AIM_BAR", + "frame_blend", + "IMAGE_FORMAT_RGBA16161616F", + "ValidPanel", + "NAV_MESH_WALK", + "RememberCursorPosition", + "ACT_HL2MP_JUMP_AR2", + "MAT_SAND", + "KEY_PAD_7", + "ACT_DOD_CROUCH_IDLE_MP40", + "ACT_IDLE_ON_FIRE", + "KEY_J", + "ACT_DOD_RUN_AIM_RIFLE", + "CLASS_PROTOSNIPER", + "RenderSuperDoF", + "kRenderFxRagdoll", + "CAP_USE_SHOT_REGULATOR", + "ACT_SLAM_TRIPMINE_TO_STICKWALL_ND", + "ACT_HL2MP_GESTURE_RELOAD_MELEE", + "ACT_PLAYER_WALK_FIRE", + "SortedPairsByValue", + "ACT_GESTURE_MELEE_ATTACK1", + "SCHED_VICTORY_DANCE", + "ACT_MP_SWIM_SECONDARY", + "DScrollBarGrip", + "ClearBackgroundImages", + "ACT_GESTURE_TURN_RIGHT90", + "KEY_SEMICOLON", + "CAP_SIMPLE_RADIUS_DAMAGE", + "ACT_DOD_STAND_IDLE_C96", + "ACT_SHIELD_KNOCKBACK", + "ACT_HL2MP_SWIM_IDLE", + "ACT_RUN_RPG_RELAXED", + "ACT_HL2MP_SWIM_IDLE_MELEE", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM", + "ACT_GMOD_GESTURE_POINT", + "ACT_DOD_PRONE_DEPLOY_MG", + "ACT_RELOAD_SHOTGUN_LOW", + "ACT_IDLE_SHOTGUN_AGITATED", + "DModelSelectMulti", + "ACT_VM_IOUT", + "STENCILCOMPARISONFUNCTION_GREATEREQUAL", + "ACT_DOD_RELOAD_GARAND", + "DMenu", + "ACT_WALK_HURT", + "ACT_DI_ALYX_ANTLION", + "ACT_VM_PRIMARYATTACK_DEPLOYED_3", + "SCHED_TAKE_COVER_FROM_ENEMY", + "ACT_MP_ATTACK_SWIM_PREFIRE", + "ACT_DOD_RUN_AIM_GREN_STICK", + "ACT_VM_SPRINT_IDLE", + "ACT_DOD_CROUCHWALK_AIM_GREN_STICK", + "ACT_MP_RELOAD_AIRWALK_SECONDARY_LOOP", + "saverestore", + "MASK_OPAQUE_AND_NPCS", + "ACT_VM_DEPLOY_5", + "ai", + "surface", + "ACT_GESTURE_FLINCH_RIGHTARM", + "ACT_SCRIPT_CUSTOM_MOVE", + "ACT_DOD_PRONE_ZOOM_PSCHRECK", + "ACT_VM_PULLPIN", + "ACT_DOD_RELOAD_PRONE_K43", + "HULL_HUMAN", + "ACT_IDLE_ANGRY_SMG1", + "CAP_NO_HIT_PLAYER", + "ACT_DOD_PRONE_AIM_BAZOOKA", + "ACT_HL2MP_RUN_SCARED", + "ACT_GET_DOWN_CROUCH", + "ACT_RUN_PISTOL", + "ACT_MP_GESTURE_VC_HANDMOUTH_PRIMARY", + "DProperties", + "ACT_DOD_WALK_AIM_MG", + "ACT_SLAM_THROW_THROW_ND", + "ACT_STRAFE_RIGHT", + "COLLISION_GROUP_WORLD", + "ACT_DOD_PRIMARYATTACK_PISTOL", + "NAV_MESH_HAS_ELEVATOR", + "DGrid", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE", + "ACT_DOD_CROUCH_ZOOMED", + "DMG_FALL", + "DImage", + "COND_FLOATING_OFF_GROUND", + "OBS_MODE_FREEZECAM", + "ChangeBackground", + "cookie", + "GetOverlayPanel", + "MAT_GRASS", + "ACT_HL2MP_GESTURE_RELOAD_GRENADE", + "ACT_MP_JUMP_START_PRIMARY", + "constraint", + "DListView_Line", + "ACT_IDLE_PISTOL", + "ACT_DOD_PRIMARYATTACK_PSCHRECK", + "MOUSE_LEFT", + "kRenderFxPulseSlowWide", + "ACT_HL2MP_WALK_CROUCH_REVOLVER", + "ACT_ZOMBIE_LEAP_START", + "ACT_VM_IDLE_TO_LOWERED", + "KEY_B", + "MOVETYPE_WALK", + "ACT_RIDE_MANNED_GUN", + "ACT_MP_JUMP_PRIMARY", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN", + "ACT_DOD_STAND_AIM_SPADE", + "SortedPairs", + "ACT_CROUCHIDLE_AGITATED", + "ACT_SMG2_RELOAD2", + "KEY_XBUTTON_LEFT", + "NAV_MESH_NO_MERGE", + "NORTH_EAST", + "ACT_CROUCHING_GRENADEIDLE", + "ACT_VM_PRIMARYATTACK_3", + "ACT_HANDGRENADE_THROW2", + "ACT_DOD_RELOAD_PRONE_M1CARBINE", + "ACT_GESTURE_RANGE_ATTACK_AR2", + "ACT_MP_ATTACK_STAND_GRENADE_BUILDING", + "COND_SEE_HATE", + "ACT_MP_ATTACK_SWIM_BUILDING", + "ACT_DOD_STAND_AIM_30CAL", + "BLEND_ONE_MINUS_SRC_COLOR", + "ACT_IDLE_AIM_RIFLE_STIMULATED", + "COND_CAN_RANGE_ATTACK2", + "ACT_DOD_RELOAD_CROUCH_BOLT", + "DeriveGamemode", + "ACT_HL2MP_JUMP_MELEE2", + "ACT_MP_GESTURE_VC_FISTPUMP_PDA", + "FVPHYSICS_DMG_SLICE", + "ACT_SIGNAL3", + "ACT_SHIELD_UP", + "ACT_GESTURE_RANGE_ATTACK_AR2_GRENADE", + "ACT_HL2MP_SWIM_IDLE_PHYSGUN", + "ACT_DOD_STAND_IDLE_GREASE", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE", + "TYPE_LIGHTUSERDATA", + "ACT_DOD_CROUCH_IDLE_MP44", + "ACT_VM_DFIREMODE", + "setmetatable", + "SNDLVL_35dB", + "ACT_ZOMBIE_LEAPING", + "ACT_DOD_RELOAD_PRONE", + "ACT_SHIELD_UP_IDLE", + "ACT_MP_ATTACK_CROUCH_PRIMARY_DEPLOYED", + "KEY_I", + "ACT_HL2MP_SIT_RPG", + "ACT_DOD_RELOAD_TOMMY", + "getmetatable", + "KEY_N", + "ACT_SLAM_THROW_ND_DRAW", + "ACT_HL2MP_WALK_CROUCH_ZOMBIE", + "ACT_HL2MP_IDLE_ZOMBIE", + "rawset", + "CNewParticleEffect", + "ACT_MP_GESTURE_VC_FINGERPOINT_MELEE", + "ACT_HL2MP_RUN_FAST", + "EFL_NO_GAME_PHYSICS_SIMULATION", + "ACT_SHIELD_DOWN", + "JOYSTICK_LAST_POV_BUTTON", + "ACT_DOD_RUN_AIM_MP44", + "SNDLVL_85dB", + "SURF_NOLIGHT", + "GetDefaultLoadingHTML", + "ACT_VM_SPRINT_LEAVE", + "ACT_DOD_PRIMARYATTACK_PRONE_BAR", + "SCHED_DISARM_WEAPON", + "KEY_XBUTTON_STICK2", + "os", + "ACT_DOD_PRONE_AIM_GREN_STICK", + "ACT_VM_RELEASE", + "ACT_HL2MP_WALK_SLAM", + "Derma_Anim", + "ACT_HL2MP_SWIM_IDLE_GRENADE", + "ACT_VM_IDLE_3", + "ENT_ANIM", + "ACT_MP_RELOAD_CROUCH", + "construct", + "ACT_DOD_RUN_IDLE_MG", + "KEY_V", + "ACT_DOD_SPRINT_IDLE_TNT", + "EFL_KILLME", + "ACT_VM_LOWERED_TO_IDLE", + "COND_HEAR_DANGER", + "ACT_MP_GESTURE_VC_THUMBSUP_PDA", + "ACT_MP_STAND_MELEE", + "COND_NPC_UNFREEZE", + "CLuaParticle", + "ACT_MP_GESTURE_VC_FISTPUMP_PRIMARY", + "ACT_HL2MP_IDLE_CROUCH_SUITCASE", + "ACT_SMALL_FLINCH", + "TEXTUREFLAGS_ALL_MIPS", + "EyeVector", + "ACT_ROLL_RIGHT", + "ACT_PHYSCANNON_UPGRADE", + "ACT_MP_MELEE_GRENADE1_ATTACK", + "ACT_DI_ALYX_ZOMBIE_MELEE", + "SendUserMessage", + "ACT_SHOTGUN_PUMP", + "FL_SWIM", + "Tool", + "PrecacheSentenceFile", + "ACT_DOD_CROUCHWALK_IDLE_TOMMY", + "ACT_DIE_GUTSHOT", + "ACT_RPG_HOLSTER_UNLOADED", + "ACT_RUN_AIM_SHOTGUN", + "ACT_RANGE_ATTACK2_LOW", + "DListBoxItem", + "ACT_DOD_STAND_AIM", + "ACT_DOD_RUN_ZOOM_BAZOOKA", + "CNavLadder", + "ACT_READINESS_AGITATED_TO_STIMULATED", + "ACT_READINESS_STIMULATED_TO_RELAXED", + "ACT_DOD_HS_CROUCH", + "ACT_BUSY_SIT_GROUND_EXIT", + "ACT_HL2MP_WALK_CROUCH_MELEE2", + "ACT_VM_UNDEPLOY_5", + "EFL_NO_DAMAGE_FORCES", + "ACT_MP_RELOAD_STAND_PRIMARY", + "KEY_PAD_3", + "ACT_DOD_WALK_IDLE_TOMMY", + "COLLISION_GROUP_NPC", + "ispanel", + "ACT_RELOAD_LOW", + "KEY_PAD_DECIMAL", + "ACT_DOD_RELOAD_CROUCH_BAZOOKA", + "DTree_Node", + "ACT_RPG_FIDGET_UNLOADED", + "AchievementIcon", + "ACT_BARNACLE_HIT", + "ACT_DOD_RUN_AIM_MP40", + "ACT_HL2MP_GESTURE_RELOAD_CAMERA", + "gmod", + "IconEditor", + "MatSelect", + "ACT_VM_IDLE_DEPLOYED_2", + "SNDLVL_105dB", + "ACT_DOD_CROUCH_AIM_MP40", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN", + "DLabel", + "ACT_HL2MP_IDLE_CROUCH_SMG1", + "ACT_RUN", + "ACT_VM_PULLBACK", + "FCVAR_PRINTABLEONLY", + "ACT_SIGNAL1", + "KEY_DELETE", + "EmitSound", + "CHAN_VOICE", + "DListView", + "STUDIO_GENERATE_STATS", + "ACT_SWIM", + "ACT_DOD_RELOAD_PISTOL", + "COND_ENEMY_FACING_ME", + "TYPE_FUNCTION", + "ACT_DOD_PRIMARYATTACK_DEPLOYED_30CAL", + "SCHED_PATROL_RUN", + "ACT_MP_ATTACK_STAND_SECONDARY", + "ACT_VM_UNDEPLOY_6", + "ACT_VM_UNDEPLOY_3", + "COND_HAVE_TARGET_LOS", + "EFL_TOUCHING_FLUID", + "ACT_DOD_RUN_AIM_C96", + "ACT_GESTURE_RANGE_ATTACK_TRIPWIRE", + "ACT_HL2MP_SWIM_IDLE_REVOLVER", + "ACT_HL2MP_IDLE_CROSSBOW", + "ACT_DOD_PRONE_DEPLOY_30CAL", + "ACT_DOD_PRONEWALK_IDLE_PISTOL", + "ACT_OVERLAY_SHIELD_KNOCKBACK", + "util", + "ACT_DOD_CROUCH_IDLE_BAR", + "ACT_HL2MP_SWIM_SUITCASE", + "ACT_PHYSCANNON_ANIMATE_PRE", + "IncludeCS", + "package", + "ACT_COVER_LOW", + "NumDownloadables", + "BOX_RIGHT", + "ACT_MP_ATTACK_SWIM_GRENADE_PRIMARY", + "ACT_MP_RELOAD_CROUCH_PRIMARY_END", + "ACT_OBJ_STARTUP", + "FVPHYSICS_HEAVY_OBJECT", + "ACT_HL2MP_JUMP_PHYSGUN", + "CurTime", + "WEAPON_PROFICIENCY_VERY_GOOD", + "COND_HEALTH_ITEM_AVAILABLE", + "ACT_IDLE_AGITATED", + "OBS_MODE_NONE", + "GetRenderTargetEx", + "RENDERGROUP_OPAQUE_HUGE", + "WEAPON_PROFICIENCY_AVERAGE", + "ACT_HL2MP_WALK_ANGRY", + "COLLISION_GROUP_DEBRIS_TRIGGER", + "USE_TOGGLE", + "TEXT_ALIGN_RIGHT", + "USE_SET", + "ACT_MP_GRENADE1_IDLE", + "TYPE_MATERIAL", + "DOF_Start", + "OBS_MODE_CHASE", + "CLASS_VORTIGAUNT", + "TYPE_VIDEO", + "TYPE_VECTOR", + "TYPE_USERMSG", + "FCVAR_ARCHIVE", + "ACT_VM_RECOIL3", + "KEY_H", + "TYPE_USERCMD", + "TYPE_THREAD", + "ChangeTooltip", + "ACT_DOD_SPRINT_IDLE_MP44", + "ACT_HL2MP_WALK_MELEE", + "SlideBar", + "ACT_VM_MISSCENTER", + "NextBot", + "ACT_MP_DOUBLEJUMP", + "DTextEntry", + "JS_Utility", + "TYPE_SOUNDHANDLE", + "DScrollPanel", + "ACT_VM_DEPLOY_3", + "ACT_MP_JUMP_START_SECONDARY", + "TEXTUREFLAGS_UNUSED_80000000", + "ACT_GESTURE_RELOAD", + "ACT_MP_GESTURE_VC_NODNO_PDA", + "MOVECOLLIDE_FLY_CUSTOM", + "ACT_HL2MP_IDLE_AR2", + "TYPE_SOUND", + "TYPE_SCRIPTEDVEHICLE", + "TYPE_RESTORE", + "ACT_MP_GESTURE_VC_HANDMOUTH", + "TYPE_RECIPIENTFILTER", + "TYPE_PROJECTEDTEXTURE", + "ACT_MP_JUMP_LAND", + "ACT_MELEE_ATTACK2", + "ACT_VM_DRYFIRE_LEFT", + "IRestore", + "ACT_DOD_CROUCHWALK_ZOOMED", + "ACT_VM_IDLE_7", + "CONTENTS_DETAIL", + "TYPE_PIXELVISHANDLE", + "TYPE_PHYSOBJ", + "SCHED_COMBAT_STAND", + "IMesh", + "ACT_DOD_CROUCH_AIM_MG", + "MAT_TILE", + "ACT_STEP_FORE", + "JOYSTICK_LAST_BUTTON", + "TYPE_PATH", + "ACT_DOD_CROUCH_AIM_GREN_STICK", + "TYPE_PARTICLEEMITTER", + "ACT_FIRE_LOOP", + "ACT_GET_UP_STAND", + "TYPE_PARTICLE", + "TYPE_PANEL", + "TYPE_NUMBER", + "TYPE_NIL", + "EFL_SERVER_ONLY", + "gameevent", + "ACT_SIGNAL_RIGHT", + "ACT_SLAM_THROW_TO_STICKWALL_ND", + "ACT_DOD_RELOAD_CROUCH_C96", + "ACT_DOD_RELOAD_BAZOOKA", + "TYPE_NAVAREA", + "ACT_FLINCH_LEFTLEG", + "TYPE_MOVEDATA", + "ACT_RUN_CROUCH", + "ACT_SWIM_IDLE", + "TYPE_MATRIX", + "USE_ON", + "ACT_MP_PRIMARY_GRENADE1_DRAW", + "ACT_VM_IDLE_SILENCED", + "ACT_RPG_DRAW_UNLOADED", + "ACT_VM_DEPLOY_2", + "ACT_CLIMB_DOWN", + "TYPE_LOCOMOTION", + "ModelImage", + "FSOLID_FORCE_WORLD_ALIGNED", + "IN_WALK", + "ACT_DOD_CROUCHWALK_AIM_GREN_FRAG", + "ACT_BUSY_SIT_GROUND", + "TYPE_IMESH", + "ACT_GESTURE_FLINCH_STOMACH", + "ACT_CROUCHIDLE_AIM_STIMULATED", + "ACT_DOD_RELOAD_PRONE_PSCHRECK", + "TYPE_ENTITY", + "Model", + "TEXTUREFLAGS_POINTSAMPLE", + "IMAGE_FORMAT_BGRA8888", + "TYPE_DAMAGEINFO", + "ACT_HL2MP_SWIM_IDLE_KNIFE", + "ACT_DOD_CROUCH_AIM_SPADE", + "ACT_DOD_STAND_AIM_GREN_FRAG", + "ClientsideModel", + "TYPE_COUNT", + "RemoveTooltip", + "COND_BEHIND_ENEMY", + "TYPE_ANGLE", + "NAV_MESH_RUN", + "KEY_F9", + "CEffectData", + "TRANSMIT_NEVER", + "TRANSMIT_ALWAYS", + "ACT_MP_ATTACK_SWIM_GRENADE_BUILDING", + "SCHED_AMBUSH", + "MATERIAL_TRIANGLE_STRIP", + "ai_task", + "STUDIO_WIREFRAME_VCOLLIDE", + "EF_NORECEIVESHADOW", + "ACT_HL2MP_RUN_MELEE", + "TRACER_LINE", + "net", + "AngleRand", + "COND_TOO_FAR_TO_ATTACK", + "ACT_GMOD_TAUNT_ROBOT", + "ACT_SLAM_DETONATOR_THROW_DRAW", + "ACT_RANGE_ATTACK_SHOTGUN", + "ACT_SLAM_DETONATOR_DRAW", + "ACT_MP_ATTACK_SWIM_GRENADE_SECONDARY", + "TEXTUREFLAGS_UNUSED_10000000", + "TEXTUREFLAGS_SSBUMP", + "TEXTUREFLAGS_VERTEXTEXTURE", + "ACT_READINESS_RELAXED_TO_STIMULATED_WALK", + "TEXTUREFLAGS_CLAMPU", + "TEXTUREFLAGS_UNUSED_01000000", + "TEXTUREFLAGS_NODEPTHBUFFER", + "CONTENTS_CURRENT_0", + "ACT_HL2MP_WALK_CROUCH_RPG", + "DSlider", + "util.worldpicker", + "ACT_HL2MP_SWIM_IDLE_SMG1", + "ACT_HL2MP_WALK_GRENADE", + "ContextBase", + "ACT_MP_RELOAD_STAND_SECONDARY", + "ACT_IDLETORUN", + "NORTH", + "IN_ATTACK", + "ACT_OVERLAY_GRENADEIDLE", + "COND_BETTER_WEAPON_AVAILABLE", + "TEXTUREFLAGS_IMMEDIATE_CLEANUP", + "TEXTUREFLAGS_NODEBUGOVERRIDE", + "KEY_F11", + "TEXTUREFLAGS_RENDERTARGET", + "ACT_IDLE_ANGRY_MELEE", + "TEXTUREFLAGS_EIGHTBITALPHA", + "TEXTUREFLAGS_ONEBITALPHA", + "SuppressHostEvents", + "IN_USE", + "ACT_DOD_RELOAD_BAR", + "NAV_MESH_NO_JUMP", + "ACT_HL2MP_IDLE_CROUCH_MELEE2", + "BLEND_DST_COLOR", + "CONTENTS_MOVEABLE", + "TEXTUREFLAGS_NOMIP", + "ACT_VM_RELOAD_INSERT_PULL", + "render", + "ACT_MP_ATTACK_CROUCH_MELEE", + "ACT_HL2MP_SWIM_IDLE_FIST", + "ACT_DOD_PRONEWALK_IDLE_30CAL", + "TEXTUREFLAGS_PWL_CORRECTED", + "TEXTUREFLAGS_HINT_DXT5", + "ACT_DOD_CROUCHWALK_AIM_PISTOL", + "TEXTUREFLAGS_ANISOTROPIC", + "TEXTUREFLAGS_CLAMPT", + "TEXTUREFLAGS_CLAMPS", + "navmesh", + "TEXT_ALIGN_TOP", + "TEXTUREFLAGS_TRILINEAR", + "TEXT_ALIGN_LEFT", + "ACT_GESTURE_RELOAD_PISTOL", + "SCHED_RUN_FROM_ENEMY_MOB", + "ACT_90_LEFT", + "TEAM_SPECTATOR", + "ENT_AI", + "TEAM_UNASSIGNED", + "TEAM_CONNECTING", + "IsUselessModel", + "BLEND_SRC_ALPHA", + "COND_ENEMY_DEAD", + "ACT_MP_RELOAD_CROUCH_SECONDARY_LOOP", + "MATERIAL_CULLMODE_CCW", + "COND_NO_WEAPON", + "SURF_NOSHADOWS", + "COND_HAVE_ENEMY_LOS", + "SURF_BUMPLIGHT", + "FCVAR_NONE", + "SURF_SKIP", + "SURF_HINT", + "HULL_TINY", + "ACT_SLAM_TRIPMINE_ATTACH", + "ControlPanel", + "ACT_STAND", + "SURF_TRIGGER", + "LocalToWorld", + "drive", + "ACT_DOD_STAND_AIM_BAR", + "GetMapList", + "ACT_MP_ATTACK_STAND_STARTFIRE", + "SURF_NOPORTAL", + "ACT_MP_SWIM", + "ACT_MP_GESTURE_VC_FINGERPOINT_PDA", + "SURF_TRANS", + "ACT_GESTURE_RANGE_ATTACK_HMG1", + "GESTURE_SLOT_ATTACK_AND_RELOAD", + "SURF_SKY", + "ACT_ARM", + "SURF_LIGHT", + "DMG_VEHICLE", + "ACT_DOD_SPRINT_IDLE_C96", + "FL_WATERJUMP", + "STUDIO_SSAODEPTHTEXTURE", + "ACT_MP_SPRINT", + "STUDIO_NOSHADOWS", + "ACT_RELOAD_FINISH", + "STUDIO_ITEM_BLINK", + "ACT_DOD_RUN_IDLE_TNT", + "ACT_VM_RECOIL2", + "CLASS_PLAYER", + "SCHED_WAKE_ANGRY", + "STUDIO_VIEWXFORMATTACHMENTS", + "ACT_CROSSBOW_DRAW_UNLOADED", + "ACT_MP_GESTURE_VC_FISTPUMP_BUILDING", + "STUDIO_RENDER", + "ACT_MP_CROUCHWALK", + "bit", + "ACT_LOOKBACK_RIGHT", + "STEPSOUNDTIME_WATER_FOOT", + "CAP_INNATE_MELEE_ATTACK1", + "KEY_F", + "STEPSOUNDTIME_WATER_KNEE", + "KEY_1", + "ACT_MP_GESTURE_VC_NODYES_PDA", + "STEPSOUNDTIME_ON_LADDER", + "STEPSOUNDTIME_NORMAL", + "STENCILOPERATION_DECR", + "STENCILOPERATION_INCR", + "STENCILOPERATION_INVERT", + "STENCILOPERATION_DECRSAT", + "ACT_SLAM_DETONATOR_DETONATE", + "ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE", + "STENCILOPERATION_INCRSAT", + "MOVETYPE_OBSERVER", + "ACT_WALK_AIM_RIFLE", + "ACT_DOD_SECONDARYATTACK_PRONE_RIFLE", + "STENCILOPERATION_ZERO", + "ACT_MP_ATTACK_SWIM_PRIMARY", + "STENCILOPERATION_KEEP", + "ACT_DOD_STAND_IDLE_PSCHRECK", + "jit", + "FSOLID_NOT_STANDABLE", + "ACT_MP_RELOAD_SWIM_PRIMARY_LOOP", + "CLASS_PLAYER_ALLY", + "ACT_VM_IDLE_EMPTY_LEFT", + "STENCILCOMPARISONFUNCTION_NOTEQUAL", + "STENCILCOMPARISONFUNCTION_GREATER", + "STENCILCOMPARISONFUNCTION_LESSEQUAL", + "ACT_DOD_CROUCH_AIM_BAZOOKA", + "Derma_Query", + "STENCILCOMPARISONFUNCTION_EQUAL", + "ACT_DOD_CROUCHWALK_IDLE_BOLT", + "EF_DIMLIGHT", + "STENCILCOMPARISONFUNCTION_NEVER", + "STENCIL_INCR", + "STENCIL_INVERT", + "ACT_HL2MP_SWIM_IDLE_ANGRY", + "NODOCK", + "STENCIL_DECRSAT", + "ControlPresets", + "STENCIL_INCRSAT", + "STENCIL_REPLACE", + "KEY_XSTICK2_DOWN", + "PLAYERANIMEVENT_ATTACK_SECONDARY", + "STENCIL_ZERO", + "ACT_GESTURE_RANGE_ATTACK1", + "ACT_GESTURE_FLINCH_CHEST", + "STENCIL_ALWAYS", + "STENCIL_GREATEREQUAL", + "STENCIL_NOTEQUAL", + "GetConVar_Internal", + "DColorButton", + "ACT_IDLE_CARRY", + "FCVAR_CLIENTCMD_CAN_EXECUTE", + "MAT_GRATE", + "ACT_MP_WALK_PRIMARY", + "STENCIL_LESSEQUAL", + "STENCIL_EQUAL", + "ACT_MP_JUMP_BUILDING", + "HULL_WIDE_HUMAN", + "STENCIL_NEVER", + "ISave", + "CLASS_COMBINE", + "SOLID_CUSTOM", + "ACT_DOD_RUN_IDLE", + "ACT_GESTURE_RELOAD_SHOTGUN", + "ACT_SLAM_STICKWALL_ND_DRAW", + "ACT_SHOTGUN_RELOAD_START", + "ACT_HL2MP_IDLE_CROUCH_GRENADE", + "Mesh", + "ACT_MP_RELOAD_SWIM_SECONDARY", + "ACT_MP_JUMP_LAND_PDA", + "ACT_DOD_SECONDARYATTACK_MP40", + "SOLID_BSP", + "ACT_DOD_SPRINT_IDLE_BAZOOKA", + "ACT_DOD_PRONEWALK_IDLE_RIFLE", + "SF_NPC_LONG_RANGE", + "ACT_VM_UNDEPLOY_7", + "ACT_VM_RELOAD_INSERT", + "PLAYER_JUMP", + "SNDLVL_150dB", + "NAV_MESH_STAIRS", + "SNDLVL_GUNFIRE", + "SCHED_WAIT_FOR_SCRIPT", + "ACT_BUSY_QUEUE", + "ACT_RANGE_ATTACK1_LOW", + "HSVToColor", + "EF_ITEM_BLINK", + "ACT_VM_PRIMARYATTACK_DEPLOYED_7", + "SNDLVL_120dB", + "PrintTable", + "ACT_HL2MP_GESTURE_RELOAD_KNIFE", + "ACT_HL2MP_SWIM_IDLE_AR2", + "DNumberScratch", + "SNDLVL_110dB", + "ACT_MP_STAND_SECONDARY", + "SNDLVL_100dB", + "DMG_SONIC", + "ACT_SLAM_THROW_DRAW", + "ACT_MP_SWIM_MELEE", + "ACT_DOD_ZOOMLOAD_PSCHRECK", + "mesh", + "SCHED_RANGE_ATTACK2", + "ACT_MP_ATTACK_AIRWALK_GRENADE_SECONDARY", + "SNDLVL_TALKING", + "KEY_D", + "ACT_OBJ_IDLE", + "SNDLVL_80dB", + "EFL_DORMANT", + "ACT_DOD_SECONDARYATTACK_PRONE_BOLT", + "CLASS_BULLSEYE", + "DMG_RADIATION", + "SNDLVL_65dB", + "ACT_MP_RELOAD_CROUCH_SECONDARY", + "Entity", + "ACT_MP_PRIMARY_GRENADE2_DRAW", + "TEXTUREFLAGS_NOLOD", + "SNDLVL_55dB", + "ACT_BUSY_SIT_CHAIR_EXIT", + "KEY_RBRACKET", + "SNDLVL_40dB", + "MASK_BLOCKLOS_AND_NPCS", + "SNDLVL_30dB", + "ACT_HL2MP_WALK_CROUCH_SCARED", + "ACT_DOD_RUN_IDLE_BAR", + "ACT_DOD_PRONE_AIM_BOLT", + "DShape", + "FCVAR_USERINFO", + "ACT_MP_RELOAD_AIRWALK_END", + "CONTENTS_AREAPORTAL", + "SNDLVL_25dB", + "team", + "ACT_DOD_HS_CROUCH_K98", + "ACT_GMOD_TAUNT_PERSISTENCE", + "DExpandButton", + "COND_LOW_PRIMARY_AMMO", + "RealTime", + "ACT_VM_IDLE_M203", + "ACT_RELOAD_SHOTGUN", + "SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL", + "ACT_DOD_WALK_AIM_TOMMY", + "ACT_HL2MP_WALK_CAMERA", + "MOVETYPE_FLY", + "SND_SHOULDPAUSE", + "ACT_SHOTGUN_RELOAD_FINISH", + "ACT_MP_ATTACK_STAND_PRIMARYFIRE_DEPLOYED", + "ACT_SHIPLADDER_DOWN", + "ACT_MP_GESTURE_FLINCH_PRIMARY", + "SND_DELAY", + "SOUTH", + "ACT_DOD_CROUCH_AIM_GREASE", + "SND_STOP", + "ACT_CROSSBOW_HOLSTER_UNLOADED", + "ACT_VM_IDLE_DEPLOYED_7", + "ACT_DOD_SPRINT_AIM_GREN_FRAG", + "ACT_DOD_HS_IDLE_TOMMY", + "SND_CHANGE_PITCH", + "ACT_GESTURE_RANGE_ATTACK_PISTOL", + "SND_CHANGE_VOL", + "SND_NOFLAGS", + "SIM_GLOBAL_FORCE", + "CAP_INNATE_RANGE_ATTACK2", + "ACT_DOD_PRIMARYATTACK_PRONE_30CAL", + "ACT_HL2MP_WALK_PHYSGUN", + "ACT_HL2MP_WALK_CROUCH_PHYSGUN", + "ACT_DOD_STAND_ZOOM_BOLT", + "ACT_MP_GESTURE_VC_NODNO_PRIMARY", + "ACT_DOD_RUN_AIM_BOLT", + "SIM_LOCAL_ACCELERATION", + "SIM_NOTHING", + "ACT_DOD_SECONDARYATTACK_CROUCH_MP40", + "ACT_DOD_PRONE_ZOOM_RIFLE", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL", + "KEY_XBUTTON_LEFT_SHOULDER", + "effects", + "UnPredictedCurTime", + "ACT_HL2MP_IDLE_CROUCH_ZOMBIE", + "SCHED_FLINCH_PHYSICS", + "SF_PHYSBOX_MOTIONDISABLED", + "ACT_DOD_WALK_AIM_MP44", + "PLAYERANIMEVENT_FLINCH_RIGHTARM", + "EyeAngles", + "SF_NPC_WAIT_TILL_SEEN", + "ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN", + "SF_NPC_WAIT_FOR_SCRIPT", + "SF_NPC_TEMPLATE", + "SF_NPC_START_EFFICIENT", + "BLOOD_COLOR_MECH", + "SOLID_NONE", + "gcinfo", + "SF_NPC_FALL_TO_GROUND", + "concommand", + "SF_NPC_DROP_HEALTHKIT", + "BLOOD_COLOR_ZOMBIE", + "PLAYER_IN_VEHICLE", + "SF_FLOOR_TURRET_CITIZEN", + "Derma_Hook", + "SF_CITIZEN_USE_RENDER_BOUNDS", + "SF_CITIZEN_RANDOM_HEAD_MALE", + "DNumPad", + "ACT_RUN_AIM_RIFLE_STIMULATED", + "ACT_DOD_PRONE_AIM_PSCHRECK", + "FL_DONTTOUCH", + "HULL_MEDIUM_TALL", + "DMG_PARALYZE", + "ACT_MP_RELOAD_STAND_END", + "system", + "SF_CITIZEN_IGNORE_SEMAPHORE", + "SF_CITIZEN_FOLLOW", + "KEY_SPACE", + "SF_CITIZEN_AMMORESUPPLIER", + "FL_INRAIN", + "KEY_XBUTTON_UP", + "URLLabel", + "ACT_WALK_PISTOL", + "KEY_PAGEDOWN", + "STUDIO_DRAWTRANSLUCENTSUBMODELS", + "SCHED_WAIT_FOR_SPEAK_FINISH", + "ACT_IDLE_RPG", + "SNDLVL_140dB", + "SCHED_TARGET_FACE", + "MAT_ANTLION", + "IMAGE_FORMAT_RGBA8888", + "ACT_VM_IDLE_2", + "ACT_VM_THROW", + "SCHED_TARGET_CHASE", + "SCHED_TAKE_COVER_FROM_BEST_SOUND", + "ACT_DOD_SPRINT_IDLE_PISTOL", + "ACT_SHIPLADDER_UP", + "SCHED_SWITCH_TO_PENDING_WEAPON", + "SCHED_STANDOFF", + "SCHED_SPECIAL_ATTACK2", + "SCHED_SPECIAL_ATTACK1", + "SCHED_SMALL_FLINCH", + "SCHED_SLEEP", + "SCHED_SHOOT_ENEMY_COVER", + "ACT_DOD_PRIMARYATTACK_BAZOOKA", + "SCHED_SCRIPTED_WALK", + "SCHED_SCRIPTED_WAIT", + "SCHED_SCRIPTED_RUN", + "SCHED_SCRIPTED_CUSTOM_MOVE", + "COND_NO_HEAR_DANGER", + "SCHED_RUN_RANDOM", + "SCHED_RUN_FROM_ENEMY_FALLBACK", + "ACT_DOD_PRONE_AIM_C96", + "KEY_LCONTROL", + "MATERIAL_FOG_LINEAR_BELOW_FOG_Z", + "ACT_DOD_PRIMARYATTACK_CROUCH_KNIFE", + "SCHED_RANGE_ATTACK1", + "GESTURE_SLOT_CUSTOM", + "MAT_FOLIAGE", + "ACT_DOD_RELOAD_PRONE_RIFLE", + "DListView_DraggerBar", + "SURF_NODECALS", + "SCHED_NONE", + "kRenderFxSpotlight", + "ACT_DOD_RELOAD_MP40", + "RENDERMODE_TRANSTEXTURE", + "ACT_VM_UNUSABLE", + "ACT_WALK_RPG_RELAXED", + "SCHED_NEW_WEAPON", + "ACT_GESTURE_TURN_LEFT45", + "ACT_SMG2_IDLE2", + "SCHED_MOVE_AWAY_FROM_ENEMY", + "SCHED_MOVE_AWAY_FAIL", + "ACT_DEPLOY", + "next", + "ACT_OBJ_DETERIORATING", + "SCHED_MOVE_AWAY_END", + "IN_WEAPON1", + "ACT_DOD_CROUCHWALK_AIM_BAZOOKA", + "ACT_SMG2_DRAW2", + "VectorRand", + "cvars", + "SCHED_MOVE_AWAY", + "DMenuOptionCVar", + "SCHED_MELEE_ATTACK2", + "KEY_T", + "COND_WEAPON_PLAYER_IN_SPREAD", + "SCHED_INVESTIGATE_SOUND", + "ACT_MP_RELOAD_STAND", + "utf8", + "CONTENTS_GRATE", + "MATERIAL_QUADS", + "SCHED_INTERACTION_MOVE_TO_PARTNER", + "IMaterial", + "ACT_DOD_CROUCHWALK_AIM_PSCHRECK", + "ACT_SLAM_STICKWALL_TO_THROW_ND", + "SCHED_IDLE_WANDER", + "SCHED_IDLE_WALK", + "SCHED_IDLE_STAND", + "ACT_MP_JUMP_FLOAT_PRIMARY", + "ACT_DOD_CROUCH_AIM_C96", + "ACT_GESTURE_RANGE_ATTACK_PISTOL_LOW", + "IsFriendEntityName", + "EFFECT", + "DProperty_Combo", + "KEY_CAPSLOCKTOGGLE", + "SCHED_FORCED_GO_RUN", + "select", + "SCHED_FORCED_GO", + "ACT_DOD_SECONDARYATTACK_TOMMY", + "RestoreCursorPosition", + "SF_PHYSPROP_MOTIONDISABLED", + "SCHED_FEAR_FACE", + "ACT_SLAM_DETONATOR_HOLSTER", + "ACT_DOD_RELOAD_RIFLE", + "FVPHYSICS_CONSTRAINT_STATIC", + "SCHED_FALL_TO_GROUND", + "ACT_MP_ATTACK_CROUCH_MELEE_SECONDARY", + "DMG_NERVEGAS", + "SCHED_FAIL_NOSTOP", + "SCHED_FAIL_ESTABLISH_LINE_OF_FIRE", + "SCHED_FAIL", + "SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK", + "ACT_HL2MP_WALK_CROUCH_SHOTGUN", + "ACT_VM_IOUT_M203", + "ACT_RUN_RIFLE", + "ACT_HL2MP_RUN_PROTECTED", + "ACT_PLAYER_CROUCH_WALK_FIRE", + "SCHED_DROPSHIP_DUSTOFF", + "SCHED_DIE_RAGDOLL", + "IN_RIGHT", + "SCHED_DIE", + "ACT_MP_ATTACK_CROUCH_SECONDARY", + "SCHED_COWER", + "MASK_PLAYERSOLID", + "ACT_SLAM_THROW_TO_STICKWALL", + "SCHED_COMBAT_FACE", + "COLLISION_GROUP_PUSHAWAY", + "DEntityProperties", + "SCHED_CHASE_ENEMY", + "ToggleFavourite", + "SCHED_BACK_AWAY_FROM_ENEMY", + "HUD_PRINTCENTER", + "ACT_MP_STAND_PDA", + "ACT_DOD_RELOAD_DEPLOYED_FG42", + "SCHED_ARM_WEAPON", + "DMenuBar", + "ACT_HL2MP_IDLE_SHOTGUN", + "SF_CITIZEN_RANDOM_HEAD_FEMALE", + "ACT_MP_GESTURE_VC_THUMBSUP_BUILDING", + "FSOLID_ROOT_PARENT_ALIGNED", + "SCHED_ALERT_SCAN", + "SCHED_ALERT_REACT_TO_COMBAT_SOUND", + "SCHED_ALERT_FACE_BESTSOUND", + "SCHED_AISCRIPT", + "ACT_HL2MP_WALK_SUITCASE", + "ACT_VM_UNDEPLOY_1", + "ACT_VM_ISHOOTDRY", + "ACT_DOD_PRONE_ZOOM_FORWARD_BOLT", + "ACT_RANGE_ATTACK1", + "ACT_VM_UNUSABLE_TO_USABLE", + "LAST_SHARED_SCHEDULE", + "IMAGE_FORMAT_RGB888", + "RT_SIZE_OFFSCREEN", + "FrameTime", + "RT_SIZE_FULL_FRAME_BUFFER", + "COND_HEAR_COMBAT", + "RT_SIZE_HDR", + "ACT_DOD_CROUCHWALK_AIM_C96", + "RT_SIZE_PICMIP", + "ACT_MP_GESTURE_FLINCH_RIGHTLEG", + "RT_SIZE_DEFAULT", + "PLAYER_RELOAD", + "DIconLayout", + "RT_SIZE_NO_CHANGE", + "COND_HEAR_PLAYER", + "ACT_VM_CRAWL_M203", + "RENDERMODE_WORLDGLOW", + "RENDERMODE_TRANSADDFRAMEBLEND", + "MATERIAL_RT_DEPTH_SHARED", + "COND_HEAR_THUMPER", + "CT_DEFAULT", + "RENDERMODE_TRANSADD", + "ACT_RANGE_AIM_AR2_LOW", + "ACT_VM_PRIMARYATTACK_DEPLOYED_4", + "RENDERMODE_TRANSALPHA", + "RENDERMODE_GLOW", + "ACT_HL2MP_IDLE_CROUCH_PASSIVE", + "SCHED_NEW_WEAPON_CHEAT", + "CAP_MOVE_SWIM", + "DListViewHeaderLabel", + "KEY_UP", + "ACT_CLIMB_UP", + "RENDERMODE_NORMAL", + "ACT_HL2MP_RUN_ANGRY", + "RENDERGROUP_OTHER", + "COND_NEW_ENEMY", + "RENDERGROUP_OPAQUE_BRUSH", + "ACT_DOD_SPRINT_AIM_SPADE", + "ACT_DOD_CROUCH_ZOOM_BAZOOKA", + "KEY_BACKQUOTE", + "RENDERGROUP_VIEWMODEL_TRANSLUCENT", + "ACT_WALK_SCARED", + "ACT_VM_MISSCENTER2", + "ACT_MELEE_ATTACK1", + "RENDERGROUP_STATIC_HUGE", + "KEY_L", + "PLAYERANIMEVENT_SPAWN", + "PLAYERANIMEVENT_SNAP_YAW", + "ACT_DOD_RELOAD_CROUCH_RIFLE", + "PLAYERANIMEVENT_RELOAD_LOOP", + "PLAYERANIMEVENT_RELOAD_END", + "ACT_IDLE_HURT", + "PLAYERANIMEVENT_RELOAD", + "ACT_HL2MP_GESTURE_RELOAD_SMG1", + "KEY_U", + "ACT_DOD_CROUCH_IDLE_TOMMY", + "PLAYERANIMEVENT_FLINCH_LEFTLEG", + "PLAYERANIMEVENT_FLINCH_HEAD", + "GESTURE_SLOT_SWIM", + "PLAYERANIMEVENT_FLINCH_CHEST", + "CHAN_VOICE2", + "ACT_MP_ATTACK_STAND_MELEE", + "PLAYERANIMEVENT_DOUBLEJUMP", + "PLAYERANIMEVENT_DIE", + "PLAYERANIMEVENT_CUSTOM_SEQUENCE", + "ACT_VM_DRAWFULL_M203", + "ACT_DOD_PRIMARYATTACK_GREN_FRAG", + "IN_WEAPON2", + "PLAYERANIMEVENT_CUSTOM_GESTURE_SEQUENCE", + "ACT_HL2MP_SWIM_IDLE_SCARED", + "PLAYERANIMEVENT_CUSTOM_GESTURE", + "PLAYERANIMEVENT_CUSTOM", + "ServerLog", + "PLAYERANIMEVENT_CANCEL", + "ACT_HL2MP_WALK_CROUCH_SMG1", + "DOF_OFFSET", + "PLAYERANIMEVENT_ATTACK_PRIMARY", + "ParticleEffectAttach", + "PLAYERANIMEVENT_ATTACK_GRENADE", + "SafeRemoveEntity", + "ACT_DOD_CROUCH_AIM_PISTOL", + "RadioButton", + "ACT_DOD_CROUCHWALK_IDLE_BAR", + "CLuaEmitter", + "ACT_MP_RELOAD_CROUCH_SECONDARY_END", + "FL_STATICPROP", + "ACT_RANGE_AIM_LOW", + "FL_GRENADE", + "EF_BONEMERGE_FASTCULL", + "ACT_HL2MP_RUN_PHYSGUN", + "ACT_DOD_PRIMARYATTACK_BAR", + "PLAYER_ATTACK1", + "ACT_MP_DEPLOYED_PRIMARY", + "PLAYER_DIE", + "PLAYER_SUPERJUMP", + "setfenv", + "COND_ENEMY_UNREACHABLE", + "PATTACH_POINT_FOLLOW", + "PATTACH_POINT", + "CAP_FRIENDLY_DMG_IMMUNE", + "ACT_MP_CROUCHWALK_PDA", + "ACT_DOD_CROUCHWALK_AIM_GREASE", + "ACT_VM_SECONDARYATTACK", + "PATTACH_ABSORIGIN_FOLLOW", + "ACT_DOD_PRIMARYATTACK_CROUCH_GREN_FRAG", + "ACT_GESTURE_RELOAD_SMG1", + "ACT_IDLE_ANGRY_RPG", + "ACT_HL2MP_SWIM_PASSIVE", + "CHAN_USER_BASE", + "ACT_HL2MP_WALK_KNIFE", + "ACT_RANGE_ATTACK_AR2_GRENADE", + "ACT_DROP_WEAPON_SHOTGUN", + "DMG_DROWNRECOVER", + "OBS_MODE_IN_EYE", + "ACT_DOD_PRIMARYATTACK_PRONE_MG", + "EF_NODRAW", + "ACT_DOD_WALK_IDLE_MP44", + "MOUSE_FIRST", + "OBS_MODE_FIXED", + "ACT_HL2MP_WALK_ZOMBIE_01", + "ACT_VM_PRIMARYATTACK_2", + "WEAPON_PROFICIENCY_GOOD", + "NUM_SPRITES", + "scripted_ents", + "KEY_BACKSPACE", + "NUM_HULLS", + "KEY_PAD_ENTER", + "ACT_HL2MP_WALK_ZOMBIE_04", + "serverlist", + "NUM_AI_CLASSES", + "ACT_TRIPMINE_WORLD", + "DDragBase", + "NPC_STATE_DEAD", + "DCategoryHeader", + "NPC_STATE_PRONE", + "NPC_STATE_PLAYDEAD", + "KEY_CAPSLOCK", + "NPC_STATE_SCRIPT", + "NPC_STATE_COMBAT", + "ACT_HL2MP_WALK_RPG", + "ACT_HL2MP_WALK_ZOMBIE_06", + "NPC_STATE_ALERT", + "ACT_IDLE_ANGRY_SHOTGUN", + "NPC_STATE_NONE", + "DMG_BLAST_SURFACE", + "NOTIFY_HINT", + "DPanelOverlay", + "ACT_DYINGTODEAD", + "NOTIFY_ERROR", + "ACT_HL2MP_WALK_CROUCH_PISTOL", + "NOTIFY_GENERIC", + "ACT_HL2MP_IDLE_CROUCH_ZOMBIE_02", + "GO_ELEVATOR_DOWN", + "ACT_MP_CROUCH_BUILDING", + "ACT_DOD_PRONEWALK_AIM_KNIFE", + "ACT_HL2MP_JUMP_SCARED", + "ACT_DOD_STAND_IDLE_MP40", + "GO_ELEVATOR_UP", + "PLAYERANIMEVENT_JUMP", + "GO_LADDER_DOWN", + "ACT_DOD_DEPLOY_RIFLE", + "unpack", + "ACT_VM_RELOAD_DEPLOYED", + "GO_SOUTH", + "ACT_COVER_LOW_RPG", + "GO_EAST", + "MATERIAL_LIGHT_POINT", + "WEST", + "ACT_GMOD_TAUNT_LAUGH", + "EAST", + "NUM_CORNERS", + "SOUTH_WEST", + "SOUTH_EAST", + "MATERIAL_FOG_LINEAR", + "BONE_SCREEN_ALIGN_SPHERE", + "IN_ATTACK2", + "NAV_MESH_AVOID", + "NORTH_WEST", + "ACT_DOD_RELOAD_PRONE_BOLT", + "ACT_MP_WALK", + "CAP_INNATE_RANGE_ATTACK1", + "ACT_DOD_RELOAD_PRONE_DEPLOYED_30CAL", + "NAV_MESH_NAV_BLOCKER", + "ACT_DOD_PRIMARYATTACK_CROUCH", + "NAV_MESH_CLIFF", + "NAV_MESH_OBSTACLE_TOP", + "NAV_MESH_NO_HOSTAGES", + "NAV_MESH_STAND", + "NAV_MESH_DONT_HIDE", + "ACT_DOD_CROUCHWALK_IDLE", + "DListView_Column", + "NAV_MESH_TRANSIENT", + "TRANSMIT_PVS", + "TEXTUREFLAGS_PROCEDURAL", + "NAV_MESH_PRECISE", + "KEY_PAD_9", + "ACT_SLAM_STICKWALL_ND_ATTACH2", + "ACT_HOP", + "NAV_MESH_CROUCH", + "NAV_MESH_INVALID", + "EF_BRIGHTLIGHT", + "PLAYER_IDLE", + "ACT_DOD_CROUCHWALK_AIM_MG", + "ACT_HL2MP_IDLE_CROUCH_KNIFE", + "rawget", + "ACT_HL2MP_GESTURE_RELOAD_CROSSBOW", + "ACT_SLAM_THROW_ND_IDLE", + "MOVETYPE_LADDER", + "CTakeDamageInfo", + "input", + "DForm", + "ENTITY", + "ACT_DOD_CROUCH_AIM_PSCHRECK", + "Derma_StringRequest", + "ACT_HL2MP_SWIM_CAMERA", + "ACT_GMOD_GESTURE_TAUNT_ZOMBIE", + "MOVETYPE_VPHYSICS", + "SND_IGNORE_PHONEMES", + "ACT_GESTURE_RANGE_ATTACK_SHOTGUN", + "ACT_MP_RUN", + "ACT_DOD_CROUCH_IDLE_BAZOOKA", + "ACT_MP_GESTURE_VC_HANDMOUTH_BUILDING", + "GMOD_CHANNEL_PLAYING", + "DRGBPicker", + "MOVETYPE_NONE", + "CreateSound", + "ACT_MP_CROUCHWALK_PRIMARY", + "ACT_DOD_RUN_AIM_BAR", + "MOVECOLLIDE_COUNT", + "COLLISION_GROUP_IN_VEHICLE", + "collectgarbage", + "ACT_DI_ALYX_ZOMBIE_SHOTGUN64", + "DBinder", + "MATERIAL_RT_DEPTH_NONE", + "ACT_GRENADE_TOSS", + "ACT_VM_IDLE_DEPLOYED", + "ACT_VM_DOWN", + "ACT_SHOTGUN_IDLE4", + "DOF_SPACING", + "ACT_DOD_STAND_IDLE_30CAL", + "steamworks", + "MATERIAL_RT_DEPTH_SEPARATE", + "MATERIAL_LIGHT_SPOT", + "EFL_CHECK_UNTOUCH", + "GO_NORTH", + "DProperty_Float", + "IN_ALT1", + "MATERIAL_LIGHT_DISABLE", + "SCHED_RELOAD", + "isangle", + "ACT_DI_ALYX_ZOMBIE_TORSO_MELEE", + "ACT_HL2MP_SIT_SMG1", + "MATERIAL_FOG_NONE", + "HULL_SMALL_CENTERED", + "ACT_DOD_DEPLOY_TOMMY", + "MATERIAL_CULLMODE_CW", + "SURF_NOCHOP", + "TRACER_LINE_AND_WHIZ", + "SCHED_INTERACTION_WAIT_FOR_PARTNER", + "sql", + "MATERIAL_POLYGON", + "MATERIAL_POINTS", + "IsValid", + "ACT_VM_HAULBACK", + "ACT_MP_WALK_SECONDARY", + "ACT_WALK_AIM_SHOTGUN", + "FCVAR_SERVER_CAN_EXECUTE", + "MAT_DEFAULT", + "MAT_WOOD", + "ACT_MP_PRIMARY_GRENADE2_IDLE", + "ACT_SIGNAL_HALT", + "ACT_HL2MP_SWIM_IDLE_MAGIC", + "ACT_MP_ATTACK_SWIM_SECONDARYFIRE", + "ACT_MP_GESTURE_VC_NODNO_BUILDING", + "ACT_COVER", + "SCHED_PATROL_WALK", + "ACT_HL2MP_SWIM_IDLE_SLAM", + "MAT_FLESH", + "MAT_EGGSHELL", + "KEY_NUMLOCK", + "CLuaLocomotion", + "ACT_DOD_PRIMARYATTACK_TOMMY", + "ACT_DOD_CROUCH_AIM_RIFLE", + "MAT_CONCRETE", + "ACT_VM_DOWN_EMPTY", + "ACT_WALK_AIM", + "ACT_DOD_WALK_AIM_MP40", + "MAT_CLIP", + "CT_REFUGEE", + "MAT_ALIENFLESH", + "MASK_WATER", + "MASK_VISIBLE_AND_NPCS", + "MASK_VISIBLE", + "ACT_HL2MP_WALK_CROUCH_ZOMBIE_03", + "MASK_SPLITAREAPORTAL", + "ACT_IDLE_SUITCASE", + "MASK_SOLID_BRUSHONLY", + "MASK_SOLID", + "ACT_DOD_ZOOMLOAD_PRONE_BAZOOKA", + "GMOD_CHANNEL_STALLED", + "ACT_DOD_PRONE_FORWARD_ZOOMED", + "MASK_SHOT_PORTAL", + "ACT_DOD_DEPLOYED", + "MASK_SHOT_HULL", + "MASK_PLAYERSOLID_BRUSHONLY", + "ACT_HL2MP_IDLE_CROUCH_RPG", + "game", + "SCHED_COMBAT_SWEEP", + "EffectData", + "MASK_OPAQUE", + "ACT_LAND", + "KEY_NUMLOCKTOGGLE", + "COND_HEAR_MOVE_AWAY", + "MASK_NPCSOLID", + "ACT_COMBAT_IDLE", + "MASK_DEADSOLID", + "MASK_CURRENT", + "OrderVectors", + "DMG_BULLET", + "COND_SEE_PLAYER", + "ACT_HL2MP_SIT_AR2", + "ACT_HL2MP_IDLE_MELEE_ANGRY", + "KEY_P", + "MASK_ALL", + "ACT_GESTURE_TURN_LEFT90", + "kRenderFxGlowShell", + "kRenderFxExplode", + "kRenderFxDistort", + "kRenderFxNoDissipation", + "ACT_VM_DRAW_SILENCED", + "kRenderFxFlickerFast", + "kRenderFxFlickerSlow", + "EFL_BOT_FROZEN", + "COND_PLAYER_ADDED_TO_SQUAD", + "DColorCombo", + "kRenderFxStrobeFast", + "kRenderFxSolidFast", + "kRenderFxSolidSlow", + "kRenderFxFadeFast", + "ACT_VM_PRIMARYATTACK_DEPLOYED_2", + "kRenderFxFadeSlow", + "OnModelLoaded", + "kRenderFxPulseFastWide", + "kRenderFxPulseFast", + "KEY_TAB", + "kRenderFxNone", + "ACT_MP_ATTACK_STAND_MELEE_SECONDARY", + "ACT_MP_ATTACK_SWIM_MELEE", + "IN_GRENADE1", + "ACT_MP_GESTURE_VC_NODNO_MELEE", + "ACT_GESTURE_FLINCH_HEAD", + "ACT_DOD_CROUCHWALK_IDLE_TNT", + "ClientsideRagdoll", + "debugoverlay", + "IN_SPEED", + "IN_SCORE", + "CHAN_BODY", + "ACT_DOD_SPRINT_IDLE_MG", + "IN_RELOAD", + "ACT_MP_ATTACK_CROUCH_SECONDARYFIRE", + "ACT_DOD_RUN_AIM_BAZOOKA", + "IN_RUN", + "IN_MOVELEFT", + "IsEnemyEntityName", + "IN_LEFT", + "ACT_WALK_RELAXED", + "ACT_DOD_RELOAD_FG42", + "IN_CANCEL", + "ACT_HL2MP_SWIM_SLAM", + "ACT_RUN_ON_FIRE", + "ACT_SMG2_TOAUTO", + "COND_NOT_FACING_ATTACK", + "numpad", + "ACT_SLAM_STICKWALL_ND_ATTACH", + "ACT_MP_RUN_PRIMARY", + "ismatrix", + "IN_FORWARD", + "IN_DUCK", + "ACT_DOD_SPRINT_IDLE_PSCHRECK", + "DamageInfo", + "ACT_WALK_CROUCH_RPG", + "JOYSTICK_FIRST_AXIS_BUTTON", + "DListViewLine", + "ACT_VM_RELOAD_M203", + "ACT_DOD_PRIMARYATTACK_SPADE", + "ACT_TURNRIGHT45", + "FL_STEPMOVEMENT", + "IMAGE_FORMAT_BGR888", + "RT_SIZE_FULL_FRAME_BUFFER_ROUNDED_UP", + "IMAGE_FORMAT_DEFAULT", + "SF_CITIZEN_MEDIC", + "HULL_LARGE_CENTERED", + "ACT_DOD_ZOOMLOAD_PRONE_PSCHRECK", + "HULL_LARGE", + "HULL_TINY_CENTERED", + "HULL_MEDIUM", + "STENCIL_LESS", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_ZOMBIE", + "ACT_MP_GESTURE_VC_NODYES_MELEE", + "ACT_MP_ATTACK_AIRWALK_GRENADE", + "HUD_PRINTTALK", + "ACT_IDLE_SMG1_STIMULATED", + "HUD_PRINTCONSOLE", + "CONTENTS_MONSTER", + "ACT_VM_UNDEPLOY", + "ACT_MP_JUMP_START_BUILDING", + "FORCE_NUMBER", + "ACT_MP_RELOAD_SWIM_PRIMARY", + "ACT_VM_DRAW", + "ACT_VM_IIN", + "ACT_VM_HITLEFT2", + "ACT_HL2MP_IDLE_CROUCH", + "HITGROUP_RIGHTARM", + "ACT_WALK_RIFLE_RELAXED", + "DMG_DISSOLVE", + "HITGROUP_GENERIC", + "SF_NPC_NO_PLAYER_PUSHAWAY", + "ACT_RANGE_ATTACK_HMG1", + "GMOD_CHANNEL_STOPPED", + "GLOBAL_DEAD", + "DrawSobel", + "GLOBAL_ON", + "PLAYER_LEAVE_AIMING", + "KEY_Z", + "DCheckBox", + "SCHED_PRE_FAIL_ESTABLISH_LINE_OF_FIRE", + "GESTURE_SLOT_VCD", + "ACT_HL2MP_RUN_PASSIVE", + "GESTURE_SLOT_FLINCH", + "ACT_HL2MP_WALK_CROSSBOW", + "ACT_DOD_RELOAD_DEPLOYED_BAR", + "MOUSE_RIGHT", + "ACT_MP_STAND_BUILDING", + "timer", + "ACT_RAPPEL_LOOP", + "newproxy", + "GESTURE_SLOT_GRENADE", + "ACT_DOD_CROUCH_AIM_BOLT", + "FVPHYSICS_PART_OF_RAGDOLL", + "ACT_HL2MP_IDLE_CROUCH_SLAM", + "ACT_MP_ATTACK_CROUCH_PRIMARYFIRE_DEPLOYED", + "ACT_GESTURE_TURN_LEFT45_FLAT", + "ACT_DOD_CROUCH_AIM_KNIFE", + "COND_SCHEDULE_DONE", + "ACT_BUSY_LEAN_LEFT_EXIT", + "FVPHYSICS_NO_SELF_COLLISIONS", + "FVPHYSICS_NO_PLAYER_PICKUP", + "FVPHYSICS_MULTIOBJECT_ENTITY", + "FSOLID_MAX_BITS", + "istable", + "ACT_VM_RELOAD_IDLE", + "COLLISION_GROUP_INTERACTIVE_DEBRIS", + "STENCILCOMPARISONFUNCTION_ALWAYS", + "ACT_RUN_CROUCH_AIM", + "FSOLID_TRIGGER", + "FSOLID_NOT_SOLID", + "DOF_Kill", + "ACT_VM_ATTACH_SILENCER", + "EFL_NO_THINK_FUNCTION", + "ACT_180_LEFT", + "FSOLID_CUSTOMBOXTEST", + "FSOLID_CUSTOMRAYTEST", + "FORCE_BOOL", + "FCVAR_DONTRECORD", + "ACT_MP_GESTURE_FLINCH_LEFTLEG", + "FL_TRANSRAGDOLL", + "ACT_RUN_RELAXED", + "kRenderFxClampMinScale", + "FL_FROZEN", + "FL_KILLME", + "FL_WORLDBRUSH", + "ACT_CROUCHING_SHIELD_ATTACK", + "PrecacheParticleSystem", + "ACT_RUN_STIMULATED", + "FL_BASEVELOCITY", + "FL_GRAPHED", + "ACT_GESTURE_BIG_FLINCH", + "FL_PARTIALGROUND", + "FL_AIMTARGET", + "ACT_DOD_CROUCHWALK_ZOOM_BAZOOKA", + "FL_NOTARGET", + "ACT_DOD_HS_IDLE_STICKGRENADE", + "ACT_RANGE_ATTACK_SNIPER_RIFLE", + "ACT_HL2MP_JUMP", + "ACT_DOD_WALK_IDLE_PISTOL", + "Localize", + "BOTTOM", + "pairs", + "FL_INWATER", + "FL_FAKECLIENT", + "FL_CLIENT", + "ACT_VM_DEPLOY_6", + "ACT_HL2MP_WALK_PISTOL", + "ACT_DIE_CHESTSHOT", + "FL_ATCONTROLS", + "FL_ONFIRE", + "ACT_DOD_RELOAD_MP44", + "ACT_MP_ATTACK_AIRWALK_GRENADE_MELEE", + "ACT_GESTURE_FLINCH_RIGHTLEG", + "FL_ONTRAIN", + "http", + "ACT_HL2MP_SWIM_DUEL", + "FL_ANIMDUCKING", + "FL_DUCKING", + "MarkupObject", + "FFT_32768", + "CompileString", + "COLLISION_GROUP_PROJECTILE", + "MOUSE_WHEEL_DOWN", + "ENT_NEXTBOT", + "COLLISION_GROUP_PLAYER", + "FFT_16384", + "FFT_8192", + "KEY_PAD_6", + "FFT_4096", + "ACT_DOD_RELOAD_PRONE_PISTOL", + "SENSORBONE", + "ACT_DOD_WALK_AIM_GREASE", + "FFT_256", + "KEY_FIRST", + "FCVAR_UNREGISTERED", + "ACT_DOD_PRONEWALK_IDLE_BAR", + "ACT_DOD_WALK_AIM_PISTOL", + "ACT_SMG2_TOBURST", + "FCVAR_UNLOGGED", + "FCVAR_SERVER_CANNOT_QUERY", + "FCVAR_REPLICATED", + "FCVAR_PROTECTED", + "COND_NO_PRIMARY_AMMO", + "FCVAR_NOT_CONNECTED", + "ACT_HANDGRENADE_THROW1", + "ACT_SLAM_STICKWALL_ND_IDLE", + "ACT_MP_ATTACK_STAND_PREFIRE", + "FCVAR_NEVER_AS_STRING", + "string", + "ACT_WALK_RIFLE_STIMULATED", + "FCVAR_LUA_SERVER", + "FCVAR_LUA_CLIENT", + "FCVAR_DEMO", + "FCVAR_CLIENTDLL", + "STENCIL_GREATER", + "FCVAR_CHEAT", + "TYPE_USERDATA", + "Vector", + "EFL_USE_PARTITION_WHEN_NOT_SOLID", + "EFL_SETTING_UP_BONES", + "ACT_BUSY_LEAN_BACK", + "sound", + "ACT_DOD_PRONE_DEPLOY_RIFLE", + "DListViewLabel", + "KEY_XBUTTON_DOWN", + "ACT_HL2MP_IDLE_ANGRY", + "ACT_IDLE_MELEE", + "CONTENTS_CURRENT_180", + "CLASS_COMBINE_HUNTER", + "ACT_DOD_PRIMARYATTACK_PRONE_RIFLE", + "DrawMaterialOverlay", + "ACT_DOD_PRIMARYATTACK_RIFLE", + "EFL_NO_PHYSCANNON_INTERACTION", + "EFL_NO_MEGAPHYSCANNON_RAGDOLL", + "EFL_NO_DISSOLVE", + "EFL_NO_AUTO_EDICT_ATTACH", + "COND_SMELL", + "EFL_NOTIFY", + "KEY_K", + "EFL_IS_BEING_LIFTED_BY_BARNACLE", + "duplicator", + "EFL_IN_SKYBOX", + "motionsensor", + "KEY_PAD_MINUS", + "ACT_HL2MP_SWIM_IDLE_RPG", + "EFL_DONTBLOCKLOS", + "ACT_VM_PICKUP", + "TypeID", + "EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS", + "EFL_DIRTY_SPATIAL_PARTITION", + "EFL_DIRTY_SHADOWUPDATE", + "ACT_HL2MP_JUMP_SUITCASE", + "EFL_DIRTY_ABSTRANSFORM", + "Sound", + "ACT_MP_ATTACK_AIRWALK_SECONDARY", + "AddWorldTip", + "MATERIAL_LIGHT_DIRECTIONAL", + "DynamicLight", + "kRenderFxStrobeFaster", + "EF_FOLLOWBONE", + "VisualizeLayout", + "ACT_HL2MP_SWIM_IDLE_DUEL", + "ACT_DOD_PRONE_AIM_KNIFE", + "MOVETYPE_PUSH", + "ACT_DOD_RELOAD_CROUCH_M1CARBINE", + "WEAPON_PROFICIENCY_POOR", + "BONE_USED_BY_HITBOX", + "PLAYER_START_AIMING", + "ACT_TURN", + "ACT_MP_ATTACK_SWIM_POSTFIRE", + "Slider", + "KEY_0", + "ACT_HL2MP_WALK_CROUCH_CAMERA", + "FL_NPC", + "TOP", + "ACT_GMOD_GESTURE_AGREE", + "RIGHT", + "DFrame", + "LEFT", + "FILL", + "DMG_SLOWBURN", + "BLOOD_COLOR_ANTLION", + "KEY_F3", + "ACT_DOD_RUN_IDLE_TOMMY", + "SNDLVL_70dB", + "PATTACH_WORLDORIGIN", + "ACT_STEP_BACK", + "DMG_PLASMA", + "ACT_GMOD_GESTURE_RANGE_ZOMBIE", + "DMG_PHYSGUN", + "ACT_RUN_AIM_AGITATED", + "OBS_MODE_ROAMING", + "AddCSLuaFile", + "HITGROUP_HEAD", + "DMG_BUCKSHOT", + "DMG_AIRBOAT", + "ACT_VM_RECOIL1", + "SCHED_FAIL_TAKE_COVER", + "ACT_DOD_CROUCH_IDLE_PSCHRECK", + "DMG_ALWAYSGIB", + "Frame", + "DMG_ENERGYBEAM", + "ACT_HL2MP_GESTURE_RELOAD_DUEL", + "SNDLVL_95dB", + "SetGlobalFloat", + "ACT_WALK_CARRY", + "ACT_GESTURE_MELEE_ATTACK2", + "JS_Language", + "ACT_VM_PRIMARYATTACK_DEPLOYED_5", + "ACT_DOD_PRONE_AIM_GREN_FRAG", + "SCHED_ESTABLISH_LINE_OF_FIRE", + "ACT_DOD_WALK_AIM_BAR", + "ACT_SLAM_TRIPMINE_TO_THROW_ND", + "STUDIO_SHADOWDEPTHTEXTURE", + "ACT_HL2MP_GESTURE_RELOAD_FIST", + "DMG_SLASH", + "ACT_DOD_HS_IDLE", + "ACT_MP_ATTACK_STAND_POSTFIRE", + "DMG_GENERIC", + "ACT_HL2MP_SWIM_SHOTGUN", + "ACT_VM_DEPLOY_1", + "D_LI", + "CAP_DUCK", + "ACT_GESTURE_TURN_LEFT90_FLAT", + "FindTooltip", + "D_FR", + "ACT_DOD_RELOAD_PRONE_FG42", + "KEY_EQUAL", + "RealFrameTime", + "WorkshopFileBase", + "D_ER", + "ACT_DOD_WALK_AIM_30CAL", + "ACT_HL2MP_SWIM_IDLE_ZOMBIE", + "ACT_DOD_PRIMARYATTACK_CROUCH_GREN_STICK", + "CT_REBEL", + "MAT_BLOODYFLESH", + "ACT_HL2MP_IDLE_REVOLVER", + "DCheckBoxLabel", + "KEY_9", + "CREATERENDERTARGETFLAGS_UNFILTERABLE_OK", + "ACT_MP_SECONDARY_GRENADE1_ATTACK", + "EFL_NOCLIP_ACTIVE", + "ACT_HL2MP_RUN_CAMERA", + "FL_GODMODE", + "EmitSentence", + "CONTENTS_TRANSLUCENT", + "ACT_DOD_HS_CROUCH_30CAL", + "ACT_DOD_PRONE_AIM_RIFLE", + "ACT_GMOD_GESTURE_ITEM_GIVE", + "CONTENTS_ORIGIN", + "HITGROUP_GEAR", + "CONTENTS_LADDER", + "ACT_DOD_WALK_AIM_RIFLE", + "CONTENTS_HITBOX", + "CONTENTS_DEBRIS", + "ACT_DOD_PRONE_AIM_MG", + "CONTENTS_CURRENT_UP", + "ACT_PHYSCANNON_ANIMATE_POST", + "ACT_MP_ATTACK_AIRWALK_SECONDARYFIRE", + "ACT_DISARM", + "CONTENTS_CURRENT_90", + "ACT_MP_RELOAD_STAND_PRIMARY_LOOP", + "CONTENTS_CURRENT_270", + "TEXTUREFLAGS_UNUSED_00400000", + "CONTENTS_PLAYERCLIP", + "Error", + "ACT_MP_SECONDARY_GRENADE1_DRAW", + "SNDLVL_60dB", + "ACT_VM_RELOAD", + "CAP_AUTO_DOORS", + "ACT_DI_ALYX_ZOMBIE_SHOTGUN26", + "CONTENTS_TEAM2", + "CONTENTS_TEAM1", + "COLLISION_GROUP_NPC_SCRIPTED", + "CONTENTS_OPAQUE", + "CONTENTS_BLOCKLOS", + "ACT_VM_DIFIREMODE", + "ACT_DOD_CROUCH_AIM_GREN_FRAG", + "ACT_HL2MP_WALK_CROUCH_FIST", + "CONTENTS_AUX", + "ACT_DOD_DEPLOY_MG", + "ACT_MP_CROUCH_DEPLOYED", + "ACT_DOD_WALK_IDLE_30CAL", + "CONTENTS_EMPTY", + "SCHED_MELEE_ATTACK1", + "ACT_90_RIGHT", + "ACT_GESTURE_RANGE_ATTACK_SMG1", + "COND_WEAPON_BLOCKED_BY_FRIEND", + "TEXTUREFLAGS_UNUSED_40000000", + "ACT_HL2MP_SWIM_SCARED", + "ACT_DOD_PRONE_ZOOM_FORWARD_BAZOOKA", + "COND_TOO_CLOSE_TO_ATTACK", + "COND_TASK_FAILED", + "ACT_POLICE_HARASS1", + "ACT_STRAFE_LEFT", + "DPanelList", + "VGUIFrameTime", + "error", + "COND_TALKER_RESPOND_TO_QUESTION", + "ACT_DOD_RELOAD_DEPLOYED", + "ACT_DOD_SECONDARYATTACK_PRONE_MP40", + "MASK_BLOCKLOS", + "ACT_MP_SECONDARY_GRENADE2_DRAW", + "ACT_GESTURE_TURN_RIGHT45_FLAT", + "ACT_CROUCHING_GRENADEREADY", + "COND_SEE_DISLIKE", + "COND_RECEIVED_ORDERS", + "ITexture", + "COND_PLAYER_REMOVED_FROM_SQUAD", + "COND_PLAYER_PUSHING", + "ACT_DOD_PRIMARYATTACK_DEPLOYED_RIFLE", + "ACT_VM_DEPLOYED_IDLE", + "COND_NPC_FREEZE", + "SCHED_NPC_FREEZE", + "COND_NO_SECONDARY_AMMO", + "COND_MOBBED_BY_ENEMIES", + "COND_LOST_PLAYER", + "COLLISION_GROUP_INTERACTIVE", + "EF_BONEMERGE", + "ACT_MP_SWIM_PDA", + "DAdjustableModelPanel", + "ACT_VM_FIRE_TO_EMPTY", + "tonumber", + "ACT_UNDEPLOY", + "ACT_VM_SWINGMISS", + "COND_IN_PVS", + "BLEND_ONE_MINUS_DST_COLOR", + "COND_IDLE_INTERRUPT", + "JOYSTICK_FIRST_BUTTON", + "COND_HEAVY_DAMAGE", + "RENDERMODE_ENVIROMENTAL", + "RunString", + "COND_HEAR_SPOOKY", + "ACT_MP_ATTACK_SWIM_GRENADE_MELEE", + "CSoundPatch", + "COND_HEAR_PHYSICS_DANGER", + "ACT_WALK_STIMULATED", + "COLLISION_GROUP_BREAKABLE_GLASS", + "MASK_NPCSOLID_BRUSHONLY", + "COND_GIVE_WAY", + "ACT_GMOD_IN_CHAT", + "BONE_USED_BY_BONE_MERGE", + "RunConsoleCommand", + "ACT_HL2MP_IDLE_CROUCH_PHYSGUN", + "COND_ENEMY_WENT_NULL", + "DMG_PREVENT_PHYSICS_FORCE", + "ACT_DEEPIDLE4", + "COND_ENEMY_TOO_FAR", + "COND_CAN_RANGE_ATTACK1", + "KEY_RSHIFT", + "CONTENTS_TEAM4", + "ACT_DOD_HS_IDLE_KNIFE", + "EditablePanel", + "ACT_HL2MP_SWIM_MAGIC", + "GetHUDPanel", + "ACT_COVER_PISTOL_LOW", + "IsEntity", + "COLLISION_GROUP_NPC_ACTOR", + "SCHED_CHASE_ENEMY_FAILED", + "COLLISION_GROUP_DISSOLVING", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_SUITCASE", + "ACT_FLY", + "COLLISION_GROUP_DOOR_BLOCKER", + "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED", + "ACT_HL2MP_RUN_SUITCASE", + "ACT_HL2MP_GESTURE_RELOAD_ZOMBIE", + "CLASS_BARNACLE", + "DSizeToContents", + "ACT_DOD_HS_IDLE_MG42", + "ACT_VM_RELOAD2", + "ACT_HL2MP_RUN_ZOMBIE", + "ACT_DOD_CROUCHWALK_IDLE_30CAL", + "ACT_DOD_PRONE_AIM_BAR", + "COLLISION_GROUP_PLAYER_MOVEMENT", + "FSOLID_USE_TRIGGER_BOUNDS", + "ACT_CROUCHING_SHIELD_KNOCKBACK", + "EF_NOINTERP", + "COLLISION_GROUP_DEBRIS", + "ACT_VM_SWINGHIT", + "ACT_DOD_WALK_IDLE_MG", + "ColorRand", + "ACT_DOD_RELOAD_CROUCH_PSCHRECK", + "CLASS_EARTH_FAUNA", + "CLASS_FLARE", + "CLASS_MISSILE", + "CLASS_STALKER", + "ACT_DOD_RUN_AIM_SPADE", + "ACT_VM_HOLSTER_EMPTY", + "ACT_BIG_FLINCH", + "CLASS_MANHACK", + "CLASS_HEADCRAB", + "ACT_RANGE_AIM_SMG1_LOW", + "CLASS_COMBINE_GUNSHIP", + "SOLID_VPHYSICS", + "CLASS_CITIZEN_REBEL", + "CLASS_CITIZEN_PASSIVE", + "ACT_DOD_PRIMARYATTACK_PRONE_KNIFE", + "CAP_USE_WEAPONS", + "MOVECOLLIDE_FLY_SLIDE", + "DermaMenu", + "STUDIO_WIREFRAME", + "MOUSE_5", + "ACT_VM_FIDGET", + "draw", + "CHAN_STATIC", + "ACT_DOD_STAND_IDLE_BAR", + "CHAN_STREAM", + "ACT_DOD_PRIMARYATTACK_30CAL", + "CHAN_ITEM", + "KEY_XBUTTON_LTRIGGER", + "GetLoadPanel", + "ACT_MP_RELOAD_SWIM_SECONDARY_LOOP", + "ACT_DOD_HS_IDLE_PSCHRECK", + "ACT_HL2MP_IDLE_CROUCH_PISTOL", + "ACT_RELOAD_SMG1", + "BUTTON_CODE_COUNT", + "ACT_TURN_LEFT", + "ACT_DOD_RELOAD_M1CARBINE", + "CAP_NO_HIT_SQUADMATES", + "CAP_AIM_GUN", + "PATTACH_CUSTOMORIGIN", + "CAP_ANIMATEDFACE", + "CAP_INNATE_MELEE_ATTACK2", + "STENCIL_KEEP", + "ACT_DOD_STAND_AIM_KNIFE", + "CancelLoading", + "ACT_VM_SPRINT_ENTER", + "ACT_HL2MP_IDLE_CAMERA", + "Path", + "CAP_WEAPON_RANGE_ATTACK2", + "ACT_HL2MP_GESTURE_RELOAD", + "CAP_TURN_HEAD", + "ACT_MP_JUMP_START", + "CONTENTS_IGNORE_NODRAW_OPAQUE", + "ACT_RANGE_ATTACK_SHOTGUN_LOW", + "CAP_SKIP_NAV_GROUND_CHECK", + "ACT_MP_GESTURE_VC_NODYES_SECONDARY", + "RENDERMODE_TRANSCOLOR", + "CAP_MOVE_CLIMB", + "CAP_MOVE_FLY", + "CAP_MOVE_JUMP", + "IVideoWriter", + "JOYSTICK_LAST", + "KEY_XBUTTON_RTRIGGER", + "cleanup", + "IN_JUMP", + "LanguageChanged", + "ACT_DOD_HS_CROUCH_PISTOL", + "DTree", + "RegisterDermaMenuForClose", + "JOYSTICK_FIRST_POV_BUTTON", + "MOUSE_COUNT", + "MOUSE_LAST", + "MOUSE_WHEEL_UP", + "MOUSE_4", + "MOUSE_MIDDLE", + "KEY_XSTICK2_UP", + "KEY_XSTICK2_LEFT", + "JOYSTICK_LAST_AXIS_BUTTON", + "module", + "ACT_OBJ_PLACING", + "ACT_MP_JUMP_PDA", + "KEY_XSTICK1_UP", + "Color", + "ACT_DIE_RIGHTSIDE", + "ACT_MP_AIRWALK", + "KEY_XSTICK1_LEFT", + "CloseDermaMenus", + "KEY_XSTICK1_RIGHT", + "ACT_HL2MP_WALK_SMG1", + "FFT_1024", + "ACT_DOD_SECONDARYATTACK_PRONE", + "ACT_VM_HOLSTER_M203", + "KEY_XBUTTON_STICK1", + "KEY_XBUTTON_START", + "KEY_XBUTTON_BACK", + "ACT_DOD_STAND_AIM_C96", + "KEY_XBUTTON_RIGHT_SHOULDER", + "KEY_7", + "KEY_COUNT", + "ACT_GMOD_GESTURE_BOW", + "ACT_DOD_PRIMARYATTACK_PRONE_DEPLOYED_30CAL", + "DVScrollBar", + "GetDownloadables", + "KEY_LAST", + "ACT_HL2MP_SWIM_IDLE_MELEE2", + "MASK_NPCWORLDSTATIC", + "ACT_DOD_RELOAD_GREASEGUN", + "TEXTUREFLAGS_DEPTHRENDERTARGET", + "KEY_F10", + "ACT_DOD_WALK_IDLE", + "KEY_F8", + "KEY_F6", + "xpcall", + "ACT_MP_AIRWALK_MELEE", + "KEY_F5", + "KEY_F4", + "KEY_APP", + "ACT_RANGE_ATTACK_SMG1", + "BONE_USED_BY_VERTEX_LOD2", + "DMG_REMOVENORAGDOLL", + "ACT_MP_MELEE_GRENADE1_IDLE", + "ACT_DOD_STAND_AIM_PSCHRECK", + "KEY_F1", + "KEY_RIGHT", + "KEY_DOWN", + "KEY_RWIN", + "ACT_VM_DRYFIRE_SILENCED", + "RichText", + "ACT_HL2MP_SWIM_PHYSGUN", + "ACT_CROUCHING_PRIMARYATTACK", + "ACT_DOD_PRONE_AIM_MP40", + "BONE_PHYSICS_PROCEDURAL", + "ProtectedCall", + "KEY_RCONTROL", + "ACT_VM_IDLE_DEPLOYED_3", + "KEY_RALT", + "LAST_SHARED_COLLISION_GROUP", + "KEY_LSHIFT", + "ACT_HL2MP_SWIM_MELEE2", + "KEY_PAGEUP", + "ACT_DOD_CROUCH_AIM_TOMMY", + "KEY_INSERT", + "KEY_SCROLLLOCK", + "KEY_ESCAPE", + "ACT_HL2MP_IDLE_CROUCH_ANGRY", + "kRenderFxPulseSlow", + "KEY_MINUS", + "KEY_BACKSLASH", + "ACT_LEAP", + "ACT_RUN_AIM", + "DrawColorModify", + "UTIL_IsUselessModel", + "EFL_HAS_PLAYER_CHILD", + "GetRenderTarget", + "KEY_PAD_DIVIDE", + "ACT_RUN_RIFLE_STIMULATED", + "ACT_BUSY_STAND", + "KEY_PAD_5", + "KEY_PAD_4", + "TauntCamera", + "SND_STOP_LOOPING", + "ACT_SMG2_DRYFIRE2", + "ACT_HL2MP_RUN_GRENADE", + "debug", + "KEY_X", + "GO_JUMP", + "KEY_S", + "KEY_R", + "ACT_SIGNAL_GROUP", + "CREATERENDERTARGETFLAGS_AUTOMIPMAP", + "KEY_G", + "CAP_WEAPON_MELEE_ATTACK1", + "KEY_A", + "ACT_DRIVE_JEEP", + "ACT_DOD_PRIMARYATTACK_PRONE_TOMMY", + "KEY_8", + "KEY_XBUTTON_Y", + "BONE_USED_MASK", + "KEY_6", + "KEY_5", + "KEY_3", + "COND_LIGHT_DAMAGE", + "ACT_HL2MP_IDLE_SCARED", + "BUTTON_CODE_LAST", + "BUTTON_CODE_NONE", + "BUTTON_CODE_INVALID", + "BOX_BOTTOM", + "ACT_MP_SWIM_DEPLOYED_PRIMARY", + "ACT_DOD_PRIMARYATTACK_C96", + "LerpVector", + "ACT_DOD_RELOAD_CROUCH_TOMMY", + "BOX_FRONT", + "BONE_USED_BY_ANYTHING", + "BONE_USED_BY_VERTEX_LOD7", + "ACT_VM_DEPLOY_4", + "assert", + "ACT_DOD_PRIMARYATTACK_PRONE_BAZOOKA", + "ACT_DOD_PRIMARYATTACK_MP44", + "BONE_USED_BY_VERTEX_LOD6", + "BONE_USED_BY_VERTEX_LOD5", + "BONE_USED_BY_VERTEX_LOD3", + "BONE_USED_BY_VERTEX_LOD1", + "ACT_DIESIMPLE", + "BONE_USED_BY_VERTEX_LOD0", + "ACT_MP_RELOAD_CROUCH_END", + "BONE_USED_BY_ATTACHMENT", + "SysTime", + "KEY_LWIN", + "ACT_CROUCHIDLE_STIMULATED", + "ACT_MP_CROUCHWALK_MELEE", + "menubar", + "BLOOD_COLOR_ANTLION_WORKER", + "GMOD_CHANNEL_PAUSED", + "ACT_HL2MP_IDLE_CROUCH_SCARED", + "BLOOD_COLOR_YELLOW", + "DONT_BLEED", + "ents", + "ACT_DOD_CROUCH_ZOOM_PSCHRECK", + "ACT_DOD_WALK_AIM_BOLT", + "BLOOD_COLOR_RED", + "GetConVar", + "BLEND_SRC_COLOR", + "BLEND_SRC_ALPHA_SATURATE", + "BLEND_ONE_MINUS_DST_ALPHA", + "BLEND_DST_ALPHA", + "ACT_VM_IDLE_5", + "ACT_FLINCH_RIGHTARM", + "ACT_FIRE_END", + "BLEND_ONE", + "LAST_SHARED_ACTIVITY", + "ACT_GMOD_GESTURE_MELEE_SHOVE_2HAND", + "ACT_MP_GESTURE_FLINCH", + "DNumberWang", + "ACT_HL2MP_RUN_CROSSBOW", + "ACT_GMOD_GESTURE_ITEM_DROP", + "ACT_HL2MP_ZOMBIE_SLUMP_IDLE", + "BLEND_ONE_MINUS_SRC_ALPHA", + "MATERIAL_RT_DEPTH_ONLY", + "ACT_DOD_PRIMARYATTACK_CROUCH_SPADE", + "ACT_HL2MP_SIT_MELEE", + "ACT_SLAM_THROW_IDLE", + "ACT_HL2MP_WALK_MAGIC", + "GetLoadStatus", + "ACT_DOD_CROUCHWALK_AIM_MP44", + "ACT_HL2MP_SIT_GRENADE", + "ACT_HL2MP_RUN_REVOLVER", + "ACT_DOD_STAND_AIM_GREN_STICK", + "RenderDoF", + "ACT_HL2MP_SIT_SHOTGUN", + "ACT_HL2MP_SIT_PISTOL", + "KEY_BREAK", + "ACT_HL2MP_GESTURE_RELOAD_MELEE2", + "ACT_FIRE_START", + "ACT_HL2MP_WALK_MELEE2", + "ACT_HL2MP_IDLE_MELEE2", + "DHTMLControls", + "ACT_HL2MP_GESTURE_RELOAD_PASSIVE", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_PASSIVE", + "COLLISION_GROUP_PASSABLE_DOOR", + "PanelList", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_KNIFE", + "ACT_DOD_PRIMARYATTACK_MG", + "ACT_HL2MP_RUN_KNIFE", + "ACT_HL2MP_IDLE_KNIFE", + "ACT_HL2MP_WALK_DUEL", + "ACT_DOD_PRONE_AIM_30CAL", + "ACT_HL2MP_SIT", + "ACT_HL2MP_SWIM_FIST", + "gui", + "Lerp", + "ACT_SLAM_STICKWALL_TO_TRIPMINE_ND", + "ACT_HL2MP_JUMP_FIST", + "ACT_DIERAGDOLL", + "umsg", + "DropEntityIfHeld", + "ACT_DOD_STAND_IDLE_BAZOOKA", + "ACT_HL2MP_JUMP_GRENADE", + "ACT_GESTURE_TURN_RIGHT", + "ACT_MP_GESTURE_FLINCH_RIGHTARM", + "notification", + "ACT_HL2MP_RUN", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST", + "CONTENTS_SLIME", + "STNDRD", + "ACT_HL2MP_RUN_FIST", + "DColumnSheet", + "ACT_DOD_CROUCH_IDLE_RIFLE", + "ACT_HL2MP_WALK_PASSIVE", + "ACT_DOD_STAND_AIM_BAZOOKA", + "SCHED_BIG_FLINCH", + "ACT_VM_HITCENTER", + "ACT_DOD_STAND_IDLE_BOLT", + "ACT_DOD_WALK_IDLE_C96", + "ACT_DOD_WALK_IDLE_BOLT", + "DIconBrowser", + "ACT_HL2MP_RUN_AR2", + "ACT_MP_VCD", + "ACT_HL2MP_IDLE_FIST", + "ACT_DOD_WALK_AIM_C96", + "ACT_VM_MISSLEFT2", + "ACT_GMOD_NOCLIP_LAYER", + "Angle", + "GO_LADDER_UP", + "ACT_HL2MP_IDLE_CROUCH_CROSSBOW", + "ACT_READINESS_RELAXED_TO_STIMULATED", + "ACT_MP_RELOAD_STAND_PRIMARY_END", + "ACT_180_RIGHT", + "ACT_DOD_CROUCH_ZOOM_RIFLE", + "ACT_IDLE_AIM_STIMULATED", + "ACT_VM_RELOAD_INSERT_EMPTY", + "ACT_DOD_RUN_AIM_PSCHRECK", + "Msg", + "TEXTUREFLAGS_NORMAL", + "ACT_PRONE_IDLE", + "ACT_HL2MP_SIT_PHYSGUN", + "ACT_RUN_AGITATED", + "DModelPanel", + "OpenFolder", + "RENDERMODE_NONE", + "ACT_VM_IFIREMODE", + "ACT_POLICE_HARASS2", + "gamemode", + "ACT_WALK_AIM_RELAXED", + "CLASS_ANTLION", + "ACT_DOD_RELOAD_PRONE_DEPLOYED", + "ACT_MP_JUMP_FLOAT_MELEE", + "ACT_MP_GESTURE_FLINCH_MELEE", + "SNDLVL_20dB", + "ACT_VM_READY_M203", + "ACT_HL2MP_SWIM_RPG", + "ACT_GMOD_GESTURE_ITEM_PLACE", + "weapons", + "ACT_IDLE_AIM_AGITATED", + "TEXTUREFLAGS_ENVMAP", + "ACT_VM_SHOOTLAST", + "ACT_DOD_CROUCH_IDLE_GREASE", + "GLOBAL_OFF", + "ParticleEmitter", + "VGUIRect", + "ACT_OVERLAY_SHIELD_DOWN", + "ACT_VM_ISHOOT_LAST", + "DComboBox", + "ACT_VM_FIREMODE", + "ACT_VM_IRECOIL2", + "ACT_VM_IIDLE_EMPTY", + "ACT_DOD_STAND_AIM_TOMMY", + "KEY_Y", + "ACT_VM_IDLE_LOWERED", + "ACT_HL2MP_SWIM_IDLE_CAMERA", + "DMenuOption", + "ACT_VM_DEPLOYED_LIFTED_OUT", + "ACT_VM_DEPLOYED_LIFTED_IN", + "ACT_DOD_CROUCHWALK_IDLE_MP40", + "ACT_DOD_STAND_AIM_PISTOL", + "ACT_PLAYER_IDLE_FIRE", + "ACT_VM_DEPLOYED_IRON_DRYFIRE", + "ACT_VM_DEPLOYED_IRON_FIRE", + "ACT_VM_DEPLOYED_IRON_IDLE", + "PhysObj", + "isnumber", + "ACT_VM_DEPLOYED_RELOAD_EMPTY", + "ACT_RUN_AIM_STEALTH_PISTOL", + "ACT_HL2MP_RUN_PANICKED", + "ACT_VM_DEPLOYED_RELOAD", + "ACT_VM_DEPLOYED_DRYFIRE", + "ACT_VM_DEPLOYED_FIRE", + "ACT_VM_HOLSTER", + "COND_PHYSICS_DAMAGE", + "ACT_TURNLEFT45", + "ACT_DOD_HS_CROUCH_STICKGRENADE", + "ACT_VM_HITKILL", + "TGAImage", + "RenderAngles", + "ACT_RUN_HURT", + "ACT_RESET", + "ACT_SLAM_THROW_THROW_ND2", + "ACT_MP_RUN_BUILDING", + "ACT_FLINCH_STOMACH", + "ACT_VM_SWINGHARD", + "ACT_VM_PRIMARYATTACK_4", + "ACT_MP_ATTACK_AIRWALK_GRENADE_PRIMARY", + "ACT_DOD_PRIMARYATTACK_GREN_STICK", + "ACT_DOD_HS_CROUCH_MP44", + "ACT_VM_IIDLE", + "ACT_HL2MP_JUMP_SHOTGUN", + "ACT_VM_IIN_EMPTY", + "Stack", + "HITGROUP_RIGHTLEG", + "ACT_VM_PRIMARYATTACK_SILENCED", + "dragndrop", + "ACT_HL2MP_IDLE_CROUCH_ZOMBIE_01", + "ACT_SIGNAL_LEFT", + "ACT_MP_GESTURE_VC_FINGERPOINT", + "SavePresets", + "PropSelect", + "ACT_WALK_ON_FIRE", + "ACT_VM_READY", + "ENT_FILTER", + "ACT_SLAM_DETONATOR_IDLE", + "ACT_HOVER", + "SNDLVL_NONE", + "ACT_MP_RELOAD_STAND_LOOP", + "ACT_VM_CRAWL_EMPTY", + "ACT_DOD_PRONEWALK_IDLE_TOMMY", + "cam", + "ACT_VM_CRAWL", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_MAGIC", + "ACT_SPECIAL_ATTACK2", + "JS_Workshop", + "ACT_DOD_PRONEWALK_IDLE_MP44", + "COLLISION_GROUP_VEHICLE_CLIP", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_SCARED", + "COND_WEAPON_HAS_LOS", + "DNumSlider", + "ACT_MP_JUMP_FLOAT", + "ACT_HL2MP_RUN_SLAM", + "KEY_END", + "ConVarExists", + "ACT_DOD_RUN_IDLE_GREASE", + "ACT_IDLE_PACKAGE", + "GetGlobalString", + "DHTML", + "ACT_SIGNAL_FORWARD", + "ACT_DOD_WALK_AIM", + "ACT_GESTURE_FLINCH_LEFTLEG", + "ACT_DOD_CROUCHWALK_ZOOM_PSCHRECK", + "ACT_GLOCK_SHOOTEMPTY", + "ACT_DOD_WALK_IDLE_BAR", + "ACT_DOD_SECONDARYATTACK_CROUCH_TOMMY", + "SafeRemoveEntityDelayed", + "ACT_HL2MP_IDLE_CROUCH_SHOTGUN", + "ACT_GAUSS_SPINUP", + "ACT_IDLE_AIM_STEALTH", + "CRecipientFilter", + "ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK", + "ACT_HL2MP_JUMP_MELEE", + "ACT_DOD_CROUCHWALK_AIM_MP40", + "tostring", + "ACT_DOD_PRIMARYATTACK_PRONE_GREN_STICK", + "ACT_DOD_PRONE_ZOOM_BAZOOKA", + "ACT_CLIMB_DISMOUNT", + "SCHED_HIDE_AND_RELOAD", + "TRACER_BEAM", + "TYPE_TABLE", + "ACT_DOD_PRIMARYATTACK_PRONE_MP40", + "ACT_HL2MP_IDLE_PASSIVE", + "ACT_HL2MP_IDLE_MELEE", + "ACT_HL2MP_SWIM_CROSSBOW", + "ACT_HL2MP_SWIM_IDLE_CROSSBOW", + "ACT_DOD_PRONE_ZOOM_BOLT", + "ACT_HL2MP_JUMP_CROSSBOW", + "ACT_BUSY_LEAN_BACK_EXIT", + "ACT_RANGE_ATTACK_ML", + "player", + "SF_CITIZEN_NOT_COMMANDABLE", + "ACT_GESTURE_MELEE_ATTACK_SWING", + "ACT_GESTURE_RANGE_ATTACK_SLAM", + "ACT_MP_RELOAD_CROUCH_PRIMARY_LOOP", + "DOFModeHack", + "ACT_HL2MP_WALK_CROUCH_CROSSBOW", + "WEAPON_PROFICIENCY_PERFECT", + "ACT_VM_IDLE_8", + "ACT_HL2MP_SWIM_ANGRY", + "ACT_VM_PRIMARYATTACK_7", + "ACT_GESTURE_SMALL_FLINCH", + "ACT_RUN_SCARED", + "ACT_VM_DEPLOY_7", + "ACT_HL2MP_RUN_RPG", + "ACT_WALK_AIM_STIMULATED", + "ACT_HL2MP_IDLE_PHYSGUN", + "LerpAngle", + "ACT_MP_MELEE_GRENADE2_ATTACK", + "ACT_MP_GRENADE2_IDLE", + "isentity", + "BroadcastLua", + "ACT_DOD_CROUCHWALK_IDLE_MG", + "ACT_HL2MP_WALK_CROUCH_DUEL", + "ACT_HL2MP_IDLE_CROUCH_DUEL", + "ACT_RUN_AIM_PISTOL", + "ACT_CROSSBOW_FIDGET_UNLOADED", + "ai_schedule", + "CreateConVar", + "ACT_WALK_AGITATED", + "ACT_MP_RELOAD_AIRWALK_PRIMARY_END", + "derma", + "ACT_HL2MP_IDLE_DUEL", + "SCHED_DUCK_DODGE", + "ACT_IDLE_ANGRY", + "ACT_VM_PRIMARYATTACK_6", + "ACT_DOD_CROUCH_IDLE_BOLT", + "ACT_DOD_STAND_AIM_MP44", + "TYPE_PARTICLESYSTEM", + "SURF_NODRAW", + "ACT_VM_DEPLOYED_OUT", + "KEY_PAD_1", + "ACT_DIE_BACKSIDE", + "ACT_DOD_RUN_AIM_KNIFE", + "ACT_DOD_WALK_IDLE_RIFLE", + "GWEN", + "print", + "ACT_HL2MP_IDLE_GRENADE", + "ACT_DOD_SPRINT_IDLE_TOMMY", + "EFL_FORCE_CHECK_TRANSMIT", + "KEY_C", + "ACT_RELOAD_START", + "ACT_HL2MP_GESTURE_RELOAD_RPG", + "CONTENTS_WINDOW", + "language", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG", + "ACT_HL2MP_GESTURE_RELOAD_SHOTGUN", + "ACT_STEP_LEFT", + "ACT_SIGNAL_TAKECOVER", + "CLASS_METROPOLICE", + "DMG_SHOCK", + "baseclass", + "ACT_HL2MP_WALK_CROUCH_MELEE", + "ACT_MP_ATTACK_SWIM_PDA", + "ACT_DOD_RUN_ZOOM_RIFLE", + "ACT_MP_GESTURE_FLINCH_CHEST", + "ACT_VM_PRIMARYATTACK_1", + "ACT_VM_HITRIGHT", + "ACT_DIEBACKWARD", + "ACT_HL2MP_SWIM_IDLE_SHOTGUN", + "ACT_VM_IDLE_6", + "ConsoleAutoComplete", + "ACT_MP_ATTACK_STAND_SECONDARYFIRE", + "ACT_GESTURE_RANGE_ATTACK2_LOW", + "ACT_RANGE_ATTACK_THROW", + "ACT_DOD_CROUCHWALK_AIM_SPADE", + "DMG_BLAST", + "ACT_ROLL_LEFT", + "ACT_HL2MP_RUN_SHOTGUN", + "ACT_DOD_PRIMARYATTACK_BOLT", + "ACT_HL2MP_WALK_SHOTGUN", + "TimedSin", + "ACT_HL2MP_GESTURE_RELOAD_AR2", + "Button", + "ACT_SLAM_THROW_THROW", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2", + "ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED", + "ACT_DOD_STAND_AIM_GREASE", + "ACT_HL2MP_WALK_CROUCH_AR2", + "ACT_HL2MP_IDLE_CROUCH_AR2", + "ACT_WALK_AIM_RIFLE_STIMULATED", + "ACT_GRENADE_ROLL", + "ACT_BUSY_LEAN_LEFT_ENTRY", + "ACT_HL2MP_SWIM_SMG1", + "TYPE_INVALID", + "TEXTUREFLAGS_UNUSED_00200000", + "ACT_IDLE_SMG1", + "RENDERGROUP_VIEWMODEL", + "ACT_DOD_RELOAD_PRONE_GREASEGUN", + "AddBackgroundImage", + "SoundDuration", + "ACT_MP_GESTURE_VC_THUMBSUP_MELEE", + "ACT_VM_UNDEPLOY_2", + "ACT_HL2MP_RUN_SMG1", + "ACT_DOD_HS_CROUCH_MG42", + "ACT_WALK_RIFLE", + "ACT_HL2MP_IDLE_SMG1", + "ACT_HL2MP_SWIM_PISTOL", + "DModelSelect", + "ACT_HL2MP_SWIM_IDLE_PISTOL", + "ACT_HL2MP_JUMP_PISTOL", + "ACT_RANGE_ATTACK_AR2_LOW", + "ACT_DOD_PRONEWALK_AIM_GREN_FRAG", + "ACT_DOD_PRONE_AIM_TOMMY", + "ACT_VM_IDLE_1", + "ACT_SLAM_THROW_DETONATOR_HOLSTER", + "ACT_PLAYER_RUN_FIRE", + "ACT_USE", + "ACT_VM_UNDEPLOY_EMPTY", + "ACT_MELEE_ATTACK_SWING", + "ACT_HL2MP_RUN_PISTOL", + "ACT_BARNACLE_CHEW", + "ACT_MP_GESTURE_VC_NODYES_BUILDING", + "ACT_HL2MP_GESTURE_RANGE_ATTACK", + "ACT_DOD_RUN_AIM_MG", + "ACT_HL2MP_IDLE", + "chat", + "ACT_GLIDE", + "GetViewEntity", + "ACT_WALK_CROUCH_AIM", + "vgui", + "ACT_SLAM_TRIPMINE_ATTACH2", + "ACT_MP_GESTURE_FLINCH_STOMACH", + "ACT_HL2MP_SWIM_IDLE_SUITCASE", + "HTML", + "ACT_RUN_STEALTH_PISTOL", + "ACT_VM_PRIMARYATTACK_5", + "RecipientFilter", + "EFL_DIRTY_ABSVELOCITY", + "IN_BACK", + "CHAN_AUTO", + "ACT_RUN_AIM_RELAXED", + "PositionSpawnIcon", + "ACT_DOD_RUN_IDLE_MP40", + "ACT_MP_CROUCH_PRIMARY", + "ACT_VM_RELOADEMPTY", + "ACT_HL2MP_SWIM_ZOMBIE", + "CT_UNIQUE", + "ACT_MP_ATTACK_CROUCH_PREFIRE", + "GetHostName", + "ACT_HL2MP_SIT_FIST", + "ACT_DOD_STAND_IDLE_MP44", + "ACT_WALK_AIM_AGITATED", + "AvatarImage", + "COLLISION_GROUP_WEAPON", + "DProgress", + "KEY_F12", + "ACT_RANGE_ATTACK_SLAM", + "ACT_DOD_PRONEWALK_AIM_GREN_STICK", + "ACT_VM_DRAW_M203", + "ACT_HL2MP_GESTURE_RELOAD_SCARED", + "ACT_HL2MP_IDLE_SUITCASE", + "BLOOD_COLOR_GREEN", + "ACT_HL2MP_WALK_SCARED", + "undo", + "ACT_VM_PRIMARYATTACK_DEPLOYED_8", + "KEY_NONE", + "ACT_HL2MP_JUMP_ANGRY", + "IGModAudioChannel", + "ACT_HL2MP_GESTURE_RELOAD_ANGRY", + "hammer", + "Label", + "IsMounted", + "ACT_MP_GESTURE_FLINCH_SECONDARY", + "ENT_POINT", + "ACT_HL2MP_SWIM_GRENADE", + "ACT_WALK_AIM_STEALTH", + "KEY_2", + "pcall", + "ACT_HL2MP_IDLE_CROUCH_CAMERA", + "SND_IGNORE_NAME", + "isbool", + "ACT_DI_ALYX_HEADCRAB_MELEE", + "ACT_MP_SWIM_IDLE", + "AddonMaterial", + "ACT_DOD_RUN_AIM_GREASE", + "ACT_HL2MP_JUMP_REVOLVER", + "ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER", + "Derma_DrawBackgroundBlur", + "ACT_VICTORY_DANCE", + "markup", + "NAV_MESH_JUMP", + "ACT_HL2MP_WALK_REVOLVER", + "CT_DOWNTRODDEN", + "getfenv", + "PrecacheSentenceGroup", + "MAT_GLASS", + "file", + "ACT_WALK_PACKAGE", + "ConVar", + "DisableClipping", + "ACT_GESTURE_TURN_RIGHT45", + "ACT_HL2MP_JUMP_MAGIC", + "ACT_FLINCH_PHYSICS", + "ACT_BARNACLE_CHOMP", + "Derma_Install_Convar_Functions", + "AddOriginToPVS", + "ACT_RANGE_ATTACK_PISTOL", + "ACT_HL2MP_WALK_CROUCH_MAGIC", + "ACT_HL2MP_RUN_MAGIC", + "ACT_HL2MP_IDLE_MAGIC", + "ACT_HL2MP_SIT_SLAM", + "ACT_ZOMBIE_CLIMB_START", + "ACT_DOD_PRONEWALK_IDLE_C96", + "ACT_BUSY_SIT_CHAIR_ENTRY", + "ACT_DOD_ZOOMLOAD_BAZOOKA", + "ACT_DOD_RUN_IDLE_BAZOOKA", + "ACT_RUN_CROUCH_AIM_RIFLE", + "ACT_OBJ_ASSEMBLING", + "ACT_GMOD_GESTURE_RANGE_FRENZY", + "TEXTUREFLAGS_BORDER", + "ACT_MP_PRIMARY_GRENADE2_ATTACK", + "ACT_HL2MP_WALK_CROUCH_ZOMBIE_05", + "widgets", + "ACT_DOD_PRONE_AIM_GREASE", + "Player", + "ACT_HL2MP_WALK_CROUCH_ZOMBIE_04", + "ACT_HL2MP_WALK_CROUCH_ZOMBIE_02", + "SNDLVL_IDLE", + "File", + "ACT_RUN_AIM_STIMULATED", + "ACT_HL2MP_WALK_ZOMBIE_03", + "ACT_HL2MP_WALK_ZOMBIE_02", + "ACT_HL2MP_RUN_CHARGING", + "DColorCube", + "LAST_VISIBLE_CONTENTS", + "ACT_DOD_STAND_IDLE_TOMMY", + "ACT_GMOD_TAUNT_CHEER", + "ACT_DOD_PRIMARYATTACK_PRONE_PSCHRECK", + "ACT_OVERLAY_SHIELD_UP_IDLE", + "ACT_GESTURE_RANGE_ATTACK1_LOW", + "ACT_CROUCHING_SHIELD_UP_IDLE", + "ACT_VM_IOUT_EMPTY", + "ACT_GMOD_GESTURE_WAVE", + "ACT_GMOD_TAUNT_SALUTE", + "CAP_MOVE_GROUND", + "ACT_RUN_RPG", + "NamedColor", + "ACT_GMOD_GESTURE_BECON", + "MOVETYPE_CUSTOM", + "ACT_DOD_RELOAD_PRONE_BAZOOKA", + "COND_SEE_FEAR", + "ACT_GESTURE_FLINCH_BLAST", + "ScrW", + "ACT_RANGE_ATTACK_TRIPWIRE", + "ACT_MP_GESTURE_VC_HANDMOUTH_PDA", + "ACT_GESTURE_FLINCH_LEFTARM", + "CAP_USE", + "ACT_TRIPMINE_GROUND", + "ACT_IDLE_STIMULATED", + "ACT_IDLE", + "ACT_VM_IDLE_DEPLOYED_6", + "SCHED_ALERT_STAND", + "ACT_RUN_CROUCH_RIFLE", + "IN_ZOOM", + "FL_ONGROUND", + "ACT_MP_GESTURE_VC_HANDMOUTH_MELEE", + "ACT_VM_RELOAD_END", + "ACT_DOD_HS_IDLE_PISTOL", + "ACT_MP_GESTURE_VC_FISTPUMP_SECONDARY", + "MAT_DIRT", + "SIM_LOCAL_FORCE", + "ACT_GESTURE_RANGE_ATTACK2", + "ACT_MP_GESTURE_VC_NODYES_PRIMARY", + "ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY", + "resource", + "ACT_MP_ATTACK_STAND_PDA", + "Material", + "ACT_MP_JUMP_FLOAT_PDA", + "ACT_MP_JUMP_START_PDA", + "ACT_SLAM_STICKWALL_DETONATOR_HOLSTER", + "ACT_GESTURE_FLINCH_BLAST_DAMAGED", + "DIRECTIONAL_USE", + "HITGROUP_STOMACH", + "CMoveData", + "DTileLayout", + "ACT_MP_CROUCH_PDA", + "ACT_ZOMBIE_CLIMB_END", + "KEY_Q", + "ACT_MP_ATTACK_CROUCH_GRENADE_BUILDING", + "GetDemoFileDetails", + "ACT_DIE_FRONTSIDE", + "ACT_MP_SWIM_PRIMARY", + "ACT_MP_ATTACK_CROUCH_BUILDING", + "DEFINE_BASECLASS", + "ACT_SLAM_STICKWALL_DETONATE", + "ACT_MP_ATTACK_STAND_BUILDING", + "DHorizontalDivider", + "ACT_HL2MP_GESTURE_RELOAD_MAGIC", + "ACT_WALK_CROUCH_AIM_RIFLE", + "Awesomium", + "ACT_MP_CROUCHWALK_BUILDING", + "ACT_MP_AIRWALK_BUILDING", + "ACT_MP_WALK_BUILDING", + "ACT_DOD_PRONEWALK_IDLE_GREASE", + "MATERIAL_LINE_LOOP", + "KEY_F2", + "ACT_MP_SECONDARY_GRENADE2_ATTACK", + "ACT_MP_ATTACK_CROUCH_POSTFIRE", + "ACT_MP_GESTURE_VC_FINGERPOINT_SECONDARY", + "DPropertySheet", + "ACT_DOD_SPRINT_IDLE_30CAL", + "GameDetails", + "USE_OFF", + "MAT_PLASTIC", + "ACT_VM_IDLE_DEPLOYED_8", + "ACT_MP_GRENADE2_ATTACK", + "ACT_MP_GRENADE1_ATTACK", + "ACT_DOD_CROUCHWALK_AIM_TOMMY", + "ACT_MP_RELOAD_STAND_SECONDARY_END", + "ACT_VM_FIZZLE", + "GetGlobalFloat", + "ACT_SPRINT", + "ACT_WALK", + "SCREENFADE", + "tobool", + "ACT_DOD_PRONE_AIM_SPADE", + "ACT_DOD_RUN_AIM_GREN_FRAG", + "KEY_PAD_2", + "DMG_CLUB", + "ACT_WALK_AIM_STEALTH_PISTOL", + "GO_WEST", + "IN_GRENADE2", + "SOLID_OBB", + "EF_PARENT_ANIMATES", + "ACT_CROUCHIDLE", + "BONE_PHYSICALLY_SIMULATED", + "ACT_HL2MP_SIT_CROSSBOW", + "ACT_MP_CROUCH_MELEE", + "DFileBrowser", + "SOLID_BBOX", + "CHAN_WEAPON", + "ErrorNoHalt", + "ErrorNoHaltWithStack", + "ACT_HL2MP_RUN_DUEL", + "ACT_DOD_WALK_ZOOM_BAZOOKA", + "type", + "MOVETYPE_STEP", + "ACT_MP_GESTURE_FLINCH_HEAD", + "ACT_SLAM_THROW_TO_TRIPMINE_ND", + "ACT_DOD_HS_IDLE_MP44", + "ACT_DOD_STAND_IDLE_PISTOL", + "ACT_GESTURE_RANGE_ATTACK_AR1", + "ACT_DOD_RUN_AIM_PISTOL", + "ACT_DIE_HEADSHOT", + "ACT_MP_JUMP_FLOAT_SECONDARY", + "ACT_FLINCH_LEFTARM", + "NumModelSkins", + "ACT_MP_RELOAD_SWIM_PRIMARY_END", + "DrawSharpen", + "ACT_VM_USABLE_TO_UNUSABLE", + "ACT_DOD_WALK_IDLE_BAZOOKA", + "ACT_VM_PRIMARYATTACK_DEPLOYED_EMPTY", + "ACT_MP_ATTACK_AIRWALK_PRIMARY", + "ACT_MP_ATTACK_STAND_PRIMARY_DEPLOYED", + "ACT_RUN_CROUCH_RPG", + "TYPE_NAVLADDER", + "DebugInfo", + "ACT_HL2MP_RUN_MELEE2", + "ACT_SLAM_THROW_THROW2", + "BOX_LEFT", + "ACT_DOD_CROUCHWALK_AIM_30CAL", + "ACT_MP_RELOAD_SWIM", + "ACT_GESTURE_BARNACLE_STRANGLE", + "ACT_MP_RELOAD_CROUCH_LOOP", + "CONTENTS_CURRENT_DOWN", + "DProperty_Boolean", + "ACT_MP_ATTACK_SWIM_GRENADE", + "math", + "ImageCheckBox", + "ACT_MP_ATTACK_CROUCH_PRIMARYFIRE", + "ACT_DOD_PRIMARYATTACK_PRONE", + "ACT_MP_ATTACK_STAND_PRIMARYFIRE", + "ACT_SHOTGUN_IDLE_DEEP", + "CAP_OPEN_DOORS", + "RecordDemoFrame", + "MAT_COMPUTER", + "Vehicle", + "ACT_HL2MP_IDLE_SLAM", + "ACT_MP_CROUCH_DEPLOYED_IDLE", + "FrameNumber", + "ACT_DOD_PLANT_TNT", + "ACT_DOD_HS_CROUCH_TOMMY", + "CLASS_NONE", + "ACT_DOD_HS_IDLE_30CAL", + "ACT_MP_GESTURE_FLINCH_LEFTARM", + "ACT_DOD_PRIMARYATTACK_PRONE_C96", + "ACT_DOD_SECONDARYATTACK_CROUCH", + "ACT_DOD_PRONE_ZOOM_FORWARD_PSCHRECK", + "ACT_DOD_CROUCH_IDLE_PISTOL", + "ACT_GESTURE_RANGE_ATTACK_THROW", + "ACT_DEPLOY_IDLE", + "ACT_DOD_RELOAD_PRONE_BAR", + "ACT_DOD_RELOAD_PRONE_MP40", + "ACT_COWER", + "GESTURE_SLOT_JUMP", + "ACT_DOD_RELOAD_DEPLOYED_MG34", + "ACT_DOD_RELOAD_DEPLOYED_MG", + "ACT_IDLE_RPG_RELAXED", + "AccessorFunc", + "ACT_DOD_RELOAD_CROUCH_PISTOL", + "GetConVarString", + "ScreenScale", + "ACT_DOD_RELOAD_CROUCH_MP40", + "ACT_SLAM_THROW_DETONATE", + "ACT_GESTURE_FLINCH_BLAST_SHOTGUN", + "ACT_DOD_WALK_IDLE_MP40", + "CtrlListBox", + "SIM_GLOBAL_ACCELERATION", + "ACT_HL2MP_WALK_CROUCH_KNIFE", + "ACT_DOD_RUN_AIM_TOMMY", + "SANDBOX", + "ACT_PHYSCANNON_ANIMATE", + "ACT_SLAM_DETONATOR_STICKWALL_DRAW", + "ACT_SLAM_STICKWALL_ATTACH", + "ACT_GESTURE_TURN_LEFT", + "ACT_VM_IDLE_DEPLOYED_1", + "ACT_BUSY_LEAN_LEFT", + "EFL_NO_ROTORWASH_PUSH", + "ACT_DOD_SECONDARYATTACK_RIFLE", + "IMAGE_FORMAT_ARGB8888", + "DMG_POISON", + "SortedPairsByMemberValue", + "ACT_DOD_WALK_ZOOM_BOLT", + "SF_ROLLERMINE_FRIENDLY", + "ACT_MP_JUMP_FLOAT_BUILDING", + "LoadLastMap", + "ACT_HL2MP_SWIM_MELEE", + "CLASS_PLAYER_ALLY_VITAL", + "ACT_DOD_PRONE_AIM_PISTOL", + "ACT_DOD_WALK_AIM_KNIFE", + "ACT_VM_DEPLOY_8", + "ACT_GMOD_GESTURE_ITEM_THROW", + "SpawnIcon", + "ACT_DOD_CROUCH_IDLE_MG", + "gmsave", + "CSEnt", + "SOLID_OBB_YAW", + "ACT_GESTURE_TURN_RIGHT90_FLAT", + "bf_read", + "ACT_DOD_STAND_AIM_BOLT", + "ACT_DOD_STAND_AIM_RIFLE", + "ACT_DOD_RUN_IDLE_C96", + "ACT_SLAM_STICKWALL_TO_THROW", + "ACT_HL2MP_JUMP_CAMERA", + "ACT_SLAM_STICKWALL_IDLE", + "ACT_WALK_AIM_PISTOL", + "ACT_MP_GESTURE_VC_THUMBSUP_SECONDARY", + "FL_DISSOLVING", + "ACT_RANGE_ATTACK_RPG", + "ACT_DOD_CROUCH_AIM_30CAL", + "ACT_VM_UNDEPLOY_4", + "ACT_DOD_CROUCHWALK_IDLE_RIFLE", + "ACT_VM_IDLE_DEPLOYED_5", + "KEY_SCROLLLOCKTOGGLE", + "isstring", + "ACT_IDLE_SHOTGUN_STIMULATED", + "VMatrix", + "ACT_DOD_RELOAD_PRONE_GARAND", + "controlpanel", + "ACT_MP_RELOAD_AIRWALK_PRIMARY", + "DVerticalDivider", + "ACT_VM_RELOAD_SILENCED", + "SScale", + "CLASS_HACKED_ROLLERMINE", + "ACT_VM_DOWN_M203", + "DListBox", + "ACT_RUN_RIFLE_RELAXED", + "ACT_OBJ_RUNNING", + "ACT_WALK_STEALTH", + "IsFirstTimePredicted", + "ACT_VM_PRIMARYATTACK_DEPLOYED_6", + "ACT_MP_JUMP_LAND_BUILDING", + "ACT_PHYSCANNON_DETACH", + "DButton", + "ACT_VM_HITLEFT", + "ACT_JUMP", + "SCHED_GET_HEALTHKIT", + "ACT_RELOAD" + -- End generated code +} diff --git a/garrysmod/gamemodes/helix/LICENSE.txt b/garrysmod/gamemodes/helix/LICENSE.txt new file mode 100644 index 0000000..3662d63 --- /dev/null +++ b/garrysmod/gamemodes/helix/LICENSE.txt @@ -0,0 +1,23 @@ + +The MIT License (MIT) + +Copyright (c) 2015 Brian Hang, Kyu Yeon Lee +Copyright (c) 2018-2021 Alexander Grist-Hucker, Igor Radovanovic + +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. diff --git a/garrysmod/gamemodes/helix/README.md b/garrysmod/gamemodes/helix/README.md new file mode 100644 index 0000000..7cba857 --- /dev/null +++ b/garrysmod/gamemodes/helix/README.md @@ -0,0 +1,50 @@ +

    + Helix +

    + +

    + + Discord + + + Build Status + +

    + +Helix is a framework for roleplay gamemodes in [Garry's Mod](https://gmod.facepunch.com/), based off of [NutScript 1.1](https://github.com/rebel1324/NutScript). Helix provides a stable, feature-filled, open-source, and DRM-free base so you can focus more on the things you want: making gameplay. + +## Getting Started +Visit the getting started guide in the [documentation](https://docs.gethelix.co/manual/getting-started/) for an in-depth guide. + +If you know what you're doing, a quick start for bootstrapping your own schema is forking/copying the skeleton schema at https://github.com/nebulouscloud/helix-skeleton. The skeleton contains all the important elements you need to have a functioning schema so you can get to coding right away. + +You can also use our HL2 RP schema at https://github.com/nebulouscloud/helix-hl2rp as a base to work off of if you need something more fleshed out. + +## Plugins +If you'd like to enhance your gamemode, you can use any of the freely provided plugins available at the [Helix Plugin Center](https://plugins.gethelix.co). It is also encouraged to submit your own plugins for others to find and use at https://github.com/nebulouscloud/helix-plugins + +## Documentation +Up-to-date documentation can be found at https://docs.gethelix.co. This is automatically updated when commits are pushed to the master branch. + +If you'd like to ask some questions or integrate with the community, you can always join our [Discord](https://discord.gg/2AutUcF) server. We highly encourage you to search through the documentation before posting a question - the docs contain a good deal of information about how the various systems in Helix work, and it might explain what you're looking for. + +### Building documentation +If you're planning on contributing to the documentation, you'll probably want to preview your changes before you commit. The documentation can be built using [LDoc](https://github.com/impulsh/ldoc) - note that we use a forked version to add some functionality. You'll need [LuaRocks](https://luarocks.org/) installed in order to get started. + +```shell +# installing ldoc +git clone https://github.com/impulsh/ldoc +cd ldoc +luarocks make + +# navigate to the helix repo folder and run +ldoc . +``` + +You may not see the syntax highlighting work on your local copy - you'll need to copy the files in `docs/js` and `docs/css` over into the `docs/html` folder after it's done building. + +## Contributing +Feel free to submit a pull request with any fixes/changes that you might find beneficial. Currently, there are no solid contributing guidelines other than keeping your code consistent with the rest of the framework. + +## Acknowledgements +Helix is a fork of NutScript 1.1 by [Chessnut](https://github.com/brianhang) and [rebel1324](https://github.com/rebel1324). diff --git a/garrysmod/gamemodes/helix/config.ld b/garrysmod/gamemodes/helix/config.ld new file mode 100644 index 0000000..81ac7be --- /dev/null +++ b/garrysmod/gamemodes/helix/config.ld @@ -0,0 +1,77 @@ + +file = { + "gamemode", + "plugins", + "docs/hooks", + exclude = {"gamemode/core/libs/thirdparty"} +} + +module_file = { + Character = "gamemode/core/meta/sh_character.lua", + Entity = "gamemode/core/meta/sh_entity.lua", + Inventory = "gamemode/core/meta/sh_inventory.lua", + Item = "gamemode/core/meta/sh_item.lua", + Player = "gamemode/core/meta/sh_player.lua" +} + +dir = "docs/html" +project = "Helix" +title = "Helix Documentation" + +no_space_before_args = true +style = "docs/css" +template = "docs/templates" +format = "markdown" +ignore = true +topics = "docs/manual" +use_markdown_titles = true +kind_names = {module = "Libraries", topic = "Manual"} +merge = true +sort = true +sort_modules = true + +simple_args_string = true -- we show optionals/defaults outside of the display name +strip_metamethod_prefix = true -- remove the name of the table when displaying metamethod names +no_viewed_topic_at_top = true -- don't put the currently viewed topic at the top +use_new_templates = true -- new templating system +pretty_urls = true -- avoid showing .html in urls +pretty_topic_names = true -- strips extension from manual filenames, this does not check filename collisions + +custom_tags = { + {"realm", hidden = true}, + {"internal", hidden = true} +} + +custom_display_name_handler = function(item, default_handler) + if (item.type == "function" and item.module) then + if (item.module.type == "classmod" or item.module.type == "panel") then + return item.module.mod_name .. ":" .. default_handler(item) + elseif (item.module.type == "hooks") then + return item.module.mod_name:upper() .. ":" .. default_handler(item) + end + end + + return default_handler(item) +end + +new_type("hooks", "Hooks", true) +new_type("panel", "Panels", true) + +-- helix types +tparam_alias("char", "Character") +tparam_alias("inventory", "Inventory") +tparam_alias("item", "Item") +tparam_alias("ixtype", "ix.type") +tparam_alias("date", "date") + +-- standard types +tparam_alias("string", "string") +tparam_alias("bool", "boolean") +tparam_alias("func", "function") +tparam_alias("player", "Player") +tparam_alias("entity", "Entity") +tparam_alias("color", "color") +tparam_alias("tab", "table") +tparam_alias("material", "material") +tparam_alias("vector", "vector") +tparam_alias("angle", "angle") diff --git a/garrysmod/gamemodes/helix/docs/banner.gif b/garrysmod/gamemodes/helix/docs/banner.gif new file mode 100644 index 0000000..6024869 Binary files /dev/null and b/garrysmod/gamemodes/helix/docs/banner.gif differ diff --git a/garrysmod/gamemodes/helix/docs/css/highlight.css b/garrysmod/gamemodes/helix/docs/css/highlight.css new file mode 100644 index 0000000..e3f984a --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/css/highlight.css @@ -0,0 +1,96 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + color: #333; +} + +.hljs-comment, +.hljs-quote { + color: #535346; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/garrysmod/gamemodes/helix/docs/css/ldoc.css b/garrysmod/gamemodes/helix/docs/css/ldoc.css new file mode 100644 index 0000000..6db10fb --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/css/ldoc.css @@ -0,0 +1,522 @@ + +:root { + --content-width: 1200px; + --sidebar-width: 330px; + + --padding-big: 48px; + --padding-normal: 24px; + --padding-small: 16px; + --padding-tiny: 10px; + + --font-massive: 32px; + --font-huge: 24px; + --font-big: 18px; + --font-normal: 16px; + --font-tiny: 12px; + + --font-style-normal: Segoe UI, Helvetica, Arial, sans-serif; + --font-style-code: Consolas, monospace; + + --color-accent: rgb(115, 53, 142); + --color-accent-dark: rgb(85, 39, 105); + --color-white: rgb(255, 255, 255); + --color-offwhite: rgb(200, 200, 200); + --color-white-accent: rgb(203, 190, 209); + --color-black: rgb(0, 0, 0); + --color-lightgrey: rgb(160, 160, 160); + --color-background-light: rgb(240, 240, 240); + --color-background-dark: rgb(33, 33, 33); +} + +* { + padding: 0; + margin: 0; + box-sizing: border-box; +} + +body { + background-color: var(--color-background-light); + font-family: var(--font-style-normal); + + display: flex; + flex-direction: column; +} + +a { + color: inherit; + text-decoration: inherit; +} + +h1, h2, h3, h4 { + font-weight: 400; +} + +ul li { + margin-left: var(--padding-small); +} + +/* landing */ +.landing { + background-color: var(--color-accent); + color: var(--color-white); + + padding: 128px 0 128px 0; +} + +.landing h1 { + margin: 0; + padding: 0; + border: none; + + font-weight: 100; + font-size: var(--font-massive); + text-align: center; +} + +.wrapper { + padding: var(--padding-small); +} + +details { + user-select: none; +} + +details summary { + outline: none; +} + +code { + font-family: "Source Code Pro", monospace; + font-size: 85%; + white-space: pre; + tab-size: 4; + -moz-tab-size: 4; + padding: 2px 4px; + background-color: rgb(33, 33, 33, 0.1); +} + +pre { + background-color: rgb(33, 33, 33, 0.1); + margin-top: var(--padding-small); + padding: var(--padding-tiny); + overflow: auto; +} + +pre code { + background-color: transparent; +} + +span.realm { + width: 14px; + height: 14px; + border-radius: 3px; + display: inline-block; + margin-right: 6px; +} + +span.realm.shared { + background: linear-gradient(45deg, #f80 0%, #f80 50%, #08f 51%, #08f 100%); +} + +span.realm.client { + background-color: #f80; +} + +span.realm.server { + background-color: #08f; +} + +/* wrapper element for sidebar/content */ +main { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + + width: var(--content-width); + margin: auto; +} + +/* sidebar */ +nav { + color: var(--color-offwhite); + background-color: var(--color-background-dark); + + position: fixed; + display: flex; + flex-direction: column; + + width: var(--sidebar-width); + height: 100%; +} + +/* sidebar header */ +nav header { + color: var(--color-white); + background-color: var(--color-accent); + + padding: var(--padding-small); +} + +nav header h1 { + font-size: var(--font-huge); + font-weight: 100; + text-align: center; + + margin-bottom: var(--padding-small); +} + +#search { + background-color: var(--color-accent-dark); + color: var(--color-white); + + border: none; + font-size: var(--font-normal); + outline: none; + + width: 100%; + padding: 6px; +} + +#search::placeholder { + color: var(--color-white-accent); +} + +#search::-webkit-search-cancel-button { + display: none; +} + +/* sidebar contents */ +nav section { + padding: var(--padding-small); + overflow: auto; +} + +nav section ul { + list-style-type: none; +} + +nav section::-webkit-scrollbar, +pre::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +nav section::-webkit-scrollbar-track, +pre::-webkit-scrollbar-track { + background: transparent; +} + +nav section::-webkit-scrollbar-thumb { + background-color: var(--color-lightgrey); +} + +pre::-webkit-scrollbar-thumb { + background-color: var(--color-lightgrey); +} + +/* sidebar contents category */ +nav section details.category { + padding-top: var(--padding-tiny); +} + +nav section details.category > ul > li { + margin: 0; + line-height: 1.5; +} + +nav section details.category > ul > li a { + display: inline-block; + width: 90%; +} + +nav section details.category:first-of-type { + padding-top: calc(var(--padding-tiny) * -1); +} + +nav section details.category summary::-webkit-details-marker { + opacity: 0.5; + cursor: pointer; +} + +nav section details.category summary h2 { + color: var(--color-accent); + + font-size: var(--font-big); + letter-spacing: 2px; + text-transform: uppercase; + cursor: pointer; + + padding-bottom: var(--padding-tiny); +} + +/* content */ +article { + background-color: rgb(255, 255, 255); + + width: calc(100% - var(--sidebar-width)); + min-height: 100vh; + margin-left: var(--sidebar-width); +} + +article .wrapper > *:first-child { + margin-top: 0; +} + +/* header */ +article header { + color: rgb(255, 255, 255); + background-color: rgb(115, 53, 142); + padding: var(--padding-tiny); +} + +article header h1 { + border-bottom: 1px solid rgba(255, 255, 255, 0.25); + padding-bottom: 8px; + font-family: var(--font-style-code); + margin: 0; +} + +article header h2 { + padding-top: var(--padding-tiny); + margin: 0; + font-size: var(--font-normal); + font-weight: normal; +} + +article header.module a { + color: white !important; + text-decoration: underline; +} + +details.category > summary { + list-style: none; +} + +details.category > summary::-webkit-details-marker { + display: none; +} + +article h1 { + font-size: 28px; + font-weight: 600; + border-bottom: 1px solid rgba(0, 0, 0, 0.25); + margin-top: 24px; + margin-bottom: 16px; + padding-bottom: 8px; +} + +article h2 { + font-size: 20px; + font-weight: 600; + margin-top: 12px; +} + +article h3 { + color: rgb(115, 53, 142); + margin-top: var(--padding-tiny); + text-transform: uppercase; +} + +article p { + margin-top: var(--padding-small); +} + +article p a, +article ul li a, +article h1 a, +article h2 a { + color: rgb(115, 53, 142); + font-weight: 600; +} + +article h1.title { + color: rgb(255, 255, 255); + background-color: rgb(115, 53, 142); + margin-top: var(--padding-small); + margin-bottom: 0; + padding: var(--padding-tiny); + + font-size: var(--font-big); + font-weight: 100; + letter-spacing: 2px; + text-transform: uppercase; +} + +a.reference { + color: rgb(115, 53, 142); + float: right; + margin-top: 8px; + padding-left: 8px; + font-size: 14px; + font-weight: 600; +} + +.notice { + --color-notice-background: var(--color-accent); + --color-notice-text: var(--color-notice-background); + + margin-top: var(--padding-tiny); + border: 2px solid var(--color-notice-background); +} + +.notice.error { + --color-notice-background: rgb(194, 52, 130); +} + +.notice.warning { + --color-notice-background: rgb(224, 169, 112); + --color-notice-text: rgb(167, 104, 37); +} + +.notice .title { + color: var(--color-white); + background-color: var(--color-notice-background); + + padding: var(--padding-tiny); + font-size: var(--font-normal); + + text-transform: uppercase; + letter-spacing: 2px; +} + +.notice p { + color: var(--color-notice-text); + + margin: 0 !important; + padding: var(--padding-tiny); +} + +/* function/table */ +.method { + display: flex; + flex-flow: column; + background-color: rgb(230, 230, 230); + padding: var(--padding-tiny); + margin-top: var(--padding-small); +} + +.method header { + color: rgb(0, 0, 0); + background-color: inherit; + padding: 0; + order: -1; +} + +.method header .anchor { + color: inherit; + text-decoration: inherit; +} + +.method header .anchor:target h1 { + background-color: rgba(115, 53, 142, 0.2); + background-clip: content-box; +} + +.method header h1 { + font-family: "Source Code Pro", monospace; + padding-bottom: var(--padding-tiny); + border-bottom: 1px solid rgba(0, 0, 0, 0.25); + font-size: 20px; +} + +.method header p:first-of-type { + margin-top: var(--padding-tiny); +} + +.method h3 { + color: rgb(115, 53, 142); + font-size: var(--font-normal); + letter-spacing: 2px; + text-transform: uppercase; +} + +.method pre { + margin-top: var(--padding-tiny); +} + +@media only screen and (max-width: 1100px) { + main nav { + position: inherit; + } + + main article { + margin-left: 0; + } +} + +.method ul { + margin-top: var(--padding-tiny); + background-color: inherit; +} + +.method ul li { + list-style: none; + margin: 4px 0 0 var(--padding-normal); +} + +.method ul li:first-of-type { + margin-top: 0; +} + +.method ul li p { + margin: 4px 0 0 var(--padding-normal); +} + +.method ul li pre { + margin: 4px 0 0 var(--padding-normal); +} + +.method ul li a { + color: rgb(115, 53, 142); + font-weight: 600; +} + +/* we have to manually specify these instead of making a shared class since you cannot customize the parameter class in ldoc */ +.parameter, .type, .default { + display: inline-block; + color: rgb(255, 255, 255) !important; + + padding: 4px; + font-size: 14px; + font-family: "Source Code Pro", monospace; +} + +.parameter { + background-color: rgb(115, 53, 142); +} + +.type { + background-color: rgb(31, 141, 155); +} + +a.type { + font-weight: 300 !important; + text-decoration: underline; +} + +.default { + background-color: rgb(193, 114, 11); +} + +.type a { + padding: 0; +} + +.or { + color: rgba(115, 53, 142, 0.5); + background-color: inherit; + + width: calc(100% - 32px); + height: 8px; + margin: 0 0 8px 32px; + + text-align: center; + font-weight: 600; + border-bottom: 1px solid rgba(115, 53, 142, 0.5); +} + +.or span { + background-color: inherit; + padding: 0 8px 0 8px; +} diff --git a/garrysmod/gamemodes/helix/docs/hooks/class.lua b/garrysmod/gamemodes/helix/docs/hooks/class.lua new file mode 100644 index 0000000..140441e --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/hooks/class.lua @@ -0,0 +1,42 @@ + +-- luacheck: ignore 111 + +--[[-- +Class setup hooks. + +As with `Faction`s, `Class`es get their own hooks for when players leave/join a class, etc. These hooks are only +valid in class tables that are created in `schema/classes/sh_classname.lua`, and cannot be used like regular gamemode hooks. +]] +-- @hooks Class + +--- Whether or not a player can switch to this class. +-- @realm shared +-- @player client Client that wants to switch to this class +-- @treturn bool True if the player is allowed to switch to this class +-- @usage function CLASS:CanSwitchTo(client) +-- return client:IsAdmin() -- only admins allowed in this class! +-- end +function CanSwitchTo(client) +end + +--- Called when a character has left this class and has joined a different one. You can get the class the character has +-- has joined by calling `character:GetClass()`. +-- @realm server +-- @player client Player who left this class +function OnLeave(client) +end + +--- Called when a character has joined this class. +-- @realm server +-- @player client Player who has joined this class +-- @usage function CLASS:OnSet(client) +-- client:SetModel("models/police.mdl") +-- end +function OnSet(client) +end + +--- Called when a character in this class has spawned in the world. +-- @realm server +-- @player client Player that has just spawned +function OnSpawn(client) +end diff --git a/garrysmod/gamemodes/helix/docs/hooks/faction.lua b/garrysmod/gamemodes/helix/docs/hooks/faction.lua new file mode 100644 index 0000000..cfbdea2 --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/hooks/faction.lua @@ -0,0 +1,48 @@ + +-- luacheck: ignore 111 + +--[[-- +Faction setup hooks. + +Factions get their own hooks that are called for various reasons, but the most common one is to set up a character +once it's created and assigned to a certain faction. For example, giving a police faction character a weapon on creation. +These hooks are used in faction tables that are created in `schema/factions/sh_factionname.lua` and cannot be used like +regular gamemode hooks. +]] +-- @hooks Faction + +--- Called when the default name for a character needs to be retrieved (i.e upon initial creation). +-- @realm shared +-- @player client Client to get the default name for +-- @treturn string Default name for the newly created character +-- @usage function FACTION:GetDefaultName(client) +-- return "MPF-RCT." .. tostring(math.random(1, 99999)) +-- end +function GetDefaultName(client) +end + +--- Called when a character has been initally created and assigned to this faction. +-- @realm server +-- @player client Client that owns the character +-- @char character Character that has been created +-- @usage function FACTION:OnCharacterCreated(client, character) +-- local inventory = character:GetInventory() +-- inventory:Add("pistol") +-- end +function OnCharacterCreated(client, character) +end + +--- Called when a character in this faction has spawned in the world. +-- @realm server +-- @player client Player that has just spawned +function OnSpawn(client) +end + +--- Called when a player's character has been transferred to this faction. +-- @realm server +-- @char character Character that was transferred +-- @usage function FACTION:OnTransferred(character) +-- character:SetModel(self.models[1]) +-- end +function OnTransferred(character) +end diff --git a/garrysmod/gamemodes/helix/docs/hooks/plugin.lua b/garrysmod/gamemodes/helix/docs/hooks/plugin.lua new file mode 100644 index 0000000..2765433 --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/hooks/plugin.lua @@ -0,0 +1,1157 @@ + +-- luacheck: ignore 111 + +--[[-- +Global hooks for general use. + +Plugin hooks are regular hooks that can be used in your schema with `Schema:HookName(args)`, in your plugin with +`PLUGIN:HookName(args)`, or in your addon with `hook.Add("HookName", function(args) end)`. +]] +-- @hooks Plugin + +--- Adjusts the data used just before creating a new character. +-- @realm server +-- @player client Player that is creating the character +-- @tab payload Table of data to be used for character creation +-- @tab newPayload Table of data be merged with the current payload +-- @usage function PLUGIN:AdjustCreationPayload(client, payload, newPayload) +-- newPayload.money = payload.attributes["stm"] -- Sets the characters initial money to the stamina attribute value. +-- end +function AdjustCreationPayload(client, payload, newPayload) +end + +--- Adjusts a player's current stamina offset amount. This is called when the player's stamina is about to be changed; every +-- `0.25` seconds on the server, and every frame on the client. +-- @realm shared +-- @player client Player whose stamina is changing +-- @number baseOffset Amount the stamina is changing by. This can be a positive or negative number depending if they are +-- exhausting or regaining stamina +-- @treturn number New offset to use +-- @usage function PLUGIN:AdjustStaminaOffset(client, baseOffset) +-- return baseOffset * 2 -- Drain/Regain stamina twice as fast. +-- end +function AdjustStaminaOffset(client, baseOffset) +end + +--- Creates the business panel in the tab menu. +-- @realm client +-- @treturn bool Whether or not to create the business menu +-- @usage function PLUGIN:BuildBusinessMenu() +-- return LocalPlayer():IsAdmin() -- Only builds the business menu for admins. +-- end +function BuildBusinessMenu() +end + +--- Whether or not a message can be auto formatted with punctuation and capitalization. +-- @realm server +-- @player speaker Player that sent the message +-- @string chatType Chat type of the message. This will be something registered with `ix.chat.Register` - like `ic`, `ooc`, etc. +-- @string text Unformatted text of the message +-- @treturn bool Whether or not to allow auto formatting on the message +-- @usage function PLUGIN:CanAutoFormatMessage(speaker, chatType, text) +-- return false -- Disable auto formatting outright. +-- end +function CanAutoFormatMessage(speaker, chatType, text) +end + +--- Whether or not certain information can be displayed in the character info panel in the tab menu. +-- @realm client +-- @tab suppress Information to **NOT** display in the UI - modify this to change the behaviour. This is a table of the names of +-- some panels to avoid displaying. Valid names include: +-- +-- - `time` - current in-game time +-- - `name` - name of the character +-- - `description` - description of the character +-- - `characterInfo` - entire panel showing a list of additional character info +-- - `faction` - faction name of the character +-- - `class` - name of the character's class if they're in one +-- - `money` - current money the character has +-- - `attributes` - attributes list for the character +-- +-- Note that schemas/plugins can add additional character info panels. +-- @usage function PLUGIN:CanCreateCharacterInfo(suppress) +-- suppress.attributes = true -- Hides the attributes panel from the character info tab +-- end +function CanCreateCharacterInfo(suppress) +end + +--- Whether or not the ammo HUD should be drawn. +-- @realm client +-- @entity weapon Weapon the player currently is holding +-- @treturn bool Whether or not to draw the ammo hud +-- @usage function PLUGIN:CanDrawAmmoHUD(weapon) +-- if (weapon:GetClass() == "weapon_frag") then -- Hides the ammo hud when holding grenades. +-- return false +-- end +-- end +function CanDrawAmmoHUD(weapon) +end + +--- Called when a player tries to use abilities on the door, such as locking. +-- @realm shared +-- @player client The client trying something on the door. +-- @entity door The door entity itself. +-- @number access The access level used when called. +-- @treturn bool Whether or not to allow the client access. +-- @usage function PLUGIN:CanPlayerAccessDoor(client, door, access) +-- return true -- Always allow access. +-- end +function CanPlayerAccessDoor(client, door, access) +end + +--- Whether or not a player is allowed to combine an item `other` into the given `item`. +-- @realm server +-- @player client Player attempting to combine an item into another +-- @number item instance ID of the item being dropped onto +-- @number other instance ID of the item being combined into the first item, this can be invalid due to it being from clientside +-- @treturn bool Whether or not to allow the player to combine the items +-- @usage function PLUGIN:CanPlayerCombineItem(client, item, other) +-- local otherItem = ix.item.instances[other] +-- +-- if (otherItem and otherItem.uniqueID == "soda") then +-- return false -- disallow combining any item that has a uniqueID equal to `soda` +-- end +-- end +function CanPlayerCombineItem(client, item, other) +end + +--- Whether or not a player is allowed to create a new character with the given payload. +-- @realm server +-- @player client Player attempting to create a new character +-- @tab payload Data that is going to be used for creating the character +-- @treturn bool Whether or not the player is allowed to create the character. This function defaults to `true`, so you +-- should only ever return `false` if you're disallowing creation. Otherwise, don't return anything as you'll prevent any other +-- calls to this hook from running. +-- @treturn string Language phrase to use for the error message +-- @treturn ... Arguments to use for the language phrase +-- @usage function PLUGIN:CanPlayerCreateCharacter(client, payload) +-- if (!client:IsAdmin()) then +-- return false, "notNow" -- only allow admins to create a character +-- end +-- end +-- -- non-admins will see the message "You are not allowed to do this right now!" +function CanPlayerCreateCharacter(client, payload) +end + +--- Whether or not a player is allowed to drop the given `item`. +-- @realm server +-- @player client Player attempting to drop an item +-- @number item instance ID of the item being dropped +-- @treturn bool Whether or not to allow the player to drop the item +-- @usage function PLUGIN:CanPlayerDropItem(client, item) +-- return false -- Never allow dropping items. +-- end +function CanPlayerDropItem(client, item) +end + +--- Whether or not a player can earn money at regular intervals. This hook runs only if the player's character faction has +-- a salary set - i.e `FACTION.pay` is set to something other than `0` for their faction. +-- @realm server +-- @player client Player to give money to +-- @tab faction Faction of the player's character +-- @treturn bool Whether or not to allow the player to earn salary +-- @usage function PLUGIN:CanPlayerEarnSalary(client, faction) +-- return client:IsAdmin() -- Restricts earning salary to admins only. +-- end +function CanPlayerEarnSalary(client, faction) +end + +--- Whether or not the player is allowed to enter observer mode. This is allowed only for admins by default and can be +-- customized by server owners if the server is using a CAMI-compliant admin mod. +-- @realm server +-- @player client Player attempting to enter observer +-- @treturn bool Whether or not to allow the player to enter observer +-- @usage function PLUGIN:CanPlayerEnterObserver(client) +-- return true -- Always allow observer. +-- end +function CanPlayerEnterObserver(client) +end + +--- Whether or not a player can equip the given `item`. This is called for items with `outfit`, `pacoutfit`, or `weapons` as +-- their base. Schemas/plugins can utilize this hook for their items. +-- @realm server +-- @player client Player attempting to equip the item +-- @tab item Item being equipped +-- @treturn bool Whether or not to allow the player to equip the item +-- @see CanPlayerUnequipItem +-- @usage function PLUGIN:CanPlayerEquipItem(client, item) +-- return client:IsAdmin() -- Restrict equipping items to admins only. +-- end +function CanPlayerEquipItem(client, item) +end + +--- Whether or not a player is allowed to hold an entity with the hands SWEP. +-- @realm server +-- @player client Player attempting to hold an entity +-- @entity entity Entity being held +-- @treturn bool Whether or not to allow the player to hold the entity +-- @usage function PLUGIN:CanPlayerHoldObject(client, entity) +-- return !(client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) -- Disallow players in observer holding objects. +-- end +function CanPlayerHoldObject(client, entity) +end + +--- Whether or not a player is allowed to interact with an entity's interaction menu if it has one. +-- @realm server +-- @player client Player attempting interaction +-- @entity entity Entity being interacted with +-- @string option Option selected by the player +-- @param data Any data passed with the interaction option +-- @treturn bool Whether or not to allow the player to interact with the entity +-- @usage function PLUGIN:CanPlayerInteractEntity(client, entity, option, data) +-- return false -- Disallow interacting with any entity. +-- end +function CanPlayerInteractEntity(client, entity, option, data) +end + +--- Whether or not a player is allowed to interact with an item via an inventory action (e.g picking up, dropping, transferring +-- inventories, etc). Note that this is for an item *table*, not an item *entity*. This is called after `CanPlayerDropItem` +-- and `CanPlayerTakeItem`. +-- @realm server +-- @player client Player attempting interaction +-- @string action The action being performed +-- @param item Item's instance ID or item table +-- @param data Any data passed with the action +-- @treturn bool Whether or not to allow the player to interact with the item +-- @usage function PLUGIN:CanPlayerInteractItem(client, action, item, data) +-- return false -- Disallow interacting with any item. +-- end +function CanPlayerInteractItem(client, action, item, data) +end + +--- Whether or not a plyer is allowed to join a class. +-- @realm shared +-- @player client Player attempting to join +-- @number class ID of the class +-- @tab info The class table +-- @treturn bool Whether or not to allow the player to join the class +-- @usage function PLUGIN:CanPlayerJoinClass(client, class, info) +-- return client:IsAdmin() -- Restrict joining classes to admins only. +-- end +function CanPlayerJoinClass(client, class, info) +end + +--- Whether or not a player can knock on the door with the hands SWEP. +-- @realm server +-- @player client Player attempting to knock +-- @entity entity Door being knocked on +-- @treturn bool Whether or not to allow the player to knock on the door +-- @usage function PLUGIN:CanPlayerKnock(client, entity) +-- return false -- Disable knocking on doors outright. +-- end +function CanPlayerKnock(client, entity) +end + +--- Whether or not a player can open a shipment spawned from the business menu. +-- @realm server +-- @player client Player attempting to open the shipment +-- @entity entity Shipment entity +-- @treturn bool Whether or not to allow the player to open the shipment +-- @usage function PLUGIN:CanPlayerOpenShipment(client, entity) +-- return client:Team() == FACTION_BMD -- Restricts opening shipments to FACTION_BMD. +-- end +function CanPlayerOpenShipment(client, entity) +end + +--- Whether or not a player is allowed to spawn a container entity. +-- @realm server +-- @player client Player attempting to spawn a container +-- @string model Model of the container being spawned +-- @entity entity Container entity +-- @treturn bool Whether or not to allow the player to spawn the container +-- @usage function PLUGIN:CanPlayerSpawnContainer(client, model, entity) +-- return client:IsAdmin() -- Restrict spawning containers to admins. +-- end +function CanPlayerSpawnContainer(client, model, entity) +end + +--- Whether or not a player is allowed to take an item and put it in their inventory. +-- @realm server +-- @player client Player attempting to take the item +-- @entity item Entity corresponding to the item +-- @treturn bool Whether or not to allow the player to take the item +-- @usage function PLUGIN:CanPlayerTakeItem(client, item) +-- return !(client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) -- Disallow players in observer taking items. +-- end +function CanPlayerTakeItem(client, item) +end + +--- Whether or not the player is allowed to punch with the hands SWEP. +-- @realm shared +-- @player client Player attempting throw a punch +-- @treturn bool Whether or not to allow the player to punch +-- @usage function PLUGIN:CanPlayerThrowPunch(client) +-- return client:GetCharacter():GetAttribute("str", 0) > 0 -- Only allow players with strength to punch. +-- end +function CanPlayerThrowPunch(client) +end + +--- Whether or not a player can trade with a vendor. +-- @realm server +-- @player client Player attempting to trade +-- @entity entity Vendor entity +-- @string uniqueID The uniqueID of the item being traded. +-- @bool isSellingToVendor If the client is selling to the vendor +-- @treturn bool Whether or not to allow the client to trade with the vendor +-- @usage function PLUGIN:CanPlayerTradeWithVendor(client, entity, uniqueID, isSellingToVendor) +-- return false -- Disallow trading with vendors outright. +-- end +function CanPlayerTradeWithVendor(client, entity, uniqueID, isSellingToVendor) +end + +--- Whether or not a player can unequip an item. +-- @realm server +-- @player client Player attempting to unequip an item +-- @tab item Item being unequipped +-- @treturn bool Whether or not to allow the player to unequip the item +-- @see CanPlayerEquipItem +-- @usage function PLUGIN:CanPlayerUnequipItem(client, item) +-- return false -- Disallow unequipping items. +-- end +function CanPlayerUnequipItem(client, item) +end + +--- Whether or not a player can buy an item from the business menu. +-- @realm shared +-- @player client Player that uses a business menu +-- @string uniqueID The uniqueID of the business menu item +-- @treturn bool Whether or not to allow the player to buy an item from the business menu +-- @usage function PLUGIN:CanPlayerUseBusiness(client, uniqueID) +-- return false -- Disallow buying from the business menu. +-- end +function CanPlayerUseBusiness(client, uniqueID) +end + +--- Whether or not a player can use a character. +-- @realm shared +-- @player client Player that wants to use a character +-- @char character Character that a player wants to use +-- @treturn bool Whether or not to allow the player to load a character +-- @usage function PLUGIN:CanPlayerUseCharacter(client, character) +-- return false -- Disallow using any character. +-- end +function CanPlayerUseCharacter(client, character) +end + +--- Whether or not a player can use a door. +-- @realm server +-- @player client Player that wants to use a door +-- @entity entity Door that a player wants to use +-- @treturn bool Whether or not to allow the player to use a door +-- @usage function PLUGIN:CanPlayerUseDoor(client, character) +-- return false -- Disallow using any door. +-- end +function CanPlayerUseDoor(client, entity) +end + +--- Determines whether a player can use a vendor. +-- @realm server +-- @player activator The player attempting to use the vendor +-- @entity vendor The vendor entity being used +-- @treturn bool Returns false if the player can't use the vendor +function CanPlayerUseVendor(activator, vendor) +end + +--- Whether or not a player can view his inventory. +-- @realm client +-- @treturn bool Whether or not to allow the player to view his inventory +-- @usage function PLUGIN:CanPlayerViewInventory() +-- return false -- Prevent player from viewing his inventory. +-- end +function CanPlayerViewInventory() +end + +--- Whether or not to save a container. +-- @realm server +-- @entity entity Container entity to save +-- @tab inventory Container inventory +-- @treturn bool Whether or not to save a container +-- @usage function PLUGIN:CanSaveContainer(entity, inventory) +-- return false -- Disallow saving any container. +-- end +function CanSaveContainer(entity, inventory) +end + +--- @realm shared +function CanTransferItem(item, currentInv, oldInv) +end + +--- @realm shared +function CharacterAttributeBoosted(client, character, attribID, boostID, boostAmount) +end + +--- @realm shared +function CharacterAttributeUpdated(client, self, key, value) +end + +--- @realm shared +function CharacterDeleted(client, id, isCurrentChar) +end + +--- @realm shared +function CharacterHasFlags(self, flags) +end + +--- @realm shared +function CharacterLoaded(character) +end + +--- Called when a character was saved. +-- @realm server +-- @char character that was saved +function CharacterPostSave(character) +end + +--- @realm shared +function CharacterPreSave(character) +end + +--- @realm shared +function CharacterRecognized() +end + +--- Called when a character was restored. +-- @realm server +-- @char character that was restored +function CharacterRestored(character) +end + +--- @realm shared +function CharacterVarChanged(character, key, oldVar, value) +end + +--- @realm shared +function CharacterVendorTraded(client, entity, uniqueID, isSellingToVendor) +end + +--- @realm client +function ChatboxCreated() +end + +--- @realm client +function ChatboxPositionChanged(x, y, width, height) +end + +--- @realm client +function ColorSchemeChanged(color) +end + +--- Called when a container was removed. +-- @realm server +-- @entity container Container that was removed +-- @tab inventory Container inventory +function ContainerRemoved(container, inventory) +end + +--- @realm client +function CreateCharacterInfo(panel) +end + +--- @realm client +function CreateCharacterInfoCategory(panel) +end + +--- @realm client +function CreateItemInteractionMenu(icon, menu, itemTable) +end + +--- @realm client +function CreateMenuButtons(tabs) +end + +--- Called when a shipment was created. +-- @realm server +-- @player client Player that ordered the shipment +-- @entity entity Shipment entity +function CreateShipment(client, entity) +end + +--- Called when a server has connected to the database. +-- @realm server +function DatabaseConnected() +end + +--- Called when a server failed to connect to the database. +-- @realm server +-- @string error Error that prevented server from connecting to the database +function DatabaseConnectionFailed(error) +end + +--- @realm shared +function DoPluginIncludes(path, pluginTable) +end + +--- @realm client +function DrawCharacterOverview() +end + +--- @realm client +function DrawHelixModelView(panel, entity) +end + +--- @realm client +function DrawPlayerRagdoll(entity) +end + +--- @realm client +function GetCharacterDescription(client) +end + +--- @realm shared +function GetCharacterName(speaker, chatType) +end + +--- @realm shared +function GetChatPrefixInfo(text) +end + +--- @realm client +function GetCrosshairAlpha(curAlpha) +end + +--- @realm shared +function GetDefaultAttributePoints(client, count) +end + +--- @realm shared +function GetDefaultCharacterName(client, faction) +end + +--- @realm shared +function GetMaxPlayerCharacter(client) +end + +--- Returns the sound to emit from the player upon death. If nothing is returned then it will use the default male/female death +-- sounds. +-- @realm server +-- @player client Player that died +-- @treturn[1] string Sound to play +-- @treturn[2] bool `false` if a sound shouldn't be played at all +-- @usage function PLUGIN:GetPlayerDeathSound(client) +-- -- play impact sound every time someone dies +-- return "physics/body/body_medium_impact_hard1.wav" +-- end +-- @usage function PLUGIN:GetPlayerDeathSound(client) +-- -- don't play a sound at all +-- return false +-- end +function GetPlayerDeathSound(client) +end + +--- @realm client +function GetPlayerEntityMenu(client, options) +end + +--- @realm client +function GetPlayerIcon(speaker) +end + +--- Returns the sound to emit from the player upon getting damage. +-- @realm server +-- @player client Client that received damage +-- @treturn string Sound to emit +-- @usage function PLUGIN:GetPlayerPainSound(client) +-- return "NPC_MetroPolice.Pain" -- Make players emit MetroPolice pain sound. +-- end +function GetPlayerPainSound(client) +end + +--- @realm shared +function GetPlayerPunchDamage(client, damage, context) +end + +--- Returns the salary that character should get instead of his faction salary. +-- @realm server +-- @player client Client that is receiving salary +-- @tab faction Faction of the player's character +-- @treturn number Character salary +-- @see CanPlayerEarnSalary +-- @usage function PLUGIN:GetSalaryAmount(client, faction) +-- return 0 -- Everyone get no salary. +-- end +function GetSalaryAmount(client, faction) +end + +--- @realm client +function GetTypingIndicator(character, text) +end + +--- Registers chat classes after the core framework chat classes have been registered. You should usually create your chat +-- classes in this hook - especially if you want to reference the properties of a framework chat class. +-- @realm shared +-- @usage function PLUGIN:InitializedChatClasses() +-- -- let's say you wanted to reference an existing chat class's color +-- ix.chat.Register("myclass", { +-- format = "%s says \"%s\"", +-- GetColor = function(self, speaker, text) +-- -- make the chat class slightly brighter than the "ic" chat class +-- local color = ix.chat.classes.ic:GetColor(speaker, text) +-- +-- return Color(color.r + 35, color.g + 35, color.b + 35) +-- end, +-- -- etc. +-- }) +-- end +-- @see ix.chat.Register +-- @see ix.chat.classes +function InitializedChatClasses() +end + +--- @realm shared +function InitializedConfig() +end + +--- @realm shared +function InitializedPlugins() +end + +--- @realm shared +function InitializedSchema() +end + +--- Called when an item was added to the inventory. +-- @realm server +-- @tab oldInv Previous item inventory +-- @tab inventory New item inventory +-- @tab item Item that was added to the inventory +function InventoryItemAdded(oldInv, inventory, item) +end + +--- Called when an item was removed from the inventory. +-- @realm server +-- @tab inventory Inventory from which item was removed +-- @tab item Item that was removed from the inventory +function InventoryItemRemoved(inventory, item) +end + +--- @realm shared +function IsCharacterRecognized(character, id) +end + +--- @realm client +function IsPlayerRecognized(client) +end + +--- @realm client +function IsRecognizedChatType(chatType) +end + +--- Called when server is loading data. +-- @realm server +function LoadData() +end + +--- @realm client +function LoadFonts(font, genericFont) +end + +--- @realm client +function LoadIntro() +end + +--- @realm client +function MenuSubpanelCreated(subpanelName, panel) +end + +--- @realm client +function MessageReceived(client, info) +end + +--- @realm client +function OnAreaChanged(oldID, newID) +end + +--- @realm shared +function OnCharacterCreated(client, character) +end + +--- Called when a player who uses a character has disconnected. +-- @realm server +-- @player client The player that has disconnected +-- @char character The character that the player was using +function OnCharacterDisconnect(client, character) +end + +--- Called when a character was ragdolled or unragdolled. +-- @realm server +-- @player client Player that was ragdolled or unradolled +-- @entity entity Ragdoll that represents the player +-- @bool bFallenOver Whether or not the character was ragdolled or unragdolled +function OnCharacterFallover(client, entity, bFallenOver) +end + +--- Called when a character has gotten up from the ground. +-- @realm server +-- @player client Player that has gotten up +-- @entity ragdoll Ragdoll used to represent the player +function OnCharacterGetup(client, ragdoll) +end + +--- @realm client +function OnCharacterMenuCreated(panel) +end + +--- Called whenever an item entity has spawned in the world. You can access the entity's item table with +-- `entity:GetItemTable()`. +-- @realm server +-- @entity entity Spawned item entity +-- @usage function PLUGIN:OnItemSpawned(entity) +-- local item = entity:GetItemTable() +-- -- do something with the item here +-- end +function OnItemSpawned(entity) +end + +--- @realm shared +function OnItemTransferred(item, curInv, inventory) +end + +--- @realm client +function OnLocalVarSet(key, var) +end + +--- @realm client +function OnPAC3PartTransferred(part) +end + +--- Called when a player has picked up the money from the ground. +-- @realm server +-- @player client Player that picked up the money +-- @entity self Money entity +-- @treturn bool Whether or not to allow the player to pick up the money +-- @usage function PLUGIN:OnPickupMoney(client, self) +-- return false -- Disallow picking up money. +-- end +function OnPickupMoney(client, self) +end + +--- @realm shared +function OnPlayerAreaChanged(client, oldID, newID) +end + +--- Called when a player has entered or exited the observer mode. +-- @realm server +-- @player client Player that entered or exited the observer mode +-- @bool state Previous observer state +function OnPlayerObserve(client, state) +end + +--- Called when a player has selected the entity interaction menu option while interacting with a player. +-- @realm server +-- @player client Player that other player has interacted with +-- @player callingClient Player that has interacted with with other player +-- @string option Option that was selected +function OnPlayerOptionSelected(client, callingClient, option) +end + +--- Called when a player has purchased or sold a door. +-- @realm server +-- @player client Player that has purchased or sold a door +-- @entity entity Door that was purchased or sold +-- @bool bBuying Whether or not the player is bying a door +-- @func bCallOnDoorChild Function to call something on the door child +function OnPlayerPurchaseDoor(client, entity, bBuying, bCallOnDoorChild) +end + +--- Called when a player was restricted. +-- @realm server +-- @player client Player that was restricted +function OnPlayerRestricted(client) +end + +--- Called when a player was unrestricted. +-- @realm server +-- @player client Player that was unrestricted +function OnPlayerUnRestricted(client) +end + +--- Called when a saved items were loaded. +-- @realm server +-- @tab loadedItems Table of items that were loaded +function OnSavedItemLoaded(loadedItems) +end + +--- Called when server database are being wiped. +-- @realm server +function OnWipeTables() +end + +--- @realm shared +function PlayerEnterSequence(client, sequence, callback, time, bNoFreeze) +end + +--- Called when a player has interacted with an entity through the entity's interaction menu. +-- @realm server +-- @player client Player that performed interaction +-- @entity entity Entity being interacted with +-- @string option Option selected by the player +-- @param data Any data passed with the interaction option +function PlayerInteractEntity(client, entity, option, data) +end + +--- Called when a player has interacted with an item. +-- @realm server +-- @player client Player that interacted with an item +-- @string action Action selected by the player +-- @tab item Item being interacted with +function PlayerInteractItem(client, action, item) +end + +--- Called when a player has joined a class. +-- @realm server +-- @player client Player that has joined a class +-- @number class Index of the class player has joined to +-- @number oldClass Index of the player's previous class +function PlayerJoinedClass(client, class, oldClass) +end + +--- @realm shared +function PlayerLeaveSequence(entity) +end + +--- Called when a player has loaded a character. +-- @realm server +-- @player client Player that has loaded a character +-- @char character Character that was loaded +-- @char currentChar Character that player was using +function PlayerLoadedCharacter(client, character, currentChar) +end + +--- Called when a player has locked a door. +-- @realm server +-- @player client Player that has locked a door +-- @entity door Door that was locked +-- @entity partner Door partner +function PlayerLockedDoor(client, door, partner) +end + +--- Called when a player has locked a vehicle. +-- @realm server +-- @player client Player that has locked a vehicle +-- @entity vehicle Vehicle that was locked +function PlayerLockedVehicle(client, vehicle) +end + +--- Called when player has said something in the text chat. +-- @realm server +-- @player speaker Player that has said something in the text chat +-- @string chatType Type of the chat that player used +-- @string text Chat message that player send +-- @bool anonymous Whether or not message was anonymous +-- @tab receivers Players who will hear that message +-- @string rawText Chat message without any formatting +-- @treturn string You can return text that will be shown instead +-- @usage function PLUGIN:PlayerMessageSend(speaker, chatType, text, anonymous, receivers, rawText) +-- return "Text" -- When a player writes something into chat, he will say "Text" instead. +-- end +function PlayerMessageSend(speaker, chatType, text, anonymous, receivers, rawText) +end + +--- Called when a player model was changed. +-- @realm server +-- @player client Player whose model was changed +-- @string oldModel Old player model +function PlayerModelChanged(client, oldModel) +end + +--- Called when a player has got stamina. +-- @realm server +-- @player client Player who has got stamina +function PlayerStaminaGained(client) +end + +--- Called when a player has lost stamina. +-- @realm server +-- @player client Player who has lost stamina +function PlayerStaminaLost(client) +end + +--- @realm shared +function PlayerThrowPunch(client, trace) +end + +--- Called when a player has unlocked a door. +-- @realm server +-- @player client Player that has unlocked a door +-- @entity door Door that was unlocked +-- @entity partner Door partner +function PlayerUnlockedDoor(client, door, partner) +end + +--- Called when a player has unlocked a vehicle. +-- @realm server +-- @player client Player that has unlocked a vehicle +-- @entity vehicle Vehicle that was unlocked +function PlayerUnlockedVehicle(client, vehicle) +end + +--- Called when a player has used an entity. +-- @realm server +-- @player client Player who has used an entity +-- @entity entity Entity that was used by the player +function PlayerUse(client, entity) +end + +--- Called when a player has used a door. +-- @realm server +-- @player client Player who has used a door +-- @entity entity Door that was used by the player +function PlayerUseDoor(client, entity) +end + +--- @realm shared +function PlayerWeaponChanged(client, weapon) +end + +--- @realm shared +function PluginLoaded(uniqueID, pluginTable) +end + +--- @realm shared +function PluginShouldLoad(uniqueID) +end + +--- @realm shared +function PluginUnloaded(uniqueID) +end + +--- @realm client +function PopulateCharacterInfo(client, character, tooltip) +end + +--- @realm client +function PopulateEntityInfo(entity, tooltip) +end + +--- @realm client +function PopulateHelpMenu(categories) +end + +--- @realm client +function PopulateImportantCharacterInfo(entity, character, tooltip) +end + +--- @realm client +function PopulateItemTooltip(tooltip, item) +end + +--- @realm client +function PopulatePlayerTooltip(client, tooltip) +end + +--- @realm client +function PopulateScoreboardPlayerMenu(client, menu) +end + +--- @realm client +function PostChatboxDraw(width, height, alpha) +end + +--- @realm client +function PostDrawHelixModelView(panel, entity) +end + +--- @realm client +function PostDrawInventory(panel) +end + +--- Called when server data was loaded. +-- @realm server +function PostLoadData() +end + +--- Called after player loadout. +-- @realm server +-- @player client +function PostPlayerLoadout(client) +end + +--- Called after player has said something in the text chat. +-- @realm server +-- @player client Player that has said something in the text chat +-- @string chatType Type of the chat that player used +-- @string message Chat message that player send +-- @bool anonymous Whether or not message was anonymous +function PostPlayerSay(client, chatType, message, anonymous) +end + +--- @realm shared +function PostSetupActs() +end + +--- Called before character deletion. +-- @realm server +-- @player client Character owner +-- @char character Chraracter that will be deleted +function PreCharacterDeleted(client, character) +end + +--- Called before character loading. +-- @realm server +-- @player client Player that loading a character +-- @char character Character that will be loaded +-- @char currentChar Character that player is using +function PrePlayerLoadedCharacter(client, character, currentChar) +end + +--- Called before a message sent by a player is processed to be sent to other players - i.e this is ran as early as possible +-- and before things like the auto chat formatting. Can be used to prevent the message from being sent at all. +-- @realm server +-- @player client Player sending the message +-- @string chatType Chat class of the message +-- @string message Contents of the message +-- @bool bAnonymous Whether or not the player is sending the message anonymously +-- @treturn bool Whether or not to prevent the message from being sent +-- @usage function PLUGIN:PrePlayerMessageSend(client, chatType, message, bAnonymous) +-- if (!client:IsAdmin()) then +-- return false -- only allow admins to talk in chat +-- end +-- end +function PrePlayerMessageSend(client, chatType, message, bAnonymous) +end + +--- Called when server is saving data. +-- @realm server +function SaveData() +end + +--- @realm client +function ScreenResolutionChanged(width, height) +end + +--- @realm shared +function SetupActs() +end + +--- @realm shared +function SetupAreaProperties() +end + +--- Called when a player has taken a shipment item. +-- @realm server +-- @player client Player that has taken a shipment item +-- @string uniqueID UniqueID of the shipment item that was taken +-- @number amount Amount of the items that were taken +function ShipmentItemTaken(client, uniqueID, amount) +end + +--- @realm client +function ShouldBarDraw(bar) +end + +--- Whether or not the server should delete saved items. +-- @realm server +-- @treturn bool Whether or not the server should delete saved items +-- @usage function PLUGIN:ShouldDeleteSavedItems() +-- return true -- Delete all saved items. +-- end +function ShouldDeleteSavedItems() +end + +--- @realm client +function ShouldDisplayArea(newID) +end + +--- @realm client +function ShouldDrawCrosshair(client, weapon) +end + +--- @realm client +function ShouldDrawItemSize(item) +end + +--- @realm client +function ShouldHideBars() +end + +--- Whether or not a character should be permakilled upon death. This is only called if the `permakill` server config is +-- enabled. +-- @realm server +-- @player client Player to permakill +-- @char character Player's current character +-- @entity inflictor Entity that inflicted the killing blow +-- @entity attacker Other player or entity that killed the player +-- @treturn bool `false` if the player should not be permakilled +-- @usage function PLUGIN:ShouldPermakillCharacter(client, character, inflictor, attacker) +-- if (client:IsAdmin()) then +-- return false -- all non-admin players will have their character permakilled +-- end +-- end +function ShouldPermakillCharacter(client, character, inflictor, attacker) +end + +--- Whether or not player should drown. +-- @realm server +-- @player client Player that is underwater +-- @treturn bool Whether or not player should drown +-- @usage function PLUGIN:ShouldPlayerDrowned(client) +-- return false -- Players will not drown. +-- end +function ShouldPlayerDrowned(client) +end + +--- Whether or not remove player ragdoll on death. +-- @realm server +-- @player client Player that died +-- @treturn bool Whether or not remove player ragdoll on death +-- @usage function PLUGIN:ShouldRemoveRagdollOnDeath(client) +-- return false -- Player ragdolls will not be removed. +-- end +function ShouldRemoveRagdollOnDeath(client) +end + +--- Whether or not to restore character inventory. +-- @realm server +-- @number characterID ID of the character +-- @number inventoryID ID of the inventory +-- @string inventoryType Type of the inventory +-- @treturn bool Whether or not to restore character inventory +-- @usage function PLUGIN:ShouldRestoreInventory(characterID, inventoryID, inventoryType) +-- return false -- Character inventories will not be restored. +-- end +function ShouldRestoreInventory(characterID, inventoryID, inventoryType) +end + +--- @realm client +function ShouldShowPlayerOnScoreboard(client) +end + +--- Whether or not spawn player ragdoll on death. +-- @realm server +-- @player client Player that died +-- @treturn bool Whether or not spawn player ragdoll on death +-- @usage function PLUGIN:ShouldSpawnClientRagdoll(client) +-- return false -- Player ragdolls will not be spawned. +-- end +function ShouldSpawnClientRagdoll(client) +end + +--- @realm client +function ShowEntityMenu(entity) +end + +--- @realm client +function ThirdPersonToggled(oldValue, value) +end + +--- @realm client +function UpdateCharacterInfo(panel, character) +end + +--- @realm client +function UpdateCharacterInfoCategory(panel, character) +end + +--- Called when the distance on which the voice can be heard was changed. +-- @realm server +-- @number newValue New voice distance +function VoiceDistanceChanged(newValue) +end + +--- @realm client +function WeaponCycleSound() +end + +--- @realm client +function WeaponSelectSound(weapon) +end diff --git a/garrysmod/gamemodes/helix/docs/js/app.js b/garrysmod/gamemodes/helix/docs/js/app.js new file mode 100644 index 0000000..ce0cee1 --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/js/app.js @@ -0,0 +1,168 @@ + +const skippedCategories = ["manual"]; + +class Node +{ + constructor(name, element, expandable, noAutoCollapse, children = []) + { + this.name = name; + this.element = element; + this.expandable = expandable; + this.noAutoCollapse = noAutoCollapse; + this.children = children; + } + + AddChild(name, element, expandable, noAutoCollapse, children) + { + let newNode = new Node(name, element, expandable, noAutoCollapse, children); + this.children.push(newNode); + + return newNode; + } +} + +class SearchManager +{ + constructor(input, contents) + { + this.input = input; + this.input.addEventListener("input", event => + { + this.OnInputUpdated(this.input.value.toLowerCase().replace(/:/g, ".")); + }); + + // setup search tree + this.tree = new Node("", document.createElement("null"), true, true); + this.entries = {}; + + const categoryElements = contents.querySelectorAll(".category"); + + // iterate each kind (hooks/libraries/classes/etc) + for (const category of categoryElements) + { + const nameElement = category.querySelector(":scope > summary > h2"); + + if (!nameElement) + { + continue; + } + + const categoryName = nameElement.textContent.trim().toLowerCase(); + + if (skippedCategories.includes(categoryName)) + { + continue; + } + + let categoryNode = this.tree.AddChild(categoryName, category, true, true); + const sectionElements = category.querySelectorAll(":scope > ul > li"); + + for (const section of sectionElements) + { + const entryElements = section.querySelectorAll(":scope > details > ul > li > a"); + const sectionName = section.querySelector(":scope > details > summary > a") + .textContent + .trim() + .toLowerCase(); + + let sectionNode = categoryNode.AddChild(sectionName, section.querySelector(":scope > details"), true); + + for (let i = 0; i < entryElements.length; i++) + { + const entryElement = entryElements[i]; + const entryName = entryElement.textContent.trim().toLowerCase(); + + sectionNode.AddChild(sectionName + "." + entryName, entryElement.parentElement); + } + } + } + } + + ResetVisibility(current) + { + current.element.style.display = ""; + + if (current.noAutoCollapse) + { + current.element.open = true; + } + else if (current.expandable) + { + current.element.open = false; + } + + for (let node of current.children) + { + this.ResetVisibility(node); + } + } + + Search(input, current) + { + let matched = false; + + if (current.name.indexOf(input) != -1) + { + matched = true; + } + + for (let node of current.children) + { + let childMatched = this.Search(input, node); + matched = matched || childMatched; + } + + if (matched) + { + current.element.style.display = ""; + + if (current.expandable) + { + current.element.open = true; + } + } + else + { + current.element.style.display = "none"; + + if (current.expandable) + { + current.element.open = false; + } + } + + return matched; + } + + OnInputUpdated(input) + { + if (input.length <= 1) + { + this.ResetVisibility(this.tree); + return; + } + + this.Search(input, this.tree); + } +} + +window.onload = function() +{ + const openDetails = document.querySelector(".category > ul > li > details[open]"); + + if (openDetails) + { + openDetails.scrollIntoView(); + } +} + +document.addEventListener("DOMContentLoaded", function() +{ + const searchInput = document.getElementById("search"); + const contents = document.querySelector("body > main > nav > section"); + + if (searchInput && contents) + { + new SearchManager(searchInput, contents); + } +}); diff --git a/garrysmod/gamemodes/helix/docs/js/highlight.min.js b/garrysmod/gamemodes/helix/docs/js/highlight.min.js new file mode 100644 index 0000000..dd8dd8d --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/js/highlight.min.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ +!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
    ":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
    ",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("lua",function(e){var t="\\[=*\\[",a="\\]=*\\]",r={b:t,e:a,c:["self"]},n=[e.C("--(?!"+t+")","$"),e.C("--"+t,a,{c:[r],r:10})];return{l:e.UIR,k:{literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstringmodule next pairs pcall print rawequal rawget rawset require select setfenvsetmetatable tonumber tostring type unpack xpcall arg selfcoroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},c:n.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:n}].concat(n)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:a,c:[r],r:5}])}}); \ No newline at end of file diff --git a/garrysmod/gamemodes/helix/docs/manual/converting-from-clockwork.md b/garrysmod/gamemodes/helix/docs/manual/converting-from-clockwork.md new file mode 100644 index 0000000..4c66c9b --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/manual/converting-from-clockwork.md @@ -0,0 +1,329 @@ +# Clockwork to Helix Migration + +If you are here, you probably want to be converting your code from another framework to Helix. Doing so should not be a difficult task. Most of the previous functions are probably within Helix in one form or another! This means all you need to do is match *x* function found in the old framework to *y* function in Helix. Some headings will contain a link - this will bring you to the documentation for Helix's equivalent library or class. + +This tutorial assumes basic to intermediate knowledge and experience with Garry's Mod Lua. + +**Before you start!** You will notice that Helix uses client for the variable that represents a player. Clockwork uses player for the variable instead, but this will conflict with the player library. So if you see `_player` being used in Clockwork, it means the Garry's Mod player library. This is just a preference and does not affect anything besides appear. So keep in mind throughout the tutorial, you may see player being used for Clockwork code and client being used for Helix code. They represent the same thing, just with a different name. + +If you are converting Clockwork code to Helix, keep in mind that `_player` is not defined so you will need to either define `_player` yourself or switch it to player instead and change the variable name to client for player objects. + +# Basics of Conversion + +## Folders +Clockwork code and file structure is not too different from Helix. In the schema, the plugins folder and schema folder stay in the same place. There are some minor differences in naming however: + +- The `schema/entities` folder should be moved outside out of the schema folder. +- The `libraries` folder needs to be renamed to `libs` to load. +- The `commands` tab will not load as each command is now defined in a single shared file, does not matter which one. + +## Deriving from Helix +This is pretty important. If you want to use Helix as the base, you need to set it as the base. So, go to your Clockwork schema's `gamemode` folder. Inside should be two files: `init.lua `and `cl_init.lua`. Open both, and you should see something along the lines of `DeriveGamemode("Clockwork")`. Change this to `DeriveGamemode("helix")`. + +# The Schema + +## Introduction +Inside of the `schema` folder of the actual schema, you should see a file named `sh_schema.lua`. This is the main schema file in both Clockwork and Helix. Most of your changes may actually be within this file. + +## Including Files +Both frameworks come with a utility function to include a file without worrying about sending them to the client and stuff. In Clockwork, this function is `Clockwork.kernel:IncludePrefixed("sh_myfile.lua")`. Change this to `ix.util.Include("sh_myfile.lua") `and save. + +# The Plugin + +## Introduction +Plugins serve as a means to add on to a schema or framework without directly modifying either. This allows for easier modifications that can be added/removed with ease. It is recommended that you keep all custom modifications left to plugins rather than editing the framework or the schema if possible. + +## Structure +All plugins in Clockwork and Helix go into the `plugins` folder. However, there are many differences with the CW plugin structure. First of all, there are two things you see when you open a plugin folder: `plugin` again and `plugin.ini`. + +Helix only has one file needed: `sh_plugin.lua` which acts like `sh_schema.lua` but for plugins. + +## Conversion +The first step is to move all of the contents from the `plugin` folder to the main folder of the plugin folder. The `sh_plugin.lua` file needs to be changed to provide basic information about the plugin.You need to define three things in `sh_plugin.lua` which can be found within the `plugin.ini` file: + +- `PLUGIN.name = "Plugin Name"` +- `PLUGIN.author = "Plugin Author"` +- `PLUGIN.description = "Plugin Description"` + +If the plugin uses a special variable (e.g. `cwPluginName`) for the plugin, change it to `PLUGIN`. + +- Note that the `PLUGIN` table is removed after the plugin is loaded. So if you want to use `PLUGIN` after the plugin has loaded (such as in console commands, in entities, etc.), add `local PLUGIN = PLUGIN` at the top. +- You can see if a global variable is defined for it by looking for `PLUGIN:SetGlobalAlias("cwMyPlugin")`. So, one would change `cwMyPlugin` to `PLUGIN`. + +# The `Character` Object +One main thing that is very notable is how the character is referenced using `client:GetCharacter()` which returns a character object. The way the object works is just like an entity you spawn. It has its own properties like the model, color, etc. that makes it unique. You can access all the characters in a table which stores loaded characters with `ix.char.loaded`. + +The character object comes with many predefined methods. You can look at how they are defined [by clicking here](https://github.com/NebulousCloud/helix/blob/master/gamemode/core/meta/sh_character.lua). The character object makes it very simple to manager character information. + +You will notice throughout the framework, the character object is used a lot. The use of the character object makes a large barrier between what belongs to the character and what belongs to the player. For example: flags, models, factions, data, and other things are stored on the character and can be accessed by the character object. + +In Clockwork, there is no use of an object. Instead, the character information is intertwined with the player object. For example: + +``` +-- in Clockwork +player:SetCharacterData("foo", "bar") + +-- in Helix +client:GetCharacter():SetData("foo", "bar") +``` + +The use of the character object allows you to access other characters a player might own without needing to have them be the active character, or even access them when the player is not on the server. Overall, the use of the character object may seem like a complex concept, but will simplify a lot of things once you get the hang of the idea. + +# The Libraries + +## Animations (`ix.anim`) +Clockwork features many functions to set up animations for a specific model. Helix too has this functionality. Helix has one function instead that pairs a model to a specific "animation class" (grouping of animation types). So, all one needs to do is find the appropriate animation class to match the model with. Looking at the Clockwork function name should tell you. + +``` +-- before +Clockwork.animation:AddCivilProtectionModel("models/mymodel.mdl") + +-- after +ix.anim.SetModelClass("models/mymodel.mdl", "metrocop") +``` + +## Attributes (`ix.attributes`) +Attributes allow the player to boost certain abilities over time. Both frameworks require one to register attributes, but they are done differently. In Clockwork, the `ATTRIBUTE` table needs to be defined and registered manually. In Helix, the `ATTRIBUTE` table is automatically defined and registered for you. All you need to do is have `ATTRIBUTE.value = "value"`. The basic parts of the attribute needed is `ATTRIBUTE.name` and `ATTRIBUTE.description`. + +One extra feature for attributes in Helix is `ATTRIBUTE:OnSetup(client, value)` which is a function that gets called on spawn to apply any effects. For example, the stamina attribute changes the player's run speed by adding the amount of stamina points the player has. + +You can find an example at [https://github.com/NebulousCloud/helix/blob/master/plugins/stamina/attributes/sh_stm.lua](https://github.com/NebulousCloud/helix/blob/master/plugins/stamina/attributes/sh_stm.lua) + +## Classes (`ix.class`) +Classes are a part of the factions. They basically are a more specific form of a faction. Factions in Helix and Clockwork work similarly. For instance, all classes are placed in the `classes` folder under the schema folder and use `CLASS` as the main variable inside the file. + +However: + +- You do not need to use `local CLASS = Clockwork.class:New("My Class")`. Instead, `CLASS` is already defined for you and you set the name using `CLASS.name = "My Class"` +- `CLASS.factions` is *not* a table, so `CLASS.factions = {FACTION_MYFACTION}` becomes `CLASS.faction = FACTION_MYFACTION` +- You do not need to use `CLASS:Register()` as classes are registered for you after the file is done processing. +- Classes are *optional* for factions rather than being required. + +## Commands (`ix.command`) +Commands no longer need to be in separate files. Instead, they are just placed into one large file. However, if you really wanted you can register multiple commands across multiple files or however you want. One thing you may notice is Clockwork uses a _COMMAND_ table while Helix does not always. It is simply a design preference. You can find examples at [https://github.com/NebulousCloud/helix/blob/master/gamemode/core/sh_commands.lua](https://github.com/NebulousCloud/helix/blob/master/gamemode/core/sh_commands.lua) + +It should be noted that: + +- `COMMAND.tip` is not used. +- `COMMAND.text` is not used. +- `COMMAND.flags` is not used. +- `COMMAND.arguments` does not need to be defined if no arguments are needed but is defined as a table of argument types when needed `arguments = {ix.type.character, ix.type.number}`. See `ix.command.CommandArgumentsStructure` for details. +- `COMMAND.access` for checking whether or not a person is a (super)admin can be replaced with `adminOnly = true` or `superAdminOnly = true` in the command table. + +## Configurations (`ix.config`) +In Helix, the method of adding configurations that can be changed by server owners is heavily simplified. [See an example here](https://github.com/NebulousCloud/helix/blob/master/gamemode/config/sh_config.lua). + +Adding a configuration is as follows: + +``` +-- before +Clockwork.config:Add("run_speed", 225) + +-- after +ix.config.Add("runSpeed", 235, ...) +``` +You'll notice that ellipses (...) were added at the end. This is because there are more arguments since adding configuration information has been placed into one function. Additionally: + +- `Clockwork.config:ShareKey()` is not needed. +- The 3rd argument for `Clockwork.config:AddToSystem(name, key, description, min, max)` is also the 3rd argument for `ix.config.Add` +- The 4th argument for `ix.config.Add` is an optional function that is called when the configuration is changed. +- The 5th argument for `ix.config.Add` is a table. You can specify the category for the configuration to group it with other configurations. There is also a data table inside which can be used to determine the minimum value and maximum value for numbers. Check out [an example here](https://github.com/NebulousCloud/helix/blob/master/gamemode/config/sh_config.lua). See also `ix.config`. + +## Currency (`ix.currency`) +Updating your currency code is simple: + +``` +-- before +Clockwork.config:SetKey("name_cash", "Tokens") +Clockwork.config:SetKey("name_cash", "Dollars") -- another example + +-- after +ix.currency.Set("", "token", "tokens") +ix.currency.Set("$", "dollar", "dollars") +``` + +Note that you need to provide a symbol for that currency (€ for Euro, £ for Pound, ¥ for Yen, etc.) or just leave it as an empty string (`""`) and then provide the singular form of the name for the currency, then the plural form. + +## Datastream +Helix uses the [net library](http://wiki.garrysmod.com/page/Net_Library_Usage) whereas Clockwork uses datastream ([netstream](https://github.com/alexgrist/NetStream/blob/master/netstream2.lua)). + +If you're unfamiliar with the net library, you can include the netstream library to your schema by downloading [netstream](https://github.com/alexgrist/NetStream/blob/master/netstream2.lua) to `schema/libs/thirdparty/sh_netstream2.lua` and adding `ix.util.Include("libs/thirdparty/sh_netstream2.lua")` to your `sh_schema.lua` file. + +Starting a datastream: + +``` +-- before +Clockwork.datastream:Start(receiver, "MessageName", {1, 2, 3}); + +-- after +netstream.Start(receiver, "MessageName", 1, 2, 3) +``` + +Receiving a datastream: + +``` +-- before +Clockwork.datastream:Hook("MessageName", function(player, data) + local a = data[1]; + local b = data[2]; + local c = data[3]; + + print(a, b, c); +end); + +-- after +netstream.Hook("MessageName", function(client, a, b, c) + print(a, b, c) +end) +``` + +## Factions (`ix.faction`) +Factions, like classes, are pretty similar too. They share pretty much the same differences as classes in Clockwork and Helix do. + +For instance: + +- You do not need to use `local FACTION = Clockwork.faction:New("Name Here")`, instead `FACTION` is already defined for you and you set the name using `FACTION.name = "Name Here"` +- `FACTION.whitelist = true` is changed to `FACTION.isDefault = false` +- `FACTION.models` does not need a male and female part. Instead, all the models are combined into one big list. +- `function FACTION:GetName(name)` becomes `function FACTION:GetDefaultName(name)` +- `FACTION.description = "Describe me"` is added to the faction. +- `FACTION_MYFACTION = FACTION:Register()` becomes `FACTION_MYFACTION = FACTION.index` + +## Flags (`ix.flag`) +Flags are functionally equivalent in Helix. To add a new flag: + +``` +-- before +Clockwork.flag:Add("x", "Name", "Description") + +-- after +ix.flag.Add("x", "Description") +``` + +To check or manipulate a character's flag(s): + +``` +-- before +Clockwork.player:GiveFlags(player, flags) +Clockwork.player:TakeFlags(player, flags) +Clockwork.player:HasFlags(player, flags) + +-- after +client:GetCharacter():GiveFlags(flags) +client:GetCharacter():TakeFlags(flags) +client:GetCharacter():HasFlags(flags) +``` + +## Inventories (`Inventory`) +Inventories have also had a change in the way they work that may seem very different than Clockwork. Similar to how characters are their own objects, inventories become their own objects as well. These inventory objects belong to character objects, which belongs to players. So, this creates a chain of objects which is neat. The use of inventories as objects makes it very simple to attach inventories to anything. + +To access a player's inventory, you need to use `client:GetCharacter():GetInventory()` which returns the main inventory object for the player's character. You can also access all loaded inventories with `ix.item.inventories` but that is not important right now. + +## Items (`Item`) +As discussed above, inventories contain items. Items are still used in inventories and world entities, use default class data, have callback functions, and can contain unique item data per instance. + +### Setting up items +Every time needs to be registered, or have information about it (such as the name, model, what it does, etc.) defined. In Clockwork, you have your items defined in schemas/plugins under the items folder. + +So let's start with the differences in structure in the item file. + +- `local ITEM = Clockwork.item:New();` is removed +- `ITEM.uniqueID` is *completely* optional +- Replace `ITEM.cost` with `ITEM.price` +- `ITEM:Register()` is removed + +### Item Sizes +Helix's inventory uses a grid and utilizes width and height instead of weight as a means of inventory capacity. This means you will have to change your item's weight (`ITEM.weight`) to something that might be analagous to the item's size using `ITEM.width` and `ITEM.height`. The item's size must be at least one by one grid cell. It's up to you to balance the sizes of items in your use case - taking into account how many items a character might have at once, the default inventory size set in the config, etc. + +### Item Functions +Item functions are defined very differently than they are in Clockwork. For example: + +``` +-- before +function ITEM:OnUse(player, entity) + print("My name is: " .. player:Name(), entity) +end + +-- after +ITEM.functions.Use = { + OnRun = function(item) + print("My name is: " .. item.player, item.entity) + end +} +``` + +All item functions are defined in the `ITEM.functions` table. This allows the drop-down menus when using the item a lot easier and cleaner to generate dynamically. There is also more control of the icons used for the options, whether or not the function should be displayed, etc. + +You can see an example of a water item here: [https://github.com/NebulousCloud/helix-hl2rp/blob/master/schema/items/sh_water.lua](https://github.com/NebulousCloud/helix-hl2rp/blob/master/schema/items/sh_water.lua) + +Here, we can define what happens when the function is run, what the icon is, and what sound it plays when used. It is basically put into one area rather than being scattered among hooks and stuff. + +### Giving/Taking Items +So before we can give/take items, we need to understand what the *item instance* is. Using the analogy earlier about how the inventory system is like a forum, and inside the forum are posts (the items in this case), we can think of instancing an item as making a new post on a forum. So when we talk about an *item instance*, it is an item that has been created in the past. The reason we use an item instance (which is its own object too, neat!) is to make each item ever created unique. Each item instance can have its own data unique to itself. + +Clockwork also uses an item instance system where you have to instance an item. So, to instance an item in Clockwork you would use: + +``` +item = Clockwork.item:CreateInstance("item") +``` + +And this would create a new instance of an item. Helix's instancing system is slightly different. Instead of having the function return the instance like it does in Clockwork, Helix relies on a callback to pass the instance. The reason for this is the item must be inserted into the database to get a unique number to represent that item. This is not done instantly, otherwise servers would freeze when new items are made. Clockwork uses the time and adds a number to get the numeric ID for an item, which allows the item to be returned which "solves" the issue, but I digress. + +The Helix equivalent would be: + +``` +ix.item.Instance(0, "item", data, x, y, function(item) end) +``` + +Let's break down the differences: + +- For Helix's item instance, the 1st argument (`0`) is the inventory that the item belongs to. You can specify 0 so it does not belong to any inventory. +- The data argument is *optional* and is just a table for the item data. +- *x* and *y* are the position of the items in inventory. You can find an available *x* and *y* with `inventory:FindEmptySlot()`. +- The function is an *optional* argument that passes the item instance. This is where you can directly access the new item. + +Keep in mind that Helix will simplify the item system for you when it can. Normally, you would not need to instance an item yourself unless you were doing something advanced. + +So you might be wondering, how do I spawn an item in the map, and how do I give a player an item? In Clockwork, you would do the following: + +``` +-- spawning an item in the map +Clockwork.entity:CreateItem(player, Clockwork.item:CreateInstance("item"), Vector(1, 2, 3)); + +-- giving a player an item +player:GiveItem(Clockwork.item:CreateInstance("item")); +``` + +The equivalent in Helix would be: + +``` +-- spawning an item in the map +ix.item.Spawn("item", Vector(1, 2, 3)) + +-- giving a player an item +client:GetCharacter():GetInventory():Add("test") +``` + +So in these two examples, the whole deal of instancing items is done for you in Helix! + +# Hooks +You will need to modify the function name and arguments for your schema or plugin hooks. + +``` +-- before +function Schema:PlayerPlayPainSound(player, gender, damageInfo, hitGroup) + -- ... +end + +-- after +function Schema:GetPlayerPainSound(client) + -- ... +end +``` + +You can see the documented hooks for the schema and plugins in the `Plugin` section. + +# Conclusion +Overall, most of the conversion from Clockwork to Helix is simply renaming a certain function and/or switching the order of arguments around. Both are frameworks so they function similarly. + +You may want to use our HL2 RP schema example for reference which can be found at [https://github.com/NebulousCloud/helix-hl2rp](https://github.com/NebulousCloud/helix-hl2rp) diff --git a/garrysmod/gamemodes/helix/docs/manual/getting-started.md b/garrysmod/gamemodes/helix/docs/manual/getting-started.md new file mode 100644 index 0000000..5582c2c --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/manual/getting-started.md @@ -0,0 +1,75 @@ +# Getting Started +It's pretty easy to get started with creating your own schema with Helix. It requires a bit of bootstrapping if you're starting from scratch, but you should quickly be on your way to developing your schema after following one of the below sections in this guide. + +# Installing the framework +Before you start working on your schema, you'll need to install Helix onto your server. The exact instructions will vary based on your server provider, or if you're hosting the server yourself. + +You'll need to download the framework from [GitHub](https://github.com/NebulousCloud/helix) into a folder called `helix`. This folder goes into your server's `gamemodes` folder at `garrysmod/gamemodes/helix`. That's it! The framework is now installed onto your server. Of course, you'll need to restart your server after installing the framework and a schema. + +# MySQL usage +By default, Helix will use SQLite (which is built into Garry's Mod) to store player/character data. This requires no configuration and will work "out of the box" after installing and will be fine for most server owners. However, you might want to connect your database to your website or use multiple servers with one database - this will require the usage of an external database accessible elsewhere. This will require the use of a MySQL server. Some server providers will provide you with a MySQL database for free to use with your server. + +## Installing +Helix uses the [MySQLOO](https://github.com/FredyH/MySQLOO) library to connect to MySQL databases. You'll need to follow the instructions for installing that library onto your server before continuing. In a nutshell, you need to make sure `gmsv_mysqloo_win32.dll` or `gmsv_mysqloo_linux.dll` (depending on your server's operating system) is in the `garrysmod/lua/bin` folder. + +In older versions of MySQLOO, you previously required a .dll called `libmysql.dll` to place in your `root` server folder, where `srcds`/`srcds_linux` was stored. Newer versions of MySQLOO no longer require this. + +## Configuring +Now that you've installed MySQLOO, you need to tell Helix that you want to connect to an external MySQL database instead of using SQLite. This requires creating a `helix.yml` configuration file in the `garrysmod/gamemodes/helix` folder. There is an example one already made for you called `helix.example.yml` that you can copy and rename to `helix.yml`. + +The first thing you'll need to change is the `adapter` entry so that it says `mysqloo`. Next is to change the other entries to match your database's connection information. Here is an example of what your `helix.yml` should look like: + +``` +database: + adapter: "mysqloo" + hostname: "myexampledatabase.com" + username: "myusername" + password: "mypassword" + database: "helix" + port: 3306 +``` + +The `hostname` field can either be a domain name (like `myexampledatabase.com`) or an IP address (`123.123.123.123`). If you don't know what the `port` field should be, simply leave it as the default `3306`; this is the default port for MySQL database connections. The `database` field is the name of the database that you've created for Helix. Note that it does not need to be `helix`, it can be whatever you'd like. + +Another important thing to note about this configuration file is that you **must** indent with **two spaces only**. `database` should not have any spacing before it, and all other entries must have two spaces before them. Failing to ensure this will make the configuration file fail to load. + +# Starting with the HL2 RP schema (Basic) +This section is for using the existing HL2 RP schema as a base for your own schema. It contains a good amount of example code if you need a stronger foundation than just a skeleton. + +First, you'll need to download the schema from [GitHub](https://github.com/NebulousCloud/helix-hl2rp). Make sure that you download the contents of the repository into a folder called `ixhl2rp` and place it into your `garrysmod/gamemodes` folder. That's all you'll need to do to get the schema installed, other than setting your gamemode to `ixhl2rp` in the server's command line. + +# Starting with the skeleton (Basic) +If you don't want excess code you might not use, or prefer to build from an almost-empty foundation that covers the basic bootstrapping, then the skeleton schema is for you. The skeleton schema contains a lot of comments explaining why code is laid out in a certain way, and some other helpful tips/explanations. Make sure you give it a read! + +You'll need to download the schema from [GitHub](https://github.com/NebulousCloud/helix-skeleton) into the folder name of your choice - just make sure it's all lowercase with no spaces. Our example for the sake of brevity will be `myschema`. Place the folder into `garrysmod/gamemodes`. + +Next up is to modify the gamemode info so that Garry's Mod will properly recognize it. Rename `skeleton.txt` in your schema folder to your folder's name. In our example we would rename `skeleton.txt` to `myschema.txt`. Next, you'll need to modify the contents of `myschema.txt` and replace the existing information with your own - making sure to replace the `"skeleton"` at the top of the file to your folder's name. In our case we would replace it with `"myschema"`. Once you've renamed the file, you're all good to go! + +# Converting from Clockwork (Intermediate) +If you are looking to switch to Helix from Clockwork, you can follow the @{converting-from-clockwork|conversion guide}. + +# Starting from scratch (Intermediate) +You can always create the gamemode files yourself if you'd like (although we suggest the skeleton schema in general). In general, a schema is a gamemode that is derived from `helix` and automatically loads `schema/sh_schema.lua`. You shouldn't have your schema files outside of the `schema` folder. The files you'll need are as follows: + +`gamemode/init.lua` +``` +AddCSLuaFile("cl_init.lua") +DeriveGamemode("helix") +``` + +`gamemode/cl_init.lua` +``` +DeriveGamemode("helix") +``` + +`schema/sh_schema.lua` +``` +Schema.name = "My Schema" +Schema.author = "me!" +Schema.description = "My awesome schema." + +-- include your other schema files +ix.util.Include("cl_schema.lua") +ix.util.Include("sv_schema.lua") +-- etc. +``` diff --git a/garrysmod/gamemodes/helix/docs/templates/landing.ltp b/garrysmod/gamemodes/helix/docs/templates/landing.ltp new file mode 100644 index 0000000..3070c22 --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/templates/landing.ltp @@ -0,0 +1,19 @@ + +
    +

    Helix Documentation

    +
    + +
    +

    Welcome to the documentation for Helix - the better gamemode framework.

    +

    Developers

    +

    The sidebar shows the entire contents of the documentation. Libraries, functions, etc are all searchable with the search box at the top of the sidebar. Migrating from Clockwork? Check out the conversion guide in the manual.

    +

    Server owners

    +

    If you're looking to get your Helix server up and running as soon as possible, check out the Getting Started guide in the manual.

    +

    Community

    +

    Questions? Want to show off your work? Maybe drop a new plugin release? Come join our community Discord server.

    +

    Contributing

    +

    Helix is a large project and there are still a few things missing here and there. Contributions to the documentation - from function references, to simple typo fixes - are welcomed! Check out the ix.storage library's source code for a good example on how to write documentation. You'll need a basic understanding of Markdown, since it's used extensively to generate the markup.

    +

    If you'd like to contribute code, you can visit the GitHub repository and make a pull request.

    +

    Learning

    +

    Getting started on developing with the Helix framework requires an intermediate level of Garry's Mod Lua knowledge. You'll want to learn the basics before you get starting making a schema. The Garry's Mod Wiki is a good place to start.

    +
    diff --git a/garrysmod/gamemodes/helix/docs/templates/ldoc.ltp b/garrysmod/gamemodes/helix/docs/templates/ldoc.ltp new file mode 100644 index 0000000..0924cca --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/templates/ldoc.ltp @@ -0,0 +1,90 @@ + +{% +local baseUrl = ldoc.css:gsub("ldoc.css", "") +local repo = "https://github.com/nebulouscloud/helix/" +local pageTitle = mod and (ldoc.display_name(mod) .. " - " .. ldoc.title) or ldoc.title + +local oldmarkup = ldoc.markup +function ldoc.markup(text, item) + return oldmarkup(text, item, ldoc.plain) +end + +function ldoc.url(path) + return baseUrl .. path +end + +function ldoc.realm_icon(realm) + return ""; +end + +function ldoc.is_kind_classmethod(kind) + return kind ~= "libraries" +end + +function ldoc.repo_reference(item) + return repo .. "tree/master" .. item.file.filename:gsub(item.file.base, "/gamemode") .. "#L" .. item.lineno +end + +local function moduleDescription(mod) + if (mod.type == "topic") then + return mod.body:gsub(mod.display_name, ""):gsub("#", ""):sub(1, 256) .. "..." + end + + return mod.summary +end +%} + + + + {{pageTitle}} + + + + + + {% if (mod) then %} + + {% else %} + + {% end %} + + + + + + + +
    + {(docs/templates/sidebar.ltp)} + +
    + {% if (ldoc.root) then -- we're rendering the landing page (index.html) %} + {(docs/templates/landing.ltp)} + {% elseif (ldoc.body) then -- we're rendering non-code elements %} +
    + {* ldoc.body *} +
    + {% elseif (module) then -- we're rendering libary contents %} +
    + {(docs/templates/module.ltp)} +
    + {% end %} +
    +
    + + + + + + diff --git a/garrysmod/gamemodes/helix/docs/templates/module.ltp b/garrysmod/gamemodes/helix/docs/templates/module.ltp new file mode 100644 index 0000000..f49b9fa --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/templates/module.ltp @@ -0,0 +1,123 @@ + +
    +

    {{mod.name}}

    +

    {* ldoc.markup(mod.summary) *}

    +
    + +

    {* ldoc.markup(mod.description) *}

    + +{% for kind, items in mod.kinds() do %} +

    {{kind}}

    + + {% for item in items() do %} +
    +
    + +

    {* ldoc.realm_icon(item.tags.realm[1]) *}{{ldoc.display_name(item)}}

    +
    + + {% if (item.tags.internal) then %} +
    +
    Internal
    +

    This is an internal function! You are able to use it, but you risk unintended side effects if used incorrectly.

    +
    + {% end %} + + {% if (item.module and item.module.type ~= "hooks") then %} + View source » + {% end %} + + {% if (ldoc.descript(item):len() == 0) then %} +
    +
    Incomplete
    +

    Documentation for this section is incomplete and needs expanding.

    +
    + {% else %} +

    {* ldoc.markup(ldoc.descript(item)) *}

    + {% end %} +
    + + {# function arguments #} + {% if (item.params and #item.params > 0) then %} + {% local subnames = mod.kinds:type_of(item).subnames %} + + {% if (subnames) then %} +

    {{subnames}}

    + {% end %} + + {% for argument in ldoc.modules.iter(item.params) do %} + {% local argument, sublist = item:subparam(argument) %} + +
      + {% for argumentName in ldoc.modules.iter(argument) do %} + {% local displayName = item:display_name_of(argumentName) %} + {% local type = ldoc.typename(item:type_of_param(argumentName)) %} + {% local default = item:default_of_param(argumentName) %} + +
    • + {{displayName}} + + {% if (type ~= "") then %} + {* type *} + {% end %} + + {% if (default and default ~= true) then %} + default: {{default}} + {% elseif (default) then %} + optional + {% end %} + +

      {* ldoc.markup(item.params.map[argumentName]) *}

      +
    • + {% end %} +
    + {% end %} + {% end %} + + {# function returns #} + {% if ((not ldoc.no_return_or_parms) and item.retgroups) then %} + {% local groups = item.retgroups %} + +

    Returns

    +
      + {% for i, group in ldoc.ipairs(groups) do %} + {% for returnValue in group:iter() do %} + {% local type, ctypes = item:return_type(returnValue) %} + {% type = ldoc.typename(type) %} + +
    • + {% if (type ~= "") then %} + {* type *} + {% else -- we'll assume that it will return a variable type if none is set %} + any + {% end %} + +

      {* ldoc.markup(returnValue.text) *}

      +
    • + {% end %} + + {% if (i ~= #groups) then %} +
      OR
      + {% end %} + {% end %} +
    + {% end %} + + {% if (item.usage) then -- function usage %} +

    Example Usage

    + {% for usage in ldoc.modules.iter(item.usage) do %} +
    {* usage *}
    + {% end %} + {% end %} + + {% if (item.see) then %} +

    See Also

    +
      + {% for see in ldoc.modules.iter(item.see) do %} +
    • {{see.label}}
    • + {% end %} +
    + {% end %} +
    + {% end %} +{% end %} diff --git a/garrysmod/gamemodes/helix/docs/templates/sidebar.ltp b/garrysmod/gamemodes/helix/docs/templates/sidebar.ltp new file mode 100644 index 0000000..b40706c --- /dev/null +++ b/garrysmod/gamemodes/helix/docs/templates/sidebar.ltp @@ -0,0 +1,69 @@ + +{% +local function isKindExpandable(kind) + return kind ~= "Manual" +end +%} + + diff --git a/garrysmod/gamemodes/helix/entities/entities/ix_item.lua b/garrysmod/gamemodes/helix/entities/entities/ix_item.lua new file mode 100644 index 0000000..7d4fc74 --- /dev/null +++ b/garrysmod/gamemodes/helix/entities/entities/ix_item.lua @@ -0,0 +1,327 @@ + +AddCSLuaFile() + +ENT.Base = "base_entity" +ENT.Type = "anim" +ENT.PrintName = "Item" +ENT.Category = "Helix" +ENT.Spawnable = false +ENT.ShowPlayerInteraction = true +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.bNoPersist = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "ItemID") +end + +if (SERVER) then + local invalidBoundsMin = Vector(-8, -8, -8) + local invalidBoundsMax = Vector(8, 8, 8) + + util.AddNetworkString("ixItemEntityAction") + + function ENT:Initialize() + self:SetModel("models/props_junk/watermelon01.mdl") + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self.health = 50 + + local physObj = self:GetPhysicsObject() + + if (IsValid(physObj)) then + physObj:EnableMotion(true) + physObj:Wake() + end + end + + function ENT:Use(activator, caller) + local itemTable = self:GetItemTable() + + if (IsValid(caller) and caller:IsPlayer() and caller:GetCharacter() and itemTable) then + itemTable.player = caller + itemTable.entity = self + + if (itemTable.functions.take.OnCanRun(itemTable)) then + caller:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client) + if (!ix.item.PerformInventoryAction(client, "take", self)) then + return false -- do not mark dirty if interaction fails + end + end) + end + + itemTable.player = nil + itemTable.entity = nil + end + end + + function ENT:SetItem(itemID) + local itemTable = ix.item.instances[itemID] + + if (itemTable) then + local material = itemTable:GetMaterial(self) + + self:SetSkin(itemTable:GetSkin()) + self:SetModel(itemTable:GetModel()) + + if (material) then + self:SetMaterial(material) + end + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetItemID(itemTable.uniqueID) + self.ixItemID = itemID + + if (!table.IsEmpty(itemTable.data)) then + self:SetNetVar("data", itemTable.data) + end + + local physObj = self:GetPhysicsObject() + + if (!IsValid(physObj)) then + self:PhysicsInitBox(invalidBoundsMin, invalidBoundsMax) + self:SetCollisionBounds(invalidBoundsMin, invalidBoundsMax) + end + + if (IsValid(physObj)) then + physObj:EnableMotion(true) + physObj:Wake() + end + + if (itemTable.OnEntityCreated) then + itemTable:OnEntityCreated(self) + end + end + end + + function ENT:OnDuplicated(entTable) + local itemID = entTable.ixItemID + local itemTable = ix.item.instances[itemID] + + ix.item.Instance(0, itemTable.uniqueID, itemTable.data, 1, 1, function(item) + self:SetItem(item:GetID()) + end) + end + + function ENT:OnTakeDamage(damageInfo) + local itemTable = ix.item.instances[self.ixItemID] + + if (itemTable.OnEntityTakeDamage + and itemTable:OnEntityTakeDamage(self, damageInfo) == false) then + return + end + + local damage = damageInfo:GetDamage() + self:SetHealth(self:Health() - damage) + + if (self:Health() <= 0 and !self.ixIsDestroying) then + self.ixIsDestroying = true + self.ixDamageInfo = {damageInfo:GetAttacker(), damage, damageInfo:GetInflictor()} + self:Remove() + end + end + + function ENT:OnRemove() + if (!ix.shuttingDown and !self.ixIsSafe and self.ixItemID) then + local itemTable = ix.item.instances[self.ixItemID] + + if (itemTable) then + if (self.ixIsDestroying) then + self:EmitSound("physics/cardboard/cardboard_box_break"..math.random(1, 3)..".wav") + local position = self:LocalToWorld(self:OBBCenter()) + + local effect = EffectData() + effect:SetStart(position) + effect:SetOrigin(position) + effect:SetScale(3) + util.Effect("GlassImpact", effect) + + if (itemTable.OnDestroyed) then + itemTable:OnDestroyed(self) + end + + ix.log.Add(self.ixDamageInfo[1], "itemDestroy", itemTable:GetName(), itemTable:GetID()) + end + + if (itemTable.OnRemoved) then + itemTable:OnRemoved() + end + + local query = mysql:Delete("ix_items") + query:Where("item_id", self.ixItemID) + query:Execute() + end + end + end + + function ENT:Think() + local itemTable = self:GetItemTable() + + if (!itemTable) then + self:Remove() + end + + if (itemTable.Think) then + itemTable:Think(self) + end + + return true + end + + function ENT:UpdateTransmitState() + return TRANSMIT_PVS + end + + net.Receive("ixItemEntityAction", function(length, client) + ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadEntity()) + end) +else + ENT.PopulateEntityInfo = true + + local shadeColor = Color(0, 0, 0, 200) + local blockSize = 4 + local blockSpacing = 2 + + function ENT:OnPopulateEntityInfo(tooltip) + local item = self:GetItemTable() + + if (!item) then + return + end + + local oldData = item.data + + item.data = self:GetNetVar("data", {}) + item.entity = self + + ix.hud.PopulateItemTooltip(tooltip, item) + + local name = tooltip:GetRow("name") + local color = name and name:GetBackgroundColor() or ix.config.Get("color") + + -- set the arrow to be the same colour as the title/name row + tooltip:SetArrowColor(color) + + if ((item.width > 1 or item.height > 1) and + hook.Run("ShouldDrawItemSize", item) != false) then + + local sizeHeight = item.height * blockSize + item.height * blockSpacing + local size = tooltip:Add("Panel") + size:SetWide(tooltip:GetWide()) + + if (tooltip:IsMinimal()) then + size:SetTall(sizeHeight) + size:Dock(TOP) + size:SetZPos(-999) + else + size:SetTall(sizeHeight + 8) + size:Dock(BOTTOM) + end + + size.Paint = function(sizePanel, width, height) + if (!tooltip:IsMinimal()) then + surface.SetDrawColor(ColorAlpha(shadeColor, 60)) + surface.DrawRect(0, 0, width, height) + end + + local x, y = width * 0.5 - 1, height * 0.5 - 1 + local itemWidth = item.width - 1 + local itemHeight = item.height - 1 + local heightDifference = ((itemHeight + 1) * blockSize + blockSpacing * itemHeight) + + x = x - (itemWidth * blockSize + blockSpacing * itemWidth) * 0.5 + y = y - heightDifference * 0.5 + + for i = 0, itemHeight do + for j = 0, itemWidth do + local blockX, blockY = x + j * blockSize + j * blockSpacing, y + i * blockSize + i * blockSpacing + + surface.SetDrawColor(shadeColor) + surface.DrawRect(blockX + 1, blockY + 1, blockSize, blockSize) + + surface.SetDrawColor(color) + surface.DrawRect(blockX, blockY, blockSize, blockSize) + end + end + end + + tooltip:SizeToContents() + end + + item.entity = nil + item.data = oldData + end + + function ENT:DrawTranslucent() + local itemTable = self:GetItemTable() + + if (itemTable and itemTable.DrawEntity) then + itemTable:DrawEntity(self) + end + end + + function ENT:Draw() + self:DrawModel() + end +end + +function ENT:GetEntityMenu(client) + local itemTable = self:GetItemTable() + local options = {} + + if (!itemTable) then + return false + end + + itemTable.player = client + itemTable.entity = self + + for k, v in SortedPairs(itemTable.functions) do + if (k == "take" or k == "combine") then + continue + end + + if (v.OnCanRun and v.OnCanRun(itemTable) == false) then + continue + end + + -- we keep the localized phrase since we aren't using the callbacks - the name won't matter in this case + options[L(v.name or k)] = function() + local send = true + + if (v.OnClick) then + send = v.OnClick(itemTable) + end + + if (v.sound) then + surface.PlaySound(v.sound) + end + + if (send != false) then + net.Start("ixItemEntityAction") + net.WriteString(k) + net.WriteEntity(self) + net.SendToServer() + end + + -- don't run callbacks since we're handling it manually + return false + end + end + + itemTable.player = nil + itemTable.entity = nil + + return options +end + +function ENT:GetItemTable() + return ix.item.list[self:GetItemID()] +end + +function ENT:GetData(key, default) + local data = self:GetNetVar("data", {}) + + return data[key] or default +end diff --git a/garrysmod/gamemodes/helix/entities/entities/ix_money.lua b/garrysmod/gamemodes/helix/entities/entities/ix_money.lua new file mode 100644 index 0000000..1124f57 --- /dev/null +++ b/garrysmod/gamemodes/helix/entities/entities/ix_money.lua @@ -0,0 +1,64 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.PrintName = "Money" +ENT.Category = "Helix" +ENT.Spawnable = false +ENT.ShowPlayerInteraction = true +ENT.bNoPersist = true + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "Amount") +end + +if (SERVER) then + local invalidBoundsMin = Vector(-8, -8, -8) + local invalidBoundsMax = Vector(8, 8, 8) + + function ENT:Initialize() + self:SetModel(ix.currency.model) + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local physObj = self:GetPhysicsObject() + + if (IsValid(physObj)) then + physObj:EnableMotion(true) + physObj:Wake() + else + self:PhysicsInitBox(invalidBoundsMin, invalidBoundsMax) + self:SetCollisionBounds(invalidBoundsMin, invalidBoundsMax) + end + end + + function ENT:Use(activator) + if (self.ixSteamID and self.ixCharID) then + local char = activator:GetCharacter() + + if (char and self.ixCharID != char:GetID() and self.ixSteamID == activator:SteamID()) then + activator:NotifyLocalized("itemOwned") + return false + end + end + + activator:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client) + if (hook.Run("OnPickupMoney", client, self) != false) then + self:Remove() + end + end) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_PVS + end +else + ENT.PopulateEntityInfo = true + + function ENT:OnPopulateEntityInfo(container) + local text = container:AddRow("name") + text:SetImportant() + text:SetText(ix.currency.Get(self:GetAmount())) + text:SizeToContents() + end +end diff --git a/garrysmod/gamemodes/helix/entities/entities/ix_shipment.lua b/garrysmod/gamemodes/helix/entities/entities/ix_shipment.lua new file mode 100644 index 0000000..c3c43ab --- /dev/null +++ b/garrysmod/gamemodes/helix/entities/entities/ix_shipment.lua @@ -0,0 +1,137 @@ + +AddCSLuaFile() + +ENT.Type = "anim" +ENT.PrintName = "Shipment" +ENT.Category = "Helix" +ENT.Spawnable = false +ENT.ShowPlayerInteraction = true +ENT.bNoPersist = true + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "DeliveryTime") +end + +if (SERVER) then + function ENT:Initialize() + self:SetModel("models/Items/item_item_crate.mdl") + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:PrecacheGibs() + + local physObj = self:GetPhysicsObject() + + if (IsValid(physObj)) then + physObj:EnableMotion(true) + physObj:Wake() + end + + self:SetDeliveryTime(CurTime() + 120) + + timer.Simple(120, function() + if (IsValid(self)) then + self:Remove() + end + end) + end + + function ENT:Use(activator) + activator:PerformInteraction(ix.config.Get("itemPickupTime", 0.5), self, function(client) + if (client:GetCharacter() and client:GetCharacter():GetID() == self:GetNetVar("owner", 0) + and hook.Run("CanPlayerOpenShipment", client, self) != false) then + client.ixShipment = self + + net.Start("ixShipmentOpen") + net.WriteEntity(self) + net.WriteTable(self.items) + net.Send(client) + end + + -- don't mark dirty since the player could come back and use this shipment again later + return false + end) + end + + function ENT:SetItems(items) + self.items = items + end + + function ENT:GetItemCount() + local count = 0 + + for _, v in pairs(self.items) do + count = count + math.max(v, 0) + end + + return count + end + + function ENT:OnRemove() + self:EmitSound("physics/cardboard/cardboard_box_break"..math.random(1, 3)..".wav") + + local position = self:LocalToWorld(self:OBBCenter()) + + local effect = EffectData() + effect:SetStart(position) + effect:SetOrigin(position) + effect:SetScale(3) + util.Effect("GlassImpact", effect) + end + + function ENT:UpdateTransmitState() + return TRANSMIT_PVS + end +else + ENT.PopulateEntityInfo = true + + local size = 150 + local tempMat = Material("particle/warp1_warp", "alphatest") + + function ENT:Draw() + local pos, ang = self:GetPos(), self:GetAngles() + + self:DrawModel() + + pos = pos + self:GetUp() * 25 + pos = pos + self:GetForward() * 1 + pos = pos + self:GetRight() * 3 + + local delTime = math.max(math.ceil(self:GetDeliveryTime() - CurTime()), 0) + + local func = function() + surface.SetMaterial(tempMat) + surface.SetDrawColor(0, 0, 0, 200) + surface.DrawTexturedRect(-size / 2, -size / 2 - 10, size, size) + + ix.util.DrawText("k", 0, 0, color_white, 1, 4, "ixIconsBig") + ix.util.DrawText(delTime, 0, -10, color_white, 1, 5, "ixBigFont") + end + + cam.Start3D2D(pos, ang, .15) + func() + cam.End3D2D() + + ang:RotateAroundAxis(ang:Right(), 180) + pos = pos - self:GetUp() * 26 + + cam.Start3D2D(pos, ang, .15) + func() + cam.End3D2D() + end + + function ENT:OnPopulateEntityInfo(container) + local owner = ix.char.loaded[self:GetNetVar("owner", 0)] + + local name = container:AddRow("name") + name:SetImportant() + name:SetText(L("shipment")) + name:SizeToContents() + + if (owner) then + local description = container:AddRow("description") + description:SetText(L("shipmentDesc", owner:GetName())) + description:SizeToContents() + end + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/cl_init.lua b/garrysmod/gamemodes/helix/gamemode/cl_init.lua new file mode 100644 index 0000000..21ba52b --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/cl_init.lua @@ -0,0 +1,50 @@ + +-- unix systems are case-sensitive, are missing fonts, or use different naming conventions +if (!system.IsWindows()) then + local fontOverrides = { + ["Roboto"] = "Roboto Regular", + ["Roboto Th"] = "Roboto Thin", + ["Roboto Lt"] = "Roboto Light", + ["Roboto Bk"] = "Roboto Black", + ["coolvetica"] = "Coolvetica", + ["tahoma"] = "Tahoma", + ["Harmonia Sans Pro Cyr"] = "Roboto Regular", + ["Harmonia Sans Pro Cyr Light"] = "Roboto Light", + ["Century Gothic"] = "Roboto Regular" + } + + if (system.IsOSX()) then + fontOverrides["Consolas"] = "Monaco" + else + fontOverrides["Consolas"] = "Courier New" + end + + local ixCreateFont = surface.CreateFont + + function surface.CreateFont(name, info) -- luacheck: globals surface + local font = info.font + + if (font and fontOverrides[font]) then + info.font = fontOverrides[font] + end + + ixCreateFont(name, info) + end +end + +DeriveGamemode("sandbox") +ix = ix or {util = {}, gui = {}, meta = {}} + +-- Include core files. +include("core/sh_util.lua") +include("core/sh_data.lua") +include("shared.lua") + +-- Sandbox stuff +CreateConVar("cl_weaponcolor", "0.30 1.80 2.10", { + FCVAR_ARCHIVE, FCVAR_USERINFO, FCVAR_DONTRECORD +}, "The value is a Vector - so between 0-1 - not between 0-255") + +timer.Remove("HintSystem_OpeningMenu") +timer.Remove("HintSystem_Annoy1") +timer.Remove("HintSystem_Annoy2") diff --git a/garrysmod/gamemodes/helix/gamemode/config/sh_config.lua b/garrysmod/gamemodes/helix/gamemode/config/sh_config.lua new file mode 100644 index 0000000..d510e72 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/config/sh_config.lua @@ -0,0 +1,223 @@ + +-- You can change the default language by setting this in your schema. +ix.config.language = "english" + +--[[ + DO NOT CHANGE ANYTHING BELOW THIS. + + This is the Helix main configuration file. + This file DOES NOT set any configurations, instead it just prepares them. + To set the configuration, there is a "Config" tab in the F1 menu for super admins and above. + Use the menu to change the variables, not this file. +--]] + +ix.config.Add("maxCharacters", 5, "The maximum number of characters a player can have.", nil, { + data = {min = 1, max = 50}, + category = "characters" +}) +ix.config.Add("color", Color(75, 119, 190, 255), "The main color theme for the framework.", function(oldValue, newValue) + if (newValue.a != 255) then + ix.config.Set("color", ColorAlpha(newValue, 255)) + return + end + + if (CLIENT) then + hook.Run("ColorSchemeChanged", newValue) + end +end, {category = "appearance"}) +ix.config.Add("font", "Roboto Th", "The font used to display titles.", function(oldValue, newValue) + if (CLIENT) then + hook.Run("LoadFonts", newValue, ix.config.Get("genericFont")) + end +end, {category = "appearance"}) + +ix.config.Add("genericFont", "Roboto", "The font used to display generic texts.", function(oldValue, newValue) + if (CLIENT) then + hook.Run("LoadFonts", ix.config.Get("font"), newValue) + end +end, {category = "appearance"}) + +ix.config.Add("maxAttributes", 100, "The maximum amount each attribute can be.", nil, { + data = {min = 0, max = 100}, + category = "characters" +}) +ix.config.Add("chatAutoFormat", true, "Whether or not to automatically capitalize and punctuate in-character text.", nil, { + category = "Chat" +}) +ix.config.Add("chatRange", 280, "The maximum distance a person's IC chat message goes to.", nil, { + data = {min = 10, max = 5000, decimals = 1}, + category = "chat" +}) +ix.config.Add("chatMax", 256, "The maximum amount of characters that can be sent in chat.", nil, { + data = {min = 32, max = 1024}, + category = "chat" +}) +ix.config.Add("chatColor", Color(255, 255, 150), "The default color for IC chat.", nil, {category = "chat"}) +ix.config.Add("chatListenColor", Color(175, 255, 150), "The color for IC chat if you are looking at the speaker.", nil, { + category = "chat" +}) +ix.config.Add("oocDelay", 10, "The delay before a player can use OOC chat again in seconds.", nil, { + data = {min = 0, max = 10000}, + category = "chat" +}) +ix.config.Add("allowGlobalOOC", true, "Whether or not Global OOC is enabled.", nil, { + category = "chat" +}) +ix.config.Add("loocDelay", 0, "The delay before a player can use LOOC chat again in seconds.", nil, { + data = {min = 0, max = 10000}, + category = "chat" +}) +ix.config.Add("spawnTime", 5, "The time it takes to respawn.", nil, { + data = {min = 0, max = 10000}, + category = "characters" +}) +ix.config.Add("inventoryWidth", 6, "How many slots in a row there is in a default inventory.", nil, { + data = {min = 0, max = 20}, + category = "characters" +}) +ix.config.Add("inventoryHeight", 4, "How many slots in a column there is in a default inventory.", nil, { + data = {min = 0, max = 20}, + category = "characters" +}) +ix.config.Add("minNameLength", 4, "The minimum number of characters in a name.", nil, { + data = {min = 4, max = 64}, + category = "characters" +}) +ix.config.Add("maxNameLength", 32, "The maximum number of characters in a name.", nil, { + data = {min = 16, max = 128}, + category = "characters" +}) +ix.config.Add("minDescriptionLength", 16, "The minimum number of characters in a description.", nil, { + data = {min = 0, max = 300}, + category = "characters" +}) +ix.config.Add("saveInterval", 300, "How often characters save in seconds.", nil, { + data = {min = 60, max = 3600}, + category = "characters" +}) +ix.config.Add("walkSpeed", 130, "How fast a player normally walks.", function(oldValue, newValue) + for _, v in player.Iterator() do + v:SetWalkSpeed(newValue) + end +end, { + data = {min = 75, max = 500}, + category = "characters" +}) +ix.config.Add("runSpeed", 235, "How fast a player normally runs.", function(oldValue, newValue) + for _, v in player.Iterator() do + v:SetRunSpeed(newValue) + end +end, { + data = {min = 75, max = 500}, + category = "characters" +}) +ix.config.Add("walkRatio", 0.5, "How fast one goes when holding ALT.", nil, { + data = {min = 0, max = 1, decimals = 1}, + category = "characters" +}) +ix.config.Add("intro", true, "Whether or not the Helix intro is enabled for new players.", nil, { + category = "appearance" +}) +ix.config.Add("music", "music/hl2_song2.mp3", "The default music played in the character menu.", nil, { + category = "appearance" +}) +ix.config.Add("communityURL", "https://nebulous.cloud/", "The URL to navigate to when the community button is clicked.", nil, { + category = "appearance" +}) +ix.config.Add("communityText", "@community", + "The text to display on the community button. You can use language phrases by prefixing with @", nil, { + category = "appearance" +}) +ix.config.Add("vignette", true, "Whether or not the vignette is shown.", nil, { + category = "appearance" +}) +ix.config.Add("scoreboardRecognition", false, "Whether or not recognition is used in the scoreboard.", nil, { + category = "characters" +}) +ix.config.Add("defaultMoney", 0, "The amount of money that players start with.", nil, { + category = "characters", + data = {min = 0, max = 1000} +}) +ix.config.Add("minMoneyDropAmount", 1, "The minimum amount of money that can be dropped.", nil, { + category = "characters", + data = {min = 1, max = 1000} +}) +ix.config.Add("allowVoice", false, "Whether or not voice chat is allowed.", function(oldValue, newValue) + if (SERVER) then + hook.Run("VoiceToggled", newValue) + end +end, { + category = "server" +}) +ix.config.Add("voiceDistance", 600.0, "How far can the voice be heard.", function(oldValue, newValue) + if (SERVER) then + hook.Run("VoiceDistanceChanged", newValue) + end +end, { + category = "server", + data = {min = 0, max = 5000, decimals = 1} +}) +ix.config.Add("weaponAlwaysRaised", false, "Whether or not weapons are always raised.", nil, { + category = "server" +}) +ix.config.Add("weaponRaiseTime", 1, "The time it takes for a weapon to raise.", nil, { + data = {min = 0.1, max = 60, decimals = 1}, + category = "server" +}) +ix.config.Add("allowBusiness", true, "Whether or not business is enabled.", nil, { + category = "server" +}) +ix.config.Add("maxHoldWeight", 500, "The maximum weight that a player can carry in their hands.", nil, { + data = {min = 1, max = 500}, + category = "interaction" +}) +ix.config.Add("throwForce", 732, "How hard a player can throw the item that they're holding.", nil, { + data = {min = 0, max = 8192}, + category = "interaction" +}) +ix.config.Add("allowPush", true, "Whether or not pushing with hands is allowed.", nil, { + category = "interaction" +}) +ix.config.Add("itemPickupTime", 0.5, "How long it takes to pick up and put an item in your inventory.", nil, { + data = {min = 0, max = 5, decimals = 1}, + category = "interaction" +}) +ix.config.Add("year", 2015, "The current in-game year.", function(oldValue, newValue) + if (SERVER and !ix.date.bSaving) then + ix.date.ResolveOffset() + ix.date.current:setyear(newValue) + ix.date.Send() + end +end, { + data = {min = 1, max = 9999}, + category = "date" +}) +ix.config.Add("month", 1, "The current in-game month.", function(oldValue, newValue) + if (SERVER and !ix.date.bSaving) then + ix.date.ResolveOffset() + ix.date.current:setmonth(newValue) + ix.date.Send() + end +end, { + data = {min = 1, max = 12}, + category = "date" +}) +ix.config.Add("day", 1, "The current in-game day.", function(oldValue, newValue) + if (SERVER and !ix.date.bSaving) then + ix.date.ResolveOffset() + ix.date.current:setday(newValue) + ix.date.Send() + end +end, { + data = {min = 1, max = 31}, + category = "date" +}) +ix.config.Add("secondsPerMinute", 60, "How many seconds it takes for a minute to pass in-game.", function(oldValue, newValue) + if (SERVER and !ix.date.bSaving) then + ix.date.UpdateTimescale(newValue) + ix.date.Send() + end +end, { + data = {min = 0.01, max = 120}, + category = "date" +}) diff --git a/garrysmod/gamemodes/helix/gamemode/config/sh_options.lua b/garrysmod/gamemodes/helix/gamemode/config/sh_options.lua new file mode 100644 index 0000000..9b05e6d --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/config/sh_options.lua @@ -0,0 +1,73 @@ + +if (CLIENT) then + ix.option.Add("animationScale", ix.type.number, 1, { + category = "appearance", min = 0.3, max = 2, decimals = 1 + }) + + ix.option.Add("24hourTime", ix.type.bool, false, { + category = "appearance" + }) + + ix.option.Add("altLower", ix.type.bool, true, { + category = "general" + }) + + ix.option.Add("alwaysShowBars", ix.type.bool, false, { + category = "appearance" + }) + + ix.option.Add("minimalTooltips", ix.type.bool, false, { + category = "appearance" + }) + + ix.option.Add("noticeDuration", ix.type.number, 8, { + category = "appearance", min = 0.1, max = 20, decimals = 1 + }) + + ix.option.Add("noticeMax", ix.type.number, 4, { + category = "appearance", min = 1, max = 20 + }) + + ix.option.Add("cheapBlur", ix.type.bool, false, { + category = "performance" + }) + + ix.option.Add("disableAnimations", ix.type.bool, false, { + category = "performance" + }) + + ix.option.Add("openBags", ix.type.bool, true, { + category = "general" + }) + + ix.option.Add("showIntro", ix.type.bool, true, { + category = "general" + }) + + ix.option.Add("escCloseMenu", ix.type.bool, false, { + category = "general" + }) +end + +ix.option.Add("language", ix.type.array, ix.config.language or "english", { + category = "general", + bNetworked = true, + populate = function() + local entries = {} + + for k, _ in SortedPairs(ix.lang.stored) do + local name = ix.lang.names[k] + local name2 = k:utf8sub(1, 1):utf8upper() .. k:utf8sub(2) + + if (name) then + name = name .. " (" .. name2 .. ")" + else + name = name2 + end + + entries[k] = name + end + + return entries + end +}) diff --git a/garrysmod/gamemodes/helix/gamemode/core/cl_skin.lua b/garrysmod/gamemodes/helix/gamemode/core/cl_skin.lua new file mode 100644 index 0000000..304e9b1 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/cl_skin.lua @@ -0,0 +1,616 @@ + +local gradient = surface.GetTextureID("vgui/gradient-d") +local gradientUp = surface.GetTextureID("vgui/gradient-u") +local gradientLeft = surface.GetTextureID("vgui/gradient-l") +local gradientRadial = Material("helix/gui/radial-gradient.png") +local defaultBackgroundColor = Color(30, 30, 30, 200) + +local SKIN = {} +derma.DefineSkin("helix", "The base skin for the Helix framework.", SKIN) + +SKIN.fontCategory = "ixMediumLightFont" +SKIN.fontCategoryBlur = "ixMediumLightBlurFont" +SKIN.fontSegmentedProgress = "ixMediumLightFont" + +SKIN.Colours = table.Copy(derma.SkinList.Default.Colours) + +SKIN.Colours.Info = Color(100, 185, 255) +SKIN.Colours.Success = Color(64, 185, 85) +SKIN.Colours.Error = Color(255, 100, 100) +SKIN.Colours.Warning = Color(230, 180, 0) +SKIN.Colours.MenuLabel = color_white +SKIN.Colours.DarkerBackground = Color(0, 0, 0, 77) + +SKIN.Colours.SegmentedProgress = {} +SKIN.Colours.SegmentedProgress.Bar = Color(64, 185, 85) +SKIN.Colours.SegmentedProgress.Text = color_white + +SKIN.Colours.Area = {} + +SKIN.Colours.Window.TitleActive = Color(0, 0, 0) +SKIN.Colours.Window.TitleInactive = color_white + +SKIN.Colours.Button.Normal = color_white +SKIN.Colours.Button.Hover = color_white +SKIN.Colours.Button.Down = Color(180, 180, 180) +SKIN.Colours.Button.Disabled = Color(0, 0, 0, 100) + +SKIN.Colours.Label.Default = color_white + +function SKIN.tex.Menu_Strip(x, y, width, height, color) + surface.SetDrawColor(0, 0, 0, 200) + surface.DrawRect(x, y, width, height) + + surface.SetDrawColor(ColorAlpha(color or ix.config.Get("color"), 175)) + surface.SetTexture(gradient) + surface.DrawTexturedRect(x, y, width, height) + + surface.SetTextColor(color_white) +end + +hook.Add("ColorSchemeChanged", "ixSkin", function(color) + SKIN.Colours.Area.Background = color +end) + +function SKIN:DrawHelixCurved(x, y, radius, segments, barHeight, fraction, color, altColor) + radius = radius or math.min(ScreenScale(72), 128) * 2 + segments = segments or 76 + barHeight = barHeight or 64 + color = color or ix.config.Get("color") + altColor = altColor or Color(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) + fraction = fraction or 1 + + surface.SetTexture(-1) + + for i = 1, math.ceil(segments) do + local angle = math.rad((i / segments) * -360) + local barX = x + math.sin(angle + (fraction * math.pi * 2)) * radius + local barY = y + math.cos(angle + (fraction * math.pi * 2)) * radius + local barOffset = math.sin(SysTime() + i * 0.5) + + if (barOffset > 0) then + surface.SetDrawColor(color) + else + surface.SetDrawColor(altColor) + end + + surface.DrawTexturedRectRotated(barX, barY, 4, barOffset * (barHeight * fraction), math.deg(angle)) + end +end + +function SKIN:DrawHelix(x, y, width, height, segments, color, fraction, speed) + segments = segments or width * 0.05 + color = color or ix.config.Get("color") + fraction = fraction or 0.25 + speed = speed or 1 + + for i = 1, math.ceil(segments) do + local offset = math.sin((SysTime() + speed) + i * fraction) + local barHeight = height * offset + + surface.SetTexture(-1) + + if (offset > 0) then + surface.SetDrawColor(color) + else + surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) + end + + surface.DrawTexturedRectRotated(x + (i / segments) * width, y + height * 0.5, 4, barHeight, 0) + end +end + +function SKIN:PaintFrame(panel) + if (!panel.bNoBackgroundBlur) then + ix.util.DrawBlur(panel, 10) + end + + surface.SetDrawColor(30, 30, 30, 150) + surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall()) + + if (panel:GetTitle() != "" or panel.btnClose:IsVisible()) then + surface.SetDrawColor(ix.config.Get("color")) + surface.DrawRect(0, 0, panel:GetWide(), 24) + + if (panel.bHighlighted) then + self:DrawImportantBackground(0, 0, panel:GetWide(), 24, ColorAlpha(color_white, 22)) + end + end + + surface.SetDrawColor(ix.config.Get("color")) + surface.DrawOutlinedRect(0, 0, panel:GetWide(), panel:GetTall()) +end + +function SKIN:PaintBaseFrame(panel, width, height) + if (!panel.bNoBackgroundBlur) then + ix.util.DrawBlur(panel, 10) + end + + surface.SetDrawColor(30, 30, 30, 150) + surface.DrawRect(0, 0, width, height) + + surface.SetDrawColor(ix.config.Get("color")) + surface.DrawOutlinedRect(0, 0, width, height) +end + +function SKIN:DrawImportantBackground(x, y, width, height, color) + color = color or defaultBackgroundColor + + surface.SetTexture(gradientLeft) + surface.SetDrawColor(color) + surface.DrawTexturedRect(x, y, width, height) +end + +function SKIN:DrawCharacterStatusBackground(panel, fraction) + surface.SetDrawColor(0, 0, 0, fraction * 100) + surface.DrawRect(0, 0, ScrW(), ScrH()) + ix.util.DrawBlurAt(0, 0, ScrW(), ScrH(), 5, nil, fraction * 255) +end + +function SKIN:PaintPanel(panel) + if (panel.m_bBackground) then + local width, height = panel:GetSize() + if (panel.m_bgColor) then + surface.SetDrawColor(panel.m_bgColor) + else + surface.SetDrawColor(30, 30, 30, 100) + end + surface.DrawRect(0, 0, width, height) + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawOutlinedRect(0, 0, width, height) + end +end + +function SKIN:PaintMenuBackground(panel, width, height, alphaFraction) + alphaFraction = alphaFraction or 1 + + surface.SetDrawColor(ColorAlpha(color_black, alphaFraction * 255)) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height) + + ix.util.DrawBlur(panel, alphaFraction * 15, nil, 200) +end + +function SKIN:PaintPlaceholderPanel(panel, width, height, barWidth, padding) + local size = math.max(width, height) + barWidth = barWidth or size * 0.05 + + local segments = size / barWidth + + for i = 1, segments do + surface.SetTexture(-1) + surface.SetDrawColor(0, 0, 0, 88) + surface.DrawTexturedRectRotated(i * barWidth, i * barWidth, barWidth, size * 2, -45) + end +end + +function SKIN:PaintCategoryPanel(panel, text, color) + text = text or "" + color = color or ix.config.Get("color") + + surface.SetFont(self.fontCategoryBlur) + + local textHeight = select(2, surface.GetTextSize(text)) + 6 + local width, height = panel:GetSize() + + surface.SetDrawColor(0, 0, 0, 100) + surface.DrawRect(0, textHeight, width, height - textHeight) + + self:DrawImportantBackground(0, 0, width, textHeight, color) + + surface.SetTextColor(color_black) + surface.SetTextPos(4, 4) + surface.DrawText(text) + + surface.SetFont(self.fontCategory) + surface.SetTextColor(color_white) + surface.SetTextPos(4, 4) + surface.DrawText(text) + + surface.SetDrawColor(color) + surface.DrawOutlinedRect(0, 0, width, height) + + return 1, textHeight, 1, 1 +end + +function SKIN:PaintButton(panel) + if (panel.m_bBackground) then + local w, h = panel:GetWide(), panel:GetTall() + local alpha = 50 + + if (panel:GetDisabled()) then + alpha = 10 + elseif (panel.Depressed) then + alpha = 180 + elseif (panel.Hovered) then + alpha = 75 + end + + if (panel:GetParent() and panel:GetParent():GetName() == "DListView_Column") then + surface.SetDrawColor(color_white) + surface.DrawRect(0, 0, w, h) + end + + surface.SetDrawColor(30, 30, 30, alpha) + surface.DrawRect(0, 0, w, h) + + surface.SetDrawColor(0, 0, 0, 180) + surface.DrawOutlinedRect(0, 0, w, h) + + surface.SetDrawColor(180, 180, 180, 2) + surface.DrawOutlinedRect(1, 1, w - 2, h - 2) + end +end + +function SKIN:PaintEntityInfoBackground(panel, width, height) + ix.util.DrawBlur(panel, 1) + + surface.SetDrawColor(self.Colours.DarkerBackground) + surface.DrawRect(0, 0, width, height) +end + +function SKIN:PaintTooltipBackground(panel, width, height) + ix.util.DrawBlur(panel, 1) + + surface.SetDrawColor(self.Colours.DarkerBackground) + surface.DrawRect(0, 0, width, height) +end + +function SKIN:PaintTooltipMinimalBackground(panel, width, height) + surface.SetDrawColor(0, 0, 0, 150 * panel.fraction) + surface.SetMaterial(gradientRadial) + surface.DrawTexturedRect(0, 0, width, height) +end + +function SKIN:PaintSegmentedProgressBackground(panel, width, height) +end + +function SKIN:PaintSegmentedProgress(panel, width, height) + local font = panel:GetFont() or self.fontSegmentedProgress + local textColor = panel:GetTextColor() or self.Colours.SegmentedProgress.Text + local barColor = panel:GetBarColor() or self.Colours.SegmentedProgress.Bar + local segments = panel:GetSegments() + local segmentHalfWidth = width / #segments * 0.5 + + surface.SetDrawColor(barColor) + surface.DrawRect(0, 0, panel:GetFraction() * width, height) + + for i = 1, #segments do + local text = segments[i] + local x = (i - 1) / #segments * width + segmentHalfWidth + local y = height * 0.5 + + draw.SimpleText(text, font, x, y, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end + +function SKIN:PaintCharacterCreateBackground(panel, width, height) + surface.SetDrawColor(40, 40, 40, 255) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height) +end + +function SKIN:PaintCharacterLoadBackground(panel, width, height) + surface.SetDrawColor(40, 40, 40, panel:GetBackgroundFraction() * 255) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height) +end + +function SKIN:PaintCharacterTransitionOverlay(panel, x, y, width, height, color) + color = color or ix.config.Get("color") + + surface.SetDrawColor(color) + surface.DrawRect(x, y, width, height) +end + +function SKIN:PaintAreaEntry(panel, width, height) + local color = ColorAlpha(panel:GetBackgroundColor() or self.Colours.Area.Background, panel:GetBackgroundAlpha()) + + self:DrawImportantBackground(0, 0, width, height, color) +end + +function SKIN:PaintListRow(panel, width, height) + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, width, height) +end + +function SKIN:PaintSettingsRowBackground(panel, width, height) + local index = panel:GetBackgroundIndex() + local bReset = panel:GetShowReset() + + if (index == 0) then + surface.SetDrawColor(30, 30, 30, 45) + surface.DrawRect(0, 0, width, height) + end + + if (bReset) then + surface.SetDrawColor(self.Colours.Warning) + surface.DrawRect(0, 0, 2, height) + end +end + +function SKIN:PaintVScrollBar(panel, width, height) +end + +function SKIN:PaintHScrollBar(panel, width, height) +end + +function SKIN:PaintScrollBarGrip(panel, width, height) + local parent = panel:GetParent() + + if (IsValid(parent.btnUp)) then + -- Vertical scrollbar + local upButtonHeight = parent.btnUp:GetTall() + local downButtonHeight = parent.btnDown:GetTall() + + DisableClipping(true) + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(4, -upButtonHeight, width - 8, height + upButtonHeight + downButtonHeight) + DisableClipping(false) + else + -- Horizontal scrollbar + local leftButtonWidth = parent.btnLeft:GetWide() + local rightButtonWidth = parent.btnRight:GetWide() + + DisableClipping(true) + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(-leftButtonWidth, 4, width + leftButtonWidth + rightButtonWidth, height - 8) + DisableClipping(false) + end +end + +function SKIN:PaintButtonUp(panel, width, height) +end + +function SKIN:PaintButtonDown(panel, width, height) +end + +function SKIN:PaintButtonLeft(panel, width, height) +end + +function SKIN:PaintButtonRight(panel, width, height) +end + +function SKIN:PaintComboBox(panel, width, height) +end + +function SKIN:PaintComboDownArrow(panel, width, height) + surface.SetFont("ixIconsSmall") + + local textWidth, textHeight = surface.GetTextSize("r") + local alpha = (panel.ComboBox:IsMenuOpen() or panel.ComboBox.Hovered) and 200 or 100 + + surface.SetTextColor(ColorAlpha(ix.config.Get("color"), alpha)) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5) + surface.DrawText("r") +end + +function SKIN:PaintMenu(panel, width, height) + ix.util.DrawBlur(panel) + + surface.SetDrawColor(30, 30, 30, 150) + surface.DrawRect(0, 0, width, height) +end + +function SKIN:PaintMenuOption(panel, width, height) + if (panel.m_bBackground and (panel.Hovered or panel.Highlight)) then + self:DrawImportantBackground(0, 0, width, height, ix.config.Get("color")) + end +end + +function SKIN:PaintHelixSlider(panel, width, height) + surface.SetDrawColor(self.Colours.DarkerBackground) + surface.DrawRect(0, 0, width, height) + + surface.SetDrawColor(self.Colours.Success) + surface.DrawRect(0, 0, panel:GetVisualFraction() * width, height) +end + +function SKIN:PaintChatboxTabButton(panel, width, height) + if (panel:GetActive()) then + surface.SetDrawColor(ix.config.Get("color", Color(75, 119, 190, 255))) + surface.DrawRect(0, 0, width, height) + else + surface.SetDrawColor(0, 0, 0, 100) + surface.DrawRect(0, 0, width, height) + + if (panel:GetUnread()) then + surface.SetDrawColor(ColorAlpha(self.Colours.Warning, Lerp(panel.unreadAlpha, 0, 100))) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height - 1) + end + end + + -- border + surface.SetDrawColor(color_black) + surface.DrawRect(width - 1, 0, 1, height) -- right +end + +function SKIN:PaintChatboxTabs(panel, width, height, alpha) + surface.SetDrawColor(0, 0, 0, 33) + surface.DrawRect(0, 0, width, height) + + surface.SetDrawColor(0, 0, 0, 100) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, height * 0.5, width, height * 0.5) + + local tab = panel:GetActiveTab() + + if (tab) then + local button = tab:GetButton() + local x, _ = button:GetPos() + + -- outline + surface.SetDrawColor(0, 0, 0, 200) + surface.DrawRect(0, height - 1, x, 1) -- left + surface.DrawRect(x + button:GetWide(), height - 1, width - x - button:GetWide(), 1) -- right + end +end + +function SKIN:PaintChatboxBackground(panel, width, height) + ix.util.DrawBlur(panel, 10) + + if (panel:GetActive()) then + surface.SetDrawColor(ColorAlpha(ix.config.Get("color"), 120)) + surface.SetTexture(gradientUp) + surface.DrawTexturedRect(0, panel.tabs.buttons:GetTall(), width, height * 0.25) + end + + surface.SetDrawColor(color_black) + surface.DrawOutlinedRect(0, 0, width, height) +end + +function SKIN:PaintChatboxEntry(panel, width, height) + surface.SetDrawColor(0, 0, 0, 66) + surface.DrawRect(0, 0, width, height) + + panel:DrawTextEntryText(color_white, ix.config.Get("color"), color_white) + + surface.SetDrawColor(color_black) + surface.DrawOutlinedRect(0, 0, width, height) +end + +function SKIN:DrawChatboxPreviewBox(x, y, text, color) + color = color or ix.config.Get("color") + + local textWidth, textHeight = surface.GetTextSize(text) + local width, height = textWidth + 8, textHeight + 8 + + -- background + surface.SetDrawColor(color) + surface.DrawRect(x, y, width, height) + + -- text + surface.SetTextColor(color_white) + surface.SetTextPos(x + width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5) + surface.DrawText(text) + + -- outline + surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255) + surface.DrawOutlinedRect(x, y, width, height) + + return width +end + +function SKIN:DrawChatboxPrefixBox(panel, width, height) + local color = panel:GetBackgroundColor() + + -- background + surface.SetDrawColor(color) + surface.DrawRect(0, 0, width, height) + + -- outline + surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255) + surface.DrawOutlinedRect(0, 0, width, height) +end + + +function SKIN:PaintChatboxAutocompleteEntry(panel, width, height) + -- selected background + if (panel.highlightAlpha > 0) then + self:DrawImportantBackground(0, 0, width, height, ColorAlpha(ix.config.Get("color"), panel.highlightAlpha * 66)) + end + + -- lower border + surface.SetDrawColor(200, 200, 200, 33) + surface.DrawRect(0, height - 1, width, 1) +end + +function SKIN:PaintWindowMinimizeButton(panel, width, height) +end + +function SKIN:PaintWindowMaximizeButton(panel, width, height) +end + +function SKIN:PaintInfoBar(panel, width, height, color) + -- bar + surface.SetDrawColor(color.r, color.g, color.b, 250) + surface.DrawRect(0, 0, width, height) + + -- gradient overlay + surface.SetDrawColor(230, 230, 230, 8) + surface.SetTexture(gradientUp) + surface.DrawTexturedRect(0, 0, width, height) +end + +function SKIN:PaintInfoBarBackground(panel, width, height) + surface.SetDrawColor(230, 230, 230, 15) + surface.DrawRect(0, 0, width, height) + surface.DrawOutlinedRect(0, 0, width, height) + + panel.bar:PaintManual() + + DisableClipping(true) + panel.label:PaintManual() + DisableClipping(false) +end + +function SKIN:PaintInventorySlot(panel, width, height) + surface.SetDrawColor(35, 35, 35, 85) + surface.DrawRect(1, 1, width - 2, height - 2) + + surface.SetDrawColor(0, 0, 0, 250) + surface.DrawOutlinedRect(1, 1, width - 2, height - 2) +end + +function SKIN:PaintDeathScreenBackground(panel, width, height, progress) + surface.SetDrawColor(0, 0, 0, (progress / 0.3) * 255) + surface.DrawRect(0, 0, width, height) +end + +function SKIN:PaintDeathScreen(panel, width, height, progress) + ix.bar.DrawAction() +end + +do + -- check if sounds exist, otherwise fall back to default UI sounds + local bWhoosh = file.Exists("sound/helix/ui/whoosh1.wav", "GAME") + local bRollover = file.Exists("sound/helix/ui/rollover.wav", "GAME") + local bPress = file.Exists("sound/helix/ui/press.wav", "GAME") + local bNotify = file.Exists("sound/helix/ui/REPLACEME.wav", "GAME") -- @todo + + sound.Add({ + name = "Helix.Whoosh", + channel = CHAN_STATIC, + volume = 0.4, + level = 80, + pitch = bWhoosh and {90, 105} or 100, + sound = bWhoosh and { + "helix/ui/whoosh1.wav", + "helix/ui/whoosh2.wav", + "helix/ui/whoosh3.wav", + "helix/ui/whoosh4.wav", + "helix/ui/whoosh5.wav", + "helix/ui/whoosh6.wav" + } or "" + }) + + sound.Add({ + name = "Helix.Rollover", + channel = CHAN_STATIC, + volume = 0.5, + level = 80, + pitch = {95, 105}, + sound = bRollover and "helix/ui/rollover.wav" or "ui/buttonrollover.wav" + }) + + sound.Add({ + name = "Helix.Press", + channel = CHAN_STATIC, + volume = 0.5, + level = 80, + pitch = bPress and {95, 110} or 100, + sound = bPress and "helix/ui/press.wav" or "ui/buttonclickrelease.wav" + }) + + sound.Add({ + name = "Helix.Notify", + channel = CHAN_STATIC, + volume = 0.35, + level = 80, + pitch = 140, + sound = bNotify and "helix/ui/REPLACEME.wav" or "weapons/grenade/tick1.wav" + }) +end + +derma.RefreshSkins() diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_attribute.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_attribute.lua new file mode 100644 index 0000000..1a76b84 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_attribute.lua @@ -0,0 +1,165 @@ + +local gradient = ix.util.GetMaterial("vgui/gradient-u") +local gradient2 = ix.util.GetMaterial("vgui/gradient-d") + +local PANEL = {} + +AccessorFunc(PANEL, "color", "Color") +AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER) +AccessorFunc(PANEL, "boostValue", "Boost", FORCE_NUMBER) +AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER) + +function PANEL:Init() + self:SetTall(20) + + self.add = self:Add("DImageButton") + self.add:SetSize(16, 16) + self.add:Dock(RIGHT) + self.add:DockMargin(2, 2, 2, 2) + self.add:SetImage("icon16/add.png") + self.add.OnMousePressed = function() + self.pressing = 1 + self:DoChange() + self.add:SetAlpha(150) + end + self.add.OnMouseReleased = function() + if (self.pressing) then + self.pressing = nil + self.add:SetAlpha(255) + end + end + self.add.OnCursorExited = self.add.OnMouseReleased + + self.sub = self:Add("DImageButton") + self.sub:SetSize(16, 16) + self.sub:Dock(LEFT) + self.sub:DockMargin(2, 2, 2, 2) + self.sub:SetImage("icon16/delete.png") + self.sub.OnMousePressed = function() + self.pressing = -1 + self:DoChange() + self.sub:SetAlpha(150) + end + self.sub.OnMouseReleased = function() + if (self.pressing) then + self.pressing = nil + self.sub:SetAlpha(255) + end + end + self.sub.OnCursorExited = self.sub.OnMouseReleased + + self.value = 0 + self.deltaValue = self.value + self.max = 10 + self.animateSpeed = 15 + + self.bar = self:Add("DPanel") + self.bar:Dock(FILL) + self.bar.Paint = function(this, w, h) + surface.SetDrawColor(35, 35, 35, 250) + surface.DrawRect(0, 0, w, h) + + w, h = w - 4, h - 4 + + local value = self.deltaValue / self.max + + if (value > 0) then + local color = self.color and self.color or ix.config.Get("color") + local boostedValue = self.boostValue or 0 + local add = 0 + + if (self.deltaValue != self.value) then + add = 35 + end + + -- your stat + do + if !(boostedValue < 0 and math.abs(boostedValue) > self.value) then + surface.SetDrawColor(color.r + add, color.g + add, color.b + add, 230) + surface.DrawRect(2, 2, w * value, h) + + surface.SetDrawColor(255, 255, 255, 35) + surface.SetMaterial(gradient) + surface.DrawTexturedRect(2, 2, w * value, h) + end + end + + -- boosted stat + do + local boostValue + + if (boostedValue != 0) then + if (boostedValue < 0) then + local please = math.min(self.value, math.abs(boostedValue)) + boostValue = ((please or 0) / self.max) * (self.deltaValue / self.value) + else + boostValue = ((boostedValue or 0) / self.max) * (self.deltaValue / self.value) + end + + if (boostedValue < 0) then + surface.SetDrawColor(200, 40, 40, 230) + + local bWidth = math.abs(w * boostValue) + surface.DrawRect(2 + w * value - bWidth, 2, bWidth, h) + + surface.SetDrawColor(255, 255, 255, 35) + surface.SetMaterial(gradient) + surface.DrawTexturedRect(2 + w * value - bWidth, 2, bWidth, h) + else + surface.SetDrawColor(40, 200, 40, 230) + surface.DrawRect(2 + w * value, 2, w * boostValue, h) + + surface.SetDrawColor(255, 255, 255, 35) + surface.SetMaterial(gradient) + surface.DrawTexturedRect(2 + w * value, 2, w * boostValue, h) + end + end + end + end + + surface.SetDrawColor(255, 255, 255, 5) + surface.SetMaterial(gradient2) + surface.DrawTexturedRect(2, 2, w, h) + end + + self.label = self.bar:Add("DLabel") + self.label:Dock(FILL) + self.label:SetExpensiveShadow(1, Color(0, 0, 60)) + self.label:SetContentAlignment(5) +end + +function PANEL:Think() + if (self.pressing) then + if ((self.nextPress or 0) < CurTime()) then + self:DoChange() + end + end + + self.deltaValue = math.Approach(self.deltaValue, self.value, FrameTime() * self.animateSpeed) +end + +function PANEL:DoChange() + if ((self.value == 0 and self.pressing == -1) or (self.value == self.max and self.pressing == 1)) then + return + end + + self.nextPress = CurTime() + 0.2 + + if (self:OnChanged(self.pressing) != false) then + self.value = math.Clamp(self.value + self.pressing, 0, self.max) + end +end + +function PANEL:OnChanged(difference) +end + +function PANEL:SetText(text) + self.label:SetText(text) +end + +function PANEL:SetReadOnly() + self.sub:Remove() + self.add:Remove() +end + +vgui.Register("ixAttributeBar", PANEL, "DPanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_bar.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_bar.lua new file mode 100644 index 0000000..ff50db8 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_bar.lua @@ -0,0 +1,197 @@ + +-- bar manager +-- this manages positions for bar panels +local PANEL = {} + +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) + +function PANEL:Init() + self:SetSize(ScrW() * 0.35, ScrH()) + self:SetPos(4, 4) + self:ParentToHUD() + + self.bars = {} + self.padding = 2 + + -- add bars that were registered before manager creation + for _, v in ipairs(ix.bar.list) do + v.panel = self:AddBar(v.index, v.color, v.priority) + end +end + +function PANEL:GetAll() + return self.bars +end + +function PANEL:Clear() + for k, v in ipairs(self.bars) do + v:Remove() + + table.remove(self.bars, k) + end +end + +function PANEL:AddBar(index, color, priority) + local panel = self:Add("ixInfoBar") + panel:SetSize(self:GetWide(), BAR_HEIGHT) + panel:SetVisible(false) + panel:SetID(index) + panel:SetColor(color) + panel:SetPriority(priority) + + self.bars[#self.bars + 1] = panel + self:Sort() + + return panel +end + +function PANEL:RemoveBar(panel) + local toRemove + + for k, v in ipairs(self.bars) do + if (v == panel) then + toRemove = k + break + end + end + + if (toRemove) then + table.remove(self.bars, toRemove) + + -- Decrease index value for the next bars + for i = toRemove, #self.bars do + ix.bar.list[i].index = i + self.bars[i]:SetID(i) + end + end + + panel:Remove() + self:Sort() +end + +-- sort bars by priority +function PANEL:Sort() + table.sort(self.bars, function(a, b) + return a:GetPriority() < b:GetPriority() + end) +end + +-- update target Y positions +function PANEL:Organize() + local currentY = 0 + + for _, v in ipairs(self.bars) do + if (!v:IsVisible()) then + continue + end + + v:SetPos(0, currentY) + + currentY = currentY + self.padding + v:GetTall() + end + + self:SetSize(self:GetWide(), currentY) +end + +function PANEL:Think() + local menu = (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) and ix.gui.characterMenu + or IsValid(ix.gui.menu) and ix.gui.menu + local fraction = menu and 1 - menu.currentAlpha / 255 or 1 + + self:SetAlpha(255 * fraction) + + -- don't update bars when not visible + if (fraction == 0) then + return + end + + local curTime = CurTime() + local bShouldHide = hook.Run("ShouldHideBars") + local bAlwaysShow = ix.option.Get("alwaysShowBars", false) + + for _, v in ipairs(self.bars) do + local info = ix.bar.list[v:GetID()] + local realValue, barText = info.GetValue() + + if (bShouldHide or realValue == false) then + v:SetVisible(false) + continue + end + + if (v:GetDelta() != realValue) then + v:SetLifetime(curTime + 5) + end + + if (v:GetLifetime() < curTime and !info.visible and !bAlwaysShow and !hook.Run("ShouldBarDraw", info)) then + v:SetVisible(false) + continue + end + + v:SetVisible(true) + v:SetValue(realValue) + v:SetText(isstring(barText) and barText or "") + end + + self:Organize() +end + +function PANEL:OnRemove() + self:Clear() +end + +vgui.Register("ixInfoBarManager", PANEL, "Panel") + +PANEL = {} + +AccessorFunc(PANEL, "index", "ID", FORCE_NUMBER) +AccessorFunc(PANEL, "color", "Color") +AccessorFunc(PANEL, "priority", "Priority", FORCE_NUMBER) +AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER) +AccessorFunc(PANEL, "delta", "Delta", FORCE_NUMBER) +AccessorFunc(PANEL, "lifetime", "Lifetime", FORCE_NUMBER) + +function PANEL:Init() + self.value = 0 + self.delta = 0 + self.lifetime = 0 + + self.bar = self:Add("DPanel") + self.bar:SetPaintedManually(true) + self.bar:Dock(FILL) + self.bar:DockMargin(2, 2, 2, 2) + self.bar.Paint = function(this, width, height) + width = width * math.min(self.delta, 1) + + derma.SkinFunc("PaintInfoBar", self, width, height, self.color) + end + + self.label = self:Add("DLabel") + self.label:SetFont("ixSmallFont") + self.label:SetContentAlignment(5) + self.label:SetText("") + self.label:SetTextColor(Color(240, 240, 240)) + self.label:SetExpensiveShadow(2, Color(20, 20, 20)) + self.label:SetPaintedManually(true) + self.label:SizeToContents() + self.label:Dock(FILL) +end + +function PANEL:SetText(text) + self.label:SetText(text) + self.label:SizeToContents() +end + +function PANEL:Think() + self.delta = math.Approach(self.delta, self.value, FrameTime()) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintInfoBarBackground", self, width, height) +end + +vgui.Register("ixInfoBar", PANEL, "Panel") + +if (IsValid(ix.gui.bars)) then + ix.gui.bars:Remove() + ix.gui.bars = vgui.Create("ixInfoBarManager") +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_business.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_business.lua new file mode 100644 index 0000000..b1f4adf --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_business.lua @@ -0,0 +1,503 @@ + +local PANEL = {} + +function PANEL:Init() + -- being relative. + local size = 120 + self:SetSize(size, size * 1.4) +end + +function PANEL:SetItem(itemTable) + self.itemName = L(itemTable.name):lower() + + self.price = self:Add("DLabel") + self.price:Dock(BOTTOM) + self.price:SetText(itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper()) + self.price:SetContentAlignment(5) + self.price:SetTextColor(color_white) + self.price:SetFont("ixSmallFont") + self.price:SetExpensiveShadow(1, Color(0, 0, 0, 200)) + + self.name = self:Add("DLabel") + self.name:Dock(TOP) + self.name:SetText(itemTable.GetName and itemTable:GetName() or L(itemTable.name)) + self.name:SetContentAlignment(5) + self.name:SetTextColor(color_white) + self.name:SetFont("ixSmallFont") + self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200)) + self.name.Paint = function(this, w, h) + surface.SetDrawColor(0, 0, 0, 75) + surface.DrawRect(0, 0, w, h) + end + + self.icon = self:Add("SpawnIcon") + self.icon:SetZPos(1) + self.icon:SetSize(self:GetWide(), self:GetWide()) + self.icon:Dock(FILL) + self.icon:DockMargin(5, 5, 5, 10) + self.icon:InvalidateLayout(true) + self.icon:SetModel(itemTable:GetModel(), itemTable:GetSkin()) + self.icon:SetHelixTooltip(function(tooltip) + ix.hud.PopulateItemTooltip(tooltip, itemTable) + end) + self.icon.itemTable = itemTable + self.icon.DoClick = function(this) + if (IsValid(ix.gui.checkout)) then + return + end + + local parent = ix.gui.business + local bAdded = parent:BuyItem(itemTable.uniqueID) + + if (bAdded) then + surface.PlaySound("buttons/button14.wav") + end + end + self.icon.PaintOver = function(this) + if (itemTable and itemTable.PaintOver) then + local w, h = this:GetSize() + + itemTable.PaintOver(this, itemTable, w, h) + end + end + + if ((itemTable.iconCam and !ICON_RENDER_QUEUE[itemTable.uniqueID]) or itemTable.forceRender) then + local iconCam = itemTable.iconCam + iconCam = { + cam_pos = iconCam.pos, + cam_fov = iconCam.fov, + cam_ang = iconCam.ang, + } + ICON_RENDER_QUEUE[itemTable.uniqueID] = true + + self.icon:RebuildSpawnIconEx( + iconCam + ) + end +end + +vgui.Register("ixBusinessItem", PANEL, "DPanel") + +PANEL = {} + +function PANEL:Init() + ix.gui.business = self + + self:SetSize(self:GetParent():GetSize()) + + self.categories = self:Add("DScrollPanel") + self.categories:Dock(LEFT) + self.categories:SetWide(260) + self.categories.Paint = function(this, w, h) + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, w, h) + end + self.categories:DockPadding(5, 5, 5, 5) + self.categories:DockMargin(0, 46, 0, 0) + self.categoryPanels = {} + + self.scroll = self:Add("DScrollPanel") + self.scroll:Dock(FILL) + + self.search = self:Add("DTextEntry") + self.search:Dock(TOP) + self.search:SetTall(36) + self.search:SetFont("ixMediumFont") + self.search:DockMargin(10 + self:GetWide() * 0.35, 0, 5, 5) + self.search.OnTextChanged = function(this) + local text = self.search:GetText():lower() + + if (self.selected) then + self:LoadItems(self.selected.category, text:find("%S") and text or nil) + self.scroll:InvalidateLayout() + end + end + self.search.PaintOver = function(this, cw, ch) + if (self.search:GetValue() == "" and !self.search:HasFocus()) then + ix.util.DrawText("V", 10, ch/2 - 1, color_black, 3, 1, "ixIconsSmall") + end + end + + self.itemList = self.scroll:Add("DIconLayout") + self.itemList:Dock(TOP) + self.itemList:DockMargin(10, 1, 5, 5) + self.itemList:SetSpaceX(10) + self.itemList:SetSpaceY(10) + self.itemList:SetMinimumSize(128, 400) + + self.checkout = self:Add("DButton") + self.checkout:Dock(BOTTOM) + self.checkout:SetTextColor(color_white) + self.checkout:SetTall(36) + self.checkout:SetFont("ixMediumFont") + self.checkout:DockMargin(10, 10, 0, 0) + self.checkout:SetExpensiveShadow(1, Color(0, 0, 0, 150)) + self.checkout:SetText(L("checkout", 0)) + self.checkout.DoClick = function() + if (!IsValid(ix.gui.checkout) and self:GetCartCount() > 0) then + vgui.Create("ixBusinessCheckout"):SetCart(self.cart) + end + end + + self.cart = {} + + local dark = Color(0, 0, 0, 50) + local first = true + + for k, v in pairs(ix.item.list) do + if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), k) == false) then + continue + end + + if (!self.categoryPanels[L(v.category)]) then + self.categoryPanels[L(v.category)] = v.category + end + end + + for category, realName in SortedPairs(self.categoryPanels) do + local button = self.categories:Add("DButton") + button:SetTall(36) + button:SetText(category) + button:Dock(TOP) + button:SetTextColor(color_white) + button:DockMargin(5, 5, 5, 0) + button:SetFont("ixMediumFont") + button:SetExpensiveShadow(1, Color(0, 0, 0, 150)) + button.Paint = function(this, w, h) + surface.SetDrawColor(self.selected == this and ix.config.Get("color") or dark) + surface.DrawRect(0, 0, w, h) + + surface.SetDrawColor(0, 0, 0, 50) + surface.DrawOutlinedRect(0, 0, w, h) + end + button.DoClick = function(this) + if (self.selected != this) then + self.selected = this + self:LoadItems(realName) + timer.Simple(0.01, function() + self.scroll:InvalidateLayout() + end) + end + end + button.category = realName + + if (first) then + self.selected = button + first = false + end + + self.categoryPanels[realName] = button + end + + if (self.selected) then + self:LoadItems(self.selected.category) + end +end + +function PANEL:GetCartCount() + local count = 0 + + for _, v in pairs(self.cart) do + count = count + v + end + + return count +end + +function PANEL:BuyItem(uniqueID) + local currentCount = self.cart[uniqueID] or 0 + + if (currentCount >= 10) then + return false + end + + self.cart[uniqueID] = currentCount + 1 + self.checkout:SetText(L("checkout", self:GetCartCount())) + + return true +end + +function PANEL:LoadItems(category, search) + category = category or "misc" + local items = ix.item.list + + self.itemList:Clear() + self.itemList:InvalidateLayout(true) + + for uniqueID, itemTable in SortedPairsByMemberValue(items, "name") do + if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), uniqueID) == false) then + continue + end + + if (itemTable.category == category) then + if (search and search != "" and !L(itemTable.name):lower():find(search, 1, true)) then + continue + end + + self.itemList:Add("ixBusinessItem"):SetItem(itemTable) + end + end +end + +function PANEL:SetPage() +end + +function PANEL:GetPageItems() +end + +vgui.Register("ixBusiness", PANEL, "EditablePanel") + +DEFINE_BASECLASS("DFrame") +PANEL = {} + +function PANEL:Init() + if (IsValid(ix.gui.checkout)) then + ix.gui.checkout:Remove() + end + + ix.gui.checkout = self + + self:SetTitle(L("checkout", 0)) + self:SetSize(ScrW() / 4 > 200 and ScrW() / 4 or ScrW() / 2, ScrH() / 2 > 300 and ScrH() / 2 or ScrH()) + self:MakePopup() + self:Center() + self:SetBackgroundBlur(true) + self:SetSizable(true) + + self.items = self:Add("DScrollPanel") + self.items:Dock(FILL) + self.items.Paint = function(this, w, h) + surface.SetDrawColor(0, 0, 0, 100) + surface.DrawRect(0, 0, w, h) + end + self.items:DockMargin(0, 0, 0, 4) + + self.buy = self:Add("DButton") + self.buy:Dock(BOTTOM) + self.buy:SetText(L"purchase") + self.buy:SetTextColor(color_white) + self.buy.DoClick = function(this) + if ((this.nextClick or 0) < CurTime()) then + this.nextClick = CurTime() + 0.5 + else + return + end + + if (self.preventBuy) then + self.finalGlow:SetText(self.final:GetText()) + self.finalGlow:SetAlpha(255) + self.finalGlow:AlphaTo(0, 0.5) + + return surface.PlaySound("buttons/button11.wav") + end + + net.Start("ixBusinessBuy") + net.WriteUInt(table.Count(self.itemData), 8) + + for k, v in pairs(self.itemData) do + net.WriteString(k) + net.WriteUInt(v, 8) + end + + net.SendToServer() + + self.itemData = {} + self.items:Remove() + self.data:Remove() + self.buy:Remove() + self:ShowCloseButton(false) + + if (IsValid(ix.gui.business)) then + ix.gui.business.cart = {} + ix.gui.business.checkout:SetText(L("checkout", 0)) + end + + self.text = self:Add("DLabel") + self.text:Dock(FILL) + self.text:SetContentAlignment(5) + self.text:SetTextColor(color_white) + self.text:SetText(L"purchasing") + self.text:SetFont("ixMediumFont") + + net.Receive("ixBusinessResponse", function() + if (IsValid(self)) then + self.text:SetText(L"buyGood") + self.done = true + self:ShowCloseButton(true) + + surface.PlaySound("buttons/button3.wav") + end + end) + + timer.Simple(4, function() + if (IsValid(self) and !self.done) then + self.text:SetText(L"buyFailed") + self:ShowCloseButton(true) + + surface.PlaySound("buttons/button11.wav") + end + end) + end + + self.data = self:Add("DPanel") + self.data:Dock(BOTTOM) + self.data:SetTall(64) + self.data:DockMargin(0, 0, 0, 4) + + self.current = self.data:Add("DLabel") + self.current:SetFont("ixSmallFont") + self.current:SetContentAlignment(6) + self.current:SetTextColor(color_white) + self.current:Dock(TOP) + self.current:SetTextInset(4, 0) + + self.total = self.data:Add("DLabel") + self.total:SetFont("ixSmallFont") + self.total:SetContentAlignment(6) + self.total:SetTextColor(color_white) + self.total:Dock(TOP) + self.total:SetTextInset(4, 0) + + local line = self.data:Add("DPanel") + line:SetTall(1) + line:DockMargin(128, 0, 4, 0) + line:Dock(TOP) + line.Paint = function(this, w, h) + surface.SetDrawColor(255, 255, 255, 150) + surface.DrawLine(0, 0, w, 0) + end + + self.final = self.data:Add("DLabel") + self.final:SetFont("ixSmallFont") + self.final:SetContentAlignment(6) + self.final:SetTextColor(color_white) + self.final:Dock(TOP) + self.final:SetTextInset(4, 0) + + self.finalGlow = self.final:Add("DLabel") + self.finalGlow:Dock(FILL) + self.finalGlow:SetFont("ixSmallFont") + self.finalGlow:SetTextColor(color_white) + self.finalGlow:SetContentAlignment(6) + self.finalGlow:SetAlpha(0) + self.finalGlow:SetTextInset(4, 0) + + self:SetFocusTopLevel(true) + self.itemData = {} + self:OnQuantityChanged() +end + +function PANEL:OnQuantityChanged() + local price = 0 + local money = LocalPlayer():GetCharacter():GetMoney() + local valid = 0 + + for k, v in pairs(self.itemData) do + local itemTable = ix.item.list[k] + + if (itemTable and v > 0) then + valid = valid + 1 + price = price + (v * (itemTable.price or 0)) + end + end + + self.current:SetText(L"currentMoney" .. ix.currency.Get(money)) + self.total:SetText("- " .. ix.currency.Get(price)) + self.final:SetText(L"moneyLeft" .. ix.currency.Get(money - price)) + self.final:SetTextColor((money - price) >= 0 and Color(46, 204, 113) or Color(217, 30, 24)) + + self.preventBuy = (money - price) < 0 or valid == 0 + + if (IsValid(ix.gui.business)) then + ix.gui.business.checkout:SetText(L("checkout", ix.gui.business:GetCartCount())) + end +end + +function PANEL:SetCart(items) + self.itemData = items + + for k, v in SortedPairs(items) do + local itemTable = ix.item.list[k] + + if (itemTable) then + local slot = self.items:Add("DPanel") + slot:SetTall(36) + slot:Dock(TOP) + slot:DockMargin(5, 5, 5, 0) + + slot.icon = slot:Add("SpawnIcon") + slot.icon:SetPos(2, 2) + slot.icon:SetSize(32, 32) + slot.icon:SetModel(itemTable:GetModel()) + slot.icon:SetTooltip() + + slot.name = slot:Add("DLabel") + slot.name:SetPos(40, 2) + slot.name:SetFont("ixChatFont") + slot.name:SetText(string.format( + "%s (%s)", + L(itemTable.GetName and itemTable:GetName() or L(itemTable.name)), + itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper() + )) + slot.name:SetTextColor(color_white) + slot.name:SizeToContents() + slot.name:DockMargin(40, 0, 0, 0) + slot.name:Dock(FILL) + + slot.quantity = slot:Add("DTextEntry") + slot.quantity:SetSize(32, 32) + slot.quantity:Dock(RIGHT) + slot.quantity:DockMargin(4, 4, 4, 4) + slot.quantity:SetContentAlignment(5) + slot.quantity:SetNumeric(true) + slot.quantity:SetText(v) + slot.quantity:SetFont("ixChatFont") + slot.quantity.OnTextChanged = function(this) + local value = tonumber(this:GetValue()) + + if (!value) then + this:SetValue(1) + return + end + + value = math.Clamp(math.Round(value), 0, 10) + + if (value == 0) then + items[k] = nil + + slot:Remove() + else + items[k] = value + end + + self:OnQuantityChanged() + end + slot.quantity.OnLoseFocus = function(this) + local value = math.Clamp(tonumber(this:GetValue()) or 1, 0, 10) + this:SetText(value) + end + else + items[k] = nil + end + end + + self:OnQuantityChanged() +end + +function PANEL:Think() + if (!self:HasFocus()) then + self:MakePopup() + end + + BaseClass.Think(self) +end + +vgui.Register("ixBusinessCheckout", PANEL, "DFrame") + +hook.Add("CreateMenuButtons", "ixBusiness", function(tabs) + if (hook.Run("BuildBusinessMenu") != false) then + tabs["business"] = function(container) + container:Add("ixBusiness") + end + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_character.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_character.lua new file mode 100644 index 0000000..28cd689 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_character.lua @@ -0,0 +1,548 @@ + +local gradient = surface.GetTextureID("vgui/gradient-d") +local audioFadeInTime = 2 +local animationTime = 0.5 +local matrixZScale = Vector(1, 1, 0.0001) + +-- character menu panel +DEFINE_BASECLASS("ixSubpanelParent") +local PANEL = {} + +function PANEL:Init() + self:SetSize(self:GetParent():GetSize()) + self:SetPos(0, 0) + + self.childPanels = {} + self.subpanels = {} + self.activeSubpanel = "" + + self.currentDimAmount = 0 + self.currentY = 0 + self.currentScale = 1 + self.currentAlpha = 255 + self.targetDimAmount = 255 + self.targetScale = 0.9 +end + +function PANEL:Dim(length, callback) + length = length or animationTime + self.currentDimAmount = 0 + + self:CreateAnimation(length, { + target = { + currentDimAmount = self.targetDimAmount, + currentScale = self.targetScale + }, + easing = "outCubic", + OnComplete = callback + }) + + self:OnDim() +end + +function PANEL:Undim(length, callback) + length = length or animationTime + self.currentDimAmount = self.targetDimAmount + + self:CreateAnimation(length, { + target = { + currentDimAmount = 0, + currentScale = 1 + }, + easing = "outCubic", + OnComplete = callback + }) + + self:OnUndim() +end + +function PANEL:OnDim() +end + +function PANEL:OnUndim() +end + +function PANEL:Paint(width, height) + local amount = self.currentDimAmount + local bShouldScale = self.currentScale != 1 + local matrix + + -- draw child panels with scaling if needed + if (bShouldScale) then + matrix = Matrix() + matrix:Scale(matrixZScale * self.currentScale) + matrix:Translate(Vector( + ScrW() * 0.5 - (ScrW() * self.currentScale * 0.5), + ScrH() * 0.5 - (ScrH() * self.currentScale * 0.5), + 1 + )) + + cam.PushModelMatrix(matrix) + self.currentMatrix = matrix + end + + BaseClass.Paint(self, width, height) + + if (bShouldScale) then + cam.PopModelMatrix() + self.currentMatrix = nil + end + + if (amount > 0) then + local color = Color(0, 0, 0, amount) + + surface.SetDrawColor(color) + surface.DrawRect(0, 0, width, height) + end +end + +vgui.Register("ixCharMenuPanel", PANEL, "ixSubpanelParent") + +-- character menu main button list +PANEL = {} + +function PANEL:Init() + local parent = self:GetParent() + self:SetSize(parent:GetWide() * 0.25, parent:GetTall()) + + self:GetVBar():SetWide(0) + self:GetVBar():SetVisible(false) +end + +function PANEL:Add(name) + local panel = vgui.Create(name, self) + panel:Dock(TOP) + + return panel +end + +function PANEL:SizeToContents() + self:GetCanvas():InvalidateLayout(true) + + -- if the canvas has extra space, forcefully dock to the bottom so it doesn't anchor to the top + if (self:GetTall() > self:GetCanvas():GetTall()) then + self:GetCanvas():Dock(BOTTOM) + else + self:GetCanvas():Dock(NODOCK) + end +end + +vgui.Register("ixCharMenuButtonList", PANEL, "DScrollPanel") + +-- main character menu panel +PANEL = {} + +AccessorFunc(PANEL, "bUsingCharacter", "UsingCharacter", FORCE_BOOL) + +function PANEL:Init() + local parent = self:GetParent() + local padding = self:GetPadding() + local halfWidth = ScrW() * 0.5 + local halfPadding = padding * 0.5 + local bHasCharacter = #ix.characters > 0 + + self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() + self:DockPadding(padding, padding, padding, padding) + + local infoLabel = self:Add("DLabel") + infoLabel:SetTextColor(Color(255, 255, 255, 25)) + infoLabel:SetFont("ixMenuMiniFont") + infoLabel:SetText(L("helix") .. " " .. GAMEMODE.Version) + infoLabel:SizeToContents() + infoLabel:SetPos(ScrW() - infoLabel:GetWide() - 4, ScrH() - infoLabel:GetTall() - 4) + + local logoPanel = self:Add("Panel") + logoPanel:SetSize(ScrW(), ScrH() * 0.25) + logoPanel:SetPos(0, ScrH() * 0.25) + logoPanel.Paint = function(panel, width, height) + local matrix = self.currentMatrix + + -- don't scale the background because it fucks the blur + if (matrix) then + cam.PopModelMatrix() + end + + local newHeight = Lerp(1 - (self.currentDimAmount / 255), 0, height) + local y = height * 0.5 - newHeight * 0.5 + local _, screenY = panel:LocalToScreen(0, 0) + screenY = screenY + y + + render.SetScissorRect(0, screenY, width, screenY + newHeight, true) + ix.util.DrawBlur(panel, 15, nil, 200) + + -- background dim + surface.SetDrawColor(0, 0, 0, 100) + surface.DrawRect(0, y, width, newHeight) + + -- border lines + surface.SetDrawColor(ix.config.Get("color") or color_white) + surface.DrawRect(0, y, width, 1) + surface.DrawRect(0, y + newHeight - 1, width, 1) + + if (matrix) then + cam.PushModelMatrix(matrix) + end + + for _, v in ipairs(panel:GetChildren()) do + v:PaintManual() + end + + render.SetScissorRect(0, 0, 0, 0, false) + end + + -- draw schema logo material instead of text if available + local logo = Schema.logo and ix.util.GetMaterial(Schema.logo) + + if (logo and !logo:IsError()) then + local logoImage = logoPanel:Add("DImage") + logoImage:SetMaterial(logo) + logoImage:SetSize(halfWidth, halfWidth * logo:Height() / logo:Width()) + logoImage:SetPos(halfWidth - logoImage:GetWide() * 0.5, halfPadding) + logoImage:SetPaintedManually(true) + + logoPanel:SetTall(logoImage:GetTall() + padding) + else + local newHeight = padding + local subtitle = L2("schemaDesc") or Schema.description + + local titleLabel = logoPanel:Add("DLabel") + titleLabel:SetTextColor(color_white) + titleLabel:SetFont("ixTitleFont") + titleLabel:SetText(L2("schemaName") or Schema.name or L"unknown") + titleLabel:SizeToContents() + titleLabel:SetPos(halfWidth - titleLabel:GetWide() * 0.5, halfPadding) + titleLabel:SetPaintedManually(true) + newHeight = newHeight + titleLabel:GetTall() + + if (subtitle) then + local subtitleLabel = logoPanel:Add("DLabel") + subtitleLabel:SetTextColor(color_white) + subtitleLabel:SetFont("ixSubTitleFont") + subtitleLabel:SetText(subtitle) + subtitleLabel:SizeToContents() + subtitleLabel:SetPos(halfWidth - subtitleLabel:GetWide() * 0.5, 0) + subtitleLabel:MoveBelow(titleLabel) + subtitleLabel:SetPaintedManually(true) + newHeight = newHeight + subtitleLabel:GetTall() + end + + logoPanel:SetTall(newHeight) + end + + -- button list + self.mainButtonList = self:Add("ixCharMenuButtonList") + self.mainButtonList:Dock(LEFT) + + -- create character button + local createButton = self.mainButtonList:Add("ixMenuButton") + createButton:SetText("create") + createButton:SizeToContents() + createButton.DoClick = function() + local maximum = hook.Run("GetMaxPlayerCharacter", LocalPlayer()) or ix.config.Get("maxCharacters", 5) + -- don't allow creation if we've hit the character limit + if (#ix.characters >= maximum) then + self:GetParent():ShowNotice(3, L("maxCharacters")) + return + end + + self:Dim() + parent.newCharacterPanel:SetActiveSubpanel("faction", 0) + parent.newCharacterPanel:SlideUp() + end + + -- load character button + self.loadButton = self.mainButtonList:Add("ixMenuButton") + self.loadButton:SetText("load") + self.loadButton:SizeToContents() + self.loadButton.DoClick = function() + self:Dim() + parent.loadCharacterPanel:SlideUp() + end + + if (!bHasCharacter) then + self.loadButton:SetDisabled(true) + end + + -- community button + local extraURL = ix.config.Get("communityURL", "") + local extraText = ix.config.Get("communityText", "@community") + + if (extraURL != "" and extraText != "") then + if (extraText:sub(1, 1) == "@") then + extraText = L(extraText:sub(2)) + end + + local extraButton = self.mainButtonList:Add("ixMenuButton") + extraButton:SetText(extraText, true) + extraButton:SizeToContents() + extraButton.DoClick = function() + gui.OpenURL(extraURL) + end + end + + -- leave/return button + self.returnButton = self.mainButtonList:Add("ixMenuButton") + self:UpdateReturnButton() + self.returnButton.DoClick = function() + if (self.bUsingCharacter) then + parent:Close() + else + RunConsoleCommand("disconnect") + end + end + + self.mainButtonList:SizeToContents() +end + +function PANEL:UpdateReturnButton(bValue) + if (bValue != nil) then + self.bUsingCharacter = bValue + end + + self.returnButton:SetText(self.bUsingCharacter and "return" or "leave") + self.returnButton:SizeToContents() +end + +function PANEL:OnDim() + -- disable input on this panel since it will still be in the background while invisible - prone to stray clicks if the + -- panels overtop slide out of the way + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) +end + +function PANEL:OnUndim() + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + + -- we may have just deleted a character so update the status of the return button + self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() + self:UpdateReturnButton() +end + +function PANEL:OnClose() + for _, v in pairs(self:GetChildren()) do + if (IsValid(v)) then + v:SetVisible(false) + end + end +end + +function PANEL:PerformLayout(width, height) + local padding = self:GetPadding() + + self.mainButtonList:SetPos(padding, height - self.mainButtonList:GetTall() - padding) +end + +vgui.Register("ixCharMenuMain", PANEL, "ixCharMenuPanel") + +-- container panel +PANEL = {} + +function PANEL:Init() + if (IsValid(ix.gui.loading)) then + ix.gui.loading:Remove() + end + + if (IsValid(ix.gui.characterMenu)) then + if (IsValid(ix.gui.characterMenu.channel)) then + ix.gui.characterMenu.channel:Stop() + end + + ix.gui.characterMenu:Remove() + end + + self:SetSize(ScrW(), ScrH()) + self:SetPos(0, 0) + + -- main menu panel + self.mainPanel = self:Add("ixCharMenuMain") + + -- new character panel + self.newCharacterPanel = self:Add("ixCharMenuNew") + self.newCharacterPanel:SlideDown(0) + + -- load character panel + self.loadCharacterPanel = self:Add("ixCharMenuLoad") + self.loadCharacterPanel:SlideDown(0) + + -- notice bar + self.notice = self:Add("ixNoticeBar") + + -- finalization + self:MakePopup() + self.currentAlpha = 255 + self.volume = 0 + + ix.gui.characterMenu = self + + if (!IsValid(ix.gui.intro)) then + self:PlayMusic() + end + + hook.Run("OnCharacterMenuCreated", self) +end + +function PANEL:PlayMusic() + local path = "sound/" .. ix.config.Get("music") + local url = path:match("http[s]?://.+") + local play = url and sound.PlayURL or sound.PlayFile + path = url and url or path + + play(path, "noplay", function(channel, error, message) + if (!IsValid(self) or !IsValid(channel)) then + return + end + + channel:SetVolume(self.volume or 0) + channel:Play() + + self.channel = channel + + self:CreateAnimation(audioFadeInTime, { + index = 10, + target = {volume = 1}, + + Think = function(animation, panel) + if (IsValid(panel.channel)) then + panel.channel:SetVolume(self.volume * 0.5) + end + end + }) + end) +end + +function PANEL:ShowNotice(type, text) + self.notice:SetType(type) + self.notice:SetText(text) + self.notice:Show() +end + +function PANEL:HideNotice() + if (IsValid(self.notice) and !self.notice:GetHidden()) then + self.notice:Slide("up", 0.5, true) + end +end + +function PANEL:OnCharacterDeleted(character) + if (#ix.characters == 0) then + self.mainPanel.loadButton:SetDisabled(true) + self.mainPanel:Undim() -- undim since the load panel will slide down + else + self.mainPanel.loadButton:SetDisabled(false) + end + + self.loadCharacterPanel:OnCharacterDeleted(character) +end + +function PANEL:OnCharacterLoadFailed(error) + self.loadCharacterPanel:SetMouseInputEnabled(true) + self.loadCharacterPanel:SlideUp() + self:ShowNotice(3, error) +end + +function PANEL:IsClosing() + return self.bClosing +end + +function PANEL:Close(bFromMenu) + self.bClosing = true + self.bFromMenu = bFromMenu + + local fadeOutTime = animationTime * 8 + + self:CreateAnimation(fadeOutTime, { + index = 1, + target = {currentAlpha = 0}, + + Think = function(animation, panel) + panel:SetAlpha(panel.currentAlpha) + end, + + OnComplete = function(animation, panel) + panel:Remove() + end + }) + + self:CreateAnimation(fadeOutTime - 0.1, { + index = 10, + target = {volume = 0}, + + Think = function(animation, panel) + if (IsValid(panel.channel)) then + panel.channel:SetVolume(self.volume * 0.5) + end + end, + + OnComplete = function(animation, panel) + if (IsValid(panel.channel)) then + panel.channel:Stop() + panel.channel = nil + end + end + }) + + -- hide children if we're already dimmed + if (bFromMenu) then + for _, v in pairs(self:GetChildren()) do + if (IsValid(v)) then + v:SetVisible(false) + end + end + else + -- fade out the main panel quicker because it significantly blocks the screen + self.mainPanel.currentAlpha = 255 + + self.mainPanel:CreateAnimation(animationTime * 2, { + target = {currentAlpha = 0}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(panel.currentAlpha) + end, + + OnComplete = function(animation, panel) + panel:SetVisible(false) + end + }) + end + + -- relinquish mouse control + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + gui.EnableScreenClicker(false) +end + +function PANEL:Paint(width, height) + surface.SetTexture(gradient) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawTexturedRect(0, 0, width, height) + + if (!ix.option.Get("cheapBlur", false)) then + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawTexturedRect(0, 0, width, height) + ix.util.DrawBlur(self, Lerp((self.currentAlpha - 200) / 255, 0, 10)) + end +end + +function PANEL:PaintOver(width, height) + if (self.bClosing and self.bFromMenu) then + surface.SetDrawColor(color_black) + surface.DrawRect(0, 0, width, height) + end +end + +function PANEL:OnRemove() + if (IsValid(self.channel)) then + self.channel:Stop() + self.channel = nil + end +end + +vgui.Register("ixCharMenu", PANEL, "EditablePanel") + +if (IsValid(ix.gui.characterMenu)) then + ix.gui.characterMenu:Remove() + + --TODO: REMOVE ME + ix.gui.characterMenu = vgui.Create("ixCharMenu") +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_charcreate.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_charcreate.lua new file mode 100644 index 0000000..1dc6a50 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_charcreate.lua @@ -0,0 +1,499 @@ + +local padding = ScreenScale(32) + +-- create character panel +DEFINE_BASECLASS("ixCharMenuPanel") +local PANEL = {} + +function PANEL:Init() + local parent = self:GetParent() + local halfWidth = parent:GetWide() * 0.5 - (padding * 2) + local halfHeight = parent:GetTall() * 0.5 - (padding * 2) + local modelFOV = (ScrW() > ScrH() * 1.8) and 100 or 78 + + self:ResetPayload(true) + + self.factionButtons = {} + self.repopulatePanels = {} + + -- faction selection subpanel + self.factionPanel = self:AddSubpanel("faction", true) + self.factionPanel:SetTitle("chooseFaction") + self.factionPanel.OnSetActive = function() + -- if we only have one faction, we are always selecting that one so we can skip to the description section + if (#self.factionButtons == 1) then + self:SetActiveSubpanel("description", 0) + end + end + + local modelList = self.factionPanel:Add("Panel") + modelList:Dock(RIGHT) + modelList:SetSize(halfWidth + padding * 2, halfHeight) + + local proceed = modelList:Add("ixMenuButton") + proceed:SetText("proceed") + proceed:SetContentAlignment(6) + proceed:Dock(BOTTOM) + proceed:SizeToContents() + proceed.DoClick = function() + self.progress:IncrementProgress() + + self:Populate() + self:SetActiveSubpanel("description") + end + + self.factionModel = modelList:Add("ixModelPanel") + self.factionModel:Dock(FILL) + self.factionModel:SetModel("models/error.mdl") + self.factionModel:SetFOV(modelFOV) + self.factionModel.PaintModel = self.factionModel.Paint + + self.factionButtonsPanel = self.factionPanel:Add("ixCharMenuButtonList") + self.factionButtonsPanel:SetWide(halfWidth) + self.factionButtonsPanel:Dock(FILL) + + local factionBack = self.factionPanel:Add("ixMenuButton") + factionBack:SetText("return") + factionBack:SizeToContents() + factionBack:Dock(BOTTOM) + factionBack.DoClick = function() + self.progress:DecrementProgress() + + self:SetActiveSubpanel("faction", 0) + self:SlideDown() + + parent.mainPanel:Undim() + end + + -- character customization subpanel + self.description = self:AddSubpanel("description") + self.description:SetTitle("chooseDescription") + + local descriptionModelList = self.description:Add("Panel") + descriptionModelList:Dock(LEFT) + descriptionModelList:SetSize(halfWidth, halfHeight) + + local descriptionBack = descriptionModelList:Add("ixMenuButton") + descriptionBack:SetText("return") + descriptionBack:SetContentAlignment(4) + descriptionBack:SizeToContents() + descriptionBack:Dock(BOTTOM) + descriptionBack.DoClick = function() + self.progress:DecrementProgress() + + if (#self.factionButtons == 1) then + factionBack:DoClick() + else + self:SetActiveSubpanel("faction") + end + end + + self.descriptionModel = descriptionModelList:Add("ixModelPanel") + self.descriptionModel:Dock(FILL) + self.descriptionModel:SetModel(self.factionModel:GetModel()) + self.descriptionModel:SetFOV(modelFOV - 13) + self.descriptionModel.PaintModel = self.descriptionModel.Paint + + self.descriptionPanel = self.description:Add("Panel") + self.descriptionPanel:SetWide(halfWidth + padding * 2) + self.descriptionPanel:Dock(RIGHT) + + local descriptionProceed = self.descriptionPanel:Add("ixMenuButton") + descriptionProceed:SetText("proceed") + descriptionProceed:SetContentAlignment(6) + descriptionProceed:SizeToContents() + descriptionProceed:Dock(BOTTOM) + descriptionProceed.DoClick = function() + if (self:VerifyProgression("description")) then + -- there are no panels on the attributes section other than the create button, so we can just create the character + if (#self.attributesPanel:GetChildren() < 2) then + self:SendPayload() + return + end + + self.progress:IncrementProgress() + self:SetActiveSubpanel("attributes") + end + end + + -- attributes subpanel + self.attributes = self:AddSubpanel("attributes") + self.attributes:SetTitle("chooseSkills") + + local attributesModelList = self.attributes:Add("Panel") + attributesModelList:Dock(LEFT) + attributesModelList:SetSize(halfWidth, halfHeight) + + local attributesBack = attributesModelList:Add("ixMenuButton") + attributesBack:SetText("return") + attributesBack:SetContentAlignment(4) + attributesBack:SizeToContents() + attributesBack:Dock(BOTTOM) + attributesBack.DoClick = function() + self.progress:DecrementProgress() + self:SetActiveSubpanel("description") + end + + self.attributesModel = attributesModelList:Add("ixModelPanel") + self.attributesModel:Dock(FILL) + self.attributesModel:SetModel(self.factionModel:GetModel()) + self.attributesModel:SetFOV(modelFOV - 13) + self.attributesModel.PaintModel = self.attributesModel.Paint + + self.attributesPanel = self.attributes:Add("Panel") + self.attributesPanel:SetWide(halfWidth + padding * 2) + self.attributesPanel:Dock(RIGHT) + + local create = self.attributesPanel:Add("ixMenuButton") + create:SetText("finish") + create:SetContentAlignment(6) + create:SizeToContents() + create:Dock(BOTTOM) + create.DoClick = function() + self:SendPayload() + end + + -- creation progress panel + self.progress = self:Add("ixSegmentedProgress") + self.progress:SetBarColor(ix.config.Get("color")) + self.progress:SetSize(parent:GetWide(), 0) + self.progress:SizeToContents() + self.progress:SetPos(0, parent:GetTall() - self.progress:GetTall()) + + -- setup payload hooks + self:AddPayloadHook("model", function(value) + local faction = ix.faction.indices[self.payload.faction] + + if (faction) then + local model = faction:GetModels(LocalPlayer())[value] + + -- assuming bodygroups + if (istable(model)) then + self.factionModel:SetModel(model[1], model[2] or 0, model[3]) + self.descriptionModel:SetModel(model[1], model[2] or 0, model[3]) + self.attributesModel:SetModel(model[1], model[2] or 0, model[3]) + else + self.factionModel:SetModel(model) + self.descriptionModel:SetModel(model) + self.attributesModel:SetModel(model) + end + end + end) + + -- setup character creation hooks + net.Receive("ixCharacterAuthed", function() + timer.Remove("ixCharacterCreateTimeout") + self.awaitingResponse = false + + local id = net.ReadUInt(32) + local indices = net.ReadUInt(6) + local charList = {} + + for _ = 1, indices do + charList[#charList + 1] = net.ReadUInt(32) + end + + ix.characters = charList + + self:SlideDown() + + if (!IsValid(self) or !IsValid(parent)) then + return + end + + if (LocalPlayer():GetCharacter()) then + parent.mainPanel:Undim() + parent:ShowNotice(2, L("charCreated")) + elseif (id) then + self.bMenuShouldClose = true + + net.Start("ixCharacterChoose") + net.WriteUInt(id, 32) + net.SendToServer() + else + self:SlideDown() + end + end) + + net.Receive("ixCharacterAuthFailed", function() + timer.Remove("ixCharacterCreateTimeout") + self.awaitingResponse = false + + local fault = net.ReadString() + local args = net.ReadTable() + + self:SlideDown() + + parent.mainPanel:Undim() + parent:ShowNotice(3, L(fault, unpack(args))) + end) +end + +function PANEL:SendPayload() + if (self.awaitingResponse or !self:VerifyProgression()) then + return + end + + self.awaitingResponse = true + + timer.Create("ixCharacterCreateTimeout", 10, 1, function() + if (IsValid(self) and self.awaitingResponse) then + local parent = self:GetParent() + + self.awaitingResponse = false + self:SlideDown() + + parent.mainPanel:Undim() + parent:ShowNotice(3, L("unknownError")) + end + end) + + self.payload:Prepare() + + net.Start("ixCharacterCreate") + net.WriteUInt(table.Count(self.payload), 8) + + for k, v in pairs(self.payload) do + net.WriteString(k) + net.WriteType(v) + end + + net.SendToServer() +end + +function PANEL:OnSlideUp() + self:ResetPayload() + self:Populate() + self.progress:SetProgress(1) + + -- the faction subpanel will skip to next subpanel if there is only one faction to choose from, + -- so we don't have to worry about it here + self:SetActiveSubpanel("faction", 0) +end + +function PANEL:OnSlideDown() +end + +function PANEL:ResetPayload(bWithHooks) + if (bWithHooks) then + self.hooks = {} + end + + self.payload = {} + + -- TODO: eh.. + function self.payload.Set(payload, key, value) + self:SetPayload(key, value) + end + + function self.payload.AddHook(payload, key, callback) + self:AddPayloadHook(key, callback) + end + + function self.payload.Prepare(payload) + self.payload.Set = nil + self.payload.AddHook = nil + self.payload.Prepare = nil + end +end + +function PANEL:SetPayload(key, value) + self.payload[key] = value + self:RunPayloadHook(key, value) +end + +function PANEL:AddPayloadHook(key, callback) + if (!self.hooks[key]) then + self.hooks[key] = {} + end + + self.hooks[key][#self.hooks[key] + 1] = callback +end + +function PANEL:RunPayloadHook(key, value) + local hooks = self.hooks[key] or {} + + for _, v in ipairs(hooks) do + v(value) + end +end + +function PANEL:GetContainerPanel(name) + -- TODO: yuck + if (name == "description") then + return self.descriptionPanel + elseif (name == "attributes") then + return self.attributesPanel + end + + return self.descriptionPanel +end + +function PANEL:AttachCleanup(panel) + self.repopulatePanels[#self.repopulatePanels + 1] = panel +end + +function PANEL:Populate() + if (!self.bInitialPopulate) then + -- setup buttons for the faction panel + -- TODO: make this a bit less janky + local lastSelected + + for _, v in pairs(self.factionButtons) do + if (v:GetSelected()) then + lastSelected = v.faction + end + + if (IsValid(v)) then + v:Remove() + end + end + + self.factionButtons = {} + + for _, v in SortedPairs(ix.faction.teams) do + if (ix.faction.HasWhitelist(v.index)) then + local button = self.factionButtonsPanel:Add("ixMenuSelectionButton") + button:SetBackgroundColor(v.color or color_white) + button:SetText(L(v.name):utf8upper()) + button:SizeToContents() + button:SetButtonList(self.factionButtons) + button.faction = v.index + button.OnSelected = function(panel) + local faction = ix.faction.indices[panel.faction] + local models = faction:GetModels(LocalPlayer()) + + self.payload:Set("faction", panel.faction) + self.payload:Set("model", math.random(1, #models)) + end + + if ((lastSelected and lastSelected == v.index) or (!lastSelected and v.isDefault)) then + button:SetSelected(true) + lastSelected = v.index + end + end + end + end + + -- remove panels created for character vars + for i = 1, #self.repopulatePanels do + self.repopulatePanels[i]:Remove() + end + + self.repopulatePanels = {} + + -- payload is empty because we attempted to send it - for whatever reason we're back here again so we need to repopulate + if (!self.payload.faction) then + for _, v in pairs(self.factionButtons) do + if (v:GetSelected()) then + v:SetSelected(true) + break + end + end + end + + self.factionButtonsPanel:SizeToContents() + + local zPos = 1 + + -- set up character vars + for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do + if (!v.bNoDisplay and k != "__SortedIndex") then + local container = self:GetContainerPanel(v.category or "description") + + if (v.ShouldDisplay and v:ShouldDisplay(container, self.payload) == false) then + continue + end + + local panel + + -- if the var has a custom way of displaying, we'll use that instead + if (v.OnDisplay) then + panel = v:OnDisplay(container, self.payload) + elseif (isstring(v.default)) then + panel = container:Add("ixTextEntry") + panel:Dock(TOP) + panel:SetFont("ixMenuButtonHugeFont") + panel:SetUpdateOnType(true) + panel.OnValueChange = function(this, text) + self.payload:Set(k, text) + end + end + + if (IsValid(panel)) then + -- add label for entry + local label = container:Add("DLabel") + label:SetFont("ixMenuButtonLabelFont") + label:SetText(L(k):utf8upper()) + label:SizeToContents() + label:DockMargin(0, 16, 0, 2) + label:Dock(TOP) + + -- we need to set the docking order so the label is above the panel + label:SetZPos(zPos - 1) + panel:SetZPos(zPos) + + self:AttachCleanup(label) + self:AttachCleanup(panel) + + if (v.OnPostSetup) then + v:OnPostSetup(panel, self.payload) + end + + zPos = zPos + 2 + end + end + end + + if (!self.bInitialPopulate) then + -- setup progress bar segments + if (#self.factionButtons > 1) then + self.progress:AddSegment("@faction") + end + + self.progress:AddSegment("@description") + + if (#self.attributesPanel:GetChildren() > 1) then + self.progress:AddSegment("@skills") + end + + -- we don't need to show the progress bar if there's only one segment + if (#self.progress:GetSegments() == 1) then + self.progress:SetVisible(false) + end + end + + self.bInitialPopulate = true +end + +function PANEL:VerifyProgression(name) + for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do + if (name ~= nil and (v.category or "description") != name) then + continue + end + + local value = self.payload[k] + + if (!v.bNoDisplay or v.OnValidate) then + if (v.OnValidate) then + local result = {v:OnValidate(value, self.payload, LocalPlayer())} + + if (result[1] == false) then + self:GetParent():ShowNotice(3, L(unpack(result, 2))) + return false + end + end + + self.payload[k] = value + end + end + + return true +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintCharacterCreateBackground", self, width, height) + BaseClass.Paint(self, width, height) +end + +vgui.Register("ixCharMenuNew", PANEL, "ixCharMenuPanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_charload.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_charload.lua new file mode 100644 index 0000000..aa9b39c --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_charload.lua @@ -0,0 +1,475 @@ + +local errorModel = "models/error.mdl" +local PANEL = {} + +AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) + +local function SetCharacter(self, character) + self.character = character + + if (character) then + self:SetModel(character:GetModel()) + self:SetSkin(character:GetData("skin", 0)) + + for i = 0, (self:GetNumBodyGroups() - 1) do + self:SetBodygroup(i, 0) + end + + local bodygroups = character:GetData("groups", nil) + + if (istable(bodygroups)) then + for k, v in pairs(bodygroups) do + self:SetBodygroup(k, v) + end + end + else + self:SetModel(errorModel) + end +end + +local function GetCharacter(self) + return self.character +end + +function PANEL:Init() + self.activeCharacter = ClientsideModel(errorModel) + self.activeCharacter:SetNoDraw(true) + self.activeCharacter.SetCharacter = SetCharacter + self.activeCharacter.GetCharacter = GetCharacter + + self.lastCharacter = ClientsideModel(errorModel) + self.lastCharacter:SetNoDraw(true) + self.lastCharacter.SetCharacter = SetCharacter + self.lastCharacter.GetCharacter = GetCharacter + + self.animationTime = 0.5 + + self.shadeY = 0 + self.shadeHeight = 0 + + self.cameraPosition = Vector(80, 0, 35) + self.cameraAngle = Angle(0, 180, 0) + self.lastPaint = 0 +end + +function PANEL:ResetSequence(model, lastModel) + local sequence = model:LookupSequence("idle_unarmed") + + if (sequence <= 0) then + sequence = model:SelectWeightedSequence(ACT_IDLE) + end + + if (sequence > 0) then + model:ResetSequence(sequence) + else + local found = false + + for _, v in ipairs(model:GetSequenceList()) do + if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then + model:ResetSequence(v) + found = true + + break + end + end + + if (!found) then + model:ResetSequence(4) + end + end + + model:SetIK(false) + + -- copy cycle if we can to avoid a jarring transition from resetting the sequence + if (lastModel) then + model:SetCycle(lastModel:GetCycle()) + end +end + +function PANEL:RunAnimation(model) + model:FrameAdvance((RealTime() - self.lastPaint) * 0.5) +end + +function PANEL:LayoutEntity(model) + model:SetIK(false) + + self:RunAnimation(model) +end + +function PANEL:SetActiveCharacter(character) + self.shadeY = self:GetTall() + self.shadeHeight = self:GetTall() + + -- set character immediately if we're an error (something isn't selected yet) + if (self.activeCharacter:GetModel() == errorModel) then + self.activeCharacter:SetCharacter(character) + self:ResetSequence(self.activeCharacter) + + return + end + + -- if the animation is already playing, we update its parameters so we can avoid restarting + local shade = self:GetTweenAnimation(1) + local shadeHide = self:GetTweenAnimation(2) + + if (shade) then + shade.newCharacter = character + return + elseif (shadeHide) then + shadeHide.queuedCharacter = character + return + end + + self.lastCharacter:SetCharacter(self.activeCharacter:GetCharacter()) + self:ResetSequence(self.lastCharacter, self.activeCharacter) + + shade = self:CreateAnimation(self.animationTime * 0.5, { + index = 1, + target = { + shadeY = 0, + shadeHeight = self:GetTall() + }, + easing = "linear", + + OnComplete = function(shadeAnimation, shadePanel) + shadePanel.activeCharacter:SetCharacter(shadeAnimation.newCharacter) + shadePanel:ResetSequence(shadePanel.activeCharacter) + + shadePanel:CreateAnimation(shadePanel.animationTime, { + index = 2, + target = {shadeHeight = 0}, + easing = "outQuint", + + OnComplete = function(animation, panel) + if (animation.queuedCharacter) then + panel:SetActiveCharacter(animation.queuedCharacter) + else + panel.lastCharacter:SetCharacter(nil) + end + end + }) + end + }) + + shade.newCharacter = character +end + +function PANEL:Paint(width, height) + local x, y = self:LocalToScreen(0, 0) + local bTransition = self.lastCharacter:GetModel() != errorModel + local modelFOV = (ScrW() > ScrH() * 1.8) and 92 or 70 + + cam.Start3D(self.cameraPosition, self.cameraAngle, modelFOV, x, y, width, height) + render.SuppressEngineLighting(true) + render.SetLightingOrigin(self.activeCharacter:GetPos()) + + -- setup lighting + render.SetModelLighting(0, 1.5, 1.5, 1.5) + + for i = 1, 4 do + render.SetModelLighting(i, 0.4, 0.4, 0.4) + end + + render.SetModelLighting(5, 0.04, 0.04, 0.04) + + -- clip anything out of bounds + local curparent = self + local rightx = self:GetWide() + local leftx = 0 + local topy = 0 + local bottomy = self:GetTall() + local previous = curparent + + while (curparent:GetParent() != nil) do + local lastX, lastY = previous:GetPos() + curparent = curparent:GetParent() + + topy = math.Max(lastY, topy + lastY) + leftx = math.Max(lastX, leftx + lastX) + bottomy = math.Min(lastY + previous:GetTall(), bottomy + lastY) + rightx = math.Min(lastX + previous:GetWide(), rightx + lastX) + + previous = curparent + end + + ix.util.ResetStencilValues() + render.SetStencilEnable(true) + render.SetStencilWriteMask(30) + render.SetStencilTestMask(30) + render.SetStencilReferenceValue(31) + + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + + self:LayoutEntity(self.activeCharacter) + + if (bTransition) then + -- only need to layout while it's used + self:LayoutEntity(self.lastCharacter) + + render.SetScissorRect(leftx, topy, rightx, bottomy - (self:GetTall() - self.shadeHeight), true) + self.lastCharacter:DrawModel() + + render.SetScissorRect(leftx, topy + self.shadeHeight, rightx, bottomy, true) + self.activeCharacter:DrawModel() + + render.SetScissorRect(leftx, topy, rightx, bottomy, true) + else + self.activeCharacter:DrawModel() + end + + render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilPassOperation(STENCIL_KEEP) + + cam.Start2D() + derma.SkinFunc("PaintCharacterTransitionOverlay", self, 0, self.shadeY, width, self.shadeHeight) + cam.End2D() + render.SetStencilEnable(false) + + render.SetScissorRect(0, 0, 0, 0, false) + render.SuppressEngineLighting(false) + cam.End3D() + + self.lastPaint = RealTime() +end + +function PANEL:OnRemove() + self.lastCharacter:Remove() + self.activeCharacter:Remove() +end + +vgui.Register("ixCharMenuCarousel", PANEL, "Panel") + +-- character load panel +PANEL = {} + +AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) +AccessorFunc(PANEL, "backgroundFraction", "BackgroundFraction", FORCE_NUMBER) + +function PANEL:Init() + local parent = self:GetParent() + local padding = self:GetPadding() + local halfWidth = parent:GetWide() * 0.5 - (padding * 2) + local halfHeight = parent:GetTall() * 0.5 - (padding * 2) + local modelFOV = (ScrW() > ScrH() * 1.8) and 102 or 78 + + self.animationTime = 1 + self.backgroundFraction = 1 + + -- main panel + self.panel = self:AddSubpanel("main") + self.panel:SetTitle("loadTitle") + self.panel.OnSetActive = function() + self:CreateAnimation(self.animationTime, { + index = 2, + target = {backgroundFraction = 1}, + easing = "outQuint", + }) + end + + -- character button list + local controlList = self.panel:Add("Panel") + controlList:Dock(LEFT) + controlList:SetSize(halfWidth, halfHeight) + + local back = controlList:Add("ixMenuButton") + back:Dock(BOTTOM) + back:SetText("return") + back:SizeToContents() + back.DoClick = function() + self:SlideDown() + parent.mainPanel:Undim() + end + + self.characterList = controlList:Add("ixCharMenuButtonList") + self.characterList.buttons = {} + self.characterList:Dock(FILL) + + -- right-hand side with carousel and buttons + local infoPanel = self.panel:Add("Panel") + infoPanel:Dock(FILL) + + local infoButtons = infoPanel:Add("Panel") + infoButtons:Dock(BOTTOM) + infoButtons:SetTall(back:GetTall()) -- hmm... + + local continueButton = infoButtons:Add("ixMenuButton") + continueButton:Dock(FILL) + continueButton:SetText("choose") + continueButton:SetContentAlignment(6) + continueButton:SizeToContents() + continueButton.DoClick = function() + self:SlideDown(self.animationTime, function() + net.Start("ixCharacterChoose") + net.WriteUInt(self.character:GetID(), 32) + net.SendToServer() + end, true) + end + + local deleteButton = infoButtons:Add("ixMenuButton") + deleteButton:Dock(LEFT) + deleteButton:SetText("delete") + deleteButton:SetContentAlignment(5) + deleteButton:SetTextInset(0, 0) + deleteButton:SizeToContents() + deleteButton:SetTextColor(derma.GetColor("Error", deleteButton)) + deleteButton.DoClick = function() + self:SetActiveSubpanel("delete") + end + + self.carousel = infoPanel:Add("ixCharMenuCarousel") + self.carousel:Dock(FILL) + + -- character deletion panel + self.delete = self:AddSubpanel("delete") + self.delete:SetTitle(nil) + self.delete.OnSetActive = function() + self.deleteModel:SetModel(self.character:GetModel()) + self:CreateAnimation(self.animationTime, { + index = 2, + target = {backgroundFraction = 0}, + easing = "outQuint" + }) + end + + local deleteInfo = self.delete:Add("Panel") + deleteInfo:SetSize(parent:GetWide() * 0.5, parent:GetTall()) + deleteInfo:Dock(LEFT) + + local deleteReturn = deleteInfo:Add("ixMenuButton") + deleteReturn:Dock(BOTTOM) + deleteReturn:SetText("no") + deleteReturn:SizeToContents() + deleteReturn.DoClick = function() + self:SetActiveSubpanel("main") + end + + local deleteConfirm = self.delete:Add("ixMenuButton") + deleteConfirm:Dock(BOTTOM) + deleteConfirm:SetText("yes") + deleteConfirm:SetContentAlignment(6) + deleteConfirm:SizeToContents() + deleteConfirm:SetTextColor(derma.GetColor("Error", deleteConfirm)) + deleteConfirm.DoClick = function() + local id = self.character:GetID() + + parent:ShowNotice(1, L("deleteComplete", self.character:GetName())) + self:Populate(id) + self:SetActiveSubpanel("main") + + net.Start("ixCharacterDelete") + net.WriteUInt(id, 32) + net.SendToServer() + end + + self.deleteModel = deleteInfo:Add("ixModelPanel") + self.deleteModel:Dock(FILL) + self.deleteModel:SetModel(errorModel) + self.deleteModel:SetFOV(modelFOV) + self.deleteModel.PaintModel = self.deleteModel.Paint + + local deleteNag = self.delete:Add("Panel") + deleteNag:SetTall(parent:GetTall() * 0.5) + deleteNag:Dock(BOTTOM) + + local deleteTitle = deleteNag:Add("DLabel") + deleteTitle:SetFont("ixTitleFont") + deleteTitle:SetText(L("areYouSure"):utf8upper()) + deleteTitle:SetTextColor(ix.config.Get("color")) + deleteTitle:SizeToContents() + deleteTitle:Dock(TOP) + + local deleteText = deleteNag:Add("DLabel") + deleteText:SetFont("ixMenuButtonFont") + deleteText:SetText(L("deleteConfirm")) + deleteText:SetTextColor(color_white) + deleteText:SetContentAlignment(7) + deleteText:Dock(FILL) + + -- finalize setup + self:SetActiveSubpanel("main", 0) +end + +function PANEL:OnCharacterDeleted(character) + if (self.bActive and #ix.characters == 0) then + self:SlideDown() + end +end + +function PANEL:Populate(ignoreID) + self.characterList:Clear() + self.characterList.buttons = {} + + local bSelected + + -- loop backwards to preserve order since we're docking to the bottom + for i = 1, #ix.characters do + local id = ix.characters[i] + local character = ix.char.loaded[id] + + if (!character or character:GetID() == ignoreID) then + continue + end + + local index = character:GetFaction() + local faction = ix.faction.indices[index] + local color = faction and faction.color or color_white + + local button = self.characterList:Add("ixMenuSelectionButton") + button:SetBackgroundColor(color) + button:SetText(character:GetName()) + button:SizeToContents() + button:SetButtonList(self.characterList.buttons) + button.character = character + button.OnSelected = function(panel) + self:OnCharacterButtonSelected(panel) + end + + -- select currently loaded character if available + local localCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() + + if (localCharacter and character:GetID() == localCharacter:GetID()) then + button:SetSelected(true) + self.characterList:ScrollToChild(button) + + bSelected = true + end + end + + if (!bSelected) then + local buttons = self.characterList.buttons + + if (#buttons > 0) then + local button = buttons[#buttons] + + button:SetSelected(true) + self.characterList:ScrollToChild(button) + else + self.character = nil + end + end + + self.characterList:SizeToContents() +end + +function PANEL:OnSlideUp() + self.bActive = true + self:Populate() +end + +function PANEL:OnSlideDown() + self.bActive = false +end + +function PANEL:OnCharacterButtonSelected(panel) + self.carousel:SetActiveCharacter(panel.character) + self.character = panel.character +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintCharacterLoadBackground", self, width, height) +end + +vgui.Register("ixCharMenuLoad", PANEL, "ixCharMenuPanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_classes.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_classes.lua new file mode 100644 index 0000000..9b316f4 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_classes.lua @@ -0,0 +1,159 @@ + +local PANEL = {} + +function PANEL:Init() + self:SetTall(64) + + local function AssignClick(panel) + panel.OnMousePressed = function() + self.pressing = -1 + self:OnClick() + end + + panel.OnMouseReleased = function() + if (self.pressing) then + self.pressing = nil + end + end + end + + + self.icon = self:Add("SpawnIcon") + self.icon:SetSize(128, 64) + self.icon:InvalidateLayout(true) + self.icon:Dock(LEFT) + self.icon.PaintOver = function(this, w, h) + end + AssignClick(self.icon) + + self.limit = self:Add("DLabel") + self.limit:Dock(RIGHT) + self.limit:SetMouseInputEnabled(true) + self.limit:SetCursor("hand") + self.limit:SetExpensiveShadow(1, Color(0, 0, 60)) + self.limit:SetContentAlignment(5) + self.limit:SetFont("ixMediumFont") + self.limit:SetWide(64) + AssignClick(self.limit) + + self.label = self:Add("DLabel") + self.label:Dock(FILL) + self.label:SetMouseInputEnabled(true) + self.label:SetCursor("hand") + self.label:SetExpensiveShadow(1, Color(0, 0, 60)) + self.label:SetContentAlignment(5) + self.label:SetFont("ixMediumFont") + AssignClick(self.label) +end + +function PANEL:OnClick() + ix.command.Send("BecomeClass", self.class) +end + +function PANEL:SetNumber(number) + local limit = self.data.limit + + if (limit > 0) then + self.limit:SetText(Format("%s/%s", number, limit)) + else + self.limit:SetText("∞") + end +end + +function PANEL:SetClass(data) + if (data.model) then + local model = data.model + if (istable(model)) then + model = table.Random(model) + end + + self.icon:SetModel(model) + else + local char = LocalPlayer():GetCharacter() + local model = LocalPlayer():GetModel() + + if (char) then + model = char:GetModel() + end + + self.icon:SetModel(model) + end + + self.label:SetText(L(data.name)) + self.data = data + self.class = data.index + + self:SetNumber(#ix.class.GetPlayers(data.index)) +end + +vgui.Register("ixClassPanel", PANEL, "DPanel") + +PANEL = {} + +function PANEL:Init() + ix.gui.classes = self + + self:SetSize(self:GetParent():GetSize()) + + self.list = vgui.Create("DPanelList", self) + self.list:Dock(FILL) + self.list:EnableVerticalScrollbar() + self.list:SetSpacing(5) + self.list:SetPadding(5) + + self.classPanels = {} + self:LoadClasses() +end + +function PANEL:LoadClasses() + self.list:Clear() + + for k, v in ipairs(ix.class.list) do + local no, why = ix.class.CanSwitchTo(LocalPlayer(), k) + local itsFull = ("class is full" == why) + + if (no or itsFull) then + local panel = vgui.Create("ixClassPanel", self.list) + panel:SetClass(v) + table.insert(self.classPanels, panel) + + self.list:AddItem(panel) + end + end +end + +vgui.Register("ixClasses", PANEL, "EditablePanel") + +hook.Add("CreateMenuButtons", "ixClasses", function(tabs) + local cnt = table.Count(ix.class.list) + + if (cnt <= 1) then return end + + for k, _ in ipairs(ix.class.list) do + if (!ix.class.CanSwitchTo(LocalPlayer(), k)) then + continue + else + tabs["classes"] = function(container) + container:Add("ixClasses") + end + + return + end + end +end) + +net.Receive("ixClassUpdate", function() + local client = net.ReadEntity() + + if (ix.gui.classes and ix.gui.classes:IsVisible()) then + if (client == LocalPlayer()) then + ix.gui.classes:LoadClasses() + else + for _, v in ipairs(ix.gui.classes.classPanels) do + local data = v.data + + v:SetNumber(#ix.class.GetPlayers(data.index)) + end + end + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_config.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_config.lua new file mode 100644 index 0000000..ead0d20 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_config.lua @@ -0,0 +1,211 @@ + +-- config manager +local PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:SetSearchEnabled(true) + + self:Populate() +end + +function PANEL:Populate() + -- gather categories + local categories = {} + local categoryIndices = {} + + for k, v in pairs(ix.config.stored) do + if (!v.type) then + continue + end + + local index = v.data and v.data.category or "misc" + + categories[index] = categories[index] or {} + categories[index][k] = v + end + + -- sort by category phrase + for k, _ in pairs(categories) do + categoryIndices[#categoryIndices + 1] = k + end + + table.sort(categoryIndices, function(a, b) + return L(a) < L(b) + end) + + -- add panels + for _, category in ipairs(categoryIndices) do + local categoryPhrase = L(category) + self:AddCategory(categoryPhrase) + + -- we can use sortedpairs since configs don't have phrases to account for + for k, v in SortedPairs(categories[category]) do + if (isfunction(v.hidden) and v.hidden()) then + continue + end + + local data = v.data.data + local type = v.type + local value = ix.util.SanitizeType(type, ix.config.Get(k)) + + local row = self:AddRow(type, categoryPhrase) + row:SetText(ix.util.ExpandCamelCase(k)) + row:Populate(k, v.data) + + -- type-specific properties + if (type == ix.type.number) then + row:SetMin(data and data.min or 0) + row:SetMax(data and data.max or 1) + row:SetDecimals(data and data.decimals or 0) + end + + row:SetValue(value, true) + row:SetShowReset(value != v.default, k, v.default) + + row.OnValueChanged = function(panel) + local newValue = ix.util.SanitizeType(type, panel:GetValue()) + + panel:SetShowReset(newValue != v.default, k, v.default) + + net.Start("ixConfigSet") + net.WriteString(k) + net.WriteType(newValue) + net.SendToServer() + end + + row.OnResetClicked = function(panel) + panel:SetValue(v.default, true) + panel:SetShowReset(false) + + net.Start("ixConfigSet") + net.WriteString(k) + net.WriteType(v.default) + net.SendToServer() + end + + row:GetLabel():SetHelixTooltip(function(tooltip) + local title = tooltip:AddRow("name") + title:SetImportant() + title:SetText(k) + title:SizeToContents() + title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5)) + + local description = tooltip:AddRow("description") + description:SetText(v.description) + description:SizeToContents() + end) + end + end + + self:SizeToContents() +end + +vgui.Register("ixConfigManager", PANEL, "ixSettings") + +-- plugin manager +PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:SetSearchEnabled(true) + + self.loadedCategory = L("loadedPlugins") + self.unloadedCategory = L("unloadedPlugins") + + if (!ix.gui.bReceivedUnloadedPlugins) then + net.Start("ixConfigRequestUnloadedList") + net.SendToServer() + end + + self:Populate() +end + +function PANEL:OnPluginToggled(uniqueID, bEnabled) + net.Start("ixConfigPluginToggle") + net.WriteString(uniqueID) + net.WriteBool(bEnabled) + net.SendToServer() +end + +function PANEL:Populate() + self:AddCategory(self.loadedCategory) + self:AddCategory(self.unloadedCategory) + + -- add loaded plugins + for k, v in SortedPairsByMemberValue(ix.plugin.list, "name") do + local row = self:AddRow(ix.type.bool, self.loadedCategory) + row.id = k + + row.setting:SetEnabledText(L("on"):utf8upper()) + row.setting:SetDisabledText(L("off"):utf8upper()) + row.setting:SizeToContents() + + -- if this plugin is not in the unloaded list currently, then it's queued for an unload + row:SetValue(!ix.plugin.unloaded[k], true) + row:SetText(v.name) + + row.OnValueChanged = function(panel, bEnabled) + self:OnPluginToggled(k, bEnabled) + end + + row:GetLabel():SetHelixTooltip(function(tooltip) + local title = tooltip:AddRow("name") + title:SetImportant() + title:SetText(v.name) + title:SizeToContents() + title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5)) + + local description = tooltip:AddRow("description") + description:SetText(v.description) + description:SizeToContents() + end) + end + + self:UpdateUnloaded(true) + self:SizeToContents() +end + +function PANEL:UpdatePlugin(uniqueID, bEnabled) + for _, v in pairs(self:GetRows()) do + if (v.id == uniqueID) then + v:SetValue(bEnabled, true) + end + end +end + +-- called from Populate and from the ixConfigUnloadedList net message +function PANEL:UpdateUnloaded(bNoSizeToContents) + for _, v in pairs(self:GetRows()) do + if (ix.plugin.unloaded[v.id]) then + v:SetValue(false, true) + end + end + + for k, v in SortedPairs(ix.plugin.unloaded) do + if (ix.plugin.list[k]) then + -- if this plugin is in the loaded plugins list then it's queued for an unload - don't display it in this category + continue + end + + local row = self:AddRow(ix.type.bool, self.unloadedCategory) + row.id = k + + row.setting:SetEnabledText(L("on"):utf8upper()) + row.setting:SetDisabledText(L("off"):utf8upper()) + row.setting:SizeToContents() + + row:SetText(k) + row:SetValue(!v, true) + + row.OnValueChanged = function(panel, bEnabled) + self:OnPluginToggled(k, bEnabled) + end + end + + if (!bNoSizeToContents) then + self:SizeToContents() + end +end + +vgui.Register("ixPluginManager", PANEL, "ixSettings") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_credits.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_credits.lua new file mode 100644 index 0000000..7d41277 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_credits.lua @@ -0,0 +1,308 @@ + +local CREDITS = { + {"Alex Grist", "76561197979205163", {"creditLeadDeveloper", "creditManager"}}, + {"Igor Radovanovic", "76561197990111113", {"creditLeadDeveloper", "creditUIDesigner"}}, + {"Jaydawg", "76561197970371430", {"creditTester"}} +} + +local SPECIALS = { + { + {"Luna", "76561197988658543"}, + {"Rain GBizzle", "76561198036111376"} + }, + { + {"Black Tea", "76561197999893894"} + } +} + +local MISC = { + {"nebulous", "Staff members finding bugs and providing input"}, + {"Contributors", "Ongoing support from various developers via GitHub"}, + {"NutScript", "Providing the base framework to build upon"} +} + +local url = "https://gethelix.co/" +local padding = 32 + +-- logo +local PANEL = {} + +function PANEL:Init() + self:SetTall(ScrH() * 0.60) + self:Dock(TOP) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25) + + -- title + surface.SetFont("ixIntroSubtitleFont") + local text = L("helix"):lower() + local textWidth, textHeight = surface.GetTextSize(text) + + surface.SetTextColor(color_white) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5) + surface.DrawText(text) + + -- version + surface.SetFont("ixMenuMiniFont") + surface.SetTextColor(200, 200, 200, 255) + surface.SetTextPos(width * 0.5 + textWidth * 0.5, height * 0.5 - textHeight * 0.5) + surface.DrawText(GAMEMODE.Version) +end + +vgui.Register("ixCreditsLogo", PANEL, "Panel") + +-- nametag +PANEL = {} + +function PANEL:Init() + self.name = self:Add("DLabel") + self.name:SetFont("ixMenuButtonFontThick") + + self.avatar = self:Add("AvatarImage") +end + +function PANEL:SetName(name) + self.name:SetText(name) +end + +function PANEL:SetAvatar(steamid) + self.avatar:SetSteamID(steamid, 64) +end + +function PANEL:PerformLayout(width, height) + self.name:SetPos(width - self.name:GetWide(), 0) + self.avatar:MoveLeftOf(self.name, padding * 0.5) +end + +function PANEL:SizeToContents() + self.name:SizeToContents() + + local tall = self.name:GetTall() + self.avatar:SetSize(tall, tall) + self:SetSize(self.name:GetWide() + self.avatar:GetWide() + padding * 0.5, self.name:GetTall()) +end + +vgui.Register("ixCreditsNametag", PANEL, "Panel") + +-- name row +PANEL = {} + +function PANEL:Init() + self:DockMargin(0, padding, 0, 0) + self:Dock(TOP) + + self.nametag = self:Add("ixCreditsNametag") + + self.tags = self:Add("DLabel") + self.tags:SetFont("ixMenuButtonFont") + + self:SizeToContents() +end + +function PANEL:SetName(name) + self.nametag:SetName(name) +end + +function PANEL:SetAvatar(steamid) + self.nametag:SetAvatar(steamid) +end + +function PANEL:SetTags(tags) + for i = 1, #tags do + tags[i] = L(tags[i]) + end + + self.tags:SetText(table.concat(tags, "\n")) +end + +function PANEL:Paint(width, height) + surface.SetDrawColor(ix.config.Get("color")) + surface.DrawRect(width * 0.5 - 1, 0, 1, height) +end + +function PANEL:PerformLayout(width, height) + self.nametag:SetPos(width * 0.5 - self.nametag:GetWide() - padding, 0) + self.tags:SetPos(width * 0.5 + padding, 0) +end + +function PANEL:SizeToContents() + self.nametag:SizeToContents() + self.tags:SizeToContents() + + self:SetTall(math.max(self.nametag:GetTall(), self.tags:GetTall())) +end + +vgui.Register("ixCreditsRow", PANEL, "Panel") + +PANEL = {} + +function PANEL:Init() + self.left = {} + self.right = {} +end + +function PANEL:AddLeft(name, steamid) + local nametag = self:Add("ixCreditsNametag") + nametag:SetName(name) + nametag:SetAvatar(steamid) + nametag:SizeToContents() + + self.left[#self.left + 1] = nametag +end + +function PANEL:AddRight(name, steamid) + local nametag = self:Add("ixCreditsNametag") + nametag:SetName(name) + nametag:SetAvatar(steamid) + nametag:SizeToContents() + + self.right[#self.right + 1] = nametag +end + +function PANEL:PerformLayout(width, height) + local y = 0 + + for _, v in ipairs(self.left) do + v:SetPos(width * 0.25 - v:GetWide() * 0.5, y) + y = y + v:GetTall() + padding + end + + y = 0 + + for _, v in ipairs(self.right) do + v:SetPos(width * 0.75 - v:GetWide() * 0.5, y) + y = y + v:GetTall() + padding + end + + if (IsValid(self.center)) then + self.center:SetPos(width * 0.5 - self.center:GetWide() * 0.5, y) + end +end + +function PANEL:SizeToContents() + local heightLeft, heightRight, centerHeight = 0, 0, 0 + + if (#self.left > #self.right) then + local center = self.left[#self.left] + centerHeight = center:GetTall() + + self.center = center + self.left[#self.left] = nil + elseif (#self.right > #self.left) then + local center = self.right[#self.right] + centerHeight = center:GetTall() + + self.center = center + self.right[#self.right] = nil + end + + for _, v in ipairs(self.left) do + heightLeft = heightLeft + v:GetTall() + padding + end + + for _, v in ipairs(self.right) do + heightRight = heightRight + v:GetTall() + padding + end + + self:SetTall(math.max(heightLeft, heightRight) + centerHeight) +end + +vgui.Register("ixCreditsSpecials", PANEL, "Panel") + +PANEL = {} + +function PANEL:Init() + self:Add("ixCreditsLogo") + + local link = self:Add("DLabel", self) + link:SetFont("ixMenuMiniFont") + link:SetTextColor(Color(200, 200, 200, 255)) + link:SetText(url) + link:SetContentAlignment(5) + link:Dock(TOP) + link:SizeToContents() + link:SetMouseInputEnabled(true) + link:SetCursor("hand") + link.OnMousePressed = function() + gui.OpenURL(url) + end + + for _, v in ipairs(CREDITS) do + local row = self:Add("ixCreditsRow") + row:SetName(v[1]) + row:SetAvatar(v[2]) + row:SetTags(v[3]) + row:SizeToContents() + end + + local specials = self:Add("ixLabel") + specials:SetFont("ixMenuButtonFont") + specials:SetText(L("creditSpecial"):utf8upper()) + specials:SetTextColor(ix.config.Get("color")) + specials:SetDropShadow(1) + specials:SetKerning(16) + specials:SetContentAlignment(5) + specials:DockMargin(0, padding * 2, 0, padding) + specials:Dock(TOP) + specials:SizeToContents() + + local specialList = self:Add("ixCreditsSpecials") + specialList:DockMargin(0, padding, 0, 0) + specialList:Dock(TOP) + + for _, v in ipairs(SPECIALS[1]) do + specialList:AddLeft(v[1], v[2]) + end + + for _, v in ipairs(SPECIALS[2]) do + specialList:AddRight(v[1], v[2]) + end + + specialList:SizeToContents() + + -- less more padding if there's a center column nametag + if (IsValid(specialList.center)) then + specialList:DockMargin(0, padding, 0, padding) + end + + for _, v in ipairs(MISC) do + local title = self:Add("DLabel") + title:SetFont("ixMenuButtonFontThick") + title:SetText(v[1]) + title:SetContentAlignment(5) + title:SizeToContents() + title:DockMargin(0, padding, 0, 0) + title:Dock(TOP) + + local description = self:Add("DLabel") + description:SetFont("ixSmallTitleFont") + description:SetText(v[2]) + description:SetContentAlignment(5) + description:SizeToContents() + description:Dock(TOP) + end + + self:Dock(TOP) + self:SizeToContents() +end + +function PANEL:SizeToContents() + local height = padding + + for _, v in pairs(self:GetChildren()) do + local _, top, _, bottom = v:GetDockMargin() + height = height + v:GetTall() + top + bottom + end + + self:SetTall(height) +end + +vgui.Register("ixCredits", PANEL, "Panel") + +hook.Add("PopulateHelpMenu", "ixCredits", function(tabs) + tabs["credits"] = function(container) + container:Add("ixCredits") + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_deathscreen.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_deathscreen.lua new file mode 100644 index 0000000..b0bbcf7 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_deathscreen.lua @@ -0,0 +1,126 @@ +surface.CreateFont("ixDeathScreenMain", { + font = "OperiusRegular", + size = 180, + weight = 300, + extended = true +}) + +surface.CreateFont("ixDeathScreenSub", { + font = "OperiusRegular", + size = 90, + weight = 300, + extended = true +}) + +surface.CreateFont("ixDeathScreenMainFallback", { + font = "Roboto", + size = 180, + weight = 700, + extended = true +}) + +surface.CreateFont("ixDeathScreenSubFallback", { + font = "Roboto", + size = 90, + weight = 500, + extended = true +}) + +local function GetFont(primary, fallback) + surface.SetFont(primary) + local w = surface.GetTextSize("TEST") + return (w > 0) and primary or fallback +end + +local deathScreenPanel = nil + +net.Receive("ixDeathScreen", function() + if not ix.config.Get("deathScreenEnabled") then return end + + if IsValid(deathScreenPanel) then + deathScreenPanel:Remove() + end + + local duration = net.ReadFloat() + local killerName = net.ReadString() + + deathScreenPanel = vgui.Create("DFrame") + deathScreenPanel:SetSize(ScrW(), ScrH()) + deathScreenPanel:SetPos(0, 0) + deathScreenPanel:SetTitle("") + deathScreenPanel:ShowCloseButton(false) + deathScreenPanel:SetDraggable(false) + deathScreenPanel:MakePopup() + deathScreenPanel:SetKeyboardInputEnabled(false) + deathScreenPanel:SetMouseInputEnabled(false) + + local colorAlpha = 0 + local textAlpha = 0 + local fadeOutStarted = false + local startTime = RealTime() + + local fadeInSpeed = ix.config.Get("deathScreenFadeInSpeed", 5) + local textFadeSpeed = ix.config.Get("deathScreenTextFadeSpeed", 0.5) + local textColor = ix.config.Get("deathScreenTextColor", Color(132, 43, 60)) + local mainText = ix.config.Get("deathScreenMainText", "YOU'RE DEAD") + local subText = "[Убил: " .. killerName .. "]" + + timer.Simple(duration - 1.5, function() + if IsValid(deathScreenPanel) then + fadeOutStarted = true + end + end) + + timer.Simple(duration, function() + if IsValid(deathScreenPanel) then + deathScreenPanel:Remove() + end + end) + + deathScreenPanel.Paint = function(s, w, h) + if fadeOutStarted then + colorAlpha = math.Approach(colorAlpha, 0, FrameTime() * 255) + else + colorAlpha = math.Approach(colorAlpha, 255, FrameTime() * fadeInSpeed * 255) + end + + draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, colorAlpha)) + end + + local textLabel = vgui.Create("DLabel", deathScreenPanel) + textLabel:SetSize(deathScreenPanel:GetWide(), deathScreenPanel:GetTall()) + textLabel:SetPos(0, 0) + textLabel:SetText("") + + textLabel.Paint = function(s, w, h) + local elapsed = RealTime() - startTime + + if fadeOutStarted then + textAlpha = math.Approach(textAlpha, 0, FrameTime() * 255 * 4) + elseif elapsed > 1 then + textAlpha = math.Approach(textAlpha, 255, FrameTime() * textFadeSpeed * 255) + end + + local mainFont = GetFont("ixDeathScreenMain", "ixDeathScreenMainFallback") + local subFont = GetFont("ixDeathScreenSub", "ixDeathScreenSubFallback") + + draw.SimpleText(mainText, mainFont, w/2, h/2, + Color(textColor.r, textColor.g, textColor.b, textAlpha), + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + draw.SimpleText(subText, subFont, w/2, h/2 + 144, + Color(textColor.r, textColor.g, textColor.b, textAlpha), + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + local remaining = math.max(0, math.ceil(duration - elapsed)) + draw.SimpleText("ВОЗРОЖДЕНИЕ ЧЕРЕЗ: " .. remaining, subFont, w/2, h/2 + 250, + Color(textColor.r, textColor.g, textColor.b, textAlpha), + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end) + +hook.Add("InitPostEntity", "ixDeathScreenCleanup", function() + if IsValid(deathScreenPanel) then + deathScreenPanel:Remove() + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_dev_icon.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_dev_icon.lua new file mode 100644 index 0000000..ee7615d --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_dev_icon.lua @@ -0,0 +1,261 @@ +--Icon Editor Base and Math Scale Functions from: https://github.com/TeslaCloud/flux-ce/tree/master + +local scaleFactorX = 1 / 1920 +local scaleFactorY = 1 / 1080 + +local function scale(size) + return math.floor(size * (ScrH() * scaleFactorY)) +end + +local function scale_x(size) + return math.floor(size * (ScrW() * scaleFactorX)) +end + +local PANEL = {} + +function PANEL:Init() + if (IsValid(ix.gui.dev_icon)) then + ix.gui.dev_icon:Remove() + end + + ix.gui.dev_icon = self + local pW, pH = ScrW() * 0.6, ScrH() * 0.6 + self:SetSize(pW, pH) + self:MakePopup() + self:Center() + + local buttonSize = scale(48) + + self.model = vgui.Create("DAdjustableModelPanel", self) + self.model:SetSize(pW * 0.5, pH - buttonSize) + self.model:Dock(LEFT) + self.model:DockMargin(0, 0, 0, buttonSize + scale(8)) + self.model:SetModel("models/props_borealis/bluebarrel001.mdl") + self.model:SetLookAt(Vector(0, 0, 0)) + + self.model.LayoutEntity = function() end + + local x = scale_x(4) + self.best = vgui.Create("DButton", self) + self.best:SetSize(buttonSize, buttonSize) + self.best:SetPos(x, pH - buttonSize - scale(4)) + self.best:SetFont("ixIconsMenuButton") + self.best:SetText("b") + self.best:SetTooltip(L("iconEditorAlignBest")) + + self.best.DoClick = function() + local entity = self.model:GetEntity() + local pos = entity:GetPos() + local camData = PositionSpawnIcon(entity, pos) + + if (camData) then + self.model:SetCamPos(camData.origin) + self.model:SetFOV(camData.fov) + self.model:SetLookAng(camData.angles) + end + end + + x = x + buttonSize + scale_x(4) + + self.front = vgui.Create("DButton", self) + self.front:SetSize(buttonSize, buttonSize) + self.front:SetPos(x, pH - buttonSize - scale(4)) + self.front:SetFont("ixIconsMenuButton") + self.front:SetText("m") + self.front:SetTooltip(L("iconEditorAlignFront")) + + self.front.DoClick = function() + local entity = self.model:GetEntity() + local pos = entity:GetPos() + local camPos = pos + Vector(-200, 0, 0) + self.model:SetCamPos(camPos) + self.model:SetFOV(45) + self.model:SetLookAng((camPos * -1):Angle()) + end + + x = x + buttonSize + scale_x(4) + + self.above = vgui.Create("DButton", self) + self.above:SetSize(buttonSize, buttonSize) + self.above:SetPos(x, pH - buttonSize - scale(4)) + self.above:SetFont("ixIconsMenuButton") + self.above:SetText("u") + self.above:SetTooltip(L("iconEditorAlignAbove")) + + self.above.DoClick = function() + local entity = self.model:GetEntity() + local pos = entity:GetPos() + local camPos = pos + Vector(0, 0, 200) + self.model:SetCamPos(camPos) + self.model:SetFOV(45) + self.model:SetLookAng((camPos * -1):Angle()) + end + + x = x + buttonSize + scale_x(4) + + self.right = vgui.Create("DButton", self) + self.right:SetSize(buttonSize, buttonSize) + self.right:SetPos(x, pH - buttonSize - scale(4)) + self.right:SetFont("ixIconsMenuButton") + self.right:SetText("t") + self.right:SetTooltip(L("iconEditorAlignRight")) + + self.right.DoClick = function() + local entity = self.model:GetEntity() + local pos = entity:GetPos() + local camPos = pos + Vector(0, 200, 0) + self.model:SetCamPos(camPos) + self.model:SetFOV(45) + self.model:SetLookAng((camPos * -1):Angle()) + end + + x = x + buttonSize + scale_x(4) + + self.center = vgui.Create("DButton", self) + self.center:SetSize(buttonSize, buttonSize) + self.center:SetPos(x, pH - buttonSize - scale(4)) + self.center:SetFont("ixIconsMenuButton") + self.center:SetText("T") + self.center:SetTooltip(L("iconEditorAlignCenter")) + + self.center.DoClick = function() + local entity = self.model:GetEntity() + local pos = entity:GetPos() + self.model:SetCamPos(pos) + self.model:SetFOV(45) + self.model:SetLookAng(Angle(0, -180, 0)) + end + + self.best:DoClick() + self.preview = vgui.Create("DPanel", self) + self.preview:Dock(FILL) + self.preview:DockMargin(scale_x(4), 0, 0, 0) + self.preview:DockPadding(scale_x(4), scale(4), scale_x(4), scale(4)) + + self.preview.Paint = function(pnl, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100)) + end + + self.modelLabel = self.preview:Add("DLabel") + self.modelLabel:Dock(TOP) + self.modelLabel:SetText("Model") + self.modelLabel:SetFont("ixMenuButtonFontSmall") + self.modelLabel:DockMargin(4, 4, 4, 4) + + self.modelPath = vgui.Create("ixTextEntry", self.preview) + self.modelPath:SetValue(self.model:GetModel()) + self.modelPath:Dock(TOP) + self.modelPath:SetFont("ixMenuButtonFontSmall") + self.modelPath:SetPlaceholderText("Model...") + + self.modelPath.OnEnter = function(pnl) + local model = pnl:GetValue() + + if (model and model != "") then + self.model:SetModel(model) + self.item:Rebuild() + end + end + + self.modelPath.OnLoseFocus = function(pnl) + local model = pnl:GetValue() + + if (model and model != "") then + self.model:SetModel(model) + self.item:Rebuild() + end + end + + self.width = vgui.Create("ixSettingsRowNumber", self.preview) + self.width:Dock(TOP) + self.width:SetText(L("iconEditorWidth")) + self.width:SetMin(1) + self.width:SetMax(24) + self.width:SetDecimals(0) + self.width:SetValue(1) + + self.width.OnValueChanged = function(pnl, value) + self.item:Rebuild() + end + + self.height = vgui.Create("ixSettingsRowNumber", self.preview) + self.height:Dock(TOP) + self.height:SetText(L("iconEditorHeight")) + self.height:SetMin(1) + self.height:SetMax(24) + self.height:SetDecimals(0) + self.height:SetValue(1) + + self.height.OnValueChanged = function(pnl, value) + self.item:Rebuild() + end + + self.itemPanel = vgui.Create("DPanel", self.preview) + self.itemPanel:Dock(FILL) + + self.itemPanel.Paint = function(pnl, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100)) + end + + self.item = vgui.Create("DModelPanel", self.itemPanel) + self.item:SetMouseInputEnabled(false) + self.item.LayoutEntity = function() end + + self.item.PaintOver = function(pnl, w, h) + surface.SetDrawColor(color_white) + surface.DrawOutlinedRect(0, 0, w, h) + end + + self.item.Rebuild = function(pnl) + local slotSize = 64 + local padding = scale(2) + local slotWidth, slotHeight = math.Round(self.width:GetValue()), math.Round(self.height:GetValue()) + local w, h = slotWidth * (slotSize + padding) - padding, slotHeight * (slotSize + padding) - padding + pnl:SetModel(self.model:GetModel()) + pnl:SetCamPos(self.model:GetCamPos()) + pnl:SetFOV(self.model:GetFOV()) + pnl:SetLookAng(self.model:GetLookAng()) + pnl:SetSize(w, h) + pnl:Center() + end + + self.item:Rebuild() + + timer.Create("ix_icon_editor_update", 0.5, 0, function() + if IsValid(self) and IsValid(self.model) then + self.item:Rebuild() + else + timer.Remove("ix_icon_editor_update") + end + end) + + self.copy = vgui.Create("DButton", self) + self.copy:SetSize(buttonSize, buttonSize) + self.copy:SetPos(pW - buttonSize - scale_x(12), pH - buttonSize - scale(12)) + self.copy:SetFont("ixIconsMenuButton") + self.copy:SetText("}") + self.copy:SetTooltip(L("iconEditorCopy")) + self.copy.DoClick = function() + local camPos = self.model:GetCamPos() + local camAng = self.model:GetLookAng() + local str = "ITEM.model = \""..self.model:GetModel().."\"\n" + .."ITEM.width = "..math.Round(self.width:GetValue()).."\n" + .."ITEM.height = "..math.Round(self.height:GetValue()).."\n" + .."ITEM.iconCam = {\n" + .."\tpos = Vector("..math.Round(camPos.x, 2)..", "..math.Round(camPos.y, 2)..", "..math.Round(camPos.z, 2).."),\n" + .."\tang = Angle("..math.Round(camAng.p, 2)..", "..math.Round(camAng.y, 2)..", "..math.Round(camAng.r, 2).."),\n" + .."\tfov = "..math.Round(self.model:GetFOV(), 2).."\n" + .."}\n" + + SetClipboardText(str) + ix.util.Notify(L("iconEditorCopied")) + end +end + +vgui.Register("ix_icon_editor", PANEL, "DFrame") + +concommand.Add("ix_dev_icon", function() + if (LocalPlayer():IsAdmin()) then + vgui.Create("ix_icon_editor") + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_entitymenu.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_entitymenu.lua new file mode 100644 index 0000000..6bc7992 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_entitymenu.lua @@ -0,0 +1,277 @@ + +local animationTime = 1 +local padding = 32 + +-- entity menu button +DEFINE_BASECLASS("ixMenuButton") +local PANEL = {} + +AccessorFunc(PANEL, "callback", "Callback") + +function PANEL:Init() + self:SetTall(ScrH() * 0.1) + self:Dock(TOP) +end + +function PANEL:DoClick() + local bStatus = true + local parent = ix.menu.panel + local entity = parent:GetEntity() + + if (parent.bClosing) then + return + end + + if (isfunction(self.callback)) then + bStatus = self.callback() + end + + if (bStatus != false) then + ix.menu.NetworkChoice(entity, self.originalText, bStatus) + end + + parent:Remove() +end + +function PANEL:SetText(text) + self.originalText = text + BaseClass.SetText(self, text) +end + +vgui.Register("ixEntityMenuButton", PANEL, "ixMenuButton") + +-- entity menu list +DEFINE_BASECLASS("EditablePanel") +PANEL = {} + +function PANEL:Init() + self.list = {} +end + +function PANEL:AddOption(text, callback) + local panel = self:Add("ixEntityMenuButton") + panel:SetText(text) + panel:SetCallback(callback) + panel:Dock(TOP) + + if (self.bPaintedManually) then + panel:SetPaintedManually(true) + end + + self.list[#self.list + 1] = panel +end + +function PANEL:SizeToContents() + local height = 0 + + for i = 1, #self.list do + height = height + self.list[i]:GetTall() + end + + self:SetSize(ScrW() * 0.5 - padding * 2, height) +end + +function PANEL:Center() + local parent = self:GetParent() + + self:SetPos( + ScrW() * 0.5 + padding, + parent:GetTall() * 0.5 - self:GetTall() * 0.5 + ) +end + +function PANEL:SetPaintedManually(bValue) + if (bValue) then + for i = 1, #self.list do + self.list[i]:SetPaintedManually(true) + end + + self.bPaintedManually = true + end + + BaseClass.SetPaintedManually(self, bValue) +end + +function PANEL:PaintManual() + BaseClass.PaintManual(self) + local list = self.list + + for i = 1, #list do + list[i]:PaintManual() + end +end + +vgui.Register("ixEntityMenuList", PANEL, "EditablePanel") + +-- entity menu +DEFINE_BASECLASS("EditablePanel") +PANEL = {} + +AccessorFunc(PANEL, "entity", "Entity") +AccessorFunc(PANEL, "bClosing", "IsClosing", FORCE_BOOL) +AccessorFunc(PANEL, "desiredHeight", "DesiredHeight", FORCE_NUMBER) + +function PANEL:Init() + if (IsValid(ix.menu.panel)) then + self:Remove() + return + end + + -- close entity tooltip if it's open + if (IsValid(ix.gui.entityInfo)) then + ix.gui.entityInfo:Remove() + end + + ix.menu.panel = self + + self:SetSize(ScrW(), ScrH()) + self:SetPos(0, 0) + + self.list = self:Add("ixEntityMenuList") + self.list:SetPaintedManually(true) + + self.desiredHeight = 0 + self.blur = 0 + self.alpha = 1 + self.bClosing = false + self.lastPosition = vector_origin + + self:CreateAnimation(animationTime, { + target = {blur = 1}, + easing = "outQuint" + }) + + self:MakePopup() +end + +function PANEL:SetOptions(options) + for k, v in pairs(options) do + self.list:AddOption(k, v) + end + + self.list:SizeToContents() + self.list:Center() +end + +function PANEL:GetApproximateScreenHeight(entity, distanceSqr) + return IsValid(entity) and + (entity:BoundingRadius() * (entity:IsPlayer() and 1.5 or 1) or 0) / math.sqrt(distanceSqr) * self:GetTall() or 0 +end + +function PANEL:Think() + local entity = self.entity + local distance = 0 + + if (IsValid(entity)) then + local position = entity:GetPos() + distance = LocalPlayer():GetShootPos():DistToSqr(position) + + if (distance > 65536) then + self:Remove() + return + end + + self.lastPosition = position + end + + self.desiredHeight = math.max(self.list:GetTall() + padding * 2, self:GetApproximateScreenHeight(entity, distance)) +end + +function PANEL:Paint(width, height) -- luacheck: ignore 312 + local selfHalf = self:GetTall() * 0.5 + local entity = self.entity + + height = self.desiredHeight + padding * 2 + width = self.blur * width + + local y = selfHalf - height * 0.5 + + DisableClipping(true) -- for cheap blur + render.SetScissorRect(0, y, width, y + height, true) + if (IsValid(entity)) then + cam.Start3D() + ix.util.ResetStencilValues() + render.SetStencilEnable(true) + cam.IgnoreZ(true) + render.SetStencilWriteMask(29) + render.SetStencilTestMask(29) + render.SetStencilReferenceValue(29) + + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + + entity:DrawModel() + + render.SetStencilCompareFunction(STENCIL_NOTEQUAL) + render.SetStencilPassOperation(STENCIL_KEEP) + + cam.Start2D() + ix.util.DrawBlur(self, 10) + cam.End2D() + cam.IgnoreZ(false) + render.SetStencilEnable(false) + cam.End3D() + else + ix.util.DrawBlur(self, 10) + end + render.SetScissorRect(0, 0, 0, 0, false) + DisableClipping(false) + + -- scissor again because 3d rendering messes with the clipping apparently? + render.SetScissorRect(0, y, width, y + height, true) + surface.SetDrawColor(ix.config.Get("color")) + surface.DrawRect(ScrW() * 0.5, y + padding, 1, height - padding * 2) + + self.list:PaintManual() + render.SetScissorRect(0, 0, 0, 0, false) +end + +function PANEL:GetOverviewInfo(origin, angles) + local entity = self.entity + + if (IsValid(entity)) then + local radius = entity:BoundingRadius() * (entity:IsPlayer() and 0.5 or 1) + local center = entity:LocalToWorld(entity:OBBCenter()) + LocalPlayer():GetRight() * radius + + return LerpAngle(self.bClosing and self.alpha or self.blur, angles, (center - origin):Angle()) + end + + return angles +end + +function PANEL:OnMousePressed(code) + if (code == MOUSE_LEFT) then + self:Remove() + end +end + +function PANEL:Remove() + if (self.bClosing) then + return + end + + self.bClosing = true + + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + gui.EnableScreenClicker(false) + + self:CreateAnimation(animationTime * 0.5, { + target = {alpha = 0}, + index = 2, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(panel.alpha * 255) + end, + + OnComplete = function(animation, panel) + ix.menu.panel = nil + BaseClass.Remove(self) + end + }) +end + +vgui.Register("ixEntityMenu", PANEL, "EditablePanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_generic.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_generic.lua new file mode 100644 index 0000000..34c82df --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_generic.lua @@ -0,0 +1,923 @@ + +-- generic panels that are applicable anywhere + +-- used for prominent text entries +DEFINE_BASECLASS("DTextEntry") +local PANEL = {} + +AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") + +function PANEL:Init() + self:SetPaintBackground(false) + self:SetTextColor(color_white) + + self.backgroundColor = Color(255, 255, 255, 25) +end + +function PANEL:SetFont(font) + surface.SetFont(font) + local _, height = surface.GetTextSize("W@") + + self:SetTall(height) + BaseClass.SetFont(self, font) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor) + BaseClass.Paint(self, width, height) +end + +vgui.Register("ixTextEntry", PANEL, "DTextEntry") + +-- similar to a frame, but is mainly used for grouping panels together in a list +PANEL = {} + +AccessorFunc(PANEL, "text", "Text", FORCE_STRING) +AccessorFunc(PANEL, "color", "Color") + +function PANEL:Init() + self.text = "" + self.paddingTop = 32 + + local skin = self:GetSkin() + + if (skin and skin.fontCategoryBlur) then + surface.SetFont(skin.fontCategoryBlur) + self.paddingTop = select(2, surface.GetTextSize("W@")) + 6 + end + + self:DockPadding(1, self.paddingTop, 1, 1) +end + +function PANEL:SizeToContents() + local height = self.paddingTop + 1 + + for _, v in ipairs(self:GetChildren()) do + if (IsValid(v) and v:IsVisible()) then + local _, top, _, bottom = v:GetDockMargin() + + height = height + v:GetTall() + top + bottom + end + end + + self:SetTall(height) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintCategoryPanel", self, self.text, self.color) +end + +vgui.Register("ixCategoryPanel", PANEL, "EditablePanel") + +-- segmented progress bar +PANEL = {} + +AccessorFunc(PANEL, "font", "Font", FORCE_STRING) +AccessorFunc(PANEL, "barColor", "BarColor") +AccessorFunc(PANEL, "textColor", "TextColor") +AccessorFunc(PANEL, "progress", "Progress", FORCE_NUMBER) +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) +AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) +AccessorFunc(PANEL, "easingType", "EasingType", FORCE_STRING) + +function PANEL:Init() + self.segments = {} + self.padding = ScrH() * 0.01 + self.fraction = 0 + self.animationTime = 0.5 + self.easingType = "outQuint" + self.progress = 0 +end + +function PANEL:AddSegment(text) + local id = #self.segments + 1 + + if (text:sub(1, 1) == "@") then + text = L(text:sub(2)) + end + + self.segments[id] = text + return id +end + +function PANEL:AddSegments(...) + local segments = {...} + + for i = 1, #segments do + self:AddSegment(segments[i]) + end +end + +function PANEL:GetSegments() + return self.segments +end + +function PANEL:SetProgress(segment) + self.progress = math.Clamp(segment, 0, #self.segments) + + self:CreateAnimation(self.animationTime, { + target = {fraction = self.progress / #self.segments}, + easing = self.easingType + }) +end + +function PANEL:IncrementProgress(amount) + self:SetProgress(self.progress + (amount or 1)) +end + +function PANEL:DecrementProgress(amount) + self:SetProgress(self.progress - (amount or 1)) +end + +function PANEL:GetFraction() + return self.fraction +end + +function PANEL:SizeToContents() + self:SetTall(draw.GetFontHeight(self.font or self:GetSkin().fontSegmentedProgress) + self.padding) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintSegmentedProgressBackground", self, width, height) + + if (#self.segments > 0) then + derma.SkinFunc("PaintSegmentedProgress", self, width, height) + end +end + +vgui.Register("ixSegmentedProgress", PANEL, "Panel") + +-- list of labelled information +PANEL = {} + +AccessorFunc(PANEL, "labelColor", "LabelColor") +AccessorFunc(PANEL, "textColor", "TextColor") +AccessorFunc(PANEL, "list", "List") +AccessorFunc(PANEL, "minWidth", "MinimumWidth", FORCE_NUMBER) + +function PANEL:Init() + self.label = self:Add("DLabel") + self.label:SetFont("ixMediumFont") + self.label:SetExpensiveShadow(1) + self.label:SetTextColor(color_white) + self.label:SetText("Label") + self.label:SetContentAlignment(5) + self.label:Dock(LEFT) + self.label:DockMargin(0, 0, 4, 0) + self.label:SizeToContents() + self.label.Paint = function(this, width, height) + derma.SkinFunc("PaintListRow", this, width, height) + end + + self.text = self:Add("DLabel") + self.text:SetFont("ixMediumLightFont") + self.text:SetTextColor(color_white) + self.text:SetText("Text") + self.text:SetTextInset(8, 0) + self.text:Dock(FILL) + self.text:DockMargin(4, 0, 0, 0) + self.text:SizeToContents() + self.text.Paint = function(this, width, height) + derma.SkinFunc("PaintListRow", this, width, height) + end + + self:DockMargin(0, 0, 0, 8) + + self.list = {} + self.minWidth = 100 +end + +function PANEL:SetRightPanel(panel) + self.text:Remove() + + self.text = self:Add(panel) + self.text:Dock(FILL) + self.text:DockMargin(8, 4, 4, 4) + self.text:SizeToContents() +end + +function PANEL:SetList(list, bNoAdd) + if (!bNoAdd) then + list[#list + 1] = self + end + + self.list = list +end + +function PANEL:UpdateLabelWidths() + local maxWidth = self.label:GetWide() + + for i = 1, #self.list do + maxWidth = math.max(self.list[i]:GetLabelWidth(), maxWidth) + end + + maxWidth = math.max(self.minWidth, maxWidth) + + for i = 1, #self.list do + self.list[i]:SetLabelWidth(maxWidth) + end +end + +function PANEL:SetLabelColor(color) + self.label:SetTextColor(color) +end + +function PANEL:SetTextColor(color) + self.text:SetTextColor(color) +end + +function PANEL:SetLabelText(text) + self.label:SetText(text) + self.label:SizeToContents() + + self:UpdateLabelWidths() +end + +function PANEL:SetText(text) + self.text:SetText(text) + self.text:SizeToContents() +end + +function PANEL:SetLabelWidth(width) + self.label:SetWide(width) +end + +function PANEL:GetLabelWidth(bWithoutMargin) + if (!bWithoutMargin) then + return self.label:GetWide() + end + + local left, _, right, _ = self.label:GetDockMargin() + return self.label:GetWide() + left + right +end + +function PANEL:SizeToContents() + self:SetTall(math.max(self.label:GetTall(), self.text:GetTall()) + 8) +end + +vgui.Register("ixListRow", PANEL, "Panel") + +-- alternative checkbox +DEFINE_BASECLASS("EditablePanel") +PANEL = {} + +AccessorFunc(PANEL, "enabledText", "EnabledText", FORCE_STRING) +AccessorFunc(PANEL, "disabledText", "DisabledText", FORCE_STRING) +AccessorFunc(PANEL, "font", "Font", FORCE_STRING) +AccessorFunc(PANEL, "bChecked", "Checked", FORCE_BOOL) +AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) +AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER) + +PANEL.GetValue = PANEL.GetChecked + +function PANEL:Init() + self:SetMouseInputEnabled(true) + self:SetCursor("hand") + + self.enabledText = L("yes"):utf8upper() + self.disabledText = L("no"):utf8upper() + self.font = "ixMenuButtonFont" + self.animationTime = 0.5 + self.bChecked = false + self.labelPadding = 8 + self.animationOffset = 0 + + self:SizeToContents() +end + +function PANEL:SizeToContents() + BaseClass.SizeToContents(self) + + surface.SetFont(self.font) + self:SetWide(math.max(surface.GetTextSize(self.enabledText), surface.GetTextSize(self.disabledText)) + self.labelPadding) +end + +-- can be overidden to change audio params +function PANEL:GetAudioFeedback() + return "weapons/ar2/ar2_empty.wav", 75, self.bChecked and 150 or 125, 0.25 +end + +function PANEL:EmitFeedback() + LocalPlayer():EmitSound(self:GetAudioFeedback()) +end + +function PANEL:SetChecked(bChecked, bInstant) + self.bChecked = tobool(bChecked) + + self:CreateAnimation(bInstant and 0 or self.animationTime, { + index = 1, + target = { + animationOffset = bChecked and 1 or 0 + }, + easing = "outElastic" + }) + + if (!bInstant) then + self:EmitFeedback() + end +end + +function PANEL:OnMousePressed(code) + if (code == MOUSE_LEFT) then + self:SetChecked(!self.bChecked) + self:DoClick() + end +end + +function PANEL:DoClick() +end + +function PANEL:Paint(width, height) + surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) + surface.DrawRect(0, 0, width, height) + + local offset = self.animationOffset + surface.SetFont(self.font) + + local text = self.disabledText + local textWidth, textHeight = surface.GetTextSize(text) + local y = offset * -textHeight + + surface.SetTextColor(250, 60, 60, 255) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5) + surface.DrawText(text) + + text = self.enabledText + y = y + textHeight + textWidth, textHeight = surface.GetTextSize(text) + + surface.SetTextColor(30, 250, 30, 255) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5) + surface.DrawText(text) +end + +vgui.Register("ixCheckBox", PANEL, "EditablePanel") + +-- alternative num slider +PANEL = {} + +AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER) + +function PANEL:Init() + self.labelPadding = 8 + + surface.SetFont("ixMenuButtonFont") + local totalWidth = surface.GetTextSize("999") -- start off with 3 digit width + + self.label = self:Add("DLabel") + self.label:Dock(RIGHT) + self.label:SetWide(totalWidth + self.labelPadding) + self.label:SetContentAlignment(5) + self.label:SetFont("ixMenuButtonFont") + self.label.Paint = function(panel, width, height) + surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) + surface.DrawRect(0, 0, width, height) + end + self.label.SizeToContents = function(panel) + surface.SetFont(panel:GetFont()) + local textWidth = surface.GetTextSize(panel:GetText()) + + if (textWidth > totalWidth) then + panel:SetWide(textWidth + self.labelPadding) + elseif (panel:GetWide() > totalWidth + self.labelPadding) then + panel:SetWide(totalWidth + self.labelPadding) + end + end + + self.slider = self:Add("ixSlider") + self.slider:Dock(FILL) + self.slider:DockMargin(0, 0, 4, 0) + self.slider.OnValueChanged = function(panel) + self:OnValueChanged() + end + self.slider.OnValueUpdated = function(panel) + self.label:SetText(string.format("%0." .. tostring(panel:GetDecimals()) .. "f", tostring(panel:GetValue()))) + self.label:SizeToContents() + + self:OnValueUpdated() + end +end + +function PANEL:GetLabel() + return self.label +end + +function PANEL:GetSlider() + return self.slider +end + +function PANEL:SetValue(value, bNoNotify) + value = tonumber(value) or self.slider:GetMin() + + self.slider:SetValue(value, bNoNotify) + self.label:SetText(string.format("%0." .. tostring(self:GetDecimals()) .. "f", tostring(self.slider:GetValue()))) + self.label:SizeToContents() +end + +function PANEL:GetValue() + return self.slider:GetValue() +end + +function PANEL:GetFraction() + return self.slider:GetFraction() +end + +function PANEL:GetVisualFraction() + return self.slider:GetVisualFraction() +end + +function PANEL:SetMin(value) + self.slider:SetMin(value) +end + +function PANEL:SetMax(value) + self.slider:SetMax(value) +end + +function PANEL:GetMin() + return self.slider:GetMin() +end + +function PANEL:GetMax() + return self.slider:GetMax() +end + +function PANEL:SetDecimals(value) + self.slider:SetDecimals(value) +end + +function PANEL:GetDecimals() + return self.slider:GetDecimals() +end + +-- called when changed by user +function PANEL:OnValueChanged() +end + +-- called when changed while dragging bar +function PANEL:OnValueUpdated() +end + +vgui.Register("ixNumSlider", PANEL, "Panel") + +-- alternative slider +PANEL = {} + +AccessorFunc(PANEL, "bDragging", "Dragging", FORCE_BOOL) +AccessorFunc(PANEL, "min", "Min", FORCE_NUMBER) +AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER) +AccessorFunc(PANEL, "decimals", "Decimals", FORCE_NUMBER) + +function PANEL:Init() + self.min = 0 + self.max = 10 + self.value = 0 + self.visualValue = 0 + self.decimals = 0 + + self:SetCursor("hand") +end + +function PANEL:SetValue(value, bNoNotify) + self.value = math.Clamp(math.Round(tonumber(value) or self.min, self.decimals), self.min, self.max) + self:ValueUpdated(bNoNotify) + + if (!bNoNotify) then + self:OnValueChanged() + end +end + +function PANEL:GetValue() + return self.value +end + +function PANEL:GetFraction() + return math.Remap(self.value, self.min, self.max, 0, 1) +end + +function PANEL:GetVisualFraction() + return math.Remap(self.visualValue, self.min, self.max, 0, 1) +end + +function PANEL:OnMousePressed(key) + if (key == MOUSE_LEFT) then + self.bDragging = true + self:MouseCapture(true) + + self:OnCursorMoved(self:CursorPos()) + end +end + +function PANEL:OnMouseReleased(key) + if (self.bDragging) then + self:OnValueChanged() + end + + self.bDragging = false + self:MouseCapture(false) +end + +function PANEL:OnCursorMoved(x, y) + if (!self.bDragging) then + return + end + + x = math.Clamp(x, 0, self:GetWide()) + local oldValue = self.value + + self.value = math.Clamp(math.Round( + math.Remap(x / self:GetWide(), 0, 1, self.min, self.max), self.decimals + ), self.min, self.max) + + self:CreateAnimation(0.5, { + index = 1, + target = {visualValue = self.value}, + easing = "outQuint" + }) + + if (self.value != oldValue) then + self:ValueUpdated() + end +end + +function PANEL:OnValueChanged() +end + +function PANEL:ValueUpdated(bNoNotify) + self:CreateAnimation(bNoNotify and 0 or 0.5, { + index = 1, + target = {visualValue = self.value}, + easing = "outQuint" + }) + + if (!bNoNotify) then + self:OnValueUpdated() + end +end + +function PANEL:OnValueUpdated() +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintHelixSlider", self, width, height) +end + +vgui.Register("ixSlider", PANEL, "EditablePanel") + +--- Alternative to DLabel that adds extra functionality. +-- This panel is meant for drawing single-line text. It can add extra kerning (spaces between letters), and it can forcefully +-- scale the text down to fit the current width, without cutting off any letters. Text scaling is most useful when docking this +-- this panel without knowing what the width could be. For example, text scaling is used for the character name in the character +-- status menu. +-- local label = vgui.Create("ixLabel") +-- label:SetText("hello world") +-- label:SetFont("ixMenuButtonHugeFont") +-- label:SetContentAlignment(5) +-- label:SetTextColor(Color(255, 255, 255, 255)) +-- label:SetBackgroundColor(Color(200, 30, 30, 255)) +-- label:SetPadding(8) +-- label:SetScaleWidth(true) +-- label:SizeToContents() +-- @panel ixLabel +PANEL = {} + +--- Sets the text for this label to display. +-- @realm client +-- @string text Text to display +-- @function SetText + +--- Returns the current text for this panel. +-- @realm client +-- @treturn string Current text +-- @function GetText +AccessorFunc(PANEL, "text", "Text", FORCE_STRING) + +--- Sets the color of the text to use when drawing. +-- @realm client +-- @color color New color to use +-- @function SetTextColor + +--- Returns the current text color for this panel. +-- @realm client +-- @treturn color Current text color +-- @function GetTextColor +AccessorFunc(PANEL, "color", "TextColor") + +--- Sets the color of the background to draw behind the text. +-- @realm client +-- @color color New color to use +-- @function SetBackgroundColor + +--- Returns the current background color for this panel. +-- @realm client +-- @treturn color Current background color +-- @function GetBackgroundColor +AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") + +--- Sets the spacing between each character of the text in pixels. Set to `0` to disable. Kerning is disabled by default. +-- @realm client +-- @number kerning How far apart to draw each letter +-- @function SetKerning + +--- Returns the current kerning for this panel. +-- @realm client +-- @treturn number Current kerning +-- @function GetKerning +AccessorFunc(PANEL, "kerning", "Kerning", FORCE_NUMBER) + +--- Sets the font used to draw the text. +-- @realm client +-- @string font Name of the font to use +-- @function SetFont + +--- Returns the current font for this panel. +-- @realm client +-- @treturn string Name of current font +-- @function GetFont +AccessorFunc(PANEL, "font", "Font", FORCE_STRING) + +--- Changes how the text is aligned when drawing. Valid content alignment values include numbers `1` through `9`. Each number's +-- corresponding alignment is based on its position on a numpad. For example, `1` is bottom-left, `5` is centered, `9` is +-- top-right, etc. +-- @realm client +-- @number alignment Alignment to use +-- @function SetContentAlignment + +--- Returns the current content alignment for this panel. +-- @realm client +-- @treturn number Current content alignment +-- @function GetContentAlignment +AccessorFunc(PANEL, "contentAlignment", "ContentAlignment", FORCE_NUMBER) + +--- Whether or not to scale the width of the text down to fit the width of this panel, if needed. +-- @realm client +-- @bool bScale Whether or not to scale +-- @function SetScaleWidth + +--- Returns whether or not this panel will scale its text down to fit its width. +-- @realm client +-- @treturn bool Whether or not this panel will scale its text +-- @function GetScaleWidth +AccessorFunc(PANEL, "bScaleWidth", "ScaleWidth", FORCE_BOOL) + +--- How much spacing to use around the text when its drawn. This uses uniform padding on the top, left, right, and bottom of +-- this panel. +-- @realm client +-- @number padding Padding to use +-- @function SetPadding + +--- Returns how much padding this panel has around its text. +-- @realm client +-- @treturn number Current padding +-- @function GetPadding +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) + +function PANEL:Init() + self.text = "" + self.color = color_white + self.backgroundColor = Color(255, 255, 255, 0) + self.kerning = 0 + self.font = "DermaDefault" + self.scaledFont = "DermaDefault" + self.contentAlignment = 5 + self.bScaleWidth = false + self.padding = 0 + + self.shadowDistance = 0 + self.bCurrentlyScaling = false +end + +function PANEL:SetText(text) + self.text = tostring(text) +end + +function PANEL:SetFont(font) + self.font = font + self.scaledFont = font +end + +--- Sets the drop shadow to draw behind the text. +-- @realm client +-- @number distance How far away to draw the shadow in pixels. Set to `0` to disable +-- @color[opt] color Color of the shadow. Defaults to a dimmed version of the text color +function PANEL:SetDropShadow(distance, color) + self.shadowDistance = distance or 1 + self.shadowColor = color or ix.util.DimColor(self.color, 0.5) +end + +PANEL.SetExpensiveShadow = PANEL.SetDropShadow -- aliasing for easier conversion from DLabels + +--- Returns the X and Y location of the text taking into account the text alignment and padding. +-- @realm client +-- @internal +-- @number width Width of the panel +-- @number height Height of the panel +-- @number textWidth Width of the text +-- @number textHeight Height of the text +-- @treturn number X location to draw the text +-- @treturn number Y location to draw the text +function PANEL:CalculateAlignment(width, height, textWidth, textHeight) + local alignment = self.contentAlignment + local x, y + + if (self.bCurrentlyScaling) then + -- if the text is currently being scaled down, then it's always centered + x = width * 0.5 - textWidth * 0.5 + else + -- x alignment + if (alignment == 7 or alignment == 4 or alignment == 1) then + -- left + x = self.padding + elseif (alignment == 8 or alignment == 5 or alignment == 2) then + -- center + x = width * 0.5 - textWidth * 0.5 + elseif (alignment == 9 or alignment == 6 or alignment == 3) then + x = width - textWidth - self.padding + end + end + + -- y alignment + if (alignment <= 3) then + -- bottom + y = height - textHeight - self.padding + elseif (alignment <= 6) then + -- center + y = height * 0.5 - textHeight * 0.5 + else + -- top + y = self.padding + end + + return x, y +end + +--- Draws the current text with the current kerning. +-- @realm client +-- @internal +-- @number width Width of the panel +-- @number height Height of the panel +function PANEL:DrawKernedText(width, height) + local contentWidth, contentHeight = self:GetContentSize() + local x, y = self:CalculateAlignment(width, height, contentWidth, contentHeight) + + for i = 1, self.text:utf8len() do + local character = self.text:utf8sub(i, i) + local textWidth, _ = surface.GetTextSize(character) + local kerning = i == 1 and 0 or self.kerning + local shadowDistance = self.shadowDistance + + -- shadow + if (self.shadowDistance > 0) then + surface.SetTextColor(self.shadowColor) + surface.SetTextPos(x + kerning + shadowDistance, y + shadowDistance) + surface.DrawText(character) + end + + -- character + surface.SetTextColor(self.color) + surface.SetTextPos(x + kerning, y) + surface.DrawText(character) + + x = x + textWidth + kerning + end +end + +--- Draws the current text. +-- @realm client +-- @internal +-- @number width Width of the panel +-- @number height Height of the panel +function PANEL:DrawText(width, height) + local textWidth, textHeight = surface.GetTextSize(self.text) + local x, y = self:CalculateAlignment(width, height, textWidth, textHeight) + + -- shadow + if (self.shadowDistance > 0) then + surface.SetTextColor(self.shadowColor) + surface.SetTextPos(x + self.shadowDistance, y + self.shadowDistance) + surface.DrawText(self.text) + end + + -- text + surface.SetTextColor(self.color) + surface.SetTextPos(x, y) + surface.DrawText(self.text) +end + +function PANEL:Paint(width, height) + surface.SetFont(self.font) + surface.SetDrawColor(self.backgroundColor) + surface.DrawRect(0, 0, width, height) + + if (self.bScaleWidth) then + local contentWidth, contentHeight = self:GetContentSize() + + if (contentWidth > (width - self.padding * 2)) then + local x, y = self:LocalToScreen(self:GetPos()) + local scale = width / (contentWidth + self.padding * 2) + local translation = Vector(x + width * 0.5, y - contentHeight * 0.5 + self.padding, 0) + local matrix = Matrix() + + matrix:Translate(translation) + matrix:Scale(Vector(scale, scale, 0)) + matrix:Translate(-translation) + + cam.PushModelMatrix(matrix, true) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + DisableClipping(true) + + self.bCurrentlyScaling = true + end + end + + if (self.kerning > 0) then + self:DrawKernedText(width, height) + else + self:DrawText(width, height) + end + + if (self.bCurrentlyScaling) then + DisableClipping(false) + render.PopFilterMin() + cam.PopModelMatrix() + + self.bCurrentlyScaling = false + end +end + +--- Returns the size of the text, taking into account the current kerning. +-- @realm client +-- @bool[opt=false] bCalculate Whether or not to recalculate the content size instead of using the cached copy +-- @treturn number Width of the text +-- @treturn number Height of the text +function PANEL:GetContentSize(bCalculate) + if (bCalculate or !self.contentSize) then + surface.SetFont(self.font) + + if (self.kerning > 0) then + local width = 0 + + for i = 1, self.text:utf8len() do + local textWidth, _ = surface.GetTextSize(self.text:utf8sub(i, i)) + width = width + textWidth + self.kerning + end + + self.contentSize = {width, draw.GetFontHeight(self.font)} + else + self.contentSize = {surface.GetTextSize(self.text)} + end + end + + return self.contentSize[1], self.contentSize[2] +end + +--- Sets the size of the panel to fit the content size with the current padding. The content size is recalculated when this +-- method is called. +-- @realm client +function PANEL:SizeToContents() + local contentWidth, contentHeight = self:GetContentSize(true) + + self:SetSize(contentWidth + self.padding * 2, contentHeight + self.padding * 2) +end + +vgui.Register("ixLabel", PANEL, "Panel") + +-- text entry with icon +DEFINE_BASECLASS("ixTextEntry") +PANEL = {} + +AccessorFunc(PANEL, "icon", "Icon", FORCE_STRING) +AccessorFunc(PANEL, "iconColor", "IconColor") + +function PANEL:Init() + self:SetIcon("V") + self:SetFont("ixSmallTitleFont") + + self.iconColor = Color(200, 200, 200, 160) +end + +function PANEL:SetIcon(newIcon) + surface.SetFont("ixSmallTitleIcons") + + self.iconWidth, self.iconHeight = surface.GetTextSize(newIcon) + self.icon = newIcon + + self:DockMargin(self.iconWidth + 4, 0, 0, 8) +end + +function PANEL:Paint(width, height) + BaseClass.Paint(self, width, height) + + -- there's no inset for text entries so we'll have to get creative + DisableClipping(true) + surface.SetDrawColor(self:GetBackgroundColor()) + surface.DrawRect(-self.iconWidth - 4, 0, self.iconWidth + 4, height) + + surface.SetFont("ixSmallTitleIcons") + surface.SetTextColor(self.iconColor) + surface.SetTextPos(-self.iconWidth - 2, 0) + surface.DrawText(self:GetIcon()) + DisableClipping(false) +end + +vgui.Register("ixIconTextEntry", PANEL, "ixTextEntry") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_help.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_help.lua new file mode 100644 index 0000000..9e2680a --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_help.lua @@ -0,0 +1,369 @@ + +local backgroundColor = Color(0, 0, 0, 66) + +local PANEL = {} + +AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER) + +function PANEL:Init() + self:SetWide(180) + self:Dock(LEFT) + + self.maxWidth = ScrW() * 0.2 +end + +function PANEL:Paint(width, height) + surface.SetDrawColor(backgroundColor) + surface.DrawRect(0, 0, width, height) +end + +function PANEL:SizeToContents() + local width = 0 + + for _, v in ipairs(self:GetChildren()) do + width = math.max(width, v:GetWide()) + end + + self:SetSize(math.max(32, math.min(width, self.maxWidth)), self:GetParent():GetTall()) +end + +vgui.Register("ixHelpMenuCategories", PANEL, "EditablePanel") + +-- help menu +PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + + self.categories = {} + self.categorySubpanels = {} + self.categoryPanel = self:Add("ixHelpMenuCategories") + + self.canvasPanel = self:Add("EditablePanel") + self.canvasPanel:Dock(FILL) + + self.idlePanel = self.canvasPanel:Add("Panel") + self.idlePanel:Dock(FILL) + self.idlePanel:DockMargin(8, 0, 0, 0) + self.idlePanel.Paint = function(_, width, height) + surface.SetDrawColor(backgroundColor) + surface.DrawRect(0, 0, width, height) + + derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25) + + surface.SetFont("ixIntroSubtitleFont") + local text = L("helix"):lower() + local textWidth, textHeight = surface.GetTextSize(text) + + surface.SetTextColor(color_white) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.75) + surface.DrawText(text) + + surface.SetFont("ixMediumLightFont") + text = L("helpIdle") + local infoWidth, _ = surface.GetTextSize(text) + + surface.SetTextColor(color_white) + surface.SetTextPos(width * 0.5 - infoWidth * 0.5, height * 0.5 + textHeight * 0.25) + surface.DrawText(text) + end + + local categories = {} + hook.Run("PopulateHelpMenu", categories) + + for k, v in SortedPairs(categories) do + if (!isstring(k)) then + ErrorNoHalt("expected string for help menu key\n") + continue + elseif (!isfunction(v)) then + ErrorNoHalt(string.format("expected function for help menu entry '%s'\n", k)) + continue + end + + self:AddCategory(k) + self.categories[k] = v + end + + self.categoryPanel:SizeToContents() + + if (ix.gui.lastHelpMenuTab) then + self:OnCategorySelected(ix.gui.lastHelpMenuTab) + end +end + +function PANEL:AddCategory(name) + local button = self.categoryPanel:Add("ixMenuButton") + button:SetText(L(name)) + button:SizeToContents() + -- @todo don't hardcode this but it's the only panel that needs docking at the bottom so it'll do for now + button:Dock(name == "credits" and BOTTOM or TOP) + button.DoClick = function() + self:OnCategorySelected(name) + end + + local panel = self.canvasPanel:Add("DScrollPanel") + panel:SetVisible(false) + panel:Dock(FILL) + panel:DockMargin(8, 0, 0, 0) + panel:GetCanvas():DockPadding(8, 8, 8, 8) + + panel.Paint = function(_, width, height) + surface.SetDrawColor(backgroundColor) + surface.DrawRect(0, 0, width, height) + end + + -- reverts functionality back to a standard panel in the case that a category will manage its own scrolling + panel.DisableScrolling = function() + panel:GetCanvas():SetVisible(false) + panel:GetVBar():SetVisible(false) + panel.OnChildAdded = function() end + end + + self.categorySubpanels[name] = panel +end + +function PANEL:OnCategorySelected(name) + local panel = self.categorySubpanels[name] + + if (!IsValid(panel)) then + return + end + + if (!panel.bPopulated) then + self.categories[name](panel) + panel.bPopulated = true + end + + if (IsValid(self.activeCategory)) then + self.activeCategory:SetVisible(false) + end + + panel:SetVisible(true) + self.idlePanel:SetVisible(false) + + self.activeCategory = panel + ix.gui.lastHelpMenuTab = name +end + +vgui.Register("ixHelpMenu", PANEL, "EditablePanel") + +local function DrawHelix(width, height, color) -- luacheck: ignore 211 + local segments = 76 + local radius = math.min(width, height) * 0.375 + + surface.SetTexture(-1) + + for i = 1, math.ceil(segments) do + local angle = math.rad((i / segments) * -360) + local x = width * 0.5 + math.sin(angle + math.pi * 2) * radius + local y = height * 0.5 + math.cos(angle + math.pi * 2) * radius + local barOffset = math.sin(SysTime() + i * 0.5) + local barHeight = barOffset * radius * 0.25 + + if (barOffset > 0) then + surface.SetDrawColor(color) + else + surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) + end + + surface.DrawTexturedRectRotated(x, y, 4, barHeight, math.deg(angle)) + end +end + +hook.Add("CreateMenuButtons", "ixHelpMenu", function(tabs) + tabs["help"] = function(container) + container:Add("ixHelpMenu") + end +end) + +hook.Add("PopulateHelpMenu", "ixHelpMenu", function(tabs) + tabs["commands"] = function(container) + -- info text + local info = container:Add("DLabel") + info:SetFont("ixSmallFont") + info:SetText(L("helpCommands")) + info:SetContentAlignment(5) + info:SetTextColor(color_white) + info:SetExpensiveShadow(1, color_black) + info:Dock(TOP) + info:DockMargin(0, 0, 0, 8) + info:SizeToContents() + info:SetTall(info:GetTall() + 16) + + info.Paint = function(_, width, height) + surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160)) + surface.DrawRect(0, 0, width, height) + end + + -- commands + for uniqueID, command in SortedPairs(ix.command.list) do + if (command.OnCheckAccess and !command:OnCheckAccess(LocalPlayer())) then + continue + end + + local bIsAlias = false + local aliasText = "" + + -- we want to show aliases in the same entry for better readability + if (command.alias) then + local alias = istable(command.alias) and command.alias or {command.alias} + + for _, v in ipairs(alias) do + if (v:lower() == uniqueID) then + bIsAlias = true + break + end + + aliasText = aliasText .. ", /" .. v + end + + if (bIsAlias) then + continue + end + end + + -- command name + local title = container:Add("DLabel") + title:SetFont("ixMediumLightFont") + title:SetText("/" .. command.name .. aliasText) + title:Dock(TOP) + title:SetTextColor(ix.config.Get("color")) + title:SetExpensiveShadow(1, color_black) + title:SizeToContents() + + -- syntax + local syntaxText = command.syntax + local syntax + + if (syntaxText != "" and syntaxText != "[none]") then + syntax = container:Add("DLabel") + syntax:SetFont("ixMediumLightFont") + syntax:SetText(syntaxText) + syntax:Dock(TOP) + syntax:SetTextColor(color_white) + syntax:SetExpensiveShadow(1, color_black) + syntax:SetWrap(true) + syntax:SetAutoStretchVertical(true) + syntax:SizeToContents() + end + + -- description + local descriptionText = command:GetDescription() + + if (descriptionText != "") then + local description = container:Add("DLabel") + description:SetFont("ixSmallFont") + description:SetText(descriptionText) + description:Dock(TOP) + description:SetTextColor(color_white) + description:SetExpensiveShadow(1, color_black) + description:SetWrap(true) + description:SetAutoStretchVertical(true) + description:SizeToContents() + description:DockMargin(0, 0, 0, 8) + elseif (syntax) then + syntax:DockMargin(0, 0, 0, 8) + else + title:DockMargin(0, 0, 0, 8) + end + end + end + + tabs["flags"] = function(container) + -- info text + local info = container:Add("DLabel") + info:SetFont("ixSmallFont") + info:SetText(L("helpFlags")) + info:SetContentAlignment(5) + info:SetTextColor(color_white) + info:SetExpensiveShadow(1, color_black) + info:Dock(TOP) + info:DockMargin(0, 0, 0, 8) + info:SizeToContents() + info:SetTall(info:GetTall() + 16) + + info.Paint = function(_, width, height) + surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160)) + surface.DrawRect(0, 0, width, height) + end + + -- flags + for k, v in SortedPairs(ix.flag.list) do + local background = ColorAlpha( + LocalPlayer():GetCharacter():HasFlags(k) and derma.GetColor("Success", info) or derma.GetColor("Error", info), 88 + ) + + local panel = container:Add("Panel") + panel:Dock(TOP) + panel:DockMargin(0, 0, 0, 8) + panel:DockPadding(4, 4, 4, 4) + panel.Paint = function(_, width, height) + derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, background) + end + + local flag = panel:Add("DLabel") + flag:SetFont("ixMonoMediumFont") + flag:SetText(string.format("[%s]", k)) + flag:Dock(LEFT) + flag:SetTextColor(color_white) + flag:SetExpensiveShadow(1, color_black) + flag:SetTextInset(4, 0) + flag:SizeToContents() + flag:SetTall(flag:GetTall() + 8) + + local description = panel:Add("DLabel") + description:SetFont("ixMediumLightFont") + description:SetText(v.description) + description:Dock(FILL) + description:SetTextColor(color_white) + description:SetExpensiveShadow(1, color_black) + description:SetTextInset(8, 0) + description:SizeToContents() + description:SetTall(description:GetTall() + 8) + + panel:SizeToChildren(false, true) + end + end + + tabs["plugins"] = function(container) + for _, v in SortedPairsByMemberValue(ix.plugin.list, "name") do + -- name + local title = container:Add("DLabel") + title:SetFont("ixMediumLightFont") + title:SetText(v.name or "Unknown") + title:Dock(TOP) + title:SetTextColor(ix.config.Get("color")) + title:SetExpensiveShadow(1, color_black) + title:SizeToContents() + + -- author + local author = container:Add("DLabel") + author:SetFont("ixSmallFont") + author:SetText(string.format("%s: %s", L("author"), v.author)) + author:Dock(TOP) + author:SetTextColor(color_white) + author:SetExpensiveShadow(1, color_black) + author:SetWrap(true) + author:SetAutoStretchVertical(true) + author:SizeToContents() + + -- description + local descriptionText = v.description + + if (descriptionText != "") then + local description = container:Add("DLabel") + description:SetFont("ixSmallFont") + description:SetText(descriptionText) + description:Dock(TOP) + description:SetTextColor(color_white) + description:SetExpensiveShadow(1, color_black) + description:SetWrap(true) + description:SetAutoStretchVertical(true) + description:SizeToContents() + description:DockMargin(0, 0, 0, 8) + else + author:DockMargin(0, 0, 0, 8) + end + end + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_information.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_information.lua new file mode 100644 index 0000000..3b2fc05 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_information.lua @@ -0,0 +1,273 @@ + +local PANEL = {} + +function PANEL:Init() + local parent = self:GetParent() + + self:SetSize(parent:GetWide() * 0.6, parent:GetTall()) + self:Dock(RIGHT) + self:DockMargin(0, ScrH() * 0.05, 0, 0) + + self.VBar:SetWide(0) + + -- entry setup + local suppress = {} + hook.Run("CanCreateCharacterInfo", suppress) + + if (!suppress.time) then + local format = ix.option.Get("24hourTime", false) and "%A, %B %d, %Y. %H:%M" or "%A, %B %d, %Y. %I:%M %p" + + self.time = self:Add("DLabel") + self.time:SetFont("ixMediumFont") + self.time:SetTall(28) + self.time:SetContentAlignment(5) + self.time:Dock(TOP) + self.time:SetTextColor(color_white) + self.time:SetExpensiveShadow(1, Color(0, 0, 0, 150)) + self.time:DockMargin(0, 0, 0, 32) + self.time:SetText(ix.date.GetFormatted(format)) + self.time.Think = function(this) + if ((this.nextTime or 0) < CurTime()) then + this:SetText(ix.date.GetFormatted(format)) + this.nextTime = CurTime() + 0.5 + end + end + end + + if (!suppress.name) then + self.name = self:Add("ixLabel") + self.name:Dock(TOP) + self.name:DockMargin(0, 0, 0, 8) + self.name:SetFont("ixMenuButtonHugeFont") + self.name:SetContentAlignment(5) + self.name:SetTextColor(color_white) + self.name:SetPadding(8) + self.name:SetScaleWidth(true) + end + + if (!suppress.description) then + self.description = self:Add("DLabel") + self.description:Dock(TOP) + self.description:DockMargin(0, 0, 0, 8) + self.description:SetFont("ixMenuButtonFont") + self.description:SetTextColor(color_white) + self.description:SetContentAlignment(5) + self.description:SetMouseInputEnabled(true) + self.description:SetCursor("hand") + + self.description.Paint = function(this, width, height) + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(0, 0, width, height) + end + + self.description.OnMousePressed = function(this, code) + if (code == MOUSE_LEFT) then + ix.command.Send("CharDesc") + + if (IsValid(ix.gui.menu)) then + ix.gui.menu:Remove() + end + end + end + + self.description.SizeToContents = function(this) + if (this.bWrap) then + -- sizing contents after initial wrapping does weird things so we'll just ignore (lol) + return + end + + local width, height = this:GetContentSize() + + if (width > self:GetWide()) then + this:SetWide(self:GetWide()) + this:SetTextInset(16, 8) + this:SetWrap(true) + this:SizeToContentsY() + this:SetTall(this:GetTall() + 16) -- eh + + -- wrapping doesn't like middle alignment so we'll do top-center + self.description:SetContentAlignment(8) + this.bWrap = true + else + this:SetSize(width + 16, height + 16) + end + end + end + + if (!suppress.characterInfo) then + self.characterInfo = self:Add("Panel") + self.characterInfo.list = {} + self.characterInfo:Dock(TOP) -- no dock margin because this is handled by ixListRow + self.characterInfo.SizeToContents = function(this) + local height = 0 + + for _, v in ipairs(this:GetChildren()) do + if (IsValid(v) and v:IsVisible()) then + local _, top, _, bottom = v:GetDockMargin() + height = height + v:GetTall() + top + bottom + end + end + + this:SetTall(height) + end + + if (!suppress.faction) then + self.faction = self.characterInfo:Add("ixListRow") + self.faction:SetList(self.characterInfo.list) + self.faction:Dock(TOP) + end + + if (!suppress.class) then + self.class = self.characterInfo:Add("ixListRow") + self.class:SetList(self.characterInfo.list) + self.class:Dock(TOP) + end + + if (!suppress.money) then + self.money = self.characterInfo:Add("ixListRow") + self.money:SetList(self.characterInfo.list) + self.money:Dock(TOP) + self.money:SizeToContents() + end + + hook.Run("CreateCharacterInfo", self.characterInfo) + self.characterInfo:SizeToContents() + end + + -- no need to update since we aren't showing the attributes panel + if (!suppress.attributes) then + local character = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() + + if (character) then + self.attributes = self:Add("ixCategoryPanel") + self.attributes:SetText(L("attributes")) + self.attributes:Dock(TOP) + self.attributes:DockMargin(0, 0, 0, 8) + + local boost = character:GetBoosts() + local bFirst = true + + for k, v in SortedPairsByMemberValue(ix.attributes.list, "name") do + local attributeBoost = 0 + + if (boost[k]) then + for _, bValue in pairs(boost[k]) do + attributeBoost = attributeBoost + bValue + end + end + + local bar = self.attributes:Add("ixAttributeBar") + bar:Dock(TOP) + + if (!bFirst) then + bar:DockMargin(0, 3, 0, 0) + else + bFirst = false + end + + local value = character:GetAttribute(k, 0) + + if (attributeBoost) then + bar:SetValue(value - attributeBoost or 0) + else + bar:SetValue(value) + end + + local maximum = v.maxValue or ix.config.Get("maxAttributes", 100) + bar:SetMax(maximum) + bar:SetReadOnly() + bar:SetText(Format("%s [%.1f/%.1f] (%.1f%%)", L(v.name), value, maximum, value / maximum * 100)) + + if (attributeBoost) then + bar:SetBoost(attributeBoost) + end + end + + self.attributes:SizeToContents() + end + end + + hook.Run("CreateCharacterInfoCategory", self) +end + +function PANEL:Update(character) + if (!character) then + return + end + + local faction = ix.faction.indices[character:GetFaction()] + local class = ix.class.list[character:GetClass()] + + if (self.name) then + self.name:SetText(character:GetName()) + + if (faction) then + self.name.backgroundColor = ColorAlpha(faction.color, 150) or Color(0, 0, 0, 150) + end + + self.name:SizeToContents() + end + + if (self.description) then + self.description:SetText(character:GetDescription()) + self.description:SizeToContents() + end + + if (self.faction) then + self.faction:SetLabelText(L("faction")) + self.faction:SetText(L(faction.name)) + self.faction:SizeToContents() + end + + if (self.class) then + -- don't show class label if the class is the same name as the faction + if (class and class.name != faction.name) then + self.class:SetLabelText(L("class")) + self.class:SetText(L(class.name)) + self.class:SizeToContents() + else + self.class:SetVisible(false) + end + end + + if (self.money) then + self.money:SetLabelText(L("money")) + self.money:SetText(ix.currency.Get(character:GetMoney())) + self.money:SizeToContents() + end + + hook.Run("UpdateCharacterInfo", self.characterInfo, character) + + self.characterInfo:SizeToContents() + + hook.Run("UpdateCharacterInfoCategory", self, character) +end + +function PANEL:OnSubpanelRightClick() + properties.OpenEntityMenu(LocalPlayer()) +end + +vgui.Register("ixCharacterInfo", PANEL, "DScrollPanel") + +hook.Add("CreateMenuButtons", "ixCharInfo", function(tabs) + tabs["you"] = { + bHideBackground = true, + buttonColor = team.GetColor(LocalPlayer():Team()), + Create = function(info, container) + container.infoPanel = container:Add("ixCharacterInfo") + + container.OnMouseReleased = function(this, key) + if (key == MOUSE_RIGHT) then + this.infoPanel:OnSubpanelRightClick() + end + end + end, + OnSelected = function(info, container) + container.infoPanel:Update(LocalPlayer():GetCharacter()) + ix.gui.menu:SetCharacterOverview(true) + end, + OnDeselected = function(info, container) + ix.gui.menu:SetCharacterOverview(false) + end + } +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_intro.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_intro.lua new file mode 100644 index 0000000..7ec74fd --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_intro.lua @@ -0,0 +1,368 @@ + +local waveSegments = 32 +local helixSegments = 76 +local helixHeight = 64 +local backgroundColor = Color(115, 53, 142) +local dimColor = Color(165, 134, 179) + +DEFINE_BASECLASS("EditablePanel") + +local PANEL = {} + +function PANEL:Init() + if (IsValid(ix.gui.intro)) then + ix.gui.intro:Remove() + end + + ix.gui.intro = self + + self:SetSize(ScrW(), ScrH()) + self:SetPos(0, 0) + self:SetZPos(99999) + self:MakePopup() + + -- animation parameters + self.bBackground = true + self.volume = 1 + self.sunbeamOffset = 0 + self.textOne = 0 + self.textTwo = 0 + self.kickTarget = 0 + self.helix = 0 + self.helixAlpha = 0 + self.continueText = 0 + self.pulse = 0 + + self.waves = { + {1.1, 0}, + {1.1, math.pi}, + {1.1, math.pi * 1.6}, + {1.1, math.pi * 0.5} + } +end + +-- @todo h a c k +function PANEL:Think() + if (IsValid(LocalPlayer())) then + self:BeginIntro() + self.Think = nil + end +end + +function PANEL:BeginIntro() + -- something could have errored on startup and invalidated all options, so we'll be extra careful with setting the option + -- because if it errors here, the sound will play each tick and proceed to hurt ears + local bLoaded = false + + if (ix and ix.option and ix.option.Set) then + local bSuccess, _ = pcall(ix.option.Set, "showIntro", false) + bLoaded = bSuccess + end + + if (!bLoaded) then + self:Remove() + + if (ix and ix.gui and IsValid(ix.gui.characterMenu)) then + ix.gui.characterMenu:Remove() + end + + ErrorNoHalt( + "[Helix] Something has errored and prevented the framework from loading correctly - check your console for errors!\n") + + return + end + + self:MoveToFront() + self:RequestFocus() + + sound.PlayFile("sound/buttons/combine_button2.wav", "", function() + timer.Create("ixIntroStart", 2, 1, function() + sound.PlayFile("sound/helix/intro.mp3", "", function(channel, status, error) + if (IsValid(channel)) then + channel:SetVolume(self.volume) + self.channel = channel + end + + self:BeginAnimation() + end) + end) + end) +end + +function PANEL:AnimateWaves(target, bReverse) + for i = bReverse and #self.waves or 1, + bReverse and 1 or #self.waves, + bReverse and -1 or 1 do + + local animation = self:CreateAnimation(2, { + index = 20 + (bReverse and (#self.waves - i) or i), + bAutoFire = false, + target = { + waves = { + [i] = {target} + } + }, + easing = bReverse and "inQuart" or "outQuint" + }) + + timer.Simple((bReverse and (#self.waves - i) or i) * 0.1, function() + if (IsValid(self) and animation) then + animation:Fire() + end + end) + + -- return last animation that plays + if ((bReverse and i == 1) or (!bReverse and i == #self.waves)) then + return animation + end + end +end + +function PANEL:BeginAnimation() + self:CreateAnimation(2, { + target = {textOne = 1}, + easing = "inQuint", + bIgnoreConfig = true + }) + :CreateAnimation(2, { + target = {textOne = 0}, + easing = "inQuint", + bIgnoreConfig = true + }) + :CreateAnimation(2, { + target = {textTwo = 1}, + easing = "inQuint", + bIgnoreConfig = true, + OnComplete = function(animation, panel) + self:AnimateWaves(0) + end + }) + :CreateAnimation(2, { + target = {textTwo = 0}, + easing = "inQuint", + bIgnoreConfig = true + }) + :CreateAnimation(4, { + target = {sunbeamOffset = 1}, + bIgnoreConfig = true, + OnComplete = function() + self:CreateAnimation(2,{ + target = {helixAlpha = 1}, + easing = "inCubic" + }) + end + }) + :CreateAnimation(2, { + target = {helix = 1}, + easing = "outQuart", + bIgnoreConfig = true + }) + :CreateAnimation(2, { + target = {continueText = 1}, + easing = "linear", + bIgnoreConfig = true + }) +end + +function PANEL:PaintCurve(y, width, offset, scale) + offset = offset or 1 + scale = scale or 32 + + local points = { + [1] = { + x = 0, + y = ScrH() + } + } + + for i = 0, waveSegments do + local angle = math.rad((i / waveSegments) * -360) + + points[#points + 1] = { + x = (width / waveSegments) * i, + y = y + (math.sin(angle * 0.5 + offset) - 1) * scale + } + end + + points[#points + 1] = { + x = width, + y = ScrH() + } + + draw.NoTexture() + surface.DrawPoly(points) +end + +function PANEL:Paint(width, height) + local time = SysTime() + local text = L("helix"):lower() + local centerY = height * self.waves[#self.waves][1] + height * 0.5 + local sunbeamOffsetEasing = math.sin(math.pi * self.sunbeamOffset) + local textWidth, textHeight + local fft + + -- background + if (self.bBackground) then + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, width, height) + end + + if (self.sunbeamOffset == 1) then + fft = {} + + if (IsValid(self.channel)) then + self.channel:FFT(fft, FFT_2048) + + local kick = (fft[4] or 0) * 8192 + self.kickTarget = math.Approach(self.kickTarget, kick, 8 * math.abs(kick - self.kickTarget) * FrameTime()) + end + end + + -- waves + for i = 1, #self.waves do + local wave = self.waves[i] + local ratio = i / #self.waves + local color = Color( + backgroundColor.r * ratio, + backgroundColor.g * ratio, + backgroundColor.b * ratio, + self.bBackground and 255 or (ratio * 320) + ) + + surface.SetDrawColor(color) + self:PaintCurve(height * wave[1], width, wave[2]) + end + + -- helix + if (self.helix > 0) then + local alpha = self.helixAlpha * 255 + + derma.SkinFunc("DrawHelixCurved", + width * 0.5, centerY, + math.min(ScreenScale(72), 128) * 2, -- font sizes are clamped to 128 + helixSegments * self.helix, helixHeight, self.helix, + ColorAlpha(color_white, alpha), + ColorAlpha(dimColor, alpha) + ) + end + + -- title text glow + surface.SetTextColor(255, 255, 255, + self.sunbeamOffset == 1 and self.kickTarget or sunbeamOffsetEasing * 255 + ) + surface.SetFont("ixIntroTitleBlurFont") + + local logoTextWidth, logoTextHeight = surface.GetTextSize(text) + surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5) + surface.DrawText(text) + + -- title text + surface.SetTextColor(255, 255, 255, self.sunbeamOffset * 255) + surface.SetFont("ixIntroTitleFont") + + logoTextWidth, logoTextHeight = surface.GetTextSize(text) + surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5) + surface.DrawText(text) + + -- text one + surface.SetFont("ixIntroSubtitleFont") + text = L("introTextOne"):lower() + textWidth = surface.GetTextSize(text) + + surface.SetTextColor(255, 255, 255, self.textOne * 255) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66) + surface.DrawText(text) + + -- text two + text = L("introTextTwo", Schema.author or "nebulous"):lower() + textWidth = surface.GetTextSize(text) + + surface.SetTextColor(255, 255, 255, self.textTwo * 255) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66) + surface.DrawText(text) + + -- continue text + surface.SetFont("ixIntroSmallFont") + text = L("introContinue"):lower() + textWidth, textHeight = surface.GetTextSize(text) + + if (self.continueText == 1) then + self.pulse = self.pulse + 6 * FrameTime() + + if (self.pulse >= 360) then + self.pulse = 0 + end + end + + surface.SetTextColor(255, 255, 255, self.continueText * 255 - (math.sin(self.pulse) * 100), 0) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, centerY * 2 - textHeight * 2) + surface.DrawText(text) + + -- sunbeams + if (self.sunbeamOffset > 0 and self.sunbeamOffset != 1) then + DrawSunbeams(0.25, sunbeamOffsetEasing * 0.1, 0.02, + (((width * 0.5 - logoTextWidth * 0.5) - 32) / width) + ((logoTextWidth + 64) / width) * self.sunbeamOffset, + 0.5 + math.sin(time * 2) * 0.01 + ) + end +end + +function PANEL:OnKeyCodePressed(key) + if (key == KEY_SPACE and self.continueText > 0.25) then + self:Remove() + end +end + +function PANEL:OnRemove() + timer.Remove("ixIntroStart") + + if (IsValid(self.channel)) then + self.channel:Stop() + end + + if (IsValid(ix.gui.characterMenu)) then + ix.gui.characterMenu:PlayMusic() + end +end + +function PANEL:Remove(bForce) + if (bForce) then + BaseClass.Remove(self) + return + end + + if (self.bClosing) then + return + end + + self.bClosing = true + self.bBackground = nil + + -- waves + local animation = self:AnimateWaves(1.1, true) + + animation.OnComplete = function(anim, panel) + panel:SetMouseInputEnabled(false) + panel:SetKeyboardInputEnabled(false) + end + + -- audio + self:CreateAnimation(4.5, { + index = 1, + target = {volume = 0}, + + Think = function(anim, panel) + if (IsValid(panel.channel)) then + panel.channel:SetVolume(panel.volume) + end + end, + + OnComplete = function() + timer.Simple(0, function() + BaseClass.Remove(self) + end) + end + }) +end + +vgui.Register("ixIntro", PANEL, "EditablePanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_inventory.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_inventory.lua new file mode 100644 index 0000000..2addb4f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_inventory.lua @@ -0,0 +1,796 @@ + +local RECEIVER_NAME = "ixInventoryItem" + +-- The queue for the rendered icons. +ICON_RENDER_QUEUE = ICON_RENDER_QUEUE or {} + +-- To make making inventory variant, This must be followed up. +local function RenderNewIcon(panel, itemTable) + local model = itemTable:GetModel() + + -- re-render icons + if ((itemTable.iconCam and !ICON_RENDER_QUEUE[string.lower(model)]) or itemTable.forceRender) then + local iconCam = itemTable.iconCam + iconCam = { + cam_pos = iconCam.pos, + cam_ang = iconCam.ang, + cam_fov = iconCam.fov, + } + ICON_RENDER_QUEUE[string.lower(model)] = true + + panel.Icon:RebuildSpawnIconEx( + iconCam + ) + end +end + +local function InventoryAction(action, itemID, invID, data) + net.Start("ixInventoryAction") + net.WriteString(action) + net.WriteUInt(itemID, 32) + net.WriteUInt(invID, 32) + net.WriteTable(data or {}) + net.SendToServer() +end + +local PANEL = {} + +AccessorFunc(PANEL, "itemTable", "ItemTable") +AccessorFunc(PANEL, "inventoryID", "InventoryID") + +function PANEL:Init() + self:Droppable(RECEIVER_NAME) +end + +function PANEL:OnMousePressed(code) + if (code == MOUSE_LEFT and self:IsDraggable()) then + self:MouseCapture(true) + self:DragMousePress(code) + + self.clickX, self.clickY = input.GetCursorPos() + elseif (code == MOUSE_RIGHT and self.DoRightClick) then + self:DoRightClick() + end +end + +function PANEL:OnMouseReleased(code) + -- move the item into the world if we're dropping on something that doesn't handle inventory item drops + if (!dragndrop.m_ReceiverSlot or dragndrop.m_ReceiverSlot.Name != RECEIVER_NAME) then + self:OnDrop(dragndrop.IsDragging()) + end + + self:DragMouseRelease(code) + self:SetZPos(99) + self:MouseCapture(false) +end + +function PANEL:DoRightClick() + local itemTable = self.itemTable + local inventory = self.inventoryID + + if (itemTable and inventory) then + itemTable.player = LocalPlayer() + + local menu = DermaMenu() + local override = hook.Run("CreateItemInteractionMenu", self, menu, itemTable) + + if (override == true) then + if (menu.Remove) then + menu:Remove() + end + + return + end + + for k, v in SortedPairs(itemTable.functions) do + if (k == "drop" or k == "combine" or (v.OnCanRun and v.OnCanRun(itemTable) == false)) then + continue + end + + -- is Multi-Option Function + if (v.isMulti) then + local subMenu, subMenuOption = menu:AddSubMenu(L(v.name or k), function() + itemTable.player = LocalPlayer() + local send = true + + if (v.OnClick) then + send = v.OnClick(itemTable) + end + + if (v.sound) then + surface.PlaySound(v.sound) + end + + if (send != false) then + InventoryAction(k, itemTable.id, inventory) + end + itemTable.player = nil + end) + subMenuOption:SetImage(v.icon or "icon16/brick.png") + + if (v.multiOptions) then + local options = isfunction(v.multiOptions) and v.multiOptions(itemTable, LocalPlayer()) or v.multiOptions + + for _, sub in pairs(options) do + subMenu:AddOption(L(sub.name or "subOption"), function() + itemTable.player = LocalPlayer() + local send = true + + if (sub.OnClick) then + send = sub.OnClick(itemTable) + end + + if (sub.sound) then + surface.PlaySound(sub.sound) + end + + if (send != false) then + InventoryAction(k, itemTable.id, inventory, sub.data) + end + itemTable.player = nil + end) + end + end + else + menu:AddOption(L(v.name or k), function() + itemTable.player = LocalPlayer() + local send = true + + if (v.OnClick) then + send = v.OnClick(itemTable) + end + + if (v.sound) then + surface.PlaySound(v.sound) + end + + if (send != false) then + InventoryAction(k, itemTable.id, inventory) + end + itemTable.player = nil + end):SetImage(v.icon or "icon16/brick.png") + end + end + + -- we want drop to show up as the last option + local info = itemTable.functions.drop + + if (info and info.OnCanRun and info.OnCanRun(itemTable) != false) then + menu:AddOption(L(info.name or "drop"), function() + itemTable.player = LocalPlayer() + local send = true + + if (info.OnClick) then + send = info.OnClick(itemTable) + end + + if (info.sound) then + surface.PlaySound(info.sound) + end + + if (send != false) then + InventoryAction("drop", itemTable.id, inventory) + end + itemTable.player = nil + end):SetImage(info.icon or "icon16/brick.png") + end + + menu:Open() + itemTable.player = nil + end +end + +function PANEL:OnDrop(bDragging, inventoryPanel, inventory, gridX, gridY) + local item = self.itemTable + + if (!item or !bDragging) then + return + end + + if (!IsValid(inventoryPanel)) then + local inventoryID = self.inventoryID + + if (inventoryID) then + InventoryAction("drop", item.id, inventoryID, {}) + end + elseif (inventoryPanel:IsAllEmpty(gridX, gridY, item.width, item.height, self)) then + local oldX, oldY = self.gridX, self.gridY + + if (oldX != gridX or oldY != gridY or self.inventoryID != inventoryPanel.invID) then + self:Move(gridX, gridY, inventoryPanel) + end + elseif (inventoryPanel.combineItem) then + local combineItem = inventoryPanel.combineItem + local inventoryID = combineItem.invID + + if (inventoryID) then + combineItem.player = LocalPlayer() + if (combineItem.functions.combine.sound) then + surface.PlaySound(combineItem.functions.combine.sound) + end + + InventoryAction("combine", combineItem.id, inventoryID, {item.id}) + combineItem.player = nil + end + end +end + +function PANEL:Move(newX, newY, givenInventory, bNoSend) + local iconSize = givenInventory.iconSize + local oldX, oldY = self.gridX, self.gridY + local oldParent = self:GetParent() + + if (givenInventory:OnTransfer(oldX, oldY, newX, newY, oldParent, bNoSend) == false) then + return + end + + local x = (newX - 1) * iconSize + 4 + local y = (newY - 1) * iconSize + givenInventory:GetPadding(2) + + self.gridX = newX + self.gridY = newY + + self:SetParent(givenInventory) + self:SetPos(x, y) + + if (self.slots) then + for _, v in ipairs(self.slots) do + if (IsValid(v) and v.item == self) then + v.item = nil + end + end + end + + self.slots = {} + + for currentX = 1, self.gridW do + for currentY = 1, self.gridH do + local slot = givenInventory.slots[self.gridX + currentX - 1][self.gridY + currentY - 1] + + slot.item = self + self.slots[#self.slots + 1] = slot + end + end +end + +function PANEL:PaintOver(width, height) + local itemTable = self.itemTable + + if (itemTable and itemTable.PaintOver) then + itemTable.PaintOver(self, itemTable, width, height) + end +end + +function PANEL:ExtraPaint(width, height) +end + +function PANEL:Paint(width, height) + surface.SetDrawColor(0, 0, 0, 85) + surface.DrawRect(2, 2, width - 4, height - 4) + + self:ExtraPaint(width, height) +end + +vgui.Register("ixItemIcon", PANEL, "SpawnIcon") + +PANEL = {} +DEFINE_BASECLASS("DFrame") + +AccessorFunc(PANEL, "iconSize", "IconSize", FORCE_NUMBER) +AccessorFunc(PANEL, "bHighlighted", "Highlighted", FORCE_BOOL) + +function PANEL:Init() + self:SetIconSize(64) + self:ShowCloseButton(false) + self:SetDraggable(true) + self:SetSizable(true) + self:SetTitle(L"inv") + self:Receiver(RECEIVER_NAME, self.ReceiveDrop) + + self.btnMinim:SetVisible(false) + self.btnMinim:SetMouseInputEnabled(false) + self.btnMaxim:SetVisible(false) + self.btnMaxim:SetMouseInputEnabled(false) + + self.panels = {} +end + +function PANEL:GetPadding(index) + return select(index, self:GetDockPadding()) +end + +function PANEL:SetTitle(text) + if (text == nil) then + self.oldPadding = {self:GetDockPadding()} + + self.lblTitle:SetText("") + self.lblTitle:SetVisible(false) + + self:DockPadding(5, 5, 5, 5) + else + if (self.oldPadding) then + self:DockPadding(unpack(self.oldPadding)) + self.oldPadding = nil + end + + BaseClass.SetTitle(self, text) + end +end + +function PANEL:FitParent(invWidth, invHeight) + local parent = self:GetParent() + + if (!IsValid(parent)) then + return + end + + local width, height = parent:GetSize() + local padding = 4 + local iconSize + + if (invWidth > invHeight) then + iconSize = (width - padding * 2) / invWidth + elseif (invHeight > invWidth) then + iconSize = (height - padding * 2) / invHeight + else + -- we use height because the titlebar will make it more tall than it is wide + iconSize = (height - padding * 2) / invHeight - 4 + end + + self:SetSize(iconSize * invWidth + padding * 2, iconSize * invHeight + padding * 2) + self:SetIconSize(iconSize) +end + +function PANEL:OnRemove() + if (self.childPanels) then + for _, v in ipairs(self.childPanels) do + if (v != self) then + v:Remove() + end + end + end +end + +function PANEL:ViewOnly() + self.viewOnly = true + + for _, icon in pairs(self.panels) do + icon.OnMousePressed = nil + icon.OnMouseReleased = nil + icon.doRightClick = nil + end +end + +function PANEL:SetInventory(inventory, bFitParent) + if (inventory.slots) then + local invWidth, invHeight = inventory:GetSize() + self.invID = inventory:GetID() + + if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels and inventory != LocalPlayer():GetCharacter():GetInventory()) then + self:SetIconSize(ix.gui.inv1:GetIconSize()) + self:SetPaintedManually(true) + self.bNoBackgroundBlur = true + + ix.gui.inv1.childPanels[#ix.gui.inv1.childPanels + 1] = self + elseif (bFitParent) then + self:FitParent(invWidth, invHeight) + else + self:SetSize(self.iconSize, self.iconSize) + end + + self:SetGridSize(invWidth, invHeight) + + for x, items in pairs(inventory.slots) do + for y, data in pairs(items) do + if (!data.id) then continue end + + local item = ix.item.instances[data.id] + + if (item and !IsValid(self.panels[item.id])) then + local icon = self:AddIcon(item:GetModel() or "models/props_junk/popcan01a.mdl", + x, y, item.width, item.height, item:GetSkin()) + + if (IsValid(icon)) then + icon:SetHelixTooltip(function(tooltip) + ix.hud.PopulateItemTooltip(tooltip, item) + end) + + self.panels[item.id] = icon + end + end + end + end + end +end + +function PANEL:SetGridSize(w, h) + local iconSize = self.iconSize + local newWidth = w * iconSize + 8 + local newHeight = h * iconSize + self:GetPadding(2) + self:GetPadding(4) + + self.gridW = w + self.gridH = h + + self:SetSize(newWidth, newHeight) + self:SetMinWidth(newWidth) + self:SetMinHeight(newHeight) + self:BuildSlots() +end + +function PANEL:PerformLayout(width, height) + BaseClass.PerformLayout(self, width, height) + + if (self.Sizing and self.gridW and self.gridH) then + local newWidth = (width - 8) / self.gridW + local newHeight = (height - self:GetPadding(2) + self:GetPadding(4)) / self.gridH + + self:SetIconSize((newWidth + newHeight) / 2) + self:RebuildItems() + end +end + +function PANEL:BuildSlots() + local iconSize = self.iconSize + + self.slots = self.slots or {} + + for _, v in ipairs(self.slots) do + for _, v2 in ipairs(v) do + v2:Remove() + end + end + + self.slots = {} + + for x = 1, self.gridW do + self.slots[x] = {} + + for y = 1, self.gridH do + local slot = self:Add("DPanel") + slot:SetZPos(-999) + slot.gridX = x + slot.gridY = y + slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2)) + slot:SetSize(iconSize, iconSize) + slot.Paint = function(panel, width, height) + derma.SkinFunc("PaintInventorySlot", panel, width, height) + end + + self.slots[x][y] = slot + end + end +end + +function PANEL:RebuildItems() + local iconSize = self.iconSize + + for x = 1, self.gridW do + for y = 1, self.gridH do + local slot = self.slots[x][y] + + slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2)) + slot:SetSize(iconSize, iconSize) + end + end + + for _, v in pairs(self.panels) do + if (IsValid(v)) then + v:SetPos(self.slots[v.gridX][v.gridY]:GetPos()) + v:SetSize(v.gridW * iconSize, v.gridH * iconSize) + end + end +end + +function PANEL:PaintDragPreview(width, height, mouseX, mouseY, itemPanel) + local iconSize = self.iconSize + local item = itemPanel:GetItemTable() + + if (item) then + local inventory = ix.item.inventories[self.invID] + local dropX = math.ceil((mouseX - 4 - (itemPanel.gridW - 1) * 32) / iconSize) + local dropY = math.ceil((mouseY - self:GetPadding(2) - (itemPanel.gridH - 1) * 32) / iconSize) + + local hoveredPanel = vgui.GetHoveredPanel() + + if (IsValid(hoveredPanel) and hoveredPanel != itemPanel and hoveredPanel.GetItemTable) then + local hoveredItem = hoveredPanel:GetItemTable() + + if (hoveredItem) then + local info = hoveredItem.functions.combine + + if (info and info.OnCanRun and info.OnCanRun(hoveredItem, {item.id}) != false) then + surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", self, Color(200, 0, 0)), 20)) + surface.DrawRect( + hoveredPanel.x, + hoveredPanel.y, + hoveredPanel:GetWide(), + hoveredPanel:GetTall() + ) + + self.combineItem = hoveredItem + + return + end + end + end + + self.combineItem = nil + + -- don't draw grid if we're dragging it out of bounds + if (inventory) then + local invWidth, invHeight = inventory:GetSize() + + if (dropX < 1 or dropY < 1 or + dropX + itemPanel.gridW - 1 > invWidth or + dropY + itemPanel.gridH - 1 > invHeight) then + return + end + end + + local bEmpty = true + + for x = 0, itemPanel.gridW - 1 do + for y = 0, itemPanel.gridH - 1 do + local x2 = dropX + x + local y2 = dropY + y + + bEmpty = self:IsEmpty(x2, y2, itemPanel) + + if (!bEmpty) then + -- no need to iterate further since we know something is blocking the hovered grid cells, break through both loops + goto finish + end + end + end + + ::finish:: + local previewColor = ColorAlpha(derma.GetColor(bEmpty and "Success" or "Error", self, Color(200, 0, 0)), 20) + + surface.SetDrawColor(previewColor) + surface.DrawRect( + (dropX - 1) * iconSize + 4, + (dropY - 1) * iconSize + self:GetPadding(2), + itemPanel:GetWide(), + itemPanel:GetTall() + ) + end +end + +function PANEL:PaintOver(width, height) + local panel = self.previewPanel + + if (IsValid(panel)) then + local itemPanel = (dragndrop.GetDroppable() or {})[1] + + if (IsValid(itemPanel)) then + self:PaintDragPreview(width, height, self.previewX, self.previewY, itemPanel) + end + end + + self.previewPanel = nil +end + +function PANEL:IsEmpty(x, y, this) + return (self.slots[x] and self.slots[x][y]) and (!IsValid(self.slots[x][y].item) or self.slots[x][y].item == this) +end + +function PANEL:IsAllEmpty(x, y, width, height, this) + for x2 = 0, width - 1 do + for y2 = 0, height - 1 do + if (!self:IsEmpty(x + x2, y + y2, this)) then + return false + end + end + end + + return true +end + +function PANEL:OnTransfer(oldX, oldY, x, y, oldInventory, noSend) + local inventories = ix.item.inventories + local inventory = inventories[oldInventory.invID] + local inventory2 = inventories[self.invID] + local item + + if (inventory) then + item = inventory:GetItemAt(oldX, oldY) + + if (!item) then + return false + end + + if (hook.Run("CanTransferItem", item, inventories[oldInventory.invID], inventories[self.invID]) == false) then + return false, "notAllowed" + end + + if (item.CanTransfer and + item:CanTransfer(inventory, inventory != inventory2 and inventory2 or nil) == false) then + return false + end + end + + if (!noSend) then + net.Start("ixInventoryMove") + net.WriteUInt(oldX, 6) + net.WriteUInt(oldY, 6) + net.WriteUInt(x, 6) + net.WriteUInt(y, 6) + net.WriteUInt(oldInventory.invID, 32) + net.WriteUInt(self != oldInventory and self.invID or oldInventory.invID, 32) + net.SendToServer() + end + + if (inventory) then + inventory.slots[oldX][oldY] = nil + end + + if (item and inventory2) then + inventory2.slots[x] = inventory2.slots[x] or {} + inventory2.slots[x][y] = item + end +end + +function PANEL:AddIcon(model, x, y, w, h, skin) + local iconSize = self.iconSize + + w = w or 1 + h = h or 1 + + if (self.slots[x] and self.slots[x][y]) then + local panel = self:Add("ixItemIcon") + panel:SetSize(w * iconSize, h * iconSize) + panel:SetZPos(999) + panel:InvalidateLayout(true) + panel:SetModel(model, skin) + panel:SetPos(self.slots[x][y]:GetPos()) + panel.gridX = x + panel.gridY = y + panel.gridW = w + panel.gridH = h + + local inventory = ix.item.inventories[self.invID] + + if (!inventory) then + return + end + + local itemTable = inventory:GetItemAt(panel.gridX, panel.gridY) + + panel:SetInventoryID(inventory:GetID()) + panel:SetItemTable(itemTable) + + if (self.panels[itemTable:GetID()]) then + self.panels[itemTable:GetID()]:Remove() + end + + if (itemTable.exRender) then + panel.Icon:SetVisible(false) + panel.ExtraPaint = function(this, panelX, panelY) + local exIcon = ikon:GetIcon(itemTable.uniqueID) + if (exIcon) then + surface.SetMaterial(exIcon) + surface.SetDrawColor(color_white) + surface.DrawTexturedRect(0, 0, panelX, panelY) + else + ikon:renderIcon( + itemTable.uniqueID, + itemTable.width, + itemTable.height, + itemTable:GetModel(), + itemTable.iconCam + ) + end + end + else + -- yeah.. + RenderNewIcon(panel, itemTable) + end + + panel.slots = {} + + for i = 0, w - 1 do + for i2 = 0, h - 1 do + local slot = self.slots[x + i] and self.slots[x + i][y + i2] + + if (IsValid(slot)) then + slot.item = panel + panel.slots[#panel.slots + 1] = slot + else + for _, v in ipairs(panel.slots) do + v.item = nil + end + + panel:Remove() + + return + end + end + end + + return panel + end +end + +function PANEL:ReceiveDrop(panels, bDropped, menuIndex, x, y) + local panel = panels[1] + + if (!IsValid(panel)) then + self.previewPanel = nil + return + end + + if (bDropped) then + local inventory = ix.item.inventories[self.invID] + + if (inventory and panel.OnDrop) then + local dropX = math.ceil((x - 4 - (panel.gridW - 1) * 32) / self.iconSize) + local dropY = math.ceil((y - self:GetPadding(2) - (panel.gridH - 1) * 32) / self.iconSize) + + panel:OnDrop(true, self, inventory, dropX, dropY) + end + + self.previewPanel = nil + else + self.previewPanel = panel + self.previewX = x + self.previewY = y + end +end + +vgui.Register("ixInventory", PANEL, "DFrame") + +hook.Add("CreateMenuButtons", "ixInventory", function(tabs) + if (hook.Run("CanPlayerViewInventory") == false) then + return + end + + tabs["inv"] = { + bDefault = true, + Create = function(info, container) + local canvas = container:Add("DTileLayout") + local canvasLayout = canvas.PerformLayout + canvas.PerformLayout = nil -- we'll layout after we add the panels instead of each time one is added + canvas:SetBorder(0) + canvas:SetSpaceX(2) + canvas:SetSpaceY(2) + canvas:Dock(FILL) + + ix.gui.menuInventoryContainer = canvas + + local panel = canvas:Add("ixInventory") + panel:SetPos(0, 0) + panel:SetDraggable(false) + panel:SetSizable(false) + panel:SetTitle(nil) + panel.bNoBackgroundBlur = true + panel.childPanels = {} + + local inventory = LocalPlayer():GetCharacter():GetInventory() + + if (inventory) then + panel:SetInventory(inventory) + end + + ix.gui.inv1 = panel + + if (ix.option.Get("openBags", true)) then + for k, _ in inventory:Iter() do + if (!k.isBag) then + continue + end + + k.functions.View.OnClick(k) + end + end + + canvas.PerformLayout = canvasLayout + canvas:Layout() + end + } +end) + +hook.Add("PostRenderVGUI", "ixInvHelper", function() + local pnl = ix.gui.inv1 + + hook.Run("PostDrawInventory", pnl) +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_menu.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_menu.lua new file mode 100644 index 0000000..205f25f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_menu.lua @@ -0,0 +1,495 @@ + +local animationTime = 1 +local matrixZScale = Vector(1, 1, 0.0001) + +DEFINE_BASECLASS("ixSubpanelParent") +local PANEL = {} + +AccessorFunc(PANEL, "bCharacterOverview", "CharacterOverview", FORCE_BOOL) + +function PANEL:Init() + if (IsValid(ix.gui.menu)) then + ix.gui.menu:Remove() + end + + ix.gui.menu = self + + -- properties + self.manualChildren = {} + self.noAnchor = CurTime() + 0.4 + self.anchorMode = true + self.rotationOffset = Angle(0, 180, 0) + self.projectedTexturePosition = Vector(0, 0, 6) + self.projectedTextureRotation = Angle(-45, 60, 0) + + self.bCharacterOverview = false + self.bOverviewOut = false + self.overviewFraction = 0 + + self.currentAlpha = 0 + self.currentBlur = 0 + + -- setup + self:SetPadding(ScreenScale(16), true) + self:SetSize(ScrW(), ScrH()) + self:SetPos(0, 0) + self:SetLeftOffset(self:GetWide() * 0.25 + self:GetPadding()) + + -- main button panel + self.buttons = self:Add("Panel") + self.buttons:SetSize(self:GetWide() * 0.25, self:GetTall() - self:GetPadding() * 2) + self.buttons:Dock(LEFT) + self.buttons:SetPaintedManually(true) + + local close = self.buttons:Add("ixMenuButton") + close:SetText("return") + close:SizeToContents() + close:Dock(BOTTOM) + close.DoClick = function() + self:Remove() + end + + local characters = self.buttons:Add("ixMenuButton") + characters:SetText("characters") + characters:SizeToContents() + characters:Dock(BOTTOM) + characters.DoClick = function() + self:Remove() + vgui.Create("ixCharMenu") + end + + -- @todo make a better way to avoid clicks in the padding PLEASE + self.guard = self:Add("Panel") + self.guard:SetPos(0, 0) + self.guard:SetSize(self:GetPadding(), self:GetTall()) + + -- tabs + self.tabs = self.buttons:Add("DScrollPanel") + self.tabs.buttons = {} + self.tabs:Dock(FILL) + self:PopulateTabs() + + self:MakePopup() + self:OnOpened() +end + +function PANEL:OnOpened() + self:SetAlpha(0) + + self:CreateAnimation(animationTime, { + target = {currentAlpha = 255}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(panel.currentAlpha) + end + }) +end + +function PANEL:GetActiveTab() + return (self:GetActiveSubpanel() or {}).subpanelName +end + +function PANEL:TransitionSubpanel(id) + local lastSubpanel = self:GetActiveSubpanel() + + -- don't transition to the same panel + if (IsValid(lastSubpanel) and lastSubpanel.subpanelID == id) then + return + end + + local subpanel = self:GetSubpanel(id) + + if (IsValid(subpanel)) then + if (!subpanel.bPopulated) then + -- we need to set the size of the subpanel if it's a section since it will be 0, 0 + if (subpanel.sectionParent) then + subpanel:SetSize(self:GetStandardSubpanelSize()) + end + + local info = subpanel.info + subpanel.Paint = nil + + if (istable(info) and info.Create) then + info:Create(subpanel) + elseif (isfunction(info)) then + info(subpanel) + end + + hook.Run("MenuSubpanelCreated", subpanel.subpanelName, subpanel) + subpanel.bPopulated = true + end + + -- only play whoosh sound only when the menu was already open + if (IsValid(lastSubpanel)) then + LocalPlayer():EmitSound("Helix.Whoosh") + end + + self:SetActiveSubpanel(id) + end + + subpanel = self:GetActiveSubpanel() + + local info = subpanel.info + local bHideBackground = istable(info) and (info.bHideBackground != nil and info.bHideBackground or false) or false + + if (bHideBackground) then + self:HideBackground() + else + self:ShowBackground() + end + + -- call hooks if we've changed subpanel + if (IsValid(lastSubpanel) and istable(lastSubpanel.info) and lastSubpanel.info.OnDeselected) then + lastSubpanel.info:OnDeselected(lastSubpanel) + end + + if (IsValid(subpanel) and istable(subpanel.info) and subpanel.info.OnSelected) then + subpanel.info:OnSelected(subpanel) + end + + ix.gui.lastMenuTab = id +end + +function PANEL:SetCharacterOverview(bValue, length) + bValue = tobool(bValue) + length = length or animationTime + + if (bValue) then + if (!IsValid(self.projectedTexture)) then + self.projectedTexture = ProjectedTexture() + end + + local faction = ix.faction.indices[LocalPlayer():Team()] + local color = faction and faction.color or color_white + + self.projectedTexture:SetEnableShadows(false) + self.projectedTexture:SetNearZ(12) + self.projectedTexture:SetFarZ(64) + self.projectedTexture:SetFOV(90) + self.projectedTexture:SetColor(color) + self.projectedTexture:SetTexture("effects/flashlight/soft") + + self:CreateAnimation(length, { + index = 3, + target = {overviewFraction = 1}, + easing = "outQuint", + bIgnoreConfig = true + }) + + self.bOverviewOut = false + self.bCharacterOverview = true + else + self:CreateAnimation(length, { + index = 3, + target = {overviewFraction = 0}, + easing = "outQuint", + bIgnoreConfig = true, + + OnComplete = function(animation, panel) + panel.bCharacterOverview = false + + if (IsValid(panel.projectedTexture)) then + panel.projectedTexture:Remove() + end + end + }) + + self.bOverviewOut = true + end +end + +function PANEL:GetOverviewInfo(origin, angles, fov) + local originAngles = Angle(0, angles.yaw, angles.roll) + local target = LocalPlayer():GetObserverTarget() + local fraction = self.overviewFraction + local bDrawPlayer = ((fraction > 0.2) or (!self.bOverviewOut and (fraction > 0.2))) and !IsValid(target) + local forward = originAngles:Forward() * 58 - originAngles:Right() * 16 + forward.z = 0 + + local newOrigin + + if (IsValid(target)) then + newOrigin = target:GetPos() + forward + else + newOrigin = origin - LocalPlayer():OBBCenter() * 0.6 + forward + end + + local newAngles = originAngles + self.rotationOffset + newAngles.pitch = 5 + newAngles.roll = 0 + + return LerpVector(fraction, origin, newOrigin), LerpAngle(fraction, angles, newAngles), Lerp(fraction, fov, 90), bDrawPlayer +end + +function PANEL:HideBackground() + self:CreateAnimation(animationTime, { + index = 2, + target = {currentBlur = 0}, + easing = "outQuint" + }) +end + +function PANEL:ShowBackground() + self:CreateAnimation(animationTime, { + index = 2, + target = {currentBlur = 1}, + easing = "outQuint" + }) +end + +function PANEL:GetStandardSubpanelSize() + return ScrW() * 0.75 - self:GetPadding() * 3, ScrH() - self:GetPadding() * 2 +end + +function PANEL:SetupTab(name, info, sectionParent) + local bTable = istable(info) + local buttonColor = (bTable and info.buttonColor) or (ix.config.Get("color") or Color(140, 140, 140, 255)) + local bDefault = (bTable and info.bDefault) or false + local qualifiedName = sectionParent and (sectionParent.name .. "/" .. name) or name + + -- setup subpanels without populating them so we can retain the order + local subpanel = self:AddSubpanel(qualifiedName, true) + local id = subpanel.subpanelID + subpanel.info = info + subpanel.sectionParent = sectionParent and qualifiedName + subpanel:SetPaintedManually(true) + subpanel:SetTitle(nil) + + if (sectionParent) then + -- hide section subpanels if they haven't been populated to seeing more subpanels than necessary + -- fly by as you navigate tabs in the menu + subpanel:SetSize(0, 0) + else + subpanel:SetSize(self:GetStandardSubpanelSize()) + + -- this is called while the subpanel has not been populated + subpanel.Paint = function(panel, width, height) + derma.SkinFunc("PaintPlaceholderPanel", panel, width, height) + end + end + + local button + + if (sectionParent) then + button = sectionParent:AddSection(L(name)) + name = qualifiedName + else + button = self.tabs:Add("ixMenuSelectionButton") + button:SetText(L(name)) + button:SizeToContents() + button:Dock(TOP) + button:SetButtonList(self.tabs.buttons) + button:SetBackgroundColor(buttonColor) + end + + button.name = name + button.id = id + button.OnSelected = function() + self:TransitionSubpanel(id) + end + + if (bTable and info.PopulateTabButton) then + info:PopulateTabButton(button) + end + + -- don't allow sections in sections + if (sectionParent or !bTable or !info.Sections) then + return bDefault, button, subpanel + end + + -- create button sections + for sectionName, sectionInfo in pairs(info.Sections) do + self:SetupTab(sectionName, sectionInfo, button) + end + + return bDefault, button, subpanel +end + +function PANEL:PopulateTabs() + local default + local tabs = {} + + hook.Run("CreateMenuButtons", tabs) + + for name, info in SortedPairs(tabs) do + local bDefault, button = self:SetupTab(name, info) + + if (bDefault) then + default = button + end + end + + if (ix.gui.lastMenuTab) then + for i = 1, #self.tabs.buttons do + local button = self.tabs.buttons[i] + + if (button.id == ix.gui.lastMenuTab) then + default = button + break + end + end + end + + if (!IsValid(default) and #self.tabs.buttons > 0) then + default = self.tabs.buttons[1] + end + + if (IsValid(default)) then + default:SetSelected(true) + self:SetActiveSubpanel(default.id, 0) + end + + self.buttons:MoveToFront() + self.guard:MoveToBefore(self.buttons) +end + +function PANEL:AddManuallyPaintedChild(panel) + panel:SetParent(self) + panel:SetPaintedManually(panel) + + self.manualChildren[#self.manualChildren + 1] = panel +end + +function PANEL:OnKeyCodePressed(key) + self.noAnchor = CurTime() + 0.5 + + if (key == KEY_TAB) then + self:Remove() + end +end + +function PANEL:Think() + if (IsValid(self.projectedTexture)) then + local forward = LocalPlayer():GetForward() + forward.z = 0 + + local right = LocalPlayer():GetRight() + right.z = 0 + + self.projectedTexture:SetBrightness(self.overviewFraction * 4) + self.projectedTexture:SetPos(LocalPlayer():GetPos() + right * 16 - forward * 8 + self.projectedTexturePosition) + self.projectedTexture:SetAngles(forward:Angle() + self.projectedTextureRotation) + self.projectedTexture:Update() + end + + if (self.bClosing) then + return + end + + local bTabDown = input.IsKeyDown(KEY_TAB) + + if (bTabDown and (self.noAnchor or CurTime() + 0.4) < CurTime() and self.anchorMode) then + self.anchorMode = false + surface.PlaySound("buttons/lightswitch2.wav") + end + + if ((!self.anchorMode and !bTabDown) or gui.IsGameUIVisible()) then + self:Remove() + + if (ix.option.Get("escCloseMenu", false)) then + gui.HideGameUI() + end + end +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintMenuBackground", self, width, height, self.currentBlur) + + local bShouldScale = self.currentAlpha != 255 + + if (bShouldScale) then + local currentScale = Lerp(self.currentAlpha / 255, 0.9, 1) + local matrix = Matrix() + + matrix:Scale(matrixZScale * currentScale) + matrix:Translate(Vector( + ScrW() * 0.5 - (ScrW() * currentScale * 0.5), + ScrH() * 0.5 - (ScrH() * currentScale * 0.5), + 1 + )) + + cam.PushModelMatrix(matrix) + end + + BaseClass.Paint(self, width, height) + self:PaintSubpanels(width, height) + self.buttons:PaintManual() + + for i = 1, #self.manualChildren do + self.manualChildren[i]:PaintManual() + end + + if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then + for i = 1, #ix.gui.inv1.childPanels do + local panel = ix.gui.inv1.childPanels[i] + + if (IsValid(panel)) then + panel:PaintManual() + end + end + end + + if (bShouldScale) then + cam.PopModelMatrix() + end +end + +function PANEL:PerformLayout() + self.guard:SetSize(self.tabs:GetWide() + self:GetPadding() * 2, self:GetTall()) +end + +function PANEL:Remove() + self.bClosing = true + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + self:SetCharacterOverview(false, animationTime * 0.5) + + -- remove input from opened child panels since they grab focus + if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then + for i = 1, #ix.gui.inv1.childPanels do + local panel = ix.gui.inv1.childPanels[i] + + if (IsValid(panel)) then + panel:SetMouseInputEnabled(false) + panel:SetKeyboardInputEnabled(false) + end + end + end + + CloseDermaMenus() + gui.EnableScreenClicker(false) + + self:CreateAnimation(animationTime * 0.5, { + index = 2, + target = {currentBlur = 0}, + easing = "outQuint" + }) + + self:CreateAnimation(animationTime * 0.5, { + target = {currentAlpha = 0}, + easing = "outQuint", + + -- we don't animate the blur because blurring doesn't draw things + -- with amount < 1 very well, resulting in jarring transition + Think = function(animation, panel) + panel:SetAlpha(panel.currentAlpha) + end, + + OnComplete = function(animation, panel) + if (IsValid(panel.projectedTexture)) then + panel.projectedTexture:Remove() + end + + BaseClass.Remove(panel) + end + }) +end + +vgui.Register("ixMenu", PANEL, "ixSubpanelParent") + +if (IsValid(ix.gui.menu)) then + ix.gui.menu:Remove() +end + +ix.gui.lastMenuTab = nil diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_menubutton.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_menubutton.lua new file mode 100644 index 0000000..1ec8d18 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_menubutton.lua @@ -0,0 +1,295 @@ + +local buttonPadding = ScreenScale(14) * 0.5 +local animationTime = 0.5 + +-- base menu button +DEFINE_BASECLASS("DButton") +local PANEL = {} + +AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") +AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha") + +function PANEL:Init() + self:SetFont("ixMenuButtonFont") + self:SetTextColor(color_white) + self:SetPaintBackground(false) + self:SetContentAlignment(4) + self:SetTextInset(buttonPadding, 0) + + self.padding = {32, 12, 32, 12} -- left, top, right, bottom + self.backgroundColor = Color(0, 0, 0) + self.backgroundAlpha = 128 + self.currentBackgroundAlpha = 0 +end + +function PANEL:GetPadding() + return self.padding +end + +function PANEL:SetPadding(left, top, right, bottom) + self.padding = { + left or self.padding[1], + top or self.padding[2], + right or self.padding[3], + bottom or self.padding[4] + } +end + +function PANEL:SetText(text, noTranslation) + BaseClass.SetText(self, noTranslation and text:utf8upper() or L(text):utf8upper()) +end + +function PANEL:SizeToContents() + BaseClass.SizeToContents(self) + + local width, height = self:GetSize() + self:SetSize(width + self.padding[1] + self.padding[3], height + self.padding[2] + self.padding[4]) +end + +function PANEL:PaintBackground(width, height) + surface.SetDrawColor(ColorAlpha(self.backgroundColor, self.currentBackgroundAlpha)) + surface.DrawRect(0, 0, width, height) +end + +function PANEL:Paint(width, height) + self:PaintBackground(width, height) + BaseClass.Paint(self, width, height) +end + +function PANEL:SetTextColorInternal(color) + BaseClass.SetTextColor(self, color) + self:SetFGColor(color) +end + +function PANEL:SetTextColor(color) + self:SetTextColorInternal(color) + self.color = color +end + +function PANEL:SetDisabled(bValue) + local color = self.color + + if (bValue) then + self:SetTextColorInternal(Color(math.max(color.r - 60, 0), math.max(color.g - 60, 0), math.max(color.b - 60, 0))) + else + self:SetTextColorInternal(color) + end + + BaseClass.SetDisabled(self, bValue) +end + +function PANEL:OnCursorEntered() + if (self:GetDisabled()) then + return + end + + local color = self:GetTextColor() + self:SetTextColorInternal(Color(math.max(color.r - 25, 0), math.max(color.g - 25, 0), math.max(color.b - 25, 0))) + + self:CreateAnimation(0.15, { + target = {currentBackgroundAlpha = self.backgroundAlpha} + }) + + LocalPlayer():EmitSound("Helix.Rollover") +end + +function PANEL:OnCursorExited() + if (self:GetDisabled()) then + return + end + + if (self.color) then + self:SetTextColor(self.color) + else + self:SetTextColor(color_white) + end + + self:CreateAnimation(0.15, { + target = {currentBackgroundAlpha = 0} + }) +end + +function PANEL:OnMousePressed(code) + if (self:GetDisabled()) then + return + end + + if (self.color) then + self:SetTextColor(self.color) + else + self:SetTextColor(ix.config.Get("color")) + end + + LocalPlayer():EmitSound("Helix.Press") + + if (code == MOUSE_LEFT and self.DoClick) then + self:DoClick(self) + elseif (code == MOUSE_RIGHT and self.DoRightClick) then + self:DoRightClick(self) + end +end + +function PANEL:OnMouseReleased(key) + if (self:GetDisabled()) then + return + end + + if (self.color) then + self:SetTextColor(self.color) + else + self:SetTextColor(color_white) + end +end + +vgui.Register("ixMenuButton", PANEL, "DButton") + +-- selection menu button +DEFINE_BASECLASS("ixMenuButton") +PANEL = {} + +AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") +AccessorFunc(PANEL, "selected", "Selected", FORCE_BOOL) +AccessorFunc(PANEL, "buttonList", "ButtonList") + +function PANEL:Init() + self.backgroundColor = color_white + self.selected = false + self.buttonList = {} + self.sectionPanel = nil -- sub-sections this button has; created only if it has any sections +end + +function PANEL:PaintBackground(width, height) + local alpha = self.selected and 255 or self.currentBackgroundAlpha + + derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ColorAlpha(self.backgroundColor, alpha)) +end + +function PANEL:SetSelected(bValue, bSelectedSection) + self.selected = bValue + + if (bValue) then + self:OnSelected() + + if (self.sectionPanel) then + self.sectionPanel:Show() + elseif (self.sectionParent) then + self.sectionParent.sectionPanel:Show() + end + elseif (self.sectionPanel and self.sectionPanel:IsVisible() and !bSelectedSection) then + self.sectionPanel:Hide() + end +end + +function PANEL:SetButtonList(list, bNoAdd) + if (!bNoAdd) then + list[#list + 1] = self + end + + self.buttonList = list +end + +function PANEL:GetSectionPanel() + return self.sectionPanel +end + +function PANEL:AddSection(name) + if (!IsValid(self.sectionPanel)) then + -- add section panel to regular button list + self.sectionPanel = vgui.Create("ixMenuSelectionList", self:GetParent()) + self.sectionPanel:Dock(self:GetDock()) + self.sectionPanel:SetParentButton(self) + end + + return self.sectionPanel:AddButton(name, self.buttonList) +end + +function PANEL:OnMousePressed(key) + for _, v in pairs(self.buttonList) do + if (IsValid(v) and v != self) then + v:SetSelected(false, self.sectionParent == v) + end + end + + self:SetSelected(true) + BaseClass.OnMousePressed(self, key) +end + +function PANEL:OnSelected() +end + +vgui.Register("ixMenuSelectionButton", PANEL, "ixMenuButton") + +-- collapsable list for menu button sections +PANEL = {} +AccessorFunc(PANEL, "parent", "ParentButton") + +function PANEL:Init() + self.parent = nil -- button that is responsible for controlling this list + self.height = 0 + self.targetHeight = 0 + + self:DockPadding(0, 1, 0, 1) + self:SetVisible(false) + self:SetTall(0) +end + +function PANEL:AddButton(name, buttonList) + assert(IsValid(self.parent), "attempted to add button to ixMenuSelectionList without a ParentButton") + assert(buttonList ~= nil, "attempted to add button to ixMenuSelectionList without a buttonList") + + local button = self:Add("ixMenuSelectionButton") + button.sectionParent = self.parent + button:SetTextInset(buttonPadding * 2, 0) + button:SetPadding(nil, 8, nil, 8) + button:SetFont("ixMenuButtonFontSmall") + button:Dock(TOP) + button:SetText(name) + button:SizeToContents() + button:SetButtonList(buttonList) + button:SetBackgroundColor(self.parent:GetBackgroundColor()) + + self.targetHeight = self.targetHeight + button:GetTall() + return button +end + +function PANEL:Show() + self:SetVisible(true) + + self:CreateAnimation(animationTime, { + index = 1, + target = { + height = self.targetHeight + 2 -- +2 for padding + }, + easing = "outQuart", + + Think = function(animation, panel) + panel:SetTall(panel.height) + end + }) +end + +function PANEL:Hide() + self:CreateAnimation(animationTime, { + index = 1, + target = { + height = 0 + }, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetTall(panel.height) + end, + + OnComplete = function(animation, panel) + panel:SetVisible(false) + end + }) +end + +function PANEL:Paint(width, height) + surface.SetDrawColor(Color(255, 255, 255, 33)) + surface.DrawRect(0, 0, width, 1) + surface.DrawRect(0, height - 1, width, 1) +end + +vgui.Register("ixMenuSelectionList", PANEL, "Panel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_modelpanel.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_modelpanel.lua new file mode 100644 index 0000000..6ff70ad --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_modelpanel.lua @@ -0,0 +1,120 @@ + +DEFINE_BASECLASS("DModelPanel") + +local PANEL = {} +local MODEL_ANGLE = Angle(0, 45, 0) + +function PANEL:Init() + self.brightness = 1 + self:SetCursor("none") +end + +function PANEL:SetModel(model, skin, bodygroups) + if (IsValid(self.Entity)) then + self.Entity:Remove() + self.Entity = nil + end + + if (!ClientsideModel) then + return + end + + local entity = ClientsideModel(model, RENDERGROUP_OPAQUE) + + if (!IsValid(entity)) then + return + end + + entity:SetNoDraw(true) + entity:SetIK(false) + + if (skin) then + entity:SetSkin(skin) + end + + if (isstring(bodygroups)) then + entity:SetBodyGroups(bodygroups) + end + + local sequence = entity:LookupSequence("idle_unarmed") + + if (sequence <= 0) then + sequence = entity:SelectWeightedSequence(ACT_IDLE) + end + + if (sequence > 0) then + entity:ResetSequence(sequence) + else + local found = false + + for _, v in ipairs(entity:GetSequenceList()) do + if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then + entity:ResetSequence(v) + found = true + + break + end + end + + if (!found) then + entity:ResetSequence(4) + end + end + + self.Entity = entity +end + +function PANEL:LayoutEntity() + local scrW, scrH = ScrW(), ScrH() + local xRatio = gui.MouseX() / scrW + local yRatio = gui.MouseY() / scrH + local x, _ = self:LocalToScreen(self:GetWide() / 2) + local xRatio2 = x / scrW + local entity = self.Entity + + entity:SetPoseParameter("head_pitch", yRatio*90 - 30) + entity:SetPoseParameter("head_yaw", (xRatio - xRatio2)*90 - 5) + entity:SetAngles(MODEL_ANGLE) + entity:SetIK(false) + + if (self.copyLocalSequence) then + entity:SetSequence(LocalPlayer():GetSequence()) + entity:SetPoseParameter("move_yaw", 360 * LocalPlayer():GetPoseParameter("move_yaw") - 180) + end + + self:RunAnimation() +end + +function PANEL:DrawModel() + local brightness = self.brightness * 0.4 + local brightness2 = self.brightness * 1.5 + + render.SetStencilEnable(false) + render.SetColorMaterial() + render.SetColorModulation(1, 1, 1) + render.SetModelLighting(0, brightness2, brightness2, brightness2) + + for i = 1, 4 do + render.SetModelLighting(i, brightness, brightness, brightness) + end + + local fraction = (brightness / 1) * 0.1 + + render.SetModelLighting(5, fraction, fraction, fraction) + + -- Excecute Some stuffs + if (self.enableHook) then + hook.Run("DrawHelixModelView", self, self.Entity) + end + + self.Entity:DrawModel() + + if (self.enableHook) then + hook.Run("PostDrawHelixModelView", self, self.Entity) + end +end + +function PANEL:OnMousePressed() +end + +vgui.Register("ixModelPanel", PANEL, "DModelPanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_notice.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_notice.lua new file mode 100644 index 0000000..ac77a61 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_notice.lua @@ -0,0 +1,270 @@ + +local animationTime = 0.75 + +-- notice manager +-- this manages positions/animations for notice panels +local PANEL = {} + +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) + +function PANEL:Init() + self:SetSize(ScrW() * 0.4, ScrH()) + self:SetPos(ScrW() - ScrW() * 0.4, 0) + self:SetZPos(-99999) + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + + self.notices = {} + self.padding = 4 +end + +function PANEL:GetAll() + return self.notices +end + +function PANEL:Clear() + for _, v in ipairs(self.notices) do + self:RemoveNotice(v) + end +end + +function PANEL:AddNotice(text, bError) + if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu.bClosing) then + return + end + + local textLength = text:utf8len() + + local panel = self:Add("ixNotice") + panel:SetText(text) + panel:SetError(bError or text:utf8sub(textLength, textLength) == "!") + panel:SizeToContents() + panel.currentY = -panel:GetTall() + panel:SetPos(self.padding, panel.currentY) + + -- setup duration timer + panel:CreateAnimation(ix.option.Get("noticeDuration", 8), { + index = 2, + target = {duration = 1}, + bIgnoreConfig = true, + + OnComplete = function(animation, this) + self:RemoveNotice(this) + end + }) + + table.insert(self.notices, 1, panel) + self:Organize() + + -- remove old notice if we've hit the limit of notices + if (#self.notices > ix.option.Get("noticeMax", 4)) then + for i = #self.notices, 1, -1 do + local notice = self.notices[i] + + if (IsValid(notice) and !notice.bClosing) then + self:RemoveNotice(notice) + break + end + end + end + + return panel +end + +function PANEL:RemoveNotice(panel) + panel.bClosing = true + panel:CreateAnimation(animationTime, { + index = 3, + target = {outAnimation = 0}, + easing = "outQuint", + + OnComplete = function(animation, this) + local toRemove + + for k, v in ipairs(self.notices) do + if (v == this) then + toRemove = k + break + end + end + + if (toRemove) then + table.remove(self.notices, toRemove) + end + + this:SetText("") -- (hack) text remains for a frame after remove is called, so let's make sure we don't draw it + this:Remove() + end + }) +end + +-- update target Y positions and animations +function PANEL:Organize() + local currentTarget = self.padding + + for _, v in ipairs(self.notices) do + v:CreateAnimation(animationTime, { + index = 1, + target = {currentY = currentTarget}, + easing = "outElastic", + + Think = function(animation, panel) + panel:SetPos( + self:GetWide() - panel:GetWide() - self.padding, + math.min(panel.currentY + 1, currentTarget) -- easing eventually hits subpixel movement so we level it off + ) + end + }) + + currentTarget = currentTarget + self.padding + v:GetTall() + end +end + +vgui.Register("ixNoticeManager", PANEL, "Panel") + +-- notice panel +-- these do not manage their own enter/exit animations or lifetime +DEFINE_BASECLASS("DLabel") +PANEL = {} + +AccessorFunc(PANEL, "bError", "Error", FORCE_BOOL) +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) + +function PANEL:Init() + self:SetSize(256, 36) + self:SetContentAlignment(5) + self:SetExpensiveShadow(1, Color(0, 0, 0, 150)) + self:SetFont("ixNoticeFont") + self:SetTextColor(color_white) + self:SetDrawOnTop(true) + self:DockPadding(0, 0, 0, 0) + self:DockMargin(0, 0, 0, 0) + + self.bError = false + self.bHovered = false + + self.errorAnimation = 0 + self.padding = 8 + self.currentY = 0 + self.duration = 0 + self.outAnimation = 1 + self.alpha = 255 + + LocalPlayer():EmitSound("Helix.Notify") +end + +function PANEL:SetError(bValue) + self.bError = tobool(bValue) + + if (bValue) then + self.errorAnimation = 1 + self:CreateAnimation(animationTime, { + index = 5, + target = {errorAnimation = 0}, + easing = "outQuint" + }) + end +end + +function PANEL:SizeToContents() + local contentWidth, contentHeight = self:GetContentSize() + contentWidth = contentWidth + self.padding * 2 + contentHeight = contentHeight + self.padding * 2 + + local manager = ix.gui.notices + local maxWidth = math.min(IsValid(manager) and (manager:GetWide() - manager:GetPadding() * 2) or ScrW(), contentWidth) + + if (contentWidth > maxWidth) then + self:SetWide(maxWidth) + self:SetTextInset(self.padding * 2, 0) + self:SetWrap(true) + + self:SizeToContentsY() + self:SetWide(self:GetContentSize()) + else + self:SetSize(contentWidth, contentHeight) + end +end + +function PANEL:SizeToContentsY() + BaseClass.SizeToContentsY(self) + self:SetTall(self:GetTall() + self.padding * 2) +end + +function PANEL:OnMouseHover() + self:CreateAnimation(animationTime * 0.5, { + index = 4, + target = {alpha = 0}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(panel.alpha) + end + }) +end + +function PANEL:OnMouseLeave() + self:CreateAnimation(animationTime * 0.5, { + index = 4, + target = {alpha = 255}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(panel.alpha) + end + }) +end + +function PANEL:Paint(width, height) + if (self.outAnimation < 1) then + local x, y = self:LocalToScreen(0, 0) + render.SetScissorRect(x, y, x + self:GetWide(), y + (self:GetTall() * self.outAnimation), true) + end + + local x, y = self:LocalToScreen(0, 0) + local mouseX, mouseY = gui.MousePos() + + if (mouseX >= x and mouseX <= x + width and + mouseY >= y and mouseY <= y + height) then + if (!self.bHovered) then + self.bHovered = true + self:OnMouseHover() + end + elseif (self.bHovered) then + self.bHovered = false + self:OnMouseLeave() + end + + ix.util.DrawBlur(self) + + if (self.errorAnimation > 0) then + local color = derma.GetColor("Error", self) + + surface.SetDrawColor( + color.r * self.errorAnimation, + color.g * self.errorAnimation, + color.b * self.errorAnimation, + self.errorAnimation * 255 + ((1 - self.errorAnimation) * 66) + ) + else + surface.SetDrawColor(0, 0, 0, 66) + end + + surface.DrawRect(0, 0, width, height) + + surface.SetDrawColor(self.bError and derma.GetColor("Error", self) or ix.config.Get("color")) + surface.DrawRect(0, height - 1, width * self.duration, 1) +end + +function PANEL:PaintOver(width, height) + render.SetScissorRect(0, 0, 0, 0, false) +end + +vgui.Register("ixNotice", PANEL, "DLabel") + +if (IsValid(ix.gui.notices)) then + ix.gui.notices:Remove() + ix.gui.notices = vgui.Create("ixNoticeManager") +else + ix.gui.notices = vgui.Create("ixNoticeManager") +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_noticebar.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_noticebar.lua new file mode 100644 index 0000000..a66cc88 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_noticebar.lua @@ -0,0 +1,88 @@ + +local PANEL = { + types = { + "Info", -- info + "Success", -- success + "Error" -- error + } +} + +AccessorFunc(PANEL, "type", "Type", FORCE_NUMBER) +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) +AccessorFunc(PANEL, "length", "Length", FORCE_NUMBER) +AccessorFunc(PANEL, "hidden", "Hidden", FORCE_BOOL) + +function PANEL:Init() + self.type = 1 + self.padding = 8 + self.length = 4 + self.currentY = 0 + self.hidden = true + + self.text = self:Add("DLabel") + self.text:SetFont("ixNoticeFont") + self.text:SetContentAlignment(5) + self.text:SetTextColor(color_white) + self.text:SizeToContents() + self.text:Dock(FILL) + + self:SetSize(self:GetParent():GetWide() - (self.padding * 4), self.text:GetTall() + (self.padding * 2)) + self:SetPos(self.padding * 2, -self:GetTall() - self.padding) +end + +function PANEL:SetFont(value) + self.text:SetFont(value) + self.text:SizeToContents() +end + +function PANEL:SetText(text) + self.text:SetText(text) + self.text:SizeToContents() +end + +function PANEL:Slide(direction, length) + direction = direction or "up" + length = length or 0.5 + + timer.Remove("ixNoticeBarAnimation") + + local x, _ = self:GetPos() + local baseY = direction == "up" and self.padding * 2 or (-self:GetTall() - self.padding) + local targetY = direction == "up" and (-self:GetTall() - self.padding) or self.padding * 2 + local easing = direction == "up" and "outQuint" or "outElastic" + + self:SetPos(x, baseY) + self.currentY = baseY + self.hidden = direction == "up" + + self:CreateAnimation(length, { + target = {currentY = targetY}, + easing = easing, + + Think = function(animation, panel) + local lastX, _ = panel:GetPos() + panel:SetPos(lastX, panel.currentY) + end + }) +end + +function PANEL:Show(bRemove) + self:Slide("down") + + timer.Create("ixNoticeBarAnimation", self.length - 0.5, 1, function() + if (!IsValid(self)) then + return + end + + self:Slide("up") + end) +end + +function PANEL:Paint(width, height) + local color = derma.GetColor(self.types[self.type], self) + + surface.SetDrawColor(color) + surface.DrawRect(0, 0, width, height) +end + +vgui.Register("ixNoticeBar", PANEL, "Panel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_overrides.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_overrides.lua new file mode 100644 index 0000000..862b424 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_overrides.lua @@ -0,0 +1,247 @@ + +-- overrides standard derma panels to add/change functionality + +local PANEL = {} +local OVERRIDES = {} + +-- @todo remove me when autorefresh support is no longer needed +local function OverridePanel(name, func) + PANEL = vgui.GetControlTable(name) + + if (!istable(PANEL)) then + return + end + + OVERRIDES = {} + func() + + for k, _ in pairs(PANEL) do + local overrideName = "ix" .. k + + if (PANEL[overrideName] and !OVERRIDES[k]) then + print("unhooking override ", overrideName) + + PANEL[k] = PANEL[overrideName] + PANEL[overrideName] = nil + end + end +end + +local function Override(name) + local oldMethod = "ix" .. name + OVERRIDES[name] = true + + if (PANEL[oldMethod]) then + return + end + + PANEL[oldMethod] = PANEL[name] +end + +OverridePanel("DMenuOption", function() + function PANEL:PerformLayout() + self:SizeToContents() + self:SetWide(self:GetWide() + 30) + + local w = math.max(self:GetParent():GetWide(), self:GetWide()) + + self:SetSize(w, self:GetTall() + 4) + + if (self.SubMenuArrow) then + self.SubMenuArrow:SetSize(15, 15) + self.SubMenuArrow:CenterVertical() + self.SubMenuArrow:AlignRight(4) + end + + DButton.PerformLayout(self) + end +end) + +OverridePanel("DMenu", function() + local animationTime = 0.33 + + Override("Init") + function PANEL:Init(...) + self:ixInit(...) + + self.ixAnimation = 0 + end + + function PANEL:SetFont(font) + for _, v in pairs(self:GetCanvas():GetChildren()) do + v:SetFont(font) + v:SizeToContents() + end + + -- reposition for the new font + self:InvalidateLayout(true) + self:Open(self.ixX, self.ixY, false, self.ixOwnerPanel) + end + + Override("SetSize") + function PANEL:SetSize(width, height) + self:ixSetSize(width, height) + self.ixTargetHeight = height + end + + Override("PerformLayout") + function PANEL:PerformLayout(...) + self:ixPerformLayout(...) + + if (self.ixAnimating) then + self.VBar:SetAlpha(0) -- setvisible doesn't seem to work here + self:SetTall(self.ixAnimation * self.ixTargetHeight) + else + self.VBar:SetAlpha(255) + end + end + + Override("OnMouseWheeled") + function PANEL:OnMouseWheeled(delta) + self:ixOnMouseWheeled(delta) + + -- don't allow the input event to fall through + return true + end + + Override("AddOption") + function PANEL:AddOption(...) + local panel = self:ixAddOption(...) + + panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black)) + panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes + + return panel + end + + Override("AddSubMenu") + function PANEL:AddSubMenu(...) + local menu, panel = self:ixAddSubMenu(...) + + panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black)) + panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes + + return menu, panel + end + + Override("Open") + function PANEL:Open(x, y, bSkipAnimation, ownerPanel) + self.ixX, self.ixY, self.ixOwnerPanel = x, y, ownerPanel + self:ixOpen(x, y, bSkipAnimation, ownerPanel) + + if (ix.option.Get("disableAnimations")) then + return + end + + -- remove pac3 derma menu hooks since animations don't play nicely + hook.Remove("CloseDermaMenus", self) + hook.Remove("Think", self) + + self.ixAnimating = true + self:CreateAnimation(animationTime, { + index = 1, + target = {ixAnimation = 1}, + easing = "outQuint", + + Think = function(animation, panel) + panel:InvalidateLayout(true) + end, + + OnComplete = function(animation, panel) + panel.ixAnimating = nil + end + }) + end + + Override("Hide") + function PANEL:Hide() + if (ix.option.Get("disableAnimations")) then + self:ixHide() + return + end + + self.ixAnimating = true + self:SetVisible(true) + self:CreateAnimation(animationTime * 0.5, { + index = 1, + target = {ixAnimation = 0}, + easing = "outQuint", + + Think = function(animation, panel) + panel:InvalidateLayout(true) + end, + + OnComplete = function(animation, panel) + panel.ixAnimating = false + panel:ixHide() + end + }) + end + + Override("Remove") + function PANEL:Remove() + if (self.ixRemoving) then + return + end + + if (ix.option.Get("disableAnimations")) then + self:ixRemove() + return + end + + self.ixAnimating = true + self.ixRemoving = true + self:SetVisible(true) + + self:CreateAnimation(animationTime * 0.5, { + index = 1, + target = {ixAnimation = 0}, + easing = "outQuint", + + Think = function(animation, panel) + panel:InvalidateLayout(true) + end, + + OnComplete = function(animation, panel) + panel:ixRemove() + end + }) + end +end) + +OverridePanel("DComboBox", function() + Override("OpenMenu") + function PANEL:OpenMenu() + self:ixOpenMenu() + + if (IsValid(self.Menu)) then + local _, y = self.Menu:LocalToScreen(self.Menu:GetPos()) + + self.Menu:SetFont(self:GetFont()) + self.Menu:SetMaxHeight(ScrH() - y) + end + end +end) + +OverridePanel("DScrollPanel", function() + Override("ScrollToChild") + function PANEL:ScrollToChild(panel) + if (!IsValid(panel)) then + return + end + + -- docked panels required InvalidateParent in order to retrieve their position correctly + if (panel:GetDock() != NODOCK) then + panel:InvalidateParent(true) + else + self:PerformLayout() + end + + local _, y = self.pnlCanvas:GetChildPosition(panel) + + y = y + panel:GetTall() * 0.5 + y = y - self:GetTall() * 0.5 + + self.VBar:SetScroll(y) + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_scoreboard.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_scoreboard.lua new file mode 100644 index 0000000..60e06a9 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_scoreboard.lua @@ -0,0 +1,349 @@ +local rowPaintFunctions = { + function(width, height) + end, + + function(width, height) + surface.SetDrawColor(30, 30, 30, 25) + surface.DrawRect(0, 0, width, height) + end +} + +-- character icon +-- we can't customize the rendering of ModelImage so we have to do it ourselves +local PANEL = {} +local BODYGROUPS_EMPTY = "000000000" + +AccessorFunc(PANEL, "model", "Model", FORCE_STRING) +AccessorFunc(PANEL, "bHidden", "Hidden", FORCE_BOOL) + +function PANEL:Init() + self:SetSize(64, 64) + self.bodygroups = BODYGROUPS_EMPTY +end + +function PANEL:SetModel(model, skin, bodygroups) + model = model:gsub("\\", "/") + + if (isstring(bodygroups)) then + if (bodygroups:len() == 9) then + for i = 1, bodygroups:len() do + self:SetBodygroup(i, tonumber(bodygroups[i]) or 0) + end + else + self.bodygroups = BODYGROUPS_EMPTY + end + end + + self.model = model + self.skin = skin + self.path = "materials/spawnicons/" .. + model:sub(1, #model - 4) .. -- remove extension + ((isnumber(skin) and skin > 0) and ("_skin" .. tostring(skin)) or "") .. -- skin number + (self.bodygroups != BODYGROUPS_EMPTY and ("_" .. self.bodygroups) or "") .. -- bodygroups + ".png" + + local material = Material(self.path, "smooth") + + -- we don't have a cached spawnicon texture, so we need to forcefully generate one + if (material:IsError()) then + self.id = "ixScoreboardIcon" .. self.path + self.renderer = self:Add("ModelImage") + self.renderer:SetVisible(false) + self.renderer:SetModel(model, skin, self.bodygroups) + self.renderer:RebuildSpawnIcon() + + -- this is the only way to get a callback for generated spawn icons, it's bad but it's only done once + hook.Add("SpawniconGenerated", self.id, function(lastModel, filePath, modelsLeft) + filePath = filePath:gsub("\\", "/"):lower() + + if (filePath == self.path) then + hook.Remove("SpawniconGenerated", self.id) + + self.material = Material(filePath, "smooth") + self.renderer:Remove() + end + end) + else + self.material = material + end +end + +function PANEL:SetBodygroup(k, v) + if (k < 0 or k > 8 or v < 0 or v > 9) then + return + end + + self.bodygroups = self.bodygroups:SetChar(k + 1, v) +end + +function PANEL:GetModel() + return self.model or "models/error.mdl" +end + +function PANEL:GetSkin() + return self.skin or 1 +end + +function PANEL:DoClick() +end + +function PANEL:DoRightClick() +end + +function PANEL:OnMouseReleased(key) + if (key == MOUSE_LEFT) then + self:DoClick() + elseif (key == MOUSE_RIGHT) then + self:DoRightClick() + end +end + +function PANEL:Paint(width, height) + if (!self.material) then + return + end + + surface.SetMaterial(self.material) + surface.SetDrawColor(self.bHidden and color_black or color_white) + surface.DrawTexturedRect(0, 0, width, height) +end + +function PANEL:Remove() + if (self.id) then + hook.Remove("SpawniconGenerated", self.id) + end +end + +vgui.Register("ixScoreboardIcon", PANEL, "Panel") + +-- player row +PANEL = {} + +AccessorFunc(PANEL, "paintFunction", "BackgroundPaintFunction") + +function PANEL:Init() + self:SetTall(64) + + self.icon = self:Add("ixScoreboardIcon") + self.icon:Dock(LEFT) + self.icon.DoRightClick = function() + local client = self.player + + if (!IsValid(client)) then + return + end + + local menu = DermaMenu() + + menu:AddOption(L("viewProfile"), function() + client:ShowProfile() + end) + + menu:AddOption(L("copySteamID"), function() + SetClipboardText(client:IsBot() and client:EntIndex() or client:SteamID()) + end) + + hook.Run("PopulateScoreboardPlayerMenu", client, menu) + menu:Open() + end + + self.icon:SetHelixTooltip(function(tooltip) + local client = self.player + + if (IsValid(self) and IsValid(client)) then + ix.hud.PopulatePlayerTooltip(tooltip, client) + end + end) + + self.name = self:Add("DLabel") + self.name:DockMargin(4, 4, 0, 0) + self.name:Dock(TOP) + self.name:SetTextColor(color_white) + self.name:SetFont("ixGenericFont") + + self.description = self:Add("DLabel") + self.description:DockMargin(5, 0, 0, 0) + self.description:Dock(TOP) + self.description:SetTextColor(color_white) + self.description:SetFont("ixSmallFont") + + self.paintFunction = rowPaintFunctions[1] + self.nextThink = CurTime() + 1 +end + +function PANEL:Update() + local client = self.player + local model = client:GetModel() + local skin = client:GetSkin() + local name = client:GetName() + local description = hook.Run("GetCharacterDescription", client) or + (client:GetCharacter() and client:GetCharacter():GetDescription()) or "" + + local bRecognize = false + local localCharacter = LocalPlayer():GetCharacter() + local character = IsValid(self.player) and self.player:GetCharacter() + + if (localCharacter and character) then + bRecognize = hook.Run("IsCharacterRecognized", localCharacter, character:GetID()) + or hook.Run("IsPlayerRecognized", self.player) + end + + self.icon:SetHidden(!bRecognize) + self:SetZPos(bRecognize and 1 or 2) + + -- no easy way to check bodygroups so we'll just set them anyway + for _, v in pairs(client:GetBodyGroups()) do + self.icon:SetBodygroup(v.id, client:GetBodygroup(v.id)) + end + + if (self.icon:GetModel() != model or self.icon:GetSkin() != skin) then + self.icon:SetModel(model, skin) + self.icon:SetTooltip(nil) + end + + if (self.name:GetText() != name) then + self.name:SetText(name) + self.name:SizeToContents() + end + + if (self.description:GetText() != description) then + self.description:SetText(description) + self.description:SizeToContents() + end +end + +function PANEL:Think() + if (CurTime() >= self.nextThink) then + local client = self.player + + if (!IsValid(client) or !client:GetCharacter() or self.character != client:GetCharacter() or self.team != client:Team()) then + self:Remove() + self:GetParent():SizeToContents() + end + + self.nextThink = CurTime() + 1 + end +end + +function PANEL:SetPlayer(client) + self.player = client + self.team = client:Team() + self.character = client:GetCharacter() + + self:Update() +end + +function PANEL:Paint(width, height) + self.paintFunction(width, height) +end + +vgui.Register("ixScoreboardRow", PANEL, "EditablePanel") + +-- faction grouping +PANEL = {} + +AccessorFunc(PANEL, "faction", "Faction") + +function PANEL:Init() + self:DockMargin(0, 0, 0, 16) + self:SetTall(32) + + self.nextThink = 0 +end + +function PANEL:AddPlayer(client, index) + if (!IsValid(client) or !client:GetCharacter() or hook.Run("ShouldShowPlayerOnScoreboard", client) == false) then + return false + end + + local id = index % 2 == 0 and 1 or 2 + local panel = self:Add("ixScoreboardRow") + panel:SetPlayer(client) + panel:Dock(TOP) + panel:SetZPos(2) + panel:SetBackgroundPaintFunction(rowPaintFunctions[id]) + + self:SizeToContents() + client.ixScoreboardSlot = panel + + return true +end + +function PANEL:SetFaction(faction) + self:SetColor(faction.color) + self:SetText(L(faction.name)) + + self.faction = faction +end + +function PANEL:Update() + local faction = self.faction + + if (team.NumPlayers(faction.index) == 0) then + self:SetVisible(false) + self:GetParent():InvalidateLayout() + else + local bHasPlayers + + for k, v in ipairs(team.GetPlayers(faction.index)) do + if (!IsValid(v.ixScoreboardSlot)) then + if (self:AddPlayer(v, k)) then + bHasPlayers = true + end + else + v.ixScoreboardSlot:Update() + bHasPlayers = true + end + end + + self:SetVisible(bHasPlayers) + end +end + +vgui.Register("ixScoreboardFaction", PANEL, "ixCategoryPanel") + +-- main scoreboard panel +PANEL = {} + +function PANEL:Init() + if (IsValid(ix.gui.scoreboard)) then + ix.gui.scoreboard:Remove() + end + + self:Dock(FILL) + + self.factions = {} + self.nextThink = 0 + + for i = 1, #ix.faction.indices do + local faction = ix.faction.indices[i] + + local panel = self:Add("ixScoreboardFaction") + panel:SetFaction(faction) + panel:Dock(TOP) + + self.factions[i] = panel + end + + ix.gui.scoreboard = self +end + +function PANEL:Think() + if (CurTime() >= self.nextThink) then + for i = 1, #self.factions do + local factionPanel = self.factions[i] + + factionPanel:Update() + end + + self.nextThink = CurTime() + 0.5 + end +end + +vgui.Register("ixScoreboard", PANEL, "DScrollPanel") + +hook.Add("CreateMenuButtons", "ixScoreboard", function(tabs) + tabs["scoreboard"] = function(container) + container:Add("ixScoreboard") + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_settings.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_settings.lua new file mode 100644 index 0000000..23e120f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_settings.lua @@ -0,0 +1,771 @@ + +local panelMap = { + [ix.type.bool] = "ixSettingsRowBool", + [ix.type.array] = "ixSettingsRowArray", + [ix.type.string] = "ixSettingsRowString", + [ix.type.number] = "ixSettingsRowNumber", + [ix.type.color] = "ixSettingsRowColor" +} + +local function EmitChange(pitch) + LocalPlayer():EmitSound("weapons/ar2/ar2_empty.wav", 75, pitch or 150, 0.25) +end + +-- color setting +local PANEL = {} + +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) + +function PANEL:Init() + self.color = table.Copy(color_white) + self.padding = 4 + + self.panel = self:Add("Panel") + self.panel:SetCursor("hand") + self.panel:SetMouseInputEnabled(true) + self.panel:Dock(RIGHT) + self.panel.Paint = function(panel, width, height) + local padding = self.padding + + surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) + surface.DrawRect(0, 0, width, height) + + surface.SetDrawColor(self.color) + surface.DrawRect(padding, padding, width - padding * 2, height - padding * 2) + end + + self.panel.OnMousePressed = function(panel, key) + if (key == MOUSE_LEFT) then + self:OpenPicker() + end + end +end + +function PANEL:OpenPicker() + if (IsValid(self.picker)) then + self.picker:Remove() + return + end + + self.picker = vgui.Create("ixSettingsRowColorPicker") + self.picker:Attach(self) + self.picker:SetValue(self.color) + + self.picker.OnValueChanged = function(panel) + local newColor = panel:GetValue() + + if (newColor != self.color) then + self.color = newColor + self:OnValueChanged(newColor) + end + end + + self.picker.OnValueUpdated = function(panel) + self.color = panel:GetValue() + end +end + +function PANEL:SetValue(value) + self.color = Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255) +end + +function PANEL:GetValue() + return self.color +end + +function PANEL:PerformLayout(width, height) + surface.SetFont("ixMenuButtonFont") + local totalWidth = surface.GetTextSize("999") + + self.panel:SetSize(totalWidth + self.padding * 2, height) +end + +vgui.Register("ixSettingsRowColor", PANEL, "ixSettingsRow") + +-- color setting picker +DEFINE_BASECLASS("Panel") +PANEL = {} + +AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL) + +function PANEL:Init() + self.m_bIsMenuComponent = true + self.bDeleteSelf = true + + self.realHeight = 200 + self.height = 200 + self:SetSize(250, 200) + self:DockPadding(4, 4, 4, 4) + + self.picker = self:Add("DColorMixer") + self.picker:Dock(FILL) + self.picker.ValueChanged = function() + self:OnValueUpdated() + end + + self:MakePopup() + RegisterDermaMenuForClose(self) +end + +function PANEL:SetValue(value) + self.picker:SetColor(Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255)) +end + +function PANEL:GetValue() + return self.picker:GetColor() +end + +function PANEL:Attach(panel) + self.attached = panel +end + +function PANEL:Think() + local panel = self.attached + + if (IsValid(panel)) then + local width, height = self:GetSize() + local x, y = panel:LocalToScreen(0, 0) + + self:SetPos( + math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width), + math.Clamp(y + panel:GetTall(), 0, ScrH() - height) + ) + end +end + +function PANEL:Paint(width, height) + surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) + surface.DrawRect(0, 0, width, height) +end + +function PANEL:OnValueChanged() +end + +function PANEL:OnValueUpdated() +end + +function PANEL:Remove() + if (self.bClosing) then + return + end + + self:OnValueChanged() + + -- @todo open/close animations + self.bClosing = true + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + BaseClass.Remove(self) +end + +vgui.Register("ixSettingsRowColorPicker", PANEL, "EditablePanel") + +-- number setting +PANEL = {} + +function PANEL:Init() + self.setting = self:Add("ixNumSlider") + self.setting.nextUpdate = 0 + self.setting:Dock(RIGHT) + self.setting.OnValueChanged = function(panel) + self:OnValueChanged(self:GetValue()) + end + self.setting.OnValueUpdated = function(panel) + local fraction = panel:GetFraction() + + if (fraction == 0) then + EmitChange(75) + return + elseif (fraction == 1) then + EmitChange(120) + return + end + + if (SysTime() > panel.nextUpdate) then + EmitChange(85 + fraction * 15) + panel.nextUpdate = SysTime() + 0.05 + end + end + + local panel = self.setting:GetLabel() + panel:SetCursor("hand") + panel:SetMouseInputEnabled(true) + panel.OnMousePressed = function(_, key) + if (key == MOUSE_LEFT) then + self:OpenEntry() + end + end +end + +function PANEL:OpenEntry() + if (IsValid(self.entry)) then + self.entry:Remove() + return + end + + self.entry = vgui.Create("ixSettingsRowNumberEntry") + self.entry:Attach(self) + self.entry:SetValue(self:GetValue(), true) + self.entry.OnValueChanged = function(panel) + local value = math.Round(panel:GetValue(), self:GetDecimals()) + + if (value != self:GetValue()) then + self:SetValue(value, true) + self:OnValueChanged(value) + end + end +end + +function PANEL:SetValue(value, bNoNotify) + self.setting:SetValue(value, bNoNotify) +end + +function PANEL:GetValue() + return self.setting:GetValue() +end + +function PANEL:SetMin(value) + self.setting:SetMin(value) +end + +function PANEL:SetMax(value) + self.setting:SetMax(value) +end + +function PANEL:SetDecimals(value) + self.setting:SetDecimals(value) +end + +function PANEL:GetDecimals() + return self.setting:GetDecimals() +end + +function PANEL:PerformLayout(width, height) + self.setting:SetWide(width * 0.5) +end + +vgui.Register("ixSettingsRowNumber", PANEL, "ixSettingsRow") + +-- number setting entry +DEFINE_BASECLASS("Panel") +PANEL = {} + +AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL) + +function PANEL:Init() + surface.SetFont("ixMenuButtonFont") + local width, height = surface.GetTextSize("999999") + + self.m_bIsMenuComponent = true + self.bDeleteSelf = true + + self.realHeight = 200 + self.height = 200 + self:SetSize(width, height) + self:DockPadding(4, 4, 4, 4) + + self.textEntry = self:Add("ixTextEntry") + self.textEntry:SetNumeric(true) + self.textEntry:SetFont("ixMenuButtonFont") + self.textEntry:Dock(FILL) + self.textEntry:RequestFocus() + self.textEntry.OnEnter = function() + self:Remove() + end + + self:MakePopup() + RegisterDermaMenuForClose(self) +end + +function PANEL:SetValue(value, bInitial) + value = tostring(value) + self.textEntry:SetValue(value) + + if (bInitial) then + self.textEntry:SetCaretPos(value:utf8len()) + end +end + +function PANEL:GetValue() + return tonumber(self.textEntry:GetValue()) or 0 +end + +function PANEL:Attach(panel) + self.attached = panel +end + +function PANEL:Think() + local panel = self.attached + + if (IsValid(panel)) then + local width, height = self:GetSize() + local x, y = panel:LocalToScreen(0, 0) + + self:SetPos( + math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width), + math.Clamp(y + panel:GetTall(), 0, ScrH() - height) + ) + end +end + +function PANEL:Paint(width, height) + surface.SetDrawColor(derma.GetColor("DarkerBackground", self)) + surface.DrawRect(0, 0, width, height) +end + +function PANEL:OnValueChanged() +end + +function PANEL:OnValueUpdated() +end + +function PANEL:Remove() + if (self.bClosing) then + return + end + + self:OnValueChanged() + + -- @todo open/close animations + self.bClosing = true + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + BaseClass.Remove(self) +end + +vgui.Register("ixSettingsRowNumberEntry", PANEL, "EditablePanel") + +-- string setting +PANEL = {} + +function PANEL:Init() + self.setting = self:Add("ixTextEntry") + self.setting:Dock(RIGHT) + self.setting:SetFont("ixMenuButtonFont") + self.setting:SetBackgroundColor(derma.GetColor("DarkerBackground", self)) + self.setting.OnEnter = function() + self:OnValueChanged(self:GetValue()) + end +end + +function PANEL:SetValue(value) + self.setting:SetValue(tostring(value)) +end + +function PANEL:GetValue() + return self.setting:GetValue() +end + +function PANEL:PerformLayout(width, height) + self.setting:SetWide(width * 0.5) +end + +vgui.Register("ixSettingsRowString", PANEL, "ixSettingsRow") + +-- bool setting +PANEL = {} + +function PANEL:Init() + self.setting = self:Add("ixCheckBox") + self.setting:Dock(RIGHT) + self.setting.DoClick = function(panel) + self:OnValueChanged(self:GetValue()) + end +end + +function PANEL:SetValue(bValue) + bValue = tobool(bValue) + + self.setting:SetChecked(bValue, true) +end + +function PANEL:GetValue() + return self.setting:GetChecked() +end + +vgui.Register("ixSettingsRowBool", PANEL, "ixSettingsRow") + +-- array setting +PANEL = {} + +function PANEL:Init() + self.array = {} + + self.setting = self:Add("DComboBox") + self.setting:Dock(RIGHT) + self.setting:SetFont("ixMenuButtonFont") + self.setting:SetTextColor(color_white) + self.setting.OnSelect = function(panel) + self:OnValueChanged(self:GetValue()) + + panel:SizeToContents() + panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice) + + if (!self.bInitial) then + EmitChange() + end + end +end + +function PANEL:Populate(key, info) + if (!isfunction(info.populate)) then + ErrorNoHalt(string.format("expected populate function for array option '%s'", key)) + return + end + + local entries = info.populate() + local i = 1 + + for k, v in pairs(entries) do + self.setting:AddChoice(v, k) + self.array[k] = i + + i = i + 1 + end +end + +function PANEL:SetValue(value) + self.bInitial = true + self.setting:ChooseOptionID(self.array[value]) + self.bInitial = false +end + +function PANEL:GetValue() + return select(2, self.setting:GetSelected()) +end + +vgui.Register("ixSettingsRowArray", PANEL, "ixSettingsRow") + +-- settings row +PANEL = {} + +AccessorFunc(PANEL, "backgroundIndex", "BackgroundIndex", FORCE_NUMBER) +AccessorFunc(PANEL, "bShowReset", "ShowReset", FORCE_BOOL) + +function PANEL:Init() + self:DockPadding(4, 4, 4, 4) + + self.text = self:Add("DLabel") + self.text:Dock(LEFT) + self.text:SetFont("ixMenuButtonFont") + self.text:SetExpensiveShadow(1, color_black) + + self.backgroundIndex = 0 +end + +function PANEL:SetShowReset(value, name, default) + value = tobool(value) + + if (value and !IsValid(self.reset)) then + self.reset = self:Add("DButton") + self.reset:SetFont("ixSmallTitleIcons") + self.reset:SetText("x") + self.reset:SetTextColor(ColorAlpha(derma.GetColor("Warning", self), 100)) + self.reset:Dock(LEFT) + self.reset:DockMargin(4, 0, 0, 0) + self.reset:SizeToContents() + self.reset.Paint = nil + self.reset.DoClick = function() + self:OnResetClicked() + end + self.reset:SetHelixTooltip(function(tooltip) + local title = tooltip:AddRow("title") + title:SetImportant() + title:SetText(L("resetDefault")) + title:SetBackgroundColor(derma.GetColor("Warning", self)) + title:SizeToContents() + + local description = tooltip:AddRow("description") + description:SetText(L("resetDefaultDescription", tostring(name), tostring(default))) + description:SizeToContents() + end) + elseif (!value and IsValid(self.reset)) then + self.reset:Remove() + end + + self.bShowReset = value +end + +function PANEL:Think() + if (IsValid(self.reset)) then + self.reset:SetVisible(self:IsHovered() or self:IsOurChild(vgui.GetHoveredPanel())) + end +end + +function PANEL:OnResetClicked() +end + +function PANEL:GetLabel() + return self.text +end + +function PANEL:SetText(text) + self.text:SetText(text) + self:SizeToContents() +end + +function PANEL:GetText() + return self.text:GetText() +end + +-- implemented by row types +function PANEL:GetValue() +end + +function PANEL:SetValue(value) +end + +-- meant for array types to populate combo box values +function PANEL:Populate(key, info) +end + +-- called when value is changed by user +function PANEL:OnValueChanged(newValue) +end + +function PANEL:SizeToContents() + local _, top, _, bottom = self:GetDockPadding() + + self.text:SizeToContents() + self:SetTall(self.text:GetTall() + top + bottom) + self.ixRealHeight = self:GetTall() + self.ixHeight = self.ixRealHeight +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintSettingsRowBackground", self, width, height) +end + +vgui.Register("ixSettingsRow", PANEL, "EditablePanel") + +-- settings panel +PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self.rows = {} + self.categories = {} + + -- scroll panel + DEFINE_BASECLASS("DScrollPanel") + + self.canvas = self:Add("DScrollPanel") + self.canvas:Dock(FILL) + self.canvas.PerformLayout = function(panel) + BaseClass.PerformLayout(panel) + + if (!panel.VBar.Enabled) then + panel.pnlCanvas:SetWide(panel:GetWide() - panel.VBar:GetWide()) + end + end +end + +function PANEL:GetRowPanelName(type) + return panelMap[type] or "ixSettingsRow" +end + +function PANEL:AddCategory(name) + local panel = self.categories[name] + + if (!IsValid(panel)) then + panel = self.canvas:Add("ixCategoryPanel") + panel:SetText(name) + panel:Dock(TOP) + panel:DockMargin(0, 8, 0, 0) + + self.categories[name] = panel + return panel + end +end + +function PANEL:AddRow(type, category) + category = self.categories[category] + local id = panelMap[type] + + if (!id) then + ErrorNoHalt("attempted to create row with unimplemented type '" .. tostring(ix.type[type]) .. "'\n") + id = "ixSettingsRow" + end + + local panel = (IsValid(category) and category or self.canvas):Add(id) + panel:Dock(TOP) + panel:SetBackgroundIndex(#self.rows % 2) + + self.rows[#self.rows + 1] = panel + return panel +end + +function PANEL:GetRows() + return self.rows +end + +function PANEL:Clear() + for _, v in ipairs(self.rows) do + if (IsValid(v)) then + v:Remove() + end + end + + self.rows = {} +end + +function PANEL:SetSearchEnabled(bValue) + if (!bValue) then + if (IsValid(self.searchEntry)) then + self.searchEntry:Remove() + end + + return + end + + -- search entry + self.searchEntry = self:Add("ixIconTextEntry") + self.searchEntry:Dock(TOP) + self.searchEntry:SetEnterAllowed(false) + + self.searchEntry.OnChange = function(entry) + self:FilterRows(entry:GetValue()) + end +end + +function PANEL:FilterRows(query) + query = string.PatternSafe(query:lower()) + + local bEmpty = query == "" + + for categoryName, category in pairs(self.categories) do + category.size = 0 + category:CreateAnimation(0.5, { + index = 21, + target = {size = 1}, + + Think = function(animation, panel) + panel:SizeToContents() + end + }) + + for _, row in ipairs(category:GetChildren()) do + local bFound = bEmpty or row:GetText():lower():find(query) or categoryName:lower():find(query) + + row:SetVisible(true) + row:CreateAnimation(0.5, { + index = 21, + target = {ixHeight = bFound and row.ixRealHeight or 0}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetTall(bFound and math.min(panel.ixHeight + 2, panel.ixRealHeight) or math.max(panel.ixHeight - 2, 0)) + end, + + OnComplete = function(animation, panel) + panel:SetVisible(bFound) + + -- need this so categories are sized properly when animations are disabled - there is no guaranteed order + -- that animations will think so we SizeToContents here. putting it here will result in redundant calls but + -- I guess we have the performance to spare + if (ix.option.Get("disableAnimations", false)) then + category:SizeToContents() + end + end + }) + end + end +end + +function PANEL:Paint(width, height) +end + +function PANEL:SizeToContents() + for _, v in pairs(self.categories) do + v:SizeToContents() + end +end + +vgui.Register("ixSettings", PANEL, "Panel") + +hook.Add("CreateMenuButtons", "ixSettings", function(tabs) + tabs["settings"] = { + PopulateTabButton = function(info, button) + local menu = ix.gui.menu + + if (!IsValid(menu)) then + return + end + + DEFINE_BASECLASS("ixMenuButton") + button:SetZPos(9999) + button.Paint = function(panel, width, height) + BaseClass.Paint(panel, width, height) + + surface.SetDrawColor(255, 255, 255, 33) + surface.DrawRect(0, 0, width, 1) + end + end, + + Create = function(info, container) + local panel = container:Add("ixSettings") + panel:SetSearchEnabled(true) + + for category, options in SortedPairs(ix.option.GetAllByCategories(true)) do + category = L(category) + panel:AddCategory(category) + + -- sort options by language phrase rather than the key + table.sort(options, function(a, b) + return L(a.phrase) < L(b.phrase) + end) + + for _, data in pairs(options) do + local key = data.key + local row = panel:AddRow(data.type, category) + local value = ix.util.SanitizeType(data.type, ix.option.Get(key)) + + row:SetText(L(data.phrase)) + row:Populate(key, data) + + -- type-specific properties + if (data.type == ix.type.number) then + row:SetMin(data.min or 0) + row:SetMax(data.max or 10) + row:SetDecimals(data.decimals or 0) + end + + row:SetValue(value, true) + row:SetShowReset(value != data.default, key, data.default) + row.OnValueChanged = function() + local newValue = row:GetValue() + + row:SetShowReset(newValue != data.default, key, data.default) + ix.option.Set(key, newValue) + end + + row.OnResetClicked = function() + row:SetShowReset(false) + row:SetValue(data.default, true) + + ix.option.Set(key, data.default) + end + + row:GetLabel():SetHelixTooltip(function(tooltip) + local title = tooltip:AddRow("name") + title:SetImportant() + title:SetText(key) + title:SizeToContents() + title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5)) + + local description = tooltip:AddRow("description") + description:SetText(L(data.description)) + description:SizeToContents() + end) + end + end + + panel:SizeToContents() + container.panel = panel + end, + + OnSelected = function(info, container) + container.panel.searchEntry:RequestFocus() + end + } +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_shipment.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_shipment.lua new file mode 100644 index 0000000..0c6e926 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_shipment.lua @@ -0,0 +1,120 @@ + +local PANEL = {} + +function PANEL:Init() + self:SetSize(460, 360) + self:SetTitle(L"shipment") + self:Center() + self:MakePopup() + + self.scroll = self:Add("DScrollPanel") + self.scroll:Dock(FILL) + + self.list = self.scroll:Add("DListLayout") + self.list:Dock(FILL) +end + +function PANEL:SetItems(entity, items) + self.entity = entity + self.items = true + self.itemPanels = {} + + for k, v in SortedPairs(items) do + local itemTable = ix.item.list[k] + + if (itemTable) then + local item = self.list:Add("DPanel") + item:SetTall(36) + item:Dock(TOP) + item:DockMargin(4, 4, 4, 0) + + item.icon = item:Add("SpawnIcon") + item.icon:SetPos(2, 2) + item.icon:SetSize(32, 32) + item.icon:SetModel(itemTable:GetModel()) + item.icon:SetHelixTooltip(function(tooltip) + ix.hud.PopulateItemTooltip(tooltip, itemTable) + end) + + item.quantity = item.icon:Add("DLabel") + item.quantity:SetSize(32, 32) + item.quantity:SetContentAlignment(3) + item.quantity:SetTextInset(0, 0) + item.quantity:SetText(v) + item.quantity:SetFont("DermaDefaultBold") + item.quantity:SetExpensiveShadow(1, Color(0, 0, 0, 150)) + + item.name = item:Add("DLabel") + item.name:SetPos(38, 0) + item.name:SetSize(200, 36) + item.name:SetFont("ixSmallFont") + item.name:SetText(L(itemTable.name)) + item.name:SetContentAlignment(4) + item.name:SetTextColor(color_white) + + item.take = item:Add("DButton") + item.take:Dock(RIGHT) + item.take:SetText(L"take") + item.take:SetWide(48) + item.take:DockMargin(3, 3, 3, 3) + item.take:SetTextColor(color_white) + item.take.DoClick = function(this) + net.Start("ixShipmentUse") + net.WriteString(k) + net.WriteBool(false) + net.SendToServer() + + items[k] = items[k] - 1 + + item.quantity:SetText(items[k]) + + if (items[k] <= 0) then + item:Remove() + items[k] = nil + end + + if (table.IsEmpty(items)) then + self:Remove() + end + end + + item.drop = item:Add("DButton") + item.drop:Dock(RIGHT) + item.drop:SetText(L"drop") + item.drop:SetWide(48) + item.drop:DockMargin(3, 3, 0, 3) + item.drop:SetTextColor(color_white) + item.drop.DoClick = function(this) + net.Start("ixShipmentUse") + net.WriteString(k) + net.WriteBool(true) + net.SendToServer() + + items[k] = items[k] - 1 + + item.quantity:SetText(items[k]) + + if (items[k] <= 0) then + item:Remove() + end + end + + self.itemPanels[k] = item + end + end +end + +function PANEL:Close() + net.Start("ixShipmentClose") + net.SendToServer() + + self:Remove() +end + +function PANEL:Think() + if (self.items and !IsValid(self.entity)) then + self:Remove() + end +end + +vgui.Register("ixShipment", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_spawnicon.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_spawnicon.lua new file mode 100644 index 0000000..4b7fb15 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_spawnicon.lua @@ -0,0 +1,97 @@ + +DEFINE_BASECLASS("DModelPanel") + +local PANEL = {} + +function PANEL:Init() + self.defaultEyeTarget = Vector(0, 0, 64) + self:SetHidden(false) + + for i = 0, 5 do + if (i == 1 or i == 5) then + self:SetDirectionalLight(i, Color(155, 155, 155)) + else + self:SetDirectionalLight(i, Color(255, 255, 255)) + end + end +end + +function PANEL:SetModel(model, skin, hidden) + BaseClass.SetModel(self, model) + + local entity = self.Entity + + if (skin) then + entity:SetSkin(skin) + end + + local sequence = entity:SelectWeightedSequence(ACT_IDLE) + + if (sequence <= 0) then + sequence = entity:LookupSequence("idle_unarmed") + end + + if (sequence > 0) then + entity:ResetSequence(sequence) + else + local found = false + + for _, v in ipairs(entity:GetSequenceList()) do + if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then + entity:ResetSequence(v) + found = true + + break + end + end + + if (!found) then + entity:ResetSequence(4) + end + end + + local data = PositionSpawnIcon(entity, entity:GetPos()) + + if (data) then + self:SetFOV(data.fov) + self:SetCamPos(data.origin) + self:SetLookAng(data.angles) + end + + entity:SetIK(false) + entity:SetEyeTarget(self.defaultEyeTarget) +end + +function PANEL:SetHidden(hidden) + if (hidden) then + self:SetAmbientLight(color_black) + self:SetColor(Color(0, 0, 0)) + + for i = 0, 5 do + self:SetDirectionalLight(i, color_black) + end + else + self:SetAmbientLight(Color(20, 20, 20)) + self:SetAlpha(255) + + for i = 0, 5 do + if (i == 1 or i == 5) then + self:SetDirectionalLight(i, Color(155, 155, 155)) + else + self:SetDirectionalLight(i, Color(255, 255, 255)) + end + end + end +end + +function PANEL:LayoutEntity() + self:RunAnimation() +end + +function PANEL:OnMousePressed() + if (self.DoClick) then + self:DoClick() + end +end + +vgui.Register("ixSpawnIcon", PANEL, "DModelPanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_storage.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_storage.lua new file mode 100644 index 0000000..e0109b4 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_storage.lua @@ -0,0 +1,197 @@ + +local PANEL = {} + +AccessorFunc(PANEL, "money", "Money", FORCE_NUMBER) + +function PANEL:Init() + self:DockPadding(1, 1, 1, 1) + self:SetTall(64) + self:Dock(BOTTOM) + + self.moneyLabel = self:Add("DLabel") + self.moneyLabel:Dock(TOP) + self.moneyLabel:SetFont("ixGenericFont") + self.moneyLabel:SetText("") + self.moneyLabel:SetTextInset(2, 0) + self.moneyLabel:SizeToContents() + self.moneyLabel.Paint = function(panel, width, height) + derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ix.config.Get("color")) + end + + self.amountEntry = self:Add("ixTextEntry") + self.amountEntry:Dock(FILL) + self.amountEntry:SetFont("ixGenericFont") + self.amountEntry:SetNumeric(true) + self.amountEntry:SetValue("0") + + self.transferButton = self:Add("DButton") + self.transferButton:SetFont("ixIconsMedium") + self:SetLeft(false) + self.transferButton.DoClick = function() + local amount = math.max(0, math.Round(tonumber(self.amountEntry:GetValue()) or 0)) + self.amountEntry:SetValue("0") + + if (amount != 0) then + self:OnTransfer(amount) + end + end + + self.bNoBackgroundBlur = true +end + +function PANEL:SetLeft(bValue) + if (bValue) then + self.transferButton:Dock(LEFT) + self.transferButton:SetText("s") + else + self.transferButton:Dock(RIGHT) + self.transferButton:SetText("t") + end +end + +function PANEL:SetMoney(money) + local name = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "") + + self.money = math.max(math.Round(tonumber(money) or 0), 0) + self.moneyLabel:SetText(string.format("%s: %d", name, money)) +end + +function PANEL:OnTransfer(amount) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintBaseFrame", self, width, height) +end + +vgui.Register("ixStorageMoney", PANEL, "EditablePanel") + +DEFINE_BASECLASS("Panel") +PANEL = {} + +AccessorFunc(PANEL, "fadeTime", "FadeTime", FORCE_NUMBER) +AccessorFunc(PANEL, "frameMargin", "FrameMargin", FORCE_NUMBER) +AccessorFunc(PANEL, "storageID", "StorageID", FORCE_NUMBER) + +function PANEL:Init() + if (IsValid(ix.gui.openedStorage)) then + ix.gui.openedStorage:Remove() + end + + ix.gui.openedStorage = self + + self:SetSize(ScrW(), ScrH()) + self:SetPos(0, 0) + self:SetFadeTime(0.25) + self:SetFrameMargin(4) + + self.storageInventory = self:Add("ixInventory") + self.storageInventory.bNoBackgroundBlur = true + self.storageInventory:ShowCloseButton(true) + self.storageInventory:SetTitle("Storage") + self.storageInventory.Close = function(this) + net.Start("ixStorageClose") + net.SendToServer() + self:Remove() + end + + self.storageMoney = self.storageInventory:Add("ixStorageMoney") + self.storageMoney:SetVisible(false) + self.storageMoney.OnTransfer = function(_, amount) + net.Start("ixStorageMoneyTake") + net.WriteUInt(self.storageID, 32) + net.WriteUInt(amount, 32) + net.SendToServer() + end + + ix.gui.inv1 = self:Add("ixInventory") + ix.gui.inv1.bNoBackgroundBlur = true + ix.gui.inv1:ShowCloseButton(true) + ix.gui.inv1.Close = function(this) + net.Start("ixStorageClose") + net.SendToServer() + self:Remove() + end + + self.localMoney = ix.gui.inv1:Add("ixStorageMoney") + self.localMoney:SetVisible(false) + self.localMoney:SetLeft(true) + self.localMoney.OnTransfer = function(_, amount) + net.Start("ixStorageMoneyGive") + net.WriteUInt(self.storageID, 32) + net.WriteUInt(amount, 32) + net.SendToServer() + end + + self:SetAlpha(0) + self:AlphaTo(255, self:GetFadeTime()) + + self.storageInventory:MakePopup() + ix.gui.inv1:MakePopup() +end + +function PANEL:OnChildAdded(panel) + panel:SetPaintedManually(true) +end + +function PANEL:SetLocalInventory(inventory) + if (IsValid(ix.gui.inv1) and !IsValid(ix.gui.menu)) then + ix.gui.inv1:SetInventory(inventory) + ix.gui.inv1:SetPos(self:GetWide() / 2 + self:GetFrameMargin() / 2, self:GetTall() / 2 - ix.gui.inv1:GetTall() / 2) + end +end + +function PANEL:SetLocalMoney(money) + if (!self.localMoney:IsVisible()) then + self.localMoney:SetVisible(true) + ix.gui.inv1:SetTall(ix.gui.inv1:GetTall() + self.localMoney:GetTall() + 2) + end + + self.localMoney:SetMoney(money) +end + +function PANEL:SetStorageTitle(title) + self.storageInventory:SetTitle(title) +end + +function PANEL:SetStorageInventory(inventory) + self.storageInventory:SetInventory(inventory) + self.storageInventory:SetPos( + self:GetWide() / 2 - self.storageInventory:GetWide() - 2, + self:GetTall() / 2 - self.storageInventory:GetTall() / 2 + ) + + ix.gui["inv" .. inventory:GetID()] = self.storageInventory +end + +function PANEL:SetStorageMoney(money) + if (!self.storageMoney:IsVisible()) then + self.storageMoney:SetVisible(true) + self.storageInventory:SetTall(self.storageInventory:GetTall() + self.storageMoney:GetTall() + 2) + end + + self.storageMoney:SetMoney(money) +end + +function PANEL:Paint(width, height) + ix.util.DrawBlurAt(0, 0, width, height) + + for _, v in ipairs(self:GetChildren()) do + v:PaintManual() + end +end + +function PANEL:Remove() + self:SetAlpha(255) + self:AlphaTo(0, self:GetFadeTime(), 0, function() + BaseClass.Remove(self) + end) +end + +function PANEL:OnRemove() + if (!IsValid(ix.gui.menu)) then + self.storageInventory:Remove() + ix.gui.inv1:Remove() + end +end + +vgui.Register("ixStorageView", PANEL, "Panel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_subpanel.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_subpanel.lua new file mode 100644 index 0000000..ecd91bb --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_subpanel.lua @@ -0,0 +1,310 @@ + +local DEFAULT_PADDING = ScreenScale(32) +local DEFAULT_ANIMATION_TIME = 1 +local DEFAULT_SUBPANEL_ANIMATION_TIME = 0.5 + +-- parent subpanel +local PANEL = {} + +function PANEL:Init() + local parent = self:GetParent() + local padding = parent.GetPadding and parent:GetPadding() or DEFAULT_PADDING + + self:SetSize(parent:GetWide() - (padding * 2), parent:GetTall() - (padding * 2)) + self:Center() +end + +function PANEL:SetTitle(text, bNoTranslation, bNoUpper) + if (text == nil) then + if (IsValid(self.title)) then + self.title:Remove() + end + + return + elseif (!IsValid(self.title)) then + self.title = self:Add("DLabel") + self.title:SetFont("ixTitleFont") + self.title:SizeToContents() + self.title:SetTextColor(ix.config.Get("color") or color_white) + self.title:Dock(TOP) + end + + local newText = bNoTranslation and text or L(text) + newText = bNoUpper and newText or newText:utf8upper() + + self.title:SetText(newText) + self.title:SizeToContents() +end + +function PANEL:SetLeftPanel(panel) + self.left = panel +end + +function PANEL:GetLeftPanel() + return self.left +end + +function PANEL:SetRightPanel(panel) + self.right = panel +end + +function PANEL:GetRightPanel() + return self.right +end + +function PANEL:OnSetActive() +end + +vgui.Register("ixSubpanel", PANEL, "EditablePanel") + +-- subpanel parent +DEFINE_BASECLASS("EditablePanel") +PANEL = {} + +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) +AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) +AccessorFunc(PANEL, "subpanelAnimationTime", "SubpanelAnimationTime", FORCE_NUMBER) +AccessorFunc(PANEL, "leftOffset", "LeftOffset", FORCE_NUMBER) + +function PANEL:Init() + self.subpanels = {} + self.childPanels = {} + + self.currentSubpanelX = DEFAULT_PADDING + self.targetSubpanelX = DEFAULT_PADDING + self.padding = DEFAULT_PADDING + self.leftOffset = 0 + + self.animationTime = DEFAULT_ANIMATION_TIME + self.subpanelAnimationTime = DEFAULT_SUBPANEL_ANIMATION_TIME +end + +function PANEL:SetPadding(amount, bSetDockPadding) + self.currentSubpanelX = amount + self.targetSubpanelX = amount + self.padding = amount + + if (bSetDockPadding) then + self:DockPadding(amount, amount, amount, amount) + end +end + +function PANEL:Add(name) + local panel = BaseClass.Add(self, name) + + if (panel.SetPaintedManually) then + panel:SetPaintedManually(true) + self.childPanels[#self.childPanels + 1] = panel + end + + return panel +end + +function PANEL:AddSubpanel(name) + local id = #self.subpanels + 1 + local panel = BaseClass.Add(self, "ixSubpanel") + panel.subpanelName = name + panel.subpanelID = id + panel:SetTitle(name) + + self.subpanels[id] = panel + self:SetupSubpanelReferences() + + return panel +end + +function PANEL:SetupSubpanelReferences() + local lastPanel + + for i = 1, #self.subpanels do + local panel = self.subpanels[i] + local nextPanel = self.subpanels[i + 1] + + if (IsValid(lastPanel)) then + lastPanel:SetRightPanel(panel) + panel:SetLeftPanel(lastPanel) + end + + if (IsValid(nextPanel)) then + panel:SetRightPanel(nextPanel) + end + + lastPanel = panel + end +end + +function PANEL:SetSubpanelPos(id, x) + local currentPanel = self.subpanels[id] + + if (!currentPanel) then + return + end + + local _, oldY = currentPanel:GetPos() + currentPanel:SetPos(x, oldY) + + -- traverse left + while (IsValid(currentPanel)) do + local left = currentPanel:GetLeftPanel() + + if (IsValid(left)) then + left:MoveLeftOf(currentPanel, self.padding + self.leftOffset) + end + + currentPanel = left + end + + currentPanel = self.subpanels[id] + + -- traverse right + while (IsValid(currentPanel)) do + local right = currentPanel:GetRightPanel() + + if (IsValid(right)) then + right:MoveRightOf(currentPanel, self.padding) + end + + currentPanel = right + end +end + +function PANEL:SetActiveSubpanel(id, length) + if (isstring(id)) then + for i = 1, #self.subpanels do + if (self.subpanels[i].subpanelName == id) then + id = i + break + end + end + end + + local activePanel = self.subpanels[id] + + if (!activePanel) then + return false + end + + if (length == 0 or !self.activeSubpanel) then + self:SetSubpanelPos(id, self.padding + self.leftOffset) + else + local x, _ = activePanel:GetPos() + local target = self.targetSubpanelX + self.leftOffset + self.currentSubpanelX = x + self.padding + self.leftOffset + + self:CreateAnimation(length or self.subpanelAnimationTime, { + index = 420, + target = {currentSubpanelX = target}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetSubpanelPos(id, panel.currentSubpanelX) + end, + + OnComplete = function(animation, panel) + panel:SetSubpanelPos(id, target) + end + }) + end + + self.activeSubpanel = id + activePanel:OnSetActive() + + return true +end + +function PANEL:GetSubpanel(id) + return self.subpanels[id] +end + +function PANEL:GetActiveSubpanel() + return self.subpanels[self.activeSubpanel] +end + +function PANEL:GetActiveSubpanelID() + return self.activeSubpanel +end + +function PANEL:Slide(direction, length, callback, bIgnoreConfig) + local _, height = self:GetParent():GetSize() + local x, _ = self:GetPos() + local targetY = direction == "up" and 0 or height + + self:SetVisible(true) + + if (length == 0) then + self:SetPos(x, targetY) + else + length = length or self.animationTime + self.currentY = direction == "up" and height or 0 + + self:CreateAnimation(length or self.animationTime, { + index = -1, + target = {currentY = targetY}, + easing = "outExpo", + bIgnoreConfig = bIgnoreConfig, + + Think = function(animation, panel) + local currentX, _ = panel:GetPos() + + panel:SetPos(currentX, panel.currentY) + end, + + OnComplete = function(animation, panel) + if (direction == "down") then + panel:SetVisible(false) + end + + if (callback) then + callback(panel) + end + end + }) + end +end + +function PANEL:SlideUp(...) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + + self:OnSlideUp() + self:Slide("up", ...) +end + +function PANEL:SlideDown(...) + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + + self:OnSlideDown() + self:Slide("down", ...) +end + +function PANEL:OnSlideUp() +end + +function PANEL:OnSlideDown() +end + +function PANEL:Paint(width, height) + for i = 1, #self.childPanels do + if not (IsValid(self.childPanels[i])) then + continue + end + + self.childPanels[i]:PaintManual() + end +end + +function PANEL:PaintSubpanels(width, height) + for i = 1, #self.subpanels do + if not (IsValid(self.subpanels[i])) then + continue + end + + self.subpanels[i]:PaintManual() + end +end + +-- ???? +PANEL.Remove = BaseClass.Remove + +vgui.Register("ixSubpanelParent", PANEL, "EditablePanel") diff --git a/garrysmod/gamemodes/helix/gamemode/core/derma/cl_tooltip.lua b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_tooltip.lua new file mode 100644 index 0000000..37ca796 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/derma/cl_tooltip.lua @@ -0,0 +1,594 @@ + +--- Text container for `ixTooltip`. +-- Rows are the main way of interacting with `ixTooltip`s. These derive from +-- [DLabel](https://wiki.garrysmod.com/page/Category:DLabel) panels, which means that making use of this panel +-- will be largely the same as any DLabel panel. +-- @panel ixTooltipRow + +local animationTime = 1 + +-- panel meta +do + local PANEL = FindMetaTable("Panel") + local ixChangeTooltip = ChangeTooltip + local ixRemoveTooltip = RemoveTooltip + local tooltip + local lastHover + + function PANEL:SetHelixTooltip(callback) + self:SetMouseInputEnabled(true) + self.ixTooltip = callback + end + + function ChangeTooltip(panel, ...) -- luacheck: globals ChangeTooltip + if (!panel.ixTooltip) then + return ixChangeTooltip(panel, ...) + end + + RemoveTooltip() + + timer.Create("ixTooltip", 0.1, 1, function() + if (!IsValid(panel) or lastHover != panel) then + return + end + + tooltip = vgui.Create("ixTooltip") + panel.ixTooltip(tooltip) + tooltip:SizeToContents() + end) + + lastHover = panel + end + + function RemoveTooltip() -- luacheck: globals RemoveTooltip + if (IsValid(tooltip)) then + tooltip:Remove() + tooltip = nil + end + + timer.Remove("ixTooltip") + lastHover = nil + + return ixRemoveTooltip() + end +end + +DEFINE_BASECLASS("DLabel") +local PANEL = {} + +AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") +AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER) +AccessorFunc(PANEL, "bNoMinimal", "MinimalHidden", FORCE_BOOL) + +function PANEL:Init() + self:SetFont("ixSmallFont") + self:SetText(L("unknown")) + self:SetTextColor(color_white) + self:SetTextInset(4, 0) + self:SetContentAlignment(4) + self:Dock(TOP) + + self.maxWidth = ScrW() * 0.2 + self.bNoMinimal = false + self.bMinimal = false +end + +--- Whether or not this tooltip row should be displayed in a minimal format. This usually means no background and/or +-- smaller font. You probably won't need this if you're using regular `ixTooltipRow` panels, but you should take into +-- account if you're creating your own panels that derive from `ixTooltipRow`. +-- @realm client +-- @treturn bool True if this tooltip row should be displayed in a minimal format +function PANEL:IsMinimal() + return self.bMinimal +end + +--- Sets this row to be more prominent with a larger font and more noticable background color. This should usually +-- be used once per tooltip as a title row. For example, item tooltips have one "important" row consisting of the +-- item's name. Note that this function is a fire-and-forget function; you cannot revert a row back to it's regular state +-- unless you set the font/colors manually. +-- @realm client +function PANEL:SetImportant() + self:SetFont("ixSmallTitleFont") + self:SetExpensiveShadow(1, color_black) + self:SetBackgroundColor(ix.config.Get("color")) +end + +--- Sets the background color of this row. This should be used sparingly to avoid overwhelming players with a +-- bunch of different colors that could convey different meanings. +-- @realm client +-- @color color New color of the background. The alpha is clamped to 100-255 to ensure visibility +function PANEL:SetBackgroundColor(color) + color = table.Copy(color) + color.a = math.min(color.a or 255, 100) + + self.backgroundColor = color +end + +--- Resizes this panel to fit its contents. This should be called after setting the text. +-- @realm client +function PANEL:SizeToContents() + local contentWidth, contentHeight = self:GetContentSize() + contentWidth = contentWidth + 4 + contentHeight = contentHeight + 4 + + if (contentWidth > self.maxWidth) then + self:SetWide(self.maxWidth - 4) -- to account for text inset + self:SetTextInset(4, 0) + self:SetWrap(true) + + self:SizeToContentsY() + else + self:SetSize(contentWidth, contentHeight) + end +end + +--- Resizes the height of this panel to fit its contents. +-- @internal +-- @realm client +function PANEL:SizeToContentsY() + BaseClass.SizeToContentsY(self) + self:SetTall(self:GetTall() + 4) +end + +--- Called when the background of this row should be painted. This will paint the background with the +-- `DrawImportantBackground` function set in the skin by default. +-- @realm client +-- @number width Width of the panel +-- @number height Height of the panel +function PANEL:PaintBackground(width, height) + if (self.backgroundColor) then + derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor) + end +end + +--- Called when the foreground of this row should be painted. If you are overriding this in a subclassed panel, +-- make sure you call `ixTooltipRow:PaintBackground` at the *beginning* of your function to make its style +-- consistent with the rest of the framework. +-- @realm client +-- @number width Width of the panel +-- @number height Height of the panel +function PANEL:Paint(width, height) + self:PaintBackground(width, height) +end + +vgui.Register("ixTooltipRow", PANEL, "DLabel") + +--- Generic information panel. +-- Tooltips are used extensively throughout Helix: for item information, character displays, entity status, etc. +-- The tooltip system can be used on any panel or entity you would like to show standardized information for. Tooltips +-- consist of the parent container panel (`ixTooltip`), which is filled with rows of information (usually +-- `ixTooltipRow`, but can be any docked panel if non-text information needs to be shown, like an item's size). +-- +-- Tooltips can be added to panel with `panel:SetHelixTooltip()`. An example taken from the scoreboard: +-- panel:SetHelixTooltip(function(tooltip) +-- local name = tooltip:AddRow("name") +-- name:SetImportant() +-- name:SetText(client:SteamName()) +-- name:SetBackgroundColor(team.GetColor(client:Team())) +-- name:SizeToContents() +-- +-- tooltip:SizeToContents() +-- end) +-- @panel ixTooltip +DEFINE_BASECLASS("Panel") +PANEL = {} + +AccessorFunc(PANEL, "entity", "Entity") +AccessorFunc(PANEL, "mousePadding", "MousePadding", FORCE_NUMBER) +AccessorFunc(PANEL, "bDrawArrow", "DrawArrow", FORCE_BOOL) +AccessorFunc(PANEL, "arrowColor", "ArrowColor") +AccessorFunc(PANEL, "bHideArrowWhenRaised", "HideArrowWhenRaised", FORCE_BOOL) +AccessorFunc(PANEL, "bArrowFollowEntity", "ArrowFollowEntity", FORCE_BOOL) + +function PANEL:Init() + self.fraction = 0 + self.mousePadding = 16 + self.arrowColor = ix.config.Get("color") + self.bHideArrowWhenRaised = true + self.bArrowFollowEntity = true + self.bMinimal = false + + self.lastX, self.lastY = self:GetCursorPosition() + self.arrowX, self.arrowY = ScrW() * 0.5, ScrH() * 0.5 + + self:SetAlpha(0) + self:SetSize(0, 0) + self:SetDrawOnTop(true) + self:SetMouseInputEnabled(false) + + self:CreateAnimation(animationTime, { + index = 1, + target = {fraction = 1}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(panel.fraction * 255) + end + }) +end + +--- Whether or not this tooltip should be displayed in a minimal format. +-- @realm client +-- @treturn bool True if this tooltip should be displayed in a minimal format +-- @see ixTooltipRow:IsMinimal +function PANEL:IsMinimal() + return self.bMinimal +end + +-- ensure all children are painted manually +function PANEL:Add(...) + local panel = BaseClass.Add(self, ...) + panel:SetPaintedManually(true) + + return panel +end + +--- Creates a new `ixTooltipRow` panel and adds it to the bottom of this tooltip. +-- @realm client +-- @string id Name of the new row. This is used to reorder rows if needed +-- @treturn panel Created row +function PANEL:AddRow(id) + local panel = self:Add("ixTooltipRow") + panel.id = id + panel:SetZPos(#self:GetChildren() * 10) + + return panel +end + +--- Creates a new `ixTooltipRow` and adds it after the row with the given `id`. The order of the rows is set via +-- setting the Z position of the panels, as this is how VGUI handles ordering with docked panels. +-- @realm client +-- @string after Name of the row to insert after +-- @string id Name of the newly created row +-- @treturn panel Created row +function PANEL:AddRowAfter(after, id) + local panel = self:AddRow(id) + after = self:GetRow(after) + + if (!IsValid(after)) then + return panel + end + + panel:SetZPos(after:GetZPos() + 1) + + return panel +end + +--- Sets the entity associated with this tooltip. Note that this function is not how you get entities to show tooltips. +-- @internal +-- @realm client +-- @entity entity Entity to associate with this tooltip +function PANEL:SetEntity(entity) + if (!IsValid(entity)) then + self.bEntity = false + return + end + + -- don't show entity tooltips if we have an entity menu open + if (IsValid(ix.menu.panel)) then + self:Remove() + return + end + + if (entity:IsPlayer()) then + --local character = entity:GetCharacter() +-- + --if (character) then + -- -- we want to group things that will most likely have backgrounds (e.g name/health status) + -- hook.Run("PopulateImportantCharacterInfo", entity, character, self) + -- hook.Run("PopulateCharacterInfo", entity, character, self) + --end + else + if (entity.OnPopulateEntityInfo) then + entity:OnPopulateEntityInfo(self) + else + hook.Run("PopulateEntityInfo", entity, self) + end + end + + self:SizeToContents() + + self.entity = entity + self.bEntity = true +end + +function PANEL:PaintUnder(width, height) +end + +function PANEL:Paint(width, height) + self:PaintUnder() + + -- directional arrow + self.bRaised = LocalPlayer():IsWepRaised() + + if (!self.bClosing) then + if (self.bEntity and IsValid(self.entity) and self.bArrowFollowEntity) then + local entity = self.entity + local position = select(1, entity:GetBonePosition(entity:LookupBone("ValveBiped.Bip01_Spine") or -1)) or + entity:LocalToWorld(entity:OBBCenter()) + + position = position:ToScreen() + self.arrowX = math.Clamp(position.x, 0, ScrW()) + self.arrowY = math.Clamp(position.y, 0, ScrH()) + end + end + + -- arrow + if (self.bDrawArrow or (self.bDrawArrow and self.bRaised and !self.bHideArrowWhenRaised)) then + local x, y = self:ScreenToLocal(self.arrowX, self.arrowY) + + DisableClipping(true) + surface.SetDrawColor(self.arrowColor) + surface.DrawLine(0, 0, x * self.fraction, y * self.fraction) + surface.DrawRect((x - 2) * self.fraction, (y - 2) * self.fraction, 4, 4) + DisableClipping(false) + end + + -- contents + local x, y = self:GetPos() + + render.SetScissorRect(x, y, x + width * self.fraction, y + height, true) + derma.SkinFunc("PaintTooltipBackground", self, width, height) + + for _, v in ipairs(self:GetChildren()) do + if (IsValid(v)) then + v:PaintManual() + end + end + render.SetScissorRect(0, 0, 0, 0, false) +end + +--- Returns the current position of the mouse cursor on the screen. +-- @realm client +-- @treturn number X position of cursor +-- @treturn number Y position of cursor +function PANEL:GetCursorPosition() + local width, height = self:GetSize() + local mouseX, mouseY = gui.MousePos() + + return math.Clamp(mouseX + self.mousePadding, 0, ScrW() - width), math.Clamp(mouseY, 0, ScrH() - height) +end + +function PANEL:Think() + if (!self.bEntity) then + if (!vgui.CursorVisible()) then + self:SetPos(self.lastX, self.lastY) + + -- if the cursor isn't visible then we don't really need the tooltip to be shown + if (!self.bClosing) then + self:Remove() + end + else + local newX, newY = self:GetCursorPosition() + + self:SetPos(newX, newY) + self.lastX, self.lastY = newX, newY + end + + self:MoveToFront() -- dragging a panel w/ tooltip will push the tooltip beneath even the menu panel(???) + elseif (IsValid(self.entity) and !self.bClosing) then + if (self.bRaised) then + self:SetPos( + ScrW() * 0.5 - self:GetWide() * 0.5, + math.min(ScrH() * 0.5 + self:GetTall() + 32, ScrH() - self:GetTall()) + ) + else + local entity = self.entity + local min, max = entity:GetRotatedAABB(entity:OBBMins() * 0.5, entity:OBBMaxs() * 0.5) + min = entity:LocalToWorld(min):ToScreen().x + max = entity:LocalToWorld(max):ToScreen().x + + self:SetPos( + math.Clamp(math.max(min, max), ScrW() * 0.5 + 64, ScrW() - self:GetWide()), + ScrH() * 0.5 - self:GetTall() * 0.5 + ) + end + end +end + +--- Returns an `ixTooltipRow` corresponding to the given name. +-- @realm client +-- @string id Name of the row +-- @treturn[1] panel Corresponding row +-- @treturn[2] nil If the row doesn't exist +function PANEL:GetRow(id) + for _, v in ipairs(self:GetChildren()) do + if (IsValid(v) and v.id == id) then + return v + end + end +end + +--- Resizes the tooltip to fit all of the child panels. You should always call this after you are done +-- adding all of your rows. +-- @realm client +function PANEL:SizeToContents() + local height = 0 + local width = 0 + + for _, v in ipairs(self:GetChildren()) do + if (v:GetWide() > width) then + width = v:GetWide() + end + + height = height + v:GetTall() + end + + self:SetSize(width, height) +end + +function PANEL:Remove() + if (self.bClosing) then + return + end + + self.bClosing = true + self:CreateAnimation(animationTime * 0.5, { + target = {fraction = 0}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(panel.fraction * 255) + end, + + OnComplete = function(animation, panel) + BaseClass.Remove(panel) + end + }) +end + +vgui.Register("ixTooltip", PANEL, "Panel") + +-- legacy tooltip row + +PANEL = {} + +function PANEL:Init() + self.bMinimal = true + self.ixAlpha = 0 -- to avoid conflicts if we're animating a non-tooltip panel + + self:SetExpensiveShadow(1, color_black) + self:SetContentAlignment(5) +end + +function PANEL:SetImportant() + self:SetFont("ixMinimalTitleFont") + self:SetBackgroundColor(ix.config.Get("color")) +end + +-- background color will affect text instead in minimal tooltips +function PANEL:SetBackgroundColor(color) + color = table.Copy(color) + color.a = math.min(color.a or 255, 100) + + self:SetTextColor(color) + self.backgroundColor = color +end + +function PANEL:PaintBackground() +end + +vgui.Register("ixTooltipMinimalRow", PANEL, "ixTooltipRow") + +-- legacy tooltip +DEFINE_BASECLASS("ixTooltip") +PANEL = {} + +function PANEL:Init() + self.bMinimal = true + + -- we don't want to animate the alpha since children will handle their own animation, but we want to keep the fraction + -- for the background to animate + self:CreateAnimation(animationTime, { + index = 1, + target = {fraction = 1}, + easing = "outQuint", + }) + + self:SetAlpha(255) +end + +-- we don't need the children to be painted manually +function PANEL:Add(...) + local panel = BaseClass.Add(self, ...) + panel:SetPaintedManually(false) + + return panel +end + +function PANEL:AddRow(id) + local panel = self:Add("ixTooltipMinimalRow") + panel.id = id + panel:SetZPos(#self:GetChildren() * 10) + + return panel +end + +function PANEL:Paint(width, height) + self:PaintUnder() + + derma.SkinFunc("PaintTooltipMinimalBackground", self, width, height) +end + +function PANEL:Think() +end + +function PANEL:SizeToContents() + -- remove any panels that shouldn't be shown in a minimal tooltip + for _, v in ipairs(self:GetChildren()) do + if (v.bNoMinimal) then + v:Remove() + end + end + + BaseClass.SizeToContents(self) + self:SetPos(ScrW() * 0.5 - self:GetWide() * 0.5, ScrH() * 0.5 + self.mousePadding) + + -- we create animation here since this is the only function that usually gets called after all the rows are populated + local children = self:GetChildren() + + -- sort by z index so we can animate them in order + table.sort(children, function(a, b) + return a:GetZPos() < b:GetZPos() + end) + + local i = 1 + local count = table.Count(children) + + for _, v in ipairs(children) do + v.ixAlpha = v.ixAlpha or 0 + + v:CreateAnimation((animationTime / count) * i, { + easing = "inSine", + target = {ixAlpha = 255}, + Think = function(animation, panel) + panel:SetAlpha(panel.ixAlpha) + end + }) + + i = i + 1 + end +end + +DEFINE_BASECLASS("Panel") +function PANEL:Remove() + if (self.bClosing) then + return + end + + self.bClosing = true + + -- we create animation here since this is the only function that usually gets called after all the rows are populated + local children = self:GetChildren() + + -- sort by z index so we can animate them in order + table.sort(children, function(a, b) + return a:GetZPos() > b:GetZPos() + end) + + local duration = animationTime * 0.5 + local i = 1 + local count = table.Count(children) + + for _, v in ipairs(children) do + v.ixAlpha = v.ixAlpha or 255 + + v:CreateAnimation(duration / count * i, { + target = {ixAlpha = 0}, + Think = function(animation, panel) + panel:SetAlpha(panel.ixAlpha) + end + }) + + i = i + 1 + end + + self:CreateAnimation(duration, { + target = {fraction = 0}, + OnComplete = function(animation, panel) + BaseClass.Remove(panel) + end + }) +end + +vgui.Register("ixTooltipMinimal", PANEL, "ixTooltip") diff --git a/garrysmod/gamemodes/helix/gamemode/core/hooks/cl_hooks.lua b/garrysmod/gamemodes/helix/gamemode/core/hooks/cl_hooks.lua new file mode 100644 index 0000000..ddec79a --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/hooks/cl_hooks.lua @@ -0,0 +1,993 @@ +function GM:ForceDermaSkin() + return "helix" +end + +function GM:ScoreboardShow() + if (LocalPlayer():GetCharacter()) then + vgui.Create("ixMenu") + end +end + +function GM:ScoreboardHide() +end + +function GM:LoadFonts(font, genericFont) + surface.CreateFont("ix3D2DFont", { + font = font, + size = 128, + extended = true, + weight = 100 + }) + + surface.CreateFont("ix3D2DMediumFont", { + font = font, + size = 48, + extended = true, + weight = 100 + }) + + surface.CreateFont("ix3D2DSmallFont", { + font = font, + size = 24, + extended = true, + weight = 400 + }) + + surface.CreateFont("ixTitleFont", { + font = font, + size = ScreenScale(30), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixSubTitleFont", { + font = font, + size = ScreenScale(16), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixMenuMiniFont", { + font = "Roboto", + size = math.max(ScreenScale(4), 18), + weight = 300, + }) + + surface.CreateFont("ixMenuButtonFont", { + font = "Roboto Th", + size = ScreenScale(14), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixMenuButtonFontSmall", { + font = "Roboto Th", + size = ScreenScale(10), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixMenuButtonFontThick", { + font = "Roboto", + size = ScreenScale(14), + extended = true, + weight = 300 + }) + + surface.CreateFont("ixMenuButtonLabelFont", { + font = "Roboto Th", + size = 28, + extended = true, + weight = 100 + }) + + surface.CreateFont("ixMenuButtonHugeFont", { + font = "Roboto Th", + size = ScreenScale(24), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixToolTipText", { + font = font, + size = 20, + extended = true, + weight = 500 + }) + + surface.CreateFont("ixMonoSmallFont", { + font = "Consolas", + size = 12, + extended = true, + weight = 800 + }) + + surface.CreateFont("ixMonoMediumFont", { + font = "Consolas", + size = 22, + extended = true, + weight = 800 + }) + + -- The more readable font. + font = genericFont + + surface.CreateFont("ixBigFont", { + font = font, + size = 36, + extended = true, + weight = 1000 + }) + + surface.CreateFont("ixMediumFont", { + font = font, + size = 25, + extended = true, + weight = 1000 + }) + + surface.CreateFont("ixNoticeFont", { + font = font, + size = math.max(ScreenScale(8), 18), + weight = 100, + extended = true, + antialias = true + }) + + surface.CreateFont("ixMediumLightFont", { + font = font, + size = 25, + extended = true, + weight = 200 + }) + + surface.CreateFont("ixMediumLightBlurFont", { + font = font, + size = 25, + extended = true, + weight = 200, + blursize = 4 + }) + + surface.CreateFont("ixGenericFont", { + font = font, + size = 20, + extended = true, + weight = 1000 + }) + + surface.CreateFont("ixChatFont", { + font = font, + size = math.max(ScreenScale(7), 17) * ix.option.Get("chatFontScale", 1), + extended = true, + weight = 600, + antialias = true + }) + + surface.CreateFont("ixChatFontItalics", { + font = font, + size = math.max(ScreenScale(7), 17) * ix.option.Get("chatFontScale", 1), + extended = true, + weight = 600, + antialias = true, + italic = true + }) + + surface.CreateFont("ixSmallTitleFont", { + font = "Roboto Th", + size = math.max(ScreenScale(12), 24), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixMinimalTitleFont", { + font = "Roboto", + size = math.max(ScreenScale(8), 22), + extended = true, + weight = 800 + }) + + surface.CreateFont("ixSmallFont", { + font = font, + size = math.max(ScreenScale(6), 17), + extended = true, + weight = 500 + }) + + surface.CreateFont("ixItemDescFont", { + font = font, + size = math.max(ScreenScale(6), 17), + extended = true, + shadow = true, + weight = 500 + }) + + surface.CreateFont("ixSmallBoldFont", { + font = font, + size = math.max(ScreenScale(8), 20), + extended = true, + weight = 800 + }) + + surface.CreateFont("ixItemBoldFont", { + font = font, + shadow = true, + size = math.max(ScreenScale(8), 20), + extended = true, + weight = 800 + }) + + -- Introduction fancy font. + font = "Roboto Th" + + surface.CreateFont("ixIntroTitleFont", { + font = font, + size = math.min(ScreenScale(128), 128), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixIntroTitleBlurFont", { + font = font, + size = math.min(ScreenScale(128), 128), + extended = true, + weight = 100, + blursize = 4 + }) + + surface.CreateFont("ixIntroSubtitleFont", { + font = font, + size = ScreenScale(24), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixIntroSmallFont", { + font = font, + size = ScreenScale(14), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixIconsSmall", { + font = "fontello", + size = 22, + extended = true, + weight = 500 + }) + + surface.CreateFont("ixSmallTitleIcons", { + font = "fontello", + size = math.max(ScreenScale(11), 23), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixIconsMedium", { + font = "fontello", + extended = true, + size = 28, + weight = 500 + }) + + surface.CreateFont("ixIconsMenuButton", { + font = "fontello", + size = ScreenScale(14), + extended = true, + weight = 100 + }) + + surface.CreateFont("ixIconsBig", { + font = "fontello", + extended = true, + size = 48, + weight = 500 + }) +end + +function GM:OnCharacterMenuCreated(panel) + if (IsValid(ix.gui.notices)) then + ix.gui.notices:Clear() + end +end + +local LOWERED_ANGLES = Angle(30, 0, -25) + +function GM:CalcViewModelView(weapon, viewModel, oldEyePos, oldEyeAngles, eyePos, eyeAngles) + if (!IsValid(weapon)) then + return + end + + local client = LocalPlayer() + local bWepRaised = client:IsWepRaised() + + -- update tween if the raised state is out of date + if (client.ixWasWeaponRaised != bWepRaised) then + local fraction = bWepRaised and 0 or 1 + + client.ixRaisedFraction = 1 - fraction + client.ixRaisedTween = ix.tween.new(0.75, client, { + ixRaisedFraction = fraction + }, "outQuint") + + client.ixWasWeaponRaised = bWepRaised + end + + local fraction = client.ixRaisedFraction + local rotation = weapon.LowerAngles or LOWERED_ANGLES + + if (ix.option.Get("altLower", true) and weapon.LowerAngles2) then + rotation = weapon.LowerAngles2 + end + + eyeAngles:RotateAroundAxis(eyeAngles:Up(), rotation.p * fraction) + eyeAngles:RotateAroundAxis(eyeAngles:Forward(), rotation.y * fraction) + eyeAngles:RotateAroundAxis(eyeAngles:Right(), rotation.r * fraction) + + viewModel:SetAngles(eyeAngles) + return self.BaseClass:CalcViewModelView(weapon, viewModel, oldEyePos, oldEyeAngles, eyePos, eyeAngles) +end + +function GM:LoadIntro() + if (!IsValid(ix.gui.intro)) then + vgui.Create("ixIntro") + end +end + +function GM:CharacterLoaded() + local menu = ix.gui.characterMenu + + if (IsValid(menu)) then + menu:Close((LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()) and true or nil) + end +end + +function GM:InitializedConfig() + local color = ix.config.Get("color") + + hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) + hook.Run("ColorSchemeChanged", color) + + if (!ix.config.loaded and !IsValid(ix.gui.loading)) then + local loader = vgui.Create("EditablePanel") + loader:ParentToHUD() + loader:Dock(FILL) + loader.Paint = function(this, w, h) + surface.SetDrawColor(0, 0, 0) + surface.DrawRect(0, 0, w, h) + end + + local statusLabel = loader:Add("DLabel") + statusLabel:Dock(FILL) + statusLabel:SetText(L"loading") + statusLabel:SetFont("ixTitleFont") + statusLabel:SetContentAlignment(5) + statusLabel:SetTextColor(color_white) + + timer.Simple(5, function() + if (IsValid(ix.gui.loading)) then + local fault = GetNetVar("dbError") + + if (fault) then + statusLabel:SetText(fault and L"dbError" or L"loading") + + local label = loader:Add("DLabel") + label:DockMargin(0, 64, 0, 0) + label:Dock(TOP) + label:SetFont("ixSubTitleFont") + label:SetText(fault) + label:SetContentAlignment(5) + label:SizeToContentsY() + label:SetTextColor(Color(255, 50, 50)) + end + end + end) + + ix.gui.loading = loader + ix.config.loaded = true + + if (ix.config.Get("intro", true) and ix.option.Get("showIntro", true)) then + hook.Run("LoadIntro") + end + end +end + +function GM:InitPostEntity() + ix.joinTime = RealTime() - 0.9716 + ix.option.Sync() + + ix.gui.bars = vgui.Create("ixInfoBarManager") +end + +function GM:NetworkEntityCreated(entity) + if (entity:IsPlayer()) then + entity:SetIK(false) + + -- we've just discovered a new player, so we need to update their animation state + if (entity != LocalPlayer()) then + -- we don't need to call the PlayerWeaponChanged hook here since it'll be handled below, + -- when this player's weapon has been discovered + hook.Run("PlayerModelChanged", entity, entity:GetModel()) + end + elseif (entity:IsWeapon()) then + local owner = entity:GetOwner() + + if (IsValid(owner) and owner:IsPlayer() and entity == owner:GetActiveWeapon()) then + hook.Run("PlayerWeaponChanged", owner, entity) + end + end +end + +local vignette = ix.util.GetMaterial("helix/gui/vignette.png") +local vignetteAlphaGoal = 0 +local vignetteAlphaDelta = 0 +local vignetteTraceHeight = Vector(0, 0, 768) +local blurGoal = 0 +local blurDelta = 0 +local hasVignetteMaterial = !vignette:IsError() + +timer.Create("ixVignetteChecker", 1, 0, function() + local client = LocalPlayer() + + if (IsValid(client)) then + local data = {} + data.start = client:GetPos() + data.endpos = data.start + vignetteTraceHeight + data.filter = client + local trace = util.TraceLine(data) + + -- this timer could run before InitPostEntity is called, so we have to check for the validity of the trace table + if (trace and trace.Hit) then + vignetteAlphaGoal = 80 + else + vignetteAlphaGoal = 0 + end + end +end) + +function GM:CalcView(client, origin, angles, fov) + local view = self.BaseClass:CalcView(client, origin, angles, fov) or {} + local entity = Entity(client:GetLocalVar("ragdoll", 0)) + local ragdoll = IsValid(client:GetRagdollEntity()) and client:GetRagdollEntity() or entity + + if ((!client:ShouldDrawLocalPlayer() and IsValid(entity) and entity:IsRagdoll()) + or (!LocalPlayer():Alive() and IsValid(ragdoll))) then + local ent = LocalPlayer():Alive() and entity or ragdoll + local index = ent:LookupAttachment("eyes") + + if (index) then + local data = ent:GetAttachment(index) + + if (data) then + view.origin = data.Pos + view.angles = data.Ang + end + + return view + end + end + + local menu = ix.gui.menu + local entityMenu = ix.menu.panel + + if (IsValid(menu) and menu:IsVisible() and menu:GetCharacterOverview()) then + local newOrigin, newAngles, newFOV, bDrawPlayer = menu:GetOverviewInfo(origin, angles, fov) + + view.drawviewer = bDrawPlayer + view.fov = newFOV + view.origin = newOrigin + view.angles = newAngles + elseif (IsValid(entityMenu)) then + view.angles = entityMenu:GetOverviewInfo(origin, angles) + end + + return view +end + +local hookRun = hook.Run + +do + local aimLength = 0.35 + local aimTime = 0 + local aimEntity + local lastEntity + local lastTrace = {} + + timer.Create("ixCheckTargetEntity", 0.1, 0, function() + local client = LocalPlayer() + local time = SysTime() + + if (!IsValid(client)) then + return + end + + local character = client:GetCharacter() + + if (!character) then + return + end + + lastTrace.start = client:GetShootPos() + lastTrace.endpos = lastTrace.start + client:GetAimVector(client) * 160 + lastTrace.filter = client + lastTrace.mask = MASK_SHOT_HULL + + lastEntity = util.TraceHull(lastTrace).Entity + + if (lastEntity != aimEntity) then + aimTime = time + aimLength + aimEntity = lastEntity + end + + local panel = ix.gui.entityInfo + local bShouldShow = time >= aimTime and (!IsValid(ix.gui.menu) or ix.gui.menu.bClosing) and + (!IsValid(ix.gui.characterMenu) or ix.gui.characterMenu.bClosing) + local bShouldPopulate = lastEntity.OnShouldPopulateEntityInfo and lastEntity:OnShouldPopulateEntityInfo() or true + + if (bShouldShow and IsValid(lastEntity) and hookRun("ShouldPopulateEntityInfo", lastEntity) != false and + (lastEntity.PopulateEntityInfo or bShouldPopulate)) then + + if (!IsValid(panel) or (IsValid(panel) and panel:GetEntity() != lastEntity)) then + if (IsValid(ix.gui.entityInfo)) then + ix.gui.entityInfo:Remove() + end + + local infoPanel = vgui.Create(ix.option.Get("minimalTooltips", false) and "ixTooltipMinimal" or "ixTooltip") + local entityPlayer = lastEntity:GetNetVar("player") + + if (entityPlayer) then + infoPanel:SetEntity(entityPlayer) + infoPanel.entity = lastEntity + else + infoPanel:SetEntity(lastEntity) + end + + infoPanel:SetDrawArrow(true) + ix.gui.entityInfo = infoPanel + end + elseif (IsValid(panel)) then + panel:Remove() + end + end) +end + +local mathApproach = math.Approach +local surface = surface + +function GM:HUDPaintBackground() + local client = LocalPlayer() + + if (!client:GetCharacter()) then + return + end + + local frameTime = FrameTime() + local scrW, scrH = ScrW(), ScrH() + + if (hasVignetteMaterial and ix.config.Get("vignette")) then + vignetteAlphaDelta = mathApproach(vignetteAlphaDelta, vignetteAlphaGoal, frameTime * 30) + + surface.SetDrawColor(0, 0, 0, 175 + vignetteAlphaDelta) + surface.SetMaterial(vignette) + surface.DrawTexturedRect(0, 0, scrW, scrH) + end + + blurGoal = client:GetLocalVar("blur", 0) + (hookRun("AdjustBlurAmount", blurGoal) or 0) + + if (blurDelta != blurGoal) then + blurDelta = mathApproach(blurDelta, blurGoal, frameTime * 20) + end + + if (blurDelta > 0 and !client:ShouldDrawLocalPlayer()) then + ix.util.DrawBlurAt(0, 0, scrW, scrH, blurDelta) + end + + self.BaseClass:PaintWorldTips() + + local weapon = client:GetActiveWeapon() + + if (IsValid(weapon) and hook.Run("CanDrawAmmoHUD", weapon) != false and weapon.DrawAmmo != false) then + local clip = weapon:Clip1() + local clipMax = weapon:GetMaxClip1() + local count = client:GetAmmoCount(weapon:GetPrimaryAmmoType()) + local secondary = client:GetAmmoCount(weapon:GetSecondaryAmmoType()) + local x, y = scrW - 80, scrH - 80 + + if (secondary > 0) then + ix.util.DrawBlurAt(x, y, 64, 64) + + surface.SetDrawColor(255, 255, 255, 5) + surface.DrawRect(x, y, 64, 64) + surface.SetDrawColor(255, 255, 255, 3) + surface.DrawOutlinedRect(x, y, 64, 64) + + ix.util.DrawText(secondary, x + 32, y + 32, nil, 1, 1, "ixBigFont") + end + + if (weapon:GetClass() != "weapon_slam" and clip > 0 or count > 0) then + x = x - (secondary > 0 and 144 or 64) + + ix.util.DrawBlurAt(x, y, 128, 64) + + surface.SetDrawColor(255, 255, 255, 5) + surface.DrawRect(x, y, 128, 64) + surface.SetDrawColor(255, 255, 255, 3) + surface.DrawOutlinedRect(x, y, 128, 64) + + ix.util.DrawText((clip == -1 or clipMax == -1) and count or clip.."/"..count, x + 64, y + 32, nil, 1, 1, "ixBigFont") + end + end + + if (client:GetLocalVar("restricted") and !client:GetLocalVar("restrictNoMsg")) then + ix.util.DrawText(L"restricted", scrW * 0.5, scrH * 0.33, nil, 1, 1, "ixBigFont") + end +end + +function GM:PostDrawOpaqueRenderables(bDepth, bSkybox) + if (bDepth or bSkybox or #ix.blurRenderQueue == 0) then + return + end + + ix.util.ResetStencilValues() + render.SetStencilEnable(true) + render.SetStencilWriteMask(27) + render.SetStencilTestMask(27) + render.SetStencilFailOperation(STENCILOPERATION_KEEP) + render.SetStencilZFailOperation(STENCILOPERATION_KEEP) + render.SetStencilPassOperation(STENCILOPERATION_REPLACE) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) + render.SetStencilReferenceValue(27) + + for i = 1, #ix.blurRenderQueue do + ix.blurRenderQueue[i]() + end + + render.SetStencilReferenceValue(34) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + render.SetStencilPassOperation(STENCILOPERATION_REPLACE) + render.SetStencilReferenceValue(27) + + cam.Start2D() + ix.util.DrawBlurAt(0, 0, ScrW(), ScrH()) + cam.End2D() + render.SetStencilEnable(false) + + ix.blurRenderQueue = {} +end + +function GM:PostDrawHUD() + cam.Start2D() + ix.hud.DrawAll() + + if (!IsValid(ix.gui.deathScreen) and (!IsValid(ix.gui.characterMenu) or ix.gui.characterMenu:IsClosing())) then + ix.bar.DrawAction() + end + cam.End2D() +end + +function GM:ShouldPopulateEntityInfo(entity) + local client = LocalPlayer() + local ragdoll = Entity(client:GetLocalVar("ragdoll", 0)) + local entityPlayer = entity:GetNetVar("player") + + if (vgui.CursorVisible() or !client:Alive() or IsValid(ragdoll) or entity == client or entityPlayer == client) then + return false + end +end + +local injTextTable = { + [.3] = {"injMajor", Color(192, 57, 43)}, + [.6] = {"injLittle", Color(231, 76, 60)}, +} + +function GM:GetInjuredText(client) + local health = client:Health() + + for k, v in pairs(injTextTable) do + if ((health / client:GetMaxHealth()) < k) then + return v[1], v[2] + end + end +end + +function GM:PopulateImportantCharacterInfo(client, character, container) + local color = team.GetColor(client:Team()) + container:SetArrowColor(color) + + -- name + local name = container:AddRow("name") + name:SetImportant() + name:SetText(hookRun("GetCharacterName", client) or character:GetName()) + name:SetBackgroundColor(color) + name:SizeToContents() + + -- injured text + local injureText, injureTextColor = hookRun("GetInjuredText", client) + + if (injureText) then + local injure = container:AddRow("injureText") + + injure:SetText(L(injureText)) + injure:SetBackgroundColor(injureTextColor) + injure:SizeToContents() + end +end + +function GM:PopulateCharacterInfo(client, character, container) + -- description + local descriptionText = character:GetDescription() + descriptionText = (descriptionText:utf8len() > 128 and + string.format("%s...", descriptionText:utf8sub(1, 125)) or + descriptionText) + + if (descriptionText != "") then + local description = container:AddRow("description") + description:SetText(descriptionText) + description:SizeToContents() + end +end + +function GM:KeyRelease(client, key) + if (!IsFirstTimePredicted()) then + return + end + + if (key == IN_USE) then + if (!ix.menu.IsOpen()) then + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + + local entity = util.TraceLine(data).Entity + + if (IsValid(entity) and isfunction(entity.GetEntityMenu)) then + hook.Run("ShowEntityMenu", entity) + end + end + + timer.Remove("ixItemUse") + + client.ixInteractionTarget = nil + client.ixInteractionStartTime = nil + end +end + +function GM:PlayerBindPress(client, bind, pressed) + bind = bind:lower() + + if (bind:find("use") and pressed) then + local pickupTime = ix.config.Get("itemPickupTime", 0.5) + + if (pickupTime > 0) then + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local entity = util.TraceLine(data).Entity + + if (IsValid(entity) and entity.ShowPlayerInteraction and !ix.menu.IsOpen()) then + client.ixInteractionTarget = entity + client.ixInteractionStartTime = SysTime() + + timer.Create("ixItemUse", pickupTime, 1, function() + client.ixInteractionTarget = nil + client.ixInteractionStartTime = nil + end) + end + end + elseif (bind:find("jump")) then + local entity = Entity(client:GetLocalVar("ragdoll", 0)) + + if (IsValid(entity)) then + ix.command.Send("CharGetUp") + end + elseif (bind:find("speed") and client:KeyDown(IN_WALK) and pressed) then + if (LocalPlayer():Crouching()) then + RunConsoleCommand("-duck") + else + RunConsoleCommand("+duck") + end + end +end + +function GM:CreateMove(command) + if ((IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu.bClosing) or + (IsValid(ix.gui.menu) and !ix.gui.menu.bClosing and ix.gui.menu:GetActiveTab() == "you")) then + command:ClearButtons() + command:ClearMovement() + end +end + +-- Called when use has been pressed on an item. +function GM:ShowEntityMenu(entity) + local options = entity:GetEntityMenu(LocalPlayer()) + + if (istable(options) and !table.IsEmpty(options)) then + ix.menu.Open(options, entity) + end +end + +local hidden = {} +hidden["CHudHealth"] = true +hidden["CHudBattery"] = true +hidden["CHudAmmo"] = true +hidden["CHudSecondaryAmmo"] = true +hidden["CHudCrosshair"] = true +hidden["CHudHistoryResource"] = true +hidden["CHudPoisonDamageIndicator"] = true +hidden["CHudSquadStatus"] = true +hidden["CHUDQuickInfo"] = true + +function GM:HUDShouldDraw(element) + if (hidden[element]) then + return false + end + + return true +end + +function GM:ShouldDrawLocalPlayer(client) + if (IsValid(ix.gui.characterMenu) and ix.gui.characterMenu:IsVisible()) then + return false + end +end + +function GM:PostProcessPermitted(class) + return false +end + +function GM:RenderScreenspaceEffects() + local menu = ix.gui.menu + + if (IsValid(menu) and menu:GetCharacterOverview()) then + local client = LocalPlayer() + local target = client:GetObserverTarget() + local weapon = client:GetActiveWeapon() + + cam.Start3D() + ix.util.ResetStencilValues() + render.SetStencilEnable(true) + render.SuppressEngineLighting(true) + cam.IgnoreZ(true) + render.SetColorModulation(1, 1, 1) + render.SetStencilWriteMask(28) + render.SetStencilTestMask(28) + render.SetStencilReferenceValue(28) + + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + + if (IsValid(target)) then + target:DrawModel() + else + client:DrawModel() + end + + if (IsValid(weapon)) then + weapon:DrawModel() + end + + hook.Run("DrawCharacterOverview") + + render.SetStencilCompareFunction(STENCIL_NOTEQUAL) + render.SetStencilPassOperation(STENCIL_KEEP) + + cam.Start2D() + derma.SkinFunc("DrawCharacterStatusBackground", menu, menu.overviewFraction) + cam.End2D() + cam.IgnoreZ(false) + render.SuppressEngineLighting(false) + render.SetStencilEnable(false) + cam.End3D() + end +end + +function GM:ShowPlayerOptions(client, options) + options["viewProfile"] = {"icon16/user.png", function() + if (IsValid(client)) then + client:ShowProfile() + end + end} + options["Copy Steam ID"] = {"icon16/user.png", function() + if (IsValid(client)) then + SetClipboardText(client:SteamID()) + end + end} +end + +function GM:DrawHelixModelView(panel, ent) + if (ent.weapon and IsValid(ent.weapon)) then + ent.weapon:DrawModel() + end +end + +net.Receive("ixStringRequest", function() + local time = net.ReadUInt(32) + local title, subTitle = net.ReadString(), net.ReadString() + local default = net.ReadString() + + if (title:sub(1, 1) == "@") then + title = L(title:sub(2)) + end + + if (subTitle:sub(1, 1) == "@") then + subTitle = L(subTitle:sub(2)) + end + + Derma_StringRequest(title, subTitle, default or "", function(text) + net.Start("ixStringRequest") + net.WriteUInt(time, 32) + net.WriteString(text) + net.SendToServer() + end) +end) + +net.Receive("ixPlayerDeath", function() + if (IsValid(ix.gui.deathScreen)) then + ix.gui.deathScreen:Remove() + end + + ix.gui.deathScreen = vgui.Create("ixDeathScreen") +end) + +function GM:Think() + local client = LocalPlayer() + + if (IsValid(client) and client:Alive() and client.ixRaisedTween) then + client.ixRaisedTween:update(FrameTime()) + end +end + +function GM:ScreenResolutionChanged(oldW, oldH) + hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) + + if (IsValid(ix.gui.notices)) then + ix.gui.notices:Remove() + ix.gui.notices = vgui.Create("ixNoticeManager") + end + + if (IsValid(ix.gui.bars)) then + ix.gui.bars:Remove() + ix.gui.bars = vgui.Create("ixInfoBarManager") + end +end + +function GM:DrawDeathNotice() + return false +end + +function GM:HUDAmmoPickedUp() + return false +end + +function GM:HUDDrawPickupHistory() + return false +end + +function GM:HUDDrawTargetID() + return false +end + +function GM:BuildBusinessMenu() + if (!ix.config.Get("allowBusiness", true)) then + return false + end +end + +gameevent.Listen("player_spawn") +hook.Add("player_spawn", "ixPlayerSpawn", function(data) + local client = Player(data.userid) + + if (IsValid(client)) then + -- GetBoneName returns __INVALIDBONE__ for everything the first time you use it, so we'll force an update to make them valid + client:SetupBones() + client:SetIK(false) + + if (client == LocalPlayer() and (IsValid(ix.gui.deathScreen) and !ix.gui.deathScreen:IsClosing())) then + ix.gui.deathScreen:Close() + end + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/hooks/sh_hooks.lua b/garrysmod/gamemodes/helix/gamemode/core/hooks/sh_hooks.lua new file mode 100644 index 0000000..6727595 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/hooks/sh_hooks.lua @@ -0,0 +1,657 @@ + +function GM:PlayerNoClip(client) + return client:IsAdmin() +end + +-- luacheck: globals HOLDTYPE_TRANSLATOR +HOLDTYPE_TRANSLATOR = {} +HOLDTYPE_TRANSLATOR[""] = "normal" +HOLDTYPE_TRANSLATOR["physgun"] = "smg" +HOLDTYPE_TRANSLATOR["ar2"] = "smg" +HOLDTYPE_TRANSLATOR["crossbow"] = "shotgun" +HOLDTYPE_TRANSLATOR["rpg"] = "shotgun" +HOLDTYPE_TRANSLATOR["slam"] = "normal" +HOLDTYPE_TRANSLATOR["grenade"] = "grenade" +HOLDTYPE_TRANSLATOR["fist"] = "normal" +HOLDTYPE_TRANSLATOR["melee2"] = "melee" +HOLDTYPE_TRANSLATOR["passive"] = "normal" +HOLDTYPE_TRANSLATOR["knife"] = "melee" +HOLDTYPE_TRANSLATOR["duel"] = "pistol" +HOLDTYPE_TRANSLATOR["camera"] = "smg" +HOLDTYPE_TRANSLATOR["magic"] = "normal" +HOLDTYPE_TRANSLATOR["revolver"] = "pistol" + +-- luacheck: globals PLAYER_HOLDTYPE_TRANSLATOR +PLAYER_HOLDTYPE_TRANSLATOR = {} +PLAYER_HOLDTYPE_TRANSLATOR[""] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["fist"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["pistol"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["grenade"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["melee"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["slam"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["melee2"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["passive"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["knife"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["duel"] = "normal" +PLAYER_HOLDTYPE_TRANSLATOR["bugbait"] = "normal" + +local PLAYER_HOLDTYPE_TRANSLATOR = PLAYER_HOLDTYPE_TRANSLATOR +local HOLDTYPE_TRANSLATOR = HOLDTYPE_TRANSLATOR +local animationFixOffset = Vector(16.5438, -0.1642, -20.5493) + +function GM:TranslateActivity(client, act) + local clientInfo = client:GetTable() + local modelClass = clientInfo.ixAnimModelClass or "player" + local bRaised = client:IsWepRaised() + + if (modelClass == "player") then + local weapon = client:GetActiveWeapon() + local bAlwaysRaised = ix.config.Get("weaponAlwaysRaised") + weapon = IsValid(weapon) and weapon or nil + + if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then + local model = string.lower(client:GetModel()) + + if (string.find(model, "zombie")) then + local tree = ix.anim.zombie + + if (string.find(model, "fast")) then + tree = ix.anim.fastZombie + end + + if (tree[act]) then + return tree[act] + end + end + + local holdType = weapon and (weapon.HoldType or weapon:GetHoldType()) or "normal" + + if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then + holdType = PLAYER_HOLDTYPE_TRANSLATOR[holdType] or "passive" + end + + local tree = ix.anim.player[holdType] + + if (tree and tree[act]) then + if (isstring(tree[act])) then + clientInfo.CalcSeqOverride = client:LookupSequence(tree[act]) + + return + else + return tree[act] + end + end + end + + return self.BaseClass:TranslateActivity(client, act) + end + + if (clientInfo.ixAnimTable) then + local glide = clientInfo.ixAnimGlide + + if (client:InVehicle()) then + act = clientInfo.ixAnimTable[1] + + local fixVector = clientInfo.ixAnimTable[2] + + if (isvector(fixVector)) then + client:SetLocalPos(animationFixOffset) + end + + if (isstring(act)) then + clientInfo.CalcSeqOverride = client:LookupSequence(act) + else + return act + end + elseif (client:OnGround()) then + if (clientInfo.ixAnimTable[act]) then + local act2 = clientInfo.ixAnimTable[act][bRaised and 2 or 1] + + if (isstring(act2)) then + clientInfo.CalcSeqOverride = client:LookupSequence(act2) + else + return act2 + end + end + elseif (glide) then + if (isstring(glide)) then + clientInfo.CalcSeqOverride = client:LookupSequence(glide) + else + return clientInfo.ixAnimGlide + end + end + end +end + +function GM:CanPlayerUseBusiness(client, uniqueID) + if (!ix.config.Get("allowBusiness", true)) then + return false + end + + local itemTable = ix.item.list[uniqueID] + + if (!client:GetCharacter()) then + return false + end + + if (itemTable.noBusiness) then + return false + end + + if (itemTable.factions) then + local allowed = false + + if (istable(itemTable.factions)) then + for _, v in pairs(itemTable.factions) do + if (client:Team() == v) then + allowed = true + + break + end + end + elseif (client:Team() != itemTable.factions) then + allowed = false + end + + if (!allowed) then + return false + end + end + + if (itemTable.classes) then + local allowed = false + + if (istable(itemTable.classes)) then + for _, v in pairs(itemTable.classes) do + if (client:GetCharacter():GetClass() == v) then + allowed = true + + break + end + end + elseif (client:GetCharacter():GetClass() == itemTable.classes) then + allowed = true + end + + if (!allowed) then + return false + end + end + + if (itemTable.flag) then + if (!client:GetCharacter():HasFlags(itemTable.flag)) then + return false + end + end + + return true +end + +function GM:DoAnimationEvent(client, event, data) + local class = client.ixAnimModelClass + + if (class == "player") then + return self.BaseClass:DoAnimationEvent(client, event, data) + else + local weapon = client:GetActiveWeapon() + + if (IsValid(weapon)) then + local animation = client.ixAnimTable + + if (event == PLAYERANIMEVENT_ATTACK_PRIMARY) then + client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true) + + return ACT_VM_PRIMARYATTACK + elseif (event == PLAYERANIMEVENT_ATTACK_SECONDARY) then + client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true) + + return ACT_VM_SECONDARYATTACK + elseif (event == PLAYERANIMEVENT_RELOAD) then + client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.reload or ACT_GESTURE_RELOAD_SMG1, true) + + return ACT_INVALID + elseif (event == PLAYERANIMEVENT_JUMP) then + client:AnimRestartMainSequence() + + return ACT_INVALID + elseif (event == PLAYERANIMEVENT_CANCEL_RELOAD) then + client:AnimResetGestureSlot(GESTURE_SLOT_ATTACK_AND_RELOAD) + + return ACT_INVALID + end + end + end + + return ACT_INVALID +end + +function GM:EntityEmitSound(data) + if (data.Entity.ixIsMuted) then + return false + end +end + +function GM:EntityRemoved(entity) + if (SERVER) then + entity:ClearNetVars() + elseif (entity:IsWeapon()) then + local owner = entity:GetOwner() + + -- GetActiveWeapon is the player's new weapon at this point so we'll assume + -- that the player switched away from this weapon + if (IsValid(owner) and owner:IsPlayer()) then + hook.Run("PlayerWeaponChanged", owner, owner:GetActiveWeapon()) + end + end +end + +local function UpdatePlayerHoldType(client, weapon) + weapon = weapon or client:GetActiveWeapon() + local holdType = "normal" + + if (IsValid(weapon)) then + holdType = weapon.HoldType or weapon:GetHoldType() + holdType = HOLDTYPE_TRANSLATOR[holdType] or holdType + end + + client.ixAnimHoldType = holdType +end + +local function UpdateAnimationTable(client, vehicle) + local baseTable = ix.anim[client.ixAnimModelClass] or {} + + if (IsValid(client) and IsValid(vehicle)) then + local vehicleClass = vehicle:IsChair() and "chair" or vehicle:GetClass() + + if (baseTable.vehicle and baseTable.vehicle[vehicleClass]) then + client.ixAnimTable = baseTable.vehicle[vehicleClass] + else + client.ixAnimTable = baseTable.normal[ACT_MP_CROUCH_IDLE] + end + else + client.ixAnimTable = baseTable[client.ixAnimHoldType] + end + + client.ixAnimGlide = baseTable["glide"] +end + +function GM:PlayerWeaponChanged(client, weapon) + UpdatePlayerHoldType(client, weapon) + UpdateAnimationTable(client) + + if (CLIENT) then + return + end + + -- update weapon raise state + if (weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()]) then + client:SetWepRaised(true, weapon) + return + elseif (weapon.IsAlwaysLowered or weapon.NeverRaised) then + client:SetWepRaised(false, weapon) + return + end + + -- If the player has been forced to have their weapon lowered. + if (client:IsRestricted()) then + client:SetWepRaised(false, weapon) + return + end + + -- Let the config decide before actual results. + if (ix.config.Get("weaponAlwaysRaised")) then + client:SetWepRaised(true, weapon) + return + end + + client:SetWepRaised(false, weapon) +end + +function GM:PlayerSwitchWeapon(client, oldWeapon, weapon) + if (!IsFirstTimePredicted()) then + return + end + + -- the player switched weapon themself (i.e not through SelectWeapon), so we have to network it here + if (SERVER) then + net.Start("PlayerSelectWeapon") + net.WriteEntity(client) + net.WriteString(weapon:GetClass()) + net.Broadcast() + end + + hook.Run("PlayerWeaponChanged", client, weapon) +end + +function GM:PlayerModelChanged(client, model) + client.ixAnimModelClass = ix.anim.GetModelClass(model) + + UpdateAnimationTable(client) +end + +do + local vectorAngle = FindMetaTable("Vector").Angle + local normalizeAngle = math.NormalizeAngle + + function GM:CalcMainActivity(client, velocity) + local clientInfo = client:GetTable() + local forcedSequence = client:GetNetVar("forcedSequence") + + if (forcedSequence) then + if (client:GetSequence() != forcedSequence) then + client:SetCycle(0) + end + + return -1, forcedSequence + end + + client:SetPoseParameter("move_yaw", normalizeAngle(vectorAngle(velocity)[2] - client:EyeAngles()[2])) + + local sequenceOverride = clientInfo.CalcSeqOverride + clientInfo.CalcSeqOverride = -1 + clientInfo.CalcIdeal = ACT_MP_STAND_IDLE + + -- we could call the baseclass function, but it's faster to do it this way + local BaseClass = self.BaseClass + + if (BaseClass:HandlePlayerNoClipping(client, velocity) or + BaseClass:HandlePlayerDriving(client) or + BaseClass:HandlePlayerVaulting(client, velocity) or + BaseClass:HandlePlayerJumping(client, velocity) or + BaseClass:HandlePlayerSwimming(client, velocity) or + BaseClass:HandlePlayerDucking(client, velocity)) then -- luacheck: ignore 542 + else + local length = velocity:Length2DSqr() + + if (length > 22500) then + clientInfo.CalcIdeal = ACT_MP_RUN + elseif (length > 0.25) then + clientInfo.CalcIdeal = ACT_MP_WALK + end + end + + clientInfo.m_bWasOnGround = client:OnGround() + clientInfo.m_bWasNoclipping = (client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle()) + + return clientInfo.CalcIdeal, sequenceOverride or clientInfo.CalcSeqOverride or -1 + end +end + +do + local KEY_BLACKLIST = IN_ATTACK + IN_ATTACK2 + + function GM:StartCommand(client, command) + if (!client:CanShootWeapon() and !IsValid(client:GetHoldingEntity())) then + command:RemoveKey(KEY_BLACKLIST) + end + end +end + +function GM:CharacterVarChanged(char, varName, oldVar, newVar) + if (ix.char.varHooks[varName]) then + for _, v in pairs(ix.char.varHooks[varName]) do + v(char, oldVar, newVar) + end + end +end + +function GM:CanPlayerThrowPunch(client) + if (!client:IsWepRaised()) then + return false + end + + return true +end + +function GM:OnCharacterCreated(client, character) + local faction = ix.faction.Get(character:GetFaction()) + + if (faction and faction.OnCharacterCreated) then + faction:OnCharacterCreated(client, character) + end +end + +function GM:GetDefaultCharacterName(client, faction) + local info = ix.faction.indices[faction] + + if (info and info.GetDefaultName) then + return info:GetDefaultName(client) + end +end + +function GM:CanPlayerUseCharacter(client, character) + local banned = character:GetData("banned") + + if (banned) then + if (!isnumber(banned)) then + return false, "@charBanned" + else + if (banned > os.time()) then + return false, "@charBannedTemp" + end + end + end + + local bHasWhitelist = client:HasWhitelist(character:GetFaction()) + + if (!bHasWhitelist) then + return false, "@noWhitelist" + end +end + +function GM:CanProperty(client, property, entity) + if (client:IsAdmin()) then + return true + end + + if (CLIENT and (property == "remover" or property == "collision")) then + return true + end + + return false +end + +function GM:PhysgunPickup(client, entity) + local bPickup = self.BaseClass:PhysgunPickup(client, entity) + + if (!bPickup and entity:IsPlayer() and (client:IsSuperAdmin() or client:IsAdmin() and !entity:IsSuperAdmin())) then + bPickup = true + end + + if (bPickup) then + if (entity:IsPlayer()) then + entity:SetMoveType(MOVETYPE_NONE) + elseif (!entity.ixCollisionGroup) then + entity.ixCollisionGroup = entity:GetCollisionGroup() + entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) + end + end + + return bPickup +end + +function GM:PhysgunDrop(client, entity) + if (entity:IsPlayer()) then + entity:SetMoveType(MOVETYPE_WALK) + elseif (entity.ixCollisionGroup) then + entity:SetCollisionGroup(entity.ixCollisionGroup) + entity.ixCollisionGroup = nil + end +end + +do + local TOOL_DANGEROUS = {} + TOOL_DANGEROUS["dynamite"] = true + TOOL_DANGEROUS["duplicator"] = true + + function GM:CanTool(client, trace, tool) + if (client:IsAdmin()) then + return true + end + + if (TOOL_DANGEROUS[tool]) then + return false + end + + return self.BaseClass:CanTool(client, trace, tool) + end +end + +function GM:Move(client, moveData) + local char = client:GetCharacter() + + if (char) then + if (client:GetNetVar("actEnterAngle")) then + moveData:SetForwardSpeed(0) + moveData:SetSideSpeed(0) + moveData:SetVelocity(vector_origin) + end + + if (client:GetMoveType() == MOVETYPE_WALK and moveData:KeyDown(IN_WALK)) then + local mf, ms = 0, 0 + local speed = client:GetWalkSpeed() + local ratio = ix.config.Get("walkRatio") + + if (moveData:KeyDown(IN_FORWARD)) then + mf = ratio + elseif (moveData:KeyDown(IN_BACK)) then + mf = -ratio + end + + if (moveData:KeyDown(IN_MOVELEFT)) then + ms = -ratio + elseif (moveData:KeyDown(IN_MOVERIGHT)) then + ms = ratio + end + + moveData:SetForwardSpeed(mf * speed) + moveData:SetSideSpeed(ms * speed) + end + end +end + +function GM:CanTransferItem(itemObject, curInv, inventory) + if (SERVER) then + local client = itemObject.GetOwner and itemObject:GetOwner() or nil + + if (IsValid(client) and curInv.GetReceivers) then + local bAuthorized = false + + for _, v in ipairs(curInv:GetReceivers()) do + if (client == v) then + bAuthorized = true + break + end + end + + if (!bAuthorized) then + return false + end + end + end + + -- we can transfer anything that isn't a bag + if (!itemObject or !itemObject.isBag) then + return + end + + -- don't allow bags to be put inside bags + if (inventory.id != 0 and curInv.id != inventory.id) then + if (inventory.vars and inventory.vars.isBag) then + local owner = itemObject:GetOwner() + + if (IsValid(owner)) then + owner:NotifyLocalized("nestedBags") + end + + return false + end + elseif (inventory.id != 0 and curInv.id == inventory.id) then + -- we are simply moving items around if we're transferring to the same inventory + return + end + + inventory = ix.item.inventories[itemObject:GetData("id")] + + -- don't allow transferring items that are in use + if (inventory) then + for k, _ in inventory:Iter() do + if (k:GetData("equip") == true) then + local owner = itemObject:GetOwner() + + if (owner and IsValid(owner)) then + owner:NotifyLocalized("equippedBag") + end + + return false + end + end + end +end + +function GM:CanPlayerEquipItem(client, item) + return item.invID == client:GetCharacter():GetInventory():GetID() +end + +function GM:CanPlayerUnequipItem(client, item) + return item.invID == client:GetCharacter():GetInventory():GetID() +end + +function GM:OnItemTransferred(item, curInv, inventory) + local bagInventory = item.GetInventory and item:GetInventory() + + if (!bagInventory) then + return + end + + -- we need to retain the receiver if the owner changed while viewing as storage + if (inventory.storageInfo and isfunction(curInv.GetOwner)) then + bagInventory:AddReceiver(curInv:GetOwner()) + end +end + +function GM:ShowHelp() end + +function GM:PreGamemodeLoaded() + hook.Remove("PostDrawEffects", "RenderWidgets") + hook.Remove("PlayerTick", "TickWidgets") + hook.Remove("RenderScene", "RenderStereoscopy") +end + +function GM:PostGamemodeLoaded() + baseclass.Set("ix_character", ix.meta.character) + baseclass.Set("ix_inventory", ix.meta.inventory) + baseclass.Set("ix_item", ix.meta.item) +end + +if (SERVER) then + util.AddNetworkString("PlayerVehicle") + + function GM:PlayerEnteredVehicle(client, vehicle, role) + UpdateAnimationTable(client) + + net.Start("PlayerVehicle") + net.WriteEntity(client) + net.WriteEntity(vehicle) + net.WriteBool(true) + net.Broadcast() + end + + function GM:PlayerLeaveVehicle(client, vehicle) + UpdateAnimationTable(client) + + net.Start("PlayerVehicle") + net.WriteEntity(client) + net.WriteEntity(vehicle) + net.WriteBool(false) + net.Broadcast() + end +else + net.Receive("PlayerVehicle", function(length) + local client = net.ReadEntity() + local vehicle = net.ReadEntity() + local bEntered = net.ReadBool() + + UpdateAnimationTable(client, bEntered and vehicle or false) + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/hooks/sv_hooks.lua b/garrysmod/gamemodes/helix/gamemode/core/hooks/sv_hooks.lua new file mode 100644 index 0000000..ddd482f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/hooks/sv_hooks.lua @@ -0,0 +1,910 @@ +util.AddNetworkString("ixPlayerDeath") + +function GM:PlayerInitialSpawn(client) + client.ixJoinTime = RealTime() + + if (client:IsBot()) then + local botID = os.time() + client:EntIndex() + local index = math.random(1, table.Count(ix.faction.indices)) + local faction = ix.faction.indices[index] + + local character = ix.char.New({ + name = client:Name(), + faction = faction and faction.uniqueID or "unknown", + model = faction and table.Random(faction:GetModels(client)) or "models/gman.mdl" + }, botID, client, client:SteamID64()) + character.isBot = true + + local inventory = ix.inventory.Create(ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight"), botID) + inventory:SetOwner(botID) + inventory.noSave = true + + character.vars.inv = {inventory} + + ix.char.loaded[botID] = character + + character:Setup() + client:Spawn() + + ix.chat.Send(nil, "connect", client:SteamName()) + + return + end + + ix.config.Send(client) + ix.date.Send(client) + + client:LoadData(function(data) + if (!IsValid(client)) then return end + + -- Don't use the character cache if they've connected to another server using the same database + local address = ix.util.GetAddress() + local bNoCache = client:GetData("lastIP", address) != address + client:SetData("lastIP", address) + + net.Start("ixDataSync") + net.WriteTable(data or {}) + net.WriteUInt(client.ixPlayTime or 0, 32) + net.Send(client) + + ix.char.Restore(client, function(charList) + if (!IsValid(client)) then return end + + MsgN("Loaded (" .. table.concat(charList, ", ") .. ") for " .. client:Name()) + + for _, v in ipairs(charList) do + ix.char.loaded[v]:Sync(client) + end + + client.ixCharList = charList + + net.Start("ixCharacterMenu") + net.WriteUInt(#charList, 6) + + for _, v in ipairs(charList) do + net.WriteUInt(v, 32) + end + + net.Send(client) + + client.ixLoaded = true + client:SetData("intro", true) + + for _, v in player.Iterator() do + if (v:GetCharacter()) then + v:GetCharacter():Sync(client) + end + end + end, bNoCache) + + ix.chat.Send(nil, "connect", client:SteamName()) + end) + + client:SetNoDraw(true) + client:SetNotSolid(true) + client:Lock() + client:SyncVars() + + timer.Simple(1, function() + if (!IsValid(client)) then + return + end + + client:KillSilent() + client:StripAmmo() + end) +end + +function GM:PlayerUse(client, entity) + if (client:IsRestricted() or (isfunction(entity.GetEntityMenu) and entity:GetClass() != "ix_item")) then + return false + end + + return true +end + +function GM:KeyPress(client, key) + if (key == IN_RELOAD) then + timer.Create("ixToggleRaise"..client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function() + if (IsValid(client)) then + client:ToggleWepRaised() + end + end) + elseif (key == IN_USE) then + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local entity = util.TraceLine(data).Entity + + if (IsValid(entity) and hook.Run("PlayerUse", client, entity)) then + if (entity:IsDoor()) then + local result = hook.Run("CanPlayerUseDoor", client, entity) + + if (result != false) then + hook.Run("PlayerUseDoor", client, entity) + end + end + end + end +end + +function GM:KeyRelease(client, key) + if (key == IN_RELOAD) then + timer.Remove("ixToggleRaise" .. client:SteamID()) + elseif (key == IN_USE) then + timer.Remove("ixCharacterInteraction" .. client:SteamID()) + end +end + +function GM:CanPlayerInteractItem(client, action, item, data) + if (client:IsRestricted()) then + return false + end + + if (IsValid(client.ixRagdoll)) then + client:NotifyLocalized("notNow") + return false + end + + if (action == "drop" and hook.Run("CanPlayerDropItem", client, item) == false) then + return false + end + + if (action == "take" and hook.Run("CanPlayerTakeItem", client, item) == false) then + return false + end + + if (action == "combine") then + local other = data[1] + + if (hook.Run("CanPlayerCombineItem", client, item, other) == false) then + return false + end + + local combineItem = ix.item.instances[other] + + if (combineItem and combineItem.invID != 0) then + local combineInv = ix.item.inventories[combineItem.invID] + + if (!combineInv:OnCheckAccess(client)) then + return false + end + else + return false + end + end + + if (isentity(item) and item.ixSteamID and item.ixCharID + and item.ixSteamID == client:SteamID() and item.ixCharID != client:GetCharacter():GetID() + and !item:GetItemTable().bAllowMultiCharacterInteraction) then + client:NotifyLocalized("itemOwned") + return false + end + + return client:Alive() +end + +function GM:CanPlayerDropItem(client, item) + +end + +function GM:CanPlayerTakeItem(client, item) + +end + +function GM:CanPlayerCombineItem(client, item, other) + +end + +function GM:PlayerShouldTakeDamage(client, attacker) + return client:GetCharacter() != nil +end + +function GM:GetFallDamage(client, speed) + return math.max(0, (speed - 526.5) * 0.75) +end + +function GM:EntityTakeDamage(entity, dmgInfo) + local inflictor = dmgInfo:GetInflictor() + + if (IsValid(inflictor) and inflictor:GetClass() == "ix_item") then + dmgInfo:SetDamage(0) + return + end + + if (IsValid(entity.ixPlayer)) then + if (IsValid(entity.ixHeldOwner)) then + dmgInfo:SetDamage(0) + return + end + + if (dmgInfo:IsDamageType(DMG_CRUSH)) then + if ((entity.ixFallGrace or 0) < CurTime()) then + if (dmgInfo:GetDamage() <= 10) then + dmgInfo:SetDamage(0) + end + + entity.ixFallGrace = CurTime() + 0.5 + else + return + end + end + + entity.ixPlayer:TakeDamageInfo(dmgInfo) + end +end + +function GM:PrePlayerLoadedCharacter(client, character, lastChar) + -- Reset all bodygroups + client:ResetBodygroups() + + -- Remove all skins + client:SetSkin(0) +end + +function GM:PlayerLoadedCharacter(client, character, lastChar) + local query = mysql:Update("ix_characters") + query:Where("id", character:GetID()) + query:Update("last_join_time", math.floor(os.time())) + query:Execute() + + if (lastChar) then + local charEnts = lastChar:GetVar("charEnts") or {} + + for _, v in ipairs(charEnts) do + if (v and IsValid(v)) then + v:Remove() + end + end + + lastChar:SetVar("charEnts", nil) + end + + if (character) then + for _, v in pairs(ix.class.list) do + if (v.faction == client:Team() and v.isDefault) then + character:SetClass(v.index) + + break + end + end + end + + if (IsValid(client.ixRagdoll)) then + client.ixRagdoll.ixNoReset = true + client.ixRagdoll.ixIgnoreDelete = true + client.ixRagdoll:Remove() + end + + local faction = ix.faction.indices[character:GetFaction()] + local uniqueID = "ixSalary" .. client:UniqueID() + + if (faction and faction.pay and faction.pay > 0) then + timer.Create(uniqueID, faction.payTime or 300, 0, function() + if (IsValid(client)) then + if (hook.Run("CanPlayerEarnSalary", client, faction) != false) then + local pay = hook.Run("GetSalaryAmount", client, faction) or faction.pay + + character:GiveMoney(pay) + client:NotifyLocalized("salary", ix.currency.Get(pay)) + end + else + timer.Remove(uniqueID) + end + end) + elseif (timer.Exists(uniqueID)) then + timer.Remove(uniqueID) + end + + hook.Run("PlayerLoadout", client) +end + +function GM:CharacterLoaded(character) + local client = character:GetPlayer() + + if (IsValid(client)) then + local uniqueID = "ixSaveChar"..client:SteamID() + + timer.Create(uniqueID, ix.config.Get("saveInterval"), 0, function() + if (IsValid(client) and client:GetCharacter()) then + client:GetCharacter():Save() + else + timer.Remove(uniqueID) + end + end) + end +end + +function GM:PlayerSay(client, text) + local chatType, message, anonymous = ix.chat.Parse(client, text, true) + + if (chatType == "ic") then + if (ix.command.Parse(client, message)) then + return "" + end + end + + text = ix.chat.Send(client, chatType, message, anonymous) + + if (isstring(text) and chatType != "ic") then + ix.log.Add(client, "chat", chatType and chatType:utf8upper() or "??", text) + end + + hook.Run("PostPlayerSay", client, chatType, message, anonymous) + return "" +end + +function GM:CanAutoFormatMessage(client, chatType, message) + return chatType == "ic" or chatType == "w" or chatType == "y" +end + +function GM:PlayerSpawn(client) + client:SetNoDraw(false) + client:UnLock() + client:SetNotSolid(false) + client:SetMoveType(MOVETYPE_WALK) + client:SetRagdolled(false) + client:SetAction() + client:SetDSP(1) + + hook.Run("PlayerLoadout", client) +end + +-- Shortcuts for (super)admin only things. +local function IsAdmin(_, client) + return client:IsAdmin() +end + +-- Set the gamemode hooks to the appropriate shortcuts. +GM.PlayerGiveSWEP = IsAdmin +GM.PlayerSpawnEffect = IsAdmin +GM.PlayerSpawnSENT = IsAdmin + +function GM:PlayerSpawnNPC(client, npcType, weapon) + return client:IsAdmin() or client:GetCharacter():HasFlags("n") +end + +function GM:PlayerSpawnSWEP(client, weapon, info) + return client:IsAdmin() +end + +function GM:PlayerSpawnProp(client) + if (client:GetCharacter() and client:GetCharacter():HasFlags("e")) then + return true + end + + return false +end + +function GM:PlayerSpawnRagdoll(client) + if (client:GetCharacter() and client:GetCharacter():HasFlags("r")) then + return true + end + + return false +end + +function GM:PlayerSpawnVehicle(client, model, name, data) + if (client:GetCharacter()) then + if (data.Category == "Chairs") then + return client:GetCharacter():HasFlags("c") + else + return client:GetCharacter():HasFlags("C") + end + end + + return false +end + +function GM:PlayerSpawnedEffect(client, model, entity) + entity:SetNetVar("owner", client:GetCharacter():GetID()) +end + +function GM:PlayerSpawnedNPC(client, entity) + entity:SetNetVar("owner", client:GetCharacter():GetID()) +end + +function GM:PlayerSpawnedProp(client, model, entity) + entity:SetNetVar("owner", client:GetCharacter():GetID()) +end + +function GM:PlayerSpawnedRagdoll(client, model, entity) + entity:SetNetVar("owner", client:GetCharacter():GetID()) +end + +function GM:PlayerSpawnedSENT(client, entity) + entity:SetNetVar("owner", client:GetCharacter():GetID()) +end + +function GM:PlayerSpawnedSWEP(client, entity) + entity:SetNetVar("owner", client:GetCharacter():GetID()) +end + +function GM:PlayerSpawnedVehicle(client, entity) + entity:SetNetVar("owner", client:GetCharacter():GetID()) +end + +ix.allowedHoldableClasses = { + ["ix_item"] = true, + ["ix_money"] = true, + ["ix_shipment"] = true, + ["prop_physics"] = true, + ["prop_physics_override"] = true, + ["prop_physics_multiplayer"] = true, + ["prop_ragdoll"] = true +} + +function GM:CanPlayerHoldObject(client, entity) + if (ix.allowedHoldableClasses[entity:GetClass()]) then + return true + end + + return hook.Run("CanPlayerPickupEntity", client, entity) == true +end + +local voiceDistance = 360000 +local function CalcPlayerCanHearPlayersVoice(listener) + if (!IsValid(listener)) then + return + end + + listener.ixVoiceHear = listener.ixVoiceHear or {} + + local eyePos = listener:EyePos() + for _, speaker in player.Iterator() do + local speakerEyePos = speaker:EyePos() + listener.ixVoiceHear[speaker] = eyePos:DistToSqr(speakerEyePos) < voiceDistance + end +end + +function GM:InitializedConfig() + ix.date.Initialize() + + voiceDistance = ix.config.Get("voiceDistance") + voiceDistance = voiceDistance * voiceDistance +end + +function GM:VoiceToggled(bAllowVoice) + for _, v in player.Iterator() do + local uniqueID = v:SteamID64() .. "ixCanHearPlayersVoice" + + if (bAllowVoice) then + timer.Create(uniqueID, 0.5, 0, function() + CalcPlayerCanHearPlayersVoice(v) + end) + else + timer.Remove(uniqueID) + + v.ixVoiceHear = nil + end + end +end + +function GM:VoiceDistanceChanged(distance) + voiceDistance = distance * distance +end + +-- Called when weapons should be given to a player. +function GM:PlayerLoadout(client) + if (client.ixSkipLoadout) then + client.ixSkipLoadout = nil + + return + end + + client:SetWeaponColor(Vector(client:GetInfo("cl_weaponcolor"))) + client:StripWeapons() + client:StripAmmo() + client:SetLocalVar("blur", nil) + + local character = client:GetCharacter() + + -- Check if they have loaded a character. + if (character) then + client:SetupHands() + -- Set their player model to the character's model. + client:SetModel(character:GetModel()) + client:Give("ix_hands") + client:SetWalkSpeed(ix.config.Get("walkSpeed")) + client:SetRunSpeed(ix.config.Get("runSpeed")) + client:SetHealth(character:GetData("health", client:GetMaxHealth())) + + local faction = ix.faction.indices[client:Team()] + + if (faction) then + -- If their faction wants to do something when the player spawns, let it. + if (faction.OnSpawn) then + faction:OnSpawn(client) + end + + -- @todo add docs for player:Give() failing if player already has weapon - which means if a player is given a weapon + -- here due to the faction weapons table, the weapon's :Give call in the weapon base will fail since the player + -- will already have it by then. This will cause issues for weapons that have pac data since the parts are applied + -- only if the weapon returned by :Give() is valid + + -- If the faction has default weapons, give them to the player. + if (faction.weapons) then + for _, v in ipairs(faction.weapons) do + client:Give(v) + end + end + end + + -- Ditto, but for classes. + local class = ix.class.list[client:GetCharacter():GetClass()] + + if (class) then + if (class.OnSpawn) then + class:OnSpawn(client) + end + + if (class.weapons) then + for _, v in ipairs(class.weapons) do + client:Give(v) + end + end + end + + -- Apply any flags as needed. + ix.flag.OnSpawn(client) + ix.attributes.Setup(client) + + hook.Run("PostPlayerLoadout", client) + + client:SelectWeapon("ix_hands") + else + client:SetNoDraw(true) + client:Lock() + client:SetNotSolid(true) + end +end + +function GM:PostPlayerLoadout(client) + -- Reload All Attrib Boosts + local character = client:GetCharacter() + if (!character) then return end + + local inventory = character:GetInventory() + + if (inventory and !isnumber(inventory)) then + for k, _ in inventory:Iter() do + k:Call("OnLoadout", client) + + if (k:GetData("equip") and k.attribBoosts) then + for attribKey, attribValue in pairs(k.attribBoosts) do + character:AddBoost(k.uniqueID, attribKey, attribValue) + end + end + end + end + + if (ix.config.Get("allowVoice")) then + timer.Create(client:SteamID64() .. "ixCanHearPlayersVoice", 0.5, 0, function() + CalcPlayerCanHearPlayersVoice(client) + end) + end +end + +local painSounds = { + Sound("vo/npc/male01/pain01.wav"), + Sound("vo/npc/male01/pain02.wav"), + Sound("vo/npc/male01/pain03.wav"), + Sound("vo/npc/male01/pain04.wav"), + Sound("vo/npc/male01/pain05.wav"), + Sound("vo/npc/male01/pain06.wav") +} + +local drownSounds = { + Sound("player/pl_drown1.wav"), + Sound("player/pl_drown2.wav"), + Sound("player/pl_drown3.wav"), +} + +function GM:GetPlayerPainSound(client) + if (client:WaterLevel() >= 3) then + return drownSounds[math.random(1, #drownSounds)] + end +end + +function GM:PlayerHurt(client, attacker, health, damage) + if ((client.ixNextPain or 0) < CurTime() and health > 0) then + local painSound = hook.Run("GetPlayerPainSound", client) or painSounds[math.random(1, #painSounds)] + + if (client:IsFemale() and !painSound:find("female")) then + painSound = painSound:gsub("male", "female") + end + + client:EmitSound(painSound) + client.ixNextPain = CurTime() + 0.33 + end + + ix.log.Add(client, "playerHurt", damage, attacker:GetName() ~= "" and attacker:GetName() or attacker:GetClass()) +end + +function GM:PlayerDeathThink(client) + if (client:GetCharacter()) then + local deathTime = client:GetNetVar("deathTime") + + if (deathTime and deathTime <= CurTime()) then + client:Spawn() + end + end + + return false +end + +hook.Add("PlayerDeath", "SyncDeathTimeWithScreen", function(ply) + local duration = ix.config.Get("deathScreenDuration", 5.5) + ply:SetNetVar("deathTime", CurTime() + duration) +end) + +function GM:PlayerDisconnected(client) + client:SaveData() + + local character = client:GetCharacter() + + if (character) then + local charEnts = character:GetVar("charEnts") or {} + + for _, v in ipairs(charEnts) do + if (v and IsValid(v)) then + v:Remove() + end + end + + hook.Run("OnCharacterDisconnect", client, character) + character:Save() + ix.chat.Send(nil, "disconnect", client:SteamName()) + end + + if (IsValid(client.ixRagdoll)) then + client.ixRagdoll:Remove() + end + + client:ClearNetVars() + + if (!client.ixVoiceHear) then + return + end + + for _, v in player.Iterator() do + if (!v.ixVoiceHear) then + continue + end + + v.ixVoiceHear[client] = nil + end + + timer.Remove(client:SteamID64() .. "ixCanHearPlayersVoice") +end + +function GM:InitPostEntity() + local doors = ents.FindByClass("prop_door_rotating") + + for _, v in ipairs(doors) do + local parent = v:GetOwner() + + if (IsValid(parent)) then + v.ixPartner = parent + parent.ixPartner = v + else + for _, v2 in ipairs(doors) do + if (v2:GetOwner() == v) then + v2.ixPartner = v + v.ixPartner = v2 + + break + end + end + end + end + + timer.Simple(2, function() + ix.entityDataLoaded = true + end) +end + +function GM:SaveData() + ix.date.Save() +end + +function GM:ShutDown() + ix.shuttingDown = true + ix.config.Save() + + hook.Run("SaveData") + + for _, v in player.Iterator() do + v:SaveData() + + if (v:GetCharacter()) then + v:GetCharacter():Save() + end + end +end + +function GM:GetGameDescription() + return (Schema and Schema.name or "Unknown") +end + +function GM:OnPlayerUseBusiness(client, item) + -- You can manipulate purchased items with this hook. + -- does not requires any kind of return. + -- ex) item:SetData("businessItem", true) + -- then every purchased item will be marked as Business Item. +end + +function GM:PlayerDeathSound() + return true +end + +function GM:InitializedSchema() + game.ConsoleCommand("sbox_persist ix_"..Schema.folder.."\n") +end + +function GM:PlayerCanHearPlayersVoice(listener, speaker) + if (!speaker:Alive()) then + return false + end + + local bCanHear = listener.ixVoiceHear and listener.ixVoiceHear[speaker] + return bCanHear, true +end + +function GM:PlayerCanPickupWeapon(client, weapon) + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local trace = util.TraceLine(data) + + if (trace.Entity == weapon and client:KeyDown(IN_USE)) then + return true + end + + return client.ixWeaponGive +end + +function GM:OnPhysgunFreeze(weapon, physObj, entity, client) + -- Validate the physObj, to prevent errors on entities who have no physics object + if (!IsValid(physObj)) then return false end + + -- Object is already frozen (!?) + if (!physObj:IsMoveable()) then return false end + if (entity:GetUnFreezable()) then return false end + + physObj:EnableMotion(false) + + -- With the jeep we need to pause all of its physics objects + -- to stop it spazzing out and killing the server. + if (entity:GetClass() == "prop_vehicle_jeep") then + local objects = entity:GetPhysicsObjectCount() + + for i = 0, objects - 1 do + entity:GetPhysicsObjectNum(i):EnableMotion(false) + end + end + + -- Add it to the player's frozen props + client:AddFrozenPhysicsObject(entity, physObj) + client:SendHint("PhysgunUnfreeze", 0.3) + client:SuppressHint("PhysgunFreeze") + + return true +end + +function GM:CanPlayerSuicide(client) + return false +end + +function GM:AllowPlayerPickup(client, entity) + if (ix.allowedHoldableClasses[entity:GetClass()]) then + return true + end + + return hook.Run("CanPlayerPickupEntity", client, entity) == true +end + +function GM:PreCleanupMap() + hook.Run("SaveData") + hook.Run("PersistenceSave") +end + +function GM:PostCleanupMap() + ix.plugin.RunLoadData() +end + +function GM:CharacterPreSave(character) + if (!character) then return end + + local client = character:GetPlayer() + local inventory = character:GetInventory() + + if (inventory and !isnumber(inventory)) then + for k, _ in inventory:Iter() do + if (k.OnSave) then + k:Call("OnSave", client) + end + end + end + + character:SetData("health", client:Alive() and client:Health() or nil) +end + +timer.Create("ixLifeGuard", 1, 0, function() + for _, v in player.Iterator() do + if (v:GetCharacter() and v:Alive() and hook.Run("ShouldPlayerDrowned", v) != false) then + if (v:WaterLevel() >= 3) then + if (!v.drowningTime) then + v.drowningTime = CurTime() + 30 + v.nextDrowning = CurTime() + v.drownDamage = v.drownDamage or 0 + end + + if (v.drowningTime < CurTime()) then + if (v.nextDrowning < CurTime()) then + v:ScreenFade(1, Color(0, 0, 255, 100), 1, 0) + v:TakeDamage(10) + v.drownDamage = v.drownDamage + 10 + v.nextDrowning = CurTime() + 1 + end + end + else + if (v.drowningTime) then + v.drowningTime = nil + v.nextDrowning = nil + v.nextRecover = CurTime() + 2 + end + + if (v.nextRecover and v.nextRecover < CurTime() and v.drownDamage > 0) then + v.drownDamage = v.drownDamage - 10 + v:SetHealth(math.Clamp(v:Health() + 10, 0, v:GetMaxHealth())) + v.nextRecover = CurTime() + 1 + end + end + end + end +end) + +net.Receive("ixStringRequest", function(length, client) + local time = net.ReadUInt(32) + local text = net.ReadString() + + if (client.ixStrReqs and client.ixStrReqs[time]) then + client.ixStrReqs[time](text) + client.ixStrReqs[time] = nil + end +end) + +function GM:GetPreferredCarryAngles(entity) + if (entity:GetClass() == "ix_item") then + local itemTable = entity:GetItemTable() + + if (itemTable) then + local preferedAngle = itemTable.preferedAngle + + if (preferedAngle) then -- I don't want to return something + return preferedAngle + end + end + end +end + +function GM:PluginShouldLoad(uniqueID) + return !ix.plugin.unloaded[uniqueID] +end + +function GM:DatabaseConnected() + -- Create the SQL tables if they do not exist. + ix.db.LoadTables() + ix.log.LoadTables() + + MsgC(Color(0, 255, 0), "Database Type: " .. ix.db.config.adapter .. ".\n") + + timer.Create("ixDatabaseThink", 0.5, 0, function() + mysql:Think() + end) + + ix.plugin.RunLoadData() +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/cl_bar.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_bar.lua new file mode 100644 index 0000000..64df41f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_bar.lua @@ -0,0 +1,121 @@ + +ix.bar = ix.bar or {} +ix.bar.list = {} +ix.bar.delta = ix.bar.delta or {} +ix.bar.actionText = "" +ix.bar.actionStart = 0 +ix.bar.actionEnd = 0 +ix.bar.totalHeight = 0 + +-- luacheck: globals BAR_HEIGHT +BAR_HEIGHT = 10 + +function ix.bar.Get(identifier) + for _, v in ipairs(ix.bar.list) do + if (v.identifier == identifier) then + return v + end + end +end + +function ix.bar.Remove(identifier) + local bar = ix.bar.Get(identifier) + + if (bar) then + table.remove(ix.bar.list, bar.index) + + if (IsValid(ix.gui.bars)) then + ix.gui.bars:RemoveBar(bar.panel) + end + end +end + +function ix.bar.Add(getValue, color, priority, identifier) + if (identifier) then + ix.bar.Remove(identifier) + end + + local index = #ix.bar.list + 1 + + color = color or Color(math.random(150, 255), math.random(150, 255), math.random(150, 255)) + priority = priority or index + + ix.bar.list[index] = { + index = index, + color = color, + priority = priority, + GetValue = getValue, + identifier = identifier, + panel = IsValid(ix.gui.bars) and ix.gui.bars:AddBar(index, color, priority) + } + + return priority +end + +local gradientD = ix.util.GetMaterial("vgui/gradient-d") + +local TEXT_COLOR = Color(240, 240, 240) +local SHADOW_COLOR = Color(20, 20, 20) + +function ix.bar.DrawAction() + local start, finish = ix.bar.actionStart, ix.bar.actionEnd + local curTime = CurTime() + local scrW, scrH = ScrW(), ScrH() + + if (finish > curTime) then + local fraction = 1 - math.TimeFraction(start, finish, curTime) + local alpha = fraction * 255 + + if (alpha > 0) then + local w, h = scrW * 0.35, 28 + local x, y = (scrW * 0.5) - (w * 0.5), (scrH * 0.725) - (h * 0.5) + + ix.util.DrawBlurAt(x, y, w, h) + + surface.SetDrawColor(35, 35, 35, 100) + surface.DrawRect(x, y, w, h) + + surface.SetDrawColor(0, 0, 0, 120) + surface.DrawOutlinedRect(x, y, w, h) + + surface.SetDrawColor(ix.config.Get("color")) + surface.DrawRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8) + + surface.SetDrawColor(200, 200, 200, 20) + surface.SetMaterial(gradientD) + surface.DrawTexturedRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8) + + draw.SimpleText(ix.bar.actionText, "ixMediumFont", x + 2, y - 22, SHADOW_COLOR) + draw.SimpleText(ix.bar.actionText, "ixMediumFont", x, y - 24, TEXT_COLOR) + end + end +end + +do + ix.bar.Add(function() + return math.max(LocalPlayer():Health() / LocalPlayer():GetMaxHealth(), 0) + end, Color(200, 50, 40), nil, "health") + + ix.bar.Add(function() + return math.min(LocalPlayer():Armor() / 100, 1) + end, Color(30, 70, 180), nil, "armor") +end + +net.Receive("ixActionBar", function() + local start, finish = net.ReadFloat(), net.ReadFloat() + local text = net.ReadString() + + if (text:sub(1, 1) == "@") then + text = L2(text:sub(2)) or text + end + + ix.bar.actionStart = start + ix.bar.actionEnd = finish + ix.bar.actionText = text:utf8upper() +end) + +net.Receive("ixActionBarReset", function() + ix.bar.actionStart = 0 + ix.bar.actionEnd = 0 + ix.bar.actionText = "" +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/cl_hud.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_hud.lua new file mode 100644 index 0000000..fa96aa4 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_hud.lua @@ -0,0 +1,88 @@ + +ix.hud = {} + +function ix.hud.DrawItemPickup() + local pickupTime = ix.config.Get("itemPickupTime", 0.5) + + if (pickupTime == 0) then + return + end + + local client = LocalPlayer() + local entity = client.ixInteractionTarget + local startTime = client.ixInteractionStartTime + + if (IsValid(entity) and startTime) then + local sysTime = SysTime() + local endTime = startTime + pickupTime + + if (sysTime >= endTime or client:GetEyeTrace().Entity != entity) then + client.ixInteractionTarget = nil + client.ixInteractionStartTime = nil + + return + end + + local fraction = math.min((endTime - sysTime) / pickupTime, 1) + local x, y = ScrW() / 2, ScrH() / 2 + local radius, thickness = 32, 6 + local startAngle = 90 + local endAngle = startAngle + (1 - fraction) * 360 + local color = ColorAlpha(color_white, fraction * 255) + + ix.util.DrawArc(x, y, radius, thickness, startAngle, endAngle, 2, color) + end +end + +function ix.hud.PopulateItemTooltip(tooltip, item) + local name = tooltip:AddRow("name") + name:SetImportant() + name:SetText(item.GetName and item:GetName() or L(item.name)) + name:SetMaxWidth(math.max(name:GetMaxWidth(), ScrW() * 0.5)) + name:SizeToContents() + + local description = tooltip:AddRow("description") + description:SetText(item:GetDescription() or "") + description:SizeToContents() + + if (item.PopulateTooltip) then + item:PopulateTooltip(tooltip) + end + + hook.Run("PopulateItemTooltip", tooltip, item) +end + +function ix.hud.PopulatePlayerTooltip(tooltip, client) + local name = tooltip:AddRow("name") + name:SetImportant() + name:SetText(client:SteamName()) + name:SetBackgroundColor(team.GetColor(client:Team())) + name:SizeToContents() + + local nameHeight = name:GetTall() + name:SetTextInset(nameHeight + 4, 0) + name:SetWide(name:GetWide() + nameHeight + 4) + + local avatar = name:Add("AvatarImage") + avatar:Dock(LEFT) + avatar:SetPlayer(client, nameHeight) + avatar:SetSize(name:GetTall(), name:GetTall()) + + local currentPing = client:Ping() + + local ping = tooltip:AddRow("ping") + ping:SetText(L("ping", currentPing)) + ping.Paint = function(_, width, height) + surface.SetDrawColor(ColorAlpha(derma.GetColor( + currentPing < 110 and "Success" or (currentPing < 165 and "Warning" or "Error") + , tooltip), 22)) + surface.DrawRect(0, 0, width, height) + end + ping:SizeToContents() + + hook.Run("PopulatePlayerTooltip", client, tooltip) +end + +function ix.hud.DrawAll() + ix.hud.DrawItemPickup() +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/cl_markup.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_markup.lua new file mode 100644 index 0000000..5baccf3 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_markup.lua @@ -0,0 +1,496 @@ + +-- luacheck: ignore +ix.markup = ix.markup or {} + +-- Temporary information used when building text frames. +local colour_stack = { {r=255,g=255,b=255,a=255} } +local font_stack = { "DermaDefault" } +local curtag = nil +local blocks = {} + +local colourmap = { +-- it's all black and white + ["black"] = { r=0, g=0, b=0, a=255 }, + ["white"] = { r=255, g=255, b=255, a=255 }, +-- it's greys + ["dkgrey"] = { r=64, g=64, b=64, a=255 }, + ["grey"] = { r=128, g=128, b=128, a=255 }, + ["ltgrey"] = { r=192, g=192, b=192, a=255 }, +-- account for speeling mistakes + ["dkgray"] = { r=64, g=64, b=64, a=255 }, + ["gray"] = { r=128, g=128, b=128, a=255 }, + ["ltgray"] = { r=192, g=192, b=192, a=255 }, +-- normal colours + ["red"] = { r=255, g=0, b=0, a=255 }, + ["green"] = { r=0, g=255, b=0, a=255 }, + ["blue"] = { r=0, g=0, b=255, a=255 }, + ["yellow"] = { r=255, g=255, b=0, a=255 }, + ["purple"] = { r=255, g=0, b=255, a=255 }, + ["cyan"] = { r=0, g=255, b=255, a=255 }, + ["turq"] = { r=0, g=255, b=255, a=255 }, +-- dark variations + ["dkred"] = { r=128, g=0, b=0, a=255 }, + ["dkgreen"] = { r=0, g=128, b=0, a=255 }, + ["dkblue"] = { r=0, g=0, b=128, a=255 }, + ["dkyellow"] = { r=128, g=128, b=0, a=255 }, + ["dkpurple"] = { r=128, g=0, b=128, a=255 }, + ["dkcyan"] = { r=0, g=128, b=128, a=255 }, + ["dkturq"] = { r=0, g=128, b=128, a=255 }, +-- light variations + ["ltred"] = { r=255, g=128, b=128, a=255 }, + ["ltgreen"] = { r=128, g=255, b=128, a=255 }, + ["ltblue"] = { r=128, g=128, b=255, a=255 }, + ["ltyellow"] = { r=255, g=255, b=128, a=255 }, + ["ltpurple"] = { r=255, g=128, b=255, a=255 }, + ["ltcyan"] = { r=128, g=255, b=255, a=255 }, + ["ltturq"] = { r=128, g=255, b=255, a=255 }, +} + +--[[ + Name: colourMatch(c) + Desc: Match a colour name to an rgb value. + Usage: ** INTERNAL ** Do not use! +]] +local function colourMatch(c) + c = string.lower(c) + + return colourmap[c] +end + +--[[ + Name: ExtractParams(p1,p2,p3) + Desc: This function is used to extract the tag information. + Usage: ** INTERNAL ** Do not use! +]] +local function ExtractParams(p1,p2,p3) + + if (string.utf8sub(p1, 1, 1) == "/") then + + local tag = string.utf8sub(p1, 2) + + if (tag == "color" or tag == "colour") then + table.remove(colour_stack) + elseif (tag == "font" or tag == "face") then + table.remove(font_stack) + end + + else + + if (p1 == "color" or p1 == "colour") then + + local rgba = colourMatch(p2) + + if (rgba == nil) then + rgba = {} + local x = { "r", "g", "b", "a" } + n = 1 + for k, v in string.gmatch(p2, "(%d+),?") do + rgba[ x[n] ] = k + n = n + 1 + end + end + + table.insert(colour_stack, rgba) + + elseif (p1 == "font" or p1 == "face") then + + table.insert(font_stack, tostring(p2)) + elseif (p1 == "img" and p2) then + local exploded = string.Explode(",", p2) + local material = exploded[1] or p2 + local p3 = exploded[2] + + local found = file.Find("materials/"..material..".*", "GAME") + + if (found[1] and found[1]:find("%.png")) then + material = material..".png" + end + + local texture = Material(material) + local sizeData = string.Explode("x", p3 or "16x16") + w = tonumber(sizeData[1]) or 16 + h = tonumber(sizeData[2]) or 16 + + if (texture) then + table.insert(blocks, { + texture = texture, + w = w, + h = h + }) + end + end + + end +end + +--[[ + Name: CheckTextOrTag(p) + Desc: This function places data in the "blocks" table + depending of if p is a tag, or some text + Usage: ** INTERNAL ** Do not use! +]] +local function CheckTextOrTag(p) + if (p == "") then return end + if (p == nil) then return end + + if (string.utf8sub(p, 1, 1) == "<") then + string.gsub(p, "<([/%a]*)=?([^>]*)", ExtractParams) + else + + local text_block = {} + text_block.text = p + text_block.colour = colour_stack[#colour_stack] + text_block.font = font_stack[#font_stack] + table.insert(blocks, text_block) + + end +end + +--[[ + Name: ProcessMatches(p1,p2,p3) + Desc: CheckTextOrTag for 3 parameters. Called by string.gsub + Usage: ** INTERNAL ** Do not use! +]] +local function ProcessMatches(p1,p2,p3) + if (p1) then CheckTextOrTag(p1) end + if (p2) then CheckTextOrTag(p2) end + if (p3) then CheckTextOrTag(p3) end +end + +local MarkupObject = {} + +--[[ + Name: MarkupObject:Create() + Desc: Called by Parse. Creates a new table, and setups the + metatable. + Usage: ** INTERNAL ** Do not use! +]] +function MarkupObject:create() + local o = {} + setmetatable(o, self) + self.__index = self + + return o +end + +--[[ + Name: MarkupObject:GetWidth() + Desc: Returns the width of a markup block + Usage: ml:GetWidth() +]] +function MarkupObject:GetWidth() + return self.totalWidth +end + +--[[ + Name: MarkupObject:GetHeight() + Desc: Returns the height of a markup block + Usage: ml:GetHeight() +]] +function MarkupObject:GetHeight() + return self.totalHeight +end + +function MarkupObject:size() + return self.totalWidth, self.totalHeight +end + +--[[ + Name: MarkupObject:Draw(xOffset, yOffset, halign, valign, alphaoverride) + Desc: Draw the markup text to the screen as position + xOffset, yOffset. Halign and Valign can be used + to align the text. Alphaoverride can be used to override + the alpha value of the text-colour. + Usage: MarkupObject:Draw(100, 100) +]] +function MarkupObject:draw(xOffset, yOffset, halign, valign, alphaoverride) + for i = 1, #self.blocks do + local blk = self.blocks[i] + + if (blk.texture) then + local y = yOffset + blk.offset.y + local x = xOffset + blk.offset.x + + if (halign == TEXT_ALIGN_CENTER) then + x = x - (self.totalWidth * 0.5) + elseif (halign == TEXT_ALIGN_RIGHT) then + x = x - (self.totalWidth) + end + + surface.SetDrawColor(blk.colour.r, blk.colour.g, blk.colour.b, alphaoverride or blk.colour.a or 255) + surface.SetMaterial(blk.texture) + surface.DrawTexturedRect(x, y, blk.w, blk.h) + else + local y = yOffset + (blk.height - blk.thisY) + blk.offset.y + local x = xOffset + + if (halign == TEXT_ALIGN_CENTER) then x = x - (self.totalWidth / 2) + elseif (halign == TEXT_ALIGN_RIGHT) then x = x - (self.totalWidth) + end + + x = x + blk.offset.x + + if (self.onDrawText) then + self.onDrawText(blk.text, blk.font, x, y, blk.colour, halign, valign, alphaoverride, blk) + else + if (valign == TEXT_ALIGN_CENTER) then y = y - (self.totalHeight / 2) + elseif (valign == TEXT_ALIGN_BOTTOM) then y = y - (self.totalHeight) + end + + local alpha = blk.colour.a + if (alphaoverride) then alpha = alphaoverride end + + surface.SetFont( blk.font ) + surface.SetTextColor( blk.colour.r, blk.colour.g, blk.colour.b, alpha ) + surface.SetTextPos( x, y ) + surface.DrawText( blk.text ) + end + end + end +end + +--[[ + Name: Parse(ml, maxwidth) + Desc: Parses the pseudo-html markup language, and creates a + MarkupObject, which can be used to the draw the + text to the screen. Valid tags are: font and colour. + \n and \t are also available to move to the next line, + or insert a tab character. + Maxwidth can be used to make the text wrap to a specific + width. + Usage: markup.Parse("changed font\nchanged colour") +]] +function ix.markup.Parse(ml, maxwidth) + + ml = utf8.force(ml) + + colour_stack = { {r=255,g=255,b=255,a=255} } + font_stack = { "DermaDefault" } + blocks = {} + + if (not string.find(ml, "<")) then + ml = ml .. "" + end + + string.gsub(ml, "([^<>]*)(<[^>]+.)([^<>]*)", ProcessMatches) + + local xOffset = 0 + local yOffset = 0 + local xSize = 0 + local xMax = 0 + local thisMaxY = 0 + local new_block_list = {} + local ymaxes = {} + local texOffset = 0 + + local lineHeight = 0 + for i = 1, #blocks do + local block = blocks[i] + + if (block.text) then + surface.SetFont(block.font) + + local thisY = 0 + local curString = "" + block.text = string.gsub(block.text, ">", ">") + block.text = string.gsub(block.text, "<", "<") + block.text = string.gsub(block.text, "&", "&") + + for j=1,string.utf8len(block.text) do + local ch = string.utf8sub(block.text,j,j) + + if (ch == "\n") then + if (thisY == 0) then + thisY = lineHeight + texOffset; + thisMaxY = lineHeight + texOffset; + else + lineHeight = thisY + texOffset + end + + if (string.utf8len(curString) > 0) then + local x1,y1 = surface.GetTextSize(curString) + + local new_block = {} + new_block.text = curString + new_block.font = block.font + new_block.colour = block.colour + new_block.thisY = thisY + new_block.thisX = x1 + new_block.offset = {} + new_block.offset.x = xOffset + new_block.offset.y = yOffset + table.insert(new_block_list, new_block) + if (xOffset + x1 > xMax) then + xMax = xOffset + x1 + end + end + + xOffset = 0 + xSize = 0 + yOffset = yOffset + thisMaxY; + thisY = 0 + curString = "" + thisMaxY = 0 + elseif (ch == "\t") then + + if (string.utf8len(curString) > 0) then + local x1,y1 = surface.GetTextSize(curString) + + local new_block = {} + new_block.text = curString + new_block.font = block.font + new_block.colour = block.colour + new_block.thisY = thisY + new_block.thisX = x1 + new_block.offset = {} + new_block.offset.x = xOffset + new_block.offset.y = yOffset + table.insert(new_block_list, new_block) + if (xOffset + x1 > xMax) then + xMax = xOffset + x1 + end + end + + local xOldSize = xSize + xSize = 0 + curString = "" + local xOldOffset = xOffset + xOffset = math.ceil( (xOffset + xOldSize) / 50 ) * 50 + + if (xOffset == xOldOffset) then + xOffset = xOffset + 50 + end + else + local x,y = surface.GetTextSize(ch) + + if (x == nil) then return end + + if (maxwidth and maxwidth > x) then + if (xOffset + xSize + x >= maxwidth) then + + -- need to: find the previous space in the curString + -- if we can't find one, take off the last character + -- and add a -. add the character to ch + -- and insert as a new block, incrementing the y etc + + local lastSpacePos = string.utf8len(curString) + for k=1,string.utf8len(curString) do + local chspace = string.utf8sub(curString,k,k) + if (chspace == " ") then + lastSpacePos = k + end + end + + if (lastSpacePos == string.utf8len(curString)) then + ch = string.utf8sub(curString,lastSpacePos,lastSpacePos) .. ch + j = lastSpacePos + curString = string.utf8sub(curString, 1, lastSpacePos-1) + else + ch = string.utf8sub(curString,lastSpacePos+1) .. ch + j = lastSpacePos+1 + curString = string.utf8sub(curString, 1, lastSpacePos) + end + + local m = 1 + while string.utf8sub(ch, m, m) == " " and m <= string.utf8len(ch) do + m = m + 1 + end + ch = string.utf8sub(ch, m) + + local x1,y1 = surface.GetTextSize(curString) + + if (y1 > thisMaxY) then thisMaxY = y1; ymaxes[yOffset] = thisMaxY; lineHeight = y1; end + + local new_block = {} + new_block.text = curString + new_block.font = block.font + new_block.colour = block.colour + new_block.thisY = thisY + new_block.thisX = x1 + new_block.offset = {} + new_block.offset.x = xOffset + new_block.offset.y = yOffset + table.insert(new_block_list, new_block) + + if (xOffset + x1 > xMax) then + xMax = xOffset + x1 + end + + xOffset = 0 + xSize = 0 + x,y = surface.GetTextSize(ch) + yOffset = yOffset + thisMaxY; + thisY = 0 + curString = "" + thisMaxY = 0 + end + end + + curString = curString .. ch + + thisY = y + xSize = xSize + x + + if (y > thisMaxY) then thisMaxY = y; ymaxes[yOffset] = thisMaxY; lineHeight = y; end + end + end + + if (string.utf8len(curString) > 0) then + + local x1,y1 = surface.GetTextSize(curString) + + local new_block = {} + new_block.text = curString + new_block.font = block.font + new_block.colour = block.colour + new_block.thisY = thisY + new_block.thisX = x1 + new_block.offset = {} + new_block.offset.x = xOffset + new_block.offset.y = yOffset + table.insert(new_block_list, new_block) + + lineHeight = thisY + + if (xOffset + x1 > xMax) then + xMax = xOffset + x1 + end + xOffset = xOffset + x1 + end + xSize = 0 + elseif (block.texture) then + local newBlock = table.Copy(block) + newBlock.colour = block.colour or {r = 255, g = 255, b = 255, a = 255} + newBlock.thisX = block.w + newBlock.thisY = block.h + newBlock.offset = { + x = xOffset, + y = 0 + } + + table.insert(new_block_list, newBlock) + xOffset = xOffset + block.w + 1 + texOffset = block.h / 2 + end + end + + local totalHeight = 0 + for i = 1, #new_block_list do + local block = new_block_list[i] + block.height = ymaxes[block.offset.y] + + if (block.texture) then + block.offset.y = ymaxes[0] * 0.5 - block.h * 0.5 + end + + if (block.height and block.offset.y + block.height > totalHeight) then + totalHeight = block.offset.y + block.height + end + end + + local newObject = MarkupObject:create() + newObject.totalHeight = totalHeight + newObject.totalWidth = xMax + newObject.blocks = new_block_list + return newObject +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/cl_networking.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_networking.lua new file mode 100644 index 0000000..41692c3 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/cl_networking.lua @@ -0,0 +1,49 @@ + +local entityMeta = FindMetaTable("Entity") +local playerMeta = FindMetaTable("Player") + +ix.net = ix.net or {} +ix.net.globals = ix.net.globals or {} + +net.Receive("ixGlobalVarSet", function() + ix.net.globals[net.ReadString()] = net.ReadType() +end) + +net.Receive("ixNetVarSet", function() + local index = net.ReadUInt(16) + + ix.net[index] = ix.net[index] or {} + ix.net[index][net.ReadString()] = net.ReadType() +end) + +net.Receive("ixNetVarDelete", function() + ix.net[net.ReadUInt(16)] = nil +end) + +net.Receive("ixLocalVarSet", function() + local key = net.ReadString() + local var = net.ReadType() + + ix.net[LocalPlayer():EntIndex()] = ix.net[LocalPlayer():EntIndex()] or {} + ix.net[LocalPlayer():EntIndex()][key] = var + + hook.Run("OnLocalVarSet", key, var) +end) + +function GetNetVar(key, default) -- luacheck: globals GetNetVar + local value = ix.net.globals[key] + + return value != nil and value or default +end + +function entityMeta:GetNetVar(key, default) + local index = self:EntIndex() + + if (ix.net[index] and ix.net[index][key] != nil) then + return ix.net[index][key] + end + + return default +end + +playerMeta.GetLocalVar = entityMeta.GetNetVar diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_animation.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_animation.lua new file mode 100644 index 0000000..27b5975 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_animation.lua @@ -0,0 +1,150 @@ + +function ix.util.InstallAnimationMethods(meta) + local function TweenAnimationThink(object) + for k, v in pairs(object.tweenAnimations) do + if (!v.bShouldPlay) then + continue + end + + local bComplete = v:update(FrameTime()) + + if (v.Think) then + v:Think(object) + end + + if (bComplete) then + v.bShouldPlay = nil + + v:ForceComplete() + + if (v.OnComplete) then + v:OnComplete(object) + end + + if (v.bRemoveOnComplete) then + object.tweenAnimations[k] = nil + end + end + end + end + + function meta:GetTweenAnimation(index, bNoPlay) + -- if we don't need to check if the animation is playing we can just return the animation + if (bNoPlay) then + return self.tweenAnimations[index] + else + for k, v in pairs(self.tweenAnimations or {}) do + if (k == index and v.bShouldPlay) then + return v + end + end + end + end + + function meta:IsPlayingTweenAnimation(index) + for k, v in pairs(self.tweenAnimations or {}) do + if (v.bShouldPlay and index == k) then + return true + end + end + + return false + end + + function meta:StopAnimations(bRemove) + for k, v in pairs(self.tweenAnimations or {}) do + if (v.bShouldPlay) then + v:ForceComplete() + + if (bRemove) then + self.tweenAnimations[k] = nil + end + end + end + end + + function meta:CreateAnimation(length, data) + local animations = self.tweenAnimations or {} + self.tweenAnimations = animations + + if (self.SetAnimationEnabled) then + self:SetAnimationEnabled(true) + end + + local index = data.index or 1 + local bCancelPrevious = data.bCancelPrevious == nil and false or data.bCancelPrevious + local bIgnoreConfig = SERVER or (data.bIgnoreConfig == nil and false or data.bIgnoreConfig) + + if (bCancelPrevious and self:IsPlayingTweenAnimation()) then + for _, v in pairs(animations) do + v:set(v.duration) + end + end + + local animation = ix.tween.new( + ((length == 0 and 1 or length) or 1) * (bIgnoreConfig and 1 or ix.option.Get("animationScale", 1)), + data.subject or self, + data.target or {}, + data.easing or "linear" + ) + + animation.index = index + animation.bIgnoreConfig = bIgnoreConfig + animation.bAutoFire = (data.bAutoFire == nil and true or data.bAutoFire) + animation.bRemoveOnComplete = (data.bRemoveOnComplete == nil and true or data.bRemoveOnComplete) + animation.Think = data.Think + animation.OnComplete = data.OnComplete + + animation.ForceComplete = function(anim) + anim:set(anim.duration) + end + + -- @todo don't use ridiculous method chaining + animation.CreateAnimation = function(currentAnimation, newLength, newData) + newData.bAutoFire = false + newData.index = currentAnimation.index + 1 + + local oldOnComplete = currentAnimation.OnComplete + local newAnimation = currentAnimation.subject:CreateAnimation(newLength, newData) + + currentAnimation.OnComplete = function(...) + if (oldOnComplete) then + oldOnComplete(...) + end + + newAnimation:Fire() + end + + return newAnimation + end + + if (length == 0 or (!animation.bIgnoreConfig and ix.option.Get("disableAnimations", false))) then + animation.Fire = function(anim) + anim:set(anim.duration) + anim.bShouldPlay = true + end + else + animation.Fire = function(anim) + anim:set(0) + anim.bShouldPlay = true + end + end + + -- we can assume if we're using this library, we're not going to use the built-in + -- AnimationTo functions, so override AnimationThink with our own + self.AnimationThink = TweenAnimationThink + + -- fire right away if autofire is enabled + if (animation.bAutoFire) then + animation:Fire() + end + + self.tweenAnimations[index] = animation + return animation + end +end + +if (CLIENT) then + local panelMeta = FindMetaTable("Panel") + ix.util.InstallAnimationMethods(panelMeta) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_anims.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_anims.lua new file mode 100644 index 0000000..7e9a9d4 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_anims.lua @@ -0,0 +1,504 @@ + +--[[-- +Player model animation. + +Helix comes with support for using NPC animations/models as regular player models by manually translating animations. There are +a few standard animation sets that are built-in that should cover most non-player models: + citizen_male + citizen_female + metrocop + overwatch + vortigaunt + player + zombie + fastZombie + +If you find that your models are T-posing when they work elsewhere, you'll probably need to set the model class for your +model with `ix.anim.SetModelClass` in order for the correct animations to be used. If you'd like to add your own animation +class, simply add to the `ix.anim` table with a model class name and the required animation translation table. +]] +-- @module ix.anim + +ix.anim = ix.anim or {} +ix.anim.citizen_male = { + normal = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + pistol = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_RANGE_ATTACK_PISTOL}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_ATTACK_PISTOL_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_GESTURE_RANGE_ATTACK_PISTOL, + reload = ACT_RELOAD_PISTOL + }, + smg = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_GESTURE_RANGE_ATTACK_SMG1, + reload = ACT_GESTURE_RELOAD_SMG1 + }, + shotgun = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN + }, + grenade = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_RANGE_ATTACK_THROW + }, + melee = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_MELEE_ATTACK_SWING + }, + glide = ACT_GLIDE, + vehicle = { + ["prop_vehicle_prisoner_pod"] = {"podpose", Vector(-3, 0, 0)}, + ["prop_vehicle_jeep"] = {ACT_BUSY_SIT_CHAIR, Vector(14, 0, -14)}, + ["prop_vehicle_airboat"] = {ACT_BUSY_SIT_CHAIR, Vector(8, 0, -20)}, + chair = {ACT_BUSY_SIT_CHAIR, Vector(1, 0, -23)} + }, +} + +ix.anim.citizen_female = { + normal = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + pistol = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_GESTURE_RANGE_ATTACK_PISTOL, + reload = ACT_RELOAD_PISTOL + }, + smg = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_GESTURE_RANGE_ATTACK_SMG1, + reload = ACT_GESTURE_RELOAD_SMG1 + }, + shotgun = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN + }, + grenade = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_RANGE_ATTACK_THROW + }, + melee = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_MELEE_ATTACK_SWING + }, + glide = ACT_GLIDE, + vehicle = ix.anim.citizen_male.vehicle +} +ix.anim.metrocop = { + normal = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + pistol = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW}, + [ACT_MP_WALK] = {ACT_WALK_PISTOL, ACT_WALK_AIM_PISTOL}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN_PISTOL, ACT_RUN_AIM_PISTOL}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_GESTURE_RANGE_ATTACK_PISTOL, + reload = ACT_GESTURE_RELOAD_PISTOL + }, + smg = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + shotgun = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + grenade = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_COMBINE_THROW_GRENADE + }, + melee = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE}, + [ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_MELEE_ATTACK_SWING_GESTURE + }, + glide = ACT_GLIDE, + vehicle = { + chair = {ACT_COVER_PISTOL_LOW, Vector(5, 0, -5)}, + ["prop_vehicle_airboat"] = {ACT_COVER_PISTOL_LOW, Vector(10, 0, 0)}, + ["prop_vehicle_jeep"] = {ACT_COVER_PISTOL_LOW, Vector(18, -2, 4)}, + ["prop_vehicle_prisoner_pod"] = {ACT_IDLE, Vector(-4, -0.5, 0)} + } +} +ix.anim.overwatch = { + normal = { + [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY}, + [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, + [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + pistol = { + [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, + [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + smg = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1}, + [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + shotgun = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SHOTGUN}, + [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, + [ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_SHOTGUN}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_SHOTGUN}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + grenade = { + [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY}, + [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, + [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + melee = { + [ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY}, + [ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE}, + [ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE}, + [ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE}, + [ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE}, + [ACT_LAND] = {ACT_RESET, ACT_RESET}, + attack = ACT_MELEE_ATTACK_SWING_GESTURE + }, + glide = ACT_GLIDE +} +ix.anim.vortigaunt = { + melee = { + ["attack"] = ACT_MELEE_ATTACK1, + [ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"}, + [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM}, + [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}, + }, + grenade = { + ["attack"] = ACT_MELEE_ATTACK1, + [ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"}, + [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN}, + [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK} + }, + normal = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY}, + [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM}, + [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}, + ["attack"] = ACT_MELEE_ATTACK1 + }, + pistol = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"}, + [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, + ["reload"] = ACT_IDLE, + [ACT_MP_RUN] = {ACT_RUN, "run_all_TC"}, + [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, + [ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"} + }, + shotgun = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"}, + [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, + ["reload"] = ACT_IDLE, + [ACT_MP_RUN] = {ACT_RUN, "run_all_TC"}, + [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, + [ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"} + }, + smg = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, "TCidlecombat"}, + [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, + ["reload"] = ACT_IDLE, + [ACT_MP_RUN] = {ACT_RUN, "run_all_TC"}, + [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, + [ACT_MP_WALK] = {ACT_WALK, "Walk_all_TC"} + }, + beam = { + [ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY}, + [ACT_MP_CROUCH_IDLE] = {"crouchidle", "crouchidle"}, + [ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM}, + [ACT_MP_CROUCHWALK] = {ACT_WALK, ACT_WALK}, + [ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}, + ["attack"] = ACT_GESTURE_RANGE_ATTACK1, + ["reload"] = ACT_IDLE, + ["glide"] = {ACT_RUN, ACT_RUN} + }, + glide = "jump_holding_glide" +} +ix.anim.player = { + normal = { + [ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE, + [ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH, + [ACT_MP_WALK] = ACT_HL2MP_WALK, + [ACT_MP_RUN] = ACT_HL2MP_RUN, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + }, + passive = { + [ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_PASSIVE, + [ACT_MP_WALK] = ACT_HL2MP_WALK_PASSIVE, + [ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_PASSIVE, + [ACT_MP_RUN] = ACT_HL2MP_RUN_PASSIVE, + [ACT_LAND] = {ACT_RESET, ACT_RESET} + } +} +ix.anim.zombie = { + [ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_ZOMBIE, + [ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE, + [ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_01, + [ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_02, + [ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE, + [ACT_LAND] = {ACT_RESET, ACT_RESET} +} +ix.anim.fastZombie = { + [ACT_MP_STAND_IDLE] = ACT_HL2MP_WALK_ZOMBIE, + [ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE, + [ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_05, + [ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_06, + [ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE_FAST, + [ACT_LAND] = {ACT_RESET, ACT_RESET} +} + +local translations = {} + +--- Sets a model's animation class. +-- @realm shared +-- @string model Model name to set the animation class for +-- @string class Animation class to assign to the model +-- @usage ix.anim.SetModelClass("models/police.mdl", "metrocop") +function ix.anim.SetModelClass(model, class) + if (!ix.anim[class]) then + error("'" .. tostring(class) .. "' is not a valid animation class!") + end + + translations[model:lower()] = class +end + +--- Gets a model's animation class. +-- @realm shared +-- @string model Model to get the animation class for +-- @treturn[1] string Animation class of the model +-- @treturn[2] nil If there was no animation associated with the given model +-- @usage ix.anim.GetModelClass("models/police.mdl") +-- > metrocop +function ix.anim.GetModelClass(model) + model = string.lower(model) + local class = translations[model] + + if (!class and string.find(model, "/player")) then + return "player" + end + + class = class or "citizen_male" + + if (class == "citizen_male" and ( + string.find(model, "female") or + string.find(model, "alyx") or + string.find(model, "mossman"))) then + class = "citizen_female" + end + + return class +end + +ix.anim.SetModelClass("models/police.mdl", "metrocop") +ix.anim.SetModelClass("models/combine_super_soldier.mdl", "overwatch") +ix.anim.SetModelClass("models/combine_soldier_prisonGuard.mdl", "overwatch") +ix.anim.SetModelClass("models/combine_soldier.mdl", "overwatch") +ix.anim.SetModelClass("models/vortigaunt.mdl", "vortigaunt") +ix.anim.SetModelClass("models/vortigaunt_blue.mdl", "vortigaunt") +ix.anim.SetModelClass("models/vortigaunt_doctor.mdl", "vortigaunt") +ix.anim.SetModelClass("models/vortigaunt_slave.mdl", "vortigaunt") + +if (SERVER) then + util.AddNetworkString("ixSequenceSet") + util.AddNetworkString("ixSequenceReset") + + local playerMeta = FindMetaTable("Player") + + --- Player anim methods + -- @classmod Player + + --- Forces this player's model to play an animation sequence. It also prevents the player from firing their weapon while the + -- animation is playing. + -- @realm server + -- @string sequence Name of the animation sequence to play + -- @func[opt=nil] callback Function to call when the animation finishes. This is also called immediately if the animation + -- fails to play + -- @number[opt=nil] time How long to play the animation for. This defaults to the duration of the animation + -- @bool[opt=false] bNoFreeze Whether or not to avoid freezing this player in place while the animation is playing + -- @see LeaveSequence + function playerMeta:ForceSequence(sequence, callback, time, bNoFreeze) + hook.Run("PlayerEnterSequence", self, sequence, callback, time, bNoFreeze) + + if (!sequence) then + net.Start("ixSequenceReset") + net.WriteEntity(self) + net.Broadcast() + + return + end + + sequence = self:LookupSequence(tostring(sequence)) + + if (sequence and sequence > 0) then + time = time or self:SequenceDuration(sequence) + + self.ixCouldShoot = self:GetNetVar("canShoot", false) + self.ixSeqCallback = callback + self:SetCycle(0) + self:SetPlaybackRate(1) + self:SetNetVar("forcedSequence", sequence) + self:SetNetVar("canShoot", false) + + if (!bNoFreeze) then + self:SetMoveType(MOVETYPE_NONE) + end + + if (time > 0) then + timer.Create("ixSeq"..self:EntIndex(), time, 1, function() + if (IsValid(self)) then + self:LeaveSequence() + end + end) + end + + net.Start("ixSequenceSet") + net.WriteEntity(self) + net.Broadcast() + + return time + elseif (callback) then + callback() + end + + return false + end + + --- Forcefully stops this player's model from playing an animation that was started by `ForceSequence`. + -- @realm server + function playerMeta:LeaveSequence() + hook.Run("PlayerLeaveSequence", self) + + net.Start("ixSequenceReset") + net.WriteEntity(self) + net.Broadcast() + + self:SetNetVar("canShoot", self.ixCouldShoot) + self:SetNetVar("forcedSequence", nil) + self:SetMoveType(MOVETYPE_WALK) + self.ixCouldShoot = nil + + if (self.ixSeqCallback) then + self:ixSeqCallback() + end + end +else + net.Receive("ixSequenceSet", function() + local entity = net.ReadEntity() + + if (IsValid(entity)) then + hook.Run("PlayerEnterSequence", entity) + end + end) + + net.Receive("ixSequenceReset", function() + local entity = net.ReadEntity() + + if (IsValid(entity)) then + hook.Run("PlayerLeaveSequence", entity) + end + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_attribs.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_attribs.lua new file mode 100644 index 0000000..d4d9137 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_attribs.lua @@ -0,0 +1,191 @@ + +-- @module ix.attributes + +if (!ix.char) then + include("sh_character.lua") +end + +ix.attributes = ix.attributes or {} +ix.attributes.list = ix.attributes.list or {} + +function ix.attributes.LoadFromDir(directory) + for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do + local niceName = v:sub(4, -5) + + ATTRIBUTE = ix.attributes.list[niceName] or {} + if (PLUGIN) then + ATTRIBUTE.plugin = PLUGIN.uniqueID + end + + ix.util.Include(directory.."/"..v) + + ATTRIBUTE.name = ATTRIBUTE.name or "Unknown" + ATTRIBUTE.description = ATTRIBUTE.description or "No description availalble." + + ix.attributes.list[niceName] = ATTRIBUTE + ATTRIBUTE = nil + end +end + +function ix.attributes.Setup(client) + local character = client:GetCharacter() + + if (character) then + for k, v in pairs(ix.attributes.list) do + if (v.OnSetup) then + v:OnSetup(client, character:GetAttribute(k, 0)) + end + end + end +end + +do + --- Character attribute methods + -- @classmod Character + local charMeta = ix.meta.character + + if (SERVER) then + util.AddNetworkString("ixAttributeUpdate") + + --- Increments one of this character's attributes by the given amount. + -- @realm server + -- @string key Name of the attribute to update + -- @number value Amount to add to the attribute + function charMeta:UpdateAttrib(key, value) + local attribute = ix.attributes.list[key] + local client = self:GetPlayer() + + if (attribute) then + local attrib = self:GetAttributes() + + attrib[key] = math.min((attrib[key] or 0) + value, attribute.maxValue or ix.config.Get("maxAttributes", 100)) + + if (IsValid(client)) then + net.Start("ixAttributeUpdate") + net.WriteUInt(self:GetID(), 32) + net.WriteString(key) + net.WriteFloat(attrib[key]) + net.Send(client) + + if (attribute.Setup) then + attribute.Setup(attrib[key]) + end + end + + self:SetAttributes(attrib) + end + + hook.Run("CharacterAttributeUpdated", client, self, key, value) + end + + --- Sets the value of an attribute for this character. + -- @realm server + -- @string key Name of the attribute to update + -- @number value New value for the attribute + function charMeta:SetAttrib(key, value) + local attribute = ix.attributes.list[key] + local client = self:GetPlayer() + + if (attribute) then + local attrib = self:GetAttributes() + + attrib[key] = value + + if (IsValid(client)) then + net.Start("ixAttributeUpdate") + net.WriteUInt(self:GetID(), 32) + net.WriteString(key) + net.WriteFloat(attrib[key]) + net.Send(client) + + if (attribute.Setup) then + attribute.Setup(attrib[key]) + end + end + + self:SetAttributes(attrib) + end + + hook.Run("CharacterAttributeUpdated", client, self, key, value) + end + + --- Temporarily increments one of this character's attributes. Useful for things like consumable items. + -- @realm server + -- @string boostID Unique ID to use for the boost to remove it later + -- @string attribID Name of the attribute to boost + -- @number boostAmount Amount to increase the attribute by + function charMeta:AddBoost(boostID, attribID, boostAmount) + local boosts = self:GetVar("boosts", {}) + + boosts[attribID] = boosts[attribID] or {} + boosts[attribID][boostID] = boostAmount + + hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, boostAmount) + + return self:SetVar("boosts", boosts, nil, self:GetPlayer()) + end + + --- Removes a temporary boost from this character. + -- @realm server + -- @string boostID Unique ID of the boost to remove + -- @string attribID Name of the attribute that was boosted + function charMeta:RemoveBoost(boostID, attribID) + local boosts = self:GetVar("boosts", {}) + + boosts[attribID] = boosts[attribID] or {} + boosts[attribID][boostID] = nil + + hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, true) + + return self:SetVar("boosts", boosts, nil, self:GetPlayer()) + end + else + net.Receive("ixAttributeUpdate", function() + local id = net.ReadUInt(32) + local character = ix.char.loaded[id] + + if (character) then + local key = net.ReadString() + local value = net.ReadFloat() + + character:GetAttributes()[key] = value + end + end) + end + + --- Returns all boosts that this character has for the given attribute. This is only valid on the server and owning client. + -- @realm shared + -- @string attribID Name of the attribute to find boosts for + -- @treturn[1] table Table of boosts that this character has for the attribute + -- @treturn[2] nil If the character has no boosts for the given attribute + function charMeta:GetBoost(attribID) + local boosts = self:GetBoosts() + + return boosts[attribID] + end + + --- Returns all boosts that this character has. This is only valid on the server and owning client. + -- @realm shared + -- @treturn table Table of boosts this character has + function charMeta:GetBoosts() + return self:GetVar("boosts", {}) + end + + --- Returns the current value of an attribute. This is only valid on the server and owning client. + -- @realm shared + -- @string key Name of the attribute to get + -- @number default Value to return if the attribute doesn't exist + -- @treturn number Value of the attribute + function charMeta:GetAttribute(key, default) + local att = self:GetAttributes()[key] or default + local boosts = self:GetBoosts()[key] + + if (boosts) then + for _, v in pairs(boosts) do + att = att + v + end + end + + return att + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_business.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_business.lua new file mode 100644 index 0000000..06ce3d8 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_business.lua @@ -0,0 +1,141 @@ + +if (SERVER) then + util.AddNetworkString("ixBusinessBuy") + util.AddNetworkString("ixBusinessResponse") + util.AddNetworkString("ixShipmentUse") + util.AddNetworkString("ixShipmentOpen") + util.AddNetworkString("ixShipmentClose") + + net.Receive("ixBusinessBuy", function(length, client) + if (client.ixNextBusiness and client.ixNextBusiness > CurTime()) then + client:NotifyLocalized("businessTooFast") + return + end + + local char = client:GetCharacter() + + if (!char) then + return + end + + local indicies = net.ReadUInt(8) + local items = {} + + for _ = 1, indicies do + items[net.ReadString()] = net.ReadUInt(8) + end + + if (table.IsEmpty(items)) then + return + end + + local cost = 0 + + for k, v in pairs(items) do + local itemTable = ix.item.list[k] + + if (itemTable and hook.Run("CanPlayerUseBusiness", client, k) != false) then + local amount = math.Clamp(tonumber(v) or 0, 0, 10) + items[k] = amount + + if (amount == 0) then + items[k] = nil + else + cost = cost + (amount * (itemTable.price or 0)) + end + else + items[k] = nil + end + end + + if (table.IsEmpty(items)) then + return + end + + if (char:HasMoney(cost)) then + char:TakeMoney(cost) + + local entity = ents.Create("ix_shipment") + entity:Spawn() + entity:SetPos(client:GetItemDropPos(entity)) + entity:SetItems(items) + entity:SetNetVar("owner", char:GetID()) + + local shipments = char:GetVar("charEnts") or {} + table.insert(shipments, entity) + char:SetVar("charEnts", shipments, true) + + net.Start("ixBusinessResponse") + net.Send(client) + + hook.Run("CreateShipment", client, entity) + + client.ixNextBusiness = CurTime() + 0.5 + end + end) + + net.Receive("ixShipmentUse", function(length, client) + local uniqueID = net.ReadString() + local drop = net.ReadBool() + + local entity = client.ixShipment + local itemTable = ix.item.list[uniqueID] + + if (itemTable and IsValid(entity)) then + if (entity:GetPos():Distance(client:GetPos()) > 128) then + client.ixShipment = nil + + return + end + + local amount = entity.items[uniqueID] + + if (amount and amount > 0) then + if (entity.items[uniqueID] <= 0) then + entity.items[uniqueID] = nil + end + + if (drop) then + ix.item.Spawn(uniqueID, entity:GetPos() + Vector(0, 0, 16), function(item, itemEntity) + if (IsValid(client)) then + itemEntity.ixSteamID = client:SteamID() + itemEntity.ixCharID = client:GetCharacter():GetID() + end + end) + else + local status, _ = client:GetCharacter():GetInventory():Add(uniqueID) + + if (!status) then + return client:NotifyLocalized("noFit") + end + end + + hook.Run("ShipmentItemTaken", client, uniqueID, amount) + + entity.items[uniqueID] = entity.items[uniqueID] - 1 + + if (entity:GetItemCount() < 1) then + entity:GibBreakServer(Vector(0, 0, 0.5)) + entity:Remove() + end + end + end + end) + + net.Receive("ixShipmentClose", function(length, client) + local entity = client.ixShipment + + if (IsValid(entity)) then + entity.ixInteractionDirty = false + client.ixShipment = nil + end + end) +else + net.Receive("ixShipmentOpen", function() + local entity = net.ReadEntity() + local items = net.ReadTable() + + ix.gui.shipment = vgui.Create("ixShipment") + ix.gui.shipment:SetItems(entity, items) + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_character.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_character.lua new file mode 100644 index 0000000..2ba6dd8 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_character.lua @@ -0,0 +1,1235 @@ + +--[[-- +Character creation and management. + +**NOTE:** For the most part you shouldn't use this library unless you know what you're doing. You can very easily corrupt +character data using these functions! +]] +-- @module ix.char + +ix.char = ix.char or {} + +--- Characters that are currently loaded into memory. This is **not** a table of characters that players are currently using. +-- Characters are automatically loaded when a player joins the server. Entries are not cleared once the player disconnects, as +-- some data is needed after the player has disconnected. Clients will also keep their own version of this table, so don't +-- expect it to be the same as the server's. +-- +-- The keys in this table are the IDs of characters, and the values are the `Character` objects that the ID corresponds to. +-- @realm shared +-- @table ix.char.loaded +-- @usage print(ix.char.loaded[1]) +-- > character[1] +ix.char.loaded = ix.char.loaded or {} + +--- Variables that are stored on characters. This table is populated automatically by `ix.char.RegisterVar`. +-- @realm shared +-- @table ix.char.vars +-- @usage print(ix.char.vars["name"]) +-- > table: 0xdeadbeef +ix.char.vars = ix.char.vars or {} + +--- Functions similar to `ix.char.loaded`, but is serverside only. This contains a table of all loaded characters grouped by +-- the SteamID64 of the player that owns them. +-- @realm server +-- @table ix.char.cache +ix.char.cache = ix.char.cache or {} + +ix.util.Include("helix/gamemode/core/meta/sh_character.lua") + +if (SERVER) then + --- Creates a character object with its assigned properties and saves it to the database. + -- @realm server + -- @tab data Properties to assign to this character. If fields are missing from the table, then it will use the default + -- value for that property + -- @func callback Function to call after the character saves + function ix.char.Create(data, callback) + local timeStamp = math.floor(os.time()) + + data.money = data.money or ix.config.Get("defaultMoney", 0) + data.schema = Schema and Schema.folder or "helix" + data.createTime = timeStamp + data.lastJoinTime = timeStamp + + local query = mysql:Insert("ix_characters") + query:Insert("name", data.name or "") + query:Insert("description", data.description or "") + query:Insert("model", data.model or "models/error.mdl") + query:Insert("schema", Schema and Schema.folder or "helix") + query:Insert("create_time", data.createTime) + query:Insert("last_join_time", data.lastJoinTime) + query:Insert("steamid", data.steamID) + query:Insert("faction", data.faction or "Unknown") + query:Insert("money", data.money) + query:Insert("data", util.TableToJSON(data.data or {})) + query:Callback(function(result, status, lastID) + local invQuery = mysql:Insert("ix_inventories") + invQuery:Insert("character_id", lastID) + invQuery:Callback(function(invResult, invStats, invLastID) + local client = player.GetBySteamID64(data.steamID) + + ix.char.RestoreVars(data, data) + + local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight") + local character = ix.char.New(data, lastID, client, data.steamID) + local inventory = ix.inventory.Create(w, h, invLastID) + + character.vars.inv = {inventory} + inventory:SetOwner(lastID) + + ix.char.loaded[lastID] = character + table.insert(ix.char.cache[data.steamID], lastID) + + if (callback) then + callback(lastID) + end + end) + invQuery:Execute() + end) + query:Execute() + end + + --- Loads all of a player's characters into memory. + -- @realm server + -- @player client Player to load the characters for + -- @func[opt=nil] callback Function to call when the characters have been loaded + -- @bool[opt=false] bNoCache Whether or not to skip the cache; players that leave and join again later will already have + -- their characters loaded which will skip the database query and load quicker + -- @number[opt=nil] id The ID of a specific character to load instead of all of the player's characters + function ix.char.Restore(client, callback, bNoCache, id) + local steamID64 = client:SteamID64() + local cache = ix.char.cache[steamID64] + + if (cache and !bNoCache) then + for _, v in ipairs(cache) do + local character = ix.char.loaded[v] + + if (character and !IsValid(character.client)) then + character.player = client + end + end + + if (callback) then + callback(cache) + end + + return + end + + local query = mysql:Select("ix_characters") + query:Select("id") + + ix.char.RestoreVars(query) + + query:Where("schema", Schema.folder) + query:Where("steamid", steamID64) + + if (id) then + query:Where("id", id) + end + + query:Callback(function(result) + local characters = {} + + for _, v in ipairs(result or {}) do + local charID = tonumber(v.id) + + if (charID) then + local data = { + steamID = steamID64 + } + + ix.char.RestoreVars(data, v) + + characters[#characters + 1] = charID + local character = ix.char.New(data, charID, client) + + hook.Run("CharacterRestored", character) + character.vars.inv = { + [1] = -1, + } + + local invQuery = mysql:Select("ix_inventories") + invQuery:Select("inventory_id") + invQuery:Select("inventory_type") + invQuery:Where("character_id", charID) + invQuery:Callback(function(info) + if (istable(info) and #info > 0) then + local inventories = {} + + for _, v2 in pairs(info) do + if (v2.inventory_type and isstring(v2.inventory_type) and v2.inventory_type == "NULL") then + v2.inventory_type = nil + end + + if (hook.Run("ShouldRestoreInventory", charID, v2.inventory_id, v2.inventory_type) != false) then + local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight") + local invType + + if (v2.inventory_type) then + invType = ix.item.inventoryTypes[v2.inventory_type] + + if (invType) then + w, h = invType.w, invType.h + end + end + + inventories[tonumber(v2.inventory_id)] = {w, h, v2.inventory_type} + end + end + + ix.inventory.Restore(inventories, nil, nil, function(inventory) + local inventoryType = inventories[inventory:GetID()][3] + + if (inventoryType) then + inventory.vars.isBag = inventoryType + + if (character.vars.inv[1] == -1) then + character.vars.inv[1] = inventory + else + table.insert(character.vars.inv, inventory) + end + else + character.vars.inv[1] = inventory + end + + inventory:SetOwner(charID) + end, true) + else + local insertQuery = mysql:Insert("ix_inventories") + insertQuery:Insert("character_id", charID) + insertQuery:Callback(function(_, status, lastID) + local w, h = ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight") + local inventory = ix.inventory.Create(w, h, lastID) + inventory:SetOwner(charID) + + character.vars.inv = { + inventory + } + end) + insertQuery:Execute() + end + end) + invQuery:Execute() + + ix.char.loaded[charID] = character + else + ErrorNoHalt("[Helix] Attempt to load character with invalid ID '" .. tostring(id) .. "'!") + end + end + + if (callback) then + callback(characters) + end + + ix.char.cache[steamID64] = characters + end) + query:Execute() + end + + --- Adds character properties to a table. This is done automatically by `ix.char.Restore`, so that should be used instead if + -- you are loading characters. + -- @realm server + -- @internal + -- @tab data Table of fields to apply to the table. If this is an SQL query object, it will instead populate the query with + -- `SELECT` statements for each applicable character var in `ix.char.vars`. + -- @tab characterInfo Table to apply the properties to. This can be left as `nil` if an SQL query object is passed in `data` + function ix.char.RestoreVars(data, characterInfo) + if (data.queryType) then + -- populate query + for _, v in pairs(ix.char.vars) do + if (v.field and v.fieldType and !v.bSaveLoadInitialOnly) then + data:Select(v.field) + + -- if FilterValues is used, any rows that contain a value in the column that isn't in the valid values table + -- will be ignored entirely (i.e the character will not load if it has an invalid value) + if (v.FilterValues) then + data:WhereIn(v.field, v:FilterValues()) + end + end + end + else + -- populate character data + for k, v in pairs(ix.char.vars) do + if (v.field and characterInfo[v.field] and !v.bSaveLoadInitialOnly) then + local value = characterInfo[v.field] + + if (isnumber(v.default)) then + value = tonumber(value) or v.default + elseif (isstring(v.default)) then + value = tostring(value) == "NULL" and v.default or tostring(value or v.default) + elseif (isbool(v.default)) then + if (tostring(value) != "NULL") then + value = tobool(value) + else + value = v.default + end + elseif (istable(v.default)) then + value = istable(value) and value or util.JSONToTable(value) + end + + data[k] = value + end + end + end + end +end + +--- Creates a new empty `Character` object. If you are looking to create a usable character, see `ix.char.Create`. +-- @realm shared +-- @internal +-- @tab data Character vars to assign +-- @number id Unique ID of the character +-- @player client Player that will own the character +-- @string[opt=client:SteamID64()] steamID SteamID64 of the player that will own the character +function ix.char.New(data, id, client, steamID) + if (data.name) then + data.name = data.name:gsub("#", "#​") + end + + if (data.description) then + data.description = data.description:gsub("#", "#​") + end + + local character = setmetatable({vars = {}}, ix.meta.character) + for k, v in pairs(data) do + if (v != nil) then + character.vars[k] = v + end + end + + character.id = id or 0 + character.player = client + + if (SERVER and IsValid(client) or steamID) then + character.steamID = IsValid(client) and client:SteamID64() or steamID + end + return character +end + +ix.char.varHooks = ix.char.varHooks or {} +function ix.char.HookVar(varName, hookName, func) + ix.char.varHooks[varName] = ix.char.varHooks[varName] or {} + + ix.char.varHooks[varName][hookName] = func +end + +do + --- Default character vars + -- @classmod Character + + --- Sets this character's name. This is automatically networked. + -- @realm server + -- @string name New name for the character + -- @function SetName + + --- Returns this character's name + -- @realm shared + -- @treturn string This character's current name + -- @function GetName + ix.char.RegisterVar("name", { + field = "name", + fieldType = ix.type.string, + default = "John Doe", + index = 1, + OnValidate = function(self, value, payload, client) + if (!value) then + return false, "invalid", "name" + end + + value = tostring(value):gsub("\r\n", ""):gsub("\n", "") + value = string.Trim(value) + + local minLength = ix.config.Get("minNameLength", 4) + local maxLength = ix.config.Get("maxNameLength", 32) + + if (value:utf8len() < minLength) then + return false, "nameMinLen", minLength + elseif (!value:find("%S")) then + return false, "invalid", "name" + elseif (value:gsub("%s", ""):utf8len() > maxLength) then + return false, "nameMaxLen", maxLength + end + + return hook.Run("GetDefaultCharacterName", client, payload.faction) or value:utf8sub(1, 70) + end, + OnPostSetup = function(self, panel, payload) + local faction = ix.faction.indices[payload.faction] + local name, disabled = hook.Run("GetDefaultCharacterName", LocalPlayer(), payload.faction) + + if (name) then + panel:SetText(name) + payload:Set("name", name) + end + + if (disabled) then + panel:SetDisabled(true) + panel:SetEditable(false) + end + + panel:SetBackgroundColor(faction.color or Color(255, 255, 255, 25)) + end + }) + + --- Sets this character's physical description. This is automatically networked. + -- @realm server + -- @string description New description for this character + -- @function SetDescription + + --- Returns this character's physical description. + -- @realm shared + -- @treturn string This character's current description + -- @function GetDescription + ix.char.RegisterVar("description", { + field = "description", + fieldType = ix.type.text, + default = "", + index = 2, + OnValidate = function(self, value, payload) + value = string.Trim((tostring(value):gsub("\r\n", ""):gsub("\n", ""))) + local minLength = ix.config.Get("minDescriptionLength", 16) + + if (value:utf8len() < minLength) then + return false, "descMinLen", minLength + elseif (!value:find("%s+") or !value:find("%S")) then + return false, "invalid", "description" + end + + return value + end, + OnPostSetup = function(self, panel, payload) + panel:SetMultiline(true) + panel:SetFont("ixMenuButtonFont") + panel:SetTall(panel:GetTall() * 2 + 6) -- add another line + panel.AllowInput = function(_, character) + if (character == "\n" or character == "\r") then + return true + end + end + end, + alias = "Desc" + }) + + --- Sets this character's model. This sets the player's current model to the given one, and saves it to the character. + -- It is automatically networked. + -- @realm server + -- @string model New model for the character + -- @function SetModel + + --- Returns this character's model. + -- @realm shared + -- @treturn string This character's current model + -- @function GetModel + ix.char.RegisterVar("model", { + field = "model", + fieldType = ix.type.string, + default = "models/error.mdl", + index = 3, + OnSet = function(character, value) + local client = character:GetPlayer() + + if (IsValid(client) and client:GetCharacter() == character) then + client:SetModel(value) + end + + character.vars.model = value + end, + OnGet = function(character, default) + return character.vars.model or default + end, + OnDisplay = function(self, container, payload) + local scroll = container:Add("DScrollPanel") + scroll:Dock(FILL) -- TODO: don't fill so we can allow other panels + scroll.Paint = function(panel, width, height) + derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, Color(255, 255, 255, 25)) + end + + local layout = scroll:Add("DIconLayout") + layout:Dock(FILL) + layout:SetSpaceX(1) + layout:SetSpaceY(1) + + local faction = ix.faction.indices[payload.faction] + + if (faction) then + local models = faction:GetModels(LocalPlayer()) + + for k, v in SortedPairs(models) do + local icon = layout:Add("SpawnIcon") + icon:SetSize(64, 128) + icon:InvalidateLayout(true) + icon.DoClick = function(this) + payload:Set("model", k) + end + icon.PaintOver = function(this, w, h) + if (payload.model == k) then + local color = ix.config.Get("color", color_white) + + surface.SetDrawColor(color.r, color.g, color.b, 200) + + for i = 1, 3 do + local i2 = i * 2 + surface.DrawOutlinedRect(i, i, w - i2, h - i2) + end + end + end + + if (isstring(v)) then + icon:SetModel(v) + else + icon:SetModel(v[1], v[2] or 0, v[3]) + end + end + end + + return scroll + end, + OnValidate = function(self, value, payload, client) + local faction = ix.faction.indices[payload.faction] + if (faction) then + local models = faction:GetModels(client) + if (!payload.model) then + return false, "needModel" + end + else + return false, "needModel" + end + end, + OnAdjust = function(self, client, data, value, newData) + local faction = ix.faction.indices[data.faction] + + if (faction) then + local model = faction:GetModels(client)[value] + + if (isstring(model)) then + newData.model = model + elseif (istable(model)) then + newData.model = model[1] + + -- save skin/bodygroups to character data + local bodygroups = {} + + for i = 1, #model[3] do + bodygroups[i - 1] = tonumber(model[3][i]) or 0 + end + + newData.data = newData.data or {} + newData.data.skin = model[2] or 0 + newData.data.groups = bodygroups + end + end + end, + ShouldDisplay = function(self, container, payload) + local faction = ix.faction.indices[payload.faction] + return #faction:GetModels(LocalPlayer()) > 1 + end + }) + + -- SetClass shouldn't be used here, character:JoinClass should be used instead + + --- Returns this character's current class. + -- @realm shared + -- @treturn number Index of the class this character is in + -- @function GetClass + ix.char.RegisterVar("class", { + bNoDisplay = true, + }) + + --- Sets this character's faction. Note that this doesn't do the initial setup for the player after the faction has been + -- changed, so you'll have to update some character vars manually. + -- @realm server + -- @number faction Index of the faction to transfer this character to + -- @function SetFaction + + --- Returns this character's faction. + -- @realm shared + -- @treturn number Index of the faction this character is currently in + -- @function GetFaction + ix.char.RegisterVar("faction", { + field = "faction", + fieldType = ix.type.string, + default = "Citizen", + bNoDisplay = true, + FilterValues = function(self) + -- make sequential table of faction unique IDs + local values = {} + + for k, v in ipairs(ix.faction.indices) do + values[k] = v.uniqueID + end + + return values + end, + OnSet = function(self, value) + local client = self:GetPlayer() + + if (IsValid(client)) then + self.vars.faction = ix.faction.indices[value] and ix.faction.indices[value].uniqueID + + client:SetTeam(value) + + -- @todo refactor networking of character vars so this doesn't need to be repeated on every OnSet override + net.Start("ixCharacterVarChanged") + net.WriteUInt(self:GetID(), 32) + net.WriteString("faction") + net.WriteType(self.vars.faction) + net.Broadcast() + end + end, + OnGet = function(self, default) + local faction = ix.faction.teams[self.vars.faction] + + return faction and faction.index or 0 + end, + OnValidate = function(self, index, data, client) + if (index and client:HasWhitelist(index)) then + return true + end + + return false + end, + OnAdjust = function(self, client, data, value, newData) + newData.faction = ix.faction.indices[value].uniqueID + end + }) + + -- attribute manipulation should be done with methods from the ix.attributes library + ix.char.RegisterVar("attributes", { + field = "attributes", + fieldType = ix.type.text, + default = {}, + index = 4, + category = "attributes", + isLocal = true, + OnDisplay = function(self, container, payload) + local maximum = hook.Run("GetDefaultAttributePoints", LocalPlayer(), payload) or 10 + + if (maximum < 1) then + return + end + + local attributes = container:Add("DPanel") + attributes:Dock(TOP) + + local y + local total = 0 + + payload.attributes = {} + + -- total spendable attribute points + local totalBar = attributes:Add("ixAttributeBar") + totalBar:SetMax(maximum) + totalBar:SetValue(maximum) + totalBar:Dock(TOP) + totalBar:DockMargin(2, 2, 2, 2) + totalBar:SetText(L("attribPointsLeft")) + totalBar:SetReadOnly(true) + totalBar:SetColor(Color(20, 120, 20, 255)) + + y = totalBar:GetTall() + 4 + + for k, v in SortedPairsByMemberValue(ix.attributes.list, "name") do + payload.attributes[k] = 0 + + local bar = attributes:Add("ixAttributeBar") + bar:SetMax(v.maxValue or maximum) + bar:Dock(TOP) + bar:DockMargin(2, 2, 2, 2) + bar:SetText(L(v.name)) + bar.OnChanged = function(this, difference) + if ((total + difference) > maximum) then + return false + end + + total = total + difference + payload.attributes[k] = payload.attributes[k] + difference + + totalBar:SetValue(totalBar.value - difference) + end + + if (v.noStartBonus) then + bar:SetReadOnly() + end + + y = y + bar:GetTall() + 4 + end + + attributes:SetTall(y) + return attributes + end, + OnValidate = function(self, value, data, client) + if (value != nil) then + if (istable(value)) then + local count = 0 + + for _, v in pairs(value) do + count = count + v + end + + if (count > (hook.Run("GetDefaultAttributePoints", client, count) or 10)) then + return false, "unknownError" + end + else + return false, "unknownError" + end + end + end, + ShouldDisplay = function(self, container, payload) + return !table.IsEmpty(ix.attributes.list) + end + }) + + --- Sets this character's current money. Money is only networked to the player that owns this character. + -- @realm server + -- @number money New amount of money this character should have + -- @function SetMoney + + --- Returns this character's money. This is only valid on the server and the owning client. + -- @realm shared + -- @treturn number Current money of this character + -- @function GetMoney + ix.char.RegisterVar("money", { + field = "money", + fieldType = ix.type.number, + default = 0, + isLocal = true, + bNoDisplay = true + }) + + --- Sets a data field on this character. This is useful for storing small bits of data that you need persisted on this + -- character. This is networked only to the owning client. If you are going to be accessing this data field frequently with + -- a getter/setter, consider using `ix.char.RegisterVar` instead. + -- @realm server + -- @string key Name of the field that holds the data + -- @param value Any value to store in the field, as long as it's supported by GMod's JSON parser + -- @function SetData + + --- Returns a data field set on this character. If it doesn't exist, it will return the given default or `nil`. This is only + -- valid on the server and the owning client. + -- @realm shared + -- @string key Name of the field that's holding the data + -- @param default Value to return if the given key doesn't exist, or is `nil` + -- @return[1] Data stored in the field + -- @treturn[2] nil If the data doesn't exist, or is `nil` + -- @function GetData + ix.char.RegisterVar("data", { + default = {}, + isLocal = true, + bNoDisplay = true, + field = "data", + fieldType = ix.type.text, + OnSet = function(character, key, value, noReplication, receiver) + local data = character:GetData() + local client = character:GetPlayer() + + data[key] = value + + if (!noReplication and IsValid(client)) then + net.Start("ixCharacterData") + net.WriteUInt(character:GetID(), 32) + net.WriteString(key) + net.WriteType(value) + net.Send(receiver or client) + end + + character.vars.data = data + end, + OnGet = function(character, key, default) + local data = character.vars.data or {} + + if (key) then + if (!data) then + return default + end + + local value = data[key] + + return value == nil and default or value + else + return default or data + end + end + }) + + ix.char.RegisterVar("var", { + default = {}, + bNoDisplay = true, + OnSet = function(character, key, value, noReplication, receiver) + local data = character:GetVar() + local client = character:GetPlayer() + + data[key] = value + + if (!noReplication and IsValid(client)) then + local id + + if (client:GetCharacter() and client:GetCharacter():GetID() == character:GetID()) then + id = client:GetCharacter():GetID() + else + id = character:GetID() + end + + net.Start("ixCharacterVar") + net.WriteUInt(id, 32) + net.WriteString(key) + net.WriteType(value) + net.Send(receiver or client) + end + + character.vars.vars = data + end, + OnGet = function(character, key, default) + character.vars.vars = character.vars.vars or {} + local data = character.vars.vars or {} + + if (key) then + if (!data) then + return default + end + + local value = data[key] + + return value == nil and default or value + else + return default or data + end + end + }) + + --- Returns the Unix timestamp of when this character was created (i.e the value of `os.time()` at the time of creation). + -- @realm server + -- @treturn number Unix timestamp of when this character was created + -- @function GetCreateTime + ix.char.RegisterVar("createTime", { + field = "create_time", + fieldType = ix.type.number, + bNoDisplay = true, + bNoNetworking = true, + bNotModifiable = true + }) + + --- Returns the Unix timestamp of when this character was last used by its owning player. + -- @realm server + -- @treturn number Unix timestamp of when this character was last used + -- @function GetLastJoinTime + ix.char.RegisterVar("lastJoinTime", { + field = "last_join_time", + fieldType = ix.type.number, + bNoDisplay = true, + bNoNetworking = true, + bNotModifiable = true, + bSaveLoadInitialOnly = true + }) + + --- Returns the schema that this character belongs to. This is useful if you are running multiple schemas off of the same + -- database, and need to differentiate between them. + -- @realm server + -- @treturn string Schema this character belongs to + -- @function GetSchema + ix.char.RegisterVar("schema", { + field = "schema", + fieldType = ix.type.string, + bNoDisplay = true, + bNoNetworking = true, + bNotModifiable = true, + bSaveLoadInitialOnly = true + }) + + --- Returns the 64-bit Steam ID of the player that owns this character. + -- @realm server + -- @treturn string Owning player's Steam ID + -- @function GetSteamID + ix.char.RegisterVar("steamID", { + field = "steamid", + fieldType = ix.type.steamid, + bNoDisplay = true, + bNoNetworking = true, + bNotModifiable = true, + bSaveLoadInitialOnly = true + }) +end + +-- Networking information here. +do + if (SERVER) then + util.AddNetworkString("ixCharacterMenu") + util.AddNetworkString("ixCharacterChoose") + util.AddNetworkString("ixCharacterCreate") + util.AddNetworkString("ixCharacterDelete") + util.AddNetworkString("ixCharacterLoaded") + util.AddNetworkString("ixCharacterLoadFailure") + + util.AddNetworkString("ixCharacterAuthed") + util.AddNetworkString("ixCharacterAuthFailed") + + util.AddNetworkString("ixCharacterInfo") + util.AddNetworkString("ixCharacterData") + util.AddNetworkString("ixCharacterKick") + util.AddNetworkString("ixCharacterSet") + util.AddNetworkString("ixCharacterVar") + util.AddNetworkString("ixCharacterVarChanged") + + net.Receive("ixCharacterChoose", function(length, client) + local id = net.ReadUInt(32) + + if (client:GetCharacter() and client:GetCharacter():GetID() == id) then + net.Start("ixCharacterLoadFailure") + net.WriteString("@usingChar") + net.Send(client) + return + end + + local character = ix.char.loaded[id] + + if (character and character:GetPlayer() == client) then + local status, result = hook.Run("CanPlayerUseCharacter", client, character) + + if (status == false) then + net.Start("ixCharacterLoadFailure") + net.WriteString(result or "") + net.Send(client) + return + end + + local currentChar = client:GetCharacter() + + if (currentChar) then + currentChar:Save() + + for _, v in ipairs(currentChar:GetInventory(true)) do + if (istable(v)) then + v:RemoveReceiver(client) + end + end + end + + hook.Run("PrePlayerLoadedCharacter", client, character, currentChar) + character:Setup() + client:Spawn() + + hook.Run("PlayerLoadedCharacter", client, character, currentChar) + else + net.Start("ixCharacterLoadFailure") + net.WriteString("@unknownError") + net.Send(client) + + ErrorNoHalt("[Helix] Attempt to load invalid character '" .. id .. "'\n") + end + end) + + net.Receive("ixCharacterCreate", function(length, client) + if ((client.ixNextCharacterCreate or 0) > RealTime()) then + return + end + + local maxChars = hook.Run("GetMaxPlayerCharacter", client) or ix.config.Get("maxCharacters", 5) + local charList = client.ixCharList + local charCount = table.Count(charList) + + if (charCount >= maxChars) then + net.Start("ixCharacterAuthFailed") + net.WriteString("maxCharacters") + net.WriteTable({}) + net.Send(client) + + return + end + + client.ixNextCharacterCreate = RealTime() + 1 + + local indicies = net.ReadUInt(8) + local payload = {} + + for _ = 1, indicies do + payload[net.ReadString()] = net.ReadType() + end + + local newPayload = {} + local results = {hook.Run("CanPlayerCreateCharacter", client, payload)} + + if (table.remove(results, 1) == false) then + net.Start("ixCharacterAuthFailed") + net.WriteString(table.remove(results, 1) or "unknownError") + net.WriteTable(results) + net.Send(client) + + return + end + + for k, _ in pairs(payload) do + local info = ix.char.vars[k] + + if (!info or (!info.OnValidate and info.bNoDisplay)) then + payload[k] = nil + end + end + + for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do + local value = payload[k] + + if (v.OnValidate) then + local result = {v:OnValidate(value, payload, client)} + + if (result[1] == false) then + local fault = result[2] + + table.remove(result, 2) + table.remove(result, 1) + + net.Start("ixCharacterAuthFailed") + net.WriteString(fault) + net.WriteTable(result) + net.Send(client) + + return + else + if (result[1] != nil) then + payload[k] = result[1] + end + + if (v.OnAdjust) then + v:OnAdjust(client, payload, value, newPayload) + end + end + end + end + + payload.steamID = client:SteamID64() + hook.Run("AdjustCreationPayload", client, payload, newPayload) + payload = table.Merge(payload, newPayload) + + ix.char.Create(payload, function(id) + if (IsValid(client)) then + ix.char.loaded[id]:Sync(client) + + net.Start("ixCharacterAuthed") + net.WriteUInt(id, 32) + net.WriteUInt(#client.ixCharList, 6) + + for _, v in ipairs(client.ixCharList) do + net.WriteUInt(v, 32) + end + + net.Send(client) + + MsgN("Created character '" .. id .. "' for " .. client:SteamName() .. ".") + hook.Run("OnCharacterCreated", client, ix.char.loaded[id]) + end + end) + end) + + net.Receive("ixCharacterDelete", function(length, client) + local id = net.ReadUInt(32) + local character = ix.char.loaded[id] + local steamID = client:SteamID64() + local isCurrentChar = client:GetCharacter() and client:GetCharacter():GetID() == id + + if (character and character.steamID == steamID) then + for k, v in ipairs(client.ixCharList or {}) do + if (v == id) then + table.remove(client.ixCharList, k) + end + end + + hook.Run("PreCharacterDeleted", client, character) + ix.char.loaded[id] = nil + + net.Start("ixCharacterDelete") + net.WriteUInt(id, 32) + net.Broadcast() + + -- remove character from database + local query = mysql:Delete("ix_characters") + query:Where("id", id) + query:Where("steamid", client:SteamID64()) + query:Execute() + + -- DBTODO: setup relations instead + -- remove inventory from database + query = mysql:Select("ix_inventories") + query:Select("inventory_id") + query:Where("character_id", id) + query:Callback(function(result) + if (istable(result)) then + -- remove associated items from database + for _, v in ipairs(result) do + local itemQuery = mysql:Delete("ix_items") + itemQuery:Where("inventory_id", v.inventory_id) + itemQuery:Execute() + + ix.item.inventories[tonumber(v.inventory_id)] = nil + end + end + + local invQuery = mysql:Delete("ix_inventories") + invQuery:Where("character_id", id) + invQuery:Execute() + end) + query:Execute() + + -- other plugins might need to deal with deleted characters. + hook.Run("CharacterDeleted", client, id, isCurrentChar) + + if (isCurrentChar) then + client:SetNetVar("char", nil) + client:KillSilent() + client:StripAmmo() + end + end + end) + else + net.Receive("ixCharacterInfo", function() + local data = net.ReadTable() + local id = net.ReadUInt(32) + local client = net.ReadUInt(8) + + ix.char.loaded[id] = ix.char.New(data, id, client) + end) + + net.Receive("ixCharacterVarChanged", function() + local id = net.ReadUInt(32) + local character = ix.char.loaded[id] + + if (character) then + local key = net.ReadString() + local value = net.ReadType() + + character.vars[key] = value + end + end) + + -- Used for setting random access vars on the "var" character var (really stupid). + -- Clean this up someday. + net.Receive("ixCharacterVar", function() + local id = net.ReadUInt(32) + local character = ix.char.loaded[id] + + if (character) then + local key = net.ReadString() + local value = net.ReadType() + local oldVar = character:GetVar()[key] + character:GetVar()[key] = value + + hook.Run("CharacterVarChanged", character, key, oldVar, value) + end + end) + + net.Receive("ixCharacterMenu", function() + local indices = net.ReadUInt(6) + local charList = {} + + for _ = 1, indices do + charList[#charList + 1] = net.ReadUInt(32) + end + + if (charList) then + ix.characters = charList + end + + vgui.Create("ixCharMenu") + end) + + net.Receive("ixCharacterLoadFailure", function() + local message = net.ReadString() + + if (isstring(message) and message:sub(1, 1) == "@") then + message = L(message:sub(2)) + end + + message = message != "" and message or L("unknownError") + + if (IsValid(ix.gui.characterMenu)) then + ix.gui.characterMenu:OnCharacterLoadFailed(message) + else + ix.util.Notify(message) + end + end) + + net.Receive("ixCharacterData", function() + local id = net.ReadUInt(32) + local key = net.ReadString() + local value = net.ReadType() + local character = ix.char.loaded[id] + + if (character) then + character.vars.data = character.vars.data or {} + character:GetData()[key] = value + end + end) + + net.Receive("ixCharacterDelete", function() + local id = net.ReadUInt(32) + local isCurrentChar = LocalPlayer():GetCharacter() and LocalPlayer():GetCharacter():GetID() == id + local character = ix.char.loaded[id] + + ix.char.loaded[id] = nil + + for k, v in ipairs(ix.characters) do + if (v == id) then + table.remove(ix.characters, k) + + if (IsValid(ix.gui.characterMenu)) then + ix.gui.characterMenu:OnCharacterDeleted(character) + end + end + end + + if (isCurrentChar and !IsValid(ix.gui.characterMenu)) then + vgui.Create("ixCharMenu") + end + end) + + net.Receive("ixCharacterKick", function() + local isCurrentChar = net.ReadBool() + + if (ix.gui.menu and ix.gui.menu:IsVisible()) then + ix.gui.menu:Remove() + end + + if (!IsValid(ix.gui.characterMenu)) then + vgui.Create("ixCharMenu") + elseif (ix.gui.characterMenu:IsClosing()) then + ix.gui.characterMenu:Remove() + vgui.Create("ixCharMenu") + end + + if (isCurrentChar) then + ix.gui.characterMenu.mainPanel:UpdateReturnButton(false) + end + end) + + net.Receive("ixCharacterLoaded", function() + hook.Run("CharacterLoaded", ix.char.loaded[net.ReadUInt(32)]) + end) + end +end + +do + --- Character util functions for player + -- @classmod Player + + local playerMeta = FindMetaTable("Player") + playerMeta.SteamName = playerMeta.SteamName or playerMeta.Name + + --- Returns this player's currently possessed `Character` object if it exists. + -- @realm shared + -- @treturn[1] Character Currently loaded character + -- @treturn[2] nil If this player has no character loaded + function playerMeta:GetCharacter() + return ix.char.loaded[self:GetNetVar("char")] + end + + playerMeta.GetChar = playerMeta.GetCharacter + + --- Returns this player's current name. + -- @realm shared + -- @treturn[1] string Name of this player's currently loaded character + -- @treturn[2] string Steam name of this player if the player has no character loaded + function playerMeta:GetName() + local character = self:GetCharacter() + + return character and character:GetName() or self:SteamName() + end + + playerMeta.Nick = playerMeta.GetName + playerMeta.Name = playerMeta.GetName +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_chatbox.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_chatbox.lua new file mode 100644 index 0000000..612aacf --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_chatbox.lua @@ -0,0 +1,628 @@ + +--[[-- +Chat manipulation and helper functions. + +Chat messages are a core part of the framework - it's takes up a good chunk of the gameplay, and is also used to interact with +the framework. Chat messages can have types or "classes" that describe how the message should be interpreted. All chat messages +will have some type of class: `ic` for regular in-character speech, `me` for actions, `ooc` for out-of-character, etc. These +chat classes can affect how the message is displayed in each player's chatbox. See `ix.chat.Register` and `ChatClassStructure` +to create your own chat classes. +]] +-- @module ix.chat + +ix.chat = ix.chat or {} + +--- List of all chat classes that have been registered by the framework, where each key is the name of the chat class, and value +-- is the chat class data. Accessing a chat class's data is useful for when you want to copy some functionality or properties +-- to use in your own. Note that if you're accessing this table, you should do so inside of the `InitializedChatClasses` hook. +-- @realm shared +-- @table ix.chat.classes +-- @usage print(ix.chat.classes.ic.format) +-- > "%s says \"%s\"" +ix.chat.classes = ix.chat.classes or {} + +if (!ix.command) then + include("sh_command.lua") +end + +CAMI.RegisterPrivilege({ +Name = "Helix - Bypass OOC Timer", + MinAccess = "admin" +}) + +-- note we can't use commas in the "color" field's default value since the metadata is separated by commas which will break the +-- formatting for that field + +--- Chat messages can have different classes or "types" of messages that have different properties. This can include how the +-- text is formatted, color, hearing distance, etc. +-- @realm shared +-- @table ChatClassStructure +-- @see ix.chat.Register +-- @field[type=string] prefix What the player must type before their message in order to use this chat class. For example, +-- having a prefix of `/Y` will require to type `/Y I am yelling` in order to send a message with this chat class. This can also +-- be a table of strings if you want to allow multiple prefixes, such as `{"//", "/OOC"}`. +-- +-- **NOTE:** the prefix should usually start with a `/` to be consistent with the rest of the framework. However, you are able +-- to use something different like the `LOOC` chat class where the prefixes are `.//`, `[[`, and `/LOOC`. +-- @field[type=bool,opt=false] noSpaceAfter Whether or not the `prefix` can be used without a space after it. For example, the +-- `OOC` chat class allows you to type `//my message` instead of `// my message`. **NOTE:** this only works if the last +-- character in the prefix is non-alphanumeric (i.e `noSpaceAfter` with `/Y` will not work, but `/!` will). +-- @field[type=string,opt] description Description to show to the user in the chatbox when they're using this chat class +-- @field[type=string,opt="%s: \"%s\""] format How to format a message with this chat class. The first `%s` will be the speaking +-- player's name, and the second one will be their message +-- @field[type=color,opt=Color(242 230 160)] color Color to use when displaying a message with this chat class +-- @field[type=string,opt="chatTyping"] indicator Language phrase to use when displaying the typing indicator above the +-- speaking player's head +-- @field[type=bool,opt=false] bNoIndicator Whether or not to avoid showing the typing indicator above the speaking player's +-- head +-- @field[type=string,opt=ixChatFont] font Font to use for displaying a message with this chat class +-- @field[type=bool,opt=false] deadCanChat Whether or not a dead player can send a message with this chat class +-- @field[type=number] CanHear This can be either a `number` representing how far away another player can hear this message. +-- IC messages will use the `chatRange` config, for example. This can also be a function, which returns `true` if the given +-- listener can hear the message emitted from a speaker. +-- -- message can be heard by any player 1000 units away from the speaking player +-- CanHear = 1000 +-- OR +-- CanHear = function(self, speaker, listener) +-- -- the speaking player will be heard by everyone +-- return true +-- end +-- @field[type=function,opt] CanSay Function to run to check whether or not a player can send a message with this chat class. +-- By default, it will return `false` if the player is dead and `deadCanChat` is `false`. Overriding this function will prevent +-- `deadCanChat` from working, and you must implement this functionality manually. +-- CanSay = function(self, speaker, text) +-- -- the speaker will never be able to send a message with this chat class +-- return false +-- end +-- @field[type=function,opt] GetColor Function to run to set the color of a message with this chat class. You should generally +-- stick to using `color`, but this is useful for when you want the color of the message to change with some criteria. +-- GetColor = function(self, speaker, text) +-- -- each message with this chat class will be colored a random shade of red +-- return Color(math.random(120, 200), 0, 0) +-- end +-- @field[type=function,opt] OnChatAdd Function to run when a message with this chat class should be added to the chatbox. If +-- using this function, make sure you end the function by calling `chat.AddText` in order for the text to show up. +-- +-- **NOTE:** using your own `OnChatAdd` function will prevent `color`, `GetColor`, or `format` from being used since you'll be +-- overriding the base function that uses those properties. In such cases you'll need to add that functionality back in +-- manually. In general, you should avoid overriding this function where possible. The `data` argument in the function is +-- whatever is passed into the same `data` argument in `ix.chat.Send`. +-- +-- OnChatAdd = function(self, speaker, text, bAnonymous, data) +-- -- adds white text in the form of "Player Name: Message contents" +-- chat.AddText(color_white, speaker:GetName(), ": ", text) +-- end + +--- Registers a new chat type with the information provided. Chat classes should usually be created inside of the +-- `InitializedChatClasses` hook. +-- @realm shared +-- @string chatType Name of the chat type +-- @tparam ChatClassStructure data Properties and functions to assign to this chat class +-- @usage -- this is the "me" chat class taken straight from the framework as an example +-- ix.chat.Register("me", { +-- format = "** %s %s", +-- color = Color(255, 50, 50), +-- CanHear = ix.config.Get("chatRange", 280) * 2, +-- prefix = {"/Me", "/Action"}, +-- description = "@cmdMe", +-- indicator = "chatPerforming", +-- deadCanChat = true +-- }) +-- @see ChatClassStructure +function ix.chat.Register(chatType, data) + chatType = string.lower(chatType) + + if (!data.CanHear) then + -- Have a substitute if the canHear property is not found. + function data:CanHear(speaker, listener) + -- The speaker will be heard by everyone. + return true + end + elseif (isnumber(data.CanHear)) then + -- Use the value as a range and create a function to compare distances. + local range = data.CanHear * data.CanHear + data.range = range + + function data:CanHear(speaker, listener) + -- Length2DSqr is faster than Length2D, so just check the squares. + return (speaker:GetPos() - listener:GetPos()):LengthSqr() <= self.range + end + end + + -- Allow players to use this chat type by default. + if (!data.CanSay) then + function data:CanSay(speaker, text) + if (!self.deadCanChat and !speaker:Alive()) then + speaker:NotifyLocalized("noPerm") + + return false + end + + return true + end + end + + -- Chat text color. + data.color = data.color or Color(242, 230, 160) + + if (!data.OnChatAdd) then + data.format = data.format or "%s: \"%s\"" + + function data:OnChatAdd(speaker, text, anonymous, info) + local color = self.color + local name = anonymous and + L"someone" or hook.Run("GetCharacterName", speaker, chatType) or + (IsValid(speaker) and speaker:Name() or "Console") + + if (self.GetColor) then + color = self:GetColor(speaker, text, info) + end + + local translated = L2(chatType.."Format", name, text) + + chat.AddText(color, translated or string.format(self.format, name, text)) + end + end + + if (CLIENT and data.prefix) then + if (istable(data.prefix)) then + for _, v in ipairs(data.prefix) do + if (v:utf8sub(1, 1) == "/") then + ix.command.Add(v:utf8sub(2), { + description = data.description, + arguments = ix.type.text, + indicator = data.indicator, + bNoIndicator = data.bNoIndicator, + chatClass = data, + OnCheckAccess = function() return true end, + OnRun = function(self, client, message) end + }) + end + end + else + ix.command.Add(isstring(data.prefix) and data.prefix:utf8sub(2) or chatType, { + description = data.description, + arguments = ix.type.text, + indicator = data.indicator, + bNoIndicator = data.bNoIndicator, + chatClass = data, + OnCheckAccess = function() return true end, + OnRun = function(self, client, message) end + }) + end + end + + data.uniqueID = chatType + ix.chat.classes[chatType] = data +end + +--- Identifies which chat mode should be used. +-- @realm shared +-- @player client Player who is speaking +-- @string message Message to parse +-- @bool[opt=false] bNoSend Whether or not to send the chat message after parsing +-- @treturn string Name of the chat type +-- @treturn string Message that was parsed +-- @treturn bool Whether or not the speaker should be anonymous +function ix.chat.Parse(client, message, bNoSend) + local anonymous = false + local chatType = "ic" + + -- Loop through all chat classes and see if the message contains their prefix. + for k, v in pairs(ix.chat.classes) do + local isChosen = false + local chosenPrefix = "" + local noSpaceAfter = v.noSpaceAfter + + -- Check through all prefixes if the chat type has more than one. + if (istable(v.prefix)) then + for _, prefix in ipairs(v.prefix) do + prefix = prefix:utf8lower() + local fullPrefix = prefix .. (noSpaceAfter and "" or " ") + + -- Checking if the start of the message has the prefix. + if (message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()) then + isChosen = true + chosenPrefix = fullPrefix + + break + end + end + -- Otherwise the prefix itself is checked. + elseif (isstring(v.prefix)) then + local prefix = v.prefix:utf8lower() + local fullPrefix = prefix .. (noSpaceAfter and "" or " ") + + isChosen = message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower() + chosenPrefix = fullPrefix + end + + -- If the checks say we have the proper chat type, then the chat type is the chosen one! + -- If this is not chosen, the loop continues. If the loop doesn't find the correct chat + -- type, then it falls back to IC chat as seen by the chatType variable above. + if (isChosen) then + -- Set the chat type to the chosen one. + chatType = k + -- Remove the prefix from the chat type so it does not show in the message. + message = message:utf8sub(chosenPrefix:utf8len() + 1) + + if (ix.chat.classes[k].noSpaceAfter and message:utf8sub(1, 1):match("%s")) then + message = message:utf8sub(2) + end + + break + end + end + + if (!message:find("%S")) then + return + end + + -- Only send if needed. + if (SERVER and !bNoSend) then + -- Send the correct chat type out so other player see the message. + ix.chat.Send(client, chatType, hook.Run("PlayerMessageSend", client, chatType, message, anonymous) or message, anonymous) + end + + -- Return the chosen chat type and the message that was sent if needed for some reason. + -- This would be useful if you want to send the message on your own. + return chatType, message, anonymous +end + +--- Formats a string to fix basic grammar - removing extra spacing at the beginning and end, capitalizing the first character, +-- and making sure it ends in punctuation. +-- @realm shared +-- @string text String to format +-- @treturn string Formatted string +-- @usage print(ix.chat.Format("hello")) +-- > Hello. +-- @usage print(ix.chat.Format("wow!")) +-- > Wow! +function ix.chat.Format(text) + text = string.Trim(text) + local last = text:utf8sub(-1) + + if (last != "." and last != "?" and last != "!" and last != "-" and last != "\"") then + text = text .. "." + end + + return text:utf8sub(1, 1):utf8upper() .. text:utf8sub(2) +end + +if (SERVER) then + util.AddNetworkString("ixChatMessage") + + --- Send a chat message using the specified chat type. + -- @realm server + -- @player speaker Player who is speaking + -- @string chatType Name of the chat type + -- @string text Message to send + -- @bool[opt=false] bAnonymous Whether or not the speaker should be anonymous + -- @tab[opt=nil] receivers The players to replicate send the message to + -- @tab[opt=nil] data Additional data for this chat message + function ix.chat.Send(speaker, chatType, text, bAnonymous, receivers, data) + if (!chatType) then + return + end + + data = data or {} + chatType = string.lower(chatType) + + if (IsValid(speaker) and hook.Run("PrePlayerMessageSend", speaker, chatType, text, bAnonymous) == false) then + return + end + + local class = ix.chat.classes[chatType] + + if (class and class:CanSay(speaker, text, data) != false) then + if (class.CanHear and !receivers) then + receivers = {} + + for _, v in player.Iterator() do + if (v:GetCharacter() and class:CanHear(speaker, v, data) != false) then + receivers[#receivers + 1] = v + end + end + + if (#receivers == 0) then + return + end + end + + -- Format the message if needed before we run the hook. + local rawText = text + local maxLength = ix.config.Get("chatMax") + + -- Trim the text and remove extra spaces. + text = string.gsub(text, "%s+", " ") + + if (text:utf8len() > maxLength) then + text = text:utf8sub(0, maxLength) + end + + if (ix.config.Get("chatAutoFormat") and hook.Run("CanAutoFormatMessage", speaker, chatType, text)) then + text = ix.chat.Format(text) + end + + text = hook.Run("PlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, rawText) or text + + net.Start("ixChatMessage") + net.WriteEntity(speaker) + net.WriteString(chatType) + net.WriteString(text) + net.WriteBool(bAnonymous or false) + net.WriteTable(data or {}) + net.Send(receivers) + + return text + end + end +else + function ix.chat.Send(speaker, chatType, text, anonymous, data) + local class = ix.chat.classes[chatType] + + if (class) then + -- Trim the text and remove extra spaces. + text = string.gsub(text, "%s+", " ") + + -- luacheck: globals CHAT_CLASS + CHAT_CLASS = class + class:OnChatAdd(speaker, text, anonymous, data) + CHAT_CLASS = nil + end + end + + -- Call OnChatAdd for the appropriate chatType. + net.Receive("ixChatMessage", function() + local client = net.ReadEntity() + local chatType = net.ReadString() + local text = net.ReadString() + local anonymous = net.ReadBool() + local data = net.ReadTable() + + if (IsValid(client)) then + local info = { + chatType = chatType, + text = text, + anonymous = anonymous, + data = data + } + + hook.Run("MessageReceived", client, info) + ix.chat.Send(client, info.chatType or chatType, info.text or text, info.anonymous or anonymous, info.data) + else + ix.chat.Send(nil, chatType, text, anonymous, data) + end + end) +end + +-- Add the default chat types here. +do + -- Load the chat types after the configs so we can access changed configs. + hook.Add("InitializedConfig", "ixChatTypes", function() + -- The default in-character chat. + ix.chat.Register("ic", { + format = "%s says \"%s\"", + indicator = "chatTalking", + GetColor = function(self, speaker, text) + -- If you are looking at the speaker, make it greener to easier identify who is talking. + if (LocalPlayer():GetEyeTrace().Entity == speaker) then + return ix.config.Get("chatListenColor") + end + + -- Otherwise, use the normal chat color. + return ix.config.Get("chatColor") + end, + CanHear = ix.config.Get("chatRange", 280) + }) + + -- Actions and such. + ix.chat.Register("me", { + format = "%s %s", + GetColor = ix.chat.classes.ic.GetColor, + CanHear = ix.config.Get("chatRange", 280) * 2, + prefix = {"/Me", "/Action"}, + description = "@cmdMe", + indicator = "chatPerforming", + deadCanChat = true + }) + + ix.chat.classes.w = nil + ix.command.list["W"] = nil + ix.command.list["Whisper"] = nil + + ix.chat.classes.y = nil + ix.command.list["Y"] = nil + ix.command.list["Yell"] = nil + + ix.command.list["y"] = nil + ix.command.list["Y"] = nil + + ix.chat.Register("y", { + format = "%s шепчет \"%s\"", + GetColor = function(self, speaker, text) + local color = ix.chat.classes.ic:GetColor(speaker, text) + return Color(color.r - 35, color.g - 35, color.b - 35) + end, + CanHear = ix.config.Get("chatRange", 280) * 0.25, + prefix = {"/y", "/шепот"}, + description = "@cmdY", + indicator = "chatWhispering" + }) + + ix.chat.Register("s", { + format = "%s кричит \"%s\"", + GetColor = function(self, speaker, text) + local color = ix.chat.classes.ic:GetColor(speaker, text) + + return Color(color.r + 35, color.g + 35, color.b + 35) + end, + CanHear = ix.config.Get("chatRange", 280) * 2, + prefix = {"/s", "/крик"}, + description = "@cmdS", + indicator = "chatYelling" + }) + + -- Out of character. + ix.chat.Register("ooc", { + CanSay = function(self, speaker, text) + if (!ix.config.Get("allowGlobalOOC")) then + speaker:NotifyLocalized("Global OOC is disabled on this server.") + return false + else + local delay = ix.config.Get("oocDelay", 10) + + -- Only need to check the time if they have spoken in OOC chat before. + if (delay > 0 and speaker.ixLastOOC) then + local lastOOC = CurTime() - speaker.ixLastOOC + + -- Use this method of checking time in case the oocDelay config changes. + if (lastOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then + speaker:NotifyLocalized("oocDelay", delay - math.ceil(lastOOC)) + + return false + end + end + + -- Save the last time they spoke in OOC. + speaker.ixLastOOC = CurTime() + end + end, + OnChatAdd = function(self, speaker, text) + -- @todo remove and fix actual cause of speaker being nil + if (!IsValid(speaker)) then + return + end + + local icon = "icon16/user.png" + + if (speaker:IsSuperAdmin()) then + icon = "icon16/shield.png" + elseif (speaker:IsAdmin()) then + icon = "icon16/star.png" + elseif (speaker:IsUserGroup("moderator") or speaker:IsUserGroup("operator")) then + icon = "icon16/wrench.png" + elseif (speaker:IsUserGroup("vip") or speaker:IsUserGroup("donator") or speaker:IsUserGroup("donor")) then + icon = "icon16/heart.png" + end + + icon = Material(hook.Run("GetPlayerIcon", speaker) or icon) + + chat.AddText(icon, Color(255, 50, 50), "[OOC] ", speaker, color_white, ": "..text) + end, + prefix = {"//", "/OOC"}, + description = "@cmdOOC", + noSpaceAfter = true + }) + + -- Local out of character. + ix.chat.Register("looc", { + CanSay = function(self, speaker, text) + local delay = ix.config.Get("loocDelay", 0) + + -- Only need to check the time if they have spoken in OOC chat before. + if (delay > 0 and speaker.ixLastLOOC) then + local lastLOOC = CurTime() - speaker.ixLastLOOC + + -- Use this method of checking time in case the oocDelay config changes. + if (lastLOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then + speaker:NotifyLocalized("loocDelay", delay - math.ceil(lastLOOC)) + + return false + end + end + + -- Save the last time they spoke in OOC. + speaker.ixLastLOOC = CurTime() + end, + OnChatAdd = function(self, speaker, text) + chat.AddText(Color(255, 50, 50), "[LOOC] ", ix.config.Get("chatColor"), speaker:Name()..": "..text) + end, + CanHear = ix.config.Get("chatRange", 280), + prefix = {".//", "[[", "/LOOC"}, + description = "@cmdLOOC", + noSpaceAfter = true + }) + + -- Roll information in chat. + ix.chat.Register("roll", { + format = "[Roll] %s кинул %s из %s.", + color = Color(155, 111, 176), + CanHear = ix.config.Get("chatRange", 280), + deadCanChat = true, + OnChatAdd = function(self, speaker, text, bAnonymous, data) + local max = data.max or 100 + local translated = L2(self.uniqueID.."Format", speaker:Name(), text, max) + + chat.AddText(self.color, translated and "** "..translated or string.format(self.format, + speaker:Name(), text, max + )) + end + }) + + -- run a hook after we add the basic chat classes so schemas/plugins can access their info as soon as possible if needed + hook.Run("InitializedChatClasses") + end) +end + +-- Private messages between players. +ix.chat.Register("pm", { + format = "[PM] %s -> %s: %s", + color = Color(125, 150, 75, 255), + deadCanChat = true, + + OnChatAdd = function(self, speaker, text, bAnonymous, data) + chat.AddText(self.color, string.format(self.format, speaker:GetName(), data.target:GetName(), text)) + + if (LocalPlayer() != speaker) then + surface.PlaySound("hl1/fvox/bell.wav") + end + end +}) + +-- Global events. +ix.chat.Register("event", { + CanHear = 1000000, + OnChatAdd = function(self, speaker, text) + chat.AddText(Color(255, 150, 0), text) + end, + indicator = "chatPerforming" +}) + +ix.chat.Register("connect", { + CanSay = function(self, speaker, text) + return !IsValid(speaker) + end, + OnChatAdd = function(self, speaker, text) + local icon = ix.util.GetMaterial("icon16/user_add.png") + + chat.AddText(icon, Color(150, 150, 200), L("playerConnected", text)) + end, + noSpaceAfter = true +}) + +ix.chat.Register("disconnect", { + CanSay = function(self, speaker, text) + return !IsValid(speaker) + end, + OnChatAdd = function(self, speaker, text) + local icon = ix.util.GetMaterial("icon16/user_delete.png") + + chat.AddText(icon, Color(200, 150, 200), L("playerDisconnected", text)) + end, + noSpaceAfter = true +}) + +ix.chat.Register("notice", { + CanSay = function(self, speaker, text) + return !IsValid(speaker) + end, + OnChatAdd = function(self, speaker, text, bAnonymous, data) + local icon = ix.util.GetMaterial(data.bError and "icon16/comment_delete.png" or "icon16/comment.png") + chat.AddText(icon, data.bError and Color(200, 175, 200, 255) or Color(175, 200, 255), text) + end, + noSpaceAfter = true +}) + +-- Why does ULX even have a /me command? +hook.Remove("PlayerSay", "ULXMeCheck") diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_class.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_class.lua new file mode 100644 index 0000000..b9e6c20 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_class.lua @@ -0,0 +1,210 @@ + +--[[-- +Helper library for loading/getting class information. + +Classes are temporary assignments for characters - analogous to a "job" in a faction. For example, you may have a police faction +in your schema, and have "police recruit" and "police chief" as different classes in your faction. Anyone can join a class in +their faction by default, but you can restrict this as you need with `CLASS.CanSwitchTo`. +]] +-- @module ix.class + +if (SERVER) then + util.AddNetworkString("ixClassUpdate") +end + +ix.class = ix.class or {} +ix.class.list = {} + +local charMeta = ix.meta.character + +--- Loads classes from a directory. +-- @realm shared +-- @internal +-- @string directory The path to the class files. +function ix.class.LoadFromDir(directory) + for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do + -- Get the name without the "sh_" prefix and ".lua" suffix. + local niceName = v:sub(4, -5) + -- Determine a numeric identifier for this class. + local index = #ix.class.list + 1 + local halt + + for _, v2 in ipairs(ix.class.list) do + if (v2.uniqueID == niceName) then + halt = true + + break + end + end + + if (halt == true) then + continue + end + + -- Set up a global table so the file has access to the class table. + CLASS = {index = index, uniqueID = niceName} + CLASS.name = "Unknown" + CLASS.description = "No description available." + CLASS.limit = 0 + + -- For future use with plugins. + if (PLUGIN) then + CLASS.plugin = PLUGIN.uniqueID + end + + ix.util.Include(directory.."/"..v, "shared") + + -- Why have a class without a faction? + if (!CLASS.faction or !team.Valid(CLASS.faction)) then + ErrorNoHalt("Class '"..niceName.."' does not have a valid faction!\n") + CLASS = nil + + continue + end + + -- Allow classes to be joinable by default. + if (!CLASS.CanSwitchTo) then + CLASS.CanSwitchTo = function(client) + return true + end + end + + ix.class.list[index] = CLASS + CLASS = nil + end +end + +--- Determines if a player is allowed to join a specific class. +-- @realm shared +-- @player client Player to check +-- @number class Index of the class +-- @treturn bool Whether or not the player can switch to the class +function ix.class.CanSwitchTo(client, class) + -- Get the class table by its numeric identifier. + local info = ix.class.list[class] + + -- See if the class exists. + if (!info) then + return false, "no info" + end + + -- If the player's faction matches the class's faction. + if (client:Team() != info.faction) then + return false, "not correct team" + end + + if (client:GetCharacter():GetClass() == class) then + return false, "same class request" + end + + if (info.limit > 0) then + if (#ix.class.GetPlayers(info.index) >= info.limit) then + return false, "class is full" + end + end + + if (hook.Run("CanPlayerJoinClass", client, class, info) == false) then + return false + end + + -- See if the class allows the player to join it. + return info:CanSwitchTo(client) +end + +--- Retrieves a class table. +-- @realm shared +-- @number identifier Index of the class +-- @treturn table Class table +function ix.class.Get(identifier) + return ix.class.list[identifier] +end + +--- Retrieves the players in a class +-- @realm shared +-- @number class Index of the class +-- @treturn table Table of players in the class +function ix.class.GetPlayers(class) + local players = {} + + for _, v in player.Iterator() do + local char = v:GetCharacter() + + if (char and char:GetClass() == class) then + table.insert(players, v) + end + end + + return players +end + +if (SERVER) then + --- Character class methods + -- @classmod Character + + --- Makes this character join a class. This automatically calls `KickClass` for you. + -- @realm server + -- @number class Index of the class to join + -- @treturn bool Whether or not the character has successfully joined the class + function charMeta:JoinClass(class) + if (!class) then + self:KickClass() + return false + end + + local oldClass = self:GetClass() + local client = self:GetPlayer() + + if (ix.class.CanSwitchTo(client, class)) then + self:SetClass(class) + hook.Run("PlayerJoinedClass", client, class, oldClass) + + return true + end + + return false + end + + --- Kicks this character out of the class they are currently in. + -- @realm server + function charMeta:KickClass() + local client = self:GetPlayer() + if (!client) then return end + + local goClass + + for k, v in pairs(ix.class.list) do + if (v.faction == client:Team() and v.isDefault) then + goClass = k + + break + end + end + + if (!goClass) then + ErrorNoHaltWithStack("[Helix] No default class set for faction '" .. team.GetName(client:Team()) .. "'") + + return + end + + self:JoinClass(goClass) + + hook.Run("PlayerJoinedClass", client, goClass) + end + + function GM:PlayerJoinedClass(client, class, oldClass) + local info = ix.class.list[class] + local info2 = ix.class.list[oldClass] + + if (info.OnSet) then + info:OnSet(client) + end + + if (info2 and info2.OnLeave) then + info2:OnLeave(client) + end + + net.Start("ixClassUpdate") + net.WriteEntity(client) + net.Broadcast() + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_command.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_command.lua new file mode 100644 index 0000000..055a189 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_command.lua @@ -0,0 +1,647 @@ + +--[[-- +Registration, parsing, and handling of commands. + +Commands can be ran through the chat with slash commands or they can be executed through the console. Commands can be manually +restricted to certain usergroups using a [CAMI](https://github.com/glua/CAMI)-compliant admin mod. +]] +-- @module ix.command + +--- When registering commands with `ix.command.Add`, you'll need to pass in a valid command structure. This is simply a table +-- with various fields defined to describe the functionality of the command. +-- @realm shared +-- @table CommandStructure +-- @field[type=function] OnRun This function is called when the command has passed all the checks and can execute. The first two +-- arguments will be the running command table and the calling player. If the arguments field has been specified, the arguments +-- will be passed as regular function parameters rather than in a table. +-- +-- When the arguments field is defined: `OnRun(self, client, target, length, message)` +-- +-- When the arguments field is NOT defined: `OnRun(self, client, arguments)` +-- @field[type=string,opt="@noDesc"] description The help text that appears when the user types in the command. If the string is +-- prefixed with `"@"`, it will use a language phrase. +-- @field[type=table,opt=nil] argumentNames An array of strings corresponding to each argument of the command. This ignores the +-- name that's specified in the `OnRun` function arguments and allows you to use any string to change the text that displays +-- in the command's syntax help. When using this field, make sure that the amount is equal to the amount of arguments, as such: +-- COMMAND.arguments = {ix.type.character, ix.type.number} +-- COMMAND.argumentNames = {"target char", "cash (1-1000)"} +-- @field[type=table,opt] arguments If this field is defined, then additional checks will be performed to ensure that the +-- arguments given to the command are valid. This removes extra boilerplate code since all the passed arguments are guaranteed +-- to be valid. See `CommandArgumentsStructure` for more information. +-- @field[type=boolean,opt=false] adminOnly Provides an additional check to see if the user is an admin before running. +-- @field[type=boolean,opt=false] superAdminOnly Provides an additional check to see if the user is a superadmin before running. +-- @field[type=string,opt=nil] privilege Manually specify a privilege name for this command. It will always be prefixed with +-- `"Helix - "`. This is used in the case that you want to group commands under the same privilege, or use a privilege that +-- you've already defined (i.e grouping `/CharBan` and `/CharUnban` into the `Helix - Ban Character` privilege). +-- @field[type=function,opt=nil] OnCheckAccess This callback checks whether or not the player is allowed to run the command. +-- This callback should **NOT** be used in conjunction with `adminOnly` or `superAdminOnly`, as populating those +-- fields create a custom a `OnCheckAccess` callback for you internally. This is used in cases where you want more fine-grained +-- access control for your command. +-- +-- Keep in mind that this is a **SHARED** callback; the command will not show up the client if the callback returns `false`. + +--- Rather than checking the validity for arguments in your command's `OnRun` function, you can have Helix do it for you to +-- reduce the amount of boilerplate code that needs to be written. This can be done by populating the `arguments` field. +-- +-- When using the `arguments` field in your command, you are specifying specific types that you expect to receive when the +-- command is ran successfully. This means that before `OnRun` is called, the arguments passed to the command from a user will +-- be verified to be valid. Each argument is an `ix.type` entry that specifies the expected type for that argument. Optional +-- arguments can be specified by using a bitwise OR with the special `ix.type.optional` type. When specified as optional, the +-- argument can be `nil` if the user has not entered anything for that argument - otherwise it will be valid. +-- +-- Note that optional arguments must always be at the end of a list of arguments - or rather, they must not follow a required +-- argument. The `syntax` field will be automatically populated when using strict arguments, which means you shouldn't fill out +-- the `syntax` field yourself. The arguments you specify will have the same names as the arguments in your OnRun function. +-- +-- Consider this example command: +-- ix.command.Add("CharSlap", { +-- description = "Slaps a character with a large trout.", +-- adminOnly = true, +-- arguments = { +-- ix.type.character, +-- bit.bor(ix.type.number, ix.type.optional) +-- }, +-- OnRun = function(self, client, target, damage) +-- -- WHAM! +-- end +-- }) +-- Here, we've specified the first argument called `target` to be of type `character`, and the second argument called `damage` +-- to be of type `number`. The `damage` argument is optional, meaning that the command will still run if the user has not +-- specified any value for the damage. In this case, we'll need to check if it was specified by doing a simple +-- `if (damage) then`. The syntax field will be automatically populated with the value `" [damage: number]"`. +-- @realm shared +-- @table CommandArgumentsStructure + +ix.command = ix.command or {} +ix.command.list = ix.command.list or {} + +local COMMAND_PREFIX = "/" + +local function ArgumentCheckStub(command, client, given) + local arguments = command.arguments + local result = {} + + for i = 1, #arguments do + local bOptional = bit.band(arguments[i], ix.type.optional) == ix.type.optional + local argType = bOptional and bit.bxor(arguments[i], ix.type.optional) or arguments[i] + local argument = given[i] + + if (!argument and !bOptional) then + return L("invalidArg", client, i) + end + + if (argType == ix.type.string) then + if (!argument and bOptional) then + result[#result + 1] = nil + else + result[#result + 1] = tostring(argument) + end + elseif (argType == ix.type.text) then + result[#result + 1] = table.concat(given, " ", i) or "" + break + elseif (argType == ix.type.number) then + local value = tonumber(argument) + + if (!bOptional and !value) then + return L("invalidArg", client, i) + end + + result[#result + 1] = value + elseif (argType == ix.type.player or argType == ix.type.character) then + local bPlayer = argType == ix.type.player + local value = ix.util.FindPlayer(argument or "") -- argument could be nil due to optional type + + -- FindPlayer emits feedback for us + if (!value and !bOptional) then + return L(bPlayer and "plyNoExist" or "charNoExist", client) + end + + -- check for the character if we're using the character type + if (!bPlayer) then + local character = value:GetCharacter() + + if (!character) then + return L("charNoExist", client) + end + + value = character + end + + result[#result + 1] = value + elseif (argType == ix.type.steamid) then + local value = argument:match("STEAM_(%d+):(%d+):(%d+)") + + if (!value and bOptional) then + return L("invalidArg", client, i) + end + + result[#result + 1] = value + elseif (argType == ix.type.bool) then + if (argument == nil and bOptional) then + result[#result + 1] = nil + else + result[#result + 1] = tobool(argument) + end + end + end + + return result +end + +--- Creates a new command. +-- @realm shared +-- @string command Name of the command (recommended in UpperCamelCase) +-- @tparam CommandStructure data Data describing the command +-- @see CommandStructure +-- @see CommandArgumentsStructure +function ix.command.Add(command, data) + data.name = string.gsub(command, "%s", "") + data.description = data.description or "" + + command = command:lower() + data.uniqueID = command + + -- Why bother adding a command if it doesn't do anything. + if (!data.OnRun) then + return ErrorNoHalt("Command '"..command.."' does not have a callback, not adding!\n") + end + + -- Add a function to get the description that can be overridden. + if (!data.GetDescription) then + -- Check if the description is using a language string. + if (data.description:sub(1, 1) == "@") then + function data:GetDescription() + return L(self.description:sub(2)) + end + else + -- Otherwise just return the raw description. + function data:GetDescription() + return self.description + end + end + end + + -- OnCheckAccess by default will rely on CAMI for access information with adminOnly/superAdminOnly being fallbacks + if (!data.OnCheckAccess) then + if (data.group) then + ErrorNoHalt("Command '" .. data.name .. "' tried to use the deprecated field 'group'!\n") + return + end + + local privilege = "Helix - " .. (isstring(data.privilege) and data.privilege or data.name) + + -- we could be using a previously-defined privilege + if (!CAMI.GetPrivilege(privilege)) then + CAMI.RegisterPrivilege({ + Name = privilege, + MinAccess = data.superAdminOnly and "superadmin" or (data.adminOnly and "admin" or "user"), + Description = data.description + }) + end + + function data:OnCheckAccess(client) + local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil) + return bHasAccess + end + end + + -- if we have an arguments table, then we're using the new command format + if (data.arguments) then + local bFirst = true + local bLastOptional = false + local bHasArgumentNames = istable(data.argumentNames) + + data.syntax = "" -- @todo deprecate this in favour of argumentNames + data.argumentNames = bHasArgumentNames and data.argumentNames or {} + + -- if one argument is supplied by itself, put it into a table + if (!istable(data.arguments)) then + data.arguments = {data.arguments} + end + + if (bHasArgumentNames and #data.argumentNames != #data.arguments) then + return ErrorNoHalt(string.format( + "Command '%s' doesn't have argument names that correspond to each argument\n", command + )) + end + + -- check the arguments table to see if its entries are valid + for i = 1, #data.arguments do + local argument = data.arguments[i] + local argumentName = debug.getlocal(data.OnRun, 2 + i) + + if (argument == ix.type.optional) then + return ErrorNoHalt(string.format( + "Command '%s' tried to use an optional argument for #%d without specifying type\n", command, i + )) + elseif (!isnumber(argument)) then + return ErrorNoHalt(string.format( + "Command '%s' tried to use an invalid type for argument #%d\n", command, i + )) + elseif (argument == ix.type.array or bit.band(argument, ix.type.array) > 0) then + return ErrorNoHalt(string.format( + "Command '%s' tried to use an unsupported type 'array' for argument #%d\n", command, i + )) + end + + local bOptional = bit.band(argument, ix.type.optional) > 0 + argument = bOptional and bit.bxor(argument, ix.type.optional) or argument + + if (!ix.type[argument]) then + return ErrorNoHalt(string.format( + "Command '%s' tried to use an invalid type for argument #%d\n", command, i + )) + elseif (!isstring(argumentName)) then + return ErrorNoHalt(string.format( + "Command '%s' is missing function argument for command argument #%d\n", command, i + )) + elseif (argument == ix.type.text and i != #data.arguments) then + return ErrorNoHalt(string.format( + "Command '%s' tried to use a text argument outside of the last argument\n", command + )) + elseif (!bOptional and bLastOptional) then + return ErrorNoHalt(string.format( + "Command '%s' tried to use an required argument after an optional one\n", command + )) + end + + -- text is always optional and will return an empty string if nothing is specified, rather than nil + if (argument == ix.type.text) then + data.arguments[i] = bit.bor(ix.type.text, ix.type.optional) + bOptional = true + end + + if (!bHasArgumentNames) then + data.argumentNames[i] = argumentName + end + + data.syntax = data.syntax .. (bFirst and "" or " ") .. + string.format((bOptional and "[%s: %s]" or "<%s: %s>"), argumentName, ix.type[argument]) + + bFirst = false + bLastOptional = bOptional + end + + if (data.syntax:utf8len() == 0) then + data.syntax = "" + end + else + data.syntax = data.syntax or "" + end + + -- Add the command to the list of commands. + local alias = data.alias + + if (alias) then + if (istable(alias)) then + for _, v in ipairs(alias) do + ix.command.list[v:lower()] = data + end + elseif (isstring(alias)) then + ix.command.list[alias:lower()] = data + end + end + + ix.command.list[command] = data +end + +--- Returns true if a player is allowed to run a certain command. +-- @realm shared +-- @player client Player to check access for +-- @string command Name of the command to check access for +-- @treturn bool Whether or not the player is allowed to run the command +function ix.command.HasAccess(client, command) + command = ix.command.list[command:lower()] + + if (command) then + if (command.OnCheckAccess) then + return command:OnCheckAccess(client) + else + return true + end + end + + return false +end + +--- Returns a table of arguments from a given string. +-- Words separated by spaces will be considered one argument. To have an argument containing multiple words, they must be +-- contained within quotation marks. +-- @realm shared +-- @string text String to extract arguments from +-- @treturn table Arguments extracted from string +-- @usage PrintTable(ix.command.ExtractArgs("these are \"some arguments\"")) +-- > 1 = these +-- > 2 = are +-- > 3 = some arguments +function ix.command.ExtractArgs(text) + local skip = 0 + local arguments = {} + local curString = "" + + for i = 1, text:utf8len() do + if (i <= skip) then continue end + + local c = text:utf8sub(i, i) + + if (c == "\"") then + local match = text:utf8sub(i):match("%b\"\"") + + if (match) then + curString = "" + skip = i + match:utf8len() + arguments[#arguments + 1] = match:utf8sub(2, -2) + else + curString = curString..c + end + elseif (c == " " and curString != "") then + arguments[#arguments + 1] = curString + curString = "" + else + if (c == " " and curString == "") then + continue + end + + curString = curString..c + end + end + + if (curString != "") then + arguments[#arguments + 1] = curString + end + + return arguments +end + +--- Returns an array of potential commands by unique id. +-- When bSorted is true, the commands will be sorted by name. When bReorganize is true, it will move any exact match to the top +-- of the array. When bRemoveDupes is true, it will remove any commands that have the same NAME. +-- @realm shared +-- @string identifier Search query +-- @bool[opt=false] bSorted Whether or not to sort the commands by name +-- @bool[opt=false] bReorganize Whether or not any exact match will be moved to the top of the array +-- @bool[opt=false] bRemoveDupes Whether or not to remove any commands that have the same name +-- @treturn table Array of command tables whose name partially or completely matches the search query +function ix.command.FindAll(identifier, bSorted, bReorganize, bRemoveDupes) + local result = {} + local iterator = bSorted and SortedPairs or pairs + local fullMatch + + identifier = identifier:lower() + + if (identifier == "/") then + -- we don't simply copy because we need numeric indices + for _, v in iterator(ix.command.list) do + result[#result + 1] = v + end + + return result + elseif (identifier:utf8sub(1, 1) == "/") then + identifier = identifier:utf8sub(2) + end + + for k, v in iterator(ix.command.list) do + if (k:match(identifier)) then + local index = #result + 1 + result[index] = v + + if (k == identifier) then + fullMatch = index + end + end + end + + if (bReorganize and fullMatch and fullMatch != 1) then + result[1], result[fullMatch] = result[fullMatch], result[1] + end + + if (bRemoveDupes) then + local commandNames = {} + + -- using pairs intead of ipairs because we might remove from array + for k, v in pairs(result) do + if (commandNames[v.name]) then + table.remove(result, k) + end + + commandNames[v.name] = true + end + end + + return result +end + +if (SERVER) then + util.AddNetworkString("ixCommand") + + --- Attempts to find a player by an identifier. If unsuccessful, a notice will be displayed to the specified player. The + -- search criteria is derived from `ix.util.FindPlayer`. + -- @realm server + -- @player client Player to give a notification to if the player could not be found + -- @string name Search query + -- @treturn[1] player Player that matches the given search query + -- @treturn[2] nil If a player could not be found + -- @see ix.util.FindPlayer + function ix.command.FindPlayer(client, name) + local target = isstring(name) and ix.util.FindPlayer(name) or NULL + + if (IsValid(target)) then + return target + else + client:NotifyLocalized("plyNoExist") + end + end + + --- Forces a player to execute a command by name. + -- @realm server + -- @player client Player who is executing the command + -- @string command Full name of the command to be executed. This string gets lowered, but it's good practice to stick with + -- the exact name of the command + -- @tab arguments Array of arguments to be passed to the command + -- @usage ix.command.Run(player.GetByID(1), "Roll", {10}) + function ix.command.Run(client, command, arguments) + if ((client.ixCommandCooldown or 0) > RealTime()) then + return + end + + command = ix.command.list[tostring(command):lower()] + + if (!command) then + return + end + + -- we throw it into a table since arguments get unpacked and only + -- the arguments table gets passed in by default + local argumentsTable = arguments + arguments = {argumentsTable} + + -- if feedback is non-nil, we can assume that the command failed + -- and is a phrase string + local feedback + + -- check for group access + if (command.OnCheckAccess) then + local bSuccess, phrase = command:OnCheckAccess(client) + feedback = !bSuccess and L(phrase and phrase or "noPerm", client) or nil + end + + -- check for strict arguments + if (!feedback and command.arguments) then + arguments = ArgumentCheckStub(command, client, argumentsTable) + + if (isstring(arguments)) then + feedback = arguments + end + end + + -- run the command if all the checks passed + if (!feedback) then + local results = {command:OnRun(client, unpack(arguments))} + local phrase = results[1] + + -- check to see if the command has returned a phrase string and display it + if (isstring(phrase)) then + if (IsValid(client)) then + if (phrase:sub(1, 1) == "@") then + client:NotifyLocalized(phrase:sub(2), unpack(results, 2)) + else + client:Notify(phrase) + end + else + -- print message since we're running from the server console + print(phrase) + end + end + + client.ixCommandCooldown = RealTime() + 0.5 + + if (IsValid(client)) then + ix.log.Add(client, "command", COMMAND_PREFIX .. command.name, argumentsTable and table.concat(argumentsTable, " ")) + end + else + client:Notify(feedback) + end + end + + --- Parses a chat string and runs the command if one is found. Specifically, it checks for commands in a string with the + -- format `/CommandName some arguments` + -- @realm server + -- @player client Player who is executing the command + -- @string text Input string to search for the command format + -- @string[opt] realCommand Specific command to check for. If this is specified, it will not try to run any command that's + -- found at the beginning - only if it matches `realCommand` + -- @tab[opt] arguments Array of arguments to pass to the command. If not specified, it will try to extract it from the + -- string specified in `text` using `ix.command.ExtractArgs` + -- @treturn bool Whether or not a command has been found + -- @usage ix.command.Parse(player.GetByID(1), "/roll 10") + function ix.command.Parse(client, text, realCommand, arguments) + if (realCommand or text:utf8sub(1, 1) == COMMAND_PREFIX) then + -- See if the string contains a command. + local match = realCommand or text:utf8lower():match(COMMAND_PREFIX.."([_%w]+)") + + -- is it unicode text? + if (!match) then + local post = string.Explode(" ", text) + local len = string.len(post[1]) + + match = post[1]:utf8sub(2, len) + end + + match = match:utf8lower() + + local command = ix.command.list[match] + -- We have a valid, registered command. + if (command) then + -- Get the arguments like a console command. + if (!arguments) then + arguments = ix.command.ExtractArgs(text:utf8sub(match:utf8len() + 3)) + end + + -- Runs the actual command. + ix.command.Run(client, match, arguments) + else + if (IsValid(client)) then + client:NotifyLocalized("cmdNoExist") + else + print("Sorry, that command does not exist.") + end + end + + return true + end + + return false + end + + concommand.Add("ix", function(client, _, arguments) + local command = arguments[1] + table.remove(arguments, 1) + + ix.command.Parse(client, nil, command or "", arguments) + end) + + net.Receive("ixCommand", function(length, client) + if ((client.ixNextCmd or 0) < CurTime()) then + local command = net.ReadString() + local indices = net.ReadUInt(4) + local arguments = {} + + for _ = 1, indices do + local value = net.ReadType() + + if (isstring(value) or isnumber(value)) then + arguments[#arguments + 1] = tostring(value) + end + end + + ix.command.Parse(client, nil, command, arguments) + client.ixNextCmd = CurTime() + 0.2 + end + end) +else + --- Request the server to run a command. This mimics similar functionality to the client typing `/CommandName` in the chatbox. + -- @realm client + -- @string command Unique ID of the command + -- @param ... Arguments to pass to the command + -- @usage ix.command.Send("roll", 10) + function ix.command.Send(command, ...) + local arguments = {...} + + net.Start("ixCommand") + net.WriteString(command) + net.WriteUInt(#arguments, 4) + + for _, v in ipairs(arguments) do + net.WriteType(v) + end + + net.SendToServer() + end + + concommand.Add("ix", function(client, _, arguments) + ix.command.Send(table.remove(arguments, 1), unpack(arguments)) + end, function(_, arguments) + arguments = arguments:TrimLeft() + + local autocomplete = {} + local command = string.Explode(" ", arguments)[1] + + for _, v in pairs(ix.command.FindAll(command, true, true)) do + if (v.OnCheckAccess and !v:OnCheckAccess(LocalPlayer())) then + continue + end + + if (arguments:find(v.uniqueID, 1, true) == 1) then + return { + "ix " .. arguments, + v:GetDescription(), + L("syntax", v.syntax) + } + end + + table.insert(autocomplete, "ix " .. v.uniqueID) + end + + return autocomplete + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_currency.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_currency.lua new file mode 100644 index 0000000..6ad219b --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_currency.lua @@ -0,0 +1,110 @@ +--- A library representing the server's currency system. +-- @module ix.currency + +ix.currency = ix.currency or {} +ix.currency.symbol = ix.currency.symbol or "₽" +ix.currency.singular = ix.currency.singular or "рубль" +ix.currency.plural = ix.currency.plural or "рублей" +ix.currency.model = ix.currency.model or "models/props_lab/box01a.mdl" + +--- Sets the currency type. +-- @realm shared +-- @string symbol The symbol of the currency. +-- @string singular The name of the currency in it's singular form. +-- @string plural The name of the currency in it's plural form. +-- @string model The model of the currency entity. +function ix.currency.Set(symbol, singular, plural, model) + ix.currency.symbol = symbol + ix.currency.singular = singular + ix.currency.plural = plural + ix.currency.model = model +end + +--- Returns a formatted string according to the current currency. +-- @realm shared +-- @number amount The amount of cash being formatted. +-- @treturn string The formatted string. +function ix.currency.Get(amount) + if (amount == 1) then + return ix.currency.symbol.."1 "..ix.currency.singular + else + return ix.currency.symbol..amount.." "..ix.currency.plural + end +end + +--- Spawns an amount of cash at a specific location on the map. +-- @realm shared +-- @vector pos The position of the money to be spawned. +-- @number amount The amount of cash being spawned. +-- @angle[opt=angle_zero] angle The angle of the entity being spawned. +-- @treturn entity The spawned money entity. +function ix.currency.Spawn(pos, amount, angle) + if (!amount or amount < 0) then + print("[Helix] Can't create currency entity: Invalid Amount of money") + return + end + + local money = ents.Create("ix_money") + money:Spawn() + + if (IsValid(pos) and pos:IsPlayer()) then + pos = pos:GetItemDropPos(money) + elseif (!isvector(pos)) then + print("[Helix] Can't create currency entity: Invalid Position") + + money:Remove() + return + end + + money:SetPos(pos) + -- double check for negative. + money:SetAmount(math.Round(math.abs(amount))) + money:SetAngles(angle or angle_zero) + money:Activate() + + return money +end + +function GM:OnPickupMoney(client, moneyEntity) + if (IsValid(moneyEntity)) then + local amount = moneyEntity:GetAmount() + + client:GetCharacter():GiveMoney(amount) + end +end + +do + local character = ix.meta.character + + function character:HasMoney(amount) + if (amount < 0) then + print("Negative Money Check Received.") + end + + return self:GetMoney() >= amount + end + + function character:GiveMoney(amount, bNoLog) + amount = math.abs(amount) + + if (!bNoLog) then + ix.log.Add(self:GetPlayer(), "money", amount) + end + + self:SetMoney(self:GetMoney() + amount) + + return true + end + + function character:TakeMoney(amount, bNoLog) + amount = math.abs(amount) + + if (!bNoLog) then + ix.log.Add(self:GetPlayer(), "money", -amount) + end + + self:SetMoney(self:GetMoney() - amount) + + return true + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_date.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_date.lua new file mode 100644 index 0000000..14efcb4 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_date.lua @@ -0,0 +1,146 @@ + +--[[-- +Persistent date and time handling. + +All of Lua's time functions are dependent on the Unix epoch, which means we can't have dates that go further than 1970. This +library remedies this problem. Time/date is represented by a `date` object that is queried, instead of relying on the seconds +since the epoch. + +## Futher documentation +This library makes use of a third-party date library found at https://github.com/Tieske/date - you can find all documentation +regarding the `date` object and its methods there. +]] +-- @module ix.date + +ix.date = ix.date or {} +ix.date.lib = ix.date.lib or include("thirdparty/sh_date.lua") +ix.date.timeScale = ix.date.timeScale or ix.config.Get("secondsPerMinute", 60) -- seconds per minute +ix.date.current = ix.date.current or ix.date.lib() -- current in-game date/time +ix.date.start = ix.date.start or CurTime() -- arbitrary start time for calculating date/time offset + +if (SERVER) then + util.AddNetworkString("ixDateSync") + + --- Loads the date from disk. + -- @realm server + -- @internal + function ix.date.Initialize() + local currentDate = ix.data.Get("date", nil, false, true) + + -- construct new starting date if we don't have it saved already + if (!currentDate) then + currentDate = { + year = ix.config.Get("year"), + month = ix.config.Get("month"), + day = ix.config.Get("day"), + hour = tonumber(os.date("%H")) or 0, + min = tonumber(os.date("%M")) or 0, + sec = tonumber(os.date("%S")) or 0 + } + + currentDate = ix.date.lib.serialize(ix.date.lib(currentDate)) + ix.data.Set("date", currentDate, false, true) + end + + ix.date.timeScale = ix.config.Get("secondsPerMinute", 60) + ix.date.current = ix.date.lib.construct(currentDate) + end + + --- Updates the internal in-game date/time representation and resets the offset. + -- @realm server + -- @internal + function ix.date.ResolveOffset() + ix.date.current = ix.date.Get() + ix.date.start = CurTime() + end + + --- Updates the time scale of the in-game date/time. The time scale is given in seconds per minute (i.e how many real life + -- seconds it takes for an in-game minute to pass). You should avoid using this function and use the in-game config menu to + -- change the time scale instead. + -- @realm server + -- @internal + -- @number secondsPerMinute New time scale + function ix.date.UpdateTimescale(secondsPerMinute) + ix.date.ResolveOffset() + ix.date.timeScale = secondsPerMinute + end + + --- Sends the current date to a player. This is done automatically when the player joins the server. + -- @realm server + -- @internal + -- @player[opt=nil] client Player to send the date to, or `nil` to send to everyone + function ix.date.Send(client) + net.Start("ixDateSync") + + net.WriteFloat(ix.date.timeScale) + net.WriteTable(ix.date.current) + net.WriteFloat(ix.date.start) + + if (client) then + net.Send(client) + else + net.Broadcast() + end + end + + --- Saves the current in-game date to disk. + -- @realm server + -- @internal + function ix.date.Save() + ix.date.bSaving = true + + ix.date.ResolveOffset() -- resolve offset so we save the actual time to disk + ix.data.Set("date", ix.date.lib.serialize(ix.date.current), false, true) + + -- update config to reflect current saved date + ix.config.Set("year", ix.date.current:getyear()) + ix.config.Set("month", ix.date.current:getmonth()) + ix.config.Set("day", ix.date.current:getday()) + + ix.date.bSaving = nil + end +else + net.Receive("ixDateSync", function() + local timeScale = net.ReadFloat() + local currentDate = ix.date.lib.construct(net.ReadTable()) + local startTime = net.ReadFloat() + + ix.date.timeScale = timeScale + ix.date.current = currentDate + ix.date.start = startTime + end) +end + +--- Returns the currently set date. +-- @realm shared +-- @treturn date Current in-game date +function ix.date.Get() + local minutesSinceStart = (CurTime() - ix.date.start) / ix.date.timeScale + + return ix.date.current:copy():addminutes(minutesSinceStart) +end + +--- Returns a string formatted version of a date. +-- @realm shared +-- @string format Format string +-- @date[opt=nil] currentDate Date to format. If nil, it will use the currently set date +-- @treturn string Formatted date +function ix.date.GetFormatted(format, currentDate) + return (currentDate or ix.date.Get()):fmt(format) +end + +--- Returns a serialized version of a date. This is useful when you need to network a date to clients, or save a date to disk. +-- @realm shared +-- @date[opt=nil] currentDate Date to serialize. If nil, it will use the currently set date +-- @treturn table Serialized date +function ix.date.GetSerialized(currentDate) + return ix.date.lib.serialize(currentDate or ix.date.Get()) +end + +--- Returns a date object from a table or serialized date. +-- @realm shared +-- @param currentDate Date to construct +-- @treturn date Constructed date object +function ix.date.Construct(currentDate) + return ix.date.lib.construct(currentDate) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_faction.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_faction.lua new file mode 100644 index 0000000..f5891b5 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_faction.lua @@ -0,0 +1,125 @@ + +--- Helper library for loading/getting faction information. +-- @module ix.faction + +ix.faction = ix.faction or {} +ix.faction.teams = ix.faction.teams or {} +ix.faction.indices = ix.faction.indices or {} + +local CITIZEN_MODELS = { + "models/humans/group01/male_01.mdl", + "models/humans/group01/male_02.mdl", + "models/humans/group01/male_04.mdl", + "models/humans/group01/male_05.mdl", + "models/humans/group01/male_06.mdl", + "models/humans/group01/male_07.mdl", + "models/humans/group01/male_08.mdl", + "models/humans/group01/male_09.mdl", + "models/humans/group02/male_01.mdl", + "models/humans/group02/male_03.mdl", + "models/humans/group02/male_05.mdl", + "models/humans/group02/male_07.mdl", + "models/humans/group02/male_09.mdl", + "models/humans/group01/female_01.mdl", + "models/humans/group01/female_02.mdl", + "models/humans/group01/female_03.mdl", + "models/humans/group01/female_06.mdl", + "models/humans/group01/female_07.mdl", + "models/humans/group02/female_01.mdl", + "models/humans/group02/female_03.mdl", + "models/humans/group02/female_06.mdl", + "models/humans/group01/female_04.mdl" +} + +--- Loads factions from a directory. +-- @realm shared +-- @string directory The path to the factions files. +function ix.faction.LoadFromDir(directory) + for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do + local niceName = v:sub(4, -5) + + FACTION = ix.faction.teams[niceName] or {index = table.Count(ix.faction.teams) + 1, isDefault = false} + if (PLUGIN) then + FACTION.plugin = PLUGIN.uniqueID + end + + ix.util.Include(directory.."/"..v, "shared") + + if (!FACTION.name) then + FACTION.name = "Unknown" + ErrorNoHalt("Faction '"..niceName.."' is missing a name. You need to add a FACTION.name = \"Name\"\n") + end + + if (!FACTION.color) then + FACTION.color = Color(150, 150, 150) + ErrorNoHalt("Faction '"..niceName.."' is missing a color. You need to add FACTION.color = Color(1, 2, 3)\n") + end + + team.SetUp(FACTION.index, FACTION.name or "Unknown", FACTION.color or Color(125, 125, 125)) + + FACTION.models = FACTION.models or CITIZEN_MODELS + FACTION.uniqueID = FACTION.uniqueID or niceName + + for _, v2 in pairs(FACTION.models) do + if (isstring(v2)) then + util.PrecacheModel(v2) + elseif (istable(v2)) then + util.PrecacheModel(v2[1]) + end + end + + if (!FACTION.GetModels) then + function FACTION:GetModels(client) + return self.models + end + end + + ix.faction.indices[FACTION.index] = FACTION + ix.faction.teams[niceName] = FACTION + FACTION = nil + end +end + +--- Retrieves a faction table. +-- @realm shared +-- @param identifier Index or name of the faction +-- @treturn table Faction table +-- @usage print(ix.faction.Get(Entity(1):Team()).name) +-- > "Citizen" +function ix.faction.Get(identifier) + return ix.faction.indices[identifier] or ix.faction.teams[identifier] +end + +--- Retrieves a faction index. +-- @realm shared +-- @string uniqueID Unique ID of the faction +-- @treturn number Faction index +function ix.faction.GetIndex(uniqueID) + for k, v in ipairs(ix.faction.indices) do + if (v.uniqueID == uniqueID) then + return k + end + end +end + +if (CLIENT) then + --- Returns true if a faction requires a whitelist. + -- @realm client + -- @number faction Index of the faction + -- @treturn bool Whether or not the faction requires a whitelist + function ix.faction.HasWhitelist(faction) + local data = ix.faction.indices[faction] + + if (data) then + if (data.isDefault) then + return true + end + + local ixData = ix.localData and ix.localData.whitelists or {} + + return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false + end + + return false + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_flag.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_flag.lua new file mode 100644 index 0000000..89bc658 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_flag.lua @@ -0,0 +1,203 @@ + +--[[-- +Grants abilities to characters. + +Flags are a simple way of adding/removing certain abilities to players on a per-character basis. Helix comes with a few flags +by default, for example to restrict spawning of props, usage of the physgun, etc. All flags will be listed in the +`Flags` section of the `Help` menu. Flags are usually used when server validation is required to allow a player to do something +on their character. However, it's usually preferable to use in-character methods over flags when possible (i.e restricting +the business menu to characters that have a permit item, rather than using flags to determine availability). + +Flags are a single alphanumeric character that can be checked on the server. Serverside callbacks can be used to provide +functionality whenever the flag is added or removed. For example: + ix.flag.Add("z", "Access to some cool stuff.", function(client, bGiven) + print("z flag given:", bGiven) + end) + + Entity(1):GetCharacter():GiveFlags("z") + > z flag given: true + + Entity(1):GetCharacter():TakeFlags("z") + > z flag given: false + + print(Entity(1):GetCharacter():HasFlags("z")) + > false + +Check out `Character:GiveFlags` and `Character:TakeFlags` for additional info. +]] +-- @module ix.flag + +ix.flag = ix.flag or {} +ix.flag.list = ix.flag.list or {} + +--- Creates a flag. This should be called shared in order for the client to be aware of the flag's existence. +-- @realm shared +-- @string flag Alphanumeric character to use for the flag +-- @string description Description of the flag +-- @func callback Function to call when the flag is given or taken from a player +function ix.flag.Add(flag, description, callback) + ix.flag.list[flag] = { + description = description, + callback = callback + } +end + +if (SERVER) then + -- Called to apply flags when a player has spawned. + -- @realm server + -- @internal + -- @player client Player to setup flags for + function ix.flag.OnSpawn(client) + -- Check if they have a valid character. + if (client:GetCharacter()) then + -- Get all of the character's flags. + local flags = client:GetCharacter():GetFlags() + + for i = 1, #flags do + -- Get each individual flag. + local flag = flags[i] + local info = ix.flag.list[flag] + + -- Check if the flag has a callback. + if (info and info.callback) then + -- Run the callback, passing the player and true so they get whatever benefits. + info.callback(client, true) + end + end + end + end +end + +do + local character = ix.meta.character + + if (SERVER) then + --- Flag util functions for character + -- @classmod Character + + --- Sets this character's accessible flags. Note that this method overwrites **all** flags instead of adding them. + -- @realm server + -- @string flags Flag(s) this charater is allowed to have + -- @see GiveFlags + function character:SetFlags(flags) + self:SetData("f", flags) + end + + --- Adds a flag to the list of this character's accessible flags. This does not overwrite existing flags. + -- @realm server + -- @string flags Flag(s) this character should be given + -- @usage character:GiveFlags("pet") + -- -- gives p, e, and t flags to the character + -- @see HasFlags + function character:GiveFlags(flags) + local addedFlags = "" + + -- Get the individual flags within the flag string. + for i = 1, #flags do + local flag = flags[i] + local info = ix.flag.list[flag] + + if (info) then + if (!self:HasFlags(flag)) then + addedFlags = addedFlags..flag + end + + if (info.callback) then + -- Pass the player and true (true for the flag being given.) + info.callback(self:GetPlayer(), true) + end + end + end + + -- Only change the flag string if it is different. + if (addedFlags != "") then + self:SetFlags(self:GetFlags()..addedFlags) + end + end + + --- Removes this character's access to the given flags. + -- @realm server + -- @string flags Flag(s) to remove from this character + -- @usage -- for a character with "pet" flags + -- character:TakeFlags("p") + -- -- character now has e, and t flags + function character:TakeFlags(flags) + local oldFlags = self:GetFlags() + local newFlags = oldFlags + + -- Get the individual flags within the flag string. + for i = 1, #flags do + local flag = flags[i] + local info = ix.flag.list[flag] + + -- Call the callback if the flag has been registered. + if (info and info.callback) then + -- Pass the player and false (false since the flag is being taken) + info.callback(self:GetPlayer(), false) + end + + newFlags = newFlags:gsub(flag, "") + end + + if (newFlags != oldFlags) then + self:SetFlags(newFlags) + end + end + end + + --- Returns all of the flags this character has. + -- @realm shared + -- @treturn string Flags this character has represented as one string. You can access individual flags by iterating through + -- the string letter by letter + function character:GetFlags() + return self:GetData("f", "") + end + + --- Returns `true` if the character has the given flag(s). + -- @realm shared + -- @string flags Flag(s) to check access for + -- @treturn bool Whether or not this character has access to the given flag(s) + function character:HasFlags(flags) + local bHasFlag = hook.Run("CharacterHasFlags", self, flags) + + if (bHasFlag == true) then + return true + end + + local flagList = self:GetFlags() + + for i = 1, #flags do + if (flagList:find(flags[i], 1, true)) then + return true + end + end + + return false + end +end + +do + ix.flag.Add("p", "Access to the physgun.", function(client, isGiven) + if (isGiven) then + client:Give("weapon_physgun") + client:SelectWeapon("weapon_physgun") + else + client:StripWeapon("weapon_physgun") + end + end) + + ix.flag.Add("t", "Access to the toolgun", function(client, isGiven) + if (isGiven) then + client:Give("gmod_tool") + client:SelectWeapon("gmod_tool") + else + client:StripWeapon("gmod_tool") + end + end) + + ix.flag.Add("c", "Access to spawn chairs.") + ix.flag.Add("C", "Access to spawn vehicles.") + ix.flag.Add("r", "Access to spawn ragdolls.") + ix.flag.Add("e", "Access to spawn props.") + ix.flag.Add("n", "Access to spawn NPCs.") +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_inventory.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_inventory.lua new file mode 100644 index 0000000..07eea3d --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_inventory.lua @@ -0,0 +1,172 @@ + +--[[-- +Inventory manipulation and helper functions. +]] +-- @module ix.inventory + +ix.inventory = ix.inventory or {} + +ix.util.Include("helix/gamemode/core/meta/sh_inventory.lua") + +--- Retrieves an inventory table. +-- @realm shared +-- @number invID Index of the inventory +-- @treturn Inventory Inventory table +function ix.inventory.Get(invID) + return ix.item.inventories[invID] +end + +function ix.inventory.Create(width, height, id) + local inventory = ix.meta.inventory:New(id, width, height) + ix.item.inventories[id] = inventory + return inventory +end + +--- Loads an inventory and associated items from the database into memory. If you are passing a table into `invID`, it +-- requires a table where the key is the inventory ID, and the value is a table of the width and height values. See below +-- for an example. +-- @realm server +-- @param invID Inventory ID or table of inventory IDs +-- @number width Width of inventory (this is not used when passing a table to `invID`) +-- @number height Height of inventory (this is not used when passing a table to `invID`) +-- @func callback Function to call when inventory is restored +-- @usage ix.inventory.Restore({ +-- [10] = {5, 5}, +-- [11] = {7, 4} +-- }) +-- -- inventories 10 and 11 with sizes (5, 5) and (7, 4) will be loaded +function ix.inventory.Restore(invID, width, height, callback) + local inventories = {} + + if (!istable(invID)) then + if (!isnumber(invID) or invID < 0) then + error("Attempt to restore inventory with an invalid ID!") + end + + inventories[invID] = {width, height} + ix.inventory.Create(width, height, invID) + else + for k, v in pairs(invID) do + inventories[k] = {v[1], v[2]} + ix.inventory.Create(v[1], v[2], k) + end + end + + local query = mysql:Select("ix_items") + query:Select("item_id") + query:Select("inventory_id") + query:Select("unique_id") + query:Select("data") + query:Select("character_id") + query:Select("player_id") + query:Select("x") + query:Select("y") + query:WhereIn("inventory_id", table.GetKeys(inventories)) + query:Callback(function(result) + if (istable(result) and #result > 0) then + local invSlots = {} + + for _, item in ipairs(result) do + local itemInvID = tonumber(item.inventory_id) + local invInfo = inventories[itemInvID] + + if (!itemInvID or !invInfo) then + -- don't restore items with an invalid inventory id or type + continue + end + + local inventory = ix.item.inventories[itemInvID] + local x, y = tonumber(item.x), tonumber(item.y) + local itemID = tonumber(item.item_id) + local data = util.JSONToTable(item.data or "[]") + local characterID, playerID = tonumber(item.character_id), tostring(item.player_id) + + if (x and y and itemID) then + if (x <= inventory.w and x > 0 and y <= inventory.h and y > 0) then + local item2 = ix.item.New(item.unique_id, itemID) + + if (item2) then + invSlots[itemInvID] = invSlots[itemInvID] or {} + local slots = invSlots[itemInvID] + + item2.data = {} + + if (data) then + item2.data = data + end + + item2.gridX = x + item2.gridY = y + item2.invID = itemInvID + item2.characterID = characterID + item2.playerID = (playerID == "" or playerID == "NULL") and nil or playerID + + for x2 = 0, item2.width - 1 do + for y2 = 0, item2.height - 1 do + slots[x + x2] = slots[x + x2] or {} + slots[x + x2][y + y2] = item2 + end + end + + if (item2.OnRestored) then + item2:OnRestored(item2, itemInvID) + end + end + end + end + end + + for k, v in pairs(invSlots) do + ix.item.inventories[k].slots = v + end + end + + if (callback) then + for k, _ in pairs(inventories) do + callback(ix.item.inventories[k]) + end + end + end) + query:Execute() +end + +function ix.inventory.New(owner, invType, callback) + local invData = ix.item.inventoryTypes[invType] or {w = 1, h = 1} + + local query = mysql:Insert("ix_inventories") + query:Insert("inventory_type", invType) + query:Insert("character_id", owner) + query:Callback(function(result, status, lastID) + local inventory = ix.inventory.Create(invData.w, invData.h, lastID) + + if (invType) then + inventory.vars.isBag = invType + end + + if (isnumber(owner) and owner > 0) then + local character = ix.char.loaded[owner] + local client = character:GetPlayer() + + inventory:SetOwner(owner) + + if (IsValid(client)) then + inventory:Sync(client) + end + end + + if (callback) then + callback(inventory) + end + end) + query:Execute() +end + +function ix.inventory.Register(invType, w, h, isBag) + ix.item.inventoryTypes[invType] = {w = w, h = h} + + if (isBag) then + ix.item.inventoryTypes[invType].isBag = invType + end + + return ix.item.inventoryTypes[invType] +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_item.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_item.lua new file mode 100644 index 0000000..7256c1d --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_item.lua @@ -0,0 +1,841 @@ +--[[-- +Item manipulation and helper functions. +]] +-- @module ix.item + +ix.item = ix.item or {} +ix.item.list = ix.item.list or {} +ix.item.base = ix.item.base or {} +ix.item.instances = ix.item.instances or {} +ix.item.inventories = ix.item.inventories or { + [0] = {} +} +ix.item.inventoryTypes = ix.item.inventoryTypes or {} + +ix.util.Include("helix/gamemode/core/meta/sh_item.lua") + +-- Declare some supports for logic inventory +local zeroInv = ix.item.inventories[0] + +function zeroInv:GetID() + return 0 +end + +function zeroInv:OnCheckAccess(client) + return true +end + +-- WARNING: You have to manually sync the data to client if you're trying to use item in the logical inventory in the vgui. +function zeroInv:Add(uniqueID, quantity, data, x, y) + quantity = quantity or 1 + + if (quantity > 0) then + if (!isnumber(uniqueID)) then + if (quantity > 1) then + for _ = 1, quantity do + self:Add(uniqueID, 1, data) + end + + return + end + + local itemTable = ix.item.list[uniqueID] + + if (!itemTable) then + return false, "invalidItem" + end + + ix.item.Instance(0, uniqueID, data, x, y, function(item) + self[item:GetID()] = item + end) + + return nil, nil, 0 + end + else + return false, "notValid" + end +end + +function ix.item.Instance(index, uniqueID, itemData, x, y, callback, characterID, playerID) + if (!uniqueID or ix.item.list[uniqueID]) then + itemData = istable(itemData) and itemData or {} + + local query = mysql:Insert("ix_items") + query:Insert("inventory_id", index) + query:Insert("unique_id", uniqueID) + query:Insert("data", util.TableToJSON(itemData)) + query:Insert("x", x) + query:Insert("y", y) + + if (characterID) then + query:Insert("character_id", characterID) + end + + if (playerID) then + query:Insert("player_id", playerID) + end + + query:Callback(function(result, status, lastID) + local item = ix.item.New(uniqueID, lastID) + + if (item) then + item.data = table.Copy(itemData) + item.invID = index + item.characterID = characterID + item.playerID = playerID + + if (callback) then + callback(item) + end + + if (item.OnInstanced) then + item:OnInstanced(index, x, y, item) + end + end + end) + query:Execute() + else + ErrorNoHalt("[Helix] Attempt to give an invalid item! (" .. (uniqueID or "nil") .. ")\n") + end +end + +--- Retrieves an item table. +-- @realm shared +-- @string identifier Unique ID of the item +-- @treturn item Item table +-- @usage print(ix.item.Get("example")) +-- > "item[example][0]" +function ix.item.Get(identifier) + return ix.item.base[identifier] or ix.item.list[identifier] +end + +function ix.item.Load(path, baseID, isBaseItem) + local uniqueID = path:match("sh_([_%w]+)%.lua") + + if (uniqueID) then + uniqueID = (isBaseItem and "base_" or "")..uniqueID + ix.item.Register(uniqueID, baseID, isBaseItem, path) + else + if (!path:find(".txt")) then + ErrorNoHalt("[Helix] Item at '"..path.."' follows invalid naming convention!\n") + end + end +end + +function ix.item.Register(uniqueID, baseID, isBaseItem, path, luaGenerated) + local meta = ix.meta.item + + if (uniqueID) then + ITEM = (isBaseItem and ix.item.base or ix.item.list)[uniqueID] or setmetatable({}, meta) + ITEM.uniqueID = uniqueID + ITEM.base = baseID or ITEM.base + ITEM.isBase = isBaseItem or false + ITEM.hooks = ITEM.hooks or {} + ITEM.postHooks = ITEM.postHooks or {} + ITEM.functions = ITEM.functions or {} + ITEM.functions.drop = ITEM.functions.drop or { + tip = "dropTip", + icon = "icon16/world.png", + OnRun = function(item) + local bSuccess, error = item:Transfer(nil, nil, nil, item.player) + + if (!bSuccess and isstring(error)) then + item.player:NotifyLocalized(error) + else + item.player:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1) + end + + return false + end, + OnCanRun = function(item) + return !IsValid(item.entity) and !item.noDrop + end + } + ITEM.functions.take = ITEM.functions.take or { + tip = "takeTip", + icon = "icon16/box.png", + OnRun = function(item) + local client = item.player + local bSuccess, error = item:Transfer(client:GetCharacter():GetInventory():GetID(), nil, nil, client) + + if (!bSuccess) then + client:NotifyLocalized(error or "unknownError") + return false + else + client:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1) + + if (item.data) then -- I don't like it but, meh... + for k, v in pairs(item.data) do + item:SetData(k, v) + end + end + end + + return true + end, + OnCanRun = function(item) + return IsValid(item.entity) + end + } + + local oldBase = ITEM.base + + if (ITEM.base) then + local baseTable = ix.item.base[ITEM.base] + + if (baseTable) then + for k, v in pairs(baseTable) do + if (ITEM[k] == nil) then + ITEM[k] = v + end + + ITEM.baseTable = baseTable + end + + local mergeTable = table.Copy(baseTable) + ITEM = table.Merge(mergeTable, ITEM) + else + ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n") + end + end + + if (PLUGIN) then + ITEM.plugin = PLUGIN.uniqueID + end + + if (!luaGenerated and path) then + ix.util.Include(path) + end + + if (ITEM.base and oldBase != ITEM.base) then + local baseTable = ix.item.base[ITEM.base] + + if (baseTable) then + for k, v in pairs(baseTable) do + if (ITEM[k] == nil) then + ITEM[k] = v + end + + ITEM.baseTable = baseTable + end + + local mergeTable = table.Copy(baseTable) + ITEM = table.Merge(mergeTable, ITEM) + else + ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n") + end + end + + ITEM.description = ITEM.description or "noDesc" + ITEM.width = ITEM.width or 1 + ITEM.height = ITEM.height or 1 + ITEM.category = ITEM.category or "misc" + + if (ITEM.OnRegistered) then + ITEM:OnRegistered() + end + + (isBaseItem and ix.item.base or ix.item.list)[ITEM.uniqueID] = ITEM + + if (IX_RELOADED) then + -- we don't know which item was actually edited, so we'll refresh all of them + for _, v in pairs(ix.item.instances) do + if (v.uniqueID == uniqueID) then + ix.util.MetatableSafeTableMerge(v, ITEM) + end + end + end + if (luaGenerated) then + return ITEM + else + ITEM = nil + end + else + ErrorNoHalt("[Helix] You tried to register an item without uniqueID!\n") + end +end + +function ix.item.LoadFromDir(directory) + local files, folders + + files = file.Find(directory.."/base/*.lua", "LUA") + + for _, v in ipairs(files) do + ix.item.Load(directory.."/base/"..v, nil, true) + end + + files, folders = file.Find(directory.."/*", "LUA") + + for _, v in ipairs(folders) do + if (v == "base") then + continue + end + + for _, v2 in ipairs(file.Find(directory.."/"..v.."/*.lua", "LUA")) do + ix.item.Load(directory.."/"..v .. "/".. v2, "base_"..v) + end + end + + for _, v in ipairs(files) do + ix.item.Load(directory.."/"..v) + end +end + +function ix.item.New(uniqueID, id) + if (ix.item.instances[id] and ix.item.instances[id].uniqueID == uniqueID) then + return ix.item.instances[id] + end + + local stockItem = ix.item.list[uniqueID] + + if (stockItem) then + local item = setmetatable({id = id, data = {}}, { + __index = stockItem, + __eq = stockItem.__eq, + __tostring = stockItem.__tostring + }) + + ix.item.instances[id] = item + + return item + else + ErrorNoHalt("[Helix] Attempt to index unknown item '"..uniqueID.."'\n") + end +end + +do + function ix.item.GetInv(invID) + ErrorNoHalt("ix.item.GetInv is deprecated. Use ix.inventory.Get instead!\n") + return ix.inventory.Get(invID) + end + + function ix.item.RegisterInv(invType, w, h, isBag) + ErrorNoHalt("ix.item.RegisterInv is deprecated. Use ix.inventory.Register instead!\n") + return ix.inventory.Register(invType, w, h, isBag) + end + + function ix.item.NewInv(owner, invType, callback) + ErrorNoHalt("ix.item.NewInv is deprecated. Use ix.inventory.New instead!\n") + return ix.inventory.New(owner, invType, callback) + end + + function ix.item.CreateInv(width, height, id) + ErrorNoHalt("ix.item.CreateInv is deprecated. Use ix.inventory.Create instead!\n") + return ix.inventory.Create(width, height, id) + end + + function ix.item.RestoreInv(invID, width, height, callback) + ErrorNoHalt("ix.item.RestoreInv is deprecated. Use ix.inventory.Restore instead!\n") + return ix.inventory.Restore(invID, width, height, callback) + end + + if (CLIENT) then + net.Receive("ixInventorySync", function() + local slots = net.ReadTable() + local id = net.ReadUInt(32) + local w, h = net.ReadUInt(6), net.ReadUInt(6) + local owner = net.ReadType() + local vars = net.ReadTable() + + if (!LocalPlayer():GetCharacter()) then + return + end + + local character = owner and ix.char.loaded[owner] + local inventory = ix.inventory.Create(w, h, id) + inventory.slots = {} + inventory.vars = vars + + local x, y + + for _, v in ipairs(slots) do + x, y = v[1], v[2] + + inventory.slots[x] = inventory.slots[x] or {} + + local item = ix.item.New(v[3], v[4]) + + item.data = {} + if (v[5]) then + item.data = v[5] + end + + item.invID = item.invID or id + inventory.slots[x][y] = item + end + + if (character) then + inventory:SetOwner(character:GetID()) + character.vars.inv = character.vars.inv or {} + + for k, v in ipairs(character:GetInventory(true)) do + if (v:GetID() == id) then + character:GetInventory(true)[k] = inventory + + return + end + end + + table.insert(character.vars.inv, inventory) + end + end) + + net.Receive("ixInventoryData", function() + local id = net.ReadUInt(32) + local item = ix.item.instances[id] + + if (item) then + local key = net.ReadString() + local value = net.ReadType() + + item.data = item.data or {} + item.data[key] = value + + local invID = item.invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or item.invID + local panel = ix.gui["inv" .. invID] + + if (panel and panel.panels) then + local icon = panel.panels[id] + + if (icon) then + icon:SetHelixTooltip(function(tooltip) + ix.hud.PopulateItemTooltip(tooltip, item) + end) + end + end + end + end) + + net.Receive("ixInventorySet", function() + local invID = net.ReadUInt(32) + local x, y = net.ReadUInt(6), net.ReadUInt(6) + local uniqueID = net.ReadString() + local id = net.ReadUInt(32) + local owner = net.ReadUInt(32) + local data = net.ReadTable() + + local character = owner != 0 and ix.char.loaded[owner] or LocalPlayer():GetCharacter() + + if (character) then + local inventory = ix.item.inventories[invID] + + if (inventory) then + local item = (uniqueID != "" and id != 0) and ix.item.New(uniqueID, id) or nil + item.invID = invID + item.data = {} + + if (data) then + item.data = data + end + + inventory.slots[x] = inventory.slots[x] or {} + inventory.slots[x][y] = item + + invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID + + local panel = ix.gui["inv" .. invID] + + if (IsValid(panel)) then + local icon = panel:AddIcon( + item:GetModel() or "models/props_junk/popcan01a.mdl", x, y, item.width, item.height, item:GetSkin() + ) + + if (IsValid(icon)) then + icon:SetHelixTooltip(function(tooltip) + ix.hud.PopulateItemTooltip(tooltip, item) + end) + + icon.itemID = item.id + panel.panels[item.id] = icon + end + end + end + end + end) + + net.Receive("ixInventoryMove", function() + local invID = net.ReadUInt(32) + local inventory = ix.item.inventories[invID] + + if (!inventory) then + return + end + + local itemID = net.ReadUInt(32) + local oldX = net.ReadUInt(6) + local oldY = net.ReadUInt(6) + local x = net.ReadUInt(6) + local y = net.ReadUInt(6) + + invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID + + local item = ix.item.instances[itemID] + local panel = ix.gui["inv" .. invID] + + -- update inventory UI if it's open + if (IsValid(panel)) then + local icon = panel.panels[itemID] + + if (IsValid(icon)) then + icon:Move(x, y, panel, true) + end + end + + -- update inventory slots + if (item) then + inventory.slots[oldX][oldY] = nil + + inventory.slots[x] = inventory.slots[x] or {} + inventory.slots[x][y] = item + end + end) + + net.Receive("ixInventoryRemove", function() + local id = net.ReadUInt(32) + local invID = net.ReadUInt(32) + + local inventory = ix.item.inventories[invID] + + if (!inventory) then + return + end + + inventory:Remove(id) + + invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID + local panel = ix.gui["inv" .. invID] + + if (IsValid(panel)) then + local icon = panel.panels[id] + + if (IsValid(icon)) then + for _, v in ipairs(icon.slots or {}) do + if (v.item == icon) then + v.item = nil + end + end + + icon:Remove() + end + end + + local item = ix.item.instances[id] + + if (!item) then + return + end + + -- we need to close any bag windows that are open because of this item + if (item.isBag) then + local itemInv = item:GetInventory() + + if (itemInv) then + local frame = ix.gui["inv" .. itemInv:GetID()] + + if (IsValid(frame)) then + frame:Remove() + end + end + end + end) + else + util.AddNetworkString("ixInventorySync") + util.AddNetworkString("ixInventorySet") + util.AddNetworkString("ixInventoryMove") + util.AddNetworkString("ixInventoryRemove") + util.AddNetworkString("ixInventoryData") + util.AddNetworkString("ixInventoryAction") + + function ix.item.LoadItemByID(itemIndex, recipientFilter) + local query = mysql:Select("ix_items") + query:Select("item_id") + query:Select("unique_id") + query:Select("data") + query:Select("character_id") + query:Select("player_id") + query:WhereIn("item_id", itemIndex) + query:Callback(function(result) + if (istable(result)) then + for _, v in ipairs(result) do + local itemID = tonumber(v.item_id) + local data = util.JSONToTable(v.data or "[]") + local uniqueID = v.unique_id + local itemTable = ix.item.list[uniqueID] + local characterID = tonumber(v.character_id) + local playerID = tostring(v.player_id) + + if (itemTable and itemID) then + local item = ix.item.New(uniqueID, itemID) + + item.data = data or {} + item.invID = 0 + item.characterID = characterID + item.playerID = (playerID == "" or playerID == "NULL") and nil or playerID + end + end + end + end) + query:Execute() + end + + function ix.item.PerformInventoryAction(client, action, item, invID, data) + local character = client:GetCharacter() + + if (!character) then + return + end + + local inventory = ix.item.inventories[invID or 0] + + if (hook.Run("CanPlayerInteractItem", client, action, item, data) == false) then + return + end + + if (!inventory:OnCheckAccess(client)) then + return + end + + if (isentity(item)) then + if (IsValid(item)) then + local entity = item + local itemID = item.ixItemID + item = ix.item.instances[itemID] + + if (!item) then + return + end + + item.entity = entity + item.player = client + else + return + end + elseif (isnumber(item)) then + item = ix.item.instances[item] + + if (!item) then + return + end + + item.player = client + end + + if (item.entity) then + if (item.entity:GetPos():Distance(client:GetPos()) > 96) then + return + end + elseif (!inventory:GetItemByID(item.id)) then + return + end + + if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then + local itemPlayerID = item:GetPlayerID() + local itemCharacterID = item:GetCharacterID() + local playerID = client:SteamID64() + local characterID = client:GetCharacter():GetID() + + if (itemPlayerID and itemCharacterID and itemPlayerID == playerID and itemCharacterID != characterID) then + client:NotifyLocalized("itemOwned") + + item.player = nil + item.entity = nil + return + end + end + + local callback = item.functions[action] + + if (callback) then + if (callback.OnCanRun and callback.OnCanRun(item, data) == false) then + item.entity = nil + item.player = nil + + return + end + + hook.Run("PlayerInteractItem", client, action, item) + + local entity = item.entity + local result + + if (item.hooks[action]) then + result = item.hooks[action](item, data) + end + + if (result == nil) then + result = callback.OnRun(item, data) + end + + if (item.postHooks[action]) then + -- Posthooks shouldn't override the result from OnRun + item.postHooks[action](item, result, data) + end + + if (result != false) then + if (IsValid(entity)) then + entity.ixIsSafe = true + entity:Remove() + else + item:Remove() + end + end + + item.entity = nil + item.player = nil + + return result != false + end + end + + local function NetworkInventoryMove(receiver, invID, itemID, oldX, oldY, x, y) + net.Start("ixInventoryMove") + net.WriteUInt(invID, 32) + net.WriteUInt(itemID, 32) + net.WriteUInt(oldX, 6) + net.WriteUInt(oldY, 6) + net.WriteUInt(x, 6) + net.WriteUInt(y, 6) + net.Send(receiver) + end + + net.Receive("ixInventoryMove", function(length, client) + local oldX, oldY, x, y = net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6) + local invID, newInvID = net.ReadUInt(32), net.ReadUInt(32) + + local character = client:GetCharacter() + + if (character) then + local inventory = ix.item.inventories[invID] + + if (!inventory or inventory == nil) then + inventory:Sync(client) + end + + if ((inventory.owner and inventory.owner == character:GetID()) or inventory:OnCheckAccess(client)) then + local item = inventory:GetItemAt(oldX, oldY) + + if (item) then + if (newInvID and invID != newInvID) then + local inventory2 = ix.item.inventories[newInvID] + + if (inventory2) then + local bStatus, error = item:Transfer(newInvID, x, y, client) + + if (!bStatus) then + NetworkInventoryMove( + client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY + ) + + client:NotifyLocalized(error or "unknownError") + end + end + + return + end + + if (inventory:CanItemFit(x, y, item.width, item.height, item)) then + item.gridX = x + item.gridY = y + + for x2 = 0, item.width - 1 do + for y2 = 0, item.height - 1 do + local previousX = inventory.slots[oldX + x2] + + if (previousX) then + previousX[oldY + y2] = nil + end + end + end + + for x2 = 0, item.width - 1 do + for y2 = 0, item.height - 1 do + inventory.slots[x + x2] = inventory.slots[x + x2] or {} + inventory.slots[x + x2][y + y2] = item + end + end + + local receivers = inventory:GetReceivers() + + if (istable(receivers)) then + local filtered = {} + + for _, v in ipairs(receivers) do + if (v != client) then + filtered[#filtered + 1] = v + end + end + + if (#filtered > 0) then + NetworkInventoryMove( + filtered, invID, item:GetID(), oldX, oldY, x, y + ) + end + end + + if (!inventory.noSave) then + local query = mysql:Update("ix_items") + query:Update("x", x) + query:Update("y", y) + query:Where("item_id", item.id) + query:Execute() + end + else + NetworkInventoryMove( + client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY + ) + end + end + else + local item = inventory:GetItemAt(oldX, oldY) + + if (item) then + NetworkInventoryMove( + client, item.invID, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY + ) + end + end + end + end) + + net.Receive("ixInventoryAction", function(length, client) + ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadUInt(32), net.ReadUInt(32), net.ReadTable()) + end) + end + + --- Instances and spawns a given item type. + -- @realm server + -- @string uniqueID Unique ID of the item + -- @vector position The position in which the item's entity will be spawned + -- @func[opt=nil] callback Function to call when the item entity is created + -- @angle[opt=angle_zero] angles The angles at which the item's entity will spawn + -- @tab[opt=nil] data Additional data for this item instance + function ix.item.Spawn(uniqueID, position, callback, angles, data) + ix.item.Instance(0, uniqueID, data or {}, 1, 1, function(item) + local entity = item:Spawn(position, angles) + + if (callback) then + callback(item, entity) + end + end) + end +end + +--- Inventory util functions for character +-- @classmod Character + +--- Returns this character's associated `Inventory` object. +-- @function GetInventory +-- @realm shared +-- @treturn Inventory This character's inventory +ix.char.RegisterVar("Inventory", { + bNoNetworking = true, + bNoDisplay = true, + OnGet = function(character, index) + if (index and !isnumber(index)) then + return character.vars.inv or {} + end + + return character.vars.inv and character.vars.inv[index or 1] + end, + alias = "Inv" +}) diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_language.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_language.lua new file mode 100644 index 0000000..66d918a --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_language.lua @@ -0,0 +1,108 @@ + +--[[-- +Multi-language phrase support. + +Helix has support for multiple languages, and you can easily leverage this system for use in your own schema, plugins, etc. +Languages will be loaded from the schema and any plugins in `languages/sh_languagename.lua`, where `languagename` is the id of a +language (`english` for English, `french` for French, etc). The structure of a language file is a table of phrases with the key +as its phrase ID and the value as its translation for that language. For example, in `plugins/area/languages/sh_english.lua`: + LANGUAGE = { + area = "Area", + areas = "Areas", + areaEditMode = "Area Edit Mode", + -- etc. + } + +The phrases defined in these language files can be used with the `L` global function: + print(L("areaEditMode")) + > Area Edit Mode + +All phrases are formatted with `string.format`, so if you wish to add some info in a phrase you can use standard Lua string +formatting arguments: + print(L("areaDeleteConfirm", "Test")) + > Are you sure you want to delete the area "Test"? + +Phrases are also usable on the server, but only when trying to localize a phrase based on a client's preferences. The server +does not have a set language. An example: + Entity(1):ChatPrint(L("areaEditMode")) + > -- "Area Edit Mode" will print in the player's chatbox +]] +-- @module ix.lang + +ix.lang = ix.lang or {} +ix.lang.stored = ix.lang.stored or {} +ix.lang.names = ix.lang.names or {} + +--- Loads language files from a directory. +-- @realm shared +-- @internal +-- @string directory Directory to load language files from +function ix.lang.LoadFromDir(directory) + for _, v in ipairs(file.Find(directory.."/sh_*.lua", "LUA")) do + local niceName = v:sub(4, -5):lower() + + ix.util.Include(directory.."/"..v, "shared") + + if (LANGUAGE) then + if (NAME) then + ix.lang.names[niceName] = NAME + NAME = nil + end + + ix.lang.AddTable(niceName, LANGUAGE) + LANGUAGE = nil + end + end +end + +--- Adds phrases to a language. This is used when you aren't adding entries through the files in the `languages/` folder. A +-- common use case is adding language phrases in a single-file plugin. +-- @realm shared +-- @string language The ID of the language +-- @tab data Language data to add to the given language +-- @usage ix.lang.AddTable("english", { +-- myPhrase = "My Phrase" +-- }) +function ix.lang.AddTable(language, data) + language = tostring(language):lower() + ix.lang.stored[language] = table.Merge(ix.lang.stored[language] or {}, data) +end + +if (SERVER) then + -- luacheck: globals L + function L(key, client, ...) + local languages = ix.lang.stored + local langKey = ix.option.Get(client, "language", "english") + local info = languages[langKey] or languages.english + + return string.format(info and info[key] or languages.english[key] or key, ...) + end + + -- luacheck: globals L2 + function L2(key, client, ...) + local languages = ix.lang.stored + local langKey = ix.option.Get(client, "language", "english") + local info = languages[langKey] or languages.english + + if (info and info[key]) then + return string.format(info[key], ...) + end + end +else + function L(key, ...) + local languages = ix.lang.stored + local langKey = ix.option.Get("language", "english") + local info = languages[langKey] or languages.english + + return string.format(info and info[key] or languages.english[key] or key, ...) + end + + function L2(key, ...) + local langKey = ix.option.Get("language", "english") + local info = ix.lang.stored[langKey] + + if (info and info[key]) then + return string.format(info[key], ...) + end + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_log.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_log.lua new file mode 100644 index 0000000..1578c45 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_log.lua @@ -0,0 +1,166 @@ + +--[[-- +Logging helper functions. + +Predefined flags: + FLAG_NORMAL + FLAG_SUCCESS + FLAG_WARNING + FLAG_DANGER + FLAG_SERVER + FLAG_DEV +]] +-- @module ix.log + +-- luacheck: globals FLAG_NORMAL FLAG_SUCCESS FLAG_WARNING FLAG_DANGER FLAG_SERVER FLAG_DEV +FLAG_NORMAL = 0 +FLAG_SUCCESS = 1 +FLAG_WARNING = 2 +FLAG_DANGER = 3 +FLAG_SERVER = 4 +FLAG_DEV = 5 + +ix.log = ix.log or {} +ix.log.color = { + [FLAG_NORMAL] = Color(200, 200, 200), + [FLAG_SUCCESS] = Color(50, 200, 50), + [FLAG_WARNING] = Color(255, 255, 0), + [FLAG_DANGER] = Color(255, 50, 50), + [FLAG_SERVER] = Color(200, 200, 220), + [FLAG_DEV] = Color(200, 200, 220), +} + +CAMI.RegisterPrivilege({ + Name = "Helix - Logs", + MinAccess = "admin" +}) + +local consoleColor = Color(50, 200, 50) + +if (SERVER) then + if (!ix.db) then + include("sv_database.lua") + end + + util.AddNetworkString("ixLogStream") + + function ix.log.LoadTables() + ix.log.CallHandler("Load") + end + + ix.log.types = ix.log.types or {} + + --- Adds a log type + -- @realm server + -- @string logType Log category + -- @string format The string format that log messages should use + -- @number flag Log level + function ix.log.AddType(logType, format, flag) + ix.log.types[logType] = {format = format, flag = flag} + end + + function ix.log.Parse(client, logType, ...) + local info = ix.log.types[logType] + + if (!info) then + ErrorNoHalt("attempted to add entry to non-existent log type \"" .. tostring(logType) .. "\"\n") + local fallback = logType.." : " + if (client) then + fallback = fallback..client:Name().." - " + end + for _, v in ipairs({...}) do + fallback = fallback..tostring(v).." " + end + return fallback, FLAG_WARNING + end + + local text = info and info.format + + if (text) then + if (isfunction(text)) then + text = text(client, ...) + end + else + text = -1 + end + + return text, info.flag + end + + function ix.log.AddRaw(logString, bNoSave) + CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers) + ix.log.Send(receivers, logString) + end) + + Msg("[LOG] ", logString .. "\n") + + if (!bNoSave) then + ix.log.CallHandler("Write", nil, logString) + end + end + + --- Add a log message + -- @realm server + -- @player client Player who instigated the log + -- @string logType Log category + -- @param ... Arguments to pass to the log + function ix.log.Add(client, logType, ...) + local logString, logFlag = ix.log.Parse(client, logType, ...) + if (logString == -1) then return end + + CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers) + ix.log.Send(receivers, logString, logFlag) + end) + + Msg("[LOG] ", logString .. "\n") + + ix.log.CallHandler("Write", client, logString, logFlag, logType, {...}) + end + + function ix.log.Send(client, logString, flag) + net.Start("ixLogStream") + net.WriteString(logString) + net.WriteUInt(flag or 0, 4) + net.Send(client) + end + + ix.log.handlers = ix.log.handlers or {} + function ix.log.CallHandler(event, ...) + for _, v in pairs(ix.log.handlers) do + if (isfunction(v[event])) then + v[event](...) + end + end + end + + function ix.log.RegisterHandler(name, data) + data.name = string.gsub(name, "%s", "") + name = name:lower() + data.uniqueID = name + + ix.log.handlers[name] = data + end + + do + local HANDLER = {} + + function HANDLER.Load() + file.CreateDir("helix/logs") + end + + function HANDLER.Write(client, message) + file.Append("helix/logs/" .. os.date("%x"):gsub("/", "-") .. ".txt", "[" .. os.date("%X") .. "]\t" .. message .. "\r\n") + end + + ix.log.RegisterHandler("File", HANDLER) + end +else + net.Receive("ixLogStream", function(length) + local logString = net.ReadString() + local flag = net.ReadUInt(4) + + if (isstring(logString) and isnumber(flag)) then + MsgC(consoleColor, "[SERVER] ", ix.log.color[flag], logString .. "\n") + end + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_menu.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_menu.lua new file mode 100644 index 0000000..143d204 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_menu.lua @@ -0,0 +1,109 @@ + +--[[-- +Entity menu manipulation. + +The `menu` library allows you to open up a context menu of arbitrary options whose callbacks will be ran when they are selected +from the panel that shows up for the player. +]] +-- @module ix.menu + +--- You'll need to pass a table of options to `ix.menu.Open` to populate the menu. This table consists of strings as its keys +-- and functions as its values. These correspond to the text displayed in the menu and the callback to run, respectively. +-- +-- Example usage: +-- ix.menu.Open({ +-- Drink = function() +-- print("Drink option selected!") +-- end, +-- Take = function() +-- print("Take option selected!") +-- end +-- }, ents.GetByIndex(1)) +-- This opens a menu with the options `"Drink"` and `"Take"` which will print a message when you click on either of the options. +-- @realm client +-- @table MenuOptionsStructure + +ix.menu = ix.menu or {} + +if (CLIENT) then + --- Opens up a context menu for the given entity. + -- @realm client + -- @tparam MenuOptionsStructure options Data describing what options to display + -- @entity[opt] entity Entity to send commands to + -- @treturn boolean Whether or not the menu opened successfully. It will fail when there is already a menu open. + function ix.menu.Open(options, entity) + if (IsValid(ix.menu.panel)) then + return false + end + + local panel = vgui.Create("ixEntityMenu") + panel:SetEntity(entity) + panel:SetOptions(options) + + return true + end + + --- Checks whether or not an entity menu is currently open. + -- @realm client + -- @treturn boolean Whether or not an entity menu is open + function ix.menu.IsOpen() + return IsValid(ix.menu.panel) + end + + --- Notifies the server of an option that was chosen for the given entity. + -- @realm client + -- @entity entity Entity to call option on + -- @string choice Option that was chosen + -- @param data Extra data to send to the entity + function ix.menu.NetworkChoice(entity, choice, data) + if (IsValid(entity)) then + net.Start("ixEntityMenuSelect") + net.WriteEntity(entity) + net.WriteString(choice) + net.WriteType(data) + net.SendToServer() + end + end +else + util.AddNetworkString("ixEntityMenuSelect") + + net.Receive("ixEntityMenuSelect", function(length, client) + local entity = net.ReadEntity() + local option = net.ReadString() + local data = net.ReadType() + + if (!IsValid(entity) or !isstring(option) or + hook.Run("CanPlayerInteractEntity", client, entity, option, data) == false or + entity:GetPos():Distance(client:GetPos()) > 96) then + return + end + + hook.Run("PlayerInteractEntity", client, entity, option, data) + + local callbackName = "OnSelect" .. option:gsub("%s", "") + + if (entity[callbackName]) then + entity[callbackName](entity, client, data) + else + entity:OnOptionSelected(client, option, data) + end + end) +end + +do + local PLAYER = FindMetaTable("Player") + + if (CLIENT) then + function PLAYER:GetEntityMenu() + local options = {} + + hook.Run("GetPlayerEntityMenu", self, options) + return options + end + else + function PLAYER:OnOptionSelected(client, option) + hook.Run("OnPlayerOptionSelected", self, client, option) + end + end +end + diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_notice.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_notice.lua new file mode 100644 index 0000000..47409f5 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_notice.lua @@ -0,0 +1,164 @@ + +--- Notification helper functions +-- @module ix.notice + +if (SERVER) then + util.AddNetworkString("ixNotify") + util.AddNetworkString("ixNotifyLocalized") + + --- Sends a notification to a specified recipient. + -- @realm server + -- @string message Message to notify + -- @player[opt=nil] recipient Player to be notified + function ix.util.Notify(message, recipient) + net.Start("ixNotify") + net.WriteString(message) + + if (recipient == nil) then + net.Broadcast() + else + net.Send(recipient) + end + end + + --- Sends a translated notification to a specified recipient. + -- @realm server + -- @string message Message to notify + -- @player[opt=nil] recipient Player to be notified + -- @param ... Arguments to pass to the translated message + function ix.util.NotifyLocalized(message, recipient, ...) + net.Start("ixNotifyLocalized") + net.WriteString(message) + net.WriteTable({...}) + + if (recipient == nil) then + net.Broadcast() + else + net.Send(recipient) + end + end + + do + --- Notification util functions for players + -- @classmod Player + + local playerMeta = FindMetaTable("Player") + + --- Displays a prominent notification in the top-right of this player's screen. + -- @realm shared + -- @string message Text to display in the notification + function playerMeta:Notify(message) + ix.util.Notify(message, self) + end + + --- Displays a notification for this player with the given language phrase. + -- @realm shared + -- @string message ID of the phrase to display to the player + -- @param ... Arguments to pass to the phrase + -- @usage client:NotifyLocalized("mapRestarting", 10) + -- -- displays "The map will restart in 10 seconds!" if the player's language is set to English + -- @see ix.lang + function playerMeta:NotifyLocalized(message, ...) + ix.util.NotifyLocalized(message, self, ...) + end + + --- Displays a notification for this player in the chatbox. + -- @realm shared + -- @string message Text to display in the notification + function playerMeta:ChatNotify(message) + local messageLength = message:utf8len() + + ix.chat.Send(nil, "notice", message, false, {self}, { + bError = message:utf8sub(messageLength, messageLength) == "!" + }) + end + + --- Displays a notification for this player in the chatbox with the given language phrase. + -- @realm shared + -- @string message ID of the phrase to display to the player + -- @param ... Arguments to pass to the phrase + -- @see NotifyLocalized + function playerMeta:ChatNotifyLocalized(message, ...) + message = L(message, self, ...) + + local messageLength = message:utf8len() + + ix.chat.Send(nil, "notice", message, false, {self}, { + bError = message:utf8sub(messageLength, messageLength) == "!" + }) + end + end +else + -- Create a notification panel. + function ix.util.Notify(message) + if (ix.option.Get("chatNotices", false)) then + local messageLength = message:utf8len() + + ix.chat.Send(LocalPlayer(), "notice", message, false, { + bError = message:utf8sub(messageLength, messageLength) == "!" + }) + + return + end + + if (IsValid(ix.gui.notices)) then + ix.gui.notices:AddNotice(message) + end + + MsgC(Color(0, 255, 255), message .. "\n") + end + + -- Creates a translated notification. + function ix.util.NotifyLocalized(message, ...) + ix.util.Notify(L(message, ...)) + end + + -- shortcut notify functions + do + local playerMeta = FindMetaTable("Player") + + function playerMeta:Notify(message) + if (self == LocalPlayer()) then + ix.util.Notify(message) + end + end + + function playerMeta:NotifyLocalized(message, ...) + if (self == LocalPlayer()) then + ix.util.NotifyLocalized(message, ...) + end + end + + function playerMeta:ChatNotify(message) + if (self == LocalPlayer()) then + local messageLength = message:utf8len() + + ix.chat.Send(LocalPlayer(), "notice", message, false, { + bError = message:utf8sub(messageLength, messageLength) == "!" + }) + end + end + + function playerMeta:ChatNotifyLocalized(message, ...) + if (self == LocalPlayer()) then + message = L(message, ...) + + local messageLength = message:utf8len() + + ix.chat.Send(LocalPlayer(), "notice", message, false, { + bError = message:utf8sub(messageLength, messageLength) == "!" + }) + end + end + end + + -- Receives a notification from the server. + net.Receive("ixNotify", function() + ix.util.Notify(net.ReadString()) + end) + + -- Receives a notification from the server. + net.Receive("ixNotifyLocalized", function() + ix.util.NotifyLocalized(net.ReadString(), unpack(net.ReadTable())) + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_option.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_option.lua new file mode 100644 index 0000000..5d46f0b --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_option.lua @@ -0,0 +1,342 @@ + +--[[-- +Client-side configuration management. + +The `option` library provides a cleaner way to manage any arbitrary data on the client without the hassle of managing CVars. It +is analagous to the `ix.config` library, but it only deals with data that needs to be stored on the client. + +To get started, you'll need to define an option in a client realm so the framework can be aware of its existence. This can be +done in the `cl_init.lua` file of your schema, or in an `if (CLIENT) then` statement in the `sh_plugin.lua` file of your plugin: + ix.option.Add("headbob", ix.type.bool, true) + +If you need to get the value of an option on the server, you'll need to specify `true` for the `bNetworked` argument in +`ix.option.Add`. *NOTE:* You also need to define your option in a *shared* realm, since the server now also needs to be aware +of its existence. This makes it so that the client will send that option's value to the server whenever it changes, which then +means that the server can now retrieve the value that the client has the option set to. For example, if you need to get what +language a client is using, you can simply do the following: + ix.option.Get(player.GetByID(1), "language", "english") + +This will return the language of the player, or `"english"` if one isn't found. Note that `"language"` is a networked option +that is already defined in the framework, so it will always be available. All options will show up in the options menu on the +client, unless `hidden` returns `true` when using `ix.option.Add`. + +Note that the labels for each option in the menu will use a language phrase to show the name. For example, if your option is +named `headbob`, then you'll need to define a language phrase called `optHeadbob` that will be used as the option title. +]] +-- @module ix.option + +ix.option = ix.option or {} +ix.option.stored = ix.option.stored or {} +ix.option.categories = ix.option.categories or {} + +--- Creates a client-side configuration option with the given information. +-- @realm shared +-- @string key Unique ID for this option +-- @ixtype optionType Type of this option +-- @param default Default value that this option will have - this can be nil if needed +-- @tparam OptionStructure data Additional settings for this option +-- @usage ix.option.Add("animationScale", ix.type.number, 1, { +-- category = "appearance", +-- min = 0.3, +-- max = 2, +-- decimals = 1 +-- }) +function ix.option.Add(key, optionType, default, data) + assert(isstring(key) and key:find("%S"), "expected a non-empty string for the key") + + data = data or {} + + local categories = ix.option.categories + local category = data.category or "misc" + local upperName = key:sub(1, 1):upper() .. key:sub(2) + + categories[category] = categories[category] or {} + categories[category][key] = true + + --- You can specify additional optional arguments for `ix.option.Add` by passing in a table of specific fields as the fourth + -- argument. + -- @table OptionStructure + -- @realm shared + -- @field[type=string,opt="opt" .. key] phrase The phrase to use when displaying in the UI. The default value is your option + -- key in UpperCamelCase, prefixed with `"opt"`. For example, if your key is `"exampleOption"`, the default phrase will be + -- `"optExampleOption"`. + -- @field[type=string,opt="optd" .. key] description The phrase to use in the tooltip when hovered in the UI. The default + -- value is your option key in UpperCamelCase, prefixed with `"optd"`. For example, if your key is `"exampleOption"`, the + -- default phrase will be `"optdExampleOption"`. + -- @field[type=string,opt="misc"] category The category that this option should reside in. This is purely for + -- aesthetic reasons when displaying the options in the options menu. When displayed in the UI, it will take the form of + -- `L("category name")`. This means that you must create a language phrase for the category name - otherwise it will only + -- show as the exact string you've specified. If no category is set, it will default to `"misc"`. + -- @field[type=number,opt=0] min The minimum allowed amount when setting this option. This field is not + -- applicable to any type other than `ix.type.number`. + -- @field[type=number,opt=10] max The maximum allowed amount when setting this option. This field is not + -- applicable to any type other than `ix.type.number`. + -- @field[type=number,opt=0] decimals How many decimals to constrain to when using a number type. This field is not + -- applicable to any type other than `ix.type.number`. + -- @field[type=boolean,opt=false] bNetworked Whether or not the server should be aware of this option for each client. + -- @field[type=function,opt] OnChanged The function to run when this option is changed - this includes whether it was set + -- by the player, or through code using `ix.option.Set`. + -- OnChanged = function(oldValue, value) + -- print("new value is", value) + -- end + -- @field[type=function,opt] hidden The function to check whether the option should be hidden from the options menu. + -- @field[type=function,opt] populate The function to run when the option needs to be added to the menu. This is a required + -- field for any array options. It should return a table of entries where the key is the value to set in `ix.option.Set`, + -- and the value is the display name for the entry. An example: + -- + -- populate = function() + -- return { + -- ["english"] = "English", + -- ["french"] = "French", + -- ["spanish"] = "Spanish" + -- } + -- end + ix.option.stored[key] = { + key = key, + phrase = data.phrase or "opt" .. upperName, + description = data.description or "optd" .. upperName, + type = optionType, + default = default, + min = data.min or 0, + max = data.max or 10, + decimals = data.decimals or 0, + category = data.category or "misc", + bNetworked = data.bNetworked and true or false, + hidden = data.hidden or nil, + populate = data.populate or nil, + OnChanged = data.OnChanged or nil + } +end + +--- Loads all saved options from disk. +-- @realm shared +-- @internal +function ix.option.Load() + ix.util.Include("helix/gamemode/config/sh_options.lua") + + if (CLIENT) then + local options = ix.data.Get("options", nil, true, true) + + if (options) then + for k, v in pairs(options) do + ix.option.client[k] = v + end + end + + ix.option.Sync() + end +end + +--- Returns all of the available options. Note that this does contain the actual values of the options, just their properties. +-- @realm shared +-- @treturn table Table of all options +-- @usage PrintTable(ix.option.GetAll()) +-- > language: +-- > bNetworked = true +-- > default = english +-- > type = 512 +-- -- etc. +function ix.option.GetAll() + return ix.option.stored +end + + +--- Returns all of the available options grouped by their categories. The returned table contains category tables, that contain +-- all the options in that category as an array (this is so you can sort them if you'd like). +-- @realm shared +-- @bool[opt=false] bRemoveHidden Remove entries that are marked as hidden +-- @treturn table Table of all options +-- @usage PrintTable(ix.option.GetAllByCategories()) +-- > general: +-- > 1: +-- > key = language +-- > bNetworked = true +-- > default = english +-- > type = 512 +-- -- etc. +function ix.option.GetAllByCategories(bRemoveHidden) + local result = {} + + for k, v in pairs(ix.option.categories) do + for k2, _ in pairs(v) do + local option = ix.option.stored[k2] + + if (bRemoveHidden and isfunction(option.hidden) and option.hidden()) then + continue + end + + -- we create the category table here because it could contain all hidden options which makes the table empty + result[k] = result[k] or {} + result[k][#result[k] + 1] = option + end + end + + return result +end + +if (CLIENT) then + ix.option.client = ix.option.client or {} + + --- Sets an option value for the local player. + -- This function will error when an invalid key is passed. + -- @realm client + -- @string key Unique ID of the option + -- @param value New value to assign to the option + -- @bool[opt=false] bNoSave Whether or not to avoid saving + function ix.option.Set(key, value, bNoSave) + local option = assert(ix.option.stored[key], "invalid option key \"" .. tostring(key) .. "\"") + + if (option.type == ix.type.number) then + value = math.Clamp(math.Round(value, option.decimals), option.min, option.max) + end + + local oldValue = ix.option.client[key] + ix.option.client[key] = value + + if (option.bNetworked) then + net.Start("ixOptionSet") + net.WriteString(key) + net.WriteType(value) + net.SendToServer() + end + + if (!bNoSave) then + ix.option.Save() + end + + if (isfunction(option.OnChanged)) then + option.OnChanged(oldValue, value) + end + end + + --- Retrieves an option value for the local player. If it is not set, it'll return the default that you've specified. + -- @realm client + -- @string key Unique ID of the option + -- @param default Default value to return if the option is not set + -- @return[1] Value associated with the key + -- @return[2] The given default if the option is not set + function ix.option.Get(key, default) + local option = ix.option.stored[key] + + if (option) then + local localValue = ix.option.client[key] + + if (localValue != nil) then + return localValue + end + + return option.default + end + + return default + end + + --- Saves all options to disk. + -- @realm client + -- @internal + function ix.option.Save() + ix.data.Set("options", ix.option.client, true, true) + end + + --- Syncs all networked options to the server. + -- @realm client + function ix.option.Sync() + local options = {} + + for k, v in pairs(ix.option.stored) do + if (v.bNetworked) then + options[#options + 1] = {k, ix.option.client[k]} + end + end + + if (#options > 0) then + net.Start("ixOptionSync") + net.WriteUInt(#options, 8) + + for _, v in ipairs(options) do + net.WriteString(v[1]) + net.WriteType(v[2]) + end + + net.SendToServer() + end + end +else + util.AddNetworkString("ixOptionSet") + util.AddNetworkString("ixOptionSync") + + ix.option.clients = ix.option.clients or {} + + --- Retrieves an option value from the specified player. If it is not set, it'll return the default that you've specified. + -- This function will error when an invalid player is passed. + -- @realm server + -- @player client Player to retrieve option value from + -- @string key Unique ID of the option + -- @param default Default value to return if the option is not set + -- @return[1] Value associated with the key + -- @return[2] The given default if the option is not set + function ix.option.Get(client, key, default) + assert(IsValid(client) and client:IsPlayer(), "expected valid player for argument #1") + + local option = ix.option.stored[key] + + if (option) then + local clientOptions = ix.option.clients[client:SteamID64()] + + if (clientOptions) then + local clientOption = clientOptions[key] + + if (clientOption != nil) then + return clientOption + end + end + + return option.default + end + + return default + end + + -- sent whenever a client's networked option has changed + net.Receive("ixOptionSet", function(length, client) + local key = net.ReadString() + local value = net.ReadType() + + local steamID = client:SteamID64() + local option = ix.option.stored[key] + + if (option) then + ix.option.clients[steamID] = ix.option.clients[steamID] or {} + ix.option.clients[steamID][key] = value + else + ErrorNoHalt(string.format( + "'%s' attempted to set option with invalid key '%s'\n", tostring(client) .. client:SteamID(), key + )) + end + end) + + -- sent on first load to sync all networked option values + net.Receive("ixOptionSync", function(length, client) + local indices = net.ReadUInt(8) + local data = {} + + for _ = 1, indices do + data[net.ReadString()] = net.ReadType() + end + + local steamID = client:SteamID64() + ix.option.clients[steamID] = ix.option.clients[steamID] or {} + + for k, v in pairs(data) do + local option = ix.option.stored[k] + + if (option) then + ix.option.clients[steamID][k] = v + else + return ErrorNoHalt(string.format( + "'%s' attempted to sync option with invalid key '%s'\n", tostring(client) .. client:SteamID(), k + )) + end + end + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_player.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_player.lua new file mode 100644 index 0000000..d90940d --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_player.lua @@ -0,0 +1,143 @@ +local playerMeta = FindMetaTable("Player") + +-- ixData information for the player. +do + if (SERVER) then + function playerMeta:GetData(key, default) + if (key == true) then + return self.ixData + end + + local data = self.ixData and self.ixData[key] + + if (data == nil) then + return default + else + return data + end + end + else + function playerMeta:GetData(key, default) + local data = ix.localData and ix.localData[key] + + if (data == nil) then + return default + else + return data + end + end + + net.Receive("ixDataSync", function() + ix.localData = net.ReadTable() + ix.playTime = net.ReadUInt(32) + end) + + net.Receive("ixData", function() + ix.localData = ix.localData or {} + ix.localData[net.ReadString()] = net.ReadType() + end) + end +end + +-- Whitelist networking information here. +do + function playerMeta:HasWhitelist(faction) + local data = ix.faction.indices[faction] + + if (data) then + if (data.isDefault) then + return true + end + + local ixData = self:GetData("whitelists", {}) + + return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false + end + + return false + end + + function playerMeta:GetItems() + local char = self:GetCharacter() + + if (char) then + local inv = char:GetInventory() + + if (inv) then + return inv:GetItems() + end + end + end + + function playerMeta:GetClassData() + local char = self:GetCharacter() + + if (char) then + local class = char:GetClass() + + if (class) then + local classData = ix.class.list[class] + + return classData + end + end + end +end + +do + if (SERVER) then + util.AddNetworkString("PlayerModelChanged") + util.AddNetworkString("PlayerSelectWeapon") + + local entityMeta = FindMetaTable("Entity") + + entityMeta.ixSetModel = entityMeta.ixSetModel or entityMeta.SetModel + playerMeta.ixSelectWeapon = playerMeta.ixSelectWeapon or playerMeta.SelectWeapon + + function entityMeta:SetModel(model) + local oldModel = self:GetModel() + + if (self:IsPlayer()) then + hook.Run("PlayerModelChanged", self, model, oldModel) + + net.Start("PlayerModelChanged") + net.WriteEntity(self) + net.WriteString(model) + net.WriteString(oldModel) + net.Broadcast() + end + + return self:ixSetModel(model) + end + + function playerMeta:SelectWeapon(className) + net.Start("PlayerSelectWeapon") + net.WriteEntity(self) + net.WriteString(className) + net.Broadcast() + + return self:ixSelectWeapon(className) + end + else + net.Receive("PlayerModelChanged", function(length) + hook.Run("PlayerModelChanged", net.ReadEntity(), net.ReadString(), net.ReadString()) + end) + + net.Receive("PlayerSelectWeapon", function(length) + local client = net.ReadEntity() + local className = net.ReadString() + + if (!IsValid(client)) then + hook.Run("PlayerWeaponChanged", client, NULL) + return + end + + for _, v in ipairs(client:GetWeapons()) do + if (v:GetClass() == className) then + hook.Run("PlayerWeaponChanged", client, v) + break + end + end + end) + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_plugin.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_plugin.lua new file mode 100644 index 0000000..551a9d4 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_plugin.lua @@ -0,0 +1,443 @@ + +ix.plugin = ix.plugin or {} +ix.plugin.list = ix.plugin.list or {} +ix.plugin.unloaded = ix.plugin.unloaded or {} + +ix.util.Include("helix/gamemode/core/meta/sh_tool.lua") + +-- luacheck: globals HOOKS_CACHE +HOOKS_CACHE = {} + +function ix.plugin.Load(uniqueID, path, isSingleFile, variable) + if (hook.Run("PluginShouldLoad", uniqueID) == false) then return end + + variable = variable or "PLUGIN" + + -- Plugins within plugins situation? + local oldPlugin = PLUGIN + local PLUGIN = { + folder = path, + plugin = oldPlugin, + uniqueID = uniqueID, + name = "Unknown", + description = "Description not available", + author = "Anonymous" + } + + if (uniqueID == "schema") then + if (Schema) then + PLUGIN = Schema + end + + variable = "Schema" + PLUGIN.folder = engine.ActiveGamemode() + elseif (ix.plugin.list[uniqueID]) then + PLUGIN = ix.plugin.list[uniqueID] + end + + _G[variable] = PLUGIN + PLUGIN.loading = true + + if (!isSingleFile) then + ix.lang.LoadFromDir(path.."/languages") + ix.util.IncludeDir(path.."/libs", true) + ix.attributes.LoadFromDir(path.."/attributes") + ix.faction.LoadFromDir(path.."/factions") + ix.class.LoadFromDir(path.."/classes") + ix.item.LoadFromDir(path.."/items") + ix.plugin.LoadFromDir(path.."/plugins") + ix.util.IncludeDir(path.."/derma", true) + ix.plugin.LoadEntities(path.."/entities") + + hook.Run("DoPluginIncludes", path, PLUGIN) + end + + ix.util.Include(isSingleFile and path or path.."/sh_"..variable:lower()..".lua", "shared") + PLUGIN.loading = false + + local uniqueID2 = uniqueID + + if (uniqueID2 == "schema") then + uniqueID2 = PLUGIN.name + end + + function PLUGIN:SetData(value, global, ignoreMap) + ix.data.Set(uniqueID2, value, global, ignoreMap) + end + + function PLUGIN:GetData(default, global, ignoreMap, refresh) + return ix.data.Get(uniqueID2, default, global, ignoreMap, refresh) or {} + end + + hook.Run("PluginLoaded", uniqueID, PLUGIN) + + if (uniqueID != "schema") then + PLUGIN.name = PLUGIN.name or "Unknown" + PLUGIN.description = PLUGIN.description or "No description available." + + for k, v in pairs(PLUGIN) do + if (isfunction(v)) then + HOOKS_CACHE[k] = HOOKS_CACHE[k] or {} + HOOKS_CACHE[k][PLUGIN] = v + end + end + + ix.plugin.list[uniqueID] = PLUGIN + _G[variable] = oldPlugin + end + + if (PLUGIN.OnLoaded) then + PLUGIN:OnLoaded() + end +end + +function ix.plugin.GetHook(pluginName, hookName) + local h = HOOKS_CACHE[hookName] + + if (h) then + local p = ix.plugin.list[pluginName] + + if (p) then + return h[p] + end + end + + return +end + +function ix.plugin.LoadEntities(path) + local bLoadedTools + local files, folders + + local function IncludeFiles(path2, bClientOnly) + if (SERVER and !bClientOnly) then + if (file.Exists(path2.."init.lua", "LUA")) then + ix.util.Include(path2.."init.lua", "server") + elseif (file.Exists(path2.."shared.lua", "LUA")) then + ix.util.Include(path2.."shared.lua") + end + + if (file.Exists(path2.."cl_init.lua", "LUA")) then + ix.util.Include(path2.."cl_init.lua", "client") + end + elseif (file.Exists(path2.."cl_init.lua", "LUA")) then + ix.util.Include(path2.."cl_init.lua", "client") + elseif (file.Exists(path2.."shared.lua", "LUA")) then + ix.util.Include(path2.."shared.lua") + end + end + + local function HandleEntityInclusion(folder, variable, register, default, clientOnly, create, complete) + files, folders = file.Find(path.."/"..folder.."/*", "LUA") + default = default or {} + + for _, v in ipairs(folders) do + local path2 = path.."/"..folder.."/"..v.."/" + v = ix.util.StripRealmPrefix(v) + + _G[variable] = table.Copy(default) + + if (!isfunction(create)) then + _G[variable].ClassName = v + else + create(v) + end + + IncludeFiles(path2, clientOnly) + + if (clientOnly) then + if (CLIENT) then + register(_G[variable], v) + end + else + register(_G[variable], v) + end + + if (isfunction(complete)) then + complete(_G[variable]) + end + + _G[variable] = nil + end + + for _, v in ipairs(files) do + local niceName = ix.util.StripRealmPrefix(string.StripExtension(v)) + + _G[variable] = table.Copy(default) + + if (!isfunction(create)) then + _G[variable].ClassName = niceName + else + create(niceName) + end + + ix.util.Include(path.."/"..folder.."/"..v, clientOnly and "client" or "shared") + + if (clientOnly) then + if (CLIENT) then + register(_G[variable], niceName) + end + else + register(_G[variable], niceName) + end + + if (isfunction(complete)) then + complete(_G[variable]) + end + + _G[variable] = nil + end + end + + local function RegisterTool(tool, className) + local gmodTool = weapons.GetStored("gmod_tool") + + if (className:sub(1, 3) == "sh_") then + className = className:sub(4) + end + + if (gmodTool) then + gmodTool.Tool[className] = tool + else + -- this should never happen + ErrorNoHalt(string.format("attempted to register tool '%s' with invalid gmod_tool weapon", className)) + end + + bLoadedTools = true + end + + -- Include entities. + HandleEntityInclusion("entities", "ENT", scripted_ents.Register, { + Type = "anim", + Base = "base_gmodentity", + Spawnable = true + }, false, nil, function(ent) + if (SERVER and ent.Holdable == true) then + ix.allowedHoldableClasses[ent.ClassName] = true + end + end) + + -- Include weapons. + HandleEntityInclusion("weapons", "SWEP", weapons.Register, { + Primary = {}, + Secondary = {}, + Base = "weapon_base" + }) + + HandleEntityInclusion("tools", "TOOL", RegisterTool, {}, false, function(className) + if (className:sub(1, 3) == "sh_") then + className = className:sub(4) + end + + TOOL = ix.meta.tool:Create() + TOOL.Mode = className + TOOL:CreateConVars() + end) + + -- Include effects. + HandleEntityInclusion("effects", "EFFECT", effects and effects.Register, nil, true) + + -- only reload spawn menu if any new tools were registered + if (CLIENT and bLoadedTools) then + RunConsoleCommand("spawnmenu_reload") + end +end + +function ix.plugin.Initialize() + if SERVER then + ix.plugin.unloaded = ix.data.Get("unloaded", {}, true, true) + end + + ix.plugin.LoadFromDir("helix/plugins") + + ix.plugin.Load("schema", engine.ActiveGamemode().."/schema") + hook.Run("InitializedSchema") + + ix.plugin.LoadFromDir(engine.ActiveGamemode().."/plugins") + hook.Run("InitializedPlugins") +end + +function ix.plugin.Get(identifier) + return ix.plugin.list[identifier] +end + +function ix.plugin.LoadFromDir(directory) + local files, folders = file.Find(directory.."/*", "LUA") + + for _, v in ipairs(folders) do + ix.plugin.Load(v, directory.."/"..v) + end + + for _, v in ipairs(files) do + ix.plugin.Load(string.StripExtension(v), directory.."/"..v, true) + end +end + +function ix.plugin.SetUnloaded(uniqueID, state, bNoSave) + local plugin = ix.plugin.list[uniqueID] + + if (state) then + if (plugin and plugin.OnUnload) then + plugin:OnUnload() + end + + ix.plugin.unloaded[uniqueID] = true + elseif (ix.plugin.unloaded[uniqueID]) then + ix.plugin.unloaded[uniqueID] = false + else + return false + end + + if (SERVER and !bNoSave) then + local status + + if (state) then + status = true + end + + local unloaded = ix.data.Get("unloaded", {}, true, true) + unloaded[uniqueID] = status + ix.data.Set("unloaded", unloaded, true, true) + end + + if (state) then + hook.Run("PluginUnloaded", uniqueID) + end + + return true +end + +if (SERVER) then + --- Runs the `LoadData` and `PostLoadData` hooks for the gamemode, schema, and plugins. Any plugins that error during the + -- hook will have their `SaveData` and `PostLoadData` hooks removed to prevent them from saving junk data. + -- @internal + -- @realm server + function ix.plugin.RunLoadData() + local errors = hook.SafeRun("LoadData") + + -- remove the SaveData and PostLoadData hooks for any plugins that error during LoadData since they would probably be + -- saving bad data. this doesn't prevent plugins from saving data via other means, but there's only so much we can do + for _, v in pairs(errors or {}) do + if (v.plugin) then + local plugin = ix.plugin.Get(v.plugin) + + if (plugin) then + local saveDataHooks = HOOKS_CACHE["SaveData"] or {} + saveDataHooks[plugin] = nil + + local postLoadDataHooks = HOOKS_CACHE["PostLoadData"] or {} + postLoadDataHooks[plugin] = nil + end + end + end + + hook.Run("PostLoadData") + end +end + +do + -- luacheck: globals hook + hook.ixCall = hook.ixCall or hook.Call + + function hook.Call(name, gm, ...) + local cache = HOOKS_CACHE[name] + + if (cache) then + for k, v in pairs(cache) do + local a, b, c, d, e, f = v(k, ...) + + if (a != nil) then + return a, b, c, d, e, f + end + end + end + + if (Schema and Schema[name]) then + local a, b, c, d, e, f = Schema[name](Schema, ...) + + if (a != nil) then + return a, b, c, d, e, f + end + end + + return hook.ixCall(name, gm, ...) + end + + --- Runs the given hook in a protected call so that the calling function will continue executing even if any errors occur + -- while running the hook. This function is much more expensive to call than `hook.Run`, so you should avoid using it unless + -- you absolutely need to avoid errors from stopping the execution of your function. + -- @internal + -- @realm shared + -- @string name Name of the hook to run + -- @param ... Arguments to pass to the hook functions + -- @treturn[1] table Table of error data if an error occurred while running + -- @treturn[1] ... Any arguments returned by the hook functions + -- @usage local errors, bCanSpray = hook.SafeRun("PlayerSpray", Entity(1)) + -- if (!errors) then + -- -- do stuff with bCanSpray + -- else + -- PrintTable(errors) + -- end + function hook.SafeRun(name, ...) + local errors = {} + local gm = gmod and gmod.GetGamemode() or nil + local cache = HOOKS_CACHE[name] + + if (cache) then + for k, v in pairs(cache) do + local bSuccess, a, b, c, d, e, f = pcall(v, k, ...) + + if (bSuccess) then + if (a != nil) then + return errors, a, b, c, d, e, f + end + else + ErrorNoHalt(string.format("[Helix] hook.SafeRun error for plugin hook \"%s:%s\":\n\t%s\n%s\n", + tostring(k and k.uniqueID or nil), tostring(name), tostring(a), debug.traceback())) + + errors[#errors + 1] = { + name = name, + plugin = k and k.uniqueID or nil, + errorMessage = tostring(a) + } + end + end + end + + if (Schema and Schema[name]) then + local bSuccess, a, b, c, d, e, f = pcall(Schema[name], Schema, ...) + + if (bSuccess) then + if (a != nil) then + return errors, a, b, c, d, e, f + end + else + ErrorNoHalt(string.format("[Helix] hook.SafeRun error for schema hook \"%s\":\n\t%s\n%s\n", + tostring(name), tostring(a), debug.traceback())) + + errors[#errors + 1] = { + name = name, + schema = Schema.name, + errorMessage = tostring(a) + } + end + end + + local bSuccess, a, b, c, d, e, f = pcall(hook.ixCall, name, gm, ...) + + if (bSuccess) then + return errors, a, b, c, d, e, f + else + ErrorNoHalt(string.format("[Helix] hook.SafeRun error for gamemode hook \"%s\":\n\t%s\n%s\n", + tostring(name), tostring(a), debug.traceback())) + + errors[#errors + 1] = { + name = name, + gamemode = "gamemode", + errorMessage = tostring(a) + } + + return errors + end + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sh_storage.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_storage.lua new file mode 100644 index 0000000..4b5960f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sh_storage.lua @@ -0,0 +1,444 @@ + +--[[-- +Player manipulation of inventories. + +This library provides an easy way for players to manipulate other inventories. The only functions that you should need are +`ix.storage.Open` and `ix.storage.Close`. When opening an inventory as a storage item, it will display both the given inventory +and the player's inventory in the player's UI, which allows them to drag items to and from the given inventory. + +Example usage: + ix.storage.Open(client, inventory, { + name = "Filing Cabinet", + entity = ents.GetByIndex(3), + bMultipleUsers = true, + searchText = "Rummaging...", + searchTime = 4 + }) +]] +-- @module ix.storage + +--- There are some parameters you can customize when opening an inventory as a storage object with `ix.storage.Open`. +-- @realm server +-- @table StorageInfoStructure +-- @field[type=entity] entity Entity to "attach" the inventory to. This is used to provide a location for the inventory for +-- things like making sure the player doesn't move too far away from the inventory, etc. This can also be a `player` object. +-- @field[type=number,opt=inventory id] id The ID of the nventory. This defaults to the inventory passed into `ix.Storage.Open`. +-- @field[type=string,opt="Storage"] name Title to display in the UI when the inventory is open. +-- @field[type=boolean,opt=false] bMultipleUsers Whether or not multiple players are allowed to view this inventory at the +-- same time. +-- @field[type=number,opt=0] searchTime How long the player has to wait before the inventory is opened. +-- @field[type=string,opt="@storageSearching"] text Text to display to the user while opening the inventory. If prefixed with +-- `"@"`, it will display a language phrase. +-- @field[type=function,opt] OnPlayerClose Called when a player who was accessing the inventory has closed it. The +-- argument passed to the callback is the player who closed it. +-- @field[type=table,opt={}] data Table of arbitrary data to send to the client when the inventory has been opened. + +ix.storage = ix.storage or {} + +if (SERVER) then + util.AddNetworkString("ixStorageOpen") + util.AddNetworkString("ixStorageClose") + util.AddNetworkString("ixStorageExpired") + util.AddNetworkString("ixStorageMoneyTake") + util.AddNetworkString("ixStorageMoneyGive") + util.AddNetworkString("ixStorageMoneyUpdate") + + --- Returns whether or not the given inventory has a storage context and is being looked at by other players. + -- @realm server + -- @inventory inventory Inventory to check + -- @treturn bool Whether or not `inventory` is in use + function ix.storage.InUse(inventory) + if (inventory.storageInfo) then + for _, v in pairs(inventory:GetReceivers()) do + if (IsValid(v) and v:IsPlayer() and v != inventory.storageInfo.entity) then + return true + end + end + end + + return false + end + + --- Returns whether or not an inventory is in use by a specific player. + -- @realm server + -- @inventory inventory Inventory to check + -- @player client Player to check + -- @treturn bool Whether or not the player is using the given `inventory` + function ix.storage.InUseBy(inventory, client) + if (inventory.storageInfo) then + for _, v in pairs(inventory:GetReceivers()) do + if (IsValid(v) and v:IsPlayer() and v == client) then + return true + end + end + end + + return false + end + + --- Creates a storage context on the given inventory. + -- @realm server + -- @internal + -- @inventory inventory Inventory to create a storage context for + -- @tab info Information to store on the context + function ix.storage.CreateContext(inventory, info) + info = info or {} + + info.id = inventory:GetID() + info.name = info.name or "Storage" + info.entity = assert(IsValid(info.entity), "expected valid entity in info table") and info.entity + info.bMultipleUsers = info.bMultipleUsers == nil and false or info.bMultipleUsers + info.searchTime = tonumber(info.searchTime) or 0 + info.searchText = info.searchText or "@storageSearching" + info.data = info.data or {} + + inventory.storageInfo = info + + -- remove context from any bags this inventory might have + for k, _ in inventory:Iter() do + if (k.isBag and k:GetInventory()) then + ix.storage.CreateContext(k:GetInventory(), table.Copy(info)) + end + end + end + + --- Removes a storage context from an inventory if it exists. + -- @realm server + -- @internal + -- @inventory inventory Inventory to remove a storage context from + function ix.storage.RemoveContext(inventory) + inventory.storageInfo = nil + + -- remove context from any bags this inventory might have + for k, _ in inventory:Iter() do + if (k.isBag and k:GetInventory()) then + ix.storage.RemoveContext(k:GetInventory()) + end + end + end + + --- Synchronizes an inventory with a storage context to the given client. + -- @realm server + -- @internal + -- @player client Player to sync storage for + -- @inventory inventory Inventory to sync storage for + function ix.storage.Sync(client, inventory) + local info = inventory.storageInfo + + -- we'll retrieve the money value as we're syncing because it may have changed while + -- we were waiting for the timer to finish + if (info.entity.GetMoney) then + info.data.money = info.entity:GetMoney() + elseif (info.entity:IsPlayer() and info.entity:GetCharacter()) then + info.data.money = info.entity:GetCharacter():GetMoney() + end + + -- bags are automatically sync'd when the owning inventory is sync'd + inventory:Sync(client) + + net.Start("ixStorageOpen") + net.WriteUInt(info.id, 32) + net.WriteEntity(info.entity) + net.WriteString(info.name) + net.WriteTable(info.data) + net.Send(client) + end + + --- Adds a receiver to a given inventory with a storage context. + -- @realm server + -- @internal + -- @player client Player to sync storage for + -- @inventory inventory Inventory to sync storage for + -- @bool bDontSync Whether or not to skip syncing the storage to the client. If this is `true`, the storage panel will not + -- show up for the player + function ix.storage.AddReceiver(client, inventory, bDontSync) + local info = inventory.storageInfo + + if (info) then + inventory:AddReceiver(client) + client.ixOpenStorage = inventory + + -- update receivers for any bags this inventory might have + for k, _ in inventory:Iter() do + if (k.isBag and k:GetInventory()) then + k:GetInventory():AddReceiver(client) + end + end + + if (isfunction(info.OnPlayerOpen)) then + info.OnPlayerOpen(client) + end + + if (!bDontSync) then + ix.storage.Sync(client, inventory) + end + + return true + end + + return false + end + + --- Removes a storage receiver and removes the context if there are no more receivers. + -- @realm server + -- @internal + -- @player client Player to remove from receivers + -- @inventory inventory Inventory with storage context to remove receiver from + -- @bool bDontRemove Whether or not to skip removing the storage context if there are no more receivers + function ix.storage.RemoveReceiver(client, inventory, bDontRemove) + local info = inventory.storageInfo + + if (info) then + inventory:RemoveReceiver(client) + + -- update receivers for any bags this inventory might have + for k, _ in inventory:Iter() do + if (k.isBag and k:GetInventory()) then + k:GetInventory():RemoveReceiver(client) + end + end + + if (isfunction(info.OnPlayerClose)) then + info.OnPlayerClose(client) + end + + if (!bDontRemove and !ix.storage.InUse(inventory)) then + ix.storage.RemoveContext(inventory) + end + + client.ixOpenStorage = nil + return true + end + + return false + end + + --- Makes a player open an inventory that they can interact with. This can be called multiple times on the same inventory, + -- if the info passed allows for multiple users. + -- @realm server + -- @player client Player to open the inventory for + -- @inventory inventory Inventory to open + -- @tab info `StorageInfoStructure` describing the storage properties + function ix.storage.Open(client, inventory, info) + assert(IsValid(client) and client:IsPlayer(), "expected valid player") + assert(type(inventory) == "table" and inventory:IsInstanceOf(ix.meta.inventory), "expected valid inventory") + + -- create storage context if one isn't already created + if (!inventory.storageInfo) then + info = info or {} + ix.storage.CreateContext(inventory, info) + end + + local storageInfo = inventory.storageInfo + + -- add the client to the list of receivers if we're allowed to have multiple users + -- or if nobody else is occupying this inventory, otherwise nag the player + if (storageInfo.bMultipleUsers or !ix.storage.InUse(inventory)) then + ix.storage.AddReceiver(client, inventory, true) + else + client:NotifyLocalized("storageInUse") + return + end + + if (storageInfo.searchTime > 0) then + client:SetAction(storageInfo.searchText, storageInfo.searchTime) + client:DoStaredAction(storageInfo.entity, function() + if (IsValid(client) and IsValid(storageInfo.entity) and inventory.storageInfo) then + ix.storage.Sync(client, inventory) + end + end, storageInfo.searchTime, function() + if (IsValid(client)) then + ix.storage.RemoveReceiver(client, inventory) + client:SetAction() + end + end) + else + ix.storage.Sync(client, inventory) + end + end + + --- Forcefully makes clients close this inventory if they have it open. + -- @realm server + -- @inventory inventory Inventory to close + function ix.storage.Close(inventory) + local receivers = inventory:GetReceivers() + + if (#receivers > 0) then + net.Start("ixStorageExpired") + net.WriteUInt(inventory.storageInfo.id, 32) + net.Send(receivers) + end + + ix.storage.RemoveContext(inventory) + end + + net.Receive("ixStorageClose", function(length, client) + local inventory = client.ixOpenStorage + + if (inventory) then + ix.storage.RemoveReceiver(client, inventory) + end + end) + + net.Receive("ixStorageMoneyTake", function(length, client) + if (CurTime() < (client.ixStorageMoneyTimer or 0)) then + return + end + + local character = client:GetCharacter() + + if (!character) then + return + end + + local storageID = net.ReadUInt(32) + local amount = net.ReadUInt(32) + + local inventory = client.ixOpenStorage + + if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then + return + end + + local entity = inventory.storageInfo.entity + + if (!IsValid(entity) or + (!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or + (entity:IsPlayer() and !entity:GetCharacter())) then + return + end + + entity = entity:IsPlayer() and entity:GetCharacter() or entity + amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, entity:GetMoney()) + + if (amount == 0) then + return + end + + character:SetMoney(character:GetMoney() + amount) + + local total = entity:GetMoney() - amount + entity:SetMoney(total) + + net.Start("ixStorageMoneyUpdate") + net.WriteUInt(storageID, 32) + net.WriteUInt(total, 32) + net.Send(inventory:GetReceivers()) + + ix.log.Add(client, "storageMoneyTake", entity, amount, total) + + client.ixStorageMoneyTimer = CurTime() + 0.5 + end) + + net.Receive("ixStorageMoneyGive", function(length, client) + if (CurTime() < (client.ixStorageMoneyTimer or 0)) then + return + end + + local character = client:GetCharacter() + + if (!character) then + return + end + + local storageID = net.ReadUInt(32) + local amount = net.ReadUInt(32) + + local inventory = client.ixOpenStorage + + if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then + return + end + + local entity = inventory.storageInfo.entity + + if (!IsValid(entity) or + (!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or + (entity:IsPlayer() and !entity:GetCharacter())) then + return + end + + entity = entity:IsPlayer() and entity:GetCharacter() or entity + amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, character:GetMoney()) + + if (amount == 0) then + return + end + + character:SetMoney(character:GetMoney() - amount) + + local total = entity:GetMoney() + amount + entity:SetMoney(total) + + net.Start("ixStorageMoneyUpdate") + net.WriteUInt(storageID, 32) + net.WriteUInt(total, 32) + net.Send(inventory:GetReceivers()) + + ix.log.Add(client, "storageMoneyGive", entity, amount, total) + + client.ixStorageMoneyTimer = CurTime() + 0.5 + end) +else + net.Receive("ixStorageOpen", function() + if (IsValid(ix.gui.menu)) then + net.Start("ixStorageClose") + net.SendToServer() + return + end + + local id = net.ReadUInt(32) + local entity = net.ReadEntity() + local name = net.ReadString() + local data = net.ReadTable() + + local inventory = ix.item.inventories[id] + + if (IsValid(entity) and inventory and inventory.slots) then + local localInventory = LocalPlayer():GetCharacter():GetInventory() + local panel = vgui.Create("ixStorageView") + + if (localInventory) then + panel:SetLocalInventory(localInventory) + end + + panel:SetStorageID(id) + panel:SetStorageTitle(name) + panel:SetStorageInventory(inventory) + + if (data.money) then + if (localInventory) then + panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney()) + end + + panel:SetStorageMoney(data.money) + end + end + end) + + net.Receive("ixStorageExpired", function() + if (IsValid(ix.gui.openedStorage)) then + ix.gui.openedStorage:Remove() + end + + local id = net.ReadUInt(32) + + if (id != 0) then + ix.item.inventories[id] = nil + end + end) + + net.Receive("ixStorageMoneyUpdate", function() + local storageID = net.ReadUInt(32) + local amount = net.ReadUInt(32) + + local panel = ix.gui.openedStorage + + if (!IsValid(panel) or panel:GetStorageID() != storageID) then + return + end + + panel:SetStorageMoney(amount) + panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney()) + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sv_database.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sv_database.lua new file mode 100644 index 0000000..d921c62 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sv_database.lua @@ -0,0 +1,188 @@ + +ix.db = ix.db or { + schema = {}, + schemaQueue = {}, + type = { + -- TODO: more specific types, lengths, and defaults + -- i.e INT(11) UNSIGNED, SMALLINT(4), LONGTEXT, VARCHAR(350), NOT NULL, DEFAULT NULL, etc + [ix.type.string] = "VARCHAR(255)", + [ix.type.text] = "TEXT", + [ix.type.number] = "INT(11)", + [ix.type.steamid] = "VARCHAR(20)", + [ix.type.bool] = "TINYINT(1)" + } +} + +ix.db.config = ix.config.server.database or {} + +function ix.db.Connect() + ix.db.config.adapter = ix.db.config.adapter or "sqlite" + + local dbmodule = ix.db.config.adapter + local hostname = ix.db.config.hostname + local username = ix.db.config.username + local password = ix.db.config.password + local database = ix.db.config.database + local port = ix.db.config.port + + mysql:SetModule(dbmodule) + mysql:Connect(hostname, username, password, database, port) +end + +function ix.db.AddToSchema(schemaType, field, fieldType) + if (!ix.db.type[fieldType]) then + error(string.format("attempted to add field in schema with invalid type '%s'", fieldType)) + return + end + + if (!mysql:IsConnected() or !ix.db.schema[schemaType]) then + ix.db.schemaQueue[#ix.db.schemaQueue + 1] = {schemaType, field, fieldType} + return + end + + ix.db.InsertSchema(schemaType, field, fieldType) +end + +-- this is only ever used internally +function ix.db.InsertSchema(schemaType, field, fieldType) + local schema = ix.db.schema[schemaType] + + if (!schema) then + error(string.format("attempted to insert into schema with invalid schema type '%s'", schemaType)) + return + end + + if (!schema[field]) then + schema[field] = true + + local query = mysql:Update("ix_schema") + query:Update("columns", util.TableToJSON(schema)) + query:Where("table", schemaType) + query:Execute() + + query = mysql:Alter(schemaType) + query:Add(field, ix.db.type[fieldType]) + query:Execute() + end +end + +function ix.db.LoadTables() + local query + + query = mysql:Create("ix_schema") + query:Create("table", "VARCHAR(64) NOT NULL") + query:Create("columns", "TEXT NOT NULL") + query:PrimaryKey("table") + query:Execute() + + -- table structure will be populated with more fields when vars + -- are registered using ix.char.RegisterVar + query = mysql:Create("ix_characters") + query:Create("id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT") + query:PrimaryKey("id") + query:Execute() + + query = mysql:Create("ix_inventories") + query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT") + query:Create("character_id", "INT(11) UNSIGNED NOT NULL") + query:Create("inventory_type", "VARCHAR(150) DEFAULT NULL") + query:PrimaryKey("inventory_id") + query:Execute() + + query = mysql:Create("ix_items") + query:Create("item_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT") + query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL") + query:Create("unique_id", "VARCHAR(60) NOT NULL") + query:Create("character_id", "INT(11) UNSIGNED DEFAULT NULL") + query:Create("player_id", "VARCHAR(20) DEFAULT NULL") + query:Create("data", "TEXT DEFAULT NULL") + query:Create("x", "SMALLINT(4) NOT NULL") + query:Create("y", "SMALLINT(4) NOT NULL") + query:PrimaryKey("item_id") + query:Execute() + + query = mysql:Create("ix_players") + query:Create("steamid", "VARCHAR(20) NOT NULL") + query:Create("steam_name", "VARCHAR(32) NOT NULL") + query:Create("play_time", "INT(11) UNSIGNED DEFAULT NULL") + query:Create("address", "VARCHAR(15) DEFAULT NULL") + query:Create("last_join_time", "INT(11) UNSIGNED DEFAULT NULL") + query:Create("data", "TEXT") + query:PrimaryKey("steamid") + query:Execute() + + -- populate schema table if rows don't exist + query = mysql:InsertIgnore("ix_schema") + query:Insert("table", "ix_characters") + query:Insert("columns", util.TableToJSON({})) + query:Execute() + + -- load schema from database + query = mysql:Select("ix_schema") + query:Callback(function(result) + if (!istable(result)) then + return + end + + for _, v in pairs(result) do + ix.db.schema[v.table] = util.JSONToTable(v.columns) + end + + -- update schema if needed + for i = 1, #ix.db.schemaQueue do + local entry = ix.db.schemaQueue[i] + ix.db.InsertSchema(entry[1], entry[2], entry[3]) + end + end) + query:Execute() +end + +function ix.db.WipeTables(callback) + local query + + query = mysql:Drop("ix_schema") + query:Execute() + + query = mysql:Drop("ix_characters") + query:Execute() + + query = mysql:Drop("ix_inventories") + query:Execute() + + query = mysql:Drop("ix_items") + query:Execute() + + query = mysql:Drop("ix_players") + query:Callback(callback) + query:Execute() +end + +hook.Add("InitPostEntity", "ixDatabaseConnect", function() + -- Connect to the database using SQLite, mysqoo, or tmysql4. + ix.db.Connect() +end) + +local resetCalled = 0 + +concommand.Add("ix_wipedb", function(client, cmd, arguments) + -- can only be ran through the server's console + if (!IsValid(client)) then + if (resetCalled < RealTime()) then + resetCalled = RealTime() + 3 + + MsgC(Color(255, 0, 0), + "[Helix] WIPING THE DATABASE WILL PERMENANTLY REMOVE ALL PLAYER, CHARACTER, ITEM, AND INVENTORY DATA.\n") + MsgC(Color(255, 0, 0), "[Helix] THE SERVER WILL RESTART TO APPLY THESE CHANGES WHEN COMPLETED.\n") + MsgC(Color(255, 0, 0), "[Helix] TO CONFIRM DATABASE RESET, RUN 'ix_wipedb' AGAIN WITHIN 3 SECONDS.\n") + else + resetCalled = 0 + MsgC(Color(255, 0, 0), "[Helix] DATABASE WIPE IN PROGRESS...\n") + + hook.Run("OnWipeTables") + ix.db.WipeTables(function() + MsgC(Color(255, 255, 0), "[Helix] DATABASE WIPE COMPLETED!\n") + RunConsoleCommand("changelevel", game.GetMap()) + end) + end + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sv_networking.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sv_networking.lua new file mode 100644 index 0000000..90653bd --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sv_networking.lua @@ -0,0 +1,199 @@ + +-- @module ix.net + +local entityMeta = FindMetaTable("Entity") +local playerMeta = FindMetaTable("Player") + +ix.net = ix.net or {} +ix.net.list = ix.net.list or {} +ix.net.locals = ix.net.locals or {} +ix.net.globals = ix.net.globals or {} + +util.AddNetworkString("ixGlobalVarSet") +util.AddNetworkString("ixLocalVarSet") +util.AddNetworkString("ixNetVarSet") +util.AddNetworkString("ixNetVarDelete") + +-- Check if there is an attempt to send a function. Can't send those. +local function CheckBadType(name, object) + if (isfunction(object)) then + ErrorNoHalt("Net var '" .. name .. "' contains a bad object type!") + + return true + elseif (istable(object)) then + for k, v in pairs(object) do + -- Check both the key and the value for tables, and has recursion. + if (CheckBadType(name, k) or CheckBadType(name, v)) then + return true + end + end + end +end + +function GetNetVar(key, default) -- luacheck: globals GetNetVar + local value = ix.net.globals[key] + + return value != nil and value or default +end + +function SetNetVar(key, value, receiver) -- luacheck: globals SetNetVar + if (CheckBadType(key, value)) then return end + if (GetNetVar(key) == value and !istable(value)) then return end + + ix.net.globals[key] = value + + net.Start("ixGlobalVarSet") + net.WriteString(key) + net.WriteType(value) + + if (receiver == nil) then + net.Broadcast() + else + net.Send(receiver) + end +end + +--- Player networked variable functions +-- @classmod Player + +--- Synchronizes networked variables to the client. +-- @realm server +-- @internal +function playerMeta:SyncVars() + for k, v in pairs(ix.net.globals) do + net.Start("ixGlobalVarSet") + net.WriteString(k) + net.WriteType(v) + net.Send(self) + end + + for k, v in pairs(ix.net.locals[self] or {}) do + net.Start("ixLocalVarSet") + net.WriteString(k) + net.WriteType(v) + net.Send(self) + end + + for entity, data in pairs(ix.net.list) do + if (IsValid(entity)) then + local index = entity:EntIndex() + + for k, v in pairs(data) do + net.Start("ixNetVarSet") + net.WriteUInt(index, 16) + net.WriteString(k) + net.WriteType(v) + net.Send(self) + end + end + end +end + +--- Retrieves a local networked variable. If it is not set, it'll return the default that you've specified. +-- Locally networked variables can only be retrieved from the owning player when used from the client. +-- @realm shared +-- @string key Identifier of the local variable +-- @param default Default value to return if the local variable is not set +-- @return Value associated with the key, or the default that was given if it doesn't exist +-- @usage print(client:GetLocalVar("secret")) +-- > 12345678 +-- @see SetLocalVar +function playerMeta:GetLocalVar(key, default) + if (ix.net.locals[self] and ix.net.locals[self][key] != nil) then + return ix.net.locals[self][key] + end + + return default +end + +--- Sets the value of a local networked variable. +-- @realm server +-- @string key Identifier of the local variable +-- @param value New value to assign to the local variable +-- @usage client:SetLocalVar("secret", 12345678) +-- @see GetLocalVar +function playerMeta:SetLocalVar(key, value) + if (CheckBadType(key, value)) then return end + + ix.net.locals[self] = ix.net.locals[self] or {} + ix.net.locals[self][key] = value + + net.Start("ixLocalVarSet") + net.WriteString(key) + net.WriteType(value) + net.Send(self) +end + +--- Entity networked variable functions +-- @classmod Entity + +--- Retrieves a networked variable. If it is not set, it'll return the default that you've specified. +-- @realm shared +-- @string key Identifier of the networked variable +-- @param default Default value to return if the networked variable is not set +-- @return Value associated with the key, or the default that was given if it doesn't exist +-- @usage print(client:GetNetVar("example")) +-- > Hello World! +-- @see SetNetVar +function entityMeta:GetNetVar(key, default) + if (ix.net.list[self] and ix.net.list[self][key] != nil) then + return ix.net.list[self][key] + end + + return default +end + +--- Sets the value of a networked variable. +-- @realm server +-- @string key Identifier of the networked variable +-- @param value New value to assign to the networked variable +-- @tab[opt=nil] receiver The players to send the networked variable to +-- @usage client:SetNetVar("example", "Hello World!") +-- @see GetNetVar +function entityMeta:SetNetVar(key, value, receiver) + if (CheckBadType(key, value)) then return end + + ix.net.list[self] = ix.net.list[self] or {} + + if (ix.net.list[self][key] != value) then + ix.net.list[self][key] = value + end + + self:SendNetVar(key, receiver) +end + +--- Sends a networked variable. +-- @realm server +-- @internal +-- @string key Identifier of the networked variable +-- @tab[opt=nil] receiver The players to send the networked variable to +function entityMeta:SendNetVar(key, receiver) + net.Start("ixNetVarSet") + net.WriteUInt(self:EntIndex(), 16) + net.WriteString(key) + net.WriteType(ix.net.list[self] and ix.net.list[self][key]) + + if (receiver == nil) then + net.Broadcast() + else + net.Send(receiver) + end +end + +--- Clears all of the networked variables. +-- @realm server +-- @internal +-- @tab[opt=nil] receiver The players to clear the networked variable for +function entityMeta:ClearNetVars(receiver) + ix.net.list[self] = nil + ix.net.locals[self] = nil + + net.Start("ixNetVarDelete") + net.WriteUInt(self:EntIndex(), 16) + + if (receiver == nil) then + net.Broadcast() + else + net.Send(receiver) + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/sv_player.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/sv_player.lua new file mode 100644 index 0000000..0188ced --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/sv_player.lua @@ -0,0 +1,113 @@ +local playerMeta = FindMetaTable("Player") + +-- Player data (outside of characters) handling. +do + util.AddNetworkString("ixData") + util.AddNetworkString("ixDataSync") + + function playerMeta:LoadData(callback) + local name = self:SteamName() + local steamID64 = self:SteamID64() + local timestamp = math.floor(os.time()) + local ip = self:IPAddress():match("%d+%.%d+%.%d+%.%d+") + + local query = mysql:Select("ix_players") + query:Select("data") + query:Select("play_time") + query:Where("steamid", steamID64) + query:Callback(function(result) + if (IsValid(self) and istable(result) and #result > 0 and result[1].data) then + local updateQuery = mysql:Update("ix_players") + updateQuery:Update("last_join_time", timestamp) + updateQuery:Update("address", ip) + updateQuery:Where("steamid", steamID64) + updateQuery:Execute() + + self.ixPlayTime = tonumber(result[1].play_time) or 0 + self.ixData = util.JSONToTable(result[1].data) + + if (callback) then + callback(self.ixData) + end + else + local insertQuery = mysql:Insert("ix_players") + insertQuery:Insert("steamid", steamID64) + insertQuery:Insert("steam_name", name) + insertQuery:Insert("play_time", 0) + insertQuery:Insert("address", ip) + insertQuery:Insert("last_join_time", timestamp) + insertQuery:Insert("data", util.TableToJSON({})) + insertQuery:Execute() + + if (callback) then + callback({}) + end + end + end) + query:Execute() + end + + function playerMeta:SaveData() + if (self:IsBot()) then return end + + local name = self:SteamName() + local steamID64 = self:SteamID64() + + local query = mysql:Update("ix_players") + query:Update("steam_name", name) + query:Update("play_time", math.floor((self.ixPlayTime or 0) + (RealTime() - (self.ixJoinTime or RealTime() - 1)))) + query:Update("data", util.TableToJSON(self.ixData)) + query:Where("steamid", steamID64) + query:Execute() + end + + function playerMeta:SetData(key, value, bNoNetworking) + self.ixData = self.ixData or {} + self.ixData[key] = value + + if (!bNoNetworking) then + net.Start("ixData") + net.WriteString(key) + net.WriteType(value) + net.Send(self) + end + end +end + +-- Whitelisting information for the player. +do + function playerMeta:SetWhitelisted(faction, whitelisted) + if (!whitelisted) then + whitelisted = nil + end + + local data = ix.faction.indices[faction] + + if (data) then + local whitelists = self:GetData("whitelists", {}) + whitelists[Schema.folder] = whitelists[Schema.folder] or {} + whitelists[Schema.folder][data.uniqueID] = whitelisted and true or nil + + self:SetData("whitelists", whitelists) + self:SaveData() + + return true + end + + return false + end +end + +do + playerMeta.ixGive = playerMeta.ixGive or playerMeta.Give + + function playerMeta:Give(className, bNoAmmo) + local weapon + + self.ixWeaponGive = true + weapon = self:ixGive(className, bNoAmmo) + self.ixWeaponGive = nil + + return weapon + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua new file mode 100644 index 0000000..bc05042 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua @@ -0,0 +1,352 @@ +--[[ + BLACK TEA ICON LIBRARY FOR NUTSCRIPT 1.1 + + The MIT License (MIT) + + Copyright (c) 2017, Kyu Yeon Lee(Black Tea Za rebel1324) + + 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 thispermission 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. + + TL;DR: https://tldrlegal.com/license/mit-license + OK - + Commercial Use + Modify + Distribute + Sublicense + Private Use + + NOT OK - + Hold Liable + + MUST - + Include Copyright + Include License +]]-- + +--[[ + Default Tables. +]]-- + +ikon = ikon or {} +ikon.cache = ikon.cache or {} +ikon.requestList = ikon.requestList or {} +ikon.dev = false +ikon.maxSize = 8 -- 8x8 (512^2) is max icon size. + +IKON_BUSY = 1 +IKON_PROCESSING = 0 +IKON_SOMETHINGWRONG = -1 + +local schemaName = schemaName or (Schema and Schema.folder) + +--[[ + Initialize hooks and RT Screens. + returns nothing +]]-- +function ikon:init() + if (self.dev) then + hook.Add("HUDPaint", "ikon_dev2", ikon.showResult) + else + hook.Remove("HUDPaint", "ikon_dev2") + end + + --[[ + Being good at gmod is knowing all of stinky hacks + - Black Tea (2017) + ]]-- + ikon.haloAdd = ikon.haloAdd or halo.Add + function halo.Add(...) + if (ikon.rendering != true) then + ikon.haloAdd(...) + end + end + + ikon.haloRender = ikon.haloRender or halo.Render + function halo.Render(...) + if (ikon.rendering != true) then + ikon.haloRender(...) + end + end + + file.CreateDir("helix/icons") + file.CreateDir("helix/icons/" .. schemaName) +end + +--[[ + IKON Library Essential Material/Texture Declare +]]-- + +local TEXTURE_FLAGS_CLAMP_S = 0x0004 +local TEXTURE_FLAGS_CLAMP_T = 0x0008 + +ikon.max = ikon.maxSize * 64 +ikon.RT = GetRenderTargetEx("ixIconRendered", + ikon.max, + ikon.max, + RT_SIZE_NO_CHANGE, + MATERIAL_RT_DEPTH_SHARED, + bit.bor(TEXTURE_FLAGS_CLAMP_S, TEXTURE_FLAGS_CLAMP_T), + CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, + IMAGE_FORMAT_RGBA8888 +) + +local tex_effect = GetRenderTarget("ixIconRenderedOutline", ikon.max, ikon.max) +local mat_outline = CreateMaterial("ixIconRenderedTemp", "UnlitGeneric", { + ["$basetexture"] = tex_effect:GetName(), + ["$translucent"] = 1 +}) + +local lightPositions = { + BOX_TOP = Color(255, 255, 255), + BOX_FRONT = Color(255, 255, 255), +} +function ikon:renderHook() + local entity = ikon.renderEntity + + if (halo.RenderedEntity() == entity) then + return + end + + local w, h = ikon.curWidth * 64, ikon.curHeight * 64 + local x, y = 0, 0 + local tab + + if (ikon.info) then + tab = { + origin = ikon.info.pos, + angles = ikon.info.ang, + fov = ikon.info.fov, + outline = ikon.info.outline, + outCol = ikon.info.outlineColor + } + + if (!tab.origin and !tab.angles and !tab.fov) then + table.Merge(tab, PositionSpawnIcon(entity, entity:GetPos(), true)) + end + else + tab = PositionSpawnIcon(entity, entity:GetPos(), true) + end + + -- Taking MDave's Tip + xpcall(function() + render.OverrideAlphaWriteEnable(true, true) -- some playermodel eyeballs will not render without this + render.SetWriteDepthToDestAlpha(false) + render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD) + render.SuppressEngineLighting(true) + render.Clear(0, 0, 0, 0, true, true) + + render.SetLightingOrigin(vector_origin) + render.ResetModelLighting(200 / 255, 200 / 255, 200 / 255) + render.SetColorModulation(1, 1, 1) + + for i = 0, 6 do + local col = lightPositions[i] + + if (col) then + render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255) + end + end + + if (tab.outline) then + ix.util.ResetStencilValues() + render.SetStencilEnable(true) + render.SetStencilWriteMask(137) -- yeah random number to avoid confliction + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) + render.SetStencilPassOperation(STENCILOPERATION_REPLACE) + render.SetStencilFailOperation(STENCILOPERATION_REPLACE) + end + + --[[ + Add more effects on the Models! + ]]-- + if (ikon.info and ikon.info.drawHook) then + ikon.info.drawHook(entity) + end + + cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h) + render.SetBlend(1) + entity:DrawModel() + cam.End3D() + + if (tab.outline) then + render.PushRenderTarget(tex_effect) + render.Clear(0, 0, 0, 0) + render.ClearDepth() + cam.Start2D() + cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h) + render.SetBlend(0) + entity:DrawModel() + + render.SetStencilWriteMask(138) -- could you please? + render.SetStencilTestMask(1) + render.SetStencilReferenceValue(1) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + render.SetStencilPassOperation(STENCILOPERATION_KEEP) + render.SetStencilFailOperation(STENCILOPERATION_KEEP) + cam.Start2D() + surface.SetDrawColor(tab.outCol or color_white) + surface.DrawRect(0, 0, ScrW(), ScrH()) + cam.End2D() + cam.End3D() + cam.End2D() + render.PopRenderTarget() + + render.SetBlend(1) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NOTEQUAL) + + --[[ + Thanks for Noiwex + NxServ.eu + ]]-- + cam.Start2D() + surface.SetMaterial(mat_outline) + surface.DrawTexturedRectUV(-2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max) + surface.DrawTexturedRectUV(2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max) + surface.DrawTexturedRectUV(0, 2, w, h, 0, 0, w / ikon.max, h / ikon.max) + surface.DrawTexturedRectUV(0, -2, w, h, 0, 0, w / ikon.max, h / ikon.max) + cam.End2D() + + render.SetStencilEnable(false) + end + + render.SuppressEngineLighting(false) + render.SetWriteDepthToDestAlpha(true) + render.OverrideAlphaWriteEnable(false) + end, function(message) + print(message) + end) +end + +function ikon:showResult() + local x, y = ScrW() / 2, ScrH() / 2 + local w, h = ikon.curWidth * 64, ikon.curHeight * 64 + + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawOutlinedRect(x, 0, w, h) + + surface.SetMaterial(mat_outline) + surface.DrawTexturedRect(x, 0, w, h) +end + +--[[ + Renders the Icon with given arguments. + returns nothing +]]-- +function ikon:renderIcon(name, w, h, mdl, camInfo, updateCache) + if (#ikon.requestList > 0) then return IKON_BUSY end + if (ikon.requestList[name]) then return IKON_PROCESSING end + if (!w or !h or !mdl) then return IKON_SOMETHINGWRONG end + + local capturedIcon + ikon.curWidth = w or 1 + ikon.curHeight = h or 1 + ikon.renderModel = mdl + + if (camInfo) then + ikon.info = camInfo + end + + local w, h = ikon.curWidth * 64, ikon.curHeight * 64 + local sw, sh = ScrW(), ScrH() + + if (ikon.renderModel) then + if (!IsValid(ikon.renderEntity)) then + ikon.renderEntity = ClientsideModel(ikon.renderModel, RENDERGROUP_BOTH) + ikon.renderEntity:SetNoDraw(true) + end + end + + ikon.renderEntity:SetModel(ikon.renderModel) + + local bone = ikon.renderEntity:LookupBone("ValveBiped.Bip01_Head1") + + if (bone) then + ikon.renderEntity:SetEyeTarget(ikon.renderEntity:GetBonePosition(bone) + ikon.renderEntity:GetForward() * 32) + end + + local oldRT = render.GetRenderTarget() + render.PushRenderTarget(ikon.RT) + + ikon.rendering = true + ikon:renderHook() + ikon.rendering = nil + + capturedIcon = render.Capture({ + format = "png", + alpha = true, + x = 0, + y = 0, + w = w, + h = h + }) + + file.Write("helix/icons/" .. schemaName .. "/" .. name .. ".png", capturedIcon) + ikon.info = nil + render.PopRenderTarget() + + if (updateCache) then + local materialID = tostring(os.time()) + file.Write(materialID .. ".png", capturedIcon) + + timer.Simple(0, function() + local material = Material("../data/".. materialID ..".png") + + ikon.cache[name] = material + file.Delete(materialID .. ".png") + end) + end + + ikon.requestList[name] = nil + return true +end + +--[[ + Gets rendered icon with given unique name. + returns IMaterial +]]-- +function ikon:GetIcon(name) + if (ikon.cache[name]) then + return ikon.cache[name] -- yeah return cache + end + + if (file.Exists("helix/icons/" .. schemaName .. "/" .. name .. ".png", "DATA")) then + ikon.cache[name] = Material("../data/helix/icons/" .. schemaName .. "/".. name ..".png") + return ikon.cache[name] -- yeah return cache + else + return false -- retryd + end +end + +concommand.Add("ix_flushicon", function() + local root = "helix/icons/" .. schemaName + + for _, v in pairs(file.Find(root .. "/*.png", "DATA")) do + file.Delete(root .. "/" .. v) + end + + ikon.cache = {} +end) + +hook.Add("InitializedSchema", "updatePath", function() + schemaName = Schema.folder + ikon:init() +end) + +if (schemaName) then + ikon:init() +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua new file mode 100644 index 0000000..8bcc38f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua @@ -0,0 +1,1860 @@ +utf8_lc_uc = { + ["a"] = "A", + ["b"] = "B", + ["c"] = "C", + ["d"] = "D", + ["e"] = "E", + ["f"] = "F", + ["g"] = "G", + ["h"] = "H", + ["i"] = "I", + ["j"] = "J", + ["k"] = "K", + ["l"] = "L", + ["m"] = "M", + ["n"] = "N", + ["o"] = "O", + ["p"] = "P", + ["q"] = "Q", + ["r"] = "R", + ["s"] = "S", + ["t"] = "T", + ["u"] = "U", + ["v"] = "V", + ["w"] = "W", + ["x"] = "X", + ["y"] = "Y", + ["z"] = "Z", + ["µ"] = "Μ", + ["à"] = "À", + ["á"] = "Á", + ["â"] = "Â", + ["ã"] = "Ã", + ["ä"] = "Ä", + ["å"] = "Å", + ["æ"] = "Æ", + ["ç"] = "Ç", + ["è"] = "È", + ["é"] = "É", + ["ê"] = "Ê", + ["ë"] = "Ë", + ["ì"] = "Ì", + ["í"] = "Í", + ["î"] = "Î", + ["ï"] = "Ï", + ["ð"] = "Ð", + ["ñ"] = "Ñ", + ["ò"] = "Ò", + ["ó"] = "Ó", + ["ô"] = "Ô", + ["õ"] = "Õ", + ["ö"] = "Ö", + ["ø"] = "Ø", + ["ù"] = "Ù", + ["ú"] = "Ú", + ["û"] = "Û", + ["ü"] = "Ü", + ["ý"] = "Ý", + ["þ"] = "Þ", + ["ÿ"] = "Ÿ", + ["ā"] = "Ā", + ["ă"] = "Ă", + ["ą"] = "Ą", + ["ć"] = "Ć", + ["ĉ"] = "Ĉ", + ["ċ"] = "Ċ", + ["č"] = "Č", + ["ď"] = "Ď", + ["đ"] = "Đ", + ["ē"] = "Ē", + ["ĕ"] = "Ĕ", + ["ė"] = "Ė", + ["ę"] = "Ę", + ["ě"] = "Ě", + ["ĝ"] = "Ĝ", + ["ğ"] = "Ğ", + ["ġ"] = "Ġ", + ["ģ"] = "Ģ", + ["ĥ"] = "Ĥ", + ["ħ"] = "Ħ", + ["ĩ"] = "Ĩ", + ["ī"] = "Ī", + ["ĭ"] = "Ĭ", + ["į"] = "Į", + ["ı"] = "I", + ["ij"] = "IJ", + ["ĵ"] = "Ĵ", + ["ķ"] = "Ķ", + ["ĺ"] = "Ĺ", + ["ļ"] = "Ļ", + ["ľ"] = "Ľ", + ["ŀ"] = "Ŀ", + ["ł"] = "Ł", + ["ń"] = "Ń", + ["ņ"] = "Ņ", + ["ň"] = "Ň", + ["ŋ"] = "Ŋ", + ["ō"] = "Ō", + ["ŏ"] = "Ŏ", + ["ő"] = "Ő", + ["œ"] = "Œ", + ["ŕ"] = "Ŕ", + ["ŗ"] = "Ŗ", + ["ř"] = "Ř", + ["ś"] = "Ś", + ["ŝ"] = "Ŝ", + ["ş"] = "Ş", + ["š"] = "Š", + ["ţ"] = "Ţ", + ["ť"] = "Ť", + ["ŧ"] = "Ŧ", + ["ũ"] = "Ũ", + ["ū"] = "Ū", + ["ŭ"] = "Ŭ", + ["ů"] = "Ů", + ["ű"] = "Ű", + ["ų"] = "Ų", + ["ŵ"] = "Ŵ", + ["ŷ"] = "Ŷ", + ["ź"] = "Ź", + ["ż"] = "Ż", + ["ž"] = "Ž", + ["ſ"] = "S", + ["ƀ"] = "Ƀ", + ["ƃ"] = "Ƃ", + ["ƅ"] = "Ƅ", + ["ƈ"] = "Ƈ", + ["ƌ"] = "Ƌ", + ["ƒ"] = "Ƒ", + ["ƕ"] = "Ƕ", + ["ƙ"] = "Ƙ", + ["ƚ"] = "Ƚ", + ["ƞ"] = "Ƞ", + ["ơ"] = "Ơ", + ["ƣ"] = "Ƣ", + ["ƥ"] = "Ƥ", + ["ƨ"] = "Ƨ", + ["ƭ"] = "Ƭ", + ["ư"] = "Ư", + ["ƴ"] = "Ƴ", + ["ƶ"] = "Ƶ", + ["ƹ"] = "Ƹ", + ["ƽ"] = "Ƽ", + ["ƿ"] = "Ƿ", + ["Dž"] = "DŽ", + ["dž"] = "DŽ", + ["Lj"] = "LJ", + ["lj"] = "LJ", + ["Nj"] = "NJ", + ["nj"] = "NJ", + ["ǎ"] = "Ǎ", + ["ǐ"] = "Ǐ", + ["ǒ"] = "Ǒ", + ["ǔ"] = "Ǔ", + ["ǖ"] = "Ǖ", + ["ǘ"] = "Ǘ", + ["ǚ"] = "Ǚ", + ["ǜ"] = "Ǜ", + ["ǝ"] = "Ǝ", + ["ǟ"] = "Ǟ", + ["ǡ"] = "Ǡ", + ["ǣ"] = "Ǣ", + ["ǥ"] = "Ǥ", + ["ǧ"] = "Ǧ", + ["ǩ"] = "Ǩ", + ["ǫ"] = "Ǫ", + ["ǭ"] = "Ǭ", + ["ǯ"] = "Ǯ", + ["Dz"] = "DZ", + ["dz"] = "DZ", + ["ǵ"] = "Ǵ", + ["ǹ"] = "Ǹ", + ["ǻ"] = "Ǻ", + ["ǽ"] = "Ǽ", + ["ǿ"] = "Ǿ", + ["ȁ"] = "Ȁ", + ["ȃ"] = "Ȃ", + ["ȅ"] = "Ȅ", + ["ȇ"] = "Ȇ", + ["ȉ"] = "Ȉ", + ["ȋ"] = "Ȋ", + ["ȍ"] = "Ȍ", + ["ȏ"] = "Ȏ", + ["ȑ"] = "Ȑ", + ["ȓ"] = "Ȓ", + ["ȕ"] = "Ȕ", + ["ȗ"] = "Ȗ", + ["ș"] = "Ș", + ["ț"] = "Ț", + ["ȝ"] = "Ȝ", + ["ȟ"] = "Ȟ", + ["ȣ"] = "Ȣ", + ["ȥ"] = "Ȥ", + ["ȧ"] = "Ȧ", + ["ȩ"] = "Ȩ", + ["ȫ"] = "Ȫ", + ["ȭ"] = "Ȭ", + ["ȯ"] = "Ȯ", + ["ȱ"] = "Ȱ", + ["ȳ"] = "Ȳ", + ["ȼ"] = "Ȼ", + ["ɂ"] = "Ɂ", + ["ɇ"] = "Ɇ", + ["ɉ"] = "Ɉ", + ["ɋ"] = "Ɋ", + ["ɍ"] = "Ɍ", + ["ɏ"] = "Ɏ", + ["ɓ"] = "Ɓ", + ["ɔ"] = "Ɔ", + ["ɖ"] = "Ɖ", + ["ɗ"] = "Ɗ", + ["ə"] = "Ə", + ["ɛ"] = "Ɛ", + ["ɠ"] = "Ɠ", + ["ɣ"] = "Ɣ", + ["ɨ"] = "Ɨ", + ["ɩ"] = "Ɩ", + ["ɫ"] = "Ɫ", + ["ɯ"] = "Ɯ", + ["ɲ"] = "Ɲ", + ["ɵ"] = "Ɵ", + ["ɽ"] = "Ɽ", + ["ʀ"] = "Ʀ", + ["ʃ"] = "Ʃ", + ["ʈ"] = "Ʈ", + ["ʉ"] = "Ʉ", + ["ʊ"] = "Ʊ", + ["ʋ"] = "Ʋ", + ["ʌ"] = "Ʌ", + ["ʒ"] = "Ʒ", + ["ͅ"] = "Ι", + ["ͻ"] = "Ͻ", + ["ͼ"] = "Ͼ", + ["ͽ"] = "Ͽ", + ["ά"] = "Ά", + ["έ"] = "Έ", + ["ή"] = "Ή", + ["ί"] = "Ί", + ["α"] = "Α", + ["β"] = "Β", + ["γ"] = "Γ", + ["δ"] = "Δ", + ["ε"] = "Ε", + ["ζ"] = "Ζ", + ["η"] = "Η", + ["θ"] = "Θ", + ["ι"] = "Ι", + ["κ"] = "Κ", + ["λ"] = "Λ", + ["μ"] = "Μ", + ["ν"] = "Ν", + ["ξ"] = "Ξ", + ["ο"] = "Ο", + ["π"] = "Π", + ["ρ"] = "Ρ", + ["ς"] = "Σ", + ["σ"] = "Σ", + ["τ"] = "Τ", + ["υ"] = "Υ", + ["φ"] = "Φ", + ["χ"] = "Χ", + ["ψ"] = "Ψ", + ["ω"] = "Ω", + ["ϊ"] = "Ϊ", + ["ϋ"] = "Ϋ", + ["ό"] = "Ό", + ["ύ"] = "Ύ", + ["ώ"] = "Ώ", + ["ϐ"] = "Β", + ["ϑ"] = "Θ", + ["ϕ"] = "Φ", + ["ϖ"] = "Π", + ["ϙ"] = "Ϙ", + ["ϛ"] = "Ϛ", + ["ϝ"] = "Ϝ", + ["ϟ"] = "Ϟ", + ["ϡ"] = "Ϡ", + ["ϣ"] = "Ϣ", + ["ϥ"] = "Ϥ", + ["ϧ"] = "Ϧ", + ["ϩ"] = "Ϩ", + ["ϫ"] = "Ϫ", + ["ϭ"] = "Ϭ", + ["ϯ"] = "Ϯ", + ["ϰ"] = "Κ", + ["ϱ"] = "Ρ", + ["ϲ"] = "Ϲ", + ["ϵ"] = "Ε", + ["ϸ"] = "Ϸ", + ["ϻ"] = "Ϻ", + ["а"] = "А", + ["б"] = "Б", + ["в"] = "В", + ["г"] = "Г", + ["д"] = "Д", + ["е"] = "Е", + ["ж"] = "Ж", + ["з"] = "З", + ["и"] = "И", + ["й"] = "Й", + ["к"] = "К", + ["л"] = "Л", + ["м"] = "М", + ["н"] = "Н", + ["о"] = "О", + ["п"] = "П", + ["р"] = "Р", + ["с"] = "С", + ["т"] = "Т", + ["у"] = "У", + ["ф"] = "Ф", + ["х"] = "Х", + ["ц"] = "Ц", + ["ч"] = "Ч", + ["ш"] = "Ш", + ["щ"] = "Щ", + ["ъ"] = "Ъ", + ["ы"] = "Ы", + ["ь"] = "Ь", + ["э"] = "Э", + ["ю"] = "Ю", + ["я"] = "Я", + ["ѐ"] = "Ѐ", + ["ё"] = "Ё", + ["ђ"] = "Ђ", + ["ѓ"] = "Ѓ", + ["є"] = "Є", + ["ѕ"] = "Ѕ", + ["і"] = "І", + ["ї"] = "Ї", + ["ј"] = "Ј", + ["љ"] = "Љ", + ["њ"] = "Њ", + ["ћ"] = "Ћ", + ["ќ"] = "Ќ", + ["ѝ"] = "Ѝ", + ["ў"] = "Ў", + ["џ"] = "Џ", + ["ѡ"] = "Ѡ", + ["ѣ"] = "Ѣ", + ["ѥ"] = "Ѥ", + ["ѧ"] = "Ѧ", + ["ѩ"] = "Ѩ", + ["ѫ"] = "Ѫ", + ["ѭ"] = "Ѭ", + ["ѯ"] = "Ѯ", + ["ѱ"] = "Ѱ", + ["ѳ"] = "Ѳ", + ["ѵ"] = "Ѵ", + ["ѷ"] = "Ѷ", + ["ѹ"] = "Ѹ", + ["ѻ"] = "Ѻ", + ["ѽ"] = "Ѽ", + ["ѿ"] = "Ѿ", + ["ҁ"] = "Ҁ", + ["ҋ"] = "Ҋ", + ["ҍ"] = "Ҍ", + ["ҏ"] = "Ҏ", + ["ґ"] = "Ґ", + ["ғ"] = "Ғ", + ["ҕ"] = "Ҕ", + ["җ"] = "Җ", + ["ҙ"] = "Ҙ", + ["қ"] = "Қ", + ["ҝ"] = "Ҝ", + ["ҟ"] = "Ҟ", + ["ҡ"] = "Ҡ", + ["ң"] = "Ң", + ["ҥ"] = "Ҥ", + ["ҧ"] = "Ҧ", + ["ҩ"] = "Ҩ", + ["ҫ"] = "Ҫ", + ["ҭ"] = "Ҭ", + ["ү"] = "Ү", + ["ұ"] = "Ұ", + ["ҳ"] = "Ҳ", + ["ҵ"] = "Ҵ", + ["ҷ"] = "Ҷ", + ["ҹ"] = "Ҹ", + ["һ"] = "Һ", + ["ҽ"] = "Ҽ", + ["ҿ"] = "Ҿ", + ["ӂ"] = "Ӂ", + ["ӄ"] = "Ӄ", + ["ӆ"] = "Ӆ", + ["ӈ"] = "Ӈ", + ["ӊ"] = "Ӊ", + ["ӌ"] = "Ӌ", + ["ӎ"] = "Ӎ", + ["ӏ"] = "Ӏ", + ["ӑ"] = "Ӑ", + ["ӓ"] = "Ӓ", + ["ӕ"] = "Ӕ", + ["ӗ"] = "Ӗ", + ["ә"] = "Ә", + ["ӛ"] = "Ӛ", + ["ӝ"] = "Ӝ", + ["ӟ"] = "Ӟ", + ["ӡ"] = "Ӡ", + ["ӣ"] = "Ӣ", + ["ӥ"] = "Ӥ", + ["ӧ"] = "Ӧ", + ["ө"] = "Ө", + ["ӫ"] = "Ӫ", + ["ӭ"] = "Ӭ", + ["ӯ"] = "Ӯ", + ["ӱ"] = "Ӱ", + ["ӳ"] = "Ӳ", + ["ӵ"] = "Ӵ", + ["ӷ"] = "Ӷ", + ["ӹ"] = "Ӹ", + ["ӻ"] = "Ӻ", + ["ӽ"] = "Ӽ", + ["ӿ"] = "Ӿ", + ["ԁ"] = "Ԁ", + ["ԃ"] = "Ԃ", + ["ԅ"] = "Ԅ", + ["ԇ"] = "Ԇ", + ["ԉ"] = "Ԉ", + ["ԋ"] = "Ԋ", + ["ԍ"] = "Ԍ", + ["ԏ"] = "Ԏ", + ["ԑ"] = "Ԑ", + ["ԓ"] = "Ԓ", + ["ա"] = "Ա", + ["բ"] = "Բ", + ["գ"] = "Գ", + ["դ"] = "Դ", + ["ե"] = "Ե", + ["զ"] = "Զ", + ["է"] = "Է", + ["ը"] = "Ը", + ["թ"] = "Թ", + ["ժ"] = "Ժ", + ["ի"] = "Ի", + ["լ"] = "Լ", + ["խ"] = "Խ", + ["ծ"] = "Ծ", + ["կ"] = "Կ", + ["հ"] = "Հ", + ["ձ"] = "Ձ", + ["ղ"] = "Ղ", + ["ճ"] = "Ճ", + ["մ"] = "Մ", + ["յ"] = "Յ", + ["ն"] = "Ն", + ["շ"] = "Շ", + ["ո"] = "Ո", + ["չ"] = "Չ", + ["պ"] = "Պ", + ["ջ"] = "Ջ", + ["ռ"] = "Ռ", + ["ս"] = "Ս", + ["վ"] = "Վ", + ["տ"] = "Տ", + ["ր"] = "Ր", + ["ց"] = "Ց", + ["ւ"] = "Ւ", + ["փ"] = "Փ", + ["ք"] = "Ք", + ["օ"] = "Օ", + ["ֆ"] = "Ֆ", + ["ᵽ"] = "Ᵽ", + ["ḁ"] = "Ḁ", + ["ḃ"] = "Ḃ", + ["ḅ"] = "Ḅ", + ["ḇ"] = "Ḇ", + ["ḉ"] = "Ḉ", + ["ḋ"] = "Ḋ", + ["ḍ"] = "Ḍ", + ["ḏ"] = "Ḏ", + ["ḑ"] = "Ḑ", + ["ḓ"] = "Ḓ", + ["ḕ"] = "Ḕ", + ["ḗ"] = "Ḗ", + ["ḙ"] = "Ḙ", + ["ḛ"] = "Ḛ", + ["ḝ"] = "Ḝ", + ["ḟ"] = "Ḟ", + ["ḡ"] = "Ḡ", + ["ḣ"] = "Ḣ", + ["ḥ"] = "Ḥ", + ["ḧ"] = "Ḧ", + ["ḩ"] = "Ḩ", + ["ḫ"] = "Ḫ", + ["ḭ"] = "Ḭ", + ["ḯ"] = "Ḯ", + ["ḱ"] = "Ḱ", + ["ḳ"] = "Ḳ", + ["ḵ"] = "Ḵ", + ["ḷ"] = "Ḷ", + ["ḹ"] = "Ḹ", + ["ḻ"] = "Ḻ", + ["ḽ"] = "Ḽ", + ["ḿ"] = "Ḿ", + ["ṁ"] = "Ṁ", + ["ṃ"] = "Ṃ", + ["ṅ"] = "Ṅ", + ["ṇ"] = "Ṇ", + ["ṉ"] = "Ṉ", + ["ṋ"] = "Ṋ", + ["ṍ"] = "Ṍ", + ["ṏ"] = "Ṏ", + ["ṑ"] = "Ṑ", + ["ṓ"] = "Ṓ", + ["ṕ"] = "Ṕ", + ["ṗ"] = "Ṗ", + ["ṙ"] = "Ṙ", + ["ṛ"] = "Ṛ", + ["ṝ"] = "Ṝ", + ["ṟ"] = "Ṟ", + ["ṡ"] = "Ṡ", + ["ṣ"] = "Ṣ", + ["ṥ"] = "Ṥ", + ["ṧ"] = "Ṧ", + ["ṩ"] = "Ṩ", + ["ṫ"] = "Ṫ", + ["ṭ"] = "Ṭ", + ["ṯ"] = "Ṯ", + ["ṱ"] = "Ṱ", + ["ṳ"] = "Ṳ", + ["ṵ"] = "Ṵ", + ["ṷ"] = "Ṷ", + ["ṹ"] = "Ṹ", + ["ṻ"] = "Ṻ", + ["ṽ"] = "Ṽ", + ["ṿ"] = "Ṿ", + ["ẁ"] = "Ẁ", + ["ẃ"] = "Ẃ", + ["ẅ"] = "Ẅ", + ["ẇ"] = "Ẇ", + ["ẉ"] = "Ẉ", + ["ẋ"] = "Ẋ", + ["ẍ"] = "Ẍ", + ["ẏ"] = "Ẏ", + ["ẑ"] = "Ẑ", + ["ẓ"] = "Ẓ", + ["ẕ"] = "Ẕ", + ["ẛ"] = "Ṡ", + ["ạ"] = "Ạ", + ["ả"] = "Ả", + ["ấ"] = "Ấ", + ["ầ"] = "Ầ", + ["ẩ"] = "Ẩ", + ["ẫ"] = "Ẫ", + ["ậ"] = "Ậ", + ["ắ"] = "Ắ", + ["ằ"] = "Ằ", + ["ẳ"] = "Ẳ", + ["ẵ"] = "Ẵ", + ["ặ"] = "Ặ", + ["ẹ"] = "Ẹ", + ["ẻ"] = "Ẻ", + ["ẽ"] = "Ẽ", + ["ế"] = "Ế", + ["ề"] = "Ề", + ["ể"] = "Ể", + ["ễ"] = "Ễ", + ["ệ"] = "Ệ", + ["ỉ"] = "Ỉ", + ["ị"] = "Ị", + ["ọ"] = "Ọ", + ["ỏ"] = "Ỏ", + ["ố"] = "Ố", + ["ồ"] = "Ồ", + ["ổ"] = "Ổ", + ["ỗ"] = "Ỗ", + ["ộ"] = "Ộ", + ["ớ"] = "Ớ", + ["ờ"] = "Ờ", + ["ở"] = "Ở", + ["ỡ"] = "Ỡ", + ["ợ"] = "Ợ", + ["ụ"] = "Ụ", + ["ủ"] = "Ủ", + ["ứ"] = "Ứ", + ["ừ"] = "Ừ", + ["ử"] = "Ử", + ["ữ"] = "Ữ", + ["ự"] = "Ự", + ["ỳ"] = "Ỳ", + ["ỵ"] = "Ỵ", + ["ỷ"] = "Ỷ", + ["ỹ"] = "Ỹ", + ["ἀ"] = "Ἀ", + ["ἁ"] = "Ἁ", + ["ἂ"] = "Ἂ", + ["ἃ"] = "Ἃ", + ["ἄ"] = "Ἄ", + ["ἅ"] = "Ἅ", + ["ἆ"] = "Ἆ", + ["ἇ"] = "Ἇ", + ["ἐ"] = "Ἐ", + ["ἑ"] = "Ἑ", + ["ἒ"] = "Ἒ", + ["ἓ"] = "Ἓ", + ["ἔ"] = "Ἔ", + ["ἕ"] = "Ἕ", + ["ἠ"] = "Ἠ", + ["ἡ"] = "Ἡ", + ["ἢ"] = "Ἢ", + ["ἣ"] = "Ἣ", + ["ἤ"] = "Ἤ", + ["ἥ"] = "Ἥ", + ["ἦ"] = "Ἦ", + ["ἧ"] = "Ἧ", + ["ἰ"] = "Ἰ", + ["ἱ"] = "Ἱ", + ["ἲ"] = "Ἲ", + ["ἳ"] = "Ἳ", + ["ἴ"] = "Ἴ", + ["ἵ"] = "Ἵ", + ["ἶ"] = "Ἶ", + ["ἷ"] = "Ἷ", + ["ὀ"] = "Ὀ", + ["ὁ"] = "Ὁ", + ["ὂ"] = "Ὂ", + ["ὃ"] = "Ὃ", + ["ὄ"] = "Ὄ", + ["ὅ"] = "Ὅ", + ["ὑ"] = "Ὑ", + ["ὓ"] = "Ὓ", + ["ὕ"] = "Ὕ", + ["ὗ"] = "Ὗ", + ["ὠ"] = "Ὠ", + ["ὡ"] = "Ὡ", + ["ὢ"] = "Ὢ", + ["ὣ"] = "Ὣ", + ["ὤ"] = "Ὤ", + ["ὥ"] = "Ὥ", + ["ὦ"] = "Ὦ", + ["ὧ"] = "Ὧ", + ["ὰ"] = "Ὰ", + ["ά"] = "Ά", + ["ὲ"] = "Ὲ", + ["έ"] = "Έ", + ["ὴ"] = "Ὴ", + ["ή"] = "Ή", + ["ὶ"] = "Ὶ", + ["ί"] = "Ί", + ["ὸ"] = "Ὸ", + ["ό"] = "Ό", + ["ὺ"] = "Ὺ", + ["ύ"] = "Ύ", + ["ὼ"] = "Ὼ", + ["ώ"] = "Ώ", + ["ᾀ"] = "ᾈ", + ["ᾁ"] = "ᾉ", + ["ᾂ"] = "ᾊ", + ["ᾃ"] = "ᾋ", + ["ᾄ"] = "ᾌ", + ["ᾅ"] = "ᾍ", + ["ᾆ"] = "ᾎ", + ["ᾇ"] = "ᾏ", + ["ᾐ"] = "ᾘ", + ["ᾑ"] = "ᾙ", + ["ᾒ"] = "ᾚ", + ["ᾓ"] = "ᾛ", + ["ᾔ"] = "ᾜ", + ["ᾕ"] = "ᾝ", + ["ᾖ"] = "ᾞ", + ["ᾗ"] = "ᾟ", + ["ᾠ"] = "ᾨ", + ["ᾡ"] = "ᾩ", + ["ᾢ"] = "ᾪ", + ["ᾣ"] = "ᾫ", + ["ᾤ"] = "ᾬ", + ["ᾥ"] = "ᾭ", + ["ᾦ"] = "ᾮ", + ["ᾧ"] = "ᾯ", + ["ᾰ"] = "Ᾰ", + ["ᾱ"] = "Ᾱ", + ["ᾳ"] = "ᾼ", + ["ι"] = "Ι", + ["ῃ"] = "ῌ", + ["ῐ"] = "Ῐ", + ["ῑ"] = "Ῑ", + ["ῠ"] = "Ῠ", + ["ῡ"] = "Ῡ", + ["ῥ"] = "Ῥ", + ["ῳ"] = "ῼ", + ["ⅎ"] = "Ⅎ", + ["ⅰ"] = "Ⅰ", + ["ⅱ"] = "Ⅱ", + ["ⅲ"] = "Ⅲ", + ["ⅳ"] = "Ⅳ", + ["ⅴ"] = "Ⅴ", + ["ⅵ"] = "Ⅵ", + ["ⅶ"] = "Ⅶ", + ["ⅷ"] = "Ⅷ", + ["ⅸ"] = "Ⅸ", + ["ⅹ"] = "Ⅹ", + ["ⅺ"] = "Ⅺ", + ["ⅻ"] = "Ⅻ", + ["ⅼ"] = "Ⅼ", + ["ⅽ"] = "Ⅽ", + ["ⅾ"] = "Ⅾ", + ["ⅿ"] = "Ⅿ", + ["ↄ"] = "Ↄ", + ["ⓐ"] = "Ⓐ", + ["ⓑ"] = "Ⓑ", + ["ⓒ"] = "Ⓒ", + ["ⓓ"] = "Ⓓ", + ["ⓔ"] = "Ⓔ", + ["ⓕ"] = "Ⓕ", + ["ⓖ"] = "Ⓖ", + ["ⓗ"] = "Ⓗ", + ["ⓘ"] = "Ⓘ", + ["ⓙ"] = "Ⓙ", + ["ⓚ"] = "Ⓚ", + ["ⓛ"] = "Ⓛ", + ["ⓜ"] = "Ⓜ", + ["ⓝ"] = "Ⓝ", + ["ⓞ"] = "Ⓞ", + ["ⓟ"] = "Ⓟ", + ["ⓠ"] = "Ⓠ", + ["ⓡ"] = "Ⓡ", + ["ⓢ"] = "Ⓢ", + ["ⓣ"] = "Ⓣ", + ["ⓤ"] = "Ⓤ", + ["ⓥ"] = "Ⓥ", + ["ⓦ"] = "Ⓦ", + ["ⓧ"] = "Ⓧ", + ["ⓨ"] = "Ⓨ", + ["ⓩ"] = "Ⓩ", + ["ⰰ"] = "Ⰰ", + ["ⰱ"] = "Ⰱ", + ["ⰲ"] = "Ⰲ", + ["ⰳ"] = "Ⰳ", + ["ⰴ"] = "Ⰴ", + ["ⰵ"] = "Ⰵ", + ["ⰶ"] = "Ⰶ", + ["ⰷ"] = "Ⰷ", + ["ⰸ"] = "Ⰸ", + ["ⰹ"] = "Ⰹ", + ["ⰺ"] = "Ⰺ", + ["ⰻ"] = "Ⰻ", + ["ⰼ"] = "Ⰼ", + ["ⰽ"] = "Ⰽ", + ["ⰾ"] = "Ⰾ", + ["ⰿ"] = "Ⰿ", + ["ⱀ"] = "Ⱀ", + ["ⱁ"] = "Ⱁ", + ["ⱂ"] = "Ⱂ", + ["ⱃ"] = "Ⱃ", + ["ⱄ"] = "Ⱄ", + ["ⱅ"] = "Ⱅ", + ["ⱆ"] = "Ⱆ", + ["ⱇ"] = "Ⱇ", + ["ⱈ"] = "Ⱈ", + ["ⱉ"] = "Ⱉ", + ["ⱊ"] = "Ⱊ", + ["ⱋ"] = "Ⱋ", + ["ⱌ"] = "Ⱌ", + ["ⱍ"] = "Ⱍ", + ["ⱎ"] = "Ⱎ", + ["ⱏ"] = "Ⱏ", + ["ⱐ"] = "Ⱐ", + ["ⱑ"] = "Ⱑ", + ["ⱒ"] = "Ⱒ", + ["ⱓ"] = "Ⱓ", + ["ⱔ"] = "Ⱔ", + ["ⱕ"] = "Ⱕ", + ["ⱖ"] = "Ⱖ", + ["ⱗ"] = "Ⱗ", + ["ⱘ"] = "Ⱘ", + ["ⱙ"] = "Ⱙ", + ["ⱚ"] = "Ⱚ", + ["ⱛ"] = "Ⱛ", + ["ⱜ"] = "Ⱜ", + ["ⱝ"] = "Ⱝ", + ["ⱞ"] = "Ⱞ", + ["ⱡ"] = "Ⱡ", + ["ⱥ"] = "Ⱥ", + ["ⱦ"] = "Ⱦ", + ["ⱨ"] = "Ⱨ", + ["ⱪ"] = "Ⱪ", + ["ⱬ"] = "Ⱬ", + ["ⱶ"] = "Ⱶ", + ["ⲁ"] = "Ⲁ", + ["ⲃ"] = "Ⲃ", + ["ⲅ"] = "Ⲅ", + ["ⲇ"] = "Ⲇ", + ["ⲉ"] = "Ⲉ", + ["ⲋ"] = "Ⲋ", + ["ⲍ"] = "Ⲍ", + ["ⲏ"] = "Ⲏ", + ["ⲑ"] = "Ⲑ", + ["ⲓ"] = "Ⲓ", + ["ⲕ"] = "Ⲕ", + ["ⲗ"] = "Ⲗ", + ["ⲙ"] = "Ⲙ", + ["ⲛ"] = "Ⲛ", + ["ⲝ"] = "Ⲝ", + ["ⲟ"] = "Ⲟ", + ["ⲡ"] = "Ⲡ", + ["ⲣ"] = "Ⲣ", + ["ⲥ"] = "Ⲥ", + ["ⲧ"] = "Ⲧ", + ["ⲩ"] = "Ⲩ", + ["ⲫ"] = "Ⲫ", + ["ⲭ"] = "Ⲭ", + ["ⲯ"] = "Ⲯ", + ["ⲱ"] = "Ⲱ", + ["ⲳ"] = "Ⲳ", + ["ⲵ"] = "Ⲵ", + ["ⲷ"] = "Ⲷ", + ["ⲹ"] = "Ⲹ", + ["ⲻ"] = "Ⲻ", + ["ⲽ"] = "Ⲽ", + ["ⲿ"] = "Ⲿ", + ["ⳁ"] = "Ⳁ", + ["ⳃ"] = "Ⳃ", + ["ⳅ"] = "Ⳅ", + ["ⳇ"] = "Ⳇ", + ["ⳉ"] = "Ⳉ", + ["ⳋ"] = "Ⳋ", + ["ⳍ"] = "Ⳍ", + ["ⳏ"] = "Ⳏ", + ["ⳑ"] = "Ⳑ", + ["ⳓ"] = "Ⳓ", + ["ⳕ"] = "Ⳕ", + ["ⳗ"] = "Ⳗ", + ["ⳙ"] = "Ⳙ", + ["ⳛ"] = "Ⳛ", + ["ⳝ"] = "Ⳝ", + ["ⳟ"] = "Ⳟ", + ["ⳡ"] = "Ⳡ", + ["ⳣ"] = "Ⳣ", + ["ⴀ"] = "Ⴀ", + ["ⴁ"] = "Ⴁ", + ["ⴂ"] = "Ⴂ", + ["ⴃ"] = "Ⴃ", + ["ⴄ"] = "Ⴄ", + ["ⴅ"] = "Ⴅ", + ["ⴆ"] = "Ⴆ", + ["ⴇ"] = "Ⴇ", + ["ⴈ"] = "Ⴈ", + ["ⴉ"] = "Ⴉ", + ["ⴊ"] = "Ⴊ", + ["ⴋ"] = "Ⴋ", + ["ⴌ"] = "Ⴌ", + ["ⴍ"] = "Ⴍ", + ["ⴎ"] = "Ⴎ", + ["ⴏ"] = "Ⴏ", + ["ⴐ"] = "Ⴐ", + ["ⴑ"] = "Ⴑ", + ["ⴒ"] = "Ⴒ", + ["ⴓ"] = "Ⴓ", + ["ⴔ"] = "Ⴔ", + ["ⴕ"] = "Ⴕ", + ["ⴖ"] = "Ⴖ", + ["ⴗ"] = "Ⴗ", + ["ⴘ"] = "Ⴘ", + ["ⴙ"] = "Ⴙ", + ["ⴚ"] = "Ⴚ", + ["ⴛ"] = "Ⴛ", + ["ⴜ"] = "Ⴜ", + ["ⴝ"] = "Ⴝ", + ["ⴞ"] = "Ⴞ", + ["ⴟ"] = "Ⴟ", + ["ⴠ"] = "Ⴠ", + ["ⴡ"] = "Ⴡ", + ["ⴢ"] = "Ⴢ", + ["ⴣ"] = "Ⴣ", + ["ⴤ"] = "Ⴤ", + ["ⴥ"] = "Ⴥ", + ["a"] = "A", + ["b"] = "B", + ["c"] = "C", + ["d"] = "D", + ["e"] = "E", + ["f"] = "F", + ["g"] = "G", + ["h"] = "H", + ["i"] = "I", + ["j"] = "J", + ["k"] = "K", + ["l"] = "L", + ["m"] = "M", + ["n"] = "N", + ["o"] = "O", + ["p"] = "P", + ["q"] = "Q", + ["r"] = "R", + ["s"] = "S", + ["t"] = "T", + ["u"] = "U", + ["v"] = "V", + ["w"] = "W", + ["x"] = "X", + ["y"] = "Y", + ["z"] = "Z", + ["𐐨"] = "𐐀", + ["𐐩"] = "𐐁", + ["𐐪"] = "𐐂", + ["𐐫"] = "𐐃", + ["𐐬"] = "𐐄", + ["𐐭"] = "𐐅", + ["𐐮"] = "𐐆", + ["𐐯"] = "𐐇", + ["𐐰"] = "𐐈", + ["𐐱"] = "𐐉", + ["𐐲"] = "𐐊", + ["𐐳"] = "𐐋", + ["𐐴"] = "𐐌", + ["𐐵"] = "𐐍", + ["𐐶"] = "𐐎", + ["𐐷"] = "𐐏", + ["𐐸"] = "𐐐", + ["𐐹"] = "𐐑", + ["𐐺"] = "𐐒", + ["𐐻"] = "𐐓", + ["𐐼"] = "𐐔", + ["𐐽"] = "𐐕", + ["𐐾"] = "𐐖", + ["𐐿"] = "𐐗", + ["𐑀"] = "𐐘", + ["𐑁"] = "𐐙", + ["𐑂"] = "𐐚", + ["𐑃"] = "𐐛", + ["𐑄"] = "𐐜", + ["𐑅"] = "𐐝", + ["𐑆"] = "𐐞", + ["𐑇"] = "𐐟", + ["𐑈"] = "𐐠", + ["𐑉"] = "𐐡", + ["𐑊"] = "𐐢", + ["𐑋"] = "𐐣", + ["𐑌"] = "𐐤", + ["𐑍"] = "𐐥", + ["𐑎"] = "𐐦", + ["𐑏"] = "𐐧", +} + + +utf8_uc_lc = { + ["A"] = "a", + ["B"] = "b", + ["C"] = "c", + ["D"] = "d", + ["E"] = "e", + ["F"] = "f", + ["G"] = "g", + ["H"] = "h", + ["I"] = "i", + ["J"] = "j", + ["K"] = "k", + ["L"] = "l", + ["M"] = "m", + ["N"] = "n", + ["O"] = "o", + ["P"] = "p", + ["Q"] = "q", + ["R"] = "r", + ["S"] = "s", + ["T"] = "t", + ["U"] = "u", + ["V"] = "v", + ["W"] = "w", + ["X"] = "x", + ["Y"] = "y", + ["Z"] = "z", + ["À"] = "à", + ["Á"] = "á", + ["Â"] = "â", + ["Ã"] = "ã", + ["Ä"] = "ä", + ["Å"] = "å", + ["Æ"] = "æ", + ["Ç"] = "ç", + ["È"] = "è", + ["É"] = "é", + ["Ê"] = "ê", + ["Ë"] = "ë", + ["Ì"] = "ì", + ["Í"] = "í", + ["Î"] = "î", + ["Ï"] = "ï", + ["Ð"] = "ð", + ["Ñ"] = "ñ", + ["Ò"] = "ò", + ["Ó"] = "ó", + ["Ô"] = "ô", + ["Õ"] = "õ", + ["Ö"] = "ö", + ["Ø"] = "ø", + ["Ù"] = "ù", + ["Ú"] = "ú", + ["Û"] = "û", + ["Ü"] = "ü", + ["Ý"] = "ý", + ["Þ"] = "þ", + ["Ā"] = "ā", + ["Ă"] = "ă", + ["Ą"] = "ą", + ["Ć"] = "ć", + ["Ĉ"] = "ĉ", + ["Ċ"] = "ċ", + ["Č"] = "č", + ["Ď"] = "ď", + ["Đ"] = "đ", + ["Ē"] = "ē", + ["Ĕ"] = "ĕ", + ["Ė"] = "ė", + ["Ę"] = "ę", + ["Ě"] = "ě", + ["Ĝ"] = "ĝ", + ["Ğ"] = "ğ", + ["Ġ"] = "ġ", + ["Ģ"] = "ģ", + ["Ĥ"] = "ĥ", + ["Ħ"] = "ħ", + ["Ĩ"] = "ĩ", + ["Ī"] = "ī", + ["Ĭ"] = "ĭ", + ["Į"] = "į", + ["İ"] = "i", + ["IJ"] = "ij", + ["Ĵ"] = "ĵ", + ["Ķ"] = "ķ", + ["Ĺ"] = "ĺ", + ["Ļ"] = "ļ", + ["Ľ"] = "ľ", + ["Ŀ"] = "ŀ", + ["Ł"] = "ł", + ["Ń"] = "ń", + ["Ņ"] = "ņ", + ["Ň"] = "ň", + ["Ŋ"] = "ŋ", + ["Ō"] = "ō", + ["Ŏ"] = "ŏ", + ["Ő"] = "ő", + ["Œ"] = "œ", + ["Ŕ"] = "ŕ", + ["Ŗ"] = "ŗ", + ["Ř"] = "ř", + ["Ś"] = "ś", + ["Ŝ"] = "ŝ", + ["Ş"] = "ş", + ["Š"] = "š", + ["Ţ"] = "ţ", + ["Ť"] = "ť", + ["Ŧ"] = "ŧ", + ["Ũ"] = "ũ", + ["Ū"] = "ū", + ["Ŭ"] = "ŭ", + ["Ů"] = "ů", + ["Ű"] = "ű", + ["Ų"] = "ų", + ["Ŵ"] = "ŵ", + ["Ŷ"] = "ŷ", + ["Ÿ"] = "ÿ", + ["Ź"] = "ź", + ["Ż"] = "ż", + ["Ž"] = "ž", + ["Ɓ"] = "ɓ", + ["Ƃ"] = "ƃ", + ["Ƅ"] = "ƅ", + ["Ɔ"] = "ɔ", + ["Ƈ"] = "ƈ", + ["Ɖ"] = "ɖ", + ["Ɗ"] = "ɗ", + ["Ƌ"] = "ƌ", + ["Ǝ"] = "ǝ", + ["Ə"] = "ə", + ["Ɛ"] = "ɛ", + ["Ƒ"] = "ƒ", + ["Ɠ"] = "ɠ", + ["Ɣ"] = "ɣ", + ["Ɩ"] = "ɩ", + ["Ɨ"] = "ɨ", + ["Ƙ"] = "ƙ", + ["Ɯ"] = "ɯ", + ["Ɲ"] = "ɲ", + ["Ɵ"] = "ɵ", + ["Ơ"] = "ơ", + ["Ƣ"] = "ƣ", + ["Ƥ"] = "ƥ", + ["Ʀ"] = "ʀ", + ["Ƨ"] = "ƨ", + ["Ʃ"] = "ʃ", + ["Ƭ"] = "ƭ", + ["Ʈ"] = "ʈ", + ["Ư"] = "ư", + ["Ʊ"] = "ʊ", + ["Ʋ"] = "ʋ", + ["Ƴ"] = "ƴ", + ["Ƶ"] = "ƶ", + ["Ʒ"] = "ʒ", + ["Ƹ"] = "ƹ", + ["Ƽ"] = "ƽ", + ["DŽ"] = "dž", + ["Dž"] = "dž", + ["LJ"] = "lj", + ["Lj"] = "lj", + ["NJ"] = "nj", + ["Nj"] = "nj", + ["Ǎ"] = "ǎ", + ["Ǐ"] = "ǐ", + ["Ǒ"] = "ǒ", + ["Ǔ"] = "ǔ", + ["Ǖ"] = "ǖ", + ["Ǘ"] = "ǘ", + ["Ǚ"] = "ǚ", + ["Ǜ"] = "ǜ", + ["Ǟ"] = "ǟ", + ["Ǡ"] = "ǡ", + ["Ǣ"] = "ǣ", + ["Ǥ"] = "ǥ", + ["Ǧ"] = "ǧ", + ["Ǩ"] = "ǩ", + ["Ǫ"] = "ǫ", + ["Ǭ"] = "ǭ", + ["Ǯ"] = "ǯ", + ["DZ"] = "dz", + ["Dz"] = "dz", + ["Ǵ"] = "ǵ", + ["Ƕ"] = "ƕ", + ["Ƿ"] = "ƿ", + ["Ǹ"] = "ǹ", + ["Ǻ"] = "ǻ", + ["Ǽ"] = "ǽ", + ["Ǿ"] = "ǿ", + ["Ȁ"] = "ȁ", + ["Ȃ"] = "ȃ", + ["Ȅ"] = "ȅ", + ["Ȇ"] = "ȇ", + ["Ȉ"] = "ȉ", + ["Ȋ"] = "ȋ", + ["Ȍ"] = "ȍ", + ["Ȏ"] = "ȏ", + ["Ȑ"] = "ȑ", + ["Ȓ"] = "ȓ", + ["Ȕ"] = "ȕ", + ["Ȗ"] = "ȗ", + ["Ș"] = "ș", + ["Ț"] = "ț", + ["Ȝ"] = "ȝ", + ["Ȟ"] = "ȟ", + ["Ƞ"] = "ƞ", + ["Ȣ"] = "ȣ", + ["Ȥ"] = "ȥ", + ["Ȧ"] = "ȧ", + ["Ȩ"] = "ȩ", + ["Ȫ"] = "ȫ", + ["Ȭ"] = "ȭ", + ["Ȯ"] = "ȯ", + ["Ȱ"] = "ȱ", + ["Ȳ"] = "ȳ", + ["Ⱥ"] = "ⱥ", + ["Ȼ"] = "ȼ", + ["Ƚ"] = "ƚ", + ["Ⱦ"] = "ⱦ", + ["Ɂ"] = "ɂ", + ["Ƀ"] = "ƀ", + ["Ʉ"] = "ʉ", + ["Ʌ"] = "ʌ", + ["Ɇ"] = "ɇ", + ["Ɉ"] = "ɉ", + ["Ɋ"] = "ɋ", + ["Ɍ"] = "ɍ", + ["Ɏ"] = "ɏ", + ["Ά"] = "ά", + ["Έ"] = "έ", + ["Ή"] = "ή", + ["Ί"] = "ί", + ["Ό"] = "ό", + ["Ύ"] = "ύ", + ["Ώ"] = "ώ", + ["Α"] = "α", + ["Β"] = "β", + ["Γ"] = "γ", + ["Δ"] = "δ", + ["Ε"] = "ε", + ["Ζ"] = "ζ", + ["Η"] = "η", + ["Θ"] = "θ", + ["Ι"] = "ι", + ["Κ"] = "κ", + ["Λ"] = "λ", + ["Μ"] = "μ", + ["Ν"] = "ν", + ["Ξ"] = "ξ", + ["Ο"] = "ο", + ["Π"] = "π", + ["Ρ"] = "ρ", + ["Σ"] = "σ", + ["Τ"] = "τ", + ["Υ"] = "υ", + ["Φ"] = "φ", + ["Χ"] = "χ", + ["Ψ"] = "ψ", + ["Ω"] = "ω", + ["Ϊ"] = "ϊ", + ["Ϋ"] = "ϋ", + ["Ϙ"] = "ϙ", + ["Ϛ"] = "ϛ", + ["Ϝ"] = "ϝ", + ["Ϟ"] = "ϟ", + ["Ϡ"] = "ϡ", + ["Ϣ"] = "ϣ", + ["Ϥ"] = "ϥ", + ["Ϧ"] = "ϧ", + ["Ϩ"] = "ϩ", + ["Ϫ"] = "ϫ", + ["Ϭ"] = "ϭ", + ["Ϯ"] = "ϯ", + ["ϴ"] = "θ", + ["Ϸ"] = "ϸ", + ["Ϲ"] = "ϲ", + ["Ϻ"] = "ϻ", + ["Ͻ"] = "ͻ", + ["Ͼ"] = "ͼ", + ["Ͽ"] = "ͽ", + ["Ѐ"] = "ѐ", + ["Ё"] = "ё", + ["Ђ"] = "ђ", + ["Ѓ"] = "ѓ", + ["Є"] = "є", + ["Ѕ"] = "ѕ", + ["І"] = "і", + ["Ї"] = "ї", + ["Ј"] = "ј", + ["Љ"] = "љ", + ["Њ"] = "њ", + ["Ћ"] = "ћ", + ["Ќ"] = "ќ", + ["Ѝ"] = "ѝ", + ["Ў"] = "ў", + ["Џ"] = "џ", + ["А"] = "а", + ["Б"] = "б", + ["В"] = "в", + ["Г"] = "г", + ["Д"] = "д", + ["Е"] = "е", + ["Ж"] = "ж", + ["З"] = "з", + ["И"] = "и", + ["Й"] = "й", + ["К"] = "к", + ["Л"] = "л", + ["М"] = "м", + ["Н"] = "н", + ["О"] = "о", + ["П"] = "п", + ["Р"] = "р", + ["С"] = "с", + ["Т"] = "т", + ["У"] = "у", + ["Ф"] = "ф", + ["Х"] = "х", + ["Ц"] = "ц", + ["Ч"] = "ч", + ["Ш"] = "ш", + ["Щ"] = "щ", + ["Ъ"] = "ъ", + ["Ы"] = "ы", + ["Ь"] = "ь", + ["Э"] = "э", + ["Ю"] = "ю", + ["Я"] = "я", + ["Ѡ"] = "ѡ", + ["Ѣ"] = "ѣ", + ["Ѥ"] = "ѥ", + ["Ѧ"] = "ѧ", + ["Ѩ"] = "ѩ", + ["Ѫ"] = "ѫ", + ["Ѭ"] = "ѭ", + ["Ѯ"] = "ѯ", + ["Ѱ"] = "ѱ", + ["Ѳ"] = "ѳ", + ["Ѵ"] = "ѵ", + ["Ѷ"] = "ѷ", + ["Ѹ"] = "ѹ", + ["Ѻ"] = "ѻ", + ["Ѽ"] = "ѽ", + ["Ѿ"] = "ѿ", + ["Ҁ"] = "ҁ", + ["Ҋ"] = "ҋ", + ["Ҍ"] = "ҍ", + ["Ҏ"] = "ҏ", + ["Ґ"] = "ґ", + ["Ғ"] = "ғ", + ["Ҕ"] = "ҕ", + ["Җ"] = "җ", + ["Ҙ"] = "ҙ", + ["Қ"] = "қ", + ["Ҝ"] = "ҝ", + ["Ҟ"] = "ҟ", + ["Ҡ"] = "ҡ", + ["Ң"] = "ң", + ["Ҥ"] = "ҥ", + ["Ҧ"] = "ҧ", + ["Ҩ"] = "ҩ", + ["Ҫ"] = "ҫ", + ["Ҭ"] = "ҭ", + ["Ү"] = "ү", + ["Ұ"] = "ұ", + ["Ҳ"] = "ҳ", + ["Ҵ"] = "ҵ", + ["Ҷ"] = "ҷ", + ["Ҹ"] = "ҹ", + ["Һ"] = "һ", + ["Ҽ"] = "ҽ", + ["Ҿ"] = "ҿ", + ["Ӏ"] = "ӏ", + ["Ӂ"] = "ӂ", + ["Ӄ"] = "ӄ", + ["Ӆ"] = "ӆ", + ["Ӈ"] = "ӈ", + ["Ӊ"] = "ӊ", + ["Ӌ"] = "ӌ", + ["Ӎ"] = "ӎ", + ["Ӑ"] = "ӑ", + ["Ӓ"] = "ӓ", + ["Ӕ"] = "ӕ", + ["Ӗ"] = "ӗ", + ["Ә"] = "ә", + ["Ӛ"] = "ӛ", + ["Ӝ"] = "ӝ", + ["Ӟ"] = "ӟ", + ["Ӡ"] = "ӡ", + ["Ӣ"] = "ӣ", + ["Ӥ"] = "ӥ", + ["Ӧ"] = "ӧ", + ["Ө"] = "ө", + ["Ӫ"] = "ӫ", + ["Ӭ"] = "ӭ", + ["Ӯ"] = "ӯ", + ["Ӱ"] = "ӱ", + ["Ӳ"] = "ӳ", + ["Ӵ"] = "ӵ", + ["Ӷ"] = "ӷ", + ["Ӹ"] = "ӹ", + ["Ӻ"] = "ӻ", + ["Ӽ"] = "ӽ", + ["Ӿ"] = "ӿ", + ["Ԁ"] = "ԁ", + ["Ԃ"] = "ԃ", + ["Ԅ"] = "ԅ", + ["Ԇ"] = "ԇ", + ["Ԉ"] = "ԉ", + ["Ԋ"] = "ԋ", + ["Ԍ"] = "ԍ", + ["Ԏ"] = "ԏ", + ["Ԑ"] = "ԑ", + ["Ԓ"] = "ԓ", + ["Ա"] = "ա", + ["Բ"] = "բ", + ["Գ"] = "գ", + ["Դ"] = "դ", + ["Ե"] = "ե", + ["Զ"] = "զ", + ["Է"] = "է", + ["Ը"] = "ը", + ["Թ"] = "թ", + ["Ժ"] = "ժ", + ["Ի"] = "ի", + ["Լ"] = "լ", + ["Խ"] = "խ", + ["Ծ"] = "ծ", + ["Կ"] = "կ", + ["Հ"] = "հ", + ["Ձ"] = "ձ", + ["Ղ"] = "ղ", + ["Ճ"] = "ճ", + ["Մ"] = "մ", + ["Յ"] = "յ", + ["Ն"] = "ն", + ["Շ"] = "շ", + ["Ո"] = "ո", + ["Չ"] = "չ", + ["Պ"] = "պ", + ["Ջ"] = "ջ", + ["Ռ"] = "ռ", + ["Ս"] = "ս", + ["Վ"] = "վ", + ["Տ"] = "տ", + ["Ր"] = "ր", + ["Ց"] = "ց", + ["Ւ"] = "ւ", + ["Փ"] = "փ", + ["Ք"] = "ք", + ["Օ"] = "օ", + ["Ֆ"] = "ֆ", + ["Ⴀ"] = "ⴀ", + ["Ⴁ"] = "ⴁ", + ["Ⴂ"] = "ⴂ", + ["Ⴃ"] = "ⴃ", + ["Ⴄ"] = "ⴄ", + ["Ⴅ"] = "ⴅ", + ["Ⴆ"] = "ⴆ", + ["Ⴇ"] = "ⴇ", + ["Ⴈ"] = "ⴈ", + ["Ⴉ"] = "ⴉ", + ["Ⴊ"] = "ⴊ", + ["Ⴋ"] = "ⴋ", + ["Ⴌ"] = "ⴌ", + ["Ⴍ"] = "ⴍ", + ["Ⴎ"] = "ⴎ", + ["Ⴏ"] = "ⴏ", + ["Ⴐ"] = "ⴐ", + ["Ⴑ"] = "ⴑ", + ["Ⴒ"] = "ⴒ", + ["Ⴓ"] = "ⴓ", + ["Ⴔ"] = "ⴔ", + ["Ⴕ"] = "ⴕ", + ["Ⴖ"] = "ⴖ", + ["Ⴗ"] = "ⴗ", + ["Ⴘ"] = "ⴘ", + ["Ⴙ"] = "ⴙ", + ["Ⴚ"] = "ⴚ", + ["Ⴛ"] = "ⴛ", + ["Ⴜ"] = "ⴜ", + ["Ⴝ"] = "ⴝ", + ["Ⴞ"] = "ⴞ", + ["Ⴟ"] = "ⴟ", + ["Ⴠ"] = "ⴠ", + ["Ⴡ"] = "ⴡ", + ["Ⴢ"] = "ⴢ", + ["Ⴣ"] = "ⴣ", + ["Ⴤ"] = "ⴤ", + ["Ⴥ"] = "ⴥ", + ["Ḁ"] = "ḁ", + ["Ḃ"] = "ḃ", + ["Ḅ"] = "ḅ", + ["Ḇ"] = "ḇ", + ["Ḉ"] = "ḉ", + ["Ḋ"] = "ḋ", + ["Ḍ"] = "ḍ", + ["Ḏ"] = "ḏ", + ["Ḑ"] = "ḑ", + ["Ḓ"] = "ḓ", + ["Ḕ"] = "ḕ", + ["Ḗ"] = "ḗ", + ["Ḙ"] = "ḙ", + ["Ḛ"] = "ḛ", + ["Ḝ"] = "ḝ", + ["Ḟ"] = "ḟ", + ["Ḡ"] = "ḡ", + ["Ḣ"] = "ḣ", + ["Ḥ"] = "ḥ", + ["Ḧ"] = "ḧ", + ["Ḩ"] = "ḩ", + ["Ḫ"] = "ḫ", + ["Ḭ"] = "ḭ", + ["Ḯ"] = "ḯ", + ["Ḱ"] = "ḱ", + ["Ḳ"] = "ḳ", + ["Ḵ"] = "ḵ", + ["Ḷ"] = "ḷ", + ["Ḹ"] = "ḹ", + ["Ḻ"] = "ḻ", + ["Ḽ"] = "ḽ", + ["Ḿ"] = "ḿ", + ["Ṁ"] = "ṁ", + ["Ṃ"] = "ṃ", + ["Ṅ"] = "ṅ", + ["Ṇ"] = "ṇ", + ["Ṉ"] = "ṉ", + ["Ṋ"] = "ṋ", + ["Ṍ"] = "ṍ", + ["Ṏ"] = "ṏ", + ["Ṑ"] = "ṑ", + ["Ṓ"] = "ṓ", + ["Ṕ"] = "ṕ", + ["Ṗ"] = "ṗ", + ["Ṙ"] = "ṙ", + ["Ṛ"] = "ṛ", + ["Ṝ"] = "ṝ", + ["Ṟ"] = "ṟ", + ["Ṡ"] = "ṡ", + ["Ṣ"] = "ṣ", + ["Ṥ"] = "ṥ", + ["Ṧ"] = "ṧ", + ["Ṩ"] = "ṩ", + ["Ṫ"] = "ṫ", + ["Ṭ"] = "ṭ", + ["Ṯ"] = "ṯ", + ["Ṱ"] = "ṱ", + ["Ṳ"] = "ṳ", + ["Ṵ"] = "ṵ", + ["Ṷ"] = "ṷ", + ["Ṹ"] = "ṹ", + ["Ṻ"] = "ṻ", + ["Ṽ"] = "ṽ", + ["Ṿ"] = "ṿ", + ["Ẁ"] = "ẁ", + ["Ẃ"] = "ẃ", + ["Ẅ"] = "ẅ", + ["Ẇ"] = "ẇ", + ["Ẉ"] = "ẉ", + ["Ẋ"] = "ẋ", + ["Ẍ"] = "ẍ", + ["Ẏ"] = "ẏ", + ["Ẑ"] = "ẑ", + ["Ẓ"] = "ẓ", + ["Ẕ"] = "ẕ", + ["Ạ"] = "ạ", + ["Ả"] = "ả", + ["Ấ"] = "ấ", + ["Ầ"] = "ầ", + ["Ẩ"] = "ẩ", + ["Ẫ"] = "ẫ", + ["Ậ"] = "ậ", + ["Ắ"] = "ắ", + ["Ằ"] = "ằ", + ["Ẳ"] = "ẳ", + ["Ẵ"] = "ẵ", + ["Ặ"] = "ặ", + ["Ẹ"] = "ẹ", + ["Ẻ"] = "ẻ", + ["Ẽ"] = "ẽ", + ["Ế"] = "ế", + ["Ề"] = "ề", + ["Ể"] = "ể", + ["Ễ"] = "ễ", + ["Ệ"] = "ệ", + ["Ỉ"] = "ỉ", + ["Ị"] = "ị", + ["Ọ"] = "ọ", + ["Ỏ"] = "ỏ", + ["Ố"] = "ố", + ["Ồ"] = "ồ", + ["Ổ"] = "ổ", + ["Ỗ"] = "ỗ", + ["Ộ"] = "ộ", + ["Ớ"] = "ớ", + ["Ờ"] = "ờ", + ["Ở"] = "ở", + ["Ỡ"] = "ỡ", + ["Ợ"] = "ợ", + ["Ụ"] = "ụ", + ["Ủ"] = "ủ", + ["Ứ"] = "ứ", + ["Ừ"] = "ừ", + ["Ử"] = "ử", + ["Ữ"] = "ữ", + ["Ự"] = "ự", + ["Ỳ"] = "ỳ", + ["Ỵ"] = "ỵ", + ["Ỷ"] = "ỷ", + ["Ỹ"] = "ỹ", + ["Ἀ"] = "ἀ", + ["Ἁ"] = "ἁ", + ["Ἂ"] = "ἂ", + ["Ἃ"] = "ἃ", + ["Ἄ"] = "ἄ", + ["Ἅ"] = "ἅ", + ["Ἆ"] = "ἆ", + ["Ἇ"] = "ἇ", + ["Ἐ"] = "ἐ", + ["Ἑ"] = "ἑ", + ["Ἒ"] = "ἒ", + ["Ἓ"] = "ἓ", + ["Ἔ"] = "ἔ", + ["Ἕ"] = "ἕ", + ["Ἠ"] = "ἠ", + ["Ἡ"] = "ἡ", + ["Ἢ"] = "ἢ", + ["Ἣ"] = "ἣ", + ["Ἤ"] = "ἤ", + ["Ἥ"] = "ἥ", + ["Ἦ"] = "ἦ", + ["Ἧ"] = "ἧ", + ["Ἰ"] = "ἰ", + ["Ἱ"] = "ἱ", + ["Ἲ"] = "ἲ", + ["Ἳ"] = "ἳ", + ["Ἴ"] = "ἴ", + ["Ἵ"] = "ἵ", + ["Ἶ"] = "ἶ", + ["Ἷ"] = "ἷ", + ["Ὀ"] = "ὀ", + ["Ὁ"] = "ὁ", + ["Ὂ"] = "ὂ", + ["Ὃ"] = "ὃ", + ["Ὄ"] = "ὄ", + ["Ὅ"] = "ὅ", + ["Ὑ"] = "ὑ", + ["Ὓ"] = "ὓ", + ["Ὕ"] = "ὕ", + ["Ὗ"] = "ὗ", + ["Ὠ"] = "ὠ", + ["Ὡ"] = "ὡ", + ["Ὢ"] = "ὢ", + ["Ὣ"] = "ὣ", + ["Ὤ"] = "ὤ", + ["Ὥ"] = "ὥ", + ["Ὦ"] = "ὦ", + ["Ὧ"] = "ὧ", + ["ᾈ"] = "ᾀ", + ["ᾉ"] = "ᾁ", + ["ᾊ"] = "ᾂ", + ["ᾋ"] = "ᾃ", + ["ᾌ"] = "ᾄ", + ["ᾍ"] = "ᾅ", + ["ᾎ"] = "ᾆ", + ["ᾏ"] = "ᾇ", + ["ᾘ"] = "ᾐ", + ["ᾙ"] = "ᾑ", + ["ᾚ"] = "ᾒ", + ["ᾛ"] = "ᾓ", + ["ᾜ"] = "ᾔ", + ["ᾝ"] = "ᾕ", + ["ᾞ"] = "ᾖ", + ["ᾟ"] = "ᾗ", + ["ᾨ"] = "ᾠ", + ["ᾩ"] = "ᾡ", + ["ᾪ"] = "ᾢ", + ["ᾫ"] = "ᾣ", + ["ᾬ"] = "ᾤ", + ["ᾭ"] = "ᾥ", + ["ᾮ"] = "ᾦ", + ["ᾯ"] = "ᾧ", + ["Ᾰ"] = "ᾰ", + ["Ᾱ"] = "ᾱ", + ["Ὰ"] = "ὰ", + ["Ά"] = "ά", + ["ᾼ"] = "ᾳ", + ["Ὲ"] = "ὲ", + ["Έ"] = "έ", + ["Ὴ"] = "ὴ", + ["Ή"] = "ή", + ["ῌ"] = "ῃ", + ["Ῐ"] = "ῐ", + ["Ῑ"] = "ῑ", + ["Ὶ"] = "ὶ", + ["Ί"] = "ί", + ["Ῠ"] = "ῠ", + ["Ῡ"] = "ῡ", + ["Ὺ"] = "ὺ", + ["Ύ"] = "ύ", + ["Ῥ"] = "ῥ", + ["Ὸ"] = "ὸ", + ["Ό"] = "ό", + ["Ὼ"] = "ὼ", + ["Ώ"] = "ώ", + ["ῼ"] = "ῳ", + ["Ω"] = "ω", + ["K"] = "k", + ["Å"] = "å", + ["Ⅎ"] = "ⅎ", + ["Ⅰ"] = "ⅰ", + ["Ⅱ"] = "ⅱ", + ["Ⅲ"] = "ⅲ", + ["Ⅳ"] = "ⅳ", + ["Ⅴ"] = "ⅴ", + ["Ⅵ"] = "ⅵ", + ["Ⅶ"] = "ⅶ", + ["Ⅷ"] = "ⅷ", + ["Ⅸ"] = "ⅸ", + ["Ⅹ"] = "ⅹ", + ["Ⅺ"] = "ⅺ", + ["Ⅻ"] = "ⅻ", + ["Ⅼ"] = "ⅼ", + ["Ⅽ"] = "ⅽ", + ["Ⅾ"] = "ⅾ", + ["Ⅿ"] = "ⅿ", + ["Ↄ"] = "ↄ", + ["Ⓐ"] = "ⓐ", + ["Ⓑ"] = "ⓑ", + ["Ⓒ"] = "ⓒ", + ["Ⓓ"] = "ⓓ", + ["Ⓔ"] = "ⓔ", + ["Ⓕ"] = "ⓕ", + ["Ⓖ"] = "ⓖ", + ["Ⓗ"] = "ⓗ", + ["Ⓘ"] = "ⓘ", + ["Ⓙ"] = "ⓙ", + ["Ⓚ"] = "ⓚ", + ["Ⓛ"] = "ⓛ", + ["Ⓜ"] = "ⓜ", + ["Ⓝ"] = "ⓝ", + ["Ⓞ"] = "ⓞ", + ["Ⓟ"] = "ⓟ", + ["Ⓠ"] = "ⓠ", + ["Ⓡ"] = "ⓡ", + ["Ⓢ"] = "ⓢ", + ["Ⓣ"] = "ⓣ", + ["Ⓤ"] = "ⓤ", + ["Ⓥ"] = "ⓥ", + ["Ⓦ"] = "ⓦ", + ["Ⓧ"] = "ⓧ", + ["Ⓨ"] = "ⓨ", + ["Ⓩ"] = "ⓩ", + ["Ⰰ"] = "ⰰ", + ["Ⰱ"] = "ⰱ", + ["Ⰲ"] = "ⰲ", + ["Ⰳ"] = "ⰳ", + ["Ⰴ"] = "ⰴ", + ["Ⰵ"] = "ⰵ", + ["Ⰶ"] = "ⰶ", + ["Ⰷ"] = "ⰷ", + ["Ⰸ"] = "ⰸ", + ["Ⰹ"] = "ⰹ", + ["Ⰺ"] = "ⰺ", + ["Ⰻ"] = "ⰻ", + ["Ⰼ"] = "ⰼ", + ["Ⰽ"] = "ⰽ", + ["Ⰾ"] = "ⰾ", + ["Ⰿ"] = "ⰿ", + ["Ⱀ"] = "ⱀ", + ["Ⱁ"] = "ⱁ", + ["Ⱂ"] = "ⱂ", + ["Ⱃ"] = "ⱃ", + ["Ⱄ"] = "ⱄ", + ["Ⱅ"] = "ⱅ", + ["Ⱆ"] = "ⱆ", + ["Ⱇ"] = "ⱇ", + ["Ⱈ"] = "ⱈ", + ["Ⱉ"] = "ⱉ", + ["Ⱊ"] = "ⱊ", + ["Ⱋ"] = "ⱋ", + ["Ⱌ"] = "ⱌ", + ["Ⱍ"] = "ⱍ", + ["Ⱎ"] = "ⱎ", + ["Ⱏ"] = "ⱏ", + ["Ⱐ"] = "ⱐ", + ["Ⱑ"] = "ⱑ", + ["Ⱒ"] = "ⱒ", + ["Ⱓ"] = "ⱓ", + ["Ⱔ"] = "ⱔ", + ["Ⱕ"] = "ⱕ", + ["Ⱖ"] = "ⱖ", + ["Ⱗ"] = "ⱗ", + ["Ⱘ"] = "ⱘ", + ["Ⱙ"] = "ⱙ", + ["Ⱚ"] = "ⱚ", + ["Ⱛ"] = "ⱛ", + ["Ⱜ"] = "ⱜ", + ["Ⱝ"] = "ⱝ", + ["Ⱞ"] = "ⱞ", + ["Ⱡ"] = "ⱡ", + ["Ɫ"] = "ɫ", + ["Ᵽ"] = "ᵽ", + ["Ɽ"] = "ɽ", + ["Ⱨ"] = "ⱨ", + ["Ⱪ"] = "ⱪ", + ["Ⱬ"] = "ⱬ", + ["Ⱶ"] = "ⱶ", + ["Ⲁ"] = "ⲁ", + ["Ⲃ"] = "ⲃ", + ["Ⲅ"] = "ⲅ", + ["Ⲇ"] = "ⲇ", + ["Ⲉ"] = "ⲉ", + ["Ⲋ"] = "ⲋ", + ["Ⲍ"] = "ⲍ", + ["Ⲏ"] = "ⲏ", + ["Ⲑ"] = "ⲑ", + ["Ⲓ"] = "ⲓ", + ["Ⲕ"] = "ⲕ", + ["Ⲗ"] = "ⲗ", + ["Ⲙ"] = "ⲙ", + ["Ⲛ"] = "ⲛ", + ["Ⲝ"] = "ⲝ", + ["Ⲟ"] = "ⲟ", + ["Ⲡ"] = "ⲡ", + ["Ⲣ"] = "ⲣ", + ["Ⲥ"] = "ⲥ", + ["Ⲧ"] = "ⲧ", + ["Ⲩ"] = "ⲩ", + ["Ⲫ"] = "ⲫ", + ["Ⲭ"] = "ⲭ", + ["Ⲯ"] = "ⲯ", + ["Ⲱ"] = "ⲱ", + ["Ⲳ"] = "ⲳ", + ["Ⲵ"] = "ⲵ", + ["Ⲷ"] = "ⲷ", + ["Ⲹ"] = "ⲹ", + ["Ⲻ"] = "ⲻ", + ["Ⲽ"] = "ⲽ", + ["Ⲿ"] = "ⲿ", + ["Ⳁ"] = "ⳁ", + ["Ⳃ"] = "ⳃ", + ["Ⳅ"] = "ⳅ", + ["Ⳇ"] = "ⳇ", + ["Ⳉ"] = "ⳉ", + ["Ⳋ"] = "ⳋ", + ["Ⳍ"] = "ⳍ", + ["Ⳏ"] = "ⳏ", + ["Ⳑ"] = "ⳑ", + ["Ⳓ"] = "ⳓ", + ["Ⳕ"] = "ⳕ", + ["Ⳗ"] = "ⳗ", + ["Ⳙ"] = "ⳙ", + ["Ⳛ"] = "ⳛ", + ["Ⳝ"] = "ⳝ", + ["Ⳟ"] = "ⳟ", + ["Ⳡ"] = "ⳡ", + ["Ⳣ"] = "ⳣ", + ["A"] = "a", + ["B"] = "b", + ["C"] = "c", + ["D"] = "d", + ["E"] = "e", + ["F"] = "f", + ["G"] = "g", + ["H"] = "h", + ["I"] = "i", + ["J"] = "j", + ["K"] = "k", + ["L"] = "l", + ["M"] = "m", + ["N"] = "n", + ["O"] = "o", + ["P"] = "p", + ["Q"] = "q", + ["R"] = "r", + ["S"] = "s", + ["T"] = "t", + ["U"] = "u", + ["V"] = "v", + ["W"] = "w", + ["X"] = "x", + ["Y"] = "y", + ["Z"] = "z", + ["𐐀"] = "𐐨", + ["𐐁"] = "𐐩", + ["𐐂"] = "𐐪", + ["𐐃"] = "𐐫", + ["𐐄"] = "𐐬", + ["𐐅"] = "𐐭", + ["𐐆"] = "𐐮", + ["𐐇"] = "𐐯", + ["𐐈"] = "𐐰", + ["𐐉"] = "𐐱", + ["𐐊"] = "𐐲", + ["𐐋"] = "𐐳", + ["𐐌"] = "𐐴", + ["𐐍"] = "𐐵", + ["𐐎"] = "𐐶", + ["𐐏"] = "𐐷", + ["𐐐"] = "𐐸", + ["𐐑"] = "𐐹", + ["𐐒"] = "𐐺", + ["𐐓"] = "𐐻", + ["𐐔"] = "𐐼", + ["𐐕"] = "𐐽", + ["𐐖"] = "𐐾", + ["𐐗"] = "𐐿", + ["𐐘"] = "𐑀", + ["𐐙"] = "𐑁", + ["𐐚"] = "𐑂", + ["𐐛"] = "𐑃", + ["𐐜"] = "𐑄", + ["𐐝"] = "𐑅", + ["𐐞"] = "𐑆", + ["𐐟"] = "𐑇", + ["𐐠"] = "𐑈", + ["𐐡"] = "𐑉", + ["𐐢"] = "𐑊", + ["𐐣"] = "𐑋", + ["𐐤"] = "𐑌", + ["𐐥"] = "𐑍", + ["𐐦"] = "𐑎", + ["𐐧"] = "𐑏", +} + diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua new file mode 100644 index 0000000..cc0993e --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/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/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua new file mode 100644 index 0000000..02320f9 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua @@ -0,0 +1,781 @@ +--------------------------------------------------------------------------------------- +-- Module for date and time calculations +-- +-- Version 2.1.1 +-- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com) +-- Copyright (C) 2013-2014, by Thijs Schreijer +-- Licensed under MIT, http://opensource.org/licenses/MIT +-- https://github.com/Tieske/date + +--[[ +The MIT License (MIT) http://opensource.org/licenses/MIT + +Copyright (c) 2013-2017 Thijs Schreijer + +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. +]] + +--[[ CONSTANTS ]]-- + local HOURPERDAY = 24 + local MINPERHOUR = 60 + local MINPERDAY = 1440 -- 24*60 + local SECPERMIN = 60 + local SECPERHOUR = 3600 -- 60*60 + local SECPERDAY = 86400 -- 24*60*60 + local TICKSPERSEC = 1000000 + local TICKSPERDAY = 86400000000 + local TICKSPERHOUR = 3600000000 + local TICKSPERMIN = 60000000 + local DAYNUM_MAX = 365242500 -- Sat Jan 01 1000000 00:00:00 + local DAYNUM_MIN = -365242500 -- Mon Jan 01 1000000 BCE 00:00:00 + local DAYNUM_DEF = 0 -- Mon Jan 01 0001 00:00:00 + local _; +--[[ LOCAL ARE FASTER ]]-- + local type = type + local pairs = pairs + local error = error + local assert = assert + local tonumber = tonumber + local tostring = tostring + local string = string + local math = math + local os = os + local unpack = unpack or table.unpack + local pack = table.pack or function(...) return { n = select('#', ...), ... } end + local setmetatable = setmetatable + local getmetatable = getmetatable +--[[ EXTRA FUNCTIONS ]]-- + local fmt = string.format + local lwr = string.lower + local upr = string.upper + local rep = string.rep + local len = string.len + local sub = string.sub + local gsub = string.gsub + local gmatch = string.gmatch or string.gfind + local find = string.find + local ostime = os.time + local osdate = os.date + local floor = math.floor + local ceil = math.ceil + local abs = math.abs + -- removes the decimal part of a number + local function fix(n) n = tonumber(n) return n and ((n > 0 and floor or ceil)(n)) end + -- returns the modulo n % d; + local function mod(n,d) return n - d*floor(n/d) end + -- rounds a number; + local function round(n, d) d=d^10 return floor((n*d)+.5)/d end + -- rounds a number to whole; + local function whole(n)return floor(n+.5)end + -- is `str` in string list `tbl`, `ml` is the minimun len + local function inlist(str, tbl, ml, tn) + local sl = len(str) + if sl < (ml or 0) then return nil end + str = lwr(str) + for k, v in pairs(tbl) do + if str == lwr(sub(v, 1, sl)) then + if tn then tn[0] = k end + return k + end + end + end + local function fnil() end + local function fret(x)return x;end +--[[ DATE FUNCTIONS ]]-- + local DATE_EPOCH -- to be set later + local sl_weekdays = { + [0]="Sunday",[1]="Monday",[2]="Tuesday",[3]="Wednesday",[4]="Thursday",[5]="Friday",[6]="Saturday", + [7]="Sun",[8]="Mon",[9]="Tue",[10]="Wed",[11]="Thu",[12]="Fri",[13]="Sat", + } + local sl_meridian = {[-1]="AM", [1]="PM"} + local sl_months = { + [00]="January", [01]="February", [02]="March", + [03]="April", [04]="May", [05]="June", + [06]="July", [07]="August", [08]="September", + [09]="October", [10]="November", [11]="December", + [12]="Jan", [13]="Feb", [14]="Mar", + [15]="Apr", [16]="May", [17]="Jun", + [18]="Jul", [19]="Aug", [20]="Sep", + [21]="Oct", [22]="Nov", [23]="Dec", + } + -- added the '.2' to avoid collision, use `fix` to remove + local sl_timezone = { + [000]="utc", [0.2]="gmt", + [300]="est", [240]="edt", + [360]="cst", [300.2]="cdt", + [420]="mst", [360.2]="mdt", + [480]="pst", [420.2]="pdt", + } + -- set the day fraction resolution + local function setticks(t) + TICKSPERSEC = t; + TICKSPERDAY = SECPERDAY*TICKSPERSEC + TICKSPERHOUR= SECPERHOUR*TICKSPERSEC + TICKSPERMIN = SECPERMIN*TICKSPERSEC + end + -- is year y leap year? + local function isleapyear(y) -- y must be int! + return (mod(y, 4) == 0 and (mod(y, 100) ~= 0 or mod(y, 400) == 0)) + end + -- day since year 0 + local function dayfromyear(y) -- y must be int! + return 365*y + floor(y/4) - floor(y/100) + floor(y/400) + end + -- day number from date, month is zero base + local function makedaynum(y, m, d) + local mm = mod(mod(m,12) + 10, 12) + return dayfromyear(y + floor(m/12) - floor(mm/10)) + floor((mm*306 + 5)/10) + d - 307 + --local yy = y + floor(m/12) - floor(mm/10) + --return dayfromyear(yy) + floor((mm*306 + 5)/10) + (d - 1) + end + -- date from day number, month is zero base + local function breakdaynum(g) + local g = g + 306 + local y = floor((10000*g + 14780)/3652425) + local d = g - dayfromyear(y) + if d < 0 then y = y - 1; d = g - dayfromyear(y) end + local mi = floor((100*d + 52)/3060) + return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) + end + --[[ for floats or int32 Lua Number data type + local function breakdaynum2(g) + local g, n = g + 306; + local n400 = floor(g/DI400Y);n = mod(g,DI400Y); + local n100 = floor(n/DI100Y);n = mod(n,DI100Y); + local n004 = floor(n/DI4Y); n = mod(n,DI4Y); + local n001 = floor(n/365); n = mod(n,365); + local y = (n400*400) + (n100*100) + (n004*4) + n001 - ((n001 == 4 or n100 == 4) and 1 or 0) + local d = g - dayfromyear(y) + local mi = floor((100*d + 52)/3060) + return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1) + end + ]] + -- day fraction from time + local function makedayfrc(h,r,s,t) + return ((h*60 + r)*60 + s)*TICKSPERSEC + t + end + -- time from day fraction + local function breakdayfrc(df) + return + mod(floor(df/TICKSPERHOUR),HOURPERDAY), + mod(floor(df/TICKSPERMIN ),MINPERHOUR), + mod(floor(df/TICKSPERSEC ),SECPERMIN), + mod(df,TICKSPERSEC) + end + -- weekday sunday = 0, monday = 1 ... + local function weekday(dn) return mod(dn + 1, 7) end + -- yearday 0 based ... + local function yearday(dn) + return dn - dayfromyear((breakdaynum(dn))-1) + end + -- parse v as a month + local function getmontharg(v) + local m = tonumber(v); + return (m and fix(m - 1)) or inlist(tostring(v) or "", sl_months, 2) + end + -- get daynum of isoweek one of year y + local function isow1(y) + local f = makedaynum(y, 0, 4) -- get the date for the 4-Jan of year `y` + local d = weekday(f) + d = d == 0 and 7 or d -- get the ISO day number, 1 == Monday, 7 == Sunday + return f + (1 - d) + end + local function isowy(dn) + local w1; + local y = (breakdaynum(dn)) + if dn >= makedaynum(y, 11, 29) then + w1 = isow1(y + 1); + if dn < w1 then + w1 = isow1(y); + else + y = y + 1; + end + else + w1 = isow1(y); + if dn < w1 then + w1 = isow1(y-1) + y = y - 1 + end + end + return floor((dn-w1)/7)+1, y + end + local function isoy(dn) + local y = (breakdaynum(dn)) + return y + (((dn >= makedaynum(y, 11, 29)) and (dn >= isow1(y + 1))) and 1 or (dn < isow1(y) and -1 or 0)) + end + local function makedaynum_isoywd(y,w,d) + return isow1(y) + 7*w + d - 8 -- simplified: isow1(y) + ((w-1)*7) + (d-1) + end +--[[ THE DATE MODULE ]]-- + local fmtstr = "%x %X"; +--#if not DATE_OBJECT_AFX then + local date = {} + setmetatable(date, date) +-- Version: VMMMRRRR; V-Major, M-Minor, R-Revision; e.g. 5.45.321 == 50450321 + date.version = 20010001 -- 2.1.1 +--#end -- not DATE_OBJECT_AFX +--[[ THE DATE OBJECT ]]-- + local dobj = {} + dobj.__index = dobj + dobj.__metatable = dobj + -- shout invalid arg + local function date_error_arg() return error("invalid argument(s)",0) end + -- create new date object + local function date_new(dn, df) + return setmetatable({daynum=dn, dayfrc=df}, dobj) + end + -- is `v` a date object? + local function date_isdobj(v) + return (istable(v) and getmetatable(v) == dobj) and v + end + +--#if not NO_LOCAL_TIME_SUPPORT then + -- magic year table + local date_epoch, yt; + local function getequivyear(y) + assert(not yt) + yt = {} + local de, dw, dy = date_epoch:copy() + for i = 0, 3000 do + de:setyear(de:getyear() + 1, 1, 1) + dy = de:getyear() + dw = de:getweekday() * (isleapyear(dy) and -1 or 1) + if not yt[dw] then yt[dw] = dy end --print(de) + if yt[1] and yt[2] and yt[3] and yt[4] and yt[5] and yt[6] and yt[7] and yt[-1] and yt[-2] and yt[-3] and yt[-4] and yt[-5] and yt[-6] and yt[-7] then + getequivyear = function(y) return yt[ (weekday(makedaynum(y, 0, 1)) + 1) * (isleapyear(y) and -1 or 1) ] end + return getequivyear(y) + end + end + end + -- TimeValue from daynum and dayfrc + local function dvtotv(dn, df) + return fix(dn - DATE_EPOCH) * SECPERDAY + (df/1000) + end + -- TimeValue from date and time + local function totv(y,m,d,h,r,s) + return (makedaynum(y, m, d) - DATE_EPOCH) * SECPERDAY + ((h*60 + r)*60 + s) + end + -- TimeValue from TimeTable + local function tmtotv(tm) + return tm and totv(tm.year, tm.month - 1, tm.day, tm.hour, tm.min, tm.sec) + end + -- Returns the bias in seconds of utc time daynum and dayfrc + local function getbiasutc2(self) + local y,m,d = breakdaynum(self.daynum) + local h,r,s = breakdayfrc(self.dayfrc) + local tvu = totv(y,m,d,h,r,s) -- get the utc TimeValue of date and time + local tml = osdate("*t", tvu) -- get the local TimeTable of tvu + if (not tml) or (tml.year > (y+1) or tml.year < (y-1)) then -- failed try the magic + y = getequivyear(y) + tvu = totv(y,m,d,h,r,s) + tml = osdate("*t", tvu) + end + local tvl = tmtotv(tml) + if tvu and tvl then + return tvu - tvl, tvu, tvl + else + return error("failed to get bias from utc time") + end + end + -- Returns the bias in seconds of local time daynum and dayfrc + local function getbiasloc2(daynum, dayfrc) + local tvu + -- extract date and time + local y,m,d = breakdaynum(daynum) + local h,r,s = breakdayfrc(dayfrc) + -- get equivalent TimeTable + local tml = {year=y, month=m+1, day=d, hour=h, min=r, sec=s} + -- get equivalent TimeValue + local tvl = tmtotv(tml) + + local function chkutc() + tml.isdst = nil; local tvug = ostime(tml) if tvug and (tvl == tmtotv(osdate("*t", tvug))) then tvu = tvug return end + tml.isdst = true; local tvud = ostime(tml) if tvud and (tvl == tmtotv(osdate("*t", tvud))) then tvu = tvud return end + tvu = tvud or tvug + end + chkutc() + if not tvu then + tml.year = getequivyear(y) + tvl = tmtotv(tml) + chkutc() + end + return ((tvu and tvl) and (tvu - tvl)) or error("failed to get bias from local time"), tvu, tvl + end +--#end -- not NO_LOCAL_TIME_SUPPORT + +--#if not DATE_OBJECT_AFX then + -- the date parser + local strwalker = {} -- ^Lua regular expression is not as powerful as Perl$ + strwalker.__index = strwalker + local function newstrwalker(s)return setmetatable({s=s, i=1, e=1, c=len(s)}, strwalker) end + function strwalker:aimchr() return "\n" .. self.s .. "\n" .. rep(".",self.e-1) .. "^" end + function strwalker:finish() return self.i > self.c end + function strwalker:back() self.i = self.e return self end + function strwalker:restart() self.i, self.e = 1, 1 return self end + function strwalker:match(s) return (find(self.s, s, self.i)) end + function strwalker:__call(s, f)-- print("strwalker:__call "..s..self:aimchr()) + local is, ie; is, ie, self[1], self[2], self[3], self[4], self[5] = find(self.s, s, self.i) + if is then self.e, self.i = self.i, 1+ie; if f then f(unpack(self)) end return self end + end + local function date_parse(str) + local y,m,d, h,r,s, z, w,u, j, e, k, x,v,c, chkfin, dn,df; + local sw = newstrwalker(gsub(gsub(str, "(%b())", ""),"^(%s*)","")) -- remove comment, trim leading space + --local function error_out() print(y,m,d,h,r,s) end + local function error_dup(q) --[[error_out()]] error("duplicate value: " .. (q or "") .. sw:aimchr()) end + local function error_syn(q) --[[error_out()]] error("syntax error: " .. (q or "") .. sw:aimchr()) end + local function error_inv(q) --[[error_out()]] error("invalid date: " .. (q or "") .. sw:aimchr()) end + local function sety(q) y = y and error_dup() or tonumber(q); end + local function setm(q) m = (m or w or j) and error_dup(m or w or j) or tonumber(q) end + local function setd(q) d = d and error_dup() or tonumber(q) end + local function seth(q) h = h and error_dup() or tonumber(q) end + local function setr(q) r = r and error_dup() or tonumber(q) end + local function sets(q) s = s and error_dup() or tonumber(q) end + local function adds(q) s = s + tonumber(q) end + local function setj(q) j = (m or w or j) and error_dup() or tonumber(q); end + local function setz(q) z = (z ~= 0 and z) and error_dup() or q end + local function setzn(zs,zn) zn = tonumber(zn); setz( ((zn<24) and (zn*60) or (mod(zn,100) + floor(zn/100) * 60))*( zs=='+' and -1 or 1) ) end + local function setzc(zs,zh,zm) setz( ((tonumber(zh)*60) + tonumber(zm))*( zs=='+' and -1 or 1) ) end + + if not (sw("^(%d%d%d%d)",sety) and (sw("^(%-?)(%d%d)%1(%d%d)",function(_,a,b) setm(tonumber(a)); setd(tonumber(b)) end) or sw("^(%-?)[Ww](%d%d)%1(%d?)",function(_,a,b) w, u = tonumber(a), tonumber(b or 1) end) or sw("^%-?(%d%d%d)",setj) or sw("^%-?(%d%d)",function(a) setm(a);setd(1) end)) + and ((sw("^%s*[Tt]?(%d%d):?",seth) and sw("^(%d%d):?",setr) and sw("^(%d%d)",sets) and sw("^(%.%d+)",adds)) + or sw:finish() or (sw"^%s*$" or sw"^%s*[Zz]%s*$" or sw("^%s-([%+%-])(%d%d):?(%d%d)%s*$",setzc) or sw("^%s*([%+%-])(%d%d)%s*$",setzn)) + ) ) + then --print(y,m,d,h,r,s,z,w,u,j) + sw:restart(); y,m,d,h,r,s,z,w,u,j = nil; + repeat -- print(sw:aimchr()) + if sw("^[tT:]?%s*(%d%d?):",seth) then --print("$Time") + _ = sw("^%s*(%d%d?)",setr) and sw("^%s*:%s*(%d%d?)",sets) and sw("^(%.%d+)",adds) + elseif sw("^(%d+)[/\\%s,-]?%s*") then --print("$Digits") + x, c = tonumber(sw[1]), len(sw[1]) + if (x >= 70) or (m and d and (not y)) or (c > 3) then + sety( x + ((x >= 100 or c>3)and 0 or 1900) ) + else + if m then setd(x) else m = x end + end + elseif sw("^(%a+)[/\\%s,-]?%s*") then --print("$Words") + x = sw[1] + if inlist(x, sl_months, 2, sw) then + if m and (not d) and (not y) then d, m = m, false end + setm(mod(sw[0],12)+1) + elseif inlist(x, sl_timezone, 2, sw) then + c = fix(sw[0]) -- ignore gmt and utc + if c ~= 0 then setz(c, x) end + elseif inlist(x, sl_weekdays, 2, sw) then + k = sw[0] + else + sw:back() + -- am pm bce ad ce bc + if sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*[Ee]%s*(%2)%s*") or sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*") then + e = e and error_dup() or -1 + elseif sw("^([aA])%s*(%.?)%s*[Dd]%s*(%2)%s*") or sw("^([cC])%s*(%.?)%s*[Ee]%s*(%2)%s*") then + e = e and error_dup() or 1 + elseif sw("^([PApa])%s*(%.?)%s*[Mm]?%s*(%2)%s*") then + x = lwr(sw[1]) -- there should be hour and it must be correct + if (not h) or (h > 12) or (h < 0) then return error_inv() end + if x == 'a' and h == 12 then h = 0 end -- am + if x == 'p' and h ~= 12 then h = h + 12 end -- pm + else error_syn() end + end + elseif not(sw("^([+-])(%d%d?):(%d%d)",setzc) or sw("^([+-])(%d+)",setzn) or sw("^[Zz]%s*$")) then -- sw{"([+-])",{"(%d%d?):(%d%d)","(%d+)"}} + error_syn("?") + end + sw("^%s*") until sw:finish() + --else print("$Iso(Date|Time|Zone)") + end + -- if date is given, it must be complete year, month & day + if (not y and not h) or ((m and not d) or (d and not m)) or ((m and w) or (m and j) or (j and w)) then return error_inv("!") end + -- fix month + if m then m = m - 1 end + -- fix year if we are on BCE + if e and e < 0 and y > 0 then y = 1 - y end + -- create date object + dn = (y and ((w and makedaynum_isoywd(y,w,u)) or (j and makedaynum(y, 0, j)) or makedaynum(y, m, d))) or DAYNUM_DEF + df = makedayfrc(h or 0, r or 0, s or 0, 0) + ((z or 0)*TICKSPERMIN) + --print("Zone",h,r,s,z,m,d,y,df) + return date_new(dn, df) -- no need to :normalize(); + end + local function date_fromtable(v) + local y, m, d = fix(v.year), getmontharg(v.month), fix(v.day) + local h, r, s, t = tonumber(v.hour), tonumber(v.min), tonumber(v.sec), tonumber(v.ticks) + -- atleast there is time or complete date + if (y or m or d) and (not(y and m and d)) then return error("incomplete table") end + return (y or h or r or s or t) and date_new(y and makedaynum(y, m, d) or DAYNUM_DEF, makedayfrc(h or 0, r or 0, s or 0, t or 0)) + end + local tmap = { + ['number'] = function(v) return date_epoch:copy():addseconds(v) end, + ['string'] = function(v) return date_parse(v) end, + ['boolean']= function(v) return date_fromtable(osdate(v and "!*t" or "*t")) end, + ['table'] = function(v) local ref = getmetatable(v) == dobj; return ref and v or date_fromtable(v), ref end + } + local function date_getdobj(v) + local o, r = (tmap[type(v)] or fnil)(v); + return (o and o:normalize() or error"invalid date time value"), r -- if r is true then o is a reference to a date obj + end +--#end -- not DATE_OBJECT_AFX + local function date_from(...) + local arg = pack(...) + local y, m, d = fix(arg[1]), getmontharg(arg[2]), fix(arg[3]) + local h, r, s, t = tonumber(arg[4] or 0), tonumber(arg[5] or 0), tonumber(arg[6] or 0), tonumber(arg[7] or 0) + if y and m and d and h and r and s and t then + return date_new(makedaynum(y, m, d), makedayfrc(h, r, s, t)):normalize() + else + return date_error_arg() + end + end + + --[[ THE DATE OBJECT METHODS ]]-- + function dobj:normalize() + local dn, df = fix(self.daynum), self.dayfrc + self.daynum, self.dayfrc = dn + floor(df/TICKSPERDAY), mod(df, TICKSPERDAY) + return (dn >= DAYNUM_MIN and dn <= DAYNUM_MAX) and self or error("date beyond imposed limits:"..self) + end + + function dobj:getdate() local y, m, d = breakdaynum(self.daynum) return y, m+1, d end + function dobj:gettime() return breakdayfrc(self.dayfrc) end + + function dobj:getclockhour() local h = self:gethours() return h>12 and mod(h,12) or (h==0 and 12 or h) end + + function dobj:getyearday() return yearday(self.daynum) + 1 end + function dobj:getweekday() return weekday(self.daynum) + 1 end -- in lua weekday is sunday = 1, monday = 2 ... + + function dobj:getyear() local r,_,_ = breakdaynum(self.daynum) return r end + function dobj:getmonth() local _,r,_ = breakdaynum(self.daynum) return r+1 end-- in lua month is 1 base + function dobj:getday() local _,_,r = breakdaynum(self.daynum) return r end + function dobj:gethours() return mod(floor(self.dayfrc/TICKSPERHOUR),HOURPERDAY) end + function dobj:getminutes() return mod(floor(self.dayfrc/TICKSPERMIN), MINPERHOUR) end + function dobj:getseconds() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN) end + function dobj:getfracsec() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN)+(mod(self.dayfrc,TICKSPERSEC)/TICKSPERSEC) end + function dobj:getticks(u) local x = mod(self.dayfrc,TICKSPERSEC) return u and ((x*u)/TICKSPERSEC) or x end + + function dobj:getweeknumber(wdb) + local wd, yd = weekday(self.daynum), yearday(self.daynum) + if wdb then + wdb = tonumber(wdb) + if wdb then + wd = mod(wd-(wdb-1),7)-- shift the week day base + else + return date_error_arg() + end + end + return (yd < wd and 0) or (floor(yd/7) + ((mod(yd, 7)>=wd) and 1 or 0)) + end + + function dobj:getisoweekday() return mod(weekday(self.daynum)-1,7)+1 end -- sunday = 7, monday = 1 ... + function dobj:getisoweeknumber() return (isowy(self.daynum)) end + function dobj:getisoyear() return isoy(self.daynum) end + function dobj:getisodate() + local w, y = isowy(self.daynum) + return y, w, self:getisoweekday() + end + function dobj:setisoyear(y, w, d) + local cy, cw, cd = self:getisodate() + if y then cy = fix(tonumber(y))end + if w then cw = fix(tonumber(w))end + if d then cd = fix(tonumber(d))end + if cy and cw and cd then + self.daynum = makedaynum_isoywd(cy, cw, cd) + return self:normalize() + else + return date_error_arg() + end + end + + function dobj:setisoweekday(d) return self:setisoyear(nil, nil, d) end + function dobj:setisoweeknumber(w,d) return self:setisoyear(nil, w, d) end + + function dobj:setyear(y, m, d) + local cy, cm, cd = breakdaynum(self.daynum) + if y then cy = fix(tonumber(y))end + if m then cm = getmontharg(m) end + if d then cd = fix(tonumber(d))end + if cy and cm and cd then + self.daynum = makedaynum(cy, cm, cd) + return self:normalize() + else + return date_error_arg() + end + end + + function dobj:setmonth(m, d)return self:setyear(nil, m, d) end + function dobj:setday(d) return self:setyear(nil, nil, d) end + + function dobj:sethours(h, m, s, t) + local ch,cm,cs,ck = breakdayfrc(self.dayfrc) + ch, cm, cs, ck = tonumber(h or ch), tonumber(m or cm), tonumber(s or cs), tonumber(t or ck) + if ch and cm and cs and ck then + self.dayfrc = makedayfrc(ch, cm, cs, ck) + return self:normalize() + else + return date_error_arg() + end + end + + function dobj:setminutes(m,s,t) return self:sethours(nil, m, s, t) end + function dobj:setseconds(s, t) return self:sethours(nil, nil, s, t) end + function dobj:setticks(t) return self:sethours(nil, nil, nil, t) end + + function dobj:spanticks() return (self.daynum*TICKSPERDAY + self.dayfrc) end + function dobj:spanseconds() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERSEC end + function dobj:spanminutes() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERMIN end + function dobj:spanhours() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERHOUR end + function dobj:spandays() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERDAY end + + function dobj:addyears(y, m, d) + local cy, cm, cd = breakdaynum(self.daynum) + if y then y = fix(tonumber(y))else y = 0 end + if m then m = fix(tonumber(m))else m = 0 end + if d then d = fix(tonumber(d))else d = 0 end + if y and m and d then + self.daynum = makedaynum(cy+y, cm+m, cd+d) + return self:normalize() + else + return date_error_arg() + end + end + + function dobj:addmonths(m, d) + return self:addyears(nil, m, d) + end + + local function dobj_adddayfrc(self,n,pt,pd) + n = tonumber(n) + if n then + local x = floor(n/pd); + self.daynum = self.daynum + x; + self.dayfrc = self.dayfrc + (n-x*pd)*pt; + return self:normalize() + else + return date_error_arg() + end + end + function dobj:adddays(n) return dobj_adddayfrc(self,n,TICKSPERDAY,1) end + function dobj:addhours(n) return dobj_adddayfrc(self,n,TICKSPERHOUR,HOURPERDAY) end + function dobj:addminutes(n) return dobj_adddayfrc(self,n,TICKSPERMIN,MINPERDAY) end + function dobj:addseconds(n) return dobj_adddayfrc(self,n,TICKSPERSEC,SECPERDAY) end + function dobj:addticks(n) return dobj_adddayfrc(self,n,1,TICKSPERDAY) end + local tvspec = { + -- Abbreviated weekday name (Sun) + ['%a']=function(self) return sl_weekdays[weekday(self.daynum) + 7] end, + -- Full weekday name (Sunday) + ['%A']=function(self) return sl_weekdays[weekday(self.daynum)] end, + -- Abbreviated month name (Dec) + ['%b']=function(self) return sl_months[self:getmonth() - 1 + 12] end, + -- Full month name (December) + ['%B']=function(self) return sl_months[self:getmonth() - 1] end, + -- Year/100 (19, 20, 30) + ['%C']=function(self) return fmt("%.2d", fix(self:getyear()/100)) end, + -- The day of the month as a number (range 1 - 31) + ['%d']=function(self) return fmt("%.2d", self:getday()) end, + -- year for ISO 8601 week, from 00 (79) + ['%g']=function(self) return fmt("%.2d", mod(self:getisoyear() ,100)) end, + -- year for ISO 8601 week, from 0000 (1979) + ['%G']=function(self) return fmt("%.4d", self:getisoyear()) end, + -- same as %b + ['%h']=function(self) return self:fmt0("%b") end, + -- hour of the 24-hour day, from 00 (06) + ['%H']=function(self) return fmt("%.2d", self:gethours()) end, + -- The hour as a number using a 12-hour clock (01 - 12) + ['%I']=function(self) return fmt("%.2d", self:getclockhour()) end, + -- The day of the year as a number (001 - 366) + ['%j']=function(self) return fmt("%.3d", self:getyearday()) end, + -- Month of the year, from 01 to 12 + ['%m']=function(self) return fmt("%.2d", self:getmonth()) end, + -- Minutes after the hour 55 + ['%M']=function(self) return fmt("%.2d", self:getminutes())end, + -- AM/PM indicator (AM) + ['%p']=function(self) return sl_meridian[self:gethours() > 11 and 1 or -1] end, --AM/PM indicator (AM) + -- The second as a number (59, 20 , 01) + ['%S']=function(self) return fmt("%.2d", self:getseconds()) end, + -- ISO 8601 day of the week, to 7 for Sunday (7, 1) + ['%u']=function(self) return self:getisoweekday() end, + -- Sunday week of the year, from 00 (48) + ['%U']=function(self) return fmt("%.2d", self:getweeknumber()) end, + -- ISO 8601 week of the year, from 01 (48) + ['%V']=function(self) return fmt("%.2d", self:getisoweeknumber()) end, + -- The day of the week as a decimal, Sunday being 0 + ['%w']=function(self) return self:getweekday() - 1 end, + -- Monday week of the year, from 00 (48) + ['%W']=function(self) return fmt("%.2d", self:getweeknumber(2)) end, + -- The year as a number without a century (range 00 to 99) + ['%y']=function(self) return fmt("%.2d", mod(self:getyear() ,100)) end, + -- Year with century (2000, 1914, 0325, 0001) + ['%Y']=function(self) return fmt("%.4d", self:getyear()) end, + -- Time zone offset, the date object is assumed local time (+1000, -0230) + ['%z']=function(self) local b = -self:getbias(); local x = abs(b); return fmt("%s%.4d", b < 0 and "-" or "+", fix(x/60)*100 + floor(mod(x,60))) end, + -- Time zone name, the date object is assumed local time + ['%Z']=function(self) return self:gettzname() end, + -- Misc -- + -- Year, if year is in BCE, prints the BCE Year representation, otherwise result is similar to "%Y" (1 BCE, 40 BCE) + ['%\b']=function(self) local x = self:getyear() return fmt("%.4d%s", x>0 and x or (-x+1), x>0 and "" or " BCE") end, + -- Seconds including fraction (59.998, 01.123) + ['%\f']=function(self) local x = self:getfracsec() return fmt("%s%.9f",x >= 10 and "" or "0", x) end, + -- percent character % + ['%%']=function(self) return "%" end, + -- Group Spec -- + -- 12-hour time, from 01:00:00 AM (06:55:15 AM); same as "%I:%M:%S %p" + ['%r']=function(self) return self:fmt0("%I:%M:%S %p") end, + -- hour:minute, from 01:00 (06:55); same as "%I:%M" + ['%R']=function(self) return self:fmt0("%I:%M") end, + -- 24-hour time, from 00:00:00 (06:55:15); same as "%H:%M:%S" + ['%T']=function(self) return self:fmt0("%H:%M:%S") end, + -- month/day/year from 01/01/00 (12/02/79); same as "%m/%d/%y" + ['%D']=function(self) return self:fmt0("%m/%d/%y") end, + -- year-month-day (1979-12-02); same as "%Y-%m-%d" + ['%F']=function(self) return self:fmt0("%Y-%m-%d") end, + -- The preferred date and time representation; same as "%x %X" + ['%c']=function(self) return self:fmt0("%x %X") end, + -- The preferred date representation, same as "%a %b %d %\b" + ['%x']=function(self) return self:fmt0("%a %b %d %\b") end, + -- The preferred time representation, same as "%H:%M:%\f" + ['%X']=function(self) return self:fmt0("%H:%M:%\f") end, + -- GroupSpec -- + -- Iso format, same as "%Y-%m-%dT%T" + ['${iso}'] = function(self) return self:fmt0("%Y-%m-%dT%T") end, + -- http format, same as "%a, %d %b %Y %T GMT" + ['${http}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, + -- ctime format, same as "%a %b %d %T GMT %Y" + ['${ctime}'] = function(self) return self:fmt0("%a %b %d %T GMT %Y") end, + -- RFC850 format, same as "%A, %d-%b-%y %T GMT" + ['${rfc850}'] = function(self) return self:fmt0("%A, %d-%b-%y %T GMT") end, + -- RFC1123 format, same as "%a, %d %b %Y %T GMT" + ['${rfc1123}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end, + -- asctime format, same as "%a %b %d %T %Y" + ['${asctime}'] = function(self) return self:fmt0("%a %b %d %T %Y") end, + } + function dobj:fmt0(str) return (gsub(str, "%%[%a%%\b\f]", function(x) local f = tvspec[x];return (f and f(self)) or x end)) end + function dobj:fmt(str) + str = str or self.fmtstr or fmtstr + return self:fmt0((gmatch(str, "${%w+}")) and (gsub(str, "${%w+}", function(x)local f=tvspec[x];return (f and f(self)) or x end)) or str) + end + + dobj.format = dobj.fmt + + function dobj.__lt(a, b) if (a.daynum == b.daynum) then return (a.dayfrc < b.dayfrc) else return (a.daynum < b.daynum) end end + function dobj.__le(a, b) if (a.daynum == b.daynum) then return (a.dayfrc <= b.dayfrc) else return (a.daynum <= b.daynum) end end + function dobj.__eq(a, b)return (a.daynum == b.daynum) and (a.dayfrc == b.dayfrc) end + function dobj.__sub(a,b) + local d1, d2 = date_getdobj(a), date_getdobj(b) + local d0 = d1 and d2 and date_new(d1.daynum - d2.daynum, d1.dayfrc - d2.dayfrc) + return d0 and d0:normalize() + end + function dobj.__add(a,b) + local d1, d2 = date_getdobj(a), date_getdobj(b) + local d0 = d1 and d2 and date_new(d1.daynum + d2.daynum, d1.dayfrc + d2.dayfrc) + return d0 and d0:normalize() + end + function dobj.__concat(a, b) return tostring(a) .. tostring(b) end + function dobj:__tostring() return self:fmt() end + + function dobj:copy() return date_new(self.daynum, self.dayfrc) end + +--[[ THE LOCAL DATE OBJECT METHODS ]]-- + function dobj:tolocal() + local dn,df = self.daynum, self.dayfrc + local bias = getbiasutc2(self) + if bias then + -- utc = local + bias; local = utc - bias + self.daynum = dn + self.dayfrc = df - bias*TICKSPERSEC + return self:normalize() + else + return nil + end + end + + function dobj:toutc() + local dn,df = self.daynum, self.dayfrc + local bias = getbiasloc2(dn, df) + if bias then + -- utc = local + bias; + self.daynum = dn + self.dayfrc = df + bias*TICKSPERSEC + return self:normalize() + else + return nil + end + end + + function dobj:getbias() return (getbiasloc2(self.daynum, self.dayfrc))/SECPERMIN end + + function dobj:gettzname() + local _, tvu, _ = getbiasloc2(self.daynum, self.dayfrc) + return tvu and osdate("%Z",tvu) or "" + end + +--#if not DATE_OBJECT_AFX then + function date.time(h, r, s, t) + h, r, s, t = tonumber(h or 0), tonumber(r or 0), tonumber(s or 0), tonumber(t or 0) + if h and r and s and t then + return date_new(DAYNUM_DEF, makedayfrc(h, r, s, t)) + else + return date_error_arg() + end + end + + function date:__call(...) + local arg = pack(...) + if arg.n > 1 then return (date_from(...)) + elseif arg.n == 0 then return (date_getdobj(false)) + else local o, r = date_getdobj(arg[1]); return r and o:copy() or o end + end + + date.diff = dobj.__sub + + function date.isleapyear(v) + local y = fix(v); + if not y then + y = date_getdobj(v) + y = y and y:getyear() + end + return isleapyear(y+0) + end + + function date.epoch() return date_epoch:copy() end + + function date.isodate(y,w,d) return date_new(makedaynum_isoywd(y + 0, w and (w+0) or 1, d and (d+0) or 1), 0) end + +-- Internal functions + function date.fmt(str) if str then fmtstr = str end; return fmtstr end + function date.daynummin(n) DAYNUM_MIN = (n and n < DAYNUM_MAX) and n or DAYNUM_MIN return n and DAYNUM_MIN or date_new(DAYNUM_MIN, 0):normalize()end + function date.daynummax(n) DAYNUM_MAX = (n and n > DAYNUM_MIN) and n or DAYNUM_MAX return n and DAYNUM_MAX or date_new(DAYNUM_MAX, 0):normalize()end + function date.ticks(t) if t then setticks(t) end return TICKSPERSEC end +--#end -- not DATE_OBJECT_AFX + + local tm = osdate("!*t", 0); + if tm then + date_epoch = date_new(makedaynum(tm.year, tm.month - 1, tm.day), makedayfrc(tm.hour, tm.min, tm.sec, 0)) + -- the distance from our epoch to os epoch in daynum + DATE_EPOCH = date_epoch and date_epoch:spandays() + else -- error will be raise only if called! + date_epoch = setmetatable({},{__index = function() error("failed to get the epoch date") end}) + end + + function date.serialize(object) + return {tostring(object.daynum), tostring(object.dayfrc)} + end + + function date.construct(object) + return date_isdobj(object) or (object.daynum and date_new(object.daynum, object.dayfrc) or date_new(object[1], object[2])) + end + +--#if not DATE_OBJECT_AFX then +return date +--#else +--$return date_from +--#end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua new file mode 100644 index 0000000..09b2821 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua @@ -0,0 +1,183 @@ +local middleclass = { + _VERSION = 'middleclass v4.1.1', + _DESCRIPTION = 'Object Orientation for Lua', + _URL = 'https://github.com/kikito/middleclass', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2011 Enrique García Cota + + 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 function _createIndexWrapper(aClass, f) + if f == nil then + return aClass.__instanceDict + else + return function(self, name) + local value = aClass.__instanceDict[name] + + if value ~= nil then + return value + elseif type(f) == "function" then + return (f(self, name)) + else + return f[name] + end + end + end +end + +local function _propagateInstanceMethod(aClass, name, f) + f = name == "__index" and _createIndexWrapper(aClass, f) or f + aClass.__instanceDict[name] = f + + for subclass in pairs(aClass.subclasses) do + if rawget(subclass.__declaredMethods, name) == nil then + _propagateInstanceMethod(subclass, name, f) + end + end +end + +local function _declareInstanceMethod(aClass, name, f) + aClass.__declaredMethods[name] = f + + if f == nil and aClass.super then + f = aClass.super.__instanceDict[name] + end + + _propagateInstanceMethod(aClass, name, f) +end + +local function _tostring(self) return "class " .. self.name end +local function _call(self, ...) return self:New(...) end + +local function _createClass(name, super) + local dict = {} + dict.__index = dict + + local aClass = { name = name, super = super, static = {}, + __instanceDict = dict, __declaredMethods = {}, + subclasses = setmetatable({}, {__mode='k'}) } + + if super then + setmetatable(aClass.static, { + __index = function(_,k) + local result = rawget(dict,k) + if result == nil then + return super.static[k] + end + return result + end + }) + else + setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end }) + end + + setmetatable(aClass, { __index = aClass.static, __tostring = _tostring, + __call = _call, __newindex = _declareInstanceMethod }) + + return aClass +end + +local function _includeMixin(aClass, mixin) + assert(type(mixin) == 'table', "mixin must be a table") + + for name,method in pairs(mixin) do + if name ~= "Included" and name ~= "static" then aClass[name] = method end + end + + for name,method in pairs(mixin.static or {}) do + aClass.static[name] = method + end + + if type(mixin.Included)=="function" then mixin:Included(aClass) end + return aClass +end + +local DefaultMixin = { + __tostring = function(self) return "instance of " .. tostring(self.class) end, + + Initialize = function(self, ...) end, + + IsInstanceOf = function(self, aClass) + return type(aClass) == 'table' + and type(self) == 'table' + and (self.class == aClass + or type(self.class) == 'table' + and type(self.class.IsSubclassOf) == 'function' + and self.class:IsSubclassOf(aClass)) + end, + + static = { + Allocate = function(self) + assert(type(self) == 'table', "Make sure that you are using 'Class:Allocate' instead of 'Class.Allocate'") + return setmetatable({ class = self }, self.__instanceDict) + end, + + New = function(self, ...) + assert(type(self) == 'table', "Make sure that you are using 'Class:New' instead of 'Class.New'") + local instance = self:Allocate() + instance:Initialize(...) + return instance + end, + + Subclass = function(self, name) + assert(type(self) == 'table', "Make sure that you are using 'Class:Subclass' instead of 'Class.Subclass'") + assert(type(name) == "string", "You must provide a name(string) for your class") + + local subclass = _createClass(name, self) + + for methodName, f in pairs(self.__instanceDict) do + _propagateInstanceMethod(subclass, methodName, f) + end + subclass.Initialize = function(instance, ...) return self.Initialize(instance, ...) end + + self.subclasses[subclass] = true + self:Subclassed(subclass) + + return subclass + end, + + Subclassed = function(self, other) end, + + IsSubclassOf = function(self, other) + return type(other) == 'table' and + type(self.super) == 'table' and + ( self.super == other or self.super:IsSubclassOf(other) ) + end, + + Include = function(self, ...) + assert(type(self) == 'table', "Make sure you that you are using 'Class:Include' instead of 'Class.Include'") + for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end + return self + end + } +} + +function middleclass.class(name, super) + assert(type(name) == 'string', "A name (string) is needed for the new class") + return super and super:Subclass(name) or _includeMixin(_createClass(name), DefaultMixin) +end + +setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end }) + +ix.middleclass = middleclass diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua new file mode 100644 index 0000000..1c8208e --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua @@ -0,0 +1,401 @@ +--[[ +DEVELOPMENTAL VERSION; +VERSION 1.2.2 +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 pon, 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, Weapon +V 1.2.0 + - Added custom handling for k,v tables without any array component. +V 1.2.1 + - fixed deserialization bug. +THANKS TO... + - VERCAS for the inspiration. +]] + + +local pon = {}; +_G.pon = pon; + +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 + + + local first = next(tbl, nil) + local predictedNumeric = 1 + local lastKey = nil + -- starts with a numeric dealio + if first == 1 then + output[#output + 1] = '{' + + for k,v in next, tbl do + if k == predictedNumeric then + predictedNumeric = predictedNumeric + 1 + + local tv = type(v) + 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 + + else + break + end + end + + predictedNumeric = predictedNumeric - 1 + else + predictedNumeric = nil + end + + if predictedNumeric == nil then + output[#output + 1] = '[' -- no array component + else + output[#output + 1] = '~' -- array component came first so shit needs to happen + end + + for k, v in next, tbl, predictedNumeric do + local tk, tv = type(k), type(v) + + -- WRITE 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 + + -- WRITE 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 + + 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['PhysObj'] = 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 pon.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 pon.decode( data ) + local _, res = decode[sub(data,1,1)]( decode, 2, data, {}); + return res; + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua new file mode 100644 index 0000000..2d710ab --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua @@ -0,0 +1,375 @@ +local tween = { + _VERSION = 'tween 2.1.1', + _DESCRIPTION = 'tweening for lua', + _URL = 'https://github.com/kikito/tween.lua', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga + + 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. + ]] +} + +-- easing + +-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits. +-- For all easing functions: +-- t = time == how much time has to pass for the tweening to complete +-- b = begin == starting property value +-- c = change == ending - beginning +-- d = duration == running time. How much time has passed *right now* + +local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin + +-- linear +local function linear(t, b, c, d) return c * t / d + b end + +-- quad +local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end +local function outQuad(t, b, c, d) + t = t / d + return -c * t * (t - 2) + b +end +local function inOutQuad(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 2) + b end + return -c / 2 * ((t - 1) * (t - 3) - 1) + b +end +local function outInQuad(t, b, c, d) + if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end + return inQuad((t * 2) - d, b + c / 2, c / 2, d) +end + +-- cubic +local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end +local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end +local function inOutCubic(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * t * t * t + b end + t = t - 2 + return c / 2 * (t * t * t + 2) + b +end +local function outInCubic(t, b, c, d) + if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end + return inCubic((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quart +local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end +local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end +local function inOutQuart(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 4) + b end + return -c / 2 * (pow(t - 2, 4) - 2) + b +end +local function outInQuart(t, b, c, d) + if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end + return inQuart((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quint +local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end +local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end +local function inOutQuint(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 5) + b end + return c / 2 * (pow(t - 2, 5) + 2) + b +end +local function outInQuint(t, b, c, d) + if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end + return inQuint((t * 2) - d, b + c / 2, c / 2, d) +end + +-- sine +local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end +local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end +local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end +local function outInSine(t, b, c, d) + if t < d / 2 then return outSine(t * 2, b, c / 2, d) end + return inSine((t * 2) -d, b + c / 2, c / 2, d) +end + +-- expo +local function inExpo(t, b, c, d) + if t == 0 then return b end + return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 +end +local function outExpo(t, b, c, d) + if t == d then return b + c end + return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b +end +local function inOutExpo(t, b, c, d) + if t == 0 then return b end + if t == d then return b + c end + t = t / d * 2 + if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end + return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b +end +local function outInExpo(t, b, c, d) + if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end + return inExpo((t * 2) - d, b + c / 2, c / 2, d) +end + +-- circ +local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end +local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end +local function inOutCirc(t, b, c, d) + t = t / d * 2 + if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end + t = t - 2 + return c / 2 * (sqrt(1 - t * t) + 1) + b +end +local function outInCirc(t, b, c, d) + if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end + return inCirc((t * 2) - d, b + c / 2, c / 2, d) +end + +-- elastic +local function calculatePAS(p,a,c,d) + p, a = p or d * 0.3, a or 0 + if a < abs(c) then return p, c, p / 4 end -- p, a, s + return p, a, p / (2 * pi) * asin(c/a) -- p,a,s +end +local function inElastic(t, b, c, d, a, p) + local s + if t == 0 then return b end + t = t / d + if t == 1 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + t = t - 1 + return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b +end +local function outElastic(t, b, c, d, a, p) + local s + if t == 0 then return b end + t = t / d + if t == 1 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b +end +local function inOutElastic(t, b, c, d, a, p) + local s + if t == 0 then return b end + t = t / d * 2 + if t == 2 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + t = t - 1 + if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b +end +local function outInElastic(t, b, c, d, a, p) + if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end + return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p) +end + +-- back +local function inBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d + return c * t * t * ((s + 1) * t - s) + b +end +local function outBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d - 1 + return c * (t * t * ((s + 1) * t + s) + 1) + b +end +local function inOutBack(t, b, c, d, s) + s = (s or 1.70158) * 1.525 + t = t / d * 2 + if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end + t = t - 2 + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b +end +local function outInBack(t, b, c, d, s) + if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end + return inBack((t * 2) - d, b + c / 2, c / 2, d, s) +end + +-- bounce +local function outBounce(t, b, c, d) + t = t / d + if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end + if t < 2 / 2.75 then + t = t - (1.5 / 2.75) + return c * (7.5625 * t * t + 0.75) + b + elseif t < 2.5 / 2.75 then + t = t - (2.25 / 2.75) + return c * (7.5625 * t * t + 0.9375) + b + end + t = t - (2.625 / 2.75) + return c * (7.5625 * t * t + 0.984375) + b +end +local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end +local function inOutBounce(t, b, c, d) + if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end + return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b +end +local function outInBounce(t, b, c, d) + if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end + return inBounce((t * 2) - d, b + c / 2, c / 2, d) +end + +tween.easing = { + linear = linear, + inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad, + inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic, + inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart, + inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint, + inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine, + inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo, + inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc, + inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic, + inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack, + inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce +} + + + +-- private stuff + +local function copyTables(destination, keysTable, valuesTable) + valuesTable = valuesTable or keysTable + local mt = getmetatable(keysTable) + if mt and getmetatable(destination) == nil then + setmetatable(destination, mt) + end + for k,v in pairs(keysTable) do + if type(v) == 'table' then + destination[k] = copyTables({}, v, valuesTable[k]) + else + destination[k] = valuesTable[k] + end + end + return destination +end + +local function checkSubjectAndTargetRecursively(subject, target, path) + path = path or {} + local newPath + for k,targetValue in pairs(target) do + newPath = copyTables({}, path) + table.insert(newPath, tostring(k)) + if isnumber(targetValue) then + assert(isnumber(subject[k]), "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number") + elseif istable(targetValue) then + checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) + else + assert(isnumber(targetValue), "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers") + end + end +end + +local function checkNewParams(duration, subject, target, easing) + assert(isnumber(duration) and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + assert(istable(target), "target must be a table. Was " .. tostring(target)) + assert(isfunction(easing), "easing must be a function. Was " .. tostring(easing)) + checkSubjectAndTargetRecursively(subject, target) +end + +local function getEasingFunction(easing) + easing = easing or "linear" + if isstring(easing) then + local name = easing + easing = tween.easing[name] + if not isfunction(easing) then + error("The easing function name '" .. name .. "' is invalid") + end + end + return easing +end + +local function performEasingOnSubject(subject, target, initial, clock, duration, easing) + local t,b,c,d + for k,v in pairs(target) do + if istable(v) then + performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) + else + t,b,c,d = clock, initial[k], v - initial[k], duration + subject[k] = easing(t,b,c,d) + end + end +end + +local function applyValues(subject, target) + for k, v in pairs(target) do + if (istable(v)) then + applyValues(subject[k], v) + else + subject[k] = v + end + end +end + +-- Tween methods + +local Tween = {} +local Tween_mt = {__index = Tween} + +function Tween:set(clock) + assert(isnumber(clock), "clock must be a positive number or 0") + + self.initial = self.initial or copyTables({}, self.target, self.subject) + self.clock = clock + + if self.clock <= 0 then + + self.clock = 0 + applyValues(self.subject, self.initial) + + elseif self.clock >= self.duration then -- the tween has expired + + self.clock = self.duration + applyValues(self.subject, self.target) + + else + + performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) + + end + + return self.clock >= self.duration +end + +function Tween:reset() + return self:set(0) +end + +function Tween:update(dt) + assert(isnumber(dt), "dt must be a number") + return self:set(self.clock + dt) +end + + +-- Public interface + +function tween.new(duration, subject, target, easing) + easing = getEasingFunction(easing) + checkNewParams(duration, subject, target, easing) + return setmetatable({ + duration = duration, + subject = subject, + target = target, + easing = easing, + clock = 0 + }, Tween_mt) +end + +ix.tween = tween diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua new file mode 100644 index 0000000..b28c3a8 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua @@ -0,0 +1,328 @@ +-- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $ +-- +-- Provides UTF-8 aware string functions implemented in pure lua: +-- * string.utf8len(s) +-- * string.utf8sub(s, i, j) +-- * string.utf8reverse(s) +-- +-- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these +-- additional functions are available: +-- * string.utf8upper(s) +-- * string.utf8lower(s) +-- +-- All functions behave as their non UTF-8 aware counterparts with the exception +-- that UTF-8 characters are used instead of bytes for all units. + +--[[ +Copyright (c) 2006-2007, Kyle Smith +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--]] + +-- ABNF from RFC 3629 +-- +-- UTF8-octets = *( UTF8-char ) +-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 +-- UTF8-1 = %x00-7F +-- UTF8-2 = %xC2-DF UTF8-tail +-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / +-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) +-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / +-- %xF4 %x80-8F 2( UTF8-tail ) +-- UTF8-tail = %x80-BF +-- + +ix.util.Include("data/sh_utf8_casemap.lua") + +-- returns the number of bytes used by the UTF-8 character at byte i in s +-- also doubles as a UTF-8 character validator +local function utf8charbytes (s, i) + -- argument defaults + i = i or 1 + + -- argument checking + if not isstring(s) then + error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")") + end + if not isnumber(i) then + error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")") + end + + local c = s:byte(i) + + -- determine bytes needed for character, based on RFC 3629 + -- validate byte 1 + if c > 0 and c <= 127 then + -- UTF8-1 + return 1 + + elseif c >= 194 and c <= 223 then + -- UTF8-2 + local c2 = s:byte(i + 1) + + if not c2 then + error("UTF-8 string terminated early") + end + + -- validate byte 2 + if c2 < 128 or c2 > 191 then + error("Invalid UTF-8 character") + end + + return 2 + + elseif c >= 224 and c <= 239 then + -- UTF8-3 + local c2 = s:byte(i + 1) + local c3 = s:byte(i + 2) + + if not c2 or not c3 then + error("UTF-8 string terminated early") + end + + -- validate byte 2 + if c == 224 and (c2 < 160 or c2 > 191) then + error("Invalid UTF-8 character") + elseif c == 237 and (c2 < 128 or c2 > 159) then + error("Invalid UTF-8 character") + elseif c2 < 128 or c2 > 191 then + error("Invalid UTF-8 character") + end + + -- validate byte 3 + if c3 < 128 or c3 > 191 then + error("Invalid UTF-8 character") + end + + return 3 + + elseif c >= 240 and c <= 244 then + -- UTF8-4 + local c2 = s:byte(i + 1) + local c3 = s:byte(i + 2) + local c4 = s:byte(i + 3) + + if not c2 or not c3 or not c4 then + error("UTF-8 string terminated early") + end + + -- validate byte 2 + if c == 240 and (c2 < 144 or c2 > 191) then + error("Invalid UTF-8 character") + elseif c == 244 and (c2 < 128 or c2 > 143) then + error("Invalid UTF-8 character") + elseif c2 < 128 or c2 > 191 then + error("Invalid UTF-8 character") + end + + -- validate byte 3 + if c3 < 128 or c3 > 191 then + error("Invalid UTF-8 character") + end + + -- validate byte 4 + if c4 < 128 or c4 > 191 then + error("Invalid UTF-8 character") + end + + return 4 + + else + error("Invalid UTF-8 character") + end +end + + +-- returns the number of characters in a UTF-8 string +local function utf8len (s) + -- argument checking + if not isstring(s) then + error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")") + end + + local pos = 1 + local bytes = s:len() + local len = 0 + + while pos <= bytes do + len = len + 1 + pos = pos + utf8charbytes(s, pos) + end + + return len +end + +-- install in the string library +if not string.utf8bytes then + string.utf8bytes = utf8charbytes +end + +-- install in the string library +if not string.utf8len then + string.utf8len = utf8len +end + + +-- functions identically to string.sub except that i and j are UTF-8 characters +-- instead of bytes +local function utf8sub (s, i, j) + -- argument defaults + j = j or -1 + + -- argument checking + if not isstring(s) then + error("bad argument #1 to 'utf8sub' (string expected, got ".. type(s).. ")") + end + if not isnumber(i) then + error("bad argument #2 to 'utf8sub' (number expected, got ".. type(i).. ")") + end + if not isnumber(j) then + error("bad argument #3 to 'utf8sub' (number expected, got ".. type(j).. ")") + end + + local pos = 1 + local bytes = s:len() + local len = 0 + + -- only set l if i or j is negative + local l = (i >= 0 and j >= 0) or s:utf8len() + local startChar = (i >= 0) and i or l + i + 1 + local endChar = (j >= 0) and j or l + j + 1 + + -- can't have start before end! + if startChar > endChar then + return "" + end + + -- byte offsets to pass to string.sub + local startByte, endByte = 1, bytes + + while pos <= bytes do + len = len + 1 + + if len == startChar then + startByte = pos + end + + pos = pos + utf8charbytes(s, pos) + + if len == endChar then + endByte = pos - 1 + break + end + end + + return s:sub(startByte, endByte) +end + +-- install in the string library +if not string.utf8sub then + string.utf8sub = utf8sub +end + + +-- replace UTF-8 characters based on a mapping table +local function utf8replace (s, mapping) + -- argument checking + if not isstring(s) then + error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")") + end + if not istable(mapping) then + error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")") + end + + local pos = 1 + local bytes = s:len() + local charbytes + local newstr = "" + + while pos <= bytes do + charbytes = utf8charbytes(s, pos) + local c = s:sub(pos, pos + charbytes - 1) + + newstr = newstr .. (mapping[c] or c) + + pos = pos + charbytes + end + + return newstr +end + + +-- identical to string.upper except it knows about unicode simple case conversions +local function utf8upper (s) + return utf8replace(s, utf8_lc_uc) +end + +-- install in the string library +if not string.utf8upper and utf8_lc_uc then + string.utf8upper = utf8upper +end + + +-- identical to string.lower except it knows about unicode simple case conversions +local function utf8lower (s) + return utf8replace(s, utf8_uc_lc) +end + +-- install in the string library +if not string.utf8lower and utf8_uc_lc then + string.utf8lower = utf8lower +end + + +-- identical to string.reverse except that it supports UTF-8 +local function utf8reverse (s) + -- argument checking + if not isstring(s) then + error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")") + end + + local bytes = s:len() + local pos = bytes + local charbytes + local newstr = "" + + while pos > 0 do + c = s:byte(pos) + while c >= 128 and c <= 191 do + pos = pos - 1 + c = s:byte(pos) + end + + charbytes = utf8charbytes(s, pos) + + newstr = newstr .. s:sub(pos, pos + charbytes - 1) + + pos = pos - 1 + end + + return newstr +end + +-- install in the string library +if not string.utf8reverse then + string.utf8reverse = utf8reverse +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua new file mode 100644 index 0000000..82fd5fe --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua @@ -0,0 +1,605 @@ +--[[ + (The MIT License) + + Copyright (c) 2017 Dominic Letz dominicletz@exosite.com + + 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 table_print_value +table_print_value = function(value, indent, done) + indent = indent or 0 + done = done or {} + if istable(value) and not done [value] then + done [value] = true + + local list = {} + for key in pairs (value) do + list[#list + 1] = key + end + table.sort(list, function(a, b) return tostring(a) < tostring(b) end) + local last = list[#list] + + local rep = "{\n" + local comma + for _, key in ipairs (list) do + if key == last then + comma = '' + else + comma = ',' + end + local keyRep + if isnumber(key) then + keyRep = key + else + keyRep = string.format("%q", tostring(key)) + end + rep = rep .. string.format( + "%s[%s] = %s%s\n", + string.rep(" ", indent + 2), + keyRep, + table_print_value(value[key], indent + 2, done), + comma + ) + end + + rep = rep .. string.rep(" ", indent) -- indent it + rep = rep .. "}" + + done[value] = false + return rep + elseif isstring(value) then + return string.format("%q", value) + else + return tostring(value) + end +end + +local table_print = function(tt) + print('return '..table_print_value(tt)) +end + +local table_clone = function(t) + local clone = {} + for k,v in pairs(t) do + clone[k] = v + end + return clone +end + +local string_trim = function(s, what) + what = what or " " + return s:gsub("^[" .. what .. "]*(.-)["..what.."]*$", "%1") +end + +local push = function(stack, item) + stack[#stack + 1] = item +end + +local pop = function(stack) + local item = stack[#stack] + stack[#stack] = nil + return item +end + +local context = function (str) + if not isstring(str) then + return "" + end + + str = str:sub(0,25):gsub("\n","\\n"):gsub("\"","\\\""); + return ", near \"" .. str .. "\"" +end + +local Parser = {} +function Parser.new (self, tokens) + self.tokens = tokens + self.parse_stack = {} + self.refs = {} + self.current = 0 + return self +end + +local exports = {version = "1.2"} + +local word = function(w) return "^("..w..")([%s$%c])" end + +local tokens = { + {"comment", "^#[^\n]*"}, + {"indent", "^\n( *)"}, + {"space", "^ +"}, + {"true", word("enabled"), const = true, value = true}, + {"true", word("true"), const = true, value = true}, + {"true", word("yes"), const = true, value = true}, + {"true", word("on"), const = true, value = true}, + {"false", word("disabled"), const = true, value = false}, + {"false", word("false"), const = true, value = false}, + {"false", word("no"), const = true, value = false}, + {"false", word("off"), const = true, value = false}, + {"null", word("null"), const = true, value = nil}, + {"null", word("Null"), const = true, value = nil}, + {"null", word("NULL"), const = true, value = nil}, + {"null", word("~"), const = true, value = nil}, + {"id", "^\"([^\"]-)\" *(:[%s%c])"}, + {"id", "^'([^']-)' *(:[%s%c])"}, + {"string", "^\"([^\"]-)\"", force_text = true}, + {"string", "^'([^']-)'", force_text = true}, + {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?):(%d%d)"}, + {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?)"}, + {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)"}, + {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d)"}, + {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?)"}, + {"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)"}, + {"doc", "^%-%-%-[^%c]*"}, + {",", "^,"}, + {"string", "^%b{} *[^,%c]+", noinline = true}, + {"{", "^{"}, + {"}", "^}"}, + {"string", "^%b[] *[^,%c]+", noinline = true}, + {"[", "^%["}, + {"]", "^%]"}, + {"-", "^%-"}, + {":", "^:"}, + {"pipe", "^(|)(%d*[+%-]?)", sep = "\n"}, + {"pipe", "^(>)(%d*[+%-]?)", sep = " "}, + {"id", "^([%w][%w %-_]*)(:[%s%c])"}, + {"string", "^[^%c]+", noinline = true}, + {"string", "^[^,%c ]+"} +}; +exports.tokenize = function (str) + local token + local row = 0 + local ignore + local indents = 0 + local lastIndents + local stack = {} + local indentAmount = 0 + local inline = false + str = str:gsub("\r\n","\010") + + while #str > 0 do + for i in ipairs(tokens) do + local captures = {} + if not inline or tokens[i].noinline == nil then + captures = {str:match(tokens[i][2])} + end + + if #captures > 0 then + captures.input = str:sub(0, 25) + token = table_clone(tokens[i]) + token[2] = captures + local str2 = str:gsub(tokens[i][2], "", 1) + token.raw = str:sub(1, #str - #str2) + str = str2 + + if token[1] == "{" or token[1] == "[" then + inline = true + elseif token.const then + -- Since word pattern contains last char we're re-adding it + str = token[2][2] .. str + token.raw = token.raw:sub(1, #token.raw - #token[2][2]) + elseif token[1] == "id" then + -- Since id pattern contains last semi-colon we're re-adding it + str = token[2][2] .. str + token.raw = token.raw:sub(1, #token.raw - #token[2][2]) + -- Trim + token[2][1] = string_trim(token[2][1]) + elseif token[1] == "string" then + -- Finding numbers + local snip = token[2][1] + if not token.force_text then + if snip:match("^(%d+%.%d+)$") or snip:match("^(%d+)$") then + token[1] = "number" + end + end + + elseif token[1] == "comment" then + ignore = true; + elseif token[1] == "indent" then + row = row + 1 + inline = false + lastIndents = indents + if indentAmount == 0 then + indentAmount = #token[2][1] + end + + if indentAmount ~= 0 then + indents = (#token[2][1] / indentAmount); + else + indents = 0 + end + + if indents == lastIndents then + ignore = true; + elseif indents > lastIndents + 2 then + error("SyntaxError: invalid indentation, got " .. tostring(indents) + .. " instead of " .. tostring(lastIndents) .. context(token[2].input)) + elseif indents > lastIndents + 1 then + push(stack, token) + elseif indents < lastIndents then + local input = token[2].input + token = {"dedent", {"", input = ""}} + token.input = input + while lastIndents > indents + 1 do + lastIndents = lastIndents - 1 + push(stack, token) + end + end + end -- if token[1] == XXX + token.row = row + break + end -- if #captures > 0 + end + + if not ignore then + if token then + push(stack, token) + token = nil + else + error("SyntaxError " .. context(str)) + end + end + + ignore = false; + end + + return stack +end + +Parser.peek = function (self, offset) + offset = offset or 1 + return self.tokens[offset + self.current] +end + +Parser.advance = function (self) + self.current = self.current + 1 + return self.tokens[self.current] +end + +Parser.advanceValue = function (self) + return self:advance()[2][1] +end + +Parser.accept = function (self, type) + if self:peekType(type) then + return self:advance() + end +end + +Parser.expect = function (self, type, msg) + return self:accept(type) or + error(msg .. context(self:peek()[1].input)) +end + +Parser.expectDedent = function (self, msg) + return self:accept("dedent") or (self:peek() == nil) or + error(msg .. context(self:peek()[2].input)) +end + +Parser.peekType = function (self, val, offset) + return self:peek(offset) and self:peek(offset)[1] == val +end + +Parser.ignore = function (self, items) + local advanced + repeat + advanced = false + for _,v in pairs(items) do + if self:peekType(v) then + self:advance() + advanced = true + end + end + until advanced == false +end + +Parser.ignoreSpace = function (self) + self:ignore{"space"} +end + +Parser.ignoreWhitespace = function (self) + self:ignore{"space", "indent", "dedent"} +end + +Parser.parse = function (self) + + local ref = nil + if self:peekType("string") and not self:peek().force_text then + local char = self:peek()[2][1]:sub(1,1) + if char == "&" then + ref = self:peek()[2][1]:sub(2) + self:advanceValue() + self:ignoreSpace() + elseif char == "*" then + ref = self:peek()[2][1]:sub(2) + return self.refs[ref] + end + end + + local result + local c = { + indent = self:accept("indent") and 1 or 0, + token = self:peek() + } + push(self.parse_stack, c) + + if c.token[1] == "doc" then + result = self:parseDoc() + elseif c.token[1] == "-" then + result = self:parseList() + elseif c.token[1] == "{" then + result = self:parseInlineHash() + elseif c.token[1] == "[" then + result = self:parseInlineList() + elseif c.token[1] == "id" then + result = self:parseHash() + elseif c.token[1] == "string" then + result = self:parseString("\n") + elseif c.token[1] == "timestamp" then + result = self:parseTimestamp() + elseif c.token[1] == "number" then + result = tonumber(self:advanceValue()) + elseif c.token[1] == "pipe" then + result = self:parsePipe() + elseif c.token.const == true then + self:advanceValue(); + result = c.token.value + else + error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input)) + end + + pop(self.parse_stack) + while c.indent > 0 do + c.indent = c.indent - 1 + local term = "term "..c.token[1]..": '"..c.token[2][1].."'" + self:expectDedent("last ".. term .." is not properly dedented") + end + + if ref then + self.refs[ref] = result + end + return result +end + +Parser.parseDoc = function (self) + self:accept("doc") + return self:parse() +end + +Parser.inline = function (self) + local current = self:peek(0) + if not current then + return {}, 0 + end + + local inline = {} + local i = 0 + + while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do + inline[self:peek(i)[1]] = true + i = i - 1 + end + return inline, -i +end + +Parser.isInline = function (self) + local _, i = self:inline() + return i > 0 +end + +Parser.parent = function(self, level) + level = level or 1 + return self.parse_stack[#self.parse_stack - level] +end + +Parser.parentType = function(self, type, level) + return self:parent(level) and self:parent(level).token[1] == type +end + +Parser.parseString = function (self) + if self:isInline() then + local result = self:advanceValue() + + --[[ + - a: this looks + flowing: but is + no: string + --]] + local types = self:inline() + if types["id"] and types["-"] then + if not self:peekType("indent") or not self:peekType("indent", 2) then + return result + end + end + + --[[ + a: 1 + b: this is + a flowing string + example + c: 3 + --]] + if self:peekType("indent") then + self:expect("indent", "text block needs to start with indent") + local addtl = self:accept("indent") + + result = result .. "\n" .. self:parseTextBlock("\n") + + self:expectDedent("text block ending dedent missing") + if addtl then + self:expectDedent("text block ending dedent missing") + end + end + return result + else + --[[ + a: 1 + b: + this is also + a flowing string + example + c: 3 + --]] + return self:parseTextBlock("\n") + end +end + +Parser.parsePipe = function (self) + local pipe = self:expect("pipe") + self:expect("indent", "text block needs to start with indent") + local result = self:parseTextBlock(pipe.sep) + self:expectDedent("text block ending dedent missing") + return result +end + +Parser.parseTextBlock = function (self, sep) + local token = self:advance() + local result = string_trim(token.raw, "\n") + local indents = 0 + while self:peek() ~= nil and ( indents > 0 or not self:peekType("dedent") ) do + local newtoken = self:advance() + while token.row < newtoken.row do + result = result .. sep + token.row = token.row + 1 + end + if newtoken[1] == "indent" then + indents = indents + 1 + elseif newtoken[1] == "dedent" then + indents = indents - 1 + else + result = result .. string_trim(newtoken.raw, "\n") + end + end + return result +end + +Parser.parseHash = function (self, hash) + hash = hash or {} + local indents = 0 + + if self:isInline() then + local id = self:advanceValue() + self:expect(":", "expected semi-colon after id") + self:ignoreSpace() + if self:accept("indent") then + indents = indents + 1 + hash[id] = self:parse() + else + hash[id] = self:parse() + if self:accept("indent") then + indents = indents + 1 + end + end + self:ignoreSpace(); + end + + while self:peekType("id") do + local id = self:advanceValue() + self:expect(":","expected semi-colon after id") + self:ignoreSpace() + hash[id] = self:parse() + self:ignoreSpace(); + end + + while indents > 0 do + self:expectDedent("expected dedent") + indents = indents - 1 + end + + return hash +end + +Parser.parseInlineHash = function (self) + local id + local hash = {} + local i = 0 + + self:accept("{") + while not self:accept("}") do + self:ignoreSpace() + if i > 0 then + self:expect(",","expected comma") + end + + self:ignoreWhitespace() + if self:peekType("id") then + id = self:advanceValue() + if id then + self:expect(":","expected semi-colon after id") + self:ignoreSpace() + hash[id] = self:parse() + self:ignoreWhitespace() + end + end + + i = i + 1 + end + return hash +end + +Parser.parseList = function (self) + local list = {} + while self:accept("-") do + self:ignoreSpace() + list[#list + 1] = self:parse() + + self:ignoreSpace() + end + return list +end + +Parser.parseInlineList = function (self) + local list = {} + local i = 0 + self:accept("[") + while not self:accept("]") do + self:ignoreSpace() + if i > 0 then + self:expect(",","expected comma") + end + + self:ignoreSpace() + list[#list + 1] = self:parse() + self:ignoreSpace() + i = i + 1 + end + + return list +end + +Parser.parseTimestamp = function (self) + local capture = self:advance()[2] + + return os.time{ + year = capture[1], + month = capture[2], + day = capture[3], + hour = capture[4] or 0, + min = capture[5] or 0, + sec = capture[6] or 0 + } +end + +exports.Eval = function (str) + return Parser:new(exports.tokenize(str)):parse() +end + +exports.Read = function(file_name) + if file.Exists(file_name, 'GAME') then + local local_name = file_name:gsub('%.y([a]?)ml', '.local.y%1ml') + + if file.Exists(local_name, 'GAME') then + file_name = local_name + end + + return Parser:new(exports.tokenize(file.Read(file_name, 'GAME'))):parse() + end +end + +exports.Dump = table_print + +ix.yaml = exports diff --git a/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua new file mode 100644 index 0000000..bc0787a --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua @@ -0,0 +1,648 @@ +--[[ + mysql - 1.0.3 + A simple MySQL wrapper for Garry's Mod. + + Alexander Grist-Hucker + http://www.alexgrist.com + + + The MIT License (MIT) + + Copyright (c) 2014 Alex Grist-Hucker + + 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. +--]] + +mysql = mysql or { + module = "sqlite" +} + +local QueueTable = {} +local tostring = tostring +local table = table + +--[[ + Replacement tables +--]] + +local Replacements = { + sqlite = { + Create = { + {"UNSIGNED ", ""}, + {"NOT NULL AUTO_INCREMENT", ""}, -- assuming primary key + {"AUTO_INCREMENT", ""}, + {"INT%(%d*%)", "INTEGER"}, + {"INT ", "INTEGER"} + } + } +} + +--[[ + Phrases +--]] + +local MODULE_NOT_EXIST = "[mysql] The %s module does not exist!\n" + +--[[ + Begin Query Class. +--]] + +local QUERY_CLASS = {} +QUERY_CLASS.__index = QUERY_CLASS + +function QUERY_CLASS:New(tableName, queryType) + local newObject = setmetatable({}, QUERY_CLASS) + newObject.queryType = queryType + newObject.tableName = tableName + newObject.selectList = {} + newObject.insertList = {} + newObject.updateList = {} + newObject.createList = {} + newObject.whereList = {} + newObject.orderByList = {} + return newObject +end + +function QUERY_CLASS:Escape(text) + return mysql:Escape(tostring(text)) +end + +function QUERY_CLASS:ForTable(tableName) + self.tableName = tableName +end + +function QUERY_CLASS:Where(key, value) + self:WhereEqual(key, value) +end + +function QUERY_CLASS:WhereEqual(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` = '"..self:Escape(value).."'" +end + +function QUERY_CLASS:WhereNotEqual(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` != '"..self:Escape(value).."'" +end + +function QUERY_CLASS:WhereLike(key, value, format) + format = format or "%%%s%%" + self.whereList[#self.whereList + 1] = "`"..key.."` LIKE '"..string.format(format, self:Escape(value)).."'" +end + +function QUERY_CLASS:WhereNotLike(key, value, format) + format = format or "%%%s%%" + self.whereList[#self.whereList + 1] = "`"..key.."` NOT LIKE '"..string.format(format, self:Escape(value)).."'" +end + +function QUERY_CLASS:WhereGT(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` > '"..self:Escape(value).."'" +end + +function QUERY_CLASS:WhereLT(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` < '"..self:Escape(value).."'" +end + +function QUERY_CLASS:WhereGTE(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` >= '"..self:Escape(value).."'" +end + +function QUERY_CLASS:WhereLTE(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` <= '"..self:Escape(value).."'" +end + +function QUERY_CLASS:WhereIn(key, value) + value = istable(value) and value or {value} + + local values = "" + local bFirst = true + + for k, v in pairs(value) do + values = values .. (bFirst and "" or ", ") .. "'" .. self:Escape(v) .. "'" + bFirst = false + end + + self.whereList[#self.whereList + 1] = "`"..key.."` IN ("..values..")" +end + +function QUERY_CLASS:OrderByDesc(key) + self.orderByList[#self.orderByList + 1] = "`"..key.."` DESC" +end + +function QUERY_CLASS:OrderByAsc(key) + self.orderByList[#self.orderByList + 1] = "`"..key.."` ASC" +end + +function QUERY_CLASS:Callback(queryCallback) + self.callback = queryCallback +end + +function QUERY_CLASS:Select(fieldName) + self.selectList[#self.selectList + 1] = "`"..fieldName.."`" +end + +function QUERY_CLASS:Insert(key, value) + self.insertList[#self.insertList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"} +end + +function QUERY_CLASS:Update(key, value) + self.updateList[#self.updateList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"} +end + +function QUERY_CLASS:Create(key, value) + self.createList[#self.createList + 1] = {"`"..key.."`", value} +end + +function QUERY_CLASS:Add(key, value) + self.add = {"`"..key.."`", value} +end + +function QUERY_CLASS:Drop(key) + self.drop = "`"..key.."`" +end + +function QUERY_CLASS:PrimaryKey(key) + self.primaryKey = "`"..key.."`" +end + +function QUERY_CLASS:Limit(value) + self.limit = value +end + +function QUERY_CLASS:Offset(value) + self.offset = value +end + +local function ApplyQueryReplacements(mode, query) + if (!Replacements[mysql.module]) then + return query + end + + local result = query + local entries = Replacements[mysql.module][mode] + + for i = 1, #entries do + result = string.gsub(result, entries[i][1], entries[i][2]) + end + + return result +end + +local function BuildSelectQuery(queryObj) + local queryString = {"SELECT"} + + if (!istable(queryObj.selectList) or #queryObj.selectList == 0) then + queryString[#queryString + 1] = " *" + else + queryString[#queryString + 1] = " "..table.concat(queryObj.selectList, ", ") + end + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " FROM `"..queryObj.tableName.."` " + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + if (istable(queryObj.whereList) and #queryObj.whereList > 0) then + queryString[#queryString + 1] = " WHERE " + queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ") + end + + if (istable(queryObj.orderByList) and #queryObj.orderByList > 0) then + queryString[#queryString + 1] = " ORDER BY " + queryString[#queryString + 1] = table.concat(queryObj.orderByList, ", ") + end + + if (isnumber(queryObj.limit)) then + queryString[#queryString + 1] = " LIMIT " + queryString[#queryString + 1] = queryObj.limit + end + + return table.concat(queryString) +end + +local function BuildInsertQuery(queryObj, bIgnore) + local suffix = (bIgnore and (mysql.module == "sqlite" and "INSERT OR IGNORE INTO" or "INSERT IGNORE INTO") or "INSERT INTO") + local queryString = {suffix} + local keyList = {} + local valueList = {} + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`" + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + for i = 1, #queryObj.insertList do + keyList[#keyList + 1] = queryObj.insertList[i][1] + valueList[#valueList + 1] = queryObj.insertList[i][2] + end + + if (#keyList == 0) then + return + end + + queryString[#queryString + 1] = " ("..table.concat(keyList, ", ")..")" + queryString[#queryString + 1] = " VALUES ("..table.concat(valueList, ", ")..")" + + return table.concat(queryString) +end + +local function BuildUpdateQuery(queryObj) + local queryString = {"UPDATE"} + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`" + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + if (istable(queryObj.updateList) and #queryObj.updateList > 0) then + local updateList = {} + + queryString[#queryString + 1] = " SET" + + for i = 1, #queryObj.updateList do + updateList[#updateList + 1] = queryObj.updateList[i][1].." = "..queryObj.updateList[i][2] + end + + queryString[#queryString + 1] = " "..table.concat(updateList, ", ") + end + + if (istable(queryObj.whereList) and #queryObj.whereList > 0) then + queryString[#queryString + 1] = " WHERE " + queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ") + end + + if (isnumber(queryObj.offset)) then + queryString[#queryString + 1] = " OFFSET " + queryString[#queryString + 1] = queryObj.offset + end + + return table.concat(queryString) +end + +local function BuildDeleteQuery(queryObj) + local queryString = {"DELETE FROM"} + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`" + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + if (istable(queryObj.whereList) and #queryObj.whereList > 0) then + queryString[#queryString + 1] = " WHERE " + queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ") + end + + if (isnumber(queryObj.limit)) then + queryString[#queryString + 1] = " LIMIT " + queryString[#queryString + 1] = queryObj.limit + end + + return table.concat(queryString) +end + +local function BuildDropQuery(queryObj) + local queryString = {"DROP TABLE"} + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`" + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + return table.concat(queryString) +end + +local function BuildTruncateQuery(queryObj) + local queryString = {"TRUNCATE TABLE"} + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`" + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + return table.concat(queryString) +end + +local function BuildCreateQuery(queryObj) + local queryString = {"CREATE TABLE IF NOT EXISTS"} + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`" + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + queryString[#queryString + 1] = " (" + + if (istable(queryObj.createList) and #queryObj.createList > 0) then + local createList = {} + + for i = 1, #queryObj.createList do + if (mysql.module == "sqlite") then + createList[#createList + 1] = queryObj.createList[i][1].." "..ApplyQueryReplacements("Create", queryObj.createList[i][2]) + else + createList[#createList + 1] = queryObj.createList[i][1].." "..queryObj.createList[i][2] + end + end + + queryString[#queryString + 1] = " "..table.concat(createList, ", ") + end + + if (isstring(queryObj.primaryKey)) then + queryString[#queryString + 1] = ", PRIMARY KEY" + queryString[#queryString + 1] = " ("..queryObj.primaryKey..")" + end + + queryString[#queryString + 1] = " )" + + return table.concat(queryString) +end + +local function BuildAlterQuery(queryObj) + local queryString = {"ALTER TABLE"} + + if (isstring(queryObj.tableName)) then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`" + else + ErrorNoHalt("[mysql] No table name specified!\n") + return + end + + if (istable(queryObj.add)) then + queryString[#queryString + 1] = " ADD "..queryObj.add[1].." "..ApplyQueryReplacements("Create", queryObj.add[2]) + elseif (isstring(queryObj.drop)) then + if (mysql.module == "sqlite") then + ErrorNoHalt("[mysql] Cannot drop columns in sqlite!\n") + return + end + + queryString[#queryString + 1] = " DROP COLUMN "..queryObj.drop + end + + return table.concat(queryString) +end + +function QUERY_CLASS:Execute(bQueueQuery) + local queryString = nil + local queryType = string.lower(self.queryType) + + if (queryType == "select") then + queryString = BuildSelectQuery(self) + elseif (queryType == "insert") then + queryString = BuildInsertQuery(self) + elseif (queryType == "insert ignore") then + queryString = BuildInsertQuery(self, true) + elseif (queryType == "update") then + queryString = BuildUpdateQuery(self) + elseif (queryType == "delete") then + queryString = BuildDeleteQuery(self) + elseif (queryType == "drop") then + queryString = BuildDropQuery(self) + elseif (queryType == "truncate") then + queryString = BuildTruncateQuery(self) + elseif (queryType == "create") then + queryString = BuildCreateQuery(self) + elseif (queryType == "alter") then + queryString = BuildAlterQuery(self) + end + + if (isstring(queryString)) then + if (!bQueueQuery) then + return mysql:RawQuery(queryString, self.callback) + else + return mysql:Queue(queryString, self.callback) + end + end +end + +--[[ + End Query Class. +--]] + +function mysql:Select(tableName) + return QUERY_CLASS:New(tableName, "SELECT") +end + +function mysql:Insert(tableName) + return QUERY_CLASS:New(tableName, "INSERT") +end + +function mysql:InsertIgnore(tableName) + return QUERY_CLASS:New(tableName, "INSERT IGNORE") +end + +function mysql:Update(tableName) + return QUERY_CLASS:New(tableName, "UPDATE") +end + +function mysql:Delete(tableName) + return QUERY_CLASS:New(tableName, "DELETE") +end + +function mysql:Drop(tableName) + return QUERY_CLASS:New(tableName, "DROP") +end + +function mysql:Truncate(tableName) + return QUERY_CLASS:New(tableName, "TRUNCATE") +end + +function mysql:Create(tableName) + return QUERY_CLASS:New(tableName, "CREATE") +end + +function mysql:Alter(tableName) + return QUERY_CLASS:New(tableName, "ALTER") +end + +local UTF8MB4 = "ALTER DATABASE %s CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci" + +-- A function to connect to the MySQL database. +function mysql:Connect(host, username, password, database, port, socket, flags) + port = port or 3306 + + if (self.module == "mysqloo") then + if (!istable(mysqloo)) then + require("mysqloo") + end + + if (mysqloo) then + if (self.connection and self.connection:ping()) then + return + end + + local clientFlag = flags or 0 + + if (!isstring(socket)) then + self.connection = mysqloo.connect(host, username, password, database, port) + else + self.connection = mysqloo.connect(host, username, password, database, port, socket, clientFlag) + end + + self.connection.onConnected = function(connection) + local success, error_message = connection:setCharacterSet("utf8mb4") + + if (!success) then + ErrorNoHalt("Failed to set MySQL encoding!\n") + ErrorNoHalt(error_message .. "\n") + else + self:RawQuery(string.format(UTF8MB4, database)) + end + + mysql:OnConnected() + end + + self.connection.onConnectionFailed = function(database, errorText) + mysql:OnConnectionFailed(errorText) + end + + self.connection:connect() + + timer.Create("mysql.KeepAlive", 300, 0, function() + self.connection:ping() + end) + else + ErrorNoHalt(string.format(MODULE_NOT_EXIST, self.module)) + end + elseif (self.module == "sqlite") then + timer.Simple(0, function() + mysql:OnConnected() + end) + end +end + +-- A function to query the MySQL database. +function mysql:RawQuery(query, callback, flags, ...) + if (self.module == "mysqloo") then + local queryObj = self.connection:query(query) + + queryObj:setOption(mysqloo.OPTION_NAMED_FIELDS) + + queryObj.onSuccess = function(queryObj, result) + if (callback) then + local bStatus, value = pcall(callback, result, true, tonumber(queryObj:lastInsert())) + + if (!bStatus) then + error(string.format("[mysql] MySQL Callback Error!\n%s\n", value)) + end + end + end + + queryObj.onError = function(queryObj, errorText) + ErrorNoHalt(string.format("[mysql] MySQL Query Error!\nQuery: %s\n%s\n", query, errorText)) + end + + queryObj:start() + elseif (self.module == "sqlite") then + local result = sql.Query(query) + + if (result == false) then + error(string.format("[mysql] SQL Query Error!\nQuery: %s\n%s\n", query, sql.LastError())) + else + if (callback) then + local bStatus, value = pcall(callback, result, true, tonumber(sql.QueryValue("SELECT last_insert_rowid()"))) + + if (!bStatus) then + error(string.format("[mysql] SQL Callback Error!\n%s\n", value)) + end + end + end + else + ErrorNoHalt(string.format("[mysql] Unsupported module \"%s\"!\n", self.module)) + end +end + +-- A function to add a query to the queue. +function mysql:Queue(queryString, callback) + if (isstring(queryString)) then + QueueTable[#QueueTable + 1] = {queryString, callback} + end +end + +-- A function to escape a string for MySQL. +function mysql:Escape(text) + if (self.connection) then + if (self.module == "mysqloo") then + return self.connection:escape(text) + end + else + return sql.SQLStr(text, true) + end +end + +-- A function to disconnect from the MySQL database. +function mysql:Disconnect() + if (self.connection) then + if (self.module == "mysqloo") then + self.connection:disconnect(true) + end + end +end + +function mysql:Think() + if (#QueueTable > 0) then + if (istable(QueueTable[1])) then + local queueObj = QueueTable[1] + local queryString = queueObj[1] + local callback = queueObj[2] + + if (isstring(queryString)) then + self:RawQuery(queryString, callback) + end + + table.remove(QueueTable, 1) + end + end +end + +-- A function to set the module that should be used. +function mysql:SetModule(moduleName) + self.module = moduleName +end + +-- Called when the database connects sucessfully. +function mysql:OnConnected() + MsgC(Color(25, 235, 25), "[mysql] Connected to the database!\n") + + hook.Run("DatabaseConnected") +end + +-- Called when the database connection fails. +function mysql:OnConnectionFailed(errorText) + ErrorNoHalt(string.format("[mysql] Unable to connect to the database!\n%s\n", errorText)) + + hook.Run("DatabaseConnectionFailed", errorText) +end + +-- A function to check whether or not the module is connected to a database. +function mysql:IsConnected() + return self.module == "mysqloo" and (self.connection and self.connection:ping()) or self.module == "sqlite" +end + +return mysql diff --git a/garrysmod/gamemodes/helix/gamemode/core/meta/sh_character.lua b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_character.lua new file mode 100644 index 0000000..38b58ea --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_character.lua @@ -0,0 +1,363 @@ + +--[[-- +Contains information about a player's current game state. + +Characters are a fundamental object type in Helix. They are distinct from players, where players are the representation of a +person's existence in the server that owns a character, and their character is their currently selected persona. All the +characters that a player owns will be loaded into memory once they connect to the server. Characters are saved during a regular +interval, and during specific events (e.g when the owning player switches away from one character to another). + +They contain all information that is not persistent with the player; names, descriptions, model, currency, etc. For the most +part, you'll want to keep all information stored on the character since it will probably be different or change if the +player switches to another character. An easy way to do this is to use `ix.char.RegisterVar` to easily create accessor functions +for variables that automatically save to the character object. +]] +-- @classmod Character + +local CHAR = ix.meta.character or {} +CHAR.__index = CHAR +CHAR.id = CHAR.id or 0 +CHAR.vars = CHAR.vars or {} + +-- @todo not this +if (!ix.db) then + ix.util.Include("../libs/sv_database.lua") +end + +--- Returns a string representation of this character +-- @realm shared +-- @treturn string String representation +-- @usage print(ix.char.loaded[1]) +-- > "character[1]" +function CHAR:__tostring() + return "character["..(self.id or 0).."]" +end + +--- Returns true if this character is equal to another character. Internally, this checks character IDs. +-- @realm shared +-- @char other Character to compare to +-- @treturn bool Whether or not this character is equal to the given character +-- @usage print(ix.char.loaded[1] == ix.char.loaded[2]) +-- > false +function CHAR:__eq(other) + return self:GetID() == other:GetID() +end + +--- Returns this character's database ID. This is guaranteed to be unique. +-- @realm shared +-- @treturn number Unique ID of character +function CHAR:GetID() + return self.id +end + +if (SERVER) then + --- Saves this character's info to the database. + -- @realm server + -- @func[opt=nil] callback Function to call when the save has completed. + -- @usage ix.char.loaded[1]:Save(function() + -- print("done!") + -- end) + -- > done! -- after a moment + function CHAR:Save(callback) + -- Do not save if the character is for a bot. + if (self.isBot) then + return + end + + -- Let plugins/schema determine if the character should be saved. + local shouldSave = hook.Run("CharacterPreSave", self) + + if (shouldSave != false) then + -- Run a query to save the character to the database. + local query = mysql:Update("ix_characters") + -- update all character vars + for k, v in pairs(ix.char.vars) do + if (v.field and self.vars[k] != nil and !v.bSaveLoadInitialOnly) then + local value = self.vars[k] + + query:Update(v.field, istable(value) and util.TableToJSON(value) or tostring(value)) + end + end + + query:Where("id", self:GetID()) + query:Callback(function() + if (callback) then + callback() + end + + hook.Run("CharacterPostSave", self) + end) + query:Execute() + end + end + + --- Networks this character's information to make the given player aware of this character's existence. If the receiver is + -- not the owner of this character, it will only be sent a limited amount of data (as it does not need anything else). + -- This is done automatically by the framework. + -- @internal + -- @realm server + -- @player[opt=nil] receiver Player to send the information to. This will sync to all connected players if set to `nil`. + function CHAR:Sync(receiver) + -- Broadcast the character information if receiver is not set. + if (receiver == nil) then + for _, v in player.Iterator() do + self:Sync(v) + end + -- Send all character information if the receiver is the character's owner. + elseif (receiver == self.player) then + local data = {} + + for k, v in pairs(self.vars) do + if (ix.char.vars[k] != nil and !ix.char.vars[k].bNoNetworking) then + data[k] = v + end + end + + net.Start("ixCharacterInfo") + net.WriteTable(data) + net.WriteUInt(self:GetID(), 32) + net.WriteUInt(self.player:EntIndex(), 8) + net.Send(self.player) + else + local data = {} + + for k, v in pairs(ix.char.vars) do + if (!v.bNoNetworking and !v.isLocal) then + data[k] = self.vars[k] + end + end + + net.Start("ixCharacterInfo") + net.WriteTable(data) + net.WriteUInt(self:GetID(), 32) + net.WriteUInt(self.player:EntIndex(), 8) + net.Send(receiver) + end + end + + -- Sets up the "appearance" related inforomation for the character. + --- Applies the character's appearance and synchronizes information to the owning player. + -- @realm server + -- @internal + -- @bool[opt] bNoNetworking Whether or not to sync the character info to other players + function CHAR:Setup(bNoNetworking) + local client = self:GetPlayer() + + if (IsValid(client)) then + -- Set the faction, model, and character index for the player. + local model = self:GetModel() + + client:SetNetVar("char", self:GetID()) + client:SetTeam(self:GetFaction()) + client:SetModel(istable(model) and model[1] or model) + + -- Apply saved body groups. + for k, v in pairs(self:GetData("groups", {})) do + client:SetBodygroup(k, v) + end + + -- Apply a saved skin. + client:SetSkin(self:GetData("skin", 0)) + + -- Synchronize the character if we should. + if (!bNoNetworking) then + if (client:IsBot()) then + timer.Simple(0.33, function() + self:Sync() + end) + else + self:Sync() + end + + for _, v in ipairs(self:GetInventory(true)) do + if (istable(v)) then + v:AddReceiver(client) + v:Sync(client) + end + end + end + + local id = self:GetID() + + hook.Run("CharacterLoaded", ix.char.loaded[id]) + + net.Start("ixCharacterLoaded") + net.WriteUInt(id, 32) + net.Send(client) + + self.firstTimeLoaded = true + end + end + + --- Forces a player off their current character, and sends them to the character menu to select a character. + -- @realm server + function CHAR:Kick() + -- Kill the player so they are not standing anywhere. + local client = self:GetPlayer() + client:KillSilent() + + local steamID = client:SteamID64() + local id = self:GetID() + local isCurrentChar = self and self:GetID() == id + + -- Return the player to the character menu. + if (self and self.steamID == steamID) then + net.Start("ixCharacterKick") + net.WriteBool(isCurrentChar) + net.Send(client) + + if (isCurrentChar) then + client:SetNetVar("char", nil) + client:Spawn() + end + end + end + + --- Forces a player off their current character, and prevents them from using the character for the specified amount of time. + -- @realm server + -- @number[opt] time Amount of seconds to ban the character for. If left as `nil`, the character will be banned permanently + function CHAR:Ban(time) + time = tonumber(time) + + if (time) then + -- If time is provided, adjust it so it becomes the un-ban time. + time = os.time() + math.max(math.ceil(time), 60) + end + + -- Mark the character as banned and kick the character back to menu. + self:SetData("banned", time or true) + self:Kick() + end +end + +--- Returns the player that owns this character. +-- @realm shared +-- @treturn player Player that owns this character +function CHAR:GetPlayer() + -- Set the player from entity index. + if (isnumber(self.player)) then + local client = Entity(self.player) + + if (IsValid(client)) then + self.player = client + + return client + end + -- Return the player from cache. + elseif (IsValid(self.player)) then + return self.player + -- Search for which player owns this character. + elseif (self.steamID) then + local steamID = self.steamID + + for _, v in player.Iterator() do + if (v:SteamID64() == steamID) then + self.player = v + + return v + end + end + end +end + +-- Sets up a new character variable. +function ix.char.RegisterVar(key, data) + -- Store information for the variable. + ix.char.vars[key] = data + data.index = data.index or table.Count(ix.char.vars) + + local upperName = key:sub(1, 1):upper() .. key:sub(2) + + if (SERVER) then + if (data.field) then + ix.db.AddToSchema("ix_characters", data.field, data.fieldType or ix.type.string) + end + + -- Provide functions to change the variable if allowed. + if (!data.bNotModifiable) then + -- Overwrite the set function if desired. + if (data.OnSet) then + CHAR["Set"..upperName] = data.OnSet + -- Have the set function only set on the server if no networking. + elseif (data.bNoNetworking) then + CHAR["Set"..upperName] = function(self, value) + self.vars[key] = value + end + -- If the variable is a local one, only send the variable to the local player. + elseif (data.isLocal) then + CHAR["Set"..upperName] = function(self, value) + local oldVar = self.vars[key] + self.vars[key] = value + + net.Start("ixCharacterVarChanged") + net.WriteUInt(self:GetID(), 32) + net.WriteString(key) + net.WriteType(value) + net.Send(self.player) + + hook.Run("CharacterVarChanged", self, key, oldVar, value) + end + -- Otherwise network the variable to everyone. + else + CHAR["Set"..upperName] = function(self, value) + local oldVar = self.vars[key] + self.vars[key] = value + + net.Start("ixCharacterVarChanged") + net.WriteUInt(self:GetID(), 32) + net.WriteString(key) + net.WriteType(value) + net.Broadcast() + + hook.Run("CharacterVarChanged", self, key, oldVar, value) + end + end + end + end + + -- The get functions are shared. + -- Overwrite the get function if desired. + if (data.OnGet) then + CHAR["Get"..upperName] = data.OnGet + -- Otherwise return the character variable or default if it does not exist. + else + CHAR["Get"..upperName] = function(self, default) + local value = self.vars[key] + + if (value != nil) then + return value + end + + if (default == nil) then + return ix.char.vars[key] and (istable(ix.char.vars[key].default) and table.Copy(ix.char.vars[key].default) + or ix.char.vars[key].default) + end + + return default + end + end + + local alias = data.alias + + if (alias) then + if (istable(alias)) then + for _, v in ipairs(alias) do + local aliasName = v:sub(1, 1):upper()..v:sub(2) + + CHAR["Get"..aliasName] = CHAR["Get"..upperName] + CHAR["Set"..aliasName] = CHAR["Set"..upperName] + end + elseif (isstring(alias)) then + local aliasName = alias:sub(1, 1):upper()..alias:sub(2) + + CHAR["Get"..aliasName] = CHAR["Get"..upperName] + CHAR["Set"..aliasName] = CHAR["Set"..upperName] + end + end + + -- Add the variable default to the character object. + CHAR.vars[key] = data.default +end + +-- Allows access to the character metatable using ix.meta.character +ix.meta.character = CHAR diff --git a/garrysmod/gamemodes/helix/gamemode/core/meta/sh_entity.lua b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_entity.lua new file mode 100644 index 0000000..31637df --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_entity.lua @@ -0,0 +1,217 @@ + +--[[-- +Physical object in the game world. + +Entities are physical representations of objects in the game world. Helix extends the functionality of entities to interface +between Helix's own classes, and to reduce boilerplate code. + +See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Entity) for all other methods that the `Player` class has. +]] +-- @classmod Entity + +local meta = FindMetaTable("Entity") +local CHAIR_CACHE = {} + +-- Add chair models to the cache by checking if its vehicle category is a class. +for _, v in pairs(list.Get("Vehicles")) do + if (v.Category == "Chairs") then + CHAIR_CACHE[v.Model] = true + end +end + +--- Returns `true` if this entity is a chair. +-- @realm shared +-- @treturn bool Whether or not this entity is a chair +function meta:IsChair() + return CHAIR_CACHE[self:GetModel()] +end + +--- Returns `true` if this entity is a door. Internally, this checks to see if the entity's class has `door` in its name. +-- @realm shared +-- @treturn bool Whether or not the entity is a door +function meta:IsDoor() + local class = self:GetClass() + + return (class and class:find("door") != nil) +end + +if (SERVER) then + --- Returns `true` if the given entity is a button or door and is locked. + -- @realm server + -- @treturn bool Whether or not this entity is locked; `false` if this entity cannot be locked at all + -- (e.g not a button or door) + function meta:IsLocked() + if (self:IsVehicle()) then + local datatable = self:GetSaveTable() + + if (datatable) then + return datatable.VehicleLocked + end + else + local datatable = self:GetSaveTable() + + if (datatable) then + return datatable.m_bLocked + end + end + + return false + end + + --- Returns the neighbouring door entity for double doors. + -- @realm shared + -- @treturn[1] Entity This door's partner + -- @treturn[2] nil If the door does not have a partner + function meta:GetDoorPartner() + return self.ixPartner + end + + --- Returns the entity that is blocking this door from opening. + -- @realm server + -- @treturn[1] Entity Entity that is blocking this door + -- @treturn[2] nil If this entity is not a door, or there is no blocking entity + function meta:GetBlocker() + local datatable = self:GetSaveTable() + + return datatable.pBlocker + end + + --- Blasts a door off its hinges. Internally, this hides the door entity, spawns a physics prop with the same model, and + -- applies force to the prop. + -- @realm server + -- @vector velocity Velocity to apply to the door + -- @number lifeTime How long to wait in seconds before the door is put back on its hinges + -- @bool bIgnorePartner Whether or not to ignore the door's partner in the case of double doors + -- @treturn[1] Entity The physics prop created for the door + -- @treturn nil If the entity is not a door + function meta:BlastDoor(velocity, lifeTime, bIgnorePartner) + if (!self:IsDoor()) then + return + end + + if (IsValid(self.ixDummy)) then + self.ixDummy:Remove() + end + + velocity = velocity or VectorRand()*100 + lifeTime = lifeTime or 120 + + local partner = self:GetDoorPartner() + + if (IsValid(partner) and !bIgnorePartner) then + partner:BlastDoor(velocity, lifeTime, true) + end + + local color = self:GetColor() + + local dummy = ents.Create("prop_physics") + dummy:SetModel(self:GetModel()) + dummy:SetPos(self:GetPos()) + dummy:SetAngles(self:GetAngles()) + dummy:Spawn() + dummy:SetColor(color) + dummy:SetMaterial(self:GetMaterial()) + dummy:SetSkin(self:GetSkin() or 0) + dummy:SetRenderMode(RENDERMODE_TRANSALPHA) + dummy:CallOnRemove("restoreDoor", function() + if (IsValid(self)) then + self:SetNotSolid(false) + self:SetNoDraw(false) + self:DrawShadow(true) + self.ignoreUse = false + self.ixIsMuted = false + + for _, v in ents.Iterator() do + if (v:GetParent() == self) then + v:SetNotSolid(false) + v:SetNoDraw(false) + + if (v.OnDoorRestored) then + v:OnDoorRestored(self) + end + end + end + end + end) + dummy:SetOwner(self) + dummy:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + self:Fire("unlock") + self:Fire("open") + self:SetNotSolid(true) + self:SetNoDraw(true) + self:DrawShadow(false) + self.ignoreUse = true + self.ixDummy = dummy + self.ixIsMuted = true + self:DeleteOnRemove(dummy) + + for _, v in ipairs(self:GetBodyGroups() or {}) do + dummy:SetBodygroup(v.id, self:GetBodygroup(v.id)) + end + + for _, v in ents.Iterator() do + if (v:GetParent() == self) then + v:SetNotSolid(true) + v:SetNoDraw(true) + + if (v.OnDoorBlasted) then + v:OnDoorBlasted(self) + end + end + end + + dummy:GetPhysicsObject():SetVelocity(velocity) + + local uniqueID = "doorRestore"..self:EntIndex() + local uniqueID2 = "doorOpener"..self:EntIndex() + + timer.Create(uniqueID2, 1, 0, function() + if (IsValid(self) and IsValid(self.ixDummy)) then + self:Fire("open") + else + timer.Remove(uniqueID2) + end + end) + + timer.Create(uniqueID, lifeTime, 1, function() + if (IsValid(self) and IsValid(dummy)) then + uniqueID = "dummyFade"..dummy:EntIndex() + local alpha = 255 + + timer.Create(uniqueID, 0.1, 255, function() + if (IsValid(dummy)) then + alpha = alpha - 1 + dummy:SetColor(ColorAlpha(color, alpha)) + + if (alpha <= 0) then + dummy:Remove() + end + else + timer.Remove(uniqueID) + end + end) + end + end) + + return dummy + end + +else + -- Returns the door's slave entity. + function meta:GetDoorPartner() + local owner = self:GetOwner() or self.ixDoorOwner + + if (IsValid(owner) and owner:IsDoor()) then + return owner + end + + for _, v in ipairs(ents.FindByClass("prop_door_rotating")) do + if (v:GetOwner() == self) then + self.ixDoorOwner = v + + return v + end + end + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/meta/sh_inventory.lua b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_inventory.lua new file mode 100644 index 0000000..84127b5 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_inventory.lua @@ -0,0 +1,964 @@ + +--[[-- +Holds items within a grid layout. + +Inventories are an object that contains `Item`s in a grid layout. Every `Character` will have exactly one inventory attached to +it, which is the only inventory that is allowed to hold bags - any item that has its own inventory (i.e a suitcase). Inventories +can be owned by a character, or it can be individually interacted with as a standalone object. For example, the container plugin +attaches inventories to props, allowing for items to be stored outside of any character inventories and remain "in the world". + + +You may be looking for the following common functions: + +`Add` Which adds an item to the inventory. + +`GetItems` Which gets all of the items inside the inventory. + +`GetItemByID` Which gets an item in the inventory by it's item ID. + +`GetItemAt` Which gets an item in the inventory by it's x and y + +`GetID` Which gets the inventory's ID. + +`HasItem` Which checks if the inventory has an item. +]] +-- @classmod Inventory + +local META = ix.meta.inventory or ix.middleclass("ix_inventory") + +META.slots = META.slots or {} +META.w = META.w or 4 +META.h = META.h or 4 +META.vars = META.vars or {} +META.receivers = META.receivers or {} + +--- Returns a string representation of this inventory +-- @realm shared +-- @treturn string String representation +-- @usage print(ix.item.inventories[1]) +-- > "inventory[1]" +function META:__tostring() + return "inventory["..(self.id or 0).."]" +end + +--- Initializes the inventory with the provided arguments. +-- @realm shared +-- @internal +-- @number id The `Inventory`'s database ID. +-- @number width The inventory's width. +-- @number height The inventory's height. +function META:Initialize(id, width, height) + self.id = id + self.w = width + self.h = height + + self.slots = {} + self.vars = {} + self.receivers = {} +end + +--- Returns this inventory's database ID. This is guaranteed to be unique. +-- @realm shared +-- @treturn number Unique ID of inventory +function META:GetID() + return self.id or 0 +end + +--- Sets the grid size of this inventory. +-- @realm shared +-- @internal +-- @number width New width of inventory +-- @number height New height of inventory +function META:SetSize(width, height) + self.w = width + self.h = height +end + +--- Returns the grid size of this inventory. +-- @realm shared +-- @treturn number Width of inventory +-- @treturn number Height of inventory +function META:GetSize() + return self.w, self.h +end + +-- this is pretty good to debug/develop function to use. +function META:Print(printPos) + for k, v in pairs(self:GetItems()) do + local str = k .. ": " .. v.name + + if (printPos) then + str = str .. " (" .. v.gridX .. ", " .. v.gridY .. ")" + end + + print(str) + end +end + + +--- Searches the inventory to find any stacked items. +-- A common problem with developing, is that items will sometimes error out, or get corrupt. +-- Sometimes, the server knows things you don't while developing live +-- This function can be helpful for getting rid of those pesky errors. +-- @realm shared +function META:FindError() + for k, _ in self:Iter() do + if (k.width == 1 and k.height == 1) then + continue + end + + print("Finding error: " .. k.name) + print("Item Position: " .. k.gridX, k.gridY) + + for x = k.gridX, k.gridX + k.width - 1 do + for y = k.gridY, k.gridY + k.height - 1 do + local item = self.slots[x][y] + + if (item and item.id != k.id) then + print("Error Found: ".. item.name) + end + end + end + end +end + +--- Prints out the id, width, height, slots and each item in each slot of an `Inventory`, used for debugging. +-- @realm shared +function META:PrintAll() + print("------------------------") + print("INVID", self:GetID()) + print("INVSIZE", self:GetSize()) + + if (self.slots) then + for x = 1, self.w do + for y = 1, self.h do + local item = self.slots[x] and self.slots[x][y] + if (item and item.id) then + print(item.name .. "(" .. item.id .. ")", x, y) + end + end + end + end + + print("INVVARS") + PrintTable(self.vars or {}) + print("------------------------") +end + +--- Returns the player that owns this inventory. +-- @realm shared +-- @treturn[1] Player Owning player +-- @treturn[2] nil If no connected player owns this inventory +function META:GetOwner() + for _, v in player.Iterator() do + if (v:GetCharacter() and v:GetCharacter().id == self.owner) then + return v + end + end +end + +--- Sets the player that owns this inventory. +-- @realm shared +-- @player owner The player to take control over the inventory. +-- @bool fullUpdate Whether or not to update the inventory immediately to the new owner. +function META:SetOwner(owner, fullUpdate) + if (type(owner) == "Player" and owner:GetNetVar("char")) then + owner = owner:GetNetVar("char") + elseif (!isnumber(owner)) then + return + end + + if (SERVER) then + if (fullUpdate) then + for _, v in player.Iterator() do + if (v:GetNetVar("char") == owner) then + self:Sync(v, true) + + break + end + end + end + + local query = mysql:Update("ix_inventories") + query:Update("character_id", owner) + query:Where("inventory_id", self:GetID()) + query:Execute() + end + + self.owner = owner +end + +--- Checks whether a player has access to an inventory +-- @realm shared +-- @internal +-- @player client Player to check access for +-- @treturn bool Whether or not the player has access to the inventory +function META:OnCheckAccess(client) + local bAccess = false + + for _, v in ipairs(self:GetReceivers()) do + if (v == client) then + bAccess = true + break + end + end + + return bAccess +end + +--- Checks whether or not an `Item` can fit into the `Inventory` starting from `x` and `y`. +-- Internally used by FindEmptySlot, in most cases you are better off using that. +-- This function will search if all of the slots within `x + width` and `y + width` are empty, +-- ignoring any space the `Item` itself already occupies. +-- @realm shared +-- @internal +-- @number x The beginning x coordinate to search for. +-- @number y The beginning y coordiate to search for. +-- @number w The `Item`'s width. +-- @number h The `Item`'s height. +-- @item[opt=nil] item2 An `Item`, if any, to ignore when searching. +function META:CanItemFit(x, y, w, h, item2) + local canFit = true + + for x2 = 0, w - 1 do + for y2 = 0, h - 1 do + local item = (self.slots[x + x2] or {})[y + y2] + + if ((x + x2) > self.w or item) then + if (item2) then + if (item and item.id == item2.id) then + continue + end + end + + canFit = false + break + end + end + + if (!canFit) then + break + end + end + + return canFit +end + + +--- Returns the amount of slots currently filled in the Inventory. +-- @realm shared +-- @treturn number The amount of slots currently filled. +function META:GetFilledSlotCount() + local count = 0 + + for x = 1, self.w do + for y = 1, self.h do + if ((self.slots[x] or {})[y]) then + count = count + 1 + end + end + end + + return count +end + +--- Finds an empty slot of a specified width and height. +-- In most cases, to check if an `Item` can actually fit in the `Inventory`, +-- as if it can't, it will just return `nil`. +-- +-- FindEmptySlot will loop through all the slots for you, as opposed to `CanItemFit` +-- which you specify an `x` and `y` for. +-- this will call CanItemFit anyway. +-- If you need to check if an item will fit *exactly* at a position, you want CanItemFit instead. +-- @realm shared +-- @number w The width of the `Item` you are trying to fit. +-- @number h The height of the `Item` you are trying to fit. +-- @bool onlyMain Whether or not to search any bags connected to this `Inventory` +-- @treturn[1] number x The `x` coordinate that the `Item` can fit into. +-- @treturn[1] number y The `y` coordinate that the `Item` can fit into. +-- @treturn[2] number x The `x` coordinate that the `Item` can fit into. +-- @treturn[2] number y The `y` coordinate that the `Item` can fit into. +-- @treturn[2] Inventory bagInv If the item was in a bag, it will return the inventory it was in. +-- @see CanItemFit +function META:FindEmptySlot(w, h, onlyMain) + w = w or 1 + h = h or 1 + + if (w > self.w or h > self.h) then + return + end + + for y = 1, self.h - (h - 1) do + for x = 1, self.w - (w - 1) do + if (self:CanItemFit(x, y, w, h)) then + return x, y + end + end + end + + if (onlyMain != true) then + local bags = self:GetBags() + + if (#bags > 0) then + for _, invID in ipairs(bags) do + local bagInv = ix.item.inventories[invID] + + if (bagInv) then + local x, y = bagInv:FindEmptySlot(w, h) + + if (x and y) then + return x, y, bagInv + end + end + end + end + end +end + +--- Returns the item that currently exists within `x` and `y` in the `Inventory`. +-- Items that have a width or height greater than 0 occupy more than 1 x and y. +-- @realm shared +-- @number x The `x` coordindate to search in. +-- @number y The `y` coordinate to search in. +-- @treturn number x The `x` coordinate that the `Item` is located at. +-- @treturn number y The `y` coordinate that the `Item` is located at. +function META:GetItemAt(x, y) + if (self.slots and self.slots[x]) then + return self.slots[x][y] + end +end + +--- Removes an item from the inventory. +-- @realm shared +-- @number id The item instance ID to remove +-- @bool[opt=false] bNoReplication Whether or not the item's removal should not be replicated +-- @bool[opt=false] bNoDelete Whether or not the item should not be fully deleted +-- @bool[opt=false] bTransferring Whether or not the item is being transferred to another inventory +-- @treturn number The X position that the item was removed from +-- @treturn number The Y position that the item was removed from +function META:Remove(id, bNoReplication, bNoDelete, bTransferring) + local x2, y2 + + for x = 1, self.w do + if (self.slots[x]) then + for y = 1, self.h do + local item = self.slots[x][y] + + if (item and item.id == id) then + self.slots[x][y] = nil + + x2 = x2 or x + y2 = y2 or y + end + end + end + end + + if (SERVER and !bNoReplication) then + local receivers = self:GetReceivers() + + if (istable(receivers)) then + net.Start("ixInventoryRemove") + net.WriteUInt(id, 32) + net.WriteUInt(self:GetID(), 32) + net.Send(receivers) + end + + -- we aren't removing the item - we're transferring it to another inventory + if (!bTransferring) then + hook.Run("InventoryItemRemoved", self, ix.item.instances[id]) + end + + if (!bNoDelete) then + local item = ix.item.instances[id] + + if (item and item.OnRemoved) then + item:OnRemoved() + end + + local query = mysql:Delete("ix_items") + query:Where("item_id", id) + query:Execute() + + ix.item.instances[id] = nil + end + end + + return x2, y2 +end + +--- Adds a player as a receiver on this `Inventory` +-- Receivers are players who will be networked the items inside the inventory. +-- +-- Calling this will *not* automatically sync it's current contents to the client. +-- All future contents will be synced, but not anything that was not synced before this is called. +-- +-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error. +-- @realm shared +-- @player client The player to add as a receiver. +function META:AddReceiver(client) + self.receivers[client] = true +end + +--- The opposite of `AddReceiver`. +-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error. +-- @realm shared +-- @player client The player to remove from the receiver list. +function META:RemoveReceiver(client) + self.receivers[client] = nil +end + +--- Get all of the receivers this `Inventory` has. +-- Receivers are players who will be networked the items inside the inventory. +-- +-- This function will automatically sort out invalid players for you. +-- @realm shared +-- @treturn table result The players who are on the server and allowed to see this table. +function META:GetReceivers() + local result = {} + + if (self.receivers) then + for k, _ in pairs(self.receivers) do + if (IsValid(k) and k:IsPlayer()) then + result[#result + 1] = k + end + end + end + + return result +end + +--- Returns a count of a *specific* `Item`s in the `Inventory` +-- @realm shared +-- @string uniqueID The Unique ID of the item. +-- @bool onlyMain Whether or not to exclude bags that are present from the search. +-- @treturn number The amount of `Item`s this inventory has. +-- @usage local curHighest, winner = 0, false +-- for client, character in ix.util.GetCharacters() do +-- local itemCount = character:GetInventory():GetItemCount('water', false) +-- if itemCount > curHighest then +-- curHighest = itemCount +-- winner = character +-- end +-- end +-- -- Finds the thirstiest character on the server and returns their Character ID or false if no character has water. +function META:GetItemCount(uniqueID, onlyMain) + local i = 0 + + for _, v in pairs(self:GetItems(onlyMain)) do + if (v.uniqueID == uniqueID) then + i = i + 1 + end + end + + return i +end + +--- Returns a table of all `Item`s in the `Inventory` by their Unique ID. +-- Not to be confused with `GetItemsByID` or `GetItemByID` which take in an Item Instance's ID instead. +-- @realm shared +-- @string uniqueID The Unique ID of the item. +-- @bool onlyMain Whether or not to exclude bags that are present from the search. +-- @treturn number The table of specified `Item`s this inventory has. +function META:GetItemsByUniqueID(uniqueID, onlyMain) + local items = {} + + for _, v in pairs(self:GetItems(onlyMain)) do + if (v.uniqueID == uniqueID) then + items[#items + 1] = v + end + end + + return items +end + +--- Returns a table of `Item`s by their base. +-- @realm shared +-- @string baseID The base to search for. +-- @bool bOnlyMain Whether or not to exclude bags that are present from the search. +function META:GetItemsByBase(baseID, bOnlyMain) + local items = {} + + for _, v in pairs(self:GetItems(bOnlyMain)) do + if (v.base == baseID) then + items[#items + 1] = v + end + end + + return items +end + +--- Get an item by it's specific Database ID. +-- @realm shared +-- @number id The ID to search for. +-- @bool onlyMain Whether or not to exclude bags that are present from the search. +-- @treturn item The item if it exists. +function META:GetItemByID(id, onlyMain) + for _, v in pairs(self:GetItems(onlyMain)) do + if (v.id == id) then + return v + end + end +end + +--- Get a table of `Item`s by their specific Database ID. +-- It's important to note that while in 99% of cases, +-- items will have a unique Database ID, developers or random GMod weirdness could +-- cause a second item with the same ID to appear, even though, `ix.item.instances` will only store one of those. +-- The inventory only stores a reference to the `ix.item.instance` ID, not the memory reference itself. +-- @realm shared +-- @number id The ID to search for. +-- @bool onlyMain Whether or not to exclude bags that are present from the search. +-- @treturn item The item if it exists. +function META:GetItemsByID(id, onlyMain) + local items = {} + + for _, v in pairs(self:GetItems(onlyMain)) do + if (v.id == id) then + items[#items + 1] = v + end + end + + return items +end + +-- This function may pretty heavy. + +--- Returns a table of all the items that an `Inventory` has. +-- @realm shared +-- @bool onlyMain Whether or not to exclude bags from this search. +-- @treturn table The items this `Inventory` has. +function META:GetItems(onlyMain) + local items = {} + + for _, v in pairs(self.slots) do + for _, v2 in pairs(v) do + if (istable(v2) and !items[v2.id]) then + items[v2.id] = v2 + + v2.data = v2.data or {} + local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id) + + if (isBag and isBag != self:GetID() and onlyMain != true) then + local bagInv = ix.item.inventories[isBag] + + if (bagInv) then + local bagItems = bagInv:GetItems() + + table.Merge(items, bagItems) + end + end + end + end + end + + return items +end + +--- Returns an iterator that returns all contained items, a better way to iterate items than `pairs(inventory:GetItems())` +-- @realm shared +-- @treturn function iterator +function META:Iter() + local x, y, item = 1, 1 + + return function() + item = nil + + repeat + if (x > self.w) then + x, y = 1, y + 1 + if (y > self.h) then return nil end + end + + item = self.slots[x] and self.slots[x][y] + x = x + 1 + until item and not isbool(item) -- skip reserved slots in which items have yet to be fully instantiated + + if (item) then + return item, x, y + end + end +end + +-- This function may pretty heavy. +--- Returns a table of all the items that an `Inventory` has. +-- @realm shared +-- @bool onlyMain Whether or not to exclude bags from this search. +-- @treturn table The items this `Inventory` has. +function META:GetBags() + local invs = {} + for _, v in pairs(self.slots) do + for _, v2 in pairs(v) do + if (istable(v2) and v2.data) then + local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id) + + if (!table.HasValue(invs, isBag)) then + if (isBag and isBag != self:GetID()) then + invs[#invs + 1] = isBag + end + end + end + end + end + + return invs +end +--- Returns the item with the given unique ID (e.g `"handheld_radio"`) if it exists in this inventory. +-- This method checks both +-- this inventory, and any bags that this inventory has inside of it. +-- @realm shared +-- @string targetID Unique ID of the item to look for +-- @tab[opt] data Item data to check for +-- @treturn[1] Item Item that belongs to this inventory with the given criteria +-- @treturn[2] bool `false` if the item does not exist +-- @see HasItems +-- @see HasItemOfBase +-- @usage local item = inventory:HasItem("handheld_radio") +-- +-- if (item) then +-- -- do something with the item table +-- end +function META:HasItem(targetID, data) + for k, _ in self:Iter() do + if (k.uniqueID == targetID) then + if (data) then + local itemData = k.data + local bFound = true + + for dataKey, dataVal in pairs(data) do + if (itemData[dataKey] != dataVal) then + bFound = false + break + end + end + + if (!bFound) then + continue + end + end + + return k + end + end + + return false +end + +--- Checks whether or not the `Inventory` has a table of items. +-- This function takes a table with **no** keys and runs in order of first item > last item, +--this is due to the usage of the `#` operator in the function. +-- +-- @realm shared +-- @tab targetIDs A table of `Item` Unique ID's. +-- @treturn[1] bool true Whether or not the `Inventory` has all of the items. +-- @treturn[1] table targetIDs Your provided targetIDs table, but it will be empty. +-- @treturn[2] bool false +-- @treturn[2] table targetIDs Table consisting of the items the `Inventory` did **not** have. +-- @usage local itemFilter = {'water', 'water_sparkling'} +-- if not Entity(1):GetCharacter():GetInventory():HasItems(itemFilter) then return end +-- -- Filters out if this player has both a water, and a sparkling water. +function META:HasItems(targetIDs) + local count = #targetIDs -- assuming array + targetIDs = table.Copy(targetIDs) + + for item, _ in self:Iter() do + for k, targetID in ipairs(targetIDs) do + if (item.uniqueID == targetID) then + table.remove(targetIDs, k) + count = count - 1 + + break + end + end + end + + return count <= 0, targetIDs +end + +--- Whether or not an `Inventory` has an item of a base, optionally with specified data. +-- This function has an optional `data` argument, which will take a `table`. +-- it will match if the data of the item is correct or not. +-- +-- Items which are a base will automatically have base_ prefixed to their Unique ID, if you are having +-- trouble finding your base, that is probably why. +-- @realm shared +-- @string baseID The Item Base's Unique ID. +-- @tab[opt] data The Item's data to compare against. +-- @treturn[1] item The first `Item` of `baseID` that is found and there is no `data` argument or `data` was matched. +-- @treturn[2] false If no `Item`s of `baseID` is found or the `data` argument, if specified didn't match. +-- @usage local bHasWeaponEquipped = Entity(1):GetCharacter():GetInventory():HasItemOfBase('base_weapons', {['equip'] = true}) +-- if bHasWeaponEquipped then +-- Entity(1):Notify('One gun is fun, two guns is Woo-tastic.') +-- end +-- -- Notifies the player that they should get some more guns. +function META:HasItemOfBase(baseID, data) + for k, _ in self:Iter() do + if (k.base == baseID) then + if (data) then + local itemData = k.data + local bFound = true + + for dataKey, dataVal in pairs(data) do + if (itemData[dataKey] != dataVal) then + bFound = false + break + end + end + + if (!bFound) then + continue + end + end + + return k + end + end + + return false +end + +if (SERVER) then + --- Sends a specific slot to a character. + -- This will *not* send all of the slots of the `Item` to the character, items can occupy multiple slots. + -- + -- This will call `OnSendData` on the Item using all of the `Inventory`'s receivers. + -- + -- This function should *not* be used to sync an entire inventory, if you need to do that, use `AddReceiver` and `Sync`. + -- @realm server + -- @internal + -- @number x The Inventory x position to send. + -- @number y The Inventory y position to send. + -- @item[opt] item The item to send, if any. + -- @see AddReceiver + -- @see Sync + function META:SendSlot(x, y, item) + local receivers = self:GetReceivers() + local sendData = item and item.data and !table.IsEmpty(item.data) and item.data or {} + + net.Start("ixInventorySet") + net.WriteUInt(self:GetID(), 32) + net.WriteUInt(x, 6) + net.WriteUInt(y, 6) + net.WriteString(item and item.uniqueID or "") + net.WriteUInt(item and item.id or 0, 32) + net.WriteUInt(self.owner or 0, 32) + net.WriteTable(sendData) + net.Send(receivers) + + if (item) then + for _, v in pairs(receivers) do + item:Call("OnSendData", v) + end + end + end + + --- Sets whether or not an `Inventory` should save. + -- This will prevent an `Inventory` from updating in the Database, if the inventory is already saved, + -- it will not be deleted when unloaded. + -- @realm server + -- @bool bNoSave Whether or not the Inventory should save. + function META:SetShouldSave(bNoSave) + self.noSave = bNoSave + end + + --- Gets whether or not an `Inventory` should save. + -- Inventories that are marked to not save will not update in the Database, if they inventory is already saved, + -- it will not be deleted when unloaded. + -- @realm server + -- @treturn[1] bool Returns the field `noSave`. + -- @treturn[2] bool Returns true if the field `noSave` is not registered to this inventory. + function META:GetShouldSave() + return self.noSave or true + end + + --- Add an item to the inventory. + -- @realm server + -- @param uniqueID The item unique ID (e.g `"handheld_radio"`) or instance ID (e.g `1024`) to add to the inventory + -- @number[opt=1] quantity The quantity of the item to add + -- @tab data Item data to add to the item + -- @number[opt=nil] x The X position for the item + -- @number[opt=nil] y The Y position for the item + -- @bool[opt=false] noReplication Whether or not the item's addition should not be replicated + -- @treturn[1] bool Whether the add was successful or not + -- @treturn[1] string The error, if applicable + -- @treturn[2] number The X position that the item was added to + -- @treturn[2] number The Y position that the item was added to + -- @treturn[2] number The inventory ID that the item was added to + function META:Add(uniqueID, quantity, data, x, y, noReplication) + quantity = quantity or 1 + + if (quantity < 1) then + return false, "noOwner" + end + + if (!isnumber(uniqueID) and quantity > 1) then + for _ = 1, quantity do + local bSuccess, error = self:Add(uniqueID, 1, data) + + if (!bSuccess) then + return false, error + end + end + + return true + end + + local client = self.GetOwner and self:GetOwner() or nil + local item = isnumber(uniqueID) and ix.item.instances[uniqueID] or ix.item.list[uniqueID] + local targetInv = self + local bagInv + + if (!item) then + return false, "invalidItem" + end + + if (isnumber(uniqueID)) then + local oldInvID = item.invID + + if (!x and !y) then + x, y, bagInv = self:FindEmptySlot(item.width, item.height) + end + + if (bagInv) then + targetInv = bagInv + end + + -- we need to check for owner since the item instance already exists + if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter() and + item:GetPlayerID() == client:SteamID64() and item:GetCharacterID() != client:GetCharacter():GetID()) then + return false, "itemOwned" + end + + if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv) == false) then + return false, "notAllowed" + end + + if (x and y) then + targetInv.slots[x] = targetInv.slots[x] or {} + targetInv.slots[x][y] = true + + item.gridX = x + item.gridY = y + item.invID = targetInv:GetID() + + for x2 = 0, item.width - 1 do + local index = x + x2 + + for y2 = 0, item.height - 1 do + targetInv.slots[index] = targetInv.slots[index] or {} + targetInv.slots[index][y + y2] = item + end + end + + if (!noReplication) then + targetInv:SendSlot(x, y, item) + end + + if (!self.noSave) then + local query = mysql:Update("ix_items") + query:Update("inventory_id", targetInv:GetID()) + query:Update("x", x) + query:Update("y", y) + query:Where("item_id", item.id) + query:Execute() + end + + hook.Run("InventoryItemAdded", ix.item.inventories[oldInvID], targetInv, item) + + return x, y, targetInv:GetID() + else + return false, "noFit" + end + else + if (!x and !y) then + x, y, bagInv = self:FindEmptySlot(item.width, item.height) + end + + if (bagInv) then + targetInv = bagInv + end + + if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv) == false) then + return false, "notAllowed" + end + + if (x and y) then + for x2 = 0, item.width - 1 do + local index = x + x2 + + for y2 = 0, item.height - 1 do + targetInv.slots[index] = targetInv.slots[index] or {} + targetInv.slots[index][y + y2] = true + end + end + + local characterID + local playerID + + if (self.owner) then + local character = ix.char.loaded[self.owner] + + if (character) then + characterID = character.id + playerID = character.steamID + end + end + + ix.item.Instance(targetInv:GetID(), uniqueID, data, x, y, function(newItem) + newItem.gridX = x + newItem.gridY = y + + for x2 = 0, newItem.width - 1 do + local index = x + x2 + + for y2 = 0, newItem.height - 1 do + targetInv.slots[index] = targetInv.slots[index] or {} + targetInv.slots[index][y + y2] = newItem + end + end + + if (!noReplication) then + targetInv:SendSlot(x, y, newItem) + end + + hook.Run("InventoryItemAdded", nil, targetInv, newItem) + end, characterID, playerID) + + return x, y, targetInv:GetID() + else + return false, "noFit" + end + end + end + + --- Syncs the `Inventory` to the receiver. + -- This will call Item.OnSendData on every item in the `Inventory`. + -- @realm server + -- @player receiver The player to + function META:Sync(receiver) + local slots = {} + + for x, items in pairs(self.slots) do + for y, item in pairs(items) do + if (istable(item) and item.gridX == x and item.gridY == y) then + slots[#slots + 1] = {x, y, item.uniqueID, item.id, item.data} + end + end + end + + net.Start("ixInventorySync") + net.WriteTable(slots) + net.WriteUInt(self:GetID(), 32) + net.WriteUInt(self.w, 6) + net.WriteUInt(self.h, 6) + net.WriteType(self.owner) + net.WriteTable(self.vars or {}) + net.Send(receiver) + + for k, _ in self:Iter() do + k:Call("OnSendData", receiver) + end + end +end + +ix.meta.inventory = META diff --git a/garrysmod/gamemodes/helix/gamemode/core/meta/sh_item.lua b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_item.lua new file mode 100644 index 0000000..a189f1e --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_item.lua @@ -0,0 +1,698 @@ + +--[[-- +Interactable entities that can be held in inventories. + +Items are objects that are contained inside of an `Inventory`, or as standalone entities if they are dropped in the world. They +usually have functionality that provides more gameplay aspects to the schema. For example, the zipties in the HL2 RP schema +allow a player to tie up and search a player. + +For an item to have an actual presence, they need to be instanced (usually with `ix.item.Instance`). Items describe the +properties, while instances are a clone of these properties that can have their own unique data (e.g an ID card will have the +same name but different numerical IDs). You can think of items as the class, while instances are objects of the `Item` class. + +## Creating item classes (`ItemStructure`) +Item classes are defined in their own file inside of your schema or plugin's `items/` folder. In these item class files you +specify how instances of the item behave. This includes default values for basic things like the item's name and description, +to more advanced things by overriding extra methods from an item base. See `ItemStructure` for information on how to define +a basic item class. + +Item classes in this folder are automatically loaded by Helix when the server starts up. + +## Item bases +If many items share the same functionality (i.e a can of soda and a bottle of water can both be consumed), then you might want +to consider using an item base to reduce the amount of duplication for these items. Item bases are defined the same way as +regular item classes, but they are placed in the `items/base/` folder in your schema or plugin. For example, a `consumables` +base would be in `items/base/sh_consumables.lua`. + +Any items that you want to use this base must be placed in a subfolder that has the name of the base you want that item to use. +For example, for a bottled water item to use the consumable base, it must be placed in `items/consumables/sh_bottled_water.lua`. +This also means that you cannot place items into subfolders as you wish, since the framework will try to use an item base that +doesn't exist. + +The default item bases that come with Helix are: + + - `ammo` - provides ammo to any items with the `weapons` base + - `bags` - holds an inventory that other items can be stored inside of + - `outfit` - changes the appearance of the player that wears it + - `pacoutfit` - changes the appearance of the player that wears it using PAC3 + - `weapons` - makes any SWEP into an item that can be equipped + +These item bases usually come with extra values and methods that you can define/override in order to change their functionality. +You should take a look at the source code for these bases to see their capabilities. + +## Item functions (`ItemFunctionStructure`) +Requiring players to interact with items in order for them to do something is quite common. As such, there is already a built-in +mechanism to allow players to right-click items and show a list of available options. Item functions are defined in your item +class file in the `ITEM.functions` table. See `ItemFunctionStructure` on how to define them. + +Helix comes with `drop`, `take`, and `combine` item functions by default that allows items to be dropped from a player's +inventory, picked up from the world, and combining items together. These can be overridden by defining an item function +in your item class file with the same name. See the `bags` base for example usage of the `combine` item function. + +## Item icons (`ItemIconStructure`) +Icons for items sometimes don't line up quite right, in which case you can modify an item's `iconCam` value and line up the +rendered model as needed. See `ItemIconStructure` for more details. +]] +-- @classmod Item + +--[[-- +All item functions live inside of an item's `functions` table. An item function entry includes a few methods and fields you can +use to customize the functionality and appearance of the item function. An example item function is below: + + -- this item function's unique ID is "MyFunction" + ITEM.functions.MyFunction = { + name = "myFunctionPhrase", -- uses the "myFunctionPhrase" language phrase when displaying in the UI + tip = "myFunctionDescription", -- uses the "myFunctionDescription" language phrase when displaying in the UI + icon = "icon16/add.png", -- path to the icon material + OnRun = function(item) + local client = item.player + local entity = item.entity -- only set if this is function is being ran while the item is in the world + + if (IsValid(client)) then + client:ChatPrint("This is a test.") + + if (IsValid(entity)) then + client:ChatPrint(entity:GetName()) + end + end + + -- do not remove this item from the owning player's inventory + return false + end, + OnCanRun = function(item) + -- only allow admins to run this item function + local client = item.player + return IsValid(client) and client:IsAdmin() + end + } +]] +-- @table ItemFunctionStructure +-- @realm shared +-- @field[type=string,opt] name Language phrase to use when displaying this item function's name in the UI. If not specified, +-- then it will use the unique ID of the item function +-- @field[type=string,opt] tip Language phrase to use when displaying this item function's detailed description in the UI +-- @field[type=string,opt] icon Path to the material to use when displaying this item function's icon +-- @field[type=function] OnRun Function to call when the item function is ran. This function is **ONLY** ran on the server. +-- +-- The only argument passed into this function is the instance of the item being called. The instance will have its `player` +-- field set if the item function is being ran by a player (which it should be most of the time). It will also have its `entity` +-- field set if the item function is being ran while the item is in the world, and not in a player's inventory. +-- +-- The item will be removed after the item function is ran. If you want to prevent this behaviour, then you can return `false` +-- in this function. See the example above. +-- @field[type=function] OnCanRun Function to call when checking whether or not this item function can be ran. This function is +-- ran **BOTH** on the client and server. +-- +-- The arguments are the same as `OnCanRun`, and the `player` and `entity` fields will be set on the item instance accordingly. +-- Returning `true` will allow the item function to be ran. Returning `false` will prevent it from running and additionally +-- hide it from the UI. See the example above. +-- @field[type=function,opt] OnClick This function is called when the player clicks on this item function's entry in the UI. +-- This function is ran **ONLY** on the client, and is only ran if `OnCanRun` succeeds. +-- +-- The same arguments from `OnCanRun` and `OnRun` apply to this function. + +--[[-- +Changing the way an item's icon is rendered is done by modifying the location and angle of the model, as well as the FOV of the +camera. You can tweak the values in code, or use the `ix_dev_icon` console command to visually position the model and camera. An +example entry for an item's icon is below: + + ITEM.iconCam = { + pos = Vector(0, 0, 60), + ang = Angle(90, 0, 0), + fov = 45 + } + +Note that this will probably not work for your item's specific model, since every model has a different size, origin, etc. All +item icons need to be tweaked individually. +]] +-- @table ItemIconStructure +-- @realm client +-- @field[type=vector] pos Location of the model relative to the camera. +X is forward, +Z is up +-- @field[type=angle] ang Angle of the model +-- @field[type=number] fov FOV of the camera + +--[[-- +When creating an item class, the file will have a global table `ITEM` set that you use to define the item's values/methods. An +example item class is below: + +`items/sh_brick.lua` + ITEM.name = "Brick" + ITEM.description = "A brick. Pretty self-explanatory. You can eat it but you'll probably lose some teeth." + ITEM.model = Model("models/props_debris/concrete_cynderblock001.mdl") + ITEM.width = 1 + ITEM.height = 1 + ITEM.price = 25 + +Note that the below list only includes the default fields available for *all* items, and not special ones defined in custom +item bases. +]] +-- @table ItemStructure +-- @realm shared +-- @field[type=string] name Display name of the item +-- @field[type=string] description Detailed description of the item +-- @field[type=string] model Model to use for the item's icon and when it's dropped in the world +-- @field[type=number,opt=1] width Width of the item in grid cells +-- @field[type=number,opt=1] height Height of the item in grid cells +-- @field[type=number,opt=0] price How much money it costs to purchase this item in the business menu +-- @field[type=string,opt] category Name of the category this item belongs to - mainly used for the business menu +-- @field[type=boolean,opt=false] noBusiness Whether or not to disallow purchasing this item in the business menu +-- @field[type=table,opt] factions List of factions allowed to purchase this item in the business menu +-- @field[type=table,opt] classes List of character classes allowed to purchase this item in the business menu. Classes are +-- checked after factions, so the character must also be in an allowed faction +-- @field[type=string,opt] flag List of flags (as a string - e.g `"a"` or `"abc"`) allowed to purchase this item in the +-- business menu. Flags are checked last, so the character must also be in an allowed faction and class +-- @field[type=ItemIconStructure,opt] iconCam How to render this item's icon +-- @field[type=table,opt] functions List of all item functions that this item has. See `ItemFunctionStructure` on how to define +-- new item functions + +local ITEM = ix.meta.item or {} +ITEM.__index = ITEM +ITEM.name = "Undefined" +ITEM.description = ITEM.description or "An item that is undefined." +ITEM.id = ITEM.id or 0 +ITEM.uniqueID = "undefined" + +--- Returns a string representation of this item. +-- @realm shared +-- @treturn string String representation +-- @usage print(ix.item.instances[1]) +-- > "item[1]" +function ITEM:__tostring() + return "item["..self.uniqueID.."]["..self.id.."]" +end + +--- Returns true if this item is equal to another item. Internally, this checks item IDs. +-- @realm shared +-- @item other Item to compare to +-- @treturn bool Whether or not this item is equal to the given item +-- @usage print(ix.item.instances[1] == ix.item.instances[2]) +-- > false +function ITEM:__eq(other) + return self:GetID() == other:GetID() +end + +--- Returns this item's database ID. This is guaranteed to be unique. +-- @realm shared +-- @treturn number Unique ID of item +function ITEM:GetID() + return self.id +end + +--- Returns the name of the item. +-- @realm shared +-- @treturn string The name of the item +function ITEM:GetName() + return (CLIENT and L(self.name) or self.name) +end + +--- Returns the description of the item. +-- @realm shared +-- @treturn string The description of the item +function ITEM:GetDescription() + if (!self.description) then return "ERROR" end + + return L(self.description or "noDesc") +end + +--- Returns the model of the item. +-- @realm shared +-- @treturn string The model of the item +function ITEM:GetModel() + return self.model +end + +--- Returns the skin of the item. +-- @realm shared +-- @treturn number The skin of the item +function ITEM:GetSkin() + return self.skin or 0 +end + +function ITEM:GetMaterial() + return nil +end + +--- Returns the ID of the owning character, if one exists. +-- @realm shared +-- @treturn number The owning character's ID +function ITEM:GetCharacterID() + return self.characterID +end + +--- Returns the SteamID64 of the owning player, if one exists. +-- @realm shared +-- @treturn number The owning player's SteamID64 +function ITEM:GetPlayerID() + return self.playerID +end + +--- A utility function which prints the item's details. +-- @realm shared +-- @bool[opt=false] detail Whether additional detail should be printed or not(Owner, X position, Y position) +function ITEM:Print(detail) + if (detail == true) then + print(Format("%s[%s]: >> [%s](%s,%s)", self.uniqueID, self.id, self.owner, self.gridX, self.gridY)) + else + print(Format("%s[%s]", self.uniqueID, self.id)) + end +end + +--- A utility function printing the item's stored data. +-- @realm shared +function ITEM:PrintData() + self:Print(true) + print("ITEM DATA:") + for k, v in pairs(self.data) do + print(Format("[%s] = %s", k, v)) + end +end + +--- Calls one of the item's methods. +-- @realm shared +-- @string method The method to be called +-- @player client The client to pass when calling the method, if applicable +-- @entity entity The eneity to pass when calling the method, if applicable +-- @param ... Arguments to pass to the method +-- @return The values returned by the method +function ITEM:Call(method, client, entity, ...) + local oldPlayer, oldEntity = self.player, self.entity + + self.player = client or self.player + self.entity = entity or self.entity + + if (isfunction(self[method])) then + local results = {self[method](self, ...)} + + self.player = nil + self.entity = nil + + return unpack(results) + end + + self.player = oldPlayer + self.entity = oldEntity +end + +--- Returns the player that owns this item. +-- @realm shared +-- @treturn player Player owning this item +function ITEM:GetOwner() + local inventory = ix.item.inventories[self.invID] + + if (inventory) then + return inventory.GetOwner and inventory:GetOwner() + end + + local id = self:GetID() + + for _, v in player.Iterator() do + local character = v:GetCharacter() + + if (character and character:GetInventory() and character:GetInventory():GetItemByID(id)) then + return v + end + end +end + +--- Sets a key within the item's data. +-- @realm shared +-- @string key The key to store the value within +-- @param[opt=nil] value The value to store within the key +-- @tab[opt=nil] receivers The players to replicate the data on +-- @bool[opt=false] noSave Whether to disable saving the data on the database or not +-- @bool[opt=false] noCheckEntity Whether to disable setting the data on the entity, if applicable +function ITEM:SetData(key, value, receivers, noSave, noCheckEntity) + self.data = self.data or {} + self.data[key] = value + + if (SERVER) then + if (!noCheckEntity) then + local ent = self:GetEntity() + + if (IsValid(ent)) then + local data = ent:GetNetVar("data", {}) + data[key] = value + + ent:SetNetVar("data", data) + end + end + end + + if (receivers != false and (receivers or self:GetOwner())) then + net.Start("ixInventoryData") + net.WriteUInt(self:GetID(), 32) + net.WriteString(key) + net.WriteType(value) + net.Send(receivers or self:GetOwner()) + end + + if (!noSave and ix.db) then + local query = mysql:Update("ix_items") + query:Update("data", util.TableToJSON(self.data)) + query:Where("item_id", self:GetID()) + query:Execute() + end +end + +--- Returns the value stored on a key within the item's data. +-- @realm shared +-- @string key The key in which the value is stored +-- @param[opt=nil] default The value to return in case there is no value stored in the key +-- @return The value stored within the key +function ITEM:GetData(key, default) + self.data = self.data or {} + + if (self.data) then + if (key == true) then + return self.data + end + + local value = self.data[key] + + if (value != nil) then + return value + elseif (IsValid(self.entity)) then + local data = self.entity:GetNetVar("data", {}) + value = data[key] + + if (value != nil) then + return value + end + end + else + self.data = {} + end + + if (default != nil) then + return default + end + + return +end + +--- Changes the function called on specific events for the item. +-- @realm shared +-- @string name The name of the hook +-- @func func The function to call once the event occurs +function ITEM:Hook(name, func) + if (name) then + self.hooks[name] = func + end +end + +--- Changes the function called after hooks for specific events for the item. +-- @realm shared +-- @string name The name of the hook +-- @func func The function to call after the original hook was called +function ITEM:PostHook(name, func) + if (name) then + self.postHooks[name] = func + end +end + +--- Removes the item. +-- @realm shared +-- @bool bNoReplication Whether or not the item's removal should not be replicated. +-- @bool bNoDelete Whether or not the item should not be fully deleted +-- @treturn bool Whether the item was successfully deleted or not +function ITEM:Remove(bNoReplication, bNoDelete) + local inv = ix.item.inventories[self.invID] + local bFailed = false + + if (self.invID > 0 and inv) then + for x = self.gridX, self.gridX + (self.width - 1) do + if (inv.slots[x]) then + for y = self.gridY, self.gridY + (self.height - 1) do + local item = inv.slots[x][y] + + if (item and item.id == self.id) then + inv.slots[x][y] = nil + else + bFailed = true + end + end + else + bFailed = true + end + end + + if (bFailed) then + local items = {} + for _, v in pairs(ix.item.instances) do + if (v.invID == self.invID and v.id != self.id) then + items[#items + 1] = v + end + end + + inv.slots = {} + for _, v in ipairs(items) do + for x = v.gridX, v.gridX + (v.width - 1) do + for y = v.gridY, v.gridY + (v.height - 1) do + inv.slots[x] = inv.slots[x] or {} + inv.slots[x][y] = v + end + end + end + end + else + -- @todo definition probably isn't needed + inv = ix.item.inventories[self.invID] + + if (inv) then + ix.item.inventories[self.invID][self.id] = nil + end + end + + if (SERVER and !bNoReplication) then + local entity = self:GetEntity() + + if (IsValid(entity)) then + entity:Remove() + end + + local receivers = inv.GetReceivers and inv:GetReceivers() + + if (self.invID != 0 and istable(receivers)) then + if (bFailed) then + inv:Sync(receivers) + else + net.Start("ixInventoryRemove") + net.WriteUInt(self.id, 32) + net.WriteUInt(self.invID, 32) + net.Send(receivers) + end + end + + if (!bNoDelete) then + local item = ix.item.instances[self.id] + + if (inv and inv.owner) then + hook.Run("InventoryItemRemoved", inv, item) + end + + if (item and item.OnRemoved) then + item:OnRemoved() + end + + local query = mysql:Delete("ix_items") + query:Where("item_id", self.id) + query:Execute() + + ix.item.instances[self.id] = nil + end + end + + return true +end + +if (SERVER) then + --- Returns the item's entity. + -- @realm server + -- @treturn entity The entity of the item + function ITEM:GetEntity() + local id = self:GetID() + + for _, v in ipairs(ents.FindByClass("ix_item")) do + if (v.ixItemID == id) then + return v + end + end + end + + --- Spawn an item entity based off the item table. + -- @realm server + -- @param[type=vector] position The position in which the item's entity will be spawned + -- @param[type=angle] angles The angles at which the item's entity will spawn + -- @treturn entity The spawned entity + function ITEM:Spawn(position, angles) + -- Check if the item has been created before. + if (ix.item.instances[self.id]) then + local client + + -- Spawn the actual item entity. + local entity = ents.Create("ix_item") + entity:Spawn() + entity:SetAngles(angles or Angle(0, 0, 0)) + entity:SetItem(self.id) + + -- If the first argument is a player, then we will find a position to drop + -- the item based off their aim. + if (type(position) == "Player") then + client = position + position = position:GetItemDropPos(entity) + end + + entity:SetPos(position) + + if (IsValid(client)) then + entity.ixSteamID = client:SteamID() + entity.ixCharID = client:GetCharacter():GetID() + entity:SetNetVar("owner", entity.ixCharID) + end + + hook.Run("OnItemSpawned", entity) + return entity + end + end + + --- Transfers an item to a specific inventory. + -- @realm server + -- @number invID The inventory to transfer the item to + -- @number x The X position to which the item should be transferred on the new inventory + -- @number y The Y position to which the item should be transferred on the new inventory + -- @player client The player to which the item is being transferred + -- @bool noReplication Whether there should be no replication of the transferral + -- @bool isLogical Whether or not an entity should spawn if the item is transferred to the world + -- @treturn[1] bool Whether the transfer was successful or not + -- @treturn[1] string The error, if applicable + function ITEM:Transfer(invID, x, y, client, noReplication, isLogical) + invID = invID or 0 + + if (self.invID == invID) then + return false, "same inv" + end + + local inventory = ix.item.inventories[invID] + local curInv = ix.item.inventories[self.invID or 0] + + if (curInv and !IsValid(client)) then + client = curInv.GetOwner and curInv:GetOwner() or nil + end + + -- check if this item doesn't belong to another one of this player's characters + local itemPlayerID = self:GetPlayerID() + local itemCharacterID = self:GetCharacterID() + + if (!self.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then + local playerID = client:SteamID64() + local characterID = client:GetCharacter():GetID() + + if (itemPlayerID and itemCharacterID) then + if (itemPlayerID == playerID and itemCharacterID != characterID) then + return false, "itemOwned" + end + else + self.characterID = characterID + self.playerID = playerID + + local query = mysql:Update("ix_items") + query:Update("character_id", characterID) + query:Update("player_id", playerID) + query:Where("item_id", self:GetID()) + query:Execute() + end + end + + if (hook.Run("CanTransferItem", self, curInv, inventory) == false) then + return false, "notAllowed" + end + + local authorized = false + + if (inventory and inventory.OnAuthorizeTransfer and inventory:OnAuthorizeTransfer(client, curInv, self)) then + authorized = true + end + + if (!authorized and self.CanTransfer and self:CanTransfer(curInv, inventory) == false) then + return false, "notAllowed" + end + + if (curInv) then + if (invID and invID > 0 and inventory) then + local targetInv = inventory + local bagInv + + if (!x and !y) then + x, y, bagInv = inventory:FindEmptySlot(self.width, self.height) + end + + if (bagInv) then + targetInv = bagInv + end + + if (!x or !y) then + return false, "noFit" + end + + local prevID = self.invID + local status, result = targetInv:Add(self.id, nil, nil, x, y, noReplication) + + if (status) then + if (self.invID > 0 and prevID != 0) then + -- we are transferring this item from one inventory to another + curInv:Remove(self.id, false, true, true) + + if (self.OnTransferred) then + self:OnTransferred(curInv, inventory) + end + + hook.Run("OnItemTransferred", self, curInv, inventory) + return true + elseif (self.invID > 0 and prevID == 0) then + -- we are transferring this item from the world to an inventory + ix.item.inventories[0][self.id] = nil + + if (self.OnTransferred) then + self:OnTransferred(curInv, inventory) + end + + hook.Run("OnItemTransferred", self, curInv, inventory) + return true + end + else + return false, result + end + elseif (IsValid(client)) then + -- we are transferring this item from an inventory to the world + self.invID = 0 + curInv:Remove(self.id, false, true) + + local query = mysql:Update("ix_items") + query:Update("inventory_id", 0) + query:Where("item_id", self.id) + query:Execute() + + inventory = ix.item.inventories[0] + inventory[self:GetID()] = self + + if (self.OnTransferred) then + self:OnTransferred(curInv, inventory) + end + + hook.Run("OnItemTransferred", self, curInv, inventory) + + if (!isLogical) then + return self:Spawn(client) + end + + return true + else + return false, "noOwner" + end + else + return false, "invalidInventory" + end + end +end + +ix.meta.item = ITEM diff --git a/garrysmod/gamemodes/helix/gamemode/core/meta/sh_player.lua b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_player.lua new file mode 100644 index 0000000..d8a7733 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_player.lua @@ -0,0 +1,616 @@ + +--[[-- +Physical representation of connected player. + +`Player`s are a type of `Entity`. They are a physical representation of a `Character` - and can possess at most one `Character` +object at a time that you can interface with. + +See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Player) for all other methods that the `Player` class has. +]] +-- @classmod Player + +local meta = FindMetaTable("Player") + +if (SERVER) then + --- Returns the amount of time the player has played on the server. + -- @realm shared + -- @treturn number Number of seconds the player has played on the server + function meta:GetPlayTime() + return self.ixPlayTime + (RealTime() - (self.ixJoinTime or RealTime())) + end +else + ix.playTime = ix.playTime or 0 + + function meta:GetPlayTime() + return ix.playTime + (RealTime() - ix.joinTime or 0) + end +end + +--- Returns `true` if the player has their weapon raised. +-- @realm shared +-- @treturn bool Whether or not the player has their weapon raised +function meta:IsWepRaised() + return self:GetNetVar("raised", false) +end + +--- Returns `true` if the player is restricted - that is to say that they are considered "bound" and cannot interact with +-- objects normally (e.g hold weapons, use items, etc). An example of this would be a player in handcuffs. +-- @realm shared +-- @treturn bool Whether or not the player is restricted +function meta:IsRestricted() + return self:GetNetVar("restricted", false) +end + +--- Returns `true` if the player is able to shoot their weapon. +-- @realm shared +-- @treturn bool Whether or not the player can shoot their weapon +function meta:CanShootWeapon() + return self:GetNetVar("canShoot", true) +end + +local vectorLength2D = FindMetaTable("Vector").Length2D + +--- Returns `true` if the player is running. Running in this case means that their current speed is greater than their +-- regularly set walk speed. +-- @realm shared +-- @treturn bool Whether or not the player is running +function meta:IsRunning() + return vectorLength2D(self:GetVelocity()) > (self:GetWalkSpeed() + 10) +end + +--- Returns `true` if the player currently has a female model. This checks if the model has `female`, `alyx` or `mossman` in its +-- name, or if the player's model class is `citizen_female`. +-- @realm shared +-- @treturn bool Whether or not the player has a female model +function meta:IsFemale() + local model = self:GetModel():lower() + + return (model:find("female") or model:find("alyx") or model:find("mossman")) != nil or + ix.anim.GetModelClass(model) == "citizen_female" +end + +--- Whether or not this player is stuck and cannot move. +-- @realm shared +-- @treturn bool Whether or not this player is stuck +function meta:IsStuck() + return util.TraceEntity({ + start = self:GetPos(), + endpos = self:GetPos(), + filter = self + }, self).StartSolid +end + +--- Returns a good position in front of the player for an entity to be placed. This is usually used for item entities. +-- @realm shared +-- @entity entity Entity to get a position for +-- @treturn vector Best guess for a good drop position in front of the player +-- @usage local position = client:GetItemDropPos(entity) +-- entity:SetPos(position) +function meta:GetItemDropPos(entity) + local data = {} + local trace + + data.start = self:GetShootPos() + data.endpos = self:GetShootPos() + self:GetAimVector() * 86 + data.filter = self + + if (IsValid(entity)) then + -- use a hull trace if there's a valid entity to avoid collisions + local mins, maxs = entity:GetRotatedAABB(entity:OBBMins(), entity:OBBMaxs()) + + data.mins = mins + data.maxs = maxs + data.filter = {entity, self} + trace = util.TraceHull(data) + else + -- trace along the normal for a few units so we can attempt to avoid a collision + trace = util.TraceLine(data) + + data.start = trace.HitPos + data.endpos = data.start + trace.HitNormal * 48 + trace = util.TraceLine(data) + end + + return trace.HitPos +end + +--- Performs a time-delay action that requires this player to look at an entity. If this player looks away from the entity +-- before the action timer completes, the action is cancelled. This is usually used in conjunction with `SetAction` to display +-- progress to the player. +-- @realm shared +-- @entity entity that this player must look at +-- @func callback Function to call when the timer completes +-- @number time How much time in seconds this player must look at the entity for +-- @func[opt=nil] onCancel Function to call when the timer has been cancelled +-- @number[opt=96] distance Maximum distance a player can move away from the entity before the action is cancelled +-- @see SetAction +-- @usage client:SetAction("Searching...", 4) -- for displaying the progress bar +-- client:DoStaredAction(entity, function() +-- print("hello!") +-- end) +-- -- prints "hello!" after looking at the entity for 4 seconds +function meta:DoStaredAction(entity, callback, time, onCancel, distance) + local uniqueID = "ixStare"..self:UniqueID() + local data = {} + data.filter = self + + timer.Create(uniqueID, 0.1, time / 0.1, function() + if (IsValid(self) and IsValid(entity)) then + data.start = self:GetShootPos() + data.endpos = data.start + self:GetAimVector()*(distance or 96) + + if (util.TraceLine(data).Entity != entity) then + timer.Remove(uniqueID) + + if (onCancel) then + onCancel() + end + elseif (callback and timer.RepsLeft(uniqueID) == 0) then + callback() + end + else + timer.Remove(uniqueID) + + if (onCancel) then + onCancel() + end + end + end) +end + +--- Resets all bodygroups this player's model has to their defaults (`0`). +-- @realm shared +function meta:ResetBodygroups() + for i = 0, (self:GetNumBodyGroups() - 1) do + self:SetBodygroup(i, 0) + end +end + +if (SERVER) then + util.AddNetworkString("ixActionBar") + util.AddNetworkString("ixActionBarReset") + util.AddNetworkString("ixStringRequest") + + --- Sets whether or not this player's current weapon is raised. + -- @realm server + -- @bool bState Whether or not the raise the weapon + -- @entity[opt=GetActiveWeapon()] weapon Weapon to raise or lower. You should pass this argument if you already have a + -- reference to this player's current weapon to avoid an expensive lookup for this player's current weapon. + function meta:SetWepRaised(bState, weapon) + weapon = weapon or self:GetActiveWeapon() + + if (IsValid(weapon)) then + local bCanShoot = !bState and weapon.FireWhenLowered or bState + self:SetNetVar("raised", bState) + + if (bCanShoot) then + -- delay shooting while the raise animation is playing + timer.Create("ixWeaponRaise" .. self:SteamID64(), 1, 1, function() + if (IsValid(self)) then + self:SetNetVar("canShoot", true) + end + end) + else + timer.Remove("ixWeaponRaise" .. self:SteamID64()) + self:SetNetVar("canShoot", false) + end + else + timer.Remove("ixWeaponRaise" .. self:SteamID64()) + self:SetNetVar("raised", false) + self:SetNetVar("canShoot", false) + end + end + + --- Inverts this player's weapon raised state. You should use `SetWepRaised` instead of this if you already have a reference + -- to this player's current weapon. + -- @realm server + function meta:ToggleWepRaised() + local weapon = self:GetActiveWeapon() + + if (!IsValid(weapon) or + weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()] or + weapon.IsAlwaysLowered or weapon.NeverRaised) then + return + end + + self:SetWepRaised(!self:IsWepRaised(), weapon) + + if (self:IsWepRaised() and weapon.OnRaised) then + weapon:OnRaised() + elseif (!self:IsWepRaised() and weapon.OnLowered) then + weapon:OnLowered() + end + end + + --- Performs a delayed action that requires this player to hold use on an entity. This is displayed to this player as a + -- closing ring over their crosshair. + -- @realm server + -- @number time How much time in seconds this player has to hold use for + -- @entity entity Entity that this player must be looking at + -- @func callback Function to run when the timer completes. It will be ran right away if `time` is `0`. Returning `false` in + -- the callback will not mark this interaction as dirty if you're managing the interaction state manually. + function meta:PerformInteraction(time, entity, callback) + if (!IsValid(entity) or entity.ixInteractionDirty) then + return + end + + if (time > 0) then + self.ixInteractionTarget = entity + self.ixInteractionCharacter = self:GetCharacter():GetID() + + timer.Create("ixCharacterInteraction" .. self:SteamID(), time, 1, function() + if (IsValid(self) and IsValid(entity) and IsValid(self.ixInteractionTarget) and + self.ixInteractionCharacter == self:GetCharacter():GetID()) then + local useEntity = self:GetUseEntity() + + if (IsValid(useEntity) and useEntity == self.ixInteractionTarget and !useEntity.ixInteractionDirty) then + if (callback(self) != false) then + useEntity.ixInteractionDirty = true + end + end + end + end) + else + if (callback(self) != false) then + entity.ixInteractionDirty = true + end + end + end + + --- Displays a progress bar for this player that takes the given amount of time to complete. + -- @realm server + -- @string text Text to display above the progress bar + -- @number[opt=5] time How much time in seconds to wait before the timer completes + -- @func callback Function to run once the timer completes + -- @number[opt=CurTime()] startTime Game time in seconds that the timer started. If you are using `time`, then you shouldn't + -- use this argument + -- @number[opt=startTime + time] finishTime Game time in seconds that the timer should complete at. If you are using `time`, + -- then you shouldn't use this argument + function meta:SetAction(text, time, callback, startTime, finishTime) + if (time and time <= 0) then + if (callback) then + callback(self) + end + + return + end + + -- Default the time to five seconds. + time = time or 5 + startTime = startTime or CurTime() + finishTime = finishTime or (startTime + time) + + if (text == false) then + timer.Remove("ixAct"..self:UniqueID()) + + net.Start("ixActionBarReset") + net.Send(self) + + return + end + + if (!text) then + net.Start("ixActionBarReset") + net.Send(self) + else + net.Start("ixActionBar") + net.WriteFloat(startTime) + net.WriteFloat(finishTime) + net.WriteString(text) + net.Send(self) + end + + -- If we have provided a callback, run it delayed. + if (callback) then + -- Create a timer that runs once with a delay. + timer.Create("ixAct"..self:UniqueID(), time, 1, function() + -- Call the callback if the player is still valid. + if (IsValid(self)) then + callback(self) + end + end) + end + end + + --- Opens up a text box on this player's screen for input and returns the result. Remember to sanitize the user's input if + -- it's needed! + -- @realm server + -- @string title Title to display on the panel + -- @string subTitle Subtitle to display on the panel + -- @func callback Function to run when this player enters their input. Callback is ran with the user's input string. + -- @string[opt=nil] default Default value to put in the text box. + -- @usage client:RequestString("Hello", "Please enter your name", function(text) + -- client:ChatPrint("Hello, " .. text) + -- end) + -- -- prints "Hello, " in the player's chat + function meta:RequestString(title, subTitle, callback, default) + local time = math.floor(os.time()) + + self.ixStrReqs = self.ixStrReqs or {} + self.ixStrReqs[time] = callback + + net.Start("ixStringRequest") + net.WriteUInt(time, 32) + net.WriteString(title) + net.WriteString(subTitle) + net.WriteString(default or "") + net.Send(self) + end + + --- Sets this player's restricted status. + -- @realm server + -- @bool bState Whether or not to restrict this player + -- @bool bNoMessage Whether or not to suppress the restriction notification + function meta:SetRestricted(bState, bNoMessage) + if (bState) then + self:SetNetVar("restricted", true) + + if (bNoMessage) then + self:SetLocalVar("restrictNoMsg", true) + end + + self.ixRestrictWeps = self.ixRestrictWeps or {} + + for _, v in ipairs(self:GetWeapons()) do + self.ixRestrictWeps[#self.ixRestrictWeps + 1] = { + class = v:GetClass(), + item = v.ixItem, + clip = v:Clip1() + } + + v:Remove() + end + + hook.Run("OnPlayerRestricted", self) + else + self:SetNetVar("restricted") + + if (self:GetLocalVar("restrictNoMsg")) then + self:SetLocalVar("restrictNoMsg") + end + + if (self.ixRestrictWeps) then + for _, v in ipairs(self.ixRestrictWeps) do + local weapon = self:Give(v.class, true) + + if (v.item) then + weapon.ixItem = v.item + end + + weapon:SetClip1(v.clip) + end + + self.ixRestrictWeps = nil + end + + hook.Run("OnPlayerUnRestricted", self) + end + end + + --- Creates a ragdoll entity of this player that will be synced with clients. This does **not** affect the player like + -- `SetRagdolled` does. + -- @realm server + -- @bool[opt=false] bDontSetPlayer Whether or not to avoid setting the ragdoll's owning player + -- @treturn entity Created ragdoll entity + function meta:CreateServerRagdoll(bDontSetPlayer) + local entity = ents.Create("prop_ragdoll") + entity:SetPos(self:GetPos()) + entity:SetAngles(self:EyeAngles()) + entity:SetModel(self:GetModel()) + entity:SetSkin(self:GetSkin()) + + for i = 0, (self:GetNumBodyGroups() - 1) do + entity:SetBodygroup(i, self:GetBodygroup(i)) + end + + entity:Spawn() + + if (!bDontSetPlayer) then + entity:SetNetVar("player", self) + end + + entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) + entity:Activate() + + local velocity = self:GetVelocity() + + for i = 0, entity:GetPhysicsObjectCount() - 1 do + local physObj = entity:GetPhysicsObjectNum(i) + + if (IsValid(physObj)) then + physObj:SetVelocity(velocity) + + local index = entity:TranslatePhysBoneToBone(i) + + if (index) then + local position, angles = self:GetBonePosition(index) + + physObj:SetPos(position) + physObj:SetAngles(angles) + end + end + end + + return entity + end + + --- Sets this player's ragdoll status. + -- @realm server + -- @bool bState Whether or not to ragdoll this player + -- @number[opt=0] time How long this player should stay ragdolled for. Set to `0` if they should stay ragdolled until they + -- get back up manually + -- @number[opt=5] getUpGrace How much time in seconds to wait before the player is able to get back up manually. Set to + -- the same number as `time` to disable getting up manually entirely + function meta:SetRagdolled(bState, time, getUpGrace) + if (!self:Alive()) then + return + end + + getUpGrace = getUpGrace or time or 5 + + if (bState) then + if (IsValid(self.ixRagdoll)) then + self.ixRagdoll:Remove() + end + + local entity = self:CreateServerRagdoll() + + entity:CallOnRemove("fixer", function() + if (IsValid(self)) then + self:SetLocalVar("blur", nil) + self:SetLocalVar("ragdoll", nil) + + if (!entity.ixNoReset) then + self:SetPos(entity:GetPos()) + end + + self:SetNoDraw(false) + self:SetNotSolid(false) + self:SetMoveType(MOVETYPE_WALK) + self:SetLocalVelocity(IsValid(entity) and entity.ixLastVelocity or vector_origin) + end + + if (IsValid(self) and !entity.ixIgnoreDelete) then + if (entity.ixWeapons) then + for _, v in ipairs(entity.ixWeapons) do + if (v.class) then + local weapon = self:Give(v.class, true) + + if (v.item) then + weapon.ixItem = v.item + end + + self:SetAmmo(v.ammo, weapon:GetPrimaryAmmoType()) + weapon:SetClip1(v.clip) + elseif (v.item and v.invID == v.item.invID) then + v.item:Equip(self, true, true) + self:SetAmmo(v.ammo, self.carryWeapons[v.item.weaponCategory]:GetPrimaryAmmoType()) + end + end + end + + if (entity.ixActiveWeapon) then + if (self:HasWeapon(entity.ixActiveWeapon)) then + self:SetActiveWeapon(self:GetWeapon(entity.ixActiveWeapon)) + else + local weapons = self:GetWeapons() + if (#weapons > 0) then + self:SetActiveWeapon(weapons[1]) + end + end + end + + if (self:IsStuck()) then + entity:DropToFloor() + self:SetPos(entity:GetPos() + Vector(0, 0, 16)) + + local positions = ix.util.FindEmptySpace(self, {entity, self}) + + for _, v in ipairs(positions) do + self:SetPos(v) + + if (!self:IsStuck()) then + return + end + end + end + end + end) + + self:SetLocalVar("blur", 25) + self.ixRagdoll = entity + + entity.ixWeapons = {} + entity.ixPlayer = self + + if (getUpGrace) then + entity.ixGrace = CurTime() + getUpGrace + end + + if (time and time > 0) then + entity.ixStart = CurTime() + entity.ixFinish = entity.ixStart + time + + self:SetAction("@wakingUp", nil, nil, entity.ixStart, entity.ixFinish) + end + + if (IsValid(self:GetActiveWeapon())) then + entity.ixActiveWeapon = self:GetActiveWeapon():GetClass() + end + + for _, v in ipairs(self:GetWeapons()) do + if (v.ixItem and v.ixItem.Equip and v.ixItem.Unequip) then + entity.ixWeapons[#entity.ixWeapons + 1] = { + item = v.ixItem, + invID = v.ixItem.invID, + ammo = self:GetAmmoCount(v:GetPrimaryAmmoType()) + } + v.ixItem:Unequip(self, false) + else + local clip = v:Clip1() + local reserve = self:GetAmmoCount(v:GetPrimaryAmmoType()) + entity.ixWeapons[#entity.ixWeapons + 1] = { + class = v:GetClass(), + item = v.ixItem, + clip = clip, + ammo = reserve + } + end + end + + self:GodDisable() + self:StripWeapons() + self:SetMoveType(MOVETYPE_OBSERVER) + self:SetNoDraw(true) + self:SetNotSolid(true) + + local uniqueID = "ixUnRagdoll" .. self:SteamID() + + if (time) then + timer.Create(uniqueID, 0.33, 0, function() + if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then + local velocity = entity:GetVelocity() + entity.ixLastVelocity = velocity + + self:SetPos(entity:GetPos()) + + if (velocity:Length2D() >= 8) then + if (!entity.ixPausing) then + self:SetAction() + entity.ixPausing = true + end + + return + elseif (entity.ixPausing) then + self:SetAction("@wakingUp", time) + entity.ixPausing = false + end + + time = time - 0.33 + + if (time <= 0) then + entity:Remove() + end + else + timer.Remove(uniqueID) + end + end) + else + timer.Create(uniqueID, 0.33, 0, function() + if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then + self:SetPos(entity:GetPos()) + else + timer.Remove(uniqueID) + end + end) + end + + self:SetLocalVar("ragdoll", entity:EntIndex()) + hook.Run("OnCharacterFallover", self, entity, true) + elseif (IsValid(self.ixRagdoll)) then + self.ixRagdoll:Remove() + + hook.Run("OnCharacterFallover", self, nil, false) + end + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/meta/sh_tool.lua b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_tool.lua new file mode 100644 index 0000000..1d2d958 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/meta/sh_tool.lua @@ -0,0 +1,130 @@ + +local TOOL = ix.meta.tool or {} + +-- code replicated from gamemodes/sandbox/entities/weapons/gmod_tool/stool.lua +function TOOL:Create() + local object = {} + + setmetatable(object, self) + self.__index = self + + object.Mode = nil + object.SWEP = nil + object.Owner = nil + object.ClientConVar = {} + object.ServerConVar = {} + object.Objects = {} + object.Stage = 0 + object.Message = "start" + object.LastMessage = 0 + object.AllowedCVar = 0 + + return object +end + +function TOOL:CreateConVars() + local mode = self:GetMode() + + if (CLIENT) then + for cvar, default in pairs(self.ClientConVar) do + CreateClientConVar(mode .. "_" .. cvar, default, true, true) + end + + return + end + + -- Note: I changed this from replicated because replicated convars don't work when they're created via Lua. + if (SERVER) then + self.AllowedCVar = CreateConVar("toolmode_allow_" .. mode, 1, FCVAR_NOTIFY) + end +end + +function TOOL:GetServerInfo(property) + local mode = self:GetMode() + return GetConVarString(mode .. "_" .. property) +end + +function TOOL:BuildConVarList() + local mode = self:GetMode() + local convars = {} + + for k, v in pairs(self.ClientConVar) do + convars[mode .. "_" .. k] = v + end + + return convars +end + +function TOOL:GetClientInfo(property) + return self:GetOwner():GetInfo(self:GetMode() .. "_" .. property) +end + +function TOOL:GetClientNumber(property, default) + return self:GetOwner():GetInfoNum(self:GetMode() .. "_" .. property, tonumber(default) or 0) +end + +function TOOL:Allowed() + if (CLIENT) then + return true + end + + return self.AllowedCVar:GetBool() +end + +-- Now for all the TOOL redirects +function TOOL:Init() +end + +function TOOL:GetMode() + return self.Mode +end + +function TOOL:GetSWEP() + return self.SWEP +end + +function TOOL:GetOwner() + return self:GetSWEP().Owner or self.Owner +end + +function TOOL:GetWeapon() + return self:GetSWEP().Weapon or self.Weapon +end + +function TOOL:LeftClick() + return false +end + +function TOOL:RightClick() + return false +end + +function TOOL:Reload() + self:ClearObjects() +end + +function TOOL:Deploy() + self:ReleaseGhostEntity() + return +end + +function TOOL:Holster() + self:ReleaseGhostEntity() + return +end + +function TOOL:Think() + self:ReleaseGhostEntity() +end + +-- Checks the objects before any action is taken +-- This is to make sure that the entities haven't been removed +function TOOL:CheckObjects() + for _, v in pairs(self.Objects) do + if (!v.Ent:IsWorld() and !v.Ent:IsValid()) then + self:ClearObjects() + end + end +end + +ix.meta.tool = TOOL diff --git a/garrysmod/gamemodes/helix/gamemode/core/sh_commands.lua b/garrysmod/gamemodes/helix/gamemode/core/sh_commands.lua new file mode 100644 index 0000000..1bf6cec --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/sh_commands.lua @@ -0,0 +1,781 @@ +ix.command.Add("Roll", { + description = "@cmdRoll", + arguments = bit.bor(ix.type.number, ix.type.optional), + OnRun = function(self, client, maximum) + maximum = math.Clamp(maximum or 100, 0, 1000000) + + local value = math.random(0, maximum) + + ix.chat.Send(client, "roll", tostring(value), nil, nil, { + max = maximum + }) + + ix.log.Add(client, "roll", value, maximum) + end +}) + +ix.command.Add("Event", { + description = "@cmdEvent", + arguments = ix.type.text, + superAdminOnly = true, + OnRun = function(self, client, text) + ix.chat.Send(client, "event", text) + end +}) + +ix.command.Add("PM", { + description = "@cmdPM", + arguments = { + ix.type.player, + ix.type.text + }, + OnRun = function(self, client, target, message) + local voiceMail = target:GetData("vm") + + if (voiceMail and voiceMail:find("%S")) then + return target:GetName()..": "..voiceMail + end + + if ((client.ixNextPM or 0) < CurTime()) then + ix.chat.Send(client, "pm", message, false, {client, target}, {target = target}) + + client.ixNextPM = CurTime() + 0.5 + target.ixLastPM = client + end + end +}) + +ix.command.Add("Reply", { + description = "@cmdReply", + arguments = ix.type.text, + OnRun = function(self, client, message) + local target = client.ixLastPM + + if (IsValid(target) and (client.ixNextPM or 0) < CurTime()) then + ix.chat.Send(client, "pm", message, false, {client, target}, {target = target}) + client.ixNextPM = CurTime() + 0.5 + end + end +}) + +ix.command.Add("SetVoicemail", { + description = "@cmdSetVoicemail", + arguments = bit.bor(ix.type.text, ix.type.optional), + OnRun = function(self, client, message) + if (isstring(message) and message:find("%S")) then + client:SetData("vm", message:utf8sub(1, 240)) + return "@vmSet" + else + client:SetData("vm") + return "@vmRem" + end + end +}) + +ix.command.Add("CharGiveFlag", { + description = "@cmdCharGiveFlag", + privilege = "Manage Character Flags", + superAdminOnly = true, + arguments = { + ix.type.character, + bit.bor(ix.type.string, ix.type.optional) + }, + OnRun = function(self, client, target, flags) + -- show string request if no flags are specified + if (!flags) then + local available = "" + + -- sort and display flags the character already has + for k, _ in SortedPairs(ix.flag.list) do + if (!target:HasFlags(k)) then + available = available .. k + end + end + + return client:RequestString("@flagGiveTitle", "@cmdCharGiveFlag", function(text) + ix.command.Run(client, "CharGiveFlag", {target:GetName(), text}) + end, available) + end + + target:GiveFlags(flags) + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("flagGive", client:GetName(), target:GetName(), flags) + end + end + end +}) + +ix.command.Add("CharTakeFlag", { + description = "@cmdCharTakeFlag", + privilege = "Manage Character Flags", + superAdminOnly = true, + arguments = { + ix.type.character, + bit.bor(ix.type.string, ix.type.optional) + }, + OnRun = function(self, client, target, flags) + if (!flags) then + return client:RequestString("@flagTakeTitle", "@cmdCharTakeFlag", function(text) + ix.command.Run(client, "CharTakeFlag", {target:GetName(), text}) + end, target:GetFlags()) + end + + target:TakeFlags(flags) + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("flagTake", client:GetName(), flags, target:GetName()) + end + end + end +}) + +ix.command.Add("ToggleRaise", { + description = "@cmdToggleRaise", + OnRun = function(self, client, arguments) + if (!timer.Exists("ixToggleRaise" .. client:SteamID())) then + timer.Create("ixToggleRaise" .. client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function() + client:ToggleWepRaised() + end) + end + end +}) + +ix.command.Add("CharSetModel", { + description = "@cmdCharSetModel", + superAdminOnly = true, + arguments = { + ix.type.character, + ix.type.string + }, + OnRun = function(self, client, target, model) + target:SetModel(model) + target:GetPlayer():SetupHands() + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("cChangeModel", client:GetName(), target:GetName(), model) + end + end + end +}) + +ix.command.Add("CharSetSkin", { + description = "@cmdCharSetSkin", + adminOnly = true, + arguments = { + ix.type.character, + bit.bor(ix.type.number, ix.type.optional) + }, + OnRun = function(self, client, target, skin) + target:SetData("skin", skin) + target:GetPlayer():SetSkin(skin or 0) + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("cChangeSkin", client:GetName(), target:GetName(), skin or 0) + end + end + end +}) + +ix.command.Add("CharSetBodygroup", { + description = "@cmdCharSetBodygroup", + adminOnly = true, + arguments = { + ix.type.character, + ix.type.string, + bit.bor(ix.type.number, ix.type.optional) + }, + OnRun = function(self, client, target, bodygroup, value) + local index = target:GetPlayer():FindBodygroupByName(bodygroup) + + if (index > -1) then + if (value and value < 1) then + value = nil + end + + local groups = target:GetData("groups", {}) + groups[index] = value + target:SetData("groups", groups) + target:GetPlayer():SetBodygroup(index, value or 0) + + ix.util.NotifyLocalized("cChangeGroups", nil, client:GetName(), target:GetName(), bodygroup, value or 0) + else + return "@invalidArg", 2 + end + end +}) + +ix.command.Add("CharSetAttribute", { + description = "@cmdCharSetAttribute", + privilege = "Manage Character Attributes", + adminOnly = true, + arguments = { + ix.type.character, + ix.type.string, + ix.type.number + }, + OnRun = function(self, client, target, attributeName, level) + for k, v in pairs(ix.attributes.list) do + if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then + target:SetAttrib(k, math.abs(level)) + return "@attributeSet", target:GetName(), L(v.name, client), math.abs(level) + end + end + + return "@attributeNotFound" + end +}) + +ix.command.Add("CharAddAttribute", { + description = "@cmdCharAddAttribute", + privilege = "Manage Character Attributes", + adminOnly = true, + arguments = { + ix.type.character, + ix.type.string, + ix.type.number + }, + OnRun = function(self, client, target, attributeName, level) + for k, v in pairs(ix.attributes.list) do + if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then + target:UpdateAttrib(k, math.abs(level)) + return "@attributeUpdate", target:GetName(), L(v.name, client), math.abs(level) + end + end + + return "@attributeNotFound" + end +}) + +ix.command.Add("CharSetName", { + description = "@cmdCharSetName", + adminOnly = true, + arguments = { + ix.type.character, + bit.bor(ix.type.text, ix.type.optional) + }, + OnRun = function(self, client, target, newName) + -- display string request panel if no name was specified + if (newName:len() == 0) then + return client:RequestString("@chgName", "@chgNameDesc", function(text) + ix.command.Run(client, "CharSetName", {target:GetName(), text}) + end, target:GetName()) + end + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("cChangeName", client:GetName(), target:GetName(), newName) + end + end + + target:SetName(newName:gsub("#", "#​")) + end +}) + +ix.command.Add("CharGiveItem", { + description = "@cmdCharGiveItem", + superAdminOnly = true, + arguments = { + ix.type.character, + ix.type.string, + bit.bor(ix.type.number, ix.type.optional) + }, + OnRun = function(self, client, target, item, amount) + local uniqueID = item:lower() + + if (!ix.item.list[uniqueID]) then + for k, v in SortedPairs(ix.item.list) do + if (ix.util.StringMatches(v.name, uniqueID)) then + uniqueID = k + + break + end + end + end + + amount = amount or 1 + local bSuccess, error = target:GetInventory():Add(uniqueID, amount) + + if (bSuccess) then + target:GetPlayer():NotifyLocalized("itemCreated") + + if (target != client:GetCharacter()) then + return "@itemCreated" + end + else + return "@" .. tostring(error) + end + end +}) + +ix.command.Add("CharKick", { + description = "@cmdCharKick", + adminOnly = true, + arguments = ix.type.character, + OnRun = function(self, client, target) + target:Save(function() + target:Kick() + end) + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("charKick", client:GetName(), target:GetName()) + end + end + end +}) + +ix.command.Add("CharBan", { + description = "@cmdCharBan", + privilege = "Ban Character", + arguments = { + ix.type.character, + bit.bor(ix.type.number, ix.type.optional) + }, + adminOnly = true, + OnRun = function(self, client, target, minutes) + if (minutes) then + minutes = minutes * 60 + end + + target:Ban(minutes) + target:Save() + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("charBan", client:GetName(), target:GetName()) + end + end + end +}) + +ix.command.Add("CharUnban", { + description = "@cmdCharUnban", + privilege = "Ban Character", + arguments = ix.type.text, + adminOnly = true, + OnRun = function(self, client, name) + if ((client.ixNextSearch or 0) >= CurTime()) then + return L("charSearching", client) + end + + for _, v in pairs(ix.char.loaded) do + if (ix.util.StringMatches(v:GetName(), name)) then + if (v:GetData("banned")) then + v:SetData("banned") + else + return "@charNotBanned" + end + + for _, v2 in player.Iterator() do + if (self:OnCheckAccess(v2) or v2 == v:GetPlayer()) then + v2:NotifyLocalized("charUnBan", client:GetName(), v:GetName()) + end + end + + return + end + end + + client.ixNextSearch = CurTime() + 15 + + local query = mysql:Select("ix_characters") + query:Select("id") + query:Select("name") + query:Select("data") + query:WhereLike("name", name) + query:Limit(1) + query:Callback(function(result) + if (istable(result) and #result > 0) then + local characterID = tonumber(result[1].id) + local data = util.JSONToTable(result[1].data or "[]") + name = result[1].name + + client.ixNextSearch = 0 + + if (!data.banned) then + return client:NotifyLocalized("charNotBanned") + end + + data.banned = nil + + local updateQuery = mysql:Update("ix_characters") + updateQuery:Update("data", util.TableToJSON(data)) + updateQuery:Where("id", characterID) + updateQuery:Execute() + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v)) then + v:NotifyLocalized("charUnBan", client:GetName(), name) + end + end + end + end) + query:Execute() + end +}) + +do + hook.Add("InitializedConfig", "ixMoneyCommands", function() + local MONEY_NAME = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "") + + ix.command.Add("Give" .. MONEY_NAME, { + alias = {"GiveMoney"}, + description = "@cmdGiveMoney", + arguments = ix.type.number, + OnRun = function(self, client, amount) + amount = math.floor(amount) + + if (amount <= 0) then + return L("invalidArg", client, 1) + end + + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local target = util.TraceLine(data).Entity + + if (IsValid(target) and target:IsPlayer() and target:GetCharacter()) then + if (!client:GetCharacter():HasMoney(amount)) then + return + end + + target:GetCharacter():GiveMoney(amount) + client:GetCharacter():TakeMoney(amount) + + target:NotifyLocalized("moneyTaken", ix.currency.Get(amount)) + client:NotifyLocalized("moneyGiven", ix.currency.Get(amount)) + end + end + }) + + ix.command.Add("CharSet" .. MONEY_NAME, { + alias = {"CharSetMoney"}, + description = "@cmdCharSetMoney", + superAdminOnly = true, + arguments = { + ix.type.character, + ix.type.number + }, + OnRun = function(self, client, target, amount) + amount = math.Round(amount) + + if (amount <= 0) then + return "@invalidArg", 2 + end + + target:SetMoney(amount) + client:NotifyLocalized("setMoney", target:GetName(), ix.currency.Get(amount)) + end + }) + end) +end + +ix.command.Add("PlyWhitelist", { + description = "@cmdPlyWhitelist", + privilege = "Manage Character Whitelist", + superAdminOnly = true, + arguments = { + ix.type.player, + ix.type.text + }, + OnRun = function(self, client, target, name) + if (name == "") then + return "@invalidArg", 2 + end + + local faction = ix.faction.teams[name] + + if (!faction) then + for _, v in ipairs(ix.faction.indices) do + if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then + faction = v + + break + end + end + end + + if (faction) then + if (target:SetWhitelisted(faction.index, true)) then + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target) then + v:NotifyLocalized("whitelist", client:GetName(), target:GetName(), L(faction.name, v)) + end + end + end + else + return "@invalidFaction" + end + end +}) + +ix.command.Add("CharGetUp", { + description = "@cmdCharGetUp", + OnRun = function(self, client, arguments) + local entity = client.ixRagdoll + + if (IsValid(entity) and entity.ixGrace and entity.ixGrace < CurTime() and + entity:GetVelocity():Length2D() < 8 and !entity.ixWakingUp) then + entity.ixWakingUp = true + entity:CallOnRemove("CharGetUp", function() + client:SetAction() + end) + + client:SetAction("@gettingUp", 5, function() + if (!IsValid(entity)) then + return + end + + hook.Run("OnCharacterGetup", client, entity) + entity:Remove() + end) + end + end +}) + +ix.command.Add("PlyUnwhitelist", { + description = "@cmdPlyUnwhitelist", + privilege = "Manage Character Whitelist", + superAdminOnly = true, + arguments = { + ix.type.string, + ix.type.text + }, + OnRun = function(self, client, target, name) + local faction = ix.faction.teams[name] + + if (!faction) then + for _, v in ipairs(ix.faction.indices) do + if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then + faction = v + + break + end + end + end + + if (faction) then + local targetPlayer = ix.util.FindPlayer(target) + + if (IsValid(targetPlayer) and targetPlayer:SetWhitelisted(faction.index, false)) then + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == targetPlayer) then + v:NotifyLocalized("unwhitelist", client:GetName(), targetPlayer:GetName(), L(faction.name, v)) + end + end + else + local steamID64 = util.SteamIDTo64(target) + local query = mysql:Select("ix_players") + query:Select("data") + query:Where("steamid", steamID64) + query:Limit(1) + query:Callback(function(result) + if (istable(result) and #result > 0) then + local data = util.JSONToTable(result[1].data or "[]") + local whitelists = data.whitelists and data.whitelists[Schema.folder] + + if (!whitelists or !whitelists[faction.uniqueID]) then + return + end + + whitelists[faction.uniqueID] = nil + + local updateQuery = mysql:Update("ix_players") + updateQuery:Update("data", util.TableToJSON(data)) + updateQuery:Where("steamid", steamID64) + updateQuery:Execute() + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v)) then + v:NotifyLocalized("unwhitelist", client:GetName(), target, L(faction.name, v)) + end + end + end + end) + query:Execute() + end + else + return "@invalidFaction" + end + end +}) + +ix.command.Add("CharFallOver", { + description = "@cmdCharFallOver", + arguments = bit.bor(ix.type.number, ix.type.optional), + OnRun = function(self, client, time) + if (!client:Alive() or client:GetMoveType() == MOVETYPE_NOCLIP) then + return "@notNow" + end + + if (time and time > 0) then + time = math.Clamp(time, 1, 60) + end + + if (!IsValid(client.ixRagdoll)) then + client:SetRagdolled(true, time) + end + end +}) + +ix.command.Add("BecomeClass", { + description = "@cmdBecomeClass", + arguments = ix.type.text, + OnRun = function(self, client, class) + local character = client:GetCharacter() + + if (character) then + local num = isnumber(tonumber(class)) and tonumber(class) or -1 + + if (ix.class.list[num]) then + local v = ix.class.list[num] + + if (character:JoinClass(num)) then + return "@becomeClass", L(v.name, client) + else + return "@becomeClassFail", L(v.name, client) + end + else + for k, v in ipairs(ix.class.list) do + if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(L(v.name, client), class)) then + if (character:JoinClass(k)) then + return "@becomeClass", L(v.name, client) + else + return "@becomeClassFail", L(v.name, client) + end + end + end + end + + return "@invalid", L("class", client) + else + return "@illegalAccess" + end + end +}) + +ix.command.Add("CharDesc", { + description = "@cmdCharDesc", + arguments = bit.bor(ix.type.text, ix.type.optional), + OnRun = function(self, client, description) + if (!description:find("%S")) then + return client:RequestString("@cmdCharDescTitle", "@cmdCharDescDescription", function(text) + ix.command.Run(client, "CharDesc", {text}) + end, client:GetCharacter():GetDescription()) + end + + local info = ix.char.vars.description + local result, fault, count = info:OnValidate(description) + + if (result == false) then + return "@" .. fault, count + end + + client:GetCharacter():SetDescription(description) + return "@descChanged" + end +}) + +ix.command.Add("PlyTransfer", { + description = "@cmdPlyTransfer", + adminOnly = true, + arguments = { + ix.type.character, + ix.type.text + }, + OnRun = function(self, client, target, name) + local faction = ix.faction.teams[name] + + if (!faction) then + for _, v in pairs(ix.faction.indices) do + if (ix.util.StringMatches(L(v.name, client), name)) then + faction = v + + break + end + end + end + + if (faction) then + local bHasWhitelist = target:GetPlayer():HasWhitelist(faction.index) + + if (bHasWhitelist) then + target.vars.faction = faction.uniqueID + target:SetFaction(faction.index) + + if (faction.OnTransferred) then + faction:OnTransferred(target) + end + + for _, v in player.Iterator() do + if (self:OnCheckAccess(v) or v == target:GetPlayer()) then + v:NotifyLocalized("cChangeFaction", client:GetName(), target:GetName(), L(faction.name, v)) + end + end + else + return "@charNotWhitelisted", target:GetName(), L(faction.name, client) + end + else + return "@invalidFaction" + end + end +}) + +ix.command.Add("CharSetClass", { + description = "@cmdCharSetClass", + adminOnly = true, + arguments = { + ix.type.character, + ix.type.text + }, + OnRun = function(self, client, target, class) + local classTable + + for _, v in ipairs(ix.class.list) do + if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(v.name, class)) then + classTable = v + end + end + + if (classTable) then + local oldClass = target:GetClass() + local targetPlayer = target:GetPlayer() + + if (targetPlayer:Team() == classTable.faction) then + target:SetClass(classTable.index) + hook.Run("PlayerJoinedClass", targetPlayer, classTable.index, oldClass) + + targetPlayer:NotifyLocalized("becomeClass", L(classTable.name, targetPlayer)) + + -- only send second notification if the character isn't setting their own class + if (client != targetPlayer) then + return "@setClass", target:GetName(), L(classTable.name, client) + end + else + return "@invalidClassFaction" + end + else + return "@invalidClass" + end + end +}) + +ix.command.Add("MapRestart", { + description = "@cmdMapRestart", + adminOnly = true, + arguments = bit.bor(ix.type.number, ix.type.optional), + OnRun = function(self, client, delay) + delay = delay or 10 + ix.util.NotifyLocalized("mapRestarting", nil, delay) + + timer.Simple(delay, function() + RunConsoleCommand("changelevel", game.GetMap()) + end) + end +}) diff --git a/garrysmod/gamemodes/helix/gamemode/core/sh_config.lua b/garrysmod/gamemodes/helix/gamemode/core/sh_config.lua new file mode 100644 index 0000000..4eea583 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/sh_config.lua @@ -0,0 +1,372 @@ + +--- Helper library for creating/setting config options. +-- @module ix.config + +ix.config = ix.config or {} +ix.config.stored = ix.config.stored or {} + +if (SERVER) then + util.AddNetworkString("ixConfigList") + util.AddNetworkString("ixConfigSet") + util.AddNetworkString("ixConfigRequestUnloadedList") + util.AddNetworkString("ixConfigUnloadedList") + util.AddNetworkString("ixConfigPluginToggle") + + ix.config.server = ix.yaml.Read("gamemodes/helix/helix.yml") or {} +end + +CAMI.RegisterPrivilege({ + Name = "Helix - Manage Config", + MinAccess = "superadmin" +}) + +--- Creates a config option with the given information. +-- @realm shared +-- @string key Unique ID of the config +-- @param value Default value that this config will have +-- @string description Description of the config +-- @func[opt=nil] callback Function to call when config is changed +-- @tab[opt=nil] data Additional settings for this config option +-- @bool[opt=false] bNoNetworking Whether or not to prevent networking the config +-- @bool[opt=false] bSchemaOnly Whether or not the config is for the schema only +function ix.config.Add(key, value, description, callback, data, bNoNetworking, bSchemaOnly) + data = istable(data) and data or {} + + local oldConfig = ix.config.stored[key] + local type = data.type or ix.util.GetTypeFromValue(value) + + if (!type) then + ErrorNoHalt("attempted to add config with invalid type\n") + return + end + + local default = value + data.type = nil + + -- using explicit nil comparisons so we don't get caught by a config's value being `false` + if (oldConfig != nil) then + if (oldConfig.value != nil) then + value = oldConfig.value + end + + if (oldConfig.default != nil) then + default = oldConfig.default + end + end + + ix.config.stored[key] = { + type = type, + data = data, + value = value, + default = default, + description = description, + bNoNetworking = bNoNetworking, + global = !bSchemaOnly, + callback = callback, + hidden = data.hidden or nil + } +end + +--- Sets the default value for a config option. +-- @realm shared +-- @string key Unique ID of the config +-- @param value Default value for the config option +function ix.config.SetDefault(key, value) + local config = ix.config.stored[key] + + if (config) then + config.default = value + else + -- set up dummy config if we're setting default of config that doesn't exist yet (i.e schema setting framework default) + ix.config.stored[key] = { + value = value, + default = value + } + end +end + +function ix.config.ForceSet(key, value, noSave) + local config = ix.config.stored[key] + + if (config) then + config.value = value + end + + if (noSave) then + ix.config.Save() + end +end + +--- Sets the value of a config option. +-- @realm shared +-- @string key Unique ID of the config +-- @param value New value to assign to the config +function ix.config.Set(key, value) + local config = ix.config.stored[key] + + if (config) then + local oldValue = value + config.value = value + + if (SERVER) then + if (!config.bNoNetworking) then + net.Start("ixConfigSet") + net.WriteString(key) + net.WriteType(value) + net.Broadcast() + end + + if (config.callback) then + config.callback(oldValue, value) + end + + ix.config.Save() + end + end +end + +--- Retrieves a value of a config option. If it is not set, it'll return the default that you've specified. +-- @realm shared +-- @string key Unique ID of the config +-- @param default Default value to return if the config is not set +-- @return Value associated with the key, or the default that was given if it doesn't exist +function ix.config.Get(key, default) + local config = ix.config.stored[key] + + -- ensure we aren't accessing a dummy value + if (config and config.type) then + if (config.value != nil) then + return config.value + elseif (config.default != nil) then + return config.default + end + end + + return default +end + +--- Loads all saved config options from disk. +-- @realm shared +-- @internal +function ix.config.Load() + if (SERVER) then + local globals = ix.data.Get("config", nil, true, true) + local data = ix.data.Get("config", nil, false, true) + + if (globals) then + for k, v in pairs(globals) do + ix.config.stored[k] = ix.config.stored[k] or {} + ix.config.stored[k].value = v + end + end + + if (data) then + for k, v in pairs(data) do + ix.config.stored[k] = ix.config.stored[k] or {} + ix.config.stored[k].value = v + end + end + end + + ix.util.Include("helix/gamemode/config/sh_config.lua") + + if (SERVER or !IX_RELOADED) then + hook.Run("InitializedConfig") + end +end + +if (SERVER) then + function ix.config.GetChangedValues() + local data = {} + + for k, v in pairs(ix.config.stored) do + if (v.default != v.value) then + data[k] = v.value + end + end + + return data + end + + function ix.config.Send(client) + net.Start("ixConfigList") + net.WriteTable(ix.config.GetChangedValues()) + net.Send(client) + end + + --- Saves all config options to disk. + -- @realm server + -- @internal + function ix.config.Save() + local globals = {} + local data = {} + + for k, v in pairs(ix.config.GetChangedValues()) do + if (ix.config.stored[k].global) then + globals[k] = v + else + data[k] = v + end + end + + -- Global and schema data set respectively. + ix.data.Set("config", globals, true, true) + ix.data.Set("config", data, false, true) + end + + net.Receive("ixConfigSet", function(length, client) + local key = net.ReadString() + local value = net.ReadType() + + if (CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil) and + type(ix.config.stored[key].default) == type(value)) then + ix.config.Set(key, value) + + if (ix.util.IsColor(value)) then + value = string.format("[%d, %d, %d]", value.r, value.g, value.b) + elseif (istable(value)) then + local value2 = "[" + local count = table.Count(value) + local i = 1 + + for _, v in SortedPairs(value) do + value2 = value2 .. v .. (i == count and "]" or ", ") + i = i + 1 + end + + value = value2 + elseif (isstring(value)) then + value = string.format("\"%s\"", tostring(value)) + elseif (isbool(value)) then + value = string.format("[%s]", tostring(value)) + end + + ix.util.NotifyLocalized("cfgSet", nil, client:Name(), key, tostring(value)) + ix.log.Add(client, "cfgSet", key, value) + end + end) + + net.Receive("ixConfigRequestUnloadedList", function(length, client) + if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then + return + end + + net.Start("ixConfigUnloadedList") + net.WriteTable(ix.plugin.unloaded) + net.Send(client) + end) + + net.Receive("ixConfigPluginToggle", function(length, client) + if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then + return + end + + local uniqueID = net.ReadString() + local bUnloaded = !!ix.plugin.unloaded[uniqueID] + local bShouldEnable = net.ReadBool() + + if ((bShouldEnable and bUnloaded) or (!bShouldEnable and !bUnloaded)) then + ix.plugin.SetUnloaded(uniqueID, !bShouldEnable) -- flip bool since we're setting unloaded, not enabled + + ix.util.NotifyLocalized(bShouldEnable and "pluginLoaded" or "pluginUnloaded", nil, client:GetName(), uniqueID) + ix.log.Add(client, bShouldEnable and "pluginLoaded" or "pluginUnloaded", uniqueID) + + net.Start("ixConfigPluginToggle") + net.WriteString(uniqueID) + net.WriteBool(bShouldEnable) + net.Broadcast() + end + end) +else + net.Receive("ixConfigList", function() + local data = net.ReadTable() + + for k, v in pairs(data) do + if (ix.config.stored[k]) then + ix.config.stored[k].value = v + end + end + + hook.Run("InitializedConfig", data) + end) + + net.Receive("ixConfigSet", function() + local key = net.ReadString() + local value = net.ReadType() + local config = ix.config.stored[key] + + if (config) then + if (config.callback) then + config.callback(config.value, value) + end + + config.value = value + + local properties = ix.gui.properties + + if (IsValid(properties)) then + local row = properties:GetCategory(L(config.data and config.data.category or "misc")):GetRow(key) + + if (IsValid(row)) then + if (istable(value) and value.r and value.g and value.b) then + value = Vector(value.r / 255, value.g / 255, value.b / 255) + end + + row:SetValue(value) + end + end + end + end) + + net.Receive("ixConfigUnloadedList", function() + ix.plugin.unloaded = net.ReadTable() + ix.gui.bReceivedUnloadedPlugins = true + + if (IsValid(ix.gui.pluginManager)) then + ix.gui.pluginManager:UpdateUnloaded() + end + end) + + net.Receive("ixConfigPluginToggle", function() + local uniqueID = net.ReadString() + local bEnabled = net.ReadBool() + + if (bEnabled) then + ix.plugin.unloaded[uniqueID] = false + else + ix.plugin.unloaded[uniqueID] = true + end + + if (IsValid(ix.gui.pluginManager)) then + ix.gui.pluginManager:UpdatePlugin(uniqueID, bEnabled) + end + end) + + hook.Add("CreateMenuButtons", "ixConfig", function(tabs) + if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Config", nil)) then + return + end + + tabs["config"] = { + Create = function(info, container) + container.panel = container:Add("ixConfigManager") + end, + + OnSelected = function(info, container) + container.panel.searchEntry:RequestFocus() + end, + + Sections = { + plugins = { + Create = function(info, container) + ix.gui.pluginManager = container:Add("ixPluginManager") + end, + + OnSelected = function(info, container) + ix.gui.pluginManager.searchEntry:RequestFocus() + end + } + } + } + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/sh_data.lua b/garrysmod/gamemodes/helix/gamemode/core/sh_data.lua new file mode 100644 index 0000000..d684110 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/sh_data.lua @@ -0,0 +1,118 @@ + +--- Helper library for reading/writing files to the data folder. +-- @module ix.data + +ix.data = ix.data or {} +ix.data.stored = ix.data.stored or {} + +-- Create a folder to store data in. +file.CreateDir("helix") + +--- Populates a file in the `data/helix` folder with some serialized data. +-- @realm shared +-- @string key Name of the file to save +-- @param value Some sort of data to save +-- @bool[opt=false] bGlobal Whether or not to write directly to the `data/helix` folder, or the `data/helix/schema` folder, +-- where `schema` is the name of the current schema. +-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and save in the schema folder, rather than +-- `data/helix/schema/map`, where `map` is the name of the current map. +function ix.data.Set(key, value, bGlobal, bIgnoreMap) + -- Get the base path to write to. + local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/") + + -- Create the schema folder if the data is not global. + if (!bGlobal) then + file.CreateDir("helix/" .. Schema.folder .. "/") + end + + -- If we're not ignoring the map, create a folder for the map. + file.CreateDir(path) + -- Write the data using JSON encoding. + file.Write(path .. key .. ".txt", util.TableToJSON({value})) + + -- Cache the data value here. + ix.data.stored[key] = value + + return path +end + +--- Retrieves the contents of a saved file in the `data/helix` folder. +-- @realm shared +-- @string key Name of the file to load +-- @param default Value to return if the file could not be loaded successfully +-- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder, +-- where `schema` is the name of the current schema. +-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and load from the schema folder, rather than +-- `data/helix/schema/map`, where `map` is the name of the current map. +-- @bool[opt=false] bRefresh Whether or not to skip the cache and forcefully load from disk. +-- @return Value associated with the key, or the default that was given if it doesn't exists +function ix.data.Get(key, default, bGlobal, bIgnoreMap, bRefresh) + -- If it exists in the cache, return the cached value so it is faster. + if (!bRefresh) then + local stored = ix.data.stored[key] + + if (stored != nil) then + return stored + end + end + + -- Get the path to read from. + local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/") + -- Read the data from a local file. + local contents = file.Read(path .. key .. ".txt", "DATA") + + if (contents and contents != "") then + local status, decoded = pcall(util.JSONToTable, contents) + + if (status and decoded) then + local value = decoded[1] + + if (value != nil) then + return value + end + end + + -- Backwards compatibility. + -- This may be removed in the future. + status, decoded = pcall(pon.decode, contents) + + if (status and decoded) then + local value = decoded[1] + + if (value != nil) then + return value + end + end + end + + return default +end + +--- Deletes the contents of a saved file in the `data/helix` folder. +-- @realm shared +-- @string key Name of the file to delete +-- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder, +-- where `schema` is the name of the current schema. +-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and delete from the schema folder, rather than +-- `data/helix/schema/map`, where `map` is the name of the current map. +-- @treturn bool Whether or not the deletion has succeeded +function ix.data.Delete(key, bGlobal, bIgnoreMap) + -- Get the path to read from. + local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/") + -- Read the data from a local file. + local contents = file.Read(path .. key .. ".txt", "DATA") + + if (contents and contents != "") then + file.Delete(path .. key .. ".txt") + ix.data.stored[key] = nil + return true + end + + return false +end + +if (SERVER) then + timer.Create("ixSaveData", 600, 0, function() + hook.Run("SaveData") + end) +end diff --git a/garrysmod/gamemodes/helix/gamemode/core/sh_util.lua b/garrysmod/gamemodes/helix/gamemode/core/sh_util.lua new file mode 100644 index 0000000..af0b3dc --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/core/sh_util.lua @@ -0,0 +1,1165 @@ + +--- Various useful helper functions. +-- @module ix.util + +ix.type = ix.type or { + [2] = "string", + [4] = "text", + [8] = "number", + [16] = "player", + [32] = "steamid", + [64] = "character", + [128] = "bool", + [1024] = "color", + [2048] = "vector", + + string = 2, + text = 4, + number = 8, + player = 16, + steamid = 32, + character = 64, + bool = 128, + color = 1024, + vector = 2048, + + optional = 256, + array = 512 +} + +ix.blurRenderQueue = {} + +--- Includes a lua file based on the prefix of the file. This will automatically call `include` and `AddCSLuaFile` based on the +-- current realm. This function should always be called shared to ensure that the client will receive the file from the server. +-- @realm shared +-- @string fileName Path of the Lua file to include. The path is relative to the file that is currently running this function +-- @string[opt] realm Realm that this file should be included in. You should usually ignore this since it +-- will be automatically be chosen based on the `SERVER` and `CLIENT` globals. This value should either be `"server"` or +-- `"client"` if it is filled in manually +function ix.util.Include(fileName, realm) + if (!fileName) then + error("[Helix] No file name specified for including.") + end + + -- Only include server-side if we're on the server. + if ((realm == "server" or fileName:find("sv_")) and SERVER) then + return include(fileName) + -- Shared is included by both server and client. + elseif (realm == "shared" or fileName:find("shared.lua") or fileName:find("sh_")) then + if (SERVER) then + -- Send the file to the client if shared so they can run it. + AddCSLuaFile(fileName) + end + + return include(fileName) + -- File is sent to client, included on client. + elseif (realm == "client" or fileName:find("cl_")) then + if (SERVER) then + AddCSLuaFile(fileName) + else + return include(fileName) + end + end +end + +--- Includes multiple files in a directory. +-- @realm shared +-- @string directory Directory to include files from +-- @bool[opt] bFromLua Whether or not to search from the base `lua/` folder, instead of contextually basing from `schema/` +-- or `gamemode/` +-- @see ix.util.Include +function ix.util.IncludeDir(directory, bFromLua) + -- By default, we include relatively to Helix. + local baseDir = "helix" + + -- If we're in a schema, include relative to the schema. + if (Schema and Schema.folder and Schema.loading) then + baseDir = Schema.folder.."/schema/" + else + baseDir = baseDir.."/gamemode/" + end + + -- Find all of the files within the directory. + for _, v in ipairs(file.Find((bFromLua and "" or baseDir)..directory.."/*.lua", "LUA")) do + -- Include the file from the prefix. + ix.util.Include(directory.."/"..v) + end +end + +--- Removes the realm prefix from a file name. The returned string will be unchanged if there is no prefix found. +-- @realm shared +-- @string name String to strip prefix from +-- @treturn string String stripped of prefix +-- @usage print(ix.util.StripRealmPrefix("sv_init.lua")) +-- > init.lua +function ix.util.StripRealmPrefix(name) + local prefix = name:sub(1, 3) + + return (prefix == "sh_" or prefix == "sv_" or prefix == "cl_") and name:sub(4) or name +end + +--- Returns `true` if the given input is a color table. This is necessary since the engine `IsColor` function only checks for +-- color metatables - which are not used for regular Lua color types. +-- @realm shared +-- @param input Input to check +-- @treturn bool Whether or not the input is a color +function ix.util.IsColor(input) + return istable(input) and + isnumber(input.a) and isnumber(input.g) and isnumber(input.b) and (input.a and isnumber(input.a) or input.a == nil) +end + +--- Returns a dimmed version of the given color by the given scale. +-- @realm shared +-- @color color Color to dim +-- @number multiplier What to multiply the red, green, and blue values by +-- @number[opt=255] alpha Alpha to use in dimmed color +-- @treturn color Dimmed color +-- @usage print(ix.util.DimColor(Color(100, 100, 100, 255), 0.5)) +-- > 50 50 50 255 +function ix.util.DimColor(color, multiplier, alpha) + return Color(color.r * multiplier, color.g * multiplier, color.b * multiplier, alpha or 255) +end + +--- Sanitizes an input value with the given type. This function ensures that a valid type is always returned. If a valid value +-- could not be found, it will return the default value for the type. This only works for simple types - e.g it does not work +-- for player, character, or Steam ID types. +-- @realm shared +-- @ixtype type Type to check for +-- @param input Value to sanitize +-- @return Sanitized value +-- @see ix.type +-- @usage print(ix.util.SanitizeType(ix.type.number, "123")) +-- > 123 +-- print(ix.util.SanitizeType(ix.type.bool, 1)) +-- > true +function ix.util.SanitizeType(type, input) + if (type == ix.type.string) then + return tostring(input) + elseif (type == ix.type.text) then + return tostring(input) + elseif (type == ix.type.number) then + return tonumber(input or 0) or 0 + elseif (type == ix.type.bool) then + return tobool(input) + elseif (type == ix.type.color) then + return istable(input) and + Color(tonumber(input.r) or 255, tonumber(input.g) or 255, tonumber(input.b) or 255, tonumber(input.a) or 255) or + color_white + elseif (type == ix.type.vector) then + return isvector(input) and input or vector_origin + elseif (type == ix.type.array) then + return input + else + error("attempted to sanitize " .. (ix.type[type] and ("invalid type " .. ix.type[type]) or "unknown type " .. type)) + end +end + +do + local typeMap = { + string = ix.type.string, + number = ix.type.number, + Player = ix.type.player, + boolean = ix.type.bool, + Vector = ix.type.vector + } + + local tableMap = { + [ix.type.character] = function(value) + return getmetatable(value) == ix.meta.character + end, + + [ix.type.color] = function(value) + return ix.util.IsColor(value) + end, + + [ix.type.steamid] = function(value) + return isstring(value) and (value:match("STEAM_(%d+):(%d+):(%d+)")) != nil + end + } + + --- Returns the `ix.type` of the given value. + -- @realm shared + -- @param value Value to get the type of + -- @treturn ix.type Type of value + -- @see ix.type + -- @usage print(ix.util.GetTypeFromValue("hello")) + -- > 2 -- i.e the value of ix.type.string + function ix.util.GetTypeFromValue(value) + local result = typeMap[type(value)] + + if (result) then + return result + end + + if (istable(value)) then + for k, v in pairs(tableMap) do + if (v(value)) then + return k + end + end + end + end +end + +function ix.util.Bind(self, callback) + return function(_, ...) + return callback(self, ...) + end +end + +-- Returns the address:port of the server. +function ix.util.GetAddress() + local address = tonumber(GetConVarString("hostip")) + + if (!address) then + return "127.0.0.1"..":"..GetConVarString("hostport") + end + + local ip = {} + ip[1] = bit.rshift(bit.band(address, 0xFF000000), 24) + ip[2] = bit.rshift(bit.band(address, 0x00FF0000), 16) + ip[3] = bit.rshift(bit.band(address, 0x0000FF00), 8) + ip[4] = bit.band(address, 0x000000FF) + return table.concat(ip, ".")..":"..GetConVarString("hostport") +end + +--- Returns a cached copy of the given material, or creates and caches one if it doesn't exist. This is a quick helper function +-- if you aren't locally storing a `Material()` call. +-- @realm shared +-- @string materialPath Path to the material +-- @treturn[1] material The cached material +-- @treturn[2] nil If the material doesn't exist in the filesystem +function ix.util.GetMaterial(materialPath) + -- Cache the material. + ix.util.cachedMaterials = ix.util.cachedMaterials or {} + ix.util.cachedMaterials[materialPath] = ix.util.cachedMaterials[materialPath] or Material(materialPath) + + return ix.util.cachedMaterials[materialPath] +end + +--- Attempts to find a player by matching their name or Steam ID. +-- @realm shared +-- @string identifier Search query +-- @bool[opt=false] bAllowPatterns Whether or not to accept Lua patterns in `identifier` +-- @treturn player Player that matches the given search query - this will be `nil` if a player could not be found +function ix.util.FindPlayer(identifier, bAllowPatterns) + if (#identifier == 0) then return end + + if (string.find(identifier, "STEAM_(%d+):(%d+):(%d+)")) then + return player.GetBySteamID(identifier) + end + + if (!bAllowPatterns) then + identifier = string.PatternSafe(identifier) + end + + for _, v in player.Iterator() do + if (ix.util.StringMatches(v:Name(), identifier)) then + return v + end + end +end + +--- Checks to see if two strings are equivalent using a fuzzy manner. Both strings will be lowered, and will return `true` if +-- the strings are identical, or if `b` is a substring of `a`. +-- @realm shared +-- @string a First string to check +-- @string b Second string to check +-- @treturn bool Whether or not the strings are equivalent +function ix.util.StringMatches(a, b) + if (a and b) then + local a2, b2 = a:utf8lower(), b:utf8lower() + + -- Check if the actual letters match. + if (a == b) then return true end + if (a2 == b2) then return true end + + -- Be less strict and search. + if (a:find(b)) then return true end + if (a2:find(b2)) then return true end + end + + return false +end + +--- Returns a string that has the named arguments in the format string replaced with the given arguments. +-- @realm shared +-- @string format Format string +-- @tparam tab|... Arguments to pass to the formatted string. If passed a table, it will use that table as the lookup table for +-- the named arguments. If passed multiple arguments, it will replace the arguments in the string in order. +-- @usage print(ix.util.FormatStringNamed("Hi, my name is {name}.", {name = "Bobby"})) +-- > Hi, my name is Bobby. +-- @usage print(ix.util.FormatStringNamed("Hi, my name is {name}.", "Bobby")) +-- > Hi, my name is Bobby. +function ix.util.FormatStringNamed(format, ...) + local arguments = {...} + local bArray = false -- Whether or not the input has numerical indices or named ones + local input + + -- If the first argument is a table, we can assumed it's going to specify which + -- keys to fill out. Otherwise we'll fill in specified arguments in order. + if (istable(arguments[1])) then + input = arguments[1] + else + input = arguments + bArray = true + end + + local i = 0 + local result = format:gsub("{(%w-)}", function(word) + i = i + 1 + return tostring((bArray and input[i] or input[word]) or word) + end) + + return result +end + +do + local upperMap = { + ["ooc"] = true, + ["looc"] = true, + ["afk"] = true, + ["url"] = true + } + --- Returns a string that is the given input with spaces in between each CamelCase word. This function will ignore any words + -- that do not begin with a capital letter. The words `ooc`, `looc`, `afk`, and `url` will be automatically transformed + -- into uppercase text. This will not capitalize non-ASCII letters due to limitations with Lua's pattern matching. + -- @realm shared + -- @string input String to expand + -- @bool[opt=false] bNoUpperFirst Whether or not to avoid capitalizing the first character. This is useful for lowerCamelCase + -- @treturn string Expanded CamelCase string + -- @usage print(ix.util.ExpandCamelCase("HelloWorld")) + -- > Hello World + function ix.util.ExpandCamelCase(input, bNoUpperFirst) + input = bNoUpperFirst and input or input:utf8sub(1, 1):utf8upper() .. input:utf8sub(2) + + -- extra parentheses to select first return value of gsub + return string.TrimRight((input:gsub("%u%l+", function(word) + if (upperMap[word:utf8lower()]) then + word = word:utf8upper() + end + + return word .. " " + end))) + end +end + +function ix.util.GridVector(vec, gridSize) + if (gridSize <= 0) then + gridSize = 1 + end + + for i = 1, 3 do + vec[i] = vec[i] / gridSize + vec[i] = math.Round(vec[i]) + vec[i] = vec[i] * gridSize + end + + return vec +end + +do + local i + local value + local character + + local function iterator(table) + repeat + i = i + 1 + value = table[i] + character = value and value:GetCharacter() + until character or value == nil + + return value, character + end + + --- Returns an iterator for characters. The resulting key/values will be a player and their corresponding characters. This + -- iterator skips over any players that do not have a valid character loaded. + -- @realm shared + -- @treturn Iterator + -- @usage for client, character in ix.util.GetCharacters() do + -- print(client, character) + -- end + -- > Player [1][Bot01] character[1] + -- > Player [2][Bot02] character[2] + -- -- etc. + function ix.util.GetCharacters() + i = 0 + return iterator, player.GetAll() + end +end + +if (CLIENT) then + local blur = ix.util.GetMaterial("pp/blurscreen") + local surface = surface + + --- Blurs the content underneath the given panel. This will fall back to a simple darkened rectangle if the player has + -- blurring disabled. + -- @realm client + -- @tparam panel panel Panel to draw the blur for + -- @number[opt=5] amount Intensity of the blur. This should be kept between 0 and 10 for performance reasons + -- @number[opt=0.2] passes Quality of the blur. This should be kept as default + -- @number[opt=255] alpha Opacity of the blur + -- @usage function PANEL:Paint(width, height) + -- ix.util.DrawBlur(self) + -- end + function ix.util.DrawBlur(panel, amount, passes, alpha) + amount = amount or 5 + + if (ix.option.Get("cheapBlur", false)) then + surface.SetDrawColor(50, 50, 50, alpha or (amount * 20)) + surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall()) + else + surface.SetMaterial(blur) + surface.SetDrawColor(255, 255, 255, alpha or 255) + + local x, y = panel:LocalToScreen(0, 0) + + for i = -(passes or 0.2), 1, 0.2 do + -- Do things to the blur material to make it blurry. + blur:SetFloat("$blur", i * amount) + blur:Recompute() + + -- Draw the blur material over the screen. + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) + end + end + end + + --- Draws a blurred rectangle with the given position and bounds. This shouldn't be used for panels, see `ix.util.DrawBlur` + -- instead. + -- @realm client + -- @number x X-position of the rectangle + -- @number y Y-position of the rectangle + -- @number width Width of the rectangle + -- @number height Height of the rectangle + -- @number[opt=5] amount Intensity of the blur. This should be kept between 0 and 10 for performance reasons + -- @number[opt=0.2] passes Quality of the blur. This should be kept as default + -- @number[opt=255] alpha Opacity of the blur + -- @usage hook.Add("HUDPaint", "MyHUDPaint", function() + -- ix.util.DrawBlurAt(0, 0, ScrW(), ScrH()) + -- end) + function ix.util.DrawBlurAt(x, y, width, height, amount, passes, alpha) + amount = amount or 5 + + if (ix.option.Get("cheapBlur", false)) then + surface.SetDrawColor(30, 30, 30, amount * 20) + surface.DrawRect(x, y, width, height) + else + surface.SetMaterial(blur) + surface.SetDrawColor(255, 255, 255, alpha or 255) + + local scrW, scrH = ScrW(), ScrH() + local x2, y2 = x / scrW, y / scrH + local w2, h2 = (x + width) / scrW, (y + height) / scrH + + for i = -(passes or 0.2), 1, 0.2 do + blur:SetFloat("$blur", i * amount) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRectUV(x, y, width, height, x2, y2, w2, h2) + end + end + end + + --- Pushes a 3D2D blur to be rendered in the world. The draw function will be called next frame in the + -- `PostDrawOpaqueRenderables` hook. + -- @realm client + -- @func drawFunc Function to call when it needs to be drawn + function ix.util.PushBlur(drawFunc) + ix.blurRenderQueue[#ix.blurRenderQueue + 1] = drawFunc + end + + --- Draws some text with a shadow. + -- @realm client + -- @string text Text to draw + -- @number x X-position of the text + -- @number y Y-position of the text + -- @color color Color of the text to draw + -- @number[opt=TEXT_ALIGN_LEFT] alignX Horizontal alignment of the text, using one of the `TEXT_ALIGN_*` constants + -- @number[opt=TEXT_ALIGN_LEFT] alignY Vertical alignment of the text, using one of the `TEXT_ALIGN_*` constants + -- @string[opt="ixGenericFont"] font Font to use for the text + -- @number[opt=color.a * 0.575] alpha Alpha of the shadow + function ix.util.DrawText(text, x, y, color, alignX, alignY, font, alpha) + color = color or color_white + + return draw.TextShadow({ + text = text, + font = font or "ixGenericFont", + pos = {x, y}, + color = color, + xalign = alignX or TEXT_ALIGN_LEFT, + yalign = alignY or TEXT_ALIGN_LEFT + }, 1, alpha or (color.a * 0.575)) + end + + --- Wraps text so it does not pass a certain width. This function will try and break lines between words if it can, + -- otherwise it will break a word if it's too long. + -- @realm client + -- @string text Text to wrap + -- @number maxWidth Maximum allowed width in pixels + -- @string[opt="ixChatFont"] font Font to use for the text + function ix.util.WrapText(text, maxWidth, font) + font = font or "ixChatFont" + surface.SetFont(font) + + local words = string.Explode("%s", text, true) + local lines = {} + local line = "" + local lineWidth = 0 -- luacheck: ignore 231 + + -- we don't need to calculate wrapping if we're under the max width + if (surface.GetTextSize(text) <= maxWidth) then + return {text} + end + + for i = 1, #words do + local word = words[i] + local wordWidth = surface.GetTextSize(word) + + -- this word is very long so we have to split it by character + if (wordWidth > maxWidth) then + local newWidth + + for i2 = 1, word:utf8len() do + local character = word[i2] + newWidth = surface.GetTextSize(line .. character) + + -- if current line + next character is too wide, we'll shove the next character onto the next line + if (newWidth > maxWidth) then + lines[#lines + 1] = line + line = "" + end + + line = line .. character + end + + lineWidth = newWidth + continue + end + + local space = (i == 1) and "" or " " + local newLine = line .. space .. word + local newWidth = surface.GetTextSize(newLine) + + if (newWidth > maxWidth) then + -- adding this word will bring us over the max width + lines[#lines + 1] = line + + line = word + lineWidth = wordWidth + else + -- otherwise we tack on the new word and continue + line = newLine + lineWidth = newWidth + end + end + + if (line != "") then + lines[#lines + 1] = line + end + + return lines + end + + local cos, sin, abs, rad1, log, pow = math.cos, math.sin, math.abs, math.rad, math.log, math.pow + + -- arc drawing functions + -- by bobbleheadbob + -- https://facepunch.com/showthread.php?t=1558060 + function ix.util.DrawArc(cx, cy, radius, thickness, startang, endang, roughness, color) + surface.SetDrawColor(color) + ix.util.DrawPrecachedArc(ix.util.PrecacheArc(cx, cy, radius, thickness, startang, endang, roughness)) + end + + function ix.util.DrawPrecachedArc(arc) -- Draw a premade arc. + for _, v in ipairs(arc) do + surface.DrawPoly(v) + end + end + + function ix.util.PrecacheArc(cx, cy, radius, thickness, startang, endang, roughness) + local quadarc = {} + + -- Correct start/end ang + startang = startang or 0 + endang = endang or 0 + + -- Define step + -- roughness = roughness or 1 + local diff = abs(startang - endang) + local smoothness = log(diff, 2) / 2 + local step = diff / (pow(2, smoothness)) + + if startang > endang then + step = abs(step) * -1 + end + + -- Create the inner circle's points. + local inner = {} + local outer = {} + local ct = 1 + local r = radius - thickness + + for deg = startang, endang, step do + local rad = rad1(deg) + local cosrad, sinrad = cos(rad), sin(rad) --calculate sin, cos + + local ox, oy = cx + (cosrad * r), cy + (-sinrad * r) --apply to inner distance + inner[ct] = { + x = ox, + y = oy, + u = (ox - cx) / radius + .5, + v = (oy - cy) / radius + .5 + } + + local ox2, oy2 = cx + (cosrad * radius), cy + (-sinrad * radius) --apply to outer distance + outer[ct] = { + x = ox2, + y = oy2, + u = (ox2 - cx) / radius + .5, + v = (oy2 - cy) / radius + .5 + } + + ct = ct + 1 + end + + -- QUAD the points. + for tri = 1, ct do + local p1, p2, p3, p4 + local t = tri + 1 + p1 = outer[tri] + p2 = outer[t] + p3 = inner[t] + p4 = inner[tri] + + quadarc[tri] = {p1, p2, p3, p4} + end + + -- Return a table of triangles to draw. + return quadarc + end + + --- Resets all stencil values to known good (i.e defaults) + -- @realm client + function ix.util.ResetStencilValues() + 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 + + -- luacheck: globals derma + -- Alternative to SkinHook that allows you to pass more arguments to skin methods + function derma.SkinFunc(name, panel, a, b, c, d, e, f, g) + local skin = (ispanel(panel) and IsValid(panel)) and panel:GetSkin() or derma.GetDefaultSkin() + + if (!skin) then + return + end + + local func = skin[name] + + if (!func) then + return + end + + return func(skin, panel, a, b, c, d, e, f, g) + end + + -- Alternative to Color that retrieves from the SKIN.Colours table + function derma.GetColor(name, panel, default) + default = default or ix.config.Get("color") + + local skin = panel:GetSkin() + + if (!skin) then + return default + end + + return skin.Colours[name] or default + end + + + hook.Add("OnScreenSizeChanged", "ix.OnScreenSizeChanged", function(oldWidth, oldHeight) + hook.Run("ScreenResolutionChanged", oldWidth, oldHeight) + end) +end + +-- Vector extension, courtesy of code_gs +do + local VECTOR = FindMetaTable("Vector") + local CrossProduct = VECTOR.Cross + local right = Vector(0, -1, 0) + + function VECTOR:Right(vUp) + if (self[1] == 0 and self[2] == 0) then + return right + end + + if (vUp == nil) then + vUp = vector_up + end + + local vRet = CrossProduct(self, vUp) + vRet:Normalize() + + return vRet + end + + function VECTOR:Up(vUp) + if (self[1] == 0 and self[2] == 0) then return Vector(-self[3], 0, 0) end + + if (vUp == nil) then + vUp = vector_up + end + + local vRet = CrossProduct(self, vUp) + vRet = CrossProduct(vRet, self) + vRet:Normalize() + + return vRet + end +end + +-- luacheck: globals FCAP_IMPULSE_USE FCAP_CONTINUOUS_USE FCAP_ONOFF_USE +-- luacheck: globals FCAP_DIRECTIONAL_USE FCAP_USE_ONGROUND FCAP_USE_IN_RADIUS +FCAP_IMPULSE_USE = 0x00000010 +FCAP_CONTINUOUS_USE = 0x00000020 +FCAP_ONOFF_USE = 0x00000040 +FCAP_DIRECTIONAL_USE = 0x00000080 +FCAP_USE_ONGROUND = 0x00000100 +FCAP_USE_IN_RADIUS = 0x00000200 + +function ix.util.IsUseableEntity(entity, requiredCaps) + if (IsValid(entity)) then + local caps = entity:ObjectCaps() + + if (bit.band(caps, bit.bor(FCAP_IMPULSE_USE, FCAP_CONTINUOUS_USE, FCAP_ONOFF_USE, FCAP_DIRECTIONAL_USE))) then + if (bit.band(caps, requiredCaps) == requiredCaps) then + return true + end + end + end +end + +do + local function IntervalDistance(x, x0, x1) + -- swap so x0 < x1 + if (x0 > x1) then + local tmp = x0 + + x0 = x1 + x1 = tmp + end + + if (x < x0) then + return x0-x + elseif (x > x1) then + return x - x1 + end + + return 0 + end + + local NUM_TANGENTS = 8 + local tangents = {0, 1, 0.57735026919, 0.3639702342, 0.267949192431, 0.1763269807, -0.1763269807, -0.267949192431} + local traceMin = Vector(-16, -16, -16) + local traceMax = Vector(16, 16, 16) + + function ix.util.FindUseEntity(player, origin, forward) + local tr + local up = forward:Up() + -- Search for objects in a sphere (tests for entities that are not solid, yet still useable) + local searchCenter = origin + + -- NOTE: Some debris objects are useable too, so hit those as well + -- A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. + local useableContents = bit.bor(MASK_SOLID, CONTENTS_DEBRIS, CONTENTS_PLAYERCLIP) + + -- UNDONE: Might be faster to just fold this range into the sphere query + local pObject + + local nearestDist = 1e37 + -- try the hit entity if there is one, or the ground entity if there isn't. + local pNearest = NULL + + for i = 1, NUM_TANGENTS do + if (i == 0) then + tr = util.TraceLine({ + start = searchCenter, + endpos = searchCenter + forward * 1024, + mask = useableContents, + filter = player + }) + + tr.EndPos = searchCenter + forward * 1024 + else + local down = forward - tangents[i] * up + down:Normalize() + + tr = util.TraceHull({ + start = searchCenter, + endpos = searchCenter + down * 72, + mins = traceMin, + maxs = traceMax, + mask = useableContents, + filter = player + }) + + tr.EndPos = searchCenter + down * 72 + end + + pObject = tr.Entity + + local bUsable = ix.util.IsUseableEntity(pObject, 0) + + while (IsValid(pObject) and !bUsable and pObject:GetMoveParent()) do + pObject = pObject:GetMoveParent() + bUsable = ix.util.IsUseableEntity(pObject, 0) + end + + if (bUsable) then + local delta = tr.EndPos - tr.StartPos + local centerZ = origin.z - player:WorldSpaceCenter().z + delta.z = IntervalDistance(tr.EndPos.z, centerZ - player:OBBMins().z, centerZ + player:OBBMaxs().z) + local dist = delta:Length() + + if (dist < 80) then + pNearest = pObject + + -- if this is directly under the cursor just return it now + if (i == 0) then + return pObject + end + end + end + end + + -- check ground entity first + -- if you've got a useable ground entity, then shrink the cone of this search to 45 degrees + -- otherwise, search out in a 90 degree cone (hemisphere) + if (IsValid(player:GetGroundEntity()) and ix.util.IsUseableEntity(player:GetGroundEntity(), FCAP_USE_ONGROUND)) then + pNearest = player:GetGroundEntity() + end + + if (IsValid(pNearest)) then + -- estimate nearest object by distance from the view vector + local point = pNearest:NearestPoint(searchCenter) + nearestDist = util.DistanceToLine(searchCenter, forward, point) + end + + for _, v in pairs(ents.FindInSphere(searchCenter, 80)) do + if (!ix.util.IsUseableEntity(v, FCAP_USE_IN_RADIUS)) then + continue + end + + -- see if it's more roughly in front of the player than previous guess + local point = v:NearestPoint(searchCenter) + + local dir = point - searchCenter + dir:Normalize() + local dot = dir:Dot(forward) + + -- Need to be looking at the object more or less + if (dot < 0.8) then + continue + end + + local dist = util.DistanceToLine(searchCenter, forward, point) + + if (dist < nearestDist) then + -- Since this has purely been a radius search to this point, we now + -- make sure the object isn't behind glass or a grate. + local trCheckOccluded = {} + + util.TraceLine({ + start = searchCenter, + endpos = point, + mask = useableContents, + filter = player, + output = trCheckOccluded + }) + + if (trCheckOccluded.fraction == 1.0 or trCheckOccluded.Entity == v) then + pNearest = v + nearestDist = dist + end + end + end + + return pNearest + end +end + +ALWAYS_RAISED = {} +ALWAYS_RAISED["weapon_physgun"] = true +ALWAYS_RAISED["gmod_tool"] = true +ALWAYS_RAISED["ix_poshelper"] = true + +function ix.util.FindEmptySpace(entity, filter, spacing, size, height, tolerance) + spacing = spacing or 32 + size = size or 3 + height = height or 36 + tolerance = tolerance or 5 + + local position = entity:GetPos() + local mins, maxs = Vector(-spacing * 0.5, -spacing * 0.5, 0), Vector(spacing * 0.5, spacing * 0.5, height) + local output = {} + + for x = -size, size do + for y = -size, size do + local origin = position + Vector(x * spacing, y * spacing, 0) + + local data = {} + data.start = origin + mins + Vector(0, 0, tolerance) + data.endpos = origin + maxs + data.filter = filter or entity + local trace = util.TraceLine(data) + + data.start = origin + Vector(-maxs.x, -maxs.y, tolerance) + data.endpos = origin + Vector(mins.x, mins.y, height) + + local trace2 = util.TraceLine(data) + + if (trace.StartSolid or trace.Hit or trace2.StartSolid or trace2.Hit or !util.IsInWorld(origin)) then + continue + end + + output[#output + 1] = origin + end + end + + table.sort(output, function(a, b) + return a:DistToSqr(position) < b:DistToSqr(position) + end) + + return output +end + +-- Time related stuff. +do + --- Gets the current time in the UTC time-zone. + -- @realm shared + -- @treturn number Current time in UTC + function ix.util.GetUTCTime() + local date = os.date("!*t") + local localDate = os.date("*t") + localDate.isdst = false + + return os.difftime(os.time(date), os.time(localDate)) + end + + -- Setup for time strings. + local TIME_UNITS = {} + TIME_UNITS["s"] = 1 -- Seconds + TIME_UNITS["m"] = 60 -- Minutes + TIME_UNITS["h"] = 3600 -- Hours + TIME_UNITS["d"] = TIME_UNITS["h"] * 24 -- Days + TIME_UNITS["w"] = TIME_UNITS["d"] * 7 -- Weeks + TIME_UNITS["mo"] = TIME_UNITS["d"] * 30 -- Months + TIME_UNITS["y"] = TIME_UNITS["d"] * 365 -- Years + + --- Gets the amount of seconds from a given formatted string. If no time units are specified, it is assumed minutes. + -- The valid values are as follows: + -- + -- - `s` - Seconds + -- - `m` - Minutes + -- - `h` - Hours + -- - `d` - Days + -- - `w` - Weeks + -- - `mo` - Months + -- - `y` - Years + -- @realm shared + -- @string text Text to interpret a length of time from + -- @treturn[1] number Amount of seconds from the length interpreted from the given string + -- @treturn[2] 0 If the given string does not have a valid time + -- @usage print(ix.util.GetStringTime("5y2d7w")) + -- > 162086400 -- 5 years, 2 days, 7 weeks + function ix.util.GetStringTime(text) + local minutes = tonumber(text) + + if (minutes) then + return math.abs(minutes * 60) + end + + local time = 0 + + for amount, unit in text:lower():gmatch("(%d+)(%a+)") do + amount = tonumber(amount) + + if (amount and TIME_UNITS[unit]) then + time = time + math.abs(amount * TIME_UNITS[unit]) + end + end + + return time + end +end + +--[[ + Credit to TFA for figuring this mess out. + Original: https://steamcommunity.com/sharedfiles/filedetails/?id=903541818 +]] + +if (system.IsLinux()) then + local cache = {} + + -- Helper Functions + local function GetSoundPath(path, gamedir) + if (!gamedir) then + path = "sound/" .. path + gamedir = "GAME" + end + + return path, gamedir + end + + local function f_IsWAV(f) + f:Seek(8) + + return f:Read(4) == "WAVE" + end + + -- WAV functions + local function f_SampleDepth(f) + f:Seek(34) + local bytes = {} + + for i = 1, 2 do + bytes[i] = f:ReadByte(1) + end + + local num = bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0) + + return num + end + + local function f_SampleRate(f) + f:Seek(24) + local bytes = {} + + for i = 1, 4 do + bytes[i] = f:ReadByte(1) + end + + local num = bit.lshift(bytes[4], 24) + bit.lshift(bytes[3], 16) + bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0) + + return num + end + + local function f_Channels(f) + f:Seek(22) + local bytes = {} + + for i = 1, 2 do + bytes[i] = f:ReadByte(1) + end + + local num = bit.lshift(bytes[2], 8) + bit.lshift(bytes[1], 0) + + return num + end + + local function f_Duration(f) + return (f:Size() - 44) / (f_SampleDepth(f) / 8 * f_SampleRate(f) * f_Channels(f)) + end + + ixSoundDuration = ixSoundDuration or SoundDuration -- luacheck: globals ixSoundDuration + + function SoundDuration(str) -- luacheck: globals SoundDuration + local path, gamedir = GetSoundPath(str) + local f = file.Open(path, "rb", gamedir) + + if (!f) then return 0 end --Return nil on invalid files + + local ret + + if (cache[str]) then + ret = cache[str] + elseif (f_IsWAV(f)) then + ret = f_Duration(f) + else + ret = ixSoundDuration(str) + end + + f:Close() + + return ret + end +end + +local ADJUST_SOUND = SoundDuration("npc/metropolice/pain1.wav") > 0 and "" or "../../hl2/sound/" + +--- Emits sounds one after the other from an entity. +-- @realm shared +-- @entity entity Entity to play sounds from +-- @tab sounds Sound paths to play +-- @number delay[opt=0] How long to wait before starting to play the sounds +-- @number spacing[opt=0.1] How long to wait between playing each sound +-- @number volume[opt=75] The sound level of each sound +-- @number pitch[opt=100] Pitch percentage of each sound +-- @treturn number How long the entire sequence of sounds will take to play +function ix.util.EmitQueuedSounds(entity, sounds, delay, spacing, volume, pitch) + -- Let there be a delay before any sound is played. + delay = delay or 0 + spacing = spacing or 0.1 + + -- Loop through all of the sounds. + for _, v in ipairs(sounds) do + local postSet, preSet = 0, 0 + + -- Determine if this sound has special time offsets. + if (istable(v)) then + postSet, preSet = v[2] or 0, v[3] or 0 + v = v[1] + end + + -- Get the length of the sound. + local length = SoundDuration(ADJUST_SOUND..v) + -- If the sound has a pause before it is played, add it here. + delay = delay + preSet + + -- Have the sound play in the future. + timer.Simple(delay, function() + -- Check if the entity still exists and play the sound. + if (IsValid(entity)) then + entity:EmitSound(v, volume, pitch) + end + end) + + -- Add the delay for the next sound. + delay = delay + length + postSet + spacing + end + + -- Return how long it took for the whole thing. + return delay +end + +--- Merges the contents of the second table with the content in the first one. The destination table will be modified. +--- If element is table but not metatable object, value's elements will be changed only. +-- @realm shared +-- @tab destination The table you want the source table to merge with +-- @tab source The table you want to merge with the destination table +-- @return table +function ix.util.MetatableSafeTableMerge(destination, source) + for k, v in pairs(source) do + if (istable(v) and istable(destination[k]) and getmetatable(v) == nil) then + -- don't overwrite one table with another + -- instead merge them recurisvely + ix.util.MetatableSafeTableMerge(destination[k], v); + else + destination[ k ] = v; + end + end + return destination; +end + +ix.util.Include("helix/gamemode/core/meta/sh_entity.lua") +ix.util.Include("helix/gamemode/core/meta/sh_player.lua") diff --git a/garrysmod/gamemodes/helix/gamemode/init.lua b/garrysmod/gamemodes/helix/gamemode/init.lua new file mode 100644 index 0000000..279bca3 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/init.lua @@ -0,0 +1,45 @@ + +-- Include Helix content. +resource.AddWorkshop("1267236756") + +-- Include features from the Sandbox gamemode. +DeriveGamemode("sandbox") +-- Define a global shared table to store Helix information. +ix = ix or {util = {}, meta = {}} + +-- Send the following files to players. +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("core/sh_util.lua") +AddCSLuaFile("core/sh_data.lua") +AddCSLuaFile("shared.lua") + +-- Include utility functions, data storage functions, and then shared.lua +include("core/sh_util.lua") +include("core/sh_data.lua") +include("shared.lua") + +-- Resources that are required for players to download are here. +resource.AddFile("materials/helix/gui/vignette.png") +resource.AddFile("resource/fonts/fontello.ttf") +resource.AddFile("sound/helix/intro.mp3") +resource.AddFile("sound/helix/ui/press.wav") +resource.AddFile("sound/helix/ui/rollover.wav") +resource.AddFile("sound/helix/ui/whoosh1.wav") +resource.AddFile("sound/helix/ui/whoosh2.wav") +resource.AddFile("sound/helix/ui/whoosh3.wav") +resource.AddFile("sound/helix/ui/whoosh4.wav") +resource.AddFile("sound/helix/ui/whoosh5.wav") +resource.AddFile("sound/helix/ui/whoosh6.wav") + +cvars.AddChangeCallback("sbox_persist", function(name, old, new) + -- A timer in case someone tries to rapily change the convar, such as addons with "live typing" or whatever + timer.Create("sbox_persist_change_timer", 1, 1, function() + hook.Run("PersistenceSave", old) + + if (new == "") then + return + end + + hook.Run("PersistenceLoad", new) + end) +end, "sbox_persist_load") diff --git a/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_357ammo.txt b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_357ammo.txt new file mode 100644 index 0000000..92e3d0e --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_357ammo.txt @@ -0,0 +1,6 @@ +ITEM.name = ".357 Ammo" +ITEM.model = "models/items/357ammo.mdl" +ITEM.ammo = "357" -- type of the ammo +ITEM.ammoAmount = 12 -- amount of the ammo +ITEM.description = "A Box that contains %s of .357 Ammo" +ITEM.price = 10 diff --git a/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_ar2ammo.txt b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_ar2ammo.txt new file mode 100644 index 0000000..b33a9f1 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_ar2ammo.txt @@ -0,0 +1,5 @@ +ITEM.name = "AR2 Cartridge" +ITEM.model = "models/Items/combine_rifle_cartridge01.mdl" +ITEM.ammo = "ar2" -- type of the ammo +ITEM.ammoAmount = 30 -- amount of the ammo +ITEM.description = "A Cartridge that contains %s of AR2 Ammo" diff --git a/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_crossbowammo.txt b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_crossbowammo.txt new file mode 100644 index 0000000..07f6bf5 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_crossbowammo.txt @@ -0,0 +1,5 @@ +ITEM.name = "Crossbow Bolts" +ITEM.model = "models/Items/CrossbowRounds.mdl" +ITEM.ammo = "XBowRounds" -- type of the ammo +ITEM.ammoAmount = 5 -- amount of the ammo +ITEM.description = "A Bundle of %s Crossbow Bolts" diff --git a/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_pistolammo.txt b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_pistolammo.txt new file mode 100644 index 0000000..d5475ea --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_pistolammo.txt @@ -0,0 +1,5 @@ +ITEM.name = "Pistol Ammo" +ITEM.model = "models/items/357ammo.mdl" +ITEM.ammo = "pistol" -- type of the ammo +ITEM.ammoAmount = 30 -- amount of the ammo +ITEM.description = "A Box that contains %s of Pistol Ammo" diff --git a/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_rocketammo.txt b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_rocketammo.txt new file mode 100644 index 0000000..8fca2f4 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_rocketammo.txt @@ -0,0 +1,11 @@ +ITEM.name = "A Rocket" +ITEM.model = "models/weapons/w_missile_closed.mdl" +ITEM.ammo = "rpg_round" -- type of the ammo +ITEM.ammoAmount = 1 -- amount of the ammo +ITEM.width = 2 +ITEM.description = "A Package of %s Rockets" +ITEM.iconCam = { + ang = Angle(-0.70499622821808, 268.25439453125, 0), + fov = 12.085652091515, + pos = Vector(7, 200, -2) +} diff --git a/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_shotgunammo.txt b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_shotgunammo.txt new file mode 100644 index 0000000..51192db --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_shotgunammo.txt @@ -0,0 +1,5 @@ +ITEM.name = "Shotgun Shells" +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.ammo = "buckshot" -- type of the ammo +ITEM.ammoAmount = 15 -- amount of the ammo +ITEM.description = "A Box of %s Shotgun Shells" diff --git a/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_smg1ammo.txt b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_smg1ammo.txt new file mode 100644 index 0000000..2c51baa --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/ammo/sh_smg1ammo.txt @@ -0,0 +1,5 @@ +ITEM.name = "Sub Machine Gun Ammo" +ITEM.model = "models/Items/BoxSRounds.mdl" +ITEM.ammo = "smg1" -- type of the ammo +ITEM.ammoAmount = 45 -- amount of the ammo +ITEM.description = "A Box that contains %s of SMG Ammo" diff --git a/garrysmod/gamemodes/helix/gamemode/items/bags/sh_large.txt b/garrysmod/gamemodes/helix/gamemode/items/bags/sh_large.txt new file mode 100644 index 0000000..818e7ef --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/bags/sh_large.txt @@ -0,0 +1,4 @@ +ITEM.name = "Big Bag" +ITEM.description = "A big bag." +ITEM.invWidth = 6 +ITEM.invHeight = 4 \ No newline at end of file diff --git a/garrysmod/gamemodes/helix/gamemode/items/bags/sh_small.txt b/garrysmod/gamemodes/helix/gamemode/items/bags/sh_small.txt new file mode 100644 index 0000000..39e4c8c --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/bags/sh_small.txt @@ -0,0 +1,2 @@ +ITEM.name = "Small Bag" +ITEM.description = "A small bag." \ No newline at end of file diff --git a/garrysmod/gamemodes/helix/gamemode/items/base/sh_ammo.lua b/garrysmod/gamemodes/helix/gamemode/items/base/sh_ammo.lua new file mode 100644 index 0000000..54c12db --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/base/sh_ammo.lua @@ -0,0 +1,46 @@ + +ITEM.name = "Ammo Base" +ITEM.model = "models/Items/BoxSRounds.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.ammo = "pistol" -- type of the ammo +ITEM.ammoAmount = 30 -- amount of the ammo +ITEM.description = "A Box that contains %s of Pistol Ammo" +ITEM.category = "Ammunition" +ITEM.useSound = "items/ammo_pickup.wav" + +function ITEM:GetDescription() + local rounds = self:GetData("rounds", self.ammoAmount) + return Format(self.description, rounds) +end + +if (CLIENT) then + function ITEM:PaintOver(item, w, h) + draw.SimpleText( + item:GetData("rounds", item.ammoAmount), "DermaDefault", w - 5, h - 5, + color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, 1, color_black + ) + end +end + +-- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item. +ITEM.functions.use = { + name = "Load", + tip = "useTip", + icon = "icon16/add.png", + OnRun = function(item) + local rounds = item:GetData("rounds", item.ammoAmount) + + item.player:GiveAmmo(rounds, item.ammo) + item.player:EmitSound(item.useSound, 110) + + return true + end, +} + +-- Called after the item is registered into the item tables. +function ITEM:OnRegistered() + if (ix.ammo) then + ix.ammo.Register(self.ammo) + end +end diff --git a/garrysmod/gamemodes/helix/gamemode/items/base/sh_bags.lua b/garrysmod/gamemodes/helix/gamemode/items/base/sh_bags.lua new file mode 100644 index 0000000..9c1016f --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/base/sh_bags.lua @@ -0,0 +1,248 @@ + +if (SERVER) then + util.AddNetworkString("ixBagDrop") +end + +ITEM.name = "Bag" +ITEM.description = "A bag to hold items." +ITEM.model = "models/props_c17/suitcase001a.mdl" +ITEM.category = "Storage" +ITEM.width = 2 +ITEM.height = 2 +ITEM.invWidth = 4 +ITEM.invHeight = 2 +ITEM.isBag = true +ITEM.functions.View = { + icon = "icon16/briefcase.png", + OnClick = function(item) + local index = item:GetData("id", "") + + if (index) then + local panel = ix.gui["inv"..index] + local inventory = ix.item.inventories[index] + local parent = IsValid(ix.gui.menuInventoryContainer) and ix.gui.menuInventoryContainer or ix.gui.openedStorage + + if (IsValid(panel)) then + panel:Remove() + end + + if (inventory and inventory.slots) then + panel = vgui.Create("ixInventory", IsValid(parent) and parent or nil) + panel:SetInventory(inventory) + panel:ShowCloseButton(true) + panel:SetTitle(item.GetName and item:GetName() or L(item.name)) + + if (parent != ix.gui.menuInventoryContainer) then + panel:Center() + + if (parent == ix.gui.openedStorage) then + panel:MakePopup() + end + else + panel:MoveToFront() + end + + ix.gui["inv"..index] = panel + else + ErrorNoHalt("[Helix] Attempt to view an uninitialized inventory '"..index.."'\n") + end + end + + return false + end, + OnCanRun = function(item) + return !IsValid(item.entity) and item:GetData("id") and !IsValid(ix.gui["inv" .. item:GetData("id", "")]) + end +} +ITEM.functions.combine = { + OnRun = function(item, data) + ix.item.instances[data[1]]:Transfer(item:GetData("id"), nil, nil, item.player) + + return false + end, + OnCanRun = function(item, data) + local index = item:GetData("id", "") + + if (index) then + local inventory = ix.item.inventories[index] + + if (inventory) then + return true + end + end + + return false + end +} + +if (CLIENT) then + function ITEM:PaintOver(item, width, height) + local panel = ix.gui["inv" .. item:GetData("id", "")] + + if (!IsValid(panel)) then + return + end + + if (vgui.GetHoveredPanel() == self) then + panel:SetHighlighted(true) + else + panel:SetHighlighted(false) + end + end +end + +-- Called when a new instance of this item has been made. +function ITEM:OnInstanced(invID, x, y) + local inventory = ix.item.inventories[invID] + + ix.inventory.New(inventory and inventory.owner or 0, self.uniqueID, function(inv) + local client = inv:GetOwner() + + inv.vars.isBag = self.uniqueID + self:SetData("id", inv:GetID()) + + if (IsValid(client)) then + inv:AddReceiver(client) + end + end) +end + +function ITEM:GetInventory() + local index = self:GetData("id") + + if (index) then + return ix.item.inventories[index] + end +end + +ITEM.GetInv = ITEM.GetInventory + +-- Called when the item first appears for a client. +function ITEM:OnSendData() + local index = self:GetData("id") + + if (index) then + local inventory = ix.item.inventories[index] + + if (inventory) then + inventory.vars.isBag = self.uniqueID + inventory:Sync(self.player) + inventory:AddReceiver(self.player) + else + local owner = self.player:GetCharacter():GetID() + + ix.inventory.Restore(self:GetData("id"), self.invWidth, self.invHeight, function(inv) + inv.vars.isBag = self.uniqueID + inv:SetOwner(owner, true) + + if (!inv.owner) then + return + end + + for client, character in ix.util.GetCharacters() do + if (character:GetID() == inv.owner) then + inv:AddReceiver(client) + break + end + end + end) + end + else + ix.inventory.New(self.player:GetCharacter():GetID(), self.uniqueID, function(inv) + self:SetData("id", inv:GetID()) + end) + end +end + +ITEM.postHooks.drop = function(item, result) + local index = item:GetData("id") + + local query = mysql:Update("ix_inventories") + query:Update("character_id", 0) + query:Where("inventory_id", index) + query:Execute() + + net.Start("ixBagDrop") + net.WriteUInt(index, 32) + net.Send(item.player) +end + +if (CLIENT) then + net.Receive("ixBagDrop", function() + local index = net.ReadUInt(32) + local panel = ix.gui["inv"..index] + + if (panel and panel:IsVisible()) then + panel:Close() + end + end) +end + +-- Called before the item is permanently deleted. +function ITEM:OnRemoved() + local index = self:GetData("id") + + if (index) then + local query = mysql:Delete("ix_items") + query:Where("inventory_id", index) + query:Execute() + + query = mysql:Delete("ix_inventories") + query:Where("inventory_id", index) + query:Execute() + end +end + +-- Called when the item should tell whether or not it can be transfered between inventories. +function ITEM:CanTransfer(oldInventory, newInventory) + local index = self:GetData("id") + + if (newInventory) then + if (newInventory.vars and newInventory.vars.isBag) then + return false + end + + local index2 = newInventory:GetID() + + if (index == index2) then + return false + end + + for k, _ in self:GetInventory():Iter() do + if (k:GetData("id") == index2) then + return false + end + end + end + + return !newInventory or newInventory:GetID() != oldInventory:GetID() or newInventory.vars.isBag +end + +function ITEM:OnTransferred(curInv, inventory) + local bagInventory = self:GetInventory() + + if (isfunction(curInv.GetOwner)) then + local owner = curInv:GetOwner() + + if (IsValid(owner)) then + bagInventory:RemoveReceiver(owner) + end + end + + if (isfunction(inventory.GetOwner)) then + local owner = inventory:GetOwner() + + if (IsValid(owner)) then + bagInventory:AddReceiver(owner) + bagInventory:SetOwner(owner) + end + else + -- it's not in a valid inventory so nobody owns this bag + bagInventory:SetOwner(nil) + end +end + +-- Called after the item is registered into the item tables. +function ITEM:OnRegistered() + ix.inventory.Register(self.uniqueID, self.invWidth, self.invHeight, true) +end diff --git a/garrysmod/gamemodes/helix/gamemode/items/base/sh_outfit.lua b/garrysmod/gamemodes/helix/gamemode/items/base/sh_outfit.lua new file mode 100644 index 0000000..aced482 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/base/sh_outfit.lua @@ -0,0 +1,322 @@ + +ITEM.name = "Outfit" +ITEM.description = "A Outfit Base." +ITEM.category = "Outfit" +ITEM.model = "models/Gibs/HGIBS.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.outfitCategory = "model" +ITEM.pacData = {} + +--[[ +-- This will change a player's skin after changing the model. Keep in mind it starts at 0. +ITEM.newSkin = 1 +-- This will change a certain part of the model. +ITEM.replacements = {"group01", "group02"} +-- This will change the player's model completely. +ITEM.replacements = "models/manhack.mdl" +-- This will have multiple replacements. +ITEM.replacements = { + {"male", "female"}, + {"group01", "group02"} +} + +-- This will apply body groups. +ITEM.bodyGroups = { + ["blade"] = 1, + ["bladeblur"] = 1 +} +]]-- + +-- Inventory drawing +if (CLIENT) then + function ITEM:PaintOver(item, w, h) + if (item:GetData("equip")) then + surface.SetDrawColor(110, 255, 110, 100) + surface.DrawRect(w - 14, h - 14, 8, 8) + end + end +end + +function ITEM:AddOutfit(client) + local character = client:GetCharacter() + + self:SetData("equip", true) + + local groups = character:GetData("groups", {}) + + -- remove original bodygroups + if (!table.IsEmpty(groups)) then + character:SetData("oldGroups" .. self.outfitCategory, groups) + character:SetData("groups", {}) + + client:ResetBodygroups() + end + + if (isfunction(self.OnGetReplacement)) then + character:SetData("oldModel" .. self.outfitCategory, + character:GetData("oldModel" .. self.outfitCategory, self.player:GetModel())) + character:SetModel(self:OnGetReplacement()) + elseif (self.replacement or self.replacements) then + character:SetData("oldModel" .. self.outfitCategory, + character:GetData("oldModel" .. self.outfitCategory, self.player:GetModel())) + + if (istable(self.replacements)) then + if (#self.replacements == 2 and isstring(self.replacements[1])) then + character:SetModel(self.player:GetModel():gsub(self.replacements[1], self.replacements[2])) + else + for _, v in ipairs(self.replacements) do + character:SetModel(self.player:GetModel():gsub(v[1], v[2])) + end + end + else + character:SetModel(self.replacement or self.replacements) + end + end + + if (self.newSkin) then + character:SetData("oldSkin" .. self.outfitCategory, self.player:GetSkin()) + self.player:SetSkin(self.newSkin) + end + + -- get outfit saved bodygroups + groups = self:GetData("groups", {}) + + -- restore bodygroups saved to the item + if (!table.IsEmpty(groups) and self:ShouldRestoreBodygroups()) then + for k, v in pairs(groups) do + client:SetBodygroup(k, v) + end + -- apply default item bodygroups if none are saved + elseif (istable(self.bodyGroups)) then + for k, v in pairs(self.bodyGroups) do + local index = client:FindBodygroupByName(k) + + if (index > -1) then + client:SetBodygroup(index, v) + end + end + end + + local materials = self:GetData("submaterial", {}) + + if (!table.IsEmpty(materials) and self:ShouldRestoreSubMaterials()) then + for k, v in pairs(materials) do + if (!isnumber(k) or !isstring(v)) then + continue + end + + client:SetSubMaterial(k - 1, v) + end + end + + if (istable(self.attribBoosts)) then + for k, v in pairs(self.attribBoosts) do + character:AddBoost(self.uniqueID, k, v) + end + end + + self:GetOwner():SetupHands() + self:OnEquipped() +end + +local function ResetSubMaterials(client) + for k, _ in ipairs(client:GetMaterials()) do + if (client:GetSubMaterial(k - 1) != "") then + client:SetSubMaterial(k - 1) + end + end +end + +function ITEM:RemoveOutfit(client) + local character = client:GetCharacter() + + self:SetData("equip", false) + + local materials = {} + + for k, _ in ipairs(client:GetMaterials()) do + if (client:GetSubMaterial(k - 1) != "") then + materials[k] = client:GetSubMaterial(k - 1) + end + end + + -- save outfit submaterials + if (!table.IsEmpty(materials)) then + self:SetData("submaterial", materials) + end + + -- remove outfit submaterials + ResetSubMaterials(client) + + local groups = {} + + for i = 0, (client:GetNumBodyGroups() - 1) do + local bodygroup = client:GetBodygroup(i) + + if (bodygroup > 0) then + groups[i] = bodygroup + end + end + + -- save outfit bodygroups + if (!table.IsEmpty(groups)) then + self:SetData("groups", groups) + end + + -- remove outfit bodygroups + client:ResetBodygroups() + + -- restore the original player model + if (character:GetData("oldModel" .. self.outfitCategory)) then + character:SetModel(character:GetData("oldModel" .. self.outfitCategory)) + character:SetData("oldModel" .. self.outfitCategory, nil) + end + + -- restore the original player model skin + if (self.newSkin) then + if (character:GetData("oldSkin" .. self.outfitCategory)) then + client:SetSkin(character:GetData("oldSkin" .. self.outfitCategory)) + character:SetData("oldSkin" .. self.outfitCategory, nil) + else + client:SetSkin(0) + end + end + + -- get character original bodygroups + groups = character:GetData("oldGroups" .. self.outfitCategory, {}) + + -- restore original bodygroups + if (!table.IsEmpty(groups)) then + for k, v in pairs(groups) do + client:SetBodygroup(k, v) + end + + character:SetData("groups", character:GetData("oldGroups" .. self.outfitCategory, {})) + character:SetData("oldGroups" .. self.outfitCategory, nil) + end + + if (istable(self.attribBoosts)) then + for k, _ in pairs(self.attribBoosts) do + character:RemoveBoost(self.uniqueID, k) + end + end + + for k, _ in pairs(self:GetData("outfitAttachments", {})) do + self:RemoveAttachment(k, client) + end + + self:GetOwner():SetupHands() + self:OnUnequipped() +end + +-- makes another outfit depend on this outfit in terms of requiring this item to be equipped in order to equip the attachment +-- also unequips the attachment if this item is dropped +function ITEM:AddAttachment(id) + local attachments = self:GetData("outfitAttachments", {}) + attachments[id] = true + + self:SetData("outfitAttachments", attachments) +end + +function ITEM:RemoveAttachment(id, client) + local item = ix.item.instances[id] + local attachments = self:GetData("outfitAttachments", {}) + + if (item and attachments[id]) then + item:OnDetached(client) + end + + attachments[id] = nil + self:SetData("outfitAttachments", attachments) +end + +ITEM:Hook("drop", function(item) + if (item:GetData("equip")) then + local character = ix.char.loaded[item.owner] + local client = character and character:GetPlayer() or item:GetOwner() + + item.player = client + item:RemoveOutfit(item:GetOwner()) + end +end) + +ITEM.functions.EquipUn = { -- sorry, for name order. + name = "unequip", + tip = "unequipTip", + icon = "icon16/cross.png", + OnRun = function(item) + item:RemoveOutfit(item.player) + return false + end, + OnCanRun = function(item) + local client = item.player + + return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and + hook.Run("CanPlayerUnequipItem", client, item) != false + end +} + +ITEM.functions.Equip = { + name = "equip", + tip = "equipTip", + icon = "icon16/tick.png", + OnRun = function(item) + local client = item.player + local char = client:GetCharacter() + + for k, _ in char:GetInventory():Iter() do + if (k.id != item.id) then + local itemTable = ix.item.instances[k.id] + + if (itemTable.pacData and k.outfitCategory == item.outfitCategory and itemTable:GetData("equip")) then + client:NotifyLocalized(item.equippedNotify or "outfitAlreadyEquipped") + return false + end + end + end + + item:AddOutfit(item.player) + return false + end, + OnCanRun = function(item) + local client = item.player + + return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and item:CanEquipOutfit() and + hook.Run("CanPlayerEquipItem", client, item) != false + end +} + +function ITEM:CanTransfer(oldInventory, newInventory) + if (newInventory and self:GetData("equip")) then + return false + end + + return true +end + +function ITEM:OnRemoved() + if (self.invID != 0 and self:GetData("equip")) then + self.player = self:GetOwner() + self:RemoveOutfit(self.player) + self.player = nil + end +end + +function ITEM:OnEquipped() +end + +function ITEM:OnUnequipped() +end + +function ITEM:CanEquipOutfit() + return true +end + +function ITEM:ShouldRestoreBodygroups() + return true +end + +function ITEM:ShouldRestoreSubMaterials() + return true +end diff --git a/garrysmod/gamemodes/helix/gamemode/items/base/sh_pacoutfit.lua b/garrysmod/gamemodes/helix/gamemode/items/base/sh_pacoutfit.lua new file mode 100644 index 0000000..8ebbe15 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/base/sh_pacoutfit.lua @@ -0,0 +1,171 @@ + +ITEM.name = "PAC Outfit" +ITEM.description = "A PAC Outfit Base." +ITEM.category = "Outfit" +ITEM.model = "models/Gibs/HGIBS.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.outfitCategory = "hat" +ITEM.pacData = {} + +--[[ +ITEM.pacData = { + [1] = { + ["children"] = { + [1] = { + ["children"] = { + }, + ["self"] = { + ["Angles"] = Angle(12.919322967529, 6.5696062847564e-006, -1.0949343050015e-005), + ["Position"] = Vector(-2.099609375, 0.019973754882813, 1.0180969238281), + ["UniqueID"] = "4249811628", + ["Size"] = 1.25, + ["Bone"] = "eyes", + ["Model"] = "models/Gibs/HGIBS.mdl", + ["ClassName"] = "model", + }, + }, + }, + ["self"] = { + ["ClassName"] = "group", + ["UniqueID"] = "907159817", + ["EditorExpand"] = true, + }, + }, +} + +-- This will change a player's skin after changing the model. Keep in mind it starts at 0. +ITEM.newSkin = 1 +-- This will change a certain part of the model. +ITEM.replacements = {"group01", "group02"} +-- This will change the player's model completely. +ITEM.replacements = "models/manhack.mdl" +-- This will have multiple replacements. +ITEM.replacements = { + {"male", "female"}, + {"group01", "group02"} +} + +-- This will apply body groups. +ITEM.bodyGroups = { + ["blade"] = 1, + ["bladeblur"] = 1 +} + +--]] + +-- Inventory drawing +if (CLIENT) then + -- Draw camo if it is available. + function ITEM:PaintOver(item, w, h) + if (item:GetData("equip")) then + surface.SetDrawColor(110, 255, 110, 100) + surface.DrawRect(w - 14, h - 14, 8, 8) + end + end +end + +function ITEM:RemovePart(client) + local char = client:GetCharacter() + + self:SetData("equip", false) + client:RemovePart(self.uniqueID) + + if (self.attribBoosts) then + for k, _ in pairs(self.attribBoosts) do + char:RemoveBoost(self.uniqueID, k) + end + end + + self:OnUnequipped() +end + +-- On item is dropped, Remove a weapon from the player and keep the ammo in the item. +ITEM:Hook("drop", function(item) + if (item:GetData("equip")) then + item:RemovePart(item:GetOwner()) + end +end) + +-- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item. +ITEM.functions.EquipUn = { -- sorry, for name order. + name = "unequip", + tip = "unequipTip", + icon = "icon16/cross.png", + OnRun = function(item) + item:RemovePart(item.player) + + return false + end, + OnCanRun = function(item) + local client = item.player + + return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and + hook.Run("CanPlayerUnequipItem", client, item) != false + end +} + +-- On player eqipped the item, Gives a weapon to player and load the ammo data from the item. +ITEM.functions.Equip = { + name = "equip", + tip = "equipTip", + icon = "icon16/tick.png", + OnRun = function(item) + local char = item.player:GetCharacter() + + for k, _ in char:GetInventory():Iter() do + if (k.id != item.id) then + local itemTable = ix.item.instances[k.id] + + if (itemTable.pacData and k.outfitCategory == item.outfitCategory and itemTable:GetData("equip")) then + item.player:NotifyLocalized(item.equippedNotify or "outfitAlreadyEquipped") + + return false + end + end + end + + item:SetData("equip", true) + item.player:AddPart(item.uniqueID, item) + + if (item.attribBoosts) then + for k, v in pairs(item.attribBoosts) do + char:AddBoost(item.uniqueID, k, v) + end + end + + item:OnEquipped() + return false + end, + OnCanRun = function(item) + local client = item.player + + return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and + hook.Run("CanPlayerEquipItem", client, item) != false + end +} + +function ITEM:CanTransfer(oldInventory, newInventory) + if (newInventory and self:GetData("equip")) then + return false + end + + return true +end + +function ITEM:OnRemoved() + local inventory = ix.item.inventories[self.invID] + local owner = inventory.GetOwner and inventory:GetOwner() + + if (IsValid(owner) and owner:IsPlayer()) then + if (self:GetData("equip")) then + self:RemovePart(owner) + end + end +end + +function ITEM:OnEquipped() +end + +function ITEM:OnUnequipped() +end diff --git a/garrysmod/gamemodes/helix/gamemode/items/base/sh_weapons.lua b/garrysmod/gamemodes/helix/gamemode/items/base/sh_weapons.lua new file mode 100644 index 0000000..22f76dc --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/base/sh_weapons.lua @@ -0,0 +1,316 @@ + +ITEM.name = "Weapon" +ITEM.description = "A Weapon." +ITEM.category = "Weapons" +ITEM.model = "models/weapons/w_pistol.mdl" +ITEM.class = "weapon_pistol" +ITEM.width = 2 +ITEM.height = 2 +ITEM.isWeapon = true +ITEM.isGrenade = false +ITEM.weaponCategory = "sidearm" +ITEM.useSound = "items/ammo_pickup.wav" + +-- Inventory drawing +if (CLIENT) then + function ITEM:PaintOver(item, w, h) + if (item:GetData("equip")) then + surface.SetDrawColor(110, 255, 110, 100) + surface.DrawRect(w - 14, h - 14, 8, 8) + end + end + + function ITEM:PopulateTooltip(tooltip) + if (self:GetData("equip")) then + local name = tooltip:GetRow("name") + name:SetBackgroundColor(derma.GetColor("Success", tooltip)) + end + end +end + +-- On item is dropped, Remove a weapon from the player and keep the ammo in the item. +ITEM:Hook("drop", function(item) + local inventory = ix.item.inventories[item.invID] + + if (!inventory) then + return + end + + -- the item could have been dropped by someone else (i.e someone searching this player), so we find the real owner + local owner + + for client, character in ix.util.GetCharacters() do + if (character:GetID() == inventory.owner) then + owner = client + break + end + end + + if (!IsValid(owner)) then + return + end + + if (item:GetData("equip")) then + item:SetData("equip", nil) + + owner.carryWeapons = owner.carryWeapons or {} + + local weapon = owner.carryWeapons[item.weaponCategory] + + if (!IsValid(weapon)) then + weapon = owner:GetWeapon(item.class) + end + + if (IsValid(weapon)) then + item:SetData("ammo", weapon:Clip1()) + + owner:StripWeapon(item.class) + owner.carryWeapons[item.weaponCategory] = nil + owner:EmitSound(item.useSound, 80) + end + + item:RemovePAC(owner) + end +end) + +-- On player uneqipped the item, Removes a weapon from the player and keep the ammo in the item. +ITEM.functions.EquipUn = { -- sorry, for name order. + name = "unequip", + tip = "unequipTip", + icon = "icon16/cross.png", + OnRun = function(item) + item:Unequip(item.player, true) + return false + end, + OnCanRun = function(item) + local client = item.player + + return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") == true and + hook.Run("CanPlayerUnequipItem", client, item) != false + end +} + +-- On player eqipped the item, Gives a weapon to player and load the ammo data from the item. +ITEM.functions.Equip = { + name = "equip", + tip = "equipTip", + icon = "icon16/tick.png", + OnRun = function(item) + item:Equip(item.player) + return false + end, + OnCanRun = function(item) + local client = item.player + + return !IsValid(item.entity) and IsValid(client) and item:GetData("equip") != true and + hook.Run("CanPlayerEquipItem", client, item) != false + end +} + +function ITEM:WearPAC(client) + if (ix.pac and self.pacData) then + client:AddPart(self.uniqueID, self) + end +end + +function ITEM:RemovePAC(client) + if (ix.pac and self.pacData) then + client:RemovePart(self.uniqueID) + end +end + +function ITEM:Equip(client, bNoSelect, bNoSound) + client.carryWeapons = client.carryWeapons or {} + + for k, _ in client:GetCharacter():GetInventory():Iter() do + if (k.id != self.id) then + local itemTable = ix.item.instances[k.id] + + if (!itemTable) then + client:NotifyLocalized("tellAdmin", "wid!xt") + + return false + else + if (itemTable.isWeapon and client.carryWeapons[self.weaponCategory] and itemTable:GetData("equip")) then + client:NotifyLocalized("weaponSlotFilled", self.weaponCategory) + + return false + end + end + end + end + + if (client:HasWeapon(self.class)) then + client:StripWeapon(self.class) + end + + local weapon = client:Give(self.class, !self.isGrenade) + + if (IsValid(weapon)) then + local ammoType = weapon:GetPrimaryAmmoType() + + client.carryWeapons[self.weaponCategory] = weapon + + if (!bNoSelect) then + client:SelectWeapon(weapon:GetClass()) + end + + if (!bNoSound) then + client:EmitSound(self.useSound, 80) + end + + -- Remove default given ammo. + if (client:GetAmmoCount(ammoType) == weapon:Clip1() and self:GetData("ammo", 0) == 0) then + client:RemoveAmmo(weapon:Clip1(), ammoType) + end + + -- assume that a weapon with -1 clip1 and clip2 would be a throwable (i.e hl2 grenade) + -- TODO: figure out if this interferes with any other weapons + if (weapon:GetMaxClip1() == -1 and weapon:GetMaxClip2() == -1 and client:GetAmmoCount(ammoType) == 0) then + client:SetAmmo(1, ammoType) + end + + self:SetData("equip", true) + + if (self.isGrenade) then + weapon:SetClip1(1) + client:SetAmmo(0, ammoType) + else + weapon:SetClip1(self:GetData("ammo", 0)) + end + + weapon.ixItem = self + + if (self.OnEquipWeapon) then + self:OnEquipWeapon(client, weapon) + end + else + print(Format("[Helix] Cannot equip weapon - %s does not exist!", self.class)) + end +end + +function ITEM:Unequip(client, bPlaySound, bRemoveItem) + client.carryWeapons = client.carryWeapons or {} + + local weapon = client.carryWeapons[self.weaponCategory] + + if (!IsValid(weapon)) then + weapon = client:GetWeapon(self.class) + end + + if (IsValid(weapon)) then + weapon.ixItem = nil + + self:SetData("ammo", weapon:Clip1()) + client:StripWeapon(self.class) + else + print(Format("[Helix] Cannot unequip weapon - %s does not exist!", self.class)) + end + + if (bPlaySound) then + client:EmitSound(self.useSound, 80) + end + + client.carryWeapons[self.weaponCategory] = nil + self:SetData("equip", nil) + self:RemovePAC(client) + + if (self.OnUnequipWeapon) then + self:OnUnequipWeapon(client, weapon) + end + + if (bRemoveItem) then + self:Remove() + end +end + +function ITEM:CanTransfer(oldInventory, newInventory) + if (newInventory and self:GetData("equip")) then + local owner = self:GetOwner() + + if (IsValid(owner)) then + owner:NotifyLocalized("equippedWeapon") + end + + return false + end + + return true +end + +function ITEM:OnLoadout() + if (self:GetData("equip")) then + local client = self.player + client.carryWeapons = client.carryWeapons or {} + + local weapon = client:Give(self.class, true) + + if (IsValid(weapon)) then + client:RemoveAmmo(weapon:Clip1(), weapon:GetPrimaryAmmoType()) + client.carryWeapons[self.weaponCategory] = weapon + + weapon.ixItem = self + weapon:SetClip1(self:GetData("ammo", 0)) + + if (self.OnEquipWeapon) then + self:OnEquipWeapon(client, weapon) + end + else + print(Format("[Helix] Cannot give weapon - %s does not exist!", self.class)) + end + end +end + +function ITEM:OnSave() + local weapon = self.player:GetWeapon(self.class) + + if (IsValid(weapon) and weapon.ixItem == self and self:GetData("equip")) then + self:SetData("ammo", weapon:Clip1()) + end +end + +function ITEM:OnRemoved() + local inventory = ix.item.inventories[self.invID] + local owner = inventory.GetOwner and inventory:GetOwner() + + if (IsValid(owner) and owner:IsPlayer()) then + local weapon = owner:GetWeapon(self.class) + + if (IsValid(weapon)) then + weapon:Remove() + end + + self:RemovePAC(owner) + end +end + +hook.Add("PlayerDeath", "ixStripClip", function(client) + client.carryWeapons = {} + + for k, _ in client:GetCharacter():GetInventory():Iter() do + if (k.isWeapon and k:GetData("equip")) then + k:SetData("ammo", nil) + k:SetData("equip", nil) + + if (k.pacData) then + k:RemovePAC(client) + end + end + end +end) + +hook.Add("EntityRemoved", "ixRemoveGrenade", function(entity) + -- hack to remove hl2 grenades after they've all been thrown + if (entity:GetClass() == "weapon_frag") then + local client = entity:GetOwner() + + if (IsValid(client) and client:IsPlayer() and client:GetCharacter()) then + local ammoName = game.GetAmmoName(entity:GetPrimaryAmmoType()) + + if (isstring(ammoName) and ammoName:lower() == "grenade" and client:GetAmmoCount(ammoName) < 1 + and entity.ixItem and entity.ixItem.Unequip) then + entity.ixItem:Unequip(client, false, true) + end + end + end +end) diff --git a/garrysmod/gamemodes/helix/gamemode/items/pacoutfit/sh_skullmask.txt b/garrysmod/gamemodes/helix/gamemode/items/pacoutfit/sh_skullmask.txt new file mode 100644 index 0000000..9cc2a89 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/pacoutfit/sh_skullmask.txt @@ -0,0 +1,30 @@ +ITEM.name = "Skull Mask" +ITEM.description = "It's a skull mask." +ITEM.model = "models/Gibs/HGIBS.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.outfitCategory = "hat" +ITEM.pacData = { + [1] = { + ["children"] = { + [1] = { + ["children"] = { + }, + ["self"] = { + ["Angles"] = Angle(12.919322967529, 6.5696062847564e-006, -1.0949343050015e-005), + ["Position"] = Vector(-2.099609375, 0.019973754882813, 1.3180969238281), + ["UniqueID"] = "4249811628", + ["Size"] = 1.25, + ["Bone"] = "eyes", + ["Model"] = "models/Gibs/HGIBS.mdl", + ["ClassName"] = "model", + }, + }, + }, + ["self"] = { + ["ClassName"] = "group", + ["UniqueID"] = "907159817", + ["EditorExpand"] = true, + }, + }, +} \ No newline at end of file diff --git a/garrysmod/gamemodes/helix/gamemode/items/sh_defaultitem.txt b/garrysmod/gamemodes/helix/gamemode/items/sh_defaultitem.txt new file mode 100644 index 0000000..71f0156 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/sh_defaultitem.txt @@ -0,0 +1,3 @@ +ITEM.name = "Test Item" +ITEM.description = "A test item!" +ITEM.model = "models/props_c17/oildrum001.mdl" \ No newline at end of file diff --git a/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_357.txt b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_357.txt new file mode 100644 index 0000000..3a37fec --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_357.txt @@ -0,0 +1,12 @@ +ITEM.name = "357" +ITEM.description = "A sidearm utilising .357 Caliber ammunition." +ITEM.model = "models/weapons/w_357.mdl" +ITEM.class = "weapon_357" +ITEM.weaponCategory = "sidearm" +ITEM.width = 2 +ITEM.height = 1 +ITEM.iconCam = { + ang = Angle(-17.581502914429, 250.7974395752, 0), + fov = 5.412494001838, + pos = Vector(57.109928131104, 181.7945098877, -60.738327026367) +} diff --git a/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_ar2.txt b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_ar2.txt new file mode 100644 index 0000000..fb30f09 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_ar2.txt @@ -0,0 +1,12 @@ +ITEM.name = "AR2" +ITEM.description = "A Weapon." +ITEM.model = "models/weapons/w_IRifle.mdl" +ITEM.class = "weapon_ar2" +ITEM.weaponCategory = "primary" +ITEM.width = 4 +ITEM.height = 2 +ITEM.iconCam = { + ang = Angle(-0.70499622821808, 268.25439453125, 0), + fov = 12.085652091515, + pos = Vector(0, 200, 0) +} \ No newline at end of file diff --git a/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_crowbar.txt b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_crowbar.txt new file mode 100644 index 0000000..48ce0cf --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_crowbar.txt @@ -0,0 +1,12 @@ +ITEM.name = "Crowbar" +ITEM.description = "A slightly rusty looking crowbar." +ITEM.model = "models/weapons/w_crowbar.mdl" +ITEM.class = "weapon_crowbar" +ITEM.weaponCategory = "melee" +ITEM.width = 2 +ITEM.height = 1 +ITEM.iconCam = { + ang = Angle(-0.23955784738064, 270.44906616211, 0), + fov = 10.780103254469, + pos = Vector(0, 200, 0) +} diff --git a/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_pistol.txt b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_pistol.txt new file mode 100644 index 0000000..d78effb --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_pistol.txt @@ -0,0 +1,12 @@ +ITEM.name = "9MM Pistol" +ITEM.description = "A sidearm utilising 9mm Ammunition." +ITEM.model = "models/weapons/w_pistol.mdl" +ITEM.class = "weapon_pistol" +ITEM.weaponCategory = "sidearm" +ITEM.width = 2 +ITEM.height = 1 +ITEM.iconCam = { + ang = Angle(0.33879372477531, 270.15808105469, 0), + fov = 5.0470897275697, + pos = Vector(0, 200, -1) +} diff --git a/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_smg1.txt b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_smg1.txt new file mode 100644 index 0000000..91783c1 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/items/weapons/sh_smg1.txt @@ -0,0 +1,12 @@ +ITEM.name = "Sub Machine Gun" +ITEM.description = "A Weapon." +ITEM.model = "models/weapons/w_smg1.mdl" +ITEM.class = "weapon_smg1" +ITEM.weaponCategory = "primary" +ITEM.width = 3 +ITEM.height = 2 +ITEM.iconCam = { + ang = Angle(-0.020070368424058, 270.40155029297, 0), + fov = 7.2253324508038, + pos = Vector(0, 200, -1) +} \ No newline at end of file diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_dutch.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_dutch.lua new file mode 100644 index 0000000..7837f32 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_dutch.lua @@ -0,0 +1,156 @@ + +-- DUTCH TRANSLATION - http://steamcommunity.com/profiles/76561198084653538/ +NAME = "Nederlands" + +LANGUAGE = { + loading = "Laden", + dbError = "Database verbinding gefaald", + unknown = "Onbekend", + noDesc = "Geen beschrijving beschikbaar", + create = "Creëer", + createTip = "Maak een nieuw karakter aan om mee te spelen.", + load = "Laad", + loadTip = "Kies een eerder gemaakte karakter om mee te spelen.", + leave = "Verlaat", + leaveTip = "Verlaat de huidige server.", + ["return"] = "Terug", + returnTip = "Ga terug naar het vorige menu.", + name = "Naam", + description = "Beschrijving", + model = "Model", + attributes = "Attributen", + charCreateTip = "Vul de velden hieronder in en druk op 'Voltooi' om je karakter te maken.", + invalid = "Je hebt een ongeldige %s voorzien", + descMinLen = "Je beschrijving moet ten minste %d tekens zijn.", + player = "Speler", + finish = "Voltooi", + finishTip = "Voltooi het maken van jouw karakter.", + needModel = "Je moet een geldig 'Model' kiezen.", + creating = "Je karakter wordt gemaakt...", + unknownError = "Er is een onbekende fout opgetreden", + delConfirm = "Weet je zeker dat je %s wilt verwijderen?", + no = "Nee", + yes = "Ja", + itemInfo = "Naam: %s\nBeschrijving: %s", + cloud_no_repo = "Deze opslaagplaats is niet geldig.", + cloud_no_plugin = "Deze plugin is niet geldig.", + inv = "Inventaris", + plugins = "Plugins", + author = "Auteur", + version = "Versie", + characters = "Karakters", + business = "Bedrijf", + settings = "Instellingen", + config = "Config", + chat = "Chat", + appearance = "Uiterlijk", + misc = "Gemengd", + oocDelay = "Je moet %s seconde(n) wachten voordat je weer in OOC kan praten.", + loocDelay = "Je moet %s seconde(n) wachten voordat je weer in LOOC kan praten.", + usingChar = "Je bent dit karakter al aan het gebruiken.", + itemNoExist = "Sorry, het item dat je hebt opgevraagd bestaat niet.", + cmdNoExist = "Sorry, dat commando bestaat niet.", + plyNoExist = "Sorry, Er is geen speler gevonden met die naam.", + cfgSet = "%s heeft \"%s\" gezet tot %s.", + drop = "Vallen", + dropTip = "Dit laat deze item(s) vallen uit je inventaris.", + take = "Nemen", + takeTip = "Dit laat deze item(s) oppakken en het in je inventaris doen.", + dTitle = "Unowned Door", + dTitleOwned = "Purchased Door", + dIsNotOwnable = "Deze deur kan niet gekocht worden.", + dIsOwnable = "Je kan deze deur kopen door op 'F2' te drukken.", + dMadeUnownable = "Je hebt deze deur niet verkoopbaar gemaakt.", + dMadeOwnable = "Je hebt deze deur verkoopbaar gemaakt.", + dNotAllowedToOwn = "Je mag deze deur niet bezitten.", + dSetDisabled = "Je hebt deze deur uitgeschakeld.", + dSetNotDisabled = "Je hebt deze deur ingeschakeld.", + dSetParentDoor = "Je hebt deze deur als je 'Parent' deur gezet. ", + dCanNotSetAsChild = "Je kan deze 'Parent' deur niet als een 'Child' deur zetten.", + dAddChildDoor = "Je hebt deze deur als een 'Child' toegevoegd.", + dRemoveChildren = "Je hebt alle 'Childs' van deze deur verwijderd.", + dRemoveChildDoor = "Je hebt de 'Child' status van deze deur verwijderd.", + dNoParentDoor = "Je hebt nog geen 'Parent' deur ingeschakeld.", + dOwnedBy = "%s is de eigenaar van deze deur.", + dConfigName = "Deuren", + dSetFaction = "Deze deur behoort nu toe aan de %s 'faction'.", + dRemoveFaction = "Deze deur behoort niet meer toe aan een 'faction'.", + dNotValid = "Je kijkt niet naar een geldige deur.", + canNotAfford = "Je kunt het je niet veroorloven om dit te kopen.", + dPurchased = "Je hebt deze deur gekocht voor %s.", + dSold = "Je hebt deze deur verkocht voor %s.", + notOwner = "Je bent niet de eigenaar van dit.", + invalidArg = "Je hebt een ongeldige waarde voor argument #%s.", + flagGive = "%s heeft %s '%s' flags gegeven.", + flagTake = "%s heeft '%s' flags van %s genomen.", + flagNoMatch = "Je moet (de) \"%s\" Flag(s) hebben om dit te doen", + textAdded = "Je hebt een tekst toegevoegd.", + textRemoved = "Je hebt de %s tekst(en) verwijderd.", + moneyTaken = "Je hebt %s gevonden.", + businessPurchase = "Je hebt %s gekocht voor %s.", + businessSell = "Je verkocht %s voor %s.", + cChangeModel = "%s veranderde %s's model naar %s.", + cChangeName = "%s veranderde %s's naam naar %s.", + playerCharBelonging = "Dit object behoort aan je andere karakter toe.", + invalidFaction = "Je hebt een ongeldige 'faction' voorzien", + spawnAdd = "Je hebt een spawn toegevoegd voor de %s 'faction'.", + spawnDeleted = "Je hebt (een) %s spawn punt(en) verwijderd.", + someone = "Iemand", + rgnLookingAt = "Toestaan dat de persoon waar je naar kijkt je herkent.", + rgnWhisper = "Toestaan dat degene(n) in het fluister gebied je herkent.", + rgnTalk = "Toestaan dat de personen in het praat gebied je herkennen.", + rgnYell = "Toestaan dat de personen in het schreeuw gebied je herkennen.", + icFormat = "%s zegt \"%s\"", + rollFormat = "%s heeft %s uitgerold.", + wFormat = "%s fluistert \"%s\"", + yFormat = "%s schreeuwt \"%s\"", + sbOptions = "Klik om opties over %s te zien.", + spawnAdded = "Je hebt een spawn toegevoegd voor %s.", + whitelist = "%s heeft %s gewhitelist voor de %s 'faction'.", + unwhitelist = "%s heeft %s geblacklist van de %s 'faction'.", + gettingUp = "Je staat nu op...", + wakingUp = "Je krijgt je bewustzijn terug...", + Weapons = "Wapens", + checkout = "Ga naar checkout (%s)", + purchase = "Koop", + purchasing = "Aan het kopen...", + success = "Succes", + buyFailed = "De koop is gefaald!", + buyGood = "Succesvol gekocht!", + shipment = "Lading", + shipmentDesc = "Deze lading behoort toe aan %s.", + class = "Class", + classes = "Classes", + illegalAccess = "Illegale toegang", + becomeClassFail = "Het is niet gelukt om %s te worden.", + becomeClass = "Je bent %s geworden.", + attributeSet = "You set %s's %s to %s.", + attributeUpdate = "You added %s's %s by %s.", + noFit = "Deze item(s) passen niet in je inventaris", + help = "Hulp", + commands = "Commando's", + helpDefault = "Selecteer een categorie", + doorSettings = "Deur Instellingen", + sell = "Verkoop", + access = "Toegang", + locking = "Bezig deze 'Entity' opslot te zetten ...", + unlocking = "Bezig deze 'Entity' van het slot te zetten ...", + modelNoSeq = "Je model ondersteunt deze 'Act' niet.", + notNow = "Je kan dat nu niet doen.", + faceWall = "Je moet de muur aankijken om dit te doen.", + faceWallBack = "Je moet met je rug tegen de muur staan om dit te doen", + descChanged = "Je hebt je karakter's beschrijving veranderd.", + charMoney = "Je hebt momenteel %s.", + charFaction = "Je bent een lid van de %s 'faction'.", + charClass = "Je bent %s van de 'faction'.", + noOwner = "De eigenaar is niet geldig.", + notAllowed = "Dit is niet toegestaan.", + invalidIndex = "De Item Index is Ongeldig.", + invalidItem = "De Item Object is Ongeldig.", + invalidInventory = "Het inventaris object is ongeldig.", + home = "Home", + charKick = "%s kickde karakter %s.", + charBan = "%s heeft karakter %s verbannen.", + charBanned = "Deze karakter is verbannen.", + setMoney = "Je hebt %s's geld tot %s gezet." +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_english.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_english.lua new file mode 100644 index 0000000..3f26943 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_english.lua @@ -0,0 +1,463 @@ +NAME = "English" + +LANGUAGE = { + helix = "Helix", + + introTextOne = "fist industries presents", + introTextTwo = "in collaboration with %s", + introContinue = "press space to continue", + + helpIdle = "Select a category", + helpCommands = "Command parameters with are required, while [brackets] are optional.", + helpFlags = "Flags with a green background are accessible by this character.", + + creditSpecial = "Special Thanks", + creditLeadDeveloper = "Lead Developer", + creditUIDesigner = "UI Designer", + creditManager = "Project Manager", + creditTester = "Lead Tester", + + chatTyping = "Typing...", + chatTalking = "Talking...", + chatYelling = "Yelling...", + chatWhispering = "Whispering...", + chatPerforming = "Performing...", + chatNewTab = "New tab...", + chatReset = "Reset position", + chatResetTabs = "Reset tabs", + chatCustomize = "Customize...", + chatCloseTab = "Close Tab", + chatTabName = "Tab Name", + chatNewTabTitle = "New Tab", + chatAllowedClasses = "Allowed Chat Classes", + chatTabExists = "A chat tab with that name already exists!", + chatMarkRead = "Mark all as read", + + community = "Community", + checkAll = "Check All", + uncheckAll = "Uncheck All", + color = "Color", + type = "Type", + display = "Display", + loading = "Loading", + dbError = "Database connection failed", + unknown = "Unknown", + noDesc = "No description available", + create = "Create", + update = "Update", + load = "Load character", + loadTitle = "Load a Character", + leave = "Leave server", + leaveTip = "Leave the current server.", + ["return"] = "Return", + returnTip = "Return to the previous menu.", + proceed = "Proceed", + faction = "Faction", + skills = "Skills", + choose = "Choose", + chooseFaction = "Choose a Faction", + chooseDescription = "Define your Narrative", + chooseSkills = "Hone your Skills", + name = "Name", + description = "Description", + model = "Model", + attributes = "Attributes", + attribPointsLeft = "Points left", + charInfo = "Character Information", + charCreated = "You have successfully created your character.", + charCreateTip = "Fill in the fields below and press 'Finish' to create your character.", + invalid = "You have provided an invalid %s!", + nameMinLen = "Your name must be at least %d characters!", + nameMaxLen = "Your name must not be more than %d characters!", + descMinLen = "Your description must be at least %d characters!", + maxCharacters = "You cannot create any more characters!", + player = "Player", + finish = "Finish", + finishTip = "Finish creating the character.", + needModel = "You need to choose a valid model!", + creating = "Your character is being created...", + unknownError = "An unknown error has occured!", + areYouSure = "Are you sure?", + delete = "Delete", + deleteConfirm = "This character will be irrevocably removed!", + deleteComplete = "%s has been deleted.", + no = "No", + yes = "Yes", + close = "Close", + save = "Save", + itemInfo = "Name: %s\nDescription: %s", + itemCreated = "Item(s) successfully created.", + cloud_no_repo = "The repository provided is not valid!", + cloud_no_plugin = "The plugin provided is not valid!", + inv = "Inventory", + plugins = "Plugins", + pluginLoaded = "%s has enabled the \"%s\" plugin to load next map change.", + pluginUnloaded = "%s has disabled the \"%s\" plugin from loading next map change.", + loadedPlugins = "Loaded Plugins", + unloadedPlugins = "Unloaded Plugins", + on = "On", + off = "Off", + author = "Author", + version = "Version", + characters = "Characters", + business = "Business", + settings = "Settings", + config = "Config", + chat = "Chat", + appearance = "Appearance", + misc = "Miscellaneous", + oocDelay = "You must wait %s more seconds before using OOC again!", + loocDelay = "You must wait %s more seconds before using LOOC again!", + usingChar = "You are already using this character.", + notAllowed = "You are not allowed to do this!", + itemNoExist = "The item that you requested does not exist!", + cmdNoExist = "That command does not exist!", + charNoExist = "A matching character could not be found!", + plyNoExist = "A matching player could not be found!", + cfgSet = "%s has set \"%s\" to %s.", + drop = "Drop", + dropTip = "Drops this item from your inventory.", + take = "Take", + takeTip = "Take this item and place it in your inventory.", + dTitle = "Unowned Door", + dTitleOwned = "Purchased Door", + dIsNotOwnable = "This door is unownable.", + dIsOwnable = "You can purchase this door by pressing F2.", + dMadeUnownable = "You have made this door unownable.", + dMadeOwnable = "You have made this door ownable.", + dNotAllowedToOwn = "You are not allowed to own this door!", + dSetDisabled = "You have made this door disabled.", + dSetNotDisabled = "You have made this door no longer disabled.", + dSetHidden = "You have made this door hidden.", + dSetNotHidden = "You have made this door no longer hidden.", + dSetParentDoor = "You have set this door as your parent door.", + dCanNotSetAsChild = "You can not set the parent door as a child!", + dAddChildDoor = "You have added a this door as a child.", + dRemoveChildren = "You have removed all of the children for this door.", + dRemoveChildDoor = "You have removed this door from being a child.", + dNoParentDoor = "You do not have a parent door set.", + dOwnedBy = "This door is owned by %s.", + dConfigName = "Doors", + dSetFaction = "This door now belongs to the %s faction.", + dRemoveFaction = "This door no longer belongs to any faction.", + dNotValid = "You are not looking at a valid door!", + canNotAfford = "You can not afford to purchase this!", + dPurchased = "You have purchased this door for %s.", + dSold = "You have sold this door for %s.", + notOwner = "You are not the owner of this!", + invalidArg = "You have provided an invalid value for argument #%s!", + invalidFaction = "The faction you provided could not be found!", + flagGive = "%s has given %s '%s' flags.", + flagGiveTitle = "Give Flags", + flagTake = "%s has taken '%s' flags from %s.", + flagTakeTitle = "Take Flags", + flagNoMatch = "You must have \"%s\" flag(s) to do this action!", + panelAdded = "You have added a panel.", + panelRemoved = "You have removed %d panel(s).", + textAdded = "You have added a text.", + textRemoved = "You have removed %s text(s).", + moneyTaken = "You were given %s.", + moneyGiven = "You have given %s.", + insufficientMoney = "You do not have enough to do this!", + belowMinMoneyDrop = "You cannot drop less than %s.", + businessPurchase = "You purchased %s for %s.", + businessSell = "You sold %s for %s.", + businessTooFast = "Please wait before purchasing another item!", + cChangeModel = "%s changed %s's model to %s.", + cChangeName = "%s changed %s's name to %s.", + cChangeSkin = "%s changed %s's skin to %s.", + cChangeGroups = "%s changed %s's \"%s\" bodygroup to %s.", + cChangeFaction = "%s has transferred %s to the %s faction.", + playerCharBelonging = "This object is your other character's belonging!", + spawnAdd = "You have added a spawn for the %s.", + spawnDeleted = "You have removed %s spawn point(s).", + someone = "Someone", + rgnLookingAt = "Allow the person you are looking at to recognize you.", + rgnWhisper = "Allow those in a whispering range to recognize you.", + rgnTalk = "Allow those in a talking range to recognize you.", + rgnYell = "Allow those in a yelling range to recognize you.", + icFormat = "%s says \"%s\"", + wFormat = "%s whispers \"%s\"", + sbOptions = "Click to see options for %s.", + spawnAdded = "You added spawn for %s.", + whitelist = "%s has whitelisted %s for the %s faction.", + unwhitelist = "%s has unwhitelisted %s from the %s faction.", + noWhitelist = "You do not have the whitelist for this character!", + charNotWhitelisted = "%s is not whitelisted for the %s faction.", + gettingUp = "You are now getting up...", + wakingUp = "You are regaining consciousness...", + Weapons = "Weapons", + checkout = "Go to Checkout (%s)", + purchase = "Purchase", + purchasing = "Purchasing...", + success = "Success", + buyFailed = "Purchase failed!", + buyGood = "Purchase successful!", + shipment = "Shipment", + shipmentDesc = "This shipment belongs to %s.", + class = "Class", + classes = "Classes", + illegalAccess = "Illegal Access.", + becomeClassFail = "You cannot become a %s!", + becomeClass = "You have become a %s.", + setClass = "You have set %s's class to %s.", + attributeSet = "You set %s's %s to %s.", + attributeNotFound = "You have specified an invalid attribute!", + attributeUpdate = "You added %s's %s by %s.", + noFit = "You do not have enough space for this item!", + itemOwned = "You cannot interact with an item that you own on a different character!", + help = "Help", + commands = "Commands", + doorSettings = "Door Settings", + sell = "Sell", + access = "Access", + locking = "Locking this entity...", + unlocking = "Unlocking this entity...", + modelNoSeq = "Your model does not support this act!", + notNow = "You are not allowed to do this right now!", + faceWall = "You must be facing the wall to do this!", + faceWallBack = "Your back must be facing the wall to do this!", + descChanged = "You have changed your character's description.", + noOwner = "The owner is invalid!", + invalidItem = "You have specified an invalid item!", + invalidInventory = "You have specified an invalid inventory!", + home = "Home", + charKick = "%s kicked char %s.", + charBan = "%s banned the character %s.", + charBanned = "This character is banned.", + charBannedTemp = "This character is temporarily banned.", + playerConnected = "%s has connected to the server.", + playerDisconnected = "%s has disconnected from the server.", + setMoney = "You have set %s's money to %s.", + itemPriceInfo = "You can purchase this item for %s.\nYou can sell this item for %s", + free = "Free", + vendorNoSellItems = "There are no items to sell.", + vendorNoBuyItems = "There are no items to purchase.", + vendorSettings = "Vendor Settings", + vendorUseMoney = "Vendor should use money?", + vendorNoBubble = "Hide vendor bubble?", + category = "Category", + mode = "Mode", + price = "Price", + stock = "Stock", + none = "None", + vendorBoth = "Buy and Sell", + vendorBuy = "Buy Only", + vendorSell = "Sell Only", + maxStock = "Max Stock", + vendorFaction = "Faction Editor", + buy = "Purchase", + vendorWelcome = "Welcome to my store, what can I get you today?", + vendorBye = "Come again soon!", + charSearching = "You are already searching another character!", + charUnBan = "%s has unbanned the character %s.", + charNotBanned = "This character is not banned!", + quickSettings = "Quick Settings", + vmSet = "You have set your voicemail.", + vmRem = "You have removed your voicemail.", + noPerm = "You are not allowed to do this!", + youreDead = "You are Dead", + injMajor = "Seems critically injured", + injLittle = "Seems injured", + chgName = "Change Name", + chgNameDesc = "Enter the character's new name below.", + weaponSlotFilled = "You cannot use another %s weapon!", + equippedBag = "You cannot move a bag that has an equipped item!", + equippedWeapon = "You cannot move a weapon that is currently equipped!", + nestedBags = "You cannot put an inventory inside of a storage inventory!", + outfitAlreadyEquipped = "You're already wearing this kind of outfit!", + useTip = "Uses the item.", + equip = "Equip", + equipTip = "Equips the item.", + unequip = "Unequip", + unequipTip = "Unequips the item.", + consumables = "Consumables", + plyNotValid = "You are not looking at a valid player!", + restricted = "You have been restrained.", + salary = "You have received %s from your salary.", + noRecog = "You do not recognize this person.", + curTime = "The current time is %s.", + vendorEditor = "Vendor Editor", + edit = "Edit", + disable = "Disable", + vendorPriceReq = "Enter the new price for this item.", + vendorEditCurStock = "Edit Current Stock", + vendorStockReq = "Enter the Maximum amount of Stock the item should have.", + vendorStockCurReq = "Enter how many items are available for purchase from the maximum stock.", + you = "You", + vendorSellScale = "Sell price scale", + vendorNoTrade = "You are not able to trade with this vendor!", + vendorNoMoney = "This vendor can not afford that item!", + vendorNoStock = "This vendor does not have that item in stock!", + vendorMaxStock = "This vendor has full stock of that item!", + contentTitle = "Helix Content Missing", + contentWarning = "You do not have the Helix content mounted. This may result in certain features missing.\nWould you like to open the Workshop page for the Helix content?", + flags = "Flags", + mapRestarting = "The map will restart in %d seconds!", + chooseTip = "Choose this character to play with.", + deleteTip = "Delete this character.", + storageInUse = "Someone else is using this!", + storageSearching = "Searching...", + container = "Container", + containerPassword = "You have set this container's password to %s.", + containerPasswordRemove = "You have removed this container's password.", + containerPasswordWrite = "Enter the password.", + containerName = "You have set this container's name to %s.", + containerNameWrite = "Enter the name.", + containerNameRemove = "You have removed this container's name.", + containerInvalid = "You need to be looking at a container to do this!", + wrongPassword = "You have entered an incorrect password!", + passwordAttemptLimit = "You have made too many incorrect password attempts!", + respawning = "Respawning...", + tellAdmin = "Inform a staff member of this error: %s", + syntax = "Syntax: %s", + + mapAdd = "You have added a map scene.", + mapDel = "You have removed %d map scene(s).", + mapRepeat = "Now add the secondary point.", + + scoreboard = "Scoreboard", + ping = "Ping: %d", + viewProfile = "View Steam Profile", + copySteamID = "Copy Steam ID", + + money = "Money", + moneyLeft = "Your Money: ", + currentMoney = "Money Left: ", + + invalidClass = "That is not a valid class!", + invalidClassFaction = "That is not a valid class for that faction!", + + miscellaneous = "Miscellaneous", + general = "General", + observer = "Observer", + performance = "Performance", + thirdperson = "Third Person", + date = "Date", + interaction = "Interaction", + server = "Server", + + resetDefault = "Reset to default", + resetDefaultDescription = "This will reset \"%s\" to its default value of \"%s\".", + optOpenBags = "Open bags with inventory", + optdOpenBags = "Automatically views all the bags in your inventory when the menu is opened.", + optShowIntro = "Show intro on join", + optdShowIntro = "Shows the Helix introduction the next time you join. This is always disabled after you've watched it.", + optCheapBlur = "Disable blurring", + optdCheapBlur = "Replaces UI blurring with simple dimming.", + optObserverTeleportBack = "Return to previous location", + optdObserverTeleportBack = "Returns you to the location that you entered observer mode.", + optObserverESP = "Show admin ESP", + optdObserverESP = "Shows the names and locations of each player in the server.", + opt24hourTime = "Use 24-hour time", + optd24hourTime = "Show timestamps in 24-hour time, rather than 12-hour time (AM/PM).", + optChatNotices = "Show notices in chat", + optdChatNotices = "Puts all notices that appear in the top-right into the chat instead.", + optChatTimestamps = "Show timestamps in chat", + optdChatTimestamps = "Prepends the time to each message in the chatbox.", + optAlwaysShowBars = "Always show info bars", + optdAlwaysShowBars = "Draws the information bars in the top-left, regardless if it should be hidden or not.", + optAltLower = "Hide hands when lowered", -- @todo remove me + optdAltLower = "Hides your hands when they are by your side.", -- @todo remove me + optThirdpersonEnabled = "Enable third person", + optdThirdpersonEnabled = "Moves the camera behind you. This can also be enabled with the \"ix_togglethirdperson\" console command.", + optThirdpersonClassic = "Enable classic third person camera", + optdThirdpersonClassic = "Moves your character's view with your mouse.", + optThirdpersonVertical = "Camera height", + optdThirdpersonVertical = "How high up the camera should be.", + optThirdpersonHorizontal = "Camera offset", + optdThirdpersonHorizontal = "How far left or right the camera should be.", + optThirdpersonDistance = "Camera distance", + optdThirdpersonDistance = "How far away the camera should be.", + optThirdpersonCrouchOffset = "Camera crouch height", + optdThirdpersonCrouchOffset = "How high up the camera should be while crouched.", + optDisableAnimations = "Disable animations", + optdDisableAnimations = "Stops animations from playing to provide instant transitions.", + optAnimationScale = "Animation scale", + optdAnimationScale = "How much faster or slower the animations should play.", + optLanguage = "Language", + optdLanguage = "The language shown in the Helix UI.", + optMinimalTooltips = "Minimal HUD tooltips", + optdMinimalTooltips = "Changes the HUD tooltip style to take up less space.", + optNoticeDuration = "Notice duration", + optdNoticeDuration = "How long to show notices for (in seconds).", + optNoticeMax = "Maximum notices", + optdNoticeMax = "The amount of notices shown before excess previous ones are removed.", + optChatFontScale = "Chat font scale", + optdChatFontScale = "How much bigger or smaller the chat font should be.", + optChatOutline = "Outline chat text", + optdChatOutline = "Draws an outline around the chat text, rather than a drop shadow. Enable this if you are having trouble reading text.", + optEscCloseMenu = "Escape returns to game", + optdEscCloseMenu = "Whether the escape menu should close itself when you're using it to close the in-game menu.", + + cmdRoll = "Rolls a number between 0 and the specified number.", + cmdPM = "Sends a private message to someone.", + cmdReply = "Sends a private message to the last person you received a message from.", + cmdSetVoicemail = "Sets or removes the auto-reply message when someone sends you a private message.", + cmdCharGiveFlag = "Gives the specified flag(s) to someone.", + cmdCharTakeFlag = "Removes the specified flag(s) from someone if they have it.", + cmdToggleRaise = "Raises or lowers the weapon you're holding.", + cmdCharSetModel = "Sets a person's character model.", + cmdCharSetSkin = "Sets the skin for a person's character model.", + cmdCharSetBodygroup = "Sets the bodygroup for a person's character model.", + cmdCharSetAttribute = "Sets the level of the specified attribute for someone.", + cmdCharAddAttribute = "Adds a level to the specified attribute for someone.", + cmdCharSetName = "Changes someone's name to the specified name.", + cmdCharGiveItem = "Gives the specified item to someone.", + cmdCharKick = "Forcefully makes someone log off of their character.", + cmdCharBan = "Prohibits someone from logging into the server with their current character.", + cmdCharUnban = "Allows a prohibited character the ability to be used again.", + cmdGiveMoney = "Gives a specified amount of money to the person you are looking at.", + cmdCharSetMoney = "Changes the total amount of money of someone to the amount specified.", + cmdDropMoney = "Drops the specified amount of money in a little box in front of you.", + cmdPlyWhitelist = "Allows someone to create a character in the specified faction.", + cmdCharGetUp = "Attempts to stand back up after having fallen over.", + cmdPlyUnwhitelist = "Disallows someone to create a character in the specified faction.", + cmdCharFallOver = "Makes your knees weak and fall over.", + cmdBecomeClass = "Attempts to become part of the specified class in your current faction.", + cmdCharDesc = "Sets your physical description.", + cmdCharDescTitle = "Physical Description", + cmdCharDescDescription = "Enter your character's physical description.", + cmdPlyTransfer = "Transfers someone to the specified faction.", + cmdCharSetClass = "Forcefully makes someone become part of the specified class in their current faction.", + cmdMapRestart = "Restarts the map after the specified amount of time.", + cmdPanelAdd = "Places a web panel in the world.", + cmdPanelRemove = "Removes a web panel that you are looking at.", + cmdTextAdd = "Places a block of text in the world.", + cmdTextRemove = "Removes blocks of text that you are looking at.", + cmdMapSceneAdd = "Adds a cinematic camera viewpoint that is shown in the character menu.", + cmdMapSceneRemove = "Removes a camera viewpoint that is shown in the character menu.", + cmdSpawnAdd = "Adds a spawn point for the specified faction.", + cmdSpawnRemove = "Removes any spawn points you are looking at.", + cmdAct = "Performs the %s animation.", + cmdContainerSetPassword = "Sets the password of the container you're looking at.", + cmdDoorSell = "Sells the door you are looking at.", + cmdDoorBuy = "Buys the door you are looking at.", + cmdDoorSetUnownable = "Makes the door you are looking at unownable.", + cmdDoorSetOwnable = "Makes the door you are looking at ownable.", + cmdDoorSetFaction = "Makes the door you are looking at owned by the specified faction.", + cmdDoorSetDisabled = "Disallows any commands to be ran on the door you are looking at.", + cmdDoorSetTitle = "Sets the title of the door you are looking at.", + cmdDoorSetParent = "Sets the parent of a pair of doors.", + cmdDoorSetChild = "Sets the child of a pair of doors.", + cmdDoorRemoveChild = "Removes the child from a pair of doors.", + cmdDoorSetHidden = "Hides the description of the door you are looking at, but still allows it to be ownable.", + cmdDoorSetClass = "Makes the door you are looking at owned by the specified class.", + cmdMe = "Perform a physical action.", + cmdIt = "Make something around you perform an action.", + cmdW = "Whisper something to the people near you.", + cmdY = "Yell something to the people around you.", + cmdEvent = "Make something perform an action that everyone can see.", + cmdOOC = "Sends a message in global out-of-character chat.", + cmdLOOC = "Sends a message in local out-of-character chat.", + + iconEditorAlignBest = "Best Guess", + iconEditorWidth = "Width", + iconEditorHeight = "Height", + iconEditorCopy = "Copy to Clipboard", + iconEditorCopied = "Copied Item Model Data to Clipboard.", + iconEditorAlignFront = "Align from Front", + iconEditorAlignAbove = "Align from Above", + iconEditorAlignRight = "Align from Right", + iconEditorAlignCenter = "Align from Center" +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_french.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_french.lua new file mode 100644 index 0000000..31a2a57 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_french.lua @@ -0,0 +1,431 @@ + +-- FRENCH TRANSLATION +-- http://steamcommunity.com/id/sadness81 +-- https://steamcommunity.com/id/Azphal + +NAME = "Français" + +LANGUAGE = { + helix = "Helix", + + introTextOne = "fist industrie vous presente", + introTextTwo = "en collaboration avec %s", + introContinue = "Appuyez sur ESPACE pour continuer", + + helpIdle = "Selectionner une catégorie", + helpCommands = "Les paramètres de commande avec les sont obligatoires, tandis que les [crochets] sont facultatifs.", + helpFlags = "Les drapeaux sur fond vert sont accessibles à ce personnage.", + + creditSpecial = "Remerciement spécial", + creditLeadDeveloper = "Développeur principal", + creditUIDesigner = "Concepteur d'interface utilisateur", + creditManager = "Chef de projet", + creditTester = "Testeur en chef", + + chatTyping = "Écrit...", + chatTalking = "Parle...", + chatYelling = "Hurle...", + chatWhispering = "Chuchote...", + chatPerforming = "Fait une action...", + chatNewTab = "Nouvel onglet...", + chatReset = "Réinitialise les discussions", + chatResetTabs = "Réinitialiser les onglets", + chatCustomize = "Personnalise...", + chatCloseTab = "Ferme les onglets", + chatTabName = "Nom de l'onglet", + chatAllowedClasses = "Types de chat acceptés", + chatTabExists = "Un onglet de discussion portant ce nom existe déjà !", + chatMarkRead = "Tout marquer comme lu", + + community = "Communauté", + checkAll = "Cocher tout", + uncheckAll = "Décocher tout", + color = "Couleur", + type = "Type", + display = "Afficher", + loading = "Chargement", + dbError = "La connexion à la base de données a échouée", + unknown = "Inconnu", + noDesc = "Aucune description disponible", + create = "Créer", + update = "Mettre à jour", + load = "Charger le personnage", + loadTitle = "Charger un personnage", + leave = "Quitter le serveur", + leaveTip = "Quitter le serveur actuel.", + ["return"] = "Revenir", + returnTip = "Retourner au menu précédent..", + proceed = "Procéder", + faction = "Faction", + skills = "Compétences", + choose = "Choisir", + chooseFaction = "Choisir une faction", + chooseDescription = "Définissez votre récit", + chooseSkills = "Affûtez vos compétences", + name = "Nom", + description = "Description", + model = "Modèle", + attributes = "Attribut", + attribPointsLeft = "Points restant", + charInfo = "Information du personnage", + charCreated = "Vous avez créé votre personnage avec succès.", + charCreateTip = "Remplissez les champs ci-dessous et appuyez sur 'Terminée' pour créer votre personnage.", + invalid = "Vous avez fourni un invalide %s !", + nameMinLen = "Votre nom doit contenir au moins %d caractères !", + nameMaxLen = "Votre nom ne doit pas dépasser %d caractères !", + descMinLen = "Votre description doit contenir au moins %d caractères !", + maxCharacters = "Vous ne pouvez pas créer plus de personnage !", + player = "Joueur", + finish = "Terminée", + finishTip = "Terminez la création du personnage.", + needModel = "Vous devez choisir un modèle valide !", + creating = "Votre personnage est en train d'être créé...", + unknownError = "Une erreur inconnue s'est produite !", + areYouSure = "Êtes-vous sûr ?", + delete = "Supprimer", + deleteConfirm = "Ce personnage sera irrévocablement supprimé !", + deleteComplete = "%s a été supprimé.", + no = "Non", + yes = "Oui", + close = "Fermer", + save = "Sauvegarder", + itemInfo = "Nom: %s\nDescription: %s", + itemCreated = "Élément(s) créé(s) avec succès.", + cloud_no_repo = "Le référentiel fourni n'est pas valide !", + cloud_no_plugin = "Le plugin fourni n'est pas valide !", + inv = "Inventaire", + plugins = "Plugins", + author = "Auteur", + version = "Version", + characters = "Personnages", + business = "Marché", + settings = "Options", + config = "Configuration", + chat = "Chat", + appearance = "Apparence", + misc = "Divers", + oocDelay = "Vous devez attendre %s seconde(s) avant de réutiliser le OOC !", + loocDelay = "Vous devez attendre %s seconde(s) avant de réutiliser le LOOC !", + usingChar = "Vous utilisez déjà ce personnage.", + notAllowed = "Vous n'êtes pas autorisé à le faire !", + itemNoExist = "L'article que vous avez demandé n'existe pas !", + cmdNoExist = "Cette commande n'existe pas !", + charNoExist = "Un personnage correspondant n'a pas pu être trouvé !", + plyNoExist = "Impossible de trouver un joueur correspondant !", + cfgSet = "%s a mis \"%s\" à %s.", + drop = "Lâcher", + dropTip = "Lâcher cet objet de votre inventaire.", + take = "Prendre", + takeTip = "Prendre cet objet et le placer dans votre inventaire.", + dTitle = "Porte sans propriétaire", + dTitleOwned = "Porte achetée", + dIsNotOwnable = "Cette porte ne peut être achetée.", + dIsOwnable = "Vous pouvez acheter cette porte en appuyant sur F2.", + dMadeUnownable = "Vous avez rendu cette porte inachetable.", + dMadeOwnable = "Vous avez rendu cette porte achetable.", + dNotAllowedToOwn = "Vous n'êtes pas autorisé à posséder cette porte !", + dSetDisabled = "Vous avez désactivé cette porte.", + dSetNotDisabled = "Vous avez réactivé cette porte.", + dSetHidden = "Vous avez caché cette porte.", + dSetNotHidden = "Vous avez montré cette porte.", + dSetParentDoor = "Vous avez défini cette porte comme porte parent.", + dCanNotSetAsChild = "Vous ne pouvez pas définir une porte héritière alors qu'elle est parente !", + dAddChildDoor = "Vous avez défini cette porte héritère.", + dRemoveChildren = "Vous avez enlevé tout les héritiers de cette porte.", + dRemoveChildDoor = "Vous avez enlevé cette porte de son status hértier.", + dNoParentDoor = "Aucune porte parent existe.", + dOwnedBy = "Cette porte appartient à %s.", + dConfigName = "Portes", + dSetFaction = "Cette porte appartient désormais à la faction %s.", + dRemoveFaction = "Cette porte n'appartient plus à aucune faction.", + dNotValid = "Vous ne regardez pas une porte valide !", + canNotAfford = "Vous ne pouvez pas vous permettre d'acheter ceci !", + dPurchased = "Vous avez acheté cette porte pour %s.", + dSold = "Vous avez vendu cette porte pour %s.", + notOwner = "Vous n'êtes pas le propriétaire de cela !", + invalidArg = "Vous avez fourni une valeur non valide pour l'argument #%s !", + invalidFaction = "La faction que vous avez fournie est introuvable !", + flagGive = "%s a donné %s '%s' flags.", + flagGiveTitle = "Donner Flags", + flagTake = "%s a pris les '%s' flags de %s.", + flagTakeTitle = "Prend les flags", + flagNoMatch = "Vous devez avoir \"%s\" flag(s) pour faire cette action !", + textAdded = "Vous avez ajouté un texte.", + textRemoved = "Vous avez supprimé le texte de %s.", + moneyTaken = "Vous avez reçu %s.", + moneyGiven = "Vous avez donné %s.", + insufficientMoney = "Tu n'as pas assez pour faire ça !", + businessPurchase = "Vous avez acheté %s pour %s.", + businessSell = "Vous avez vendu %s pour %s.", + businessTooFast = "Veuillez patienter avant d'acheter un autre article !", + cChangeModel = "%s a changé le model de %s en %s.", + cChangeName = "%s a changé le nom de %s en %s.", + cChangeSkin = "%s a changé le skin de %s en %s.", + cChangeGroups = "%s a changé le Bodygroup de %s \"%s\" en %s.", + cChangeFaction = "%s a transféré %s dans la faction %s.", + playerCharBelonging = "Cet objet appartient à votre autre personnage !", + spawnAdd = "Vous avez ajouté un spawn pour le %s.", + spawnDeleted = "Vous avez supprimé le point d'apparition de %s.", + someone = "Quelqu'un", + rgnLookingAt = "Permettre à la personne que vous regardez de vous reconnaître.", + rgnWhisper = "Permettre à ceux qui entendent vos chuchotements de vous reconnaître.", + rgnTalk = "Permettre à ceux qui entendent vos conversations de vous reconnaître.", + rgnYell = "Permettre à ceux qui entendent vos cris de vous reconnaître.", + icFormat = "%s dit \"%s\"", + rollFormat = "%s fait un roll de %s.", + wFormat = "%s chuchote \"%s\"", + yFormat = "%s hurle \"%s\"", + sbOptions = "Cliquez pour voir les options pour %s.", + spawnAdded = "Vous avez ajouté un spawn pour %s.", + whitelist = "%s a mis dans la whitelist %s de la faction %s.", + unwhitelist = "%s a enlevé de la whitelist %s de la faction %s.", + noWhitelist = "vous n'avez pas la whitelist pour ce personnage !", + gettingUp = "Vous êtes maintenant relevé...", + wakingUp = "Vous reprenez conscience...", + Weapons = "Arme", + checkout = "Aller au Checkpoint (%s)", + purchase = "Achat", + purchasing = "Achat...", + success = "Succès", + buyFailed = "Achat raté !", + buyGood = "Achat réussi !", + shipment = "Expédition", + shipmentDesc = "Cet envoi appartient à %s.", + class = "Classe", + classes = "Classes", + illegalAccess = "Accès illégal.", + becomeClassFail = "Vous ne pouvez pas devenir un %s!", + becomeClass = "Vous êtes devenu un %s.", + setClass = "Vous avez défini la classe de %s en %s.", + attributeSet = "Vous avez définis %s's %s en %s.", + attributeNotFound = "Vous avez spécifié un attribut non valide !", + attributeUpdate = "Vous avez ajouté %s's %s par %s.", + noFit = "Vous n'avez pas assez d'espace pour cet article !", + itemOwned = "Vous ne pouvez pas interagir avec un objet que vous possédez sur un personnage différent !", + help = "Aide", + commands = "Commande", + doorSettings = "Réglages de la porte", + sell = "Vendre", + access = "Accès", + locking = "VERROUILLAGE DE L'ENTITÉ...", + unlocking = "DÉVERROUILLAGE DE L'ENTITÉ...", + modelNoSeq = "Votre modèle ne supporte pas cet action.", + notNow = "Vous n'êtes pas autorisé à le faire ceci maintenant.", + faceWall = "Vous devez être face au mur pour faire ceci.", + faceWallBack = "Votre dos doit faire face au mur pour faire ceci.", + descChanged = "Vous avez changé la description de votre personnage.", + noOwner = "Le propriétaire est invalide !", + invalidItem = "Vous avez spécifié un élément non valide !", + invalidInventory = "Vous avez spécifié un inventaire non valide !", + home = "Maison", + charKick = "%s a exclu le personnage %s.", + charBan = "%s a banni le personnage %s.", + charBanned = "Ce personnage est banni.", + playerConnected = "%s s'est connecté au serveur.", + playerDisconnected = "%s s'est déconnecté du serveur.", + setMoney = "Vous avez défini l'argent de %s à %s..", + itemPriceInfo = "Vous pouvez acheter cet article pour %s.\nVous pouvez vendre cet article pour %s", + free = "Gratuit", + vendorNoSellItems = "Il n'y a pas d'objets à vendre.", + vendorNoBuyItems = "Il n'y a pas d'objets à acheter.", + vendorSettings = "Paramètres du vendeur", + vendorUseMoney = "Le vendeur doit utiliser de l'argent ?", + vendorNoBubble = "Masquer la bulle du vendeur ?", + mode = "Mode", + price = "Prix", + stock = "Stock", + none = "Aucun", + vendorBoth = "Acheter et vendre", + vendorBuy = "Achat seulement", + vendorSell = "Vente seulement", + maxStock = "Stock max", + vendorFaction = "Éditeur de faction", + buy = "Achat", + vendorWelcome = "Bienvenue dans mon magasin, que puis-je vous offrir aujourd'hui ?", + vendorBye = "Reviens bientôt !", + charSearching = "Vous recherchez déjà un autre personnage !", + charUnBan = "%s a débanni le personnage %s.", + charNotBanned = "Ce personnage n'est pas banni!", + quickSettings = "Réglages rapides", + vmSet = "Vous avez configuré votre messagerie vocale.", + vmRem = "Vous avez supprimé votre messagerie vocale.", + noPerm = "Vous n'êtes pas autorisé à faire ceci!", + youreDead = "Tu es mort", + injMajor = "Semble gravement blessé", + injLittle = "Semble blessé", + chgName = "Changer de nom", + chgNameDesc = "Entrez le nouveau nom du personnage ci-dessous.", + weaponSlotFilled = "Vous ne pouvez pas utiliser une autre arme %s!", + equippedBag = "Vous ne pouvez pas déplacer un sac contenant un objet équipé !", + equippedWeapon = "Vous ne pouvez pas déplacer une arme actuellement équipée !", + nestedBags = "Vous ne pouvez pas mettre un inventaire à l'intérieur d'un inventaire de stockage !", + outfitAlreadyEquipped = "Tu portes déjà ce genre de tenue !", + useTip = "Utiliser l'objet.", + equip = "Équiper", + equipTip = "Équiper l'objet.", + unequip = "Déséquiper", + unequipTip = "Déséquiper l'objet.", + consumables = "Consommables", + plyNotValid = "Vous ne regardez pas un joueur valide !", + restricted = "Vous avez été restreint.", + salary = "Vous avez reçu %s de votre salaire.", + noRecog = "Vous ne reconnaissez pas cette personne.", + curTime = "L'heure actuelle est %s.", + vendorEditor = "Éditeur du vendeur", + edit = "Edit", + disable = "Désactiver", + vendorPriceReq = "Entrez le nouveau prix pour cet article.", + vendorEditCurStock = "Modifier le stock actuel", + you = "Vous", + vendorSellScale = "Échelle de prix", + vendorNoTrade = "Vous ne pouvez pas échanger avec ce marchand !", + vendorNoMoney = "Ce marchand ne peut pas se acheter cet article !", + vendorNoStock = "Ce marchand n'a pas cet article en stock !", + contentTitle = "Contenu Helix manquant", + contentWarning = "Le contenu d'Helix n'est pas monté. Certaines fonctionnalités peuvent être manquantes.\nSouhaitez-vous ouvrir la page du Workshop pour le contenu Helix ?", + flags = "Flags", + mapRestarting = "La carte va redémarrer dans %d secondes !", + chooseTip = "Choisissez ce personnage jouer avec.", + deleteTip = "Supprimer ce personnage.", + storageInUse = "Quelqu'un d'autre utilise ceci !", + storageSearching = "Recherche...", + container = "Conteneur", + containerPassword = "Vous avez défini le mot de passe de ce conteneur en %s.", + containerPasswordRemove = "Vous avez supprimé le mot de passe de ce conteneur.", + containerPasswordWrite = "Entrer le mot de passe.", + containerName = "Vous avez défini le nom de ce conteneur en %s.", + containerNameWrite = "Entrez le nom.", + containerNameRemove = "Vous avez supprimé le nom de ce conteneur.", + containerInvalid = "Vous devez regarder un conteneur pour faire ceci !", + wrongPassword = "Vous avez entré un mot de passe incorrect !", + respawning = "Réapparition...", + + scoreboard = "Tableau des joueurs", + ping = "Ping: %d", + viewProfile = "Voir le profil Steam", + copySteamID = "Copier le Steam ID", + + money = "Argent", + moneyLeft = "Votre argent: ", + currentMoney = "Argent restant: ", + + invalidClass = "Ceci n'est pas une classe valide !", + invalidClassFaction = "Il ne s'agit pas d'une classe valide pour cette faction !", + + miscellaneous = "Divers", + general = "General", + observer = "Observateur", + performance = "Performance", + thirdperson = "Troisième personne", + date = "Date", + interaction = "Interaction", + server = "Serveur", + + resetDefault = "Réinitialiser aux valeurs par défaut", + resetDefaultDescription = "Ceci réinitialisera \"%s\" à sa valeur par défaut de \"%s\".", + optOpenBags = "Sacs ouverts avec inventaire", + optdOpenBags = "Visualise automatiquement tous les sacs de votre inventaire lorsque le menu est ouvert.", + optShowIntro = "Afficher l'intro au démarrage", + optdShowIntro = "Affiche l'introduction de Helix la prochaine fois que vous vous inscrivez. Ceci est toujours désactivé après l'avoir regardé.", + optCheapBlur = "Désactiver le flou", + optdCheapBlur = "Remplace le flou de l'interface utilisateur par une simple gradation.", + optObserverTeleportBack = "Retour à l'emplacement précédent", + optdObserverTeleportBack = "Vous ramène à l'emplacement où vous êtes entré en mode observateur.", + optObserverESP = "Montrer l'ESP admin", + optdObserverESP = "Affiche les noms et les emplacements de chaque joueur sur le serveur.", + opt24hourTime = "Utilisez le temps de 24 heures", + optd24hourTime = "Afficher les horodatages sur 24 heures au lieu de 12 heures (AM / PM).", + optChatNotices = "Afficher les avis dans le chat", + optdChatNotices = "Met à la place tous les avis qui apparaissent en haut à droite dans le chat.", + optChatTimestamps = "Afficher les horodatages dans le chat", + optdChatTimestamps = "Prépose l'heure à chaque message dans la chatbox.", + optAlwaysShowBars = "Toujours afficher les barres d'informations", + optdAlwaysShowBars = "Affiche les barres d’information en haut à gauche, qu’elles soient masquées ou non.", + optAltLower = "Masquer les mains une fois abaissé", -- @todo remove me + optdAltLower = "Cacher les mains quand elles sont sur les côtés.", -- @todo remove me + optThirdpersonEnabled = "Activer la troisième personne", + optdThirdpersonEnabled = "Déplace la caméra derrière toi. Ceci peut également être activé dans la console avec la commande \"ix_togglethirdperson\"", + optThirdpersonClassic = "Activer la caméra classique à la troisième personne", + optdThirdpersonClassic = "Déplace la vue de ton personnage avec ta souris.", + optThirdpersonVertical = "Troisième personne hauteur", + optdThirdpersonVertical = "À quelle hauteur la caméra devrait être.", + optThirdpersonHorizontal = "Troisième personne horizontale", + optdThirdpersonHorizontal = "Quelle distance gauche ou droite la caméra doit être.", + optThirdpersonDistance = "Troisième personne distance", + optdThirdpersonDistance = "À quelle distance la caméra doit être.", + optDisableAnimations = "Désactiver les animations", + optdDisableAnimations = "Arrête la lecture des animations pour fournir des transitions instantanées.", + optAnimationScale = "Échelle d'animation", + optdAnimationScale = "A quelle vitesse les animations doivent être faites.", + optLanguage = "Langue", + optdLanguage = "La langue affichée dans l'interface utilisateur Helix.", + optNoticeDuration = "Durée de la notification", + optdNoticeDuration = "Combien de temps afficher les notifications pour (en secondes).", + optNoticeMax = "Notification maximum", + optdNoticeMax = "Le nombre de notification affichés avant que les précédentes soient supprimés.", + optChatFontScale = "Échelle de polices de discussion", + optdChatFontScale = "A quelle taille doit être la police de discussion.", + optChatOutline = "Décrire le texte du chat", + optdChatOutline = "Affiche un contour autour du texte de la discussion plutôt qu’une ombre. Activez cette option si vous rencontrez des difficultés pour lire du texte.", + + cmdRoll = "Rolls un nombre entre 0 et le nombre spécifié", + cmdPM = "Envoie un message privé à quelqu'un.", + cmdReply = "Envoie un message privé à la dernière personne à qui vous avez envoyé un message.", + cmdSetVoicemail = "Définit ou supprime le message de réponse automatique lorsque quelqu'un vous envoie un message privé.", + cmdCharGiveFlag = "Donne le(s) flag(s) spécifié(s) à quelqu'un.", + cmdCharTakeFlag = "Supprime le flag spécifié de la personnage si elle le possède.", + cmdToggleRaise = "Lève ou baisse l'arme que vous tenez.", + cmdCharSetModel = "Définit le modèle de personnage d'une personne.", + cmdCharSetSkin = "Définit l'apparence du modèle de personnage d'une personne.", + cmdCharSetBodygroup = "Définit le Bodygroup pour le modèle de personnage d'une personne.", + cmdCharSetAttribute = "Définit le niveau de l'attribut spécifié pour quelqu'un.", + cmdCharAddAttribute = "Ajoute un niveau à l'attribut spécifié pour quelqu'un.", + cmdCharSetName = "Remplace le nom de quelqu'un par le nom spécifié.", + cmdCharGiveItem = "Donne l'élément spécifié à quelqu'une.", + cmdCharKick = "Oblige quelqu'un à se déconnecter de son personnage.", + cmdCharBan = "Interdit à une personne de jouer son personnage actuel.", + cmdCharUnban = "Permet à un personnage banni d'être utiliser à nouveau.", + cmdGiveMoney = "Donne une somme d'argent précise à la personne que vous regardez.", + cmdCharSetMoney = "Modifie le montant total d'argent d'une personne en fonction du montant spécifié.", + cmdDropMoney = "Dépose le montant d'argent spécifié dans une petite boîte devant vous.", + cmdPlyWhitelist = "Permet à quelqu'un de créer un personnage dans la faction spécifiée.", + cmdCharGetUp = "Tentatives de se relever après être tombé.", + cmdPlyUnwhitelist = "Interdit à quelqu'un de créer un personnage dans la faction spécifiée.", + cmdCharFallOver = "Rends vos genoux faibles et vous fait tomber.", + cmdBecomeClass = "Tente de faire partie de la classe spécifiée dans votre faction actuelle.", + cmdCharDesc = "Définir votre description physique", + cmdCharDescTitle = "Description physique", + cmdCharDescDescription = "Entrez la description physique de votre personnage.", + cmdPlyTransfer = "Transfère quelqu'un à la faction spécifiée.", + cmdCharSetClass = "Force quelqu'un à faire partie d'une classe dans sa faction.", + cmdMapRestart = "Redémarre la carte après le délai spécifié.", + cmdPanelAdd = "Place un panneau Web dans le monde.", + cmdPanelRemove = "Supprime un panneau Web que vous regardez.", + cmdTextAdd = "Place un bloc de texte dans le monde.", + cmdTextRemove = "Supprime les blocs de texte que vous regardez.", + cmdMapSceneAdd = "Ajoute un point de vue de caméra cinématique affiché dans le menu du personnage..", + cmdMapSceneRemove = "Supprime un point de vue de la caméra qui est affiché dans le menu du personnage.", + cmdSpawnAdd = "Ajoute un point d'apparition pour la faction spécifiée.", + cmdSpawnRemove = "Supprime les points d'apparition que vous regardez.", + cmdAct = "Effectuer l'animation %s.", + cmdContainerSetPassword = "Définit le mot de passe du conteneur que vous consultez.", + cmdDoorSell = "Vend la porte que vous regardez.", + cmdDoorBuy = "Achète la porte que vous regardez.", + cmdDoorSetUnownable = "Rend la porte que vous regardez inachetable.", + cmdDoorSetOwnable = "Rend la porte que vous regardez achetable.", + cmdDoorSetFaction = "Attribut la porte que vous regardez à la faction spécifiée.", + cmdDoorSetDisabled = "N'autorise aucune commande sur la porte que vous regardez.", + cmdDoorSetTitle = "Définit le titre de la porte que vous regardez.", + cmdDoorSetParent = "Définit le parent d'une paire de portes.", + cmdDoorSetChild = "Définit l'héritié d'une paire de portes.", + cmdDoorRemoveChild = "Enlève l'héritié d'une paire de portes.", + cmdDoorSetHidden = "Cache la description de la porte que vous regardez, mais lui permet quand même d'être achetable.", + cmdDoorSetClass = "Attribut la porte que vous regardez à la classe spécifiée.", + cmdMe = "Effectuer une action physique.", + cmdIt = "Décrire une action/situation en tant que narrateur.", + cmdW = "Chuchoter quelque chose au gens près de vous.", + cmdY = "Crier quelque chose au gens autour de vous.", + cmdEvent = "Décrire une action que tout le monde peut voir.", + cmdOOC = "Envoie un message dans le chat général en dehors du Rôleplay.", + cmdLOOC = "Envoie un message dans le chat local en dehors du Rôleplay." +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_german.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_german.lua new file mode 100644 index 0000000..dcbf55b --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_german.lua @@ -0,0 +1,452 @@ + +-- GERMAN TRANSLATION + +NAME = "Deutsch" + +LANGUAGE = { + helix = "Helix", + + introTextOne = "fist industries präsentiert", + introTextTwo = "in Zusammenarbeit mit %s", + introContinue = "Drücke Leertaste um fortzufahren", + + helpIdle = "Wähle eine Kategorie", + helpCommands = "Kommandoparameter mit sind Voraussetung, während [Klammern] optional sind.", + helpFlags = "Dieser Charakter kann auf Flags mit einem grünen Hintergrund zugreifen.", + + creditSpecial = "Vielen Dank", + creditLeadDeveloper = "Lead Developer", + creditUIDesigner = "UI Designer", + creditManager = "Project Manager", + creditTester = "Lead Tester", + + chatTyping = "Am Schreiben...", + chatTalking = "Am Reden...", + chatYelling = "Am Schreien...", + chatWhispering = "Am Flüstern...", + chatPerforming = "Am Ausführen...", + chatNewTab = "Neuer Tab...", + chatReset = "Position zurücksetzen", + chatResetTabs = "Tabs zurücksetzen", + chatCustomize = "Individualisieren...", + chatCloseTab = "Tab schließen", + chatTabName = "Tab Name", + chatNewTabTitle = "Neuer Tab", + chatAllowedClasses = "Erlaubte Chatklassen", + chatTabExists = "Ein Chat-Tab mit diesem Namen exisitert bereits!", + chatMarkRead = "Alles als gelesen markieren.", + + community = "Community", + checkAll = "Alles auswählen", + uncheckAll = "Auswahl aufheben", + color = "Farbe", + type = "Typ", + display = "Anzeige", + loading = "Am Laden", + dbError = "Datenbankverbindung fehlgeschlagen", + unknown = "Unbekannt", + noDesc = "Keine Beschreibung verfügbar", + create = "Erstellen", + update = "Update", + load = "Charakter laden", + loadTitle = "Lade einen Charakter", + leave = "Server verlassen", + leaveTip = "Verlasse den aktuellen Server", + ["return"] = "Zurück", + returnTip = "Gehe zum vorherigen Menü zurück", + proceed = "Weiter", + faction = "Fraktion", + skills = "Fähigkeiten", + choose = "Auswählen", + chooseFaction = "Wähle eine Fraktion", + chooseDescription = "Definiere deine Geschichte", + chooseSkills = "Verfeinere deine Fähigkeiten", + name = "Name", + description = "Beschreibung", + model = "Model", + attributes = "Attribute", + attribPointsLeft = "Verbleibende Punkte", + charInfo = "Charakter Informationen", + charCreated = "Charakter erfolgreich erstellt.", + charCreateTip = "Vervollständige alle Felder und klicke Anschließend auf 'Bestätigen' um deinen Charakter zu erstellen.", + invalid = "Falscher %s!", + nameMinLen = "Dein Name muss mindestens %d Zeichen enthalten!", + nameMaxLen = "Deine Name darf maximal %d Zeichen enthalten!", + descMinLen = "Deine Beschreibung muss mindestens %d Zeichen enthalten!", + maxCharacters = "Du kannst keine weiteren Charakter erstellen!", + player = "Spieler", + finish = "Bestätigen", + finishTip = "Beende die Charaktererstellung", + needModel = "Du musst ein gültiges Model auswählen!", + creating = "Dein Charakter wird erstellt...", + unknownError = "Ein unbekannter Fehler ist aufgetreten!", + areYouSure = "Bist du dir sicher?", + delete = "Löschen", + deleteConfirm = "Dieser Charakter kann nicht wiederhergestellt werden!", + deleteComplete = "%s wurde gelöscht.", + no = "Nein", + yes = "Ja", + close = "Schließen", + save = "Speichern", + itemInfo = "Name: %s\nBeschreibung: %s", + itemCreated = "Item(s) erfolgreich erstellt.", + cloud_no_repo = "Das gewählte Lager ist nicht gültig!", + cloud_no_plugin = "Das gewählte Plugin ist nicht gültig!", + inv = "Inventar", + plugins = "Plugins", + pluginLoaded = "%s hat Plugin \"%s\" für das Laden eines Mapchange aktiviert.", + pluginUnloaded = "%s hat Plugin \"%s\" für das Laden eines Mapchange deaktivier.", + loadedPlugins = "Geladene Plugins", + unloadedPlugins = "Nicht-Geladene Plugins", + on = "Aktiviert", + off = "Deaktiviert", + author = "Author", + version = "Version", + characters = "Charaktere", + business = "Geschäft", + settings = "Einstellungen", + config = "Konfiguration", + chat = "Chat", + appearance = "Aussehen", + misc = "Sonstiges", + oocDelay = "Du musst %s Sekunden warten bevor du OOC erneut benutzt!", + loocDelay = "Du musst %s Sekunden warten bevor du LOOC erneut benutzt!", + usingChar = "Du benutzt diesen Charakter schon.", + notAllowed = "Das darfst du nicht tun!", + itemNoExist = "Das angefragte Item existiert nicht!", + cmdNoExist = "Dieser Befehl existiert nicht!", + charNoExist = "Ein passendes Zeichen wurde nicht gefunden!", + plyNoExist = "Ein passendes Spieler wurde nicht gefunden!", + cfgSet = "%s hat \"%s\" zu %s gesetzt.", + drop = "Fallenlassen", + dropTip = "Lasse diese Item fallen.", + take = "Nehmen", + takeTip = "Nehme dieses Item und packe es in dein Inventar.", + dTitle = "Unbekannte Tür", + dTitleOwned = "Gekaufte Tür", + dIsNotOwnable = "Tür nicht verfügbar", + dIsOwnable = "Du kannst diese Tür mit F2 kaufen.", + dMadeUnownable = "Diese Tür ist nun nicht mehr verfügbar.", + dMadeOwnable = "Diese Tür ist nun wieder verfügbar.", + dNotAllowedToOwn = "Du darfst diese Tür nicht besitzen!", + dSetDisabled = "Diese Tür ist nun deaktiviert.", + dSetNotDisabled = "Diese Tür ist nun wieder aktiviert.", + dSetHidden = "Diese Tür ist nun versteckt.", + dSetNotHidden = "Diese Tür ist nun sichtbar.", + dSetParentDoor = "Du hast diese Tür als deine Parent Tür gesetzt", + dCanNotSetAsChild = "Du kannst diese Parent-Tür nicht als Child setzen.", + dAddChildDoor = "Du hast diese Tür als Child gesetzt.", + dRemoveChildren = "Du hast alle Children dieser Tür entfernt.", + dRemoveChildDoor = "Diese Tür ist kein Child mehr.", + dNoParentDoor = "Du hast keine Parent-Tür ausgewählt.", + dOwnedBy = "%s besitzt diese Tür bereits.", + dConfigName = "Türen", + dSetFaction = "Fraktion %s besitzt diese Tür.", + dRemoveFaction = "Diese Tür gehört nun keine Fraktion mehr.", + dNotValid = "Du gucks auf keine Tür!", + canNotAfford = "Du hast nicht genug Geld!", + dPurchased = "Tür wurde für %s von dir gekauft.", + dSold = "Du hast diese Tür für %s verkauft.", + notOwner = "Du bist nicht der Besitzer!", + invalidArg = "Ungültiger Wert für Argument #%s!", + invalidFaction = "Fraktion konnte nicht gefunden werden!", + flagGive = "%s hat %s '%s' Berechtigungen gegeben.", + flagGiveTitle = "Berechtigungen geben", + flagTake = "%s hat '%s' Berechtigungen von %s genommen.", + flagTakeTitle = "Berechtigungen nehmen", + flagNoMatch = "Du benötigt \"%s\" Berechtigung(en) um diese Aktion auszuführen!", + panelAdded = "Feld hinzugefügt.", + panelRemoved = "Du hast %d Feld(er) entfernt.", + textAdded = "Du hast einen Text hinzugefügt.", + textRemoved = "Du hast %s Text(e) gelöscht.", + moneyTaken = "Dir wurde %s gegeben.", + moneyGiven = "Du hast %s gegeben.", + insufficientMoney = "Du hast nicht genug Geld!", + businessPurchase = "Du hast %s für %s gekauft.", + businessSell = "Du hast %s für %s verkauft.", + businessTooFast = "Bitte warte bevor du ein weitere Item kaufst!", + cChangeModel = "%s hat %s's Model zu %s geändert.", + cChangeName = "%s hat %s's Name zu %s geändert.", + cChangeSkin = "%s hat %s's Skin zu %s geändert.", + cChangeGroups = "%s hat %s's \"%s\" Bodygroup zu %s geändert.", + cChangeFaction = "%s hat %s zu Fraktion %s gewechselt.", + playerCharBelonging = "Dieser Gegenstand gehört deinem anderen Charakter!", + spawnAdd = "Du hast einen Spawn für %s hinzugefügt.", + spawnDeleted = "Du hast %s Spawnpoint(s) entfernt.", + someone = "Jemand", + rgnLookingAt = "Erlaube allen in Sichtreichweite dich zu erkennen", + rgnWhisper = "Erlaube allen in Flüsterreichweite dich zu erkennen.", + rgnTalk = "Erlaube allen in Sprachreichweite dich zu erkennen", + rgnYell = "Erlaube allen in Rufreichweite dich zu erkennen", + icFormat = "%s sagt \"%s\"", + rollFormat = "%s hat eine %s von %s gewürfelt.", + wFormat = "%s flüstert \"%s\"", + yFormat = "%s ruft \"%s\"", + sbOptions = "Auswählen um Optionen für %s zu sehen.", + spawnAdded = "Du hast einen Spawn für %s hinzugefügt.", + whitelist = "%s hat %s auf die Whitelist der Fraktion %s gesetzt.", + unwhitelist = "%s hat %s von der Whitelist der Fraktion %s entfernt.", + noWhitelist = "Du hast keine Whitelist für diesen Charakter!", + charNotWhitelisted = "%s ist nicht auf der Whitelist der Fraktion %s.", + gettingUp = "Du stehst auf...", + wakingUp = "Du bist wieder bei Bewusstsein...", + Weapons = "Waffen", + checkout = "Guck dir (%s) an", + purchase = "Kaufen", + purchasing = "Kaufprozess im Gange...", + success = "Erfolgreich", + buyFailed = "Kauf fehlgeschlagen!", + buyGood = "Kauf erfolgreich!", + shipment = "Lieferung", + shipmentDesc = "Diese Lieferung gehört %s.", + class = "Klasse", + classes = "Klassen", + illegalAccess = "Unerlaubter Zugriff.", + becomeClassFail = "Du kannst kein %s werden!", + becomeClass = "Du bist nun %s.", + setClass = "Du hast %s's Klasse zu %s gesetzt.", + attributeSet = "Du hast %s's %s zu %s gesetzt.", + attributeNotFound = "Du hast ein ungültiges Attribut angegeben!", + attributeUpdate = "Du hast %s's %s %s hinzugefügt.", + noFit = "Du hast kein Platz für dieses Item.", + itemOwned = "Du kannst nicht mit einem Item interagieren das ein anderer deiner Charakter besitzt!", + help = "Hilfe", + commands = "Commands", + doorSettings = "Türeinstellungen", + sell = "Verkaufen", + access = "Zugriff", + locking = "Entity am zuschließen...", + unlocking = "Entity am aufschließen...", + modelNoSeq = "Dein Model unterstütz diese Aktion nicht!", + notNow = "Das kannst du nicht tun!", + faceWall = "Du musst mit dem Gesicht zu einer Wand stehen!", + faceWallBack = "Du musst mit dem Rücken zu einer Wand stehen!", + descChanged = "Du hast deine Charakterbeschreibung geändert.", + noOwner = "Ungültiger Besitzer!", + invalidItem = "Ungültiges Item ausgewählt!", + invalidInventory = "Ungültiges Inventar ausgewählt!", + home = "Start", + charKick = "%s hat Charakter %s gekickt.", + charBan = "%s hat Charakter %s gebannt.", + charBanned = "Dieser Charakter ist gebannt.", + charBannedTemp = "Dieser Charakter ist temporär gebannt.", + playerConnected = "%s hat den Server betreten.", + playerDisconnected = "%s hat den Server verlassen.", + setMoney = "Du hast %s's Geld auf %s gesetzt.", + itemPriceInfo = "Du kannst das Item für %s kaufen.\nDu kannst das Item für %s verkaufen.", + free = "Kostenlos", + vendorNoSellItems = "Es gibt keine Items zu verkaufen.", + vendorNoBuyItems = "Es gibt keine Items zu kaufen.", + vendorSettings = "Verkäufereinstellungen", + vendorUseMoney = "Soll der Verkäufer Geld benutzen?", + vendorNoBubble = "Blase verstecken?", + category = "Kategorie", + mode = "Modus", + price = "Preis", + stock = "Bestand", + none = "Keine", + vendorBoth = "Kauf und Verkauf", + vendorBuy = "Nur Kauf", + vendorSell = "Nur Verkauf", + maxStock = "Höchster Bestand", + vendorFaction = "Fraktionseditor", + buy = "Kaufen", + vendorWelcome = "Wilkommen in meinem Laden, wie kann ich behilflich sein?", + vendorBye = "Auf Wiedersehen!", + charSearching = "Du suchst bereits einen anderen Charakter!", + charUnBan = "%s hat Charakter %s entbannt.", + charNotBanned = "Dieser Charakter ist nicht gebannt!", + quickSettings = "Schnelleinstellungen", + vmSet = "Du hast deine Sprachmitteilung gesetzt.", + vmRem = "Du hast deiner Sprachmitteilung entfernt.", + noPerm = "Das ist dir nicht gestattet!", + youreDead = "Du bist tot", + injMajor = "Wirkt schwer verletzt", + injLittle = "Wirkt leicht verletzt", + chgName = "Namen ändern", + chgNameDesc = "Gebe den Charakternamen unten ein.", + weaponSlotFilled = "Du kannst keine weitere %s Waffe benutzen!", + equippedBag = "Du kannst keine Tasche mit einen ausgerüsteten Item bewegen!", + equippedWeapon = "Du kannst keine ausgerüstete Waffe bewegen!", + nestedBags = "Du kannst kein Inventar in ein anderes Inventar legen!", + outfitAlreadyEquipped = "Du trägst dieses Outfit bereits!", + useTip = "Benutze das Item.", + equipTip = "Rüste das Item aus.", + unequipTip = "Lege das Item ab.", + consumables = "Verbrauchsgegenstand", + plyNotValid = "Du schaust nicht auf einen gültigen Spieler!", + restricted = "Du wurdest festgenommen.", + salary = "Du hast %s Lohn bekommen.", + noRecog = "Du erkennst diese Person nicht.", + curTime = "Zeit: %s", + vendorEditor = "Verkäufer Einstellungen", + edit = "Bearbeiten", + disable = "Deaktivieren", + vendorPriceReq = "Neuen Preis eingeben.", + vendorEditCurStock = "Bestand bearbeiten", + vendorStockReq = "Maximalen Bestand für diese Item eingeben.", + vendorStockCurReq = "Gebe ein wie viele Items benötigt werden um vom Bestand kaufen zu dürfen.", + you = "Du", + vendorSellScale = "Verkaufspreis Faktor", + vendorNoTrade = "Du kannst mit diesem Verkäufer nicht tauschen!", + vendorNoMoney = "Dieser Verkäufer kann sich diese Item nicht leiten!", + vendorNoStock = "Dieser Verkäufer hat das Item nicht mehr!", + vendorMaxStock = "Dieser Verkäufer hat einen vollen Bestand dieses Items!", + contentTitle = "Helix Content fehlt!", + contentWarning = "Du hast die Helix-Inhalte nicht installiert. Dies kann dazu führen, dass bestimmte Funktionen fehlen.\nMöchtest du die Workshop-Seite für die Helix-Inhalte öffnen?", + flags = "Berechtigungen", + mapRestarting = "Die Map wird in %d Sekunden neustarten!", + chooseTip = "Wähle einen Charakter zum spielen.", + deleteTip = "Lösche diesen Charakter.", + storageInUse = "Gegenstand in Nutzung!", + storageSearching = "Suche...", + container = "Behälter", + containerPassword = "Passwort auf %s gesetzt.", + containerPasswordRemove = "Du hast das Passwort entfernt.", + containerPasswordWrite = "Passwort eingeben.", + containerName = "Namen des Behälters auf %s gesetzt.", + containerNameWrite = "Namen eingeben.", + containerNameRemove = "Name des Behälters entfernt.", + containerInvalid = "Du musst einen Behälter anschauen!", + wrongPassword = "Falsches Passwort!", + respawning = "Wiederbeleben...", + tellAdmin = "Informiere ein Teammitglied über diese Fehler: %s", + + mapAdd = "Du hast eine Mapszene hinzugefügt.", + mapDel = "du hast %d Mapszene(n) entfernt.", + mapRepeat = "Füge nun den zweiten Punkt hinzu.", + + scoreboard = "Spieleübersicht", + ping = "Ping: %d", + viewProfile = "Steamprofil anschauen", + copySteamID = "Kopiere Steam ID", + + money = "Credits", + moneyLeft = "Deine Credits: ", + currentMoney = "Verbleibende Credits: ", + + invalidClass = "Das ist keine gültige Klasse!", + invalidClassFaction = "Das ist keine gültige Klasse für deine Fraktion!", + + miscellaneous = "Sonstiges", + general = "Allgemein", + observer = "Beobachter", + performance = "Leistung", + thirdperson = "Third Person", + date = "Datum", + interaction = "Interaktion", + server = "Server", + + resetDefault = "Standarteinstellung wiederherstellen", + resetDefaultDescription = "\"%s\" wird auf den Standartwert \"%s\" zurückgesetzt.", + optOpenBags = "Öffne Tasche mit Inventar", + optdOpenBags = "Öffnet alle Taschen in deinem Inventar automatisch", + optShowIntro = "Intro beim Verbinden anzeigen", + optdShowIntro = "Zeigt die Helix Einführen beim nächsten Verbinden. Wird nach dem ersten Schauen automatisch deaktiviert.", + optCheapBlur = "Deaktiviere Blur", + optdCheapBlur = "Ersetzt UI Blur mit einfachem Verdunkeln.", + optObserverTeleportBack = "Kehre zur vorherigen Position zurück.", + optdObserverTeleportBack = "Kehre zum Ort zurück an dem du in den Beobachtungsmodus gegangen bist.", + optObserverESP = "Zeige Admin ESP", + optdObserverESP = "Zeige Namen und Position aller Spieler auf dem Server.", + opt24hourTime = "Nutze 24-Stundenformat", + optd24hourTime = "Zeige Zeitstempel in 24-Stundenformat, statt 12-Stundenformat (AM/PM).", + optChatNotices = "Zeige Hinweise im Chat", + optdChatNotices = "Zeige alle Hinweise Rechts-Oben stattdessen im Chat an.", + optChatTimestamps = "Zeige Zeitstempel im Chat.", + optdChatTimestamps = "Zeigt bei jeder Nachricht im Chat die Zeit an.", + optAlwaysShowBars = "Zeige Statusbalken dauerhaft.", + optdAlwaysShowBars = "Zeige Statusbalken Links-Oben dauerhaft.", + optAltLower = "Verstecke Hände wenn runtergenommen.", -- @todo remove me + optdAltLower = "Versteckt Händer wenn sie an deinem Körper herunterhängen.", -- @todo remove me + optThirdpersonEnabled = "Aktiviere Third-Person", + optdThirdpersonEnabled = "Bewegt die Kamera hinter dich. Kann auch mit dem \"ix_togglethirdperson\" Console-Command umgeschaltet werden.", + optThirdpersonClassic = "Aktiviere die klassische Third-Person Kamera", + optdThirdpersonClassic = "Bewegt die Sicht deines Charakters mit der Maus.", + optThirdpersonVertical = "Kamera Höhe", + optdThirdpersonVertical = "Wie hoch die Kamera sein soll.", + optThirdpersonHorizontal = "Kamera Offset", + optdThirdpersonHorizontal = "Wie weit links oder rechts die Kamera sein soll.", + optThirdpersonDistance = "Kamera Entfernung", + optdThirdpersonDistance = "Wie weit entfernt die Kamera sein soll.", + optThirdpersonCrouchOffset = "Camera Höhe beim Kriechen", + optdThirdpersonCrouchOffset = "Wie hoch die Kamerahöhe beim Kriechen sein soll.", + optDisableAnimations = "Deaktiviere Animationen", + optdDisableAnimations = "Verhindert Animationen für einen sofortigen Übergang.", + optAnimationScale = "Animation Scale", + optdAnimationScale = "Wie schnell Animationen laufen sollen.", + optLanguage = "Sprache", + optdLanguage = "Die Sprache der Helix UI.", + optMinimalTooltips = "Minimale HUD Tips", + optdMinimalTooltips = "Ändert die HUD Tips um weniger auffällig zu sein.", + optNoticeDuration = "Hinweiszeit", + optdNoticeDuration = "Wie lange Hinweise angezeigt werden sollen (in Sekunden).", + optNoticeMax = "Maximale Hinweise", + optdNoticeMax = "Die maximal gleichzeitig angezeigten Hinweise.", + optChatFontScale = "Chat Schiftgröße", + optdChatFontScale = "Die Größe der Schirft im Chat.", + optChatOutline = "Chat Schirftrand", + optdChatOutline = "Größe des Randes um die Schirft im Text.", + + cmdRoll = "Würfelt eine Zahl zwischen 0 und der angegebenen Zahl.", + cmdPM = "Sendet eine Privatnachricht.", + cmdReply = "Sendet eine private Antwort zur letzten Person die dich angeschrieben hat.", + cmdSetVoicemail = "Setzt oder entfernt die Automatische Nachricht wenn dir jemand eine Privatnachricht sendet.", + cmdCharGiveFlag = "Gibt einer Person die angegebenen Berechtigungen.", + cmdCharTakeFlag = "Entfernt einer Person die angegebenen Berechtigungen.", + cmdToggleRaise = "Hebt oder Senkt die Waffe die du hälst.", + cmdCharSetModel = "Setzt das Model einer Person.", + cmdCharSetSkin = "Setzt den Skin des Models einer Person.", + cmdCharSetBodygroup = "Setzt die Bodygroup des Models einer Person.", + cmdCharSetAttribute = "Setzt das Level des angegebenen Attributs einer Person.", + cmdCharAddAttribute = "Fügt ein Level zum angegebenen Attirbuts einer Person hinzu.", + cmdCharSetName = "Ändert den Namen einer Person.", + cmdCharGiveItem = "Gibt das angegebenen Item einer Person.", + cmdCharKick = "Wirft jemanden aus seinem Charakter heraus.", + cmdCharBan = "Bannt einen bestimmten Charakter.", + cmdCharUnban = "Entbannt einen bestimmten Charakter.", + cmdGiveMoney = "Gibt dem Spieler den du anschaust eine angegebene Menge Geld.", + cmdCharSetMoney = "Setzt das Geld eines Spielers.", + cmdDropMoney = "Lässt Geld in einer Box vor dir fallen.", + cmdPlyWhitelist = "Whitelistet eine Person zu einer Fraktion.", + cmdCharGetUp = "Lässt deine Charakter versuchen wieder aufzustehen.", + cmdPlyUnwhitelist = "Entwhitelisted einen Charakter von eine Fraktion.", + cmdCharFallOver = "Lässt dich ohnmächtig werden.", + cmdBecomeClass = "Versucht die gegebene Klasse in deiner Fraktion zu werden.", + cmdCharDesc = "Setzt eine Beschreibung deine Körpers.", + cmdCharDescTitle = "Körperbeschreibung", + cmdCharDescDescription = "Geben deine Körperbeschreibung ein.", + cmdPlyTransfer = "Transferiert einen Spieler zu gegebenen Position.", + cmdCharSetClass = "Setzt jemanden zu einer Klasse.", + cmdMapRestart = "Führt nach der angegebenen Zeit einen Maprestart aus.", + cmdPanelAdd = "Platziert eine Webpanel.", + cmdPanelRemove = "Entfernt das Webpanel auf das du schaust.", + cmdTextAdd = "Platziert einen Text in die Welt.", + cmdTextRemove = "Entfernt den Text auf den du schaust.", + cmdMapSceneAdd = "Platziert eine Kamera für eine Mapszene.", + cmdMapSceneRemove = "Entfernt eine Kamera für eine Mapszene.", + cmdSpawnAdd = "Fügt einen Spawnpoint für die Fraktion hinzu.", + cmdSpawnRemove = "Entfernt alle Spawnpoints in deinem Sichtfeld.", + cmdAct = "Führt die %s Animation aus.", + cmdContainerSetPassword = "Setzt das Passwort des Behälters auf den du schaust.", + cmdDoorSell = "Verkauft die tür auf die du schaust.", + cmdDoorBuy = "Kauft die tür auf die du schaust.", + cmdDoorSetUnownable = "Macht die Tür auf die du schaust nicht-besitzbar.", + cmdDoorSetOwnable = "Macht die Tür auf die du schaust wieder beitzbar.", + cmdDoorSetFaction = "Setzt die Tür auf die du schaust als Besitzt einer Fraktion", + cmdDoorSetDisabled = "Deaktiviert die Tür auf die du schaust.", + cmdDoorSetTitle = "Setzt den Namen der Tür auf die du schaust.", + cmdDoorSetParent = "Setzt den Parent der Tür auf die du schaust.", + cmdDoorSetChild = "Setzt das Child ein Paar von Türen.", + cmdDoorRemoveChild = "Entfernt das Child ein Paar von Türen.", + cmdDoorSetHidden = "Versteckt die Beschreibung der Tür auf die du schaust.", + cmdDoorSetClass = "Setzt die Tür auf die du schaust als Besitz einer bestimmten Klasse.", + cmdMe = "Führe eine lokale Aktion aus.", + cmdIt = "Führe eine lokale Aktion für ein Objekt in deiner Nähe aus.", + cmdW = "Flüstere den Personen in deiner Nähe etwas.", + cmdY = "Rufe den Personen um dich etwas zu.", + cmdEvent = "Führe eine globale Aktion aus.", + cmdOOC = "Sendet eine Nachricht in den globalen Out-of-Character Chat.", + cmdLOOC = "Sendet eine Nachricht in den lokalen Out-of-Character Chat.", + +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_korean.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_korean.lua new file mode 100644 index 0000000..344a4df --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_korean.lua @@ -0,0 +1,228 @@ + +NAME = "한국어" + +LANGUAGE = { + loading = "불러오는 중", + dbError = "DB 서버 연결 실패", + unknown = "알 수 없음", + noDesc = "정보가 존재하지 않습니다.", + create = "생성", + createTip = "새로운 캐릭터를 생성합니다.", + load = "계속", + loadTip = "플레이할 캐릭터를 불러옵니다.", + leave = "종료", + leaveTip = "서버에서 퇴장합니다.", + ["return"] = "뒤로", + returnTip = "이전 메뉴로 돌아갑니다.", + name = "이름", + description = "정보", + model = "외관", + attributes = "능력", + charCreateTip = "빈칸들을 채우고 아래 '완료' 버튼을 눌러 캐릭터를 생성하십시오.", + invalid = "다음 정보가 존재하지 않습니다: %s", + descMinLen = "정보는 적어도 %d 자 이상이어야 합니다.", + player = "플레이어", + finish = "완료", + finishTip = "캐릭터 생성을 완료합니다.", + needModel = "올바른 외관을 선택하여야 합니다.", + creating = "캐릭터를 생성중입니다...", + unknownError = "오류가 발생하였습니다.", + delConfirm = "%s 영구히 완전히 삭제합니까?", + no = "아니오", + yes = "예", + itemInfo = "이름: %s\n정보: %s", + cloud_no_repo = "클라우드 경로가 존재하지 않습니다.", + cloud_no_plugin = "클라우드 추가 기능이 존재하지 않습니다.", + inv = "인벤토리", + plugins = "추가 기능", + author = "제작자", + version = "버전", + characters = "캐릭터", + business = "사업", + settings = "설정", + config = "설정", + chat = "대화", + appearance = "외관", + misc = "기타", + oocDelay = "%s 초를 더 기다려야 OOC 대화가 가능합니다.", + loocDelay = "%s 초를 더 기다려야 LOOC 대화가 가능합니다.", + usingChar = "이미 이 캐릭터로 서버에서 플레이하고 있습니다.", + notAllowed = "당신은 이것을 할 권한이 없습니다.", + itemNoExist = "당신이 요청한 아이템은 존재하지 않습니다.", + cmdNoExist = "당신이 요청한 명령은 존재하지 않습니다.", + plyNoExist = "그 이름을 가진 플레이어를 검색할 수 없습니다.", + cfgSet = "%s 님이 \"%s\" 를 %s 으로 설정하였습니다.", + drop = "버리기", + dropTip = "아이템을 소지품에서 제외시킵니다.", + take = "가지기", + takeTip = "아이템을 소지품에 추가시킵니다.", + dTitle = "소유되지 않은 문", + dTitleOwned = "소유된 문", + dIsNotOwnable = "이 문은 소유할 수 없습니다.", + dIsOwnable = "F2를 눌러서 이 문을 소유할 수 있습니다.", + dMadeUnownable = "당신은 이 문을 소유할 수 없도록 설정했습니다.", + dMadeOwnable = "당신은 이 문을을 소유할 수 있도록 설정했습니다.", + dNotAllowedToOwn = "이 문을 소유하도록 허가되지 않았습니다.", + dSetDisabled = "당신은 이 문의 기능을 껐습니다.", + dSetNotDisabled = "당신은 이 문의 기능을 다시 켰습니다.", + dSetHidden = "당신은 이 문을 숨겼습니다.", + dSetNotHidden = "당신은 이 문을 숨김 해제했습니다.", + dSetParentDoor = "당신은 이 문을 상위 개체로 설정하였습니다.", + dCanNotSetAsChild = "당신은 이 문을 하위 개체로 설정할 수 없습니다.", + dAddChildDoor = "당신은 이 문을 하위 개체로 설정하였습니다.", + dRemoveChildren = "당신은 이 문에 할당된 모든 하위 개체를 삭제했습니다.", + dRemoveChildDoor = "당신은 이 문을 하위 개체에서 삭제했습니다.", + dNoParentDoor = "상위 개체인 문이 없습니다.", + dOwnedBy = "이 문은 %s 님의 소유입니다.", + dConfigName = "문", + dSetFaction = "이 문은 이제 %s 단체에 속하게 됩니다.", + dRemoveFaction = "이 문은 이제 어느 단체에도 속하지 않습니다.", + dNotValid = "유효한 문을 바라보고 있어야 합니다.", + canNotAfford = "이 문을 구매할 충분한 자금을 가지고 있지 않습니다.", + dPurchased = "이 문을 %s으로 구매했습니다.", + dSold = "당신은 이 문을 %s으로 판매했습니다.", + notOwner = "당신은 이 문을 소유하고 있지 않습니다.", + invalidArg = "#%s 번째 명령 변수에 올바른 값을 입력해야 합니다.", + invalidFaction = "제시된 이름으로 된 단체를 찾을 수 없습니다.", + flagGive = "%s 님이 %s 님에게 '%s' 권한을 주었습니다.", + flagGiveTitle = "권한 주기", + flagGiveDesc = "이 권한들을 플레이어에게 줍니다.", + flagTake = "%s 님이 '%s' 권한을 %s 님으로 부터 받았습니다.", + flagTakeTitle = "플래그 가지기.", + flagTakeDesc = "이 권한들을 플레이어에게서 뺏습니다.", + flagNoMatch = "이 행동은 \"%s\" 권한을 필요로 합니다.", + textAdded = "텍스트를 추가하였습니다.", + textRemoved = "%s개의 택스트를 삭제하였습니다.", + moneyTaken = "%s 발견.", + businessPurchase = "당신은 %s 을/를 %s에 구매하였습니다.", + businessSell = "당신은 %s 을/를 %s에 판매했습니다.", + cChangeModel = "%s님이 %s님의 외관을 교체했습니다: %s.", + cChangeName = "%s님이 %s님의 이름을 교체했습니다: %s.", + cChangeSkin = "%s 가 %s's 의 스킨을 %s 로 바꾸었습니다.", + cChangeGroups = "%s 가 %s 의 \"%s\" 바디그룹을 %s 로 바꾸었습니다.", + cChangeFaction = "%s 는 %s 를 %s 팩션으로 이동시켰습니다.", + playerCharBelonging = "이 물건은 당신의 다른 캐릭터의 물건입니다.", + spawnAdd = "%s 개의 시작지점을 추가하였습니다.", + spawnDeleted = "%s개의 시작지점을 삭제하였습니다.", + someone = "누군가", + rgnLookingAt = "당신이 보고 있는 사람이 당신을 인식하도록 선언.", + rgnWhisper = "귓속말 거리에 있는 사람을 당신을 인식하도록 선언", + rgnTalk = "일반 대화 거리에 있는 사람을 당신을 인식하도록 선언", + rgnYell = "외침 대화 거리에 있는 사람을 당신을 인식하도록 선언", + icFormat = "%s: \"%s\"", + rollFormat = "%s님이 주사위를 굴렸습니다: %s.", + wFormat = "%s(귓속말): \"%s\"", + yFormat = "%s(외침): \"%s\"", + sbOptions = "%s님에 대한 선택지를 보려면 클릭하십시오.", + spawnAdded = "%s 단체를 위한 시작 지점이 추가되었습니다.", + whitelist = "%s님이 %s님을 %s 단체에 들어가도록 허가했습니다.", + unwhitelist = "%s님이 %s님을 %s 단체에 들어가는 것을 금지했습니다.", + gettingUp = "몸을 일으키는 중입니다...", + wakingUp = "정신을 차리는 중입니다...", + Weapons = "무기", + checkout = "물건 결제 (%s)", + purchase = "구매", + purchasing = "결제 진행중...", + success = "성공", + buyFailed = "결제 실패.", + buyGood = "결제가 완료되었습니다!", + shipment = "소유물", + shipmentDesc = "이 소유물은 %s님의 명의로 되어있습니다.", + class = "직업", + classes = "직업", + illegalAccess = "잘못된 접근입니다.", + becomeClassFail = "%s이/가 되는 것에 실패했습니다.", + becomeClass = "%s이/가 되었습니다.", + attributeSet = "당신은 %s님의 %s을/를 %s로 설정하였습니다.", + attributeUpdate = "당신은 %s님의 %s을/를 %s만큼 추가하였습니다.", + noFit = "소지품 공간이 부족합니다.", + help = "도움말", + commands = "명령어", + helpDefault = "목차 선택", + doorSettings = "문 설정", + sell = "판매", + access = "접근", + locking = "이 물건을 잠그는 중입니다...", + unlocking = "이 물건을 여는 중입니다...", + modelNoSeq = "당신의 외관은 이 행동을 지원하지 않습니다.", + notNow = "당신은 아직 이 행동을 할 수 없습니다.", + faceWall = "이 행동을 위해선 벽을 바라보고 있어야 합니다.", + faceWallBack = "이 행동을 위해선 벽을 등지고 있어야 합니다.", + descChanged = "당신의 캐릭터의 정보를 변경했습니다.", + charMoney = "당신의 소지금은 %s 입니다.", + charFaction = "당신은 %s 단체에 소속되어 있습니다.", + charClass = "당신은 이 단체의 %s 입니다.", + noOwner = "소유자가 존재하지 않습니다.", + invalidIndex = "아이템의 구분 번호가 올바르지 않습니다.", + invalidItem = "아이템 객체 참조가 잘못되었습니다.", + invalidInventory = "소지품 객체 참조가 잘못되었습니다.", + home = "초기", + charKick = "%s님이 %s님의 캐릭터를 추방하였습니다.", + charBan = "%s님이 %s님의 캐릭터를 영구히 추방하였습니다.", + charBanned = "이 캐릭터는 사용이 금지되었습니다.", + setMoney = "당신은 %s님의 돈을 %s으로 설정하였습니다.", + itemPriceInfo = "이 아이템을 %s에 구매가 가능합니다.\n이 아이템을 %s에 탄매가 가능합니다", + free = "무료", + vendorNoSellItems = "판매할 아이템이 없습니다.", + vendorNoBuyItems = "구매할 아이템이 없습니다.", + vendorSettings = "상인 설정", + vendorUseMoney = "상인에 제한된 돈", + vendorNoBubble = "말풍선 보이기", + mode = "상태", + price = "가격", + stock = "재고", + none = "없음", + vendorBoth = "판매와 구매", + vendorBuy = "구매 전용", + vendorSell = "판매 전용", + maxStock = "최대 재고", + vendorFaction = "팩션 에디터", + buy = "구매", + vendorWelcome = "어서오세요. 무엇을 찾으십니까?", + vendorBye = "다음에 또 오세요!", + charSearching = "이미 캐릭터를 수색하고 있습니다.", + charUnBan = "%s 님이 다음 캐릭터를 금지 해제했습니다: %s.", + charNotBanned = "이 캐릭터는 금지되지 않았습니다.", + containerPassword = "이 보관함의 암호를 %s 으로 설정하였습니다.", + containerPasswordRemove = "이 보관함의 암호를 삭제했습니다.", + containerPasswordWrite = "암호를 입력해 주십시오.", + wrongPassword = "암호가 다릅니다.", + cheapBlur = "블러 효과 사용 (FPS 향상)", + quickSettings = "빠른 설정", + vmSet = "개인 귓속말을 설정했습니다.", + vmRem = "개인 귓속말을 삭제했습니다.", + altLower = "주먹 미사용시 숨김", + noPerm = "이 행위를 할 권한이 없습니다.", + youreDead = "당신은 죽었습니다", + injMajor = "중상을 입음.", + injLittle = "부상을 입음.", + toggleESP = "어드민 월핵 사용", + chgName = "이름 변경", + chgNameDesc = "아래에 캐릭터의 새로운 이름을 입력하세요.", + thirdpersonToggle = "3인칭 사용", + thirdpersonClassic = "클래식 3인칭 사용", + equippedBag = "가방 내부에 사용중인 아이템이 있습니다.", + useTip = "이 아이템을 사용합니다.", + equipTip = "이 아이템을 착용합니다.", + unequipTip = "이 아이템을 착용해제합니다.", + consumables = "소모품", + plyNotValid = "당신은 잘못된 플레이어를 보고있습니다.", + restricted = "당신은 저지되었습니다.", + viewProfile = "스팀 프로필 보기", + salary = "당신은 월급으로 부터 &s 만큼의 돈이 들어왔습니다.", + noRecog = "당신은 이 사람을 인식하지 않았습니다.", + curTime = "지금 시각은 %s.", + vendorEditor = "상인 수정", + edit = "수정", + disable = "해제", + vendorPriceReq = "이 물품의 새로운 가격을 적으십시오.", + vendorEditCurStock = "현재 재고 수정", + you = "당신", + vendorSellScale = "판매 가격 규모", + vendorNoTrade = "당신은 이 상인과 거래 할수없습니다.", + vendorNoMoney = "이 상인은 해당 물품을 사 들일수 없습니다.", + vendorNoStock = "이 상인은 해당 물품의 재고가 없습니다.", + contentTitle = "Helix 콘텐츠 없음.", + contentWarning = "당신은 Helix 콘텐츠가 적용되어있지 않습니다. 특정 기능이 누락될 수 있습니다.\nHelix 콘텐츠를 적용해야 합니다.", + flags = "플래그" +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_norwegian.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_norwegian.lua new file mode 100644 index 0000000..3b65f40 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_norwegian.lua @@ -0,0 +1,228 @@ + +NAME = "Norwegian" + +LANGUAGE = { + loading = "Laster", + dbError = "Database tilkobling feilet", + unknown = "Ukjent", + noDesc = "Ingen beskrivelse tilgjengelig", + create = "Lag", + createTip = "Lag en ny karakter til å spille med.", + load = "Last", + loadTip = "Velg en tidligere brukt karakter til å spille med.", + leave = "Forlat", + leaveTip = "Forlat den nåværende serveren.", + ["return"] = "Tilbake", + returnTip = "Tilbake til den forrige menyen.", + name = "Navn", + description = "Beskrivelse", + model = "Modell", + attributes = "Attributter", + charCreateTip = "Fyll inn feltene nedenfor og trykk på 'Fullfør' for å skape din karakter.", + invalid = "Du har gitt et ugyldig %s", + descMinLen = "Din beskrivelse må være minst %d bokstav(er).", + player = "Spiller", + finish = "Fullfør", + finishTip = "Fullfør med å lage karakteren.", + needModel = "Du må velge en gyldig modell", + creating = "Din karakter blir skapt...", + unknownError = "Det har oppstått en ukjent feil", + delConfirm = "Er du sikker på at du vil PERMANENT slette %s?", + no = "Nei", + yes = "Ja", + itemInfo = "Navn: %s\nDescription: %s", + cloud_no_repo = "The repository provided is not valid.", + cloud_no_plugin = "The plugin provided is not valid.", + inv = "Inventar", + plugins = "Tillegg", + author = "Forfatter", + version = "Versjon", + characters = "Karakterer", + business = "Handel", + settings = "Innstillinger", + config = "Konfigurasjon", + chat = "Chat", + appearance = "Utseende", + misc = "Diverse", + oocDelay = "Du må vente %s sekund(er) med å bruke OOC igjen.", + loocDelay = "Du må vente %s sekund(er) med å bruke LOOC igjen.", + usingChar = "Du bruker allerede denne karakteren.", + itemNoExist = "Beklager, det elementet du forespurte finnes ikke.", + cmdNoExist = "Beklager, den kommandoen ekisterer ikke.", + plyNoExist = "Beklager, en matchende spiller ble ikke funnet.", + cfgSet = "%s har satt \"%s\" til %s.", + drop = "Frigjør", + dropTip = "Frigjør dette elementet fra ditt inventar.", + take = "Ta", + takeTip = "Ta dette elementet og putt det i inventaret ditt.", + dTitle = "Ueid Dør", + dTitleOwned = "Kjøpt Dør", + dIsNotOwnable = "Denne døren er ikke mulig å kjøpe.", + dIsOwnable = "Du kan kjøpe denne døren med å trykke på F2.", + dMadeUnownable = "Du har gjordt denne døren umulig å kjøpe.", + dMadeOwnable = "Du har gjordt denne døren mulig å kjøpe.", + dNotAllowedToOwn = "Du er ikke tillatt å eie denne døra.", + dSetDisabled = "Du har deaktivert denne døren.", + dSetNotDisabled = "Du har gjordt denne døren ikke lenger deaktivert.", + dSetHidden = "Du har gjordt denne døren gjemt.", + dSetNotHidden = "Du har gjordt at døren ikke er gjemt lenger.", + dSetParentDoor = "Du har gjort denne døren, hoveddøren.", + dCanNotSetAsChild = "Du kan ikke sette hoveddøren som sekundær døren.", + dAddChildDoor = "Du har lagt til en sekundær dør.", + dRemoveChildren = "Du har fjernet alle sekundære dører fra denne døren.", + dRemoveChildDoor = "Du har fjernet denne døren fra å være en sekundær dør.", + dNoParentDoor = "Du har ikke en hoveddør satt.", + dOwnedBy = "Denne døren er eid av %s.", + dConfigName = "Dører", + dSetFaction = "Denne døren tilhører %s gruppen.", + dRemoveFaction = "Denne døren tilhører ikke en gruppe lenger.", + dNotValid = "Du ser ikke på en gyldig dør.", + canNotAfford = "Du har ikke råd til å kjøpe dette.", + dPurchased = "Du har kjøpt denne døren for %s.", + dSold = "Du har solgt denne døren for %s.", + notOwner = "Du er ikke eieren av dette.", + invalidArg = "Du har gitt en ugyldig verdi for argumentet #%s.", + flagGive = "%s har gitt %s '%s' flaggene.", + flagGiveTitle = "Gi Flagg", + flagGiveDesc = "Gi de følgene flaggene til en spiller.", + flagTake = "%s har tatt '%s' flaggene fra %s.", + flagTakeTitle = "Ta Flagg", + flagTakeDesc = "Fjern de følgene flaggene til en spiller.", + flagNoMatch = "Du må ha \"%s\" Flagg for å gjøre denne handlingen.", + textAdded = "Du har lagt til en tekst.", + textRemoved = "Du har fjernet %s tekst(er).", + moneyTaken = "Du har funnet %s.", + businessPurchase = "Du har kjøpt %s for %s.", + businessSell = "Du har solgt %s for %s.", + cChangeModel = "%s endret %s's modell til %s.", + cChangeName = "%s endret %s's navn til %s.", + cChangeSkin = "%s endret %s's skin til %s.", + cChangeGroups = "%s endret %s's \"%s\" kroppsgruppe to %s.", + cChangeFaction = "%s har overført %s til %s gruppen.", + playerCharBelonging = "Dette objektet tilhører en av dine andre karakterer.", + invalidFaction = "Du har gitt en ugyldig gruppe.", + spawnAdd = "Du har lagt til spawnen for %s.", + spawnDeleted = "Du har fjernet %s spawn punkt(er).", + someone = "Noen", + rgnLookingAt = "Tillat personen du ser på å gjenkjenne deg.", + rgnWhisper = "Tillat de innen hviske radius å gjenkjenne deg.", + rgnTalk = "Tillat de innen prate radius å gjenkjenne deg.", + rgnYell = "Tillat de innen rope radius å gjenkjenne deg.", + icFormat = "%s sier \"%s\"", + rollFormat = "%s har rullet %s.", + wFormat = "%s hvisker \"%s\"", + yFormat = "%s roper \"%s\"", + sbOptions = "Klikk for å se instillingene for %s.", + spawnAdded = "Du har lagt til spawnen for %s.", + whitelist = "%s har hvitelistet %s for %s gruppen.", + unwhitelist = "%s har fjernet %s fra hvitelisten til %s gruppen.", + gettingUp = "Du reiser deg opp...", + wakingUp = "Du er kommer til bevissthet...", + Weapons = "Våpen", + checkout = "Gå til kassen (%s)", + purchase = "Kjøp", + purchasing = "Kjøp = er...", + success = "Suksess", + buyFailed = "Kjøpet mislyktes.", + buyGood = "Kjøp vellykket!", + shipment = "Forsendelsen", + shipmentDesc = "Denne forsendelsen tilhører %s.", + class = "Klasse", + classes = "Klasser", + illegalAccess = "Ulovlig Tilgang.", + becomeClassFail = "Klarte ikke å bli %s.", + becomeClass = "Du har bltt %s.", + attributeSet = "Du har satt %s's %s til %s.", + attributeUpdate = "Du har lagt til %s's %s av %s.", + noFit = "Dette elementet kan ikke passe i inventaret ditt.", + help = "Hjelp", + commands = "Kommandoer", + helpDefault = "Velg et katagori", + doorSettings = "Dør innstillinger", + sell = "Selg", + access = "Tilgang", + locking = "Låser denne enheten...", + unlocking = "Låser opp denne enheten...", + modelNoSeq = "Din modell støtter ikke denne handlingen.", + notNow = "Du er ikke tillatt.", + faceWall = "Du må stå mot veggen for å gjøre dette.", + faceWallBack = "Din rygg må stå mot veggen for å gjøre dette.", + descChanged = "Du har endret din karakters beskrivelse.", + charMoney = "Du har akkurat nå %s.", + charFaction = "Du er et medlem av denne %s gruppen.", + charClass = "Du er %s i gruppen.", + noOwner = "Eieren er ugyldig.", + notAllowed = "Denne handlingen er ikke tillatt.", + invalidIndex = "Elementet's Index er ugyldig.", + invalidItem = "Element Objektet er ugyldig.", + invalidInventory = "Inventar objektet er ugyldig.", + home = "Hjem", + charKick = "%s sparket karakteren %s.", + charBan = "%s utestengte karakteren %s.", + charBanned = "Denne karakteren er utestengt.", + setMoney = "Du har satt %s's penger til %s.", + itemPriceInfo = "Du kan kjøpe dette elementet for %s.\nDu kan kjøpe dette elementet for %s", + free = "Gratis", + vendorNoSellItems = "Det er ingen elementer for å selle.", + vendorNoBuyItems = "Det er ingen elementer til å kjøpe.", + vendorSettings = "Leverandør Innstillinger", + vendorUseMoney = "Leverandør skal bruke penger?", + vendorNoBubble = "Gjem leverandør bobblen?", + mode = "Modus", + price = "Pris", + stock = "På lager", + none = "Ingen", + vendorBoth = "Kjøp og selg", + vendorBuy = "Kun Kjøp", + vendorSell = "Kun Selg", + maxStock = "Maks på lager", + vendorFaction = "Gruppe endrer", + buy = "Kjøp", + vendorWelcome = "Velkommen til butikken min, hva kan jeg gi deg i dag?", + vendorBye = "Kom tilbake snart!", + charSearching = "Du søker allerede etter en annen karakter, vennligst vent.", + charUnBan = "%s har fjernet %s fra karakter utestengt listen.", + charNotBanned = "Denne karakteren er ikke utestengt.", + containerPassword = "Du har satt dette lagerets passord til %s.", + containerPasswordRemove = "Du har fjernet dette lagerets passord.", + containerPasswordWrite = "Skriv inn passordet.", + wrongPassword = "Du har skrivd inn feil passord.", + cheapBlur = "Deaktiver uklarhet? (Bedre FPS)", + quickSettings = "Hurtiginnstillinger", + vmSet = "Du har satt ditt mobilsvar.", + vmRem = "Du har fjernet ditt mobilsvar.", + altLower = "Gjemme hendene når senket?", + noPerm = "Du er ikke tillatt til å gjøre dette.", + youreDead = "Du er død.", + injMajor = "Ser kritisk skadd ut.", + injLittle = "Ser skadd ut.", + toggleESP = "Veksle Admin ESP", + chgName = "Endre navn", + chgNameDesc = "Skriv in karakterens nye navn under.", + thirdpersonToggle = "Veksle Tredje-Person", + thirdpersonClassic = "Bruk klassisk Tredje-Person", + equippedBag = "Posen at du flyttet har utstyrt element.", + useTip = "Bruk dette elementet.", + equipTip = "Ta på dette elementet.", + unequipTip = "Ta av dette elementet.", + consumables = "Forbruksvarer", + plyNotValid = "Du ser ikke på en gyldig spiller.", + restricted = "Du har blitt tilbakeholdne.", + viewProfile = "Se Steam Profil", + salary = "Du har motatt %s fra lønnen din.", + noRecog = "Du kjenner ikke denne personen.", + curTime = "Tiden er %s.", + vendorEditor = "Leverandør Redigering", + edit = "Rediger", + disable = "Deaktiver", + vendorPriceReq = "Skriv in den nye prisen for dette elementet.", + vendorEditCurStock = "Rediger nåværende lager", + you = "Du", + vendorSellScale = "Utsalgspris skala", + vendorNoTrade = "Du er ikke i stand til å handle med denne leverandøren.", + vendorNoMoney = "Denne leverandøren ikke har råd til dette elementet.", + vendorNoStock = "Denne leverandøren har ikke at varen på lager.", + contentTitle = "Helix Innhold Mangler", + contentWarning = "Du har ikke Helix innhold montert. Dette kan føre til enkelte funksjoner mangler. \nVil du åpne Workshop side for Helix innhold?", + flags = "Flagg" +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_polish.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_polish.lua new file mode 100644 index 0000000..d435eb7 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_polish.lua @@ -0,0 +1,446 @@ +-- Autorzy: zgredinzyyy (Poprawki + Brakujące rzeczy) || Veran120, Michał, Lechu2375 https://github.com/lechu2375/helix-polishlocalization/blob/master/sh_polish.lua + +NAME = "Polski" + +LANGUAGE = { + helix = "Helix", + + introTextOne = "fist industries prezentuje", + introTextTwo = "w współpracy z %s", + introContinue = "wciśnij spację by kontynuować", + + helpIdle = "Wybierz kategorię", + helpCommands = "Komendy z parametrami w są wymagane, a w [nawiasach kwadratowych] są opcjonalne", + helpFlags = "Flagi z zielonym tłem są dostępne przez tą postać.", + + creditSpecial = "Podziękowania dla", + creditLeadDeveloper = "Główny Developer", + creditUIDesigner = "UI Designer", + creditManager = "Project Manager", + creditTester = "Lead Tester", + + chatTyping = "Pisze...", + chatTalking = "Mówi...", + chatYelling = "Krzyczy...", + chatWhispering = "Szepcze...", + chatPerforming = "Wykonuje...", + chatNewTab = "Nowa karta", + chatReset = "Zresetuj pozycję", + chatResetTabs = "Resetuj karty", + chatCustomize = "Personalizuj...", + chatCloseTab = "Zamknij kartę", + chatTabName = "Nazwa karty", + chatAllowedClasses = "Dostępne Klasy czatów", + chatTabExists = "Karta z taką nazwą już instnieje!", + chatMarkRead = "Odznacz wszystko jako przeczytane", + + community = "Community", + checkAll = "Zaznacz wszystko", + uncheckAll = "Odznacz wszystko", + color = "Kolor", + type = "Typ", + display = "Wyświetlanie", + loading = "Ładowanie", + dbError = "Połączenie z bazą danych nie powiodło się", + unknown = "Nieznane", + noDesc = "Opis niedostępny", + create = "Stwórz", + update = "Zaktualizuj", + load = "Załaduj postać", + loadTitle = "Załaduj swoją postać", + leave = "Opuść serwer", + leaveTip = "Opuść ten serwer.", + ["return"] = "Powrót", + returnTip = "Powróć do poprzedniego menu.", + proceed = "Kontynuuj", + faction = "Frakcja", + skills = "Umiejętności", + choose = "Wybierz", + chooseFaction = "Wybierz Frakcje", + chooseDescription = "Zdefiniuj swoją postać", + chooseSkills = "Dostosuj swoje umiejętności", + name = "Imię i Nazwisko", + description = "Opis", + model = "Model", + attributes = "Atrybuty", + attribPointsLeft = "Pozostałe punkty", + charInfo = "Informacje o postaci", + charCreated = "Udało ci się stworzyć swoją postać.", + charCreateTip = "Wypełnij pola poniżej i klinij 'Zakończ' aby stworzyć swoją postać.", + invalid = "Podałeś niewłaściwe(ą) %s", + nameMinLen = "Twoje imię musi zawierać conajmniej %d znaków!", + nameMaxLen = "Twoje imię nie może posiadać więcej niż %d znaków!", + descMinLen = "Twoje opis musi zawierać conajmniej %d znaków!", + maxCharacters = "Nie możesz utworzyć więcej postaci!", + player = "Gracz", + finish = "Zakończ", + finishTip = "Ukończ tworzenie postaci.", + needModel = "Musisz wybrać poprawny model!", + creating = "Twoja postać jest aktualnie tworzona...", + unknownError = "Wystąpił nieznany błąd!", + areYouSure = "jesteś pewien?", + delete = "Usuń", + deleteConfirm = "Ta postać będzie bezpowrotnie usunięta!", + deleteComplete = "%s został(a) usunięty(a).", + no = "Nie", + yes = "Tak", + close = "Zamknij", + save = "Zapisz", + itemInfo = "Imię Nazwisko: %s\nOpis: %s", + itemCreated = "Przedmiot(y) pomyślnie utworzony.", + cloud_no_repo = "Repozytorium, które zostało podane jest nieprawidłowe.", + cloud_no_plugin = "Podany plugin jest nieprawidłowy.", + inv = "Ekwipunek", + plugins = "Pluginy", + pluginLoaded = "%s włączył \"%s\" plugin na następne załadowanie mapy.", + pluginUnloaded = "%s wyłączył \"%s\" plugin z następnego załadowania mapy.", + loadedPlugins = "Załadowane Pluginy", + unloadedPlugins = "Niezaładowane Pluginy", + on = "On", + off = "Off", + author = "Autor", + version = "Wersja", + characters = "Postacie", + business = "Biznes", + settings = "Opcje", + config = "Konfiguracja", + chat = "Czat", + appearance = "Wygląd", + misc = "Różne", + oocDelay = "Musisz poczekać %s sekund przed ponownym użyciem OOC.", + loocDelay = "Musisz poczekać %s sekund przed ponownym użyciem LOOC.", + usingChar = "Aktualnie używasz tej postaci.", + notAllowed = "Przepraszamy, nie masz uprawnień do zrobienia tego.", + itemNoExist = "Przedmiot o który prosiłeś nie istnieje.", + cmdNoExist = "Taka komenda nie istnieje.", + charNoExist = "Nie znaleziono pasującej postaci.", + plyNoExist = "Nie znaleziono pasującego gracza.", + cfgSet = "%s ustawił \"%s\" na %s.", + drop = "Wyrzuć", + dropTip = "Upuszcza ten przedmiot z twojego ekwipunku.", + take = "Podnieś", + takeTip = "Weź ten przedmiot i umieść go w swoim ekwipunku.", + dTitle = "Drzwi do kupienia", + dTitleOwned = "Wykupione Drzwi", + dIsNotOwnable = "Tych drzwi nie można kupić.", + dIsOwnable = "Możesz kupić te drzwi naciskając F2.", + dMadeUnownable = "Uczyniłeś te drzwi niemożliwymi do kupienia.", + dMadeOwnable = "Uczyniłeś te drzwi możliwymi do kupienia.", + dNotAllowedToOwn = "Nie możesz kupić tych drzwi.", + dSetDisabled = "Wyłączyłeś te drzwi z użytku.", + dSetNotDisabled = "Ponownie można używać tych drzwi.", + dSetHidden = "Schowałeś te drzwi.", + dSetNotHidden = "Usunąłeś te drzwi z ukrytych.", + dSetParentDoor = "Uczyniłeś te drzwi swoimi drzwiami nadrzędnymi.", + dCanNotSetAsChild = "Nie możesz ustawi aby drzwi nadrzędne były drzwiami podrzędnymi.", + dAddChildDoor = "You have added a this door as a child.", + dRemoveChildren = "Usunąłeś wszystkie drzwi podrzędne należące do tych drzwi.", + dRemoveChildDoor = "Te drzwi już nie są drzwiami podrzędnymi.", + dNoParentDoor = "Nie masz ustawionych drzwi nadrzędnych.", + dOwnedBy = "Te drzwi należą do %s.", + dConfigName = "Drzwi", + dSetFaction = "Te drzwi należą teraz do frakcji %s.", + dRemoveFaction = "Te drzwi już nie należą do żadnej frakcji.", + dNotValid = "Nie patrzysz na prawidłowe drzwi.", + canNotAfford = "Nie stać Cię na kupienie tego.", + dPurchased = "Kupiłeś te drzwi za %s.", + dSold = "Sprzedałeś te drzwi za %s.", + notOwner = "Nie jesteś właścicielem tego.", + invalidArg = "Podałeś niepoprawną wartość dla argumentu #%s.", + invalidFaction = "Frakcja, którą podałeś nie została znaleziona!", + flagGive = "%s dał %s następujące flagi: '%s'.", + flagGiveTitle = "Daj Flagi", + flagTake = "%s zabrał od %s następujące flagi: '%s'.", + flagTakeTitle = "Zabierz Flagi", + flagNoMatch = "Musisz posiadać flagę(i) \"%s\" aby wykonać tą czynność.", + panelAdded = "Dodałeś nowy panel.", + panelRemoved = "Usunąłęś %d panel(e)", + textAdded = "Dodałeś tekst.", + textRemoved = "Usunąłeś %s tekst(y).", + moneyTaken = "Znalazłeś %s.", + moneyGiven = "Otrzymałeś %s.", + insufficientMoney = "Nie posiadasz tyle środków!", + businessPurchase = "Kupiłeś %s za %s.", + businessSell = "Sprzedałeś %s za %s.", + businessTooFast = "Zaczekaj przed następnym kupnem!", + cChangeModel = "%s zmienił model gracza %s na %s.", + cChangeName = "%s zmienił imię gracza %s na %s.", + cChangeSkin = "%s zmienił model %s na %s.", + cChangeGroups = "%s zmienił bodygroupy %s \"%s\" na %s.", + cChangeFaction = "%s przeniósł %s do frakcji %s.", + playerCharBelonging = "Ten przedmiot należy do innej postaci należącej do Ciebie.", + spawnAdd = "Dodałeś punkt odradzania dla %s.", + spawnDeleted = "Usunąłeś %s punkt(y) odradzania się.", + someone = "Ktoś", + rgnLookingAt = "Pozwól osobie na którą patrzysz, aby Cię rozpoznawała.", + rgnWhisper = "Pozwól tym, którzy są w zasięgu Twoich szeptów, aby Cię rozpoznawali.", + rgnTalk = "Pozwól tym, którzy są w zasięgu normalnych rozmów, aby Cię rozpoznawali.", + rgnYell = "Pozwól tym, którzy są w zasięgu Twoich krzyków, aby Cię rozpoznawali.", + icFormat = "%s mówi: \"%s\"", + rollFormat = "%s wylosował %s.", + wFormat = "%s szepcze: \"%s\"", + yFormat = "%s krzyczy: \"%s\"", + sbOptions = "Kliknij aby zobaczyć opcje dla %s.", + spawnAdded = "Dodałeś punkt odradzania dla %s.", + whitelist = "%s dodał %s na whitelistę frakcji %s.", + unwhitelist = "%s usunął %s z whitelisty frakcji %s.", + noWhitelist = "Nie masz dostępu do whitelisty na tą postać!", + gettingUp = "Podnosisz się...", + wakingUp = "Wraca Ci świadomość...", + Weapons = "Broń", + checkout = "Idź do kasy (%s)", + purchase = "Kup", + purchasing = "Kupuję...", + success = "Sukces", + buyFailed = "Zakupy nie powiodły się.", + buyGood = "Zakupy udane!", + shipment = "Dostawa", + shipmentDesc = "Ta dostawa należy do %s.", + class = "Klasa", + classes = "Klasy", + illegalAccess = "Nielegalny Dostęp.", + becomeClassFail = "Nie udało Ci się zostać %s.", + becomeClass = "Zostałeś %s.", + setClass = "Ustawiłeś klasę %s na %s.", + attributeSet = "Ustawiłeś %s %s na %s.", + attributeNotFound = "You have specified an invalid attribute!", + attributeUpdate = "Podwyższyłeś %s %s o %s.", + noFit = "Nieposiadasz wystarczająco miejsca, aby zmieścić ten przedmiot!", + itemOwned = "Nie możesz wchodzić w interakcje z przedmiotami, które posiadasz jako inna postać!", + help = "Pomoc", + commands = "Komendy", + doorSettings = "Ustawienia Drzwi", + sell = "Sprzedaj", + access = "Dostęp", + locking = "Blokowanie tego przedmiotu...", + unlocking = "Odblokowywanie tego przedmiotu...", + modelNoSeq = "Twój model nie obsługuje tej animacji.", + notNow = "Nie możesz tego aktualnie zrobić.", + faceWall = "Musisz patrzeć na ścianę aby to wykonać.", + faceWallBack = "Musisz stać tyłem do ściany aby to wykonać.", + descChanged = "Zmieniłeś rysopis swojej postaci.", + noOwner = "Nieprawidłowy właściciel.", + invalidItem = "Wskazałeś nieprawidłowy przedmiot!", + invalidInventory = "Wskazałeś nieprawidłowy ekwipunek!", + home = "Strona Główna", + charKick = "%s wyrzucił %s.", + charBan = "%s zablokował postać %s.", + charBanned = "Ta postać jest zablokowana.", + charBannedTemp = "Ta postać jest tymczasowo zablokowana.", + playerConnected = "%s połączył się z serwerem.", + playerDisconnected = "%s wyszedł z serwera.", + setMoney = "Ustawiłeś ilość pieniędzy %s na %s.", + itemPriceInfo = "Możesz kupić ten przedmiot za %s.\nMożesz sprzedać ten przedmiot za %s", + free = "Darmowe", + vendorNoSellItems = "Nie ma przedmiotów do sprzedania.", + vendorNoBuyItems = "Nie ma przedmiotów do kupienia.", + vendorSettings = "Ustawienia sprzedawców", + vendorUseMoney = "Czy sprzedawcy powinni używać pieniędzy?", + vendorNoBubble = "Ukryć dymek sprzedawcy?", + mode = "Tryb", + price = "Cena", + stock = "Zasób", + none = "Nic", + vendorBoth = "Kupowanie i Sprzedawanie", + vendorBuy = "Tylko kupowanie", + vendorSell = "Tylko sprzedawanie", + maxStock = "Maksymalny zasób", + vendorFaction = "Edytor frakcji", + buy = "Kup", + vendorWelcome = "Witaj w moim sklepie, czy mogę Ci coś podać?", + vendorBye = "Przyjdź niedługo z powrotem!", + charSearching = "Aktualnie szukasz już innej postaci, proszę poczekać.", + charUnBan = "%s odblokował postać %s.", + charNotBanned = "Ta postać nie jest zablokowana.", + quickSettings = "Szybkie Ustawienia", + vmSet = "Ustawiłeś swoją pocztę głosową.", + vmRem = "Usunąłęś swoją pocztę głosową.", + noPerm = "Nie możesz tego zrobić!", + youreDead = "Jesteś martwy", + injMajor = "Widoczne krytyczne obrażenia.", + injLittle = "Widoczne obrażenia.", + chgName = "Zmień Imię i Nazwisko.", + chgNameDesc = "Wprowadź nowę imię i nazwisko postaci poniżej.", + weaponSlotFilled = "Nie możesz użyć kolejnej broni typu %s!", + equippedBag = "Nie możesz przemieszczać torby z wyekwipowanym przedmiotem!", + equippedWeapon = "Nie możesz przemieszczać aktualnie wyekwipowanej broni!", + nestedBags = "Nie możesz wrzucić torby do torby!", + outfitAlreadyEquipped = "Już nosisz ubranie tego typu!", + useTip = "Używa przedmiotu.", + equipTip = "Zakłada przedmiot.", + unequipTip = "Zdejmuje przedmiot.", + consumables = "Towary konsumpcyjne.", + plyNotValid = "Nie patrzysz na prawidłowego gracza.", + restricted = "Zostałeś związany.", + salary = "Otrzymałeś wynagrodzenie w wysokości %s", + noRecog = "Nie poznajesz tej osoby.", + curTime = "Aktualny czas to %s.", + vendorEditor = "Edytor Sprzedawcy", + edit = "Edytuj", + disable = "Wyłącz", + vendorPriceReq = "Wprowadź nową cenę dla tego produktu.", + vendorEditCurStock = "Edytuj aktualny zapas", + vendorStockReq = "Wprowadź ile produktów powinno się znajdować maksymalnie w zasobie", + vendorStockCurReq = "Wprowadź ile przedmiotów jest dostępnych do sprzedarzy z całego zasobu.", + you = "Ty", + vendorSellScale = "Skala ceny sprzedaży", + vendorNoTrade = "Nie możesz dokonać wymiany z tym sprzedawcą!", + vendorNoMoney = "Sprzedawce nie stać na ten przedmiot!", + vendorNoStock = "Sprzedawca nie ma tego produktu aktualnie w asortymencie!", + contentTitle = "Nie znaleziono zawartości dla trybu Helix", + contentWarning = "Zawartość dla trybu Helix nie został wgrana. Rezultatem tego może być brak części funkcji.\nCzy chciałbyś otworzyć stronę warsztatu z daną zawartością?", + flags = "Flagi", + mapRestarting = "Restart mapy za %d sekund!", + chooseTip = "Wybierz postać do gry.", + deleteTip = "Usuń tą postać.", + storageInUse = "Ktoś inny używa tego teraz!", + storageSearching = "Przeszukiwanie...", + container = "Pojemnik", + containerPassword = "Ustawiłeś hasło tego pojemnika na %s.", + containerPasswordRemove = "Usunąłeś hasło z tego pojemnika.", + containerPasswordWrite = "Wprowadź hasło.", + containerName = "Ustawiłeś nazwę tego pojemnika na %s.", + containerNameWrite = "Wprowadź nazwę.", + containerNameRemove = "Usunąłeś nazwę z tego pojemnika.", + containerInvalid = "Musisz patrzeć na pojemnik, aby tego użyć!", + wrongPassword = "Wprowadziłeś złe hasło!", + respawning = "Odradzanie...", + tellAdmin = "Powiadom administrację o tym błędzie: %s", + + mapAdd = "Dodałeś nową scenerie mapy.", + mapDel = "Usunąłęś %d scenerie mapy.", + mapRepeat = "Teraz dodaj punkt drugorzędny", + + scoreboard = "Tabela", + ping = "Ping: %d", + viewProfile = "Obejrzyj profil Steam.", + copySteamID = "Skopiuj Steam ID", + + money = "Pieniądze", + moneyLeft = "Twoje Pieniądze: ", + currentMoney = "Obecna ilość pieniędzy: ", + + invalidClass = "To nie jest odpowiednia klasa!", + invalidClassFaction = "To nie jest poprawna klasa dla tej frakcji!", + + miscellaneous = "Różne", + general = "Generalne", + observer = "Obserwator", + performance = "Wydajnośc", + thirdperson = "Trzecia osoba", + date = "Data", + interaction = "Interakcja", + server = "Serwer", + + resetDefault = "Ustaw domyślnie", + resetDefaultDescription = "To zresetuje \"%s\" do swojej domyślej wartości \"%s\".", + optOpenBags = "Otwórz torbe z ekwipunkiem", + optdOpenBags = "Automatycznie pokazuje wszystkie torby w twoim ekwipunku gdy menu jest otwarte.", + optShowIntro = "Pokaż intro przy wchodzeniu na serwer", + optdShowIntro = "Pokazuje wstęp do Helixa następnym razem gdy będziesz wchodzić. Ta opcja jest zawsze wyłączona po tym gdy obejrzałeś wstęp.", + optCheapBlur = "Wyłącz rozmazanie", + optdCheapBlur = "Zastępuje rozmazanie interfejsu z zwykłym przyciemnieniem.", + optObserverTeleportBack = "Przywraca cię do poprzedniej lokalizacji", + optdObserverTeleportBack = "Przywraca cię do lokalizacji w której włączyłeś tryb obserwatora.", + optObserverESP = "Pokaż ESP administracyjne", + optdObserverESP = "Pokazuje nazwę i lokalizację każdego gracza na serwerze.", + opt24hourTime = "Używaj czasu 24-godzinnego", + optd24hourTime = "Pokazuj znacznik czasu w formacie 24-godzinnym, zamiast 12-godzinnego (AM/PM).", + optChatNotices = "Pokazuj uwagi/ogłoszenia na czacie", + optdChatNotices = "Przenosi wszystkie uwagi/ogłoszenia wyskakujące w prawym górnym rogu do czatu.", + optChatTimestamps = "Show timestamps in chat", + optdChatTimestamps = "Pokazuje godzinę wysłania przy każdej wiadomości na czacie.", + optAlwaysShowBars = "Zawsze pokazuj tabelkę z informacjami", + optdAlwaysShowBars = "Tworzy tabelkę z informacjami w lewym górnym rogu, bez znaczenia czy ma być ukryte czy nie.", + optAltLower = "Ukryj ręce gdy są opuszczone.", + optdAltLower = "Ukrywa ręce, gdy są opuszczone.", + optThirdpersonEnabled = "Włącz trzecią osobe", + optdThirdpersonEnabled = "Przenosi kamerę za ciebie. Również może być włączone w konsoli za pomocą \"ix_togglethirdperson\" ", + optThirdpersonClassic = "Włącz klasyczną trzecią osobe", + optdThirdpersonClassic = "Moves your character's view with your mouse.", + optThirdpersonVertical = "Wysokość kamery", + optdThirdpersonVertical = "Jak wysoko powinna być kamera.", + optThirdpersonHorizontal = "Wyrównanie kamery", + optdThirdpersonHorizontal = "Jak bardzo na lewo lub prawo powinna być kamera.", + optThirdpersonDistance = "Odległość kamery", + optdThirdpersonDistance = "Jak oddalona powinna być kamera.", + optThirdpersonCrouchOffset = "Kamera na wysokości kucania", + optdThirdpersonCrouchOffset = "Jak wysoko powinna być kamera podczas kucania.", + optDisableAnimations = "Wyłącz animacje", + optdDisableAnimations = "Zatrzymuje animację by zapewnić natychmiastowe przejście.", + optAnimationScale = "Skala animacji", + optdAnimationScale = "Jak bardziej szybko lub długo powinny być animacje.", + optLanguage = "Język", + optdLanguage = "Język interfejsu.", + optMinimalTooltips = "Minimalne powiadomienia z HUD'u", + optdMinimalTooltips = "Changes the HUD tooltip style to take up less space.", + optNoticeDuration = "Długość powiadomienia", + optdNoticeDuration = "Jak długo powinny wyświetlać się uwagi/ogłoszenia (w sekundach).", + optNoticeMax = "Maksimum powiadomień", + optdNoticeMax = "Ilość powiadomień zanim nadmiar zostanie usunięty.", + optChatFontScale = "Rozmiar czcionki", + optdChatFontScale = "Skalowanie czcionki na czacie.", + optChatOutline = "Obrysuj tekst w czacie", + optdChatOutline = " Obramowuje tekst czatu. Włącz to, jeśli masz problemy z czytaniem czatu.", + + cmdRoll = "Losuje liczbę pomiędzy 0 a wyznaczoną liczbą.", + cmdPM = "Wysyła prywatną wiadomośc do kogoś.", + cmdReply = "Wysyła prywatną wiadomośc do ostatniej osoby, od której otrzymałeś wiadomość.", + cmdSetVoicemail = "Ustawia lub usuwa automatyczną odpowiedź gdy ktoś wysyła ci prywatną wiadomość.", + cmdCharGiveFlag = "Daje wyznaczoną flagę(i) do danej osoby.", + cmdCharTakeFlag = "Usuwa wyznaczoną flagę(i) osobie jeśli ją ma.", + cmdToggleRaise = "Podnosi albo opuscza broń, którą trzymasz.", + cmdCharSetModel = "Ustawia model postaci.", + cmdCharSetSkin = "Ustawia skórkę dla danej postaci.", + cmdCharSetBodygroup = "Ustawia bodygroupy dla danego modelu.", + cmdCharSetAttribute = "Ustawia poziom danego atrybutu dla osoby.", + cmdCharAddAttribute = "Dodaje poziom do danego atrybutu.", + cmdCharSetName = "Zmienia nazwę na wyznaczoną nazwę.", + cmdCharGiveItem = "Daje wyznaczony przedmiot osobie.", + cmdCharKick = "Zmusza osobę do wyjścia z jej postaci.", + cmdCharBan = "Zabrania osobie wchodzenia na danej postaci.", + cmdCharUnban = "Unban na zbanowaną postać.", + cmdGiveMoney = "Daje wyznaczoną ilość pieniędzy osobie, na którą patrzysz", + cmdCharSetMoney = "Zmienia ilośc pieniędzy do wyznaczonej ilośći.", + cmdDropMoney = "Wyrzuca wyznaczoną ilość pieniędzy przed tobą.", + cmdPlyWhitelist = "Pozwala osobie na stworzenie postaci w wyznaczonej frakcji.", + cmdCharGetUp = "Sprróbuj wstać po upadku.", + cmdPlyUnwhitelist = "Usuwa osobie whiteliste z danej frakcji.", + cmdCharFallOver = "Walisz salto na plecy", + cmdBecomeClass = "Zostań daną klasą w obecnej frakcji.", + cmdCharDesc = "Ustaw opis zewnętrzny postaci.", + cmdCharDescTitle = "Opis zewnętrzny", + cmdCharDescDescription = "Napisz opis zewnętrzny twojej postaci.", + cmdPlyTransfer = "Przenosi osobę do wyznaczonej frakcji.", + cmdCharSetClass = "Zmusza osobę do zostania wyznaczoną klasą w obecnej frakcji.", + cmdMapRestart = "Restartuje mape po wyznaczonej ilości czasu.", + cmdPanelAdd = "Dodaje Web Panel.", + cmdPanelRemove = "Usuwa Web Panel na który patrzysz.", + cmdTextAdd = "Dodaje napis.", + cmdTextRemove = "Usuwa napis na który patrzysz.", + cmdMapSceneAdd = "Dodaje punkt widokowy widoczny w menu postaci.", + cmdMapSceneRemove = "Usuwa punkt widokowy widoczny w menu postaci.", + cmdSpawnAdd = "Dodaje punkt odrodzenia danej frakcji.", + cmdSpawnRemove = "Usuwa punkt odrodzenia na który patrzysz.", + cmdAct = "Wykonuje animację %s .", + cmdContainerSetPassword = "Ustawia hasło kontenera na który patrzysz.", + cmdDoorSell = "Sprzedaje drzwi na które patrzysz.", + cmdDoorBuy = "Kupuje drzwi na które patrzysz.", + cmdDoorSetUnownable = "Ustawia drzwi na które patrzysz na niemożliwe do posiadania.", + cmdDoorSetOwnable = "Ustawia drzwi na które patrzysz na możliwe do posiadania.", + cmdDoorSetFaction = "Ustawia drzwi na które patrzysz na posiadane przed daną frakcję.", + cmdDoorSetDisabled = "Zabrania wykonywania komend na drzwi na które patrzysz.", + cmdDoorSetTitle = "Ustawia opis drzwi na które patrzysz.", + cmdDoorSetParent = "Ustawia właściciela danych drzwi.", + cmdDoorSetChild = "Ustawia podwłaścicieli danych drzwi.", + cmdDoorRemoveChild = "Usuwa podwłaściciela danych drzwi.", + cmdDoorSetHidden = "Ukrywa opis drzwi na które patrzysz. Wciąż są możliwe do posiadania.", + cmdDoorSetClass = "Ustawia drzwi na które patrzysz na posiadane przez daną klasę.", + cmdMe = "Wykonaj akcję fizyczną.", + cmdIt = "Wykonaj akcję twojego otoczenia.", + cmdW = "Szeptaj.", + cmdY = "Krzycz.", + cmdEvent = "Wykonuje akcję, którą każdy widzi.", + cmdOOC = "Wysyła wiadomość na czacie out-of-character.", + cmdLOOC = "Wysyła wiadomość na lokalnym czacie out-of-character." +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_portuguese.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_portuguese.lua new file mode 100644 index 0000000..136c677 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_portuguese.lua @@ -0,0 +1,433 @@ +NAME = "Portuguese" + +LANGUAGE = { + helix = "Helix", + + introTextOne = "fist industries apresenta", + introTextTwo = "em colaboração com %s", + introContinue = "pressione espaço para continuar", + + helpIdle = "Selecione uma categoria", + helpCommands = "Parametros de comando são são requiridos, enquanto [colchetes] são opcionais.", + helpFlags = "As flags com um fundo verde são acessíveis por este personagem.", + + creditSpecial = "Agradecimentos especiais", + creditLeadDeveloper = "Desenvolvedor Principal", + creditUIDesigner = "Designer de interface do usuário", + creditManager = "Gestor de projeto", + creditTester = "Testador principal", + + chatTyping = "Escrevendo...", + chatTalking = "Falando...", + chatYelling = "Gritando...", + chatWhispering = "Cochichando...", + chatPerforming = "Atuando...", + chatNewTab = "Nova aba...", + chatReset = "Redefinir posição", + chatResetTabs = "Redefinir abas", + chatCustomize = "Customizar...", + chatCloseTab = "Fechar aba", + chatTabName = "Nome da aba", + chatAllowedClasses = "Classe de bate-papo permitidas", + chatTabExists = "Uma aba de chat com esse nome já existe!", + chatMarkRead = "Marcar tudo como lido", + + community = "Comunidade", + checkAll = "Selecionar tudo", + uncheckAll = "Deselecionar tudo", + color = "Cor", + type = "Tipo", + display = "Visualização", + loading = "Carregando", + dbError = "Falha na conexão com o banco de dados", + unknown = "Desconhecido", + noDesc = "Sem descrição disponível", + create = "Criar", + update = "Atualizar", + load = "Carregar personagem", + loadTitle = "Carregar um personagem", + leave = "Sair", + leaveTip = "Sair deste servidor.", + ["return"] = "Retornar", + returnTip = "Retornar para o menu anterior.", + proceed = "Prosseguir", + faction = "Facção", + skills = "Habilidades", + choose = "Escolher", + chooseFaction = "Escolha uma facção", + chooseDescription = "Descreva a aparência do seu personagem", + chooseSkills = "Ajuste suas habilidades", + name = "Nome", + description = "Descrição", + model = "Modelo", + attributes = "Atributos", + attribPointsLeft = "Pontos restantes", + charInfo = "Character Information", + charCreated = "Você criou seu personagem com sucesso.", + charCreateTip = "Preencha os campos abaixo e pressione 'Finalizar' para criar o seu personagem.", + invalid = "Você providênciou um %s inválido!", + nameMinLen = "Seu nome deve conter ao menos %d caracteres!", + nameMaxLen = "Seu nome não pode ser maior que %d caracteres!", + descMinLen = "Sua descrição precisa conter ao menos %d caracteres!", + maxCharacters = "Você não pode criar mais personagens!", + player = "Jogador", + finish = "Finalizar", + finishTip = "Finalizar a criação do personagem.", + needModel = "Você precisa escolher um modelo válido!", + creating = "O seu personagem está sendo criado...", + unknownError = "Um erro desconhecido ocorreu!", + areYouSure = "Você tem certeza?", + delete = "Deletar", + deleteConfirm = "Este personagem será irrevogavelmente removido!", + deleteComplete = "%s foi deletado.", + no = "Não", + yes = "Sim", + close = "Fechar", + save = "Salvar", + itemInfo = "Nome: %s\nDescrição: %s", + itemCreated = "Item(ns) criado com sucesso.", + cloud_no_repo = "O repositório fornecido não é válido!", + cloud_no_plugin = "O plugin providênciado não é válido!", + inv = "Inventário", + plugins = "Plugins", + author = "Autor", + version = "Versão", + characters = "Personagens", + business = "Négocios", + settings = "Opções", + config = "Configuração", + chat = "Chat", + appearance = "Aparência", + misc = "Diversos", + oocDelay = "Você precisa aguardar %s mais segundos antes de usar o OOC novamente!", + loocDelay = "Você precisa aguardar %s mais segundos antes de usar o LOOC novamente!", + usingChar = "Você já está utilizando este personagem.", + notAllowed = "Você não tem permissão para fazer isso!", + itemNoExist = "O item que você solicitou não existe!", + cmdNoExist = "Esse comando não existe!", + charNoExist = "Um personagem correspondente não foi encontrado!", + plyNoExist = "Um jogador correspondente não foi encontrado!", + cfgSet = "%s definiu \"%s\" para %s.", + drop = "Soltar", + dropTip = "Solta esse item do seu inventário.", + take = "Pegar", + takeTip = "Pega este item e coloca no seu inventário.", + dTitle = "Porta sem proprietário", + dTitleOwned = "Porta Comprada", + dIsNotOwnable = "Esta porta não pode ser comprada.", + dIsOwnable = "Você pode comprar esta porta pressionando F2.", + dMadeUnownable = "Você tornou esta porta 'não comprável'.", + dMadeOwnable = "Você tornou esta porta 'comprável'.", + dNotAllowedToOwn = "Você não tem permissão para possuir esta porta!", + dSetDisabled = "Você tornou esta porta 'desativada'.", + dSetNotDisabled = "Você tornou esta porta 'ativada'.", + dSetHidden = "Você tornou esta porta oculta.", + dSetNotHidden = "Você tornou esta porta não mais oculta.", + dSetParentDoor = "Você definiu esta porta como sua porta mãe.", + dCanNotSetAsChild = "Você não pode definir a porta dos pais sendo criança!", + dAddChildDoor = "Você adicionou essa porta como criança.", + dRemoveChildren = "Você removeu todas as crianças desta porta.", + dRemoveChildDoor = "Você removeu esta porta de ser uma criança.", + dNoParentDoor = "Você não tem uma porta mãe estabelecida.", + dOwnedBy = "Esta porta é de propriedade de %s.", + dConfigName = "Portas", + dSetFaction = "Esta porta agora pertence a facção %s.", + dRemoveFaction = "Esta porta não pertence mais a nenhuma facção.", + dNotValid = "Você não está olhando para nenhuma porta válida!", + canNotAfford = "Você não pode pagar por isto!", + dPurchased = "Você comprou essa porta por %s.", + dSold = "Você vendeu esta porta por %s.", + notOwner = "Você não é o dono disso!", + invalidArg = "Você forneceu um valor inválido para o argumento #%s!", + invalidFaction = "A facção que você forneceu não pôde ser encontrada!", + flagGive = "%s atribuiu a %s as flags '%s'.", + flagGiveTitle = "Dar Flags", + flagTake = "%s revogou as flags '%s' de %s.", + flagTakeTitle = "Revogar Flags", + flagNoMatch = "Você precisa possuir as flags \"%s\" para realizar esta ação!", + textAdded = "Você adicionou um texto.", + textRemoved = "Você removeu o(s) texto(s) %s .", + moneyTaken = "Você recebeu %s.", + moneyGiven = "Você deu %s.", + insufficientMoney = "Você não tem o suficiente para fazer isso!", + businessPurchase = "Você comprou %s por %s.", + businessSell = "Você vendeu %s por %s.", + businessTooFast = "Por favor espere antes de comprar outro item!", + cChangeModel = "%s mudou o modelo de %s para %s.", + cChangeName = "%s mudou o nome de %s para %s.", + cChangeSkin = "%s mudou a 'skin' de %s para %s.", + cChangeGroups = "%s mudou o 'bodygroup' \"%s\" de %s para %s.", + cChangeFaction = "%s mudou %s para a facção %s.", + playerCharBelonging = "Este objeto pertençe ao seu outro personagem!", + spawnAdd = "Você adiconou um ponto de ressurgimento para %s.", + spawnDeleted = "Você removeu %s ponto(s) de ressurgimento.", + someone = "Alguém", + rgnLookingAt = "Permite que a pessoa que você está olhando reconheça você.", + rgnWhisper = "Permita que te reconheça aqueles que te escutam sussurar.", + rgnTalk = "Permite que aqueles que te escutam falando reconheça você.", + rgnYell = "Permite que aqueles que te escutam gritando rechoneça você.", + icFormat = "%s disse \"%s\"", + rollFormat = "%s jogou os dados e tirou %s.", + wFormat = "%s susurra \"%s\"", + yFormat = "%s grita \"%s\"", + sbOptions = "Clique para ver opções de %s.", + spawnAdded = "Você adicionou um ponto de ressurgimento para %s.", + whitelist = "%s adicionou %s à facção %s.", + unwhitelist = "%s retirou %s da facção %s.", + noWhitelist = "Você não tem permissão para usar este personagem!", + gettingUp = "Você está se levantando...", + wakingUp = "Você está retomando consciência...", + Weapons = "Armas", + checkout = "Ir ao Cheque (%s)", + purchase = "Comprar", + purchasing = "Comprando...", + success = "Sucesso", + buyFailed = "Falha na compra!", + buyGood = "Compra sucesiva!", + shipment = "Carga", + shipmentDesc = "Esta carga pertence à %s.", + class = "Classe", + classes = "Classes", + illegalAccess = "Acesso Ilegal.", + becomeClassFail = "Você não pode se tornar um(a) %s!", + becomeClass = "Você se tornou em um(a) %s.", + setClass = "Você mudou a classe de %s para %s.", + attributeSet = "Estabeleceu o atributo de %s de %s à %s.", + attributeNotFound = "Você especificou um atributo inválido!", + attributeUpdate = "You added %s's %s by %s.", + noFit = "Você não tem espaço o suficiente para este item!", + itemOwned = "You cannot interact with an item that you own on a different character!", + help = "Ajuda", + commands = "Comandos", + doorSettings = "Propiedades da Porta", + sell = "Vender", + access = "Acessar", + locking = "Trancando esta entidade...", + unlocking = "Destrancando esta entidade...", + modelNoSeq = "Seu modelo não suporta esta ação!", + notNow = "Você não tem permissão para fazer isto agora!", + faceWall = "Você precisa encarar uma parede para fazer isto!", + faceWallBack = "Você precisa estar contra a parede para fazer isto!", + descChanged = "Você mudou a descrição do seu personagem.", + noOwner = "O dono é inválido!", + invalidItem = "Você especificou um item inválido!", + invalidInventory = "Você especificou um inventário inválido!", + home = "Início", + charKick = "%s expulsou o personagem %s.", + charBan = "%s baniu o personagem %s.", + charBanned = "Este personagem está banido.", + playerConnected = "%s conectou-se ao servidor.", + playerDisconnected = "%s desconectou-se do servidor.", + setMoney = "Você ajustou o dinheiro de %s para %s.", + itemPriceInfo = "Você pode comprar este item por %s.\nVocê pode vender este item por %s", + free = "Grátis", + vendorNoSellItems = "Não há items para vender.", + vendorNoBuyItems = "Não há items para comprar.", + vendorSettings = "Propiedades do Fornecedor", + vendorUseMoney = "Fornecedor deve usar dinheiro?", + vendorNoBubble = "Esconder o balão do vendedor?", + mode = "Modo", + price = "Preço", + stock = "Estoque", + none = "Nenhum", + vendorBoth = "Comprar e Vender", + vendorBuy = "Apenas Comprar", + vendorSell = "Apenas Vender", + maxStock = "Estoque Máximo", + vendorFaction = "Editar Facção", + buy = "Comprar", + vendorWelcome = "Bem-vindo, o que posso lhe oferecer hoje?", + vendorBye = "Até mais!", + charSearching = "Você já está procurando um outro personagem!", + charUnBan = "%s desbaniu o personagem %s.", + charNotBanned = "Este personagem não está banido!", + quickSettings = "Configurações Rápidas", + vmSet = "Você ajustou seu correio de voz.", + vmRem = "Você removeu seu correio de voz.", + noPerm = "Você não é permitido a fazer isto!", + youreDead = "Você está Morto.", + injMajor = "Parece críticamente lesionado", + injLittle = "Parece lesionado", + chgName = "Mudar Nome", + chgNameDesc = "Insira o nome do personagem abaixo.", + weaponSlotFilled = "Você não pode usar outra arma de %s!", + equippedBag = "Você não pode mover uma bolsa que tem um item equipado!", + equippedWeapon = "Você não pode mover uma arma que está sendo equipada!", + nestedBags = "Você não pode colocar um inventário dentro de outro!", + outfitAlreadyEquipped = "Você já está vestindo este tipo de roupa!", + useTip = "Usa este item.", + equip = "Equipar", + equipTip = "Equipa o item..", + unequip = "Desequipar", + unequipTip = "Desequipa o item.", + consumables = "Consumíveis", + plyNotValid = "Você não está olhando para um jogador válido!", + restricted = "Você foi preso.", + salary = "Você recebeu %s do seu salário.", + noRecog = "Você não reconhece esta pessoa.", + curTime = "A hora é %s.", + vendorEditor = "Editar Fornecedor", + edit = "Editar", + disable = "Desativar", + vendorPriceReq = "Insira um novo preço para este item.", + vendorEditCurStock = "Edite o Estoque Atual", + vendorStockReq = "Insira a quantidade Máxima de Estoque o item deve ter.", + vendorStockCurReq = "Insira quantos itens estão disponíveis para compra do estoque máximo.", + you = "Você", + vendorSellScale = "Escala do preço de venda", + vendorNoTrade = "Você não consegue trocar com este fornecedor!", + vendorNoMoney = "Este fornecedor não consegue pagar este item!", + vendorNoStock = "Este fornecedor não possui esse item no estoque!", + contentTitle = "Faltando Conteúdo do Helix", + contentWarning = "Você não tem o conteúdo do Helix instalado. Isto pode resultar na falta de alguns aspectos.\nVocê gostaria de abrir a página da Workshop para baixar o conteúdo?", + flags = "Flags", + mapRestarting = "O mapa irá reiniciar em %d segundos!", + chooseTip = "Escolha este personagem para jogar.", + deleteTip = "Delete este personagem.", + storageInUse = "Alguém já está usando isto!", + storageSearching = "Procurando...", + container = "Container", + containerPassword = "Você ajustou a senha deste container para %s.", + containerPasswordRemove = "Você retirou a senha deste container.", + containerPasswordWrite = "Insira a senha.", + containerName = "Você mudou o nome deste container para %s.", + containerNameWrite = "Insira o nome.", + containerNameRemove = "Você removeu o nome deste container.", + containerInvalid = "Você precisa estar olhando para um container para fazer isto!", + wrongPassword = "Você inseriu uma senha errada!", + respawning = "Ressurgindo...", + tellAdmin = "Informe um membro da staff sobre este erro: %s", + + scoreboard = "Scoreboard", + ping = "Ping: %d", + viewProfile = "Ver Perfil da Steam", + copySteamID = "Copiar Steam ID", + + money = "Dinheiro", + moneyLeft = "Seu Dinheiro: ", + currentMoney = "Dinheiro Restante: ", + + invalidClass = "Essa não é uma classe válida!", + invalidClassFaction = "Essa não é uma classe válida para esta facção!", + + miscellaneous = "Diversos", + general = "Geral", + observer = "Observador", + performance = "Performance", + thirdperson = "Terceira Pessoa", + date = "Data", + interaction = "Interação", + server = "Servidor", + + resetDefault = "Reiniciar para o padrão", + resetDefaultDescription = "Isto irá reiniciar \"%s\" ao seu valor padrão de \"%s\".", + optOpenBags = "Abrir bolsa com inventário", + optdOpenBags = "Automáticamente vê todas as bolsas em seu inventário quando o menu é aberto.", + optShowIntro = "Mostrar intro ao entrar", + optdShowIntro = "Mostra a introdução do Helix na próxima vez que entrar. Isto é sempre desativado depois que já assiste.", + optCheapBlur = "Desativar embaçamento", + optdCheapBlur = "Substitui o embaçamento da UI com uma ofuscação simples.", + optObserverTeleportBack = "Retornar ao último local", + optdObserverTeleportBack = "Retorna você ao local que você usou o modo observador..", + optObserverESP = "Mostrar admin ESP", + optdObserverESP = "Mostra o nome e o local de cada jogador no servidor.", + opt24hourTime = "Usar horário de 24 horas.", + optd24hourTime = "Mostra horários em formato de 24 horas, ao em vez de 12 (AM/PM).", + optChatNotices = "Mostrar noticias no chat", + optdChatNotices = "Coloca todas as notícias que aparecem no topo da tela no chat.", + optChatTimestamps = "Mostrar horários no chat", + optdChatTimestamps = "Acopla o horário à cada mensagem enviada no chat.", + optAlwaysShowBars = "Sempre mostrar barras de informação", + optdAlwaysShowBars = "Mostra as informações de barra no topo esquerdo da tela, mesmo se estiverem escondidas.", + optAltLower = "Esconder mãos quando abaixadas", -- @todo remove me + optdAltLower = "Esconde suas mãos quando elas estão ao lado.", -- @todo remove me + optThirdpersonEnabled = "Ativar terceira pessoa", + optdThirdpersonEnabled = "Move a câmera para atrás de você. Também pode ser ativo com o comando \"ix_togglethirdperson\".", + optThirdpersonClassic = "Ativar terceira pessoa clássico", + optdThirdpersonClassic = "Move a visão do seu personagem com o mouse.", + optThirdpersonVertical = "Altura da câmera", + optdThirdpersonVertical = "O quão alto a câmera deve estar.", + optThirdpersonHorizontal = "Posição da câmera", + optdThirdpersonHorizontal = "O quão para a direita ou esquerda a câmera deve estar.", + optThirdpersonDistance = "Distância da câmera", + optdThirdpersonDistance = "O quão longe a câmera deve estar.", + optThirdpersonCrouchOffset = "Altura da câmera agachada.", + optdThirdpersonCrouchOffset = "O quão alto a câmera deve estar enquanto agachado.", + optDisableAnimations = "Desativar animações", + optdDisableAnimations = "Desativa as animações do menu do Helix para transições instantâneas..", + optAnimationScale = "Escala da Animação", + optdAnimationScale = "O quão rápido ou devagar as animações devem estar.", + optLanguage = "Língua", + optdLanguage = "A língua mostrada na UI do Helix.", + optMinimalTooltips = "Dicas minimalistas no HUD", + optdMinimalTooltips = "Muda o estilo de dicas no HUD para tomar menos espaço.", + optNoticeDuration = "Duração de noticia", + optdNoticeDuration = "Por quanto tempo mostrar noticias (em segundos).", + optNoticeMax = "Máximo de noticias", + optdNoticeMax = "O número máximo de noticias até que noticias passadas sejam removidas.", + optChatFontScale = "Tamanho da fonte do Chat", + optdChatFontScale = "O quão grande ou pequeno a fonte deve ser.", + optChatOutline = "Borda no texto do Chat", + optdChatOutline = "Desenha uma borda ao redor do texto mostrado no Chat. Ative caso não consiga ver o texto.", + + cmdRoll = "Rola um número entre 0 e o número especificado.", + cmdPM = "Manda uma mensagem privada para alguém.", + cmdReply = "Manda uma mensagem privada para a última pessoa que lhe mandou uma mensagem.", + cmdSetVoicemail = "Ajuste ou remova a mensagem de resposta automática quando alguém lhe manda uma mensagem.", + cmdCharGiveFlag = "Dá a(s) flag(s) especificadas para alguém.", + cmdCharTakeFlag = "Remove a(s) flag(s) especificadas de alguém caso tenham.", + cmdToggleRaise = "Levanta ou abaixa a arma que está segurando.", + cmdCharSetModel = "Ajusta o modelo do personagem da pessoa.", + cmdCharSetSkin = "Ajusta a skin para o modelo do personagem da pessoa.", + cmdCharSetBodygroup = "Ajusta o bodygroup para o modelo do personagem da pessoa.", + cmdCharSetAttribute = "Ajusta o nível do atributo especificado para a pessoa.", + cmdCharAddAttribute = "Adiciona um nível ao atributo especificado para a pessoa.", + cmdCharSetName = "Muda o nome de alguém para o nome especificado.", + cmdCharGiveItem = "Dá o item especificado para alguém.", + cmdCharKick = "Faz alguém sair do personagem que está jogando forçadamente.", + cmdCharBan = "Proibe alguém de entrar no servidor com o personagem atual.", + cmdCharUnban = "Permite que um personagem que foi banido possa ser usado novamente.", + cmdGiveMoney = "Dá uma quantia de dinheiro especificada á pessoa que está olhando.", + cmdCharSetMoney = "Muda a quantia total de dinheiro de alguém para a quantia especificada.", + cmdDropMoney = "Deixa cair a quantia especificada de dinheiro em uma caixinha à sua frente.", + cmdPlyWhitelist = "Permite que alguém crie um personagem na facção especificada.", + cmdCharGetUp = "Tenta se levantar após ter caído.", + cmdPlyUnwhitelist = "Tira a permissão de alguém para criar um personagem na facção especificada.", + cmdCharFallOver = "Faz você cair.", + cmdBecomeClass = "Tenta se tornar parte da classe especificada na sua facção.", + cmdCharDesc = "Ajusta a tua descrição física.", + cmdCharDescTitle = "Descrição Física", + cmdCharDescDescription = "Insira a descrição física de teu personagem.", + cmdPlyTransfer = "Transfere alguém para a facção designada.", + cmdCharSetClass = "Força alguém a se tornar a classe especificada na facção da pessoa.", + cmdMapRestart = "Reinicia o mapa após um certo intervalo de tempo.", + cmdPanelAdd = "Coloca um painel da Web no mundo.", + cmdPanelRemove = "Remove o painel que está a olhar.", + cmdTextAdd = "Adiciona um bloco de texto no mundo.", + cmdTextRemove = "Remove o bloco de texto que está a olhar.", + cmdMapSceneAdd = "Adiciona uma câmera cinemática que pode ser vista do menu.", + cmdMapSceneRemove = "Removes a camera viewpoint that is shown in the character menu.", + cmdSpawnAdd = "Adiciona um ponto de ressurgimento para a facção especificada.", + cmdSpawnRemove = "Remove qualquer ponto de ressurgimento que esteja a olhar.", + cmdAct = "Faz a animação de %s", + cmdContainerSetPassword = "Ajuste a senha do container que está a olhar.", + cmdDoorSell = "Venda a porta que está a olhar.", + cmdDoorBuy = "Compra a porta que está a olhar.", + cmdDoorSetUnownable = "Faz a porta que está a olhar incomprável.", + cmdDoorSetOwnable = "Faz a porta que está a olhar comprável.", + cmdDoorSetFaction = "Designa a porta que está a olhar à uma facção especificada.", + cmdDoorSetDisabled = "Faz com que seja impossível realizar comandos na porta que está a olhar.", + cmdDoorSetTitle = "Coloca um título na porta que está a olhar.", + cmdDoorSetParent = "Define o parentesco de um par de portas.", + cmdDoorSetChild = "Define o herdeiro de um par de portas.", + cmdDoorRemoveChild = "Remove o herdeiro de um par de portas.", + cmdDoorSetHidden = "Esconde a descrição da porta que está a olhar, mas ainda permite que alguém a compre.", + cmdDoorSetClass = "Permite que a porta que está a olhar seja propiedade da classe especificada.", + cmdMe = "Faça uma ação física.", + cmdIt = "Faça algo ao seu redor realizar uma ação.", + cmdW = "Susurre algo para as pessoas próximas a você.", + cmdY = "Grite algo para as pessoas ao seu redor.", + cmdEvent = "Faça algo realizar uma ação que todos podem ver.", + cmdOOC = "Mande uma mensagem para o chat fora-de-personagem global.", + cmdLOOC = "Mande uma mensagem para o chat dentro-de-personagem local." +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_russian.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_russian.lua new file mode 100644 index 0000000..c7f09c2 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_russian.lua @@ -0,0 +1,453 @@ +--Maybe this thing have some trouble with translate +NAME = "Русский" + +LANGUAGE = { + helix = "Helix", + + introTextOne = "Fist Industries представляет", + introTextTwo = "В сотрудничестве с %s", + introContinue = "Чтобы продолжить, нажмите Пробел", + + helpIdle = "Выберите категорию", + helpCommands = "Параметры команды отмеченные <стрелками> обязательны, тогда как [скобки] опциональны.", + helpFlags = "Флаги с зеленым фоном обозначают доступные этому персонажу флаги.", + + creditSpecial = "Особая благодарность", + creditLeadDeveloper = "Ведущий разработчик", + creditUIDesigner = "Дизайнер интерфейса", + creditManager = "Менеджер проекта", + creditTester = "Ведущий тестировщик", + + chatTyping = "Пишет...", + chatTalking = "Говорит...", + chatPerforming = "Выполняет действие...", + chatNewTab = "Новая вкладка...", + chatReset = "Сбросить позицию", + chatResetTabs = "Сбросить позицию вкладок", + chatCustomize = "Настроить...", + chatCloseTab = "Закрыть вкладку", + chatTabName = "Имя вкладки", + chatNewTabTitle = "Новая Вкладка", + chatAllowedClasses = "Разрешенные классы чата", + chatTabExists = "Вкладка чата с таким именем уже существует!", + chatMarkRead = "Отметить как прочитанные", + + trySuccess = "[Успешно]", + tryFail = "[Неудачно]", + community = "Сообщество", + checkAll = "Отметить все", + uncheckAll = "Убрать все", + color = "Цвет", + type = "Тип", + display = "Изображение", + loading = "Загрузка", + dbError = "Сбой подключения к базе данных", + unknown = "Неизвестный", + noDesc = "Нет описания", + create = "Создать", + update = "Обновить", + load = "Загрузить персонажа", + loadTitle = "Загрузить персонажа.", + leave = "Выйти с сервера", + leaveTip = "Покинуть сервер.", + ["return"] = "Вернуться", + returnTip = "Вернуться в предыдущее меню.", + proceed = "Продолжить", + faction = "Фракция", + skills = "Навыки", + choose = "Выбрать", + chooseFaction = "Выберите фракцию", + chooseDescription = "Опишите свою внешность", + chooseSkills = "Выберите ваши навыки", + name = "Имя", + description = "Описание", + model = "Модель", + attributes = "Атрибуты", + attribPointsLeft = "Очки", + Endurance = "Выносливость", + Stamina = "Быстроходность", + Strength = "Сила", + charInfo = "Информация о персонаже", + charCreated = "Персонаж успешно создан.", + charCreateTip = "Заполните поля ниже и нажмите 'Создать' чтобы создать персонажа.", + invalid = "Вы ввели недействительный %s!", + nameMinLen = "Имя не должно быть короче %d символов!", + nameMaxLen = "Имя не должно быть длиннее %d символов!", + descMinLen = "Описание не должно быть короче %d символов!", + maxCharacters = "Достигнут лимит созданных персонажей!", + player = "Игрок", + finish = "Создать", + finishTip = "Закончить создание персонажа.", + needModel = "Выберите одну из моделей!", + creating = "Персонаж создается...", + unknownError = "Произошла неизвестная ошибка!", + areYouSure = "Вы уверены?", + delete = "Удалить", + deleteConfirm = "Этот персонаж будет безвозвратно удален!", + deleteComplete = "%s был удален.", + no = "Нет", + yes = "Да", + close = "Закрыть", + save = "Сохранить", + itemInfo = "Имя: %s\nОписание: %s", + itemCreated = "Предмет(ы) успешно создан(ы).", + cloud_no_repo = "Данный репозиторий недействителен!", + cloud_no_plugin = "Данный плагин недействителен!", + inv = "Инвентарь", + plugins = "Плагины", + author = "Автор", + version = "Версия", + characters = "Меню персонажей", + business = "Бизнес", + settings = "Настройки", + config = "Конфигурация", + chat = "Чат", + appearance = "Описание", + misc = "Остальное", + oocDelay = "Подождите %s секунд чтобы снова написать в OOC чат!", + loocDelay = "Подождите %s секунд чтобы снова написать в LOOC чат!", + usingChar = "Вы уже используете этого персонажа.", + notAllowed = "Эта команда вам недоступна!", + itemNoExist = "Запрошенный предмет не существует!", + cmdNoExist = "Такой команды не существует!", + charNoExist = "Такой персонаж не найден!", + plyNoExist = "Такой игрок не найден!", + cfgSet = "%s установил с \"%s\" на %s.", + drop = "Выбросить", + dropTip = "Выбросить предмет из инвентаря.", + take = "Взять", + takeTip = "Взять этот предмет в инвентарь.", + dTitle = "Дверь", + dTitleOwned = "Купленная дверь", + dIsNotOwnable = "Эта дверь не имеет владельца.", + dIsOwnable = "Вы можете приобрести эту дверь, нажав F2.", + dMadeUnownable = "Вы сделали эту дверь непокупаемой.", + dMadeOwnable = "Вы сделали эту дверь приобретаемой.", + dNotAllowedToOwn = "Вы не можете владеть этой дверью!", + dSetDisabled = "Вы заблокировали данную дверь.", + dSetNotDisabled = "Вы разблокировали данную дверь.", + dSetHidden = "Вы скрыли описание данной двери.", + dSetNotHidden = "Вы раскрыли описание данной двери.", + dSetParentDoor = "Вы сделали эту дверь родительской.", + dCanNotSetAsChild = "Вы не можете сделать родительскую дверь дочерней!", + dAddChildDoor = "Вы сделали эту дверь дочерней.", + dRemoveChildren = "Вы удалили у этой двери ее дочерние двери.", + dRemoveChildDoor = "Вы удалили дочернюю связь у этой двери.", + dNoParentDoor = "У вас нет родительской двери для связи.", + dOwnedBy = "Эта дверь принадлежит %s.", + dConfigName = "Двери", + dSetFaction = "Эта дверь теперь принадлежит фракции %s.", + dRemoveFaction = "Эта дверь больше не принадлежит ни одной фракции.", + dNotValid = "Вы не смотрите на валидную дверь!", + canNotAfford = "Вы не можете позволить это себе!", + dPurchased = "Вы купили эту дверь за %s.", + dSold = "Вы продали эту дверь за %s.", + notOwner = "Вы не владелец этой двери!", + invalidArg = "Указано недопустимое значение для аргумента #%s!", + invalidFaction = "Указанная фракция не найдена!", + flagGive = "%s выдал игроку %s флаги'%s'.", + flagGiveTitle = "Выдать флаги", + flagTake = "%s забрал у игрока '%s' флаги %s.", + flagTakeTitle = "Забрать флаги", + flagNoMatch = "Вы должны иметь \"%s\" флаш(и) чтобы сделать это!", + textAdded = "Текст добавлен.", + textRemoved = "Вы удалили %s текст(ов).", + moneyTaken = "Вы получили %s.", + moneyGiven = "Вы отдали %s.", + insufficientMoney = "У вас не хватает денег, чтобы сделать это!", + businessPurchase = "Вы купили %s за %s.", + businessSell = "Вы продали %s за %s.", + businessTooFast = "Подождите, прежде чем покупать другой товар!", + cChangeModel = "%s сменил %s модель на %s.", + cChangeName = "%s сменил %s имя на %s.", + cChangeSkin = "%s сменил %s скин на %s.", + cChangeGroups = "%s сменил %s \"%s\" на бодигруппу %s.", + cChangeFaction = "%s перенес персонажа %s во фракцию %s ", + playerCharBelonging = "Этот предмет принадлежит другому вашему персонажу!", + spawnAdd = "Вы добавили спаун для фракции%s.", + spawnDeleted = "Вы удалили %s спаун(ов).", + someone = "Кто-то", + rgnLookingAt = "Представиться человеку, на которого вы смотрите.", + rgnTalk = "Представиться людям в радиусе разговора.", + meFormat = "%s %s", + icFormat = "%s говорит \"%s\"", + rollFormat = "%s получил число %s из %s.", + yFormat = "%s шепчет \"%s\"", + sFormat = "%s кричит \"%s\"", + itFormat = "%s", + sbOptions = "Нажмите, чтобы посмотреть настройки %s.", + spawnAdded = "Вы добавили спаун для %s.", + whitelist = "%s выдал вайтлист %s для фракции %s.", + unwhitelist = "%s забрал вайтлист у %s фракции %s.", + noWhitelist = "У вас нет вайтлиста для этого персонажа!", + gettingUp = "Вы поднимаетесь...", + wakingUp = "Вы приходите в себя...", + Ammunition = "Боеприпасы", + Storage = "Вместилища", + Outfit = "Одежда", + Weapons = "Оружие", + checkout = "Оформить заказ (%s)", + purchase = "Купить", + purchasing = "Покупка...", + success = "Успешно", + buyFailed = "Покупка не выполнена!", + buyGood = "Покупка выполнена!", + shipment = "Товар", + shipmentDesc = "Этот товар принадлежит %s.", + class = "Класс", + classes = "Классы", + illegalAccess = "Незаконный доступ.", + becomeClassFail = "Вы не можете выбрать класс %s!", + becomeClass = "Вы выбрали класс %s.", + setClass = "Вы установили игроку %s класс %s.", + attributeSet = "Вы сменили игроку %s %s на %s.", + attributeNotFound = "Вы указали неверный атрибут!", + attributeUpdate = "Вы добавили игроку %s %s, %s единицы.", + noFit = "Недостаточно места для этого предмета!", + itemOwned = "Вы не можете взаимодействовать с предметом, который принадлежит другому вашему персонажу!", + help = "Помощь", + commands = "Команды", + doorSettings = "Настройки двери", + sell = "Продать", + access = "Доступ", + locking = "Обьект блокируется...", + unlocking = "Обьект разблокируется...", + modelNoSeq = "Ваша модель не поддерживает эту анимацию.", + notNow = "Вы не можете сделать это сейчас.", + faceWall = "Прислонитесь лицом к стене, чтобы выполнить эту анимацию.", + faceWallBack = "Прислонитесь спиной к стене, чтобы выполнить эту анимацию.", + descChanged = "Описание вашего персонажа успешно изменено.", + noOwner = "Неверный владелец!", + invalidItem = "Вы указали неверный предмет!", + invalidInventory = "Вы указали неверный инвентарь!", + home = "Домой", + charKick = "%s кикнул персонажа %s.", + charBan = "%s забанил персонажа %s.", + charBanned = "Этот персонаж забанен.", + charBannedTemp = "Этот персонаж временно забанен.", + playerConnected = "%s подключился к серверу.", + playerDisconnected = "%s отключился от сервера.", + setMoney = "Вы установили игроку %s баланс %s единиц.", + itemPriceInfo = "Вы можете приобрести этот предмет за %s.\nВы можете продать этот предмет за %s", + free = "Бесплатно", + vendorNoSellItems = "У торговца нету предметов для продажи.", + vendorNoBuyItems = "Нету предметов для покупки.", + vendorSettings = "Настройки торговца", + vendorUseMoney = "Торговец использует деньги?", + vendorNoBubble = "Убрать пузырек над головой?", + mode = "Режим", + price = "Цена", + stock = "Кол-во", + none = "Нет", + vendorBoth = "Покупать и продавать", + vendorBuy = "Только покупать", + vendorSell = "Только продавать", + maxStock = "Максимальное кол-во", + vendorFaction = "Доступ фракций", + buy = "Купить", + vendorWelcome = "Приветствую вас в моем магазине. Желаете что-то купить?", + vendorBye = "Приходите еще!", + charSearching = "Вы уже ищите другого персонажа!", + charUnBan = "%s разбанил персонажа %s.", + charNotBanned = "Этот персонаж не забанен!", + quickSettings = "Быстрые настройки", + vmSet = "Вы настроили свою голосовую почту.", + vmRem = "Вы удалили свою голосовую почту.", + noPerm = "У вас нет прав чтобы это сделать!", + youreDead = "Вы погибли", + injMajor = "Очень сильно ранен", + injLittle = "Слегка ранен", + chgName = "Сменить имя", + chgNameDesc = "Введите новое имя персонажа.", + weaponSlotFilled = "Слот %s занять другим оружием!", + equippedBag = "Вы не можете переместить сумку с предметами внутри!", + equippedWeapon = "Вы не можете манипулировать экипированным оружием!", + nestedBags = "Вы не можете положить сумку с вещами в хранилище!", + outfitAlreadyEquipped = "Вы уже носите этот элемент экипировки!", + Load = "Зарядить", + useTip = "Использовать этот предмет.", + View = "Просмотреть", + Equip = "Экипировать", + equipTip = "Экипировать этот предмет.", + Unequip = "Стянуть", + unequipTip = "Стянуть этот предмет.", + consumables = "Расходные материалы", + plyNotValid = "Вы должны смотреть на существующего игрока!", + restricted = "Вы были связаны.", + salary = "Вы получили свою зарплату в размере %s.", + noRecog = "Вы не знаете этого персонажа.", + curTime = "Время: %s.", + vendorEditor = "Редактор торговца", + edit = "Изменить макс. кол-во", + disable = "Отключить", + vendorPriceReq = "Введите новую цену для этого предмета.", + vendorEditCurStock = "Изменить настоящее кол-во", + vendorStockReq = "Введите максимальное количество предметов.", + vendorStockCurReq = "Введите доступное количество предметов для покупки.", + you = "Персонаж", + vendorSellScale = "Множитель продажи", + vendorNoTrade = "Вы не можете торговать с этим торговцем!", + vendorNoMoney = "У торговца недостаточно денег для покупки!", + vendorNoStock = "У торговца нету этого товара!", + contentTitle = "Отсутствует контент Helix", + contentWarning = "У вас нет контента фреймворка Helix. Могут отсутствовать некоторые функции.\nХотите открыть страницу контента в Steam Workshop?", + flags = "Флаги", + mapRestarting = "Рестарт карты произойдет через %d секунд!", + chooseTip = "Выберите персонажа, чтобы начать играть.", + deleteTip = "Удалить этого персонажа.", + storageInUse = "Кто-то уже использует это!", + storageSearching = "Открываю...", + container = "Контейнер", + containerPassword = "На контейнер установлен замок с паролем %s.", + containerPasswordRemove = "Пароль с этого контейнера был убран.", + containerPasswordWrite = "Введите пароль.", + containerName = "Имя контейнера установлено %s.", + containerNameWrite = "Введите имя.", + containerNameRemove = "Имя контейнера убрано.", + containerInvalid = "Вы должны смотреть на контейнер, чтобы это выполнить это!", + wrongPassword = "Неверный пароль!", + respawning = "Респавнимся...", + tellAdmin = "Сообщите администрации эту ошибку: %s", + + mapAdd = "Вы добавили сцену карты.", + mapDel = "Вы удалили сцену(ы) %d.", + mapRepeat = "Теперь добавьте вторую точку.", + + scoreboard = "Игроки", + ping = "Пинг: %d", + viewProfile = "Открыть профиль Steam", + copySteamID = "Скопировать Steam ID", + + money = "Наличные", + moneyLeft = "Ваши наличные: ", + currentMoney = "Баланс: ", + + invalidClass = "Недопустимый класс!", + invalidClassFaction = "Недопустимый класс или фракция!", + + miscellaneous = "Разное", + general = "Общие", + observer = "ESP и наблюдение", + performance = "Производительность", + thirdperson = "Третье лицо", + date = "Дата", + interaction = "Взаимодействие", + server = "Сервер", + + resetDefault = "По умолчанию", + resetDefaultDescription = "Это сбросит значение \"%s\" до стандартного значения \"%s\".", + optOpenBags = "Открывать сумки в инвентаре ", + optdOpenBags = "При открытии меню, автоматически открывает все сумки в вашем инвентаре.", + optShowIntro = "Интро при подключении", + optdShowIntro = "Показывает интро фреймворка при следующем подключении. Автоматически отключается после просмотра.", + optCheapBlur = "Отключить блюр", + optdCheapBlur = "Заменяет блюр в интерфейсе на простое затемнение.", + optObserverTeleportBack = "Возвращение на место", + optdObserverTeleportBack = "Возвращает вас к месту, в котором вы вошли в режим наблюдения.", + optObserverESP = "Админ ESP", + optdObserverESP = "Показывает имена и местоположение каждого игрока на сервере.", + opt24hourTime = "24-часовой формат времени", + optd24hourTime = "Показывать время в 24-х часовом формате, вместо 12 часового (AM/PM).", + optChatNotices = "Уведомления в чате", + optdChatNotices = "Выводит все уведомления которые появляются в правом верхнем углу в текстовый чат.", + optChatTimestamps = "Время в чате", + optdChatTimestamps = "Отмечает время отправки кажого сообщения в чате.", + optAlwaysShowBars = "Всегда показывать информационную панель", + optdAlwaysShowBars = "Отображает состояние персонажа в левом верхнем углу на постоянной основе.", + optAltLower = "Опуская, скрывать руки", -- @todo remove me + optdAltLower = "Скрывать руки, при опускани оных.", -- @todo remove me + optThirdpersonEnabled = "|Третье лицо", + optdThirdpersonEnabled = "Перемещение камеры позади вас. Также переключается консольной командой \'ix_togglethirdperson\'", + optThirdpersonClassic = "|Классическая камера", + optdThirdpersonClassic = "Управлять взглядом вашего персонажа с помощью мыши.", + optThirdpersonVertical = "Высота камеры", + optdThirdpersonVertical = "Высота расположения камеры от 3 лица.", + optThirdpersonHorizontal = "Камера по горизонтали", + optdThirdpersonHorizontal = "Расположение камеры влево или вправо от персонажа.", + optThirdpersonDistance = "Расстояние камеры", + optdThirdpersonDistance = "Как далеко располагается камера от персонажа.", + optThirdpersonCrouchOffset = "Высота камеры при присяде", + optdThirdpersonCrouchOffset = "Высота расположения камеры от 3 лица.", + optDisableAnimations = "Отключить анимации интерфейса", + optdDisableAnimations = "Отключает анимации интерфейса, для резкого перехода между окнами.", + optAnimationScale = "Скорость анимаций", + optdAnimationScale = "Насколько быстрее или медленнее проигрывается анимация.", + optLanguage = "Язык", + optdLanguage = "Язык интерфейса Helix.", + optMinimalTooltips = "Минималистичные подсказки", + optdMinimalTooltips = "Изменяет стиль показа подсказок, чтобы занимать меньше места.", + optNoticeDuration = "Длительность уведомления", + optdNoticeDuration = "Как долго показываются уведомления (в секундах).", + optNoticeMax = "Максимальное кол-во уведомлений", + optdNoticeMax = "Количество уведомлений, показываемых одновременно.", + optChatFontScale = "Шрифт чата", + optdChatFontScale = "Насколько больше или меньше шрифт чата должен быть.", + optChatOutline = "Контур в чате", + optdChatOutline = "Рисует контур вокруг текста в чате. Включите, если у вас проблемы с чтением чата.", + + cmdRoll = "Случайное число от 0 до указанного значения.", + cmdPM = "Отправить личное сообщение выбранному персонажу.", + cmdReply = "Отправить личное сообщение последнему человеку, от которого вы получили сообщение.", + cmdSetVoicemail = "Установить или убрать сообщение автоответчика, при отправке вам сообщения.", + cmdCharGiveFlag = "Дать указанному игроку флаг(и)", + cmdCharTakeFlag = "Забрать указанный(е) флаг(и) у игрока, если такие имеются.", + cmdToggleRaise = "Поднять или опустить оружие, которое вы удерживаете.", + cmdCharSetModel = "Установить выбранную модель игроку.", + cmdCharSetSkin = "Установить выбранный скин для модели игрока.", + cmdCharSetBodygroup = "Установить бодигруппу для модели игрока.", + cmdCharSetAttribute = "Установить уровень выбранного атрибута у игрока.", + cmdCharAddAttribute = "Добавить к уровню выбранное количество очков.", + cmdCharSetName = "Изменить имя пользователя на заданное.", + cmdCharGiveItem = "Дать заданный предмет игроку.", + cmdCharKick = "Кикнуть выбранного персонажа.", + cmdCharBan = "Ограничить игроку доступ к выбранному персонажу.", + cmdCharUnban = "Снимает ограничение на игру за выбранного персонажа", + cmdGiveMoney = "Передать сумму денег персонажу, на которого вы смотрите.", + cmdCharSetMoney = "Изменить сумму денег у персонажа на указанную.", + cmdDropMoney = "Положить определенную сумму денег в хранилище на которое вы смотрите.", + cmdPlyWhitelist = "Выдать доступ игроку к созданию персонажа в указанной фракции", + cmdCharGetUp = "Попытаться встать после падения.", + cmdPlyUnwhitelist = "Забрать доступ у игрока к созданию персонажа в указанной фракции.", + cmdCharFallOver = "Расслабиться и упасть на землю.", + cmdBecomeClass = "Взять выбранный класс в вашей текущей фракции.", + cmdCharDesc = "Установить физическое описание вашего персонажа.", + cmdCharDescTitle = "Физическое описание", + cmdCharDescDescription = "Введите физическое описание персонажа.", + cmdPlyTransfer = "Перевести выбранного персонажа в указанную фракцию.", + cmdCharSetClass = "Сменить выбранному игроку указанный класс в его фракции.", + cmdMapRestart = "Перезапустить карту через указанное время.", + cmdPanelAdd = "Создать веб изображение там, куда направлен взгляд.", + cmdPanelRemove = "Удалить веб изображение, на которое вы смотрите.", + cmdTextAdd = "Создать блок текста в мире.", + cmdTextRemove = "Удалить блоки текста, на которые вы смотрите.", + cmdMapSceneAdd = "Добавить кинематографичную камеру, в меню выбора персонажа.", + cmdMapSceneRemove = "Убрать кинематографичную камеру, в меню выбора персонажа.", + cmdSpawnAdd = "Добавить точку спавна для указанной фракции.", + cmdSpawnRemove = "Удалить все точки спавна, на которые вы смотрите.", + cmdAct = "Выполнить анимацию %s.", + cmdContainerSetPassword = "Установить пароль для контейнера, на который вы смотрите.", + cmdDoorSell = "Продать дверь, на которую вы смотрите.", + cmdDoorBuy = "Купить дверь, на которую вы смотрите.", + cmdDoorSetUnownable = "Сделать дверь, на которую вы смотрите, недоступной для покупки.", + cmdDoorSetOwnable = "Сделать дверь, на которую вы смотрите, доступной для приобретения.", + cmdDoorSetFaction = "Установить двери, на которую вы смотрите, принадлежность к фракции.", + cmdDoorSetDisabled = "Отключить возможность взаимодействия с дверью, на которую вы смотрите.", + cmdDoorSetTitle = "Установить заголовок двери, на которую вы смотрите.", + cmdDoorSetParent = "Выбрать родительскую дверь для дочерних дверей.", + cmdDoorSetChild = "Выбрать дочерние двери для родительской двери.", + cmdDoorRemoveChild = "Убрать у двери дочернюю зависимость.", + cmdDoorSetHidden = "Убирает описание двери на которую вы смотрите, но оставляет возможность владения.", + cmdDoorSetClass = "Установить двери, на которую вы смотрите, принадлежность к классу во фракции.", + cmdMe = "Описывает действие вашего персонажа.", + cmdIt = "Совершить какое-либо действие вокруг себя.", + cmdDo = "/do — описать ситуацию или окружение.", + cmdTry = "/try — попытка выполнить действие.", + cmdS = "/s — крик, слышно далеко.", + cmdY = "/y — шёпот, слышно очень близко.", + cmdEvent = "Выполнить действие, которое увидят все игроки.", + cmdOOC = "Отправить сообщение в глобальный out-of-character чат .", + cmdLOOC = "Отправить сообщение в локальный out-of-character чат.", + cmdadmin = "Включить/выключить админ-мод.", + + Instructions = "Информация" +} diff --git a/garrysmod/gamemodes/helix/gamemode/languages/sh_spanish.lua b/garrysmod/gamemodes/helix/gamemode/languages/sh_spanish.lua new file mode 100644 index 0000000..86e8307 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/languages/sh_spanish.lua @@ -0,0 +1,469 @@ +-- SPANISH TRANSLATION +-- Cuboxis (http://steamcommunity.com/id/Cuboxis) +-- Geferon (https://steamcommunity.com/id/GEFERON) +-- Whitehole (https://steamcommunity.com/id/whitehole) +-- Carlos Bes + +NAME = "Español" + +LANGUAGE = { + helix = "Helix", + + introTextOne = "fist industries presenta", + introTextTwo = "en colaboración con %s", + introContinue = "pulsa espacio para continuar", + + helpIdle = "Selecciona una categoría", + helpCommands = "Los parámetros de comando rodeados de son obligatorios, mientras que los rodeados de [corchetes] son opcionales.", + helpFlags = "Las Flags con el fondo verde son accesibles por este personaje.", + + creditSpecial = "Muchas Gracias", + creditLeadDeveloper = "Desarrollador Principal", + creditUIDesigner = "Diseñador de la Interfaz de Usuario", + creditManager = "Jefe de Proyecto", + creditTester = "Tester Principal", + + chatTyping = "Escribiendo...", + chatTalking = "Hablando...", + chatYelling = "Gritando...", + chatWhispering = "Susurrando...", + chatPerforming = "Actuando...", + chatNewTab = "Nueva pestaña...", + chatReset = "Restablecer posición", + chatResetTabs = "Restablecer pestañas", + chatCustomize = "Personalizar...", + chatCloseTab = "Cerrar Pestaña", + chatTabName = "Nombre de Pestaña", + chatNewTabTitle = "Nueva Pestaña", + chatAllowedClasses = "Categorías de chat permitidas", + chatTabExists = "¡Ya existe una pestaña de chat con ese nombre!", + chatMarkRead = "Marcar todo como leído", + + community = "Comunidad", + checkAll = "Seleccionar todo", + uncheckAll = "Deseleccionar todo", + color = "Color", + type = "Tipo", + display = "Visualización", + loading = "Cargando", + dbError = "Fallo de conexión con la Base de Datos", + unknown = "Desconocido", + noDesc = "Descripción no disponible", + create = "Crear", + update = "Actualizar", + load = "Cargar personaje", + loadTitle = "Cargar un Personaje", + leave = "Abandonar", + leaveTip = "Salir del servidor.", + ["return"] = "Volver", + returnTip = "Vuelve al menú anterior.", + proceed = "Continuar", + faction = "Facción", + skills = "Habilidades", + choose = "Escoger", + chooseFaction = "Escoge una Facción", + chooseDescription = "Describe la apariencia de tu personaje", + chooseSkills = "Ajusta tus habilidades", + name = "Nombre", + description = "Descripción", + model = "Modelo", + attributes = "Atributos", + attribPointsLeft = "Puntos restantes", + charInfo = "Información del personaje", + charCreated = "Has creado tu personaje correctamente.", + charCreateTip = "Completa todos los campos obligatorios y haz clic en 'Finalizar' para crear a tu personaje.", + invalid = "Has introducido un(a) %s inválido/a.", + nameMinLen = "Tu nombre debe de tener al menos %d caracteres.", + nameMaxLen = "Tu nombre no puede tener mas de %d caracteres.", + descMinLen = "Tu descripción debe tener al menos %d caracteres.", + maxCharacters = "¡No puedes crear mas personajes!", + player = "Jugador", + finish = "Finalizar", + finishTip = "Finaliza la creación de personaje.", + needModel = "Necesitas escoger un modelo válido.", + creating = "Tu personaje está siendo creado...", + unknownError = "Ha ocurrido un error desconocido", + areYouSure = "¿Estás seguro?", + delete = "Borrar", + deleteConfirm = "¡Este personaje será eliminado PERMANENTEMENTE!", + deleteComplete = "%s ha sido eliminado.", + no = "No", + yes = "Sí", + close = "Cerrar", + save = "Guardar", + itemInfo = "Nombre: %s\nDescripción: %s", + itemCreated = "Objeto(s) creado(s) correctamente.", + cloud_no_repo = "El repositorio introducido no es válido.", + cloud_no_plugin = "El plugin introducido no es válido.", + inv = "Inventario", + plugins = "Plugins", + pluginLoaded = "%s ha habilitado el plugin \"%s\" y se habilitará con el siguiente cambio de mapa.", + pluginUnloaded = "%s ha deshabilitado el plugin \"%s\" y se deshabilitará con el siguiente cambio de mapa.", + loadedPlugins = "Plugins Habilitados", + unloadedPlugins = "Plugins Deshabilitados", + on = "On", + off = "Off", + author = "Autor", + version = "Versión", + characters = "Personajes", + business = "Negocios", + settings = "Opciones", + config = "Configuración", + chat = "Chat", + appearance = "Apariencia", + misc = "Misceláneo", + oocDelay = "Debes esperar %s segundo(s) antes de usar de nuevo el chat OOC.", + loocDelay = "Debes esperar %s segundo(s) antes de usar de nuevo el chat LOOC.", + usingChar = "Ya estás usando éste personaje.", + notAllowed = "Lo siento, no tienes permitido hacer esto.", + itemNoExist = "Lo siento, el objeto especificado no existe.", + cmdNoExist = "Lo siento, este comando no existe.", + charNoExist = "Lo siento, el personaje especificado no ha podido ser encontrado.", + plyNoExist = "Lo siento, el jugador correspondiente no ha podido ser encontrado.", + cfgSet = "%s ha establecido \"%s\" a %s.", + drop = "Tirar", + dropTip = "Suelta el objeto de tu inventario.", + take = "Coger", + takeTip = "Pone este objeto en tu inventario.", + dTitle = "Puerta sin propietario", + dTitleOwned = "Puerta comprada", + dIsNotOwnable = "Esta puerta no puede tener propietario.", + dIsOwnable = "Puedes comprar esta puerta pulsando F2.", + dMadeUnownable = "Has hecho que esta puerta no pueda tener propietario.", + dMadeOwnable = "Has hecho que esta puerta pueda tener propietario.", + dNotAllowedToOwn = "No tienes permiso para hacer esta puerta de tu propiedad.", + dSetDisabled = "Has deshabilitado esta puerta.", + dSetNotDisabled = "Has habilitado esta puerta.", + dSetHidden = "Has hecho que esta puerta esté oculta.", + dSetNotHidden = "Has hecho que esta puerta no esté oculta.", + dSetParentDoor = "Has establecido esta puerta como puerta primaria.", + dCanNotSetAsChild = "No puedes establecer la puerta primaria como puerta secundaria.", + dAddChildDoor = "Has añadido esta puerta como puerta secundaria.", + dRemoveChildren = "Has eliminado todas las puertas secundarias de esta puerta.", + dRemoveChildDoor = "Has eliminado esta puerta como puerta secundaria.", + dNoParentDoor = "No tienes seleccionada una puerta primaria.", + dOwnedBy = "Esta puerta es propiedad de %s.", + dConfigName = "Puertas", + dSetFaction = "Esta puerta ahora pertenece a %s.", + dRemoveFaction = "Esta puerta ya no pertenece a ninguna facción.", + dNotValid = "No estás mirando a una puerta válida.", + canNotAfford = "No puedes permitirte comprar esto.", + dPurchased = "Has comprado esta puerta por %s.", + dSold = "Has vendido esta puerta por %s.", + notOwner = "Esto no te pertenece.", + invalidArg = "El valor introducido para el argumento #%s es inválido.", + invalidFaction = "La facción que has introducido no se ha podido encontrar.", + flagGive = "%s ha dado a %s las flags '%s'.", + flagGiveTitle = "Dar Flags", + flagTake = "%s ha quitado las flags '%s' de %s.", + flagTakeTitle = "Quitar Flags", + flagNoMatch = "Debes tener la(s) flag(s) \"%s\" para hacer esta acción.", + panelAdded = "Has añadido un panel.", + panelRemoved = "Has borrado %d panel(es).", + textAdded = "Has añadido un texto.", + textRemoved = "Has borrado %s texto(s).", + moneyTaken = "Has recibido %s.", + moneyGiven = "Has dado %s.", + insufficientMoney = "No puedes permitirte esto con tu dinero.", + belowMinMoneyDrop = "No puedes soltar menos de %s.", + businessPurchase = "Has comprado %s por %s.", + businessSell = "Has vendido %s por %s.", + businessTooFast = "Por favor, espera antes de comprar otro objeto.", + cChangeModel = "%s ha cambiado el modelo de %s a %s.", + cChangeName = "%s ha cambiado el nombre de %s a %s.", + cChangeSkin = "%s ha cambiado el 'skin' de %s a %s.", + cChangeGroups = "%s ha cambiado el 'bodygroup' \"%s\" de %s a %s.", + cChangeFaction = "%s ha transferido a %s a la facción %s.", + playerCharBelonging = "Este objeto ya pertenece a otro de tus personajes.", + spawnAdd = "Has añadido un punto de reaparición para %s.", + spawnDeleted = "Has eliminado un punto de reaparición de %s.", + someone = "Alguien", + rgnLookingAt = "Permite que te reconozca la persona a la que estás mirando.", + rgnWhisper = "Permite que te reconozcan aquellos que te escuchen susurrar.", + rgnTalk = "Permite que te reconozcan aquellos que te escuchen hablar.", + rgnYell = "Permite que te reconozcan aquellos que te escuchen gritar.", + icFormat = "%s dice \"%s\"", + rollFormat = "%s ha tirado los dados y ha sacado un %s.", + wFormat = "%s susurra \"%s\"", + yFormat = "%s grita \"%s\"", + sbOptions = "Haz clic para ver las opciones de %s.", + spawnAdded = "Has añadido punto de aparición de %s.", + whitelist = "%s ha añadido a %s en la lista blanca de la facción %s.", + unwhitelist = "%s ha eliminado a %s de la lista blanca de la facción %s.", + noWhitelist = "No tienes la lista blanca para la facción de este personaje.", + charNotWhitelisted = "%s no está en la lista blanca para la facción %s.", + gettingUp = "Te estás levantando...", + wakingUp = "Estás recuperando la consciencia...", + Weapons = "Armas", + checkout = "Ir a la cesta (%s)", + purchase = "Comprar", + purchasing = "Comprando...", + success = "Éxito.", + buyFailed = "Compra fallida.", + buyGood = "Compra exitosa!", + shipment = "Envío", + shipmentDesc = "Este envío pertenece a %s.", + class = "Clase", + classes = "Clases", + illegalAccess = "Acceso ilegal.", + becomeClassFail = "Fallo en convertirse en %s.", + becomeClass = "Te has convertido en %s.", + setClass = "Has establecido la clase de %s a %s.", + attributeSet = "Has establecido el nivel de %s de %s a %s puntos.", + attributeNotFound = "Has especificado un atributo inválido.", + attributeUpdate = "Has añadido al nivel de %s de %s %s puntos.", + noFit = "Este objeto no cabe en tu inventario.", + itemOwned = "No puedes interactuar con un objeto de tus otros personajes.", + help = "Ayuda", + commands = "Comandos", + doorSettings = "Configuración de puertas", + sell = "Vender", + access = "Acceso", + locking = "Bloqueando esta entidad...", + unlocking = "Desbloqueando esta entidad...", + modelNoSeq = "Tu modelo no es compatible con este acto.", + notNow = "No tienes permiso para hacer esto ahora.", + faceWall = "Debes estar mirando una pared para hacer esto.", + faceWallBack = "Tu espalda tiene que estar mirando una pared para hacer esto.", + descChanged = "Has cambiado la descripción de tu personaje.", + noOwner = "El propietario no es válido.", + invalidItem = "El objeto no es válido.", + invalidInventory = "El inventario no es válido.", + home = "Inicio", + charKick = "%s ha echado a %s.", + charBan = "%s ha bloqueado el personaje %s.", + charBanned = "Este personaje está bloqueado.", + charBannedTemp = "Este personaje está temporalmente bloqueado.", + playerConnected = "%s se ha conectado al servidor.", + playerDisconnected = "%s se ha desconectado del servidor.", + setMoney = "Has establecido el dinero de %s a %s.", + itemPriceInfo = "Puedes comprar este objeto por %s.\nPuedes vender este objeto por %s.", + free = "Gratis", + vendorNoSellItems = "No hay objetos para vender.", + vendorNoBuyItems = "No hay objetos para comprar.", + vendorSettings = "Configuración del Vendedor", + vendorUseMoney = "¿El vendedor debe usar dinero?", + vendorNoBubble = "¿Esconder burbuja del vendedor?", + category = "Categoría", + mode = "Modo", + price = "Precio", + stock = "Existencias", + none = "Nada", + vendorBoth = "Comprar y vender", + vendorBuy = "Sólo comprar", + vendorSell = "Sólo vender", + maxStock = "Existencias máximas", + vendorFaction = "Editor de facción", + buy = "Comprar", + vendorWelcome = "Bienvenido/a a mi tienda, ¿Qué puedo hacer por usted?", + vendorBye = "¡Vuelva pronto!", + charSearching = "Ya te encuentras buscando a otro personaje, por favor espera.", + charUnBan = "%s ha desbloqueado el personaje %s.", + charNotBanned = "Este personaje no está bloqueado.", + quickSettings = "Configuración rápida", + vmSet = "Has establecido tu buzón de voz.", + vmRem = "Has eliminado tu buzón de voz.", + noPerm = "No tienes permiso para hacer esto.", + youreDead = "Estás muerto.", + injMajor = "Parece que está gravemente herido.", + injLittle = "Parece que está herido.", + chgName = "Cambiar nombre", + chgNameDesc = "Introduce abajo el nuevo nombre del personaje.", + weaponSlotFilled = "No puedes usar otra arma %s!", + equippedBag = "La bolsa que has movido tiene un objeto que te has equipado.", + equippedWeapon = "¡No puedes mover un arma que tienes equipada actualmente!", + nestedBags = "¡No puedes poner un inventario dentro de un inventario de almacenamiento!", + outfitAlreadyEquipped = "¡Ya estas usando este tipo de ropa!", + useTip = "Usa el objeto.", + equip = "Equipar", + equipTip = "Equipa el objeto.", + unequip = "Desequipar", + unequipTip = "Desequipa el objeto.", + consumables = "Consumibles", + plyNotValid = "No estás mirando a un jugador válido.", + restricted = "Has sido atado.", + salary = "Has recibido %s de tu salario.", + noRecog = "No reconoces a esta persona.", + curTime = "La hora actual es %s.", + vendorEditor = "Editor de vendedor", + edit = "Editar", + disable = "Deshabilitar", + vendorPriceReq = "Introduce el nuevo precio del artículo.", + vendorEditCurStock = "Editar las existencias actuales", + vendorStockReq = "Introduce la cantidad máxima de existencias que debe tener el artículo.", + vendorStockCurReq = "Introduce cuantos artículos de las existencias máximas estan disponibles para comprar.", + you = "Tú", + vendorSellScale = "Escala de precio de venta", + vendorNoTrade = "¡No puedes intercambiar con este vendedor!", + vendorNoMoney = "Este vendedor no puede permitirse comprar ese artículo.", + vendorNoStock = "Este vendedor no tiene ese artículo en existencia.", + vendorMaxStock = "¡Este vendedor ya tiene las existencias máximas de ese artículo!", + contentTitle = "Falta el Contenido de Helix", + contentWarning = "No tienes el contenido de Helix montado. Puede que falten ciertas características.\n¿Quieres abrir la página de Workshop del contenido de Helix?", + flags = "Flags", + mapRestarting = "¡El mapa se reiniciará en %d segundos!", + chooseTip = "Escoge este personaje para jugar.", + deleteTip = "Eliminar este personaje.", + storageInUse = "¡Alguien ya está usando esto!", + storageSearching = "Buscando...", + container = "Contenedor", + containerPassword = "Has establecido la contraseña de este contenedor a %s.", + containerPasswordRemove = "Has eliminado la contraseña de este contenedor.", + containerPasswordWrite = "Introduce la contraseña.", + containerName = "Has cambiado el nombre de este contenedor a %s.", + containerNameWrite = "Introduce el nombre.", + containerNameRemove = "Has borrado el nombre de este contenedor.", + containerInvalid = "¡Necesitas estar mirando a un contenedor para hacer esto!", + passwordAttemptLimit = "¡Demasiados intentos fallidos de contraseña!", + wrongPassword = "Has introducido una contraseña errónea.", + respawning = "Reapareciendo...", + syntax = "Formato: %s", + tellAdmin = "Informa a un miembro del staff de este error: %s", + + mapAdd = "Has añadido una escena de mapa.", + mapDel = "Has borrado %d escena(s) de mapa.", + mapRepeat = "Ahora añade el segundo punto.", + + scoreboard = "Marcador", + ping = "Ping: %d", + viewProfile = "Ver el perfil de Steam.", + copySteamID = "Copiar Steam ID", + + money = "Dinero", + moneyLeft = "Dinero restante: ", + currentMoney = "Dinero actual: ", + + invalidClass = "¡Esa no es una clase válida!", + invalidClassFaction = "¡Esa no es una clase válida para la facción!", + + miscellaneous = "Misceláneo", + general = "General", + observer = "Observador", + performance = "Rendimiento", + thirdperson = "Tercera Persona", + date = "Fecha", + interaction = "Interacción", + server = "Servidor", + + resetDefault = "Restablecer a valores predeterminados", + resetDefaultDescription = "Esto restablecerá \"%s\" a su valor predeterminado de \"%s\".", + optOpenBags = "Abrir objetos con inventario", + optdOpenBags = "Abrir automáticamente todos los objetos con inventario cuando el menú es abierto.", + optShowIntro = "Mostrar la intro al entrar", + optdShowIntro = "Muestra la introducción de Helix la siguiente vez que entres. Esta opción siempre es deshabilitada una vez la has visto.", + optCheapBlur = "Deshabilitar difuminación", + optdCheapBlur = "Remplaza la difuminación de la interfaz con un oscurecido simple.", + optObserverTeleportBack = "Volver a la posición anterior", + optdObserverTeleportBack = "Te hace volver a la posición en la cual te encontrabas antes de entrar en modo observador.", + optObserverESP = "Mostrar admin ESP", + optdObserverESP = "Muestra los nombres y las localizaciones de cada jugador en el servidor.", + opt24hourTime = "Usar formato de 24 horas", + optd24hourTime = "Muestra las marcas de tiempo en formato 24 horas, en vez de usar el formato de 12 horas (AM/PM).", + optChatNotices = "Mostrar avisos en el chat", + optdChatNotices = "Pone todos los avisos que aparecen en la esquina superior derecha, en el chat.", + optChatTimestamps = "Mostrar marcas de tiempo en el chat", + optdChatTimestamps = "Prepone una marca temporal a cada mensaje en el chat.", + optAlwaysShowBars = "Mostrar siempre las barras de información", + optdAlwaysShowBars = "Siempre muestra las barras de información en la esquina superior izquierda, sin importar si deberían de mostrarse o no.", + optAltLower = "Ocultar las manos al estar bajadas", -- @todo remove me + optdAltLower = "Oculta tus manos cuando están bajadas.", -- @todo remove me + optThirdpersonEnabled = "Activar Tercera Persona", + optdThirdpersonEnabled = "Pone la cámara detrás de ti. Esto también puede ser activado con el comando de consola \"ix_togglethirdperson\".", + optThirdpersonClassic = "Activar el estilo clásico de tercera persona", + optdThirdpersonClassic = "Mueve la vista de tu personaje con tu ratón.", + optThirdpersonVertical = "Altura de Tercera Persona", + optdThirdpersonVertical = "Como de alto debería de estar la cámara de tercera persona.", + optThirdpersonHorizontal = "Horizontal de Tercera Persona", + optdThirdpersonHorizontal = "Como de lejos a la izquierda o la derecha debería de estar la cámara.", + optThirdpersonDistance = "Distancia de Tercera Persona", + optdThirdpersonDistance = "Como de lejos debería de estar la cámara.", + optThirdpersonCrouchOffset = "Altura de la cámara al agacharse", + optdThirdpersonCrouchOffset = "A que altura debe de estar la cámara cuando se está agachado.", + optDisableAnimations = "Desactivar animaciones", + optdDisableAnimations = "Elimina las animaciones de la interfaz de usuario, haciendo que las transiciones sean instantáneas.", + optAnimationScale = "Escala de animación", + optdAnimationScale = "Velocidad de reproducción de las animaciones de la interfaz de usuario.", + optLanguage = "Idioma", + optdLanguage = "El idioma mostrado en la interfaz de usuario de Helix.", + optMinimalTooltips = "Información del HUD minimalista", + optdMinimalTooltips = "Cambia el estilo de la información del HUD para que ocupe menos espacio.", + optNoticeDuration = "Duración de Avisos", + optdNoticeDuration = "Cuanto tiempo duran los avisos (en segundos).", + optNoticeMax = "Máximo de Avisos", + optdNoticeMax = "La cantidad de avisos mostrados antes de que sean eliminados los anteriores.", + optChatFontScale = "Tamaño de fuente del chat", + optdChatFontScale = "Modifica el tamaño de la fuente del chat.", + optChatOutline = "Contorno en el texto de chat", + optdChatOutline = "Dibuja un contorno alrededor del texto del chat, en vez de una sombra paralela. Activa esto si tienes problemas leyendo el texto.", + + cmdRoll = "Tira un numero entre 0 y el número especificado.", + cmdPM = "Envía un mensaje privado a alguien.", + cmdReply = "Responde a la ultima persona de la cual recibiste un mensaje privado.", + cmdSetVoicemail = "Establece o elimina el mensaje de respuesta automática cuando alguien te envía un mensaje privado.", + cmdCharGiveFlag = "Da la(s) flag(s) especificadas a alguien.", + cmdCharTakeFlag = "Elimina la(s) flag(s) especificada(s) de alguien si las tienen.", + cmdToggleRaise = "Levanta o baja el arma que estas que estas sosteniendo.", + cmdCharSetModel = "Establece el modelo del personaje de una persona.", + cmdCharSetSkin = "Establece la 'skin' del modelo de un personaje.", + cmdCharSetBodygroup = "Establece el 'bodygroup' especificado del modelo de un personaje.", + cmdCharSetAttribute = "Establece el nivel del atributo especificado para alguien.", + cmdCharAddAttribute = "Añade un nivel al atributo especificado para alguien.", + cmdCharSetName = "Cambia el nombre de un personaje al nombre especificado.", + cmdCharGiveItem = "Da el objeto especificado a alguien.", + cmdCharKick = "Saca al jugador de su personaje, devolviéndolo a la pantalla de selección.", + cmdCharBan = "Bloquea a un jugador el uso del personaje especificado.", + cmdCharUnban = "Desbloquea un personaje para que el jugador pueda usarlo otra vez.", + cmdGiveMoney = "Da una cantidad específica de dinero a la persona a la que estas mirando.", + cmdCharSetMoney = "Cambia la cantidad total de dinero de alguien a la cantidad especificada.", + cmdDropMoney = "Tira una cantidad específica de dinero en una pequeña caja en frente de ti.", + cmdPlyWhitelist = "Permite el acceso de alguien a la facción especificada (lista blanca).", + cmdCharGetUp = "Intenta levantarte después de haber caído.", + cmdPlyUnwhitelist = "Elimina el acceso de alguien a la facción especificada.", + cmdCharFallOver = "Hace que tus rodillas se debiliten y te caigas.", + cmdBecomeClass = "Intenta formar parte de la clase especificada en tu facción actual.", + cmdCharDesc = "Establece tu descripción física.", + cmdCharDescTitle = "Descripción Física", + cmdCharDescDescription = "Introduce la descripción física de tu personaje.", + cmdPlyTransfer = "Transfiere a alguien a la facción especificada.", + cmdCharSetClass = "Forzar a alguien formar parte de la clase especificada de su facción.", + cmdMapRestart = "Reinicia el mapa después de la cantidad de tiempo especificada.", + cmdPanelAdd = "Pone un panel web en el mundo.", + cmdPanelRemove = "Elimina el panel web al que estas mirando.", + cmdTextAdd = "Pone un bloque de texto en el mundo.", + cmdTextRemove = "Elimina bloques de texto de donde estas mirando.", + cmdMapSceneAdd = "Añade un punto de cámara cinemática la cual es mostrada en el menú de selección de personaje.", + cmdMapSceneRemove = "Elimina un punto de cámara que es mostrado en el menú de selección de personaje.", + cmdSpawnAdd = "Añade un punto de aparición para la facción especificada.", + cmdSpawnRemove = "Elimina cualquier punto de aparición a los cuales estés mirando.", + cmdAct = "Realiza la animación %s.", + cmdContainerSetPassword = "Establece la contraseña para el contenedor que estas mirando.", + cmdDoorSell = "Vende la puerta a la que estas mirando.", + cmdDoorBuy = "Compra la puerta a la que estas mirando.", + cmdDoorSetUnownable = "Hace que la puerta a la que estas mirando no pueda tener dueño.", + cmdDoorSetOwnable = "Hace que la puerta a la que estas mirando pueda tener dueño.", + cmdDoorSetFaction = "Hace que la puerta a la que estas mirando pertenezca a una facción.", + cmdDoorSetDisabled = "No permite que ningún comando sea ejecutado en la puerta a la cual estas mirando.", + cmdDoorSetTitle = "Establece el titulo de la puerta a la cual estas mirando.", + cmdDoorSetParent = "Selecciona la puerta como madre de un grupo de puertas.", + cmdDoorSetChild = "Establece la puerta como hija de un grupo de puertas.", + cmdDoorRemoveChild = "Elimina la puerta como hija del grupo de puertas seleccionado.", + cmdDoorSetHidden = "Oculta la descripción de la puerta a la cual estas mirando, pero sigue permitiendo que pueda tener dueño.", + cmdDoorSetClass = "Hace que la puerta a la que estas mirando pertenezca la clase especificada de una facción.", + cmdMe = "Describe una acción de tu personaje en tercera persona.", + cmdIt = "Describe un evento a tu alrededor.", + cmdW = "Susurra algo a las personas a tu alrededor.", + cmdY = "Grita algo a las personas a tu alrededor.", + cmdEvent = "Describe un evento que todos pueden ver en el servidor.", + cmdOOC = "Envía un mensaje en el chat (global) fuera de personaje.", + cmdLOOC = "Envía un mensaje en el chat (local) fuera de personaje.", + + iconEditorAlignBest = "Alineación óptima", + iconEditorWidth = "Ancho", + iconEditorHeight = "Altura", + iconEditorCopy = "Copiar al portapapeles", + iconEditorCopied = "Datos del modelo del objeto copiados al portapapeles.", + iconEditorAlignFront = "Alinear desde el frente", + iconEditorAlignAbove = "Alinear desde arriba", + iconEditorAlignRight = "Alinear desde la derecha", + iconEditorAlignCenter = "Alinear desde el centro" +} diff --git a/garrysmod/gamemodes/helix/gamemode/shared.lua b/garrysmod/gamemodes/helix/gamemode/shared.lua new file mode 100644 index 0000000..2e96f93 --- /dev/null +++ b/garrysmod/gamemodes/helix/gamemode/shared.lua @@ -0,0 +1,158 @@ + +--- Top-level library containing all Helix libraries. A large majority of the framework is split into respective libraries that +-- reside within `ix`. +-- @module ix + +--- A table of variable types that are used throughout the framework. It represents types as a table with the keys being the +-- name of the type, and the values being some number value. **You should never directly use these number values!** Using the +-- values from this table will ensure backwards compatibility if the values in this table change. +-- +-- This table also contains the numerical values of the types as keys. This means that if you need to check if a type exists, or +-- if you need to get the name of a type, you can do a table lookup with a numerical value. Note that special types are not +-- included since they are not real types that can be compared with. +-- @table ix.type +-- @realm shared +-- @field string A regular string. In the case of `ix.command.Add`, this represents one word. +-- @field text A regular string. In the case of `ix.command.Add`, this represents all words concatenated into a string. +-- @field number Any number. +-- @field player Any player that matches the given query string in `ix.util.FindPlayer`. +-- @field steamid A string that matches the Steam ID format of `STEAM_X:X:XXXXXXXX`. +-- @field character Any player's character that matches the given query string in `ix.util.FindPlayer`. +-- @field bool A string representation of a bool - `false` and `0` will return `false`, anything else will return `true`. +-- @field color A color represented by its red/green/blue/alpha values. +-- @field vector A 3D vector represented by its x/y/z values. +-- @field optional This is a special type that can be bitwise OR'd with any other type to make it optional. Currently only +-- supported in `ix.command.Add`. +-- @field array This is a special type that can be bitwise OR'd with any other type to make it an array of that type. Currently +-- only supported in `ix.option.Add`. +-- @see ix.command.Add +-- @see ix.option.Add +-- @usage -- checking if type exists +-- print(ix.type[2] != nil) +-- > true +-- +-- -- getting name of type +-- print(ix.type[ix.type.string]) +-- > "string" +ix.type = ix.type or {} + +-- Define gamemode information. +GM.Name = "Helix" +GM.Author = "nebulous.cloud" +GM.Website = "https://nebulous.cloud" +GM.Version = "β" + +do + -- luacheck: globals player_manager + player_manager.ixTranslateModel = player_manager.ixTranslateModel or player_manager.TranslateToPlayerModelName + + function player_manager.TranslateToPlayerModelName(model) + model = model:lower():gsub("\\", "/") + local result = player_manager.ixTranslateModel(model) + + if (result == "kleiner" and !model:find("kleiner")) then + local model2 = model:gsub("models/", "models/player/") + result = player_manager.ixTranslateModel(model2) + + if (result != "kleiner") then + return result + end + + model2 = model:gsub("models/humans", "models/player") + result = player_manager.ixTranslateModel(model2) + + if (result != "kleiner") then + return result + end + + model2 = model:gsub("models/zombie/", "models/player/zombie_") + result = player_manager.ixTranslateModel(model2) + + if (result != "kleiner") then + return result + end + end + + return result + end +end + +-- Include core framework files. +ix.util.Include("core/cl_skin.lua") +ix.util.IncludeDir("core/libs/thirdparty") +ix.util.Include("core/sh_config.lua") +ix.util.IncludeDir("core/libs") +ix.util.IncludeDir("core/derma") +ix.util.IncludeDir("core/hooks") + +-- Include language and default base items. +ix.lang.LoadFromDir("helix/gamemode/languages") +ix.item.LoadFromDir("helix/gamemode/items") + +-- Called after the gamemode has loaded. +function GM:Initialize() + -- Load all of the Helix plugins. + ix.plugin.Initialize() + -- Restore client options + ix.option.Load() + -- Restore the configurations from earlier if applicable. + ix.config.Load() +end + +-- luacheck: globals IX_RELOADED +IX_RELOADED = false + +-- Called when a file has been modified. +function GM:OnReloaded() + -- Reload the default fonts. + if (CLIENT) then + hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) + + -- Reload the scoreboard. + if (IsValid(ix.gui.scoreboard)) then + ix.gui.scoreboard:Remove() + end + else + -- Auto-reload support for faction pay timers. + for index, faction in ipairs(ix.faction.indices) do + for _, v in ipairs(team.GetPlayers(index)) do + if (faction.pay and faction.pay > 0) then + timer.Adjust("ixSalary"..v:UniqueID(), faction.payTime or 300, 0) + else + timer.Remove("ixSalary"..v:UniqueID()) + end + end + end + end + + if (!IX_RELOADED) then + IX_RELOADED = true + + -- Load all of the Helix plugins. + ix.plugin.Initialize() + -- Restore the configurations from earlier if applicable. + ix.config.Load() + -- Restore client options + ix.option.Load() + end +end + +-- Include default Helix chat commands. +ix.util.Include("core/sh_commands.lua") + +if (SERVER and game.IsDedicated()) then + concommand.Remove("gm_save") + + concommand.Add("gm_save", function(client, command, arguments) end) + concommand.Add("gmod_admin_cleanup", function(client, command, arguments) end) +end + +-- add entries for c_viewmodels that aren't set by default +player_manager.AddValidModel("group02male01", "models/humans/group02/male_01.mdl") +player_manager.AddValidHands("group02male01", "models/weapons/c_arms_citizen.mdl", 1, "0000000") +player_manager.AddValidModel("group02male03", "models/humans/group02/male_03.mdl") +player_manager.AddValidHands("group02male03", "models/weapons/c_arms_citizen.mdl", 1, "0000000") +player_manager.AddValidModel("group01female07", "models/player/group01/female_07.mdl") +player_manager.AddValidHands("group01female07", "models/weapons/c_arms_citizen.mdl", 1, "0000000") +player_manager.AddValidModel("group02female03", "models/player/group01/female_03.mdl") +player_manager.AddValidHands("group02female03", "models/weapons/c_arms_citizen.mdl", 1, "0000000") diff --git a/garrysmod/gamemodes/helix/helix.txt b/garrysmod/gamemodes/helix/helix.txt new file mode 100644 index 0000000..c314ddd --- /dev/null +++ b/garrysmod/gamemodes/helix/helix.txt @@ -0,0 +1,6 @@ +"helix" +{ + "base" "sandbox" + "title" "Helix" + "author" "nebulous.cloud" +} diff --git a/garrysmod/gamemodes/helix/helix.yml b/garrysmod/gamemodes/helix/helix.yml new file mode 100644 index 0000000..b340785 --- /dev/null +++ b/garrysmod/gamemodes/helix/helix.yml @@ -0,0 +1,7 @@ +database: + adapter: "mysqloo" + hostname: "77.91.76.196" + username: "u3_3OLj1JmZKT" + password: "R7zGPG!pzbnT^4jI18sw7da=" + database: "s3_ftvnu" + port: 3306 diff --git a/garrysmod/gamemodes/helix/plugins/3dpanel.lua b/garrysmod/gamemodes/helix/plugins/3dpanel.lua new file mode 100644 index 0000000..f0ee408 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/3dpanel.lua @@ -0,0 +1,388 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "3D Panels" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Adds web panels that can be placed on the map." + +-- List of available panel dislays. +PLUGIN.list = PLUGIN.list or {} + +if (SERVER) then + util.AddNetworkString("ixPanelList") + util.AddNetworkString("ixPanelAdd") + util.AddNetworkString("ixPanelRemove") + + -- Called when the player is sending client info. + function PLUGIN:PlayerInitialSpawn(client) + -- Send the list of panel displays. + timer.Simple(1, function() + if (IsValid(client)) then + local json = util.TableToJSON(self.list) + local compressed = util.Compress(json) + local length = compressed:len() + + net.Start("ixPanelList") + net.WriteUInt(length, 32) + net.WriteData(compressed, length) + net.Send(client) + end + end) + end + + -- Adds a panel to the list, sends it to the players, and saves data. + function PLUGIN:AddPanel(position, angles, url, scale, brightness) + scale = math.Clamp((scale or 1) * 0.1, 0.001, 5) + brightness = math.Clamp(math.Round((brightness or 100) * 2.55), 1, 255) + + -- Find an ID for this panel within the list. + local index = #self.list + 1 + + -- Add the panel to the list so it can be sent and saved. + self.list[index] = {position, angles, nil, nil, scale, url, nil, brightness} + + -- Send the panel information to the players. + net.Start("ixPanelAdd") + net.WriteUInt(index, 32) + net.WriteVector(position) + net.WriteAngle(angles) + net.WriteFloat(scale) + net.WriteString(url) + net.WriteUInt(brightness, 8) + net.Broadcast() + + -- Save the plugin data. + self:SavePanels() + end + + -- Removes a panel that are within the radius of a position. + function PLUGIN:RemovePanel(position, radius) + -- Default the radius to 100. + radius = radius or 100 + + local panelsDeleted = {} + + -- Loop through all of the panels. + for k, v in pairs(self.list) do + if (k == 0) then + continue + end + + -- Check if the distance from our specified position to the panel is less than the radius. + if (v[1]:Distance(position) <= radius) then + panelsDeleted[#panelsDeleted + 1] = k + end + end + + -- Save the plugin data if we actually changed anything. + if (#panelsDeleted > 0) then + -- Invert index table to delete from highest -> lowest + panelsDeleted = table.Reverse(panelsDeleted) + + for _, v in ipairs(panelsDeleted) do + -- Remove the panel from the list of panels. + table.remove(self.list, v) + + -- Tell the players to stop showing the panel. + net.Start("ixPanelRemove") + net.WriteUInt(v, 32) + net.Broadcast() + end + + self:SavePanels() + end + + -- Return the number of deleted panels. + return #panelsDeleted + end + + -- Called after entities have been loaded on the map. + function PLUGIN:LoadData() + self.list = self:GetData() or {} + + -- Formats table to sequential to support legacy panels. + self.list = table.ClearKeys(self.list) + end + + -- Called when the plugin needs to save information. + function PLUGIN:SavePanels() + self:SetData(self.list) + end +else + -- Pre-define the zero index in client before the net receives + PLUGIN.list[0] = PLUGIN.list[0] or 0 + + -- Holds the current cached material and filename. + local cachedPreview = {} + + local function CacheMaterial(index) + if (index < 1) then + return + end + + local info = PLUGIN.list[index] + local exploded = string.Explode("/", info[6]) + local filename = exploded[#exploded] + local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/" + + if (file.Exists(path..filename, "DATA")) then + local material = Material("../data/"..path..filename, "noclamp smooth") + + if (!material:IsError()) then + info[7] = material + + -- Set width and height + info[3] = material:GetInt("$realwidth") + info[4] = material:GetInt("$realheight") + end + else + file.CreateDir(path) + + http.Fetch(info[6], function(body) + file.Write(path..filename, body) + + local material = Material("../data/"..path..filename, "noclamp smooth") + + if (!material:IsError()) then + info[7] = material + + -- Set width and height + info[3] = material:GetInt("$realwidth") + info[4] = material:GetInt("$realheight") + end + end) + end + end + + local function UpdateCachedPreview(url) + local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/" + + -- Gets the file name + local exploded = string.Explode("/", url) + local filename = exploded[#exploded] + + if (file.Exists(path..filename, "DATA")) then + local preview = Material("../data/"..path..filename, "noclamp smooth") + + -- Update the cached preview if success + if (!preview:IsError()) then + cachedPreview = {url, preview} + else + cachedPreview = {} + end + else + file.CreateDir(path) + + http.Fetch(url, function(body) + file.Write(path..filename, body) + + local preview = Material("../data/"..path..filename, "noclamp smooth") + + -- Update the cached preview if success + if (!preview:IsError()) then + cachedPreview = {url, preview} + else + cachedPreview = {} + end + end) + end + end + + -- Receives new panel objects that need to be drawn. + net.Receive("ixPanelAdd", function() + local index = net.ReadUInt(32) + local position = net.ReadVector() + local angles = net.ReadAngle() + local scale = net.ReadFloat() + local url = net.ReadString() + local brightness = net.ReadUInt(8) + + if (url != "") then + PLUGIN.list[index] = {position, angles, nil, nil, scale, url, nil, brightness} + + CacheMaterial(index) + + PLUGIN.list[0] = #PLUGIN.list + end + end) + + net.Receive("ixPanelRemove", function() + local index = net.ReadUInt(32) + + table.remove(PLUGIN.list, index) + + PLUGIN.list[0] = #PLUGIN.list + end) + + -- Receives a full update on ALL panels. + net.Receive("ixPanelList", function() + local length = net.ReadUInt(32) + local data = net.ReadData(length) + local uncompressed = util.Decompress(data) + + if (!uncompressed) then + ErrorNoHalt("[Helix] Unable to decompress panel data!\n") + return + end + + -- Set the list of panels to the ones provided by the server. + PLUGIN.list = util.JSONToTable(uncompressed) + + -- Will be saved, but refresh just to make sure. + PLUGIN.list[0] = #PLUGIN.list + + local CacheQueue = {} + + -- Loop through the list of panels. + for k, _ in pairs(PLUGIN.list) do + if (k == 0) then + continue + end + + CacheQueue[#CacheQueue + 1] = k + end + + if (#CacheQueue == 0) then + return + end + + timer.Create("ixCache3DPanels", 1, #CacheQueue, function() + if (#CacheQueue > 0) then + CacheMaterial(CacheQueue[1]) + + table.remove(CacheQueue, 1) + else + timer.Remove("ixCache3DPanels") + end + end) + end) + + -- Called after all translucent objects are drawn. + function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox) + if (bDrawingDepth or bDrawingSkybox) then + return + end + + -- Panel preview + if (ix.chat.currentCommand == "paneladd") then + self:PreviewPanel() + end + + -- Store the position of the player to be more optimized. + local ourPosition = LocalPlayer():GetPos() + + local panel = self.list + + for i = 1, panel[0] do + local position = panel[i][1] + local image = panel[i][7] + + -- Older panels do not have a brightness index + local brightness = panel[i][8] or 255 + + if (panel[i][7] and ourPosition:DistToSqr(position) <= 4194304) then + cam.Start3D2D(position, panel[i][2], panel[i][5] or 0.1) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + render.PushFilterMag(TEXFILTER.ANISOTROPIC) + surface.SetDrawColor(brightness, brightness, brightness) + surface.SetMaterial(image) + surface.DrawTexturedRect(0, 0, panel[i][3] or image:Width(), panel[i][4] or image:Height()) + render.PopFilterMag() + render.PopFilterMin() + cam.End3D2D() + end + end + end + + function PLUGIN:ChatTextChanged(text) + if (ix.chat.currentCommand == "paneladd") then + -- Allow time for ix.chat.currentArguments to update + timer.Simple(0, function() + local arguments = ix.chat.currentArguments + + if (!arguments[1]) then + return + end + + UpdateCachedPreview(arguments[1]) + end) + end + end + + function PLUGIN:PreviewPanel() + local arguments = ix.chat.currentArguments + + -- if there's no URL, then no preview. + if (!arguments[1]) then + return + end + + -- If the material is valid, preview the panel + if (cachedPreview[2] and !cachedPreview[2]:IsError()) then + local trace = LocalPlayer():GetEyeTrace() + local angles = trace.HitNormal:Angle() + angles:RotateAroundAxis(angles:Up(), 90) + angles:RotateAroundAxis(angles:Forward(), 90) + local position = (trace.HitPos + angles:Up() * 0.1) + local ourPosition = LocalPlayer():GetPos() + + -- validate argument types + local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5) + local brightness = math.Clamp(math.Round((tonumber(arguments[3]) or 100) * 2.55), 1, 255) + + -- Attempt to collect the dimensions from the Material + local width, height = cachedPreview[2]:GetInt("$realwidth"), cachedPreview[2]:GetInt("$realheight") + + if (ourPosition:DistToSqr(position) <= 4194304) then + cam.Start3D2D(position, angles, scale or 0.1) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + render.PushFilterMag(TEXFILTER.ANISOTROPIC) + surface.SetDrawColor(brightness, brightness, brightness) + surface.SetMaterial(cachedPreview[2]) + surface.DrawTexturedRect(0, 0, width or cachedPreview[2]:Width(), height or cachedPreview[2]:Height()) + render.PopFilterMag() + render.PopFilterMin() + cam.End3D2D() + end + end + end +end + +ix.command.Add("PanelAdd", { + description = "@cmdPanelAdd", + privilege = "Manage Panels", + adminOnly = true, + arguments = { + ix.type.string, + bit.bor(ix.type.number, ix.type.optional), + bit.bor(ix.type.number, ix.type.optional) + }, + OnRun = function(self, client, url, scale, brightness) + -- Get the position and angles of the panel. + local trace = client:GetEyeTrace() + local position = trace.HitPos + local angles = trace.HitNormal:Angle() + angles:RotateAroundAxis(angles:Up(), 90) + angles:RotateAroundAxis(angles:Forward(), 90) + + -- Add the panel. + PLUGIN:AddPanel(position + angles:Up() * 0.1, angles, url, scale, brightness) + return "@panelAdded" + end +}) + +ix.command.Add("PanelRemove", { + description = "@cmdPanelRemove", + privilege = "Manage Panels", + adminOnly = true, + arguments = bit.bor(ix.type.number, ix.type.optional), + OnRun = function(self, client, radius) + -- Get the origin to remove panel. + local trace = client:GetEyeTrace() + local position = trace.HitPos + -- Remove the panel(s) and get the amount removed. + local amount = PLUGIN:RemovePanel(position, radius) + + return "@panelRemoved", amount + end +}) diff --git a/garrysmod/gamemodes/helix/plugins/3dtext.lua b/garrysmod/gamemodes/helix/plugins/3dtext.lua new file mode 100644 index 0000000..0a683a7 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/3dtext.lua @@ -0,0 +1,341 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "3D Text" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Adds text that can be placed on the map." + +-- List of available text panels +PLUGIN.list = PLUGIN.list or {} + +if (SERVER) then + util.AddNetworkString("ixTextList") + util.AddNetworkString("ixTextAdd") + util.AddNetworkString("ixTextRemove") + + ix.log.AddType("undo3dText", function(client) + return string.format("%s has removed their last 3D text.", client:GetName()) + end) + + -- Called when the player is sending client info. + function PLUGIN:PlayerInitialSpawn(client) + timer.Simple(1, function() + if (IsValid(client)) then + local json = util.TableToJSON(self.list) + local compressed = util.Compress(json) + local length = compressed:len() + + net.Start("ixTextList") + net.WriteUInt(length, 32) + net.WriteData(compressed, length) + net.Send(client) + end + end) + end + + -- Adds a text to the list, sends it to the players, and saves data. + function PLUGIN:AddText(position, angles, text, scale) + local index = #self.list + 1 + scale = math.Clamp((scale or 1) * 0.1, 0.001, 5) + + self.list[index] = {position, angles, text, scale} + + net.Start("ixTextAdd") + net.WriteUInt(index, 32) + net.WriteVector(position) + net.WriteAngle(angles) + net.WriteString(text) + net.WriteFloat(scale) + net.Broadcast() + + self:SaveText() + return index + end + + -- Removes a text that are within the radius of a position. + function PLUGIN:RemoveText(position, radius) + radius = radius or 100 + + local textDeleted = {} + + for k, v in pairs(self.list) do + if (k == 0) then + continue + end + + if (v[1]:Distance(position) <= radius) then + textDeleted[#textDeleted + 1] = k + end + end + + if (#textDeleted > 0) then + -- Invert index table to delete from highest -> lowest + textDeleted = table.Reverse(textDeleted) + + for _, v in ipairs(textDeleted) do + table.remove(self.list, v) + + net.Start("ixTextRemove") + net.WriteUInt(v, 32) + net.Broadcast() + end + + self:SaveText() + end + + return #textDeleted + end + + function PLUGIN:RemoveTextByID(id) + local info = self.list[id] + + if (!info) then + return false + end + + net.Start("ixTextRemove") + net.WriteUInt(id, 32) + net.Broadcast() + + table.remove(self.list, id) + return true + end + + -- Called after entities have been loaded on the map. + function PLUGIN:LoadData() + self.list = self:GetData() or {} + + -- Formats table to sequential to support legacy panels. + self.list = table.ClearKeys(self.list) + end + + -- Called when the plugin needs to save information. + function PLUGIN:SaveText() + self:SetData(self.list) + end +else + -- Pre-define the zero index in client before the net receives + PLUGIN.list[0] = PLUGIN.list[0] or 0 + + language.Add("Undone_ix3dText", "Removed 3D Text") + + function PLUGIN:GenerateMarkup(text) + local object = ix.markup.Parse(""..text:gsub("\\n", "\n")) + + object.onDrawText = function(surfaceText, font, x, y, color, alignX, alignY, alpha) + -- shadow + surface.SetTextPos(x + 1, y + 1) + surface.SetTextColor(0, 0, 0, alpha) + surface.SetFont(font) + surface.DrawText(surfaceText) + + surface.SetTextPos(x, y) + surface.SetTextColor(color.r or 255, color.g or 255, color.b or 255, alpha) + surface.SetFont(font) + surface.DrawText(surfaceText) + end + + return object + end + + -- Receives new text objects that need to be drawn. + net.Receive("ixTextAdd", function() + local index = net.ReadUInt(32) + local position = net.ReadVector() + local angles = net.ReadAngle() + local text = net.ReadString() + local scale = net.ReadFloat() + + if (text != "") then + PLUGIN.list[index] = { + position, + angles, + PLUGIN:GenerateMarkup(text), + scale + } + + PLUGIN.list[0] = #PLUGIN.list + end + end) + + net.Receive("ixTextRemove", function() + local index = net.ReadUInt(32) + + table.remove(PLUGIN.list, index) + + PLUGIN.list[0] = #PLUGIN.list + end) + + -- Receives a full update on ALL texts. + net.Receive("ixTextList", function() + local length = net.ReadUInt(32) + local data = net.ReadData(length) + local uncompressed = util.Decompress(data) + + if (!uncompressed) then + ErrorNoHalt("[Helix] Unable to decompress text data!\n") + return + end + + PLUGIN.list = util.JSONToTable(uncompressed) + + -- Will be saved, but refresh just to make sure. + PLUGIN.list[0] = #PLUGIN.list + + for k, v in pairs(PLUGIN.list) do + if (k == 0) then + continue + end + + local object = ix.markup.Parse(""..v[3]:gsub("\\n", "\n")) + + object.onDrawText = function(text, font, x, y, color, alignX, alignY, alpha) + draw.TextShadow({ + pos = {x, y}, + color = ColorAlpha(color, alpha), + text = text, + xalign = 0, + yalign = alignY, + font = font + }, 1, alpha) + end + + v[3] = object + end + end) + + function PLUGIN:StartChat() + self.preview = nil + end + + function PLUGIN:FinishChat() + self.preview = nil + end + + function PLUGIN:HUDPaint() + if (ix.chat.currentCommand != "textremove") then + return + end + + local radius = tonumber(ix.chat.currentArguments[1]) or 100 + + surface.SetDrawColor(200, 30, 30) + surface.SetTextColor(200, 30, 30) + surface.SetFont("ixMenuButtonFont") + + local i = 0 + + for k, v in pairs(self.list) do + if (k == 0) then + continue + end + + if (v[1]:Distance(LocalPlayer():GetEyeTraceNoCursor().HitPos) <= radius) then + local screen = v[1]:ToScreen() + surface.DrawLine( + ScrW() * 0.5, + ScrH() * 0.5, + math.Clamp(screen.x, 0, ScrW()), + math.Clamp(screen.y, 0, ScrH()) + ) + + i = i + 1 + end + end + + if (i > 0) then + local textWidth, textHeight = surface.GetTextSize(i) + surface.SetTextPos(ScrW() * 0.5 - textWidth * 0.5, ScrH() * 0.5 + textHeight + 8) + surface.DrawText(i) + end + end + + function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox) + if (bDrawingDepth or bDrawingSkybox) then + return + end + + -- preview for textadd command + if (ix.chat.currentCommand == "textadd") then + local arguments = ix.chat.currentArguments + local text = tostring(arguments[1] or "") + local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5) + local trace = LocalPlayer():GetEyeTraceNoCursor() + local position = trace.HitPos + local angles = trace.HitNormal:Angle() + local markup + + angles:RotateAroundAxis(angles:Up(), 90) + angles:RotateAroundAxis(angles:Forward(), 90) + + -- markup will error with invalid fonts + pcall(function() + markup = PLUGIN:GenerateMarkup(text) + end) + + if (markup) then + cam.Start3D2D(position, angles, scale) + markup:draw(0, 0, 1, 1, 255) + cam.End3D2D() + end + end + + local position = LocalPlayer():GetPos() + local texts = self.list + + for i = 1, texts[0] do + local distance = texts[i][1]:DistToSqr(position) + + if (distance > 1048576) then + continue + end + + cam.Start3D2D(texts[i][1], texts[i][2], texts[i][4] or 0.1) + local alpha = (1 - ((distance - 65536) / 768432)) * 255 + texts[i][3]:draw(0, 0, 1, 1, alpha) + cam.End3D2D() + end + end +end + +ix.command.Add("TextAdd", { + description = "@cmdTextAdd", + adminOnly = true, + arguments = { + ix.type.string, + bit.bor(ix.type.number, ix.type.optional) + }, + OnRun = function(self, client, text, scale) + local trace = client:GetEyeTrace() + local position = trace.HitPos + local angles = trace.HitNormal:Angle() + angles:RotateAroundAxis(angles:Up(), 90) + angles:RotateAroundAxis(angles:Forward(), 90) + + local index = PLUGIN:AddText(position + angles:Up() * 0.1, angles, text, scale) + + undo.Create("ix3dText") + undo.SetPlayer(client) + undo.AddFunction(function() + if (PLUGIN:RemoveTextByID(index)) then + ix.log.Add(client, "undo3dText") + end + end) + undo.Finish() + + return "@textAdded" + end +}) + +ix.command.Add("TextRemove", { + description = "@cmdTextRemove", + adminOnly = true, + arguments = bit.bor(ix.type.number, ix.type.optional), + OnRun = function(self, client, radius) + local trace = client:GetEyeTrace() + local position = trace.HitPos + trace.HitNormal * 2 + local amount = PLUGIN:RemoveText(position, radius) + + return "@textRemoved", amount + end +}) diff --git a/garrysmod/gamemodes/helix/plugins/act/cl_hooks.lua b/garrysmod/gamemodes/helix/plugins/act/cl_hooks.lua new file mode 100644 index 0000000..269c352 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/act/cl_hooks.lua @@ -0,0 +1,139 @@ + +local animationTime = 2 + +local PLUGIN = PLUGIN +PLUGIN.cameraFraction = 0 + +local function GetHeadBone(client) + local head + + for i = 1, client:GetBoneCount() do + local name = client:GetBoneName(i) + + if (string.find(name:lower(), "head")) then + head = i + break + end + end + + return head +end + +function PLUGIN:PlayerBindPress(client, bind, bPressed) + if (!client:GetNetVar("actEnterAngle")) then + return + end + + if (bind:find("+jump") and bPressed) then + ix.command.Send("ExitAct") + return true + end +end + +function PLUGIN:ShouldDrawLocalPlayer(client) + if (client:GetNetVar("actEnterAngle") and self.cameraFraction > 0.25) then + return true + elseif (self.cameraFraction > 0.25) then + return true + end +end + +local forwardOffset = 16 +local backwardOffset = -32 +local heightOffset = Vector(0, 0, 20) +local idleHeightOffset = Vector(0, 0, 6) +local traceMin = Vector(-4, -4, -4) +local traceMax = Vector(4, 4, 4) + +function PLUGIN:CalcView(client, origin) + local enterAngle = client:GetNetVar("actEnterAngle") + local fraction = self.cameraFraction + local offset = self.bIdle and forwardOffset or backwardOffset + local height = self.bIdle and idleHeightOffset or heightOffset + + if (!enterAngle) then + if (fraction > 0) then + local view = { + origin = LerpVector(fraction, origin, origin + self.forward * offset + height) + } + + if (self.cameraTween) then + self.cameraTween:update(FrameTime()) + end + + return view + end + + return + end + + local view = {} + local forward = enterAngle:Forward() + local head = GetHeadBone(client) + + local bFirstPerson = true + + if (ix.option.Get("thirdpersonEnabled", false)) then + local originPosition = head and client:GetBonePosition(head) or client:GetPos() + + -- check if the camera will hit something + local data = util.TraceHull({ + start = originPosition, + endpos = originPosition - client:EyeAngles():Forward() * 48, + mins = traceMin * 0.75, + maxs = traceMax * 0.75, + filter = client + }) + + bFirstPerson = data.Hit + + if (!bFirstPerson) then + view.origin = data.HitPos + end + end + + if (bFirstPerson) then + if (head) then + local position = client:GetBonePosition(head) + forward * offset + height + local data = { + start = (client:GetBonePosition(head) or Vector(0, 0, 64)) + forward * 8, + endpos = position + forward * offset, + mins = traceMin, + maxs = traceMax, + filter = client + } + + data = util.TraceHull(data) + + if (data.Hit) then + view.origin = data.HitPos + else + view.origin = position + end + else + view.origin = origin + forward * forwardOffset + height + end + end + + view.origin = LerpVector(fraction, origin, view.origin) + + if (self.cameraTween) then + self.cameraTween:update(FrameTime()) + end + + return view +end + +net.Receive("ixActEnter", function() + PLUGIN.bIdle = net.ReadBool() + PLUGIN.forward = LocalPlayer():GetNetVar("actEnterAngle"):Forward() + PLUGIN.cameraTween = ix.tween.new(animationTime, PLUGIN, { + cameraFraction = 1 + }, "outQuint") +end) + +net.Receive("ixActLeave", function() + PLUGIN.cameraTween = ix.tween.new(animationTime * 0.5, PLUGIN, { + cameraFraction = 0 + }, "outQuint") +end) diff --git a/garrysmod/gamemodes/helix/plugins/act/sh_definitions.lua b/garrysmod/gamemodes/helix/plugins/act/sh_definitions.lua new file mode 100644 index 0000000..09c23aa --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/act/sh_definitions.lua @@ -0,0 +1,166 @@ + +local function FacingWall(client) + local data = {} + data.start = client:EyePos() + data.endpos = data.start + client:GetForward() * 20 + data.filter = client + + if (!util.TraceLine(data).Hit) then + return "@faceWall" + end +end + +local function FacingWallBack(client) + local data = {} + data.start = client:LocalToWorld(client:OBBCenter()) + data.endpos = data.start - client:GetForward() * 20 + data.filter = client + + if (!util.TraceLine(data).Hit) then + return "@faceWallBack" + end +end + +function PLUGIN:SetupActs() + -- sit + ix.act.Register("Sit", {"citizen_male", "citizen_female"}, { + start = {"idle_to_sit_ground", "idle_to_sit_chair"}, + sequence = {"sit_ground", "sit_chair"}, + finish = { + {"sit_ground_to_idle", duration = 2.1}, + "" + }, + untimed = true, + idle = true + }) + + ix.act.Register("SitWall", {"citizen_male", "citizen_female"}, { + sequence = { + {"plazaidle4", check = FacingWallBack}, + {"injured1", check = FacingWallBack, offset = function(client) + return client:GetForward() * 14 + end} + }, + untimed = true, + idle = true + }) + + ix.act.Register("Sit", "vortigaunt", { + sequence = "chess_wait", + untimed = true, + idle = true + }) + + -- stand + ix.act.Register("Stand", "citizen_male", { + sequence = {"lineidle01", "lineidle02", "lineidle03", "lineidle04"}, + untimed = true, + idle = true + }) + + ix.act.Register("Stand", "citizen_female", { + sequence = {"lineidle01", "lineidle02", "lineidle03"}, + untimed = true, + idle = true + }) + + ix.act.Register("Stand", "metrocop", { + sequence = "plazathreat2" + }) + + -- cheer + ix.act.Register("Cheer", "citizen_male", { + sequence = {{"cheer1", duration = 1.6}, "cheer2", "wave_smg1"} + }) + + ix.act.Register("Cheer", "citizen_female", { + sequence = {"cheer1", "wave_smg1"} + }) + + -- lean + ix.act.Register("Lean", {"citizen_male", "citizen_female"}, { + start = {"idle_to_lean_back", "", ""}, + sequence = { + {"lean_back", check = FacingWallBack}, + {"plazaidle1", check = FacingWallBack}, + {"plazaidle2", check = FacingWallBack} + }, + untimed = true, + idle = true + }) + + ix.act.Register("Lean", {"metrocop"}, { + sequence = {{"idle_baton", check = FacingWallBack}, "busyidle2"}, + untimed = true, + idle = true + }) + + -- injured + ix.act.Register("Injured", "citizen_male", { + sequence = {"d1_town05_wounded_idle_1", "d1_town05_wounded_idle_2", "d1_town05_winston_down"}, + untimed = true, + idle = true + }) + + ix.act.Register("Injured", "citizen_female", { + sequence = "d1_town05_wounded_idle_1", + untimed = true, + idle = true + }) + + -- arrest + ix.act.Register("ArrestWall", "citizen_male", { + sequence = { + {"apcarrestidle", + check = FacingWall, + offset = function(client) + return -client:GetForward() * 23 + end}, + "spreadwallidle" + }, + untimed = true + }) + + ix.act.Register("Arrest", "citizen_male", { + sequence = "arrestidle", + untimed = true + }) + + -- threat + ix.act.Register("Threat", "metrocop", { + sequence = "plazathreat1", + }) + + -- deny + ix.act.Register("Deny", "metrocop", { + sequence = "harassfront2", + }) + + -- motion + ix.act.Register("Motion", "metrocop", { + sequence = {"motionleft", "motionright", "luggagewarn"} + }) + + -- wave + ix.act.Register("Wave", {"citizen_male", "citizen_female"}, { + sequence = {{"wave", duration = 2.75}, {"wave_close", duration = 1.75}} + }) + + -- pant + ix.act.Register("Pant", {"citizen_male", "citizen_female"}, { + start = {"d2_coast03_postbattle_idle02_entry", "d2_coast03_postbattle_idle01_entry"}, + sequence = {"d2_coast03_postbattle_idle02", {"d2_coast03_postbattle_idle01", check = FacingWall}}, + untimed = true + }) + + -- window + ix.act.Register("Window", "citizen_male", { + sequence = "d1_t03_tenements_look_out_window_idle", + untimed = true + }) + + ix.act.Register("Window", "citizen_female", { + sequence = "d1_t03_lookoutwindow", + untimed = true + }) +end diff --git a/garrysmod/gamemodes/helix/plugins/act/sh_plugin.lua b/garrysmod/gamemodes/helix/plugins/act/sh_plugin.lua new file mode 100644 index 0000000..0a486a0 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/act/sh_plugin.lua @@ -0,0 +1,259 @@ + +--[[-- +Provides players the ability to perform animations. + +]] +-- @module ix.act + +local PLUGIN = PLUGIN + +PLUGIN.name = "Player Acts" +PLUGIN.description = "Adds animations that can be performed by certain models." +PLUGIN.author = "`impulse" + +ix.act = ix.act or {} +ix.act.stored = ix.act.stored or {} + +CAMI.RegisterPrivilege({ + Name = "Helix - Player Acts", + MinAccess = "user" +}) + +--- Registers a sequence as a performable animation. +-- @realm shared +-- @string name Name of the animation (in CamelCase) +-- @string modelClass Model class to add this animation to +-- @tab data An `ActInfoStructure` table describing the animation +function ix.act.Register(name, modelClass, data) + ix.act.stored[name] = ix.act.stored[name] or {} -- might be adding onto an existing act + + if (!data.sequence) then + return ErrorNoHalt(string.format( + "Act '%s' for '%s' tried to register without a provided sequence\n", name, modelClass + )) + end + + if (!istable(data.sequence)) then + data.sequence = {data.sequence} + end + + if (data.start and istable(data.start) and #data.start != #data.sequence) then + return ErrorNoHalt(string.format( + "Act '%s' tried to register without matching number of enter sequences\n", name + )) + end + + if (data.finish and istable(data.finish) and #data.finish != #data.sequence) then + return ErrorNoHalt(string.format( + "Act '%s' tried to register without matching number of exit sequences\n", name + )) + end + + if (istable(modelClass)) then + for _, v in ipairs(modelClass) do + ix.act.stored[name][v] = data + end + else + ix.act.stored[name][modelClass] = data + end +end + +--- Removes a sequence from being performable if it has been previously registered. +-- @realm shared +-- @string name Name of the animation +function ix.act.Remove(name) + ix.act.stored[name] = nil + ix.command.list["Act" .. name] = nil +end + +ix.util.Include("sh_definitions.lua") +ix.util.Include("sv_hooks.lua") +ix.util.Include("cl_hooks.lua") + +function PLUGIN:InitializedPlugins() + hook.Run("SetupActs") + hook.Run("PostSetupActs") +end + +function PLUGIN:ExitAct(client) + client.ixUntimedSequence = nil + client:SetNetVar("actEnterAngle") + + net.Start("ixActLeave") + net.Send(client) +end + +function PLUGIN:PostSetupActs() + -- create chat commands for all stored acts + for act, classes in pairs(ix.act.stored) do + local variants = 1 + local COMMAND = { + privilege = "Player Acts" + } + + -- check if this act has any variants (i.e /ActSit 2) + for _, v in pairs(classes) do + if (#v.sequence > 1) then + variants = math.max(variants, #v.sequence) + end + end + + -- setup command arguments if there are variants for this act + if (variants > 1) then + COMMAND.arguments = bit.bor(ix.type.number, ix.type.optional) + COMMAND.argumentNames = {"variant (1-" .. variants .. ")"} + end + + COMMAND.GetDescription = function(command) + return L("cmdAct", act) + end + + local privilege = "Helix - " .. COMMAND.privilege + + -- we'll perform a model class check in OnCheckAccess to prevent the command from showing up on the client at all + COMMAND.OnCheckAccess = function(command, client) + local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil) + + if (!bHasAccess) then + return false + end + + local modelClass = ix.anim.GetModelClass(client:GetModel()) + + if (!classes[modelClass]) then + return false, "modelNoSeq" + end + + return true + end + + COMMAND.OnRun = function(command, client, variant) + variant = math.Clamp(tonumber(variant) or 1, 1, variants) + + if (client:GetNetVar("actEnterAngle")) then + return "@notNow" + end + + local modelClass = ix.anim.GetModelClass(client:GetModel()) + local bCanEnter, error = PLUGIN:CanPlayerEnterAct(client, modelClass, variant, classes) + + if (!bCanEnter) then + return error + end + + local data = classes[modelClass] + local mainSequence = data.sequence[variant] + local mainDuration + + -- check if the main sequence has any extra info + if (istable(mainSequence)) then + -- any validity checks to perform (i.e facing a wall) + if (mainSequence.check) then + local result = mainSequence.check(client) + + if (result) then + return result + end + end + + -- position offset + if (mainSequence.offset) then + client.ixOldPosition = client:GetPos() + client:SetPos(client:GetPos() + mainSequence.offset(client)) + end + + mainDuration = mainSequence.duration + mainSequence = mainSequence[1] + end + + local startSequence = data.start and data.start[variant] or "" + local startDuration + + if (istable(startSequence)) then + startDuration = startSequence.duration + startSequence = startSequence[1] + end + + client:SetNetVar("actEnterAngle", client:GetAngles()) + + client:ForceSequence(startSequence, function() + -- we've finished the start sequence + client.ixUntimedSequence = data.untimed -- client can exit after the start sequence finishes playing + + local duration = client:ForceSequence(mainSequence, function() + -- we've stopped playing the main sequence (either duration expired or user cancelled the act) + if (data.finish) then + local finishSequence = data.finish[variant] + local finishDuration + + if (istable(finishSequence)) then + finishDuration = finishSequence.duration + finishSequence = finishSequence[1] + end + + client:ForceSequence(finishSequence, function() + -- client has finished the end sequence and is no longer playing any animations + self:ExitAct(client) + end, finishDuration) + else + -- there's no end sequence so we can exit right away + self:ExitAct(client) + end + end, data.untimed and 0 or (mainDuration or nil)) + + if (!duration) then + -- the model doesn't support this variant + self:ExitAct(client) + client:NotifyLocalized("modelNoSeq") + + return + end + end, startDuration, nil) + + net.Start("ixActEnter") + net.WriteBool(data.idle or false) + net.Send(client) + + client.ixNextAct = CurTime() + 4 + end + + ix.command.Add("Act" .. act, COMMAND) + end + + -- setup exit act command + local COMMAND = { + privilege = "Player Acts", + OnRun = function(command, client) + if (client.ixUntimedSequence) then + client:LeaveSequence() + end + end + } + + if (CLIENT) then + -- hide this command from the command list + COMMAND.OnCheckAccess = function(client) + return false + end + end + + ix.command.Add("ExitAct", COMMAND) +end + +function PLUGIN:UpdateAnimation(client, moveData) + local angle = client:GetNetVar("actEnterAngle") + + if (angle) then + client:SetRenderAngles(angle) + end +end + +do + local keyBlacklist = IN_ATTACK + IN_ATTACK2 + + function PLUGIN:StartCommand(client, command) + if (client:GetNetVar("actEnterAngle")) then + command:RemoveKey(keyBlacklist) + end + end +end diff --git a/garrysmod/gamemodes/helix/plugins/act/sv_hooks.lua b/garrysmod/gamemodes/helix/plugins/act/sv_hooks.lua new file mode 100644 index 0000000..a3789e4 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/act/sv_hooks.lua @@ -0,0 +1,52 @@ + +local PLUGIN = PLUGIN + +util.AddNetworkString("ixActEnter") +util.AddNetworkString("ixActLeave") + +function PLUGIN:CanPlayerEnterAct(client, modelClass, variant, act) + if (!client:Alive() or client:GetLocalVar("ragdoll") or client:WaterLevel() > 0 or !client:IsOnGround()) then + return false, L("notNow", client) + end + + -- check if player's model class has an entry in this act table + modelClass = modelClass or ix.anim.GetModelClass(client:GetModel()) + local data = act[modelClass] + + if (!data) then + return false, L("modelNoSeq", client) + end + + -- some models don't support certain variants + local sequence = data.sequence[variant] + + if (!sequence) then + return false, L("modelNoSeq", client) + end + + return true +end + +function PLUGIN:PlayerDeath(client) + if (client.ixUntimedSequence) then + client:SetNetVar("actEnterAngle") + client:LeaveSequence() + client.ixUntimedSequence = nil + end +end + +function PLUGIN:PlayerSpawn(client) + if (client.ixUntimedSequence) then + client:SetNetVar("actEnterAngle") + client:LeaveSequence() + client.ixUntimedSequence = nil + end +end + +function PLUGIN:OnCharacterFallover(client) + if (client.ixUntimedSequence) then + client:SetNetVar("actEnterAngle") + client:LeaveSequence() + client.ixUntimedSequence = nil + end +end diff --git a/garrysmod/gamemodes/helix/plugins/ammosave.lua b/garrysmod/gamemodes/helix/plugins/ammosave.lua new file mode 100644 index 0000000..c5fd7cd --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/ammosave.lua @@ -0,0 +1,89 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Ammo Saver" +PLUGIN.author = "Black Tea" +PLUGIN.description = "Saves the ammo of a character." +PLUGIN.ammoList = {} + +ix.ammo = ix.ammo or {} + +function ix.ammo.Register(name) + name = name:lower() + + if (!table.HasValue(PLUGIN.ammoList, name)) then + PLUGIN.ammoList[#PLUGIN.ammoList + 1] = name + end +end + +-- Register Default HL2 Ammunition. +ix.ammo.Register("ar2") +ix.ammo.Register("pistol") +ix.ammo.Register("357") +ix.ammo.Register("smg1") +ix.ammo.Register("xbowbolt") +ix.ammo.Register("buckshot") +ix.ammo.Register("rpg_round") +ix.ammo.Register("smg1_grenade") +ix.ammo.Register("grenade") +ix.ammo.Register("ar2altfire") +ix.ammo.Register("slam") + +-- Register Cut HL2 Ammunition. +ix.ammo.Register("alyxgun") +ix.ammo.Register("sniperround") +ix.ammo.Register("sniperpenetratedround") +ix.ammo.Register("thumper") +ix.ammo.Register("gravity") +ix.ammo.Register("battery") +ix.ammo.Register("gaussenergy") +ix.ammo.Register("combinecannon") +ix.ammo.Register("airboatgun") +ix.ammo.Register("striderminigun") +ix.ammo.Register("helicoptergun") + +-- Called right before the character has its information save. +function PLUGIN:CharacterPreSave(character) + -- Get the player from the character. + local client = character:GetPlayer() + + -- Check to see if we can get the player's ammo. + if (IsValid(client)) then + local ammoTable = {} + + for _, v in ipairs(self.ammoList) do + local ammo = client:GetAmmoCount(v) + + if (ammo > 0) then + ammoTable[v] = ammo + end + end + + character:SetData("ammo", ammoTable) + end +end + +-- Called after the player's loadout has been set. +function PLUGIN:PlayerLoadedCharacter(client) + timer.Simple(0.25, function() + if (!IsValid(client)) then + return + end + + -- Get the saved ammo table from the character data. + local character = client:GetCharacter() + + if (!character) then + return + end + + local ammoTable = character:GetData("ammo") + + -- Check if the ammotable is exists. + if (ammoTable) then + for k, v in pairs(ammoTable) do + client:SetAmmo(v, tostring(k)) + end + end + end) +end diff --git a/garrysmod/gamemodes/helix/plugins/area/cl_hooks.lua b/garrysmod/gamemodes/helix/plugins/area/cl_hooks.lua new file mode 100644 index 0000000..662f0fc --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/cl_hooks.lua @@ -0,0 +1,258 @@ + +local PLUGIN = PLUGIN + +local function DrawTextBackground(x, y, text, font, backgroundColor, padding) + font = font or "ixSubTitleFont" + padding = padding or 8 + backgroundColor = backgroundColor or Color(88, 88, 88, 255) + + surface.SetFont(font) + local textWidth, textHeight = surface.GetTextSize(text) + local width, height = textWidth + padding * 2, textHeight + padding * 2 + + ix.util.DrawBlurAt(x, y, width, height) + surface.SetDrawColor(0, 0, 0, 40) + surface.DrawRect(x, y, width, height) + + derma.SkinFunc("DrawImportantBackground", x, y, width, height, backgroundColor) + + surface.SetTextColor(color_white) + surface.SetTextPos(x + padding, y + padding) + surface.DrawText(text) + + return height +end + +function PLUGIN:InitPostEntity() + hook.Run("SetupAreaProperties") +end + +function PLUGIN:ChatboxCreated() + if (IsValid(self.panel)) then + self.panel:Remove() + end + + self.panel = vgui.Create("ixArea") +end + +function PLUGIN:ChatboxPositionChanged(x, y, width, height) + if (!IsValid(self.panel)) then + return + end + + self.panel:SetSize(width, y) + self.panel:SetPos(32, 0) +end + +function PLUGIN:ShouldDrawCrosshair() + if (ix.area.bEditing) then + return true + end +end + +function PLUGIN:PlayerBindPress(client, bind, bPressed) + if (!ix.area.bEditing) then + return + end + + if ((bind:find("invnext") or bind:find("invprev")) and bPressed) then + return true + elseif (bind:find("attack2") and bPressed) then + self:EditRightClick() + return true + elseif (bind:find("attack") and bPressed) then + self:EditClick() + return true + elseif (bind:find("reload") and bPressed) then + self:EditReload() + return true + end +end + +function PLUGIN:HUDPaint() + if (!ix.area.bEditing) then + return + end + + local id = LocalPlayer():GetArea() + local area = ix.area.stored[id] + local height = ScrH() + + local y = 64 + y = y + DrawTextBackground(64, y, L("areaEditMode"), nil, ix.config.Get("color")) + + if (!self.editStart) then + y = y + DrawTextBackground(64, y, L("areaEditTip"), "ixSmallTitleFont") + DrawTextBackground(64, y, L("areaRemoveTip"), "ixSmallTitleFont") + else + DrawTextBackground(64, y, L("areaFinishTip"), "ixSmallTitleFont") + end + + if (area) then + DrawTextBackground(64, height - 64 - ScreenScale(12), id, "ixSmallTitleFont", area.properties.color) + end +end + +function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox) + if (bSkybox or !ix.area.bEditing) then + return + end + + -- draw all areas + for k, v in pairs(ix.area.stored) do + local center, min, max = self:GetLocalAreaPosition(v.startPosition, v.endPosition) + local color = ColorAlpha(v.properties.color or ix.config.Get("color"), 255) + + render.DrawWireframeBox(center, angle_zero, min, max, color) + + cam.Start2D() + local centerScreen = center:ToScreen() + local _, textHeight = draw.SimpleText( + k, "BudgetLabel", centerScreen.x, centerScreen.y, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if (v.type != "area") then + draw.SimpleText( + "(" .. L(v.type) .. ")", "BudgetLabel", + centerScreen.x, centerScreen.y + textHeight, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER + ) + end + cam.End2D() + end + + -- draw currently edited area + if (self.editStart) then + local center, min, max = self:GetLocalAreaPosition(self.editStart, self:GetPlayerAreaTrace().HitPos) + local color = Color(255, 255, 255, 25 + (1 + math.sin(SysTime() * 6)) * 115) + + render.DrawWireframeBox(center, angle_zero, min, max, color) + + cam.Start2D() + local centerScreen = center:ToScreen() + + draw.SimpleText(L("areaNew"), "BudgetLabel", + centerScreen.x, centerScreen.y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End2D() + end +end + +function PLUGIN:EditRightClick() + if (self.editStart) then + self.editStart = nil + else + self:StopEditing() + end +end + +function PLUGIN:EditClick() + if (!self.editStart) then + self.editStart = LocalPlayer():GetEyeTraceNoCursor().HitPos + elseif (self.editStart and !self.editProperties) then + self.editProperties = true + + local panel = vgui.Create("ixAreaEdit") + panel:MakePopup() + end +end + +function PLUGIN:EditReload() + if (self.editStart) then + return + end + + local id = LocalPlayer():GetArea() + local area = ix.area.stored[id] + + if (!area) then + return + end + + Derma_Query(L("areaDeleteConfirm", id), L("areaDelete"), + L("no"), nil, + L("yes"), function() + net.Start("ixAreaRemove") + net.WriteString(id) + net.SendToServer() + end + ) +end + +function PLUGIN:ShouldDisplayArea(id) + if (ix.area.bEditing) then + return false + end +end + +function PLUGIN:OnAreaChanged(oldID, newID) + local client = LocalPlayer() + client.ixArea = newID + + local area = ix.area.stored[newID] + + if (!area) then + client.ixInArea = false + return + end + + client.ixInArea = true + + if (hook.Run("ShouldDisplayArea", newID) == false or !area.properties.display) then + return + end + + local format = newID .. (ix.option.Get("24hourTime", false) and ", %H:%M." or ", %I:%M %p.") + format = ix.date.GetFormatted(format) + + self.panel:AddEntry(format, area.properties.color) +end + +net.Receive("ixAreaEditStart", function() + PLUGIN:StartEditing() +end) + +net.Receive("ixAreaEditEnd", function() + PLUGIN:StopEditing() +end) + +net.Receive("ixAreaAdd", function() + local name = net.ReadString() + local type = net.ReadString() + local startPosition, endPosition = net.ReadVector(), net.ReadVector() + local properties = net.ReadTable() + + if (name != "") then + ix.area.stored[name] = { + type = type, + startPosition = startPosition, + endPosition = endPosition, + properties = properties + } + end +end) + +net.Receive("ixAreaRemove", function() + local name = net.ReadString() + + if (ix.area.stored[name]) then + ix.area.stored[name] = nil + end +end) + +net.Receive("ixAreaSync", function() + local length = net.ReadUInt(32) + local data = net.ReadData(length) + local uncompressed = util.Decompress(data) + + if (!uncompressed) then + ErrorNoHalt("[Helix] Unable to decompress area data!\n") + return + end + + -- Set the list of texts to the ones provided by the server. + ix.area.stored = util.JSONToTable(uncompressed) +end) + +net.Receive("ixAreaChanged", function() + local oldID, newID = net.ReadString(), net.ReadString() + + hook.Run("OnAreaChanged", oldID, newID) +end) diff --git a/garrysmod/gamemodes/helix/plugins/area/cl_plugin.lua b/garrysmod/gamemodes/helix/plugins/area/cl_plugin.lua new file mode 100644 index 0000000..9539ab9 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/cl_plugin.lua @@ -0,0 +1,26 @@ + +local PLUGIN = PLUGIN + +function PLUGIN:GetPlayerAreaTrace() + local client = LocalPlayer() + + return util.TraceLine({ + start = client:GetShootPos(), + endpos = client:GetShootPos() + client:GetForward() * 96, + filter = client + }) +end + +function PLUGIN:StartEditing() + ix.area.bEditing = true + self.editStart = nil + self.editProperties = nil +end + +function PLUGIN:StopEditing() + ix.area.bEditing = false + + if (IsValid(ix.gui.areaEdit)) then + ix.gui.areaEdit:Remove() + end +end diff --git a/garrysmod/gamemodes/helix/plugins/area/derma/cl_area.lua b/garrysmod/gamemodes/helix/plugins/area/derma/cl_area.lua new file mode 100644 index 0000000..20eb873 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/derma/cl_area.lua @@ -0,0 +1,182 @@ + +-- area entry +DEFINE_BASECLASS("Panel") +local PANEL = {} + +AccessorFunc(PANEL, "text", "Text", FORCE_STRING) +AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") +AccessorFunc(PANEL, "tickSound", "TickSound", FORCE_STRING) +AccessorFunc(PANEL, "tickSoundRange", "TickSoundRange") +AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha", FORCE_NUMBER) +AccessorFunc(PANEL, "expireTime", "ExpireTime", FORCE_NUMBER) +AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER) + +function PANEL:Init() + self:DockPadding(4, 4, 4, 4) + self:SetSize(self:GetParent():GetWide(), 0) + + self.label = self:Add("DLabel") + self.label:Dock(FILL) + self.label:SetFont("ixMediumLightFont") + self.label:SetTextColor(color_white) + self.label:SetExpensiveShadow(1, color_black) + self.label:SetText("Area") + + self.text = "" + self.tickSound = "ui/buttonrollover.wav" + self.tickSoundRange = {190, 200} + self.backgroundAlpha = 255 + self.expireTime = 8 + self.animationTime = 2 + + self.character = 1 + self.createTime = RealTime() + self.currentAlpha = 255 + self.currentHeight = 0 + self.nextThink = RealTime() +end + +function PANEL:Show() + self:CreateAnimation(0.5, { + index = -1, + target = {currentHeight = self.label:GetTall() + 8}, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetTall(panel.currentHeight) + end + }) +end + +function PANEL:SetFont(font) + self.label:SetFont(font) +end + +function PANEL:SetText(text) + if (text:sub(1, 1) == "@") then + text = L(text:sub(2)) + end + + self.label:SetText(text) + self.text = text + self.character = 1 +end + +function PANEL:Think() + local time = RealTime() + + if (time >= self.nextThink) then + if (self.character < self.text:utf8len()) then + self.character = self.character + 1 + self.label:SetText(string.utf8sub(self.text, 1, self.character)) + + LocalPlayer():EmitSound(self.tickSound, 100, math.random(self.tickSoundRange[1], self.tickSoundRange[2])) + end + + if (time >= self.createTime + self.expireTime and !self.bRemoving) then + self:Remove() + end + + self.nextThink = time + 0.05 + end +end + +function PANEL:SizeToContents() + self:SetWide(self:GetParent():GetWide()) + + self.label:SetWide(self:GetWide()) + self.label:SizeToContentsY() +end + +function PANEL:Paint(width, height) + self.backgroundAlpha = math.max(self.backgroundAlpha - 200 * FrameTime(), 0) + + derma.SkinFunc("PaintAreaEntry", self, width, height) +end + +function PANEL:Remove() + if (self.bRemoving) then + return + end + + self:CreateAnimation(self.animationTime, { + target = {currentAlpha = 0}, + + Think = function(animation, panel) + panel:SetAlpha(panel.currentAlpha) + end, + + OnComplete = function(animation, panel) + panel:CreateAnimation(0.5, { + index = -1, + target = {currentHeight = 0}, + easing = "outQuint", + + Think = function(_, sizePanel) + sizePanel:SetTall(sizePanel.currentHeight) + end, + + OnComplete = function(_, sizePanel) + sizePanel:OnRemove() + BaseClass.Remove(sizePanel) + end + }) + end + }) + + self.bRemoving = true +end + +function PANEL:OnRemove() +end + +vgui.Register("ixAreaEntry", PANEL, "Panel") + +-- main panel +PANEL = {} + +function PANEL:Init() + local chatWidth, _ = chat.GetChatBoxSize() + local _, chatY = chat.GetChatBoxPos() + + self:SetSize(chatWidth, chatY) + self:SetPos(32, 0) + self:ParentToHUD() + + self.entries = {} + ix.gui.area = self +end + +function PANEL:AddEntry(entry, color) + color = color or ix.config.Get("color") + + local id = #self.entries + 1 + local panel = entry + + if (isstring(entry)) then + panel = self:Add("ixAreaEntry") + panel:SetText(entry) + end + + panel:SetBackgroundColor(color) + panel:SizeToContents() + panel:Dock(BOTTOM) + panel:Show() + panel.OnRemove = function() + for k, v in pairs(self.entries) do + if (v == panel) then + table.remove(self.entries, k) + break + end + end + end + + self.entries[id] = panel + return id +end + +function PANEL:GetEntries() + return self.entries +end + +vgui.Register("ixArea", PANEL, "Panel") diff --git a/garrysmod/gamemodes/helix/plugins/area/derma/cl_areaedit.lua b/garrysmod/gamemodes/helix/plugins/area/derma/cl_areaedit.lua new file mode 100644 index 0000000..4cfd971 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/derma/cl_areaedit.lua @@ -0,0 +1,194 @@ + +local PLUGIN = PLUGIN +local PANEL = {} + +function PANEL:Init() + if (IsValid(ix.gui.areaEdit)) then + ix.gui.areaEdit:Remove() + end + + ix.gui.areaEdit = self + self.list = {} + self.properties = {} + + self:SetDeleteOnClose(true) + self:SetSizable(true) + self:SetTitle(L("areaNew")) + + -- scroll panel + self.canvas = self:Add("DScrollPanel") + self.canvas:Dock(FILL) + + -- name entry + self.nameEntry = vgui.Create("ixTextEntry") + self.nameEntry:SetFont("ixMediumLightFont") + self.nameEntry:SetText(L("areaNew")) + + local listRow = self.canvas:Add("ixListRow") + listRow:SetList(self.list) + listRow:SetLabelText(L("name")) + listRow:SetRightPanel(self.nameEntry) + listRow:Dock(TOP) + listRow:SizeToContents() + + -- type entry + self.typeEntry = self.canvas:Add("DComboBox") + self.typeEntry:Dock(RIGHT) + self.typeEntry:SetFont("ixMediumLightFont") + self.typeEntry:SetTextColor(color_black) + self.typeEntry.OnSelect = function(panel) + panel:SizeToContents() + panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice) + end + + for id, name in pairs(ix.area.types) do + self.typeEntry:AddChoice(L(name), id, id == "area") + end + + listRow = self.canvas:Add("ixListRow") + listRow:SetList(self.list) + listRow:SetLabelText(L("type")) + listRow:SetRightPanel(self.typeEntry) + listRow:Dock(TOP) + listRow:SizeToContents() + + -- properties + for k, v in pairs(ix.area.properties) do + local panel + + if (v.type == ix.type.string or v.type == ix.type.number) then + panel = vgui.Create("ixTextEntry") + panel:SetFont("ixMenuButtonFont") + panel:SetText(tostring(v.default)) + + if (v.type == ix.type.number) then + panel.realGetValue = panel.GetValue + panel.GetValue = function() + return tonumber(panel:realGetValue()) or v.default + end + end + elseif (v.type == ix.type.bool) then + panel = vgui.Create("ixCheckBox") + panel:SetChecked(v.default, true) + panel:SetFont("ixMediumLightFont") + elseif (v.type == ix.type.color) then + panel = vgui.Create("DButton") + panel.value = v.default + panel:SetText("") + panel:SetSize(64, 64) + + panel.picker = vgui.Create("DColorCombo") + panel.picker:SetColor(panel.value) + panel.picker:SetVisible(false) + panel.picker.OnValueChanged = function(_, newColor) + panel.value = newColor + end + + panel.Paint = function(_, width, height) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawOutlinedRect(0, 0, width, height) + + surface.SetDrawColor(panel.value) + surface.DrawRect(4, 4, width - 8, height - 8) + end + + panel.DoClick = function() + if (!panel.picker:IsVisible()) then + local x, y = panel:LocalToScreen(0, 0) + + panel.picker:SetPos(x, y + 32) + panel.picker:SetColor(panel.value) + panel.picker:SetVisible(true) + panel.picker:MakePopup() + else + panel.picker:SetVisible(false) + end + end + + panel.OnRemove = function() + panel.picker:Remove() + end + + panel.GetValue = function() + return panel.picker:GetColor() + end + end + + if (IsValid(panel)) then + local row = self.canvas:Add("ixListRow") + row:SetList(self.list) + row:SetLabelText(L(k)) + row:SetRightPanel(panel) + row:Dock(TOP) + row:SizeToContents() + end + + self.properties[k] = function() + return panel:GetValue() + end + end + + -- save button + self.saveButton = self:Add("DButton") + self.saveButton:SetText(L("save")) + self.saveButton:SizeToContents() + self.saveButton:Dock(BOTTOM) + self.saveButton.DoClick = function() + self:Submit() + end + + self:SizeToContents() + self:SetPos(64, 0) + self:CenterVertical() +end + +function PANEL:SizeToContents() + local width = 600 + local height = 37 + + for _, v in ipairs(self.canvas:GetCanvas():GetChildren()) do + width = math.max(width, v:GetLabelWidth()) + height = height + v:GetTall() + end + + self:SetWide(width + 200) + self:SetTall(height + self.saveButton:GetTall() + 50) +end + +function PANEL:Submit() + local name = self.nameEntry:GetValue() + + if (ix.area.stored[name]) then + ix.util.NotifyLocalized("areaAlreadyExists") + return + end + + local properties = {} + + for k, v in pairs(self.properties) do + properties[k] = v() + end + + local _, type = self.typeEntry:GetSelected() + + net.Start("ixAreaAdd") + net.WriteString(name) + net.WriteString(type) + net.WriteVector(PLUGIN.editStart) + net.WriteVector(PLUGIN:GetPlayerAreaTrace().HitPos) + net.WriteTable(properties) + net.SendToServer() + + PLUGIN.editStart = nil + self:Remove() +end + +function PANEL:OnRemove() + PLUGIN.editProperties = nil +end + +vgui.Register("ixAreaEdit", PANEL, "DFrame") + +if (IsValid(ix.gui.areaEdit)) then + ix.gui.areaEdit:Remove() +end diff --git a/garrysmod/gamemodes/helix/plugins/area/languages/sh_english.lua b/garrysmod/gamemodes/helix/plugins/area/languages/sh_english.lua new file mode 100644 index 0000000..53ec68b --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/languages/sh_english.lua @@ -0,0 +1,16 @@ +LANGUAGE = { + area = "Area", + areas = "Areas", + areaEditMode = "Area Edit Mode", + areaNew = "New Area", + areaAlreadyExists = "An area with this name already exists!", + areaDoesntExist = "An area with that name doesn't exist!", + areaInvalidType = "You have specified an invalid area type!", + areaEditTip = "Click to start creating an area. Right click to exit.", + areaFinishTip = "Click again to finish drawing the area. Right click to go back.", + areaRemoveTip = "Press reload to remove the area you're currently in.", + areaDeleteConfirm = "Are you sure you want to delete the area \"%s\"?", + areaDelete = "Delete Area", + + cmdAreaEdit = "Enters area edit mode." +} diff --git a/garrysmod/gamemodes/helix/plugins/area/languages/sh_russian.lua b/garrysmod/gamemodes/helix/plugins/area/languages/sh_russian.lua new file mode 100644 index 0000000..a79ee08 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/languages/sh_russian.lua @@ -0,0 +1,16 @@ +LANGUAGE = { + area = "Зона", + areas = "Зоны", + areaEditMode = "Редактирование зоны", + areaNew = "Новая зона", + areaAlreadyExists = "Зона с таким названием уже существует!", + areaDoesntExist = "Зона с таким названием не существует!", + areaInvalidType = "Вы указали неверный тип зоны!", + areaEditTip = "Нажмите, чтобы начать создание зоны. Щелкните правой кнопкой мыши, чтобы выйти.", + areaFinishTip = "Нажмите еще раз, чтобы закончить создание зоны. Щелкните правой кнопкой мыши, чтобы вернуться.", + areaRemoveTip = "Нажмите перезарядку, чтобы удалить зону, в которой вы находитесь.", + areaDeleteConfirm = "Вы уверены, что хотите удалить зону \"%s\"?", + areaDelete = "Удалить зону", + + cmdAreaEdit = "Вход в режим редактирования зоны." +} diff --git a/garrysmod/gamemodes/helix/plugins/area/sh_plugin.lua b/garrysmod/gamemodes/helix/plugins/area/sh_plugin.lua new file mode 100644 index 0000000..de19acf --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/sh_plugin.lua @@ -0,0 +1,90 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Areas" +PLUGIN.author = "`impulse" +PLUGIN.description = "Provides customizable area definitions." + +ix.area = ix.area or {} +ix.area.types = ix.area.types or {} +ix.area.properties = ix.area.properties or {} +ix.area.stored = ix.area.stored or {} + +ix.config.Add("areaTickTime", 1, "How many seconds between each time a character's current area is calculated.", + function(oldValue, newValue) + if (SERVER) then + timer.Remove("ixAreaThink") + timer.Create("ixAreaThink", newValue, 0, function() + PLUGIN:AreaThink() + end) + end + end, + { + data = {min = 0.1, max = 4}, + category = "areas" + } +) + +function ix.area.AddProperty(name, type, default, data) + ix.area.properties[name] = { + type = type, + default = default + } +end + +function ix.area.AddType(type, name) + name = name or type + + -- only store localized strings on the client + ix.area.types[type] = CLIENT and name or true +end + +function PLUGIN:SetupAreaProperties() + ix.area.AddType("area") + + ix.area.AddProperty("color", ix.type.color, ix.config.Get("color")) + ix.area.AddProperty("display", ix.type.bool, true) +end + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") +ix.util.Include("sv_hooks.lua") +ix.util.Include("cl_hooks.lua") + +-- return world center, local min, and local max from world start/end positions +function PLUGIN:GetLocalAreaPosition(startPosition, endPosition) + local center = LerpVector(0.5, startPosition, endPosition) + local min = WorldToLocal(startPosition, angle_zero, center, angle_zero) + local max = WorldToLocal(endPosition, angle_zero, center, angle_zero) + + return center, min, max +end + +do + local COMMAND = {} + COMMAND.description = "@cmdAreaEdit" + COMMAND.adminOnly = true + + function COMMAND:OnRun(client) + client:SetWepRaised(false) + + net.Start("ixAreaEditStart") + net.Send(client) + end + + ix.command.Add("AreaEdit", COMMAND) +end + +do + local PLAYER = FindMetaTable("Player") + + -- returns the current area the player is in, or the last valid one if the player is not in an area + function PLAYER:GetArea() + return self.ixArea + end + + -- returns true if the player is in any area, this does not use the last valid area like GetArea does + function PLAYER:IsInArea() + return self.ixInArea + end +end diff --git a/garrysmod/gamemodes/helix/plugins/area/sv_hooks.lua b/garrysmod/gamemodes/helix/plugins/area/sv_hooks.lua new file mode 100644 index 0000000..33197d5 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/sv_hooks.lua @@ -0,0 +1,126 @@ + +function PLUGIN:LoadData() + hook.Run("SetupAreaProperties") + ix.area.stored = self:GetData() or {} + + timer.Create("ixAreaThink", ix.config.Get("areaTickTime"), 0, function() + self:AreaThink() + end) +end + +function PLUGIN:SaveData() + self:SetData(ix.area.stored) +end + +function PLUGIN:PlayerInitialSpawn(client) + timer.Simple(1, function() + if (IsValid(client)) then + local json = util.TableToJSON(ix.area.stored) + local compressed = util.Compress(json) + local length = compressed:len() + + net.Start("ixAreaSync") + net.WriteUInt(length, 32) + net.WriteData(compressed, length) + net.Send(client) + end + end) +end + +function PLUGIN:PlayerLoadedCharacter(client) + client.ixArea = "" + client.ixInArea = nil +end + +function PLUGIN:PlayerSpawn(client) + client.ixArea = "" + client.ixInArea = nil +end + +function PLUGIN:AreaThink() + for _, client in player.Iterator() do + local character = client:GetCharacter() + + if (!client:Alive() or !character) then + continue + end + + local overlappingBoxes = {} + local position = client:GetPos() + client:OBBCenter() + + for id, info in pairs(ix.area.stored) do + if (position:WithinAABox(info.startPosition, info.endPosition)) then + overlappingBoxes[#overlappingBoxes + 1] = id + end + end + + if (#overlappingBoxes > 0) then + local oldID = client:GetArea() + local id = overlappingBoxes[1] + + if (oldID != id) then + hook.Run("OnPlayerAreaChanged", client, client.ixArea, id) + client.ixArea = id + end + + client.ixInArea = true + else + client.ixInArea = false + end + end +end + +function PLUGIN:OnPlayerAreaChanged(client, oldID, newID) + net.Start("ixAreaChanged") + net.WriteString(oldID) + net.WriteString(newID) + net.Send(client) +end + +net.Receive("ixAreaAdd", function(length, client) + if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then + return + end + + local id = net.ReadString() + local type = net.ReadString() + local startPosition, endPosition = net.ReadVector(), net.ReadVector() + local properties = net.ReadTable() + + if (!ix.area.types[type]) then + client:NotifyLocalized("areaInvalidType") + return + end + + if (ix.area.stored[id]) then + client:NotifyLocalized("areaAlreadyExists") + return + end + + for k, v in pairs(properties) do + if (!isstring(k) or !ix.area.properties[k]) then + continue + end + + properties[k] = ix.util.SanitizeType(ix.area.properties[k].type, v) + end + + ix.area.Create(id, type, startPosition, endPosition, nil, properties) + ix.log.Add(client, "areaAdd", id) +end) + +net.Receive("ixAreaRemove", function(length, client) + if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then + return + end + + local id = net.ReadString() + + if (!ix.area.stored[id]) then + client:NotifyLocalized("areaDoesntExist") + return + end + + ix.area.Remove(id) + ix.log.Add(client, "areaRemove", id) +end) diff --git a/garrysmod/gamemodes/helix/plugins/area/sv_plugin.lua b/garrysmod/gamemodes/helix/plugins/area/sv_plugin.lua new file mode 100644 index 0000000..d993d7a --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/area/sv_plugin.lua @@ -0,0 +1,55 @@ + +util.AddNetworkString("ixAreaSync") +util.AddNetworkString("ixAreaAdd") +util.AddNetworkString("ixAreaRemove") +util.AddNetworkString("ixAreaChanged") + +util.AddNetworkString("ixAreaEditStart") +util.AddNetworkString("ixAreaEditEnd") + +ix.log.AddType("areaAdd", function(client, name) + return string.format("%s has added area \"%s\".", client:Name(), tostring(name)) +end) + +ix.log.AddType("areaRemove", function(client, name) + return string.format("%s has removed area \"%s\".", client:Name(), tostring(name)) +end) + +local function SortVector(first, second) + return Vector(math.min(first.x, second.x), math.min(first.y, second.y), math.min(first.z, second.z)), + Vector(math.max(first.x, second.x), math.max(first.y, second.y), math.max(first.z, second.z)) +end + +function ix.area.Create(name, type, startPosition, endPosition, bNoReplicate, properties) + local min, max = SortVector(startPosition, endPosition) + + ix.area.stored[name] = { + type = type or "area", + startPosition = min, + endPosition = max, + bNoReplicate = bNoReplicate, + properties = properties + } + + -- network to clients if needed + if (!bNoReplicate) then + net.Start("ixAreaAdd") + net.WriteString(name) + net.WriteString(type) + net.WriteVector(startPosition) + net.WriteVector(endPosition) + net.WriteTable(properties) + net.Broadcast() + end +end + +function ix.area.Remove(name, bNoReplicate) + ix.area.stored[name] = nil + + -- network to clients if needed + if (!bNoReplicate) then + net.Start("ixAreaRemove") + net.WriteString(name) + net.Broadcast() + end +end diff --git a/garrysmod/gamemodes/helix/plugins/containers/entities/entities/ix_container.lua b/garrysmod/gamemodes/helix/plugins/containers/entities/entities/ix_container.lua new file mode 100644 index 0000000..37a84b3 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/containers/entities/entities/ix_container.lua @@ -0,0 +1,180 @@ + +ENT.Type = "anim" +ENT.PrintName = "Container" +ENT.Category = "Helix" +ENT.Spawnable = false +ENT.bNoPersist = true + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "ID") + self:NetworkVar("Bool", 0, "Locked") + self:NetworkVar("String", 0, "DisplayName") +end + +if (SERVER) then + function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self.receivers = {} + + local definition = ix.container.stored[self:GetModel():lower()] + + if (definition) then + self:SetDisplayName(definition.name) + end + + local physObj = self:GetPhysicsObject() + + if (IsValid(physObj)) then + physObj:EnableMotion(true) + physObj:Wake() + end + end + + function ENT:SetInventory(inventory) + if (inventory) then + self:SetID(inventory:GetID()) + end + end + + function ENT:SetMoney(amount) + self.money = math.max(0, math.Round(tonumber(amount) or 0)) + end + + function ENT:GetMoney() + return self.money or 0 + end + + function ENT:OnRemove() + local index = self:GetID() + + if (!ix.shuttingDown and !self.ixIsSafe and ix.entityDataLoaded and index) then + local inventory = ix.item.inventories[index] + + if (inventory) then + ix.item.inventories[index] = nil + + local query = mysql:Delete("ix_items") + query:Where("inventory_id", index) + query:Execute() + + query = mysql:Delete("ix_inventories") + query:Where("inventory_id", index) + query:Execute() + + hook.Run("ContainerRemoved", self, inventory) + end + end + end + + function ENT:OpenInventory(activator) + local inventory = self:GetInventory() + + if (inventory) then + local name = self:GetDisplayName() + local definition = ix.container.stored[self:GetModel():lower()] + + ix.storage.Open(activator, inventory, { + name = name, + entity = self, + searchTime = ix.config.Get("containerOpenTime", 0.7), + data = {money = self:GetMoney()}, + OnPlayerOpen = function() + if (definition.OnOpen) then + definition.OnOpen(self, activator) + end + end, + OnPlayerClose = function() + if (definition.OnClose) then + definition.OnClose(self, activator) + end + + ix.log.Add(activator, "closeContainer", name, inventory:GetID()) + end + }) + + if (self:GetLocked()) then + self.Sessions[activator:GetCharacter():GetID()] = true + end + + ix.log.Add(activator, "openContainer", name, inventory:GetID()) + end + end + + function ENT:Use(activator) + local inventory = self:GetInventory() + + if (inventory and (activator.ixNextOpen or 0) < CurTime()) then + local character = activator:GetCharacter() + + if (character) then + local definition = ix.container.stored[self:GetModel():lower()] + + if (self:GetLocked() and !self.Sessions[character:GetID()]) then + self:EmitSound(definition.locksound or "doors/default_locked.wav") + + if (!self.keypad) then + net.Start("ixContainerPassword") + net.WriteEntity(self) + net.Send(activator) + end + else + self:OpenInventory(activator) + end + end + + activator.ixNextOpen = CurTime() + 1 + end + end +else + ENT.PopulateEntityInfo = true + + local COLOR_LOCKED = Color(200, 38, 19, 200) + local COLOR_UNLOCKED = Color(135, 211, 124, 200) + + function ENT:OnPopulateEntityInfo(tooltip) + local definition = ix.container.stored[self:GetModel():lower()] + local bLocked = self:GetLocked() + + surface.SetFont("ixIconsSmall") + + local iconText = bLocked and "P" or "Q" + local iconWidth, iconHeight = surface.GetTextSize(iconText) + + -- minimal tooltips have centered text so we'll draw the icon above the name instead + if (tooltip:IsMinimal()) then + local icon = tooltip:AddRow("icon") + icon:SetFont("ixIconsSmall") + icon:SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED) + icon:SetText(iconText) + icon:SizeToContents() + end + + local title = tooltip:AddRow("name") + title:SetImportant() + title:SetText(self:GetDisplayName()) + title:SetBackgroundColor(ix.config.Get("color")) + title:SetTextInset(iconWidth + 8, 0) + title:SizeToContents() + + if (!tooltip:IsMinimal()) then + title.Paint = function(panel, width, height) + panel:PaintBackground(width, height) + + surface.SetFont("ixIconsSmall") + surface.SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED) + surface.SetTextPos(4, height * 0.5 - iconHeight * 0.5) + surface.DrawText(iconText) + end + end + + local description = tooltip:AddRow("description") + description:SetText(definition.description) + description:SizeToContents() + end +end + +function ENT:GetInventory() + return ix.item.inventories[self:GetID()] +end diff --git a/garrysmod/gamemodes/helix/plugins/containers/sh_definitions.lua b/garrysmod/gamemodes/helix/plugins/containers/sh_definitions.lua new file mode 100644 index 0000000..516b723 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/containers/sh_definitions.lua @@ -0,0 +1,126 @@ +--[[ + ix.container.Register(model, { + name = "Crate", + description = "A simple wooden create.", + width = 4, + height = 4, + locksound = "", + opensound = "" + }) +]]-- + +ix.container.Register("models/props_junk/wood_crate001a.mdl", { + name = "Crate", + description = "A simple wooden crate.", + width = 4, + height = 4, +}) + +ix.container.Register("models/props_c17/lockers001a.mdl", { + name = "Locker", + description = "A white locker.", + width = 3, + height = 5, +}) + +ix.container.Register("models/props_wasteland/controlroom_storagecloset001a.mdl", { + name = "Metal Cabinet", + description = "A green metal cabinet.", + width = 4, + height = 5, +}) + +ix.container.Register("models/props_wasteland/controlroom_storagecloset001b.mdl", { + name = "Metal Cabinet", + description = "A green metal cabinet.", + width = 4, + height = 5, +}) + +ix.container.Register("models/props_wasteland/controlroom_filecabinet001a.mdl", { + name = "File Cabinet", + description = "A metal filing cabinet.", + width = 5, + height = 3 +}) + +ix.container.Register("models/props_wasteland/controlroom_filecabinet002a.mdl", { + name = "File Cabinet", + description = "A metal filing cabinet.", + width = 3, + height = 6, +}) + +ix.container.Register("models/props_lab/filecabinet02.mdl", { + name = "File Cabinet", + description = "A metal filing cabinet.", + width = 5, + height = 3 +}) + +ix.container.Register("models/props_c17/furniturefridge001a.mdl", { + name = "Refrigerator", + description = "A metal box for keeping food in.", + width = 2, + height = 3, +}) + +ix.container.Register("models/props_wasteland/kitchen_fridge001a.mdl", { + name = "Large Refrigerator", + description = "A large metal box for storing even more food in.", + width = 4, + height = 5, +}) + +ix.container.Register("models/props_junk/trashbin01a.mdl", { + name = "Trash Bin", + description = "What do you expect to find in here?", + width = 2, + height = 2, +}) + +ix.container.Register("models/props_junk/trashdumpster01a.mdl", { + name = "Dumpster", + description = "A dumpster meant to stow away trash. It emanates an unpleasant smell.", + width = 6, + height = 3 +}) + +ix.container.Register("models/items/ammocrate_smg1.mdl", { + name = "Ammo Crate", + description = "A heavy crate that stores ammo.", + width = 5, + height = 3, + OnOpen = function(entity, activator) + local closeSeq = entity:LookupSequence("Close") + entity:ResetSequence(closeSeq) + + timer.Simple(2, function() + if (entity and IsValid(entity)) then + local openSeq = entity:LookupSequence("Open") + entity:ResetSequence(openSeq) + end + end) + end +}) + +ix.container.Register("models/props_forest/footlocker01_closed.mdl", { + name = "Footlocker", + description = "A small chest to store belongings in.", + width = 5, + height = 3 +}) + +ix.container.Register("models/Items/item_item_crate.mdl", { + name = "Item Crate", + description = "A crate to store some belongings in.", + width = 5, + height = 3 +}) + +ix.container.Register("models/props_c17/cashregister01a.mdl", { + name = "Cash Register", + description = "A register with some buttons and a drawer.", + width = 2, + height = 1 +}) diff --git a/garrysmod/gamemodes/helix/plugins/containers/sh_plugin.lua b/garrysmod/gamemodes/helix/plugins/containers/sh_plugin.lua new file mode 100644 index 0000000..d5bde20 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/containers/sh_plugin.lua @@ -0,0 +1,346 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Containers" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Provides the ability to store items." + +ix.container = ix.container or {} +ix.container.stored = ix.container.stored or {} + +ix.config.Add("containerSave", true, "Whether or not containers will save after a server restart.", nil, { + category = "Containers" +}) + +ix.config.Add("containerOpenTime", 0.7, "How long it takes to open a container.", nil, { + data = {min = 0, max = 50}, + category = "Containers" +}) + +function ix.container.Register(model, data) + ix.container.stored[model:lower()] = data +end + +ix.util.Include("sh_definitions.lua") + +if (SERVER) then + util.AddNetworkString("ixContainerPassword") + + function PLUGIN:PlayerSpawnedProp(client, model, entity) + model = tostring(model):lower() + local data = ix.container.stored[model] + + if (data) then + if (hook.Run("CanPlayerSpawnContainer", client, model, entity) == false) then return end + + local container = ents.Create("ix_container") + container:SetPos(entity:GetPos()) + container:SetAngles(entity:GetAngles()) + container:SetModel(model) + container:Spawn() + + ix.inventory.New(0, "container:" .. model:lower(), function(inventory) + -- we'll technically call this a bag since we don't want other bags to go inside + inventory.vars.isBag = true + inventory.vars.isContainer = true + + if (IsValid(container)) then + container:SetInventory(inventory) + self:SaveContainer() + end + end) + + entity:Remove() + end + end + + function PLUGIN:CanSaveContainer(entity, inventory) + return ix.config.Get("containerSave", true) + end + + function PLUGIN:SaveContainer() + local data = {} + + for _, v in ipairs(ents.FindByClass("ix_container")) do + if (hook.Run("CanSaveContainer", v, v:GetInventory()) != false) then + local inventory = v:GetInventory() + + if (inventory) then + data[#data + 1] = { + v:GetPos(), + v:GetAngles(), + inventory:GetID(), + v:GetModel(), + v.password, + v:GetDisplayName(), + v:GetMoney() + } + end + else + local index = v:GetID() + + local query = mysql:Delete("ix_items") + query:Where("inventory_id", index) + query:Execute() + + query = mysql:Delete("ix_inventories") + query:Where("inventory_id", index) + query:Execute() + end + end + + self:SetData(data) + end + + function PLUGIN:SaveData() + if (!ix.shuttingDown) then + self:SaveContainer() + end + end + + function PLUGIN:ContainerRemoved(entity, inventory) + self:SaveContainer() + end + + function PLUGIN:LoadData() + local data = self:GetData() + + if (data) then + for _, v in ipairs(data) do + local data2 = ix.container.stored[v[4]:lower()] + + if (data2) then + local inventoryID = tonumber(v[3]) + + if (!inventoryID or inventoryID < 1) then + ErrorNoHalt(string.format( + "[Helix] Attempted to restore container inventory with invalid inventory ID '%s' (%s, %s)\n", + tostring(inventoryID), v[6] or "no name", v[4] or "no model")) + + continue + end + + local entity = ents.Create("ix_container") + entity:SetPos(v[1]) + entity:SetAngles(v[2]) + entity:Spawn() + entity:SetModel(v[4]) + entity:SetSolid(SOLID_VPHYSICS) + entity:PhysicsInit(SOLID_VPHYSICS) + + if (v[5]) then + entity.password = v[5] + entity:SetLocked(true) + entity.Sessions = {} + entity.PasswordAttempts = {} + end + + if (v[6]) then + entity:SetDisplayName(v[6]) + end + + if (v[7]) then + entity:SetMoney(v[7]) + end + + ix.inventory.Restore(inventoryID, data2.width, data2.height, function(inventory) + inventory.vars.isBag = true + inventory.vars.isContainer = true + + if (IsValid(entity)) then + entity:SetInventory(inventory) + end + end) + + local physObject = entity:GetPhysicsObject() + + if (IsValid(physObject)) then + physObject:EnableMotion() + end + end + end + end + end + + net.Receive("ixContainerPassword", function(length, client) + if ((client.ixNextContainerPassword or 0) > RealTime()) then + return + end + + local entity = net.ReadEntity() + local steamID = client:SteamID() + local attempts = entity.PasswordAttempts[steamID] + + if (attempts and attempts >= 10) then + client:NotifyLocalized("passwordAttemptLimit") + + return + end + + local password = net.ReadString() + local dist = entity:GetPos():DistToSqr(client:GetPos()) + + if (dist < 16384 and password) then + if (entity.password and entity.password == password) then + entity:OpenInventory(client) + else + entity.PasswordAttempts[steamID] = attempts and attempts + 1 or 1 + + client:NotifyLocalized("wrongPassword") + end + end + + client.ixNextContainerPassword = RealTime() + 1 + end) + + ix.log.AddType("containerPassword", function(client, ...) + local arg = {...} + return string.format("%s has %s the password for '%s'.", client:Name(), arg[3] and "set" or "removed", arg[1], arg[2]) + end) + + ix.log.AddType("containerName", function(client, ...) + local arg = {...} + + if (arg[3]) then + return string.format("%s has set container %d name to '%s'.", client:Name(), arg[2], arg[1]) + else + return string.format("%s has removed container %d name.", client:Name(), arg[2]) + end + end) + + ix.log.AddType("openContainer", function(client, ...) + local arg = {...} + return string.format("%s opened the '%s' #%d container.", client:Name(), arg[1], arg[2]) + end, FLAG_NORMAL) + + ix.log.AddType("closeContainer", function(client, ...) + local arg = {...} + return string.format("%s closed the '%s' #%d container.", client:Name(), arg[1], arg[2]) + end, FLAG_NORMAL) +else + net.Receive("ixContainerPassword", function(length) + local entity = net.ReadEntity() + + Derma_StringRequest( + L("containerPasswordWrite"), + L("containerPasswordWrite"), + "", + function(val) + net.Start("ixContainerPassword") + net.WriteEntity(entity) + net.WriteString(val) + net.SendToServer() + end + ) + end) +end + +function PLUGIN:InitializedPlugins() + for k, v in pairs(ix.container.stored) do + if (v.name and v.width and v.height) then + ix.inventory.Register("container:" .. k:lower(), v.width, v.height) + else + ErrorNoHalt("[Helix] Container for '"..k.."' is missing all inventory information!\n") + ix.container.stored[k] = nil + end + end +end + +-- properties +properties.Add("container_setpassword", { + MenuLabel = "Set Password", + Order = 400, + MenuIcon = "icon16/lock_edit.png", + + Filter = function(self, entity, client) + if (entity:GetClass() != "ix_container") then return false end + if (!gamemode.Call("CanProperty", client, "container_setpassword", entity)) then return false end + + return true + end, + + Action = function(self, entity) + Derma_StringRequest(L("containerPasswordWrite"), "", "", function(text) + self:MsgStart() + net.WriteEntity(entity) + net.WriteString(text) + self:MsgEnd() + end) + end, + + Receive = function(self, length, client) + local entity = net.ReadEntity() + + if (!IsValid(entity)) then return end + if (!self:Filter(entity, client)) then return end + + local password = net.ReadString() + + entity.Sessions = {} + entity.PasswordAttempts = {} + + if (password:len() != 0) then + entity:SetLocked(true) + entity.password = password + + client:NotifyLocalized("containerPassword", password) + else + entity:SetLocked(false) + entity.password = nil + + client:NotifyLocalized("containerPasswordRemove") + end + + local name = entity:GetDisplayName() + local inventory = entity:GetInventory() + + ix.log.Add(client, "containerPassword", name, inventory:GetID(), password:len() != 0) + end +}) + +properties.Add("container_setname", { + MenuLabel = "Set Name", + Order = 400, + MenuIcon = "icon16/tag_blue_edit.png", + + Filter = function(self, entity, client) + if (entity:GetClass() != "ix_container") then return false end + if (!gamemode.Call("CanProperty", client, "container_setname", entity)) then return false end + + return true + end, + + Action = function(self, entity) + Derma_StringRequest(L("containerNameWrite"), "", "", function(text) + self:MsgStart() + net.WriteEntity(entity) + net.WriteString(text) + self:MsgEnd() + end) + end, + + Receive = function(self, length, client) + local entity = net.ReadEntity() + + if (!IsValid(entity)) then return end + if (!self:Filter(entity, client)) then return end + + local name = net.ReadString() + + if (name:len() != 0) then + entity:SetDisplayName(name) + + client:NotifyLocalized("containerName", name) + else + local definition = ix.container.stored[entity:GetModel():lower()] + + entity:SetDisplayName(definition.name) + + client:NotifyLocalized("containerNameRemove") + end + + local inventory = entity:GetInventory() + + ix.log.Add(client, "containerName", name, inventory:GetID(), name:len() != 0) + end +}) diff --git a/garrysmod/gamemodes/helix/plugins/crosshair.lua b/garrysmod/gamemodes/helix/plugins/crosshair.lua new file mode 100644 index 0000000..bfe995a --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/crosshair.lua @@ -0,0 +1,110 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Crosshair" +PLUGIN.author = "Black Tea" +PLUGIN.description = "A Crosshair." + +if (CLIENT) then + local function drawdot( pos, size, col ) + local color = col[2] + surface.SetDrawColor(color.r, color.g, color.b, color.a) + surface.DrawRect(pos[1] - size/2, pos[2] - size/2, size, size) + + color = col[1] + surface.SetDrawColor(color.r, color.g, color.b, color.a) + surface.DrawOutlinedRect(pos[1] - size/2, pos[2] - size/2 , size, size) + end + + local aimVector, punchAngle, ft, screen, scaleFraction, distance + local math_round = math.Round + local curGap = 0 + local curAlpha = 0 + local maxDistance = 1000 ^ 2 + local crossSize = 4 + local crossGap = 0 + local colors = {color_black} + local filter = {} + + function PLUGIN:DrawCrosshair(x, y, trace) + local entity = trace.Entity + distance = trace.StartPos:DistToSqr(trace.HitPos) + scaleFraction = 1 - math.Clamp(distance / maxDistance, 0, .5) + crossSize = 4 + crossGap = 25 * (scaleFraction - (LocalPlayer():IsWepRaised() and 0 or .1)) + + if (IsValid(entity) and entity:GetClass() == "ix_item" and + entity:GetPos():DistToSqr(trace.StartPos) <= 16384) then + crossGap = 0 + crossSize = 5 + end + + curGap = Lerp(ft * 2, curGap, crossGap) + curAlpha = Lerp(ft * 2, curAlpha, !LocalPlayer():IsWepRaised() and 255 or 150) + curAlpha = hook.Run("GetCrosshairAlpha", curAlpha) or curAlpha + colors[2] = Color(255, curAlpha, curAlpha, curAlpha) + + if (curAlpha > 1) then + drawdot( {math_round(screen.x), math_round(screen.y)}, crossSize, colors) + drawdot( {math_round(screen.x + curGap), math_round(screen.y)}, crossSize, colors) + drawdot( {math_round(screen.x - curGap), math_round(screen.y)}, crossSize, colors) + drawdot( {math_round(screen.x), math_round(screen.y + curGap * .8)}, crossSize, colors) + drawdot( {math_round(screen.x), math_round(screen.y - curGap * .8)}, crossSize, colors) + end + end + + -- luacheck: globals g_ContextMenu + function PLUGIN:PostDrawHUD() + local client = LocalPlayer() + if (!client:GetCharacter() or !client:Alive()) then + return + end + + local entity = Entity(client:GetLocalVar("ragdoll", 0)) + + if (entity:IsValid()) then + return + end + + local wep = client:GetActiveWeapon() + local bShouldDraw = hook.Run("ShouldDrawCrosshair", client, wep) + + if (bShouldDraw == false or !IsValid(wep) or wep.DrawCrosshair == false) then + return + end + + if (bShouldDraw == false or g_ContextMenu:IsVisible() or + (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing())) then + return + end + + aimVector = client:EyeAngles() + punchAngle = client:GetViewPunchAngles() + ft = FrameTime() + filter = {client} + + local vehicle = client:GetVehicle() + if (vehicle and IsValid(vehicle)) then + aimVector = aimVector + vehicle:GetAngles() + table.insert(filter, vehicle) + end + + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + (aimVector + punchAngle):Forward() * 65535 + data.filter = filter + local trace = util.TraceLine(data) + + local drawTarget = self + local drawFunction = self.DrawCrosshair + + -- we'll manually call this since CHudCrosshair is never drawn; checks are already performed + if (wep.DoDrawCrosshair) then + drawTarget = wep + drawFunction = wep.DoDrawCrosshair + end + + screen = trace.HitPos:ToScreen() + drawFunction(drawTarget, screen.x, screen.y, trace) + end +end diff --git a/garrysmod/gamemodes/helix/plugins/doors/cl_plugin.lua b/garrysmod/gamemodes/helix/plugins/doors/cl_plugin.lua new file mode 100644 index 0000000..d6a3148 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/doors/cl_plugin.lua @@ -0,0 +1,204 @@ + +-- luacheck: globals ACCESS_LABELS +ACCESS_LABELS = {} +ACCESS_LABELS[DOOR_OWNER] = "owner" +ACCESS_LABELS[DOOR_TENANT] = "tenant" +ACCESS_LABELS[DOOR_GUEST] = "guest" +ACCESS_LABELS[DOOR_NONE] = "none" + +function PLUGIN:GetDefaultDoorInfo(door) + local owner = IsValid(door:GetDTEntity(0)) and door:GetDTEntity(0) or nil + local name = door:GetNetVar("title", door:GetNetVar("name", IsValid(owner) and L"dTitleOwned" or L"dTitle")) + local description = door:GetNetVar("ownable") and L("dIsOwnable") or L("dIsNotOwnable") + local color = ix.config.Get("color") + local faction = door:GetNetVar("faction") + local class = door:GetNetVar("class") + + if (class) then + local classData = ix.class.list[class] + + if (classData) then + if (classData.color) then + color = classData.color + end + + if (!owner) then + description = L("dOwnedBy", L2(classData.name) or classData.name) + end + end + elseif (faction) then + local info = ix.faction.indices[faction] + color = team.GetColor(faction) + + if (info and !owner) then + description = L("dOwnedBy", L2(info.name) or info.name) + end + end + + if (owner) then + description = L("dOwnedBy", owner:GetName()) + end + + return { + name = name, + description = description, + color = color + } +end + +function PLUGIN:DrawDoorInfo(door, width, position, angles, scale, clientPosition) + local alpha = math.max((1 - clientPosition:DistToSqr(door:GetPos()) / 65536) * 255, 0) + + if (alpha < 1) then + return + end + + local info = hook.Run("GetDoorInfo", door) or self:GetDefaultDoorInfo(door) + + if (!istable(info) or table.IsEmpty(info)) then + return + end + + -- title + background + surface.SetFont("ix3D2DMediumFont") + local nameWidth, nameHeight = surface.GetTextSize(info.name) + + derma.SkinFunc("DrawImportantBackground", -width * 0.5, -nameHeight * 0.5, + width, nameHeight, ColorAlpha(info.color, alpha * 0.5)) + + surface.SetTextColor(ColorAlpha(color_white, alpha)) + surface.SetTextPos(-nameWidth * 0.5, -nameHeight * 0.5) + surface.DrawText(info.name) + + -- description + local lines = ix.util.WrapText(info.description, width, "ix3D2DSmallFont") + local y = nameHeight * 0.5 + 4 + + for i = 1, #lines do + local line = lines[i] + local textWidth, textHeight = surface.GetTextSize(line) + + surface.SetTextPos(-textWidth * 0.5, y) + surface.DrawText(line) + + y = y + textHeight + end + + -- background blur + ix.util.PushBlur(function() + cam.Start3D2D(position, angles, scale) + surface.SetDrawColor(11, 11, 11, math.max(alpha - 100, 0)) + surface.DrawRect(-width * 0.5, -nameHeight * 0.5, width, y + nameHeight * 0.5 + 4) + cam.End3D2D() + end) +end + +function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox) + if (bDepth or bSkybox or !LocalPlayer():GetCharacter()) then + return + end + + local entities = ents.FindInSphere(EyePos(), 256) + local clientPosition = LocalPlayer():GetPos() + + for _, v in ipairs(entities) do + if (!IsValid(v) or !v:IsDoor() or !v:GetNetVar("visible")) then + continue + end + + local color = v:GetColor() + + if (v:IsEffectActive(EF_NODRAW) or color.a <= 0) then + continue + end + + local position = v:LocalToWorld(v:OBBCenter()) + local mins, maxs = v:GetCollisionBounds() + local width = 0 + local size = maxs - mins + local trace = { + collisiongroup = COLLISION_GROUP_WORLD, + ignoreworld = true, + endpos = position + } + + -- trace from shortest side to center to get correct position for rendering + if (size.z < size.x and size.z < size.y) then + trace.start = position - v:GetUp() * size.z + width = size.y + elseif (size.x < size.y) then + trace.start = position - v:GetForward() * size.x + width = size.y + elseif (size.y < size.x) then + trace.start = position - v:GetRight() * size.y + width = size.x + end + + width = math.max(width, 12) + trace = util.TraceLine(trace) + + local angles = trace.HitNormal:Angle() + local anglesOpposite = trace.HitNormal:Angle() + + angles:RotateAroundAxis(angles:Forward(), 90) + angles:RotateAroundAxis(angles:Right(), 90) + anglesOpposite:RotateAroundAxis(anglesOpposite:Forward(), 90) + anglesOpposite:RotateAroundAxis(anglesOpposite:Right(), -90) + + local positionFront = trace.HitPos - (((position - trace.HitPos):Length() * 2) + 1) * trace.HitNormal + local positionOpposite = trace.HitPos + (trace.HitNormal * 2) + + if (trace.HitNormal:Dot((clientPosition - position):GetNormalized()) < 0) then + -- draw front + cam.Start3D2D(positionFront, angles, 0.1) + self:DrawDoorInfo(v, width * 8, positionFront, angles, 0.1, clientPosition) + cam.End3D2D() + else + -- draw back + cam.Start3D2D(positionOpposite, anglesOpposite, 0.1) + self:DrawDoorInfo(v, width * 8, positionOpposite, anglesOpposite, 0.1, clientPosition) + cam.End3D2D() + end + end +end + +net.Receive("ixDoorMenu", function() + if (IsValid(ix.gui.door)) then + return ix.gui.door:Remove() + end + + local door = net.ReadEntity() + local access = net.ReadTable() + + if (IsValid(door) and !table.IsEmpty(access)) then + local entity = net.ReadEntity() + + ix.gui.door = vgui.Create("ixDoorMenu") + ix.gui.door:SetDoor(door, access, entity) + end +end) + +net.Receive("ixDoorPermission", function() + local door = net.ReadEntity() + + if (!IsValid(door)) then + return + end + + local target = net.ReadEntity() + local access = net.ReadUInt(4) + + local panel = door.ixPanel + + if (IsValid(panel) and IsValid(target)) then + panel.access[target] = access + + for _, v in ipairs(panel.access:GetLines()) do + if (v.player == target) then + v:SetColumnText(2, L(ACCESS_LABELS[access or 0])) + + return + end + end + end +end) diff --git a/garrysmod/gamemodes/helix/plugins/doors/derma/cl_door.lua b/garrysmod/gamemodes/helix/plugins/doors/derma/cl_door.lua new file mode 100644 index 0000000..81f063e --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/doors/derma/cl_door.lua @@ -0,0 +1,102 @@ + +local PANEL = {} + +local function DoorSetPermission(door, target, permission) + net.Start("ixDoorPermission") + net.WriteEntity(door) + net.WriteEntity(target) + net.WriteUInt(permission, 4) + net.SendToServer() +end + +function PANEL:Init() + self:SetSize(280, 240) + self:SetTitle(L"doorSettings") + self:Center() + self:MakePopup() + + self.access = self:Add("DListView") + self.access:Dock(FILL) + self.access:AddColumn(L"name").Header:SetTextColor(Color(25, 25, 25)) + self.access:AddColumn(L"access").Header:SetTextColor(Color(25, 25, 25)) + self.access.OnClickLine = function(this, line, selected) + if (IsValid(line.player)) then + local menu = DermaMenu() + menu:AddOption(L"tenant", function() + if (self.accessData and self.accessData[line.player] != DOOR_TENANT) then + DoorSetPermission(self.door, line.player, DOOR_TENANT) + end + end):SetImage("icon16/user_add.png") + menu:AddOption(L"guest", function() + if (self.accessData and self.accessData[line.player] != DOOR_GUEST) then + DoorSetPermission(self.door, line.player, DOOR_GUEST) + end + end):SetImage("icon16/user_green.png") + menu:AddOption(L"none", function() + if (self.accessData and self.accessData[line.player] != DOOR_NONE) then + DoorSetPermission(self.door, line.player, DOOR_NONE) + end + end):SetImage("icon16/user_red.png") + menu:Open() + end + end +end + +function PANEL:SetDoor(door, access, door2) + door.ixPanel = self + + self.accessData = access + self.door = door + + for _, v in player.Iterator() do + if (v != LocalPlayer() and v:GetCharacter()) then + self.access:AddLine(v:Name(), L(ACCESS_LABELS[access[v] or 0])).player = v + end + end + + if (self:CheckAccess(DOOR_OWNER)) then + self.sell = self:Add("DButton") + self.sell:Dock(BOTTOM) + self.sell:SetText(L"sell") + self.sell:SetTextColor(color_white) + self.sell:DockMargin(0, 5, 0, 0) + self.sell.DoClick = function(this) + self:Remove() + ix.command.Send("doorsell") + end + end + + if (self:CheckAccess(DOOR_TENANT)) then + self.name = self:Add("DTextEntry") + self.name:Dock(TOP) + self.name:DockMargin(0, 0, 0, 5) + self.name.Think = function(this) + if (!this:IsEditing()) then + local entity = IsValid(door2) and door2 or door + + self.name:SetText(entity:GetNetVar("title", L"dTitleOwned")) + end + end + self.name.OnEnter = function(this) + ix.command.Send("doorsettitle", this:GetText()) + end + end +end + +function PANEL:CheckAccess(access) + access = access or DOOR_GUEST + + if ((self.accessData[LocalPlayer()] or 0) >= access) then + return true + end + + return false +end + +function PANEL:Think() + if (self.accessData and !IsValid(self.door) and self:CheckAccess()) then + self:Remove() + end +end + +vgui.Register("ixDoorMenu", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/helix/plugins/doors/entities/weapons/ix_keys.lua b/garrysmod/gamemodes/helix/plugins/doors/entities/weapons/ix_keys.lua new file mode 100644 index 0000000..3d91406 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/doors/entities/weapons/ix_keys.lua @@ -0,0 +1,201 @@ + +AddCSLuaFile() + +if (CLIENT) then + SWEP.PrintName = "Keys" + SWEP.Slot = 0 + SWEP.SlotPos = 2 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +SWEP.Author = "Chessnut" +SWEP.Instructions = "Primary Fire: Lock\nSecondary Fire: Unlock" +SWEP.Purpose = "Hitting things and knocking on doors." +SWEP.Drop = false + +SWEP.ViewModelFOV = 45 +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "rpg" + +SWEP.ViewTranslation = 4 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" +SWEP.Primary.Damage = 5 +SWEP.Primary.Delay = 0.75 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + +SWEP.ViewModel = Model("models/weapons/c_arms_animations.mdl") +SWEP.WorldModel = "" + +SWEP.UseHands = false +SWEP.LowerAngles = Angle(0, 5, -14) +SWEP.LowerAngles2 = Angle(0, 5, -22) + +SWEP.IsAlwaysLowered = true +SWEP.FireWhenLowered = true +SWEP.HoldType = "passive" + +-- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER +ACT_VM_FISTS_DRAW = 2 +ACT_VM_FISTS_HOLSTER = 1 + +function SWEP:Holster() + if (!IsValid(self.Owner)) then + return + end + + local viewModel = self.Owner:GetViewModel() + + if (IsValid(viewModel)) then + viewModel:SetPlaybackRate(1) + viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER) + end + + return true +end + +function SWEP:Precache() +end + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) +end + +function SWEP:PrimaryAttack() + local time = ix.config.Get("doorLockTime", 1) + local time2 = math.max(time, 1) + + self:SetNextPrimaryFire(CurTime() + time2) + self:SetNextSecondaryFire(CurTime() + time2) + + if (!IsFirstTimePredicted()) then + return + end + + if (CLIENT) then + return + end + + local data = {} + data.start = self.Owner:GetShootPos() + data.endpos = data.start + self.Owner:GetAimVector()*96 + data.filter = self.Owner + local entity = util.TraceLine(data).Entity + + --[[ + Locks the entity if the contiditon fits: + 1. The entity is door and client has access to the door. + 2. The entity is vehicle and the "owner" variable is same as client's character ID. + --]] + if (IsValid(entity) and + ( + (entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or + (entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner) + ) + ) then + self.Owner:SetAction("@locking", time, function() + self:ToggleLock(entity, true) + end) + + return + end +end + +function SWEP:ToggleLock(door, state) + if (IsValid(self.Owner) and self.Owner:GetPos():Distance(door:GetPos()) > 96) then + return + end + + if (door:IsDoor()) then + local partner = door:GetDoorPartner() + + if (state) then + if (IsValid(partner)) then + partner:Fire("lock") + end + + door:Fire("lock") + self.Owner:EmitSound("doors/door_latch3.wav") + + hook.Run("PlayerLockedDoor", self.Owner, door, partner) + else + if (IsValid(partner)) then + partner:Fire("unlock") + end + + door:Fire("unlock") + self.Owner:EmitSound("doors/door_latch1.wav") + + hook.Run("PlayerUnlockedDoor", self.Owner, door, partner) + end + elseif (door:IsVehicle()) then + if (state) then + door:Fire("lock") + + if (door.IsSimfphyscar) then + door.IsLocked = true + end + + self.Owner:EmitSound("doors/door_latch3.wav") + hook.Run("PlayerLockedVehicle", self.Owner, door) + else + door:Fire("unlock") + + if (door.IsSimfphyscar) then + door.IsLocked = nil + end + + self.Owner:EmitSound("doors/door_latch1.wav") + hook.Run("PlayerUnlockedVehicle", self.Owner, door) + end + end +end + +function SWEP:SecondaryAttack() + local time = ix.config.Get("doorLockTime", 1) + local time2 = math.max(time, 1) + + self:SetNextPrimaryFire(CurTime() + time2) + self:SetNextSecondaryFire(CurTime() + time2) + + if (!IsFirstTimePredicted()) then + return + end + + if (CLIENT) then + return + end + + local data = {} + data.start = self.Owner:GetShootPos() + data.endpos = data.start + self.Owner:GetAimVector()*96 + data.filter = self.Owner + local entity = util.TraceLine(data).Entity + + + --[[ + Unlocks the entity if the contiditon fits: + 1. The entity is door and client has access to the door. + 2. The entity is vehicle and the "owner" variable is same as client's character ID. + ]]-- + if (IsValid(entity) and + ( + (entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or + (entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner) + ) + ) then + self.Owner:SetAction("@unlocking", time, function() + self:ToggleLock(entity, false) + end) + + return + end +end diff --git a/garrysmod/gamemodes/helix/plugins/doors/sh_commands.lua b/garrysmod/gamemodes/helix/plugins/doors/sh_commands.lua new file mode 100644 index 0000000..39da15d --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/doors/sh_commands.lua @@ -0,0 +1,497 @@ + +local PLUGIN = PLUGIN + +ix.command.Add("DoorSell", { + description = "@cmdDoorSell", + OnRun = function(self, client, arguments) + -- Get the entity 96 units infront of the player. + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local trace = util.TraceLine(data) + local entity = trace.Entity + + -- Check if the entity is a valid door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + -- Check if the player owners the door. + if (client == entity:GetDTEntity(0)) then + entity = IsValid(entity.ixParent) and entity.ixParent or entity + + -- Get the price that the door is sold for. + local price = math.Round(entity:GetNetVar("price", ix.config.Get("doorCost")) * ix.config.Get("doorSellRatio")) + local character = client:GetCharacter() + + -- Remove old door information. + entity:RemoveDoorAccessData() + + local doors = character:GetVar("doors") or {} + + for k, v in ipairs(doors) do + if (v == entity) then + table.remove(doors, k) + end + end + + character:SetVar("doors", doors, true) + + -- Take their money and notify them. + character:GiveMoney(price) + hook.Run("OnPlayerPurchaseDoor", client, entity, false, PLUGIN.CallOnDoorChildren) + + ix.log.Add(client, "selldoor") + return "@dSold", ix.currency.Get(price) + else + -- Otherwise tell them they can not. + return "@notOwner" + end + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorBuy", { + description = "@cmdDoorBuy", + OnRun = function(self, client, arguments) + -- Get the entity 96 units infront of the player. + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local trace = util.TraceLine(data) + local entity = trace.Entity + + -- Check if the entity is a valid door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + if (!entity:GetNetVar("ownable") or entity:GetNetVar("faction") or entity:GetNetVar("class")) then + return "@dNotAllowedToOwn" + end + + if (IsValid(entity:GetDTEntity(0))) then + return "@dOwnedBy", entity:GetDTEntity(0):Name() + end + + entity = IsValid(entity.ixParent) and entity.ixParent or entity + + -- Get the price that the door is bought for. + local price = entity:GetNetVar("price", ix.config.Get("doorCost")) + local character = client:GetCharacter() + + -- Check if the player can actually afford it. + if (character:HasMoney(price)) then + -- Set the door to be owned by this player. + entity:SetDTEntity(0, client) + entity.ixAccess = { + [client] = DOOR_OWNER + } + + PLUGIN:CallOnDoorChildren(entity, function(child) + child:SetDTEntity(0, client) + end) + + local doors = character:GetVar("doors") or {} + doors[#doors + 1] = entity + character:SetVar("doors", doors, true) + + -- Take their money and notify them. + character:TakeMoney(price) + hook.Run("OnPlayerPurchaseDoor", client, entity, true, PLUGIN.CallOnDoorChildren) + + ix.log.Add(client, "buydoor") + return "@dPurchased", ix.currency.Get(price) + else + -- Otherwise tell them they can not. + return "@canNotAfford" + end + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetUnownable", { + description = "@cmdDoorSetUnownable", + privilege = "Manage Doors", + adminOnly = true, + arguments = ix.type.text, + OnRun = function(self, client, name) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + -- Set it so it is unownable. + entity:SetNetVar("ownable", nil) + + -- Change the name of the door if needed. + if (name:find("%S")) then + entity:SetNetVar("name", name) + end + + PLUGIN:CallOnDoorChildren(entity, function(child) + child:SetNetVar("ownable", nil) + + if (name:find("%S")) then + child:SetNetVar("name", name) + end + end) + + -- Save the door information. + PLUGIN:SaveDoorData() + return "@dMadeUnownable" + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetOwnable", { + description = "@cmdDoorSetOwnable", + privilege = "Manage Doors", + adminOnly = true, + arguments = ix.type.text, + OnRun = function(self, client, name) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + -- Set it so it is ownable. + entity:SetNetVar("ownable", true) + entity:SetNetVar("visible", true) + + -- Update the name. + if (name:find("%S")) then + entity:SetNetVar("name", name) + end + + PLUGIN:CallOnDoorChildren(entity, function(child) + child:SetNetVar("ownable", true) + child:SetNetVar("visible", true) + + if (name:find("%S")) then + child:SetNetVar("name", name) + end + end) + + -- Save the door information. + PLUGIN:SaveDoorData() + return "@dMadeOwnable" + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetFaction", { + description = "@cmdDoorSetFaction", + privilege = "Manage Doors", + adminOnly = true, + arguments = bit.bor(ix.type.text, ix.type.optional), + OnRun = function(self, client, name) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + if (!name or name == "") then + entity.ixFactionID = nil + entity:SetNetVar("faction", nil) + + PLUGIN:CallOnDoorChildren(entity, function() + entity.ixFactionID = nil + entity:SetNetVar("faction", nil) + end) + + PLUGIN:SaveDoorData() + return "@dRemoveFaction" + end + + local faction + + -- Loop through each faction, checking the uniqueID and name. + for k, v in pairs(ix.faction.teams) do + if (ix.util.StringMatches(k, name) or ix.util.StringMatches(L(v.name, client), name)) then + -- This faction matches the provided string. + faction = v + + -- Escape the loop. + break + end + end + + -- Check if a faction was found. + if (faction) then + entity.ixFactionID = faction.uniqueID + entity:SetNetVar("faction", faction.index) + + PLUGIN:CallOnDoorChildren(entity, function() + entity.ixFactionID = faction.uniqueID + entity:SetNetVar("faction", faction.index) + end) + + PLUGIN:SaveDoorData() + return "@dSetFaction", L(faction.name, client) + -- The faction was not found. + else + return "@invalidFaction" + end + end + end +}) + +ix.command.Add("DoorSetDisabled", { + description = "@cmdDoorSetDisabled", + privilege = "Manage Doors", + adminOnly = true, + arguments = ix.type.bool, + OnRun = function(self, client, bDisabled) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor()) then + -- Set it so it is ownable. + entity:SetNetVar("disabled", bDisabled) + + PLUGIN:CallOnDoorChildren(entity, function(child) + child:SetNetVar("disabled", bDisabled) + end) + + PLUGIN:SaveDoorData() + + -- Tell the player they have made the door (un)disabled. + return "@dSet" .. (bDisabled and "" or "Not") .. "Disabled" + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetTitle", { + description = "@cmdDoorSetTitle", + arguments = ix.type.text, + OnRun = function(self, client, name) + -- Get the door infront of the player. + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local trace = util.TraceLine(data) + local entity = trace.Entity + + -- Validate the door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + -- Make sure the name contains actual characters. + if (!name:find("%S")) then + return "@invalidArg", 1 + end + + --[[ + NOTE: Here, we are setting two different networked names. + The title is a temporary name, while the other name is the + default name for the door. The reason for this is so when the + server closes while someone owns the door, it doesn't save THEIR + title, which could lead to unwanted things. + --]] + + name = name:utf8sub(1, 24) + + -- Check if they are allowed to change the door's name. + if (entity:CheckDoorAccess(client, DOOR_TENANT)) then + entity:SetNetVar("title", name) + elseif (CAMI.PlayerHasAccess(client, "Helix - Manage Doors", nil)) then + entity:SetNetVar("name", name) + + PLUGIN:CallOnDoorChildren(entity, function(child) + child:SetNetVar("name", name) + end) + else + -- Otherwise notify the player he/she can't. + return "@notOwner" + end + else + -- Notification of the door not being valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetParent", { + description = "@cmdDoorSetParent", + privilege = "Manage Doors", + adminOnly = true, + OnRun = function(self, client, arguments) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + client.ixDoorParent = entity + return "@dSetParentDoor" + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetChild", { + description = "@cmdDoorSetChild", + privilege = "Manage Doors", + adminOnly = true, + OnRun = function(self, client, arguments) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + if (client.ixDoorParent == entity) then + return "@dCanNotSetAsChild" + end + + -- Check if the player has set a door as a parent. + if (IsValid(client.ixDoorParent)) then + -- Add the door to the parent's list of children. + client.ixDoorParent.ixChildren = client.ixDoorParent.ixChildren or {} + client.ixDoorParent.ixChildren[entity:MapCreationID()] = true + + -- Set the door's parent to the parent. + entity.ixParent = client.ixDoorParent + + -- Save the door information. + PLUGIN:SaveDoorData() + PLUGIN:CopyParentDoor(entity) + + return "@dAddChildDoor" + else + -- Tell the player they do not have a door parent. + return "@dNoParentDoor" + end + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorRemoveChild", { + description = "@cmdDoorRemoveChild", + privilege = "Manage Doors", + adminOnly = true, + OnRun = function(self, client, arguments) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + if (client.ixDoorParent == entity) then + PLUGIN:CallOnDoorChildren(entity, function(child) + child.ixParent = nil + end) + + entity.ixChildren = nil + return "@dRemoveChildren" + end + + -- Check if the player has set a door as a parent. + if (IsValid(entity.ixParent) and entity.ixParent.ixChildren) then + -- Remove the door from the list of children. + entity.ixParent.ixChildren[entity:MapCreationID()] = nil + -- Remove the variable for the parent. + entity.ixParent = nil + + PLUGIN:SaveDoorData() + return "@dRemoveChildDoor" + end + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetHidden", { + description = "@cmdDoorSetHidden", + privilege = "Manage Doors", + adminOnly = true, + arguments = ix.type.bool, + OnRun = function(self, client, bHidden) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor()) then + entity:SetNetVar("visible", !bHidden) + + PLUGIN:CallOnDoorChildren(entity, function(child) + child:SetNetVar("visible", !bHidden) + end) + + PLUGIN:SaveDoorData() + + -- Tell the player they have made the door (un)hidden. + return "@dSet" .. (bHidden and "" or "Not") .. "Hidden" + else + -- Tell the player the door isn't valid. + return "@dNotValid" + end + end +}) + +ix.command.Add("DoorSetClass", { + description = "@cmdDoorSetClass", + privilege = "Manage Doors", + adminOnly = true, + arguments = bit.bor(ix.type.text, ix.type.optional), + OnRun = function(self, client, name) + -- Get the door the player is looking at. + local entity = client:GetEyeTrace().Entity + + -- Validate it is a door. + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then + if (!name or name == "") then + entity:SetNetVar("class", nil) + + PLUGIN:CallOnDoorChildren(entity, function() + entity:SetNetVar("class", nil) + end) + + PLUGIN:SaveDoorData() + return "@dRemoveClass" + end + + local class, classData + + for k, v in pairs(ix.class.list) do + if (ix.util.StringMatches(v.name, name) or ix.util.StringMatches(L(v.name, client), name)) then + class, classData = k, v + + break + end + end + + -- Check if a faction was found. + if (class) then + entity.ixClassID = class + entity:SetNetVar("class", class) + + PLUGIN:CallOnDoorChildren(entity, function() + entity.ixClassID = class + entity:SetNetVar("class", class) + end) + + PLUGIN:SaveDoorData() + return "@dSetClass", L(classData.name, client) + else + return "@invalidClass" + end + end + end +}) diff --git a/garrysmod/gamemodes/helix/plugins/doors/sh_plugin.lua b/garrysmod/gamemodes/helix/plugins/doors/sh_plugin.lua new file mode 100644 index 0000000..0b60be7 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/doors/sh_plugin.lua @@ -0,0 +1,83 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Doors" +PLUGIN.author = "Chessnut" +PLUGIN.description = "A simple door system." + +-- luacheck: globals DOOR_OWNER DOOR_TENANT DOOR_GUEST DOOR_NONE +DOOR_OWNER = 3 +DOOR_TENANT = 2 +DOOR_GUEST = 1 +DOOR_NONE = 0 + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") +ix.util.Include("sh_commands.lua") + +do + local entityMeta = FindMetaTable("Entity") + + function entityMeta:CheckDoorAccess(client, access) + if (!self:IsDoor()) then + return false + end + + access = access or DOOR_GUEST + + local parent = self.ixParent + + if (IsValid(parent)) then + return parent:CheckDoorAccess(client, access) + end + + if (hook.Run("CanPlayerAccessDoor", client, self, access)) then + return true + end + + if (self.ixAccess and (self.ixAccess[client] or 0) >= access) then + return true + end + + return false + end + + if (SERVER) then + function entityMeta:RemoveDoorAccessData() + local receivers = {} + + for k, _ in pairs(self.ixAccess or {}) do + receivers[#receivers + 1] = k + end + + if (#receivers > 0) then + net.Start("ixDoorMenu") + net.WriteEntity(self) + net.WriteTable({}) + net.Send(receivers) + end + + self.ixAccess = {} + self:SetDTEntity(0, nil) + + -- Remove door information on child doors + PLUGIN:CallOnDoorChildren(self, function(child) + child:SetDTEntity(0, nil) + end) + end + end +end + +-- Configurations for door prices. +ix.config.Add("doorCost", 10, "The price to purchase a door.", nil, { + data = {min = 0, max = 500}, + category = "dConfigName" +}) +ix.config.Add("doorSellRatio", 0.5, "How much of the door price is returned when selling a door.", nil, { + data = {min = 0, max = 1.0, decimals = 1}, + category = "dConfigName" +}) +ix.config.Add("doorLockTime", 1, "How long it takes to (un)lock a door.", nil, { + data = {min = 0, max = 10.0, decimals = 1}, + category = "dConfigName" +}) diff --git a/garrysmod/gamemodes/helix/plugins/doors/sv_plugin.lua b/garrysmod/gamemodes/helix/plugins/doors/sv_plugin.lua new file mode 100644 index 0000000..4c193b4 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/doors/sv_plugin.lua @@ -0,0 +1,285 @@ + +util.AddNetworkString("ixDoorMenu") +util.AddNetworkString("ixDoorPermission") + +-- Variables for door data. +local variables = { + -- Whether or not the door will be disabled. + "disabled", + -- The name of the door. + "name", + -- Price of the door. + "price", + -- If the door is ownable. + "ownable", + -- The faction that owns a door. + "faction", + -- The class that owns a door. + "class", + -- Whether or not the door will be hidden. + "visible" +} + +function PLUGIN:CallOnDoorChildren(entity, callback) + local parent + + if (entity.ixChildren) then + parent = entity + elseif (entity.ixParent) then + parent = entity.ixParent + end + + if (IsValid(parent)) then + callback(parent) + + for k, _ in pairs(parent.ixChildren) do + local child = ents.GetMapCreatedEntity(k) + + if (IsValid(child)) then + callback(child) + end + end + end +end + +function PLUGIN:CopyParentDoor(child) + local parent = child.ixParent + + if (IsValid(parent)) then + for _, v in ipairs(variables) do + local value = parent:GetNetVar(v) + + if (child:GetNetVar(v) != value) then + child:SetNetVar(v, value) + end + end + end +end + +-- Called after the entities have loaded. +function PLUGIN:LoadData() + -- Restore the saved door information. + local data = self:GetData() + + if (!data) then + return + end + + -- Loop through all of the saved doors. + for k, v in pairs(data) do + -- Get the door entity from the saved ID. + local entity = ents.GetMapCreatedEntity(k) + + -- Check it is a valid door in-case something went wrong. + if (IsValid(entity) and entity:IsDoor()) then + -- Loop through all of our door variables. + for k2, v2 in pairs(v) do + if (k2 == "children") then + entity.ixChildren = v2 + + for index, _ in pairs(v2) do + local door = ents.GetMapCreatedEntity(index) + + if (IsValid(door)) then + door.ixParent = entity + end + end + elseif (k2 == "faction") then + for k3, v3 in pairs(ix.faction.teams) do + if (k3 == v2) then + entity.ixFactionID = k3 + entity:SetNetVar("faction", v3.index) + + break + end + end + else + entity:SetNetVar(k2, v2) + end + end + end + end +end + +-- Called before the gamemode shuts down. +function PLUGIN:SaveDoorData() + -- Create an empty table to save information in. + local data = {} + local doors = {} + + for _, v in ents.Iterator() do + if (v:IsDoor()) then + doors[v:MapCreationID()] = v + end + end + + local doorData + + -- Loop through doors with information. + for k, v in pairs(doors) do + -- Another empty table for actual information regarding the door. + doorData = {} + + -- Save all of the needed variables to the doorData table. + for _, v2 in ipairs(variables) do + local value = v:GetNetVar(v2) + + if (value) then + doorData[v2] = v:GetNetVar(v2) + end + end + + if (v.ixChildren) then + doorData.children = v.ixChildren + end + + if (v.ixClassID) then + doorData.class = v.ixClassID + end + + if (v.ixFactionID) then + doorData.faction = v.ixFactionID + end + + -- Add the door to the door information. + if (!table.IsEmpty(doorData)) then + data[k] = doorData + end + end + -- Save all of the door information. + self:SetData(data) +end + +function PLUGIN:CanPlayerUseDoor(client, entity) + if (entity:GetNetVar("disabled")) then + return false + end +end + +-- Whether or not a player a player has any abilities over the door, such as locking. +function PLUGIN:CanPlayerAccessDoor(client, door, access) + local faction = door:GetNetVar("faction") + + -- If the door has a faction set which the client is a member of, allow access. + if (faction and client:Team() == faction) then + return true + end + + local class = door:GetNetVar("class") + + -- If the door has a faction set which the client is a member of, allow access. + local classData = ix.class.list[class] + local charClass = client:GetCharacter():GetClass() + local classData2 = ix.class.list[charClass] + + if (class and classData and classData2) then + if (classData.team) then + if (classData.team != classData2.team) then + return false + end + else + if (charClass != class) then + return false + end + end + + return true + end +end + +function PLUGIN:PostPlayerLoadout(client) + client:Give("ix_keys") +end + +function PLUGIN:ShowTeam(client) + local data = {} + data.start = client:GetShootPos() + data.endpos = data.start + client:GetAimVector() * 96 + data.filter = client + local trace = util.TraceLine(data) + local entity = trace.Entity + + if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("faction") and !entity:GetNetVar("class")) then + if (entity:CheckDoorAccess(client, DOOR_TENANT)) then + local door = entity + + if (IsValid(door.ixParent)) then + door = door.ixParent + end + + net.Start("ixDoorMenu") + net.WriteEntity(door) + net.WriteTable(door.ixAccess) + net.WriteEntity(entity) + net.Send(client) + elseif (!IsValid(entity:GetDTEntity(0))) then + ix.command.Run(client, "doorbuy") + else + client:NotifyLocalized("notAllowed") + end + + return true + end +end + +function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar) + if (prevChar) then + local doors = prevChar:GetVar("doors") or {} + + for _, v in ipairs(doors) do + if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then + v:RemoveDoorAccessData() + end + end + + prevChar:SetVar("doors", nil) + end +end + +function PLUGIN:PlayerDisconnected(client) + local character = client:GetCharacter() + + if (character) then + local doors = character:GetVar("doors") or {} + + for _, v in ipairs(doors) do + if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then + v:RemoveDoorAccessData() + end + end + + character:SetVar("doors", nil) + end +end + +net.Receive("ixDoorPermission", function(length, client) + local door = net.ReadEntity() + local target = net.ReadEntity() + local access = net.ReadUInt(4) + + if (IsValid(target) and target:GetCharacter() and door.ixAccess and door:GetDTEntity(0) == client and target != client) then + access = math.Clamp(access or 0, DOOR_NONE, DOOR_TENANT) + + if (access == door.ixAccess[target]) then + return + end + + door.ixAccess[target] = access + + local recipient = {} + + for k, v in pairs(door.ixAccess) do + if (v > DOOR_GUEST) then + recipient[#recipient + 1] = k + end + end + + if (#recipient > 0) then + net.Start("ixDoorPermission") + net.WriteEntity(door) + net.WriteEntity(target) + net.WriteUInt(access, 4) + net.Send(recipient) + end + end +end) diff --git a/garrysmod/gamemodes/helix/plugins/logging.lua b/garrysmod/gamemodes/helix/plugins/logging.lua new file mode 100644 index 0000000..6132b54 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/logging.lua @@ -0,0 +1,228 @@ + +PLUGIN.name = "Logging" +PLUGIN.author = "Black Tea" +PLUGIN.description = "You can modfiy the logging text/lists on this plugin." + +if (SERVER) then + local L = Format + + ix.log.AddType("chat", function(client, ...) + local arg = {...} + return L("[%s] %s: %s", arg[1], client:Name(), arg[2]) + end) + + ix.log.AddType("command", function(client, ...) + local arg = {...} + + if (arg[2] and #arg[2] > 0) then + return L("%s used command '%s %s'.", client:Name(), arg[1], arg[2]) + else + return L("%s used command '%s'.", client:Name(), arg[1]) + end + end) + + ix.log.AddType("cfgSet", function(client, ...) + local arg = {...} + return L("%s set %s to '%s'.", client:Name(), arg[1], arg[2]) + end, FLAG_DANGER) + + ix.log.AddType("connect", function(client, ...) + return L("%s has connected.", client:SteamName()) + end, FLAG_NORMAL) + + ix.log.AddType("disconnect", function(client, ...) + if (client:IsTimingOut()) then + return L("%s (%s) has disconnected (timed out).", client:SteamName(), client:SteamID()) + else + return L("%s (%s) has disconnected.", client:SteamName(), client:SteamID()) + end + end, FLAG_NORMAL) + + ix.log.AddType("charCreate", function(client, ...) + local arg = {...} + return L("%s created the character '%s'", client:SteamName(), arg[1]) + end, FLAG_SERVER) + + ix.log.AddType("charLoad", function(client, ...) + local arg = {...} + return L("%s loaded the character '%s'", client:SteamName(), arg[1]) + end, FLAG_SERVER) + + ix.log.AddType("charDelete", function(client, ...) + local arg = {...} + return L("%s (%s) deleted character '%s'", client:SteamName(), client:SteamID(), arg[1]) + end, FLAG_SERVER) + + ix.log.AddType("itemAction", function(client, ...) + local arg = {...} + local item = arg[2] + return L("%s ran '%s' on item '%s' (#%s)", client:Name(), arg[1], item:GetName(), item:GetID()) + end, FLAG_NORMAL) + + ix.log.AddType("itemDestroy", function(client, itemName, itemID) + local name = client:GetName() ~= "" and client:GetName() or client:GetClass() + return L("%s destroyed a '%s' #%d.", name, itemName, itemID) + end, FLAG_WARNING) + + ix.log.AddType("shipmentTake", function(client, ...) + local arg = {...} + return L("%s took '%s' from the shipment", client:Name(), arg[1]) + end, FLAG_WARNING) + + ix.log.AddType("shipmentOrder", function(client, ...) + return L("%s ordered a shipment", client:Name()) + end, FLAG_SUCCESS) + + ix.log.AddType("buy", function(client, ...) + local arg = {...} + return L("%s purchased '%s' from the NPC", client:Name(), arg[1]) + end, FLAG_SUCCESS) + + ix.log.AddType("buydoor", function(client, ...) + return L("%s has purchased a door.", client:Name()) + end, FLAG_SUCCESS) + + ix.log.AddType("selldoor", function(client, ...) + return L("%s has sold a door.", client:Name()) + end, FLAG_SUCCESS) + + ix.log.AddType("playerHurt", function(client, ...) + local arg = {...} + return L("%s has taken %d damage from %s.", client:Name(), arg[1], arg[2]) + end, FLAG_WARNING) + + ix.log.AddType("playerDeath", function(client, ...) + local arg = {...} + return L("%s has killed %s%s.", arg[1], client:Name(), arg[2] and (" with " .. arg[2]) or "") + end, FLAG_DANGER) + + ix.log.AddType("money", function(client, amount) + return L("%s has %s %s.", client:Name(), amount < 0 and "lost" or "gained", ix.currency.Get(math.abs(amount))) + end, FLAG_SUCCESS) + + ix.log.AddType("inventoryAdd", function(client, characterName, itemName, itemID) + return L("%s has gained a '%s' #%d.", characterName, itemName, itemID) + end, FLAG_WARNING) + + ix.log.AddType("inventoryRemove", function(client, characterName, itemName, itemID) + return L("%s has lost a '%s' #%d.", characterName, itemName, itemID) + end, FLAG_WARNING) + + ix.log.AddType("storageMoneyTake", function(client, entity, amount, total) + local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName() + + return string.format("%s has taken %d %s from '%s' #%d (%d %s left).", + client:GetName(), amount, ix.currency.plural, name, + entity:GetInventory():GetID(), total, ix.currency.plural) + end) + + ix.log.AddType("storageMoneyGive", function(client, entity, amount, total) + local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName() + + return string.format("%s has given %d %s to '%s' #%d (%d %s left).", + client:GetName(), amount, ix.currency.plural, name, + entity:GetInventory():GetID(), total, ix.currency.plural) + end) + + ix.log.AddType("roll", function(client, value, max) + return string.format("%s rolled %d out of %d.", client:Name(), value, max) + end) + + ix.log.AddType("pluginLoaded", function(client, uniqueID) + return string.format("%s has enabled the %s plugin for next restart.", client:GetName(), uniqueID) + end) + + ix.log.AddType("pluginUnloaded", function(client, uniqueID) + return string.format("%s has disabled the %s plugin for next restart.", client:GetName(), uniqueID) + end) + + function PLUGIN:PlayerInitialSpawn(client) + ix.log.Add(client, "connect") + end + + function PLUGIN:PlayerDisconnected(client) + ix.log.Add(client, "disconnect") + end + + function PLUGIN:OnCharacterCreated(client, character) + ix.log.Add(client, "charCreate", character:GetName()) + end + + function PLUGIN:CharacterLoaded(character) + local client = character:GetPlayer() + ix.log.Add(client, "charLoad", character:GetName()) + end + + function PLUGIN:PreCharacterDeleted(client, character) + ix.log.Add(client, "charDelete", character:GetName()) + end + + function PLUGIN:ShipmentItemTaken(client, itemClass, amount) + local itemTable = ix.item.list[itemClass] + ix.log.Add(client, "shipmentTake", itemTable:GetName()) + end + + function PLUGIN:CreateShipment(client, shipmentEntity) + ix.log.Add(client, "shipmentOrder") + end + + function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell) + end + + function PLUGIN:PlayerInteractItem(client, action, item) + if (isentity(item)) then + if (IsValid(item)) then + local itemID = item.ixItemID + item = ix.item.instances[itemID] + else + return + end + elseif (isnumber(item)) then + item = ix.item.instances[item] + end + + if (!item) then + return + end + + ix.log.Add(client, "itemAction", action, item) + end + + function PLUGIN:InventoryItemAdded(oldInv, inventory, item) + if (!inventory.owner or (oldInv and oldInv.owner == inventory.owner)) then + return + end + + local character = ix.char.loaded[inventory.owner] + + ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), item:GetName(), item:GetID()) + + if (item.isBag) then + local bagInventory = item:GetInventory() + + if (!bagInventory) then + return + end + + for k, _ in bagInventory:Iter() do + ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), k:GetName(), k:GetID()) + end + end + end + + function PLUGIN:InventoryItemRemoved(inventory, item) + if (!inventory.owner) then + return + end + + local character = ix.char.loaded[inventory.owner] + + ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), item:GetName(), item:GetID()) + + if (item.isBag) then + for k, _ in item:GetInventory():Iter() do + ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), k:GetName(), k:GetID()) + end + end + end +end diff --git a/garrysmod/gamemodes/helix/plugins/mapscene.lua b/garrysmod/gamemodes/helix/plugins/mapscene.lua new file mode 100644 index 0000000..13c2ef2 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/mapscene.lua @@ -0,0 +1,277 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Map Scenes" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Adds areas of the map that are visible during character selection." +PLUGIN.scenes = PLUGIN.scenes or {} + +local x3, y3 = 0, 0 +local realOrigin = Vector(0, 0, 0) +local realAngles = Angle(0, 0, 0) +local view = {} + +if (CLIENT) then + PLUGIN.ordered = PLUGIN.ordered or {} + + function PLUGIN:CalcView(client, origin, angles, fov) + local scenes = self.scenes + + if (IsValid(ix.gui.characterMenu) and !IsValid(ix.gui.menu) and !ix.gui.characterMenu:IsClosing() and + !table.IsEmpty(scenes)) then + local key = self.index + local value = scenes[self.index] + + if (!self.index or !value) then + value, key = table.Random(scenes) + self.index = key + end + + if (self.orderedIndex or value.origin or isvector(key)) then + local curTime = CurTime() + + self.orderedIndex = self.orderedIndex or 1 + + local ordered = self.ordered[self.orderedIndex] + + if (ordered) then + key = ordered[1] + value = ordered[2] + end + + if (!self.startTime) then + self.startTime = curTime + self.finishTime = curTime + 30 + end + + local fraction = math.min(math.TimeFraction(self.startTime, self.finishTime, CurTime()), 1) + + if (value) then + realOrigin = LerpVector(fraction, key, value[1]) + realAngles = LerpAngle(fraction, value[2], value[3]) + end + + if (fraction >= 1) then + self.startTime = curTime + self.finishTime = curTime + 30 + + if (ordered) then + self.orderedIndex = self.orderedIndex + 1 + + if (self.orderedIndex > #self.ordered) then + self.orderedIndex = 1 + end + else + local keys = {} + + for k, _ in pairs(scenes) do + if (isvector(k)) then + keys[#keys + 1] = k + end + end + + self.index = table.Random(keys) + end + end + elseif (value) then + realOrigin = value[1] + realAngles = value[2] + end + + local x, y = gui.MousePos() + local x2, y2 = surface.ScreenWidth() * 0.5, surface.ScreenHeight() * 0.5 + local frameTime = FrameTime() * 0.5 + + y3 = Lerp(frameTime, y3, math.Clamp((y - y2) / y2, -1, 1) * -6) + x3 = Lerp(frameTime, x3, math.Clamp((x - x2) / x2, -1, 1) * 6) + + view.origin = realOrigin + realAngles:Up()*y3 + realAngles:Right()*x3 + view.angles = realAngles + Angle(y3 * -0.5, x3 * -0.5, 0) + + return view + end + end + + function PLUGIN:PreDrawViewModel(viewModel, client, weapon) + if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) then + return true + end + end + + net.Receive("ixMapSceneAdd", function() + local data = net.ReadTable() + + PLUGIN.scenes[#PLUGIN.scenes + 1] = data + end) + + net.Receive("ixMapSceneRemove", function() + local index = net.ReadUInt(16) + + PLUGIN.scenes[index] = nil + end) + + net.Receive("ixMapSceneAddPair", function() + local data = net.ReadTable() + local origin = net.ReadVector() + + PLUGIN.scenes[origin] = data + + table.insert(PLUGIN.ordered, {origin, data}) + end) + + net.Receive("ixMapSceneRemovePair", function() + local key = net.ReadVector() + + PLUGIN.scenes[key] = nil + + for k, v in ipairs(PLUGIN.ordered) do + if (v[1] == key) then + table.remove(PLUGIN.ordered, k) + + break + end + end + end) + + net.Receive("ixMapSceneSync", function() + local length = net.ReadUInt(32) + local data = net.ReadData(length) + local uncompressed = util.Decompress(data) + + if (!uncompressed) then + ErrorNoHalt("[Helix] Unable to decompress map scene data!\n") + return + end + + -- Set the list of texts to the ones provided by the server. + PLUGIN.scenes = util.JSONToTable(uncompressed) + + for k, v in pairs(PLUGIN.scenes) do + if (v.origin or isvector(k)) then + table.insert(PLUGIN.ordered, {v.origin and v.origin or k, v}) + end + end + end) +else + util.AddNetworkString("ixMapSceneSync") + util.AddNetworkString("ixMapSceneAdd") + util.AddNetworkString("ixMapSceneRemove") + + util.AddNetworkString("ixMapSceneAddPair") + util.AddNetworkString("ixMapSceneRemovePair") + + function PLUGIN:SaveScenes() + self:SetData(self.scenes) + end + + function PLUGIN:LoadData() + self.scenes = self:GetData() or {} + end + + function PLUGIN:PlayerInitialSpawn(client) + local json = util.TableToJSON(self.scenes) + local compressed = util.Compress(json) + local length = compressed:len() + + net.Start("ixMapSceneSync") + net.WriteUInt(length, 32) + net.WriteData(compressed, length) + net.Send(client) + end + + function PLUGIN:AddScene(position, angles, position2, angles2) + local data + + if (position2) then + data = {origin=position, position2, angles, angles2} + self.scenes[#self.scenes + 1] = data + + net.Start("ixMapSceneAddPair") + net.WriteTable(data) + net.WriteVector(position) + net.Broadcast() + else + data = {position, angles} + self.scenes[#self.scenes + 1] = data + + net.Start("ixMapSceneAdd") + net.WriteTable(data) + net.Broadcast() + end + + self:SaveScenes() + end +end + +ix.command.Add("MapSceneAdd", { + description = "@cmdMapSceneAdd", + privilege = "Manage Map Scenes", + adminOnly = true, + arguments = bit.bor(ix.type.bool, ix.type.optional), + OnRun = function(self, client, bIsPair) + local position, angles = client:EyePos(), client:EyeAngles() + + -- This scene is in a pair for moving scenes. + if (tobool(bIsPair) and !client.ixScnPair) then + client.ixScnPair = {position, angles} + + return "@mapRepeat" + else + if (client.ixScnPair) then + PLUGIN:AddScene(client.ixScnPair[1], client.ixScnPair[2], position, angles) + client.ixScnPair = nil + else + PLUGIN:AddScene(position, angles) + end + + return "@mapAdd" + end + end +}) + +ix.command.Add("MapSceneRemove", { + description = "@cmdMapSceneRemove", + privilege = "Manage Map Scenes", + adminOnly = true, + arguments = bit.bor(ix.type.number, ix.type.optional), + OnRun = function(self, client, radius) + radius = radius or 280 + + local position = client:GetPos() + local i = 0 + + for k, v in pairs(PLUGIN.scenes) do + local delete = false + + if (isvector(k)) then + if (k:Distance(position) <= radius or v[1]:Distance(position) <= radius) then + delete = true + end + elseif (v[1]:Distance(position) <= radius) then + delete = true + end + + if (delete) then + if (isvector(k)) then + net.Start("ixMapSceneRemovePair") + net.WriteVector(k) + net.Broadcast() + else + net.Start("ixMapSceneRemove") + net.WriteString(k) + net.Broadcast() + end + + PLUGIN.scenes[k] = nil + + i = i + 1 + end + end + + if (i > 0) then + PLUGIN:SaveScenes() + end + + return "@mapDel", i + end +}) diff --git a/garrysmod/gamemodes/helix/plugins/pac.lua b/garrysmod/gamemodes/helix/plugins/pac.lua new file mode 100644 index 0000000..30a08d1 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/pac.lua @@ -0,0 +1,357 @@ + +-- luacheck: globals pac pace + +-- This Library is just for PAC3 Integration. +-- You must install PAC3 to make this library work. + +PLUGIN.name = "PAC3 Integration" +PLUGIN.author = "Black Tea" +PLUGIN.description = "PAC3 integration for item parts." + +if (!pace) then return end + +ix.pac = ix.pac or {} +ix.pac.list = ix.pac.list or {} + +CAMI.RegisterPrivilege({ + Name = "Helix - Manage PAC", + MinAccess = "superadmin" +}) + +-- this stores pac3 part information to plugin's table' +function ix.pac.RegisterPart(id, outfit) + ix.pac.list[id] = outfit +end + +-- Fixing the PAC3's default stuffs to fit on Helix. +if (CLIENT) then + -- Disable the "in editor" HUD element. + hook.Add("InitializedPlugins", "PAC3Fixer", function() + hook.Remove("HUDPaint", "pac_in_editor") + end) + + -- Remove PAC3 LoadParts + function pace.LoadParts(name, clear, override_part) end + + -- Prohibits players from deleting their own PAC3 outfit. + concommand.Add("pac_clear_parts", function() + RunConsoleCommand("pac_restart") + end) + + -- you need the proper permission to open the editor + function PLUGIN:PrePACEditorOpen() + if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage PAC", nil)) then + return false + end + end +end + +function PLUGIN:pac_CanWearParts(client) + if (!CAMI.PlayerHasAccess(client, "Helix - Manage PAC", nil)) then + return false + end +end + +local meta = FindMetaTable("Player") + +-- Get Player's PAC3 Parts. +function meta:GetParts() + if (!pac) then return end + + return self:GetNetVar("parts", {}) +end + +if (SERVER) then + util.AddNetworkString("ixPartWear") + util.AddNetworkString("ixPartRemove") + util.AddNetworkString("ixPartReset") + + function meta:AddPart(uniqueID, item) + if (!pac) then return end + + local curParts = self:GetParts() + + -- wear the parts. + net.Start("ixPartWear") + net.WriteEntity(self) + net.WriteString(uniqueID) + net.Broadcast() + + curParts[uniqueID] = true + + self:SetNetVar("parts", curParts) + end + + function meta:RemovePart(uniqueID) + if (!pac) then return end + + local curParts = self:GetParts() + + -- remove the parts. + net.Start("ixPartRemove") + net.WriteEntity(self) + net.WriteString(uniqueID) + net.Broadcast() + + curParts[uniqueID] = nil + + self:SetNetVar("parts", curParts) + end + + function meta:ResetParts() + if (!pac) then return end + + net.Start("ixPartReset") + net.WriteEntity(self) + net.WriteTable(self:GetParts()) + net.Broadcast() + + self:SetNetVar("parts", {}) + end + + function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar) + -- Reset the characters parts. + local curParts = client:GetParts() + + if (curParts) then + client:ResetParts() + end + + -- After resetting all PAC3 outfits, wear all equipped PAC3 outfits. + if (curChar) then + local inv = curChar:GetInventory() + + for k, _ in inv:Iter() do + if (k:GetData("equip") == true and k.pacData) then + client:AddPart(k.uniqueID, k) + end + end + end + end + + function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon) + local oldItem = IsValid(oldWeapon) and oldWeapon.ixItem + local newItem = IsValid(newWeapon) and newWeapon.ixItem + + if (oldItem and oldItem.isWeapon and oldItem:GetData("equip") and oldItem.pacData) then + oldItem:WearPAC(client) + end + + if (newItem and newItem.isWeapon and newItem.pacData) then + newItem:RemovePAC(client) + end + end + + -- Hides PAC parts when a player enters observer. + function PLUGIN:OnPlayerObserve(client, state) + local curParts = client:GetParts() + + -- Remove all the parts + if (curParts) then + client:ResetParts() + end + + -- If exiting of observer, re-add all parts. + if (!state) then + local character = client:GetCharacter() + local inventory = character:GetInventory() + + for k, _ in inventory:Iter() do + if (k:GetData("equip") == true and k.pacData) then + client:AddPart(k.uniqueID, k) + end + end + end + end +else + local function AttachPart(client, uniqueID) + local itemTable = ix.item.list[uniqueID] + local pacData = ix.pac.list[uniqueID] + + if (pacData) then + if (itemTable and itemTable.pacAdjust) then + pacData = table.Copy(pacData) + pacData = itemTable:pacAdjust(pacData, client) + end + + if (isfunction(client.AttachPACPart)) then + client:AttachPACPart(pacData) + else + pac.SetupENT(client) + + timer.Simple(0.1, function() + if (IsValid(client) and isfunction(client.AttachPACPart)) then + client:AttachPACPart(pacData) + end + end) + end + end + end + + local function RemovePart(client, uniqueID) + local itemTable = ix.item.list[uniqueID] + local pacData = ix.pac.list[uniqueID] + + if (pacData) then + if (itemTable and itemTable.pacAdjust) then + pacData = table.Copy(pacData) + pacData = itemTable:pacAdjust(pacData, client) + end + + if (isfunction(client.RemovePACPart)) then + client:RemovePACPart(pacData) + else + pac.SetupENT(client) + end + end + end + + hook.Add("Think", "ix_pacupdate", function() + if (!pac) then + hook.Remove("Think", "ix_pacupdate") + return + end + + if (IsValid(pac.LocalPlayer)) then + for _, v in player.Iterator() do + local character = v:GetCharacter() + + if (character) then + local parts = v:GetParts() + + for k2, _ in pairs(parts) do + AttachPart(v, k2) + end + end + end + + hook.Remove("Think", "ix_pacupdate") + end + end) + + net.Receive("ixPartWear", function(length) + if (!pac) then return end + + local wearer = net.ReadEntity() + local uid = net.ReadString() + + if (!wearer.pac_owner) then + pac.SetupENT(wearer) + end + + AttachPart(wearer, uid) + end) + + net.Receive("ixPartRemove", function(length) + if (!pac) then return end + + local wearer = net.ReadEntity() + local uid = net.ReadString() + + if (!wearer.pac_owner) then + pac.SetupENT(wearer) + end + + RemovePart(wearer, uid) + end) + + net.Receive("ixPartReset", function(length) + if (!pac) then return end + + local wearer = net.ReadEntity() + local uidList = net.ReadTable() + + if (!wearer.pac_owner) then + pac.SetupENT(wearer) + end + + for k, _ in pairs(uidList) do + RemovePart(wearer, k) + end + end) + + function PLUGIN:DrawPlayerRagdoll(entity) + local ply = entity.objCache + + if (IsValid(ply)) then + if (!entity.overridePAC3) then + if ply.pac_parts then + for _, part in pairs(ply.pac_parts) do + if part.last_owner and part.last_owner:IsValid() then + hook.Run("OnPAC3PartTransferred", part) + part:SetOwner(entity) + part.last_owner = entity + end + end + end + ply.pac_playerspawn = pac.RealTime -- used for events + + entity.overridePAC3 = true + end + end + end + + function PLUGIN:OnEntityCreated(entity) + local class = entity:GetClass() + + -- For safe progress, I skip one frame. + timer.Simple(0.01, function() + if (class == "prop_ragdoll") then + if (entity:GetNetVar("player")) then + entity.RenderOverride = function() + entity.objCache = entity:GetNetVar("player") + entity:DrawModel() + + hook.Run("DrawPlayerRagdoll", entity) + end + end + end + + if (class:find("HL2MPRagdoll")) then + for _, v in player.Iterator() do + if (v:GetRagdollEntity() == entity) then + entity.objCache = v + end + end + + entity.RenderOverride = function() + entity:DrawModel() + + hook.Run("DrawPlayerRagdoll", entity) + end + end + end) + end + + function PLUGIN:DrawCharacterOverview() + if (!pac) then + return + end + + if (LocalPlayer().pac_outfits) then + pac.RenderOverride(LocalPlayer(), "opaque") + pac.RenderOverride(LocalPlayer(), "translucent", true) + end + end + + function PLUGIN:DrawHelixModelView(panel, ent) + if (!pac) then + return + end + + if (LocalPlayer():GetCharacter()) then + pac.RenderOverride(ent, "opaque") + pac.RenderOverride(ent, "translucent", true) + end + end +end + +function PLUGIN:InitializedPlugins() + local items = ix.item.list + + for _, v in pairs(items) do + if (v.pacData) then + ix.pac.list[v.uniqueID] = v.pacData + end + end +end diff --git a/garrysmod/gamemodes/helix/plugins/permakill.lua b/garrysmod/gamemodes/helix/plugins/permakill.lua new file mode 100644 index 0000000..9eb44a5 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/permakill.lua @@ -0,0 +1,37 @@ + +PLUGIN.name = "Permakill" +PLUGIN.author = "Thadah Denyse" +PLUGIN.description = "Adds permanent death in the server options." + +ix.config.Add("permakill", false, "Whether or not permakill is activated on the server.", nil, { + category = "Permakill" +}) + +ix.config.Add("permakillWorld", false, "Whether or not world and self damage produce permanent death.", nil, { + category = "Permakill" +}) + +function PLUGIN:PlayerDeath(client, inflictor, attacker) + local character = client:GetCharacter() + + if (ix.config.Get("permakill") and character) then + if (hook.Run("ShouldPermakillCharacter", client, character, inflictor, attacker) == false) then + return + end + + if (ix.config.Get("permakillWorld") and (client == attacker or inflictor:IsWorld())) then + return + end + + character:SetData("permakilled", true) + end +end + +function PLUGIN:PlayerSpawn(client) + local character = client:GetCharacter() + + if (ix.config.Get("permakill") and character and character:GetData("permakilled")) then + character:Ban() + character:SetData("permakilled") + end +end diff --git a/garrysmod/gamemodes/helix/plugins/persistence.lua b/garrysmod/gamemodes/helix/plugins/persistence.lua new file mode 100644 index 0000000..2daa27a --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/persistence.lua @@ -0,0 +1,195 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Persistence" +PLUGIN.description = "Define entities to persist through restarts." +PLUGIN.author = "alexgrist" +PLUGIN.stored = PLUGIN.stored or {} + +local function GetRealModel(entity) + return entity:GetClass() == "prop_effect" and entity.AttachedEntity:GetModel() or entity:GetModel() +end + +properties.Add("persist", { + MenuLabel = "#makepersistent", + Order = 400, + MenuIcon = "icon16/link.png", + + Filter = function(self, entity, client) + if (entity:IsPlayer() or entity:IsVehicle() or entity.bNoPersist) then return false end + if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end + + return !entity:GetNetVar("Persistent", false) + end, + + Action = function(self, entity) + self:MsgStart() + net.WriteEntity(entity) + self:MsgEnd() + end, + + Receive = function(self, length, client) + local entity = net.ReadEntity() + + if (!IsValid(entity)) then return end + if (!self:Filter(entity, client)) then return end + + PLUGIN.stored[#PLUGIN.stored + 1] = entity + + entity:SetNetVar("Persistent", true) + + ix.log.Add(client, "persist", GetRealModel(entity), true) + end +}) + +properties.Add("persist_end", { + MenuLabel = "#stoppersisting", + Order = 400, + MenuIcon = "icon16/link_break.png", + + Filter = function(self, entity, client) + if (entity:IsPlayer()) then return false end + if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end + + return entity:GetNetVar("Persistent", false) + end, + + Action = function(self, entity) + self:MsgStart() + net.WriteEntity(entity) + self:MsgEnd() + end, + + Receive = function(self, length, client) + local entity = net.ReadEntity() + + if (!IsValid(entity)) then return end + if (!self:Filter(entity, client)) then return end + + for k, v in ipairs(PLUGIN.stored) do + if (v == entity) then + table.remove(PLUGIN.stored, k) + + break + end + end + + entity:SetNetVar("Persistent", false) + + ix.log.Add(client, "persist", GetRealModel(entity), false) + end +}) + +function PLUGIN:PhysgunPickup(client, entity) + if (entity:GetNetVar("Persistent", false)) then + return false + end +end + +if (SERVER) then + function PLUGIN:LoadData() + local entities = self:GetData() or {} + + for _, v in ipairs(entities) do + local entity = ents.Create(v.Class) + + if (IsValid(entity)) then + entity:SetPos(v.Pos) + entity:SetAngles(v.Angle) + entity:SetModel(v.Model) + entity:SetSkin(v.Skin) + entity:SetColor(v.Color) + entity:SetMaterial(v.Material) + entity:Spawn() + entity:Activate() + + if (v.bNoCollision) then + entity:SetCollisionGroup(COLLISION_GROUP_WORLD) + end + + if (istable(v.BodyGroups)) then + for k2, v2 in pairs(v.BodyGroups) do + entity:SetBodygroup(k2, v2) + end + end + + if (istable(v.SubMaterial)) then + for k2, v2 in pairs(v.SubMaterial) do + if (!isnumber(k2) or !isstring(v2)) then + continue + end + + entity:SetSubMaterial(k2 - 1, v2) + end + end + + local physicsObject = entity:GetPhysicsObject() + + if (IsValid(physicsObject)) then + physicsObject:EnableMotion(v.Movable) + end + + self.stored[#self.stored + 1] = entity + + entity:SetNetVar("Persistent", true) + end + end + end + + function PLUGIN:SaveData() + local entities = {} + + for _, v in ipairs(self.stored) do + if (IsValid(v)) then + local data = {} + data.Class = v.ClassOverride or v:GetClass() + data.Pos = v:GetPos() + data.Angle = v:GetAngles() + data.Model = GetRealModel(v) + data.Skin = v:GetSkin() + data.Color = v:GetColor() + data.Material = v:GetMaterial() + data.bNoCollision = v:GetCollisionGroup() == COLLISION_GROUP_WORLD + + local materials = v:GetMaterials() + + if (istable(materials)) then + data.SubMaterial = {} + + for k2, _ in pairs(materials) do + if (v:GetSubMaterial(k2 - 1) != "") then + data.SubMaterial[k2] = v:GetSubMaterial(k2 - 1) + end + end + end + + local bodyGroups = v:GetBodyGroups() + + if (istable(bodyGroups)) then + data.BodyGroups = {} + + for _, v2 in pairs(bodyGroups) do + if (v:GetBodygroup(v2.id) > 0) then + data.BodyGroups[v2.id] = v:GetBodygroup(v2.id) + end + end + end + + local physicsObject = v:GetPhysicsObject() + + if (IsValid(physicsObject)) then + data.Movable = physicsObject:IsMoveable() + end + + entities[#entities + 1] = data + end + end + + self:SetData(entities) + end + + ix.log.AddType("persist", function(client, ...) + local arg = {...} + return string.format("%s has %s persistence for '%s'.", client:Name(), arg[2] and "enabled" or "disabled", arg[1]) + end) +end diff --git a/garrysmod/gamemodes/helix/plugins/propprotect.lua b/garrysmod/gamemodes/helix/plugins/propprotect.lua new file mode 100644 index 0000000..1d4786b --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/propprotect.lua @@ -0,0 +1,157 @@ + +PLUGIN.name = "Basic Prop Protection" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Adds a simple prop protection system." + +CAMI.RegisterPrivilege({ + Name = "Helix - Bypass Prop Protection", + MinAccess = "admin" +}) + +local PROP_BLACKLIST = { + ["models/props_combine/combinetrain02b.mdl"] = true, + ["models/props_combine/combinetrain02a.mdl"] = true, + ["models/props_combine/combinetrain01.mdl"] = true, + ["models/cranes/crane_frame.mdl"] = true, + ["models/props_junk/trashdumpster02.mdl"] = true, + ["models/props_c17/oildrum001_explosive.mdl"] = true, + ["models/props_canal/canal_bridge02.mdl"] = true, + ["models/props_canal/canal_bridge01.mdl"] = true, + ["models/props_canal/canal_bridge03a.mdl"] = true, + ["models/props_canal/canal_bridge03b.mdl"] = true, + ["models/props_wasteland/cargo_container01.mdl"] = true, + ["models/props_wasteland/cargo_container01c.mdl"] = true, + ["models/props_wasteland/cargo_container01b.mdl"] = true, + ["models/props_combine/combine_mine01.mdl"] = true, + ["models/props_junk/glassjug01.mdl"] = true, + ["models/props_c17/paper01.mdl"] = true, + ["models/props_junk/garbage_takeoutcarton001a.mdl"] = true, + ["models/props_c17/trappropeller_engine.mdl"] = true, + ["models/props/cs_office/microwave.mdl"] = true, + ["models/items/item_item_crate.mdl"] = true, + ["models/props_junk/gascan001a.mdl"] = true, + ["models/props_c17/consolebox01a.mdl"] = true, + ["models/props_buildings/building_002a.mdl"] = true, + ["models/props_phx/mk-82.mdl"] = true, + ["models/props_phx/cannonball.mdl"] = true, + ["models/props_phx/ball.mdl"] = true, + ["models/props_phx/amraam.mdl"] = true, + ["models/props_phx/misc/flakshell_big.mdl"] = true, + ["models/props_phx/ww2bomb.mdl"] = true, + ["models/props_phx/torpedo.mdl"] = true, + ["models/props/de_train/biohazardtank.mdl"] = true, + ["models/props_buildings/project_building01.mdl"] = true, + ["models/props_combine/prison01c.mdl"] = true, + ["models/props/cs_militia/silo_01.mdl"] = true, + ["models/props_phx/huge/evildisc_corp.mdl"] = true, + ["models/props_phx/misc/potato_launcher_explosive.mdl"] = true, + ["models/props_combine/combine_citadel001.mdl"] = true, + ["models/props_phx/oildrum001_explosive.mdl"] = true, + ["models/props_junk/wood_crate01_explosive.mdl"] = true, + ["models/props_junk/propane_tank001a.mdl"] = true, + ["models/props_explosive/explosive_butane_can.mdl"] = true, + ["models/props_explosive/explosive_butane_can02.mdl"] = true +} + +if (SERVER) then + ix.log.AddType("spawnProp", function(client, ...) + local arg = {...} + return string.format("%s has spawned '%s'.", client:Name(), arg[1]) + end) + + ix.log.AddType("spawnEntity", function(client, ...) + local arg = {...} + return string.format("%s has spawned a '%s'.", client:Name(), arg[1]) + end) + + function PLUGIN:PlayerSpawnObject(client, model, entity) + if ((client.ixNextSpawn or 0) < CurTime()) then + client.ixNextSpawn = CurTime() + 0.75 + else + return false + end + + if (!client:IsAdmin() and PROP_BLACKLIST[model:lower()]) then + return false + end + end + + function PLUGIN:PhysgunPickup(client, entity) + local characterID = client:GetCharacter():GetID() + + if (entity:GetNetVar("owner", 0) != characterID + and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then + return false + end + end + + function PLUGIN:OnPhysgunReload(weapon, client) + local characterID = client:GetCharacter():GetID() + local trace = client:GetEyeTrace() + + if (IsValid(trace.Entity) and trace.Entity:GetNetVar("owner", 0) != characterID + and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then + return false + end + end + + function PLUGIN:CanProperty(client, property, entity) + local characterID = client:GetCharacter():GetID() + + if (entity:GetNetVar("owner", 0) != characterID + and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then + return false + end + end + + function PLUGIN:CanTool(client, trace, tool) + local entity = trace.Entity + local characterID = client:GetCharacter():GetID() + + if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID + and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then + return false + end + end + + function PLUGIN:PlayerSpawnedProp(client, model, entity) + ix.log.Add(client, "spawnProp", model) + end + + PLUGIN.PlayerSpawnedEffect = PLUGIN.PlayerSpawnedProp + PLUGIN.PlayerSpawnedRagdoll = PLUGIN.PlayerSpawnedProp + + function PLUGIN:PlayerSpawnedNPC(client, entity) + ix.log.Add(client, "spawnEntity", entity) + end + + PLUGIN.PlayerSpawnedSWEP = PLUGIN.PlayerSpawnedNPC + PLUGIN.PlayerSpawnedSENT = PLUGIN.PlayerSpawnedNPC + PLUGIN.PlayerSpawnedVehicle = PLUGIN.PlayerSpawnedNPC +else + function PLUGIN:PhysgunPickup(client, entity) + if (entity:GetNetVar("owner", 0) != client:GetCharacter():GetID() + and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then + return false + end + end + + function PLUGIN:CanProperty(client, property, entity) + local characterID = client:GetCharacter():GetID() + + if (entity:GetNetVar("owner", 0) != characterID + and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then + return false + end + end + + function PLUGIN:CanTool(client, trace, tool) + local entity = trace.Entity + local characterID = client:GetCharacter():GetID() + + if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID + and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then + return false + end + end +end diff --git a/garrysmod/gamemodes/helix/plugins/saveitems.lua b/garrysmod/gamemodes/helix/plugins/saveitems.lua new file mode 100644 index 0000000..ed3cff1 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/saveitems.lua @@ -0,0 +1,121 @@ + +PLUGIN.name = "Save Items" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Saves items that were dropped." + +--[[ + function PLUGIN:OnSavedItemLoaded(items) + for k, v in ipairs(items) do + -- do something + end + end + + function PLUGIN:ShouldDeleteSavedItems() + return true + end +]]-- + +-- as title says. + +function PLUGIN:LoadData() + local items = self:GetData() + + if (items) then + local idRange = {} + local info = {} + + for _, v in ipairs(items) do + idRange[#idRange + 1] = v[1] + info[v[1]] = {v[2], v[3], v[4]} + end + + if (#idRange > 0) then + if (hook.Run("ShouldDeleteSavedItems") == true) then + -- don't spawn saved item and just delete them. + local query = mysql:Delete("ix_items") + query:WhereIn("item_id", idRange) + query:Execute() + + print("Server Deleted Server Items (does not includes Logical Items)") + else + local query = mysql:Select("ix_items") + query:Select("item_id") + query:Select("unique_id") + query:Select("data") + query:WhereIn("item_id", idRange) + query:Callback(function(result) + if (istable(result)) then + local loadedItems = {} + local bagInventories = {} + + for _, v in ipairs(result) do + local itemID = tonumber(v.item_id) + local data = util.JSONToTable(v.data or "[]") + local uniqueID = v.unique_id + local itemTable = ix.item.list[uniqueID] + + if (itemTable and itemID) then + local item = ix.item.New(uniqueID, itemID) + item.data = data or {} + + local itemInfo = info[itemID] + local position, angles, bMovable = itemInfo[1], itemInfo[2], true + + if (isbool(itemInfo[3])) then + bMovable = itemInfo[3] + end + + local itemEntity = item:Spawn(position, angles) + itemEntity.ixItemID = itemID + + local physicsObject = itemEntity:GetPhysicsObject() + + if (IsValid(physicsObject)) then + physicsObject:EnableMotion(bMovable) + end + + item.invID = 0 + loadedItems[#loadedItems + 1] = item + + if (item.isBag) then + local invType = ix.item.inventoryTypes[uniqueID] + bagInventories[item:GetData("id")] = {invType.w, invType.h} + end + end + end + + -- we need to manually restore bag inventories in the world since they don't have a current owner + -- that it can automatically restore along with the character when it's loaded + if (!table.IsEmpty(bagInventories)) then + ix.inventory.Restore(bagInventories) + end + + hook.Run("OnSavedItemLoaded", loadedItems) -- when you have something in the dropped item. + end + end) + query:Execute() + end + end + end +end + +function PLUGIN:SaveData() + local items = {} + + for _, v in ipairs(ents.FindByClass("ix_item")) do + if (v.ixItemID and !v.bTemporary) then + local physicsObject = v:GetPhysicsObject() + local bMovable = nil + + if (IsValid(physicsObject)) then + bMovable = physicsObject:IsMoveable() + end + + items[#items + 1] = { + v.ixItemID, v:GetPos(), v:GetAngles(), bMovable + } + end + end + + self:SetData(items) +end diff --git a/garrysmod/gamemodes/helix/plugins/spawns.lua b/garrysmod/gamemodes/helix/plugins/spawns.lua new file mode 100644 index 0000000..53e1e19 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/spawns.lua @@ -0,0 +1,205 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Spawns" +PLUGIN.description = "Spawn points for factions and classes." +PLUGIN.author = "Chessnut" +PLUGIN.spawns = PLUGIN.spawns or {} + +local function IsSpawnPointClear(pos) + local mins = Vector(-16, -16, 0) + local maxs = Vector(16, 16, 72) + + for _, ply in ipairs(player.GetAll()) do + if ply:Alive() and ply:GetPos():Distance(pos) < 50 then + return false + end + end + + local ents = ents.FindInBox(pos + mins, pos + maxs) + for _, ent in ipairs(ents) do + if IsValid(ent) and not ent:IsPlayer() and ent:GetSolid() ~= SOLID_NONE then + return false + end + end + + return true +end + +function PLUGIN:PlayerLoadout(client) + local character = client:GetCharacter() + + if (self.spawns and !table.IsEmpty(self.spawns) and character) then + local class = character:GetClass() + local points + local className = "default" + + for k, v in ipairs(ix.faction.indices) do + if (k == client:Team()) then + points = self.spawns[v.uniqueID] or {} + + break + end + end + + if (points) then + local factionTable = ix.faction.indices[client:Team()] + local podr = character:GetPodr() + + if (podr and factionTable and factionTable.Podr and factionTable.Podr[podr]) then + className = factionTable.Podr[podr].name:lower() + else + for _, v in ipairs(ix.class.list) do + if (class == v.index) then + className = v.uniqueID + + break + end + end + end + + points = points[className] or points["default"] + + if (points and !table.IsEmpty(points)) then + local tries = 0 + local position + + repeat + position = table.Random(points) + tries = tries + 1 + until IsSpawnPointClear(position) or tries > 10 + + client:SetPos(position) + end + end + end +end + +function PLUGIN:LoadData() + self.spawns = self:GetData() or {} +end + +function PLUGIN:SaveSpawns() + self:SetData(self.spawns) +end + +ix.command.Add("SpawnAdd", { + description = "@cmdSpawnAdd", + privilege = "Manage Spawn Points", + adminOnly = true, + arguments = { + ix.type.string, + bit.bor(ix.type.text, ix.type.optional) + }, + OnRun = function(self, client, name, class) + local full = (name .. " " .. (class or "")):Trim() + local words = string.Explode(" ", full) + + local info, info2, faction, className + + -- Перебираем возможные разбиения строки на фракцию и класс + for i = 1, #words do + local fName = table.concat(words, " ", 1, i) + local cName = table.concat(words, " ", i + 1) + + for _, v in ipairs(ix.faction.indices) do + -- Проверяем совпадение фракции + if (ix.util.StringMatches(v.uniqueID, fName) or ix.util.StringMatches(L(v.name, client), fName)) then + local potentialInfo = v + local potentialFaction = v.uniqueID + local potentialClassName, potentialInfo2 + + if (cName == "") then + potentialClassName = "default" + else + -- Ищем класс + for _, c in ipairs(ix.class.list) do + if (c.faction == v.index and (c.uniqueID:lower() == cName:lower() or ix.util.StringMatches(L(c.name, client), cName))) then + potentialInfo2 = c + potentialClassName = c.uniqueID + break + end + end + + -- Ищем подразделение (Podr) + if (!potentialClassName and v.Podr) then + for _, p in pairs(v.Podr) do + local pName = p.name:lower() + local qName = cName:lower() + + -- Сравнение с запасом на окончания (по первым 4-5 символам) или по вхождению + if (ix.util.StringMatches(pName, qName) or ix.util.StringMatches(qName, pName) or + pName:find(qName, 1, true) or qName:find(pName, 1, true) or + (pName:sub(1, 8) == qName:sub(1, 8))) then -- 8 байт = ~4 русских символа + potentialInfo2 = p + potentialClassName = p.name:lower() + break + end + end + end + end + + if (potentialClassName) then + info = potentialInfo + faction = potentialFaction + className = potentialClassName + info2 = potentialInfo2 + break + end + end + end + + if (className) then break end + end + + if (info) then + if (!className) then + return "@invalidClass" + end + + PLUGIN.spawns[faction] = PLUGIN.spawns[faction] or {} + PLUGIN.spawns[faction][className] = PLUGIN.spawns[faction][className] or {} + + table.insert(PLUGIN.spawns[faction][className], client:GetPos()) + PLUGIN:SaveSpawns() + + local logName = L(info.name, client) + if (info2) then + logName = logName .. " (" .. L(info2.name, client) .. ")" + end + + return "@spawnAdded", logName + else + return "@invalidFaction" + end + end +}) + +ix.command.Add("SpawnRemove", { + description = "@cmdSpawnRemove", + privilege = "Manage Spawn Points", + adminOnly = true, + arguments = bit.bor(ix.type.number, ix.type.optional), + OnRun = function(self, client, radius) + radius = radius or 120 + + local position = client:GetPos() + local i = 0 + + for _, v in pairs(PLUGIN.spawns) do + for _, v2 in pairs(v) do + for k3, v3 in pairs(v2) do + if (v3:Distance(position) <= radius) then + v2[k3] = nil + i = i + 1 + end + end + end + end + + if (i > 0) then + PLUGIN:SaveSpawns() + end + + return "@spawnDeleted", i + end +}) diff --git a/garrysmod/gamemodes/helix/plugins/stamina/attributes/sh_end.lua b/garrysmod/gamemodes/helix/plugins/stamina/attributes/sh_end.lua new file mode 100644 index 0000000..24b486f --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/stamina/attributes/sh_end.lua @@ -0,0 +1,2 @@ +ATTRIBUTE.name = "Endurance" +ATTRIBUTE.description = "Affects how long you can run for." diff --git a/garrysmod/gamemodes/helix/plugins/stamina/attributes/sh_stm.lua b/garrysmod/gamemodes/helix/plugins/stamina/attributes/sh_stm.lua new file mode 100644 index 0000000..c8b01df --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/stamina/attributes/sh_stm.lua @@ -0,0 +1,6 @@ +ATTRIBUTE.name = "Stamina" +ATTRIBUTE.description = "Affects how fast you can run." + +function ATTRIBUTE:OnSetup(client, value) + client:SetRunSpeed(ix.config.Get("runSpeed") + value) +end diff --git a/garrysmod/gamemodes/helix/plugins/stamina/sh_plugin.lua b/garrysmod/gamemodes/helix/plugins/stamina/sh_plugin.lua new file mode 100644 index 0000000..5852cf4 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/stamina/sh_plugin.lua @@ -0,0 +1,184 @@ +PLUGIN.name = "Stamina" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Adds a stamina system to limit running." + +-- luacheck: push ignore 631 +ix.config.Add("staminaDrain", 1, "How much stamina to drain per tick (every quarter second). This is calculated before attribute reduction.", nil, { + data = {min = 0, max = 10, decimals = 2}, + category = "characters" +}) + +ix.config.Add("staminaRegeneration", 1.75, "How much stamina to regain per tick (every quarter second).", nil, { + data = {min = 0, max = 10, decimals = 2}, + category = "characters" +}) + +ix.config.Add("staminaCrouchRegeneration", 2, "How much stamina to regain per tick (every quarter second) while crouching.", nil, { + data = {min = 0, max = 10, decimals = 2}, + category = "characters" +}) + +ix.config.Add("punchStamina", 10, "How much stamina punches use up.", nil, { + data = {min = 0, max = 100}, + category = "characters" +}) +-- luacheck: pop +local function CalcStaminaChange(client) + local character = client:GetCharacter() + + if (!character) then + return 0 + end + + -- Если в технике или в ноуклипе - сразу реген и выходим + local inVehicle = client:InVehicle() or IsValid(client:GetVehicle()) + if (not inVehicle and client.lvsGetVehicle) then + local v = client:lvsGetVehicle() + inVehicle = IsValid(v) + end + if (not inVehicle and IsValid(client:GetParent()) and client:GetParent():IsVehicle()) then inVehicle = true end + + local isNoClip = client:GetMoveType() == MOVETYPE_NOCLIP + + if (inVehicle or isNoClip) then + local offset = ix.config.Get("staminaRegeneration", 1.75) + if (SERVER) then + local current = client:GetLocalVar("stm", 0) + client:SetLocalVar("stm", math.Clamp(current + offset, 0, 100)) + end + return offset + end + + local walkSpeed = ix.config.Get("walkSpeed") + local maxAttributes = ix.config.Get("maxAttributes", 100) + local offset + + if (client:KeyDown(IN_SPEED) and client:GetVelocity():LengthSqr() >= 5000 and client:OnGround()) then + -- characters could have attribute values greater than max if the config was changed + offset = -ix.config.Get("staminaDrain", 1) + math.min(character:GetAttribute("end", 0), maxAttributes) / 100 + + -- Ensure minimum drain + if (offset > -0.2) then + offset = -0.2 + end + + -- Armor weight influence (built-in fix) + local armor = client:Armor() + if (armor > 0) then + offset = offset * (1 + (math.min(armor, 100) / 100) * 0.7) + end + else + offset = client:Crouching() and ix.config.Get("staminaCrouchRegeneration", 2) or ix.config.Get("staminaRegeneration", 1.75) + + -- Armor slows down regeneration but doesn't cause drain while standing + local armor = client:Armor() + if (armor > 0 and offset > 0) then + offset = offset * math.max(1 - (math.min(armor, 100) / 100) * 0.5, 0.1) + end + + -- Гарантируем, что регенерация это регенерация (не меньше нуля) + offset = math.max(offset, 0.05) + end + + offset = hook.Run("AdjustStaminaOffset", client, offset) or offset + + if (CLIENT) then + return offset -- for the client we need to return the estimated stamina change + else + local current = client:GetLocalVar("stm", 0) + local value = math.Clamp(current + offset, 0, 100) + + if (current != value) then + client:SetLocalVar("stm", value) + + if (value == 0 and !client:GetNetVar("brth", false)) then + client:SetNetVar("brth", true) + + character:UpdateAttrib("end", 0.1) + character:UpdateAttrib("stm", 0.01) + + hook.Run("PlayerStaminaLost", client) + elseif (value >= 50 and client:GetNetVar("brth", false)) then + client:SetNetVar("brth", nil) + + hook.Run("PlayerStaminaGained", client) + end + end + end +end + +function PLUGIN:SetupMove(client, mv, cmd) + if (client:GetNetVar("brth", false)) then + mv:SetMaxClientSpeed(client:GetWalkSpeed()) + end +end + +if (SERVER) then + function PLUGIN:PostPlayerLoadout(client) + local uniqueID = "ixStam" .. client:SteamID() + + timer.Create(uniqueID, 0.25, 0, function() + if (!IsValid(client)) then + timer.Remove(uniqueID) + return + end + + CalcStaminaChange(client) + end) + end + + function PLUGIN:CharacterPreSave(character) + local client = character:GetPlayer() + + if (IsValid(client)) then + character:SetData("stamina", client:GetLocalVar("stm", 0)) + end + end + + function PLUGIN:PlayerLoadedCharacter(client, character) + timer.Simple(0.25, function() + client:SetLocalVar("stm", character:GetData("stamina", 100)) + end) + end + + local playerMeta = FindMetaTable("Player") + + function playerMeta:RestoreStamina(amount) + local current = self:GetLocalVar("stm", 0) + local value = math.Clamp(current + amount, 0, 100) + + self:SetLocalVar("stm", value) + end + + function playerMeta:ConsumeStamina(amount) + local current = self:GetLocalVar("stm", 0) + local value = math.Clamp(current - amount, 0, 100) + + self:SetLocalVar("stm", value) + end + +else + + local predictedStamina = 100 + + function PLUGIN:Think() + local offset = CalcStaminaChange(LocalPlayer()) + -- the server check it every 0.25 sec, here we check it every [FrameTime()] seconds + offset = math.Remap(FrameTime(), 0, 0.25, 0, offset) + + if (offset != 0) then + predictedStamina = math.Clamp(predictedStamina + offset, 0, 100) + end + end + + function PLUGIN:OnLocalVarSet(key, var) + if (key != "stm") then return end + if (math.abs(predictedStamina - var) > 5) then + predictedStamina = var + end + end + + ix.bar.Add(function() + return predictedStamina / 100 + end, Color(200, 200, 40), nil, "stm") +end diff --git a/garrysmod/gamemodes/helix/plugins/strength/attributes/sh_str.lua b/garrysmod/gamemodes/helix/plugins/strength/attributes/sh_str.lua new file mode 100644 index 0000000..3342469 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/strength/attributes/sh_str.lua @@ -0,0 +1,2 @@ +ATTRIBUTE.name = "Strength" +ATTRIBUTE.description = "A measure of how strong you are." diff --git a/garrysmod/gamemodes/helix/plugins/strength/sh_plugin.lua b/garrysmod/gamemodes/helix/plugins/strength/sh_plugin.lua new file mode 100644 index 0000000..343c171 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/strength/sh_plugin.lua @@ -0,0 +1,24 @@ +PLUGIN.name = "Strength" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Adds a strength attribute." + +if (SERVER) then + function PLUGIN:GetPlayerPunchDamage(client, damage, context) + if (client:GetCharacter()) then + -- Add to the total fist damage. + context.damage = context.damage + (client:GetCharacter():GetAttribute("str", 0) * ix.config.Get("strengthMultiplier")) + end + end + + function PLUGIN:PlayerThrowPunch(client, trace) + if (client:GetCharacter() and IsValid(trace.Entity) and trace.Entity:IsPlayer()) then + client:GetCharacter():UpdateAttrib("str", 0.001) + end + end +end + +-- Configuration for the plugin +ix.config.Add("strengthMultiplier", 0.3, "The strength multiplier scale", nil, { + data = {min = 0, max = 1.0, decimals = 1}, + category = "Strength" +}) diff --git a/garrysmod/gamemodes/helix/plugins/thirdperson.lua b/garrysmod/gamemodes/helix/plugins/thirdperson.lua new file mode 100644 index 0000000..50580e3 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/thirdperson.lua @@ -0,0 +1,233 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Third Person" +PLUGIN.author = "Black Tea" +PLUGIN.description = "Enables third person camera usage." + +-- Localization +ix.lang.AddTable("russian", { + optThirdPerson = "Вид от третьего лица", + optThirdPersonEnabled = "Включить вид от 3-го лица", + optThirdPersonEnabledDesc = "Использовать камеру от третьего лица", + optThirdPersonClassic = "Классический режим", + optThirdPersonClassicDesc = "Использовать классический вид камеры", + optThirdPersonVertical = "Вертикальное смещение", + optThirdPersonVerticalDesc = "Высота камеры над персонажем", + optThirdPersonHorizontal = "Горизонтальное смещение", + optThirdPersonHorizontalDesc = "Боковое смещение камеры", + optThirdPersonDistance = "Расстояние камеры", + optThirdPersonDistanceDesc = "Дистанция камеры от персонажа", +}) + +ix.lang.AddTable("english", { + optThirdPerson = "Third Person", + optThirdPersonEnabled = "Enable Third Person", + optThirdPersonEnabledDesc = "Use third person camera view", + optThirdPersonClassic = "Classic Mode", + optThirdPersonClassicDesc = "Use classic camera view", + optThirdPersonVertical = "Vertical Offset", + optThirdPersonVerticalDesc = "Camera height above character", + optThirdPersonHorizontal = "Horizontal Offset", + optThirdPersonHorizontalDesc = "Camera side offset", + optThirdPersonDistance = "Camera Distance", + optThirdPersonDistanceDesc = "Distance of camera from character", +}) + +ix.config.Add("thirdperson", false, "Allow Thirdperson in the server.", nil, { + category = "server" +}) + +if (CLIENT) then + local function isHidden() + return !ix.config.Get("thirdperson") + end + + ix.option.Add("thirdpersonEnabled", ix.type.bool, false, { + category = "optThirdPerson", + name = "optThirdPersonEnabled", + description = "optThirdPersonEnabledDesc", + hidden = isHidden, + OnChanged = function(oldValue, value) + hook.Run("ThirdPersonToggled", oldValue, value) + end + }) + + ix.option.Add("thirdpersonClassic", ix.type.bool, false, { + category = "optThirdPerson", + name = "optThirdPersonClassic", + description = "optThirdPersonClassicDesc", + hidden = isHidden + }) + + ix.option.Add("thirdpersonVertical", ix.type.number, 10, { + category = "optThirdPerson", + name = "optThirdPersonVertical", + description = "optThirdPersonVerticalDesc", + min = 0, max = 30, + hidden = isHidden + }) + + ix.option.Add("thirdpersonHorizontal", ix.type.number, 0, { + category = "optThirdPerson", + name = "optThirdPersonHorizontal", + description = "optThirdPersonHorizontalDesc", + min = -30, max = 30, + hidden = isHidden + }) + + ix.option.Add("thirdpersonDistance", ix.type.number, 50, { + category = "optThirdPerson", + name = "optThirdPersonDistance", + description = "optThirdPersonDistanceDesc", + category = "thirdperson", min = 0, max = 100, + hidden = isHidden + }) + + + concommand.Add("ix_togglethirdperson", function() + local bEnabled = !ix.option.Get("thirdpersonEnabled", false) + + ix.option.Set("thirdpersonEnabled", bEnabled) + end) + + local function isAllowed() + return ix.config.Get("thirdperson") + end + + local playerMeta = FindMetaTable("Player") + local traceMin = Vector(-10, -10, -10) + local traceMax = Vector(10, 10, 10) + + function playerMeta:CanOverrideView() + local entity = Entity(self:GetLocalVar("ragdoll", 0)) + + if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing() and ix.gui.characterMenu:IsVisible()) then + return false + end + + if (IsValid(ix.gui.menu) and ix.gui.menu:GetCharacterOverview()) then + return false + end + + if (ix.option.Get("thirdpersonEnabled", false) and + !IsValid(self:GetVehicle()) and + isAllowed() and + IsValid(self) and + self:GetCharacter() and + !self:GetNetVar("actEnterAngle") and + !IsValid(entity) and + LocalPlayer():Alive() + ) then + return true + end + end + + local view, traceData, traceData2, aimOrigin, crouchFactor, ft, curAng, owner + local clmp = math.Clamp + crouchFactor = 0 + + function PLUGIN:CalcView(client, origin, angles, fov) + ft = FrameTime() + owner = client + + if (client:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then + local bNoclip = LocalPlayer():GetMoveType() == MOVETYPE_NOCLIP + + if ((client:OnGround() and client:KeyDown(IN_DUCK)) or client:Crouching()) then + crouchFactor = Lerp(ft*5, crouchFactor, 1) + else + crouchFactor = Lerp(ft*5, crouchFactor, 0) + end + + curAng = owner.camAng or angle_zero + view = {} + + local headPos = client:GetPos() + client:GetViewOffset() + local offset = curAng:Up() * ix.option.Get("thirdpersonVertical", 10) + + curAng:Right() * ix.option.Get("thirdpersonHorizontal", 0) - + client:GetViewOffsetDucked() * .5 * crouchFactor + + local startTrace = util.TraceLine({ + start = headPos, + endpos = headPos + offset, + filter = client + }) + + traceData = {} + traceData.start = startTrace.HitPos + traceData.endpos = traceData.start - curAng:Forward() * ix.option.Get("thirdpersonDistance", 50) + traceData.filter = client + traceData.ignoreworld = bNoclip + traceData.mins = traceMin + traceData.maxs = traceMax + view.origin = util.TraceHull(traceData).HitPos + aimOrigin = view.origin + view.angles = curAng + client:GetViewPunchAngles() + + traceData2 = {} + traceData2.start = aimOrigin + traceData2.endpos = aimOrigin + curAng:Forward() * 65535 + traceData2.filter = client + traceData2.ignoreworld = bNoclip + + local bClassic = ix.option.Get("thirdpersonClassic", false) + + if (bClassic or owner:IsWepRaised() or + (owner:KeyDown(bit.bor(IN_FORWARD, IN_BACK, IN_MOVELEFT, IN_MOVERIGHT)) and owner:GetVelocity():Length() >= 10)) then + client:SetEyeAngles((util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle()) + else + local currentAngles = client:EyeAngles() + currentAngles.pitch = (util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle().pitch + + client:SetEyeAngles(currentAngles) + end + + return view + end + end + + local diff, fm, sm + function PLUGIN:CreateMove(cmd) + owner = LocalPlayer() + + if (owner:CanOverrideView() and owner:GetMoveType() != MOVETYPE_NOCLIP and + LocalPlayer():GetViewEntity() == LocalPlayer()) then + local fm = cmd:GetForwardMove() + local sm = cmd:GetSideMove() + + local camAng = owner.camAng or owner:EyeAngles() + local eyeAng = owner:EyeAngles() + + local wishDir = (camAng:Forward() * fm) + (camAng:Right() * sm) + wishDir.z = 0 + + local localMove = WorldToLocal(wishDir, angle_zero, vector_origin, Angle(0, eyeAng.y, 0)) + + cmd:SetForwardMove(localMove.x) + cmd:SetSideMove(-localMove.y) + return false + end + end + + function PLUGIN:InputMouseApply(cmd, x, y, ang) + owner = LocalPlayer() + + if (!owner.camAng) then + owner.camAng = Angle(0, 0, 0) + end + + owner.camAng.p = clmp(math.NormalizeAngle(owner.camAng.p + y / 50), -85, 85) + owner.camAng.y = math.NormalizeAngle(owner.camAng.y - x / 50) + + if (owner:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then + return true + end + end + + function PLUGIN:ShouldDrawLocalPlayer() + if (LocalPlayer():GetViewEntity() == LocalPlayer() and !IsValid(LocalPlayer():GetVehicle())) then + return LocalPlayer():CanOverrideView() + end + end +end diff --git a/garrysmod/gamemodes/helix/plugins/typing.lua b/garrysmod/gamemodes/helix/plugins/typing.lua new file mode 100644 index 0000000..8a04029 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/typing.lua @@ -0,0 +1,244 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Typing Indicator" +PLUGIN.description = "Shows an indicator when someone is typing." +PLUGIN.author = "`impulse" +PLUGIN.animationTime = 0.5 + +if (CLIENT) then + local standingOffset = Vector(0, 0, 72) + local crouchingOffset = Vector(0, 0, 38) + local boneOffset = Vector(0, 0, 10) + local textColor = Color(250, 250, 250) + local shadowColor = Color(66, 66, 66) + local currentClass + + -- we can't rely on matching non-alphanumeric characters (i.e %W) due to patterns matching single bytes and not UTF-8 chars + local symbolPattern = "[~`!@#$%%%^&*()_%+%-={}%[%]|;:'\",%./<>?]" + + function PLUGIN:LoadFonts(font, genericFont) + surface.CreateFont("ixTypingIndicator", { + font = genericFont, + size = 128, + extended = true, + weight = 1000 + }) + end + + function PLUGIN:ChatTextChanged(text) + if (!IsValid(LocalPlayer())) then + return + end + + local character = LocalPlayer():GetCharacter() + + if (!character) then + return + end + + if (text == "") then + currentClass = nil + + net.Start("ixTypeClass") + net.WriteString("") + net.SendToServer() + + return + end + + local newClass = hook.Run("GetTypingIndicator", character, text) + + if (newClass != currentClass) then + currentClass = newClass + + net.Start("ixTypeClass") + net.WriteString(currentClass or "") + net.SendToServer() + end + end + + function PLUGIN:FinishChat() + currentClass = nil + + net.Start("ixTypeClass") + net.WriteString("") + net.SendToServer() + end + + function PLUGIN:GetTypingIndicator(character, text) + local prefix = text:utf8sub(1, 1) + + if (!prefix:find(symbolPattern) and text:utf8len() > 1) then + return "ic" + else + local chatType = ix.chat.Parse(nil, text) + + if (chatType and chatType != "ic") then + return !ix.chat.classes[chatType].bNoIndicator and chatType or nil + end + + -- some commands will have their own typing indicator, so we'll make sure we're actually typing out a command first + local start, _, commandName = text:find("/(%S+)%s") + + if (start == 1) then + for uniqueID, command in pairs(ix.command.list) do + if (command.bNoIndicator) then + continue + end + + if (commandName == uniqueID) then + return command.indicator and "@" .. command.indicator or "ooc" + end + end + end + end + end + + function PLUGIN:GetTypingIndicatorPosition(client) + local head + + for i = 1, client:GetBoneCount() do + local name = client:GetBoneName(i) + + if (string.find(name:lower(), "head")) then + head = i + break + end + end + + local position = head and client:GetBonePosition(head) or (client:Crouching() and crouchingOffset or standingOffset) + return position + boneOffset + end + + function PLUGIN:PostDrawTranslucentRenderables() + local client = LocalPlayer() + local position = client:GetPos() + + for _, v in player.Iterator() do + if (v == client) then + continue + end + + local distance = v:GetPos():DistToSqr(position) + local moveType = v:GetMoveType() + + if (!IsValid(v) or !v:Alive() or + (moveType != MOVETYPE_WALK and moveType != MOVETYPE_NONE) or + !v.ixChatClassText or + distance >= v.ixChatClassRange) then + continue + end + + local text = v.ixChatClassText + local range = v.ixChatClassRange + + local bAnimation = !ix.option.Get("disableAnimations", false) + local fraction + + if (bAnimation) then + local bComplete = v.ixChatClassTween:update(FrameTime()) + + if (bComplete and !v.ixChatStarted) then + v.ixChatClassText = nil + v.ixChatClassRange = nil + + continue + end + + fraction = v.ixChatClassAnimation + else + fraction = 1 + end + + local angle = EyeAngles() + angle:RotateAroundAxis(angle:Forward(), 90) + angle:RotateAroundAxis(angle:Right(), 90) + + cam.Start3D2D(self:GetTypingIndicatorPosition(v), Angle(0, angle.y, 90), 0.05) + surface.SetFont("ixTypingIndicator") + + local _, textHeight = surface.GetTextSize(text) + local alpha = bAnimation and ((1 - math.min(distance, range) / range) * 255 * fraction) or 255 + + draw.SimpleTextOutlined(text, "ixTypingIndicator", 0, + -textHeight * 0.5 * fraction, + ColorAlpha(textColor, alpha), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, 4, + ColorAlpha(shadowColor, alpha) + ) + cam.End3D2D() + end + end + + net.Receive("ixTypeClass", function() + local client = net.ReadEntity() + + if (!IsValid(client) or client == LocalPlayer()) then + return + end + + local newClass = net.ReadString() + local chatClass = ix.chat.classes[newClass] + local text + local range + + if (chatClass) then + text = L(chatClass.indicator or "chatTyping") + range = chatClass.range or math.pow(ix.config.Get("chatRange", 280), 2) + elseif (newClass and newClass:sub(1, 1) == "@") then + text = L(newClass:sub(2)) + range = math.pow(ix.config.Get("chatRange", 280), 2) + end + + if (ix.option.Get("disableAnimations", false)) then + client.ixChatClassText = text + client.ixChatClassRange = range + else + client.ixChatClassAnimation = tonumber(client.ixChatClassAnimation) or 0 + + if (text and !client.ixChatStarted) then + client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 1}, "outCubic") + + client.ixChatClassText = text + client.ixChatClassRange = range + client.ixChatStarted = true + elseif (!text and client.ixChatStarted) then + client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 0}, "inCubic") + client.ixChatStarted = nil + end + end + end) +else + util.AddNetworkString("ixTypeClass") + + function PLUGIN:PlayerSpawn(client) + net.Start("ixTypeClass") + net.WriteEntity(client) + net.WriteString("") + net.Broadcast() + end + + net.Receive("ixTypeClass", function(length, client) + if ((client.ixNextTypeClass or 0) > RealTime()) then + return + end + + local newClass = net.ReadString() + + -- send message to players in pvs only since they're the only ones who can see the indicator + -- we'll broadcast if the type class is empty because they might move out of pvs before the ending net message is sent + net.Start("ixTypeClass") + net.WriteEntity(client) + net.WriteString(newClass) + + if (newClass == "") then + net.Broadcast() + else + net.SendPVS(client:GetPos()) + end + + client.ixNextTypeClass = RealTime() + 0.2 + end) +end diff --git a/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendor.lua b/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendor.lua new file mode 100644 index 0000000..ee170c0 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendor.lua @@ -0,0 +1,287 @@ + +local PANEL = {} + +AccessorFunc(PANEL, "bReadOnly", "ReadOnly", FORCE_BOOL) + +function PANEL:Init() + self:SetSize(ScrW() * 0.45, ScrH() * 0.65) + self:SetTitle("") + self:MakePopup() + self:Center() + + local header = self:Add("DPanel") + header:SetTall(34) + header:Dock(TOP) + + self.vendorName = header:Add("DLabel") + self.vendorName:Dock(LEFT) + self.vendorName:SetWide(self:GetWide() * 0.5 - 7) + self.vendorName:SetText("John Doe") + self.vendorName:SetTextInset(4, 0) + self.vendorName:SetTextColor(color_white) + self.vendorName:SetFont("ixMediumFont") + + self.ourName = header:Add("DLabel") + self.ourName:Dock(RIGHT) + self.ourName:SetWide(self:GetWide() * 0.5 - 7) + self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")") + self.ourName:SetTextInset(0, 0) + self.ourName:SetTextColor(color_white) + self.ourName:SetFont("ixMediumFont") + + local footer = self:Add("DPanel") + footer:SetTall(34) + footer:Dock(BOTTOM) + footer:SetPaintBackground(false) + + self.vendorSell = footer:Add("DButton") + self.vendorSell:SetFont("ixMediumFont") + self.vendorSell:SetWide(self.vendorName:GetWide()) + self.vendorSell:Dock(LEFT) + self.vendorSell:SetContentAlignment(5) + -- The text says purchase but the vendor is selling it to us. + self.vendorSell:SetText(L"purchase") + self.vendorSell:SetTextColor(color_white) + + self.vendorSell.DoClick = function(this) + if (IsValid(self.activeSell)) then + net.Start("ixVendorTrade") + net.WriteString(self.activeSell.item) + net.WriteBool(false) + net.SendToServer() + end + end + + self.vendorBuy = footer:Add("DButton") + self.vendorBuy:SetFont("ixMediumFont") + self.vendorBuy:SetWide(self.ourName:GetWide()) + self.vendorBuy:Dock(RIGHT) + self.vendorBuy:SetContentAlignment(5) + self.vendorBuy:SetText(L"sell") + self.vendorBuy:SetTextColor(color_white) + self.vendorBuy.DoClick = function(this) + if (IsValid(self.activeBuy)) then + net.Start("ixVendorTrade") + net.WriteString(self.activeBuy.item) + net.WriteBool(true) + net.SendToServer() + end + end + + self.selling = self:Add("DScrollPanel") + self.selling:SetWide(self:GetWide() * 0.5 - 7) + self.selling:Dock(LEFT) + self.selling:DockMargin(0, 4, 0, 4) + self.selling:SetPaintBackground(true) + + self.sellingItems = self.selling:Add("DListLayout") + self.sellingItems:SetSize(self.selling:GetSize()) + self.sellingItems:DockPadding(0, 0, 0, 4) + self.sellingItems:SetTall(ScrH()) + + self.buying = self:Add("DScrollPanel") + self.buying:SetWide(self:GetWide() * 0.5 - 7) + self.buying:Dock(RIGHT) + self.buying:DockMargin(0, 4, 0, 4) + self.buying:SetPaintBackground(true) + + self.buyingItems = self.buying:Add("DListLayout") + self.buyingItems:SetSize(self.buying:GetSize()) + self.buyingItems:DockPadding(0, 0, 0, 4) + + self.sellingList = {} + self.buyingList = {} +end + +function PANEL:addItem(uniqueID, listID) + local entity = self.entity + local items = entity.items + local data = items[uniqueID] + + if ((!listID or listID == "selling") and !IsValid(self.sellingList[uniqueID]) + and ix.item.list[uniqueID]) then + if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_BUYONLY) then + local item = self.sellingItems:Add("ixVendorItem") + item:Setup(uniqueID) + + self.sellingList[uniqueID] = item + self.sellingItems:InvalidateLayout() + end + end + + if ((!listID or listID == "buying") and !IsValid(self.buyingList[uniqueID]) + and LocalPlayer():GetCharacter():GetInventory():HasItem(uniqueID)) then + if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_SELLONLY) then + local item = self.buyingItems:Add("ixVendorItem") + item:Setup(uniqueID) + item.isLocal = true + + self.buyingList[uniqueID] = item + self.buyingItems:InvalidateLayout() + end + end +end + +function PANEL:removeItem(uniqueID, listID) + if (!listID or listID == "selling") then + if (IsValid(self.sellingList[uniqueID])) then + self.sellingList[uniqueID]:Remove() + self.sellingItems:InvalidateLayout() + end + end + + if (!listID or listID == "buying") then + if (IsValid(self.buyingList[uniqueID])) then + self.buyingList[uniqueID]:Remove() + self.buyingItems:InvalidateLayout() + end + end +end + +function PANEL:Setup(entity) + self.entity = entity + self:SetTitle(entity:GetDisplayName()) + self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..entity.money..")" or "")) + + self.vendorBuy:SetEnabled(!self:GetReadOnly()) + self.vendorSell:SetEnabled(!self:GetReadOnly()) + + for k, _ in SortedPairs(entity.items) do + self:addItem(k, "selling") + end + + for _, v in SortedPairs(LocalPlayer():GetCharacter():GetInventory():GetItems()) do + self:addItem(v.uniqueID, "buying") + end +end + +function PANEL:OnRemove() + net.Start("ixVendorClose") + net.SendToServer() + + if (IsValid(ix.gui.vendorEditor)) then + ix.gui.vendorEditor:Remove() + end +end + +function PANEL:Think() + local entity = self.entity + + if (!IsValid(entity)) then + self:Remove() + + return + end + + if ((self.nextUpdate or 0) < CurTime()) then + self:SetTitle(self.entity:GetDisplayName()) + self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..ix.currency.Get(entity.money)..")" or "")) + self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")") + + self.nextUpdate = CurTime() + 0.25 + end +end + +function PANEL:OnItemSelected(panel) + local price = self.entity:GetPrice(panel.item, panel.isLocal) + + if (panel.isLocal) then + self.vendorBuy:SetText(L"sell".." ("..ix.currency.Get(price)..")") + else + self.vendorSell:SetText(L"purchase".." ("..ix.currency.Get(price)..")") + end +end + +vgui.Register("ixVendor", PANEL, "DFrame") + +PANEL = {} + +function PANEL:Init() + self:SetTall(36) + self:DockMargin(4, 4, 4, 0) + + self.icon = self:Add("SpawnIcon") + self.icon:SetPos(2, 2) + self.icon:SetSize(32, 32) + self.icon:SetModel("models/error.mdl") + + self.name = self:Add("DLabel") + self.name:Dock(FILL) + self.name:DockMargin(42, 0, 0, 0) + self.name:SetFont("ixChatFont") + self.name:SetTextColor(color_white) + self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200)) + + self.click = self:Add("DButton") + self.click:Dock(FILL) + self.click:SetText("") + self.click.Paint = function() end + self.click.DoClick = function(this) + if (self.isLocal) then + ix.gui.vendor.activeBuy = self + else + ix.gui.vendor.activeSell = self + end + + ix.gui.vendor:OnItemSelected(self) + end +end + +function PANEL:SetCallback(callback) + self.click.DoClick = function(this) + callback() + self.selected = true + end +end + +function PANEL:Setup(uniqueID) + local item = ix.item.list[uniqueID] + + if (item) then + self.item = uniqueID + self.icon:SetModel(item:GetModel(), item:GetSkin()) + self.name:SetText(item:GetName()) + self.itemName = item:GetName() + + self.click:SetHelixTooltip(function(tooltip) + ix.hud.PopulateItemTooltip(tooltip, item) + + local entity = ix.gui.vendor.entity + if (entity and entity.items[self.item] and entity.items[self.item][VENDOR_MAXSTOCK]) then + local info = entity.items[self.item] + local stock = tooltip:AddRowAfter("name", "stock") + stock:SetText(string.format("Stock: %d/%d", info[VENDOR_STOCK], info[VENDOR_MAXSTOCK])) + stock:SetBackgroundColor(derma.GetColor("Info", self)) + stock:SizeToContents() + end + end) + end +end + +function PANEL:Think() + if ((self.nextUpdate or 0) < CurTime()) then + local entity = ix.gui.vendor.entity + + if (entity and self.isLocal) then + local count = LocalPlayer():GetCharacter():GetInventory():GetItemCount(self.item) + + if (count == 0) then + self:Remove() + end + end + + self.nextUpdate = CurTime() + 0.1 + end +end + +function PANEL:Paint(w, h) + if (ix.gui.vendor.activeBuy == self or ix.gui.vendor.activeSell == self) then + surface.SetDrawColor(ix.config.Get("color")) + else + surface.SetDrawColor(0, 0, 0, 100) + end + + surface.DrawRect(0, 0, w, h) +end + +vgui.Register("ixVendorItem", PANEL, "DPanel") diff --git a/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendoreditor.lua b/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendoreditor.lua new file mode 100644 index 0000000..6d39974 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendoreditor.lua @@ -0,0 +1,286 @@ + +local PANEL = {} + +function PANEL:Init() + local entity = ix.gui.vendor.entity + + self:SetSize(320, 480) + self:MoveLeftOf(ix.gui.vendor, 8) + self:MakePopup() + self:CenterVertical() + self:SetTitle(L"vendorEditor") + self.lblTitle:SetTextColor(color_white) + + self.name = self:Add("DTextEntry") + self.name:Dock(TOP) + self.name:SetText(entity:GetDisplayName()) + self.name:SetPlaceholderText(L"name") + self.name.OnEnter = function(this) + if (entity:GetDisplayName() != this:GetText()) then + self:updateVendor("name", this:GetText()) + end + end + + self.description = self:Add("DTextEntry") + self.description:Dock(TOP) + self.description:DockMargin(0, 4, 0, 0) + self.description:SetText(entity:GetDescription()) + self.description:SetPlaceholderText(L"description") + self.description.OnEnter = function(this) + if (entity:GetDescription() != this:GetText()) then + self:updateVendor("description", this:GetText()) + end + end + + self.model = self:Add("DTextEntry") + self.model:Dock(TOP) + self.model:DockMargin(0, 4, 0, 0) + self.model:SetText(entity:GetModel()) + self.model:SetPlaceholderText(L"model") + self.model.OnEnter = function(this) + if (entity:GetModel():lower() != this:GetText():lower()) then + self:updateVendor("model", this:GetText():lower()) + end + end + + local useMoney = tonumber(entity.money) != nil + + self.money = self:Add("DTextEntry") + self.money:Dock(TOP) + self.money:DockMargin(0, 4, 0, 0) + self.money:SetText(!useMoney and "∞" or entity.money) + self.money:SetPlaceholderText(L"money") + self.money:SetDisabled(!useMoney) + self.money:SetEnabled(useMoney) + self.money:SetNumeric(true) + self.money.OnEnter = function(this) + local value = tonumber(this:GetText()) or entity.money + + if (value == entity.money) then + return + end + + self:updateVendor("money", value) + end + + self.bubble = self:Add("DCheckBoxLabel") + self.bubble:SetText(L"vendorNoBubble") + self.bubble:Dock(TOP) + self.bubble:DockMargin(0, 4, 0, 0) + self.bubble:SetValue(entity:GetNoBubble() and 1 or 0) + self.bubble.OnChange = function(this, value) + if (this.noSend) then + this.noSend = nil + else + self:updateVendor("bubble", value) + end + end + + self.useMoney = self:Add("DCheckBoxLabel") + self.useMoney:SetText(L"vendorUseMoney") + self.useMoney:Dock(TOP) + self.useMoney:DockMargin(0, 4, 0, 0) + self.useMoney:SetChecked(useMoney) + self.useMoney.OnChange = function(this, value) + self:updateVendor("useMoney") + end + + self.sellScale = self:Add("DNumSlider") + self.sellScale:Dock(TOP) + self.sellScale:DockMargin(0, 4, 0, 0) + self.sellScale:SetText(L"vendorSellScale") + self.sellScale.Label:SetTextColor(color_white) + self.sellScale.TextArea:SetTextColor(color_white) + self.sellScale:SetDecimals(1) + self.sellScale.noSend = true + self.sellScale:SetValue(entity.scale) + self.sellScale.OnValueChanged = function(this, value) + if (this.noSend) then + this.noSend = nil + else + timer.Create("ixVendorScale", 1, 1, function() + if (IsValid(self) and IsValid(self.sellScale)) then + value = self.sellScale:GetValue() + + if (value != entity.scale) then + self:updateVendor("scale", value) + end + end + end) + end + end + + self.faction = self:Add("DButton") + self.faction:SetText(L"vendorFaction") + self.faction:Dock(TOP) + self.faction:SetTextColor(color_white) + self.faction:DockMargin(0, 4, 0, 0) + self.faction.DoClick = function(this) + if (IsValid(ix.gui.editorFaction)) then + ix.gui.editorFaction:Remove() + end + + ix.gui.editorFaction = vgui.Create("ixVendorFactionEditor") + ix.gui.editorFaction.updateVendor = self.updateVendor + ix.gui.editorFaction.entity = entity + ix.gui.editorFaction:Setup() + end + + self.searchBar = self:Add("DTextEntry") + self.searchBar:Dock(TOP) + self.searchBar:DockMargin(0, 4, 0, 0) + self.searchBar:SetUpdateOnType(true) + self.searchBar:SetPlaceholderText("Search...") + self.searchBar.OnValueChange = function(this, value) + self:ReloadItemList(value) + end + + local menu + + self.items = self:Add("DListView") + self.items:Dock(FILL) + self.items:DockMargin(0, 4, 0, 0) + self.items:AddColumn(L"name").Header:SetTextColor(color_black) + self.items:AddColumn(L"category").Header:SetTextColor(color_black) + self.items:AddColumn(L"mode").Header:SetTextColor(color_black) + self.items:AddColumn(L"price").Header:SetTextColor(color_black) + self.items:AddColumn(L"stock").Header:SetTextColor(color_black) + self.items:SetMultiSelect(false) + self.items.OnRowRightClick = function(this, index, line) + if (IsValid(menu)) then + menu:Remove() + end + + local uniqueID = line.item + + menu = DermaMenu() + -- Modes of the item. + local mode, panel = menu:AddSubMenu(L"mode") + panel:SetImage("icon16/key.png") + + -- Disable buying/selling of the item. + mode:AddOption(L"none", function() + self:updateVendor("mode", {uniqueID, nil}) + end):SetImage("icon16/cog_error.png") + + -- Allow the vendor to sell and buy this item. + mode:AddOption(L"vendorBoth", function() + self:updateVendor("mode", {uniqueID, VENDOR_SELLANDBUY}) + end):SetImage("icon16/cog.png") + + -- Only allow the vendor to buy this item from players. + mode:AddOption(L"vendorBuy", function() + self:updateVendor("mode", {uniqueID, VENDOR_BUYONLY}) + end):SetImage("icon16/cog_delete.png") + + -- Only allow the vendor to sell this item to players. + mode:AddOption(L"vendorSell", function() + self:updateVendor("mode", {uniqueID, VENDOR_SELLONLY}) + end):SetImage("icon16/cog_add.png") + + local itemTable = ix.item.list[uniqueID] + + -- Set the price of the item. + menu:AddOption(L"price", function() + Derma_StringRequest( + itemTable.GetName and itemTable:GetName() or L(itemTable.name), + L"vendorPriceReq", + entity:GetPrice(uniqueID), + function(text) + text = tonumber(text) + + if (text == itemTable.price) then + text = nil + end + + self:updateVendor("price", {uniqueID, text}) + end + ) + end):SetImage("icon16/coins.png") + + -- Set the stock of the item or disable it. + local stock, menuPanel = menu:AddSubMenu(L"stock") + menuPanel:SetImage("icon16/table.png") + + -- Disable the use of stocks for this item. + stock:AddOption(L"disable", function() + self:updateVendor("stockDisable", uniqueID) + end):SetImage("icon16/table_delete.png") + + -- Edit the maximum stock for this item. + stock:AddOption(L"edit", function() + local _, max = entity:GetStock(uniqueID) + + Derma_StringRequest( + itemTable.GetName and itemTable:GetName() or L(itemTable.name), + L"vendorStockReq", + max or 1, + function(text) + self:updateVendor("stockMax", {uniqueID, text}) + end + ) + end):SetImage("icon16/table_edit.png") + + -- Edit the current stock of this item. + stock:AddOption(L"vendorEditCurStock", function() + Derma_StringRequest( + itemTable.GetName and itemTable:GetName() or L(itemTable.name), + L"vendorStockCurReq", + entity:GetStock(uniqueID) or 0, + function(text) + self:updateVendor("stock", {uniqueID, text}) + end + ) + end):SetImage("icon16/table_edit.png") + menu:Open() + end + + self:ReloadItemList() +end + +function PANEL:ReloadItemList(filter) + local entity = ix.gui.vendor.entity + self.lines = {} + + self.items:Clear() + + for k, v in SortedPairs(ix.item.list) do + local itemName = v.GetName and v:GetName() or L(v.name) + + if (filter and !itemName:lower():find(filter:lower(), 1, false)) then + continue + end + + local mode = entity.items[k] and entity.items[k][VENDOR_MODE] + local current, max = entity:GetStock(k) + local panel = self.items:AddLine( + itemName, + v.category or L"none", + mode and L(VENDOR_TEXT[mode]) or L"none", + entity:GetPrice(k), + max and current.."/"..max or "-" + ) + + panel.item = k + self.lines[k] = panel + end +end + +function PANEL:OnRemove() + if (IsValid(ix.gui.vendor)) then + ix.gui.vendor:Remove() + end + + if (IsValid(ix.gui.editorFaction)) then + ix.gui.editorFaction:Remove() + end +end + +function PANEL:updateVendor(key, value) + net.Start("ixVendorEdit") + net.WriteString(key) + net.WriteType(value) + net.SendToServer() +end + +vgui.Register("ixVendorEditor", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendorfaction.lua b/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendorfaction.lua new file mode 100644 index 0000000..eb19874 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/vendor/derma/cl_vendorfaction.lua @@ -0,0 +1,60 @@ + +local PANEL = {} + +function PANEL:Init() + self:SetSize(256, 280) + self:Center() + self:MakePopup() + self:SetTitle(L"vendorFaction") + self.scroll = self:Add("DScrollPanel") + self.scroll:Dock(FILL) + self.scroll:DockPadding(0, 0, 0, 4) + + self.factions = {} + self.classes = {} + + for k, v in ipairs(ix.faction.indices) do + local panel = self.scroll:Add("DPanel") + panel:Dock(TOP) + panel:DockPadding(4, 4, 4, 4) + panel:DockMargin(0, 0, 0, 4) + + local faction = panel:Add("DCheckBoxLabel") + faction:Dock(TOP) + faction:SetText(L(v.name)) + faction:DockMargin(0, 0, 0, 4) + faction.OnChange = function(this, state) + self:updateVendor("faction", v.uniqueID) + end + + self.factions[v.uniqueID] = faction + + for _, v2 in ipairs(ix.class.list) do + if (v2.faction == k) then + local class = panel:Add("DCheckBoxLabel") + class:Dock(TOP) + class:DockMargin(16, 0, 0, 4) + class:SetText(L(v2.name)) + class.OnChange = function(this, state) + self:updateVendor("class", v2.uniqueID) + end + + self.classes[v2.uniqueID] = class + + panel:SetTall(panel:GetTall() + class:GetTall() + 4) + end + end + end +end + +function PANEL:Setup() + for k, _ in pairs(self.entity.factions or {}) do + self.factions[k]:SetChecked(true) + end + + for k, _ in pairs(self.entity.classes or {}) do + self.classes[k]:SetChecked(true) + end +end + +vgui.Register("ixVendorFactionEditor", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/helix/plugins/vendor/entities/entities/ix_vendor.lua b/garrysmod/gamemodes/helix/plugins/vendor/entities/entities/ix_vendor.lua new file mode 100644 index 0000000..0d539c6 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/vendor/entities/entities/ix_vendor.lua @@ -0,0 +1,349 @@ + +ENT.Type = "anim" +ENT.PrintName = "Vendor" +ENT.Category = "Helix" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.isVendor = true +ENT.bNoPersist = true + +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "NoBubble") + self:NetworkVar("String", 0, "DisplayName") + self:NetworkVar("String", 1, "Description") +end + +function ENT:Initialize() + if (SERVER) then + self:SetModel("models/mossman.mdl") + self:SetUseType(SIMPLE_USE) + self:SetMoveType(MOVETYPE_NONE) + self:DrawShadow(true) + self:InitPhysObj() + + self:AddCallback("OnAngleChange", function(entity) + local mins, maxs = entity:GetAxisAlignedBoundingBox() + + entity:SetCollisionBounds(mins, maxs) + end) + + self.items = {} + self.messages = {} + self.factions = {} + self.classes = {} + + self:SetDisplayName("John Doe") + self:SetDescription("") + + self.receivers = {} + end + + timer.Simple(1, function() + if (IsValid(self)) then + self:SetAnim() + end + end) +end + +function ENT:InitPhysObj() + local mins, maxs = self:GetAxisAlignedBoundingBox() + local bPhysObjCreated = self:PhysicsInitBox(mins, maxs) + + if (bPhysObjCreated) then + local physObj = self:GetPhysicsObject() + physObj:EnableMotion(false) + physObj:Sleep() + end +end + +function ENT:GetAxisAlignedBoundingBox() + local mins, maxs = self:GetModelBounds() + mins = Vector(mins.x, mins.y, 0) + mins, maxs = self:GetRotatedAABB(mins, maxs) + + return mins, maxs +end + +function ENT:CanAccess(client) + local bAccess = false + local uniqueID = ix.faction.indices[client:Team()].uniqueID + + if (self.factions and !table.IsEmpty(self.factions)) then + if (self.factions[uniqueID]) then + bAccess = true + else + return false + end + end + + if (bAccess and self.classes and !table.IsEmpty(self.classes)) then + local class = ix.class.list[client:GetCharacter():GetClass()] + local classID = class and class.uniqueID + + if (classID and !self.classes[classID]) then + return false + end + end + + return true +end + +function ENT:GetStock(uniqueID) + if (self.items[uniqueID] and self.items[uniqueID][VENDOR_MAXSTOCK]) then + return self.items[uniqueID][VENDOR_STOCK] or 0, self.items[uniqueID][VENDOR_MAXSTOCK] + end +end + +function ENT:GetPrice(uniqueID, selling) + local price = ix.item.list[uniqueID] and self.items[uniqueID] and + self.items[uniqueID][VENDOR_PRICE] or ix.item.list[uniqueID].price or 0 + + if (selling) then + price = math.floor(price * (self.scale or 0.5)) + end + + return price +end + +function ENT:CanSellToPlayer(client, uniqueID) + local data = self.items[uniqueID] + + if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then + return false + end + + if (data[VENDOR_MODE] == VENDOR_BUYONLY) then + return false + end + + if (!client:GetCharacter():HasMoney(self:GetPrice(uniqueID))) then + return false + end + + if (data[VENDOR_STOCK] and data[VENDOR_STOCK] < 1) then + return false + end + + return true +end + +function ENT:CanBuyFromPlayer(client, uniqueID) + local data = self.items[uniqueID] + + if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then + return false + end + + if (data[VENDOR_MODE] != VENDOR_SELLONLY) then + return false + end + + if (!self:HasMoney(data[VENDOR_PRICE] or ix.item.list[uniqueID].price or 0)) then + return false + end + + return true +end + +function ENT:HasMoney(amount) + -- Vendor not using money system so they can always afford it. + if (!self.money) then + return true + end + + return self.money >= amount +end + +function ENT:SetAnim() + for k, v in ipairs(self:GetSequenceList()) do + if (v:lower():find("idle") and v != "idlenoise") then + return self:ResetSequence(k) + end + end + + if (self:GetSequenceCount() > 1) then + self:ResetSequence(4) + end +end + +if (SERVER) then + local PLUGIN = PLUGIN + + function ENT:SpawnFunction(client, trace) + local angles = (trace.HitPos - client:GetPos()):Angle() + angles.r = 0 + angles.p = 0 + angles.y = angles.y + 180 + + local entity = ents.Create("ix_vendor") + entity:SetPos(trace.HitPos) + entity:SetAngles(angles) + entity:Spawn() + + PLUGIN:SaveData() + + return entity + end + + function ENT:Use(activator) + local character = activator:GetCharacter() + + if (!self:CanAccess(activator) or hook.Run("CanPlayerUseVendor", activator, self) == false) then + if (self.messages[VENDOR_NOTRADE]) then + activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_NOTRADE]) + else + activator:NotifyLocalized("vendorNoTrade") + end + + return + end + + self.receivers[#self.receivers + 1] = activator + + if (self.messages[VENDOR_WELCOME]) then + activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_WELCOME]) + end + + local items = {} + + -- Only send what is needed. + for k, v in pairs(self.items) do + if (!table.IsEmpty(v) and (CAMI.PlayerHasAccess(activator, "Helix - Manage Vendors", nil) or v[VENDOR_MODE])) then + items[k] = v + end + end + + self.scale = self.scale or 0.5 + + activator.ixVendor = self + + -- force sync to prevent outdated inventories while buying/selling + if (character) then + character:GetInventory():Sync(activator, true) + end + + net.Start("ixVendorOpen") + net.WriteEntity(self) + net.WriteUInt(self.money or 0, 16) + net.WriteTable(items) + net.WriteFloat(self.scale or 0.5) + net.Send(activator) + + ix.log.Add(activator, "vendorUse", self:GetDisplayName()) + end + + function ENT:SetMoney(value) + self.money = value + + net.Start("ixVendorMoney") + net.WriteUInt(value and value or -1, 16) + net.Send(self.receivers) + end + + function ENT:GiveMoney(value) + if (self.money) then + self:SetMoney(self:GetMoney() + value) + end + end + + function ENT:TakeMoney(value) + if (self.money) then + self:GiveMoney(-value) + end + end + + function ENT:SetStock(uniqueID, value) + if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then + return + end + + self.items[uniqueID] = self.items[uniqueID] or {} + self.items[uniqueID][VENDOR_STOCK] = math.min(value, self.items[uniqueID][VENDOR_MAXSTOCK]) + + net.Start("ixVendorStock") + net.WriteString(uniqueID) + net.WriteUInt(value, 16) + net.Send(self.receivers) + end + + function ENT:AddStock(uniqueID, value) + if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then + return + end + + self:SetStock(uniqueID, self:GetStock(uniqueID) + (value or 1)) + end + + function ENT:TakeStock(uniqueID, value) + if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then + return + end + + self:AddStock(uniqueID, -(value or 1)) + end +else + function ENT:CreateBubble() + self.bubble = ClientsideModel("models/extras/info_speech.mdl", RENDERGROUP_OPAQUE) + self.bubble:SetPos(self:GetPos() + Vector(0, 0, 84)) + self.bubble:SetModelScale(0.6, 0) + end + + function ENT:Draw() + local bubble = self.bubble + + if (IsValid(bubble)) then + local realTime = RealTime() + + bubble:SetRenderOrigin(self:GetPos() + Vector(0, 0, 84 + math.sin(realTime * 3) * 0.05)) + bubble:SetRenderAngles(Angle(0, realTime * 100, 0)) + end + + self:DrawModel() + end + + function ENT:Think() + local noBubble = self:GetNoBubble() + + if (IsValid(self.bubble) and noBubble) then + self.bubble:Remove() + elseif (!IsValid(self.bubble) and !noBubble) then + self:CreateBubble() + end + + if ((self.nextAnimCheck or 0) < CurTime()) then + self:SetAnim() + self.nextAnimCheck = CurTime() + 60 + end + + self:SetNextClientThink(CurTime() + 0.25) + + return true + end + + function ENT:OnRemove() + if (IsValid(self.bubble)) then + self.bubble:Remove() + end + end + + ENT.PopulateEntityInfo = true + + function ENT:OnPopulateEntityInfo(container) + local name = container:AddRow("name") + name:SetImportant() + name:SetText(self:GetDisplayName()) + name:SizeToContents() + + local descriptionText = self:GetDescription() + + if (descriptionText != "") then + local description = container:AddRow("description") + description:SetText(self:GetDescription()) + description:SizeToContents() + end + end +end + +function ENT:GetMoney() + return self.money +end diff --git a/garrysmod/gamemodes/helix/plugins/vendor/sh_plugin.lua b/garrysmod/gamemodes/helix/plugins/vendor/sh_plugin.lua new file mode 100644 index 0000000..8687530 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/vendor/sh_plugin.lua @@ -0,0 +1,711 @@ + +-- luacheck: globals VENDOR_BUY VENDOR_SELL VENDOR_BOTH VENDOR_WELCOME VENDOR_LEAVE VENDOR_NOTRADE VENDOR_PRICE +-- luacheck: globals VENDOR_STOCK VENDOR_MODE VENDOR_MAXSTOCK VENDOR_SELLANDBUY VENDOR_SELLONLY VENDOR_BUYONLY VENDOR_TEXT + +local PLUGIN = PLUGIN + +PLUGIN.name = "Vendors" +PLUGIN.author = "Chessnut" +PLUGIN.description = "Adds NPC vendors that can sell things." + +CAMI.RegisterPrivilege({ + Name = "Helix - Manage Vendors", + MinAccess = "admin" +}) + +VENDOR_BUY = 1 +VENDOR_SELL = 2 +VENDOR_BOTH = 3 + +-- Keys for vendor messages. +VENDOR_WELCOME = 1 +VENDOR_LEAVE = 2 +VENDOR_NOTRADE = 3 + +-- Keys for item information. +VENDOR_PRICE = 1 +VENDOR_STOCK = 2 +VENDOR_MODE = 3 +VENDOR_MAXSTOCK = 4 + +-- Sell and buy the item. +VENDOR_SELLANDBUY = 1 +-- Only sell the item to the player. +VENDOR_SELLONLY = 2 +-- Only buy the item from the player. +VENDOR_BUYONLY = 3 + +if (SERVER) then + util.AddNetworkString("ixVendorOpen") + util.AddNetworkString("ixVendorClose") + util.AddNetworkString("ixVendorTrade") + + util.AddNetworkString("ixVendorEdit") + util.AddNetworkString("ixVendorEditFinish") + util.AddNetworkString("ixVendorEditor") + util.AddNetworkString("ixVendorMoney") + util.AddNetworkString("ixVendorStock") + util.AddNetworkString("ixVendorAddItem") + + function PLUGIN:SaveData() + local data = {} + + for _, entity in ipairs(ents.FindByClass("ix_vendor")) do + local bodygroups = {} + + for _, v in ipairs(entity:GetBodyGroups() or {}) do + bodygroups[v.id] = entity:GetBodygroup(v.id) + end + + data[#data + 1] = { + name = entity:GetDisplayName(), + description = entity:GetDescription(), + pos = entity:GetPos(), + angles = entity:GetAngles(), + model = entity:GetModel(), + skin = entity:GetSkin(), + bodygroups = bodygroups, + bubble = entity:GetNoBubble(), + items = entity.items, + factions = entity.factions, + classes = entity.classes, + money = entity.money, + scale = entity.scale + } + end + + self:SetData(data) + end + + function PLUGIN:LoadData() + for _, v in ipairs(self:GetData() or {}) do + local entity = ents.Create("ix_vendor") + entity:SetPos(v.pos) + entity:SetAngles(v.angles) + entity:Spawn() + + entity:SetModel(v.model) + entity:SetSkin(v.skin or 0) + entity:InitPhysObj() + + entity:SetNoBubble(v.bubble) + entity:SetDisplayName(v.name) + entity:SetDescription(v.description) + + for id, bodygroup in pairs(v.bodygroups or {}) do + entity:SetBodygroup(id, bodygroup) + end + + local items = {} + + for uniqueID, data in pairs(v.items) do + items[tostring(uniqueID)] = data + end + + entity.items = items + entity.factions = v.factions or {} + entity.classes = v.classes or {} + entity.money = v.money + entity.scale = v.scale or 0.5 + end + end + + function PLUGIN:CanVendorSellItem(client, vendor, itemID) + local tradeData = vendor.items[itemID] + local char = client:GetCharacter() + + if (!tradeData or !char) then + return false + end + + if (!char:HasMoney(tradeData[1] or 0)) then + return false + end + + return true + end + + ix.log.AddType("vendorUse", function(client, ...) + local arg = {...} + return string.format("%s used the '%s' vendor.", client:Name(), arg[1]) + end) + + ix.log.AddType("vendorBuy", function(client, ...) + local arg = {...} + + return string.format("%s purchased a '%s' from the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3]) + end) + + ix.log.AddType("vendorSell", function(client, ...) + local arg = {...} + + return string.format("%s sold a '%s' to the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3]) + end) + + net.Receive("ixVendorClose", function(length, client) + local entity = client.ixVendor + + if (IsValid(entity)) then + for k, v in ipairs(entity.receivers) do + if (v == client) then + table.remove(entity.receivers, k) + + break + end + end + + client.ixVendor = nil + end + end) + + local function UpdateEditReceivers(receivers, key, value) + net.Start("ixVendorEdit") + net.WriteString(key) + net.WriteType(value) + net.Send(receivers) + end + + net.Receive("ixVendorEdit", function(length, client) + if (!CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil)) then + return + end + + local entity = client.ixVendor + + if (!IsValid(entity)) then + return + end + + local key = net.ReadString() + local data = net.ReadType() + local feedback = true + + if (key == "name") then + entity:SetDisplayName(data) + elseif (key == "description") then + entity:SetDescription(data) + elseif (key == "bubble") then + entity:SetNoBubble(data) + elseif (key == "mode") then + local uniqueID = data[1] + + entity.items[uniqueID] = entity.items[uniqueID] or {} + entity.items[uniqueID][VENDOR_MODE] = data[2] + + UpdateEditReceivers(entity.receivers, key, data) + elseif (key == "price") then + local uniqueID = data[1] + data[2] = tonumber(data[2]) + + if (data[2]) then + data[2] = math.Round(data[2]) + end + + entity.items[uniqueID] = entity.items[uniqueID] or {} + entity.items[uniqueID][VENDOR_PRICE] = data[2] + + UpdateEditReceivers(entity.receivers, key, data) + + data = uniqueID + elseif (key == "stockDisable") then + local uniqueID = data[1] + + entity.items[data] = entity.items[uniqueID] or {} + entity.items[data][VENDOR_MAXSTOCK] = nil + + UpdateEditReceivers(entity.receivers, key, data) + elseif (key == "stockMax") then + local uniqueID = data[1] + data[2] = math.max(math.Round(tonumber(data[2]) or 1), 1) + + entity.items[uniqueID] = entity.items[uniqueID] or {} + entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2] + entity.items[uniqueID][VENDOR_STOCK] = math.Clamp(entity.items[uniqueID][VENDOR_STOCK] or data[2], 1, data[2]) + + data[3] = entity.items[uniqueID][VENDOR_STOCK] + + UpdateEditReceivers(entity.receivers, key, data) + + data = uniqueID + elseif (key == "stock") then + local uniqueID = data[1] + + entity.items[uniqueID] = entity.items[uniqueID] or {} + + if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then + data[2] = math.max(math.Round(tonumber(data[2]) or 0), 0) + entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2] + end + + data[2] = math.Clamp(math.Round(tonumber(data[2]) or 0), 0, entity.items[uniqueID][VENDOR_MAXSTOCK]) + entity.items[uniqueID][VENDOR_STOCK] = data[2] + + UpdateEditReceivers(entity.receivers, key, data) + + data = uniqueID + elseif (key == "faction") then + local faction = ix.faction.teams[data] + + if (faction) then + entity.factions[data] = !entity.factions[data] + + if (!entity.factions[data]) then + entity.factions[data] = nil + end + end + + local uniqueID = data + data = {uniqueID, entity.factions[uniqueID]} + elseif (key == "class") then + local class + + for _, v in ipairs(ix.class.list) do + if (v.uniqueID == data) then + class = v + + break + end + end + + if (class) then + entity.classes[data] = !entity.classes[data] + + if (!entity.classes[data]) then + entity.classes[data] = nil + end + end + + local uniqueID = data + data = {uniqueID, entity.classes[uniqueID]} + elseif (key == "model") then + entity:SetModel(data) + entity:InitPhysObj() + entity:SetAnim() + elseif (key == "useMoney") then + if (entity.money) then + entity:SetMoney() + else + entity:SetMoney(0) + end + elseif (key == "money") then + data = math.Round(math.abs(tonumber(data) or 0)) + + entity:SetMoney(data) + feedback = false + elseif (key == "scale") then + data = tonumber(data) or 0.5 + + entity.scale = data + + UpdateEditReceivers(entity.receivers, key, data) + end + + PLUGIN:SaveData() + + if (feedback) then + local receivers = {} + + for _, v in ipairs(entity.receivers) do + if (CAMI.PlayerHasAccess(v, "Helix - Manage Vendors", nil)) then + receivers[#receivers + 1] = v + end + end + + net.Start("ixVendorEditFinish") + net.WriteString(key) + net.WriteType(data) + net.Send(receivers) + end + end) + + net.Receive("ixVendorTrade", function(length, client) + if ((client.ixVendorTry or 0) < CurTime()) then + client.ixVendorTry = CurTime() + 0.33 + else + return + end + + local entity = client.ixVendor + + if (!IsValid(entity) or client:GetPos():Distance(entity:GetPos()) > 192) then + return + end + + if (!entity:CanAccess(client)) then + return + end + + local uniqueID = net.ReadString() + local isSellingToVendor = net.ReadBool() + + if (entity.items[uniqueID] and + hook.Run("CanPlayerTradeWithVendor", client, entity, uniqueID, isSellingToVendor) != false) then + local price = entity:GetPrice(uniqueID, isSellingToVendor) + + if (isSellingToVendor) then + local found = false + local name + + if (!entity:HasMoney(price)) then + return client:NotifyLocalized("vendorNoMoney") + end + + local stock, max = entity:GetStock(uniqueID) + + if (stock and stock >= max) then + return client:NotifyLocalized("vendorMaxStock") + end + + local invOkay = true + + for k, _ in client:GetCharacter():GetInventory():Iter() do + if (k.uniqueID == uniqueID and k:GetID() != 0 and ix.item.instances[k:GetID()] and k:GetData("equip", false) == false) then + invOkay = k:Remove() + found = true + name = L(k.name, client) + + break + end + end + + if (!found) then + return + end + + if (!invOkay) then + client:GetCharacter():GetInventory():Sync(client, true) + return client:NotifyLocalized("tellAdmin", "trd!iid") + end + + client:GetCharacter():GiveMoney(price, price == 0) + client:NotifyLocalized("businessSell", name, ix.currency.Get(price)) + entity:TakeMoney(price) + entity:AddStock(uniqueID) + + ix.log.Add(client, "vendorSell", name, entity:GetDisplayName(), ix.currency.Get(price)) + else + local stock = entity:GetStock(uniqueID) + + if (stock and stock < 1) then + return client:NotifyLocalized("vendorNoStock") + end + + if (!client:GetCharacter():HasMoney(price)) then + return client:NotifyLocalized("canNotAfford") + end + + if !entity:CanSellToPlayer(client, uniqueID) then + return false + end + + local name = L(ix.item.list[uniqueID].name, client) + + client:GetCharacter():TakeMoney(price, price == 0) + client:NotifyLocalized("businessPurchase", name, ix.currency.Get(price)) + + entity:GiveMoney(price) + + if (!client:GetCharacter():GetInventory():Add(uniqueID)) then + ix.item.Spawn(uniqueID, client) + else + net.Start("ixVendorAddItem") + net.WriteString(uniqueID) + net.Send(client) + end + + entity:TakeStock(uniqueID) + + ix.log.Add(client, "vendorBuy", name, entity:GetDisplayName(), ix.currency.Get(price)) + end + + PLUGIN:SaveData() + hook.Run("CharacterVendorTraded", client, entity, uniqueID, isSellingToVendor) + else + client:NotifyLocalized("vendorNoTrade") + end + end) +else + VENDOR_TEXT = {} + VENDOR_TEXT[VENDOR_SELLANDBUY] = "vendorBoth" + VENDOR_TEXT[VENDOR_BUYONLY] = "vendorBuy" + VENDOR_TEXT[VENDOR_SELLONLY] = "vendorSell" + + net.Receive("ixVendorOpen", function() + local entity = net.ReadEntity() + + if (!IsValid(entity)) then + return + end + + entity.money = net.ReadUInt(16) + entity.items = net.ReadTable() + entity.scale = net.ReadFloat() + + ix.gui.vendor = vgui.Create("ixVendor") + ix.gui.vendor:SetReadOnly(false) + ix.gui.vendor:Setup(entity) + end) + + net.Receive("ixVendorEditor", function() + local entity = net.ReadEntity() + + if (!IsValid(entity) or !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Vendors", nil)) then + return + end + + entity.money = net.ReadUInt(16) + entity.items = net.ReadTable() + entity.scale = net.ReadFloat() + entity.messages = net.ReadTable() + entity.factions = net.ReadTable() + entity.classes = net.ReadTable() + + ix.gui.vendor = vgui.Create("ixVendor") + ix.gui.vendor:SetReadOnly(true) + ix.gui.vendor:Setup(entity) + ix.gui.vendorEditor = vgui.Create("ixVendorEditor") + end) + + net.Receive("ixVendorEdit", function() + local panel = ix.gui.vendor + + if (!IsValid(panel)) then + return + end + + local entity = panel.entity + + if (!IsValid(entity)) then + return + end + + local key = net.ReadString() + local data = net.ReadType() + + if (key == "mode") then + entity.items[data[1]] = entity.items[data[1]] or {} + entity.items[data[1]][VENDOR_MODE] = data[2] + + if (!data[2]) then + panel:removeItem(data[1]) + elseif (data[2] == VENDOR_SELLANDBUY) then + panel:addItem(data[1]) + else + panel:addItem(data[1], data[2] == VENDOR_SELLONLY and "selling" or "buying") + panel:removeItem(data[1], data[2] == VENDOR_SELLONLY and "buying" or "selling") + end + elseif (key == "price") then + local uniqueID = data[1] + + entity.items[uniqueID] = entity.items[uniqueID] or {} + entity.items[uniqueID][VENDOR_PRICE] = tonumber(data[2]) + elseif (key == "stockDisable") then + if (entity.items[data]) then + entity.items[data][VENDOR_MAXSTOCK] = nil + end + elseif (key == "stockMax") then + local uniqueID = data[1] + local value = data[2] + local current = data[3] + + entity.items[uniqueID] = entity.items[uniqueID] or {} + entity.items[uniqueID][VENDOR_MAXSTOCK] = value + entity.items[uniqueID][VENDOR_STOCK] = current + elseif (key == "stock") then + local uniqueID = data[1] + local value = data[2] + + entity.items[uniqueID] = entity.items[uniqueID] or {} + + if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then + entity.items[uniqueID][VENDOR_MAXSTOCK] = value + end + + entity.items[uniqueID][VENDOR_STOCK] = value + elseif (key == "scale") then + entity.scale = data + end + end) + + net.Receive("ixVendorEditFinish", function() + local panel = ix.gui.vendor + local editor = ix.gui.vendorEditor + + if (!IsValid(panel) or !IsValid(editor)) then + return + end + + local entity = panel.entity + + if (!IsValid(entity)) then + return + end + + local key = net.ReadString() + local data = net.ReadType() + + if (key == "name") then + editor.name:SetText(data) + elseif (key == "description") then + editor.description:SetText(data) + elseif (key == "bubble") then + editor.bubble.noSend = true + editor.bubble:SetValue(data and 1 or 0) + elseif (key == "mode") then + if (data[2] == nil) then + editor.lines[data[1]]:SetValue(3, L"none") + else + editor.lines[data[1]]:SetValue(3, L(VENDOR_TEXT[data[2]])) + end + elseif (key == "price") then + editor.lines[data]:SetValue(4, entity:GetPrice(data)) + elseif (key == "stockDisable") then + editor.lines[data]:SetValue(5, "-") + elseif (key == "stockMax" or key == "stock") then + local current, max = entity:GetStock(data) + + editor.lines[data]:SetValue(5, current.."/"..max) + elseif (key == "faction") then + local uniqueID = data[1] + local state = data[2] + local editPanel = ix.gui.editorFaction + + entity.factions[uniqueID] = state + + if (IsValid(editPanel) and IsValid(editPanel.factions[uniqueID])) then + editPanel.factions[uniqueID]:SetChecked(state == true) + end + elseif (key == "class") then + local uniqueID = data[1] + local state = data[2] + local editPanel = ix.gui.editorFaction + + entity.classes[uniqueID] = state + + if (IsValid(editPanel) and IsValid(editPanel.classes[uniqueID])) then + editPanel.classes[uniqueID]:SetChecked(state == true) + end + elseif (key == "model") then + editor.model:SetText(entity:GetModel()) + elseif (key == "scale") then + editor.sellScale.noSend = true + editor.sellScale:SetValue(data) + end + + surface.PlaySound("buttons/button14.wav") + end) + + net.Receive("ixVendorMoney", function() + local panel = ix.gui.vendor + + if (!IsValid(panel)) then + return + end + + local entity = panel.entity + + if (!IsValid(entity)) then + return + end + + local value = net.ReadUInt(16) + value = value != -1 and value or nil + + entity.money = value + + local editor = ix.gui.vendorEditor + + if (IsValid(editor)) then + local useMoney = tonumber(value) != nil + + editor.money:SetDisabled(!useMoney) + editor.money:SetEnabled(useMoney) + editor.money:SetText(useMoney and value or "∞") + end + end) + + net.Receive("ixVendorStock", function() + local panel = ix.gui.vendor + + if (!IsValid(panel)) then + return + end + + local entity = panel.entity + + if (!IsValid(entity)) then + return + end + + local uniqueID = net.ReadString() + local amount = net.ReadUInt(16) + + entity.items[uniqueID] = entity.items[uniqueID] or {} + entity.items[uniqueID][VENDOR_STOCK] = amount + + local editor = ix.gui.vendorEditor + + if (IsValid(editor)) then + local _, max = entity:GetStock(uniqueID) + + editor.lines[uniqueID]:SetValue(4, amount .. "/" .. max) + end + end) + + net.Receive("ixVendorAddItem", function() + local uniqueID = net.ReadString() + + if (IsValid(ix.gui.vendor)) then + ix.gui.vendor:addItem(uniqueID, "buying") + end + end) +end + +properties.Add("vendor_edit", { + MenuLabel = "Edit Vendor", + Order = 999, + MenuIcon = "icon16/user_edit.png", + + Filter = function(self, entity, client) + if (!IsValid(entity)) then return false end + if (entity:GetClass() != "ix_vendor") then return false end + if (!gamemode.Call( "CanProperty", client, "vendor_edit", entity)) then return false end + + return CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil) + end, + + Action = function(self, entity) + self:MsgStart() + net.WriteEntity(entity) + self:MsgEnd() + end, + + Receive = function(self, length, client) + local entity = net.ReadEntity() + + if (!IsValid(entity)) then return end + if (!self:Filter(entity, client)) then return end + + entity.receivers[#entity.receivers + 1] = client + + local itemsTable = {} + + for k, v in pairs(entity.items) do + if (!table.IsEmpty(v)) then + itemsTable[k] = v + end + end + + client.ixVendor = entity + + net.Start("ixVendorEditor") + net.WriteEntity(entity) + net.WriteUInt(entity.money or 0, 16) + net.WriteTable(itemsTable) + net.WriteFloat(entity.scale or 0.5) + net.WriteTable(entity.messages) + net.WriteTable(entity.factions) + net.WriteTable(entity.classes) + net.Send(client) + end +}) diff --git a/garrysmod/gamemodes/helix/plugins/wepselect.lua b/garrysmod/gamemodes/helix/plugins/wepselect.lua new file mode 100644 index 0000000..fadc566 --- /dev/null +++ b/garrysmod/gamemodes/helix/plugins/wepselect.lua @@ -0,0 +1,209 @@ + +PLUGIN.name = "Weapon Select" +PLUGIN.author = "Chessnut" +PLUGIN.description = "A replacement for the default weapon selection." + +if (CLIENT) then + PLUGIN.index = PLUGIN.index or 1 + PLUGIN.deltaIndex = PLUGIN.deltaIndex or PLUGIN.index + PLUGIN.infoAlpha = PLUGIN.infoAlpha or 0 + PLUGIN.alpha = PLUGIN.alpha or 0 + PLUGIN.alphaDelta = PLUGIN.alphaDelta or PLUGIN.alpha + PLUGIN.fadeTime = PLUGIN.fadeTime or 0 + + local matrixScale = Vector(1, 1, 0) + + function PLUGIN:LoadFonts(font, genericFont) + surface.CreateFont("ixWeaponSelectFont", { + font = font, + size = ScreenScale(16), + extended = true, + weight = 1000 + }) + end + + function PLUGIN:HUDShouldDraw(name) + if (name == "CHudWeaponSelection") then + return false + end + end + + function PLUGIN:HUDPaint() + local frameTime = FrameTime() + + self.alphaDelta = Lerp(frameTime * 10, self.alphaDelta, self.alpha) + + local fraction = self.alphaDelta + + if (fraction > 0.01) then + local x, y = ScrW() * 0.5, ScrH() * 0.5 + local spacing = math.pi * 0.85 + local radius = 240 * self.alphaDelta + local shiftX = ScrW() * .02 + + self.deltaIndex = Lerp(frameTime * 12, self.deltaIndex, self.index) + + local weapons = LocalPlayer():GetWeapons() + local index = self.deltaIndex + + if (!weapons[self.index]) then + self.index = #weapons + end + + for i = 1, #weapons do + local theta = (i - index) * 0.1 + local color = ColorAlpha( + i == self.index and ix.config.Get("color") or color_white, + (255 - math.abs(theta * 3) * 255) * fraction + ) + + local lastY = 0 + + if (self.markup and (i < self.index or i == 1)) then + if (self.index != 1) then + local _, h = self.markup:Size() + lastY = h * fraction + end + + if (i == 1 or i == self.index - 1) then + self.infoAlpha = Lerp(frameTime * 3, self.infoAlpha, 255) + self.markup:Draw(x + 6 + shiftX, y + 30, 0, 0, self.infoAlpha * fraction) + end + end + + surface.SetFont("ixWeaponSelectFont") + local weaponName = language.GetPhrase(weapons[i]:GetPrintName()):utf8upper() + local _, ty = surface.GetTextSize(weaponName) + local scale = 1 - math.abs(theta * 2) + + local matrix = Matrix() + matrix:Translate(Vector( + shiftX + x + math.cos(theta * spacing + math.pi) * radius + radius, + y + lastY + math.sin(theta * spacing + math.pi) * radius - ty / 2 , + 1)) + matrix:Scale(matrixScale * scale) + + cam.PushModelMatrix(matrix) + ix.util.DrawText(weaponName, 2, ty / 2, color, 0, 1, "ixWeaponSelectFont") + cam.PopModelMatrix() + end + + if (self.fadeTime < CurTime() and self.alpha > 0) then + self.alpha = 0 + end + end + end + + function PLUGIN:OnIndexChanged(weapon) + self.alpha = 1 + self.fadeTime = CurTime() + 5 + self.markup = nil + + if (IsValid(weapon)) then + local instructions = weapon.Instructions + local text = "" + + if (instructions != nil and instructions:find("%S")) then + local color = ix.config.Get("color") + text = text .. string.format( + "%s\n%s\n", + color.r, color.g, color.b, L("Instructions"), instructions + ) + end + + if (text != "") then + self.markup = markup.Parse(""..text, ScrW() * 0.3) + self.infoAlpha = 0 + end + + local source, pitch = hook.Run("WeaponCycleSound") + LocalPlayer():EmitSound(source or "common/talk.wav", 50, pitch or 180) + end + end + + function PLUGIN:PlayerBindPress(client, bind, pressed) + bind = bind:lower() + + // Блокировка селектора при изменении обвесов CW 2.0 + -- if client:Alive() then + -- local wep = client:GetActiveWeapon() + -- if weapons.IsBasedOn(wep:GetClass(), "cw_base") then + -- if wep.dt.State == CW_CUSTOMIZE then return end + -- end + -- end + + + if (!pressed or !bind:find("invprev") and !bind:find("invnext") + and !bind:find("slot") and !bind:find("attack")) then + return + end + + local currentWeapon = client:GetActiveWeapon() + local bValid = IsValid(currentWeapon) + local bTool + + if (client:InVehicle() or (bValid and currentWeapon:GetClass() == "weapon_physgun" and client:KeyDown(IN_ATTACK))) then + return + end + + if (bValid and currentWeapon:GetClass() == "gmod_tool") then + local tool = client:GetTool() + bTool = tool and (tool.Scroll != nil) + end + + local weapons = client:GetWeapons() + + if (bind:find("invprev") and !bTool) then + local oldIndex = self.index + self.index = math.min(self.index + 1, #weapons) + + if (self.alpha == 0 or oldIndex != self.index) then + self:OnIndexChanged(weapons[self.index]) + end + + return true + elseif (bind:find("invnext") and !bTool) then + local oldIndex = self.index + self.index = math.max(self.index - 1, 1) + + if (self.alpha == 0 or oldIndex != self.index) then + self:OnIndexChanged(weapons[self.index]) + end + + return true + elseif (bind:find("slot")) then + self.index = math.Clamp(tonumber(bind:match("slot(%d)")) or 1, 1, #weapons) + self:OnIndexChanged(weapons[self.index]) + + return true + elseif (bind:find("attack") and self.alpha > 0) then + local weapon = weapons[self.index] + + if (IsValid(weapon)) then + LocalPlayer():EmitSound(hook.Run("WeaponSelectSound", weapon) or "HL2Player.Use") + + input.SelectWeapon(weapon) + self.alpha = 0 + end + + return true + end + end + + function PLUGIN:Think() + local client = LocalPlayer() + if (!IsValid(client) or !client:Alive()) then + self.alpha = 0 + end + end + + function PLUGIN:ScoreboardShow() + self.alpha = 0 + end + + function PLUGIN:ShouldPopulateEntityInfo(entity) + if (self.alpha > 0) then + return false + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/cl_init.lua b/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/cl_init.lua new file mode 100644 index 0000000..16167d5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/cl_init.lua @@ -0,0 +1,30 @@ +include("shared.lua") + +killicon.Add( "ent_lordi_rocket", "vgui/entities/ent_lordi_rocket", Color( 255, 255, 255, 255 ) ) + +--Couldnt be bothered to make a world model... This will hold as a placeholder for now + +function ENT:Initialize() + self.modelEnt = ClientsideModel("models/weapons/lordi/c_sledgehammer.mdl", RENDER_GROUP_OPAQUE) + + self.Spin_Ang = 0 +end + +function ENT:Draw() + +self.Spin_Ang = self.Spin_Ang + 10 +if (IsValid(self.modelEnt)) and self.modelEnt:LookupBone("sledge_hammer") then + self.modelEnt:SetPos(self:GetPos() + self:GetAngles():Right() * 20) + self.modelEnt:SetAngles(self:GetAngles()) + self.modelEnt:SetParent(self) + self.modelEnt:ManipulateBoneAngles(self.modelEnt:LookupBone("sledge_hammer"),Angle(174 + self.Spin_Ang,110,87)) + self.modelEnt:ManipulateBonePosition(self.modelEnt:LookupBone("sledge_hammer"),Vector(-20,27,8.6)) +end + +end + +function ENT:OnRemove() +if (IsValid(self.modelEnt)) then + self.modelEnt:Remove() +end +end diff --git a/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/init.lua b/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/init.lua new file mode 100644 index 0000000..3ebda26 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/init.lua @@ -0,0 +1,75 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +function ENT:Initialize() + self:SetModel("models/weapons/w_crowbar.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:EmitSound("npc/zombie/claw_miss1.wav",75,60) + local phys = self:GetPhysicsObject() + + if IsValid(phys) then phys:Wake() phys:EnableGravity(false) end + + util.SpriteTrail(self, 0, Color(255,255,255), false, 10, 1, 0.5, 8, "trails/smoke.vmt") +end + +function ENT:Think() + if self:GetVelocity():Length() <= 400 then + self:PhysicsCollide() + return + end + + self:EmitSound("weapons/slam/throw.wav") +end + +function ENT:Use(activator,caller) +end + +hook.Add("EntityTakeDamage","Sledgehammer_Kill",function(ply,dmginfo) + if IsValid(dmginfo:GetAttacker()) and dmginfo:GetAttacker():GetClass() and dmginfo:GetAttacker():GetClass() == "ent_lordi_sledgehammer" then + if IsValid(dmginfo:GetAttacker():GetOwner()) and dmginfo:GetAttacker():GetOwner():IsPlayer() then + dmginfo:SetAttacker(dmginfo:GetAttacker():GetOwner()) + end + end +end) + +function ENT:Touch( ent ) + if ent:IsValid() then + if ent:IsPlayer() or ent:IsNPC() then + for i=1,3 do + ent:EmitSound("physics/body/body_medium_break"..math.random(2,4)..".wav",75,math.random(70,90)) + local effectdata = EffectData() + effectdata:SetOrigin(ent:GetPos() + Vector(0,0,math.random(5,20))) + effectdata:SetEntity(ent) + util.Effect( "BloodImpact", effectdata ) + end + ent:TakeDamage(math.random(70,120),self) + end + end +end + +function ENT:PhysicsCollide(data,obj) + timer.Create("sledge_hammer_next_frame"..self:EntIndex(),0,1,function() + self:EmitSound("physics/metal/metal_barrel_impact_hard7.wav") + local sledge = ents.Create("weapon_lordi_sledgehammer") + sledge:SetPos(self:GetPos()) + sledge:SetAngles(self:GetAngles()) + sledge:Spawn() + + sledge:GetPhysicsObject():ApplyForceCenter( self:GetVelocity() * 15 ) + + self:Remove() + /* + local explode = ents.Create("env_explosion") + explode:SetPos( self:GetPos() ) + explode:SetOwner( self:GetOwner() ) + explode:Spawn() + explode:SetKeyValue("iMagnitude","125") + explode:Fire("Explode", 0, 0 ) + self:EmitSound("ambient/explosions/explode_"..math.random(1,5)..".wav") + self:Remove() + */ + end) +end diff --git a/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/shared.lua b/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/shared.lua new file mode 100644 index 0000000..7b4cc5a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/entities/ent_lordi_sledgehammer/shared.lua @@ -0,0 +1,7 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.Author = "LordiAnders" +ENT.Category = "[FT] Специальное Оружие" +ENT.PrintName = "Rocket" +ENT.Spawnable = false +ENT.AdminSpawnable = false diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/advdupe2.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/advdupe2.lua new file mode 100644 index 0000000..e5a9cac --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/advdupe2.lua @@ -0,0 +1,1951 @@ +--[[ + Title: Adv. Dupe 2 Tool + + Desc: Defines the AD2 tool and assorted functionalities. + + Author: TB + + Version: 1.0 +]] + +-- AdvDupe2 должен быть загружен плагином buildtools +-- Если он не загружен, инициализируем базовую структуру +AdvDupe2 = AdvDupe2 or {} + +TOOL.Category = "Construction" +TOOL.Name = "#Tool.advdupe2.name" +cleanup.Register( "AdvDupe2" ) +require( "controlpanel" ) + +if(SERVER) then + CreateConVar("sbox_maxgmod_contr_spawners",5) + + local phys_constraint_system_types = { + Weld = true, + Rope = true, + Elastic = true, + Slider = true, + Axis = true, + AdvBallsocket = true, + Motor = true, + Pulley = true, + Ballsocket = true, + Winch = true, + Hydraulic = true, + WireMotor = true, + WireHydraulic = true + } + --Orders constraints so that the dupe uses as little constraint systems as possible + local function GroupConstraintOrder( ply, constraints ) + --First separate the nocollides, sorted, and unsorted constraints + local sorted, unsorted = {}, {} + for k, v in pairs(constraints) do + if phys_constraint_system_types[v.Type] then + sorted[#sorted+1] = v + else + unsorted[#unsorted+1] = v + end + end + + local sortingSystems = {} + local fullSystems = {} + local function buildSystems(input) + while next(input) ~= nil do + for k, v in pairs(input) do + for systemi, system in pairs(sortingSystems) do + for _, target in pairs(system) do + for x = 1, 4 do + if v.Entity[x] then + for y = 1, 4 do + if target.Entity[y] and v.Entity[x].Index == target.Entity[y].Index then + system[#system + 1] = v + if #system == 100 then + fullSystems[#fullSystems + 1] = system + table.remove(sortingSystems, systemi) + end + input[k] = nil + goto super_loopbreak + end + end + end + end + end + end + end + + --Normally skipped by the goto unless no cluster is found. If so, make a new one. + local k = next(input) + sortingSystems[#sortingSystems + 1] = {input[k]} + input[k] = nil + + ::super_loopbreak:: + end + end + buildSystems(sorted) + + local ret = {} + for _, system in pairs(fullSystems) do + for _, v in pairs(system) do + ret[#ret + 1] = v + end + end + for _, system in pairs(sortingSystems) do + for _, v in pairs(system) do + ret[#ret + 1] = v + end + end + for k, v in pairs(unsorted) do + ret[#ret + 1] = v + end + + if #fullSystems ~= 0 then + ply:ChatPrint("DUPLICATOR: WARNING, Number of constraints exceeds 100: (".. #ret .."). Constraint sorting might not work as expected.") + end + + return ret + end + + local function CreationConstraintOrder( constraints ) + local ret = {} + for k, v in pairs( constraints ) do + ret[#ret + 1] = k + end + table.sort(ret) + for i = 1, #ret do + ret[i] = constraints[ret[i]] + end + return ret + end + + local function GetSortedConstraints( ply, constraints ) + if ply:GetInfo("advdupe2_sort_constraints") ~= "0" then + return GroupConstraintOrder( ply, constraints ) + else + return CreationConstraintOrder( constraints ) + end + end + + local areacopy_classblacklist = { + gmod_anchor = true + } + + local function PlayerCanDupeCPPI(ply, ent) + if not AdvDupe2.duplicator.IsCopyable(ent) or areacopy_classblacklist[ent:GetClass()] then return false end + return ent:CPPIGetOwner()==ply + end + + -- Code from WireLib.CanTool + local zero = Vector(0, 0, 0) + local norm = Vector(1, 0, 0) + + local tr = { ---@type TraceResult + Hit = true, HitNonWorld = true, HitNoDraw = false, HitSky = false, AllSolid = true, + HitNormal = zero, Normal = norm, + + Fraction = 1, FractionLeftSolid = 0, + HitBox = 0, HitGroup = 0, HitTexture = "**studio**", + MatType = 0, PhysicsBone = 0, SurfaceProps = 0, DispFlags = 0, Contents = 0, + + Entity = NULL, HitPos = zero, StartPos = zero, + } + + local function PlayerCanDupeTool(ply, ent) + if not AdvDupe2.duplicator.IsCopyable(ent) or areacopy_classblacklist[ent:GetClass()] then return false end + + local pos = ent:GetPos() + tr.Entity, tr.HitPos, tr.StartPos = ent, pos, pos + + return hook.Run( "CanTool", ply, tr, "advdupe2" ) ~= false + end + + --Find all the entities in a box, given the adjacent corners and the player + local function FindInBox(min, max, ply) + local PPCheck = (tobool(ply:GetInfo("advdupe2_copy_only_mine")) and ply.CPPIGetOwner~=nil) and PlayerCanDupeCPPI or PlayerCanDupeTool + local EntTable = {} + + for _, ent in ipairs(ents.FindInBox(min, max)) do + if PPCheck(ply, ent) then + EntTable[ent:EntIndex()] = ent + end + end + + return EntTable + end + + --[[ + Name: GetDupeAngleOffset + Desc: Retrieves duplication angle offsets from player + Returns: Created angle + ]] + local function GetDupeAngleOffset(ply) + local p = math.Clamp(ply:GetInfoNum("advdupe2_offset_pitch", 0), -180, 180) + local y = math.Clamp(ply:GetInfoNum("advdupe2_offset_yaw" , 0), -180, 180) + local r = math.Clamp(ply:GetInfoNum("advdupe2_offset_roll" , 0), -180, 180) + return Angle(p, y, r) + end + + --[[ + Name: GetDupeElevation + Desc: Retrieves duplication Z elevation + Returns: Dupe elevation + ]] + local function GetDupeElevation(ply) + local con = ply:GetInfoNum("advdupe2_offset_z", 0) + local enz = (tonumber(ply.AdvDupe2.HeadEnt.Z) or 0) + return math.Clamp(con + enz, -32000, 32000) + end + + --[[ + Name: LeftClick + Desc: Defines the tool's behavior when the player left-clicks. + Params: trace + Returns: success + ]] + local messages = { + ["Pasting"] = "pasting.", + ["Downloading"] = "downloading.", + ["Uploading"] = "uploading." + } + function TOOL:LeftClick( trace ) + if(not trace) then return false end + + local ply = self:GetOwner() + local dupe = ply.AdvDupe2 + + if not (dupe and dupe.Entities) then return false end + + for key, msg in pairs(messages) do + if dupe[key] then + AdvDupe2.Notify(ply, "Advanced Duplicator 2 is busy " .. msg, NOTIFY_ERROR) + return false + end + end + + dupe.Angle = GetDupeAngleOffset(ply) + dupe.Position = Vector(trace.HitPos) + dupe.Position.z = dupe.Position.z + GetDupeElevation(ply) + + if(tobool(ply:GetInfo("advdupe2_offset_world"))) then + dupe.Angle = dupe.Angle - dupe.Entities[dupe.HeadEnt.Index].PhysicsObjects[0].Angle + end + + dupe.Pasting = true + AdvDupe2.Notify(ply,"Pasting...") + local origin + if(tobool(ply:GetInfo("advdupe2_original_origin"))) then + origin = dupe.HeadEnt.Pos + end + + AdvDupe2.InitPastingQueue(ply, dupe.Position, dupe.Angle, origin, + tobool(ply:GetInfo("advdupe2_paste_constraints")), + tobool(ply:GetInfo("advdupe2_paste_parents")), + tobool(ply:GetInfo("advdupe2_paste_disparents")), + tobool(ply:GetInfo("advdupe2_paste_protectoveride"))) + + return true + end + + --[[ + Name: RightClick + Desc: Defines the tool's behavior when the player right-clicks. + Params: trace + Returns: success + ]] + function TOOL:RightClick( trace ) + local ply = self:GetOwner() + local dupe = ply.AdvDupe2 + + if not dupe then dupe = {}; ply.AdvDupe2 = dupe end + + if(dupe.Pasting or dupe.Downloading) then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.", NOTIFY_ERROR) + return false + end + + --Set Area Copy on or off + if( ply:KeyDown(IN_SPEED) and not ply:KeyDown(IN_WALK) ) then + if(self:GetStage()==0) then + AdvDupe2.DrawSelectBox(ply) + self:SetStage(1) + return false + elseif(self:GetStage()==1) then + AdvDupe2.RemoveSelectBox(ply) + self:SetStage(0) + return false + end + end + + if(not trace or not trace.Hit) then return false end + + local Entities, Constraints, AddOne + local HeadEnt = {} + --If area copy is on + if(self:GetStage()==1) then + local area_size = math.Clamp(tonumber(ply:GetInfo("advdupe2_area_copy_size")) or 50, 0, 30720) + local Pos = trace.HitNonWorld and trace.Entity:GetPos() or trace.HitPos + local T = (Vector(area_size,area_size,area_size)+Pos) + local B = (Vector(-area_size,-area_size,-area_size)+Pos) + + local Ents = FindInBox(B,T, ply) + local _, Ent = next(Ents) + if not Ent then + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + return true + end + + Ent = trace.HitNonWorld and trace.Entity or Ent + HeadEnt.Index = Ent:EntIndex() + HeadEnt.Pos = Ent:GetPos() + + Entities, Constraints = AdvDupe2.duplicator.AreaCopy(ply, Ents, HeadEnt.Pos, tobool(ply:GetInfo("advdupe2_copy_outside"))) + + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + elseif trace.HitNonWorld then --Area Copy is off + -- Filter duplicator blocked entities out. + if not AdvDupe2.duplicator.IsCopyable( trace.Entity ) then + return false + end + + --If Alt is being held, add a prop to the dupe + if(ply:KeyDown(IN_WALK) and dupe.Entities~=nil and next(dupe.Entities)~=nil) then + Entities = dupe.Entities + Constraints = dupe.Constraints + HeadEnt = dupe.HeadEnt + + AdvDupe2.duplicator.Copy( ply, trace.Entity, Entities, Constraints, HeadEnt.Pos) + + --Only add the one ghost + AddOne = Entities[trace.Entity:EntIndex()] + else + Entities = {} + Constraints = {} + HeadEnt.Index = trace.Entity:EntIndex() + HeadEnt.Pos = trace.HitPos + + AdvDupe2.duplicator.Copy( ply, trace.Entity, Entities, Constraints, trace.HitPos ) + end + else --Non valid entity or clicked the world + if dupe.Entities then + --clear the dupe + net.Start("AdvDupe2_RemoveGhosts") + net.Send(ply) + dupe.Entities = nil + dupe.Constraints = nil + net.Start("AdvDupe2_ResetDupeInfo") + net.Send(ply) + AdvDupe2.ResetOffsets(ply) + return true + else + --select all owned props + Entities = {} + local PPCheck = (tobool(ply:GetInfo("advdupe2_copy_only_mine")) and CPPI~=nil) and PlayerCanDupeCPPI or PlayerCanDupeTool + for _, ent in ents.Iterator() do + if PPCheck( ply, ent ) then + Entities[ent:EntIndex()] = ent + end + end + + local _, Ent = next(Entities) + if not Ent then + net.Start("AdvDupe2_RemoveGhosts") + net.Send(ply) + return true + end + + HeadEnt.Index = Ent:EntIndex() + HeadEnt.Pos = Ent:GetPos() + + Entities, Constraints = AdvDupe2.duplicator.AreaCopy(ply, Entities, HeadEnt.Pos, tobool(ply:GetInfo("advdupe2_copy_outside"))) + end + end + + if not HeadEnt.Z then + local WorldTrace = util.TraceLine({ + mask = MASK_NPCWORLDSTATIC, + start = HeadEnt.Pos + Vector(0,0,1), + endpos = HeadEnt.Pos-Vector(0,0,50000) + }) + + HeadEnt.Z = WorldTrace.Hit and math.abs(HeadEnt.Pos.Z - WorldTrace.HitPos.Z) or 0 + end + + dupe.HeadEnt = HeadEnt + dupe.Entities = Entities + dupe.Constraints = GetSortedConstraints(ply, Constraints) + dupe.Revision = AdvDupe2.CodecRevision + + net.Start("AdvDupe2_SetDupeInfo") + net.WriteString("") + net.WriteString(ply:Nick()) + net.WriteString(os.date("%d %B %Y")) + net.WriteString(os.date("%I:%M %p")) + net.WriteString("") + net.WriteString("") + net.WriteString(table.Count(dupe.Entities)) + net.WriteString(#dupe.Constraints) + net.Send(ply) + + if AddOne then + AdvDupe2.SendGhost(ply, AddOne) + else + AdvDupe2.SendGhosts(ply) + end + + AdvDupe2.ResetOffsets(ply) + + return true + end + + --Checks table, re-draws loading bar, and recreates ghosts when tool is pulled out + function TOOL:Deploy() + local ply = self:GetOwner() + local dupe = ply.AdvDupe2 + + if not dupe then dupe = {}; ply.AdvDupe2 = dupe end + + if(not dupe.Entities) then return end + + net.Start("AdvDupe2_StartGhosting") + net.Send(ply) + + if(dupe.Queued) then + AdvDupe2.InitProgressBar(ply, "Queued: ") + return + end + + if(dupe.Pasting) then + AdvDupe2.InitProgressBar(ply, "Pasting: ") + return + else + if(dupe.Uploading) then + AdvDupe2.InitProgressBar(ply, "Uploading: ") + return + elseif(dupe.Downloading) then + AdvDupe2.InitProgressBar(ply, "Saving: ") + return + end + end + + end + + --Removes progress bar + function TOOL:Holster() + AdvDupe2.RemoveProgressBar(self:GetOwner()) + end + + --[[ + Name: Reload + Desc: Creates an Advance Contraption Spawner. + Params: trace + Returns: success + ]] + function TOOL:Reload( trace ) + if(not trace.Hit) then return false end + + local ply = self:GetOwner() + local dupe = ply.AdvDupe2 + + if not dupe then dupe = {}; ply.AdvDupe2 = dupe end + + if(self:GetStage()==1) then + local areasize = math.Clamp(tonumber(ply:GetInfo("advdupe2_area_copy_size")) or 50, 0, 30720) + net.Start("AdvDupe2_CanAutoSave") + net.WriteVector(trace.HitPos) + net.WriteFloat(areasize) + if(trace.Entity) then + net.WriteUInt(trace.Entity:EntIndex(), 16) + else + net.WriteUInt(0, 16) + end + net.Send(ply) + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + dupe.TempAutoSavePos = trace.HitPos + dupe.TempAutoSaveSize = areasize + dupe.TempAutoSaveOutSide = tobool(ply:GetInfo("advdupe2_copy_outside")) + return true + end + + --If a contraption spawner was clicked then update it with the current settings + if(trace.Entity:GetClass()=="gmod_contr_spawner") then + local delay = tonumber(ply:GetInfo("advdupe2_contr_spawner_delay")) + local undo_delay = tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_delay")) + local min + local max + if(not delay) then + delay = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + else + if(not game.SinglePlayer()) then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + if (delay < min) then + delay = min + end + elseif(delay<0) then + delay = 0 + end + end + + if(not undo_delay) then + undo_delay = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) + else + if(not game.SinglePlayer()) then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) or 0.1 + max = tonumber(GetConVarString("AdvDupe2_MaxContraptionUndoDelay")) or 60 + if(undo_delay < min) then + undo_delay = min + elseif(undo_delay > max) then + undo_delay = max + end + elseif(undo_delay < 0) then + undo_delay = 0 + end + end + + trace.Entity:GetTable():SetOptions( + ply, delay, undo_delay, + tonumber(ply:GetInfo("advdupe2_contr_spawner_key")), + tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_key")), + tonumber(ply:GetInfo("advdupe2_contr_spawner_disgrav")) or 0, + tonumber(ply:GetInfo("advdupe2_contr_spawner_disdrag")) or 0, + tonumber(ply:GetInfo("advdupe2_contr_spawner_addvel")) or 1 ) + + return true + end + + --Create a contraption spawner + if dupe and dupe.Entities then + local headent = dupe.Entities[dupe.HeadEnt.Index] + local Pos, Ang + + if(headent) then + if(tobool(ply:GetInfo("advdupe2_original_origin"))) then + Pos = dupe.HeadEnt.Pos + headent.PhysicsObjects[0].Pos + Ang = headent.PhysicsObjects[0].Angle + else + local EntAngle = headent.PhysicsObjects[0].Angle + if(tobool(ply:GetInfo("advdupe2_offset_world"))) then EntAngle = Angle(0,0,0) end + trace.HitPos.Z = trace.HitPos.Z + GetDupeElevation(ply) + Pos, Ang = LocalToWorld(headent.PhysicsObjects[0].Pos, EntAngle, trace.HitPos, GetDupeAngleOffset(ply)) + end + else + AdvDupe2.Notify(ply, "Invalid head entity to spawn contraption spawner.") + return false + end + + if(headent.Class=="gmod_contr_spawner") then + AdvDupe2.Notify(ply, "Cannot make a contraption spawner from a contraption spawner.") + return false + end + + + local spawner = MakeContraptionSpawner( + ply, Pos, Ang, dupe.HeadEnt.Index, + table.Copy(dupe.Entities), + table.Copy(dupe.Constraints), + tonumber(ply:GetInfo("advdupe2_contr_spawner_delay")), + tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_delay")), headent.Model, + tonumber(ply:GetInfo("advdupe2_contr_spawner_key")), + tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_key")), + tonumber(ply:GetInfo("advdupe2_contr_spawner_disgrav")) or 0, + tonumber(ply:GetInfo("advdupe2_contr_spawner_disdrag")) or 0, + tonumber(ply:GetInfo("advdupe2_contr_spawner_addvel")) or 1, + tonumber(ply:GetInfo("advdupe2_contr_spawner_hideprops")) or 0) + + if not spawner then + return false + end + + local undotxt = "AdvDupe2: Spawner ["..tostring(spawner:EntIndex()).."]"..(dupe.Name and "("..tostring(dupe.Name)..")" or "") + + ply:AddCleanup( "AdvDupe2", spawner ) + undo.Create( undotxt ) + undo.AddEntity( spawner ) + undo.SetPlayer( ply ) + undo.Finish() + + return true + end + end + + --Called to clean up the tool when pasting is finished or undo during pasting + function AdvDupe2.FinishPasting(Player, Paste) + Player.AdvDupe2.Pasting=false + AdvDupe2.RemoveProgressBar(Player) + if(Paste) then AdvDupe2.Notify(Player,"Finished Pasting!") end + end + + --function for creating a contraption spawner + function MakeContraptionSpawner( ply, Pos, Ang, HeadEnt, EntityTable, ConstraintTable, delay, undo_delay, model, key, undo_key, disgrav, disdrag, addvel, hideprops) + + if not ply:CheckLimit("gmod_contr_spawners") then return nil end + + if(not game.SinglePlayer()) then + if(table.Count(EntityTable)>tonumber(GetConVarString("AdvDupe2_MaxContraptionEntities"))) then + AdvDupe2.Notify(ply,"Contraption Spawner exceeds the maximum amount of "..GetConVarString("AdvDupe2_MaxContraptionEntities").." entities for a spawner!",NOTIFY_ERROR) + return false + end + if(#ConstraintTable>tonumber(GetConVarString("AdvDupe2_MaxContraptionConstraints"))) then + AdvDupe2.Notify(ply,"Contraption Spawner exceeds the maximum amount of "..GetConVarString("AdvDupe2_MaxContraptionConstraints").." constraints for a spawner!",NOTIFY_ERROR) + return false + end + end + + local spawner = ents.Create("gmod_contr_spawner") + if not IsValid(spawner) then return end + + spawner:SetPos(Pos) + spawner:SetAngles(Ang) + spawner:SetModel(model) + spawner:SetRenderMode(RENDERMODE_TRANSALPHA) + spawner:SetCreator(ply) + spawner:Spawn() + + duplicator.ApplyEntityModifiers(ply, spawner) + + if IsValid(spawner:GetPhysicsObject()) then + spawner:GetPhysicsObject():EnableMotion(false) + end + + local min + local max + if(not delay) then + delay = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + else + if(not game.SinglePlayer()) then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + if (delay < min) then + delay = min + end + elseif(delay<0) then + delay = 0 + end + end + + if(not undo_delay) then + undo_delay = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) + else + if(not game.SinglePlayer()) then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) or 0.1 + max = tonumber(GetConVarString("AdvDupe2_MaxContraptionUndoDelay")) or 60 + if(undo_delay < min) then + undo_delay = min + elseif(undo_delay > max) then + undo_delay = max + end + elseif(undo_delay < 0) then + undo_delay = 0 + end + end + + -- Set options + spawner:SetPlayer(ply) + spawner:GetTable():SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel, hideprops) + + local tbl = { + ply = ply, + delay = delay, + undo_delay = undo_delay, + disgrav = disgrav, + disdrag = disdrag, + addvel = addvel, + hideprops = hideprops + } + table.Merge(spawner:GetTable(), tbl) + spawner:SetDupeInfo(HeadEnt, EntityTable, ConstraintTable) + spawner:AddGhosts(ply) + + ply:AddCount("gmod_contr_spawners", spawner) + ply:AddCleanup("gmod_contr_spawner", spawner) + return spawner + end + + duplicator.RegisterEntityClass("gmod_contr_spawner", MakeContraptionSpawner, + "Pos", "Ang", "HeadEnt", "EntityTable", "ConstraintTable", "delay", + "undo_delay", "model", "key", "undo_key", "disgrav", "disdrag", "addvel", "hideprops") + + function AdvDupe2.InitProgressBar(ply,label) + net.Start("AdvDupe2_InitProgressBar") + net.WriteString(label) + net.Send(ply) + end + + function AdvDupe2.DrawSelectBox(ply) + net.Start("AdvDupe2_DrawSelectBox") + net.Send(ply) + end + + function AdvDupe2.RemoveSelectBox(ply) + net.Start("AdvDupe2_RemoveSelectBox") + net.Send(ply) + end + + function AdvDupe2.UpdateProgressBar(ply,percent) + net.Start("AdvDupe2_UpdateProgressBar") + net.WriteFloat(percent) + net.Send(ply) + end + + function AdvDupe2.RemoveProgressBar(ply) + net.Start("AdvDupe2_RemoveProgressBar") + net.Send(ply) + end + + --Reset the offsets of height, pitch, yaw, and roll back to default + function AdvDupe2.ResetOffsets(ply, keep) + + if(not keep) then + ply.AdvDupe2.Name = nil + end + net.Start("AdvDupe2_ResetOffsets") + net.Send(ply) + end + + net.Receive("AdvDupe2_CanAutoSave", function(len, ply, len2) + + local desc = net.ReadString() + local ent = net.ReadInt(16) + local dupe = ply.AdvDupe2 + + if(ent~=0) then + dupe.AutoSaveEnt = ent + if(ply:GetInfo("advdupe2_auto_save_contraption")=="1") then + dupe.AutoSaveEnt = ents.GetByIndex( dupe.AutoSaveEnt ) + end + else + if(ply:GetInfo("advdupe2_auto_save_contraption")=="1") then + AdvDupe2.Notify(ply, "No entity selected to auto save contraption.", NOTIFY_ERROR) + return + end + dupe.AutoSaveEnt = nil + end + + dupe.AutoSavePos = dupe.TempAutoSavePos + dupe.AutoSaveSize = dupe.TempAutoSaveSize + dupe.AutoSaveOutSide = dupe.TempAutoSaveOutSide + dupe.AutoSaveContr = ply:GetInfo("advdupe2_auto_save_contraption")=="1" + dupe.AutoSaveDesc = desc + + local time = math.Clamp(tonumber(ply:GetInfo("advdupe2_auto_save_time")) or 2, 2, 30) + if(game.SinglePlayer()) then + dupe.AutoSavePath = net.ReadString() + end + + AdvDupe2.Notify(ply, "Your area will be auto saved every "..(time*60).." seconds.") + local name = "AdvDupe2_AutoSave_"..ply:UniqueID() + if(timer.Exists(name)) then + timer.Adjust(name, time*60, 0) + return + end + timer.Create(name, time*60, 0, function() + if(not IsValid(ply)) then + timer.Remove(name) + return + end + + local dupe = ply.AdvDupe2 + if(dupe.Downloading) then + AdvDupe2.Notify(ply, "Skipping auto save, tool is busy.", NOTIFY_ERROR) + return + end + + local Tab = {Entities={}, Constraints={}, HeadEnt={}} + + if(dupe.AutoSaveContr) then + if(not IsValid(dupe.AutoSaveEnt)) then + timer.Remove(name) + AdvDupe2.Notify(ply, "Head entity for auto save no longer valid; stopping auto save.", NOTIFY_ERROR) + return + end + + Tab.HeadEnt.Index = dupe.AutoSaveEnt:EntIndex() + Tab.HeadEnt.Pos = dupe.AutoSaveEnt:GetPos() + + local WorldTrace = util.TraceLine({ + mask = MASK_NPCWORLDSTATIC, + start = Tab.HeadEnt.Pos + Vector(0,0,1), + endpos = Tab.HeadEnt.Pos - Vector(0,0,50000) + }) + + Tab.HeadEnt.Z = WorldTrace.Hit and math.abs(Tab.HeadEnt.Pos.Z - WorldTrace.HitPos.Z) or 0 + AdvDupe2.duplicator.Copy( ply, dupe.AutoSaveEnt, Tab.Entities, Tab.Constraints, Tab.HeadEnt.Pos ) + else + local i = dupe.AutoSaveSize + local Pos = dupe.AutoSavePos + local T = Vector( i, i, i); T:Add(Pos) + local B = Vector(-i,-i,-i); B:Add(Pos) + + local Entities = FindInBox(B,T, ply) + local _, HeadEnt = next(Entities) + if not HeadEnt then + AdvDupe2.Notify(ply, "Area Auto Save copied 0 entities; be sure to turn it off.", NOTIFY_ERROR) + return + end + + if(dupe.AutoSaveEnt and Entities[dupe.AutoSaveEnt]) then + Tab.HeadEnt.Index = dupe.AutoSaveEnt + else + Tab.HeadEnt.Index = HeadEnt:EntIndex() + end + Tab.HeadEnt.Pos = HeadEnt:GetPos() + + local WorldTrace = util.TraceLine({ + mask = MASK_NPCWORLDSTATIC, + start = Tab.HeadEnt.Pos + Vector(0,0,1), + endpos = Tab.HeadEnt.Pos - Vector(0,0,50000) + }) + + Tab.HeadEnt.Z = WorldTrace.Hit and math.abs(Tab.HeadEnt.Pos.Z - WorldTrace.HitPos.Z) or 0 + Tab.Entities, Tab.Constraints = AdvDupe2.duplicator.AreaCopy(ply, Entities, Tab.HeadEnt.Pos, dupe.AutoSaveOutSide) + end + Tab.Constraints = GetSortedConstraints(ply, Tab.Constraints) + Tab.Description = dupe.AutoSaveDesc + + AdvDupe2.Encode( Tab, AdvDupe2.GenerateDupeStamp(ply), function(data) + AdvDupe2.SendToClient(ply, data, true) + end) + dupe.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")) + end) + timer.Start(name) + end) + + concommand.Add("AdvDupe2_SetStage", function(ply, cmd, args) + ply:GetTool("advdupe2"):SetStage(1) + end) + + concommand.Add("AdvDupe2_RemoveAutoSave", function(ply, cmd, args) + timer.Remove("AdvDupe2_AutoSave_"..ply:UniqueID()) + end) + + concommand.Add("AdvDupe2_SaveMap", function(ply, cmd, args) + if not ply:IsSuperAdmin() then + AdvDupe2.Notify(ply, "You do not have permission to this function.", NOTIFY_ERROR) + return + end + + local Entities = {} + + for _, v in ents.Iterator() do + if not v:CreatedByMap() and AdvDupe2.duplicator.IsCopyable(v) then + Entities[v:EntIndex()] = v + end + end + + local _, HeadEnt = next(Entities) + if not HeadEnt then AdvDupe2.Notify(ply, "There is nothing to save!", NOTIFY_ERROR) return end + + local Tab = {Entities = {}, Constraints = {}, HeadEnt = {}, Description = ""} + Tab.HeadEnt.Index = HeadEnt:EntIndex() + Tab.HeadEnt.Pos = HeadEnt:GetPos() + + local WorldTrace = util.TraceLine({ + mask = MASK_NPCWORLDSTATIC, + start = Tab.HeadEnt.Pos + Vector(0, 0, 1), + endpos = Tab.HeadEnt.Pos - Vector(0, 0, 50000) + }) + + Tab.HeadEnt.Z = WorldTrace.Hit and math.abs(Tab.HeadEnt.Pos.Z - WorldTrace.HitPos.Z) or 0 + Tab.Entities, Tab.Constraints = AdvDupe2.duplicator.AreaCopy(ply, Entities, Tab.HeadEnt.Pos, true) + Tab.Constraints = GetSortedConstraints(ply, Tab.Constraints) + Tab.Map = true + + AdvDupe2.Encode(Tab, AdvDupe2.GenerateDupeStamp(ply), function(data) + if #data > AdvDupe2.MaxDupeSize then + AdvDupe2.Notify(ply, "Copied duplicator filesize is too big!", NOTIFY_ERROR) + return + end + + local map = game.GetMap() + file.CreateDir("advdupe2/maps/" .. map) + + local savename = AdvDupe2.SanitizeFilename(args[1] and #args[1] > 0 and args[1] or util.DateStamp()) + file.Write("advdupe2/maps/" .. map .. "/" .. savename .. ".txt", data) + + AdvDupe2.Notify(ply, "Map save, saved successfully.") + end) + end) +end + +if(CLIENT) then + + function TOOL:LeftClick(trace) + if(trace and AdvDupe2.HeadGhost) then + return true + end + return false + end + + function TOOL:RightClick(trace) + if( self:GetOwner():KeyDown(IN_SPEED) and not self:GetOwner():KeyDown(IN_WALK) ) then + return false + end + return true + end + + --Removes progress bar and removes ghosts when tool is put away + function TOOL:ReleaseGhostEntity() + AdvDupe2.RemoveGhosts() + AdvDupe2.RemoveSelectBox() + if(AdvDupe2.Rotation) then + hook.Remove("PlayerBindPress", "AdvDupe2_BindPress") + hook.Remove("CreateMove", "AdvDupe2_MouseControl") + end + return + end + + function TOOL:Reload( trace ) + if(trace and (AdvDupe2.HeadGhost or self:GetStage() == 1)) then + return true + end + return false + end + + --Take control of the mouse wheel bind so the player can modify the height of the dupe + local function MouseWheelScrolled(ply, bind, pressed) + + if(bind == "invprev") then + if(ply:GetTool("advdupe2"):GetStage() == 1) then + local size = math.min(tonumber(ply:GetInfo("advdupe2_area_copy_size")) + 25, 30720) + RunConsoleCommand("advdupe2_area_copy_size",size) + else + local Z = tonumber(ply:GetInfo("advdupe2_offset_z")) + 5 + RunConsoleCommand("advdupe2_offset_z",Z) + end + return true + elseif(bind == "invnext") then + if(ply:GetTool("advdupe2"):GetStage() == 1) then + local size = math.max(tonumber(ply:GetInfo("advdupe2_area_copy_size")) - 25, 25) + RunConsoleCommand("advdupe2_area_copy_size",size) + else + local Z = tonumber(ply:GetInfo("advdupe2_offset_z")) - 5 + RunConsoleCommand("advdupe2_offset_z",Z) + end + return true + end + + GAMEMODE:PlayerBindPress(ply, bind, pressed) + end + + local YawTo = 0 + local BsAng = Angle() + + local function GetRotationSign(ply) + local VY = tonumber(ply:GetInfo("advdupe2_offset_yaw")) or 0 + BsAng:Zero(); BsAng:RotateAroundAxis(BsAng:Up(), VY) + local PR = ply:GetRight() + local DP = BsAng:Right():Dot(PR) + local DR = BsAng:Forward():Dot(PR) + if(math.abs(DR) > math.abs(DP)) then -- Roll priority + if(DR >= 0) then return -1, 1 else return 1, -1 end + else -- Pitch axis takes priority. Normal X-Y map + if(DP >= 0) then return 1, 1 else return -1, -1 end + end + end + + local function MouseControl( cmd ) + local ply = LocalPlayer() + local X = cmd:GetMouseX() / 20 + local Y = -cmd:GetMouseY() / 20 + local ru = ply:KeyDown(IN_SPEED) + local mm = input.IsMouseDown(MOUSE_MIDDLE) + + if(mm) then + if(ru) then + YawTo = 0 -- Reset total integrated yaw + RunConsoleCommand("advdupe2_offset_pitch", 0) + RunConsoleCommand("advdupe2_offset_yaw" , 0) + RunConsoleCommand("advdupe2_offset_roll" , 0) + else + if(Y ~= 0) then + local VR = tonumber(ply:GetInfo("advdupe2_offset_roll")) or 0 + local VP = tonumber(ply:GetInfo("advdupe2_offset_pitch")) or 0 + local SP, SR, P, R = GetRotationSign(ply) + if(SP ~= SR) then + P = math.NormalizeAngle(VP + X * SR) + R = math.NormalizeAngle(VR + Y * SP) + else + P = math.NormalizeAngle(VP + Y * SP) + R = math.NormalizeAngle(VR + X * SR) + end + RunConsoleCommand("advdupe2_offset_pitch", P) + RunConsoleCommand("advdupe2_offset_roll" , R) + end + end + else + if(X ~= 0) then + VY = tonumber(ply:GetInfo("advdupe2_offset_yaw")) or 0 + if(ru) then + YawTo = YawTo + X -- Integrate the mouse on the X value from the mouse + RunConsoleCommand("advdupe2_offset_yaw", math.SnapTo(math.NormalizeAngle(YawTo), 45)) + else + YawTo = VY + X -- Update the last yaw with the current value from the mouse + RunConsoleCommand("advdupe2_offset_yaw", math.NormalizeAngle(YawTo)) + end + end + end + end + + --Checks binds to modify dupes position and angles + function TOOL:Think() + + if AdvDupe2.HeadGhost then + AdvDupe2.UpdateGhosts() + end + + if(LocalPlayer():KeyDown(IN_USE)) then + if(not AdvDupe2.Rotation) then + hook.Add("PlayerBindPress", "AdvDupe2_BindPress", MouseWheelScrolled) + hook.Add("CreateMove", "AdvDupe2_MouseControl", MouseControl) + AdvDupe2.Rotation = true + end + else + if(AdvDupe2.Rotation) then + AdvDupe2.Rotation = false + hook.Remove("PlayerBindPress", "AdvDupe2_BindPress") + hook.Remove("CreateMove", "AdvDupe2_MouseControl") + end + end + end + + --Hinder the player from looking to modify offsets with the mouse + function TOOL:FreezeMovement() + return AdvDupe2.Rotation + end + + TOOL.Information = { + { name = "left" }, + { name = "right", stage = 0 }, + { name = "right_world", stage = 0, icon2 = "icon16/world.png" }, + { name = "right_shift", stage = 0, icon2 = "gui/info" }, + { name = "right_alt", stage = 1 }, + { name = "reload_alt", stage = 1 }, + { name = "right_shift_alt", stage = 1, icon2 = "gui/info" }, + } + + language.Add( "Tool.advdupe2.name", "Advanced Duplicator 2" ) + language.Add( "Tool.advdupe2.desc", "Duplicate things." ) + language.Add( "Tool.advdupe2.left", "Paste" ) + language.Add( "Tool.advdupe2.right", "Copy" ) + language.Add( "Tool.advdupe2.right_world", "Hit the world to select/deselect all" ) + language.Add( "Tool.advdupe2.right_shift", "Press Shift to area copy" ) + language.Add( "Tool.advdupe2.right_alt", "Copy an area" ) + language.Add( "Tool.advdupe2.reload_alt", "Autosave an area" ) + language.Add( "Tool.advdupe2.right_shift_alt", "Press Shift to cancel" ) + language.Add( "Undone_AdvDupe2", "Undone AdvDupe2 paste" ) + language.Add( "Cleanup_AdvDupe2", "AdvDupe2 Duplications" ) + language.Add( "Cleaned_AdvDupe2", "Cleaned up all AdvDupe2 Duplications" ) + language.Add( "SBoxLimit_AdvDupe2", "You've reached the AdvDupe2 Duplicator limit!" ) + + CreateClientConVar("advdupe2_offset_world", 0, false, true) + CreateClientConVar("advdupe2_offset_z", 0, false, true) + CreateClientConVar("advdupe2_offset_pitch", 0, false, true) + CreateClientConVar("advdupe2_offset_yaw", 0, false, true) + CreateClientConVar("advdupe2_offset_roll", 0, false, true) + CreateClientConVar("advdupe2_original_origin", 0, false, true) + CreateClientConVar("advdupe2_paste_constraints", 1, false, true) + CreateClientConVar("advdupe2_sort_constraints", 1, true, true) + CreateClientConVar("advdupe2_paste_parents", 1, false, true) + CreateClientConVar("advdupe2_paste_unfreeze", 0, false, true) + CreateClientConVar("advdupe2_preserve_freeze", 0, false, true) + CreateClientConVar("advdupe2_copy_outside", 0, false, true) + CreateClientConVar("advdupe2_copy_only_mine", 1, false, true) + CreateClientConVar("advdupe2_limit_ghost", 100, false, true, nil, 0, 100) + CreateClientConVar("advdupe2_ghost_rate", 5, true, true, nil, 1, 50) + CreateClientConVar("advdupe2_area_copy_size", 300, false, true) + CreateClientConVar("advdupe2_auto_save_contraption", 0, false, true) + CreateClientConVar("advdupe2_auto_save_overwrite", 1, false, true) + CreateClientConVar("advdupe2_auto_save_time", 2, false, true) + + --Contraption Spawner + CreateClientConVar("advdupe2_contr_spawner_key", -1, false, true) + CreateClientConVar("advdupe2_contr_spawner_undo_key", -1, false, true) + CreateClientConVar("advdupe2_contr_spawner_delay", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_undo_delay", 10, false, true) + CreateClientConVar("advdupe2_contr_spawner_disgrav", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_disdrag", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_addvel", 1, false, true) + CreateClientConVar("advdupe2_contr_spawner_hideprops", 0, false, true) + + --Experimental + CreateClientConVar("advdupe2_paste_disparents", 0, false, true) + CreateClientConVar("advdupe2_paste_protectoveride", 1, false, true) + CreateClientConVar("advdupe2_debug_openfile", 1, false, true) + + local function BuildCPanel(CPanel) + CPanel:ClearControls() + + local FileBrowser = vgui.Create("advdupe2_browser") + CPanel:AddItem(FileBrowser) + FileBrowser:SetSize(CPanel:GetWide(), 405) + AdvDupe2.FileBrowser = FileBrowser + + local Check = vgui.Create("DCheckBoxLabel") + + Check:SetText( "Paste at original position" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_original_origin" ) + Check:SetValue( 0 ) + Check:SetToolTip("Paste at the position originally copied") + CPanel:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste with constraints" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_constraints" ) + Check:SetValue( 1 ) + Check:SetToolTip("Paste with or without constraints") + CPanel:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste with parenting" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_parents" ) + Check:SetValue( 1 ) + Check:SetToolTip("Paste with or without parenting") + CPanel:AddItem(Check) + + local Check_1 = vgui.Create("DCheckBoxLabel") + local Check_2 = vgui.Create("DCheckBoxLabel") + + Check_1:SetText( "Unfreeze all after paste" ) + Check_1:SetDark(true) + Check_1:SetConVar( "advdupe2_paste_unfreeze" ) + Check_1:SetValue( 0 ) + Check_1.OnChange = function() + if(Check_1:GetChecked() and Check_2:GetChecked()) then + Check_2:SetValue(0) + end + end + Check_1:SetToolTip("Unfreeze all props after pasting") + CPanel:AddItem(Check_1) + + Check_2:SetText( "Preserve frozen state after paste" ) + Check_2:SetDark(true) + Check_2:SetConVar( "advdupe2_preserve_freeze" ) + Check_2:SetValue( 0 ) + Check_2.OnChange = function() + if(Check_2:GetChecked() and Check_1:GetChecked()) then + Check_1:SetValue(0) + end + end + Check_2:SetToolTip("Makes props have the same frozen state as when they were copied") + CPanel:AddItem(Check_2) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Area copy constrained props outside of box" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_copy_outside" ) + Check:SetValue( 0 ) + Check:SetToolTip("Copy entities outside of the area copy that are constrained to entities insde") + CPanel:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "World/Area copy only your own props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_copy_only_mine" ) + Check:SetValue( 1 ) + Check:SetToolTip("Copy entities outside of the area copy that are constrained to entities insde") + CPanel:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Sort constraints by their connections" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_sort_constraints" ) + Check:SetValue( GetConVarNumber("advdupe2_sort_constraints") ) + Check:SetToolTip( "Orders constraints so that they build a rigid constraint system." ) + CPanel:AddItem(Check) + + -- Ghost Percentage + local NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Ghost Percentage:" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 100 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_limit_ghost" ) + NumSlider:SetToolTip("Change the percent of ghosts to spawn") + --If these funcs are not here, problems occur for each + local func = NumSlider.Slider.OnMouseReleased + NumSlider.Slider.OnMouseReleased = function(self, mcode) func(self, mcode) AdvDupe2.StartGhosting() end + local func2 = NumSlider.Slider.Knob.OnMouseReleased + NumSlider.Slider.Knob.OnMouseReleased = function(self, mcode) func2(self, mcode) AdvDupe2.StartGhosting() end + local func3 = NumSlider.Wang.Panel.OnLoseFocus + NumSlider.Wang.Panel.OnLoseFocus = function(txtBox) func3(txtBox) AdvDupe2.StartGhosting() end + CPanel:AddItem(NumSlider) + + -- Ghost Rate + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Ghost speed:" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( 1 ) + NumSlider:SetMax( 50 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_ghost_rate" ) + NumSlider:SetToolTip("Change how quickly the ghosts are generated") + CPanel:AddItem(NumSlider) + + -- Area Copy Size + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Area Copy Size:" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 30720 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_area_copy_size" ) + NumSlider:SetToolTip("Change the size of the area copy") + CPanel:AddItem(NumSlider) + + local Category1 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category1) + Category1:SetLabel("Offsets") + Category1:SetExpanded(0) + + local parent = FileBrowser:GetParent():GetParent():GetParent():GetParent() + --[[Offsets]]-- + local CategoryContent1 = vgui.Create( "DPanelList" ) + CategoryContent1:SetAutoSize( true ) + CategoryContent1:SetDrawBackground( false ) + CategoryContent1:SetSpacing( 1 ) + CategoryContent1:SetPadding( 2 ) + CategoryContent1.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end --Fix the damned mouse not scrolling when it's over the catagories + + Category1:SetContents( CategoryContent1 ) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Height Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( -2500 ) + NumSlider:SetMax( 2500 ) + NumSlider:SetDefaultValue( 0 ) + NumSlider:SetDecimals( 3 ) + NumSlider:SetConVar("advdupe2_offset_z") + NumSlider:SetToolTip("Changes the dupe Z offset") + CategoryContent1:AddItem(NumSlider) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Use World Angles" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_offset_world" ) + Check:SetValue( 0 ) + Check:SetToolTip("Use world angles for the offset instead of the main entity") + CategoryContent1:AddItem(Check) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Pitch Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDefaultValue( 0 ) + NumSlider:SetDecimals( 3 ) + NumSlider:SetToolTip("Changes the dupe pitch offset") + NumSlider:SetConVar("advdupe2_offset_pitch") + CategoryContent1:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Yaw Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDefaultValue( 0 ) + NumSlider:SetDecimals( 3 ) + NumSlider:SetToolTip("Changes the dupe yaw offset") + NumSlider:SetConVar("advdupe2_offset_yaw") + CategoryContent1:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Roll Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDefaultValue( 0 ) + NumSlider:SetDecimals( 3 ) + NumSlider:SetToolTip("Changes the dupe roll offset") + NumSlider:SetConVar("advdupe2_offset_roll") + CategoryContent1:AddItem(NumSlider) + + local Btn = vgui.Create("DButton") + Btn:SetText("Reset") + Btn.DoClick = function() + RunConsoleCommand("advdupe2_offset_z", 0) + RunConsoleCommand("advdupe2_offset_pitch", 0) + RunConsoleCommand("advdupe2_offset_yaw", 0) + RunConsoleCommand("advdupe2_offset_roll", 0) + end + CategoryContent1:AddItem(Btn) + + + --[[Dupe Information]]-- + local Category2 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category2) + Category2:SetLabel("Dupe Information") + Category2:SetExpanded(0) + + local CategoryContent2 = vgui.Create( "DPanelList" ) + CategoryContent2:SetAutoSize( true ) + CategoryContent2:SetDrawBackground( false ) + CategoryContent2:SetSpacing( 3 ) + CategoryContent2:SetPadding( 2 ) + Category2:SetContents( CategoryContent2 ) + CategoryContent2.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + AdvDupe2.Info = {} + + local lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.File or "File: ") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.File = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Creator or "Creator:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Creator = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Date or "Date:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Date = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Time or "Time:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Time = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Size or "Size:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Size = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Desc or "Desc:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Desc = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Entities or "Entities:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Entities = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Constraints or "Constraints:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Constraints = lbl + + --[[Contraption Spawner]]-- + local Category3 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category3) + Category3:SetLabel("Contraption Spawner") + Category3:SetExpanded(0) + + local CategoryContent3 = vgui.Create( "DPanelList" ) + CategoryContent3:SetAutoSize( true ) + CategoryContent3:SetDrawBackground( false ) + CategoryContent3:SetSpacing( 3 ) + CategoryContent3:SetPadding( 2 ) + Category3:SetContents( CategoryContent3 ) + CategoryContent3.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + local ctrl = vgui.Create( "CtrlNumPad" ) + ctrl:SetConVar1( "advdupe2_contr_spawner_key" ) + ctrl:SetConVar2( "advdupe2_contr_spawner_undo_key" ) + ctrl:SetLabel1( "Spawn Key") + ctrl:SetLabel2( "Undo Key" ) + CategoryContent3:AddItem(ctrl) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Spawn Delay" ) + NumSlider.Label:SetDark(true) + if(game.SinglePlayer()) then + NumSlider:SetMin( 0 ) + else + local min = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + if(tonumber(LocalPlayer():GetInfo("advdupe2_contr_spawner_delay")) max) then + RunConsoleCommand("advdupe2_contr_spawner_undo_delay", tostring(max)) + end + NumSlider:SetMin( min ) + NumSlider:SetMax( max ) + end + NumSlider:SetDecimals( 1 ) + NumSlider:SetConVar("advdupe2_contr_spawner_undo_delay") + CategoryContent3:AddItem(NumSlider) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable gravity for all spawned props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_disgrav" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable drag for all spawned props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_disdrag" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Add spawner's velocity to contraption" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_addvel" ) + Check:SetValue( 1 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable drawing spawner props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_hideprops" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + --[[Area Auto Save]]-- + local Category4 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category4) + Category4:SetLabel("Area Auto Save") + Category4:SetExpanded(0) + + local CategoryContent4 = vgui.Create( "DPanelList" ) + CategoryContent4:SetAutoSize( true ) + CategoryContent4:SetDrawBackground( false ) + CategoryContent4:SetSpacing( 3 ) + CategoryContent4:SetPadding( 2 ) + Category4:SetContents( CategoryContent4 ) + CategoryContent4.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Only copy contraption" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_auto_save_contraption" ) + Check:SetValue( 0 ) + Check:SetToolTip("Only copy a contraption instead of an area") + CategoryContent4:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Overwrite File" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_auto_save_overwrite" ) + Check:SetValue( 1 ) + Check:SetToolTip("Overwrite the file instead of creating a new one everytime") + CategoryContent4:AddItem(Check) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Minutes to Save:" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( 2 ) + NumSlider:SetMax( 30 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_auto_save_time" ) + NumSlider:SetToolTip("Interval time to save in minutes") + CategoryContent4:AddItem(NumSlider) + + local pnl = vgui.Create("Panel") + pnl:SetWide(CPanel:GetWide()-40) + pnl:SetTall(75) + pnl:SetPos(0, 50) + CategoryContent4:AddItem(pnl) + + local label = vgui.Create("DLabel", pnl) + label:SetText("Directory: ") + label:SizeToContents() + label:SetDark(true) + label:SetPos(5,7) + + AdvDupe2.AutoSavePath = "" + local txtbox = vgui.Create("DTextEntry", pnl) + txtbox:SetWide(pnl:GetWide()-100) + txtbox:SetPos(60, 5) + txtbox:SetUpdateOnType(true) + txtbox.OnTextChanged = function(self) + self:SetValue(AdvDupe2.AutoSavePath) + end + + local btn = vgui.Create("DImageButton", pnl) + local x, y = txtbox:GetPos() + btn:SetPos(x + txtbox:GetWide() + 5, 7) + btn:SetMaterial("icon16/folder_explore.png") + btn:SizeToContents() + btn:SetToolTip("Browse") + btn.DoClick = function() + local ScrollBar = parent.VBar + ScrollBar:AnimateTo(0, 1, 0, 0.2) + + FileBrowser.Submit:SetMaterial("icon16/disk.png") + FileBrowser.Submit:SetTooltip("Directory for Area Auto Save") + if(FileBrowser.FileName:GetValue()=="Folder_Name...") then + FileBrowser.FileName:SetValue("File_Name...") + end + FileBrowser.Desc:SetVisible(true) + FileBrowser.Info:SetVisible(false) + FileBrowser.FileName:SetVisible(true) + FileBrowser.FileName:SelectAllOnFocus(true) + FileBrowser.FileName:OnMousePressed() + FileBrowser.FileName:RequestFocus() + FileBrowser.Expanding=true + FileBrowser:Slide(true) + FileBrowser.Submit.DoClick = function() + local name = FileBrowser.FileName:GetValue() + if(name=="" or name=="File_Name...") then + AdvDupe2.Notify("Name field is blank.", NOTIFY_ERROR) + FileBrowser.FileName:SelectAllOnFocus(true) + FileBrowser.FileName:OnGetFocus() + FileBrowser.FileName:RequestFocus() + return + end + local desc = FileBrowser.Desc:GetValue() + if(desc=="Description...") then desc="" end + + if(not IsValid(FileBrowser.Browser.pnlCanvas.m_pSelectedItem) or FileBrowser.Browser.pnlCanvas.m_pSelectedItem.Derma.ClassName~="advdupe2_browser_folder") then + AdvDupe2.Notify("Folder to save Area Auto Save not selected.", NOTIFY_ERROR) + return + end + + FileBrowser.AutoSaveNode = FileBrowser.Browser.pnlCanvas.m_pSelectedItem + txtbox:SetValue(FileBrowser:GetFullPath(FileBrowser.Browser.pnlCanvas.m_pSelectedItem)..name) + AdvDupe2.AutoSavePath = txtbox:GetValue() + txtbox:SetToolTip(txtbox:GetValue()) + AdvDupe2.AutoSaveDesc = desc + + FileBrowser:Slide(false) + ScrollBar:AnimateTo(ScrollBar.CanvasSize, 1, 0, 0.2) + + RunConsoleCommand("AdvDupe2_SetStage") + hook.Add("HUDPaint", "AdvDupe2_DrawSelectionBox", AdvDupe2.DrawSelectionBox) + end + FileBrowser.FileName.OnEnter = function() + FileBrowser.FileName:KillFocus() + FileBrowser.Desc:SelectAllOnFocus(true) + FileBrowser.Desc.OnMousePressed() + FileBrowser.Desc:RequestFocus() + end + FileBrowser.Desc.OnEnter = FileBrowser.Submit.DoClick + end + + btn = vgui.Create("DButton", pnl) + btn:SetSize(50, 35) + btn:SetPos(pnl:GetWide()/4-10, 30) + btn:SetText("Show") + btn.DoClick = function() + if(AdvDupe2.AutoSavePos) then + RunConsoleCommand("advdupe2_area_copy_size", AdvDupe2.AutoSaveSize) + LocalPlayer():SetEyeAngles( (AdvDupe2.AutoSavePos - LocalPlayer():GetShootPos()):Angle() ) + RunConsoleCommand("AdvDupe2_SetStage") + hook.Add("HUDPaint", "AdvDupe2_DrawSelectionBox", AdvDupe2.DrawSelectionBox) + end + end + + btn = vgui.Create("DButton", pnl) + btn:SetSize(50, 35) + btn:SetPos((pnl:GetWide()/4)*3-40, 30) + btn:SetText("Turn Off") + btn:SetDisabled(true) + btn.DoClick = function(self) + RunConsoleCommand("AdvDupe2_RemoveAutoSave") + self:SetDisabled(true) + AdvDupe2.AutoSavePos = nil + end + AdvDupe2.OffButton = btn + + + --[[Experimental Section]]-- + local Category5 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category5) + Category5:SetLabel("Experimental Section") + Category5:SetExpanded(0) + + local CategoryContent5 = vgui.Create( "DPanelList" ) + CategoryContent5:SetAutoSize( true ) + CategoryContent5:SetDrawBackground( false ) + CategoryContent5:SetSpacing( 3 ) + CategoryContent5:SetPadding( 2 ) + Category5:SetContents( CategoryContent5 ) + CategoryContent5.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable parented props physics interaction" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_disparents" ) + Check:SetValue( 0 ) + CategoryContent5:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable Dupe Spawn Protection" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_protectoveride" ) + Check:SetValue( 1 ) + Check:SetToolTip("Check this if you things don't look right after pasting.") + CategoryContent5:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Open file after Saving" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_debug_openfile" ) + Check:SetValue( 1 ) + Check:SetToolTip("Check this if you want your files to be opened after saving them.") + CategoryContent5:AddItem(Check) + + --[[Save Map]]-- + if(LocalPlayer():IsAdmin()) then + local Category6 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category6) + Category6:SetLabel("Save Map") + Category6:SetExpanded(0) + + local CategoryContent6 = vgui.Create( "DPanelList" ) + CategoryContent6:SetAutoSize( true ) + CategoryContent6:SetDrawBackground( false ) + CategoryContent6:SetSpacing( 3 ) + CategoryContent6:SetPadding( 2 ) + Category6:SetContents( CategoryContent6 ) + CategoryContent6.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + pnl = vgui.Create("Panel") + pnl:SetWide(CPanel:GetWide()-40) + pnl:SetTall(75) + pnl:SetPos(0, 50) + CategoryContent6:AddItem(pnl) + + label = vgui.Create("DLabel", pnl) + label:SetText("File Name: ") + label:SizeToContents() + label:SetDark(true) + label:SetPos(5,7) + + AdvDupe2.AutoSavePath = "" + + local txtbox2 = vgui.Create("DTextEntry", pnl) + txtbox2:SetWide(pnl:GetWide()-100) + txtbox2:SetPos(60, 5) + + local btn2 = vgui.Create("DImageButton", pnl) + x, y = txtbox2:GetPos() + btn2:SetPos(x + txtbox2:GetWide() + 5, 7) + btn2:SetMaterial("icon16/disk.png") + btn2:SizeToContents() + btn2:SetToolTip("Save Map") + btn2.DoClick = function() + RunConsoleCommand("AdvDupe2_SaveMap", txtbox2:GetValue()) + end + txtbox2.OnEnter = function() + btn2:DoClick() + end + end + end + + function TOOL.BuildCPanel(panel) + panel:ClearControls() + panel:AddControl("Header", { + Text = "Advanced Duplicator 2", + Description = "Duplicate stuff." + }) + local function tryToBuild() + local CPanel = controlpanel.Get("advdupe2") + if CPanel and CPanel:GetWide()>16 then + BuildCPanel(CPanel) + else + timer.Simple(0.1,tryToBuild) + end + end + tryToBuild() + end + + local StColor = {r=130, g=25, b=40, a=255} + local NoColor = {r=25, g=100, b=40, a=255} + local CurColor = {r=25, g=100, b=40, a=255} + local CWhite = Color(255,255,255,255) + surface.CreateFont ("AD2Font", {font="Arial", size=40, weight=1000}) ---Remember to use gm_clearfonts + surface.CreateFont ("AD2TitleFont", {font="Arial", size=24, weight=1000}) + + function TOOL:DrawToolScreen() + if(not AdvDupe2) then return true end + + local text = "Ready" + local state, co = false + local ply = LocalPlayer() + + if(AdvDupe2.Preview) then + text = "Preview" + end + if(AdvDupe2.ProgressBar.Text) then + state = true + text = AdvDupe2.ProgressBar.Text + end + + cam.Start2D() + + surface.SetDrawColor(32, 32, 32, 255) + surface.DrawRect(0, 0, 256, 256) + + if(state) then + co = StColor + else + co = NoColor + end + + local rate = FrameTime() * 160 + CurColor.r = math.Approach( CurColor.r, co.r, rate ) + CurColor.g = math.Approach( CurColor.g, co.g, rate ) + + surface.SetDrawColor(CurColor) + surface.DrawRect(13, 13, 230, 230) + + surface.SetTextColor( 255, 255, 255, 255 ) + + draw.SimpleText("Advanced Duplicator 2", "AD2TitleFont", 128, 50, CWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText(text, "AD2Font", 128, 128, CWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if(state) then + draw.RoundedBox( 6, 32, 178, 192, 28, Color( 255, 255, 255, 150 ) ) + draw.RoundedBox( 6, 34, 180, 188*(AdvDupe2.ProgressBar.Percent / 100), 24, Color( 0, 255, 0, 255 ) ) + elseif(ply:KeyDown(IN_USE)) then + local font, align = "AD2TitleFont", TEXT_ALIGN_BOTTOM + draw.SimpleText("H: "..ply:GetInfo("advdupe2_offset_z") , font, 20, 210, CWhite, TEXT_ALIGN_LEFT , align) + draw.SimpleText("P: "..ply:GetInfo("advdupe2_offset_pitch"), font, 236, 210, CWhite, TEXT_ALIGN_RIGHT, align) + draw.SimpleText("Y: "..ply:GetInfo("advdupe2_offset_yaw") , font, 20 , 240, CWhite, TEXT_ALIGN_LEFT , align) + draw.SimpleText("R: "..ply:GetInfo("advdupe2_offset_roll") , font, 236, 240, CWhite, TEXT_ALIGN_RIGHT, align) + end + + cam.End2D() + end + + + local function FindInBox(min, max) + local EntTable = {} + + for _, ent in ipairs(ents.FindInBox(min, max)) do + EntTable[ent:EntIndex()] = ent + end + + return EntTable + end + + + local GreenSelected = Color(0, 255, 0, 255) + function AdvDupe2.DrawSelectionBox() + + local TraceRes = util.TraceLine(util.GetPlayerTrace(LocalPlayer())) + local i = math.Clamp(tonumber(LocalPlayer():GetInfo("advdupe2_area_copy_size")) or 50, 0, 30720) + + --Bottom Points + local B1 = (Vector(-i,-i,-i) + TraceRes.HitPos) + local B2 = (Vector(-i, i,-i) + TraceRes.HitPos) + local B3 = (Vector( i, i,-i) + TraceRes.HitPos) + local B4 = (Vector( i,-i,-i) + TraceRes.HitPos) + + --Top Points + local T1 = (Vector(-i,-i, i) + TraceRes.HitPos):ToScreen() + local T2 = (Vector(-i, i, i) + TraceRes.HitPos):ToScreen() + local T3 = (Vector( i, i, i) + TraceRes.HitPos):ToScreen() + local T4 = (Vector( i,-i, i) + TraceRes.HitPos):ToScreen() + + if(not AdvDupe2.LastUpdate or CurTime()>=AdvDupe2.LastUpdate) then + + if AdvDupe2.ColorEntities then + for k,v in pairs(AdvDupe2.EntityColors)do + local ent = AdvDupe2.ColorEntities[k] + if(IsValid(ent)) then + AdvDupe2.ColorEntities[k]:SetColor(v) + end + end + end + + local Entities = FindInBox(B1, (Vector(i,i,i)+TraceRes.HitPos), LocalPlayer()) + AdvDupe2.ColorEntities = Entities + AdvDupe2.EntityColors = {} + for k,v in pairs(Entities)do + AdvDupe2.EntityColors[k] = v:GetColor() + v:SetColor(GreenSelected) + end + AdvDupe2.LastUpdate = CurTime()+0.25 + + end + + local tracedata = {} + tracedata.mask = MASK_NPCWORLDSTATIC + local WorldTrace + + tracedata.start = B1+Vector(0,0,i*2) + tracedata.endpos = B1 + WorldTrace = util.TraceLine( tracedata ) + B1 = WorldTrace.HitPos:ToScreen() + tracedata.start = B2+Vector(0,0,i*2) + tracedata.endpos = B2 + WorldTrace = util.TraceLine( tracedata ) + B2 = WorldTrace.HitPos:ToScreen() + tracedata.start = B3+Vector(0,0,i*2) + tracedata.endpos = B3 + WorldTrace = util.TraceLine( tracedata ) + B3 = WorldTrace.HitPos:ToScreen() + tracedata.start = B4+Vector(0,0,i*2) + tracedata.endpos = B4 + WorldTrace = util.TraceLine( tracedata ) + B4 = WorldTrace.HitPos:ToScreen() + + surface.SetDrawColor( 0, 255, 0, 255 ) + + --Draw Sides + surface.DrawLine(B1.x, B1.y, T1.x, T1.y) + surface.DrawLine(B2.x, B2.y, T2.x, T2.y) + surface.DrawLine(B3.x, B3.y, T3.x, T3.y) + surface.DrawLine(B4.x, B4.y, T4.x, T4.y) + + --Draw Bottom + surface.DrawLine(B1.x, B1.y, B2.x, B2.y) + surface.DrawLine(B2.x, B2.y, B3.x, B3.y) + surface.DrawLine(B3.x, B3.y, B4.x, B4.y) + surface.DrawLine(B4.x, B4.y, B1.x, B1.y) + + --Draw Top + surface.DrawLine(T1.x, T1.y, T2.x, T2.y) + surface.DrawLine(T2.x, T2.y, T3.x, T3.y) + surface.DrawLine(T3.x, T3.y, T4.x, T4.y) + surface.DrawLine(T4.x, T4.y, T1.x, T1.y) + + end + + net.Receive("AdvDupe2_DrawSelectBox", function() + hook.Add("HUDPaint", "AdvDupe2_DrawSelectionBox", AdvDupe2.DrawSelectionBox) + end) + + function AdvDupe2.RemoveSelectBox() + hook.Remove("HUDPaint", "AdvDupe2_DrawSelectionBox") + if AdvDupe2.ColorEntities then + for k,v in pairs(AdvDupe2.EntityColors)do + if(not IsValid(AdvDupe2.ColorEntities[k])) then + AdvDupe2.ColorEntities[k]=nil + else + AdvDupe2.ColorEntities[k]:SetColor(v) + end + end + AdvDupe2.ColorEntities={} + AdvDupe2.EntityColors={} + end + end + net.Receive("AdvDupe2_RemoveSelectBox",function() + AdvDupe2.RemoveSelectBox() + end) + + function AdvDupe2.InitProgressBar(label) + AdvDupe2.ProgressBar = {} + AdvDupe2.ProgressBar.Text = label + AdvDupe2.ProgressBar.Percent = 0 + AdvDupe2.BusyBar = true + end + net.Receive("AdvDupe2_InitProgressBar", function() + AdvDupe2.InitProgressBar(net.ReadString()) + end) + + net.Receive("AdvDupe2_UpdateProgressBar", function() + AdvDupe2.ProgressBar.Percent = net.ReadFloat() + end) + + function AdvDupe2.RemoveProgressBar() + AdvDupe2.ProgressBar = {} + AdvDupe2.BusyBar = false + if(AdvDupe2.Ghosting) then + AdvDupe2.InitProgressBar("Ghosting: ") + AdvDupe2.BusyBar = false + AdvDupe2.ProgressBar.Percent = AdvDupe2.CurrentGhost/AdvDupe2.TotalGhosts*100 + end + end + net.Receive("AdvDupe2_RemoveProgressBar", function() + AdvDupe2.RemoveProgressBar() + end) + + net.Receive("AdvDupe2_ResetOffsets", function() + RunConsoleCommand("advdupe2_original_origin", "0") + RunConsoleCommand("advdupe2_paste_constraints","1") + RunConsoleCommand("advdupe2_offset_z","0") + RunConsoleCommand("advdupe2_offset_pitch","0") + RunConsoleCommand("advdupe2_offset_yaw","0") + RunConsoleCommand("advdupe2_offset_roll","0") + RunConsoleCommand("advdupe2_paste_parents","1") + RunConsoleCommand("advdupe2_paste_disparents","0") + end) + + net.Receive("AdvDupe2_ReportModel", function() + print("Advanced Duplicator 2: Invalid Model: "..net.ReadString()) + end) + + net.Receive("AdvDupe2_ReportClass", function() + print("Advanced Duplicator 2: Invalid Class: "..net.ReadString()) + end) + + net.Receive("AdvDupe2_ResetDupeInfo", function() + if not AdvDupe2.Info then return end + AdvDupe2.Info.File:SetText("File:") + AdvDupe2.Info.Creator:SetText("Creator:") + AdvDupe2.Info.Date:SetText("Date:") + AdvDupe2.Info.Time:SetText("Time:") + AdvDupe2.Info.Size:SetText("Size:") + AdvDupe2.Info.Desc:SetText("Desc:") + AdvDupe2.Info.Entities:SetText("Entities:") + AdvDupe2.Info.Constraints:SetText("Constraints:") + end) + + net.Receive("AdvDupe2_CanAutoSave", function() + if(AdvDupe2.AutoSavePath~="") then + AdvDupe2.AutoSavePos = net.ReadVector() + AdvDupe2.AutoSaveSize = net.ReadFloat() + local ent = net.ReadUInt(16) + AdvDupe2.OffButton:SetDisabled(false) + net.Start("AdvDupe2_CanAutoSave") + net.WriteString(AdvDupe2.AutoSaveDesc) + net.WriteInt(ent, 16) + if(game.SinglePlayer()) then + net.WriteString(string.sub(AdvDupe2.AutoSavePath, 10, #AdvDupe2.AutoSavePath)) + end + net.SendToServer() + else + AdvDupe2.Notify("Select a directory for the Area Auto Save.", NOTIFY_ERROR) + end + end) + + net.Receive("AdvDupe2_SetDupeInfo", function(len, ply, len2) + if AdvDupe2.Info then + AdvDupe2.Info.File:SetText("File: "..net.ReadString()) + AdvDupe2.Info.Creator:SetText("Creator: "..net.ReadString()) + AdvDupe2.Info.Date:SetText("Date: "..net.ReadString()) + AdvDupe2.Info.Time:SetText("Time: "..net.ReadString()) + AdvDupe2.Info.Size:SetText("Size: "..net.ReadString()) + AdvDupe2.Info.Desc:SetText("Desc: "..net.ReadString()) + AdvDupe2.Info.Entities:SetText("Entities: "..net.ReadString()) + AdvDupe2.Info.Constraints:SetText("Constraints: "..net.ReadString()) + else + AdvDupe2.InfoText.File = "File: "..net.ReadString() + AdvDupe2.InfoText.Creator = "Creator: "..net.ReadString() + AdvDupe2.InfoText.Date = "Date: "..net.ReadString() + AdvDupe2.InfoText.Time = "Time: "..net.ReadString() + AdvDupe2.InfoText.Size = "Size: "..net.ReadString() + AdvDupe2.InfoText.Desc = "Desc: "..net.ReadString() + AdvDupe2.InfoText.Entities = "Entities: "..net.ReadString() + AdvDupe2.InfoText.Constraints = "Constraints: "..net.ReadString() + end + end) +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/ledscreen.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/ledscreen.lua new file mode 100644 index 0000000..e65ec76 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/ledscreen.lua @@ -0,0 +1,319 @@ +TOOL.Category = "Construction" +TOOL.Name = "LED screens" +TOOL.Command = nil +TOOL.ConfigName = "" + +local TextBox = {} +local slider = {} +local slider2 = {} +local slider3 = {} +local frame = {} +TOOL.ClientConVar[ "text" ] = "" +TOOL.ClientConVar[ "type" ] = 1 +TOOL.ClientConVar[ "speed" ] = 1.5 +TOOL.ClientConVar[ "wide" ] = 6 +TOOL.ClientConVar[ "fx" ] = 0 +TOOL.ClientConVar[ "wire" ] = 0 +TOOL.ClientConVar[ "r"] = 255 +TOOL.ClientConVar[ "g" ] = 200 +TOOL.ClientConVar[ "b" ] = 0 + +if (SERVER) then + CreateConVar('sbox_maxledscreens', 5) +end + +cleanup.Register("ledscreens") + +TOOL.Information = { + { name = "left" }, + { name = "right" } +} + +if (CLIENT) then + language.Add("Tool.ledscreen.name", "LED screen") + language.Add("Tool.ledscreen.desc", "Create a LED panel") + + language.Add("Tool.ledscreen.left", "Spawn a LED panel"); + language.Add("Tool.ledscreen.right", "Update LED panel"); + language.Add("Tool.ledscreen.reload", "Copy LED panel's settings"); + + language.Add("Undone.ledscreens", "Undone ledscreen") + language.Add("Undone_ledscreens", "Undone ledscreen") + language.Add("Cleanup.ledscreens", "ledscreens") + language.Add("Cleanup_ledscreens", "ledscreens") + language.Add("Cleaned.ledscreens", "Cleaned up all ledscreens") + language.Add("Cleaned_ledscreens", "Cleaned up all ledscreens") + + language.Add("SBoxLimit.ledscreens", "You've hit the ledscreen limit!") + language.Add("SBoxLimit_ledscreens", "You've hit the ledscreen limit!") +end + +local function GetConvars(self) + local type = tonumber(self:GetClientInfo("type")) + if !isnumber(type) then type = 1 end + + local speed = tonumber(self:GetClientInfo("speed")) + if !isnumber(speed) then speed = 1.5 end + + local wide = tonumber(self:GetClientInfo("wide")) + if !isnumber(wide) then wide = 6 end + + local fx = tonumber(self:GetClientInfo("fx")) + if !isnumber(fx) then fx = 0 end + + local r, g, b = tonumber(self:GetClientInfo("r")), tonumber(self:GetClientInfo("g")), tonumber(self:GetClientInfo("b")) + if !isnumber(r) or !isnumber(g) or !isnumber(b) then r, g, b = 255, 200, 100 end + + return math.Clamp(type, 1, 4), math.Clamp(speed, 1, 10), math.Clamp(wide, 3, 8), math.Clamp(fx, 0, 1), r, g, b +end + +function TOOL:LeftClick(tr) + if (tr.Entity:GetClass() == "player") then return false end + if (CLIENT) then return true end + + local Ply = self:GetOwner() + local centerpos = { + [3] = {18, 0}, + [4] = {11.5, 6}, + [5] = {18, 6}, + [6] = {36, 6}, + [7] = {42, 6}, + [8] = {48, 6}, + } + + local type, speed, wide, fx, r, g, b = GetConvars(self) + + local angle = tr.HitNormal:Angle() + local SpawnPos = tr.HitPos + angle:Right() * centerpos[wide][1] - angle:Up() * centerpos[wide][2] + + if not (self:GetWeapon():CheckLimit("ledscreens")) then return false end + + local TextScreen + if tonumber(self:GetClientInfo("wire")) > 0 then + TextScreen = ents.Create("gb_rp_sign_wire") + else + TextScreen = ents.Create("gb_rp_sign") + end + TextScreen:SetPos(SpawnPos) + TextScreen:Spawn() + + angle:RotateAroundAxis(tr.HitNormal:Angle():Right(), -90) + angle:RotateAroundAxis(tr.HitNormal:Angle():Forward(), 90) + TextScreen:SetAngles(angle) + TextScreen:SetText(self:GetClientInfo("text")) + TextScreen:SetType(type) + TextScreen:SetSpeed(speed) + TextScreen:SetWide(wide) + TextScreen:SetModel("models/squad/sf_plates/sf_plate1x"..wide..".mdl") + TextScreen:SetTColor(Vector(r/100, g/100, b/100)) + TextScreen:SetFX(fx) + TextScreen:Activate() + local Phys = TextScreen:GetPhysicsObject() + Phys:EnableMotion( false ) + + undo.Create("ledscreens") + + undo.AddEntity(TextScreen) + undo.SetPlayer(Ply) + undo.Finish() + + Ply:AddCount("ledscreens", TextScreen) + Ply:AddCleanup("ledscreens", TextScreen) + + 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 + + local type, speed, wide, fx, r, g, b = GetConvars(self) + + if (TraceEnt:IsValid() and TraceEnt:GetClass() == "gb_rp_sign") then + TraceEnt:SetText(self:GetClientInfo("text")) + TraceEnt:SetType(type) + TraceEnt:SetSpeed(speed) + TraceEnt:SetWide(wide) + TraceEnt:SetFX(fx) + TraceEnt:SetModel("models/squad/sf_plates/sf_plate1x"..wide..".mdl") + TraceEnt:SetTColor(Vector(r/100, g/100, b/100)) + return true + end + + +end + +function TOOL:Reload(tr) + + if !IsValid(tr.Entity) then return false end + local TraceEnt = tr.Entity + + if (TraceEnt:IsValid() and TraceEnt:GetClass() == "gb_rp_sign") then + if CLIENT or game.SinglePlayer() then + local color = TraceEnt:GetTColor() + RunConsoleCommand("ledscreen_text", TraceEnt:GetText()) + RunConsoleCommand("ledscreen_type", TraceEnt:GetType()) + RunConsoleCommand("ledscreen_r", color.x*100) + RunConsoleCommand("ledscreen_g", color.y*100) + RunConsoleCommand("ledscreen_b", color.z*100) + RunConsoleCommand("ledscreen_speed", TraceEnt:GetSpeed()) + RunConsoleCommand("ledscreen_wide", TraceEnt:GetWide()) + RunConsoleCommand("ledscreen_fx", TraceEnt:GetFX()) + end + end + + + return true + +end + + +function TOOL.BuildCPanel(CPanel) + CPanel:AddControl("Header", { Text = "#Tool.ledscreen.name" } ) + resetall = vgui.Create("DButton", resetbuttons) + resetall:SetSize(100, 25) + resetall:SetText("Reset all") + resetall.DoClick = function() + RunConsoleCommand("ledscreen_text", "") + RunConsoleCommand("ledscreen_type", 1) + RunConsoleCommand("ledscreen_r", 255) + RunConsoleCommand("ledscreen_g", 200) + RunConsoleCommand("ledscreen_b", 0) + RunConsoleCommand("ledscreen_speed", 1.5) + RunConsoleCommand("ledscreen_wide", 6) + RunConsoleCommand("ledscreen_fx", 0) + RunConsoleCommand("ledscreen_wire", 0) + slider:SetValue(1) + slider2:SetValue(1.5) + slider3:SetValue(6) + TextBox:SetValue("") + + end + CPanel:AddItem(resetall) + local font = "InfoRUS3" + frame = vgui.Create( "DPanel" ) + frame:SetSize( CPanel:GetWide(), 50 ) + frame.appr = nil + frame.Paint = function(self,w,h) + draw.RoundedBox(0,0,0,w,h,Color(0,0,0)) + surface.SetFont(font) + local alfa + if GetConVarNumber("ledscreen_fx") > 0 then + alfa = math.random(100,220) + else + alfa = 255 + end + self.Text = GetConVarString("ledscreen_text") + self.Type = GetConVarNumber("ledscreen_type") + self.Speed = GetConVarNumber("ledscreen_speed") + self.static = false + local ww,hh = surface.GetTextSize(self.Text) + local multiplier = self.Speed * 100 + self.Color = Color(GetConVarNumber("ledscreen_r"),GetConVarNumber("ledscreen_g"),GetConVarNumber("ledscreen_b"), alfa) + if self.Type == 1 then + + local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww + draw.DrawText(self.Text,font,xs,0,self.Color,0) + + elseif self.Type == 2 then + + if !self.appr or self.appr > ww then + self.appr = -w + else + self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier) + end + + draw.DrawText(self.Text,font,self.appr * -1,0,self.Color,0) + + else + if !self.appr then + self.appr = 0 + end + + if w > ww then + if self.Type == 3 then + if self.appr < w-ww and !self.refl then + self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier) + else + if self.appr <= 0 then + self.refl = nil + else + self.refl = true + self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier) + end + end + else + self.static = true + end + else + if self.appr > w-ww-50 and !self.refl then + self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier) + else + if self.appr >= 50 then + self.refl = nil + else + self.refl = true + self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier) + end + end + end + if self.static then + draw.DrawText(self.Text,font,w/2,0,self.Color,1) + else + draw.DrawText(self.Text,font,self.appr,0,self.Color,0) + end + end + end + + CPanel:AddItem(frame) + + + slider = vgui.Create("DNumSlider") + slider:SetText("Type") + slider:SetMinMax(1, 4) + slider:SetDecimals(0) + slider:SetValue(1) + slider:SetConVar("ledscreen_type") + CPanel:AddItem(slider) + + slider2 = vgui.Create("DNumSlider") + slider2:SetText("Speed") + slider2:SetMinMax(1, 10) + slider2:SetDecimals(1) + slider2:SetValue(1) + slider2:SetConVar("ledscreen_speed") + CPanel:AddItem(slider2) + + slider3 = vgui.Create("DNumSlider") + slider3:SetText("Wide") + slider3:SetMinMax(3, 8) + slider3:SetDecimals(0) + slider3:SetValue(6) + slider3:SetConVar("ledscreen_wide") + CPanel:AddItem(slider3) + + TextBox = vgui.Create("DTextEntry") + TextBox:SetUpdateOnType(true) + TextBox:SetEnterAllowed(true) + TextBox:SetConVar("ledscreen_text") + TextBox:SetValue(GetConVarString("ledscreen_text")) + CPanel:AddItem(TextBox) + + CPanel:AddControl( "CheckBox", { Label = "Flicker effect", Description = "", Command = "ledscreen_fx" } ) + CPanel:AddControl( "CheckBox", { Label = "WireMod support", Description = "", Command = "ledscreen_wire" } ) + + CPanel:AddControl("Color", { + Label = "LED color", + Red = "ledscreen_r", + Green = "ledscreen_g", + Blue = "ledscreen_b", + ShowHSV = 1, + ShowRGB = 1, + Multiplier = 255 + }) + + CPanel:AddControl("Label", { Text = "Gmod-Best.Ru ©2013-2019\nWith <3 from Mac" } ) + +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/nocollide_world.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/nocollide_world.lua new file mode 100644 index 0000000..4c21f11 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/nocollide_world.lua @@ -0,0 +1,1925 @@ +TOOL.Category = "Construction" +TOOL.Name = "#No Collide World" +TOOL.Command = nil +TOOL.ConfigName = nil + +TOOL.ClientConVar["options"] = "1" +TOOL.ClientConVar["distance"] = "10" +TOOL.ClientConVar["ignore"] = "1" +TOOL.ClientConVar["effect"] = "0" +TOOL.ClientConVar["remove"] = "0" + +if SERVER then + --resource.AddFile("materials/effects/render_vector.vtf") + --resource.AddFile("materials/effects/render_vector.vmt") + util.AddNetworkString("DrawNoCollide") +end + +if CLIENT then + language.Add("Tool.nocollide_world.name", "No collide world") + language.Add("Tool.nocollide_world.desc", "To let an objects ignore collisions") + language.Add("Tool.nocollide_world.0", "Click on 2 objects or world to make them not collide or right click to make an object not collide with anything.") + language.Add("Tool.nocollide_world.1", "Now click on something else.") + language.Add("Tool.nocollide_world.2", "Click on an object to prevent it from colliding with the world or right click to make an object not collide with anything.") + language.Add("Tool.nocollide_world.3", "Click on 2 objects with or without connected objects to make them not collide including connected objects or right click to make an object not collide with anything.") + language.Add("Tool.nocollide_world.4", "Now click on something else.") + language.Add("Tool.nocollide_world.5", "Click on an object with connected objects to prevent it from colliding with each other or right click to make an object not collide with anything.") + language.Add("Tool.nocollide_world.6", "Prevent an object from colliding with players or right click to make an object not collide with anything.") + language.Add("Tool.nocollide_world.7", "Prevent an object from colliding with objects within box or right click to make an object not collide with anything.") + language.Add("Tool.nocollide_world.8", "Prevent an object from colliding with objects within sphere or right click to make an object not collide with anything.") + language.Add("Tool.nocollide_world.9", "Click on an object to select. Right click to apply no collide between all selected objects.") + + local EFFECT = {} + EFFECT.Draw = {} + EFFECT.Register = {} + EFFECT.Ents = {} + --[[ + EFFECT.Ents[Index][1] = Entity + EFFECT.Ents[Index][2] = Color + EFFECT.Ents[Index][3] = Rendermode + EFFECT.Ents[Index][4][Type] = Eff_Count + or + EFFECT.Ents[Index][4][Type][Count] = Eff_Count + + EFFECT.Register[Type][Eff_Count][Ent_Count] = Index + EFFECT.Register[Type][Eff_Count][3] = DrawCount + ]] + + local function RegisterEffect(Type,Index,Ent,Count) + if !EFFECT.Ents[Index] then EFFECT.Ents[Index] = {} end + if !EFFECT.Ents[Index][1] then EFFECT.Ents[Index][1] = Ent end + if !EFFECT.Ents[Index][2] then EFFECT.Ents[Index][2] = Ent:GetColor() end + if !EFFECT.Ents[Index][3] then EFFECT.Ents[Index][3] = Ent:GetRenderMode() end + + if !EFFECT.Register[Type] then EFFECT.Register[Type] = {} end + Count = Count or #EFFECT.Register[Type]+1 + if !EFFECT.Register[Type][Count] then EFFECT.Register[Type][Count] = {} end + if EFFECT.Register[Type][Count][1] then EFFECT.Register[Type][Count][2] = Index else EFFECT.Register[Type][Count][1] = Index end + + if !EFFECT.Ents[Index][4] then EFFECT.Ents[Index][4] = {} end + if Type == 1 or Type == 2 or Type == 3 or Type == 4 or Type == 15 or Type == 16 or Type == 17 then + if !EFFECT.Ents[Index][4][Type] then EFFECT.Ents[Index][4][Type] = {} end + EFFECT.Ents[Index][4][Type][#EFFECT.Ents[Index][4][Type]+1] = Count + else + EFFECT.Ents[Index][4][Type] = Count + end + return Count + end + + local function CleanupTables() + local DrawPositions = {} + local NewDraw = {} + local Count = 0 + for i=1,#EFFECT.Draw do + if EFFECT.Draw[i] then + Count = Count+1 + DrawPositions[i] = Count + NewDraw[Count] = EFFECT.Draw[i] + end + end + EFFECT.Draw = NewDraw + + local RegisterPositions = {} + local NewRegister = {} + Count = {} + + for Type,v in pairs(EFFECT.Register) do + RegisterPositions[Type] = {} + NewRegister[Type] = {} + Count[Type] = 0 + for EffectCount=1,#EFFECT.Register[Type] do + if EFFECT.Register[Type][EffectCount] then + Count[Type] = Count[Type]+1 + RegisterPositions[Type][EffectCount] = Count[Type] + NewRegister[Type][Count[Type]] = EFFECT.Register[Type][EffectCount] + if EFFECT.Register[Type][EffectCount][3] then NewRegister[Type][Count[Type]][3] = DrawPositions[EFFECT.Register[Type][EffectCount][3]] end + end + end + end + + local Continue + for k,v in pairs(Count) do + if v > 0 then + Continue = true + break + end + end + + if Continue then + EFFECT.Register = NewRegister + + for Index,v1 in pairs(EFFECT.Ents) do if EFFECT.Ents[Index] and EFFECT.Ents[Index][4] then for Type,v2 in pairs(EFFECT.Ents[Index][4]) do if RegisterPositions[Type] then if Type == 1 or Type == 2 or Type == 3 or Type == 4 or Type == 15 or Type == 16 or Type == 17 then for k3,v3 in pairs(v2) do EFFECT.Ents[Index][4][Type][k3] = RegisterPositions[Type][v3] end else EFFECT.Ents[Index][4][Type] = RegisterPositions[Type][v2] end else EFFECT.Ents[Index][4][Type] = nil end end end end + + local EntFound + for k,v in pairs(EFFECT.Ents) do + if v then + EntFound = true + break + end + end + if !EntFound then + EFFECT.Draw = {} + EFFECT.Register = {} + EFFECT.Ents = {} + if EFFECT.Remove == false then EFFECT.Remove = true end + end + else + for k,v in pairs(EFFECT.Ents) do + if v then + local Ent = v[1] + if IsValid(Ent) then + Ent:SetColor(v[2]) + Ent:SetRenderMode(v[3]) + end + end + end + EFFECT.Draw = {} + EFFECT.Register = {} + EFFECT.Ents = {} + if EFFECT.Remove == false then EFFECT.Remove = true end + end + end + + local function RemoveEntFromEffect(Ent,Index,Type,EffectCount) + if IsValid(Ent) and type(Index) == "number" and EFFECT.Ents[Index] then + local Found + local Removed + for k1,v1 in pairs(EFFECT.Ents[Index][4]) do + if k1 == Type then + if type(v1) == "table" then + for k2,v2 in pairs(v1) do + if v2 == EffectCount then + EFFECT.Ents[Index][4][k1][k2] = nil + if Found then return end + Removed = true + else + if Removed then return end + Found = true + end + end + else + EFFECT.Ents[Index][4][k1] = nil + if Found then return end + Removed = true + end + else + if type(v1) == "table" then + for k2,v2 in pairs(v1) do + if Removed then return end + Found = true + break + end + else + if Removed then return end + Found = true + end + end + end + if !Found then + Ent:SetColor(EFFECT.Ents[Index][2]) + Ent:SetRenderMode(EFFECT.Ents[Index][3]) + EFFECT.Ents[Index] = false + end + elseif Index then + EFFECT.Ents[Index] = false + end + end + + net.Receive("DrawNoCollide",function() + local String = net.ReadString() + if String == "0" then + EFFECT.Draw = {} + EFFECT.Register = {} + for k,v in pairs(EFFECT.Ents) do + if v then + local Ent = v[1] + if IsValid(Ent) then + Ent:SetColor(v[2]) + Ent:SetRenderMode(v[3]) + end + end + end + EFFECT.Ents = {} + if EFFECT.Remove == false then EFFECT.Remove = true end + elseif String == "0a" then + for Type,v1 in pairs(EFFECT.Register) do + if Type == 1 or Type == 2 or Type == 3 or Type == 4 then + for EffectCount=1,#EFFECT.Register[Type] do + if EFFECT.Register[Type][EffectCount] then + local Ent1Index,Ent2Index = EFFECT.Register[Type][EffectCount][1],EFFECT.Register[Type][EffectCount][2] + local Ent1 + local Ent2 + if Ent1Index and EFFECT.Ents[Ent1Index] then Ent1 = EFFECT.Ents[Ent1Index][1] end + if Ent2Index and EFFECT.Ents[Ent2Index] then Ent2 = EFFECT.Ents[Ent2Index][1] end + if EFFECT.Register[Type][EffectCount][3] and EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] then EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] = false end + RemoveEntFromEffect(Ent1,Ent1Index,Type,EffectCount) + RemoveEntFromEffect(Ent2,Ent2Index,Type,EffectCount) + EFFECT.Register[Type][EffectCount] = false + end + end + elseif Type == 5 or Type == 6 or Type == 7 or Type == 8 or Type == 9 or Type == 10 or Type == 11 then + for EffectCount=1,#EFFECT.Register[Type] do + if EFFECT.Register[Type][EffectCount] then + local Ent1Index = EFFECT.Register[Type][EffectCount][1] + local Ent1 + if Ent1Index and EFFECT.Ents[Ent1Index] then Ent1 = EFFECT.Ents[Ent1Index][1] end + if EFFECT.Register[Type][EffectCount][3] and EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] then EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] = false end + RemoveEntFromEffect(Ent1,Ent1Index,Type,EffectCount) + EFFECT.Register[Type][EffectCount] = false + end + end + end + end + CleanupTables() + elseif String == "0b" then + for Type,v1 in pairs(EFFECT.Register) do + if Type == 15 or Type == 16 or Type == 17 then + for EffectCount=1,#EFFECT.Register[Type] do + if EFFECT.Register[Type][EffectCount] then + local Ent1Index,Ent2Index = EFFECT.Register[Type][EffectCount][1],EFFECT.Register[Type][EffectCount][2] + local Ent1 + local Ent2 + if Ent1Index and EFFECT.Ents[Ent1Index] then Ent1 = EFFECT.Ents[Ent1Index][1] end + if Ent2Index and EFFECT.Ents[Ent2Index] then Ent2 = EFFECT.Ents[Ent2Index][1] end + if EFFECT.Register[Type][EffectCount][3] and EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] then EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] = false end + RemoveEntFromEffect(Ent1,Ent1Index,Type,EffectCount) + RemoveEntFromEffect(Ent2,Ent2Index,Type,EffectCount) + EFFECT.Register[Type][EffectCount] = false + end + end + elseif Type == 12 or Type == 13 or Type == 14 then + for EffectCount=1,#EFFECT.Register[Type] do + if EFFECT.Register[Type][EffectCount] then + local Ent1Index = EFFECT.Register[Type][EffectCount][1] + local Ent1 + if Ent1Index and EFFECT.Ents[Ent1Index] then Ent1 = EFFECT.Ents[Ent1Index][1] end + if EFFECT.Register[Type][EffectCount][3] and EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] then EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] = false end + RemoveEntFromEffect(Ent1,Ent1Index,Type,EffectCount) + EFFECT.Register[Type][EffectCount] = false + end + end + end + end + CleanupTables() + else + if EFFECT.Remove == nil then util.Effect("render_no_collide", EffectData()) end + EFFECT.Remove = false + local Table = string.Explode("_",String) + if #Table == 2 then + local Index = tonumber(Table[1]) + if Table[2][1] == "-" then + local Type = tonumber(string.sub(Table[2],2,string.len(Table[2]))) + if EFFECT.Ents[Index] and EFFECT.Ents[Index][4] and EFFECT.Ents[Index][4][Type] then + local EffectCount = EFFECT.Ents[Index][4][Type] + local Ent1 + if Index and EFFECT.Ents[Index] then Ent1 = EFFECT.Ents[Index][1] end + if EFFECT.Register[Type][EffectCount][3] and EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] then EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] = false end + RemoveEntFromEffect(Ent1,Index,Type,EffectCount) + EFFECT.Register[Type][EffectCount] = false + CleanupTables() + end + else + local Type = tonumber(Table[2]) + if !(EFFECT.Ents[Index] and EFFECT.Ents[Index][4] and EFFECT.Ents[Index][4][Type]) then + local Ent = ents.GetByIndex(Index) + if IsValid(Ent) then RegisterEffect(Type,Index,Ent) end + end + end + else + local Ent1Index = math.min(tonumber(Table[1]),tonumber(Table[2])) + local Ent2Index = math.max(tonumber(Table[1]),tonumber(Table[2])) + if Table[3][1] == "-" then + local Type = tonumber(string.sub(Table[3],2,string.len(Table[3]))) + if EFFECT.Ents[Ent1Index] and EFFECT.Ents[Ent2Index] and EFFECT.Ents[Ent1Index][4] and EFFECT.Ents[Ent2Index][4] and EFFECT.Ents[Ent1Index][4][Type] and EFFECT.Ents[Ent2Index][4][Type] then + local EffectCount + for k,v in pairs(EFFECT.Ents[Ent1Index][4][Type]) do + if EFFECT.Register[Type] and EFFECT.Register[Type][v] and EFFECT.Register[Type][v][1] == Ent1Index and EFFECT.Register[Type][v][2] == Ent2Index then + EffectCount = v + break + end + end + if !EffectCount then + for k,v in pairs(EFFECT.Ents[Ent2Index][4][Type]) do + if EFFECT.Register[Type] and EFFECT.Register[Type][v] and EFFECT.Register[Type][v][1] == Ent1Index and EFFECT.Register[Type][v][2] == Ent2Index then + EffectCount = v + break + end + end + end + if EffectCount then + local Ent1 + local Ent2 + if Ent1Index and EFFECT.Ents[Ent1Index] then Ent1 = EFFECT.Ents[Ent1Index][1] end + if Ent2Index and EFFECT.Ents[Ent2Index] then Ent2 = EFFECT.Ents[Ent2Index][1] end + if EFFECT.Register[Type][EffectCount][3] and EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] then EFFECT.Draw[EFFECT.Register[Type][EffectCount][3]] = false end + RemoveEntFromEffect(Ent1,Ent1Index,Type,EffectCount) + RemoveEntFromEffect(Ent2,Ent2Index,Type,EffectCount) + EFFECT.Register[Type][EffectCount] = false + CleanupTables() + end + end + else + local Type = tonumber(Table[3]) + local NotRegister + if EFFECT.Ents[Ent1Index] and EFFECT.Ents[Ent2Index] and EFFECT.Ents[Ent1Index][4] and EFFECT.Ents[Ent2Index][4] and EFFECT.Ents[Ent1Index][4][Type] and EFFECT.Ents[Ent2Index][4][Type] then + for k,v in pairs(EFFECT.Ents[Ent1Index][4][Type]) do + if EFFECT.Register[Type] and EFFECT.Register[Type][v] and EFFECT.Register[Type][v][1] == Ent1Index and EFFECT.Register[Type][v][2] == Ent2Index then + NotRegister = true + break + end + end + if !NotRegister then + for k,v in pairs(EFFECT.Ents[Ent2Index][4][Type]) do + if EFFECT.Register[Type] and EFFECT.Register[Type][v] and EFFECT.Register[Type][v][1] == Ent1Index and EFFECT.Register[Type][v][2] == Ent2Index then + NotRegister = true + break + end + end + end + end + if !NotRegister then + local Ent1 = ents.GetByIndex(Ent1Index) + local Ent2 = ents.GetByIndex(Ent2Index) + if IsValid(Ent1) and IsValid(Ent2) then RegisterEffect(Type,Ent2Index,Ent2,RegisterEffect(Type,Ent1Index,Ent1)) end + end + end + end + end + end) + + function EFFECT:Init(data) end + + function EFFECT:Think() + -- This makes the effect always visible. + local pl = LocalPlayer() + if IsValid(pl) then + local Pos = pl:EyePos() + local Trace = {} + Trace.start = Pos + Trace.endpos = Pos+(pl:GetAimVector()*10) + Trace.filter = {pl} + local TR = util.TraceLine(Trace) + self:SetPos(TR.HitPos) + end + + -- Update positions. + for Type,v in pairs(EFFECT.Register) do + if EFFECT.Register[Type] then + if Type == 1 or Type == 2 or Type == 3 or Type == 4 or Type == 15 or Type == 16 or Type == 17 then + for EffectCount=1,#v do + if EFFECT.Register[Type] and EFFECT.Register[Type][EffectCount] then + local Ent1,Ent2 + if v[EffectCount][1] and EFFECT.Ents[v[EffectCount][1]] then Ent1 = EFFECT.Ents[v[EffectCount][1]][1] end + if v[EffectCount][2] and EFFECT.Ents[v[EffectCount][2]] then Ent2 = EFFECT.Ents[v[EffectCount][2]][1] end + if IsValid(Ent1) and IsValid(Ent2) then + if !v[EffectCount][3] then EFFECT.Register[Type][EffectCount][3] = #EFFECT.Draw+1 end + local DrawID = v[EffectCount][3] + if !EFFECT.Draw[DrawID] then EFFECT.Draw[DrawID] = {} end + EFFECT.Draw[DrawID][1] = Ent1:LocalToWorld(Ent1:OBBCenter()) + EFFECT.Draw[DrawID][2] = Ent2:LocalToWorld(Ent2:OBBCenter()) + if !EFFECT.Draw[DrawID][3] then + if Type == 15 then Type = 1 end + if Type == 16 then Type = 3 end + if Type == 17 then Type = 4 end + EFFECT.Draw[DrawID][3] = Type + end + else + if v[EffectCount][3] and EFFECT.Draw[v[EffectCount][3]] then EFFECT.Draw[v[EffectCount][3]] = false end + local Ent1Index,Ent2Index = v[EffectCount][1],v[EffectCount][2] + RemoveEntFromEffect(Ent1,Ent1Index,Type,EffectCount) + RemoveEntFromEffect(Ent2,Ent2Index,Type,EffectCount) + EFFECT.Register[Type][EffectCount] = false + CleanupTables() + end + end + end + elseif Type == 5 or Type == 6 or Type == 7 or Type == 8 or Type == 12 or Type == 13 or Type == 14 then + for EffectCount=1,#v do + if EFFECT.Register[Type] and EFFECT.Register[Type][EffectCount] then + local Ent1 + if v[EffectCount][1] and EFFECT.Ents[v[EffectCount][1]] then Ent1 = EFFECT.Ents[v[EffectCount][1]][1] end + if IsValid(Ent1) then + if !v[EffectCount][3] then EFFECT.Register[Type][EffectCount][3] = #EFFECT.Draw+1 end + local DrawID = v[EffectCount][3] + if !EFFECT.Draw[DrawID] then EFFECT.Draw[DrawID] = {} end + EFFECT.Draw[DrawID][1] = Ent1:LocalToWorld(Ent1:OBBCenter()) + if !EFFECT.Draw[DrawID][3] then + if Type == 12 then Type = 5 end + if Type == 13 then Type = 7 end + if Type == 14 then Type = 8 end + EFFECT.Draw[DrawID][3] = Type + end + else + if v[EffectCount][3] and EFFECT.Draw[v[EffectCount][3]] then EFFECT.Draw[v[EffectCount][3]] = false end + if v[EffectCount][1] then EFFECT.Ents[v[EffectCount][1]] = false end + EFFECT.Register[Type][EffectCount] = false + CleanupTables() + end + end + end + else + for EffectCount=1,#v do + if EFFECT.Register[Type] and EFFECT.Register[Type][EffectCount] then + local Ent1 + if v[EffectCount][1] and EFFECT.Ents[v[EffectCount][1]] then Ent1 = EFFECT.Ents[v[EffectCount][1]][1] end + if IsValid(Ent1) then + if !v[EffectCount][3] then EFFECT.Register[Type][EffectCount][3] = #EFFECT.Draw+1 end + local DrawID = v[EffectCount][3] + if !EFFECT.Draw[DrawID] then EFFECT.Draw[DrawID] = {} end + EFFECT.Draw[DrawID][3] = Type + EFFECT.Draw[DrawID][4] = Ent1 + else + if v[EffectCount][3] and EFFECT.Draw[v[EffectCount][3]] then EFFECT.Draw[v[EffectCount][3]] = false end + if v[EffectCount][1] then EFFECT.Ents[v[EffectCount][1]] = false end + EFFECT.Register[Type][EffectCount] = false + CleanupTables() + end + end + end + end + end + end + + -- Set alpha to 100. + for k,v in pairs(EFFECT.Ents) do + if v then + local Ent = v[1] + if IsValid(Ent) then + local Col = Ent:GetColor() + Col["a"] = 100 + Ent:SetRenderMode(1) + Ent:SetColor(Col) + end + end + end + + return true + end + + local DrawLine = Material("effects/render_vector") + + function EFFECT:Render() + render.SetMaterial(DrawLine) + for i=1,#EFFECT.Draw do + if EFFECT.Draw[i] then + if EFFECT.Draw[i][3] == 1 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][2], 16, 0.2, 0.8, Color(255, 255, 255, 255)) + elseif EFFECT.Draw[i][3] == 2 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][2], 16, 0.2, 0.8, Color(100, 255, 100, 255)) + elseif EFFECT.Draw[i][3] == 3 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][2], 16, 0.2, 0.8, Color(255, 50, 50, 255)) + elseif EFFECT.Draw[i][3] == 4 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][2], 16, 0.2, 0.8, Color(50, 50, 255, 255)) + elseif EFFECT.Draw[i][3] == 5 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][1]+Vector(0,0,-100), 32, 0.2, 0.8, Color(255, 255, 255, 255)) + elseif EFFECT.Draw[i][3] == 6 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][1]+Vector(0,0,-100), 32, 0.2, 0.8, Color(255, 150, 50, 255)) + elseif EFFECT.Draw[i][3] == 7 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][1]+Vector(0,0,-100), 32, 0.2, 0.8, Color(255, 50, 50, 255)) + elseif EFFECT.Draw[i][3] == 8 then + render.DrawBeam(EFFECT.Draw[i][1], EFFECT.Draw[i][1]+Vector(0,0,-100), 32, 0.2, 0.8, Color(50, 50, 255, 255)) + elseif EFFECT.Draw[i][3] == 9 then + halo.Add({EFFECT.Draw[i][4]}, Color(255, 255, 255, 255), 10, 10, 1, true, false) + elseif EFFECT.Draw[i][3] == 10 then + halo.Add({EFFECT.Draw[i][4]}, Color(255, 150, 50, 255), 10, 10, 1, true, false) + elseif EFFECT.Draw[i][3] == 11 then + halo.Add({EFFECT.Draw[i][4]}, Color(100, 255, 100, 255), 10, 10, 1, true, false) + end + end + end + end + + effects.Register(EFFECT,"render_no_collide",true) +end + +local function ExtractEntities(Entity, Entities, Constraints, Ignore) + Constraints = Constraints or {} + Entities = Entities or {} + if !Entity:IsValid() and Entity != game.GetWorld() then return Entities, Constraints end + local Index = Entity:EntIndex() + Entities[Index] = Entity + if !constraint.HasConstraints(Entity) then return Entities, Constraints end + + for k1, v1 in pairs(constraint.GetTable(Entity)) do + if !(Ignore and v1["Type"] and (v1["Type"] == "NoCollideWorld" or v1["Type"] == "NoCollide")) then + local Index = v1.Constraint + if !Constraints[Index] then + Constraints[Index] = v1.Constraint + for k2, v2 in pairs(v1.Entity) do + local Ents, Cons = ExtractEntities(v2.Entity, Entities, Constraints, Ignore) + table.Merge(Entities, Ents) + table.Merge(Constraints, Cons) + end + end + end + end + + return Entities, Constraints +end + +local SendToClient2 = {} +local SendDone2 = {} + +function TOOL:LeftClick(trace) + local pl = self:GetOwner() + if !IsValid(pl) then return end + if !trace.Entity then return end + if trace.Entity:IsPlayer() then return end + + if SERVER then + if self.Hold then self.Hold[pl] = false end + if self.AimEnt then self.AimEnt[pl] = nil end + end + + local Option = self:GetClientNumber("options") + + if Option == 1 then -- Like default no collide + if SERVER and !trace.Entity:IsValid() and trace.Entity != game.GetWorld() then return end + local iNum = self:NumObjects() + local Phys = trace.Entity:GetPhysicsObjectNum(trace.PhysicsBone) + self:SetObject(iNum+1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal) + if CLIENT then + if iNum > 0 then self:ClearObjects() end + return true + end + if iNum > 0 then + local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2) + local Bone1, Bone2 = self:GetBone(1), self:GetBone(2) + + if Ent1 == game.GetWorld() then + Ent1 = Ent2 + Ent2 = game.GetWorld() + end + if Ent1:GetTable().Constraints then + for k, v in pairs(Ent1:GetTable().Constraints) do + if v:IsValid() then + local CTab = v:GetTable() + if CTab.Type == "NoCollideWorld" and ((CTab.Ent1 == Ent1 and CTab.Ent2 == Ent2) or (CTab.Ent2 == Ent1 and CTab.Ent1 == Ent2)) then + self:ClearObjects() + v:Remove() + return true + end + end + end + end + + local Const = constraint.NoCollideWorld(Ent1, Ent2, Bone1, Bone2) + + if IsValid(Const) then + undo.Create("No Collide World, Default") + undo.AddEntity(Const) + undo.AddFunction(function(Undo, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + end, self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Default") + undo.Finish() + self:ClearObjects() + return true + end + self:ClearObjects() + else + self:SetStage(iNum+1) + return true + end + elseif Option == 2 then -- No collide world only + if !trace.Entity:IsValid() then return end + if CLIENT then return true end + + if trace.Entity:GetTable().Constraints then + for k, v in pairs(trace.Entity:GetTable().Constraints) do + if v:IsValid() then + local CTab = v:GetTable() + if CTab.Type == "NoCollideWorld" and CTab.Ent1 == trace.Entity and CTab.Ent2 == game.GetWorld() then + v:Remove() + return true + end + end + end + end + + local Const = constraint.NoCollideWorld(trace.Entity, game.GetWorld(), trace.PhysicsBone, 0) + if IsValid(Const) then + undo.Create("No Collide World, World Only") + undo.AddEntity(Const) + undo.AddFunction(function(Undo, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + end, self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, World Only") + undo.Finish() + return true + end + elseif Option == 3 then -- Select all constrained + if SERVER and !trace.Entity:IsValid() and trace.Entity != game.GetWorld() then return end + if CLIENT then return true end + local iNum = self:GetStage() + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if self.Ents1 and Entities and iNum == 4 then + local UndoTable = {} + for k1, Ent1 in pairs(self.Ents1) do + if Ent1:IsValid() or Ent1 == game.GetWorld() then + for k2, Ent2 in pairs(Entities) do + if (Ent2:IsValid() or Ent2 == game.GetWorld()) and Ent1 != Ent2 then + local Const = constraint.NoCollideWorld(Ent1, Ent2, 0, 0) + if IsValid(Const) then UndoTable[#UndoTable+1] = Const end + end + end + end + end + if #UndoTable == 0 then + self.Ents1 = nil + self:SetStage(3) + return + end + undo.Create("No Collide World, Select all constrained") + for i=1,#UndoTable do undo.AddEntity(UndoTable[i]) end + undo.AddFunction(function(Undo, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + end, self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Select all constrained") + undo.Finish() + self.Ents1 = nil + self:SetStage(3) + return true + else + self.Ents1 = Entities + self:SetStage(4) + return true + end + elseif Option == 4 then -- To all constrained + if !trace.Entity:IsValid() then return end + if CLIENT then return true end + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if Entities then + local UndoTable = {} + for k1, Ent1 in pairs(Entities) do + for k2, Ent2 in pairs(Entities) do + if Ent1:IsValid() and Ent2:IsValid() and Ent1 != Ent2 then + local Const = constraint.NoCollideWorld(Ent1, Ent2, 0, 0) + if IsValid(Const) then UndoTable[#UndoTable+1] = Const end + end + end + end + if #UndoTable == 0 then return end + undo.Create("No Collide World, To All Constrained") + for i=1,#UndoTable do undo.AddEntity(UndoTable[i]) end + undo.AddFunction(function(Undo, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + end, self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, To All Constrained") + undo.Finish() + return true + end + elseif Option == 5 then -- No collide player only + if !trace.Entity:IsValid() then return end + if CLIENT then return true end + + if trace.Entity:GetCollisionGroup() == COLLISION_GROUP_WEAPON then + trace.Entity:SetCollisionGroup(COLLISION_GROUP_NONE) + if IsValid(trace.Entity.NocollideDummy) then trace.Entity.NocollideDummy:Remove() end + return true + else + trace.Entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + undo.Create("Undone No Collide World, Player Only") + local Dummy = ents.Create("info_null") + if !trace.Entity.UndoNoCollidePlayer then trace.Entity:CallOnRemove("UndoNoCollidePlayer"..trace.Entity:EntIndex(),function(Ent) if Ent.NocollideDummy and Ent.NocollideDummy:IsValid() then Ent.NocollideDummy:Remove() end end,trace.Entity) end + trace.Entity.UndoNoCollidePlayer = true + trace.Entity.NocollideDummy = Dummy + undo.AddEntity(Dummy) + + undo.AddFunction(function(Undo, Ent, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + if Ent and Ent:IsValid() then Ent:SetCollisionGroup(COLLISION_GROUP_NONE) end + end, trace.Entity,self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Player Only") + undo.Finish() + return true + end + elseif Option == 6 then -- No collide within box + if !trace.Entity:IsValid() then return end + if CLIENT then return true end + local Distance = self:GetClientNumber("distance") + local AddVector = Vector(Distance,Distance,Distance) + local UndoTable = {} + for k,v in pairs(ents.FindInBox(trace.Entity:LocalToWorld(trace.Entity:OBBMins()-AddVector), trace.Entity:LocalToWorld(trace.Entity:OBBMaxs()+AddVector))) do + if v:IsValid() and v != trace.Entity and !v:IsPlayer() then + local Const = constraint.NoCollideWorld(trace.Entity, v, 0, 0) + if IsValid(Const) then UndoTable[#UndoTable+1] = Const end + end + end + if #UndoTable == 0 then return end + undo.Create("No Collide World, Within Box") + for i=1,#UndoTable do undo.AddEntity(UndoTable[i]) end + undo.AddFunction(function(Undo, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + end, self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Within Box") + undo.Finish() + return true + elseif Option == 7 then -- No collide within sphere + if !trace.Entity:IsValid() then return end + if CLIENT then return true end + local Distance = self:GetClientNumber("distance") + local UndoTable = {} + for k,v in pairs(ents.FindInSphere(trace.Entity:LocalToWorld(trace.Entity:OBBCenter()), (trace.Entity:OBBMaxs()/2):Length()+Distance)) do + if v:IsValid() and v != trace.Entity and !v:IsPlayer() then + local Const = constraint.NoCollideWorld(trace.Entity, v, 0, 0) + if IsValid(Const) then UndoTable[#UndoTable+1] = Const end + end + end + if #UndoTable == 0 then return end + undo.Create("No Collide World, Within Sphere") + for i=1,#UndoTable do undo.AddEntity(UndoTable[i]) end + undo.AddFunction(function(Undo, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + end, self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Within Sphere") + undo.Finish() + return true + elseif Option == 8 then -- No collide within box all constrained + if !trace.Entity:IsValid() then return end + if CLIENT then return true end + local Distance = self:GetClientNumber("distance") + local AddVector = Vector(Distance,Distance,Distance) + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if Entities then + local UndoTable = {} + for k1, Ent1 in pairs(Entities) do + if Ent1:IsValid() and !Ent1:IsPlayer() then + for k,v in pairs(ents.FindInBox(Ent1:LocalToWorld(Ent1:OBBMins()-AddVector), Ent1:LocalToWorld(Ent1:OBBMaxs()+AddVector))) do + if v:IsValid() and v != Ent1 and !v:IsPlayer() then + local Const = constraint.NoCollideWorld(Ent1, v, 0, 0) + if IsValid(Const) then UndoTable[#UndoTable+1] = Const end + end + end + end + end + if #UndoTable == 0 then return end + undo.Create("No Collide World, Within Box All Constrained") + for i=1,#UndoTable do undo.AddEntity(UndoTable[i]) end + undo.AddFunction(function(Undo, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + end, self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Within Box All Constrained") + undo.Finish() + return true + end + elseif Option == 9 then -- No collide within sphere all constrained + if !trace.Entity:IsValid() then return end + if CLIENT then return true end + local Distance = self:GetClientNumber("distance") + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if Entities then + local UndoTable = {} + for k1, Ent1 in pairs(Entities) do + if Ent1:IsValid() and !Ent1:IsPlayer() then + for k,v in pairs(ents.FindInSphere(Ent1:LocalToWorld(Ent1:OBBCenter()), (Ent1:OBBMaxs()/2):Length()+Distance)) do + if v:IsValid() and v != Ent1 and !v:IsPlayer() then + local Const = constraint.NoCollideWorld(Ent1, v, 0, 0) + if IsValid(Const) then UndoTable[#UndoTable+1] = Const end + end + end + end + end + if #UndoTable == 0 then return end + undo.Create("No Collide World, Within Sphere All Constrained") + for i=1,#UndoTable do undo.AddEntity(UndoTable[i]) end + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Within Sphere All Constrained") + undo.Finish() + return true + end + elseif Option == 10 then -- To all selected entities + if CLIENT then return true end + if SERVER and !trace.Entity:IsValid() and trace.Entity != game.GetWorld() then return end + local EntityIndex = trace.Entity:EntIndex() + if !SendDone2[pl] then SendDone2[pl] = 0 end + if !SendToClient2[pl] then SendToClient2[pl] = {} end + + if !self.TASE then + self.TASE = {} + self.TASE[1] = {} + self.TASE[2] = {} + self.TASE[3] = {} + --[[ + self.TASE[1][Index][Count] = Do count + + self.TASE[2][Low index][High index] = 1 = create, 2 = remove, 3 = ignore + + self.TASE[3][Do count][1] = Low index + self.TASE[3][Do count][2] = High index + ]] + end + + local function NocollideFind(Ent1, Ent2) + if Ent1 == game.GetWorld() then + Ent1 = Ent2 + Ent2 = game.GetWorld() + end + if !IsValid(Ent1) then return end + if !Ent1:GetTable().Constraints then return end + for k, v in pairs(Ent1:GetTable().Constraints) do + if v:IsValid() then + local CTab = v:GetTable() + if (CTab.Type == "NoCollideWorld" or CTab.Type == "NoCollide") and ((CTab.Ent1 == Ent1 and CTab.Ent2 == Ent2) or (CTab.Ent2 == Ent1 and CTab.Ent1 == Ent2)) then return v,CTab.Type end + end + end + return + end + + local function RemoveObject(Index) + for i1=1,#self.TASE[1][Index] do + local DoC = self.TASE[1][Index][i1] + if DoC and self.TASE[3][DoC] then + local LIndex = self.TASE[3][DoC][1] + local HIndex = self.TASE[3][DoC][2] + if self.TASE[2][LIndex] and self.TASE[2][LIndex][HIndex] then + local SendC = #SendToClient2[pl]+1 + SendToClient2[pl][SendC] = {} + if LIndex == 0 then + if self.TASE[2][LIndex][HIndex] == 1 then + SendToClient2[pl][SendC][1] = HIndex + SendToClient2[pl][SendC][3] = -12 + elseif self.TASE[2][LIndex][HIndex] == 2 then + SendToClient2[pl][SendC][1] = HIndex + SendToClient2[pl][SendC][3] = -13 + else + SendToClient2[pl][SendC][1] = HIndex + SendToClient2[pl][SendC][3] = -14 + end + elseif HIndex == 0 then + if self.TASE[2][LIndex][HIndex] == 1 then + SendToClient2[pl][SendC][1] = LIndex + SendToClient2[pl][SendC][3] = -12 + elseif self.TASE[2][LIndex][HIndex] == 2 then + SendToClient2[pl][SendC][1] = LIndex + SendToClient2[pl][SendC][3] = -13 + else + SendToClient2[pl][SendC][1] = LIndex + SendToClient2[pl][SendC][3] = -14 + end + else + if self.TASE[2][LIndex][HIndex] == 1 then + SendToClient2[pl][SendC][1] = LIndex + SendToClient2[pl][SendC][2] = HIndex + SendToClient2[pl][SendC][3] = -15 + elseif self.TASE[2][LIndex][HIndex] == 2 then + SendToClient2[pl][SendC][1] = LIndex + SendToClient2[pl][SendC][2] = HIndex + SendToClient2[pl][SendC][3] = -16 + else + SendToClient2[pl][SendC][1] = LIndex + SendToClient2[pl][SendC][2] = HIndex + SendToClient2[pl][SendC][3] = -17 + end + end + self.TASE[2][LIndex][HIndex] = nil + end + self.TASE[3][DoC] = false + for i2=1,#self.TASE[1][HIndex] do if !self.TASE[3][self.TASE[1][HIndex][i2]] then self.TASE[1][HIndex][i2] = false end end + for i2=1,#self.TASE[1][LIndex] do if !self.TASE[3][self.TASE[1][LIndex][i2]] then self.TASE[1][LIndex][i2] = false end end + end + end + self.TASE[1][Index] = false + local Translate = {} + local New = {} + local Count = 0 + for i1=1,#self.TASE[3] do + if self.TASE[3][i1] then + Count = Count+1 + Translate[i1] = Count + local LIndex = self.TASE[3][i1][1] + local HIndex = self.TASE[3][i1][2] + New[Count] = self.TASE[3][i1] + end + end + self.TASE[3] = New + for k,v in pairs(self.TASE[1]) do + if v then + self.TASE[1][k] = {} + local Count = 0 + for i=1,#v do + if v[i] and self.TASE[3][Translate[v[i]]] then + Count = Count+1 + self.TASE[1][k][Count] = Translate[v[i]] + end + end + end + end + end + + if self.TASE[1][EntityIndex] then + RemoveObject(EntityIndex) + return true + else + self.TASE[1][EntityIndex] = {} + end + + if tobool(self:GetClientNumber("remove")) then + for k,v in pairs(self.TASE[1]) do + if k != EntityIndex and self.TASE[1][EntityIndex] and self.TASE[1][k] then + local Ent2 + if k == 0 then Ent2 = game.GetWorld() else Ent2 = ents.GetByIndex(k) end + local Nocollide, Type = NocollideFind(trace.Entity, Ent2) + if Type == "NoCollideWorld" then + local SendC = #SendToClient2[pl]+1 + SendToClient2[pl][SendC] = {} + if EntityIndex == 0 then + SendToClient2[pl][SendC][1] = k + SendToClient2[pl][SendC][3] = 13 + elseif k == 0 then + SendToClient2[pl][SendC][1] = EntityIndex + SendToClient2[pl][SendC][3] = 13 + else + SendToClient2[pl][SendC][1] = k + SendToClient2[pl][SendC][2] = EntityIndex + SendToClient2[pl][SendC][3] = 16 + end + local LIndex = math.min(k,EntityIndex) + local HIndex = math.max(k,EntityIndex) + local DoC = #self.TASE[3]+1 + self.TASE[1][LIndex][#self.TASE[1][LIndex]+1] = DoC + self.TASE[1][HIndex][#self.TASE[1][HIndex]+1] = DoC + + if !self.TASE[2][LIndex] then self.TASE[2][LIndex] = {} end + self.TASE[2][LIndex][HIndex] = 2 + + self.TASE[3][DoC] = {} + self.TASE[3][DoC][1] = LIndex + self.TASE[3][DoC][2] = HIndex + elseif !Nocollide then + local SendC = #SendToClient2[pl]+1 + SendToClient2[pl][SendC] = {} + if EntityIndex == 0 then + SendToClient2[pl][SendC][1] = k + SendToClient2[pl][SendC][3] = 14 + elseif k == 0 then + SendToClient2[pl][SendC][1] = EntityIndex + SendToClient2[pl][SendC][3] = 14 + else + SendToClient2[pl][SendC][1] = k + SendToClient2[pl][SendC][2] = EntityIndex + SendToClient2[pl][SendC][3] = 17 + end + local LIndex = math.min(k,EntityIndex) + local HIndex = math.max(k,EntityIndex) + local DoC = #self.TASE[3]+1 + self.TASE[1][LIndex][#self.TASE[1][LIndex]+1] = DoC + self.TASE[1][HIndex][#self.TASE[1][HIndex]+1] = DoC + + if !self.TASE[2][LIndex] then self.TASE[2][LIndex] = {} end + self.TASE[2][LIndex][HIndex] = 3 + + self.TASE[3][DoC] = {} + self.TASE[3][DoC][1] = LIndex + self.TASE[3][DoC][2] = HIndex + end + end + end + else + for k,v in pairs(self.TASE[1]) do + if k != EntityIndex and self.TASE[1][EntityIndex] and self.TASE[1][k] then + local Ent2 + if k == 0 then Ent2 = game.GetWorld() else Ent2 = ents.GetByIndex(k) end + if !NocollideFind(trace.Entity, Ent2) then + local SendC = #SendToClient2[pl]+1 + SendToClient2[pl][SendC] = {} + if EntityIndex == 0 then + SendToClient2[pl][SendC][1] = k + SendToClient2[pl][SendC][3] = 12 + elseif k == 0 then + SendToClient2[pl][SendC][1] = EntityIndex + SendToClient2[pl][SendC][3] = 12 + else + SendToClient2[pl][SendC][1] = k + SendToClient2[pl][SendC][2] = EntityIndex + SendToClient2[pl][SendC][3] = 15 + end + local LIndex = math.min(k,EntityIndex) + local HIndex = math.max(k,EntityIndex) + local DoC = #self.TASE[3]+1 + self.TASE[1][LIndex][#self.TASE[1][LIndex]+1] = DoC + self.TASE[1][HIndex][#self.TASE[1][HIndex]+1] = DoC + + if !self.TASE[2][LIndex] then self.TASE[2][LIndex] = {} end + self.TASE[2][LIndex][HIndex] = 1 + + self.TASE[3][DoC] = {} + self.TASE[3][DoC][1] = LIndex + self.TASE[3][DoC][2] = HIndex + end + end + end + end + return true + end +end + +function TOOL:RightClick(trace) + if self:GetClientNumber("options") == 10 then + if CLIENT then return true end + if !self.TASE or !self.TASE[3] then return end + local pl = self:GetOwner() + if !IsValid(pl) then return end + if self.Hold then self.Hold[pl] = false end + if self.AimEnt then self.AimEnt[pl] = nil end + SendDone2[pl] = 0 + SendToClient2[pl] = {} + net.Start("DrawNoCollide") + net.WriteString("0b") + net.Send(pl) + + local UndoTable = {} + local Ents = {} + local DidRemove + + for i=1,#self.TASE[3] do + if self.TASE[3][i] then + local LIndex = self.TASE[3][i][1] + local HIndex = self.TASE[3][i][2] + if Ents[LIndex] == nil then + if LIndex == 0 then + Ents[LIndex] = game.GetWorld() + else + local Ent = ents.GetByIndex(LIndex) + if IsValid(Ent) or Ent == game.GetWorld() then Ents[LIndex] = Ent else Ents[LIndex] = false end + end + end + if Ents[HIndex] == nil then + if HIndex == 0 then + Ents[HIndex] = game.GetWorld() + else + local Ent = ents.GetByIndex(HIndex) + if IsValid(Ent) or Ent == game.GetWorld() then Ents[HIndex] = Ent else Ents[HIndex] = false end + end + end + if Ents[LIndex] and Ents[HIndex] then + if self.TASE[2][LIndex][HIndex] == 1 then + local Const = constraint.NoCollideWorld(Ents[LIndex], Ents[HIndex], 0, 0) + if IsValid(Const) then UndoTable[#UndoTable+1] = Const end + elseif self.TASE[2][LIndex][HIndex] == 2 then + local Ent1 = Ents[LIndex] + local Ent2 = Ents[HIndex] + if Ent1 == game.GetWorld() then + Ent1 = Ent2 + Ent2 = game.GetWorld() + end + if Ent1:GetTable().Constraints then + for k, v in pairs(Ent1:GetTable().Constraints) do + if v:IsValid() then + local CTab = v:GetTable() + if CTab.Type == "NoCollideWorld" and ((CTab.Ent1 == Ent1 and CTab.Ent2 == Ent2) or (CTab.Ent2 == Ent1 and CTab.Ent1 == Ent2)) then + DidRemove = true + v:Remove() + break + end + end + end + end + end + end + end + end + + self.TASE = nil + + if #UndoTable == 0 then if DidRemove then return true else return end end + undo.Create("No Collide World, To All Selected Entities") + for i=1,#UndoTable do undo.AddEntity(UndoTable[i]) end + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, To All Selected Entities") + undo.Finish() + return true + else + if !trace.Entity then return end + if !trace.Entity:IsValid() then return end + if trace.Entity:IsPlayer() then return end + if CLIENT then return true end + + local pl = self:GetOwner() + if !IsValid(pl) then return end + if self.Hold then self.Hold[pl] = false end + if self.AimEnt then self.AimEnt[pl] = nil end + + if trace.Entity:GetCollisionGroup() == COLLISION_GROUP_WORLD then + trace.Entity:SetCollisionGroup(COLLISION_GROUP_NONE) + if trace.Entity.Nocollide and trace.Entity.Nocollide:IsValid() then trace.Entity.Nocollide:Remove() end + return true + else + local function NocollideFind(Ent1, Ent2) + if !Ent1:GetTable().Constraints then return end + for k, v in pairs(Ent1:GetTable().Constraints) do + if v:IsValid() then + local CTab = v:GetTable() + if (CTab.Type == "NoCollideWorld" or CTab.Type == "NoCollide") and ((CTab.Ent1 == Ent1 and CTab.Ent2 == Ent2) or (CTab.Ent2 == Ent1 and CTab.Ent1 == Ent2)) then return v end + end + end + return + end + local Const = NocollideFind(trace.Entity, game.GetWorld()) + if IsValid(Const) then + Const:Remove() + Const = NULL + end + Const = constraint.NoCollideWorld(trace.Entity, game.GetWorld(), trace.PhysicsBone, 0) + if IsValid(Const) then + pl:AddCount("nocollide_world", Const) + trace.Entity:SetCollisionGroup(COLLISION_GROUP_WORLD) + trace.Entity.Nocollide = Const + if IsValid(trace.Entity.NocollideDummy) then trace.Entity.NocollideDummy:Remove() end + + undo.Create("No Collide World, Disable Collisions") + undo.AddEntity(Const) + undo.AddFunction(function(Undo, Ent, Tool, pl) + if Tool and pl and pl:IsValid() then + if Tool.Hold then Tool.Hold[pl] = false end + if Tool.AimEnt then Tool.AimEnt[pl] = nil end + end + if Ent and Ent:IsValid() and !IsValid(Ent.NocollideDummy) then Ent:SetCollisionGroup(COLLISION_GROUP_NONE) end + end, trace.Entity,self,pl) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone No Collide World, Disable Collisions") + undo.Finish() + return true + end + end + end +end + +function TOOL:Reload() + if CLIENT then return true end + local pl = self:GetOwner() + if !self.Hold then self.Hold = {} end + if !IsValid(pl) then return end + self.Hold[pl] = !self.Hold[pl] +end + +local SendToClient = {} +local SendDone = {} + +function TOOL:Think() + if CLIENT then return end + local Option = self:GetClientNumber("options") + local Stage = self:GetStage() + if Option == 1 and Stage != 0 and Stage != 1 then self:SetStage(0) elseif Option == 2 and Stage != 2 then self:SetStage(2) elseif Option == 3 and Stage != 3 and Stage != 4 then self:SetStage(3) elseif Option == 4 and Stage != 5 then self:SetStage(5) elseif Option == 5 and Stage != 6 then self:SetStage(6) elseif Option == 6 and Stage != 7 then self:SetStage(7) elseif Option == 7 and Stage != 8 then self:SetStage(8) elseif Option == 8 and Stage != 5 then self:SetStage(5) elseif Option == 9 and Stage != 5 then self:SetStage(5) elseif Option == 10 and Stage != 9 then self:SetStage(9) end + + local pl = self:GetOwner() + if !IsValid(pl) then return end + if !SendDone[pl] then SendDone[pl] = 0 end + if !SendToClient[pl] then SendToClient[pl] = {} end + if !self.AimEnt then self.AimEnt = {} end + if !self.Hold then self.Hold = {} end + if Option != self.OldOption then + self.OldOption = Option + self:ClearObjects() + SendDone[pl] = 0 + self.AimEnt[pl] = nil + SendToClient[pl] = {} + self.TASE = nil + net.Start("DrawNoCollide") + net.WriteString("0") + net.Send(pl) + end + if !self.Hold[pl] then + local trace = pl:GetEyeTrace() + if !tobool(self:GetClientNumber("effect")) and trace.Entity and trace.Entity:IsValid() and !trace.Entity:IsPlayer() then + if trace.Entity != self.AimEnt[pl] or Option != self.OldOption then + self.OldOption = Option + Stage = self:GetStage() + SendDone[pl] = 0 + SendToClient[pl] = {} + net.Start("DrawNoCollide") + net.WriteString("0a") + net.Send(pl) + local TraceEntityIndex = trace.Entity:EntIndex() + local Ignore + local ToolEnt1 = self:GetEnt(1) + local function NocollideFind(Ent1, Ent2) + if Ent1 == game.GetWorld() then + Ent1 = Ent2 + Ent2 = game.GetWorld() + end + if !Ent1:GetTable().Constraints then return end + for k, v in pairs(Ent1:GetTable().Constraints) do + if v:IsValid() then + local CTab = v:GetTable() + if (CTab.Type == "NoCollideWorld" or CTab.Type == "NoCollide") and ((CTab.Ent1 == Ent1 and CTab.Ent2 == Ent2) or (CTab.Ent2 == Ent1 and CTab.Ent1 == Ent2)) then return v end + end + end + return + end + if Option == 1 or Option == 2 or Option == 3 or Option == 5 or Option == 10 then + local AllEnts = {} + AllEnts[TraceEntityIndex] = true + if constraint.HasConstraints(trace.Entity) and Stage != 4 then + local Cons = constraint.GetTable(trace.Entity) + for i=1,#Cons do + if Cons[i]["Type"] == "NoCollideWorld" or Cons[i]["Type"] == "NoCollide" then + local Ent1Index + local Ent2Index + if Cons[i]["Entity"] then + if Cons[i]["Entity"][1] then Ent1Index = Cons[i]["Entity"][1]["Index"] end + if Cons[i]["Entity"][2] then Ent2Index = Cons[i]["Entity"][2]["Index"] end + end + if Ent1Index and Ent2Index then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if Ent1Index == 0 then + AllEnts[Ent2Index] = true + if Stage == 1 then + if self:GetEnt(1) != trace.Entity and (game.GetWorld() == self:GetEnt(1) or ents.GetByIndex(Ent2Index) == self:GetEnt(1)) and Cons[i]["Type"] == "NoCollideWorld" then + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 7 + else + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 6 + end + elseif Option == 10 and self.TASE then + local LIndex = math.min(Ent1Index,Ent2Index) + local HIndex = math.max(Ent1Index,Ent2Index) + if self.TASE[2][LIndex] and self.TASE[2][LIndex][HIndex] and self.TASE[2][LIndex][HIndex] == 2 then + SendToClient[pl][Count] = nil + else + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 6 + end + else + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 6 + end + if ents.GetByIndex(Ent2Index) == trace.Entity and ((Stage == 1 and self:GetEnt(1) == game.GetWorld()) or Stage == 2) then Ignore = true end + elseif Ent2Index == 0 then + AllEnts[Ent1Index] = true + if Stage == 1 then + if self:GetEnt(1) != trace.Entity and (ents.GetByIndex(Ent1Index) == self:GetEnt(1) or game.GetWorld() == self:GetEnt(1)) and Cons[i]["Type"] == "NoCollideWorld" then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 7 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 6 + end + elseif Option == 10 and self.TASE then + local LIndex = math.min(Ent1Index,Ent2Index) + local HIndex = math.max(Ent1Index,Ent2Index) + if self.TASE[2][LIndex] and self.TASE[2][LIndex][HIndex] and self.TASE[2][LIndex][HIndex] == 2 then + SendToClient[pl][Count] = nil + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 6 + end + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 6 + end + if ents.GetByIndex(Ent1Index) == trace.Entity and ((Stage == 1 and self:GetEnt(1) == game.GetWorld()) or Stage == 2) then Ignore = true end + else + AllEnts[Ent1Index] = true + AllEnts[Ent2Index] = true + if Stage == 1 then + if self:GetEnt(1) != trace.Entity and (ents.GetByIndex(Ent1Index) == self:GetEnt(1) or ents.GetByIndex(Ent2Index) == self:GetEnt(1)) and Cons[i]["Type"] == "NoCollideWorld" then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 3 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 2 + end + local Ent1 = ents.GetByIndex(Ent1Index) + local Ent2 = ents.GetByIndex(Ent2Index) + if (Ent1 == trace.Entity and Ent2 == self:GetEnt(1)) or (Ent2 == trace.Entity and Ent1 == self:GetEnt(1)) then Ignore = true end + elseif Option == 10 and self.TASE then + local LIndex = math.min(Ent1Index,Ent2Index) + local HIndex = math.max(Ent1Index,Ent2Index) + if self.TASE[2][LIndex] and self.TASE[2][LIndex][HIndex] and self.TASE[2][LIndex][HIndex] == 2 then + SendToClient[pl][Count] = nil + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 2 + end + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 2 + end + end + end + end + end + end + for k,v in pairs(AllEnts) do + if k != 0 then + local CollisionGroup = ents.GetByIndex(k):GetCollisionGroup() + if CollisionGroup == COLLISION_GROUP_WORLD then + if Option == 5 and ents.GetByIndex(k) == trace.Entity then Ignore = true end + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 10 + elseif CollisionGroup == COLLISION_GROUP_WEAPON then + if Option == 5 and ents.GetByIndex(k) == trace.Entity then Ignore = true end + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 11 + end + end + end + if ((Stage == 1 and self:GetEnt(1) != trace.Entity) or Stage == 2) and !Ignore then + local Ent1Index = TraceEntityIndex + local Ent2Index + if Stage == 1 then Ent2Index = self:GetEnt(1):EntIndex() else Ent2Index = 0 end + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if Ent1Index == 0 then + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 5 + elseif Ent2Index == 0 then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 5 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 1 + end + elseif Stage == 4 then + local AllEnts = {} + AllEnts[TraceEntityIndex] = true + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if self.Ents1 and Entities then + local Done = {} + for k1, Ent1 in pairs(self.Ents1) do + if Ent1:IsValid() or Ent1 == game.GetWorld() then + for k2, Ent2 in pairs(Entities) do + if (Ent2:IsValid() or Ent2 == game.GetWorld()) and Ent1 != Ent2 then + local Ent1Index = Ent1:EntIndex() + local Ent2Index = Ent2:EntIndex() + local Lowest = math.min(Ent1Index,Ent2Index) + local Highest = math.max(Ent1Index,Ent2Index) + if !Done[Lowest] then Done[Lowest] = {} end + if !Done[Lowest][Highest] then + Done[Lowest][Highest] = true + AllEnts[Ent1Index] = true + AllEnts[Ent2Index] = true + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if NocollideFind(Ent1,Ent2) then + if Ent1Index == 0 then + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 6 + elseif Ent2Index == 0 then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 6 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 2 + end + else + if Ent1Index == 0 then + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 5 + elseif Ent2Index == 0 then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 5 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 1 + end + end + end + end + end + end + end + end + for k,v in pairs(AllEnts) do + if k != 0 then + local CollisionGroup = ents.GetByIndex(k):GetCollisionGroup() + if CollisionGroup == COLLISION_GROUP_WORLD then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 10 + elseif CollisionGroup == COLLISION_GROUP_WEAPON then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 11 + end + end + end + elseif Option == 5 and !Ignore then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = TraceEntityIndex + SendToClient[pl][Count][3] = 9 + end + elseif Option == 4 then + local AllEnts = {} + AllEnts[TraceEntityIndex] = true + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if Entities then + local Done = {} + for k1, Ent1 in pairs(Entities) do + for k2, Ent2 in pairs(Entities) do + if Ent1 != Ent2 then + if Ent1:IsValid() and Ent2:IsValid() then + local Ent1Index = Ent1:EntIndex() + local Ent2Index = Ent2:EntIndex() + local Lowest = math.min(Ent1Index,Ent2Index) + local Highest = math.max(Ent1Index,Ent2Index) + if !Done[Lowest] then Done[Lowest] = {} end + if !Done[Lowest][Highest] then + Done[Lowest][Highest] = true + AllEnts[Ent1Index] = true + AllEnts[Ent2Index] = true + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if NocollideFind(Ent1,Ent2) then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 2 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 1 + end + end + elseif (Ent1:IsValid() or Ent1 == game.GetWorld()) and (Ent2:IsValid() or Ent2 == game.GetWorld()) and NocollideFind(Ent1,Ent2) then + local Ent1Index = Ent1:EntIndex() + local Ent2Index = Ent2:EntIndex() + local Lowest = math.min(Ent1Index,Ent2Index) + local Highest = math.max(Ent1Index,Ent2Index) + if !Done[Lowest] then Done[Lowest] = {} end + if !Done[Lowest][Highest] then + Done[Lowest][Highest] = true + AllEnts[Ent1Index] = true + AllEnts[Ent2Index] = true + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if Ent1Index == 0 then + SendToClient[pl][Count][1] = Ent2Index + SendToClient[pl][Count][3] = 6 + elseif Ent2Index == 0 then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][3] = 6 + end + end + end + end + end + end + end + for k,v in pairs(AllEnts) do + if k != 0 then + local CollisionGroup = ents.GetByIndex(k):GetCollisionGroup() + if CollisionGroup == COLLISION_GROUP_WORLD then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 10 + elseif CollisionGroup == COLLISION_GROUP_WEAPON then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 11 + end + end + end + else + local AllEnts = {} + AllEnts[TraceEntityIndex] = true + local Distance = self:GetClientNumber("distance") + if Option == 6 then + local AddVector = Vector(Distance,Distance,Distance) + for k,v in pairs(ents.FindInBox(trace.Entity:LocalToWorld(trace.Entity:OBBMins()-AddVector), trace.Entity:LocalToWorld(trace.Entity:OBBMaxs()+AddVector))) do + if v:IsValid() and v:GetPhysicsObject():IsValid() and v != trace.Entity and !v:IsPlayer() then + local Ent = v:EntIndex() + AllEnts[Ent] = true + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if NocollideFind(v,trace.Entity) then + SendToClient[pl][Count][1] = Ent + SendToClient[pl][Count][2] = TraceEntityIndex + SendToClient[pl][Count][3] = 2 + else + SendToClient[pl][Count][1] = Ent + SendToClient[pl][Count][2] = TraceEntityIndex + SendToClient[pl][Count][3] = 1 + end + end + end + elseif Option == 7 then + for k,v in pairs(ents.FindInSphere(trace.Entity:LocalToWorld(trace.Entity:OBBCenter()), (trace.Entity:OBBMaxs()/2):Length()+Distance)) do + if v:IsValid() and v:GetPhysicsObject():IsValid() and v != trace.Entity and !v:IsPlayer() then + local Ent = v:EntIndex() + AllEnts[Ent] = true + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if NocollideFind(v,trace.Entity) then + SendToClient[pl][Count][1] = Ent + SendToClient[pl][Count][2] = TraceEntityIndex + SendToClient[pl][Count][3] = 2 + else + SendToClient[pl][Count][1] = Ent + SendToClient[pl][Count][2] = TraceEntityIndex + SendToClient[pl][Count][3] = 1 + end + end + end + elseif Option == 8 then + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if Entities then + local AddVector = Vector(Distance,Distance,Distance) + for k1, Ent1 in pairs(Entities) do + if Ent1:IsValid() and !Ent1:IsPlayer() then + for k,v in pairs(ents.FindInBox(Ent1:LocalToWorld(Ent1:OBBMins()-AddVector), Ent1:LocalToWorld(Ent1:OBBMaxs()+AddVector))) do + if v:IsValid() and v:GetPhysicsObject():IsValid() and v != Ent1 and !v:IsPlayer() then + local Ent1Index = Ent1:EntIndex() + local Ent2Index = v:EntIndex() + AllEnts[Ent1Index] = true + AllEnts[Ent2Index] = true + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if NocollideFind(v,Ent1) then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 2 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 1 + end + end + end + end + end + end + elseif Option == 9 then + local Entities = ExtractEntities(trace.Entity,nil,nil,tobool(self:GetClientNumber("ignore"))) + if Entities then + for k1, Ent1 in pairs(Entities) do + if Ent1:IsValid() and !Ent1:IsPlayer() then + for k,v in pairs(ents.FindInSphere(Ent1:LocalToWorld(Ent1:OBBCenter()), (Ent1:OBBMaxs()/2):Length()+Distance)) do + if v:IsValid() and v:GetPhysicsObject():IsValid() and v != Ent1 and !v:IsPlayer() then + local Ent1Index = Ent1:EntIndex() + local Ent2Index = v:EntIndex() + AllEnts[Ent1Index] = true + AllEnts[Ent2Index] = true + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + if NocollideFind(v,Ent1) then + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 2 + else + SendToClient[pl][Count][1] = Ent1Index + SendToClient[pl][Count][2] = Ent2Index + SendToClient[pl][Count][3] = 1 + end + end + end + end + end + end + end + for k,v in pairs(AllEnts) do + if k != 0 then + local CollisionGroup = ents.GetByIndex(k):GetCollisionGroup() + if CollisionGroup == COLLISION_GROUP_WORLD then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 10 + elseif CollisionGroup == COLLISION_GROUP_WEAPON then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 11 + end + if NocollideFind(ents.GetByIndex(k),game.GetWorld()) then + local Count = #SendToClient[pl]+1 + SendToClient[pl][Count] = {} + SendToClient[pl][Count][1] = k + SendToClient[pl][Count][3] = 6 + end + end + end + end + self.AimEnt[pl] = trace.Entity + end + else + if self.AimEnt[pl] != nil then + SendDone[pl] = 0 + self.AimEnt[pl] = nil + SendToClient[pl] = {} + net.Start("DrawNoCollide") + net.WriteString("0a") + net.Send(pl) + end + end + end +end + +function TOOL:Holster() + if SERVER then + local pl = self:GetOwner() + if self.Hold then self.Hold[pl] = false end + SendDone[pl] = 0 + if self.AimEnt then self.AimEnt[pl] = nil end + SendToClient[pl] = {} + self.TASE = nil + net.Start("DrawNoCollide") + net.WriteString("0") + net.Send(pl) + self:ClearObjects() + end +end + +function TOOL.BuildCPanel(CPanel) + CPanel:AddControl("Header", {Text = "#Tool.nocollide_world.name", Description = "#Tool.nocollide_world.desc"}) + + local ctrl = vgui.Create("CtrlListBox", CPanel) + ctrl:AddOption("Like default no collide", {nocollide_world_options = "1"}) + ctrl:AddOption("No collide world only", {nocollide_world_options = "2"}) + ctrl:AddOption("Select all constrained", {nocollide_world_options = "3"}) + ctrl:AddOption("To all constrained", {nocollide_world_options = "4"}) + ctrl:AddOption("No collide player only", {nocollide_world_options = "5"}) + ctrl:AddOption("No collide within box", {nocollide_world_options = "6"}) + ctrl:AddOption("No collide within sphere", {nocollide_world_options = "7"}) + ctrl:AddOption("No collide within box all constrained", {nocollide_world_options = "8"}) + ctrl:AddOption("No collide within sphere all constrained", {nocollide_world_options = "9"}) + ctrl:AddOption("To all selected entities", {nocollide_world_options = "10"}) + + local left = vgui.Create("DLabel", CPanel) + left:SetText("Nocollide Options") + left:SetDark(true) + ctrl:SetHeight(25) + ctrl:Dock(TOP) + + CPanel:AddItem(left, ctrl) + + CPanel.IgnoreCheckbox = CPanel:AddControl("Checkbox", {Label = "Ignore No Collide", Command = "nocollide_world_ignore"}) + + CPanel.RemoveCheckbox = CPanel:AddControl("Checkbox", {Label = "Remove No Collide Or Ignore", Command = "nocollide_world_remove"}) + + CPanel.AddDistance = vgui.Create("Panel", CPanel) + CPanel.AddDistance:Dock(TOP) + CPanel.AddDistance:DockMargin(4, 20, 0, 0) + CPanel.AddDistance:SetVisible(true) + + CPanel.AddDistance.TextArea = CPanel.AddDistance:Add("DTextEntry") + CPanel.AddDistance.TextArea:SetDrawBackground(false) + CPanel.AddDistance.TextArea:SetNumeric(true) + CPanel.AddDistance.TextArea.OnChange = function(val) + val = tonumber(val:GetValue()) or 0 + if val then + CPanel.AddDistance.Scratch:SetValue(val) + val = tonumber(CPanel.AddDistance.Scratch:GetFloatValue()) or 0 + CPanel.AddDistance.Slider:SetSlideX(CPanel.AddDistance.Scratch:GetFraction(val)) + end + end + + CPanel.AddDistance.Slider = CPanel.AddDistance:Add("DSlider", CPanel.AddDistance) + CPanel.AddDistance.Slider:SetLockY(0.5) + CPanel.AddDistance.Slider.TranslateValues = function(slider, x, y) + local val = math.Clamp(x*1000, 0, 1000) + if val then + CPanel.AddDistance.Scratch:SetValue(val) + if CPanel.AddDistance.TextArea != vgui.GetKeyboardFocus() then + local str = CPanel.AddDistance.Scratch:GetTextValue() + if string.find(str,".",1,true) then str = string.Explode(".", str, true)[1] end + CPanel.AddDistance.TextArea:SetValue(str) + end + end + return CPanel.AddDistance.Scratch:GetFraction(), y + end + CPanel.AddDistance.Slider:SetTrapInside(true) + Derma_Hook(CPanel.AddDistance.Slider, "Paint", "Paint", "NumSlider") + CPanel.AddDistance.Slider:SetNotches(10) + + CPanel.AddDistance.Label = vgui.Create("DLabel", CPanel.AddDistance) + CPanel.AddDistance.Label:SetMouseInputEnabled(true) + CPanel.AddDistance.Label:SetDark(true) + CPanel.AddDistance.Label:SetText("Add Distance") + + CPanel.AddDistance.Scratch = CPanel.AddDistance.Label:Add("DNumberScratch") + CPanel.AddDistance.Scratch:SetImageVisible(false) + CPanel.AddDistance.Scratch:Dock(FILL) + CPanel.AddDistance.Scratch.OnValueChanged = function() + local val = tonumber(CPanel.AddDistance.Scratch:GetFloatValue()) or 0 + CPanel.AddDistance.Slider:SetSlideX(CPanel.AddDistance.Scratch:GetFraction(val)) + if CPanel.AddDistance.TextArea != vgui.GetKeyboardFocus() then + local str = CPanel.AddDistance.Scratch:GetTextValue() + if string.find(str,".",1,true) then str = string.Explode(".", str, true)[1] end + CPanel.AddDistance.TextArea:SetValue(str) + end + end + CPanel.AddDistance.Scratch:SetMin(0) + CPanel.AddDistance.Scratch:SetMax(1000) + CPanel.AddDistance.Scratch:SetDecimals(0) + CPanel.AddDistance.Scratch:SetConVar("nocollide_world_distance") + + CPanel.AddDistance:SetTall(32) + + function CPanel.AddDistance:PerformLayout() + local Left = 5 + CPanel.AddDistance.Label:SetPos(Left, 0) + CPanel.AddDistance.Label:SetWide(70, 0) + Left = Left+70 + CPanel.AddDistance.Slider:SetPos(Left, 0) + local Right = CPanel:GetWide()-10 + Right = Right-35 + CPanel.AddDistance.TextArea:SetPos(Right, 0) + CPanel.AddDistance.TextArea:SetWide(30) + CPanel.AddDistance.Slider:SetWide((Right-Left)-5) + end + + local val = GetConVarNumber("nocollide_world_distance") or 0 + if val then + CPanel.AddDistance.Scratch:SetValue(val) + val = tonumber(CPanel.AddDistance.Scratch:GetFloatValue()) or 0 + CPanel.AddDistance.Slider:SetSlideX(CPanel.AddDistance.Scratch:GetFraction(val)) + if CPanel.AddDistance.TextArea != vgui.GetKeyboardFocus() then + local str = CPanel.AddDistance.Scratch:GetTextValue() + if string.find(str,".",1,true) then str = string.Explode(".", str, true)[1] end + CPanel.AddDistance.TextArea:SetValue(str) + end + end + + CPanel:AddControl("Checkbox", {Label = "Hide Effect", Command = "nocollide_world_effect"}) + + local function CVarChange(_,Old,New) + if New then + if CPanel.IgnoreCheckbox then if New == "3" or New == "4" or New == "8" or New == "9" then CPanel.IgnoreCheckbox:SetVisible(true) else CPanel.IgnoreCheckbox:SetVisible(false) end end + if CPanel.AddDistance then if New == "6" or New == "7" or New == "8" or New == "9" then CPanel.AddDistance:SetVisible(true) else CPanel.AddDistance:SetVisible(false) end end + if CPanel.RemoveCheckbox and New == "10" then CPanel.RemoveCheckbox:SetVisible(true) else CPanel.RemoveCheckbox:SetVisible(false) end + end + end + cvars.AddChangeCallback("nocollide_world_options", CVarChange) + CVarChange(nil,nil,GetConVarString("nocollide_world_options")) +end + +if SERVER then + hook.Add("Tick", "NoCollideWorldTick", function() + for k,pl in pairs(player.GetAll()) do + if SendToClient[pl] and SendToClient[pl][SendDone[pl]+1] then + local S = SendDone[pl]+1 + SendDone[pl] = S + net.Start("DrawNoCollide") + if SendToClient[pl][S][2] then net.WriteString(tostring(SendToClient[pl][S][1]).."_"..SendToClient[pl][S][2].."_"..SendToClient[pl][S][3]) else net.WriteString(tostring(SendToClient[pl][S][1]).."_"..SendToClient[pl][S][3]) end + net.Send(pl) + if !SendToClient[pl][S+1] then + SendDone[pl] = 0 + SendToClient[pl] = {} + end + end + if SendToClient2[pl] and SendToClient2[pl][SendDone2[pl]+1] then + local S = SendDone2[pl]+1 + SendDone2[pl] = S + net.Start("DrawNoCollide") + if SendToClient2[pl][S][2] then net.WriteString(tostring(SendToClient2[pl][S][1]).."_"..SendToClient2[pl][S][2].."_"..SendToClient2[pl][S][3]) else net.WriteString(tostring(SendToClient2[pl][S][1]).."_"..SendToClient2[pl][S][3]) end + net.Send(pl) + if !SendToClient2[pl][S+1] then + SendDone2[pl] = 0 + SendToClient2[pl] = {} + end + end + end + end) + + local MAX_CONSTRAINTS_PER_SYSTEM = 100 + + local function CreateConstraintSystem() + local System = ents.Create("phys_constraintsystem") + if !IsValid(System) then return end + System:SetKeyValue("additionaliterations", GetConVarNumber("gmod_physiterations")) + System:Spawn() + System:Activate() + return System + end + + local function FindOrCreateConstraintSystem(Ent1, Ent2) + local System + if !Ent1:IsWorld() and Ent1:GetTable().ConstraintSystem and Ent1:GetTable().ConstraintSystem:IsValid() then System = Ent1:GetTable().ConstraintSystem end + if System and System:IsValid() and System:GetVar("constraints", 0) > MAX_CONSTRAINTS_PER_SYSTEM then System = nil end + if !System and !Ent2:IsWorld() and Ent2:GetTable().ConstraintSystem and Ent2:GetTable().ConstraintSystem:IsValid() then System = Ent2:GetTable().ConstraintSystem end + if System and System:IsValid() and System:GetVar("constraints", 0) > MAX_CONSTRAINTS_PER_SYSTEM then System = nil end + if !System or !System:IsValid() then System = CreateConstraintSystem() end + if !System then return end + Ent1.ConstraintSystem = System + Ent2.ConstraintSystem = System + System.UsedEntities = System.UsedEntities or {} + table.insert(System.UsedEntities, Ent1) + table.insert(System.UsedEntities, Ent2) + System:SetVar("constraints", System:GetVar("constraints", 0)+1) + return System + end + + function constraint.NoCollideWorld(Ent1, Ent2, Bone1, Bone2) + if !Ent1 or !Ent2 then return false end + + if Ent1 == game.GetWorld() then + Ent1 = Ent2 + Ent2 = game.GetWorld() + Bone1 = Bone2 + Bone2 = 0 + end + + if !Ent1:IsValid() or (!Ent2:IsWorld() and !Ent2:IsValid()) then return false end + + Bone1 = Bone1 or 0 + Bone2 = Bone2 or 0 + + local Phys1 = Ent1:GetPhysicsObjectNum(Bone1) + local Phys2 = Ent2:GetPhysicsObjectNum(Bone2) + + if !Phys1 or !Phys1:IsValid() or !Phys2 or !Phys2:IsValid() then return false end + + if Phys1 == Phys2 then return false end + + if Ent1:GetTable().Constraints then + for k, v in pairs(Ent1:GetTable().Constraints) do + if v:IsValid() then + local CTab = v:GetTable() + if (CTab.Type == "NoCollideWorld" or CTab.Type == "NoCollide") and ((CTab.Ent1 == Ent1 and CTab.Ent2 == Ent2) or (CTab.Ent2 == Ent1 and CTab.Ent1 == Ent2)) then return false end + end + end + end + + local System = FindOrCreateConstraintSystem(Ent1, Ent2) + + if !IsValid(System) then return false end + + SetPhysConstraintSystem(System) + + local Constraint = ents.Create("phys_ragdollconstraint") + + if !IsValid(Constraint) then + SetPhysConstraintSystem(NULL) + return false + end + Constraint:SetKeyValue("xmin", -180) + Constraint:SetKeyValue("xmax", 180) + Constraint:SetKeyValue("ymin", -180) + Constraint:SetKeyValue("ymax", 180) + Constraint:SetKeyValue("zmin", -180) + Constraint:SetKeyValue("zmax", 180) + Constraint:SetKeyValue("spawnflags", 3) + Constraint:SetPhysConstraintObjects(Phys1, Phys2) + Constraint:Spawn() + Constraint:Activate() + + SetPhysConstraintSystem(NULL) + constraint.AddConstraintTable(Ent1, Constraint, Ent2) + + local ctable = + { + Type = "NoCollideWorld", + Ent1 = Ent1, + Ent2 = Ent2, + Bone1 = Bone1, + Bone2 = Bone2 + } + + Constraint:SetTable(ctable) + + return Constraint + end + duplicator.RegisterConstraint("NoCollideWorld", constraint.NoCollideWorld, "Ent1", "Ent2", "Bone1", "Bone2") +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/permaprops.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/permaprops.lua new file mode 100644 index 0000000..d2ee2ef --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/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/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/precision.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/precision.lua new file mode 100644 index 0000000..8b6e63a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/gmod_tool/stools/precision.lua @@ -0,0 +1,1633 @@ + +TOOL.Category = "Constraints" +TOOL.Name = "#Precision" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "mode" ] = "1" +TOOL.ClientConVar[ "user" ] = "1" + +TOOL.ClientConVar[ "freeze" ] = "1" +TOOL.ClientConVar[ "nocollide" ] = "1" +TOOL.ClientConVar[ "nocollideall" ] = "0" +TOOL.ClientConVar[ "rotation" ] = "15" +TOOL.ClientConVar[ "rotate" ] = "1" +TOOL.ClientConVar[ "offset" ] = "0" +TOOL.ClientConVar[ "forcelimit" ] = "0" +TOOL.ClientConVar[ "torquelimit" ] = "0" +TOOL.ClientConVar[ "friction" ] = "0" +TOOL.ClientConVar[ "width" ] = "1" +TOOL.ClientConVar[ "offsetpercent" ] = "1" +TOOL.ClientConVar[ "removal" ] = "0" +TOOL.ClientConVar[ "move" ] = "1" +TOOL.ClientConVar[ "physdisable" ] = "0" +TOOL.ClientConVar[ "ShadowDisable" ] = "0" +TOOL.ClientConVar[ "allowphysgun" ] = "0" +TOOL.ClientConVar[ "autorotate" ] = "0" +TOOL.ClientConVar[ "entirecontrap" ] = "0" +TOOL.ClientConVar[ "nudge" ] = "25" +TOOL.ClientConVar[ "nudgepercent" ] = "1" +TOOL.ClientConVar[ "disablesliderfix" ] = "0" + +//adv ballsocket +TOOL.ClientConVar[ "XRotMin" ] = "-180" +TOOL.ClientConVar[ "XRotMax" ] = "180" +TOOL.ClientConVar[ "YRotMin" ] = "-180" +TOOL.ClientConVar[ "YRotMax" ] = "180" +TOOL.ClientConVar[ "ZRotMin" ] = "-180" +TOOL.ClientConVar[ "ZRotMax" ] = "180" +TOOL.ClientConVar[ "XRotFric" ] = "0" +TOOL.ClientConVar[ "YRotFric" ] = "0" +TOOL.ClientConVar[ "ZRotFric" ] = "0" +TOOL.ClientConVar[ "FreeMov" ] = "0" + +//Removal +TOOL.ClientConVar[ "removal_nocollide" ] = "1" +TOOL.ClientConVar[ "removal_weld" ] = "1" +TOOL.ClientConVar[ "removal_axis" ] = "1" +TOOL.ClientConVar[ "removal_ballsocket" ] = "1" +TOOL.ClientConVar[ "removal_advballsocket" ]= "1" +TOOL.ClientConVar[ "removal_slider" ] = "1" +TOOL.ClientConVar[ "removal_parent" ] = "1" +TOOL.ClientConVar[ "removal_other" ] = "1" + + +TOOL.ClientConVar[ "enablefeedback" ] = "1" +TOOL.ClientConVar[ "chatfeedback" ] = "1" +TOOL.ClientConVar[ "nudgeundo" ] = "0" +TOOL.ClientConVar[ "moveundo" ] = "1" +TOOL.ClientConVar[ "rotateundo" ] = "1" + +function TOOL:DoParent( Ent1, Ent2 ) + local TempEnt = Ent2 + if !(Ent1 && Ent1:IsValid() && Ent1:EntIndex() != 0) then + self:SendMessage( "Oops, First Target was world or something invalid" ) + return + end + if !(Ent2 && Ent2:IsValid() && Ent2:EntIndex() != 0) then + self:SendMessage( "Oops, Second Target was world or something invalid" ) + return + end + if ( Ent1 == Ent2 ) then + self:SendMessage( "Oops, Can't parent something to itself" ) + return + end + Ent1:SetMoveType(MOVETYPE_NONE) + local disablephysgun = self:GetClientNumber( "allowphysgun" ) == 0 + Ent1.PhysgunDisabled = disablephysgun + Ent1:SetUnFreezable( disablephysgun ) + local Phys1 = Ent1:GetPhysicsObject() + if Phys1:IsValid() then + Phys1:EnableCollisions( false ) + end + while true do + if ( !TempEnt:GetParent():IsValid() ) then + Ent1:SetParent( Ent2 ) + if self:GetClientNumber( "entirecontrap" ) == 0 then self:SendMessage( "Parent Set." ) end + Phys1:Wake() + break + elseif ( TempEnt:GetParent() == Ent1 ) then + UndoParent( TempEnt ) + timer.Simple( 0.1, function()//delay to stop crash + Ent1.SetParent( Ent1, Ent2) + end) + self:SendMessage( "Oops, Closed Parent Loop Detected; Broken loop and set parent." ) + break + else + TempEnt = TempEnt:GetParent() + end + end + Phys1:Wake() + //Phys1:UpdateShadow(Ent1:GetAngles(),Ent1:GetAngles()) +end + +function TOOL:UndoParent( Ent1 ) + Ent1:SetParent( nil ) + Ent1:SetMoveType(MOVETYPE_VPHYSICS) + Ent1.PhysgunDisabled = false + Ent1:SetUnFreezable( false ) + local Phys1 = Ent1:GetPhysicsObject() + if Phys1:IsValid() then + Phys1:EnableCollisions( true ) + Phys1:Wake() + //Phys1:UpdateShadow(Ent1:GetAngles(),Ent1:GetAngles()) + end +end + +function TOOL:DoApply(CurrentEnt, FirstEnt, autorotate, nocollideall, ShadowDisable ) + local CurrentPhys = CurrentEnt:GetPhysicsObject() + + //local col = CurrentEnt:GetCollisionGroup() + //col = 19 + //CurrentEnt:SetCollisionGroup(col) + //self:SendMessage("New group: "..col) + + //if ( CurrentPhys:IsDragEnabled() ) then + //end + //CurrentPhys:SetAngleDragCoefficient(1.05) + //CurrentPhys:SetDragCoefficient(1.05) + + if ( autorotate ) then + if ( CurrentEnt == FirstEnt ) then//Snap-rotate original object first. Rest needs to rotate around it. + local angle = CurrentPhys:RotateAroundAxis( Vector( 0, 0, 1 ), 0 ) + self.anglechange = Vector( angle.p - (math.Round(angle.p/45))*45, angle.r - (math.Round(angle.r/45))*45, angle.y - (math.Round(angle.y/45))*45 ) + if ( table.Count(self.TaggedEnts) == 1 ) then + angle.p = (math.Round(angle.p/45))*45 + angle.r = (math.Round(angle.r/45))*45//Only rotate on these axies if it's singular. + end + angle.y = (math.Round(angle.y/45))*45 + CurrentPhys:SetAngles( angle ) + else + local distance = math.sqrt(math.pow((CurrentEnt:GetPos().X-FirstEnt:GetPos().X),2)+math.pow((CurrentEnt:GetPos().Y-FirstEnt:GetPos().Y),2)) + local theta = math.atan((CurrentEnt:GetPos().Y-FirstEnt:GetPos().Y) / (CurrentEnt:GetPos().X-FirstEnt:GetPos().X)) - math.rad(self.anglechange.Z) + if (CurrentEnt:GetPos().X-FirstEnt:GetPos().X) < 0 then + CurrentEnt:SetPos( Vector( FirstEnt:GetPos().X - (distance*(math.cos(theta))), FirstEnt:GetPos().Y - (distance*(math.sin(theta))), CurrentEnt:GetPos().Z ) ) + else + CurrentEnt:SetPos( Vector( FirstEnt:GetPos().X + (distance*(math.cos(theta))), FirstEnt:GetPos().Y + (distance*(math.sin(theta))), CurrentEnt:GetPos().Z ) ) + end + CurrentPhys:SetAngles( CurrentPhys:RotateAroundAxis( Vector( 0, 0, -1 ), self.anglechange.Z ) ) + end + end + + CurrentPhys:EnableCollisions( !nocollideall ) + CurrentEnt:DrawShadow( !ShadowDisable ) + if physdis then + CurrentEnt:SetMoveType(MOVETYPE_NONE) + CurrentEnt.PhysgunDisabled = disablephysgun + CurrentEnt:SetUnFreezable( disablephysgun ) + else + CurrentEnt:SetMoveType(MOVETYPE_VPHYSICS) + CurrentEnt.PhysgunDisabled = false + CurrentEnt:SetUnFreezable( false ) + end + CurrentPhys:Wake() +end + +function TOOL:CreateUndo(constraint,undoname) + if (constraint) then + undo.Create(undoname) + undo.AddEntity( constraint ) + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + self:GetOwner():AddCleanup( "constraints", constraint ) + end +end + +function TOOL:UndoRepairToggle() + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + if !(CurrentEnt == Ent2 ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() && !CurrentEnt:GetParent():IsValid() ) then//parent? + if ( CurrentEnt:GetPhysicsObjectCount() < 2 ) then //not a ragdoll + if ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_WORLD ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_NONE ) + elseif ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_NONE ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + if ( speeddamp == 0 && angledamp == 0 ) then + CurrentPhys:SetDamping( 5, 5 ) + elseif ( speeddamp == 5 && angledamp == 5 ) then + CurrentPhys:SetDamping( 0, 0 ) + end + CurrentPhys:Wake() + end + end + end + end + end + self.RepairTodo = false +end + +function TOOL:DoConstraint(mode) + self:SetStage(0) + // Get information we're about to use + local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2) + + if ( !Ent1:IsValid() || CLIENT ) then + self:ClearObjects() + return false//Something happened to original target, don't continue + end + // Get client's CVars + local forcelimit = self:GetClientNumber( "forcelimit", 0 ) + local freeze = util.tobool( self:GetClientNumber( "freeze", 1 ) ) + local nocollide = self:GetClientNumber( "nocollide", 0 ) + local nocollideall = util.tobool( self:GetClientNumber( "nocollideall", 0 ) ) + local torquelimit = self:GetClientNumber( "torquelimit", 0 ) + local width = self:GetClientNumber( "width", 1 ) + local friction = self:GetClientNumber( "friction", 0 ) + local physdis = util.tobool( self:GetClientNumber( "physdisable", 0 ) ) + local ShadowDisable = util.tobool( self:GetClientNumber( "ShadowDisable", 0 ) ) + local autorotate = util.tobool(self:GetClientNumber( "autorotate",1 )) + local removal_nocollide = util.tobool(self:GetClientNumber( "removal_nocollide",1 )) + local removal_weld = util.tobool(self:GetClientNumber( "removal_weld",1 )) + local removal_axis = util.tobool(self:GetClientNumber( "removal_axis",1 )) + local removal_ballsocket = util.tobool(self:GetClientNumber( "removal_ballsocket",1 )) + local removal_advballsocket = util.tobool(self:GetClientNumber( "removal_advballsocket",1 )) + local removal_slider = util.tobool(self:GetClientNumber( "removal_slider",1 )) + local removal_parent = util.tobool(self:GetClientNumber( "removal_parent",1 )) + local removal_other = util.tobool(self:GetClientNumber( "removal_other",1 )) + local Bone1 = self:GetBone(1) + local LPos1 = self:GetLocalPos(1) + local Bone2 = nil + local LPos2 = nil + if ( Ent2 && (Ent2:IsValid() || Ent2:IsWorld()) ) then + Bone2 = self:GetBone(2) + LPos2 = self:GetLocalPos(2) + end + local Phys1 = self:GetPhys(1) + + local NumApp = 0 + + + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + if !(CurrentEnt == Ent2 ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() && !CurrentEnt:GetParent():IsValid() ) then//parent? + if ( CurrentEnt:GetPhysicsObjectCount() < 2 ) then //not a ragdoll + if ( util.tobool( nocollide ) && (mode == 1 || mode == 3)) then // not weld/axis/ballsocket or single application + local constraint = constraint.NoCollide(CurrentEnt, Ent2, 0, Bone2) + end + if ( mode == 1 ) then //Apply + self:DoApply( CurrentEnt, Ent1, autorotate, nocollideall, ShadowDisable ) + elseif ( mode == 2 ) then //Rotate + //self:SendMessage("Sorry, No entire contraption rotating... yet") + //return false//TODO: Entire contrpation rotaton + elseif ( mode == 3 ) then //move + //self:SendMessage("Sorry, No entire contraption moving... yet") + //return false//todo: entire contraption move/snap + elseif ( mode == 4 ) then //weld + local constr = constraint.Weld( CurrentEnt, Ent2, 0, Bone2, forcelimit, util.tobool( nocollide ) ) + self:CreateUndo(constr,"Precision_Weld") + elseif ( mode == 5 ) then //doaxis + local constr = constraint.Axis( CurrentEnt, Ent2, Bone1, Bone2, LPos1, LPos2, forcelimit, torquelimit, friction, nocollide ) + self:CreateUndo(constr,"Precision_Axis") + elseif ( mode == 6 ) then //ballsocket + local constr = constraint.Ballsocket( CurrentEnt, Ent2, 0, Bone2, LPos2, forcelimit, torquelimit, nocollide ) + self:CreateUndo(constr,"Precision_Ballsocket") + elseif ( mode == 7 ) then //adv ballsocket + local constr = constraint.AdvBallsocket( CurrentEnt, Ent2, 0, Bone2, LPos1, LPos2, forcelimit, torquelimit, self:GetClientNumber( "XRotMin", -180 ), self:GetClientNumber( "YRotMin", -180 ), self:GetClientNumber( "ZRotMin", -180 ), self:GetClientNumber( "XRotMax", 180 ), self:GetClientNumber( "YRotMax", 180 ), self:GetClientNumber( "ZRotMax", 180 ), self:GetClientNumber( "XRotFric", 0 ), self:GetClientNumber( "YRotFric", 0 ), self:GetClientNumber( "ZRotFric", 0 ), self:GetClientNumber( "FreeMov", 0 ), nocollide ) + self:CreateUndo(constr,"Precision_Advanced_Ballsocket") + elseif ( mode == 8 ) then //slider + local constraint0 = constraint.Slider( CurrentEnt, Ent2, 0, Bone2, LPos1, LPos2, width ) + if (constraint0) then + undo.Create("Precision_Slider") + if ( self:GetClientNumber( "disablesliderfix" ) == 0 ) then + local constraint2 = constraint.AdvBallsocket( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, 0, 0, 0, -180, -180, 0, 180, 180, 50, 0, 0, 1, 0 ) + if (constraint2) then + undo.AddEntity( constraint2 ) + end + local constraint3 = constraint.AdvBallsocket( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, 0, 0, -180, 0, -180, 180, 0, 180, 0, 50, 0, 1, 0 ) + if (constraint3) then + undo.AddEntity( constraint3 ) + end + local constraint4 = constraint.AdvBallsocket( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, 0, 0, -180, -180, 0, 180, 180, 0, 0, 0, 50, 1, 0 ) + if (constraint4) then + undo.AddEntity( constraint4 ) + end + end + undo.AddEntity( constraint0 ) + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + self:GetOwner():AddCleanup( "constraints", constraint0 ) + end + elseif ( mode == 9 ) then //Parent + self:DoParent( CurrentEnt, Ent2 ) + elseif ( mode == 10 && !self.RepairTodo ) then//Repair spaz + if ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_WORLD ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_NONE ) + elseif ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_NONE ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + //CurrentPhys:EnableGravity( !CurrentPhys:IsGravityEnabled() )//Can't disable gravity - sliders would go nuts and disappear. + local speeddamp,angledamp = CurrentPhys:GetDamping() + if ( speeddamp == 0 && angledamp == 0 ) then + CurrentPhys:SetDamping( 5, 5 ) + elseif ( speeddamp == 5 && angledamp == 5 ) then + CurrentPhys:SetDamping( 0, 0 ) + end + CurrentEnt:SetPos(CurrentEnt:GetPos()) + CurrentPhys:Wake() + elseif ( mode == 11 ) then //Removal + if ( CLIENT ) then return true end//? should probably be in more places + if ( removal_nocollide ) then + constraint.RemoveConstraints( CurrentEnt, "NoCollide" ) + CurrentPhys:EnableCollisions(true) + end + if ( removal_weld ) then + constraint.RemoveConstraints( CurrentEnt, "Weld" ) + end + if ( removal_axis ) then + constraint.RemoveConstraints( CurrentEnt, "Axis" ) + end + if ( removal_ballsocket ) then + constraint.RemoveConstraints( CurrentEnt, "Ballsocket" ) + end + if ( removal_advballsocket ) then + constraint.RemoveConstraints( CurrentEnt, "AdvBallsocket" ) + end + if ( removal_slider ) then + constraint.RemoveConstraints( CurrentEnt, "Slider" ) + end + if ( removal_parent) then + if ( CurrentEnt:GetParent():IsValid() ) then + self:UndoParent( CurrentEnt ) + end + end + if ( removal_other ) then + constraint.RemoveConstraints( CurrentEnt, "Elastic" ) + constraint.RemoveConstraints( CurrentEnt, "Hydraulic" ) + constraint.RemoveConstraints( CurrentEnt, "Keepupright" ) + constraint.RemoveConstraints( CurrentEnt, "Motor" ) + constraint.RemoveConstraints( CurrentEnt, "Muscle" ) + constraint.RemoveConstraints( CurrentEnt, "Pulley" ) + constraint.RemoveConstraints( CurrentEnt, "Rope" ) + constraint.RemoveConstraints( CurrentEnt, "Winch" ) + end + end + if ( mode <= 8 ) then + CurrentPhys:EnableMotion( !freeze ) + CurrentPhys:Wake() + end + end + end + end + end + NumApp = NumApp + 1 + end//Next + if ( mode == 1 ) then + self:SendMessage( NumApp .. " items targeted for apply." ) + elseif ( mode == 2 ) then + self:SendMessage( NumApp .. " items targeted for rotate." ) + elseif ( mode == 3 ) then + self:SendMessage( NumApp .. " items targeted for move." ) + elseif ( mode == 4 ) then + self:SendMessage( NumApp .. " items targeted for weld." ) + elseif ( mode == 5 ) then + self:SendMessage( NumApp .. " items targeted for axis." ) + elseif ( mode == 6 ) then + self:SendMessage( NumApp .. " items targeted for ballsocket." ) + elseif ( mode == 7 ) then + self:SendMessage( NumApp .. " items targeted for adv. ballsocket." ) + elseif ( mode == 8 ) then + self:SendMessage( NumApp .. " items targeted for slider." ) + elseif ( mode == 9 ) then + self:SendMessage( NumApp .. " items targeted for parenting." ) + elseif ( mode == 10 ) then + self:SendMessage( NumApp .. " items targeted for repair." ) + elseif ( mode == 11 ) then + self:SendMessage( NumApp .. " items targeted for constraint removal." ) + end + + + if ( mode == 10 ) then + self.RepairTodo = true + timer.Simple( 1.0, function() + self:ClearSelection() + end) + else + self:ClearSelection() + end + // Clear the objects so we're ready to go again + self:ClearObjects() +end + +function TOOL:SendMessage( message ) + if ( self:GetClientNumber( "enablefeedback" ) == 0 ) then return end + if ( self:GetClientNumber( "chatfeedback" ) == 1 ) then + self:GetOwner():PrintMessage( HUD_PRINTTALK, "Tool: " .. message ) + else + self:GetOwner():PrintMessage( HUD_PRINTCENTER, message ) + end +end + +function TOOL:TargetValidity ( trace, Phys ) //TODO: Parented stuff should return 1 + if ( SERVER && (!util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) || !Phys:IsValid()) ) then + local mode = self:GetClientNumber( "mode" ) + if ( trace.Entity:GetParent():IsValid() ) then + return 2//Valid parent, but itself isn't + else + return 0//No valid phys + end + elseif ( trace.Entity:IsPlayer() ) then + return 0// Don't attach players, or to players + elseif ( trace.HitWorld ) then + return 1// Only allow second click to be here... + else + return 3//Everything seems good + end +end + +function TOOL:StartRotate() + local Ent = self:GetEnt(1) + local Phys = self:GetPhys(1) + local oldposu = Ent:GetPos() + local oldangles = Ent:GetAngles() + + local function MoveUndo( Undo, Entity, oldposu, oldangles ) + if Entity:IsValid() then + Entity:SetAngles( oldangles ) + Entity:SetPos( oldposu ) + end + end + + if ( self:GetClientNumber( "rotateundo" )) then + if SERVER then + undo.Create("Precision_Rotate") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( MoveUndo, Ent, oldposu, oldangles ) + undo.Finish() + end + end + + if IsValid( Phys ) then + Phys:EnableMotion( false ) //else it drifts + end + + local rotation = self:GetClientNumber( "rotation" ) + if ( rotation < 0.02 ) then rotation = 0.02 end + self.axis = self:GetNormal(1) + self.axisY = self.axis:Cross(Ent:GetUp()) + if self:WithinABit( self.axisY, Vector(0,0,0) ) then + self.axisY = self.axis:Cross(Ent:GetForward()) + end + self.axisZ = self.axisY:Cross(self.axis) + self.realdegrees = 0 + self.lastdegrees = -((rotation/2) % rotation) + self.realdegreesY = 0 + self.lastdegreesY = -((rotation/2) % rotation) + self.realdegreesZ = 0 + self.lastdegreesZ = -((rotation/2) % rotation) + self.OldPos = self:GetPos(1)//trace.HitPos +end + +function TOOL:DoMove() + // Get information we're about to use + local Norm1, Norm2 = self:GetNormal(1), self:GetNormal(2) + local Phys1, Phys2 = self:GetPhys(1), self:GetPhys(2) + + local Ang1, Ang2 = Norm1:Angle(), (Norm2 * -1):Angle() + if self:GetClientNumber( "autorotate" ) == 1 then + Ang2.p = (math.Round(Ang2.p/45))*45 + Ang2.r = (math.Round(Ang2.r/45))*45 + Ang2.y = (math.Round(Ang2.y/45))*45 + Norm2 = Ang2:Forward() * -1 + end + + + local oldposu = self:GetEnt(1):GetPos() + local oldangles = self:GetEnt(1):GetAngles() + + local function MoveUndo( Undo, Entity, oldposu, oldangles ) + if Entity:IsValid() then + Entity:SetAngles( oldangles ) + Entity:SetPos( oldposu ) + end + end + if self:GetClientNumber( "moveundo" ) == 1 then + undo.Create("Precision Move") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( MoveUndo, self:GetEnt(1), oldposu, oldangles ) + undo.Finish() + end + + local rotation = self:GetClientNumber( "rotation" ) + if ( rotation < 0.02 ) then rotation = 0.02 end + if ( (self:GetClientNumber( "rotate" ) == 1 && mode != 1) || mode == 2) then//Set axies for rotation mode directions + self.axis = Norm2 + self.axisY = self.axis:Cross(Vector(0,1,0)) + if self:WithinABit( self.axisY, Vector(0,0,0) ) then + self.axisY = self.axis:Cross(Vector(0,0,1)) + end + self.axisY:Normalize() + self.axisZ = self.axisY:Cross(self.axis) + self.axisZ:Normalize() + self.realdegrees = 0 + self.lastdegrees = -((rotation/2) % rotation) + self.realdegreesY = 0 + self.lastdegreesY = -((rotation/2) % rotation) + self.realdegreesZ = 0 + self.lastdegreesZ = -((rotation/2) % rotation) + else + self.axis = Norm2 + self.axisY = self.axis:Cross(Vector(0,1,0)) + if self:WithinABit( self.axisY, Vector(0,0,0) ) then + self.axisY = self.axis:Cross(Vector(0,0,1)) + end + self.axisY:Normalize() + self.axisZ = self.axisY:Cross(self.axis) + self.axisZ:Normalize() + end + + + + local TargetAngle = Phys1:AlignAngles( Ang1, Ang2 )//Get angle Phys1 would be at if difference between Ang1 and Ang2 was added + + + if self:GetClientNumber( "autorotate" ) == 1 then + TargetAngle.p = (math.Round(TargetAngle.p/45))*45 + TargetAngle.r = (math.Round(TargetAngle.r/45))*45 + TargetAngle.y = (math.Round(TargetAngle.y/45))*45 + end + + Phys1:SetAngles( TargetAngle ) + + + local NewOffset = math.Clamp( self:GetClientNumber( "offset" ), -5000, 5000 ) + local offsetpercent = self:GetClientNumber( "offsetpercent" ) == 1 + if ( offsetpercent ) then + local Ent2 = self:GetEnt(2) + local glower = Ent2:OBBMins() + local gupper = Ent2:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(Norm2,Ent2:GetForward()) then + height = math.abs(gupper.x - glower.x)-0.5 + elseif self:WithinABit(Norm2,Ent2:GetRight()) then + height = math.abs(gupper.y - glower.y)-0.5 + end + NewOffset = NewOffset / 100 + NewOffset = NewOffset * height + end + Norm2 = Norm2 * (-0.0625 + NewOffset) + local TargetPos = self:GetPos(2) + (Phys1:GetPos() - self:GetPos(1)) + (Norm2) + //self:SetPos(2) + + // Set the position + + Phys1:SetPos( TargetPos ) + Phys1:EnableMotion( false ) + + // Wake up the physics object so that the entity updates + Phys1:Wake() +end + +function TOOL:ToggleColor( CurrentEnt ) + color = CurrentEnt:GetColor() + color["a"] = color["a"] - 128 + if ( color["a"] < 0 ) then + color["a"] = color["a"] + 256 + end + color["r"] = color["r"] - 128 + if ( color["r"] < 0 ) then + color["r"] = color["r"] + 256 + end + color["g"] = color["g"] - 128 + if ( color["g"] < 0 ) then + color["g"] = color["g"] + 256 + end + color["b"] = color["b"] - 128 + if ( color["b"] < 0 ) then + color["b"] = color["b"] + 256 + end + CurrentEnt:SetColor( color ) + if ( color["a"] == 255 ) then + CurrentEnt:SetRenderMode( 0 ) + else + CurrentEnt:SetRenderMode( 1 ) + end +end + +function TOOL:ClearSelection() + if ( self.RepairTodo ) then + self:UndoRepairToggle() + end + if ( self.TaggedEnts ) then + local color + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + self:ToggleColor(CurrentEnt) + end + end + end + end + self.TaggedEnts = {} +end + +function TOOL:SelectEnts(StartEnt, AllConnected) + self:ClearSelection() + if ( CLIENT ) then return end + local color + if ( AllConnected == 1 ) then + local NumApp = 0 + EntsTab = {} + ConstsTab = {} + GetAllEnts(StartEnt, self.TaggedEnts, EntsTab, ConstsTab) + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + self:ToggleColor(CurrentEnt) + end + end + NumApp = NumApp + 1 + end + self:SendMessage( NumApp .. " objects selected." ) + else + if ( StartEnt and StartEnt:IsValid() ) then + local CurrentPhys = StartEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + table.insert(self.TaggedEnts, StartEnt) + self:ToggleColor(StartEnt) + end + end + end + +end + +function TOOL:LeftClick( trace ) + local stage = self:GetStage()//0 = started, 1 = moving/second target, 2 = rotation? + local mode = self:GetClientNumber( "mode" ) + local moving = ( mode == 3 || (self:GetClientNumber( "move" ) == 1 && mode >= 3 && mode <= 8 ) ) + local rotating = ( self:GetClientNumber( "rotate" ) == 1 ) + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + + + if ( stage == 0 ) then//first click - choose a target. + if ( self:TargetValidity(trace, Phys) <= 1 ) then + return false//No phys or hit world + end + self:SetObject( 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + if (self:GetClientNumber( "entirecontrap" ) == 1 || mode == 10 ) then + self:SelectEnts(trace.Entity,1) + else + self:SelectEnts(trace.Entity,0) + end + if ( mode == 1 || mode == 10 || mode == 11 ) then //Don't care about stage, always apply. + self:DoConstraint(mode) + else + if ( mode == 9 ) then + self:SetStage(1) + else + if ( moving ) then//Moving + self:StartGhostEntity( trace.Entity ) + self:SetStage(1) + elseif ( mode == 2 ) then//Straight to rotate + self:StartRotate() + self:SetStage(2) + else + self:SetStage(1) + end + end + end + elseif ( stage == 1 ) then//Second click + self:SetObject( 2, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + if ( self:GetEnt(1) == self:GetEnt(2) ) then + SavedPos = self:GetPos(2) + end + if ( mode == 9 ) then + self:DoConstraint(mode) + else + if ( moving ) then + if ( CLIENT ) then + self:ReleaseGhostEntity() + return true + end + if ( SERVER && !game.SinglePlayer() ) then + self:ReleaseGhostEntity() + //return true + end + self:DoMove() + end + if ( rotating ) then + self:StartRotate() + self:SetStage(2) + else + self:DoConstraint(mode) + end + end + elseif ( stage == 2 ) then//Done rotate + self:DoConstraint(mode) + end + return true +end + +function TOOL:WithinABit( v1, v2 ) + local tol = 0.1 + local da = v1.x-v2.x + local db = v1.y-v2.y + local dc = v1.z-v2.z + if da < tol && da > -tol && db < tol && db > -tol && dc < tol && dc > -tol then + return true + else + da = v1.x+v2.x + db = v1.y+v2.y + dc = v1.z+v2.z + if da < tol && da > -tol && db < tol && db > -tol && dc < tol && dc > -tol then + return true + else + return false + end + end +end + +if ( SERVER ) then + + function GetAllEnts( Ent, OrderedEntList, EntsTab, ConstsTab ) + if ( Ent and Ent:IsValid() ) and ( !EntsTab[ Ent:EntIndex() ] ) then + EntsTab[ Ent:EntIndex() ] = Ent + table.insert(OrderedEntList, Ent) + if ( !constraint.HasConstraints( Ent ) ) then return OrderedEntList end + for key, ConstraintEntity in pairs( Ent.Constraints ) do + if ( !ConstsTab[ ConstraintEntity ] ) then + ConstsTab[ ConstraintEntity ] = true + local ConstTable = ConstraintEntity:GetTable() + for i=1, 6 do + local e = ConstTable[ "Ent"..i ] + if ( e and e:IsValid() ) and ( !EntsTab[ e:EntIndex() ] ) then + GetAllEnts( e, OrderedEntList, EntsTab, ConstsTab ) + end + end + end + end + end + return OrderedEntList + end + + function GetAllConstraints( EntsTab ) + local ConstsTab = {} + for key, Ent in pairs( EntsTab ) do + if ( Ent and Ent:IsValid() ) then + local MyTable = constraint.GetTable( Ent ) + for key, Constraint in pairs( MyTable ) do + if ( !ConstsTab[ Constraint.Constraint ] ) then + ConstsTab[ Constraint.Constraint ] = Constraint + end + end + end + end + return ConstsTab + end +end + +function TOOL:UpdateCustomGhost( ghost, player, offset ) + + // Ghost is identically buggy to that of easyweld... welding two frozen props and two unfrozen props yields different ghosts even if identical allignment + + if (ghost == nil) then return end + if (!ghost:IsValid()) then ghost = nil return end + + local tr = util.GetPlayerTrace( player, player:GetAimVector() ) + local trace = util.TraceLine( tr ) + if (!trace.Hit) then return end + + local Ang1, Ang2 = self:GetNormal(1):Angle(), (trace.HitNormal * -1):Angle() + local TargetAngle = self:GetEnt(1):AlignAngles( Ang1, Ang2 ) + + self.GhostEntity:SetPos( self:GetEnt(1):GetPos() ) + + if self:GetClientNumber( "autorotate" ) == 1 then + TargetAngle.p = (math.Round(TargetAngle.p/45))*45 + TargetAngle.r = (math.Round(TargetAngle.r/45))*45 + TargetAngle.y = (math.Round(TargetAngle.y/45))*45 + end + self.GhostEntity:SetAngles( TargetAngle ) + + local TraceNormal = trace.HitNormal + + local offsetpercent = self:GetClientNumber( "offsetpercent" ) == 1 + local NewOffset = offset + if ( offsetpercent ) then + local glower = trace.Entity:OBBMins() + local gupper = trace.Entity:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(TraceNormal,trace.Entity:GetForward()) then + height = math.abs(gupper.x - glower.x) -0.5 + elseif self:WithinABit(TraceNormal,trace.Entity:GetRight()) then + height = math.abs(gupper.y - glower.y) -0.5 + end + NewOffset = NewOffset / 100 + NewOffset = NewOffset * height + end + + local TranslatedPos = ghost:LocalToWorld( self:GetLocalPos(1) ) + local TargetPos = trace.HitPos + (self:GetEnt(1):GetPos() - TranslatedPos) + (TraceNormal*NewOffset) + + self.GhostEntity:SetPos( TargetPos ) +end + + +function TOOL:Think() + //if CLIENT then return end + local pl = self:GetOwner() + local wep = pl:GetActiveWeapon() + if not wep:IsValid() or wep:GetClass() != "gmod_tool" or pl:GetInfo("gmod_toolmode") != "precision" then return end + + if (self:NumObjects() < 1) then return end + local Ent1 = self:GetEnt(1) + if ( SERVER ) then + if ( !Ent1:IsValid() ) then + self:ClearObjects() + return + end + end + local mode = self:GetClientNumber( "mode" ) + + if self:NumObjects() == 1 && mode != 2 then + if ( (self:GetClientNumber( "move" ) == 1 && mode >= 3) || mode == 3 ) then + if ( mode <= 8 ) then//no move = no ghost in parent mode + local offset = math.Clamp( self:GetClientNumber( "offset" ), -5000, 5000 ) + self:UpdateCustomGhost( self.GhostEntity, self:GetOwner(), offset ) + end + end + else + local rotate = (self:GetClientNumber( "rotate" ) == 1 && mode != 1) || mode == 2 + if ( SERVER && rotate && mode <= 8 ) then + local offset = math.Clamp( self:GetClientNumber( "offset" ), -5000, 5000 ) + + local Phys1 = self:GetPhys(1) + + local cmd = self:GetOwner():GetCurrentCommand() + + local rotation = self:GetClientNumber( "rotation" ) + if ( rotation < 0.02 ) then rotation = 0.02 end + local degrees = cmd:GetMouseX() * 0.02 + + local newdegrees = 0 + local changedegrees = 0 + + local angle = 0 + if( self:GetOwner():KeyDown( IN_RELOAD ) ) then + self.realdegreesY = self.realdegreesY + degrees + newdegrees = self.realdegreesY - ((self.realdegreesY + (rotation/2)) % rotation) + changedegrees = self.lastdegreesY - newdegrees + self.lastdegreesY = newdegrees + angle = Phys1:RotateAroundAxis( self.axisY , changedegrees ) + elseif( self:GetOwner():KeyDown( IN_ATTACK2 ) ) then + self.realdegreesZ = self.realdegreesZ + degrees + newdegrees = self.realdegreesZ - ((self.realdegreesZ + (rotation/2)) % rotation) + changedegrees = self.lastdegreesZ - newdegrees + self.lastdegreesZ = newdegrees + angle = Phys1:RotateAroundAxis( self.axisZ , changedegrees ) + else + self.realdegrees = self.realdegrees + degrees + newdegrees = self.realdegrees - ((self.realdegrees + (rotation/2)) % rotation) + changedegrees = self.lastdegrees - newdegrees + self.lastdegrees = newdegrees + angle = Phys1:RotateAroundAxis( self.axis , changedegrees ) + end + Phys1:SetAngles( angle ) + + if ( ( self:GetClientNumber( "move" ) == 1 && mode >= 3) || mode == 3 ) then + local WPos2 = self:GetPos(2) + local Ent2 = self:GetEnt(2) + // Move so spots join up + local Norm2 = self:GetNormal(2) + + local NewOffset = offset + local offsetpercent = self:GetClientNumber( "offsetpercent" ) == 1 + if ( offsetpercent ) then + local glower = Ent2:OBBMins() + local gupper = Ent2:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(Norm2,Ent2:GetForward()) then + height = math.abs(gupper.x - glower.x) -0.5 + elseif self:WithinABit(Norm2,Ent2:GetRight()) then + height = math.abs(gupper.y - glower.y) -0.5 + end + NewOffset = NewOffset / 100 + NewOffset = NewOffset * height + end + + Norm2 = Norm2 * (-0.0625 + NewOffset) + local TargetPos = Vector(0,0,0) + if ( self:GetEnt(1) == self:GetEnt(2) ) then + ////////////////////////////////////////// + TargetPos = SavedPos + (Phys1:GetPos() - self:GetPos(1)) + (Norm2) + else + TargetPos = WPos2 + (Phys1:GetPos() - self:GetPos(1)) + (Norm2) + end + Phys1:SetPos( TargetPos ) + else + // Move so rotating on axis + + local TargetPos = (Phys1:GetPos() - self:GetPos(1)) + self.OldPos + Phys1:SetPos( TargetPos ) + end + Phys1:Wake() + end + end +end + +function TOOL:Nudge( trace, direction ) + if (!trace.Entity:IsValid() || trace.Entity:IsPlayer() ) then return false end + local Phys1 = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + local offsetpercent = self:GetClientNumber( "nudgepercent" ) == 1 + local offset = self:GetClientNumber( "nudge", 100 ) + local max = 8192 + if ( offsetpercent != 1 ) then + if ( offset > max ) then + offset = max + elseif ( offset < -max ) then + offset = -max + end + end + //if ( offset == 0 ) then offset = 1 end + local NewOffset = offset + if ( offsetpercent ) then + local glower = trace.Entity:OBBMins() + local gupper = trace.Entity:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(trace.HitNormal,trace.Entity:GetForward()) then + height = math.abs(gupper.x - glower.x)-0.5 + elseif self:WithinABit(trace.HitNormal,trace.Entity:GetRight()) then + height = math.abs(gupper.y - glower.y)-0.5 + end + NewOffset = NewOffset / 100 + local cap = math.floor(max / height)//No more than max units. + if ( NewOffset > cap ) then + NewOffset = cap + elseif ( NewOffset < -cap ) then + NewOffset = -cap + end + NewOffset = NewOffset * height + end + + if ( self:GetClientNumber( "entirecontrap" ) == 1 ) then + local NumApp = 0 + local TargetEnts = {} + local EntsTab = {} + local ConstsTab = {} + GetAllEnts(trace.Entity, TargetEnts, EntsTab, ConstsTab) + for key,CurrentEnt in pairs(TargetEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + + /*if ( self:GetClientNumber( "nudgeundo" ) == 1 ) then + local oldpos = CurrentPhys:GetPos() + local function NudgeUndo( Undo, Entity, oldpos ) + if CurrentEnt:IsValid() then + CurrentEnt:SetPos( oldpos ) + end + end + undo.Create("Nrecision Nudge") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( NudgeUndo, CurrentEnt, oldpos ) + undo.Finish() + end*/// todo: all in 1 undo for mass nudging + + local TargetPos = CurrentPhys:GetPos() + trace.HitNormal * NewOffset * direction + CurrentPhys:SetPos( TargetPos ) + CurrentPhys:Wake() + if (CurrentEnt:GetMoveType() == 0 ) then //phys disabled, so move manually + CurrentEnt:SetPos( TargetPos ) + end + + end + end + NumApp = NumApp + 1 + end + if ( direction == -1 ) then + self:SendMessage( NumApp .. " items pushed." ) + elseif ( direction == 1 ) then + self:SendMessage( NumApp .. " items pulled." ) + else + self:SendMessage( NumApp .. " items nudged." ) + end + else + if ( self:GetClientNumber( "nudgeundo" ) == 1 ) then + local oldpos = Phys1:GetPos() + local function NudgeUndo( Undo, Entity, oldpos ) + if trace.Entity:IsValid() then + trace.Entity:SetPos( oldpos ) + end + end + undo.Create("Precision PushPull") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( NudgeUndo, trace.Entity, oldpos ) + undo.Finish() + end + local TargetPos = Phys1:GetPos() + trace.HitNormal * NewOffset * direction + Phys1:SetPos( TargetPos ) + Phys1:Wake() + if ( trace.Entity:GetMoveType() == 0 ) then + trace.Entity:SetPos( TargetPos ) + end + if ( direction == -1 ) then + self:SendMessage( "target pushed." ) + elseif ( direction == 1 ) then + self:SendMessage( "target pulled." ) + else + self:SendMessage( "target nudged." ) + end + end + return true +end + +function TOOL:RightClick( trace ) + local rotate = self:GetClientNumber( "rotate" ) == 1 + local mode = self:GetClientNumber( "mode" ) + if ( (mode == 2 && self:NumObjects() == 1) || (rotate && self:NumObjects() == 2 ) ) then + if ( CLIENT ) then return false end + else + if ( CLIENT ) then return true end + return self:Nudge( trace, -1 ) + end +end + +function TOOL:Reload( trace ) + local rotate = self:GetClientNumber( "rotate" ) == 1 + local mode = self:GetClientNumber( "mode" ) + if ( (mode == 2 && self:NumObjects() == 1) || (rotate && self:NumObjects() == 2 ) ) then + if ( CLIENT ) then return false end + else + if ( CLIENT ) then return true end + return self:Nudge( trace, 1 ) + end +end + +if CLIENT then + + language.Add( "Tool.precision.name", "Precision Tool 0.98e" ) + language.Add( "Tool.precision.desc", "Accurately moves/constrains objects" ) + language.Add( "Tool.precision.0", "Primary: Move/Apply | Secondary: Push | Reload: Pull" ) + language.Add( "Tool.precision.1", "Target the second item. If enabled, this will move the first item. (Swap weps to cancel)" ) + language.Add( "Tool.precision.2", "Rotate enabled: Turn left and right to rotate the object (Hold Reload or Secondary for other rotation directions!)" ) + + + language.Add("Undone.precision", "Undone Precision Constraint") + language.Add("Undone.precision.nudge", "Undone Precision PushPull") + language.Add("Undone.precision.rotate", "Undone Precision Rotate") + language.Add("Undone.precision.move", "Undone Precision Move") + language.Add("Undone.precision.weld", "Undone Precision Weld") + language.Add("Undone.precision.axis", "Undone Precision Axis") + language.Add("Undone.precision.ballsocket", "Undone Precision Ballsocket") + language.Add("Undone.precision.advanced.ballsocket", "Undone Precision Advanced Ballsocket") + language.Add("Undone.precision.slider", "Undone Precision Slider") + + local showgenmenu = 0//Seems to hide often, probably for the best + + local function AddDefControls( Panel ) + Panel:ClearControls() + + Panel:AddControl("ComboBox", + { + Label = "#Presets", + MenuButton = 1, + Folder = "precision", + Options = {}, + CVars = + { + [0] = "precision_offset", + [1] = "precision_forcelimit", + [2] = "precision_freeze", + [3] = "precision_nocollide", + [4] = "precision_nocollideall", + [5] = "precision_rotation", + [6] = "precision_rotate", + [7] = "precision_torquelimit", + [8] = "precision_friction", + [9] = "precision_mode", + [10] = "precision_width", + [11] = "precision_offsetpercent", + [12] = "precision_removal", + [13] = "precision_move", + [14] = "precision_physdisable", + [15] = "precision_advballsocket", + [16] = "precision_XRotMin", + [17] = "precision_XRotMax", + [18] = "precision_YRotMin", + [19] = "precision_YRotMax", + [20] = "precision_ZRotMin", + [21] = "precision_ZRotMax", + [22] = "precision_XRotFric", + [23] = "precision_YRotFric", + [24] = "precision_ZRotFric", + [25] = "precision_FreeMov", + [26] = "precision_ShadowDisable", + [27] = "precision_allowphysgun", + [28] = "precision_autorotate", + [29] = "precision_massmode", + [30] = "precision_nudge", + [31] = "precision_nudgepercent", + [32] = "precision_disablesliderfix" + } + }) + + //Panel:AddControl( "Label", { Text = "Secondary attack pushes, Reload pulls by this amount:", Description = "Phx 1x is 47.45, Small tiled cube is 11.8625 and thin is 3 exact units" } ) + Panel:AddControl( "Slider", { Label = "Push/Pull Amount", + Type = "Float", + Min = 1, + Max = 100, + Command = "precision_nudge", + Description = "Distance to push/pull props with altfire/reload"} ):SetDecimals( 4 ) + + + Panel:AddControl( "Checkbox", { Label = "Push/Pull as Percent (%) of target's depth", Command = "precision_nudgepercent", Description = "Unchecked = Exact units, Checked = takes % of width from target prop when pushing/pulling" } ) + + + local user = LocalPlayer():GetInfoNum( "precision_user", 0 ) + local mode = LocalPlayer():GetInfoNum( "precision_mode", 0 ) + //Panel:AddControl( "Label", { Text = "Primary attack uses the tool's main mode.", Description = "Select a mode and configure the options, be sure to try new things out!" } ) + + local list = vgui.Create("DListView") + + //17 per item + 16 for title + local height = 203 //All 11 shown + if ( user < 2 ) then + height = 135 //7 shown + elseif ( user < 3 ) then + height = 170 //9 shown + end + + + list:SetSize(30,height) + //list:SizeToContents() + list:AddColumn("Tool Mode") + list:SetMultiSelect(false) + function list:OnRowSelected(LineID, line) + if not (mode == LineID) then + RunConsoleCommand("precision_setmode", LineID) + end + end + + if ( mode == 1 ) then + list:AddLine(" 1 ->Apply<- (Directly apply settings to target)") + else + list:AddLine(" 1 Apply (Directly apply settings to target)") + end + if ( mode == 2 ) then + list:AddLine(" 2 ->Rotate<- (Turn an object without moving it)") + else + list:AddLine(" 2 Rotate (Turn an object without moving it)") + end + if ( mode == 3 ) then + list:AddLine(" 3 ->Move<- (Snap objects together - Great for building!)") + else + list:AddLine(" 3 Move (Snap objects together - Great for building!)") + end + if ( mode == 4 ) then + list:AddLine(" 4 ->Weld<-") + else + list:AddLine(" 4 Weld") + end + if ( mode == 5 ) then + list:AddLine(" 5 ->Axis<-") + else + list:AddLine(" 5 Axis") + end + if ( mode == 6 ) then + list:AddLine(" 6 ->Ballsocket<-") + else + list:AddLine(" 6 Ballsocket") + end + if ( user >= 2 ) then + if ( mode == 7 ) then + list:AddLine(" 7 ->Adv Ballsocket<-") + else + list:AddLine(" 7 Adv Ballsocket") + end + if ( mode == 8 ) then + list:AddLine(" 8 ->Slider<-") + else + list:AddLine(" 8 Slider") + end + end + if ( user >= 3 ) then + if ( mode == 9 ) then + list:AddLine(" 9 ->Parent<- (Like a solid weld, but without object collision)") + else + list:AddLine(" 9 Parent (Like a solid weld, but without object collision)") + end + if ( mode == 10 ) then + list:AddLine("10 ->Repair<- (Attempts to fix a flailing contraption)") + else + list:AddLine("10 Repair (Attempts to fix a flailing contraption)") + end + end + if ( mode == 11 ) then + list:AddLine("11 ->Removal<- (Undoes constraints from target)") + else + list:AddLine("11 Removal (Undoes constraints from target)") + end + list:SortByColumn(1) + Panel:AddItem(list) + + if ( mode >= 4 && mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "Move Target? ('Easy' constraint mode)", Command = "precision_move", Description = "Uncheck this to apply the constraint without altering positions." } ) + end + if ( mode >= 3 && mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "Rotate Target? (Rotation after moving)", Command = "precision_rotate", Description = "Uncheck this to remove the extra click for rotation. Handy for speed building." } ) + //Panel:AddControl( "Label", { Text = "This is the distance from touching of the targeted props after moving:", Description = "Use 0 mostly, % takes the second prop's width." } ) + Panel:AddControl( "Slider", { Label = "Snap Distance", + Type = "Float", + Min = 0, + Max = 10, + Command = "precision_offset", + Description = "Distance offset between joined props. Type in negative to inset when moving."} ) + Panel:AddControl( "Checkbox", { Label = "Snap distance as Percent (%) of target's depth", Command = "precision_offsetpercent", Description = "Unchecked = Exact units, Checked = takes % of width from second prop" } ) + end + if ( mode >= 2 && mode <= 8 ) then + Panel:AddControl( "Slider", { Label = "Rotation Snap (Degrees)", + Type = "Float", + Min = 0.02, + Max = 90, + Command = "precision_rotation", + Description = "Rotation rotates by this amount at a time. No more guesswork. Min: 0.02 degrees "} ):SetDecimals( 4 ) + end + if ( mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "Freeze Target", Command = "precision_freeze", Description = "Freeze props when this tool is used" } ) + + if ( mode >= 3 && mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "No Collide Targets", Command = "precision_nocollide", Description = "Nocollide pairs of props when this tool is used. Note: No current way to remove this constraint when used alone." } ) + end + end + + if ( user >= 2 || mode == 1 ) then + if ( (mode >= 3 && mode <= 8) || mode == 1 ) then + Panel:AddControl( "Checkbox", { Label = "Auto-align to world (nearest 45 degrees)", Command = "precision_autorotate", Description = "Rotates to the nearest world axis (similar to holding sprint and use with physgun)" } ) + end + + if ( mode == 1 ) then + Panel:AddControl( "Checkbox", { Label = "Disable target shadow", Command = "precision_ShadowDisable", Description = "Disables shadows cast from the prop" } ) + end + end + + if ( user >= 3 ) then + if ( mode == 1 ) then //apply + Panel:AddControl( "Checkbox", { Label = "Only Collide with Player", Command = "precision_nocollideall", Description = "Nocollides the first prop to everything and the world (except players collide with it). Warning: don't let it fall away through the world." } ) + Panel:AddControl( "Checkbox", { Label = "Disable Physics on object", Command = "precision_physdisable", Description = "Disables physics on the first prop (gravity, being shot etc won't effect it)" } ) + Panel:AddControl( "Checkbox", { Label = "Adv: Allow Physgun on PhysDisable objects", Command = "precision_allowphysgun", Description = "Disabled to stop accidents, use if you want to be able to manually move props after phyics disabling them (may break clipboxes)." } ) + + //Panel:AddControl( "Checkbox", { Label = "Drag", Command = "precision_drag", Description = "" } ) + end + if ( mode == 9 ) then //parent + Panel:AddControl( "Checkbox", { Label = "Adv: Allow Physgun on Parented objects", Command = "precision_allowphysgun", Description = "Disabled to stop accidents, use this if you want to play with the parenting hierarchy etc." } ) + end + end + if ( user >= 2 ) then + if ( mode != 2 && mode != 3 && mode != 10 ) then //todo: entire contrap move/rotate support + Panel:AddControl( "Checkbox", { Label = "Entire Contraption! (Everything connected to target)", Command = "precision_entirecontrap", Description = "For mass constraining or removal or nudging or applying of things. Yay generic." } ) + end + end + + if ( user >= 2 ) then + if ( (mode >= 4 && mode <= 7) ) then //breakable constraint + Panel:AddControl( "Slider", { Label = "Force Breakpoint", + Type = "Float", + Min = 0.0, + Max = 5000, + Command = "precision_forcelimit", + Description = "Applies to most constraint modes" } ) + end + + + if ( mode == 5 || mode == 6 || mode == 7 ) then //axis or ballsocket + Panel:AddControl( "Slider", { Label = "Torque Breakpoint", + Type = "Float", + Min = 0.0, + Max = 5000, + Command = "precision_torquelimit", + Description = "Breakpoint of turning/rotational force"} ) + end + end + + if ( mode == 5 ) then //axis + Panel:AddControl( "Slider", { Label = "Axis Friction", + Type = "Float", + Min = 0.0, + Max = 100, + Command = "precision_friction", + Description = "Turning resistance, this is best at 0 in most cases to conserve energy"} ) + end + + if ( mode ==7 ) then //adv ballsocket + Panel:AddControl( "Slider", { Label = "X Rotation Minimum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_XRotMin", + Description = "Rotation minimum of advanced ballsocket in X axis"} ) + + Panel:AddControl( "Slider", { Label = "X Rotation Maximum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_XRotMax", + Description = "Rotation maximum of advanced ballsocket in X axis"} ) + + Panel:AddControl( "Slider", { Label = "Y Rotation Minimum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_YRotMin", + Description = "Rotation minimum of advanced ballsocket in Y axis"} ) + + Panel:AddControl( "Slider", { Label = "Y Rotation Maximum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_YRotMax", + Description = "Rotation maximum of advanced ballsocket in Y axis"} ) + + Panel:AddControl( "Slider", { Label = "Z Rotation Minimum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_ZRotMin", + Description = "Rotation minimum of advanced ballsocket in Z axis"} ) + + Panel:AddControl( "Slider", { Label = "Z Rotation Maximum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_ZRotMax", + Description = "Rotation maximum of advanced ballsocket in Z axis"} ) + + Panel:AddControl( "Slider", { Label = "X Rotation Friction", + Type = "Float", + Min = 0, + Max = 100, + Command = "precision_XRotFric", + Description = "Rotation friction of advanced ballsocket in X axis"} ) + + Panel:AddControl( "Slider", { Label = "Y Rotation Friction", + Type = "Float", + Min = 0, + Max = 100, + Command = "precision_YRotFric", + Description = "Rotation friction of advanced ballsocket in Y axis"} ) + + Panel:AddControl( "Slider", { Label = "Z Rotation Friction", + Type = "Float", + Min = 0, + Max = 100, + Command = "precision_ZRotFric", + Description = "Rotation friction of advanced ballsocket in Z axis"} ) + + Panel:AddControl( "Checkbox", { Label = "Free Movement", Command = "precision_FreeMov", Description = "Only lock relative rotation, not position?" } ) + end + + if ( mode == 8 ) then //slider + Panel:AddControl( "Slider", { Label = "Slider Width", + Type = "Float", + Min = 0.0, + Max = 10, + Command = "precision_width", + Description = "Width of the slider black line (0 = invisible)"} ) + + Panel:AddControl( "Checkbox", { Label = "Turn Off Minor Slider Stabilisation", Command = "precision_disablesliderfix", Description = "Fix being separate X/Y/Z advanced ballsocket locks between the props. This stops most spaz caused by rotation, but not spaz caused by displacement." } ) + Panel:AddControl( "Label", { Text = "Stabilisation is separate X/Y/Z adv. ballsockets; it makes it far less prone to rotation triggered spaz, but the difference is only noticeable sometimes as it's still just as prone to spaz caused by drifting.", Description = "Due to lack of working descriptions at time of coding" } ) + end + + if ( mode == 9 ) then //parent + Panel:AddControl( "Label", { Text = "Parenting Notes:", Description = "Due to lack of working descriptions at time of coding" } ) + Panel:AddControl( "Label", { Text = "Parenting objects is most similar to a very strong weld, but it stops most interaction on the first object when you attach it to the second. Players can walk on it, but it will fall through players. It will not collide with objects or the world. It will also not cause any extra physics lag/spaz. Try it out on a test object, and decide if it's useful to you!", Description = "Due to lack of working descriptions at time of coding" } ) + + Panel:AddControl( "Label", { Text = "Parented objects are most useful for: Adding detail to moving objects without creating extra physics lag. Things like houses that you want to move (though you can only safely walk on parented objects when they are still.)", Description = "Due to lack of working descriptions at time of coding" } ) + + Panel:AddControl( "Label", { Text = "Possible issues: Remove constraints first to avoid spaz. Duplicating or such may cause the collision model to become separated. Best to test it if in doubt.", Description = "Why must labels cause menu flicker? D:" } ) + end + + if ( mode == 10 ) then //repair + Panel:AddControl( "Label", { Text = "Repair mode", Description = "" } ) + Panel:AddControl( "Label", { Text = "Usage: When a contraption is going crazy, colliding, making rubbing noises.", Description = "" } ) + Panel:AddControl( "Label", { Text = "What it does: Temporarily toggles collisions, allowing things that are bent out of shape to pop back.", Description = "" } ) + Panel:AddControl( "Label", { Text = "Warning: No guarantees. This may turn things inside-out or make things worse depending on the situation.", Description = "" } ) + end + if ( mode == 11 ) then //removal + Panel:AddControl( "Label", { Text = "This mode will remove:", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Nocollide", Command = "precision_removal_nocollide", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Weld", Command = "precision_removal_weld", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Axis", Command = "precision_removal_axis", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Ballsocket", Command = "precision_removal_ballsocket", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Adv. Ballsocket", Command = "precision_removal_advballsocket", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Slider", Command = "precision_removal_slider", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Parent", Command = "precision_removal_parent", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Other", Command = "precision_removal_other", Description = "" } ) + Panel:AddControl( "Label", { Text = "(Other = Rope/slider variants like winch/hydraulic, also motor/keepupright)", Description = "" } ) + Panel:AddControl( "Button", { Label = "Select All", Command = "precision_removal_all", Description = "" } ) + Panel:AddControl( "Button", { Label = "Select None", Command = "precision_removal_none", Description = "" } ) + + end + if ( showgenmenu == 1 ) then + Panel:AddControl( "Button", { Label = "\\/ General Tool Options \\/", Command = "precision_generalmenu", Description = "Collapse menu" } ) + + + + + local params = {Label = "User Level",Description = "Shows options appropriate to user experience level", MenuButton = "0", Height = 67, Options = {}} + if ( user == 1 ) then + params.Options[" 1 ->Normal<-"] = { precision_setuser = "1" } + else + params.Options[" 1 Normal"] = { precision_setuser = "1" } + end + if ( user == 2 ) then + params.Options[" 2 ->Advanced<-"] = { precision_setuser = "2" } + else + params.Options[" 2 Advanced"] = { precision_setuser = "2" } + end + if ( user == 3 ) then + params.Options[" 3 ->Experimental<-"] = { precision_setuser = "3" } + else + params.Options[" 3 Experimental"] = { precision_setuser = "3" } + end + + Panel:AddControl( "ListBox", params ) + + //Panel:AddControl( "Label", { Text = "General Tool Options:", Description = "Note: These don't save with presets." } ) + Panel:AddControl( "Checkbox", { Label = "Enable tool feedback messages?", Command = "precision_enablefeedback", Description = "Toggle for feedback messages incase they get annoying" } ) + Panel:AddControl( "Checkbox", { Label = "On = Feedback in Chat, Off = Centr Scrn", Command = "precision_chatfeedback", Description = "Chat too cluttered? Can have messages centre screen instead" } ) + //Panel:AddControl( "Checkbox", { Label = "Hide Menu Tips?", Command = "precision_hidehints", Description = "Streamline the menu once you're happy with using the tool." } ) + Panel:AddControl( "Checkbox", { Label = "Add Push/Pull to Undo List", Command = "precision_nudgeundo", Description = "For if you're in danger of nudging somthing to where you can't reach it" } ) + Panel:AddControl( "Checkbox", { Label = "Add Movement to Undo List", Command = "precision_moveundo", Description = "So you don't have to secondary fire with nocollide to undo mistakes" } ) + Panel:AddControl( "Checkbox", { Label = "Add Rotation to Undo List", Command = "precision_rotateundo", Description = "So you can find the exact rotation value easier" } ) + Panel:AddControl( "Button", { Label = "Restore Current Mode Default", Command = "precision_defaultrestore", Description = "Collapse menu" } ) + else + Panel:AddControl( "Button", { Label = "-- General Tool Options --", Command = "precision_generalmenu", Description = "Expand menu" } ) + if ( user == 1 ) then + Panel:AddControl( "Label", { Text = "(Note: For more modes and options like slider, use this options button and change the user level)", Description = "" } ) + end + end + end + + + + local function precision_defaults() + local mode = LocalPlayer():GetInfoNum( "precision_mode", 3 ) + if mode == 1 then + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_autorotate", "1") + RunConsoleCommand("precision_ShadowDisable", "0") + RunConsoleCommand("precision_nocollideall", "0") + RunConsoleCommand("precision_physdisable", "0") + RunConsoleCommand("precision_allowphysgun", "0") + RunConsoleCommand("precision_entirecontrap", "0") + elseif mode == 2 then + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_entirecontrap", "0") + elseif mode == 3 then + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "1") + RunConsoleCommand("precision_entirecontrap", "0") + elseif mode == 4 then + RunConsoleCommand("precision_move", "1") + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "0") + RunConsoleCommand("precision_entirecontrap", "0") + RunConsoleCommand("precision_forcelimit", "0") + elseif mode == 5 then + RunConsoleCommand("precision_move", "1") + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "0") + RunConsoleCommand("precision_entirecontrap", "0") + RunConsoleCommand("precision_forcelimit", "0") + RunConsoleCommand("precision_torquelimit", "0") + RunConsoleCommand("precision_friction", "0") + elseif mode == 6 then + RunConsoleCommand("precision_move", "1") + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "0") + RunConsoleCommand("precision_entirecontrap", "0") + RunConsoleCommand("precision_forcelimit", "0") + RunConsoleCommand("precision_torquelimit", "0") + elseif mode == 7 then + RunConsoleCommand("precision_move", "0") + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "0") + RunConsoleCommand("precision_entirecontrap", "0") + RunConsoleCommand("precision_forcelimit", "0") + RunConsoleCommand("precision_torquelimit", "0") + RunConsoleCommand("precision_XRotMin", "0") + RunConsoleCommand("precision_XRotMax", "0") + RunConsoleCommand("precision_YRotMin", "0") + RunConsoleCommand("precision_YRotMax", "0") + RunConsoleCommand("precision_ZRotMin", "0") + RunConsoleCommand("precision_ZRotMax", "0") + RunConsoleCommand("precision_XRotFric", "0") + RunConsoleCommand("precision_YRotFric", "0") + RunConsoleCommand("precision_ZRotFric", "0") + RunConsoleCommand("precision_FreeMov", "1") + elseif mode == 8 then + RunConsoleCommand("precision_move", "1") + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "0") + RunConsoleCommand("precision_autorotate", "0") + RunConsoleCommand("precision_entirecontrap", "0") + RunConsoleCommand("precision_width", "1") + RunConsoleCommand("precision_disablesliderfix", "0") + elseif mode == 9 then + RunConsoleCommand("precision_allowphysgun", "0") + RunConsoleCommand("precision_entirecontrap", "0") + end + precision_updatecpanel() + end + concommand.Add( "precision_defaultrestore", precision_defaults ) + + local function precision_genmenu() + if ( showgenmenu == 1 ) then + showgenmenu = 0 + else + showgenmenu = 1 + end + precision_updatecpanel() + end + concommand.Add( "precision_generalmenu", precision_genmenu ) + + + function precision_setmode( player, tool, args ) + if LocalPlayer():GetInfoNum( "precision_mode", 3 ) != args[1] then + RunConsoleCommand("precision_mode", args[1]) + timer.Simple(0.05, function() precision_updatecpanel() end ) + end + end + concommand.Add( "precision_setmode", precision_setmode ) + + + function precision_setuser( player, tool, args ) + if LocalPlayer():GetInfoNum( "precision_user", 3 ) != args[1] then + RunConsoleCommand("precision_user", args[1]) + timer.Simple(0.05, function() precision_updatecpanel() end ) + end + end + concommand.Add( "precision_setuser", precision_setuser ) + + + function precision_updatecpanel() + local Panel = controlpanel.Get( "precision" ) + if (!Panel) then return end + //custom panel building ( wtf does Panel:AddDefaultControls() get it's defaults from? ) + AddDefControls( Panel ) + end + concommand.Add( "precision_updatecpanel", precision_updatecpanel ) + + function TOOL.BuildCPanel( Panel ) + AddDefControls( Panel ) + end + + local function precision_removalall() + RunConsoleCommand("precision_removal_nocollide", "1") + RunConsoleCommand("precision_removal_weld", "1") + RunConsoleCommand("precision_removal_axis", "1") + RunConsoleCommand("precision_removal_ballsocket", "1") + RunConsoleCommand("precision_removal_advballsocket", "1") + RunConsoleCommand("precision_removal_slider", "1") + RunConsoleCommand("precision_removal_parent", "1") + RunConsoleCommand("precision_removal_other", "1") + precision_updatecpanel() + end + concommand.Add( "precision_removal_all", precision_removalall ) + local function precision_removalnone() + RunConsoleCommand("precision_removal_nocollide", "0") + RunConsoleCommand("precision_removal_weld", "0") + RunConsoleCommand("precision_removal_axis", "0") + RunConsoleCommand("precision_removal_ballsocket", "0") + RunConsoleCommand("precision_removal_advballsocket", "0") + RunConsoleCommand("precision_removal_slider", "0") + RunConsoleCommand("precision_removal_parent", "0") + RunConsoleCommand("precision_removal_other", "0") + precision_updatecpanel() + end + concommand.Add( "precision_removal_none", precision_removalnone ) + + function TOOL:FreezeMovement() + local stage = self:GetStage() + if ( stage == 2 ) then + return true + //elseif ( iNum > 0 && self:GetClientNumber("mode") == 2 ) then + // return true + end + return false + end +end + +function TOOL:Holster() + self:ClearObjects() + self:SetStage(0) + self:ClearSelection() +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/guitar/shared.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/guitar/shared.lua new file mode 100644 index 0000000..628da92 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/guitar/shared.lua @@ -0,0 +1,285 @@ +if ( SERVER ) then + AddCSLuaFile( "shared.lua" ) + + util.AddNetworkString("anterg0_guitarTab") + util.AddNetworkString("anterg0_guitarPlaySong") +end + +if CLIENT then + SWEP.DrawCrosshair = false + SWEP.PrintName = "Русская гитара" +end + +SWEP.Base = "weapon_base" + +SWEP.PrintName = "Русская гитара" +SWEP.Slot = 2 +SWEP.SlotPos = 4 +SWEP.DrawAmmo = false +SWEP.ViewModel = "models/weapons/tayley/v_guitar.mdl" +SWEP.HoldType = "slam" +SWEP.ViewModelFOV = 70 +SWEP.ViewModelFlip = false +SWEP.WorldModel = "models/anterg0_Guitar/marauder_guitar.mdl" +-- Other settings +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +-- SWEP info +SWEP.Author = "DuckFlit" +SWEP.Category = "[FT] Атмосфера" +SWEP.Instructions = "Левый клик дабы играть случайную песню. Правый клик чтобы выбрать песню." + +-- Primary fire settings +SWEP.Primary.Damage = -1 +SWEP.Primary.NumShots = -1 +SWEP.Primary.Delay = 2 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Tracer = -1 +SWEP.Primary.Force = -1 +SWEP.Primary.TakeAmmoPerBullet = false +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.ReloadTimer = 1 + +local songsTbl = { + + [1] = {"Спецназ - Денис Майданов", "weapons/guitar/ad.mp3"}, + [2] = {"Мужество Ведёт на Небеса", "weapons/guitar/ac.mp3"}, + [3] = {"Витязи", "weapons/guitar/ag.mp3"}, + [4] = {"Чёрный Октябрь ", "weapons/guitar/aj.mp3"}, + [5] = {"За что мы пьём", "weapons/guitar/ak.mp3"}, + [6] = {"Набирает высоту", "weapons/guitar/ar.mp3"}, + [7] = {"Стрим - Песня Бритвы", "weapons/guitar/ay.mp3"}, + [8] = {"Простите меня", "weapons/guitar/am.mp3"}, + [9] = {"Владимир Высоцкий - Солдаты Группы", "weapons/guitar/ap.mp3"}, + [10] = {"ГСН-Российскийспецназ", "weapons/guitar/fa.mp3"}, + [11] = {"Егор Авдеев - 333 с ночи до зари", "weapons/guitar/fr.mp3"}, + [12] = {"Николай Анисимов - По самому прямому назначению", "weapons/guitar/fy.mp3"}, + [13] = {"UKRAINE - Гімн Українскої Артилерії", "weapons/guitar/jj.mp3"}, +} + +if CLIENT then + + SWEP.WepSelectIcon = surface.GetTextureID("vgui/selection/guitar_ico") + function SWEP:DrawWeaponSelection(x, y, wide, tall, alpha) + x = x + wide / 2 + y = y + tall / 2 + + tall = tall * 0.75 + + x = x - tall / 2 + y = y - tall / 2 - 10 + + surface.SetDrawColor(255, 255, 255, alpha) + surface.SetTexture(self.WepSelectIcon) + + surface.DrawTexturedRect(x, y, tall, tall) + end + +end + +function SWEP:Initialize() + self:SetWeaponHoldType(self.HoldType) +end + +function SWEP:Holster() + self.Weapon:EmitSound(Sound("weapons/guitar/squak2.mp3")) + return true +end + +function SWEP:OnRemove() + self:Holster() +end + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay); + self.Weapon:EmitSound(Sound("weapons/guitar/squak2.mp3"), 50, math.random(85,100), 1, CHAN_SWEP) + + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + if SERVER then + local songChoice = math.random(1, #songsTbl) + self.Weapon:SetNWBool("guitar_isPlaying", true) + self.Weapon:SetNWInt("guitar_songNum", songChoice) + end + timer.Simple(0.5, function() + local song = songsTbl[self.Weapon:GetNWInt("guitar_songNum")][2] + if(song == nil) then + song = "" + end + self.Weapon:EmitSound(song, 80, 100, 1, CHAN_SWEP) + end) +end + +function SWEP:SecondaryAttack() + local ply = self.Owner + if SERVER then + net.Start("anterg0_guitarTab") + net.Send(ply) + end + return false +end + +function SWEP:Think() +end + +function SWEP:Reload() + if(self.Weapon:GetNextSecondaryFire() <= CurTime() ) then + self.Weapon:SetNextSecondaryFire(CurTime() + self.ReloadTimer) + self.Weapon:SetNWBool("guitar_isPlaying", false) + self.Weapon:EmitSound(Sound("weapons/guitar/squak2.mp3"), 50, math.random(85,100), 1, CHAN_SWEP) + return false + end +end + +function SWEP:Deploy() + self:SetWeaponHoldType(self.HoldType) + return true +end + +SWEP.RenderGroup = RENDERGROUP_BOTH + +function SWEP:DrawWorldModelTranslucent() + self:DrawModel() + + if self.time == nil then self.time = 0 end + surface.SetFont("Trebuchet24") + local tag = "Now playing ... " + if(self.Weapon:GetNWInt("guitar_songNum") ~= 0) then + songName = songsTbl[self.Weapon:GetNWInt("guitar_songNum")][1] + tag = "Now playing ... " .. songName + end + local textwidth = surface.GetTextSize(tag) + + if LocalPlayer():GetPos():Distance(self:GetPos()) < 550 then + if(self.Weapon:GetNWBool("guitar_isPlaying") == false) then return end + local alpha = (LocalPlayer():GetPos():Distance(self:GetPos()) / 100.0) + alpha = math.Clamp(2.5 - alpha, 0 ,1) + local a = Angle(0,0,0) + a:RotateAroundAxis(Vector(1,0,0),90) + a.y = LocalPlayer():GetAngles().y - 90 + cam.Start3D2D(self:GetPos() + Vector(0,0,45 + (math.sin(self.time*0.5)*2)), a , 0.15) + draw.SimpleText(tag,"Trebuchet24", -textwidth*0.5,0,Color(205,225,255,255*alpha) , 0 , 1) + cam.End3D2D() + end + + self.time = self.time + FrameTime() +end + +net.Receive("anterg0_guitarTab", function(len, ply) + local songNum = net.ReadFloat() + + local wep = ply:GetActiveWeapon() + if(wep:GetClass() == "guitar") then + wep:SetNWBool("guitar_isPlaying", true) + wep:SetNWInt("guitar_songNum", songNum) + wep:EmitSound( songsTbl[songNum][2], 80, 100, 1, CHAN_SWEP) + + end +end) + +// DERMA STUFF + +if CLIENT then + + local PANEL = {} + + function PANEL:Init() + local w = ScrW()*0.3 + local h = ScrH()*0.4 + self:SetSize(w, h) + self:SetPos(ScrW()*0.7, ScrH()/2 - (h/2)) + + self:MakePopup() + self:SetDeleteOnClose(true) + self:SetDraggable(true) + self:ShowCloseButton(true) + self:SetTitle("") + + self.Paint = function(_, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 225)) + end + + self.MusicOptions = vgui.Create("anterg0_Guitar:DScroll", self) + + end + + vgui.Register("anterg0_Guitar:Menu", PANEL, "DFrame") + + net.Receive("anterg0_guitarTab", function() + if(IsValid(guitPanel)) then return end + guitPanel = vgui.Create("anterg0_Guitar:Menu") + end) + + // MUSIC + local PANEL = {} + + function PANEL:Init() + local x,y = self:GetParent():GetSize() + self:SetSize(x, y + 60) + self:SetPos(0, 30) + + --Modify Scroll Bar-- + local sbar = self:GetVBar() + sbar:SetHideButtons(true) + sbar.Paint = function(_, w, h) + draw.RoundedBox(0, 0, 0, (w/2), h, Color(0, 0, 0, 225)) + end + + sbar.btnGrip.Paint = function(_, w, h) + draw.RoundedBox(0, 0, 0, (w/2), h, Color(255, 255, 255, 255)) + end + + self.Paint = function(_, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 0)) + end + + for k, v in ipairs(songsTbl) do + local btn = self:Add("DButton") + btn:Dock(TOP) + btn:DockMargin(10, 0, 10, 10) + btn:SetSize(0, 25) + btn:SetText("") + btn.HoverLerp = 0 + btn.Paint = function(_, w, h) + if btn:IsHovered() then + btn.HoverLerp = Lerp( FrameTime() * 10, btn.HoverLerp, 1 ) + else + btn.HoverLerp = Lerp( FrameTime() * 3, btn.HoverLerp, 0.1) + end + draw.RoundedBox(0, 0, 0, w, h, Color(50,50,50,255*btn.HoverLerp)) + draw.SimpleText(v[1], "Trebuchet18", w/2, 2, Color(255,255,255, 255), 1, 0) + end + + btn.PaintOver = function(_, w, h) + surface.SetDrawColor(Color(255, 255, 255, 125), 1, 1) + surface.DrawOutlinedRect(0, 0, w, h) + end + + btn.DoClick = function() + local wep = LocalPlayer():GetActiveWeapon() + if(wep ~= nil or IsValid(wep)) then + LocalPlayer():EmitSound(Sound("weapons/guitar/squak2.mp3") ) + wep:EmitSound( v[2], 100, 100, 1, CHAN_SWEP ) + end + net.Start("anterg0_guitarTab") + net.WriteFloat(k) + net.SendToServer() + + self:GetParent():Close() + end + + end + + self:Dock(FILL) + self:DockMargin(0, 0, 0, 10) + end + + vgui.Register("anterg0_Guitar:DScroll", PANEL, "DScrollPanel") +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/ix_consumable.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/ix_consumable.lua new file mode 100644 index 0000000..8442655 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/ix_consumable.lua @@ -0,0 +1,195 @@ +AddCSLuaFile() + +SWEP.Base = "weapon_base" + +if CLIENT then + SWEP.DrawWeaponInfoBox = false + SWEP.BounceWeaponIcon = false +end + +SWEP.PrintName = "Consumable" +SWEP.Author = "Helix" +SWEP.Purpose = "Base consumable SWEP with animations" +SWEP.Category = "Helix" + +SWEP.ViewModelFOV = 70 +SWEP.ViewModel = "models/weapons/v_hands.mdl" +SWEP.WorldModel = "" +SWEP.UseHands = true +SWEP.DrawCrosshair = false + +SWEP.Spawnable = false +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.Slot = 5 +SWEP.SlotPos = 0 + +SWEP.SwayScale = 0 +SWEP.BobScale = 0 + +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.ItemHunger = 0 +SWEP.ItemThirst = 0 +SWEP.ItemAlcohol = 0 +SWEP.ItemEmpty = false +SWEP.ItemType = "food" -- "food", "drink", "consumable" + +function SWEP:Initialize() + self:SetHoldType("slam") + self.Consuming = false +end + +function SWEP:SetItemData(hunger, thirst, alcohol, empty, itemType, viewModel) + self.ItemHunger = hunger or 0 + self.ItemThirst = thirst or 0 + self.ItemAlcohol = alcohol or 0 + self.ItemEmpty = empty or false + self.ItemType = itemType or "food" + + -- Устанавливаем viewmodel, если указана + if viewModel and viewModel != "" then + self.ViewModel = viewModel + end + print(self.ViewModel) +end + +function SWEP:Deploy() + local owner = self:GetOwner() + + if not IsValid(owner) or not owner:IsPlayer() then + return false + end + + -- Проигрываем idle анимацию + local vm = owner:GetViewModel() + if IsValid(vm) then + local idleSeq = vm:LookupSequence("idle") + if idleSeq > 0 then + vm:SendViewModelMatchingSequence(idleSeq) + end + end + + -- Начинаем процесс употребления + self.Consuming = true + self:InitializeConsumable() + + return true +end + +function SWEP:InitializeConsumable() + local owner = self:GetOwner() + if not IsValid(self) or not IsValid(owner) or not owner:IsPlayer() then return end + + -- Проигрываем анимацию use + local vm = owner:GetViewModel() + local sequenceDuration = 3 + + if IsValid(vm) then + local useSeq = vm:LookupSequence("use") + if useSeq > 0 then + vm:SendViewModelMatchingSequence(useSeq) + sequenceDuration = vm:SequenceDuration(useSeq) + end + end + + -- Применяем эффекты в середине анимации + timer.Simple(sequenceDuration * 0.5, function() + if IsValid(self) and IsValid(owner) and owner:Alive() then + self:ApplyEffects(owner) + end + end) + + -- Завершаем употребление и удаляем оружие + timer.Simple(sequenceDuration, function() + if IsValid(self) and IsValid(owner) and owner:Alive() then + self.Consuming = false + + if SERVER then + owner:StripWeapon(self:GetClass()) + + local prev = owner:GetPreviousWeapon() or "" + if isentity(prev) and IsValid(prev) then + owner:SelectWeapon(prev:GetClass()) + elseif isstring(prev) and prev != "" then + owner:SelectWeapon(prev) + end + end + end + end) +end + +function SWEP:ApplyEffects(owner) + if not SERVER then return end + if not IsValid(owner) then return end + + local char = owner:GetCharacter() + if not char then return end + + -- Получаем текущие значения + local hunger = char:GetData("hunger", 100) + local thirst = char:GetData("thirst", 100) + + -- Применяем изменения (SetHunger и SetThirst автоматически обновляют состояние) + owner:SetHunger(hunger + self.ItemHunger) + owner:SetThirst(thirst + self.ItemThirst) + + -- Алкоголь + if self.ItemAlcohol > 0 then + owner:IncreaseDrunkLevel(self.ItemAlcohol) + end + + -- Пустая тара + if self.ItemEmpty then + local inv = char:GetInventory() + if inv then + inv:Add(self.ItemEmpty) + end + end + + -- Звук употребления из STALKER 2 Consumables + if self.ItemType == "drink" then + owner:EmitSound("Stalker2.Drink") + elseif self.ItemType == "food" then + owner:EmitSound("Stalker2.Eat") + elseif self.ItemType == "medical" then + owner:EmitSound("Stalker2.Healing") + end +end + +function SWEP:PrimaryAttack() + -- Ничего не делаем +end + +function SWEP:SecondaryAttack() + -- Ничего не делаем +end + +function SWEP:Reload() + -- Ничего не делаем +end + +function SWEP:Think() + -- Можно добавить дополнительную логику +end + +function SWEP:Holster() + -- Блокируем переключение во время употребления + if self.Consuming then + return false + end + return true +end + +function SWEP:OnRemove() + -- Очистка +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/ix_hands.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/ix_hands.lua new file mode 100644 index 0000000..f71c555 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/ix_hands.lua @@ -0,0 +1,441 @@ + +AddCSLuaFile() + +if (CLIENT) then + SWEP.PrintName = "Hands" + SWEP.Slot = 0 + SWEP.SlotPos = 1 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = true +end + +SWEP.Author = "Chessnut" +SWEP.Instructions = [[Primary Fire: Throw/Punch +Secondary Fire: Knock/Pickup +Secondary Fire + Mouse: Rotate Object +Reload: Drop]] +SWEP.Purpose = "Hitting things and knocking on doors." +SWEP.Drop = false + +SWEP.ViewModelFOV = 45 +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "rpg" + +SWEP.ViewTranslation = 4 +if CLIENT then + SWEP.NextAllowedPlayRateChange = 0 +end + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" +SWEP.Primary.Damage = 5 +SWEP.Primary.Delay = 0.75 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" +SWEP.Secondary.Delay = 0.5 + +SWEP.ViewModel = "" +SWEP.WorldModel = "" + +SWEP.UseHands = true +SWEP.LowerAngles = Angle(0, 5, -14) +SWEP.LowerAngles2 = Angle(0, 5, -19) +SWEP.KnockViewPunchAngle = Angle(-1.3, 1.8, 0) + +SWEP.FireWhenLowered = true +SWEP.HoldType = "normal" + +SWEP.holdDistance = 64 +SWEP.maxHoldDistance = 96 -- how far away the held object is allowed to travel before forcefully dropping +SWEP.maxHoldStress = 4000 -- how much stress the held object can undergo before forcefully dropping + +-- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER +ACT_VM_FISTS_DRAW = 2 +ACT_VM_FISTS_HOLSTER = 1 + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) + + self.lastHand = 0 + self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2 + self.heldObjectAngle = Angle(angle_zero) +end + +if (CLIENT) then + function SWEP:DoDrawCrosshair(x, y) + surface.SetDrawColor(255, 255, 255, 66) + surface.DrawRect(x - 2, y - 2, 4, 4) + end + + hook.Add("CreateMove", "ixHandsCreateMove", function(cmd) + if (LocalPlayer():GetLocalVar("bIsHoldingObject", false) and cmd:KeyDown(IN_ATTACK2)) then + cmd:ClearMovement() + local angle = RenderAngles() + angle.z = 0 + cmd:SetViewAngles(angle) + end + end) +end + + + +function SWEP:Deploy() + if (!IsValid(self:GetOwner())) then + return + end + + if CLIENT then + local viewModel = self:GetOwner():GetViewModel() + if IsValid(viewModel) then + viewModel:SetNoDraw(true) + end + end + + self:DropObject() + return true +end + +function SWEP:Precache() + util.PrecacheSound("npc/vort/claw_swing1.wav") + util.PrecacheSound("npc/vort/claw_swing2.wav") + util.PrecacheSound("physics/plastic/plastic_box_impact_hard1.wav") + util.PrecacheSound("physics/plastic/plastic_box_impact_hard2.wav") + util.PrecacheSound("physics/plastic/plastic_box_impact_hard3.wav") + util.PrecacheSound("physics/plastic/plastic_box_impact_hard4.wav") + util.PrecacheSound("physics/wood/wood_crate_impact_hard2.wav") + util.PrecacheSound("physics/wood/wood_crate_impact_hard3.wav") +end + +function SWEP:OnReloaded() + self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2 + self:DropObject() +end + +function SWEP:Holster() + if (!IsValid(self:GetOwner())) then + return + end + + local viewModel = self:GetOwner():GetViewModel() + + if (IsValid(viewModel)) then + viewModel:SetPlaybackRate(1) + viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER) + if CLIENT then + self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() + end + end + + return true +end + +function SWEP:Think() + if (!IsValid(self:GetOwner())) then + return + end + + if (CLIENT) then + local viewModel = self:GetOwner():GetViewModel() + + if (IsValid(viewModel) and self.NextAllowedPlayRateChange < CurTime()) then + viewModel:SetPlaybackRate(1) + end + else + if (self:IsHoldingObject()) then + local physics = self:GetHeldPhysicsObject() + local bIsRagdoll = self.heldEntity:IsRagdoll() + local holdDistance = bIsRagdoll and self.holdDistance * 0.5 or self.holdDistance + local targetLocation = self:GetOwner():GetShootPos() + self:GetOwner():GetForward() * holdDistance + + if (bIsRagdoll) then + targetLocation.z = math.min(targetLocation.z, self:GetOwner():GetShootPos().z - 32) + end + + if (!IsValid(physics)) then + self:DropObject() + return + end + + if (physics:GetPos():DistToSqr(targetLocation) > self.maxHoldDistanceSquared) then + self:DropObject() + else + local physicsObject = self.holdEntity:GetPhysicsObject() + local currentPlayerAngles = self:GetOwner():EyeAngles() + local client = self:GetOwner() + + if (client:KeyDown(IN_ATTACK2)) then + local cmd = client:GetCurrentCommand() + self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Forward(), cmd:GetMouseX() / 15) + self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Right(), cmd:GetMouseY() / 15) + end + + self.lastPlayerAngles = self.lastPlayerAngles or currentPlayerAngles + self.heldObjectAngle.y = self.heldObjectAngle.y - math.AngleDifference(self.lastPlayerAngles.y, currentPlayerAngles.y) + self.lastPlayerAngles = currentPlayerAngles + + physicsObject:Wake() + physicsObject:ComputeShadowControl({ + secondstoarrive = 0.01, + pos = targetLocation, + angle = self.heldObjectAngle, + maxangular = 256, + maxangulardamp = 10000, + maxspeed = 256, + maxspeeddamp = 10000, + dampfactor = 0.8, + teleportdistance = self.maxHoldDistance * 0.75, + deltatime = FrameTime() + }) + + if (physics:GetStress() > self.maxHoldStress) then + self:DropObject() + end + end + end + -- Prevents the camera from getting stuck when the object that the client is holding gets deleted. + if(!IsValid(self.heldEntity) and self:GetOwner():GetLocalVar("bIsHoldingObject", true)) then + self:GetOwner():SetLocalVar("bIsHoldingObject", false) + end + end +end + +function SWEP:GetHeldPhysicsObject() + return IsValid(self.heldEntity) and self.heldEntity:GetPhysicsObject() or nil +end + +function SWEP:CanHoldObject(entity) + local physics = entity:GetPhysicsObject() + + return IsValid(physics) and + (physics:GetMass() <= ix.config.Get("maxHoldWeight", 100) and physics:IsMoveable()) and + !self:IsHoldingObject() and + !IsValid(entity.ixHeldOwner) and + hook.Run("CanPlayerHoldObject", self:GetOwner(), entity) +end + +function SWEP:IsHoldingObject() + return IsValid(self.heldEntity) and + IsValid(self.heldEntity.ixHeldOwner) and + self.heldEntity.ixHeldOwner == self:GetOwner() +end + +function SWEP:PickupObject(entity) + if (self:IsHoldingObject() or + !IsValid(entity) or + !IsValid(entity:GetPhysicsObject())) then + return + end + + local physics = entity:GetPhysicsObject() + physics:EnableGravity(false) + physics:AddGameFlag(FVPHYSICS_PLAYER_HELD) + + entity.ixHeldOwner = self:GetOwner() + entity.ixCollisionGroup = entity:GetCollisionGroup() + entity:StartMotionController() + entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + self.heldObjectAngle = entity:GetAngles() + self.heldEntity = entity + + self.holdEntity = ents.Create("prop_physics") + self.holdEntity:SetPos(self.heldEntity:LocalToWorld(self.heldEntity:OBBCenter())) + self.holdEntity:SetAngles(self.heldEntity:GetAngles()) + self.holdEntity:SetModel("models/weapons/w_bugbait.mdl") + self.holdEntity:SetOwner(self:GetOwner()) + + self.holdEntity:SetNoDraw(true) + self.holdEntity:SetNotSolid(true) + self.holdEntity:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self.holdEntity:DrawShadow(false) + + self.holdEntity:Spawn() + + local trace = self:GetOwner():GetEyeTrace() + local physicsObject = self.holdEntity:GetPhysicsObject() + + if (IsValid(physicsObject)) then + physicsObject:SetMass(2048) + physicsObject:SetDamping(0, 1000) + physicsObject:EnableGravity(false) + physicsObject:EnableCollisions(false) + physicsObject:EnableMotion(false) + end + + if (trace.Entity:IsRagdoll()) then + local tracedEnt = trace.Entity + self.holdEntity:SetPos(tracedEnt:GetBonePosition(tracedEnt:TranslatePhysBoneToBone(trace.PhysicsBone))) + end + + self.constraint = constraint.Weld(self.holdEntity, self.heldEntity, 0, + trace.Entity:IsRagdoll() and trace.PhysicsBone or 0, 0, true, true) +end + +function SWEP:DropObject(bThrow) + if (!IsValid(self.heldEntity) or self.heldEntity.ixHeldOwner != self:GetOwner()) then + return + end + + self.lastPlayerAngles = nil + self:GetOwner():SetLocalVar("bIsHoldingObject", false) + + self.constraint:Remove() + self.holdEntity:Remove() + + self.heldEntity:StopMotionController() + self.heldEntity:SetCollisionGroup(self.heldEntity.ixCollisionGroup or COLLISION_GROUP_NONE) + + local physics = self:GetHeldPhysicsObject() + physics:EnableGravity(true) + physics:Wake() + physics:ClearGameFlag(FVPHYSICS_PLAYER_HELD) + + if (bThrow) then + timer.Simple(0, function() + if (IsValid(physics) and IsValid(self:GetOwner())) then + physics:AddGameFlag(FVPHYSICS_WAS_THROWN) + physics:ApplyForceCenter(self:GetOwner():GetAimVector() * ix.config.Get("throwForce", 732)) + end + end) + end + + self.heldEntity.ixHeldOwner = nil + self.heldEntity.ixCollisionGroup = nil + self.heldEntity = nil +end + +function SWEP:PlayPickupSound(surfaceProperty) + local result = "Flesh.ImpactSoft" + + if (surfaceProperty != nil) then + local surfaceName = util.GetSurfacePropName(surfaceProperty) + local soundName = surfaceName:gsub("^metal$", "SolidMetal") .. ".ImpactSoft" + + if (sound.GetProperties(soundName)) then + result = soundName + end + end + + self:GetOwner():EmitSound(result, 75, 100, 40) +end + +function SWEP:Holster() + if (!IsFirstTimePredicted() or CLIENT) then + return + end + + self:DropObject() + return true +end + +function SWEP:OnRemove() + if (SERVER) then + self:DropObject() + end +end + +function SWEP:OwnerChanged() + if (SERVER) then + self:DropObject() + end +end + + +function SWEP:DoPunchAnimation() + self.lastHand = math.abs(1 - self.lastHand) + local sequence = 3 + self.lastHand -- punch anims + local viewModel = self:GetOwner():GetViewModel() + if (IsValid(viewModel)) then + viewModel:SetPlaybackRate(0.5) + viewModel:SetSequence(sequence) + if CLIENT then + self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2 + end + end +end + + +function SWEP:PrimaryAttack() + if (!IsFirstTimePredicted()) then + return + end + + if (SERVER and self:IsHoldingObject()) then + self:DropObject(true) + end +end + +function SWEP:SecondaryAttack() + if (!IsFirstTimePredicted()) then + return + end + + local data = {} + data.start = self:GetOwner():GetShootPos() + data.endpos = data.start + self:GetOwner():GetAimVector() * 84 + data.filter = {self, self:GetOwner()} + local trace = util.TraceLine(data) + local entity = trace.Entity + + if CLIENT then + local viewModel = self:GetOwner():GetViewModel() + + if (IsValid(viewModel)) then + viewModel:SetPlaybackRate(0.5) + if CLIENT then + self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2 + end + end + end + + if (SERVER) then + if (self:IsHoldingObject()) then + self:DropObject() + return + end + + if (IsValid(entity)) then + if (entity:IsDoor()) then + if (hook.Run("CanPlayerKnock", self:GetOwner(), entity) == false) then + return + end + + self:GetOwner():ViewPunch(self.KnockViewPunchAngle) + self:GetOwner():EmitSound("physics/wood/wood_crate_impact_hard"..math.random(2, 3)..".wav") + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + + self:DoPunchAnimation() + self:SetNextSecondaryFire(CurTime() + 0.4) + self:SetNextPrimaryFire(CurTime() + 1) + elseif (entity:IsPlayer() and ix.config.Get("allowPush", true)) then + local direction = self:GetOwner():GetAimVector() * (300 + (self:GetOwner():GetCharacter():GetAttribute("str", 0) * 3)) + direction.z = 0 + entity:SetVelocity(direction) + + self:GetOwner():EmitSound("Weapon_Crossbow.BoltHitBody") + self:SetNextSecondaryFire(CurTime() + 1.5) + self:SetNextPrimaryFire(CurTime() + 1.5) + elseif (!entity:IsNPC() and self:CanHoldObject(entity)) then + self:GetOwner():SetLocalVar("bIsHoldingObject", true) + self:PickupObject(entity) + self:PlayPickupSound(trace.SurfaceProps) + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + end + end + end +end + +function SWEP:Reload() + if (!IsFirstTimePredicted()) then + return + end + + if (SERVER and IsValid(self.heldEntity)) then + self:DropObject() + end +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/tacrp_ifak/shared.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/tacrp_ifak/shared.lua new file mode 100644 index 0000000..c5f4922 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/tacrp_ifak/shared.lua @@ -0,0 +1,514 @@ +SWEP.Base = "tacrp_base" +SWEP.Spawnable = true + +AddCSLuaFile() + +// names and stuff +SWEP.PrintName = "Infantry First Aid Kit" +SWEP.AbbrevName = "IFAK" +SWEP.Category = "[FT] Медицина" + +SWEP.SubCatTier = "9Special" +SWEP.SubCatType = "9Special" + +SWEP.Description = "Индивидуальная аптечка первой помощи. Содержит бинты, гемостаты и порошки QuikClot для остановки кровотечений." +SWEP.Description_Quote = "Поддержка жизни в полевых условиях." + +SWEP.Trivia_Manufacturer = "Military Issue" +SWEP.Trivia_Year = "2000s" + +SWEP.Faction = TacRP.FACTION_NEUTRAL +SWEP.Credits = "Original: Firearms Source 2\nPort: TacRP" + +SWEP.ViewModel = "models/weapons/v_ifak.mdl" +SWEP.WorldModel = "models/Items/HealthKit.mdl" + +SWEP.Slot = 4 + +SWEP.NoRanger = true +SWEP.NoStatBox = true + +// handling + +SWEP.MoveSpeedMult = 1 +SWEP.ShootingSpeedMult = 1 +SWEP.SightedSpeedMult = 1 + +SWEP.AimDownSightsTime = 0.25 +SWEP.SprintToFireTime = 0.25 + +SWEP.Sway = 0 +SWEP.FreeAimMaxAngle = 0 + +// hold types + +SWEP.HoldType = "slam" +SWEP.HoldTypeSprint = "normal" +SWEP.HoldTypeBlindFire = false + +SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL +SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL + +SWEP.PassiveAng = Angle(0, 0, 0) +SWEP.PassivePos = Vector(0, -2, -4) + +SWEP.BlindFireAng = Angle(0, 0, 0) +SWEP.BlindFirePos = Vector(0, 0, 0) + +SWEP.SprintAng = Angle(0, 30, 0) +SWEP.SprintPos = Vector(2, 0, -12) + +SWEP.SightAng = Angle(-0.5, 0, 0) +SWEP.SightPos = Vector(-3.412, -6.4, -2.238) + +SWEP.CorrectivePos = Vector(0, 0, 0) +SWEP.CorrectiveAng = Angle(0, 0, 0) + +SWEP.HolsterVisible = true +SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_GEAR +SWEP.HolsterPos = Vector(2, 0, 0) +SWEP.HolsterAng = Angle(-90, 0, 0) + +// reload + +SWEP.ClipSize = -1 +SWEP.Ammo = "none" + +// sounds + +SWEP.Sounds = {} +SWEP.Sounds["bandage"] = { + {time = 0.4, sound = "FAS2_Bandage.Retrieve"}, + {time = 1.25, sound = "FAS2_Bandage.Open"}, + {time = 2.15, sound = "FAS2_Hemostat.Retrieve"} +} + +SWEP.Sounds["quikclot"] = { + {time = 0.3, sound = "FAS2_QuikClot.Retrieve"}, + {time = 1.45, sound = "FAS2_QuikClot.Loosen"}, + {time = 2.55, sound = "FAS2_QuikClot.Open"} +} + +SWEP.Sounds["suture"] = { + {time = 0.3, sound = "FAS2_Hemostat.Retrieve"}, + {time = 3.5, sound = "FAS2_Hemostat.Close"} +} + +// attachments + +SWEP.Attachments = { + [1] = { + PrintName = "Аксессуар", + Category = "acc", + AttachSound = "TacRP/weapons/flashlight_on.wav", + DetachSound = "TacRP/weapons/flashlight_off.wav", + }, +} + +SWEP.FreeAim = false +SWEP.Scope = false + +SWEP.DrawCrosshair = true +SWEP.DrawCrosshairInSprint = true + +// Custom vars +SWEP.EasterWait = 0 + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) + + if CLIENT then + self.SoundTime = 0 + self.SoundEntry = 1 + self.CurrentSoundTable = nil + end +end + +function SWEP:Deploy() + local owner = self:GetOwner() + if SERVER and IsValid(owner) then + owner:GiveAmmo(4, "Bandages", true) + owner:GiveAmmo(3, "Quikclots", true) + owner:GiveAmmo(2, "Hemostats", true) + end + + return true +end + +function SWEP:Holster() + if self.HealTime and CurTime() < self.HealTime then + return false + end + + if CLIENT then + self.CurrentSoundTable = nil + self.SoundEntry = 1 + self.SoundTime = 0 + end + + return true +end +local Mins, Maxs = Vector(-8, -8, -8), Vector(8, 8, 8) + +function SWEP:FindHealTarget() + local owner = self:GetOwner() + if not IsValid(owner) then return owner end + + local tr = util.TraceHull({ + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + owner:GetAimVector() * 50, + filter = owner, + mins = Mins, + maxs = Maxs + }) + + if tr.Hit and IsValid(tr.Entity) and tr.Entity:IsPlayer() then + return tr.Entity + end + + return owner +end + +function SWEP:PlaySounds(soundTable) + if not self.Sounds[soundTable] then return end + + self.CurrentSoundTable = soundTable + self.SoundEntry = 1 + self.SoundTime = CurTime() +end + +function SWEP:Think() + local CT = CurTime() + + -- Sound system + if CLIENT and self.CurrentSoundTable then + local sounds = self.Sounds[self.CurrentSoundTable] + if sounds and sounds[self.SoundEntry] then + local snd = sounds[self.SoundEntry] + if CT >= self.SoundTime + snd.time then + self:EmitSound(snd.sound, 70, 100) + + if sounds[self.SoundEntry + 1] then + self.SoundEntry = self.SoundEntry + 1 + else + self.CurrentSoundTable = nil + self.SoundEntry = 1 + end + end + end + end + + -- Healing process + if self.CurrentHeal and CT >= self.HealTime then + self:EndHealingProcess() + end + + -- Update viewmodel bodygroups + if CLIENT and not self.CurrentHeal then + self:UpdateBodygroups() + end +end + +function SWEP:UpdateBodygroups() + local owner = self:GetOwner() + if not IsValid(owner) then return end + + local vm = owner:GetViewModel() + if not IsValid(vm) then return end + + -- Bodygroup 2 - бинты (0-2) + vm:SetBodygroup(2, math.Clamp(owner:GetAmmoCount("Bandages"), 0, 2)) + + -- Bodygroup 3 - гемостаты/квикклоты + local hemostats = owner:GetAmmoCount("Hemostats") + + if hemostats > 0 then + vm:SetBodygroup(3, hemostats == 1 and 2 or 3) + else + local quikclots = owner:GetAmmoCount("Quikclots") + vm:SetBodygroup(3, quikclots > 0 and 1 or 0) + end +end + +function SWEP:EndHealingProcess() + local owner = self:GetOwner() + if not IsValid(owner) then return end + + -- Play end animation + local vm = owner:GetViewModel() + if IsValid(vm) then + local anim = "idle" + if self.CurrentHeal == "suture" and owner:GetAmmoCount("Hemostats") == 1 then + anim = "bandage_end" + else + anim = self.CurrentHeal .. "_end" + end + + local seq = vm:LookupSequence(anim) + if seq > 0 then + vm:SendViewModelMatchingSequence(seq) + end + end + + owner:RemoveAmmo(1, self.AmmoType) + + if CLIENT then + self:UpdateBodygroups() + end + + if SERVER then + if self.OwnHeal then + owner:SetHealth(math.Clamp(owner:Health() + self.HealAmount, 0, owner:GetMaxHealth())) + else + if IsValid(self.Target) then + self.Target:SetHealth(math.Clamp(self.Target:Health() + self.HealAmount, 0, self.Target:GetMaxHealth())) + end + end + end + + self.CurrentHeal = nil + self.HealTime = nil + self.HealAmount = nil + self.Target = nil + self.OwnHeal = false +end + +function SWEP:PrimaryAttack() + if not IsFirstTimePredicted() then return end + + local owner = self:GetOwner() + if not IsValid(owner) then return end + + local bandages = owner:GetAmmoCount("Bandages") + if bandages <= 0 then return end + + local target = self:FindHealTarget() + if not IsValid(target) then return end + + -- Проверка здоровья + if CLIENT then + if target:Health() >= 100 then return end + else + if target:Health() >= target:GetMaxHealth() then return end + end + + local CT = CurTime() + + self:SetNextPrimaryFire(CT + 2.95) + self:SetNextSecondaryFire(CT + 2.95) + self.HealTime = CT + 2.5 + self.CurrentHeal = "bandage" + self.AmmoType = "Bandages" + self.HealAmount = 10 + self.Target = target + self.OwnHeal = (target == owner) + + -- Анимация + local vm = owner:GetViewModel() + if IsValid(vm) then + local seq = vm:LookupSequence("bandage") + if seq > 0 then + vm:SendViewModelMatchingSequence(seq) + end + end + + -- Звуки + self:PlaySounds("bandage") + + return true +end + +function SWEP:SecondaryAttack() + if not IsFirstTimePredicted() then return end + + local owner = self:GetOwner() + if not IsValid(owner) then return end + + local hemostats = owner:GetAmmoCount("Hemostats") + local quikclots = owner:GetAmmoCount("Quikclots") + + if hemostats <= 0 and quikclots <= 0 then return end + + local target = self:FindHealTarget() + if not IsValid(target) then return end + + -- Проверка здоровья + if CLIENT then + if target:Health() >= 100 then return end + else + if target:Health() >= target:GetMaxHealth() then return end + end + + local CT = CurTime() + + if hemostats > 0 then + -- Гемостат + self:SetNextPrimaryFire(CT + 5.5) + self:SetNextSecondaryFire(CT + 5.5) + self.HealTime = CT + 4.5 + self.CurrentHeal = "suture" + self.AmmoType = "Hemostats" + self.HealAmount = 30 + + local vm = owner:GetViewModel() + if IsValid(vm) then + local seq = vm:LookupSequence("suture") + if seq > 0 then + vm:SendViewModelMatchingSequence(seq) + end + end + + self:PlaySounds("suture") + else + -- QuikClot + self:SetNextPrimaryFire(CT + 4.65) + self:SetNextSecondaryFire(CT + 4.65) + self.HealTime = CT + 4.2 + self.CurrentHeal = "quikclot" + self.AmmoType = "Quikclots" + self.HealAmount = 20 + + local vm = owner:GetViewModel() + if IsValid(vm) then + local seq = vm:LookupSequence("quikclot") + if seq > 0 then + vm:SendViewModelMatchingSequence(seq) + end + end + + self:PlaySounds("quikclot") + end + + self.Target = target + self.OwnHeal = (target == owner) + + return true +end + +if CLIENT then + local White = Color(255, 255, 255, 255) + local Black = Color(0, 0, 0, 255) + local Green = Color(202, 255, 163, 255) + local Grey = Color(200, 200, 200, 255) + + surface.CreateFont("IFAK_HUD24", { + font = "Arial", + size = 24, + weight = 700, + antialias = true + }) + + surface.CreateFont("IFAK_HUD36", { + font = "Arial", + size = 36, + weight = 700, + antialias = true + }) + + function SWEP:DrawHUD() + local owner = self:GetOwner() + if not IsValid(owner) then return end + + local x, y = ScrW() / 2, ScrH() / 2 + + local target = self:FindHealTarget() + local targetName = "СЕБЯ" + if IsValid(target) and target != owner then + targetName = target:Nick() + end + + draw.SimpleTextOutlined( + "ЦЕЛЬ ЛЕЧЕНИЯ: " .. targetName, + "IFAK_HUD24", + x, y + 200, + White, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, + 2, Black + ) + + -- Информация о расходниках + local bandages = owner:GetAmmoCount("Bandages") + local hemostats = owner:GetAmmoCount("Hemostats") + local quikclots = owner:GetAmmoCount("Quikclots") + + -- Левая сторона - Бинты + draw.SimpleTextOutlined( + "БИНТЫ: " .. bandages, + "IFAK_HUD36", + x - 100, y + 125, + White, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP, + 2, Black + ) + + if bandages > 0 then + draw.SimpleTextOutlined( + "ЛКМ - БИНТ", + "IFAK_HUD24", + x - 100, y + 100, + Green, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP, + 2, Black + ) + + draw.SimpleTextOutlined( + "+10 HP", + "IFAK_HUD24", + x - 100, y + 75, + Green, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP, + 2, Black + ) + end + + -- Правая сторона - Гемостаты/QuikClot + if hemostats > 0 then + draw.SimpleTextOutlined( + "ГЕМОСТАТЫ: " .. hemostats, + "IFAK_HUD36", + x + 100, y + 125, + White, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, + 2, Black + ) + + draw.SimpleTextOutlined( + "ПКМ - ГЕМОСТАТ", + "IFAK_HUD24", + x + 100, y + 100, + Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, + 2, Black + ) + + draw.SimpleTextOutlined( + "+30 HP", + "IFAK_HUD24", + x + 100, y + 75, + Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, + 2, Black + ) + else + draw.SimpleTextOutlined( + "QUIKCLOTS: " .. quikclots, + "IFAK_HUD36", + x + 100, y + 125, + White, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, + 2, Black + ) + + if quikclots > 0 then + draw.SimpleTextOutlined( + "ПКМ - QUIKCLOT", + "IFAK_HUD24", + x + 100, y + 100, + Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, + 2, Black + ) + + draw.SimpleTextOutlined( + "+20 HP", + "IFAK_HUD24", + x + 100, y + 75, + Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, + 2, Black + ) + end + end + end +end + diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/cl_init.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/cl_init.lua new file mode 100644 index 0000000..1769d74 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/cl_init.lua @@ -0,0 +1,615 @@ +include('shared.lua') + +if not cigaParticleEmitter then cigaParticleEmitter = ParticleEmitter(Vector(0,0,0)) end + +function SWEP:DrawWorldModel() + local ply = self:GetOwner() + + local cigaScale = self.cigaScale or 1 + self:SetModelScale(cigaScale, 0) + self:SetSubMaterial() + + if IsValid(ply) then + local modelStr = ply:GetModel():sub(1,17) + local isPony = modelStr=="models/ppm/player" or modelStr=="models/mlp/player" or modelStr=="models/cppm/playe" + + local bn = isPony and "LrigScull" or "ValveBiped.Bip01_R_Hand" + if ply.cigaArmFullyUp then bn ="ValveBiped.Bip01_Head1" end + local bon = ply:LookupBone(bn) or 0 + + local opos = self:GetPos() + local oang = self:GetAngles() + local bp,ba = ply:GetBonePosition(bon) + if bp then opos = bp end + if ba then oang = ba end + + if ply.cigaArmFullyUp then + --head position + opos = opos + (oang:Forward()*0.95) + (oang:Right()*7) + (oang:Up()*0.035) + oang:RotateAroundAxis(oang:Forward(),-100) + oang:RotateAroundAxis(oang:Up(),100) + opos = opos + (oang:Up()*(cigaScale-1)*-10.25) + else + --hand position + oang:RotateAroundAxis(oang:Forward(),50) + oang:RotateAroundAxis(oang:Right(),90) + opos = opos + (oang:Forward()*2) + (oang:Up()*-4.5) + (oang:Right()*-2) + oang:RotateAroundAxis(oang:Forward(),90) + oang:RotateAroundAxis(oang:Up(),10) + opos = opos + (oang:Up()*(cigaScale-1)*-10.25) + opos = opos + (oang:Up() * 2) + opos = opos + (oang:Right() * 0.5) + opos = opos + (oang:Forward() * -1.5) + end + self:SetupBones() + + local mrt = self:GetBoneMatrix(0) + if mrt then + mrt:SetTranslation(opos) + mrt:SetAngles(oang) + + self:SetBoneMatrix(0, mrt) + end + end + + self:DrawModel() +end + +function SWEP:GetViewModelPosition(pos, ang) + --mouth pos + local vmpos1=self.cigaVMPos1 or Vector(18.5,-3.4,-3.25) + local vmang1=self.cigaVMAng1 or Vector(170,-180,20) + --hand pos + local vmpos2=self.cigaVMPos2 or Vector(24,-8,-11.2) + local vmang2=self.cigaVMAng2 or Vector(120,-180,150) + + if not LocalPlayer().cigaArmTime then LocalPlayer().cigaArmTime=0 end + local lerp = math.Clamp((os.clock()-LocalPlayer().cigaArmTime)*3,0,1) + if LocalPlayer().cigaArm then lerp = 1-lerp end + /* + local newpos = LerpVector(lerp,vmpos1,vmpos2) + local newang = LerpVector(lerp,vmang1,vmang2) + --I have a good reason for doing it like this + newang = Angle(newang.x,newang.y,newang.z) + + pos,ang = LocalToWorld(newpos,newang,pos,ang)*/ + local difvec = Vector(-10,-3.5,-12)--vmpos1 - vmpos2 + local orig = Vector(0,0,0) + local topos = orig+difvec + + local difang = Vector(-30,0,0)--vmang1 - vmang2 + local origang = Vector(0,0,0) + local toang = origang+difang + + + + local newpos = LerpVector(lerp,topos,orig) + local newang = LerpVector(lerp,toang,origang) + + newang = Angle(newang.x, newang.y, newang.z) + + + pos,ang = LocalToWorld(newpos,newang,pos,ang) + return pos, ang +end + +sound.Add({ + name = "ciga_inhale", + channel = CHAN_WEAPON, + volume = 0.24, + level = 60, + pitch = { 95 }, + sound = "cigainhale.wav" +}) + +net.Receive("ciga",function() + local ply = net.ReadEntity() + local amt = net.ReadInt(8) + local fx = net.ReadInt(8) + if !IsValid(ply) then return end + + if amt>=50 then + ply:EmitSound("cigacough1.wav",90) + + for i=1,200 do + local d=i+10 + if i>140 then d=d+150 end + timer.Simple((d-1)*0.003,function() ciga_do_pulse(ply, 1, 100, fx) end) + end + + return + elseif amt>=35 then + ply:EmitSound("cigabreath2.wav",75,100,0.7) + elseif amt>=10 then + ply:EmitSound("cigabreath1.wav",70,130-math.min(100,amt*2),0.4+(amt*0.005)) + end + + for i=1,amt*2 do + timer.Simple((i-1)*0.02,function() ciga_do_pulse(ply,math.floor(((amt*2)-i)/10), fx==2 and 100 or 0, fx) end) + end +end) + +net.Receive("cigaArm",function() + local ply = net.ReadEntity() + local z = net.ReadBool() + if !IsValid(ply) then return end + if ply.cigaArm != z then + if z then + timer.Simple(0.3, function() + if !IsValid(ply) then return end + if ply.cigaArm then ply:EmitSound("ciga_inhale") end + end) + else + ply:StopSound("ciga_inhale") + end + ply.cigaArm = z + ply.cigaArmTime = os.clock() + local m = 0 + if z then m = 1 end + + for i=0,9 do + timer.Simple(i/30,function() ciga_interpolate_arm(ply,math.abs(m-((9-i)/10)),z and 0 or 0.2) end) + end + end +end) + +net.Receive("cigaTalking",function() + local ply = net.ReadEntity() + if IsValid(ply) then ply.cigaTalkingEndtime = net.ReadFloat() end +end) + +function ciga_interpolate_arm(ply, mult, mouth_delay) + if !IsValid(ply) then return end + + if mouth_delay>0 then + timer.Simple(mouth_delay,function() if IsValid(ply) then ply.cigaMouthOpenAmt = mult end end) + else + ply.cigaMouthOpenAmt = mult + end + + local b1 = ply:LookupBone("ValveBiped.Bip01_R_Upperarm") + local b2 = ply:LookupBone("ValveBiped.Bip01_R_Forearm") + if (not b1) or (not b2) then return end + ply:ManipulateBoneAngles(b1,Angle(20*mult,-62*mult,10*mult)) + ply:ManipulateBoneAngles(b2,Angle(-5*mult,-10*mult,0)) + if mult==1 then ply.cigaArmFullyUp=true else ply.cigaArmFullyUp=false end +end + +--this makes the mouth opening work without clobbering other addons +hook.Add("InitPostEntity", "cigaMouthMoveSetup", function() + timer.Simple(1, function() + if ciga_OriginalMouthMove ~= nil then return end + + ciga_OriginalMouthMove = GAMEMODE.MouthMoveAnimation + + function GAMEMODE:MouthMoveAnimation(ply) + --run the base MouthMoveAnimation if player isn't vaping/cigatalking + if ((ply.cigaMouthOpenAmt or 0) == 0) and ((ply.cigaTalkingEndtime or 0) < CurTime()) then + return ciga_OriginalMouthMove(GAMEMODE, ply) + end + + local FlexNum = ply:GetFlexNum() - 1 + if ( FlexNum <= 0 ) then return end + for i = 0, FlexNum - 1 do + local Name = ply:GetFlexName(i) + if ( Name == "jaw_drop" || Name == "right_part" || Name == "left_part" || Name == "right_mouth_drop" || Name == "left_mouth_drop" ) then + ply:SetFlexWeight(i, math.max(((ply.cigaMouthOpenAmt or 0)*0.5), math.Clamp(((ply.cigaTalkingEndtime or 0)-CurTime())*3.0, 0, 1)*math.Rand(0.1,0.8) )) + end + end + end + end) +end) + +function ciga_do_pulse(ply, amt, spreadadd, fx) + if !IsValid(ply) then return end + + if ply:WaterLevel()==3 then return end + + if not spreadadd then spreadadd=0 end + + local attachid = ply:LookupAttachment("eyes") + cigaParticleEmitter:SetPos(LocalPlayer():GetPos()) + + local angpos = ply:GetAttachment(attachid) or {Ang=Angle(0,0,0), Pos=Vector(0,0,0)} + local fwd + local pos + + if (ply != LocalPlayer()) then + fwd = (angpos.Ang:Forward()-angpos.Ang:Up()):GetNormalized() + pos = angpos.Pos + (fwd*3.5) + else + fwd = ply:GetAimVector():GetNormalized() + pos = ply:GetShootPos() + fwd*1.5 + gui.ScreenToVector( ScrW()/2, ScrH() )*5 + end + + fwd = ply:GetAimVector():GetNormalized() + + for i = 1,amt do + if !IsValid(ply) then return end + local particle = cigaParticleEmitter:Add(string.format("particle/smokesprites_00%02d",math.random(7,16)), pos ) + if particle then + local dir = VectorRand():GetNormalized() * ((amt+5)/10) + ciga_do_particle(particle, (ply:GetVelocity()*0.25)+(((fwd*9)+dir):GetNormalized() * math.Rand(50,80) * (amt + 1) * 0.2), fx) + end + end +end + +function ciga_do_particle(particle, vel, fx) + particle:SetColor(255,255,255,255) + if fx == 3 then particle:SetColor(100,100,100,100) end + if fx >= 4 then + local c = JuicycigaJuices[fx-3].color + if c == nil then c = HSVToColor(math.random(0,359),1,1) end + particle:SetColor(c.r, c.g, c.b, 255) + end + + local mega = 1 + if fx == 2 then mega = 4 end + mega = mega * 0.3 + + particle:SetVelocity( vel * mega ) + particle:SetGravity( Vector(0,0,1.5) ) + particle:SetLifeTime(0) + particle:SetDieTime(math.Rand(80,100)*0.11*mega) + particle:SetStartSize(3*mega) + particle:SetEndSize(40*mega*mega) + particle:SetStartAlpha(150) + particle:SetEndAlpha(0) + particle:SetCollide(true) + particle:SetBounce(0.25) + particle:SetRoll(math.Rand(0,360)) + particle:SetRollDelta(0.01*math.Rand(-40,40)) + particle:SetAirResistance(50) +end + +matproxy.Add({ + name = "cigaTankColor", + init = function( self, mat, values ) + self.ResultTo = values.resultvar + end, + bind = function( self, mat, ent ) + if ( !IsValid( ent ) ) then return end + if ent:GetClass()=="viewmodel" then + ent=ent:GetOwner() + if ( !IsValid( ent ) or !ent:IsPlayer() ) then return end + ent=ent:GetActiveWeapon() + if ( !IsValid( ent ) ) then return end + end + local v = ent.cigaTankColor or Vector(0.3,0.3,0.3) + if v==Vector(-1,-1,-1) then + local c = HSVToColor((CurTime()*60)%360,0.9,0.9) + v = Vector(c.r,c.g,c.b)/255.0 + end + mat:SetVector(self.ResultTo, v) + end +}) + +matproxy.Add({ + name = "cigaAccentColor", + init = function( self, mat, values ) + self.ResultTo = values.resultvar + end, + bind = function( self, mat, ent ) + if ( !IsValid( ent ) ) then return end + local o = ent:GetOwner() + if ent:GetClass()=="viewmodel" then + if (!IsValid(o)) or (!o:IsPlayer()) then return end + ent=o:GetActiveWeapon() + if ( !IsValid( ent ) ) then return end + end + local special = false + local col = ent.cigaAccentColor or special and Vector(1,0.8,0) or Vector(1,1,1) + if col==Vector(-1,-1,-1) then + col=Vector(1,1,1) + if IsValid(o) then col=o:GetWeaponColor() end + end + mat:SetVector(self.ResultTo, col) + end +}) + + +--Swep Construction Kit code-- + +if CLIENT then + + SWEP.vRenderOrder = nil + function SWEP:ViewModelDrawn() + + local vm = self.Owner:GetViewModel() + if !IsValid(vm) then return end + + if (!self.VElements) then return end + + self:UpdateBonePositions(vm) + + if (!self.vRenderOrder) then + + // we build a render order because sprites need to be drawn after models + self.vRenderOrder = {} + + for k, v in pairs( self.VElements ) do + if (v.type == "Model") then + table.insert(self.vRenderOrder, 1, k) + elseif (v.type == "Sprite" or v.type == "Quad") then + table.insert(self.vRenderOrder, k) + end + end + + end + + for k, name in ipairs( self.vRenderOrder ) do + + local v = self.VElements[name] + if (!v) then self.vRenderOrder = nil break end + if (v.hide) then continue end + + local model = v.modelEnt + local sprite = v.spriteMaterial + + if (!v.bone) then continue end + + local pos, ang = self:GetBoneOrientation( self.VElements, v, vm ) + + if (!pos) then continue end + + if (v.type == "Model" and IsValid(model)) then + + model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z ) + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + model:SetAngles(ang) + //model:SetModelScale(v.size) + local matrix = Matrix() + matrix:Scale(v.size) + model:EnableMatrix( "RenderMultiply", matrix ) + + if (v.material == "") then + model:SetMaterial("") + elseif (model:GetMaterial() != v.material) then + model:SetMaterial( v.material ) + end + + if (v.skin and v.skin != model:GetSkin()) then + model:SetSkin(v.skin) + end + + if (v.bodygroup) then + for k, v in pairs( v.bodygroup ) do + if (model:GetBodygroup(k) != v) then + model:SetBodygroup(k, v) + end + end + end + + if (v.surpresslightning) then + render.SuppressEngineLighting(true) + end + + render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255) + render.SetBlend(v.color.a/255) + model:DrawModel() + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if (v.surpresslightning) then + render.SuppressEngineLighting(false) + end + + elseif (v.type == "Sprite" and sprite) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + render.SetMaterial(sprite) + render.DrawSprite(drawpos, v.size.x, v.size.y, v.color) + + elseif (v.type == "Quad" and v.draw_func) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + cam.Start3D2D(drawpos, ang, v.size) + v.draw_func( self ) + cam.End3D2D() + + end + + end + + end + + function SWEP:GetBoneOrientation( basetab, tab, ent, bone_override ) + + local bone, pos, ang + if (tab.rel and tab.rel != "") then + + local v = basetab[tab.rel] + + if (!v) then return end + + // Technically, if there exists an element with the same name as a bone + // you can get in an infinite loop. Let's just hope nobody's that stupid. + pos, ang = self:GetBoneOrientation( basetab, v, ent ) + + if (!pos) then return end + + pos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + else + + bone = ent:LookupBone(bone_override or tab.bone) + + if (!bone) then return end + + pos, ang = Vector(0,0,0), Angle(0,0,0) + local m = ent:GetBoneMatrix(bone) + if (m) then + pos, ang = m:GetTranslation(), m:GetAngles() + end + + if (IsValid(self.Owner) and self.Owner:IsPlayer() and + ent == self.Owner:GetViewModel() and self.ViewModelFlip) then + ang.r = -ang.r // Fixes mirrored models + end + + end + + return pos, ang + end + + function SWEP:CreateModels( tab ) + + if (!tab) then return end + + // Create the clientside models here because Garry says we can't do it in the render hook + for k, v in pairs( tab ) do + if (v.type == "Model" and v.model and v.model != "" and (!IsValid(v.modelEnt) or v.createdModel != v.model) and + string.find(v.model, ".mdl") and file.Exists (v.model, "GAME") ) then + + v.modelEnt = ClientsideModel(v.model, RENDER_GROUP_VIEW_MODEL_OPAQUE) + if (IsValid(v.modelEnt)) then + v.modelEnt:SetPos(self:GetPos()) + v.modelEnt:SetAngles(self:GetAngles()) + v.modelEnt:SetParent(self) + v.modelEnt:SetNoDraw(true) + v.createdModel = v.model + else + v.modelEnt = nil + end + + elseif (v.type == "Sprite" and v.sprite and v.sprite != "" and (!v.spriteMaterial or v.createdSprite != v.sprite) + and file.Exists ("materials/"..v.sprite..".vmt", "GAME")) then + + local name = v.sprite.."-" + local params = { ["$basetexture"] = v.sprite } + // make sure we create a unique name based on the selected options + local tocheck = { "nocull", "additive", "vertexalpha", "vertexcolor", "ignorez" } + for i, j in pairs( tocheck ) do + if (v[j]) then + params["$"..j] = 1 + name = name.."1" + else + name = name.."0" + end + end + + v.createdSprite = v.sprite + v.spriteMaterial = CreateMaterial(name,"UnlitGeneric",params) + + end + end + + end + + local allbones + local hasGarryFixedBoneScalingYet = false + + function SWEP:UpdateBonePositions(vm) + + if self.ViewModelBoneMods then + + if (!vm:GetBoneCount()) then return end + + // !! WORKAROUND !! // + // We need to check all model names :/ + local loopthrough = self.ViewModelBoneMods + if (!hasGarryFixedBoneScalingYet) then + allbones = {} + for i=0, vm:GetBoneCount() do + local bonename = vm:GetBoneName(i) + if (self.ViewModelBoneMods[bonename]) then + allbones[bonename] = self.ViewModelBoneMods[bonename] + else + allbones[bonename] = { + scale = Vector(1,1,1), + pos = Vector(0,0,0), + angle = Angle(0,0,0) + } + end + end + + loopthrough = allbones + end + // !! ----------- !! // + + for k, v in pairs( loopthrough ) do + local bone = vm:LookupBone(k) + if (!bone) then continue end + + // !! WORKAROUND !! // + local s = Vector(v.scale.x,v.scale.y,v.scale.z) + local p = Vector(v.pos.x,v.pos.y,v.pos.z) + local ms = Vector(1,1,1) + if (!hasGarryFixedBoneScalingYet) then + local cur = vm:GetBoneParent(bone) + while(cur >= 0) do + local pscale = loopthrough[vm:GetBoneName(cur)].scale + ms = ms * pscale + cur = vm:GetBoneParent(cur) + end + end + + s = s * ms + // !! ----------- !! // + + if vm:GetManipulateBoneScale(bone) != s then + vm:ManipulateBoneScale( bone, s ) + end + if vm:GetManipulateBoneAngles(bone) != v.angle then + vm:ManipulateBoneAngles( bone, v.angle ) + end + if vm:GetManipulateBonePosition(bone) != p then + vm:ManipulateBonePosition( bone, p ) + end + end + else + self:ResetBonePositions(vm) + end + + end + + function SWEP:ResetBonePositions(vm) + + if (!vm:GetBoneCount()) then return end + for i=0, vm:GetBoneCount() do + vm:ManipulateBoneScale( i, Vector(1, 1, 1) ) + vm:ManipulateBoneAngles( i, Angle(0, 0, 0) ) + vm:ManipulateBonePosition( i, Vector(0, 0, 0) ) + end + + end + + /************************** + Global utility code + **************************/ + + // Fully copies the table, meaning all tables inside this table are copied too and so on (normal table.Copy copies only their reference). + // Does not copy entities of course, only copies their reference. + // WARNING: do not use on tables that contain themselves somewhere down the line or you'll get an infinite loop + function table.FullCopy( tab ) + + if (!tab) then return nil end + + local res = {} + for k, v in pairs( tab ) do + if (type(v) == "table") then + res[k] = table.FullCopy(v) // recursion ho! + elseif (type(v) == "Vector") then + res[k] = Vector(v.x, v.y, v.z) + elseif (type(v) == "Angle") then + res[k] = Angle(v.p, v.y, v.r) + else + res[k] = v + end + end + + return res + + end + +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/init.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/init.lua new file mode 100644 index 0000000..338179e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/init.lua @@ -0,0 +1,55 @@ +AddCSLuaFile ("cl_init.lua") +AddCSLuaFile ("shared.lua") +include ("shared.lua") + +util.AddNetworkString("ciga") +util.AddNetworkString("cigaArm") +util.AddNetworkString("cigaTalking") + +function cigaUpdate(ply, cigaID) + if not ply.cigaCount then ply.cigaCount = 0 end + if not ply.cantStartciga then ply.cantStartciga=false end + if ply.cigaCount == 0 and ply.cantStartciga then return end + + ply.cigaID = cigaID + ply.cigaCount = ply.cigaCount + 1 + if ply.cigaCount == 1 then + ply.cigaArm = true + net.Start("cigaArm") + net.WriteEntity(ply) + net.WriteBool(true) + net.Broadcast() + end + if ply.cigaCount >= 50 then + ply.cantStartciga = true + Releaseciga(ply) + end +end + +hook.Add("KeyRelease","DocigaHook",function(ply, key) + if key == IN_ATTACK then + Releaseciga(ply) + ply.cantStartciga=false + end +end) + +function Releaseciga(ply) + if not ply.cigaCount then ply.cigaCount = 0 end + if IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon():GetClass():sub(1,11) == "weapon_ciga" then + if ply.cigaCount >= 5 then + net.Start("ciga") + net.WriteEntity(ply) + net.WriteInt(ply.cigaCount, 8) + net.WriteInt(ply.cigaID + (ply:GetActiveWeapon().juiceID or 0), 8) + net.Broadcast() + end + end + if ply.cigaArm then + ply.cigaArm = false + net.Start("cigaArm") + net.WriteEntity(ply) + net.WriteBool(false) + net.Broadcast() + end + ply.cigaCount=0 +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/shared.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/shared.lua new file mode 100644 index 0000000..be36942 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_ciga/shared.lua @@ -0,0 +1,131 @@ +SWEP.PrintName = "Marlboro" + +SWEP.IconLetter = "" +SWEP.Author = "AeroMatix" +SWEP.Category = "[FT] Атмосфера" -- Support the author by not changing this. +SWEP.Slot = 1 +SWEP.SlotPos = 0 + +SWEP.ViewModelFOV = 62 --default + +SWEP.BounceWeaponIcon = false + +SWEP.ViewModel = "models/oldcigshib.mdl" +SWEP.WorldModel = "models/oldcigshib.mdl" +SWEP.Spawnable = true +SWEP.AdminOnly = false + +SWEP.Primary.Clipsize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.Clipsize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.DrawAmmo = false +SWEP.HoldType = "slam" + +SWEP.cigaID = 1 + +function SWEP:Deploy() + self:SetHoldType("slam") +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Initialize() + + if !self.CigaInitialized then + self.CigaInitialized = true + self.VElements = { + ["ciga"] = { type = "Model", model = self.ViewModel, bone = "ValveBiped.Bip01_Spine4", rel = "", pos = Vector(-7.1, -2.401, 23.377), angle = Angle(111.039, 10.519, 0), size = Vector(1, 1, 1), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} } + } + --self.VElements["ciga"].model = self.ViewModel + self.OldCigaModel = self.ViewModel + self.ViewModel = "models/weapons/c_slam.mdl" + self.UseHands = true + self.ViewModelFlip = true + self.ShowViewModel = true + self.ShowWorldModel = true + self.ViewModelBoneMods = { + ["ValveBiped.Bip01_L_Finger1"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(-23.334, -12.223, -32.223) }, + ["ValveBiped.Bip01_L_Finger12"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -21.112, 0) }, + ["ValveBiped.Bip01_L_Finger4"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -65.556, 0) }, + ["ValveBiped.Bip01_R_UpperArm"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, 72.222, -41.112) }, + ["ValveBiped.Bip01_L_Finger0"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(10, 1.11, -1.111) }, + ["Detonator"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) }, + ["ValveBiped.Bip01_L_Hand"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(-27.778, 1.11, -7.778) }, + ["Slam_panel"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) }, + ["ValveBiped.Bip01_L_Finger2"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -47.778, 0) }, + ["ValveBiped.Bip01_L_Finger3"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -43.334, 0) }, + ["Slam_base"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) }, + ["ValveBiped.Bip01_R_Hand"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) } + } + + end + + if CLIENT then + + // Create a new table for every weapon instance + self.VElements = table.FullCopy( self.VElements ) + self.WElements = table.FullCopy( self.WElements ) + self.ViewModelBoneMods = table.FullCopy( self.ViewModelBoneMods ) + + self:CreateModels(self.VElements) // create viewmodels + self:CreateModels(self.WElements) // create worldmodels + + // init view model bone build function + if IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + self:ResetBonePositions(vm) + + // Init viewmodel visibility + if (self.ShowViewModel == nil or self.ShowViewModel) then + vm:SetColor(Color(255,255,255,255)) + else + // we set the alpha to 1 instead of 0 because else ViewModelDrawn stops being called + vm:SetColor(Color(255,255,255,1)) + // ^ stopped working in GMod 13 because you have to do Entity:SetRenderMode(1) for translucency to kick in + // however for some reason the view model resets to render mode 0 every frame so we just apply a debug material to prevent it from drawing + vm:SetMaterial("Debug/hsv") + end + end + end + + end + if self.Initialize2 then self:Initialize2() end +end + +function SWEP:PrimaryAttack() + if SERVER then + cigaUpdate(self.Owner, self.cigaID) + end + self.Weapon:SetNextPrimaryFire(CurTime() + 0.1) +end + +function SWEP:Reload() + +end + +function SWEP:Holster() + if SERVER and IsValid(self.Owner) then + Releaseciga(self.Owner) + end + + if CLIENT and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + self:ResetBonePositions(vm) + end + end + + return true +end + +SWEP.OnDrop = SWEP.Holster +SWEP.OnRemove = SWEP.Holster diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_cigarette_camel.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_cigarette_camel.lua new file mode 100644 index 0000000..9366ecf --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_cigarette_camel.lua @@ -0,0 +1,17 @@ +if CLIENT then + include('weapon_ciga/cl_init.lua') +else + include('weapon_ciga/shared.lua') +end + +function SWEP:SecondaryAttack() +end + +SWEP.PrintName = "Camel" + +SWEP.ViewModel = "models/oldcigshib.mdl" +SWEP.WorldModel = "models/oldcigshib.mdl" + +SWEP.cigaID = 3 + +SWEP.cigaAccentColor = Vector(1,1,1.1) diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_cigarette_newport.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_cigarette_newport.lua new file mode 100644 index 0000000..2525c52 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_cigarette_newport.lua @@ -0,0 +1,89 @@ +if CLIENT then + include('weapon_ciga/cl_init.lua') +else + include('weapon_ciga/shared.lua') +end + +SWEP.PrintName = "Newport" + +SWEP.cigaAccentColor = nil + +SWEP.cigaID = 4 +SWEP.ViewModel = "models/ciga.mdl" +SWEP.WorldModel = "models/ciga.mdl" + +--Add your own flavors here, obviously +JuicycigaJuices = { + {name = "without filter", color = Color(40,40,40,255)}, + {name = "with filter", color = Color(210,180,140,255)}, +} + +if SERVER then + function SWEP:Initialize2() + self.juiceID = 0 + timer.Simple(0.1, function() SendcigaJuice(self, JuicycigaJuices[self.juiceID+1]) end) + end + + util.AddNetworkString("cigaTankColor") + util.AddNetworkString("cigaMessage") +end + +function SWEP:SecondaryAttack() + if SERVER then + if not self.juiceID then self.juiceID = 0 end + self.juiceID = (self.juiceID + 1) % (#JuicycigaJuices) + SendcigaJuice(self, JuicycigaJuices[self.juiceID+1]) + + --Client hook isn't called in singleplayer + if game.SinglePlayer() then self.Owner:SendLua([[surface.PlaySound("weapons/smg1/switch_single.wav")]]) end + else + if IsFirstTimePredicted() then + surface.PlaySound("weapons/smg1/switch_single.wav") + end + end +end + +if SERVER then + function SendcigaJuice(ent, tab) + local col = tab.color + if col then + local min = math.min(col.r,col.g,col.b)*0.8 + col = (Vector(col.r-min, col.g-min, col.b-min)*1.0)/255.0 + else + col = Vector(-1,-1,-1) + end + net.Start("cigaTankColor") + net.WriteEntity(ent) + net.WriteVector(col) + net.Broadcast() + + if IsValid(ent.Owner) then + net.Start("cigaMessage") + net.WriteString("Loaded "..tab.name.."") + net.Send(ent.Owner) + end + end +else + net.Receive("cigaTankColor", function() + local ent = net.ReadEntity() + local col = net.ReadVector() + if IsValid(ent) then ent.cigaTankColor = col end + end) + + cigaMessageDisplay = "" + cigaMessageDisplayTime = 0 + + net.Receive("cigaMessage", function() + cigaMessageDisplay = net.ReadString() + cigaMessageDisplayTime = CurTime() + end) + + hook.Add("HUDPaint", "cigaDrawJuiceMessage", function() + local alpha = math.Clamp((cigaMessageDisplayTime+3-CurTime())*1.5,0,1) + if alpha == 0 then return end + + surface.SetFont("Trebuchet24") + local w,h = surface.GetTextSize(cigaMessageDisplay) + draw.WordBox(8, ((ScrW() - w)/2)-8, ScrH() - (h + 24), cigaMessageDisplay, "Trebuchet24", Color(0,0,0,128*alpha), Color(255,255,255,255*alpha)) + end) +end diff --git a/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_lordi_sledgehammer.lua b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_lordi_sledgehammer.lua new file mode 100644 index 0000000..295c08b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/entities/weapons/weapon_lordi_sledgehammer.lua @@ -0,0 +1,830 @@ +CreateConVar( "lordi_sledge_break_doors", "1", { FCVAR_REPLICATED, FCVAR_NOTIFY }, "Should people be able to break down doors using the sledgehammer? 1 for true, 0 for false" ) +CreateConVar( "lordi_sledge_break_doors_chance", "5", { FCVAR_REPLICATED, FCVAR_NOTIFY }, "What should the 1/ chance be before a door breaks? The higher this is, the harder it is. 1 makes it break immediadly" ) + +SWEP.WElements = { + ["sledge"] = { type = "Model", model = "models/weapons/lordi/c_sledgehammer.mdl", bone = "ValveBiped.Bip01_R_Hand", rel = "", pos = Vector(-16.33, -3.294, -16.605), angle = Angle(8.395, 0, -115.988), size = Vector(1, 1, 1), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} } +} + +SWEP.ViewModelFOV = 70 +SWEP.ViewModel = "models/weapons/lordi/c_sledgehammer.mdl" +SWEP.WorldModel = "models/weapons/w_crowbar.mdl" +SWEP.UseHands = true +SWEP.AutoSwitchTo = true +SWEP.Slot = 0 +SWEP.HoldType = "passive" +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.DrawCrosshair = false +SWEP.Category = "[FT] Специальное Оружие" +SWEP.SlotPos = 0 +SWEP.DrawAmmo = true +SWEP.PrintName = "Sledgehammer" +SWEP.Author = "LordiAnders" +SWEP.Instructions = "Bash heads in! Or if they are afar, throw the thing after them!" +SWEP.ShowViewModel = true +SWEP.ShowWorldModel = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Ammo = "none" +SWEP.Primary.Automatic = true + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.Ammo = "none" +SWEP.Secondary.Automatic = true + +SWEP.AboutToSwing = false +SWEP.IsSwinging = false + +SWEP.AboutToSwing2 = false +SWEP.IsSwinging2 = false + +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID("vgui/entities/weapon_lordi_sledgehammer") + killicon.Add("weapon_lordi_sledgehammer","vgui/entities/weapon_lordi_sledgehammer",Color(255,255,255,255)) + killicon.Add("ent_lordi_sledgehammer","vgui/entities/weapon_lordi_sledgehammer",Color(255,255,255,255)) + SWEP.BounceWeaponIcon = false +end + +function SWEP:VerifyAndSet(bone,data) + if not IsValid(self.Owner) then return end + local bone = self.Owner:LookupBone(bone) + if bone then + self.Owner:ManipulateBoneAngles(bone,data) + end +end + +function SWEP:SetPassiveHoldType() + if not IsValid(self.Owner) then return end + if CLIENT then return end + + self:SetHoldType("passive") + + self:VerifyAndSet('ValveBiped.Bip01_R_UpperArm',Angle(13.8940719901227,12.334109775164,14.597405385818)) + self:VerifyAndSet('ValveBiped.Bip01_R_Hand',Angle(17,30,9)) + self:VerifyAndSet('ValveBiped.Bip01_L_Forearm',Angle(-16.224373259067,-24.886877720213,7.8929222269922)) + self:VerifyAndSet('ValveBiped.Bip01_L_UpperArm',Angle(-32.017127057138,-35.637168682527,16)) + self:VerifyAndSet('ValveBiped.Bip01_L_Hand',Angle(0.17154435310358,0,0)) +end + +function SWEP:SetMelee2HoldType() + if not IsValid(self.Owner) then return end + if CLIENT then return end + + self:SetHoldType("melee2") + + self:VerifyAndSet('ValveBiped.Bip01_R_UpperArm',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_R_Hand',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_L_Forearm',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_L_UpperArm',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_L_Hand',Angle(0,0,0)) +end + +function SWEP:Deploy() +self.Weapon:SendWeaponAnim(ACT_VM_DRAW) +self:SetDeploySpeed(self.Owner:GetViewModel():SequenceDuration()) +self.Weapon:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() ) +self.Weapon:SetNextSecondaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() ) + +self.AboutToSwing = true --Just to disable holstering + +self:SetPassiveHoldType() + +if SERVER then + timer.Simple(0.9,function() + if not self:IsValid() then return end + self:EmitSound("npc/combine_soldier/gear6.wav") + self.Owner:ViewPunch( Angle(-2,0,1) ) + end) + timer.Simple(2,function() + if not self:IsValid() then return end + self.Owner:ViewPunch( Angle(5,0,-5) ) + self:EmitSound("physics/flesh/flesh_impact_hard2.wav",75,180) + self.AboutToSwing = false + end) +end +end + +function SWEP:Think() + if self.IsSwinging and not self.Owner:KeyDown(IN_ATTACK) then + self:PrimaryAttack() + elseif self.IsSwinging2 and not self.Owner:KeyDown(IN_ATTACK2) then + self:SecondaryAttack() + end +end + +function SWEP:PrimaryAttack() + if self.IsSwinging2 or self.AboutToSwing2 then return end + if not self.IsSwinging then + self.AboutToSwing = true + self:SetMelee2HoldType() + self.Weapon:SetNextPrimaryFire( CurTime() + 5000 ) + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + timer.Simple(self.Owner:GetViewModel():SequenceDuration(),function() + self.AboutToSwing = false + self.IsSwinging = true + end) + end + + if self.IsSwinging then + --Trace shit from weapon_fists.lua packed with Gmod + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + timer.Simple(0.25,function() if not self:IsValid() then return end self:SetPassiveHoldType() end) + local trace = util.TraceLine( { + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 105, + filter = self.Owner + } ) + + if ( !IsValid( trace.Entity ) ) then + trace = util.TraceHull( { + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 105, + filter = self.Owner, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ) + } ) + end + + if trace.Entity:IsValid() or trace.HitWorld then + self.Weapon:SendWeaponAnim(ACT_VM_HITKILL) + self.Owner:ViewPunch( Angle(-10,0,0) ) + timer.Simple(0.1,function() + if not self:IsValid() then return end + self.Owner:ViewPunch( Angle(15,0,0) ) + end) + if trace.Entity:IsValid() then + if trace.Entity:IsPlayer() or trace.Entity:IsNPC() then + if SERVER then trace.Entity:EmitSound("physics/body/body_medium_break"..math.random(2,4)..".wav",75,math.random(70,90)) end + local effectdata = EffectData() + effectdata:SetOrigin(trace.HitPos) + effectdata:SetEntity(trace.Entity) + util.Effect( "BloodImpact", effectdata ) + elseif trace.Entity:GetClass() == "prop_door_rotating" and GetConVarNumber( "lordi_sledge_break_doors" ) == 1 then + if SERVER and math.random(1,GetConVarNumber( "lordi_sledge_break_doors_chance" )) == 1 then + trace.Entity:Fire("unlock") + trace.Entity:Fire("open") + + trace.Entity:SetNotSolid(true) + trace.Entity:SetNoDraw(true) + + --Bit of madcow stuff here... :L + + local ent = ents.Create("prop_physics") + + trace.Entity:EmitSound("physics/wood/wood_furniture_break1.wav") + + ent:SetPos(trace.Entity:GetPos()) + ent:SetAngles(trace.Entity:GetAngles()) + ent:SetModel(trace.Entity:GetModel()) + + if trace.Entity:GetSkin() then + ent:SetSkin(trace.Entity:GetSkin()) + end + + ent:Spawn() + + ent:GetPhysicsObject():ApplyForceCenter( self.Owner:GetAimVector() * 10000 ) + + timer.Simple(25,function() + if ent:IsValid() then + ent:Remove() + end + trace.Entity:SetNotSolid(false) + trace.Entity:SetNoDraw(false) + end) + elseif SERVER then + trace.Entity:EmitSound("physics/wood/wood_box_break"..math.random(1,2)..".wav",75,math.random(50,150)) + end + end + if SERVER then trace.Entity:TakeDamage(math.random(70,120),self.Owner) end + end + if trace.HitWorld then + local trace = self.Owner:GetEyeTrace() + + if self.Owner:GetShootPos():Distance(trace.HitPos) <= 105 then + util.Decal("Impact.Sand",trace.HitPos + trace.HitNormal,trace.HitPos - trace.HitNormal) + end + end + if SERVER then timer.Simple(0,function() if not self:IsValid() then return end self:EmitSound("physics/metal/metal_canister_impact_hard"..math.random(1,3)..".wav",75,math.random(90,110)) end) end + else + self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER) + if SERVER then timer.Simple(0,function() if not self:IsValid() then return end self:EmitSound("npc/zombie/claw_miss1.wav",75,60) end) end + self.Owner:ViewPunch( Angle(-10,0,0) ) + timer.Simple(0.1,function() + if not self:IsValid() then return end + self.Owner:ViewPunch( Angle(30,0,0) ) + end) + end + self.IsSwinging = false + self.Weapon:SetNextPrimaryFire( CurTime() + 1 ) + self.Weapon:SetNextSecondaryFire( CurTime() + 1 ) + end +end + +function SWEP:GetViewModelPosition(pos, ang) + if self.IsSwinging then + pos = pos + (math.random(-1,1) / 40) * ang:Right() + pos = pos + (math.random(-1,1) / 40) * ang:Forward() + pos = pos + (math.random(-1,1) / 40) * ang:Up() + end + + return pos, ang +end + +function SWEP:SecondaryAttack() + if self.IsSwinging or self.AboutToSwing then return end + if not self.IsSwinging2 then + self.AboutToSwing2 = true + self:SetMelee2HoldType() + self.Weapon:SetNextSecondaryFire( CurTime() + 5000 ) + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + timer.Simple(self.Owner:GetViewModel():SequenceDuration(),function() + self.AboutToSwing2 = false + self.IsSwinging2 = true + end) + end + + if self.IsSwinging2 then + self.IsSwinging2 = false + self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER) + timer.Simple(0.1,function() + if SERVER then + local sledge = ents.Create("ent_lordi_sledgehammer") + sledge:SetOwner(self.Owner) + sledge:SetPos(self.Owner:EyePos() + self.Owner:GetAimVector()) + sledge:SetAngles(self.Owner:GetAngles()) + sledge:Spawn() + sledge:Activate() + + sledge:GetPhysicsObject():ApplyForceCenter( self.Owner:GetAimVector() * 7000 ) + + timer.Simple(0.25,function() self.Owner:StripWeapon(self:GetClass()) end) + end + end) + end +end + +function SWEP:Reload() end + +function SWEP:OnRemove() + if IsValid(self.Owner) then + self:VerifyAndSet('ValveBiped.Bip01_R_UpperArm',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_R_Hand',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_L_Forearm',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_L_UpperArm',Angle(0,0,0)) + self:VerifyAndSet('ValveBiped.Bip01_L_Hand',Angle(0,0,0)) + local vm = self.Owner:GetViewModel() + if IsValid(vm) then vm:SetMaterial("") end + end + if CLIENT and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + self:ResetBonePositions(vm) + end + end +end + +/******************************************************** + SWEP Construction Kit base code + Created by Clavus + Available for public use, thread at: + facepunch.com/threads/1032378 + + + DESCRIPTION: + This script is meant for experienced scripters + that KNOW WHAT THEY ARE DOING. Don't come to me + with basic Lua questions. + + Just copy into your SWEP or SWEP base of choice + and merge with your own code. + + The SWEP.VElements, SWEP.WElements and + SWEP.ViewModelBoneMods tables are all optional + and only have to be visible to the client. +********************************************************/ + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) + + // other initialize code goes here + + if CLIENT then + + // Create a new table for every weapon instance + self.VElements = table.FullCopy( self.VElements ) + self.WElements = table.FullCopy( self.WElements ) + self.ViewModelBoneMods = table.FullCopy( self.ViewModelBoneMods ) + + self:CreateModels(self.VElements) // create viewmodels + self:CreateModels(self.WElements) // create worldmodels + + // init view model bone build function + if IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + self:ResetBonePositions(vm) + end + + // Init viewmodel visibility + end + + end + +end + +function SWEP:Holster() + + if self.AboutToSwing or self.IsSwinging or self.IsSwinging2 or self.AboutToSwing2 then return end + + self:OnRemove() + + if CLIENT and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + self:ResetBonePositions(vm) + end + end + + return true +end + +if CLIENT then + + SWEP.vRenderOrder = nil + function SWEP:ViewModelDrawn() + + local vm = self.Owner:GetViewModel() + if !IsValid(vm) then return end + + if (!self.VElements) then return end + + self:UpdateBonePositions(vm) + + if (!self.vRenderOrder) then + + // we build a render order because sprites need to be drawn after models + self.vRenderOrder = {} + + for k, v in pairs( self.VElements ) do + if (v.type == "Model") then + table.insert(self.vRenderOrder, 1, k) + elseif (v.type == "Sprite" or v.type == "Quad") then + table.insert(self.vRenderOrder, k) + end + end + + end + + for k, name in ipairs( self.vRenderOrder ) do + + local v = self.VElements[name] + if (!v) then self.vRenderOrder = nil break end + if (v.hide) then continue end + + local model = v.modelEnt + local sprite = v.spriteMaterial + + if (!v.bone) then continue end + + local pos, ang = self:GetBoneOrientation( self.VElements, v, vm ) + + if (!pos) then continue end + + if (v.type == "Model" and IsValid(model)) then + + model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z ) + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + model:SetAngles(ang) + //model:SetModelScale(v.size) + local matrix = Matrix() + matrix:Scale(v.size) + model:EnableMatrix( "RenderMultiply", matrix ) + + if (v.material == "") then + model:SetMaterial("") + elseif (model:GetMaterial() != v.material) then + model:SetMaterial( v.material ) + end + + if (v.skin and v.skin != model:GetSkin()) then + model:SetSkin(v.skin) + end + + if (v.bodygroup) then + for k, v in pairs( v.bodygroup ) do + if (model:GetBodygroup(k) != v) then + model:SetBodygroup(k, v) + end + end + end + + if (v.surpresslightning) then + render.SuppressEngineLighting(true) + end + + render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255) + render.SetBlend(v.color.a/255) + model:DrawModel() + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if (v.surpresslightning) then + render.SuppressEngineLighting(false) + end + + elseif (v.type == "Sprite" and sprite) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + render.SetMaterial(sprite) + render.DrawSprite(drawpos, v.size.x, v.size.y, v.color) + + elseif (v.type == "Quad" and v.draw_func) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + cam.Start3D2D(drawpos, ang, v.size) + v.draw_func( self ) + cam.End3D2D() + + end + + end + + end + + SWEP.wRenderOrder = nil + function SWEP:DrawWorldModel() + + self:DrawModel() + self:SetMaterial( "engine/occlusionproxy" ) + + if (!self.WElements) then return end + + if (!self.wRenderOrder) then + + self.wRenderOrder = {} + + for k, v in pairs( self.WElements ) do + if (v.type == "Model") then + table.insert(self.wRenderOrder, 1, k) + elseif (v.type == "Sprite" or v.type == "Quad") then + table.insert(self.wRenderOrder, k) + end + end + + end + + if (IsValid(self.Owner)) then + bone_ent = self.Owner + else + // when the weapon is dropped + bone_ent = self + end + + for k, name in pairs( self.wRenderOrder ) do + + local v = self.WElements[name] + if (!v) then self.wRenderOrder = nil break end + if (v.hide) then continue end + + local pos, ang + + if (v.bone) then + pos, ang = self:GetBoneOrientation( self.WElements, v, bone_ent ) + else + pos, ang = self:GetBoneOrientation( self.WElements, v, bone_ent, "ValveBiped.Bip01_R_Hand" ) + end + + if (!pos) then continue end + + local model = v.modelEnt + local sprite = v.spriteMaterial + + if (v.type == "Model" and IsValid(model)) then + + model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z ) + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + model:SetAngles(ang) + //model:SetModelScale(v.size) + local matrix = Matrix() + matrix:Scale(v.size) + model:EnableMatrix( "RenderMultiply", matrix ) + + if (v.material == "") then + model:SetMaterial("") + elseif (model:GetMaterial() != v.material) then + model:SetMaterial( v.material ) + end + + if (v.skin and v.skin != model:GetSkin()) then + model:SetSkin(v.skin) + end + + if (v.bodygroup) then + for k, v in pairs( v.bodygroup ) do + if (model:GetBodygroup(k) != v) then + model:SetBodygroup(k, v) + end + end + end + + if (v.surpresslightning) then + render.SuppressEngineLighting(true) + end + + render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255) + render.SetBlend(v.color.a/255) + model:DrawModel() + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if (v.surpresslightning) then + render.SuppressEngineLighting(false) + end + + elseif (v.type == "Sprite" and sprite) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + render.SetMaterial(sprite) + render.DrawSprite(drawpos, v.size.x, v.size.y, v.color) + + elseif (v.type == "Quad" and v.draw_func) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + cam.Start3D2D(drawpos, ang, v.size) + v.draw_func( self ) + cam.End3D2D() + + end + + end + + end + + function SWEP:GetBoneOrientation( basetab, tab, ent, bone_override ) + + local bone, pos, ang + if (tab.rel and tab.rel != "") then + + local v = basetab[tab.rel] + + if (!v) then return end + + // Technically, if there exists an element with the same name as a bone + // you can get in an infinite loop. Let's just hope nobody's that stupid. + pos, ang = self:GetBoneOrientation( basetab, v, ent ) + + if (!pos) then return end + + pos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + else + + bone = ent:LookupBone(bone_override or tab.bone) + + if (!bone) then return end + + pos, ang = Vector(0,0,0), Angle(0,0,0) + local m = ent:GetBoneMatrix(bone) + if (m) then + pos, ang = m:GetTranslation(), m:GetAngles() + end + + if (IsValid(self.Owner) and self.Owner:IsPlayer() and + ent == self.Owner:GetViewModel() and self.ViewModelFlip) then + ang.r = -ang.r // Fixes mirrored models + end + + end + + return pos, ang + end + + function SWEP:CreateModels( tab ) + + if (!tab) then return end + + // Create the clientside models here because Garry says we can't do it in the render hook + for k, v in pairs( tab ) do + if (v.type == "Model" and v.model and v.model != "" and (!IsValid(v.modelEnt) or v.createdModel != v.model) and + string.find(v.model, ".mdl") and file.Exists (v.model, "GAME") ) then + + v.modelEnt = ClientsideModel(v.model, RENDER_GROUP_VIEW_MODEL_OPAQUE) + if (IsValid(v.modelEnt)) then + v.modelEnt:SetPos(self:GetPos()) + v.modelEnt:SetAngles(self:GetAngles()) + v.modelEnt:SetParent(self) + v.modelEnt:SetNoDraw(true) + v.createdModel = v.model + else + v.modelEnt = nil + end + + elseif (v.type == "Sprite" and v.sprite and v.sprite != "" and (!v.spriteMaterial or v.createdSprite != v.sprite) + and file.Exists ("materials/"..v.sprite..".vmt", "GAME")) then + + local name = v.sprite.."-" + local params = { ["$basetexture"] = v.sprite } + // make sure we create a unique name based on the selected options + local tocheck = { "nocull", "additive", "vertexalpha", "vertexcolor", "ignorez" } + for i, j in pairs( tocheck ) do + if (v[j]) then + params["$"..j] = 1 + name = name.."1" + else + name = name.."0" + end + end + + v.createdSprite = v.sprite + v.spriteMaterial = CreateMaterial(name,"UnlitGeneric",params) + + end + end + + end + + local allbones + local hasGarryFixedBoneScalingYet = false + + function SWEP:UpdateBonePositions2(vm) + + if self.GrenadeThrowBoneMods then + + if (!vm:GetBoneCount()) then return end + + local loopthrough = self.GrenadeThrowBoneMods + if (!hasGarryFixedBoneScalingYet) then + allbones = {} + for i=0, vm:GetBoneCount() do + local bonename = vm:GetBoneName(i) + if (self.GrenadeThrowBoneMods[bonename]) then + allbones[bonename] = self.GrenadeThrowBoneMods[bonename] + else + allbones[bonename] = { + scale = Vector(1,1,1), + pos = Vector(0,0,0), + angle = Angle(0,0,0) + } + end + end + + loopthrough = allbones + end + + for k, v in pairs( loopthrough ) do + local bone = vm:LookupBone(k) + if (!bone) then continue end + + local s = Vector(v.scale.x,v.scale.y,v.scale.z) + local p = Vector(v.pos.x,v.pos.y,v.pos.z) + local ms = Vector(1,1,1) + if (!hasGarryFixedBoneScalingYet) then + local cur = vm:GetBoneParent(bone) + while(cur >= 0) do + local pscale = loopthrough[vm:GetBoneName(cur)].scale + ms = ms * pscale + cur = vm:GetBoneParent(cur) + end + end + + s = s * ms + + if vm:GetManipulateBoneScale(bone) != s then + vm:ManipulateBoneScale( bone, s ) + end + if vm:GetManipulateBoneAngles(bone) != v.angle then + vm:ManipulateBoneAngles( bone, v.angle ) + end + if vm:GetManipulateBonePosition(bone) != p then + vm:ManipulateBonePosition( bone, p ) + end + end + else + self:ResetBonePositions(vm) + end + + end + + function SWEP:UpdateBonePositions(vm) + + if self.ViewModelBoneMods then + + if (!vm:GetBoneCount()) then return end + + // !! WORKAROUND !! // + // We need to check all model names :/ + local loopthrough = self.ViewModelBoneMods + if (!hasGarryFixedBoneScalingYet) then + allbones = {} + for i=0, vm:GetBoneCount() do + local bonename = vm:GetBoneName(i) + if (self.ViewModelBoneMods[bonename]) then + allbones[bonename] = self.ViewModelBoneMods[bonename] + else + allbones[bonename] = { + scale = Vector(1,1,1), + pos = Vector(0,0,0), + angle = Angle(0,0,0) + } + end + end + + loopthrough = allbones + end + // !! ----------- !! // + + for k, v in pairs( loopthrough ) do + local bone = vm:LookupBone(k) + if (!bone) then continue end + + // !! WORKAROUND !! // + local s = Vector(v.scale.x,v.scale.y,v.scale.z) + local p = Vector(v.pos.x,v.pos.y,v.pos.z) + local ms = Vector(1,1,1) + if (!hasGarryFixedBoneScalingYet) then + local cur = vm:GetBoneParent(bone) + while(cur >= 0) do + local pscale = loopthrough[vm:GetBoneName(cur)].scale + ms = ms * pscale + cur = vm:GetBoneParent(cur) + end + end + + s = s * ms + // !! ----------- !! // + + if vm:GetManipulateBoneScale(bone) != s then + vm:ManipulateBoneScale( bone, s ) + end + if vm:GetManipulateBoneAngles(bone) != v.angle then + vm:ManipulateBoneAngles( bone, v.angle ) + end + if vm:GetManipulateBonePosition(bone) != p then + vm:ManipulateBonePosition( bone, p ) + end + end + else + self:ResetBonePositions(vm) + end + + end + + function SWEP:ResetBonePositions(vm) + + if (!vm:GetBoneCount()) then return end + for i=0, vm:GetBoneCount() do + vm:ManipulateBoneScale( i, Vector(1, 1, 1) ) + vm:ManipulateBoneAngles( i, Angle(0, 0, 0) ) + vm:ManipulateBonePosition( i, Vector(0, 0, 0) ) + end + + end + + /************************** + Global utility code + **************************/ + + // Fully copies the table, meaning all tables inside this table are copied too and so on (normal table.Copy copies only their reference). + // Does not copy entities of course, only copies their reference. + // WARNING: do not use on tables that contain themselves somewhere down the line or you'll get an infinite loop + function table.FullCopy( tab ) + + if (!tab) then return nil end + + local res = {} + for k, v in pairs( tab ) do + if (type(v) == "table") then + res[k] = table.FullCopy(v) // recursion ho! + elseif (type(v) == "Vector") then + res[k] = Vector(v.x, v.y, v.z) + elseif (type(v) == "Angle") then + res[k] = Angle(v.p, v.y, v.r) + else + res[k] = v + end + end + + return res + + end + +end diff --git a/garrysmod/gamemodes/militaryrp/gamemode/autorun/server/sv_ammo_limits.lua b/garrysmod/gamemodes/militaryrp/gamemode/autorun/server/sv_ammo_limits.lua new file mode 100644 index 0000000..a098e3b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/gamemode/autorun/server/sv_ammo_limits.lua @@ -0,0 +1,4 @@ +if SERVER then + game.SetAmmoMax("rpg_round", 1) + game.SetAmmoMax("PanzerFaust3 Rocket", 1) +end diff --git a/garrysmod/gamemodes/militaryrp/gamemode/cl_init.lua b/garrysmod/gamemodes/militaryrp/gamemode/cl_init.lua new file mode 100644 index 0000000..32443cc --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/gamemode/cl_init.lua @@ -0,0 +1,2 @@ + +DeriveGamemode("helix") diff --git a/garrysmod/gamemodes/militaryrp/gamemode/init.lua b/garrysmod/gamemodes/militaryrp/gamemode/init.lua new file mode 100644 index 0000000..ab32876 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/gamemode/init.lua @@ -0,0 +1,9 @@ + +AddCSLuaFile("cl_init.lua") +DeriveGamemode("helix") + +if SERVER then + -- Limit ammo for anti-tank rockets to 1 + game.SetAmmoMax("rpg_round", 1) + game.SetAmmoMax("PanzerFaust3 Rocket", 1) +end diff --git a/garrysmod/gamemodes/militaryrp/militaryrp.txt b/garrysmod/gamemodes/militaryrp/militaryrp.txt new file mode 100644 index 0000000..56aa185 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/militaryrp.txt @@ -0,0 +1,8 @@ + +"exp_surv" +{ + "base" "helix" + "title" "FT Team VNU" + "author" "nebulous" + "menusystem" "1" +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/cl_plugin.lua new file mode 100644 index 0000000..e899242 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/cl_plugin.lua @@ -0,0 +1,199 @@ +local PLUGIN = PLUGIN + +surface.CreateFont("ixAdminShopTitle", { font = "Montserrat", size = 28, weight = 900, antialias = true }) +surface.CreateFont("ixAdminShopTab", { font = "Montserrat", size = 18, weight = 700, antialias = true }) +surface.CreateFont("ixAdminShopItem", { font = "Montserrat", size = 20, weight = 800, antialias = true }) +surface.CreateFont("ixAdminShopDesc", { font = "Montserrat", size = 14, weight = 500, antialias = true }) +surface.CreateFont("ixAdminShopPrice", { font = "Montserrat", size = 18, weight = 900, antialias = true }) +surface.CreateFont("ixAdminShopSmall", { font = "Montserrat", size = 14, weight = 600, antialias = true }) +surface.CreateFont("ixAdminShopBold", { font = "Montserrat", size = 16, weight = 800, antialias = true }) + +-- Modern Color Palette #00431c +local COLOR_BASE = Color(0, 67, 28) +local COLOR_BG = Color(15, 18, 16, 250) +local COLOR_CARD = Color(22, 26, 24, 255) +local COLOR_CARD_LIGHT = Color(30, 36, 32, 255) +local COLOR_TEXT = Color(240, 245, 240) +local COLOR_TEXT_DIM = Color(140, 150, 145) +local COLOR_ACCENT = Color(0, 140, 60) +local COLOR_DANGER = Color(200, 50, 50) +local COLOR_WARN = Color(200, 150, 0) + +function PLUGIN:WrapText(text, width, font) + surface.SetFont(font) + local words = string.Explode(" ", text) + local lines = {} + local currentLine = "" + + for _, word in ipairs(words) do + local testLine = currentLine == "" and word or currentLine .. " " .. word + local w, _ = surface.GetTextSize(testLine) + + if w > width then + table.insert(lines, currentLine) + currentLine = word + else + currentLine = testLine + end + end + + if currentLine != "" then + table.insert(lines, currentLine) + end + + return lines +end + +local PANEL = {} + +function PANEL:Init() + self:SetSize(ScrW() * 0.7, ScrH() * 0.75) + self:Center() + self:MakePopup() + self:SetTitle("") + self:ShowCloseButton(false) + self.currentView = "shop" + + self:SetAlpha(0) + self:AlphaTo(255, 0.2, 0) + + self.startTime = SysTime() + + self.Paint = function(s, w, h) + Derma_DrawBackgroundBlur(s, s.startTime) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG) + surface.SetDrawColor(COLOR_BASE.r, COLOR_BASE.g, COLOR_BASE.b, 150) + surface.SetMaterial(Material("vgui/gradient-u")) + surface.DrawTexturedRect(0, 0, w, 80) + surface.SetDrawColor(COLOR_BASE) + surface.DrawRect(0, 80, w, 2) + + local title = self.currentView == "shop" and "АДМИН МАГАЗИН" or (self.currentView == "inventory" and "МОЙ ИНВЕНТАРЬ" or "УПРАВЛЕНИЕ АДМИНИСТРАТОРАМИ") + draw.SimpleText(title, "ixAdminShopTitle", 40, 40, COLOR_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + local points = LocalPlayer():GetAdminPoints() + draw.SimpleText("Текущий баланс:", "ixAdminShopSmall", w * 0.45, 40, COLOR_TEXT_DIM, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + draw.SimpleText(points .. " Очков", "ixAdminShopItem", w * 0.45 + 10, 40, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local closeBtn = self:Add("DButton") + closeBtn:SetSize(40, 40) + closeBtn:SetPos(self:GetWide() - 50, 20) + closeBtn:SetText("✕") + closeBtn:SetFont("ixAdminShopTitle") + closeBtn:SetTextColor(COLOR_TEXT_DIM) + closeBtn.Paint = nil + closeBtn.DoClick = function() + self:AlphaTo(0, 0.2, 0, function() self:Close() end) + end + closeBtn.OnCursorEntered = function(s) s:SetTextColor(COLOR_DANGER) end + closeBtn.OnCursorExited = function(s) s:SetTextColor(COLOR_TEXT_DIM) end + + self.nav = self:Add("Panel") + self.nav:SetSize(450, 40) + self.nav:SetPos(self:GetWide() - 520, 20) + + local function CreateNavBtn(text, view, x, activeColor) + local btn = self.nav:Add("DButton") + btn:SetSize(140, 40) + btn:SetPos(x, 0) + btn:SetText(text) + btn:SetFont("ixAdminShopBold") + btn:SetTextColor(COLOR_TEXT) + btn.Paint = function(s, w, h) + if self.currentView == view then + draw.RoundedBox(4, 0, 0, w, h, activeColor) + elseif s:IsHovered() then + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 10)) + end + end + btn.DoClick = function() + surface.PlaySound("ui/buttonclick.wav") + self.currentView = view + self:Refresh() + end + return btn + end + + CreateNavBtn("Магазин", "shop", 0, COLOR_BASE) + + self.content = self:Add("DScrollPanel") + self.content:Dock(FILL) + self.content:DockMargin(40, 100, 40, 40) + + local sbar = self.content:GetVBar() + sbar:SetWide(8) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(0, 0, 0, 100)) + end + sbar.btnGrip.Paint = function(s, w, h) + draw.RoundedBox(4, 2, 0, w-4, h, s:IsHovered() and COLOR_ACCENT or COLOR_BASE) + end + + self:Refresh() +end + +function PANEL:Refresh() + self.content:Clear() + + if (self.currentView == "shop") then + local layout = self.content:Add("DIconLayout") + layout:Dock(TOP) + layout:SetSpaceX(20) + layout:SetSpaceY(20) + + for id, item in pairs(PLUGIN.Items) do + local card = layout:Add("DPanel") + card:SetSize(250, 340) + card.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_CARD) + surface.SetDrawColor(COLOR_CARD_LIGHT) + surface.SetMaterial(Material("vgui/gradient-d")) + surface.DrawTexturedRect(0, 0, w, 140) + + draw.SimpleText(item.name, "ixAdminShopItem", w/2, 160, COLOR_TEXT, TEXT_ALIGN_CENTER) + + local descLines = PLUGIN:WrapText(item.desc, w - 30, "ixAdminShopDesc") + for i, line in ipairs(descLines) do + if (i > 4) then break end + draw.SimpleText(line, "ixAdminShopDesc", w/2, 185 + (i-1)*18, COLOR_TEXT_DIM, TEXT_ALIGN_CENTER) + end + end + + local icon = card:Add("DPanel") + icon:SetSize(60, 60) + icon:SetPos(250/2 - 30, 40) + icon.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG) + surface.SetDrawColor(item.color or COLOR_ACCENT) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local buy = card:Add("DButton") + buy:SetSize(210, 45) + buy:SetPos(20, 275) + buy:SetText("Купить за " .. item.price .. " Очков") + buy:SetFont("ixAdminShopBold") + buy:SetTextColor(COLOR_TEXT) + buy.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and COLOR_ACCENT or COLOR_BASE) + end + buy.DoClick = function() + surface.PlaySound("ui/buttonclick.wav") + net.Start("ixAdminShopBuy") + net.WriteString(id) + net.SendToServer() + end + end + end +end + +vgui.Register("ixAdminShop", PANEL, "DFrame") + +net.Receive("ixAdminShopOpen", function() + if (IsValid(ixAdminShop)) then + ixAdminShop:Close() + end + ixAdminShop = vgui.Create("ixAdminShop") +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sh_plugin.lua new file mode 100644 index 0000000..75934a2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sh_plugin.lua @@ -0,0 +1,75 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Admin Shop" +PLUGIN.author = "Scripty" +PLUGIN.description = "Adds an admin shop with a unique currency 'Points', inventory, and admin panel." + +PLUGIN.Items = { + ["remove_warn"] = { + name = "Снятие выговора", + desc = "Снимает один активный выговор с вашего аккаунта. Выговор будет удален из базы данных, и это отобразится в истории.", + price = 150, + category = "Other", + icon = "icon16/error_delete.png", + oneTime = true + }, + ["tacrp_ex_m4a1"] = { + name = "Оружие: HK416", + desc = "Мощная штурмовая винтовка HK416. Доступ на 10 дней. Оружие выдается через инвентарь.", + price = 50, + category = "Weapons", + duration = 10, + class = "tacrp_ex_m4a1", + color = Color(30, 144, 255) + }, + ["tacrp_mg4"] = { + name = "Оружие: M249", + desc = "Тяжелый пулемет M249. Доступ на 15 дней. Оружие выдается через инвентарь.", + price = 60, + category = "Weapons", + duration = 15, + class = "tacrp_mg4", + color = Color(255, 140, 0) + }, + ["tacrp_ex_hecate"] = { + name = "Оружие: MSR", + desc = "Снайперская винтовка MSR. Доступ на 13 дней. Оружие выдается через инвентарь.", + price = 60, + category = "Weapons", + duration = 13, + class = "tacrp_ex_hecate", + color = Color(138, 43, 226) + }, + ["tacrp_ak_ak12"] = { + name = "Оружие: AK-12", + desc = "Современная штурмовая винтовка АК-12. Доступ на 5 дней. Оружие выдается через инвентарь.", + price = 45, + category = "Weapons", + duration = 5, + class = "tacrp_ak_ak12", + color = Color(255, 215, 0) + }, + ["tacrp_sd_aac_hb"] = { + name = "Оружие: LVO-AC", + desc = "Превосходная штурмовая винтовка TFA LVO-AC. Доступ на 15 дней. Оружие выдается через инвентарь.", + price = 50, + category = "Weapons", + duration = 15, + class = "tacrp_sd_aac_hb", + color = Color(255, 165, 0) + } +} + +-- Point utility functions +local playerMeta = debug.getregistry().Player + +function playerMeta:GetAdminPoints() + return self:GetNetVar("adminPoints", 0) +end + +function playerMeta:GetAdminInventory() + return self:GetNetVar("adminInventory", {}) +end + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sv_plugin.lua new file mode 100644 index 0000000..ad4ee33 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sv_plugin.lua @@ -0,0 +1,184 @@ +local PLUGIN = PLUGIN + +util.AddNetworkString("ixAdminShopOpen") +util.AddNetworkString("ixAdminShopBuy") +util.AddNetworkString("ixAdminShopRetrieve") +util.AddNetworkString("ixAdminShopAdminSync") +util.AddNetworkString("ixAdminShopAdminAction") + +function PLUGIN:Initialize() + self.shopData = ix.data.Get("adminShop", {}) +end + +function PLUGIN:SaveShopData() + ix.data.Set("adminShop", self.shopData) +end + +function PLUGIN:PlayerLoaded(client) + local steamID = client:SteamID() + self.shopData[steamID] = self.shopData[steamID] or {points = 0, inventory = {}} + + client:SetNetVar("adminPoints", self.shopData[steamID].points) + client:SetNetVar("adminInventory", self.shopData[steamID].inventory) +end + +local playerMeta = debug.getregistry().Player + +function playerMeta:SetAdminPoints(amount) + amount = math.max(0, amount) + self:SetNetVar("adminPoints", amount) + + local steamID = self:SteamID() + PLUGIN.shopData[steamID] = PLUGIN.shopData[steamID] or {points = 0, inventory = {}} + PLUGIN.shopData[steamID].points = amount + PLUGIN:SaveShopData() +end + +function playerMeta:GiveAdminPoints(amount) + self:SetAdminPoints(self:GetAdminPoints() + amount) +end + +function playerMeta:TakeAdminPoints(amount) + self:SetAdminPoints(self:GetAdminPoints() - amount) +end + +function playerMeta:SetAdminInventory(data) + self:SetNetVar("adminInventory", data) + + local steamID = self:SteamID() + PLUGIN.shopData[steamID] = PLUGIN.shopData[steamID] or {points = 0, inventory = {}} + PLUGIN.shopData[steamID].inventory = data + PLUGIN:SaveShopData() +end + +ix.command.Add("AdminShop", { + description = "Открыть магазин администратора.", + OnRun = function(self, client) + if (!client:IsAdmin() and !client:IsSuperAdmin() and !IsAdminRank(client:GetUserGroup())) then + client:Notify("Эта команда доступна только администраторам.") + return + end + net.Start("ixAdminShopOpen") + net.Send(client) + end +}) + +net.Receive("ixAdminShopBuy", function(len, client) + local itemID = net.ReadString() + local item = PLUGIN.Items[itemID] + + if (!item) then return end + + local points = client:GetAdminPoints() + if (points < item.price) then + client:Notify("У вас недостаточно очков!") + return + end + + if (itemID == "remove_warn") then + local warnPlugin = ix.plugin.Get("warn") + if (warnPlugin) then + local steamID = client:SteamID() + local warns = ix.data.Get("warns", {}) + local targetData = warns[steamID] + + if (targetData and targetData.count > 0) then + targetData.count = targetData.count - 1 + table.insert(targetData.history, { + admin = "Admin Shop", + adminSteamID = "STEAM_0:0:0", + reason = "Покупка в магазине", + time = os.time(), + type = "remove" + }) + ix.data.Set("warns", warns) + warnPlugin.warns = warns + client:TakeAdminPoints(item.price) + client:Notify("Вы успешно сняли выговор!") + else + client:Notify("У вас нет активных выговоров для снятия!") + end + else + client:Notify("Система выговоров не найдена!") + end + else + -- Immediate delivery if class exists + if (item.class) then + client:GiveItem(item.class) + client:TakeAdminPoints(item.price) + client:Notify("Вы купили " .. item.name .. ". Предмет выдан!") + else + -- Fallback to inventory just in case (hidden) + local inv = client:GetAdminInventory() + local expireTime = 0 + if (item.duration) then + expireTime = os.time() + (item.duration * 24 * 60 * 60) + end + + table.insert(inv, { + id = itemID, + buyTime = os.time(), + expireTime = expireTime, + name = item.name, + class = item.class + }) + + client:SetAdminInventory(inv) + client:TakeAdminPoints(item.price) + client:Notify("Вы купили " .. item.name .. ". Предмет добавлен в инвентарь (скрыто).") + end + end +end) + +net.Receive("ixAdminShopRetrieve", function(len, client) + local index = net.ReadUInt(16) + local inv = client:GetAdminInventory() + local itemData = inv[index] + + if (!itemData) then return end + + if (itemData.expireTime > 0 and os.time() > itemData.expireTime) then + client:Notify("Срок действия предмета истек!") + table.remove(inv, index) + client:SetAdminInventory(inv) + return + end + + if (itemData.class) then + client:GiveItem(itemData.class) + client:Notify("Вы получили: " .. itemData.name) + end +end) + +local adminRanks = { + ["super admin"] = true, + ["superadmin"] = true, + ["projectteam"] = true, + ["teh.admin"] = true, + ["curator"] = true, + ["sudo-curator"] = true, + ["asist-sudo"] = true, + ["admin"] = true, + ["st.admin"] = true, + ["ivent"] = true, + ["st.event"] = true, + ["event"] = true, + ["disp"] = true, + ["assistant"] = true, + ["prem"] = true, + ["dsmoder"] = true +} + +local function IsAdminRank(rank) + if not rank or rank == "user" then return false end + + local lowerRank = string.lower(rank) + + -- Check exact match in the provided list + if adminRanks[lowerRank] then return true end + + -- Keep generic string searches just in case + if lowerRank == "founder" or lowerRank == "owner" or lowerRank == "manager" then return true end + + return false +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/adminmode/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/adminmode/cl_plugin.lua new file mode 100644 index 0000000..99a67ce --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/adminmode/cl_plugin.lua @@ -0,0 +1,194 @@ +local PLUGIN = PLUGIN + +function PLUGIN:HUDPaint() + local client = LocalPlayer() + if not IsValid(client) then return end + + if not client:IsAdminMode() then return end + + local scrW, scrH = ScrW(), ScrH() + local w, h = 220, 36 + local x, y = scrW / 2 - w / 2, 20 + + surface.SetDrawColor(13, 13, 13, 220) + surface.DrawRect(x, y, w, h) + + surface.SetDrawColor(200, 80, 80, 255) + surface.DrawOutlinedRect(x, y, w, h, 2) + + draw.SimpleText("ADMIN MODE", "ixMenuButtonFont", scrW / 2, y + h / 2, + Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) +end + +surface.CreateFont("AdminESPFontSmall", { + font = "Arial", + size = 12, + weight = 500 +}) + +CLIENT_ADMIN_GROUPS = CLIENT_ADMIN_GROUPS or {} + +local function Draw3DBox(ent, color) + local mins, maxs = ent:OBBMins(), ent:OBBMaxs() + local corners = { + Vector(mins.x, mins.y, mins.z), + Vector(mins.x, maxs.y, mins.z), + Vector(maxs.x, maxs.y, mins.z), + Vector(maxs.x, mins.y, mins.z), + + Vector(mins.x, mins.y, maxs.z), + Vector(mins.x, maxs.y, maxs.z), + Vector(maxs.x, maxs.y, maxs.z), + Vector(maxs.x, mins.y, maxs.z), + } + + local lines = { + {1,2},{2,3},{3,4},{4,1}, + {5,6},{6,7},{7,8},{8,5}, + {1,5},{2,6},{3,7},{4,8} + } + + cam.Start3D() + render.SetColorMaterial() + for _, l in ipairs(lines) do + render.DrawLine( + ent:LocalToWorld(corners[l[1]]), + ent:LocalToWorld(corners[l[2]]), + color, true + ) + end + cam.End3D() +end + +local function CanSeeESP() + local client = LocalPlayer() + if not IsValid(client) then return false end + if not client:IsAdminMode() then return false end + + local group = client:GetUserGroup() + if not CLIENT_ADMIN_GROUPS[group] then return false end + + return true +end + +hook.Add("HUDPaint", "AdminMode_ESP", function() + if not CanSeeESP() then return end + + local client = LocalPlayer() + + for _, ply in ipairs(player.GetAll()) do + if ply ~= client and ply:Alive() then + + local distance = client:GetPos():Distance(ply:GetPos()) + + Draw3DBox(ply, Color(255, 60, 60)) + + local char = ply:GetCharacter() + local top = (ply:GetPos() + Vector(0,0,85)):ToScreen() + local bottom = (ply:GetPos() + Vector(0,0,5)):ToScreen() + + local faction = "Неизвестно" + local podr = "Неизвестно" + local spec = "Неизвестно" + local rank = "Неизвестно" + + local samGroup = ply:GetUserGroup() + local samName = samGroup + + if sam and sam.ranks and sam.ranks[samGroup] and sam.ranks[samGroup].Name then + samName = sam.ranks[samGroup].Name + end + + if char then + local factionID = char:GetFaction() + local factionTable = ix.faction.indices[factionID] + + if factionTable and factionTable.name then + faction = factionTable.name + end + + if factionTable and factionTable.Podr and char.GetPodr then + local id = char:GetPodr() + if factionTable.Podr[id] and factionTable.Podr[id].name then + podr = factionTable.Podr[id].name + end + end + + if factionTable and factionTable.Spec and char.GetSpec then + local id = char:GetSpec() + if factionTable.Spec[id] and factionTable.Spec[id].name then + spec = factionTable.Spec[id].name + end + end + + if factionTable and factionTable.Ranks and char.GetRank then + local id = char:GetRank() + if factionTable.Ranks[id] and factionTable.Ranks[id][1] then + rank = factionTable.Ranks[id][1] + end + end + end + + local steamid = ply:SteamID() + local hp = ply:Health() + local armor = ply:Armor() + local wep = IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon():GetClass() or "—" + + local topLines = {} + + if distance > 500 then + -- Издалека: только ник + topLines = { ply:Name() } + elseif distance > 200 then + -- Средняя дальность: ник + фракция + SteamID + topLines = { + ply:Name(), + "" .. faction, + "" .. steamid + } + else + -- Близко: вся информация + topLines = { + ply:Name(), + "" .. faction, + "" .. podr, + "" .. spec, + "" .. rank, + "" .. samName, + "" .. steamid + } + end + + local y = top.y + for _, text in ipairs(topLines) do + surface.SetDrawColor(0, 0, 0, 160) + surface.DrawRect(top.x - 50, y - 5, 100, 12) + + draw.SimpleText(text, "AdminESPFontSmall", top.x, y, + Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + y = y + 15 + end + + -- Нижняя информация всегда показывается близко + if distance <= 200 then + local bottomLines = { + "HP: " .. hp, + "Armor: " .. armor, + "Weapon: " .. wep + } + + local y2 = bottom.y + for _, text in ipairs(bottomLines) do + surface.SetDrawColor(0, 0, 0, 160) + surface.DrawRect(bottom.x - 40, y2 - 5, 80, 10) + + draw.SimpleText(text, "AdminESPFontSmall", bottom.x, y2, + Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + y2 = y2 + 12 + end + end + end + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/adminmode/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/adminmode/sh_plugin.lua new file mode 100644 index 0000000..2f9cf60 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/adminmode/sh_plugin.lua @@ -0,0 +1,386 @@ +ix.util.Include("cl_plugin.lua") +ix.util.Include("sv_plugin.lua") + +local PLUGIN = PLUGIN +PLUGIN.name = "Admin Mode" +PLUGIN.author = "Scripty" +PLUGIN.description = "Перекид игрока по команде в админ фракцию" + +local accessAdminGroups = { + ["superadmin"] = true, + ["super admin"] = true, + ["projectteam"] = true, + ["teh.admin"] = true, + ["curator"] = true, + ["sudo-curator"] = true, + ["asist-sudo"] = true, + ["admin"] = true, + ["st.admin"] = true, + ["ivent"] = true, + ["st.event"] = true, + ["event"] = true, + ["disp"] = true, + ["assistant"] = true, + ["specadmin"] = true +} +local playerMeta = FindMetaTable("Player") + +function playerMeta:IsAdminMode() + return self:GetNetVar("AdminMode", false) +end + +local function CanUseAdminMode(ply) + return accessAdminGroups[ply:GetUserGroup()] == true +end + +local function CanUseNoclip(ply) + if ply:IsAdminMode() then return true end + + if ply:GetUserGroup() == "superadmin" then return true end + + return false +end + +local function GetWeaponsList(ply) + local list = {} + for _, wep in ipairs(ply:GetWeapons()) do + if IsValid(wep) then + list[#list + 1] = wep:GetClass() + end + end + return list +end + +if (SERVER) then + util.AddNetworkString("AdminMode_Groups") + + hook.Add("PlayerInitialSpawn", "AdminMode_SendGroups", function(ply) + net.Start("AdminMode_Groups") + net.WriteTable(accessAdminGroups) + net.Send(ply) + end) + + function PLUGIN:PlayerDisconnected(ply) + if ply:IsAdminMode() then + local char = ply:GetCharacter() + if char then + local oldFaction = char:GetData("AdminMode_OldFaction") + if oldFaction then char:SetFaction(oldFaction) end + + local oldPodr = char:GetData("AdminMode_OldPodr") + if oldPodr then char:SetPodr(oldPodr) end + + local oldSpec = char:GetData("AdminMode_OldSpec") + if oldSpec then char:SetSpec(oldSpec) end + + local oldRank = char:GetData("AdminMode_OldRank") + if oldRank then char:SetRank(oldRank) end + end + ply:SetNetVar("HideFromTab", false) + ply:SetNetVar("AdminMode", false) + end + end + + function PLUGIN:PlayerLoadedCharacter(ply, char) + local squads = ix.plugin.list["squads"] + + if ply:IsAdminMode() and squads then + local squad, squadID = squads:GetPlayerSquad(ply) + if squad then + if squad.leader == ply:SteamID() then + squads:DisbandSquad(squadID) + else + squads:LeaveSquad(ply) + end + net.Start("ixSquadSync") + net.WriteString("{}") + net.Send(ply) + end + end + + if char:GetFaction() == FACTION_ADMIN and not ply:IsAdminMode() then + + local oldFaction = char:GetData("AdminMode_OldFaction") + local oldPodr = char:GetData("AdminMode_OldPodr") + local oldSpec = char:GetData("AdminMode_OldSpec") + local oldRank = char:GetData("AdminMode_OldRank") + + if oldFaction then char:SetFaction(oldFaction) end + if oldPodr then char:SetPodr(oldPodr) end + if oldSpec then char:SetSpec(oldSpec) end + if oldRank then char:SetRank(oldRank) end + + char:SetData("AdminMode_OldFaction", nil) + char:SetData("AdminMode_OldPodr", nil) + char:SetData("AdminMode_OldSpec", nil) + char:SetData("AdminMode_OldRank", nil) + char:SetData("AdminMode_OldModel", nil) + char:SetData("AdminMode_OldWeapons", nil) + + ply:SetNetVar("AdminMode", false) + ply:SetNetVar("HideFromTab", false) + + local radio = ix.plugin.list["radio"] + if radio then + radio:SyncRadioState(ply, radio.defaultFrequency, false, false) + end + elseif ply:IsAdminMode() and char:GetFaction() ~= FACTION_ADMIN then + self:ToggleAdminMode(ply) + end + + if ply:IsAdminMode() then + local radio = ix.plugin.list["radio"] + if radio then + radio:SyncRadioState(ply, radio.defaultFrequency, false, false) + end + end + end + + function PLUGIN:OnCharacterSave(char) + local ply = char:GetPlayer() + if not IsValid(ply) then return end + + if ply:IsAdminMode() then + local oldFaction = char:GetData("AdminMode_OldFaction") + if oldFaction then char:SetFaction(oldFaction) end + + local oldPodr = char:GetData("AdminMode_OldPodr") + if oldPodr then char:SetPodr(oldPodr) end + + local oldSpec = char:GetData("AdminMode_OldSpec") + if oldSpec then char:SetSpec(oldSpec) end + + local oldRank = char:GetData("AdminMode_OldRank") + if oldRank then char:SetRank(oldRank) end + + ply:SetNetVar("AdminMode", false) + end + end + + function PLUGIN:ToggleAdminMode(ply) + if not IsValid(ply) then return end + if not CanUseAdminMode(ply) then + ply:ChatPrint("[!] У вас нет доступа к админ-моду.") + return + end + + local char = ply:GetCharacter() + if not char then + ply:ChatPrint("[!] У вас нет активного персонажа.") + return + end + + local enable = not ply:IsAdminMode() + ply:SetNetVar("AdminMode", enable) + + if enable then + local squads = ix.plugin.list["squads"] + if squads then + local squad, squadID = squads:GetPlayerSquad(ply) + if squad then + if squad.leader == ply:SteamID() then + squads:DisbandSquad(squadID) + else + squads:LeaveSquad(ply) + end + end + end + char:SetData("AdminMode_OldFaction", char:GetFaction()) + char:SetData("AdminMode_OldPodr", char:GetPodr()) + char:SetData("AdminMode_OldSpec", char:GetSpec()) + char:SetData("AdminMode_OldRank", char:GetRank()) + + char:SetData("AdminMode_OldModel", ply:GetModel()) + char:SetData("AdminMode_OldWeapons", GetWeaponsList(ply)) + + ply:SetModel("models/ft/ft_admin.mdl") + + if FACTION_ADMIN then + char:SetFaction(FACTION_ADMIN) + end + + ply:GodEnable() + ply:SetNoDraw(true) + ply:SetMoveType(MOVETYPE_NOCLIP) + + ply:StripWeapons() + ply:Give("weapon_physgun") + ply:Give("gmod_tool") + ply:Give("ix_hands") + ply:SelectWeapon("ix_hands") + ply:SetNetVar("HideFromTab", true) + + ply:ChatPrint("[!] Админ-мод включен.") + else + local oldFaction = char:GetData("AdminMode_OldFaction") + local oldPodr = char:GetData("AdminMode_OldPodr") + local oldSpec = char:GetData("AdminMode_OldSpec") + local oldRank = char:GetData("AdminMode_OldRank") + + if oldFaction and char:GetFaction() == FACTION_ADMIN then char:SetFaction(oldFaction) end + if oldPodr then char:SetPodr(oldPodr) end + if oldSpec then char:SetSpec(oldSpec) end + if oldRank then char:SetRank(oldRank) end + + local oldModel = char:GetData("AdminMode_OldModel") + if oldModel then + ply:SetModel(oldModel) + end + + local oldWeapons = char:GetData("AdminMode_OldWeapons") or {} + ply:StripWeapons() + + for _, class in ipairs(oldWeapons) do + ply:Give(class) + end + + if ply.sam_uncloak then + ply:sam_uncloak() + end + + ply:SetNoDraw(false) + ply:SetRenderMode(RENDERMODE_NORMAL) + ply:SetColor(Color(255, 255, 255, 255)) + ply:Fire("alpha", 255, 0) + ply:DrawWorldModel(true) + ply:SetMaterial("") + + ply:SetNetVar("ixNoDraw", false) + ply:DrawShadow(true) + ply:SetupHands() + + if #oldWeapons == 0 then + ply:Give("ix_hands") + end + + ply:GodDisable() + ply:SetMoveType(MOVETYPE_WALK) + + local restoredFaction = char:GetFaction() + local class = char:GetClass() or "default" + + local spawnPlugin = ix.plugin.list["spawns"] + + if spawnPlugin and spawnPlugin.spawns then + local factionTable = ix.faction.indices[restoredFaction] + + if factionTable then + local factionID = factionTable.uniqueID + local factionSpawns = spawnPlugin.spawns[factionID] + + if factionSpawns then + local className = "default" + + for _, v in ipairs(ix.class.list) do + if v.index == class then + className = v.uniqueID + break + end + end + + local points = factionSpawns[className] or factionSpawns["default"] + + if points and not table.IsEmpty(points) then + local pos = table.Random(points) + + timer.Simple(0.1, function() + if IsValid(ply) then + ply:SetPos(pos) + end + end) + end + end + end + end + + char:SetData("AdminMode_OldFaction", nil) + char:SetData("AdminMode_OldPodr", nil) + char:SetData("AdminMode_OldSpec", nil) + char:SetData("AdminMode_OldRank", nil) + char:SetData("AdminMode_OldModel", nil) + char:SetData("AdminMode_OldWeapons", nil) + ply:SetNetVar("HideFromTab", false) + local radio = ix.plugin.list["radio"] + if radio then + radio:SyncRadioState(ply, radio.defaultFrequency, false, false) + end + ply:ChatPrint("[!] Админ-мод выключен.") + end + + hook.Run("PlayerAdminModeToggled", ply, enable) + end + + function PLUGIN:EntityTakeDamage(target, dmg) + if IsValid(target) and target:IsPlayer() and target:IsAdminMode() then + return true + end + + local attacker = dmg:GetAttacker() + if IsValid(attacker) and attacker:IsPlayer() and attacker:IsAdminMode() then + return true + end + end + + function PLUGIN:OnCharacterVarChanged(char, key, oldValue, newValue) + if (key == "faction") then + local ply = char:GetPlayer() + + if (IsValid(ply) and ply:IsAdminMode() and newValue ~= FACTION_ADMIN) then + self:ToggleAdminMode(ply) + end + end + end + + function PLUGIN:PlayerNoClip(ply, desiredState) + if not IsValid(ply) then return false end + + if CanUseNoclip(ply) then + ply:SetNoDraw(desiredState) + + if desiredState then + ply:SetRenderMode(RENDERMODE_NONE) + ply:SetColor(Color(255, 255, 255, 0)) + else + ply:SetRenderMode(RENDERMODE_NORMAL) + ply:SetColor(Color(255, 255, 255, 255)) + end + + if desiredState then + ply:SetNetVar("HideFromTab", true) + ply:SetNetVar("ixNoDraw", true) + else + if not ply:IsAdminMode() then + ply:SetNetVar("HideFromTab", false) + ply:SetNetVar("ixNoDraw", false) + end + end + + return true + end + + return false + end + + ix.command.Add("admin", { + description = "Включить/выключить админ-мод.", + OnRun = function(self, ply) + PLUGIN:ToggleAdminMode(ply) + end + }) + + ix.command.Add("adm", { + description = "Синоним /admin.", + OnRun = function(self, ply) + PLUGIN:ToggleAdminMode(ply) + end + }) + +end + +CLIENT_ADMIN_GROUPS = CLIENT_ADMIN_GROUPS or {} + +if CLIENT then + net.Receive("AdminMode_Groups", function() + CLIENT_ADMIN_GROUPS = net.ReadTable() + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/adminmode/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/adminmode/sv_plugin.lua new file mode 100644 index 0000000..87c8e1a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/adminmode/sv_plugin.lua @@ -0,0 +1 @@ +local PLUGIN = PLUGIN \ No newline at end of file diff --git a/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/cl_init.lua new file mode 100644 index 0000000..82fb317 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/cl_init.lua @@ -0,0 +1,5 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/init.lua b/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/init.lua new file mode 100644 index 0000000..a1b4916 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/init.lua @@ -0,0 +1,51 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/sw/shared/airdrop_large.mdl") + self:SetSolid(SOLID_VPHYSICS) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if (IsValid(phys)) then + phys:Wake() + end +end + +function ENT:Use(activator) + if (IsValid(activator) and activator:IsPlayer()) then + if (self.opened) then return end + self.opened = true + + local weaponClass = self.ixWeapon or "tacrp_m320" + local character = activator:GetCharacter() + local inventory = character and character:GetInventory() + + if (inventory and !isnumber(inventory) and ix.item.list[weaponClass]) then + inventory:Add(weaponClass) + ix.util.Notify("Вы получили " .. (ix.item.list[weaponClass].name or weaponClass) .. " из аирдропа.", activator) + else + activator:Give(weaponClass) + ix.util.Notify("Вы получили " .. (self.ixWeapon or "неизвестное оружие") .. " из аирдропа.", activator) + end + + if (IsValid(self.ixSmoke)) then + self.ixSmoke:Remove() + end + + local effect = EffectData() + effect:SetOrigin(self:GetPos()) + effect:SetScale(1) + util.Effect("Explosion", effect) + + self:Remove() + end +end + +function ENT:OnTakeDamage(damage) + if (damage:GetDamage() > 50) then + self:Use(damage:GetAttacker()) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/shared.lua new file mode 100644 index 0000000..4eff0f0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/airdrop/entities/entities/ix_airdrop/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Airdrop" +ENT.Category = "Helix" +ENT.Spawnable = false +ENT.AdminOnly = true diff --git a/garrysmod/gamemodes/militaryrp/plugins/airdrop/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/airdrop/sh_plugin.lua new file mode 100644 index 0000000..5f615d1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/airdrop/sh_plugin.lua @@ -0,0 +1,92 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Airdrop" +PLUGIN.author = "Scripty" +PLUGIN.description = "Airdrop plugin." + +ix.config.Add("airdropPlaneModel", "models/il76/il_76_fly.mdl", "Модель самолета для аирдропа.", nil, { + category = "Airdrop" +}) + +ix.config.Add("airstrikePlaneModel", "models/gunkov2056/su25.mdl", "Модель самолета для авиаудара.", nil, { + category = "Airdrop" +}) + +ix.config.Add("airdropMinInterval", 3600, "Минимальный интервал между аирдропами (в секундах).", nil, { + data = {min = 60, max = 86400}, + category = "Airdrop" +}) + +ix.config.Add("airdropMaxInterval", 7200, "Максимальный интервал между аирдропами (в секундах).", nil, { + data = {min = 60, max = 86400}, + category = "Airdrop" +}) + +ix.config.Add("airdropWeapons", [[tacrp_io_degala +tacrp_io_fiveseven +tacrp_mr96 +tacrp_pdw +tacrp_superv +tacrp_sd_aac_hb +tacrp_ak_ak12 +tacrp_ak_an94 +tacrp_sg551 +tacrp_io_xm8car +tacrp_hk417 +tacrp_io_scarh +tacrp_mg4 +tacrp_io_xm8lmg +tacrp_io_sg550r +tacrp_io_sl8 +tacrp_io_sg550 +tacrp_io_vss +tacrp_as50 +tacrp_ex_hecate +tacrp_civ_m320]], "Список оружия, которое может выпасть из аирдропа (каждое с новой строки).", nil, { + category = "Airdrop" +}) + +ix.config.Add("airstrikeMinInterval", 1800, "Минимальный интервал между авиаударами (в секундах).", nil, { + data = {min = 60, max = 86400}, + category = "Airdrop" +}) + +ix.config.Add("airstrikeMaxInterval", 3600, "Максимальный интервал между авиаударами (в секундах).", nil, { + data = {min = 60, max = 86400}, + category = "Airdrop" +}) + +ix.command.Add("AirdropForce", { + description = "Принудительно вызвать случайный аирдроп.", + privilege = "Manage Airdrops", + superAdminOnly = true, + OnRun = function(self, client) + PLUGIN:SpawnAirdrop() + ix.util.Notify("Аирдроп был вызван администратором.", nil, "all") + end +}) + +ix.command.Add("AirdropGive", { + description = "Вызвать аирдроп с конкретным оружием.", + privilege = "Manage Airdrops", + superAdminOnly = true, + arguments = { + ix.type.string + }, + OnRun = function(self, client, weapon) + PLUGIN:SpawnAirdrop(weapon) + ix.util.Notify("Аирдроп с " .. weapon .. " был вызван администратором.", nil, "all") + end +}) + +ix.command.Add("AirstrikeForce", { + description = "Принудительно вызвать авиаудар.", + privilege = "Manage Airdrops", + superAdminOnly = true, + OnRun = function(self, client) + PLUGIN:SpawnAirstrike() + ix.util.Notify("Авиаудар был вызван администратором.", nil, "all") + end +}) + +ix.util.Include("sv_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/airdrop/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/airdrop/sv_plugin.lua new file mode 100644 index 0000000..b74a5ea --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/airdrop/sv_plugin.lua @@ -0,0 +1,329 @@ +local PLUGIN = PLUGIN + +-- Координаты баз для запрета авиаударов +local BASE_RF = { + min = Vector(-12686, 4350, -10000), + max = Vector(-8714, 12126, 10000) +} +local BASE_UK = { + min = Vector(9778, 2972, -10000), + max = Vector(13576, 10852, 10000) +} + +local function IsInBase(pos) + if (pos:WithinAABox(BASE_RF.min, BASE_RF.max)) then return true end + if (pos:WithinAABox(BASE_UK.min, BASE_UK.max)) then return true end + return false +end + +function PLUGIN:SpawnAirdrop(weapon) + local center = Vector(0, 0, 0) + local players = player.GetAll() + if (#players > 0) then + local targetPlayer = players[math.random(#players)] + center = targetPlayer:GetPos() + end + + local angle = math.random(0, 360) + local radian = math.rad(angle) + local altitude = math.random(2000, 2500) + local startPos = center + Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0) + startPos.z = altitude + local endPos = center - Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0) + endPos.z = altitude + + util.PrecacheModel(ix.config.Get("airdropPlaneModel", "models/gunkov2056/su25.mdl")) + local plane = ents.Create("prop_dynamic") + plane:SetModel(ix.config.Get("airdropPlaneModel", "models/gunkov2056/su25.mdl")) + plane:SetPos(startPos) + local angles = (endPos - startPos):Angle() + plane:SetAngles(angles) + plane:SetMoveType(MOVETYPE_FLY) + plane:Spawn() + plane:Activate() + + print("[Airdrop] Спавн самолета: " .. plane:GetModel() .. " на " .. tostring(startPos)) + + local speed = 2500 + local distance = startPos:Distance(endPos) + local duration = distance / speed + local dropFrac = math.Rand(0.45, 0.55) + local dropped = false + local startTime = CurTime() + + plane:EmitSound("su25/su25.wav", 150, 100, 1, CHAN_AUTO) + + timer.Create("AirdropPlaneMove_" .. plane:EntIndex(), 0.05, 0, function() + if (!IsValid(plane)) then return end + local elapsed = CurTime() - startTime + local frac = elapsed / duration + if (frac >= 1) then + plane:StopSound("su25/su25.wav") + plane:Remove() + timer.Remove("AirdropPlaneMove_" .. plane:EntIndex()) + return + end + plane:SetPos(LerpVector(frac, startPos, endPos)) + if (!dropped and frac >= dropFrac) then + dropped = true + self:DropCrate(plane:GetPos(), weapon) + end + end) +end + +function PLUGIN:SpawnAirstrike() + ix.util.Notify("Внимание! Обнаружена воздушная угроза. Авиаудар через 30 секунд!", nil, "all") + + timer.Simple(30, function() + local targetPos = Vector(0, 0, 0) + local players = player.GetAll() + local attempts = 0 + + -- Ищем валидную точку для удара (не в базе) + repeat + if (#players > 0) then + targetPos = players[math.random(#players)]:GetPos() + else + targetPos = Vector(math.random(-10000, 10000), math.random(-10000, 10000), 0) + end + attempts = attempts + 1 + until (!IsInBase(targetPos) or attempts > 10) + + if (IsInBase(targetPos)) then return end -- Отмена если не нашли безопасную точку + + local angle = math.random(0, 360) + local radian = math.rad(angle) + local altitude = math.random(2000, 2500) + local startPos = targetPos + Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0) + startPos.z = altitude + local endPos = targetPos - Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0) + endPos.z = altitude + + util.PrecacheModel(ix.config.Get("airstrikePlaneModel", "models/gunkov2056/su25.mdl")) + local plane = ents.Create("prop_dynamic") + plane:SetModel(ix.config.Get("airstrikePlaneModel", "models/gunkov2056/su25.mdl")) + plane:SetPos(startPos) + plane:SetAngles((endPos - startPos):Angle()) + plane:SetMoveType(MOVETYPE_FLY) + plane:Spawn() + plane:Activate() + + print("[Airstrike] Спавн самолета: " .. plane:GetModel() .. " на " .. tostring(startPos)) + + local speed = 3000 + local duration = startPos:Distance(endPos) / speed + local dropped = false + local startTime = CurTime() + + plane:EmitSound("su25/su25.wav", 150, 100, 1, CHAN_AUTO) + + timer.Create("AirstrikePlaneMove_" .. plane:EntIndex(), 0.05, 0, function() + if (!IsValid(plane)) then return end + local elapsed = CurTime() - startTime + local frac = elapsed / duration + if (frac >= 1) then + plane:StopSound("su25/su25.wav") + plane:Remove() + timer.Remove("AirstrikePlaneMove_" .. plane:EntIndex()) + return + end + plane:SetPos(LerpVector(frac, startPos, endPos)) + if (!dropped and frac >= 0.5) then + dropped = true + self:DropBomb(plane:GetPos(), plane:GetForward() * speed) + end + end) + end) +end + +function PLUGIN:DropBomb(pos, velocity) + local bomb = ents.Create("sw_bomb_fab250m62_v3") + if (!IsValid(bomb)) then + -- Если энтити нет, создадим обычный взрыв для теста + local exp = ents.Create("env_explosion") + exp:SetPos(pos) + exp:SetKeyValue("iMagnitude", "300") + exp:Spawn() + exp:Fire("Explode", 0, 0) + return + end + + bomb:SetPos(pos) + bomb:SetAngles(velocity:Angle()) + bomb:Spawn() + bomb:Activate() + + local phys = bomb:GetPhysicsObject() + if (IsValid(phys)) then + phys:SetVelocity(velocity) + end +end + +function PLUGIN:DropCrate(pos, weapon) + if (!weapon) then + local weaponsConfig = ix.config.Get("airdropWeapons", "") + local weapons = {} + + if (type(weaponsConfig) == "string" and weaponsConfig:Trim() != "") then + for _, v in pairs(string.Split(weaponsConfig, "\n")) do + local s = v:Trim() + if (s != "") then + table.insert(weapons, s) + end + end + end + + -- Если конфиг пустой или старый, берем список по умолчанию + if (#weapons == 0) then + weapons = { + "tacrp_io_degala", + "tacrp_io_fiveseven", + "tacrp_mr96", + "tacrp_pdw", + "tacrp_superv", + "tacrp_sd_aac_hb", + "tacrp_ak_ak12", + "tacrp_ak_an94", + "tacrp_sg551", + "tacrp_io_xm8car", + "tacrp_hk417", + "tacrp_io_scarh", + "tacrp_mg4", + "tacrp_io_xm8lmg", + "tacrp_io_sg550r", + "tacrp_io_sl8", + "tacrp_io_sg550", + "tacrp_io_vss", + "tacrp_as50", + "tacrp_ex_hecate", + "tacrp_civ_m320" + } + end + + weapon = weapons[math.random(#weapons)] + end + + local crate = ents.Create("ix_airdrop") + if (!IsValid(crate)) then + print("[Airdrop] ОШИБКА: Не удалось создать энтити ix_airdrop!") + return + end + crate:SetPos(pos) + crate:SetAngles(Angle(0, math.random(0, 360), 0)) + crate:Spawn() + crate.ixWeapon = weapon + + -- Создаем парашют + util.PrecacheModel("models/jessev92/bf2/parachute.mdl") + local parachute = ents.Create("prop_dynamic") + parachute:SetModel("models/jessev92/bf2/parachute.mdl") + parachute:SetPos(crate:GetPos() + Vector(0, 0, 50)) + parachute:SetParent(crate) + parachute:Spawn() + crate.ixParachute = parachute + + local phys = crate:GetPhysicsObject() + if (IsValid(phys)) then + phys:Wake() + phys:SetVelocity(Vector(0, 0, -200)) -- Скорость падения + phys:SetDragCoefficient(5) + phys:SetAngleDragCoefficient(100) + end + + local function StartSmoke() + if (!IsValid(crate)) then return end + local smoke = ents.Create("env_smokestack") + smoke:SetPos(crate:GetPos()) + smoke:SetAngles(Angle(-90, 0, 0)) + smoke:SetKeyValue("InitialState", "1") + smoke:SetKeyValue("rendercolor", "255 100 0") + smoke:SetKeyValue("renderamt", "255") + smoke:SetKeyValue("SmokeMaterial", "particle/smokesprites_0001.vmt") + smoke:SetKeyValue("BaseSpread", "20") + smoke:SetKeyValue("SpreadSpeed", "10") + smoke:SetKeyValue("Speed", "80") + smoke:SetKeyValue("StartSize", "30") + smoke:SetKeyValue("EndSize", "120") + smoke:SetKeyValue("Rate", "40") + smoke:SetKeyValue("JetLength", "300") + smoke:Spawn() + smoke:Activate() + smoke:SetParent(crate) + crate.ixSmoke = smoke + ix.util.Notify("Аирдроп приземлился! Ищите оранжевый дым.", nil, "all") + end + + -- Принудительно держим ящик ровно во время падения + timer.Create("AirdropUpright_" .. crate:EntIndex(), 0.1, 0, function() + if (!IsValid(crate)) then + timer.Remove("AirdropUpright_" .. crate:EntIndex()) + return + end + + local phys = crate:GetPhysicsObject() + if (IsValid(phys) and phys:IsMotionEnabled()) then + crate:SetAngles(Angle(0, crate:GetAngles().y, 0)) + phys:SetAngleVelocity(Vector(0, 0, 0)) + else + timer.Remove("AirdropUpright_" .. crate:EntIndex()) + end + end) + + timer.Create("AirdropLand_" .. crate:EntIndex(), 0.2, 0, function() + if (!IsValid(crate)) then + timer.Remove("AirdropLand_" .. crate:EntIndex()) + return + end + local phys = crate:GetPhysicsObject() + if (!IsValid(phys)) then return end + + -- Если скорость мала или мы коснулись земли + local tr = util.TraceLine({ + start = crate:GetPos() + Vector(0, 0, 10), + endpos = crate:GetPos() - Vector(0, 0, 40), + filter = crate + }) + + if (tr.Hit or phys:GetVelocity():Length() < 5) then + phys:EnableMotion(false) + crate:SetPos(tr.HitPos) + crate:SetAngles(Angle(0, crate:GetAngles().y, 0)) + + if (IsValid(crate.ixParachute)) then + crate.ixParachute:Remove() + end + + StartSmoke() + timer.Remove("AirdropLand_" .. crate:EntIndex()) + timer.Remove("AirdropUpright_" .. crate:EntIndex()) + end + end) + + timer.Simple(900, function() + if (IsValid(crate)) then + if (IsValid(crate.ixSmoke)) then crate.ixSmoke:Remove() end + crate:Remove() + end + end) +end + +function PLUGIN:Initialize() + self:SetupAirdropTimer() + self:SetupAirstrikeTimer() +end + +function PLUGIN:SetupAirdropTimer() + local delay = math.random(ix.config.Get("airdropMinInterval", 3600), ix.config.Get("airdropMaxInterval", 7200)) + timer.Create("ixAirdropTimer", delay, 1, function() + self:SpawnAirdrop() + self:SetupAirdropTimer() + end) +end + +function PLUGIN:SetupAirstrikeTimer() + local delay = math.random(ix.config.Get("airstrikeMinInterval", 1800), ix.config.Get("airstrikeMaxInterval", 3600)) + timer.Create("ixAirstrikeTimer", delay, 1, function() + self:SpawnAirstrike() + self:SetupAirstrikeTimer() + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/animation_menu/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/animation_menu/cl_plugin.lua new file mode 100644 index 0000000..e84853b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/animation_menu/cl_plugin.lua @@ -0,0 +1,553 @@ +-- Цветовая схема (в стиле остальных интерфейсов) +local PLUGIN = PLUGIN + +local COLOR_BG_DARK = Color(3, 5, 4) +local COLOR_BG_MEDIUM = Color(8, 12, 10) +local COLOR_BG_LIGHT = Color(12, 18, 14) +local COLOR_PRIMARY = Color(27, 94, 32) +local COLOR_PRIMARY_HOVER = Color(33, 110, 38) +local COLOR_ACCENT = Color(56, 102, 35) +local COLOR_ACCENT_HOVER = Color(66, 120, 45) +local COLOR_BORDER = Color(46, 125, 50, 80) +local COLOR_TEXT_PRIMARY = Color(165, 214, 167) +local COLOR_TEXT_SECONDARY = Color(129, 199, 132) +local COLOR_TEXT_DIM = Color(100, 150, 105) + +-- Хук для камеры во время анимации +function PLUGIN:CalcView(ply, pos, angles, fov) + local str = ply:GetNW2String('TauntAnim') + if str != "" then + if GetConVar("rp_taunt_firstperson"):GetBool() then + local eye_id = ply:LookupAttachment('eyes') + local att = eye_id > 0 and ply:GetAttachment(eye_id) or nil + + if not att then return end + + local ang1 = angles + if GetConVar("rp_taunt_realistic_firstperson"):GetBool() then + ang1 = att.Ang + end + local view = { + origin = att.Pos, + angles = ang1, + fov = fov, + drawviewer = true + } + return view + else + local view = { + origin = pos - (angles:Forward() * 100), + angles = angles, + fov = fov, + drawviewer = true + } + return view + end + end +end + +function PLUGIN:ShouldDrawLocalPlayer(ply) + local str = ply:GetNW2String('TauntAnim') + if str != "" and not GetConVar("rp_taunt_firstperson"):GetBool() then + return true + end +end + +-- Подсказка на экране +function PLUGIN:HUDPaint() + local ply = LocalPlayer() + local str = ply:GetNW2String('TauntAnim') + if str != "" then + draw.SimpleTextOutlined("Нажмите SHIFT, чтобы убрать анимацию.", "Trebuchet18", ScrW()/2, ScrH()-250, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, color_black) + end +end + +-- Таблица анимаций (импортируем из существующего аддона) +local AnimationCategories = { + { + name = "ЖЕСТЫ", + anims = { + {id = 1, name = "Помахать руками"}, + {id = 2, name = "Помахать рукой"}, + {id = 3, name = "Показать пальцем вперед"}, + {id = 4, name = "Показать пальцем назад"}, + {id = 5, name = "Поправить галстук"}, + } + }, + { + name = "СПОРТ", + anims = { + {id = 6, name = "Приседания"}, + {id = 7, name = "Отжимания"}, + {id = 8, name = "Подъем корпуса"}, + {id = 9, name = "Берпи"}, + } + }, + { + name = "ПОЗЫ", + anims = { + {id = 10, name = "Стоять злобно"}, + {id = 11, name = "Стоять напуганно"}, + {id = 12, name = "Стоять, сложив руки"}, + {id = 13, name = "Стоять, руки на поясе"}, + {id = 14, name = "Стоять, держась за пояс"}, + {id = 15, name = "Сидеть, рука на колене"}, + {id = 16, name = "Сидеть в позе лотоса"}, + {id = 17, name = "Сидеть в сонном состоянии"}, + {id = 18, name = "Лежать в плохом состоянии"}, + } + }, + { + name = "ТАНЦЫ", + anims = { + {id = 19, name = "Танец 1"}, + {id = 20, name = "Танец 2"}, + {id = 21, name = "Танец 3"}, + {id = 22, name = "Танец 4"}, + {id = 33, name = "Танец в присядь"}, + } + }, + { + name = "ДЕЙСТВИЯ", + anims = { + {id = 23, name = "Согласие"}, + {id = 24, name = "Несогласие"}, + {id = 25, name = "Позвать с собой"}, + {id = 26, name = "Поклониться"}, + {id = 27, name = "Отдать честь"}, + {id = 28, name = "Пить кофе"}, + {id = 29, name = "Смотреть на объект"}, + {id = 30, name = "Записывать в блокнот"}, + {id = 31, name = "Спать"}, + {id = 32, name = "Воинское приветствие"}, + } + } +} + +local animMenu = nil +local animMenuOpen = false + +-- Функция создания меню анимаций +local function CreateAnimationMenu() + if IsValid(animMenu) then + animMenu:Remove() + end + + -- Невидимая панель на весь экран для перехвата ввода + local basePanel = vgui.Create("DPanel") + basePanel:SetSize(ScrW(), ScrH()) + basePanel:SetPos(0, 0) + basePanel:MakePopup() + basePanel.Paint = function() end -- Полностью прозрачная + + -- Меню в правом верхнем углу + local menuWidth = 320 + local menuHeight = 500 + local padding = 20 + + local menu = vgui.Create("DPanel", basePanel) + menu:SetSize(menuWidth, menuHeight) + menu:SetPos(ScrW() - menuWidth - padding, padding) + menu.Paint = function(s, w, h) + -- Основной фон + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK) + + -- Заголовок + draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawLine(0, 45, w, 45) + + -- Текст заголовка + draw.SimpleText("АНИМАЦИИ", "DermaLarge", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Декоративная линия + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(10, 43, w-20, 2) + + -- Внешняя граница + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + -- Подсказка внизу + draw.SimpleText("C - Закрыть | SHIFT - Остановить анимацию", "DermaDefault", w/2, h-15, COLOR_TEXT_DIM, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Кнопка закрытия + local closeBtn = vgui.Create("DButton", menu) + closeBtn:SetPos(menuWidth - 35, 8) + closeBtn:SetSize(28, 28) + closeBtn:SetText("") + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_ACCENT_HOVER or COLOR_ACCENT + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("×", "DermaLarge", w/2, h/2-2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + closeBtn.DoClick = function() + basePanel:Remove() + animMenuOpen = false + end + + -- Скролл панель для категорий + local scroll = vgui.Create("DScrollPanel", menu) + scroll:SetPos(10, 55) + scroll:SetSize(menuWidth - 20, menuHeight - 90) + + local sbar = scroll:GetVBar() + sbar:SetWide(6) + sbar.Paint = function(s, w, h) + draw.RoundedBox(3, 0, 0, w, h, COLOR_BG_MEDIUM) + end + sbar.btnGrip.Paint = function(s, w, h) + draw.RoundedBox(3, 0, 0, w, h, COLOR_ACCENT) + end + sbar.btnUp.Paint = function() end + sbar.btnDown.Paint = function() end + + -- Создаем категории с анимациями + for _, category in ipairs(AnimationCategories) do + -- Заголовок категории + local catHeader = vgui.Create("DPanel", scroll) + catHeader:Dock(TOP) + catHeader:DockMargin(0, 5, 0, 3) + catHeader:SetTall(28) + catHeader.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText(category.name, "DermaDefaultBold", 10, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + -- Анимации в категории + for _, anim in ipairs(category.anims) do + local animBtn = vgui.Create("DButton", scroll) + animBtn:Dock(TOP) + animBtn:DockMargin(3, 2, 3, 0) + animBtn:SetTall(32) + animBtn:SetText("") + + animBtn.Paint = function(s, w, h) + local col = COLOR_BG_MEDIUM + if s:IsHovered() then + col = COLOR_PRIMARY_HOVER + -- Акцентная линия слева + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(0, 0, 3, h) + end + + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + -- Иконка + draw.SimpleText("▶", "DermaDefault", 12, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Название анимации + draw.SimpleText(anim.name, "DermaDefault", 28, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + animBtn.DoClick = function() + RunConsoleCommand("rp_set_taunt", tostring(anim.id)) + surface.PlaySound("buttons/button15.wav") + basePanel:Remove() + animMenuOpen = false + end + end + end + + -- Обработка закрытия по нажатию вне меню + basePanel.OnMousePressed = function(s, keyCode) + if keyCode == MOUSE_LEFT then + -- Проверяем, кликнули ли вне меню + local mx, my = input.GetCursorPos() + local x, y = menu:GetPos() + local w, h = menu:GetSize() + + if mx < x or mx > x + w or my < y or my > y + h then + basePanel:Remove() + animMenuOpen = false + end + end + end + + -- Меню рации в левом верхнем углу + local radioWidth = 320 + local radioHeight = 200 + + local radioMenu = vgui.Create("DPanel", basePanel) + radioMenu:SetSize(radioWidth, radioHeight) + radioMenu:SetPos(padding, padding) + radioMenu.Paint = function(s, w, h) + -- Основной фон + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK) + + -- Заголовок + draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawLine(0, 45, w, 45) + + -- Текст заголовка + draw.SimpleText("ЧАСТОТА РАЦИИ", "DermaLarge", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Декоративная линия + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(10, 43, w-20, 2) + + -- Внешняя граница + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Получаем плагин рации + local radioPlugin = ix.plugin.list["radio"] + if not radioPlugin then + radioMenu:SetVisible(false) + else + local currentFreq = radioPlugin:GetFrequency(LocalPlayer()) + + -- Текущая частота + local freqLabel = vgui.Create("DLabel", radioMenu) + freqLabel:SetPos(20, 60) + freqLabel:SetSize(radioWidth - 40, 25) + freqLabel:SetText("Текущая частота: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", currentFreq)) + freqLabel:SetFont("DermaDefault") + freqLabel:SetTextColor(COLOR_TEXT_PRIMARY) + + -- Слайдер частоты + local freqSlider = vgui.Create("DNumSlider", radioMenu) + freqSlider:SetPos(10, 90) + freqSlider:SetSize(radioWidth - 20, 40) + freqSlider:SetText("") + freqSlider:SetMin(radioPlugin.minFrequency) + freqSlider:SetMax(radioPlugin.maxFrequency) + freqSlider:SetDecimals(radioPlugin.frequencyPrecision) + freqSlider:SetValue(currentFreq) + + freqSlider.Label:SetTextColor(COLOR_TEXT_SECONDARY) + freqSlider.Label:SetFont("DermaDefault") + freqSlider.TextArea:SetTextColor(COLOR_TEXT_PRIMARY) + freqSlider.TextArea:SetFont("DermaDefault") + freqSlider.TextArea:SetEditable(true) + freqSlider.TextArea:SetNumeric(true) + freqSlider.TextArea.Paint = function(panel, w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + panel:DrawTextEntryText(COLOR_TEXT_PRIMARY, COLOR_ACCENT, COLOR_TEXT_PRIMARY) + end + + -- Обработчик ввода вручную + freqSlider.TextArea.OnEnter = function(panel) + local val = tonumber(panel:GetValue()) + if val then + val = math.Clamp(val, radioPlugin.minFrequency, radioPlugin.maxFrequency) + freqSlider:SetValue(val) + end + end + + freqSlider.Slider.Paint = function(panel, w, h) + draw.RoundedBox(4, 0, h/2-2, w, 4, COLOR_BG_MEDIUM) + local progress = (freqSlider:GetValue() - freqSlider:GetMin()) / (freqSlider:GetMax() - freqSlider:GetMin()) + draw.RoundedBox(4, 0, h/2-2, w * progress, 4, COLOR_ACCENT) + end + + freqSlider.Slider.Knob.Paint = function(s, w, h) + draw.RoundedBox(w/2, 0, 0, w, h, COLOR_PRIMARY) + if s:IsHovered() then + draw.RoundedBox(w/2, 2, 2, w-4, h-4, COLOR_ACCENT_HOVER) + end + end + + freqSlider.OnValueChanged = function(_, value) + local format = "%0." .. radioPlugin.frequencyPrecision .. "f" + freqLabel:SetText("Текущая частота: " .. string.format(format, value)) + end + + -- Кнопка применения + local btnWidth = (radioWidth - 50) / 2 + local applyBtn = vgui.Create("DButton", radioMenu) + applyBtn:SetPos(20, 150) + applyBtn:SetSize(btnWidth, 35) + applyBtn:SetText("") + applyBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_PRIMARY_HOVER or COLOR_PRIMARY + draw.RoundedBox(6, 0, 0, w, h, col) + + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + draw.SimpleText("ПРИМЕНИТЬ", "DermaDefaultBold", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + applyBtn.DoClick = function() + net.Start("ixRadioSetFrequency") + net.WriteFloat(freqSlider:GetValue()) + net.SendToServer() + + LocalPlayer():Notify("Частота рации изменена на: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", freqSlider:GetValue())) + end + + -- Кнопка ручного ввода + local manualBtn = vgui.Create("DButton", radioMenu) + manualBtn:SetPos(30 + btnWidth, 150) + manualBtn:SetSize(btnWidth, 35) + manualBtn:SetText("") + manualBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_ACCENT_HOVER or COLOR_ACCENT + draw.RoundedBox(6, 0, 0, w, h, col) + + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + draw.SimpleText("ВВЕСТИ", "DermaDefaultBold", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + manualBtn.DoClick = function() + Derma_StringRequest( + "Ввод частоты", + "Введите частоту рации (от " .. radioPlugin.minFrequency .. " до " .. radioPlugin.maxFrequency .. "):", + tostring(currentFreq), + function(text) + local freq = tonumber(text) + if freq then + freq = math.Clamp(freq, radioPlugin.minFrequency, radioPlugin.maxFrequency) + + net.Start("ixRadioSetFrequency") + net.WriteFloat(freq) + net.SendToServer() + + freqSlider:SetValue(freq) + LocalPlayer():Notify("Частота рации изменена на: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", freq)) + else + LocalPlayer():Notify("Неверный формат частоты!") + end + end, + function() end + ) + end + end + + -- Кнопки включения/выключения рации (справа от меню рации) + if radioPlugin then + local controlsWidth = 150 + local controlsHeight = 200 + + local controlsPanel = vgui.Create("DPanel", basePanel) + controlsPanel:SetSize(controlsWidth, controlsHeight) + controlsPanel:SetPos(padding + radioWidth + 10, padding) + controlsPanel.Paint = function(s, w, h) + -- Основной фон + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK) + + -- Заголовок + draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawLine(0, 45, w, 45) + + -- Текст заголовка + draw.SimpleText("РАЦИЯ", "DermaDefault", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Декоративная линия + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(10, 43, w-20, 2) + + -- Внешняя граница + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Кнопка включения прослушивания + local listenBtn = vgui.Create("DButton", controlsPanel) + listenBtn:SetPos(10, 60) + listenBtn:SetSize(controlsWidth - 20, 40) + listenBtn:SetText("") + listenBtn.Paint = function(s, w, h) + local isListening = radioPlugin:IsListening(LocalPlayer()) + local col = isListening and COLOR_ACCENT or COLOR_BG_MEDIUM + if s:IsHovered() then + col = isListening and COLOR_ACCENT_HOVER or COLOR_PRIMARY_HOVER + end + + draw.RoundedBox(6, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + local status = isListening and "ВКЛ" or "ВЫКЛ" + draw.SimpleText("Прослушивание", "DermaDefault", w/2, h/2 - 7, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(status, "DermaDefaultBold", w/2, h/2 + 7, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + listenBtn.DoClick = function() + net.Start("ixRadioToggleListen") + net.SendToServer() + + timer.Simple(0.1, function() + local status = radioPlugin:IsListening(LocalPlayer()) and "включено" or "выключено" + LocalPlayer():Notify("Прослушивание рации " .. status) + end) + end + + -- Кнопка включения передачи + local transmitBtn = vgui.Create("DButton", controlsPanel) + transmitBtn:SetPos(10, 110) + transmitBtn:SetSize(controlsWidth - 20, 40) + transmitBtn:SetText("") + transmitBtn.Paint = function(s, w, h) + local isTransmitting = radioPlugin:IsTransmitting(LocalPlayer()) + local col = isTransmitting and COLOR_ACCENT or COLOR_BG_MEDIUM + if s:IsHovered() then + col = isTransmitting and COLOR_ACCENT_HOVER or COLOR_PRIMARY_HOVER + end + + draw.RoundedBox(6, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + local status = isTransmitting and "ВКЛ" or "ВЫКЛ" + draw.SimpleText("Передача", "DermaDefault", w/2, h/2 - 7, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(status, "DermaDefaultBold", w/2, h/2 + 7, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + transmitBtn.DoClick = function() + net.Start("ixRadioToggleTransmit") + net.SendToServer() + + timer.Simple(0.1, function() + local status = radioPlugin:IsTransmitting(LocalPlayer()) and "включена" or "выключена" + LocalPlayer():Notify("Передача рации " .. status) + end) + end + end + + animMenu = basePanel + animMenuOpen = true +end + +-- Блокировка стандартного Context Menu и открытие нашего +hook.Add("OnContextMenuOpen", "AnimationMenuOverride", function() + -- Проверка на Alt+C для администраторов (открывает стандартное меню) + if input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then + local client = LocalPlayer() + if IsValid(client) and client:GetCharacter() then + -- Проверка на права администратора + if client:IsAdmin() or client:IsSuperAdmin() then + return -- Пропускаем и открываем стандартное меню + end + end + end + + if not animMenuOpen then + CreateAnimationMenu() + end + return false -- Блокируем стандартное меню +end) + +-- Закрытие меню при закрытии Context Menu +hook.Add("OnContextMenuClose", "AnimationMenuClose", function() + if animMenuOpen and IsValid(animMenu) then + animMenu:Remove() + animMenuOpen = false + end +end) + +-- Подсказка на HUD +--hook.Add("HUDPaint", "AnimationMenuHint", function() +-- if not animMenuOpen then +-- draw.SimpleTextOutlined("C - Открыть меню (анимации + рация)", "DermaDefault", ScrW() - 10, ScrH() - 30, COLOR_TEXT_DIM, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER, 1, Color(0, 0, 0, 200)) +-- end +--end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/animation_menu/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/animation_menu/sh_plugin.lua new file mode 100644 index 0000000..c729606 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/animation_menu/sh_plugin.lua @@ -0,0 +1,192 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Animation Context Menu" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "Замена стандартного Context Menu на меню выбора анимаций" + +PLUGIN.AnimationTable = { + [1] = {name = "Помахать руками", anim = "rp_wave", loop = false}, + [2] = {name = "Помахать рукой", anim = "gesture_wave_original", loop = false}, + [3] = {name = "Показать пальцем вперед", anim = "rp_point", loop = false}, + [4] = {name = "Показать пальцем назад", anim = "rp_point_back", loop = false}, + [5] = {name = "Поправить галстук", anim = "menu_gman", loop = false}, + [6] = {name = "Приседания", anim = "rp_sport1_loop", loop = true}, + [7] = {name = "Отжимания", anim = "rp_sport2_loop", loop = true}, + [8] = {name = "Подъем корпуса", anim = "rp_sport3_loop", loop = true}, + [9] = {name = "Берпи", anim = "rp_sport4_loop", loop = true}, + [10] = {name = "Стоять злобно", anim = "rp_angry_loop", loop = true}, + [11] = {name = "Стоять напуганно", anim = "idle_all_scared", loop = true}, + [12] = {name = "Стоять, сложив руки", anim = "pose_standing_01", loop = true}, + [13] = {name = "Стоять, руки на поясе", anim = "pose_standing_02", loop = true}, + [14] = {name = "Стоять, держась за пояс", anim = "rp_cop_idle", loop = true}, + [15] = {name = "Сидеть, рука на колене", anim = "pose_ducking_01", loop = true}, + [16] = {name = "Сидеть в позе лотоса", anim = "pose_ducking_02", loop = true}, + [17] = {name = "Сидеть в сонном состоянии", anim = "rp_sit_loop", loop = true}, + [18] = {name = "Лежать в плохом состоянии", anim = "rp_injured_loop", loop = true}, + [19] = {name = "Танец 1", anim = "rp_dance1_loop", loop = true}, + [20] = {name = "Танец 2", anim = "rp_dance2_loop", loop = true}, + [21] = {name = "Танец 3", anim = "rp_dance3_loop", loop = true}, + [23] = {name = "Согласие", anim = "gesture_agree_original", loop = false}, + [24] = {name = "Несогласие", anim = "gesture_disagree_original", loop = false}, + [25] = {name = "Позвать с собой", anim = "gesture_becon_original", loop = false}, + [26] = {name = "Поклониться", anim = "gesture_bow_original", loop = false}, + [27] = {name = "Отдать честь", anim = "gesture_salute_original", loop = false}, + [28] = {name = "Пить кофе", anim = "rp_drinking", loop = true, prop = {model = "models/props_junk/garbage_coffeemug001a.mdl", spawnfunc = function(mod, ent) + local att = ent:LookupAttachment("anim_attachment_RH") + local lvec = Vector(1,0,1) + + timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function() + if IsValid(mod) then + local tab = ent:GetAttachment(att) + + if (tab) then + mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x) + mod:SetAngles(tab.Ang) + end + + if !IsValid(ent) or !ent:Alive() then + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + mod:Remove() + end + else + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + end + end) + end}}, + [29] = {name = "Смотреть на объект", anim = "rp_medic_idle", loop = true}, + [30] = {name = "Записывать в блокнот", anim = "rp_writing", loop = true, prop = {model = "models/props_lab/clipboard.mdl", spawnfunc = function(mod, ent) + local att = ent:LookupAttachment("anim_attachment_RH") + local lvec = Vector(2,-2,2) + + timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function() + if IsValid(mod) then + local tab = ent:GetAttachment(att) + + if (tab) then + mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x) + mod:SetAngles(tab.Ang+Angle(140,0,-100)) + end + + if !IsValid(ent) or !ent:Alive() then + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + mod:Remove() + end + else + timer.Remove("WhileThatPropExist"..ent:EntIndex()) + end + end) + end}}, + [31] = {name = "Спать", anim = "rp_sleep", loop = true}, + [32] = {name = "Воинское приветствие", anim = "rp_salute1", loop = true, loop_stop = true}, + [33] = {name = "Танец в присядь", anim = "rp_dance_squat", loop = true}, +} + +if SERVER then + concommand.Add("rp_set_taunt", function(ply, cmd, args) + local arg = tonumber(args[1]) + if arg and arg > 0 and arg <= #PLUGIN.AnimationTable then + local tab = PLUGIN.AnimationTable[arg] + PLUGIN:SetTAnimation(ply, tab.anim, not tab.loop, arg) + end + end) + + function PLUGIN:SetTAnimation(ply, anim, autostop, id) + ply:SetNW2String('TauntAnim', anim) + ply:SetNW2Float('TauntID', id) + ply:SetNW2Float('TAnimDelay', select(2, ply:LookupSequence(anim))) + ply:SetNW2Float('TAnimStartTime', CurTime()) + ply:SetCycle(0) + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and anim != "" then + ply.TauntPreviousWeapon = wep:GetClass() + ply:SetActiveWeapon(nil) + elseif anim == "" and isstring(ply.TauntPreviousWeapon) then + ply:SelectWeapon(ply.TauntPreviousWeapon) + ply.TauntPreviousWeapon = nil + end + + if autostop then + local delay = select(2, ply:LookupSequence(anim)) + timer.Create("TauntAGRP"..ply:EntIndex(), delay, 1, function() + if not IsValid(ply) then return end + local anim2 = ply:GetNW2String('TauntAnim') + if anim == anim2 then + PLUGIN:SetTAnimation(ply, "") + end + end) + end + end + + function PLUGIN:PlayerSwitchWeapon(ply, oldWeapon, newWeapon) + local str = ply:GetNW2String('TauntAnim') + if str != "" then + return true + end + end + + function PLUGIN:Think() + for _, ply in ipairs(player.GetAll()) do + local str = ply:GetNW2String('TauntAnim') + if str != "" and (ply:InVehicle() or not ply:Alive() or ply:WaterLevel() >= 2) then + PLUGIN:SetTAnimation(ply, "") + end + end + end +else + CreateConVar("rp_taunt_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1) + CreateConVar("rp_taunt_realistic_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1) +end + +function PLUGIN:SetupMove(ply, mvd, cmd) + local str = ply:GetNW2String('TauntAnim') + if str != "" then + mvd:SetMaxSpeed(1) + mvd:SetMaxClientSpeed(1) + if SERVER and ply:KeyDown(IN_SPEED) then + PLUGIN:SetTAnimation(ply, "") + end + end +end + +function PLUGIN:CalcMainActivity(ply, vel) + local str = ply:GetNW2String('TauntAnim') + local num = ply:GetNW2Float('TAnimDelay') + local id = ply:GetNW2Float('TauntID') + local st = ply:GetNW2Float('TAnimStartTime') + + if str != "" and ply:Alive() then + local ls = PLUGIN.AnimationTable[id] and PLUGIN.AnimationTable[id].loop_stop + + if ply:GetCycle() >= 1 then + if not ls then + ply:SetCycle(0) + if SERVER then + ply:SetNW2Float('TAnimStartTime', CurTime()) + end + end + else + ply:SetCycle((CurTime()-st)/num) + + local tab = PLUGIN.AnimationTable[id] and PLUGIN.AnimationTable[id].prop + if CLIENT and istable(tab) and (not IsValid(ply.TauntProp) or ply.TauntProp:GetModel() != tab.model) then + if IsValid(ply.TauntProp) then + ply.TauntProp:Remove() + end + ply.TauntProp = ClientsideModel(tab.model) + tab.spawnfunc(ply.TauntProp, ply) + elseif CLIENT and not istable(tab) and IsValid(ply.TauntProp) then + ply.TauntProp:Remove() + end + end + + return -1, ply:LookupSequence(str) + else + if SERVER then + ply:SetNW2Float('TauntID', 0) + elseif CLIENT and IsValid(ply.TauntProp) then + ply.TauntProp:Remove() + end + end +end + +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/arsenal/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/arsenal/cl_plugin.lua new file mode 100644 index 0000000..a10528a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/arsenal/cl_plugin.lua @@ -0,0 +1,706 @@ +local PLUGIN = PLUGIN + +local COLOR_BG_DARK = Color(3, 5, 4) +local COLOR_BG_MEDIUM = Color(8, 12, 10) +local COLOR_BG_LIGHT = Color(12, 18, 14) +local COLOR_PRIMARY = Color(27, 94, 32) +local COLOR_PRIMARY_DARK = Color(15, 60, 18) +local COLOR_ACCENT = Color(56, 102, 35) +local COLOR_TEXT_PRIMARY = Color(165, 214, 167) +local COLOR_TEXT_SECONDARY = Color(102, 187, 106) +local COLOR_DANGER = Color(136, 14, 14) +local COLOR_WARNING = Color(191, 130, 0) +local COLOR_BORDER = Color(15, 60, 18, 60) + +if not draw.Circle then + function draw.Circle(x, y, radius, color) + local segmentCount = math.max(16, radius) + surface.SetDrawColor(color or color_white) + + local circle = {} + for i = 0, segmentCount do + local angle = math.rad((i / segmentCount) * 360) + table.insert(circle, { + x = x + math.cos(angle) * radius, + y = y + math.sin(angle) * radius + }) + end + surface.DrawPoly(circle) + end +end + +if not surface.DrawCircle then + function surface.DrawCircle(x, y, radius, color) + draw.Circle(x, y, radius, color) + end +end +local COLOR_TEXT_SECONDARY = Color(174, 213, 129) +local COLOR_DANGER = Color(229, 57, 53) +local COLOR_WARNING = Color(255, 193, 7) +local COLOR_BORDER = Color(46, 125, 50, 100) + +net.Receive("ixArsenalOpen", function() + local supply = net.ReadUInt(32) + local factionID = net.ReadUInt(8) + local weaponsData = net.ReadTable() + local weaponHas = net.ReadTable() + local armorData = net.ReadTable() + local freeRemain = net.ReadUInt(32) + + if not weaponsData or type(weaponsData) ~= "table" then weaponsData = {} end + if not weaponHas or type(weaponHas) ~= "table" then weaponHas = {} end + + if IsValid(PLUGIN.menu) then + PLUGIN.menu:Remove() + end + + local scrW, scrH = ScrW(), ScrH() + local frame = vgui.Create("DFrame") + frame:SetSize(scrW, scrH) + frame:SetPos(0, 0) + frame:SetTitle("") + frame:SetDraggable(false) + frame:ShowCloseButton(false) + frame:MakePopup() + + frame.Paint = function(s, w, h) + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawRect(0, 0, w, h) + + local gradHeight = 300 + for i = 0, gradHeight do + local alpha = (1 - i/gradHeight) * 40 + surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha) + surface.DrawRect(0, i, w, 1) + end + + surface.SetDrawColor(COLOR_BG_MEDIUM) + surface.DrawRect(0, 0, w, 100) + + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawRect(0, 100, w, 3) + + surface.SetDrawColor(COLOR_BORDER) + for i = 0, w, 200 do + surface.DrawRect(i, 0, 1, 100) + end + + draw.SimpleText("◆ ВОЕННЫЙ АРСЕНАЛ ◆", "ixMenuButtonFont", w/2, 35, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("СИСТЕМА СНАБЖЕНИЯ", "ixSmallFont", w/2, 68, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetText("") + closeBtn:SetSize(42, 42) + closeBtn:SetPos(scrW - 60, 18) + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7) + + draw.RoundedBox(4, 0, 0, w, h, col) + + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + surface.SetDrawColor(COLOR_TEXT_PRIMARY) + surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7) + surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30)) + end + end + closeBtn.DoClick = function() frame:Close() end + + local infoPanel = vgui.Create("DPanel", frame) + infoPanel:SetSize(scrW - 80, 70) + infoPanel:SetPos(40, 115) + infoPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local supplyBox = vgui.Create("DPanel", infoPanel) + supplyBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall()) + supplyBox:SetPos(5, 0) + supplyBox.Paint = function(s, w, h) + draw.SimpleText("ОЧКИ СНАБЖЕНИЯ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(tostring(supply), "ixMenuButtonFont", 20, 32, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + surface.SetDrawColor(COLOR_PRIMARY) + draw.NoTexture() + surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY) + end + + local currencySymbol = "$" + if PLUGIN and PLUGIN.config and PLUGIN.config.currencySymbol and factionID then + currencySymbol = PLUGIN.config.currencySymbol[factionID] or currencySymbol + end + + local function formatTime(seconds) + seconds = math.max(0, tonumber(seconds) or 0) + if seconds <= 0 then return "Готово" end + local h = math.floor(seconds / 3600) + local m = math.floor((seconds % 3600) / 60) + local s = seconds % 60 + if h > 0 then + return string.format("%02d:%02d:%02d", h, m, s) + else + return string.format("%02d:%02d", m, s) + end + end + + local cooldownBox = vgui.Create("DPanel", infoPanel) + cooldownBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall()) + cooldownBox:SetPos(infoPanel:GetWide() / 2 + 5, 0) + cooldownBox.Paint = function(s, w, h) + draw.SimpleText("БЕСПЛАТНОЕ СНАРЯЖЕНИЕ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local timeText = formatTime(freeRemain) + local timeColor = freeRemain <= 0 and COLOR_PRIMARY or COLOR_WARNING + draw.SimpleText(timeText, "ixMenuButtonFont", 20, 32, timeColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + if freeRemain > 0 then + surface.SetDrawColor(COLOR_WARNING) + draw.NoTexture() + surface.DrawCircle(w - 35, h/2, 12, COLOR_WARNING) + else + surface.SetDrawColor(COLOR_PRIMARY) + draw.NoTexture() + surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY) + end + end + + local colWidth = (scrW - 120) / 2 + local startY = 205 + + -- Панель оружия + local weaponsPanel = vgui.Create("DPanel", frame) + weaponsPanel:SetSize(colWidth, scrH - startY - 40) + weaponsPanel:SetPos(40, startY) + weaponsPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local weaponsHeader = vgui.Create("DPanel", weaponsPanel) + weaponsHeader:SetSize(weaponsPanel:GetWide(), 45) + weaponsHeader:SetPos(0, 0) + weaponsHeader.Paint = function(s, w, h) + draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false) + + draw.SimpleText("▣ ОРУЖИЕ", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Декоративная линия + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(0, h-2, w, 2) + end + + local weaponsScroll = vgui.Create("DScrollPanel", weaponsPanel) + weaponsScroll:SetSize(weaponsPanel:GetWide() - 20, weaponsPanel:GetTall() - 60) + weaponsScroll:SetPos(10, 50) + + local sbar = weaponsScroll:GetVBar() + sbar:SetHideButtons(true) + function sbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK) + end + function sbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY) + end + + -- Функция для создания окна выбора оружия из категории + local function OpenCategoryWeapons(categoryName, categoryIcon, categoryWeapons) + local catFrame = vgui.Create("DFrame") + catFrame:SetSize(800, 600) + catFrame:Center() + catFrame:SetTitle("") + catFrame:SetDraggable(true) + catFrame:ShowCloseButton(false) + catFrame:MakePopup() + + catFrame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK) + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawOutlinedRect(0, 0, w, h, 3) + + -- Заголовок + draw.RoundedBoxEx(8, 0, 0, w, 60, COLOR_PRIMARY_DARK, true, true, false, false) + draw.SimpleText(categoryIcon .. " " .. categoryName, "ixMenuButtonFont", w/2, 30, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(0, 60, w, 2) + end + + -- Кнопка закрытия + local catCloseBtn = vgui.Create("DButton", catFrame) + catCloseBtn:SetText("") + catCloseBtn:SetSize(35, 35) + catCloseBtn:SetPos(catFrame:GetWide() - 45, 12) + catCloseBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7) + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_TEXT_PRIMARY) + surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7) + surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7) + end + catCloseBtn.DoClick = function() catFrame:Close() end + + -- Скролл с оружием + local catScroll = vgui.Create("DScrollPanel", catFrame) + catScroll:SetSize(catFrame:GetWide() - 30, catFrame:GetTall() - 85) + catScroll:SetPos(15, 70) + + local catSbar = catScroll:GetVBar() + catSbar:SetHideButtons(true) + function catSbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK) + end + function catSbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY) + end + + -- Создаем карточки оружия + for class, data in SortedPairsByMemberValue(categoryWeapons, "name", true) do + local has = weaponHas[class] + local row = vgui.Create("DPanel", catScroll) + row:SetSize(catScroll:GetWide() - 10, 110) + row:Dock(TOP) + row:DockMargin(0, 0, 0, 10) + + local isHovered = false + row.Paint = function(s, w, h) + local bgColor = has and Color(45, 25, 20) or COLOR_BG_LIGHT + if isHovered then + bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10) + end + draw.RoundedBox(6, 0, 0, w, h, bgColor) + + local statusColor = has and Color(205, 127, 50) or COLOR_PRIMARY + draw.RoundedBoxEx(6, 0, 0, 5, h, statusColor, true, false, true, false) + + surface.SetDrawColor(has and Color(205, 127, 50, 80) or COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + if isHovered then + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(5, h-3, w-10, 3) + end + end + + row.OnCursorEntered = function() isHovered = true end + row.OnCursorExited = function() isHovered = false end + + -- Название + local nameLabel = vgui.Create("DLabel", row) + nameLabel:SetText(data.name or class) + nameLabel:SetFont("ixSmallFont") + nameLabel:SetTextColor(COLOR_TEXT_PRIMARY) + nameLabel:SetPos(15, 10) + nameLabel:SizeToContents() + + -- Цена + local money = data.moneyPrice or 0 + local supply = data.supplyPrice or 0 + local priceLabel = vgui.Create("DLabel", row) + local priceText = "" + if (money or 0) <= 0 and (supply or 0) <= 0 then + priceText = "★ БЕСПЛАТНО" + else + local parts = {} + if money > 0 then table.insert(parts, currencySymbol .. tostring(money)) end + if supply > 0 then table.insert(parts, tostring(supply) .. " ОС") end + priceText = table.concat(parts, " • ") + end + priceLabel:SetText(priceText) + priceLabel:SetFont("ixSmallFont") + priceLabel:SetTextColor(money <= 0 and supply <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY) + priceLabel:SetPos(15, 40) + priceLabel:SizeToContents() + + -- Донат метка + if data.donate then + local donateLabel = vgui.Create("DLabel", row) + donateLabel:SetText("★ ПРЕМИУМ") + donateLabel:SetFont("ixSmallFont") + donateLabel:SetTextColor(COLOR_WARNING) + donateLabel:SetPos(15, 70) + donateLabel:SizeToContents() + end + + -- Кнопка действия + local btn = vgui.Create("DButton", row) + btn:SetSize(120, 40) + btn:SetPos(row:GetWide() - 130, 35) + btn:SetText("") + + local btnText = has and "◄ ВЕРНУТЬ" or "ВЗЯТЬ ►" + local btnColor = has and Color(165, 85, 60) or COLOR_PRIMARY + local btnColorHover = has and Color(205, 105, 70) or COLOR_ACCENT + + btn.Paint = function(s, w, h) + local col = s:IsHovered() and btnColorHover or btnColor + draw.RoundedBox(6, 0, 0, w, h, col) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30)) + end + + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + draw.SimpleText(btnText, "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + btn.DoClick = function() + if has then + Derma_Query("Вернуть снаряжение? Фракция получит 80% стоимости.", "Возврат", "Подтвердить", function() + net.Start("ixArsenalAction") + net.WriteString("return_weapon") + net.WriteString(class) + net.SendToServer() + catFrame:Close() + frame:Close() + end, "Отмена", function() end) + else + local options = {} + if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end + if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end + + if #options == 0 then + Derma_Message("Невозможно приобрести это оружие", "Ошибка", "OK") + return + end + + if #options == 1 then + net.Start("ixArsenalAction") + net.WriteString("buy_weapon") + net.WriteString(class) + net.WriteString(options[1].method) + net.SendToServer() + catFrame:Close() + frame:Close() + else + local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function() + net.Start("ixArsenalAction") + net.WriteString("buy_weapon") + net.WriteString(class) + net.WriteString(options[1].method) + net.SendToServer() + catFrame:Close() + frame:Close() + end, options[2].text, function() + net.Start("ixArsenalAction") + net.WriteString("buy_weapon") + net.WriteString(class) + net.WriteString(options[2].method) + net.SendToServer() + catFrame:Close() + frame:Close() + end, "Отмена", function() end) + + if IsValid(query) then + query:ShowCloseButton(true) + end + end + end + end + end + end + + -- Панель брони + local armorPanel = vgui.Create("DPanel", frame) + armorPanel:SetSize(colWidth, scrH - startY - 40) + armorPanel:SetPos(scrW - colWidth - 40, startY) + armorPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local armorHeader = vgui.Create("DPanel", armorPanel) + armorHeader:SetSize(armorPanel:GetWide(), 45) + armorHeader:SetPos(0, 0) + armorHeader.Paint = function(s, w, h) + draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false) + + draw.SimpleText("⬢ ЗАЩИТА", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Декоративная линия + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(0, h-2, w, 2) + end + + local armorScroll = vgui.Create("DScrollPanel", armorPanel) + armorScroll:SetSize(armorPanel:GetWide() - 20, armorPanel:GetTall() - 60) + armorScroll:SetPos(10, 50) + + local sbar2 = armorScroll:GetVBar() + sbar2:SetHideButtons(true) + function sbar2:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK) + end + function sbar2.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY) + end + + -- Группировка оружия по категориям + local categories = { + {name = "Основное оружие", icon = "▣", key = "primary", weapons = {}}, + {name = "Пистолеты", icon = "▢", key = "secondary", weapons = {}}, + {name = "Гранаты", icon = "◆", key = "grenades", weapons = {}}, + {name = "Холодное оружие", icon = "▸", key = "melee", weapons = {}}, + {name = "Прочее", icon = "◇", key = "other", weapons = {}} + } + + -- Распределяем оружие по категориям + for class, data in pairs(weaponsData) do + if type(data) == "table" then + local cat = data.category or "other" + local added = false + + if cat == "primary" then + table.insert(categories[1].weapons, {class = class, data = data}) + added = true + elseif cat == "secondary" then + table.insert(categories[2].weapons, {class = class, data = data}) + added = true + elseif cat == "grenade1" or cat == "grenade2" or cat == "grenade" then + table.insert(categories[3].weapons, {class = class, data = data}) + added = true + elseif cat == "melee" then + table.insert(categories[4].weapons, {class = class, data = data}) + added = true + end + + if not added then + table.insert(categories[5].weapons, {class = class, data = data}) + end + end + end + + -- Создаем кнопки категорий + local totalWeapons = 0 + for _, category in ipairs(categories) do + if #category.weapons > 0 then + totalWeapons = totalWeapons + #category.weapons + + local catButton = vgui.Create("DPanel", weaponsScroll) + catButton:SetSize(weaponsScroll:GetWide() - 10, 80) + catButton:Dock(TOP) + catButton:DockMargin(0, 0, 0, 10) + catButton:SetCursor("hand") + + local isHovered = false + catButton.Paint = function(s, w, h) + local bgColor = COLOR_BG_LIGHT + if isHovered then + bgColor = Color(bgColor.r + 15, bgColor.g + 20, bgColor.b + 15) + end + draw.RoundedBox(6, 0, 0, w, h, bgColor) + + -- Боковая полоска + draw.RoundedBoxEx(6, 0, 0, 6, h, COLOR_PRIMARY, true, false, true, false) + + -- Рамка + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Акцентная линия при наведении + if isHovered then + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(6, h-3, w-12, 3) + end + + -- Иконка категории + draw.SimpleText(category.icon, "ixMenuButtonFont", 30, h/2, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Название категории + draw.SimpleText(category.name, "ixSmallFont", 60, h/2 - 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Количество оружия + draw.SimpleText(#category.weapons .. " ед.", "ixSmallFont", 60, h/2 + 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Стрелка + draw.SimpleText("►", "ixSmallFont", w - 25, h/2, COLOR_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + catButton.OnCursorEntered = function() isHovered = true end + catButton.OnCursorExited = function() isHovered = false end + catButton.OnMousePressed = function() + -- Подготовка данных для окна категории + local categoryWeapons = {} + for _, wpn in ipairs(category.weapons) do + categoryWeapons[wpn.class] = wpn.data + end + OpenCategoryWeapons(category.name, category.icon, categoryWeapons) + end + end + end + + -- Если нет оружия вообще + if totalWeapons == 0 then + local emptyLabel = vgui.Create("DPanel", weaponsScroll) + emptyLabel:SetSize(weaponsScroll:GetWide(), 100) + emptyLabel:Dock(TOP) + emptyLabel:DockMargin(0, 20, 0, 0) + emptyLabel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + draw.SimpleText("⚠ НЕТ ДОСТУПНОГО ОРУЖИЯ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + -- Заполнение брони + if not armorData or type(armorData) ~= "table" then armorData = {} end + if table.Count(armorData) > 0 then + for class, data in SortedPairsByMemberValue(armorData, "name", true) do + local row = vgui.Create("DPanel", armorScroll) + row:SetSize(armorScroll:GetWide() - 10, 100) + row:Dock(TOP) + row:DockMargin(0, 0, 0, 10) + + local isHovered = false + row.Paint = function(s, w, h) + local bgColor = COLOR_BG_LIGHT + if isHovered then + bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10) + end + draw.RoundedBox(6, 0, 0, w, h, bgColor) + + -- Боковая полоска + draw.RoundedBoxEx(6, 0, 0, 5, h, COLOR_ACCENT, true, false, true, false) + + -- Рамка + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Акцентная линия при наведении + if isHovered then + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(5, h-3, w-10, 3) + end + end + + row.OnCursorEntered = function() isHovered = true end + row.OnCursorExited = function() isHovered = false end + + -- Название брони + local nameLabel = vgui.Create("DLabel", row) + nameLabel:SetText(data.name or class) + nameLabel:SetFont("ixSmallFont") + nameLabel:SetTextColor(COLOR_TEXT_PRIMARY) + nameLabel:SetPos(15, 10) + nameLabel:SizeToContents() + + -- Параметры брони + local amountLabel = vgui.Create("DLabel", row) + amountLabel:SetText("⬢ Защита: " .. tostring(data.amount or 0) .. " ед.") + amountLabel:SetFont("ixSmallFont") + amountLabel:SetTextColor(COLOR_TEXT_SECONDARY) + amountLabel:SetPos(15, 35) + amountLabel:SizeToContents() + + -- Цена + local moneyP = data.moneyPrice or 0 + local supplyP = data.supplyPrice or 0 + local priceText = "" + if (moneyP or 0) <= 0 and (supplyP or 0) <= 0 then + priceText = "★ БЕСПЛАТНО" + else + local parts = {} + if moneyP > 0 then table.insert(parts, currencySymbol .. tostring(moneyP)) end + if supplyP > 0 then table.insert(parts, tostring(supplyP) .. " ОС") end + priceText = table.concat(parts, " • ") + end + local priceLabel = vgui.Create("DLabel", row) + priceLabel:SetText(priceText) + priceLabel:SetFont("ixSmallFont") + priceLabel:SetTextColor(moneyP <= 0 and supplyP <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY) + priceLabel:SetPos(15, 58) + priceLabel:SizeToContents() + + -- Кнопка покупки + local btn = vgui.Create("DButton", row) + btn:SetSize(120, 40) + btn:SetPos(row:GetWide() - 130, 30) + btn:SetText("") + + btn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY + draw.RoundedBox(6, 0, 0, w, h, col) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30)) + end + + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + draw.SimpleText("КУПИТЬ ►", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + btn.DoClick = function() + local options = {} + if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end + if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end + + if #options == 0 then + Derma_Message("Невозможно приобрести эту броню", "Ошибка", "OK") + return + end + + if #options == 1 then + net.Start("ixArsenalAction") + net.WriteString("buy_armor") + net.WriteString(class) + net.WriteString(options[1].method) + net.SendToServer() + frame:Close() + else + local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function() + net.Start("ixArsenalAction") + net.WriteString("buy_armor") + net.WriteString(class) + net.WriteString(options[1].method) + net.SendToServer() + frame:Close() + end, options[2].text, function() + net.Start("ixArsenalAction") + net.WriteString("buy_armor") + net.WriteString(class) + net.WriteString(options[2].method) + net.SendToServer() + frame:Close() + end, "Отмена", function() end) + + if IsValid(query) then + query:ShowCloseButton(true) + end + end + end + end + else + local emptyLabel = vgui.Create("DPanel", armorScroll) + emptyLabel:SetSize(armorScroll:GetWide(), 100) + emptyLabel:Dock(TOP) + emptyLabel:DockMargin(0, 20, 0, 0) + emptyLabel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + draw.SimpleText("⚠ НЕТ ДОСТУПНОЙ ЗАЩИТЫ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + PLUGIN.menu = frame +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_ammobox.lua b/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_ammobox.lua new file mode 100644 index 0000000..d942d5a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_ammobox.lua @@ -0,0 +1,234 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Ящик с патронами" +ENT.Author = "Server" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.Category = "[FT] Система арсенала" +ENT.bNoPersist = true + +if SERVER then + function ENT:Initialize() + self:SetModel("models/trenches/prop/para_ammo/ammo_supply.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + + -- Таблица для хранения кулдаунов игроков + self.playerCooldowns = {} + end + + -- Получить информацию о патронах для оружия + function ENT:GetWeaponAmmoInfo(weapon) + if not IsValid(weapon) then return nil end + + local ammoType = weapon:GetPrimaryAmmoType() + if ammoType == -1 then return nil end + + local clipSize = weapon:GetMaxClip1() + if clipSize <= 0 then return nil end + + return { + ammoType = ammoType, + clipSize = clipSize, + ammoName = game.GetAmmoName(ammoType) or "unknown" + } + end + + -- Использование ящика + function ENT:Use(activator, caller) + if not IsValid(activator) or not activator:IsPlayer() then return end + + -- Проверка кулдауна (1 секунда) + local steamID = activator:SteamID() + local now = CurTime() + if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 1 then + return + end + + local char = activator:GetCharacter() + if not char then + activator:Notify("У вас нет активного персонажа") + return + end + + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + if not plugin then + activator:Notify("Плагин арсенала не найден") + return + end + + -- Проверка оружия в руках + local weapon = activator:GetActiveWeapon() + if not IsValid(weapon) then + activator:Notify("Возьмите оружие в руки") + return + end + + -- Получение информации о патронах + local ammoInfo = self:GetWeaponAmmoInfo(weapon) + local isMedicalKit = weapon:GetClass() == "tacrp_ifak" + + if not ammoInfo and not isMedicalKit then + activator:Notify("Это оружие не использует патроны") + return + end + + -- Специальная логика для медицинской аптечки IFAK + if isMedicalKit then + -- Проверяем текущие запасы медицинских расходников + local bandages = activator:GetAmmoCount(game.GetAmmoID("Bandages")) + local quikclots = activator:GetAmmoCount(game.GetAmmoID("Quikclots")) + local hemostats = activator:GetAmmoCount(game.GetAmmoID("Hemostats")) + + local maxBandages = 4 + local maxQuikclots = 3 + local maxHemostats = 2 + + if bandages >= maxBandages and quikclots >= maxQuikclots and hemostats >= maxHemostats then + activator:Notify("У вас уже полный комплект медицинских расходников") + return + end + + -- Рассчитываем стоимость пополнения + local costPerItem = 5 -- стоимость за единицу + local totalCost = 0 + local itemsToGive = {} + + if bandages < maxBandages then + local needed = maxBandages - bandages + totalCost = totalCost + needed * costPerItem + itemsToGive["Bandages"] = needed + end + + if quikclots < maxQuikclots then + local needed = maxQuikclots - quikclots + totalCost = totalCost + needed * costPerItem + itemsToGive["Quikclots"] = needed + end + + if hemostats < maxHemostats then + local needed = maxHemostats - hemostats + totalCost = totalCost + needed * costPerItem + itemsToGive["Hemostats"] = needed + end + + -- Проверка достаточно ли очков снабжения + if supply < totalCost then + activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply)) + return + end + + -- Выдаем расходники + for ammoType, amount in pairs(itemsToGive) do + activator:GiveAmmo(amount, ammoType, true) + end + + -- Списываем очки снабжения + plugin:AddFactionSupply(factionID, -totalCost) + + -- Устанавливаем кулдаун + self.playerCooldowns[steamID] = now + + -- Уведомление + local itemList = {} + for ammoType, amount in pairs(itemsToGive) do + table.insert(itemList, string.format("%s: +%d", ammoType, amount)) + end + activator:Notify(string.format("Получены медицинские расходники: %s", table.concat(itemList, ", "))) + + -- Звуковой эффект + self:EmitSound("items/ammo_pickup.wav") + + -- Логирование + print(string.format("[AMMOBOX] %s получил медицинские расходники. Стоимость: %d очков", + activator:Nick(), totalCost)) + return + end + + -- Проверяем конфиг арсенала для этого оружия (одноразовые гранатомёты) + local weaponClass = weapon:GetClass() + local arsenalConfig = plugin.config and plugin.config.weapons and plugin.config.weapons[weaponClass] + -- Детект по конфигу ИЛИ по типу патронов — rpg_round и PanzerFaust3 Rocket всегда одноразовые + local launcherAmmoTypes = { ["rpg_round"] = true, ["PanzerFaust3 Rocket"] = true } + local isOneShotLauncher = (arsenalConfig and (arsenalConfig.maxAmmo == 1 or arsenalConfig.maxCount == 1)) + or launcherAmmoTypes[ammoInfo.ammoName] + + -- Проверка фракции + local factionID = char:GetFaction() + local supply = plugin:GetFactionSupply(factionID) + supply = tonumber(supply) or 0 + + if isOneShotLauncher then + -- Одноразовый гранатомёт: max 1 снаряд в запасе + local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType) + if currentAmmo >= 1 then + activator:Notify("У вас уже есть снаряд для этого оружия") + return + end + + -- Стоимость 1 снаряда через costPerBullet + local totalCost = 1 + if supply < totalCost then + activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply)) + return + end + + -- Выдаём ровно 1 снаряд через SetAmmo (GiveAmmo выдаёт минимум 6 для rpg_round) + activator:SetAmmo(currentAmmo + 1, ammoInfo.ammoType) + plugin:AddFactionSupply(factionID, -totalCost) + self.playerCooldowns[steamID] = now + + activator:Notify("Получен 1 снаряд") + self:EmitSound("items/ammo_pickup.wav") + else + -- Обычное оружие: проверка реального количества патронов у игрока + local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType) + local maxAllowedAmmo = ammoInfo.clipSize * 6 -- 6 магазинов запаса + + if currentAmmo >= maxAllowedAmmo then + local currentMags = math.floor(currentAmmo / ammoInfo.clipSize) + activator:Notify(string.format("У вас уже максимум патронов (%d магазинов, 6/6)", currentMags)) + return + end + + -- Рассчитываем количество патронов (1 магазин) + local totalAmmo = ammoInfo.clipSize + local costPerBullet = 1 + local totalCost = totalAmmo * costPerBullet + + if supply < totalCost then + activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply)) + return + end + + -- Выдаем патроны + activator:GiveAmmo(totalAmmo, ammoInfo.ammoType, true) + plugin:AddFactionSupply(factionID, -totalCost) + self.playerCooldowns[steamID] = now + + -- Подсчёт текущих магазинов после выдачи + local newAmmo = activator:GetAmmoCount(ammoInfo.ammoType) + local newMags = math.floor(newAmmo / ammoInfo.clipSize) + local magsStr = math.min(newMags, 6) .. "/6" + activator:Notify(string.format("Получено %d %s (магазинов: %s)", totalAmmo, + totalAmmo == 1 and "патрон" or (totalAmmo < 5 and "патрона" or "патронов"), magsStr)) + self:EmitSound("items/ammo_pickup.wav") + end + end +end + +if CLIENT then + function ENT:Draw() + self:DrawModel() + end +end + + diff --git a/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_arsenal.lua b/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_arsenal.lua new file mode 100644 index 0000000..7ff6c55 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_arsenal.lua @@ -0,0 +1,70 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Арсенал" +ENT.Author = "Server" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.Category = "[FT] Система арсенала" +ENT.bNoPersist = true + +if SERVER then + function ENT:Initialize() + self:SetModel("models/ft/shkaf.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + if IsValid(phys) then phys:Wake() end + end + + function ENT:Use(activator, caller) + if not IsValid(activator) or not activator:IsPlayer() then return end + local char = activator:GetCharacter() + if not char then return end + + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + if not plugin then return end + + local avail = plugin:GetAvailableWeapons(char) + local weaponsData = {} + local weaponHas = {} + for class, data in pairs(avail) do + weaponsData[class] = data + weaponHas[class] = false + end + + for _, wep in ipairs(activator:GetWeapons()) do + if IsValid(wep) then + local c = wep:GetClass() + if weaponHas[c] ~= nil then weaponHas[c] = true end + end + end + + local supply = plugin:GetFactionSupply(char:GetFaction()) + supply = tonumber(supply) or 0 + + local freeRemain = plugin:GetFreeWeaponCooldownForPlayer(activator) + local factionID = char:GetFaction() + net.Start("ixArsenalOpen") + net.WriteUInt(supply, 32) + net.WriteUInt(factionID or 0, 8) + net.WriteTable(weaponsData) + net.WriteTable(weaponHas) + net.WriteTable(plugin.config.armor or {}) + net.WriteUInt(math.max(0, tonumber(freeRemain) or 0), 32) + net.Send(activator) + end +end + +if CLIENT then + function ENT:Draw() + self:DrawModel() + --local pos = self:GetPos() + Vector(0,0,10) + --local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90) + --cam.Start3D2D(pos, ang, 0.1) + -- draw.SimpleText("АРСЕНАЛ", "DermaDefaultBold", 0, 0, Color(200,200,200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + --cam.End3D2D() + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_wardrobe.lua b/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_wardrobe.lua new file mode 100644 index 0000000..0975c94 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/arsenal/entities/entities/ix_wardrobe.lua @@ -0,0 +1,596 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Гардероб" +ENT.Author = "Server" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.Category = "[FT] Система арсенала" +ENT.bNoPersist = true + +if SERVER then + function ENT:Initialize() + self:SetModel("models/props_wasteland/controlroom_storagecloset001a.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + if IsValid(phys) then phys:Wake() end + self.playerCooldowns = {} + end + + function ENT:GetModelBodygroups(model) + local tempEnt = ents.Create("prop_dynamic") + if not IsValid(tempEnt) then return {} end + tempEnt:SetModel(model) + tempEnt:Spawn() + local bodygroups = {} + for i = 0, tempEnt:GetNumBodyGroups() - 1 do + local name = tempEnt:GetBodygroupName(i) + local count = tempEnt:GetBodygroupCount(i) + if count > 1 then + bodygroups[i] = {name = name, count = count, index = i} + end + end + tempEnt:Remove() + return bodygroups + end + + function ENT:GetModelSkinCount(model) + local tempEnt = ents.Create("prop_dynamic") + if not IsValid(tempEnt) then return 0 end + tempEnt:SetModel(model) + tempEnt:Spawn() + local skinCount = tempEnt:SkinCount() or 0 + tempEnt:Remove() + return skinCount + end + + function ENT:IsBlacklisted(model, type, index) + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + if not plugin or not plugin.config.wardrobeBlacklist then return false end + local blacklist = plugin.config.wardrobeBlacklist[model] + if not blacklist then return false end + if type == "bodygroup" and blacklist.bodygroups then + return table.HasValue(blacklist.bodygroups, index) + elseif type == "skin" and blacklist.skins then + return table.HasValue(blacklist.skins, index) + end + return false + end + + function ENT:Use(activator) + if not IsValid(activator) or not activator:IsPlayer() then return end + if not self.playerCooldowns then self.playerCooldowns = {} end + local steamID = activator:SteamID() + local now = CurTime() + if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 2 then return end + local char = activator:GetCharacter() + if not char then return end + local model = activator:GetModel() + if not model then return end + + local bodygroups = self:GetModelBodygroups(model) + local skinCount = self:GetModelSkinCount(model) + local availableBodygroups = {} + for idx, data in pairs(bodygroups) do + if not self:IsBlacklisted(model, "bodygroup", idx) then + availableBodygroups[idx] = data + end + end + local availableSkins = {} + for i = 0, skinCount - 1 do + if not self:IsBlacklisted(model, "skin", i) then + table.insert(availableSkins, i) + end + end + + local currentBodygroups = {} + for idx in pairs(availableBodygroups) do + currentBodygroups[idx] = activator:GetBodygroup(idx) + end + local currentSkin = activator:GetSkin() + local currentPatch = char:GetData("patchIndex", 1) + + self.playerCooldowns[steamID] = now + net.Start("ixWardrobeOpen") + net.WriteString(model) + net.WriteTable(availableBodygroups) + net.WriteTable(availableSkins) + net.WriteTable(currentBodygroups) + net.WriteUInt(currentSkin, 8) + net.WriteUInt(currentPatch, 8) + net.Send(activator) + end + + net.Receive("ixWardrobeApply", function(len, client) + if not IsValid(client) then return end + local char = client:GetCharacter() + if not char then return end + + local isWipe = net.ReadBool() + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + + if isWipe then + client:SetSkin(0) + char:SetData("skin", 0) + char:SetData("patchIndex", 1) + char:SetData("bodygroups", nil) + + local bgs = client:GetBodyGroups() + for _, bg in ipairs(bgs) do + client:SetBodygroup(bg.id, 0) + end + + if plugin and plugin.config.patchIndex then + client:SetSubMaterial(plugin.config.patchIndex, "") + end + + return + end + + local bodygroups = net.ReadTable() + local skin = net.ReadUInt(8) + local patch = net.ReadUInt(8) + local model = client:GetModel() + + local faction = char:GetFaction() + for idx, value in pairs(bodygroups) do + if plugin and plugin.config.wardrobeAccess and plugin.config.wardrobeAccess[model] then + local bAccess = plugin.config.wardrobeAccess[model][idx] + if bAccess and bAccess[value] then + if not table.HasValue(bAccess[value], faction) then + value = 0 + end + end + end + client:SetBodygroup(idx, value) + end + + client:SetSkin(skin) + char:SetData("bodygroups", bodygroups) + char:SetData("skin", skin) + char:SetData("patchIndex", patch) + + if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then + local pMat = plugin.config.patchMaterials[patch] + if pMat then + client:SetSubMaterial(plugin.config.patchIndex, pMat) + else + client:SetSubMaterial(plugin.config.patchIndex, "") + end + end + end) +end + +if CLIENT then + local C_BG_D = Color(3, 5, 4) + local C_BG_M = Color(8, 12, 10) + local C_BG_L = Color(12, 18, 14) + local C_PRI = Color(27, 94, 32) + local C_PRI_H = Color(33, 110, 38) + local C_ACC = Color(56, 102, 35) + local C_ACC_H = Color(66, 120, 45) + local C_BDR = Color(46, 125, 50, 80) + local C_TXT_P = Color(165, 214, 167) + local C_TXT_S = Color(129, 199, 132) + local C_GRAD = Color(27, 94, 32, 15) + local C_WARN = Color(150, 40, 40) + local C_WARN_H = Color(180, 50, 50) + + function ENT:Draw() + self:DrawModel() + end + + net.Receive("ixWardrobeOpen", function() + local model = net.ReadString() + local bodygroups = net.ReadTable() + local availableSkins = net.ReadTable() + local currentBodygroups = net.ReadTable() + local currentSkin = net.ReadUInt(8) + local currentPatch = net.ReadUInt(8) + if IsRecruit(ply) then + ply:Notify("Новоприбывший не имеет доступа к гардеробу.") + return + end + + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + if not plugin then return end + + local frame = vgui.Create("DFrame") + frame:SetSize(900, 600) + frame:Center() + frame:SetTitle("") + frame:ShowCloseButton(false) + frame:MakePopup() + frame:SetBackgroundBlur(true) + frame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, C_BG_D) + surface.SetDrawColor(C_GRAD) + surface.DrawRect(0, 0, w, 3) + draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false) + surface.SetDrawColor(C_BDR) + surface.DrawLine(0, 50, w, 50) + draw.SimpleText("ГАРДЕРОБ", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.SetDrawColor(C_ACC) + surface.DrawRect(20, 48, 30, 2) + surface.DrawRect(w-50, 48, 30, 2) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(frame:GetWide() - 40, 10) + closeBtn:SetSize(30, 30) + closeBtn:SetText("") + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_ACC_H or C_ACC + draw.RoundedBox(4, 0, 0, w, h, col) + draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + closeBtn.DoClick = function() frame:Close() end + + local modelPanel = vgui.Create("DModelPanel", frame) + modelPanel:SetPos(20, 60) + modelPanel:SetSize(400, 460) + modelPanel:SetModel(model) + + local oldPaint = modelPanel.Paint + modelPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + oldPaint(s, w, h) + end + + modelPanel.LayoutEntity = function(s, ent) + if not s.bAnimSet then + local seq = ent:LookupSequence("idle_subtle") + if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end + if seq > 0 then + ent:SetSequence(seq) + end + s.bAnimSet = true + end + + if (s.bDragging) then + local x, y = gui.MousePos() + s.camAnims.rot.yaw = s.camAnims.rot.yaw + (x - s.lastX) * 0.8 + s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch + (y - s.lastY) * 0.8, -45, 45) + s.lastX, s.lastY = x, y + end + + local speed = FrameTime() * 10 + s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom) + s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot) + + local eyePos = ent:GetPos() + Vector(0, 0, 40) + s:SetCamPos(eyePos - s.curRot:Forward() * s.curZoom) + s:SetLookAng(s.curRot) + + ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1) + s.lastTick = RealTime() + end + + modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0) } + modelPanel.curZoom = 60 + modelPanel.curRot = Angle(0, 180, 0) + + local function SetCameraAngle(angle) + if angle == "front" then + modelPanel.camAnims.rot = Angle(0, 180, 0) + elseif angle == "side" then + modelPanel.camAnims.rot = Angle(0, 90, 0) + elseif angle == "back" then + modelPanel.camAnims.rot = Angle(0, 0, 0) + end + end + + modelPanel.OnMousePressed = function(s, code) + if code == MOUSE_LEFT then + s.bDragging = true + s.lastX, s.lastY = gui.MousePos() + end + end + + modelPanel.OnMouseReleased = function(s, code) + if code == MOUSE_LEFT then + s.bDragging = false + end + end + + modelPanel.OnCursorExited = function(s) + s.bDragging = false + end + + modelPanel.OnMouseWheeled = function(s, delta) + s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 20, 120) + end + + timer.Simple(0.05, function() + if IsValid(modelPanel) then SetCameraAngle("front") end + end) + + modelPanel.Think = function(s) + local entity = s:GetEntity() + if IsValid(entity) then + for idx, value in pairs(currentBodygroups) do + if entity:GetBodygroup(idx) ~= value then + entity:SetBodygroup(idx, value) + end + end + if entity:GetSkin() ~= currentSkin then + entity:SetSkin(currentSkin) + end + + if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then + local pMat = plugin.config.patchMaterials[currentPatch] + if pMat then + entity:SetSubMaterial(plugin.config.patchIndex, pMat) + else + entity:SetSubMaterial(plugin.config.patchIndex, "") + end + end + end + end + + local viewButtons = vgui.Create("DPanel", frame) + viewButtons:SetPos(20, 530) + viewButtons:SetSize(400, 50) + viewButtons.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local bw = 125 + local bs = 12 + + local frontBtn = vgui.Create("DButton", viewButtons) + frontBtn:SetPos(10, 10) + frontBtn:SetSize(bw, 30) + frontBtn:SetText("") + frontBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("СПЕРЕДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + frontBtn.DoClick = function() SetCameraAngle("front") end + + local sideBtn = vgui.Create("DButton", viewButtons) + sideBtn:SetPos(10 + bw + bs, 10) + sideBtn:SetSize(bw, 30) + sideBtn:SetText("") + sideBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("СБОКУ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + sideBtn.DoClick = function() SetCameraAngle("side") end + + local backBtn = vgui.Create("DButton", viewButtons) + backBtn:SetPos(10 + (bw + bs) * 2, 10) + backBtn:SetSize(bw, 30) + backBtn:SetText("") + backBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("СЗАДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + backBtn.DoClick = function() SetCameraAngle("back") end + + local settingsPanel = vgui.Create("DPanel", frame) + settingsPanel:SetPos(440, 60) + settingsPanel:SetSize(440, 520) + settingsPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, C_BG_M) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local settingsScroll = vgui.Create("DScrollPanel", settingsPanel) + settingsScroll:Dock(FILL) + settingsScroll:DockMargin(10, 10, 10, 140) + + local sbar = settingsScroll:GetVBar() + sbar:SetWide(8) + sbar.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) end + sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_ACC) end + sbar.btnUp.Paint = function() end + sbar.btnDown.Paint = function() end + + local yPos = 5 + + if plugin and plugin.config.patchMaterials and #plugin.config.patchMaterials > 0 then + local patchLabel = vgui.Create("DPanel", settingsScroll) + patchLabel:SetPos(10, yPos) + patchLabel:SetSize(400, 35) + patchLabel.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_L) + surface.SetDrawColor(C_ACC) + surface.DrawRect(0, 0, 3, h) + draw.SimpleText("ПАТЧИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + yPos = yPos + 40 + + local pH = vgui.Create("DPanel", settingsScroll) + pH:SetPos(10, yPos) + pH:SetSize(400, 65) + pH.Paint = function(s, w, h) + local col = s:IsHovered() and C_BG_L or C_BG_D + draw.RoundedBox(6, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local pS = vgui.Create("DNumSlider", pH) + pS:SetPos(5, 15) + pS:SetSize(390, 40) + pS:SetMin(1) + pS:SetMax(#plugin.config.patchMaterials) + pS:SetDecimals(0) + pS:SetValue(currentPatch) + pS:SetText("Тип патча") + pS.Slider.Paint = function(s, w, h) + draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D) + local maxV = math.max(1, pS:GetMax() - pS:GetMin()) + local frac = (pS:GetValue() - pS:GetMin()) / maxV + draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC) + end + pS.Slider.Knob.Paint = function(s, w, h) + draw.RoundedBox(w/2, 0, 0, w, h, C_PRI) + if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end + end + pS.OnValueChanged = function(s, value) currentPatch = math.floor(value) end + + yPos = yPos + 70 + end + + if table.Count(bodygroups) > 0 then + local bgLabel = vgui.Create("DPanel", settingsScroll) + bgLabel:SetPos(10, yPos) + bgLabel:SetSize(400, 35) + bgLabel.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_L) + surface.SetDrawColor(C_ACC) + surface.DrawRect(0, 0, 3, h) + draw.SimpleText("ЧАСТИ ТЕЛА", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + yPos = yPos + 40 + + for idx, data in SortedPairsByMemberValue(bodygroups, "index") do + local bgPanel = vgui.Create("DPanel", settingsScroll) + bgPanel:SetPos(10, yPos) + bgPanel:SetSize(400, 65) + bgPanel.Paint = function(s, w, h) + local col = s:IsHovered() and C_BG_L or C_BG_D + draw.RoundedBox(6, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local label = vgui.Create("DLabel", bgPanel) + label:SetPos(10, 8) + label:SetSize(380, 20) + label:SetFont("DermaDefault") + label:SetText(data.name) + label:SetTextColor(C_TXT_P) + + local slider = vgui.Create("DNumSlider", bgPanel) + slider:SetPos(5, 28) + slider:SetSize(390, 30) + slider:SetMin(0) + slider:SetMax(data.count - 1) + slider:SetDecimals(0) + slider:SetValue(currentBodygroups[idx] or 0) + slider:SetText("") + slider.Slider.Paint = function(s, w, h) + draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D) + local maxV = math.max(1, slider:GetMax() - slider:GetMin()) + local frac = (slider:GetValue() - slider:GetMin()) / maxV + draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC) + end + slider.Slider.Knob.Paint = function(s, w, h) + draw.RoundedBox(w/2, 0, 0, w, h, C_PRI) + if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end + end + slider.OnValueChanged = function(s, value) currentBodygroups[idx] = math.floor(value) end + + yPos = yPos + 70 + end + end + + if #availableSkins > 1 then + local skinLabel = vgui.Create("DPanel", settingsScroll) + skinLabel:SetPos(10, yPos) + skinLabel:SetSize(400, 35) + skinLabel.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, C_BG_L) + surface.SetDrawColor(C_ACC) + surface.DrawRect(0, 0, 3, h) + draw.SimpleText("СКИНЫ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + yPos = yPos + 40 + + local skinPanel = vgui.Create("DPanel", settingsScroll) + skinPanel:SetPos(10, yPos) + skinPanel:SetSize(400, 65) + skinPanel.Paint = function(s, w, h) + local col = s:IsHovered() and C_BG_L or C_BG_D + draw.RoundedBox(6, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local skinSlider = vgui.Create("DNumSlider", skinPanel) + skinSlider:SetPos(5, 15) + skinSlider:SetSize(390, 40) + skinSlider:SetMin(0) + skinSlider:SetMax(#availableSkins) + skinSlider:SetDecimals(0) + skinSlider:SetValue(currentSkin) + skinSlider:SetText("Скин") + skinSlider.Slider.Paint = function(s, w, h) + draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D) + local maxV = math.max(1, skinSlider:GetMax() - skinSlider:GetMin()) + local frac = (skinSlider:GetValue() - skinSlider:GetMin()) / maxV + draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC) + end + skinSlider.Slider.Knob.Paint = function(s, w, h) + draw.RoundedBox(w/2, 0, 0, w, h, C_PRI) + if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end + end + skinSlider.OnValueChanged = function(s, value) currentSkin = math.floor(value) end + + yPos = yPos + 70 + end + + local wipeBtn = vgui.Create("DButton", settingsPanel) + wipeBtn:SetPos(10, 390) + wipeBtn:SetSize(420, 50) + wipeBtn:SetText("") + wipeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_WARN_H or C_WARN + draw.RoundedBox(6, 0, 0, w, h, col) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("СБРОСИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + wipeBtn.DoClick = function() + net.Start("ixWardrobeApply") + net.WriteBool(true) + net.SendToServer() + frame:Close() + end + + local applyBtn = vgui.Create("DButton", settingsPanel) + applyBtn:SetPos(10, 450) + applyBtn:SetSize(420, 60) + applyBtn:SetText("") + applyBtn.Paint = function(s, w, h) + local col = s:IsHovered() and C_PRI_H or C_PRI + draw.RoundedBox(6, 0, 0, w, h, col) + surface.SetDrawColor(C_GRAD) + surface.DrawRect(0, 0, w, h/2) + surface.SetDrawColor(C_BDR) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("ПРИМЕНИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + applyBtn.DoClick = function() + net.Start("ixWardrobeApply") + net.WriteBool(false) + net.WriteTable(currentBodygroups) + net.WriteUInt(currentSkin, 8) + net.WriteUInt(currentPatch, 8) + net.SendToServer() + frame:Close() + end + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_config.lua b/garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_config.lua new file mode 100644 index 0000000..6c86c73 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_config.lua @@ -0,0 +1,979 @@ +-- проверка пениса + + + + + + + + + + + + + + + +local PLUGIN = PLUGIN + + + + + + + +PLUGIN.config = { + + + + + + + + currencyName = "Очки снабжения", + + + + + + + + + + + + + + + + -- Начальное снабжение для фракций + + + + + + + + startSupply = { + + + + + + + + [FACTION_RUSSIAN or 1] = 2000, + [FACTION_UKRAINE or 2] = 2000, + }, -- [FACTION_ID] = amount + + -- Максиммальное снабжение + maxSupply = 20000, + + + + + + + + + + + + + + + + -- Минимум, при котором арсенал считается закрытым + + + + + + + + minSupply = 0, + + + + + + + + + + + + + + + + -- Cooldown for free weapons (supplyPrice == 0), per-player, seconds + + + + + + + + freeWeaponCooldown = 3600, -- default 1 hour + + + + + + + + + + + + + + + + -- Currency symbols per faction (client uses this to display money symbol) + + + + + + + + currencySymbol = { + + + + + + + + [FACTION_RUSSIAN or 1] = "₽", + [FACTION_UKRAINE or 2] = "$", + }, + + + + + + + + + + + + + + + + weapons = { +["tfa_inss_wpn_ak12"] = { +["category"] = "primary", +["moneyPrice"] = 11000, +["supplyPrice"] = 220, +["name"] = "AK-12" +}, +["tacrp_mr96"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 1, +["name"] = "MR-96" +}, +["tacrp_ak_ak74u"] = { +["category"] = "primary", +["moneyPrice"] = 3000, +["supplyPrice"] = 50, +["name"] = "AKС-74У" +}, +["tacrp_io_glock18"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "Glock 18C" +}, +["tacrp_ks23"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "КС-23" +}, +["tacrp_as50"] = { +["category"] = "primary", +["moneyPrice"] = 20000, +["supplyPrice"] = 1000, +["name"] = "AS50" +}, +["weapon_lvsrepair"] = { +["category"] = "tool1", +["moneyPrice"] = 100, +["supplyPrice"] = 10, +["name"] = "Сварка" +}, +["tacrp_io_trg42"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 450, +["name"] = "Sako TRG-42" +}, +["tacrp_sd_dual_degala"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 1, +["name"] = "Двойные Deagle" +}, +["weapon_cigarette_camel"] = { +["category"] = "tool1", +["moneyPrice"] = 100, +["supplyPrice"] = 10, +["name"] = "Сигаретка" +}, +["tacrp_io_sg550"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 200, +["name"] = "SIG SG 550" +}, +["tacrp_io_rpk"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 150, +["name"] = "РПК-74" +}, +["tacrp_uzi"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "UZI" +}, +["parachute_swep"] = { +["category"] = "tool4", +["moneyPrice"] = 150, +["supplyPrice"] = 10, +["name"] = "Парашют" +}, +["tacrp_hk417"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "HK 417" +}, +["tacrp_sd_bizon"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "ПП-19 Бизон" +}, +["weapon_sw_at4"] = { +["moneyPrice"] = 4500, +["supplyPrice"] = 450, +["maxCount"] = 1, +["ammoType"] = "rpg_round", +["category"] = "heavy", +["maxAmmo"] = 1, +["name"] = "AT4" +}, +["fas2_ifak"] = { +["category"] = "tool7", +["moneyPrice"] = 500, +["supplyPrice"] = 50, +["name"] = "Аптечка" +}, +["bandage"] = { +["category"] = "tool7", +["moneyPrice"] = 500, +["supplyPrice"] = 100, +["name"] = "Бинт" +}, +["tacrp_io_saiga"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "Сайга-12К" +}, +["tacrp_nade_frag"] = { +["category"] = "grenade", +["moneyPrice"] = 300, +["supplyPrice"] = 30, +["name"] = "Осколочная граната" +}, +["tacrp_g36k"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "G36K" +}, +["tfa_inss_wpn_l1a1"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 240, +["name"] = "L1A1" +}, +["bodycam_tablet"] = { +["category"] = "tool3", +["moneyPrice"] = 500, +["supplyPrice"] = 50, +["name"] = "Боди-камера" +}, +["gmod_camera"] = { +["category"] = "tool3", +["moneyPrice"] = 50, +["supplyPrice"] = 10, +["name"] = "Камера" +}, +["admin_defib"] = { +["category"] = "tool7", +["moneyPrice"] = 8000, +["supplyPrice"] = 5000, +["name"] = "Дефибриллятор" +}, +["tacrp_sg551"] = { +["category"] = "primary", +["moneyPrice"] = 20000, +["supplyPrice"] = 200, +["name"] = "SG 551" +}, +["tacrp_knife"] = { +["category"] = "melee", +["moneyPrice"] = 50, +["supplyPrice"] = 0, +["name"] = "Нож" +}, +["guitar"] = { +["category"] = "tool3", +["moneyPrice"] = 200, +["supplyPrice"] = 10, +["name"] = "Гитара" +}, +["v92_bf2_medikit"] = { +["category"] = "tool9", +["moneyPrice"] = 1500, +["supplyPrice"] = 150, +["name"] = "Аптечка" +}, +["weapon_sw_9k38"] = { +["category"] = "heavy", +["moneyPrice"] = 5000, +["supplyPrice"] = 500, +["name"] = "9K38" +}, +["tacrp_mp7"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "MP7" +}, +["weapon_lvsspikestrip"] = { +["category"] = "tool2", +["moneyPrice"] = 100, +["supplyPrice"] = 10, +["name"] = "Шипы" +}, +["tacrp_p250"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 1, +["name"] = "P250" +}, +["tacrp_m4"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "HK416" +}, +["tfa_inss_wpn_m16a4"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 240, +["name"] = "M16A4" +}, +["tacrp_ak_svd"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 450, +["name"] = "СВД" +}, +["tacrp_io_xm8lmg"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 200, +["name"] = "ХМ8 LMG" +}, +["tacrp_mg4"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 300, +["name"] = "HK MG4" +}, +["tacrp_io_degala"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 1, +["name"] = "Desert Eagle" +}, +["tacrp_mp5"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "MP5A3" +}, +["special_bandage"] = { +["category"] = "tool8", +["moneyPrice"] = 1000, +["supplyPrice"] = 500, +["name"] = "Шина от переломов" +}, +["tacrp_ak_aek971"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "АЕК-971" +}, +["tacrp_ak_ak12"] = { +["category"] = "primary", +["moneyPrice"] = 11000, +["supplyPrice"] = 200, +["name"] = "АК-12" +}, +["weapon_sw_rpg26"] = { +["moneyPrice"] = 4500, +["supplyPrice"] = 450, +["maxCount"] = 1, +["ammoType"] = "rpg_round", +["category"] = "heavy", +["maxAmmo"] = 1, +["name"] = "РПГ-26" +}, +["tacrp_spr"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "M700" +}, +["tacrp_ex_m4a1"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 300, +["name"] = "М4А1" +}, +["tfa_new_inss_mk18"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 240, +["name"] = "MK18" +}, +["tacrp_io_fiveseven"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 1, +["name"] = "FiveSeven" +}, +["engineertoolmines"] = { +["category"] = "tool5", +["moneyPrice"] = 50000, +["supplyPrice"] = 1000, +["name"] = "Мины" +}, +["tacrp_pdw"] = { +["category"] = "primary", +["moneyPrice"] = 3000, +["supplyPrice"] = 100, +["name"] = "KAC PDW" +}, +["tacrp_ak_an94"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "АН-94" +}, +["weapon_r_handcuffs"] = { +["category"] = "tool8", +["moneyPrice"] = 1000, +["supplyPrice"] = 10, +["name"] = "Наручники" +}, +["tfa_inss_wpn_fn_fal"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 240, +["name"] = "FN FAL" +}, +["weapon_rope_knife"] = { +["category"] = "tool6", +["moneyPrice"] = 50000, +["supplyPrice"] = 500, +["name"] = "Крюк кошка" +}, +["tacrp_io_sg550r"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 300, +["name"] = "SG-550R" +}, +["weapon_sw_fim92"] = { +["category"] = "heavy", +["moneyPrice"] = 5000, +["supplyPrice"] = 500, +["name"] = "FIM-92" +}, +["tacrp_sd_groza"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "ОЦ-14" +}, +["tacrp_ifak"] = { +["category"] = "tool7", +["moneyPrice"] = 500, +["supplyPrice"] = 50, +["name"] = "Аптечка IFAK" +}, +["tacrp_aug"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 250, +["name"] = "AUG" +}, +["weapon_sw_panzerfaust3"] = { +["moneyPrice"] = 5000, +["supplyPrice"] = 500, +["maxCount"] = 1, +["ammoType"] = "PanzerFaust3 Rocket", +["category"] = "heavy", +["maxAmmo"] = 1, +["name"] = "Панцерфауст 3" +}, +["tacrp_io_scarh"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "FN SCAR" +}, +["tacrp_io_xm8car"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 250, +["name"] = "XM8" +}, +["tacrp_pa_makarov"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 1, +["name"] = "ПМ" +}, +["tacrp_bekas"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "Bekas" +}, +["weapon_cuff_elastic"] = { +["category"] = "tool8", +["moneyPrice"] = 300, +["supplyPrice"] = 20, +["name"] = "Наручники" +}, +["tacrp_ex_hecate"] = { +["category"] = "primary", +["moneyPrice"] = 17000, +["supplyPrice"] = 300, +["name"] = "PGM Hecate" +}, +["tacrp_ex_glock"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 20, +["name"] = "Glock" +}, +["tfa_ins2_aks_r"] = { +["category"] = "primary", +["moneyPrice"] = 11000, +["supplyPrice"] = 220, +["name"] = "AKS-74U" +}, +["tacrp_ak_ak74"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 50, +["name"] = "АК-74" +}, +["tacrp_io_sl8"] = { +["category"] = "primary", +["moneyPrice"] = 9000, +["supplyPrice"] = 180, +["name"] = "SL8" +}, +["tfa_ins2_moe_akm"] = { +["category"] = "primary", +["moneyPrice"] = 12000, +["supplyPrice"] = 240, +["name"] = "AKM MOE" +}, +["tacrp_io_val"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "АС ВАЛ" +}, +["tacrp_skorpion"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "VZ-61" +}, +["swep_drone_grenade"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 1000, +["name"] = "Дрон с гранатой" +}, +["tacrp_sd_pkm"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 300, +["name"] = "ПКМ" +}, +["weapon_sw_rpg28"] = { +["moneyPrice"] = 5000, +["supplyPrice"] = 500, +["maxCount"] = 1, +["ammoType"] = "rpg_round", +["category"] = "heavy", +["maxAmmo"] = 1, +["name"] = "РПГ-28" +}, +["tacrp_nade_flashbang"] = { +["category"] = "grenade1", +["moneyPrice"] = 200, +["supplyPrice"] = 20, +["name"] = "Светошумовая граната" +}, +["v92_bf2_ammokit"] = { +["category"] = "tool3", +["moneyPrice"] = 1500, +["supplyPrice"] = 150, +["name"] = "Ящик с патронами" +}, +["tacrp_io_vss"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 200, +["name"] = "ВСС Винторез" +}, +["tacrp_io_p226"] = { +["category"] = "secondary", +["moneyPrice"] = 200, +["supplyPrice"] = 1, +["name"] = "P226" +}, +["tfa_blast_ksvk_cqb"] = { +["category"] = "primary", +["moneyPrice"] = 15000, +["supplyPrice"] = 300, +["name"] = "KSvK 12.7" +}, +["tacrp_io_k98"] = { +["category"] = "primary", +["moneyPrice"] = 5000, +["supplyPrice"] = 100, +["name"] = "KAR98K" +}, +["tacrp_superv"] = { +["category"] = "primary", +["moneyPrice"] = 9000, +["supplyPrice"] = 180, +["name"] = "KRISS Vector" +}, +["tacrp_nade_smoke"] = { +["category"] = "grenade2", +["moneyPrice"] = 150, +["supplyPrice"] = 15, +["name"] = "Дымовая граната" +}, +["tacrp_sd_aac_hb"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "AAC Honey badger" +}, +["tacrp_pa_vykhlop"] = { +["category"] = "primary", +["moneyPrice"] = 17000, +["supplyPrice"] = 350, +["name"] = "ВСК Выхлоп" +}, +["tacrp_sd_g3"] = { +["category"] = "primary", +["moneyPrice"] = 10000, +["supplyPrice"] = 200, +["name"] = "G3A3" +}, +["tfa_ins2_warface_cheytac_m200"] = { +["category"] = "primary", +["moneyPrice"] = 17000, +["supplyPrice"] = 340, +["name"] = "Cheytac M200" +} +}, + + + + + + + + + + + + + + + + + + + + + + + + -- ============================================ + + + + + + + + + + + + + + + + -- DONATE WEAPONS (donate_only = true означает что доступно ТОЛЬКО через донат) + + + + + + + + -- Если donate_only НЕ указано или false, оружие может быть добавлено в spec/podr и будет платным + + + + + + + + -- ============================================ + + + + + + + + + + + + + + + + -- Патроны: тип -> {name, price, amount} + + + + + + + + ammo = { + + + + + + + + -- ["rifle"] = { name = "Патроны для винтовки", price = 10, amount = 30 }, + + + + + + + + }, + + + + + + + + + + + + + + + + -- Броня (опционально) + + + + + + + + armor = { + + + + + + + + -- amount: how much armor will be set on the player after purchase + + + + + + + + -- supplyPrice: cost in faction supply points (keeps naming consistent with weapons) + + + + + + + + ["armor_light"] = { name = "Легкая броня", supplyPrice = 50, moneyPrice = 0, amount = 25 }, + + + + + + + + ["armor_medium"] = { name = "Средняя броня", supplyPrice = 75, moneyPrice = 0, amount = 50 }, + + + + + + + + ["armor_heavy"] = { name = "Тяжелая броня", supplyPrice = 150, moneyPrice = 0, amount = 100 }, + + + + + + + + }, + + + + + + + + + + + + + + + + wardrobeBlacklist = {}, + + + + + + + + patchIndex = 15, + + + + + + + + patchMaterials = { + + + + + + + + "models/texture_vsu_1amb/path", + + + + + + + + "models/texture_vsu_1amb/path2", + + + + + + + + "models/texture_vsu_1amb/path3" + + + + + + + + }, + + + + + + + + wardrobeAccess = {}, + + + + + + + + wardrobeStats = {}, + + + + + + + + wardrobeCHands = {} + + + + + + + +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_plugin.lua new file mode 100644 index 0000000..211fe72 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_plugin.lua @@ -0,0 +1,64 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Arsenal" +PLUGIN.author = "Refosel" +PLUGIN.description = "Arsenal plugin (skeleton) — выдача оружия, патронов и управление очками снабжения." + +ix.util.Include("sh_config.lua") +ix.util.Include("cl_plugin.lua") +ix.util.Include("sv_plugin.lua") + + +ix.command.Add("test_arsenal", { + description = "Test arsenal weapon availability.", + OnRun = function(self, client) + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + if not plugin then return "Plugin not found" end + + local char = client:GetCharacter() + if not char then return "No character" end + + client:ChatPrint("--- Arsenal Test for " .. client:Nick() .. " ---") + local avail = plugin:GetAvailableWeapons(char) + client:ChatPrint("Available weapons total: " .. table.Count(avail)) + for class, data in pairs(avail) do + client:ChatPrint(string.format(" [%s] %s (Donate: %s)", class, data.name or "N/A", tostring(data.isDonateVersion or false))) + end + client:ChatPrint("--- End Test ---") + end +}) + +ix.command.Add("arsenal_supply_set", { + description = "Set faction supply. Usage: arsenal_supply_set [faction id]", + adminOnly = true, + arguments = { + ix.type.number, + ix.type.number + }, + OnRun = function(self, client, amount, faction) + if CLIENT then return end + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + if not plugin then return client:Notify("Arsenal plugin not loaded") end + + local fid = faction or client:GetCharacter():GetFaction() + plugin:SetFactionSupply(fid, math.max(tonumber(amount) or 0, 0)) + client:Notify("Set supply for faction " .. tostring(fid) .. " to " .. tostring(plugin:GetFactionSupply(fid))) + end +}) + +ix.command.Add("arsenal_supply_add", { + description = "Add (or subtract) supply for a faction. Usage: arsenal_supply_add [faction id]", + adminOnly = true, + arguments = { + ix.type.number, + ix.type.number + }, + OnRun = function(self, client, delta, faction) + if CLIENT then return end + local plugin = ix.plugin.list and ix.plugin.list["arsenal"] + if not plugin then return client:Notify("Arsenal plugin not loaded") end + + local fid = faction or client:GetCharacter():GetFaction() + plugin:AddFactionSupply(fid, tonumber(delta) or 0) + client:Notify("Faction " .. tostring(fid) .. " supply now: " .. tostring(plugin:GetFactionSupply(fid))) + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/arsenal/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/arsenal/sv_plugin.lua new file mode 100644 index 0000000..61f3610 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/arsenal/sv_plugin.lua @@ -0,0 +1,751 @@ +if SERVER then + util.AddNetworkString("ixArsenalOpen") + util.AddNetworkString("ixArsenalAction") + util.AddNetworkString("ixWardrobeOpen") + util.AddNetworkString("ixWardrobeApply") +end + +local PLUGIN = PLUGIN +local config = PLUGIN.config + + + +local function IsRecruit(ply) + local char = ply:GetCharacter() + if not char then return false end + local spec = char.GetSpec and char:GetSpec() or 0 + return spec == 1 +end + +-- Helpers to store complex data as JSON strings because SetData/GetData may not accept tables +local function _loadJSONData(plugin, key) + -- plugin:GetData() returns the whole plugin store (or default table). We keep subkeys inside that store. + local store = plugin:GetData() or {} + if type(store) ~= "table" then return {} end + local raw = store[key] + if raw == nil then return {} end + if type(raw) == "table" then return raw end + if type(raw) == "string" then + local ok, tbl = pcall(util.JSONToTable, raw) + if ok and istable(tbl) then return tbl end + end + return {} +end + +local function _saveJSONData(plugin, key, tbl) + if type(tbl) ~= "table" then tbl = {} end + local store = plugin:GetData() or {} + if type(store) ~= "table" then store = {} end + -- store the table directly under the subkey; ix.data.Set will serialize the whole store + store[key] = tbl + plugin:SetData(store) +end + +-- Faction supply storage helpers +function PLUGIN:GetFactionSupply(faction) + local store = _loadJSONData(self, "factionSupply") or {} + local key = tostring(faction) + local val = store[faction] + if val == nil then val = store[key] end + if val == nil then + -- fallback to configured startSupply or 0 + return config.startSupply[faction] or 0 + end + local maxSupply = (self.config and self.config.maxSupply) or 20000 + return math.Clamp(tonumber(val) or 0, 0, maxSupply) +end + +-- Returns remaining cooldown seconds for free weapons for a specific player (per-player persistent) +function PLUGIN:GetFreeWeaponCooldownForPlayer(client) + if not IsValid(client) or not client:IsPlayer() then return 0 end + local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {} + local steam = client:SteamID() or tostring(client:SteamID64() or "unknown") + local last = tonumber(stamps[steam]) or 0 + local now = os.time() + local cd = tonumber(config.freeWeaponCooldown) or 0 + if cd <= 0 then return 0 end + local remain = cd - (now - last) + if remain < 0 then remain = 0 end + return math.floor(remain) +end + +-- Returns remaining cooldown seconds for donate weapons (10 minutes = 600 seconds) +function PLUGIN:GetDonateWeaponCooldown(client, weaponClass) + if not IsValid(client) or not client:IsPlayer() then return 0 end + local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {} + local steam = client:SteamID() or tostring(client:SteamID64() or "unknown") + if not stamps[steam] then return 0 end + local last = tonumber(stamps[steam][weaponClass]) or 0 + local now = os.time() + local cd = 600 -- 10 минут + local remain = cd - (now - last) + if remain < 0 then remain = 0 end + return math.floor(remain) +end + +function PLUGIN:SetDonateWeaponCooldown(client, weaponClass) + if not IsValid(client) or not client:IsPlayer() then return end + local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {} + local steam = client:SteamID() or tostring(client:SteamID64() or "unknown") + if not stamps[steam] then stamps[steam] = {} end + stamps[steam][weaponClass] = os.time() + _saveJSONData(self, "donateWeaponTimestamps", stamps) +end + +function PLUGIN:SetFactionSupply(faction, amount) + local store = _loadJSONData(self, "factionSupply") or {} + local key = tostring(faction) + local value = math.max(tonumber(amount) or 0, 0) + store[key] = value + store[faction] = value + _saveJSONData(self, "factionSupply", store) +end + +-- Compatibility helper: add (or subtract) supply for a faction +function PLUGIN:AddFactionSupply(faction, delta) + local amount = tonumber(delta) or 0 + local cur = tonumber(self:GetFactionSupply(faction)) or 0 + local maxSupply = (self.config and self.config.maxSupply) or 20000 + local new = math.Clamp(cur + amount, 0, maxSupply) + + if new == cur then return end -- Если ничего не изменилось (например, уперлись в лимит) + + self:SetFactionSupply(faction, new) + + -- Логирование изменения снабжения + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + if amount > 0 then + local message = string.format("Фракция '%s' получила +%d очков снабжения (итого: %d)", factionName, amount, new) + serverlogsPlugin:AddLog("FACTION_SUPPLY_ADD", message, nil, { + faction = faction, + factionName = factionName, + amount = amount, + total = new + }) + elseif amount < 0 then + local message = string.format("Фракция '%s' потратила %d очков снабжения (итого: %d)", factionName, math.abs(amount), new) + serverlogsPlugin:AddLog("FACTION_SUPPLY_USE", message, nil, { + faction = faction, + factionName = factionName, + amount = math.abs(amount), + total = new + }) + end + end + + return new +end + +-- Получить список доступного оружия для персонажа (заглушка) +-- Рекомендуется реализовать: читать FACTION.Spec и FACTION.Podr из ix.faction.Get(faction) +function PLUGIN:GetAvailableWeapons(char) + local weapons = {} + local faction = char:GetFaction() + local factionTable = ix.faction.Get(faction) + if not factionTable then + return weapons + end + + local allowed = {} + local specIndex = tonumber(char:GetSpec()) or 1 + if factionTable.Spec and factionTable.Spec[specIndex] and istable(factionTable.Spec[specIndex].weapons) then + for _, c in ipairs(factionTable.Spec[specIndex].weapons) do + allowed[c] = true + end + end + + local podrIndex = tonumber(char:GetPodr()) or 1 + if factionTable.Podr and factionTable.Podr[podrIndex] and istable(factionTable.Podr[podrIndex].preset) then + for _, c in ipairs(factionTable.Podr[podrIndex].preset) do + allowed[c] = true + end + end + + -- donate weapons from character data (with expiration check) + local donateWeaponsTimed = char:GetData("donate_weapons_timed", {}) + local donateTable = {} + if donateWeaponsTimed and istable(donateWeaponsTimed) then + local now = os.time() + for wepClass, wepData in pairs(donateWeaponsTimed) do + if istable(wepData) and wepData.expires and tonumber(wepData.expires) > now then + donateTable[wepClass] = true + end + end + end + + -- Поддержка старой системы без срока (если есть) + local donateList = char:GetData("donate_weapons", false) + if donateList and istable(donateList) then + for _, c in ipairs(donateList) do donateTable[c] = true end + end + + -- IGS integration: check if player has any weapons bought via IGS + local client = char:GetPlayer() + if IsValid(client) then + if IGS then + if isfunction(IGS.GetItems) then + local items = IGS.GetItems() + for _, ITEM in pairs(items) do + local uid = (isfunction(ITEM.GetUID) and ITEM:GetUID()) or ITEM.uid + if not uid then continue end + + local has = false + if isfunction(client.HasPurchase) then + has = client:HasPurchase(uid) + elseif isfunction(IGS.PlayerHasItem) then + has = IGS.PlayerHasItem(client, uid) + end + + if has then + -- Try all possible ways IGS stores the weapon class + local weaponClass = nil + local weaponCandidates = { + (isfunction(ITEM.GetWeapon) and ITEM:GetWeapon()), + (isfunction(ITEM.GetMeta) and ITEM:GetMeta("weapon")), + ITEM.weapon, + ITEM.weapon_class, + ITEM.class, + ITEM.uniqueID, + ITEM.val, + uid + } + for _, candidate in ipairs(weaponCandidates) do + if candidate and candidate ~= "" then + weaponClass = candidate + break + end + end + + if weaponClass and type(weaponClass) ~= "string" then + weaponClass = tostring(weaponClass) + end + + if not weaponClass then + weaponClass = uid + end + + if weaponClass then + -- Check if this class or a similar one exists in config + if config.weapons[weaponClass] then + donateTable[weaponClass] = true + else + for configClass, _ in pairs(config.weapons) do + if configClass:find(weaponClass, 1, true) then + donateTable[configClass] = true + end + end + end + end + end + end + end + end + end + + -- Build result from config only if class is allowed and exists in config + local supply = tonumber(self:GetFactionSupply(faction)) or 0 + for class, _ in pairs(allowed) do + local data = config.weapons[class] + if not data then continue end -- skip if not configured + + -- Проверяем есть ли оружие в донате у игрока + local hasDonate = donateTable[class] + + if hasDonate then + -- Если куплено в донате - показываем как донатное (бесплатно, с кулдауном) + local donateData = table.Copy(data) + donateData.isDonateVersion = true -- флаг что это донатная версия + donateData.supplyPrice = 0 + donateData.moneyPrice = 0 + weapons[class] = donateData + elseif data.donate_only then + -- Оружие только для доната, но не куплено - не показываем + continue + else + -- Обычное оружие из spec/podr - показываем с ценой из конфига + if supply <= 0 then + if (data.supplyPrice or 0) == 0 then weapons[class] = data end + else + weapons[class] = data + end + end + end + + -- Добавляем оружие которое есть ТОЛЬКО в донате (не в spec/podr) + for wepClass, _ in pairs(donateTable) do + if not allowed[wepClass] then + local data = config.weapons[wepClass] + if data then + local donateData = table.Copy(data) + donateData.isDonateVersion = true + donateData.supplyPrice = 0 + donateData.moneyPrice = 0 + weapons[wepClass] = donateData + end + end + end + + return weapons +end + +-- Заглушки для действий (реализуйте логику по необходимости) +function PLUGIN:BuyWeapon(client, weaponClass, payMethod) + local char = client:GetCharacter() + if not char then return false, "Нет персонажа" end + local available = self:GetAvailableWeapons(char) + local data = available[weaponClass] + if not data then return false, "Оружие недоступно для покупки" end + payMethod = payMethod or "supply" + local faction = char:GetFaction() + + -- Option A: pay by money (Helix) + if payMethod == "money" then + local price = data.moneyPrice or 0 + if price == 0 then return false, "Это оружие нельзя купить за деньги" end + if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end + char:TakeMoney(price) + client:Give(weaponClass) + return true + end + + -- Check if this is a donate weapon version + local isDonateWeapon = (data.isDonateVersion == true) + + if isDonateWeapon then + -- Проверяем что оружие уже есть у игрока + if client:HasWeapon(weaponClass) then + return false, "У вас уже есть это оружие" + end + + -- Проверяем кулдаун для донат-оружия (10 минут) + local cooldown = self:GetDonateWeaponCooldown(client, weaponClass) + if cooldown > 0 then + local minutes = math.floor(cooldown / 60) + local seconds = cooldown % 60 + return false, string.format("Вы недавно брали это оружие. Подождите %02d:%02d", minutes, seconds) + end + + -- Проверяем категорию (донат оружие тоже может иметь категорию) + local newCat = data.category or "" + if newCat ~= "" and not newCat:find("^tool") then + for _, wep in ipairs(client:GetWeapons()) do + if not IsValid(wep) then continue end + local wclass = wep:GetClass() + local wdata = config.weapons[wclass] + if wdata and wdata.category == newCat then + return false, "Вы уже имеете оружие этой категории: " .. newCat + end + end + end + + -- Check max ammo limit for launchers + if data.maxAmmo then + local totalAmmo = 0 + if data.ammoType == "PanzerFaust3 Rocket" or data.ammoType == "rpg_round" then + totalAmmo = client:GetAmmoCount("rpg_round") + client:GetAmmoCount("PanzerFaust3 Rocket") + else + totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round") + end + if totalAmmo >= data.maxAmmo then + return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")" + end + end + + -- Выдаем оружие и ставим кулдаун + client:Give(weaponClass) + + -- Reset ammo for launchers to prevent extra ammo + if data.ammoType then + client:SetAmmo(0, data.ammoType) + end + + self:SetDonateWeaponCooldown(client, weaponClass) + + -- Логирование выдачи донат-оружия + end + + -- Default: pay by supply + local supplyCost = data.supplyPrice or 0 + local factionSupply = self:GetFactionSupply(faction) + + -- If weapon is free (supplyCost == 0), enforce per-player persistent cooldown + if supplyCost == 0 then + local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {} + local steam = client:SteamID() or tostring(client:SteamID64() or "unknown") + local last = tonumber(stamps[steam]) or 0 + local now = os.time() + local cd = tonumber(config.freeWeaponCooldown) or 0 + if cd > 0 and (now - last) < cd then + local remain = cd - (now - last) + return false, "Вы уже брали бесплатное оружие недавно. Подождите " .. tostring(remain) .. " сек." + end + -- don't write timestamp yet: write after successful give + else + if supplyCost > 0 then + if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end + self:AddFactionSupply(faction, -supplyCost) + end + end + -- Check category conflict: prevent two weapons from same category + local newCat = data.category or "" + if newCat ~= "" and not newCat:find("^tool") then + for _, wep in ipairs(client:GetWeapons()) do + if not IsValid(wep) then continue end + local wclass = wep:GetClass() + local wdata = config.weapons[wclass] + if wdata and wdata.category == newCat then + return false, "Вы уже имеете оружие этой категории: " .. newCat + end + end + end + + -- Check max ammo limit for launchers + if data.maxAmmo then + local totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round") + if totalAmmo >= data.maxAmmo then + return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")" + end + end + -- Check max count limit for weapons + if data.maxCount and client:HasWeapon(weaponClass) then + return false, "У вас уже есть это оружие" + end + + client:Give(weaponClass) + + -- Reset ammo for launchers to prevent extra ammo + if data.ammoType then + client:SetAmmo(0, data.ammoType) + end + -- If free weapon granted, save timestamp per-player + if supplyCost == 0 then + local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {} + local steam = client:SteamID() or tostring(client:SteamID64() or "unknown") + stamps[steam] = os.time() + _saveJSONData(self, "freeWeaponTimestamps", stamps) + end + + -- Логирование выдачи оружия из арсенала + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local weaponName = data.name or weaponClass + local factionName = ix.faction.Get(faction).name or tostring(faction) + local message = string.format("%s получил оружие '%s' из арсенала (стоимость: %d снабжения)", client:Nick(), weaponName, supplyCost) + serverlogsPlugin:AddLog("WEAPON_SPAWN", message, client, { + weaponClass = weaponClass, + weaponName = weaponName, + supplyCost = supplyCost, + faction = faction, + factionName = factionName + }) + end + + return true +end + +function PLUGIN:BuyAmmo(client, ammoType) + local char = client:GetCharacter() + if not char then return false, "Нет персонажа" end + + -- Запрет на покупку патронов для ракетниц + if ammoType == "rpg_round" or ammoType == "PanzerFaust3 Rocket" then + return false, "Патроны для этого типа оружия недоступны для покупки" + end + + local ammoData = config.ammo[ammoType] + if not ammoData then return false, "Тип патронов не найден" end + + local faction = char:GetFaction() + local supply = self:GetFactionSupply(faction) + + local price = ammoData.price or 0 + if price > 0 then + if supply < price then return false, "Недостаточно очков снабжения у фракции" end + self:AddFactionSupply(faction, -price) + end + + client:GiveAmmo(ammoData.amount or 30, ammoType, true) + return true +end + +function PLUGIN:ReturnWeapon(client, weaponClass) + local char = client:GetCharacter() + if not char then return false, "Нет персонажа" end + local data = config.weapons[weaponClass] + if not data then return false, "Оружие не найдено" end + + local faction = char:GetFaction() + -- Remove weapon + client:StripWeapon(weaponClass) + + -- Reset ammo for launchers to prevent extra ammo + if data.ammoType then + client:SetAmmo(0, data.ammoType) + end + + -- Если это донат-оружие, сбрасываем кулдаун + if data.donate == true then + local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {} + local steam = client:SteamID() or tostring(client:SteamID64() or "unknown") + if stamps[steam] and stamps[steam][weaponClass] then + stamps[steam][weaponClass] = nil + _saveJSONData(self, "donateWeaponTimestamps", stamps) + end + return true + end + + local refund = math.floor((data.supplyPrice or 0) * 0.8) + if refund > 0 then self:AddFactionSupply(faction, refund) end + return true +end + +function PLUGIN:SaveData() + local data = self:GetData() or {} + data.entities = {} + + for _, entity in ipairs(ents.FindByClass("ix_arsenal")) do + data.entities[#data.entities + 1] = { + class = "ix_arsenal", + pos = entity:GetPos(), + angles = entity:GetAngles() + } + end + + for _, entity in ipairs(ents.FindByClass("ix_ammobox")) do + data.entities[#data.entities + 1] = { + class = "ix_ammobox", + pos = entity:GetPos(), + angles = entity:GetAngles() + } + end + + for _, entity in ipairs(ents.FindByClass("ix_wardrobe")) do + data.entities[#data.entities + 1] = { + class = "ix_wardrobe", + pos = entity:GetPos(), + angles = entity:GetAngles() + } + end + + self:SetData(data) +end + +function PLUGIN:LoadData() + local data = self:GetData() or {} + local entities = data.entities or data -- fallback to compatibility if no 'entities' key + + for _, v in ipairs(entities) do + if not v.class then continue end + local entity = ents.Create(v.class or "ix_arsenal") + entity:SetPos(v.pos) + entity:SetAngles(v.angles) + entity:Spawn() + + local phys = entity:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + end +end + +-- Обработчик сетевых действий от клиента +if SERVER then + net.Receive("ixArsenalAction", function(len, ply) + local action = net.ReadString() + local id = net.ReadString() + local plugin = ix.plugin.list["arsenal"] + if not plugin then return end + + if IsRecruit(ply) then + ply:Notify("Новоприбывший не может пользоваться арсеналом.") + return + end + + if action == "buy_weapon" then + local method = net.ReadString() + local ok, msg = plugin:BuyWeapon(ply, id, method) + if not ok then + ply:Notify(msg or "Ошибка покупки оружия") + end + elseif action == "buy_ammo" then + local ok, msg = plugin:BuyAmmo(ply, id) + if not ok then + ply:Notify(msg or "Ошибка покупки патронов") + end + elseif action == "return_weapon" then + local ok, msg = plugin:ReturnWeapon(ply, id) + if not ok then + ply:Notify(msg or "Ошибка возврата оружия") + end + elseif action == "buy_armor" then + local method = net.ReadString() + local ok, msg = plugin:BuyArmor(ply, id, method) + if not ok then + ply:Notify(msg or "Ошибка покупки брони") + end + end + end) +end + +-- Buy armor implementation +function PLUGIN:BuyArmor(client, armorClass, payMethod) + local char = client:GetCharacter() + if not char then return false, "Нет персонажа" end + local aData = config.armor[armorClass] + if not aData then return false, "Броня не найдена" end + payMethod = payMethod or "supply" + + -- Check current armor: Armor() is the player's current armor value + local curArmor = tonumber(client:Armor()) or 0 + local giveAmount = tonumber(aData.amount) or 0 + if curArmor >= giveAmount then + return false, "Вы не можете купить броню — у вас уже есть броня этого уровня или выше" + end + + local faction = char:GetFaction() + + -- money path + if payMethod == "money" then + local price = aData.moneyPrice or 0 + if price <= 0 then return false, "Эту броню нельзя купить за деньги" end + if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end + char:TakeMoney(price) + client:SetArmor(giveAmount) + return true + end + + -- supply path + local supplyCost = aData.supplyPrice or 0 + local factionSupply = self:GetFactionSupply(faction) + if supplyCost > 0 then + if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end + self:AddFactionSupply(faction, -supplyCost) + end + + client:SetArmor(giveAmount) + return true +end + +function PLUGIN:PlayerLoadedCharacter(client, character, currentChar) + if not IsValid(client) then return end + timer.Simple(0.5, function() + if not IsValid(client) or not client:GetCharacter() then return end + local char = client:GetCharacter() + local bodygroups = char:GetData("bodygroups", {}) + local skin = char:GetData("skin", 0) + local patch = char:GetData("patchIndex", 1) + for idx, value in pairs(bodygroups) do + client:SetBodygroup(idx, value) + end + client:SetSkin(skin) + if self.config.patchIndex and self.config.patchMaterials then + local pMat = self.config.patchMaterials[patch] + if pMat then + client:SetSubMaterial(self.config.patchIndex, pMat) + else + client:SetSubMaterial(self.config.patchIndex, "") + end + end + hook.Run("PostPlayerLoadout", client) + end) +end + +function PLUGIN:PostPlayerLoadout(client) + local char = client:GetCharacter() + if not char then return end + local model = client:GetModel() + local bgs = char:GetData("bodygroups", {}) + local speedMod = 1 + local maxArmor = 100 + local ammoMod = 1 + local stats = self.config.wardrobeStats and self.config.wardrobeStats[model] + if stats then + for idx, val in pairs(bgs) do + if stats[idx] and stats[idx][val] then + local st = stats[idx][val] + if st.speed then speedMod = speedMod * st.speed end + if st.armor then maxArmor = maxArmor + st.armor end + if st.ammo then ammoMod = ammoMod * st.ammo end + end + end + end + client:SetWalkSpeed(ix.config.Get("walkSpeed") * speedMod) + client:SetRunSpeed(ix.config.Get("runSpeed") * speedMod) + client:SetMaxArmor(maxArmor) + char:SetData("ammoMod", ammoMod) +end + +function PLUGIN:EntityTakeDamage(target, dmginfo) + if not target:IsPlayer() then return end + local char = target:GetCharacter() + if not char then return end + local model = target:GetModel() + local bgs = char:GetData("bodygroups", {}) + local resist = 1 + local stats = self.config.wardrobeStats and self.config.wardrobeStats[model] + if stats then + for idx, val in pairs(bgs) do + if stats[idx] and stats[idx][val] then + local st = stats[idx][val] + if st.dmgResist then + resist = resist * st.dmgResist + end + end + end + end + if resist ~= 1 then + dmginfo:ScaleDamage(resist) + end +end + +function PLUGIN:InitPostEntity() + -- Уменьшаем задержку до 2 секунд для более быстрой очистки при старте + timer.Simple(2, function() + -- Загружаем сырые данные из стора напрямую + local currentData = _loadJSONData(self, "factionSupply") or {} + + -- Перебираем все записи в сторе. Это надежнее, чем идти по индексам фракций, + -- так как это затронет любые старые или "кривые" ключи в базе. + local changed = false + for key, value in pairs(currentData) do + local factionID = tonumber(key) + local rawVal = tonumber(value) or 0 + + if factionID and rawVal > 5000 then + local startVal = (self.config.startSupply and self.config.startSupply[factionID]) or 2000 + + -- Устанавливаем новое значение + currentData[key] = startVal + currentData[factionID] = startVal + changed = true + + print(string.format("[Arsenal] Автосброс при старте: фракция %s, было %d, стало %d", key, rawVal, startVal)) + end + end + + if changed then + _saveJSONData(self, "factionSupply", currentData) + end + end) +end + +function PLUGIN:PlayerSetHandsModel(client, ent) + local char = client:GetCharacter() + if not char then return end + local model = client:GetModel() + local bgs = char:GetData("bodygroups", {}) + local chands = self.config.wardrobeCHands and self.config.wardrobeCHands[model] + if chands then + for idx, val in pairs(bgs) do + if chands[idx] and chands[idx][val] then + local hData = chands[idx][val] + ent:SetModel(hData.model or "models/weapons/c_arms_cstrike.mdl") + ent:SetSkin(hData.skin or 0) + ent:SetBodyGroups(hData.bodygroups or "0000000") + return true + end + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/cl_file.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/cl_file.lua new file mode 100644 index 0000000..b54b52c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/cl_file.lua @@ -0,0 +1,113 @@ +function AdvDupe2.ReceiveFile(data, autoSave) + AdvDupe2.RemoveProgressBar() + if not data then + AdvDupe2.Notify("File was not saved! (No data)",NOTIFY_ERROR,5) + return + end + local path + if autoSave then + if(LocalPlayer():GetInfo("advdupe2_auto_save_overwrite")~="0")then + path = AdvDupe2.GetFilename(AdvDupe2.AutoSavePath, true) + else + path = AdvDupe2.GetFilename(AdvDupe2.AutoSavePath) + end + else + path = AdvDupe2.GetFilename(AdvDupe2.SavePath) + end + + path = AdvDupe2.SanitizeFilename(path) + local dupefile = file.Open(path, "wb", "DATA") + if not dupefile then + AdvDupe2.Notify("File was not saved! (Could not open file for writing)",NOTIFY_ERROR,5) + return + end + dupefile:Write(data) + dupefile:Close() + + local errored = false + if(LocalPlayer():GetInfo("advdupe2_debug_openfile")=="1")then + if(not file.Exists(path, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end + + local readFile = file.Open(path, "rb", "DATA") + if not readFile then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end + local readData = readFile:Read(readFile:Size()) + readFile:Close() + local success, dupe, _info, _moreinfo = AdvDupe2.Decode(readData) + if(success)then + AdvDupe2.Notify("DEBUG CHECK: File successfully opens. No EOF errors.") + else + AdvDupe2.Notify("DEBUG CHECK: " .. dupe, NOTIFY_ERROR) + errored = true + end + end + + local filename = string.StripExtension(string.GetFileFromFilename( path )) + if autoSave then + if(IsValid(AdvDupe2.FileBrowser.AutoSaveNode))then + local add = true + for i=1, #AdvDupe2.FileBrowser.AutoSaveNode.Files do + if(filename==AdvDupe2.FileBrowser.AutoSaveNode.Files[i].Label:GetText())then + add=false + break + end + end + if(add)then + AdvDupe2.FileBrowser.AutoSaveNode:AddFile(filename) + AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.AutoSaveNode) + end + end + else + AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode:AddFile(filename) + AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode) + end + if not errored then + AdvDupe2.Notify("File successfully saved!",NOTIFY_GENERIC, 5) + end +end + +net.Receive("AdvDupe2_ReceiveFile", function() + local autoSave = net.ReadBool() + net.ReadStream(nil, function(data) + AdvDupe2.ReceiveFile(data, autoSave) + end) +end) + +AdvDupe2.Uploading = false +function AdvDupe2.SendFile(name, data) + net.Start("AdvDupe2_ReceiveFile") + net.WriteString(name) + AdvDupe2.Uploading = net.WriteStream(data, function() + AdvDupe2.Uploading = nil + AdvDupe2.File = nil + AdvDupe2.RemoveProgressBar() + end) + net.SendToServer() +end + +function AdvDupe2.UploadFile(ReadPath, ReadArea) + if AdvDupe2.Uploading then AdvDupe2.Notify("Already opening file, please wait.", NOTIFY_ERROR) return end + if(ReadArea==0)then + ReadPath = AdvDupe2.DataFolder.."/"..ReadPath..".txt" + elseif(ReadArea==1)then + ReadPath = AdvDupe2.DataFolder.."/-Public-/"..ReadPath..".txt" + else + ReadPath = "adv_duplicator/"..ReadPath..".txt" + end + + if(not file.Exists(ReadPath, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end + + local read = file.Read(ReadPath) + if not read then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end + local name = string.Explode("/", ReadPath) + name = name[#name] + name = string.sub(name, 1, #name-4) + + local success, dupe, info, moreinfo = AdvDupe2.Decode(read) + if(success)then + AdvDupe2.SendFile(name, read) + + AdvDupe2.LoadGhosts(dupe, info, moreinfo, name) + else + AdvDupe2.Notify("File could not be decoded. ("..dupe..") Upload Canceled.", NOTIFY_ERROR) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/cl_ghost.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/cl_ghost.lua new file mode 100644 index 0000000..a7554de --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/cl_ghost.lua @@ -0,0 +1,353 @@ +function AdvDupe2.LoadGhosts(dupe, info, moreinfo, name, preview) + AdvDupe2.RemoveGhosts() + AdvDupe2.Ghosting = true + AdvDupe2.GhostToSpawn = {} + local count = 0 + local time, desc, date, creator + + if(info.ad1) then + local z = dupe.HeadEnt.Z + local Pos, Ang + + time = moreinfo.Time or "" + desc = info.Description or "" + date = info.Date or "" + creator = info.Creator or "" + + AdvDupe2.HeadEnt = dupe.HeadEnt.Index + AdvDupe2.HeadPos = dupe.HeadEnt.Pos + AdvDupe2.HeadZPos = z + AdvDupe2.HeadPos.Z = AdvDupe2.HeadPos.Z + z + + for k, v in pairs(dupe.Entities) do + if(v.SavedParentIdx) then + if(not v.BuildDupeInfo) then v.BuildDupeInfo = {} end + v.BuildDupeInfo.DupeParentID = v.SavedParentIdx + Pos = v.LocalPos + Ang = v.LocalAngle + else + Pos, Ang = nil, nil + end + + for i, p in pairs(v.PhysicsObjects) do + p.Pos = Pos or p.LocalPos + p.Pos.Z = p.Pos.Z - z + p.Angle = Ang or p.LocalAngle + p.LocalPos = nil + p.LocalAngle = nil + end + + v.LocalPos = nil + v.LocalAngle = nil + AdvDupe2.GhostToSpawn[count] = + { + Model = v.Model, + PhysicsObjects = v.PhysicsObjects + } + + if(AdvDupe2.HeadEnt == k) then + AdvDupe2.HeadEnt = count + end + + count = count + 1 + end + + AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + else + time = info.time or "" + desc = dupe.Description or "" + date = info.date or "" + creator = info.name or "" + + AdvDupe2.HeadEnt = dupe.HeadEnt.Index + AdvDupe2.HeadZPos = dupe.HeadEnt.Z + AdvDupe2.HeadPos = dupe.HeadEnt.Pos + AdvDupe2.HeadOffset = dupe.Entities[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = dupe.Entities[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + + for k, v in pairs(dupe.Entities) do + AdvDupe2.GhostToSpawn[count] = + { + Model = v.Model, + PhysicsObjects = v.PhysicsObjects + } + + if(AdvDupe2.HeadEnt == k) then + AdvDupe2.HeadEnt = count + end + + count = count + 1 + end + end + + if(not preview) then + AdvDupe2.Info.File:SetText("File: "..name) + AdvDupe2.Info.Creator:SetText("Creator: "..creator) + AdvDupe2.Info.Date:SetText("Date: "..date) + AdvDupe2.Info.Time:SetText("Time: "..time) + AdvDupe2.Info.Size:SetText("Size: "..string.NiceSize(tonumber(info.size) or 0)) + AdvDupe2.Info.Desc:SetText("Desc: "..(desc or "")) + AdvDupe2.Info.Entities:SetText("Entities: "..table.Count(dupe.Entities)) + AdvDupe2.Info.Constraints:SetText("Constraints: "..table.Count(dupe.Constraints)) + end + + AdvDupe2.StartGhosting() + AdvDupe2.Preview = preview +end + +function AdvDupe2.RemoveGhosts() + if(AdvDupe2.Ghosting) then + hook.Remove("Tick", "AdvDupe2_SpawnGhosts") + AdvDupe2.Ghosting = false + + if(not AdvDupe2.BusyBar) then + AdvDupe2.RemoveProgressBar() + end + end + + if(AdvDupe2.GhostEntities) then + for k, v in pairs(AdvDupe2.GhostEntities) do + if(IsValid(v))then + v:Remove() + end + end + end + + if(IsValid(AdvDupe2.HeadGhost))then + AdvDupe2.HeadGhost:Remove() + end + + AdvDupe2.CurrentGhost = 1 + AdvDupe2.HeadGhost = nil + AdvDupe2.GhostEntities = nil + AdvDupe2.Preview = false +end + +--Creates a ghost from the given entity's table +local function MakeGhostsFromTable(EntTable) + + if(not EntTable) then return end + if(not EntTable.Model or EntTable.Model:sub(-4,-1) ~= ".mdl") then + EntTable.Model = "models/error.mdl" + end + + local GhostEntity = ClientsideModel(EntTable.Model, RENDERGROUP_TRANSLUCENT) + + -- If there are too many entities we might not spawn.. + if not IsValid(GhostEntity) then + AdvDupe2.RemoveGhosts() + AdvDupe2.Notify("Too many entities to spawn ghosts!", NOTIFY_ERROR) + return + end + + GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA ) --Was broken, making ghosts invisible + GhostEntity:SetColor( Color(255, 255, 255, 150) ) + GhostEntity.Phys = EntTable.PhysicsObjects[0] + + if util.IsValidRagdoll(EntTable.Model) then + local ref, parents, angs = {}, {}, {} + + GhostEntity:SetupBones() + for k, v in pairs(EntTable.PhysicsObjects) do + local bone = GhostEntity:TranslatePhysBoneToBone(k) + local bonp = GhostEntity:GetBoneParent(bone) + if bonp == -1 then + ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR() + else + bonp = GhostEntity:TranslatePhysBoneToBone(GhostEntity:TranslateBoneToPhysBone(bonp)) + parents[bone] = bonp + ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR() * GhostEntity:GetBoneMatrix(bonp) + end + + local m = Matrix() m:SetAngles(v.Angle) + angs[bone] = m + end + + for bone, ang in pairs( angs ) do + if parents[bone] and angs[parents[bone]] then + local localrotation = angs[parents[bone]]:GetInverseTR() * ang + local m = ref[bone] * localrotation + GhostEntity:ManipulateBoneAngles(bone, m:GetAngles()) + else + local pos = GhostEntity:GetBonePosition(bone) + GhostEntity:ManipulateBonePosition(bone, -pos) + GhostEntity:ManipulateBoneAngles(bone, ref[bone]:GetAngles()) + end + end + end + + return GhostEntity +end + +local function StopGhosting() + AdvDupe2.Ghosting = false + hook.Remove( "Tick", "AdvDupe2_SpawnGhosts" ) + + if not BusyBar then AdvDupe2.RemoveProgressBar() end +end + +local function SpawnGhosts() + local ghostsPerTick = GetConVar( "advdupe2_ghost_rate" ):GetInt() + local ghostPercentLimit = GetConVar( "advdupe2_limit_ghost" ):GetFloat() + + local finalGhost = math.min( AdvDupe2.TotalGhosts, math.max( math.Round( (ghostPercentLimit / 100) * AdvDupe2.TotalGhosts ), 0 ) ) + local finalGhostInFrame = math.min( AdvDupe2.CurrentGhost + ghostsPerTick - 1, finalGhost ) + + for i = AdvDupe2.CurrentGhost, finalGhostInFrame do + local g = AdvDupe2.GhostToSpawn[i] + if g and i ~= AdvDupe2.HeadEnt then AdvDupe2.GhostEntities[i] = MakeGhostsFromTable( g ) end + end + AdvDupe2.CurrentGhost = finalGhostInFrame + 1 + + AdvDupe2.UpdateGhosts( true ) + if not AdvDupe2.BusyBar then + AdvDupe2.ProgressBar.Percent = (AdvDupe2.CurrentGhost / AdvDupe2.TotalGhosts) * 100 + end + + if AdvDupe2.CurrentGhost > finalGhost then + StopGhosting() + end +end + +net.Receive("AdvDupe2_SendGhosts", function(len, ply, len2) + AdvDupe2.RemoveGhosts() + AdvDupe2.GhostToSpawn = {} + AdvDupe2.HeadEnt = net.ReadInt(16) + AdvDupe2.HeadZPos = net.ReadFloat() + AdvDupe2.HeadPos = net.ReadVector() + + local cache = {} + for i = 1, net.ReadInt(16) do + cache[i] = net.ReadString() + end + + for i = 1, net.ReadInt(16) do + AdvDupe2.GhostToSpawn[i] = + { + Model = cache[net.ReadInt(16)], + PhysicsObjects = {} + } + + for k = 0, net.ReadInt(8) do + AdvDupe2.GhostToSpawn[i].PhysicsObjects[k] = + { + Angle = net.ReadAngle(), + Pos = net.ReadVector() + } + end + end + + AdvDupe2.CurrentGhost = 1 + AdvDupe2.GhostEntities = {} + AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt]) + AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost + AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn + + if(AdvDupe2.TotalGhosts > 1) then + AdvDupe2.Ghosting = true + + if(not AdvDupe2.BusyBar) then + AdvDupe2.InitProgressBar("Ghosting: ") + AdvDupe2.BusyBar = false + end + + hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts) + else + AdvDupe2.Ghosting = false + end +end) + +net.Receive("AdvDupe2_AddGhost", function(len, ply, len2) + local ghost = {Model = net.ReadString(), PhysicsObjects = {}} + for k = 0, net.ReadInt(8) do + ghost.PhysicsObjects[k] = {Angle = net.ReadAngle(), Pos = net.ReadVector()} + end + + AdvDupe2.GhostEntities[AdvDupe2.CurrentGhost] = MakeGhostsFromTable(ghost) + AdvDupe2.CurrentGhost = AdvDupe2.CurrentGhost + 1 +end) + +function AdvDupe2.StartGhosting() + AdvDupe2.RemoveGhosts() + if(not AdvDupe2.GhostToSpawn) then return end + AdvDupe2.CurrentGhost = 1 + AdvDupe2.GhostEntities = {} + AdvDupe2.Ghosting = true + AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt]) + AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost + AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn + + if AdvDupe2.TotalGhosts > 1 then + if not AdvDupe2.BusyBar then + AdvDupe2.InitProgressBar("Ghosting: ") + AdvDupe2.BusyBar = false + end + hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts) + else + AdvDupe2.Ghosting = false + end +end +net.Receive("AdvDupe2_StartGhosting", function() + AdvDupe2.StartGhosting() +end) + +net.Receive("AdvDupe2_RemoveGhosts", AdvDupe2.RemoveGhosts) + +--Update the ghost's postion and angles based on where the player is looking and the offsets +local Lheadpos, Lheadang = Vector(), Angle() +function AdvDupe2.UpdateGhosts(force) + if not IsValid(AdvDupe2.HeadGhost) then + AdvDupe2.RemoveGhosts() + AdvDupe2.Notify("Invalid ghost parent!", NOTIFY_ERROR) + return + end + + local trace = LocalPlayer():GetEyeTrace() + if (not trace.Hit) then return end + + local originpos, originang, headpos, headang + local worigin = GetConVar("advdupe2_offset_world"):GetBool() + if(GetConVar("advdupe2_original_origin"):GetBool())then + originang = Angle() + originpos = Vector(AdvDupe2.HeadPos) + headpos = AdvDupe2.HeadPos + AdvDupe2.HeadOffset + headang = AdvDupe2.HeadAngle + else + local hangle = worigin and Angle(0,0,0) or AdvDupe2.HeadAngle + local pz = math.Clamp(AdvDupe2.HeadZPos + GetConVar("advdupe2_offset_z"):GetFloat() or 0, -16000, 16000) + local ap = math.Clamp(GetConVar("advdupe2_offset_pitch"):GetFloat() or 0, -180, 180) + local ay = math.Clamp(GetConVar("advdupe2_offset_yaw" ):GetFloat() or 0, -180, 180) + local ar = math.Clamp(GetConVar("advdupe2_offset_roll" ):GetFloat() or 0, -180, 180) + originang = Angle(ap, ay, ar) + originpos = Vector(trace.HitPos); originpos.z = originpos.z + pz + headpos, headang = LocalToWorld(AdvDupe2.HeadOffset, hangle, originpos, originang) + end + + if math.abs(Lheadpos.x - headpos.x) > 0.01 or + math.abs(Lheadpos.y - headpos.y) > 0.01 or + math.abs(Lheadpos.z - headpos.z) > 0.01 or + math.abs(Lheadang.p - headang.p) > 0.01 or + math.abs(Lheadang.y - headang.y) > 0.01 or + math.abs(Lheadang.r - headang.r) > 0.01 or force then + + Lheadpos = headpos + Lheadang = headang + + AdvDupe2.HeadGhost:SetPos(headpos) + AdvDupe2.HeadGhost:SetAngles(headang) + + for k, ghost in ipairs(AdvDupe2.GhostEntities) do + local phys = ghost.Phys + + if phys then + local pos, ang = LocalToWorld(phys.Pos, phys.Angle, originpos, originang) + ghost:SetPos(pos) + ghost:SetAngles(ang) + end + end + + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/file_browser.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/file_browser.lua new file mode 100644 index 0000000..7c44dbe --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/file_browser.lua @@ -0,0 +1,1446 @@ +--[[ + Title: Adv. Dupe 2 File Browser + + Desc: Displays and interfaces with duplication files. + + Author: TB + + Version: 1.0 +]] + +local History = {} +local Narrow = {} + +local switch = true +local count = 0 + +local function AddHistory(txt) + txt = string.lower(txt) + local char1 = txt[1] + local char2 + for i = 1, #History do + char2 = History[i][1] + if (char1 == char2) then + if (History[i] == txt) then + return + end + elseif (char1 < char2) then + break + end + end + + table.insert(History, txt) + table.sort(History, function(a, b) return a < b end) +end + +local function NarrowHistory(txt, last) + txt = string.lower(txt) + local temp = {} + if (last <= #txt and last ~= 0 and #txt ~= 1) then + for i = 1, #Narrow do + if (Narrow[i][last + 1] == txt[last + 1]) then + table.insert(temp, Narrow[i]) + elseif (Narrow[i][last + 1] ~= '') then + break + end + end + else + local char1 = txt[1] + local char2 + for i = 1, #History do + char2 = History[i][1] + if (char1 == char2) then + if (#txt > 1) then + for k = 2, #txt do + if (txt[k] ~= History[i][k]) then + break + end + if (k == #txt) then + table.insert(temp, History[i]) + end + end + else + table.insert(temp, History[i]) + end + elseif (char1 < char2) then + break + end + end + end + + Narrow = temp +end + +local function tableSortNodes(tbl) + for k, v in ipairs(tbl) do tbl[k] = {string.lower(v.Label:GetText()), v} end + table.sort(tbl, function(a,b) return a[1] 22) then + parent.Info:SetText( + 'Are you sure that you want to delete \nthe FILE, "' .. + node.Label:GetText() .. '" \nfrom your CLIENT?') + else + parent.Info:SetText( + 'Are you sure that you want to delete \nthe FILE, "' .. + node.Label:GetText() .. '" from your CLIENT?') + end + parent.Info:SizeToContents() + parent.Info:SetVisible(true) + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + local path, area = GetNodePath(node) + if (area == 1) then + path = "-Public-/" .. path + end + if (area == 2) then + path = "adv_duplicator/" .. path .. ".txt" + else + path = AdvDupe2.DataFolder .. "/" .. path .. ".txt" + end + node.Control:RemoveNode(node) + file.Delete(path) + AdvDupe2.FileBrowser:Slide(false) + end + end) + end + else + if (root ~= "-Advanced Duplicator 1-") then + Menu:AddOption("Save", function() + if (parent.Expanding) then return end + parent.Submit:SetMaterial("icon16/page_save.png") + parent.Submit:SetTooltip("Save Duplication") + if (parent.FileName:GetValue() == "Folder_Name...") then + parent.FileName:SetText("File_Name...") + end + parent.Desc:SetVisible(true) + parent.Info:SetVisible(false) + parent.FileName.FirstChar = true + parent.FileName.PrevText = parent.FileName:GetValue() + parent.FileName:SetVisible(true) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnMousePressed() + parent.FileName:RequestFocus() + node.Control.ActionNode = node + parent.Expanding = true + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + local name = parent.FileName:GetValue() + if (name == "" or name == "File_Name...") then + AdvDupe2.Notify("Name field is blank.", NOTIFY_ERROR) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnGetFocus() + parent.FileName:RequestFocus() + return + end + local desc = parent.Desc:GetValue() + if (desc == "Description...") then + desc = "" + end + AdvDupe2.SavePath = GetFullPath(node) .. name + AddHistory(name) + if (game.SinglePlayer()) then + RunConsoleCommand("AdvDupe2_SaveFile", name, desc, GetNodePath(node)) + else + RunConsoleCommand("AdvDupe2_SaveFile", name) + end + AdvDupe2.FileBrowser:Slide(false) + end + parent.FileName.OnEnter = + function() + parent.FileName:KillFocus() + parent.Desc:SelectAllOnFocus(true) + parent.Desc.OnMousePressed() + parent.Desc:RequestFocus() + end + parent.Desc.OnEnter = parent.Submit.DoClick + end) + end + Menu:AddOption("New Folder", function() + if (parent.Expanding) then return end + parent.Submit:SetMaterial("icon16/folder_add.png") + parent.Submit:SetTooltip("Add new folder") + if (parent.FileName:GetValue() == "File_Name...") then + parent.FileName:SetText("Folder_Name...") + end + parent.Desc:SetVisible(false) + parent.Info:SetVisible(false) + parent.FileName.FirstChar = true + parent.FileName.PrevText = parent.FileName:GetValue() + parent.FileName:SetVisible(true) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnMousePressed() + parent.FileName:RequestFocus() + parent.Expanding = true + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() AddNewFolder(node) end + parent.FileName.OnEnter = parent.Submit.DoClick + end) + Menu:AddOption("Search", function() + parent.Submit:SetMaterial("icon16/find.png") + parent.Submit:SetTooltip("Search Files") + if (parent.FileName:GetValue() == "Folder_Name...") then + parent.FileName:SetText("File_Name...") + end + parent.Desc:SetVisible(false) + parent.Info:SetVisible(false) + parent.FileName.FirstChar = true + parent.FileName.PrevText = parent.FileName:GetValue() + parent.FileName:SetVisible(true) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnMousePressed() + parent.FileName:RequestFocus() + parent.Expanding = true + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + Search(node, string.lower(parent.FileName:GetValue())) + AddHistory(parent.FileName:GetValue()) + parent.FileName:SetVisible(false) + parent.Submit:SetMaterial("icon16/arrow_undo.png") + parent.Submit:SetTooltip("Return to Browser") + parent.Info:SetVisible(true) + parent.Info:SetText(#parent.Search.pnlCanvas.Files .. + ' files found searching for, "' .. + parent.FileName:GetValue() .. '"') + parent.Info:SizeToContents() + parent.Submit.DoClick = function() + parent.Search:Remove() + parent.Search = nil + parent.Browser:SetVisible(true) + AdvDupe2.FileBrowser:Slide(false) + parent.Cancel:SetVisible(true) + end + parent.Cancel:SetVisible(false) + end + parent.FileName.OnEnter = parent.Submit.DoClick + end) + if (node.Label:GetText()[1] ~= "-") then + Menu:AddOption("Delete", function() + parent.Submit:SetMaterial("icon16/bin_empty.png") + parent.Submit:SetTooltip("Delete Folder") + parent.FileName:SetVisible(false) + parent.Desc:SetVisible(false) + if (#node.Label:GetText() > 22) then + parent.Info:SetText( + 'Are you sure that you want to delete \nthe FOLDER, "' .. + node.Label:GetText() .. '" \nfrom your CLIENT?') + else + parent.Info:SetText( + 'Are you sure that you want to delete \nthe FOLDER, "' .. + node.Label:GetText() .. '" from your CLIENT?') + end + parent.Info:SizeToContents() + parent.Info:SetVisible(true) + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + local path, area = GetNodePath(node) + if (area == 1) then + path = "-Public-/" .. path + end + if (area == 2) then + path = "adv_duplicator/" .. path .. "/" + else + path = AdvDupe2.DataFolder .. "/" .. path .. "/" + end + node.Control:RemoveNode(node) + DeleteFilesInFolders(path) + AdvDupe2.FileBrowser:Slide(false) + end + end) + end + end + if (not node.Control.Search) then + Menu:AddSpacer() + Menu:AddOption("Collapse Folder", function() + if (node.ParentNode.ParentNode) then + node.ParentNode:SetExpanded(false) + end + end) + Menu:AddOption("Collapse Root", function() CollapseParentsComplete(node) end) + if (parent.Expanded) then + Menu:AddOption("Cancel Action", function() parent.Cancel:DoClick() end) + end + end + + Menu:Open() +end + +local function CollapseParents(node, val) + if (not node) then return end + node.ChildList:SetTall(node.ChildList:GetTall() - val) + CollapseParents(node.ParentNode, val) +end + +function BROWSER:RemoveNode(node) + local parent = node.ParentNode + parent.Nodes = parent.Nodes - 1 + if (node.IsFolder) then + if (node.m_bExpanded) then + CollapseParents(parent, node.ChildList:GetTall() + 20) + for i = 1, #parent.ChildrenExpanded do + if (node == parent.ChildrenExpanded[i]) then + table.remove(parent.ChildrenExpanded, i) + break + end + end + elseif (parent.m_bExpanded) then + CollapseParents(parent, 20) + end + for i = 1, #parent.Folders do + if (node == parent.Folders[i]) then + table.remove(parent.Folders, i) + end + end + node.ChildList:Remove() + node:Remove() + else + for i = 1, #parent.Files do + if (node == parent.Files[i]) then + table.remove(parent.Files, i) + end + end + CollapseParents(parent, 20) + node:Remove() + if (#parent.Files == 0 and #parent.Folders == 0) then + parent.Expander:Remove() + parent.Expander = nil + parent.m_bExpanded = false + end + end + if (self.VBar.Scroll > self.VBar.CanvasSize) then + self.VBar:SetScroll(self.VBar.Scroll) + end + if (self.m_pSelectedItem) then + self.m_pSelectedItem = nil + end +end + +function BROWSER:OnMouseWheeled(dlta) + return self.VBar:OnMouseWheeled(dlta) +end + +function BROWSER:AddFolder(text) + local node = vgui.Create("advdupe2_browser_folder", self) + node.Control = self + + node.Offset = 0 + node.ChildrenExpanded = {} + node.Icon:SetPos(18, 1) + node.Label:SetPos(44, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.ParentNode = self + node.IsFolder = true + self.Nodes = self.Nodes + 1 + node.Folders = {} + node.Files = {} + table.insert(self.Folders, node) + self:SetTall(self:GetTall() + 20) + + return node +end + +function BROWSER:AddFile(text) + local node = vgui.Create("advdupe2_browser_file", self) + node.Control = self + node.Offset = 0 + node.Icon:SetPos(18, 1) + node.Label:SetPos(44, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.ParentNode = self + self.Nodes = self.Nodes + 1 + table.insert(self.Files, node) + self:SetTall(self:GetTall() + 20) + + return node +end + +function BROWSER:Sort(node) + tableSortNodes(node.Folders) + tableSortNodes(node.Files) + + for i = 1, #node.Folders do + node.Folders[i]:SetParent(nil) + node.Folders[i]:SetParent(node.ChildList) + node.Folders[i].ChildList:SetParent(nil) + node.Folders[i].ChildList:SetParent(node.ChildList) + end + for i = 1, #node.Files do + node.Files[i]:SetParent(nil) + node.Files[i]:SetParent(node.ChildList) + end +end + +function BROWSER:SetSelected(node) + if (IsValid(self.m_pSelectedItem)) then + self.m_pSelectedItem:SetSelected(false) + end + self.m_pSelectedItem = node + if (node) then node:SetSelected(true) end +end + +local function ExpandParents(node, val) + if (not node) then return end + node.ChildList:SetTall(node.ChildList:GetTall() + val) + ExpandParents(node.ParentNode, val) +end + +function BROWSER:Expand(node) + node.ChildList:SetTall(node.Nodes * 20) + table.insert(node.ParentNode.ChildrenExpanded, node) + ExpandParents(node.ParentNode, node.Nodes * 20) +end + +local function ExtendParents(node) + if (not node) then return end + node.ChildList:SetTall(node.ChildList:GetTall() + 20) + ExtendParents(node.ParentNode) +end + +function BROWSER:Extend(node) + node.ChildList:SetTall(node.ChildList:GetTall() + 20) + ExtendParents(node.ParentNode) +end + +function BROWSER:Collapse(node) + CollapseParents(node.ParentNode, node.ChildList:GetTall()) + + for i = 1, #node.ParentNode.ChildrenExpanded do + if (node.ParentNode.ChildrenExpanded[i] == node) then + table.remove(node.ParentNode.ChildrenExpanded, i) + break + end + end + CollapseChildren(node) +end + +function BROWSER:RenameNode(name) + self.ActionNode.Label:SetText(name) + self.ActionNode.Label:SizeToContents() + self:Sort(self.ActionNode.ParentNode) +end + +function BROWSER:MoveNode(name) + self:RemoveNode(self.ActionNode) + self.ActionNode2:AddFile(name) + self:Sort(self.ActionNode2) +end + +function BROWSER:DeleteNode() + self:RemoveNode(self.ActionNode) +end + +derma.DefineControl("advdupe2_browser_tree", "AD2 File Browser", BROWSER, "Panel") + +local FOLDER = {} + +AccessorFunc(FOLDER, "m_bBackground", "PaintBackground", FORCE_BOOL) +AccessorFunc(FOLDER, "m_bgColor", "BackgroundColor") + +Derma_Hook(FOLDER, "Paint", "Paint", "Panel") + +function FOLDER:Init() + self:SetMouseInputEnabled(true) + + self:SetTall(20) + self:SetPaintBackground(true) + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + self:SetBackgroundColor(Color(0, 0, 0, 0)) + + self.Icon = vgui.Create("DImage", self) + self.Icon:SetImage("icon16/folder.png") + + self.Icon:SizeToContents() + + self.Label = vgui.Create("DLabel", self) + self.Label:SetDark(true) + + self.m_bExpanded = false + self.Nodes = 0 + self.ChildrenExpanded = {} + + self:Dock(TOP) + + self.ChildList = vgui.Create("Panel", self:GetParent()) + self.ChildList:Dock(TOP) + self.ChildList:SetTall(0) +end + +local function ExpandNode(self) + self:GetParent():SetExpanded() +end + +function FOLDER:AddFolder(text) + if (self.Nodes == 0) then + self.Expander = vgui.Create("DExpandButton", self) + self.Expander.DoClick = ExpandNode + self.Expander:SetPos(self.Offset, 2) + end + + local node = vgui.Create("advdupe2_browser_folder", self.ChildList) + node.Control = self.Control + + node.Offset = self.Offset + 20 + + node.Icon:SetPos(18 + node.Offset, 1) + node.Label:SetPos(44 + node.Offset, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.Label:SetDark(true) + node.ParentNode = self + node.IsFolder = true + node.Folders = {} + node.Files = {} + + self.Nodes = self.Nodes + 1 + self.Folders[#self.Folders + 1] = node + + if (self.m_bExpanded) then + self.Control:Extend(self) + end + + return node +end + +function FOLDER:Clear() + for _, node in ipairs(self.Folders) do + node:Remove() end + for _, node in ipairs(self.Files) do + node:Remove() end + self.Nodes = 0 +end + +function FOLDER:AddFile(text) + if (self.Nodes == 0) then + self.Expander = vgui.Create("DExpandButton", self) + self.Expander.DoClick = ExpandNode + self.Expander:SetPos(self.Offset, 2) + end + + local node = vgui.Create("advdupe2_browser_file", self.ChildList) + node.Control = self.Control + node.Offset = self.Offset + 20 + node.Icon:SetPos(18 + node.Offset, 1) + node.Label:SetPos(44 + node.Offset, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.Label:SetDark(true) + node.ParentNode = self + + self.Nodes = self.Nodes + 1 + table.insert(self.Files, node) + + if (self.m_bExpanded) then + self.Control:Extend(self) + end + + return node +end + + +function FOLDER:LoadDataFolder(folderPath) + self:Clear() + self.LoadingPath = folderPath + self.LoadingFiles, self.LoadingDirectories = file.Find(folderPath .. "*", "DATA", "nameasc") + if self.LoadingFiles == nil then self.LoadingFiles = {} end + if self.LoadingDirectories == nil then self.LoadingDirectories = {} end + self.FileI, self.DirI = 1, 1 + self.LoadingFirst = true +end + +function FOLDER:Think() + if self.LoadingPath then + local path, files, dirs, fileI, dirI = self.LoadingPath, self.LoadingFiles, self.LoadingDirectories, self.FileI, self.DirI + if dirI > #dirs then + if fileI > #files then + self.LoadingPath = nil + return + else + local fileName = files[fileI] + local fileNode = self:AddFile(string.StripExtension(fileName)) + fileI = fileI + 1 + end + else + local dirName = dirs[dirI] + local dirNode = self:AddFolder(dirName) + dirNode:LoadDataFolder(path .. dirName .. "/") + dirI = dirI + 1 + end + + self.FileI = fileI + self.DirI = dirI + + if self.LoadingFirst then + if self.LoadingPath == "advdupe2/" then self:SetExpanded(true) end + self.LoadingFirst = false + end + end +end + + +function FOLDER:SetExpanded(bool) + if (not self.Expander) then return end + if (bool == nil) then + self.m_bExpanded = not self.m_bExpanded + else + self.m_bExpanded = bool + end + self.Expander:SetExpanded(self.m_bExpanded) + if (self.m_bExpanded) then + self.Control:Expand(self) + else + self.Control:Collapse(self) + end +end + +function FOLDER:SetSelected(bool) + if (bool) then + self:SetBackgroundColor(self:GetSkin().bg_color_bright) + else + self:SetBackgroundColor(Color(0, 0, 0, 0)) + end +end + +function FOLDER:OnMousePressed(code) + if (code == 107) then + self.Control:DoNodeLeftClick(self) + elseif (code == 108) then + self.Control:DoNodeRightClick(self) + end +end + +derma.DefineControl("advdupe2_browser_folder", "AD2 Browser Folder node", FOLDER, "Panel") + +local FILE = {} + +AccessorFunc(FILE, "m_bBackground", "PaintBackground", FORCE_BOOL) +AccessorFunc(FILE, "m_bgColor", "BackgroundColor") +Derma_Hook(FILE, "Paint", "Paint", "Panel") + +function FILE:Init() + self:SetMouseInputEnabled(true) + + self:SetTall(20) + self:SetPaintBackground(true) + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + self:SetBackgroundColor(Color(0, 0, 0, 0)) + + self.Icon = vgui.Create("DImage", self) + self.Icon:SetImage("icon16/page.png") + + self.Icon:SizeToContents() + + self.Label = vgui.Create("DLabel", self) + self.Label:SetDark(true) + + self:Dock(TOP) +end + +function FILE:SetSelected(bool) + if (bool) then + self:SetBackgroundColor(self:GetSkin().bg_color_bright) + else + self:SetBackgroundColor(Color(0, 0, 0, 0)) + end +end + +function FILE:OnMousePressed(code) + if (code == 107) then + self.Control:DoNodeLeftClick(self) + elseif (code == 108) then + self.Control:DoNodeRightClick(self) + end +end + +derma.DefineControl("advdupe2_browser_file", "AD2 Browser File node", FILE, "Panel") + +local PANEL = {} +AccessorFunc(PANEL, "m_bBackground", "PaintBackground", FORCE_BOOL) +AccessorFunc(PANEL, "m_bgColor", "BackgroundColor") +Derma_Hook(PANEL, "Paint", "Paint", "Panel") +Derma_Hook(PANEL, "PerformLayout", "Layout", "Panel") + +function PANEL:PerformLayout() + if (self:GetWide() == self.LastX) then return end + local x = self:GetWide() + + if (self.Search) then + self.Search:SetWide(x) + end + + self.Browser:SetWide(x) + local x2, y2 = self.Browser:GetPos() + local BtnX = x - self.Help:GetWide() - 5 + self.Help:SetPos(BtnX, 3) + BtnX = BtnX - self.Refresh:GetWide() - 5 + self.Refresh:SetPos(BtnX, 3) + + BtnX = x - self.Submit:GetWide() - 15 + self.Cancel:SetPos(BtnX, self.Browser:GetTall() + 20) + BtnX = BtnX - self.Submit:GetWide() - 5 + self.Submit:SetPos(BtnX, self.Browser:GetTall() + 20) + + self.FileName:SetWide(BtnX - 10) + self.FileName:SetPos(5, self.Browser:GetTall() + 20) + self.Desc:SetWide(x - 10) + self.Desc:SetPos(5, self.Browser:GetTall() + 39) + self.Info:SetPos(5, self.Browser:GetTall() + 20) + + self.LastX = x +end + +local pnlorigsetsize +local function PanelSetSize(self, x, y) + if (not self.LaidOut) then + pnlorigsetsize(self, x, y) + + self.Browser:SetSize(x, y - 20) + self.Browser:SetPos(0, 20) + + if (self.Search) then + self.Search:SetSize(x, y - 20) + self.Search:SetPos(0, 20) + end + + self.LaidOut = true + else + pnlorigsetsize(self, x, y) + end + +end + +local function UpdateClientFiles() + + local pnlCanvas = AdvDupe2.FileBrowser.Browser.pnlCanvas + + for i = 1, 2 do + if (pnlCanvas.Folders[1]) then + pnlCanvas:RemoveNode(pnlCanvas.Folders[1]) + end + end + + local advdupe2 = pnlCanvas:AddFolder("Advanced Duplicator 2") + local advdupe1 = pnlCanvas:AddFolder("Advanced Duplicator 1") + + advdupe1:LoadDataFolder("adv_duplicator/") + advdupe2:LoadDataFolder("advdupe2/") + + if (pnlCanvas.Folders[2]) then + if (#pnlCanvas.Folders[2].Folders == 0 and #pnlCanvas.Folders[2].Files == 0) then + pnlCanvas:RemoveNode(pnlCanvas.Folders[2]) + end + + pnlCanvas.Folders[1]:SetParent(nil) + pnlCanvas.Folders[1]:SetParent(pnlCanvas.ChildList) + pnlCanvas.Folders[1].ChildList:SetParent(nil) + pnlCanvas.Folders[1].ChildList:SetParent(pnlCanvas.ChildList) + end + +end + +function PANEL:Init() + + AdvDupe2.FileBrowser = self + self.Expanded = false + self.Expanding = false + self.LastX = 0 + self.LastY = 0 + pnlorigsetsize = self.SetSize + self.SetSize = PanelSetSize + + self:SetPaintBackground(true) + self:SetPaintBackgroundEnabled(false) + self:SetBackgroundColor(self:GetSkin().bg_color_bright) + + self.Browser = vgui.Create("advdupe2_browser_panel", self) + UpdateClientFiles() + self.Refresh = vgui.Create("DImageButton", self) + self.Refresh:SetMaterial("icon16/arrow_refresh.png") + self.Refresh:SizeToContents() + self.Refresh:SetTooltip("Refresh Files") + self.Refresh.DoClick = function(button) UpdateClientFiles() end + + self.Help = vgui.Create("DImageButton", self) + self.Help:SetMaterial("icon16/help.png") + self.Help:SizeToContents() + self.Help:SetTooltip("Help Section") + self.Help.DoClick = function(btn) + local Menu = DermaMenu() + Menu:AddOption("Bug Reporting", function() + gui.OpenURL("https://github.com/wiremod/advdupe2/issues") + end) + Menu:AddOption("Controls", function() + gui.OpenURL("https://github.com/wiremod/advdupe2/wiki/Controls") + end) + Menu:AddOption("Commands", function() + gui.OpenURL( + "https://github.com/wiremod/advdupe2/wiki/Server-settings") + end) + Menu:Open() + end + + self.Submit = vgui.Create("DImageButton", self) + self.Submit:SetMaterial("icon16/page_save.png") + self.Submit:SizeToContents() + self.Submit:SetTooltip("Confirm Action") + self.Submit.DoClick = function() + self.Expanding = true + AdvDupe2.FileBrowser:Slide(false) + end + + self.Cancel = vgui.Create("DImageButton", self) + self.Cancel:SetMaterial("icon16/cross.png") + self.Cancel:SizeToContents() + self.Cancel:SetTooltip("Cancel Action") + self.Cancel.DoClick = function() + self.Expanding = true + AdvDupe2.FileBrowser:Slide(false) + end + + self.FileName = vgui.Create("DTextEntry", self) + self.FileName:SetAllowNonAsciiCharacters(true) + self.FileName:SetText("File_Name...") + self.FileName.Last = 0 + + self.FileName.OnEnter = function() + self.FileName:KillFocus() + self.Desc:SelectAllOnFocus(true) + self.Desc.OnMousePressed() + self.Desc:RequestFocus() + end + self.FileName.OnMousePressed = function() + self.FileName:OnGetFocus() + if (self.FileName:GetValue() == "File_Name..." or + self.FileName:GetValue() == "Folder_Name...") then + self.FileName:SelectAllOnFocus(true) + end + end + self.FileName:SetUpdateOnType(true) + self.FileName.OnTextChanged = function() + + if (self.FileName.FirstChar) then + if (string.lower(self.FileName:GetValue()[1] or "") == string.lower(input.LookupBinding("menu") or "q")) then + self.FileName:SetText(self.FileName.PrevText) + self.FileName:SelectAll() + self.FileName.FirstChar = false + else + self.FileName.FirstChar = false + end + end + + local new, changed = self.FileName:GetValue():gsub("[^%w_ ]", "") + if changed > 0 then + self.FileName:SetText(new) + self.FileName:SetCaretPos(#new) + end + if (#self.FileName:GetValue() > 0) then + NarrowHistory(self.FileName:GetValue(), self.FileName.Last) + local options = {} + if (#Narrow > 4) then + for i = 1, 4 do table.insert(options, Narrow[i]) end + else + options = Narrow + end + if (#options ~= 0 and #self.FileName:GetValue() ~= 0) then + self.FileName.HistoryPos = 0 + self.FileName:OpenAutoComplete(options) + self.FileName.Menu.Attempts = 1 + if (#Narrow > 4) then + self.FileName.Menu:AddOption("...", function() end) + end + elseif (IsValid(self.FileName.Menu)) then + self.FileName.Menu:Remove() + end + end + self.FileName.Last = #self.FileName:GetValue() + end + self.FileName.OnKeyCodeTyped = function(txtbox, code) + txtbox:OnKeyCode(code) + + if (code == KEY_ENTER and not txtbox:IsMultiline() and txtbox:GetEnterAllowed()) then + if (txtbox.HistoryPos == 5 and txtbox.Menu:ChildCount() == 5) then + if ((txtbox.Menu.Attempts + 1) * 4 < #Narrow) then + for i = 1, 4 do + txtbox.Menu:GetChild(i):SetText(Narrow[i + txtbox.Menu.Attempts * 4]) + end + else + txtbox.Menu:GetChild(5):Remove() + for i = 4, (txtbox.Menu.Attempts * 4 - #Narrow) * -1 + 1, -1 do + txtbox.Menu:GetChild(i):Remove() + end + + for i = 1, #Narrow - txtbox.Menu.Attempts * 4 do + txtbox.Menu:GetChild(i):SetText(Narrow[i + txtbox.Menu.Attempts * 4]) + end + end + txtbox.Menu:ClearHighlights() + txtbox.Menu:HighlightItem(txtbox.Menu:GetChild(1)) + txtbox.HistoryPos = 1 + txtbox.Menu.Attempts = txtbox.Menu.Attempts + 1 + return true + end + + if (IsValid(txtbox.Menu)) then + txtbox.Menu:Remove() + end + txtbox:FocusNext() + txtbox:OnEnter() + txtbox.HistoryPos = 0 + end + + if (txtbox.m_bHistory or IsValid(txtbox.Menu)) then + if (code == KEY_UP) then + txtbox.HistoryPos = txtbox.HistoryPos - 1; + if (txtbox.HistoryPos ~= -1 or txtbox.Menu:ChildCount() ~= 5) then + txtbox:UpdateFromHistory() + else + txtbox.Menu:ClearHighlights() + txtbox.Menu:HighlightItem(txtbox.Menu:GetChild(5)) + txtbox.HistoryPos = 5 + end + end + if (code == KEY_DOWN or code == KEY_TAB) then + txtbox.HistoryPos = txtbox.HistoryPos + 1; + if (txtbox.HistoryPos ~= 5 or txtbox.Menu:ChildCount() ~= 5) then + txtbox:UpdateFromHistory() + else + txtbox.Menu:ClearHighlights() + txtbox.Menu:HighlightItem(txtbox.Menu:GetChild(5)) + end + end + + end + end + self.FileName.OnValueChange = function() + if (self.FileName:GetValue() ~= "File_Name..." and + self.FileName:GetValue() ~= "Folder_Name...") then + local new, changed = self.FileName:GetValue():gsub("[^%w_ ]", "") + if changed > 0 then + self.FileName:SetText(new) + self.FileName:SetCaretPos(#new) + end + end + end + + self.Desc = vgui.Create("DTextEntry", self) + self.Desc.OnEnter = self.Submit.DoClick + self.Desc:SetText("Description...") + self.Desc.OnMousePressed = function() + self.Desc:OnGetFocus() + if (self.Desc:GetValue() == "Description...") then + self.Desc:SelectAllOnFocus(true) + end + end + + self.Info = vgui.Create("DLabel", self) + self.Info:SetVisible(false) + +end + +function PANEL:Slide(expand) + if (expand) then + if (self.Expanded) then + self:SetTall(self:GetTall() - 40) + self.Expanded = false + else + self:SetTall(self:GetTall() + 5) + end + else + if (not self.Expanded) then + self:SetTall(self:GetTall() + 40) + self.Expanded = true + else + self:SetTall(self:GetTall() - 5) + end + end + count = count + 1 + if (count < 9) then + timer.Simple(0.01, function() self:Slide(expand) end) + else + if (expand) then + self.Expanded = true + else + self.Expanded = false + end + self.Expanding = false + count = 0 + end +end + +function PANEL:GetFullPath(node) + return GetFullPath(node) +end + +function PANEL:GetNodePath(node) + return GetNodePath(node) +end + +if (game.SinglePlayer()) then + net.Receive("AdvDupe2_AddFile", function() + local asvNode = AdvDupe2.FileBrowser.AutoSaveNode + local actNode = AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode + if (net.ReadBool()) then + if (IsValid(asvNode)) then + local name = net.ReadString() + for iD = 1, #asvNode.Files do + if (name == asvNode.Files[i]) then return end + end + asvNode:AddFile(name) + asvNode.Control:Sort(asvNode) + end + else + actNode:AddFile(net.ReadString()) + actNode.Control:Sort(actNode) + end + end) +end + +vgui.Register("advdupe2_browser", PANEL, "Panel") diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sh_codec.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sh_codec.lua new file mode 100644 index 0000000..4e2810c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sh_codec.lua @@ -0,0 +1,564 @@ +--[[ + Title: Adv. Dupe 2 Codec + + Desc: Dupe encoder/decoder. + + Author: emspike + + Version: 2.0 +]] + +local REVISION = 5 +AdvDupe2.CodecRevision = REVISION +AdvDupe2.MaxDupeSize = 32e6 -- 32 MB + +include( "sh_codec_legacy.lua" ) +AddCSLuaFile( "sh_codec_legacy.lua" ) + +local pairs = pairs +local error = error +local Vector = Vector +local Angle = Angle +local format = string.format +local char = string.char +local concat = table.concat +local compress = util.Compress +local decompress = util.Decompress + +--[[ + Name: GenerateDupeStamp + Desc: Generates an info table. + Params: ply + Return: stamp +]] +function AdvDupe2.GenerateDupeStamp(ply) + local stamp = {} + stamp.name = ply:GetName() + stamp.time = os.date("%I:%M %p") + stamp.date = os.date("%d %B %Y") + stamp.timezone = os.date("%z") + hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp) + return stamp +end + +function AdvDupe2.SanitizeFilename(filename) + filename = string.gsub( filename, "[\":]", "_" ) + filename = string.gsub( filename, "%s+", " " ) + filename = string.gsub( filename, "%s*([\\/%.])%s*", "%1" ) + return filename +end + +local function makeInfo(tbl) + local info = "" + for k, v in pairs(tbl) do + info = concat{info,k,"\1",v,"\1"} + end + return info.."\2" +end + +local AD2FF = "AD2F%s\n%s\n%s" + +local tables, buff + +local function noserializer() end + +local enc = {} +for i = 1, 255 do enc[i] = noserializer end + +local function isArray(tbl) + local ret = true + local m = 0 + + for k, v in pairs(tbl) do + m = m + 1 + if k ~= m or enc[TypeID(v)] == noserializer then + ret = false + break + end + end + + return ret +end + +local function write(obj) + enc[TypeID(obj)](obj) +end + +local len, tables, tablesLookup + +enc[TYPE_TABLE] = function(obj) --table + if not tablesLookup[obj] then + tables = tables + 1 + tablesLookup[obj] = tables + else + buff:WriteByte(247) + buff:WriteShort(tablesLookup[obj]) + return + end + + if isArray(obj) then + buff:WriteByte(254) + for i, v in pairs(obj) do + write(v) + end + else + buff:WriteByte(255) + for k, v in pairs(obj) do + if(enc[TypeID(k)] ~= noserializer and enc[TypeID(v)] ~= noserializer) then + write(k) + write(v) + end + end + end + buff:WriteByte(246) +end + +enc[TYPE_BOOL] = function(obj) --boolean + buff:WriteByte(obj and 253 or 252) +end + +enc[TYPE_NUMBER] = function(obj) --number + buff:WriteByte(251) + buff:WriteDouble(obj) +end + +enc[TYPE_VECTOR] = function(obj) --vector + buff:WriteByte(250) + buff:WriteDouble(obj.x) + buff:WriteDouble(obj.y) + buff:WriteDouble(obj.z) +end + +enc[TYPE_ANGLE] = function(obj) --angle + buff:WriteByte(249) + buff:WriteDouble(obj.p) + buff:WriteDouble(obj.y) + buff:WriteDouble(obj.r) +end + +enc[TYPE_STRING] = function(obj) --string + len = #obj + if len < 246 then + buff:WriteByte(len) + buff:Write(obj) + else + buff:WriteByte(248) + buff:WriteULong(len) + buff:Write(obj) + end +end + +local function error_nodeserializer() + buff:Seek(buff:Tell()-1) + error(format("Couldn't find deserializer for type {typeid:%d}!", buff:ReadByte())) +end + +local reference = 0 +local read4, read5 + +do --Version 4 + local dec = {} + for i = 1, 255 do dec[i] = error_nodeserializer end + + local function read() + local tt = buff:ReadByte() + if not tt then + error("Expected value, got EOF!") + end + if tt == 0 then + return nil + end + return dec[tt]() + end + read4 = read + + dec[255] = function() --table + local t = {} + local k + reference = reference + 1 + local ref = reference + repeat + k = read() + if k ~= nil then + t[k] = read() + end + until (k == nil) + tables[ref] = t + return t + end + + dec[254] = function() --array + local t = {} + local k = 0 + local v + reference = reference + 1 + local ref = reference + repeat + k = k + 1 + v = read() + if(v ~= nil) then + t[k] = v + end + + until (v == nil) + tables[ref] = t + return t + end + + dec[253] = function() + return true + end + dec[252] = function() + return false + end + dec[251] = function() + return buff:ReadDouble() + end + dec[250] = function() + return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[249] = function() + return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[248] = function() --null-terminated string + local start = buff:Tell() + local slen = 0 + + while buff:ReadByte() ~= 0 do + slen = slen + 1 + end + + buff:Seek(start) + + local retv = buff:Read(slen) + if(not retv) then retv="" end + buff:ReadByte() + + return retv + end + dec[247] = function() --table reference + reference = reference + 1 + return tables[buff:ReadShort()] + end + + for i = 1, 246 do dec[i] = function() return buff:Read(i) end end +end + +do --Version 5 + local dec = {} + for i = 1, 255 do dec[i] = error_nodeserializer end + + local function read() + local tt = buff:ReadByte() + if not tt then + error("Expected value, got EOF!") + end + return dec[tt]() + end + read5 = read + + dec[255] = function() --table + local t = {} + reference = reference + 1 + tables[reference] = t + + for k in read do + t[k] = read() + end + + return t + end + + dec[254] = function() --array + local t = {} + reference = reference + 1 + tables[reference] = t + + local k = 1 + for v in read do + t[k] = v + k = k + 1 + end + + return t + end + + dec[253] = function() + return true + end + dec[252] = function() + return false + end + dec[251] = function() + return buff:ReadDouble() + end + dec[250] = function() + return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[249] = function() + return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[248] = function() -- Length>246 string + local slen = buff:ReadULong() + local retv = buff:Read(slen) + if(not retv) then retv = "" end + return retv + end + dec[247] = function() --table reference + return tables[buff:ReadShort()] + end + dec[246] = function() --nil + return + end + + for i = 1, 245 do dec[i] = function() return buff:Read(i) end end + + dec[0] = function() return "" end +end + +local function serialize(tbl) + tables = 0 + tablesLookup = {} + + buff = file.Open("ad2temp.txt", "wb", "DATA") + if not buff then error("Failed to open file data/ad2temp.txt for writing!") end + write(tbl) + buff:Close() + + buff = file.Open("ad2temp.txt","rb","DATA") + if not buff then error("Failed to open file data/ad2temp.txt for reading!") end + local ret = buff:Read(buff:Size()) + buff:Close() + return ret +end + + +local function deserialize(str, read) + + if(str == nil) then + error("File could not be decompressed!") + return {} + end + + tables = {} + reference = 0 + buff = file.Open("ad2temp.txt","wb","DATA") + if not buff then error("Failed to open file data/ad2temp.txt for writing!") end + buff:Write(str) + buff:Flush() + buff:Close() + + buff = file.Open("ad2temp.txt","rb", "DATA") + if not buff then error("Failed to open file data/ad2temp.txt for reading!") end + local success, tbl = pcall(read) + buff:Close() + + if success then + return tbl + else + error(tbl) + end +end + +--[[ + Name: Encode + Desc: Generates the string for a dupe file with the given data. + Params:
    dupe,
    info, callback, <...> args + Return: runs callback( encoded_dupe, <...> args) +]] +function AdvDupe2.Encode(dupe, info, callback, ...) + local encodedTable = compress(serialize(dupe)) + info.check = "\r\n\t\n" + info.size = #encodedTable + + callback(AD2FF:format(char(REVISION), makeInfo(info), encodedTable),...) +end + +--seperates the header and body and converts the header to a table +local function getInfo(str) + local last = str:find("\2") + if not last then + error("Attempt to read AD2 file with malformed info block!") + end + local info = {} + local ss = str:sub(1, last - 1) + for k, v in ss:gmatch("(.-)\1(.-)\1") do + info[k] = v + end + + if info.check ~= "\r\n\t\n" then + if info.check == "\10\9\10" then + error("Detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)!") + elseif info.check ~= nil then + error("Detected AD2 file corrupted by newline replacements (copy/pasting the data in various editors can cause this!)") + else + error("Attempt to read AD2 file with malformed info block!") + end + end + return info, str:sub(last+2) +end + +--decoders for individual versions go here +local versions = {} + +versions[1] = AdvDupe2.LegacyDecoders[1] +versions[2] = AdvDupe2.LegacyDecoders[2] + +versions[3] = function(encodedDupe) + encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n", "\n") + encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n") + return versions[4](encodedDupe) +end + +versions[4] = function(encodedDupe) + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize(decompress(dupestring, AdvDupe2.MaxDupeSize), read4), info +end + +versions[5] = function(encodedDupe) + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize(decompress(dupestring, AdvDupe2.MaxDupeSize), read5), info +end + +function AdvDupe2.CheckValidDupe(dupe, info) + if not dupe.HeadEnt then return false, "Missing HeadEnt table" end + if not dupe.Entities then return false, "Missing Entities table" end + if not dupe.Constraints then return false, "Missing Constraints table" end + if not dupe.HeadEnt.Z then return false, "Missing HeadEnt.Z" end + if not dupe.HeadEnt.Pos then return false, "Missing HeadEnt.Pos" end + if not dupe.HeadEnt.Index then return false, "Missing HeadEnt.Index" end + if not dupe.Entities[dupe.HeadEnt.Index] then return false, "Missing HeadEnt index ["..dupe.HeadEnt.Index.."] from Entities table" end + for key, data in pairs(dupe.Entities) do + if not data.PhysicsObjects then return false, "Missing PhysicsObject table from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end + if not data.PhysicsObjects[0] then return false, "Missing PhysicsObject[0] table from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end + if info.ad1 then -- Advanced Duplicator 1 + if not data.PhysicsObjects[0].LocalPos then return false, "Missing PhysicsObject[0].LocalPos from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end + if not data.PhysicsObjects[0].LocalAngle then return false, "Missing PhysicsObject[0].LocalAngle from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end + else -- Advanced Duplicator 2 + if not data.PhysicsObjects[0].Pos then return false, "Missing PhysicsObject[0].Pos from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end + if not data.PhysicsObjects[0].Angle then return false, "Missing PhysicsObject[0].Angle from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end + end + end + return true, dupe +end + +--[[ + Name: Decode + Desc: Generates the table for a dupe from the given string. Inverse of Encode + Params: encodedDupe, callback, <...> args + Return: runs callback( success,
    tbl,
    info) +]] +function AdvDupe2.Decode(encodedDupe) + + local sig, rev = encodedDupe:match("^(....)(.)") + + if not rev then + return false, "Malformed dupe (wtf <5 chars long)!" + end + + rev = rev:byte() + + if sig ~= "AD2F" then + if sig == "[Inf" then --legacy support, ENGAGE (AD1 dupe detected) + local success, tbl, info, moreinfo = pcall(AdvDupe2.LegacyDecoders[0], encodedDupe) + + if success then + info.ad1 = true + info.size = #encodedDupe + info.revision = 0 + + local index = tonumber(moreinfo.Head) or (istable(tbl.Entities) and next(tbl.Entities)) + if not index then return false, "Missing head index" end + local pos + if isstring(moreinfo.StartPos) then + local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$") + pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0) + else + pos = Vector() + end + local z + if isstring(moreinfo.HoldPos) then + z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1 + else + z = 0 + end + tbl.HeadEnt = { + Index = index, + Pos = pos, + Z = z + } + else + ErrorNoHalt(tbl) + end + + if success then + success, tbl = AdvDupe2.CheckValidDupe(tbl, info) + end + + return success, tbl, info, moreinfo + else + return false, "Unknown duplication format!" + end + elseif rev > REVISION then + return false, format("Newer codec needed. (have rev %u, need rev %u) Update Advdupe2.",REVISION,rev) + elseif rev < 1 then + return false, format("Attempt to use an invalid format revision (rev %d)!", rev) + else + local success, tbl, info = pcall(versions[rev], encodedDupe) + + if success then + success, tbl = AdvDupe2.CheckValidDupe(tbl, info) + end + if success then + info.revision = rev + end + + return success, tbl, info + end +end + +if CLIENT then + + concommand.Add("advdupe2_to_json", function(_,_,arg) + if not arg[1] then print("Need AdvDupe2 file name argument!") return end + local readFileName = "advdupe2/"..arg[1] + local writeFileName = "advdupe2/"..string.StripExtension(arg[1])..".json" + + writeFileName = AdvDupe2.SanitizeFilename(writeFileName) + + local readFile = file.Open(readFileName, "rb", "DATA") + if not readFile then print("File could not be read or found! ("..readFileName..")") return end + local readData = readFile:Read(readFile:Size()) + readFile:Close() + local ok, tbl = AdvDupe2.Decode(readData) + local writeFile = file.Open(writeFileName, "wb", "DATA") + if not writeFile then print("File could not be written! ("..writeFileName..")") return end + writeFile:Write(util.TableToJSON(tbl)) + writeFile:Close() + print("File written! ("..writeFileName..")") + end) + + concommand.Add("advdupe2_from_json", function(_,_,arg) + if not arg[1] then print("Need json file name argument!") return end + local readFileName = "advdupe2/"..arg[1] + local writeFileName = "advdupe2/"..string.StripExtension(arg[1])..".txt" + + local readFile = file.Open(readFileName, "rb", "DATA") + if not readFile then print("File could not be read or found! ("..readFileName..")") return end + local readData = readFile:Read(readFile:Size()) + readFile:Close() + + AdvDupe2.Encode(util.JSONToTable(readData), {}, function(data) + local writeFile = file.Open(writeFileName, "wb", "DATA") + if not writeFile then print("File could not be written! ("..writeFileName..")") return end + writeFile:Write(data) + writeFile:Close() + print("File written! ("..writeFileName..")") + end) + end) + +end + + diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sh_codec_legacy.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sh_codec_legacy.lua new file mode 100644 index 0000000..6158cd8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sh_codec_legacy.lua @@ -0,0 +1,559 @@ +--[[ + Title: Adv. Dupe 2 Codec Legacy Support + + Desc: Facilitates opening of dupes from AD1 and earlier AD2 versions. + + Author: emspike + + Version: 2.0 +]] + +local pairs = pairs +local type = type +local tonumber = tonumber +local error = error +local Vector = Vector +local Angle = Angle +local format = string.format +local char = string.char +local byte = string.byte +local sub = string.sub +local gsub = string.gsub +local find = string.find +local gmatch = string.gmatch +local match = string.match +local concat = table.concat + +--[[ + Name: GenerateDupeStamp + Desc: Generates an info table. + Params: ply + Return:
    stamp +]] +function AdvDupe2.GenerateDupeStamp(ply) + local stamp = {} + stamp.name = ply:GetName() + stamp.time = os.date("%I:%M %p") + stamp.date = os.date("%d %B %Y") + stamp.timezone = os.date("%z") + hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp) + return stamp +end + +local AD2FF = "AD2F%s\n%s\n%s" + +local decode_types_v1, decode_types_v2 +local tables = 0 +local str,pos +local a,b,c,m,n,w,tblref + +local function read_v2() + local t = byte(str, pos+1) + if t then + local dt = decode_types_v2[t] + if dt then + pos = pos + 1 + return dt() + else + error(format("encountered invalid data type (%u)\n",t)) + end + else + error("expected value, got EOF\n") + end +end + +decode_types_v2 = { + [1 ] = function() + error("expected value, got terminator\n") + end, + [2 ] = function() -- table + + m = find(str, "\1", pos) + if m then + w = sub(str, pos+1, m-1) + pos = m + else + error("expected table identifier, got EOF\n") + end + + local t = {} + tables[w] = t + + while true do + if byte(str, pos+1) == 1 then + pos = pos + 1 + return t + else + t[read_v2()] = read_v2() + end + end + end, + [3 ] = function() -- array + + m = find(str, "\1", pos) + if m then + w = sub(str, pos+1, m-1) + pos = m + else + error("expected table identifier, got EOF\n") + end + + local t, i = {}, 1 + + tables[w] = t + + while true do + if byte(str,pos+1) == 1 then + pos = pos+1 + return t + else + t[i] = read_v2() + i = i + 1 + end + end + end, + [4 ] = function() -- false boolean + return false + end, + [5 ] = function() -- true boolean + return true + end, + [6 ] = function() -- number + m = find(str, "\1", pos) + if m then + a = tonumber(sub(str, pos+1, m-1)) or 0 + pos = m + return a + else + error("expected number, got EOF\n") + end + end, + [7 ] = function() -- string + m = find(str,"\1",pos) + if m then + w = sub(str, pos+1, m-1) + pos = m + return w + else + error("expected string, got EOF\n") + end + end, + [8 ] = function() -- Vector + m,n = find(str,".-\1.-\1.-\1", pos) + if m then + a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Vector(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected vector, got EOF\n") + end + end, + [9 ] = function() -- Angle + m,n = find(str, ".-\1.-\1.-\1", pos) + if m then + a,b,c = match(str, "^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Angle(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected angle, got EOF\n") + end + end, + [10 ] = function() -- Table Reference + m = find(str,"\1",pos) + if m then + w = sub(str,pos+1,m-1) + pos = m + else + error("expected table identifier, got EOF\n") + end + tblref = tables[w] + + if tblref then + return tblref + else + error(format("table identifier %s points to nil\n", w)) + end + + end +} + + + +local function read_v1() + local t = byte(str,pos+1) + if t then + local dt = decode_types_v1[t] + if dt then + pos = pos + 1 + return dt() + else + error(format("encountered invalid data type (%u)\n",t)) + end + else + error("expected value, got EOF\n") + end +end + +decode_types_v1 = { + [1 ] = function() + error("expected value, got terminator\n") + end, + [2 ] = function() -- table + local t = {} + while true do + if byte(str,pos+1) == 1 then + pos = pos+1 + return t + else + t[read_v1()] = read_v1() + end + end + end, + [3 ] = function() -- array + local t, i = {}, 1 + while true do + if byte(str,pos+1) == 1 then + pos = pos+1 + return t + else + t[i] = read_v1() + i = i + 1 + end + end + end, + [4 ] = function() -- false boolean + return false + end, + [5 ] = function() -- true boolean + return true + end, + [6 ] = function() -- number + m = find(str,"\1",pos) + if m then + a = tonumber(sub(str,pos+1,m-1)) or 0 + pos = m + return a + else + error("expected number, got EOF\n") + end + end, + [7 ] = function() -- string + m = find(str,"\1",pos) + if m then + w = sub(str,pos+1,m-1) + pos = m + return w + else + error("expected string, got EOF\n") + end + end, + [8 ] = function() -- Vector + m,n = find(str,".-\1.-\1.-\1",pos) + if m then + a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Vector(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected vector, got EOF\n") + end + end, + [9 ] = function() -- Angle + m,n = find(str,".-\1.-\1.-\1",pos) + if m then + a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Angle(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected angle, got EOF\n") + end + end +} + +local function deserialize_v1(data) + str = data + pos = 0 + tables = {} + return read_v1() +end + +local function deserialize_v2(data) + str = data + pos = 0 + tables = {} + return read_v2() +end + +local function lzwDecode(encoded) + local dictionary_length = 256 + local dictionary = {} + for i = 0, 255 do + dictionary[i] = char(i) + end + + local pos = 2 + local decompressed = {} + local decompressed_length = 1 + + local index = byte(encoded) + local word = dictionary[index] + + decompressed[decompressed_length] = dictionary[index] + + local entry + local encoded_length = #encoded + local firstbyte --of an index + while pos <= encoded_length do + firstbyte = byte(encoded,pos) + if firstbyte > 252 then --now we know it's a length indicator for a multibyte index + index = 0 + firstbyte = 256 - firstbyte + + --[[if pos+firstbyte > encoded_length then --will test for performance impact + error("expected index got EOF") + end]] + + for i = pos+firstbyte, pos+1, -1 do + index = bit.bor(bit.lshift(index, 8), byte(encoded,i)) + end + pos = pos + firstbyte + 1 + else + index = firstbyte + pos = pos + 1 + end + entry = dictionary[index] or (word..sub(word,1,1)) + decompressed_length = decompressed_length + 1 + decompressed[decompressed_length] = entry + dictionary[dictionary_length] = word..sub(entry,1,1) + dictionary_length = dictionary_length + 1 + word = entry + end + return concat(decompressed) +end + +--http://en.wikipedia.org/wiki/Huffman_coding#Decompression + +local invcodes = {[2]={[0]="\254"},[5]={[22]="\1",[11]="\2"},[6]={[13]="\7",[35]="\6",[37]="\5",[58]="\3",[31]="\8",[9]="\13",[51]="\9",[55]="\10",[57]="\4",[59]="\15"},[7]={[1]="\14",[15]="\16",[87]="\31",[89]="\30",[62]="\26",[17]="\27",[97]="\19",[19]="\43",[10]="\12",[39]="\33",[41]="\24",[82]="\40",[3]="\32",[46]="\41",[47]="\38",[94]="\25",[65]="\23",[50]="\39",[26]="\11",[7]="\28",[33]="\18",[61]="\17",[25]="\42"},[8]={[111]="\101",[162]="\29",[2]="\34",[133]="\21",[142]="\36",[5]="\20",[21]="\37",[170]="\44",[130]="\22",[66]="\35"},[9]={[241]="\121",[361]="\104",[365]="\184",[125]="\227",[373]="\198",[253]="\117",[381]="\57",[270]="\49",[413]="\80",[290]="\47",[294]="\115",[38]="\112",[429]="\74",[433]="\0",[437]="\48",[158]="\183",[453]="\107",[166]="\111",[469]="\182",[477]="\241",[45]="\86",[489]="\69",[366]="\100",[497]="\61",[509]="\76",[49]="\53",[390]="\78",[279]="\196",[283]="\70",[414]="\98",[53]="\55",[422]="\109",[233]="\79",[349]="\89",[369]="\52",[14]="\105",[238]="\56",[319]="\162",[323]="\83",[327]="\63",[458]="\65",[335]="\231",[339]="\225",[337]="\114",[347]="\193",[493]="\139",[23]="\209",[359]="\250",[490]="\68",[42]="\54",[63]="\91",[286]="\97",[254]="\50",[510]="\108",[109]="\73",[67]="\103",[255]="\122",[69]="\170",[70]="\110",[407]="\176",[411]="\119",[110]="\120",[83]="\146",[149]="\163",[151]="\224",[85]="\51",[155]="\177",[79]="\251",[27]="\118",[447]="\159",[451]="\228",[455]="\175",[383]="\174",[463]="\243",[467]="\157",[173]="\210",[475]="\167",[177]="\84",[90]="\45",[487]="\206",[93]="\226",[495]="\245",[207]="\64",[127]="\147",[191]="\155",[511]="\153",[195]="\208",[197]="\85",[199]="\178",[181]="\82",[102]="\116",[103]="\71",[285]="\144",[105]="\102",[211]="\199",[213]="\123",[301]="\66",[305]="\46",[219]="\137",[81]="\67",[91]="\88",[157]="\130",[325]="\95",[29]="\58",[231]="\201",[117]="\99",[341]="\222",[237]="\77",[239]="\211",[71]="\223"},[10]={[710]="\149",[245]="\60",[742]="\172",[774]="\81",[134]="\151",[917]="\145",[274]="\216",[405]="\242",[146]="\194",[838]="\246",[298]="\248",[870]="\189",[1013]="\150",[894]="\190",[326]="\244",[330]="\166",[334]="\217",[465]="\179",[346]="\59",[354]="\180",[966]="\212",[974]="\143",[370]="\148",[998]="\154",[625]="\138",[382]="\161",[194]="\141",[198]="\126",[402]="\96",[206]="\185",[586]="\129",[721]="\187",[610]="\135",[618]="\181",[626]="\72",[226]="\62",[454]="\127",[658]="\113",[462]="\164",[234]="\214",[474]="\140",[242]="\106",[714]="\188",[730]="\87",[498]="\237",[746]="\125",[754]="\229",[786]="\128",[202]="\93",[18]="\255",[810]="\173",[846]="\131",[74]="\192",[842]="\142",[977]="\252",[858]="\235",[78]="\134",[874]="\234",[882]="\90",[646]="\92",[1006]="\160",[126]="\165",[914]="\221",[718]="\94",[738]="\238",[638]="\197",[482]="\230",[34]="\220",[962]="\133",[6]="\213",[706]="\219",[986]="\171",[994]="\233",[866]="\200",[1010]="\247",[98]="\169",[518]="\236",[494]="\207",[230]="\205",[542]="\191",[501]="\202",[530]="\203",[450]="\204",[209]="\158",[106]="\186",[590]="\136",[218]="\232",[733]="\124",[309]="\168",[221]="\152",[757]="\240",[113]="\215",[114]="\156",[362]="\239",[486]="\132",[358]="\249",[262]="\75",[30]="\218",[821]="\195",[546]="\253"}} + +local function huffmanDecode(encoded) + + local h1,h2,h3 = byte(encoded, 1, 3) + + if (not h3) or (#encoded < 4) then + error("invalid input") + end + + local original_length = bit.bor(bit.lshift(h3,16), bit.lshift(h2,8), h1) + local encoded_length = #encoded+1 + local decoded = {} + local decoded_length = 0 + local buffer = 0 + local buffer_length = 0 + local code + local code_len = 2 + local temp + local pos = 4 + + while decoded_length < original_length do + if code_len <= buffer_length then + temp = invcodes[code_len] + code = bit.band(buffer, bit.lshift(1, code_len)-1) + if temp and temp[code] then --most of the time temp is nil + decoded_length = decoded_length + 1 + decoded[decoded_length] = temp[code] + buffer = bit.rshift(buffer, code_len) + buffer_length = buffer_length - code_len + code_len = 2 + else + code_len = code_len + 1 + if code_len > 10 then + error("malformed code") + end + end + else + buffer = bit.bor(buffer, bit.lshift(byte(encoded, pos), buffer_length)) + buffer_length = buffer_length + 8 + pos = pos + 1 + if pos > encoded_length then + error("malformed code") + end + end + end + + return concat(decoded) +end + +local function invEscapeSub(str) + local escseq,body = match(str,"^(.-)\n(.-)$") + + if not escseq then error("invalid input") end + + return gsub(body,escseq,"\26") +end + +local dictionary +local subtables + +local function deserializeChunk(chunk) + + local ctype,val = byte(chunk),sub(chunk,3) + + if ctype == 89 then return dictionary[ val ] + elseif ctype == 86 then + local a,b,c = match(val,"^(.-),(.-),(.+)$") + return Vector( tonumber(a), tonumber(b), tonumber(c) ) + elseif ctype == 65 then + local a,b,c = match(val,"^(.-),(.-),(.+)$") + return Angle( tonumber(a), tonumber(b), tonumber(c) ) + elseif ctype == 84 then + local t = {} + local tv = subtables[val] + if not tv then + tv = {} + subtables[ val ] = tv + end + tv[#tv+1] = t + return t + elseif ctype == 78 then return tonumber(val) + elseif ctype == 83 then return gsub(sub(val,2,-2),"",";") + elseif ctype == 66 then return val == "t" + elseif ctype == 80 then return 1 + end + + error(format("AD1 deserialization failed: invalid chunk (%u:%s)",ctype,val)) + +end + +local function deserializeAD1(dupestring) + + dupestring = dupestring:Replace("\r\n", "\n") + local header, extraHeader, dupeBlock, dictBlock = dupestring:match("%[Info%]\n(.+)\n%[More Information%]\n(.+)\n%[Save%]\n(.+)\n%[Dict%]\n(.+)") + + if not header then + error("unknown duplication format") + end + + local info = {} + for k,v in header:gmatch("([^\n:]+):([^\n]+)") do + info[k] = v + end + + local moreinfo = {} + for k,v in extraHeader:gmatch("([^\n:]+):([^\n]+)") do + moreinfo[k] = v + end + + dictionary = {} + for k,v in dictBlock:gmatch("(.-):\"(.-)\"\n") do + dictionary[k] = v + end + + local dupe = {} + for key,block in dupeBlock:gmatch("([^\n:]+):([^\n]+)") do + + local tables = {} + subtables = {} + local head + + for id,chunk in block:gmatch('(%w+){(.-)}') do + + --check if this table is the trunk + if byte(id) == 72 then + id = sub(id,2) + head = id + end + + tables[id] = {} + + for kv in gmatch(chunk,'[^;]+') do + + local k,v = match(kv,'(.-)=(.+)') + + if k then + k = deserializeChunk( k ) + v = deserializeChunk( v ) + + tables[id][k] = v + else + v = deserializeChunk( kv ) + local tid = tables[id] + tid[#tid+1]=v + end + + end + end + + --Restore table references + for id,tbls in pairs( subtables ) do + for _,tbl in pairs( tbls ) do + if not tables[id] then error("attempt to reference a nonexistent table") end + for k,v in pairs(tables[id]) do + tbl[k] = v + end + end + end + + dupe[key] = tables[ head ] + + end + + return dupe, info, moreinfo + +end + +--seperates the header and body and converts the header to a table +local function getInfo(str) + local last = str:find("\2") + if not last then + error("attempt to read AD2 file with malformed info block error 1") + end + local info = {} + local ss = str:sub(1,last-1) + for k,v in ss:gmatch("(.-)\1(.-)\1") do + info[k] = v + end + if info.check ~= "\r\n\t\n" then + if info.check == "\10\9\10" then + error("detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)") + else + error("attempt to read AD2 file with malformed info block error 2") + end + end + return info, str:sub(last+2) +end + +--decoders for individual versions go here +local versions = {} + +versions[2] = function(encodedDupe) + encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n", "\n") + encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n") + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize_v2( + lzwDecode( + huffmanDecode( + invEscapeSub(dupestring) + ) + ) + ), info +end + +versions[1] = function(encodedDupe) + encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n", "\n") + encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n") + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize_v1( + lzwDecode( + huffmanDecode( + invEscapeSub(dupestring) + ) + ) + ), info +end + +versions[0] = deserializeAD1 + +AdvDupe2.LegacyDecoders = versions diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_clipboard.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_clipboard.lua new file mode 100644 index 0000000..e81dcfe --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_clipboard.lua @@ -0,0 +1,1694 @@ +--[[ + Title: Adv. Duplicator 2 Module + Desc: Provides advanced duplication functionality for the Adv. Dupe 2 tool. + Author: TB + Version: 1.0 +]] + +require( "duplicator" ) + +AdvDupe2.duplicator = {} + +AdvDupe2.JobManager = {} +AdvDupe2.JobManager.PastingHook = false +AdvDupe2.JobManager.Queue = {} + +local debugConvar = GetConVar("AdvDupe2_DebugInfo") + +local gtSetupTable = { + SERIAL = { + [TYPE_BOOL] = true, + [TYPE_ANGLE] = true, + [TYPE_TABLE] = true, + [TYPE_NUMBER] = true, + [TYPE_VECTOR] = true, + [TYPE_STRING] = true + }, + CONSTRAINT = { + Weld = true, + Axis = true, + Rope = true, + Motor = true, + Winch = true, + Muscle = true, + Pulley = true, + Slider = true, + Elastic = true, + Hydraulic = true, + Ballsocket = true + }, + COMPARE = { + V1 = Vector(1, 1, 1), + A0 = Angle (0, 0, 0), + V0 = Vector(0, 0, 0) + }, + POS = { + pos = true, + Pos = true, + position = true, + Position = true + }, + ANG = { + ang = true, + Ang = true, + angle = true, + Angle = true + }, + MODEL = { + model = true, + Model = true + }, + PLAYER = { + pl = true, + ply = true + }, + ENT1 = { + Ent = true, + Ent1 = true, + }, + TVEHICLE = { + VehicleTable = true + }, + SPECIAL = { + Data = true + } +} + +local function CopyClassArgTable(tab) + local done = {} + local function recursiveCopy(oldtable) + local newtable = {} + done[oldtable] = newtable + for k, v in pairs(oldtable) do + local varType = TypeID(v) + if gtSetupTable.SERIAL[varType] then + if varType == TYPE_TABLE then + if done[v] then + newtable[k] = done[v] + else + newtable[k] = recursiveCopy(v) + end + else + newtable[k] = v + end + else + if debugConvar:GetBool() then + print("[AdvDupe2] ClassArg table with key \"" .. tostring(k) .. "\" has unsupported value of type \"".. type(v) .."\"!") + end + end + end + return newtable + end + return recursiveCopy(tab) +end + +--[[ + Name: CopyEntTable + Desc: Returns a copy of the passed entity's table + Params: Ent + Returns:
    enttable +]] + +--[[--------------------------------------------------------- + Returns a copy of the passed entity's table +---------------------------------------------------------]] + +function AdvDupe2.duplicator.IsCopyable(ent) + local class = ent:GetClass() + if not duplicator.IsAllowed(class) or ent.DoNotDuplicate then return false end + + if not ent:GetPhysicsObject():IsValid() then + -- Support for rare cases when an entity does not have a physical object but still can be copied + if scripted_ents.GetMember(class, "Spawnable") == false and not duplicator.FindEntityClass(class) then return false end + if ent:IsWeapon() and ent:GetOwner():IsValid() then return false end + end + + return true +end + +local function CopyEntTable(Ent, Offset) + -- Filter duplicator blocked entities out. + if not AdvDupe2.duplicator.IsCopyable(Ent) then return nil end + + local Tab = {} + + if Ent.PreEntityCopy then + local status, valid = pcall(Ent.PreEntityCopy, Ent) + if (not status) then print("AD2 PreEntityCopy Error: " .. tostring(valid)) end + end + + local EntityClass = duplicator.FindEntityClass(Ent:GetClass()) + + local EntTable = Ent:GetTable() + + if EntityClass then + for iNumber, Key in pairs(EntityClass.Args) do + if gtSetupTable.SPECIAL[Key] then + Tab = CopyClassArgTable(EntTable) + end + -- Ignore keys from old system + if (not gtSetupTable.POS[Key] and + not gtSetupTable.ANG[Key] and + not gtSetupTable.MODEL[Key]) then + local varType = TypeID(EntTable[Key]) + if gtSetupTable.SERIAL[varType] then + if varType == TYPE_TABLE then + Tab[Key] = CopyClassArgTable(EntTable[Key]) + else + Tab[Key] = EntTable[Key] + end + elseif varType ~= TYPE_NIL and debugConvar:GetBool() then + print("[AdvDupe2] Entity ClassArg \"" .. Key .. "\" of type \"" .. Ent:GetClass() .. "\" has unsupported value of type \"" .. type(EntTable[Key]) .. "\"!\n") + end + end + end + end + + Tab.BoneMods = table.Copy(Ent.BoneMods) + if(Ent.EntityMods)then + Tab.EntityMods = table.Copy(Ent.EntityMods) + end + + if Ent.PostEntityCopy then + local status, valid = pcall(Ent.PostEntityCopy, Ent) + if(not status)then + print("AD2 PostEntityCopy Error: "..tostring(valid)) + end + end + + Tab.Pos = Ent:GetPos() + Tab.Class = Ent:GetClass() + Tab.Model = Ent:GetModel() + Tab.Skin = Ent:GetSkin() + Tab.CollisionGroup = Ent:GetCollisionGroup() + Tab.ModelScale = Ent:GetModelScale() + + if (Tab.Skin == 0) then Tab.Skin = nil end + if (Tab.ModelScale == 1) then Tab.ModelScale = nil end + + if(Tab.Class == "gmod_cameraprop")then + Tab.key = Ent:GetNetworkedInt("key") + end + + -- Allow the entity to override the class + -- This is a hack for the jeep, since it's real class is different from the one it reports as + -- (It reports a different class to avoid compatibility problems) + if Ent.ClassOverride then Tab.Class = Ent.ClassOverride end + + Tab.PhysicsObjects = {} + + -- Physics Objects + local PhysObj + for Bone = 0, Ent:GetPhysicsObjectCount() - 1 do + PhysObj = Ent:GetPhysicsObjectNum(Bone) + if IsValid(PhysObj) then + Tab.PhysicsObjects[Bone] = Tab.PhysicsObjects[Bone] or {} + if (PhysObj:IsMoveable()) then Tab.PhysicsObjects[Bone].Frozen = true end + PhysObj:EnableMotion(false) + Tab.PhysicsObjects[Bone].Pos = PhysObj:GetPos() - Tab.Pos + Tab.PhysicsObjects[Bone].Angle = PhysObj:GetAngles() + end + end + + if not Tab.PhysicsObjects[0] then Tab.PhysicsObjects[0] = {Angle = Ent:GetAngles()} end + Tab.PhysicsObjects[0].Pos = Tab.Pos - Offset + + Tab.Pos = nil + if (Tab.Class ~= "prop_physics") then + if (not Tab.BuildDupeInfo) then Tab.BuildDupeInfo = {} end + Tab.BuildDupeInfo.IsNPC = Ent:IsNPC() + Tab.BuildDupeInfo.IsVehicle = Ent:IsVehicle() + end + if (IsValid(Ent:GetParent())) then + if (not Tab.BuildDupeInfo) then Tab.BuildDupeInfo = {} end + Tab.PhysicsObjects[0].Angle = Ent:GetAngles() + Tab.BuildDupeInfo.DupeParentID = Ent:GetParent():EntIndex() + end + + -- Flexes + local FlexNum = Ent:GetFlexNum() + Tab.Flex = Tab.Flex or {} + local weight + local flexes + for i = 0, FlexNum do + weight = Ent:GetFlexWeight(i) + if (weight ~= 0) then + Tab.Flex[i] = weight + flexes = true + end + end + if (flexes or Ent:GetFlexScale() ~= 1) then + Tab.FlexScale = Ent:GetFlexScale() + else + Tab.Flex = nil + end + + -- Body Groups + Tab.BodyG = {} + for k, v in pairs(Ent:GetBodyGroups()) do + if ( Ent:GetBodygroup( v.id ) > 0 ) then + Tab.BodyG[ v.id ] = Ent:GetBodygroup( v.id ) + end + end + + if(next(Tab.BodyG) == nil)then + Tab.BodyG = nil + end + + -- Bone Manipulator + if (Ent:HasBoneManipulations()) then + Tab.BoneManip = {} + local c = gtSetupTable.COMPARE + for i = 0, Ent:GetBoneCount() do + local s = Ent:GetManipulateBoneScale(i) + s = ((s ~= c.V1) and s or nil) + local a = Ent:GetManipulateBoneAngles(i) + a = ((a ~= c.A0) and a or nil) + local p = Ent:GetManipulateBonePosition(i) + p = ((p ~= c.V0) and p or nil) + -- Avoid making a vector just to compare it + if (s or a or p) then + Tab.BoneManip[i] = {s = s, a = a, p = p} + end + end + end + + if Ent.GetNetworkVars then Tab.DT = Ent:GetNetworkVars() end + + -- Make this function on your SENT if you want to modify the + -- returned table specifically for your entity. + if Ent.OnEntityCopyTableFinish then + local status, valid = pcall(Ent.OnEntityCopyTableFinish, Ent, Tab) + if (not status) then + print("AD2 OnEntityCopyTableFinish Error: " .. tostring(valid)) + end + end + + return Tab + +end + +--[[ + Name: CopyConstraintTable + Desc: Create a table for constraints + Params:
    Constraints + Returns:
    Constraints,
    Entities +]] +local function CopyConstraintTable(Const, Offset) + if (Const == nil) then return nil, {} end + + -- Filter duplicator blocked constraints out. + if Const.DoNotDuplicate then return nil, {} end + + local Type = duplicator.ConstraintType[Const.Type] + if (not Type) then return nil, {} end + local Constraint = {} + local Entities = {} + + Const.Constraint = nil + Const.OnDieFunctions = nil + Constraint.Entity = {} + for k, key in pairs(Type.Args) do + if (key ~= "pl" and not string.find(key, "Ent") and not string.find(key, "Bone")) then + Constraint[key] = Const[key] + end + end + + if ((Const["Ent"] and Const["Ent"]:IsWorld()) or IsValid(Const["Ent"])) then + Constraint.Entity[1] = {} + Constraint.Entity[1].Index = Const["Ent"]:EntIndex() + if (not Const["Ent"]:IsWorld()) then table.insert(Entities, Const["Ent"]) end + Constraint.Type = Const.Type + if (Const.BuildDupeInfo) then Constraint.BuildDupeInfo = table.Copy(Const.BuildDupeInfo) end + else + for i = 1, 4 do + local ent = "Ent" .. i + local lpos = "LPos" .. i + local wpos = "WPos" .. i + + if ((Const[ent] and Const[ent]:IsWorld()) or IsValid(Const[ent])) then + Constraint.Entity[i] = {} + Constraint.Entity[i].Index = Const[ent]:EntIndex() + Constraint.Entity[i].Bone = Const["Bone" .. i] + Constraint.Entity[i].Length = Const["Length" .. i] + Constraint.Entity[i].World = Const["World" .. i] + + if Const[ent]:IsWorld() then + Constraint.Entity[i].World = true + if (Const[lpos]) then + if (i ~= 4 and i ~= 2) then + if (Const["Ent2"]) then + Constraint.Entity[i].LPos = Const[lpos] - Const["Ent2"]:GetPos() + Constraint[lpos] = Const[lpos] - Const["Ent2"]:GetPos() + elseif (Const["Ent4"]) then + Constraint.Entity[i].LPos = Const[lpos] - Const["Ent4"]:GetPos() + Constraint[lpos] = Const[lpos] - Const["Ent4"]:GetPos() + end + elseif (Const["Ent1"]) then + Constraint.Entity[i].LPos = Const[lpos] - Const["Ent1"]:GetPos() + Constraint[lpos] = Const[lpos] - Const["Ent1"]:GetPos() + end + else + Constraint.Entity[i].LPos = Offset + Constraint[lpos] = Offset + end + else + Constraint.Entity[i].LPos = Const[lpos] + Constraint.Entity[i].WPos = Const[wpos] + end + + if (not Const[ent]:IsWorld()) then table.insert(Entities, Const[ent]) end + end + + if (Const[wpos]) then + if (not Const["Ent1"]:IsWorld()) then + Constraint[wpos] = Const[wpos] - Const["Ent1"]:GetPos() + else + Constraint[wpos] = Const[wpos] - Const["Ent4"]:GetPos() + end + end + end + + Constraint.Type = Const.Type + if (Const.BuildDupeInfo) then + Constraint.BuildDupeInfo = table.Copy(Const.BuildDupeInfo) + end + end + return Constraint, Entities +end + +--[[ + Name: Copy + Desc: Copy an entity and all entities constrained + Params: Entity + Returns:
    Entities,
    Constraints +]] +local function Copy(ply, Ent, EntTable, ConstraintTable, Offset) + + local function RecursiveCopy(Ent) + local index = Ent:EntIndex() + if EntTable[index] then return end + + local cantool = Ent.CPPICanTool + if cantool and not cantool(Ent, ply, "advdupe2") then return end + + local EntData = CopyEntTable(Ent, Offset) + if EntData == nil then return end + EntTable[index] = EntData + + if Ent.Constraints then + for k, Constraint in pairs(Ent.Constraints) do + if Constraint:IsValid() then + index = Constraint:GetCreationID() + if index and not ConstraintTable[index] then + local ConstTable, EntTab = CopyConstraintTable(table.Copy(Constraint:GetTable()), Offset) + ConstraintTable[index] = ConstTable + for j, e in pairs(EntTab) do + if e and (e:IsWorld() or e:IsValid()) then + RecursiveCopy(e) + end + end + end + end + end + end + + do -- Wiremod Wire Connections + if istable(Ent.Inputs) then + for k, v in pairs(Ent.Inputs) do + if isentity(v.Src) and v.Src:IsValid() then + RecursiveCopy(v.Src) + end + end + end + + if istable(Ent.Outputs) then + for k, v in pairs(Ent.Outputs) do + if istable(v.Connected) then + for k, v in pairs(v.Connected) do + if isentity(v.Entity) and v.Entity:IsValid() then + RecursiveCopy(v.Entity) + end + end + end + end + end + end + + do -- Parented stuff + local parent = Ent:GetParent() + if IsValid(parent) then RecursiveCopy(parent) end + for k, child in pairs(Ent:GetChildren()) do + RecursiveCopy(child) + end + end + + for k, v in pairs(EntData.PhysicsObjects) do + local phys = Ent:GetPhysicsObjectNum(k) + + if IsValid(phys) then + phys:EnableMotion(v.Frozen) + end + end + end + RecursiveCopy(Ent) + + return EntTable, ConstraintTable +end +AdvDupe2.duplicator.Copy = Copy + +--[[ + Name: AreaCopy + Desc: Copy based on a box + Returns:
    Entities,
    Constraints +]] +function AdvDupe2.duplicator.AreaCopy(ply, Entities, Offset, CopyOutside) + local Constraints, EntTable, ConstraintTable = {}, {}, {} + local index, add, AddEnts, AddConstrs, ConstTable, EntTab + + for _, Ent in pairs(Entities) do + index = Ent:EntIndex() + EntTable[index] = CopyEntTable(Ent, Offset) + if (EntTable[index] ~= nil) then + + if (not constraint.HasConstraints(Ent)) then + for k, v in pairs(EntTable[Ent:EntIndex()].PhysicsObjects) do + local phys = Ent:GetPhysicsObjectNum(k) + + if IsValid(phys) then + phys:EnableMotion(v.Frozen) + end + end + else + for k, v in pairs(Ent.Constraints) do + -- Filter duplicator blocked constraints out. + if not v.DoNotDuplicate then + index = v:GetCreationID() + if (index and not Constraints[index]) then + Constraints[index] = v + end + end + end + + for k, v in pairs(EntTable[Ent:EntIndex()].PhysicsObjects) do + Ent:GetPhysicsObjectNum(k):EnableMotion(v.Frozen) + end + end + end + end + + for _, Constraint in pairs(Constraints) do + ConstTable, EntTab = CopyConstraintTable(table.Copy(Constraint:GetTable()), Offset) + -- If the entity is constrained to an entity outside of the area box, don't copy the constraint. + if (not CopyOutside) then + add = true + for k, v in pairs(EntTab) do + if (not Entities[v:EntIndex()]) then add = false end + end + if (add) then ConstraintTable[_] = ConstTable end + else -- Copy entities and constraints outside of the box that are constrained to entities inside the box + ConstraintTable[_] = ConstTable + for k, v in pairs(EntTab) do + Copy(ply, v, EntTable, ConstraintTable, Offset) + end + end + end + + return EntTable, ConstraintTable +end + +--[[ + Name: CreateConstraintFromTable + Desc: Creates a constraint from a given table + Params:
    Constraint,
    EntityList,
    EntityTable + Returns: CreatedConstraint +]] +local function CreateConstraintFromTable(Constraint, EntityList, EntityTable, Player, DontEnable) + local Factory = duplicator.ConstraintType[Constraint.Type] + if not Factory then return end + + -- Unfortunately we cannot distinguish here if this is a ropeconstraint or not + if Player and not Player:CheckLimit( "constraints" ) then return end + if Player and not Player:CheckLimit( "ropeconstraints" ) then return end + + local first, firstindex -- Ent1 or Ent in the constraint's table + local second, secondindex -- Any other Ent that is not Ent1 or Ent + local Args = {} -- Build the argument list for the Constraint's spawn function + for k, Key in ipairs(Factory.Args) do + + local Val = Constraint[Key] + + if gtSetupTable.PLAYER[Key] then Val = Player end + + for i = 1, 4 do + if (Constraint.Entity and Constraint.Entity[i]) then + if Key == "Ent" .. i or Key == "Ent" then + if (Constraint.Entity[i].World) then + Val = game.GetWorld() + else + Val = EntityList[Constraint.Entity[i].Index] + + if not IsValid(Val) then + if (Player) then + Player:ChatPrint("DUPLICATOR: ERROR, " .. Constraint.Type .. " Constraint could not find an entity!") + else + print("DUPLICATOR: ERROR, " .. Constraint.Type .. " Constraint could not find an entity!") + end + return + else + if (IsValid(Val:GetPhysicsObject())) then + Val:GetPhysicsObject():EnableMotion(false) + end + -- Important for perfect duplication + -- Get which entity is which so we can reposition them before constraining + if (gtSetupTable.ENT1[Key]) then + first = Val + firstindex = Constraint.Entity[i].Index + else + second = Val + secondindex = Constraint.Entity[i].Index + end + + end + end + + end + + if Key == "Bone" .. i or Key == "Bone" then + Val = Constraint.Entity[i].Bone or 0 + end + + if Key == "LPos" .. i then + if (Constraint.Entity[i].World and Constraint.Entity[i].LPos) then + if (i == 2 or i == 4) then + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[1].Index]:GetPos() + elseif (i == 1) then + if (Constraint.Entity[2]) then + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[2].Index]:GetPos() + else + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[4].Index]:GetPos() + end + end + elseif (Constraint.Entity[i].LPos) then + Val = Constraint.Entity[i].LPos + end + end + + if Key == "Length" .. i then + Val = Constraint.Entity[i].Length + end + end + if Key == "WPos" .. i then + if (not Constraint.Entity[1].World) then + Val = Constraint[Key] + EntityList[Constraint.Entity[1].Index]:GetPos() + else + Val = Constraint[Key] + EntityList[Constraint.Entity[4].Index]:GetPos() + end + end + + end + + Args[k] = Val + end + + local Bone1, Bone1Index, ReEnableFirst + local Bone2, Bone2Index, ReEnableSecond + local buildInfo = Constraint.BuildDupeInfo + if (buildInfo) then + + if first ~= nil and second ~= nil and not second:IsWorld() and buildInfo.EntityPos ~= nil then + local SecondPhys = second:GetPhysicsObject() + if IsValid(SecondPhys) then + if not DontEnable then ReEnableSecond = SecondPhys:IsMoveable() end + SecondPhys:EnableMotion(false) + second:SetPos(first:GetPos() - buildInfo.EntityPos) + if (buildInfo.Bone2) then + Bone2Index = buildInfo.Bone2 + Bone2 = second:GetPhysicsObjectNum(Bone2Index) + if IsValid(Bone2) then + Bone2:EnableMotion(false) + Bone2:SetPos(second:GetPos() + buildInfo.Bone2Pos) + Bone2:SetAngles(buildInfo.Bone2Angle) + end + end + end + end + + if first ~= nil and not first:IsWorld() and buildInfo.Ent1Ang ~= nil then + local FirstPhys = first:GetPhysicsObject() + if IsValid(FirstPhys) then + if not DontEnable then ReEnableFirst = FirstPhys:IsMoveable() end + FirstPhys:EnableMotion(false) + first:SetAngles(buildInfo.Ent1Ang) + if (buildInfo.Bone1) then + Bone1Index = buildInfo.Bone1 + Bone1 = first:GetPhysicsObjectNum(Bone1Index) + if IsValid(Bone1) then + Bone1:EnableMotion(false) + Bone1:SetPos(first:GetPos() + buildInfo.Bone1Pos) + Bone1:SetAngles(buildInfo.Bone1Angle) + end + end + end + end + + if second ~= nil and not second:IsWorld() then + if buildInfo.Ent2Ang ~= nil then + second:SetAngles(buildInfo.Ent2Ang) + elseif buildInfo.Ent4Ang ~= nil then + second:SetAngles(buildInfo.Ent4Ang) + end + end + end + + -- Pulley, Hydraulic can return up to 4 ents + local ok, Ent, Ent2, Ent3, Ent4 = pcall(Factory.Func, unpack(Args, 1, #Factory.Args)) + + if not ok or not Ent then + if Player then + AdvDupe2.Notify(Player, "ERROR, Failed to create " .. Constraint.Type .. " Constraint!", NOTIFY_ERROR) + else + print("DUPLICATOR: ERROR, Failed to create " .. Constraint.Type .. " Constraint!") + end + + return + end + + if Player then + -- Hacky way to determine if the constraint is a rope one, since we have no better way + local function IsRopeConstraint(ent) + return ent and ent:GetClass() == "keyframe_rope" + end + + local is_rope = IsRopeConstraint(Ent) or IsRopeConstraint(Ent2) or IsRopeConstraint(Ent3) or IsRopeConstraint(Ent4) + Player:AddCount(is_rope and "ropeconstraints" or "constraints", Ent) + end + + Ent.BuildDupeInfo = table.Copy(buildInfo) + + -- Move the entities back after constraining them. No point in moving the world though. + if (EntityTable) then + local fEnt = EntityTable[firstindex] + local sEnt = EntityTable[secondindex] + + if (first ~= nil and not first:IsWorld()) then + first:SetPos(fEnt.BuildDupeInfo.PosReset) + first:SetAngles(fEnt.BuildDupeInfo.AngleReset) + if (IsValid(Bone1) and Bone1Index ~= 0) then + Bone1:SetPos(fEnt.BuildDupeInfo.PosReset + + fEnt.BuildDupeInfo.PhysicsObjects[Bone1Index].Pos) + Bone1:SetAngles(fEnt.PhysicsObjects[Bone1Index].Angle) + end + + local FirstPhys = first:GetPhysicsObject() + if IsValid(FirstPhys) then + if ReEnableFirst then + FirstPhys:EnableMotion(true) + end + end + end + + if (second ~= nil and not second:IsWorld()) then + second:SetPos(sEnt.BuildDupeInfo.PosReset) + second:SetAngles(sEnt.BuildDupeInfo.AngleReset) + if (IsValid(Bone2) and Bone2Index ~= 0) then + Bone2:SetPos(sEnt.BuildDupeInfo.PosReset + + sEnt.BuildDupeInfo.PhysicsObjects[Bone2Index].Pos) + Bone2:SetAngles(sEnt.PhysicsObjects[Bone2Index].Angle) + end + + local SecondPhys = second:GetPhysicsObject() + if IsValid(SecondPhys) then + if ReEnableSecond then + SecondPhys:EnableMotion(true) + end + end + end + end + + if (Ent and Ent.length) then + Ent.length = Constraint["length"] + end -- Fix for weird bug with ropes + + return Ent +end + +local function ApplyEntityModifiers(Player, Ent) + if not Ent.EntityMods then return end + if Ent.EntityMods.trail then + Ent.EntityMods.trail.EndSize = math.Clamp(tonumber(Ent.EntityMods.trail.EndSize) or 0, 0, 1024) + Ent.EntityMods.trail.StartSize = math.Clamp(tonumber(Ent.EntityMods.trail.StartSize) or 0, 0, 1024) + end + + for Type, Data in SortedPairs(Ent.EntityMods) do + local ModFunction = duplicator.EntityModifiers[Type] + if (ModFunction) then + local ok, err = pcall(ModFunction, Player, Ent, Data) + if (not ok) then + if (Player) then + Player:ChatPrint('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) + else + print('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) + end + end + end + end + if (Ent.EntityMods["mass"] and duplicator.EntityModifiers["mass"]) then + local ok, err = pcall(duplicator.EntityModifiers["mass"], Player, Ent, Ent.EntityMods["mass"]) + if (not ok) then + if (Player) then + Player:ChatPrint('Error applying entity modifer, "mass". ERROR: ' .. err) + else + print('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) + end + end + end + if(Ent.EntityMods["buoyancy"] and duplicator.EntityModifiers["buoyancy"]) then + local ok, err = pcall(duplicator.EntityModifiers["buoyancy"], Player, Ent, Ent.EntityMods["buoyancy"]) + if (not ok) then + if (Player) then + Player:ChatPrint('Error applying entity modifer, "buoyancy". ERROR: ' .. err) + else + print('Error applying entity modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) + end + end + end +end + +local function ApplyBoneModifiers(Player, Ent) + if (not Ent.BoneMods or not Ent.PhysicsObjects) then return end + + for Type, ModFunction in pairs(duplicator.BoneModifiers) do + for Bone, Args in pairs(Ent.PhysicsObjects) do + if (Ent.BoneMods[Bone] and Ent.BoneMods[Bone][Type]) then + local PhysObj = Ent:GetPhysicsObjectNum(Bone) + if (Ent.PhysicsObjects[Bone]) then + local ok, err = pcall(ModFunction, Player, Ent, Bone, PhysObj, Ent.BoneMods[Bone][Type]) + if (not ok) then + Player:ChatPrint('Error applying bone modifer, "' .. tostring(Type) .. '". ERROR: ' .. err) + end + end + end + end + end +end + +--[[ + Name: DoGenericPhysics + Desc: Applies bone data, generically. + Params: Player,
    data + Returns: Entity,
    data +]] +local function DoGenericPhysics(entity, data, ply) + if not data.PhysicsObjects then return end + + for bone, args in pairs(data.PhysicsObjects) do + local phys = entity:GetPhysicsObjectNum(bone) + + if IsValid(phys) then + phys:SetPos(args.Pos) + phys:SetAngles(args.Angle) + phys:EnableMotion(false) + + if ply then + ply:AddFrozenPhysicsObject(entity, phys) + end + end + end +end + +--[[ + Name: GenericDuplicatorFunction + Desc: Override the default duplicator's GenericDuplicatorFunction function + Params:
    data, Player + Returns: Entity +]] +local strictConvar = GetConVar("AdvDupe2_Strict") + +local function GenericDuplicatorFunction(data, ply) + if not util.IsValidModel(data.Model) then + if ply then + net.Start("AdvDupe2_ReportModel") + net.WriteString(data.Model) + net.Send(ply) + else + print("Advanced Duplicator 2 Invalid Model: " .. data.Model) + end + + return + end + + local entity = ents.Create(data.Class) + + if not IsValid(entity) then + if ply then + net.Start("AdvDupe2_ReportClass") + net.WriteString(data.Class) + net.Send(ply) + else + print("Advanced Duplicator 2 Invalid Class: " .. data.Class) + end + + return + end + + duplicator.DoGeneric(entity, data) + if ply then entity:SetCreator(ply) end + + entity:Spawn() + entity:Activate() + + DoGenericPhysics(entity, data, ply) + + if not strictConvar:GetBool() then + table.Add(entity:GetTable(), data) + end + + return entity +end + +--[[ + Name: MakeProp + Desc: Make prop without spawn effects + Params: Player, Pos, Ang, Model,
    PhysicsObject,
    Data + Returns: Prop +]] +local function MakeProp(ply, pos, ang, model, physicsobject, data) + if data.ModelScale then data.ModelScale = math.Clamp(data.ModelScale, 1e-5, 1e5) end + + if not util.IsValidModel(model) then + if ply then + net.Start("AdvDupe2_ReportModel") + net.WriteString(model) + net.Send(ply) + else + print("Advanced Duplicator 2 Invalid Model: " .. model) + end + + return + end + + data.Pos = pos + data.Angle = ang + data.Model = model + data.Frozen = true + + if ply then + if not gamemode.Call("PlayerSpawnProp", ply, model) then + return + end + end + + local prop = ents.Create("prop_physics") + if not IsValid(prop) then return end + + duplicator.DoGeneric(prop, data) + + if ply then prop:SetCreator(ply) end + prop:Spawn() + + DoGenericPhysics(prop, data, ply) + + if data.Flex then + duplicator.DoFlex(prop, data.Flex, data.FlexScale) + end + + return prop +end + +local function RestoreBodyGroups(ent, BodyG) + for k, v in pairs(BodyG) do + ent:SetBodygroup(k, v) + end +end + +--[[ + Name: CreateEntityFromTable + Desc: Creates an entity from a given table + Params:
    EntTable, Player + Returns: nil +]] +local function IsAllowed(Player, Class, EntityClass) + if (scripted_ents.GetMember(Class, "DoNotDuplicate")) then return false end + + if (IsValid(Player) and not Player:IsAdmin()) then + if not duplicator.IsAllowed(Class) then return false end + + local weapon = list.GetForEdit("Weapon")[Class] + + if weapon then + if (not weapon.Spawnable) then return false end + if (weapon.AdminOnly) then return false end + else + if (not scripted_ents.GetMember(Class, "Spawnable") and not EntityClass) then return false end + if (scripted_ents.GetMember(Class, "AdminOnly")) then return false end + end + end + + return true +end + +local function CreateEntityFromTable(EntTable, Player) + hook.Run("AdvDupe2_PreCreateEntity", EntTable, Player) + + local EntityClass = duplicator.FindEntityClass(EntTable.Class) + if not IsAllowed(Player, EntTable.Class, EntityClass) then + Player:ChatPrint([[Entity Class Black listed, "]] .. EntTable.Class .. [["]]) + return nil + end + + local canCreate, blockReason = hook.Run( "AdvDupe2_CanCreateEntity", Player, EntTable.Class ) + if canCreate == false then + local msg = [[Entity Class, "]] .. EntTable.Class .. [[" is Blocked! ]] + if isstring( blockReason ) then -- allow nil blockReason + msg = msg .. blockReason + + end + Player:ChatPrint( msg ) + return nil + end + + local sent = false + local status, valid + local GENERIC = false + local CreatedEntities = {} + + -- This class is unregistered. Instead of failing try using a generic + -- Duplication function to make a new copy. + if (not EntityClass) then + GENERIC = true + sent = true + + if Player then + if(EntTable.Class=="prop_effect")then + sent = gamemode.Call( "PlayerSpawnEffect", Player, EntTable.Model) + else + local weapon = list.Get("Weapon")[EntTable.Class] + + if weapon then + sent = gamemode.Call("PlayerSpawnSWEP", Player, EntTable.Class, weapon) + else + sent = gamemode.Call("PlayerSpawnSENT", Player, EntTable.Class) + end + end + else + sent = true + end + + if (sent == false) then + print("Advanced Duplicator 2: Creation rejected for class, : " .. EntTable.Class) + return nil + else + sent = true + end + + if IsAllowed(Player, EntTable.Class, EntityClass) then + status, valid = pcall(GenericDuplicatorFunction, EntTable, Player) + else + print("Advanced Duplicator 2: ENTITY CLASS IS BLACKLISTED, CLASS NAME: " .. EntTable.Class) + return nil + end + end + + if (not GENERIC) then + + -- Build the argument list for the Entitie's spawn function + local ArgList, Arg = {} + + for iNumber, Key in pairs(EntityClass.Args) do + + Arg = nil + -- Translate keys from old system + if (gtSetupTable.POS[Key]) then Key = "Pos" end + if (gtSetupTable.ANG[Key]) then Key = "Angle" end + if (gtSetupTable.MODEL[Key]) then Key = "Model" end + if (gtSetupTable.TVEHICLE[Key] and EntTable[Key] and EntTable[Key].KeyValues) then + EntTable[Key].KeyValues = { + limitview = EntTable[Key].KeyValues.limitview, + vehiclescript = EntTable[Key].KeyValues.vehiclescript + } + end + + Arg = EntTable[Key] + + -- Special keys + if (gtSetupTable.SPECIAL[Key]) then + Arg = EntTable + end + + ArgList[iNumber] = Arg + + end + + -- Create and return the entity + if (EntTable.Class == "prop_physics") then + valid = MakeProp(Player, unpack(ArgList, 1, #EntityClass.Args)) -- Create prop_physics like this because if the model doesn't exist it will cause + elseif IsAllowed(Player, EntTable.Class, EntityClass) then + -- Create sents using their spawn function with the arguments we stored earlier + sent = true + + if Player then + if (not EntTable.BuildDupeInfo.IsVehicle and not EntTable.BuildDupeInfo.IsNPC and EntTable.Class ~= "prop_ragdoll" and EntTable.Class ~= "prop_effect") then + local weapon = list.Get("Weapon")[EntTable.Class] + + if weapon then + sent = gamemode.Call("PlayerSpawnSWEP", Player, EntTable.Class, weapon) + else + sent = gamemode.Call("PlayerSpawnSENT", Player, EntTable.Class) + end + end + else + sent = true + end + + if (sent == false) then + print("Advanced Duplicator 2: Creation rejected for class, : " .. EntTable.Class) + return nil + else + sent = true + end + + hook.Add( "OnEntityCreated", "AdvDupe2_GetLastEntitiesCreated", function( ent ) + table.insert( CreatedEntities, ent ) + end ) + + status, valid = xpcall(EntityClass.Func, ErrorNoHaltWithStack, Player, unpack(ArgList, 1, #EntityClass.Args)) + + hook.Remove( "OnEntityCreated", "AdvDupe2_GetLastEntitiesCreated" ) + else + print("Advanced Duplicator 2: ENTITY CLASS IS BLACKLISTED, CLASS NAME: " .. EntTable.Class) + return nil + end + end + + -- If its a valid entity send it back to the entities list so we can constrain it + if (status ~= false and IsValid(valid)) then + if (sent) then + local iNumPhysObjects = valid:GetPhysicsObjectCount() + local PhysObj + if (Player) then + for Bone = 0, iNumPhysObjects - 1 do + PhysObj = valid:GetPhysicsObjectNum(Bone) + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + Player:AddFrozenPhysicsObject(valid, PhysObj) + end + end + else + for Bone = 0, iNumPhysObjects - 1 do + PhysObj = valid:GetPhysicsObjectNum(Bone) + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + end + end + end + if (EntTable.Skin) then valid:SetSkin(EntTable.Skin) end + if (EntTable.BodyG) then RestoreBodyGroups(valid, EntTable.BodyG) end + + if valid.RestoreNetworkVars then + valid:RestoreNetworkVars(EntTable.DT) + end + + if GENERIC and Player then + if(EntTable.Class=="prop_effect")then + gamemode.Call("PlayerSpawnedEffect", Player, valid:GetModel(), valid) + elseif valid:IsWeapon() then + gamemode.Call("PlayerSpawnedSWEP", Player, valid) + else + gamemode.Call("PlayerSpawnedSENT", Player, valid) + end + end + + elseif (Player) then + gamemode.Call("PlayerSpawnedProp", Player, valid:GetModel(), valid) + end + + return valid + else + if (status == false) then + print("Advanced Duplicator 2: Error creating entity, removing last created entities") + for _, CreatedEntity in pairs(CreatedEntities) do + SafeRemoveEntity(CreatedEntity) + end + end + + if (valid == false) then + return false + else + return nil + end + end +end + +--[[ + Name: Paste + Desc: Override the default duplicator's paste function + Params: Player,
    Entities,
    Constraints + Returns:
    Entities,
    Constraints +]] +function AdvDupe2.duplicator.Paste(Player, EntityList, ConstraintList, Position, AngleOffset, OrigPos, Parenting) + + local CreatedEntities = {} + -- + -- Create entities + -- + local proppos + DisablePropCreateEffect = true + for k, v in pairs(EntityList) do + if (not v.BuildDupeInfo) then v.BuildDupeInfo = {} end + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + proppos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PhysicsObjects[0].Pos = Vector(0, 0, 0) + if (OrigPos) then + for i, p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos = p.Pos + proppos + OrigPos + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.PosReset = v.Pos + v.BuildDupeInfo.AngleReset = v.Angle + else + for i, p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos, v.PhysicsObjects[i].Angle = + LocalToWorld(p.Pos + proppos, p.Angle, Position, AngleOffset) + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PosReset = v.Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.AngleReset = v.Angle + end + + AdvDupe2.SpawningEntity = true + local Ent = CreateEntityFromTable(v, Player) + AdvDupe2.SpawningEntity = false + + if Ent then + if (Player) then Player:AddCleanup("AdvDupe2", Ent) end + Ent.BoneMods = table.Copy(v.BoneMods) + Ent.EntityMods = table.Copy(v.EntityMods) + Ent.PhysicsObjects = table.Copy(v.PhysicsObjects) + if (v.CollisionGroup) then Ent:SetCollisionGroup(v.CollisionGroup) end + if (Ent.OnDuplicated) then Ent:OnDuplicated(v) end + ApplyEntityModifiers(Player, Ent) + ApplyBoneModifiers(Player, Ent) + Ent.SolidMod = not Ent:IsSolid() + Ent:SetNotSolid(true) + elseif (Ent == false) then + Ent = nil + -- ConstraintList = {} + -- break + else + Ent = nil + end + CreatedEntities[k] = Ent + end + + local CreatedConstraints, Entity = {} + -- + -- Create constraints + -- + for k, Constraint in pairs(ConstraintList) do + Entity = CreateConstraintFromTable(Constraint, CreatedEntities, EntityList, Player) + if (IsValid(Entity)) then + table.insert(CreatedConstraints, Entity) + end + end + + if (Player) then + local undotxt = "AdvDupe2"..(Player.AdvDupe2.Name and (": ("..tostring(Player.AdvDupe2.Name)..")") or "") + + undo.Create(undotxt) + for _, v in pairs(CreatedEntities) do + -- If the entity has a PostEntityPaste function tell it to use it now + if v.PostEntityPaste then + local status, valid = pcall(v.PostEntityPaste, v, Player, v, CreatedEntities) + if (not status) then + print("AD2 PostEntityPaste Error: " .. tostring(valid)) + end + end + + if IsValid(v:GetPhysicsObject()) then + v:GetPhysicsObject():EnableMotion(false) + end + + if (EntityList[_].BuildDupeInfo.DupeParentID and Parenting) then + v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) + end + v:SetNotSolid(v.SolidMod) + undo.AddEntity(v) + end + undo.SetPlayer(Player) + undo.Finish() + + -- if(Tool)then AdvDupe2.FinishPasting(Player, true) end + else + + for _, v in pairs(CreatedEntities) do + -- If the entity has a PostEntityPaste function tell it to use it now + if v.PostEntityPaste then + local status, valid = pcall(v.PostEntityPaste, v, Player, v, CreatedEntities) + if (not status) then + print("AD2 PostEntityPaste Error: " .. tostring(valid)) + end + end + + if IsValid(v:GetPhysicsObject()) then + v:GetPhysicsObject():EnableMotion(false) + end + + if (EntityList[_].BuildDupeInfo.DupeParentID and Parenting) then + v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) + end + + v:SetNotSolid(v.SolidMod) + end + end + DisablePropCreateEffect = nil + hook.Call("AdvDupe_FinishPasting", nil, { + { + EntityList = EntityList, + CreatedEntities = CreatedEntities, + ConstraintList = ConstraintList, + CreatedConstraints = CreatedConstraints, + HitPos = OrigPos or Position, + Player = Player + } + }, 1) + + return CreatedEntities, CreatedConstraints +end + +local function AdvDupe2_Spawn() + + local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] + + if (not Queue or not IsValid(Queue.Player)) then + if Queue then + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + end + + if (#AdvDupe2.JobManager.Queue == 0) then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + end + return + end + + if (Queue.Entity) then + if (Queue.Current == 1) then + AdvDupe2.InitProgressBar(Queue.Player, "Pasting:") + Queue.Player.AdvDupe2.Queued = false + end + if (Queue.Current > #Queue.SortedEntities) then + Queue.Entity = false + Queue.Constraint = true + Queue.Current = 1 + return + end + if (not Queue.SortedEntities[Queue.Current]) then + Queue.Current = Queue.Current + 1 + return + end + + local k = Queue.SortedEntities[Queue.Current] + local v = Queue.EntityList[k] + + if (not v.BuildDupeInfo) then v.BuildDupeInfo = {} end + if Queue.Revision < 1 and v.LocalPos then + for i, _ in pairs(v.PhysicsObjects) do + v.PhysicsObjects[i] = {Pos = v.LocalPos, Angle = v.LocalAngle} + end + end + + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + local proppos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PhysicsObjects[0].Pos = Vector(0, 0, 0) + if (Queue.OrigPos) then + for i, p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos = p.Pos + proppos + Queue.OrigPos + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.PosReset = v.Pos + v.BuildDupeInfo.AngleReset = v.Angle + else + for i, p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos, v.PhysicsObjects[i].Angle = + LocalToWorld(p.Pos + proppos, p.Angle, Queue.PositionOffset, Queue.AngleOffset) + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PosReset = v.Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.AngleReset = v.Angle + end + + AdvDupe2.SpawningEntity = true + local Ent = CreateEntityFromTable(v, Queue.Player) + AdvDupe2.SpawningEntity = false + + if Ent then + Queue.Player:AddCleanup("AdvDupe2", Ent) + Ent.BoneMods = table.Copy(v.BoneMods) + Ent.EntityMods = table.Copy(v.EntityMods) + Ent.PhysicsObjects = table.Copy(v.PhysicsObjects) + Ent.SolidMod = not Ent:IsSolid() + + local Phys = Ent:GetPhysicsObject() + if (IsValid(Phys)) then Phys:EnableMotion(false) end + if (not Queue.DisableProtection) then Ent:SetNotSolid(true) end + if (v.CollisionGroup) then Ent:SetCollisionGroup(v.CollisionGroup) end + if (Ent.OnDuplicated) then Ent:OnDuplicated(v) end + elseif (Ent == false) then + Ent = nil + else + Ent = nil + end + Queue.CreatedEntities[k] = Ent + + AdvDupe2.UpdateProgressBar(Queue.Player, math.floor((Queue.Percent * Queue.Current) * 100)) + Queue.Current = Queue.Current + 1 + if (Queue.Current > #Queue.SortedEntities) then + + for _, Ent in pairs(Queue.CreatedEntities) do + ApplyEntityModifiers(Queue.Player, Ent) + ApplyBoneModifiers(Queue.Player, Ent) + + -- If the entity has a PostEntityPaste function tell it to use it now + if Ent.PostEntityPaste then + local status, valid = pcall(Ent.PostEntityPaste, Ent, Queue.Player, Ent, Queue.CreatedEntities) + if (not status) then + print("AD2 PostEntityPaste Error: " .. tostring(valid)) + end + end + end + + Queue.Entity = false + Queue.Constraint = true + Queue.Current = 1 + end + + if (#AdvDupe2.JobManager.Queue >= AdvDupe2.JobManager.CurrentPlayer + 1) then + AdvDupe2.JobManager.CurrentPlayer = AdvDupe2.JobManager.CurrentPlayer + 1 + else + AdvDupe2.JobManager.CurrentPlayer = 1 + end + else + if (#Queue.ConstraintList > 0) then + + if (#AdvDupe2.JobManager.Queue == 0) then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + end + if (not Queue.ConstraintList[Queue.Current]) then + Queue.Current = Queue.Current + 1 + return + end + + local Entity = CreateConstraintFromTable(Queue.ConstraintList[Queue.Current], Queue.CreatedEntities, + Queue.EntityList, Queue.Player, true) + if IsValid(Entity) then + table.insert(Queue.CreatedConstraints, Entity) + end + elseif (next(Queue.ConstraintList) ~= nil) then + local tbl = {} + for k, v in pairs(Queue.ConstraintList) do + table.insert(tbl, v) + end + Queue.ConstraintList = tbl + Queue.Current = 0 + end + + AdvDupe2.UpdateProgressBar(Queue.Player, math.floor((Queue.Percent * (Queue.Current + Queue.Plus)) * 100)) + Queue.Current = Queue.Current + 1 + + if (Queue.Current > #Queue.ConstraintList) then + + local unfreeze = tobool(Queue.Player:GetInfo("advdupe2_paste_unfreeze")) or false + local preservefrozenstate = tobool(Queue.Player:GetInfo("advdupe2_preserve_freeze")) or false + + -- Remove the undo for stopping pasting + local undotxt = "AdvDupe2"..(Queue.Name and (": ("..tostring(Queue.Name)..")") or "") + local undos = undo.GetTable()[Queue.Player:UniqueID()] + + if undos then + for i = #undos, 1, -1 do + if (undos[i] and undos[i].Name == undotxt) then + undos[i] = nil + -- Undo module netmessage + net.Start("Undo_Undone") + net.WriteInt(i, 16) + net.Send(Queue.Player) + break + end + end + end + + undo.Create(undotxt) + local phys, edit, mass + for k, v in pairs(Queue.CreatedEntities) do + if (not IsValid(v)) then + v = nil + else + edit = true + if (Queue.EntityList[k].BuildDupeInfo.DupeParentID ~= nil and Queue.Parenting) then + v:SetParent(Queue.CreatedEntities[Queue.EntityList[k].BuildDupeInfo.DupeParentID]) + if (v.Constraints ~= nil) then + for i, c in pairs(v.Constraints) do + if (c and gtSetupTable.CONSTRAINT[c.Type]) then + edit = false + break + end + end + end + if (edit and IsValid(v:GetPhysicsObject())) then + mass = v:GetPhysicsObject():GetMass() + v:PhysicsInitShadow(false, false) + v:SetCollisionGroup(COLLISION_GROUP_WORLD) + v:GetPhysicsObject():EnableMotion(false) + v:GetPhysicsObject():Sleep() + v:GetPhysicsObject():SetMass(mass) + end + else + edit = false + end + + local physCount = v:GetPhysicsObjectCount() - 1 + + if (unfreeze) then + for i = 0, physCount do + phys = v:GetPhysicsObjectNum(i) + if (IsValid(phys)) then + phys:EnableMotion(true) -- Unfreeze the entitiy and all of its objects + phys:Wake() + end + end + elseif (preservefrozenstate) then + for i = 0, physCount do + phys = v:GetPhysicsObjectNum(i) + if (IsValid(phys)) then + if (Queue.EntityList[k].BuildDupeInfo.PhysicsObjects[i].Frozen) then + phys:EnableMotion(true) -- Restore the entity and all of its objects to their original frozen state + phys:Wake() + else + Queue.Player:AddFrozenPhysicsObject(v, phys) + end + end + end + else + for i = 0, physCount do + phys = v:GetPhysicsObjectNum(i) + if (IsValid(phys)) then + if (phys:IsMoveable()) then + phys:EnableMotion(false) -- Freeze the entitiy and all of its objects + Queue.Player:AddFrozenPhysicsObject(v, phys) + end + end + end + end + + if (not edit or not Queue.DisableParents) then + v:SetNotSolid(v.SolidMod) + end + + undo.AddEntity(v) + end + end + undo.SetPlayer(Queue.Player) + undo.Finish() + + hook.Call("AdvDupe_FinishPasting", nil, { + { + EntityList = Queue.EntityList, + CreatedEntities = Queue.CreatedEntities, + ConstraintList = Queue.ConstraintList, + CreatedConstraints = Queue.CreatedConstraints, + HitPos = Queue.PositionOffset, + Player = Queue.Player + } + }, 1) + AdvDupe2.FinishPasting(Queue.Player, true) + + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + if (#AdvDupe2.JobManager.Queue == 0) then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + end + end + if (#AdvDupe2.JobManager.Queue >= AdvDupe2.JobManager.CurrentPlayer + 1) then + AdvDupe2.JobManager.CurrentPlayer = AdvDupe2.JobManager.CurrentPlayer + 1 + else + AdvDupe2.JobManager.CurrentPlayer = 1 + end + end +end + +local ticktotal = 0 +local function ErrorCatchSpawning() + + ticktotal = ticktotal + math.max(GetConVarNumber("AdvDupe2_SpawnRate"), 0.01) + while ticktotal >= 1 do + ticktotal = ticktotal - 1 + local status, err = pcall(AdvDupe2_Spawn) + + if (not status) then + -- PUT ERROR LOGGING HERE + + if (not AdvDupe2.JobManager.Queue) then + print("[AdvDupe2Notify]\t" .. err) + AdvDupe2.JobManager.Queue = {} + return + end + + local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] + if (not Queue) then + print("[AdvDupe2Notify]\t" .. err) + return + end + + if (IsValid(Queue.Player)) then + AdvDupe2.Notify(Queue.Player, err) + + local undos = undo.GetTable()[Queue.Player:UniqueID()] + + if undos then + local undotxt = Queue.Name and ("AdvDupe2 ("..Queue.Name..")") or "AdvDupe2" + for i = #undos, 1, -1 do + if (undos[i] and undos[i].Name == undotxt) then + undos[i] = nil + -- Undo module netmessage + net.Start("Undo_Undone") + net.WriteInt(i, 16) + net.Send(Queue.Player) + break + end + end + end + else + print("[AdvDupe2Notify]\t" .. err) + end + + for k, v in pairs(Queue.CreatedEntities) do + if (IsValid(v)) then v:Remove() end + end + + if (IsValid(Queue.Player)) then + AdvDupe2.FinishPasting(Queue.Player, true) + end + + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + + if (#AdvDupe2.JobManager.Queue == 0) then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + else + if (#Queue < AdvDupe2.JobManager.CurrentPlayer) then + AdvDupe2.JobManager.CurrentPlayer = 1 + end + end + + end + end +end + +local function RemoveSpawnedEntities(tbl, i) + if (not AdvDupe2.JobManager.Queue[i]) then return end -- Without this some errors come up, double check the errors without this line + + for k, v in pairs(AdvDupe2.JobManager.Queue[i].CreatedEntities) do + if (IsValid(v)) then v:Remove() end + end + + AdvDupe2.FinishPasting(AdvDupe2.JobManager.Queue[i].Player, false) + table.remove(AdvDupe2.JobManager.Queue, i) + if (#AdvDupe2.JobManager.Queue == 0) then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + end +end + +function AdvDupe2.InitPastingQueue(Player, PositionOffset, AngleOffset, OrigPos, Constrs, Parenting, DisableParents, DisableProtection) + local i = #AdvDupe2.JobManager.Queue + 1 + + local Queue = { + Player = Player, + SortedEntities = {}, + EntityList = table.Copy(Player.AdvDupe2.Entities), + Current = 1, + Name = Player.AdvDupe2.Name, + Entity = true, + Constraint = false, + Parenting = Parenting, + DisableParents = DisableParents, + DisableProtection = DisableProtection, + CreatedEntities = {}, + CreatedConstraints = {}, + PositionOffset = PositionOffset or Vector(0, 0, 0), + AngleOffset = AngleOffset or Angle(0, 0, 0), + Revision = Player.AdvDupe2.Revision, + } + AdvDupe2.JobManager.Queue[i] = Queue + + if (Constrs) then + Queue.ConstraintList = table.Copy(Player.AdvDupe2.Constraints) + else + Queue.ConstraintList = {} + end + Queue.OrigPos = OrigPos + for k, v in pairs(Player.AdvDupe2.Entities) do + table.insert(Queue.SortedEntities, k) + end + + if (Player.AdvDupe2.Name) then + print( + "[AdvDupe2NotifyPaste]\t Player: " .. Player:Nick() .. " Pasted File, " .. Player.AdvDupe2.Name .. " with, " .. + #Queue.SortedEntities .. " Entities and " .. #Player.AdvDupe2.Constraints .. " Constraints.") + else + print("[AdvDupe2NotifyPaste]\t Player: " .. Player:Nick() .. " Pasted, " .. #Queue.SortedEntities .. + " Entities and " .. #Player.AdvDupe2.Constraints .. " Constraints.") + end + + Queue.Plus = #Queue.SortedEntities + Queue.Percent = 1 / (#Queue.SortedEntities + #Queue.ConstraintList) + AdvDupe2.InitProgressBar(Player, "Queued:") + Player.AdvDupe2.Queued = true + if (not AdvDupe2.JobManager.PastingHook) then + DisablePropCreateEffect = true + hook.Add("Tick", "AdvDupe2_Spawning", ErrorCatchSpawning) + AdvDupe2.JobManager.PastingHook = true + AdvDupe2.JobManager.CurrentPlayer = 1 + end + + local undotxt = "AdvDupe2"..(Player.AdvDupe2.Name and (": ("..tostring(Player.AdvDupe2.Name)..")") or "") + undo.Create(undotxt) + undo.SetPlayer(Player) + undo.AddFunction(RemoveSpawnedEntities, i) + undo.Finish() +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_file.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_file.lua new file mode 100644 index 0000000..2f661e6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_file.lua @@ -0,0 +1,143 @@ +--Save a file to the client +local function SaveFile(ply, cmd, args) + if(not ply.AdvDupe2 or not ply.AdvDupe2.Entities or next(ply.AdvDupe2.Entities)==nil)then AdvDupe2.Notify(ply,"Duplicator is empty, nothing to save.", NOTIFY_ERROR) return end + if(not game.SinglePlayer() and CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then + AdvDupe2.Notify(ply,"Cannot save at the moment. Please Wait...", NOTIFY_ERROR) + return + end + + if(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR) + return false + end + + ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")+2) + + local name = string.Explode("/", args[1]) + ply.AdvDupe2.Name = name[#name] + + net.Start("AdvDupe2_SetDupeInfo") + net.WriteString(ply.AdvDupe2.Name) + net.WriteString(ply:Nick()) + net.WriteString(os.date("%d %B %Y")) + net.WriteString(os.date("%I:%M %p")) + net.WriteString("") + net.WriteString(args[2] or "") + net.WriteString(table.Count(ply.AdvDupe2.Entities)) + net.WriteString(#ply.AdvDupe2.Constraints) + net.Send(ply) + + local Tab = {Entities = ply.AdvDupe2.Entities, Constraints = ply.AdvDupe2.Constraints, HeadEnt = ply.AdvDupe2.HeadEnt, Description=args[2]} + + AdvDupe2.Encode( Tab, AdvDupe2.GenerateDupeStamp(ply), function(data) + AdvDupe2.SendToClient(ply, data, false) + end) +end +concommand.Add("AdvDupe2_SaveFile", SaveFile) + +function AdvDupe2.SendToClient(ply, data, autosave) + if(not IsValid(ply))then return end + if #data > AdvDupe2.MaxDupeSize then + AdvDupe2.Notify(ply,"Copied duplicator filesize is too big!",NOTIFY_ERROR) + return + end + + ply.AdvDupe2.Downloading = true + AdvDupe2.InitProgressBar(ply,"Saving:") + + net.Start("AdvDupe2_ReceiveFile") + net.WriteBool(autosave) + net.WriteStream(data, function() + ply.AdvDupe2.Downloading = false + end) + net.Send(ply) +end + +function AdvDupe2.LoadDupe(ply,success,dupe,info,moreinfo) + if(not IsValid(ply))then return end + + if not success then + AdvDupe2.Notify(ply,"Could not open "..dupe,NOTIFY_ERROR) + return + end + + if(not game.SinglePlayer())then + if(tonumber(GetConVarString("AdvDupe2_MaxConstraints"))~=0 and #dupe["Constraints"]>tonumber(GetConVarString("AdvDupe2_MaxConstraints")))then + AdvDupe2.Notify(ply,"Amount of constraints is greater than "..GetConVarString("AdvDupe2_MaxConstraints"),NOTIFY_ERROR) + return false + end + end + + ply.AdvDupe2.Entities = {} + ply.AdvDupe2.Constraints = {} + ply.AdvDupe2.HeadEnt={} + ply.AdvDupe2.Revision = info.revision + + if(info.ad1)then + + ply.AdvDupe2.HeadEnt.Index = tonumber(moreinfo.Head) + local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$") + ply.AdvDupe2.HeadEnt.Pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0) + local z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1 + ply.AdvDupe2.HeadEnt.Z = z + ply.AdvDupe2.HeadEnt.Pos.Z = ply.AdvDupe2.HeadEnt.Pos.Z + z + local Pos + local Ang + for k,v in pairs(dupe["Entities"])do + Pos = nil + Ang = nil + if(v.SavedParentIdx)then + if(not v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.DupeParentID = v.SavedParentIdx + Pos = v.LocalPos*1 + Ang = v.LocalAngle*1 + end + for i,p in pairs(v.PhysicsObjects)do + p.Pos = Pos or (p.LocalPos*1) + p.Pos.Z = p.Pos.Z - z + p.Angle = Ang or (p.LocalAngle*1) + p.LocalPos = nil + p.LocalAngle = nil + p.Frozen = not p.Frozen -- adv dupe 2 does this wrong way + end + v.LocalPos = nil + v.LocalAngle = nil + end + + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + + else + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + ply.AdvDupe2.HeadEnt = dupe["HeadEnt"] + end + AdvDupe2.ResetOffsets(ply, true) +end + +local function AdvDupe2_ReceiveFile(len, ply) + if not IsValid(ply) then return end + if not ply.AdvDupe2 then ply.AdvDupe2 = {} end + + ply.AdvDupe2.Name = string.match(net.ReadString(), "([%w_ ]+)") or "Advanced Duplication" + + local stream = net.ReadStream(ply, function(data) + if data then + AdvDupe2.LoadDupe(ply, AdvDupe2.Decode(data)) + else + AdvDupe2.Notify(ply, "Duplicator Upload Failed!", NOTIFY_ERROR, 5) + end + ply.AdvDupe2.Uploading = false + end) + + if ply.AdvDupe2.Uploading then + if stream then + stream:Remove() + end + AdvDupe2.Notify(ply, "Duplicator is Busy!", NOTIFY_ERROR, 5) + elseif stream then + ply.AdvDupe2.Uploading = true + AdvDupe2.InitProgressBar(ply, "Uploading: ") + end +end +net.Receive("AdvDupe2_ReceiveFile", AdvDupe2_ReceiveFile) diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_ghost.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_ghost.lua new file mode 100644 index 0000000..3a38f3f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_ghost.lua @@ -0,0 +1,70 @@ + +util.AddNetworkString("AdvDupe2_SendGhosts") +util.AddNetworkString("AdvDupe2_AddGhost") + +function AdvDupe2.SendGhost(ply, AddOne) + net.Start("AdvDupe2_AddGhost") + net.WriteString(AddOne.Model) + net.WriteInt(#AddOne.PhysicsObjects, 8) + for i=0, #AddOne.PhysicsObjects do + net.WriteAngle(AddOne.PhysicsObjects[i].Angle) + net.WriteVector(AddOne.PhysicsObjects[i].Pos) + end + net.Send(ply) +end + +function AdvDupe2.SendGhosts(ply) + if(not ply.AdvDupe2.Entities)then return end + + local cache = {} + local temp = {} + local mdls = {} + local cnt = 1 + local add = true + local head + + for k,v in pairs(ply.AdvDupe2.Entities)do + temp[cnt] = v + for i=1,#cache do + if(cache[i]==v.Model)then + mdls[cnt] = i + add=false + break + end + end + if(add)then + mdls[cnt] = table.insert(cache, v.Model) + else + add = true + end + if(k==ply.AdvDupe2.HeadEnt.Index)then + head = cnt + end + cnt = cnt+1 + end + + if(!head)then + AdvDupe2.Notify(ply, "Invalid head entity for ghosts.", NOTIFY_ERROR); + return + end + + net.Start("AdvDupe2_SendGhosts") + net.WriteInt(head, 16) + net.WriteFloat(ply.AdvDupe2.HeadEnt.Z) + net.WriteVector(ply.AdvDupe2.HeadEnt.Pos) + net.WriteInt(#cache, 16) + for i=1,#cache do + net.WriteString(cache[i]) + end + net.WriteInt(cnt-1, 16) + for i=1, #temp do + net.WriteInt(mdls[i], 16) + net.WriteInt(#temp[i].PhysicsObjects, 8) + for k=0, #temp[i].PhysicsObjects do + net.WriteAngle(temp[i].PhysicsObjects[k].Angle) + net.WriteVector(temp[i].PhysicsObjects[k].Pos) + end + end + net.Send(ply) + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_misc.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_misc.lua new file mode 100644 index 0000000..9934ab3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/advdupe2/sv_misc.lua @@ -0,0 +1,104 @@ +--[[ + Title: Miscellaneous + + Desc: Contains miscellaneous (serverside) things AD2 needs to function that don't fit anywhere else. + + Author: TB + + Version: 1.0 +]] + +--[[ + Name: SavePositions + Desc: Save the position of the entities to prevent sagging on dupe. + Params: Constraint + Returns: nil +]] + +local function SavePositions( Constraint ) + + if IsValid(Constraint) then + + if Constraint.BuildDupeInfo then return end + local BuildDupeInfo = {} + Constraint.BuildDupeInfo = BuildDupeInfo + + local Ent1, Ent2 + if IsValid(Constraint.Ent) then + if Constraint.Ent:GetPhysicsObjectCount()>1 then + BuildDupeInfo.Ent1Ang = Constraint.Ent:GetAngles() + else + BuildDupeInfo.Ent1Ang = Constraint.Ent:GetPhysicsObject():GetAngles() + end + end + + if IsValid(Constraint.Ent1) then + if Constraint.Ent1:GetPhysicsObjectCount()>1 then + local Bone = Constraint.Ent1:GetPhysicsObjectNum(Constraint.Bone1) + BuildDupeInfo.Ent1Ang = Constraint.Ent1:GetAngles() + BuildDupeInfo.Ent1Pos = Constraint.Ent1:GetPos() + BuildDupeInfo.Bone1 = Constraint.Bone1 + BuildDupeInfo.Bone1Pos = Bone:GetPos() - Constraint.Ent1:GetPos() + BuildDupeInfo.Bone1Angle = Bone:GetAngles() + else + local Bone = Constraint.Ent1:GetPhysicsObject() + BuildDupeInfo.Ent1Ang = Bone:GetAngles() + BuildDupeInfo.Ent1Pos = Bone:GetPos() + end + + if IsValid(Constraint.Ent2) then + if Constraint.Ent2:GetPhysicsObjectCount()>1 then + local Bone = Constraint.Ent2:GetPhysicsObjectNum(Constraint.Bone2) + BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Constraint.Ent2:GetPos() + BuildDupeInfo.Ent2Ang = Constraint.Ent2:GetAngles() + BuildDupeInfo.Bone2 = Constraint.Bone2 + BuildDupeInfo.Bone2Pos = Bone:GetPos() - Constraint.Ent2:GetPos() + BuildDupeInfo.Bone2Angle = Bone:GetAngles() + else + local Bone = Constraint.Ent2:GetPhysicsObject() + BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Bone:GetPos() + BuildDupeInfo.Ent2Ang = Bone:GetAngles() + end + elseif IsValid(Constraint.Ent4) then + if Constraint.Ent4:GetPhysicsObjectCount()>1 then + local Bone = Constraint.Ent4:GetPhysicsObjectNum(Constraint.Bone4) + BuildDupeInfo.Bone2 = Constraint.Bone4 + BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Constraint.Ent4:GetPos() + BuildDupeInfo.Ent2Ang = Constraint.Ent4:GetAngles() + BuildDupeInfo.Bone2Pos = Bone:GetPos() - Constraint.Ent4:GetPos() + BuildDupeInfo.Bone2Angle = Bone:GetAngles() + else + local Bone = Constraint.Ent4:GetPhysicsObject() + BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Bone:GetPos() + BuildDupeInfo.Ent2Ang = Bone:GetAngles() + end + end + + end + + end +end + +local function monitorConstraint(name) + local oldFunc = constraint[name] + constraint[name] = function(...) + local Constraint, b, c = oldFunc(...) + if Constraint and Constraint:IsValid() then + SavePositions(Constraint) + end + return Constraint, b, c + end +end +monitorConstraint("AdvBallsocket") +monitorConstraint("Axis") +monitorConstraint("Ballsocket") +monitorConstraint("Elastic") +monitorConstraint("Hydraulic") +monitorConstraint("Keepupright") +monitorConstraint("Motor") +monitorConstraint("Muscle") +monitorConstraint("Pulley") +monitorConstraint("Rope") +monitorConstraint("Slider") +monitorConstraint("Weld") +monitorConstraint("Winch") diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/cl_init.lua new file mode 100644 index 0000000..98c5fd6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/cl_init.lua @@ -0,0 +1,145 @@ +include("shared.lua") + +surface.CreateFont( "InfoRUS2", { font = "Enhanced Dot Digital-7", extended = true, size = 90, weight = 800, antialias = true }) +surface.CreateFont( "InfoRUS3", { font = "Enhanced Dot Digital-7", extended = true, size = 50, weight = 800, antialias = true }) + +local font = "InfoRUS2" + +local sizetable = { + [3] = {350, 0.5}, + [4] = {470, -11.5}, + [5] = {590, -11.5}, + [6] = {710, 0.5}, + [7] = {830, 0.5}, + [8] = {950, 0.5}, +} + +function ENT:Initialize() + + self.OldWide = self:GetWide() + + self.frame = vgui.Create( "DPanel" ) + self.frame:SetSize( sizetable[self:GetWide()][1], 120 ) + self.frame.Text = self:GetText() + self.frame.Type = self:GetType() + self.frame.col = self:GetTColor() + self.frame.damage = 0 + self.frame.appr = nil + self.frame.FX = self:GetFX() + self.frame.On = self:GetOn() + self.frame.alfa = 0 + self.frame.speed = self:GetSpeed() + self.frame:SetPaintedManually( true ) + self.frame.Paint = function(self,w,h) + + if self.On <= 0 then + if self.alfa < 1 then return end + self.alfa = Lerp(FrameTime() * 5,self.alfa,0) + else + if self.FX > 0 then + self.alfa = math.random(100,220) + else + self.alfa = 255 + end + end + + surface.DisableClipping( false ) + surface.SetFont(font) + local ww,hh = surface.GetTextSize(self.Text) + local multiplier = self.speed * 100 + + self.static = false + + if self.damage < CurTime() and self.On then + if self.Type == 1 then + + local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww + + draw.DrawText(self.Text,font,xs,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0) + elseif self.Type == 2 then + + if !self.appr or self.appr > ww then + self.appr = -w + else + self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier) + end + + draw.DrawText(self.Text,font,self.appr * -1,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0) + else + if !self.appr then + self.appr = 0 + end + + if w > ww then + if self.Type == 3 then + if self.appr < w-ww and !self.refl then + self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier) + else + if self.appr <= 0 then + self.refl = nil + else + self.refl = true + self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier) + end + end + else + self.static = true + end + else + if self.appr > w-ww-50 and !self.refl then + self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier) + else + if self.appr >= 50 then + self.refl = nil + else + self.refl = true + self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier) + end + end + end + + if self.static then + draw.DrawText(self.Text,font,w/2,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),1) + else + draw.DrawText(self.Text,font,self.appr,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0) + end + end + else + draw.DrawText(self.Text,font,math.random(0,w-ww),10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, math.random(0,255)),0) + end + surface.DisableClipping( true ) + end +end + +function ENT:Draw() + + self:DrawModel() + + if self.frame then + self.frame.Text = self:GetText() + self.frame.Type = self:GetType() + self.frame.col = self:GetTColor() + self.frame.FX = self:GetFX() + self.frame.On = self:GetOn() + self.frame.damage = self:GetNWInt("LastDamaged") + self.frame.speed = self:GetSpeed() + end + + local Pos = self:GetPos() + local Ang = self:GetAngles() + local hight = 12 + + if self.OldWide != self:GetWide() then + self.frame:SetSize( sizetable[self:GetWide()][1], 120 ) + self.OldWide = self:GetWide() + end + + if self:GetWide() == 3 then + hight = 6 + end + + cam.Start3D2D(Pos + Ang:Up() * 1.1 - Ang:Right() * hight + Ang:Forward() * sizetable[self:GetWide()][2], Ang, 0.1) + self.frame:PaintManual() + cam.End3D2D() + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/init.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/init.lua new file mode 100644 index 0000000..ca85808 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/init.lua @@ -0,0 +1,54 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("LEDDamaged") + +function ENT:Initialize() + + self:SetText("Made by Mac with <3") + self:SetTColor(Vector(2.55,2,0)) + self:SetType(1) + self:SetSpeed(1.5) + self:SetWide(6) + self:SetFX(1) + self:SetOn(1) + + self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMaterial("phoenix_storms/mat/mat_phx_carbonfiber") + self:SetColor(Color(0,0,0)) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetNWInt("LastDamaged", 0) + local phys = self:GetPhysicsObject() + self.nodupe = true + self.ShareGravgun = true + + phys:Wake() + +end + +function ENT:Think() + + if self:GetModel() != "models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl" then + self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl") + end + + self:NextThink( CurTime() + 1 ) + return true + +end + +function ENT:OnTakeDamage(data) + + if data:GetDamagePosition() then + self:EmitSound("ambient/energy/spark".. math.random(1,6) ..".wav") + local effectdata = EffectData() + effectdata:SetOrigin( data:GetDamagePosition() ) + util.Effect( "StunstickImpact", effectdata ) + self:SetNWInt("LastDamaged", math.Round(CurTime()+2)) + end + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/shared.lua new file mode 100644 index 0000000..aec6dc8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign/shared.lua @@ -0,0 +1,16 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Spawned Sign" +ENT.Author = "Mac" +ENT.Spawnable = false +ENT.AdminSpawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "Text") + self:NetworkVar("Vector", 0, "TColor") + self:NetworkVar("Int", 0, "Type") + self:NetworkVar("Int", 1, "Speed") + self:NetworkVar("Int", 2, "Wide") + self:NetworkVar("Int", 3, "On") + self:NetworkVar("Int", 4, "FX") +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/cl_init.lua new file mode 100644 index 0000000..3d5c624 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/cl_init.lua @@ -0,0 +1,142 @@ +include("shared.lua") + +local font = "InfoRUS2" + +local sizetable = { + [3] = {350, 0.5}, + [4] = {470, -11.5}, + [5] = {590, -11.5}, + [6] = {710, 0.5}, + [7] = {830, 0.5}, + [8] = {950, 0.5}, +} + +function ENT:Initialize() + + self.OldWide = self:GetWide() + + self.frame = vgui.Create( "DPanel" ) + self.frame:SetSize( sizetable[self:GetWide()][1], 120 ) + self.frame.Text = self:GetText() + self.frame.Type = self:GetType() + self.frame.col = self:GetTColor() + self.frame.damage = 0 + self.frame.appr = nil + self.frame.FX = self:GetFX() + self.frame.On = self:GetOn() + self.frame.alfa = 0 + self.frame.speed = self:GetSpeed() + self.frame:SetPaintedManually( true ) + self.frame.Paint = function(self,w,h) + + if self.On <= 0 then + if self.alfa < 1 then return end + self.alfa = Lerp(FrameTime() * 5,self.alfa,0) + else + if self.FX > 0 then + self.alfa = math.random(100,220) + else + self.alfa = 255 + end + end + + surface.DisableClipping( false ) + surface.SetFont(font) + local ww,hh = surface.GetTextSize(self.Text) + local multiplier = self.speed * 100 + + self.static = false + + if self.damage < CurTime() and self.On then + if self.Type == 1 then + + local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww + + draw.DrawText(self.Text,font,xs,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0) + elseif self.Type == 2 then + + if !self.appr or self.appr > ww then + self.appr = -w + else + self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier) + end + + draw.DrawText(self.Text,font,self.appr * -1,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0) + else + if !self.appr then + self.appr = 0 + end + + if w > ww then + if self.Type == 3 then + if self.appr < w-ww and !self.refl then + self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier) + else + if self.appr <= 0 then + self.refl = nil + else + self.refl = true + self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier) + end + end + else + self.static = true + end + else + if self.appr > w-ww-50 and !self.refl then + self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier) + else + if self.appr >= 50 then + self.refl = nil + else + self.refl = true + self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier) + end + end + end + + if self.static then + draw.DrawText(self.Text,font,w/2,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),1) + else + draw.DrawText(self.Text,font,self.appr,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0) + end + end + else + draw.DrawText(self.Text,font,math.random(0,w-ww),10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, math.random(0,255)),0) + end + surface.DisableClipping( true ) + end +end + +function ENT:Draw() + + self:DrawModel() + + if self.frame then + self.frame.Text = self:GetText() + self.frame.Type = self:GetType() + self.frame.col = self:GetTColor() + self.frame.FX = self:GetFX() + self.frame.On = self:GetOn() + self.frame.damage = self:GetNWInt("LastDamaged") + self.frame.speed = self:GetSpeed() + end + + local Pos = self:GetPos() + local Ang = self:GetAngles() + local hight = 12 + + if self.OldWide != self:GetWide() then + self.frame:SetSize( sizetable[self:GetWide()][1], 120 ) + self.OldWide = self:GetWide() + end + + if self:GetWide() == 3 then + hight = 6 + end + + cam.Start3D2D(Pos + Ang:Up() * 1.1 - Ang:Right() * hight + Ang:Forward() * sizetable[self:GetWide()][2], Ang, 0.1) + self.frame:PaintManual() + cam.End3D2D() + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/init.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/init.lua new file mode 100644 index 0000000..5c21b50 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/init.lua @@ -0,0 +1,70 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("LEDDamaged") + +function ENT:Initialize() + + self:SetText("Made by Mac with Wire and <3") + self:SetTColor(Vector(2.55,2,0)) + self:SetType(1) + self:SetSpeed(1.5) + self:SetWide(6) + self:SetFX(1) + self:SetOn(1) + + self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMaterial("phoenix_storms/mat/mat_phx_carbonfiber") + self:SetColor(Color(0,0,0)) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetNWInt("LastDamaged", 0) + local phys = self:GetPhysicsObject() + self.nodupe = true + self.ShareGravgun = true + + phys:Wake() + + + self.Inputs = WireLib.CreateSpecialInputs(self, { "Text", "Type", "Speed", "Color", "Flicker", "On" }, { "STRING", "NORMAL", "NORMAL", "VECTOR", "NORMAL", "NORMAL" }) + +end + +function ENT:TriggerInput(iname, value) + if iname == "Text" then + self:SetText(value) + elseif iname == "Type" then + self:SetType(math.Clamp(math.Round(value), 1, 4)) + elseif iname == "Speed" then + self:SetSpeed(math.Clamp(value, 1, 10)) + elseif iname == "Color" then + self:SetTColor(Vector(value.x/100, value.y/100, value.z/100)) + elseif iname == "On" then + self:SetOn(value) + elseif iname == "Flicker" then + self:SetFX(math.Clamp(value, 0, 1)) + end +end + +function ENT:Think() + + if self:GetModel() != "models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl" then + self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl") + end + +end + +function ENT:OnTakeDamage(data) + + if data:GetDamagePosition() then + self:EmitSound("ambient/energy/spark".. math.random(1,6) ..".wav") + local effectdata = EffectData() + effectdata:SetOrigin( data:GetDamagePosition() ) + util.Effect( "StunstickImpact", effectdata ) + self:SetNWInt("LastDamaged", math.Round(CurTime()+2)) + end + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/shared.lua new file mode 100644 index 0000000..4489420 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gb_rp_sign_wire/shared.lua @@ -0,0 +1,17 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" +ENT.PrintName = "Spawned Sign Wire" +ENT.WireDebugName = "LED Screen" +ENT.Author = "Mac" +ENT.Spawnable = false +ENT.AdminSpawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "Text") + self:NetworkVar("Vector", 0, "TColor") + self:NetworkVar("Int", 0, "Type") + self:NetworkVar("Int", 1, "Speed") + self:NetworkVar("Int", 2, "Wide") + self:NetworkVar("Int", 3, "On") + self:NetworkVar("Int", 4, "FX") +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/cl_init.lua new file mode 100644 index 0000000..9696702 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/cl_init.lua @@ -0,0 +1,6 @@ +include( "shared.lua" ) + +function ENT:Draw() + self.BaseClass.Draw(self) + self.Entity:DrawModel() +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/init.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/init.lua new file mode 100644 index 0000000..ccd48ac --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/init.lua @@ -0,0 +1,291 @@ +--[[ + Title: Adv. Dupe 2 Contraption Spawner + + Desc: A mobile duplicator + + Author: TB + + Version: 1.0 +]] + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) +if(WireLib)then + include( "entities/base_wire_entity.lua" ) +end + +include( "shared.lua" ) + +function ENT:Initialize() + + self.Entity:SetMoveType( MOVETYPE_NONE ) + self.Entity:PhysicsInit( SOLID_VPHYSICS ) + self.Entity:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self.Entity:DrawShadow( false ) + + local phys = self.Entity:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + end + + self.UndoList = {} + self.Ghosts = {} + + self.SpawnLastValue = 0 + self.UndoLastValue = 0 + + self.LastSpawnTime = 0 + self.DupeName = "" + self.CurrentPropCount = 0 + + if WireLib then + self.Inputs = Wire_CreateInputs(self.Entity, {"Spawn", "Undo"}) + self.Outputs = WireLib.CreateSpecialOutputs(self.Entity, {"Out"}, { "NORMAL" }) + end +end + +/*-----------------------------------------------------------------------* + * Sets options for this spawner + *-----------------------------------------------------------------------*/ +function ENT:SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel, hideprops ) + + self.delay = delay + self.undo_delay = undo_delay + + --Key bindings + self.key = key + self.undo_key = undo_key + numpad.Remove( self.CreateKey ) + numpad.Remove( self.UndoKey ) + self.CreateKey = numpad.OnDown( ply, self.key , "ContrSpawnerCreate", self.Entity, true ) + self.UndoKey = numpad.OnDown( ply, self.undo_key, "ContrSpawnerUndo" , self.Entity, true ) + + -- Other parameters + self.DisableGravity = disgrav + self.DisableDrag = disdrag + self.AddVelocity = addvel + self.HideProps = hideprops + + -- Store the player's current dupe name + self.DupeName = tostring(ply.AdvDupe2.Name) + + self:ShowOutput() +end + +function ENT:UpdateOptions( options ) + self:SetOptions( options["delay"], options["undo_delay"], options["key"], options["undo_key"]) +end + +function ENT:AddGhosts() + if self.HideProps then return end + local moveable = self:GetPhysicsObject():IsMoveable() + self:GetPhysicsObject():EnableMotion(false) + local EntTable, GhostEntity, Phys + local Offset = self.DupeAngle - self.EntAngle + for EntIndex,v in pairs(self.EntityTable)do + if(EntIndex!=self.HeadEnt)then + if(self.EntityTable[EntIndex].Class=="gmod_contr_spawner")then self.EntityTable[EntIndex] = nil continue end + EntTable = table.Copy(self.EntityTable[EntIndex]) + if(EntTable.BuildDupeInfo && EntTable.BuildDupeInfo.PhysicsObjects)then + Phys = EntTable.BuildDupeInfo.PhysicsObjects[0] + else + if(!v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + Phys = EntTable.PhysicsObjects[0] + end + + GhostEntity = nil + + if(EntTable.Model==nil || !util.IsValidModel(EntTable.Model)) then EntTable.Model="models/error.mdl" end + + if ( EntTable.Model:sub( 1, 1 ) == "*" ) then + GhostEntity = ents.Create( "func_physbox" ) + else + GhostEntity = ents.Create( "gmod_ghost" ) + end + + // If there are too many entities we might not spawn.. + if ( !GhostEntity || GhostEntity == NULL ) then return end + + duplicator.DoGeneric( GhostEntity, EntTable ) + + GhostEntity:Spawn() + + GhostEntity:DrawShadow( false ) + GhostEntity:SetMoveType( MOVETYPE_NONE ) + GhostEntity:SetSolid( SOLID_VPHYSICS ); + GhostEntity:SetNotSolid( true ) + GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA ) + GhostEntity:SetColor( Color(255, 255, 255, 150) ) + + GhostEntity:SetAngles(Phys.Angle) + GhostEntity:SetPos(self:GetPos() + Phys.Pos - self.Offset) + self:SetAngles(self.EntAngle) + GhostEntity:SetParent( self ) + self:SetAngles(self.DupeAngle) + self.Ghosts[EntIndex] = GhostEntity + end + end + self:SetAngles(self.DupeAngle) + self:GetPhysicsObject():EnableMotion(moveable) +end + +function ENT:GetCreationDelay() return self.delay end +function ENT:GetDeletionDelay() return self.undo_delay end + +function ENT:OnTakeDamage( dmginfo ) self.Entity:TakePhysicsDamage( dmginfo ) end + +function ENT:SetDupeInfo( HeadEnt, EntityTable, ConstraintTable ) + self.HeadEnt = HeadEnt + self.EntityTable = EntityTable + self.ConstraintTable = ConstraintTable + if(!self.DupeAngle)then self.DupeAngle = self:GetAngles() end + if(!self.EntAngle)then self.EntAngle = EntityTable[HeadEnt].PhysicsObjects[0].Angle end + if(!self.Offset)then self.Offset = self.EntityTable[HeadEnt].PhysicsObjects[0].Pos end + + local headpos, headang = EntityTable[HeadEnt].PhysicsObjects[0].Pos, EntityTable[HeadEnt].PhysicsObjects[0].Angle + for k, v in pairs(EntityTable) do + for o, p in pairs(v.PhysicsObjects) do + p.LPos, p.LAngle = WorldToLocal(p.Pos, p.Angle, headpos, headang) + end + end +end + +function ENT:DoSpawn( ply ) + -- Explicitly allow spawning if no player is provided, but an invalid player gets denied. This can happen when a player leaves the server. + if not (ply and ply:IsValid()) then return end + + for k, v in pairs(self.EntityTable) do + for o, p in pairs(v.PhysicsObjects) do + p.Pos, p.Angle = self:LocalToWorld(p.LPos), self:LocalToWorldAngles(p.LAngle) + end + end + + /*local AngleOffset = self.EntAngle + AngleOffset = self:GetAngles() - AngleOffset + local AngleOffset2 = Angle(0,0,0) + //AngleOffset2.y = AngleOffset.y + AngleOffset2:RotateAroundAxis(self:GetUp(), AngleOffset.y) + AngleOffset2:RotateAroundAxis(self:GetRight(),AngleOffset.p) + AngleOffset2:RotateAroundAxis(self:GetForward(),AngleOffset.r)*/ + + local Ents, Constrs = AdvDupe2.duplicator.Paste(ply, self.EntityTable, self.ConstraintTable, nil, nil, Vector(0,0,0), true) + local i = #self.UndoList+1 + self.UndoList[i] = Ents + + local undotxt = "AdvDupe2: Contraption ("..tostring(self.DupeName)..")" + + undo.Create(undotxt) + local phys + for k,ent in pairs(Ents)do + phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + if(self.DisableGravity==1)then phys:EnableGravity(false) end + if(self.DisableDrag==1)then phys:EnableDrag(false) end + phys:EnableMotion(true) + if(ent.SetForce)then ent.SetForce(ent, ent.force, ent.mul) end + if(self.AddVelocity==1)then + phys:SetVelocity( self:GetVelocity() ) + phys:AddAngleVelocity( self:GetPhysicsObject():GetAngleVelocity() ) + end + end + + undo.AddEntity(ent) + end + + undo.SetPlayer(ply) + undo.Finish() + + if(self.undo_delay>0)then + timer.Simple(self.undo_delay, function() + if(self.UndoList && self.UndoList[i])then + for k,ent in pairs(self.UndoList[i]) do + if(IsValid(ent)) then + ent:Remove() + end + end + end + end) + end +end + +function ENT:DoUndo( ply ) + + if(!self.UndoList || #self.UndoList == 0)then return end + + local entities = self.UndoList[ #self.UndoList ] + self.UndoList[ #self.UndoList ] = nil + for _,ent in pairs(entities) do + if (IsValid(ent)) then + ent:Remove() + end + end +end + +function ENT:TriggerInput(iname, value) + local ply = self:GetPlayer() + + if(iname == "Spawn")then + if ((value > 0) == self.SpawnLastValue) then return end + self.SpawnLastValue = (value > 0) + + if(self.SpawnLastValue)then + local delay = self:GetCreationDelay() + if (delay == 0) then self:DoSpawn( ply ) return end + if(CurTime() < self.LastSpawnTime)then return end + self:DoSpawn( ply ) + self.LastSpawnTime=CurTime()+delay + end + elseif (iname == "Undo") then + // Same here + if((value > 0) == self.UndoLastValue)then return end + self.UndoLastValue = (value > 0) + + if(self.UndoLastValue)then self:DoUndo(ply) end + end +end + +local flags = {"Enabled", "Disabled"} +function ENT:ShowOutput() + local text = "\nGravity: "..((self.DisableGravity == 1) and flags[1] or flags[2]) + text = text.."\nDrag: " ..((self.DisableDrag == 1) and flags[1] or flags[2]) + text = text.."\nVelocity: "..((self.AddVelocity == 1) and flags[1] or flags[2]) + + self.Entity:SetOverlayText( + "Spawn Name: " .. tostring(self.DupeName) .. + "\nSpawn Delay: " .. tostring(self:GetCreationDelay()) .. + "\nUndo Delay: ".. tostring(self:GetDeletionDelay()) .. + text + ) +end + +/*-----------------------------------------------------------------------* + * Handler for spawn keypad input + *-----------------------------------------------------------------------*/ +function SpawnContrSpawner( ply, ent ) + + if (!ent || !ent:IsValid()) then return end + + local delay = ent:GetTable():GetCreationDelay() + + if(delay == 0) then + ent:DoSpawn( ply ) + return + end + + if(CurTime() < ent.LastSpawnTime)then return end + ent:DoSpawn( ply ) + ent.LastSpawnTime=CurTime()+delay +end + +/*-----------------------------------------------------------------------* + * Handler for undo keypad input + *-----------------------------------------------------------------------*/ +function UndoContrSpawner( ply, ent ) + if (!ent || !ent:IsValid()) then return end + ent:DoUndo( ply, true ) +end + +numpad.Register( "ContrSpawnerCreate", SpawnContrSpawner ) +numpad.Register( "ContrSpawnerUndo" , UndoContrSpawner ) diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/shared.lua new file mode 100644 index 0000000..bfc1947 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/gmod_contr_spawner/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = WireLib and "base_wire_entity" or "base_gmodentity" +ENT.PrintName = "Contraption Spawner" +ENT.Author = "TB" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false +ENT.AdminSpawnable = false diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/pp_prop_effect.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/entities/pp_prop_effect.lua new file mode 100644 index 0000000..d3011ad --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/entities/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/garrysmod/gamemodes/militaryrp/plugins/buildtools/netstream.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/netstream.lua new file mode 100644 index 0000000..c0bf9b4 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/netstream.lua @@ -0,0 +1,386 @@ +--A net extension which allows sending large streams of data without overflowing the reliable channel +--Keep it in lua/autorun so it will be shared between addons +AddCSLuaFile() + +net.Stream = {} +net.Stream.SendSize = 20000 --This is the size of each packet to send +net.Stream.Timeout = 30 --How long to wait for client response before cleaning up +net.Stream.MaxWriteStreams = 1024 --The maximum number of write data items to store +net.Stream.MaxReadStreams = 128 --The maximum number of queued read data items to store +net.Stream.MaxChunks = 3200 --Maximum number of pieces the stream can send to the server. 64 MB +net.Stream.MaxSize = net.Stream.SendSize*net.Stream.MaxChunks +net.Stream.MaxTries = 3 --Maximum times the client may retry downloading the whole data + +local WriteStreamQueue = { + __index = { + Add = function(self, stream) + local identifier = self.curidentifier + local startid = identifier + while self.queue[identifier] do + identifier = identifier % net.Stream.MaxWriteStreams + 1 + if identifier == startid then + ErrorNoHalt("Netstream is full of WriteStreams!") + net.WriteUInt(0, 32) + return + end + end + self.curidentifier = identifier % net.Stream.MaxWriteStreams + 1 + + if next(self.queue)==nil then + self.activitytimeout = CurTime()+net.Stream.Timeout + timer.Create("netstream_queueclean", 5, 0, function() self:Clean() end) + end + self.queue[identifier] = stream + stream.identifier = identifier + return stream + end, + + Write = function(self, ply) + local identifier = net.ReadUInt(32) + local chunkidx = net.ReadUInt(32) + local stream = self.queue[identifier] + --print("Got request", identifier, chunkidx, stream) + if stream then + if stream:Write(ply, chunkidx) then + self.activitytimeout = CurTime()+net.Stream.Timeout + stream.timeout = CurTime()+net.Stream.Timeout + end + else + -- Tell them the stream doesn't exist + net.Start("NetStreamRead") + net.WriteUInt(identifier, 32) + net.WriteUInt(0, 32) + if SERVER then net.Send(ply) else net.SendToServer() end + end + end, + + Clean = function(self) + local t = CurTime() + for k, stream in pairs(self.queue) do + if (next(stream.clients)~=nil and t >= stream.timeout) or t >= self.activitytimeout then + stream:Remove() + self.queue[k] = nil + end + end + if next(self.queue)==nil then + timer.Remove("netstream_queueclean") + end + end, + }, + __call = function(t) + return setmetatable({ + activitytimeout = CurTime()+net.Stream.Timeout, + curidentifier = 1, + queue = {} + }, t) + end +} +setmetatable(WriteStreamQueue, WriteStreamQueue) +net.Stream.WriteStreams = WriteStreamQueue() + +local ReadStreamQueue = { + __index = { + Add = function(self, stream) + local queue = self.queues[stream.player] + + if #queue == net.Stream.MaxReadStreams then + ErrorNoHalt("Receiving too many ReadStream requests!") + return + end + + for _, v in ipairs(queue) do + if v.identifier == stream.identifier then + ErrorNoHalt("Tried to start a new ReadStream for an already existing stream!") + return + end + end + + queue[#queue+1] = stream + if #queue == 1 then + stream:Request() + end + return stream + end, + + Remove = function(self, stream) + local queue = rawget(self.queues, stream.player) + if queue then + if stream == queue[1] then + table.remove(queue, 1) + local nextInQueue = queue[1] + if nextInQueue then + nextInQueue:Request() + else + self.queues[stream.player] = nil + end + else + for k, v in ipairs(queue) do + if v == stream then + table.remove(queue, k) + break + end + end + end + end + end, + + Read = function(self, ply) + local identifier = net.ReadUInt(32) + local queue = rawget(self.queues, ply) + if queue and queue[1] then + queue[1]:Read(identifier) + end + end + }, + __call = function(t) + return setmetatable({ + queues = setmetatable({}, {__index = function(t,k) local r={} t[k]=r return r end}) + }, t) + end +} +setmetatable(ReadStreamQueue, ReadStreamQueue) +net.Stream.ReadStreams = ReadStreamQueue() + + +local WritingDataItem = { + __index = { + Write = function(self, ply, chunkidx) + local client = self.clients[ply] + if client.finished then return false end + if chunkidx == #self.chunks+1 then self:Finished(ply) return true end + + if client.downloads+#self.chunks-client.progress >= net.Stream.MaxTries * #self.chunks then self:Finished(ply) return false end + client.downloads = client.downloads + 1 + + local chunk = self.chunks[chunkidx] + if not chunk then return false end + + client.progress = chunkidx + + --print("Sending", "NetStreamRead", self.identifier, #chunk.data, chunkidx, chunk.crc) + net.Start("NetStreamRead") + net.WriteUInt(self.identifier, 32) + net.WriteUInt(#chunk.data, 32) + net.WriteUInt(chunkidx, 32) + net.WriteString(chunk.crc) + net.WriteData(chunk.data, #chunk.data) + if CLIENT then net.SendToServer() else net.Send(ply) end + return true + end, + + Finished = function(self, ply) + self.clients[ply].finished = true + if self.callback then + local ok, err = xpcall(self.callback, debug.traceback, ply) + if not ok then ErrorNoHalt(err) end + end + end, + + GetProgress = function(self, ply) + return self.clients[ply].progress / #self.chunks + end, + + Remove = function(self) + local sendTo = {} + for ply, client in pairs(self.clients) do + if not client.finished then + client.finished = true + if CLIENT or ply:IsValid() then sendTo[#sendTo+1] = ply end + end + end + + if next(sendTo)~=nil then + --print("Sending", "NetStreamRead", self.identifier, 0) + net.Start("NetStreamRead") + net.WriteUInt(self.identifier, 32) + net.WriteUInt(0, 32) + if SERVER then net.Send(sendTo) else net.SendToServer() end + end + end + + }, + __call = function(t, data, callback) + local chunks = {} + for i=1, math.ceil(#data / net.Stream.SendSize) do + local datachunk = string.sub(data, (i - 1) * net.Stream.SendSize + 1, i * net.Stream.SendSize) + chunks[i] = { data = datachunk, crc = util.CRC(datachunk) } + end + + return setmetatable({ + timeout = CurTime()+net.Stream.Timeout, + chunks = chunks, + callback = callback, + lasttouched = 0, + clients = setmetatable({},{__index = function(t,k) + local r = { + finished = false, + downloads = 0, + progress = 0, + } t[k]=r return r + end}) + }, t) + end +} +setmetatable(WritingDataItem, WritingDataItem) + +local ReadingDataItem = { + __index = { + Request = function(self) + if self.downloads+self.numchunks-#self.chunks >= net.Stream.MaxTries*self.numchunks then self:Remove() return end + self.downloads = self.downloads + 1 + timer.Create("NetStreamReadTimeout" .. self.identifier, net.Stream.Timeout*0.5, 1, function() self:Request() end) + self:WriteRequest() + end, + + WriteRequest = function(self) + --print("Requesting", self.identifier, #self.chunks) + net.Start("NetStreamWrite") + net.WriteUInt(self.identifier, 32) + net.WriteUInt(#self.chunks+1, 32) + if CLIENT then net.SendToServer() else net.Send(self.player) end + end, + + Read = function(self, identifier) + if self.identifier ~= identifier then self:Request() return end + + local size = net.ReadUInt(32) + if size == 0 then self:Remove() return end + + local chunkidx = net.ReadUInt(32) + if chunkidx ~= #self.chunks+1 then self:Request() return end + + local crc = net.ReadString() + local data = net.ReadData(size) + + if crc ~= util.CRC(data) then self:Request() return end + + self.chunks[chunkidx] = data + if #self.chunks == self.numchunks then self:Remove(true) return end + + self:Request() + end, + + GetProgress = function(self) + return #self.chunks/self.numchunks + end, + + Remove = function(self, finished) + timer.Remove("NetStreamReadTimeout" .. self.identifier) + + local data + if finished then + data = table.concat(self.chunks) + if self.compressed then + data = util.Decompress(data, net.Stream.MaxSize) + end + self:WriteRequest() -- Notify we finished + end + + local ok, err = xpcall(self.callback, debug.traceback, data) + if not ok then ErrorNoHalt(err) end + + net.Stream.ReadStreams:Remove(self) + end + }, + __call = function(t, ply, callback, numchunks, identifier, compressed) + return setmetatable({ + identifier = identifier, + chunks = {}, + compressed = compressed, + numchunks = numchunks, + callback = callback, + player = ply, + downloads = 0 + }, t) + end +} +setmetatable(ReadingDataItem, ReadingDataItem) + + +function net.WriteStream(data, callback, dontcompress) + if not isstring(data) then + error("bad argument #1 to 'WriteStream' (string expected, got " .. type(data) .. ")", 2) + end + if callback ~= nil and not isfunction(callback) then + error("bad argument #2 to 'WriteStream' (function expected, got " .. type(callback) .. ")", 2) + end + + local compressed = not dontcompress + if compressed then + data = util.Compress(data) or "" + end + + if #data == 0 then + net.WriteUInt(0, 32) + return + end + + if #data > net.Stream.MaxSize then + ErrorNoHalt("net.WriteStream request is too large! ", #data/1048576, "MiB") + net.WriteUInt(0, 32) + return + end + + local stream = net.Stream.WriteStreams:Add(WritingDataItem(data, callback, compressed)) + if not stream then return end + + --print("WriteStream", #stream.chunks, stream.identifier, compressed) + net.WriteUInt(#stream.chunks, 32) + net.WriteUInt(stream.identifier, 32) + net.WriteBool(compressed) + + return stream +end + +--If the receiver is a player then add it to a queue. +--If the receiver is the server then add it to a queue for each individual player +function net.ReadStream(ply, callback) + if CLIENT then + ply = NULL + else + if type(ply) ~= "Player" then + error("bad argument #1 to 'ReadStream' (Player expected, got " .. type(ply) .. ")", 2) + elseif not ply:IsValid() then + error("bad argument #1 to 'ReadStream' (Tried to use a NULL entity!)", 2) + end + end + if not isfunction(callback) then + error("bad argument #2 to 'ReadStream' (function expected, got " .. type(callback) .. ")", 2) + end + + local numchunks = net.ReadUInt(32) + if numchunks == nil then + return + elseif numchunks == 0 then + local ok, err = xpcall(callback, debug.traceback, "") + if not ok then ErrorNoHalt(err) end + return + end + + local identifier = net.ReadUInt(32) + local compressed = net.ReadBool() + + if numchunks > net.Stream.MaxChunks then + ErrorNoHalt("ReadStream requests from ", ply, " is too large! ", numchunks * net.Stream.SendSize / 1048576, "MiB") + return + end + + --print("ReadStream", numchunks, identifier, compressed) + + return net.Stream.ReadStreams:Add(ReadingDataItem(ply, callback, numchunks, identifier, compressed)) +end + +if SERVER then + util.AddNetworkString("NetStreamWrite") + util.AddNetworkString("NetStreamRead") +end + +--Send requested stream data +net.Receive("NetStreamWrite", function(len, ply) + net.Stream.WriteStreams:Write(ply or NULL) +end) + +--Download the sent stream data +net.Receive("NetStreamRead", function(len, ply) + net.Stream.ReadStreams:Read(ply or NULL) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/cl_drawent.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/cl_drawent.lua new file mode 100644 index 0000000..f6a6cda --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/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) diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/cl_menu.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/cl_menu.lua new file mode 100644 index 0000000..1e17b17 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/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/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_lib.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_lib.lua new file mode 100644 index 0000000..b3d2d3d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/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/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_menu.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_menu.lua new file mode 100644 index 0000000..122b298 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/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/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_permissions.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_permissions.lua new file mode 100644 index 0000000..237687f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/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/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_specialfcn.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_specialfcn.lua new file mode 100644 index 0000000..addbaa6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/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/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_sql.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/permaprops/sv_sql.lua new file mode 100644 index 0000000..a90d539 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/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 diff --git a/garrysmod/gamemodes/militaryrp/plugins/buildtools/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/buildtools/sh_plugin.lua new file mode 100644 index 0000000..17f2d40 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/buildtools/sh_plugin.lua @@ -0,0 +1,80 @@ +PLUGIN.name = "Build Tools" +PLUGIN.author = "Various (Портирован для Helix)" +PLUGIN.description = "Набор строительных инструментов: AdvDupe2, Permaprops, Precision Tool" + +-- Устанавливаем высокий приоритет загрузки, чтобы плагин загрузился раньше инструментов +PLUGIN.priority = 100 + +-- Конфигурация +ix.config.Add("buildToolsEnabled", true, "Включить строительные инструменты", nil, { + category = "Build Tools" +}) + +ix.config.Add("buildToolsAdvDupe2", true, "Включить AdvDupe2 (дублирование)", nil, { + category = "Build Tools" +}) + +ix.config.Add("buildToolsPermaprops", true, "Включить Permaprops (постоянные пропы)", nil, { + category = "Build Tools" +}) + +ix.config.Add("buildToolsPrecision", true, "Включить Precision Tool (точное позиционирование)", nil, { + category = "Build Tools" +}) + +ix.config.Add("buildToolsNoCollideWorld", true, "Включить NoCollide World (отключение коллизий с миром)", nil, { + category = "Build Tools" +}) + +-- Загрузка NetStream (библиотека для сетевых сообщений) +ix.util.Include("netstream.lua") + +-- Загрузка AdvDupe2 +if ix.config.Get("buildToolsAdvDupe2", true) then + -- Инициализация глобальной таблицы AdvDupe2 + AdvDupe2 = AdvDupe2 or {} + print("[Build Tools] Initializing AdvDupe2...") + + -- Shared + ix.util.Include("advdupe2/sh_codec_legacy.lua") + ix.util.Include("advdupe2/sh_codec.lua") + + -- Server + ix.util.Include("advdupe2/sv_file.lua") + ix.util.Include("advdupe2/sv_ghost.lua") + ix.util.Include("advdupe2/sv_clipboard.lua") + ix.util.Include("advdupe2/sv_misc.lua") + + -- Client + ix.util.Include("advdupe2/cl_file.lua") + ix.util.Include("advdupe2/cl_ghost.lua") + ix.util.Include("advdupe2/file_browser.lua") + + print("[Build Tools] AdvDupe2 loaded successfully") +end + +-- Загрузка Permaprops +if ix.config.Get("buildToolsPermaprops", true) then + -- Server + ix.util.Include("permaprops/sv_lib.lua") + ix.util.Include("permaprops/sv_menu.lua") + ix.util.Include("permaprops/sv_permissions.lua") + ix.util.Include("permaprops/sv_specialfcn.lua") + ix.util.Include("permaprops/sv_sql.lua") + + -- Client + ix.util.Include("permaprops/cl_drawent.lua") + ix.util.Include("permaprops/cl_menu.lua") +end + +function PLUGIN:Initialize() + print("[Build Tools] Загружены строительные инструменты!") + + if ix.config.Get("buildToolsAdvDupe2", true) then + print("[Build Tools] AdvDupe2 активирован") + end + + if ix.config.Get("buildToolsPermaprops", true) then + print("[Build Tools] Permaprops активирован") + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/capture/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/capture/cl_plugin.lua new file mode 100644 index 0000000..b2bacdc --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/capture/cl_plugin.lua @@ -0,0 +1,135 @@ +PLUGIN.captureData = PLUGIN.captureData or {} + +net.Receive("ixCaptureSync", function() + local plugin = ix.plugin.Get("capture") + if (!plugin) then return end + + local point = net.ReadEntity() + local progress = net.ReadFloat() + local owner = net.ReadUInt(8) + local capturing = net.ReadUInt(8) + local players = net.ReadUInt(8) + local blocked = net.ReadBool() + + if (IsValid(point)) then + plugin.captureData[point] = { + progress = progress, + owner = owner, + capturing = capturing, + players = players, + blocked = blocked + } + end +end) + +local PANEL = {} + +function PANEL:Init() + self.progress = 0 + self.targetProgress = 0 + self.alpha = 0 + self.targetAlpha = 0 + self.blinkAlpha = 0 +end + +function PANEL:SetCaptureData(data) + self.targetProgress = data.progress or 0 + self.owner = data.owner or 0 + self.capturing = data.capturing or 0 + self.players = data.players or 0 + self.blocked = data.blocked or false + + if (data.capturing and data.capturing > 0) then + self.targetAlpha = 255 + else + self.targetAlpha = 0 + end +end + +function PANEL:Think() + self.progress = Lerp(FrameTime() * 5, self.progress, self.targetProgress) + self.alpha = Lerp(FrameTime() * 8, self.alpha, self.targetAlpha) + + if (self.blocked) then + self.blinkAlpha = math.abs(math.sin(CurTime() * 5)) * 255 + else + self.blinkAlpha = 0 + end + + local plugin = ix.plugin.Get("capture") + if (plugin) then + self:SetSize(plugin.hudWidth, plugin.hudHeight) + self:SetPos(ScrW() - plugin.hudWidth - plugin.hudPosX, plugin.hudPosY) + end +end + +function PANEL:Paint(w, h) + if (self.alpha < 1) then return end + + surface.SetAlphaMultiplier(self.alpha / 255) + + local barW = 40 + local barH = h + local padding = 10 + local barX = (w - barW) / 2 + + draw.RoundedBox(8, barX, 0, barW, barH, ColorAlpha(Color(20, 20, 20), self.alpha * 0.9)) + + if (self.progress > 0) then + local fillH = (barH - 4) * (self.progress / 100) + local color = Color(100, 150, 255) + + if (self.capturing and self.capturing > 0) then + if (self.capturing == FACTION_RUSSIAN) then + color = Color(200, 50, 50) + elseif (self.capturing == FACTION_UKRAINE) then + color = Color(50, 100, 200) + end + end + + if (self.blocked) then + color = ColorAlpha(color, self.blinkAlpha) + end + + draw.RoundedBox(6, barX + 2, 2, barW - 4, fillH, ColorAlpha(color, self.alpha)) + end + + local text = string.format("%.0f%%", self.progress) + if (self.players and self.players > 0) then + text = text .. "\n(" .. self.players .. ")" + end + + draw.SimpleText(text, "DermaDefault", w / 2, barH + 15, ColorAlpha(color_white, self.alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + + surface.SetAlphaMultiplier(1) +end + +vgui.Register("ixCaptureUI", PANEL, "DPanel") + +function PLUGIN:HUDPaint() + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + for point, data in pairs(self.captureData) do + if (IsValid(point) and ply:GetPos():Distance(point:GetPos()) <= self.captureRadius) then + if (!IsValid(self.captureUI)) then + self.captureUI = vgui.Create("ixCaptureUI") + end + + self.captureUI:SetCaptureData(data) + return + end + end + + if (IsValid(self.captureUI)) then + self.captureUI:SetCaptureData({progress = 0, owner = 0, capturing = 0, players = 0, blocked = false}) + end +end + +function PLUGIN:OnReloaded() + if (IsValid(self.captureUI)) then + self.captureUI:Remove() + self.captureUI = nil + end +end + diff --git a/garrysmod/gamemodes/militaryrp/plugins/capture/entities/entities/ix_capture_point.lua b/garrysmod/gamemodes/militaryrp/plugins/capture/entities/entities/ix_capture_point.lua new file mode 100644 index 0000000..1a8f4b8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/capture/entities/entities/ix_capture_point.lua @@ -0,0 +1,117 @@ +ENT.Type = "anim" +ENT.PrintName = "Точка захвата" +ENT.Category = "[FT] Захват точек" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.bNoPersist = true + +function ENT:SetupDataTables() + self:NetworkVar("Float", 0, "CaptureProgress") + self:NetworkVar("Int", 0, "OwnerFaction") + self:NetworkVar("Int", 1, "FlagSkin") +end + +if SERVER then + function ENT:Initialize() + self:SetModel("models/flag/ua_flag_rework.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if (IsValid(phys)) then + phys:EnableMotion(false) + end + + -- Установка начального скина (никто не захватил) + self:SetFlagSkin(2) + self:SetSkin(2) + self:SetOwnerFaction(0) + + local plugin = ix.plugin.Get("capture") + if (plugin) then + plugin:RegisterCapturePoint(self) + end + end + + -- Функция для установки владельца с автоматической сменой скина + function ENT:SetOwnerFactionID(factionID) + self:SetOwnerFaction(factionID or 0) + + local skinID = 2 -- По умолчанию никто + + if factionID == FACTION_RUSSIAN then + skinID = 1 -- РФ - скин 1 + elseif factionID == FACTION_UKRAINE then + skinID = 0 -- ВСУ - скин 0 + end + + self:SetFlagSkin(skinID) + self:SetSkin(skinID) + end + + function ENT:Use(activator, caller) + end +else + function ENT:Draw() + self:DrawModel() + + -- Синхронизация скина с NetworkVar + local skinID = self:GetFlagSkin() + if skinID and self:GetSkin() ~= skinID then + self:SetSkin(skinID) + end + + --local plugin = ix.plugin.Get("capture") + --if (!plugin or !plugin.captureData[self]) then return end + -- + --local data = plugin.captureData[self] + --local pos = self:GetPos() + Vector(0, 0, plugin.labelOffsetZ) + --local ang = EyeAngles() + --ang:RotateAroundAxis(ang:Forward(), 90) + --ang:RotateAroundAxis(ang:Right(), 90) + + --cam.Start3D2D(pos, Angle(0, ang.y, 90), plugin.labelScale) + -- if (data.capturing and data.capturing > 0) then + -- local barW = 400 + -- local barH = 60 + -- local x, y = -barW / 2, -barH / 2 + -- + -- draw.RoundedBox(8, x, y - 80, barW, barH, Color(20, 20, 20, 230)) + -- + -- local fillW = (barW - 8) * (data.progress / 100) + -- local color = Color(100, 150, 255) + -- + -- if (data.capturing == FACTION_RUSSIAN) then + -- color = Color(200, 50, 50) + -- elseif (data.capturing == FACTION_UKRAINE) then + -- color = Color(50, 100, 200) + -- end + -- + -- draw.RoundedBox(6, x + 4, y - 76, fillW, barH - 8, color) + -- + -- local text = string.format("%.0f%%", data.progress) + -- draw.SimpleText(text, "DermaLarge", 0, -80, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + -- end + -- + -- local ownerText = "Никем не захвачена" + -- local ownerColor = Color(150, 150, 150) + -- + -- if (data.owner and data.owner > 0) then + -- local faction = ix.faction.Get(data.owner) + -- if (faction) then + -- ownerText = faction.name + -- if (data.owner == FACTION_RUSSIAN) then + -- ownerColor = Color(200, 50, 50) + -- elseif (data.owner == FACTION_UKRAINE) then + -- ownerColor = Color(50, 100, 200) + -- end + -- end + -- end + -- + -- draw.SimpleText(ownerText, "DermaLarge", 0, 0, ownerColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + --cam.End3D2D() + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/capture/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/capture/sh_plugin.lua new file mode 100644 index 0000000..84f669d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/capture/sh_plugin.lua @@ -0,0 +1,44 @@ +PLUGIN.name = "Capture Points" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "Система захвата точек" + +PLUGIN.captureRadius = 1024 +PLUGIN.captureBaseTime = 35 +PLUGIN.moneyReward = 3000 +PLUGIN.arsenalReward = 500 +PLUGIN.vehicleReward = 500 + +-- Настройки периодических наград +PLUGIN.tickInterval = 60 -- Интервал начисления в секундах +PLUGIN.tickSupplyPerPoint = 200 -- Очки снабжения за одну точку +PLUGIN.tickVehiclePerPoint = 150 -- Очки техники за одну точку + +PLUGIN.hudPosX = 0 +PLUGIN.hudPosY = 20 +PLUGIN.hudWidth = 200 +PLUGIN.hudHeight = 300 + +PLUGIN.labelOffsetZ = 80 +PLUGIN.labelScale = 0.1 + +PLUGIN.factionMinOnline = { + [FACTION_RUSSIAN] = 1, + [FACTION_UKRAINE] = 0 +} + +PLUGIN.defaultPoints = { + {pos = Vector(-6788.718262, -2758.777832, 620.980103), ang = Angle(0, 90, 0)}, + {pos = Vector(-386.355896, -13121.211914, 139.868973), ang = Angle(0, 90, 0)}, + + {pos = Vector(4969.474121, -10055.811523, 114.868973), ang = Angle(0, 90, 0)}, + {pos = Vector(15625.968750, -13238.951172, 282.868958), ang = Angle(0, 90, 0)}, + {pos = Vector(12809.052734, -4790.340820, 115.868973), ang = Angle(0, 90, 0)}, + {pos = Vector(13539.985352, 4858.571289, 121.868973), ang = Angle(0, 90, 0)}, + {pos = Vector(1916.585205, 2236.532715, 226.868958), ang = Angle(0, 90, 0)}, + {pos = Vector(5527.783203, 2510.990479, 736.868958), ang = Angle(0, 90, 0)}, +} + +PLUGIN.minCapturePlayers = 1 + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/capture/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/capture/sv_plugin.lua new file mode 100644 index 0000000..740e094 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/capture/sv_plugin.lua @@ -0,0 +1,414 @@ +util.AddNetworkString("ixCaptureSync") + +PLUGIN.capturePoints = PLUGIN.capturePoints or {} + +function PLUGIN:GetPlayersInRadius(point, radius) + local players = {} + local pos = point:GetPos() + + for _, ply in ipairs(player.GetAll()) do + if ply:Alive() and pos:Distance(ply:GetPos()) <= radius and not (ply.IsAdminMode and ply:IsAdminMode()) then + table.insert(players, ply) + end + end + + return players +end + +function PLUGIN:GetFactionFromPlayers(players) + local factions = {} + + for _, ply in ipairs(players) do + local char = ply:GetCharacter() + if (char) then + local faction = char:GetFaction() + factions[faction] = (factions[faction] or 0) + 1 + end + end + + return factions +end + +function PLUGIN:GetFactionOnlineCount(faction) + local count = 0 + + for _, ply in ipairs(player.GetAll()) do + local char = ply:GetCharacter() + if char and char:GetFaction() == faction and not (ply.IsAdminMode and ply:IsAdminMode()) then + count = count + 1 + end + end + + return count +end + +function PLUGIN:CanFactionCapture(faction) + local minOnline = self.factionMinOnline[faction] + if (!minOnline) then return true end + + local online = self:GetFactionOnlineCount(faction) + return online >= minOnline +end + +function PLUGIN:CanCapture(factions) + local count = 0 + for _ in pairs(factions) do + count = count + 1 + end + + return count == 1 +end + +function PLUGIN:GiveRewards(faction, players) + for _, ply in ipairs(players) do + local char = ply:GetCharacter() + if (char) then + char:GiveMoney(self.moneyReward) + ply:Notify("Вы получили " .. self.moneyReward .. " за захват точки!") + end + end + + if (ix.plugin.list["arsenal"]) then + ix.plugin.list["arsenal"]:AddFactionSupply(faction, self.arsenalReward) + end + + if (ix.plugin.list["vehicles"]) then + ix.plugin.list["vehicles"]:AddFactionPoints(faction, self.vehicleReward) + end + + -- Логируем выдачу наград + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + local message = string.format("Фракция '%s' получила награду за захват: %d снабжения, %d очков техники, %d денег на игрока", + factionName, self.arsenalReward, self.vehicleReward, self.moneyReward) + serverlogsPlugin:AddLog("CAPTURE_REWARD", message, nil, { + faction = faction, + factionName = factionName, + arsenalReward = self.arsenalReward, + vehicleReward = self.vehicleReward, + moneyReward = self.moneyReward, + playerCount = #players + }) + end +end + +function PLUGIN:ProcessCapture(point) + local players = self:GetPlayersInRadius(point, self.captureRadius) + + if (#players == 0) then + if point.capturingFaction != nil then + point.capturingFaction = nil + point.capturingPlayers = 0 + self:SyncCapturePoint(point) + end + return + end + + local factions = self:GetFactionFromPlayers(players) + + if (!self:CanCapture(factions)) then + if point.capturingFaction != nil or !point.captureBlocked then + point.capturingFaction = nil + point.capturingPlayers = 0 + point.captureBlocked = true + self:SyncCapturePoint(point) + end + return + end + + point.captureBlocked = false + + local faction, playerCount = next(factions) + + if (playerCount < self.minCapturePlayers) then + if point.capturingFaction != nil or !point.captureBlocked then + point.capturingFaction = nil + point.capturingPlayers = 0 + point.captureBlocked = true + self:SyncCapturePoint(point) + end + return + end + + if (!self:CanFactionCapture(faction)) then + if point.capturingFaction != nil or !point.captureBlocked then + point.capturingFaction = nil + point.capturingPlayers = 0 + point.captureBlocked = true + self:SyncCapturePoint(point) + end + return + end + + if (point.ownerFaction == faction) then + if point.captureProgress != 100 or point.capturingFaction != nil then + point.captureProgress = 100 + point.capturingFaction = nil + point.capturingPlayers = 0 + self:SyncCapturePoint(point) + end + return + end + + -- Логируем начало захвата при первом касании + if (!point.capturingFaction or point.capturingFaction != faction) then + -- Сбрасываем прогресс если начинается захват другой фракцией + point.captureProgress = 0 + + -- Уведомляем владельцев о захвате + if (point.ownerFaction and point.ownerFaction != 0 and point.ownerFaction != faction) then + local capturerName = ix.faction.Get(faction).name or "Противник" + for _, v in ipairs(player.GetAll()) do + local char = v:GetCharacter() + if (char and char:GetFaction() == point.ownerFaction) then + v:Notify("ВНИМАНИЕ! Ваша точка захватывается фракцией " .. capturerName .. "!") + end + end + end + + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + local pointPos = tostring(point:GetPos()) + local message = string.format("Фракция '%s' начала захват точки (позиция: %s)", factionName, pointPos) + serverlogsPlugin:AddLog("CAPTURE_START", message, nil, { + faction = faction, + factionName = factionName, + playerCount = playerCount, + pointPosition = pointPos + }) + end + end + + point.capturingFaction = faction + point.capturingPlayers = playerCount + + local baseSpeed = 100 / self.captureBaseTime -- Progress per second + local captureSpeed = baseSpeed * playerCount * FrameTime() + + point.captureProgress = (point.captureProgress or 0) + captureSpeed + + if (point.captureProgress >= 100) then + point.captureProgress = 100 + local oldOwner = point.ownerFaction + point.ownerFaction = faction + point:SetOwnerFactionID(faction) + point.capturingFaction = nil + point.capturingPlayers = 0 + + self:GiveRewards(faction, players) + self:SaveData() + + -- Логируем успешный захват точки + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + local pointPos = tostring(point:GetPos()) + local oldFactionData = oldOwner and ix.faction.Get(oldOwner) + local oldFactionName = (oldFactionData and oldFactionData.name) or (oldOwner and tostring(oldOwner)) or "Нейтральная" + local message = string.format("Точка захвачена фракцией '%s' (предыдущий владелец: %s)", factionName, oldFactionName) + serverlogsPlugin:AddLog("CAPTURE_COMPLETE", message, nil, { + faction = faction, + factionName = factionName, + oldOwner = oldOwner, + oldOwnerName = oldFactionName, + playerCount = playerCount, + pointPosition = pointPos + }) + end + + for _, ply in ipairs(player.GetAll()) do + local char = ply:GetCharacter() + if (char and char:GetFaction() == faction) then + ply:Notify("Точка захвачена вашей фракцией!") + end + end + end + + -- Only sync if progress changed significantly, something started/stopped, or a timer passed + local curTime = CurTime() + point.nextSync = point.nextSync or 0 + + if (point.lastSyncProgress == nil or + math.abs(point.captureProgress - point.lastSyncProgress) >= 1 or + curTime >= point.nextSync) then + + self:SyncCapturePoint(point) + point.lastSyncProgress = point.captureProgress + point.nextSync = curTime + 0.2 -- Sync at most 5 times per second + end +end + +function PLUGIN:SyncCapturePoint(point) + net.Start("ixCaptureSync") + net.WriteEntity(point) + net.WriteFloat(point.captureProgress or 0) + net.WriteUInt(point.ownerFaction or 0, 8) + net.WriteUInt(point.capturingFaction or 0, 8) + net.WriteUInt(point.capturingPlayers or 0, 8) + net.WriteBool(point.captureBlocked or false) + net.Broadcast() +end + +function PLUGIN:Think() + if (!self.nextTickReward) then + self.nextTickReward = CurTime() + (self.tickInterval or 300) + elseif (CurTime() >= self.nextTickReward) then + self:ProcessPeriodicRewards() + self.nextTickReward = CurTime() + (self.tickInterval or 300) + end + + for _, point in ipairs(self.capturePoints) do + if (IsValid(point)) then + self:ProcessCapture(point) + end + end + + if (!self.nextAutoSave or CurTime() >= self.nextAutoSave) then + self:SaveData() + self.nextAutoSave = CurTime() + 60 + end +end + +function PLUGIN:RegisterCapturePoint(point) + table.insert(self.capturePoints, point) + point.captureProgress = 0 + point.ownerFaction = nil + point:SetOwnerFactionID(0) + point.capturingFaction = nil + point.capturingPlayers = 0 +end + +function PLUGIN:EntityRemoved(entity) + if (entity:GetClass() == "ix_capture_point") then + for i, point in ipairs(self.capturePoints) do + if (point == entity) then + table.remove(self.capturePoints, i) + break + end + end + end +end + +function PLUGIN:SaveData() + local data = {} + + for _, point in ipairs(ents.FindByClass("ix_capture_point")) do + if IsValid(point) then + data[#data + 1] = { + pos = point:GetPos(), + angles = point:GetAngles(), + ownerFaction = point.ownerFaction or 0, + captureProgress = point.captureProgress or 0 + } + end + end + + self:SetData(data) +end + +function PLUGIN:LoadData() + local data = self:GetData() + + if (!data or #data == 0) then + for _, v in ipairs(self.defaultPoints or {}) do + local point = ents.Create("ix_capture_point") + if IsValid(point) then + point:SetPos(v.pos) + point:SetAngles(v.ang) + point:Spawn() + + timer.Simple(0.1, function() + if IsValid(point) then + point.ownerFaction = 0 + point:SetOwnerFactionID(0) + point.captureProgress = 0 + end + end) + end + end + + return + end + + for _, v in ipairs(data) do + local point = ents.Create("ix_capture_point") + if IsValid(point) then + point:SetPos(v.pos) + point:SetAngles(v.angles) + point:Spawn() + + timer.Simple(0.1, function() + if IsValid(point) then + point.ownerFaction = v.ownerFaction + point:SetOwnerFactionID(v.ownerFaction or 0) + point.captureProgress = v.captureProgress or 0 + + self:SyncCapturePoint(point) + end + end) + end + end +end + +function PLUGIN:OnUnloaded() + self:SaveData() +end + +-- Подсчет захваченных точек каждой фракцией +function PLUGIN:GetCapturedPointsCount() + local counts = {} + + for _, point in ipairs(self.capturePoints) do + if IsValid(point) and point.ownerFaction and point.ownerFaction ~= 0 then + counts[point.ownerFaction] = (counts[point.ownerFaction] or 0) + 1 + end + end + + return counts +end + +-- Периодическое начисление очков +function PLUGIN:ProcessPeriodicRewards() + local counts = self:GetCapturedPointsCount() + + for faction, pointCount in pairs(counts) do + if pointCount > 0 then + local supplyReward = pointCount * self.tickSupplyPerPoint + local vehicleReward = pointCount * self.tickVehiclePerPoint + + if ix.plugin.list["arsenal"] then + ix.plugin.list["arsenal"]:AddFactionSupply(faction, supplyReward) + end + + if ix.plugin.list["vehicles"] then + ix.plugin.list["vehicles"]:AddFactionPoints(faction, vehicleReward) + end + + local factionName = ix.faction.Get(faction).name or "Фракция" + for _, ply in ipairs(player.GetAll()) do + local char = ply:GetCharacter() + if char and char:GetFaction() == faction then + ply:Notify(string.format("Ваша фракция получила: +%d снабжения, +%d очков техники (точек: %d)", + supplyReward, vehicleReward, pointCount)) + end + end + end + end +end + +ix.command.Add("CaptureSystemSave", { + description = "Сохранить точки захвата", + superAdminOnly = true, + OnRun = function(self, client) + local plugin = ix.plugin.Get("capture") + if (!plugin) then + return "@commandNoExist" + end + + plugin:SaveData() + local count = #ents.FindByClass("ix_capture_point") + client:Notify("Точки захвата сохранены (" .. count .. " шт.)") + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/carrying/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/carrying/sh_plugin.lua new file mode 100644 index 0000000..74baee9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/carrying/sh_plugin.lua @@ -0,0 +1,53 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Carrying Entities" +PLUGIN.author = "Scripty" +PLUGIN.description = "Позволяет игрокам поднимать любые энтити на клавишу E через стандартную механику." + +-- Список классов, которые ЗАПРЕЩЕНО поднимать +local BLACKLIST = { + ["player"] = true, + ["worldspawn"] = true, + ["func_door"] = true, + ["func_door_rotating"] = true, + ["prop_door_rotating"] = true, + ["func_movelinear"] = true, + ["prop_dynamic"] = true, + ["ix_vendor"] = true, + ["gmod_hands"] = true, + ["viewmodel"] = true +} + +if SERVER then + -- Этот хук вызывается ядром Helix во время GM:AllowPlayerPickup + function PLUGIN:CanPlayerPickupEntity(client, entity) + if (!IsValid(entity) or entity:IsPlayer() or entity:IsWorld()) then + return false + end + + local class = entity:GetClass() + if (BLACKLIST[class]) then + return false + end + + -- Проверка защиты Helix (Prop Protection) + local char = client:GetCharacter() + local owner = entity:GetNetVar("owner", 0) + + -- Разрешаем владельцу, админу или если объект ничей + if (owner == 0 or (char and owner == char:GetID()) or client:IsAdmin()) then + local phys = entity:GetPhysicsObject() + + -- Разрешаем подбирать только то, что имеет физику + if (IsValid(phys) and phys:IsMoveable()) then + return true + end + + -- Прямое разрешение для пакетов крови (если физика считается не-moveable) + if (class == "bloodbag_medicmod") then + return true + end + end + + return false + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatbox.lua b/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatbox.lua new file mode 100644 index 0000000..335254a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatbox.lua @@ -0,0 +1,1331 @@ + +local PLUGIN = PLUGIN + +local animationTime = 0.5 +local chatBorder = 32 +local sizingBorder = 20 +local maxChatEntries = 100 + +-- called when a markup object should paint its text +local function PaintMarkupOverride(text, font, x, y, color, alignX, alignY, alpha) + alpha = alpha or 255 + + if (ix.option.Get("chatOutline", false)) then + -- outlined background for even more visibility + draw.SimpleTextOutlined(text, font, x, y, ColorAlpha(color, alpha), alignX, alignY, 1, Color(0, 0, 0, alpha)) + else + -- background for easier reading + surface.SetTextPos(x + 1, y + 1) + surface.SetTextColor(0, 0, 0, alpha) + surface.SetFont(font) + surface.DrawText(text) + + surface.SetTextPos(x, y) + surface.SetTextColor(color.r, color.g, color.b, alpha) + surface.SetFont(font) + surface.DrawText(text) + end +end + +-- chat message +local PANEL = {} + +AccessorFunc(PANEL, "fadeDelay", "FadeDelay", FORCE_NUMBER) +AccessorFunc(PANEL, "fadeDuration", "FadeDuration", FORCE_NUMBER) + +function PANEL:Init() + self.text = "" + self.alpha = 255 + self.fadeDelay = 15 + self.fadeDuration = 5 +end + +function PANEL:SetMarkup(text) + self.text = text + + self.markup = ix.markup.Parse(self.text, self:GetWide()) + self.markup.onDrawText = PaintMarkupOverride + + self:SetTall(self.markup:GetHeight()) + + timer.Simple(self.fadeDelay, function() + if (!IsValid(self)) then + return + end + + self:CreateAnimation(self.fadeDuration, { + index = 3, + target = {alpha = 0} + }) + end) +end + +function PANEL:PerformLayout(width, height) + if ((IsValid(ix.gui.chat) and ix.gui.chat.bSizing) or width == self.markup:GetWidth()) then + return + end + + self.markup = ix.markup.Parse(self.text, width) + self.markup.onDrawText = PaintMarkupOverride + + self:SetTall(self.markup:GetHeight()) +end + +function PANEL:Paint(width, height) + local newAlpha + + -- we'll want to hide the chat while some important menus are open + if (IsValid(ix.gui.characterMenu)) then + newAlpha = math.min(255 - ix.gui.characterMenu.currentAlpha, self.alpha) + elseif (IsValid(ix.gui.menu)) then + newAlpha = math.min(255 - ix.gui.menu.currentAlpha, self.alpha) + elseif (ix.gui.chat:GetActive()) then + newAlpha = math.max(ix.gui.chat.alpha, self.alpha) + else + newAlpha = self.alpha + end + + if (newAlpha < 1) then + return + end + + self.markup:draw(0, 0, nil, nil, newAlpha) +end + +vgui.Register("ixChatMessage", PANEL, "Panel") + +-- chatbox tab button +PANEL = {} + +AccessorFunc(PANEL, "bActive", "Active", FORCE_BOOL) +AccessorFunc(PANEL, "bUnread", "Unread", FORCE_BOOL) + +function PANEL:Init() + self:SetFont("ixChatFont") + self:SetContentAlignment(5) + + self.unreadAlpha = 0 +end + +function PANEL:SetUnread(bValue) + self.bUnread = bValue + + self:CreateAnimation(animationTime, { + index = 4, + target = {unreadAlpha = bValue and 1 or 0}, + easing = "outQuint" + }) +end + +function PANEL:SizeToContents() + local width, height = self:GetContentSize() + self:SetSize(width + 12, height + 6) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintChatboxTabButton", self, width, height) +end + +vgui.Register("ixChatboxTabButton", PANEL, "DButton") + +-- chatbox tab panel +-- holds all tab buttons and corresponding history panels +PANEL = {} + +function PANEL:Init() + -- holds all tab buttons + self.buttons = self:Add("Panel") + self.buttons:Dock(TOP) + self.buttons:DockPadding(1, 1, 0, 0) + self.buttons.OnMousePressed = ix.util.Bind(ix.gui.chat, ix.gui.chat.OnMousePressed) -- we want mouse events to fall through + self.buttons.OnMouseReleased = ix.util.Bind(ix.gui.chat, ix.gui.chat.OnMouseReleased) + self.buttons.Paint = function(_, width, height) + derma.SkinFunc("PaintChatboxTabs", self, width, height) + end + + self.tabs = {} +end + +function PANEL:GetTabs() + return self.tabs +end + +function PANEL:AddTab(id, filter) + local button = self.buttons:Add("ixChatboxTabButton") + button:Dock(LEFT) + button:SetText(id) -- display name is also the ID + button:SetActive(false) + button:SetMouseInputEnabled(true) + button:SizeToContents() + + button.DoClick = function(this) + self:SetActiveTab(this:GetText()) + end + + local panel = self:Add("ixChatboxHistory") + panel:SetButton(button) + panel:SetID(id) + panel:Dock(FILL) + panel:SetVisible(false) + panel:SetFilter(filter or {}) + + button.DoRightClick = function(this) + ix.gui.chat:OnTabRightClick(this, panel, panel:GetID()) + end + + self.tabs[id] = panel + return panel +end + +function PANEL:RemoveTab(id) + local tab = self.tabs[id] + + if (!tab) then + return + end + + tab:GetButton():Remove() + tab:Remove() + + self.tabs[id] = nil + + -- add default tab if we don't have any tabs left + if (table.IsEmpty(self.tabs)) then + self:AddTab(L("chat"), {}) + self:SetActiveTab(L("chat")) + elseif (id == self:GetActiveTabID()) then + -- set a different active tab if we've removed a tab that is currently active + self:SetActiveTab(next(self.tabs)) + end +end + +function PANEL:RenameTab(id, newID) + local tab = self.tabs[id] + + if (!tab) then + return + end + + tab:GetButton():SetText(newID) + tab:GetButton():SizeToContents() + + tab:SetID(newID) + + self.tabs[id] = nil + self.tabs[newID] = tab + + if (id == self:GetActiveTabID()) then + self:SetActiveTab(newID) + end +end + +function PANEL:SetActiveTab(id) + local tab = self.tabs[id] + + if (!tab) then + error("attempted to set non-existent active tab") + end + + for _, v in ipairs(self.buttons:GetChildren()) do + v:SetActive(v:GetText() == id) + end + + for _, v in pairs(self.tabs) do + v:SetVisible(v:GetID() == id) + end + + tab:GetButton():SetUnread(false) + + self.activeTab = id + self:OnTabChanged(tab) +end + +function PANEL:GetActiveTabID() + return self.activeTab +end + +function PANEL:GetActiveTab() + return self.tabs[self.activeTab] +end + +-- called when the active tab is changed +-- `panel` is the corresponding history panel +function PANEL:OnTabChanged(panel) +end + +vgui.Register("ixChatboxTabs", PANEL, "EditablePanel") + +-- chatbox history panel +-- holds individual messages in a scrollable panel +PANEL = {} + +AccessorFunc(PANEL, "filter", "Filter") -- blacklist of message classes +AccessorFunc(PANEL, "id", "ID", FORCE_STRING) +AccessorFunc(PANEL, "button", "Button") -- button panel that this panel corresponds to + +function PANEL:Init() + self:DockMargin(4, 2, 4, 4) -- smaller top margin to help blend tab button/history panel transition + self:SetPaintedManually(true) + + local bar = self:GetVBar() + bar:SetWide(0) + + self.entries = {} + self.filter = {} +end + +DEFINE_BASECLASS("Panel") -- DScrollPanel doesn't have SetVisible member +function PANEL:SetVisible(bState) + self:GetCanvas():SetVisible(bState) + BaseClass.SetVisible(self, bState) +end + +DEFINE_BASECLASS("DScrollPanel") +function PANEL:PerformLayoutInternal() + local bar = self:GetVBar() + local bScroll = !ix.gui.chat:GetActive() or bar.Scroll == bar.CanvasSize -- only scroll when we're not at the bottom/inactive + + BaseClass.PerformLayoutInternal(self) + + if (bScroll) then + self:ScrollToBottom() + end +end + +function PANEL:ScrollToBottom() + local bar = self:GetVBar() + bar:SetScroll(bar.CanvasSize) +end + +-- adds a line of text as described by its elements +function PANEL:AddLine(elements, bShouldScroll) + -- table.concat is faster than regular string concatenation where there are lots of strings to concatenate + local buffer = { + "" + } + + if (ix.option.Get("chatTimestamps", false)) then + buffer[#buffer + 1] = "(" + + if (ix.option.Get("24hourTime", false)) then + buffer[#buffer + 1] = os.date("%H:%M") + else + buffer[#buffer + 1] = os.date("%I:%M %p") + end + + buffer[#buffer + 1] = ") " + end + + if (CHAT_CLASS) then + buffer[#buffer + 1] = " ", texture, v:Width(), v:Height()) + end + elseif (istable(v) and v.r and v.g and v.b) then + buffer[#buffer + 1] = string.format("", v.r, v.g, v.b) + elseif (type(v) == "Player") then + local color = team.GetColor(v:Team()) + + buffer[#buffer + 1] = string.format("%s", color.r, color.g, color.b, + v:GetName():gsub("<", "<"):gsub(">", ">")) + else + buffer[#buffer + 1] = tostring(v):gsub("<", "<"):gsub(">", ">"):gsub("%b**", function(value) + local inner = value:utf8sub(2, -2) + + if (inner:find("%S")) then + return "" .. value:utf8sub(2, -2) .. "" + end + end) + end + end + + local panel = self:Add("ixChatMessage") + panel:Dock(TOP) + panel:InvalidateParent(true) + panel:SetMarkup(table.concat(buffer)) + + if (#self.entries >= maxChatEntries) then + local oldPanel = table.remove(self.entries, 1) + + if (IsValid(oldPanel)) then + oldPanel:Remove() + end + end + + self.entries[#self.entries + 1] = panel + return panel +end + +vgui.Register("ixChatboxHistory", PANEL, "DScrollPanel") + +PANEL = {} +DEFINE_BASECLASS("DTextEntry") + +function PANEL:Init() + self:SetFont("ixChatFont") + self:SetUpdateOnType(true) + self:SetHistoryEnabled(true) + + self.History = ix.chat.history + self.m_bLoseFocusOnClickAway = false +end + +function PANEL:SetFont(font) + BaseClass.SetFont(self, font) + + surface.SetFont(font) + local _, height = surface.GetTextSize("W@") + + self:SetTall(height + 8) +end + +function PANEL:AllowInput(newCharacter) + local text = self:GetText() + local maxLength = ix.config.Get("chatMax") + + -- we can't check for the proper length using utf-8 since AllowInput is called for single bytes instead of full characters + if (string.len(text .. newCharacter) > maxLength) then + surface.PlaySound("common/talk.wav") + return true + end +end + +function PANEL:Think() + local text = self:GetText() + local maxLength = ix.config.Get("chatMax", 256) + + if (text:utf8len() > maxLength) then + local newText = text:utf8sub(0, maxLength) + + self:SetText(newText) + self:SetCaretPos(newText:utf8len()) + end +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintChatboxEntry", self, width, height) +end + +vgui.Register("ixChatboxEntry", PANEL, "DTextEntry") + +-- chatbox additional command info panel +PANEL = {} + +AccessorFunc(PANEL, "text", "Text", FORCE_STRING) +AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER) +AccessorFunc(PANEL, "backgroundColor", "BackgroundColor") +AccessorFunc(PANEL, "textColor", "TextColor") + +function PANEL:Init() + self.text = "" + self.padding = 4 + self.currentWidth = 0 + self.currentMargin = 0 + self.backgroundColor = ix.config.Get("color") + self.textColor = color_white + + self:SetWide(0) + self:DockMargin(0, 0, 0, 0) +end + +function PANEL:SetText(text) + self:SetVisible(true) + + if (!isstring(text) or text == "") then + self:CreateAnimation(animationTime, { + index = 9, + easing = "outQuint", + target = { + currentWidth = 0, + currentMargin = 0 + }, + + Think = function(animation, panel) + panel:SetWide(panel.currentWidth) + panel:DockMargin(0, 0, panel.currentMargin, 0) + end, + + OnComplete = function(animation, panel) + panel:SetVisible(false) + self.text = "" + end + }) + else + text = tostring(text) + + surface.SetFont("ixChatFont") + local textWidth = surface.GetTextSize(text) + + self:CreateAnimation(animationTime, { + index = 9, + easing = "outQuint", + target = { + currentWidth = textWidth + self.padding * 2, + currentMargin = 4 + }, + + Think = function(animation, panel) + panel:SetWide(panel.currentWidth) + panel:DockMargin(0, 0, panel.currentMargin, 0) + end, + }) + + self.text = text + end +end + +function PANEL:Paint(width, height) + derma.SkinFunc("DrawChatboxPrefixBox", self, width, height) + + surface.SetFont("ixChatFont") + local textWidth, textHeight = surface.GetTextSize(self.text) + + surface.SetTextColor(self.textColor) + surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5) + surface.DrawText(self.text) +end + +vgui.Register("ixChatboxPrefix", PANEL, "Panel") + +-- chatbox command preview panel +PANEL = {} +DEFINE_BASECLASS("Panel") + +AccessorFunc(PANEL, "targetHeight", "TargetHeight", FORCE_NUMBER) +AccessorFunc(PANEL, "command", "Command", FORCE_STRING) + +function PANEL:Init() + self:SetTall(0) + self:SetVisible(false, true) + + self.height = 0 + self.targetHeight = 16 + self.margin = 0 + + self.command = "" +end + +function PANEL:SetCommand(command) + -- if we're setting it to an empty command, then we'll hold the reference to the old command table to render it for the + -- fade out animation + if (command == "") then + self.command = "" + ix.chat.currentCommand = "" + + return + end + + local commandTable = ix.command.list[command] + + if (!commandTable) then + return + end + + self.command = command + self.commandTable = commandTable + self.arguments = {} + + ix.chat.currentCommand = command:lower() +end + +function PANEL:UpdateArguments(text) + if (self.command == "") then + ix.chat.currentArguments = {} + return + end + + local commandName = text:match("(/(%w+)%s)") or self.command -- we could be using a chat class prefix and not a proper command + local givenArguments = ix.command.ExtractArgs(text:utf8sub(commandName:utf8len())) + local commandArguments = self.commandTable.arguments or {} + local arguments = {} + + -- we want to concat any text types so they show up as one argument at the end of the list, this is so the argument + -- highlighting is accurate since ExtractArgs will not account because it has no type context + for k, v in ipairs(givenArguments) do + if (k == #commandArguments) then + arguments[#arguments + 1] = table.concat(givenArguments, " ", k) + break + end + + arguments[#arguments + 1] = v + end + + self.arguments = arguments + ix.chat.currentArguments = table.Copy(arguments) +end + +-- returns the target SetVisible value +function PANEL:IsOpen() + return self.bOpen +end + +function PANEL:SetVisible(bValue, bForce) + if (bForce) then + BaseClass.SetVisible(self, bValue) + return + end + + BaseClass.SetVisible(self, true) -- make sure this panel is visible during animation + self.bOpen = bValue + + self:CreateAnimation(animationTime * 0.5, { + index = 5, + target = { + height = bValue and self.targetHeight or 0, + margin = bValue and 4 or 0 + }, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetTall(math.ceil(panel.height)) + panel:DockMargin(4, 0, 4, math.ceil(panel.margin)) + end, + + OnComplete = function(animation, panel) + BaseClass.SetVisible(panel, bValue) + end + }) +end + +function PANEL:Paint(width, height) + local command = self.commandTable + + if (!command) then + return + end + + local color = ix.config.Get("color") + surface.SetFont("ixChatFont") + + -- command name + local x = derma.SkinFunc("DrawChatboxPreviewBox", 0, 0, "/" .. command.name) + 6 + + -- command arguments + if (istable(command.arguments)) then + for k, v in ipairs(command.arguments) do + local bOptional = bit.band(v, ix.type.optional) > 0 + local type = bOptional and bit.bxor(v, ix.type.optional) or v + + x = x + derma.SkinFunc( + "DrawChatboxPreviewBox", x, 0, + -- draw text in format of or [name: type] if it's optional + string.format(bOptional and "[%s: %s]" or "<%s: %s>", command.argumentNames[k], ix.type[type]), + -- fill in the color for arguments that are before the one the user is currently typing, otherwise draw a faded + -- color instead (optional arguments will not have any background color unless it's been filled out by user) + (k <= #self.arguments) and color or (bOptional and Color(0, 0, 0, 66) or ColorAlpha(color, 100)) + ) + 6 + end + end +end + +vgui.Register("ixChatboxPreview", PANEL, "Panel") + +-- chatbox autocomplete panel +-- holds and displays similar commands based on the textentry +PANEL = {} +DEFINE_BASECLASS("Panel") + +AccessorFunc(PANEL, "maxEntries", "MaxEntries", FORCE_NUMBER) + +function PANEL:Init() + self:SetVisible(false, true) + self:SetMouseInputEnabled(true) + + self.maxEntries = 20 + self.currentAlpha = 0 + + self.commandIndex = 0 -- currently selected entry in command list + self.commands = {} + self.commandPanels = {} +end + +function PANEL:GetCommands() + return self.commands +end + +function PANEL:IsOpen() + return self.bOpen +end + +function PANEL:SetVisible(bValue, bForce) + if (bForce) then + BaseClass.SetVisible(self, bValue) + return + end + + BaseClass.SetVisible(self, true) -- make sure this panel is visible during animation + self.bOpen = bValue + + self:CreateAnimation(animationTime, { + index = 6, + target = { + currentAlpha = bValue and 255 or 0 + }, + easing = "outQuint", + + Think = function(animation, panel) + panel:SetAlpha(math.ceil(panel.currentAlpha)) + end, + + OnComplete = function(animation, panel) + BaseClass.SetVisible(panel, bValue) + + if (!bValue) then + self.commands = {} + end + end + }) +end + +function PANEL:Update(text) + local commands = ix.command.FindAll(text, true, true, true) + + self.commandIndex = 0 -- reset the command index because the command list could be different + self.commands = {} + + for _, v in ipairs(self.commandPanels) do + v:Remove() + end + + self.commandPanels = {} + + -- manually loop over the found commands so we can ignore commands the user doesn't have access to + local i = 1 + local bSelected -- just to make sure we don't reset it during the loop for whatever reason + + for _, v in ipairs(commands) do + -- @todo chat classes aren't checked since they're done through the class's OnCanSay callback + if (v.OnCheckAccess and !v:OnCheckAccess(LocalPlayer())) then + continue + end + + local panel = self:Add("ixChatboxAutocompleteEntry") + panel:SetCommand(v) + + if (!bSelected and text:utf8lower():utf8sub(1, v.uniqueID:utf8len()) == v.uniqueID) then + panel:SetHighlighted(true) + + self.commandIndex = i + bSelected = true + end + + self.commandPanels[i] = panel + self.commands[i] = v + + if (i == self.maxEntries) then + break + end + + i = i + 1 + end +end + +-- selects the next entry in the autocomplete if possible and returns the text that should replace the textentry +function PANEL:SelectNext() + -- wrap back to beginning if we're past the end + if (self.commandIndex == #self.commands) then + self.commandIndex = 1 + else + self.commandIndex = self.commandIndex + 1 + end + + for k, v in ipairs(self.commandPanels) do + if (k == self.commandIndex) then + v:SetHighlighted(true) + self:ScrollToChild(v) + else + v:SetHighlighted(false) + end + end + + return "/" .. self.commands[self.commandIndex].uniqueID +end + +function PANEL:Paint(width, height) + ix.util.DrawBlur(self) + + surface.SetDrawColor(0, 0, 0, 200) + surface.DrawRect(0, 0, width, height) +end + +vgui.Register("ixChatboxAutocomplete", PANEL, "DScrollPanel") + +-- autocomplete entry +PANEL = {} + +AccessorFunc(PANEL, "bSelected", "Highlighted", FORCE_BOOL) + +function PANEL:Init() + self:Dock(TOP) + + self.name = self:Add("DLabel") + self.name:Dock(TOP) + self.name:DockMargin(4, 4, 0, 0) + self.name:SetContentAlignment(4) + self.name:SetFont("ixChatFont") + self.name:SetTextColor(ix.config.Get("color")) + self.name:SetExpensiveShadow(1, color_black) + + self.description = self:Add("DLabel") + self.description:Dock(BOTTOM) + self.description:DockMargin(4, 4, 0, 4) + self.description:SetContentAlignment(4) + self.description:SetFont("ixChatFont") + self.description:SetTextColor(color_white) + self.description:SetExpensiveShadow(1, color_black) + + self.highlightAlpha = 0 +end + +function PANEL:SetHighlighted(bValue) + self:CreateAnimation(animationTime * 2, { + index = 7, + target = {highlightAlpha = bValue and 1 or 0}, + easing = "outQuint" + }) + + self.bHighlighted = true +end + +function PANEL:SetCommand(command) + local description = command:GetDescription() + + self.name:SetText("/" .. command.name) + + if (description and description != "") then + self.description:SetText(command:GetDescription()) + else + self.description:SetVisible(false) + end + + self:SizeToContents() + self.command = command +end + +function PANEL:SizeToContents() + local bDescriptionVisible = self.description:IsVisible() + local _, height = self.name:GetContentSize() + + self.name:SetTall(height) + + if (bDescriptionVisible) then + _, height = self.description:GetContentSize() + self.description:SetTall(height) + else + self.description:SetTall(0) + end + + self:SetTall(self.name:GetTall() + self.description:GetTall() + (bDescriptionVisible and 12 or 8)) +end + +function PANEL:Paint(width, height) + derma.SkinFunc("PaintChatboxAutocompleteEntry", self, width, height) +end + +vgui.Register("ixChatboxAutocompleteEntry", PANEL, "Panel") + +-- main chatbox panel +-- this contains the text entry, tab sheets, and callbacks for other panel events +PANEL = {} + +AccessorFunc(PANEL, "bActive", "Active", FORCE_BOOL) + +function PANEL:Init() + ix.gui.chat = self + + self:SetSize(self:GetDefaultSize()) + self:SetPos(self:GetDefaultPosition()) + + local entryPanel = self:Add("Panel") + entryPanel:SetZPos(1) + entryPanel:Dock(BOTTOM) + entryPanel:DockMargin(4, 0, 4, 4) + + self.entry = entryPanel:Add("ixChatboxEntry") + self.entry:Dock(FILL) + self.entry.OnValueChange = ix.util.Bind(self, self.OnTextChanged) + self.entry.OnKeyCodeTyped = ix.util.Bind(self, self.OnKeyCodeTyped) + self.entry.OnEnter = ix.util.Bind(self, self.OnMessageSent) + + self.prefix = entryPanel:Add("ixChatboxPrefix") + self.prefix:Dock(LEFT) + + self.preview = self:Add("ixChatboxPreview") + self.preview:SetZPos(2) -- ensure the preview is docked above the text entry + self.preview:Dock(BOTTOM) + self.preview:SetTargetHeight(self.entry:GetTall()) + + self.tabs = self:Add("ixChatboxTabs") + self.tabs:Dock(FILL) + self.tabs.OnTabChanged = ix.util.Bind(self, self.OnTabChanged) + + self.autocomplete = self.tabs:Add("ixChatboxAutocomplete") + self.autocomplete:Dock(FILL) + self.autocomplete:DockMargin(4, 3, 4, 4) -- top margin is 3 to account for tab 1px border + self.autocomplete:SetZPos(3) + + self.alpha = 0 + self:SetActive(false) + + -- luacheck: globals chat + chat.GetChatBoxPos = function() + return self:GetPos() + end + + chat.GetChatBoxSize = function() + return self:GetSize() + end +end + +function PANEL:GetDefaultSize() + return ScrW() * 0.4, ScrH() * 0.375 +end + +function PANEL:GetDefaultPosition() + return chatBorder, ScrH() - self:GetTall() - chatBorder +end + +DEFINE_BASECLASS("Panel") +function PANEL:SetAlpha(amount, duration) + self:CreateAnimation(duration or animationTime, { + index = 1, + target = {alpha = amount}, + easing = "outQuint", + + Think = function(animation, panel) + BaseClass.SetAlpha(panel, panel.alpha) + end + }) +end + +function PANEL:SizingInBounds() + local screenX, screenY = self:LocalToScreen(0, 0) + local mouseX, mouseY = gui.MousePos() + + return mouseX > screenX + self:GetWide() - sizingBorder and mouseY > screenY + self:GetTall() - sizingBorder +end + +function PANEL:DraggingInBounds() + local _, screenY = self:LocalToScreen(0, 0) + local mouseY = gui.MouseY() + + return mouseY > screenY and mouseY < screenY + self.tabs.buttons:GetTall() +end + +function PANEL:SetActive(bActive) + if (bActive) then + self:SetAlpha(255) + self:MakePopup() + self.entry:RequestFocus() + + input.SetCursorPos(self:LocalToScreen(-1, -1)) + + hook.Run("StartChat") + self.prefix:SetText(hook.Run("GetChatPrefixInfo", "")) + else + -- make sure we aren't still sizing/dragging anything + if (self.bSizing or self.DragOffset) then + self:OnMouseReleased(MOUSE_LEFT) + end + + self:SetAlpha(0) + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + + self.autocomplete:SetVisible(false) + self.preview:SetVisible(false) + self.entry:SetText("") + self.preview:SetCommand("") + self.prefix:SetText(hook.Run("GetChatPrefixInfo", "")) + + CloseDermaMenus() + gui.EnableScreenClicker(false) + + hook.Run("FinishChat") + end + + local tab = self.tabs:GetActiveTab() + + if (tab) then + -- we'll scroll to bottom even if we're opening since the SetVisible for the textentry will shift things a bit + tab:ScrollToBottom() + end + + self.bActive = tobool(bActive) +end + +function PANEL:SetupTabs(tabs) + if (!tabs or table.IsEmpty(tabs)) then + self.tabs:AddTab(L("chat"), {}) + self.tabs:SetActiveTab(L("chat")) + + return + end + + for id, filter in pairs(tabs) do + self.tabs:AddTab(id, filter) + end + + self.tabs:SetActiveTab(next(tabs)) +end + +function PANEL:SetupPosition(info) + local x, y, width, height + + if (!istable(info)) then + x, y = self:GetDefaultPosition() + width, height = self:GetDefaultSize() + else + -- screen size may have changed so we'll need to clamp the values + width = math.Clamp(info[3], 32, ScrW() - chatBorder * 2) + height = math.Clamp(info[4], 32, ScrH() - chatBorder * 2) + x = math.Clamp(info[1], 0, ScrW() - width) + y = math.Clamp(info[2], 0, ScrH() - height) + end + + self:SetSize(width, height) + self:SetPos(x, y) + + PLUGIN:SavePosition() +end + +function PANEL:OnMousePressed(key) + if (key == MOUSE_RIGHT) then + local menu = DermaMenu() + menu:AddOption(L("chatNewTab"), function() + if (IsValid(ix.gui.chatTabCustomize)) then + ix.gui.chatTabCustomize:Remove() + end + + local panel = vgui.Create("ixChatboxTabCustomize") + panel.OnTabCreated = ix.util.Bind(self, self.OnTabCreated) + end) + + menu:AddOption(L("chatMarkRead"), function() + for _, v in pairs(self.tabs:GetTabs()) do + v:GetButton():SetUnread(false) + end + end) + + menu:AddSpacer() + + menu:AddOption(L("chatReset"), function() + local x, y = self:GetDefaultPosition() + local width, height = self:GetDefaultSize() + + self:SetSize(width, height) + self:SetPos(x, y) + + ix.option.Set("chatPosition", "") + hook.Run("ChatboxPositionChanged", x, y, width, height) + end) + + menu:AddOption(L("chatResetTabs"), function() + for id, _ in pairs(self.tabs:GetTabs()) do + self.tabs:RemoveTab(id) + end + + ix.option.Set("chatTabs", "") + end) + menu:Open() + menu:MakePopup() + + return + end + + if (key != MOUSE_LEFT) then + return + end + + -- capture the mouse if we're in bounds for sizing this panel + if (self:SizingInBounds()) then + self.bSizing = true + self:MouseCapture(true) + elseif (self:DraggingInBounds()) then + local mouseX, mouseY = self:ScreenToLocal(gui.MousePos()) + + -- mouse offset relative to the panel + self.DragOffset = {mouseX, mouseY} + self:MouseCapture(true) + end +end + +function PANEL:OnMouseReleased() + self:MouseCapture(false) + self:SetCursor("arrow") + + -- save new position/size if we were dragging/resizing + if (self.bSizing or self.DragOffset) then + PLUGIN:SavePosition() + + self.bSizing = nil + self.DragOffset = nil + + -- resize chat messages to fit new width + self:InvalidateChildren(true) + + local x, y = self:GetPos() + local width, height = self:GetSize() + + hook.Run("ChatboxPositionChanged", x, y, width, height) + end +end + +function PANEL:Think() + if (!self.bActive) then + return + end + + local mouseX = math.Clamp(gui.MouseX(), 0, ScrW()) + local mouseY = math.Clamp(gui.MouseY(), 0, ScrH()) + + if (self.bSizing) then + local x, y = self:GetPos() + local width = math.Clamp(mouseX - x, chatBorder, ScrW() - chatBorder * 2) + local height = math.Clamp(mouseY - y, chatBorder, ScrH() - chatBorder * 2) + + self:SetSize(width, height) + self:SetCursor("sizenwse") + elseif (self.DragOffset) then + local x = math.Clamp(mouseX - self.DragOffset[1], 0, ScrW() - self:GetWide()) + local y = math.Clamp(mouseY - self.DragOffset[2], 0, ScrH() - self:GetTall()) + + self:SetPos(x, y) + elseif (self:SizingInBounds()) then + self:SetCursor("sizenwse") + elseif (self:DraggingInBounds()) then + -- we have to set the cursor on the list panel since that's the actual hovered panel + self.tabs.buttons:SetCursor("sizeall") + else + self:SetCursor("arrow") + end +end + +function PANEL:Paint(width, height) + local tab = self.tabs:GetActiveTab() + local alpha = self:GetAlpha() + + derma.SkinFunc("PaintChatboxBackground", self, width, height) + + if (tab) then + -- manually paint active tab since messages handle their own alpha lifetime + surface.SetAlphaMultiplier(1) + tab:PaintManual() + surface.SetAlphaMultiplier(alpha / 255) + end + + if (alpha > 0) then + hook.Run("PostChatboxDraw", width, height, self:GetAlpha()) + end +end + +-- get the command of the current chat class in the textentry if possible +function PANEL:GetTextEntryChatClass(text) + text = text or self.entry:GetText() + + local chatType = ix.chat.Parse(LocalPlayer(), text, true) + + if (chatType and chatType != "ic") then + -- OOC is the only one with two slashes as its prefix, so we'll make a special case for it here + if (chatType == "ooc") then + return "ooc" + end + + local class = ix.chat.classes[chatType] + + if (istable(class.prefix)) then + for _, v in ipairs(class.prefix) do + if (v:utf8sub(1, 1) == "/") then + return v:utf8sub(2):utf8lower() + end + end + elseif (class.prefix:utf8sub(1, 1) == "/") then + return class.prefix:utf8sub(2):utf8lower() + end + end +end + +-- chatbox panel hooks +-- called when the textentry value changes +function PANEL:OnTextChanged(text) + hook.Run("ChatTextChanged", text) + + local preview = self.preview + local autocomplete = self.autocomplete + local chatClassCommand = self:GetTextEntryChatClass(text) + + self.prefix:SetText(hook.Run("GetChatPrefixInfo", text)) + + if (chatClassCommand) then + preview:SetCommand(chatClassCommand) + preview:SetVisible(true) + preview:UpdateArguments(text) + + autocomplete:SetVisible(false) + return + end + + local start, _, command = text:find("(/(%w+)%s)") + command = ix.command.list[tostring(command):utf8sub(2, tostring(command):utf8len() - 1):utf8lower()] + + -- update preview if we've found a command + if (start == 1 and command) then + preview:SetCommand(command.uniqueID) + preview:SetVisible(true) + preview:UpdateArguments(text) + + -- we don't need the autocomplete because we have a command already typed out + autocomplete:SetVisible(false) + return + -- if there's a slash then we're probably going to be (or are currently) typing out a command + elseif (text:utf8sub(1, 1) == "/") then + command = text:match("(/(%w+))") or "/" + + preview:SetVisible(false) -- we don't have a valid command yet + autocomplete:Update(command:utf8sub(2)) + autocomplete:SetVisible(true) + + return + end + + if (preview:GetCommand() != "") then + preview:SetCommand("") + preview:SetVisible(false) + end + + if (autocomplete:IsVisible()) then + autocomplete:SetVisible(false) + end +end + +DEFINE_BASECLASS("DTextEntry") +function PANEL:OnKeyCodeTyped(key) + if (key == KEY_TAB) then + if (self.autocomplete:IsOpen() and #self.autocomplete:GetCommands() > 0) then + local newText = self.autocomplete:SelectNext() + + self.entry:SetText(newText) + self.entry:SetCaretPos(newText:utf8len()) + end + + return true + end + + return BaseClass.OnKeyCodeTyped(self.entry, key) +end + +-- called when player types something and presses enter in the textentry +function PANEL:OnMessageSent() + local text = self.entry:GetText() + + if (text:find("%S")) then + local lastEntry = ix.chat.history[#ix.chat.history] + + -- only add line to textentry history if it isn't the same message + if (lastEntry != text) then + if (#ix.chat.history >= 20) then + table.remove(ix.chat.history, 1) + end + + ix.chat.history[#ix.chat.history + 1] = text + end + + net.Start("ixChatMessage") + net.WriteString(text) + net.SendToServer() + end + + self:SetActive(false) -- textentry is set to "" in SetActive +end + +-- called when the player changes the currently active tab +function PANEL:OnTabChanged(panel) + panel:InvalidateLayout(true) + panel:ScrollToBottom() +end + +-- called when the player creates a new tab +function PANEL:OnTabCreated(id, filter) + self.tabs:AddTab(id, filter) + PLUGIN:SaveTabs() +end + +-- called when the player updates a tab's filter +function PANEL:OnTabUpdated(id, filter, newID) + local tab = self.tabs:GetTabs()[id] + + if (!tab) then + return + end + + tab:SetFilter(filter) + self.tabs:RenameTab(id, newID) + + PLUGIN:SaveTabs() +end + +-- called when a tab's button was right-clicked +function PANEL:OnTabRightClick(button, tab, id) + local menu = DermaMenu() + menu:AddOption(L("chatCustomize"), function() + if (IsValid(ix.gui.chatTabCustomize)) then + ix.gui.chatTabCustomize:Remove() + end + + local panel = vgui.Create("ixChatboxTabCustomize") + panel:PopulateFromTab(id, tab:GetFilter()) + panel.OnTabUpdated = ix.util.Bind(self, self.OnTabUpdated) + end) + + menu:AddSpacer() + + menu:AddOption(L("chatCloseTab"), function() + self.tabs:RemoveTab(id) + PLUGIN:SaveTabs() + end) + menu:Open() + menu:MakePopup() -- HACK: mouse input doesn't work when created immediately after opening chatbox +end + +-- called when a message needs to be added to applicable tabs +function PANEL:AddMessage(...) + local class = CHAT_CLASS and CHAT_CLASS.uniqueID or "notice" + local activeTab = self.tabs:GetActiveTab() + + -- track whether or not the message was filtered out in the active tab + local bShown = false + + if (activeTab and !activeTab:GetFilter()[class]) then + activeTab:AddLine({...}, true) + bShown = true + end + + for _, v in pairs(self.tabs:GetTabs()) do + if (v:GetID() == activeTab:GetID()) then + continue -- we already added it to the active tab + end + + if (!v:GetFilter()[class]) then + v:AddLine({...}, true) + + -- mark other tabs as unread if we didn't show the message in the active tab + if (!bShown) then + v:GetButton():SetUnread(true) + end + end + end + + if (bShown) then + chat.PlaySound() + end +end + +vgui.Register("ixChatbox", PANEL, "EditablePanel") diff --git a/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatboxcustomize.lua b/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatboxcustomize.lua new file mode 100644 index 0000000..28d498e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatboxcustomize.lua @@ -0,0 +1,121 @@ + +local PLUGIN = PLUGIN + +local PANEL = {} + +function PANEL:Init() + ix.gui.chatTabCustomize = self + + self:SetTitle(L("chatNewTab")) + self:SetSizable(true) + self:SetSize(ScrW() * 0.5, ScrH() * 0.5) + + self.settings = self:Add("ixSettings") + self.settings:Dock(FILL) + self.settings:SetSearchEnabled(true) + self.settings:AddCategory(L("chatAllowedClasses")) + + -- controls + local controlsPanel = self:Add("Panel") + controlsPanel:Dock(BOTTOM) + controlsPanel:DockMargin(0, 4, 0, 0) + controlsPanel:SetTall(32) + + self.create = controlsPanel:Add("DButton") + self.create:SetText(L("create")) + self.create:SizeToContents() + self.create:Dock(FILL) + self.create:DockMargin(0, 0, 4, 0) + self.create.DoClick = ix.util.Bind(self, self.CreateClicked) + + local uncheckAll = controlsPanel:Add("DButton") + uncheckAll:SetText(L("uncheckAll")) + uncheckAll:SizeToContents() + uncheckAll:Dock(RIGHT) + uncheckAll.DoClick = function() + self:SetAllValues(false) + end + + local checkAll = controlsPanel:Add("DButton") + checkAll:SetText(L("checkAll")) + checkAll:SizeToContents() + checkAll:Dock(RIGHT) + checkAll:DockMargin(0, 0, 4, 0) + checkAll.DoClick = function() + self:SetAllValues(true) + end + + -- chat class settings + self.name = self.settings:AddRow(ix.type.string) + self.name:SetText(L("chatTabName")) + self.name:SetValue(L("chatNewTabTitle")) + self.name:SetZPos(-1) + + for k, _ in SortedPairs(ix.chat.classes) do + local panel = self.settings:AddRow(ix.type.bool, L("chatAllowedClasses")) + panel:SetText(k) + panel:SetValue(true, true) + end + + self.settings:SizeToContents() + self:Center() + self:MakePopup() +end + +function PANEL:PopulateFromTab(name, filter) + self.tab = name + + self:SetTitle(L("chatCustomize")) + self.create:SetText(L("update")) + self.name:SetValue(name) + + for _, v in ipairs(self.settings:GetRows()) do + if (filter[v:GetText()]) then + v:SetValue(false, true) + end + end +end + +function PANEL:SetAllValues(bValue) + for _, v in ipairs(self.settings:GetRows()) do + if (v == self.name) then + continue + end + + v:SetValue(tobool(bValue), true) + end +end + +function PANEL:CreateClicked() + local name = self.tab and self.tab or self.name:GetValue() + + if (self.tab != self.name:GetValue() and PLUGIN:TabExists(self.name:GetValue())) then + ix.util.Notify(L("chatTabExists")) + return + end + + local filter = {} + + for _, v in ipairs(self.settings:GetRows()) do + -- we only want to add entries for classes we don't want shown + if (!v:GetValue()) then + filter[v:GetText()] = true + end + end + + if (self.tab) then + self:OnTabUpdated(name, filter, self.name:GetValue()) + else + self:OnTabCreated(name, filter) + end + + self:Remove() +end + +function PANEL:OnTabCreated(id, filter) +end + +function PANEL:OnTabUpdated(id, filter, newID) +end + +vgui.Register("ixChatboxTabCustomize", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatskin.lua b/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatskin.lua new file mode 100644 index 0000000..0c9087a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatskin.lua @@ -0,0 +1,205 @@ +-- Переопределение стилей чатбокса под общий дизайн Military RP + +local PLUGIN = PLUGIN +local gradient = surface.GetTextureID("vgui/gradient-u") +local gradientUp = surface.GetTextureID("vgui/gradient-d") + +-- Цвета в стиле HUD +local Colors = { + green = Color(84, 147, 90), + dark_green = Color(52, 91, 60), + darker_green = Color(35, 53, 29), + gray = Color(193, 193, 193), + gray_dark = Color(94, 94, 94), + black = Color(0, 0, 0, 220), + black_light = Color(0, 0, 0, 150), + white = Color(255, 255, 255) +} + +-- Переопределяем стандартные функции рисования чатбокса +local SKIN = derma.GetDefaultSkin() + +-- Фон чатбокса +function SKIN:PaintChatboxBackground(panel, width, height) + -- Blur эффект + ix.util.DrawBlur(panel, 8) + + -- Основной фон с градиентом + surface.SetDrawColor(Colors.black) + surface.DrawRect(0, 0, width, height) + + if (panel:GetActive()) then + -- Зелёный градиент сверху когда чат активен + surface.SetDrawColor(Colors.dark_green.r, Colors.dark_green.g, Colors.dark_green.b, 120) + surface.SetTexture(gradientUp) + surface.DrawTexturedRect(0, panel.tabs.buttons:GetTall(), width, height * 0.25) + end + + -- Обводка в стиле HUD + surface.SetDrawColor(Colors.darker_green) + surface.DrawOutlinedRect(0, 0, width, height) + + -- Тонкая внутренняя обводка + surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 60) + surface.DrawOutlinedRect(1, 1, width - 2, height - 2) +end + +-- Кнопки табов +function SKIN:PaintChatboxTabButton(panel, width, height) + if (panel:GetActive()) then + -- Активная вкладка - зелёный градиент + surface.SetDrawColor(Colors.dark_green) + surface.DrawRect(0, 0, width, height) + + surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 100) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height) + else + -- Неактивная вкладка + surface.SetDrawColor(Colors.black_light) + surface.DrawRect(0, 0, width, height) + + -- Индикатор непрочитанных сообщений + if (panel:GetUnread()) then + surface.SetDrawColor(ColorAlpha(Color(200, 200, 50), Lerp(panel.unreadAlpha, 0, 120))) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height - 1) + end + end + + -- Разделитель вкладок + surface.SetDrawColor(Colors.darker_green) + surface.DrawRect(width - 1, 0, 1, height) +end + +-- Панель табов +function SKIN:PaintChatboxTabs(panel, width, height, alpha) + -- Фон панели табов + surface.SetDrawColor(Colors.black_light) + surface.DrawRect(0, 0, width, height) + + -- Градиент вниз + surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 150) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, height * 0.5, width, height * 0.5) + + local tab = panel:GetActiveTab() + + if (tab) then + local button = tab:GetButton() + local x, _ = button:GetPos() + + -- Линия под активной вкладкой (зелёная) + surface.SetDrawColor(Colors.green) + surface.DrawRect(x, height - 2, button:GetWide(), 2) + + -- Обводка панели табов + surface.SetDrawColor(Colors.darker_green) + surface.DrawRect(0, height - 1, x, 1) -- слева + surface.DrawRect(x + button:GetWide(), height - 1, width - x - button:GetWide(), 1) -- справа + end +end + +-- Поле ввода текста +function SKIN:PaintChatboxEntry(panel, width, height) + -- Фон поля ввода + surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 100) + surface.DrawRect(0, 0, width, height) + + -- Текст + panel:DrawTextEntryText(Colors.white, Colors.green, Colors.white) + + -- Обводка + surface.SetDrawColor(Colors.dark_green) + surface.DrawOutlinedRect(0, 0, width, height) + + -- Внутренний highlight когда активно + if panel:HasFocus() then + surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 80) + surface.DrawOutlinedRect(1, 1, width - 2, height - 2) + end +end + +-- Превью команды +function SKIN:DrawChatboxPreviewBox(x, y, text, color) + color = color or Colors.green + + local textWidth, textHeight = surface.GetTextSize(text) + local width, height = textWidth + 8, textHeight + 8 + + -- Фон + surface.SetDrawColor(Colors.dark_green) + surface.DrawRect(x, y, width, height) + + -- Градиент + surface.SetDrawColor(color.r, color.g, color.b, 60) + surface.SetTexture(gradient) + surface.DrawTexturedRect(x, y, width, height) + + -- Текст + surface.SetTextColor(Colors.white) + surface.SetTextPos(x + width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5) + surface.DrawText(text) + + -- Обводка + surface.SetDrawColor(Colors.darker_green) + surface.DrawOutlinedRect(x, y, width, height) + + return width +end + +-- Префикс команды +function SKIN:DrawChatboxPrefixBox(panel, width, height) + local color = panel:GetBackgroundColor() or Colors.green + + -- Фон + surface.SetDrawColor(Colors.dark_green) + surface.DrawRect(0, 0, width, height) + + -- Градиент + surface.SetDrawColor(color.r, color.g, color.b, 80) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height) + + -- Обводка + surface.SetDrawColor(Colors.darker_green) + surface.DrawOutlinedRect(0, 0, width, height) +end + +-- Автодополнение команд +function SKIN:PaintChatboxAutocompleteEntry(panel, width, height) + -- Выделенный фон + if (panel.highlightAlpha > 0) then + surface.SetDrawColor(Colors.dark_green.r, Colors.dark_green.g, Colors.dark_green.b, panel.highlightAlpha * 150) + surface.DrawRect(0, 0, width, height) + + -- Градиент выделения + surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, panel.highlightAlpha * 60) + surface.SetTexture(gradient) + surface.DrawTexturedRect(0, 0, width, height) + end + + -- Разделитель между командами + surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 100) + surface.DrawRect(0, height - 1, width, 1) +end + +hook.Add("LoadFonts", "MilitaryRPChatFonts", function() + -- Переопределяем шрифты чата на Montserrat + local scale = ix.option.Get("chatFontScale", 1) + + surface.CreateFont("ixChatFont", { + font = "Montserrat", + size = 17 * scale, + weight = 400, + extended = true + }) + + surface.CreateFont("ixChatFontItalics", { + font = "Montserrat", + size = 17 * scale, + weight = 400, + italic = true, + extended = true + }) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/chatbox/languages/sh_english.lua b/garrysmod/gamemodes/militaryrp/plugins/chatbox/languages/sh_english.lua new file mode 100644 index 0000000..0eae6d9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/chatbox/languages/sh_english.lua @@ -0,0 +1,25 @@ +NAME = "English" + +LANGUAGE = { + -- Category + optChat = "Chat", + + -- Settings + optChatNotices = "Chat Notices", + optChatNoticesDesc = "Show system notifications in chat", + + optChatTimestamps = "Timestamps", + optChatTimestampsDesc = "Display message send time", + + optChatFontScale = "Font Scale", + optChatFontScaleDesc = "Chat font scale (0.1-2)", + + optChatOutline = "Text Outline", + optChatOutlineDesc = "Add outline to chat text for better readability", + + optChatTabs = "Chat Tabs", + optChatTabsDesc = "Chat tabs configuration (JSON)", + + optChatPosition = "Chat Position", + optChatPositionDesc = "Chat window position and size (JSON)", +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/chatbox/languages/sh_russian.lua b/garrysmod/gamemodes/militaryrp/plugins/chatbox/languages/sh_russian.lua new file mode 100644 index 0000000..b37fce5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/chatbox/languages/sh_russian.lua @@ -0,0 +1,25 @@ +NAME = "Русский" + +LANGUAGE = { + -- Category + optChat = "Чат", + + -- Settings + optChatNotices = "Уведомления в чате", + optChatNoticesDesc = "Показывать системные уведомления в чате", + + optChatTimestamps = "Временные метки", + optChatTimestampsDesc = "Отображать время отправки сообщений", + + optChatFontScale = "Размер шрифта", + optChatFontScaleDesc = "Масштаб шрифта в чате (0.1-2)", + + optChatOutline = "Обводка текста", + optChatOutlineDesc = "Добавить обводку к тексту чата для лучшей читаемости", + + optChatTabs = "Вкладки чата", + optChatTabsDesc = "Конфигурация вкладок чата (JSON)", + + optChatPosition = "Позиция чата", + optChatPositionDesc = "Позиция и размер окна чата (JSON)", +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/chatbox/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/chatbox/sh_plugin.lua new file mode 100644 index 0000000..4b9acdd --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/chatbox/sh_plugin.lua @@ -0,0 +1,188 @@ + +local PLUGIN = PLUGIN + +PLUGIN.name = "Chatbox" +PLUGIN.author = "`impulse" +PLUGIN.description = "Replaces the chatbox to enable customization, autocomplete, and useful info." + +if (CLIENT) then + ix.util.Include("derma/cl_chatskin.lua") + + ix.chat.history = ix.chat.history or {} -- array of strings the player has entered into the chatbox + ix.chat.currentCommand = "" + ix.chat.currentArguments = {} + + ix.option.Add("chatNotices", ix.type.bool, false, { + category = "optChat", + name = "optChatNotices", + description = "optChatNoticesDesc" + }) + + ix.option.Add("chatTimestamps", ix.type.bool, false, { + category = "optChat", + name = "optChatTimestamps", + description = "optChatTimestampsDesc" + }) + + ix.option.Add("chatFontScale", ix.type.number, 1, { + category = "optChat", + name = "optChatFontScale", + description = "optChatFontScaleDesc", + min = 0.1, max = 2, decimals = 2, + OnChanged = function() + hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont")) + PLUGIN:CreateChat() + end + }) + + ix.option.Add("chatOutline", ix.type.bool, false, { + category = "optChat", + name = "optChatOutline", + description = "optChatOutlineDesc" + }) + + -- tabs and their respective filters + ix.option.Add("chatTabs", ix.type.string, "", { + category = "optChat", + name = "optChatTabs", + description = "optChatTabsDesc", + hidden = function() + return true + end + }) + + -- chatbox size and position + ix.option.Add("chatPosition", ix.type.string, "", { + category = "optChat", + name = "optChatPosition", + description = "optChatPositionDesc", + hidden = function() + return true + end + }) + + function PLUGIN:CreateChat() + if (IsValid(self.panel)) then + self.panel:Remove() + end + + self.panel = vgui.Create("ixChatbox") + self.panel:SetupTabs(util.JSONToTable(ix.option.Get("chatTabs", ""))) + self.panel:SetupPosition(util.JSONToTable(ix.option.Get("chatPosition", ""))) + + hook.Run("ChatboxCreated") + end + + function PLUGIN:TabExists(id) + if (!IsValid(self.panel)) then + return false + end + + return self.panel.tabs:GetTabs()[id] != nil + end + + function PLUGIN:SaveTabs() + local tabs = {} + + for id, panel in pairs(self.panel.tabs:GetTabs()) do + tabs[id] = panel:GetFilter() + end + + ix.option.Set("chatTabs", util.TableToJSON(tabs)) + end + + function PLUGIN:SavePosition() + local x, y = self.panel:GetPos() + local width, height = self.panel:GetSize() + + ix.option.Set("chatPosition", util.TableToJSON({x, y, width, height})) + end + + function PLUGIN:InitPostEntity() + self:CreateChat() + end + + function PLUGIN:PlayerBindPress(client, bind, pressed) + bind = bind:lower() + + if (bind:find("messagemode") and pressed) then + -- Запрещаем открывать чат если игрок мёртв + if !LocalPlayer():Alive() then + return true + end + + self.panel:SetActive(true) + + return true + end + end + + function PLUGIN:OnPauseMenuShow() + if (!IsValid(ix.gui.chat) or !ix.gui.chat:GetActive()) then + return + end + + ix.gui.chat:SetActive(false) + + return false + end + + function PLUGIN:HUDShouldDraw(element) + if (element == "CHudChat") then + return false + end + end + + function PLUGIN:ScreenResolutionChanged(oldWidth, oldHeight) + self:CreateChat() + end + + function PLUGIN:ChatText(index, name, text, messageType) + if (messageType == "none" and IsValid(self.panel)) then + self.panel:AddMessage(text) + end + end + + -- luacheck: globals chat + chat.ixAddText = chat.ixAddText or chat.AddText + + function chat.AddText(...) + if (IsValid(PLUGIN.panel)) then + PLUGIN.panel:AddMessage(...) + end + + -- log chat message to console + local text = {} + + for _, v in ipairs({...}) do + if (istable(v) or isstring(v)) then + text[#text + 1] = v + elseif (isentity(v) and v:IsPlayer()) then + text[#text + 1] = team.GetColor(v:Team()) + text[#text + 1] = v:Name() + elseif (type(v) != "IMaterial") then + text[#text + 1] = tostring(v) + end + end + + text[#text + 1] = "\n" + MsgC(unpack(text)) + end +else + util.AddNetworkString("ixChatMessage") + + net.Receive("ixChatMessage", function(length, client) + local text = net.ReadString() + + if ((client.ixNextChat or 0) < CurTime() and isstring(text) and text:find("%S")) then + local maxLength = ix.config.Get("chatMax") + + if (text:utf8len() > maxLength) then + text = text:utf8sub(0, maxLength) + end + + hook.Run("PlayerSay", client, text) + client.ixNextChat = CurTime() + 0.5 + end + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/chatcontrol/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/chatcontrol/sh_plugin.lua new file mode 100644 index 0000000..cdc01ae --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/chatcontrol/sh_plugin.lua @@ -0,0 +1,158 @@ +PLUGIN.name = "Chat Control" +PLUGIN.author = "RefoselDev" +PLUGIN.description = "Удаление ненужных типов чатов" + + +hook.Add("InitializedChatClasses", "ixChatControl.RemoveChats", function() + ix.chat.classes.it = nil + ix.chat.classes.connect = nil + ix.chat.classes.disconnect = nil + ix.chat.classes.event = nil + + ix.chat.Register("rp", { + OnChatAdd = function(self, speaker, text) + local name = IsValid(speaker) and speaker:Name() or "Console" + chat.AddText(Color(26, 221, 0), "[RP] ", name, ": ", text) + end, + prefix = {"/rp", "/RP"}, + description = "Чат РП отыгровок", + color = Color(150, 200, 255), + CanHear = function(self, speaker, listener) + return listener:GetCharacter() != nil + end, + deadCanChat = false + }) + + -- Чат между фракциями + ix.chat.Register("adgl", { + OnChatAdd = function(self, speaker, text) + local name = IsValid(speaker) and speaker:Name() or "Console" + local prefix = "[ФРАКЦИЯ] " + local prefixColor = Color(255, 200, 100) + + if (IsValid(speaker) and speaker:GetCharacter()) then + local faction = speaker:GetCharacter():GetFaction() + + if (faction == FACTION_RUSSIAN) then + prefix = "[ВС РФ → ВСУ] " + prefixColor = Color(255, 196, 0) + elseif (faction == FACTION_UKRAINE) then + prefix = "[ВСУ → ВС РФ] " + prefixColor = Color(255, 196, 0) + end + end + + chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text) + end, + prefix = {"/adgl"}, + description = "Чат между фракциями", + color = Color(255, 200, 100), + CanHear = function(self, speaker, listener) + -- Слышат только игроки с персонажем и определенной фракцией + if (!listener:GetCharacter()) then + return false + end + + local listenerFaction = listener:GetCharacter():GetFaction() + return listenerFaction == FACTION_RUSSIAN or listenerFaction == FACTION_UKRAINE + end, + deadCanChat = false + }) + + -- Объявления стороны + ix.chat.Register("ad", { + OnChatAdd = function(self, speaker, text) + local name = IsValid(speaker) and speaker:Name() or "Console" + local prefix = "[ОБЪЯВЛЕНИЕ] " + local prefixColor = Color(200, 200, 200) + + if (IsValid(speaker) and speaker:GetCharacter()) then + local faction = speaker:GetCharacter():GetFaction() + + if (faction == FACTION_RUSSIAN) then + prefix = "[ВС РФ] " + prefixColor = Color(255, 100, 100) + elseif (faction == FACTION_UKRAINE) then + prefix = "[ВСУ] " + prefixColor = Color(255, 196, 0) + end + end + + chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text) + end, + prefix = {"/ad"}, + description = "Объявления стороны", + color = Color(200, 200, 200), + CanHear = function(self, speaker, listener) + -- Слышат только игроки той же фракции + if (!listener:GetCharacter() or !speaker:GetCharacter()) then + return false + end + + local speakerFaction = speaker:GetCharacter():GetFaction() + local listenerFaction = listener:GetCharacter():GetFaction() + + return speakerFaction == listenerFaction + end, + deadCanChat = false + }) + + -- Чат подразделения + ix.chat.Register("podr", { + OnChatAdd = function(self, speaker, text) + local name = IsValid(speaker) and speaker:Name() or "Console" + local prefix = "[ПОДРАЗДЕЛЕНИЕ] " + local prefixColor = Color(100, 150, 255) + + if (IsValid(speaker) and speaker:GetCharacter()) then + local character = speaker:GetCharacter() + local faction = character:GetFaction() + local podrID = (character.GetPodr and character:GetPodr()) or 1 + local factionTable = ix.faction.indices[faction] + + if (factionTable and factionTable.Podr and factionTable.Podr[podrID]) then + local podrName = factionTable.Podr[podrID].name or "Неизвестно" + prefix = "["..podrName.."] " + + if (faction == FACTION_RUSSIAN) then + prefixColor = Color(255, 196, 0) + elseif (faction == FACTION_UKRAINE) then + prefixColor = Color(255, 196, 0) + end + end + end + + chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text) + end, + prefix = {"/podr", "/p"}, + description = "Чат подразделения", + color = Color(100, 150, 255), + CanHear = function(self, speaker, listener) + -- Слышат только игроки той же фракции и того же подразделения + if (!listener:GetCharacter() or !speaker:GetCharacter()) then + return false + end + + local speakerChar = speaker:GetCharacter() + local listenerChar = listener:GetCharacter() + + local speakerFaction = speakerChar:GetFaction() + local listenerFaction = listenerChar:GetFaction() + + if (speakerFaction != listenerFaction) then + return false + end + + local speakerPodr = (speakerChar.GetPodr and speakerChar:GetPodr()) or 1 + local listenerPodr = (listenerChar.GetPodr and listenerChar:GetPodr()) or 1 + + return speakerPodr == listenerPodr + end, + deadCanChat = false + }) + + if (SERVER) then + print("[Chat Control] Удалены чаты: it, connect, disconnect, event") + print("[Chat Control] Добавлены чаты: /rp, /adgl, /ad, /podr") + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/death_screen/cl_deathscreen.lua b/garrysmod/gamemodes/militaryrp/plugins/death_screen/cl_deathscreen.lua new file mode 100644 index 0000000..06f2c20 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/death_screen/cl_deathscreen.lua @@ -0,0 +1,209 @@ +-- Создание шрифтов +surface.CreateFont("ixDeathScreenMain", { + font = "OperiusRegular", + size = 180, + weight = 300, + extended = true +}) + +surface.CreateFont("ixDeathScreenSub", { + font = "OperiusRegular", + size = 90, + weight = 300, + extended = true +}) + +-- Резервный шрифт если OperiusRegular недоступен +surface.CreateFont("ixDeathScreenMainFallback", { + font = "Roboto", + size = 180, + weight = 700, + extended = true +}) + +surface.CreateFont("ixDeathScreenSubFallback", { + font = "Roboto", + size = 90, + weight = 500, + extended = true +}) + +surface.CreateFont("ixDeathScreenSmall", { + font = "Roboto", + size = 40, + weight = 400, + extended = true +}) + +-- Проверка доступности шрифта +local function GetFont(primary, fallback) + local w, h = surface.GetTextSize("TEST") + if w > 0 then + return primary + end + return fallback +end + +local deathScreenPanel = nil + +-- Получение экрана смерти от сервера +net.Receive("ixDeathScreen", function() + if not ix.config.Get("deathScreenEnabled") then + return + end + + -- Закрываем старую панель если существует + if IsValid(deathScreenPanel) then + deathScreenPanel:Remove() + end + + local duration = net.ReadFloat() + local minDuration = net.ReadFloat() + local killerName = net.ReadString() + + deathScreenPanel = vgui.Create("DFrame") + deathScreenPanel:SetSize(ScrW(), ScrH()) + deathScreenPanel:SetPos(0, 0) + deathScreenPanel:SetTitle("") + deathScreenPanel:ShowCloseButton(false) + deathScreenPanel:SetDraggable(false) + deathScreenPanel:MakePopup() + deathScreenPanel:SetKeyboardInputEnabled(false) + deathScreenPanel:SetMouseInputEnabled(false) + + local colorAlpha = 0 + local textAlpha = 0 + local fadeOutStarted = false + local startTime = RealTime() + local canRespawnTime = startTime + minDuration + + local fadeInSpeed = ix.config.Get("deathScreenFadeInSpeed", 5) + local textFadeSpeed = ix.config.Get("deathScreenTextFadeSpeed", 0.5) + local textColor = ix.config.Get("deathScreenTextColor", Color(132, 43, 60)) + local mainText = ix.config.Get("deathScreenMainText", "YOU'RE DEAD") + local respawnText = ix.config.Get("deathScreenRespawnText", "НАЖМИТЕ [ПРОБЕЛ] ЧТОБЫ ВОЗРОДИТЬСЯ") + local subText = "[Убил: " .. killerName .. "]" + + -- Убраны таймеры автоматического закрытия + + -- Обработка нажатия пробела + deathScreenPanel.Think = function(s) + if (fadeOutStarted) then return end + + local lp = LocalPlayer() + if (!IsValid(lp)) then return end + + -- Если игрока возродили (медик или админ), убираем экран + -- Проверяем только спустя 2 секунды, чтобы избежать багов при переходе в режим смерти + if (RealTime() > startTime + 2) then + if (lp:Alive() and !lp:GetNWBool("HeartAttack", false)) then + fadeOutStarted = true + timer.Simple(0.5, function() + if (IsValid(s)) then s:Remove() end + end) + return + end + end + + -- Проверка возможности сдаться + local character = lp:GetCharacter() + local minBalance = ix.config.Get("deathRespawnMinBalance", 5000) + local canAfford = character and character:GetMoney() >= minBalance + + if (canAfford and RealTime() >= canRespawnTime and input.IsKeyDown(KEY_SPACE)) then + net.Start("ixPlayerRespawn") + net.SendToServer() + + fadeOutStarted = true + timer.Simple(0.5, function() + if (IsValid(s)) then s:Remove() end + end) + end + end + + + + deathScreenPanel.Paint = function(s, w, h) + if fadeOutStarted then + colorAlpha = math.Approach(colorAlpha, 0, FrameTime() * 255) + else + colorAlpha = math.Approach(colorAlpha, 255, FrameTime() * fadeInSpeed * 255) + end + + draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, colorAlpha)) + end + + local textLabel = vgui.Create("DLabel", deathScreenPanel) + textLabel:SetSize(deathScreenPanel:GetWide(), deathScreenPanel:GetTall()) + textLabel:SetPos(0, 0) + textLabel:SetText("") + + textLabel.Paint = function(s, w, h) + local elapsed = RealTime() - startTime + + if fadeOutStarted then + textAlpha = math.Approach(textAlpha, 0, FrameTime() * 255 * 4) + elseif elapsed > 1 then + textAlpha = math.Approach(textAlpha, 255, FrameTime() * textFadeSpeed * 255) + end + + -- Определяем доступные шрифты + local mainFont = GetFont("ixDeathScreenMain", "ixDeathScreenMainFallback") + local subFont = GetFont("ixDeathScreenSub", "ixDeathScreenSubFallback") + + -- Рисуем текст + draw.SimpleText(mainText, mainFont, w/2, h/2, + Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(subText, subFont, w/2, h/2 + 144, + Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Рисуем оставшееся время или подсказку + local character = LocalPlayer():GetCharacter() + local minBalance = ix.config.Get("deathRespawnMinBalance", 5000) + local canAfford = character and character:GetMoney() >= minBalance + + if (RealTime() < canRespawnTime) then + local remaining = math.max(0, math.ceil(canRespawnTime - RealTime())) + draw.SimpleText("ВОЗРОЖДЕНИЕ ЧЕРЕЗ: " .. remaining, subFont, w/2, h/2 + 250, + Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif (canAfford) then + draw.SimpleText(respawnText, subFont, w/2, h/2 + 250, + Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText("НЕДОСТАТОЧНО СРЕДСТВ ДЛЯ СДАЧИ (" .. ix.currency.Get(minBalance) .. ")", subFont, w/2, h/2 + 250, + Color(200, 50, 50, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Рисуем предупреждение о штрафе + local penalty = ix.config.Get("deathPenaltyAmount", 3000) + if (penalty > 0) then + local warningText = string.format(ix.config.Get("deathPenaltyWarningText", "При сдаче с вас спишут штраф: %s"), ix.currency.Get(penalty)) + draw.SimpleText(warningText, "ixDeathScreenSmall", w/2, h/2 + 330, + Color(textColor.r, textColor.g, textColor.b, textAlpha * 0.8), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end +end) + +-- Очистка при отключении +hook.Add("InitPostEntity", "ixDeathScreenCleanup", function() + if IsValid(deathScreenPanel) then + deathScreenPanel:Remove() + end +end) + +-- Камера следует за регдоллом при смерти +hook.Add("CalcView", "ixDeathScreenView", function(ply, pos, angles, fov) + if (IsValid(deathScreenPanel) and !ply:Alive()) then + local ragdoll = ply:GetRagdollEntity() + if (IsValid(ragdoll)) then + local eyes = ragdoll:GetAttachment(ragdoll:LookupAttachment("eyes")) + if (eyes) then + return { + origin = eyes.Pos, + angles = eyes.Ang, + fov = fov + } + end + end + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/death_screen/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/death_screen/sh_plugin.lua new file mode 100644 index 0000000..358c70f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/death_screen/sh_plugin.lua @@ -0,0 +1,138 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Murder Drones Death Screen" +PLUGIN.description = "Добавляет кинематографичный экран смерти в стиле Murder Drones" +PLUGIN.author = "Scripty & Ported to Helix" + +-- Конфигурация +ix.config.Add("deathScreenEnabled", true, "Включить экран смерти Murder Drones", nil, { + category = "Death Screen" +}) + +ix.config.Add("deathScreenDuration", 300, "Длительность показа экрана смерти (секунды)", nil, { + data = {min = 1, max = 600, decimals = 1}, + category = "Death Screen" +}) + +ix.config.Add("deathScreenMinDuration", 20, "Минимальное время до возможности возродиться (секунды)", nil, { + data = {min = 0, max = 60, decimals = 1}, + category = "Death Screen" +}) + +ix.config.Add("deathScreenFadeInSpeed", 5, "Скорость появления экрана", nil, { + data = {min = 1, max = 20, decimals = 1}, + category = "Death Screen" +}) + +ix.config.Add("deathScreenTextFadeSpeed", 0.5, "Скорость появления текста", nil, { + data = {min = 0.1, max = 5, decimals = 1}, + category = "Death Screen" +}) + +ix.config.Add("deathScreenTextColor", Color(132, 43, 60), "Цвет текста экрана смерти", nil, { + category = "Death Screen" +}) + +ix.config.Add("deathScreenMainText", "YOU'RE DEAD", "Основной текст экрана смерти", nil, { + category = "Death Screen" +}) + +ix.config.Add("deathScreenSubText", "[IDIOT]", "Подзаголовок экрана смерти", nil, { + category = "Death Screen" +}) + +ix.config.Add("deathScreenRespawnText", "НАЖМИТЕ [ПРОБЕЛ] ЧТОБЫ ВОЗРОДИТЬСЯ", "Текст подсказки для возрождения", nil, { + category = "Death Screen" +}) + +ix.config.Add("deathScreenDisableSound", true, "Отключить стандартный звук смерти", nil, { + category = "Death Screen" +}) + +ix.config.Add("deathPenaltyAmount", 3000, "Сумма штрафа за возрождение", nil, { + data = {min = 0, max = 100000}, + category = "Death Screen" +}) + +ix.config.Add("deathPenaltyWarningText", "При сдаче с вас спишут штраф: %s", "Текст предупреждения о штрафе", nil, { + category = "Death Screen" +}) + +ix.config.Add("deathRespawnMinBalance", 5000, "Минимальный баланс для возможности сдаться", nil, { + data = {min = 0, max = 100000}, + category = "Death Screen" +}) + +if SERVER then + util.AddNetworkString("ixDeathScreen") + util.AddNetworkString("ixPlayerRespawn") + + function PLUGIN:PlayerDeath(ply, inflictor, attacker) + if not ix.config.Get("deathScreenEnabled") then + return + end + + local maxDuration = ix.config.Get("deathScreenDuration", 300) + local minDuration = ix.config.Get("deathScreenMinDuration", 20) + local killerName = "Неизвестно" + + if (IsValid(attacker)) then + if (attacker:IsPlayer()) then + killerName = attacker:Name() + elseif (attacker:IsNPC()) then + killerName = attacker:GetClass() + elseif (IsValid(attacker:GetOwner()) and attacker:GetOwner():IsPlayer()) then + killerName = attacker:GetOwner():Name() + elseif (attacker.GetCreator and IsValid(attacker:GetCreator()) and attacker:GetCreator():IsPlayer()) then + killerName = attacker:GetCreator():Name() + else + killerName = (attacker:GetClass() != "worldspawn") and attacker:GetClass() or "Окружение" + end + end + + net.Start("ixDeathScreen") + net.WriteFloat(maxDuration) + net.WriteFloat(minDuration) + net.WriteString(killerName) + net.Send(ply) + + -- Блокируем стандартный спавн Helix + local duration = RespawnTime[ply:GetUserGroup()] or 60 + ply.ixDeathTime = RealTime() + ply.ixNextSpawn = RealTime() + duration + ply:SetNetVar("deathTime", CurTime() + maxDuration) + + -- Сохраняем реальное минимальное время для проверки + ply.ixDeathScreenRespawnTime = CurTime() + minDuration + end + + -- Разрешаем создание стандартного регдолла Helix + function PLUGIN:ShouldSpawnClientRagdoll(ply) + return true + end + + net.Receive("ixPlayerRespawn", function(len, ply) + if (IsValid(ply) and !ply:Alive() and ply:GetCharacter()) then + if ((ply.ixDeathScreenRespawnTime or 0) <= CurTime()) then + local character = ply:GetCharacter() + local minBalance = ix.config.Get("deathRespawnMinBalance", 5000) + + -- Проверка баланса перед спавном + if (character:GetMoney() < minBalance) then + ply:Notify("У вас недостаточно средств, чтобы сдаться. Нужно минимум " .. ix.currency.Get(minBalance)) + return + end + + local penalty = ix.config.Get("deathPenaltyAmount", 3000) + if (penalty > 0) then + character:TakeMoney(penalty) + ply:Notify("Вы сдались. С вашего счета списано " .. ix.currency.Get(penalty) .. " штрафа.") + end + + ply:Spawn() + end + end + end) +end + +ix.util.Include("cl_deathscreen.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/disguise/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/disguise/cl_plugin.lua new file mode 100644 index 0000000..0070a37 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/disguise/cl_plugin.lua @@ -0,0 +1,11 @@ +local PLUGIN = PLUGIN + +-- Просмотр украденного документа +net.Receive("ixDisguise_ViewStolenID", function() + local data = net.ReadTable() + + local plugin = ix.plugin.Get("military_id") + if plugin then + plugin:ShowDocument(data) + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/disguise/items/sh_stolen_military_id.lua b/garrysmod/gamemodes/militaryrp/plugins/disguise/items/sh_stolen_military_id.lua new file mode 100644 index 0000000..5e16da5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/disguise/items/sh_stolen_military_id.lua @@ -0,0 +1,83 @@ +ITEM.name = "Украденный военный билет" +ITEM.description = "Военный билет, снятый с убитого противника. Содержит его данные." +ITEM.model = "models/props_c17/paper01.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.category = "Маскировка" +ITEM.noDrop = false + +-- Функция предъявления документа +ITEM.functions.Show = { + name = "Предъявить", + tip = "Показать военный билет ближайшему игроку", + icon = "icon16/eye.png", + OnRun = function(item) + local client = item.player + + if CLIENT then + -- Создаем меню выбора игрока + local menu = DermaMenu() + + for _, ply in ipairs(player.GetAll()) do + if ply ~= client and ply:GetPos():Distance(client:GetPos()) <= 200 then + menu:AddOption(ply:Name(), function() + net.Start("ixDisguise_ShowStolenID") + net.WriteUInt(ply:UserID(), 16) + net.WriteUInt(item:GetID(), 32) + net.SendToServer() + end) + end + end + + if menu:ChildCount() == 0 then + menu:AddOption("Нет игроков поблизости", function() end):SetDisabled(true) + end + + menu:Open() + end + + return false + end, + OnCanRun = function(item) + return !IsValid(item.entity) + end +} + +-- Функция просмотра своего документа +ITEM.functions.View = { + name = "Просмотреть", + tip = "Посмотреть содержимое военного билета", + icon = "icon16/page_white_text.png", + OnRun = function(item) + local client = item.player + + if SERVER then + local data = { + name = item:GetData("name", "Неизвестно"), + faction = item:GetData("faction", "Неизвестно"), + subdivision = item:GetData("subdivision", "Неизвестно"), + specialization = item:GetData("specialization", "Неизвестно"), + rank = item:GetData("rank", "Неизвестно") + } + + net.Start("ixDisguise_ViewStolenID") + net.WriteTable(data) + net.Send(client) + end + + return false + end, + OnCanRun = function(item) + return !IsValid(item.entity) + end +} + +-- Кастомное отображение в инвентаре +if CLIENT then + function ITEM:PaintOver(item, w, h) + local victimName = self:GetData("name") + if victimName then + draw.SimpleText(victimName, "DermaDefault", w/2, h - 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/disguise/items/sh_stolen_uniform.lua b/garrysmod/gamemodes/militaryrp/plugins/disguise/items/sh_stolen_uniform.lua new file mode 100644 index 0000000..3c467c6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/disguise/items/sh_stolen_uniform.lua @@ -0,0 +1,101 @@ +ITEM.name = "Украденная форма" +ITEM.description = "Военная форма, снятая с убитого противника. Позволяет замаскироваться под врага." +ITEM.model = "models/props_c17/suitcase001a.mdl" +ITEM.width = 2 +ITEM.height = 2 +ITEM.category = "Маскировка" +ITEM.noDrop = false + +-- Функция надевания формы +ITEM.functions.Wear = { + name = "Надеть", + tip = "Надеть украденную форму для маскировки", + icon = "icon16/user_suit.png", + OnRun = function(item) + local client = item.player + local character = client:GetCharacter() + + if SERVER then + -- Проверяем кулдаун + local plugin = ix.plugin.Get("disguise") + local canUse, msg = plugin:CanUseDisguise(client) + + if not canUse then + client:Notify(msg) + return false + end + + -- Проверяем, не в маскировке ли уже + if character:GetData("disguised") then + client:Notify("Вы уже в маскировке!") + return false + end + + -- Получаем украденную модель + local stolenModel = item:GetData("stolenModel") + if not stolenModel then + client:Notify("Форма повреждена!") + return false + end + + -- Сохраняем оригинальную модель и надеваем украденную + local originalModel = client:GetModel() + character:SetData("originalModel", originalModel) + character:SetData("disguiseModel", stolenModel) + character:SetData("disguised", true) + + client:SetModel(stolenModel) + + local victimName = item:GetData("victimName", "неизвестного") + client:Notify("Вы надели форму " .. victimName .. ". Будьте осторожны!") + + -- Удаляем предмет после использования + item:Remove() + end + + return false + end, + OnCanRun = function(item) + return !IsValid(item.entity) + end +} + +-- Функция снятия формы +ITEM.functions.Remove = { + name = "Снять", + tip = "Снять маскировку", + icon = "icon16/user_delete.png", + OnRun = function(item) + local client = item.player + local character = client:GetCharacter() + + if SERVER then + if not character:GetData("disguised") then + client:Notify("Вы не в маскировке!") + return false + end + + local plugin = ix.plugin.Get("disguise") + if plugin then + plugin:RemoveDisguise(client) + end + end + + return false + end, + OnCanRun = function(item) + local client = item.player + local character = client and client:GetCharacter() + return !IsValid(item.entity) and character and character:GetData("disguised") + end +} + +-- Кастомное отображение в инвентаре +if CLIENT then + function ITEM:PaintOver(item, w, h) + local victimName = self:GetData("victimName") + if victimName then + draw.SimpleText("Форма: " .. victimName, "DermaDefault", w/2, h - 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/disguise/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/disguise/sh_plugin.lua new file mode 100644 index 0000000..e9b3499 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/disguise/sh_plugin.lua @@ -0,0 +1,15 @@ +PLUGIN.name = "Disguise System" +PLUGIN.author = "Your Name" +PLUGIN.description = "Система маскировки с украденной формой и документами" + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") + +if SERVER then + util.AddNetworkString("ixDisguise_ViewStolenID") + util.AddNetworkString("ixDisguise_ShowStolenID") +end + +-- Конфигурация +PLUGIN.dropChance = 0.3 -- 30% шанс выпадения предметов +PLUGIN.disguiseCooldown = 600 -- 10 минут в секундах diff --git a/garrysmod/gamemodes/militaryrp/plugins/disguise/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/disguise/sv_plugin.lua new file mode 100644 index 0000000..c4c0ff6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/disguise/sv_plugin.lua @@ -0,0 +1,99 @@ +local PLUGIN = PLUGIN + +-- Таблица для хранения кулдаунов +PLUGIN.disguiseCooldowns = PLUGIN.disguiseCooldowns or {} + +-- Снятие маскировки +function PLUGIN:RemoveDisguise(client) + if not IsValid(client) then return end + + local character = client:GetCharacter() + if not character then return end + + -- Возвращаем оригинальную модель + local originalModel = character:GetModel() + client:SetModel(originalModel) + + -- Удаляем флаг маскировки + character:SetData("disguised", false) + character:SetData("disguiseModel", nil) + + -- Устанавливаем кулдаун + self.disguiseCooldowns[client:SteamID64()] = CurTime() + self.disguiseCooldown + + client:Notify("Маскировка снята! Вы не сможете переодеться в течение 10 минут.") +end + +-- Проверка кулдауна +function PLUGIN:CanUseDisguise(client) + local steamID = client:SteamID64() + local cooldown = self.disguiseCooldowns[steamID] + + if cooldown and CurTime() < cooldown then + local remaining = math.ceil(cooldown - CurTime()) + local minutes = math.floor(remaining / 60) + local seconds = remaining % 60 + return false, string.format("Переодеться можно через %d:%02d", minutes, seconds) + end + + return true +end + +-- Хук на получение урона +function PLUGIN:EntityTakeDamage(target, dmg) + if not IsValid(target) or not target:IsPlayer() then return end + + local character = target:GetCharacter() + if not character then return end + + -- Проверяем, в маскировке ли игрок + if character:GetData("disguised") then + self:RemoveDisguise(target) + end +end + +-- Хук на взятие оружия в руки +function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon) + if not IsValid(client) or not IsValid(newWeapon) then return end + + local character = client:GetCharacter() + if not character then return end + + -- Проверяем, в маскировке ли игрок + if character:GetData("disguised") then + self:RemoveDisguise(client) + end +end + +-- Отправка данных украденного военного билета +net.Receive("ixDisguise_ShowStolenID", function(len, client) + local targetID = net.ReadUInt(16) + local itemID = net.ReadUInt(32) + + local target = Player(targetID) + if not IsValid(target) or target:GetPos():Distance(client:GetPos()) > 200 then + client:Notify("Игрок слишком далеко!") + return + end + + local item = ix.item.instances[itemID] + if not item or item.player ~= client then + return + end + + -- Отправляем данные документа целевому игроку + local data = { + name = item:GetData("name", "Неизвестно"), + faction = item:GetData("faction", "Неизвестно"), + subdivision = item:GetData("subdivision", "Неизвестно"), + specialization = item:GetData("specialization", "Неизвестно"), + rank = item:GetData("rank", "Неизвестно") + } + + net.Start("ixDisguise_ViewStolenID") + net.WriteTable(data) + net.Send(target) + + client:Notify("Вы предъявили военный билет игроку " .. target:Name()) + target:Notify(client:Name() .. " предъявил вам военный билет") +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/cl_plugin.lua new file mode 100644 index 0000000..b9e48da --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/cl_plugin.lua @@ -0,0 +1,3 @@ +local PLUGIN = PLUGIN + +-- Клиентские опции (если нужны дополнительные настройки на клиенте) diff --git a/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/sh_plugin.lua new file mode 100644 index 0000000..afa1c4e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/sh_plugin.lua @@ -0,0 +1,6 @@ +PLUGIN.name = "Dynamic Height" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "Автоматическая настройка высоты камеры игрока в зависимости от модели персонажа." + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/sv_plugin.lua new file mode 100644 index 0000000..64e374d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/dynamicheight/sv_plugin.lua @@ -0,0 +1,113 @@ +local PLUGIN = PLUGIN + +-- Конфигурационные переменные +ix.config.Add("dynamicHeightEnabled", true, "Включить динамическую высоту камеры.", nil, { + category = "Динамическая высота" +}) + +ix.config.Add("dynamicHeightMaxManual", false, "Использовать ручную максимальную высоту.", nil, { + category = "Динамическая высота" +}) + +ix.config.Add("dynamicHeightMinManual", false, "Использовать ручную минимальную высоту.", nil, { + category = "Динамическая высота" +}) + +ix.config.Add("dynamicHeightMin", 16, "Минимальная высота камеры (присед).", nil, { + data = {min = 5, max = 180}, + category = "Динамическая высота" +}) + +ix.config.Add("dynamicHeightMax", 64, "Максимальная высота камеры (стоя).", nil, { + data = {min = 5, max = 180}, + category = "Динамическая высота" +}) + +local function UpdateView(ply) + if not ix.config.Get("dynamicHeightEnabled", true) then + if ply.ixDynamicHeightChanged then + ply:SetViewOffset(Vector(0, 0, 64)) + ply:SetViewOffsetDucked(Vector(0, 0, 28)) + ply.ixDynamicHeightChanged = nil + end + return + end + + -- Определяем высоту модели + local height_max = 64 + local height_min = 16 + + -- Создаем временную сущность для измерения высоты стоя + local entity = ents.Create("base_anim") + entity:SetModel(ply:GetModel()) + entity:ResetSequence(entity:LookupSequence("idle_all_01")) + local bone = entity:LookupBone("ValveBiped.Bip01_Neck1") + if bone then + height_max = entity:GetBonePosition(bone).z + 5 + end + + -- Создаем временную сущность для измерения высоты приседа + local entity2 = ents.Create("base_anim") + entity2:SetModel(ply:GetModel()) + entity2:ResetSequence(entity2:LookupSequence("cidle_all")) + local bone2 = entity2:LookupBone("ValveBiped.Bip01_Neck1") + if bone2 then + height_min = entity2:GetBonePosition(bone2).z + 5 + end + + -- Удаляем временные сущности + entity:Remove() + entity2:Remove() + + -- Применяем настройки высоты + local min = ix.config.Get("dynamicHeightMin", 16) + local max = ix.config.Get("dynamicHeightMax", 64) + + if ix.config.Get("dynamicHeightMinManual", false) then + ply:SetViewOffsetDucked(Vector(0, 0, min)) + else + ply:SetViewOffsetDucked(Vector(0, 0, height_min)) + end + + if ix.config.Get("dynamicHeightMaxManual", false) then + ply:SetViewOffset(Vector(0, 0, max)) + else + ply:SetViewOffset(Vector(0, 0, height_max)) + end + + ply.ixDynamicHeightChanged = true +end + +local function UpdateTrueModel(ply) + if ply:GetNWString("ixDynamicHeight:TrueModel") ~= ply:GetModel() then + ply:SetNWString("ixDynamicHeight:TrueModel", ply:GetModel()) + UpdateView(ply) + end +end + +function PLUGIN:PlayerSpawn(ply) + UpdateTrueModel(ply) +end + +function PLUGIN:PlayerTick(ply) + UpdateTrueModel(ply) +end + +-- Обновление при изменении конфигов +local function OnConfigChanged(key, oldValue, newValue) + if string.StartWith(key, "dynamicHeight") then + for _, ply in pairs(player.GetAll()) do + UpdateView(ply) + end + end +end + +function PLUGIN:OnConfigChanged(key, oldValue, newValue) + if key == "dynamicHeightEnabled" or + key == "dynamicHeightMin" or + key == "dynamicHeightMax" or + key == "dynamicHeightMinManual" or + key == "dynamicHeightMaxManual" then + OnConfigChanged(key, oldValue, newValue) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/event_announce/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/event_announce/cl_plugin.lua new file mode 100644 index 0000000..694d827 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/event_announce/cl_plugin.lua @@ -0,0 +1,22 @@ +net.Receive("AnnounceMessage", function() + local msg = net.ReadString() + chat.AddText(Color(255, 0, 0), msg) +end) + +local eventText = "" +local eventTime = 0 + +net.Receive("EventMessage", function() + eventText = net.ReadString() + eventTime = CurTime() + 5 -- show for 5 seconds +end) + +function PLUGIN:HUDPaint() + if CurTime() < eventTime then + surface.SetFont("ixBigFont") + local w, h = surface.GetTextSize(eventText) + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(ScrW() / 2 - w / 2, 50) + surface.DrawText(eventText) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/event_announce/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/event_announce/sh_plugin.lua new file mode 100644 index 0000000..b6e21c3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/event_announce/sh_plugin.lua @@ -0,0 +1,6 @@ +PLUGIN.name = "Event Announce" +PLUGIN.author = "Scripty" +PLUGIN.description = "/event /annonce" + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/event_announce/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/event_announce/sv_plugin.lua new file mode 100644 index 0000000..174755a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/event_announce/sv_plugin.lua @@ -0,0 +1,21 @@ +util.AddNetworkString("EventMessage") +util.AddNetworkString("AnnounceMessage") + +function PLUGIN:PlayerSay(ply, text) + local userGroup = ply:GetUserGroup() or "" + if userGroup == "superadmin" or userGroup == "ivent" then + if string.StartWith(text, "/event ") then + local msg = string.sub(text, 8) + net.Start("EventMessage") + net.WriteString(msg) + net.Broadcast() + return "" + elseif string.StartWith(text, "/annonce ") then + local msg = string.sub(text, 10) + net.Start("AnnounceMessage") + net.WriteString(msg) + net.Broadcast() + return "" + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/events_system/cl_doomsday.lua b/garrysmod/gamemodes/militaryrp/plugins/events_system/cl_doomsday.lua new file mode 100644 index 0000000..0cf1895 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/events_system/cl_doomsday.lua @@ -0,0 +1,115 @@ +local PLUGIN = PLUGIN + +local doomsday_active = false +local doomsday_start_time = 0 + +net.Receive("doomsday_night_hud_update", function() + doomsday_active = net.ReadBool() + doomsday_start_time = net.ReadInt(32) +end) + +function PLUGIN:DoomsdayHUDPaint() + local active = doomsday_active or GetGlobalBool("doomsday_night_active", false) + local start_time = doomsday_start_time == 0 and GetGlobalInt("doomsday_night_start_time", 0) or doomsday_start_time + local alive_count = GetGlobalInt("doomsday_night_alive_count", 0) + + if (!active or start_time == 0) then return end + + local elapsed = CurTime() - start_time + local minutes = math.floor(elapsed / 60) + local seconds = math.floor(elapsed % 60) + + local x, y = ScrW() - 280, 20 + local w, h = 260, 120 + + surface.SetDrawColor(20, 20, 40, 200) + surface.DrawRect(x, y, w, h) + + local border_alpha = 150 + math.abs(math.sin(CurTime() * 2)) * 105 + surface.SetDrawColor(100, 0, 100, border_alpha) + surface.DrawOutlinedRect(x, y, w, h, 3) + + surface.SetFont("DermaDefaultBold") + surface.SetTextColor(200, 100, 255, 255) + local title = "🌙 СУДНАЯ НОЧЬ" + local tw, th = surface.GetTextSize(title) + surface.SetTextPos(x + (w - tw) / 2, y + 8) + surface.DrawText(title) + + surface.SetFont("DermaDefault") + surface.SetTextColor(255, 255, 255, 255) + local time_text = string.format("Время: %02d:%02d", minutes, seconds) + tw, th = surface.GetTextSize(time_text) + surface.SetTextPos(x + (w - tw) / 2, y + th + 15) + surface.DrawText(time_text) + + local alive_pulse = 200 + math.abs(math.sin(CurTime() * 3)) * 55 + surface.SetTextColor(255, 150, 150, alive_pulse) + local alive_text = string.format("Выживших: %d", alive_count) + tw, th = surface.GetTextSize(alive_text) + surface.SetTextPos(x + (w - tw) / 2, y + th + 45) + surface.DrawText(alive_text) + + if (LocalPlayer():GetObserverMode() != OBS_MODE_NONE) then + surface.SetTextColor(255, 100, 100, alive_pulse) + local spec_text = "РЕЖИМ НАБЛЮДЕНИЯ" + tw, th = surface.GetTextSize(spec_text) + surface.SetTextPos(x + (w - tw) / 2, y + h - 25) + surface.DrawText(spec_text) + end +end + +function PLUGIN:DoomsdayPlayerStats() + if (!GetGlobalBool("doomsday_night_active", false)) then return end + local ply = LocalPlayer() + + local x, y, w, h = 20, 20, 220, 120 + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(x, y, w, h) + surface.SetDrawColor(100, 0, 100, 200) + surface.DrawOutlinedRect(x, y, w, h, 2) + + surface.SetFont("DermaDefaultBold") + surface.SetTextColor(200, 200, 255, 255) + surface.SetTextPos(x + 10, y + 10) + surface.DrawText("📊 Ваша статистика:") + + surface.SetFont("DermaDefault") + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(x + 10, y + 35) + surface.DrawText(string.format("❤️ HP: %d", ply:Health())) + surface.SetTextPos(x + 10, y + 55) + surface.DrawText(string.format("🛡️ Armor: %d", ply:Armor())) + + local weapon = ply:GetActiveWeapon() + if (IsValid(weapon)) then + local w_name = string.gsub(weapon:GetClass(), "tfa_ins2_", ""):upper() + surface.SetTextPos(x + 10, y + 75) + surface.DrawText("🔫 Оружие: " .. w_name) + end +end + +function PLUGIN:DoomsdayDeathEffect() + if (!GetGlobalBool("doomsday_night_active", false)) then return end + local ply = LocalPlayer() + if (ply:Alive()) then return end + + local alpha = 50 + math.abs(math.sin(CurTime() * 2)) * 30 + surface.SetDrawColor(150, 0, 0, alpha) + surface.DrawRect(0, 0, ScrW(), ScrH()) + + surface.SetFont("DermaLarge") + local title_pulse = 200 + math.abs(math.sin(CurTime() * 3)) * 55 + surface.SetTextColor(255, 50, 50, title_pulse) + local text = "💀 ВЫБЫЛ 💀" + local tw, th = surface.GetTextSize(text) + surface.SetTextPos((ScrW() - tw) / 2, ScrH() / 2 - 100) + surface.DrawText(text) +end + +function PLUGIN:HUDPaint() + self:DrawEventHUD() + self:DoomsdayHUDPaint() + self:DoomsdayPlayerStats() + self:DoomsdayDeathEffect() +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/events_system/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/events_system/cl_plugin.lua new file mode 100644 index 0000000..6be7b5e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/events_system/cl_plugin.lua @@ -0,0 +1,131 @@ +local PLUGIN = PLUGIN + +local event_active = false +local event_start_time = 0 +local event_zone_active = false +local event_zone_min = nil +local event_zone_max = nil + +-- Net Receivers +net.Receive("events_system_notification", function() + local message = net.ReadString() + local lines = string.Split(message, "\n") + for _, line in ipairs(lines) do + if (string.Trim(line) != "") then + chat.AddText(Color(255, 100, 100), " [EVENT] ", Color(255, 255, 255), line) + end + end + surface.PlaySound("buttons/button15.wav") +end) + +net.Receive("events_system_hud_update", function() + event_active = net.ReadBool() + event_start_time = net.ReadInt(32) +end) + +net.Receive("events_system_zone_update", function() + event_zone_active = net.ReadBool() + if (event_zone_active) then + event_zone_min = net.ReadVector() + event_zone_max = net.ReadVector() + else + event_zone_min = nil + event_zone_max = nil + end +end) + +-- Fonts +surface.CreateFont("EventHUD_Title", { font = "Roboto", size = 24, weight = 700, antialias = true, shadow = true }) +surface.CreateFont("EventHUD_Time", { font = "Roboto", size = 32, weight = 800, antialias = true, shadow = false }) +surface.CreateFont("EventHUD_Label", { font = "Roboto", size = 16, weight = 500, antialias = true }) +surface.CreateFont("EventHUD_Warning", { font = "Roboto", size = 14, weight = 600, antialias = true }) + +local function DrawGradientBox(x, y, w, h, color1, color2, vertical) + local steps = 20 + local step_size = vertical and (h / steps) or (w / steps) + for i = 0, steps - 1 do + local t = i / steps + local r = Lerp(t, color1.r, color2.r) + local g = Lerp(t, color1.g, color2.g) + local b = Lerp(t, color1.b, color2.b) + local a = Lerp(t, color1.a, color2.a) + surface.SetDrawColor(r, g, b, a) + if (vertical) then + surface.DrawRect(x, y + i * step_size, w, step_size + 1) + else + surface.DrawRect(x + i * step_size, y, step_size + 1, h) + end + end +end + +function PLUGIN:DrawEventHUD() + local active = event_active or GetGlobalBool("events_system_active", false) + local start_time = event_start_time == 0 and GetGlobalInt("events_system_start_time", 0) or event_start_time + + if (!active or start_time == 0) then return end + + local elapsed = CurTime() - start_time + local minutes = math.floor(elapsed / 60) + local seconds = math.floor(elapsed % 60) + + local padding = 20 + local x = ScrW() - 320 - padding + local y = padding + local w, h = 320, 140 + + DrawGradientBox(x, y, w, h, Color(15, 15, 20, 220), Color(25, 25, 35, 200), true) + + local pulse = math.abs(math.sin(CurTime() * 2)) + DrawGradientBox(x, y, w, 4, Color(255, 50, 80, 180 + pulse * 75), Color(255, 100, 50, 180 + pulse * 75), false) + + surface.SetDrawColor(255, 80, 100, 100 + pulse * 80) + for i = 1, 2 do surface.DrawOutlinedRect(x - i, y - i, w + i * 2, h + i * 2, 1) end + + local offsetY = y + 15 + surface.SetFont("EventHUD_Title") + surface.SetTextColor(255, 80 + pulse * 50, 80 + pulse * 50, 255) + local title = "⚔ БОЕВОЙ ИВЕНТ" + local tw, th = surface.GetTextSize(title) + surface.SetTextPos(x + (w - tw) / 2, offsetY) + surface.DrawText(title) + + offsetY = offsetY + th + 10 + surface.SetFont("EventHUD_Time") + surface.SetTextColor(255, 255, 255, 255) + local time_text = string.format("%02d:%02d", minutes, seconds) + tw, th = surface.GetTextSize(time_text) + surface.SetTextPos(x + (w - tw) / 2, offsetY) + surface.DrawText(time_text) + + offsetY = offsetY + th + 10 + surface.SetFont("EventHUD_Warning") + surface.SetTextColor(255, 220, 100, 200 + pulse * 55) + local warning = "⚠ При смерти выбываете" + tw, th = surface.GetTextSize(warning) + surface.SetTextPos(x + (w - tw) / 2, offsetY) + surface.DrawText(warning) +end + +function PLUGIN:PostDrawTranslucentRenderables() + if (!event_zone_active or !event_zone_min or !event_zone_max) then return end + + local min, max = event_zone_min, event_zone_max + local corners = { + Vector(min.x, min.y, min.z), Vector(max.x, min.y, min.z), Vector(max.x, max.y, min.z), Vector(min.x, max.y, min.z), + Vector(min.x, min.y, max.z), Vector(max.x, min.y, max.z), Vector(max.x, max.y, max.z), Vector(min.x, max.y, max.z) + } + + local color = Color(0, 255, 100, 150) + render.DrawLine(corners[1], corners[2], color, true) + render.DrawLine(corners[2], corners[3], color, true) + render.DrawLine(corners[3], corners[4], color, true) + render.DrawLine(corners[4], corners[1], color, true) + render.DrawLine(corners[5], corners[6], color, true) + render.DrawLine(corners[6], corners[7], color, true) + render.DrawLine(corners[7], corners[8], color, true) + render.DrawLine(corners[8], corners[5], color, true) + render.DrawLine(corners[1], corners[5], color, true) + render.DrawLine(corners[2], corners[6], color, true) + render.DrawLine(corners[3], corners[7], color, true) + render.DrawLine(corners[4], corners[8], color, true) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/events_system/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/events_system/sh_plugin.lua new file mode 100644 index 0000000..9c80137 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/events_system/sh_plugin.lua @@ -0,0 +1,212 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Система Ивентов" +PLUGIN.author = "Scripty & RefoselTeamWork" +PLUGIN.description = "Продвинутая система ивентов с сужающейся зоной и кастомными спавнами." + +ix.config.Add("eventAllowedRanks", "superadmin,curator,ivent", "Ранги, которым разрешено управление ивентами (через запятую).", nil, { + category = "Events" +}) + +-- Shared Config (ported from sh_config.lua) +PLUGIN.Config = { + sounds = { + event_start = "ambient/alarms/klaxon1.wav", + event_end = "ambient/levels/citadel/citadel_hit.wav", + player_join = "buttons/button15.wav" + } +} + +ix.util.Include("sv_plugin.lua") +ix.util.Include("sv_doomsday.lua") +ix.util.Include("cl_plugin.lua") +ix.util.Include("cl_doomsday.lua") + +-- Commands Adaptation +ix.command.Add("EventStart", { + description = "Запустить стандартный боевой ивент.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + if (PLUGIN.active) then + return "Ивент уже активен!" + end + PLUGIN:StartEvent(client) + return "Запуск ивента..." + end +}) + +ix.command.Add("EventStop", { + description = "Остановить текущий активный ивент.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + if (!PLUGIN.active) then + return "Нет активного ивента для остановки!" + end + PLUGIN:EndEvent(client) + return "Остановка ивента..." + end +}) + +ix.command.Add("EventOtkat", { + description = "Отменить активный ивент с уведомлением.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + if (!PLUGIN.active) then + return "Нет активного ивента для отмены!" + end + PLUGIN:EndEvent(client, true) + return "Отмена ивента..." + end +}) + +ix.command.Add("EventJoin", { + description = "Присоединиться к активному ивенту.", + OnRun = function(self, client) + if (!PLUGIN.active) then + return "Нет активного ивента для входа." + end + + if (PLUGIN.participants[client:SteamID()]) then + return "Вы уже участвуете!" + end + + PLUGIN:AddParticipant(client) + + local participants_count = table.Count(PLUGIN.participants) + PLUGIN:BroadcastMessage(string.format("%s присоединился к ивенту! Участников: %d", client:Nick(), participants_count)) + end +}) + +ix.command.Add("EventAddAll", { + description = "Добавить всех игроков онлайн в ивент.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + if (!PLUGIN.active) then + return "Ивент не активен!" + end + PLUGIN:AddAllPlayers(client) + end +}) + +ix.command.Add("EventSetZone1", { + description = "Установить первую точку зоны ивента.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + PLUGIN:SetZonePoint1(client) + end +}) + +ix.command.Add("EventSetZone2", { + description = "Установить вторую точку зоны ивента.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + PLUGIN:SetZonePoint2(client) + end +}) + +ix.command.Add("EventClearZone", { + description = "Очистить зону ивента.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + PLUGIN:ClearZone(client) + end +}) + +ix.command.Add("EventZoneStart", { + description = "Начать сужение зоны ивента.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + PLUGIN:StartZoneShrinking(client) + end +}) + +ix.command.Add("EventZoneStop", { + description = "Остановить сужение зоны ивента.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + PLUGIN:StopZoneShrinking(client) + end +}) + +-- Temporary Spawns Commands +ix.command.Add("EventSpawnAdd", { + description = "Добавить временный спавн для фракции или spec_def.", + privilege = "Manage Events", + adminOnly = true, + arguments = { + ix.type.string, -- Faction Name + bit.bor(ix.type.number, ix.type.optional) -- spec_def + }, + OnRun = function(self, client, factionName, specDef) + local faction = ix.faction.teams[factionName] + if (!faction) then + -- Try fuzzy match + for _, v in pairs(ix.faction.indices) do + if (ix.util.StringMatches(v.name, factionName) or ix.util.StringMatches(v.uniqueID, factionName)) then + faction = v + break + end + end + end + + if (!faction) then + return "Некорректное название фракции!" + end + + PLUGIN:AddTempSpawn(faction.uniqueID, specDef, client:GetPos(), client:GetAngles()) + + local msg = "Временный спавн добавлен для фракции: " .. faction.name + if (specDef) then + msg = msg .. " (spec_def: " .. specDef .. ")" + end + return msg + end +}) + +ix.command.Add("EventSpawnRemove", { + description = "Удалить временные спавны ивента в радиусе.", + privilege = "Manage Events", + adminOnly = true, + arguments = { + bit.bor(ix.type.number, ix.type.optional) -- Radius + }, + OnRun = function(self, client, radius) + radius = radius or 120 + local count = PLUGIN:RemoveTempSpawns(client:GetPos(), radius) + return "Удалено " .. count .. " временных спавнов." + end +}) + +-- Doomsday Commands +ix.command.Add("DoomsdayStart", { + description = "Запустить ивент Судная Ночь.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + if (DoomsdayNight.active) then + return "Судная Ночь уже активна!" + end + DoomsdayNight:StartEvent(client) + end +}) + +ix.command.Add("DoomsdayStop", { + description = "Остановить ивент Судная Ночь.", + privilege = "Manage Events", + adminOnly = true, + OnRun = function(self, client) + if (!DoomsdayNight.active) then + return "Судная Ночь не активна!" + end + DoomsdayNight:EndEvent(client, true) + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/events_system/sv_doomsday.lua b/garrysmod/gamemodes/militaryrp/plugins/events_system/sv_doomsday.lua new file mode 100644 index 0000000..5583938 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/events_system/sv_doomsday.lua @@ -0,0 +1,184 @@ +DoomsdayNight = DoomsdayNight or {} +DoomsdayNight.active = false +DoomsdayNight.start_time = 0 +DoomsdayNight.participants = {} +DoomsdayNight.spectators = {} +DoomsdayNight.spawn_positions = {} + +DoomsdayNight.weapons = { + "tfa_ins2_ak74", "tfa_ins2_m4a1", "tfa_ins2_ak12", "tfa_ins2_m16a4", "tfa_ins2_scar_h_ssr", + "tfa_ins2_m40a1", "tfa_ins2_mosin", "tfa_ins2_svd", "tfa_ins2_m590", "tfa_ins2_toz", + "tfa_ins2_glock_17", "tfa_ins2_m9", "tfa_ins2_makarov", "tfa_ins2_mp5", "tfa_ins2_ump45", "tfa_ins2_mp40" +} + +util.AddNetworkString("doomsday_night_notification") +util.AddNetworkString("doomsday_night_hud_update") + +function DoomsdayNight:Init() + SetGlobalBool("doomsday_night_active", false) + SetGlobalInt("doomsday_night_start_time", 0) + SetGlobalInt("doomsday_night_alive_count", 0) + self:GenerateSpawnPositions() +end + +function DoomsdayNight:GenerateSpawnPositions() + local map = game.GetMap() + local map_positions = { + ["gm_construct"] = { + Vector(980, -1500, -79), Vector(-700, 1000, 200), Vector(1500, 800, 100), Vector(-1200, -800, 50) + }, + ["rp_valkyrie_forest_v1"] = { + Vector(5000, 3000, 200), Vector(-5000, -3000, 150), Vector(8000, -2000, 100), Vector(-8000, 2000, 180) + } + } + self.spawn_positions = map_positions[map] or { Vector(1000, 1000, 100), Vector(-1000, -1000, 100) } +end + +function DoomsdayNight:StartEvent(ply) + local players = player.GetAll() + if (#players < 2) then + ply:Notify("Недостаточно игроков для начала Судной Ночи!") + return + end + + self.active = true + self.start_time = CurTime() + self.participants = {} + self.spectators = {} + + SetGlobalBool("doomsday_night_active", true) + SetGlobalInt("doomsday_night_start_time", CurTime()) + + for _, player in ipairs(players) do + if (IsValid(player)) then self:PreparePlayer(player) end + end + + self:BroadcastMessage("🌙 СУДНАЯ НОЧЬ НАЧАЛАСЬ! 🌙\nСражайтесь до последнего выжившего!\nВыдано случайное оружие.") + + timer.Simple(3, function() if (self.active) then self:TeleportPlayers() end end) + self:UpdateHUD() +end + +function DoomsdayNight:PreparePlayer(ply) + local steamid = ply:SteamID() + self.participants[steamid] = { + start_pos = ply:GetPos(), + join_time = CurTime(), + kills = 0, + nick = ply:Nick(), + alive = true + } + ply:StripWeapons() + self:GiveRandomWeapons(ply) +end + +function DoomsdayNight:GiveRandomWeapons(ply) + if (!IsValid(ply) or !ply:Alive()) then return end + ply:Give("ix_hands") + + local weapons_to_give = {} + local used = {} + for i = 1, math.random(2, 3) do + local w = self.weapons[math.random(1, #self.weapons)] + if (!used[w]) then + used[w] = true + table.insert(weapons_to_give, w) + end + end + + for _, w_class in ipairs(weapons_to_give) do + local weapon = ply:Give(w_class) + if (IsValid(weapon)) then + ply:GiveAmmo(500, weapon:GetPrimaryAmmoType(), true) + end + end + + ply:Give("weapon_frag") + ply:GiveAmmo(2, "Grenade", true) + ply:SetHealth(100) + ply:SetArmor(100) +end + +function DoomsdayNight:TeleportPlayers() + local players = {} + for steamid, data in pairs(self.participants) do + local ply = player.GetBySteamID(steamid) + if (IsValid(ply)) then table.insert(players, ply) end + end + + local positions = table.Copy(self.spawn_positions) + for i, ply in ipairs(players) do + local spawn_pos = positions[((i - 1) % #positions) + 1] + ply:SetPos(spawn_pos) + ply:SetEyeAngles(Angle(0, math.random(0, 360), 0)) + end + + SetGlobalInt("doomsday_night_alive_count", #players) + self:BroadcastMessage("⚡ Игроки телепортированы! К БОЮ!") +end + +function DoomsdayNight:EndEvent(ply, cancelled) + if (!self.active) then return end + self.active = false + SetGlobalBool("doomsday_night_active", false) + + for steamid, _ in pairs(self.spectators) do + local spec_ply = player.GetBySteamID(steamid) + if (IsValid(spec_ply)) then + spec_ply:UnSpectate() + spec_ply:Spawn() + end + end + + self:BroadcastMessage(cancelled and "🛑 СУДНАЯ НОЧЬ ОТМЕНЕНА" or "🏆 СУДНАЯ НОЧЬ ЗАВЕРШЕНА") + self.participants = {} + self.spectators = {} + self:UpdateHUD() +end + +function DoomsdayNight:OnPlayerKilled(victim, attacker) + if (!self.active) then return end + local data = self.participants[victim:SteamID()] + if (!data) then return end + + data.alive = false + if (IsValid(attacker) and attacker:IsPlayer()) then + local attacker_data = self.participants[attacker:SteamID()] + if (attacker_data) then attacker_data.kills = attacker_data.kills + 1 end + end + + timer.Simple(2, function() + if (IsValid(victim)) then + self.spectators[victim:SteamID()] = true + victim:Spectate(OBS_MODE_ROAMING) + victim:Notify("👁️ Вы перешли в режим наблюдения до конца ивента.") + end + end) + + local alive_count = 0 + for _, d in pairs(self.participants) do + if (d.alive) then alive_count = alive_count + 1 end + end + SetGlobalInt("doomsday_night_alive_count", alive_count) + + if (alive_count <= 1) then + timer.Simple(3, function() if (self.active) then self:EndEvent() end end) + end +end + +function DoomsdayNight:BroadcastMessage(msg) + for _, ply in ipairs(player.GetAll()) do ply:ChatPrint(msg) end +end + +function DoomsdayNight:UpdateHUD() + net.Start("doomsday_night_hud_update") + net.WriteBool(self.active) + net.WriteInt(self.start_time, 32) + net.Broadcast() +end + +-- Hook aliases for PLUGIN +function PLUGIN:DoomsdayNight_Init() DoomsdayNight:Init() end +function PLUGIN:DoomsdayNight_PlayerDeath(victim, inflictor, attacker) DoomsdayNight:OnPlayerKilled(victim, attacker) end + +hook.Add("InitPostEntity", "DoomsdayNight_Init", function() DoomsdayNight:Init() end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/events_system/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/events_system/sv_plugin.lua new file mode 100644 index 0000000..a64aa29 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/events_system/sv_plugin.lua @@ -0,0 +1,360 @@ +local PLUGIN = PLUGIN + +PLUGIN.active = PLUGIN.active or false +PLUGIN.start_time = PLUGIN.start_time or 0 +PLUGIN.participants = PLUGIN.participants or {} +PLUGIN.eliminated_players = PLUGIN.eliminated_players or {} +PLUGIN.initiator = PLUGIN.initiator or nil +PLUGIN.tempSpawns = PLUGIN.tempSpawns or {} -- [factionUniqueID] = { [specDef or "default"] = { {pos, ang}, ... } } + +-- Zone Data +PLUGIN.zone = PLUGIN.zone or { + pos1 = nil, + pos2 = nil, + min = nil, + max = nil, + shrinking = false, + center = nil, + shrink_speed = 1, + min_size = 500 +} + +-- Networking +util.AddNetworkString("events_system_notification") +util.AddNetworkString("events_system_hud_update") +util.AddNetworkString("events_system_zone_update") + +-- Initialization +function PLUGIN:InitPostEntity() + SetGlobalBool("events_system_active", false) + SetGlobalInt("events_system_start_time", 0) + + self.tempSpawns = self:GetData() or {} +end + +function PLUGIN:SaveTempSpawns() + self:SetData(self.tempSpawns) +end + +-- Permissions Check (using ix.config) +function PLUGIN:HasPermission(ply) + if (!IsValid(ply)) then return false end + if (ply:IsSuperAdmin()) then return true end + + local allowed = string.Split(ix.config.Get("eventAllowedRanks", ""), ",") + local userGroup = ply:GetUserGroup() + + for _, rank in ipairs(allowed) do + if (string.lower(userGroup) == string.lower(string.Trim(rank))) then + return true + end + end + + return false +end + +-- Zone Logic +function PLUGIN:SetZonePoint1(ply) + self.zone.pos1 = ply:GetPos() + self:SendMessage(ply, "✅ Точка зоны 1 установлена: " .. tostring(self.zone.pos1)) + if (self.zone.pos2) then self:CalculateZoneBounds() end +end + +function PLUGIN:SetZonePoint2(ply) + self.zone.pos2 = ply:GetPos() + self:SendMessage(ply, "✅ Точка зоны 2 установлена: " .. tostring(self.zone.pos2)) + if (self.zone.pos1) then self:CalculateZoneBounds() end +end + +function PLUGIN:CalculateZoneBounds() + if (!self.zone.pos1 or !self.zone.pos2) then return end + + self.zone.min = Vector( + math.min(self.zone.pos1.x, self.zone.pos2.x), + math.min(self.zone.pos1.y, self.zone.pos2.y), + math.min(self.zone.pos1.z, self.zone.pos2.z) + ) + + self.zone.max = Vector( + math.max(self.zone.pos1.x, self.zone.pos2.x), + math.max(self.zone.pos1.y, self.zone.pos2.y), + math.max(self.zone.pos1.z, self.zone.pos2.z) + ) + + self:BroadcastZone() + local size = self.zone.max - self.zone.min + self:BroadcastMessage(string.format("🎯 Зона ивента установлена! Размер: %.0f x %.0f x %.0f", size.x, size.y, size.z)) +end + +function PLUGIN:BroadcastZone() + if (!self.zone.min or !self.zone.max) then return end + net.Start("events_system_zone_update") + net.WriteBool(true) + net.WriteVector(self.zone.min) + net.WriteVector(self.zone.max) + net.Broadcast() +end + +function PLUGIN:ClearZone(ply) + self.zone.pos1 = nil + self.zone.pos2 = nil + self.zone.min = nil + self.zone.max = nil + self.zone.shrinking = false + + if (timer.Exists("EventsSystem_ZoneShrink")) then + timer.Remove("EventsSystem_ZoneShrink") + end + + net.Start("events_system_zone_update") + net.WriteBool(false) + net.Broadcast() + + if (IsValid(ply)) then + self:SendMessage(ply, "🗑️ Зона ивента очищена.") + end +end + +function PLUGIN:StartZoneShrinking(ply) + if (!self.zone.min or !self.zone.max) then + if (IsValid(ply)) then self:SendMessage(ply, "❌ Сначала установите зону!") end + return + end + + if (self.zone.shrinking) then + if (IsValid(ply)) then self:SendMessage(ply, "⚠️ Зона уже сужается!") end + return + end + + self.zone.center = (self.zone.min + self.zone.max) / 2 + self.zone.shrinking = true + + timer.Create("EventsSystem_ZoneShrink", 0.1, 0, function() + self:ShrinkZone() + end) + + self:BroadcastMessage("🔴 ВНИМАНИЕ! Зона начала сужаться к центру!") + for _, player in ipairs(player.GetAll()) do + player:EmitSound("ambient/alarms/warningbell1.wav", 75, 100) + end +end + +function PLUGIN:StopZoneShrinking(ply) + if (!self.zone.shrinking) then return end + self.zone.shrinking = false + timer.Remove("EventsSystem_ZoneShrink") + + local size = self.zone.max - self.zone.min + self:BroadcastMessage(string.format("⏸️ Сужение зоны остановлено! Текущий размер: %.0f x %.0f", size.x, size.y)) +end + +function PLUGIN:ShrinkZone() + if (!self.zone.shrinking or !self.zone.min or !self.zone.max or !self.zone.center) then return end + + local shrink_amount = self.zone.shrink_speed * 0.1 + local new_min = Vector(self.zone.min.x + shrink_amount, self.zone.min.y + shrink_amount, self.zone.min.z) + local new_max = Vector(self.zone.max.x - shrink_amount, self.zone.max.y - shrink_amount, self.zone.max.z) + + if (new_max.x - new_min.x <= self.zone.min_size or new_max.y - new_min.y <= self.zone.min_size) then + self:StopZoneShrinking(nil) + self:BroadcastMessage("❗ Зона достигла минимального размера!") + return + end + + self.zone.min = new_min + self.zone.max = new_max + self:BroadcastZone() +end + +function PLUGIN:IsInZone(pos) + if (!self.zone.min or !self.zone.max) then return true end + return pos.x >= self.zone.min.x and pos.x <= self.zone.max.x and + pos.y >= self.zone.min.y and pos.y <= self.zone.max.y and + pos.z >= self.zone.min.z and pos.z <= self.zone.max.z +end + +function PLUGIN:CheckZoneBoundaries() + if (!self.active) then return end + for steamid, data in pairs(self.participants) do + local ply = player.GetBySteamID(steamid) + if (IsValid(ply) and ply:Alive()) then + if (!self:IsInZone(ply:GetPos())) then + ply:TakeDamage(10, ply, ply) + ply:Notify("⚠️ ВНИМАНИЕ! Вы вне зоны ивента! Немедленно вернитесь!") + ply:EmitSound("buttons/button10.wav", 75, 100) + end + end + end +end + +-- Event Control +function PLUGIN:StartEvent(ply) + self.active = true + self.start_time = CurTime() + self.participants = {} + self.eliminated_players = {} + self.initiator = ply + + SetGlobalBool("events_system_active", true) + SetGlobalInt("events_system_start_time", CurTime()) + + local initiator_name = IsValid(ply) and ply:Nick() or "Администратор" + local start_message = "⚔️ БОЕВОЙ ИВЕНТ НАЧАЛСЯ! ⚔️\nОрганизатор: " .. initiator_name .. "\nКак участвовать: Используйте /EventJoin\nЦель: Выжить и победить!" + self:BroadcastMessage(start_message) + + for _, player in ipairs(player.GetAll()) do + player:EmitSound(self.Config.sounds.event_start, 75, 100, 0.8) + end + + self:UpdateHUD() + timer.Create("EventsSystem_ZoneCheck", 1, 0, function() self:CheckZoneBoundaries() end) +end + +function PLUGIN:EndEvent(ply, cancelled) + if (!self.active) then return end + + local stats_lines = { + "╔═══════════════════════════════════════╗", + "║ 📊 ФИНАЛЬНАЯ СТАТИСТИКА 📊 ║", + "╚═══════════════════════════════════════╝", + string.format("👥 Всего участников: %d", table.Count(self.participants) + table.Count(self.eliminated_players)) + } + + self.active = false + SetGlobalBool("events_system_active", false) + SetGlobalInt("events_system_start_time", 0) + + local end_message = cancelled and "🛑 ИВЕНТ ОТМЕНЕН АДМИНИСТРАТОРОМ" or "🏆 ИВЕНТ ЗАВЕРШЕН 🏆" + self:BroadcastMessage(end_message) + for _, line in ipairs(stats_lines) do self:BroadcastMessage(line) end + + for _, player in ipairs(player.GetAll()) do + player:EmitSound(self.Config.sounds.event_end, 75, 100, 0.6) + end + + self.participants = {} + self.eliminated_players = {} + timer.Remove("EventsSystem_ZoneCheck") + self:UpdateHUD() +end + +function PLUGIN:AddParticipant(ply) + if (!self.active or !IsValid(ply)) then return end + self.participants[ply:SteamID()] = { + join_time = CurTime(), + kills = 0, + nick = ply:Nick() + } + ply:Notify("✅ ВЫ ПРИСОЕДИНИЛИСЬ К ИВЕНТУ!") + ply:EmitSound(self.Config.sounds.player_join, 50, 100, 0.5) +end + +function PLUGIN:AddAllPlayers(admin_ply) + for _, ply in ipairs(player.GetAll()) do + self:AddParticipant(ply) + end + self:BroadcastMessage("📢 Все игроки добавлены в ивент!") +end + +-- Hooks +function PLUGIN:PlayerDeath(victim, inflictor, attacker) + if (!self.active) then return end + local steamid = victim:SteamID() + local data = self.participants[steamid] + if (!data) then return end + + if (IsValid(attacker) and attacker:IsPlayer()) then + local attacker_data = self.participants[attacker:SteamID()] + if (attacker_data) then attacker_data.kills = attacker_data.kills + 1 end + end + + self.eliminated_players[steamid] = data + self.participants[steamid] = nil + + victim:Notify("💀 Вы выбыли из ивента!") + self:BroadcastMessage(string.format("💀 %s выбыл! Убийств: %d", victim:Nick(), data.kills)) + + timer.Simple(0.5, function() self:CheckEventEnd() end) +end + +function PLUGIN:CheckEventEnd() + if (!self.active) then return end + if (table.Count(self.participants) <= 1) then + self:EndEvent() + end +end + +function PLUGIN:PlayerDisconnected(ply) + if (!self.active) then return end + self.participants[ply:SteamID()] = nil +end + +-- Messaging +function PLUGIN:SendMessage(ply, message) + if (!IsValid(ply)) then return end + net.Start("events_system_notification") + net.WriteString(message) + net.Send(ply) +end + +function PLUGIN:BroadcastMessage(message) + for _, ply in ipairs(player.GetAll()) do + ply:ChatPrint(message) + end +end + +function PLUGIN:UpdateHUD() + net.Start("events_system_hud_update") + net.WriteBool(self.active) + net.WriteInt(self.start_time, 32) + net.Broadcast() +end + +-- Temporary Spawns Implementation +function PLUGIN:AddTempSpawn(factionID, specDef, pos, ang) + specDef = specDef or "default" + self.tempSpawns[factionID] = self.tempSpawns[factionID] or {} + self.tempSpawns[factionID][specDef] = self.tempSpawns[factionID][specDef] or {} + + table.insert(self.tempSpawns[factionID][specDef], {pos = pos, ang = ang}) + self:SaveTempSpawns() +end + +function PLUGIN:RemoveTempSpawns(pos, radius) + local count = 0 + for factionID, specs in pairs(self.tempSpawns) do + for specDef, points in pairs(specs) do + for k, v in pairs(points) do + if (v.pos:Distance(pos) <= radius) then + points[k] = nil + count = count + 1 + end + end + end + end + if (count > 0) then self:SaveTempSpawns() end + return count +end + +function PLUGIN:PostPlayerLoadout(client) + if (!self.active) then return end + + local char = client:GetCharacter() + if (!char) then return end + + local faction = ix.faction.indices[client:Team()] + if (!faction) then return end + + local factionID = faction.uniqueID + local podr = char:GetPodr() + local factionTable = ix.faction.Get(client:Team()) + local specDef = (factionTable and factionTable.Podr and factionTable.Podr[podr]) and factionTable.Podr[podr].spec_def or "default" + + local points = (self.tempSpawns[factionID] and self.tempSpawns[factionID][specDef]) or (self.tempSpawns[factionID] and self.tempSpawns[factionID]["default"]) + + if (points and !table.IsEmpty(points)) then + local point = table.Random(points) + client:SetPos(point.pos) + client:SetEyeAngles(point.ang) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/cl_legacy.lua b/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/cl_legacy.lua new file mode 100644 index 0000000..5932d4e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/cl_legacy.lua @@ -0,0 +1,444 @@ +-- Extended Spawnmenu - Legacy Addons (старые аддоны и загрузки) +local PLUGIN = PLUGIN + +language.Add("spawnmenu.category.addonslegacy", "Addons - Legacy") +language.Add("spawnmenu.category.downloads", "Downloads") + +local function AddRecursive( pnl, folder ) + local files, folders = file.Find( folder .. "*", "MOD" ) + + for k, v in pairs( files or {} ) do + if ( !string.EndsWith( v, ".mdl" ) ) then continue end + + local cp = spawnmenu.GetContentType( "model" ) + if ( cp ) then + local mdl = folder .. v + mdl = string.sub( mdl, string.find( mdl, "models/" ), string.len( mdl ) ) + mdl = string.gsub( mdl, "models/models/", "models/" ) + cp( pnl, { model = mdl } ) + end + end + + for k, v in pairs( folders or {} ) do AddRecursive( pnl, folder .. v .. "/" ) end +end + +local function CountRecursive( folder ) + local files, folders = file.Find( folder .. "*", "MOD" ) + local val = 0 + + for k, v in pairs( files or {} ) do if ( string.EndsWith( v, ".mdl" ) ) then val = val + 1 end end + for k, v in pairs( folders or {} ) do val = val + CountRecursive( folder .. v .. "/" ) end + return val +end + +hook.Add( "PopulateContent", "LegacyAddonProps", function( pnlContent, tree, node ) + + if ( !IsValid( node ) or !IsValid( pnlContent ) ) then + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR LEGACY ADDONS!!!" ) + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR LEGACY ADDONS!!!" ) + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR LEGACY ADDONS!!!" ) + return + end + + local ViewPanel = vgui.Create( "ContentContainer", pnlContent ) + ViewPanel:SetVisible( false ) + + local addons = {} + + local _files, folders = file.Find( "addons/*", "MOD" ) + for _, f in pairs( folders ) do + + if ( !file.IsDir( "addons/" .. f .. "/models/", "MOD" ) ) then continue end + + local count = CountRecursive( "addons/" .. f .. "/models/", "MOD" ) + if ( count == 0 ) then continue end + + table.insert( addons, { + name = f, + count = count, + path = "addons/" .. f .. "/models/" + } ) + + end + + local LegacyAddons = node:AddNode( "#spawnmenu.category.addonslegacy", "icon16/folder_database.png" ) + for _, f in SortedPairsByMemberValue( addons, "name" ) do + + local models = LegacyAddons:AddNode( f.name .. " (" .. f.count .. ")", "icon16/bricks.png" ) + models.DoClick = function() + ViewPanel:Clear( true ) + AddRecursive( ViewPanel, f.path ) + pnlContent:SwitchPanel( ViewPanel ) + end + + end + + --[[ -------------------------- DOWNLOADS -------------------------- ]] + + local fi, fo = file.Find( "download/models", "MOD" ) + if ( !fi && !fo ) then return end + + local Downloads = node:AddFolder( "#spawnmenu.category.downloads", "download/models", "MOD", false, false, "*.*" ) + Downloads:SetIcon( "icon16/folder_database.png" ) + + Downloads.OnNodeSelected = function( self, selectedNode ) + ViewPanel:Clear( true ) + + local path = selectedNode:GetFolder() + + if ( !string.EndsWith( path, "/" ) && string.len( path ) > 1 ) then path = path .. "/" end + local path_mdl = string.sub( path, string.find( path, "/models/" ) + 1 ) + + for k, v in pairs( file.Find( path .. "/*.mdl", selectedNode:GetPathID() ) ) do + + local cp = spawnmenu.GetContentType( "model" ) + if ( cp ) then + cp( ViewPanel, { model = path_mdl .. "/" .. v } ) + end + + end + + pnlContent:SwitchPanel( ViewPanel ) + end + +end ) + +--[[ -------------------------------------------------------------------------- The addon info -------------------------------------------------------------------------- ]] + +concommand.Add( "extsm_addoninfo", function() + local frame = vgui.Create( "DFrame" ) + frame:SetSize( ScrW() - 100, ScrH() - 100 ) + frame:Center() + frame:MakePopup() + + local sp = frame:Add( "DScrollPanel" ) + sp:Dock( FILL ) + + sp:Add( "rb655_addonInfo" ) +end ) + +hook.Add( "AddToolMenuCategories", "LegacyAddonPropsInfoCategory", function() + spawnmenu.AddToolCategory( "Utilities", "Robotboy655", "#Robotboy655" ) +end ) + +hook.Add( "PopulateToolMenu", "LegacyAddonPropsInfoThing", function() + spawnmenu.AddToolMenuOption( "Utilities", "Robotboy655", "LegacyInfoPanel", "Addon Information", "", "", function( panel ) + panel:ClearControls() + panel:Button( "Open addon data window", "extsm_addoninfo" ) + end ) +end ) + +---------------------------------- + +function ScreenScaleH( size ) + return size * ( ScrH() / 480.0 ) +end + +surface.CreateFont( "AddonInfo_Header", { + font = "Helvetica", + size = ScreenScaleH( 24 ), + weight = 1000 +} ) + +surface.CreateFont( "AddonInfo_Text", { + font = "Helvetica", + size = ScreenScaleH( 9 ), + weight = 1000 +} ) + +surface.CreateFont( "AddonInfo_Small", { + font = "Helvetica", + size = ScreenScaleH( 8 ) +} ) + +local function GetWorkshopLeftovers() + + local subscriptions = {} + + for id, t in pairs( engine.GetAddons() ) do + subscriptions[ tonumber( t.wsid ) ] = true + end + + local t = {} + for id, fileh in pairs( file.Find( "addons/*.gma", "MOD" ) ) do + local a = string.StripExtension( fileh ) + a = string.Explode( "_", a ) + a = tonumber( a[ #a ] ) + if ( !subscriptions[ a ] ) then + table.insert( t, fileh ) + end + end + + return t + +end + +local function GetSize( b ) + b = b / 1000 + + if ( b < 1000 ) then + return math.floor( b * 10 ) / 10 .. " KB" + end + + b = b / 1000 + + if ( b < 1000 ) then + return math.floor( b * 10 ) / 10 .. " MB" + end + + b = b / 1000 + + return math.floor( b * 10 ) / 10 .. " GB" +end + +local function DrawText( txt, font, x, y, clr ) + draw.SimpleText( txt, font, x, y, clr ) + + surface.SetFont( font ) + return surface.GetTextSize( txt ) +end + +local PANEL = {} + +function PANEL:Init() + self.Computed = false +end + +function PANEL:Compute() + + self.WorkshopSize = 0 + for id, fle in pairs( file.Find( "addons/*.gma", "MOD" ) ) do + self.WorkshopSize = self.WorkshopSize + ( file.Size( "addons/" .. fle, "MOD" ) or 0 ) + end + + self.WorkshopWaste = 0 + self.WorkshopWasteFiles = {} + for id, fle in pairs( GetWorkshopLeftovers() ) do + self.WorkshopWaste = self.WorkshopWaste + ( file.Size( "addons/" .. fle, "MOD" ) or 0 ) + table.insert( self.WorkshopWasteFiles, { "addons/" .. fle, ( file.Size( "addons/" .. fle, "MOD" ) or 0 ) } ) + end + + -- ------------------------------------------- + + local _files, folders = file.Find( "addons/*", "MOD" ) + + self.LegacyAddons = {} + for k, v in pairs( folders or {} ) do + self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed" + + if ( file.IsDir( "addons/" .. v .. "/models/", "MOD" ) ) then + self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed (Has Models)" + end + + local _fi, fo = file.Find( "addons/" .. v .. "/*", "MOD" ) + if ( table.Count( fo or {} ) < 1 ) then + self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed (Empty)" + end + + if ( !file.IsDir( "addons/" .. v .. "/models/", "MOD" ) && !file.IsDir( "addons/" .. v .. "/materials/", "MOD" ) && !file.IsDir( "addons/" .. v .. "/lua/", "MOD" ) && !file.IsDir( "addons/" .. v .. "/sound/", "MOD" ) ) then + self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed Incorrectly!" + end + end + + -- ------------------------------------------- + + local luaFiles = file.Find( "cache/lua/*", "MOD" ) -- Too many files to count actual size! + self.LuaCacheSize = #luaFiles * 1400 + self.LuaCacheFiles = #luaFiles + + local wsFiles = file.Find( "cache/workshop/*", "MOD" ) + self.WSCacheSize = 0 + for id, fle in pairs( wsFiles ) do + self.WSCacheSize = self.WSCacheSize + ( file.Size( "cache/workshop/" .. fle, "MOD" ) or 0 ) + end + self.WSCacheFiles = #wsFiles + + self.Computed = true + +end + +function PANEL:Paint( w, h ) + + if ( !self.Computed ) then + self:Compute() + end + + local txtW = self:GetParent():GetWide() + local txtH = 0 + + -- ----------------------- + + local tW, tH = DrawText( "Cache Sizes", "AddonInfo_Header", 0, txtH, color_white ) + txtH = txtH + tH + + local localH = 0 + local localW = 0 + + -- ----------------------- + + tW, tH = DrawText( "~" .. GetSize( self.LuaCacheSize or 0 ) .. " (" .. self.LuaCacheFiles .. " files)", "AddonInfo_Small", 0, txtH + localH, Color( 220, 220, 220 ) ) + localH = localH + tH + localW = math.max( localW, tW ) + + tW, tH = DrawText( "~" .. GetSize( self.WSCacheSize or 0 ) .. " (" .. self.WSCacheFiles .. " files)", "AddonInfo_Small", 0, txtH + localH, Color( 220, 220, 220 ) ) + localH = localH + tH + localW = math.max( localW, tW ) + + -- ----------------------- + + localW = localW + 25 + + tW, tH = DrawText( "Server Lua cache", "AddonInfo_Small", localW, txtH, color_white ) + txtH = txtH + tH + + tW, tH = DrawText( "Workshop download cache", "AddonInfo_Small", localW, txtH, color_white ) + txtH = txtH + tH + + -- ------------------------------------------- + + txtH = txtH + ScreenScaleH( 8 ) + tW, tH = DrawText( "Workshop Subscriptions", "AddonInfo_Header", 0, txtH, color_white ) + txtH = txtH + tH + + -- ------------------------------------------- + + tW, tH = DrawText( "Used Size: ", "AddonInfo_Text", 0, txtH, color_white ) + local maxW = tW + txtH = txtH + tH + + tW, tH = DrawText( "Wasted Space: ", "AddonInfo_Text", 0, txtH, color_white ) + maxW = math.max( maxW, tW ) + txtH = txtH + tH + + tW, tH = DrawText( "Total Size: ", "AddonInfo_Text", 0, txtH, color_white ) + maxW = math.max( maxW, tW ) + txtH = txtH - tH * 2 + + -- ------------------------------------------- + + tW, tH = DrawText( GetSize( ( self.WorkshopSize - self.WorkshopWaste ) or 0 ), "AddonInfo_Text", maxW, txtH, Color( 220, 220, 220 ) ) + txtH = txtH + tH + + tW, tH = DrawText( GetSize( self.WorkshopWaste or 0 ), "AddonInfo_Text", maxW, txtH, Color( 220, 220, 220 ) ) + txtH = txtH + tH + + tW, tH = DrawText( GetSize( self.WorkshopSize or 0 ), "AddonInfo_Text", maxW, txtH, Color( 220, 220, 220 ) ) + txtH = txtH + tH * 2 + + -- ------------------------------------------- + + tW, tH = DrawText( "Files that aren't used: ( Safe to delete )", "AddonInfo_Text", 0, txtH, color_white ) + txtH = txtH + tH + + localH = 0 + localW = 0 + for id, t in pairs( self.WorkshopWasteFiles or {} ) do + tW, tH = DrawText( GetSize( t[ 2 ] ) .. " ", "AddonInfo_Small", 0, txtH + localH, Color( 220, 220, 220 ) ) + localH = localH + tH + localW = math.max( localW, tW ) + end + + for id, t in pairs( self.WorkshopWasteFiles or {} ) do + tW, tH = DrawText( t[ 1 ], "AddonInfo_Small", localW, txtH, color_white ) + txtH = txtH + tH + end + + -- ------------------------------------------- + + tW, tH = DrawText( "Legacy Addons", "AddonInfo_Header", 0, txtH + ScreenScaleH( 8 ), color_white ) + txtH = txtH + tH + ScreenScaleH( 8 ) + + -- ------------------------------------------- + + tW, tH = DrawText( "Legacy Addons with models:", "AddonInfo_Text", 0, txtH, color_white ) + txtH = txtH + tH + + if ( table.Count( self.LegacyAddons or {} ) > 0 ) then + local maxNameW = 0 + local oldH = txtH + for path, status in pairs( self.LegacyAddons or {} ) do + tW, tH = DrawText( path, "AddonInfo_Small", 0, txtH, color_white ) + maxNameW = math.max( maxNameW, tW ) + txtH = txtH + tH + end + + maxNameW = maxNameW + 25 + txtH = oldH + + for path, status in pairs( self.LegacyAddons or {} ) do + tW, tH = DrawText( status, "AddonInfo_Small", maxNameW, txtH, Color( 220, 220, 220 ) ) + txtH = txtH + tH + end + else + tW, tH = DrawText( "None.", "AddonInfo_Small", 0, txtH, color_white ) + txtH = txtH + tH + end + + if ( !system.IsWindows() ) then + txtH = txtH + tH + + tW, tH = DrawText( "OSX AND LINUX USERS BEWARE:", "AddonInfo_Text", 0, txtH, color_white ) + txtH = txtH + tH + tW, tH = DrawText( "MAKE SURE ALL FILE AND FOLDER NAMES", "AddonInfo_Text", 0, txtH, color_white ) + txtH = txtH + tH + tW, tH = DrawText( "IN ALL ADDONS ARE LOWERCASE ONLY", "AddonInfo_Text", 0, txtH, color_white ) + txtH = txtH + tH + tW, tH = DrawText( "INCLUDING ALL SUB FOLDERS", "AddonInfo_Text", 0, txtH, color_white ) + txtH = txtH + tH + end + + txtH = txtH + tH + + -- ------------------------------------------- + + self:SetSize( txtW, txtH ) +end + +vgui.Register( "rb655_addonInfo", PANEL, "Panel" ) + +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] + +-- I spent too much time on this than I care to admit +hook.Add( "PopulatePropMenu", "rb655_LoadLegacySpawnlists", function() + + local sid = 0 --table.Count( spawnmenu.GetPropTable() ) + + --local added = false + + for id, spawnlist in pairs( file.Find( "settings/spawnlist/*.txt", "MOD" ) ) do + local content = file.Read( "settings/spawnlist/" .. spawnlist, "MOD" ) + if ( !content ) then continue end + + --[[local is = string.find( content, "TableToKeyValues" ) + if ( is != nil ) then continue end + + for id, t in pairs( spawnmenu.GetPropTable() ) do -- This somehow freezes the game when opening Q menu => FUCK THIS SHIT + if ( t.name == "Legacy Spawnlists" ) then + added = true + sid = t.id + end + end + + if ( !added ) then + spawnmenu.AddPropCategory( "rb655_legacy_spawnlists", "Legacy Spawnlists", {}, "icon16/folder.png", sid, 0 ) + added = true + end]] + + content = util.KeyValuesToTable( content ) + + if ( !content.entries or content.contents ) then continue end + + local contents = {} + + for eid, entry in pairs( content.entries ) do + if ( type( entry ) == "table" ) then entry = entry.model end + table.insert( contents, { type = "model", model = entry } ) + end + + if ( !content.information ) then content.information = { name = spawnlist } end + + spawnmenu.AddPropCategory( "settings/spawnlist/" .. spawnlist, content.information.name, contents, "icon16/page.png", sid + id, sid ) + + end + +end ) diff --git a/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/cl_spawnmenu.lua b/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/cl_spawnmenu.lua new file mode 100644 index 0000000..6dc81eb --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/cl_spawnmenu.lua @@ -0,0 +1,790 @@ +-- Extended Spawnmenu - Клиентская часть (звуки и материалы) +local PLUGIN = PLUGIN + +local cl_addTabs = CreateClientConVar("rb655_create_sm_tabs", "0", true, true) + +--[[local function removeOldTabls() + for k, v in pairs( g_SpawnMenu.CreateMenu.Items ) do + if (v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.npcs" ) or + v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.entities" ) or + v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.weapons" ) or + v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.vehicles" ) or + v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.postprocess" ) ) then + g_SpawnMenu.CreateMenu:CloseTab( v.Tab, true ) + end + end +end + +hook.Add( "PopulateContent", "rb655_extended_spawnmenu", function( pnlContent, tree, node ) + removeOldTabls() removeOldTabls() removeOldTabls() -- For some reason it doesn't work with only one call +end )]] + +local function getGameList() + local games = engine.GetGames() + table.insert( games, { + title = "All", + folder = "GAME", + icon = "all", + mounted = true + } ) + table.insert( games, { + title = "Garry's Mod", + folder = "garrysmod", + mounted = true + } ) + return games +end + +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] + +local theSound = nil + +function rb655_playsound( snd ) + + if ( theSound ) then theSound:Stop() end + + theSound = CreateSound( LocalPlayer(), snd ) + theSound:Play() + +end + +net.Receive( "rb655_playsound", function( len ) + rb655_playsound( net.ReadString() ) +end ) + +spawnmenu.AddContentType( "sound", function( container, obj ) + if ( !obj.nicename ) then return end + if ( !obj.spawnname ) then return end + + local icon = vgui.Create( "ContentIcon", container ) + icon:SetContentType( "sound" ) + icon:SetSpawnName( obj.spawnname ) + icon:SetName( obj.nicename ) + icon:SetMaterial( "icon16/sound.png" ) + + icon.DoClick = function() + rb655_playsound( obj.spawnname ) + end + + icon.OpenMenu = function( icn ) + local menu = DermaMenu() + menu:AddOption( "#spawnmenu.menu.copy", function() SetClipboardText( obj.spawnname ) end ):SetIcon( "icon16/page_copy.png" ) + menu:AddOption( "Play on all clients", function() RunConsoleCommand( "rb655_playsound_all", obj.spawnname ) end ):SetIcon( "icon16/sound.png" ) + menu:AddOption( "Stop all sounds", function() RunConsoleCommand( "stopsound" ) end ):SetIcon( "icon16/sound_mute.png" ) + menu:AddSpacer() + menu:AddOption( "#spawnmenu.menu.delete", function() icn:Remove() hook.Run( "SpawnlistContentChanged", icn ) end ):SetIcon( "icon16/bin_closed.png" ) + menu:Open() + end + + if ( IsValid( container ) ) then + container:Add( icon ) + end + + return icon + +end ) + +local function OnSndNodeSelected( self, node, name, path, pathid, icon, ViewPanel, pnlContent ) + + ViewPanel:Clear( true ) + + local Path = node:GetFolder() + + local files = file.Find( Path .. "/*.wav", node:GetPathID() ) + files = table.Add( files, file.Find( Path .. "/*.mp3", node:GetPathID() ) ) + files = table.Add( files, file.Find( Path .. "/*.ogg", node:GetPathID() ) ) + + local offset = 0 + local limit = 512 + if ( node.offset ) then offset = node.offset or 0 end + + for k, v in pairs( files ) do + if ( k > limit + offset ) then + if ( !node.Done ) then + offset = offset + limit + local mats = ( self.Parent or node ):AddNode( ( self.Text or node:GetText() ) .. " (" .. offset .. " - " .. offset + limit .. ")" ) + mats:SetFolder( node:GetFolder() ) + mats.Text = self.Text or node:GetText() + mats.Parent = self.Parent or node + mats:SetPathID( node:GetPathID() ) + mats:SetIcon( node:GetIcon() ) + mats.offset = offset + mats.OnNodeSelected = function( mats_self, mats_node ) + OnSndNodeSelected( mats_self, mats_node, mats_self.Text, mats_node:GetFolder(), mats_node:GetPathID(), mats_node:GetIcon(), ViewPanel, pnlContent ) + end + end + node.Done = true + break end + if ( k <= offset ) then continue end + + local p = Path .. "/" + if ( string.StartWith( path, "addons/" ) or string.StartWith( path, "download/" ) ) then + p = string.sub( p, string.find( p, "/sound/" ) + 1 ) + end + + p = string.sub( p .. v, 7 ) + + spawnmenu.CreateContentIcon( "sound", ViewPanel, { spawnname = p, nicename = string.Trim( v ) } ) + + end + + pnlContent:SwitchPanel( ViewPanel ) + +end + +local function AddBrowseContentSnd( node, name, icon, path, pathid ) + + local ViewPanel = node.ViewPanel + local pnlContent = node.pnlContent + + if ( !string.EndsWith( path, "/" ) && string.len( path ) > 1 ) then path = path .. "/" end + + local fi, fo = file.Find( path .. "sound", pathid ) + if ( !fo && !fi ) then return end + + local sounds = node:AddFolder( name, path .. "sound", pathid, false, false, "*.*" ) + sounds:SetIcon( icon ) + + sounds.OnNodeSelected = function( self, node_sel ) + OnSndNodeSelected( self, node_sel, name, path, pathid, icon, ViewPanel, pnlContent ) + end + +end + +language.Add( "spawnmenu.category.browsesounds", "Browse Sounds" ) + +local function RefreshAddonSounds( browseAddonSounds ) + for _, addon in SortedPairsByMemberValue( engine.GetAddons(), "title" ) do + + if ( !addon.downloaded ) then continue end + if ( !addon.mounted ) then continue end + if ( !table.HasValue( select( 2, file.Find( "*", addon.title ) ), "sound" ) ) then continue end + + AddBrowseContentSnd( browseAddonSounds, addon.title, "icon16/bricks.png", "", addon.title ) + end +end +local function RefreshGameSounds( browseGameSounds ) + local games = getGameList() + + for _, game in SortedPairsByMemberValue( games, "title" ) do + if ( !game.mounted ) then continue end + AddBrowseContentSnd( browseGameSounds, game.title, "games/16/" .. ( game.icon or game.folder ) .. ".png", "", game.folder ) + end +end + +local browseGameSounds +local browseAddonSounds +hook.Add( "PopulateContent", "SpawnmenuLoadSomeSounds", function( pnlContent, tree, browseNode ) timer.Simple( 0.5, function() + + if ( !IsValid( tree ) or !IsValid( pnlContent ) ) then + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR SOUNDS !!!" ) + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR SOUNDS !!!" ) + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR SOUNDS !!!" ) + return + end + + local ViewPanel = vgui.Create( "ContentContainer", pnlContent ) + ViewPanel:SetVisible( false ) + + local browseSounds = tree:AddNode( "#spawnmenu.category.browsesounds", "icon16/sound.png" ) + browseSounds.ViewPanel = ViewPanel + browseSounds.pnlContent = pnlContent + + --[[ --------------------------------------------------------------------------------------- ]] + + browseAddonSounds = browseSounds:AddNode( "#spawnmenu.category.addons", "icon16/folder_database.png" ) + browseAddonSounds.ViewPanel = ViewPanel + browseAddonSounds.pnlContent = pnlContent + + RefreshAddonSounds( browseAddonSounds ) + + --[[ --------------------------------------------------------------------------------------- ]] + + local addon_sounds = {} + local _, snd_folders = file.Find( "addons/*", "MOD" ) + for _, addon in SortedPairs( snd_folders ) do + + if ( !file.IsDir( "addons/" .. addon .. "/sound/", "MOD" ) ) then continue end + + table.insert( addon_sounds, addon ) + + end + + local browseLegacySounds = browseSounds:AddNode( "#spawnmenu.category.addonslegacy", "icon16/folder_database.png" ) + browseLegacySounds.ViewPanel = ViewPanel + browseLegacySounds.pnlContent = pnlContent + + for _, addon in SortedPairsByValue( addon_sounds ) do + + AddBrowseContentSnd( browseLegacySounds, addon, "icon16/bricks.png", "addons/" .. addon .. "/", "MOD" ) + + end + + --[[ --------------------------------------------------------------------------------------- ]] + + AddBrowseContentSnd( browseSounds, "#spawnmenu.category.downloads", "icon16/folder_database.png", "download/", "MOD" ) + + --[[ --------------------------------------------------------------------------------------- ]] + + browseGameSounds = browseSounds:AddNode( "#spawnmenu.category.games", "icon16/folder_database.png" ) + browseGameSounds.ViewPanel = ViewPanel + browseGameSounds.pnlContent = pnlContent + + RefreshGameSounds( browseGameSounds ) + +end ) end ) + +hook.Add( "GameContentChanged", "ES_RefreshSpawnmenuSounds", function() + + if ( IsValid( browseAddonSounds ) ) then + + -- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones? + browseAddonSounds:Clear() + browseAddonSounds.ViewPanel:Clear( true ) + + RefreshAddonSounds( browseAddonSounds ) + + end + + if ( IsValid( browseGameSounds ) ) then + + -- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones? + browseGameSounds:Clear() + browseGameSounds.ViewPanel:Clear( true ) + + RefreshGameSounds( browseGameSounds ) + + end + +end ) + +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] + +local function IsMaterialUsableOnEntities( matPath ) + -- A png file? No thanks + if ( string.GetExtensionFromFilename( matPath ) ) then return false end + + local mat = Material( matPath ) + if ( !string.find( mat:GetShader(), "LightmappedGeneric" ) + && !string.find( mat:GetShader(), "WorldVertexTransition" ) + && !string.find( mat:GetShader(), "Spritecard" ) + && !string.find( mat:GetShader(), "Water" ) + && !string.find( mat:GetShader(), "Cable" ) + --&& !string.find( mat:GetShader(), "UnlitGeneric" ) + && !string.find( mat:GetShader(), "Refract" ) ) then + return true + end + + return false +end + +local DisplayedWarning = false +local function DisplayOneTimeWarning() + if ( DisplayedWarning ) then return end + DisplayedWarning = true + + Derma_Message( "Please note that not all materials are usable on entities, such as map textures, etc.\nYou can still try though!", "Warning", "OK" ) +end + +spawnmenu.AddContentType( "material", function( container, obj ) + if ( !obj.nicename ) then return end + if ( !obj.spawnname ) then return end + + local icon = vgui.Create( "ContentIcon", container ) + icon:SetContentType( "material" ) + icon:SetSpawnName( obj.spawnname ) + icon:SetName( obj.nicename ) + if ( string.GetExtensionFromFilename( obj.spawnname ) == "png" ) then + icon:SetMaterial( obj.spawnname ) + else + icon.Image:SetImage( obj.spawnname ) + end + + icon.DoClick = function() + if ( !IsMaterialUsableOnEntities( obj.spawnname ) ) then DisplayOneTimeWarning() end + + RunConsoleCommand( "material_override", obj.spawnname ) + spawnmenu.ActivateTool( "material" ) + surface.PlaySound( "garrysmod/ui_click.wav" ) + end + + icon.OpenMenu = function( icn ) + local menu = DermaMenu() + menu:AddOption( "#spawnmenu.menu.copy", function() SetClipboardText( obj.spawnname ) end ):SetIcon( "icon16/page_copy.png" ) + + local str = "Use with Material Tool" + if ( !IsMaterialUsableOnEntities( obj.spawnname ) ) then + str = "Try to use with Material Tool (Probably won't work)" + end + menu:AddOption( str, function() + RunConsoleCommand( "material_override", obj.spawnname ) + spawnmenu.ActivateTool( "material" ) + end ):SetIcon( "icon16/pencil.png" ) + + menu:AddSpacer() + menu:AddOption( "#spawnmenu.menu.delete", function() icn:Remove() hook.Run( "SpawnlistContentChanged", icn ) end ):SetIcon( "icon16/bin_closed.png" ) + menu:Open() + end + + if ( IsValid( container ) ) then + container:Add( icon ) + end + + return icon + +end ) + +local function OnMatNodeSelected( self, node, name, path, pathid, icon, ViewPanel, pnlContent ) + + ViewPanel:Clear( true ) + + local Path = node:GetFolder() + + local mat_files = file.Find( Path .. "/*.vmt", node:GetPathID() ) + mat_files = table.Add( mat_files, file.Find( Path .. "/*.png", node:GetPathID() ) ) + + local offset = 0 + local limit = 512 + if ( node.offset ) then offset = node.offset or 0 end + + for k, v in pairs( mat_files ) do + if ( k > limit + offset ) then + if ( !node.Done ) then + offset = offset + limit + local mats = ( self.Parent or node ):AddNode( ( self.Text or node:GetText() ) .. " (" .. offset .. " - " .. offset + limit .. ")" ) + mats:SetFolder( node:GetFolder() ) + mats.Text = self.Text or node:GetText() + mats.Parent = self.Parent or node + mats:SetPathID( node:GetPathID() ) + mats:SetIcon( node:GetIcon() ) + mats.offset = offset + mats.OnNodeSelected = function( self_mats, node_sel ) + OnMatNodeSelected( self_mats, node_sel, self_mats.Text, node_sel:GetFolder(), node_sel:GetPathID(), node_sel:GetIcon(), ViewPanel, pnlContent ) + end + end + node.Done = true + break end + if ( k <= offset ) then continue end + + local p = Path .. "/" + if ( string.StartWith( path, "addons/" ) or string.StartWith( path, "download/" ) ) then + p = string.sub( p, string.find( p, "/materials/" ) + 1 ) + end + + p = string.sub( p .. v, 11 ) + + if ( string.GetExtensionFromFilename( p ) == "vmt" ) then + p = string.StripExtension( p ) + v = string.StripExtension( v ) + end + + if ( Material( p ):GetShader() == "Spritecard" ) then continue end + + spawnmenu.CreateContentIcon( "material", ViewPanel, { spawnname = p, nicename = v } ) + end + + pnlContent:SwitchPanel( ViewPanel ) + +end + +local function AddBrowseContentMaterial( node, name, icon, path, pathid ) + + local ViewPanel = node.ViewPanel + local pnlContent = node.pnlContent + + if ( !string.EndsWith( path, "/" ) && string.len( path ) > 1 ) then path = path .. "/" end + + local fi, fo = file.Find( path .. "materials", pathid ) + if ( !fi && !fo ) then return end + + local materials = node:AddFolder( name, path .. "materials", pathid, false, false, "*.*" ) + materials:SetIcon( icon ) + + materials.OnNodeSelected = function( self, node_sel ) + OnMatNodeSelected( self, node_sel, name, path, pathid, icon, ViewPanel, pnlContent ) + end + +end + +language.Add( "spawnmenu.category.browsematerials", "Browse Materials" ) + +local function RefreshAddonMaterials( node ) + for _, addon in SortedPairsByMemberValue( engine.GetAddons(), "title" ) do + + if ( !addon.downloaded ) then continue end + if ( !addon.mounted ) then continue end + if ( !table.HasValue( select( 2, file.Find( "*", addon.title ) ), "materials" ) ) then continue end + + AddBrowseContentMaterial( node, addon.title, "icon16/bricks.png", "", addon.title ) + + end +end +local function RefreshGameMaterials( node ) + local games = getGameList() + + for _, game in SortedPairsByMemberValue( games, "title" ) do + if ( !game.mounted ) then continue end + AddBrowseContentMaterial( node, game.title, "games/16/" .. ( game.icon or game.folder ) .. ".png", "", game.folder ) + end +end + +local browseAddonMaterials +local browseGameMaterials +hook.Add( "PopulateContent", "SpawnmenuLoadSomeMaterials", function( pnlContent, tree, browseNode ) timer.Simple( 0.5, function() + + if ( !IsValid( tree ) or !IsValid( pnlContent ) ) then + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR MATERIALS!!!" ) + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR MATERIALS!!!" ) + print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR MATERIALS!!!" ) + return + end + + local ViewPanel = vgui.Create( "ContentContainer", pnlContent ) + ViewPanel:SetVisible( false ) + + local browseMaterials = tree:AddNode( "#spawnmenu.category.browsematerials", "icon16/picture_empty.png" ) + browseMaterials.ViewPanel = ViewPanel + browseMaterials.pnlContent = pnlContent + + --[[ --------------------------------------------------------------------------------------- ]] + + browseAddonMaterials = browseMaterials:AddNode( "#spawnmenu.category.addons", "icon16/folder_database.png" ) + browseAddonMaterials.ViewPanel = ViewPanel + browseAddonMaterials.pnlContent = pnlContent + + RefreshAddonMaterials( browseAddonMaterials ) + + --[[ --------------------------------------------------------------------------------------- ]] + + local addon_mats = {} + local _, mat_folders = file.Find( "addons/*", "MOD" ) + for _, addon in SortedPairs( mat_folders ) do + + if ( !file.IsDir( "addons/" .. addon .. "/materials/", "MOD" ) ) then continue end + + table.insert( addon_mats, addon ) + + end + + local browseLegacyMaterials = browseMaterials:AddNode( "#spawnmenu.category.addonslegacy", "icon16/folder_database.png" ) + browseLegacyMaterials.ViewPanel = ViewPanel + browseLegacyMaterials.pnlContent = pnlContent + + for _, addon in SortedPairsByValue( addon_mats ) do + + AddBrowseContentMaterial( browseLegacyMaterials, addon, "icon16/bricks.png", "addons/" .. addon .. "/", "MOD" ) + + end + + --[[ --------------------------------------------------------------------------------------- ]] + + AddBrowseContentMaterial( browseMaterials, "#spawnmenu.category.downloads", "icon16/folder_database.png", "download/", "MOD" ) + + --[[ --------------------------------------------------------------------------------------- ]] + + browseGameMaterials = browseMaterials:AddNode( "#spawnmenu.category.games", "icon16/folder_database.png" ) + browseGameMaterials.ViewPanel = ViewPanel + browseGameMaterials.pnlContent = pnlContent + + RefreshGameMaterials( browseGameMaterials ) + +end ) end ) + +hook.Add( "GameContentChanged", "ES_RefreshSpawnmenuMaterials", function() + + if ( IsValid( browseAddonMaterials ) ) then + + -- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones? + browseAddonMaterials:Clear() + browseAddonMaterials.ViewPanel:Clear( true ) + + RefreshAddonMaterials( browseAddonMaterials ) + + end + + if ( IsValid( browseGameMaterials ) ) then + + -- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones? + browseGameMaterials:Clear() + browseGameMaterials.ViewPanel:Clear( true ) + + RefreshGameMaterials( browseGameMaterials ) + + end + +end ) + +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] +--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]] + +hook.Add( "PopulateContent", "rb655_extended_spawnmenu_entities", function( pnlContent, tree, node ) + if ( !cl_addTabs:GetBool() ) then return end + + local node_w = tree:AddNode( "#spawnmenu.category.entities", "icon16/bricks.png" ) + + node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + node_w.PropPanel:SetVisible( false ) + + function node_w:DoClick() + pnlContent:SwitchPanel( self.PropPanel ) + end + + local Categorised = {} + + local SpawnableEntities = list.Get( "SpawnableEntities" ) + if ( SpawnableEntities ) then + for k, v in pairs( SpawnableEntities ) do + v.Category = v.Category or "Other" + Categorised[ v.Category ] = Categorised[ v.Category ] or {} + table.insert( Categorised[ v.Category ], v ) + end + end + + for CategoryName, v in SortedPairs( Categorised ) do + + local node_new = node_w:AddNode( CategoryName, "icon16/bricks.png" ) + + local CatPropPanel = vgui.Create( "ContentContainer", pnlContent ) + CatPropPanel:SetVisible( false ) + + local Header = vgui.Create("ContentHeader", node_w.PropPanel ) + Header:SetText( CategoryName ) + node_w.PropPanel:Add( Header ) + + for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do + local t = { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = "entities/" .. ent.ClassName .. ".png", + admin = ent.AdminOnly + } + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", CatPropPanel, t ) + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", node_w.PropPanel, t ) + end + + function node_new:DoClick() + pnlContent:SwitchPanel( CatPropPanel ) + end + + end +end ) + +hook.Add( "PopulateContent", "rb655_extended_spawnmenu_post_processing", function( pnlContent, tree, node ) + if ( !cl_addTabs:GetBool() ) then return end + + local node_w = tree:AddNode( "#spawnmenu.category.postprocess", "icon16/picture.png" ) + + node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + node_w.PropPanel:SetVisible( false ) + + function node_w:DoClick() + pnlContent:SwitchPanel( self.PropPanel ) + end + + -- Get the table + + local Categorised = {} + local PostProcess = list.Get( "PostProcess" ) + + if ( PostProcess ) then + for k, v in pairs( PostProcess ) do + v.category = v.category or "Other" + v.name = k + Categorised[ v.category ] = Categorised[ v.category ] or {} + table.insert( Categorised[ v.category ], v ) + end + end + + -- Put table into panels + for CategoryName, v in SortedPairs( Categorised ) do + + local node_new = node_w:AddNode( CategoryName, "icon16/picture.png" ) + + local CatPropPanel = vgui.Create( "ContentContainer", pnlContent ) + CatPropPanel:SetVisible( false ) + + local Header = vgui.Create( "ContentHeader", node_w.PropPanel ) + Header:SetText( CategoryName ) + node_w.PropPanel:Add( Header ) + + for k, pp in SortedPairsByMemberValue( v, "PrintName" ) do + if ( pp.func ) then pp.func( CatPropPanel ) pp.func( node_w.PropPanel ) continue end + + local t = { + name = pp.name, + icon = pp.icon + } + + spawnmenu.CreateContentIcon( "postprocess", CatPropPanel, t ) + spawnmenu.CreateContentIcon( "postprocess", node_w.PropPanel, t ) + end + + function node_new:DoClick() + pnlContent:SwitchPanel( CatPropPanel ) + end + end + +end ) + +hook.Add( "PopulateContent", "rb655_extended_spawnmenu_npcs", function( pnlContent, tree, node ) + if ( !cl_addTabs:GetBool() ) then return end + + local node_w = tree:AddNode( "#spawnmenu.category.npcs", "icon16/monkey.png" ) + + node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + node_w.PropPanel:SetVisible( false ) + + function node_w:DoClick() + pnlContent:SwitchPanel( self.PropPanel ) + end + + local NPCList = list.Get( "NPC" ) + local Categories = {} + + for k, v in pairs( NPCList ) do + local Category = v.Category or "Other" + local Tab = Categories[ Category ] or {} + + Tab[ k ] = v + + Categories[ Category ] = Tab + end + + for CategoryName, v in SortedPairs( Categories ) do + + local node_new = node_w:AddNode( CategoryName, "icon16/monkey.png" ) + + local CatPropPanel = vgui.Create( "ContentContainer", pnlContent ) + CatPropPanel:SetVisible( false ) + + local Header = vgui.Create("ContentHeader", node_w.PropPanel ) + Header:SetText( CategoryName ) + node_w.PropPanel:Add( Header ) + + for name, ent in SortedPairsByMemberValue( v, "Name" ) do + local t = { + nicename = ent.Name or name, + spawnname = name, + material = "entities/" .. name .. ".png", + weapon = ent.Weapons, + admin = ent.AdminOnly + } + spawnmenu.CreateContentIcon( "npc", CatPropPanel, t ) + spawnmenu.CreateContentIcon( "npc", node_w.PropPanel, t ) + end + + function node_new:DoClick() + pnlContent:SwitchPanel( CatPropPanel ) + end + + end +end ) + +hook.Add( "PopulateContent", "rb655_extended_spawnmenu_vehicles", function( pnlContent, tree, node ) + if ( !cl_addTabs:GetBool() ) then return end + + local node_w = tree:AddNode( "#spawnmenu.category.vehicles", "icon16/car.png" ) + + node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + node_w.PropPanel:SetVisible( false ) + + function node_w:DoClick() + pnlContent:SwitchPanel( self.PropPanel ) + end + + local Categorised = {} + local Vehicles = list.Get( "Vehicles" ) + if ( Vehicles ) then + for k, v in pairs( Vehicles ) do + v.Category = v.Category or "Other" + Categorised[ v.Category ] = Categorised[ v.Category ] or {} + v.ClassName = k + v.PrintName = v.Name + v.ScriptedEntityType = "vehicle" + table.insert( Categorised[ v.Category ], v ) + end + end + + for CategoryName, v in SortedPairs( Categorised ) do + + local node_new = node_w:AddNode( CategoryName, "icon16/car.png" ) + + local CatPropPanel = vgui.Create( "ContentContainer", pnlContent ) + CatPropPanel:SetVisible( false ) + + local Header = vgui.Create("ContentHeader", node_w.PropPanel ) + Header:SetText( CategoryName ) + node_w.PropPanel:Add( Header ) + + for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do + local t = { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = "entities/" .. ent.ClassName .. ".png", + admin = ent.AdminOnly + } + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", node_w.PropPanel, t ) + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", CatPropPanel, t ) + end + + function node_new:DoClick() + pnlContent:SwitchPanel( CatPropPanel ) + end + + end + +end ) + +hook.Add( "PopulateContent", "rb655_extended_spawnmenu_weapons", function( pnlContent, tree, node ) + if ( !cl_addTabs:GetBool() ) then return end + + local node_w = tree:AddNode( "#spawnmenu.category.weapons", "icon16/gun.png" ) + + node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + node_w.PropPanel:SetVisible( false ) + + function node_w:DoClick() + pnlContent:SwitchPanel( self.PropPanel ) + end + + local Weapons = list.Get( "Weapon" ) + local Categorised = {} + + for k, weapon in pairs( Weapons ) do + if ( !weapon.Spawnable && !weapon.AdminSpawnable ) then continue end + + Categorised[ weapon.Category ] = Categorised[ weapon.Category ] or {} + table.insert( Categorised[ weapon.Category ], weapon ) + end + + for CategoryName, v in SortedPairs( Categorised ) do + local node_new = node_w:AddNode( CategoryName, "icon16/gun.png" ) + + local CatPropPanel = vgui.Create( "ContentContainer", pnlContent ) + CatPropPanel:SetVisible( false ) + + local Header = vgui.Create("ContentHeader", node_w.PropPanel ) + Header:SetText( CategoryName ) + node_w.PropPanel:Add( Header ) + + for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do + local t = { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = "entities/" .. ent.ClassName .. ".png", + admin = ent.AdminOnly + } + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", CatPropPanel, t ) + spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", node_w.PropPanel, t ) + end + + function node_new:DoClick() + pnlContent:SwitchPanel( CatPropPanel ) + end + + end + +end ) diff --git a/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/sh_plugin.lua new file mode 100644 index 0000000..7dc96ee --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/extendedspawnmenu/sh_plugin.lua @@ -0,0 +1,39 @@ +PLUGIN.name = "Extended Spawnmenu" +PLUGIN.author = "RB655 (Портирован для Helix)" +PLUGIN.description = "Расширенное спавн-меню с поддержкой звуков и legacy аддонов" + +ix.util.Include("cl_spawnmenu.lua") +ix.util.Include("cl_legacy.lua") +-- Конфигурация +ix.config.Add("extSpawnmenuEnabled", true, "Включить расширенное спавн-меню", nil, { + category = "Extended Spawnmenu" +}) + +ix.config.Add("extSpawnmenuCreateTabs", false, "Создавать отдельные вкладки для категорий", nil, { + category = "Extended Spawnmenu" +}) + +-- Серверная часть +if SERVER then + util.AddNetworkString("rb655_playsound") + + -- Команда для проигрывания звуков всем игрокам + concommand.Add("rb655_playsound_all", function(ply, cmd, args) + if not ply:IsSuperAdmin() or not args[1] or string.Trim(args[1]) == "" then return end + + net.Start("rb655_playsound") + net.WriteString(args[1] or "") + net.Broadcast() + end) +end + +-- Клиентская часть +if CLIENT then + -- ConVar для совместимости + CreateClientConVar("rb655_create_sm_tabs", "0", true, true) +end + +function PLUGIN:Initialize() + print("[Extended Spawnmenu] Расширенное спавн-меню загружено!") + print("[Extended Spawnmenu] Добавлены: Browse Sounds, Materials, Legacy Addons") +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/f4menu/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/f4menu/cl_plugin.lua new file mode 100644 index 0000000..235dc86 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/f4menu/cl_plugin.lua @@ -0,0 +1,2709 @@ +local PLUGIN = PLUGIN + +-- Материалы +PLUGIN.backgroundMaterial = Material("materials/ft_ui/military/vnu/charcreate/bg.png") +PLUGIN.logoMaterial = Material("materials/ft_ui/military/vnu/charcreate/logo.png") + +if CLIENT then + if not ix.currency._originalGet then + ix.currency._originalGet = ix.currency.Get + end + + function ix.currency.Get(amount, noFormat) + amount = tonumber(amount) or 0 + + local formatted = tostring(math.floor(amount)):reverse():gsub("(%d%d%d)", "%1 "):reverse():gsub("^ ", "") + + local client = LocalPlayer() + if not IsValid(client) then + return formatted + end + + local char = client:GetCharacter() + if not char then + return formatted + end + + local faction = char:GetFaction() + + if faction == FACTION_UKRAINE then + return formatted .. "$" + end + + if faction == FACTION_RUSSIAN then + return formatted .. "₽" + end + + return formatted + end +end + +-- Масштабирование +local function GetScale() + local scale = ScrH() / 1080 + return math.Clamp(scale, 0.5, 2.0) +end +local function ScaleSize(val) + return math.max(1, math.Round(val * GetScale())) +end +local function ScalePos(val) + return math.Round(val * GetScale()) +end + +local function CreateF4MenuFonts() + surface.CreateFont("F4Menu_Title", { + font = "exo2", + size = math.max(ScaleSize(25), 14), + weight = 400 + }) + surface.CreateFont("F4Menu_Username", { + font = "exo2", + size = math.max(ScaleSize(18), 12), + weight = 600 + }) + surface.CreateFont("F4Menu_Balance", { + font = "exo2", + size = math.max(ScaleSize(18), 12), + weight = 600 + }) + surface.CreateFont("F4Menu_Category", { + font = "exo2", + size = math.max(ScaleSize(20), 13), + weight = 500 + }) + surface.CreateFont("F4Menu_Item", { + font = "exo2", + size = math.max(ScaleSize(16), 11), + weight = 400 + }) + surface.CreateFont("F4Menu_InfoHeader", { + font = "exo2", + size = math.max(ScaleSize(28), 16), + weight = 700 + }) + surface.CreateFont("F4Menu_InfoValue", { + font = "exo2", + size = math.max(ScaleSize(22), 14), + weight = 500 + }) + surface.CreateFont("F4Menu_InfoSmall", { + font = "exo2", + size = math.max(ScaleSize(15), 10), + weight = 400 + }) + surface.CreateFont("F4Menu_RulesTitle", { + font = "exo2", + size = math.max(ScaleSize(22), 13), + weight = 400 + }) + surface.CreateFont("F4Menu_RulesText", { + font = "exo2", + size = math.max(ScaleSize(20), 12), + weight = 400 + }) + surface.CreateFont("F4Menu_DonateHero", { + font = "exo2", + size = math.max(ScaleSize(30), 18), + weight = 700 + }) + surface.CreateFont("F4Menu_DonateSub", { + font = "exo2", + size = math.max(ScaleSize(18), 12), + weight = 400 + }) + surface.CreateFont("F4Menu_DonatePrice", { + font = "exo2", + size = math.max(ScaleSize(28), 16), + weight = 700 + }) + surface.CreateFont("F4Menu_DonateBalance", { + font = "exo2", + size = math.max(ScaleSize(26), 15), + weight = 600 + }) + surface.CreateFont("F4Menu_DonateTag", { + font = "exo2", + size = math.max(ScaleSize(16), 10), + weight = 600 + }) + surface.CreateFont("F4Menu_DonateTitle", { + font = "exo2", + size = math.max(ScaleSize(25), 14), + weight = 500 + }) + surface.CreateFont("F4Menu_DonateBold", { + font = "exo2", + size = math.max(ScaleSize(25), 14), + weight = 700 + }) + surface.CreateFont("F4Menu_ItemSmall", { + font = "exo2", + size = math.max(ScaleSize(19), 11), + weight = 300 + }) +end +CreateF4MenuFonts() +hook.Add("OnScreenSizeChanged", "F4Menu_UpdateFonts", CreateF4MenuFonts) + +PLUGIN.infoData = PLUGIN.infoData or {} +PLUGIN.infoRequestAt = 0 + +local function FormatNumber(value) + local number = tonumber(value) + if not number then return "—" end + + local negative = number < 0 + number = math.floor(math.abs(number)) + local text = tostring(number) + + while true do + local formatted, k = string.gsub(text, "^(%d+)(%d%d%d)", "%1 %2") + text = formatted + if k == 0 then break end + end + + if negative then + text = "-" .. text + end + + return text +end + +local function FormatList(list, limit) + if not istable(list) or #list == 0 then + return "—" + end + + limit = limit or 4 + local buffer = {} + for i = 1, math.min(#list, limit) do + buffer[#buffer + 1] = tostring(list[i]) + end + + if #list > limit then + buffer[#buffer + 1] = string.format("…%d", #list - limit) + end + + return table.concat(buffer, ", ") +end + +local DONATE_NOTICE_COLOR = Color(233, 184, 73) + +local function FormatDonatePrice(value, currency) + if not value then + return currency or "—" + end + + return string.format("%s %s", FormatNumber(value), currency or "₽") +end + +local function NotifyDonate(message) + local ply = LocalPlayer() + + if IsValid(ply) and ply.Notify then + ply:Notify(message) + else + chat.AddText(DONATE_NOTICE_COLOR, "[Донат] ", color_white, message) + end +end + +function PLUGIN:GetDonateBalance() + local client = LocalPlayer() + if not IsValid(client) then return 0 end + + local balance = 0 + + if isfunction(client.IGSFunds) then + local ok, value = pcall(client.IGSFunds, client) + if ok and isnumber(value) then + balance = value + end + elseif isfunction(client.GetIGSFunds) then + local ok, value = pcall(client.GetIGSFunds, client) + if ok and isnumber(value) then + balance = value + end + elseif client.GetNetVar then + balance = client:GetNetVar("igsFunds", 0) or 0 + end + + balance = math.floor(tonumber(balance) or 0) + if balance < 0 then balance = 0 end + + return balance +end + +function PLUGIN:SendDonatePurchase(entry, price, mode) + if not entry then return false end + + if entry.externalOnly and entry.url and gui and gui.OpenURL then + gui.OpenURL(entry.url) + return true + end + + if entry.netString then + net.Start(entry.netString) + net.WriteString(entry.id or "") + net.WriteUInt(price or 0, 32) + net.WriteString(mode or "month") + net.SendToServer() + return true + end + + if entry.command and IsValid(LocalPlayer()) then + LocalPlayer():ConCommand(entry.command) + return true + end + + if not entry.id or entry.id == "" then + return false + end + + net.Start("ix.F4_DonatePurchase") + net.WriteString(entry.id) + net.WriteUInt(price or entry.price or 0, 32) + net.WriteString(mode or "month") + net.SendToServer() + + return true +end + +function PLUGIN:ConfirmDonatePurchase(entry, selectedPrice, selectedMode) + if not entry then return end + + local price = selectedPrice or entry.priceMonth or entry.price or 0 + local mode = selectedMode or "month" + local priceText = FormatDonatePrice(price, entry.currency or "RUB") + + local modeText = "" + if entry.priceMonth or entry.priceForever then + modeText = mode == "forever" and " (навсегда)" or " (на месяц)" + end + + Derma_Query( + string.format("Приобрести «%s»%s за %s?", entry.title or "позицию", modeText, priceText or "—"), + "Подтверждение покупки", + "Купить", + function() + if not self:SendDonatePurchase(entry, price, mode) then + NotifyDonate("Серверная обработка покупки недоступна. Используйте сайт проекта.") + end + end, + "Отмена" + ) +end + +local function ResolveColor(data, alpha) + if istable(data) then + local r = data.r or data[1] or 56 + local g = data.g or data[2] or 84 + local b = data.b or data[3] or 45 + return Color(r, g, b, alpha or 255) + end + + return Color(56, 84, 45, alpha or 255) +end + +local function ShortModel(path) + if not isstring(path) or path == "" then + return "—" + end + + local parts = string.Split(path, "/") + return parts[#parts] or path +end + +function PLUGIN:RequestInfoData(force) + local now = CurTime() + if not force and self.infoRequestAt and (now - self.infoRequestAt) < 3 then return end + + self.infoRequestAt = now + net.Start("ix.F4_RequestInfo") + net.SendToServer() +end + +net.Receive("ix.F4_SendInfo", function() + local plugin = ix.plugin.Get("f4menu") + if not plugin then return end + + plugin.infoData = net.ReadTable() or {} + plugin.infoData.receivedAt = CurTime() + + if plugin.activeTab == "Информация" and IsValid(plugin.contentPanel) then + plugin:CreateInfoTab(true) + end +end) + +net.Receive("ix.F4_DonatePurchaseResult", function() + local plugin = ix.plugin.Get("f4menu") + if not plugin then return end + + local success = net.ReadBool() + local message = net.ReadString() + local productID = net.ReadString() + + if message and message ~= "" then + NotifyDonate(message) + end + + -- Принудительно обновляем баланс после покупки + timer.Simple(0.5, function() + if success and plugin.activeTab == "Донат" and IsValid(plugin.contentPanel) then + plugin:CreateDonateTab() + end + end) +end) + +-- Переменная для задержки +PLUGIN.lastF4Press = 0 +PLUGIN.f4Cooldown = 1 + +function PLUGIN:CreateF4Menu() + if IsValid(self.f4Menu) then + self.f4Menu:Remove() + end + + local scrW, scrH = ScrW(), ScrH() + CreateF4MenuFonts() + self.f4Menu = vgui.Create("DFrame") + self.f4Menu:SetSize(scrW, scrH) + self.f4Menu:SetPos(0, 0) + self.f4Menu:SetTitle("") + self.f4Menu:SetDraggable(false) + self.f4Menu:ShowCloseButton(false) + self.f4Menu:MakePopup() + + -- Закрытие меню по Esc + self.f4Menu.OnKeyCodePressed = function(s, key) + if key == KEY_ESCAPE then + s:Close() + gui.HideGameUI() + return true + end + end + + -- Отключаем стандартное поведение фрейма + self.f4Menu:SetPaintBackgroundEnabled(false) + self.f4Menu:SetBackgroundBlur(false) + + -- Основной фон + self.f4Menu.Paint = function(s, w, h) + -- Фоновое изображение + if self.backgroundMaterial then + surface.SetDrawColor(color_white) + surface.SetMaterial(self.backgroundMaterial) + surface.DrawTexturedRect(0, 0, w, h) + end + + -- Темный оверлей с размытием + surface.SetDrawColor(Color(1, 48, 21, 140)) + surface.DrawRect(0, 74, w, h - 74) + + surface.SetDrawColor(Color(10, 10, 10, 140)) + surface.DrawRect(0, 74, w, h - 74) + + -- Верхняя панель + surface.SetDrawColor(Color(13, 13, 13)) + surface.DrawRect(0, 0, w, 74) + + -- Декоративные линии на верхней панели + local linePositions = { + {555, 7}, {718, 7}, {852.5, 7}, {950, 7}, + {1049, 7}, {1184, 7}, {1317, 7} + } + + surface.SetDrawColor(Color(21, 21, 21)) + for _, pos in ipairs(linePositions) do + surface.DrawRect(pos[1], pos[2], 3, 52) + end + end + + -- Логотип + local logo = vgui.Create("DImage", self.f4Menu) + logo:SetSize(ScaleSize(124), ScaleSize(44)) + logo:SetPos(ScalePos(37), ScalePos(15)) + if self.logoMaterial then + logo:SetMaterial(self.logoMaterial) + end + + -- Аватар пользователя + local avatar = vgui.Create("AvatarImage", self.f4Menu) + avatar:SetSize(ScaleSize(50), ScaleSize(50)) + avatar:SetPos(ScalePos(220), ScalePos(14)) + avatar:SetPlayer(LocalPlayer(), 64) + + -- Имя пользователя + local username = vgui.Create("DLabel", self.f4Menu) + username:SetPos(ScalePos(280), ScalePos(17)) + username:SetSize(ScaleSize(200), ScaleSize(22)) + username:SetFont("F4Menu_Username") + username:SetTextColor(Color(56, 84, 45)) + local character = LocalPlayer():GetCharacter() + if character then + username:SetText(character:GetName()) + else + username:SetText("Игрок") + end + + -- Баланс + local balance = vgui.Create("DLabel", self.f4Menu) + balance:SetPos(ScalePos(280), ScalePos(39)) + balance:SetSize(ScaleSize(200), ScaleSize(22)) + balance:SetFont("F4Menu_Balance") + balance:SetTextColor(color_white) + if character then + local money = character:GetMoney() + balance:SetText("Баланс: " .. ix.currency.Get(money)) + else + balance:SetText("Баланс: 0") + end + + -- Меню навигации + local menuItems = { + {"Информация", ScalePos(580), ScaleSize(145), ScaleSize(52), ScalePos(576)}, + {"Правила", ScalePos(727.5), ScaleSize(120), ScaleSize(52), ScalePos(736)}, + {"Донат", ScalePos(842), ScaleSize(80), ScaleSize(52), ScalePos(870)}, + {"Отряды", ScalePos(942), ScaleSize(90), ScaleSize(52), ScalePos(965)}, + {"Настройки", ScalePos(1060), ScaleSize(120), ScaleSize(52), ScalePos(1068)}, + {"Спасибо", ScalePos(1192), ScaleSize(120), ScaleSize(52), ScalePos(1201)} + } + + self.activeTab = self.activeTab or "Информация" + + -- Сначала создаем подсветки для активной вкладки + for _, item in ipairs(menuItems) do + if self.activeTab == item[1] then + local highlight = vgui.Create("DPanel", self.f4Menu) + highlight:SetSize(item[3], item[4]) + highlight:SetPos(item[5] - ScalePos(9), ScalePos(9)) + highlight.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(1, 67, 29)) + draw.RoundedBox(8, 0+1, 0+1, w-2, h-2, Color(21, 21, 21)) + end + break + end + end + + -- Создаем кастомные кнопки вкладок + for _, item in ipairs(menuItems) do + local menuBtn = vgui.Create("DButton", self.f4Menu) + menuBtn:SetPos(item[2], ScalePos(19)) + menuBtn:SetSize(ScaleSize(120), ScaleSize(30)) + menuBtn:SetText("") + menuBtn:SetPaintBackground(false) + menuBtn:NoClipping(true) + menuBtn.Paint = function(s, w, h) + --if self.activeTab == item[1] then + -- surface.SetDrawColor(Color(21, 21, 21)) + -- surface.DrawRect(0, 0, w, h) + -- surface.SetDrawColor(Color(1, 67, 29)) + -- surface.DrawOutlinedRect(0, 0, w, h, 1) + --end + draw.SimpleText(item[1], "F4Menu_Title", w/2, h/2, + self.activeTab == item[1] and color_white or Color(194, 194, 194), + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + menuBtn.DoClick = function(s) + s:MouseCapture(false) + + if item[1] == "Донат" then + if IGS and IGS.UI then + IGS.UI() + else + RunConsoleCommand("igs") + end + return + end + + self.activeTab = item[1] + self:SwitchTab(item[1]) + if IsValid(self.f4Menu) then + self:CreateF4Menu() + end + end + menuBtn.OnMousePressed = function(s, code) + if code == MOUSE_LEFT then + s:DoClick() + return true + end + end + menuBtn.OnCursorEntered = function(s) + s:SetCursor("hand") + end + menuBtn.OnCursorExited = function(s) + s:SetCursor("arrow") + end + end + + -- Социальные иконки + local socialIcons = { + {ScalePos(1700), "discord", "Discord"}, + {ScalePos(1750), "steam", "Steam"}, + {ScalePos(1800), "tiktok", "TikTok"}, + {ScalePos(1850), "telegram", "Telegram"} + } + + -- Загружаем материалы иконок + local iconMaterials = {} + for key, path in pairs(PLUGIN.socialIcons or {}) do + iconMaterials[key] = Material(path, "smooth") + end + + for _, icon in ipairs(socialIcons) do + local socialBtn = vgui.Create("DButton", self.f4Menu) + socialBtn:SetSize(ScaleSize(35), ScaleSize(35)) + socialBtn:SetPos(icon[1], ScalePos(18)) + socialBtn:SetText("") + socialBtn:SetTooltip("Открыть " .. icon[3]) + + local iconMat = iconMaterials[icon[2]] + + socialBtn.Paint = function(s, w, h) + -- Иконка + if iconMat and not iconMat:IsError() then + surface.SetDrawColor(Color(255, 255, 255)) + surface.SetMaterial(iconMat) + surface.DrawTexturedRect(0, 0, w, h) + else + -- Запасной вариант если материал не найден + surface.SetDrawColor(Color(194, 194, 194)) + surface.DrawRect(w/4, h/4, w/2, h/2) + end + + -- Подсветка при наведении + if s:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + socialBtn.DoClick = function(s) + s:MouseCapture(false) + local link = PLUGIN.socialLinks[icon[2]] + if link then + gui.OpenURL(link) + chat.AddText(Color(0, 255, 0), "Открыта ссылка: " .. icon[3]) + else + chat.AddText(Color(255, 100, 100), "Ссылка не настроена для: " .. icon[3]) + end + end + socialBtn.OnMousePressed = function(s, code) + if code == MOUSE_LEFT then + s:DoClick() + return true + end + end + end + + -- Основная контентная область + self.contentPanel = vgui.Create("DPanel", self.f4Menu) + self.contentPanel:SetSize(ScaleSize(1500), ScaleSize(750)) + self.contentPanel:SetPos(ScalePos(210), ScalePos(165)) + self.contentPanel.Paint = function(s, w, h) + draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Инициализируем активную вкладку + self:SwitchTab(self.activeTab) + + -- Обработка клавиши F4 для закрытия + self.f4Menu.OnKeyCodePressed = function(panel, key) + if key == KEY_F4 then + local currentTime = CurTime() + if currentTime - self.lastF4Press >= self.f4Cooldown then + self.lastF4Press = currentTime + panel:Remove() + end + return true + end + end + + -- Блокируем все другие клавиши + self.f4Menu.OnKeyCode = function(panel, key) + return true + end + + -- Блокируем клики по фону + self.f4Menu.OnMousePressed = function(panel, code) + return true + end + + -- Блокируем колесо мыши + self.f4Menu.OnMouseWheeled = function(panel, delta) + return true + end +end + +function PLUGIN:SwitchTab(tabName) + if not IsValid(self.contentPanel) then return end + + -- Очищаем предыдущий контент + self.contentPanel:Clear() + + -- Контент в зависимости от вкладки + if tabName == "Информация" then + self:CreateInfoTab() + elseif tabName == "Правила" then + self:CreateRulesTab() + elseif tabName == "Донат" then + self:CreateDonateTab() + elseif tabName == "Отряды" then + self:CreateSquadsTab() + elseif tabName == "Настройки" then + self:CreateSettingsTab() + elseif tabName == "Спасибо" then + self:CreateThanksTab() + end +end + +function PLUGIN:OpenChangeNameDialog(character) + if not character then return end + + local frame = vgui.Create("DFrame") + frame:SetSize(ScaleSize(500), ScaleSize(200)) + frame:Center() + frame:SetTitle("") + frame:SetDraggable(true) + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(s, w, h) + draw.RoundedBox(18, 0, 0, w, h, Color(13, 13, 13, 240)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("ИЗМЕНИТЬ ИМЯ", "F4Menu_Category", w/2, 20, color_white, TEXT_ALIGN_CENTER) + end + + local textEntry = frame:Add("DTextEntry") + textEntry:SetPos(ScalePos(30), ScalePos(70)) + textEntry:SetSize(ScaleSize(440), ScaleSize(40)) + textEntry:SetFont("F4Menu_Item") + textEntry:SetTextColor(color_white) + textEntry:SetCursorColor(color_white) + textEntry:SetText(character:GetName()) + textEntry:SetPlaceholderText("Введите новое имя...") + textEntry.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 18)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(color_white, color_white, color_white) + end + + local confirmBtn = frame:Add("DButton") + confirmBtn:SetPos(ScalePos(30), ScalePos(130)) + confirmBtn:SetSize(ScaleSize(200), ScaleSize(40)) + confirmBtn:SetText("") + confirmBtn.DoClick = function() + local newName = textEntry:GetValue() + if newName and newName ~= "" and newName ~= character:GetName() then + net.Start("ixChangeName") + net.WriteString(newName) + net.SendToServer() + frame:Close() + end + end + confirmBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) + draw.RoundedBox(8, 0, 0, w, h, col) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ПОДТВЕРДИТЬ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local cancelBtn = frame:Add("DButton") + cancelBtn:SetPos(ScalePos(270), ScalePos(130)) + cancelBtn:SetSize(ScaleSize(200), ScaleSize(40)) + cancelBtn:SetText("") + cancelBtn.DoClick = function() + frame:Close() + end + cancelBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(50, 18, 18) or Color(35, 13, 13) + draw.RoundedBox(8, 0, 0, w, h, col) + surface.SetDrawColor(Color(67, 1, 1)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ОТМЕНА", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end + +function PLUGIN:CreateInfoTab(skipRequest) + if not IsValid(self.contentPanel) then return end + + self.contentPanel:Clear() + + local character = LocalPlayer():GetCharacter() + if not character then + local warn = vgui.Create("DLabel", self.contentPanel) + warn:SetPos(20, 20) + warn:SetSize(self.contentPanel:GetWide() - 40, 40) + warn:SetFont("F4Menu_Category") + warn:SetText("Нет активного персонажа") + warn:SetTextColor(color_white) + warn:SetContentAlignment(5) + return + end + + -- Запрос данных с сервера + if not skipRequest then + local needData = true + if self.infoData.character and self.infoData.receivedAt then + needData = (CurTime() - self.infoData.receivedAt) > 15 + end + + if needData then + self:RequestInfoData() + end + end + + local data = self.infoData or {} + local charData = data.character or {} + local factionData = data.faction or {} + local factionTable = ix.faction.Get(character:GetFaction()) + + -- Получаем данные + local charName = character:GetName() + local money = charData.money or character:GetMoney() + local moneyText = ix.currency.Get(money) + + local factionName = (factionData.name and factionData.name ~= "") and factionData.name or (factionTable and L(factionTable.name) or "Неизвестно") + local rankName = (charData.rank and charData.rank.name) or (LocalPlayer().GetRankName and LocalPlayer():GetRankName()) or "—" + if rankName == false then rankName = "—" end + + local unitData = charData.subdivision or {} + local specData = charData.spec or {} + + local unitName = unitData.name or (LocalPlayer().GetPodrName and LocalPlayer():GetPodrName()) or "—" + local specName = specData.name or (LocalPlayer().GetSpecName and LocalPlayer():GetSpecName()) or "—" + if unitName == false then unitName = "—" end + if specName == false then specName = "—" end + + local techPoints = factionData.techPoints or 0 + local supplyPoints = factionData.supplyPoints or 0 + + -- Левая панель с моделью + local modelPanel = vgui.Create("DPanel", self.contentPanel) + modelPanel:SetPos(ScalePos(20), ScalePos(20)) + modelPanel:SetSize(ScaleSize(400), ScaleSize(710)) + modelPanel.Paint = function(s, w, h) + draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- DModelPanel + local modelView = vgui.Create("DModelPanel", modelPanel) + modelView:SetPos(ScalePos(10), ScalePos(10)) + modelView:SetSize(ScaleSize(380), ScaleSize(480)) + modelView:SetModel(LocalPlayer():GetModel()) + modelView:SetFOV(45) + + local eyepos = modelView.Entity:GetBonePosition(modelView.Entity:LookupBone("ValveBiped.Bip01_Head1")) + if eyepos then + eyepos:Add(Vector(0, 0, 2)) + else + eyepos = modelView.Entity:GetPos() + eyepos:Add(Vector(0, 0, 64)) + end + + modelView:SetLookAt(eyepos) + modelView:SetCamPos(eyepos + Vector(50, 0, 0)) + + modelView.LayoutEntity = function(self, ent) + if self.bAnimated then + self:RunAnimation() + end + end + + -- Имя персонажа под моделью + local nameLabel = vgui.Create("DLabel", modelPanel) + nameLabel:SetPos(0, ScalePos(500)) + nameLabel:SetSize(ScaleSize(400), ScaleSize(40)) + nameLabel:SetFont("F4Menu_InfoHeader") + nameLabel:SetText(charName) + nameLabel:SetTextColor(color_white) + nameLabel:SetContentAlignment(5) + + -- Фракция под именем + local factionLabel = vgui.Create("DLabel", modelPanel) + factionLabel:SetPos(0, ScalePos(545)) + factionLabel:SetSize(ScaleSize(400), ScaleSize(30)) + factionLabel:SetFont("F4Menu_Category") + factionLabel:SetText(factionName) + factionLabel:SetTextColor(Color(1, 67, 29)) + factionLabel:SetContentAlignment(5) + + -- Баланс внизу + local balanceLabel = vgui.Create("DLabel", modelPanel) + balanceLabel:SetPos(0, ScalePos(590)) + balanceLabel:SetSize(ScaleSize(400), ScaleSize(30)) + balanceLabel:SetFont("F4Menu_RulesTitle") + balanceLabel:SetText("Баланс: " .. moneyText) + balanceLabel:SetTextColor(Color(165, 214, 167)) + balanceLabel:SetContentAlignment(5) + + -- Кнопка изменения имени + local changeNameBtn = vgui.Create("DButton", modelPanel) + changeNameBtn:SetPos(ScalePos(20), ScalePos(640)) + changeNameBtn:SetSize(ScaleSize(360), ScaleSize(40)) + changeNameBtn:SetText("") + changeNameBtn.DoClick = function() + self:OpenChangeNameDialog(character) + end + changeNameBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) + draw.RoundedBox(8, 0, 0, w, h, col) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ИЗМЕНИТЬ ИМЯ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Правая панель с информацией + local infoPanel = vgui.Create("DPanel", self.contentPanel) + infoPanel:SetPos(ScalePos(440), ScalePos(20)) + infoPanel:SetSize(ScaleSize(1040), ScaleSize(710)) + infoPanel.Paint = function(s, w, h) + draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Заголовок + local titleLabel = vgui.Create("DLabel", infoPanel) + titleLabel:SetPos(ScalePos(25), ScalePos(20)) + titleLabel:SetFont("F4Menu_DonateHero") + titleLabel:SetText("ИНФОРМАЦИЯ О ПЕРСОНАЖЕ") + titleLabel:SetTextColor(color_white) + titleLabel:SizeToContents() + + -- Создаем строки информации + local infoRows = { + {label = "Звание:", value = rankName}, + {label = "Подразделение:", value = unitName}, + {label = "Специализация:", value = specName}, + {label = "Очки техники фракции:", value = FormatNumber(techPoints)}, + {label = "Очки снабжения фракции:", value = FormatNumber(supplyPoints)}, + } + + local startY = ScalePos(100) + local rowHeight = ScaleSize(70) + + for i, row in ipairs(infoRows) do + local yPos = startY + (i - 1) * rowHeight + + -- Панель строки + local rowPanel = vgui.Create("DPanel", infoPanel) + rowPanel:SetPos(ScalePos(25), yPos) + rowPanel:SetSize(ScaleSize(990), ScaleSize(60)) + rowPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(21, 21, 21, 230)) + surface.SetDrawColor(Color(1, 67, 29, 100)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + -- Лейбл + draw.SimpleText(row.label, "F4Menu_Category", 20, h/2, Color(165, 214, 167), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Значение + draw.SimpleText(row.value, "F4Menu_InfoValue", w - 20, h/2, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end +end + +function PLUGIN:CreateDonateTab() + if not IsValid(self.contentPanel) then return end + + self.contentPanel:Clear() + + local primaryGreen = Color(38, 166, 91) + local darkGreen = Color(16, 82, 46) + local cardBg = Color(20, 20, 22) + local cardHover = Color(25, 25, 28) + local accentOrange = Color(255, 149, 0) + local catalog = self:GetDonateCatalog() + + if not self.activeDonateCategory or not self:GetDonateCategory(self.activeDonateCategory) then + self.activeDonateCategory = catalog[1] and catalog[1].id or nil + end + + local BuildProducts + + -- Hero section with balance + local heroPanel = self.contentPanel:Add("DPanel") + heroPanel:SetPos(ScalePos(12), ScalePos(11)) + heroPanel:SetSize(ScaleSize(1475), ScaleSize(110)) + heroPanel.Paint = function(s, w, h) + -- Gradient background + surface.SetDrawColor(15, 15, 17, 250) + surface.DrawRect(0, 0, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(15, 15, 17, 0)) + + -- Top accent line + surface.SetDrawColor(primaryGreen) + surface.DrawRect(0, 0, w, 3) + + -- Subtle border + surface.SetDrawColor(30, 30, 35) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Balance display + local balanceLabel = vgui.Create("DLabel", heroPanel) + balanceLabel:SetPos(ScalePos(25), ScalePos(18)) + balanceLabel:SetFont("F4Menu_DonateTitle") + balanceLabel:SetText("ВАШ БАЛАНС РУБ") + balanceLabel:SetTextColor(Color(150, 150, 150)) + balanceLabel:SizeToContents() + + local balanceAmount = vgui.Create("DLabel", heroPanel) + balanceAmount:SetPos(ScalePos(25), ScalePos(43)) + balanceAmount:SetFont("F4Menu_DonateHero") + balanceAmount:SetTextColor(primaryGreen) + + local function UpdateBalance() + local balance = self:GetDonateBalance() + balanceAmount:SetText(string.Comma(balance) .. " РУБ.") + balanceAmount:SizeToContents() + end + + UpdateBalance() + + -- Auto-refresh balance every 5 seconds + local balanceTimer = "F4Menu_BalanceRefresh_" .. CurTime() + timer.Create(balanceTimer, 5, 0, function() + if IsValid(balanceAmount) and IsValid(self.f4Menu) and self.f4Menu:IsVisible() then + UpdateBalance() + else + timer.Remove(balanceTimer) + end + end) + + -- Topup button + local topupBtn = vgui.Create("DButton", heroPanel) + topupBtn:SetPos(ScalePos(25), ScalePos(73)) + topupBtn:SetSize(ScaleSize(220), ScaleSize(28)) + topupBtn:SetText("") + topupBtn:SetCursor("hand") + topupBtn.Paint = function(s, w, h) + local hovered = s:IsHovered() + local bg = hovered and Color(48, 186, 111) or primaryGreen + draw.RoundedBox(6, 0, 0, w, h, bg) + draw.SimpleText("ПОПОЛНИТЬ", "F4Menu_Category", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + topupBtn.DoClick = function() + surface.PlaySound("ui/buttonclickrelease.wav") + local steamID = LocalPlayer():SteamID() + local url = "https://gm-donate.net/donate/6282?steamid=" .. steamID + gui.OpenURL(url) + end + + -- Promo button + local promoBtn = vgui.Create("DButton", heroPanel) + promoBtn:SetPos(ScalePos(260), ScalePos(73)) + promoBtn:SetSize(ScaleSize(180), ScaleSize(28)) + promoBtn:SetText("") + promoBtn:SetCursor("hand") + promoBtn.Paint = function(s, w, h) + local hovered = s:IsHovered() + local alpha = hovered and 255 or 200 + draw.RoundedBox(6, 0, 0, w, h, Color(30, 30, 35, alpha)) + surface.SetDrawColor(60, 60, 70) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ПРОМОКОД", "F4Menu_Category", w / 2, h / 2, Color(200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + promoBtn.DoClick = function() + surface.PlaySound("ui/buttonclickrelease.wav") + + -- Открываем окно ввода промокода + local promoFrame = vgui.Create("DFrame") + promoFrame:SetSize(ScaleSize(500), ScaleSize(200)) + promoFrame:Center() + promoFrame:SetTitle("") + promoFrame:SetDraggable(false) + promoFrame:ShowCloseButton(false) + promoFrame:MakePopup() + promoFrame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(25, 25, 28)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, 3) + draw.SimpleText("АКТИВАЦИЯ ПРОМОКОДА", "F4Menu_Category", w/2, ScalePos(25), Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local closeBtn = vgui.Create("DButton", promoFrame) + closeBtn:SetPos(promoFrame:GetWide() - ScaleSize(35), ScalePos(5)) + closeBtn:SetSize(ScaleSize(30), ScaleSize(30)) + closeBtn:SetText("✕") + closeBtn:SetFont("F4Menu_Category") + closeBtn:SetTextColor(Color(255, 255, 255)) + closeBtn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(0, 0, 0, 0)) + end + closeBtn.DoClick = function() + promoFrame:Close() + end + + local infoLabel = vgui.Create("DLabel", promoFrame) + infoLabel:SetPos(ScalePos(20), ScalePos(60)) + infoLabel:SetSize(promoFrame:GetWide() - ScaleSize(40), ScaleSize(25)) + infoLabel:SetFont("F4Menu_Item") + infoLabel:SetTextColor(Color(200, 200, 200)) + infoLabel:SetText("Введите промокод для получения бонусной валюты:") + infoLabel:SetContentAlignment(5) + + local promoEntry = vgui.Create("DTextEntry", promoFrame) + promoEntry:SetPos(ScalePos(50), ScalePos(95)) + promoEntry:SetSize(promoFrame:GetWide() - ScaleSize(100), ScaleSize(40)) + promoEntry:SetFont("F4Menu_Category") + promoEntry:SetPlaceholderText("ВВЕДИТЕ КОД") + promoEntry.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) + s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) + end + + local activateBtn = vgui.Create("DButton", promoFrame) + activateBtn:SetPos(ScalePos(50), ScalePos(145)) + activateBtn:SetSize(promoFrame:GetWide() - ScaleSize(100), ScaleSize(40)) + activateBtn:SetText("") + activateBtn.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) + draw.SimpleText("АКТИВИРОВАТЬ", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + activateBtn.DoClick = function() + local code = promoEntry:GetValue() + if code ~= "" then + local promoPlugin = ix.plugin.Get("promocodes") + if promoPlugin then + promoPlugin:ActivatePromoCode(code) + promoFrame:Close() + else + LocalPlayer():Notify("Система промокодов недоступна") + end + else + LocalPlayer():Notify("Введите промокод") + end + end + end + + -- Categories horizontal tabs + local categoriesPanel = self.contentPanel:Add("DPanel") + categoriesPanel:SetPos(ScalePos(12), ScalePos(135)) + categoriesPanel:SetSize(ScaleSize(1475), ScaleSize(60)) + categoriesPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(18, 18, 20)) + surface.SetDrawColor(30, 30, 35) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local tabWidth = math.floor(ScaleSize(1475) / math.max(#catalog, 1)) + for i, category in ipairs(catalog) do + local btn = vgui.Create("DButton", categoriesPanel) + btn:SetPos((i - 1) * tabWidth, 0) + btn:SetSize(tabWidth, ScaleSize(60)) + btn:SetText("") + btn:SetCursor("hand") + btn.hoverAnim = 0 + btn.Think = function(s) + local target = s:IsHovered() and 1 or 0 + s.hoverAnim = Lerp(FrameTime() * 10, s.hoverAnim, target) + end + btn.Paint = function(s, w, h) + local isActive = self.activeDonateCategory == category.id + + if isActive then + draw.RoundedBox(6, 5, 5, w - 10, h - 10, darkGreen) + surface.SetDrawColor(primaryGreen) + surface.DrawRect(5, h - 7, w - 10, 3) + elseif s.hoverAnim > 0.01 then + local alpha = math.floor(s.hoverAnim * 100) + draw.RoundedBox(6, 5, 5, w - 10, h - 10, Color(25, 25, 28, alpha)) + end + + local textColor = isActive and primaryGreen or Color(180, 180, 180) + local itemCount = istable(category.items) and #category.items or 0 + draw.SimpleText(category.name or "Категория", "F4Menu_Category", w / 2, h / 2 - 5, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(itemCount .. " товаров", "F4Menu_InfoSmall", w / 2, h / 2 + 15, Color(120, 120, 120), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + btn.DoClick = function() + if self.activeDonateCategory == category.id then return end + surface.PlaySound("ui/buttonclickrelease.wav") + self.activeDonateCategory = category.id + if BuildProducts then + BuildProducts() + end + end + end + + local itemsScroll = vgui.Create("DScrollPanel", self.contentPanel) + itemsScroll:SetPos(ScalePos(12), ScalePos(210)) + itemsScroll:SetSize(ScaleSize(1475), ScaleSize(530)) + itemsScroll.Paint = nil + + local vbar = itemsScroll:GetVBar() + function vbar:Paint(w, h) + draw.RoundedBox(8, 3, 0, w - 6, h, Color(15, 15, 17)) + end + function vbar.btnUp:Paint(w, h) end + function vbar.btnDown:Paint(w, h) end + function vbar.btnGrip:Paint(w, h) + draw.RoundedBox(8, 3, 0, w - 6, h, primaryGreen) + end + + BuildProducts = function() + if not IsValid(itemsScroll) then return end + itemsScroll:Clear() + + local category = self:GetDonateCategory(self.activeDonateCategory) or catalog[1] + if not category then return end + + if not istable(category.items) or #category.items == 0 then + local emptyPanel = vgui.Create("DPanel", itemsScroll) + emptyPanel:SetPos(0, ScalePos(100)) + emptyPanel:SetSize(ScaleSize(1475), ScaleSize(200)) + emptyPanel.Paint = function(s, w, h) + draw.RoundedBox(12, 0, 0, w, h, Color(18, 18, 20)) + surface.SetDrawColor(40, 40, 45) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("", "F4Menu_DonateHero", w / 2, h / 2 - 30, Color(80, 80, 85), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("В этой категории пока нет товаров", "F4Menu_Category", w / 2, h / 2 + 20, Color(120, 120, 125), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + return + end + + local columns = 3 + local cardWidth = math.floor((ScaleSize(1475) - ScaleSize(30) * (columns - 1)) / columns) + local currentX = 0 + local currentY = 0 + local rowHeight = 0 + + for idx, entry in ipairs(category.items) do + local perksList = istable(entry.perks) and entry.perks or {} + local showPerks = (#perksList > 0) and perksList or { "Без дополнительных преимуществ" } + + local baseHeight = ScaleSize(220) + local perkHeight = #showPerks * ScaleSize(20) + ScaleSize(30) + local cardHeight = baseHeight + perkHeight + + local card = vgui.Create("DPanel", itemsScroll) + card:SetPos(currentX, currentY) + card:SetSize(cardWidth, cardHeight + ScaleSize(10)) + card.hoverAnim = 0 + card.glowAnim = 0 + + card.Think = function(s) + local hoverTarget = s:IsHovered() and 1 or 0 + s.hoverAnim = Lerp(FrameTime() * 8, s.hoverAnim, hoverTarget) + s.glowAnim = (s.glowAnim + FrameTime() * 2) % (math.pi * 2) + end + + local accent = ResolveColor(entry.accent or category.accent, 255) + local price1Month = entry.price1Month or entry.price or 0 + local price3Month = entry.price3Month + local hasTimedPurchase = (entry.price1Month ~= nil or entry.price3Month ~= nil) -- Проверяем есть ли временные опции + card.selectedPrice = price1Month + card.selectedMode = "1month" + + card.Paint = function(s, w, h) + -- Compensate for lift effect + local actualHeight = h - ScaleSize(10) + + -- Card shadow + if s.hoverAnim > 0.01 then + local shadowOffset = math.floor(s.hoverAnim * 8) + draw.RoundedBox(12, shadowOffset / 2, shadowOffset / 2 + ScaleSize(5), w, actualHeight, Color(0, 0, 0, 50 * s.hoverAnim)) + end + + -- Main card + local liftY = math.floor(s.hoverAnim * -5) + ScaleSize(5) + draw.RoundedBox(12, 0, liftY, w, actualHeight, cardBg) + + -- Border glow on hover + if s.hoverAnim > 0.01 then + local glowAlpha = math.floor(50 + 30 * math.sin(s.glowAnim)) + surface.SetDrawColor(accent.r, accent.g, accent.b, glowAlpha * s.hoverAnim) + surface.DrawOutlinedRect(0, liftY, w, actualHeight, 2) + end + + -- Title + draw.SimpleText(entry.title or "Пакет", "F4Menu_DonateTitle", 15, liftY + ScaleSize(55), color_white) + + -- Price + local priceText = FormatDonatePrice(card.selectedPrice, entry.currency or "RUB") + draw.SimpleText(priceText, "F4Menu_DonatePrice", w - 15, liftY + ScaleSize(55), primaryGreen, TEXT_ALIGN_RIGHT) + + -- Period selector background (только если есть временные опции) + if hasTimedPurchase then + draw.RoundedBox(6, 15, liftY + ScaleSize(95), w - 30, ScaleSize(35), Color(15, 15, 17)) + end + + -- Separator + local separatorY = hasTimedPurchase and ScaleSize(145) or ScaleSize(100) + surface.SetDrawColor(40, 40, 45) + surface.DrawRect(15, liftY + separatorY, w - 30, 1) + + -- Perks title + local perksY = hasTimedPurchase and ScaleSize(160) or ScaleSize(115) + draw.SimpleText("Преимущества:", "F4Menu_Category", 15, liftY + perksY, Color(180, 180, 185)) + + -- Perks list + local perkY = liftY + (hasTimedPurchase and ScaleSize(185) or ScaleSize(140)) + for _, perk in ipairs(showPerks) do + draw.SimpleText("• " .. perk, "F4Menu_Item", 20, perkY, Color(200, 200, 205)) + perkY = perkY + ScaleSize(20) + end + end + + -- Period selector buttons (только если есть временные опции) + if hasTimedPurchase and price1Month then + local month1Btn = vgui.Create("DButton", card) + month1Btn:SetPos(20, ScaleSize(100)) + month1Btn:SetSize((cardWidth - 55) / 2, ScaleSize(25)) + month1Btn:SetText("") + month1Btn:SetCursor("hand") + month1Btn.selected = true + month1Btn.Paint = function(s, w, h) + local bg = s.selected and primaryGreen or Color(25, 25, 28) + local textCol = s.selected and color_white or Color(150, 150, 150) + draw.RoundedBox(4, 0, 0, w, h, bg) + draw.SimpleText("1 месяц", "F4Menu_Item", w / 2, h / 2, textCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + month1Btn.DoClick = function() + if month1Btn.selected then return end + surface.PlaySound("ui/buttonclick.wav") + month1Btn.selected = true + if card.month3Btn then card.month3Btn.selected = false end + card.selectedPrice = price1Month + card.selectedMode = "1month" + end + card.month1Btn = month1Btn + end + + if hasTimedPurchase and price3Month then + local month3Btn = vgui.Create("DButton", card) + month3Btn:SetPos(25 + (cardWidth - 55) / 2, ScaleSize(100)) + month3Btn:SetSize((cardWidth - 55) / 2, ScaleSize(25)) + month3Btn:SetText("") + month3Btn:SetCursor("hand") + month3Btn.selected = false + month3Btn.Paint = function(s, w, h) + local bg = s.selected and primaryGreen or Color(25, 25, 28) + local textCol = s.selected and color_white or Color(150, 150, 150) + draw.RoundedBox(4, 0, 0, w, h, bg) + draw.SimpleText("3 месяца", "F4Menu_Item", w / 2, h / 2, textCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + month3Btn.DoClick = function() + if month3Btn.selected then return end + surface.PlaySound("ui/buttonclick.wav") + month3Btn.selected = true + if card.month1Btn then card.month1Btn.selected = false end + card.selectedPrice = price3Month + card.selectedMode = "3month" + end + card.month3Btn = month3Btn + end + + -- Buy button + local buyBtn = vgui.Create("DButton", card) + buyBtn:SetPos(15, cardHeight - ScaleSize(50)) + buyBtn:SetSize(cardWidth - 30, ScaleSize(38)) + buyBtn:SetText("") + buyBtn:SetCursor("hand") + buyBtn.hoverAnim = 0 + buyBtn.Think = function(s) + local target = s:IsHovered() and 1 or 0 + s.hoverAnim = Lerp(FrameTime() * 12, s.hoverAnim, target) + end + buyBtn.Paint = function(s, w, h) + local bgColor = Color( + primaryGreen.r + (accent.r - primaryGreen.r) * s.hoverAnim, + primaryGreen.g + (accent.g - primaryGreen.g) * s.hoverAnim, + primaryGreen.b + (accent.b - primaryGreen.b) * s.hoverAnim + ) + draw.RoundedBox(6, 0, 0, w, h, bgColor) + + draw.SimpleText("КУПИТЬ", "F4Menu_DonateBold", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + buyBtn.DoClick = function() + surface.PlaySound("ui/buttonclickrelease.wav") + self:ConfirmDonatePurchase(entry, card.selectedPrice, card.selectedMode) + end + + rowHeight = math.max(rowHeight, cardHeight + ScaleSize(10)) + currentX = currentX + cardWidth + ScaleSize(30) + + if (idx % columns) == 0 then + currentX = 0 + currentY = currentY + rowHeight + ScaleSize(25) + rowHeight = 0 + end + end + end + + BuildProducts() + timer.Simple(0.1, function() + if BuildProducts and IsValid(itemsScroll) then + BuildProducts() + end + end) +end + + + + +function PLUGIN:CreateRulesTab() + if IsValid(self.contentPanel) then self.contentPanel:Remove() end + local panelW, panelH = ScaleSize(1500), ScaleSize(750) + local panelX, panelY = ScalePos(210), ScalePos(165) + self.contentPanel = vgui.Create("DPanel", self.f4Menu) + self.contentPanel:SetSize(panelW, panelH) + self.contentPanel:SetPos(panelX, panelY) + self.contentPanel.Paint = function(s, w, h) + draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local html = vgui.Create("DHTML", self.contentPanel) + html:SetSize(panelW - ScaleSize(20), panelH - ScaleSize(20)) + html:SetPos(ScalePos(10), ScalePos(10)) + html:OpenURL("https://sites.google.com/view/frontteamsite/правила") +end + + +--[[ СИСТЕМА ЖАЛОБ УДАЛЕНА +function PLUGIN:CreateContactsTab() + local panelW, panelH = ScaleSize(1500), ScaleSize(750) + local panelX, panelY = ScalePos(210), ScalePos(165) + local contactsPanel = vgui.Create("DPanel", self.f4Menu) + contactsPanel:SetSize(panelW, panelH) + contactsPanel:SetPos(panelX, panelY) + contactsPanel.Paint = function(s, w, h) + draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + self.contentPanel = contactsPanel + + -- Левая колонка: список админов + local adminListPanel = vgui.Create("DScrollPanel", contactsPanel) + adminListPanel:SetSize(ScaleSize(680), ScaleSize(590)) + adminListPanel:SetPos(ScalePos(34), ScalePos(126)) + adminListPanel.Paint = function(s, w, h) + draw.RoundedBox(10, 0, 0, w, h, Color(21, 21, 21, 255)) + end + -- Стилизуем скроллбар + local bar = adminListPanel:GetVBar() + function bar:Paint(w, h) end + function bar.btnUp:Paint(w, h) end + function bar.btnDown:Paint(w, h) end + function bar.btnGrip:Paint(w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(1, 67, 29, 180)) + end + + local adminTitle = vgui.Create("DLabel", contactsPanel) + adminTitle:SetFont("F4Menu_Title") + adminTitle:SetText("СПИСОК АДМИНИСТРАТОРОВ") + adminTitle:SetTextColor(color_white) + adminTitle:SetPos(ScalePos(230), ScalePos(18)) + adminTitle:SetSize(ScaleSize(628), ScaleSize(54)) + adminTitle:SetContentAlignment(4) + adminTitle:SetFont("F4Menu_Title") + + -- Список админов (живые игроки с привилегией не 'user') + local admins = {} + for _, ply in ipairs(player.GetAll()) do + if ply:GetUserGroup() and ply:GetUserGroup() ~= "user" then + table.insert(admins, ply) + end + end + local startY = ScalePos(40) + for i, ply in ipairs(admins) do + local row = vgui.Create("DPanel", adminListPanel) + row:SetSize(ScaleSize(620), ScaleSize(55)) + row:Dock(TOP) + -- Для первого row делаем больший верхний отступ + if i == 1 then + row:DockMargin(ScaleSize(20), ScaleSize(15), ScaleSize(20), ScaleSize(10)) + else + row:DockMargin(ScaleSize(20), 0, ScaleSize(20), ScaleSize(10)) + end + row.Paint = function(s, w, h) + draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + -- Аватар Steam + local avatar = vgui.Create("AvatarImage", row) + avatar:SetSize(ScaleSize(40), ScaleSize(40)) + avatar:SetPos(ScalePos(10), ScalePos(7)) + avatar:SetPlayer(ply, 64) + -- Имя + local name = vgui.Create("DLabel", row) + name:SetFont("F4Menu_RulesTitle") + name:SetText(ply:Nick()) + name:SetTextColor(color_white) + name:SetPos(ScalePos(61), ScalePos(10)) + name:SetSize(ScaleSize(200), ScaleSize(34)) + -- Должность + local rank = vgui.Create("DLabel", row) + rank:SetFont("F4Menu_RulesTitle") + rank:SetText(ply:GetUserGroup()) + rank:SetTextColor(color_white) + rank:SetPos(ScalePos(450), ScalePos(10)) + rank:SetSize(ScaleSize(155), ScaleSize(34)) + end + + -- Правая колонка: связь с администрацией + + local callTitle = vgui.Create("DLabel", contactsPanel) + callTitle:SetFont("F4Menu_Title") + callTitle:SetText("ПОЗВАТЬ АДМИНИСТРАТОРА") + callTitle:SetTextColor(color_white) + callTitle:SetPos(ScalePos(980), ScalePos(18)) + callTitle:SetSize(ScaleSize(620), ScaleSize(54)) + callTitle:SetContentAlignment(4) + + local callDesc = vgui.Create("DLabel", contactsPanel) + callDesc:SetFont("F4Menu_Item") + callDesc:SetText("Выберите категорию чтобы связаться с Администрацией") + callDesc:SetTextColor(color_white) + callDesc:SetPos(ScalePos(850), ScalePos(150)) + callDesc:SetSize(ScaleSize(575), ScaleSize(58)) + callDesc:SetContentAlignment(5) + + -- Кнопки категорий + -- Кнопки категорий + local btnComplaint, btnHelp + local mode = "complaint" -- "complaint" или "help" + btnComplaint = vgui.Create("DButton", contactsPanel) + btnComplaint:SetSize(ScaleSize(125), ScaleSize(35)) + btnComplaint:SetPos(ScalePos(900) + ScalePos(92), ScalePos(200)) + btnComplaint:SetText("") + btnComplaint.Paint = function(s, w, h) + if mode == "complaint" then + draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29, 255)) + else + draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) + end + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("Жалоба", "F4Menu_RulesTitle", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + btnComplaint.DoClick = function() + mode = "complaint" + updatePanels() + end + + btnHelp = vgui.Create("DButton", contactsPanel) + btnHelp:SetSize(ScaleSize(125), ScaleSize(35)) + btnHelp:SetPos(ScalePos(1040) + ScalePos(92), ScalePos(200)) + btnHelp:SetText("") + btnHelp.Paint = function(s, w, h) + if mode == "help" then + draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29, 255)) + else + draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) + end + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("Помощь", "F4Menu_RulesTitle", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + btnHelp.DoClick = function() + mode = "help" + updatePanels() + end + + local reasonPanel = vgui.Create("DPanel", contactsPanel) + reasonPanel:SetSize(ScaleSize(250), ScaleSize(100)) + reasonPanel:SetPos(ScalePos(850), ScalePos(250)) + reasonPanel.Paint = function() end + local reasonEntry = vgui.Create("DTextEntry", reasonPanel) + reasonEntry:SetFont("F4Menu_Item") + reasonEntry:SetTextColor(Color(194, 194, 194)) + reasonEntry:SetPos(0, 0) + reasonEntry:SetSize(ScaleSize(250), ScaleSize(100)) + reasonEntry:SetMultiline(true) + reasonEntry:SetVerticalScrollbarEnabled(false) + reasonEntry:SetDrawBorder(false) + reasonEntry:SetDrawBackground(false) + reasonEntry.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(29, 28, 28, 255)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + -- Показываем плейсхолдер в зависимости от режима + if s:GetValue() == "" then + local placeholder = mode == "complaint" and "Введите причину жалобы..." or "Введите ваш вопрос администраторам..." + draw.SimpleText(placeholder, "F4Menu_Item", 5, 5, Color(100, 100, 100)) + end + + s:DrawTextEntryText(Color(194, 194, 194), Color(1, 67, 29), Color(194, 194, 194)) + end + + -- Панель нарушителей (как раньше) + local offendersPanel = vgui.Create("DPanel", contactsPanel) + offendersPanel:SetSize(ScaleSize(315), ScaleSize(360)) + offendersPanel:SetPos(ScalePos(1150), ScalePos(250)) + offendersPanel.Paint = function(s, w, h) + draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28, 255)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + local searchEntry = vgui.Create("DTextEntry", offendersPanel) + searchEntry:SetSize(ScaleSize(285), ScaleSize(38)) + searchEntry:SetPos(ScalePos(15), ScalePos(10)) + searchEntry:SetFont("F4Menu_Item") + searchEntry:SetPlaceholderText("Поиск игрока по нику...") + searchEntry:SetTextColor(Color(194, 194, 194)) + searchEntry.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(29, 28, 28, 255)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(Color(194, 194, 194), Color(1, 67, 29), Color(194, 194, 194)) + end + local offendersScroll = vgui.Create("DScrollPanel", offendersPanel) + local function layoutOffendersScroll() + local scrollY = ScalePos(10) + ScaleSize(38) + ScalePos(2) + local scrollH = offendersPanel:GetTall() - scrollY - ScalePos(10) + offendersScroll:SetPos(ScalePos(15), scrollY) + offendersScroll:SetSize(ScaleSize(285), scrollH) + end + offendersPanel.PerformLayout = layoutOffendersScroll + layoutOffendersScroll() + local vbar = offendersScroll:GetVBar() + function vbar:Paint(w, h) end + function vbar.btnUp:Paint(w, h) end + function vbar.btnDown:Paint(w, h) end + function vbar.btnGrip:Paint(w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(1, 67, 29, 180)) + end + local selectedOffender = nil + local function UpdateOffendersList(filter) + offendersScroll:Clear() + local players = player.GetAll() + for _, ply in ipairs(players) do + if not filter or string.find(string.lower(ply:Nick()), string.lower(filter), 1, true) then + local row = vgui.Create("DButton", offendersScroll) + row:SetSize(ScaleSize(265), ScaleSize(38)) + row:Dock(TOP) + row:DockMargin(0, 0, 0, 7) + row:SetText("") + row.Paint = function(s, w, h) + if selectedOffender == ply then + draw.RoundedBox(6, 0, 0, w, h, Color(1, 67, 29, 220)) + else + draw.RoundedBox(6, 0, 0, w, h, Color(21, 21, 21, 230)) + end + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + row.DoClick = function() + selectedOffender = ply + UpdateOffendersList(searchEntry:GetValue()) + end + local avatar = vgui.Create("AvatarImage", row) + avatar:SetSize(ScaleSize(28), ScaleSize(28)) + avatar:SetPos(ScalePos(6), ScalePos(5)) + avatar:SetPlayer(ply, 64) + local name = vgui.Create("DLabel", row) + name:SetFont("F4Menu_Item") + name:SetText(ply:Nick()) + name:SetTextColor(color_white) + name:SetPos(ScalePos(42), ScalePos(8)) + name:SetSize(ScaleSize(200), ScaleSize(22)) + end + end + end + UpdateOffendersList("") + searchEntry.OnChange = function(self) + UpdateOffendersList(self:GetValue()) + end + + -- Кнопка отправить + local btnSend = vgui.Create("DButton", contactsPanel) + btnSend:SetSize(ScaleSize(120), ScaleSize(38)) + btnSend:SetText("") + btnSend.Paint = function(s, w, h) + draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("Отправить", "F4Menu_RulesTitle", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + btnSend.DoClick = function() + if mode == "complaint" then + -- Отправка жалобы через complaints-плагин + local targetName = selectedOffender and selectedOffender:Nick() or "" + local reason = reasonEntry:GetValue() or "" + + if not selectedOffender then + LocalPlayer():Notify("Выберите нарушителя из списка!") + return + end + + if reason == "" then + LocalPlayer():Notify("Укажите причину жалобы!") + return + end + + if #reason < 5 then + LocalPlayer():Notify("Причина жалобы должна содержать минимум 5 символов!") + return + end + + net.Start("ix.SubmitComplaint") + net.WriteString(targetName) + net.WriteString(reason) + net.SendToServer() + + reasonEntry:SetValue("") + selectedOffender = nil + UpdateOffendersList("") + elseif mode == "help" then + -- Отправка вопроса администраторам + local question = reasonEntry:GetValue() or "" + + if question == "" then + LocalPlayer():Notify("Укажите ваш вопрос!") + return + end + + if #question < 10 then + LocalPlayer():Notify("Вопрос должен содержать минимум 10 символов!") + return + end + + -- Отправляем вопрос в чат администраторов + net.Start("ix.SendHelpRequest") + net.WriteString(question) + net.SendToServer() + + LocalPlayer():Notify("Ваш вопрос отправлен администраторам!") + reasonEntry:SetValue("") + end + end + -- Если игрок админ — добавить кнопку просмотра жалоб + timer.Simple(0, function() + if not IsValid(contactsPanel) then return end + local complaintsPlugin = ix and ix.plugins and ix.plugins.Get("complaints") + if complaintsPlugin and complaintsPlugin.CanSeeComplaints and complaintsPlugin:CanSeeComplaints(LocalPlayer()) then + local btnViewComplaints = vgui.Create("DButton", contactsPanel) + btnViewComplaints:SetSize(ScaleSize(180), ScaleSize(36)) + btnViewComplaints:SetPos(ScalePos(900), ScalePos(650)) + btnViewComplaints:SetText("Просмотреть жалобы игроков") + btnViewComplaints:SetFont("F4Menu_Item") + btnViewComplaints.Paint = function(s, w, h) + draw.RoundedBox(7, 0, 0, w, h, Color(1, 67, 29, 255)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + btnViewComplaints.DoClick = function() + complaintsPlugin:RequestComplaints() + timer.Simple(0.1, function() + complaintsPlugin:ShowComplaintsPanel() + end) + end + end + end) + + -- Функция обновления видимости панелей + function updatePanels() + if mode == "complaint" then + offendersPanel:SetVisible(true) + reasonPanel:SetVisible(true) + btnSend:SetVisible(true) + -- Слева, как обычно + reasonPanel:SetPos(ScalePos(850), ScalePos(250)) + btnSend:SetPos(ScalePos(900) + ScalePos(20), ScalePos(370)) + elseif mode == "help" then + offendersPanel:SetVisible(false) + reasonPanel:SetVisible(true) + btnSend:SetVisible(true) + -- Центрируем по правой стороне (там где offendersPanel) + local rightX = ScalePos(970) + local rightW = ScaleSize(315) + local panelW = reasonPanel:GetWide() + local panelX = rightX + math.floor((rightW - panelW) / 2) + reasonPanel:SetPos(panelX, ScalePos(250)) + -- Кнопка под причиной + local btnX = panelX + math.floor((panelW - btnSend:GetWide()) / 2) + ScalePos(8) + btnSend:SetPos(btnX, ScalePos(250) + reasonPanel:GetTall() + ScalePos(15)) + else + offendersPanel:SetVisible(false) + reasonPanel:SetVisible(false) + btnSend:SetVisible(false) + end + end + -- Скрыть всё по умолчанию (и выбрать жалобу) + updatePanels() +end +--]] + +function PLUGIN:CreateThanksTab() + if IsValid(self.contentPanel) then self.contentPanel:Remove() end + local panelW, panelH = ScaleSize(1500), ScaleSize(750) + local panelX, panelY = ScalePos(210), ScalePos(165) + self.contentPanel = vgui.Create("DPanel", self.f4Menu) + self.contentPanel:SetSize(panelW, panelH) + self.contentPanel:SetPos(panelX, panelY) + self.contentPanel.Paint = function(s, w, h) + draw.RoundedBox(15, 0, 0, w, h, Color(13, 13, 13, 218)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local pages = self:GetThanksPages() + if not pages or #pages == 0 then + local noData = vgui.Create("DLabel", self.contentPanel) + noData:SetFont("F4Menu_Category") + noData:SetText("Нет доступных страниц благодарностей") + noData:SetTextColor(Color(150, 150, 150)) + noData:SetPos(0, panelH / 2 - 20) + noData:SetSize(panelW, 40) + noData:SetContentAlignment(5) + return + end + + -- Текущая страница + self.currentThanksPage = self.currentThanksPage or 1 + self.currentThanksPage = math.Clamp(self.currentThanksPage, 1, #pages) + + local currentPage = pages[self.currentThanksPage] + + -- Заголовок страницы + local title = vgui.Create("DLabel", self.contentPanel) + title:SetFont("F4Menu_Title") + title:SetText(currentPage.title or "БЛАГОДАРНОСТИ") + title:SetTextColor(color_white) + title:SetPos(0, ScalePos(20)) + title:SetSize(panelW, ScaleSize(40)) + title:SetContentAlignment(5) + + -- Кнопки переключения страниц + if #pages > 1 then + -- Кнопка назад + local prevBtn = vgui.Create("DButton", self.contentPanel) + prevBtn:SetPos(ScalePos(30), ScalePos(25)) + prevBtn:SetSize(ScaleSize(40), ScaleSize(40)) + prevBtn:SetText("") + prevBtn.DoClick = function() + self.currentThanksPage = self.currentThanksPage - 1 + if self.currentThanksPage < 1 then + self.currentThanksPage = #pages + end + self:CreateThanksTab() + end + prevBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) + draw.RoundedBox(8, 0, 0, w, h, col) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("<", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Кнопка вперед + local nextBtn = vgui.Create("DButton", self.contentPanel) + nextBtn:SetPos(panelW - ScalePos(70), ScalePos(25)) + nextBtn:SetSize(ScaleSize(40), ScaleSize(40)) + nextBtn:SetText("") + nextBtn.DoClick = function() + self.currentThanksPage = self.currentThanksPage + 1 + if self.currentThanksPage > #pages then + self.currentThanksPage = 1 + end + self:CreateThanksTab() + end + nextBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) + draw.RoundedBox(8, 0, 0, w, h, col) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText(">", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Индикатор страниц + local pageIndicator = vgui.Create("DLabel", self.contentPanel) + pageIndicator:SetFont("F4Menu_Item") + pageIndicator:SetText(self.currentThanksPage .. " / " .. #pages) + pageIndicator:SetTextColor(Color(165, 214, 167)) + pageIndicator:SetPos(0, ScalePos(70)) + pageIndicator:SetSize(panelW, ScaleSize(20)) + pageIndicator:SetContentAlignment(5) + end + + -- Контейнер для моделей + local modelsContainer = vgui.Create("DPanel", self.contentPanel) + modelsContainer:SetPos(ScalePos(50), ScalePos(120)) + modelsContainer:SetSize(panelW - ScaleSize(100), panelH - ScaleSize(140)) + modelsContainer.Paint = function() end + + local people = currentPage.people or {} + local peopleCount = math.min(#people, 10) + + if peopleCount == 0 then + local noPeople = vgui.Create("DLabel", modelsContainer) + noPeople:SetFont("F4Menu_Item") + noPeople:SetText("Нет людей на этой странице") + noPeople:SetTextColor(Color(150, 150, 150)) + noPeople:SetPos(0, 0) + noPeople:SetSize(modelsContainer:GetWide(), modelsContainer:GetTall()) + noPeople:SetContentAlignment(5) + return + end + + -- Расчет размеров для моделей + local containerWidth = modelsContainer:GetWide() + local containerHeight = modelsContainer:GetTall() + local modelWidth = ScaleSize(140) -- Фиксированная ширина для каждой модели + local modelHeight = containerHeight - ScaleSize(40) -- Оставляем место для имени + + -- Расчет общей ширины всех моделей и отступа для центрирования + local totalWidth = peopleCount * modelWidth + local startX = (containerWidth - totalWidth) / 2 + + -- Создаем модели + for i, person in ipairs(people) do + if i > 10 then break end + + -- Позиция с учетом центрирования + local xPos = startX + (i - 1) * modelWidth + + -- Контейнер для модели и имени + local personContainer = vgui.Create("DButton", modelsContainer) + personContainer:SetPos(xPos, 0) + personContainer:SetSize(modelWidth, containerHeight) + personContainer:SetText("") + personContainer.DoClick = function() + self:OpenPersonInfoDialog(person) + end + personContainer.Paint = function(s, w, h) + if s:IsHovered() then + surface.SetDrawColor(Color(27, 94, 32, 50)) + surface.DrawRect(0, 0, w, h) + end + end + + -- DModelPanel для модели + local modelPanel = vgui.Create("DModelPanel", personContainer) + modelPanel:SetPos(ScaleSize(5), 0) + modelPanel:SetSize(modelWidth - ScaleSize(10), modelHeight) + modelPanel:SetModel(person.model or "models/player/group01/male_01.mdl") + modelPanel:SetFOV(35) + modelPanel:SetMouseInputEnabled(false) + + -- Настройка камеры для отображения модели в полный рост + local ent = modelPanel.Entity + if IsValid(ent) then + local mins, maxs = ent:GetRenderBounds() + local modelHeight = maxs.z - mins.z + local modelCenter = (mins + maxs) / 2 + + modelPanel:SetLookAt(modelCenter) + modelPanel:SetCamPos(modelCenter + Vector(modelHeight * 0.8, 0, modelHeight * 0.1)) + + -- Устанавливаем анимацию + if person.sequence then + local seq = ent:LookupSequence(person.sequence) + if seq > 0 then + ent:ResetSequence(seq) + end + end + end + + modelPanel.LayoutEntity = function(pnl, ent) + -- Отключаем стандартную анимацию вращения + end + + -- Имя под моделью + local nameLabel = vgui.Create("DLabel", personContainer) + nameLabel:SetPos(0, modelHeight + ScaleSize(5)) + nameLabel:SetSize(modelWidth, ScaleSize(35)) + nameLabel:SetFont("F4Menu_Item") + nameLabel:SetText(person.name or "Неизвестно") + nameLabel:SetTextColor(Color(165, 214, 167)) + nameLabel:SetContentAlignment(5) + nameLabel:SetMouseInputEnabled(false) + end +end + +function PLUGIN:OpenPersonInfoDialog(person) + if not person then return end + + local frame = vgui.Create("DFrame") + frame:SetSize(ScaleSize(600), ScaleSize(400)) + frame:Center() + frame:SetTitle("") + frame:SetDraggable(true) + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(s, w, h) + draw.RoundedBox(18, 0, 0, w, h, Color(13, 13, 13, 240)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + -- Модель слева + local modelPanel = vgui.Create("DModelPanel", frame) + modelPanel:SetPos(ScalePos(20), ScalePos(20)) + modelPanel:SetSize(ScaleSize(250), ScaleSize(360)) + modelPanel:SetModel(person.model or "models/player/group01/male_01.mdl") + modelPanel:SetFOV(50) + + local ent = modelPanel.Entity + if IsValid(ent) then + local mins, maxs = ent:GetRenderBounds() + local modelHeight = maxs.z - mins.z + local modelCenter = (mins + maxs) / 2 + + -- Центр смотрит на середину модели по высоте + modelPanel:SetLookAt(Vector(modelCenter.x, modelCenter.y, mins.z + modelHeight * 0.4)) + -- Камера отодвинута достаточно далеко чтобы показать всю модель + modelPanel:SetCamPos(modelCenter + Vector(modelHeight * 1.0, 0, 0)) + + if person.sequence then + local seq = ent:LookupSequence(person.sequence) + if seq > 0 then + ent:ResetSequence(seq) + end + end + end + + modelPanel.LayoutEntity = function() end + + -- Информация справа + local infoPanel = vgui.Create("DPanel", frame) + infoPanel:SetPos(ScalePos(290), ScalePos(20)) + infoPanel:SetSize(ScaleSize(290), ScaleSize(310)) + infoPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 18)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Имя + local nameLabel = vgui.Create("DLabel", infoPanel) + nameLabel:SetPos(ScalePos(15), ScalePos(15)) + nameLabel:SetSize(ScaleSize(260), ScaleSize(30)) + nameLabel:SetFont("F4Menu_Category") + nameLabel:SetText(person.name or "Неизвестно") + nameLabel:SetTextColor(Color(165, 214, 167)) + nameLabel:SetContentAlignment(5) + + -- Разделитель + local divider = vgui.Create("DPanel", infoPanel) + divider:SetPos(ScalePos(15), ScalePos(50)) + divider:SetSize(ScaleSize(260), 1) + divider.Paint = function(s, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + end + + -- Описание + local descLabel = vgui.Create("DLabel", infoPanel) + descLabel:SetPos(ScalePos(15), ScalePos(65)) + descLabel:SetSize(ScaleSize(260), ScaleSize(230)) + descLabel:SetFont("F4Menu_InfoSmall") + descLabel:SetText(person.description or "Нет описания") + descLabel:SetTextColor(Color(220, 220, 220)) + descLabel:SetContentAlignment(7) + descLabel:SetWrap(true) + descLabel:SetAutoStretchVertical(true) + + -- Кнопка открытия ссылки + if person.link and person.link ~= "" then + local linkBtn = vgui.Create("DButton", frame) + linkBtn:SetPos(ScalePos(290), ScalePos(345)) + linkBtn:SetSize(ScaleSize(140), ScaleSize(35)) + linkBtn:SetText("") + linkBtn.DoClick = function() + gui.OpenURL(person.link) + end + linkBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(56, 102, 35) or Color(27, 94, 32) + draw.RoundedBox(8, 0, 0, w, h, col) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ПРОФИЛЬ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + -- Кнопка закрытия + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(ScalePos(445), ScalePos(345)) + closeBtn:SetSize(ScaleSize(135), ScaleSize(35)) + closeBtn:SetText("") + closeBtn.DoClick = function() + frame:Close() + end + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(50, 18, 18) or Color(35, 13, 13) + draw.RoundedBox(8, 0, 0, w, h, col) + surface.SetDrawColor(Color(67, 1, 1)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("ЗАКРЫТЬ", "F4Menu_Category", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end + +function PLUGIN:CreateSettingsTab() + if IsValid(self.contentPanel) then self.contentPanel:Remove() end + local panelW, panelH = ScaleSize(1500), ScaleSize(750) + local panelX, panelY = ScalePos(210), ScalePos(165) + self.contentPanel = vgui.Create("DPanel", self.f4Menu) + self.contentPanel:SetSize(panelW, panelH) + self.contentPanel:SetPos(panelX, panelY) + self.contentPanel.Paint = function(s, w, h) end -- Прозрачный фон + + -- Скролл панель для категорий + local scroll = vgui.Create("DScrollPanel", self.contentPanel) + scroll:SetPos(0, 0) + scroll:SetSize(panelW, panelH) + + local sbar = scroll:GetVBar() + sbar:SetWide(8) + sbar.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(21, 21, 21, 200)) + end + sbar.btnGrip.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29, 200)) + end + sbar.btnUp.Paint = function() end + sbar.btnDown.Paint = function() end + + -- Получаем все настройки по категориям + local allOptions = ix.option.GetAllByCategories(true) + + if not allOptions or table.Count(allOptions) == 0 then + local noSettings = vgui.Create("DLabel", scroll) + noSettings:SetFont("F4Menu_InfoValue") + noSettings:SetText("Настройки не найдены") + noSettings:SetTextColor(Color(150, 150, 150)) + noSettings:Dock(TOP) + noSettings:DockMargin(20, 20, 20, 0) + noSettings:SizeToContents() + return + end + + -- Создаем категории как отдельные панели + for category, options in SortedPairs(allOptions) do + local categoryName = L(category) + + -- Сортируем опции по имени + table.sort(options, function(a, b) + return L(a.phrase) < L(b.phrase) + end) + + -- Вычисляем высоту категории + local headerHeight = ScaleSize(34) + local headerMargin = ScaleSize(37) + local settingGap = ScaleSize(25) + local bottomPadding = ScaleSize(45) + local totalSettingsHeight = 0 + + -- Рассчитываем высоту для каждой настройки + for _, data in pairs(options) do + if data.type == ix.type.number then + totalSettingsHeight = totalSettingsHeight + ScaleSize(56) + settingGap + elseif data.type == ix.type.bool then + totalSettingsHeight = totalSettingsHeight + ScaleSize(24) + settingGap + elseif data.type == ix.type.array or data.type == ix.type.string then + totalSettingsHeight = totalSettingsHeight + ScaleSize(34) + settingGap + else + totalSettingsHeight = totalSettingsHeight + ScaleSize(24) + settingGap + end + end + totalSettingsHeight = totalSettingsHeight - settingGap -- убираем последний gap + + local categoryHeight = headerHeight + headerMargin + totalSettingsHeight + bottomPadding + + -- Панель категории (Rectangle 933 из Figma - width: 1265px) + local categoryBox = vgui.Create("DPanel", scroll) + categoryBox:Dock(TOP) + categoryBox:DockMargin(ScalePos(118), 0, ScalePos(118), ScalePos(35)) + categoryBox:SetTall(categoryHeight) + categoryBox.Paint = function(s, w, h) + -- background: #0D0D0D; opacity: 0.85; + draw.RoundedBox(15, 0, 0, w, h, ColorAlpha(Color(13, 13, 13), 217)) + -- border: 1px solid #01431D; + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Заголовок категории + local catTitle = vgui.Create("DLabel", categoryBox) + catTitle:SetPos(ScalePos(25), ScalePos(22)) + catTitle:SetFont("F4Menu_InfoHeader") -- font-size: 28px; font-weight: 600; + catTitle:SetText(categoryName) + catTitle:SetTextColor(color_white) + catTitle:SizeToContents() + + -- Контейнер для настроек (Frame 15) + local settingsContainer = vgui.Create("DPanel", categoryBox) + settingsContainer:SetPos(ScalePos(27), headerHeight + headerMargin) + settingsContainer:SetSize(ScaleSize(1200), totalSettingsHeight) + settingsContainer.Paint = function() end + + -- Создаем настройки + local yOffset = 0 + for _, data in pairs(options) do + local key = data.key + local value = ix.util.SanitizeType(data.type, ix.option.Get(key)) + + -- Название настройки (Group 21/25) + local nameLabel = vgui.Create("DLabel", settingsContainer) + nameLabel:SetPos(0, yOffset) + nameLabel:SetFont("F4Menu_RulesTitle") -- font-size: 20px + nameLabel:SetText(L(data.phrase)) + nameLabel:SetTextColor(color_white) + nameLabel:SizeToContents() + + -- Создаем контрол в зависимости от типа + if data.type == ix.type.number then + -- Слайдер (Group 22) + local sliderHeight = ScaleSize(56) + + -- Значение (0.999999) + local valueLabel = vgui.Create("DLabel", settingsContainer) + valueLabel:SetPos(ScaleSize(1114), yOffset) + valueLabel:SetFont("F4Menu_RulesTitle") + valueLabel:SetText(tostring(value)) + valueLabel:SetTextColor(ColorAlpha(color_white, 165)) -- rgba(255, 255, 255, 0.65) + valueLabel:SizeToContents() + + -- Трек слайдера (Rectangle 18) + local trackY = yOffset + ScaleSize(39) + local slider = vgui.Create("DPanel", settingsContainer) + slider:SetPos(0, trackY) + slider:SetSize(ScaleSize(1200), ScaleSize(17)) + slider:SetCursor("hand") + slider.min = data.min or 0 + slider.max = data.max or 100 + slider.decimals = data.decimals or 0 + slider.value = value + slider.dragging = false + + slider.Paint = function(s, w, h) + -- Трек (Rectangle 18) + draw.RoundedBox(50, 0, ScaleSize(7), w, ScaleSize(3), ColorAlpha(color_white, 165)) + + -- Прогресс (Rectangle 20) + local progress = (s.value - s.min) / (s.max - s.min) + local progressW = w * progress + draw.RoundedBox(50, 0, ScaleSize(7), progressW, ScaleSize(3), Color(56, 84, 45)) -- #38542D + + -- Ручка (Rectangle 19) + local knobX = math.Clamp(progressW - ScaleSize(8.5), 0, w - ScaleSize(17)) + draw.RoundedBox(20, knobX, 0, ScaleSize(17), ScaleSize(17), Color(56, 84, 45)) + end + + slider.OnMousePressed = function(s) + s.dragging = true + s:MouseCapture(true) + end + + slider.OnMouseReleased = function(s) + s.dragging = false + s:MouseCapture(false) + end + + slider.Think = function(s) + if s.dragging then + local x, _ = s:CursorPos() + local fraction = math.Clamp(x / s:GetWide(), 0, 1) + local newValue = s.min + fraction * (s.max - s.min) + newValue = math.Round(newValue, s.decimals) + + if newValue != s.value then + s.value = newValue + valueLabel:SetText(tostring(newValue)) + valueLabel:SizeToContents() + ix.option.Set(key, newValue) + end + end + end + + yOffset = yOffset + sliderHeight + settingGap + + elseif data.type == ix.type.bool then + -- Чекбокс (Group 24) + local checkW = ScaleSize(34) + local checkH = ScaleSize(17) + local checkX = ScaleSize(1166) + local checkY = yOffset + ScaleSize(4) + + -- Текст состояния + local stateLabel = vgui.Create("DLabel", settingsContainer) + stateLabel:SetPos(ScaleSize(1041), yOffset) + stateLabel:SetFont("F4Menu_RulesTitle") + stateLabel:SetText(value and "Включено" or "Выключено") + stateLabel:SetTextColor(ColorAlpha(color_white, 165)) + stateLabel:SizeToContents() + + -- Чекбокс + local checkbox = vgui.Create("DButton", settingsContainer) + checkbox:SetPos(checkX, checkY) + checkbox:SetSize(checkW, checkH) + checkbox:SetText("") + checkbox.checked = value + checkbox.Paint = function(s, w, h) + if s.checked then + -- Включено (transform: rotate(180deg)) + -- Ручка слева + draw.RoundedBox(20, 0, 0, ScaleSize(17), ScaleSize(17), Color(1, 67, 29)) -- #01431D + -- Фон справа + draw.RoundedBoxEx(10, ScaleSize(14), ScaleSize(3), ScaleSize(20), ScaleSize(11), ColorAlpha(Color(56, 84, 45), 64), false, true, false, true) + else + -- Выключено + -- Ручка справа + draw.RoundedBox(20, ScaleSize(17), 0, ScaleSize(17), ScaleSize(17), ColorAlpha(color_white, 165)) + -- Фон слева + draw.RoundedBoxEx(10, 0, ScaleSize(3), ScaleSize(20), ScaleSize(11), ColorAlpha(color_white, 64), true, false, true, false) + end + end + checkbox.DoClick = function(s) + s.checked = not s.checked + stateLabel:SetText(s.checked and "Включено" or "Выключено") + stateLabel:SizeToContents() + ix.option.Set(key, s.checked) + LocalPlayer():EmitSound("buttons/button15.wav", 75, s.checked and 120 or 100, 0.3) + end + + yOffset = yOffset + ScaleSize(24) + settingGap + + elseif data.type == ix.type.array then + -- Выпадающий список (Frame 25) + local comboW = ScaleSize(244) + local comboH = ScaleSize(34) + local comboX = ScaleSize(955) + local comboY = yOffset - ScaleSize(5) + + local combo = vgui.Create("DComboBox", settingsContainer) + combo:SetPos(comboX, comboY) + combo:SetSize(comboW, comboH) + combo:SetFont("F4Menu_RulesTitle") + combo:SetTextColor(color_white) + + combo.Paint = function(s, w, h) + -- background: #1D1C1C; border: 1px solid #01431D; + draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + combo.DropButton.Paint = function(s, w, h) + -- Arrow 1 (Stroke) - стрелка вниз + surface.SetDrawColor(Color(1, 67, 29)) + local cx, cy = w/2, h/2 + surface.DrawLine(cx - 4, cy - 2, cx, cy + 2) + surface.DrawLine(cx, cy + 2, cx + 4, cy - 2) + end + + if isfunction(data.populate) then + local entries = data.populate() + for k, v in pairs(entries) do + combo:AddChoice(v, k, k == value) + end + end + + combo.OnSelect = function(s, index, val, optionData) + ix.option.Set(key, optionData) + LocalPlayer():EmitSound("buttons/button15.wav", 75, 100, 0.3) + end + + yOffset = yOffset + comboH + settingGap + + elseif data.type == ix.type.string then + -- Текстовое поле + local entryW = ScaleSize(244) + local entryH = ScaleSize(34) + local entryX = ScaleSize(955) + local entryY = yOffset - ScaleSize(5) + + local entry = vgui.Create("DTextEntry", settingsContainer) + entry:SetPos(entryX, entryY) + entry:SetSize(entryW, entryH) + entry:SetFont("F4Menu_RulesTitle") + entry:SetValue(value) + entry:SetTextColor(color_white) + entry.Paint = function(s, w, h) + draw.RoundedBox(7, 0, 0, w, h, Color(29, 28, 28)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(color_white, Color(1, 67, 29), color_white) + end + entry.OnEnter = function(s) + ix.option.Set(key, s:GetValue()) + LocalPlayer():EmitSound("buttons/button15.wav", 75, 100, 0.3) + end + + yOffset = yOffset + entryH + settingGap + else + -- Остальные типы (по умолчанию как текст) + yOffset = yOffset + ScaleSize(24) + settingGap + end + end + end +end + +function PLUGIN:CreateSquadsTab() + if not IsValid(self.contentPanel) then return end + self.contentPanel:Clear() + + local squadsPlugin = ix.plugin.Get("squads") + if not squadsPlugin then + local errorLabel = vgui.Create("DLabel", self.contentPanel) + errorLabel:SetPos(20, 20) + errorLabel:SetFont("F4Menu_Category") + errorLabel:SetTextColor(Color(255, 100, 100)) + errorLabel:SetText("Система отрядов не загружена") + errorLabel:SizeToContents() + return + end + + -- Безопасное получение данных отряда + local squad = squadsPlugin.currentSquad + local isInSquad = squad ~= nil + local isLeader = false + + if squad and squad.leader then + isLeader = (squad.leader == LocalPlayer():SteamID()) + end + + -- Заголовок + local headerPanel = vgui.Create("DPanel", self.contentPanel) + headerPanel:SetPos(ScalePos(12), ScalePos(12)) + headerPanel:SetSize(ScaleSize(1475), ScaleSize(60)) + headerPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) + surface.SetDrawColor(30, 30, 35) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + draw.SimpleText("СИСТЕМА ОТРЯДОВ", "F4Menu_DonateHero", w/2, h/2, Color(1, 67, 29), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + if not isInSquad then + -- Панель создания отряда + local createPanel = vgui.Create("DPanel", self.contentPanel) + createPanel:SetPos(ScalePos(12), ScalePos(85)) + createPanel:SetSize(ScaleSize(1475), ScaleSize(200)) + createPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) + surface.SetDrawColor(30, 30, 35) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local infoLabel = vgui.Create("DLabel", createPanel) + infoLabel:SetPos(0, ScalePos(40)) + infoLabel:SetSize(createPanel:GetWide(), ScaleSize(30)) + infoLabel:SetFont("F4Menu_Category") + infoLabel:SetTextColor(Color(200, 200, 200)) + infoLabel:SetText("Вы не состоите в отряде") + infoLabel:SetContentAlignment(5) + + local createBtn = vgui.Create("DButton", createPanel) + createBtn:SetPos((createPanel:GetWide() - ScaleSize(300)) / 2, ScalePos(100)) + createBtn:SetSize(ScaleSize(300), ScaleSize(50)) + createBtn:SetText("") + createBtn.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) + draw.SimpleText("Создать отряд", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + createBtn.DoClick = function() + print("[SQUADS CLIENT] Нажата кнопка создания отряда") + + net.Start("ixSquadCreate") + net.SendToServer() + + print("[SQUADS CLIENT] Сетевое сообщение отправлено") + + timer.Simple(0.5, function() + if IsValid(self.f4Menu) then + print("[SQUADS CLIENT] Обновление вкладки отрядов") + self:SwitchTab("Отряды") + end + end) + end + + -- Информация + local infoText = vgui.Create("DLabel", self.contentPanel) + infoText:SetPos(ScalePos(12), ScalePos(300)) + infoText:SetSize(ScaleSize(1475), ScaleSize(300)) + infoText:SetFont("F4Menu_Item") + infoText:SetTextColor(Color(150, 150, 150)) + infoText:SetText("• Максимум 16 человек в отряде\n• Приглашать можно только союзников из вашей фракции\n• Члены отряда видят позиции друг друга на компасе\n• Лидер может управлять составом отряда") + infoText:SetWrap(true) + infoText:SetContentAlignment(7) + else + -- Информация об отряде + local infoPanel = vgui.Create("DPanel", self.contentPanel) + infoPanel:SetPos(ScalePos(12), ScalePos(85)) + infoPanel:SetSize(ScaleSize(1475), ScaleSize(100)) + infoPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) + surface.SetDrawColor(1, 67, 29) + surface.DrawRect(0, 0, w, 3) + + local memberCount = squad and #squad.members or 0 + local maxMembers = ix.config.Get("squadMaxMembers", 16) + local leaderName = squad and squad.memberData[squad.leader] and squad.memberData[squad.leader].name or "Неизвестно" + + draw.SimpleText("Лидер: " .. leaderName, "F4Menu_Category", ScalePos(30), h/2 - ScalePos(15), Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText("Состав: " .. memberCount .. "/" .. maxMembers, "F4Menu_Category", ScalePos(30), h/2 + ScalePos(15), Color(200, 200, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + if isLeader then + draw.SimpleText("Вы лидер отряда", "F4Menu_Item", w - ScalePos(30), h/2, Color(0, 255, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + + -- Список членов отряда + local membersScroll = vgui.Create("DScrollPanel", self.contentPanel) + membersScroll:SetPos(ScalePos(12), ScalePos(200)) + membersScroll:SetSize(ScaleSize(1475), ScaleSize(400)) + membersScroll.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) + end + + local vbar = membersScroll:GetVBar() + vbar:SetWide(ScaleSize(8)) + function vbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(15, 15, 17)) + end + function vbar.btnUp:Paint(w, h) end + function vbar.btnDown:Paint(w, h) end + function vbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29)) + end + + if squad and squad.members then + for i, memberSteamID in ipairs(squad.members) do + local memberData = squad.memberData[memberSteamID] + if memberData then + local memberPanel = vgui.Create("DPanel", membersScroll) + memberPanel:Dock(TOP) + memberPanel:DockMargin(ScalePos(10), ScalePos(5), ScalePos(10), ScalePos(5)) + memberPanel:SetTall(ScaleSize(60)) + memberPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(25, 25, 28)) + surface.SetDrawColor(40, 40, 45) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + local statusColor = Color(100, 255, 100) + surface.SetDrawColor(statusColor) + surface.DrawRect(0, 0, 4, h) + + local nameText = memberData.name + if memberData.isLeader then + nameText = nameText .. " [ЛИДЕР]" + end + + draw.SimpleText(nameText, "F4Menu_Category", ScalePos(20), h/2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + local joinDate = os.date("%d.%m.%Y %H:%M", memberData.joinTime) + draw.SimpleText("Присоединился: " .. joinDate, "F4Menu_InfoSmall", ScalePos(20), h/2 + ScalePos(20), Color(150, 150, 150), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + -- Кнопка исключения (только для лидера и не для себя) + if isLeader and memberSteamID ~= LocalPlayer():SteamID() then + local kickBtn = vgui.Create("DButton", memberPanel) + kickBtn:SetPos(memberPanel:GetWide() + ScaleSize(250), ScalePos(15)) + kickBtn:SetSize(ScaleSize(130), ScaleSize(30)) + kickBtn:SetText("") + kickBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) + draw.SimpleText("Исключить", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + kickBtn.DoClick = function() + net.Start("ixSquadKick") + net.WriteString(memberSteamID) + net.SendToServer() + + timer.Simple(0.5, function() + if IsValid(self.f4Menu) then + self:SwitchTab("Отряды") + end + end) + end + end + end + end + end + + -- Панель управления + local controlPanel = vgui.Create("DPanel", self.contentPanel) + controlPanel:SetPos(ScalePos(12), ScalePos(615)) + controlPanel:SetSize(ScaleSize(1475), ScaleSize(80)) + controlPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) + end + + if isLeader then + -- Кнопка приглашения + local inviteBtn = vgui.Create("DButton", controlPanel) + inviteBtn:SetPos(ScalePos(20), ScalePos(15)) + inviteBtn:SetSize(ScaleSize(350), ScaleSize(50)) + inviteBtn:SetText("") + inviteBtn.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) + draw.SimpleText("Пригласить игрока", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + inviteBtn.DoClick = function() + self:OpenInviteMenu() + end + + -- Кнопка расформирования + local disbandBtn = vgui.Create("DButton", controlPanel) + disbandBtn:SetPos(ScalePos(390), ScalePos(15)) + disbandBtn:SetSize(ScaleSize(350), ScaleSize(50)) + disbandBtn:SetText("") + disbandBtn.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) + draw.SimpleText("Расформировать отряд", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + disbandBtn.DoClick = function() + Derma_Query( + "Вы уверены, что хотите расформировать отряд?", + "Подтверждение", + "Да", + function() + net.Start("ixSquadDisband") + net.SendToServer() + + timer.Simple(0.5, function() + if IsValid(self.f4Menu) then + self:SwitchTab("Отряды") + end + end) + end, + "Нет" + ) + end + else + -- Кнопка выхода + local leaveBtn = vgui.Create("DButton", controlPanel) + leaveBtn:SetPos((controlPanel:GetWide() - ScaleSize(350)) / 2, ScalePos(15)) + leaveBtn:SetSize(ScaleSize(350), ScaleSize(50)) + leaveBtn:SetText("") + leaveBtn.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) + draw.SimpleText("Покинуть отряд", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + leaveBtn.DoClick = function() + Derma_Query( + "Вы уверены, что хотите покинуть отряд?", + "Подтверждение", + "Да", + function() + net.Start("ixSquadLeave") + net.SendToServer() + + timer.Simple(0.5, function() + if IsValid(self.f4Menu) then + self:SwitchTab("Отряды") + end + end) + end, + "Нет" + ) + end + end + end +end + +-- Меню приглашения игроков +function PLUGIN:OpenInviteMenu() + local frame = vgui.Create("DFrame") + frame:SetSize(ScaleSize(600), ScaleSize(500)) + frame:Center() + frame:SetTitle("") + frame:SetDraggable(true) + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) + surface.SetDrawColor(1, 67, 29) + surface.DrawRect(0, 0, w, 3) + draw.SimpleText("Выберите игрока", "F4Menu_Category", w/2, ScalePos(25), Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(frame:GetWide() - ScaleSize(35), ScalePos(5)) + closeBtn:SetSize(ScaleSize(30), ScaleSize(30)) + closeBtn:SetText("✕") + closeBtn:SetFont("F4Menu_Category") + closeBtn:SetTextColor(Color(255, 255, 255)) + closeBtn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(0, 0, 0, 0)) + end + closeBtn.DoClick = function() + frame:Close() + end + + local playerList = vgui.Create("DScrollPanel", frame) + playerList:SetPos(ScalePos(10), ScalePos(50)) + playerList:SetSize(frame:GetWide() - ScaleSize(20), frame:GetTall() - ScaleSize(60)) + + local vbar = playerList:GetVBar() + vbar:SetWide(ScaleSize(8)) + function vbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(15, 15, 17)) + end + function vbar.btnUp:Paint(w, h) end + function vbar.btnDown:Paint(w, h) end + function vbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29)) + end + + local myChar = LocalPlayer():GetCharacter() + if not myChar then return end + local myFaction = myChar:GetFaction() + + for _, ply in ipairs(player.GetAll()) do + if ply ~= LocalPlayer() and IsValid(ply) then + local char = ply:GetCharacter() + if char and char:GetFaction() == myFaction then + local plyPanel = vgui.Create("DButton", playerList) + plyPanel:Dock(TOP) + plyPanel:DockMargin(ScalePos(5), ScalePos(5), ScalePos(5), 0) + plyPanel:SetTall(ScaleSize(50)) + plyPanel:SetText("") + plyPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(35, 35, 38) or Color(25, 25, 28)) + draw.SimpleText(ply:Name(), "F4Menu_Category", ScalePos(15), h/2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText("Пригласить →", "F4Menu_Item", w - ScalePos(15), h/2, Color(1, 67, 29), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + plyPanel.DoClick = function() + print("[F4MENU] Отправка приглашения игроку: " .. ply:Name() .. " (SteamID: " .. ply:SteamID() .. ")") + net.Start("ixSquadInvite") + net.WriteString(ply:SteamID()) + net.SendToServer() + frame:Close() + end + end + end + end +end + +-- Открытие меню по F4 с задержкой +function PLUGIN:PlayerButtonDown(ply, button) + if button == KEY_F4 and ply == LocalPlayer() then + local currentTime = CurTime() + if currentTime - self.lastF4Press >= self.f4Cooldown then + self.lastF4Press = currentTime + + if IsValid(self.f4Menu) then + self.f4Menu:Remove() + else + self:CreateF4Menu() + end + end + end +end + +-- Закрытие меню при смерти +function PLUGIN:OnCharacterDeleted(character) + if IsValid(self.f4Menu) then + self.f4Menu:Remove() + end +end + +-- Обновление баланса при изменении +function PLUGIN:CharacterMoneyChanged(character, money) + if IsValid(self.f4Menu) and character == LocalPlayer():GetCharacter() then + self:SwitchTab("Информация") + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_donate_config.lua b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_donate_config.lua new file mode 100644 index 0000000..da9922e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_donate_config.lua @@ -0,0 +1,253 @@ +local PLUGIN = PLUGIN + +PLUGIN.donateCatalog = PLUGIN.donateCatalog or { + { + id = "currency", + name = "Рубли", + tagline = "Пополнение игрового баланса", + accent = { r = 1, g = 104, b = 44 }, + items = { + { + id = "money_1000", + title = "1.000 ₽", + price = 50, + currency = "IGS", + perks = { + "Пополнение игрового баланса", + "Мгновенное зачисление", + "Использование в магазинах" + }, + reward = { + type = "money", + amount = 1000 + } + }, + { + id = "money_5000", + title = "5.000 ₽", + price = 225, + currency = "IGS", + perks = { + "Пополнение игрового баланса", + "Мгновенное зачисление", + "Использование в магазинах", + "Выгодно: +10% бонус" + }, + tag = "ВЫГОДНО", + reward = { + type = "money", + amount = 5000 + } + }, + { + id = "money_10000", + title = "10.000 ₽", + price = 400, + currency = "IGS", + perks = { + "Пополнение игрового баланса", + "Мгновенное зачисление", + "Использование в магазинах", + "Выгодно: +20% бонус" + }, + tag = "ХИТ", + reward = { + type = "money", + amount = 10000 + } + }, + { + id = "money_25000", + title = "25.000 ₽", + price = 900, + currency = "IGS", + perks = { + "Пополнение игрового баланса", + "Мгновенное зачисление", + "Использование в магазинах", + "Максимально выгодно: +28% бонус" + }, + tag = "ЛУЧШЕЕ", + reward = { + type = "money", + amount = 25000 + } + } + } + }, + { + id = "weapons", + name = "Оружие", + tagline = "Донатное оружие в ваш арсенал", + accent = { r = 1, g = 104, b = 44 }, + items = { + { id = "ak12", title = "AK-12", price1Month = 1200, price3Month = 3200, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "tacrp_ak_ak12", name = "AK-12" } }, + { id = "mdr", title = "Desert Tech MDR", price1Month = 1500, price3Month = 4000, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_mdr", name = "MDR" } }, + { id = "mp7a1", title = "MP7A1", price1Month = 1300, price3Month = 3500, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_mp7a1", name = "MP7A1" } }, + { id = "ai_axmc", title = "AI AXMC", price1Month = 2000, price3Month = 5500, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ai_axmc", name = "AI AXMC" } }, + { id = "t5000", title = "T-5000", price1Month = 1900, price3Month = 5200, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_t5000", name = "T-5000" } }, + { id = "saiga12k", title = "Сайга-12К", price1Month = 1400, price3Month = 3800, currency = "IGS", perks = { "Автоматический дробовик", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_saiga12k", name = "Сайга-12К" } }, + { id = "deagle", title = "Desert Eagle XIX", price1Month = 800, price3Month = 2200, currency = "IGS", perks = { "Мощный пистолет", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_deagle_xix", name = "Desert Eagle" } }, + { id = "apb", title = "АПБ", price1Month = 700, price3Month = 1900, currency = "IGS", perks = { "Бесшумный пистолет", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_apb", name = "АПБ" } }, + { id = "spear", title = "SPEAR", price1Month = 1600, price3Month = 4400, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_spear", name = "SPEAR" } }, + { id = "sr25", title = "SR-25", price1Month = 1800, price3Month = 4900, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_sr25", name = "SR-25" } }, + { id = "dvl10", title = "ДВЛ-10", price1Month = 1900, price3Month = 5200, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_dvl10", name = "ДВЛ-10" } }, + { id = "hultafors", title = "Dead Blow Hammer", price1Month = 300, price3Month = 800, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_hultafors", name = "Hammer" } }, + { id = "cultist", title = "Cultist Knife", price1Month = 400, price3Month = 1100, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_cultist", name = "Cultist Knife" } }, + { id = "akula", title = "Akula", price1Month = 350, price3Month = 950, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_akula", name = "Akula" } }, + { id = "crash", title = "Crash Axe", price1Month = 350, price3Month = 950, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_crash", name = "Crash Axe" } }, + { id = "kukri", title = "Kukri", price1Month = 400, price3Month = 1100, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_kukri", name = "Kukri" } }, + { id = "ak50", title = "AK-50", price1Month = 2200, price3Month = 6000, currency = "IGS", perks = { "Мощная штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, tag = "ХИТ", reward = { type = "weapon", weaponClass = "arc9_eft_ak50", name = "AK-50" } }, + { id = "vss", title = "ВСС Винторез", price1Month = 1500, price3Month = 4100, currency = "IGS", perks = { "Бесшумная винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_vss", name = "ВСС" } }, + { id = "mr43", title = "МР-43", price1Month = 1000, price3Month = 2700, currency = "IGS", perks = { "Дробовик", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_mr43", name = "МР-43" } }, + { id = "rpk16", title = "РПК-16", price1Month = 1700, price3Month = 4600, currency = "IGS", perks = { "Ручной пулемёт", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_rpk16", name = "РПК-16" } }, + { id = "rshg2", title = "РШГ-2", price1Month = 2500, price3Month = 6800, currency = "IGS", perks = { "Гранатомёт", "Доступен в арсенале", "Кулдаун 10 минут" }, tag = "РЕДКОЕ", reward = { type = "weapon", weaponClass = "arc9_eft_rshg2", name = "РШГ-2" } }, + { id = "ash12", title = "АШ-12", price1Month = 1800, price3Month = 4900, currency = "IGS", perks = { "Штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ash12", name = "АШ-12" } }, + { id = "rd704", title = "RD-704", price1Month = 1400, price3Month = 3800, currency = "IGS", perks = { "Штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_rd704", name = "RD-704" } }, + { id = "ppsh41", title = "ППШ-41", price1Month = 900, price3Month = 2400, currency = "IGS", perks = { "Легендарный ПП", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ppsh41", name = "ППШ-41" } } + } + }, + { + id = "other", + name = "Другое", + tagline = "Дополнительные возможности", + accent = { r = 1, g = 104, b = 44 }, + items = { + { + id = "voice_chat", + title = "Говорилка", + price = 500, + currency = "IGS", + perks = { + "Разблокировка голосового чата", + "Радиус слышимости 200м", + "Без ограничений по времени", + "Мгновенная активация" + }, + reward = { + type = "voice_chat", + successMessage = "Голосовой чат разблокирован!" + } + } + } + }, + { + id = "privileges", + name = "Привелегии", + tagline = "VIP статусы и возможности", + accent = { r = 1, g = 104, b = 44 }, + items = { + { + id = "vip", + title = "VIP", + price1Month = 300, + price3Month = 800, + currency = "IGS", + perks = { + "Цветной ник и иконка VIP в чате", + "Приоритет при входе на сервер", + "Скидка 10% в арсенале", + "Доступ к VIP-командам" + }, + reward = { + type = "privilege", + tier = "vip" + } + }, + { + id = "vip_plus", + title = "VIP+", + price1Month = 500, + price3Month = 1400, + currency = "IGS", + perks = { + "Все преимущества VIP", + "Увеличенная зарплата (+15%)", + "Скидка 15% в арсенале", + "Доступ к уникальным моделям", + "Еженедельный бонус валюты" + }, + reward = { + type = "privilege", + tier = "vip_plus" + } + }, + { + id = "premium", + title = "Premium", + price1Month = 800, + price3Month = 2200, + currency = "IGS", + perks = { + "Все преимущества VIP+", + "Увеличенная зарплата (+25%)", + "Скидка 20% в арсенале", + "Приоритетный спавн техники", + "Доступ к Premium-командам", + "Уникальные анимации и эффекты" + }, + tag = "ХИТ", + reward = { + type = "privilege", + tier = "premium" + } + }, + { + id = "sponsor", + title = "Спонсор", + price1Month = 1300, + price3Month = 3600, + currency = "IGS", + perks = { + "Доступ к личному Discord-серверу разработчиков проекта с различной информацией, включая оповещения о свежих обновлениях", + "Увеличенный доход за убийство противника на сервере", + "Быстрый захват точки противника (в 1,5 раза быстрее)", + "Уменьшенное ограничение (по времени) на выкат техники", + "Постоянно действующие скидки в 20% на покупку доната (НЕ ЧЕРЕЗ АВТО-ДОНАТ)", + "Уникальные возможности на сервере", + "Участие в закрытых бета-тестированиях и голосованиях" + }, + tag = "ЭКСКЛЮЗИВ", + reward = { + type = "privilege", + tier = "sponsor" + } + } + } + } +} + +local function BuildDonateLookup() + PLUGIN.donateItemsByID = {} + for _, category in ipairs(PLUGIN.donateCatalog or {}) do + for _, item in ipairs(category.items or {}) do + PLUGIN.donateItemsByID[item.id] = item + end + end +end + +BuildDonateLookup() + +function PLUGIN:GetDonateCatalog() + return self.donateCatalog or {} +end + +function PLUGIN:GetDonateCategory(identifier) + if not identifier then return end + + for _, category in ipairs(self:GetDonateCatalog()) do + if category.id == identifier then + return category + end + end +end + +function PLUGIN:GetDonateProduct(identifier) + if not identifier then return end + + if not self.donateItemsByID then + BuildDonateLookup() + end + + return self.donateItemsByID[identifier] +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_plugin.lua new file mode 100644 index 0000000..fe58705 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_plugin.lua @@ -0,0 +1,36 @@ +PLUGIN.name = "F4 Menu" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "F4 Menu for Military RP" + +-- Ссылки на социальные сети +PLUGIN.socialLinks = { + discord = "https://discord.gg/paQdrP7aD7", + steam = "https://steamcommunity.com/sharedfiles/filedetails/?id=3630170114", + tiktok = "https://www.tiktok.com/@front.team0?_t=ZS-8zQYdHdzDB1&_r=1", + telegram = "https://t.me/frontteamproject" +} + +-- Материалы иконок социальных сетей +PLUGIN.socialIcons = { + discord = "materials/ft_ui/military/vnu/f4menu/icons/discord.png", + steam = "materials/ft_ui/military/vnu/f4menu/icons/steam.png", + tiktok = "materials/ft_ui/military/vnu/f4menu/icons/tiktok.png", + telegram = "materials/ft_ui/military/vnu/f4menu/icons/telegram.png" +} + +ix.util.Include("sh_donate_config.lua") +ix.util.Include("sh_thanks_config.lua") +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") + +ix.command.Add("Donate", { + description = "Открыть меню доната (IGS)", + alias = {"Donat", "Донат"}, + OnRun = function(self, client) + if (CLIENT) then + RunConsoleCommand("igs") + else + client:ConCommand("igs") + end + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_thanks_config.lua b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_thanks_config.lua new file mode 100644 index 0000000..920f358 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sh_thanks_config.lua @@ -0,0 +1,181 @@ +local PLUGIN = PLUGIN + +PLUGIN.thanksPages = PLUGIN.thanksPages or { + { + id = "founders", + title = "Руководители", + people = { + { + name = "Oleg Zakon", + model = "models/player/gman_high.mdl", + sequence = "pose_standing_01", + description = "Основатель и идейный вдохновитель Front Team. Отвечает за общее развитие проекта, стратегию и ключевые решения. Следит за качеством контента, стабильностью сервера и взаимодействием с сообществом.", + link = "https://steamcommunity.com/profiles/76561198204118180" + }, + { + name = "Бугор", + model = "models/player/breen.mdl", + sequence = "pose_standing_01", + description = "Правая рука владельца. Курирует техническую часть, развитие игровых механик и поддержку проекта. Обеспечивает бесперебойную работу сервера и помогает в реализации идей сообщества.", + link = "https://steamcommunity.com/profiles/76561199186141452" + } + } + }, + { + id = "developers", + title = "Разработчики", + people = { + + { + name = "Биба", + model = "models/player/police.mdl", + sequence = "pose_ducking_01", + description = "Настройщик игровых механик", + link = "https://steamcommunity.com/profiles/76561199189433293" + }, + { + name = "Scripty", + model = "models/player/skeleton.mdl", + sequence = "pose_standing_04", + description = "Разработчик", + link = "https://steamcommunity.com/profiles/76561198164572709" + }, + { + name = "ItzTomber", + model = "models/player/kleiner.mdl", + sequence = "pose_standing_04", + description = "Разработчик", + link = "https://steamcommunity.com/profiles/76561198341626975" + }, + { + name = "Hari", + model = "models/player/magnusson.mdl", + sequence = "pose_standing_02", + description = "Разработчик.", + link = "https://steamcommunity.com/profiles/76561198201651767" + }, + { + name = "Refosel", + model = "models/kemono_friends/ezo_red_fox/ezo_red_fox_player.mdl", + sequence = "pose_standing_01", + description = "Главный Разработчик", + link = "https://steamcommunity.com/profiles/76561198393073512" + }, + { + name = "Прохор", + model = "models/player/odessa.mdl", + sequence = "pose_standing_02", + description = "Маппер.", + link = "no link" + }, + { + name = "Сварщик", + model = "models/player/eli.mdl", + sequence = "pose_standing_04", + description = "Модделер.", + link = "https://steamcommunity.com/profiles/76561198440513870" + }, + { + name = "Козырный", + model = "models/player/police.mdl", + sequence = "pose_ducking_01", + description = "Figma дизайнер.", + link = "https://steamcommunity.com/profiles/76561199077227396" + } + } + }, + { + id = "helix", + title = "Helix", + people = { + { + name = "NutScript", + model = "models/player/police.mdl", + sequence = "pose_ducking_01", + description = "Спасибо за Helix.", + link = "https://github.com/NutScript/NutScript" + }, + { + name = "Alex Grist", + model = "models/player/police.mdl", + sequence = "pose_ducking_01", + description = "Спасибо за Helix.", + link = "https://steamcommunity.com/profiles/76561197979205163" + }, + { + name = "Igor Radovanovic", + model = "models/player/combine_soldier.mdl", + sequence = "pose_standing_04", + description = "Спасибо за Helix.", + link = "https://steamcommunity.com/profiles/76561197990111113" + }, + { + name = "Jaydawg", + model = "models/player/combine_soldier_prisonguard.mdl", + sequence = "pose_standing_02", + description = "Спасибо за Helix.", + link = "https://steamcommunity.com/profiles/76561197970371430" + }, + { + name = "nebulous", + model = "models/player/combine_super_soldier.mdl", + sequence = "pose_standing_01", + description = "Спасибо за Helix.", + link = "https://github.com/NebulousCloud" + }, + { + name = "Black Tea", + model = "models/player/combine_soldier_prisonguard.mdl", + sequence = "pose_standing_02", + description = "Спасибо за Helix.", + link = "https://steamcommunity.com/profiles/76561197999893894" + }, + { + name = "Rain GBizzle", + model = "models/player/combine_soldier.mdl", + sequence = "pose_standing_04", + description = "Спасибо за Helix.", + link = "https://steamcommunity.com/profiles/76561198036111376" + }, + { + name = "Luna", + model = "models/player/police.mdl", + sequence = "pose_ducking_01", + description = "Спасибо за Helix.", + link = "https://steamcommunity.com/profiles/76561197988658543" + }, + { + name = "Contributors", + model = "models/player/police.mdl", + sequence = "pose_ducking_01", + description = "Спасибо за Helix.", + link = "https://github.com/NebulousCloud/helix/graphs/contributors" + } + } + } +} + +function PLUGIN:GetThanksPages() + return self.thanksPages or {} +end + +function PLUGIN:GetThanksPage(identifier) + if not identifier then return end + + for _, page in ipairs(self:GetThanksPages()) do + if page.id == identifier then + return page + end + end +end + +function PLUGIN:GetThanksPerson(pageID, personName) + local page = self:GetThanksPage(pageID) + if not page then return end + + for _, person in ipairs(page.people or {}) do + if person.name == personName then + return person + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/f4menu/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sv_plugin.lua new file mode 100644 index 0000000..89fbabd --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/f4menu/sv_plugin.lua @@ -0,0 +1,667 @@ +local PLUGIN = PLUGIN + +util.AddNetworkString("ix.F4_RequestInfo") +util.AddNetworkString("ix.F4_SendInfo") +util.AddNetworkString("ix.F4_DonatePurchase") +util.AddNetworkString("ix.F4_DonatePurchaseResult") +util.AddNetworkString("ixChangeName") +util.AddNetworkString("ixF4UpdateName") + +local function GetFactionCounters(factionID, podIndex, specIndex) + local factionOnline, samePod, sameSpec = 0, 0, 0 + + for _, ply in ipairs(player.GetAll()) do + if not IsValid(ply) then continue end + if ply:Team() ~= factionID then continue end + + factionOnline = factionOnline + 1 + + local char = ply:GetCharacter() + if not char then continue end + + if char:GetPodr() == podIndex then + samePod = samePod + 1 + end + + if char:GetSpec() == specIndex then + sameSpec = sameSpec + 1 + end + end + + return factionOnline, samePod, sameSpec +end + +local function BuildInfoPayload(client) + local char = client:GetCharacter() + if not char then return end + + local factionID = char:GetFaction() + local factionTable = ix.faction.Get(factionID) + local podIndex = char:GetPodr() + local specIndex = char:GetSpec() + local rankIndex = char:GetRank() or 1 + + local factionOnline, samePod, sameSpec = GetFactionCounters(factionID, podIndex, specIndex) + + local vehiclesPlugin = ix.plugin.Get("vehicles") + local arsenalPlugin = ix.plugin.Get("arsenal") + + local techPoints = 0 + if vehiclesPlugin and vehiclesPlugin.GetFactionPoints then + techPoints = vehiclesPlugin:GetFactionPoints(factionID) or 0 + end + + local supplyPoints = 0 + if arsenalPlugin and arsenalPlugin.GetFactionSupply then + supplyPoints = arsenalPlugin:GetFactionSupply(factionID) or 0 + end + + local activeVehicles = 0 + if vehiclesPlugin and vehiclesPlugin.activeVehicles then + for _, data in pairs(vehiclesPlugin.activeVehicles) do + if not istable(data) then continue end + if data.faction == factionID then + activeVehicles = activeVehicles + 1 + end + end + end + + local subdivisionData = { + index = podIndex, + name = "Не назначено", + preset = {}, + specDefault = 1, + members = samePod, + model = "", + skin = 0, + bodygroups = {} + } + + local specData = { + index = specIndex, + name = "Не назначено", + weapons = {}, + members = sameSpec, + podr = 0 + } + + local rankData = { + index = rankIndex, + name = "Без звания" + } + + if factionTable then + if factionTable.Podr and factionTable.Podr[podIndex] then + local unit = factionTable.Podr[podIndex] + subdivisionData.name = unit.name or subdivisionData.name + subdivisionData.preset = unit.preset or subdivisionData.preset + subdivisionData.specDefault = unit.spec_def or subdivisionData.specDefault + subdivisionData.model = unit.model or subdivisionData.model + subdivisionData.skin = unit.skin or subdivisionData.skin + subdivisionData.bodygroups = unit.bodygroups or subdivisionData.bodygroups + end + + if factionTable.Spec and factionTable.Spec[specIndex] then + local spec = factionTable.Spec[specIndex] + specData.name = spec.name or specData.name + specData.weapons = spec.weapons or specData.weapons + specData.podr = spec.podr or specData.podr + end + + if factionTable.Ranks and factionTable.Ranks[rankIndex] then + local rank = factionTable.Ranks[rankIndex] + rankData.name = rank[1] or rankData.name + end + end + + local factionColor = nil + if factionTable and factionTable.color then + factionColor = { r = factionTable.color.r, g = factionTable.color.g, b = factionTable.color.b } + end + + local supplyStatus = "Стабильно" + local minSupply = 0 + if arsenalPlugin and arsenalPlugin.config then + minSupply = arsenalPlugin.config.minSupply or 0 + end + + if supplyPoints <= minSupply then + supplyStatus = "Критически низко" + elseif supplyPoints <= minSupply * 2 and minSupply > 0 then + supplyStatus = "Низко" + end + + return { + timestamp = os.time(), + faction = { + id = factionID, + name = factionTable and factionTable.name or "Неизвестно", + description = factionTable and factionTable.description or "", + color = factionColor, + online = factionOnline, + techPoints = techPoints, + supplyPoints = supplyPoints, + supplyStatus = supplyStatus, + activeVehicles = activeVehicles + }, + character = { + name = char:GetName(), + money = char:GetMoney(), + id = char:GetID() or 0, + rank = rankData, + subdivision = subdivisionData, + spec = specData + } + } +end + +net.Receive("ix.F4_RequestInfo", function(_, client) + if not IsValid(client) then return end + local payload = BuildInfoPayload(client) + if not payload then return end + + net.Start("ix.F4_SendInfo") + net.WriteTable(payload) + net.Send(client) +end) + +local DONATE_PURCHASE_COOLDOWN = 2 +PLUGIN.donatePurchaseCooldowns = PLUGIN.donatePurchaseCooldowns or {} + +local function GetCooldownKey(client) + return client:SteamID64() or client:SteamID() or client:EntIndex() +end + +function PLUGIN:GetIGSBalance(client) + if not IsValid(client) then return 0 end + + if client.IGSFunds then + local ok, funds = pcall(client.IGSFunds, client) + if ok and isnumber(funds) then + return math.max(math.floor(funds), 0) + end + end + + if client.GetNetVar then + local funds = client:GetNetVar("igsFunds", 0) + if isnumber(funds) then + return math.max(math.floor(funds), 0) + end + end + + return 0 +end + +function PLUGIN:AdjustIGSBalance(client, amount) + if not IsValid(client) then return false, "Игрок недоступен" end + amount = tonumber(amount) or 0 + if amount == 0 then return true end + + if not client.AddIGSFunds then + return false, "IGS недоступен" + end + + local ok, err = pcall(client.AddIGSFunds, client, amount) + if ok then + return true + end + + return false, err or "Ошибка IGS" +end + +function PLUGIN:IsDonateOnCooldown(client) + local key = GetCooldownKey(client) + local expires = self.donatePurchaseCooldowns[key] or 0 + if CurTime() < expires then + return true + end +end + +function PLUGIN:ArmDonateCooldown(client) + local key = GetCooldownKey(client) + self.donatePurchaseCooldowns[key] = CurTime() + DONATE_PURCHASE_COOLDOWN +end + +function PLUGIN:CanPurchaseDonateProduct(client, entry) + local char = client:GetCharacter() + if not char then + return false, "Нет активного персонажа" + end + + if not entry then + return false, "Предложение не найдено" + end + + if entry.oneTime then + local history = char:GetData("donate_purchases", {}) + if history[entry.id] then + return false, "Этот набор уже был приобретен" + end + end + + local hookResult, hookMessage = hook.Run("CanPlayerBuyDonateProduct", client, entry) + if hookResult == false then + return false, hookMessage or "Покупка отклонена" + end + + return true +end + +function PLUGIN:GrantDonateItems(client, grantData) + if not grantData then return end + local char = client:GetCharacter() + if not char then return end + + if istable(grantData.weapons) and #grantData.weapons > 0 then + local owned = char:GetData("donate_weapons", {}) + local changed + for _, class in ipairs(grantData.weapons) do + if isstring(class) and not table.HasValue(owned, class) then + table.insert(owned, class) + changed = true + end + end + if changed then + char:SetData("donate_weapons", owned) + end + end + + if istable(grantData.cosmetics) and #grantData.cosmetics > 0 then + local cosmetics = char:GetData("donate_cosmetics", {}) + for _, cosmeticID in ipairs(grantData.cosmetics) do + if isstring(cosmeticID) then + cosmetics[cosmeticID] = true + end + end + char:SetData("donate_cosmetics", cosmetics) + end + + if istable(grantData.vehicles) and #grantData.vehicles > 0 then + local vehicles = char:GetData("donate_vehicles", {}) + for _, certificate in ipairs(grantData.vehicles) do + if isstring(certificate) then + vehicles[#vehicles + 1] = certificate + end + end + char:SetData("donate_vehicles", vehicles) + end + + if istable(grantData.boosts) and #grantData.boosts > 0 then + local calls = char:GetData("donate_support_calls", {}) + for _, callID in ipairs(grantData.boosts) do + calls[#calls + 1] = callID + end + char:SetData("donate_support_calls", calls) + end + + if istable(grantData.items) and #grantData.items > 0 then + local pending = char:GetData("donate_items", {}) + for _, itemID in ipairs(grantData.items) do + pending[#pending + 1] = { id = itemID, ts = os.time() } + end + char:SetData("donate_items", pending) + end +end + +function PLUGIN:ApplyDonateReward(client, entry, mode) + local char = client:GetCharacter() + if not char then + return false, "Нет активного персонажа" + end + + local reward = entry.reward + if not reward then + local hookResult, hookMessage = hook.Run("OnDonateReward", client, entry, mode) + if hookResult ~= nil then + return hookResult, hookMessage + end + return false, "Награда не настроена" + end + + local rewardType = reward.type + + if rewardType == "privilege" then + local tier = reward.tier or entry.id + local duration = nil + + if mode == "3month" then + duration = os.time() + (90 * 86400) -- 90 дней + else + duration = os.time() + (30 * 86400) -- 30 дней (1 месяц) + end + + local privileges = char:GetData("donate_privileges", {}) + privileges[tier] = { + tier = tier, + expires = duration, + granted = os.time(), + mode = mode or "month" + } + char:SetData("donate_privileges", privileges) + + -- Применяем группу через SAM если доступно + if sam then + local groupMap = { + vip = "vip", + vip_plus = "vip_plus", + premium = "premium", + sponsor = "sponsor" + } + local rank = groupMap[tier] + if rank then + local expireTime = mode == "3month" and os.time() + (90 * 86400) or os.time() + (30 * 86400) + sam.player.set_rank(client, rank, expireTime) + end + end + + local modeText = mode == "3month" and "на 3 месяца" or "на 1 месяц" + return true, reward.successMessage or ("Привилегия " .. tier .. " активирована " .. modeText) + + elseif rewardType == "weapon" then + local weaponClass = reward.weaponClass or reward.class + if not weaponClass then + return false, "Класс оружия не указан" + end + + -- Добавляем оружие в список донат-оружия персонажа со сроком действия + local donateWeapons = char:GetData("donate_weapons_timed", {}) + if not istable(donateWeapons) then donateWeapons = {} end + + -- Вычисляем срок действия + local expireTime + if mode == "3month" then + expireTime = os.time() + (90 * 86400) -- 90 дней + else + expireTime = os.time() + (30 * 86400) -- 30 дней + end + + -- Добавляем или обновляем срок + donateWeapons[weaponClass] = { + expires = expireTime, + granted = os.time(), + mode = mode or "1month" + } + char:SetData("donate_weapons_timed", donateWeapons) + + local modeText = mode == "3month" and "на 3 месяца" or "на 1 месяц" + return true, reward.successMessage or ("Донат-оружие " .. (reward.name or weaponClass) .. " добавлено в ваш арсенал " .. modeText) + + elseif rewardType == "money" then + local amount = reward.amount or 0 + if amount > 0 and char.GiveMoney then + char:GiveMoney(math.floor(amount)) + return true, reward.successMessage or ("Вам начислено " .. amount .. " ₽") + end + return false, "Ошибка начисления денег" + + elseif rewardType == "voice_chat" then + -- Разблокировка голосового чата + char:SetData("voice_chat_unlocked", true) + + -- Если есть система SAM, можно добавить флаг + if sam then + -- Можно добавить специальную группу или флаг для голосового чата + end + + return true, reward.successMessage or "Голосовой чат разблокирован!" + + elseif rewardType == "bundle" then + if reward.money and reward.money > 0 and char.GiveMoney then + char:GiveMoney(math.floor(reward.money)) + end + + if reward.supply and reward.supply ~= 0 then + local arsenal = ix.plugin.Get("arsenal") + if arsenal and arsenal.AddFactionSupply then + arsenal:AddFactionSupply(client:Team(), reward.supply) + end + end + + if reward.techPoints and reward.techPoints ~= 0 then + local vehicles = ix.plugin.Get("vehicles") + if vehicles and vehicles.AddFactionPoints then + vehicles:AddFactionPoints(client:Team(), reward.techPoints) + end + end + + if reward.grantedItems then + self:GrantDonateItems(client, reward.grantedItems) + end + + return true, reward.successMessage or "Комплект успешно выдан" + + elseif rewardType == "pass" then + local tier = reward.tier or entry.id + local duration = math.max(1, tonumber(reward.durationDays) or 30) * 86400 + local passes = char:GetData("donate_passes", {}) + passes[tier] = { + tier = tier, + expires = os.time() + duration, + bonuses = reward.bonuses or {} + } + char:SetData("donate_passes", passes) + return true, reward.successMessage or "Подписка активирована" + elseif rewardType == "booster" then + local boosterID = reward.boosterID or entry.id + local duration = math.max(1, tonumber(reward.durationHours) or 24) * 3600 + local boosters = char:GetData("donate_boosters", {}) + boosters[boosterID] = { + expires = os.time() + duration, + multiplier = reward.multiplier or 1.0 + } + char:SetData("donate_boosters", boosters) + return true, reward.successMessage or "Бустер активирован" + elseif rewardType == "case" then + local caseID = reward.caseID or entry.id + local cases = char:GetData("donate_cases", {}) + cases[caseID] = (cases[caseID] or 0) + 1 + char:SetData("donate_cases", cases) + return true, reward.successMessage or "Кейс добавлен в коллекцию" + elseif rewardType == "cosmetic" then + local unlockID = reward.unlockID or entry.id + local cosmetics = char:GetData("donate_cosmetics", {}) + cosmetics[unlockID] = true + char:SetData("donate_cosmetics", cosmetics) + return true, reward.successMessage or "Косметика разблокирована" + end + + local hookResult, hookMessage = hook.Run("OnDonateReward", client, entry) + if hookResult ~= nil then + return hookResult, hookMessage + end + + return false, "Тип награды не поддерживается" +end + +function PLUGIN:MarkDonatePurchase(client, entry) + local char = client:GetCharacter() + if not char then return end + + local history = char:GetData("donate_purchases", {}) + history[entry.id] = (history[entry.id] or 0) + 1 + char:SetData("donate_purchases", history) +end + +function PLUGIN:SendDonateResult(client, success, message, productID) + if not IsValid(client) then return end + + net.Start("ix.F4_DonatePurchaseResult") + net.WriteBool(success and true or false) + net.WriteString(message or "") + net.WriteString(productID or "") + net.Send(client) +end + +function PLUGIN:HandleDonatePurchase(client, productID, price, mode) + local entry = self:GetDonateProduct(productID) + local ok, msg = self:CanPurchaseDonateProduct(client, entry) + if not ok then + return false, msg, entry + end + + local actualPrice = price or tonumber(entry and entry.price) or 0 + if actualPrice <= 0 then + return false, "Цена предложения не настроена", entry + end + + local funds = self:GetIGSBalance(client) + if funds < actualPrice then + return false, "Недостаточно средств на балансе", entry + end + + local debited, debitError = self:AdjustIGSBalance(client, -actualPrice) + if not debited then + return false, debitError or "Не удалось списать средства", entry + end + + local success, rewardMessage = self:ApplyDonateReward(client, entry, mode) + if not success then + self:AdjustIGSBalance(client, actualPrice) + return false, rewardMessage or "Ошибка выдачи награды", entry + end + + self:MarkDonatePurchase(client, entry, mode) + hook.Run("PostDonatePurchase", client, entry, actualPrice, mode) + + return true, rewardMessage or "Покупка завершена", entry +end + +net.Receive("ix.F4_DonatePurchase", function(_, client) + if not IsValid(client) or not client:IsPlayer() then return end + + local productID = net.ReadString() or "" + local price = net.ReadUInt(32) or 0 + local mode = net.ReadString() or "month" + + if productID == "" then return end + + if PLUGIN:IsDonateOnCooldown(client) then + PLUGIN:SendDonateResult(client, false, "Слишком частые запросы", productID) + return + end + + PLUGIN:ArmDonateCooldown(client) + local success, message, entry = PLUGIN:HandleDonatePurchase(client, productID, price, mode) + + local entryID = entry and entry.id or productID + if success then + local priceText = tostring(price) + local modeText = mode == "forever" and " (навсегда)" or " (на месяц)" + print(string.format("[F4 Donate] %s (%s) купил %s%s за %s", client:Name(), client:SteamID(), entryID, modeText, priceText)) + else + print(string.format("[F4 Donate] Ошибка покупки %s игроком %s: %s", entryID, client:Name(), tostring(message))) + end + + PLUGIN:SendDonateResult(client, success, message, entryID) +end) +-- Admin command to add IGS funds +ix.command.Add("GiveIGS", { + description = "Выдать IGS средства игроку", + CanRun = function(self, client) + local userGroup = string.lower(client:GetUserGroup() or "user") + return client:IsAdmin() or AdminPrivs[userGroup] + end, + arguments = { + ix.type.player, + ix.type.number + }, + OnRun = function(self, client, target, amount) + if not IsValid(target) then + return "@invalidTarget" + end + + amount = math.floor(tonumber(amount) or 0) + if amount == 0 then + return "Укажите корректную сумму" + end + + local success, err = PLUGIN:AdjustIGSBalance(target, amount) + if success then + local action = amount > 0 and "выдал" or "снял" + client:Notify(string.format("Вы %s %d IGS для %s", action, math.abs(amount), target:Name())) + target:Notify(string.format("Администратор %s вам %d IGS", action == "выдал" and "выдал" or "снял у вас", math.abs(amount))) + + print(string.format("[IGS Admin] %s (%s) %s %d IGS для %s (%s)", + client:Name(), client:SteamID(), action, math.abs(amount), target:Name(), target:SteamID())) + return "" + else + return err or "Ошибка при изменении баланса" + end + end +}) + +-- Admin command to check IGS balance +ix.command.Add("CheckIGS", { + description = "Проверить IGS баланс игрока", + CanRun = function(self, client) + local userGroup = string.lower(client:GetUserGroup() or "user") + return client:IsAdmin() or AdminPrivs[userGroup] + end, + arguments = { + ix.type.player + }, + OnRun = function(self, client, target) + if not IsValid(target) then + return "@invalidTarget" + end + + local balance = PLUGIN:GetIGSBalance(target) + return string.format("Баланс IGS игрока %s: %d руб.", target:Name(), balance) + end +}) + +net.Receive("ixChangeName", function(len, client) + if not IsValid(client) then return end + + local character = client:GetCharacter() + if not character then return end + + local newName = net.ReadString() + if not newName or newName == "" then return end + + if #newName < 3 or #newName > 50 then + client:Notify("Имя должно содержать от 3 до 50 символов") + return + end + + if newName:find("[<>\"\\/]") then + client:Notify("Имя содержит недопустимые символы") + return + end + + local oldName = character:GetName() + character:SetName(newName) + client:Notify("Ваше имя успешно изменено на: " .. newName) + + net.Start("ixF4UpdateName") + net.WriteString(newName) + net.Send(client) + + hook.Run("OnCharacterVarChanged", character, "name", oldName, newName) +end) + +-- Фикс выдачи донат-оружия IGS в Helix +hook.Add("PostPlayerLoadout", "IGS_Helix_WeaponLoadoutFix", function(client) + if not IsValid(client) or not IGS then return end + + timer.Simple(1.5, function() + if not IsValid(client) or not client:GetCharacter() then return end + + if isfunction(IGS.PlayerLoadout) then + IGS.PlayerLoadout(client) + end + + if isfunction(IGS.GetItems) and IsValid(client) and client.HasPurchase then + for _, ITEM in pairs(IGS.GetItems()) do + if not ITEM or not isfunction(ITEM.GetUID) then continue end + + local weaponClass = (isfunction(ITEM.GetWeapon) and ITEM:GetWeapon()) or ITEM.weapon + if weaponClass and client:HasPurchase(ITEM:GetUID()) then + if not client:HasWeapon(weaponClass) then + client:Give(weaponClass) + end + end + end + end + end) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/cl_menu.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/cl_menu.lua new file mode 100644 index 0000000..86a3eb4 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/cl_menu.lua @@ -0,0 +1,220 @@ +-- Клиентское меню строительства +local PLUGIN = PLUGIN + +if CLIENT then + function DrawItems(items, panel, mode, main) + for i=1, #items do + if items[i].cat == mode then + local DButton = panel:Add( "DButton" ) + DButton:SetHeight(60) + DButton:SetFont( "Font1" ) + DButton:SetText( items[i].name .. " | Запас: " .. items[i].count ) + DButton:SetIcon( items[i].icon ) + DButton:Dock( TOP ) + DButton:DockMargin( 0, 10, 0, 10 ) + DButton.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255)) + draw.RoundedBox(5, 2, 2, w-4, h-4, Color(200, 200, 200, 255)) + end + DButton.DoClick = function() + if items[i].model != nil then + net.Start("buildtrench") + net.WriteInt(i, 13) + net.SendToServer() + main:Close() + end + if items[i].entity != nil then + net.Start("setentity") + net.WriteInt(i, 13) + net.SendToServer() + main:Close() + end + end + end + end + end + surface.CreateFont("Font3", { + font = "Arial", + extended = true, + weight = 900, + size = 24 + }) + surface.CreateFont("Font2", { + font = "Arial", + extended = true, + weight = 800, + size = 20 + }) + surface.CreateFont("Font", { + font = "Arial", + extended = true, + size = 20 + }) + surface.CreateFont("Font1", { + font = "Arial", + weight = 900, + extended = true, + size = 20 + }) + local faded_black = Color(100, 100, 100, 0) + + net.Receive("openmenu", function(len, ply) + local alpha1 = 255 + local alpha2 = 0 + local alpha3 = 0 + local alpha4 = 0 + local catlabel + local DScrollPanel + local items = net.ReadTable() + local mode = 0 + local main = vgui.Create("DFrame") + main:SetSize(500, 500) + main:Center() + main:SetTitle(" ") + main:SetDraggable(false) + main:ShowCloseButton(false) + main:MakePopup() + main.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, faded_black) + end + + local p = vgui.Create( "DPanel", main ) + p:Dock( TOP ) + p:DockMargin( 0, 0, 0, 0 ) + p:SetHeight(40) + p.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255)) + draw.RoundedBox(5, 2, 2, w-4, h-4, Color(150, 150, 150, 255)) + end + local namelabel = vgui.Create( "DLabel", p ) + namelabel:Dock( LEFT ) + namelabel:DockMargin( 20, 0, 20, 0 ) + namelabel:SetSize(200, 20) + namelabel:SetFont( "Font" ) + namelabel:SetText( "Инженерный комплект" ) + + local closebutton = vgui.Create( "DButton", p ) + closebutton:Dock( RIGHT ) + closebutton:DockMargin( 20, 0, 20, 0 ) + closebutton:SetText( " " ) + closebutton:SetImage("entities/closebutton.png") + closebutton:SetSize( 40, 40 ) + closebutton.Paint = function(self, w, h) + draw.RoundedBox(2, 0, 0, w, h, Color(150, 150, 150, 0)) + end + closebutton.DoClick = function() + main:Close() + end + + local ppp = vgui.Create( "DPanel", main ) + ppp:Dock( TOP ) + ppp:DockMargin( 0, 20, 0, 20 ) + ppp:SetHeight(60) + ppp.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255)) + draw.RoundedBox(5, 2, 2, w-4, h-4, Color(150, 150, 150, 255)) + end + + local entity = vgui.Create( "DButton", ppp ) + entity:Dock( LEFT ) + entity:DockMargin( 70, 10, 0, 10 ) + entity:SetText( " " ) + entity:SetImage("entities/entity.png") + entity:SetHeight(40) + entity:SetWidth(40) + entity.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha1)) + end + entity.DoClick = function() + DScrollPanel:GetCanvas():Clear() + DrawItems(items, DScrollPanel, 0, main) + catlabel:SetText( " Объекты" ) + alpha1 = 255 + alpha2 = 0 + alpha3 = 0 + alpha4 = 0 + end + + local tier1 = vgui.Create( "DButton", ppp ) + tier1:Dock( LEFT ) + tier1:DockMargin( 70, 10, 0, 10 ) + tier1:SetText( " " ) + tier1:SetImage("entities/tier1.png") + tier1:SetHeight(40) + tier1:SetWidth(40) + tier1.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha2)) + end + tier1.DoClick = function() + DScrollPanel:GetCanvas():Clear() + DrawItems(items, DScrollPanel, 1, main) + catlabel:SetText( "Легкие укрепления" ) + alpha1 = 0 + alpha2 = 255 + alpha3 = 0 + alpha4 = 0 + end + + local tier2 = vgui.Create( "DButton", ppp ) + tier2:Dock( LEFT ) + tier2:DockMargin( 70, 10, 0, 10 ) + tier2:SetText( " " ) + tier2:SetImage("entities/tier2.png") + tier2:SetHeight(40) + tier2:SetWidth(40) + tier2.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha3)) + end + tier2.DoClick = function() + DScrollPanel:GetCanvas():Clear() + DrawItems(items, DScrollPanel, 2, main) + catlabel:SetText( "Средние укрепления" ) + alpha1 = 0 + alpha2 = 0 + alpha3 = 255 + alpha4 = 0 + end + + local tier3 = vgui.Create( "DButton", ppp ) + tier3:Dock( LEFT ) + tier3:DockMargin( 70, 10, 0, 10 ) + tier3:SetText( " " ) + tier3:SetImage("entities/tier3.png") + tier3:SetHeight(40) + tier3:SetWidth(40) + tier3.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha4)) + end + tier3.DoClick = function() + DScrollPanel:GetCanvas():Clear() + DrawItems(items, DScrollPanel, 3, main) + catlabel:SetText( "Тяжелые укрепления" ) + alpha1 = 0 + alpha2 = 0 + alpha3 = 0 + alpha4 = 255 + end + + local pp = vgui.Create( "DPanel", main ) + pp:Dock( TOP ) + pp:DockMargin( 0, 0, 0, 20 ) + pp:SetHeight(300) + pp.Paint = function(self, w, h) + draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255)) + draw.RoundedBox(5, 2, 2, w-4, h-4, Color(150, 150, 150, 255)) + end + + catlabel = vgui.Create( "DLabel", pp ) + catlabel:Dock( TOP ) + catlabel:DockMargin( 170, 20, 0, 10 ) + catlabel:SetSize(350, 20) + catlabel:SetFont( "Font" ) + catlabel:SetText( " Объекты" ) + + DScrollPanel = vgui.Create( "DScrollPanel", pp ) + DScrollPanel:Dock( TOP ) + DScrollPanel:SetHeight(240) + DScrollPanel:DockMargin( 6, 6, 6, 6 ) + DrawItems(items, DScrollPanel, 0, main) + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/cl_skin.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/cl_skin.lua new file mode 100644 index 0000000..4b03042 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/cl_skin.lua @@ -0,0 +1,114 @@ +-- Клиентская часть смены скинов +local PLUGIN = PLUGIN + +local function OpenSkinMenu(ply, ent) + if not IsValid(ent) then return end + + local frame = vgui.Create("DFrame") + frame:SetSize(250, 400) + frame:SetTitle("Выбор скина") + frame:Center() + frame:MakePopup() + + local skinPanel = vgui.Create("DScrollPanel", frame) + skinPanel:SetSize(230, 350) + skinPanel:SetPos(10, 30) + + net.Start("RequestSkinData") + net.WriteEntity(ent) + net.SendToServer() + + net.Receive("ReceiveSkinData", function() + local totalSkins = net.ReadInt(8) + local skinNames = net.ReadTable() + for i = 0, totalSkins - 1 do + local skinName = skinNames[i] or "Скин " .. i + local button = vgui.Create("DButton", skinPanel) + button:SetText(skinName) + button:SetSize(210, 40) + button:SetPos(10, 10 + i * 50) + button.DoClick = function() + net.Start("ChangeEntitySkin") + net.WriteEntity(ent) + net.WriteInt(i, 8) + net.SendToServer() + end + end + end) +end + +hook.Add("PlayerBindPress", "OpenSkinMenuOnUse", function(ply, bind, pressed) + local allowedGroups = { + owner = true, + admin = true, + ["spec admin"] = true, + prem = true, + sponsor = true + } + +if allowedGroups[ply:GetUserGroup()] then + if bind == "+use" and pressed then + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if IsValid(ent) and trace.HitPos:Distance(ply:GetPos()) < 1000 then + local activeWeapon = ply:GetActiveWeapon() + if IsValid(activeWeapon) and activeWeapon:GetClass() == "weapon_hands" then + net.Start("CheckAllowedEntity") + net.WriteEntity(ent) + net.SendToServer() + net.Receive("EntityAllowed", function() + OpenSkinMenu(ply, ent) + end) + end + end + end + end +end) + +concommand.Add("get_bodygroup_info", function(ply) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) then + print("[Ошибка] Вы не смотрите на объект!") + return + end + + local bodygroups = ent:GetBodyGroups() + print("========[ Bodygroup Info ]========") + print("Модель: " .. ent:GetModel()) + + for _, bg in ipairs(bodygroups) do + local currentValue = ent:GetBodygroup(bg.id) + print(string.format("Bodygroup: %s (ID: %d) | Выбрано: %d", bg.name, bg.id, currentValue)) + end + + local materials = ent:GetMaterials() + print("========[ Sub-Materials ]========") + + for i = 0, #materials - 1 do + local mat = ent:GetSubMaterial(i) + if mat == "" then mat = "По умолчанию" end + print(string.format("SubMaterial ID: %d | Материал: %s", i, mat)) + end +end) + +concommand.Add("get_skin_info", function(ply) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) then + print("[Ошибка] Вы не смотрите на объект!") + return + end + + local totalSkins = ent:SkinCount() - 1 + print("========[ Skin Info ]========") + print("Модель: " .. ent:GetModel()) + print("Доступно скинов: " .. totalSkins) + + for i = 0, totalSkins do + print(string.format("Skin ID: %d", i)) + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/cl_init.lua new file mode 100644 index 0000000..5872400 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/cl_init.lua @@ -0,0 +1,9 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/init.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/init.lua new file mode 100644 index 0000000..0e3b2a6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/init.lua @@ -0,0 +1,58 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +local StartPos +local StartAng +function ENT:Initialize() + self:SetMoveType(MOVETYPE_NONE) + StartPos = self:GetPos() + StartAng = self:GetAngles() + self:SetNWInt('MaxHp',self.hp) +end + +function ENT:Think() + self:SetNWInt('Hp',self.hp) + if self:GetNWInt("Hp") > self:GetNWInt("MaxHp") then + self:SetNWInt('Hp',self:GetNWInt("MaxHp")) + end + if SERVER then + if self.poscoef == 0 and self.building then + self.Owner:Freeze(false) + self.Owner.building = false + self:PhysicsInitStatic(6) + self:SetCollisionGroup(0) + self.building = false + end + if self.building then + self.Owner.building = true + self.poscoef = self.poscoef + 1 + self.Owner:Freeze(true) + self:SetAngles(StartAng) + self:SetPos(StartPos - self:GetAngles():Up() * -self.poscoef) + end +end +end + +function ENT:OnRemove() + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + end +end + +function ENT:Use(activator, caller) + +end + +function ENT:OnTakeDamage( dmg ) + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + if self.hp > 0 and self.hp - dmg:GetDamage() <= 0 then + self.Exploded = true + self:Remove() + end + self.hp = self.hp - dmg:GetDamage() + dmg:SetAttacker(self) + dmg:SetInflictor(self) + self:TakePhysicsDamage( dmg ) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/shared.lua new file mode 100644 index 0000000..1d6e140 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = "anim" +ENT.PrintName = "Contact Explosive" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/cl_init.lua new file mode 100644 index 0000000..5872400 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/cl_init.lua @@ -0,0 +1,9 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/init.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/init.lua new file mode 100644 index 0000000..0e3b2a6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/init.lua @@ -0,0 +1,58 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +local StartPos +local StartAng +function ENT:Initialize() + self:SetMoveType(MOVETYPE_NONE) + StartPos = self:GetPos() + StartAng = self:GetAngles() + self:SetNWInt('MaxHp',self.hp) +end + +function ENT:Think() + self:SetNWInt('Hp',self.hp) + if self:GetNWInt("Hp") > self:GetNWInt("MaxHp") then + self:SetNWInt('Hp',self:GetNWInt("MaxHp")) + end + if SERVER then + if self.poscoef == 0 and self.building then + self.Owner:Freeze(false) + self.Owner.building = false + self:PhysicsInitStatic(6) + self:SetCollisionGroup(0) + self.building = false + end + if self.building then + self.Owner.building = true + self.poscoef = self.poscoef + 1 + self.Owner:Freeze(true) + self:SetAngles(StartAng) + self:SetPos(StartPos - self:GetAngles():Up() * -self.poscoef) + end +end +end + +function ENT:OnRemove() + if self.HasIdle then + self:StopSound("TFA_INS2_RPG7.Loop") + self.HasIdle = false + end +end + +function ENT:Use(activator, caller) + +end + +function ENT:OnTakeDamage( dmg ) + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + if self.hp > 0 and self.hp - dmg:GetDamage() <= 0 then + self.Exploded = true + self:Remove() + end + self.hp = self.hp - dmg:GetDamage() + dmg:SetAttacker(self) + dmg:SetInflictor(self) + self:TakePhysicsDamage( dmg ) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/shared.lua new file mode 100644 index 0000000..1d6e140 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = "anim" +ENT.PrintName = "Contact Explosive" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.DoNotDuplicate = true +ENT.DisableDuplicator = true diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/cl_init.lua new file mode 100644 index 0000000..48a3981 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/cl_init.lua @@ -0,0 +1,29 @@ +include("shared.lua") + +surface.CreateFont( "PlayerTagFont", { + font = "Arial", + size = 72, +} ) + +function ENT:Draw() + self:DrawModel() + angle = self:GetAngles() + Angle(0,90,90) + pos = self:GetPos() + angle:Forward() * 0 + angle:Up() * 0 + angle:Right() * -30 + cam.Start3D2D( pos, angle, 0.05 ) + + surface.SetFont( "PlayerTagFont" ) + local tW, tH = surface.GetTextSize( self.DisplayName ) + + local padX = 20 + local padY = 5 + + surface.SetDrawColor( 0, 0, 0, 200 ) + surface.DrawRect( -tW / 2 - padX, -padY, tW + padX * 2, tH + padY * 2 ) + + draw.SimpleText( self.DisplayName, "PlayerTagFont", -tW / 2, 0, color_white ) + cam.End3D2D() +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/init.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/init.lua new file mode 100644 index 0000000..925761e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/init.lua @@ -0,0 +1,43 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +ENT.hp = 500 + +function ENT:Initialize() + self:SetModel(self.Model) + self:PhysicsInit(6) + +end + +function ENT:Think() + +end + +function ENT:OnRemove() + +end + +function ENT:Use(activator, caller) + if activator:GetActiveWeapon():GetClass() == "engineertool" or "engineertoolfpv" or "engineertoolmines" then + print(activator:GetActiveWeapon().Structures) + activator:GetActiveWeapon().Structures[self.ItemId].count = activator:GetActiveWeapon().Structures[self.ItemId].count + self.ItemCount + net.Start('additem') + net.WriteInt(self.ItemId, 13) + net.WriteInt(self.ItemCount, 13) + net.Send(activator) + self:Remove() + end +end + +function ENT:OnTakeDamage( dmg ) + if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end + if self.Exploded then return end + if self.hp > 0 and self.hp - dmg:GetDamage() <= 0 then + self.Exploded = true + self:Remove() + end + self.hp = self.hp - dmg:GetDamage() + dmg:SetAttacker(self) + dmg:SetInflictor(self) + self:TakePhysicsDamage( dmg ) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/shared.lua new file mode 100644 index 0000000..de59ac3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/entities/util_structure_box/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.PrintName = "Ящик с припасами" +ENT.Category = "Engineer Kit" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Spawnable = true +ENT.Model = "models/Items/ammocrate_smg1.mdl" +ENT.ItemId = 1 +ENT.ItemCount = 1 +ENT.DisplayName = "ПТРК 'Корнет'" diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertool.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertool.lua new file mode 100644 index 0000000..7f4deb3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertool.lua @@ -0,0 +1,251 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/weapon_doietoolus") + SWEP.DrawWeaponInfoBox = false + SWEP.BounceWeaponIcon = false + killicon.Add( "weapon_doietoolus", "vgui/hud/weapon_doietoolus", Color( 0, 0, 0, 255 ) ) + end + + SWEP.PrintName = "Entrenching Tool" + + SWEP.Category = "Engiener Tools" + + SWEP.Spawnable= true + SWEP.AdminSpawnable= true + SWEP.AdminOnly = false + + SWEP.ViewModelFOV = 60 + SWEP.ViewModel = "models/weapons/doi/v_etool_us.mdl" + SWEP.WorldModel = "models/weapons/doi/w_etool_us.mdl" + SWEP.ViewModelFlip = false + + SWEP.AutoSwitchTo = false + SWEP.AutoSwitchFrom = false + + SWEP.Slot = 0 + SWEP.SlotPos = 0 + + SWEP.UseHands = true + + SWEP.HoldType = "melee2" + + SWEP.FiresUnderwater = false + + SWEP.DrawCrosshair = false + + SWEP.DrawAmmo = true + + SWEP.Base = "weapon_base" + + SWEP.CSMuzzleFlashes = true + + SWEP.Sprint = 0 + + SWEP.Primary.ClipSize = 0 + SWEP.Primary.DefaultClip = 0 + SWEP.Primary.Automatic = false + SWEP.Primary.Ammo = "none" + + SWEP.Secondary.ClipSize = 0 + SWEP.Secondary.DefaultClip = 0 + SWEP.Secondary.Automatic = false + SWEP.Secondary.Ammo = "none" + + SWEP.Structures = { + { + name = "Мешки с песком", + icon = "entities/closebutton.png", + cat = 1, + hp = 10000, + model = "models/props_fortifications/sandbags_corner1.mdl", + count = 3, + corrangle = Angle(0,0,0) + }, + { + name = "Большие мешки с песком", + icon = "entities/closebutton.png", + cat = 1, + hp = 20000, + model = "models/props_fortifications/sandbags_corner1_tall.mdl", + count = 2, + corrangle = Angle(0,0,0) + }, + { + name = "Противотанковые ёжи", + icon = "entities/closebutton.png", + cat = 1, + hp = 50000, + model = "models/test_sborka_props/barricades/ezi_antitank_2.mdl", + count = 2, + corrangle = Angle(0,-90,0) + }, + { + name = "Танковый окоп", + icon = "entities/closebutton.png", + cat = 2, + hp = 100000, + model = "models/trenches/prop/tank_cover/tank_cover.mdl", + count = 3, + corrangle = Angle(0,0,0) + }, + } + + function SWEP:Initialize() + self:SetWeaponHoldType( self.HoldType ) + self:GetOwner().buildtime = 0 + end + + function SWEP:Deploy() + self:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() ) + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) + Sprint = 0 + end + + function SWEP:Holster() + self.NextSecondaryAttack = 0 + Sprint = 0 + return true + end + + function SWEP:PrimaryAttack() + if !(self.Weapon:GetNextPrimaryFire() < CurTime()) || not(Sprint == 0) then return end + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + self:SetNextPrimaryFire( CurTime() + 1) + self:SetNextSecondaryFire( CurTime() + 1) + + if SERVER then + net.Start("openmenu") + net.WriteTable(self.Structures) + net.Send(self:GetOwner()) + end + + end + + function SWEP:SecondaryAttack() + end + + function SWEP:Reload() + if SERVER then + local item = self:GetOwner():GetEyeTrace().Entity + if item:IsValid() then + if item.id != nil then + if item.Owner == self:GetOwner() then + if (item:GetClass() == self.Structures[item.id].entity) or (item:GetModel() == self.Structures[item.id].model) then + item:Remove() + self.Structures[item.id].count = self.Structures[item.id].count + 1 + end + end + end + end + end + end + function SWEP:DrawHUDBackground() + if self:GetOwner().buildtime > 0 then + draw.RoundedBox( 20, ScrW()/2-150, ScrH()/2-30, 0+300-self:GetOwner().buildtime*15, 60, Color( 50, 50, 50 , 180) ) + draw.RoundedBox( 20, ScrW()/2-145, ScrH()/2-25, 0+290-self:GetOwner().buildtime*14.5, 50, Color( 0, 255, 0 ,125) ) + draw.DrawText( tostring(100 - self:GetOwner().buildtime*5) .. '%', "Font3", ScrW()/2-20, ScrH()/2-15, Color( 255, 255, 255 ,255), 0 ) + end + end + + local delay = 0.5 + local nextOccurance = 0 + local flag1 = true + if CLIENT then + function SWEP:RenderScreen() + if self.Owner.buildtime < 0 or Sprint == 2 then + self.Weapon:SendWeaponAnim(ACT_VM_IDLE) + end + + + if input.IsKeyDown(KEY_F) and flag1 then + net.Start("buildstop12") + net.SendToServer() + self.Owner.building = false + self.Owner.buildtime = -3 + flag1 = false + timer.Simple(1, function() flag1 = true end) + end + + local timeLeft = nextOccurance - CurTime() + if timeLeft < 0 and self:GetOwner().buildtime >= 0 then + self:GetOwner().buildtime = self:GetOwner().buildtime - 0.5 + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:GetOwner():SetAnimation( PLAYER_ATTACK1 ) + self:SetNextPrimaryFire( CurTime() + delay) + nextOccurance = CurTime() + delay + end + net.Receive("buildanim", function(len, ply) + self:GetOwner().buildtime = 20 + end) + end + end + if SERVER then + function SWEP:Think() + + + net.Receive("buildstop12", function(len, ply) + ply:Freeze(false) + if self.LastEnt:IsValid() then + self.Structures[self.LastEnt.id].count = self.Structures[self.LastEnt.id].count + 1 + self.LastEnt:Remove() + end + end) + net.Receive("buildtrench", function(len, ply) + if ply:GetActiveWeapon():GetClass() != "engineertool" then return end + local id = net.ReadInt(13) + if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then + if self.Structures[id].count > 0 then + net.Start("buildanim") + net.WriteEntity(item) + net.Send(self:GetOwner()) + local item = ents.Create("util_structure") + item:SetPos(ply:GetEyeTrace().HitPos) + item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + Angle(90,ply:EyeAngles().y,0)) + item:SetModel(self.Structures[id].model) + item.hp = self.Structures[id].hp + item.id = id + item.poscoef = -100 + item.building = true + item.Owner = self:GetOwner() + item:Spawn() + self.LastEnt = item + self.Structures[id].count = self.Structures[id].count - 1 + + end + end + end) + net.Receive("setentity", function(len, ply) + if ply:GetActiveWeapon():GetClass() != "engineertool" then return end + local id = net.ReadInt(13) + if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then + if self.Structures[id].count > 0 then + local item = ents.Create(self.Structures[id].entity) + item:SetPos(ply:GetEyeTrace().HitPos + ply:GetEyeTrace().HitNormal * 40) + item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + self.Structures[id].corrangle) + item.id = id + item.Owner = self:GetOwner() + item:Spawn() + self.LastEnt = item + self.Structures[id].count = self.Structures[id].count - 1 + end + end + end) + if (Sprint == 0) then + + if self.Owner:KeyDown(IN_SPEED) and (self.Owner:KeyDown(IN_FORWARD) || self.Owner:KeyDown(IN_BACK) || self.Owner:KeyDown(IN_MOVELEFT) || self.Owner:KeyDown(IN_MOVERIGHT)) then + Sprint = 1 + end + end + if (Sprint == 1) then + self.Weapon:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + Sprint = 2 + end + if (Sprint == 2) then + if self.Owner:KeyReleased(IN_SPEED) then + self.Weapon:SendWeaponAnim(ACT_VM_IDLE) + Sprint = 0 + end + end + + end + end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertoolfpv.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertoolfpv.lua new file mode 100644 index 0000000..61d55c1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertoolfpv.lua @@ -0,0 +1,231 @@ +if CLIENT then + + SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/weapon_doietoolus") + SWEP.DrawWeaponInfoBox = false + SWEP.BounceWeaponIcon = false + killicon.Add( "weapon_doietoolus", "vgui/hud/weapon_doietoolus", Color( 0, 0, 0, 255 ) ) + end + + SWEP.PrintName = "Entrenching Tool FPV" + + SWEP.Category = "Engiener Tools" + + SWEP.Spawnable= true + SWEP.AdminSpawnable= true + SWEP.AdminOnly = false + + SWEP.ViewModelFOV = 60 + SWEP.ViewModel = "models/weapons/doi/v_etool_us.mdl" + SWEP.WorldModel = "models/weapons/doi/w_etool_us.mdl" + SWEP.ViewModelFlip = false + + SWEP.AutoSwitchTo = false + SWEP.AutoSwitchFrom = false + + SWEP.Slot = 0 + SWEP.SlotPos = 0 + + SWEP.UseHands = true + + SWEP.HoldType = "melee2" + + SWEP.FiresUnderwater = false + + SWEP.DrawCrosshair = false + + SWEP.DrawAmmo = true + + SWEP.Base = "weapon_base" + + SWEP.CSMuzzleFlashes = true + + SWEP.Sprint = 0 + + SWEP.Primary.ClipSize = 0 + SWEP.Primary.DefaultClip = 0 + SWEP.Primary.Automatic = false + SWEP.Primary.Ammo = "none" + + SWEP.Secondary.ClipSize = 0 + SWEP.Secondary.DefaultClip = 0 + SWEP.Secondary.Automatic = false + SWEP.Secondary.Ammo = "none" + + SWEP.Structures = { + { + name = "FPV-дрон", + icon = "entities/entity.png", + cat = 0, + entity = "entity_drone_bomb", + count = 2, + id = 1, + corrangle = Angle(90,0,0) + }, + { + name = "Разведывательный дрон", + icon = "entities/entity.png", + cat = 0, + entity = "entity_drone_base", + count = 1, + id = 2, + corrangle = Angle(90,0,0) + }, + { + name = "РЭБ", + icon = "entities/entity.png", + cat = 0, + entity = "entity_dronejammer", + count = 1, + id = 3, + corrangle = Angle(90,0,0) + }, + { + name = "Зарядная станция для дрона", + icon = "entities/entity.png", + cat = 0, + entity = "entity_dronepad", + count = 1, + id = 4, + corrangle = Angle(90,0,0) + }, + } + + function SWEP:Initialize() + self:SetWeaponHoldType( self.HoldType ) + self:GetOwner().buildtime = 0 + end + + function SWEP:Deploy() + self:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() ) + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) + Sprint = 0 + end + + function SWEP:Holster() + self.NextSecondaryAttack = 0 + Sprint = 0 + return true + end + + function SWEP:PrimaryAttack() + if !(self.Weapon:GetNextPrimaryFire() < CurTime()) || not(Sprint == 0) then return end + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + self:SetNextPrimaryFire( CurTime() + 1) + self:SetNextSecondaryFire( CurTime() + 1) + + if SERVER then + net.Start("openmenu") + net.WriteTable(self.Structures) + net.Send(self:GetOwner()) + end + + end + + function SWEP:SecondaryAttack() + end + + function SWEP:Reload() + if SERVER then + local item = self:GetOwner():GetEyeTrace().Entity + if item:IsValid() then + local structure_id = item.structure_id + if structure_id != nil then + if item:GetOwner() == self:GetOwner() then + local struct = self.Structures[structure_id] + if (item:GetClass() == struct.entity) or (item:GetModel() == struct.model) then + item:Remove() + struct.count = struct.count + 1 + end + end + end + end + end +end + function SWEP:DrawHUDBackground() + if self:GetOwner().buildtime > 0 then + draw.RoundedBox( 20, ScrW()/2-150, ScrH()/2-30, 0+300-self:GetOwner().buildtime*15, 60, Color( 50, 50, 50 , 180) ) + draw.RoundedBox( 20, ScrW()/2-145, ScrH()/2-25, 0+290-self:GetOwner().buildtime*14.5, 50, Color( 0, 255, 0 ,125) ) + draw.DrawText( tostring(100 - self:GetOwner().buildtime*5) .. '%', "Font3", ScrW()/2-20, ScrH()/2-15, Color( 255, 255, 255 ,255), 0 ) + end + end + + local delay = 0.5 + local nextOccurance = 0 + local flag1 = true + if CLIENT then + function SWEP:RenderScreen() + if self.Owner.buildtime < 0 or Sprint == 2 then + self.Weapon:SendWeaponAnim(ACT_VM_IDLE) + end + + + if input.IsKeyDown(KEY_F) and flag1 then + net.Start("buildstop12") + net.SendToServer() + self.Owner.building = false + self.Owner.buildtime = -3 + flag1 = false + timer.Simple(1, function() flag1 = true end) + end + + local timeLeft = nextOccurance - CurTime() + if timeLeft < 0 and self:GetOwner().buildtime >= 0 then + self:GetOwner().buildtime = self:GetOwner().buildtime - 0.5 + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:GetOwner():SetAnimation( PLAYER_ATTACK1 ) + self:SetNextPrimaryFire( CurTime() + delay) + nextOccurance = CurTime() + delay + end + net.Receive("buildanim", function(len, ply) + self:GetOwner().buildtime = 20 + end) + end + end + if SERVER then + function SWEP:Think() + net.Receive("buildstop12", function(len, ply) + ply:Freeze(false) + if self.LastEnt:IsValid() then + self.Structures[self.LastEnt.id].count = self.Structures[self.LastEnt.id].count + 1 + self.LastEnt:Remove() + end + end) + net.Receive("setentity", function(len, ply) + if ply:GetActiveWeapon():GetClass() ~= "engineertoolfpv" then return end + local id = net.ReadInt(13) + local struct = self.Structures[id] + + if struct and struct.count > 0 then + local trace = ply:GetEyeTrace() + if (trace.HitPos - trace.StartPos):Length() > 300 then return end + + local item = ents.Create(struct.entity) + item:SetPos(trace.HitPos + trace.HitNormal * 10) + item:SetAngles(trace.HitNormal:Angle() + struct.corrangle) + item:Spawn() + struct.count = struct.count - 1 + item.structure_id = id + if IsValid(ply) and ply:IsPlayer() then + item:SetOwner(ply) + end + end +end) + if (Sprint == 0) then + if self.Owner:KeyDown(IN_SPEED) and (self.Owner:KeyDown(IN_FORWARD) || self.Owner:KeyDown(IN_BACK) || self.Owner:KeyDown(IN_MOVELEFT) || self.Owner:KeyDown(IN_MOVERIGHT)) then + Sprint = 1 + end + end + if (Sprint == 1) then + self.Weapon:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + Sprint = 2 + end + if (Sprint == 2) then + if self.Owner:KeyReleased(IN_SPEED) then + self.Weapon:SendWeaponAnim(ACT_VM_IDLE) + Sprint = 0 + end + end + end +end + diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertoolmines.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertoolmines.lua new file mode 100644 index 0000000..eea7a43 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/engineertoolmines.lua @@ -0,0 +1,225 @@ +if CLIENT then + SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/weapon_doietoolus") + SWEP.DrawWeaponInfoBox = false + SWEP.BounceWeaponIcon = false + killicon.Add( "weapon_doietoolus", "vgui/hud/weapon_doietoolus", Color( 0, 0, 0, 255 ) ) + end + + SWEP.PrintName = "Entrenching Tool MINES" + + SWEP.Category = "Engiener Tools" + + SWEP.Spawnable= true + SWEP.AdminSpawnable= true + SWEP.AdminOnly = false + + SWEP.ViewModelFOV = 60 + SWEP.ViewModel = "models/weapons/doi/v_etool_us.mdl" + SWEP.WorldModel = "models/weapons/doi/w_etool_us.mdl" + SWEP.ViewModelFlip = false + + SWEP.AutoSwitchTo = false + SWEP.AutoSwitchFrom = false + + SWEP.Slot = 0 + SWEP.SlotPos = 0 + + SWEP.UseHands = true + + SWEP.HoldType = "melee2" + + SWEP.FiresUnderwater = false + + SWEP.DrawCrosshair = false + + SWEP.DrawAmmo = true + + SWEP.Base = "weapon_base" + + SWEP.CSMuzzleFlashes = true + + SWEP.Sprint = 0 + + SWEP.Primary.ClipSize = 0 + SWEP.Primary.DefaultClip = 0 + SWEP.Primary.Automatic = false + SWEP.Primary.Ammo = "none" + + SWEP.Secondary.ClipSize = 0 + SWEP.Secondary.DefaultClip = 0 + SWEP.Secondary.Automatic = false + SWEP.Secondary.Ammo = "none" + + SWEP.Structures = { + { + name = "Мина 'ТМ-62'", + icon = "entities/entity.png", + cat = 0, + entity = "sw_mine_tm62_v3", + count = 10, + corrangle = Angle(90,0,0) + }, + } + + function SWEP:Initialize() + self:SetWeaponHoldType( self.HoldType ) + self:GetOwner().buildtime = 0 + end + + function SWEP:Deploy() + self:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() ) + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) + Sprint = 0 + end + + function SWEP:Holster() + self.NextSecondaryAttack = 0 + Sprint = 0 + return true + end + + function SWEP:PrimaryAttack() + if !(self.Weapon:GetNextPrimaryFire() < CurTime()) || not(Sprint == 0) then return end + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + self:SetNextPrimaryFire( CurTime() + 1) + self:SetNextSecondaryFire( CurTime() + 1) + + if SERVER then + net.Start("openmenu") + net.WriteTable(self.Structures) + net.Send(self:GetOwner()) + end + + end + + function SWEP:SecondaryAttack() + end + + function SWEP:Reload() + if SERVER then + local item = self:GetOwner():GetEyeTrace().Entity + if item:IsValid() then + if item.id != nil then + if item.Owner == self:GetOwner() then + if (item:GetClass() == self.Structures[item.id].entity) or (item:GetModel() == self.Structures[item.id].model) then + item:Remove() + self.Structures[item.id].count = self.Structures[item.id].count + 1 + end + end + end + end + end + end + function SWEP:DrawHUDBackground() + if self:GetOwner().buildtime > 0 then + draw.RoundedBox( 20, ScrW()/2-150, ScrH()/2-30, 0+300-self:GetOwner().buildtime*15, 60, Color( 50, 50, 50 , 180) ) + draw.RoundedBox( 20, ScrW()/2-145, ScrH()/2-25, 0+290-self:GetOwner().buildtime*14.5, 50, Color( 0, 255, 0 ,125) ) + draw.DrawText( tostring(100 - self:GetOwner().buildtime*5) .. '%', "Font3", ScrW()/2-20, ScrH()/2-15, Color( 255, 255, 255 ,255), 0 ) + end + end + + local delay = 0.5 + local nextOccurance = 0 + local flag1 = true + if CLIENT then + function SWEP:RenderScreen() + if self.Owner.buildtime < 0 or Sprint == 2 then + --print(1) + self.Weapon:SendWeaponAnim(ACT_VM_IDLE) + end + + + if input.IsKeyDown(KEY_F) and flag1 then + net.Start("buildstop12") + net.SendToServer() + self.Owner.building = false + self.Owner.buildtime = -3 + flag1 = false + timer.Simple(1, function() flag1 = true end) + end + + local timeLeft = nextOccurance - CurTime() + if timeLeft < 0 and self:GetOwner().buildtime >= 0 then + self:GetOwner().buildtime = self:GetOwner().buildtime - 0.5 + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self:GetOwner():SetAnimation( PLAYER_ATTACK1 ) + self:SetNextPrimaryFire( CurTime() + delay) + nextOccurance = CurTime() + delay + end + net.Receive("buildanim", function(len, ply) + print(2) + self:GetOwner().buildtime = 20 + end) + end + end + if SERVER then + function SWEP:Think() + + + net.Receive("buildstop12", function(len, ply) + ply:Freeze(false) + if self.LastEnt:IsValid() then + self.Structures[self.LastEnt.id].count = self.Structures[self.LastEnt.id].count + 1 + self.LastEnt:Remove() + end + end) + net.Receive("buildtrench", function(len, ply) + if ply:GetActiveWeapon():GetClass() != "engineertool" then return end + local id = net.ReadInt(13) + if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then + if self.Structures[id].count > 0 then + net.Start("buildanim") + net.WriteEntity(item) + net.Send(self:GetOwner()) + local item = ents.Create("util_structure") + item:SetPos(ply:GetEyeTrace().HitPos) + item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + Angle(90,ply:EyeAngles().y,0)) + item:SetModel(self.Structures[id].model) + item.hp = self.Structures[id].hp + item.id = id + item.poscoef = -100 + item.building = true + item.Owner = self:GetOwner() + item:Spawn() + self.LastEnt = item + self.Structures[id].count = self.Structures[id].count - 1 + + end + end + end) + net.Receive("setentity", function(len, ply) + if ply:GetActiveWeapon():GetClass() != "engineertoolmines" then return end + local id = net.ReadInt(13) + if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then + if self.Structures[id].count > 0 then + local item = ents.Create(self.Structures[id].entity) + item:SetPos(ply:GetEyeTrace().HitPos + ply:GetEyeTrace().HitNormal * 40) + item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + self.Structures[id].corrangle) + item.id = id + item.Owner = self:GetOwner() + item:Spawn() + self.LastEnt = item + self.Structures[id].count = self.Structures[id].count - 1 + end + end + end) + if (Sprint == 0) then + + if self.Owner:KeyDown(IN_SPEED) and (self.Owner:KeyDown(IN_FORWARD) || self.Owner:KeyDown(IN_BACK) || self.Owner:KeyDown(IN_MOVELEFT) || self.Owner:KeyDown(IN_MOVERIGHT)) then + Sprint = 1 + end + end + if (Sprint == 1) then + self.Weapon:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + Sprint = 2 + end + if (Sprint == 2) then + if self.Owner:KeyReleased(IN_SPEED) then + self.Weapon:SendWeaponAnim(ACT_VM_IDLE) + Sprint = 0 + end + end + + end + end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/repairtool.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/repairtool.lua new file mode 100644 index 0000000..e9dc281 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/entities/weapons/repairtool.lua @@ -0,0 +1,122 @@ +if CLIENT then +SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/weapon_doietoolus") +SWEP.DrawWeaponInfoBox = false +SWEP.BounceWeaponIcon = false +killicon.Add( "weapon_doietoolus", "vgui/hud/weapon_doietoolus", Color( 0, 0, 0, 255 ) ) +end + +SWEP.PrintName = "Repair Tool" + +SWEP.Category = "Engiener Tools" + +SWEP.Spawnable= true +SWEP.AdminSpawnable= true +SWEP.AdminOnly = false + +SWEP.ViewModelFOV = 60 +SWEP.ViewModel = "models/weapons/doi/v_etool_us.mdl" +SWEP.WorldModel = "models/weapons/doi/w_etool_us.mdl" +SWEP.ViewModelFlip = false + +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.Slot = 0 +SWEP.SlotPos = 0 + +SWEP.UseHands = true + +SWEP.HoldType = "melee2" + +SWEP.FiresUnderwater = false + +SWEP.DrawCrosshair = false + +SWEP.DrawAmmo = true + +SWEP.Base = "weapon_base" + +SWEP.CSMuzzleFlashes = true + +SWEP.Sprint = 0 + +SWEP.Primary.ClipSize = 0 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = 0 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + + +function SWEP:Initialize() + self:SetWeaponHoldType( self.HoldType ) +end + +function SWEP:Deploy() + self:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() ) + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) + Sprint = 0 +end + +function SWEP:Holster() + self.NextSecondaryAttack = 0 + Sprint = 0 + return true +end + +function SWEP:PrimaryAttack() + if !(self.Weapon:GetNextPrimaryFire() < CurTime()) || not(Sprint == 0) then return end + self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + self:SetNextPrimaryFire( CurTime() + 1) + self:SetNextSecondaryFire( CurTime() + 1) + + if SERVER then + if self:GetOwner():GetEyeTrace().Entity.hp != self:GetOwner():GetEyeTrace().Entity.MaxHp then + self:GetOwner():GetEyeTrace().Entity.hp = self:GetOwner():GetEyeTrace().Entity.hp + 100 + if self:GetOwner():GetEyeTrace().Entity.hp == self:GetOwner():GetEyeTrace().Entity.MaxHp then + self:GetOwner():GetEyeTrace().Entity.hp = self:GetOwner():GetEyeTrace().Entity.MaxHp + end + end + end + +end + +function SWEP:SecondaryAttack() +end + +function SWEP:Reload() + +end +function SWEP:DrawHUD() + if self:GetOwner():GetEyeTrace().Entity:GetClass() == 'util_structure'then + draw.RoundedBox( 20, ScrW()/2-150, ScrH()/2-30, 300, 60, Color( 50, 50, 50 , 180) ) + local tW, tH = surface.GetTextSize(tostring(self:GetOwner():GetEyeTrace().Entity:GetNWInt("Hp")) ..' | '.. tostring(self:GetOwner():GetEyeTrace().Entity:GetNWInt("MaxHp"))) + draw.RoundedBox( 20, ScrW()/2-145, ScrH()/2-25, self:GetOwner():GetEyeTrace().Entity:GetNWInt("Hp")*(290/self:GetOwner():GetEyeTrace().Entity:GetNWInt("MaxHp")), 50, Color( 0, 255, 0 ,125) ) + draw.DrawText( tostring(self:GetOwner():GetEyeTrace().Entity:GetNWInt("Hp")) ..' | '.. tostring(self:GetOwner():GetEyeTrace().Entity:GetNWInt("MaxHp")), "Font3", ScrW()/2, ScrH()/2-15, Color( 255, 255, 255 ,255), 1 ) + end +end + + +function SWEP:Think() + if SERVER then + if (Sprint == 0) then + if self.Owner:KeyDown(IN_SPEED) and (self.Owner:KeyDown(IN_FORWARD) || self.Owner:KeyDown(IN_BACK) || self.Owner:KeyDown(IN_MOVELEFT) || self.Owner:KeyDown(IN_MOVERIGHT)) then + Sprint = 1 + end + end + if (Sprint == 1) then + self.Weapon:SendWeaponAnim(ACT_VM_SPRINT_IDLE) + Sprint = 2 + end + if (Sprint == 2) then + if self.Owner:KeyReleased(IN_SPEED) then + self.Weapon:SendWeaponAnim(ACT_VM_IDLE) + Sprint = 0 + end + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/sh_plugin.lua new file mode 100644 index 0000000..b15d182 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/sh_plugin.lua @@ -0,0 +1,74 @@ +PLUGIN.name = "Fortifications" +PLUGIN.author = "Fortic (Портирован для Helix)" +PLUGIN.description = "Система строительства укреплений: окопы, баррикады, мешки с песком" + +-- Конфигурация +ix.config.Add("fortificationsBuildingEnabled", true, "Включить систему строительства укреплений", nil, { + category = "Fortifications" +}) + +ix.config.Add("fortificationsMaxStructures", 50, "Максимальное количество структур на карте", nil, { + data = {min = 1, max = 200}, + category = "Fortifications" +}) + +ix.config.Add("fortificationsBuildTime", 3, "Время постройки структуры (секунды)", nil, { + data = {min = 1, max = 30}, + category = "Fortifications" +}) + +ix.config.Add("fortificationsRepairAmount", 100, "Количество HP восстановления за использование ремонтного инструмента", nil, { + data = {min = 10, max = 1000}, + category = "Fortifications" +}) + +ix.config.Add("fortificationsSkinChangeEnabled", true, "Разрешить смену скинов сущностей (дроны, транспорт)", nil, { + category = "Fortifications" +}) + +-- Network strings +if SERVER then + util.AddNetworkString("openmenu") + util.AddNetworkString("setentity") + util.AddNetworkString("buildtrench") + util.AddNetworkString("buildanim") + util.AddNetworkString("buildstop12") + util.AddNetworkString("additem") + util.AddNetworkString("CheckAllowedEntity") + util.AddNetworkString("EntityAllowed") + util.AddNetworkString("RequestSkinData") + util.AddNetworkString("ReceiveSkinData") + util.AddNetworkString("ChangeEntitySkin") +end + +function PLUGIN:Initialize() + print("[Fortifications] Система строительства загружена!") +end + +-- Звуки оружия (регистрация дополнительных звуков) +for i = 1, 6 do + sound.Add({ + name = "Universal.Draw", + channel = CHAN_ITEM, + volume = 1.0, + soundlevel = 75, + sound = "weapons/universal/uni_weapon_draw_0" .. i .. ".wav" + }) + + sound.Add({ + name = "Universal.Holster", + channel = CHAN_ITEM, + volume = 1.0, + soundlevel = 75, + sound = "weapons/universal/uni_weapon_holster_0" .. i .. ".wav" + }) +end + +-- Клиентская часть +ix.util.Include("cl_menu.lua") +ix.util.Include("cl_skin.lua") + +-- Серверная часть +ix.util.Include("sv_net.lua") +ix.util.Include("sv_skin.lua") + diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/sv_net.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/sv_net.lua new file mode 100644 index 0000000..5995f82 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/sv_net.lua @@ -0,0 +1 @@ +-- Серверные сетевые события уже зарегистрированы в sh_plugin.lua diff --git a/garrysmod/gamemodes/militaryrp/plugins/fortifications/sv_skin.lua b/garrysmod/gamemodes/militaryrp/plugins/fortifications/sv_skin.lua new file mode 100644 index 0000000..e738a72 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/fortifications/sv_skin.lua @@ -0,0 +1,71 @@ +-- Серверная часть смены скинов +local PLUGIN = PLUGIN + +local allowedEntities = { + ["entity_drone_base"] = true, + ["entity_drone_bomb"] = true, + ["sw_m1151"] = true, + ["sw_gaz2330"] = true, +} + +local skinNames = { + ["entity_drone_base"] = { + [0] = "Стандартный дрон", + [1] = "Белый дрон", + [2] = "Медицинский дрон", + [3] = "Полицейский дрон", + [4] = "Блатной дрон", + [5] = "Золотой дрон", + [6] = "Дрон невидимка", + [7] = "Камуфляжный дрон", + [8] = "Потрёпаный дрон", + }, + ["entity_drone_bomb"] = { + [0] = "Стандартная бомба", + [1] = "Камуфляжная бомба", + [2] = "Золотая бомба", + }, + ["sw_m1151"] = { + [0] = "Стандартный M1151", + [1] = "Хамви Военной полиции", + }, + ["sw_gaz2330"] = { + [0] = "Стандартный GAZ-2330", + [1] = "ГАЗ-2330 Военной Полиции", + }, +} + +net.Receive("CheckAllowedEntity", function(len, ply) + local ent = net.ReadEntity() + if IsValid(ent) and allowedEntities[ent:GetClass()] then + net.Start("EntityAllowed") + net.Send(ply) + end +end) + +net.Receive("RequestSkinData", function(len, ply) + local ent = net.ReadEntity() + if IsValid(ent) and allowedEntities[ent:GetClass()] then + local class = ent:GetClass() + local totalSkins = ent:SkinCount() + local skins = skinNames[class] or {} + + net.Start("ReceiveSkinData") + net.WriteInt(totalSkins, 8) + net.WriteTable(skins) + net.Send(ply) + end +end) + +net.Receive("ChangeEntitySkin", function(len, ply) + local ent = net.ReadEntity() + local skinID = net.ReadInt(8) + + if IsValid(ent) and allowedEntities[ent:GetClass()] then + local totalSkins = ent:SkinCount() - 1 + if skinID >= 0 and skinID <= totalSkins then + ent:SetSkin(skinID) + end + end +end) + diff --git a/garrysmod/gamemodes/militaryrp/plugins/hud/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/hud/cl_plugin.lua new file mode 100644 index 0000000..f36cf21 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/hud/cl_plugin.lua @@ -0,0 +1,492 @@ +surface.CreateFont("MilitaryHUD_Small", { + font = "Montserrat", + size = 16, + weight = 400 +}) + +surface.CreateFont("MilitaryHUD_SmallBold", { + font = "Montserrat", + size = 16, + weight = 500 +}) + +surface.CreateFont("MilitaryHUD_Medium", { + font = "Montserrat", + size = 18, + weight = 400 +}) + +surface.CreateFont("MilitaryHUD_MediumBold", { + font = "Montserrat", + size = 18, + weight = 500 +}) + +surface.CreateFont("MilitaryHUD_Large", { + font = "Montserrat", + size = 28, + weight = 500 +}) + +local Colors = { + white = Color(255, 255, 255), + white_half = Color(255, 255, 255, 128), + green = Color(84, 147, 90), + dark_green = Color(52, 91, 60), + darker_green = Color(35, 53, 29), + gray = Color(193, 193, 193), + gray_dark = Color(94, 94, 94), + gray_light = Color(213, 213, 213) +} + +local PLUGIN = PLUGIN + +-- Масштабирование HUD +function GetScale() + local scale = ScrH() / 1080 + return math.Clamp(scale, 0.5, 2.0) +end + +function ScaleSize(val) + return math.max(1, math.Round(val * GetScale())) +end + +function ScalePos(val) + return math.Round(val * GetScale()) +end + +Colors = { + white = Color(255, 255, 255), + white_half = Color(255, 255, 255, 128), + green = Color(84, 147, 90), + dark_green = Color(52, 91, 60), + darker_green = Color(35, 53, 29), + gray = Color(193, 193, 193), + gray_dark = Color(94, 94, 94), + gray_light = Color(213, 213, 213), + overlay = Color(0, 0, 0, 180) +} + +local cachedScale = 0 +function CreateHUDFonts() + local currentScale = GetScale() + if math.abs(currentScale - cachedScale) < 0.01 then return end + cachedScale = currentScale + surface.CreateFont("MilitaryHUD_Small", { + font = "Montserrat", + size = ScaleSize(16), + weight = 400 + }) + surface.CreateFont("MilitaryHUD_SmallBold", { + font = "Montserrat", + size = ScaleSize(16), + weight = 500 + }) + surface.CreateFont("MilitaryHUD_Medium", { + font = "Montserrat", + size = ScaleSize(18), + weight = 400 + }) + surface.CreateFont("MilitaryHUD_MediumBold", { + font = "Montserrat", + size = ScaleSize(18), + weight = 500 + }) + surface.CreateFont("MilitaryHUD_Large", { + font = "Montserrat", + size = ScaleSize(28), + weight = 500 + }) +end + +CreateHUDFonts() +hook.Add("OnScreenSizeChanged", "MilitaryHUD_UpdateFonts", CreateHUDFonts) + +local healthLerp = 100 +local armorLerp = 100 +local ammoLerp = 30 +local hungerLerp = 100 +local thirstLerp = 100 +local staminaLerp = 100 + +-- Материалы (пути будут заполнены позже) +local logoMaterial = Material("materials/ft_ui/military/vnu/hud/logo.png") +local radioMaterial = Material("materials/ft_ui/military/vnu/hud/radio.png") +local clockMaterial = Material("materials/ft_ui/military/vnu/hud/cloak.png") +local rankMaterial = Material("materials/ft_ui/military/vnu/hud/rank.png") +local foodMaterial = Material("materials/ft_ui/military/vnu/hud/food.png") +local thirstMaterial = Material("materials/ft_ui/military/vnu/hud/thirst.png") + +function PLUGIN:GetRadioStatus() + if ix.plugin and ix.plugin.list and ix.plugin.list["radio"] then + return ix.plugin.list["radio"]:IsListening(LocalPlayer()) + end + return false +end + +function PLUGIN:GetFormattedTime() + if StormFox2 and StormFox2.Time then + local hours = StormFox2.Time.GetHours() or 12 + local minutes = StormFox2.Time.GetMinutes() or 0 + local seconds = StormFox2.Time.GetSeconds() or 0 + + return string.format("%02d:%02d:%02d", hours, minutes, seconds) + else + -- Fallback на системное время + return os.date("%H:%M:%S") + end +end + +function PLUGIN:LerpColor(progress, color1, color2) + return Color( + Lerp(progress, color1.r, color2.r), + Lerp(progress, color1.g, color2.g), + Lerp(progress, color1.b, color2.b), + Lerp(progress, color1.a, color2.a) + ) +end + +function PLUGIN:DrawHealthBar(x, y, w, h) + local health = LocalPlayer():Health() + healthLerp = Lerp(FrameTime() * 5, healthLerp, health) + local healthPercent = math.Clamp(healthLerp / 100, 0, 1) + draw.SimpleText("Здоровье", "MilitaryHUD_Small", x, y - ScalePos(25), Colors.white_half, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(math.Round(healthLerp), "MilitaryHUD_SmallBold", x + w, y - ScalePos(25), Colors.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + local barWidth = w * healthPercent + surface.SetDrawColor(Colors.gray) + surface.DrawRect(x, y, w, h) + for i = 0, barWidth - 1 do + local progress = i / w + local color = self:LerpColor(progress, Colors.darker_green, Colors.dark_green) + surface.SetDrawColor(color) + surface.DrawRect(x + i, y, 1, h) + end + local segmentWidth = w / 3 + for i = 1, 2 do + surface.SetDrawColor(Color(0, 0, 0, 100)) + surface.DrawRect(x + segmentWidth * i, y, 1, h) + end +end + +function PLUGIN:DrawArmorBar(x, y, w, h) + local armor = LocalPlayer():Armor() + armorLerp = Lerp(FrameTime() * 5, armorLerp, armor) + local armorPercent = math.Clamp(armorLerp / 100, 0, 1) + draw.SimpleText("Броня", "MilitaryHUD_Small", x, y - ScalePos(25), Colors.white_half, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(math.Round(armorLerp), "MilitaryHUD_SmallBold", x + w, y - ScalePos(25), Colors.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + local barWidth = w * armorPercent + surface.SetDrawColor(Colors.gray) + surface.DrawRect(x, y, w, h) + for i = 0, barWidth - 1 do + local progress = i / w + local color = self:LerpColor(progress, Colors.darker_green, Colors.dark_green) + surface.SetDrawColor(color) + surface.DrawRect(x + i, y, 1, h) + end + local segmentWidth = w / 3 + for i = 1, 2 do + surface.SetDrawColor(Color(0, 0, 0, 100)) + surface.DrawRect(x + segmentWidth * i, y, 1, h) + end +end + +function PLUGIN:DrawStaminaBar(x, y, w, h) + local stamina = LocalPlayer():GetLocalVar("stm", 100) + staminaLerp = Lerp(FrameTime() * 5, staminaLerp, stamina) + local staminaPercent = math.Clamp(staminaLerp / 100, 0, 1) + + draw.SimpleText("Выносливость", "MilitaryHUD_Small", x, y - ScalePos(25), Colors.white_half, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(math.Round(staminaLerp), "MilitaryHUD_SmallBold", x + w, y - ScalePos(25), Colors.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + + local barWidth = w * staminaPercent + surface.SetDrawColor(Colors.gray) + surface.DrawRect(x, y, w, h) + + local staminaColor = Color(0, 67, 28) -- #00431c + local staminaColorDark = Color(0, 30, 10) + + for i = 0, barWidth - 1 do + local progress = i / w + local color = self:LerpColor(progress, staminaColorDark, staminaColor) + surface.SetDrawColor(color) + surface.DrawRect(x + i, y, 1, h) + end + + local segmentWidth = w / 3 + for i = 1, 2 do + surface.SetDrawColor(Color(0, 0, 0, 100)) + surface.DrawRect(x + segmentWidth * i, y, 1, h) + end +end + + +function PLUGIN:DrawRankInfo(x, y, w, h) + local iconSize = ScaleSize(32) + if rankMaterial then + surface.SetMaterial(rankMaterial) + surface.SetDrawColor(Colors.white) + surface.DrawTexturedRect(x, y, iconSize, iconSize) + else + surface.SetDrawColor(Colors.gray_light) + surface.DrawRect(x, y, iconSize, iconSize) + draw.SimpleText("R", "MilitaryHUD_Medium", x + iconSize/2, y + iconSize/2, Colors.gray_dark, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + draw.SimpleText(LocalPlayer():GetSpecName(), "MilitaryHUD_SmallBold", x + iconSize + ScalePos(10), y + iconSize/2, Colors.white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +function PLUGIN:DrawHungerIndicator(x, y) + local hunger = LocalPlayer():GetLocalVar("hunger", 100) + hungerLerp = Lerp(FrameTime() * 5, hungerLerp, hunger) + + -- Иконка голода + local iconSize = ScaleSize(24) + if foodMaterial then + surface.SetMaterial(foodMaterial) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(x, y, iconSize, iconSize) + else + surface.SetDrawColor(Color(205, 133, 63)) + surface.DrawRect(x, y, iconSize, iconSize) + end + + -- Значение + local hungerColor = hungerLerp > 50 and Colors.white or Color(200, 50, 50) + draw.SimpleText(math.Round(hungerLerp) .. "%", "MilitaryHUD_SmallBold", x + iconSize + ScalePos(5), y + iconSize/2, hungerColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +function PLUGIN:DrawThirstIndicator(x, y) + local thirst = LocalPlayer():GetLocalVar("thirst", 100) + thirstLerp = Lerp(FrameTime() * 5, thirstLerp, thirst) + + -- Иконка жажды + local iconSize = ScaleSize(24) + if thirstMaterial then + surface.SetMaterial(thirstMaterial) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(x, y, iconSize, iconSize) + else + surface.SetDrawColor(Color(30, 144, 255)) + surface.DrawRect(x, y, iconSize, iconSize) + end + + -- Значение + local thirstColor = thirstLerp > 50 and Colors.white or Color(200, 50, 50) + draw.SimpleText(math.Round(thirstLerp) .. "%", "MilitaryHUD_SmallBold", x + iconSize + ScalePos(5), y + iconSize/2, thirstColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + + +function PLUGIN:DrawRadio() + local x, y = ScalePos(40), ScalePos(240) + local iconSize = ScaleSize(32) + local radioStatus = self:GetRadioStatus() + if radioMaterial then + surface.SetMaterial(radioMaterial) + surface.SetDrawColor(Colors.white) + surface.DrawTexturedRect(x, y, iconSize, iconSize) + else + surface.SetDrawColor(Colors.gray_light) + surface.DrawRect(x, y, iconSize, iconSize) + draw.SimpleText("R", "MilitaryHUD_Medium", x + iconSize/2, y + iconSize/2, Colors.gray_dark, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + local radioText = radioStatus and "Включена" or "Выключена" + local radioColor = radioStatus and Colors.green or Color(200, 50, 50) + draw.SimpleText("Рация:", "MilitaryHUD_MediumBold", x + iconSize + ScalePos(10), y + ScalePos(11), Colors.white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(radioText, "MilitaryHUD_MediumBold", x + iconSize + ScalePos(78), y + ScalePos(11), radioColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +function PLUGIN:DrawTime() + local x, y = ScalePos(40), ScalePos(290) + local iconSize = ScaleSize(32) + local timeText = self:GetFormattedTime() + if clockMaterial then + surface.SetMaterial(clockMaterial) + surface.SetDrawColor(Colors.white) + surface.DrawTexturedRect(x, y, iconSize, iconSize) + else + surface.SetDrawColor(Colors.gray_light) + surface.DrawRect(x, y, iconSize, iconSize) + draw.SimpleText("T", "MilitaryHUD_Medium", x + iconSize/2, y + iconSize/2, Colors.gray_dark, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + draw.SimpleText(timeText, "MilitaryHUD_MediumBold", x + iconSize + ScalePos(10), y + iconSize/2, Color(255, 255, 255, 165), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +function PLUGIN:DrawLogo() + local x, y = ScalePos(20), ScalePos(39) + local logoWidth, logoHeight = ScaleSize(128), ScaleSize(72) + if logoMaterial then + surface.SetMaterial(logoMaterial) + surface.SetDrawColor(Colors.white) + surface.DrawTexturedRect(x, y, logoWidth, logoHeight) + else + surface.SetDrawColor(Colors.dark_green) + surface.DrawRect(x, y, logoWidth/2, logoHeight) + draw.SimpleText("ЛОГО", "MilitaryHUD_Small", x + logoWidth/4, y + logoHeight/2, Colors.white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + --draw.SimpleText("Война на Украине", "MilitaryHUD_MediumBold", x + ScalePos(120), y + ScalePos(35), Colors.white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +function PLUGIN:DrawAmmoInfo() + local weapon = LocalPlayer():GetActiveWeapon() + if not IsValid(weapon) then return end + + local clip = weapon:Clip1() + local ammoType = weapon:GetPrimaryAmmoType() + local reserve = LocalPlayer():GetAmmoCount(ammoType) + + -- Пропускаем оружие без патронов (например, руки, граната) + if clip < 0 and (ammoType == -1 or reserve == 0) then return end + + local x, y = ScrW() - ScalePos(90), ScrH() - ScalePos(50) + + -- Отображаем патроны в обойме + if clip >= 0 then + draw.SimpleText(clip, "MilitaryHUD_Large", x, y, Colors.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + + -- Разделитель + draw.SimpleText("/", "MilitaryHUD_Medium", x + ScalePos(10), y, Colors.white_half, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Запасные патроны + draw.SimpleText(reserve, "MilitaryHUD_Medium", x + ScalePos(30), y, Colors.white_half, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + else + -- Если нет обоймы (например, РПГ), показываем только запас + draw.SimpleText(reserve, "MilitaryHUD_Large", x, y, Colors.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + -- Название оружия + local weaponName = weapon:GetPrintName() or weapon:GetClass() + draw.SimpleText(weaponName, "MilitaryHUD_Small", x + ScalePos(15), y - ScalePos(30), Colors.white_half, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) +end + +function PLUGIN:DrawPlayerInfo() + local x, y = ScalePos(40), ScrH() - ScalePos(210) + local infoWidth = ScaleSize(420) + + -- Индикаторы голода и жажды НАД здоровьем + self:DrawHungerIndicator(x, y - ScalePos(35)) + self:DrawThirstIndicator(x + ScalePos(100), y - ScalePos(35)) + + -- Полоски здоровья, брони и выносливости + self:DrawHealthBar(x, y + ScalePos(22), infoWidth, ScaleSize(12)) + self:DrawArmorBar(x, y + ScalePos(74), infoWidth, ScaleSize(12)) + self:DrawStaminaBar(x, y + ScalePos(126), infoWidth, ScaleSize(12)) + + -- Информация о ранге + self:DrawRankInfo(x, y + ScalePos(152), ScaleSize(223), ScaleSize(32)) +end + +function PLUGIN:HUDPaint() + local lp = LocalPlayer() + if (!lp:GetCharacter() or !lp:Alive() or ix.option.Get("disablehud", false)) then + return + end + CreateHUDFonts() + self:DrawPlayerInfo() + self:DrawAmmoInfo() + self:DrawRadio() + self:DrawTime() + self:DrawLogo() +end + + +function PLUGIN:PostDrawTranslucentRenderables() + local target = self:GetLookTarget() + if IsValid(target) then + self:DrawTargetPlayerInfo(target) + end +end + +-- Шрифты для информации о цели +surface.CreateFont("TargetInfo_Name", { + font = "Montserrat", + size = 38, + weight = 600 +}) + +surface.CreateFont("TargetInfo_Detail", { + font = "Montserrat", + size = 30, + weight = 400 +}) + +surface.CreateFont("TargetInfo_Rank", { + font = "Montserrat", + size = 26, + weight = 500 +}) + +-- Получение цели +function PLUGIN:GetLookTarget() + local trace = LocalPlayer():GetEyeTrace() + + if not trace.Hit then return nil end + if not IsValid(trace.Entity) then return nil end + if not trace.Entity:IsPlayer() then return nil end + if trace.Entity == LocalPlayer() then return nil end + if trace.Entity:Team() != LocalPlayer():Team() then return nil end + + local distance = LocalPlayer():GetPos():Distance(trace.Entity:GetPos()) + if distance > 250 then return nil end + + return trace.Entity +end + +-- Отрисовка информации о цели +function PLUGIN:DrawTargetPlayerInfo() + -- Проверка настройки + if ix.option.Get("disableplayerinfo", false) then + return + end + + local target = self:GetLookTarget() + if not target then return end + + local char = target:GetCharacter() + if not char then return end + + -- Получаем данные + local name = target:Name() + + local podrName = target:GetPodrName() + if not podrName or podrName == false then + podrName = "Неизвестно" + end + + local specName = target:GetSpecName() + if not specName or specName == false then + specName = "Неизвестно" + end + + local rankName = target:GetRankName() + if not rankName or rankName == false then + rankName = "Неизвестно" + end + + -- Позиция 3D2D над головой + local pos = target:GetPos() + Vector(0, 0, 85) + + -- Угол всегда смотрит на игрока + local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90) + + -- Масштаб для 3D2D + local scale = 0.1 + + cam.Start3D2D(pos, ang, scale) + -- Настройки текста + local lineHeight = 42 + local yOffset = 0 + + -- Имя (крупно, по центру) + draw.SimpleText(name, "TargetInfo_Name", 0, yOffset, Color(0, 146, 12), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + yOffset = yOffset + lineHeight + if podrName and podrName != "Новоприбывшие" then + local unitText = podrName .. " | " .. specName + draw.SimpleText(unitText, "TargetInfo_Detail", 0, yOffset, Color(0, 146, 12), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + yOffset = yOffset + lineHeight + + -- Звание + draw.SimpleText(rankName, "TargetInfo_Rank", 0, yOffset, Color(0, 146, 12), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText(rankName, "TargetInfo_Rank", 0, yOffset, Color(0, 146, 12), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + cam.End3D2D() +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/hud/languages/sh_english.lua b/garrysmod/gamemodes/militaryrp/plugins/hud/languages/sh_english.lua new file mode 100644 index 0000000..b6b0713 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/hud/languages/sh_english.lua @@ -0,0 +1,13 @@ +NAME = "English" + +LANGUAGE = { + -- Category + optHUD = "HUD", + + -- Settings + optDisableHUD = "Hide HUD", + optDisableHUDDesc = "Completely hide the game interface", + + optDisablePlayerInfo = "Hide Player Info", + optDisablePlayerInfoDesc = "Hide player information when looking at them", +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/hud/languages/sh_russian.lua b/garrysmod/gamemodes/militaryrp/plugins/hud/languages/sh_russian.lua new file mode 100644 index 0000000..dde5a04 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/hud/languages/sh_russian.lua @@ -0,0 +1,13 @@ +NAME = "Русский" + +LANGUAGE = { + -- Category + optHUD = "HUD", + + -- Settings + optDisableHUD = "Скрыть HUD", + optDisableHUDDesc = "Полностью скрыть игровой интерфейс", + + optDisablePlayerInfo = "Скрыть инфо игроков", + optDisablePlayerInfoDesc = "Скрыть информацию о игроках при взгляде на них", +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/hud/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/hud/sh_plugin.lua new file mode 100644 index 0000000..46c0dcc --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/hud/sh_plugin.lua @@ -0,0 +1,34 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Military RP HUD" +PLUGIN.author = "Military RP Team" +PLUGIN.desc = "Military-themed HUD for Military RP" + +ix.util.Include("cl_plugin.lua") + +function PLUGIN:CanDrawAmmoHUD() + return false +end + +function PLUGIN:ShouldHideBars() + return true +end + +-- Настройки для HUD (using localization) +ix.option.Add("disablehud", ix.type.bool, false, { + category = "optHUD", + name = "optDisableHUD", + description = "optDisableHUDDesc" +}) + +ix.option.Add("disableplayerinfo", ix.type.bool, false, { + category = "optHUD", + name = "optDisablePlayerInfo", + description = "optDisablePlayerInfoDesc" +}) + +if CLIENT then + -- Обновление ранга + net.Receive("MilitaryRP_RankUpdate", function() + -- Здесь можно обновить ранг игрока + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_inventory.lua b/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_inventory.lua new file mode 100644 index 0000000..cb5b645 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_inventory.lua @@ -0,0 +1,1186 @@ +local RECEIVER_NAME = "ixInventoryItem" + + ICON_RENDER_QUEUE = ICON_RENDER_QUEUE or {} + + local function RenderNewIcon(panel, itemTable) + local model = itemTable:GetModel() + if ((itemTable.iconCam and !ICON_RENDER_QUEUE[string.lower(model)]) or itemTable.forceRender) then + local iconCam = itemTable.iconCam + iconCam = { + cam_pos = iconCam.pos, + cam_ang = iconCam.ang, + cam_fov = iconCam.fov, + } + ICON_RENDER_QUEUE[string.lower(model)] = true + + panel.Icon:RebuildSpawnIconEx( + iconCam + ) + end + end + + local function InventoryAction(action, itemID, invID, data) + net.Start("ixInventoryAction") + net.WriteString(action) + net.WriteUInt(itemID, 32) + net.WriteUInt(invID, 32) + net.WriteTable(data or {}) + net.SendToServer() + end + + local PANEL = {} + + AccessorFunc(PANEL, "itemTable", "ItemTable") + AccessorFunc(PANEL, "inventoryID", "InventoryID") + + function PANEL:Init() + self:Droppable(RECEIVER_NAME) + end + + function PANEL:DoDoubleClick() + local invPanel = IsValid(ix.gui.openedStorage) and ix.gui.openedStorage.storageInventory + + if IsValid(invPanel) then + if self:GetInventoryID() ~= ix.gui.inv1.invID then + invPanel = ix.gui.inv1 + end + + local newX, newY = invPanel:FindEmptySlotItemPanel(self) + if newX ~= nil and newY ~= nil then + self:Move(newX, newY, invPanel) + return + end + end + + local itemTable = self.itemTable + local inventory = self.inventoryID + + if (itemTable and inventory) then + itemTable.player = LocalPlayer() + local defaultAction = nil + + local priorityActions = {"use", "eat", "drink", "equip", "wear", "open"} + for _, actionName in ipairs(priorityActions) do + local v = itemTable.functions[actionName] + if (v and (not v.OnCanRun or v.OnCanRun(itemTable) ~= false)) then + defaultAction = actionName + break + end + end + + if (not defaultAction) then + for k, v in SortedPairs(itemTable.functions) do + if (k == "drop" or k == "combine" or (v.OnCanRun and v.OnCanRun(itemTable) == false)) then + continue + end + defaultAction = k + break + end + end + + if (defaultAction) then + local v = itemTable.functions[defaultAction] + local send = true + + if (v.OnClick) then + send = v.OnClick(itemTable) + end + + if (v.sound) then + surface.PlaySound(v.sound) + end + + if (send ~= false) then + InventoryAction(defaultAction, itemTable.id, inventory) + end + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_properties_2.ogg") + end + itemTable.player = nil + end + end + + function PANEL:DoDoubleClickShift() + local item = self:GetItemTable() + if item.inv_slot and type(item.inv_slot) == "string" then + if not LocalPlayer():GetCharacter():GetInventorySlot(item.inv_slot) then return end + local invPanel = ix.gui["inv"..LocalPlayer():GetCharacter():GetInventorySlot(item.inv_slot):GetID()] + if not IsValid(invPanel) then return end + if item.invID != LocalPlayer():GetCharacter():GetInventory():GetID() then + invPanel = ix.gui.inv1 + end + local newX, newY = invPanel:FindEmptySlotItemPanel(self) + if newX ~= nil and newY ~= nil then + self:Move(newX, newY, invPanel) + end + end + end + + function PANEL:OnMousePressed(code) + if code == MOUSE_LEFT then + if self.DoDoubleClick ~= nil and self.LastClickTime ~= nil and SysTime() - self.LastClickTime < 0.2 and not input.IsKeyDown(KEY_LSHIFT) then + self:DoDoubleClick() + end + + if self.DoDoubleClick ~= nil and self.LastClickTime ~= nil and SysTime() - self.LastClickTime < 0.2 and input.IsKeyDown(KEY_LSHIFT) then + self:DoDoubleClickShift() + end + + self.LastClickTime = SysTime() + + if self:IsDraggable() then + self:MouseCapture(true) + self:DragMousePress(code) + + self.clickX, self.clickY = input.GetCursorPos() + end + elseif (code == MOUSE_RIGHT and self.DoRightClick) then + self:DoRightClick() + end + end + + function PANEL:OnMouseReleased(code) + if (!dragndrop.m_ReceiverSlot or dragndrop.m_ReceiverSlot.Name != RECEIVER_NAME) then + self:OnDrop(dragndrop.IsDragging()) + end + self:DragMouseRelease(code) + --self:SetZPos(99) + self:MouseCapture(false) + end + + function PANEL:DoRightClick() + local itemTable = self.itemTable + local inventory = self.inventoryID + + if (itemTable and inventory) then + itemTable.player = LocalPlayer() + + local menu = DermaMenu() + local override = hook.Run("CreateItemInteractionMenu", self, menu, itemTable) + + if (override == true) then + if (menu.Remove) then + menu:Remove() + end + + return + end + + for k, v in SortedPairs(itemTable.functions) do + if (k == "drop" or k == "combine" or (v.OnCanRun and v.OnCanRun(itemTable) == false)) then + continue + end + + if (v.isMulti) then + local subMenu, subMenuOption = menu:AddSubMenu(L(v.name or k), function() + itemTable.player = LocalPlayer() + local send = true + + if (v.OnClick) then + send = v.OnClick(itemTable) + end + + if (v.sound) then + surface.PlaySound(v.sound) + end + + if (send != false) then + InventoryAction(k, itemTable.id, inventory) + end + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_properties_2.ogg") + itemTable.player = nil + end) + subMenuOption:SetImage(v.icon or "icon16/brick.png") + + if (v.multiOptions) then + local options = isfunction(v.multiOptions) and v.multiOptions(itemTable, LocalPlayer()) or v.multiOptions + + for _, sub in pairs(options) do + subMenu:AddOption(L(sub.name or "subOption"), function() + itemTable.player = LocalPlayer() + local send = true + + if (sub.OnClick) then + send = sub.OnClick(itemTable) + end + + if (sub.sound) then + surface.PlaySound(sub.sound) + end + + if (send != false) then + InventoryAction(k, itemTable.id, inventory, sub.data) + end + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_properties_2.ogg") + itemTable.player = nil + end) + end + end + else + menu:AddOption(L(v.name or k), function() + itemTable.player = LocalPlayer() + local send = true + + if (v.OnClick) then + send = v.OnClick(itemTable) + end + + if (v.sound) then + surface.PlaySound(v.sound) + end + + if (send != false) then + InventoryAction(k, itemTable.id, inventory) + end + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_properties_2.ogg") + itemTable.player = nil + end):SetImage(v.icon or "icon16/brick.png") + end + end + + local info = itemTable.functions.drop + + if (info and info.OnCanRun and info.OnCanRun(itemTable) != false) then + menu:AddOption(L(info.name or "drop"), function() + itemTable.player = LocalPlayer() + local send = true + + if (info.OnClick) then + send = info.OnClick(itemTable) + end + + if (info.sound) then + surface.PlaySound(info.sound) + end + + if (send != false) then + InventoryAction("drop", itemTable.id, inventory) + end + itemTable.player = nil + end):SetImage(info.icon or "icon16/brick.png") + end + surface.PlaySound(Sound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_properties.ogg")) + + menu:Open() + itemTable.player = nil + end + end + + function PANEL:OnDrop(bDragging, inventoryPanel, inventory, gridX, gridY) + local item = self.itemTable + + if (!item or !bDragging) then + return + end + + if (!IsValid(inventoryPanel)) then + local inventoryID = self.inventoryID + + if (inventoryID) then + InventoryAction("drop", item.id, inventoryID, {}) + end + elseif (inventoryPanel:IsAllEmpty(gridX, gridY, item.width, item.height, self)) then + local oldX, oldY = self.gridX, self.gridY + + if (oldX != gridX or oldY != gridY or self.inventoryID != inventoryPanel.invID) then + self:Move(gridX, gridY, inventoryPanel) + end + elseif (inventoryPanel.combineItem) then + local combineItem = inventoryPanel.combineItem + local inventoryID = combineItem.invID + + if (inventoryID) then + if ix.item.inventories[inventoryID] and ix.item.inventories[inventoryID].vars and ix.item.inventories[inventoryID].vars.isNewVendor then return end + if ix.item.inventories[item.invID] and ix.item.inventories[item.invID].vars and ix.item.inventories[item.invID].vars.isNewVendor then return end + combineItem.player = LocalPlayer() + if (combineItem.functions.combine.sound) then + surface.PlaySound(combineItem.functions.combine.sound) + end + + InventoryAction("combine", combineItem.id, inventoryID, {item.id}) + combineItem.player = nil + end + end + end + + function PANEL:Move(newX, newY, givenInventory, bNoSend) + local iconSize = givenInventory.iconSize + local oldX, oldY = self.gridX, self.gridY + local oldParent = self:GetParent() + if givenInventory.invID == LocalPlayer():GetCharacter():GetInventory():GetID() and SORT_CATEGORY then + LocalPlayer():Notify("Во время сортировки в своём инвентаре нельзя перемещать вещи!") + return false + end + + if (givenInventory:OnTransfer(oldX, oldY, newX, newY, oldParent, bNoSend) == false) then + return + end + + local x = (newX - 1) * iconSize + 4 + local y = (newY - 1) * iconSize + givenInventory:GetPadding(2) + + self.gridX = newX + self.gridY = newY + + self:SetParent(givenInventory) + self:SetPos(x, y) + + if (self.slots) then + for _, v in next, self.slots do + if (IsValid(v) and v.item == self) then + v.item = nil + end + end + end + + self.slots = {} + + for currentX = 1, self.gridW do + for currentY = 1, self.gridH do + local slot = givenInventory.slots[self.gridX + currentX - 1][self.gridY + currentY - 1] + + slot.item = self + self.slots[#self.slots + 1] = slot + end + end + local table_item_pickup_sound = { + "anomalyzone/ui_sounds/item_pick_up/inv_items_cloth_1.ogg", + "anomalyzone/ui_sounds/item_pick_up/inv_items_cloth_2.ogg", + "anomalyzone/ui_sounds/item_pick_up/inv_items_cloth_3.ogg", + "anomalyzone/ui_sounds/item_pick_up/inv_items_generic_1.ogg", + "anomalyzone/ui_sounds/item_pick_up/inv_items_generic_2.ogg", + "anomalyzone/ui_sounds/item_pick_up/inv_items_generic_3.ogg" + } + local item = self:GetItemTable() + local sound = item.MoveSound + if item.MoveSound and istable(item.MoveSound) then + sound = table.Random(item.MoveSound) + elseif item.MoveSound == nil or !item.MoveSound and table_item_pickup_sound != nil then + sound = table.Random(table_item_pickup_sound) + else + sound = "anomalyzone/ui_sounds/item_pick_up/inv_items_generic_3.ogg" + end + LocalPlayer():EmitSound(sound) + local inv = ix.inventory.Get(givenInventory.invID) + if inv != LocalPlayer():GetCharacter():GetInventory() then + net.Start("INVENTORY.ValidateItemData") + net.WriteString(tostring(inv:GetID())) + net.WriteString(tostring(item:GetID())) + net.SendToServer() + end + end + + function PANEL:PaintOver(width, height) + local itemTable = self.itemTable + + if (itemTable and itemTable.PaintOver) then + itemTable.PaintOver(self, itemTable, width, height) + end + end + + function PANEL:ExtraPaint(width, height) + end + + function PANEL:Paint(width, height) + self:ExtraPaint(width, height) + end + + local INV_MOUSE_DATA = { + --["sniper"] = { + -- position = {.38, .013}, + -- info = {.071, .335}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["primary"] = { + -- position = {.54, .013}, + -- info = {.071, .335}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["armor"] = { + -- position = {.454, .153}, + -- info = {.083, .19}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["shlem"] = { + -- position = {.454, .018}, + -- info = {.083, .107}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["electronika"] = { + -- position = {.382, .372}, + -- info = {.034, .053}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["pda"] = { + -- position = {.417, .372}, + -- info = {.034, .053}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["flashlight"] = { + -- position = {.54, .374}, + -- info = {.034, .053}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["detector"] = { + -- position = {.576, .374}, + -- info = {.034, .053}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["sumka"] = { + -- position = {.454, .374}, + -- info = {.083, .118}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["knife"] = { + -- position = {.38, .442}, + -- info = {.074, .05}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["pistol"] = { + -- position = {.537, .444}, + -- info = {.074, .05}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["hotkey_1"] = { + -- position = {.38, .52}, + -- info = {.0592, .075}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["hotkey_2"] = { + -- position = {.437, .52}, + -- info = {.0592, .075}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["hotkey_3"] = { + -- position = {.494, .52}, + -- info = {.0592, .075}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["hotkey_4"] = { + -- position = {.552, .52}, + -- info = {.0592, .075}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["art_slot_1"] = { + -- position = {.38, .618}, + -- info = {.048, .063}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["art_slot_2"] = { + -- position = {.426, .618}, + -- info = {.048, .063}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["art_slot_3"] = { + -- position = {.472, .618}, + -- info = {.048, .063}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["art_slot_4"] = { + -- position = {.517, .618}, + -- info = {.048, .063}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + --["art_slot_5"] = { + -- position = {.563, .618}, + -- info = {.048, .063}, + -- texture_info = { + -- x = 215, + -- y = 545, + -- w = 90, + -- h = 360, + -- }, + --}, + } + + + function PANEL:OnCursorEntered() + local item = self.itemTable + if item and item.inv_slot then + if type(item.inv_slot) == "string" then + if LocalPlayer():GetCharacter():GetInventorySlot(item.inv_slot) --[[and table.Count(LocalPlayer():GetCharacter():GetInventorySlot(item.inv_slot):GetItems()) <= 0]] then + for k, v in pairs(INV_MOUSE_DATA) do + if k == item.inv_slot then + if IsValid(ix.gui["inv" .. LocalPlayer():GetCharacter():GetInventorySlot(item.inv_slot):GetID()]) then + local x, y = ScrW() * INV_MOUSE_DATA[item.inv_slot].position[1], ScrH() * INV_MOUSE_DATA[item.inv_slot].position[2] + local width, height = ScrW() * INV_MOUSE_DATA[item.inv_slot].info[1], ScrH() * INV_MOUSE_DATA[item.inv_slot].info[2] + hook.Add("PostRenderVGUI", "SlotTrasferHelper", function() + surface.SetDrawColor(255, 255, 255, 70) + --surface.SetMaterial(Material("stalplay/ui/ui_actor_sleep_screen.png")) + surface.DrawPartialTexturedRect( x, y, width, height, INV_MOUSE_DATA[item.inv_slot].texture_info.x, INV_MOUSE_DATA[item.inv_slot].texture_info.y, INV_MOUSE_DATA[item.inv_slot].texture_info.w, INV_MOUSE_DATA[item.inv_slot].texture_info.h, 1024, 1024 ) + end) + end + end + end + end + elseif type(item.inv_slot) == "table" then + hook.Add("PostRenderVGUI", "SlotTrasferHelper", function() + for slot, result in pairs(item.inv_slot) do + if LocalPlayer():GetCharacter():GetInventorySlot(result) --[[and table.Count(LocalPlayer():GetCharacter():GetInventorySlot(result):GetItems()) <= 0]] then + for k, v in pairs(INV_MOUSE_DATA) do + if k == result then + if IsValid(ix.gui["inv" .. LocalPlayer():GetCharacter():GetInventorySlot(result):GetID()]) then + local x, y = ScrW() * INV_MOUSE_DATA[result].position[1], ScrH() * INV_MOUSE_DATA[result].position[2] + local width, height = ScrW() * INV_MOUSE_DATA[result].info[1], ScrH() * INV_MOUSE_DATA[result].info[2] + surface.SetDrawColor(255, 255, 255, 70) + --surface.SetMaterial(Material("stalplay/ui/ui_actor_sleep_screen.png")) + surface.DrawPartialTexturedRect( x, y, width, height, INV_MOUSE_DATA[result].texture_info.x, INV_MOUSE_DATA[result].texture_info.y, INV_MOUSE_DATA[result].texture_info.w, INV_MOUSE_DATA[result].texture_info.h, 1024, 1024 ) + end + end + end + end + end + end) + end + end + end + + function PANEL:OnCursorExited() + hook.Remove("PostRenderVGUI", "SlotTrasferHelper") + end + + vgui.Register("ixItemIcon", PANEL, "SpawnIcon") + + PANEL = {} + DEFINE_BASECLASS("DFrame") + + AccessorFunc(PANEL, "iconSize", "IconSize", FORCE_NUMBER) + AccessorFunc(PANEL, "bHighlighted", "Highlighted", FORCE_BOOL) + + function PANEL:Init() + self:SetIconSize(ScreenScale(17.90)) + self:ShowCloseButton(false) + self:SetDraggable(false) + self:SetSizable(false) + --self:MakePopup() + self:SetTitle() + self:Receiver(RECEIVER_NAME, self.ReceiveDrop) + self.Paint = function() end + + self.btnMinim:SetVisible(false) + self.btnMinim:SetMouseInputEnabled(false) + self.btnMaxim:SetVisible(false) + self.btnMaxim:SetMouseInputEnabled(false) + + self.panels = {} + hook.Run("StalkerInventoryCreated") + end + + function PANEL:GetPadding(index) + return select(index, self:GetDockPadding()) + end + + function PANEL:SetTitle(text) + if (text == nil) then + self.oldPadding = {self:GetDockPadding()} + + self.lblTitle:SetText("") + self.lblTitle:SetVisible(false) + + self:DockPadding(5, 5, 5, 5) + else + if (self.oldPadding) then + self:DockPadding(unpack(self.oldPadding)) + self.oldPadding = nil + end + + BaseClass.SetTitle(self, text) + end + end + + function PANEL:FitParent(invWidth, invHeight) + local parent = self:GetParent() + + if (!IsValid(parent)) then + return + end + + local width, height = parent:GetSize() + local padding = 4 + local iconSize + + if (invWidth > invHeight) then + iconSize = (width - padding * 2) / invWidth + elseif (invHeight > invWidth) then + iconSize = (height - padding * 2) / invHeight + else + iconSize = (height - padding * 2) / invHeight - 4 + end + + self:SetSize(iconSize * invWidth + padding * 2, iconSize * invHeight + padding * 2) + self:SetIconSize(iconSize) + end + + function PANEL:OnRemove() + if (self.childPanels) then + for _, v in ipairs(self.childPanels) do + if (v != self) then + v:Remove() + end + end + end + hook.Remove("PostRenderVGUI", "SlotTrasferHelper") + hook.Run("StalkerInventoryRemoved") + end + + function PANEL:ViewOnly() + self.viewOnly = true + + for _, icon in pairs(self.panels) do + icon.OnMousePressed = nil + icon.OnMouseReleased = nil + icon.doRightClick = nil + end + end + + function PANEL:SetInventory(inventory, bFitParent) + if (inventory.slots) then + local invWidth, invHeight = inventory:GetSize() + self.invID = inventory:GetID() + + if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels and inventory != LocalPlayer():GetCharacter():GetInventory()) then + self:SetIconSize(ix.gui.inv1:GetIconSize()) + self:SetPaintedManually(true) + self.bNoBackgroundBlur = true + + ix.gui.inv1.childPanels[#ix.gui.inv1.childPanels + 1] = self + elseif (bFitParent) then + self:FitParent(invWidth, invHeight) + else + self:SetSize(self.iconSize, self.iconSize) + end + + self:SetGridSize(invWidth, invHeight) + + for x, items in next, inventory.slots do + for y, data in next, items do + if (!data.id) then continue end + + local item = ix.item.instances[data.id] + + if (item and !IsValid(self.panels[item.id])) then + local function AddItemIcon() + local icon = self:AddIcon(item:GetModel() or "models/props_junk/popcan01a.mdl", + x, y, item.width, item.height, item:GetSkin()) + + if (IsValid(icon)) then + icon:SetHelixTooltip(function(tooltip) + ix.hud.PopulateItemTooltip(tooltip, item) + end) + + self.panels[item.id] = icon + end + end + if SORT_CATEGORY == nil then + AddItemIcon() + else + if string.find(item.uniqueID, "_flipped") then + if SORT_CATEGORY != nil and type(SORT_CATEGORY) == "string" then + if string.find(item.subcategory:lower(), SORT_CATEGORY:lower()) and LocalPlayer():GetCharacter():GetInventory() == inventory then + AddItemIcon() + elseif SORT_CATEGORY != nil and inventory != LocalPlayer():GetCharacter():GetInventory() then + AddItemIcon() + end + elseif SORT_CATEGORY != nil and type(SORT_CATEGORY) == "table" then + if inventory != LocalPlayer():GetCharacter():GetInventory() then + AddItemIcon() + else + for k, v in next, SORT_CATEGORY do + if string.find(item.subcategory:lower(), tostring(v:lower())) and LocalPlayer():GetCharacter():GetInventory() == inventory then + AddItemIcon() + end + end + end + end + else + if SORT_CATEGORY != nil and type(SORT_CATEGORY) == "string" then + if string.find(item.category:lower(), SORT_CATEGORY:lower()) and LocalPlayer():GetCharacter():GetInventory() == inventory then + AddItemIcon() + elseif SORT_CATEGORY != nil and inventory != LocalPlayer():GetCharacter():GetInventory() then + AddItemIcon() + end + elseif SORT_CATEGORY != nil and type(SORT_CATEGORY) == "table" then + if inventory != LocalPlayer():GetCharacter():GetInventory() then + AddItemIcon() + else + for k, v in next, SORT_CATEGORY do + if string.find(item.category:lower(), tostring(v:lower())) and LocalPlayer():GetCharacter():GetInventory() == inventory then + AddItemIcon() + end + end + end + end + end + end + end + end + end + end + end + + + function PANEL:SetGridSize(w, h) + local iconSize = self.iconSize + local newWidth = w * iconSize + 8 + local newHeight = h * iconSize + self:GetPadding(2) + self:GetPadding(4) + + self.gridW = w + self.gridH = h + + self:SetSize(newWidth, newHeight) + self:SetMinWidth(newWidth) + self:SetMinHeight(newHeight) + self:BuildSlots() + end + + function PANEL:PerformLayout(width, height) + BaseClass.PerformLayout(self, width, height) + + if (self.Sizing and self.gridW and self.gridH) then + local newWidth = (width - 8) / self.gridW + local newHeight = (height - self:GetPadding(2) + self:GetPadding(4)) / self.gridH + + self:SetIconSize((newWidth + newHeight) / 2) + self:RebuildItems() + end + end + + function PANEL:BuildSlots() + local iconSize = self.iconSize + + self.slots = self.slots or {} + + local function PaintSlot(slot, w, h) + local inventory = ix.item.inventories[self.invID] + if inventory and inventory.vars and inventory.vars.isBag and ix.stalker.slots[inventory.vars.isBag] then + --nice + elseif inventory:GetID() == LocalPlayer():GetCharacter():GetInventory():GetID() or inventory and inventory.vars and inventory.vars.isBag and not ix.stalker.slots[inventory.vars.isBag] then + surface.SetDrawColor(35, 35, 35, 85) + surface.DrawRect(0, 0, w - 2, h - 2) + + surface.SetDrawColor(0, 0, 0, 250) + surface.DrawOutlinedRect(0, 0, w - 2, h - 2) + elseif inventory:GetID() != LocalPlayer():GetCharacter():GetInventory():GetID() or inventory and inventory.vars and inventory.vars.isBag and not ix.stalker.slots[inventory.vars.isBag] then + surface.SetDrawColor(35, 35, 35, 85) + surface.DrawRect(0, 0, w - 2, h - 2) + + surface.SetDrawColor(0, 0, 0, 250) + surface.DrawOutlinedRect(0, 0, w - 2, h - 2) + end + end + + for _, v in ipairs(self.slots) do + for _, v2 in ipairs(v) do + v2:Remove() + end + end + + self.slots = {} + + for x = 1, self.gridW do + self.slots[x] = {} + + for y = 1, self.gridH do + local slot = self:Add("DPanel") + slot:SetZPos(-999) + slot.gridX = x + slot.gridY = y + slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2)) + slot:SetSize(iconSize, iconSize) + slot.Paint = PaintSlot + self.slots[x][y] = slot + end + end + end + + function PANEL:RebuildItems() + local iconSize = self.iconSize + + for x = 1, self.gridW do + for y = 1, self.gridH do + local slot = self.slots[x][y] + + slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2)) + slot:SetSize(iconSize, iconSize) + end + end + + for _, v in pairs(self.panels) do + if (IsValid(v)) then + v:SetPos(self.slots[v.gridX][v.gridY]:GetPos()) + v:SetSize(v.gridW * iconSize, v.gridH * iconSize) + end + end + end + + local INV_DATA = { + ["armor"] = { + position = {.445, .15}, + slotssize = 23 + }, + } + + function PANEL:PaintDragPreview(width, height, mouseX, mouseY, itemPanel) + local iconSize = self.iconSize + local item = itemPanel:GetItemTable() + + if (item) then + local inventory = ix.item.inventories[self.invID] + local dropX = math.ceil((mouseX - 4 - (itemPanel.gridW - 1) * 32) / iconSize) + local dropY = math.ceil((mouseY - self:GetPadding(2) - (itemPanel.gridH - 1) * 32) / iconSize) + + local hoveredPanel = vgui.GetHoveredPanel() + + if (IsValid(hoveredPanel) and hoveredPanel != itemPanel and hoveredPanel.GetItemTable) then + local hoveredItem = hoveredPanel:GetItemTable() + + if (hoveredItem) then + local info = hoveredItem.functions.combine + + if (info and info.OnCanRun and info.OnCanRun(hoveredItem, {item.id}) != false) then + surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", self, Color(200, 0, 0)), 20)) + surface.DrawRect( + hoveredPanel.x, + hoveredPanel.y, + hoveredPanel:GetWide(), + hoveredPanel:GetTall() + ) + + self.combineItem = hoveredItem + + return + end + end + end + + self.combineItem = nil + + -- don't draw grid if we're dragging it out of bounds + if (inventory) then + local invWidth, invHeight = inventory:GetSize() + + if (dropX < 1 or dropY < 1 or + dropX + itemPanel.gridW - 1 > invWidth or + dropY + itemPanel.gridH - 1 > invHeight) then + return + end + end + + local bEmpty = true + + for x = 0, itemPanel.gridW - 1 do + for y = 0, itemPanel.gridH - 1 do + local x2 = dropX + x + local y2 = dropY + y + + bEmpty = self:IsEmpty(x2, y2, itemPanel) + + if (!bEmpty) then + goto finish + end + end + end + + ::finish:: + local previewColor = ColorAlpha(bEmpty and Color(95, 186, 119) or Color(232, 114, 114), 20) + surface.SetDrawColor(previewColor) + surface.DrawRect( + (dropX - 1) * iconSize + 4, + (dropY - 1) * iconSize + self:GetPadding(2), + itemPanel:GetWide(), + itemPanel:GetTall() + ) + end + end + + function PANEL:PaintOver(width, height) + local panel = self.previewPanel + if (IsValid(panel)) then + local itemPanel = (dragndrop.GetDroppable() or {})[1] + + if (IsValid(itemPanel)) then + self:PaintDragPreview(width, height, self.previewX, self.previewY, itemPanel) + end + end + + self.previewPanel = nil + end + + function PANEL:IsEmpty(x, y, this) + return (self.slots[x] and self.slots[x][y]) and (!IsValid(self.slots[x][y].item) or self.slots[x][y].item == this) + end + + function PANEL:IsAllEmpty(x, y, width, height, this) + for x2 = 0, width - 1 do + for y2 = 0, height - 1 do + if (!self:IsEmpty(x + x2, y + y2, this)) then + return false + end + end + end + + return true + end + + function PANEL:OnTransfer(oldX, oldY, x, y, oldInventory, noSend) + local inventories = ix.item.inventories + local inventory = inventories[oldInventory.invID] + local inventory2 = inventories[self.invID] + local item + + if (inventory) then + item = inventory:GetItemAt(oldX, oldY) + + if (!item) then + return false + end + + if (hook.Run("CanTransferItem", item, inventories[oldInventory.invID], inventories[self.invID]) == false) then + return false, "notAllowed" + end + + if (item.CanTransfer and + item:CanTransfer(inventory, inventory != inventory2 and inventory2 or nil) == false) then + return false + end + end + + if (!noSend) then + net.Start("ixInventoryMove") + net.WriteUInt(oldX, 6) + net.WriteUInt(oldY, 6) + net.WriteUInt(x, 6) + net.WriteUInt(y, 6) + net.WriteUInt(oldInventory.invID, 32) + net.WriteUInt(self != oldInventory and self.invID or oldInventory.invID, 32) + net.SendToServer() + end + + if (inventory) then + inventory.slots[oldX][oldY] = nil + end + + if (item and inventory2) then + inventory2.slots[x] = inventory2.slots[x] or {} + inventory2.slots[x][y] = item + end + end + + function PANEL:AddIcon(model, x, y, w, h, skin) + local iconSize = self.iconSize + + w = w or 1 + h = h or 1 + if (self.slots[x] and self.slots[x][y]) then + local panel = self:Add("ixItemIcon") + panel:SetSize(w * iconSize, h * iconSize) + panel:SetZPos(999) + panel:InvalidateLayout(true) + panel:SetModel(model, skin) + panel:SetPos(self.slots[x][y]:GetPos()) + panel.gridX = x + panel.gridY = y + panel.gridW = w + panel.gridH = h + + local inventory = ix.item.inventories[self.invID] + + if (!inventory) then + return + end + + local itemTable = inventory:GetItemAt(panel.gridX, panel.gridY) + + + panel:SetInventoryID(inventory:GetID()) + panel:SetItemTable(itemTable) + + if (self.panels[itemTable:GetID()]) then + self.panels[itemTable:GetID()]:Remove() + end + + if itemTable.img then + panel.Icon:SetVisible(false) + panel.ExtraPaint = function(this, panel_x, panel_y) + local icon = itemTable.img + if (icon) then + surface.SetMaterial(icon) + surface.SetDrawColor(color_white) + surface.DrawTexturedRect(0, 0, panel_x, panel_y) + end + end + else + RenderNewIcon(panel, itemTable) + end + + panel.slots = {} + + for i = 0, w - 1 do + for i2 = 0, h - 1 do + local slot = self.slots[x + i] and self.slots[x + i][y + i2] + + if (IsValid(slot)) then + slot.item = panel + panel.slots[#panel.slots + 1] = slot + else + for _, v in ipairs(panel.slots) do + v.item = nil + end + + panel:Remove() + + return + end + end + end + + return panel + end + end + + function PANEL:ReceiveDrop(panels, bDropped, menuIndex, x, y) + local panel = panels[1] + + if (!IsValid(panel)) then + self.previewPanel = nil + return + end + + if (bDropped) then + local inventory = ix.item.inventories[self.invID] + + if (inventory and panel.OnDrop) then + local dropX = math.ceil((x - 4 - (panel.gridW - 1) * 32) / self.iconSize) + local dropY = math.ceil((y - self:GetPadding(2) - (panel.gridH - 1) * 32) / self.iconSize) + + panel:OnDrop(true, self, inventory, dropX, dropY) + end + + self.previewPanel = nil + else + self.previewPanel = panel + self.previewX = x + self.previewY = y + end + end + + function PANEL:FindEmptySlotItemPanel(itemPanel) + local item = itemPanel.itemTable + local inventory_w, inventory_h, item_w, item_h = self.gridW, self.gridH, item.width, item.height + + if item_w > inventory_w or item_h > inventory_h then + return false + end + + for y = 1, inventory_h - (item_h - 1) do + for x = 1, inventory_w - (item_w - 1) do + if self:IsAllEmpty(x, y, item_w, item_h, itemPanel) then + return x, y + end + end + end + end + + vgui.Register("ixInventory", PANEL, "DFrame") + + hook.Remove("CreateMenuButtons", "ixInventory") + + hook.Add("PostRenderVGUI", "ixInvHelper", function() + local pnl = ix.gui.inv1 + + hook.Run("PostDrawInventory", pnl) + end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_main_inv.lua b/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_main_inv.lua new file mode 100644 index 0000000..f06a5db --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_main_inv.lua @@ -0,0 +1,218 @@ +local PANEL = {} +local INV_DATA = { + --["shlem"] = { + -- position = {.459, .13}, + -- slotssize = 20 + --}, + --["armor"] = { + -- position = {.463, .30}, + -- slotssize = 20 + --}, + --["sumka"] = { + -- position = {.541, .13}, + -- slotssize = 20 + --}, + --["flashlight"] = { + -- position = {.383, .13}, + -- slotssize = 20 + --}, + --["primary"] = { + -- position = {.542, .27}, + -- slotssize = 20 + --}, + --["primary2"] = { + -- position = {.383, .27}, + -- slotssize = 20 + --}, + --["pistol"] = { + -- position = {.542, .650}, + -- slotssize = 20 + --}, + --["melee"] = { + -- position = {.383, .650}, + -- slotssize = 20 + --} +} + +function PANEL:CreateExitButton() + self.CloseButton = self:Add("DButton") + self.CloseButton:SetFont("ixChatFont") + self.CloseButton:SetSize(We(300), He(40)) -- Example size, adjust as needed + self.CloseButton:SetPos((ScrW() - 300) / 2, ScrH() - 50) -- Centered at the bottom + self.CloseButton:SetText("Закрыть") + self.CloseButton:SetTextColor(Color(255, 255, 255)) + self.CloseButton:MakePopup() + self.CloseButton.Paint = function(s, w, h) + -- RoundedBox(radius, x, y, width, height, color) + draw.RoundedBox(0, 0, 0, w, h, Color(50, 50, 50, 200)) -- You can change the color and radius as needed + end + self.CloseButton.DoClick = function(this) + ix.gui.invframe:OnRemove() + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_torch.ogg") + end +end + +function PANEL:DrawPlayerModel() + self.IDModelPanel = self:Add("DModelPanel") + self.IDModelPanel:SetSize(We(900), He(800)) -- Example size, adjust as needed + self.IDModelPanel:SetPos(We(500), He(100)) -- Centered above the button + self.IDModelPanel:SetModel(LocalPlayer():GetModel()) -- you can only change colors on playermodels + self.IDModelPanel:SetCamPos(Vector(60, 0, 60)) + + -- Sync bodygroups from player to model panel + local ent = self.IDModelPanel:GetEntity() + if IsValid(ent) and IsValid(LocalPlayer()) then + for i = 0, LocalPlayer():GetNumBodyGroups() - 1 do + ent:SetBodygroup(i, LocalPlayer():GetBodygroup(i)) + end + end + + -- Disables default rotation + function self.IDModelPanel:LayoutEntity(Entity) + return + end + + -- Make the panel non-clickable + self.IDModelPanel:SetMouseInputEnabled(false) + self.IDModelPanel:SetKeyboardInputEnabled(false) +end + +timer.Create("CheckInventoryAnimation", 1, 0, function() + local client = LocalPlayer() + if client and IsValid(client) then + local char = client:GetCharacter() + if char then + local inv = char:GetInventory() + for k, v in pairs(inv:GetItems()) do + if v.AnimStatus then + if IsValid(ix.gui.openedStorage) then + return + end + if IsValid(ix.gui.invframe) then + return + end + if IsValid(ix.gui.vendorRemake) then + return + end + v.AnimStatus = 0 + end + end + end + end +end) + +local black_list_slots = {} + +function PANEL:CreateInventorySlots() + local character = LocalPlayer():GetCharacter() + for slot_index, slot_data in pairs(INV_DATA) do + if not black_list_slots[slot_index] then + local slot_inv = character:GetInventorySlot(slot_index) + local posX, posY = slot_data.position[1], slot_data.position[2] + local slot_size = slot_data.slotssize or 40 + + if not IsValid(ix.gui["inv" .. slot_inv:GetID()]) then + local inventory = self:Add("ixInventory") + inventory:SetPos(ScrW() * posX, ScrH() * posY) + inventory:SetTitle() + inventory:SetIconSize(ScreenScale(slot_size)) + inventory.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(50,50,50, 100)) + end + + + if (slot_inv) then + inventory:SetInventory(slot_inv) + ix.gui["inv" .. slot_inv:GetID()] = inventory + end + end + end + end +end + + +function PANEL:CreateInventory() + self.IDScrollPanel = self:Add("DScrollPanel") + self.IDScrollPanel:SetSize(We(445), He(930)) + self.IDScrollPanel:SetPos(We(1450), He(100)) + self.IDScrollPanel:MakePopup() + + local vbar = self.IDScrollPanel:GetVBar() + + vbar.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(40,40,40)) + end + + vbar.btnUp.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 692, 20, 18, 1024, 1024 ) + end + + vbar.btnGrip.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(87, 87, 87)) + end + + vbar.btnDown.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 739.5, 20, 20, 1024, 1024 ) + end + + ui = self.IDScrollPanel:Add("ixInventory") + + local character = LocalPlayer():GetCharacter() + local inventory = character:GetInventory() + + if (inventory) then + ui:SetInventory(inventory) + end + + ui:SetTitle() + ui:SetDraggable(false) + + ix.gui.inv1 = ui +end + +function PANEL:Init() + ix.gui.invframe = self + self:SetTitle("") + self:SetSize(ScrW(), ScrH()) + gui.EnableScreenClicker(true) + self:SetPos(0, 0) + self:ShowCloseButton(false) + self:SetDraggable(false) + self:SetBackgroundBlur(false) + + self:DrawPlayerModel() + self:CreateInventory() + self:CreateExitButton() + self:CreateInventorySlots() + + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_open.ogg") +end + +function PANEL:PopulateInventoryTexts() + if !LocalPlayer():GetCharacter() then return end + + draw.SimpleText(LocalPlayer():GetCharacter():GetMoney().." RU", "ixChatFont", ScrW() * .757, ScrH() * .11, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + --draw.SimpleText( "Общий вес: "..carry .. "/"..max_weight.." кг", "ixChatFont", ScrW() * .85, ScrH() * .962, Color(99, 99, 99), TEXT_ALIGN_RIGHT, TEXT_ALIGN_RIGHT) +end + +function PANEL:Paint(w,h) + ix.util.DrawBlur(self, 5) + + draw.RoundedBox(0, We(1450), He(100), We(447), He(930), Color(50, 50, 50, 170)) + draw.RoundedBox(0, We(1450), He(50), We(447), He(50), Color(50, 50, 50, 200)) + draw.SimpleText("Инвентарь", "Trebuchet24", We(1670), He(60), Color(255, 255, 255, 255), TEXT_ALIGN_CENTER) +end + +function PANEL:OnRemove() + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_close.ogg") + gui.EnableScreenClicker(false) + self:Remove() +end + +vgui.Register("NDInventory", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_storage.lua b/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_storage.lua new file mode 100644 index 0000000..26c628c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/inventory/derma/cl_storage.lua @@ -0,0 +1,417 @@ +local PANEL = {} + + AccessorFunc(PANEL, "money", "Money", FORCE_NUMBER) + + function PANEL:CreateZeroStats() + SmoothBullet = 0 + SmoothHealth = 0 + SmoothPsy = 0 + SmoothRun = 0 + SmoothBurn = 0 + SmoothRadiaton = 0 + SmoothAcid = 0 + SmoothElectra = 0 + ArmorStatus = 0 + HelmetStatus = 0 + BagStatus = 0 + SmoothMutant = 0 + SniperSlotStatus = 0 + KnifeSlotStatus = 0 + PistolSlotStatus = 0 + PrimarySlotStatus = 0 + end + + local SortResetbuttonColor = Color(255, 255, 255, 255) +local SortWeaponbuttonColor = Color(255, 255, 255, 255) +local SortAmmobuttonColor = Color(255, 255, 255, 255) +local SortArmorbuttonColor = Color(255, 255, 255, 255) +local SortArtefactbuttonColor = Color(255, 255, 255, 255) +local SortFoodbuttonColor = Color(255, 255, 255, 255) +local SortDetectorsbuttonColor = Color(255, 255, 255, 255) +local SortHlambuttonColor = Color(255, 255, 255, 255) + + function PANEL:RefrashMyInventory() + self.scroller_inv_me = self:Add("DScrollPanel") + self.scroller_inv_me:SetSize(ScrW() * .23, ScrH() * .77) + self.scroller_inv_me:SetPos(ScrW() * .624, ScrH() * .17) + self.scroller_inv_me:MakePopup() + + local vbar = self.scroller_inv_me:GetVBar() + vbar.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(40,40,40)) + end + vbar.btnUp.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 692, 20, 18, 1024, 1024 ) + end + vbar.btnGrip.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(87, 87, 87)) + end + vbar.btnDown.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 739.5, 20, 20, 1024, 1024 ) + end + + ui = self.scroller_inv_me:Add("ixInventory") + + local character = LocalPlayer():GetCharacter() + local inventory = character:GetInventory() + + if (inventory) then + ui:SetInventory(inventory) + end + + ui:SetTitle() + ui:SetDraggable(false) + + ix.gui.inv1 = ui + end + + function PANEL:Init() + self:SetSize(ScrW() * .145, ScrH() * .07) + self:SetPos(ScrW() * .093, ScrH() * .025) + + self.amountEntry = self:Add("ixTextEntry") + self.amountEntry:Dock(FILL) + self.amountEntry:SetFont("ixGenericFont") + self.amountEntry:SetNumeric(false) + self.amountEntry:SetValue("1000") + + self.transferButton = self:Add("DButton") + self.transferButton:SetFont("ixIconsMedium") + self:SetLeft(false) + self.transferButton.DoClick = function() + local amount = math.max(0, math.Round(tonumber(self.amountEntry:GetValue()) or 0)) + self.amountEntry:SetValue("1000") + + if (amount != 0) then + self:OnTransfer(amount) + end + end + + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_open.ogg") + end + + function PANEL:SetLeft(bValue) + if (bValue) then + self.transferButton:Dock(LEFT) + self.transferButton:SetText("s") + else + self.transferButton:Dock(RIGHT) + self.transferButton:SetText("t") + end + end + + function PANEL:SetMoney(money) + local name = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "") + + self.money = math.max(math.Round(tonumber(money) or 0), 0) + --self.moneyLabel:SetText(string.format("%d", money)) + end + + function PANEL:OnTransfer(amount) + end + + function PANEL:Paint(width, height) + --derma.SkinFunc("PaintBaseFrame", self, width, height) + end + + function PANEL:Paint(width, height) + --derma.SkinFunc("PaintBaseFrame", self, width, height) + end + + vgui.Register("ixStorageMoney", PANEL, "EditablePanel") + + DEFINE_BASECLASS("Panel") + PANEL = {} + + AccessorFunc(PANEL, "fadeTime", "FadeTime", FORCE_NUMBER) + AccessorFunc(PANEL, "frameMargin", "FrameMargin", FORCE_NUMBER) + AccessorFunc(PANEL, "storageID", "StorageID", FORCE_NUMBER) + + local INV_DATA = { + ["shlem"] = { + position = {.459, .13}, + slotssize = 20 + }, + ["armor"] = { + position = {.463, .30}, + slotssize = 20 + }, + ["sumka"] = { + position = {.541, .13}, + slotssize = 20 + }, + ["flashlight"] = { + position = {.383, .13}, + slotssize = 20 + }, + ["primary"] = { + position = {.542, .27}, + slotssize = 20 + }, + ["primary2"] = { + position = {.383, .27}, + slotssize = 20 + }, + ["pistol"] = { + position = {.542, .650}, + slotssize = 20 + }, + ["melee"] = { + position = {.383, .650}, + slotssize = 20 + } + } + + function PANEL:DrawPlayerModel() + self.IDModelPanel = self:Add("DModelPanel") + self.IDModelPanel:SetSize(We(900), He(800)) -- Example size, adjust as needed + self.IDModelPanel:SetPos(We(500), He(100)) -- Centered above the button + self.IDModelPanel:SetModel(LocalPlayer():GetModel()) -- you can only change colors on playermodels + self.IDModelPanel:SetCamPos(Vector(60, 0, 60)) + + -- Sync bodygroups from player to model panel + local ent = self.IDModelPanel:GetEntity() + if IsValid(ent) and IsValid(LocalPlayer()) then + for i = 0, LocalPlayer():GetNumBodyGroups() - 1 do + ent:SetBodygroup(i, LocalPlayer():GetBodygroup(i)) + end + end + + -- Disables default rotation + function self.IDModelPanel:LayoutEntity(Entity) + return + end + + -- Make the panel non-clickable + self.IDModelPanel:SetMouseInputEnabled(false) + self.IDModelPanel:SetKeyboardInputEnabled(false) + end + + local black_list_slots = {} + + --function PANEL:CreateInventorySlots() + -- local character = LocalPlayer():GetCharacter() + -- for slot_index, slot_data in pairs(INV_DATA) do + -- if not black_list_slots[slot_index] then + -- local slot_inv = character:GetInventorySlot(slot_index) + -- local posX, posY = slot_data.position[1], slot_data.position[2] + -- local slot_size = slot_data.slotssize or 40 + -- + -- if not IsValid(ix.gui["inv" .. slot_inv:GetID()]) then + -- local inventory = self:Add("ixInventory") + -- inventory:SetPos(ScrW() * posX, ScrH() * posY) + -- inventory:SetTitle() + -- inventory:SetIconSize(ScreenScale(slot_size)) + -- inventory.Paint = function(s, w, h) + -- draw.RoundedBox(0, 0, 0, w, h, Color(50,50,50, 100)) + -- end + -- + -- if (slot_inv) then + -- inventory:SetInventory(slot_inv) + -- ix.gui["inv" .. slot_inv:GetID()] = inventory + -- end + -- end + -- end + -- end + --end + +function PANEL:CreateInventory() + self.scroller_inv_me = self:Add("DScrollPanel") + self.scroller_inv_me:SetSize(We(445), He(930)) + self.scroller_inv_me:SetPos(We(1450), He(100)) + self.scroller_inv_me:MakePopup() + + local vbar = self.scroller_inv_me:GetVBar() + vbar.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(40,40,40)) + end + vbar.btnUp.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 692, 20, 18, 1024, 1024 ) + end + vbar.btnGrip.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(87, 87, 87)) + end + vbar.btnDown.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 739.5, 20, 20, 1024, 1024 ) + end + + self.scroller_inv_storage = self:Add("DScrollPanel") + self.scroller_inv_storage:SetSize(We(445), He(930)) + self.scroller_inv_storage:SetPos(We(50), He(100)) + self.scroller_inv_storage:MakePopup() + + local vbar = self.scroller_inv_storage:GetVBar() + vbar.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(40,40,40)) + end + vbar.btnUp.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 692, 20, 18, 1024, 1024 ) + end + vbar.btnGrip.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width, height, Color(87, 87, 87)) + end + vbar.btnDown.Paint = function(self_panel, width, height) + draw.RoundedBox(0, 0, 0, width , height, Color(87, 87, 87)) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material("stalplay/ui/ui_pda2_noice.png")) + surface.DrawPartialTexturedRect( 0, 0, width, height, 809.5, 739.5, 20, 20, 1024, 1024 ) + end + + self.storageInventory = self.scroller_inv_storage:Add("ixInventory") + self.storageInventory:SetTitle() + self.storageInventory:SetDraggable(false) + + self.storageMoney = self:Add("ixStorageMoney") + self.storageMoney:SetVisible(false) + self.storageMoney:SetSize(ScrW() * .09, ScrH() * .033) + self.storageMoney:SetPos(ScrW() * .134, ScrH() * .957) + self.storageMoney.OnTransfer = function(_, amount) + net.Start("ixStorageMoneyTake") + net.WriteUInt(self.storageID, 32) + net.WriteUInt(amount, 32) + net.SendToServer() + end + ix.gui.inv1 = self.scroller_inv_me:Add("ixInventory") + ix.gui.inv1:SetTitle() + ix.gui.inv1:SetDraggable(false) +end + + +function PANEL:CreateExitButton() + self.CloseButton = self:Add("DButton") + self.CloseButton:SetFont("ixChatFont") + self.CloseButton:SetSize(We(300), He(40)) -- Example size, adjust as needed + self.CloseButton:SetPos((ScrW() - 300) / 2, ScrH() - 50) -- Centered at the bottom + self.CloseButton:SetText("Закрыть") + self.CloseButton:SetTextColor(Color(255, 255, 255)) + self.CloseButton:MakePopup() + self.CloseButton.Paint = function(s, w, h) + -- RoundedBox(radius, x, y, width, height, color) + draw.RoundedBox(0, 0, 0, w, h, Color(50, 50, 50, 200)) -- You can change the color and radius as needed + end + self.CloseButton.DoClick = function(this) + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_torch.ogg") + net.Start("ixStorageClose") + net.SendToServer() + self:Remove() + end +end + + +function PANEL:Init() + if (IsValid(ix.gui.openedStorage)) then + ix.gui.openedStorage:Remove() + end + + ix.gui.openedStorage = self + + self:SetSize(ScrW(), ScrH()) + self:SetPos(0, 0) + self:SetFadeTime(0.25) + self:SetFrameMargin(4) + self:CreateInventory() + --self:CreateInventorySlots() + self:CreateExitButton() + self:DrawPlayerModel() + + --self.localMoney = self:Add("ixStorageMoney") + --self.localMoney:SetVisible(false) + --self.localMoney:SetSize(ScrW() * .09, ScrH() * .033) + --self.localMoney:SetPos(ScrW() * .624, ScrH() * .957) + --self.localMoney:SetLeft(true) + --self.localMoney.OnTransfer = function(_, amount) + -- net.Start("ixStorageMoneyGive") + -- net.WriteUInt(self.storageID, 32) + -- net.WriteUInt(amount, 32) + -- net.SendToServer() + --end +-- + --self:SetAlpha(255) +end + + +function PANEL:OnChildAdded(panel) + panel:SetPaintedManually(true) +end + +function PANEL:SetLocalInventory(inventory) + if (IsValid(ix.gui.inv1) and !IsValid(ix.gui.menu)) then + ix.gui.inv1:SetInventory(inventory) + end +end + +--function PANEL:SetLocalMoney(money) +-- if (!self.localMoney:IsVisible()) then +-- self.localMoney:SetVisible(true) +-- ix.gui.inv1:SetTall(ix.gui.inv1:GetTall() + self.localMoney:GetTall() + 2) +-- end +-- +-- self.localMoney:SetMoney(money) +--end + +function PANEL:SetStorageTitle(title) + -- +end + +function PANEL:SetStorageInventory(inventory) + self.storageInventory:SetInventory(inventory) + ix.gui["inv" .. inventory:GetID()] = self.storageInventory + self.Inventory = inventory +end + +function PANEL:SetStorageMoney(money) + if (!self.storageMoney:IsVisible()) then + self.storageMoney:SetVisible(true) + end + + self.storageMoney:SetMoney(money) +end + + + + +function PANEL:Paint(width, height) + ix.util.DrawBlur(self, 5) + + draw.RoundedBox(0, 1450, 100, 447, 930, Color(50, 50, 50, 170)) + draw.RoundedBox(0, 1450, 50, 447, 50, Color(50, 50, 50, 200)) + draw.RoundedBox(0, 50, 100, 447, 930, Color(50, 50, 50, 170)) + draw.RoundedBox(0, 50, 50, 447, 50, Color(50, 50, 50, 200)) + draw.SimpleText("Инвентарь", "Trebuchet24", 1670, 60, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER) + + for _, v in ipairs(self:GetChildren()) do + v:PaintManual() + end +end + +function PANEL:Remove() + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_close.ogg") + gui.EnableScreenClicker(false) + BaseClass.Remove(self) +end + +function PANEL:OnRemove() + if (!IsValid(ix.gui.menu)) then + self.storageInventory:Remove() + ix.gui.inv1:Remove() + surface.PlaySound("anomalyzone/ui_sounds/item_use/anomaly_1_5_2/inv_close.ogg") + gui.EnableScreenClicker(false) + end +end + +vgui.Register("ixStorageView", PANEL, "Panel") diff --git a/garrysmod/gamemodes/militaryrp/plugins/inventory/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/inventory/sh_plugin.lua new file mode 100644 index 0000000..d767212 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/inventory/sh_plugin.lua @@ -0,0 +1,81 @@ +local PLUGIN = PLUGIN +local meta = ix.meta.character + +PLUGIN.name = "Inventory" +PLUGIN.author = "MediQ" +PLUGIN.description = "Adds simple stalker inventory" + +ix.stalker = ix.stalker or {} +ix.stalker.slots = ix.stalker.slots or { + --["shlem"] = {2, 2}, + --["armor"] = {2, 2}, + --["sumka"] = {2, 2}, + --["flashlight"] = {2, 2}, + --["primary"] = {2, 6}, + --["primary2"] = {2, 6}, + --["pistol"] = {2, 2}, + --["melee"] = {2, 2} +} + +ix.config.Add("maxWeight", 30, "The maximum number of weight a player can take.", nil, { data = {min = 10, max = 100}, category = "characters" }) + +if (CLIENT) then + -- Helper function for drawing partial textured rectangles + function surface.DrawPartialTexturedRect(x, y, w, h, startX, startY, endX, endY, texW, texH) + local u1 = startX / texW + local v1 = startY / texH + local u2 = (startX + endX) / texW + local v2 = (startY + endY) / texH + + surface.DrawTexturedRectUV(x, y, w, h, u1, v1, u2, v2) + end +end + +function meta:GetInventorySlot(slot) + if !self then return end + slot = slot:lower() + local character_inv = self.vars.inv + + if character_inv then + for slot_name, inv in pairs(character_inv) do + if inv.vars and inv.vars.isBag == slot then + return inv + end + end + end +end + +if (CLIENT) then + local d = CurTime() + + function PLUGIN:Think() + local c = LocalPlayer():GetCharacter() + local b = ix.gui.invframe + if input.IsKeyDown(KEY_I) && d < CurTime() then + + if !c then return end + + if not IsValid(ix.gui.invframe) then + local a = true + if !LocalPlayer():IsRagdoll() && !vgui.CursorVisible() && !LocalPlayer():IsTyping() && !LocalPlayer():IsFrozen() && a then + vgui.Create('NDInventory') + end + else + ix.gui.invframe:OnRemove() + end + d = CurTime() + 0.5 + end + + if b then + if (LocalPlayer():IsFrozen() || LocalPlayer():IsTyping() || gui.IsGameUIVisible() || (IsValid(c) && LocalPlayer():IsRagdoll())) && b:IsVisible() then + b:OnRemove() + end + end + end +else + ix.util.Include("sv_inventory.lua", "server") +end + +ix.models = {} +ix.models.avatars = {} +--ix.models.avatars[FACTION_NEUTRAL] = "vgui/icons/face_3.png" diff --git a/garrysmod/gamemodes/militaryrp/plugins/inventory/sv_inventory.lua b/garrysmod/gamemodes/militaryrp/plugins/inventory/sv_inventory.lua new file mode 100644 index 0000000..b8ceb17 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/inventory/sv_inventory.lua @@ -0,0 +1,80 @@ +local PLUGIN = PLUGIN + + +util.AddNetworkString("INVENTORY.ValidateItemData") + +function PLUGIN:InitializedPlugins() + for b, c in pairs(ix.stalker.slots) do + local d, d2 = c[1], c[2] + ix.inventory.Register(b, d, d2, true) + end +end + +function PLUGIN:OnCharacterCreated(a, b) + local d = b:GetID() + + for d2, d3 in pairs(ix.stalker.slots) do + ix.inventory.New(d, d2, function(d4) + table.insert(b.vars.inv, d4) + d4:AddReceiver(a) + d4:Sync(a) + end) + end +end + +function PLUGIN:CanTransferItem(a, b, c) + if c and c.vars and ix.stalker.slots[c.vars.isBag] then + local d2 = a.slot + if d2 and string.find(c.vars.isBag, d2) then + local d3 = c:GetItems() + if d3 and table.Count(d3) > 0 then + return false + end + local status = hook.Run("NewCanTransferItem", a, b, c) + if status == false then + return false + end + + return true + else + local status = hook.Run("NewCanTransferItem", a, b, c) + if status == false then + return false + end + + return false + end + else + return true + end +end + +function PLUGIN:OnItemTransferred(a, b, c) + local d2 = a.player or a:GetOwner() + + if b and b.vars and ix.stalker.slots[b.vars.isBag] then + if not a then + return + end + + if a.ActionEquipment then + a:ActionEquipment(false) + end + + // net.Start("ri") + // net.Send(d2) + end + + if c and c.vars and ix.stalker.slots[c.vars.isBag] then + if not a then + return + end + + if a.ActionEquipment then + a:ActionEquipment(true) + end + + // net.Start("ri") + // net.Send(d2) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/inventory_limits/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/inventory_limits/sh_plugin.lua new file mode 100644 index 0000000..aaff122 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/inventory_limits/sh_plugin.lua @@ -0,0 +1,95 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Inventory Group Limits" +PLUGIN.author = "Scripty" +PLUGIN.description = "Adjusts character inventory size based on player user group via types." + +-- Регистрируем типы инвентарей (Shared) +ix.inventory.Register("player_user", 8, 4) -- 32 слота +ix.inventory.Register("player_prem", 8, 6) -- 48 слотов +ix.inventory.Register("player_sponsor", 8, 8) -- 64 слота +ix.inventory.Register("player_admin", 8, 8) -- 64 слота + +local groupToType = { + ["user"] = "player_user", + ["prem"] = "player_prem", + ["premium"] = "player_prem", + ["sponsor"] = "player_sponsor", + ["superadmin"] = "player_admin", + ["admin"] = "player_admin" +} + +function PLUGIN:GetTargetType(client) + if (!IsValid(client)) then return "player_user" end + local group = client:GetUserGroup() + local t = groupToType[group] or "player_user" + return t +end + +if (SERVER) then + function PLUGIN:UpdatePlayerInventorySize(client) + if (!IsValid(client)) then return end + local character = client:GetCharacter() + if (!character) then return end + + local inventory = character:GetInventory() + + if (!inventory or isnumber(inventory)) then + -- Пытаемся еще раз через небольшой промежуток времени + timer.Simple(2, function() + if (IsValid(client)) then + self:UpdatePlayerInventorySize(client) + end + end) + return + end + + local targetType = self:GetTargetType(client) + local typeData = ix.item.inventoryTypes[targetType] + if (!typeData) then return end + + -- Если тип уже совпадает, ничего не делаем + if (inventory.vars and inventory.vars.isBag == targetType) then + inventory:SetSize(typeData.w, typeData.h) -- На всякий случай обновляем размер в памяти + return + end + + -- Обновляем в БД + local updateQuery = mysql:Update("ix_inventories") + updateQuery:Update("inventory_type", targetType) + updateQuery:Where("inventory_id", inventory:GetID()) + updateQuery:Execute() + + -- Обновляем в памяти + inventory.vars.isBag = targetType + inventory:SetSize(typeData.w, typeData.h) + inventory:Sync(client) + + print(string.format("[Inventory] Обновлен размер для %s: %dx%d (%s)", + client:Name(), typeData.w, typeData.h, targetType)) + end + + function PLUGIN:PlayerLoadedCharacter(client, character) + timer.Simple(2, function() + if (IsValid(client)) then + self:UpdatePlayerInventorySize(client) + end + end) + end + + hook.Add("SAM.ChangedRank", "ixInventoryUpdateOnRank", function(client, old, new) + timer.Simple(1, function() + if (IsValid(client)) then + PLUGIN:UpdatePlayerInventorySize(client) + end + end) + end) + + ix.command.Add("InvUpdate", { + description = "Обновить размер инвентаря согласно группе.", + OnRun = function(self, client) + PLUGIN:UpdatePlayerInventorySize(client) + return "Попытка обновления инвентаря..." + end + }) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/kill_rewards/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/kill_rewards/sh_plugin.lua new file mode 100644 index 0000000..a7141b6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/kill_rewards/sh_plugin.lua @@ -0,0 +1,54 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Kill Rewards" +PLUGIN.author = "Scripty" +PLUGIN.description = "Adds money rewards for killing players of another faction." + +ix.config.Add("killRewardBase", 300, "Базовая награда за убийство игрока другой фракции.", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) + +ix.config.Add("killRewardVIP", 750, "Награда за убийство для VIP игроков.", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) + +ix.config.Add("killRewardVIPPlus", 1000, "Награда за убийство для VIP+ игроков.", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) + +ix.config.Add("killRewardPremium", 1500, "Награда за убийство для Premium игроков.", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) + +ix.config.Add("killRewardSponsor", 2000, "Награда за убийство для Спонсоров.", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) + +-- Добавляем множитель для админов, если нужно, или просто оставим базовую +ix.config.Add("killRewardAdmin", 1000, "Награда за убийство для администрации.", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) +ix.config.Add("deathPenalty", 3000, "Штраф за смерть (вычитается из баланса игрока).", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) +ix.config.Add("friendlyFirePenalty", 5000, "Штраф за убийство игрока своей фракции.", nil, { + data = {min = 0, max = 50000}, + category = "Убийства" +}) + +if (CLIENT) then + ix.lang.AddTable("russian", { + killRewardMsg = "Вы получили %s за ликвидацию противника!", + killPenaltyMsg = "Вы потеряли %s за убийство союзника!", + deathPenaltyMsg = "Вы потеряли %s из-за смерти." + }) +end + +ix.util.Include("sv_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/kill_rewards/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/kill_rewards/sv_plugin.lua new file mode 100644 index 0000000..1a8d7ed --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/kill_rewards/sv_plugin.lua @@ -0,0 +1,116 @@ +local PLUGIN = PLUGIN + +local groupToConfig = { + ["sponsor"] = "killRewardSponsor", + ["premium"] = "killRewardPremium", + ["vip_plus"] = "killRewardVIPPlus", + ["vip"] = "killRewardVIP", + ["admin"] = "killRewardAdmin", + ["st.admin"] = "killRewardAdmin", + ["curator"] = "killRewardAdmin", + ["projectteam"] = "killRewardAdmin", + ["teh.admin"] = "killRewardAdmin", + ["superadmin"] = "killRewardAdmin" +} + +-- Прямой вывод в консоль при загрузке файла для проверки +print("[KillRewards] Server-side script loaded successfully!") + +local function FormatMoney(amount, ply) + local amountVal = math.floor(math.abs(amount)) + local formatted = tostring(amountVal):reverse():gsub("(%d%d%d)", "%1 "):reverse():gsub("^ ", "") + + local char = ply:GetCharacter() + if (!char) then return formatted end + + local faction = char:GetFaction() + + -- Логика из F4 меню (cl_plugin.lua:29-38) + if (faction == _G.FACTION_UKRAINE) then + return formatted .. " валюты " + elseif (faction == _G.FACTION_RUSSIAN) then + return formatted .. " валюты " + end + + return formatted +end + +-- Используем PLUGIN:PlayerDeath вместо hook.Add для лучшей интеграции с Helix +function PLUGIN:PlayerDeath(victim, inflictor, attacker) + if (!IsValid(victim) or !victim:IsPlayer()) then return end + + local victimChar = victim:GetCharacter() + if (!victimChar) then return end + + -- Штраф за смерть (УДАЛЕНО: теперь штраф списывается в плагине death_screen при нажатии Пробела) + --[[ + local deathPenalty = ix.config.Get("deathPenalty", 3000) + if (deathPenalty > 0) then + local money = victimChar:GetMoney() + local toTake = math.min(money, deathPenalty) + if (toTake > 0) then + victimChar:TakeMoney(toTake) + victim:NotifyLocalized("deathPenaltyMsg", FormatMoney(toTake, victim)) + end + end + ]] + + -- Пытаемся найти реального игрока-убийцу + local killer = attacker + if (IsValid(killer)) then + if (killer.GetDriver and IsValid(killer:GetDriver())) then + killer = killer:GetDriver() + elseif (killer.lvsGetDriver and IsValid(killer:lvsGetDriver())) then + killer = killer:lvsGetDriver() + elseif (killer:IsVehicle() and killer.GetPassenger and IsValid(killer:GetPassenger(0))) then + killer = killer:GetPassenger(0) + elseif (IsValid(killer:GetOwner()) and killer:GetOwner():IsPlayer()) then + killer = killer:GetOwner() + elseif (killer.GetCreator and IsValid(killer:GetCreator()) and killer:GetCreator():IsPlayer()) then + killer = killer:GetCreator() + end + end + + -- Если убийца не игрок или убил сам себя - выходим + if (!IsValid(killer) or !killer:IsPlayer() or killer == victim) then + return + end + + local killerChar = killer:GetCharacter() + if (!killerChar) then return end + + -- Отладочная инфа в консоль сервера (безопасное форматирование через tostring) + MsgC(Color(0, 255, 0), string.format("[KillRewards] %s (F:%s) killed %s (F:%s)\n", + tostring(killer:Nick()), + tostring(killerChar:GetFaction()), + tostring(victim:Nick()), + tostring(victimChar:GetFaction()) + )) + + -- Сравнение фракций. В Helix GetFaction() может возвращать как число, так и строку (internal name) + if (tostring(killerChar:GetFaction()) != tostring(victimChar:GetFaction())) then + -- НАГРАДА + local group = killer:GetUserGroup() + local confKey = groupToConfig[group] or "killRewardBase" + local reward = ix.config.Get(confKey, 300) + + killerChar:GiveMoney(reward) + killer:NotifyLocalized("killRewardMsg", FormatMoney(reward, killer)) + + ix.log.Add(killer, "playerKillReward", victim:Nick(), reward) + else + -- ШТРАФ (FF) + local penalty = ix.config.Get("friendlyFirePenalty", 5000) + if (penalty > 0) then + local money = killerChar:GetMoney() + local toTake = math.min(money, penalty) + + if (toTake > 0) then + killerChar:TakeMoney(toTake) + killer:NotifyLocalized("killPenaltyMsg", FormatMoney(toTake, killer)) + + ix.log.Add(killer, "playerKillPenalty", victim:Nick(), toTake) + end + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/killzone/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/killzone/sh_plugin.lua new file mode 100644 index 0000000..5a6172f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/killzone/sh_plugin.lua @@ -0,0 +1,28 @@ +-- d:\ft 4.0\VnU\garrysmod\gamemodes\militaryrp\plugins\killzone\sh_plugin.lua + +local PLUGIN = PLUGIN + +PLUGIN.name = "Killzone Camper Punishment" +PLUGIN.author = "Scripty" +PLUGIN.description = "Punishes campers in specific zones with a barrage of bombs after 5 minutes." + +-- Определение киллзон +PLUGIN.zones = { + { + min = Vector(6520, -538, -5000), -- Опустил ниже на всякий случай + max = Vector(8129, 14097, 10000), -- Поднял до неба + name = "Killzone 1" + }, + { + min = Vector(8129, -538, -5000), + max = Vector(15312, 2213, 10000), + name = "Killzone 2" + } +} + +ix.config.Add("killzonePunishDelay", 300, "Время стоянки в киллзоне до начала наказания (секунды).", nil, { + data = {min = 1, max = 3600}, + category = "Killzone" +}) + +ix.util.Include("sv_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/killzone/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/killzone/sv_plugin.lua new file mode 100644 index 0000000..e3993d6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/killzone/sv_plugin.lua @@ -0,0 +1,141 @@ +-- d:\ft 4.0\VnU\garrysmod\gamemodes\militaryrp\plugins\killzone\sv_plugin.lua + +local PLUGIN = PLUGIN + +PLUGIN.camperTimers = PLUGIN.camperTimers or {} + +local function IsInKillzone(pos) + for _, zone in pairs(PLUGIN.zones) do + if (pos:WithinAABox(zone.min, zone.max)) then + return true, zone.name + end + end + return false +end + +function PLUGIN:TickKillzoneChecks() + local punishDelay = ix.config.Get("killzonePunishDelay", 300) + local currentTime = CurTime() + + for _, ply in ipairs(player.GetAll()) do + if (!IsValid(ply) or !ply:Alive() or !ply:GetCharacter() or (ply.IsAdminMode and ply:IsAdminMode())) then + if (self.camperTimers[ply]) then + self.camperTimers[ply] = nil + end + continue + end + + local target = ply + local pos = ply:GetPos() + + -- Проверка LVS / Техники + local veh = ply:GetVehicle() + if (IsValid(veh)) then + -- Для LVS ищем корень всей конструкции + local root = veh + if (veh.GetBaseEntity and IsValid(veh:GetBaseEntity())) then + root = veh:GetBaseEntity() + elseif (IsValid(veh:GetParent())) then + root = veh:GetParent() + end + target = root + pos = target:GetPos() + end + + local inZone, zoneName = IsInKillzone(pos) + + if (inZone) then + if (!self.camperTimers[target]) then + self.camperTimers[target] = currentTime + print("[Killzone] " .. ply:Name() .. " вошел в " .. zoneName) + else + local timeInZone = currentTime - self.camperTimers[target] + + if (timeInZone >= punishDelay) then + self:PunishTarget(target, ply) + end + end + else + if (self.camperTimers[target]) then + print("[Killzone] " .. ply:Name() .. " покинул киллзону.") + end + self.camperTimers[target] = nil + end + end +end + +function PLUGIN:PunishTarget(ent, ply) + if (!ent.nextKillzoneBomb or ent.nextKillzoneBomb <= CurTime()) then + ent.nextKillzoneBomb = CurTime() + 5 + + local targetPos = ent:GetPos() + + -- Создаем "свист" падающей бомбы только для игрока + if (IsValid(ply)) then + ply:EmitSound("ambient/levels/canals/headcrab_canister_ambient.wav", 100, 100) + end + + -- Небольшая задержка перед ударом + timer.Simple(0.5, function() + if (!IsValid(ent)) then return end + local currentPos = ent:GetPos() + + -- Создаем мощный взрыв + local explosion = ents.Create("env_explosion") + if (IsValid(explosion)) then + explosion:SetPos(currentPos) + explosion:SetOwner(ent) + explosion:SetKeyValue("iMagnitude", "500") + explosion:SetKeyValue("iRadiusOverride", "300") + explosion:Spawn() + explosion:Fire("Explode", 0, 0) + end + + -- Эффект взрыва + local effectData = EffectData() + effectData:SetOrigin(currentPos) + util.Effect("Explosion", effectData) + + -- Наносим урон игроку (если это игрок) + if (IsValid(ply) and ply:Alive()) then + local dmg = DamageInfo() + dmg:SetDamage(1000) + dmg:SetDamageType(DMG_BLAST) + dmg:SetAttacker(game.GetWorld()) + dmg:SetInflictor(game.GetWorld()) + ply:TakeDamageInfo(dmg) + end + + -- Если это техника, наносим ей урон отдельно + if (ent != ply and IsValid(ent)) then + local dmg = DamageInfo() + dmg:SetDamage(5000) + dmg:SetDamageType(DMG_BLAST) + dmg:SetAttacker(game.GetWorld()) + dmg:SetInflictor(game.GetWorld()) + ent:TakeDamageInfo(dmg) + end + + print("[Killzone] Наказание применено к " .. (IsValid(ply) and ply:Name() or ent:GetClass())) + end) + end +end + +-- Запуск таймера при загрузке плагина +if (timer.Exists("ixKillzoneTimer")) then + timer.Remove("ixKillzoneTimer") +end + +timer.Create("ixKillzoneTimer", 1, 0, function() + if (PLUGIN) then + PLUGIN:TickKillzoneChecks() + end +end) + +function PLUGIN:OnLoaded() + -- Дублируем на случай перезагрузки плагина + if (timer.Exists("ixKillzoneTimer")) then timer.Remove("ixKillzoneTimer") end + timer.Create("ixKillzoneTimer", 1, 0, function() + self:TickKillzoneChecks() + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/lean/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/lean/cl_plugin.lua new file mode 100644 index 0000000..54b0b7e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/lean/cl_plugin.lua @@ -0,0 +1,125 @@ +local PLUGIN = PLUGIN + +--[[CLIENT SIDE]] +function PLUGIN:PreRender() + if not ix.config.Get("leanEnabled", true) then return end + TFALeanModel() +end + +local minvec = Vector(-6, -6, -6) +local maxvec = Vector(6, 6, 6) + +local function filterfunc(ent) + if (ent:IsPlayer() or ent:IsNPC() or ent:IsWeapon() or ent:GetClass() == "rpg_missile" or ent:GetClass() == "crossbow_bolt" or ent:GetClass() == "npc_grenade_frag" or ent:GetClass() == "apc_missile" or ent:GetClass() == "viewmodel_predicted") then + return false + else + return true + end +end + +local function bestcase(pos, endpos, ply) + local off = endpos - pos + local trace = {} + trace.start = pos + trace.endpos = pos + off + trace.mask = MASK_SOLID + trace.filter = filterfunc + trace.ignoreworld = false + trace.maxs = maxvec + trace.mins = minvec + local traceres = util.TraceHull(trace) + + return pos + off:GetNormalized() * math.Clamp(traceres.Fraction, 0, 1) * off:Length() +end + +function LeanCalcView(ply, pos, angles, fov) + local view = {} + view.origin = pos + view.angles = angles + view.fov = fov + if not ply:Alive() or ply:Health() <= 0 then return view end + + local status = ply.TFALean or 0 + local leanOffset = ix.config.Get("leanOffset", 16) + local rollAngle = ix.config.Get("leanRollAngle", 15) + local off = Vector(0, status * -leanOffset, 0) + off:Rotate(angles) + view.angles:RotateAroundAxis(view.angles:Forward(), status * rollAngle) + view.origin = bestcase(view.origin, view.origin + off, ply) + + return view +end + +local ISLEANINGCV = false + +function PLUGIN:CalcView(ply, pos, angles, fov) + if ISLEANINGCV then return end + if not ix.config.Get("leanEnabled", true) then return end + if GetViewEntity() ~= ply then return end + if not ply:Alive() then return end + if ply:InVehicle() then return end + + ISLEANINGCV = true + local preTableRaw = hook.Run("CalcView", ply, pos, angles, fov) + ISLEANINGCV = false + + local preOrigin, preAngles, preFov + if type(preTableRaw) == "table" then + preOrigin = rawget(preTableRaw, "origin") or pos + preAngles = rawget(preTableRaw, "angles") or angles + preFov = tonumber(rawget(preTableRaw, "fov")) or fov + else + preOrigin = pos + preAngles = angles + preFov = fov + end + local preTable = { origin = preOrigin, angles = preAngles, fov = preFov } + + local finalTable = LeanCalcView(ply, preTable.origin, preTable.angles, preTable.fov) + + for k, v in pairs(preTable) do + if finalTable[k] == nil and (k == "origin" or k == "angles" or k == "fov" or k == "drawviewer" or k == "znear" or k == "zfar") then + rawset(finalTable, k, v) + end + end + + return finalTable +end + +local ISLEANINGCV_VM = false + +function LeanCalcVMView(wep, vm, oldPos, oldAng, pos, ang) + if ISLEANINGCV_VM then return end + local ply = LocalPlayer() + if GetViewEntity() ~= ply then return end + + if not ply.tfacastoffset or ply.tfacastoffset <= 0.001 then + local status = ply.TFALean or 0 + + if math.abs(status) > 0.001 then + local leanOffset = ix.config.Get("leanOffset", 16) + local off = Vector(0, status * -leanOffset, 0) + off:Rotate(ang) + ang:RotateAroundAxis(ang:Forward(), status * 12 * (wep.ViewModelFlip and -1 or 1)) + pos = bestcase(pos, pos + off, ply) + ISLEANINGCV_VM = true + local tpos, tang = hook.Run("CalcViewModelView", wep, vm, oldPos, oldAng, pos, ang) + ISLEANINGCV_VM = false + + if tpos then + pos = tpos + end + + if tang then + ang = tang + end + + return pos, ang + end + end +end + +function PLUGIN:CalcViewModelView(wep, vm, oldPos, oldAng, pos, ang) + if not ix.config.Get("leanEnabled", true) then return end + return LeanCalcVMView(wep, vm, oldPos, oldAng, pos, ang) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/lean/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/lean/sh_plugin.lua new file mode 100644 index 0000000..81d9245 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/lean/sh_plugin.lua @@ -0,0 +1,275 @@ +PLUGIN.name = "TFA Lean" +PLUGIN.author = "TFA (Портирован для Helix)" +PLUGIN.description = "Система наклонов влево/вправо с автоматическим наклоном при прицеливании" + +ix.util.Include("cl_plugin.lua") + +-- Конфигурация +ix.config.Add("leanEnabled", true, "Включить систему наклонов", nil, { + category = "Lean System" +}) + +ix.config.Add("leanOffset", 16, "Расстояние наклона (в юнитах)", nil, { + data = {min = 8, max = 32}, + category = "Lean System" +}) + +ix.config.Add("leanAutomatic", true, "Автоматический наклон при прицеливании за укрытием", nil, { + category = "Lean System" +}) + +ix.config.Add("leanAutoDist", 20, "Дистанция для определения укрытия (автонаклон)", nil, { + data = {min = 10, max = 50}, + category = "Lean System" +}) + +ix.config.Add("leanRollAngle", 15, "Угол наклона камеры и модели", nil, { + data = {min = 5, max = 30}, + category = "Lean System" +}) + +-- Локальные переменные +local LeanOffset = ix.config.Get("leanOffset", 16) + +--[[Shared]] +local function MetaInitLean() + local PlayerMeta = FindMetaTable("Player") + PlayerMeta.LeanGetShootPosOld = PlayerMeta.LeanGetShootPosOld or PlayerMeta.GetShootPos + PlayerMeta.LeanEyePosOld = PlayerMeta.LeanEyePosOld or PlayerMeta.GetShootPos + PlayerMeta.GetNW2Int = PlayerMeta.GetNW2Int or PlayerMeta.GetNWFloat + PlayerMeta.SetNW2Int = PlayerMeta.SetNW2Int or PlayerMeta.SetNWFloat + + function PlayerMeta:GetShootPos() + if not IsValid(self) or not self.LeanGetShootPosOld then return end + local ply = self + local status = ply.TFALean or ply:GetNW2Int("TFALean") + local off = Vector(0, status * -LeanOffset, 0) + off:Rotate(self:EyeAngles()) + local gud, ret = pcall(self.LeanGetShootPosOld, self) + + if gud then + return ret + off + else + return off + end + end + + function PlayerMeta:EyePos() + if not IsValid(self) then return end + local gud, pos, ang = pcall(self.GetShootPos, self) + + if gud then + return pos, ang + else + return vector_origin, angle_zero + end + end + + hook.Add("ShutDown", "TFALeanPMetaCleanup", function() + if PlayerMeta.LeanGetShootPosOld then + PlayerMeta.GetShootPos = PlayerMeta.LeanGetShootPosOld + PlayerMeta.LeanGetShootPosOld = nil + end + end) + + MetaOverLean = true +end + +pcall(MetaInitLean) + +function PLUGIN:PlayerSpawn(ply) + ply:SetNW2Int("TFALean", 0) + ply.TFALean = 0 +end + +function PLUGIN:OnConfigChanged(key, oldValue, newValue) + if key == "leanOffset" then + LeanOffset = newValue + end +end + +--[[Lean Calculations]] +local targ +local autoLeanDist = ix.config.Get("leanAutoDist", 20) +local traceRes, traceResLeft, traceResRight + +local traceData = { + ["mask"] = MASK_SOLID, + ["collisiongroup"] = COLLISION_GROUP_DEBRIS, + ["mins"] = Vector(-4, -4, -4), + ["maxs"] = Vector(4, 4, 4) +} + +local function AngleOffset(new, old) + local _, ang = WorldToLocal(vector_origin, new, vector_origin, old) + return ang +end + +local function RollBone(ply, bone, roll) + ply:ManipulateBoneAngles(bone, angle_zero) + + if CLIENT then + ply:SetupBones() + end + + local boneMat = ply:GetBoneMatrix(bone) + local boneAngle = boneMat:GetAngles() + local boneAngleOG = boneMat:GetAngles() + boneAngle:RotateAroundAxis(ply:EyeAngles():Forward(), roll) + ply:ManipulateBoneAngles(bone, AngleOffset(boneAngle, boneAngleOG)) + + if CLIENT then + ply:SetupBones() + end +end + +function TFALeanModel() + local rollAngle = ix.config.Get("leanRollAngle", 15) + + for k, ply in ipairs(player.GetAll()) do + ply.TFALean = Lerp(FrameTime() * 10, ply.TFALean or 0, ply:GetNW2Int("TFALean")) + local lean = ply.TFALean + local bone = ply:LookupBone("ValveBiped.Bip01_Spine") + + if bone then + RollBone(ply, bone, lean * rollAngle) + end + + bone = ply:LookupBone("ValveBiped.Bip01_Spine1") + + if bone then + RollBone(ply, bone, lean * rollAngle) + end + end +end + +function PLUGIN:Move(ply) + if not ix.config.Get("leanEnabled", true) then return end + + targ = (ply:KeyDown(IN_ALT1) and -1 or (ply:KeyDown(IN_ALT2) and 1 or 0)) + autoLeanDist = ix.config.Get("leanAutoDist", 20) + + -- Автонаклон + if targ == 0 and ix.config.Get("leanAutomatic", true) and ply:KeyDown(IN_ATTACK2) then + local headpos = ply.LeanGetShootPosOld and ply:LeanGetShootPosOld() or ply:GetShootPos() + traceData.start = headpos + traceData.endpos = traceData.start + ply:EyeAngles():Forward() * autoLeanDist + traceRes = util.TraceHull(traceData) + + if traceRes.Hit and traceRes.Fraction < 1 then + traceData.start = headpos - ply:EyeAngles():Right() * LeanOffset + traceData.endpos = traceData.start + ply:EyeAngles():Forward() * autoLeanDist * 2 + traceResLeft = util.TraceLine(traceData) + traceData.start = headpos + ply:EyeAngles():Right() * LeanOffset + traceData.endpos = traceData.start + ply:EyeAngles():Forward() * autoLeanDist * 2 + traceResRight = util.TraceLine(traceData) + + if traceResLeft.Fraction > 1 or not traceResLeft.Hit then + targ = -1 + elseif traceResRight.Fraction > 1 or not traceResRight.Hit then + targ = 1 + end + end + end + + ply:SetNW2Int("TFALean", targ) + + if SERVER then + for _, v in ipairs(player.GetAll()) do + v.TFALean = Lerp(FrameTime() * 10, v.TFALean or 0, v:GetNW2Int("TFALean")) + end + end +end + +if SERVER and not game.SinglePlayer() then + timer.Create("TFALeanSynch", 0.2, 0, function() + if not ix.config.Get("leanEnabled", true) then return end + local rollAngle = ix.config.Get("leanRollAngle", 15) + + for k, v in ipairs(player.GetAll()) do + local lean = v:GetNW2Int("TFALean") + v.OldLean = v.OldLean or lean + + if lean ~= v.OldLean then + v.OldLean = lean + local bone = v:LookupBone("ValveBiped.Bip01_Spine") + + if bone then + RollBone(v, bone, lean * rollAngle) + end + + bone = v:LookupBone("ValveBiped.Bip01_Spine1") + + if bone then + RollBone(v, bone, lean * rollAngle) + end + end + end + end) +end + +--[[Projectile Redirection]] +local PlayerPosEntities = { + ["rpg_missile"] = true, + ["crossbow_bolt"] = true, + ["npc_grenade_frag"] = true, + ["apc_missile"] = true, + ["viewmodel_predicted"] = true +} + +function PLUGIN:OnEntityCreated(ent) + local ply = ent.Owner or ent:GetOwner() + if not IsValid(ply) or not ply:IsPlayer() then return end + if ent:IsPlayer() then return end + + local headposold + local gud, ret = pcall(ply.LeanGetShootPosOld, ply) + + if gud then + headposold = ret + else + headposold = ply:EyePos() + end + + local entpos = ent:GetPos() + + if PlayerPosEntities[ent:GetClass()] or (math.floor(entpos.x) == math.floor(headposold.x) and math.floor(entpos.y) == math.floor(headposold.y) and math.floor(entpos.z) == math.floor(headposold.z)) then + ent:SetPos(ply:EyePos()) + end +end + +local ISPATCHING = false + +function PLUGIN:EntityFireBullets(ply, bul) + if ISPATCHING then return end + if not ix.config.Get("leanEnabled", true) then return end + + local bak = table.Copy(bul) + ISPATCHING = true + local call = hook.Run("EntityFireBullets", ply, bul) + ISPATCHING = false + + if call == false then + return false + elseif call == nil then + table.Empty(bul) + table.CopyFromTo(bak, bul) + end + + if ply:IsWeapon() and ply.Owner then + ply = ply.Owner + end + + if not ply:IsPlayer() then return end + + if ply.LeanGetShootPosOld and bul.Src == ply:LeanGetShootPosOld() then + bul.Src = ply:GetShootPos() + end + + return true +end + +function PLUGIN:Initialize() + print("[TFA Lean] Система наклонов загружена!") + print("[TFA Lean] Управление: ALT1 (Q) - наклон влево, ALT2 (E) - наклон вправо") +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/client/sendshots/fetchspreads.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/client/sendshots/fetchspreads.lua new file mode 100644 index 0000000..354d056 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/client/sendshots/fetchspreads.lua @@ -0,0 +1,137 @@ +LeyHitreg.WeaponSpreads = {} + +local vector_origin = vector_origin + +function LeyHitreg:PlayerSwitchWeapon(ply, oldWep, newWep) + if (not IsValid(newWep)) then + return + end + + local classname = newWep:GetClass() + + if (not classname or self.WeaponSpreads[classname]) then + return + end + + if (self:IsIgnoreWep(newWep)) then + self.WeaponSpreads[classname] = vector_origin + return + end + + ply.LeyHitreg_NeedsSpreadForce = newWep + + timer.Simple(1, function() + if (not IsValid(ply) or not IsValid(newWep)) then + return + end + + ply.LeyHitreg_NeedsSpreadForce = nil + + if (ply:GetActiveWeapon() != newWep) then + return + end + + LeyHitreg.WeaponSpreads[classname] = LeyHitreg.WeaponSpreads[classname] or vector_origin + end) + + LeyHitreg:SetFittingValidClip(newWep) + + if (newWep.PrimaryAttack) then + newWep:PrimaryAttack() + elseif (newWep.Primary and newWep.Primary.Attack) then + newWep.Primary.Attack() + end +end + +hook.Add("PlayerSwitchWeapon", "LeyHitreg:PlayerSwitchWeapon", function(...) + -- process switch at next frame so FireBullets uses proper wep + local t = {...} + + timer.Simple(0, function() + LeyHitreg:PlayerSwitchWeapon(unpack(t)) + end) +end) + +function LeyHitreg:FetchSpreadFireBullets(ply, wep, bullet) + local spreadForceWep = ply.LeyHitreg_NeedsSpreadForce + local validSpreadForceWep = spreadForceWep != nil and IsValid(spreadForceWep) + + -- if (validSpreadForceWep) then + -- wep = spreadForceWep + -- end + + local weaponSpread = LeyHitreg:GetWeaponSpread(ply, wep, bullet) + self.WeaponSpreads[wep:GetClass()] = weaponSpread + + if (validSpreadForceWep and wep == spreadForceWep) then + bullet.Damage = 1 + bullet.Distance = 1 + bullet.Src = Vector(-100000, -10000, -10000) + bullet.Dir = vector_origin + + timer.Simple(0, function() + if (not IsValid(ply)) then + return + end + + if (ply.LeyHitreg_NeedsSpreadForce == wep) then + ply.LeyHitreg_NeedsSpreadForce = nil + end + end) + + return bullet + end +end + +function LeyHitreg:EntityEmitSoundSpreadPrefire(data) + if (not data) then + return + end + + local ent = data.Entity + + if (not IsValid(ent)) then + return + end + + + if (ent:IsPlayer()) then + if (ent.LeyHitreg_NeedsSpreadForce) then + return false + end + + local wep = ent:GetActiveWeapon() + + if (not IsValid(wep)) then + return + end + + if (wep.LeyHitreg_NeedsSpreadForce) then + return false + end + + return + end + + if (not ent:IsWeapon()) then + return + end + + local ply = ent:GetOwner() + + if (IsValid(ply) and ply:IsPlayer() and ply.LeyHitreg_NeedsSpreadForce) then + return false + end + + if (ent.LeyHitreg_NeedsSpreadForce) then + return false + end +end + +hook.Add("EntityEmitSound", "LeyHitreg:EntityEmitSoundSpreadPrefire", function(data) + local ret = LeyHitreg:EntityEmitSoundSpreadPrefire(data) + + if (ret != nil) then + return ret + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/client/sendshots/sendshots.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/client/sendshots/sendshots.lua new file mode 100644 index 0000000..916647f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/client/sendshots/sendshots.lua @@ -0,0 +1,213 @@ + +local IsValid = IsValid +local inputIsMouseDown = input.IsMouseDown +local vector_origin = vector_origin + +IN_LEYHITREG1 = bit.lshift(1, 27) + +function LeyHitreg:ShouldPrimaryAttack() + return inputIsMouseDown(MOUSE_LEFT) or inputIsMouseDown(MOUSE_RIGHT) +end + +local lastPrim = nil +function LeyHitreg:CanShoot(cmd, wep, primary) + local canShoot = true + + local nextPrim = wep:GetNextPrimaryFire() + + if (primary) then + if (nextPrim == lastPrim or wep:Clip1() == 0) then + canShoot = false + else + lastPrim = nextPrim + end + end + + return canShoot +end + +local bitbor = bit.bor +local trace = {} +local traceres = {} +trace.filter = LocalPlayer() +trace.mask = MASK_SHOT +trace.output = traceres + +local lply = nil + +timer.Create("LeyHitreg.LocalPlayerGet", 0.1, 0, function() + if (not lply and IsValid(LocalPlayer())) then + lply = LocalPlayer() + trace.filter = lply + timer.Remove("LeyHitreg.LocalPlayerGet") + end +end) + +LeyHitreg.WeaponSpreads = {} + +function LeyHitreg:IsAutoWep(wep) + if (wep.Primary) then + return wep.Primary.Automatic + end + + return true +end + +local NeedsPrimReset = false + +function LeyHitreg:CreateMove(cmd) + if (not lply or LeyHitreg.Disabled or LeyHitreg.DisabledOnlyOnClient) then + return + end + + local spreadWep = lply.LeyHitreg_NeedsSpreadForce + + if (spreadWep and IsValid(spreadWep)) then + LeyHitreg:SetFittingValidClip(spreadWep) + end + + if (cmd:CommandNumber() == 0) then + return + end + + local cmdAttack1 = cmd:KeyDown(IN_ATTACK) + + if (not cmdAttack1) then + NeedsPrimReset = false + return + elseif (NeedsPrimReset and not cmdAttack1) then + NeedsPrimReset = false + end + + local shouldPrimary = self:ShouldPrimaryAttack() + + if (not shouldPrimary) then + return + end + + local wep = lply:GetActiveWeapon() + + if (not IsValid(wep)) then + return + end + + if (self:IsIgnoreWep(wep)) then + return + end + + if (not self:CanShoot(cmd, wep, shouldPrimary)) then + return + end + + local primAuto = self:IsAutoWep(wep) + + if (NeedsPrimReset and shouldPrimary) then + return + end + + if (not primAuto and shouldPrimary) then + NeedsPrimReset = true + end + + if (shouldPrimary) then + cmd:SetButtons(bitbor(cmd:GetButtons(), IN_LEYHITREG1)) + end + + trace.start = lply:GetShootPos() + local viewang = cmd:GetViewAngles() + local dir = viewang:Forward() + + local weaponSpread = self:GetWeaponSpread(lply, wep) + + if (weaponSpread) then + local applied, newDir = self:ApplyBulletSpread(lply, dir, weaponSpread) + + if (applied) then + dir = newDir + end + else + -- LocalPlayer():ChatPrint("NO WEAPONSPREAD") + end + + trace.endpos = trace.start + (dir * (56756 * 8)) + traceres.Entity = nil + traceres.HitGroup = nil + traceres.HitBox = nil + + util.TraceLine(trace) + + local target = traceres.Entity + + if (not IsValid(target) or not (target:IsNPC() or target:IsPlayer())) then + cmd:SetUpMove(-1) + if (LeyHitreg.AnnounceClientHits) then + LocalPlayer():ChatPrint("It's a miss!") + -- PrintTable(trace) + end + return + end + + local hitgroup = traceres.HitGroup + local hitbox = traceres.HitBox + local hitbone = target:GetHitBoxBone(hitbox, 0) + + if (not hitbone or not hitgroup) then + print("[/LeyHitreg/] Bone not found") + return + end + + cmd:SetUpMove(target:EntIndex()) + cmd:SetMouseWheel(hitbone) + + if (LeyHitreg.AnnounceClientHits) then + LocalPlayer():ChatPrint("It's a hit!") + end +end + +hook.Add("CreateMove", "LeyHitreg:CreateMove", function(...) + LeyHitreg:CreateMove(...) +end) + +function LeyHitreg:EntityFireBullets(plyorwep, bullet) + if (LeyHitreg.Disabled or LeyHitreg.DisabledOnlyOnClient) then + return + end + + if (bullet.Num >= 2) then + return + end + + local ply, wep = self:GetPlayerFromPlyOrBullet(plyorwep, bullet) + + if (not ply) then + return + end + + if (not wep or self:IsIgnoreWep(wep)) then + return + end + + if (not LeyHitreg.ShotDirForceDisabled) then + bullet.Dir = ply:GetAimVector() + end + + local forcedShot = LeyHitreg:FetchSpreadFireBullets(ply, wep, bullet) + + if (forcedShot != nil) then + return forcedShot + end + + local ret = LeyHitreg:SpreadedEntityFireBullets(ply, wep, bullet) + + if (ret != nil) then + return ret + end +end + +hook.Add("EntityFireBullets", "LeyHitreg:EntityFireBullets", function(plyorwep, bullet) + local ret = LeyHitreg:EntityFireBullets(plyorwep, bullet) + + if (ret != nil) then + return ret + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/bulletprocessing/hitscan.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/bulletprocessing/hitscan.lua new file mode 100644 index 0000000..014ebb2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/bulletprocessing/hitscan.lua @@ -0,0 +1,60 @@ +-- By just locking onto a specific bone, we don't always get a match. We might lock onto a bone pos +-- and that bone pos just happens to be the center. Then, if we're looking through a edge +-- it's possible that the bullet would miss +-- so how do we fix this? well, hit scanning +-- if a bullet which should hit locked onto a bone pos misses, +-- then attempt to use a more expensive calculation to scan for the precise +-- location which would hit +-- if one is found -> fire bullet again but with that information +-- alternatively, dont fire +-- the calculation is done in bullet cb to make it less expensive +-- because most of the time, just picking the bone pos should work + +-- bullet missed even though it should not have, try hitscan +function LeyHitreg:HitScan(ply, target, bullet, shot, targetBone, targetHitGroup, shootpos) + -- TODO: do hitscan here +end + +local IsValid = IsValid + +function LeyHitreg:HitScanBulletCallback(ply, target, bullet, shot, targetBone, targetHitGroup, shootpos, cbdata) + local traceres = cbdata[2] + + if (IsValid(traceres.Entity)) then + return + end + + self:HitScan(ply, target, bullet, shot, targetBone, targetHitGroup, shootpos) +end + +function LeyHitreg:HitScanBullet(ply, target, bullet, shot, targetBone, targetHitGroup, shootpos) + if (self.BulletOverwriteDisabled) then + return + end + + local oldCallback = bullet.Callback or function() end + + bullet.Callback = function(...) + local cbdata = {...} + + if (cbdata[1] != ply) then + return oldCallback(...) + end + + local ignoreBullet = hook.Call("LeyHitreg.OnBulletCallback", cbdata[1], cbdata[2], cbdata[3]) + + if (ignoreBullet) then + return + end + + if (LeyHitreg.HitScanDisabled) then + return oldCallback(...) + end + + if (LeyHitreg:HitScanBulletCallback(ply, target, bullet, shot, targetBone, targetHitGroup, shootpos, cbdata)) then + return + end + + return oldCallback(...) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/bulletprocessing/processbullet.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/bulletprocessing/processbullet.lua new file mode 100644 index 0000000..5fea003 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/bulletprocessing/processbullet.lua @@ -0,0 +1,322 @@ +local IsValid = IsValid +local CurTime = CurTime +local ipairs = ipairs +local tableremove = table.remove + +LeyHitreg.ForceHit = {} + +local toRemove = {} + +function LeyHitreg:CleanHits(ply, wep, tbl) + local needsRemove = false + local highestKey = nil + + local curTime = CurTime() + + for k,v in ipairs(tbl) do + local target = v.target + + if (not IsValid(target) or target:Health() < 0 or curTime > v.expireTime or v.weapon != wep) then + toRemove[#toRemove + 1] = k + + if (not highestKey or k > highestKey) then + highestKey = k + end + + needsRemove = true + end + end + + if (not needsRemove) then + return tbl + end + + for i = #toRemove, 1, -1 do + local key = toRemove[i] + tableremove(tbl, key) + toRemove[i] = nil + end + + return tbl +end + +LeyHitreg.ScaleDamageBlockEntity = LeyHitreg.ScaleDamageBlockEntity or {} + +local spread = vector_origin + +function LeyHitreg:FallbackEntityFireBullets(ply, wep, bullet) + local ret = LeyHitreg:SpreadedEntityFireBullets(ply, wep, bullet) + if (LeyHitreg.ShowActualShotSpreadedHit) then + LeyHitreg:DebugShowActualShotHit(bullet) + end + if (ret != nil) then + return ret + end +end + +function LeyHitreg:DebugShowActualShotHit(bullet) + + local ocb = bullet.Callback or function() end + + bullet.Callback = function(atk, tr, dmginfo, ...) + util.Decal("Eye", tr.HitPos + tr.HitNormal, tr.HitPos - tr.HitNormal) + return ocb(atk, tr, dmginfo, ...) + end + +end + +function LeyHitreg:EntityTakeDamage(ent, dmg) + if (not IsValid(ent)) then + return + end + + if (not ent.IsNPC or not ent.IsPlayer) then + return + end + + if (not ent:IsNPC() and not ent:IsPlayer()) then + return + end + + local atk = dmg:GetAttacker() + + if (not IsValid(atk)) then + return + end + + if (atk.LeyHitreg_CurrentBullet) then + atk.LeyHitreg_CurrentBullet = nil + atk.LeyHitreg_LastShotMissed = nil + end +end + +hook.Add("EntityTakeDamage","LeyHitreg:EntityTakeDamage", function(ent, dmg) + local ret = LeyHitreg:EntityTakeDamage(ent, dmg) + + if (ret != nil) then + return ret + end +end) + +local tickCount = engine.TickCount +function LeyHitreg:PlayerTick(ply) + if (ply.LeyHitreg_CurrentBullet) then + local curTick = tickCount() + ply.LeyHitreg_CurrentBullet = nil + ply.LeyHitreg_LastShotMissed = curTick + + local hitTable = LeyHitreg.ForceHit[ply] + + if (not hitTable) then + return + end + + local shot = nil + local bestDelta = nil + + for k,v in ipairs(hitTable) do + local delta = curTick - v.tickCount + if (delta >= 0) then + if (not bestDelta or bestDelta > delta) then + bestDelta = delta + shot = v + end + end + end + + if (not shot) then + return + end + + if (bestDelta > 6) then -- Ideally I'd probably assume it's the same tick + return + end + + ply.LeyHitreg_LastBulletEnt:FireBullets(ply.LeyHitreg_LastBullet) + end +end + +hook.Add("PlayerTick", "LeyHitreg:PlayerTick", function(ply) + LeyHitreg:PlayerTick(ply) +end) + +function LeyHitreg:EntityFireBullets(plyorwep, bullet) + local ocb = bullet.Callback or function() end + + bullet.Callback = function(atk, tr, dmginfo, ...) + atk.LeyHitreg_CurrentBullet = true + atk.LeyHitreg_LastBullet = table.Copy(bullet) + atk.LeyHitreg_LastBulletEnt = plyorwep + return ocb(atk, tr, dmginfo, ...) + end + + if (LeyHitreg.ShowActualShotHit) then + LeyHitreg:DebugShowActualShotHit(bullet) + end + + local ply, wep = self:GetPlayerFromPlyOrBullet(plyorwep, bullet) + + if (not ply or not wep) then + return + end + + if (self:IsIgnoreWep(wep)) then + return + end + + if (not LeyHitreg.ShotDirForceDisabled) then + bullet.Dir = ply:GetAimVector() + end + + local hitTable = LeyHitreg.ForceHit[ply] + + if (not hitTable) then + return self:FallbackEntityFireBullets(ply, wep, bullet) + end + + local shot = self:CleanHits(ply, wep, hitTable)[1] + + if (not shot) then + return self:FallbackEntityFireBullets(ply, wep, bullet) + end + + tableremove(hitTable, 1) + local target = shot.target + + + -- print(canSee) + -- print(target) + -- PrintTable(shot) + + local targetpos = target:GetBonePosition(shot.targetBone) + + if (not targetpos) then + ply:ChatPrint("[/LeyHitreg/] Bone not found") + return self:FallbackEntityFireBullets(ply, wep, bullet) + end + + local newshootpos = ply:GetShootPos() + local newdir = (targetpos - bullet.Src) + + LeyHitreg:HitScanBullet(ply, target, bullet, shot, shot.targetBone, shot.targetHitGroup, newshootpos) + + bullet.Src = newshootpos + bullet.Dir = newdir + bullet.Spread = vector_origin + + self.ScaleDamageBlockEntity[ply] = true + + ply.LeyHitReg_ShouldHit = shot.targetHitGroup + + if (LeyHitreg.LogFixedBullets) then + ply.LeyHitreg_Bullets = (ply.LeyHitreg_Bullets or 0) + 1 + + timer.Create("LeyHitreg." .. ply:SteamID64() .. ".LogFixedBullets", 1, 1, function() + ply:ChatPrint("bullets hitregged: " .. tostring(ply.LeyHitreg_Bullets)) + ply.LeyHitreg_Bullets = 0 + end) + end + + if (LeyHitreg.BulletAimbot) then + timer.Simple(0, function() + ply:SetEyeAngles(newdir:Angle()) + end) + end + + if (LeyHitreg.LogTargetBone) then + ply:ChatPrint("Target Bone: " .. tostring(shot.targetBone)) + end + + return true +end + +function LeyHitreg:InsertPlayerData(ply, cmd, wep, shouldPrimary, target, targetBone, targetHitGroup) + if (#self.ForceHit[ply] > 500) then + ply:Kick("[/LeyHitreg/] No Exploiting, little boy.") + return + end + + table.insert(self.ForceHit[ply], { + ["shouldPrimary"] = shouldPrimary, + ["target"] = target, + ["targetPos"] = target:GetPos(), + ["targetBone"] = targetBone, + ["targetHitGroup"] = targetHitGroup, + ["shootPos"] = ply:GetShootPos(), + ["eyeAngles"] = cmd:GetViewAngles(), + ["aimVec"] = ply:GetAimVector(), + ["shootPos"] = ply:GetPos(), + ["weapon"] = wep, + ["expireTime"] = CurTime() + 0.8, + ["tickCount"] = engine.TickCount() + }) +end + +function LeyHitreg:CanPrimaryAttack(wep) + local ply = wep:GetOwner() + if (wep:Clip1() == 0) then + return false + end + + if (not self.IgnoreCanNextPrimaryAttack and wep.CanPrimaryAttack) then + if (not wep:CanPrimaryAttack()) then + return false + end + + return true + end + + local nextPrim = wep:GetNextPrimaryFire() + + if (wep.LastNextPrim and wep.LastNextPrim == nextPrim) then + return false + end + + wep.LastNextPrim = nextPrim + + return true +end + +function LeyHitreg:ProcessBullet(ply, cmd, wep, shouldPrimary, target, targetBone) + self.ForceHit[ply] = self.ForceHit[ply] or {} + + if (not target or target:Health() < 0) then + return + end + + if (wep.LeyHitregIgnore) then + return + end + + if (shouldPrimary and self:CanPrimaryAttack(wep)) then + local targetHitGroup = HITGROUP_GENERIC + + local hitboxsets = target.GetHitBoxSetCount and target:GetHitBoxSetCount() or 1 + for hitboxset = 0, hitboxsets - 1 do + local hitboxes = target:GetHitBoxCount(hitboxset) + + for hitbox = 0, hitboxes - 1 do + local bone = target:GetHitBoxBone(hitbox, hitboxset) + + if (bone == targetBone) then + targetHitGroup = target:GetHitBoxHitGroup(hitbox, hitboxset) + end + end + end + + local hookRet = hook.Call("LeyHitreg:ProcessBullet", nil, ply, cmd, wep, shouldPrimary, target, targetBone, targetHitGroup) + if (hookRet == false) then + return + end + + self:InsertPlayerData(ply, cmd, wep, shouldPrimary, target, targetBone, targetHitGroup) + end +end + +hook.Add("EntityFireBullets", "LeyHitreg:EntityFireBullets", function(...) + local ret = LeyHitreg:EntityFireBullets(...) + + if (ret != nil) then + return ret + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/damageinfo/fixscaling.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/damageinfo/fixscaling.lua new file mode 100644 index 0000000..15c12f8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/damageinfo/fixscaling.lua @@ -0,0 +1,133 @@ +function LeyHitreg:OriginalScaleDamage(ent, hitgroup, dmg, orighitgroup) + if (not self.LogHitgroupMismatches) then + return + end + + local atk = dmg:GetAttacker() + + if (orighitgroup and IsValid(atk) and atk.IsPlayer and atk:IsPlayer()) then + local shouldHit = hitgroup + atk:ChatPrint("HITGROUP_HEAD: " .. tostring(hitgroup == HITGROUP_HEAD)) + atk:ChatPrint(shouldHit .. "==" .. orighitgroup) + return + end +end + +hook.Add("ScalePlayerDamage", "LeyHitreg.DamageLog", function(ent, hitgroup, dmg, orighitgroup) + local ret = LeyHitreg:OriginalScaleDamage(ent, hitgroup, dmg, orighitgroup) + if (ret != nil) then + return ret + end +end) + +hook.Add("ScaleNPCDamage", "LeyHitreg.DamageLog", function(ent, hitgroup, dmg, orighitgroup) + local ret = LeyHitreg:OriginalScaleDamage(ent, hitgroup, dmg, orighitgroup) + if (ret != nil) then + return ret + end +end) + +LeyHitreg.ScaleDamagePlayersHooks = {} +LeyHitreg.ScaleDamageNPCsHooks = {} + +function LeyHitreg:ScaleDamageCorrectly(target, hitgroup, dmg, targetisplayer) + local atk = dmg:GetAttacker() + local orighitgroup = hitgroup + + if (IsValid(atk) and atk:IsPlayer()) then + if (LeyHitreg.ScaleDamageBlockEntity[atk]) then + LeyHitreg.ScaleDamageBlockEntity[atk] = false + end + + hitgroup = atk.LeyHitReg_ShouldHit or hitgroup + atk.LeyHitReg_ShouldHit = nil + end + + local damageHooks = targetisplayer and self.ScaleDamagePlayersHooks or self.ScaleDamageNPCsHooks + + for k,v in pairs(damageHooks) do + local ret = v(target, hitgroup, dmg, orighitgroup) + + if (ret != nil) then + return ret + end + end + + if (targetisplayer) then + local ret = GAMEMODE:OldScalePlayerDamage(target, hitgroup, dmg, orighitgroup) + + if (ret != nil) then + return ret + end + else + local ret = GAMEMODE:OldScaleNPCDamage(target, hitgroup, dmg, orighitgroup) + + if (ret != nil) then + return ret + end + end +end + +function LeyHitreg:ScalePlayerDamage(ply, hitgroup, dmg) + return LeyHitreg:ScaleDamageCorrectly(ply, hitgroup, dmg, true) +end + +function LeyHitreg:ScaleNPCDamage(npc, hitgroup, dmg) + return LeyHitreg:ScaleDamageCorrectly(npc, hitgroup, dmg, false) +end + +function LeyHitreg:AbsorbScaleDamageHooks() + if (self.Disabled) then + return + end + + GAMEMODE.OldScalePlayerDamage = GAMEMODE.OldScalePlayerDamage or GAMEMODE.ScalePlayerDamage + GAMEMODE.OldScaleNPCDamage = GAMEMODE.OldScaleNPCDamage or GAMEMODE.ScaleNPCDamage + + function GAMEMODE:ScalePlayerDamage(...) + if (LeyHitreg.Disabled) then + return self:OldScalePlayerDamage(...) + end + + local ret = LeyHitreg:ScalePlayerDamage(...) + + if (ret) then + return ret + end + end + + function GAMEMODE:ScaleNPCDamage(...) + if (LeyHitreg.Disabled) then + return self:OldScaleNPCDamage(...) + end + + local ret = LeyHitreg:ScaleNPCDamage(...) + + if (ret) then + return ret + end + end + + local allHooks = hook.GetTable() + + local scalePlayers = allHooks["ScalePlayerDamage"] or {} + local scaleNPCs = allHooks["ScaleNPCDamage"] or {} + + for k,v in pairs(scalePlayers) do + hook.Remove("ScalePlayerDamage", k) + self.ScaleDamagePlayersHooks[k] = v + end + + for k,v in pairs(scaleNPCs) do + hook.Remove("ScaleNPCDamage", k) + self.ScaleDamageNPCsHooks[k] = v + end +end + +timer.Simple(1, function() + LeyHitreg:AbsorbScaleDamageHooks() +end) + +timer.Create("LeyHitreg:AbsorbScaleDamageHooks", 5, 0, function() + LeyHitreg:AbsorbScaleDamageHooks() +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/damageinfo/scaledamagehack.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/damageinfo/scaledamagehack.lua new file mode 100644 index 0000000..d063ff3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/damageinfo/scaledamagehack.lua @@ -0,0 +1,12 @@ +local meta = FindMetaTable("CTakeDamageInfo") + +LeyHitreg.ScaleDamageBlockEntity = LeyHitreg.ScaleDamageBlockEntity or {} +meta.OldScaleDamage = meta.OldScaleDamage or meta.ScaleDamage + +function meta:ScaleDamage(scale) + if (self:IsBulletDamage() and LeyHitreg and LeyHitreg.ScaleDamageBlockEntity[self:GetAttacker()]) then + return + end + + return self:OldScaleDamage(scale) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/receiveshotinfo/receiveshotinfo.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/receiveshotinfo/receiveshotinfo.lua new file mode 100644 index 0000000..8f5f5d4 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/receiveshotinfo/receiveshotinfo.lua @@ -0,0 +1,101 @@ +IN_LEYHITREG1 = bit.lshift(1, 27) + +local Entity = Entity +local IsValid = IsValid + +local PlyNeedsPrimReset = {} + +LeyHitreg.BulletCount = {} +LeyHitreg.InvalidShots = {} + +function LeyHitreg:StartCommand(ply, cmd) + if (not ply:Alive() or LeyHitreg.Disabled) then + return + end + + local shouldPrimary = cmd:KeyDown(IN_LEYHITREG1) + + if (not shouldPrimary) then + return + end + + local targetEntIndex = cmd:GetUpMove() + local targetBone = cmd:GetMouseWheel() + + cmd:SetUpMove(0) + cmd:SetMouseWheel(0) + + if (targetBone > 0xFF) then + return + end + + local wep = ply:GetActiveWeapon() + + if (not IsValid(wep)) then + return + end + + if (self:IsIgnoreWep(wep)) then + return + end + + if ((LeyHitreg.BulletCount[ply] or 0) > 50) then + return + end + + LeyHitreg.BulletCount[ply] = (LeyHitreg.BulletCount[ply] or 0) + 1 + + local target + + if (targetEntIndex and targetEntIndex > 0) then + target = Entity(targetEntIndex) + + if (not IsValid(target) or not (target:IsNPC() or target:IsPlayer())) then + target = nil + end + else + targetEntIndex = 0 + end + + if (target) then + if (self:IsInvalidShot(ply, cmd, wep, shouldPrimary, target, targetBone)) then + if (self.LogInvalidShots) then + ply:ChatPrint("Invalid shot!") + end + + LeyHitreg.InvalidShots[ply] = (LeyHitreg.InvalidShots[ply] or 0) + 1 + return + end + + end + + LeyHitreg:ProcessBullet(ply, cmd, wep, shouldPrimary, target, targetBone) +end + +timer.Create("LeyHitreg.BulletMax", 0.1, 0, function() + for k, ply in ipairs(player.GetAll()) do + LeyHitreg.BulletCount[ply] = 0 + end +end) + +timer.Create("LeyHitreg.KickInvalidShooters", 30, 0, function() + for k, ply in ipairs(player.GetAll()) do + if ((LeyHitreg.InvalidShots[ply] or 0) >= 10) then + ply:Kick("[/LeyHitreg/] Too many invalid shots!") + end + + LeyHitreg.InvalidShots[ply] = 0 + end +end) + +hook.Add("StartCommand", "LeyHitreg:StartCommand", function(ply, cmd) + LeyHitreg:StartCommand(ply, cmd) +end) + +function LeyHitreg:PlayerSwitchWeapon(ply, oldWep, newWep) + PlyNeedsPrimReset[ply] = nil +end + +hook.Add("PlayerSwitchWeapon", "LeyHitreg:PlayerSwitchWeapon", function(ply, oldWep, newWep) + LeyHitreg:PlayerSwitchWeapon(ply, oldWep, newWep) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/receiveshotinfo/shotvalidator.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/receiveshotinfo/shotvalidator.lua new file mode 100644 index 0000000..182cdd6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/server/receiveshotinfo/shotvalidator.lua @@ -0,0 +1,72 @@ +-- just ideas to make aimbot using this harder + +-- if the angle which client supposedly used to hit target is more than MaxFOVDiffDeg degrees off of the target +-- chances are it's not a information mismatch but a manipulation attempt + +local mathacos = math.acos +local mathdeg = math.deg + +local FOVCheckCloseDist = 350 * 350 +local FOVCheckFarDist = 700 * 700 + +local MaxCloseFOVDiffDeg = 90 +local MaxFarFOVDiffDeg = 40 + +local MaxCloseFOVDiffRad = math.rad(MaxCloseFOVDiffDeg) +local MaxFarFOVDiffRad = math.rad(MaxFarFOVDiffDeg) + + +function LeyHitreg:IsInvalidShotOutOfFOV(ply, plyang, plypos, tarpos, distsqr) + if (distsqr < FOVCheckCloseDist) then + if (self.LogInvalidFOV) then + ply:ChatPrint("FOV: Close, so no checking") + end + + return false + end + + local maxRad = distsqr > FOVCheckFarDist and MaxFarFOVDiffRad or MaxCloseFOVDiffRad + + local targetdir = (tarpos - plypos) + local curdir = plyang:Forward() + local FOVDiff = mathacos(targetdir:Dot(curdir) / (targetdir:Length() * curdir:Length())) + + if (self.LogInvalidFOV) then + ply:ChatPrint("FOV: " .. tostring(mathdeg(FOVDiff)) .. " Max: " .. tostring(mathdeg(maxRad))) + end + + return FOVDiff > maxRad +end + +-- if the weapon has spread, and the client did not apply any spread, then reject the bullet +-- this requires the unmodified va to be sent together in the usercmd somehow +-- then can compare the two and if equal, yeet +function LeyHitreg:IsSpreadNotApplied(ply, cmd, wep, shouldPrimary) + if (self.NoSpread) then + return false + end + + return false +end + +function LeyHitreg:IsInvalidShot(ply, cmd, wep, shouldPrimary, target, targetBone) + if (LeyHitreg.DisableSecurityChecks) then + return false + end + + local plyang = cmd:GetViewAngles() + local plypos = ply:GetPos() + local tarpos = target:GetPos() + local distsqr = plypos:DistToSqr(tarpos) + + if (self:IsInvalidShotOutOfFOV(ply, plyang, plypos, tarpos, distsqr)) then + return true + end + + + if (self:IsSpreadNotApplied(ply, cmd, wep, shouldPrimary)) then + return true + end + + return false +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/disablelagcomp/disablelagcomp.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/disablelagcomp/disablelagcomp.lua new file mode 100644 index 0000000..0aae217 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/disablelagcomp/disablelagcomp.lua @@ -0,0 +1,10 @@ +local meta = FindMetaTable("Player") +meta.OldLagCompensation = meta.OldLagCompensation or meta.LagCompensation + +function meta:LagCompensation(...) + if (LeyHitreg.DisableLagComp and not LeyHitreg.Disabled) then + return + end + + return self:OldLagCompensation(...) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/spreadsystem/bulletspread.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/spreadsystem/bulletspread.lua new file mode 100644 index 0000000..ee1bd58 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/spreadsystem/bulletspread.lua @@ -0,0 +1,50 @@ +local Vector = Vector +local mathmodf = math.modf +local mathrandomseed = math.randomseed +local mathrandom = math.random +local mathsqrt = math.sqrt +local isnumber = isnumber +local vector_origin = vector_origin + +local timefn = function() + return 1 -- os.date("%S") +end + +function LeyHitreg:ApplyBulletSpread(ply, dir, spread) + if (LeyHitreg.NoSpread) then + return true, dir, vector_origin + end + + if (not spread or spread == vector_origin or LeyHitreg.BrokenSpread) then + return false + end + + if (isnumber(spread)) then + spread = Vector(spread, spread, spread) + end + + local add = (8969 * timefn()) + + mathrandomseed(add + CurTime()) + + local ang = dir:Angle() + + local appliedSpread, rgt, up = Vector(), ang:Right(), ang:Up() + + local x, y, z + + repeat + x = mathrandom() + mathrandom() - 1 + y = mathrandom() + mathrandom() - 1 + + z = x * x + y * y + until z <= 1 + + for i = 1, 3 do + appliedSpread[i] = x * spread.x * rgt[i] + y * spread.y * up[i] + end + + dir = dir + appliedSpread + + return true, dir, appliedSpread +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/spreadsystem/firebullets.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/spreadsystem/firebullets.lua new file mode 100644 index 0000000..ea540f3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/spreadsystem/firebullets.lua @@ -0,0 +1,20 @@ +function LeyHitreg:SpreadedEntityFireBullets(ply, wep, bullet, spread) + if (LeyHitreg.Disabled) then + return + end + + if (LeyHitreg.BrokenDefaultSpread) then + return + end + + local bulletSpread = spread or LeyHitreg:GetWeaponSpread(ply, wep, bullet) + local appliedAny, newDir = self:ApplyBulletSpread(ply, bullet.Dir, bulletSpread) + + if (not appliedAny) then + return + end + + bullet.Spread = vector_origin + bullet.Dir = newDir + return true +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/workarounds/swepbases.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/workarounds/swepbases.lua new file mode 100644 index 0000000..5a342fd --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/workarounds/swepbases.lua @@ -0,0 +1,16 @@ + +function LeyHitreg:ConVarSet(name, val) + if (GetConVar(name)) then + RunConsoleCommand(name, val) + end +end + +function LeyHitreg:SWEPConvars() + self:ConVarSet("arccw_enable_penetration", "0") + self:ConVarSet("sv_tfa_bullet_penetration", "0") + self:ConVarSet("sv_tfa_bullet_randomseed", "0") +end + +timer.Simple(1, function() + LeyHitreg:SWEPConvars() +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/workarounds/workarounds.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/workarounds/workarounds.lua new file mode 100644 index 0000000..0fb610c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/leyhitreg/shared/workarounds/workarounds.lua @@ -0,0 +1,205 @@ +local HL2Ignore = {} +HL2Ignore["weapon_physcannon"] = true +HL2Ignore["weapon_physgun"] = true +HL2Ignore["weapon_frag"] = true +HL2Ignore["weapon_rpg"] = true +HL2Ignore["gmod_camera"] = true +HL2Ignore["gmod_tool"] = true +HL2Ignore["weapon_physcannon"] = true +HL2Ignore["weapon_shotgun"] = true + +local MeleeHoldType = {} +MeleeHoldType["knife"] = true +MeleeHoldType["melee"] = true +MeleeHoldType["melee2"] = true + +local ExtraIgnores = {} + +function LeyHitreg:IsIgnoreWep(wep) + if (HL2Ignore[wep:GetClass()]) then + return true + end + + if (ExtraIgnores[wep:GetClass()]) then + return true + end + + -- Ignore all melees + if (wep.IsMelee or wep.Melee or wep:Clip1() < 0) then + return true + end + + + if (wep.GetHoldType) then + local holdType = wep:GetHoldType() + + if (MeleeHoldType[holdType]) then + return true + end + end + + -- Ignore shotguns + if (wep.Shotgun or wep.IsShotgun or wep.ShotgunReload or wep.ShotGun or wep.Primary and wep.Primary.NumShots and wep.Primary.NumShots > 1) then + return true + end + -- Ignore modern day SWEP creators who are too busy reinventing the wheel to add a single wep.IsShotgun variable + if (wep.ShotgunEmptyAnim or wep.ShotgunStartAnimShell) then + return true + end + + if (wep.Category and string.find(string.lower(wep.Category), "shotgun", 1, true)) then + return true + end + + if (wep.Purpose and string.find(string.lower(wep.Purpose), "shotgun", 1, true)) then + return true + end + + if (wep.PrintName and string.find(string.lower(wep.PrintName), "shotgun", 1, true)) then + return true + end + + if (ACT3_CAT_SHOTGUN and wep.ACT3Cat and wep.ACT3Cat == ACT3_CAT_SHOTGUN) then + return true + end + + return false +end + +function LeyHitreg:AddIgnoreWeapon(weporclass) + if (isstring(weporclass)) then + ExtraIgnores[weporclass] = true + else + ExtraIgnores[weporclass:GetClass()] = true + end +end + +function LeyHitreg:GetPlayerFromWeapon(wep) + local ply = wep:GetOwner() + + if (not IsValid(ply)) then + return + end + + return ply +end + +local IsValid = IsValid +function LeyHitreg:GetPlayerFromPlyOrBullet(plyorwep, bullet) + if (not bullet or not IsValid(plyorwep)) then + return + end + + local ply = bullet.Attacker + + if (not IsValid(ply) or not ply:IsPlayer()) then + ply = nil + end + + if (plyorwep:IsWeapon()) then + if (ply) then + return ply, plyorwep + end + + local owner = plyorwep:GetOwner() + + if (not IsValid(owner) or not owner:IsPlayer()) then + return + end + + return owner, plyorwep + end + + if (not ply) then + ply = plyorwep + + if (not ply:IsPlayer()) then + return + end + end + + local wep = ply:GetActiveWeapon() + + if (not IsValid(wep)) then + return + end + + return ply, wep +end + +local vector_origin = vector_origin + +function LeyHitreg:GetWeaponSpread(ply, wep, bullet) + -- MW Swep pack workaround + if (wep.CalculateCone) then + return wep:CalculateCone() * 0.1 * 0.7 + end + + -- TFA workaround + if (wep.CalculateConeRecoil) then + return wep:CalculateConeRecoil() + end + + -- ARCCW workaround + if (wep.GetBuff and wep.ApplyRandomSpread and wep.TryBustDoor and ArcCW) then + return ArcCW.MOAToAcc * wep:GetBuff("AccuracyMOA") * 4.5 + end + + + -- CW2 workaround + + if (wep.AimSpread and wep.recalculateAimSpread and wep.getBaseCone) then + return wep:getBaseCone() + end + + if (bullet) then + local bulletSpread = bullet.Spread + + if (bulletSpread and bulletSpread != vector_origin) then + return bulletSpread + end + end + + if (self.WeaponSpreads and self.WeaponSpreads[wep:GetClass()]) then + return self.WeaponSpreads[wep:GetClass()] + end + + if (wep.PrimarySpread) then + return wep.PrimarySpread + end + + if (wep.PrimaryCone) then + return wep.PrimaryCone + end + + if (wep.Primary) then + if (wep.Primary.Spread) then + return wep.Primary.Spread + end + + if (wep.Primary.Cone) then + return wep.Primary.Cone + end + + return bulletSpread + end + + return vector_origin +end + +function LeyHitreg:SetFittingValidClip(wep) + local clip1 = wep:Clip1() + + if (clip1 == -1 or clip1 > 0) then + return + end + + local max = wep:GetMaxClip1() + + if (max > 0) then + wep:SetClip1(max) + return + end + + wep:SetClip1(30) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/sh_plugin.lua new file mode 100644 index 0000000..8ac297c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/leyhitreg/sh_plugin.lua @@ -0,0 +1,142 @@ +PLUGIN.name = "LeyHitReg" +PLUGIN.author = "Ley (Портирован для Helix)" +PLUGIN.description = "Улучшенная система регистрации попаданий для точной стрельбы" + +-- Конфигурация системы +LeyHitreg = LeyHitreg or {} + +-- Настройки через ix.config +ix.config.Add("leyHitregEnabled", true, "Включить систему улучшенной регистрации попаданий", nil, { + category = "LeyHitReg" +}) + +ix.config.Add("leyHitregShotDirForceDisabled", false, "Принудительно отключить направление выстрела (для тестирования проблемных SWEP баз)", nil, { + category = "LeyHitReg" +}) + +ix.config.Add("leyHitregDisableSecurityChecks", false, "Отключить проверки безопасности выстрелов", nil, { + category = "LeyHitReg" +}) + +ix.config.Add("leyHitregIgnoreCanNextPrimaryAttack", true, "Игнорировать задержку между выстрелами (рекомендуется)", nil, { + category = "LeyHitReg" +}) + +ix.config.Add("leyHitregNoSpread", false, "[DEBUG] Включить режим без разброса для всех", nil, { + category = "LeyHitReg - Debug" +}) + +ix.config.Add("leyHitregShowActualHit", false, "[DEBUG] Показывать где на самом деле попала пуля (без разброса)", nil, { + category = "LeyHitReg - Debug" +}) + +ix.config.Add("leyHitregShowSpreadedHit", false, "[DEBUG] Показывать где на самом деле попала пуля (с разбросом)", nil, { + category = "LeyHitReg - Debug" +}) + +ix.config.Add("leyHitregLogFixedBullets", false, "[DEBUG] Логировать количество исправленных пуль", nil, { + category = "LeyHitReg - Debug" +}) + +ix.config.Add("leyHitregLogInvalidShots", false, "[DEBUG] Логировать невалидные выстрелы", nil, { + category = "LeyHitReg - Debug" +}) + +-- Применяем настройки к LeyHitreg +LeyHitreg.ShotDirForceDisabled = ix.config.Get("leyHitregShotDirForceDisabled", false) +LeyHitreg.DisableSecurityChecks = ix.config.Get("leyHitregDisableSecurityChecks", false) +LeyHitreg.IgnoreCanNextPrimaryAttack = ix.config.Get("leyHitregIgnoreCanNextPrimaryAttack", true) + +-- Debug настройки +LeyHitreg.Disabled = not ix.config.Get("leyHitregEnabled", true) +LeyHitreg.DisabledOnlyOnClient = false +LeyHitreg.NoSpread = ix.config.Get("leyHitregNoSpread", false) +LeyHitreg.ShowActualShotHit = ix.config.Get("leyHitregShowActualHit", false) +LeyHitreg.ShowActualShotSpreadedHit = ix.config.Get("leyHitregShowSpreadedHit", false) +LeyHitreg.BrokenDefaultSpread = false +LeyHitreg.LogHitgroupMismatches = false +LeyHitreg.LogFixedBullets = ix.config.Get("leyHitregLogFixedBullets", false) +LeyHitreg.LogInvalidFOV = false +LeyHitreg.LogInvalidShots = ix.config.Get("leyHitregLogInvalidShots", false) +LeyHitreg.BulletAimbot = false +LeyHitreg.LogTargetBone = false +LeyHitreg.HitScanDisabled = false +LeyHitreg.BulletOverwriteDisabled = false +LeyHitreg.AnnounceClientHits = false +LeyHitreg.DisableLagComp = false + +if LeyHitreg.Disabled then + print("[LeyHitReg] Отключен через настройки") + return +end + +-- Отключение конфликтующих систем хитрега +function LeyHitreg:DisableMoatHitreg() + if MOAT_HITREG then + MOAT_HITREG.MaxPing = 1 + end + + if ConVarExists("moat_alt_hitreg") then + RunConsoleCommand("moat_alt_hitreg", "0") + end + + if SHR then + if SHR.Config then + SHR.Config.Enabled = false + SHR.Config.ClientDefault = 0 + end + hook.Remove("EntityFireBullets", "SHR.FireBullets") + hook.Remove("EntityFireBullets", "‍a") + net.Receivers["shr"] = function() end + end +end + +function PLUGIN:Initialize() + print("[LeyHitReg] Загрузка системы регистрации попаданий...") + + -- Отключаем конфликтующие системы + LeyHitreg:DisableMoatHitreg() + + print("[LeyHitReg] Система загружена!") +end + +-- Обновление настроек при изменении конфига +function PLUGIN:OnConfigChanged(key, oldValue, newValue) + if key == "leyHitregEnabled" then + LeyHitreg.Disabled = not newValue + print("[LeyHitReg] Система " .. (newValue and "включена" or "отключена")) + elseif key == "leyHitregShotDirForceDisabled" then + LeyHitreg.ShotDirForceDisabled = newValue + elseif key == "leyHitregDisableSecurityChecks" then + LeyHitreg.DisableSecurityChecks = newValue + elseif key == "leyHitregIgnoreCanNextPrimaryAttack" then + LeyHitreg.IgnoreCanNextPrimaryAttack = newValue + elseif key == "leyHitregNoSpread" then + LeyHitreg.NoSpread = newValue + elseif key == "leyHitregShowActualHit" then + LeyHitreg.ShowActualShotHit = newValue + elseif key == "leyHitregShowSpreadedHit" then + LeyHitreg.ShowActualShotSpreadedHit = newValue + elseif key == "leyHitregLogFixedBullets" then + LeyHitreg.LogFixedBullets = newValue + elseif key == "leyHitregLogInvalidShots" then + LeyHitreg.LogInvalidShots = newValue + end +end + +-- Загрузка модулей +ix.util.Include("leyhitreg/shared/spreadsystem/bulletspread.lua") +ix.util.Include("leyhitreg/shared/spreadsystem/firebullets.lua") +ix.util.Include("leyhitreg/shared/disablelagcomp/disablelagcomp.lua") +ix.util.Include("leyhitreg/shared/workarounds/workarounds.lua") +ix.util.Include("leyhitreg/shared/workarounds/swepbases.lua") + +ix.util.Include("leyhitreg/client/sendshots/sendshots.lua") +ix.util.Include("leyhitreg/client/sendshots/fetchspreads.lua") + +ix.util.Include("leyhitreg/server/bulletprocessing/hitscan.lua") +ix.util.Include("leyhitreg/server/bulletprocessing/processbullet.lua") +ix.util.Include("leyhitreg/server/damageinfo/scaledamagehack.lua") +ix.util.Include("leyhitreg/server/damageinfo/fixscaling.lua") +ix.util.Include("leyhitreg/server/receiveshotinfo/receiveshotinfo.lua") +ix.util.Include("leyhitreg/server/receiveshotinfo/shotvalidator.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/military_id/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/military_id/cl_plugin.lua new file mode 100644 index 0000000..cd65b8a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/military_id/cl_plugin.lua @@ -0,0 +1,357 @@ +local PLUGIN = PLUGIN + +-- Цветовая схема (как в арсенале) +local COLOR_BG_DARK = Color(3, 5, 4) +local COLOR_BG_MEDIUM = Color(8, 12, 10) +local COLOR_BG_LIGHT = Color(12, 18, 14) +local COLOR_PRIMARY = Color(27, 94, 32) +local COLOR_PRIMARY_DARK = Color(15, 60, 18) +local COLOR_ACCENT = Color(56, 102, 35) +local COLOR_TEXT_PRIMARY = Color(165, 214, 167) +local COLOR_TEXT_SECONDARY = Color(102, 187, 106) +local COLOR_DANGER = Color(136, 14, 14) +local COLOR_WARNING = Color(191, 130, 0) +local COLOR_BORDER = Color(15, 60, 18, 60) + +-- Вспомогательная функция для рисования кругов +if not draw.Circle then + function draw.Circle(x, y, radius, color) + local segmentCount = math.max(16, radius) + surface.SetDrawColor(color or color_white) + + local circle = {} + for i = 0, segmentCount do + local angle = math.rad((i / segmentCount) * 360) + table.insert(circle, { + x = x + math.cos(angle) * radius, + y = y + math.sin(angle) * radius + }) + end + surface.DrawPoly(circle) + end +end + +if not surface.DrawCircle then + function surface.DrawCircle(x, y, radius, color) + draw.Circle(x, y, radius, color) + end +end + +-- Открытие опроса +net.Receive("ixMilitaryID_OpenQuiz", function() + PLUGIN:OpenQuizMenu() +end) + +-- Просмотр документа +net.Receive("ixMilitaryID_ViewDocument", function() + local data = net.ReadTable() + PLUGIN:ShowDocument(data) +end) + +-- UI для опроса +function PLUGIN:OpenQuizMenu() + if IsValid(self.quizFrame) then + self.quizFrame:Remove() + end + + local scrW, scrH = ScrW(), ScrH() + local frame = vgui.Create("DFrame") + frame:SetSize(scrW, scrH) + frame:SetPos(0, 0) + frame:SetTitle("") + frame:SetDraggable(false) + frame:ShowCloseButton(false) + frame:MakePopup() + + -- Градиентный фон с зеленым оттенком (как в арсенале) + frame.Paint = function(s, w, h) + -- Основной фон + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawRect(0, 0, w, h) + + -- Градиент сверху + local gradHeight = 300 + for i = 0, gradHeight do + local alpha = (1 - i/gradHeight) * 40 + surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha) + surface.DrawRect(0, i, w, 1) + end + + -- Верхняя панель + surface.SetDrawColor(COLOR_BG_MEDIUM) + surface.DrawRect(0, 0, w, 100) + + -- Акцентная линия + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawRect(0, 100, w, 3) + + -- Декоративные элементы + surface.SetDrawColor(COLOR_BORDER) + for i = 0, w, 200 do + surface.DrawRect(i, 0, 1, 100) + end + + -- Заголовок + draw.SimpleText("◆ ВОЕННЫЙ ОПРОСНИК ◆", "ixMenuButtonFont", w/2, 35, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("ПРОХОЖДЕНИЕ ВОЕННОЙ КОМИССИИ", "ixSmallFont", w/2, 68, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Кнопка закрытия + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetText("") + closeBtn:SetSize(42, 42) + closeBtn:SetPos(scrW - 60, 18) + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7) + + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + surface.SetDrawColor(COLOR_TEXT_PRIMARY) + surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7) + surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30)) + end + end + closeBtn.DoClick = function() + frame:Close() + end + + -- Основная панель контента + local contentPanel = vgui.Create("DPanel", frame) + contentPanel:SetSize(1000, scrH - 240) + contentPanel:SetPos((scrW - 1000) / 2, 140) + contentPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local currentQuestion = 1 + local answers = {} + + -- Панель прогресса + local progressPanel = vgui.Create("DPanel", contentPanel) + progressPanel:Dock(TOP) + progressPanel:DockMargin(0, 0, 0, 10) + progressPanel:SetTall(60) + progressPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Прогресс бар + local progress = currentQuestion / #self.quizQuestions + local barWidth = (w - 40) * progress + draw.RoundedBox(4, 20, h - 15, w - 40, 8, COLOR_BG_DARK) + draw.RoundedBox(4, 20, h - 15, barWidth, 8, COLOR_ACCENT) + + -- Текст прогресса + draw.SimpleText("ВОПРОС " .. currentQuestion .. " ИЗ " .. #self.quizQuestions, "ixSmallFont", w/2, 20, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Панель вопроса + local questionPanel = vgui.Create("DPanel", contentPanel) + questionPanel:Dock(TOP) + questionPanel:DockMargin(0, 0, 0, 20) + questionPanel:SetTall(100) + questionPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local questionLabel = vgui.Create("DLabel", questionPanel) + questionLabel:Dock(FILL) + questionLabel:DockMargin(20, 20, 20, 20) + questionLabel:SetFont("DermaLarge") + questionLabel:SetTextColor(COLOR_TEXT_PRIMARY) + questionLabel:SetWrap(true) + questionLabel:SetContentAlignment(5) + + -- Панель ответов + local answersScroll = vgui.Create("DScrollPanel", contentPanel) + answersScroll:Dock(FILL) + answersScroll:DockMargin(0, 0, 0, 0) + + local sbar = answersScroll:GetVBar() + sbar:SetHideButtons(true) + function sbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK) + end + function sbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY) + end + + local function ShowQuestion(index) + if index > #self.quizQuestions then + -- Все вопросы пройдены + net.Start("ixMilitaryID_SubmitQuiz") + net.WriteTable(answers) + net.SendToServer() + frame:Close() + return + end + + local question = self.quizQuestions[index] + questionLabel:SetText(question.question) + + answersScroll:Clear() + + for i, answer in ipairs(question.answers) do + local btn = vgui.Create("DButton", answersScroll) + btn:Dock(TOP) + btn:DockMargin(20, 10, 20, 10) + btn:SetTall(60) + btn:SetText("") + + local isHovered = false + btn.Paint = function(s, w, h) + local bgColor = isHovered and COLOR_PRIMARY_DARK or COLOR_BG_LIGHT + draw.RoundedBox(6, 0, 0, w, h, bgColor) + + -- Рамка + surface.SetDrawColor(isHovered and COLOR_ACCENT or COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Акцентная линия слева + draw.RoundedBoxEx(6, 0, 0, 5, h, COLOR_PRIMARY, true, false, true, false) + + -- Номер ответа + draw.SimpleText(tostring(i), "DermaLarge", 30, h/2, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Текст ответа + draw.SimpleText(answer, "ixSmallFont", 60, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + if isHovered then + surface.SetDrawColor(255, 255, 255, 20) + draw.RoundedBox(6, 0, 0, w, h, Color(255, 255, 255, 20)) + end + end + + btn.OnCursorEntered = function() isHovered = true end + btn.OnCursorExited = function() isHovered = false end + + btn.DoClick = function() + answers[index] = i + currentQuestion = currentQuestion + 1 + ShowQuestion(currentQuestion) + end + end + end + + ShowQuestion(1) + + self.quizFrame = frame +end + +-- UI для просмотра документа +function PLUGIN:ShowDocument(data) + if IsValid(self.documentFrame) then + self.documentFrame:Remove() + end + + -- Отладка: выводим полученные данные + print("[MilitaryID] ShowDocument called with data:") + PrintTable(data or {}) + + local frame = vgui.Create("DFrame") + frame:SetSize(500, 400) + frame:SetTitle("") + frame:Center() + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(s, w, h) + -- Фон документа + draw.RoundedBox(8, 0, 0, w, h, Color(230, 220, 200)) + + -- Рамка + surface.SetDrawColor(100, 50, 0) + surface.DrawOutlinedRect(5, 25, w - 10, h - 30, 3) + + -- Заголовок + draw.SimpleText("ВОЕННЫЙ БИЛЕТ", "DermaLarge", w/2, 40, Color(100, 50, 0), TEXT_ALIGN_CENTER) + end + + local infoPanel = vgui.Create("DPanel", frame) + infoPanel:SetPos(30, 80) + infoPanel:SetSize(440, 280) + infoPanel.Paint = nil + + local yPos = 0 + local fields = { + {"Имя:", data and data.name or "Не указано"}, + {"Фракция:", data and data.faction or "Не указано"}, + {"Подразделение:", data and data.subdivision or "Не указано"}, + {"Специализация:", data and data.specialization or "Не указано"}, + {"Звание:", data and data.rank or "Не указано"}, + {"Розыск:", (data and data.wanted_fsb and data.wanted_sbu) and "В РОЗЫСКЕ ФСБ И СБУ" or (data and data.wanted_fsb) and "В РОЗЫСКЕ ФСБ" or (data and data.wanted_sbu) and "В РОЗЫСКЕ СБУ" or "Чист"} + } + + for _, field in ipairs(fields) do + local label = vgui.Create("DLabel", infoPanel) + label:SetPos(0, yPos) + label:SetFont("DermaDefaultBold") + label:SetText(field[1]) + label:SetTextColor(Color(50, 50, 50)) + label:SizeToContents() + + local value = vgui.Create("DLabel", infoPanel) + value:SetPos(150, yPos) + value:SetFont("DermaDefault") + value:SetText(field[2]) + + -- Make the "Wanted" value red if wanted + if field[1] == "Розыск:" and data and (data.wanted_fsb or data.wanted_sbu) then + value:SetTextColor(Color(200, 0, 0)) + value:SetFont("DermaDefaultBold") + else + value:SetTextColor(Color(0, 0, 0)) + end + + value:SizeToContents() + + yPos = yPos + 35 + end + + -- Visual Wanted Stamp + if data and (data.wanted_fsb or data.wanted_sbu) then + local stamp = vgui.Create("DPanel", frame) + stamp:SetSize(180, 60) + stamp:SetPos(300, 280) + stamp.Paint = function(s, w, h) + local x, y = s:LocalToScreen(0, 0) + local matrix = Matrix() + matrix:Translate(Vector(x + w / 2, y + h / 2, 0)) + matrix:Rotate(Angle(0, 15, 0)) + matrix:Translate(Vector(-(x + w / 2), -(y + h / 2), 0)) + + cam.PushModelMatrix(matrix) + surface.SetDrawColor(200, 0, 0, 150) + surface.DrawOutlinedRect(x, y, w, h, 4) + draw.SimpleText("В РОЗЫСКЕ", "DermaLarge", x + w / 2, y + h / 2, Color(200, 0, 0, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.PopModelMatrix() + end + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(175, 350) + closeBtn:SetSize(150, 30) + closeBtn:SetText("Закрыть") + closeBtn.DoClick = function() + frame:Close() + end + + frame.OnRemove = function() + net.Start("ixMilitaryID_StopView") + net.SendToServer() + end + + self.documentFrame = frame +end + + diff --git a/garrysmod/gamemodes/militaryrp/plugins/military_id/entities/entities/ix_military_id_terminal.lua b/garrysmod/gamemodes/militaryrp/plugins/military_id/entities/entities/ix_military_id_terminal.lua new file mode 100644 index 0000000..9c644a7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/military_id/entities/entities/ix_military_id_terminal.lua @@ -0,0 +1,76 @@ +ENT.Type = "anim" +ENT.PrintName = "Военкомат" +ENT.Category = "[FT] Система документов" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:SetupDataTables() +end + +if SERVER then + function ENT:Initialize() + self:SetModel("models/props_lab/monitor02.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + end + + function ENT:Use(activator, caller) + if not IsValid(activator) or not activator:IsPlayer() then + return + end + + local character = activator:GetCharacter() + if not character then + return + end + + -- Проверяем, есть ли уже военный билет + local inventory = character:GetInventory() + if inventory then + for _, item in pairs(inventory:GetItems()) do + if item.uniqueID == "military_id" then + activator:Notify("У вас уже есть военный билет!") + return + end + end + end + + -- Открываем опросник + net.Start("ixMilitaryID_OpenQuiz") + net.Send(activator) + end +else + function ENT:Draw() + self:DrawModel() + + local plugin = ix.plugin.Get("military_id") + if not plugin then return end + + local pos = self:GetPos() + self:GetUp() * 30 + local ang = self:GetAngles() + + ang:RotateAroundAxis(ang:Forward(), 90) + ang:RotateAroundAxis(ang:Right(), -90) + + cam.Start3D2D(pos, ang, 0.1) + draw.SimpleTextOutlined( + plugin.terminalText or "ВОЕНКОМАТ", + "DermaLarge", + 0, 0, + Color(255, 255, 255), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + 2, + Color(0, 0, 0) + ) + cam.End3D2D() + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/military_id/items/sh_military_id.lua b/garrysmod/gamemodes/militaryrp/plugins/military_id/items/sh_military_id.lua new file mode 100644 index 0000000..4672ac1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/military_id/items/sh_military_id.lua @@ -0,0 +1,129 @@ +ITEM.name = "Военный билет" +ITEM.description = "Документ, удостоверяющий военную службу." +ITEM.model = "models/props_c17/paper01.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.category = "Документы" + +-- Функция предъявления документа +ITEM.functions.Show = { + name = "Предъявить", + tip = "Показать военный билет игроку перед собой", + icon = "icon16/eye.png", + OnRun = function(item) + if CLIENT then return false end + + local client = item.player + + -- Кулдаун + if (client.ixNextIDShow or 0) > CurTime() then + client:Notify("Подождите " .. math.ceil(client.ixNextIDShow - CurTime()) .. " сек. перед повторным показом") + return false + end + + -- Трассировка взгляда от лица игрока (на сервере) + local trace = client:GetEyeTrace() + local target = trace.Entity + + -- Проверяем что смотрим на игрока и он близко + if IsValid(target) and target:IsPlayer() and target ~= client and target:GetPos():Distance(client:GetPos()) <= 200 then + -- Проверка, не смотрит ли уже цель чужой военник + if target.ixIsViewingID then + client:Notify("Этот человек уже изучает другой документ") + return false + end + + local character = client:GetCharacter() + if not character then return false end + + local factionTable = ix.faction.Get(character:GetFaction()) + local podrData = factionTable and factionTable.Podr and factionTable.Podr[character:GetPodr()] + local specData = factionTable and factionTable.Spec and factionTable.Spec[character:GetSpec()] + + local data = { + name = character:GetName(), + faction = factionTable and factionTable.name or "Неизвестно", + subdivision = podrData and podrData.name or "Неизвестно", + specialization = specData and specData.name or "Неизвестно", + rank = client.GetRankName and client:GetRankName() or "Неизвестно", + wanted = character:GetData("wanted", false), + wanted_fsb = character:GetData("wanted_fsb", false), + wanted_sbu = character:GetData("wanted_sbu", false) + } + + -- Устанавливаем статус просмотра для цели + target.ixIsViewingID = true + -- Ставим кулдаун отправителю + client.ixNextIDShow = CurTime() + 5 + + -- Отправляем данные документа целевому игроку + net.Start("ixMilitaryID_ViewDocument") + net.WriteTable(data) + net.Send(target) + + client:Notify("Вы показали военный билет игроку " .. target:GetName()) + else + client:Notify("Посмотрите на игрока поблизости") + end + + return false + end, + OnCanRun = function(item) + return !IsValid(item.entity) + end +} + +-- Функция просмотра своего документа +ITEM.functions.View = { + name = "Просмотреть", + tip = "Посмотреть содержимое военного билета", + icon = "icon16/page_white_text.png", + OnRun = function(item) + local client = item.player + + if SERVER then + local character = client:GetCharacter() + if not character then return false end + + local factionTable = ix.faction.Get(character:GetFaction()) + local podrData = factionTable and factionTable.Podr and factionTable.Podr[character:GetPodr()] + local specData = factionTable and factionTable.Spec and factionTable.Spec[character:GetSpec()] + + local data = { + name = character:GetName(), + faction = factionTable and factionTable.name or "Неизвестно", + subdivision = podrData and podrData.name or "Неизвестно", + specialization = specData and specData.name or "Неизвестно", + rank = client.GetRankName and client:GetRankName() or "Неизвестно", + wanted = character:GetData("wanted", false), + wanted_fsb = character:GetData("wanted_fsb", false), + wanted_sbu = character:GetData("wanted_sbu", false) + } + + net.Start("ixMilitaryID_ViewDocument") + net.WriteTable(data) + net.Send(client) + end + + return false + end, + OnCanRun = function(item) + return !IsValid(item.entity) + end +} + + + +-- Запрет на выброс документа +ITEM.noDrop = true + +-- Кастомное отображение в инвентаре +if CLIENT then + function ITEM:PaintOver(item, w, h) + -- Отображаем маленькую иконку документа + surface.SetDrawColor(200, 200, 150, 255) + surface.DrawRect(w - 16, h - 16, 12, 12) + + draw.SimpleText("ID", "DermaDefaultBold", w - 10, h - 10, Color(100, 50, 0), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/military_id/sh_config.lua b/garrysmod/gamemodes/militaryrp/plugins/military_id/sh_config.lua new file mode 100644 index 0000000..d51e92d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/military_id/sh_config.lua @@ -0,0 +1,55 @@ +PLUGIN.quizQuestions = { + { + question = "Какова ваша основная задача в подразделении?", + answers = { + "Выполнение боевых задач", + "Охрана объектов", + "Медицинская поддержка", + "Техническое обслуживание", + "Разведка и наблюдение" + }, + correctAnswer = 1 -- Индекс правильного ответа (необязательно, можно убрать проверку) + }, + { + question = "Вы готовы соблюдать воинскую дисциплину?", + answers = { + "Да, готов", + "Нет, не готов" + }, + correctAnswer = 1 + }, + { + question = "Ваш опыт службы:", + answers = { + "Новобранец", + "Опытный боец", + "Ветеран", + "Офицер запаса", + "Без опыта" + } + }, + { + question = "Почему вы хотите вступить в ряды вооруженных сил?", + answers = { + "Защита Родины", + "Карьерный рост", + "Семейная традиция", + "Долг перед страной" + } + }, + { + question = "Готовы ли вы следовать приказам командования?", + answers = { + "Да, безусловно", + "Да, но с оговорками", + "Нет" + }, + correctAnswer = 1 + } +} + +-- Минимальный балл для прохождения (если используется проверка) +PLUGIN.minimumScore = 3 + +-- Текст на терминале +PLUGIN.terminalText = "ВОЕНКОМАТ\nНажмите E для прохождения опроса" diff --git a/garrysmod/gamemodes/militaryrp/plugins/military_id/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/military_id/sh_plugin.lua new file mode 100644 index 0000000..914a921 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/military_id/sh_plugin.lua @@ -0,0 +1,17 @@ +PLUGIN.name = "Military ID" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "Система военных билетов с опросом" + +ix.util.Include("sh_config.lua") +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") + +if (SERVER) then + util.AddNetworkString("ixMilitaryID_ShowToPlayer") + util.AddNetworkString("ixMilitaryID_ViewDocument") + util.AddNetworkString("ixMilitaryID_OpenQuiz") + util.AddNetworkString("ixMilitaryID_SubmitQuiz") + util.AddNetworkString("ixMilitaryID_EditDocument") + util.AddNetworkString("ixMilitaryID_SaveEdit") + util.AddNetworkString("ixMilitaryID_StopView") +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/military_id/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/military_id/sv_plugin.lua new file mode 100644 index 0000000..043bc56 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/military_id/sv_plugin.lua @@ -0,0 +1,263 @@ +local PLUGIN = PLUGIN + +-- Обработка отправки опроса +net.Receive("ixMilitaryID_SubmitQuiz", function(len, client) + local answers = net.ReadTable() + local character = client:GetCharacter() + + if not character then return end + + -- Проверка правильности ответов (опционально) + local score = 0 + for i, answer in ipairs(answers) do + local question = PLUGIN.quizQuestions[i] + if question.correctAnswer and answer == question.correctAnswer then + score = score + 1 + end + end + + -- Если есть минимальный балл + if PLUGIN.minimumScore and score < PLUGIN.minimumScore then + client:Notify("Вы не прошли опрос. Попробуйте снова.") + return + end + + -- Создание военного билета + local inventory = character:GetInventory() + if not inventory or isnumber(inventory) then + client:Notify("Инвентарь еще не загрузился, подождите немного...") + return + end + + -- Проверяем, есть ли уже военный билет + for _, item in pairs(inventory:GetItems()) do + if item.uniqueID == "military_id" then + client:Notify("У вас уже есть военный билет!") + return + end + end + + -- Выдаем пустой военный билет (данные заполнятся при первом просмотре) + inventory:Add("military_id") + client:Notify("Вы получили военный билет!") +end) + +-- Показать документ другому игроку +net.Receive("ixMilitaryID_ShowToPlayer", function(len, client) + local targetIndex = net.ReadUInt(16) + local itemID = net.ReadUInt(32) + print("Received ShowToPlayer request: targetIndex=" .. targetIndex .. ", itemID=" .. itemID) + local target = Entity(targetIndex) + if not IsValid(target) or not target:IsPlayer() or target:GetPos():Distance(client:GetPos()) > 200 then + client:Notify("Игрок слишком далеко!") + return + end + + if (client.ixNextIDShow or 0) > CurTime() then + client:Notify("Подождите " .. math.ceil(client.ixNextIDShow - CurTime()) .. " сек. перед повторным показом") + return + end + + if target.ixIsViewingID then + client:Notify("Этот человек уже изучает другой документ") + return + end + + local item = ix.item.instances[itemID] + if not item or item.player ~= client then + return + end + + -- Берем актуальные данные персонажа + local character = client:GetCharacter() + if not character then return end + + local factionTable = ix.faction.Get(character:GetFaction()) + local podrData = factionTable and factionTable.Podr and factionTable.Podr[character:GetPodr()] + local specData = factionTable and factionTable.Spec and factionTable.Spec[character:GetSpec()] + + local data = { + name = character:GetName(), + faction = factionTable and factionTable.name or "Неизвестно", + subdivision = podrData and podrData.name or "Неизвестно", + specialization = specData and specData.name or "Неизвестно", + rank = client.GetRankName and client:GetRankName() or "Неизвестно", + wanted = character:GetData("wanted", false) + } + + + -- Устанавливаем статус просмотра для цели + target.ixIsViewingID = true + -- Ставим кулдаун отправителю + client.ixNextIDShow = CurTime() + 5 + + -- Отправляем данные документа целевому игроку + net.Start("ixMilitaryID_ViewDocument") + net.WriteTable(data) + net.Send(target) + + client:Notify("Вы предъявили военный билет игроку " .. target:Name()) + target:Notify(client:Name() .. " предъявил вам военный билет") +end) + +-- Сохранение отредактированного документа +net.Receive("ixMilitaryID_SaveEdit", function(len, client) + if not client:IsAdmin() then + client:Notify("У вас нет прав для редактирования!") + return + end + + local itemID = net.ReadUInt(32) + local newData = net.ReadTable() + + local item = ix.item.instances[itemID] + if not item or item.uniqueID ~= "military_id" then + return + end + + -- Обновляем данные + for key, value in pairs(newData) do + item:SetData(key, value) + end + + client:Notify("Военный билет успешно отредактирован!") + + -- Если владелец онлайн, уведомляем его + if IsValid(item.player) and item.player ~= client then + item.player:Notify("Ваш военный билет был отредактирован администратором") + end +end) + +-- Сброс состояния просмотра документа +net.Receive("ixMilitaryID_StopView", function(len, client) + client.ixIsViewingID = nil +end) + +function PLUGIN:PlayerDisconnected(client) + client.ixIsViewingID = nil +end + +function PLUGIN:OnCharacterLoaded(character) + local ply = character:GetPlayer() + if IsValid(ply) then + ply:SetNWBool("ix_wanted", character:GetData("wanted", false)) + end +end + +-- Консольная команда для предъявления военного билета другому игроку +concommand.Add("show_military_id_to_player", function(ply) + local character = ply:GetCharacter() + if not character then + ply:Notify("У вас нет активного персонажа!") + return + end + + -- Проверяем наличие военного билета в инвентаре + local inventory = character:GetInventory() + local hasItem = false + if inventory then + for _, item in pairs(inventory:GetItems()) do + if item.uniqueID == "military_id" then + hasItem = true + break + end + end + end + + if not hasItem then + ply:Notify("У вас нет военного билета! Вам нужно его получить.") + return + end + + -- Трассировка взгляда от лица игрока + local trace = ply:GetEyeTrace() + local target = trace.Entity + + -- Проверяем что смотрим на игрока и он близко + if IsValid(target) and target:IsPlayer() and target ~= ply and target:GetPos():Distance(ply:GetPos()) <= 200 then + if (ply.ixNextIDShow or 0) > CurTime() then + ply:Notify("Подождите " .. math.ceil(ply.ixNextIDShow - CurTime()) .. " сек. перед повторным показом") + return + end + + if target.ixIsViewingID then + ply:Notify("Этот человек уже изучает другой документ") + return + end + local factionTable = ix.faction.Get(character:GetFaction()) + local podrData = factionTable and factionTable.Podr and factionTable.Podr[character:GetPodr()] + local specData = factionTable and factionTable.Spec and factionTable.Spec[character:GetSpec()] + + local data = { + name = character:GetName(), + faction = factionTable and factionTable.name or "Неизвестно", + subdivision = podrData and podrData.name or "Неизвестно", + specialization = specData and specData.name or "Неизвестно", + rank = ply.GetRankName and ply:GetRankName() or "Неизвестно", + wanted = character:GetData("wanted", false), + wanted_fsb = character:GetData("wanted_fsb", false), + wanted_sbu = character:GetData("wanted_sbu", false) + } + + + -- Устанавливаем статус просмотра для цели + target.ixIsViewingID = true + -- Ставим кулдаун отправителю + ply.ixNextIDShow = CurTime() + 5 + + -- Отправляем данные документа целевому игроку + net.Start("ixMilitaryID_ViewDocument") + net.WriteTable(data) + net.Send(target) + + ply:Notify("Вы показали военный билет игроку " .. target:GetName()) + else + ply:Notify("Посмотрите на игрока поблизости") + end +end) + +-- Консольная команда для показа военного билета себе +concommand.Add("show_military_id", function(ply) + local character = ply:GetCharacter() + if not character then + ply:Notify("У вас нет активного персонажа!") + return + end + + -- Проверяем наличие военного билета в инвентаре + local inventory = character:GetInventory() + local hasItem = false + if inventory then + for _, item in pairs(inventory:GetItems()) do + if item.uniqueID == "military_id" then + hasItem = true + break + end + end + end + + if not hasItem then + ply:Notify("У вас нет военного билета! Вам нужно его получить.") + return + end + + local factionTable = ix.faction.Get(character:GetFaction()) + local podrData = factionTable and factionTable.Podr and factionTable.Podr[character:GetPodr()] + local specData = factionTable and factionTable.Spec and factionTable.Spec[character:GetSpec()] + + local data = { + name = character:GetName(), + faction = factionTable and factionTable.name or "Неизвестно", + subdivision = podrData and podrData.name or "Неизвестно", + specialization = specData and specData.name or "Неизвестно", + rank = ply.GetRankName and ply:GetRankName() or "Неизвестно", + wanted = character:GetData("wanted", false), + wanted_fsb = character:GetData("wanted_fsb", false), + wanted_sbu = character:GetData("wanted_sbu", false) + } + + + net.Start("ixMilitaryID_ViewDocument") + net.WriteTable(data) + net.Send(ply) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/nlr/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/nlr/cl_plugin.lua new file mode 100644 index 0000000..ba8119b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/nlr/cl_plugin.lua @@ -0,0 +1,119 @@ +local PLUGIN = PLUGIN + +if CLIENT then + -- Данные NLR + local nlrEndTime = 0 + local inWarning = false + local warningEndTime = 0 + + -- Шрифты + surface.CreateFont("NLRTimer", { + font = "Exo 2", + size = 32, + weight = 700 + }) + + surface.CreateFont("NLRWarning", { + font = "Exo 2", + size = 48, + weight = 700 + }) + + -- Получение сетевых сообщений + net.Receive("ixNLRStart", function() + nlrEndTime = net.ReadFloat() + inWarning = false + warningEndTime = 0 + end) + + net.Receive("ixNLREnd", function() + nlrEndTime = 0 + inWarning = false + warningEndTime = 0 + end) + + net.Receive("ixNLRWarning", function() + inWarning = net.ReadBool() + warningEndTime = net.ReadFloat() + end) + + -- Отрисовка эффектов + function PLUGIN:HUDPaint() + local ply = LocalPlayer() + if not IsValid(ply) or not ply:Alive() then return end + + local scrW, scrH = ScrW(), ScrH() + local currentTime = CurTime() + + -- Если NLR активен + if nlrEndTime > 0 and currentTime < nlrEndTime then + local timeLeft = math.ceil(nlrEndTime - currentTime) + local minutes = math.floor(timeLeft / 60) + local seconds = timeLeft % 60 + + -- Таймер NLR + local timerText = string.format("NLR: %02d:%02d", minutes, seconds) + draw.SimpleText(timerText, "NLRTimer", scrW / 2, 50, Color(255, 200, 0), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + + -- Если игрок вне зоны (предупреждение) + if inWarning and warningEndTime > 0 then + local warningTimeLeft = math.ceil(warningEndTime - currentTime) + + if warningTimeLeft > 0 then + -- Красный экран + local alpha = 100 + math.sin(currentTime * 5) * 50 + surface.SetDrawColor(255, 0, 0, alpha) + surface.DrawRect(0, 0, scrW, scrH) + + -- Рамка + surface.SetDrawColor(255, 0, 0, 255) + surface.DrawOutlinedRect(10, 10, scrW - 20, scrH - 20, 5) + + -- Предупреждение + local warningText = "ВЕРНИТЕСЬ НА БАЗУ!" + draw.SimpleText(warningText, "NLRWarning", scrW / 2, scrH / 2 - 50, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Таймер предупреждения + local warningTimerText = string.format("Осталось: %d секунд", warningTimeLeft) + draw.SimpleText(warningTimerText, "NLRTimer", scrW / 2, scrH / 2 + 20, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + -- Отображение информации о зонах (для дебага) + function PLUGIN:DrawNLRZones() + if not GetConVar("developer"):GetBool() then return end + + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local char = ply:GetCharacter() + if not char then return end + + local faction = char:GetFaction() + local zones = self.config.zones[faction] + + if not zones then return end + + for _, zone in ipairs(zones) do + -- Рисуем границы зоны (упрощенно) + local corners = { + Vector(zone.min.x, zone.min.y, zone.min.z), + Vector(zone.max.x, zone.min.y, zone.min.z), + Vector(zone.max.x, zone.max.y, zone.min.z), + Vector(zone.min.x, zone.max.y, zone.min.z), + Vector(zone.min.x, zone.min.y, zone.max.z), + Vector(zone.max.x, zone.min.y, zone.max.z), + Vector(zone.max.x, zone.max.y, zone.max.z), + Vector(zone.min.x, zone.max.y, zone.max.z), + } + + for i = 1, 4 do + debugoverlay.Line(corners[i], corners[i % 4 + 1], 0.1, Color(0, 255, 0), true) + debugoverlay.Line(corners[i + 4], corners[(i % 4 + 1) + 4], 0.1, Color(0, 255, 0), true) + debugoverlay.Line(corners[i], corners[i + 4], 0.1, Color(0, 255, 0), true) + end + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/nlr/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/nlr/sh_plugin.lua new file mode 100644 index 0000000..765c84c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/nlr/sh_plugin.lua @@ -0,0 +1,112 @@ +PLUGIN.name = "NLR System" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "New Life Rule система для Military RP" + +-- Конфигурация зон для каждой фракции +PLUGIN.config = { + -- Время NLR после смерти (в секундах) + nlrTime = 300, -- 5 минут + + -- Время предупреждения перед смертью (в секундах) + warningTime = 15, -- 1 минута + + -- Разрешенные зоны для каждой фракции + zones = { + -- Россия + [FACTION_RUSSIAN] = { + -- Основная база + { + min = Vector(16209.329102, 16203.910156, -1046.129639), + max = Vector(10388.949219, 9995.708984, 2110.358643), + name = "Российская база" + }, + -- База Русичей (добавлено чтобы NLR не убивал при спавне) + { + min = Vector(7412.163574, 14589.896484, -287.295044), + max = Vector(6238.277832, 13538.660156, 769.099426), + name = "База Русичей" + }, + -- База Украины (теперь РФ может быть тут без NLR) + { + min = Vector(-16190.572266, -12279.253906, 1879.668945), + max = Vector(-12284.045898, -4947.237305, -458.217285), + name = "Украинская база" + } + }, + + -- Украина + [FACTION_UKRAINE] = { + -- Основная база + { + min = Vector(-16190.572266, -12279.253906, 1879.668945), + max = Vector(-12284.045898, -4947.237305, -458.217285), + name = "Украинская база" + }, + -- База России (теперь УК может быть тут без NLR) + { + min = Vector(16209.329102, 16203.910156, -1046.129639), + max = Vector(10388.949219, 9995.708984, 2110.358643), + name = "Российская база" + }, + -- База Русичей + { + min = Vector(7412.163574, 14589.896484, -287.295044), + max = Vector(6238.277832, 13538.660156, 769.099426), + name = "База Русичей" + } + } + } + } + +-- Проверка, находится ли позиция в зоне +function PLUGIN:IsInZone(pos, zone) + -- Нормализуем координаты зоны (min должен быть меньше max) + local minX = math.min(zone.min.x, zone.max.x) + local maxX = math.max(zone.min.x, zone.max.x) + local minY = math.min(zone.min.y, zone.max.y) + local maxY = math.max(zone.min.y, zone.max.y) + local minZ = math.min(zone.min.z, zone.max.z) + local maxZ = math.max(zone.min.z, zone.max.z) + + return pos.x >= minX and pos.x <= maxX and + pos.y >= minY and pos.y <= maxY and + pos.z >= minZ and pos.z <= maxZ +end + +-- Проверка, находится ли игрок в разрешенной зоне +function PLUGIN:IsPlayerInAllowedZone(ply) + local char = ply:GetCharacter() + if not char then return false end + + local faction = char:GetFaction() + local zones = self.config.zones[faction] + + if not zones then return false end + + local pos = ply:GetPos() + for _, zone in ipairs(zones) do + if self:IsInZone(pos, zone) then + return true, zone.name + end + end + + return false +end + +-- Локализация +if CLIENT then + ix.lang.AddTable("russian", { + nlrResetSuccess = "NLR игрока %s успешно сброшен", + nlrNotActive = "У игрока %s нет активного NLR", + cmdResetNLR = "Сбросить NLR игрока" + }) + + ix.lang.AddTable("english", { + nlrResetSuccess = "NLR of %s has been reset", + nlrNotActive = "Player %s has no active NLR", + cmdResetNLR = "Reset player's NLR" + }) +end + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/nlr/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/nlr/sv_plugin.lua new file mode 100644 index 0000000..885b51f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/nlr/sv_plugin.lua @@ -0,0 +1,221 @@ +local PLUGIN = PLUGIN + +if SERVER then + util.AddNetworkString("ixNLRStart") + util.AddNetworkString("ixNLREnd") + util.AddNetworkString("ixNLRWarning") + + -- Таблица для хранения данных NLR игроков + PLUGIN.nlrData = PLUGIN.nlrData or {} + + -- Начало NLR при смерти + function PLUGIN:PlayerDeath(ply, inflictor, attacker) + if not IsValid(ply) then return end + + local char = ply:GetCharacter() + if not char then return end + + -- Устанавливаем время окончания NLR + local nlrEndTime = CurTime() + self.config.nlrTime + + self.nlrData[ply:SteamID()] = { + endTime = nlrEndTime, + inWarning = false, + warningStartTime = nil + } + + -- Отправляем клиенту информацию о начале NLR + net.Start("ixNLRStart") + net.WriteFloat(nlrEndTime) + net.Send(ply) + + ply:Notify("Вы мертвы. NLR активен на " .. math.floor(self.config.nlrTime / 60) .. " минут") + end + + function PLUGIN:Think() + for _, ply in ipairs(player.GetAll()) do + if not IsValid(ply) or not ply:Alive() then continue end + + if ply:IsAdminMode() then + local steamID = ply:SteamID() + local nlrData = self.nlrData[steamID] + + if nlrData and nlrData.inWarning then + nlrData.inWarning = false + nlrData.warningStartTime = nil + + net.Start("ixNLRWarning") + net.WriteBool(false) + net.WriteFloat(0) + net.Send(ply) + end + + continue + end + + local steamID = ply:SteamID() + local nlrData = self.nlrData[steamID] + + if nlrData and CurTime() < nlrData.endTime then + local inZone, zoneName = self:IsPlayerInAllowedZone(ply) + + if not inZone and not nlrData.inWarning then + nlrData.inWarning = true + nlrData.warningStartTime = CurTime() + + net.Start("ixNLRWarning") + net.WriteBool(true) + net.WriteFloat(CurTime() + self.config.warningTime) + net.Send(ply) + + --ply:Notify("Вернитесь на базу в течении " .. math.floor(self.config.warningTime / 60) .. " минуты или вы умрете!") + + elseif inZone and nlrData.inWarning then + -- Игрок вернулся в зону + nlrData.inWarning = false + nlrData.warningStartTime = nil + + net.Start("ixNLRWarning") + net.WriteBool(false) + net.WriteFloat(0) + net.Send(ply) + + ply:Notify("Вы вернулись на базу. Предупреждение снято.") + + elseif nlrData.inWarning and nlrData.warningStartTime then + -- Проверяем, истекло ли время предупреждения + if CurTime() - nlrData.warningStartTime >= self.config.warningTime then + ply:Kill() + ply:Notify("Вы не вернулись на базу вовремя") + + nlrData.inWarning = false + nlrData.warningStartTime = nil + end + end + elseif nlrData and CurTime() >= nlrData.endTime then + -- NLR закончился + self.nlrData[steamID] = nil + + net.Start("ixNLREnd") + net.Send(ply) + + ply:Notify("NLR закончился. Вы можете свободно перемещаться") + end + end + end + + -- Очистка данных при выходе игрока + function PLUGIN:PlayerDisconnected(ply) + if ply:SteamID() then + self.nlrData[ply:SteamID()] = nil + end + end + + -- Очистка данных при смене персонажа + function PLUGIN:OnCharacterDeleted(ply, char) + if ply:SteamID() then + self.nlrData[ply:SteamID()] = nil + + net.Start("ixNLREnd") + net.Send(ply) + end + end + + -- Команда для проверки зоны + concommand.Add("nlr_check", function(ply) + if not IsValid(ply) then return end + + local char = ply:GetCharacter() + if not char then + ply:ChatPrint("Нет персонажа") + return + end + + local faction = char:GetFaction() + local zones = PLUGIN.config.zones[faction] + + if not zones then + ply:ChatPrint("Для вашей фракции (" .. faction .. ") не настроены зоны") + return + end + + local pos = ply:GetPos() + ply:ChatPrint("Ваша позиция: " .. tostring(pos)) + ply:ChatPrint("Фракция: " .. tostring(faction)) + ply:ChatPrint("Количество зон: " .. #zones) + + for i, zone in ipairs(zones) do + ply:ChatPrint("Зона " .. i .. " (" .. zone.name .. "):") + ply:ChatPrint(" Min: " .. tostring(zone.min)) + ply:ChatPrint(" Max: " .. tostring(zone.max)) + + local inZone = PLUGIN:IsInZone(pos, zone) + ply:ChatPrint(" В зоне: " .. tostring(inZone)) + end + + local inAnyZone, zoneName = PLUGIN:IsPlayerInAllowedZone(ply) + ply:ChatPrint("Итог: " .. (inAnyZone and ("В зоне: " .. zoneName) or "ВНЕ ЗОНЫ")) + end) + + -- Команда Helix для сброса NLR + ix.command.Add("ResetNLR", { + description = "Сбросить NLR у игрока", + adminOnly = true, + arguments = { + ix.type.player + }, + OnCheckAccess = function(self, client) + return AdminPrivs[client:GetUserGroup()] + end, + OnRun = function(self, client, target) + if not IsValid(target) then + return "@invalidTarget" + end + + local steamID = target:SteamID() + local hadNLR = PLUGIN.nlrData[steamID] ~= nil + + if hadNLR then + -- Очищаем данные NLR + PLUGIN.nlrData[steamID] = nil + + -- Отправляем клиенту информацию об окончании NLR + net.Start("ixNLREnd") + net.Send(target) + + -- Уведомляем игрока + target:Notify("Ваш NLR был сброшен администратором") + + -- Логируем в serverlogs + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if serverlogsPlugin then + local message = string.format("%s сбросил NLR игроку %s", + client:Nick(), target:Nick()) + serverlogsPlugin:AddLog("ADMIN_COMMAND", message, client, { + targetSteamID = steamID, + targetName = target:Nick() + }) + end + + return "@nlrResetSuccess", target:Name() + else + return "@nlrNotActive", target:Name() + end + end + }) + + -- Сброс NLR при оживлении медиком + hook.Add("onPlayerRevived", "ixNLRResetOnRevival", function(ply) + if IsValid(ply) then + local steamID = ply:SteamID() + if PLUGIN.nlrData[steamID] then + PLUGIN.nlrData[steamID] = nil + + net.Start("ixNLREnd") + net.Send(ply) + + ply:Notify("Ваш NLR был сброшен, так как вас реанимировали") + end + end + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/parachute/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/parachute/cl_plugin.lua new file mode 100644 index 0000000..eeb36c8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/parachute/cl_plugin.lua @@ -0,0 +1,62 @@ +local PLUGIN = PLUGIN + +-- Клиентская часть HUD +net.Receive("ParachuteState", function() + local state = net.ReadBool() + LocalPlayer().Parachuting = state + + if state then + -- Запуск звука полёта + if LoopingSound then + LoopingSound:Stop() + end + + LoopingSound = CreateSound(LocalPlayer(), "v92/bf2/vehicles/air/parachute/parachute_ride_loop.wav") + LoopingSound:PlayEx(ix.config.Get("parachuteVolume", 0.7), 100) + else + -- Остановка звука + if LoopingSound then + LoopingSound:Stop() + end + end +end) + +function PLUGIN:HUDPaint() + local client = LocalPlayer() + if not client:Alive() or not IsValid(client) then return end + if not IsValid(client:GetActiveWeapon()) then return end + if client:GetActiveWeapon():GetClass() ~= "parachute_swep" then return end + + local reload_time = ix.config.Get("parachuteReloadTime", 20) + local width = ScrW() * 0.15 + local height = ScrH() * 0.03 + local progression = (reload_time - math.abs(math.min(CurTime() - client:GetNW2Int("r_parachute"), 0))) / reload_time + + -- Фон прогресс-бара + draw.RoundedBox(1, (ScrW() / 2) - (width / 2), ScrH() / 1.08, width, height, Color(59, 59, 59, 255)) + surface.SetDrawColor(89, 89, 89, 128) + surface.DrawOutlinedRect((ScrW() / 2) - (width / 2) + 1, (ScrH() / 1.08) + 2, width - 2, height - 2, 1) + + -- Полоса прогресса + draw.RoundedBox(1, (ScrW() / 2) - (width / 2) + 3, ScrH() / 1.08 + 3, progression * (width - 6), height - 6, Color(176, 151, 28, 255)) + + -- Текст подсказки + if progression >= 1 then + draw.SimpleText("ЛКМ - Раскрыть парашют", "DermaDefault", ScrW() / 2, ScrH() / 1.08 - 20, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText("Перезарядка...", "DermaDefault", ScrW() / 2, ScrH() / 1.08 - 20, Color(255, 100, 100, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Если парашют активен + if client.Parachuting then + draw.SimpleText("Ctrl + Shift - Отцепить парашют", "DermaDefault", ScrW() / 2, ScrH() / 1.08 - 40, Color(255, 200, 100, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("E - Замедлить спуск", "DermaDefault", ScrW() / 2, ScrH() / 1.08 - 60, Color(150, 200, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end + +-- Остановка звука при выходе +function PLUGIN:ShutDown() + if LoopingSound then + LoopingSound:Stop() + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_abandon.lua b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_abandon.lua new file mode 100644 index 0000000..cf63d65 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_abandon.lua @@ -0,0 +1,40 @@ + +AddCSLuaFile( ) + +ENT.Type = "anim" +ENT.PrintName = "Ditched BF2 ZParachute" +ENT.Author = "Magenta/V92" + +ENT.Spawnable = false +ENT.AdminOnly = true + +function ENT:Initialize( ) + + self:SetModel( Model( "models/jessev92/codmw2/parachute_ground_skins.mdl" ) ) + self:SetMoveType( MOVETYPE_FLYGRAVITY ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetSkin( 1 ) + self:DrawShadow( true ) + self:SetCollisionBounds( Vector( -1 , -1 , -1 ) , Vector( 1 , 1 , 1 ) ) + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self:SetNetworkedString( "Owner" , "World" ) + self:EmitSound( "VNT_ParachuteZ_DetachClip" ) + self.RemoveMe = CurTime( ) + 10 + +end + +function ENT:Think( ) + + if not SERVER then return false end + + if self.RemoveMe < CurTime( ) then + + if SERVER then + + SafeRemoveEntity( self ) + + end + + end + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_active.lua b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_active.lua new file mode 100644 index 0000000..f2fe700 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_active.lua @@ -0,0 +1,65 @@ + +AddCSLuaFile( ) + +ENT.Type = "anim" +ENT.PrintName = "Active BF2 ZParachute" +ENT.Author = "Magenta/V92" + +ENT.Spawnable = false +ENT.AdminOnly = true + +function ENT:Initialize( ) + + self:SetModel( Model( "models/jessev92/bf2/parachute.mdl" ) ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_BBOX ) + self:DrawShadow( true ) + self:SetCollisionBounds( Vector( -1 , -1 , -1 ) , Vector( 1 , 1 , 1 ) ) + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self:SetNetworkedString( "Owner" , "World" ) + self:EmitSound( "VNT_ParachuteZ_BF2_Deploy" ) + +end + +function ENT:Think( ) + + local owner = self:GetOwner( ) + + if owner:IsValid( ) then + + if CLIENT then + + self:SetRenderOrigin( owner:GetPos( ) + owner:GetUp( ) * 5 + owner:GetForward( ) * -2 ) + + end + + if SERVER then + + self:SetAngles( self:GetOwner( ):GetAngles( ) ) + self:SetPos( self:GetOwner( ):GetPos( ) + self:GetOwner( ):GetUp( ) * 2 + self:GetOwner( ):GetForward( ) * -4 ) + + end + + end + + if owner.Parachuting == false then + + if SERVER and IsValid( self ) then + + SafeRemoveEntity( self ) + + end + + end + +end + +function ENT:OnRemove( ) + + if SERVER and IsValid( self ) then + + SafeRemoveEntity( self ) + + end + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_land.lua b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_land.lua new file mode 100644 index 0000000..f58313e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/entities/v92_zchute_bf2_land.lua @@ -0,0 +1,40 @@ + +AddCSLuaFile( ) + +ENT.Type = "anim" +ENT.PrintName = "Landed BF2 ZParachute" +ENT.Author = "Magenta/V92" + +ENT.Spawnable = false +ENT.AdminOnly = true + +function ENT:Initialize( ) + + self:SetModel( Model( "models/jessev92/codmw2/parachute_ground_skins.mdl" ) ) + self:SetMoveType( MOVETYPE_FLYGRAVITY ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetSkin( 1 ) + self:DrawShadow( true ) + self:SetCollisionBounds( Vector( -1 , -1 , -1 ) , Vector( 1 , 1 , 1 ) ) + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self:SetNetworkedString( "Owner" , "World" ) + self:EmitSound( "VNT_ParachuteZ_DetachClip" ) + self.RemoveMe = CurTime( ) + 10 + +end + +function ENT:Think( ) + + if not SERVER then return false end + + if self.RemoveMe < CurTime( ) then + + if SERVER then + + SafeRemoveEntity( self ) + + end + + end + +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/weapons/parachute_swep.lua b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/weapons/parachute_swep.lua new file mode 100644 index 0000000..a1e2ddd --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/parachute/entities/weapons/parachute_swep.lua @@ -0,0 +1,52 @@ +AddCSLuaFile() + +--SWEP.Base = "weapon_rp_base" + +if CLIENT then + SWEP.PrintName = "Парашют" + SWEP.DrawAmmo = false +end + +SWEP.ViewModel = Model('models/weapons/v_hands.mdl') +SWEP.ViewModelFOV = 62 +SWEP.Category = "Other" +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.HoldType = "normal" +SWEP.ViewModel = "models/weapons/c_arms.mdl" +SWEP.WorldModel = "" + +function SWEP:Initialize() + if not SERVER then return end + self.preventWalkPress = 0 +end + +function SWEP:PrimaryAttack() + if not IsValid(self.Owner) then return end + + local ply = self.Owner + local reloadTime = ix and ix.config.Get("parachuteReloadTime", 20) or 20 + local minVel = ix and ix.config.Get("parachuteMinVelocity", -600) or -600 + + if ply:Alive() and ((ply:GetNW2Int("r_parachute") or 0) <= CurTime()) and ply:IsValid() and not (ply:GetMoveType() == MOVETYPE_NOCLIP or ply:InVehicle() or ply:OnGround() or ply:GetVelocity().z > minVel) then + ply:SetNW2Int("r_parachute", CurTime() + reloadTime) + self.preventWalkPress = CurTime() + reloadTime + end +end + +function SWEP:CanSecondaryAttack() + return false +end + +function SWEP:SecondaryAttack() + +end + +function SWEP:Reload() + return false +end + + + + + diff --git a/garrysmod/gamemodes/militaryrp/plugins/parachute/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/parachute/sh_plugin.lua new file mode 100644 index 0000000..8c21030 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/parachute/sh_plugin.lua @@ -0,0 +1,205 @@ +PLUGIN.name = "Parachute System" +PLUGIN.author = "V92 (Портирован для Helix)" +PLUGIN.description = "Система парашютов в стиле Battlefield 2 для безопасного приземления" + +-- Конфигурация +ix.config.Add("parachuteMode", 0, "Режим доступа к парашюту: -1=Отключен, 0=Все, 1=Админы, 2=Супер-админы", nil, { + data = {min = -1, max = 2}, + category = "Parachute System" +}) + +ix.config.Add("parachuteVolume", 0.7, "Громкость звука полёта на парашюте", nil, { + data = {min = 0.2, max = 1.0, decimals = 1}, + category = "Parachute System" +}) + +ix.config.Add("parachuteSpeedBack", 300, "Скорость при движении назад", nil, { + data = {min = 200, max = 500}, + category = "Parachute System" +}) + +ix.config.Add("parachuteSpeedNormal", 375, "Скорость без управления", nil, { + data = {min = 250, max = 600}, + category = "Parachute System" +}) + +ix.config.Add("parachuteSpeedForward", 450, "Скорость при движении вперёд", nil, { + data = {min = 300, max = 700}, + category = "Parachute System" +}) + +ix.config.Add("parachuteReloadTime", 20, "Время перезарядки парашюта (секунды)", nil, { + data = {min = 5, max = 60}, + category = "Parachute System" +}) + +ix.config.Add("parachuteMinVelocity", -600, "Минимальная скорость падения для активации", nil, { + data = {min = -1000, max = -100}, + category = "Parachute System" +}) + +-- Регистрация звуков +sound.Add({ + ["name"] = "VNT_ParachuteZ_DetachClip", + ["channel"] = CHAN_BODY, + ["volume"] = 1.0, + ["level"] = 50, + ["pitch"] = {90, 110}, + ["sound"] = {"npc/combine_soldier/zipline_clip1.wav", "npc/combine_soldier/zipline_clip2.wav"} +}) + +sound.Add({ + ["name"] = "VNT_ParachuteZ_BF2_Deploy", + ["channel"] = CHAN_BODY, + ["volume"] = 1.0, + ["level"] = 75, + ["pitch"] = {105, 110}, + ["sound"] = {"v92/bf2/vehicles/air/parachute/parachute_deploy.wav"} +}) + +sound.Add({ + ["name"] = "VNT_ParachuteZ_BF2_Idle", + ["channel"] = CHAN_STATIC, + ["volume"] = 1.0, + ["level"] = 75, + ["pitch"] = {100}, + ["sound"] = {"v92/bf2/vehicles/air/parachute/parachute_ride_loop.wav"} +}) + +if SERVER then + -- Загрузка workshop контента + --resource.AddWorkshop("105699464") + + -- Координаты смещения для сущностей парашюта + local ActiveParachute = {10, 0, 100} + local LandedParachute = {-100, 50, 10} + local DitchedParachute = {10, 0, 0} + + function PLUGIN:KeyPress(ply, key) + if not ply:Alive() or not IsValid(ply) then return end + if not IsValid(ply:GetActiveWeapon()) then return end + if ply:GetActiveWeapon():GetClass() ~= "parachute_swep" then return end + + local reloadTime = ix.config.Get("parachuteReloadTime", 20) + local minVel = ix.config.Get("parachuteMinVelocity", -600) + + if ((ply:GetNW2Int("r_parachute") or 0) <= CurTime()) and + (key == IN_ATTACK and ply:IsValid()) and + not (ply:GetMoveType() == MOVETYPE_NOCLIP or ply:InVehicle() or ply:OnGround() or ply.Parachuting == true or ply:GetVelocity().z > minVel) then + + ply.EndParaTime = nil + ply.Parachuting = true + net.Start("ParachuteState") + net.WriteBool(ply.Parachuting) + net.Send(ply) + + ply.FlarePara = 1 + ply:ViewPunch(Angle(35, 0, 0)) + + local Para = ents.Create("v92_zchute_bf2_active") + Para:SetOwner(ply) + Para:SetPos(ply:GetPos() + ply:GetForward() * ActiveParachute[1] + ply:GetRight() * ActiveParachute[2] + ply:GetUp() * ActiveParachute[3]) + Para:SetAngles(ply:GetAngles()) + Para:Spawn() + Para:Activate() + end + end + + function PLUGIN:Think() + local mode = ix.config.Get("parachuteMode", 0) + + -- Если отключено + if mode == -1 then return end + + local speedBack = ix.config.Get("parachuteSpeedBack", 300) + local speedNormal = ix.config.Get("parachuteSpeedNormal", 375) + local speedForward = ix.config.Get("parachuteSpeedForward", 450) + + for k, v in pairs(player.GetAll()) do + -- Проверка прав доступа + if (mode == 1 and not v:IsAdmin()) or (mode == 2 and not v:IsSuperAdmin()) then + if v.Parachuting then + v.Parachuting = false + end + return + end + + if v.Parachuting == true then + -- Приземление + if (v.EndParaTime and CurTime() >= v.EndParaTime and v:OnGround() == true) or + v:WaterLevel() > 0 or + v:GetMoveType() == MOVETYPE_LADDER then + + v.EndParaTime = nil + v.Parachuting = false + net.Start("ParachuteState") + net.WriteBool(v.Parachuting) + net.Send(v) + + v.FlarePara = 1 + v:ViewPunch(Angle(-16, 0, 0)) + + local ParaLand = ents.Create("v92_zchute_bf2_land") + ParaLand:SetOwner(v) + ParaLand:SetPos(v:GetPos() + v:GetForward() * LandedParachute[1] + v:GetRight() * LandedParachute[2] + v:GetUp() * LandedParachute[3]) + ParaLand:SetAngles(v:GetAngles() + Angle(0, 270, 0)) + ParaLand:Spawn() + ParaLand:Activate() + end + + -- Управление скоростью спуска + if v:KeyDown(IN_USE) and v.FlarePara > 0.4 then + v.FlarePara = v.FlarePara - 0.005 + if v.FlarePara < 0.4 then + v.FlarePara = 0.4 + end + end + + -- Управление направлением + if v:KeyDown(IN_FORWARD) then + v:SetLocalVelocity(v:GetForward() * speedForward * v.FlarePara * 1.1 - v:GetUp() * 320 * v.FlarePara) + elseif v:KeyDown(IN_BACK) then + v:SetLocalVelocity(v:GetForward() * speedBack * v.FlarePara * 1.1 - v:GetUp() * 320 * v.FlarePara) + else + v:SetLocalVelocity(v:GetForward() * speedNormal * v.FlarePara * 1.1 - v:GetUp() * 320 * v.FlarePara) + end + + -- Отцепление парашюта (Ctrl + Shift) + if (v:KeyDown(IN_DUCK) and v:KeyDown(IN_WALK) and v.Parachuting == true) or not (v:Alive() or IsValid(v)) then + v.Parachuting = false + net.Start("ParachuteState") + net.WriteBool(v.Parachuting) + net.Send(v) + + v.FlarePara = 1 + v:ViewPunch(Angle(-15, 0, 0)) + + local ParaDitch = ents.Create("v92_zchute_bf2_abandon") + ParaDitch:SetOwner(v) + ParaDitch:SetPos(v:GetPos() + v:GetForward() * DitchedParachute[1] + v:GetRight() * DitchedParachute[2] + v:GetUp() * DitchedParachute[3]) + ParaDitch:SetAngles(v:GetAngles()) + ParaDitch:Spawn() + ParaDitch:Activate() + end + + -- Касание земли + if v:OnGround() and v.Parachuting == true and not v.EndParaTime then + v.EndParaTime = CurTime() + 0.25 + net.Start("ParachuteState") + net.WriteBool(false) + net.Send(v) + v:ViewPunch(Angle(7, 0, 0)) + end + end + end + end + + util.AddNetworkString("ParachuteState") +end + +function PLUGIN:Initialize() + print("[Parachute System] Система парашютов загружена!") + print("[Parachute System] Используйте ЛКМ для раскрытия, Ctrl+Shift для отцепления") +end + +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/pause_menu/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/pause_menu/cl_plugin.lua new file mode 100644 index 0000000..4fc067f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/pause_menu/cl_plugin.lua @@ -0,0 +1,205 @@ + +-- Fonts for pause menu +surface.CreateFont("PauseMenuTitle", { + font = "Exo 2", + size = 73, + weight = 700, + extended = true +}) + +surface.CreateFont("PauseMenuButton", { + font = "Exo 2", + size = 35, + weight = 600, + extended = true +}) + +-- Materials +local bgMaterial = Material("materials/ft_ui/military/vnu/charcreate/bg.png", "smooth") +local logoMaterial = Material("materials/ft_ui/military/vnu/charcreate/logo.png", "smooth") + +-- Main pause menu frame +local pauseMenu = nil + +-- Create button function +local function CreateMenuButton(parent, text, x, y) + local btn = vgui.Create("DButton", parent) + btn:SetPos(x, y) + btn:SetSize(350, 66) + btn:SetText("") + + btn.Paint = function(self, w, h) + -- Background with border + surface.SetDrawColor(13, 13, 13, 217) -- #0D0D0D with 85% opacity + draw.RoundedBox(10, 0, 0, w, h, Color(13, 13, 13, 217)) + + -- Border + surface.SetDrawColor(1, 67, 29, 255) -- #01431D + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Text centered + draw.SimpleText(text, "PauseMenuButton", w / 2, h / 2, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Hover effect + if self:IsHovered() then + surface.SetDrawColor(1, 104, 44, 30) -- #01682C with low opacity + draw.RoundedBox(10, 0, 0, w, h, Color(1, 104, 44, 30)) + end + end + + return btn +end + +-- Create pause menu +local function CreatePauseMenu() + if IsValid(pauseMenu) then + pauseMenu:Remove() + return + end + + pauseMenu = vgui.Create("DFrame") + pauseMenu:SetSize(ScrW(), ScrH()) + pauseMenu:SetPos(0, 0) + pauseMenu:SetTitle("") + pauseMenu:SetDraggable(false) + pauseMenu:ShowCloseButton(false) + pauseMenu:MakePopup() + + -- Закрытие меню по Esc + pauseMenu.OnKeyCodePressed = function(self, key) + if key == KEY_ESCAPE then + self:Close() + gui.HideGameUI() + return true + end + end + + pauseMenu.Paint = function(self, w, h) + -- Background image + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(bgMaterial) + surface.DrawTexturedRect(0, 0, w, h) + + -- Dark overlay + surface.SetDrawColor(10, 10, 10, 140) -- rgba(10, 10, 10, 0.55) + surface.DrawRect(0, 0, w, h) + + -- Top bar + surface.SetDrawColor(13, 13, 13, 255) -- #0D0D0D + surface.DrawRect(0, 0, w, 100) + + -- Logo (centered at 885, 23 for 1920x1080) + local logoX = (w / 2) - 75 -- Center logo (150px width / 2) + local logoY = 23 + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(logoMaterial) + surface.DrawTexturedRect(logoX, logoY, 150, 53) + + -- Title "Front Team" (767, 284) + local titleX = (w / 2) - 192 -- Approximate center for "Front Team" + local titleY = 284 + --draw.SimpleText("Front Team", "PauseMenuTitle", titleX, titleY, Color(255, 255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + -- Calculate center X position for buttons (785px for 1920px screen) + local centerX = (ScrW() / 2) - 175 -- 350px button width / 2 + + -- Button 1: Вернуться в игру (380px from top) + local btnReturn = CreateMenuButton(pauseMenu, "Вернуться в игру", centerX, 380) + btnReturn.DoClick = function() + pauseMenu:Close() + gui.HideGameUI() + end + + -- Button 2: Правила (450px from top) + local btnRules = CreateMenuButton(pauseMenu, "Правила", centerX, 450) + btnRules.DoClick = function() + -- Open rules URL or show rules frame + gui.OpenURL("https://sites.google.com/view/frontteamsite/правила") -- Replace with actual URL + end + + -- Button 3: Discord (520px from top) + local btnDiscord = CreateMenuButton(pauseMenu, "Discord", centerX, 520) + btnDiscord.DoClick = function() + -- Open Discord invite link + gui.OpenURL("https://discord.gg/paQdrP7aD7") -- Replace with actual Discord link + end + + -- Button 4: Настройки (590px from top) + local btnSettings = CreateMenuButton(pauseMenu, "Настройки", centerX, 590) + btnSettings.DoClick = function() + if IsValid(pauseMenu) then + pauseMenu:Close() + end + + gui.ActivateGameUI() + + timer.Simple(0.1, function() + RunConsoleCommand("gamemenucommand", "openoptionsdialog") + end) + end + + + -- Button 6: Выйти с сервера (730px from top) + local btnDisconnect = CreateMenuButton(pauseMenu, "Выйти с сервера", centerX, 730) + btnDisconnect.DoClick = function() + RunConsoleCommand("disconnect") + end + + -- Button 7: Config (800px from top) - только для superadmin + if LocalPlayer():IsSuperAdmin() then + local btnConfig = CreateMenuButton(pauseMenu, "Config", centerX, 800) + btnConfig.DoClick = function() + -- Создаем DFrame с конфигом Helix + local configFrame = vgui.Create("DFrame") + configFrame:SetSize(ScrW() * 0.8, ScrH() * 0.8) + configFrame:Center() + configFrame:SetTitle("Helix Config") + configFrame:MakePopup() + + -- Создаем tabs панель + local tabs = configFrame:Add("DPropertySheet") + tabs:Dock(FILL) + + -- Вкладка Config + local configPanel = vgui.Create("DPanel") + configPanel:Dock(FILL) + + local configManager = configPanel:Add("ixConfigManager") + + tabs:AddSheet("Config", configPanel, "icon16/cog.png") + + -- Вкладка Plugins + local pluginsPanel = vgui.Create("DPanel") + pluginsPanel:Dock(FILL) + + local pluginManager = pluginsPanel:Add("ixPluginManager") + ix.gui.pluginManager = pluginManager + + tabs:AddSheet("Plugins", pluginsPanel, "icon16/plugin.png") + + -- При закрытии фрейма убираем ссылку на pluginManager + configFrame.OnClose = function() + ix.gui.pluginManager = nil + end + end + end + + return pauseMenu +end + +-- Hook to show custom pause menu +hook.Add("OnPauseMenuShow", "ixShowCustomPauseMenu", function() + if IsValid(ix.plugin.list["f4menu"].f4Menu) then + ix.plugin.list["f4menu"].f4Menu:Remove() + return false + end + + CreatePauseMenu() + return false -- Prevent default pause menu +end) + +-- Console command to open pause menu (for testing) +concommand.Add("ix_pausemenu", function() + CreatePauseMenu() +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/pause_menu/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/pause_menu/sh_plugin.lua new file mode 100644 index 0000000..82e1b4a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/pause_menu/sh_plugin.lua @@ -0,0 +1,6 @@ + +PLUGIN.name = "Pause Menu" +PLUGIN.author = "FrontTeam" +PLUGIN.description = "Custom escape/pause menu" + +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/personal_storage/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/cl_plugin.lua new file mode 100644 index 0000000..e41589f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/cl_plugin.lua @@ -0,0 +1,202 @@ +local PLUGIN = PLUGIN + +local storageData = {} + +net.Receive("ixStorageSync", function() + storageData = net.ReadTable() + + if (IsValid(ix.gui.personalStorage)) then + ix.gui.personalStorage:UpdateStorage(storageData) + ix.gui.personalStorage:UpdatePlayerItems() + else + PLUGIN:OpenUI() + end +end) + +function PLUGIN:OpenUI() + if (IsValid(ix.gui.personalStorage)) then + ix.gui.personalStorage:Remove() + end + + local frame = vgui.Create("DFrame") + frame:SetSize(800, 600) + frame:Center() + frame:SetTitle("ПЕРСОНАЛЬНЫЙ СКЛАД") + frame:MakePopup() + ix.gui.personalStorage = frame + + frame.Paint = function(self, w, h) + surface.SetDrawColor(30, 30, 30, 240) + surface.DrawRect(0, 0, w, h) + local accent = ix.config.Get("color") or Color(100, 150, 100) + surface.SetDrawColor(accent) + surface.DrawOutlinedRect(0, 0, w, h) + + surface.SetDrawColor(accent.r, accent.g, accent.b, 50) + surface.DrawRect(0, 0, w, 24) + end + + local leftPanel = frame:Add("DPanel") + leftPanel:Dock(LEFT) + leftPanel:SetWide(385) + leftPanel:DockMargin(10, 30, 5, 10) + leftPanel.Paint = function(self, w, h) + surface.SetDrawColor(0, 0, 0, 100) + surface.DrawRect(0, 0, w, h) + end + + local rightPanel = frame:Add("DPanel") + rightPanel:Dock(RIGHT) + rightPanel:SetWide(385) + rightPanel:DockMargin(5, 30, 10, 10) + rightPanel.Paint = function(self, w, h) + surface.SetDrawColor(0, 0, 0, 100) + surface.DrawRect(0, 0, w, h) + end + + local pScroll = leftPanel:Add("DScrollPanel") + pScroll:Dock(FILL) + pScroll:DockMargin(5, 5, 5, 5) + + function frame:UpdatePlayerItems() + pScroll:Clear() + + local wepHeader = pScroll:Add("DLabel") + wepHeader:SetText("ОРУЖИЕ В РУКАХ") + wepHeader:SetFont("ixSmallFont") + wepHeader:Dock(TOP) + wepHeader:SetContentAlignment(5) + wepHeader:SetTall(25) + wepHeader:SetTextColor(ix.config.Get("color")) + + for _, wep in pairs(LocalPlayer():GetWeapons()) do + if (!IsValid(wep)) then continue end + + local wepName = wep:GetPrintName() + local class = wep:GetClass() + + local btn = pScroll:Add("ixMenuButton") + btn:Dock(TOP) + btn:SetTall(35) + btn:SetText(wepName) + btn.DoClick = function() + if (frame.nextClick and frame.nextClick > CurTime()) then return end + frame.nextClick = CurTime() + 1.0 -- Increase cooldown for safety + + net.Start("ixStorageDeposit") + net.WriteString("weapon") + net.WriteString(class) + net.SendToServer() + + btn:SetEnabled(false) + btn:SetText("ОЖИДАНИЕ...") + + -- Fail-safe to re-enable UI if server doesn't respond + timer.Simple(2, function() + if (IsValid(btn) and !btn:IsEnabled()) then + btn:SetEnabled(true) + btn:SetText(wepName) + end + end) + end + end + + local invHeader = pScroll:Add("DLabel") + invHeader:SetText("ПРЕДМЕТЫ В ИНВЕНТАРЕ") + invHeader:SetFont("ixSmallFont") + invHeader:Dock(TOP) + invHeader:SetContentAlignment(5) + invHeader:SetTall(25) + invHeader:DockMargin(0, 10, 0, 0) + invHeader:SetTextColor(ix.config.Get("color")) + + local char = LocalPlayer():GetCharacter() + if (char) then + local inv = char:GetInventory() + if (inv) then + for _, item in pairs(inv:GetItems()) do + local btn = pScroll:Add("ixMenuButton") + btn:Dock(TOP) + btn:SetTall(35) + btn:SetText(item:GetName()) + local originalText = item:GetName() + btn.DoClick = function() + if (frame.nextClick and frame.nextClick > CurTime()) then return end + frame.nextClick = CurTime() + 1.0 + + net.Start("ixStorageDeposit") + net.WriteString("item") + net.WriteString(tostring(item:GetID())) + net.SendToServer() + + btn:SetEnabled(false) + btn:SetText("ОЖИДАНИЕ...") + + timer.Simple(2, function() + if (IsValid(btn) and !btn:IsEnabled()) then + btn:SetEnabled(true) + btn:SetText(originalText) + end + end) + end + end + end + end + end + frame:UpdatePlayerItems() + + local sScroll = rightPanel:Add("DScrollPanel") + sScroll:Dock(FILL) + sScroll:DockMargin(5, 5, 5, 5) + + local storageHeader = sScroll:Add("DLabel") + storageHeader:SetText("СОДЕРЖИМОЕ ХРАНИЛИЩА") + storageHeader:SetFont("ixSmallFont") + storageHeader:Dock(TOP) + storageHeader:SetContentAlignment(5) + storageHeader:SetTall(25) + storageHeader:SetTextColor(ix.config.Get("color")) + + local grid = sScroll:Add("DIconLayout") + grid:Dock(TOP) + grid:SetSpaceX(5) + grid:SetSpaceY(5) + + function frame:UpdateStorage(data) + grid:Clear() + + for i, entry in ipairs(data) do + if (!istable(entry)) then continue end + + local icon = grid:Add("SpawnIcon") + + local model = "models/error.mdl" + if (entry.type == "weapon" and entry.class) then + local wepTable = weapons.Get(entry.class) + model = (wepTable and wepTable.WorldModel) or "models/weapons/w_pistol.mdl" + elseif (entry.type == "item" and entry.uniqueID) then + local itemTable = ix.item.list[entry.uniqueID] + model = (itemTable and itemTable.model) or "models/props_junk/garbage_bag001a.mdl" + end + + icon:SetModel(model) + icon:SetSize(64, 64) + icon:SetTooltip(entry.name or entry.class or entry.uniqueID or "Неизвестный предмет") + icon.DoClick = function() + if (frame.nextClick and frame.nextClick > CurTime()) then return end + frame.nextClick = CurTime() + 1.0 + + net.Start("ixStorageWithdraw") + net.WriteUInt(i, 8) + net.SendToServer() + + icon:SetAlpha(100) + + timer.Simple(2, function() + if (IsValid(icon)) then icon:SetAlpha(255) end + end) + end + end + end + frame:UpdateStorage(storageData) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/personal_storage/entities/entities/ix_personal_storage.lua b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/entities/entities/ix_personal_storage.lua new file mode 100644 index 0000000..246e885 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/entities/entities/ix_personal_storage.lua @@ -0,0 +1,40 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Личный склад" +ENT.Author = "Scripty" +ENT.Category = "Helix" +ENT.Spawnable = true +ENT.AdminOnly = true + +if SERVER then + function ENT:Initialize() + self:SetModel("models/props_c17/Lockers001a.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:EnableMotion(false) + end + end + + function ENT:Use(activator) + if (IsValid(activator) and activator:IsPlayer() and activator:GetCharacter()) then + local plugin = ix.plugin.list["personal_storage"] + if (plugin) then + plugin:OpenStorage(activator) + end + end + end +end + +if CLIENT then + function ENT:Draw() + self:DrawModel() + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sh_config.lua b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sh_config.lua new file mode 100644 index 0000000..c2e3c12 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sh_config.lua @@ -0,0 +1 @@ +-- Empty config file for future use diff --git a/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sh_plugin.lua new file mode 100644 index 0000000..7a089db --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sh_plugin.lua @@ -0,0 +1,8 @@ +local PLUGIN = PLUGIN or {} +PLUGIN.name = "Personal Storage" +PLUGIN.author = "Scripty" +PLUGIN.description = "Character-based weapon storage system." + +ix.util.Include("sh_config.lua") +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sv_plugin.lua new file mode 100644 index 0000000..4e721c8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/personal_storage/sv_plugin.lua @@ -0,0 +1,180 @@ +local PLUGIN = PLUGIN + +util.AddNetworkString("ixStorageOpen") +util.AddNetworkString("ixStorageDeposit") +util.AddNetworkString("ixStorageWithdraw") +util.AddNetworkString("ixStorageSync") + +-- Robust and STABLE data retrieval: ensures we ALWAYS return a sequential numeric array +-- handled in a stable order based on original keys. +function PLUGIN:GetStorage(char) + local data = char:GetData("personal_storage", {}) + if (!istable(data)) then return {} end + + local fixedData = {} + local keys = {} + for k, _ in pairs(data) do + local n = tonumber(k) + if (n) then table.insert(keys, n) end + end + table.sort(keys) -- Ensure key stability (1 before 2, etc) + + for _, k in ipairs(keys) do + local v = data[k] or data[tostring(k)] + if (istable(v) and v.type) then + table.insert(fixedData, v) + end + end + + return fixedData +end + +function PLUGIN:SaveStorage(char, data) + -- We save it as a clean array. Helix will JSON-encode it. + char:SetData("personal_storage", data) +end + +net.Receive("ixStorageDeposit", function(len, ply) + local char = ply:GetCharacter() + if (!char) then return end + + local type = net.ReadString() + local id = net.ReadString() + + local storage = PLUGIN:GetStorage(char) + local entry = {type = type} + + if (type == "weapon") then + local weapon = ply:GetWeapon(id) + if (!IsValid(weapon)) then + net.Start("ixStorageSync") + net.WriteTable(storage) + net.Send(ply) + return + end + + local blacklist = {["weapon_physgun"] = true, ["gmod_tool"] = true, ["gmod_camera"] = true} + if (blacklist[id]) then + net.Start("ixStorageSync") + net.WriteTable(storage) + net.Send(ply) + return + end + + entry.class = id + entry.name = weapon:GetPrintName() or id + entry.clip1 = weapon:Clip1() + entry.clip2 = weapon:Clip2() + + ply:StripWeapon(id) + + table.insert(storage, entry) + PLUGIN:SaveStorage(char, storage) + + net.Start("ixStorageSync") + net.WriteTable(storage) + net.Send(ply) + elseif (type == "item") then + local inv = char:GetInventory() + local itemID = tonumber(id) + local item = inv:GetItemByID(itemID) + + if (!item) then + net.Start("ixStorageSync") + net.WriteTable(storage) + net.Send(ply) + return + end + + entry.uniqueID = item.uniqueID + entry.data = item.data or {} + entry.name = item:GetName() or item.name + + item:Remove():next(function() + -- Add to storage only after successful removal from inventory + local currentStorage = PLUGIN:GetStorage(char) + table.insert(currentStorage, entry) + PLUGIN:SaveStorage(char, currentStorage) + + net.Start("ixStorageSync") + net.WriteTable(currentStorage) + net.Send(ply) + end):catch(function(err) + net.Start("ixStorageSync") + net.WriteTable(PLUGIN:GetStorage(char)) + net.Send(ply) + ply:Notify("Ошибка удаления: " .. (err or "неизвестно")) + end) + end +end) + +net.Receive("ixStorageWithdraw", function(len, ply) + local char = ply:GetCharacter() + if (!char) then return end + + local index = net.ReadUInt(8) + local storage = PLUGIN:GetStorage(char) + local entry = storage[index] + + if (!entry) then + net.Start("ixStorageSync") + net.WriteTable(storage) + net.Send(ply) + return + end + + -- PRE-EMPTIVE REMOVAL to prevent duplication exploit/bug + table.remove(storage, index) + PLUGIN:SaveStorage(char, storage) + + if (entry.type == "weapon") then + local weapon = ply:Give(entry.class) + if (IsValid(weapon)) then + weapon:SetClip1(entry.clip1 or 0) + weapon:SetClip2(entry.clip2 or 0) + + -- Already removed from storage, just sync + net.Start("ixStorageSync") + net.WriteTable(storage) + net.Send(ply) + else + -- FAIL: Put back + local currentStorage = PLUGIN:GetStorage(char) + table.insert(currentStorage, entry) + PLUGIN:SaveStorage(char, currentStorage) + + net.Start("ixStorageSync") + net.WriteTable(currentStorage) + net.Send(ply) + end + else + local inv = char:GetInventory() + inv:Add(entry.uniqueID, 1, entry.data):next(function(res) + -- Already removed from storage, just sync to confirm removal + net.Start("ixStorageSync") + net.WriteTable(PLUGIN:GetStorage(char)) + net.Send(ply) + end):catch(function(err) + -- FAIL: Put back + local currentStorage = PLUGIN:GetStorage(char) + table.insert(currentStorage, entry) + PLUGIN:SaveStorage(char, currentStorage) + + net.Start("ixStorageSync") + net.WriteTable(currentStorage) + net.Send(ply) + + ply:Notify("Нет места в инвентаре! Предмет возвращен в шкаф.") + end) + end +end) + +function PLUGIN:OpenStorage(ply) + local char = ply:GetCharacter() + if (!char) then return end + + local data = self:GetStorage(char) + net.Start("ixStorageSync") + net.WriteTable(data) + net.Send(ply) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/podr_model/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/podr_model/sh_plugin.lua new file mode 100644 index 0000000..9cf4f40 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/podr_model/sh_plugin.lua @@ -0,0 +1,111 @@ +PLUGIN.name = "Подразделение: модель и параметры" +PLUGIN.author = "Automated" +PLUGIN.description = "Устанавливает модель игрока, skin и bodygroups согласно его подразделению (Podr) из таблицы фракции." + +if SERVER then + local function applyPodrModel(client, character) + if not IsValid(client) then return end + if not character then character = client:GetCharacter() end + if not character then return end + + local faction = client:Team() + local factionTable = ix.faction.Get(faction) + if not factionTable then return end + + local podrID = character:GetPodr() + local specID = character:GetSpec() + local podrTable = factionTable.Podr and factionTable.Podr[podrID] + local specTable = factionTable.Spec and factionTable.Spec[specID] + + -- Determine source for model: prefer spec.model, fallback to podr.model + local sourceModelTable = nil + if specTable and specTable.model and type(specTable.model) == "string" and specTable.model ~= "" then + sourceModelTable = specTable + elseif podrTable and podrTable.model and type(podrTable.model) == "string" and podrTable.model ~= "" then + sourceModelTable = podrTable + end + + if sourceModelTable then + pcall(function() client:SetModel(sourceModelTable.model) end) + end + + -- Skin: prefer spec.skin, then podr.skin + local skinSource = nil + if specTable and specTable.skin then + skinSource = specTable.skin + elseif podrTable and podrTable.skin then + skinSource = podrTable.skin + end + + if skinSource then + if isnumber(skinSource) then + pcall(function() client:SetSkin(skinSource) end) + elseif istable(skinSource) then + local skinVal = nil + if skinSource[client:Team()] then + skinVal = skinSource[client:Team()] + else + for _, v in ipairs(skinSource) do + skinVal = v + break + end + end + if isnumber(skinVal) then + pcall(function() client:SetSkin(skinVal) end) + end + end + end + + -- Bodygroups: merge spec.bodygroups over podr.bodygroups + local mergedBodygroups = {} + if podrTable and istable(podrTable.bodygroups) then + for k, v in pairs(podrTable.bodygroups) do mergedBodygroups[k] = v end + end + if specTable and istable(specTable.bodygroups) then + for k, v in pairs(specTable.bodygroups) do mergedBodygroups[k] = v end + end + + timer.Simple(0.1, function() + if not IsValid(client) then return end + + if mergedBodygroups and next(mergedBodygroups) then + for k, v in pairs(mergedBodygroups) do + if isnumber(k) and isnumber(v) then + pcall(function() client:SetBodygroup(k, v) end) + elseif istable(v) and v.id and v.value then + pcall(function() client:SetBodygroup(v.id, v.value) end) + end + end + end + end) + end + + function PLUGIN:CharacterLoaded(character) + local client = character:GetPlayer() + if IsValid(client) then + applyPodrModel(client, character) + end + end + + -- Apply on spawn too (in case Helix or gamemode overwrote model earlier) + function PLUGIN:PlayerSpawn(client) + local character = client:GetCharacter() + if character then + -- delay a tick to let other PlayerSpawn logic run + timer.Simple(0, function() + if IsValid(client) then + applyPodrModel(client, character) + end + end) + end + end +end + +if CLIENT then + -- nothing client-side needed for now, but keep file shared for simplicity +end + +-- Документация/пример для FACTION.Podr: +-- FACTION.Podr = { +-- [1] = { name = "Новоприбывшие", model = "models/player/group01/male_04.mdl", skin = 0, bodygroups = { [1] = 1, {id = 2, value = 0} } }, +-- } diff --git a/garrysmod/gamemodes/militaryrp/plugins/presencer.lua b/garrysmod/gamemodes/militaryrp/plugins/presencer.lua new file mode 100644 index 0000000..1f8d721 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/presencer.lua @@ -0,0 +1,43 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Presencer" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.desc = "Добавляет Rich Presence в Discord и Steam Presence." +PLUGIN.DiscordID = "1424715569265115247" +PLUGIN.RefreshTime = 10 +PLUGIN.DiscordStart = PLUGIN.DiscordStart or -1 + +if (CLIENT) then + if util.IsBinaryModuleInstalled("gdiscord") then + require("gdiscord") + + function DiscordUpdate() + local rpc_data = {} + if LocalPlayer():GetCharacter() then + rpc_data["details"] = "Игрок: " .. LocalPlayer():Nick() + rpc_data["state"] = "Фракция: " .. (ix.faction.Get( LocalPlayer():GetFaction() ) and ix.faction.Get( LocalPlayer():GetFaction() ).name or "Неизвестна") + rpc_data["buttonPrimaryLabel"] = "Присоединиться к серверу" + rpc_data["buttonPrimaryUrl"] = "steam://connect/" .. game.GetIPAddress() + rpc_data["partySize"] = player.GetCount() + rpc_data["partyMax"] = game.MaxPlayers() + rpc_data["largeImageKey"] = "logo" + rpc_data["largeImageText"] = "Front-Team | Война на Украине" + rpc_data["startTimestamp"] = PLUGIN.DiscordStart + end + DiscordUpdateRPC(rpc_data) + end + end + + if util.IsBinaryModuleInstalled("steamrichpresencer") then + require("steamrichpresencer") + steamworks.SetRichPresence("generic", "Сервер Front-Team | Война на Украине") + end + + function PLUGIN:Initialize() + if util.IsBinaryModuleInstalled("gdiscord") then + PLUGIN.DiscordStart = os.time() + DiscordRPCInitialize(PLUGIN.DiscordID) + DiscordUpdate() + timer.Create("DiscordRPCTimer", PLUGIN.RefreshTime, 0, DiscordUpdate) + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/promocodes/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/promocodes/cl_plugin.lua new file mode 100644 index 0000000..5c63462 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/promocodes/cl_plugin.lua @@ -0,0 +1,411 @@ +-- Клиентские данные +local PLUGIN = PLUGIN +PLUGIN.promoCodesList = PLUGIN.promoCodesList or {} + +-- Получение результата активации +net.Receive("ixPromoCodeResult", function() + local success = net.ReadBool() + local message = net.ReadString() + + if success then + notification.AddLegacy(message, NOTIFY_GENERIC, 5) + surface.PlaySound("buttons/button15.wav") + else + notification.AddLegacy(message, NOTIFY_ERROR, 5) + surface.PlaySound("buttons/button10.wav") + end + + LocalPlayer():Notify(message) +end) + +-- Получение списка промокодов (для админов) +net.Receive("ixPromoCodeSync", function() + local jsonData = net.ReadString() + PLUGIN.promoCodesList = util.JSONToTable(jsonData) or {} +end) + +-- Функция активации промокода +function PLUGIN:ActivatePromoCode(code) + net.Start("ixPromoCodeActivate") + net.WriteString(code) + net.SendToServer() +end + +-- Админ-панель управления промокодами +function PLUGIN:OpenPromoCodesAdmin() + if not LocalPlayer():IsSuperAdmin() then + LocalPlayer():Notify("У вас нет доступа к этой панели") + return + end + + -- Запрашиваем список промокодов + net.Start("ixPromoCodeList") + net.SendToServer() + + local frame = vgui.Create("DFrame") + frame:SetSize(1200, 700) + frame:Center() + frame:SetTitle("") + frame:SetDraggable(true) + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(18, 18, 20)) + surface.SetDrawColor(1, 67, 29) + surface.DrawRect(0, 0, w, 3) + draw.SimpleText("УПРАВЛЕНИЕ ПРОМОКОДАМИ", "F4Menu_DonateHero", w/2, 30, Color(1, 67, 29), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetPos(frame:GetWide() - 35, 5) + closeBtn:SetSize(30, 30) + closeBtn:SetText("✕") + closeBtn:SetFont("F4Menu_Category") + closeBtn:SetTextColor(Color(255, 255, 255)) + closeBtn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(0, 0, 0, 0)) + end + closeBtn.DoClick = function() + frame:Close() + end + + -- Панель создания промокода + local createPanel = vgui.Create("DPanel", frame) + createPanel:SetPos(20, 70) + createPanel:SetSize(550, 600) + createPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(25, 25, 28)) + draw.SimpleText("СОЗДАТЬ ПРОМОКОД", "F4Menu_Category", w/2, 20, Color(200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local yPos = 60 + + -- Переменные для доступа в обработчиках + local codeEntry + local isCustomCode = false + + -- Тип промокода + local typeLabel = vgui.Create("DLabel", createPanel) + typeLabel:SetPos(20, yPos) + typeLabel:SetSize(200, 25) + typeLabel:SetFont("F4Menu_Item") + typeLabel:SetTextColor(Color(200, 200, 200)) + typeLabel:SetText("Тип промокода:") + + local typeSelector = vgui.Create("DPanel", createPanel) + typeSelector:SetPos(20, yPos + 30) + typeSelector:SetSize(510, 40) + typeSelector.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) + end + + local randomBtn = vgui.Create("DButton", typeSelector) + randomBtn:SetPos(5, 5) + randomBtn:SetSize(250, 30) + randomBtn:SetText("") + randomBtn.Paint = function(s, w, h) + local active = not isCustomCode + draw.RoundedBox(6, 0, 0, w, h, active and Color(1, 67, 29) or Color(45, 45, 48)) + draw.SimpleText("Случайный код", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + randomBtn.DoClick = function() + isCustomCode = false + codeEntry:SetEnabled(false) + codeEntry:SetValue("") + codeEntry:SetPlaceholderText("Будет сгенерирован автоматически") + end + + local customBtn = vgui.Create("DButton", typeSelector) + customBtn:SetPos(255, 5) + customBtn:SetSize(250, 30) + customBtn:SetText("") + customBtn.Paint = function(s, w, h) + local active = isCustomCode + draw.RoundedBox(6, 0, 0, w, h, active and Color(1, 67, 29) or Color(45, 45, 48)) + draw.SimpleText("Именной код", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + customBtn.DoClick = function() + isCustomCode = true + codeEntry:SetEnabled(true) + codeEntry:SetValue("") + codeEntry:SetPlaceholderText("NEW_YEAR2026") + end + + yPos = yPos + 85 + + -- Код промокода + local codeLabel = vgui.Create("DLabel", createPanel) + codeLabel:SetPos(20, yPos) + codeLabel:SetSize(400, 25) + codeLabel:SetFont("F4Menu_Item") + codeLabel:SetTextColor(Color(200, 200, 200)) + codeLabel:SetText("Код промокода:") + + local examplesLabel = vgui.Create("DLabel", createPanel) + examplesLabel:SetPos(20, yPos + 20) + examplesLabel:SetSize(510, 15) + examplesLabel:SetFont("F4Menu_InfoSmall") + examplesLabel:SetTextColor(Color(120, 120, 120)) + examplesLabel:SetText("Примеры: NEW_YEAR2026, SUMMER_SALE, WELCOME100, STREAMER_GIFT") + + codeEntry = vgui.Create("DTextEntry", createPanel) + codeEntry:SetPos(20, yPos + 40) + codeEntry:SetSize(510, 35) + codeEntry:SetFont("F4Menu_Item") + codeEntry:SetPlaceholderText("Будет сгенерирован автоматически") + codeEntry:SetEnabled(false) + codeEntry:SetUpdateOnType(true) + codeEntry.Paint = function(s, w, h) + local bgColor = s:IsEnabled() and Color(35, 35, 38) or Color(25, 25, 28) + draw.RoundedBox(6, 0, 0, w, h, bgColor) + s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) + + -- Валидация в реальном времени + if s:IsEnabled() and s:GetValue() ~= "" then + local value = s:GetValue() + local isValid = string.match(value, "^[A-Z0-9_]+$") ~= nil + + if not isValid then + draw.RoundedBox(6, 0, 0, w, h, Color(80, 20, 20, 50)) + draw.SimpleText("Только A-Z, 0-9 и _", "F4Menu_InfoSmall", w - 10, h/2, Color(255, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + end + codeEntry.OnTextChanged = function(s) + -- Автоматическое приведение к верхнему регистру + local text = s:GetValue() + local upperText = string.upper(text) + if text ~= upperText then + s:SetValue(upperText) + s:SetCaretPos(#upperText) + end + end + + yPos = yPos + 80 + + -- Количество IGS + local amountLabel = vgui.Create("DLabel", createPanel) + amountLabel:SetPos(20, yPos) + amountLabel:SetSize(200, 25) + amountLabel:SetFont("F4Menu_Item") + amountLabel:SetTextColor(Color(200, 200, 200)) + amountLabel:SetText("Количество IGS:") + + local amountEntry = vgui.Create("DTextEntry", createPanel) + amountEntry:SetPos(20, yPos + 30) + amountEntry:SetSize(510, 35) + amountEntry:SetFont("F4Menu_Item") + amountEntry:SetPlaceholderText("100") + amountEntry:SetNumeric(true) + amountEntry.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) + s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) + end + + yPos = yPos + 80 + + -- Максимум использований + local usesLabel = vgui.Create("DLabel", createPanel) + usesLabel:SetPos(20, yPos) + usesLabel:SetSize(200, 25) + usesLabel:SetFont("F4Menu_Item") + usesLabel:SetTextColor(Color(200, 200, 200)) + usesLabel:SetText("Максимум использований:") + + local usesEntry = vgui.Create("DTextEntry", createPanel) + usesEntry:SetPos(20, yPos + 30) + usesEntry:SetSize(510, 35) + usesEntry:SetFont("F4Menu_Item") + usesEntry:SetPlaceholderText("1") + usesEntry:SetNumeric(true) + usesEntry.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) + s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) + end + + yPos = yPos + 80 + + -- Срок действия (дни) + local daysLabel = vgui.Create("DLabel", createPanel) + daysLabel:SetPos(20, yPos) + daysLabel:SetSize(200, 25) + daysLabel:SetFont("F4Menu_Item") + daysLabel:SetTextColor(Color(200, 200, 200)) + daysLabel:SetText("Срок действия (дней):") + + local daysEntry = vgui.Create("DTextEntry", createPanel) + daysEntry:SetPos(20, yPos + 30) + daysEntry:SetSize(510, 35) + daysEntry:SetFont("F4Menu_Item") + daysEntry:SetPlaceholderText("30") + daysEntry:SetNumeric(true) + daysEntry.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) + s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) + end + + yPos = yPos + 80 + + -- Доступные ранги + local ranksLabel = vgui.Create("DLabel", createPanel) + ranksLabel:SetPos(20, yPos) + ranksLabel:SetSize(300, 25) + ranksLabel:SetFont("F4Menu_Item") + ranksLabel:SetTextColor(Color(200, 200, 200)) + ranksLabel:SetText("Доступно для рангов (пусто = всем):") + + local ranksEntry = vgui.Create("DTextEntry", createPanel) + ranksEntry:SetPos(20, yPos + 30) + ranksEntry:SetSize(510, 35) + ranksEntry:SetFont("F4Menu_Item") + ranksEntry:SetPlaceholderText("user, vip, moderator (через запятую)") + ranksEntry.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) + s:DrawTextEntryText(Color(255, 255, 255), Color(100, 150, 255), Color(255, 255, 255)) + end + + yPos = yPos + 80 + + -- Кнопка создания + local createBtn = vgui.Create("DButton", createPanel) + createBtn:SetPos(20, yPos) + createBtn:SetSize(510, 45) + createBtn:SetText("") + createBtn.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) + draw.SimpleText("СОЗДАТЬ ПРОМОКОД", "F4Menu_Category", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + createBtn.DoClick = function() + local code = codeEntry:GetValue() + local amount = tonumber(amountEntry:GetValue()) or 100 + local maxUses = tonumber(usesEntry:GetValue()) or 1 + local days = tonumber(daysEntry:GetValue()) or 30 + local expiresAt = os.time() + (days * 86400) + + local ranksText = ranksEntry:GetValue() + local allowedRanks = {} + if ranksText ~= "" then + for rank in string.gmatch(ranksText, "([^,]+)") do + table.insert(allowedRanks, string.Trim(rank)) + end + end + + net.Start("ixPromoCodeCreate") + net.WriteString(code) + net.WriteUInt(amount, 32) + net.WriteUInt(maxUses, 16) + net.WriteUInt(expiresAt, 32) + net.WriteUInt(#allowedRanks, 8) + for _, rank in ipairs(allowedRanks) do + net.WriteString(rank) + end + net.SendToServer() + + -- Очистка полей + codeEntry:SetValue("") + amountEntry:SetValue("") + usesEntry:SetValue("") + daysEntry:SetValue("") + ranksEntry:SetValue("") + end + + -- Список промокодов + local listPanel = vgui.Create("DPanel", frame) + listPanel:SetPos(590, 70) + listPanel:SetSize(590, 600) + listPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(25, 25, 28)) + draw.SimpleText("АКТИВНЫЕ ПРОМОКОДЫ", "F4Menu_Category", w/2, 20, Color(200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local listScroll = vgui.Create("DScrollPanel", listPanel) + listScroll:SetPos(10, 50) + listScroll:SetSize(570, 540) + + local vbar = listScroll:GetVBar() + vbar:SetWide(8) + function vbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(15, 15, 17)) + end + function vbar.btnUp:Paint(w, h) end + function vbar.btnDown:Paint(w, h) end + function vbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(1, 67, 29)) + end + + local function RefreshList() + listScroll:Clear() + + for _, promo in ipairs(self.promoCodesList) do + local promoPanel = vgui.Create("DPanel", listScroll) + promoPanel:Dock(TOP) + promoPanel:DockMargin(5, 5, 5, 0) + promoPanel:SetTall(100) + promoPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(35, 35, 38)) + + -- Код + draw.SimpleText(promo.code, "F4Menu_Category", 15, 15, Color(100, 150, 255), TEXT_ALIGN_LEFT) + + -- Информация + local info = string.format("IGS: %d | Использований: %d/%d", promo.amount, promo.currentUses, promo.maxUses) + draw.SimpleText(info, "F4Menu_Item", 15, 40, Color(200, 200, 200), TEXT_ALIGN_LEFT) + + -- Срок действия + local expiresDate = os.date("%d.%m.%Y %H:%M", promo.expiresAt) + local expired = os.time() > promo.expiresAt + draw.SimpleText("До: " .. expiresDate, "F4Menu_InfoSmall", 15, 65, expired and Color(255, 100, 100) or Color(150, 150, 150), TEXT_ALIGN_LEFT) + + -- Ранги + if promo.allowedRanks and #promo.allowedRanks > 0 then + local ranksText = "Ранги: " .. table.concat(promo.allowedRanks, ", ") + draw.SimpleText(ranksText, "F4Menu_InfoSmall", 15, 80, Color(150, 150, 150), TEXT_ALIGN_LEFT) + end + end + + -- Кнопка удаления + local deleteBtn = vgui.Create("DButton", promoPanel) + deleteBtn:SetPos(promoPanel:GetWide() + 385, 30) + deleteBtn:SetSize(100, 40) + deleteBtn:SetText("") + deleteBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(200, 50, 50) or Color(150, 40, 40)) + draw.SimpleText("Удалить", "F4Menu_Item", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + deleteBtn.DoClick = function() + Derma_Query( + "Удалить промокод '" .. promo.code .. "'?", + "Подтверждение", + "Да", + function() + net.Start("ixPromoCodeDelete") + net.WriteString(promo.code) + net.SendToServer() + + timer.Simple(0.5, RefreshList) + end, + "Нет" + ) + end + end + end + + -- Обновляем список каждую секунду + timer.Create("PromoCodesRefresh", 1, 0, function() + if not IsValid(frame) then + timer.Remove("PromoCodesRefresh") + return + end + RefreshList() + end) + + RefreshList() +end + +-- Команда для открытия админ-панели +concommand.Add("promocodes_admin", function() + local plugin = ix.plugin.Get("promocodes") + if plugin then + plugin:OpenPromoCodesAdmin() + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/promocodes/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/promocodes/sh_plugin.lua new file mode 100644 index 0000000..f9cddd5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/promocodes/sh_plugin.lua @@ -0,0 +1,8 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Promocodes System" +PLUGIN.author = "MilitaryRP" +PLUGIN.description = "Система промокодов для доната" + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/promocodes/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/promocodes/sv_plugin.lua new file mode 100644 index 0000000..2281656 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/promocodes/sv_plugin.lua @@ -0,0 +1,327 @@ +local PLUGIN = PLUGIN +util.AddNetworkString("ixPromoCodeActivate") +util.AddNetworkString("ixPromoCodeResult") +util.AddNetworkString("ixPromoCodeCreate") +util.AddNetworkString("ixPromoCodeDelete") +util.AddNetworkString("ixPromoCodeList") +util.AddNetworkString("ixPromoCodeSync") +PLUGIN.promoCodes = PLUGIN.promoCodes or {} +PLUGIN.promoUsage = PLUGIN.promoUsage or {} -- [code] = {[steamID] = true} + +-- Путь к файлу сохранения +local dataPath = "militaryrp/promocodes.txt" + +-- Сохранение промокодов +function PLUGIN:SavePromoCodes() + local data = { + codes = self.promoCodes, + usage = self.promoUsage + } + + file.CreateDir("militaryrp") + file.Write(dataPath, util.TableToJSON(data)) + print("[PROMOCODES] Промокоды сохранены") +end + +-- Загрузка промокодов +function PLUGIN:LoadPromoCodes() + if file.Exists(dataPath, "DATA") then + local jsonData = file.Read(dataPath, "DATA") + local data = util.JSONToTable(jsonData) + + if data then + self.promoCodes = data.codes or {} + self.promoUsage = data.usage or {} + print("[PROMOCODES] Загружено " .. table.Count(self.promoCodes) .. " промокодов") + end + else + print("[PROMOCODES] Файл промокодов не найден, создается новый") + end +end + +-- Генерация случайного кода +function PLUGIN:GenerateCode(length) + local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + local code = "" + + for i = 1, length do + local rand = math.random(1, #chars) + code = code .. string.sub(chars, rand, rand) + end + + return code +end + +-- Валидация именного промокода +function PLUGIN:ValidateCustomCode(code) + -- Проверка на пустоту + if not code or code == "" then + return false, "Код не может быть пустым" + end + + -- Проверка длины (минимум 3, максимум 32 символа) + if #code < 3 then + return false, "Код должен содержать минимум 3 символа" + end + + if #code > 32 then + return false, "Код должен содержать максимум 32 символа" + end + + -- Проверка на допустимые символы (только A-Z, 0-9 и _) + if not string.match(code, "^[A-Z0-9_]+$") then + return false, "Код может содержать только буквы A-Z, цифры 0-9 и символ подчеркивания _" + end + + return true, "OK" +end + +-- Создание промокода +function PLUGIN:CreatePromoCode(admin, codeText, amount, maxUses, expiresAt, allowedRanks) + if not IsValid(admin) then return false, "Недействительный игрок" end + + -- Проверка прав + if not admin:IsSuperAdmin() then + return false, "У вас нет прав для создания промокодов" + end + + -- Генерация кода если не указан + if not codeText or codeText == "" then + codeText = self:GenerateCode(8) + print(string.format("[PROMOCODES] Сгенерирован случайный промокод: %s", codeText)) + else + -- Валидация именного кода + local valid, error = self:ValidateCustomCode(codeText) + if not valid then + return false, error + end + print(string.format("[PROMOCODES] Создается именной промокод: %s", codeText)) + end + + codeText = string.upper(codeText) + + -- Проверка существования + if self.promoCodes[codeText] then + return false, "Промокод с таким кодом уже существует" + end + + -- Создание промокода + self.promoCodes[codeText] = { + code = codeText, + amount = math.max(1, tonumber(amount) or 100), + maxUses = math.max(1, tonumber(maxUses) or 1), + currentUses = 0, + expiresAt = tonumber(expiresAt) or (os.time() + 86400 * 30), -- По умолчанию 30 дней + allowedRanks = allowedRanks or {}, -- Пустой массив = доступно всем + createdBy = admin:SteamID(), + createdAt = os.time() + } + + self.promoUsage[codeText] = {} + self:SavePromoCodes() + + return true, "Промокод '" .. codeText .. "' создан на " .. self.promoCodes[codeText].amount .. " IGS" +end + +-- Удаление промокода +function PLUGIN:DeletePromoCode(admin, codeText) + if not IsValid(admin) then return false, "Недействительный игрок" end + + if not admin:IsSuperAdmin() then + return false, "У вас нет прав для удаления промокодов" + end + + codeText = string.upper(codeText) + + if not self.promoCodes[codeText] then + return false, "Промокод не найден" + end + + self.promoCodes[codeText] = nil + self.promoUsage[codeText] = nil + self:SavePromoCodes() + + return true, "Промокод '" .. codeText .. "' удален" +end + +-- Проверка доступности промокода для игрока +function PLUGIN:CanUsePromoCode(client, codeText) + local promo = self.promoCodes[codeText] + if not promo then + return false, "Промокод не найден" + end + + local steamID = client:SteamID() + + -- Проверка использования игроком + if self.promoUsage[codeText] and self.promoUsage[codeText][steamID] then + return false, "Вы уже использовали этот промокод" + end + + -- Проверка лимита использований + if promo.currentUses >= promo.maxUses then + return false, "Промокод исчерпан" + end + + -- Проверка срока действия + if os.time() > promo.expiresAt then + return false, "Срок действия промокода истек" + end + + -- Проверка доступных рангов + if promo.allowedRanks and #promo.allowedRanks > 0 then + local playerRank = client:GetUserGroup() + local hasAccess = false + + for _, rank in ipairs(promo.allowedRanks) do + if playerRank == rank then + hasAccess = true + break + end + end + + if not hasAccess then + return false, "Этот промокод недоступен для вашего ранга" + end + end + + return true, "OK" +end + +-- Активация промокода +function PLUGIN:ActivatePromoCode(client, codeText) + if not IsValid(client) then return false, "Недействительный игрок" end + + codeText = string.upper(string.Trim(codeText)) + + if codeText == "" then + return false, "Введите промокод" + end + + local canUse, reason = self:CanUsePromoCode(client, codeText) + if not canUse then + return false, reason + end + + local promo = self.promoCodes[codeText] + local steamID = client:SteamID() + + -- Начисление валюты через F4 плагин + local f4Plugin = ix.plugin.Get("f4menu") + if f4Plugin and f4Plugin.AdjustIGSBalance then + local success, error = f4Plugin:AdjustIGSBalance(client, promo.amount) + if not success then + return false, "Ошибка начисления валюты: " .. (error or "неизвестная ошибка") + end + else + return false, "Система доната недоступна" + end + + -- Обновление данных промокода + promo.currentUses = promo.currentUses + 1 + + if not self.promoUsage[codeText] then + self.promoUsage[codeText] = {} + end + self.promoUsage[codeText][steamID] = true + + self:SavePromoCodes() + + -- Лог + print(string.format("[PROMOCODES] %s активировал промокод '%s' (+%d IGS)", client:Name(), codeText, promo.amount)) + + return true, string.format("Промокод активирован! Вы получили %d IGS", promo.amount) +end + +-- Загрузка при старте +function PLUGIN:InitializedPlugins() + self:LoadPromoCodes() +end + +-- Сетевые обработчики +net.Receive("ixPromoCodeActivate", function(len, client) + local code = net.ReadString() + + local plugin = ix.plugin.Get("promocodes") + if not plugin then return end + + local success, message = plugin:ActivatePromoCode(client, code) + + net.Start("ixPromoCodeResult") + net.WriteBool(success) + net.WriteString(message) + net.Send(client) +end) + +net.Receive("ixPromoCodeCreate", function(len, client) + if not client:IsSuperAdmin() then return end + + local code = net.ReadString() + local amount = net.ReadUInt(32) + local maxUses = net.ReadUInt(16) + local expiresAt = net.ReadUInt(32) + local ranksCount = net.ReadUInt(8) + + local allowedRanks = {} + for i = 1, ranksCount do + table.insert(allowedRanks, net.ReadString()) + end + + local plugin = ix.plugin.Get("promocodes") + if not plugin then return end + + local success, message = plugin:CreatePromoCode(client, code, amount, maxUses, expiresAt, allowedRanks) + + net.Start("ixPromoCodeResult") + net.WriteBool(success) + net.WriteString(message) + net.Send(client) + + if success then + -- Отправляем обновленный список + plugin:SyncPromoCodesList(client) + end +end) + +net.Receive("ixPromoCodeDelete", function(len, client) + if not client:IsSuperAdmin() then return end + + local code = net.ReadString() + + local plugin = ix.plugin.Get("promocodes") + if not plugin then return end + + local success, message = plugin:DeletePromoCode(client, code) + + net.Start("ixPromoCodeResult") + net.WriteBool(success) + net.WriteString(message) + net.Send(client) + + if success then + plugin:SyncPromoCodesList(client) + end +end) + +net.Receive("ixPromoCodeList", function(len, client) + if not client:IsSuperAdmin() then return end + + local plugin = ix.plugin.Get("promocodes") + if not plugin then return end + + plugin:SyncPromoCodesList(client) +end) + +-- Синхронизация списка промокодов +function PLUGIN:SyncPromoCodesList(client) + if not IsValid(client) or not client:IsSuperAdmin() then return end + + local codesList = {} + for code, data in pairs(self.promoCodes) do + table.insert(codesList, data) + end + + net.Start("ixPromoCodeSync") + net.WriteString(util.TableToJSON(codesList)) + net.Send(client) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/radio/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/radio/cl_plugin.lua new file mode 100644 index 0000000..8db371d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/radio/cl_plugin.lua @@ -0,0 +1,205 @@ +local PLUGIN = PLUGIN + +function PLUGIN:CreateRadioMenu() + if (IsValid(self.radioMenu)) then + self.radioMenu:Remove() + end + + local scrW, scrH = ScrW(), ScrH() + + local frame = vgui.Create("DFrame") + frame:SetSize(500, 200) + frame:SetPos(scrW/2 - 250, scrH/2 - 100) + frame:SetTitle("") + frame:SetDraggable(false) + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(panel, w, h) + -- Фон + surface.SetDrawColor(Color(13, 13, 13, 240)) + surface.DrawRect(0, 0, w, h) + + -- Обводка + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Верхняя панель + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, 40) + + -- Заголовок + draw.SimpleText("НАСТРОЙКИ РАЦИИ", "ixMenuButtonFont", w/2, 20, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + self.radioMenu = frame + + local currentFreq = self:GetFrequency(LocalPlayer()) + + local freqLabel = frame:Add("DLabel") + freqLabel:SetPos(50, 60) + freqLabel:SetSize(400, 25) + freqLabel:SetText("Текущая частота: " .. currentFreq) + freqLabel:SetFont("ixSmallFont") + freqLabel:SetTextColor(color_white) + + local freqSlider = frame:Add("DNumSlider") + freqSlider:SetPos(-231, 90) + freqSlider:SetSize(680, 40) + freqSlider:SetText("") + freqSlider:SetMin(self.minFrequency) + freqSlider:SetMax(self.maxFrequency) + freqSlider:SetDecimals(self.frequencyPrecision) + freqSlider:SetValue(currentFreq) + + -- Стилизация слайдера + freqSlider.Slider.Paint = function(panel, w, h) + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + local progress = (freqSlider:GetValue() - freqSlider:GetMin()) / (freqSlider:GetMax() - freqSlider:GetMin()) + local barWidth = w * progress + + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, barWidth, h) + end + + freqSlider.Label:SetTextColor(color_white) + freqSlider.Label:SetFont("ixSmallFont") + freqSlider.TextArea:SetTextColor(color_white) + freqSlider.TextArea:SetFont("ixSmallFont") + freqSlider.TextArea.Paint = function(panel, w, h) + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + panel:DrawTextEntryText(color_white, Color(30, 130, 255), color_white) + end + + freqSlider.OnValueChanged = function(_, value) + local format = "%0." .. PLUGIN.frequencyPrecision .. "f" + freqLabel:SetText("Текущая частота: " .. string.format(format, value)) + end + + -- Кнопка закрытия + local closeBtn = frame:Add("DButton") + closeBtn:SetPos(50, 150) + closeBtn:SetSize(150, 35) + closeBtn:SetText("") + closeBtn.Paint = function(panel, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(29, 29, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("ЗАКРЫТЬ", "ixSmallFont", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if panel:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + closeBtn.DoClick = function() + frame:Remove() + end + + -- Кнопка применения + local applyBtn = frame:Add("DButton") + applyBtn:SetPos(300, 150) + applyBtn:SetSize(150, 35) + applyBtn:SetText("") + applyBtn.Paint = function(panel, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(29, 29, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("ПРИМЕНИТЬ", "ixSmallFont", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if panel:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + applyBtn.DoClick = function() + net.Start("ixRadioSetFrequency") + net.WriteFloat(freqSlider:GetValue()) + net.SendToServer() + + frame:Remove() + + -- Уведомление об успешном применении + LocalPlayer():Notify("Частота рации изменена на: " .. string.format("%0." .. PLUGIN.frequencyPrecision .. "f", freqSlider:GetValue())) + end +end + +-- Комбинации клавиш отключены - используйте Context Menu (C) для управления рацией +--[[ +local lastPress = { + listen = 0, + transmit = 0, + menu = 0 +} +local pressCooldown = 0.2 + +hook.Add("PlayerButtonDown", "ixRadioBinds", function(ply, button) + if (ply != LocalPlayer()) then + return + end + + if (vgui.GetKeyboardFocus()) then + return + end + + local ct = CurTime() + + if (button == KEY_F2) then + if (ct - lastPress.menu < pressCooldown) then + return + end + + lastPress.menu = ct + PLUGIN:CreateRadioMenu() + elseif if (button == KEY_O and input.IsKeyDown(KEY_LSHIFT)) then + if (ct - lastPress.listen < pressCooldown) then + return + end + + lastPress.listen = ct + + net.Start("ixRadioToggleListen") + net.SendToServer() + + -- Уведомление о переключении режима прослушивания + local status = ix.plugin.list["radio"]:IsListening(LocalPlayer()) and "включено" or "выключено" + LocalPlayer():Notify("Прослушивание рации " .. status) + elseif (button == KEY_P and input.IsKeyDown(KEY_LSHIFT)) then + if (ct - lastPress.transmit < pressCooldown) then + return + end + + lastPress.transmit = ct + + net.Start("ixRadioToggleTransmit") + net.SendToServer() + + -- Уведомление о переключении режима передачи + local status = ix.plugin.list["radio"]:IsTransmitting(LocalPlayer()) and "включена" or "выключена" + LocalPlayer():Notify("Передача рации " .. status) + end +end) +--]] + +-- Команды для биндов +concommand.Add("radio_menu", function() + PLUGIN:CreateRadioMenu() +end) + +concommand.Add("radio_listen", function() + net.Start("ixRadioToggleListen") + net.SendToServer() +end) + +concommand.Add("radio_transmit", function() + net.Start("ixRadioToggleTransmit") + net.SendToServer() +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/radio/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/radio/sh_plugin.lua new file mode 100644 index 0000000..4a54989 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/radio/sh_plugin.lua @@ -0,0 +1,24 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Radio" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "Provides configurable radio communication." +PLUGIN.defaultFrequency = 100.0 +PLUGIN.minFrequency = 30.0 +PLUGIN.maxFrequency = 900.0 +PLUGIN.frequencyPrecision = 3 +PLUGIN.frequencyTolerance = 0.001 + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") + +function PLUGIN:GetFrequency(client) + return client:GetNetVar("radioFrequency", self.defaultFrequency) +end + +function PLUGIN:IsListening(client) + return client:GetNetVar("radioListen", true) +end + +function PLUGIN:IsTransmitting(client) + return client:GetNetVar("radioTransmit", true) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/radio/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/radio/sv_plugin.lua new file mode 100644 index 0000000..bd59689 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/radio/sv_plugin.lua @@ -0,0 +1,151 @@ +local PLUGIN = PLUGIN +util.AddNetworkString("ixRadioSetFrequency") +util.AddNetworkString("ixRadioToggleListen") +util.AddNetworkString("ixRadioToggleTransmit") + +function PLUGIN:SyncRadioState(client, frequency, listen, transmit) + local character = client:GetCharacter() + + if (!character) then + return + end + + frequency = math.Round(math.Clamp(frequency or self.defaultFrequency, self.minFrequency, self.maxFrequency), self.frequencyPrecision) + listen = listen and true or false + transmit = transmit and true or false + + if (!listen) then + transmit = false + end + + character:SetData("radioFrequency", frequency) + character:SetData("radioListen", listen) + character:SetData("radioTransmit", transmit) + + client:SetNetVar("radioFrequency", frequency) + client:SetNetVar("radioListen", listen) + client:SetNetVar("radioTransmit", transmit) +end + +function PLUGIN:PlayerLoadedCharacter(client, character, lastChar) + local frequency = character:GetData("radioFrequency", self.defaultFrequency) + + -- Force radio to be OFF after reconnect or character load + local listen = false + local transmit = false + + self:SyncRadioState(client, frequency, listen, transmit) +end + +net.Receive("ixRadioSetFrequency", function(_, client) + local character = client:GetCharacter() + if client:IsAdminMode() then return end + + if (!character) then + return + end + + local frequency = net.ReadFloat() + + if (!isnumber(frequency)) then + return + end + + frequency = math.Round(math.Clamp(frequency, PLUGIN.minFrequency, PLUGIN.maxFrequency), PLUGIN.frequencyPrecision) + + local listen = character:GetData("radioListen", true) + local transmit = character:GetData("radioTransmit", true) + + if (!listen) then + transmit = false + end + + PLUGIN:SyncRadioState(client, frequency, listen, transmit) + + local format = "%0." .. PLUGIN.frequencyPrecision .. "f" + client:Notify("Частота рации установлена на " .. string.format(format, frequency)) +end) + +net.Receive("ixRadioToggleListen", function(_, client) + local character = client:GetCharacter() + if client:IsAdminMode() then return end + + if (!character) then + return + end + + local listen = !character:GetData("radioListen", true) + local frequency = character:GetData("radioFrequency", PLUGIN.defaultFrequency) + local transmit = character:GetData("radioTransmit", true) + + if (!listen) then + transmit = false + end + + PLUGIN:SyncRadioState(client, frequency, listen, transmit) + client:Notify(listen and "Звук рации включен" or "Звук рации выключен") + + if (!listen and transmit) then + client:Notify("Передача микрофона выключена") + end +end) + +net.Receive("ixRadioToggleTransmit", function(_, client) + local character = client:GetCharacter() + if client:IsAdminMode() then return end + + if (!character) then + return + end + + local listen = character:GetData("radioListen", true) + local frequency = character:GetData("radioFrequency", PLUGIN.defaultFrequency) + + if (!listen) then + PLUGIN:SyncRadioState(client, frequency, listen, false) + client:Notify("Сначала включите звук рации") + return + end + + local transmit = !character:GetData("radioTransmit", true) + + PLUGIN:SyncRadioState(client, frequency, listen, transmit) + client:Notify(transmit and "Передача микрофона включена" or "Передача микрофона выключена") +end) + +function PLUGIN:PlayerCanHearPlayersVoice(listener, speaker) + if (listener == speaker) then + return + end + if listener:IsAdminMode() then return end + if speaker:IsAdminMode() then return end + + local listenerChar = listener:GetCharacter() + local speakerChar = speaker:GetCharacter() + + if (!listenerChar or !speakerChar) then + return + end + + + if (!self:IsListening(listener)) then + return + end + + if (!self:IsListening(speaker)) then + return + end + + if (!self:IsTransmitting(speaker)) then + return + end + + local lf = self:GetFrequency(listener) + local sf = self:GetFrequency(speaker) + + if (math.abs(lf - sf) > self.frequencyTolerance) then + return + end + + return true, false +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/recruit_zapret/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/recruit_zapret/sh_plugin.lua new file mode 100644 index 0000000..f0f0624 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/recruit_zapret/sh_plugin.lua @@ -0,0 +1,63 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Recruit Zapret" +PLUGIN.author = "Scripty" +PLUGIN.description = "Ограничения для новобранцев" + +local function IsRecruit(client) + local char = client:GetCharacter() + if not char then return false end + + local spec = char.GetSpec and char:GetSpec() or 0 + + return spec == 1 +end + +function PLUGIN:CanPlayerEnterVehicle(client, vehicle) + if IsRecruit(client) then + client:Notify("Новоприбывший не может садиться в транспорт.") + return false + end +end + +local lastNotify = {} + +function PLUGIN:PlayerUse(client, entity) + if not IsRecruit(client) then return end + if not IsValid(entity) then return end + if not entity:IsDoor() then return end + + local t = CurTime() + if (lastNotify[client] or 0) > t then + return false + end + lastNotify[client] = t + 0.5 + + client:Notify("Новоприбывший не может открывать двери.") + return false +end + +function PLUGIN:PlayerCanPickupWeapon(client, weapon) + if IsRecruit(client) then + client:Notify("Новоприбывший не может поднимать оружие.") + return false + end +end + +function PLUGIN:CanPlayerEquipItem(client, item) + if IsRecruit(client) and item.isWeapon then + client:Notify("Новоприбывший не может экипировать оружие.") + return false + end +end + +function PLUGIN:PlayerUseArsenal(client) + local char = client:GetCharacter() + if not char then return false end + + local spec = char.GetSpec and char:GetSpec() or 0 + if spec == 1 then + client:Notify("Новоприбывший не имеет доступа к арсеналу.") + return false + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/rpchat/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/rpchat/sh_plugin.lua new file mode 100644 index 0000000..4262889 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/rpchat/sh_plugin.lua @@ -0,0 +1,66 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "RP Chat Extras" +PLUGIN.author = "Scripty" +PLUGIN.description = "Добавляет /do, /try и алиасы /s, /y." + +hook.Add("InitializedChatClasses", "rp_chat_extras", function() + timer.Simple(0, function() + + ix.chat.Register("do", { + OnChatAdd = function(self, speaker, text) + chat.AddText(ix.config.Get("chatColor"), text) + end, + CanHear = ix.config.Get("chatRange", 280) * 2, + prefix = {"/do", "/Do"}, + description = "@cmdDo", + indicator = "chatPerforming", + deadCanChat = true + }) + + ix.chat.Register("try", { + color = Color(155, 111, 176), + CanHear = ix.config.Get("chatRange", 280), + deadCanChat = true, + prefix = {"/try", "/Try"}, + description = "@cmdTry", + indicator = "chatPerforming", + + OnChatAdd = function(self, speaker, text) + local success = math.random(0, 1) == 1 + + local resultColor = success and Color(0, 200, 0) or Color(200, 0, 0) + local resultText = success and "[Успешно]" or "[Неудачно]" + + chat.AddText( + resultColor, resultText .. " ", + self.color, speaker:Name() .. ": " .. text + ) + end + }) + + local whisper = ix.chat.classes.y + if whisper then + whisper.format = "%s шепчет \"%s\"" + whisper.description = "@cmdY" + + if istable(whisper.prefix) then + table.insert(whisper.prefix, "/y") + else + whisper.prefix = { whisper.prefix, "/y" } + end + end + + local yell = ix.chat.classes.s + if yell then + yell.format = "%s кричит \"%s\"" + yell.description = "@cmdS" + + if istable(yell.prefix) then + table.insert(yell.prefix, "/s") + else + yell.prefix = { yell.prefix, "/s" } + end + end + + end) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/salary/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/salary/sh_plugin.lua new file mode 100644 index 0000000..24d6f40 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/salary/sh_plugin.lua @@ -0,0 +1,64 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Salary" +PLUGIN.author = "Scripty" +PLUGIN.description = "Adds a periodic salary based on player rank and real-time clock." + +ix.util.Include("sv_plugin.lua") + +-- Salary configuration per rank (1-16) +-- You can adjust these values here +ix.config.Add("salary_amounts", { + [1] = 500, -- новобранец + [2] = 630, -- рядовой + [3] = 760, -- ефрейтор + [4] = 890, -- младший сержант + [5] = 1020, -- сержант + [6] = 1150, -- старший сержант + [7] = 1280, -- старшина + [8] = 1410, -- прапорщик + [9] = 1540, -- старший прапорщик + [10] = 1670, -- младший лейтенант + [11] = 1800, -- лейтенант + [12] = 1930, -- старший лейтенант + [13] = 2060, -- капитан + [14] = 2190, -- майор + [15] = 2320, -- подполковник + [16] = 2500 -- полковник +}, "Таблица выплат зарплаты в зависимости от ранга (индекс ранга => сумма).", nil, { + category = "Salary" +}) + +ix.config.Add("salary_min_playtime", 30, "Минимальное время игры (в минутах) для получения зарплаты.", nil, { + data = {min = 1, max = 60}, + category = "Salary" +}) + +-- Request from user: 00 and 30 minutes of real time +-- This will be handled in sv_plugin.lua + +ix.command.Add("CharSalary", { + description = "Принудительно выдать зарплату всем игрокам (для отыгравших время).", + privilege = "Manage Salary", + adminOnly = true, + OnRun = function(self, client) + if (SERVER) then + ix.plugin.list["salary"]:DistributeSalary(false) + end + + return "Зарплата выдана игрокам, отыгравшим положенное время." + end +}) + +ix.command.Add("CharSalaryForce", { + description = "Принудительно выдать зарплату всем игрокам (ИГНОРИРУЯ время игры).", + privilege = "Manage Salary", + superAdminOnly = true, + OnRun = function(self, client) + if (SERVER) then + ix.plugin.list["salary"]:DistributeSalary(true) + end + + return "Зарплата принудительно выдана всем игрокам." + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/salary/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/salary/sv_plugin.lua new file mode 100644 index 0000000..8cd5edd --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/salary/sv_plugin.lua @@ -0,0 +1,82 @@ +local PLUGIN = PLUGIN + +-- Tracking when the last salary "tick" happened to avoid double triggers within the same minute +PLUGIN.lastTickMinute = PLUGIN.lastTickMinute or -1 + +function PLUGIN:Tick() + local currentMinute = tonumber(os.date("%M")) + + -- Check for 00 or 30 minutes + if (currentMinute == 0 or currentMinute == 30) then + if (PLUGIN.lastTickMinute != currentMinute) then + PLUGIN.lastTickMinute = currentMinute + self:DistributeSalary() + end + else + -- Reset the tick tracker when we are not in the target minute + if (PLUGIN.lastTickMinute != -1) then + PLUGIN.lastTickMinute = -1 + end + end +end + +function PLUGIN:OnCharacterLoaded(character) + -- Store the time the character was loaded/joined if not already set + -- This helps tracking playtime for the first salary + if (!character:GetData("salary_start_time")) then + character:SetData("salary_start_time", os.time()) + end +end + +function PLUGIN:DistributeSalary(bIgnorePlaytime) + local minPlaytime = ix.config.Get("salary_min_playtime", 30) * 60 -- convert to seconds + local salaryTable = ix.config.Get("salary_amounts", {}) + local currentTime = os.time() + + for _, client in ipairs(player.GetAll()) do + local character = client:GetCharacter() + if (!character) then continue end + + local startTime = character:GetData("salary_start_time", currentTime) + local playtime = currentTime - startTime + + if (bIgnorePlaytime or playtime >= minPlaytime) then + local rank = tonumber(character:GetRank()) or 1 + + -- Hardcoded fallback table in case config is empty or missing keys in database + local defaultSalaries = { + [1] = 500, [2] = 630, [3] = 760, [4] = 890, [5] = 1020, + [6] = 1150, [7] = 1280, [8] = 1410, [9] = 1540, [10] = 1670, + [11] = 1800, [12] = 1930, [13] = 2060, [14] = 2190, [15] = 2320, [16] = 2500 + } + + -- 1. Try to get value from config first + local amount = 0 + for k, v in pairs(salaryTable) do + if (tonumber(k) == rank) then + amount = v + break + end + end + + -- 2. If config has no data for this rank (or it's fallback 500 but not rank 1), + -- use the hardcoded table in code. + if (amount == 0 or (amount == 500 and rank != 1)) then + amount = defaultSalaries[rank] or defaultSalaries[1] or 500 + end + + character:GiveMoney(amount) + character:SetData("salary_start_time", currentTime) -- Reset timer for next salary + + client:Notify("Вы получили зарплату в размере " .. ix.currency.Get(amount) .. " за вашу службу!") + else + local remaining = math.ceil((minPlaytime - playtime) / 60) + client:Notify("Вам не начислена зарплата, так как вы отыграли меньше " .. (minPlaytime / 60) .. " минут. Осталось: " .. remaining .. " мин.") + end + end +end + +-- Force reset salary start time on join just in case +function PLUGIN:PlayerLoadedCharacter(client, character, oldCharacter) + character:SetData("salary_start_time", os.time()) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/sambans/sh_config.lua b/garrysmod/gamemodes/militaryrp/plugins/sambans/sh_config.lua new file mode 100644 index 0000000..89576df --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sambans/sh_config.lua @@ -0,0 +1,21 @@ + +-- Конфигурация синхронизации банов +PLUGIN.config = PLUGIN.config or {} + +-- API настройки +PLUGIN.config.apiKey = "2Pa7LFR_i9rpg_hFHlMCG_Ri1bDMdX" +PLUGIN.config.apiHost = "https://front-team.ru" -- Замените на ваш домен +PLUGIN.config.apiEndpoint = "/api/update_ban.php" + +-- Тип банов для синхронизации +PLUGIN.config.banType = "server" -- general, server, community + +-- Логирование +PLUGIN.config.enableLogging = true + +-- Функция логирования +function PLUGIN:Log(message) + if (self.config.enableLogging) then + print(string.format("[SAM Bans Sync] %s", message)) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/sambans/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/sambans/sh_plugin.lua new file mode 100644 index 0000000..5b63526 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sambans/sh_plugin.lua @@ -0,0 +1,8 @@ + +PLUGIN.name = "SAM Bans Sync" +PLUGIN.author = "RefoselDev" +PLUGIN.description = "Синхронизация банов SAM с веб-сервером" + +ix.util.Include("sh_config.lua") +ix.util.Include("sv_plugin.lua") + diff --git a/garrysmod/gamemodes/militaryrp/plugins/sambans/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/sambans/sv_plugin.lua new file mode 100644 index 0000000..85cefab --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sambans/sv_plugin.lua @@ -0,0 +1,131 @@ + +-- Функция отправки данных на API +function PLUGIN:SendBanToAPI(steamID, action, banData) + local url = self.config.apiHost .. self.config.apiEndpoint + + local params = { + api_key = self.config.apiKey, + action = action, + user_steam_id = steamID + } + + if (action == "ban") then + params.admin_steam_id = tostring(banData.adminSteamID or "") + params.reason = tostring(banData.reason or "No reason provided") + params.ban_type = tostring(self.config.banType or "server") + params.duration = tostring(math.floor(banData.duration or 0)) + params.banned_at = tostring(math.floor(banData.bannedAt or os.time())) + + if (banData.unbanTime) then + params.unbanned_at = tostring(math.floor(banData.unbanTime)) + end + elseif (action == "unban") then + params.unbanned_at = tostring(os.time()) + end + + http.Post(url, params, + function(body) + local response = util.JSONToTable(body) + if (response and response.success) then + self:Log(string.format("%s успешно синхронизирован для %s", action == "ban" and "Бан" or "Разбан", steamID)) + else + self:Log(string.format("Ошибка синхронизации: %s", response and response.error or "Unknown error")) + end + end, + function(error) + self:Log(string.format("HTTP ошибка: %s", error)) + end + ) +end + +-- Хук на бан игрока (объект игрока) +hook.Add("SAM.BannedPlayer", "ixSAMBansSyncPlayer", function(ply, unbanDate, reason, adminSteamID) + local steamID = ply:SteamID64() + local duration = 0 + local unbanTime = nil + + -- Вычисляем длительность бана + if (unbanDate and unbanDate > 0) then + duration = unbanDate - os.time() + unbanTime = unbanDate + end + + -- Обрабатываем админа + local adminID = nil + if (adminSteamID and adminSteamID != "Console") then + if (tostring(adminSteamID):sub(1, 7) == "7656119") then + adminID = adminSteamID + else + adminID = util.SteamIDTo64(adminSteamID) + end + end + + local banData = { + adminSteamID = adminID, + reason = reason, + duration = duration, + bannedAt = os.time(), + unbanTime = unbanTime + } + + ix.plugin.list["sambans"]:SendBanToAPI(steamID, "ban", banData) +end) + +-- Хук на бан по SteamID (оффлайн игрок) +hook.Add("SAM.BannedSteamID", "ixSAMBansSyncSteamID", function(steamID, unbanDate, reason, adminSteamID) + local steamID64 = util.SteamIDTo64(steamID) + local duration = 0 + local unbanTime = nil + + -- Вычисляем длительность бана + if (unbanDate and unbanDate > 0) then + duration = unbanDate - os.time() + unbanTime = unbanDate + end + + -- Обрабатываем админа + local adminID = nil + if (adminSteamID and adminSteamID != "Console") then + if (tostring(adminSteamID):sub(1, 7) == "7656119") then + adminID = adminSteamID + else + adminID = util.SteamIDTo64(adminSteamID) + end + end + + local banData = { + adminSteamID = adminID, + reason = reason, + duration = duration, + bannedAt = os.time(), + unbanTime = unbanTime + } + + ix.plugin.list["sambans"]:SendBanToAPI(steamID64, "ban", banData) +end) + +-- Хук на разбан +hook.Add("SAM.UnbannedSteamID", "ixSAMBansSyncUnban", function(steamID, admin) + local steamID64 = util.SteamIDTo64(steamID) + + ix.plugin.list["sambans"]:SendBanToAPI(steamID64, "unban", {}) +end) + +-- Команда для теста API +concommand.Add("ix_sambans_test", function(ply) + if (IsValid(ply) and not ply:IsSuperAdmin()) then return end + + local plugin = ix.plugin.list["sambans"] + plugin:Log("Тестирование подключения к API...") + + local testData = { + adminSteamID = "76561198000000000", + reason = "Test ban from Helix plugin", + duration = 3600, + bannedAt = os.time() + } + + plugin:SendBanToAPI("76561198000000001", "ban", testData) +end) + +PLUGIN:Log("Плагин загружен. Синхронизация банов SAM активна.") diff --git a/garrysmod/gamemodes/militaryrp/plugins/scoreboard/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/scoreboard/cl_plugin.lua new file mode 100644 index 0000000..bd1d7e8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/scoreboard/cl_plugin.lua @@ -0,0 +1,460 @@ +local PLUGIN = PLUGIN + +-- Создание шрифтов +surface.CreateFont("ScoreboardHeader", { + font = "Exo 2", + size = 24, + weight = 600 +}) + +surface.CreateFont("ScoreboardPlayer", { + font = "Exo 2", + size = 22, + weight = 500 +}) + +surface.CreateFont("ScoreboardButton", { + font = "Exo 2", + size = 20, + weight = 600 +}) + +surface.CreateFont("ScoreboardButtonSmall", { + font = "Exo 2", + size = 16, + weight = 600 +}) + +-- Фоновый материал +local bgMaterial = Material("materials/ft_ui/military/vnu/charcreate/bg.png") +local logoMaterial = Material("materials/ft_ui/military/vnu/charcreate/logo.png") + +-- Переменные +PLUGIN.scoreboardFrame = nil +PLUGIN.selectedPlayer = nil + +-- Получение иконки фракции +local function GetFactionIcon(faction) + if faction == FACTION_RUSSIAN then + return Material("materials/ft_ui/military/vnu/scoreboard/rf.png") + elseif faction == FACTION_UKRAINE then + return Material("materials/ft_ui/military/vnu/scoreboard/ua.png") + end + return nil +end + +-- Создание заголовка секции +local function CreateSectionHeader(parent, y, faction) + local header = vgui.Create("DPanel", parent) + header:SetPos(200, y) + header:SetSize(1520, 40) + header.Paint = function(s, w, h) + surface.SetDrawColor(1, 54, 23) + draw.RoundedBox(5, 0, 0, w, h, Color(1, 54, 23)) + surface.SetDrawColor(1, 104, 44) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Иконка фракции + local icon = vgui.Create("DImage", header) + icon:SetPos(4, 5) + icon:SetSize(45, 30) + local mat = GetFactionIcon(faction) + if mat then + icon:SetMaterial(mat) + end + + -- Заголовки колонок + local columns = { + {text = "Имя", x = 55}, + {text = "Подразделение", x = 320}, + {text = "Специализация", x = 640}, + {text = "Звание", x = 960}, + {text = "Привилегия", x = 1200}, + {text = "Пинг", x = 1420} + } + + for _, col in ipairs(columns) do + local label = vgui.Create("DLabel", header) + label:SetPos(col.x, 5) + label:SetFont("ScoreboardHeader") + label:SetText(col.text) + label:SetTextColor(Color(255, 255, 255)) + label:SizeToContents() + end + + return header +end + +-- Создание строки игрока +local function CreatePlayerRow(parent, y, ply, isExpanded) + local height = isExpanded and 100 or 40 + + local row = vgui.Create("DButton", parent) + row:SetPos(200, y) + row:SetSize(1520, height) + row:SetText("") + row.Player = ply + + row.Paint = function(s, w, h) + local bgColor = Color(13, 13, 13, 217) + if PLUGIN.selectedPlayer == ply then + bgColor = Color(1, 54, 23, 150) + elseif s:IsHovered() then + bgColor = Color(20, 20, 20, 217) + end + + draw.RoundedBox(3, 0, 0, w, h, bgColor) + end + + row.DoClick = function() + if PLUGIN.selectedPlayer == ply then + PLUGIN.selectedPlayer = nil + else + PLUGIN.selectedPlayer = ply + end + end + + -- Проверка на противоположную фракцию + local localChar = LocalPlayer():GetCharacter() + local targetChar = ply:GetCharacter() + local isEnemyFaction = false + + if localChar and targetChar then + local localFaction = localChar:GetFaction() + local targetFaction = targetChar:GetFaction() + + local iAmAdmin = LocalPlayer().IsAdminMode and LocalPlayer():IsAdminMode() + local targetIsAdmin = (ply.IsAdminMode and ply:IsAdminMode()) or (FACTION_ADMIN and targetFaction == FACTION_ADMIN) + + isEnemyFaction = (localFaction ~= targetFaction) and not iAmAdmin and not targetIsAdmin + end + + -- Аватар (скрыт для противоположной фракции) + if not isEnemyFaction then + local avatar = vgui.Create("AvatarImage", row) + avatar:SetPos(4, 3) + avatar:SetSize(33, 34) + avatar:SetPlayer(ply, 64) + end + + -- Имя (скрыт для противоположной фракции) + local name = vgui.Create("DLabel", row) + name:SetPos(55, 7) + name:SetFont("ScoreboardPlayer") + name:SetText(isEnemyFaction and "???" or ply:Nick()) + name:SetTextColor(Color(255, 255, 255)) + name:SizeToContents() + + -- Подразделение + local char = ply:GetCharacter() + if char then + local podrID = char:GetPodr() + local factionTable = ix.faction.Get(char:GetFaction()) + local podrName = "—" + + if not isEnemyFaction and factionTable and factionTable.Podr and factionTable.Podr[podrID] then + podrName = factionTable.Podr[podrID].name or "—" + elseif isEnemyFaction then + podrName = "???" + end + + local podr = vgui.Create("DLabel", row) + podr:SetPos(320, 7) + podr:SetFont("ScoreboardPlayer") + podr:SetText(podrName) + podr:SetTextColor(Color(255, 255, 255)) + podr:SizeToContents() + + -- Специализация + local specID = char:GetSpec() + local specName = "—" + + if not isEnemyFaction and factionTable and factionTable.Spec and factionTable.Spec[specID] then + specName = factionTable.Spec[specID].name or "—" + elseif isEnemyFaction then + specName = "???" + end + + local spec = vgui.Create("DLabel", row) + spec:SetPos(640, 7) + spec:SetFont("ScoreboardPlayer") + spec:SetText(specName) + spec:SetTextColor(Color(255, 255, 255)) + spec:SizeToContents() + + -- Звание + local rankName = "—" + + if not isEnemyFaction then + rankName = ply.GetRankName and ply:GetRankName() or "—" + else + rankName = "???" + end + + local rank = vgui.Create("DLabel", row) + rank:SetPos(960, 7) + rank:SetFont("ScoreboardPlayer") + rank:SetText(rankName) + rank:SetTextColor(Color(255, 255, 255)) + rank:SizeToContents() + end + + -- Привилегия + local usergroup = ply:GetUserGroup() + local priv = vgui.Create("DLabel", row) + priv:SetPos(1200, 7) + priv:SetFont("ScoreboardPlayer") + priv:SetText(PLUGIN.NiceUserGroupName[usergroup] or usergroup) + priv:SetTextColor(Color(255, 255, 255)) + priv:SizeToContents() + + -- Пинг + local ping = vgui.Create("DLabel", row) + ping:SetPos(1420, 7) + ping:SetFont("ScoreboardPlayer") + ping:SetText(tostring(ply:Ping())) + ping:SetTextColor(Color(255, 255, 255)) + ping:SizeToContents() + + -- Кнопки действий (появляются при клике на игрока) + if isExpanded then + local buttons = { + {text = "Скопировать SteamID", x = 204, w = 200, action = "copy_steamid"}, + {text = "Скопировать SteamID64", x = 414, w = 200, action = "copy_steamid64"}, + {text = "Скопировать никнейм", x = 624, w = 200, action = "copy_nick"}, + {text = "Открыть профиль", x = 834, w = 200, action = "open_profile"} + } + + for _, btn in ipairs(buttons) do + local button = vgui.Create("DButton", row) + button:SetPos(btn.x, 47) + button:SetSize(btn.w, 28) + button:SetText("") + button.Paint = function(s, w, h) + surface.SetDrawColor(1, 54, 23) + draw.RoundedBox(3, 0, 0, w, h, Color(1, 54, 23)) + surface.SetDrawColor(1, 104, 44) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 20) + surface.DrawRect(0, 0, w, h) + end + + draw.SimpleText(btn.text, "ScoreboardButtonSmall", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + button.DoClick = function() + if btn.action == "copy_steamid" then + SetClipboardText(ply:SteamID()) + chat.AddText(Color(0, 255, 0), "SteamID скопирован: " .. ply:SteamID()) + elseif btn.action == "copy_steamid64" then + SetClipboardText(ply:SteamID64()) + chat.AddText(Color(0, 255, 0), "SteamID64 скопирован: " .. ply:SteamID64()) + elseif btn.action == "copy_nick" then + SetClipboardText(ply:Nick()) + chat.AddText(Color(0, 255, 0), "Никнейм скопирован: " .. ply:Nick()) + elseif btn.action == "open_profile" then + ply:ShowProfile() + chat.AddText(Color(0, 255, 0), "Открыт профиль игрока: " .. ply:Nick()) + end + end + end + end + + return row +end + +-- Создание скроллпанели +local function CreatePlayerList(parent) + local scroll = vgui.Create("DScrollPanel", parent) + scroll:SetPos(0, 179) + scroll:SetSize(1920, 754) + + -- Кастомный скроллбар + local vbar = scroll:GetVBar() + vbar:SetWide(7) + vbar:SetPos(1714, 0) + + function vbar:Paint(w, h) + draw.RoundedBox(10, 0, 0, 3, h, Color(0, 0, 0, 191)) + end + + function vbar.btnUp:Paint(w, h) end + function vbar.btnDown:Paint(w, h) end + + function vbar.btnGrip:Paint(w, h) + draw.RoundedBox(10, 0, 0, w, h, Color(1, 104, 44)) + end + + return scroll +end + +-- Обновление списка игроков +local function UpdatePlayerList(scroll) + scroll:Clear() + + local yPos = 0 + local players = player.GetAll() + + -- Группируем игроков по фракциям + local factions = {} + local factionKeys = {} + for _, ply in ipairs(players) do + local char = ply:GetCharacter() + if char then + local faction = char:GetFaction() + if not factions[faction] then + factions[faction] = {} + table.insert(factionKeys, faction) + end + table.insert(factions[faction], ply) + end + end + + -- Сортируем фракции (Админы всегда первые) + table.sort(factionKeys, function(a, b) + if a == FACTION_ADMIN then return true end + if b == FACTION_ADMIN then return false end + return a < b + end) + + -- Отображаем каждую фракцию + for _, faction in ipairs(factionKeys) do + local plyList = factions[faction] + + -- Сортировка игроков внутри фракции + table.sort(plyList, function(a, b) + local charA = a:GetCharacter() + local charB = b:GetCharacter() + + if charA and charB then + local podrA = charA:GetPodr() or 0 + local podrB = charB:GetPodr() or 0 + + -- Веса для подразделений: 8 (Штаб) первый, 1 (Новоприбывшие) последний + local function GetPodrWeight(id) + if id == 8 then return 0 end + if id == 1 then return 999 end + return id + end + + local weightA = GetPodrWeight(podrA) + local weightB = GetPodrWeight(podrB) + + if weightA ~= weightB then + return weightA < weightB + end + + -- Если подразделения одинаковые, сортируем по рангу + local rankA = charA:GetRank() or 0 + local rankB = charB:GetRank() or 0 + + if rankA ~= rankB then + return rankA > rankB -- Высший ранг выше + end + end + + return a:Nick() < b:Nick() + end) + + -- Заголовок секции + local header = CreateSectionHeader(scroll, yPos, faction) + yPos = yPos + 49 + + -- Игроки + for _, ply in ipairs(plyList) do + local isExpanded = (PLUGIN.selectedPlayer == ply) + local row = CreatePlayerRow(scroll, yPos, ply, isExpanded) + yPos = yPos + (isExpanded and 103 or 43) + end + + yPos = yPos + 10 -- Отступ между секциями + end +end + +-- Создание скорборда +function PLUGIN:CreateScoreboard() + if IsValid(self.scoreboardFrame) then + return + end + + local scrW, scrH = ScrW(), ScrH() + + self.scoreboardFrame = vgui.Create("DFrame") + self.scoreboardFrame:SetSize(scrW, scrH) + self.scoreboardFrame:SetPos(0, 0) + self.scoreboardFrame:SetTitle("") + self.scoreboardFrame:SetDraggable(false) + self.scoreboardFrame:ShowCloseButton(false) + self.scoreboardFrame:SetKeyboardInputEnabled(false) + self.scoreboardFrame:SetMouseInputEnabled(true) + self.scoreboardFrame:MakePopup() + + gui.EnableScreenClicker(true) + + self.scoreboardFrame.Paint = function(s, w, h) + -- Затемнение + surface.SetDrawColor(10, 10, 10, 140) + surface.DrawRect(0, 0, w, h) + end + + -- Верхняя панель + local topBar = vgui.Create("DPanel", self.scoreboardFrame) + topBar:SetPos(0, 0) + topBar:SetSize(scrW, 100) + topBar.Paint = function(s, w, h) + surface.SetDrawColor(13, 13, 13) + surface.DrawRect(0, 0, w, h) + end + + -- Логотип + local logo = vgui.Create("DImage", topBar) + logo:SetPos((scrW - 150) / 2, 23) + logo:SetSize(150, 53) + if logoMaterial then + logo:SetMaterial(logoMaterial) + end + + -- Список игроков + local scroll = CreatePlayerList(self.scoreboardFrame) + self.scoreboardScroll = scroll + + UpdatePlayerList(scroll) + + -- Таймер обновления + timer.Create("ScoreboardUpdate", 1, 0, function() + if IsValid(self.scoreboardFrame) then + UpdatePlayerList(scroll) + else + timer.Remove("ScoreboardUpdate") + end + end) +end + +-- Удаление скорборда +function PLUGIN:RemoveScoreboard() + if IsValid(self.scoreboardFrame) then + self.scoreboardFrame:Remove() + self.scoreboardFrame = nil + end + + gui.EnableScreenClicker(false) + + timer.Remove("ScoreboardUpdate") + self.selectedPlayer = nil +end + +-- Хуки +function PLUGIN:ScoreboardShow() + self:CreateScoreboard() +end + +function PLUGIN:ScoreboardHide() + self:RemoveScoreboard() +end + +-- Закрытие при смерти +function PLUGIN:OnCharacterDeleted() + self:RemoveScoreboard() +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/scoreboard/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/scoreboard/sh_plugin.lua new file mode 100644 index 0000000..e74d76a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/scoreboard/sh_plugin.lua @@ -0,0 +1,24 @@ +PLUGIN.name = "Custom Scoreboard" +PLUGIN.author = "Server" +PLUGIN.description = "Custom TAB menu scoreboard" + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") + +PLUGIN.NiceUserGroupName = { + ["superadmin"] = "Супер Администратор", + ["teh.admin"] = "Тех. Администратор", + ["projectteam"] = "Команда Проекта", + ["curator"] = "Куратор", + ["disp"] = "Дисциплинёр", + ["sudo-curator"] = "Судо-Куратор", + ["assistant"] = "Ассистент Куратора", + ["st.admin"] = "Старший Администратор", + ["admin"] = "Администратор", + ["st.event"] = "Старший Ивентолог", + ["event"] = "Ивентолог", + ["media"] = "Медиа-Партнёр", + ["sponsor"] = "Спонсор", + ["vip"] = "VIP", + ["user"] = "Игрок" +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/scoreboard/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/scoreboard/sv_plugin.lua new file mode 100644 index 0000000..b6b412e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/scoreboard/sv_plugin.lua @@ -0,0 +1,47 @@ +local PLUGIN = PLUGIN + +-- Обработка действий администратора +net.Receive("ixScoreboardAction", function(len, client) + if not IsValid(client) or not client:IsAdmin() then return end + + local action = net.ReadString() + local target = net.ReadEntity() + + if not IsValid(target) or not target:IsPlayer() then return end + + if action == "goto" then + client:SetPos(target:GetPos()) + client:Notify("Телепортирован к " .. target:Nick()) + + elseif action == "bring" then + target:SetPos(client:GetPos() + client:GetForward() * 100) + client:Notify("Телепортирован " .. target:Nick()) + + elseif action == "respawn" then + target:Spawn() + client:Notify("Респавн " .. target:Nick()) + + elseif action == "return" then + local char = target:GetCharacter() + if char and char.lastPos then + target:SetPos(char.lastPos) + client:Notify("Возвращен " .. target:Nick()) + end + + elseif action == "whitelist" then + -- Открыть меню whitelist для целевого игрока + -- Реализация зависит от вашей системы whitelist + + elseif action == "mute" then + target:SetNWBool("IsMuted", not target:GetNWBool("IsMuted", false)) + client:Notify((target:GetNWBool("IsMuted") and "Замучен " or "Размучен ") .. target:Nick()) + + elseif action == "ban" then + -- Открыть меню бана + -- Реализация через SAM или другую систему администрирования + + elseif action == "kick" then + target:Kick("Kicked by " .. client:Nick()) + client:Notify("Кикнут " .. target:Nick()) + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/serverlogs/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/serverlogs/cl_plugin.lua new file mode 100644 index 0000000..a9188b1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/serverlogs/cl_plugin.lua @@ -0,0 +1,343 @@ +local PLUGIN = PLUGIN + +-- Цветовая схема +local COLOR_BG_DARK = Color(3, 5, 4) +local COLOR_BG_MEDIUM = Color(8, 12, 10) +local COLOR_BG_LIGHT = Color(12, 18, 14) +local COLOR_PRIMARY = Color(27, 94, 32) +local COLOR_PRIMARY_DARK = Color(15, 60, 18) +local COLOR_ACCENT = Color(56, 102, 35) +local COLOR_TEXT_PRIMARY = Color(165, 214, 167) +local COLOR_TEXT_SECONDARY = Color(102, 187, 106) +local COLOR_BORDER = Color(15, 60, 18, 60) + +-- Локальное хранилище логов +local clientLogs = {} +local isLoadingLogs = false + +-- Сетевой получатель для открытия меню (из команды Helix) +net.Receive("ixServerLogs_OpenMenu", function() + PLUGIN:OpenLogsMenu() +end) + +-- Получение логов с сервера +function PLUGIN:RequestLogs(filters) + if isLoadingLogs then return end + + isLoadingLogs = true + clientLogs = {} + + net.Start("ixServerLogs_Request") + net.WriteTable(filters or {}) + net.SendToServer() +end + +-- Получение ответа с логами +net.Receive("ixServerLogs_Response", function() + local isFirst = net.ReadUInt(2) == 1 + local logs = net.ReadTable() + + if isFirst then + clientLogs = {} + end + + for _, log in ipairs(logs) do + table.insert(clientLogs, log) + end + + isLoadingLogs = false + + -- Обновляем UI если открыто + if IsValid(PLUGIN.logsFrame) and IsValid(PLUGIN.logsList) then + PLUGIN:UpdateLogsList() + end +end) + +-- Главное меню логов +function PLUGIN:OpenLogsMenu() + if IsValid(self.logsFrame) then + self.logsFrame:Remove() + end + + local scrW, scrH = ScrW(), ScrH() + local frame = vgui.Create("DFrame") + frame:SetSize(scrW * 0.9, scrH * 0.85) + frame:Center() + frame:SetTitle("") + frame:SetDraggable(true) + frame:ShowCloseButton(false) + frame:MakePopup() + + frame.Paint = function(s, w, h) + -- Основной фон + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK) + + -- Верхняя панель + draw.RoundedBoxEx(8, 0, 0, w, 60, COLOR_BG_MEDIUM, true, true, false, false) + + -- Акцентная линия + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawRect(0, 60, w, 3) + + -- Заголовок + draw.SimpleText("◆ ЛОГИ СЕРВЕРА ◆", "ixMenuButtonFont", w/2, 20, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("СИСТЕМА МОНИТОРИНГА СОБЫТИЙ", "ixSmallFont", w/2, 43, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Рамка + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + -- Кнопка закрытия + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetSize(40, 40) + closeBtn:SetPos(frame:GetWide() - 50, 10) + closeBtn:SetText("") + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(200, 50, 50) or Color(150, 50, 50) + draw.RoundedBox(4, 0, 0, w, h, col) + + surface.SetDrawColor(255, 255, 255) + surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7) + surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7) + end + closeBtn.DoClick = function() + frame:Close() + end + + -- Панель фильтров + local filterPanel = vgui.Create("DPanel", frame) + filterPanel:SetSize(250, frame:GetTall() - 80) + filterPanel:SetPos(10, 70) + filterPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Заголовок фильтров + local filterHeader = vgui.Create("DLabel", filterPanel) + filterHeader:SetPos(10, 10) + filterHeader:SetFont("ixSmallFont") + filterHeader:SetText("⚙ ФИЛЬТРЫ") + filterHeader:SetTextColor(COLOR_TEXT_PRIMARY) + filterHeader:SizeToContents() + + -- Категории + local categoryLabel = vgui.Create("DLabel", filterPanel) + categoryLabel:SetPos(10, 40) + categoryLabel:SetFont("DermaDefault") + categoryLabel:SetText("Категория:") + categoryLabel:SetTextColor(COLOR_TEXT_SECONDARY) + categoryLabel:SizeToContents() + + local categoryBox = vgui.Create("DComboBox", filterPanel) + categoryBox:SetPos(10, 60) + categoryBox:SetSize(230, 30) + categoryBox:SetValue("Все категории") + categoryBox:AddChoice("Все категории") + + for catID, cat in SortedPairs(self.LOG_CATEGORIES) do + categoryBox:AddChoice(cat.icon .. " " .. cat.name, catID) + end + + categoryBox.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Поиск + local searchLabel = vgui.Create("DLabel", filterPanel) + searchLabel:SetPos(10, 100) + searchLabel:SetFont("DermaDefault") + searchLabel:SetText("Поиск:") + searchLabel:SetTextColor(COLOR_TEXT_SECONDARY) + searchLabel:SizeToContents() + + local searchBox = vgui.Create("DTextEntry", filterPanel) + searchBox:SetPos(10, 120) + searchBox:SetSize(230, 30) + searchBox:SetPlaceholderText("Введите текст для поиска...") + searchBox.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(COLOR_TEXT_PRIMARY, COLOR_ACCENT, COLOR_TEXT_PRIMARY) + end + + -- Лимит + local limitLabel = vgui.Create("DLabel", filterPanel) + limitLabel:SetPos(10, 160) + limitLabel:SetFont("DermaDefault") + limitLabel:SetText("Количество записей:") + limitLabel:SetTextColor(COLOR_TEXT_SECONDARY) + limitLabel:SizeToContents() + + local limitSlider = vgui.Create("DNumSlider", filterPanel) + limitSlider:SetPos(10, 180) + limitSlider:SetSize(230, 30) + limitSlider:SetMin(10) + limitSlider:SetMax(500) + limitSlider:SetDecimals(0) + limitSlider:SetValue(100) + limitSlider:SetText("") + + -- Кнопка применить фильтры + local applyBtn = vgui.Create("DButton", filterPanel) + applyBtn:SetPos(10, 230) + applyBtn:SetSize(230, 40) + applyBtn:SetText("") + applyBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("🔍 ПРИМЕНИТЬ", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + applyBtn.DoClick = function() + local filters = { + limit = limitSlider:GetValue() + } + + local _, catID = categoryBox:GetSelected() + if catID then + filters.category = catID + end + + local searchText = searchBox:GetValue() + if searchText and searchText ~= "" then + filters.search = searchText + end + + self:RequestLogs(filters) + end + + -- Кнопка очистить логи + local clearBtn = vgui.Create("DButton", filterPanel) + clearBtn:SetPos(10, 280) + clearBtn:SetSize(230, 40) + clearBtn:SetText("") + clearBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(200, 50, 50) or Color(150, 50, 50) + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + draw.SimpleText("🗑 ОЧИСТИТЬ ЛОГИ", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + clearBtn.DoClick = function() + Derma_Query( + "Вы уверены что хотите очистить все логи?", + "Подтверждение", + "Да", + function() + net.Start("ixServerLogs_Clear") + net.SendToServer() + clientLogs = {} + self:UpdateLogsList() + end, + "Нет" + ) + end + + -- Панель логов + local logsPanel = vgui.Create("DPanel", frame) + logsPanel:SetSize(frame:GetWide() - 280, frame:GetTall() - 80) + logsPanel:SetPos(270, 70) + logsPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + -- Заголовок логов + local logsHeader = vgui.Create("DPanel", logsPanel) + logsHeader:SetSize(logsPanel:GetWide(), 40) + logsHeader:Dock(TOP) + logsHeader.Paint = function(s, w, h) + draw.RoundedBoxEx(6, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false) + + draw.SimpleText("📋 ЗАПИСИ ЛОГОВ", "ixSmallFont", 10, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText("Всего: " .. #clientLogs, "DermaDefault", w - 10, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + -- Список логов + local logsList = vgui.Create("DScrollPanel", logsPanel) + logsList:Dock(FILL) + logsList:DockMargin(5, 5, 5, 5) + + local sbar = logsList:GetVBar() + sbar:SetHideButtons(true) + function sbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK) + end + function sbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY) + end + + self.logsFrame = frame + self.logsList = logsList + self.logsHeader = logsHeader + + -- Загружаем логи + self:RequestLogs({limit = 100}) +end + +-- Обновление списка логов +function PLUGIN:UpdateLogsList() + if not IsValid(self.logsList) then return end + + self.logsList:Clear() + + if #clientLogs == 0 then + local noLogs = vgui.Create("DLabel", self.logsList) + noLogs:SetText("Логи не найдены") + noLogs:SetFont("ixSmallFont") + noLogs:SetTextColor(COLOR_TEXT_SECONDARY) + noLogs:SizeToContents() + noLogs:Dock(TOP) + noLogs:DockMargin(10, 10, 10, 0) + return + end + + -- Обновляем заголовок + if IsValid(self.logsHeader) then + self.logsHeader.Paint = function(s, w, h) + draw.RoundedBoxEx(6, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false) + + draw.SimpleText("📋 ЗАПИСИ ЛОГОВ", "ixSmallFont", 10, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText("Всего: " .. #clientLogs, "DermaDefault", w - 10, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + + for _, log in ipairs(clientLogs) do + local logEntry = vgui.Create("DPanel", self.logsList) + logEntry:SetTall(60) + logEntry:Dock(TOP) + logEntry:DockMargin(5, 5, 5, 0) + + local category = self.LOG_CATEGORIES[log.category] + local categoryColor = category and category.color or color_white + + logEntry.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_LIGHT) + + -- Боковая полоска категории + draw.RoundedBoxEx(4, 0, 0, 5, h, categoryColor, true, false, true, false) + + -- Рамка + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + -- Время + draw.SimpleText(log.timeString, "DermaDefault", 15, 8, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + -- Категория + local catText = category and (category.icon .. " " .. category.name) or log.category + draw.SimpleText(catText, "DermaDefault", 15, 25, categoryColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + -- Сообщение + draw.SimpleText(log.message, "ixSmallFont", 15, 43, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/serverlogs/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/serverlogs/sh_plugin.lua new file mode 100644 index 0000000..86769f7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/serverlogs/sh_plugin.lua @@ -0,0 +1,161 @@ +PLUGIN.name = "Server Logs" +PLUGIN.author = "Server" +PLUGIN.description = "Система логирования событий сервера для администраторов" + +-- Категории логов +PLUGIN.LOG_CATEGORIES = { + COMBAT = { + name = "Боевые действия", + color = Color(255, 100, 100), + icon = "⚔" + }, + ADMIN = { + name = "Администрирование", + color = Color(255, 200, 50), + icon = "⚙" + }, + ECONOMY = { + name = "Экономика", + color = Color(100, 200, 100), + icon = "💰" + }, + CHAT = { + name = "Чат", + color = Color(150, 150, 255), + icon = "💬" + }, + CONNECTION = { + name = "Подключения", + color = Color(200, 200, 200), + icon = "🔌" + }, + INVENTORY = { + name = "Инвентарь", + color = Color(255, 150, 100), + icon = "🎒" + }, + VEHICLE = { + name = "Техника", + color = Color(100, 150, 255), + icon = "🚗" + }, + CHARACTER = { + name = "Персонажи", + color = Color(150, 255, 150), + icon = "👤" + } +} + +-- Типы событий +PLUGIN.LOG_TYPES = { + -- Боевые + PLAYER_DEATH = {category = "COMBAT", description = "Смерть игрока"}, + PLAYER_HURT = {category = "COMBAT", description = "Получение урона"}, + NPC_DEATH = {category = "COMBAT", description = "Смерть NPC"}, + + -- Административные + ADMIN_COMMAND = {category = "ADMIN", description = "Админ команда"}, + PLAYER_KICK = {category = "ADMIN", description = "Кик игрока"}, + PLAYER_BAN = {category = "ADMIN", description = "Бан игрока"}, + PLAYER_WARN = {category = "ADMIN", description = "Предупреждение"}, + PLAYER_ARREST = {category = "ADMIN", description = "Арест игрока"}, + PLAYER_TIE = {category = "ADMIN", description = "Связывание игрока"}, + HANDCUFF_RESTRAIN = {category = "ADMIN", description = "Надевание наручников"}, + HANDCUFF_UNRESTRAIN = {category = "ADMIN", description = "Снятие наручников"}, + PLAYER_JAIL = {category = "ADMIN", description = "Отправка в тюрьму"}, + PLAYER_RELEASE = {category = "ADMIN", description = "Освобождение из тюрьмы"}, + PLAYER_UNARREST = {category = "ADMIN", description = "Снятие ареста"}, + + -- Экономика + MONEY_TRANSACTION = {category = "ECONOMY", description = "Денежная транзакция"}, + ITEM_PURCHASE = {category = "ECONOMY", description = "Покупка предмета"}, + SUPPLY_CHANGE = {category = "ECONOMY", description = "Изменение снабжения"}, + FACTION_SUPPLY_ADD = {category = "ECONOMY", description = "Пополнение снабжения фракции"}, + FACTION_SUPPLY_USE = {category = "ECONOMY", description = "Расход снабжения фракции"}, + VEHICLE_POINTS_ADD = {category = "ECONOMY", description = "Пополнение баллов техники"}, + VEHICLE_POINTS_USE = {category = "ECONOMY", description = "Расход баллов техники"}, + + -- Чат + CHAT_MESSAGE = {category = "CHAT", description = "Сообщение в чат"}, + COMMAND_EXECUTED = {category = "CHAT", description = "Выполнена команда"}, + + -- Подключения + PLAYER_CONNECT = {category = "CONNECTION", description = "Подключение"}, + PLAYER_DISCONNECT = {category = "CONNECTION", description = "Отключение"}, + PLAYER_SPAWN = {category = "CONNECTION", description = "Спавн игрока"}, + + -- Инвентарь + ITEM_PICKUP = {category = "INVENTORY", description = "Поднятие предмета"}, + ITEM_DROP = {category = "INVENTORY", description = "Выброс предмета"}, + ITEM_USE = {category = "INVENTORY", description = "Использование предмета"}, + WEAPON_SPAWN = {category = "INVENTORY", description = "Выдача оружия из арсенала"}, + WEAPON_DONATE = {category = "INVENTORY", description = "Выдача донат оружия"}, + + -- Техника + VEHICLE_SPAWN = {category = "VEHICLE", description = "Вызов техники"}, + VEHICLE_RETURN = {category = "VEHICLE", description = "Возврат техники"}, + + -- Персонажи + CHARACTER_CREATE = {category = "CHARACTER", description = "Создание персонажа"}, + CHARACTER_DELETE = {category = "CHARACTER", description = "Удаление персонажа"}, + CHARACTER_SWITCH = {category = "CHARACTER", description = "Смена персонажа"}, + + -- Захват точек + CAPTURE_START = {category = "COMBAT", description = "Начало захвата точки"}, + CAPTURE_COMPLETE = {category = "COMBAT", description = "Точка захвачена"}, + CAPTURE_LOST = {category = "COMBAT", description = "Точка потеряна"}, + CAPTURE_REWARD = {category = "ECONOMY", description = "Награда за захват"}, + + -- Отряды + SQUAD_CREATE = {category = "CHARACTER", description = "Создание отряда"}, + SQUAD_DELETE = {category = "CHARACTER", description = "Расформирование отряда"}, + SQUAD_INVITE = {category = "CHARACTER", description = "Приглашение в отряд"}, + SQUAD_JOIN = {category = "CHARACTER", description = "Вступление в отряд"}, + SQUAD_LEAVE = {category = "CHARACTER", description = "Выход из отряда"}, + SQUAD_KICK = {category = "CHARACTER", description = "Исключение из отряда"} +} + +ix.log = ix.log or {} +ix.log.stored = ix.log.stored or {} + +-- Конфигурация +ix.config.Add("logMaxEntries", 1000, "Максимальное количество логов в памяти", nil, { + data = {min = 100, max = 5000}, + category = "server" +}) + +ix.config.Add("logSaveToFile", true, "Сохранять логи в файл", nil, { + category = "server" +}) + +ix.config.Add("logFilePath", "helix_logs", "Папка для сохранения логов", nil, { + category = "server" +}) + +-- Локализация команд +if CLIENT then + ix.lang.AddTable("russian", { + serverLogsOpened = "Панель логов открыта", + serverLogsCleared = "Логи сервера очищены", + serverLogsDisplayed = "Показано логов: %s", + serverLogsSearchResults = "Найдено логов: %s", + cmdViewLogs = "Открыть панель логов", + cmdClearLogs = "Очистить логи", + cmdLastLogs = "Показать последние логи", + cmdSearchLogs = "Поиск в логах" + }) + + ix.lang.AddTable("english", { + serverLogsOpened = "Logs panel opened", + serverLogsCleared = "Server logs cleared", + serverLogsDisplayed = "Logs displayed: %s", + serverLogsSearchResults = "Logs found: %s", + cmdViewLogs = "Open logs panel", + cmdClearLogs = "Clear logs", + cmdLastLogs = "Show recent logs", + cmdSearchLogs = "Search logs" + }) +end + +ix.util.Include("cl_plugin.lua") +ix.util.Include("sv_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/serverlogs/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/serverlogs/sv_plugin.lua new file mode 100644 index 0000000..3d0f964 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/serverlogs/sv_plugin.lua @@ -0,0 +1,455 @@ +local PLUGIN = PLUGIN + +util.AddNetworkString("ixServerLogs_Request") +util.AddNetworkString("ixServerLogs_Response") +util.AddNetworkString("ixServerLogs_Clear") +util.AddNetworkString("ixServerLogs_OpenMenu") + +-- Функция проверки доступа к логам через CAMI +local function HasLogAccess(client) + return AdminPrivs[client:GetUserGroup()] +end + +-- Инициализация системы логов +function PLUGIN:InitializeLogSystem() + self.logs = self.logs or {} + self.logFile = nil + + -- Создаем папку для логов + if ix.config.Get("logSaveToFile", true) then + local logPath = ix.config.Get("logFilePath", "helix_logs") + if not file.Exists(logPath, "DATA") then + file.CreateDir(logPath) + end + + -- Создаем файл для текущей сессии + local date = os.date("%Y-%m-%d_%H-%M-%S") + self.logFile = logPath .. "/log_" .. date .. ".txt" + + file.Write(self.logFile, "=== Server Logs Started: " .. os.date("%Y-%m-%d %H:%M:%S") .. " ===\n") + end +end + +-- Добавление лога +function PLUGIN:AddLog(logType, message, target, data) + if not self.LOG_TYPES[logType] then + print("[ServerLogs] Unknown log type: " .. tostring(logType)) + return + end + + local logEntry = { + id = #self.logs + 1, + type = logType, + category = self.LOG_TYPES[logType].category, + message = message, + target = target and { + name = IsValid(target) and target:Nick() or tostring(target), + steamid = IsValid(target) and target:SteamID() or "N/A" + } or nil, + data = data or {}, + timestamp = os.time(), + timeString = os.date("%H:%M:%S") + } + + table.insert(self.logs, logEntry) + + -- Ограничиваем количество логов в памяти + local maxEntries = ix.config.Get("logMaxEntries", 1000) + if #self.logs > maxEntries then + table.remove(self.logs, 1) + end + + -- Сохраняем в файл + if self.logFile then + local logString = string.format("[%s][%s] %s\n", + logEntry.timeString, + self.LOG_CATEGORIES[logEntry.category].name, + message + ) + file.Append(self.logFile, logString) + end + + -- Выводим в консоль + MsgC(self.LOG_CATEGORIES[logEntry.category].color, "[LOG] ", Color(255, 255, 255), message .. "\n") +end + +-- Получение логов с фильтрацией +function PLUGIN:GetLogs(filters) + filters = filters or {} + local result = {} + + for i = #self.logs, 1, -1 do + local log = self.logs[i] + local match = true + + -- Фильтр по категории + if filters.category and log.category ~= filters.category then + match = false + end + + -- Фильтр по типу + if filters.type and log.type ~= filters.type then + match = false + end + + -- Фильтр по игроку + if filters.player and log.target and log.target.steamid ~= filters.player then + match = false + end + + -- Фильтр по времени + if filters.startTime and log.timestamp < filters.startTime then + match = false + end + + if filters.endTime and log.timestamp > filters.endTime then + match = false + end + + -- Фильтр по тексту + if filters.search and not string.find(string.lower(log.message), string.lower(filters.search)) then + match = false + end + + if match then + table.insert(result, log) + + -- Ограничиваем количество результатов + if filters.limit and #result >= filters.limit then + break + end + end + end + + return result +end + +-- Сетевая обработка запроса логов +net.Receive("ixServerLogs_Request", function(len, client) + if not HasLogAccess(client) then return end + + local filters = net.ReadTable() + local logs = PLUGIN:GetLogs(filters) + + -- Отправляем логи порциями + local chunkSize = 50 + for i = 1, #logs, chunkSize do + local chunk = {} + for j = i, math.min(i + chunkSize - 1, #logs) do + table.insert(chunk, logs[j]) + end + + net.Start("ixServerLogs_Response") + net.WriteUInt(i == 1 and 1 or 0, 2) -- Первый пакет или нет + net.WriteTable(chunk) + net.Send(client) + end + + -- Отправляем финальный пакет если логов нет + if #logs == 0 then + net.Start("ixServerLogs_Response") + net.WriteUInt(1, 2) + net.WriteTable({}) + net.Send(client) + end +end) + +-- Очистка логов +net.Receive("ixServerLogs_Clear", function(len, client) + if not client:IsSuperAdmin() then return end + + PLUGIN.logs = {} + PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client) +end) + +-- ====================== +-- ХУКИ ДЛЯ ЛОГИРОВАНИЯ +-- ====================== + +-- Смерть игрока +function PLUGIN:PlayerDeath(victim, inflictor, attacker) + local attackerName = "Мир" + local attackerSteamID = "world" + local weapon = "unknown" + + if IsValid(attacker) and attacker:IsPlayer() then + attackerName = attacker:Nick() + attackerSteamID = attacker:SteamID() + weapon = IsValid(attacker:GetActiveWeapon()) and attacker:GetActiveWeapon():GetClass() or "unknown" + end + + local message = "" + + if IsValid(attacker) and attacker:IsPlayer() then + if attacker == victim then + message = victim:Nick() .. " покончил с собой" + else + message = attackerName .. " убил " .. victim:Nick() .. " используя " .. weapon + end + else + message = victim:Nick() .. " умер" + end + + self:AddLog("PLAYER_DEATH", message, victim, { + attacker = attackerSteamID, + weapon = weapon + }) +end + +-- Получение урона +function PLUGIN:PlayerHurt(victim, attacker, health, damage) + if not IsValid(attacker) or not attacker:IsPlayer() or attacker == victim then return end + + local message = string.format("%s нанёс %d урона игроку %s (осталось %d HP)", + attacker:Nick(), damage, victim:Nick(), health) + + self:AddLog("PLAYER_HURT", message, victim, { + attacker = attacker:SteamID(), + damage = damage, + health = health + }) +end + +-- Подключение игрока +function PLUGIN:PlayerInitialSpawn(client) + local message = string.format("%s подключился к серверу (SteamID: %s)", + client:Nick(), client:SteamID()) + + self:AddLog("PLAYER_CONNECT", message, client) +end + +-- Отключение игрока +function PLUGIN:PlayerDisconnected(client) + local message = string.format("%s отключился от сервера", client:Nick()) + + self:AddLog("PLAYER_DISCONNECT", message, client) +end + +-- Спавн игрока +function PLUGIN:PlayerSpawn(client) + if not client:GetCharacter() then return end + + local message = string.format("%s заспавнился", client:Nick()) + + self:AddLog("PLAYER_SPAWN", message, client) +end + +-- Сообщения в чат +function PLUGIN:PlayerSay(client, text, teamChat) + local chatType = teamChat and "[TEAM]" or "[ALL]" + local message = string.format("%s %s: %s", chatType, client:Nick(), text) + + self:AddLog("CHAT_MESSAGE", message, client, { + text = text, + team = teamChat + }) +end + +-- Создание персонажа +function PLUGIN:OnCharacterCreated(client, character) + local message = string.format("%s создал персонажа '%s'", + client:Nick(), character:GetName()) + + self:AddLog("CHARACTER_CREATE", message, client, { + characterID = character:GetID(), + characterName = character:GetName() + }) +end + +-- Удаление персонажа +function PLUGIN:PreCharacterDeleted(client, character) + local message = string.format("%s удалил персонажа '%s'", + client:Nick(), character:GetName()) + + self:AddLog("CHARACTER_DELETE", message, client, { + characterID = character:GetID(), + characterName = character:GetName() + }) +end + +-- Смена персонажа +function PLUGIN:PlayerLoadedCharacter(client, character, oldCharacter) + if oldCharacter then + local message = string.format("%s сменил персонажа с '%s' на '%s'", + client:Nick(), oldCharacter:GetName(), character:GetName()) + + self:AddLog("CHARACTER_SWITCH", message, client, { + oldCharacter = oldCharacter:GetName(), + newCharacter = character:GetName() + }) + end +end + +-- Покупка предмета +function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell) + local action = isSell and "продал" or "купил" + local message = string.format("%s %s предмет за %d", + client:Nick(), action, price) + + self:AddLog("ITEM_PURCHASE", message, client, { + vendor = vendor, + price = price, + sell = isSell + }) +end + +-- Использование предмета +function PLUGIN:OnItemTransferred(item, curInv, inventory) + if item.player then + local message = string.format("%s переместил предмет '%s'", + item.player:Nick(), item:GetName()) + + self:AddLog("INVENTORY", message, item.player, { + item = item:GetName(), + itemID = item:GetID() + }) + end +end + +-- Инициализация при загрузке плагина +function PLUGIN:InitPostEntity() + self:InitializeLogSystem() + self:AddLog("ADMIN_COMMAND", "Система логов инициализирована") +end + +-- ====================== +-- КОМАНДЫ HELIX +-- ====================== + +-- Регистрация CAMI разрешения +if CAMI then + CAMI.RegisterPrivilege({ + Name = "Helix - View Server Logs", + MinAccess = "admin", + Description = "Позволяет просматривать логи сервера" + }) + + CAMI.RegisterPrivilege({ + Name = "Helix - Clear Server Logs", + MinAccess = "superadmin", + Description = "Позволяет очищать логи сервера" + }) + + CAMI.RegisterPrivilege({ + Name = "Helix - Use Handcuffs", + MinAccess = "admin", + Description = "Позволяет использовать наручники" + }) +end + +-- Команда для просмотра логов +ix.command.Add("ViewLogs", { + description = "Открыть панель просмотра логов сервера", + OnCheckAccess = function(self, client) + return HasLogAccess(client) + end, + OnRun = function(self, client) + -- Отправляем команду на открытие меню на клиенте + net.Start("ixServerLogs_OpenMenu") + net.Send(client) + + return "@serverLogsOpened" + end +}) + +-- Альтернативная команда +ix.command.Add("ServerLogs", { + description = "Открыть панель просмотра логов сервера", + OnCheckAccess = function(self, client) + return HasLogAccess(client) + end, + OnRun = function(self, client) + -- Отправляем команду на открытие меню на клиенте + net.Start("ixServerLogs_OpenMenu") + net.Send(client) + + return "@serverLogsOpened" + end +}) + +-- Команда для очистки логов +ix.command.Add("ClearLogs", { + description = "Очистить все логи сервера", + superAdminOnly = true, + OnCheckAccess = function(self, client) + -- Проверка через CAMI + if CAMI then + return CAMI.PlayerHasAccess(client, "Helix - Clear Server Logs", function(allowed) + return allowed + end) + end + -- Fallback на стандартную проверку суперадмина + return client:IsSuperAdmin() + end, + OnRun = function(self, client) + PLUGIN.logs = {} + PLUGIN:AddLog("ADMIN_COMMAND", client:Nick() .. " очистил логи сервера", client) + return "@serverLogsCleared" + end +}) + +-- Команда для просмотра последних логов в консоли +ix.command.Add("LastLogs", { + description = "Показать последние 20 логов в консоли", + adminOnly = true, + arguments = { + ix.type.number, + }, + argumentNames = {"количество"}, + OnCheckAccess = function(self, client) + -- Проверка через CAMI + if CAMI then + return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed) + return allowed + end) + end + return client:IsAdmin() + end, + OnRun = function(self, client, count) + count = math.Clamp(count or 20, 1, 100) + + local logs = PLUGIN:GetLogs({limit = count}) + + client:ChatPrint("=== Последние " .. #logs .. " логов ===") + for i, log in ipairs(logs) do + local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name + local targetInfo = log.target and (" [" .. log.target.name .. "]") or "" + client:ChatPrint(string.format("[%s][%s]%s %s", + log.timeString, categoryName, targetInfo, log.message)) + end + + return "@serverLogsDisplayed", #logs + end +}) + +-- Команда для поиска в логах +ix.command.Add("SearchLogs", { + description = "Найти логи по тексту или игроку", + adminOnly = true, + arguments = { + ix.type.text, + }, + argumentNames = {"поисковый запрос"}, + OnCheckAccess = function(self, client) + -- Проверка через CAMI + if CAMI then + return CAMI.PlayerHasAccess(client, "Helix - View Server Logs", function(allowed) + return allowed + end) + end + return client:IsAdmin() + end, + OnRun = function(self, client, search) + local logs = PLUGIN:GetLogs({search = search, limit = 50}) + + client:ChatPrint("=== Найдено логов: " .. #logs .. " ===") + for i, log in ipairs(logs) do + local categoryName = PLUGIN.LOG_CATEGORIES[log.category].name + local targetInfo = log.target and (" [" .. log.target.name .. "]") or "" + client:ChatPrint(string.format("[%s][%s]%s %s", + log.timeString, categoryName, targetInfo, log.message)) + end + + return "@serverLogsSearchResults", #logs + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/serverwhitelist.lua b/garrysmod/gamemodes/militaryrp/plugins/serverwhitelist.lua new file mode 100644 index 0000000..03e7966 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/serverwhitelist.lua @@ -0,0 +1,57 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Whitelist" +PLUGIN.author = "Refosel" +PLUGIN.description = "На скорую руку" + +local WhiteList = false +local beta = true + +local allowplayers = { + ["STEAM_0:0:216403892"] = true, -- Refosel + ["STEAM_0:1:614583782"] = true, -- Бибослав + ["STEAM_0:0:121926226"] = true, -- Олег + ["STEAM_0:0:612937862"] = true, -- Бугор + ["STEAM_0:1:196106795"] = true, -- Акуленок + ["STEAM_0:0:136760122"] = true, -- Прохор + ["STEAM_0:0:102882540"] = true, -- Мурка + ["STEAM_0:0:38428498"] = true, -- Куд + ["STEAM_0:1:624282080"] = true, -- Арка + ["STEAM_0:1:190680623"] = true, -- ItzTomber + ["STEAM_0:1:102153490"] = true, -- Scripty + ["STEAM_0:0:584005207"] = true -- Ksanex (Оксана) +} + +local betatesters = { + ["STEAM_0:1:549687868"] = true, -- Islad + ["STEAM_0:0:225712601"] = true, -- gratecg + ["STEAM_0:1:498228022"] = true, -- t1mushk3n x gymbro + ["STEAM_0:1:555346726"] = true, -- Немец + ["STEAM_0:1:446567529"] = true, -- Morphling + ["STEAM_0:1:55975235"] = true, -- Rusich + ["STEAM_0:1:527158246"] = true, -- longuegay + ["STEAM_0:0:590020674"] = true, -- vexxx31. + ["STEAM_0:0:232956470"] = true, -- Гун + ["STEAM_0:0:704833454"] = true, -- Samson + ["STEAM_0:1:171971554"] = true, -- чечон + ["STEAM_0:1:193498479"] = true, -- Француз Дали + ["STEAM_0:0:168263382"] = true, -- Француз Fayzen + ["STEAM_0:0:57222315"] = true, -- Солнышко + ["STEAM_0:0:243955299"] = true, -- морозов (куратор сервера) + ["STEAM_0:1:606711709"] = true +} + +if (SERVER) then + hook.Add("CheckPassword", "FT_ServWhitelist", function(s64) + local s = util.SteamIDFrom64(s64) + if WhiteList then + if allowplayers[s] then + return true + elseif beta and betatesters[s] then + return true + else + return false, "FT Team\n-Сервер находится на технических работах.\n-Следите за новостями на нашем сайте\nС уважением команда, FT Team." + end + end + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/shop/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/shop/cl_plugin.lua new file mode 100644 index 0000000..47eae49 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/shop/cl_plugin.lua @@ -0,0 +1,371 @@ +local PLUGIN = PLUGIN + +ix.util.Include("sh_config.lua") + +-- Цветовая схема (как в арсенале) +local COLOR_BG_DARK = Color(3, 5, 4) +local COLOR_BG_MEDIUM = Color(8, 12, 10) +local COLOR_BG_LIGHT = Color(12, 18, 14) +local COLOR_PRIMARY = Color(27, 94, 32) +local COLOR_PRIMARY_DARK = Color(15, 60, 18) +local COLOR_ACCENT = Color(56, 102, 35) +local COLOR_TEXT_PRIMARY = Color(165, 214, 167) +local COLOR_TEXT_SECONDARY = Color(174, 213, 129) +local COLOR_DANGER = Color(229, 57, 53) +local COLOR_WARNING = Color(255, 193, 7) +local COLOR_BORDER = Color(46, 125, 50, 100) + +surface.CreateFont("ixShopPriceFont", { + font = "Roboto", + size = 28, + weight = 900, + antialias = true +}) + +surface.CreateFont("ixShopNameFont", { + font = "Roboto", + size = 20, + weight = 700, + antialias = true +}) + +-- Вспомогательная функция для рисования кругов +if not draw.Circle then + function draw.Circle(x, y, radius, color) + local segmentCount = math.max(16, radius) + surface.SetDrawColor(color or color_white) + + local circle = {} + for i = 0, segmentCount do + local angle = math.rad((i / segmentCount) * 360) + table.insert(circle, { + x = x + math.cos(angle) * radius, + y = y + math.sin(angle) * radius + }) + end + surface.DrawPoly(circle) + end +end + +if not surface.DrawCircle then + function surface.DrawCircle(x, y, radius, color) + draw.Circle(x, y, radius, color) + end +end + +-- Получение меню магазина +net.Receive("ixShop_OpenMenu", function() + local shopType = net.ReadString() + local shopData = net.ReadTable() + + PLUGIN:OpenShopMenu(shopType, shopData) +end) + +-- Создание меню магазина +function PLUGIN:OpenShopMenu(shopType, shopData) + if IsValid(self.shopFrame) then + self.shopFrame:Remove() + end + + local scrW, scrH = ScrW(), ScrH() + local frame = vgui.Create("DFrame") + frame:SetSize(math.min(1200, scrW - 100), math.min(800, scrH - 100)) + frame:Center() + frame:SetTitle("") + frame:SetDraggable(true) + frame:ShowCloseButton(false) + frame:MakePopup() + frame:SetDeleteOnClose(true) + self.shopFrame = frame + + -- Градиентный фон с зеленым оттенком (как в арсенале) + frame.Paint = function(s, w, h) + -- Основной фон + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawRect(0, 0, w, h) + + -- Градиент сверху + local gradHeight = 200 + for i = 0, gradHeight do + local alpha = (1 - i/gradHeight) * 40 + surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha) + surface.DrawRect(0, i, w, 1) + end + + -- Верхняя панель + surface.SetDrawColor(COLOR_BG_MEDIUM) + surface.DrawRect(0, 0, w, 80) + + -- Акцентная линия + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawRect(0, 80, w, 3) + + -- Декоративные элементы + surface.SetDrawColor(COLOR_BORDER) + for i = 0, w, 150 do + surface.DrawRect(i, 0, 1, 80) + end + + -- Заголовок + draw.SimpleText("◆ " .. (shopData.name or "МАГАЗИН") .. " ◆", "ixMenuButtonFont", w/2, 28, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText((shopData.description or ""), "ixSmallFont", w/2, 55, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Кнопка закрытия + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetText("") + closeBtn:SetSize(38, 38) + closeBtn:SetPos(frame:GetWide() - 50, 12) + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7) + + -- Фон кнопки + draw.RoundedBox(4, 0, 0, w, h, col) + + -- Внешняя рамка + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Крестик + surface.SetDrawColor(COLOR_TEXT_PRIMARY) + surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7) + surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30)) + end + end + closeBtn.DoClick = function() frame:Close() end + + -- Боковая панель категорий + local sidePanel = vgui.Create("DPanel", frame) + sidePanel:SetSize(250, frame:GetTall() - 110) + sidePanel:SetPos(20, 90) + sidePanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local categoryList = vgui.Create("DScrollPanel", sidePanel) + categoryList:Dock(FILL) + categoryList:DockMargin(5, 5, 5, 5) + + -- Панель товаров (основная) + local contentPanel = vgui.Create("DPanel", frame) + contentPanel:SetSize(frame:GetWide() - 300, frame:GetTall() - 110) + contentPanel:SetPos(280, 90) + contentPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local itemList = vgui.Create("DScrollPanel", contentPanel) + itemList:SetSize(contentPanel:GetWide() - 20, contentPanel:GetTall() - 20) + itemList:SetPos(10, 10) + + local function LoadCategory(catID) + itemList:Clear() + local data = PLUGIN.shops[catID] + if not data then return end + + local items = data.items or {} + if table.Count(items) == 0 then + local noItems = vgui.Create("DLabel", itemList) + noItems:SetText("В этой категории пока нет товаров.") + noItems:SetFont("ixMediumFont") + noItems:SetTextColor(Color(200, 50, 50)) + noItems:SizeToContents() + noItems:SetPos(itemList:GetWide()/2 - noItems:GetWide()/2, 100) + return + end + + for itemID, itemData in SortedPairs(items) do + local ixItem = ix.item.list[itemID] + if not ixItem then continue end + + local itemPanel = vgui.Create("DPanel", itemList) + itemPanel:SetSize(itemList:GetWide() - 20, 100) + itemPanel:Dock(TOP) + itemPanel:DockMargin(0, 0, 10, 8) + + local isHovered = false + itemPanel.Paint = function(s, w, h) + local bgColor = COLOR_BG_LIGHT + if isHovered then + bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10) + end + draw.RoundedBox(6, 0, 0, w, h, bgColor) + draw.RoundedBoxEx(6, 0, 0, 5, h, COLOR_PRIMARY, true, false, true, false) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + itemPanel.OnCursorEntered = function() isHovered = true end + itemPanel.OnCursorExited = function() isHovered = false end + + -- Иконка + local itemIcon = vgui.Create("DModelPanel", itemPanel) + itemIcon:SetSize(80, 80) + itemIcon:SetPos(15, 10) + itemIcon:SetFOV(30) + + function itemIcon:LayoutEntity(ent) + return + end + + if ixItem.model then + itemIcon:SetModel(ixItem.model) + + itemIcon.LayoutEntity = function() end + + local ent = itemIcon:GetEntity() + if IsValid(ent) then + local mins, maxs = ent:GetRenderBounds() + local size = math.max(maxs.x - mins.x, maxs.y - mins.y, maxs.z - mins.z) + local center = (mins + maxs) / 2 + + if shopType == "consumables" then + itemIcon:SetFOV(18) + itemIcon:SetCamPos(center + Vector(size * 0.6, size * 0.6, size * 0.4)) + itemIcon:SetLookAt(center) + + elseif shopType == "ammo_basic" or shopType == "ammo_special" then + itemIcon:SetFOV(45) + itemIcon:SetCamPos(center + Vector(size * 2.2, size * 2.2, size * 1.2)) + itemIcon:SetLookAt(center) + + else + itemIcon:SetFOV(30) + itemIcon:SetCamPos(center + Vector(size * 1.4, size * 1.4, size * 0.8)) + itemIcon:SetLookAt(center) + end + end + end + + -- Название + local nameLabel = vgui.Create("DLabel", itemPanel) + nameLabel:SetText(ixItem.name or itemID) + nameLabel:SetFont("ixShopNameFont") + nameLabel:SetTextColor(COLOR_TEXT_PRIMARY) + nameLabel:SetPos(110, 25) + nameLabel:SizeToContents() + + -- Цена + local price = itemData.price or 0 + local priceLabel = vgui.Create("DLabel", itemPanel) + priceLabel:SetText(price .. " ₽") + priceLabel:SetFont("ixShopPriceFont") + priceLabel:SetTextColor(COLOR_ACCENT) + priceLabel:SetPos(110, 55) + priceLabel:SizeToContents() + + -- Кнопка покупки + local buyButton = vgui.Create("DButton", itemPanel) + buyButton:SetSize(120, 40) + buyButton:SetPos(itemPanel:GetWide() - 135, 30) + buyButton:SetText("") + buyButton.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY + draw.RoundedBox(6, 0, 0, w, h, col) + draw.SimpleText("КУПИТЬ ►", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + buyButton.DoClick = function() + net.Start("ixShop_BuyItem") + net.WriteString(catID) + net.WriteString(itemID) + net.SendToServer() + end + end + end + + -- Заполнение списка категорий + for id, data in SortedPairsByMemberValue(PLUGIN.shops, "name") do + local catBtn = categoryList:Add("DButton") + catBtn:SetText(data.name or id) + catBtn:SetFont("ixSmallFont") + catBtn:Dock(TOP) + catBtn:SetTall(45) + catBtn:DockMargin(0, 0, 0, 5) + catBtn:SetTextColor(COLOR_TEXT_PRIMARY) + + catBtn.Paint = function(s, w, h) + local active = (shopType == id) + local col = active and COLOR_PRIMARY or COLOR_BG_LIGHT + if s:IsHovered() then col = COLOR_ACCENT end + + draw.RoundedBox(4, 0, 0, w, h, col) + if active then + draw.RoundedBox(0, 0, 0, 4, h, COLOR_TEXT_PRIMARY) + end + end + + catBtn.DoClick = function() + shopType = id + LoadCategory(id) + end + end + + -- Загружаем начальную категорию + LoadCategory(shopType) +end + +-- Добавление опций в C-Menu (свойства сущности) +properties.Add("ixShopModel", { + MenuLabel = "Изменить модель NPC", + Order = 999, + MenuIcon = "icon16/user_edit.png", + + Filter = function(self, ent, ply) + if (ent:GetClass() != "ix_shop_npc") then return false end + if (!ply:IsAdmin()) then return false end + return true + end, + + Action = function(self, ent) + Derma_StringRequest( + "Изменить модель NPC", + "Введите путь к модели:", + ent:GetModel(), + function(text) + net.Start("ixShop_UpdateModel") + net.WriteEntity(ent) + net.WriteString(text) + net.SendToServer() + end + ) + end +}) + +properties.Add("ixShopType", { + MenuLabel = "Изменить тип магазина", + Order = 1000, + MenuIcon = "icon16/cart_edit.png", + + Filter = function(self, ent, ply) + if (ent:GetClass() != "ix_shop_npc") then return false end + if (!ply:IsAdmin()) then return false end + return true + end, + + MenuOpen = function(self, option, ent, ply) + local plugin = ix.plugin.Get("shop") + if (not plugin) then return end + + local subMenu = option:AddSubMenu() + for typeID, data in pairs(plugin.shops) do + local target = subMenu:AddOption(data.name or typeID, function() + net.Start("ixShop_UpdateType") + net.WriteEntity(ent) + net.WriteString(typeID) + net.SendToServer() + end) + target:SetIcon("icon16/tag_blue.png") + end + end, + + Action = function(self, ent) + -- Опции обрабатываются в MenuOpen + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/shop/entities/entities/ix_shop_npc.lua b/garrysmod/gamemodes/militaryrp/plugins/shop/entities/entities/ix_shop_npc.lua new file mode 100644 index 0000000..3a283e1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/shop/entities/entities/ix_shop_npc.lua @@ -0,0 +1,87 @@ +ENT.Type = "anim" +ENT.PrintName = "Магазин NPC" +ENT.Category = "[FT] Магазины" +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.bNoPersist = true +ENT.AutomaticFrameAdvance = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "ShopType") +end + +if SERVER then + function ENT:Initialize() + self:SetModel("models/humans/group01/male_01.mdl") + self:PhysicsInit(SOLID_BBOX) + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_BBOX) + self:SetUseType(SIMPLE_USE) + self:SetCollisionGroup(COLLISION_GROUP_NPC) + + -- Установка bbox для корректной коллизии + self:SetCollisionBounds(Vector(-16, -16, 0), Vector(16, 16, 72)) + + -- Дефолтный тип магазина + self:SetShopType("consumables") + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + + -- Анимация простоя + local sequence = self:LookupSequence("idle_subtle") + if sequence > 0 then + self:ResetSequence(sequence) + end + end + + function ENT:Use(activator, caller) + if not IsValid(activator) or not activator:IsPlayer() then return end + + local shopType = self:GetShopType() + local plugin = ix.plugin.Get("shop") + local shopData = plugin and plugin.shops[shopType] + + if not shopData then + activator:Notify("Этот магазин не настроен или не имеет товаров (ID: " .. tostring(shopType) .. ")") + return + end + + net.Start("ixShop_OpenMenu") + net.WriteString(shopType) + net.WriteTable(shopData) + net.Send(activator) + end + + function ENT:Think() + self:NextThink(CurTime()) + return true + end +else + function ENT:Draw() + self:DrawModel() + + local pos = self:GetPos() + Vector(0, 0, 85) + local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90) + + local distance = LocalPlayer():GetPos():Distance(self:GetPos()) + if distance > 300 then return end + + local shopType = self:GetShopType() + local plugin = ix.plugin.Get("shop") + local shopData = plugin and plugin.shops[shopType] + + if not shopData then return end + + cam.Start3D2D(pos, ang, 0.1) + -- Название магазина + draw.SimpleText(shopData.name, "DermaLarge", 0, 0, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Подсказка + draw.SimpleText("Нажмите E для взаимодействия", "DermaDefault", 0, 25, Color(200, 200, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/shop/sh_config.lua b/garrysmod/gamemodes/militaryrp/plugins/shop/sh_config.lua new file mode 100644 index 0000000..e41a326 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/shop/sh_config.lua @@ -0,0 +1,298 @@ +PLUGIN.shops = PLUGIN.shops or {} + +-- ═══════════════════════════════════════════════════════════ +-- МАГАЗИНЫ ОБВЕСОВ TACRP +-- ═══════════════════════════════════════════════════════════ + +-- Магазин оптики и прицелов +PLUGIN.shops["optics"] = { + name = "Оптика и прицелы", + description = "Прицелы, коллиматоры и тактические устройства", + items = { + -- Оптика + ["optic_rds"] = {price = 400}, + ["optic_holographic"] = {price = 500}, + ["optic_rmr"] = {price = 500}, + ["optic_rds2"] = {price = 450}, + ["optic_okp7"] = {price = 600}, + ["optic_rmr_rifle"] = {price = 550}, + ["optic_shortdot"] = {price = 700}, + ["optic_elcan"] = {price = 700}, + ["optic_acog"] = {price = 800}, + ["optic_8x"] = {price = 1000}, + ["optic_irons"] = {price = 300}, + -- Специфичные прицелы + ["optic_ak_kobra"] = {price = 700}, + ["optic_galil"] = {price = 650}, + ["optic_m16a2_colt"] = {price = 700}, + ["optic_ar_colt"] = {price = 900}, + ["optic_pso1"] = {price = 1200}, + ["optic_ak_pso1"] = {price = 1100}, + ["optic_k98_zf42"] = {price = 1000}, + ["optic_xm8_4x"] = {price = 1100}, + ["optic_xm8_6x"] = {price = 1300}, + ["optic_xm8_8x"] = {price = 1500}, + ["optic_m1_scope"] = {price = 850}, + ["optic_delisle_scope"] = {price = 950}, + -- Тактические устройства + ["tac_flashlight"] = {price = 200}, + ["tac_laser"] = {price = 300}, + ["tac_combo"] = {price = 450}, + ["tac_dmic"] = {price = 500}, + ["tac_rangefinder"] = {price = 1200}, + ["tac_spreadgauge"] = {price = 800}, + } +} + +-- Магазин дульных устройств +PLUGIN.shops["muzzle"] = { + name = "Дульные устройства", + description = "Глушители, компенсаторы и тормоза", + items = { + -- Глушители + ["muzz_silencer"] = {price = 800}, + ["muzz_supp_compact"] = {price = 750}, + ["muzz_supp_weighted"] = {price = 750}, + ["muzz_supp_pbs"] = {price = 850}, + ["muzz_supp_assassin"] = {price = 1500}, + -- Компенсаторы + ["muzz_pistol_comp"] = {price = 500}, + ["muzz_comp_mac10"] = {price = 550}, + ["muzz_comp_usp"] = {price = 550}, + ["muzz_ak_comp"] = {price = 600}, + ["muzz_comp_io_m14"] = {price = 650}, + -- Стволы + ["muzz_hbar"] = {price = 600}, + ["muzz_lbar"] = {price = 500}, + ["muzz_ak_booster"] = {price = 550}, + -- Тормоза + ["muzz_brake_aggressor"] = {price = 600}, + ["muzz_brake_breaching"] = {price = 700}, + ["muzz_brake_concussive"] = {price = 700}, + -- Специфичные + ["muzz_tec9_shroud"] = {price = 550}, + } +} + +-- Магазин рукояток и аксессуаров +PLUGIN.shops["accessories"] = { + name = "Рукоятки и аксессуары", + description = "Рукоятки, приклады, сошки и другие аксессуары", + items = { + -- Рукоятки + ["acc_ergo"] = {price = 450}, + ["acc_dual_ergo"] = {price = 550}, + ["acc_dual_quickdraw"] = {price = 500}, + ["acc_quickdraw"] = {price = 450}, + ["acc_skel"] = {price = 400}, + ["acc_dual_skel"] = {price = 650}, + -- Сошки и приклады + ["acc_bipod"] = {price = 500}, + ["acc_brace"] = {price = 600}, + ["acc_foldstock"] = {price = 600}, + ["acc_foldstock2"] = {price = 550}, + ["acc_cheekrest"] = {price = 400}, + ["acc_pad"] = {price = 400}, + ["acc_sling"] = {price = 350}, + -- Прочее + ["acc_conceal"] = {price = 300}, + ["acc_duffelbag"] = {price = 800}, + ["acc_extendedbelt"] = {price = 900}, + ["acc_ak74_poly"] = {price = 500}, + -- Расширенные магазины + ["acc_extmag_pistol"] = {price = 500}, + ["acc_extmag_pistol2"] = {price = 550}, + ["acc_extmag_rifle"] = {price = 550}, + ["acc_extmag_rifle2"] = {price = 600}, + ["acc_extmag_smg"] = {price = 500}, + ["acc_extmag_shotgun"] = {price = 600}, + ["acc_extmag_shotgun_mag"] = {price = 700}, + ["acc_extmag_shotgun_tube"] = {price = 650}, + ["acc_extmag_dual"] = {price = 700}, + ["acc_extmag_dual2"] = {price = 750}, + ["acc_extmag_dualsmg"] = {price = 700}, + ["acc_extmag_auto5"] = {price = 650}, + ["acc_extmag_sniper"] = {price = 800}, + } +} + +-- Магазин патронов (основные) +PLUGIN.shops["ammo_basic"] = { + name = "Боеприпасы - Основные", + description = "Универсальные патроны для пистолетов, винтовок и дробовиков", + items = { + -- Универсальные + ["ammo_pistol_ap"] = {price = 600}, + ["ammo_pistol_hollowpoints"] = {price = 500}, + ["ammo_pistol_match"] = {price = 400}, + ["ammo_pistol_headshot"] = {price = 1500}, + ["ammo_rifle_match"] = {price = 450}, + ["ammo_rifle_jhp"] = {price = 500}, + ["ammo_magnum"] = {price = 700}, + ["ammo_subsonic"] = {price = 550}, + ["ammo_surplus"] = {price = 200}, + ["ammo_tmj"] = {price = 450}, + -- Дробовик + ["ammo_shotgun_bird"] = {price = 300}, + ["ammo_shotgun_slugs"] = {price = 500}, + ["ammo_shotgun_slugs2"] = {price = 550}, + ["ammo_shotgun_mag"] = {price = 550}, + ["ammo_shotgun_breach"] = {price = 400}, + ["ammo_shotgun_dragon"] = {price = 1100}, + ["ammo_shotgun_frag"] = {price = 900}, + ["ammo_shotgun_triple"] = {price = 1000}, + ["ammo_shotgun_triple2"] = {price = 1100}, + ["ammo_shotgun_minishell"] = {price = 600}, + } +} + +-- Магазин патронов (специальные) +PLUGIN.shops["ammo_special"] = { + name = "Боеприпасы - Специальные", + description = "Гранаты 40mm, RPG, Stinger и экзотические патроны", + items = { + -- 40mm гранаты + ["ammo_40mm_3gl"] = {price = 1200}, + ["ammo_40mm_buck"] = {price = 800}, + ["ammo_40mm_gas"] = {price = 900}, + ["ammo_40mm_heat"] = {price = 1500}, + ["ammo_40mm_impact"] = {price = 1000}, + ["ammo_40mm_lvg"] = {price = 950}, + ["ammo_40mm_ratshot"] = {price = 700}, + ["ammo_40mm_smoke"] = {price = 600}, + ["ammo_40mm_heal"] = {price = 2000}, + -- AMR патроны + ["ammo_amr_hv"] = {price = 1500}, + ["ammo_amr_ratshot"] = {price = 1200}, + ["ammo_amr_saphe"] = {price = 1800}, + -- RPG + ["ammo_rpg_improvised"] = {price = 800}, + ["ammo_rpg_mortar"] = {price = 1400}, + ["ammo_rpg_ratshot"] = {price = 1000}, + ["ammo_rpg_harpoon"] = {price = 1100}, + -- Stinger ракеты + ["ammo_stinger_saam"] = {price = 2500}, + ["ammo_stinger_qaam"] = {price = 2800}, + ["ammo_stinger_4aam"] = {price = 3500}, + ["ammo_stinger_apers"] = {price = 2000}, + -- Специфичные + ["ammo_ak12_762"] = {price = 600}, + ["ammo_usp_9mm"] = {price = 700}, + ["ammo_r700_300winmag"] = {price = 900}, + ["ammo_star15_300blk"] = {price = 850}, + ["ammo_star15_50beo"] = {price = 1200}, + ["ammo_1858_45colt"] = {price = 750}, + ["ammo_1858_36perc"] = {price = 600}, + -- Экзотические + ["ammo_roulette"] = {price = 3000}, + ["ammo_buckshotroulette"] = {price = 2500}, + ["ammo_ks23_flashbang"] = {price = 1200}, + ["ammo_ks23_flashbang_top"] = {price = 1300}, + ["ammo_gyrojet_ratshot"] = {price = 900}, + ["ammo_gyrojet_pipe"] = {price = 1200}, + ["ammo_gyrojet_lv"] = {price = 800}, + ["ammo_gyrojet_he"] = {price = 1400}, + } +} + +-- Магазин болтов и триггеров +PLUGIN.shops["modifications"] = { + name = "Модификации оружия", + description = "Болты, триггеры и специальные модификации", + items = { + -- Болты + ["bolt_fine"] = {price = 500}, + ["bolt_greased"] = {price = 400}, + ["bolt_heavy"] = {price = 600}, + ["bolt_light"] = {price = 450}, + ["bolt_tactical"] = {price = 550}, + ["bolt_refurbished"] = {price = 300}, + ["bolt_rough"] = {price = 250}, + ["bolt_surplus"] = {price = 200}, + ["bolt_af2011_alt"] = {price = 800}, + -- Триггеры базовые + ["trigger_burst"] = {price = 600}, + ["trigger_burst2"] = {price = 700}, + ["trigger_burstauto"] = {price = 850}, + ["trigger_semi"] = {price = 400}, + ["trigger_comp"] = {price = 700}, + ["trigger_comp2"] = {price = 800}, + ["trigger_hair"] = {price = 650}, + ["trigger_heavy"] = {price = 500}, + ["trigger_heavy2"] = {price = 550}, + ["trigger_straight"] = {price = 450}, + ["trigger_wide"] = {price = 400}, + ["trigger_wide2"] = {price = 500}, + ["trigger_tactical"] = {price = 550}, + ["trigger_slam"] = {price = 800}, + ["trigger_slam2"] = {price = 900}, + ["trigger_dualstage"] = {price = 750}, + ["trigger_frcd"] = {price = 900}, + ["trigger_frcd2"] = {price = 950}, + -- Триггеры специальные + ["trigger_akimbo"] = {price = 1500}, + ["trigger_hair_akimbo"] = {price = 1600}, + ["trigger_vp70_auto"] = {price = 1200}, + ["trigger_vp70_semi"] = {price = 600}, + ["trigger_dual_uzis_semi"] = {price = 1500}, + -- Прочее + ["toploader_stripper_clip"] = {price = 400}, + ["tac_1858_spin"] = {price = 700}, + } +} + +-- Магазин перков +PLUGIN.shops["perks"] = { + name = "Навыки и перки", + description = "Улучшения персонажа и холодного оружия", + items = { + -- Перки персонажа + ["perk_aim"] = {price = 1000}, + ["perk_hipfire"] = {price = 800}, + ["perk_reload"] = {price = 900}, + ["perk_speed"] = {price = 850}, + ["perk_melee"] = {price = 700}, + ["perk_shock"] = {price = 950}, + ["perk_throw"] = {price = 600}, + ["perk_blindfire"] = {price = 750}, + ["perk_mlg"] = {price = 2000}, + -- Улучшения холодного оружия + ["melee_boost_all"] = {price = 1500}, + ["melee_boost_str"] = {price = 800}, + ["melee_boost_agi"] = {price = 750}, + ["melee_boost_int"] = {price = 700}, + ["melee_boost_lifesteal"] = {price = 1200}, + ["melee_boost_momentum"] = {price = 900}, + ["melee_boost_afterimage"] = {price = 1000}, + ["melee_boost_shock"] = {price = 1100}, + } +} + +-- ═══════════════════════════════════════════════════════════ +-- МАГАЗИН РАСХОДНИКОВ (CONSUMABLES) +-- ═══════════════════════════════════════════════════════════ + +-- ═══════════════════════════════════════════════════════════ +-- МАГАЗИН ПО УМОЛЧАНИЮ +-- ═══════════════════════════════════════════════════════════ + +PLUGIN.shops["drones"] = { + name = "Дроны", + description = "Различные модели дронов и аксессуары к ним", + items = { + ["swep_drone"] = {price = 30000}, + ["swep_drone_grenade"] = {price = 25000}, + } +} + +PLUGIN.shops["consumables"] = { + name = "Расходники", + description = "Еда, напитки, медикаменты и прочие расходные материалы", + items = { + ["bread"] = {price = 20}, + ["canned"] = {price = 40}, + ["milk"] = {price = 40}, + ["sausage"] = {price = 35}, + ["water"] = {price = 20}, + } +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/shop/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/shop/sh_plugin.lua new file mode 100644 index 0000000..1a92edb --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/shop/sh_plugin.lua @@ -0,0 +1,7 @@ +PLUGIN.name = "Shop System" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "Система магазинов с NPC" + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") +ix.util.Include("sh_config.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/shop/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/shop/sv_plugin.lua new file mode 100644 index 0000000..78c6ea0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/shop/sv_plugin.lua @@ -0,0 +1,170 @@ +local PLUGIN = PLUGIN + +util.AddNetworkString("ixShop_OpenMenu") +util.AddNetworkString("ixShop_BuyItem") +util.AddNetworkString("ixShop_UpdateModel") +util.AddNetworkString("ixShop_UpdateType") + +ix.util.Include("sh_config.lua") + +function PLUGIN:SaveData() + local data = {} + + for _, npc in ipairs(ents.FindByClass("ix_shop_npc")) do + if IsValid(npc) then + data[#data + 1] = { + pos = npc:GetPos(), + angles = npc:GetAngles(), + model = npc:GetModel(), + shopType = npc:GetShopType() + } + end + end + + self:SetData(data) +end + +function PLUGIN:LoadData() + for _, v in ipairs(self:GetData() or {}) do + local npc = ents.Create("ix_shop_npc") + if IsValid(npc) then + npc:SetPos(v.pos) + npc:SetAngles(v.angles) + npc:SetModel(v.model or "models/humans/group01/male_01.mdl") + npc:Spawn() + + timer.Simple(0.1, function() + if IsValid(npc) then + npc:SetShopType(v.shopType or "general") + end + end) + end + end +end + +function PLUGIN:OnUnloaded() + self:SaveData() +end + +-- Обработка изменения типа магазина +net.Receive("ixShop_UpdateType", function(len, client) + if not client:IsAdmin() then return end + + local npc = net.ReadEntity() + local shopType = net.ReadString() + + if not IsValid(npc) or npc:GetClass() != "ix_shop_npc" then return end + if client:GetPos():Distance(npc:GetPos()) > 300 then return end + + npc:SetShopType(shopType) + PLUGIN:SaveData() + + client:Notify("Тип магазина изменен на: " .. shopType) +end) + +-- Обработка покупки предмета +net.Receive("ixShop_BuyItem", function(len, client) + local shopType = net.ReadString() + local itemID = net.ReadString() + + local shopData = PLUGIN.shops[shopType] + if not shopData or not shopData.items[itemID] then + client:Notify("Предмет не найден!") + return + end + + local itemData = shopData.items[itemID] + local character = client:GetCharacter() + + if not character then return end + + -- Проверка денег + if not character:HasMoney(itemData.price) then + client:Notify("Недостаточно денег! Требуется: " .. itemData.price .. "₽") + return + end + + -- Проверка наличия предмета в базе ix.item + local ixItem = ix.item.list[itemID] + if not ixItem then + client:Notify("Предмет не существует в базе данных!") + return + end + + -- Попытка дать предмет + if shopType == "drones" then + local weaponClass = ixItem.class or itemID + + -- Проверка, нет ли уже такого дрона у игрока + if client:HasWeapon(weaponClass) then + client:Notify("У вас уже есть этот дрон!") + return + end + + -- Если это магазин дронов, даем оружие напрямую (SWEP) + client:Give(weaponClass) + client:SelectWeapon(weaponClass) + else + -- Для остальных магазинов добавляем в инвентарь + local inventory = character:GetInventory() + + if not inventory or isnumber(inventory) then + client:Notify("Инвентарь еще загружается, подождите немного...") + return + end + + if not inventory:Add(itemID) then + client:Notify("Недостаточно места в инвентаре!") + return + end + end + + -- Списание денег + character:TakeMoney(itemData.price) + client:Notify("Вы купили " .. (ixItem.name or itemID) .. " за " .. itemData.price .. "₽") + + -- Логирование покупки + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if serverlogsPlugin then + local message = string.format("%s купил %s за %d₽ (магазин: %s)", + client:GetName(), (ixItem.name or itemID), itemData.price, shopData.name) + serverlogsPlugin:AddLog("SHOP_PURCHASE", message, client, { + itemID = itemID, + itemName = (ixItem.name or itemID), + price = itemData.price, + shopType = shopType + }) + end +end) + +-- Обработка изменения модели NPC +net.Receive("ixShop_UpdateModel", function(len, client) + if not client:IsAdmin() then return end + + local npc = net.ReadEntity() + local model = net.ReadString() + + if not IsValid(npc) or npc:GetClass() != "ix_shop_npc" then return end + if client:GetPos():Distance(npc:GetPos()) > 300 then return end + + npc:SetModel(model) + PLUGIN:SaveData() + + client:Notify("Модель NPC изменена на: " .. model) +end) + +-- Команда для сохранения магазинов +ix.command.Add("ShopSave", { + description = "Сохранить все магазины", + superAdminOnly = true, + OnRun = function(self, client) + local plugin = ix.plugin.Get("shop") + if not plugin then + return "@commandNoExist" + end + + plugin:SaveData() + local count = #ents.FindByClass("ix_shop_npc") + client:Notify("Магазины сохранены (" .. count .. " шт.)") + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/cl_plugin.lua new file mode 100644 index 0000000..4b9aca3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/cl_plugin.lua @@ -0,0 +1,161 @@ +local TAG = "SitAny_" +local PLUGIN = PLUGIN + +CreateClientConVar("sitting_use_walk", "1.00", true, true, "Требовать шаг для сидения", 0, 1) +CreateClientConVar("sitting_force_left_alt", "0", true, true, "Использовать левый Alt как шаг для сидения", 0, 1) +CreateClientConVar("sitting_allow_on_me", "1.00", true, true, "Разрешить другим садиться на вас", 0, 1) + +local useAlt = GetConVar("sitting_use_walk") +local forceBinds = GetConVar("sitting_force_left_alt") + +local function ShouldSit(ply) + return hook.Run("ShouldSit", ply) +end + +local arrow, drawScale, traceDist = Material("widgets/arrow.png"), 0.1, 20 +local traceScaled = traceDist / drawScale + +local function StartSit(trace) + local wantedAng = nil + local cancelled = false + local start = CurTime() + local ply = LocalPlayer() + + hook.Add("PostDrawOpaqueRenderables", TAG .. "PostDrawOpaqueRenderables", function(depth, skybox) + if CurTime() - start <= 0.25 then return end + if trace.StartPos:Distance(ply:EyePos()) > 10 then + cancelled, wantedAng = true, nil + hook.Remove("PostDrawOpaqueRenderables", TAG .. "PostDrawOpaqueRenderables") + return + end + + local vec = util.IntersectRayWithPlane(ply:EyePos(), ply:EyeAngles():Forward(), trace.HitPos, Vector(0, 0, 1)) + if not vec then return end + + local posOnPlane = WorldToLocal(vec, Angle(0, 90, 0), trace.HitPos, Angle(0, 0, 0)) + local testVec = posOnPlane:GetNormal() * traceScaled + local currentAng = (trace.HitPos - vec):Angle() + wantedAng = currentAng + + if posOnPlane:Length() < 2 then + wantedAng = nil + return + end + + if wantedAng then + local goodSit = SitAnywhere.CheckValidAngForSit(trace.HitPos, trace.HitNormal:Angle(), wantedAng.y) + if not goodSit then wantedAng = nil end + cam.Start3D2D(trace.HitPos + Vector(0, 0, 1), Angle(0, 0, 0), drawScale) + surface.SetDrawColor(goodSit and Color(255, 255, 255, 255) or Color(255, 0, 0, 255)) + surface.SetMaterial(arrow) + surface.DrawTexturedRectRotated(testVec.x * 0.5, testVec.y * -0.5, 2 / drawScale, traceScaled, currentAng.y + 90) + cam.End3D2D() + end + end) + + return function() + hook.Remove("PostDrawOpaqueRenderables", TAG .. "PostDrawOpaqueRenderables") + if cancelled then return end + + if CurTime() - start < 0.25 then + RunConsoleCommand("sit") + return + end + + if wantedAng then + net.Start("SitAnywhere") + net.WriteInt(SitAnywhere.NET.SitWantedAng, 4) + net.WriteFloat(wantedAng.y) + net.WriteVector(trace.StartPos) + net.WriteVector(trace.Normal) + net.SendToServer() + wantedAng = nil + end + end +end + +local function DoSit(trace) + if not trace.Hit then return end + + local surfaceAng = trace.HitNormal:Angle() + Angle(-270, 0, 0) + local playerTrace = not trace.HitWorld and IsValid(trace.Entity) and trace.Entity:IsPlayer() + + local goodSit = SitAnywhere.GetAreaProfile(trace.HitPos + Vector(0, 0, 0.1), 24, true) + if math.abs(surfaceAng.pitch) >= 15 or not goodSit or playerTrace then + RunConsoleCommand("sit") + return + end + + local valid = SitAnywhere.ValidSitTrace(LocalPlayer(), trace) + if not valid then return end + + return StartSit(trace) +end + +local currSit + +concommand.Add("+sit", function(ply, cmd, args) + if currSit then return end + if not IsValid(ply) or not ply.GetEyeTrace then return end + currSit = DoSit(ply:GetEyeTrace()) +end) + +concommand.Add("-sit", function(ply, cmd, args) + if currSit then + currSit() + currSit = nil + end +end) + +function PLUGIN:KeyPress(ply, key) + if not IsFirstTimePredicted() and not game.SinglePlayer() then return end + if currSit then return end + if key ~= IN_USE then return end + + local good = not useAlt:GetBool() + local alwaysSit = ShouldSit(ply) + + if forceBinds:GetBool() then + if useAlt:GetBool() and input.IsKeyDown(KEY_LALT) then + good = true + end + else + if useAlt:GetBool() and ply:KeyDown(IN_WALK) then + good = true + end + end + + if ix.config.Get("sitForceNoWalk", false) then + good = true + end + + if alwaysSit == true then + good = true + elseif alwaysSit == false then + good = false + end + + if not good then return end + local trace = LocalPlayer():GetEyeTrace() + + if trace.Hit then + currSit = DoSit(trace) + hook.Add("KeyRelease", TAG .. "KeyRelease", function(releasePly, releaseKey) + if not IsFirstTimePredicted() and not game.SinglePlayer() then return end + if ply ~= releasePly or releaseKey ~= IN_USE then return end + hook.Remove("KeyRelease", TAG .. "KeyRelease") + if not currSit then return end + + currSit() + currSit = nil + end) + end +end + +hook.Add("ShouldDrawLocalPlayer", "SitAnywhere_DrawLocalPlayer", function(ply) + if not IsValid(ply) then return end + local veh = ply:GetVehicle() + if IsValid(veh) and veh:GetClass() == "prop_vehicle_prisoner_pod" and veh:GetNWBool("playerdynseat", false) then + return true + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_ground_sit.lua b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_ground_sit.lua new file mode 100644 index 0000000..e69de29 diff --git a/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_helpers.lua b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_helpers.lua new file mode 100644 index 0000000..c78d86c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_helpers.lua @@ -0,0 +1,209 @@ +SitAnywhere = SitAnywhere or {} +SitAnywhere.NET = { + ["SitWantedAng"] = 0, + ["SitRequestExit"] = 1, +} + +SitAnywhere.ClassBlacklist = { + ["gmod_wire_keyboard"] = true, + ["prop_combine_ball"] = true +} + +SitAnywhere.DoNotParent = { + ["yava_chunk"] = true +} + +SitAnywhere.ModelBlacklist = {} + +local EMETA = FindMetaTable("Entity") + +function SitAnywhere.GetAreaProfile(pos, resolution, simple) + local filter = player.GetAll() + local dists = {} + local distsang = {} + local ang_smallest_hori = nil + local smallest_hori = 90000 + local angPerIt = (360 / resolution) + + for I = 0, 360, angPerIt do + local rad = math.rad(I) + local dir = Vector(math.cos(rad), math.sin(rad), 0) + local trace = util.QuickTrace(pos + dir * 20 + Vector(0,0,5), Vector(0,0,-15000), filter) + trace.HorizontalTrace = util.QuickTrace(pos + Vector(0,0,5), dir * 1000, filter) + trace.Distance = trace.StartPos:Distance(trace.HitPos) + trace.Distance2 = trace.HorizontalTrace.StartPos:Distance(trace.HorizontalTrace.HitPos) + trace.ang = I + + if (not trace.Hit or trace.Distance > 14) and (not trace.HorizontalTrace.Hit or trace.Distance2 > 20) then + if simple then return true end + table.insert(dists, trace) + end + + if trace.Distance2 < smallest_hori and (not trace.HorizontalTrace.Hit or trace.Distance2 > 3) then + smallest_hori = trace.Distance2 + ang_smallest_hori = I + end + distsang[I] = trace + end + + if simple then return false end + return dists, distsang, ang_smallest_hori, smallest_hori +end + +function SitAnywhere.CheckValidAngForSit(pos, surfaceAng, ang) + local rad = math.rad(ang) + local dir = Vector(math.cos(rad), math.sin(rad), 0) + local trace2 = util.TraceLine({ + start = pos - dir * (20 - .5) + surfaceAng:Forward() * 5, + endpos = pos - dir * (20 - .5) + surfaceAng:Forward() * -160, + filter = player.GetAll() + }) + + local hor_trace = util.TraceLine({ + start = pos + Vector(0, 0, 5), + endpos = pos + Vector(0, 0, 5) - dir * 1600, + filter = player.GetAll() + }) + + return hor_trace.StartPos:Distance(hor_trace.HitPos) > 20 and trace2.StartPos:Distance(trace2.HitPos) > 14 +end + +-- Настройки через ix.config +if SERVER then + ix.config.Add("sitEntMode", 3, "Режим сидения на объектах: 0=нет, 1=только мировые, 2=свои+мировые, 3=любые", nil, { + data = {min = 0, max = 3}, + category = "Sit Anywhere" + }) +end + +local function GetSitOnEntsMode() + if SERVER then + return ix.config.Get("sitEntMode", 3) + end + return 3 +end + +local blacklist = SitAnywhere.ClassBlacklist +local model_blacklist = SitAnywhere.ModelBlacklist + +function SitAnywhere.ValidSitTrace(ply, EyeTrace) + if not EyeTrace.Hit then return false end + if EyeTrace.HitPos:Distance(EyeTrace.StartPos) > 100 then return false end + + local t = hook.Run("CheckValidSit", ply, EyeTrace) + if t == false or t == true then + return t + end + + local mode = GetSitOnEntsMode() + + if not EyeTrace.HitWorld and mode == 0 then return false end + if not EyeTrace.HitWorld and blacklist[string.lower(EyeTrace.Entity:GetClass())] then return false end + if not EyeTrace.HitWorld and EyeTrace.Entity:GetModel() and model_blacklist[string.lower(EyeTrace.Entity:GetModel())] then return false end + + if EMETA.CPPIGetOwner and mode >= 1 then + if mode == 1 then + if not EyeTrace.HitWorld then + local owner = EyeTrace.Entity:CPPIGetOwner() + if type(owner) == "Player" and owner ~= nil and owner:IsValid() and owner:IsPlayer() then + return false + end + end + elseif mode == 2 then + if not EyeTrace.HitWorld then + local owner = EyeTrace.Entity:CPPIGetOwner() + if type(owner) == "Player" and owner ~= nil and owner:IsValid() and owner:IsPlayer() and owner ~= ply then + return false + end + end + end + end + + return true +end + +local seatClass = "prop_vehicle_prisoner_pod" +local PMETA = FindMetaTable("Player") + +function PMETA:GetSitters() + local seats, holders = {}, {} + + local function processSeat(seat, depth) + depth = (depth or 0) + 1 + if IsValid(seat:GetDriver()) and seat:GetDriver() ~= self then + table.insert(seats, seat) + end + for _, v in pairs(seat:GetChildren()) do + if IsValid(v) and v:GetClass() == seatClass and IsValid(v:GetDriver()) and #v:GetChildren() > 0 and depth <= 128 then + processSeat(v, depth) + end + end + end + + local plyVehicle = self:GetVehicle() + if IsValid(plyVehicle) and plyVehicle:GetClass() == seatClass then + processSeat(plyVehicle) + end + + for _, v in pairs(self:GetChildren()) do + if IsValid(v) and v:GetClass() == seatClass then + processSeat(v) + end + end + + for _, v in pairs(ents.FindByClass("sit_holder")) do + if v.GetTargetPlayer and v:GetTargetPlayer() == self then + table.insert(holders, v) + if v.GetSeat and IsValid(v:GetSeat()) then + processSeat(v:GetSeat()) + end + end + end + + return seats, holders +end + +function PMETA:IsPlayerSittingOn(ply) + local seats = ply:GetSitters() + for _,v in pairs(seats) do + if IsValid(v:GetDriver()) and v:GetDriver() == self then return true end + end + return false +end + +function PMETA:GetSitting() + if not IsValid(self:GetVehicle()) then return false end + local veh = self:GetVehicle() + if veh:GetNWBool("playerdynseat", false) then + local parent = veh:GetParent() + if IsValid(parent) and parent:GetClass() == "sit_holder" then + return veh, parent + else + return veh + end + end + return false +end + +function PMETA:ExitSit() + if CLIENT then + if self ~= LocalPlayer() then return end + net.Start("SitAnywhere") + net.WriteInt(SitAnywhere.NET.SitRequestExit, 4) + net.SendToServer() + else + local seat, holder = self:GetSitting() + SafeRemoveEntity(seat) + SafeRemoveEntity(holder) + + if SitAnywhere.GroundSit and self:GetNWBool("SitGroundSitting", false) then + self:SetNWBool("SitGroundSitting", false) + end + end +end + +function EMETA:IsSitAnywhereSeat() + if self:GetClass() ~= "prop_vehicle_prisoner_pod" then return false end + if SERVER and self.playerdynseat then return true end + return self:GetNWBool("playerdynseat", false) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_plugin.lua new file mode 100644 index 0000000..03724d7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sh_plugin.lua @@ -0,0 +1,9 @@ +PLUGIN.name = "Sit Anywhere" +PLUGIN.author = "Xerasin / Ported by RefoselTeamWork" +PLUGIN.description = "Позволяет игрокам садиться везде с помощью клавиши использования." + +ix.util.Include("sh_helpers.lua") + +ix.util.Include("sv_plugin.lua", "server") +ix.util.Include("sv_unstuck.lua", "server") +ix.util.Include("cl_plugin.lua", "client") diff --git a/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sv_plugin.lua new file mode 100644 index 0000000..3ffcc03 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sv_plugin.lua @@ -0,0 +1,605 @@ +local TAG = "SitAny_" +local PLUGIN = PLUGIN +SitAnywhere = SitAnywhere or {} + +local NextUse = setmetatable({}, {__mode = 'k', __index = function() return 0 end}) + +-- Конфигурация через ix.config +ix.config.Add("sitOnPlayers", true, "Разрешить сидеть на игроках в SitAnywhere", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitOnPlayerEntities", true, "Разрешить сидеть на сущностях игроков", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitPlayerDamage", false, "Разрешить урон сидящим игрокам", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitAllowWeapons", false, "Разрешить использование оружия при сидении", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitAdminOnly", false, "Только админы могут сидеть", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitAntiPropSurf", true, "Запретить физган на объектах с сидящими игроками", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitAntiToolAbuse", true, "Запретить тулган на объектах с сидящими игроками", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitAllowTightPlaces", false, "Разрешить сидеть в тесных местах", nil, { + category = "Sit Anywhere" +}) + +ix.config.Add("sitForceNoWalk", false, "Отключить необходимость шага для сидения", nil, { + category = "Sit Anywhere" +}) + +local META = FindMetaTable("Player") + +util.AddNetworkString("SitAnywhere") + +net.Receive("SitAnywhere", function(len, ply) + local netID = net.ReadInt(4) + if netID == SitAnywhere.NET.SitWantedAng then + local wantedAng, traceStart, traceNormal = net.ReadFloat(), net.ReadVector(), net.ReadVector() + + if traceStart:Distance(ply:EyePos()) > 10 then return end + local trace = util.TraceLine({ + start = traceStart, + endpos = traceStart + traceNormal * 12000, + filter = player.GetAll() + }) + + ply:Sit(trace, nil, nil, nil, nil, nil, wantedAng) + elseif netID == SitAnywhere.NET.SitRequestExit then + ply:ExitSit() + end +end) + +local function Sit(ply, pos, ang, parent, parentbone, func, exit) + if IsValid(ply:GetVehicle()) then + local veh = ply:GetVehicle() + if veh:GetClass() == "prop_vehicle_prisoner_pod" and IsValid(veh.holder) then + SafeRemoveEntity(veh.holder) + end + ply:ExitVehicle() + end + + local vehicle = ents.Create("prop_vehicle_prisoner_pod") + local t = hook.Run("OnPlayerSit", ply, pos, ang, parent or NULL, parentbone, vehicle) + + if t == false then + SafeRemoveEntity(vehicle) + return false + end + + vehicle:SetAngles(ang) + pos = pos + vehicle:GetUp() * 18 + vehicle:SetPos(pos) + + vehicle.playerdynseat = true + vehicle:SetNWBool("playerdynseat", true) + vehicle.sittingPly = ply + vehicle.oldpos = vehicle:WorldToLocal(ply:GetPos()) + vehicle.wasCrouching = ply:Crouching() + + vehicle:SetModel("models/nova/airboat_seat.mdl") + vehicle:SetKeyValue("vehiclescript", "scripts/vehicles/prisoner_pod.txt") + vehicle:SetKeyValue("limitview", "0") + vehicle:Spawn() + vehicle:Activate() + + if not IsValid(vehicle) or not IsValid(vehicle:GetPhysicsObject()) then + SafeRemoveEntity(vehicle) + return false + end + + local phys = vehicle:GetPhysicsObject() + vehicle:SetMoveType(MOVETYPE_PUSH) + phys:Sleep() + vehicle:SetCollisionGroup(COLLISION_GROUP_WORLD) + vehicle:SetNotSolid(true) + phys:Sleep() + phys:EnableGravity(false) + phys:EnableMotion(false) + phys:EnableCollisions(false) + phys:SetMass(1) + vehicle:CollisionRulesChanged() + + vehicle:DrawShadow(false) + vehicle:SetColor(Color(0,0,0,0)) + vehicle:SetRenderMode(RENDERMODE_TRANSALPHA) + vehicle:SetNoDraw(true) + + vehicle.VehicleName = "Airboat Seat" + vehicle.ClassOverride = "prop_vehicle_prisoner_pod" + vehicle.PhysgunDisabled = true + vehicle.m_tblToolsAllowed = {} + vehicle.customCheck = function() return false end + + if IsValid(parent) then + local r = math.rad(ang.yaw + 90) + vehicle.plyposhack = vehicle:WorldToLocal(pos + Vector(math.cos(r) * 2, math.sin(r) * 2, 2)) + vehicle:SetParent(parent) + vehicle.parent = parent + else + vehicle.OnWorld = true + end + + local prev = ply:GetAllowWeaponsInVehicle() + if prev then + ply.sitting_allowswep = nil + elseif ix.config.Get("sitAllowWeapons", false) then + ply.sitting_allowswep = prev + ply:SetAllowWeaponsInVehicle(true) + end + + ply:EnterVehicle(vehicle) + + if ix.config.Get("sitPlayerDamage", false) then + ply:SetCollisionGroup(COLLISION_GROUP_WEAPON) + ply:CollisionRulesChanged() + end + + if func then + func(ply) + end + + ply.seatExit = exit + ply:SetEyeAngles(Angle(0,90,0)) + + return vehicle +end + +local d = function(a,b) return math.abs(a-b) end + +local SittingOnPlayerPoses = { + { + Pos = Vector(-33, 13, 7), + Ang = Angle(0, 90, 90), + FindAng = 90, + }, + { + Pos = Vector(33, 13, 7), + Ang = Angle(0, 270, 90), + Func = function(ply) + if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,90,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,90,0)) + end, + OnExitFunc = function(ply) + if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,0,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,0,0)) + end, + FindAng = 270, + }, + { + Pos = Vector(0, 16, -15), + Ang = Angle(0, 180, 0), + Func = function(ply) + if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(45,0,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(-45,0,0)) + end, + OnExitFunc = function(ply) + if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,0,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,0,0)) + end, + FindAng = 0, + }, + { + Pos = Vector(0, 8, -18), + Ang = Angle(0, 0, 0), + FindAng = 180, + }, +} + +local lookup = {} +for k,v in pairs(SittingOnPlayerPoses) do + table.insert(lookup, {v.FindAng, v}) + table.insert(lookup, {v.FindAng + 360, v}) + table.insert(lookup, {v.FindAng - 360, v}) +end + +local function FindPose(this,me) + local avec = me:GetAimVector() + avec.z = 0 + avec:Normalize() + local evec = this:GetRight() + evec.z = 0 + evec:Normalize() + local derp = avec:Dot(evec) + + local avec2 = me:GetAimVector() + avec2.z = 0 + avec2:Normalize() + local evec2 = this:GetForward() + evec2.z = 0 + evec2:Normalize() + local herp = avec2:Dot(evec2) + local v = Vector(derp,herp,0) + local a = v:Angle() + + local ang = a.y + ang = ang + 90 + 180 + ang = ang % 360 + + table.sort(lookup,function(aa,bb) + return d(ang,aa[1]) < d(ang,bb[1]) + end) + return lookup[1][2] +end + +function META.Sit(ply, EyeTrace, ang, parent, parentbone, func, exit, wantedAng) + if EyeTrace == nil then + EyeTrace = ply:GetEyeTrace() + elseif type(EyeTrace) == "Vector" then + return Sit(ply, EyeTrace, ang or Angle(0,0,0), parent, parentbone or 0, func, exit) + end + + local valid, ent = SitAnywhere.ValidSitTrace(ply, EyeTrace) + if not valid then return end + local surfaceAng = EyeTrace.HitNormal:Angle() + Angle(-270, 0, 0) + ang = surfaceAng + + if wantedAng and math.abs(surfaceAng.pitch) <= 15 then + ent = EyeTrace.Entity + if EyeTrace.HitWorld or not ent:IsPlayer() then + if SitAnywhere.CheckValidAngForSit(EyeTrace.HitPos, EyeTrace.HitNormal:Angle(), wantedAng) then + ang.yaw = wantedAng + 90 + else + return + end + end + return Sit(ply, EyeTrace.HitPos - Vector(0, 0, 23), ang, ent, EyeTrace.PhysicsBone or 0) + end + + if ix.config.Get("sitOnPlayers", true) then + local veh + if not EyeTrace.HitWorld and IsValid(EyeTrace.Entity) and EyeTrace.Entity:IsPlayer() and IsValid(EyeTrace.Entity:GetVehicle()) and EyeTrace.Entity:GetVehicle().playerdynseat then + local safe = 256 + veh = EyeTrace.Entity:GetVehicle() + while IsValid(veh.SittingOnMe) and IsValid(veh.SittingOnMe:GetDriver()) and safe > 0 do + safe = safe - 1 + veh = veh.SittingOnMe + end + else + for k, v in pairs(ents.FindInSphere(EyeTrace.HitPos, 12)) do + local safe = 256 + veh = v + while IsValid(veh.SittingOnMe) and IsValid(veh.SittingOnMe:GetDriver()) and safe > 0 do + safe = safe - 1 + veh = veh.SittingOnMe + end + end + end + + if IsValid(veh) and veh:GetClass() == "prop_vehicle_prisoner_pod" and veh:GetModel() ~= "models/vehicles/prisoner_pod_inner.mdl" and veh:GetDriver() and veh:GetDriver():IsValid() and not veh.PlayerSitOnPlayer then + if IsValid(veh.holder) and veh.holder.GetTargetPlayer and veh.holder:GetTargetPlayer() == ply then return end + + if veh:GetDriver():GetInfoNum("sitting_allow_on_me", 1) == 0 then + ply:ChatPrint(("%s отключил возможность сидеть на нём!"):format(veh:GetDriver():Name())) + return false + end + + local pose = FindPose(veh, ply) + local pos = veh:GetDriver():GetPos() + + if veh.plyposhack then + pos = veh:LocalToWorld(veh.plyposhack) + end + + local vec, ang2 = LocalToWorld(pose.Pos, pose.Ang, pos, veh:GetAngles()) + + if veh:GetParent() == ply then return false end + + ent = Sit(ply, vec, ang2, veh, 0, pose.Func, pose.OnExitFunc) + + if ent and IsValid(ent) then + ent.PlayerOnPlayer = true + veh.SittingOnMe = ent + end + + return true, ent + end + else + for k, v in pairs(ents.FindInSphere(EyeTrace.HitPos, 5)) do + if v.playerdynseat then + return false + end + end + end + + local shouldSitOnPlayer = (ply.IsFlying and ply:IsFlying()) or EyeTrace.Entity == ply:GetGroundEntity() or ply:GetMoveType() == MOVETYPE_NOCLIP + if IsValid(EyeTrace.Entity) and EyeTrace.Entity:IsPlayer() and ix.config.Get("sitOnPlayerEntities", true) and shouldSitOnPlayer then + ent = EyeTrace.Entity + if IsValid(ent:GetVehicle()) then return end + + local min, max = ent:GetCollisionBounds() + local zadjust = math.abs(min.z) + math.abs(max.z) + local seatPos = ent:GetPos() + Vector(0, 0, 10 + zadjust / 2) + + local vehicle = Sit(ply, seatPos, ply:GetAngles(), ent, EyeTrace.PhysicsBone or 0) + return vehicle + end + + if math.abs(surfaceAng.pitch) <= 15 then + ang = Angle() + local sampleResolution = 24 + local dists, distsang, ang_smallest_hori, smallest_hori = SitAnywhere.GetAreaProfile(EyeTrace.HitPos, sampleResolution, false) + local infront = ((ang_smallest_hori or 0) + 180) % 360 + + local cancelSit, seat = hook.Run("HandleSit", ply, dists, EyeTrace) + if cancelSit then + return seat + end + + if ang_smallest_hori and distsang[infront].Hit and distsang[infront].Distance > 14 and smallest_hori <= 16 then + local hori = distsang[ang_smallest_hori].HorizontalTrace + ang.yaw = (hori.HitNormal:Angle().yaw - 90) + return Sit(ply, EyeTrace.HitPos - Vector(0, 0, 23), ang, (not EyeTrace.HitWorld) and EyeTrace.Entity, EyeTrace.PhysicsBone or 0) + else + table.sort(dists, function(a,b) return b.Distance < a.Distance end) + local wants = {} + local eyeang = ply:EyeAngles() + Angle(0, 180, 0) + for I = 1, #dists do + local trace = dists[I] + local behind = distsang[(trace.ang + 180) % 360] + if behind.Distance2 > 3 then + table.insert(wants, { + cost = math.abs(eyeang.yaw - trace.ang), + ang = trace.ang, + }) + end + end + + table.sort(wants,function(a,b) return b.cost > a.cost end) + if #wants == 0 then return end + ang.yaw = (wants[1].ang - 90) + return Sit(ply, EyeTrace.HitPos - Vector(0, 0, 23), ang, (not EyeTrace.HitWorld) and EyeTrace.Entity, EyeTrace.PhysicsBone or 0) + end + end +end + +local function checkAllowSit(ply) + local allowSit = hook.Run("ShouldAllowSit", ply) + + local bottom, top = ply:GetHull() + local diff = top.Z - bottom.Z + local trace = util.QuickTrace(ply:GetPos(), Vector(0, 0, diff), player.GetAll()) + + if not ix.config.Get("sitAllowTightPlaces", false) and trace.HitWorld then return false end + if allowSit == false or allowSit == true then + return allowSit + end + + if ix.config.Get("sitAdminOnly", false) and not ply:IsAdmin() then + return false + end + + return true +end + +local function sitcmd(ply) + if not IsValid(ply) then return end + if ply:InVehicle() then return end + if not checkAllowSit(ply) then return end + + local now = CurTime() + if NextUse[ply] > now then return end + + if ply:Sit() then + NextUse[ply] = now + 1 + else + NextUse[ply] = now + 0.1 + end +end + +concommand.Add("sit", function(ply, cmd, args) + sitcmd(ply) +end) + +local function UndoSitting(ply) + if not IsValid(ply) then return end + + local prev = ply.sitting_allowswep + if prev ~= nil then + ply.sitting_allowswep = nil + ply:SetAllowWeaponsInVehicle(prev) + end + + if ix.config.Get("sitPlayerDamage", false) then + ply:SetCollisionGroup(COLLISION_GROUP_PLAYER) + ply:CollisionRulesChanged() + end + + if ply.seatExit then + ply.seatExit(ply) + ply.seatExit = nil + end +end + +-- Хуки +function PLUGIN:CanExitVehicle(seat, ply) + if not IsValid(seat) or not IsValid(ply) then return end + if not seat.playerdynseat then return end + if CurTime() < NextUse[ply] then return false end +end + +function PLUGIN:PlayerLeaveVehicle(ply, seat) + if not IsValid(seat) or not IsValid(ply) then return end + if not seat.playerdynseat then return end + + local oldpos = seat:LocalToWorld(seat.oldpos) + ply:SetPos(oldpos) + if ply.UnStuck then + ply:UnStuck() + end + + for _, v in next, seat:GetChildren() do + if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then + v.sittingPly:ExitVehicle() + end + end + + SafeRemoveEntityDelayed(seat, 1) + UndoSitting(ply) +end + +function PLUGIN:AllowPlayerPickup(ply) + if ply:KeyDown(IN_WALK) then + return false + end +end + +function PLUGIN:PlayerDeath(pl) + local veh = pl:GetVehicle() + if IsValid(veh) and veh.playerdynseat then + SafeRemoveEntity(veh) + end + + for k, v in next, pl:GetChildren() do + if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then + v.sittingPly:ExitVehicle() + end + end +end + +function PLUGIN:PlayerEnteredVehicle(pl, veh) + for k,v in next, pl:GetChildren() do + if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then + v.sittingPly:ExitVehicle() + end + end + + DropEntityIfHeld(veh) + + local parent = veh:GetParent() + if IsValid(parent) then + DropEntityIfHeld(parent) + end +end + +function PLUGIN:OnPlayerSit(ply, pos, ang, parent, parentbone, vehicle) + if IsValid(parent) and parent ~= game.GetWorld() then + if parent:GetModel():sub(1, 6) ~= "models" then return false end + if parent:IsPlayer() then + if not ix.config.Get("sitOnPlayerEntities", true) then return false end + if parent:GetInfoNum("sitting_allow_on_me", 1) == 0 then + ply:ChatPrint(("%s отключил возможность сидеть на нём!"):format(parent:Name())) + return false + end + + if IsValid(parent:GetVehicle()) then return false end + end + end +end + +function PLUGIN:EntityRemoved(ent) + if ent.playerdynseat and IsValid(ent.sittingPly) then + UndoSitting(ent.sittingPly) + end + + for _, v in next, ent:GetChildren() do + if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then + v.sittingPly:ExitVehicle() + end + end +end + +function PLUGIN:PhysgunPickup(ply, ent) + if ix.config.Get("sitAntiPropSurf", true) then + local function CheckSeat(checkPly, checkEnt, tbl) + if not checkPly:InVehicle() then return true end + + local vehicle = checkPly:GetVehicle() + local parent = vehicle.parent + + if parent == checkEnt then return false end + + for _,v in next, checkEnt:GetChildren() do + if IsValid(v) and not tbl[v] then + tbl[v] = true + if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then + return false + end + end + end + + local cEnts = constraint.GetAllConstrainedEntities(checkEnt) + if cEnts then + for _,v in next, cEnts do + if IsValid(v) and not tbl[v] then + tbl[v] = true + if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then + return false + end + end + end + end + end + + if IsValid(ply:GetVehicle()) and ply:GetVehicle().playerdynseat then + if CheckSeat(ply, ent, {}) == false then + return false + end + end + end +end + +function PLUGIN:CanTool(ply, tr) + if ix.config.Get("sitAntiToolAbuse", true) and IsValid(tr.Entity) then + local function CheckSeat(checkPly, checkEnt, tbl) + if not checkPly:InVehicle() then return true end + + local vehicle = checkPly:GetVehicle() + local parent = vehicle.parent + + if parent == checkEnt then return false end + + for _,v in next, checkEnt:GetChildren() do + if IsValid(v) and not tbl[v] then + tbl[v] = true + if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then + return false + end + end + end + + local cEnts = constraint.GetAllConstrainedEntities(checkEnt) + if cEnts then + for _,v in next, cEnts do + if IsValid(v) and not tbl[v] then + tbl[v] = true + if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then + return false + end + end + end + end + end + + if IsValid(ply:GetVehicle()) and ply:GetVehicle().playerdynseat then + if CheckSeat(ply, tr.Entity, {}) == false then + return false + end + end + end +end + +timer.Create("SitAnywhere_RemoveSeats", 15, 0, function() + for k,v in pairs(ents.FindByClass("prop_vehicle_prisoner_pod")) do + if v.playerdynseat and (not IsValid(v.sittingPly) or v:GetDriver() == nil or not v:GetDriver():IsValid() or v:GetDriver():GetVehicle() ~= v) then + v:Remove() + end + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sv_unstuck.lua b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sv_unstuck.lua new file mode 100644 index 0000000..393b364 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/sitanywhere/sv_unstuck.lua @@ -0,0 +1,99 @@ +local function returnFilter(pl) + return function(e) + if e == pl then return false end + local cg = e:GetCollisionGroup() + + return cg ~= 15 and cg ~= 11 and cg ~= 1 and cg ~= 2 and cg ~= 20 + end +end + +local function IsStuck(pl, fast, pos) + local t = {mask = MASK_PLAYERSOLID} + t.start = pos or pl:GetPos() + t.endpos = t.start + + if fast then + t.filter = {pl} + else + t.filter = returnFilter(pl) + end + + local output = util.TraceEntity(t, pl) + return output.StartSolid, output.Entity, output +end + +local function FindPassableSpace(ply, dirs, n, direction, step) + local origin = dirs[n] + if not origin then + origin = ply:GetPos() + dirs[n] = origin + end + + origin:Add(step * direction) + + if not IsStuck(ply, false, origin) then + ply:SetPos(origin) + if not IsStuck(ply, false) then + ply.NewPos = ply:GetPos() + return true + end + end + + return false +end + +local right = Vector(0, 1, 0) +local up = Vector(0, 0, 1) + +local function UnstuckPlayer(ply) + ply.NewPos = ply:GetPos() + local OldPos = ply.NewPos + + local dirs = {} + if IsStuck(ply) then + local SearchScale = 1 + local ok + local forward = ply:GetAimVector() + forward.z = 0 + forward:Normalize() + right = forward:Angle():Right() + + for i = 1, 20 do + ok = true + if not FindPassableSpace(ply, dirs, 1, forward, SearchScale * i) + and not FindPassableSpace(ply, dirs, 2, right, SearchScale * i) + and not FindPassableSpace(ply, dirs, 3, right, -SearchScale * i) + and not FindPassableSpace(ply, dirs, 4, up, SearchScale * i) + and not FindPassableSpace(ply, dirs, 5, up, -SearchScale * i) + and not FindPassableSpace(ply, dirs, 6, forward, -SearchScale * i) then + ok = false + end + if ok then break end + end + + if not ok then return false end + + if OldPos == ply.NewPos then + ply:SetPos(ply.NewPos) + ply.NewPos = nil + return true + else + ply:SetPos(ply.NewPos) + ply.NewPos = nil + + if SERVER and ply and ply:IsValid() and ply:GetPhysicsObject():IsValid() then + ply:SetVelocity(-ply:GetVelocity()) + end + + return true + end + end +end + +util.UnstuckPlayer = UnstuckPlayer + +local Player = FindMetaTable("Player") + +function Player:UnStuck() + return UnstuckPlayer(self) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/spawnmenu_itemtab.lua b/garrysmod/gamemodes/militaryrp/plugins/spawnmenu_itemtab.lua new file mode 100644 index 0000000..8770502 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/spawnmenu_itemtab.lua @@ -0,0 +1,146 @@ +PLUGIN.name = "Меню выдачи предметов (SuperAdmin)" +PLUGIN.author = "RefoselTeamWork" +PLUGIN.description = "Adds a tab to the spawn menu which players can use to spawn items." + +CAMI.RegisterPrivilege({ + Name = "Helix - Выдача предметов", + MinAccess = "superadmin" +}) + +function PLUGIN:GetExpectedIcon(s) + local i = { + ["Патроны"] = "icon16/tab.png" + } + + return hook.Run("GetIconsForSpawnMenuItems", s) or i[s] or "icon16/folder.png" +end + +if SERVER then + util.AddNetworkString("spawnmenuspawnitem") + util.AddNetworkString("spawnmenugiveitem") + ix.log.AddType("spawnmenuspawnitem", function(client, name) return string.format("%s has spawned \"%s\".", client:GetCharacter():GetName(), tostring(name)) end) + + net.Receive("spawnmenuspawnitem", function(len, client) + local u = net.ReadString() + if not CAMI.PlayerHasAccess(client, "Helix - Выдача предметов", nil) then return end + + for _, t in pairs(ix.item.list) do + if t.uniqueID == u then + ix.item.Spawn(t.uniqueID, client:GetShootPos() + client:GetAimVector() * 84 + Vector(0, 0, 16)) + ix.log.Add(client, "spawnmenuspawnitem", t.name) + break + end + end + end) + + net.Receive("spawnmenugiveitem", function(len, client) + local u = net.ReadString() + if not CAMI.PlayerHasAccess(client, "Helix - Выдача предметов", nil) then return end + + for _, t in pairs(ix.item.list) do + if t.uniqueID == u then + local character = client:GetCharacter() + if not character then return end + + local inventory = character:GetInventory() + if not inventory or isnumber(inventory) then + client:Notify("Inventory is still loading, please wait...") + return + end + + inventory:Add(t.uniqueID) + ix.log.Add(client, "spawnmenuspawnitem", t.name) + client:Notify("You have given yourself a " .. t.name .. ". Check your inventory.") + break + end + end + end) +else + function PLUGIN:InitializedPlugins() + if SERVER then return end + RunConsoleCommand("spawnmenu_reload") + end + + local PLUGIN = PLUGIN + + spawnmenu.AddCreationTab("SuperAdmin - Выдача", function() + local p = vgui.Create("SpawnmenuContentPanel") + local t, n = p.ContentNavBar.Tree, p.OldSpawnlists + local l = {} + + for uid, i in pairs(ix.item.list) do + local c = i.category + l[c] = l[c] or {} + table.insert(l[c], i) + end + + for c, i in SortedPairs(l) do + local icon16 = PLUGIN:GetExpectedIcon(c) + local node = t:AddNode(L(c), icon16) + + node.DoClick = function(self) + if self.PropPanel and IsValid(self.PropPanel) then + self.PropPanel:Remove() + self.PropPanel = nil + end + + self.PropPanel = vgui.Create("ContentContainer", p) + self.PropPanel:SetVisible(false) + self.PropPanel:SetTriggerSpawnlistChange(false) + + for _, t in SortedPairsByMemberValue(i, "name") do + spawnmenu.CreateContentIcon("item", self.PropPanel, { + nicename = (t.GetName and t:GetName()) or t.name, + spawnname = t.uniqueID, + }) + end + + p:SwitchPanel(self.PropPanel) + end + end + + local FirstNode = t:Root():GetChildNode(0) + + if IsValid(FirstNode) then + FirstNode:InternalDoClick() + end + + return p + end, "icon16/cog_add.png", 201) + + spawnmenu.AddContentType("item", function(p, data) + local n = data.nicename + local u = data.spawnname + local icon = vgui.Create("SpawnIcon", p) + icon:SetWide(64) + icon:SetTall(64) + icon:InvalidateLayout(true) + local t = ix.item.list + local i = t[u] + icon:SetModel((i.GetModel and i:GetModel()) or i.model) + icon:SetTooltip(n) + + icon.OnMousePressed = function(this, code) + surface.PlaySound("ui/buttonclickrelease.wav") + if not CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Выдача предметов", nil) then return end + + if code == MOUSE_LEFT then + net.Start("spawnmenuspawnitem") + net.WriteString(u) + net.SendToServer() + elseif code == MOUSE_RIGHT then + net.Start("spawnmenugiveitem") + net.WriteString(u) + net.SendToServer() + end + end + + icon:InvalidateLayout(true) + + if IsValid(p) then + p:Add(icon) + end + + return icon + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/spec/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/spec/cl_plugin.lua new file mode 100644 index 0000000..7b1c730 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/spec/cl_plugin.lua @@ -0,0 +1 @@ +local PLUGIN = PLUGIN diff --git a/garrysmod/gamemodes/militaryrp/plugins/spec/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/spec/sh_plugin.lua new file mode 100644 index 0000000..73328f3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/spec/sh_plugin.lua @@ -0,0 +1,112 @@ +PLUGIN.name = "Подразделения и Специализации" +PLUGIN.author = "Refosel" +PLUGIN.description = "" + +ix.util.Include("cl_plugin.lua") +--ix.util.Include("sv_plugin.lua") + +-- Регистрация переменных персонажа +ix.char.RegisterVar("rank", { + field = "rank", + fieldType = ix.type.number, + default = 1 +}) + +ix.char.RegisterVar("podr", { + field = "podr", + fieldType = ix.type.number, + default = 1 +}) + +ix.char.RegisterVar("spec", { + field = "spec", + fieldType = ix.type.number, + default = 1 +}) + +local PMETA = FindMetaTable("Player") + +function PMETA:GetRankName() + local client = self + local char = client:GetCharacter() + if not char then return false end + + local rank = char:GetRank() + local podr = char:GetPodr() + local factiontable = ix.faction.Get(client:Team()) + if not factiontable then return false end + + -- Check for subdivision specific ranks + if (podr) and factiontable.Podr and factiontable.Podr[podr] and factiontable.Podr[podr].ranks then + local podrRanks = factiontable.Podr[podr].ranks + if podrRanks[rank] then + return podrRanks[rank][1] + elseif rank > 0 then + -- Find the highest rank index in the subdivision table + local maxPodrRank = 0 + for k, _ in pairs(podrRanks) do + if k > maxPodrRank then maxPodrRank = k end + end + + -- Clamp if rank is higher than the max available in subdivision + if rank >= maxPodrRank and maxPodrRank > 0 then + return podrRanks[maxPodrRank][1] + end + end + end + + if (rank) and factiontable.Ranks then + if factiontable.Ranks[rank] then + return factiontable.Ranks[rank][1] + elseif rank > 0 then + -- Find the highest rank index in the global table + local maxGlobalRank = 0 + for k, _ in pairs(factiontable.Ranks) do + if k > maxGlobalRank then maxGlobalRank = k end + end + + -- Clamp if rank is higher than the max available globally + if rank >= maxGlobalRank and maxGlobalRank > 0 then + return factiontable.Ranks[maxGlobalRank][1] + end + end + end + + return false +end + +function PMETA:GetPodrName() + local client = self + local char = client:GetCharacter() + if not char then return false end + + local podr = char:GetPodr() + local factiontable = ix.faction.Get(client:Team()) + if (podr) and factiontable and factiontable.Podr and factiontable.Podr[podr] then + return factiontable.Podr[podr].name + else + return false + end +end + +function PMETA:GetSpecName() + local client = self + local char = client:GetCharacter() + if not char then return false end + + local spec = char:GetSpec() + local factiontable = ix.faction.Get(client:Team()) + if (spec) and factiontable and factiontable.Spec and factiontable.Spec[spec] then + return factiontable.Spec[spec].name + else + return false + end +end + +-- ix.plugin.SetUnloaded("stamina", true) +-- ix.plugin.SetUnloaded("strength", true) +ix.plugin.SetUnloaded("doors", true) +ix.plugin.SetUnloaded("recognition", true) + +ix.char.vars["description"].bNoDisplay = true +ix.char.vars["description"].OnValidate = function() return true end diff --git a/garrysmod/gamemodes/militaryrp/plugins/spec/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/spec/sv_plugin.lua new file mode 100644 index 0000000..ee45738 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/spec/sv_plugin.lua @@ -0,0 +1,12 @@ +local PLUGIN = PLUGIN + +function PLUGIN:CharacterVarChanged(character, key, oldValue, value) + local client = character:GetPlayer() + if (key == "rank") then + local factionTable = ix.faction.Get(client:Team()) + + if (factionTable.OnRankChanged) then + factionTable:OnRankChanged(client, oldValue, value) + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/squads/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/squads/cl_plugin.lua new file mode 100644 index 0000000..aa5081e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/squads/cl_plugin.lua @@ -0,0 +1,253 @@ +local PLUGIN = PLUGIN +PLUGIN.lastMarkerPress = 0 +-- Клиентские данные +PLUGIN.currentSquad = PLUGIN.currentSquad or nil +PLUGIN.activeMarkers = PLUGIN.activeMarkers or {} + +-- Получение сетевых данных +net.Receive("ixSquadSync", function() + local jsonData = net.ReadString() + local data = util.JSONToTable(jsonData) + + -- Если пришла пустая таблица - очищаем данные отряда + if not data or not data.id then + PLUGIN.currentSquad = nil + + -- Очищаем маркеры + for _, markerID in ipairs(PLUGIN.activeMarkers) do + if Adv_Compass_RemoveMarker then + Adv_Compass_RemoveMarker(markerID) + end + end + PLUGIN.activeMarkers = {} + else + PLUGIN.currentSquad = data + end +end) + +net.Receive("ixSquadNotify", function() + local message = net.ReadString() + LocalPlayer():Notify(message) +end) + +net.Receive("ixSquadInvite", function() + local squadID = net.ReadString() + local inviterName = net.ReadString() + + -- Создаем окно приглашения + local frame = vgui.Create("DFrame") + frame:SetSize(400, 150) + frame:Center() + frame:SetTitle("") + frame:SetDraggable(false) + frame:ShowCloseButton(false) + frame:MakePopup() + frame.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(25, 25, 28)) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, 3) + end + + local title = vgui.Create("DLabel", frame) + title:SetPos(0, 20) + title:SetSize(400, 30) + title:SetFont("F4Menu_Category") + title:SetTextColor(Color(255, 255, 255)) + title:SetText("Приглашение в отряд") + title:SetContentAlignment(5) + + local text = vgui.Create("DLabel", frame) + text:SetPos(20, 55) + text:SetSize(360, 40) + text:SetFont("F4Menu_Item") + text:SetTextColor(Color(200, 200, 200)) + text:SetText(inviterName .. " приглашает вас вступить в отряд") + text:SetContentAlignment(5) + text:SetWrap(true) + + local acceptBtn = vgui.Create("DButton", frame) + acceptBtn:SetPos(20, 105) + acceptBtn:SetSize(175, 30) + acceptBtn:SetText("Принять") + acceptBtn:SetFont("F4Menu_Item") + acceptBtn:SetTextColor(Color(255, 255, 255)) + acceptBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(1, 87, 39) or Color(1, 67, 29)) + end + acceptBtn.DoClick = function() + net.Start("ixSquadAcceptInvite") + net.SendToServer() + frame:Close() + end + + local declineBtn = vgui.Create("DButton", frame) + declineBtn:SetPos(205, 105) + declineBtn:SetSize(175, 30) + declineBtn:SetText("Отклонить") + declineBtn:SetFont("F4Menu_Item") + declineBtn:SetTextColor(Color(255, 255, 255)) + declineBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and Color(70, 25, 25) or Color(50, 25, 25)) + end + declineBtn.DoClick = function() + net.Start("ixSquadDeclineInvite") + net.SendToServer() + frame:Close() + end + + -- Автоматическое закрытие через 30 секунд + timer.Simple(30, function() + if IsValid(frame) then + frame:Close() + end + end) +end) + +PLUGIN.worldMarker = nil +PLUGIN.nextMarkerTime = 0 + +net.Receive("ixSquadWorldMarker", function() + local pos = net.ReadVector() + local name = net.ReadString() + + PLUGIN.worldMarker = { + pos = pos, + name = name, + expire = CurTime() + 10 + } +end) + +net.Receive("ixSquadUpdateMarkers", function() + for _, markerID in ipairs(PLUGIN.activeMarkers) do + if Adv_Compass_RemoveMarker then + Adv_Compass_RemoveMarker(markerID) + end + end + PLUGIN.activeMarkers = {} + + local count = net.ReadUInt(8) + local markerColor = ix.config.Get("squadMarkerColor", Color(0, 255, 0)) + + for i = 1, count do + local pos = net.ReadVector() + local name = net.ReadString() + local ent = net.ReadEntity() + + if IsValid(ent) and mCompass_AddEntityMarker then + -- Используем маркер на сущности для отслеживания движения + local markerID = mCompass_AddEntityMarker( + LocalPlayer(), + ent, + {LocalPlayer()}, + CurTime() + 1, -- 1 секунда (обновляется регулярно) + markerColor, + "", -- Иконка (пустая, используем стандартную) + name + ) + table.insert(PLUGIN.activeMarkers, markerID) + end + end +end) + +-- Очистка при закрытии +function PLUGIN:ShutDown() + for _, markerID in ipairs(self.activeMarkers) do + if Adv_Compass_RemoveMarker then + Adv_Compass_RemoveMarker(markerID) + end + end + self.activeMarkers = {} +end + +-- Вспомогательные функции +function PLUGIN:GetCurrentSquad() + return self.currentSquad +end + +function PLUGIN:IsInSquad() + return self.currentSquad ~= nil +end + +function PLUGIN:IsSquadLeader() + if not self.currentSquad then return false end + return self.currentSquad.leader == LocalPlayer():SteamID() +end + +PLUGIN.wasGPressed = false +PLUGIN.nextMarkerTime = 0 + +hook.Add("Think", "ixSquadPlaceMarkerKey", function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + + if vgui.CursorVisible() then return end + if gui.IsGameUIVisible() then return end + if ix and ix.gui and ix.gui.menu and IsValid(ix.gui.menu) then return end + + local isDown = input.IsKeyDown(KEY_G) + + if isDown and not PLUGIN.wasGPressed then + PLUGIN.wasGPressed = true + + if not PLUGIN:IsInSquad() then + LocalPlayer():Notify("Вы не состоите в отряде") + return + end + + if CurTime() < PLUGIN.nextMarkerTime then + LocalPlayer():Notify("Метка будет доступна через " .. math.ceil(PLUGIN.nextMarkerTime - CurTime()) .. " сек.") + return + end + + local tr = ply:GetEyeTrace() + if not tr.Hit then return end + + PLUGIN.nextMarkerTime = CurTime() + 5 + + net.Start("ixSquadPlaceMarker") + net.WriteVector(tr.HitPos) + net.SendToServer() + end + + if not isDown then + PLUGIN.wasGPressed = false + end +end) + +local icon = Material("materials/squads/eye1.png") + +hook.Add("HUDPaint", "ixSquadDrawWorldMarkerHUD", function() + local m = PLUGIN.worldMarker + if not m then return end + + if CurTime() > m.expire then + PLUGIN.worldMarker = nil + return + end + + local screen = m.pos:ToScreen() + + local dist = LocalPlayer():GetPos():Distance(m.pos) + + local scale = math.Clamp(1 - (dist / 3000), 0.4, 1) + + local size = 96 + + surface.SetMaterial(icon) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(screen.x - size/2, screen.y - size/2, size, size) + + surface.SetFont("DermaLarge") + local tw, th = surface.GetTextSize(m.name) + + draw.SimpleText( + m.name, + "DermaLarge", + screen.x, + screen.y + size/2 + 10, + Color(255, 255, 255), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_TOP, + scale + ) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/squads/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/squads/sh_plugin.lua new file mode 100644 index 0000000..fc9db83 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/squads/sh_plugin.lua @@ -0,0 +1,21 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Squads System" +PLUGIN.author = "MilitaryRP" +PLUGIN.description = "Система отрядов для военного сервера" + +ix.config.Add("squadMaxMembers", 16, "Максимальное количество игроков в отряде", nil, { + data = {min = 2, max = 32}, + category = "Squads" +}) + +ix.config.Add("squadMarkerUpdateRate", 0.5, "Частота обновления маркеров отряда (сек)", nil, { + data = {min = 0.1, max = 5}, + category = "Squads" +}) + +ix.config.Add("squadMarkerColor", Color(0, 255, 0), "Цвет маркеров союзников", nil, { + category = "Squads" +}) + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/squads/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/squads/sv_plugin.lua new file mode 100644 index 0000000..527e079 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/squads/sv_plugin.lua @@ -0,0 +1,692 @@ +local PLUGIN = PLUGIN +-- Таблица активных отрядов +PLUGIN.squads = PLUGIN.squads or {} +PLUGIN.playerSquads = PLUGIN.playerSquads or {} -- [SteamID] = squadID +PLUGIN.squadInvites = PLUGIN.squadInvites or {} -- [SteamID] = {squadID, inviterSteamID} + +util.AddNetworkString("ixSquadCreate") +util.AddNetworkString("ixSquadDisband") +util.AddNetworkString("ixSquadInvite") +util.AddNetworkString("ixSquadAcceptInvite") +util.AddNetworkString("ixSquadDeclineInvite") +util.AddNetworkString("ixSquadKick") +util.AddNetworkString("ixSquadLeave") +util.AddNetworkString("ixSquadPromote") +util.AddNetworkString("ixSquadSync") +util.AddNetworkString("ixSquadNotify") +util.AddNetworkString("ixSquadUpdateMarkers") +util.AddNetworkString("ixSquadPlaceMarker") +util.AddNetworkString("ixSquadWorldMarker") + + +local function IsAdminMode(ply) + return ply.GetNetVar and ply:GetNetVar("AdminMode", false) +end +-- Генерация уникального ID отряда +function PLUGIN:GenerateSquadID() + local id + repeat + id = "squad_" .. os.time() .. "_" .. math.random(1000, 9999) + until not self.squads[id] + return id +end + +-- Получить отряд игрока +function PLUGIN:GetPlayerSquad(client) + local steamID = client:SteamID() + local squadID = self.playerSquads[steamID] + if squadID then + return self.squads[squadID], squadID + end + return nil, nil +end + +-- Получить фракцию игрока +function PLUGIN:GetPlayerFaction(client) + local character = client:GetCharacter() + if not character then return nil end + return character:GetFaction() +end + +-- Создание отряда +function PLUGIN:CreateSquad(leader) + + if IsAdminMode(leader) then + return false, "Администратор не может создавать отряд" + end + + if not IsValid(leader) then + return false, "Недействительный игрок" + end + + local steamID = leader:SteamID() + + -- Проверяем, не состоит ли уже в отряде + if self.playerSquads[steamID] then + return false, "Вы уже состоите в отряде" + end + + local faction = self:GetPlayerFaction(leader) + + if not faction then + return false, "У вас нет активного персонажа" + end + + local squadID = self:GenerateSquadID() + + self.squads[squadID] = { + id = squadID, + leader = steamID, + faction = faction, + members = {steamID}, + memberData = { + [steamID] = { + name = leader:Name(), + isLeader = true, + joinTime = os.time() + } + }, + createdAt = os.time() + } + + self.playerSquads[steamID] = squadID + + self:SyncSquadToMembers(squadID) + self:StartSquadMarkers(squadID) + + -- Логирование создания отряда + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + local message = string.format("%s создал отряд (ID: %s, фракция: %s)", leader:Nick(), squadID, factionName) + serverlogsPlugin:AddLog("SQUAD_CREATE", message, leader, { + squadID = squadID, + faction = faction, + factionName = factionName + }) + end + + return true, "Отряд успешно создан" +end + +-- Расформирование отряда +function PLUGIN:DisbandSquad(squadID) + local squad = self.squads[squadID] + if not squad then return false, "Отряд не найден" end + + -- Логируем расформирование перед удалением + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local leaderName = squad.memberData[squad.leader] and squad.memberData[squad.leader].name or "Unknown" + local factionName = ix.faction.Get(squad.faction).name or tostring(squad.faction) + local message = string.format("Отряд %s расформирован (лидер: %s, членов: %d, фракция: %s)", + squadID, leaderName, #squad.members, factionName) + serverlogsPlugin:AddLog("SQUAD_DELETE", message, nil, { + squadID = squadID, + leader = squad.leader, + leaderName = leaderName, + memberCount = #squad.members, + faction = squad.faction, + factionName = factionName + }) + end + + -- Останавливаем маркеры + self:StopSquadMarkers(squadID) + + -- Очищаем данные на клиенте и уведомляем всех членов + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + -- Отправляем пустую таблицу для очистки данных на клиенте + net.Start("ixSquadSync") + net.WriteString("{}") + net.Send(member) + + net.Start("ixSquadNotify") + net.WriteString("Отряд был расформирован") + net.Send(member) + end + self.playerSquads[memberSteamID] = nil + end + + self.squads[squadID] = nil + + return true, "Отряд расформирован" +end + +-- Приглашение в отряд +function PLUGIN:InviteToSquad(inviter, target) + print("[SQUADS] InviteToSquad вызван: inviter=" .. (IsValid(inviter) and inviter:Name() or "NIL") .. ", target=" .. (IsValid(target) and target:Name() or "NIL")) + + if IsAdminMode(inviter) then + return false, "Администратор не может приглашать в отряд" + end + + if not IsValid(inviter) or not IsValid(target) then + print("[SQUADS] Ошибка: недействительный игрок") + return false, "Недействительный игрок" + end + + local squad, squadID = self:GetPlayerSquad(inviter) + if not squad then + print("[SQUADS] Ошибка: инвайтер не в отряде") + return false, "Вы не состоите в отряде" + end + + print("[SQUADS] Отряд найден: " .. squadID) + + local inviterSteamID = inviter:SteamID() + if squad.leader ~= inviterSteamID then + print("[SQUADS] Ошибка: не лидер (leader=" .. squad.leader .. ", inviter=" .. inviterSteamID .. ")") + return false, "Только лидер может приглашать игроков" + end + + local targetSteamID = target:SteamID() + + -- Проверяем, не состоит ли уже в отряде + if self.playerSquads[targetSteamID] then + print("[SQUADS] Ошибка: target уже в отряде") + return false, target:Name() .. " уже состоит в отряде" + end + + -- Проверяем лимит + if #squad.members >= ix.config.Get("squadMaxMembers", 16) then + print("[SQUADS] Ошибка: отряд заполнен") + return false, "Отряд заполнен (макс. " .. ix.config.Get("squadMaxMembers", 16) .. " человек)" + end + + -- Проверяем фракцию + local targetFaction = self:GetPlayerFaction(target) + if targetFaction ~= squad.faction then + print("[SQUADS] Ошибка: разные фракции (squad=" .. squad.faction .. ", target=" .. (targetFaction or "NIL") .. ")") + return false, target:Name() .. " из другой фракции" + end + + -- Проверяем, нет ли уже приглашения + if self.squadInvites[targetSteamID] then + print("[SQUADS] Ошибка: у target уже есть приглашение") + return false, target:Name() .. " уже имеет активное приглашение" + end + + -- Сохраняем приглашение + self.squadInvites[targetSteamID] = { + squadID = squadID, + inviterSteamID = inviterSteamID, + inviterName = inviter:Name(), + time = CurTime() + } + + print("[SQUADS] Приглашение сохранено для " .. target:Name()) + + -- Отправляем приглашение + net.Start("ixSquadInvite") + net.WriteString(squadID) + net.WriteString(inviter:Name()) + net.Send(target) + + print("[SQUADS] Приглашение отправлено клиенту " .. target:Name()) + + -- Уведомляем инвайтера + net.Start("ixSquadNotify") + net.WriteString("Приглашение отправлено " .. target:Name()) + net.Send(inviter) + + -- Логирование приглашения в отряд + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local message = string.format("%s пригласил %s в отряд %s", inviter:Nick(), target:Nick(), squadID) + serverlogsPlugin:AddLog("SQUAD_INVITE", message, inviter, { + squadID = squadID, + targetSteamID = targetSteamID, + targetName = target:Nick() + }) + end + + -- Автоматическое удаление через 30 секунд + timer.Simple(30, function() + if self.squadInvites[targetSteamID] and self.squadInvites[targetSteamID].squadID == squadID then + self.squadInvites[targetSteamID] = nil + print("[SQUADS] Приглашение для " .. targetSteamID .. " истекло") + end + end) + + return true, "Приглашение отправлено" +end + +-- Принятие приглашения +function PLUGIN:AcceptSquadInvite(client) + local steamID = client:SteamID() + local invite = self.squadInvites[steamID] + + if IsAdminMode(client) then + return false, "Администратор не может вступать в отряд" + end + + if not invite then + return false, "У вас нет активных приглашений" + end + + local squad = self.squads[invite.squadID] + if not squad then + self.squadInvites[steamID] = nil + return false, "Отряд больше не существует" + end + + -- Проверяем лимит еще раз + if #squad.members >= ix.config.Get("squadMaxMembers", 16) then + self.squadInvites[steamID] = nil + return false, "Отряд уже заполнен" + end + + -- Проверяем фракцию еще раз (на случай смены персонажа) + local currentFaction = self:GetPlayerFaction(client) + if currentFaction ~= squad.faction then + self.squadInvites[steamID] = nil + return false, "Вы больше не в той же фракции" + end + + -- Добавляем в отряд + table.insert(squad.members, steamID) + squad.memberData[steamID] = { + name = client:Name(), + isLeader = false, + joinTime = os.time() + } + + self.playerSquads[steamID] = invite.squadID + self.squadInvites[steamID] = nil + + -- Уведомляем всех членов отряда + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + net.Start("ixSquadNotify") + net.WriteString(client:Name() .. " присоединился к отряду") + net.Send(member) + end + end + + self:SyncSquadToMembers(invite.squadID) + + -- Логирование вступления в отряд + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(squad.faction).name or tostring(squad.faction) + local message = string.format("%s присоединился к отряду %s (лидер: %s)", + client:Nick(), invite.squadID, squad.memberData[squad.leader].name) + serverlogsPlugin:AddLog("SQUAD_JOIN", message, client, { + squadID = invite.squadID, + inviterSteamID = invite.inviterSteamID, + inviterName = invite.inviterName, + faction = squad.faction, + factionName = factionName + }) + end + + return true, "Вы присоединились к отряду" +end + +-- Исключение из отряда +function PLUGIN:KickFromSquad(kicker, targetSteamID) + if not IsValid(kicker) then return false, "Недействительный игрок" end + + local squad, squadID = self:GetPlayerSquad(kicker) + if not squad then + return false, "Вы не состоите в отряде" + end + + if squad.leader ~= kicker:SteamID() then + return false, "Только лидер может исключать игроков" + end + + if targetSteamID == kicker:SteamID() then + return false, "Используйте расформирование отряда" + end + + -- Удаляем из отряда + for i, memberSteamID in ipairs(squad.members) do + if memberSteamID == targetSteamID then + table.remove(squad.members, i) + break + end + end + + local targetName = squad.memberData[targetSteamID].name + squad.memberData[targetSteamID] = nil + self.playerSquads[targetSteamID] = nil + + -- Уведомляем исключенного игрока и очищаем его данные + local target = player.GetBySteamID(targetSteamID) + if IsValid(target) then + -- Очищаем данные отряда на клиенте + net.Start("ixSquadSync") + net.WriteString("{}") + net.Send(target) + + net.Start("ixSquadNotify") + net.WriteString("Вы были исключены из отряда") + net.Send(target) + end + + -- Уведомляем остальных + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + net.Start("ixSquadNotify") + net.WriteString(targetName .. " был исключен из отряда") + net.Send(member) + end + end + + self:SyncSquadToMembers(squadID) + + -- Логирование исключения из отряда + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local message = string.format("%s исключил %s из отряда %s", kicker:Nick(), targetName, squadID) + serverlogsPlugin:AddLog("SQUAD_KICK", message, kicker, { + squadID = squadID, + targetSteamID = targetSteamID, + targetName = targetName + }) + end + + return true, targetName .. " исключен из отряда" +end + +-- Выход из отряда +function PLUGIN:LeaveSquad(client) + local squad, squadID = self:GetPlayerSquad(client) + if not squad then + return false, "Вы не состоите в отряде" + end + + local steamID = client:SteamID() + + -- Если лидер - расформировываем отряд + if squad.leader == steamID then + return self:DisbandSquad(squadID) + end + + -- Удаляем из отряда + for i, memberSteamID in ipairs(squad.members) do + if memberSteamID == steamID then + table.remove(squad.members, i) + break + end + end + + squad.memberData[steamID] = nil + self.playerSquads[steamID] = nil + + -- Уведомляем остальных + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + net.Start("ixSquadNotify") + net.WriteString(client:Name() .. " покинул отряд") + net.Send(member) + end + end + + self:SyncSquadToMembers(squadID) + + -- Логирование выхода из отряда + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local message = string.format("%s покинул отряд %s", client:Nick(), squadID) + serverlogsPlugin:AddLog("SQUAD_LEAVE", message, client, { + squadID = squadID + }) + end + + return true, "Вы покинули отряд" +end + +-- Система маркеров на компасе +function PLUGIN:StartSquadMarkers(squadID) + local timerName = "ixSquadMarkers_" .. squadID + + timer.Create(timerName, ix.config.Get("squadMarkerUpdateRate", 0.5), 0, function() + local squad = self.squads[squadID] + if not squad then + timer.Remove(timerName) + return + end + + -- Обновляем маркеры для каждого члена отряда + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + -- Получаем других членов отряда + local teammates = {} + for _, otherSteamID in ipairs(squad.members) do + if otherSteamID ~= memberSteamID then + local teammate = player.GetBySteamID(otherSteamID) + if IsValid(teammate) and teammate:Alive() then + table.insert(teammates, teammate) + end + end + end + + -- Отправляем позиции союзников этому игроку + if #teammates > 0 then + net.Start("ixSquadUpdateMarkers") + net.WriteUInt(#teammates, 8) + for _, teammate in ipairs(teammates) do + net.WriteVector(teammate:GetPos()) + net.WriteString(teammate:Name()) + net.WriteEntity(teammate) + end + net.Send(member) + end + end + end + end) +end + +function PLUGIN:StopSquadMarkers(squadID) + timer.Remove("ixSquadMarkers_" .. squadID) +end + +-- Синхронизация данных отряда +function PLUGIN:SyncSquadToMembers(squadID) + local squad = self.squads[squadID] + if not squad then return end + + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + net.Start("ixSquadSync") + net.WriteString(util.TableToJSON(squad)) + net.Send(member) + end + end +end + +function PLUGIN:SyncAllSquads() + for _, client in ipairs(player.GetAll()) do + local squad, squadID = self:GetPlayerSquad(client) + if squad then + net.Start("ixSquadSync") + net.WriteString(util.TableToJSON(squad)) + net.Send(client) + end + end +end + +-- Обработка отключения игрока +function PLUGIN:PlayerDisconnected(client) + if not IsValid(client) then return end + + local steamID = client:SteamID() + local squad, squadID = self:GetPlayerSquad(client) + + if squad then + -- Если это лидер - расформировываем отряд + if squad.leader == steamID then + print("[SQUADS] Лидер " .. client:Name() .. " отключился, расформирование отряда " .. squadID) + self:DisbandSquad(squadID) + else + -- Обычный участник - просто покидает отряд + print("[SQUADS] Игрок " .. client:Name() .. " отключился, удаление из отряда " .. squadID) + + -- Удаляем из списка участников + for i, memberSteamID in ipairs(squad.members) do + if memberSteamID == steamID then + table.remove(squad.members, i) + break + end + end + + squad.memberData[steamID] = nil + self.playerSquads[steamID] = nil + + -- Уведомляем остальных участников + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + net.Start("ixSquadNotify") + net.WriteString(client:Name() .. " покинул отряд (отключение)") + net.Send(member) + end + end + + -- Синхронизируем данные с оставшимися участниками + self:SyncSquadToMembers(squadID) + end + end + + -- Удаляем приглашения + self.squadInvites[steamID] = nil +end + +-- Сетевые обработчики +net.Receive("ixSquadCreate", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then + return + end + if IsAdminMode(client) then return end + + local success, message = plugin:CreateSquad(client) + + net.Start("ixSquadNotify") + net.WriteString(message) + net.Send(client) +end) + +net.Receive("ixSquadDisband", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then return end + if IsAdminMode(client) then return end + + local squad, squadID = plugin:GetPlayerSquad(client) + if squad and squad.leader == client:SteamID() then + plugin:DisbandSquad(squadID) + end +end) + +net.Receive("ixSquadInvite", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then return end + if IsAdminMode(client) then return end + + local targetSteamID = net.ReadString() + print("[SQUADS SERVER] Получен запрос на приглашение от " .. client:Name() .. " для SteamID: " .. targetSteamID) + + local target = player.GetBySteamID(targetSteamID) + print("[SQUADS SERVER] Найден игрок: " .. (IsValid(target) and target:Name() or "NIL")) + + if IsValid(target) then + local success, message = plugin:InviteToSquad(client, target) + print("[SQUADS SERVER] Результат приглашения: " .. tostring(success) .. " - " .. message) + net.Start("ixSquadNotify") + net.WriteString(message) + net.Send(client) + else + net.Start("ixSquadNotify") + net.WriteString("Игрок не найден") + net.Send(client) + end +end) + +net.Receive("ixSquadAcceptInvite", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then return end + if IsAdminMode(client) then return end + + local success, message = plugin:AcceptSquadInvite(client) + net.Start("ixSquadNotify") + net.WriteString(message) + net.Send(client) +end) + +net.Receive("ixSquadDeclineInvite", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then return end + if IsAdminMode(client) then return end + + local invite = plugin.squadInvites[client:SteamID()] + + -- Уведомляем лидера об отклонении + if invite then + local inviter = player.GetBySteamID(invite.inviterSteamID) + if IsValid(inviter) then + net.Start("ixSquadNotify") + net.WriteString(client:Name() .. " отклонил приглашение") + net.Send(inviter) + end + end + + plugin.squadInvites[client:SteamID()] = nil + net.Start("ixSquadNotify") + net.WriteString("Приглашение отклонено") + net.Send(client) +end) + +net.Receive("ixSquadKick", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then return end + if IsAdminMode(client) then return end + + local targetSteamID = net.ReadString() + local success, message = plugin:KickFromSquad(client, targetSteamID) + net.Start("ixSquadNotify") + net.WriteString(message) + net.Send(client) +end) + +net.Receive("ixSquadLeave", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then return end + if IsAdminMode(client) then return end + + local success, message = plugin:LeaveSquad(client) + net.Start("ixSquadNotify") + net.WriteString(message) + net.Send(client) +end) + +net.Receive("ixSquadPlaceMarker", function(len, client) + local plugin = ix.plugin.Get("squads") + if not plugin then return end + if not IsValid(client) or not client:Alive() then return end + + local squad, squadID = plugin:GetPlayerSquad(client) + if not squad then return end + + local pos = net.ReadVector() + if not pos then return end + + for _, memberSteamID in ipairs(squad.members) do + local member = player.GetBySteamID(memberSteamID) + if IsValid(member) then + net.Start("ixSquadWorldMarker") + net.WriteVector(pos) + net.WriteString(client:Name()) + net.Send(member) + end + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/starter_money/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/starter_money/sh_plugin.lua new file mode 100644 index 0000000..68a882e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/starter_money/sh_plugin.lua @@ -0,0 +1,31 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Starter Money" +PLUGIN.author = "Scripty" +PLUGIN.description = "Gives 10000 starting money to the first character ever created by a player." + +if (SERVER) then + function PLUGIN:OnCharacterCreated(client, character) + local steamID = client:SteamID64() + local key = "starter_money_" .. steamID + + -- Check if player already received their one-time bonus + if (!ix.data.Get(key, false)) then + -- Save that they've now received it + ix.data.Set(key, true) + + -- Set starting money + character:SetMoney(character:GetMoney() + 10000) + + -- If you want it to be *added* to existing starting money if any + -- character:SetMoney(character:GetMoney() + 10000) + -- But the user said "давало 10000", which usually means "start with 10k" + + client:Notify("Вам начислено 10,000 как новому игроку!") + end + end + + ix.log.AddType("starterBonus", function(client, amount) + return string.format("%s received a starter bonus of %d.", client:Name(), amount) + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/survival_system.lua b/garrysmod/gamemodes/militaryrp/plugins/survival_system.lua new file mode 100644 index 0000000..abd6ff2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/survival_system.lua @@ -0,0 +1,219 @@ +PLUGIN.name = "Survival System" +PLUGIN.author = "ZeMysticalTaco" +PLUGIN.description = "A survival system consisting of hunger and thirst." + +if SERVER then + function PLUGIN:OnCharacterCreated(client, character) + character:SetData("hunger", 100) + character:SetData("thirst", 100) + end + + function PLUGIN:PlayerLoadedCharacter(client, character) + timer.Simple(0.25, function() + client:SetLocalVar("hunger", character:GetData("hunger", 100)) + client:SetLocalVar("thirst", character:GetData("thirst", 100)) + end) + end + + function PLUGIN:CharacterPreSave(character) + local client = character:GetPlayer() + + if (IsValid(client)) then + character:SetData("hunger", client:GetLocalVar("hunger", 0)) + character:SetData("thirst", client:GetLocalVar("thirst", 0)) + end + end + + local playerMeta = FindMetaTable("Player") + + function playerMeta:SetHunger(amount) + local char = self:GetCharacter() + + if (char) then + amount = math.Clamp(amount, 0, 100) + char:SetData("hunger", amount) + self:SetLocalVar("hunger", amount) + end + end + + function playerMeta:SetThirst(amount) + local char = self:GetCharacter() + + if (char) then + amount = math.Clamp(amount, 0, 100) + char:SetData("thirst", amount) + self:SetLocalVar("thirst", amount) + end + end + + function playerMeta:TickThirst(amount) + local char = self:GetCharacter() + + if (char) then + char:SetData("thirst", char:GetData("thirst", 100) - amount) + self:SetLocalVar("thirst", char:GetData("thirst", 100) - amount) + + if char:GetData("thirst", 100) < 0 then + char:SetData("thirst", 0) + self:SetLocalVar("thirst", 0) + end + end + end + + function playerMeta:TickHunger(amount) + local char = self:GetCharacter() + + if (char) then + char:SetData("hunger", char:GetData("hunger", 100) - amount) + self:SetLocalVar("hunger", char:GetData("hunger", 100) - amount) + + if char:GetData("hunger", 100) < 0 then + char:SetData("hunger", 0) + self:SetLocalVar("hunger", 0) + end + end + end + + function PLUGIN:PlayerTick(ply) + if not ply:Alive() then return end + + local char = ply:GetCharacter() + if not char then return end + + if ply.IsAdminMode and ply:IsAdminMode() then return end + + if ply:GetNetVar("hungertick", 0) <= CurTime() then + ply:SetNetVar("hungertick", 200 + CurTime()) + ply:TickHunger(1) + end + + if ply:GetNetVar("thirsttick", 0) <= CurTime() then + ply:SetNetVar("thirsttick", 300 + CurTime()) + ply:TickThirst(1) + end + + if ply:GetNetVar("survivaldamage", 0) <= CurTime() then + ply:SetNetVar("survivaldamage", 1 + CurTime()) + + local hunger = ply:GetLocalVar("hunger", 100) + local thirst = ply:GetLocalVar("thirst", 100) + local damage = 0 + + if hunger <= 0 and thirst <= 0 then + damage = 2 + elseif hunger <= 0 or thirst <= 0 then + damage = 1 + end + + if damage > 0 then + local dmg = DamageInfo() + dmg:SetDamage(damage) + dmg:SetDamageType(DMG_GENERIC) + dmg:SetAttacker(ply) + dmg:SetInflictor(ply) + ply:TakeDamageInfo(dmg) + end + end + end + + function PLUGIN:PlayerDeath(ply) + local char = ply:GetCharacter() + if char then + char:SetData("hunger", 20) + char:SetData("thirst", 20) + ply:SetLocalVar("hunger", 20) + ply:SetLocalVar("thirst", 20) + end + end +end + +local playerMeta = FindMetaTable("Player") + +function playerMeta:GetHunger() + local char = self:GetCharacter() + + if (char) then + return char:GetData("hunger", 100) + end +end + +function playerMeta:GetThirst() + local char = self:GetCharacter() + + if (char) then + return char:GetData("thirst", 100) + end +end + +function PLUGIN:AdjustStaminaOffset(client, offset) + local hunger = client:GetHunger() or 100 + local thirst = client:GetThirst() or 100 + local multiplier = 1 + + if (hunger <= 0) or (thirst <= 0) then + multiplier = 0.2 + elseif (hunger <= 30) or (thirst <= 30) then + multiplier = 0.5 + elseif (hunger <= 60) or (thirst <= 60) then + multiplier = 0.8 + end + + if (multiplier != 1) then + if (offset > 0) then + return offset * multiplier + else + return offset * (2 - multiplier) + end + end +end + +ix.command.Add("charsetthirst", { + adminOnly = true, + arguments = { + ix.type.string, + ix.type.number, + }, + OnRun = function(self, client, target, thirst) + local target = ix.util.FindPlayer(target) + local thirst = tonumber(thirst) + + if !target then + client:Notify("Invalid Target!") + return + end + target:SetThirst(thirst) + + if client == target then + client:Notify("You have set your thrist to "..thirst) + else + client:Notify("You have set "..target:Name().."'s thirst to "..thirst) + target:Notify(client:Name().." has set your thirst to "..thirst) + end + end +}) + +ix.command.Add("charsethunger", { + adminOnly = true, + arguments = { + ix.type.string, + ix.type.number, + }, + OnRun = function(self, client, target, hunger) + local target = ix.util.FindPlayer(target) + local hunger = tonumber(hunger) + + if !target then + client:Notify("Invalid Target!") + return + end + + target:SetHunger(hunger) + + if client == target then + client:Notify("You have set your hunger to "..hunger) + else + client:Notify("You have set "..target:Name().."'s hunger to "..hunger) + target:Notify(client:Name().." has set your hunger to "..hunger) + end + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/vehicles/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/vehicles/cl_plugin.lua new file mode 100644 index 0000000..78b4aa8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/vehicles/cl_plugin.lua @@ -0,0 +1,923 @@ +local PLUGIN = PLUGIN + +-- Хранение выбранного loadout для каждой техники +local selectedLoadouts = {} + +-- Цветовая схема (такая же как в арсенале) +local COLOR_BG_DARK = Color(3, 5, 4) +local COLOR_BG_MEDIUM = Color(8, 12, 10) +local COLOR_BG_LIGHT = Color(12, 18, 14) +local COLOR_PRIMARY = Color(27, 94, 32) +local COLOR_PRIMARY_DARK = Color(15, 60, 18) +local COLOR_ACCENT = Color(56, 102, 35) +local COLOR_TEXT_PRIMARY = Color(165, 214, 167) +local COLOR_TEXT_SECONDARY = Color(102, 187, 106) +local COLOR_DANGER = Color(136, 14, 14) +local COLOR_WARNING = Color(191, 130, 0) +local COLOR_BORDER = Color(15, 60, 18, 60) + +-- Вспомогательная функция для рисования кругов +if not draw.Circle then + function draw.Circle(x, y, radius, color) + local segmentCount = math.max(16, radius) + surface.SetDrawColor(color or color_white) + + local circle = {} + for i = 0, segmentCount do + local angle = math.rad((i / segmentCount) * 360) + table.insert(circle, { + x = x + math.cos(angle) * radius, + y = y + math.sin(angle) * radius + }) + end + surface.DrawPoly(circle) + end +end + +if not surface.DrawCircle then + function surface.DrawCircle(x, y, radius, color) + draw.Circle(x, y, radius, color) + end +end + +PLUGIN.clientFactionPoints = {} +PLUGIN.selectedLoadouts = {} -- Глобальное хранилище выбранных loadout'ов + +-- Проверка доступности техники для игрока +function PLUGIN:CanPlayerAccessVehicle(vehicleConfig) + local character = LocalPlayer():GetCharacter() + if not character then return false end + + local playerSubdivision = character:GetPodr() + + -- Если нет ограничений по подразделениям - доступно всем + if not vehicleConfig.allowedPodr or not istable(vehicleConfig.allowedPodr) then + return true + end + + -- Проверяем доступность для подразделения игрока + return vehicleConfig.allowedPodr[playerSubdivision] ~= nil +end + +function PLUGIN:GetFactions() + if not self.cachedFactions then + if not self.config then + -- Fallback to global if self.config is missing for some reason + self.config = PLUGIN.config or {} + end + if not self.config or not istable(self.config) then + self.cachedFactions = {} + else + self.cachedFactions = self.config.getFactions and self.config.getFactions() or self.config.factions or {} + end + end + return self.cachedFactions +end + +-- Сетевые сообщения +net.Receive("LVS_SyncFactionPoints", function() + local faction = net.ReadUInt(8) + local points = net.ReadUInt(32) + PLUGIN.clientFactionPoints[faction] = points +end) + +-- Открытие меню наземной техники +net.Receive("LVS_OpenGroundMenu", function() + PLUGIN:OpenVehicleMenu("ground") +end) + +-- Открытие меню воздушной техники +net.Receive("LVS_OpenAirMenu", function() + PLUGIN:OpenVehicleMenu("air") +end) + +local function UpdateVehicleInfo(vehicleConfig, vehicleID, infoContent, modelPanel, vehicleName, vehicleDesc, vehiclePrice, vehicleCooldown, spawnBtn, factionPoints, vehicleType, returnBtn, loadoutPanel, updateLoadoutCallback) + if not vehicleConfig then return end + + -- Инициализируем выбранный loadout если его нет (дефолтный = 1) + if not PLUGIN.selectedLoadouts[vehicleID] then + PLUGIN.selectedLoadouts[vehicleID] = 1 + end + + local faction = LocalPlayer():Team() + local factions = PLUGIN:GetFactions() + local factionConfig = factions and factions[faction] + local character = LocalPlayer():GetCharacter() + local playerSubdivision = character and character:GetPodr() or 1 + + -- Проверяем доступность по подразделению + local canSpawnBySubdivision = true + local subdivisionMessage = "" + + -- Проверяем разрешенные подразделения и специализации + if vehicleConfig.allowedPodr and istable(vehicleConfig.allowedPodr) then + local playerSpec = character and character:GetSpec() or 1 + local podrConfig = vehicleConfig.allowedPodr[playerSubdivision] + + if podrConfig == nil then + -- Подразделение не в списке + canSpawnBySubdivision = false + elseif podrConfig == true then + -- Разрешено всем специализациям этого подразделения + canSpawnBySubdivision = true + elseif istable(podrConfig) then + -- Проверяем специализацию + canSpawnBySubdivision = table.HasValue(podrConfig, playerSpec) + end + + if not canSpawnBySubdivision then + -- Получаем названия разрешенных подразделений и специализаций + local podrNames = {} + local factionTable = ix.faction.Get(faction) + + if factionTable and factionTable.Podr and factionTable.Spec then + for podrID, config in pairs(vehicleConfig.allowedPodr) do + local podrData = factionTable.Podr[podrID] + if podrData and podrData.name then + if config == true then + table.insert(podrNames, podrData.name) + elseif istable(config) then + local specNames = {} + for _, specID in ipairs(config) do + local specData = factionTable.Spec[specID] + if specData and specData.name then + table.insert(specNames, specData.name) + end + end + if #specNames > 0 then + table.insert(podrNames, podrData.name .. " (" .. table.concat(specNames, ", ") .. ")") + end + end + end + end + end + + if #podrNames > 0 then + subdivisionMessage = "Только: " .. table.concat(podrNames, "; ") + else + subdivisionMessage = "Недоступно для вашего подразделения" + end + end + end + + -- Показываем элементы + modelPanel:SetVisible(true) + vehicleName:SetVisible(true) + vehicleDesc:SetVisible(true) + vehiclePrice:SetVisible(true) + vehicleCooldown:SetVisible(true) + spawnBtn:SetVisible(true) + returnBtn:SetVisible(true) + + -- Устанавливаем модель + modelPanel:SetModel(vehicleConfig.model) + + local ent = modelPanel.Entity + if IsValid(ent) then + local center = ent:OBBCenter() + local distance = ent:BoundingRadius() * 1.5 + modelPanel:SetLookAt(center) + modelPanel:SetCamPos(center + Vector(distance, distance, distance)) + modelPanel:SetFOV(50) + end + + -- Устанавливаем текстовую информацию + vehicleName:SetText(vehicleConfig.name) + vehicleDesc:SetText(vehicleConfig.description) + vehiclePrice:SetText("Стоимость: " .. vehicleConfig.price .. " очков техники") + + -- Проверяем кд и доступность + local vehiclesData = character and character:GetData("lvs_vehicles", {}) or {} + local lastSpawn = vehiclesData[vehicleID] + local cooldown = lastSpawn and lastSpawn > os.time() and (lastSpawn - os.time()) or 0 + + -- Проверяем, есть ли активная техника этого типа + local hasActiveVehicle = PLUGIN:HasActiveVehicle(vehicleID, vehicleType) + local refundAmount = math.floor(vehicleConfig.price * 0.8) + + -- Настраиваем кнопку возврата + if hasActiveVehicle then + returnBtn:SetEnabled(true) + returnBtn:SetText("") + returnBtn.Paint = function(s, w, h) + local col = s:IsHovered() and Color(185, 60, 60) or Color(165, 85, 60) + draw.RoundedBox(6, 0, 0, w, h, col) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30)) + end + + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("◄ ВЕРНУТЬ (" .. refundAmount .. " очков)", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + -- Отправляем запрос на возврат техники при нажатии кнопки + returnBtn.DoClick = function() + local veh = PLUGIN:GetActiveVehicle(vehicleID, vehicleType) + if IsValid(veh) then + net.Start("LVS_RequestReturn") + net.WriteUInt(veh:EntIndex(), 16) + net.SendToServer() + else + LocalPlayer():Notify("Активная техника не найдена") + end + end + else + returnBtn:SetEnabled(false) + returnBtn:SetText("") + returnBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60)) + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("НЕТ АКТИВНОЙ ТЕХНИКИ", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + if not canSpawnBySubdivision then + vehicleCooldown:SetText(subdivisionMessage) + vehicleCooldown:SetTextColor(COLOR_DANGER) + spawnBtn:SetEnabled(false) + spawnBtn:SetText("") + spawnBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60)) + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("НЕДОСТУПНО", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + elseif cooldown > 0 then + local minutes = math.floor(cooldown / 60) + local seconds = math.floor(cooldown % 60) + vehicleCooldown:SetText("Перезарядка: " .. string.format("%02d:%02d", minutes, seconds)) + vehicleCooldown:SetTextColor(COLOR_WARNING) + spawnBtn:SetEnabled(false) + spawnBtn:SetText("") + spawnBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60)) + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("В КД", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + elseif factionPoints < vehicleConfig.price then + vehicleCooldown:SetText("Недостаточно очков техники") + vehicleCooldown:SetTextColor(COLOR_WARNING) + spawnBtn:SetEnabled(false) + spawnBtn:SetText("") + spawnBtn.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, Color(60, 60, 60)) + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("НЕТ ОЧКОВ", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + else + vehicleCooldown:SetText("Готов к вызову") + vehicleCooldown:SetTextColor(COLOR_ACCENT) + spawnBtn:SetEnabled(true) + spawnBtn:SetText("") + spawnBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY + draw.RoundedBox(6, 0, 0, w, h, col) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30)) + end + + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("ВЫЗВАТЬ ТЕХНИКУ ►", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + spawnBtn.DoClick = function() + local loadoutID = PLUGIN.selectedLoadouts[vehicleID] or 1 + + print("[LVS] Запрос спавна техники " .. vehicleConfig.name .. " с loadout ID: " .. loadoutID) + PLUGIN:RequestSpawnVehicle(vehicleID, vehicleType, loadoutID) + end + end + + -- Обновляем панель loadout если она передана + if IsValid(loadoutPanel) and updateLoadoutCallback then + updateLoadoutCallback(vehicleConfig, vehicleID, loadoutPanel) + end +end + + +function PLUGIN:GetActiveVehicle(vehicleID, vehicleType) + local ply = LocalPlayer() + if not IsValid(ply) then return nil end + + for _, ent in ipairs(ents.GetAll()) do + if IsValid(ent) then + local nwID = ent:GetNWString("LVS_VehicleID", "") + local nwType = ent:GetNWString("LVS_VehicleType", "") + local nwOwner = ent:GetNWString("LVS_OwnerSteamID", "") + + if nwID == tostring(vehicleID) and nwType == tostring(vehicleType) then + if ply:IsAdmin() or ply:SteamID() == nwOwner then + return ent + end + end + end + end + + return nil +end + +-- Функция обновления кд +function PLUGIN:UpdateVehicleCooldown(panel, vehicleID) + local character = LocalPlayer():GetCharacter() + if not character then return end + + local vehicles = character:GetData("lvs_vehicles", {}) + local lastSpawn = vehicles[vehicleID] + + if lastSpawn and lastSpawn > os.time() then + panel.cooldown = lastSpawn - os.time() + + timer.Create("LVS_Cooldown_" .. vehicleID, 1, 0, function() + if not IsValid(panel) then + timer.Remove("LVS_Cooldown_" .. vehicleID) + return + end + + panel.cooldown = panel.cooldown - 1 + if panel.cooldown <= 0 then + panel.cooldown = 0 + timer.Remove("LVS_Cooldown_" .. vehicleID) + end + end) + else + panel.cooldown = 0 + end +end + +-- Основная функция создания меню в стиле Military RP +function PLUGIN:OpenVehicleMenu(vehicleType) + -- Если окно уже открыто — закроем старое, чтобы не было дубликатов + if IsValid(PLUGIN._vehicleMenuFrame) then + PLUGIN._vehicleMenuFrame:Remove() + PLUGIN._vehicleMenuFrame = nil + end + + local scrW, scrH = ScrW(), ScrH() + local faction = LocalPlayer():Team() + local factions = PLUGIN:GetFactions() + local factionConfig = factions and factions[faction] + + if not factionConfig then + return + end + + local vehicles = vehicleType == "ground" and factionConfig.groundVehicles or factionConfig.airVehicles + + -- Фильтруем технику по доступности + local availableVehicles = {} + for vehicleID, vehicleConfig in pairs(vehicles) do + if self:CanPlayerAccessVehicle(vehicleConfig) then + availableVehicles[vehicleID] = vehicleConfig + end + end + + -- Если нет доступной техники - не открываем меню + if table.Count(availableVehicles) == 0 then + LocalPlayer():Notify("Техника вам не доступна") + return + end + + local frame = vgui.Create("DFrame") + frame:SetSize(scrW, scrH) + frame:SetPos(0, 0) + frame:SetTitle("") + frame:SetDraggable(false) + frame:ShowCloseButton(false) + frame:MakePopup() + + local factionPoints = self.clientFactionPoints[faction] or 0 + + -- Градиентный фон с зеленым оттенком + frame.Paint = function(s, w, h) + -- Основной фон + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawRect(0, 0, w, h) + + -- Градиент сверху + local gradHeight = 300 + for i = 0, gradHeight do + local alpha = (1 - i/gradHeight) * 40 + surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha) + surface.DrawRect(0, i, w, 1) + end + + -- Верхняя панель + surface.SetDrawColor(COLOR_BG_MEDIUM) + surface.DrawRect(0, 0, w, 100) + + -- Акцентная линия + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawRect(0, 100, w, 3) + + -- Декоративные элементы + surface.SetDrawColor(COLOR_BORDER) + for i = 0, w, 200 do + surface.DrawRect(i, 0, 1, 100) + end + + -- Заголовок + local title = vehicleType == "ground" and "◆ НАЗЕМНАЯ ТЕХНИКА ◆" or "◆ ВОЗДУШНАЯ ТЕХНИКА ◆" + local subtitle = vehicleType == "ground" and "СИСТЕМА ВЫЗОВА ТЕХНИКИ" or "СИСТЕМА ВЫЗОВА АВИАЦИИ" + draw.SimpleText(title, "ixMenuButtonFont", w/2, 35, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(subtitle, "ixSmallFont", w/2, 68, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Кнопка закрытия + local closeBtn = vgui.Create("DButton", frame) + closeBtn:SetText("") + closeBtn:SetSize(42, 42) + closeBtn:SetPos(scrW - 60, 18) + closeBtn.Paint = function(s, w, h) + local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7) + + draw.RoundedBox(4, 0, 0, w, h, col) + surface.SetDrawColor(COLOR_BG_DARK) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + surface.SetDrawColor(COLOR_TEXT_PRIMARY) + surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7) + surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7) + + if s:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30)) + end + end + closeBtn.DoClick = function() + frame:Close() + end + + -- Панель информации об очках + local infoPanel = vgui.Create("DPanel", frame) + infoPanel:SetSize(300, 70) + infoPanel:SetPos(40, 115) + infoPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_LIGHT) + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + draw.SimpleText("ОЧКИ ТЕХНИКИ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(factionPoints, "ixMenuButtonFont", 20, 32, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + -- Иконка + surface.SetDrawColor(COLOR_PRIMARY) + draw.NoTexture() + surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY) + end + + local colWidth = (scrW - 120) / 2 + local startY = 205 + + -- Панель списка техники + local vehiclesPanel = vgui.Create("DPanel", frame) + vehiclesPanel:SetSize(colWidth, scrH - startY - 40) + vehiclesPanel:SetPos(40, startY) + vehiclesPanel.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local vehiclesHeader = vgui.Create("DPanel", vehiclesPanel) + vehiclesHeader:SetSize(vehiclesPanel:GetWide(), 45) + vehiclesHeader:SetPos(0, 0) + vehiclesHeader.Paint = function(s, w, h) + draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false) + + draw.SimpleText("▣ ДОСТУПНАЯ ТЕХНИКА", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(0, h-2, w, 2) + end + + -- Панель информации о технике + local infoPanel2 = vgui.Create("DPanel", frame) + infoPanel2:SetSize(colWidth, scrH - startY - 40) + infoPanel2:SetPos(scrW - colWidth - 40, startY) + infoPanel2.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM) + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + local infoHeader = vgui.Create("DPanel", infoPanel2) + infoHeader:SetSize(infoPanel2:GetWide(), 45) + infoHeader:SetPos(0, 0) + infoHeader.Paint = function(s, w, h) + draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false) + + draw.SimpleText("⬢ ИНФОРМАЦИЯ", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(0, h-2, w, 2) + end + + -- Список техники + local vehiclesScroll = vgui.Create("DScrollPanel", vehiclesPanel) + vehiclesScroll:SetSize(vehiclesPanel:GetWide() - 20, vehiclesPanel:GetTall() - 60) + vehiclesScroll:SetPos(10, 50) + + local sbar = vehiclesScroll:GetVBar() + sbar:SetHideButtons(true) + function sbar:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK) + end + function sbar.btnGrip:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY) + end + + -- Панель информации + local infoContent = vgui.Create("DPanel", infoPanel2) + infoContent:SetSize(infoPanel2:GetWide() - 20, infoPanel2:GetTall() - 60) + infoContent:SetPos(10, 50) + infoContent.selectedVehicle = nil + infoContent.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT) + + if not s.selectedVehicle then + draw.SimpleText("⚠ ВЫБЕРИТЕ ТЕХНИКУ", "ixSmallFont", w/2, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + -- Модель техники + local modelPanel = vgui.Create("DModelPanel", infoContent) + modelPanel:SetSize(400, 200) + modelPanel:SetPos(10, 10) + modelPanel:SetVisible(false) + + -- Информация о технике + local vehicleName = vgui.Create("DLabel", infoContent) + vehicleName:SetFont("ixSmallFont") + vehicleName:SetTextColor(color_white) + vehicleName:SetPos(10, 220) + vehicleName:SetSize(400, 20) + vehicleName:SetVisible(false) + + local vehicleDesc = vgui.Create("DLabel", infoContent) + vehicleDesc:SetFont("ixSmallFont") + vehicleDesc:SetTextColor(Color(200, 200, 200)) + vehicleDesc:SetPos(10, 245) + vehicleDesc:SetSize(400, 40) + vehicleDesc:SetVisible(false) + + local vehiclePrice = vgui.Create("DLabel", infoContent) + vehiclePrice:SetFont("ixSmallFont") + vehiclePrice:SetTextColor(color_white) + vehiclePrice:SetPos(10, 290) + vehiclePrice:SetSize(400, 20) + vehiclePrice:SetVisible(false) + + local vehicleCooldown = vgui.Create("DLabel", infoContent) + vehicleCooldown:SetFont("ixSmallFont") + vehicleCooldown:SetTextColor(color_white) + vehicleCooldown:SetPos(10, 315) + vehicleCooldown:SetSize(400, 20) + vehicleCooldown:SetVisible(false) + + -- Кнопка вызова + local spawnBtn = vgui.Create("DButton", infoContent) + spawnBtn:SetSize(200, 35) + spawnBtn:SetPos(10, 350) + spawnBtn:SetText("") + spawnBtn:SetVisible(false) + + -- Кнопка возврата + local returnBtn = vgui.Create("DButton", infoContent) + returnBtn:SetSize(200, 35) + returnBtn:SetPos(220, 350) + returnBtn:SetText("") + returnBtn:SetVisible(false) + + -- Используем глобальное хранилище selectedLoadouts + local selectedLoadouts = PLUGIN.selectedLoadouts + + -- Создаем панель выбора loadout (скрыта по умолчанию) + local loadoutPanel = vgui.Create("DPanel", infoContent) + loadoutPanel:SetSize(infoContent:GetWide() - 20, 120) + loadoutPanel:SetPos(10, 400) + loadoutPanel:SetVisible(false) + loadoutPanel.Paint = function(s, w, h) + draw.RoundedBox(6, 0, 0, w, h, ColorAlpha(COLOR_BG_DARK, 200)) + surface.SetDrawColor(COLOR_PRIMARY) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + draw.SimpleText("⚙ ВООРУЖЕНИЕ:", "ixSmallFont", 10, 8, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + -- Функция обновления панели loadout + local function UpdateLoadoutPanel(vehicleConfig, vehicleID, panel) + if not IsValid(panel) then return end + + for _, child in pairs(panel:GetChildren()) do + child:Remove() + end + + local loadoutConfig = vehicleConfig.loadout + if not istable(loadoutConfig) or table.Count(loadoutConfig) == 0 then + panel:SetVisible(false) + return + end + + panel:SetVisible(true) + + local btnWidth = 130 + local btnHeight = 80 + local gap = 10 + local startX = 10 + local startY = 30 + + -- соберём пары в массив и отсортируем по ID, чтобы кнопки шли в правильном порядке + local entries = {} + for loadoutName, loadoutID in pairs(loadoutConfig) do + if isnumber(loadoutID) then + table.insert(entries, {name = loadoutName, id = loadoutID}) + end + end + table.sort(entries, function(a, b) return a.id < b.id end) + + -- вставим пустые слоты для пропущенных ID, чтобы кнопки располагались на своих позициях + local filled = {} + local nextIdx = 1 + local maxID = entries[#entries] and entries[#entries].id or 0 + for id = 1, maxID do + if entries[nextIdx] and entries[nextIdx].id == id then + table.insert(filled, entries[nextIdx]) + nextIdx = nextIdx + 1 + else + table.insert(filled, {name = "", id = id}) + end + end + + for idx, entry in ipairs(filled) do + local btn = vgui.Create("DButton", panel) + btn:SetSize(btnWidth, btnHeight) + btn:SetPos(startX + (idx - 1) * (btnWidth + gap), startY) + btn:SetText("") + btn.variantIndex = idx + btn.loadoutID = entry.id + btn.loadoutName = tostring(entry.name) + + btn.Paint = function(s, w, h) + local isSelected = PLUGIN.selectedLoadouts[vehicleID] == s.loadoutID + local bgColor = isSelected and COLOR_PRIMARY or COLOR_BG_MEDIUM + + if s:IsHovered() and not isSelected then + bgColor = ColorAlpha(COLOR_PRIMARY, 100) + end + + draw.RoundedBox(4, 0, 0, w, h, bgColor) + + if isSelected then + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawOutlinedRect(0, 0, w, h, 3) + else + surface.SetDrawColor(COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + draw.SimpleText(s.loadoutName or ("Вариант " .. s.variantIndex), "ixSmallFont", w/2, 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("ID: " .. tostring(s.loadoutID), "DermaDefault", w/2, 50, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER) + + if isSelected then + draw.SimpleText("✓", "ixMenuButtonFont", w/2, h - 20, COLOR_ACCENT, TEXT_ALIGN_CENTER) + end + end + + btn.DoClick = function() + print("[LVS] Пользователь выбрал loadout '" .. btn.loadoutName .. "' (ID=" .. btn.loadoutID .. ") для " .. vehicleConfig.name) + PLUGIN.selectedLoadouts[vehicleID] = btn.loadoutID + + for _, child in pairs(panel:GetChildren()) do + child:Remove() + end + + UpdateLoadoutPanel(vehicleConfig, vehicleID, panel) + end + end + end + + -- Привяжем frame к плагину, чтобы можно было предотвращать дубликаты + PLUGIN._vehicleMenuFrame = frame + + -- Функция создания строки техники + local function CreateVehicleRow(parent, vehicleID, vehicleConfig) + local row = vgui.Create("DButton", parent) + row:SetSize(parent:GetWide() - 10, 70) + row:SetText("") + row.vehicleID = vehicleID + row.vehicleConfig = vehicleConfig + + local isHovered = false + row.Paint = function(s, w, h) + local isSelected = infoContent.selectedVehicle == vehicleID + local canAfford = factionPoints >= vehicleConfig.price + local cooldown = s.cooldown or 0 + local hasActive = PLUGIN:HasActiveVehicle(vehicleID, vehicleType) + + -- Фон строки + local bgColor = isSelected and COLOR_PRIMARY_DARK or COLOR_BG_LIGHT + if isHovered and not isSelected then + bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10) + end + draw.RoundedBox(6, 0, 0, w, h, bgColor) + + -- Боковая полоска + local statusColor = hasActive and Color(56, 142, 200) or (canAfford and COLOR_PRIMARY or COLOR_DANGER) + draw.RoundedBoxEx(6, 0, 0, 5, h, statusColor, true, false, true, false) + + -- Рамка + surface.SetDrawColor(isSelected and COLOR_ACCENT or COLOR_BORDER) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Акцентная линия при наведении + if isHovered then + surface.SetDrawColor(COLOR_ACCENT) + surface.DrawRect(5, h-3, w-10, 3) + end + + -- Название техники + draw.SimpleText(vehicleConfig.name, "ixSmallFont", 15, 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT) + + -- Цена + local priceColor = canAfford and COLOR_ACCENT or COLOR_WARNING + draw.SimpleText(vehicleConfig.price .. " очков", "ixSmallFont", 15, 35, priceColor, TEXT_ALIGN_LEFT) + + -- Статус + local statusText = "" + local statusTextColor = COLOR_TEXT_SECONDARY + + if hasActive then + statusText = "● АКТИВНА" + statusTextColor = Color(100, 180, 255) + elseif cooldown > 0 then + local minutes = math.floor(cooldown / 60) + local seconds = math.floor(cooldown % 60) + statusText = "КД: " .. string.format("%02d:%02d", minutes, seconds) + statusTextColor = COLOR_WARNING + elseif not canAfford then + statusText = "НЕТ ОЧКОВ" + statusTextColor = COLOR_WARNING + else + statusText = "● ГОТОВ" + statusTextColor = COLOR_ACCENT + end + + draw.SimpleText(statusText, "ixSmallFont", w - 15, h/2, statusTextColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + row.OnCursorEntered = function() isHovered = true end + row.OnCursorExited = function() isHovered = false end + + row.DoClick = function(s) + infoContent.selectedVehicle = vehicleID + UpdateVehicleInfo(vehicleConfig, vehicleID, infoContent, modelPanel, vehicleName, vehicleDesc, vehiclePrice, vehicleCooldown, spawnBtn, factionPoints, vehicleType, returnBtn, loadoutPanel, UpdateLoadoutPanel) + end + + -- Обновляем кд + PLUGIN:UpdateVehicleCooldown(row, vehicleID) + + return row + end + + -- Создаем строки с техникой (только доступной) + for vehicleID, vehicleConfig in SortedPairsByMemberValue(availableVehicles, "price") do + local row = CreateVehicleRow(vehiclesScroll, vehicleID, vehicleConfig) + row:Dock(TOP) + row:DockMargin(0, 0, 0, 5) + end + + -- Функция обновления интерфейса + local function UpdateInterface() + if not IsValid(frame) then return end + + local currentPoints = PLUGIN.clientFactionPoints[faction] or 0 + factionPoints = currentPoints + + -- Обновляем отображение очков + if IsValid(pointsPanel) then + pointsPanel.Paint = function(s, w, h) + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + draw.SimpleText("ОЧКИ ТЕХНИКИ", "ixSmallFont", w/2, 11, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(currentPoints, "ixSmallFont", w/2, 28, Color(100, 255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + -- Обновляем информацию о выбранной технике + if infoContent.selectedVehicle then + local vehicleConfig = availableVehicles[infoContent.selectedVehicle] + if vehicleConfig then + UpdateVehicleInfo(vehicleConfig, infoContent.selectedVehicle, infoContent, modelPanel, vehicleName, vehicleDesc, vehiclePrice, vehicleCooldown, spawnBtn, currentPoints, vehicleType, returnBtn) + end + end + end + + -- Таймер для обновления интерфейса + timer.Create("LVS_InterfaceUpdate_" .. vehicleType, 1, 0, function() + if not IsValid(frame) then + timer.Remove("LVS_InterfaceUpdate_" .. vehicleType) + return + end + UpdateInterface() + end) + + -- Автоматически выбираем первую технику + timer.Simple(0.1, function() + if IsValid(vehiclesScroll) and vehiclesScroll:GetCanvas():GetChildren()[1] then + local firstRow = vehiclesScroll:GetCanvas():GetChildren()[1] + if firstRow.DoClick then + firstRow:DoClick() + end + end + end) + + frame.OnCursorEntered = function(s) + s:MoveToFront() + end + + frame.OnClose = function() + timer.Remove("LVS_InterfaceUpdate_" .. vehicleType) + if PLUGIN._vehicleMenuFrame == frame then + PLUGIN._vehicleMenuFrame = nil + end + end +end + +-- Запрос спавна техники +function PLUGIN:RequestSpawnVehicle(vehicleID, vehicleType, loadoutVariant) + print("[LVS] Отправка запроса спавна на сервер - vehicleID: " .. vehicleID .. ", loadoutVariant: " .. (loadoutVariant or 0)) + net.Start("LVS_RequestSpawn") + net.WriteString(vehicleID) + net.WriteString(vehicleType) + net.WriteUInt(loadoutVariant or 0, 8) + net.SendToServer() +end + +-- Запрос возврата техники +function PLUGIN:RequestReturnVehicle(vehicleID, vehicleType) + local vehicle = self:GetActiveVehicle(vehicleID, vehicleType) + + if IsValid(vehicle) then + net.Start("LVS_RequestReturn") + net.WriteUInt(vehicle:EntIndex(), 16) + net.SendToServer() + else + LocalPlayer():Notify("Активная техника не найдена") + end +end + +net.Receive("LVS_SyncVehicleInfo", function() + local vehicle = net.ReadEntity() + local ownerSteamID = net.ReadString() + local vehicleID = net.ReadString() + local vehicleType = net.ReadString() + + if IsValid(vehicle) then + vehicle.lvsVehicleInfo = { + ownerSteamID = ownerSteamID, + vehicleID = vehicleID, + vehicleType = vehicleType + } + end +end) + +-- Получаем таблицу моделей от сервера и подставляем в конфиг при отсутствии +net.Receive("LVS_SyncVehicleModels", function() + local mapping = net.ReadTable() + if not istable(mapping) then return end + + for factionID, data in pairs(mapping) do + local factionConfig = PLUGIN:GetFactions()[factionID] + if not factionConfig then continue end + + if istable(data.ground) then + for id, mdl in pairs(data.ground) do + if factionConfig.groundVehicles and factionConfig.groundVehicles[id] then + if not factionConfig.groundVehicles[id].model or factionConfig.groundVehicles[id].model == "" then + factionConfig.groundVehicles[id].model = mdl + end + end + end + end + + if istable(data.air) then + for id, mdl in pairs(data.air) do + if factionConfig.airVehicles and factionConfig.airVehicles[id] then + if not factionConfig.airVehicles[id].model or factionConfig.airVehicles[id].model == "" then + factionConfig.airVehicles[id].model = mdl + end + end + end + end + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/cl_init.lua b/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/cl_init.lua new file mode 100644 index 0000000..47a0022 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/cl_init.lua @@ -0,0 +1,35 @@ +include("shared.lua") + +function ENT:Draw() + self:DrawModel() + + local pos = self:GetPos() + local ang = self:GetAngles() + + pos = pos + ang:Up() * 40 + + ang:RotateAroundAxis(ang:Right(), -90) + ang:RotateAroundAxis(ang:Up(), 90) + + cam.Start3D2D(pos, ang, 0.15) + local terminalType = self:GetTerminalType() + local title = "Терминал техники" + + if terminalType == "ground" then + title = "НАЗЕМНАЯ ТЕХНИКА" + elseif terminalType == "air" then + title = "ВОЗДУШНАЯ ТЕХНИКА" + end + + draw.SimpleTextOutlined( + title, + "DermaLarge", + 0, 0, + Color(255, 255, 255), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER, + 2, + Color(0, 0, 0) + ) + cam.End3D2D() +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/init.lua b/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/init.lua new file mode 100644 index 0000000..7a7b18b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/init.lua @@ -0,0 +1,37 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +function ENT:Initialize() + self:SetModel(self:GetModel() or "models/props_c17/oildrum001.mdl") + self:PhysicsInit(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:Use(activator, caller) + if not IsValid(activator) or not activator:IsPlayer() then return end + + local vehiclePlugin = ix.plugin.list["vehicles"] + if vehiclePlugin and vehiclePlugin.GetFactionPoints then + net.Start("LVS_SyncFactionPoints") + net.WriteUInt(activator:Team(), 8) + net.WriteUInt(vehiclePlugin:GetFactionPoints(activator:Team()) or 0, 32) + net.Send(activator) + end + + local terminalType = self:GetTerminalType() + if terminalType == "ground" then + net.Start("LVS_OpenGroundMenu") + net.Send(activator) + elseif terminalType == "air" then + net.Start("LVS_OpenAirMenu") + net.Send(activator) + end +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/shared.lua b/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/shared.lua new file mode 100644 index 0000000..c1546d5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/vehicles/entities/entities/ix_vehicle_terminal/shared.lua @@ -0,0 +1,129 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Vehicle Terminal" +ENT.Author = "RefoselDev" +ENT.Category = "[FT] Система техники" +ENT.Spawnable = false +ENT.AdminSpawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "TerminalType") +end + +scripted_ents.Register({ + Type = "anim", + Base = "base_gmodentity", + PrintName = "Терминал наземной техники", + Category = "[FT] Система техники", + Author = "RefoselDev", + Spawnable = true, + AdminOnly = true, + bNoPersist = true, + Model = "models/props_c17/oildrum001.mdl", + SetupDataTables = function(self) + self:NetworkVar("String", 0, "TerminalType") + end, + Initialize = function(self) + if (SERVER) then + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetTerminalType("ground") + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:EnableMotion(false) + end + end + end, + Use = function(self, activator, caller) + if (SERVER) then + if not IsValid(activator) or not activator:IsPlayer() then return end + + local vehiclePlugin = ix.plugin.list["vehicles"] + if vehiclePlugin and vehiclePlugin.GetFactionPoints then + net.Start("LVS_SyncFactionPoints") + net.WriteUInt(activator:Team(), 8) + net.WriteUInt(vehiclePlugin:GetFactionPoints(activator:Team()) or 0, 32) + net.Send(activator) + end + + net.Start("LVS_OpenGroundMenu") + net.Send(activator) + end + end, + Draw = function(self) + self:DrawModel() + + local pos = self:GetPos() + Vector(0, 0, 80) + local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90) + + cam.Start3D2D(pos, ang, 0.15) + draw.SimpleText("НАЗЕМНАЯ ТЕХНИКА", "DermaLarge", 0, 0, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + end +}, "ix_vehicle_terminal_ground") + +scripted_ents.Register({ + Type = "anim", + Base = "base_gmodentity", + PrintName = "Терминал воздушной техники", + Category = "[FT] Система техники", + Author = "RefoselDev", + Spawnable = true, + AdminOnly = true, + bNoPersist = true, + Model = "models/props_c17/oildrum001.mdl", + SetupDataTables = function(self) + self:NetworkVar("String", 0, "TerminalType") + end, + Initialize = function(self) + if (SERVER) then + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetTerminalType("air") + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:EnableMotion(false) + end + end + end, + Use = function(self, activator, caller) + if (SERVER) then + if not IsValid(activator) or not activator:IsPlayer() then return end + + local vehiclePlugin = ix.plugin.list["vehicles"] + if vehiclePlugin and vehiclePlugin.GetFactionPoints then + net.Start("LVS_SyncFactionPoints") + net.WriteUInt(activator:Team(), 8) + net.WriteUInt(vehiclePlugin:GetFactionPoints(activator:Team()) or 0, 32) + net.Send(activator) + end + + net.Start("LVS_OpenAirMenu") + net.Send(activator) + end + end, + Draw = function(self) + self:DrawModel() + + if (not CLIENT) then return end + + local pos = self:GetPos() + self:GetUp() * 100 + local ang = LocalPlayer():EyeAngles() + ang:RotateAroundAxis(ang:Right(), 90) + ang:RotateAroundAxis(ang:Up(), -90) + + cam.Start3D2D(pos, ang, 0.1) + draw.SimpleText("ВОЗДУШНАЯ ТЕХНИКА", "DermaLarge", 0, 0, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + end +}, "ix_vehicle_terminal_air") diff --git a/garrysmod/gamemodes/militaryrp/plugins/vehicles/sh_config.lua b/garrysmod/gamemodes/militaryrp/plugins/vehicles/sh_config.lua new file mode 100644 index 0000000..435375f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/vehicles/sh_config.lua @@ -0,0 +1,5302 @@ +PLUGIN.config = { + + + + + + + + maxPoints = 35000, + currencyName = "Очки техники", + + + + + + + + + + + + + + + + returnSettings = { + + + + + + + + enabled = true, + + + + + + + + refundPercent = 0.8, + + + + + + + + maxReturnDistance = 4000, + + + + + + + + adminCanReturnAll = true + + + + + + + + }, + + + + + + + + spawnPoints = { + + ["rp_ft_kherson_v4"] = function() + return { + { id = "base_rf_air", pos = Vector(13014.523438, 14606.519531, 290.593201), ang = Angle(0, -45, 0), factions = { [FACTION_RUSSIAN] = true }, vehicleTypes = { "air" } }, + { id = "base_rf_ground", pos = Vector(15495.674805, 14435.076172, 242.270096), ang = Angle(0, -90, 0), factions = { [FACTION_RUSSIAN] = true }, vehicleTypes = { "ground" } }, + { id = "rusich_ground", pos = Vector(7334.606445, 13380.044922, 227.586441), ang = Angle(0, 180, 0), factions = { [FACTION_RUSSIAN] = true }, vehicleTypes = { "ground" }, allowedPodr = { [7] = true } }, + { id = "base_uk_air", pos = Vector(-14027.627930, -5491.296875, 278.593201), ang = Angle(0, 50, 0), factions = { [FACTION_UKRAINE] = true }, vehicleTypes = { "air" } }, + { id = "base_uk_ground", pos = Vector(-13715.644531, -11530.956055, 281.633179), ang = Angle(0, 0, 0), factions = { [FACTION_UKRAINE] = true }, vehicleTypes = { "ground" } } + } + end, + + ["rp_ft_izum_4"] = function() + + + + + + + + return { + + + + + + + + { + + + + + + + + id = "base_rf_air", + + + + + + + + pos = Vector(-9918.658203125, 5209.5434570313, 47.407127380371), + + + + + + + + ang = Angle(0, 90, 0), + + + + + + + + factions = { [FACTION_RUSSIAN] = true }, + + + + + + + + vehicleTypes = { "air" } + + + + + + + + }, + + + + + + + + { + + + + + + + + id = "base_rf_ground", + + + + + + + + pos = Vector(-11577.208007813, 5384.4838867188, 18.081562042236), + + + + + + + + ang = Angle(0, 0, 0), + + + + + + + + factions = { [FACTION_RUSSIAN] = true }, + + + + + + + + vehicleTypes = { "ground" } + + + + + + + + }, + + + + + + + + { + + + + + + + + id = "rusich_ground", + + + + + + + + pos = Vector(-8643.179688, -900.301392, 83.406105), + + + + + + + + ang = Angle(0, 0, 0), + + + + + + + + factions = { [FACTION_RUSSIAN] = true }, + + + + + + + + vehicleTypes = { "ground" }, + + + + + + + + allowedPodr = { [7] = true } + + + + + + + + }, + + + + + + + + { + + + + id = "rusich_air", + + + + pos = Vector(-9308.386719, -551.469055, 1260.064941), + + + + ang = Angle(2, -178, 0), + + + + factions = { [FACTION_RUSSIAN] = true }, + + + + vehicleTypes = { "air" }, + + + + allowedPodr = { [7] = true } + + + + }, + + + + + + + + { + + + + + + + + id = "base_uk_air", + + + + + + + + pos = Vector(10932.612304688, 10092.45703125, 3.267448425293), + + + + + + + + ang = Angle(0, 0, 0), + + + + + + + + factions = { [FACTION_UKRAINE] = true }, + + + + + + + + vehicleTypes = { "air" } + + + + + + + + }, + + + + + + + + { + + + + + + + + id = "base_uk_ground", + + + + + + + + pos = Vector(12539.872070313, 9882.435546875, 39.024681091309), + + + + + + + + ang = Angle(0, 0, 0), + + + + + + + + factions = { [FACTION_UKRAINE] = true }, + + + + + + + + vehicleTypes = { "ground" } + + + + + + + + } + + + + + + + + } + + + + + + + + end + + + + + + + + }, + + + + + + + + getFactions = function() + + + + + + + + return { + + + + + + + +[FACTION_ADMIN] = { + + + + + + + +["seaVehicles"] = {}, + + + + + + + +["groundVehicles"] = {}, + + + + + + + +["name"] = "admin", + + + + + + + +["points"] = 2000, + + + + + + + +["airVehicles"] = {} + + + + + + + +}, + + + + + + + +[FACTION_RUSSIAN] = { + + + + + + + +["seaVehicles"] = {}, + + + + + + + +["groundVehicles"] = { + + + + + + + +["t80bvm"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1500, + + + + + + + +["name"] = "Т-80БВМ", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "lvs_wheeldrive_dt80bvm", + + + + + + + +["model"] = "models/diggercars/t80bvm/t80bvm.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 7000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["bmpt"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1400, + + + + + + + +["name"] = "БМПТ-72", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_bmpt72", + + + + + + + +["model"] = "models/sw/ground/bmpt72/bmpt72.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 5000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["gaz2330"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 600, + + + + + + + +["name"] = "ГАЗ-2330", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_gaz2330", + + + + + + + +["model"] = "models/sw/ground/gaz2330/gaz2330.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 1000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[7] = true, + + + + + + + +[2] = true, + + + + + + + +[5] = true, + + + + + + + +[6] = true + + + + + + + +}, + + + + + + + +["loadout"] = { + + + + + + + +["Корд"] = 2, + + + + + + + +["АГС-30"] = 3, + + + + + + + +["Корнет"] = 4, + + + + + + + +} + + + + + + + +}, + + + + + + + +["bmd4"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "БМД-4", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "bmd4-3", + + + + + + + +["model"] = "models/vehicles_shelby/tanks/bmd4/bmd4.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 4500, + + + + + + + +["allowedPodr"] = { + + + + + + + +[2] = true, + + + + + + + +[5] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["t90m"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1500, + + + + + + + +["name"] = "Т-90М", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "lvs_wheeldrive_rp_t90m", + + + + + + + +["model"] = "models/diggercars/t90m/t90m.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 10000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["btrzd"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "БТР-ЗД", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "btrzd2", + + + + + + + +["model"] = "models/vehicles_shelby/tanks/btrzd/btr_zd.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 4000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[2] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["zsu234"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "ЗСУ-23-4", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_zsu234", + + + + + + + +["model"] = "models/sw/ground/zsu234/zsu234.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 4000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["t80bvmloh"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 3000, + + + + + + + +["name"] = "Т-80БВМ", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "lvs_wheeldrive_dt80bvm", + + + + + + + +["model"] = "models/diggercars/t80bvm/t80bvm.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 15000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[5] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["bmp1am"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "БМП-1АМ", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "bmp1am", + + + + + + + +["model"] = "models/vehicles_shelby/tanks/btrzd/btr_zd.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 4000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[7] = true, + + + + + + + +[5] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["dirt_bike"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["name"] = "Мопед", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "lvs_wheeldrive_dirt_bike", + + + + + + + +["model"] = "models/tiggomods_edited/bf3/vehicles/motorbike_body.mdl", + + + + + + + +["cooldown"] = 600, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 500, + + + + + + + +["allowedPodr"] = { + + + + + + + +[5] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["sprut"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "2C25 Cпрут", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sprut-sd", + + + + + + + +["model"] = "models/vehicles_shelby/tanks/2s25/2s25_sprutsd.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 6000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[2] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["t72b_v2"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "Т-72Б Обр. 1985", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "lvs_wheeldrive_ft_t72b_v2", + + + + + + + +["model"] = "models/diggercars/t72b/t72b_v2.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 6000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +} + + + + + + + +}, + + + + + + + +["name"] = "Россия", + + + + + + + +["color"] = Color(255, 50, 50, 255), + + + + + + + +["points"] = 2000, + + + + + + + +["airVehicles"] = { + + + + + + + +["mi35"] = { + + + + + + + +["description"] = "Ударный вертолет,с возможностью транспортировки десанта", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "МИ-35М", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_mi35m", + + + + + + + +["model"] = "models/sw/avia/mi35/mi35.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "air", + + + + + + + +["price"] = 10000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[4] = true + + + + + + + +}, + + + + + + + +["loadout"] = { + + + + + + + +["40 НАР С-8КО + 10 НАР С-13"] = 3, + + + + + + + +["80 НАР С-8КО"] = 1, + + + + + + + +["20 НАР С-13"] = 2 + + + + + + + +} + + + + + + + +}, + + + + + + + +["mi8"] = { + + + + + + + +["description"] = "Многоцелевой вертолет", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "МИ-8АМТШ", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_mi8amtsh", + + + + + + + +["model"] = "models/sw/avia/mi8/mi8.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "air", + + + + + + + +["price"] = 5000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[7] = true, + + + + + + + +[4] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["ka52"] = { + + + + + + + +["description"] = "Ударный вертолет", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "КА-52", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_ka52", + + + + + + + +["model"] = "models/sw/avia/ka52/ka52.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "air", + + + + + + + +["price"] = 15000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[4] = true + + + + + + + +}, + + + + + + + +["loadout"] = { + + + + + + + +["20 НАР С-13"] = 2, + + + + + + + +["80 НАР С-8КО"] = 1 + + + + + + + +} + + + + + + + +} + + + + + + + +} + + + + + + + +}, + + + + + + + +[FACTION_UKRAINE] = { + + + + + + + +["seaVehicles"] = {}, + + + + + + + +["groundVehicles"] = { + + + + + + + +["m1128"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "M1128 MGS", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_m1128", + + + + + + + +["model"] = "models/sw/ground/stryker/m1128.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 6000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[2] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["btr4e"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "БТР-4Е", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_btr4e", + + + + + + + +["model"] = "models/sw/ground/btr4e/btr4e.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 3000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[5] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["oplot"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1500, + + + + + + + +["name"] = "Т-84БМ Оплот", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "bm_oplot", + + + + + + + +["model"] = "models/mbt/ukr/oplot/bm_oplot.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 10000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["btrzd"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 1, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "БТР-ЗД", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "btrzd2", + + + + + + + +["model"] = "models/vehicles_shelby/tanks/btrzd/btr_zd.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 4000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[2] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["bmp1u"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "БМП-1У", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_bmp1u", + + + + + + + +["model"] = "models/sw/ground/bmp1u/bmp1u.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 4500, + + + + + + + +["allowedPodr"] = { + [2] = true, + [9] = true +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["humvee"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 600, + + + + + + + +["name"] = "Humvee", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_m1151", + + + + + + + +["model"] = "models/sw/ground/m1151/m1151.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 1000, + + + + + + + +["allowedPodr"] = { + [2] = true, + [5] = true, + [6] = true, + [8] = true, + [9] = true +}, + + + + + + + +["loadout"] = { + + + + + + + +["MK-19"] = 3, + + + + + + + +["TOW"] = 4, + + + + + + + +["M2 Browning"] = 2 + + + + + + + +} + + + + + + + +}, + + + + + + + +["m1a1"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1500, + + + + + + + +["name"] = "M1A1SA", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "lvs_wheeldrive_rp_dabrams_m1a1", + + + + + + + +["model"] = "models/diggercars/abrams_m1a1/abrams.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 7000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["t64bm"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "Т-64БМ2 Булат", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "bm_bulat", + + + + + + + +["model"] = "models/mbt/ukr/t64bm/t64bm2.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 6000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["btrt"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1400, + + + + + + + +["name"] = "БТР-Т", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_btrt", + + + + + + + +["model"] = "models/sw/ground/btrt/btrt.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 5000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["gepard"] = { + + + + + + + +["description"] = "", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "Flakpanzer Gepard", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "flakpanzer_gepard", + + + + + + + +["model"] = "models/vehicles_shelby/tanks/flakpanzerigepard/flakpanzer_i_gepard.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "ground", + + + + + + + +["price"] = 4000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[3] = true + + + + + + + +}, + + + + + + + +["loadout"] = {} + + + + + + + +} + + + + + + + +}, + + + + + + + +["name"] = "Украина", + + + + + + + +["color"] = Color(50, 50, 255, 255), + + + + + + + +["points"] = 2000, + + + + + + + +["airVehicles"] = { + + + + + + + +["mi8"] = { + + + + + + + +["description"] = "Многоцелевой вертолет", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 3, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 900, + + + + + + + +["name"] = "МИ-8АМТШ", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_mi8amtsh", + + + + + + + +["model"] = "models/sw/avia/mi8/mi8.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "air", + + + + + + + +["price"] = 5000, + + + + + + + +["allowedPodr"] = { + [4] = true, + [9] = true +}, + + + + + + + +["loadout"] = {} + + + + + + + +}, + + + + + + + +["ah64"] = { + + + + + + + +["description"] = "Ударный вертолет", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "AH-64", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_ah64", + + + + + + + +["model"] = "models/sw/avia/ah64/ah64.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "air", + + + + + + + +["price"] = 15000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[4] = true + + + + + + + +}, + + + + + + + +["loadout"] = { + + + + + + + +["16 ПТУР AGM-114"] = 3, + + + + + + + +["38 НАР + 8 ПТУР"] = 2, + + + + + + + +["76 НАР HYDRA-70"] = 1 + + + + + + + +} + + + + + + + +}, + + + + + + + +["mh60l"] = { + + + + + + + +["description"] = "Ударный вертолет", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "MH-60L", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_mh60l", + + + + + + + +["model"] = "models/sw/avia/uh60/mh60.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "air", + + + + + + + +["price"] = 15000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[4] = true + + + + + + + +}, + + + + + + + +["loadout"] = { + + + + + + + +["36 НАР и пушка"] = 1 + + + + + + + +} + + + + + + + +}, + + + + + + + +["mi24v"] = { + + + + + + + +["description"] = "Ударный вертолет,с возможностью транспортировки десанта", + + + + + + + +["bodygroups"] = {}, + + + + + + + +["default_skin"] = 0, + + + + + + + +["skins"] = {}, + + + + + + + +["cooldown"] = 1200, + + + + + + + +["name"] = "МИ-24В", + + + + + + + +["skin"] = {}, + + + + + + + +["class"] = "sw_mi24v", + + + + + + + +["model"] = "models/sw/avia/mi24v/mi24v.mdl", + + + + + + + +["default_bodygroups"] = {}, + + + + + + + +["type"] = "air", + + + + + + + +["price"] = 10000, + + + + + + + +["allowedPodr"] = { + + + + + + + +[4] = true + + + + + + + +}, + + + + + + + +["loadout"] = { + + + + + + + +["40 НАР С-8КО + 10 НАР С-13"] = 3, + + + + + + + +["80 НАР С-8КО"] = 1, + + + + + + + +["20 НАР С-13"] = 2 + + + + + + + +} + + + + + + + +} + + + + + + + +} + + + + + + + +} + + + + + + + +} + + + + + + + + end, + + + + + + + + + + + + + + + + spawnSettings = { + + + + + + + + ground = { + + + + + + + + maxDistance = 300, + + + + + + + + minDistance = 100, + + + + + + + + spawnHeight = 50, + + + + + + + + useSpawnPoints = true, + + + + + + + + spawnPointRadius = 200 + + + + + + + + }, + + + + + + + + air = { + + + + + + + + maxDistance = 300, + + + + + + + + minDistance = 200, + + + + + + + + spawnHeight = 300, + + + + + + + + useSpawnPoints = true, + + + + + + + + spawnPointRadius = 300 + + + + + + + + } + + + + + + + + }, + + + + + + + + + + + + + + + + terminalModels = { + + + + + + + + ground = "models/props_lab/reciever01d.mdl", + + + + + + + + air = "models/props_lab/reciever01a.mdl" + + + + + + + + }, + + + + + + + + + + + + + + + + terminalNames = { + + + + + + + + ground = "Терминал наземной техники", + + + + + + + + air = "Терминал воздушной техники" + + + + + + + + } + + + + + + + +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/vehicles/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/vehicles/sh_plugin.lua new file mode 100644 index 0000000..9e4b42c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/vehicles/sh_plugin.lua @@ -0,0 +1,40 @@ +PLUGIN.name = "LVS Vehicles System" +PLUGIN.author = "RefoselDev" +PLUGIN.description = "Система вызова LVS техники за очки" + +ix.util.Include("sh_config.lua", "shared") +ix.util.Include("cl_plugin.lua", "client") +ix.util.Include("sv_plugin.lua", "server") + +-- Функция проверки активной техники +function PLUGIN:HasActiveVehicle(vehicleID, vehicleType) + local ply = LocalPlayer() + if not IsValid(ply) then return false end + + -- Ищем активную технику игрока + for _, ent in pairs(ents.GetAll()) do + if IsValid(ent) and ent.lvsVehicleInfo then + local info = ent.lvsVehicleInfo + if info.vehicleID == vehicleID and info.vehicleType == vehicleType then + -- Проверяем владельца или права админа + if ply:IsAdmin() or ply:SteamID() == info.ownerSteamID then + return true + end + end + end + -- fallback: проверим NWVars если lvsVehicleInfo ещё не синхронизировалось + if IsValid(ent) then + local nwOwner = ent:GetNWString("LVS_OwnerSteamID", "") + local nwID = ent:GetNWString("LVS_VehicleID", "") + local nwType = ent:GetNWString("LVS_VehicleType", "") + + if nwID == tostring(vehicleID) and nwType == tostring(vehicleType) and nwOwner ~= "" then + if ply:IsAdmin() or ply:SteamID() == nwOwner then + return true + end + end + end + end + + return false +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/vehicles/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/vehicles/sv_plugin.lua new file mode 100644 index 0000000..906e824 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/vehicles/sv_plugin.lua @@ -0,0 +1,1052 @@ +local PLUGIN = PLUGIN + +PLUGIN.factionPoints = PLUGIN.factionPoints or {} +PLUGIN.spawnPoints = PLUGIN.spawnPoints or {} +PLUGIN.activeVehicles = PLUGIN.activeVehicles or {} -- Таблица для отслеживания активной техники + +function PLUGIN:LoadData() + local data = self:GetData() or {} + + -- Загрузка очков фракций + if type(data) == "table" and data.factionPoints then + self.factionPoints = data.factionPoints + elseif type(data) == "table" and not data.factionPoints then + -- Старый формат (только очки) + self.factionPoints = data + else + self.factionPoints = {} + end + + -- Загрузка терминалов + if data.terminals then + for _, v in ipairs(data.terminals) do + local terminal = ents.Create(v.class or "ix_vehicle_terminal_ground") + if IsValid(terminal) then + terminal:SetPos(v.pos) + terminal:SetAngles(v.angles) + terminal:Spawn() + + local phys = terminal:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + end + end + end + + -- Загрузка точек спавна из конфига + local mapName = game.GetMap() + local spawnPointsForMap = self.config.spawnPoints[mapName] + if spawnPointsForMap then + self.spawnPoints = type(spawnPointsForMap) == "function" and spawnPointsForMap() or spawnPointsForMap + else + self.spawnPoints = {} + end + + local factions = self.config.getFactions and self.config.getFactions() or self.config.factions or {} + for factionID, factionConfig in pairs(factions) do + if not self.factionPoints[factionID] then + self.factionPoints[factionID] = factionConfig.points or 1000 + end + end + + -- Прекеширование LVS классов техники: создаём и сразу удаляем каждый класс, + -- чтобы LVS инициализировал их заранее и при первом спавне игроком всё работало + timer.Simple(5, function() + local precached = {} + for _, factionConfig in pairs(factions) do + for _, vehicleList in pairs({factionConfig.groundVehicles or {}, factionConfig.airVehicles or {}}) do + for _, vehConfig in pairs(vehicleList) do + local class = vehConfig.class + if class and not precached[class] then + local ent = ents.Create(class) + if IsValid(ent) then + ent:SetPos(Vector(0, 0, -10000)) -- далеко под картой + ent:Spawn() + ent:Activate() + SafeRemoveEntityDelayed(ent, 1) -- удаляем через секунду после инициализации + precached[class] = true + print("[LVS] Прекешировано: " .. class) + end + end + end + end + end + print("[LVS] Прекеширование завершено, классов: " .. table.Count(precached)) + end) +end + +function PLUGIN:GetFactions() + if not self.cachedFactions then + self.cachedFactions = self.config.getFactions and self.config.getFactions() or self.config.factions or {} + end + return self.cachedFactions +end + +function PLUGIN:SaveData() + local data = { + terminals = {} + } + + -- Сохраняем все терминалы + for _, entity in ipairs(ents.FindByClass("ix_vehicle_terminal_ground")) do + if IsValid(entity) then + data.terminals[#data.terminals + 1] = { + class = "ix_vehicle_terminal_ground", + pos = entity:GetPos(), + angles = entity:GetAngles() + } + end + end + + for _, entity in ipairs(ents.FindByClass("ix_vehicle_terminal_air")) do + if IsValid(entity) then + data.terminals[#data.terminals + 1] = { + class = "ix_vehicle_terminal_air", + pos = entity:GetPos(), + angles = entity:GetAngles() + } + end + end + + self:SetData(data) +end + +function PLUGIN:GetFactionPoints(faction) + local points = self.factionPoints[faction] or 0 + local maxPoints = (self.config and self.config.maxPoints) or 20000 + return math.min(points, maxPoints) +end + +function PLUGIN:AddFactionPoints(faction, amount) + if not self.factionPoints[faction] then + self.factionPoints[faction] = 0 + end + + local maxPoints = (self.config and self.config.maxPoints) or 20000 + self.factionPoints[faction] = math.Clamp(self.factionPoints[faction] + amount, 0, maxPoints) + self:SyncFactionPoints(faction) + + -- Логирование изменения очков техники + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + if amount > 0 then + local message = string.format("Фракция '%s' получила +%d очков техники (итого: %d)", factionName, amount, self.factionPoints[faction]) + serverlogsPlugin:AddLog("VEHICLE_POINTS_ADD", message, nil, { + faction = faction, + factionName = factionName, + amount = amount, + total = self.factionPoints[faction] + }) + elseif amount < 0 then + local message = string.format("Фракция '%s' потратила %d очков техники (итого: %d)", factionName, math.abs(amount), self.factionPoints[faction]) + serverlogsPlugin:AddLog("VEHICLE_POINTS_USE", message, nil, { + faction = faction, + factionName = factionName, + amount = math.abs(amount), + total = self.factionPoints[faction] + }) + end + end + + return self.factionPoints[faction] +end + +function PLUGIN:SyncFactionPoints(faction) + net.Start("LVS_SyncFactionPoints") + net.WriteUInt(faction, 8) + net.WriteUInt(self.factionPoints[faction] or 0, 32) + net.Broadcast() +end + +function PLUGIN:GetSpawnPoints(faction, vehicleType, podr) + local validPoints = {} + local podrPoints = {} -- Точки, специфичные для подразделения + + for _, point in pairs(self.spawnPoints) do + if istable(point) and point.factions and point.factions[faction] then + -- Проверяем тип техники + local validType = false + if point.vehicleTypes then + for _, allowedType in ipairs(point.vehicleTypes) do + if allowedType == vehicleType then + validType = true + break + end + end + else + validType = true + end + + if validType then + -- Если у точки есть привязка к подразделениям + if point.allowedPodr then + if podr and point.allowedPodr[podr] then + table.insert(podrPoints, point) + end + else + -- Точка без привязки к подразделению — доступна всем + table.insert(validPoints, point) + end + end + end + end + + -- Если есть точки для конкретного подразделения — используем их + -- Иначе — используем общие точки фракции + if #podrPoints > 0 then + return podrPoints + end + + return validPoints +end + +function PLUGIN:FindSpawnPosition(player, spawnSettings, vehicleType) + local faction = player:Team() + + if spawnSettings.useSpawnPoints then + local spawnPoints = self:GetSpawnPoints(faction, vehicleType) + + if #spawnPoints > 0 then + local point = spawnPoints[math.random(1, #spawnPoints)] + + local angle = math.random(0, 360) + local distance = math.random(0, spawnSettings.spawnPointRadius) + local offset = Vector( + math.cos(angle) * distance, + math.sin(angle) * distance, + 0 + ) + + local spawnPos = point.pos + offset + Vector(0, 0, spawnSettings.spawnHeight) + + local traceCheck = util.TraceHull({ + start = spawnPos, + endpos = spawnPos, + mins = Vector(-130, -130, 20), + maxs = Vector(130, 130, 180), + filter = function(ent) + if ent == player or ent:GetClass() == "lvs_vehicle_repair" then return false end + return true + end + }) + + if not traceCheck.Hit then + return spawnPos, point.ang + end + end + end + + local trace = util.TraceLine({ + start = player:GetShootPos(), + endpos = player:GetShootPos() + player:GetAimVector() * spawnSettings.maxDistance, + filter = player + }) + + if trace.Hit then + local spawnPos = trace.HitPos + Vector(0, 0, spawnSettings.spawnHeight) + + local traceCheck = util.TraceHull({ + start = spawnPos, + endpos = spawnPos, + mins = Vector(-130, -130, 20), + maxs = Vector(130, 130, 180), + filter = function(ent) + if ent == player or ent:GetClass() == "lvs_vehicle_repair" then return false end + return true + end + }) + + if not traceCheck.Hit then + return spawnPos, player:GetAngles() + end + end + + return nil +end + +function PLUGIN:SpawnVehicle(player, vehicleID, vehicleType, loadoutVariant) + if not IsValid(player) then print("[LVS DEBUG] FAIL: player not valid") return false, "Игрок не валиден" end + + local character = player:GetCharacter() + if not character then print("[LVS DEBUG] FAIL: no character") return false, "Персонаж не найден" end + + local faction = player:Team() + local factionConfig = PLUGIN:GetFactions()[faction] + + print("[LVS DEBUG] Player:", player:Nick(), "Faction:", faction, "VehicleID:", vehicleID, "Type:", vehicleType) + print("[LVS DEBUG] FactionConfig exists:", factionConfig ~= nil) + + loadoutVariant = loadoutVariant or 0 + + if not factionConfig then + print("[LVS DEBUG] FAIL: no factionConfig for faction", faction) + print("[LVS DEBUG] Available factions:") + for k, v in pairs(PLUGIN:GetFactions()) do + print("[LVS DEBUG] faction key:", k, "name:", v.name) + end + return false, "Ваша фракция не может вызывать технику" + end + + -- Проверка подразделения + local playerSubdivision = character:GetPodr() + print("[LVS DEBUG] PlayerSubdivision:", playerSubdivision) + + -- Проверяем массив разрешенных подразделений для конкретной техники + local vehicles = vehicleType == "ground" and factionConfig.groundVehicles or factionConfig.airVehicles + local vehicleConfig = vehicles[vehicleID] + + if not vehicleConfig then + print("[LVS DEBUG] FAIL: vehicleConfig not found for ID:", vehicleID) + print("[LVS DEBUG] Available vehicles:") + for k, v in pairs(vehicles) do + print("[LVS DEBUG] ", k, "->", v.name) + end + return false, "Техника не найдена" + end + + if vehicleConfig.allowedPodr and istable(vehicleConfig.allowedPodr) then + if not vehicleConfig.allowedPodr[playerSubdivision] then + -- Получаем названия разрешенных подразделений + local podrNames = {} + local factionTable = ix.faction.Get(faction) + + if factionTable and factionTable.Podr then + for podrID, _ in pairs(vehicleConfig.allowedPodr) do + local podrData = factionTable.Podr[podrID] + if podrData and podrData.name then + table.insert(podrNames, podrData.name) + end + end + end + + if #podrNames > 0 then + return false, "Эту технику могут вызывать только: " .. table.concat(podrNames, ", ") + else + return false, "Эта техника недоступна для вашего подразделения" + end + end + end + + local factionPoints = self:GetFactionPoints(faction) + if factionPoints < vehicleConfig.price then + print("[LVS DEBUG] FAIL: not enough points. Has:", factionPoints, "Need:", vehicleConfig.price) + return false, "Недостаточно очков техники у фракции" + end + + local vehiclesData = character:GetData("lvs_vehicles", {}) + local lastSpawn = vehiclesData[vehicleID] + + if lastSpawn and lastSpawn > os.time() then + local remaining = lastSpawn - os.time() + local minutes = math.floor(remaining / 60) + local seconds = math.floor(remaining % 60) + return false, "Техника в кд: " .. string.format("%02d:%02d", minutes, seconds) + end + + local spawnSettings = PLUGIN.config.spawnSettings[vehicleType] or PLUGIN.config.spawnSettings.ground + + -- Попытаемся взять точку спавна из конфигурации (если такие точки заданы для карты) + local spawnPos, spawnAng = nil, nil + local spawnPointUsed = nil + local validPoints = self:GetSpawnPoints(faction, vehicleType, playerSubdivision) + print("[LVS DEBUG] SpawnPoints found:", #validPoints, "for faction:", faction, "type:", vehicleType, "podr:", tostring(playerSubdivision)) + -- Требуем, чтобы для карты были настроены точки спавна + if not validPoints or #validPoints == 0 then + print("[LVS DEBUG] FAIL: no spawn points!") + print("[LVS DEBUG] Total spawnPoints loaded:", table.Count(self.spawnPoints)) + for i, sp in pairs(self.spawnPoints) do + if istable(sp) then + print("[LVS DEBUG] point", i, "id:", sp.id, "factions:", table.ToString(sp.factions or {}), "types:", table.ToString(sp.vehicleTypes or {}), "podr:", table.ToString(sp.allowedPodr or {})) + end + end + return false, "На этой карте не настроены точки спавна техники" + end + + local effectiveSpawnHeight = spawnSettings.spawnHeight + if vehicleType == "air" then + effectiveSpawnHeight = 20 + end + + local function IsPosClear(pos) + local traceCheck = util.TraceHull({ + start = pos, + endpos = pos, + mins = Vector(-70, -70, 30), -- Еще меньше, чтобы танки с длинными дулами или широкие машины не цепляли стены + maxs = Vector(70, 70, 100), + mask = MASK_SOLID, + filter = function(ent) + if ent == player then return false end + + local class = ent:GetClass() + -- Игнорируем ремонтные станции и терминалы + if class == "lvs_vehicle_repair" or class:find("ix_vehicle_terminal") then + return false + end + + return true + end + }) + + if traceCheck.Hit then + if IsValid(traceCheck.Entity) then + print("[LVS DEBUG] Точка спавна заблокирована энтити: " .. traceCheck.Entity:GetClass()) + else + print("[LVS DEBUG] Точка спавна заблокирована картой (World/Brush)!") + end + end + + return not traceCheck.Hit and not traceCheck.StartSolid + end + + -- Сначала пробуем найти свободную точку среди всех доступных + for _, p in ipairs(validPoints) do + -- 1. Сначала пробуем ровно в центре точки (без смещения) + local testPos = p.pos + Vector(0, 0, effectiveSpawnHeight) + if IsPosClear(testPos) then + spawnPos = testPos + spawnAng = p.ang or player:GetAngles() + spawnPointUsed = p + break + end + + -- 2. Если центр занят, пробуем 5 случайных смещений в радиусе + local radius = spawnSettings.spawnPointRadius or 0 + if radius > 0 then + for i = 1, 5 do + local angle = math.random(0, 360) + local distance = math.random(50, radius) + local offset = Vector(math.cos(angle) * distance, math.sin(angle) * distance, 0) + local offsetPos = p.pos + offset + Vector(0, 0, effectiveSpawnHeight) + + if IsPosClear(offsetPos) then + spawnPos = offsetPos + spawnAng = p.ang or player:GetAngles() + spawnPointUsed = p + break + end + end + end + + if spawnPos then break end + end + + if not spawnPos then + return false, "Все точки спавна заняты или заблокированы препятствиями" + end + + -- Создаём технику (проверка класса через IsValid после создания) + + local vehicle = ents.Create(vehicleConfig.class) + if not IsValid(vehicle) then + return false, "Ошибка создания техники" + end + + vehicle:SetPos(spawnPos) + vehicle:SetAngles(spawnAng or player:GetAngles()) + + vehicle:Spawn() + + -- Для LVS транспорта нужен Activate после Spawn + if vehicle.LVS then + vehicle:Activate() + end + + timer.Simple(1, function() + if not IsValid(vehicle) then return end + + if vehicle.Loadouts and loadoutVariant then + local loadoutID = tonumber(loadoutVariant) or 1 + print("[LVS] Применяю loadout ID=" .. loadoutID .. " для техники " .. vehicleConfig.name) + vehicle:SetLoadout(loadoutID) + end + end) + + local chosenSkin = nil + if istable(vehicleConfig.skin) and next(vehicleConfig.skin or {}) ~= nil then + if vehicleConfig.skin[faction] ~= nil then + chosenSkin = vehicleConfig.skin[faction] + else + for k, v in pairs(vehicleConfig.skin) do + if tostring(k) == tostring(faction) then + chosenSkin = v + break + end + end + end + end + + if chosenSkin == nil and vehicleConfig.default_skin ~= nil then + chosenSkin = vehicleConfig.default_skin + end + + if chosenSkin ~= nil then + pcall(function() vehicle:SetSkin(chosenSkin) end) + end + + if istable(vehicleConfig.default_bodygroups) then + for _, bg in ipairs(vehicleConfig.default_bodygroups) do + if bg and bg.id and bg.value then + pcall(function() + vehicle:SetBodygroup(bg.id, bg.value) + end) + end + end + end + + if istable(vehicleConfig.bodygroups) then + for k, v in pairs(vehicleConfig.bodygroups) do + if isnumber(k) and istable(v) and v.id and v.value then + pcall(function() vehicle:SetBodygroup(v.id, v.value) end) + elseif isnumber(k) and isnumber(v) then + pcall(function() vehicle:SetBodygroup(k, v) end) + end + end + end + + -- Сохраняем информацию о технике для возможности возврата + local vehicleInfo = { + owner = player, + ownerSteamID = player:SteamID(), + vehicleID = vehicleID, + vehicleType = vehicleType, + vehicleConfig = vehicleConfig, + spawnTime = os.time(), + faction = faction, + appliedSkin = chosenSkin, + appliedBodygroups = vehicleConfig.default_bodygroups, + appliedLoadout = loadoutVariant, + lastUse = CurTime() + } + -- Сохраняем использованную точку спавна (если была) + if spawnPointUsed and spawnPointUsed.pos then + vehicleInfo.spawnPoint = spawnPointUsed.pos + vehicleInfo.spawnPointAng = spawnPointUsed.ang + vehicleInfo.spawnPointID = spawnPointUsed.id + end + + vehicle.lvsVehicleInfo = vehicleInfo + self.activeVehicles[vehicle] = vehicleInfo + + -- Устанавливаем сетевые переменные на сущности, чтобы клиенты могли узнать данные даже если net сообщение не доспело + pcall(function() + vehicle:SetNWString("LVS_OwnerSteamID", vehicleInfo.ownerSteamID or "") + vehicle:SetNWString("LVS_VehicleID", tostring(vehicleInfo.vehicleID or "")) + vehicle:SetNWString("LVS_VehicleType", tostring(vehicleInfo.vehicleType or "")) + end) + + -- Синхронизируем информацию с клиентом + self:SyncVehicleInfo(vehicle) + + self:AddFactionPoints(faction, -vehicleConfig.price) + + vehiclesData[vehicleID] = os.time() + vehicleConfig.cooldown + character:SetData("lvs_vehicles", vehiclesData) + + self:NotifyFaction(faction, player:Name() .. " вызвал " .. vehicleConfig.name .. " за " .. vehicleConfig.price .. " очков") + + -- Логирование вызова техники + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + local message = string.format("%s вызвал технику '%s' (стоимость: %d очков техники)", + player:Nick(), vehicleConfig.name, vehicleConfig.price) + serverlogsPlugin:AddLog("VEHICLE_SPAWN", message, player, { + vehicleID = vehicleID, + vehicleName = vehicleConfig.name, + vehicleClass = vehicleConfig.class, + vehicleType = vehicleType, + price = vehicleConfig.price, + faction = faction, + factionName = factionName + }) + end + + print(player:Name() .. " вызвал " .. vehicleConfig.name .. " (" .. vehicleType .. ")") + + return true, vehicleConfig.name .. " успешно вызвана!" +end + +-- Функция возврата техники +function PLUGIN:ReturnVehicle(vehicle, player) + if not IsValid(vehicle) or not self.activeVehicles[vehicle] then + return false, "Техника не найдена" + end + + local vehicleInfo = self.activeVehicles[vehicle] + + -- Проверка: Можно ли вернуть технику по месту спавна (если точка сохранена) + local maxDist = PLUGIN.config.returnSettings and PLUGIN.config.returnSettings.maxReturnDistance or 200 + if vehicleInfo.spawnPoint then + local curPos = vehicle:GetPos() + local dist = curPos:DistToSqr(vehicleInfo.spawnPoint) + if dist > (maxDist * maxDist) and not player:IsAdmin() then + return false, "Техника находится слишком далеко от точки возврата" + end + end + + -- Проверяем, может ли игрок вернуть эту технику + if not player:IsAdmin() and player:SteamID() ~= vehicleInfo.ownerSteamID then + return false, "Вы не можете вернуть чужую технику" + end + + local faction = vehicleInfo.faction + local vehicleConfig = vehicleInfo.vehicleConfig + local refundAmount = math.floor(vehicleConfig.price * 0.8) -- Возвращаем 80% стоимости + + -- Возвращаем очки фракции + self:AddFactionPoints(faction, refundAmount) + + -- Сбрасываем кд для владельца + if IsValid(vehicleInfo.owner) then + local character = vehicleInfo.owner:GetCharacter() + if character then + local vehiclesData = character:GetData("lvs_vehicles", {}) + vehiclesData[vehicleInfo.vehicleID] = nil + character:SetData("lvs_vehicles", vehiclesData) + + vehicleInfo.owner:Notify("Вы вернули " .. vehicleConfig.name .. ". Возвращено " .. refundAmount .. " очков техники") + end + end + + -- Удаляем технику + vehicle:Remove() + self.activeVehicles[vehicle] = nil + + -- Уведомляем фракцию + local returnMessage = player:Name() .. " вернул " .. vehicleConfig.name .. ". Возвращено " .. refundAmount .. " очков техники" + self:NotifyFaction(faction, returnMessage) + + -- Логирование возврата техники + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + local message = string.format("%s вернул технику '%s' (возврат: %d очков техники)", + player:Nick(), vehicleConfig.name, refundAmount) + serverlogsPlugin:AddLog("VEHICLE_RETURN", message, player, { + vehicleID = vehicleInfo.vehicleID, + vehicleName = vehicleConfig.name, + vehicleClass = vehicleConfig.class, + refundAmount = refundAmount, + faction = faction, + factionName = factionName + }) + end + + print(returnMessage) + + return true, vehicleConfig.name .. " успешно возвращена. Возвращено " .. refundAmount .. " очков" +end + +-- Функция автоматической очистки уничтоженной техники +function PLUGIN:VehicleDestroyed(vehicle) + if self.activeVehicles[vehicle] then + self.activeVehicles[vehicle] = nil + end +end + +-- Функция синхронизации информации о технике +function PLUGIN:SyncVehicleInfo(vehicle) + if not IsValid(vehicle) or not vehicle.lvsVehicleInfo then return end + + local info = vehicle.lvsVehicleInfo + + -- Отправляем информацию всем клиентам + net.Start("LVS_SyncVehicleInfo") + net.WriteEntity(vehicle) + net.WriteString(info.ownerSteamID or "") + net.WriteString(info.vehicleID or "") + net.WriteString(info.vehicleType or "") + net.Broadcast() +end + +function PLUGIN:NotifyFaction(faction, message) + for _, v in ipairs(player.GetAll()) do + if v:Team() == faction then + v:Notify(message) + end + end +end + +util.AddNetworkString("LVS_SyncFactionPoints") +util.AddNetworkString("LVS_OpenGroundMenu") +util.AddNetworkString("LVS_OpenAirMenu") +util.AddNetworkString("LVS_RequestSpawn") +util.AddNetworkString("LVS_RequestReturn") +util.AddNetworkString("LVS_SyncVehicleInfo") +util.AddNetworkString("LVS_SyncVehicleModels") + +net.Receive("LVS_RequestSpawn", function(len, player) + local vehicleID = net.ReadString() + local vehicleType = net.ReadString() + local loadoutVariant = net.ReadUInt(8) + + print("[LVS] Запрос спавна от " .. player:Name() .. " - ID: " .. vehicleID .. ", Тип: " .. vehicleType .. ", LoadoutID: " .. loadoutVariant) + + local success, message = PLUGIN:SpawnVehicle(player, vehicleID, vehicleType, loadoutVariant) + player:Notify(message) +end) + +-- Обработка запроса на возврат техники +net.Receive("LVS_RequestReturn", function(len, player) + local vehicleIndex = net.ReadUInt(16) + -- Логирование для отладки + print("[LVS] Received LVS_RequestReturn from ", player:Nick(), " (SteamID: ", player:SteamID(), ") with index:", vehicleIndex) + local vehicle = Entity(vehicleIndex) + if not IsValid(vehicle) then + player:Notify("Техника не найдена (индекс: " .. tostring(vehicleIndex) .. ")") + print("[LVS] Entity is not valid for index:", vehicleIndex) + return + end + + local success, message = PLUGIN:ReturnVehicle(vehicle, player) + -- Сообщаем игроку подробное сообщение + if success then + player:Notify(message) + else + player:Notify(message or "Не удалось вернуть технику") + end +end) + +-- Хук для удаления уничтоженной техники +hook.Add("EntityRemoved", "LVS_VehicleCleanup", function(ent) + if PLUGIN.activeVehicles[ent] then + PLUGIN:VehicleDestroyed(ent) + end +end) + +-- Хук автоудаления обломков взорванной техники через 5 минут +local WRECK_LIFETIME = 300 -- 5 минут в секундах +hook.Add("OnEntityCreated", "LVS_WreckAutoRemove", function(ent) + -- Даем энтити немного времени на инициализацию класса + timer.Simple(0.1, function() + if IsValid(ent) and ent:GetClass() == "lvs_destruction" then + timer.Simple(WRECK_LIFETIME, function() + if IsValid(ent) then + ent:Remove() + print("[LVS] Обломки уничтоженной техники очищены (прошло 5 минут)") + end + end) + end + end) +end) + +-- Система автоудаления неиспользуемой техники (10 минут) +timer.Create("LVS_UnusedVehicleCleanup", 30, 0, function() + local currentTime = CurTime() + local idleLimit = 600 -- 10 минут + + for vehicle, info in pairs(PLUGIN.activeVehicles) do + if not IsValid(vehicle) then + PLUGIN.activeVehicles[vehicle] = nil + continue + end + + -- Если в технике есть кто-то, обновляем время использования + local hasOccupant = false + if IsValid(vehicle:GetDriver()) then + hasOccupant = true + else + if istable(vehicle.pSeats) then + for _, seat in pairs(vehicle.pSeats) do + if IsValid(seat) and IsValid(seat:GetDriver()) then + hasOccupant = true + break + end + end + end + end + + if hasOccupant then + info.lastUse = currentTime + else + -- Если техника не занята, проверяем время простоя + if (currentTime - (info.lastUse or currentTime)) >= idleLimit then + local faction = info.faction + local vehicleConfig = info.vehicleConfig + local refundAmount = vehicleConfig.price -- Возвращаем полную стоимость при автоудалении + + -- Возвращаем очки фракции + PLUGIN:AddFactionPoints(faction, refundAmount) + + -- Сбрасываем кд для владельца + if IsValid(info.owner) then + local character = info.owner:GetCharacter() + if character then + local vehiclesData = character:GetData("lvs_vehicles", {}) + vehiclesData[info.vehicleID] = nil + character:SetData("lvs_vehicles", vehiclesData) + + info.owner:Notify("Ваша техника " .. vehicleConfig.name .. " была удалена из-за простоя. Очки возвращены.") + end + end + + -- Удаляем технику + vehicle:Remove() + PLUGIN.activeVehicles[vehicle] = nil + + -- Уведомляем фракцию + PLUGIN:NotifyFaction(faction, "Техника " .. vehicleConfig.name .. " удалена из-за 10-минутного простоя. Очки возвращены.") + + -- Логирование + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local factionName = ix.faction.Get(faction).name or tostring(faction) + local message = string.format("Техника '%s' удалена автоматически (простой 10 мин). Возвращено %d очков.", + vehicleConfig.name, refundAmount) + serverlogsPlugin:AddLog("VEHICLE_AUTO_REMOVE", message, nil, { + vehicleName = vehicleConfig.name, + refundAmount = refundAmount, + faction = faction + }) + end + + print("[LVS] Техника " .. vehicleConfig.name .. " удалена автоматически за простой.") + end + end + end +end) + +-- Команда для возврата техники +ix.command.Add("returnvehicle", { + description = "Вернуть технику и получить часть очков обратно", + arguments = { + ix.type.number + }, + OnRun = function(self, client, vehicleIndex) + local vehicle = Entity(tonumber(vehicleIndex)) + + if not IsValid(vehicle) then + return "Техника с индексом " .. vehicleIndex .. " не найдена" + end + + local success, message = PLUGIN:ReturnVehicle(vehicle, client) + return message + end +}) + +-- Команда для просмотра активной техники +ix.command.Add("listvehicles", { + description = "Показать активную технику", + adminOnly = true, + OnRun = function(self, client) + local count = 0 + local result = "Активная техника:\n" + + for vehicle, info in pairs(PLUGIN.activeVehicles) do + if IsValid(vehicle) then + count = count + 1 + local ownerName = IsValid(info.owner) and info.owner:Name() or "Неизвестно" + result = result .. count .. ". " .. info.vehicleConfig.name .. " (ID: " .. vehicle:EntIndex() .. ") - Владелец: " .. ownerName .. "\n" + else + PLUGIN.activeVehicles[vehicle] = nil + end + end + + if count == 0 then + return "Активной техники нет" + end + + return result + end +}) + +ix.command.Add("GiveVehiclePoints", { + description = "Выдать очки техники фракции", + adminOnly = true, + arguments = { + ix.type.string, + ix.type.number + }, + OnRun = function(self, client, factionName, amount) + local targetFaction + + for factionID, config in pairs(PLUGIN:GetFactions()) do + if string.lower(config.name) == string.lower(factionName) then + targetFaction = factionID + break + end + end + + if not targetFaction then + return "Фракция не найдена. Доступные: Россия, Украина" + end + + local newPoints = PLUGIN:AddFactionPoints(targetFaction, amount) + local factionData = PLUGIN:GetFactions()[targetFaction] + + PLUGIN:NotifyFaction(targetFaction, "Администратор выдал " .. amount .. " очков техники фракции. Теперь: " .. newPoints) + + return "Выдано " .. amount .. " очков фракции " .. factionData.name .. ". Теперь: " .. newPoints + end +}) + +ix.command.Add("SetVehiclePoints", { + description = "Установить очки техники фракции", + adminOnly = true, + arguments = { + ix.type.string, + ix.type.number + }, + OnRun = function(self, client, factionName, amount) + local targetFaction + + for factionID, config in pairs(PLUGIN.config.factions) do + if string.lower(config.name) == string.lower(factionName) then + targetFaction = factionID + break + end + end + + if not targetFaction then + return "Фракция не найдена. Доступные: Россия, Украина" + end + + PLUGIN.factionPoints[targetFaction] = amount + PLUGIN:SyncFactionPoints(targetFaction) + + local factionData = PLUGIN:GetFactions()[targetFaction] + PLUGIN:NotifyFaction(targetFaction, "Администратор установил очки техники на " .. amount) + + return "Установлено " .. amount .. " очков фракции " .. factionData.name + end +}) + +ix.command.Add("CheckVehiclePoints", { + description = "Проверить очки всех фракций", + adminOnly = true, + OnRun = function(self, client) + local result = "Очки техники фракций:\n" + + for factionID, config in pairs(PLUGIN:GetFactions()) do + local points = PLUGIN:GetFactionPoints(factionID) + result = result .. config.name .. ": " .. points .. " очков\n" + end + + return result + end +}) + +ix.command.Add("ResetVehicleCooldowns", { + description = "Сбросить кд техники для игрока", + adminOnly = true, + arguments = { + ix.type.player + }, + OnRun = function(self, client, target) + local character = target:GetCharacter() + if character then + character:SetData("lvs_vehicles", {}) + target:Notify("Ваши кд техники были сброшены администратором") + return "Кд техники сброшены для " .. target:Name() + end + + return "Ошибка сброса кд" + end +}) + +ix.command.Add("ResetAllVehicleCooldowns", { + description = "Сбросить кд техники для всех игроков", + adminOnly = true, + OnRun = function(self, client) + for _, ply in ipairs(player.GetAll()) do + local character = ply:GetCharacter() + if character then + character:SetData("lvs_vehicles", {}) + ply:Notify("Кд техники сброшены администратором") + end + end + + return "Кд техники сброшены для всех игроков" + end +}) + +function PLUGIN:InitializedPlugins() + timer.Simple(5, function() + for factionID, _ in pairs(self:GetFactions()) do + self:SyncFactionPoints(factionID) + end + + -- Синхронизируем существующую технику + timer.Simple(2, function() + for vehicle, _ in pairs(self.activeVehicles) do + if IsValid(vehicle) then + self:SyncVehicleInfo(vehicle) + else + self.activeVehicles[vehicle] = nil + end + end + end) + + -- Подготовим таблицу моделей для клиентов: если в конфиге нет поля model, попробуем получить его из класса + timer.Simple(1, function() + local mapping = {} + + for factionID, factionConfig in pairs(self:GetFactions()) do + mapping[factionID] = { ground = {}, air = {} } + + -- ground + if factionConfig.groundVehicles then + for id, v in pairs(factionConfig.groundVehicles) do + local mdl = v.model + if not mdl or mdl == "" then + -- Попробуем создать временную сущность указанного класса, чтобы прочитать модель + if v.class then + local ok, ent = pcall(function() return ents.Create(v.class) end) + if ok and IsValid(ent) then + mdl = ent:GetModel() or "" + ent:Remove() + else + mdl = "" + end + else + mdl = "" + end + end + + mapping[factionID].ground[id] = mdl or "" + end + end + + -- air + if factionConfig.airVehicles then + for id, v in pairs(factionConfig.airVehicles) do + local mdl = v.model + if not mdl or mdl == "" then + if v.class then + local ok, ent = pcall(function() return ents.Create(v.class) end) + if ok and IsValid(ent) then + mdl = ent:GetModel() or "" + ent:Remove() + else + mdl = "" + end + else + mdl = "" + end + end + + mapping[factionID].air[id] = mdl or "" + end + end + end + + net.Start("LVS_SyncVehicleModels") + net.WriteTable(mapping) + net.Broadcast() + end) + end) +end + +function PLUGIN:InitPostEntity() + timer.Simple(10, function() + for _, factionID in ipairs(ix.faction.indices) do + local cur = self:GetFactionPoints(factionID) or 0 + if cur >= 50000 then -- If it's stuck on old limit/bug + self.factionPoints[factionID] = 1000 + self:SyncFactionPoints(factionID) + end + end + end) +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/voice_chat/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/voice_chat/cl_plugin.lua new file mode 100644 index 0000000..0439dab --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/voice_chat/cl_plugin.lua @@ -0,0 +1,109 @@ +local PLUGIN = PLUGIN + +-- Таблица активных звуков +PLUGIN.activeSounds = PLUGIN.activeSounds or {} + +-- Функция для воспроизведения TTS +function PLUGIN:PlayTTS(url, speakerName, speakerPos) + -- Проверяем, включен ли звук у игрока + if not GetConVar("volume"):GetFloat() or GetConVar("volume"):GetFloat() <= 0 then + return + end + + -- Воспроизводим звук через URL + sound.PlayURL(url, "3d noblock", function(station, errCode, errStr) + if IsValid(station) then + -- Устанавливаем позицию звука + station:SetPos(speakerPos) + + -- Добавляем 3D эффект + station:Set3DEnabled(true) + station:Set3DFadeDistance(50, 500) + + -- Громкость зависит от расстояния + local radius = ix.config.Get("voiceChatRadius", 200) + local distance = LocalPlayer():GetPos():Distance(speakerPos) + local volume = math.Clamp(1 - (distance / radius), 0.1, 1) + station:SetVolume(volume) + + -- Воспроизводим + station:Play() + + -- Сохраняем ссылку на звук + PLUGIN.activeSounds[station] = true + + -- Показываем индикатор говорящего + local hookID = "ixVoiceChat_" .. tostring(SysTime()) + hook.Add("HUDPaint", hookID, function() + if not IsValid(station) or station:GetState() ~= GMOD_CHANNEL_PLAYING then + hook.Remove("HUDPaint", hookID) + PLUGIN.activeSounds[station] = nil + return + end + + -- Рисуем индикатор в правом верхнем углу + local w, h = ScrW(), ScrH() + local text = "🔊 " .. speakerName + + surface.SetFont("ixMenuButtonFont") + local tw, th = surface.GetTextSize(text) + + -- Фон + surface.SetDrawColor(0, 0, 0, 200) + surface.DrawRect(w - tw - 30, 10, tw + 20, th + 10) + + -- Текст + draw.SimpleText(text, "ixMenuButtonFont", w - tw - 20, 15, Color(100, 255, 100), TEXT_ALIGN_LEFT) + end) + + -- Автоматически останавливаем через 30 секунд (защита от зависания) + timer.Simple(30, function() + if IsValid(station) then + station:Stop() + end + end) + else + -- Ошибка загрузки + LocalPlayer():ChatPrint(string.format("[TTS] Ошибка загрузки звука: %s (%s)", errStr or "Unknown", errCode or "0")) + end + end) +end + +-- Получение сетевого сообщения +net.Receive("ixVoiceChatPlay", function() + local url = net.ReadString() + local speakerName = net.ReadString() + local speakerPos = net.ReadVector() + + PLUGIN:PlayTTS(url, speakerName, speakerPos) +end) + +-- Очистка звуков при отключении +function PLUGIN:ShutDown() + for station, _ in pairs(self.activeSounds) do + if IsValid(station) then + station:Stop() + end + end + + self.activeSounds = {} +end + +-- Команда для проверки статуса TTS +concommand.Add("voice_chat_status", function() + local character = LocalPlayer():GetCharacter() + if not character then + LocalPlayer():ChatPrint("[TTS] У вас нет активного персонажа") + return + end + + local hasAccess = character:GetData("voice_chat_unlocked", false) + + if hasAccess then + LocalPlayer():ChatPrint("[TTS] ✓ У вас есть доступ к говорилке") + LocalPlayer():ChatPrint("[TTS] Используйте !tts <текст> для воспроизведения") + else + LocalPlayer():ChatPrint("[TTS] ✗ У вас нет доступа к говорилке") + LocalPlayer():ChatPrint("[TTS] Приобретите её в F4 меню (Другое → Говорилка)") + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/voice_chat/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/voice_chat/sh_plugin.lua new file mode 100644 index 0000000..415692e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/voice_chat/sh_plugin.lua @@ -0,0 +1,25 @@ +PLUGIN.name = "Voice Chat TTS" +PLUGIN.author = "MilitaryRP" +PLUGIN.description = "Система преобразования текста в речь для донатеров" + +ix.config.Add("voiceChatRadius", 200, "Радиус слышимости TTS в метрах", nil, { + data = {min = 50, max = 1000}, + category = "Voice Chat" +}) + +ix.config.Add("voiceChatCommand", "tts", "Команда для активации TTS (без !)", nil, { + category = "Voice Chat" +}) + +ix.config.Add("voiceChatCooldown", 5, "Задержка между использованием TTS в секундах", nil, { + data = {min = 1, max = 60}, + category = "Voice Chat" +}) + +ix.config.Add("voiceChatMaxLength", 200, "Максимальная длина сообщения TTS", nil, { + data = {min = 50, max = 500}, + category = "Voice Chat" +}) + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/voice_chat/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/voice_chat/sv_plugin.lua new file mode 100644 index 0000000..5afd4ec --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/voice_chat/sv_plugin.lua @@ -0,0 +1,122 @@ +local PLUGIN = PLUGIN + +-- Таблица для отслеживания кулдаунов +PLUGIN.voiceChatCooldowns = PLUGIN.voiceChatCooldowns or {} + +-- Функция для проверки доступа к TTS +function PLUGIN:CanUseTTS(client) + local character = client:GetCharacter() + if not character then return false end + + return character:GetData("voice_chat_unlocked", false) +end + +-- Функция для получения игроков в радиусе +function PLUGIN:GetPlayersInRadius(origin, radius) + local players = {} + local radiusSqr = radius * radius + + for _, ply in ipairs(player.GetAll()) do + if ply:GetPos():DistToSqr(origin) <= radiusSqr then + table.insert(players, ply) + end + end + + return players +end + +-- Функция для генерации TTS URL (Google Translate TTS) +function PLUGIN:GenerateTTSURL(text) + -- Кодируем текст для URL + local encoded = text + encoded = string.gsub(encoded, "([^%w%s%-%.%_%~])", function(c) + return string.format("%%%02X", string.byte(c)) + end) + encoded = string.gsub(encoded, " ", "+") + + -- Google Translate TTS API (бесплатный, без ключа) + -- lang=ru - русский язык + return string.format("https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=ru&q=%s", encoded) +end + +-- Команда чата для TTS +function PLUGIN:OnPlayerChat(client, text, isTeam, isDead) + local command = ix.config.Get("voiceChatCommand", "tts") + local prefix = "!" .. command .. " " + + -- Проверяем, начинается ли сообщение с команды + if not string.StartWith(text, prefix) then + return + end + + -- Проверяем доступ + if not self:CanUseTTS(client) then + client:Notify("У вас нет доступа к говорилке! Приобретите её в F4 меню.") + return "" + end + + -- Проверяем кулдаун + local cooldownTime = ix.config.Get("voiceChatCooldown", 5) + local lastUse = self.voiceChatCooldowns[client:SteamID()] or 0 + + if CurTime() - lastUse < cooldownTime then + local remaining = math.ceil(cooldownTime - (CurTime() - lastUse)) + client:Notify(string.format("Подождите %d секунд перед следующим использованием!", remaining)) + return "" + end + + -- Получаем текст сообщения + local message = string.sub(text, #prefix + 1) + message = string.Trim(message) + + -- Проверяем длину + local maxLength = ix.config.Get("voiceChatMaxLength", 200) + if #message == 0 then + client:Notify("Введите текст после команды !" .. command) + return "" + end + + if #message > maxLength then + client:Notify(string.format("Сообщение слишком длинное! Максимум %d символов.", maxLength)) + return "" + end + + -- Устанавливаем кулдаун + self.voiceChatCooldowns[client:SteamID()] = CurTime() + + -- Генерируем URL для TTS + local ttsURL = self:GenerateTTSURL(message) + + -- Получаем игроков в радиусе + local radius = ix.config.Get("voiceChatRadius", 200) + local nearbyPlayers = self:GetPlayersInRadius(client:GetPos(), radius) + + -- Отправляем TTS всем игрокам в радиусе + local clientName = client:Name() + for _, ply in ipairs(nearbyPlayers) do + net.Start("ixVoiceChatPlay") + net.WriteString(ttsURL) + net.WriteString(clientName) + net.WriteVector(client:GetPos()) + net.Send(ply) + end + + -- Показываем сообщение в чате + local chatText = string.format("[TTS] %s: %s", clientName, message) + for _, ply in ipairs(nearbyPlayers) do + ply:ChatPrint(chatText) + end + + -- Не показываем оригинальное сообщение + return "" +end + +-- Очистка кулдаунов при отключении игрока +function PLUGIN:PlayerDisconnected(client) + self.voiceChatCooldowns[client:SteamID()] = nil +end + +-- Сетевой канал для отправки TTS клиентам +if SERVER then + util.AddNetworkString("ixVoiceChatPlay") +end diff --git a/garrysmod/gamemodes/militaryrp/plugins/warn/cl_derma.lua b/garrysmod/gamemodes/militaryrp/plugins/warn/cl_derma.lua new file mode 100644 index 0000000..183252e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/warn/cl_derma.lua @@ -0,0 +1,202 @@ +local PANEL = {} + +local COLOR_BG = Color(30, 30, 30, 240) +local COLOR_HEADER = Color(20, 20, 20, 255) +local COLOR_ACCENT = Color(200, 50, 50) -- Red for warnings +local COLOR_TEXT = Color(240, 240, 240) +local COLOR_TEXT_DIM = Color(180, 180, 180) + +function PANEL:Init() + self:SetSize(ScrW() * 0.6, ScrH() * 0.7) + self:Center() + self:MakePopup() + self:SetTitle("") + self:ShowCloseButton(false) + + self.admins = {} + self.warns = {} + + -- Close Button + local closeBtn = self:Add("DButton") + closeBtn:SetSize(40, 30) + closeBtn:SetPos(self:GetWide() - 40, 0) + closeBtn:SetText("X") + closeBtn:SetFont("ixMediumFont") + closeBtn:SetTextColor(COLOR_TEXT) + closeBtn.Paint = function(s, w, h) + if (s:IsHovered()) then + draw.RoundedBox(0, 0, 0, w, h, Color(200, 50, 50)) + end + end + closeBtn.DoClick = function() + self:Close() + end + + -- Container + self.container = self:Add("Panel") + self.container:Dock(FILL) + self.container:DockMargin(10, 35, 10, 10) + + -- Left: Admin List + self.leftPanel = self.container:Add("Panel") + self.leftPanel:Dock(LEFT) + self.leftPanel:SetWide(self:GetWide() * 0.4) + self.leftPanel:DockMargin(0, 0, 10, 0) + + self.scroll = self.leftPanel:Add("DScrollPanel") + self.scroll:Dock(FILL) + + -- Right: Details & History + self.rightPanel = self.container:Add("Panel") + self.rightPanel:Dock(FILL) + self.rightPanel.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(40, 40, 40, 255)) + end + + self.details = self.rightPanel:Add("Panel") + self.details:Dock(TOP) + self.details:SetTall(150) + self.details:DockPadding(15, 15, 15, 15) + + self.historyScroll = self.rightPanel:Add("DScrollPanel") + self.historyScroll:Dock(FILL) + self.historyScroll:DockMargin(10, 10, 10, 10) + + self.noSelected = self.rightPanel:Add("DLabel") + self.noSelected:SetText("Выберите администратора из списка слева") + self.noSelected:SetFont("ixMediumFont") + self.noSelected:Dock(FILL) + self.noSelected:SetContentAlignment(5) + self.noSelected:SetTextColor(COLOR_TEXT_DIM) +end + +function PANEL:Populate(admins, warns) + self.admins = admins + self.warns = warns + + local sortedAdmins = {} + for k, v in pairs(admins) do + table.insert(sortedAdmins, v) + end + table.sort(sortedAdmins, function(a, b) + if (a.online != b.online) then return a.online end + return a.name < b.name + end) + + for _, admin in ipairs(sortedAdmins) do + local btn = self.scroll:Add("DButton") + btn:Dock(TOP) + btn:SetTall(50) + btn:DockMargin(0, 0, 0, 5) + btn:SetText("") + + local warnCount = (warns[admin.steamID] and warns[admin.steamID].count) or 0 + + btn.Paint = function(s, w, h) + local bg = s:IsHovered() and Color(60, 60, 60) or Color(45, 45, 45) + draw.RoundedBox(4, 0, 0, w, h, bg) + + -- Online/Offline Indicator + surface.SetDrawColor(admin.online and Color(100, 255, 100) or Color(150, 150, 150)) + surface.DrawRect(3, 3, 4, h - 6) + + draw.SimpleText(admin.name, "ixMediumFont", 15, h/2 - 8, COLOR_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(admin.rank, "ixSmallFont", 15, h/2 + 10, COLOR_TEXT_DIM, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + local warnCol = warnCount > 0 and COLOR_ACCENT or COLOR_TEXT_DIM + draw.SimpleText("Warns: " .. warnCount .. "/3", "ixMediumFont", w - 15, h/2, warnCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + btn.DoClick = function() + self:ShowAdminDetails(admin) + end + end +end + +function PANEL:ShowAdminDetails(admin) + self.noSelected:SetVisible(false) + self.details:Clear() + self.historyScroll:Clear() + + local name = self.details:Add("DLabel") + name:SetText(admin.name) + name:SetFont("ixMediumFont") -- Fixed font + name:Dock(TOP) + name:SetTextColor(COLOR_TEXT) + name:SizeToContents() + + local steamid = self.details:Add("DLabel") + steamid:SetText(admin.steamID) + steamid:SetFont("ixSmallFont") + steamid:Dock(TOP) + steamid:SetTextColor(COLOR_TEXT_DIM) + steamid:SetMouseInputEnabled(true) + steamid:SetCursor("hand") + steamid.DoClick = function() + SetClipboardText(admin.steamID) + surface.PlaySound("ui/buttonclick.wav") + LocalPlayer():Notify("SteamID скопирован: " .. admin.steamID) + end + + local actionPanel = self.details:Add("Panel") + actionPanel:Dock(BOTTOM) + actionPanel:SetTall(40) + + local addWarn = actionPanel:Add("ixMenuButton") + addWarn:SetText("ВЫДАТЬ ВЫГОВОР") + addWarn:Dock(LEFT) + addWarn:SetWide(150) + addWarn.DoClick = function() + Derma_StringRequest("Выдача выговора", "Введите причину выговора для " .. admin.name, "", function(text) + net.Start("ixWarnAdd") + net.WriteString(admin.steamID) + net.WriteString(text) + net.SendToServer() + self:Close() + end) + end + + -- History + local warnData = self.warns[admin.steamID] + if (warnData and warnData.history) then + for i = #warnData.history, 1, -1 do + local h = warnData.history[i] + local entry = self.historyScroll:Add("Panel") + entry:Dock(TOP) + entry:SetTall(60) + entry:DockMargin(0, 0, 0, 5) + + entry.Paint = function(s, w, h_tall) + draw.RoundedBox(4, 0, 0, w, h_tall, Color(50, 50, 50)) + local typeText = h.type == "add" and "[ВЫДАНО]" or "[СНЯТО]" + local typeCol = h.type == "add" and COLOR_ACCENT or Color(100, 200, 100) + + draw.SimpleText(typeText .. " От: " .. h.admin, "ixSmallFont", 10, 15, typeCol) + draw.SimpleText(os.date("%d/%m/%Y %H:%M", h.time), "ixSmallFont", w - 10, 15, COLOR_TEXT_DIM, TEXT_ALIGN_RIGHT) + draw.SimpleText(h.reason, "ixMediumFont", 10, 40, COLOR_TEXT) + end + + if (h.type == "add" and admin.online) then + local remove = entry:Add("DButton") + remove:SetText("Снять") + remove:SetSize(60, 20) + remove:SetPos(self.historyScroll:GetWide() - 70, 35) + remove.DoClick = function() + net.Start("ixWarnRemove") + net.WriteString(admin.steamID) + net.WriteInt(i, 16) + net.SendToServer() + self:Close() + end + end + end + end +end + +function PANEL:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, COLOR_BG) + draw.RoundedBoxEx(4, 0, 0, w, 30, COLOR_HEADER, true, true, false, false) + draw.SimpleText("СИСТЕМА ВЫГОВОРОВ (WARN SYSTEM)", "ixMediumFont", 10, 15, COLOR_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +vgui.Register("ixWarnMenu", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/militaryrp/plugins/warn/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/warn/cl_plugin.lua new file mode 100644 index 0000000..80605c5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/warn/cl_plugin.lua @@ -0,0 +1,13 @@ +local PLUGIN = PLUGIN + +net.Receive("ixWarnMenu", function() + local admins = net.ReadTable() + local warns = net.ReadTable() + + if (IsValid(ix.gui.warnMenu)) then + ix.gui.warnMenu:Remove() + end + + ix.gui.warnMenu = vgui.Create("ixWarnMenu") + ix.gui.warnMenu:Populate(admins, warns) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/warn/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/warn/sh_plugin.lua new file mode 100644 index 0000000..512792d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/warn/sh_plugin.lua @@ -0,0 +1,89 @@ +local PLUGIN = PLUGIN +PLUGIN.name = "Warn System" +PLUGIN.author = "Scripty" +PLUGIN.description = "Modern Warning System for administrators with automatic demotion." + +print("[Warn System] Loading Plugin...") + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") +ix.util.Include("cl_derma.lua") + +if (SERVER) then + util.AddNetworkString("ixWarnSync") + util.AddNetworkString("ixWarnAdd") + util.AddNetworkString("ixWarnRemove") + util.AddNetworkString("ixWarnMenu") +end + +ix.command.Add("warn", { + description = "Open the warning management menu.", + adminOnly = true, + superAdminOnly = true, -- Helix support varies, so we keep CanRun + CanRun = function(self, client) + return client:IsSuperAdmin() + end, + OnRun = function(self, client) + if (!client:IsSuperAdmin()) then return end + + local admins = {} + local warns = ix.data.Get("warns", {}) + + -- Get all players who are online and admins + for _, v in ipairs(player.GetAll()) do + if (v:IsAdmin()) then + admins[v:SteamID()] = { + name = v:Name(), + steamID = v:SteamID(), + rank = v:GetUserGroup(), + online = true + } + end + end + + -- Add offline admins who have warnings (Helix data) + for steamID, data in pairs(warns) do + if (!admins[steamID]) then + admins[steamID] = { + name = data.name or "Unknown", + steamID = steamID, + rank = data.rank or "N/A", + online = false + } + end + end + + -- Try to fetch ALL admins from SAM database if available + if (SERVER and sam and sam.SQL) then + sam.SQL.FQuery("SELECT `steamid`, `name`, `rank` FROM `sam_players` WHERE `rank` != 'user'", {}, function(data) + if (data) then + for _, row in ipairs(data) do + local steamID = row.steamid + -- Use SteamID if it's already a string, or format it + if (!admins[steamID]) then + admins[steamID] = { + name = (row.name != "" and row.name) or "Unknown", + steamID = steamID, + rank = row.rank or "N/A", + online = false + } + end + end + end + + if (IsValid(client)) then + net.Start("ixWarnMenu") + net.WriteTable(admins) + net.WriteTable(warns) + net.Send(client) + end + end) + else + -- Fallback/Helix only + net.Start("ixWarnMenu") + net.WriteTable(admins) + net.WriteTable(warns) + net.Send(client) + end + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/warn/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/warn/sv_plugin.lua new file mode 100644 index 0000000..3cf830f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/warn/sv_plugin.lua @@ -0,0 +1,93 @@ +local PLUGIN = PLUGIN +print("[Warn System] Server Side Loaded") + +-- Load warnings on startup +function PLUGIN:Initialize() + self.warns = ix.data.Get("warns", {}) +end + +-- Save warnings to permanent storage +function PLUGIN:SaveWarns() + ix.data.Set("warns", self.warns) +end + +net.Receive("ixWarnAdd", function(length, client) + if (!client:IsSuperAdmin()) then return end + + local targetSteamID = net.ReadString() + local reason = net.ReadString() or "No reason provided" + + PLUGIN.warns = ix.data.Get("warns", {}) + PLUGIN.warns[targetSteamID] = PLUGIN.warns[targetSteamID] or {count = 0, history = {}} + + local targetData = PLUGIN.warns[targetSteamID] + targetData.count = targetData.count + 1 + + table.insert(targetData.history, { + admin = client:Name(), + adminSteamID = client:SteamID(), + reason = reason, + time = os.time(), + type = "add" + }) + + local targetPlayer = player.GetBySteamID(targetSteamID) + if (IsValid(targetPlayer)) then + targetData.name = targetPlayer:Name() + targetData.rank = targetPlayer:GetUserGroup() + + targetPlayer:Notify("Вы получили выговор (" .. targetData.count .. "/3). Причина: " .. reason) + end + + -- Automatic demotion (3/3 warnings) - Now works for offline players too + if (targetData.count >= 3) then + if (sam) then + -- Use SAM's Lua API for reliable rank setting (offline or online) + sam.player.set_rank_id(targetSteamID, "user", 0) + print("[Warn System] Initiated demotion for " .. targetSteamID .. " via SAM.") + else + -- Fallback for other systems + if (IsValid(targetPlayer)) then + targetPlayer:SetUserGroup("user") + else + -- If offline and not SAM, we attempt console command as last resort + RunConsoleCommand("ulx", "adduser", targetSteamID, "user") + end + end + + PLUGIN.warns[targetSteamID] = nil -- Remove from system after demotion + + if (IsValid(targetPlayer)) then + targetPlayer:Notify("Вы были автоматически сняты с админ-прав за достижение 3 выговоров.") + end + + ix.util.Notify((targetData.name or targetSteamID) .. " был автоматически снят с админ-прав (3/3 выговоров).") + end + + PLUGIN:SaveWarns() + ix.util.Notify(client:Name() .. " выдал выговор " .. (targetData.name or targetSteamID) .. ". Причина: " .. reason) +end) + +net.Receive("ixWarnRemove", function(length, client) + if (!client:IsSuperAdmin()) then return end + + local targetSteamID = net.ReadString() + local index = net.ReadInt(16) + + PLUGIN.warns = ix.data.Get("warns", {}) + local targetData = PLUGIN.warns[targetSteamID] + + if (targetData and targetData.history[index]) then + targetData.count = math.max(0, targetData.count - 1) + table.insert(targetData.history, { + admin = client:Name(), + adminSteamID = client:SteamID(), + reason = "Снятие выговора #" .. index, + time = os.time(), + type = "remove" + }) + + PLUGIN:SaveWarns() + ix.util.Notify(client:Name() .. " снял выговор у " .. (targetData.name or targetSteamID)) + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/cl_plugin.lua new file mode 100644 index 0000000..813efc7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/cl_plugin.lua @@ -0,0 +1,62 @@ +local PLUGIN = PLUGIN + +net.Receive("ixKitSync", function() + PLUGIN.kits = net.ReadTable() +end) + +net.Receive("ixKitMenu", function() + if (IsValid(ix.gui.kitMenu)) then + ix.gui.kitMenu:Remove() + end + + ix.gui.kitMenu = vgui.Create("DFrame") + local menu = ix.gui.kitMenu + menu:SetSize(500, 400) + menu:SetTitle("Меню донатных наборов") + menu:Center() + menu:MakePopup() + + local scroll = menu:Add("DScrollPanel") + scroll:Dock(FILL) + + for weaponClass, kit in pairs(PLUGIN.kits) do + local panel = scroll:Add("DPanel") + panel:Dock(TOP) + panel:SetHeight(60) + panel:DockMargin(5, 5, 5, 5) + + local name = panel:Add("DLabel") + name:SetText(kit.name) + name:SetFont("ixMediumFont") + name:SetTextColor(color_black) + name:Dock(LEFT) + name:DockMargin(10, 0, 0, 0) + name:SizeToContents() + + local btn = panel:Add("DButton") + btn:SetText("Взять") + btn:Dock(RIGHT) + btn:SetWidth(100) + btn:DockMargin(10, 10, 10, 10) + + btn.DoClick = function() + net.Start("ixKitClaim") + net.WriteString(weaponClass) + net.SendToServer() + menu:Remove() + end + + -- Обработка отображения КД (простая версия) + local character = LocalPlayer():GetCharacter() + if (character) then + local cooldowns = character:GetData("kit_cooldowns", {}) + local lastClaim = cooldowns[weaponClass] or 0 + local remaining = (lastClaim + kit.cooldown) - os.time() + + if (remaining > 0) then + btn:SetEnabled(false) + btn:SetText(math.ceil(remaining / 60) .. " мин.") + end + end + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/sh_plugin.lua new file mode 100644 index 0000000..622ffee --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/sh_plugin.lua @@ -0,0 +1,83 @@ +local PLUGIN = PLUGIN + +PLUGIN.name = "Weapon Kits" +PLUGIN.author = "Scripty" +PLUGIN.description = "Adds weapon kits for specific user groups with cooldowns." + +ix.util.Include("sv_plugin.lua") +ix.util.Include("cl_plugin.lua") + +PLUGIN.kits = PLUGIN.kits or {} + +ix.command.Add("KitMenu", { + description = "Open the weapon kit selection menu.", + OnRun = function(self, client) + local character = client:GetCharacter() + if (!character) then return end + + local allowedGroups = { + ["superadmin"] = true, + ["sponsor"] = true, + ["prem"] = true + } + + if (!allowedGroups[client:GetUserGroup()]) then + return "У вас нет доступа к этому меню." + end + + net.Start("ixKitMenu") + net.Send(client) + end +}) + +ix.command.Add("KitAdd", { + description = "Add a new weapon kit (Admin only).", + privilege = "Manage Kits", + adminOnly = true, + arguments = { + ix.type.string, + ix.type.number + }, + OnRun = function(self, client, weaponClass, cooldown) + local weaponTable = weapons.GetStored(weaponClass) + if (!weaponTable) then + return "Некорректный класс оружия." + end + + PLUGIN.kits[weaponClass] = { + name = weaponTable.PrintName or weaponClass, + cooldown = cooldown -- in seconds + } + + if (SERVER) then + PLUGIN:SaveData() + PLUGIN:SyncKits() + end + + return "Вы добавили набор: " .. (weaponTable.PrintName or weaponClass) .. " с КД " .. (cooldown / 60) .. " мин." + end +}) + +ix.command.Add("KitRemove", { + description = "Remove a weapon kit (Admin only).", + privilege = "Manage Kits", + adminOnly = true, + arguments = { + ix.type.string + }, + OnRun = function(self, client, weaponClass) + if (!PLUGIN.kits[weaponClass]) then + return "Такого набора не существует." + end + + PLUGIN.kits[weaponClass] = nil + + if (SERVER) then + PLUGIN:SaveData() + PLUGIN:SyncKits() + end + + return "Вы удалили набор: " .. weaponClass + end +}) + diff --git a/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/sv_plugin.lua new file mode 100644 index 0000000..3e1433e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/weapon_kits/sv_plugin.lua @@ -0,0 +1,69 @@ +local PLUGIN = PLUGIN + +util.AddNetworkString("ixKitMenu") +util.AddNetworkString("ixKitSync") +util.AddNetworkString("ixKitClaim") + +function PLUGIN:SaveData() + self:SetData(self.kits) +end + +function PLUGIN:LoadData() + self.kits = self:GetData() or {} +end + +function PLUGIN:SyncKits(client) + net.Start("ixKitSync") + net.WriteTable(self.kits) + if (client) then + net.Send(client) + else + net.Broadcast() + end +end + +function PLUGIN:PlayerLoadedCharacter(client, character) + self:SyncKits(client) +end + +net.Receive("ixKitClaim", function(len, client) + local character = client:GetCharacter() + if (!character) then return end + + local weaponClass = net.ReadString() + local kit = PLUGIN.kits[weaponClass] + + if (!kit) then return end + + -- Проверка группы + local allowedGroups = { + ["superadmin"] = true, + ["sponsor"] = true, + ["prem"] = true + } + + if (!allowedGroups[client:GetUserGroup()]) then + client:Notify("У вас нет доступа.") + return + end + + -- Проверка КД на персонаже + local cooldowns = character:GetData("kit_cooldowns", {}) + local lastClaim = cooldowns[weaponClass] or 0 + local remaining = (lastClaim + kit.cooldown) - os.time() + + if (remaining > 0) then + client:Notify("Откат еще " .. math.ceil(remaining / 60) .. " мин.") + return + end + + -- Выдача + client:Give(weaponClass) + client:SelectWeapon(weaponClass) + + -- Установка КД + cooldowns[weaponClass] = os.time() + character:SetData("kit_cooldowns", cooldowns) + + client:Notify("Вы получили кит: " .. kit.name) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/cl_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/cl_plugin.lua new file mode 100644 index 0000000..80d97cb --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/cl_plugin.lua @@ -0,0 +1,384 @@ +local menuFrame = nil +local factions = {} + +local function GetPlayerBySteamID(steamID) + for _, ply in ipairs(player.GetAll()) do + if ply:SteamID() == steamID then + return ply + end + end + return nil +end + +local function GetDisplayName(input) + if isstring(input) then + return input + elseif istable(input) then + return input[1] or tostring(input) + else + return tostring(input) + end +end + +local selectedPlayer = nil +local selectedFaction = nil +local selectedPodr = nil +local selectedSpec = nil +local selectedRank = nil + +local playerButtons = {} +local factionButtons = {} +local podrButtons = {} +local specButtons = {} +local rankButtons = {} + +local originalPlayers = {} + +net.Receive("ixWhitelistMenuOpen", function() + if menuFrame and menuFrame:IsValid() then menuFrame:Close() end + net.Start("ixWhitelistMenuDataRequest") + net.SendToServer() +end) + +net.Receive("ixWhitelistMenuData", function() + if menuFrame and menuFrame:IsValid() then menuFrame:Close() end + + originalPlayers = net.ReadTable() + factions = net.ReadTable() + + selectedPlayer = nil + selectedFaction = nil + selectedPodr = nil + selectedSpec = nil + selectedRank = nil + + playerButtons = {} + factionButtons = {} + podrButtons = {} + specButtons = {} + rankButtons = {} + + local scrW, scrH = ScrW(), ScrH() + + menuFrame = vgui.Create("DFrame") + menuFrame:SetSize(900, 650) + menuFrame:SetPos(scrW/2 - 450, scrH/2 - 325) + menuFrame:SetTitle("") + menuFrame:SetDraggable(false) + menuFrame:ShowCloseButton(false) + menuFrame:MakePopup() + menuFrame.Paint = function(s, w, h) + -- Фон + surface.SetDrawColor(Color(13, 13, 13, 240)) + surface.DrawRect(0, 0, w, h) + + -- Обводка + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + + -- Верхняя панель + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, 40) + + -- Заголовок + draw.SimpleText("МЕНЕДЖЕР ВАЙТЛИСТА", "ixMenuButtonFont", w/2, 20, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local closeBtn = vgui.Create("DButton", menuFrame) + closeBtn:SetText("") + closeBtn:SetSize(30, 30) + closeBtn:SetPos(menuFrame:GetWide() - 40, 5) + closeBtn.Paint = function(s, w, h) + surface.SetDrawColor(Color(200, 50, 50)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(29, 29, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("×", "ixSmallFont", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if s:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + closeBtn.DoClick = function() + menuFrame:Close() + end + + local function CreateRow(parent, text, onSelect, data, buttonGroup) + local btn = parent:Add("DButton") + btn:SetText("") + btn:SetTall(30) + btn:SetFont("ixSmallFont") + + btn.Paint = function(s, w, h) + if s.isSelected then + surface.SetDrawColor(Color(1, 67, 29, 200)) + surface.DrawRect(0, 0, w, h) + else + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + end + + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + + local textColor = s.isSelected and color_white or Color(200, 200, 200) + draw.SimpleText(text, "ixSmallFont", 8, h/2, textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + if s:IsHovered() and not s.isSelected then + surface.SetDrawColor(Color(255, 255, 255, 20)) + surface.DrawRect(0, 0, w, h) + end + end + + btn.DoClick = function() + for _, otherBtn in ipairs(buttonGroup) do + if otherBtn ~= btn then + otherBtn.isSelected = false + end + end + btn.isSelected = true + if onSelect then onSelect(data) end + end + + btn:DockMargin(2, 2, 2, 2) + btn:Dock(TOP) + + table.insert(buttonGroup, btn) + return btn + end + + local colWidth = 160 + local colSpacing = 10 + local colX = 20 + local headerHeight = 30 + local scrollHeight = 500 + local totalHeight = headerHeight + scrollHeight + local startY = 60 + local buttonHeight = 40 + + local function CreateColumnPanel(name, x) + local panel = vgui.Create("DPanel", menuFrame) + panel:SetSize(colWidth, totalHeight) + panel:SetPos(x, startY) + panel.Paint = function(s, w, h) + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local header = vgui.Create("DLabel", panel) + header:SetText(name) + header:SetPos(0, 5) + header:SetSize(colWidth, 20) + header:SetFont("ixSmallFont") + header:SetTextColor(color_white) + header:SetContentAlignment(5) + + local scroll = vgui.Create("DScrollPanel", panel) + scroll:SetSize(colWidth - 4, scrollHeight) + scroll:SetPos(2, headerHeight) + + return scroll + end + + -- Панель игроков с поиском + local playerPanel = vgui.Create("DPanel", menuFrame) + playerPanel:SetSize(colWidth, totalHeight) + playerPanel:SetPos(colX, startY) + playerPanel.Paint = function(s, w, h) + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + end + + local playerHeader = vgui.Create("DLabel", playerPanel) + playerHeader:SetText("ИГРОКИ") + playerHeader:SetPos(0, 5) + playerHeader:SetSize(colWidth, 20) + playerHeader:SetFont("ixSmallFont") + playerHeader:SetTextColor(color_white) + playerHeader:SetContentAlignment(5) + + local searchBox = vgui.Create("DTextEntry", playerPanel) + searchBox:SetPos(5, 30) + searchBox:SetSize(colWidth - 10, 25) + searchBox:SetPlaceholderText("Поиск...") + searchBox:SetFont("ixSmallFont") + searchBox:SetUpdateOnType(true) + searchBox.Paint = function(s, w, h) + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + s:DrawTextEntryText(color_white, Color(30, 130, 255), color_white) + end + + local playerScroll = vgui.Create("DScrollPanel", playerPanel) + playerScroll:SetSize(colWidth - 4, scrollHeight - 40) + playerScroll:SetPos(2, 60) + + -- Создаем колонки + local factionScroll = CreateColumnPanel("ФРАКЦИИ", colX + (colWidth + colSpacing) * 1) + local podrScroll = CreateColumnPanel("ПОДРАЗДЕЛЕНИЯ", colX + (colWidth + colSpacing) * 2) + local specScroll = CreateColumnPanel("СПЕЦИАЛИЗАЦИИ", colX + (colWidth + colSpacing) * 3) + local rankScroll = CreateColumnPanel("ЗВАНИЯ", colX + (colWidth + colSpacing) * 4) + + local function UpdatePlayerList(filterText) + playerScroll:Clear() + playerButtons = {} + + local lowerFilter = string.lower(filterText or "") + + for _, p in ipairs(originalPlayers) do + if filterText == "" or string.find(string.lower(p.name), lowerFilter, 1, true) then + CreateRow(playerScroll, p.name, function(data) + selectedPlayer = data + factionScroll:Clear() + podrScroll:Clear() + specScroll:Clear() + rankScroll:Clear() + selectedFaction = nil + selectedPodr = nil + selectedSpec = nil + selectedRank = nil + + factionButtons = {} + podrButtons = {} + specButtons = {} + rankButtons = {} + + for id, f in pairs(factions) do + CreateRow(factionScroll, GetDisplayName(f.name), function(factionData) + selectedFaction = factionData + local f = factions[factionData.id] + + podrScroll:Clear() + specScroll:Clear() + rankScroll:Clear() + selectedPodr = nil + selectedSpec = nil + selectedRank = nil + + podrButtons = {} + specButtons = {} + rankButtons = {} + + for podId, podr in pairs(f.Podr) do + CreateRow(podrScroll, podr.name, function(podrData) + selectedPodr = podrData + local f2 = factions[selectedFaction.id] + + -- Update specialties + specScroll:Clear() + selectedSpec = nil + specButtons = {} + for specId, spec in pairs(f2.Spec) do + if spec.podr == podrData.id then + CreateRow(specScroll, spec.name, function(specData) + selectedSpec = specData + end, { id = specId }, specButtons) + end + end + + -- Update ranks based on subdivision + rankScroll:Clear() + selectedRank = nil + rankButtons = {} + local targetRanks = (f2.Podr[podrData.id] and f2.Podr[podrData.id].ranks) or f2.Ranks + if targetRanks then + local keys = {} + for k in pairs(targetRanks) do table.insert(keys, k) end + table.sort(keys) + for _, rankId in ipairs(keys) do + local rank = targetRanks[rankId] + CreateRow(rankScroll, GetDisplayName(rank[1]), function(rankData) + selectedRank = rankData + end, { id = rankId }, rankButtons) + end + end + end, { id = podId }, podrButtons) + end + + local factionRanks = f.Ranks + if factionRanks then + local keys = {} + for k in pairs(factionRanks) do table.insert(keys, k) end + table.sort(keys) + for _, rankId in ipairs(keys) do + local rank = factionRanks[rankId] + CreateRow(rankScroll, GetDisplayName(rank[1]), function(rankData) + selectedRank = rankData + end, { id = rankId }, rankButtons) + end + end + end, { id = id }, factionButtons) + end + end, { steamID = p.steamID }, playerButtons) + end + end + end + + searchBox.OnValueChange = function(_, value) + UpdatePlayerList(value) + end + + UpdatePlayerList("") + + -- Кнопка назначения + local applyBtn = vgui.Create("DButton", menuFrame) + applyBtn:SetText("") + applyBtn:SetSize(200, 40) + applyBtn:SetPos(menuFrame:GetWide()/2 - 100, startY + totalHeight + 10) + applyBtn.Paint = function(s, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(29, 29, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.SimpleText("НАЗНАЧИТЬ", "ixSmallFont", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if s:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + applyBtn.DoClick = function() + if not (selectedPlayer and selectedFaction and selectedPodr and selectedSpec and selectedRank) then + LocalPlayer():Notify("Выберите все поля!") + return + end + + net.Start("ixWhitelistMenuApply") + net.WriteString(selectedPlayer.steamID) + net.WriteUInt(selectedFaction.id, 16) + net.WriteUInt(selectedPodr.id, 8) + net.WriteUInt(selectedSpec.id, 8) + net.WriteUInt(selectedRank.id, 8) + net.SendToServer() + + menuFrame:Close() + LocalPlayer():Notify("Вайтлист успешно назначен!") + end + + -- Статус выбора + local statusPanel = vgui.Create("DPanel", menuFrame) + statusPanel:SetSize(400, 30) + statusPanel:SetPos(menuFrame:GetWide()/2 - 200, startY + totalHeight - 553.5) + statusPanel.Paint = function(s, w, h) + local statusText = "Выберите игрока и все параметры" + local statusColor = Color(200, 200, 200) + + if selectedPlayer and selectedFaction and selectedPodr and selectedSpec and selectedRank then + statusText = "Готово к назначению" + statusColor = Color(84, 147, 90) + end + + draw.SimpleText(statusText, "ixSmallFont", w/2, h/2, statusColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + menuFrame.OnCursorEntered = function(s) + s:MoveToFront() + end +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/sh_plugin.lua new file mode 100644 index 0000000..ef5a344 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/sh_plugin.lua @@ -0,0 +1,63 @@ +PLUGIN.name = "Whitelist Menu" +PLUGIN.author = "Refosel" +PLUGIN.description = "Меню для выдачи фракции, подразделения, специализации и звания." + +-- Регистрация CAMI-привилегии при загрузке +function PLUGIN:Load() + CAMI.RegisterPrivilege({ + Name = "WhitelistMenu - Open", + Description = "Может открывать меню назначения роли (фракция, подразделение, специализация, звание).", + MinAccess = "user" + }) +end + +PLUGIN.AllowedGroups = { + ["superadmin"] = true, + ["super admin"] = true, + ["projectteam"] = true, + ["teh.admin"] = true, + ["curator"] = true, + ["sudo-curator"] = true, + ["asist-sudo"] = true, + ["admin"] = true, + ["st.admin"] = true, + ["ivent"] = true, + ["st.event"] = true, + ["event"] = true, + ["disp"] = true, + ["assistant"] = true, + ["specadmin"] = true, + ["sponsor"] = true, + ["prem"] = true, + ["cmd"] = true, + ["inst"] = true +} + +function PLUGIN:HasAccess(client) + local character = client:GetCharacter() + if (character and (character:HasFlags("W") or character:HasFlags("w"))) then + return true + end + + local userGroup = string.lower(client:GetUserGroup() or "user") + return self.AllowedGroups[userGroup] or client:IsAdmin() or client:IsSuperAdmin() +end + +ix.util.Include("cl_plugin.lua") +ix.util.Include("sv_plugin.lua") + +-- Команда без аргументов +ix.command.Add("wl", { + description = "Открыть меню назначения роли", + arguments = {}, + CanRun = function(self, client) + return PLUGIN:HasAccess(client) + end, + OnRun = function(self, client) + if not client:Alive() then + return "@notAlive" + end + net.Start("ixWhitelistMenuOpen") + net.Send(client) + end +}) diff --git a/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/sv_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/sv_plugin.lua new file mode 100644 index 0000000..815ad6c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/whitelistmenu/sv_plugin.lua @@ -0,0 +1,105 @@ +local PLUGIN = PLUGIN +util.AddNetworkString("ixWhitelistMenuOpen") +util.AddNetworkString("ixWhitelistMenuDataRequest") +util.AddNetworkString("ixWhitelistMenuData") +util.AddNetworkString("ixWhitelistMenuApply") + +local function GetFactionData() + local data = {} + for k, v in pairs(ix.faction.indices) do + if v.Podr and v.Spec and v.Ranks then + data[k] = { + name = v.name, + Podr = v.Podr, + Spec = v.Spec, + Ranks = v.Ranks + } + end + end + return data +end + +net.Receive("ixWhitelistMenuDataRequest", function(_, client) + if not PLUGIN:HasAccess(client) then + return + end + + local players = {} + for _, ply in ipairs(player.GetAll()) do + if ply:Alive() and ply:GetCharacter() then + table.insert(players, { + steamID = ply:SteamID(), + name = ply:Name() + }) + end + end + + net.Start("ixWhitelistMenuData") + net.WriteTable(players) + net.WriteTable(GetFactionData()) + net.Send(client) +end) + +net.Receive("ixWhitelistMenuApply", function(_, client) + if not PLUGIN:HasAccess(client) then + return + end + + local targetSteamID = net.ReadString() + local factionID = net.ReadUInt(16) + local podrID = net.ReadUInt(8) + local specID = net.ReadUInt(8) + local rankID = net.ReadUInt(8) + + local target = nil + for _, ply in ipairs(player.GetAll()) do + if ply:SteamID() == targetSteamID then + target = ply + break + end + end + + if not target or not target:GetCharacter() then + client:Notify("Игрок не найден.") + return + end + + local faction = ix.faction.indices[factionID] + if not faction then + client:Notify("Фракция не найдена.") + return + end + + if not faction.Podr[podrID] then + client:Notify("Подразделение не найдено.") + return + end + + if not faction.Spec[specID] then + client:Notify("Специализация не найдена.") + return + end + + local targetRanks = (faction.Podr[podrID] and faction.Podr[podrID].ranks) or faction.Ranks + if not (targetRanks and targetRanks[rankID]) then + client:Notify("Звание не найдено.") + return + end + + if faction.Spec[specID].podr ~= podrID then + client:Notify("Специализация не соответствует подразделению.") + return + end + + local char = target:GetCharacter() + char:SetFaction(factionID) + char:SetPodr(podrID) + char:SetSpec(specID) + char:SetRank(rankID) + + -- Респавн игрока для применения изменений + target:Spawn() + + client:Notify("Роль назначена: " .. target:Name()) + target:Notify("Вам назначена новая роль!") +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/writing/cl_hooks.lua b/garrysmod/gamemodes/militaryrp/plugins/writing/cl_hooks.lua new file mode 100644 index 0000000..e7f2fcc --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/writing/cl_hooks.lua @@ -0,0 +1,9 @@ + +netstream.Hook("ixViewPaper", function(itemID, text, bEditable) + bEditable = tobool(bEditable) + + local panel = vgui.Create("ixPaper") + panel:SetText(text) + panel:SetEditable(bEditable) + panel:SetItemID(itemID) +end) diff --git a/garrysmod/gamemodes/militaryrp/plugins/writing/derma/cl_paper.lua b/garrysmod/gamemodes/militaryrp/plugins/writing/derma/cl_paper.lua new file mode 100644 index 0000000..9a93ae0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/writing/derma/cl_paper.lua @@ -0,0 +1,85 @@ + +local PLUGIN = PLUGIN + +local PANEL = {} + +AccessorFunc(PANEL, "bEditable", "Editable", FORCE_BOOL) +AccessorFunc(PANEL, "itemID", "ItemID", FORCE_NUMBER) + +function PANEL:Init() + if (IsValid(PLUGIN.panel)) then + PLUGIN.panel:Remove() + end + + self:SetSize(256, 318) + self:Center() + self:SetBackgroundBlur(true) + self:SetDeleteOnClose(true) + self:SetTitle(L("paper")) + + self.close = self:Add("DButton") + self.close:Dock(BOTTOM) + self.close:DockMargin(0, 4, 0, 0) + self.close:SetText(L("close")) + self.close.DoClick = function() + if (self.bEditable) then + netstream.Start("ixWritingEdit", self.itemID, self.text:GetValue():sub(1, PLUGIN.maxLength)) + end + + self:Close() + end + + self.text = self:Add("DTextEntry") + self.text:SetMultiline(true) + self.text:SetEditable(false) + self.text:SetDisabled(true) + self.text:Dock(FILL) + + self:MakePopup() + + self.bEditable = false + PLUGIN.panel = self +end + +function PANEL:Think() + local text = self.text:GetValue() + + if (text:len() > PLUGIN.maxLength) then + local newText = text:sub(1, PLUGIN.maxLength) + + self.text:SetValue(newText) + self.text:SetCaretPos(newText:len()) + + surface.PlaySound("common/talk.wav") + end +end + +function PANEL:SetEditable(bValue) + bValue = tobool(bValue) + + if (bValue == self.bEditable) then + return + end + + if (bValue) then + self.close:SetText(L("save")) + self.text:SetEditable(true) + self.text:SetDisabled(false) + else + self.close:SetText(L("close")) + self.text:SetEditable(false) + self.text:SetDisabled(true) + end + + self.bEditable = bValue +end + +function PANEL:SetText(text) + self.text:SetValue(text) +end + +function PANEL:OnRemove() + PLUGIN.panel = nil +end + +vgui.Register("ixPaper", PANEL, "DFrame") diff --git a/garrysmod/gamemodes/militaryrp/plugins/writing/items/base/sh_writing.lua b/garrysmod/gamemodes/militaryrp/plugins/writing/items/base/sh_writing.lua new file mode 100644 index 0000000..18c112b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/writing/items/base/sh_writing.lua @@ -0,0 +1,4 @@ + +ITEM.name = "Writing Base" +ITEM.model = Model("models/props_c17/paper01.mdl") +ITEM.description = "Something that can be written on." diff --git a/garrysmod/gamemodes/militaryrp/plugins/writing/items/writing/sh_paper.lua b/garrysmod/gamemodes/militaryrp/plugins/writing/items/writing/sh_paper.lua new file mode 100644 index 0000000..f541403 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/writing/items/writing/sh_paper.lua @@ -0,0 +1,51 @@ + +local PLUGIN = PLUGIN + +ITEM.name = "Бумага" +ITEM.description = "Кусочек бумаги, %s." +ITEM.price = 2 +ITEM.model = Model("models/props_c17/paper01.mdl") +ITEM.width = 1 +ITEM.height = 1 +ITEM.classes = {CLASS_EOW} +ITEM.business = true +ITEM.bAllowMultiCharacterInteraction = true + +function ITEM:GetDescription() + return self:GetData("owner", 0) == 0 + and string.format(self.description, "он потрепанный и грязный.") + or string.format(self.description, "на нем написано.") +end + +function ITEM:SetText(text, character) + text = tostring(text):sub(1, PLUGIN.maxLength) + + self:SetData("text", text, false, false, true) + self:SetData("owner", character and character:GetID() or 0) +end + +ITEM.functions.View = { + OnRun = function(item) + netstream.Start(item.player, "ixViewPaper", item:GetID(), item:GetData("text", ""), 0) + return false + end, + + OnCanRun = function(item) + local owner = item:GetData("owner", 0) + + return owner != 0 + end +} + +ITEM.functions.Edit = { + OnRun = function(item) + netstream.Start(item.player, "ixViewPaper", item:GetID(), item:GetData("text", ""), 1) + return false + end, + + OnCanRun = function(item) + local owner = item:GetData("owner", 0) + + return owner == 0 or owner == item.player:GetCharacter():GetID() and item:GetData("text", "") == "" + end +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/writing/languages/sh_english.lua b/garrysmod/gamemodes/militaryrp/plugins/writing/languages/sh_english.lua new file mode 100644 index 0000000..7abbae0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/writing/languages/sh_english.lua @@ -0,0 +1,4 @@ + +LANGUAGE = { + paper = "Бумага" +} diff --git a/garrysmod/gamemodes/militaryrp/plugins/writing/sh_plugin.lua b/garrysmod/gamemodes/militaryrp/plugins/writing/sh_plugin.lua new file mode 100644 index 0000000..7f1faf7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/writing/sh_plugin.lua @@ -0,0 +1,8 @@ + +PLUGIN.name = "Writing" +PLUGIN.description = "Adds purchasable items which players can write/edit." +PLUGIN.author = "`impulse" +PLUGIN.maxLength = 512 + +ix.util.Include("sv_hooks.lua") +ix.util.Include("cl_hooks.lua") diff --git a/garrysmod/gamemodes/militaryrp/plugins/writing/sv_hooks.lua b/garrysmod/gamemodes/militaryrp/plugins/writing/sv_hooks.lua new file mode 100644 index 0000000..ac3facc --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/plugins/writing/sv_hooks.lua @@ -0,0 +1,18 @@ + +local PLUGIN = PLUGIN + +netstream.Hook("ixWritingEdit", function(client, itemID, text) + text = tostring(text):sub(1, PLUGIN.maxLength) + + local character = client:GetCharacter() + local item = ix.item.instances[itemID] + + -- we don't check for entity since data can be changed in the player's inventory + if (character and item and item.base == "base_writing") then + local owner = item:GetData("owner", 0) + + if ((owner == 0 or owner == character:GetID()) and text:len() > 0) then + item:SetText(text, character) + end + end +end) diff --git a/garrysmod/gamemodes/militaryrp/schema/cl_hooks.lua b/garrysmod/gamemodes/militaryrp/schema/cl_hooks.lua new file mode 100644 index 0000000..5b08e74 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/cl_hooks.lua @@ -0,0 +1,89 @@ +function We(x) + return x/1920*ScrW() +end + +function He(y) + return y/1080*ScrH() +end + +function Schema:BuildBusinessMenu(panel) + local bHasItems = false + + for k, _ in pairs(ix.item.list) do + if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), k) != false) then + bHasItems = true + + break + end + end + + return bHasItems +end + +function Schema:ScoreboardShow() + if (LocalPlayer():GetCharacter() && !hook.Run("ShouldSuppressMenu", LocalPlayer())) then + --vgui.Create("ixMenu") + end + + return "suppress" +end + +function Schema:SpawnMenuOpen() + if not LocalPlayer():IsAdmin() then + return false + end + + local ply = LocalPlayer() + local isSuperAdmin = ply:IsSuperAdmin() + + if ply:IsAdmin() and not isSuperAdmin then + timer.Simple(0, function() + local tabs = spawnmenu.GetCreationTabs() + local tabsToHide = { + ["#spawnmenu.category.weapons"] = true, + ["#spawnmenu.category.npcs"] = true, + ["#spawnmenu.category.entities"] = true, + ["#spawnmenu.category.postprocess"] = true, + ["#spawnmenu.category.saves"] = true, + ["#spawnmenu.category.dupes"] = true + } + + for name, tab in pairs(tabs) do + if tabsToHide[name] and IsValid(tab.Tab) then + tab.Tab:SetVisible(false) + end + end + end) + end +end + +-- Закрытие всех меню при смерти игрока +function Schema:PlayerDeath(client) + if client != LocalPlayer() then return end + + -- Закрываем F4 меню + local f4Plugin = ix.plugin.list["f4menu"] + if f4Plugin and IsValid(f4Plugin.frame) then + f4Plugin.frame:Remove() + f4Plugin.frame = nil + end + + -- Закрываем pause menu + local pausePlugin = ix.plugin.list["pause_menu"] + if pausePlugin and IsValid(pausePlugin.menu) then + pausePlugin.menu:Close() + end + + -- Закрываем scoreboard + local scoreboardPlugin = ix.plugin.list["scoreboard"] + if scoreboardPlugin and scoreboardPlugin.RemoveScoreboard then + scoreboardPlugin:RemoveScoreboard() + end + + -- Закрываем все DFrames (на всякий случай) + for _, panel in ipairs(vgui.GetWorldPanel():GetChildren()) do + if IsValid(panel) and panel:GetClassName() == "DFrame" then + panel:Close() + end + end +end diff --git a/garrysmod/gamemodes/militaryrp/schema/cl_schema.lua b/garrysmod/gamemodes/militaryrp/schema/cl_schema.lua new file mode 100644 index 0000000..4ba2805 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/cl_schema.lua @@ -0,0 +1 @@ +-- diff --git a/garrysmod/gamemodes/militaryrp/schema/derma/cl_avatar.lua b/garrysmod/gamemodes/militaryrp/schema/derma/cl_avatar.lua new file mode 100644 index 0000000..4a52128 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/derma/cl_avatar.lua @@ -0,0 +1,37 @@ +local PANEL = {} + +function PANEL:Init() + self:SetAvatarRender() +end + +local vector_002 = Vector(0, 0, 2) + +function PANEL:SetAvatarRender() + local entity = self.Entity + if IsValid(entity) then + local boneid = entity:LookupBone("ValveBiped.Bip01_Head1") or entity:LookupBone("ValveBiped.Bip01_Head") or entity:LookupBone("bip01_head") + if not boneid then + return + end + + local positionEye = entity:GetBonePosition(boneid) + positionEye:Add(vector_002) + self:SetLookAt(positionEye) + + local eyeposing = Vector(0, -250, 0) + + if boneid == 7 then + eyeposing = Vector(-360, 25, 0) + end + + local camposition = positionEye - eyeposing + self:SetCamPos(camposition) + self:SetFOV(5) + + entity:SetEyeTarget(positionEye - Vector(0, -250, 0)) + end +end + +function PANEL:LayoutEntity() end + +vgui.Register("ixModelAvatarPanel", PANEL, "ixModelPanel") diff --git a/garrysmod/gamemodes/militaryrp/schema/derma/cl_character.lua b/garrysmod/gamemodes/militaryrp/schema/derma/cl_character.lua new file mode 100644 index 0000000..f40c544 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/derma/cl_character.lua @@ -0,0 +1,498 @@ + + +-- Масштабирование для меню +local function GetScale() + local scale = ScrH() / 1080 + return math.Clamp(scale, 0.5, 2.0) +end +local function ScaleSize(val) + return math.max(1, math.Round(val * GetScale())) +end +local function ScalePos(val) + return math.Round(val * GetScale()) +end + +local function CreateMenuFonts() + surface.CreateFont("ixMenuButtonFont_Adaptive", { + font = "Roboto", + size = math.max(ScaleSize(18), 12), + weight = 500, + antialias = true + }) + surface.CreateFont("ixMenuButtonHugeFont_Adaptive", { + font = "Roboto", + size = math.max(ScaleSize(32), 20), + weight = 700, + antialias = true + }) +end +CreateMenuFonts() +hook.Add("OnScreenSizeChanged", "CharMenu_UpdateFonts", CreateMenuFonts) + +local CharMenu = {} + +function CharMenu:Init() + if IsValid(ix.gui.characterMenu) then + ix.gui.characterMenu:Remove() + end + + self:SetSize(ScrW(), ScrH()) + self:SetPos(0, 0) + + -- Переменные для совместимости + self.bClosing = false + self.currentAlpha = 255 + self.bMenuShouldClose = false + + -- Проверяем наличие персонажа + self:CheckCharacterAndLoad() + + ix.gui.characterMenu = self + ix.gui.menu = self -- Для совместимости + + self:MakePopup() + self:SetKeyboardInputEnabled(true) + self:SetMouseInputEnabled(true) +end + +function CharMenu:CheckCharacterAndLoad() + -- Ждем немного пока загрузятся данные персонажей + timer.Simple(0.5, function() + if not IsValid(self) then return end + + local hasCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter() + + if hasCharacter then + -- Если персонаж уже загружен, закрываем меню + print("Персонаж уже загружен, закрываем меню") + self:Close(true) + return + end + + local characters = ix.characters + if (#characters > 0) then + local firstChar = characters[1] + if not firstChar then + return + end + net.Start("ixCharacterChoose") + net.WriteUInt(firstChar, 32) + net.SendToServer() + else + -- Если персонажей нет, показываем выбор стороны + print("Персонажей нет, показываем выбор фракции") + self:ShowFactionSelection() + end + end) +end + +function CharMenu:ShowFactionSelection() + -- Очищаем предыдущие панели + if IsValid(self.currentPanel) then + self.currentPanel:Remove() + end + + -- Создаем панель выбора фракции + self.currentPanel = vgui.Create("DPanel", self) + self.currentPanel:SetSize(ScrW(), ScrH()) + self.currentPanel:SetPos(0, 0) + + -- Фон + local backgroundImage = vgui.Create("DImage", self.currentPanel) + backgroundImage:SetSize(ScrW(), ScrH()) + backgroundImage:SetPos(0, 0) + backgroundImage:SetImage("ft_ui/military/vnu/charcreate/bg.png") + + -- Верхняя панель + CreateMenuFonts() + local topBar = vgui.Create("DPanel", self.currentPanel) + topBar:SetSize(ScrW() + 2, ScaleSize(100)) + topBar:SetPos(-1, 0) + topBar.Paint = function(panel, w, h) + surface.SetDrawColor(Color(13, 13, 13)) + surface.DrawRect(0, 0, w, h) + end + + -- Логотип + local logo = vgui.Create("DImage", self.currentPanel) + logo:SetSize(ScaleSize(150), ScaleSize(53)) + logo:SetPos(ScrW()/2 - ScaleSize(75), ScalePos(23)) + logo:SetImage("ft_ui/military/vnu/charcreate/logo.png") + + -- Заголовок + local title = vgui.Create("DLabel", self.currentPanel) + title:SetText("ВЫБОР СТОРОНЫ") + title:SetFont("ixMenuButtonHugeFont_Adaptive") + title:SetTextColor(color_white) + title:SizeToContents() + title:SetPos(ScrW()/2 - title:GetWide()/2, ScalePos(187)) + + -- Панель ВСУ + local ukrainePanel = vgui.Create("DPanel", self.currentPanel) + ukrainePanel:SetSize(ScaleSize(425), ScaleSize(500)) + ukrainePanel:SetPos(ScrW()/2 - ScaleSize(425) - ScaleSize(25), ScalePos(310)) + ukrainePanel.Paint = function(panel, w, h) + surface.SetDrawColor(Color(0, 0, 0, 180)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + -- Панель России + local russiaPanel = vgui.Create("DPanel", self.currentPanel) + russiaPanel:SetSize(ScaleSize(425), ScaleSize(500)) + russiaPanel:SetPos(ScrW()/2 + ScaleSize(25), ScalePos(310)) + russiaPanel.Paint = function(panel, w, h) + surface.SetDrawColor(Color(0, 0, 0, 180)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + -- Изображения фракций + local ukraineImage = vgui.Create("DImage", self.currentPanel) + ukraineImage:SetSize(ScaleSize(200), ScaleSize(200)) + ukraineImage:SetPos(ScrW()/2 - ScaleSize(425) - ScaleSize(25) + ScaleSize(113), ScalePos(333)) + ukraineImage:SetImage("ft_ui/military/vnu/charcreate/ukraine.png") + + local russiaImage = vgui.Create("DImage", self.currentPanel) + russiaImage:SetSize(ScaleSize(295), ScaleSize(200)) + russiaImage:SetPos(ScrW()/2 + ScaleSize(25) + ScaleSize(65), ScalePos(333)) + russiaImage:SetImage("ft_ui/military/vnu/charcreate/russia.png") + + -- Кнопки выбора + local ukraineButton = vgui.Create("DButton", self.currentPanel) + ukraineButton:SetSize(ScaleSize(425), ScaleSize(75)) + ukraineButton:SetPos(ScrW()/2 - ScaleSize(425) - ScaleSize(25), ScalePos(818)) + ukraineButton:SetText("") + ukraineButton.Paint = function(panel, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(29, 29, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 3) + draw.SimpleText("УКРАИНА", "ixMenuButtonHugeFont_Adaptive", w/2, h/2, Color(255, 255, 255, 218), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if panel:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + ukraineButton.DoClick = function() + self:ShowCharacterCreation(FACTION_UKRAINE) + end + + local russiaButton = vgui.Create("DButton", self.currentPanel) + russiaButton:SetSize(ScaleSize(425), ScaleSize(75)) + russiaButton:SetPos(ScrW()/2 + ScaleSize(25), ScalePos(818)) + russiaButton:SetText("") + russiaButton.Paint = function(panel, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(29, 29, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 3) + draw.SimpleText("РОССИЯ", "ixMenuButtonHugeFont_Adaptive", w/2, h/2, Color(255, 255, 255, 218), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if panel:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + russiaButton.DoClick = function() + self:ShowCharacterCreation(FACTION_RUSSIAN) + end +end + +function CharMenu:ShowCharacterCreation(faction) + -- Очищаем предыдущие панели + if IsValid(self.currentPanel) then + self.currentPanel:Remove() + end + + self.currentPanel = vgui.Create("DPanel", self) + self.currentPanel:SetSize(ScrW(), ScrH()) + self.currentPanel:SetPos(0, 0) + + -- Фон + local backgroundImage = vgui.Create("DImage", self.currentPanel) + backgroundImage:SetSize(ScrW(), ScrH()) + backgroundImage:SetPos(0, 0) + backgroundImage:SetImage("ft_ui/military/vnu/charcreate/bg.png") + + -- Верхняя панель + CreateMenuFonts() + local topBar = vgui.Create("DPanel", self.currentPanel) + topBar:SetSize(ScrW() + 2, ScaleSize(100)) + topBar:SetPos(-1, 0) + topBar.Paint = function(panel, w, h) + surface.SetDrawColor(Color(13, 13, 13)) + surface.DrawRect(0, 0, w, h) + end + + -- Логотип + local logo = vgui.Create("DImage", self.currentPanel) + logo:SetSize(ScaleSize(150), ScaleSize(53)) + logo:SetPos(ScrW()/2 - ScaleSize(75), ScalePos(23)) + logo:SetImage("ft_ui/military/vnu/charcreate/logo.png") + + -- Основная панель + local mainPanel = vgui.Create("DPanel", self.currentPanel) + mainPanel:SetSize(ScaleSize(1500), ScaleSize(750)) + mainPanel:SetPos(ScrW()/2 - ScaleSize(750), ScalePos(199)) + mainPanel.Paint = function(panel, w, h) + surface.SetDrawColor(Color(13, 13, 13, 200)) + surface.DrawRect(0, 0, w, h) + end + + -- Заголовки + local title = vgui.Create("DLabel", self.currentPanel) + title:SetText("СОЗДАНИЕ ПЕРСОНАЖА") + title:SetFont("ixMenuButtonHugeFont_Adaptive") + title:SetTextColor(color_white) + title:SizeToContents() + title:SetPos(ScrW()/2 - title:GetWide()/2, ScalePos(224)) + + -- Разделители + local divider1 = vgui.Create("DPanel", self.currentPanel) + divider1:SetSize(ScaleSize(770), ScaleSize(2)) + divider1:SetPos(ScrW()/2 - ScaleSize(385), ScalePos(314)) + divider1.Paint = function(panel, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + end + + -- Поля ввода + local startX = ScrW()/2 - ScaleSize(420) + + -- Имя + local nameLabel = vgui.Create("DLabel", self.currentPanel) + nameLabel:SetText("Ваше имя") + nameLabel:SetFont("ixMenuButtonFont_Adaptive") + nameLabel:SetTextColor(color_white) + nameLabel:SizeToContents() + nameLabel:SetPos(startX, ScalePos(360)) + + local nameEntry = vgui.Create("DTextEntry", self.currentPanel) + nameEntry:SetSize(ScaleSize(265), ScaleSize(50)) + nameEntry:SetPos(startX, ScalePos(401)) + nameEntry:SetFont("ixMenuButtonFont_Adaptive") + nameEntry:SetTextColor(Color(255, 255, 255, 230)) + nameEntry:SetDrawBackground(false) + nameEntry.Paint = function(panel, w, h) + surface.SetDrawColor(Color(23, 23, 23)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 1) + panel:DrawTextEntryText(Color(255, 255, 255, 230), Color(30, 130, 255), Color(255, 255, 255, 230)) + end + + -- Фамилия + local lastNameLabel = vgui.Create("DLabel", self.currentPanel) + lastNameLabel:SetText("Ваша фамилия") + lastNameLabel:SetFont("ixMenuButtonFont_Adaptive") + lastNameLabel:SetTextColor(color_white) + lastNameLabel:SizeToContents() + lastNameLabel:SetPos(startX + ScaleSize(285), ScalePos(360)) + + local lastNameEntry = vgui.Create("DTextEntry", self.currentPanel) + lastNameEntry:SetSize(ScaleSize(265), ScaleSize(50)) + lastNameEntry:SetPos(startX + ScaleSize(285), ScalePos(401)) + lastNameEntry:SetFont("ixMenuButtonFont_Adaptive") + lastNameEntry:SetTextColor(Color(255, 255, 255, 230)) + lastNameEntry:SetDrawBackground(false) + lastNameEntry.Paint = nameEntry.Paint + + -- Позывной + local callSignLabel = vgui.Create("DLabel", self.currentPanel) + callSignLabel:SetText("Ваш позывной") + callSignLabel:SetFont("ixMenuButtonFont_Adaptive") + callSignLabel:SetTextColor(color_white) + callSignLabel:SizeToContents() + callSignLabel:SetPos(startX + ScaleSize(570), ScalePos(360)) + + local callSignEntry = vgui.Create("DTextEntry", self.currentPanel) + callSignEntry:SetSize(ScaleSize(265), ScaleSize(50)) + callSignEntry:SetPos(startX + ScaleSize(570), ScalePos(401)) + callSignEntry:SetFont("ixMenuButtonFont_Adaptive") + callSignEntry:SetTextColor(Color(255, 255, 255, 230)) + callSignEntry:SetDrawBackground(false) + callSignEntry.Paint = nameEntry.Paint + + -- Кнопка создания + local createButton = vgui.Create("DButton", self.currentPanel) + createButton:SetSize(ScaleSize(300), ScaleSize(80)) + createButton:SetPos(ScrW()/2 - ScaleSize(150), ScrH() - ScaleSize(120)) + createButton:SetText("") + createButton.Paint = function(panel, w, h) + surface.SetDrawColor(Color(1, 67, 29)) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(Color(29, 29, 29)) + surface.DrawOutlinedRect(0, 0, w, h, 3) + draw.SimpleText("СОЗДАТЬ", "ixMenuButtonHugeFont_Adaptive", w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if panel:IsHovered() then + surface.SetDrawColor(Color(255, 255, 255, 30)) + surface.DrawRect(0, 0, w, h) + end + end + createButton.DoClick = function() + local name = nameEntry:GetText() + local lastName = lastNameEntry:GetText() + local callSign = callSignEntry:GetText() + if name == "" or lastName == "" or callSign == "" then + print("Ошибка: Заполните все поля!") + return + end + local fullName = name .. " " .. lastName .. " '" .. callSign .. "'" + print("Создание персонажа: " .. fullName) + self:CreateCharacter(faction, fullName) + end + self.selectedFaction = faction +end + +function CharMenu:CreateCharacter(faction, name) + if self.awaitingResponse then + print("Уже ожидаем ответ от сервера...") + return + end + + -- Подготавливаем данные для создания персонажа + local payload = { + name = name, + faction = faction, + model = "models/player/group01/male_04.mdl", + description = "Солдат" + } + + print("Отправка запроса на создание персонажа...") + PrintTable(payload) + + -- Отправляем запрос на создание персонажа + net.Start("ixCharacterCreate") + net.WriteUInt(table.Count(payload), 8) + + for k, v in pairs(payload) do + net.WriteString(k) + net.WriteType(v) + end + + net.SendToServer() + + -- Ждем ответа от сервера + self.awaitingResponse = true + + -- Таймаут на случай проблем с соединением + timer.Create("CharCreateTimeout", 10, 1, function() + if IsValid(ix.gui.characterMenu) and ix.gui.characterMenu.awaitingResponse then + ix.gui.characterMenu.awaitingResponse = false + Derma_Message("Таймаут создания персонажа! Проверьте соединение.", "Ошибка", "OK") + end + end) +end + +-- Методы для совместимости с Helix +function CharMenu:IsClosing() + return self.bClosing or false +end + +function CharMenu:GetAlpha() + return self.currentAlpha or 255 +end + +function CharMenu:GetCharacterOverview() + -- Заглушка для совместимости + return nil +end + +function CharMenu:Close(bFromMenu) + self.bClosing = true + if ix.gui.loading then + ix.gui.loading:Remove() + end + + self:Remove() +end + +function CharMenu:Paint(w, h) + -- Черный фон + surface.SetDrawColor(Color(0, 0, 0, 255)) + surface.DrawRect(0, 0, w, h) +end + +vgui.Register("ixCharMenu", CharMenu, "EditablePanel") + +-- Исправляем CalcView hook +hook.Add("CalcView", "MilitaryRPMenuFix", function(ply, origin, angles, fov) + local menu = ix.gui.characterMenu or ix.gui.menu + if IsValid(menu) and not menu:IsClosing() then + -- Возвращаем стандартный вид, чтобы избежать ошибок + return { + origin = origin, + angles = angles, + fov = fov, + drawviewer = false + } + end +end) + +-- Глобальные сетевые обработчики (устанавливаются один раз при загрузке файла) +net.Receive("ixCharacterLoad", function() + local characters = net.ReadUInt(8) + local charList = {} + + for i = 1, characters do + charList[i] = net.ReadUInt(32) + end + + ix.characters = charList + print("Получен список персонажей: ", table.concat(charList, ", ")) + + -- Если меню открыто, обновляем его + if IsValid(ix.gui.characterMenu) then + ix.gui.characterMenu:CheckCharacterAndLoad() + end +end) + +net.Receive("ixCharacterAuthed", function() + timer.Remove("CharCreateTimeout") + + local id = net.ReadUInt(32) + local characters = net.ReadUInt(8) + local charList = {} + + for i = 1, characters do + charList[i] = net.ReadUInt(32) + end + + ix.characters = charList + + print("Персонаж успешно создан! ID: " .. id) + print("Теперь доступно персонажей: " .. characters) + + -- Автоматически выбираем созданного персонажа + if id then + print("Автовыбор персонажа ID: " .. id) + net.Start("ixCharacterChoose") + net.WriteUInt(id, 32) + net.SendToServer() + end + + -- Закрываем меню если оно открыто + if IsValid(ix.gui.characterMenu) then + ix.gui.characterMenu.awaitingResponse = false + ix.gui.characterMenu:Close(true) + end +end) + +net.Receive("ixCharacterAuthFailed", function() + timer.Remove("CharCreateTimeout") + + local fault = net.ReadString() + local args = net.ReadTable() + + print("Ошибка создания персонажа: " .. fault) + PrintTable(args) + + -- Показываем ошибку пользователю + if IsValid(ix.gui.characterMenu) then + ix.gui.characterMenu.awaitingResponse = false + Derma_Message("Ошибка создания персонажа: " .. fault, "Ошибка", "OK") + end +end) + diff --git a/garrysmod/gamemodes/militaryrp/schema/derma/cl_charcreate.lua b/garrysmod/gamemodes/militaryrp/schema/derma/cl_charcreate.lua new file mode 100644 index 0000000..4ba2805 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/derma/cl_charcreate.lua @@ -0,0 +1 @@ +-- diff --git a/garrysmod/gamemodes/militaryrp/schema/derma/cl_charload.lua b/garrysmod/gamemodes/militaryrp/schema/derma/cl_charload.lua new file mode 100644 index 0000000..4ba2805 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/derma/cl_charload.lua @@ -0,0 +1 @@ +-- diff --git a/garrysmod/gamemodes/militaryrp/schema/factions/sh_admin.lua b/garrysmod/gamemodes/militaryrp/schema/factions/sh_admin.lua new file mode 100644 index 0000000..682f61d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/factions/sh_admin.lua @@ -0,0 +1,30 @@ +FACTION.name = "Администратор" +FACTION.description = "Фракция администрации сервера" +FACTION.isDefault = true +FACTION.color = Color(150, 150, 150) +FACTION.models = { + "models/player/group01/male_01.mdl" +} + +FACTION.Ranks = { + [1] = {"Администратор", nil, nil} +} + +FACTION.Podr = { + [1] = { + name = "Администрация", + preset = {}, + model = "models/player/group01/male_01.mdl", + spec_def = 1 + } +} + +FACTION.Spec = { + [1] = { + name = "Администратор", + weapons = {}, + podr = 1 + } +} + +FACTION_ADMIN = FACTION.index diff --git a/garrysmod/gamemodes/militaryrp/schema/factions/sh_russian.lua b/garrysmod/gamemodes/militaryrp/schema/factions/sh_russian.lua new file mode 100644 index 0000000..7f6d9a4 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/factions/sh_russian.lua @@ -0,0 +1,412 @@ +FACTION.name = "ВС РФ" + + + +FACTION.description = "" + + + +FACTION.isDefault = true + + + +FACTION.color = Color(156, 0, 0) + + + +FACTION.models = { + + + + "models/player/group01/male_04.mdl" + + + +} + + + +FACTION.Ranks = { + [1] = {"Новобранец", nil, nil}, + [2] = {"Рядовой", nil, nil}, + [3] = {"Ефрейтор", nil, nil}, + [4] = {"Младший Сержант", nil, nil}, + [5] = {"Сержант", nil, nil}, + [6] = {"Старший Сержант", nil, nil}, + [7] = {"Старшина", nil, nil}, + [8] = {"Прапорщик", nil, nil}, + [9] = {"Старший Прапорщик", nil, nil}, + [10] = {"Младший лейтенант", nil, nil}, + [11] = {"Лейтенант", nil, nil}, + [12] = {"Старший лейтенант", nil, nil}, + [13] = {"Капитан", nil, nil}, + [14] = {"Майор", nil, nil}, + [15] = {"Подполковник", nil, nil}, + [16] = {"Полковник", nil, nil} +} + + + +FACTION.Podr = { +[1] = { +["name"] = "Новоприбывшие", +["preset"] = {}, +["spec_def"] = 1, +["model"] = "models/player/group01/male_04.mdl" +}, +[2] = { +["name"] = "1-й ДШБ", +["preset"] = { +[7] = "tacrp_knife", +[1] = "tacrp_nade_frag", +[2] = "tacrp_nade_smoke", +[3] = "tacrp_pa_makarov", +[4] = "tacrp_nade_flashbang", +[5] = "engineertool", +[6] = "parachute_swep" +}, +["spec_def"] = 2, +["model"] = "models/arma3/russia/soldier.mdl" +}, +[3] = { +["name"] = "1-й УТБ", +["preset"] = { +[3] = "weapon_lvsrepair", +[1] = "tacrp_ak_ak74u", +[2] = "tacrp_pa_makarov", +[4] = "tacrp_knife" +}, +["spec_def"] = 12, +["model"] = "models/welding/rf/soldier_rf.mdl" +}, +[4] = { +["name"] = "6-й ОУЭ", +["preset"] = { +[3] = "weapon_lvsrepair", +[1] = "tacrp_sd_bizon", +[2] = "tacrp_pa_makarov", +[4] = "tacrp_knife" +}, +["spec_def"] = 14, +["model"] = "models/arma3/russia/soldier.mdl" +}, +[5] = { +["name"] = "78-я орСпН", +["preset"] = { +[7] = "tacrp_knife", +[1] = "tacrp_nade_frag", +[2] = "tacrp_nade_smoke", +[3] = "tacrp_pa_makarov", +[4] = "tacrp_nade_flashbang", +[5] = "weapon_r_handcuffs", +[6] = "parachute_swep" +}, +["spec_def"] = 15, +["model"] = "models/nb/wizard/SSO_RU_Syria/sso_pm.mdl" +}, +[6] = { +["name"] = "ФСБ «Вымпел»", +["preset"] = { +[7] = "weapon_lvsspikestrip", +[1] = "tacrp_nade_frag", +[2] = "tacrp_nade_smoke", +[3] = "tacrp_pa_makarov", +[4] = "tacrp_nade_flashbang", +[5] = "tacrp_knife", +[6] = "weapon_r_handcuffs" +}, +["spec_def"] = 19, +["model"] = "models/knyaje pack/fsb_rosn/fsb_rosn.mdl" +}, +[8] = { +["name"] = "Штаб РФ", +["preset"] = { +[3] = "tacrp_knife", +[1] = "tacrp_ak_ak74u", +[2] = "tacrp_pa_makarov" +}, +["spec_def"] = 1, +["model"] = "models/Knyaje Pack/generals/General_VDV.mdl", +["ranks"] = { + [10] = {"Младший лейтенант", nil, nil}, + [11] = {"Лейтенант", nil, nil}, + [12] = {"Старший лейтенант", nil, nil}, + [13] = {"Капитан", nil, nil}, + [14] = {"Майор", nil, nil}, + [15] = {"Подполковник", nil, nil}, + [16] = {"Полковник", nil, nil}, + [17] = {"Генерал-майор", nil, nil}, + [18] = {"Генерал-лейтенант", nil, nil}, + [19] = {"Генерал-полковник", nil, nil}, + [20] = {"Генерал армии", nil, nil} +} +} +} + + + + + +FACTION.Spec = { +[31] = { +["weapons"] = { +[3] = "swep_drone_grenade", +[1] = "tacrp_ak_ak74u", +[2] = "weapon_rope_knife", +[4] = "bandage" +}, +["podr"] = 2, +["name"] = "Оператор БПЛА" +}, +[1] = { +["weapons"] = {}, +["podr"] = 1, +["name"] = "Новоприбывший контрактник" +}, +[2] = { +["weapons"] = { +[1] = "v92_bf2_ammokit", +[2] = "tacrp_nade_frag", +[3] = "tacrp_ak_ak74", +[4] = "parachute_swep", +[5] = "bandage" +}, +["podr"] = 2, +["name"] = "Стрелок" +}, +[3] = { +["weapons"] = { +[3] = "tacrp_nade_frag", +[1] = "parachute_swep", +[2] = "tacrp_sd_pkm", +[4] = "bandage" +}, +["podr"] = 2, +["name"] = "Пулеметчик" +}, +[4] = { +["weapons"] = { +[1] = "tacrp_io_val", +[2] = "weapon_rope_knife", +[3] = "tacrp_ak_svd", +[4] = "tacrp_nade_frag", +[5] = "parachute_swep", +[6] = "bandage" +}, +["podr"] = 2, +["name"] = "Снайпер" +}, +[5] = { +["weapons"] = { +[7] = "bandage", +[1] = "parachute_swep", +[2] = "tacrp_nade_frag", +[3] = "tacrp_ak_ak74u", +[4] = "v92_bf2_medikit", +[5] = "admin_defib", +[6] = "special_bandage" +}, +["podr"] = 2, +["name"] = "Санинструктор" +}, +[6] = { +["weapons"] = { +[1] = "weapon_sw_9k38", +[2] = "tacrp_ak_ak74u", +[3] = "weapon_sw_rpg28", +[4] = "weapon_sw_rpg26", +[5] = "parachute_swep", +[6] = "bandage" +}, +["podr"] = 2, +["name"] = "Гранатометчик" +}, +[7] = { +["weapons"] = { +[1] = "sw_laser_pointer_v3", +[2] = "tacrp_nade_frag", +[3] = "tacrp_ak_ak74u", +[4] = "parachute_swep", +[5] = "bandage" +}, +["podr"] = 2, +["name"] = "Артиллерист" +}, +[8] = { +["weapons"] = { +[3] = "tacrp_pa_makarov", +[1] = "weapon_lvsrepair", +[2] = "tacrp_ak_ak74u", +[4] = "bandage" +}, +["podr"] = 3, +["name"] = "Командир машины" +}, +[9] = { +["weapons"] = { +[3] = "tacrp_pa_makarov", +[1] = "weapon_lvsrepair", +[2] = "tacrp_ak_ak74u", +[4] = "bandage" +}, +["podr"] = 3, +["name"] = "Наводчик-оператор" +}, +[10] = { +["weapons"] = { +[3] = "tacrp_pa_makarov", +[1] = "weapon_lvsrepair", +[2] = "tacrp_ak_ak74u", +[4] = "bandage" +}, +["podr"] = 3, +["name"] = "Механик-водитель" +}, +[11] = { +["weapons"] = { +[3] = "parachute_swep", +[1] = "tacrp_pa_makarov", +[2] = "tacrp_ak_ak74u" +}, +["podr"] = 4, +["name"] = "Пилот" +}, +[12] = { +["weapons"] = { +[3] = "parachute_swep", +[1] = "tacrp_pa_makarov", +[2] = "tacrp_ak_ak74u" +}, +["podr"] = 4, +["name"] = "Помощник пилота" +}, +[13] = { +["weapons"] = { +[1] = "weapon_sw_9k38", +[2] = "tacrp_io_scarh", +[3] = "weapon_sw_rpg26", +[4] = "engineertoolmines", +[5] = "tacrp_nade_frag", +[6] = "bandage" +}, +["podr"] = 5, +["name"] = "Штурмовик" +}, +[14] = { +["weapons"] = { +[3] = "tacrp_nade_frag", +[1] = "weapon_rope_knife", +[2] = "tacrp_sd_pkm", +[4] = "bandage" +}, +["podr"] = 5, +["name"] = "Пулеметчик" +}, +[15] = { +["weapons"] = { +[3] = "weapon_rope_knife", +[1] = "tacrp_nade_frag", +[2] = "tacrp_io_trg42", +[4] = "bandage" +}, +["podr"] = 5, +["name"] = "Снайпер" +}, +[16] = { +["weapons"] = { +[7] = "special_bandage", +[1] = "tacrp_nade_frag", +[2] = "tacrp_io_rpk", +[3] = "v92_bf2_medikit", +[4] = "weapon_rope_knife", +[5] = "admin_defib", +[6] = "bandage" +}, +["podr"] = 5, +["name"] = "Санинструктор" +}, +[18] = { +["weapons"] = { +[3] = "bandage", +[1] = "tacrp_io_val", +[2] = "tacrp_nade_frag" +}, +["podr"] = 6, +["name"] = "Оперативник" +}, +[20] = { +["weapons"] = { +[1] = "tacrp_nade_frag", +[2] = "tacrp_io_rpk", +[3] = "bandage", +[4] = "admin_defib", +[5] = "special_bandage" +}, +["podr"] = 6, +["name"] = "Санинструктор" +}, +[21] = { +["weapons"] = { +[3] = "tacrp_nade_frag", +[1] = "tacrp_ak_aek971", +[2] = "weapon_r_handcuffs" +}, +["podr"] = 6, +["name"] = "Директор ФСБ" +}, +[26] = { +["weapons"] = { +[7] = "special_bandage", +[1] = "engineertoolmines", +[2] = "weapon_rope_knife", +[3] = "tacrp_io_val", +[4] = "v92_bf2_medikit", +[5] = "tacrp_nade_frag", +[6] = "bandage" +}, +["podr"] = 8, +["name"] = "Сотрудник" +}, +[27] = { +["weapons"] = { +[1] = "tacrp_io_val", +[2] = "weapon_rope_knife", +[3] = "weapon_r_handcuffs", +[4] = "tacrp_knife", +[5] = "tacrp_nade_frag", +[6] = "bandage" +}, +["podr"] = 6, +["name"] = "Снайпер" +}, +[28] = { +["weapons"] = { +[1] = "tacrp_io_val", +[2] = "weapon_rope_knife", +[3] = "engineertoolmines", +[4] = "tacrp_nade_frag", +[5] = "v92_bf2_medikit" +}, +["podr"] = 5, +["name"] = "Стрелок" +}, +[30] = { +["weapons"] = { +[1] = "weapon_sw_rpg26", +[2] = "weapon_sw_9k38", +[3] = "tacrp_io_val", +[4] = "tacrp_nade_frag", +[5] = "bandage" +}, +["podr"] = 6, +["name"] = "Гранатометчик" +} +} + + + + + + + +FACTION_RUSSIAN = FACTION.index diff --git a/garrysmod/gamemodes/militaryrp/schema/factions/sh_ukraine.lua b/garrysmod/gamemodes/militaryrp/schema/factions/sh_ukraine.lua new file mode 100644 index 0000000..e7f3b8c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/factions/sh_ukraine.lua @@ -0,0 +1,647 @@ +FACTION.name = "ВСУ" + + + + + + + +FACTION.description = "" + + + + + + + +FACTION.isDefault = true + + + + + + + +FACTION.color = Color(255, 196, 0) + + + + + + + +FACTION.models = { + + + + + + + + "models/player/group01/male_04.mdl" + + + + + + + +} + + + + + + + + + + + + + + + +FACTION.Ranks = { + + + + + + + + [1] = {"Новобранец", nil, nil}, + + + + + + + + [2] = {"Солдат", nil, nil}, + + + + + + + + [3] = {"Старший солдат", nil, nil}, + + + + + + + + [4] = {"Младший сержант", nil, nil}, + + + + + + + + [5] = {"Сержант", nil, nil}, + + + + + + + + [6] = {"Старший сержант", nil, nil}, + + + + + + + + [7] = {"Главный сержант", nil, nil}, + + + + + + + + [8] = {"Штаб-сержант", nil, nil}, + + + + + + + + [9] = {"Мастер-сержант", nil, nil}, + + + + + + + + [10] = {"Младший лейтенант", nil, nil}, + + + + + + + + [11] = {"Лейтенант", nil, nil}, + + + + + + + + [12] = {"Старший лейтенант", nil, nil}, + + + + + + + + [13] = {"Капитан", nil, nil}, + + + + + + + + [14] = {"Майор", nil, nil}, + + + + + + + + [15] = {"Подполковник", nil, nil}, + + + + + + + + [16] = {"Полковник", nil, nil}, + + + + + + + +} + + + + + + + + + + + + + + + +FACTION.Podr = { +[1] = { +["model"] = "models/player/group01/male_04.mdl", +["preset"] = { +[1] = "" +}, +["spec_def"] = 1, +["name"] = "Новоприбывшие" +}, +[2] = { +["model"] = "models/ft/soldier vsu.mdl", +["preset"] = { +[3] = "tacrp_pa_makarov", +[1] = "tacrp_nade_frag", +[2] = "tacrp_nade_smoke", +[4] = "tacrp_nade_flashbang" +}, +["spec_def"] = 2, +["name"] = "63 ОМБр" +}, +[3] = { +["model"] = "models/ft/soldier vsu.mdl", +["preset"] = { +[3] = "weapon_lvsrepair", +[1] = "tacrp_ak_ak74u", +[2] = "tacrp_pa_makarov" +}, +["spec_def"] = 11, +["name"] = "1 ОТБр" +}, +[4] = { +["model"] = "models/bibas/uk_pilot/pilot.mdl", +["preset"] = { +[3] = "weapon_lvsrepair", +[1] = "tacrp_mp5", +[2] = "tacrp_pa_makarov" +}, +["spec_def"] = 13, +["name"] = "40-я БРТА" +}, +[5] = { +["model"] = "models/sbu/securityserviceukraine.mdl", +["preset"] = { +[3] = "tacrp_pa_makarov", +[1] = "tacrp_nade_frag", +[2] = "tacrp_nade_smoke", +[4] = "tacrp_nade_flashbang" +}, +["spec_def"] = 17, +["name"] = "СБУ" +}, +[6] = { +["name"] = "ГБР Атлант", +["model"] = "models/lucie/sfod/sfod_operator_mask.mdl", +["spec_def"] = 17, +["preset"] = { +[1] = "tacrp_nade_frag", +[2] = "tacrp_nade_smoke", +[3] = "tacrp_pa_makarov", +[4] = "tacrp_nade_flashbang", +[5] = "ix_handcuffs", +[6] = "weapon_lvsspikestrip" +} +}, +[7] = { +["model"] = "models/player/male_gk_swe.mdl", +["preset"] = {}, +["spec_def"] = 21, +["name"] = "Легион «Ragnarök»" +}, +[8] = { +["model"] = "models/ftmodel/murom_soldier/sso_soldier.mdl", +["preset"] = {}, +["spec_def"] = 25, +["name"] = "ДРГ «Тишина»" +}, +[9] = { +["model"] = "models/kalinouskaha/operation/operation_kalinouskaha.mdl", +["preset"] = {}, +["spec_def"] = "", +["name"] = "ГУР «Kraken»" +} +} + + + + + + + + + + + + + + + +FACTION.Spec = { +[1] = { +["weapons"] = {}, +["name"] = "Новоприбывший контрактник", +["podr"] = 1 +}, +[2] = { +["weapons"] = { +[3] = "tacrp_ak_ak74", +[1] = "parachute_swep", +[2] = "tacrp_ex_m4a1", +[4] = "bandage" +}, +["name"] = "Стрелок", +["podr"] = 2 +}, +[3] = { +["weapons"] = { +[3] = "bandage", +[1] = "tacrp_sd_pkm", +[2] = "parachute_swep" +}, +["name"] = "Пулеметчик", +["podr"] = 2 +}, +[4] = { +["weapons"] = { +[3] = "bandage", +[1] = "tacrp_spr", +[2] = "parachute_swep" +}, +["name"] = "Снайпер", +["podr"] = 2 +}, +[5] = { +["weapons"] = { +[3] = "bandage", +[1] = "tacrp_ak_ak74u", +[2] = "parachute_swep", +[4] = "special_bandage" +}, +["name"] = "Санитар", +["podr"] = 2 +}, +[6] = { +["weapons"] = { +[1] = "weapon_sw_fim92", +[2] = "tacrp_mp5", +[3] = "weapon_sw_panzerfaust3", +[4] = "weapon_sw_at4", +[5] = "parachute_swep", +[6] = "bandage" +}, +["name"] = "Гранатометчик", +["podr"] = 2 +}, +[7] = { +["weapons"] = { +[3] = "sw_laser_pointer_v3", +[1] = "parachute_swep", +[2] = "tacrp_ak_ak74u", +[4] = "bandage" +}, +["name"] = "Артиллерист", +["podr"] = 2 +}, +[8] = { +["weapons"] = { +[3] = "weapon_lvsrepair", +[1] = "tacrp_ak_ak74u", +[2] = "tacrp_pa_makarov" +}, +["name"] = "Командир машины", +["podr"] = 3 +}, +[9] = { +["weapons"] = { +[3] = "weapon_lvsrepair", +[1] = "tacrp_ak_ak74u", +[2] = "tacrp_pa_makarov" +}, +["name"] = "Оператор-наводчик", +["podr"] = 3 +}, +[10] = { +["weapons"] = { +[3] = "weapon_lvsrepair", +[1] = "tacrp_ak_ak74u", +[2] = "tacrp_pa_makarov" +}, +["name"] = "Механик-водитель", +["podr"] = 3 +}, +[11] = { +["weapons"] = { +[3] = "tacrp_pa_makarov", +[1] = "parachute_swep", +[2] = "tacrp_ak_ak74u" +}, +["name"] = "Пилот", +["podr"] = 4 +}, +[12] = { +["weapons"] = { +[3] = "tacrp_pa_makarov", +[1] = "parachute_swep", +[2] = "tacrp_ak_ak74u" +}, +["name"] = "Помощник пилота", +["podr"] = 4 +}, +[13] = { +["weapons"] = { +[1] = "tacrp_ak_an94", +[2] = "bandage" +}, +["name"] = "Стрелок", +["podr"] = 5 +}, +[14] = { +["weapons"] = { +[1] = "tacrp_sd_pkm", +[2] = "bandage" +}, +["name"] = "Пулеметчик", +["podr"] = 5 +}, +[15] = { +["weapons"] = { +[1] = "tacrp_io_vss", +[2] = "bandage" +}, +["name"] = "Снайпер", +["podr"] = 5 +}, +[16] = { +["weapons"] = { +[3] = "special_bandage", +[1] = "tacrp_ak_an94", +[2] = "bandage" +}, +["name"] = "Медик", +["podr"] = 5 +}, +[17] = { +["weapons"] = { +[3] = "bandage", +[1] = "tacrp_ak_an94", +[2] = "weapon_sw_rpg28" +}, +["name"] = "Гранатометчик", +["podr"] = 5 +}, +[18] = { +["weapons"] = { +[1] = "tacrp_m4", +[2] = "bandage" +}, +["name"] = "Оперативник", +["podr"] = 6 +}, +[19] = { +["weapons"] = { +[3] = "weapon_sw_at4", +[1] = "weapon_sw_fim92", +[2] = "tacrp_m4", +[4] = "bandage" +}, +["name"] = "Гранатометчик", +["podr"] = 6 +}, +[20] = { +["weapons"] = { +[3] = "admin_defib", +[1] = "tacrp_m4", +[2] = "bandage", +[4] = "special_bandage" +}, +["name"] = "Санинструктор", +["podr"] = 6 +}, +[21] = { +["weapons"] = { +[1] = "tacrp_nade_frag", +[2] = "tacrp_spr", +[3] = "tacrp_mp5", +[4] = "bandage", +[5] = "special_bandage" +}, +["name"] = "Феланг", +["podr"] = 7 +}, +[22] = { +["weapons"] = { +[3] = "bandage", +[1] = "tacrp_mg4", +[2] = "tacrp_nade_frag", +[4] = "special_bandage" +}, +["name"] = "Берсерк", +["podr"] = 7 +}, +[23] = { +["weapons"] = { +[3] = "tacrp_nade_frag", +[1] = "weapon_sw_fim92", +[2] = "tacrp_ex_m4a1" +}, +["name"] = "Дёрнг", +["podr"] = 7 +}, +[24] = { +["weapons"] = { +[3] = "weapon_sw_at4", +[1] = "tacrp_mp7", +[2] = "tacrp_nade_frag" +}, +["name"] = "Хольд", +["podr"] = 7 +}, +[25] = { +["weapons"] = { +[1] = "tacrp_m4", +[2] = "bandage" +}, +["name"] = "Оперативник", +["podr"] = 8 +}, +[26] = { +["weapons"] = { +[3] = "bandage", +[1] = "weapon_rope_knife", +[2] = "tacrp_spr" +}, +["name"] = "Снайпер", +["podr"] = 8 +}, +[27] = { +["weapons"] = { +[3] = "special_bandage", +[1] = "tacrp_mp5", +[2] = "bandage" +}, +["name"] = "Медик", +["podr"] = 8 +}, +[28] = { +["weapons"] = { +[3] = "weapon_rope_knife", +[1] = "tacrp_ak_ak74u", +[2] = "swep_drone_grenade" +}, +["name"] = "Оператор БПЛА", +["podr"] = 2 +}, +[29] = { +["weapons"] = { +[1] = "tacrp_sd_aac_hb", +[2] = "weapon_cuff_elastic", +[3] = "tacrp_pa_makarov", +[4] = "parachute_swep", +[5] = "tacrp_knife", +[6] = "bandage" +}, +["name"] = "Штурмовик", +["podr"] = 9 +}, +[30] = { +["weapons"] = { +[7] = "bandage", +[1] = "tacrp_as50", +[2] = "weapon_cuff_elastic", +[3] = "tacrp_nade_frag", +[4] = "weapon_rope_knife", +[5] = "parachute_swep", +[6] = "tacrp_knife" +}, +["name"] = "Марксман", +["podr"] = 9 +}, +[31] = { +["weapons"] = { +[7] = "admin_defib", +[1] = "tacrp_sd_aac_hb", +[2] = "tacrp_pa_makarov", +[3] = "weapon_cuff_elastic", +[4] = "tacrp_nade_frag", +[5] = "parachute_swep", +[6] = "special_bandage", +[8] = "bandage" +}, +["name"] = "Санінструктор ", +["podr"] = 9 +}, +[32] = { +["weapons"] = { +[1] = "tacrp_sd_pkm", +[2] = "tacrp_pa_makarov", +[3] = "tacrp_nade_frag", +[4] = "weapon_cuff_elastic", +[5] = "parachute_swep", +[6] = "bandage" +}, +["name"] = "Кулiметчик", +["podr"] = 9 +}, +[33] = { +["weapons"] = { +[3] = "weapon_sw_rpg26", +[1] = "tacrp_io_val", +[2] = "weapon_cuff_elastic", +[4] = "bandage" +}, +["name"] = 998, +["podr"] = 9 +} +} + + + + + + + + + + + + + + + +FACTION_UKRAINE = FACTION.index diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_ak74_poly.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_ak74_poly.lua new file mode 100644 index 0000000..1faffc7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_ak74_poly.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "AK-74 полимерная обвеска" +ITEM.description = "Полимерная обвеска для АК-74. Снижает вес оружия и улучшает эргономику." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.attachmentType = "Accessories" +ITEM.tacRPAttachmentID = "acc_ak74_poly" +ITEM.tacRPSlot = "acc" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK", "Galil"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_bipod.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_bipod.lua new file mode 100644 index 0000000..48ec497 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_bipod.lua @@ -0,0 +1,11 @@ +ITEM.name = "Сошки" +ITEM.description = "Сошки для оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 400 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "grip" +ITEM.tacRPSlot = "grip" +ITEM.tacRPAttachmentID = "acc_bipod" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_brace.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_brace.lua new file mode 100644 index 0000000..cbd07b6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_brace.lua @@ -0,0 +1,11 @@ +ITEM.name = "Пистолетный приклад-упор" +ITEM.description = "Приклад для пистолета." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 450 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "accessory" +ITEM.tacRPSlot = "accessory" +ITEM.tacRPAttachmentID = "acc_brace" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_cheekrest.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_cheekrest.lua new file mode 100644 index 0000000..8df78c7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_cheekrest.lua @@ -0,0 +1,11 @@ +ITEM.name = "Щёчный упор" +ITEM.description = "Щёчный упор для оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 350 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "accessory" +ITEM.tacRPSlot = "accessory" +ITEM.tacRPAttachmentID = "acc_cheekrest" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_conceal.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_conceal.lua new file mode 100644 index 0000000..56c5f33 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_conceal.lua @@ -0,0 +1,11 @@ +ITEM.name = "Скрытное ношение оружия" +ITEM.description = "Скрытое ношение оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 300 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "accessory" +ITEM.tacRPSlot = "accessory" +ITEM.tacRPAttachmentID = "acc_conceal" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_ergo.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_ergo.lua new file mode 100644 index 0000000..eaef5a9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_ergo.lua @@ -0,0 +1,11 @@ +ITEM.name = "Двойные эргономичные рукоятки" +ITEM.description = "Двойные эргономичные рукоятки для оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 550 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "grip" +ITEM.tacRPSlot = "grip" +ITEM.tacRPAttachmentID = "acc_dual_ergo" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_quickdraw.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_quickdraw.lua new file mode 100644 index 0000000..f7c23d0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_quickdraw.lua @@ -0,0 +1,11 @@ +ITEM.name = "Быстрое доставание оружия." +ITEM.description = "" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 500 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "grip" +ITEM.tacRPSlot = "grip" +ITEM.tacRPAttachmentID = "acc_dual_quickdraw" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_skel.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_skel.lua new file mode 100644 index 0000000..cb2b6f7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_dual_skel.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Двойная скелетная рукоять" +ITEM.description = "Парная скелетная рукоять для улучшенного контроля оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 650 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_dual_skel" +ITEM.tacRPSlot = "grip" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_duffelbag.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_duffelbag.lua new file mode 100644 index 0000000..b04d635 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_duffelbag.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Сумка для дополнительных патрон" +ITEM.description = "Вещмешок для переноски дополнительных патронов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_duffelbag" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_ergo.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_ergo.lua new file mode 100644 index 0000000..8ffc771 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_ergo.lua @@ -0,0 +1,11 @@ +ITEM.name = "Эргономическая рукоятка для оружия" +ITEM.description = "Эргономичная рукоятка для оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 400 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "grip" +ITEM.tacRPSlot = "grip" +ITEM.tacRPAttachmentID = "acc_ergo" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extendedbelt.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extendedbelt.lua new file mode 100644 index 0000000..3d9af8a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extendedbelt.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Удлиненная лента" +ITEM.description = "Удлиненная патронная лента для пулеметов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 900 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_extendedbelt" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dual.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dual.lua new file mode 100644 index 0000000..a0d2bc1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dual.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Парные увеличенные магазины" +ITEM.description = "Парные удлиненные магазины для увеличения боезапаса." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_dual" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dual2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dual2.lua new file mode 100644 index 0000000..c637d11 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dual2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Парные увеличенные магазины II" +ITEM.description = "Улучшенные парные удлиненные магазины второго поколения." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 750 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_dual2" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dualsmg.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dualsmg.lua new file mode 100644 index 0000000..1c1dc41 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_dualsmg.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Удлинённые магазины для двух ПП" +ITEM.description = "Парные удлиненные магазины для пистолетов-пулеметов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_dualsmg" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_pistol.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_pistol.lua new file mode 100644 index 0000000..a574e67 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_pistol.lua @@ -0,0 +1,11 @@ +ITEM.name = "Удлинённый магазин (Пистолет)" +ITEM.description = "Расширенный магазин для пистолета." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 500 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "accessory" +ITEM.tacRPSlot = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_pistol" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_pistol2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_pistol2.lua new file mode 100644 index 0000000..48b91c2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_pistol2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Удлиненный магазин для пистолета II" +ITEM.description = "Удлиненный магазин для пистолета второго поколения." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_pistol2" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_rifle.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_rifle.lua new file mode 100644 index 0000000..f91be29 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_rifle.lua @@ -0,0 +1,11 @@ +ITEM.name = "Удлинённый магазин (Винтовка)" +ITEM.description = "Расширенный магазин для винтовки." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 550 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "accessory" +ITEM.tacRPSlot = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_rifle" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_rifle2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_rifle2.lua new file mode 100644 index 0000000..e56a584 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_rifle2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Удлинённый магазин винтовки II" +ITEM.description = "Удлиненный магазин для винтовки второго поколения." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_rifle2" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun.lua new file mode 100644 index 0000000..620dceb --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun.lua @@ -0,0 +1,11 @@ +ITEM.name = "Удлинённый магазин (Дробовик)" +ITEM.description = "Расширенный магазин для дробовика." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 600 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "accessory" +ITEM.tacRPSlot = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_shotgun" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun_mag.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun_mag.lua new file mode 100644 index 0000000..5290ac8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun_mag.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Удлинённый магазин дробовика" +ITEM.description = "Расширенный магазин для дробовиков магазинного типа. Увеличивает емкость боекомплекта." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "Accessories" +ITEM.tacRPAttachmentID = "acc_extmag_shotgun_mag" +ITEM.tacRPSlot = "acc" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Shotguns"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun_tube.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun_tube.lua new file mode 100644 index 0000000..28e11fc --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_shotgun_tube.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Удлинённая трубка дробовика" +ITEM.description = "Расширенная трубка для дробовиков трубчатого типа. Увеличивает емкость боекомплекта." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 650 +ITEM.attachmentType = "Accessories" +ITEM.tacRPAttachmentID = "acc_extmag_shotgun_tube" +ITEM.tacRPSlot = "acc" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Shotguns"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_smg.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_smg.lua new file mode 100644 index 0000000..492f28e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_smg.lua @@ -0,0 +1,11 @@ +ITEM.name = "Удлинённый магазин (ПП)" +ITEM.description = "Расширенный магазин для пистолета-пулемёта." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 500 +ITEM.category = "Обвесы" +ITEM.base = "base_attachments" +ITEM.attachmentType = "accessory" +ITEM.tacRPSlot = "accessory" +ITEM.tacRPAttachmentID = "acc_extmag_smg" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_sniper.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_sniper.lua new file mode 100644 index 0000000..0faafc7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_extmag_sniper.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Удлинённый магазин снайперской" +ITEM.description = "Расширенный магазин для снайперских винтовок. Увеличивает количество патронов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "Accessories" +ITEM.tacRPAttachmentID = "acc_extmag_sniper" +ITEM.tacRPSlot = "acc" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Sniper Rifles"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_foldstock.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_foldstock.lua new file mode 100644 index 0000000..3baded2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_foldstock.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Складной приклад" +ITEM.description = "Складной приклад, повышающий мобильность и скорость прицеливания." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_foldstock" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_foldstock2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_foldstock2.lua new file mode 100644 index 0000000..d3fb189 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_foldstock2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Компактный складной приклад" +ITEM.description = "Компактный складной приклад для улучшенной маневренности." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_foldstock2" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_pad.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_pad.lua new file mode 100644 index 0000000..4d272f5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_pad.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Затыльник против отдачи" +ITEM.description = "Амортизирующий затыльник для снижения отдачи." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 400 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_pad" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_sling.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_sling.lua new file mode 100644 index 0000000..818fde9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_acc_sling.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Оружейный ремень" +ITEM.description = "Тактический ремень для улучшенной стабильности оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 350 +ITEM.attachmentType = "accessory" +ITEM.tacRPAttachmentID = "acc_sling" +ITEM.tacRPSlot = "accessory" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_1858_36perc.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_1858_36perc.txt new file mode 100644 index 0000000..d8ca10e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_1858_36perc.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "1858 .36 Перкуссия" +ITEM.description = "Оригинальные капсюльные патроны .36 калибра для револьвера 1858. Сбалансированная баллистика." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_1858_36perc" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"1858"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_1858_45colt.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_1858_45colt.txt new file mode 100644 index 0000000..3c0b87f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_1858_45colt.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "1858 .45 Colt Конверсия" +ITEM.description = "Конверсия револьвера 1858 под патрон .45 Colt. Увеличивает останавливающую силу." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 750 +ITEM.attachmentType = "Ammunition" +ITEM.tacRPAttachmentID = "ammo_1858_45colt" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"1858"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_3gl.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_3gl.lua new file mode 100644 index 0000000..83a8daf --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_3gl.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Гранаты 3GL" +ITEM.description = "Тройной залп гранат 40mm для подствольного гранатомета. Выстреливает 3 гранаты залпом." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_3gl" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_buck.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_buck.lua new file mode 100644 index 0000000..b8686d9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_buck.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "40мм Картечь" +ITEM.description = "Картечь 40mm для подствольного гранатомета. Эффективна на ближних дистанциях против живой силы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_buck" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_gas.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_gas.lua new file mode 100644 index 0000000..545a7ce --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_gas.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Газовая граната" +ITEM.description = "Газовая граната 40mm для подствольного гранатомета. Создает облако слезоточивого газа при взрыве." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 900 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_gas" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_heal.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_heal.lua new file mode 100644 index 0000000..8cc68fa --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_heal.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Лечебная граната" +ITEM.description = "Лечащая граната 40mm для подствольного гранатомета. Восстанавливает здоровье союзников в зоне взрыва." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 2000 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_heal" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_heat.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_heat.lua new file mode 100644 index 0000000..0f2431c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_heat.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Кумулятивный снаряд" +ITEM.description = "Кумулятивный снаряд 40mm для подствольного гранатомета. Эффективен против бронированных целей." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_heat" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_impact.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_impact.lua new file mode 100644 index 0000000..3ab3e41 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_impact.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Ударная граната" +ITEM.description = "Контактная граната 40mm для подствольного гранатомета. Взрывается при попадании в цель." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1000 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_impact" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_lvg.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_lvg.lua new file mode 100644 index 0000000..ba3eeea --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_lvg.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Граната малой скорости" +ITEM.description = "Граната 40mm с низкой скоростью полета. Имеет дугообразную траекторию и меньшую дальность." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 950 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_lvg" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_ratshot.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_ratshot.lua new file mode 100644 index 0000000..4fa3d51 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_ratshot.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "40мм Смешанная дробь" +ITEM.description = "Мелкая дробь 40mm для подствольного гранатомета. Выпускает большое количество мелких снарядов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_ratshot" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_smoke.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_smoke.lua new file mode 100644 index 0000000..6e35d41 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_40mm_smoke.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Дымовая граната" +ITEM.description = "Дымовая граната 40mm для подствольного гранатомета. Создает плотную дымовую завесу." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_40mm_smoke" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M203", "GP-25", "M320"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ak12_762.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ak12_762.lua new file mode 100644 index 0000000..57ce305 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ak12_762.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "АК-12 7.62x39мм" +ITEM.description = "Магазин АК-12 под патроны 7.62x39mm. Повышает убойность и дальность стрельбы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "Ammunition" +ITEM.tacRPAttachmentID = "ammo_ak12_762" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK", "Galil"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_hv.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_hv.lua new file mode 100644 index 0000000..b5cd2d7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_hv.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "AMR Высокоскоростные" +ITEM.description = "Сверхскоростные патроны для AMR. Гиперзвуковая скорость пули с невероятным пробитием." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_amr_hv" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AMR", "Barrett", "M82"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_ratshot.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_ratshot.lua new file mode 100644 index 0000000..c55d3a5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_ratshot.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "AMR Смешанная дробь" +ITEM.description = "Дробовая пуля для AMR. Превращает снайперскую винтовку в мощнейший дробовик." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_amr_ratshot" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AMR", "Barrett", "M82"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_saphe.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_saphe.lua new file mode 100644 index 0000000..b2a9347 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_amr_saphe.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "AMR SAPHE" +ITEM.description = "Бронебойно-фугасные патроны для AMR. Пробивают броню и взрываются внутри цели." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 1800 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_amr_saphe" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AMR", "Barrett", "M82"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_buckshotroulette.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_buckshotroulette.lua new file mode 100644 index 0000000..e64a931 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_buckshotroulette.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Картечная рулетка" +ITEM.description = "Рулетка с картечью. Чередование холостых и боевых патронов. Психологическая игра со смертью." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 2500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_buckshotroulette" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_he.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_he.lua new file mode 100644 index 0000000..9d3994a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_he.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Гироджет ОФ" +ITEM.description = "Реактивные патроны Gyrojet с фугасной боеголовкой. Максимальная разрушительная мощь." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1400 +ITEM.attachmentType = "Ammunition" +ITEM.tacRPAttachmentID = "ammo_gyrojet_he" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Gyrojet"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_lv.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_lv.lua new file mode 100644 index 0000000..a1a3fbf --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_lv.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Гироджет дозвуковые" +ITEM.description = "Реактивные патроны Gyrojet с пониженной скоростью. Меньше шума, больше контроля." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "Ammunition" +ITEM.tacRPAttachmentID = "ammo_gyrojet_lv" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Gyrojet"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_pipe.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_pipe.lua new file mode 100644 index 0000000..c46d7c7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_pipe.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Гироджет Самодельная бомба" +ITEM.description = "Самодельные реактивные снаряды Gyrojet типа 'трубчатая бомба'. Мощный взрывной эффект." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "Ammunition" +ITEM.tacRPAttachmentID = "ammo_gyrojet_pipe" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Gyrojet"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_ratshot.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_ratshot.lua new file mode 100644 index 0000000..482b308 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_gyrojet_ratshot.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Гироджет Смешанная дробь" +ITEM.description = "Реактивные патроны Gyrojet с дробовым снарядом. Эффективны против живых целей на близком расстоянии." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 900 +ITEM.attachmentType = "Ammunition" +ITEM.tacRPAttachmentID = "ammo_gyrojet_ratshot" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Gyrojet"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ks23_flashbang.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ks23_flashbang.lua new file mode 100644 index 0000000..6ce6f6b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ks23_flashbang.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "КС-23 Светошумовой" +ITEM.description = "Светошумовые патроны для КС-23. Ослепляют и оглушают противников без смертельного урона." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_ks23_flashbang" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"KS-23", "TOZ-123"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ks23_flashbang_top.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ks23_flashbang_top.lua new file mode 100644 index 0000000..1bf96c7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_ks23_flashbang_top.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "КС-23 Светошумовой верхний" +ITEM.description = "Усиленные светошумовые патроны для КС-23. Увеличенная дальность и мощность ослепления." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 1300 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_ks23_flashbang_top" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"KS-23", "TOZ-123"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_magnum.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_magnum.lua new file mode 100644 index 0000000..7aaa3c3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_magnum.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Патроны Магнум" +ITEM.description = "Мощные патроны с увеличенным зарядом пороха. Значительно повышают урон, но усиливают отдачу." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 700 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_magnum" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_ap.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_ap.lua new file mode 100644 index 0000000..70abab8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_ap.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Бронебойные патроны" +ITEM.description = "Бронебойные патроны с усиленным сердечником. Эффективно пробивают броню, но имеют меньшее останавливающее действие." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_pistol_ap" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_headshot.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_headshot.lua new file mode 100644 index 0000000..d4051bb --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_headshot.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Патроны в голову" +ITEM.description = "Специальные патроны с увеличенным уроном при попадании в голову. Требуют высокой точности стрельбы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_pistol_headshot" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_hollowpoints.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_hollowpoints.lua new file mode 100644 index 0000000..cd174c0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_hollowpoints.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Экспансивные пули" +ITEM.description = "Экспансивные патроны с полостью в головке. Увеличивают урон по незащищенным целям, но хуже пробивают броню." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_pistol_hollowpoints" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_match.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_match.lua new file mode 100644 index 0000000..f9cfa07 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_pistol_match.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Патроны матч-класса" +ITEM.description = "Высококачественные патроны для пистолетов. Улучшают точность и кучность стрельбы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 400 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_pistol_match" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_r700_300winmag.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_r700_300winmag.lua new file mode 100644 index 0000000..04c51d0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_r700_300winmag.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = ".300 Winchester Magnum" +ITEM.description = "Магнум патроны .300 Winchester для снайперских винтовок. Повышенная дальность и урон." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 900 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_r700_300winmag" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"R700", "M24", "Remington 700"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rifle_jhp.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rifle_jhp.lua new file mode 100644 index 0000000..95ea6c6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rifle_jhp.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Полуоболочечные патроны" +ITEM.description = "Охотничьи патроны с полуоболочкой. Обеспечивают высокое останавливающее действие и экспансию при попадании." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_rifle_jhp" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rifle_match.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rifle_match.lua new file mode 100644 index 0000000..56da5da --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rifle_match.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Винтовочные матч-патроны" +ITEM.description = "Прецизионные винтовочные патроны. Значительно повышают точность стрельбы на дальних дистанциях." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 450 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_rifle_match" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_roulette.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_roulette.lua new file mode 100644 index 0000000..82a68fb --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_roulette.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Русская рулетка" +ITEM.description = "Русская рулетка. Каждый патрон либо критический урон, либо осечка. Рискованно, но смертельно." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 3000 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_roulette" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_harpoon.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_harpoon.txt new file mode 100644 index 0000000..bdaba8d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_harpoon.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Гарпун" +ITEM.description = "Гарпун для РПГ. Не взрывается, но наносит огромный колющий урон и пробивает насквозь." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1100 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_rpg_harpoon" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"RPG", "RPG-7", "SMAW"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_improvised.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_improvised.txt new file mode 100644 index 0000000..7651448 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_improvised.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Самодельный РПГ" +ITEM.description = "Самодельная граната для РПГ. Нестабильная, но дешевая. Меньший урон и радиус поражения." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_rpg_improvised" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"RPG", "RPG-7", "SMAW"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_mortar.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_mortar.txt new file mode 100644 index 0000000..a5f6865 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_mortar.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Миномётная мина" +ITEM.description = "Миномётный снаряд для РПГ. Высокая траектория полета и огромный радиус поражения." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1400 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_rpg_mortar" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"RPG", "RPG-7", "SMAW"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_ratshot.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_ratshot.txt new file mode 100644 index 0000000..9c5018d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_rpg_ratshot.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "РПГ Смешанная дробь" +ITEM.description = "Шрапнельная граната для РПГ. При взрыве выпускает множество смертоносных осколков." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1000 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_rpg_ratshot" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"RPG", "RPG-7", "SMAW"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_bird.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_bird.lua new file mode 100644 index 0000000..097b223 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_bird.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Мелкая дробь" +ITEM.description = "Мелкая дробь для охоты на птицу. Широкий разброс, но малоэффективна на больших дистанциях и против брони." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 300 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_bird" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_breach.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_breach.lua new file mode 100644 index 0000000..88a9834 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_breach.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Патроны для пролома" +ITEM.description = "Специализированные патроны для взлома замков и петель дверей. Эффективны против препятствий, но слабы против живых целей." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 400 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_breach" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_dragon.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_dragon.lua new file mode 100644 index 0000000..9e204fe --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_dragon.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Дыхание дракона" +ITEM.description = "Зажигательные патроны с магниевыми элементами. Выпускают струю огня, поджигая цели и создавая устрашающий эффект." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 1100 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_dragon" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_frag.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_frag.lua new file mode 100644 index 0000000..b0f0609 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_frag.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Frag-12" +ITEM.description = "Осколочные патроны с взрывчаткой. Взрываются при попадании, нанося урон по площади. Крайне эффективны, но дороги." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 900 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_frag" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_mag.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_mag.lua new file mode 100644 index 0000000..e51bcde --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_mag.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Картечь Магнум" +ITEM.description = "Усиленная картечь с увеличенным зарядом. Больше урона и дальность, но сильнее отдача." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 550 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_mag" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_minishell.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_minishell.lua new file mode 100644 index 0000000..4aa5e7f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_minishell.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Мини-гильзы" +ITEM.description = "Мини патроны для дробовика. Меньший урон, но увеличенная емкость магазина и скорострельность." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 600 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_minishell" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_slugs.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_slugs.lua new file mode 100644 index 0000000..b59af79 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_slugs.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Пули-слаги" +ITEM.description = "Тяжелые цельные пули для дробовика. Превращают дробовик в мощное оружие для стрельбы на средних дистанциях." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_slugs" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_triple.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_triple.lua new file mode 100644 index 0000000..da07301 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_triple.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тройной удар" +ITEM.description = "Тройной выстрел из дробовика. Три быстрых выстрела за раз с уменьшенным уроном каждого." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 1000 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_triple" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_triple2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_triple2.lua new file mode 100644 index 0000000..7b02dd6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_shotgun_triple2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тройной удар II" +ITEM.description = "Улучшенная версия тройного выстрела. Три выстрела с лучшей кучностью и уроном." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 1100 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_shotgun_triple2" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_star15_300blk.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_star15_300blk.txt new file mode 100644 index 0000000..fca4107 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_star15_300blk.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "STAR-15 .300 Blackout" +ITEM.description = "Патроны .300 Blackout для STAR-15. Обеспечивают высокую останавливающую силу и эффективность с глушителем." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 850 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_star15_300blk" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"STAR-15"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_star15_50beo.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_star15_50beo.txt new file mode 100644 index 0000000..596d5d5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_star15_50beo.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "STAR-15 .50 Beowulf" +ITEM.description = "Мощные патроны .50 Beowulf для STAR-15. Максимальная останавливающая сила за счет повышенной отдачи." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_star15_50beo" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"STAR-15"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_4aam.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_4aam.txt new file mode 100644 index 0000000..827c921 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_4aam.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Стингер 4AAM" +ITEM.description = "Система залпового пуска четырех ракет. Позволяет поражать несколько целей одновременно." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 3500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_stinger_4aam" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Stinger"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_apers.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_apers.txt new file mode 100644 index 0000000..e3d1cf1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_apers.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Стингер APERS" +ITEM.description = "Противопехотная ракета с осколочной боеголовкой. Эффективна против пехоты и небронированных целей." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 2000 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_stinger_apers" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Stinger"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_qaam.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_qaam.txt new file mode 100644 index 0000000..43f38a8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_qaam.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Стингер QAAM" +ITEM.description = "Быстрая ракета с улучшенной скоростью полета. Сокращает время до попадания в цель." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 2800 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_stinger_qaam" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Stinger"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_saam.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_saam.txt new file mode 100644 index 0000000..4214fb8 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_stinger_saam.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Стингер SAAM" +ITEM.description = "Полу-активная ракета с радарным наведением. Требует постоянного захвата цели для попадания." +ITEM.model = "models/Items/BoxBuckshot.mdl" +ITEM.price = 2500 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_stinger_saam" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Stinger"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_subsonic.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_subsonic.lua new file mode 100644 index 0000000..600c5d4 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_subsonic.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Дозвуковые патроны" +ITEM.description = "Дозвуковые патроны с уменьшенной начальной скоростью. Идеально подходят для стрельбы с глушителем, практически бесшумны." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 550 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_subsonic" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_surplus.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_surplus.lua new file mode 100644 index 0000000..4e6cc64 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_surplus.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Списанные патроны" +ITEM.description = "Дешевые армейские патроны из старых запасов. Низкое качество может снижать точность и надежность." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 200 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_surplus" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_tmj.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_tmj.lua new file mode 100644 index 0000000..9457d08 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_tmj.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Патроны TMJ" +ITEM.description = "Патроны с полной металлической оболочкой. Стабильный урон и точность без экспансии." +ITEM.model = "models/Items/BoxMRounds.mdl" +ITEM.price = 450 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_tmj" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_usp_9mm.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_usp_9mm.txt new file mode 100644 index 0000000..b5f78bf --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_ammo_usp_9mm.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "USP Конверсия 9мм" +ITEM.description = "Конверсия USP под 9mm патроны. Меньший урон, но больше боезапас и ниже отдача." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "ammo" +ITEM.tacRPAttachmentID = "ammo_usp_9mm" +ITEM.tacRPSlot = "ammo" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"USP", "USP-45", "HK45"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_af2011_alt.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_af2011_alt.txt new file mode 100644 index 0000000..3b74aa2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_af2011_alt.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "AF2011 Альтернативный затвор" +ITEM.description = "Альтернативный затвор для AF2011. Специфичная модификация, оптимизированная под двуствольную конструкцию." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_af2011_alt" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AF2011"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_fine.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_fine.lua new file mode 100644 index 0000000..635b1c9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_fine.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Точно настроенный затвор" +ITEM.description = "Точно настроенный затвор, повышающий точность стрельбы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_fine" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_greased.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_greased.lua new file mode 100644 index 0000000..9303f0c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_greased.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Смазанный затвор" +ITEM.description = "Смазанный затвор, увеличивающий скорострельность оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 400 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_greased" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_heavy.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_heavy.lua new file mode 100644 index 0000000..2029ed5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_heavy.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тяжелый затвор" +ITEM.description = "Тяжелый затвор, уменьшающий отдачу вниз за счет большей массы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_heavy" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_light.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_light.lua new file mode 100644 index 0000000..7c05410 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_light.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Легкий затвор" +ITEM.description = "Легкий затвор, повышающий скорость работы механизмов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 450 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_light" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_refurbished.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_refurbished.lua new file mode 100644 index 0000000..692885a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_refurbished.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Восстановленный затвор" +ITEM.description = "Восстановленный затвор, повышающий надежность оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 300 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_refurbished" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_rough.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_rough.lua new file mode 100644 index 0000000..d1612a0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_rough.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Грубо обработанный затвор" +ITEM.description = "Грубо обработанный затвор, увеличивающий скорость стрельбы, но снижающий точность." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 250 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_rough" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_surplus.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_surplus.lua new file mode 100644 index 0000000..ec07e99 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_surplus.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Дешевый армейский затвор" +ITEM.description = "Дешевый армейский затвор, базовая модификация без особых преимуществ." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 200 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_surplus" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_tactical.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_tactical.lua new file mode 100644 index 0000000..1730adb --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_bolt_tactical.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тактический затвор" +ITEM.description = "Тактический затвор, обеспечивающий сбалансированные характеристики." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.attachmentType = "bolt" +ITEM.tacRPAttachmentID = "bolt_tactical" +ITEM.tacRPSlot = "bolt" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_afterimage.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_afterimage.txt new file mode 100644 index 0000000..a06fe40 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_afterimage.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Послеобраз" +ITEM.description = "Остаточные следы, создающие визуальные эффекты при взмахах холодным оружием" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1000 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_afterimage" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_agi.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_agi.lua new file mode 100644 index 0000000..bd26e0c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_agi.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тренировка ловкости" +ITEM.description = "Тренировка ловкости, увеличивающая скорость атаки холодным оружием" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 750 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_agi" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_all.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_all.lua new file mode 100644 index 0000000..cd0bde0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_all.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Разносторонняя тренировка" +ITEM.description = "Комплексная тренировка, улучшающая все характеристики холодного оружия" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_all" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_int.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_int.lua new file mode 100644 index 0000000..d846e40 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_int.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тренировка интеллекта" +ITEM.description = "Тренировка интеллекта, повышающая точность ударов холодным оружием" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_int" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_lifesteal.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_lifesteal.lua new file mode 100644 index 0000000..8b86ebf --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_lifesteal.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Кража жизни" +ITEM.description = "Вампиризм, позволяющий восстанавливать здоровье при атаках холодным оружием" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_lifesteal" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_momentum.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_momentum.lua new file mode 100644 index 0000000..e5f8a80 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_momentum.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Импульс" +ITEM.description = "Разгон, увеличивающий урон с каждым последующим ударом холодным оружием" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 900 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_momentum" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_shock.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_shock.lua new file mode 100644 index 0000000..6bacf8c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_shock.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Ударная волна" +ITEM.description = "Ударная волна, создающая взрывной эффект при ударах холодным оружием" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1100 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_shock" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_str.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_str.lua new file mode 100644 index 0000000..c72e233 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_melee_boost_str.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тренировка силы" +ITEM.description = "Тренировка силы, увеличивающая урон от холодного оружия" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "melee_boost" +ITEM.tacRPAttachmentID = "melee_boost_str" +ITEM.tacRPSlot = "melee_boost" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_ak_booster.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_ak_booster.lua new file mode 100644 index 0000000..3958c16 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_ak_booster.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Дульный усилитель для АК" +ITEM.description = "Дульный усилитель для АК. Увеличивает скорострельность и снижает отдачу." +ITEM.model = "models/weapons/tacint_extras/addons/ak74u_booster.mdl" +ITEM.price = 550 +ITEM.attachmentType = "Muzzle" +ITEM.tacRPAttachmentID = "muzz_ak_booster" +ITEM.tacRPSlot = "muzzle" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK", "Galil"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_ak_comp.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_ak_comp.lua new file mode 100644 index 0000000..31ba1f9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_ak_comp.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Дульный компенсатор" +ITEM.description = "Дульный компенсатор для АК. Значительно снижает вертикальную отдачу при стрельбе." +ITEM.model = "models/weapons/tacint_extras/addons/ak74_comp.mdl" +ITEM.price = 600 +ITEM.attachmentType = "Muzzle" +ITEM.tacRPAttachmentID = "muzz_ak_comp" +ITEM.tacRPSlot = "muzzle" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK", "Galil"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_aggressor.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_aggressor.lua new file mode 100644 index 0000000..125fb74 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_aggressor.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Агрессивный дульный тормоз" +ITEM.description = "Агрессивный дульный тормоз. Значительно снижает отдачу, но увеличивает звук выстрела." +ITEM.model = "models/weapons/tacint_extras/addons/brake_aggressor.mdl" +ITEM.price = 650 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_brake_aggressor" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_breaching.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_breaching.lua new file mode 100644 index 0000000..360c105 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_breaching.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тормоз для пролома" +ITEM.description = "Штурмовой дульный тормоз. Оптимизирован для ближнего боя и быстрого перемещения." +ITEM.model = "models/weapons/tacint_extras/addons/brake_breacher.mdl" +ITEM.price = 600 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_brake_breaching" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_concussive.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_concussive.lua new file mode 100644 index 0000000..225b8b0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_brake_concussive.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Контузионный тормоз" +ITEM.description = "Контузионный дульный тормоз. Создаёт мощную ударную волну при выстреле." +ITEM.model = "models/weapons/tacint_extras/addons/brake_concussive.mdl" +ITEM.price = 700 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_brake_concussive" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_io_m14.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_io_m14.txt new file mode 100644 index 0000000..f86b429 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_io_m14.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Компенсатор M14" +ITEM.description = "Дульный компенсатор для M14. Эффективно снижает отдачу при стрельбе из винтовки калибра 7.62x51mm." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 650 +ITEM.attachmentType = "Muzzle" +ITEM.tacRPAttachmentID = "muzz_comp_io_m14" +ITEM.tacRPSlot = "muzzle" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M14", "M14 EBR", "M1A"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_mac10.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_mac10.txt new file mode 100644 index 0000000..d73d5de --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_mac10.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Компенсатор MAC-10" +ITEM.description = "Специализированный компенсатор для MAC-10. Улучшает контроль отдачи." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_comp_mac10" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_usp.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_usp.txt new file mode 100644 index 0000000..8dbc4d6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_comp_usp.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Компенсатор USP" +ITEM.description = "Специализированный компенсатор для USP. Улучшает контроль отдачи." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_comp_usp" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_hbar.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_hbar.lua new file mode 100644 index 0000000..ccd4ff9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_hbar.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тяжёлый ствол" +ITEM.description = "Тяжёлый ствол. Повышает точность, но увеличивает вес оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_hbar" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_lbar.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_lbar.lua new file mode 100644 index 0000000..e40c93f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_lbar.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Облегчённый ствол" +ITEM.description = "Облегчённый ствол. Снижает вес оружия и улучшает манёвренность." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_lbar" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_pistol_comp.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_pistol_comp.txt new file mode 100644 index 0000000..134731f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_pistol_comp.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Компенсатор для пистолета" +ITEM.description = "Компенсатор для пистолетов. Снижает отдачу при стрельбе." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.category = "Обвесы" +ITEM.attachmentType = "muzzle" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_pistol_comp" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_silencer.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_silencer.lua new file mode 100644 index 0000000..1833c41 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_silencer.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Глушитель" +ITEM.description = "Глушитель для оружия. Снижает шум выстрела." +ITEM.model = "models/weapons/tacint/addons/silencer.mdl" +ITEM.price = 800 +ITEM.category = "Обвесы" +ITEM.attachmentType = "suppressor" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_silencer" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_assassin.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_assassin.lua new file mode 100644 index 0000000..ef59e7e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_assassin.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Профессиональный глушитель" +ITEM.description = "Профессиональный глушитель для скрытных операций. Максимально снижает шум выстрела." +ITEM.model = "models/weapons/tacint_shark/addons/suppressor_assassin.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "suppressor" +ITEM.tacRPAttachmentID = "muzz_supp_assassin" +ITEM.tacRPSlot = "muzzle" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Pistols", "SMGs"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_compact.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_compact.lua new file mode 100644 index 0000000..a3988ec --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_compact.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Компактный глушитель" +ITEM.description = "Компактный глушитель. Снижает шум выстрела с минимальным увеличением длины ствола." +ITEM.model = "models/weapons/tacint_extras/addons/suppressor.mdl" +ITEM.price = 750 +ITEM.category = "Обвесы" +ITEM.attachmentType = "suppressor" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_supp_compact" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_pbs.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_pbs.lua new file mode 100644 index 0000000..de1ee2c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_pbs.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Глушитель ПБС-5" +ITEM.description = "Глушитель ПБС-5 для АК. Значительно снижает звук выстрела и скрывает дульную вспышку." +ITEM.model = "models/weapons/tacint_extras/addons/suppressor_pbs.mdl" +ITEM.price = 850 +ITEM.attachmentType = "Muzzle" +ITEM.tacRPAttachmentID = "muzz_supp_pbs" +ITEM.tacRPSlot = "muzzle" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK", "Galil"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_weighted.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_weighted.lua new file mode 100644 index 0000000..3c763e6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_supp_weighted.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Утяжелённый глушитель" +ITEM.description = "Утяжелённый глушитель. Снижает шум выстрела и улучшает стабильность оружия." +ITEM.model = "models/weapons/tacint_extras/addons/suppressor_salvo.mdl" +ITEM.price = 800 +ITEM.category = "Обвесы" +ITEM.attachmentType = "suppressor" +ITEM.tacRPSlot = "muzzle" +ITEM.tacRPAttachmentID = "muzz_supp_weighted" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_tec9_shroud.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_tec9_shroud.txt new file mode 100644 index 0000000..a9ee929 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_muzz_tec9_shroud.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Кожух TEC-9" +ITEM.description = "Защитный кожух для TEC-9. Улучшает теплоотвод и защищает руку от нагрева ствола при интенсивной стрельбе." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.attachmentType = "Muzzle" +ITEM.tacRPAttachmentID = "muzz_tec9_shroud" +ITEM.tacRPSlot = "muzzle" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"TEC-9", "TEC9", "Intratec TEC-9"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_8x.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_8x.lua new file mode 100644 index 0000000..bafc14c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_8x.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Телескопический прицел 8x" +ITEM.description = "Снайперский прицел с 8-кратным увеличением для дальних дистанций" +ITEM.model = "models/weapons/tacint/addons/8x.mdl" +ITEM.price = 1000 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_8x" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_acog.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_acog.lua new file mode 100644 index 0000000..e37d138 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_acog.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "ACOG" +ITEM.description = "Боевой прицел с 4-кратным увеличением для средних дистанций" +ITEM.model = "models/weapons/tacint/addons/acog.mdl" +ITEM.price = 700 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_acog" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ak_kobra.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ak_kobra.lua new file mode 100644 index 0000000..e7778aa --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ak_kobra.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Кобра" +ITEM.description = "Коллиматорный прицел Кобра для АК. Обеспечивает быстрое прицеливание на ближних и средних дистанциях." +ITEM.model = "models/weapons/tacint_extras/addons/ak_kobra.mdl" +ITEM.price = 700 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_ak_kobra" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK", "Galil"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ak_pso1.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ak_pso1.lua new file mode 100644 index 0000000..7038dc4 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ak_pso1.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "AK ПСО-1" +ITEM.description = "Оптический прицел ПСО-1, адаптированный для АК. Позволяет вести прицельный огонь на средних и дальних дистанциях." +ITEM.model = "models/weapons/tacint_extras/addons/pso1.mdl" +ITEM.price = 1100 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_ak_pso1" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ar_colt.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ar_colt.lua new file mode 100644 index 0000000..67a77c1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_ar_colt.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел Colt 4x" +ITEM.description = "Оптический прицел Colt 4x для штурмовых винтовок. Идеален для средних дистанций боя." +ITEM.model = "models/weapons/tacint_extras/addons/coltscope.mdl" +ITEM.price = 900 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_ar_colt" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M4A1", "M16", "AR-15", "SCAR", "HK416"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_delisle_scope.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_delisle_scope.txt new file mode 100644 index 0000000..237b284 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_delisle_scope.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел De Lisle" +ITEM.description = "Оптический прицел для De Lisle Carbine. Специализированная оптика для бесшумного оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 950 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_delisle_scope" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"De Lisle", "DeLisle Carbine"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_elcan.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_elcan.lua new file mode 100644 index 0000000..42db567 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_elcan.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "ELCAN" +ITEM.description = "Боевой прицел с 3.4-кратным увеличением для средних дистанций" +ITEM.model = "models/weapons/tacint_extras/addons/elcan.mdl" +ITEM.price = 650 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_elcan" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_galil.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_galil.txt new file mode 100644 index 0000000..497e92f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_galil.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел Galil" +ITEM.description = "Оптический прицел Galil для АК. Обеспечивает точную стрельбу на средних и дальних дистанциях." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 650 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_galil" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"AK", "AK-74", "AK-12", "AKM", "RPK", "Galil"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_holographic.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_holographic.lua new file mode 100644 index 0000000..ca60c62 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_holographic.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Голографический прицел" +ITEM.description = "Голографический прицел для быстрого прицеливания на ближних и средних дистанциях" +ITEM.model = "models/weapons/tacint/addons/holosight.mdl" +ITEM.price = 400 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_holographic" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_irons.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_irons.txt new file mode 100644 index 0000000..2a60326 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_irons.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Механические прицелы" +ITEM.description = "Стандартные механические прицелы без дополнительных функций" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 300 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_irons" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_k98_zf42.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_k98_zf42.txt new file mode 100644 index 0000000..e9f2b86 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_k98_zf42.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел K98 ZF42" +ITEM.description = "Немецкий снайперский прицел ZF42 для Mauser K98. Историческая оптика времён Второй мировой войны." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1000 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_k98_zf42" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"K98", "Kar98k", "Mauser"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_m16a2_colt.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_m16a2_colt.txt new file mode 100644 index 0000000..f6920f5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_m16a2_colt.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Ручка переноски M16A2" +ITEM.description = "Фирменная рукоятка-прицел M16A2 с механическим прицелом. Классика американской армии." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_m16a2_colt" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M16", "M16A2", "M16A4", "M4A1"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_m1_scope.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_m1_scope.txt new file mode 100644 index 0000000..96b8336 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_m1_scope.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел M1 Garand" +ITEM.description = "Оптический прицел для M1 Garand. Классическая американская оптика эпохи Второй мировой." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 850 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_m1_scope" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"M1 Garand", "M1", "Garand"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_okp7.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_okp7.lua new file mode 100644 index 0000000..66d5b39 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_okp7.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "ОКП-7" +ITEM.description = "Российский коллиматорный прицел для быстрого прицеливания" +ITEM.model = "models/weapons/tacint/addons/okp7.mdl" +ITEM.price = 450 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_okp7" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_pso1.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_pso1.lua new file mode 100644 index 0000000..bb0c34c --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_pso1.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "ПСО-1" +ITEM.description = "Оптический прицел ПСО-1 для СВД. Классический советский снайперский прицел с 4x увеличением и дальномерной сеткой." +ITEM.model = "models/weapons/tacint_extras/addons/pso1.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_pso1" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"SVD", "СВД", "Dragunov"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rds.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rds.lua new file mode 100644 index 0000000..a586aa4 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rds.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел красная точка" +ITEM.description = "Коллиматорный прицел с красной точкой для быстрого прицеливания" +ITEM.model = "models/wystan/attachments/doctorrds.mdl" +ITEM.price = 400 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_rds" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rds2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rds2.lua new file mode 100644 index 0000000..413fa0a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rds2.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел красная точка (Компактный)" +ITEM.description = "Компактный коллиматорный прицел с красной точкой" +ITEM.model = "models/weapons/tacint/addons/rds2.mdl" +ITEM.price = 350 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_rds2" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rmr.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rmr.lua new file mode 100644 index 0000000..f87833d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rmr.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "RMR" +ITEM.description = "Миниатюрный коллиматорный прицел для пистолетов" +ITEM.model = "models/weapons/tacint/addons/optic_rmr.mdl" +ITEM.price = 350 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_rmr" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rmr_rifle.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rmr_rifle.lua new file mode 100644 index 0000000..a6fdb2f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_rmr_rifle.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "RMR (Винтовочный)" +ITEM.description = "Миниатюрный коллиматорный прицел для винтовок" +ITEM.model = "models/weapons/tacint/addons/optic_rmr_hq.mdl" +ITEM.price = 400 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_rmr_rifle" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_shortdot.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_shortdot.lua new file mode 100644 index 0000000..e9d9386 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_shortdot.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Компактный прицел" +ITEM.description = "Короткий оптический прицел для ближних и средних дистанций" +ITEM.model = "models/weapons/tacint/addons/reddot.mdl" +ITEM.price = 500 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "scope" +ITEM.tacRPAttachmentID = "optic_shortdot" +ITEM.tacRPSlot = "optic" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_4x.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_4x.lua new file mode 100644 index 0000000..8795520 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_4x.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "4х прицел для ХМ8" +ITEM.description = "Оптический прицел 4x для XM8. Интегрированная система прицеливания для средних дистанций." +ITEM.model = "models/weapons/tacint/addons/8x.mdl" +ITEM.price = 1100 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_xm8_4x" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"XM8", "XM-8"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_6x.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_6x.lua new file mode 100644 index 0000000..b278b5e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_6x.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел XM8 6x" +ITEM.description = "Оптический прицел 6x для XM8. Улучшенная оптика для дальних дистанций." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1300 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_xm8_6x" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"XM8", "XM-8"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_8x.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_8x.lua new file mode 100644 index 0000000..a910de1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_optic_xm8_8x.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прицел XM8 8x" +ITEM.description = "Оптический прицел 8x для XM8. Максимальное увеличение для снайперского режима." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "Optic" +ITEM.tacRPAttachmentID = "optic_xm8_8x" +ITEM.tacRPSlot = "optic" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"XM8", "XM-8"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_aim.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_aim.lua new file mode 100644 index 0000000..8a753cf --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_aim.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Стабильность прицела" +ITEM.description = "Перк улучшает стабильность прицеливания, позволяя вести более точный огонь." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1000 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_aim" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_blindfire.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_blindfire.lua new file mode 100644 index 0000000..91a6d38 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_blindfire.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Стрельба вслепую" +ITEM.description = "Перк позволяет эффективно вести огонь вслепую из-за укрытия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 750 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_blindfire" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_hipfire.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_hipfire.lua new file mode 100644 index 0000000..d768ef6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_hipfire.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Специалист стрельбы с бедра" +ITEM.description = "Перк повышает точность стрельбы от бедра без использования прицела." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_hipfire" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_melee.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_melee.lua new file mode 100644 index 0000000..1749c53 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_melee.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Рукопашный бой" +ITEM.description = "Перк улучшает навыки ближнего боя, увеличивая урон рукопашных атак." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_melee" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_mlg.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_mlg.txt new file mode 100644 index 0000000..0ad3996 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_mlg.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "MLG Про" +ITEM.description = "Элитный перк, предоставляющий все преимущества профессионального бойца." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 2000 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_mlg" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_reload.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_reload.lua new file mode 100644 index 0000000..7476f51 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_reload.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Быстрая перезарядка" +ITEM.description = "Перк ускоряет процесс перезарядки оружия, позволяя быстрее вернуться в бой." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 900 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_reload" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_shock.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_shock.lua new file mode 100644 index 0000000..91a9ac1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_shock.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Устойчивость к шоку" +ITEM.description = "Перк повышает устойчивость к отдаче оружия, облегчая контроль над стрельбой." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 950 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_shock" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_speed.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_speed.lua new file mode 100644 index 0000000..d416877 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_speed.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Быстрое заряжание" +ITEM.description = "Перк увеличивает скорость всех манипуляций с оружием." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 850 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_speed" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_throw.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_throw.lua new file mode 100644 index 0000000..5a83e08 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_perk_throw.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Бросковая рука" +ITEM.description = "Перк улучшает навыки метания гранат, увеличивая дальность и точность броска." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "perk" +ITEM.tacRPAttachmentID = "perk_throw" +ITEM.tacRPSlot = "perk" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_1858_spin.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_1858_spin.txt new file mode 100644 index 0000000..9b96e51 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_1858_spin.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "1858 Вращающийся барабан" +ITEM.description = "Модифицированный барабан для 1858. Ускоренное вращение для быстрой перезарядки." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "tactical" +ITEM.tacRPAttachmentID = "tac_1858_spin" +ITEM.tacRPSlot = "tactical" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"1858"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_combo.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_combo.lua new file mode 100644 index 0000000..8efd932 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_combo.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Комбинированный модуль ЛЦУ/Фонарик" +ITEM.description = "Комбинированный тактический модуль с лазерным целеуказателем и фонариком" +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "laser" +ITEM.tacRPAttachmentID = "tac_combo" +ITEM.tacRPSlot = "tactical" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_dmic.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_dmic.txt new file mode 100644 index 0000000..94c5153 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_dmic.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "DMIC" +ITEM.description = "Встроенный микрофон для тактической связи. Позволяет передавать команды в бою." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.attachmentType = "tactical" +ITEM.tacRPAttachmentID = "tac_dmic" +ITEM.tacRPSlot = "tactical" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_flashlight.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_flashlight.lua new file mode 100644 index 0000000..1a308c1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_flashlight.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Фонарик" +ITEM.description = "Тактический фонарик для освещения темных помещений" +ITEM.model = "models/weapons/tacint/addons/flashlight.mdl" +ITEM.price = 200 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "flashlight" +ITEM.tacRPAttachmentID = "tac_flashlight" +ITEM.tacRPSlot = "tactical" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_laser.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_laser.lua new file mode 100644 index 0000000..54f0a1a --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_laser.lua @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Лазерный целеуказатель" +ITEM.description = "Лазерный целеуказатель для улучшения точности стрельбы от бедра" +ITEM.model = "models/weapons/tacint/addons/laser_mounted.mdl" +ITEM.price = 300 +ITEM.category = "Обвесы" + +ITEM.attachmentType = "laser" +ITEM.tacRPAttachmentID = "tac_laser" +ITEM.tacRPSlot = "tactical" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_rangefinder.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_rangefinder.txt new file mode 100644 index 0000000..58835c9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_rangefinder.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Дальномер" +ITEM.description = "Лазерный дальномер для точного определения расстояния до цели. Улучшает дальнюю стрельбу." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "tactical" +ITEM.tacRPAttachmentID = "tac_rangefinder" +ITEM.tacRPSlot = "tactical" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_spreadgauge.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_spreadgauge.txt new file mode 100644 index 0000000..df1a2d3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_tac_spreadgauge.txt @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Измеритель разброса" +ITEM.description = "Индикатор разброса в режиме реального времени. Показывает текущую точность оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "tactical" +ITEM.tacRPAttachmentID = "tac_spreadgauge" +ITEM.tacRPSlot = "tactical" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_toploader_stripper_clip.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_toploader_stripper_clip.lua new file mode 100644 index 0000000..e191fe3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_toploader_stripper_clip.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Обойма" +ITEM.description = "Обойма для быстрой зарядки винтовки сверху. Используется для винтовок типа Мосина, позволяет быстро заряжать патроны." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 400 +ITEM.attachmentType = "toploader" +ITEM.tacRPAttachmentID = "toploader_stripper_clip" +ITEM.tacRPSlot = "toploader" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_akimbo.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_akimbo.lua new file mode 100644 index 0000000..57622dd --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_akimbo.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Режим Акимбо" +ITEM.description = "Модификация триггера позволяющая использовать два пистолета одновременно. Значительно увеличивает огневую мощь." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_akimbo" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burst.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burst.lua new file mode 100644 index 0000000..b676b33 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burst.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Очередь из 3х выстрелов" +ITEM.description = "Спусковой механизм с режимом очереди из 3 выстрелов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_burst" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burst2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burst2.lua new file mode 100644 index 0000000..a96d8e2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burst2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Улучшенный спуск очередями" +ITEM.description = "Улучшенный механизм очередного огня. Обеспечивает более стабильную и точную стрельбу короткими очередями." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_burst2" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burstauto.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burstauto.lua new file mode 100644 index 0000000..fd8220f --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_burstauto.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Очередь-Авто" +ITEM.description = "Гибридный триггер сочетающий режимы очередного и автоматического огня. Универсальное решение для любой ситуации." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 850 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_burstauto" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_comp.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_comp.lua new file mode 100644 index 0000000..80977dc --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_comp.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Соревновательный спуск" +ITEM.description = "Спортивный спусковой механизм, значительно повышающий точность стрельбы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 700 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_comp" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_comp2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_comp2.lua new file mode 100644 index 0000000..8c58985 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_comp2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Соревновательный спуск II" +ITEM.description = "Улучшенный спортивный триггер второго поколения. Обеспечивает исключительную точность и минимальное усилие спуска." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_comp2" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_dual_uzis_semi.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_dual_uzis_semi.txt new file mode 100644 index 0000000..dac85a1 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_dual_uzis_semi.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "Двойные Узи полуавто" +ITEM.description = "Модификация триггера для парных Узи. Переводит оба пистолета-пулемета в полуавтоматический режим для лучшего контроля." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1500 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_dual_uzis_semi" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"Dual Uzis"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_dualstage.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_dualstage.lua new file mode 100644 index 0000000..b7f6951 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_dualstage.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Двухступенчатый спуск" +ITEM.description = "Двухступенчатый спусковой механизм для точного и контролируемого огня." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 750 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_dualstage" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_frcd.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_frcd.lua new file mode 100644 index 0000000..6f275e6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_frcd.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Принудительный сброс" +ITEM.description = "Механизм принудительного сброса спускового крючка. Позволяет вести почти автоматический огонь в полуавтоматическом режиме." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 900 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_frcd" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_frcd2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_frcd2.lua new file mode 100644 index 0000000..68f8e06 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_frcd2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Принудительный сброс II" +ITEM.description = "Улучшенный механизм принудительного сброса второго поколения. Еще более быстрый цикл сброса для максимальной скорострельности." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 950 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_frcd2" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_hair.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_hair.lua new file mode 100644 index 0000000..9ab3104 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_hair.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Чувствительный спуск" +ITEM.description = "Чувствительный спусковой механизм с минимальным усилием спуска." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 650 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_hair" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_hair_akimbo.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_hair_akimbo.lua new file mode 100644 index 0000000..4a46872 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_hair_akimbo.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Чувствительный спуск Акимбо" +ITEM.description = "Комбинация сверхлегкого триггера и режима акимбо. Два пистолета с минимальным усилием спуска для максимальной огневой мощи." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1600 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_hair_akimbo" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_heavy.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_heavy.lua new file mode 100644 index 0000000..6568888 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_heavy.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тяжёлый спуск" +ITEM.description = "Тяжелый спусковой механизм, улучшающий контроль над оружием." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_heavy" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_heavy2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_heavy2.lua new file mode 100644 index 0000000..a1657da --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_heavy2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тяжёлый спуск II" +ITEM.description = "Очень тяжелый триггер второго поколения. Максимальное усилие спуска для предотвращения случайных выстрелов." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_heavy2" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_semi.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_semi.lua new file mode 100644 index 0000000..a8a0453 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_semi.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Только полуавтомат" +ITEM.description = "Спусковой механизм, ограничивающий режим стрельбы только полуавтоматическим." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 400 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_semi" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_slam.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_slam.lua new file mode 100644 index 0000000..a6e23ac --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_slam.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Ударный огонь" +ITEM.description = "Механизм быстрой стрельбы, значительно увеличивающий скорострельность." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 800 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_slam" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_slam2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_slam2.lua new file mode 100644 index 0000000..9538598 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_slam2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Улучшенный ударный огонь" +ITEM.description = "Продвинутый механизм беспрерывного огня. Позволяет вести непрерывную стрельбу удерживая спусковой крючок и передергивая затвор." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 900 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_slam2" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_straight.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_straight.lua new file mode 100644 index 0000000..7fbe1a9 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_straight.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Прямой спуск" +ITEM.description = "Прямой спусковой крючок, повышающий эргономику оружия." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 450 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_straight" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_tactical.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_tactical.lua new file mode 100644 index 0000000..cec8c92 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_tactical.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Тактический спуск" +ITEM.description = "Тактический спусковой механизм, обеспечивающий универсальные характеристики." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 550 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_tactical" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_vp70_auto.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_vp70_auto.txt new file mode 100644 index 0000000..d6a53ce --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_vp70_auto.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "VP70 Авто спуск" +ITEM.description = "Автоматический триггер для VP70. Позволяет вести полностью автоматический огонь." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 1200 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_vp70_auto" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"VP70"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_vp70_semi.txt b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_vp70_semi.txt new file mode 100644 index 0000000..a567d99 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_vp70_semi.txt @@ -0,0 +1,10 @@ +ITEM.base = "base_attachments" +ITEM.name = "VP70 Полуавто спуск" +ITEM.description = "Полуавтоматический триггер для VP70. Повышает точность стрельбы одиночными выстрелами." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 600 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_vp70_semi" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" +ITEM.compatibleWeapons = {"VP70"} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_wide.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_wide.lua new file mode 100644 index 0000000..03e11f0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_wide.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Широкий спуск" +ITEM.description = "Широкий спусковой крючок, улучшающий стабильность стрельбы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 400 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_wide" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_wide2.lua b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_wide2.lua new file mode 100644 index 0000000..806981e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/attachments/sh_trigger_wide2.lua @@ -0,0 +1,9 @@ +ITEM.base = "base_attachments" +ITEM.name = "Широкий спуск Про" +ITEM.description = "Профессиональный широкий спусковой крючок. Увеличенная площадь контакта для более комфортной стрельбы." +ITEM.model = "models/tacint/props_containers/supply_case-2.mdl" +ITEM.price = 500 +ITEM.attachmentType = "trigger" +ITEM.tacRPAttachmentID = "trigger_wide2" +ITEM.tacRPSlot = "trigger" +ITEM.category = "Обвесы" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/base/sh_attachments.lua b/garrysmod/gamemodes/militaryrp/schema/items/base/sh_attachments.lua new file mode 100644 index 0000000..6f5f89d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/base/sh_attachments.lua @@ -0,0 +1,125 @@ +ITEM.name = "Обвес для оружия" +ITEM.description = "Модификация для оружия" +ITEM.model = "models/Items/BoxSRounds.mdl" +ITEM.category = "Обвесы" + +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 0 + +-- Тип обвеса: scope, suppressor, grip, laser, flashlight, underbarrel +ITEM.attachmentType = "scope" + +-- ID обвеса в TacRP (например: "reddot", "holo", "acog") +ITEM.tacRPAttachmentID = "" + +-- Слот для установки в TacRP +ITEM.tacRPSlot = "optic" -- optic, tactical, grip, muzzle + +-- Совместимые классы оружия (пусто = все оружие) +ITEM.compatibleWeapons = {} + +function ITEM:GetDescription() + local desc = self.description + + if self.attachmentType then + local typeNames = { + scope = "Прицел", + suppressor = "Глушитель", + grip = "Рукоятка", + laser = "ЛЦУ", + flashlight = "Фонарик", + underbarrel = "Подствольник" + } + desc = desc .. "\n\nТип: " .. (typeNames[self.attachmentType] or self.attachmentType) + end + + if self.tacRPSlot then + local slotNames = { + optic = "Оптика", + tactical = "Тактический", + grip = "Рукоятка", + muzzle = "Дульный" + } + desc = desc .. "\nСлот: " .. (slotNames[self.tacRPSlot] or self.tacRPSlot) + end + + return desc +end + +-- Проверка совместимости с оружием +function ITEM:IsCompatibleWith(weaponClass) + if not self.compatibleWeapons or #self.compatibleWeapons == 0 then + return true -- Совместимо со всем оружием + end + + for _, wepClass in ipairs(self.compatibleWeapons) do + if weaponClass == wepClass then + return true + end + end + + return false +end + +ITEM.functions.use = { + name = "Использовать", + tip = "Добавить обвес в инвентарь TacRP", + icon = "icon16/add.png", + OnCanRun = function(item) + local client = item.player + return (not IsValid(item.entity)) and IsValid(client) and client:GetCharacter() and item.invID != 0 + end, + OnRun = function(item) + local client = item.player + local attachmentID = item.tacRPAttachmentID + + if (CLIENT) then + return false + end + + if (not attachmentID or attachmentID == "") then + client:Notify("Обвес не имеет ID для использования!") + return false + end + + -- Даём обвес в TacRP инвентарь игрока + if (TacRP) then + -- Проверяем, есть ли уже такой обвес (если включена блокировка дубликатов) + if (TacRP.ConVars["lock_atts"] and TacRP.ConVars["lock_atts"]:GetBool() and TacRP:PlayerGetAtts(client, attachmentID) > 0) then + client:Notify("У вас уже есть этот обвес!") + return false -- Не удаляем предмет из инвентаря Helix, если он уже есть в TacRP + end + + local success = TacRP:PlayerGiveAtt(client, attachmentID, 1) + + -- TacRP:PlayerGiveAtt может вернуть nil если обвес уже есть, или true при успехе + if (success != false) then + TacRP:PlayerSendAttInv(client) + client:Notify("Обвес '" .. item.name .. "' добавлен в инвентарь TacRP") + + -- Логирование + local serverlogsPlugin = ix.plugin.list["serverlogs"] + if (serverlogsPlugin) then + local message = string.format("%s использовал обвес '%s' (ID: %s)", + client:GetName(), item.name, attachmentID) + serverlogsPlugin:AddLog("ATTACHMENT_USE", message, client, { + itemName = item.name, + attachmentID = attachmentID + }) + end + + return true -- Удаляем предмет + else + client:Notify("Не удалось добавить обвес (возможно, он бесплатный или только для админов)") + return false + end + else + client:Notify("TacRP не установлен на сервере!") + return false + end + end +} + +-- Запрет на выброс в мир (опционально, можно убрать) +-- ITEM.noDrop = true diff --git a/garrysmod/gamemodes/militaryrp/schema/items/base/sh_consumables.lua b/garrysmod/gamemodes/militaryrp/schema/items/base/sh_consumables.lua new file mode 100644 index 0000000..b6597c0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/base/sh_consumables.lua @@ -0,0 +1,95 @@ +ITEM.name = "Расходник" +ITEM.description = "Что-то съедобное" +ITEM.model = "models/kleiner.mdl" +ITEM.category = "Расходники" + +ITEM.width = 1 +ITEM.height = 1 +ITEM.price = 0 + +ITEM.alcohol = 0 +ITEM.hunger = 0 +ITEM.thirst = 0 +ITEM.empty = false +ITEM.quantity = 1 + +ITEM.weight = 0 +ITEM.flatweight = 0 + +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "" + +function ITEM:GetDescription() + local invdesc = "" + if self.longdesc then + invdesc = "\n\n"..(self.longdesc) + end + + if (self.entity) then + return (self.description) + else + return (self.description..invdesc) + end +end + +ITEM.functions.use = { + name = "Употребить", + icon = "icon16/cup.png", + + OnCanRun = function(item) + local client = item.player + return (not IsValid(item.entity)) and IsValid(client) and client:GetCharacter() and item.invID != 0 + end, + + OnRun = function(item) + local client = item.player + if not IsValid(client) then return false end + + client._nextConsumePress = client._nextConsumePress or 0 + if CurTime() < client._nextConsumePress then + return false + end + client._nextConsumePress = CurTime() + 5 + + local weaponClass = item.consumableWeapon or "ix_consumable" + client:Give(weaponClass) + + local weapon = client:GetWeapon(weaponClass) + if IsValid(weapon) then + local itemType = "consumable" + if (item.thirst or 0) > 0 and (item.hunger or 0) == 0 then + itemType = "drink" + elseif (item.hunger or 0) > 0 and (item.thirst or 0) == 0 then + itemType = "food" + end + + weapon:SetItemData( + item.hunger or 0, + item.thirst or 0, + item.alcohol or 0, + item.empty or false, + itemType, + item.consumableModel or "" + ) + + client:SelectWeapon(weaponClass) + end + + -- Удаляем предмет через небольшую задержку, чтобы анимация/оружие успели инициализироваться + timer.Simple(0.15, function() + if (item) then + item:Remove() + end + end) + + return false + end +} + +function ITEM:GetWeight() + return self.flatweight + self.weight +end + +function ITEM:GetPrice() + return self.price +end diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_bread.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_bread.lua new file mode 100644 index 0000000..01c792d --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_bread.lua @@ -0,0 +1,22 @@ +ITEM.base = "base_consumables" +ITEM.name = "Хлеб" +ITEM.desc = "Хороший и вкусный пшеничный хлеб. Пользуется спросом у обеих сторон конфликта" +ITEM.model = "models/weapons/sweps/stalker2/bread/w_item_bread.mdl" +ITEM.icon = Material("entities/weapon_stalker2_bread.png") + +ITEM.width = 1 +ITEM.height = 1 + +ITEM.hunger = 15 +ITEM.thirst = 0 +ITEM.price = 50 +ITEM.weight = 0.3 + +ITEM.iconCam = { + ang = Angle(-17.58, 250.8, 0), + fov = 25, + pos = Vector(57.1, 181.8, -60.7) +} + +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "models/weapons/sweps/stalker2/bread/v_item_bread.mdl" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_c4_ammo.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_c4_ammo.lua new file mode 100644 index 0000000..9981212 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_c4_ammo.lua @@ -0,0 +1,19 @@ +ITEM.name = "Заряд C4" +ITEM.desc = "Заряд взрывчатки C4. Требуется детонатор для использования.\nИспользуйте, чтобы получить 1 заряд." +ITEM.category = "Снаряжение" +ITEM.model = "models/weapons/tacint/c4_charge-1.mdl" +ITEM.width = 1 +ITEM.height = 1 + +ITEM.functions.use = { + name = "Использовать", + onRun = function(item) + local client = item.player + client:GiveAmmo(1, "ti_c4", true) + client:Notify("Вы получили 1 заряд C4.") + return true + end, + onCanRun = function(item) + return !IsValid(item.entity) + end +} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_c4_bundle.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_c4_bundle.lua new file mode 100644 index 0000000..a3228d5 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_c4_bundle.lua @@ -0,0 +1,44 @@ +ITEM.name = "Комплект C4" +ITEM.desc = "Набор, содержащий детонатор и 3 заряда C4. Распакуйте для получения содержимого." +ITEM.category = "Снаряжение" +ITEM.model = "models/items/box_stack.mdl" +ITEM.width = 1 +ITEM.height = 1 + +ITEM.functions.Unpack = { + name = "Распаковать", + onRun = function(item) + local client = item.player + local inventory = client:GetCharacter():GetInventory() + if not inventory or isnumber(inventory) then + client:Notify("Инвентарь еще не загрузился, подождите...") + return false + end + + local successDet = inventory:Add("c4_detonator") + if not successDet then + client:Notify("Нет места в инвентаре для детонатора!") + return false + end + + local count = 0 + for i = 1, 3 do + if inventory:Add("c4_ammo") then + count = count + 1 + else + break + end + end + + if count < 3 then + client:Notify("Заряды добавлены частично (" .. count .. "/3), не хватило места!") + else + client:Notify("Вы распаковали комплект C4.") + end + + return true + end, + onCanRun = function(item) + return !IsValid(item.entity) + end +} diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_canned.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_canned.lua new file mode 100644 index 0000000..fe21cff --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_canned.lua @@ -0,0 +1,21 @@ +ITEM.base = "base_consumables" +ITEM.name = "Консервы" +ITEM.desc = "Консервы,хоть и не первой свежести,но съедобные" +ITEM.model = "models/weapons/sweps/stalker2/canned/w_item_canned.mdl" +ITEM.icon = Material("entities/weapon_stalker2_canned.png") + +ITEM.width = 1 +ITEM.height = 1 +ITEM.iconCam = { + pos = Vector(0, 200, 0), + ang = Angle(0, 270, 0), + fov = 25 +} + +ITEM.hunger = 35 +ITEM.thirst = 0 +ITEM.price = 120 +ITEM.weight = 0.6 + +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "models/weapons/sweps/stalker2/canned/v_item_canned.mdl" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_energy.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_energy.lua new file mode 100644 index 0000000..8d421c6 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_energy.lua @@ -0,0 +1,22 @@ +ITEM.base = "base_consumables" +ITEM.name = "Энергетический напиток" +ITEM.desc = "Обладает усовершенствованной формулой из кофеина, таурина и витаминов C, B5, B6, B9 для уверенных штурмов" +ITEM.model = "models/weapons/sweps/stalker2/energy/w_item_energy.mdl" +ITEM.icon = Material("entities/weapon_stalker2_energy.png") + +ITEM.width = 1 +ITEM.height = 1 +ITEM.iconCam = { + pos = Vector(0, 200, 0), + ang = Angle(0, 270, 0), + fov = 25 +} + +ITEM.hunger = 0 +ITEM.thirst = 30 +ITEM.price = 100 +ITEM.weight = 0.4 + +ITEM.isDrink = true +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "models/weapons/sweps/stalker2/energy/v_item_energy.mdl" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_milk.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_milk.lua new file mode 100644 index 0000000..d18e297 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_milk.lua @@ -0,0 +1,21 @@ +ITEM.base = "base_consumables" +ITEM.name = "Сгущенное молоко" +ITEM.desc = "Сгущенное молоко белорусского производства" +ITEM.model = "models/weapons/sweps/stalker2/milk/w_item_milk.mdl" +ITEM.icon = Material("entities/weapon_stalker2_milk.png") + +ITEM.width = 1 +ITEM.height = 1 +ITEM.iconCam = { + pos = Vector(0, 200, 0), + ang = Angle(0, 270, 0), + fov = 25 +} + +ITEM.hunger = 30 +ITEM.thirst = 0 +ITEM.price = 150 +ITEM.weight = 0.5 + +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "models/weapons/sweps/stalker2/milk/v_item_milk.mdl" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_sausage.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_sausage.lua new file mode 100644 index 0000000..b39a231 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_sausage.lua @@ -0,0 +1,21 @@ +ITEM.base = "base_consumables" +ITEM.name = "Колбаса" +ITEM.desc = "Ключевое преимущество этой колбасы — её длительный срок хранения. Лучше не размышлять о том, как это достигается, чтобы не испортить аппетит себе и окружающим." +ITEM.model = "models/weapons/sweps/stalker2/sausage/w_item_sausage.mdl" +ITEM.icon = Material("entities/weapon_stalker2_sausage.png") + +ITEM.width = 1 +ITEM.height = 1 +ITEM.iconCam = { + pos = Vector(0, 200, 0), + ang = Angle(0, 270, 0), + fov = 25 +} + +ITEM.hunger = 25 +ITEM.thirst = 0 +ITEM.price = 100 +ITEM.weight = 0.4 + +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "models/weapons/sweps/stalker2/sausage/v_item_sausage.mdl" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_vodka.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_vodka.lua new file mode 100644 index 0000000..fda0249 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_vodka.lua @@ -0,0 +1,23 @@ +ITEM.base = "base_consumables" +ITEM.name = "Водка" +ITEM.desc = "Народный напиток каждого солдата на фронте" +ITEM.model = "models/weapons/sweps/stalker2/vodka/w_item_vodka.mdl" +ITEM.icon = Material("entities/weapon_stalker2_vodka.png") + +ITEM.width = 1 +ITEM.height = 1 +ITEM.iconCam = { + pos = Vector(0, 200, 0), + ang = Angle(0, 270, 0), + fov = 25 +} + +ITEM.hunger = 0 +ITEM.thirst = 15 +ITEM.alcohol = 20 +ITEM.price = 80 +ITEM.weight = 0.5 + +ITEM.isDrink = true +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "models/weapons/sweps/stalker2/vodka/v_item_vodka.mdl" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_water.lua b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_water.lua new file mode 100644 index 0000000..5789559 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/consumables/sh_water.lua @@ -0,0 +1,22 @@ +ITEM.base = "base_consumables" +ITEM.name = "Вода" +ITEM.desc = "Самая обычная вода из Макеевского родничка" +ITEM.model = "models/weapons/sweps/stalker2/water/w_item_water.mdl" +ITEM.icon = Material("entities/weapon_stalker2_water.png") + +ITEM.width = 1 +ITEM.height = 1 +ITEM.iconCam = { + pos = Vector(0, 200, 0), + ang = Angle(0, 270, 0), + fov = 25 +} + +ITEM.hunger = 0 +ITEM.thirst = 20 +ITEM.price = 30 +ITEM.weight = 0.5 + +ITEM.isDrink = true +ITEM.consumableWeapon = "ix_consumable" +ITEM.consumableModel = "models/weapons/sweps/stalker2/water/v_item_water.mdl" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_c4_detonator.lua b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_c4_detonator.lua new file mode 100644 index 0000000..562a30b --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_c4_detonator.lua @@ -0,0 +1,10 @@ +ITEM.name = "Детонатор C4" +ITEM.desc = "Устройство для дистанционного подрыва зарядов C4." +ITEM.category = "Снаряжение" +ITEM.model = "models/weapons/tacint/w_c4_det.mdl" +ITEM.class = "tacrp_c4_detonator" +ITEM.width = 1 +ITEM.height = 1 +ITEM.isWeapon = true +ITEM.weaponCategory = "equipment" +ITEM.base = "base_weapons" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone.lua b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone.lua new file mode 100644 index 0000000..4959daf --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone.lua @@ -0,0 +1,10 @@ +ITEM.name = "FPV дрон" +ITEM.desc = "Дрон-камикадзе для тактических операций." +ITEM.category = "Дроны" +ITEM.model = "models/murdered/weapons/drone_ex.mdl" +ITEM.class = "swep_drone" +ITEM.width = 1 +ITEM.height = 1 +ITEM.isWeapon = true +ITEM.weaponCategory = "drone" +ITEM.base = "base_weapons" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone_grenade.lua b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone_grenade.lua new file mode 100644 index 0000000..6b7fd0e --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone_grenade.lua @@ -0,0 +1,10 @@ +ITEM.name = "Дрон со сбросом" +ITEM.desc = "Дрон, предназначенный для сброса гранат и грузов." +ITEM.category = "Дроны" +ITEM.model = "models/murdered/weapons/drone_ex.mdl" +ITEM.class = "swep_drone_grenade" +ITEM.width = 1 +ITEM.height = 1 +ITEM.isWeapon = true +ITEM.weaponCategory = "drone" +ITEM.base = "base_weapons" diff --git a/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone_spy.lua b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone_spy.lua new file mode 100644 index 0000000..0705325 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/items/weapons/sh_swep_drone_spy.lua @@ -0,0 +1,10 @@ +ITEM.name = "Разведывательный дрон" +ITEM.desc = "Дрон, предназначенный для ведения наблюдения и разведки с воздуха." +ITEM.category = "Дроны" +ITEM.model = "models/murdered/weapons/drone_ex.mdl" +ITEM.class = "swep_drone_spy" +ITEM.width = 1 +ITEM.height = 1 +ITEM.isWeapon = true +ITEM.weaponCategory = "drone" +ITEM.base = "base_weapons" diff --git a/garrysmod/gamemodes/militaryrp/schema/languages/sh_english.lua b/garrysmod/gamemodes/militaryrp/schema/languages/sh_english.lua new file mode 100644 index 0000000..8492ca2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/languages/sh_english.lua @@ -0,0 +1,3 @@ +LANGUAGE = { + example = "Example!" +} diff --git a/garrysmod/gamemodes/militaryrp/schema/libs/thirdparty/sh_netstream2.lua b/garrysmod/gamemodes/militaryrp/schema/libs/thirdparty/sh_netstream2.lua new file mode 100644 index 0000000..0b991de --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/libs/thirdparty/sh_netstream2.lua @@ -0,0 +1,164 @@ +--[[ + NetStream - 2.0.0 + + Alexander Grist-Hucker + http://www.revotech.org + + Credits to: + thelastpenguin for pON. + https://github.com/thelastpenguin/gLUA-Library/tree/master/pON +--]] + + +AddCSLuaFile(); + +local _player = player + +netstream = netstream or {}; +netstream.stored = netstream.stored or {}; + +-- A function to split data for a data stream. +function netstream.Split(data) + local index = 1; + local result = {}; + local buffer = {}; + + for i = 0, string.len(data) do + buffer[#buffer + 1] = string.sub(data, i, i); + + if (#buffer == 32768) then + result[#result + 1] = table.concat(buffer); + index = index + 1; + buffer = {}; + end; + end; + + result[#result + 1] = table.concat(buffer); + + return result; +end; + +-- A function to hook a data stream. +function netstream.Hook(name, Callback) + netstream.stored[name] = Callback; +end; + +if (SERVER) then + util.AddNetworkString("NetStreamDS"); + + -- A function to start a net stream. + function netstream.Start(player, name, ...) + local recipients = {}; + local bShouldSend = false; + local bSendPVS = false; + + if (type(player) != "table") then + if (!player) then + player = _player.GetAll(); + elseif (type(player) == "Vector") then + bSendPVS = true; + else + player = {player}; + end; + end; + + if (type(player) != "Vector") then + for k, v in pairs(player) do + if (type(v) == "Player") then + recipients[#recipients + 1] = v; + + bShouldSend = true; + elseif (type(k) == "Player") then + recipients[#recipients + 1] = k; + + bShouldSend = true; + end; + end; + else + bShouldSend = true; + end; + + local dataTable = {...}; + local encodedData = pon.encode(dataTable); + + if (encodedData and #encodedData > 0 and bShouldSend) then + net.Start("NetStreamDS"); + net.WriteString(name); + net.WriteUInt(#encodedData, 32); + net.WriteData(encodedData, #encodedData); + if (bSendPVS) then + net.SendPVS(player); + else + net.Send(recipients); + end; + end; + end; + + net.Receive("NetStreamDS", function(length, player) + local NS_DS_NAME = net.ReadString(); + local NS_DS_LENGTH = net.ReadUInt(32); + local NS_DS_DATA = net.ReadData(NS_DS_LENGTH); + + if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then + player.nsDataStreamName = NS_DS_NAME; + player.nsDataStreamData = ""; + + if (player.nsDataStreamName and player.nsDataStreamData) then + player.nsDataStreamData = NS_DS_DATA; + + if (netstream.stored[player.nsDataStreamName]) then + local bStatus, value = pcall(pon.decode, player.nsDataStreamData); + + if (bStatus) then + netstream.stored[player.nsDataStreamName](player, unpack(value)); + else + ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n"); + end; + else + ErrorNoHalt("NetStream: Undefined hook for '"..NS_DS_NAME.."'\n") + end; + + player.nsDataStreamName = nil; + player.nsDataStreamData = nil; + end; + end; + + NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil; + end); +else + -- A function to start a net stream. + function netstream.Start(name, ...) + local dataTable = {...}; + local encodedData = pon.encode(dataTable); + + if (encodedData and #encodedData > 0) then + net.Start("NetStreamDS"); + net.WriteString(name); + net.WriteUInt(#encodedData, 32); + net.WriteData(encodedData, #encodedData); + net.SendToServer(); + end; + end; + + net.Receive("NetStreamDS", function(length) + local NS_DS_NAME = net.ReadString(); + local NS_DS_LENGTH = net.ReadUInt(32); + local NS_DS_DATA = net.ReadData(NS_DS_LENGTH); + + if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then + if (netstream.stored[NS_DS_NAME]) then + local bStatus, value = pcall(pon.decode, NS_DS_DATA); + + if (bStatus) then + netstream.stored[NS_DS_NAME](unpack(value)); + else + ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n"); + end; + else + ErrorNoHalt("NetSteam: Undefined hook for '"..NS_DS_NAME.."'\n") + end; + end; + + NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil; + end); +end; diff --git a/garrysmod/gamemodes/militaryrp/schema/meta/sh_character.lua b/garrysmod/gamemodes/militaryrp/schema/meta/sh_character.lua new file mode 100644 index 0000000..e2d7ed7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/meta/sh_character.lua @@ -0,0 +1,5 @@ +local CHAR = ix.meta.character + +function CHAR:Tip(message) + ix.util.Notify(self:GetPlayer(), message) +end diff --git a/garrysmod/gamemodes/militaryrp/schema/meta/sh_player.lua b/garrysmod/gamemodes/militaryrp/schema/meta/sh_player.lua new file mode 100644 index 0000000..c1830a2 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/meta/sh_player.lua @@ -0,0 +1 @@ +local PLAYER = FindMetaTable("Player") diff --git a/garrysmod/gamemodes/militaryrp/schema/sh_hooks.lua b/garrysmod/gamemodes/militaryrp/schema/sh_hooks.lua new file mode 100644 index 0000000..d2bc9f7 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/sh_hooks.lua @@ -0,0 +1,3 @@ +function Schema:CanPlayerUseBusiness(client, uniqueID) + return false +end diff --git a/garrysmod/gamemodes/militaryrp/schema/sh_schema.lua b/garrysmod/gamemodes/militaryrp/schema/sh_schema.lua new file mode 100644 index 0000000..27bc5c3 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/sh_schema.lua @@ -0,0 +1,88 @@ + +Schema.name = "MilitaryRP" +Schema.author = "RefoselTeamWork" +Schema.description = "" + +ix.util.Include("cl_schema.lua") +ix.util.Include("sv_schema.lua") + +ix.util.Include("cl_hooks.lua") +ix.util.Include("sh_hooks.lua") +ix.util.Include("sh_sounds.lua") +ix.util.Include("sv_hooks.lua") +ix.util.Include("sv_workshop.lua") +ix.util.Include("libs/thirdparty/sh_netstream2.lua") + +RespawnTime = { + ["superadmin"] = 10, + ["teh.admin"] = 10, + ["projectteam"] = 20, + ["curator"] = 20, + ["disp"] = 20, + ["sudo-curator"] = 20, + ["assistant"] = 20, + ["st.admin"] = 20, + ["admin"] = 30, + ["st.event"] = 20, + ["event"] = 30, + ["ivent"] = 20, + ["media"] = 50, + ["sponsor"] = 20, + ["vip"] = 50, + ["user"] = 60 +} + + +AdminPrivs = { + ["superadmin"] = true, + ["teh.admin"] = true, + ["projectteam"] = true, + ["curator"] = true, + ["disp"] = true, + ["sudo-curator"] = true, + ["assistant"] = true, + ["st.admin"] = true, + ["admin"] = true, + ["st.event"] = true, + ["event"] = true, + ["ivent"] = true +} + +SuperAdminPrivs = { + ["superadmin"] = true, + ["teh.admin"] = true, + ["ivent"] = true +} + +ix.config.SetDefault("adminRanks", "superadmin,admin,curator,sudo-curator,teh.admin,projectteam,st.admin,specadmin,asist-sudo,ivent,st.event,event,disp,assistant") + + +local models = { + ["models/player/group01/male_04.mdl"] = true, + ["models/arma3/rf/soldier_belarus.mdl"] = true, + ["models/arma3/russia/soldier.mdl"] = true, + ["models/bibas/prigozin/wagner_company.mdl"] = true, + ["models/bibas/uk_pilot/pilot.mdl"] = true, + ["models/ft/soldier vsu.mdl"] = true, + ["models/knyaje pack/militarypolice_rf/militarypolice_rf_emrs.mdl"] = true, + ["models/welding/rf/soldier_rf.mdl"] = true, + ["models/2021s_devgru/2021s_devgru_bluesquadron_op1_pm.mdl"] = true, + ["models/ftmodel/murom_soldier/sso_soldier.mdl"] = true, + ["models/player/male_gk_swe.mdl"] = true, + ["models/skynet/operator_skynet/scp_tactical_model_operator_skynet.mdl"] = true, + ["models/sso_ru_asset_xyi/char/sso_character_g3_btop3_pm.mdl"] = true, + ["models/lucie/sfod/sfod_operator_mask.mdl"] = true, + ["models/2021s_devgru/2021s_devgru_bluesquadron_op1_pm.mdl"] = true, + ["models/player/male_gk_swe.mdl"] = true, + ["models/knyaje pack/fsb_rosn/fsb_rosn.mdl"] = true, + ["models/sbu/securityserviceukraine.mdl"] = true, + ["models/knyaje pack/generals/general_genshtab.mdl"] = true, + ["models/Knyaje Pack/generals/General_VDV.mdl"] = true, + ["models/nb/wizard/SSO_RU_Syria/sso_pm.mdl"] = true, + ["models/kalinouskaha/operation/operation_kalinouskaha.mdl"] = true, +} + +for k, v in pairs(models) do + ix.anim.SetModelClass(k, "player") + util.PrecacheModel(k) +end diff --git a/garrysmod/gamemodes/militaryrp/schema/sh_sounds.lua b/garrysmod/gamemodes/militaryrp/schema/sh_sounds.lua new file mode 100644 index 0000000..6112be0 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/sh_sounds.lua @@ -0,0 +1,47 @@ +-- Sound aliases для STALKER 2 Consumables + +sound.Add({ + name = "Stalker2.Drink", + channel = CHAN_ITEM, + volume = 0.7, + level = 60, + pitch = {95, 105}, + sound = { + "weapons/stalker2/drink/sfx_consumables_common_drink_02.mp3", + "weapons/stalker2/drink/sfx_consumables_common_drink_03.mp3", + "weapons/stalker2/drink/sfx_consumables_common_drink_04.mp3", + "weapons/stalker2/drink/sfx_consumables_common_drink_05.mp3", + "weapons/stalker2/drink/sfx_consumables_common_drink_06.mp3", + "weapons/stalker2/drink/sfx_consumables_common_drink_07.mp3", + "weapons/stalker2/drink/sfx_consumables_common_drink_08.mp3" + } +}) + +sound.Add({ + name = "Stalker2.Eat", + channel = CHAN_ITEM, + volume = 0.7, + level = 60, + pitch = {95, 105}, + sound = { + "weapons/stalker2/bread/sfx_consumables_bread_bite_01.mp3", + "weapons/stalker2/bread/sfx_consumables_bread_bite_02.mp3", + "weapons/stalker2/bread/sfx_consumables_bread_bite_03.mp3", + "weapons/stalker2/bread/sfx_consumables_bread_bite_04.mp3", + "weapons/stalker2/bread/sfx_consumables_bread_bite_05.mp3", + "weapons/stalker2/bread/sfx_consumables_bread_bite_06.mp3" + } +}) + +sound.Add({ + name = "Stalker2.Healing", + channel = CHAN_ITEM, + volume = 0.7, + level = 60, + pitch = {98, 102}, + sound = { + "weapons/stalker2/medkit/sfx_medkit_healing_1.ogg", + "weapons/stalker2/medkit/sfx_medkit_healing_2.ogg", + "weapons/stalker2/medkit/sfx_medkit_healing_3.ogg" + } +}) diff --git a/garrysmod/gamemodes/militaryrp/schema/sv_hooks.lua b/garrysmod/gamemodes/militaryrp/schema/sv_hooks.lua new file mode 100644 index 0000000..8173039 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/sv_hooks.lua @@ -0,0 +1,165 @@ +local deathSounds = { + Sound("vo/npc/male01/pain07.wav"), + Sound("vo/npc/male01/pain08.wav"), + Sound("vo/npc/male01/pain09.wav") +} + +-- Кастомные спавны по spec_def подразделений +-- Helix ставит позицию игрока внутри хука PlayerLoadout (spawns.lua) +-- Арсенал-плагин вызывает PostPlayerLoadout после полной загрузки персонажа +-- Поэтому перехватываем PostPlayerLoadout чтобы перебить позицию Helix'а + +-- Поиск свободной точки рядом с базовой, чтобы игроки не спавнились друг в друге +local function FindClearSpawnPos(basePos) + -- Сначала проверяем саму базовую точку + local isClear = true + for _, ply in ipairs(player.GetAll()) do + if ply:Alive() and ply:GetPos():DistToSqr(basePos) < 2500 then -- 50^2 + isClear = false + break + end + end + if isClear then return basePos end + + -- Если занята — ищем свободную позицию по кругу вокруг базовой точки + local offsets = {60, 120, 180} + for _, radius in ipairs(offsets) do + for angle = 0, 315, 45 do + local rad = math.rad(angle) + local testPos = basePos + Vector(math.cos(rad) * radius, math.sin(rad) * radius, 0) + + local clear = true + for _, ply in ipairs(player.GetAll()) do + if ply:Alive() and ply:GetPos():DistToSqr(testPos) < 2500 then + clear = false + break + end + end + + if clear then return testPos end + end + end + + -- Если вообще всё занято — спавним на базовой точке + return basePos +end + +hook.Add("DoPlayerDeath", "YaEbal", function(client, attacker, damageinfo) + -- Даем другим хукам (MedicMod) возможность сработать + client:AddDeaths(1) +end) + +hook.Add("PlayerDeath", "YaEbal", function(client, inflictor, attacker) + -- Мы перенесли основную логику в плагин death_screen + -- Просто логируем смерть + + local character = client:GetCharacter() + + if (character) then + if (IsValid(client.ixRagdoll)) then + client.ixRagdoll.ixIgnoreDelete = true + client:SetLocalVar("blur", nil) + + if (hook.Run("ShouldRemoveRagdollOnDeath", client) != false) then + client.ixRagdoll:Remove() + end + end + +--[[ + -- Отправка экрана смерти Murder Drones + if ix.config.Get("deathScreenEnabled") then + client.ixDeathTime = RealTime() + + local killerName = "Неизвестно" + if IsValid(attacker) and attacker:IsPlayer() then + killerName = attacker:GetName() + elseif IsValid(attacker) then + killerName = attacker:GetClass() + end + + net.Start("ixDeathScreen") + net.WriteFloat(RespawnTime[client:GetUserGroup()]) + net.WriteString(killerName) + net.Send(client) + end +]] + + -- client:SetNetVar("deathStartTime", CurTime()) + -- client:SetNetVar("deathTime", CurTime() + RespawnTime[client:GetUserGroup()]) + + character:SetData("health", nil) + + -- Звук смерти (с учетом настройки экрана смерти) + if not ix.config.Get("deathScreenDisableSound") then + local deathSound = hook.Run("GetPlayerDeathSound", client) + + if (deathSound != false) then + deathSound = deathSound or deathSounds[math.random(1, #deathSounds)] + + if (client:IsFemale() and !deathSound:find("female")) then + deathSound = deathSound:gsub("male", "female") + end + + client:EmitSound(deathSound) + end + end + + local weapon = IsValid(attacker) and attacker:IsPlayer() and attacker:GetActiveWeapon() + local attackerName = IsValid(attacker) and (attacker:IsPlayer() and attacker:GetName() or attacker:GetClass()) or "Unknown" + + ix.log.Add(client, "playerDeath", attackerName, IsValid(weapon) and weapon:GetClass()) + end +end) + +-- Блокировка возрождения на время показа экрана смерти +hook.Add("PlayerDeathThink", "ixDeathScreenBlock", function(client) + if ix.config.Get("deathScreenEnabled") then + local duration = RespawnTime[client:GetUserGroup()] or 60 + if (client.ixDeathTime or 0) + duration > RealTime() then + return false + end + end +end) + +-- Ограничение спавна различных элементов из Q-меню только для суперадминов +function Schema:PlayerSpawnSENT(client, class) + local userGroup = string.lower(client:GetUserGroup() or "user") + if not (userGroup == "superadmin" or AdminPrivs[userGroup]) then + client:Notify("Спавн энтити доступен только администрации!") + return false + end +end + +function Schema:PlayerSpawnSWEP(client, class, info) + local userGroup = string.lower(client:GetUserGroup() or "user") + if not (client:IsSuperAdmin() or AdminPrivs[userGroup]) then + client:Notify("Выдача оружия из Q-меню доступна только администрации!") + return false + end +end + +function Schema:PlayerSpawnNPC(client, class, weapon) + local userGroup = string.lower(client:GetUserGroup() or "user") + if not (client:IsSuperAdmin() or AdminPrivs[userGroup]) then + client:Notify("Спавн NPC доступен только администрации!") + return false + end +end + +function Schema:PlayerSpawnVehicle(client, class, name, table) + local userGroup = string.lower(client:GetUserGroup() or "user") + if not (client:IsSuperAdmin() or AdminPrivs[userGroup]) then + client:Notify("Спавн машин из Q-меню доступен только администрации!") + return false + end +end + +function Schema:PlayerSpawnProp(client, model) + local userGroup = string.lower(client:GetUserGroup() or "user") + if (client:IsSuperAdmin() or AdminPrivs[userGroup]) then + return true + end + + client:Notify("Спавн пропов доступен только администрации!") + return false +end diff --git a/garrysmod/gamemodes/militaryrp/schema/sv_schema.lua b/garrysmod/gamemodes/militaryrp/schema/sv_schema.lua new file mode 100644 index 0000000..47b6d38 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/sv_schema.lua @@ -0,0 +1 @@ +-- Logic moved to kill_rewards plugin diff --git a/garrysmod/gamemodes/militaryrp/schema/sv_workshop.lua b/garrysmod/gamemodes/militaryrp/schema/sv_workshop.lua new file mode 100644 index 0000000..032e852 --- /dev/null +++ b/garrysmod/gamemodes/militaryrp/schema/sv_workshop.lua @@ -0,0 +1,24 @@ +resource.AddWorkshop( "3622791102" ) -- Map +resource.AddWorkshop( "3622797401" ) -- Map2 +resource.AddWorkshop( "3654520063" ) -- FT VnU | Other Content +resource.AddWorkshop( "3617042718" ) -- FT | Vehicles Content #3 +resource.AddWorkshop( "3654538417" ) -- FT VnU | Tools Content +resource.AddWorkshop( "3385235497" ) -- FT | Vehicles Content #2 +resource.AddWorkshop( "2917050373" ) -- [wOS] DynaBase Legacy Extensions +resource.AddWorkshop( "2916561591" ) -- [wOS] DynaBase - Dynamic Animation Manager +resource.AddWorkshop( "3661221985" ) -- FT | SW CONTENT +resource.AddWorkshop( "3661183967" ) -- FT | Weapons content +resource.AddWorkshop( "3647165910" ) -- FT Test +resource.AddWorkshop( "3675152067" ) -- FT VnU | UI Content +resource.AddWorkshop( "3661561300" ) -- FT | Игровые модели +resource.AddWorkshop( "3661563066" ) -- FT | Игровые модели донат +resource.AddWorkshop( "761228248" ) -- Realistic Handcuffs Content +resource.AddWorkshop( "1323026111" ) -- 3D VoiceChat for ANY gamemode +resource.AddWorkshop( "2919093249" ) -- [wOS DynaBase] Mixamo Movement Animations Extension +resource.AddWorkshop( "2912816023" ) -- [LVS] - Framework +resource.AddWorkshop( "3680652464" ) -- FT VnU | SWEPS Content +resource.AddWorkshop( "3680654214" ) -- FT VnU | Animation Content +resource.AddWorkshop( "3141624788" ) -- Гитара2 +resource.AddWorkshop( "2786390227" ) -- Командующие Минобороны / ГенШтаб; ВДВ; ВКС; ВМФ +resource.AddWorkshop( "3681336394" ) -- Наручники +resource.AddWorkshop( "3683515841" ) -- FT VnU | Prop Content diff --git a/garrysmod/lua/autorun/binder_init.lua b/garrysmod/lua/autorun/binder_init.lua new file mode 100644 index 0000000..b51eeeb --- /dev/null +++ b/garrysmod/lua/autorun/binder_init.lua @@ -0,0 +1,18 @@ +-- Garry's Mod Binder - Entry Point +AddCSLuaFile("binder/sh_lang.lua") +AddCSLuaFile("binder/core/sh_config.lua") +AddCSLuaFile("binder/cl_init.lua") +AddCSLuaFile("binder/vgui/cl_frame.lua") +AddCSLuaFile("binder/vgui/cl_profiles_tab.lua") +AddCSLuaFile("binder/vgui/cl_keybind_tab.lua") +AddCSLuaFile("binder/vgui/cl_radial_tab.lua") +AddCSLuaFile("binder/vgui/cl_radial_hud.lua") +AddCSLuaFile("binder/vgui/cl_settings_tab.lua") + +if SERVER then + include("binder/sv_init.lua") + print("[Binder] Server loaded") +else + include("binder/cl_init.lua") + print("[Binder] Client loaded") +end \ No newline at end of file diff --git a/garrysmod/lua/autorun/realistic_police_helix_sync.lua b/garrysmod/lua/autorun/realistic_police_helix_sync.lua new file mode 100644 index 0000000..30de4b8 --- /dev/null +++ b/garrysmod/lua/autorun/realistic_police_helix_sync.lua @@ -0,0 +1,135 @@ + +-- Realistic Police Helix Sync +-- Created to restrict computer to FSB and sync wanted status with Military ID + +local FSB_PODR_ID = 6 + +Realistic_Police = Realistic_Police or {} + +-- New helper to get the job name, respecting Helix units (podrazdeneniya) +function Realistic_Police.GetPlayerJob(ply) + if not IsValid(ply) then return "" end + local char = ply.GetCharacter and ply:GetCharacter() + if not char then return team.GetName(ply:Team()) end + + -- In this server, if the faction is FACTION_RUSSIAN, we check the unit ID + if char:GetFaction() == (FACTION_RUSSIAN or -1) then + local podrId = char:GetPodr() + if podrId == FSB_PODR_ID then + return "ФСБ «Вымпел»" -- Note: must match config exactly (2 spaces) + end + end + + return team.GetName(ply:Team()) +end + +if SERVER then + local plyMeta = FindMetaTable("Player") + + -- Define wanted status for players in Helix + function plyMeta:isWanted() + local char = self:GetCharacter() + if not char then return false end + return char:GetData("wanted", false) + end + + function plyMeta:wanted(actor, reason, duration) + local char = self:GetCharacter() + if not char then return end + char:SetData("wanted", true) + char:SetData("wanted_reason", reason or "Розыск ФСБ") + + -- Custom long-term wanted logic: we ignore duration and keep it until manual removal + net.Start("RealisticPolice:Open") + net.WriteString("Notify") + net.WriteString(self:Name() .. " теперь находится в розыске!") + net.Broadcast() + end + + function plyMeta:unWanted(actor) + local char = self:GetCharacter() + if not char then return end + char:SetData("wanted", false) + char:SetData("wanted_reason", nil) + + net.Start("RealisticPolice:Open") + net.WriteString("Notify") + net.WriteString(self:Name() .. " снят с розыска.") + net.Broadcast() + end + + -- Hook into say commands if the police addon uses them + hook.Add("PlayerSay", "RealisticPolice_WantedCommands", function(ply, text) + local lowText = string.lower(text) + local isWanted = string.StartWith(lowText, "/wanted") or string.StartWith(lowText, "!wanted") + local isUnwanted = string.StartWith(lowText, "/unwanted") or string.StartWith(lowText, "!unwanted") + + if isWanted or isUnwanted then + -- Permission check + local job = Realistic_Police.GetPlayerJob(ply) + if not Realistic_Police.OpenComputer[job] and not Realistic_Police.AdminRank[ply:GetUserGroup()] then + return + end + + -- Parse Name and Reason + local cmd = isWanted and (string.StartWith(lowText, "/wanted") and "/wanted" or "!wanted") or (string.StartWith(lowText, "/unwanted") and "/unwanted" or "!unwanted") + local fullParams = string.Trim(string.sub(text, #cmd + 1)) + + local target = nil + local reason = "" + + -- Find the player whose name matches the start of fullParams + for _, v in pairs(player.GetAll()) do + local name = v:Name() + if string.StartWith(fullParams, name) then + -- If multiple players match (e.g. "Ivan" and "Ivan Ivanov"), we want the longest match + if not target or #name > #target:Name() then + target = v + reason = string.Trim(string.sub(fullParams, #name + 1)) + end + end + end + + if target then + if isWanted then + target:wanted(ply, reason) + else + target:unWanted(ply) + end + return "" -- Hide from chat + end + end + end) + + -- Robust server-side check for opening the computer + hook.Add("PlayerUse", "RealisticPolice_FSBRestrict", function(ply, ent) + if IsValid(ent) and ent:GetClass() == "realistic_police_computer" then + local job = Realistic_Police.GetPlayerJob(ply) + if not Realistic_Police.OpenComputer[job] and not Realistic_Police.AdminRank[ply:GetUserGroup()] then + if not (Realistic_Police.HackerJob and Realistic_Police.HackerJob[job]) then + Realistic_Police.SendNotify(ply, "Доступ к компьютеру разрешен только подразделению ФСБ!") + return false + end + end + end + end) +end + +if CLIENT then + -- Override the computer open check to restrict to FSB + timer.Simple(1, function() + if not Realistic_Police then return end + + local oldOpenMenu = Realistic_Police.OpenMenu + Realistic_Police.OpenMenu = function(ent, bool) + local job = Realistic_Police.GetPlayerJob(LocalPlayer()) + if not Realistic_Police.OpenComputer[job] and not Realistic_Police.AdminRank[LocalPlayer():GetUserGroup()] then + if not (Realistic_Police.HackerJob and Realistic_Police.HackerJob[job]) then + RealisticPoliceNotify("Доступ запрещен. Только для подразделения ФСБ.") + return + end + end + oldOpenMenu(ent, bool) + end + end) +end diff --git a/garrysmod/lua/autorun/server/sv_admin_physics_fix.lua b/garrysmod/lua/autorun/server/sv_admin_physics_fix.lua new file mode 100644 index 0000000..d5f6f45 --- /dev/null +++ b/garrysmod/lua/autorun/server/sv_admin_physics_fix.lua @@ -0,0 +1,77 @@ +if not SERVER then return end + +-- Таблица групп, которым разрешён административный доступ к Physgun +local AdminGroups = { + ["superadmin"] = true, + ["super admin"] = true, + ["owner"] = true, + ["curator"] = true, + ["sudo-curator"] = true, + ["asist-sudo"] = true, + ["admin"] = true, + ["st.admin"] = true, + ["projectteam"] = true, + ["teh.admin"] = true, + ["ivent"] = true, + ["st.event"] = true, + ["event"] = true, + ["specadmin"] = true, + ["assistant"] = true, + ["disp"] = true, +} + +-- Хук для Physgun: позволяем администраторам поднимать энтити и игроков +hook.Add("PhysgunPickup", "!!!!Admin_PhysgunPickup_Fix", function(ply, ent) + if not IsValid(ply) then return end + + local group = ply:GetUserGroup() + if AdminGroups[group] then + -- Если это игрок, SAM уже имеет свою логику, но мы подтверждаем наше разрешение + if ent:IsPlayer() then + -- Запрещаем поднимать суперадминов если мы просто "админ" (опционально) + -- if group == "admin" and ent:IsSuperAdmin() then return false end + return true + end + + -- Для всех остальных энтитей — разрешаем + return true + end +end) + +-- Хук для ToolGun: позволяем администраторам использовать инструменты на защищённых объектах +hook.Add("CanTool", "!!!!Admin_CanTool_Fix", function(ply, tr, tool) + if IsValid(ply) and AdminGroups[ply:GetUserGroup()] then + return true + end +end) + +-- Хук для Properties (ПКМ меню): позволяем администраторам видеть всё +hook.Add("CanProperty", "!!!!Admin_CanProperty_Fix", function(ply, property, ent) + if IsValid(ply) and AdminGroups[ply:GetUserGroup()] then + return true + end +end) + +-- Хук для Unfreeze: позволяем администраторам размораживать объекты +hook.Add("CanPlayerUnfreeze", "!!!!Admin_CanPlayerUnfreeze_Fix", function(ply, ent, phys) + if IsValid(ply) and AdminGroups[ply:GetUserGroup()] then + return true + end +end) + +-- Принудительная регистрация привилегий в CAMI для Helix и SAM +hook.Add("InitPostEntity", "!!!!Admin_CAMI_Permissions_Fix", function() + if CAMI then + CAMI.RegisterPrivilege({ + Name = "Helix - Bypass Prop Protection", + MinAccess = "admin" + }) + + -- Для SAM + if SAM_LOADED and sam then + sam.permissions.add("can_physgun_players", nil, "admin") + end + end +end) + +print("[ADMIN FIX] Physics and Prop Protection bypass for 'admin' and 'curator' groups loaded.") diff --git a/garrysmod/lua/autorun/server/sv_disable_sprays.lua b/garrysmod/lua/autorun/server/sv_disable_sprays.lua new file mode 100644 index 0000000..9d4e85d --- /dev/null +++ b/garrysmod/lua/autorun/server/sv_disable_sprays.lua @@ -0,0 +1,5 @@ +-- Script to globally disable player sprays + +hook.Add("PlayerSpray", "DisablePlayerSprays", function(ply) + return true -- Returning true prevents the spray +end) diff --git a/garrysmod/lua/autorun/server/sv_hostname_force.lua b/garrysmod/lua/autorun/server/sv_hostname_force.lua new file mode 100644 index 0000000..229722a --- /dev/null +++ b/garrysmod/lua/autorun/server/sv_hostname_force.lua @@ -0,0 +1,17 @@ +local ForceHostname = "FT 4.0 | Война на Украине | ВАЙП" + +local function EnforceHostname() + if GetConVar("hostname"):GetString() ~= ForceHostname then + RunConsoleCommand("hostname", ForceHostname) + end +end + +hook.Add("Think", "ForceHostnameAggressive", function() + EnforceHostname() +end) + +timer.Create("ForceHostnameTimer", 0.1, 0, function() + EnforceHostname() +end) + +EnforceHostname() diff --git a/garrysmod/lua/autorun/server/sv_lvs_health_entry_fix.lua b/garrysmod/lua/autorun/server/sv_lvs_health_entry_fix.lua new file mode 100644 index 0000000..4874a3b --- /dev/null +++ b/garrysmod/lua/autorun/server/sv_lvs_health_entry_fix.lua @@ -0,0 +1,35 @@ +if not SERVER then return end + +-- MRP Fix: Disable entering LVS vehicles if health is below 0 or destroyed +hook.Add( "LVS.CanPlayerDrive", "MRP_LVS_HealthFix", function( ply, vehicle ) + if not IsValid( vehicle ) then return end + + -- Check if vehicle is destroyed or has negative health + if vehicle.IsDestroyed and vehicle:IsDestroyed() then + if not ply:IsAdmin() then + ply:Notify("Техника уничтожена и не может управляться!") + return false + end + end + + if vehicle.GetHP and vehicle:GetHP() <= 0 then + if not ply:IsAdmin() then + ply:Notify("Техника слишком сильно повреждена для эксплуатации!") + return false + end + end +end ) + +-- Prevents sitting in passenger seats of destroyed LVS vehicles +hook.Add( "CanPlayerEnterVehicle", "MRP_LVS_SeatSafety", function( ply, vehicle ) + local lvsVehicle = vehicle.lvsGetVehicle and vehicle:lvsGetVehicle() or vehicle:GetParent() + + if IsValid( lvsVehicle ) and lvsVehicle.LVS then + if (lvsVehicle.IsDestroyed and lvsVehicle:IsDestroyed()) or (lvsVehicle.GetHP and lvsVehicle:GetHP() <= 0) then + if not ply:IsAdmin() then + ply:Notify("Техника уничтожена!") + return false + end + end + end +end ) diff --git a/garrysmod/lua/autorun/server/sv_lvs_missile_fix.lua b/garrysmod/lua/autorun/server/sv_lvs_missile_fix.lua new file mode 100644 index 0000000..89b009b --- /dev/null +++ b/garrysmod/lua/autorun/server/sv_lvs_missile_fix.lua @@ -0,0 +1,65 @@ +-- Фикс краша IVP от lvs_missile вылетающих за пределы карты +-- IVP Failed at ivu_vhash.cxx 157 — физдвижок падает когда ракета улетает за map boundary +-- Решение: отслеживаем lvs_missile и удаляем их до краша при выходе за пределы + +if not SERVER then return end + +-- Получаем границы карты при старте +local mapMin, mapMax + +hook.Add("InitPostEntity", "MRP_LVSMissileSafetyInit", function() + local wMin, wMax = game.GetWorld():GetModelBounds() + + -- Fallback for weird maps + if not wMin or wMin == vector_origin then + wMin = Vector(-16384, -16384, -16384) + wMax = Vector(16383, 16383, 16383) + end + + -- margin 1000 instead of 2000 to be safer on small maps + local margin = Vector(1000, 1000, 1000) + mapMin = wMin + margin + mapMax = wMax - margin + + print("[MRP] LVS Missile Safety initialized. Bounds: " .. tostring(mapMin) .. " -> " .. tostring(mapMax)) +end) + +-- Таймер мониторинга, не хук Think (чтобы не перегружать каждый тик) +timer.Create("MRP_LVSMissileWatchdog", 0.1, 0, function() + if not mapMin then return end + + for _, ent in ipairs(ents.FindByClass("lvs_missile")) do + if not IsValid(ent) then continue end + + -- ЗАЩИТА 1: Не удаляем ракеты которые еще не инициализированы через Enable() + if not ent.IsEnabled or not ent.SpawnTime then + continue + end + + local pos = ent:GetPos() + local age = CurTime() - ent.SpawnTime + + -- ЗАЩИТА 2: Не удаляем ракеты моложе 1 сек (время на полную инициализацию) + if age < 1 then + continue + end + + -- Проверка 1: вышла за границы карты + local outOfBounds = pos.x < mapMin.x or pos.x > mapMax.x + or pos.y < mapMin.y or pos.y > mapMax.y + or pos.z < mapMin.z or pos.z > mapMax.z + + -- Проверка 2: координаты явно безумные (> 15000 = за пределами любой карты GMod) + local crazyOrigin = pos:Length() > 15000 + + if outOfBounds or crazyOrigin then + -- Безопасное удаление через SafeRemoveEntityDelayed + -- НЕ вызываем Detonate() — это создаст взрыв в рандомной точке + if ent.IsDetonated then continue end + ent.IsDetonated = true + + print("[Debug] Watchdog: Removing OOB missile age=" .. string.format("%.2f", age) .. "s at " .. tostring(pos)) + SafeRemoveEntityDelayed(ent, 0) + end + end +end) diff --git a/garrysmod/lua/autorun/server/sv_spawn_protection.lua b/garrysmod/lua/autorun/server/sv_spawn_protection.lua new file mode 100644 index 0000000..1beeeb0 --- /dev/null +++ b/garrysmod/lua/autorun/server/sv_spawn_protection.lua @@ -0,0 +1,80 @@ +-- Script to prevent players from spawning inside each other +-- Especially useful for MilitaryRP/Helix where many players spawn simultaneously + +local UNSTUCK_TIMEOUT = 5 -- How many seconds we consider for unstuck logic +local UNSTUCK_SEARCH_RADIUS = 300 -- Max search radius + +local function IsPosEmpty(pos, filter) + local trace = util.TraceHull({ + start = pos + Vector(0,0,10), + endpos = pos + Vector(0,0,10), + mins = Vector(-16, -16, 0), + maxs = Vector(16, 16, 71), + filter = filter + }) + + if trace.Hit then return false end + + -- Also check for other players explicitly (TraceHull filter might skip them if they are within each other) + local entsNear = ents.FindInBox(pos + Vector(-16, -16, 0), pos + Vector(16, 16, 72)) + for _, v in pairs(entsNear) do + if v:IsPlayer() and v:Alive() and v ~= filter then + return false + end + end + + return true +end + +local function FindSafePos(startPos, ply) + if IsPosEmpty(startPos, ply) then return startPos end + + -- Spiral search + for r = 32, UNSTUCK_SEARCH_RADIUS, 32 do + local steps = math.floor(r / 4) + for i = 0, steps do + local ang = (i / steps) * 360 + local offset = Angle(0, ang, 0):Forward() * r + local tryPos = startPos + offset + + if IsPosEmpty(tryPos, ply) then + -- Ensure there is ground below + local groundTrace = util.TraceLine({ + start = tryPos + Vector(0,0,50), + endpos = tryPos - Vector(0,0,100), + filter = ply + }) + + if groundTrace.Hit then + return groundTrace.HitPos + end + end + end + end + + return startPos +end + +hook.Add("PlayerSpawn", "Helix_AntiStuckSpawn", function(ply) + -- Wait a frame for Helix to finish its set-spawn-point logic + timer.Simple(0, function() + if not IsValid(ply) or not ply:Alive() then return end + + local currentPos = ply:GetPos() + local safePos = FindSafePos(currentPos, ply) + + if safePos ~= currentPos then + ply:SetPos(safePos) + end + + -- Temporary NO COLLISION with other players for 3 seconds to avoid "jittery" physics if slightly overlapping + ply:SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER) + timer.Create("Unstuck_CD_" .. ply:SteamID64(), 3, 1, function() + if IsValid(ply) then + ply:SetCollisionGroup(COLLISION_GROUP_PLAYER) + end + end) + end) +end) + +print("[Anti-Stuck] Player spawn protection loaded") diff --git a/garrysmod/lua/autorun/server/sv_tacrp_att_fix.lua b/garrysmod/lua/autorun/server/sv_tacrp_att_fix.lua new file mode 100644 index 0000000..ee375f9 --- /dev/null +++ b/garrysmod/lua/autorun/server/sv_tacrp_att_fix.lua @@ -0,0 +1,78 @@ +-- Патч для tacrp_att: корректный подбор и возможность удаления суперадминами +-- Проблема: если free_atts = true, GiveAtt блокирует подбор с сообщением +-- "All attachments are free! This is not necessary!" и обвес не удаляется. + +if not SERVER then return end + +-- Таблица групп, имеющих админские права для поднятия сущностей +local AdminPrivs = { + ["superadmin"] = true, + ["curator"] = true, + ["admin"] = true, + ["owner"] = true, + ["projectteam"] = true, + ["teh.admin"] = true, +} + + +-- Ждём загрузки всех энтитей перед патчем +hook.Add("InitPostEntity", "MRP_PatchTacRPAtt", function() + -- Идём по всем зарегистрированным энтитям и патчим те, что начинаются на tacrp_att + for classname, meta in pairs(scripted_ents.GetList()) do + if string.StartWith(classname, "tacrp_att") and meta.t then + local ENT = meta.t + + ENT.GiveAtt = function(self, ply) + if not self.AttToGive then return end + + -- Проверка lock_atts: если включён и обвес уже есть — не выдаём + if TacRP and TacRP.ConVars and TacRP.ConVars["lock_atts"] and + TacRP.ConVars["lock_atts"]:GetBool() and + TacRP:PlayerGetAtts(ply, self.AttToGive) > 0 then + ply:PrintMessage(HUD_PRINTTALK, "У вас уже есть этот обвес!") + return + end + + -- Выдаём обвес + if TacRP then + local given = TacRP:PlayerGiveAtt(ply, self.AttToGive, 1) + if given then + TacRP:PlayerSendAttInv(ply) + self:EmitSound("TacRP/weapons/flashlight_on.wav") + self:Remove() + end + end + end + + -- Патчим Use чтобы не падал если TacRP не загружен + ENT.Use = function(self, ply) + if not ply:IsPlayer() then return end + self:GiveAtt(ply) + end + end + end + + print("[MRP] tacrp_att patched: pickup fix applied to all derived attachments") +end) + +-- Хук для удаления tacrp_att через physgun для суперадминов +hook.Add("PhysgunPickup", "MRP_AllowAttPickup", function(ply, ent) + if string.StartWith(ent:GetClass(), "tacrp_att") then + -- Разрешаем суперадминам поднимать и удалять обвесы + if ply:IsSuperAdmin() or AdminPrivs and AdminPrivs[ply:GetUserGroup()] then + return true + end + end +end) + +-- Хук для удаления через ToolGun (суперадмин right-click) +hook.Add("CanTool", "MRP_AllowAttRemove", function(ply, tr, tool) + if tool == "remover" then + local ent = tr.Entity + if IsValid(ent) and string.StartWith(ent:GetClass(), "tacrp_att") then + if ply:IsSuperAdmin() or AdminPrivs and AdminPrivs[ply:GetUserGroup()] then + return true + end + end + end +end) diff --git a/garrysmod/lua/bin/gmsv_chttp_linux64.dll b/garrysmod/lua/bin/gmsv_chttp_linux64.dll new file mode 100644 index 0000000..91adff2 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_chttp_linux64.dll differ diff --git a/garrysmod/lua/bin/gmsv_chttp_win64.dll b/garrysmod/lua/bin/gmsv_chttp_win64.dll new file mode 100644 index 0000000..5a8a839 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_chttp_win64.dll differ diff --git a/garrysmod/lua/bin/gmsv_dumptimers_fix_linux.dll b/garrysmod/lua/bin/gmsv_dumptimers_fix_linux.dll new file mode 100644 index 0000000..fe0fd6e Binary files /dev/null and b/garrysmod/lua/bin/gmsv_dumptimers_fix_linux.dll differ diff --git a/garrysmod/lua/bin/gmsv_enginespew_linux.dll b/garrysmod/lua/bin/gmsv_enginespew_linux.dll new file mode 100644 index 0000000..210ccb5 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_enginespew_linux.dll differ diff --git a/garrysmod/lua/bin/gmsv_enginespew_win32.dll b/garrysmod/lua/bin/gmsv_enginespew_win32.dll new file mode 100644 index 0000000..a0f3968 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_enginespew_win32.dll differ diff --git a/garrysmod/lua/bin/gmsv_fileio_linux.dll b/garrysmod/lua/bin/gmsv_fileio_linux.dll new file mode 100644 index 0000000..857f436 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_fileio_linux.dll differ diff --git a/garrysmod/lua/bin/gmsv_fileio_win32.dll b/garrysmod/lua/bin/gmsv_fileio_win32.dll new file mode 100644 index 0000000..0dd3157 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_fileio_win32.dll differ diff --git a/garrysmod/lua/bin/gmsv_mysqloo_linux.dll b/garrysmod/lua/bin/gmsv_mysqloo_linux.dll new file mode 100644 index 0000000..36db92a Binary files /dev/null and b/garrysmod/lua/bin/gmsv_mysqloo_linux.dll differ diff --git a/garrysmod/lua/bin/gmsv_mysqloo_linux64.dll b/garrysmod/lua/bin/gmsv_mysqloo_linux64.dll new file mode 100644 index 0000000..cfc6b7e Binary files /dev/null and b/garrysmod/lua/bin/gmsv_mysqloo_linux64.dll differ diff --git a/garrysmod/lua/bin/gmsv_mysqloo_win32.dll b/garrysmod/lua/bin/gmsv_mysqloo_win32.dll new file mode 100644 index 0000000..fe49ba7 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_mysqloo_win32.dll differ diff --git a/garrysmod/lua/bin/gmsv_mysqloo_win64.dll b/garrysmod/lua/bin/gmsv_mysqloo_win64.dll new file mode 100644 index 0000000..e39b775 Binary files /dev/null and b/garrysmod/lua/bin/gmsv_mysqloo_win64.dll differ diff --git a/garrysmod/lua/binder/cl_init.lua b/garrysmod/lua/binder/cl_init.lua new file mode 100644 index 0000000..3e7d015 --- /dev/null +++ b/garrysmod/lua/binder/cl_init.lua @@ -0,0 +1,107 @@ +-- Garry's Mod Binder - Main Client Logic +include("binder/sh_lang.lua") +include("binder/core/sh_config.lua") +include("binder/vgui/cl_frame.lua") +include("binder/vgui/cl_profiles_tab.lua") +include("binder/vgui/cl_keybind_tab.lua") +include("binder/vgui/cl_radial_tab.lua") +include("binder/vgui/cl_radial_hud.lua") +include("binder/vgui/cl_settings_tab.lua") + +Binder = Binder or {} +Binder.Profiles = Binder.Profiles or {} +Binder.F6Pressed = false + +surface.CreateFont("Binder_Key", { + font = "Roboto", + size = 12, + weight = 700, + extended = true, +}) + +CreateClientConVar("binder_show_feedback", "1", true, false) +CreateClientConVar("binder_dark_theme", "1", true, false) +CreateClientConVar("binder_radial_key", tostring(Binder.Config.DefaultRadialKey), true, false) + +-- Networking +net.Receive("Binder_SyncProfiles", function() + local length = net.ReadUInt(32) + local compressed = net.ReadData(length) + local data = util.Decompress(compressed) + + if data then + Binder.Profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {}) + if IsValid(Binder.Frame) then + Binder.Frame:RefreshActiveTab() + end + end +end) + +function Binder.SaveToServer() + local data = util.TableToJSON(Binder.Profiles) + local compressed = util.Compress(data) + + net.Start("Binder_UpdateProfile") + net.WriteUInt(#compressed, 32) + net.WriteData(compressed, #compressed) + net.SendToServer() +end + +-- Input Hook for Binds (Client-side Think for Singleplayer support) +local PrevBindState = {} + +hook.Add("Think", "Binder_CheckBindsThink", function() + local activeProfile + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then activeProfile = p break end + end + + if not activeProfile or not activeProfile.Binds then return end + + -- Don't trigger new presses if in console, ESC menu, typing in chat, or focused on a VGUI text entry + local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or LocalPlayer():IsTyping() + + for key, commandStr in pairs(activeProfile.Binds) do + local isDown = input.IsButtonDown(key) + local wasDown = PrevBindState[key] or false + + if isDown and not wasDown then + if not blockNewInput then + PrevBindState[key] = true + if string.sub(commandStr, 1, 1) == "/" then + LocalPlayer():ConCommand("say " .. commandStr) + else + LocalPlayer():ConCommand(commandStr) + end + end + elseif not isDown and wasDown then + PrevBindState[key] = false + + -- Automatically release + commands + local cmd = string.Explode(" ", commandStr)[1] + if cmd and string.sub(cmd, 1, 1) == "+" then + local minusCmd = "-" .. string.sub(cmd, 2) + LocalPlayer():ConCommand(minusCmd) + end + end + end +end) + + +concommand.Add("binder_menu", function() + if Binder.Frame and IsValid(Binder.Frame) then + Binder.Frame:Remove() + end + + Binder.Frame = vgui.Create("Binder_Frame") +end) + +-- Open menu on F6 +hook.Add("Think", "Binder_F6Check", function() + if input.IsKeyDown(KEY_F6) and not Binder.F6Pressed then + Binder.F6Pressed = true + RunConsoleCommand("binder_menu") + elseif not input.IsKeyDown(KEY_F6) then + Binder.F6Pressed = false + end +end) diff --git a/garrysmod/lua/binder/core/sh_config.lua b/garrysmod/lua/binder/core/sh_config.lua new file mode 100644 index 0000000..de66708 --- /dev/null +++ b/garrysmod/lua/binder/core/sh_config.lua @@ -0,0 +1,65 @@ +-- Garry's Mod Binder - Shared Configuration +Binder = Binder or {} +Binder.Config = { + AccentColor = Color(0, 67, 28), -- Dark green (#00431c) + BgColor = Color(30, 30, 30, 240), + HeaderColor = Color(0, 67, 28), + TabActiveColor = Color(255, 255, 255, 20), + TabHoverColor = Color(255, 255, 255, 10), + Font = "Inter", -- Assuming Inter is available or we'll define it + DefaultRadialKey = KEY_N, +} + +function Binder.SanitizeProfiles(profiles) + for _, p in ipairs(profiles or {}) do + if p.Binds then + local newBinds = {} + for k, v in pairs(p.Binds) do + newBinds[tonumber(k) or k] = v + end + p.Binds = newBinds + end + if p.Radial then + local newRadial = {} + for k, v in pairs(p.Radial) do + newRadial[tonumber(k) or k] = v + end + p.Radial = newRadial + end + end + return profiles +end + +if CLIENT then + surface.CreateFont("Binder_Title", { + font = "Roboto", + size = 24, + weight = 800, + extended = true, + antialias = true, + }) + + surface.CreateFont("Binder_Tab", { + font = "Roboto", + size = 18, + weight = 600, + extended = true, + antialias = true, + }) + + surface.CreateFont("Binder_Main", { + font = "Roboto", + size = 18, + weight = 500, + extended = true, + antialias = true, + }) + + surface.CreateFont("Binder_Small", { + font = "Roboto", + size = 14, + weight = 500, + extended = true, + antialias = true, + }) +end diff --git a/garrysmod/lua/binder/sh_lang.lua b/garrysmod/lua/binder/sh_lang.lua new file mode 100644 index 0000000..19f380b --- /dev/null +++ b/garrysmod/lua/binder/sh_lang.lua @@ -0,0 +1,37 @@ +-- Garry's Mod Binder - Shared Localization (Russian) +Binder = Binder or {} +Binder.Lang = { + title = "[BindMenu]", + profiles = "Профили", + keybinds = "Клавиши", + radial = "Радиальное меню", + settings = "Настройки", + profile_management = "Управление профилями", + profile_desc = "Выберите профиль для редактирования или создайте новый. Ранги Premium и Loyalty открывают дополнительные слоты.", + create_profile = "Создать профиль", + raiding_profile = "Рейд Профиль", + banker_profile = "Банкир Профиль", + binds_count = "%s биндов", + general_settings = "Общие настройки", + show_feedback = "Показывать сообщения обратной связи", + auto_scale = "Автоматическое масштабирование", + dark_theme = "Темная тема", + import_defaults = "Импорт стандартных биндов", + import_btn = "Импортировать бинды", + appearance_settings = "Настройки внешнего вида", + accent_color = "Акцентный цвет:", + change_color = "Изменить цвет", + preset_colors = "Предустановленные цвета:", + radial_config = "Настройка радиального меню", + radial_desc = "Нажмите на сегмент ниже для настройки бинда", + radial_center = "Радиальное", + slot = "Слот %s", + save = "Сохранить", + delete = "Удалить", + cancel = "Отмена", +} + +function Binder.GetPhrase(key, ...) + local phrase = Binder.Lang[key] or key + return string.format(phrase, ...) +end diff --git a/garrysmod/lua/binder/sv_init.lua b/garrysmod/lua/binder/sv_init.lua new file mode 100644 index 0000000..0b7ade4 --- /dev/null +++ b/garrysmod/lua/binder/sv_init.lua @@ -0,0 +1,71 @@ +-- Garry's Mod Binder - Main Server Logic +include("binder/sh_lang.lua") +include("binder/core/sh_config.lua") + +util.AddNetworkString("Binder_SyncProfiles") +util.AddNetworkString("Binder_UpdateProfile") +util.AddNetworkString("Binder_DeleteProfile") +util.AddNetworkString("Binder_SetActiveProfile") + +Binder = Binder or {} +Binder.Profiles = Binder.Profiles or {} + +-- Default structure +-- Profile = { Name = "Profile1", Binds = { [KEY_X] = "cmd" }, Radial = { [1] = { Label = "L", Cmd = "cmd" } }, Active = true } + +function Binder.SaveProfiles(ply) + local data = util.TableToJSON(Binder.Profiles[ply:SteamID64()] or {}) + ply:SetPData("Binder_Profiles", data) +end + +function Binder.LoadProfiles(ply) + local data = ply:GetPData("Binder_Profiles", "[]") + local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {}) + + -- Ensure at least one default profile if empty + if table.Count(profiles) == 0 then + table.insert(profiles, { + Name = Binder.GetPhrase("raiding_profile"), + Binds = {}, + Radial = {}, + Active = true + }) + end + + Binder.Profiles[ply:SteamID64()] = profiles + + -- Sync to client + net.Start("Binder_SyncProfiles") + local compressed = util.Compress(util.TableToJSON(profiles)) + net.WriteUInt(#compressed, 32) + net.WriteData(compressed, #compressed) + net.Send(ply) +end + +hook.Add("PlayerInitialSpawn", "Binder_LoadOnSpawn", function(ply) + Binder.LoadProfiles(ply) +end) + +-- Receive entire profile updates from client (e.g., binds changed) +net.Receive("Binder_UpdateProfile", function(len, ply) + local length = net.ReadUInt(32) + local compressed = net.ReadData(length) + local data = util.Decompress(compressed) + + if data then + local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {}) + if profiles then + Binder.Profiles[ply:SteamID64()] = profiles + Binder.SaveProfiles(ply) + end + end +end) + +-- Chat Command to open the Binder +hook.Add("PlayerSay", "Binder_ChatCommand", function(ply, text, teamTalk) + local lowerText = string.lower(text) + if string.sub(lowerText, 1, 5) == "/bind" or string.sub(lowerText, 1, 5) == "!bind" then + ply:ConCommand("binder_menu") + return "" -- Hide the command from chat + end +end) \ No newline at end of file diff --git a/garrysmod/lua/binder/vgui/cl_frame.lua b/garrysmod/lua/binder/vgui/cl_frame.lua new file mode 100644 index 0000000..178e595 --- /dev/null +++ b/garrysmod/lua/binder/vgui/cl_frame.lua @@ -0,0 +1,138 @@ +-- Garry's Mod Binder - Custom Frame +local PANEL = {} + +function PANEL:Init() + self:SetSize(ScrW() * 0.8, ScrH() * 0.8) + self:Center() + self:MakePopup() + + self.AccentColor = Binder.Config.AccentColor + + self.CurrentTab = "Profiles" + + self.HeaderHeight = 60 + self.TabHeight = 40 + + -- Tabs definitions + self.Tabs = { + {id = "Profiles", name = Binder.GetPhrase("profiles")}, + {id = "Keybinds", name = Binder.GetPhrase("keybinds")}, + {id = "Radial", name = Binder.GetPhrase("radial")}, + {id = "Settings", name = Binder.GetPhrase("settings")}, + } + + -- Close Button + self.CloseBtn = vgui.Create("DButton", self) + self.CloseBtn:SetText("✕") + self.CloseBtn:SetFont("Binder_Main") + self.CloseBtn:SetTextColor(Color(255, 255, 255, 150)) + self.CloseBtn.DoClick = function() self:Remove() end + self.CloseBtn.Paint = function(s, w, h) + if s:IsHovered() then + s:SetTextColor(Color(255, 255, 255, 255)) + else + s:SetTextColor(Color(255, 255, 255, 150)) + end + end + + -- Tab Content Container + self.Content = vgui.Create("DPanel", self) + self.Content:Dock(FILL) + self.Content:DockMargin(0, self.HeaderHeight + self.TabHeight, 0, 0) + self.Content.Paint = function(s, w, h) end + + self:SwitchTab("Profiles") +end + +include("binder/vgui/cl_profiles_tab.lua") +include("binder/vgui/cl_keybind_tab.lua") +include("binder/vgui/cl_radial_tab.lua") +include("binder/vgui/cl_settings_tab.lua") + +function PANEL:SwitchTab(id) + self.CurrentTab = id + self.Content:Clear() + + if id == "Profiles" then + self.ActiveTab = vgui.Create("Binder_ProfilesTab", self.Content) + elseif id == "Keybinds" then + self.ActiveTab = vgui.Create("Binder_KeybindsTab", self.Content) + elseif id == "Radial" then + self.ActiveTab = vgui.Create("Binder_RadialTab", self.Content) + elseif id == "Settings" then + self.ActiveTab = vgui.Create("Binder_SettingsTab", self.Content) + else + local label = vgui.Create("DLabel", self.Content) + + + label:SetText("Content for " .. id) + label:SetFont("Binder_Title") + label:SizeToContents() + label:Center() + end +end + +function PANEL:RefreshActiveTab() + if IsValid(self.ActiveTab) and self.ActiveTab.Refresh then + self.ActiveTab:Refresh() + elseif self.CurrentTab == "Profiles" and IsValid(self.ActiveTab) then + -- Fallback if the tab logic doesn't have a specific refresh method, + -- though we should add :Refresh to all of them. + self:SwitchTab(self.CurrentTab) + end +end + +function PANEL:PerformLayout(w, h) + if IsValid(self.CloseBtn) then + self.CloseBtn:SetSize(30, 30) + self.CloseBtn:SetPos(w - 40, 15) + end +end + +function PANEL:Paint(w, h) + -- Background with blur + draw.RoundedBox(8, 0, 0, w, h, Color(30, 30, 30, 200)) + + -- Header + draw.RoundedBoxEx(8, 0, 0, w, self.HeaderHeight, self.AccentColor, true, true, false, false) + draw.SimpleText(Binder.GetPhrase("title"), "Binder_Title", 20, self.HeaderHeight / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Tabs Bar + draw.RoundedBox(0, 0, self.HeaderHeight, w, self.TabHeight, Color(45, 45, 45)) + + local tabWidth = w / #self.Tabs + for i, tab in ipairs(self.Tabs) do + local x = (i - 1) * tabWidth + local isActive = self.CurrentTab == tab.id + + if isActive then + draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 10)) + draw.RoundedBox(0, x + 20, self.HeaderHeight + self.TabHeight - 3, tabWidth - 40, 3, Color(255, 255, 255)) + end + + local isHovered = gui.MouseX() >= self:GetPos() + x and gui.MouseX() < self:GetPos() + x + tabWidth and + gui.MouseY() >= self:GetY() + self.HeaderHeight and gui.MouseY() < self:GetY() + self.HeaderHeight + self.TabHeight + + if isHovered and not isActive then + draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 5)) + end + + draw.SimpleText(tab.name, "Binder_Tab", x + tabWidth / 2, self.HeaderHeight + self.TabHeight / 2, + isActive and Color(255, 255, 255) or Color(150, 150, 150), + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + -- Hidden button for tab click + if not self.TabButtons then self.TabButtons = {} end + if not self.TabButtons[i] then + local btn = vgui.Create("DButton", self) + btn:SetText("") + btn.Paint = function() end + btn.DoClick = function() self:SwitchTab(tab.id) end + self.TabButtons[i] = btn + end + self.TabButtons[i]:SetSize(tabWidth, self.TabHeight) + self.TabButtons[i]:SetPos(x, self.HeaderHeight) + end +end + +vgui.Register("Binder_Frame", PANEL, "EditablePanel") diff --git a/garrysmod/lua/binder/vgui/cl_keybind_tab.lua b/garrysmod/lua/binder/vgui/cl_keybind_tab.lua new file mode 100644 index 0000000..a011741 --- /dev/null +++ b/garrysmod/lua/binder/vgui/cl_keybind_tab.lua @@ -0,0 +1,204 @@ +-- Garry's Mod Binder - Keybinds Tab (Virtual Keyboard) +local PANEL = {} + +local KEY_LAYOUT = { + {"ESC", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"}, + {"~", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "BKSP"}, + {"TAB", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\"}, + {"CAPS", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "ENTER"}, + {"SHIFT", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "SHIFT"}, + {"CTRL", "WIN", "ALT", "SPACE", "ALT", "WIN", "MENU"} +} + +local KEY_SPECIAL = { + {"PRSC", "SCRLK", "PAUSE"}, + {"INS", "HOME", "PGUP"}, + {"DEL", "END", "PGDN"}, + {"↑"}, + {"←", "↓", "→"} +} + +local NUM_PAD = { + {"NUM", "/", "*", "-"}, + {"7", "8", "9", "+"}, + {"4", "5", "6"}, + {"1", "2", "3", "ENT"}, + {"0", "."} +} + +local STRING_TO_KEY = { + ["ESC"] = KEY_ESCAPE, ["~"] = KEY_TILDE, ["-"] = KEY_MINUS, ["="] = KEY_EQUAL, ["BKSP"] = KEY_BACKSPACE, ["TAB"] = KEY_TAB, + ["["] = KEY_LBRACKET, ["]"] = KEY_RBRACKET, ["\\"] = KEY_BACKSLASH, ["CAPS"] = KEY_CAPSLOCK, [";"] = KEY_SEMICOLON, + ["'"] = KEY_APOSTROPHE, ["ENTER"] = KEY_ENTER, ["SHIFT"] = KEY_LSHIFT, [","] = KEY_COMMA, ["."] = KEY_PERIOD, + ["/"] = KEY_SLASH, ["CTRL"] = KEY_LCONTROL, ["WIN"] = KEY_LWIN, ["ALT"] = KEY_LALT, ["SPACE"] = KEY_SPACE, + ["MENU"] = KEY_APP, + ["PRSC"] = KEY_SYSRQ, ["SCRLK"] = KEY_SCROLLLOCK, ["PAUSE"] = KEY_BREAK, ["INS"] = KEY_INSERT, ["HOME"] = KEY_HOME, + ["PGUP"] = KEY_PAGEUP, ["DEL"] = KEY_DELETE, ["END"] = KEY_END, ["PGDN"] = KEY_PAGEDOWN, + ["↑"] = KEY_UP, ["←"] = KEY_LEFT, ["↓"] = KEY_DOWN, ["→"] = KEY_RIGHT, + ["NUM"] = KEY_NUMLOCK, ["/"] = KEY_PAD_DIVIDE, ["*"] = KEY_PAD_MULTIPLY, ["-"] = KEY_PAD_MINUS, ["+"] = KEY_PAD_PLUS, + ["ENT"] = KEY_PAD_ENTER, ["."] = KEY_PAD_DECIMAL, + ["1"] = KEY_1, ["2"] = KEY_2, ["3"] = KEY_3, ["4"] = KEY_4, ["5"] = KEY_5, ["6"] = KEY_6, ["7"] = KEY_7, ["8"] = KEY_8, ["9"] = KEY_9, ["0"] = KEY_0, +} + +-- Add letters and F-keys +for i = 1, 26 do STRING_TO_KEY[string.char(64 + i)] = _G["KEY_" .. string.char(64 + i)] end +for i = 1, 12 do STRING_TO_KEY["F" .. i] = _G["KEY_F" .. i] end + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(20, 20, 20, 20) + self.Paint = function() end + + self.InfoBar = vgui.Create("DPanel", self) + self.InfoBar:Dock(TOP) + self.InfoBar:SetHeight(40) + self.InfoBar.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(255, 255, 255, 5)) + + local activeProf = self:GetActiveProfile() + local name = activeProf and activeProf.Name or "No Active Profile" + local bindCount = activeProf and table.Count(activeProf.Binds or {}) or 0 + + draw.SimpleText(name, "Binder_Main", 50, h / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w - 20, h / 2, Color(255, 255, 255, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + self.KeyboardContainer = vgui.Create("DPanel", self) + self.KeyboardContainer:Dock(FILL) + self.KeyboardContainer:DockMargin(0, 40, 0, 0) + self.KeyboardContainer.Paint = function() end + + self.KeyButtons = {} + self:BuildKeyboard() + self:Refresh() +end + +function PANEL:GetActiveProfile() + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then return p end + end + return nil +end + +function PANEL:Refresh() + local profile = self:GetActiveProfile() + local binds = profile and profile.Binds or {} + + for _, btnData in ipairs(self.KeyButtons) do + local isBound = binds[btnData.enum] != nil + btnData.btn.IsActiveNode = isBound + end +end + +function PANEL:PromptBind(keyStr, keyEnum) + local profile = self:GetActiveProfile() + if not profile then return end + + local currentCmd = profile.Binds[keyEnum] or "" + + Derma_StringRequest( + "Bind " .. keyStr, + "Enter console command to execute on press (e.g., 'say /raid'):\nLeave blank to unbind.", + currentCmd, + function(text) + if text == "" then + profile.Binds[keyEnum] = nil + else + profile.Binds[keyEnum] = text + end + Binder.SaveToServer() + self:Refresh() + end + ) +end + +function PANEL:CreateKey(parent, text, x, y, w, h) + local btn = vgui.Create("DButton", parent) + btn:SetSize(w or 35, h or 35) + btn:SetPos(x, y) + btn:SetText("") + btn.IsActiveNode = false + + local keyEnum = STRING_TO_KEY[text] + + btn.Paint = function(s, bw, bh) + local bgColor = s.IsActiveNode and Binder.Config.AccentColor or Color(40, 40, 40) + if s:IsHovered() then + bgColor = Color(bgColor.r + 20, bgColor.g + 20, bgColor.b + 20) + end + draw.RoundedBox(4, 0, 0, bw, bh, bgColor) + draw.SimpleText(text, "Binder_Key", bw / 2, bh / 2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + btn.DoClick = function() + if keyEnum then + self:PromptBind(text, keyEnum) + else + print("Binder: Unmapped key - " .. text) + end + end + + table.insert(self.KeyButtons, {btn = btn, sysName = text, enum = keyEnum}) +end + +function PANEL:BuildKeyboard() + local startX, startY = 10, 10 + local keySize = 38 + local spacing = 4 + + -- Main Block + for r, row in ipairs(KEY_LAYOUT) do + local rx = startX + local ry = startY + (r - 1) * (keySize + spacing) + + -- Adjustments + if r == 1 then ry = ry - 5 end + + for k, key in ipairs(row) do + local kw = keySize + if key == "BKSP" then kw = keySize * 2 + spacing + elseif key == "TAB" then kw = keySize * 1.5 + elseif key == "CAPS" then kw = keySize * 1.8 + elseif key == "ENTER" then kw = keySize * 1.8 + elseif key == "SHIFT" then kw = keySize * 2.3 + elseif key == "SPACE" then kw = keySize * 5 + spacing * 4 + elseif key == "CTRL" or key == "ALT" or key == "WIN" then kw = keySize * 1.2 + end + + self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, keySize) + rx = rx + kw + spacing + end + end + + -- Special Keys Block + local specX = startX + 15 * (keySize + spacing) + for r, row in ipairs(KEY_SPECIAL) do + local rx = specX + local ry = startY + (r - 1) * (keySize + spacing) + if r == 4 then rx = rx + keySize + spacing end + + for k, key in ipairs(row) do + self:CreateKey(self.KeyboardContainer, key, rx, ry, keySize, keySize) + rx = rx + keySize + spacing + end + end + + -- Num Pad + local numX = specX + 4 * (keySize + spacing) + for r, row in ipairs(NUM_PAD) do + local rx = numX + local ry = startY + (r - 1) * (keySize + spacing) + for k, key in ipairs(row) do + local kh = keySize + if key == "+" or key == "ENT" then kh = keySize * 2 + spacing end + + local kw = keySize + if key == "0" then kw = keySize * 2 + spacing end + + self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, kh) + rx = rx + kw + spacing + end + end +end + +vgui.Register("Binder_KeybindsTab", PANEL, "EditablePanel") diff --git a/garrysmod/lua/binder/vgui/cl_profiles_tab.lua b/garrysmod/lua/binder/vgui/cl_profiles_tab.lua new file mode 100644 index 0000000..50681e2 --- /dev/null +++ b/garrysmod/lua/binder/vgui/cl_profiles_tab.lua @@ -0,0 +1,104 @@ +-- Garry's Mod Binder - Profiles Tab +local PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(40, 20, 40, 20) + self.Paint = function() end + + -- Title and Description + self.Header = vgui.Create("DPanel", self) + self.Header:Dock(TOP) + self.Header:SetHeight(50) + self.Header.Paint = function(s, w, h) + draw.SimpleText(Binder.GetPhrase("profile_management"), "Binder_Title", 0, 0, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + -- Profile Grid + self.Grid = vgui.Create("DIconLayout", self) + self.Grid:Dock(FILL) + self.Grid:SetSpaceX(20) + self.Grid:SetSpaceY(20) + + self:Refresh() +end + +function PANEL:Refresh() + self.Grid:Clear() + + local profiles = Binder.Profiles or {} + + for i, profile in ipairs(profiles) do + local card = self.Grid:Add("DButton") + card:SetSize(200, 150) + card:SetText("") + card.Paint = function(s, w, h) + local bgColor = profile.Active and Binder.Config.AccentColor or Color(40, 40, 40) + draw.RoundedBox(8, 0, 0, w, h, bgColor) + + draw.SimpleText(profile.Name, "Binder_Main", w / 2, h / 2 - 10, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + local bindCount = table.Count(profile.Binds or {}) + draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w / 2, h / 2 + 15, Color(255, 255, 255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if profile.Active then + draw.RoundedBox(4, w - 15, 10, 8, 8, Color(0, 255, 0)) -- Active dot + end + end + card.DoClick = function() + -- Set active + for _, p in ipairs(Binder.Profiles) do p.Active = false end + profile.Active = true + Binder.SaveToServer() + self:Refresh() + end + card.DoRightClick = function() + -- Delete profile + if #Binder.Profiles > 1 then + local menu = DermaMenu() + menu:AddOption(Binder.GetPhrase("delete"), function() + table.remove(Binder.Profiles, i) + -- ensure one is active + local hasActive = false + for _, p in ipairs(Binder.Profiles) do if p.Active then hasActive = true break end end + if not hasActive and #Binder.Profiles > 0 then Binder.Profiles[1].Active = true end + + Binder.SaveToServer() + self:Refresh() + end):SetIcon("icon16/delete.png") + menu:Open() + end + end + end + + -- Create New Profile Buttons (to fill up to 6 as in screenshot) + if #profiles < 6 then + local card = self.Grid:Add("DButton") + card:SetSize(200, 150) + card:SetText("") + card.Paint = function(s, w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(40, 40, 40)) + draw.SimpleText("+", "Binder_Title", w / 2, h / 2 - 15, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(Binder.GetPhrase("create_profile"), "Binder_Main", w / 2, h / 2 + 20, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + card.DoClick = function() + Derma_StringRequest( + Binder.GetPhrase("create_profile"), + "Enter profile name:", + "New Profile", + function(text) + table.insert(Binder.Profiles, { + Name = text, + Binds = {}, + Radial = {}, + Active = false + }) + Binder.SaveToServer() + self:Refresh() + end + ) + end + end +end + +vgui.Register("Binder_ProfilesTab", PANEL, "EditablePanel") diff --git a/garrysmod/lua/binder/vgui/cl_radial_hud.lua b/garrysmod/lua/binder/vgui/cl_radial_hud.lua new file mode 100644 index 0000000..c795e33 --- /dev/null +++ b/garrysmod/lua/binder/vgui/cl_radial_hud.lua @@ -0,0 +1,94 @@ +-- Garry's Mod Binder - Radial HUD +local IsRadialOpen = false +local SelectedSlot = 0 + +local function GetActiveProfile() + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then return p end + end + return nil +end + +hook.Add("HUDPaint", "Binder_DrawRadial", function() + if not IsRadialOpen then return end + + local profile = GetActiveProfile() + if not profile or not profile.Radial then return end + + local cx, cy = ScrW() / 2, ScrH() / 2 + local radius = 150 + + -- Draw Center + draw.NoTexture() + surface.SetDrawColor(Color(30, 30, 30, 240)) + surface.DrawCircle(cx, cy, 50, 255, 255, 255, 255) + draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Main", cx, cy, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + local mouseX, mouseY = gui.MousePos() + if mouseX == 0 and mouseY == 0 then + mouseX, mouseY = cx, cy -- fallback if not capturing properly + end + + local dist = math.Dist(cx, cy, mouseX, mouseY) + local angle = math.deg(math.atan2(mouseY - cy, mouseX - cx)) + 90 + if angle < 0 then angle = angle + 360 end + + -- Determine selected segment based on angle (8 segments, 45 deg each) + if dist > 50 then + SelectedSlot = math.floor((angle + 22.5) / 45) + 1 + if SelectedSlot > 8 then SelectedSlot = 1 end + else + SelectedSlot = 0 + end + + -- Draw segments + for i = 1, 8 do + local slotData = profile.Radial[i] + if slotData then + local segAngle = (i - 1) * 45 - 90 + local rad = math.rad(segAngle) + local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius + + local boxColor = (SelectedSlot == i) and Binder.Config.AccentColor or Color(40, 40, 45, 240) + + draw.RoundedBox(8, lx - 40, ly - 20, 80, 40, boxColor) + draw.SimpleText(slotData.Label, "Binder_Main", lx, ly, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + -- Draw cursor line + surface.SetDrawColor(255, 255, 255, 50) + surface.DrawLine(cx, cy, mouseX, mouseY) +end) + +local PrevRadialState = false + +hook.Add("Think", "Binder_RadialThink", function() + local cvar = GetConVar("binder_radial_key") + local radialKey = cvar and cvar:GetInt() or Binder.Config.DefaultRadialKey + + local isDown = input.IsButtonDown(radialKey) + local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or (LocalPlayer() and LocalPlayer():IsValid() and LocalPlayer():IsTyping()) + + if isDown and not PrevRadialState then + if not blockNewInput then + PrevRadialState = true + IsRadialOpen = true + gui.EnableScreenClicker(true) + SelectedSlot = 0 + end + elseif not isDown and PrevRadialState then + PrevRadialState = false + if IsRadialOpen then + IsRadialOpen = false + gui.EnableScreenClicker(false) + + if SelectedSlot > 0 then + local profile = GetActiveProfile() + if profile and profile.Radial and profile.Radial[SelectedSlot] then + LocalPlayer():ConCommand(profile.Radial[SelectedSlot].Command) + end + end + end + end +end) diff --git a/garrysmod/lua/binder/vgui/cl_radial_tab.lua b/garrysmod/lua/binder/vgui/cl_radial_tab.lua new file mode 100644 index 0000000..3fe6950 --- /dev/null +++ b/garrysmod/lua/binder/vgui/cl_radial_tab.lua @@ -0,0 +1,150 @@ +-- Garry's Mod Binder - Radial Menu Tab +local PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(40, 20, 40, 20) + self.Paint = function() end + + self.Header = vgui.Create("DPanel", self) + self.Header:Dock(TOP) + self.Header:SetHeight(80) + self.Header.Paint = function(s, w, h) + draw.SimpleText(Binder.GetPhrase("radial_config"), "Binder_Title", 0, 10, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(Binder.GetPhrase("radial_desc"), "Binder_Small", 0, 40, Color(255, 255, 255, 100), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + self.RadialContainer = vgui.Create("DPanel", self) + self.RadialContainer:Dock(FILL) + self.RadialContainer.Paint = function(s, w, h) + local cx, cy = w / 2, h / 2 + + draw.NoTexture() + surface.SetDrawColor(Color(255, 255, 255, 10)) + surface.DrawCircle(cx, cy, 40, 255, 255, 255, 255) + draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Small", cx, cy, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + self.Segments = {} + self:BuildSegments() + self:Refresh() +end + +function PANEL:GetActiveProfile() + for _, p in ipairs(Binder.Profiles or {}) do + if p.Active then return p end + end + return nil +end + +function PANEL:BuildSegments() + local cx, cy = 0, 0 -- Will be updated in PerformLayout + local radius = 120 + + for i = 1, 8 do + local btn = vgui.Create("DButton", self.RadialContainer) + btn:SetSize(80, 80) + btn:SetText("") + btn.Slot = i + btn.Paint = function(s, w, h) + local bgColor = s:IsHovered() and Binder.Config.AccentColor or Color(40, 40, 45) + draw.RoundedBox(20, 0, 0, w, h, bgColor) + + local activeProf = self:GetActiveProfile() + local text = Binder.GetPhrase("slot", i) + if activeProf and activeProf.Radial and activeProf.Radial[i] then + text = activeProf.Radial[i].Label + end + + draw.SimpleText(text, "Binder_Small", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + btn.DoClick = function(s) + self:PromptSegment(s.Slot) + end + + table.insert(self.Segments, btn) + end +end + +function PANEL:Refresh() + -- Nothing inherently needed here unless we drastically change profile +end + +function PANEL:PerformLayout(w, h) + local cx, cy = w / 2, (h - 80) / 2 + local radius = 120 + + for i, btn in ipairs(self.Segments) do + local angle = (i - 1) * (360 / 8) - 90 -- Start at top + local rad = math.rad(angle) + local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius + + btn:SetPos(lx - 40, ly - 40) + end +end + +function PANEL:PromptSegment(slot) + local profile = self:GetActiveProfile() + if not profile then return end + + profile.Radial = profile.Radial or {} + local currentLabel = profile.Radial[slot] and profile.Radial[slot].Label or "" + local currentCmd = profile.Radial[slot] and profile.Radial[slot].Command or "" + + local frame = vgui.Create("DFrame") + frame:SetTitle("Configure Radial Slot " .. slot) + frame:SetSize(300, 150) + frame:Center() + frame:MakePopup() + + local lbl1 = vgui.Create("DLabel", frame) + lbl1:SetPos(10, 30) + lbl1:SetText("Label (Text):") + lbl1:SizeToContents() + + local txtLabel = vgui.Create("DTextEntry", frame) + txtLabel:SetPos(10, 50) + txtLabel:SetSize(280, 20) + txtLabel:SetText(currentLabel) + + local lbl2 = vgui.Create("DLabel", frame) + lbl2:SetPos(10, 80) + lbl2:SetText("Console Command:") + lbl2:SizeToContents() + + local txtCmd = vgui.Create("DTextEntry", frame) + txtCmd:SetPos(10, 100) + txtCmd:SetSize(280, 20) + txtCmd:SetText(currentCmd) + + local saveBtn = vgui.Create("DButton", frame) + saveBtn:SetPos(10, 125) + saveBtn:SetSize(135, 20) + saveBtn:SetText("Save") + saveBtn.DoClick = function() + local l = txtLabel:GetValue() + local c = txtCmd:GetValue() + if l == "" or c == "" then + profile.Radial[slot] = nil + else + profile.Radial[slot] = { Label = l, Command = c } + end + Binder.SaveToServer() + self:Refresh() + frame:Remove() + end + + local clearBtn = vgui.Create("DButton", frame) + clearBtn:SetPos(155, 125) + clearBtn:SetSize(135, 20) + clearBtn:SetText("Clear Slot") + clearBtn.DoClick = function() + profile.Radial[slot] = nil + Binder.SaveToServer() + self:Refresh() + frame:Remove() + end +end + +vgui.Register("Binder_RadialTab", PANEL, "EditablePanel") diff --git a/garrysmod/lua/binder/vgui/cl_settings_tab.lua b/garrysmod/lua/binder/vgui/cl_settings_tab.lua new file mode 100644 index 0000000..6565642 --- /dev/null +++ b/garrysmod/lua/binder/vgui/cl_settings_tab.lua @@ -0,0 +1,182 @@ +-- Garry's Mod Binder - Settings Tab +local PANEL = {} + +function PANEL:Init() + self:Dock(FILL) + self:DockMargin(40, 20, 40, 20) + self.Paint = function() end + + self.Scroll = vgui.Create("DScrollPanel", self) + self.Scroll:Dock(FILL) + + local sbar = self.Scroll:GetVBar() + sbar:SetWide(6) + sbar:SetHideButtons(true) + sbar.Paint = function(s, w, h) end + sbar.btnGrip.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30)) + end + + self:AddCategory(Binder.GetPhrase("general_settings"), { + {type = "check", label = Binder.GetPhrase("show_feedback"), convar = "binder_show_feedback"}, + {type = "check", label = Binder.GetPhrase("dark_theme"), convar = "binder_dark_theme"}, + }) + + self:AddCategory("Radial Menu Key", { + {type = "keybind", label = "Activation Key", convar = "binder_radial_key"}, + }) + + self:AddCategory(Binder.GetPhrase("import_defaults"), { + {type = "button", label = Binder.GetPhrase("import_btn"), btnText = Binder.GetPhrase("import_btn"), + onClick = function() + Binder.Profiles = {} + table.insert(Binder.Profiles, {Name = Binder.GetPhrase("raiding_profile"), Binds = {}, Radial = {}, Active = true}) + Binder.SaveToServer() + if Binder.Frame and IsValid(Binder.Frame) then Binder.Frame:RefreshActiveTab() end + end}, + }) + + self:AddCategory(Binder.GetPhrase("appearance_settings"), { + {type = "color", label = Binder.GetPhrase("accent_color")}, + {type = "presets", label = Binder.GetPhrase("preset_colors")}, + }) +end + +function PANEL:AddCategory(title, items) + local cat = self.Scroll:Add("DPanel") + cat:Dock(TOP) + cat:DockMargin(0, 0, 0, 20) + cat:SetHeight(40 + #items * 40) + cat.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, 30, Binder.Config.AccentColor) + draw.SimpleText(title, "Binder_Main", 10, 15, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.RoundedBoxEx(4, 0, 30, w, h - 30, Color(40, 40, 40), false, false, true, true) + end + + local itemContainer = vgui.Create("DPanel", cat) + itemContainer:Dock(FILL) + itemContainer:DockMargin(10, 35, 10, 5) + itemContainer.Paint = function() end + + for _, item in ipairs(items) do + local row = vgui.Create("DPanel", itemContainer) + row:Dock(TOP) + row:SetHeight(35) + row.Paint = function() end + + if item.type == "check" and item.convar then + local chk = vgui.Create("DCheckBox", row) + chk:SetPos(0, 10) + chk:SetConVar(item.convar) + + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(25, 8) + lbl:SizeToContents() + + elseif item.type == "keybind" and item.convar then + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(0, 8) + lbl:SizeToContents() + + local binderBtn = vgui.Create("DBinder", row) + binderBtn:SetSize(120, 20) + binderBtn:SetPos(150, 8) + binderBtn:SetConVar(item.convar) + binderBtn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60)) + draw.SimpleText(input.GetKeyName(s:GetValue()) or "NONE", "Binder_Small", w/2, h/2, Color(255,255,255), 1, 1) + end + + elseif item.type == "button" then + local btn = vgui.Create("DButton", row) + btn:SetText(item.btnText) + btn:SetFont("Binder_Small") + btn:SetSize(150, 25) + btn:SetPos(0, 5) + btn:SetTextColor(Color(255, 255, 255)) + btn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor) + end + if item.onClick then btn.DoClick = item.onClick end + + elseif item.type == "color" then + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(0, 8) + lbl:SizeToContents() + + local colPreview = vgui.Create("DPanel", row) + colPreview:SetSize(30, 20) + colPreview:SetPos(150, 8) + colPreview.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor) + end + + local btn = vgui.Create("DButton", row) + btn:SetText(Binder.GetPhrase("change_color")) + btn:SetSize(100, 20) + btn:SetPos(190, 8) + btn:SetFont("Binder_Small") + btn:SetTextColor(Color(255, 255, 255)) + btn.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60)) + end + btn.DoClick = function() + local frame = vgui.Create("DFrame") + frame:SetTitle("Select Theme Color") + frame:SetSize(300, 250) + frame:Center() + frame:MakePopup() + + local mixer = vgui.Create("DColorMixer", frame) + mixer:Dock(FILL) + mixer:SetPalette(true) + mixer:SetAlphaBar(false) + mixer:SetWangs(true) + mixer:SetColor(Binder.Config.AccentColor) + + local apply = vgui.Create("DButton", frame) + apply:Dock(BOTTOM) + apply:SetText("Apply") + apply.DoClick = function() + Binder.Config.AccentColor = mixer:GetColor() + if IsValid(Binder.Frame) then Binder.Frame.AccentColor = Binder.Config.AccentColor end + frame:Remove() + end + end + + elseif item.type == "presets" then + local lbl = vgui.Create("DLabel", row) + lbl:SetText(item.label) + lbl:SetFont("Binder_Small") + lbl:SetPos(0, 8) + lbl:SizeToContents() + + local presets = {Color(0, 67, 28), Color(52, 152, 219), Color(46, 204, 113), Color(231, 76, 60), Color(155, 89, 182), Color(230, 126, 34)} + for i, col in ipairs(presets) do + local p = vgui.Create("DButton", row) + p:SetSize(20, 20) + p:SetPos(150 + (i-1)*25, 8) + p:SetText("") + p.Paint = function(s, w, h) + draw.RoundedBox(4, 0, 0, w, h, col) + if Binder.Config.AccentColor == col then + surface.SetDrawColor(255, 255, 255) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + end + p.DoClick = function() + Binder.Config.AccentColor = col + if IsValid(Binder.Frame) then Binder.Frame.AccentColor = col end + end + end + end + end +end + +vgui.Register("Binder_SettingsTab", PANEL, "EditablePanel")